* [PATCH v2 0/6] hw/ppc: SPI model
@ 2024-04-09 17:56 Chalapathi V
2024-04-09 17:56 ` [PATCH v2 1/6] hw/ppc: remove SPI responder model Chalapathi V
` (5 more replies)
0 siblings, 6 replies; 16+ messages in thread
From: Chalapathi V @ 2024-04-09 17:56 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-ppc, fbarrat, npiggin, clg, calebs, chalapathi.v,
chalapathi.v, saif.abrar, dantan
Hello,
Thank You so much for reviewing patchset V1.
In PATCHSET V2, removed the PNV_SPI_RESPONDER model and an existing
QEMU SSI framework is used to model SPI BUS and SEEPROM model and
also most of Steve's comments have been addressed.
Also added the pnv-spi-seeprom qtest is added to test to check SPI
traffic between SPI controller and it's attached SEEPROM device.
The new qom-tree looks like below.
(qemu) info qom-tree
/machine (powernv10-machine)
/chip[0] (power10_v2.0-pnv-chip)
/pib_spic[0] (pnv-spi-controller)
/bus (pnv-spi-bus)
/pnv-spi-bus.0 (SSI)
/xscom-spi-controller-regs[0] (memory-region)
/pib_spic[1] (pnv-spi-controller)
/bus (pnv-spi-bus)
/pnv-spi-bus.1 (SSI)
/xscom-spi-controller-regs[0] (memory-region)
/pib_spic[2] (pnv-spi-controller)
/bus (pnv-spi-bus)
/pnv-spi-bus.2 (SSI)
/xscom-spi-controller-regs[0] (memory-region)
/pib_spic[3] (pnv-spi-controller)
/bus (pnv-spi-bus)
/pnv-spi-bus.3 (SSI)
/xscom-spi-controller-regs[0] (memory-region)
/pib_spic[4] (pnv-spi-controller)
/bus (pnv-spi-bus)
/pnv-spi-bus.4 (SSI)
/xscom-spi-controller-regs[0] (memory-region)
/pib_spic[5] (pnv-spi-controller)
/bus (pnv-spi-bus)
/pnv-spi-bus.5 (SSI)
/xscom-spi-controller-regs[0] (memory-region)
/unattached (container)
/device[7] (seeprom-25csm04)
/ssi-gpio-cs[0] (irq)
Patches overview in V2.
PATCH1: Remove SPI responder model and used SSI framework instead.
PATCH2: Create a SPI controller model and implement configuration unit
to model SCOM registers. Move register constants to a seperate
header file pnv_spi_controller_regs.h
PATCH3: SPI controller model: implement sequencer FSM and shift engine.
PATCH4: Create a Microchip's SEEPROM 25csm04 model using SSI framework and
move it hw/misc as it is not a power specific device.
PATCH5: Connect SPI controllers to p10 chip and create keystore seeprom
device of type "seeprom-25csm04" and connect cs line PIB_SPIC[2].
PATCH6: Write a qtest pnv-spi-seeprom-test to check the SPI transactions
between spi controller and seeprom device.
Test covered:
Ran make check.
Thank You,
Chalapathi
Chalapathi V (6):
hw/ppc: remove SPI responder model
hw/ppc: SPI controller model - registers implementation
hw/ppc: SPI controller model - sequencer and shifter
hw/misc: Microchip's 25CSM04 SEEPROM model
hw/ppc: SPI controller wiring to P10 chip and create seeprom device
tests/qtest: Add pnv-spi-seeprom qtest
include/hw/misc/seeprom_25csm04.h | 48 +
include/hw/ppc/pnv_chip.h | 3 +
include/hw/ppc/pnv_spi_controller.h | 127 ++
include/hw/ppc/pnv_spi_controller_regs.h | 114 ++
include/hw/ppc/pnv_xscom.h | 3 +
hw/misc/seeprom_25csm04.c | 780 +++++++++++
hw/ppc/pnv.c | 36 +-
hw/ppc/pnv_spi_controller.c | 1587 ++++++++++++++++++++++
tests/qtest/pnv-spi-seeprom-test.c | 126 ++
hw/misc/Kconfig | 3 +
hw/misc/meson.build | 1 +
hw/ppc/Kconfig | 2 +
hw/ppc/meson.build | 1 +
tests/qtest/meson.build | 1 +
14 files changed, 2831 insertions(+), 1 deletion(-)
create mode 100644 include/hw/misc/seeprom_25csm04.h
create mode 100644 include/hw/ppc/pnv_spi_controller.h
create mode 100644 include/hw/ppc/pnv_spi_controller_regs.h
create mode 100644 hw/misc/seeprom_25csm04.c
create mode 100644 hw/ppc/pnv_spi_controller.c
create mode 100644 tests/qtest/pnv-spi-seeprom-test.c
--
2.39.3
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v2 1/6] hw/ppc: remove SPI responder model
2024-04-09 17:56 [PATCH v2 0/6] hw/ppc: SPI model Chalapathi V
@ 2024-04-09 17:56 ` Chalapathi V
2024-04-15 14:46 ` Cédric Le Goater
2024-04-09 17:56 ` [PATCH v2 2/6] hw/ppc: SPI controller model - registers implementation Chalapathi V
` (4 subsequent siblings)
5 siblings, 1 reply; 16+ messages in thread
From: Chalapathi V @ 2024-04-09 17:56 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-ppc, fbarrat, npiggin, clg, calebs, chalapathi.v,
chalapathi.v, saif.abrar, dantan
-- Empty commit to align the patch numbers between PATCH v1 and PATCH v2.
SPI responder model is removed as pnv spi controller and seeprom is
implemented using QEMU SSI framework.
Signed-off-by: Chalapathi V <chalapathi.v@linux.ibm.com>
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v2 2/6] hw/ppc: SPI controller model - registers implementation
2024-04-09 17:56 [PATCH v2 0/6] hw/ppc: SPI model Chalapathi V
2024-04-09 17:56 ` [PATCH v2 1/6] hw/ppc: remove SPI responder model Chalapathi V
@ 2024-04-09 17:56 ` Chalapathi V
2024-04-15 15:14 ` Cédric Le Goater
2024-04-09 17:56 ` [PATCH v2 3/6] hw/ppc: SPI controller model - sequencer and shifter Chalapathi V
` (3 subsequent siblings)
5 siblings, 1 reply; 16+ messages in thread
From: Chalapathi V @ 2024-04-09 17:56 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-ppc, fbarrat, npiggin, clg, calebs, chalapathi.v,
chalapathi.v, saif.abrar, dantan
SPI controller device model supports a connection to a single SPI responder.
This provide access to SPI seeproms, TPM, flash device and an ADC controller.
All SPI function control is mapped into the SPI register space to enable full
control by firmware. In this commit SPI configuration component is modelled
which contains all SPI configuration and status registers as well as the hold
registers for data to be sent or having been received.
An existing QEMU SSI framework is used and SSI_BUS is created.
Signed-off-by: Chalapathi V <chalapathi.v@linux.ibm.com>
---
include/hw/ppc/pnv_spi_controller.h | 55 +++++
include/hw/ppc/pnv_spi_controller_regs.h | 114 ++++++++++
include/hw/ppc/pnv_xscom.h | 3 +
hw/ppc/pnv_spi_controller.c | 278 +++++++++++++++++++++++
hw/ppc/Kconfig | 1 +
hw/ppc/meson.build | 1 +
6 files changed, 452 insertions(+)
create mode 100644 include/hw/ppc/pnv_spi_controller.h
create mode 100644 include/hw/ppc/pnv_spi_controller_regs.h
create mode 100644 hw/ppc/pnv_spi_controller.c
diff --git a/include/hw/ppc/pnv_spi_controller.h b/include/hw/ppc/pnv_spi_controller.h
new file mode 100644
index 0000000000..5ec50fb14c
--- /dev/null
+++ b/include/hw/ppc/pnv_spi_controller.h
@@ -0,0 +1,55 @@
+/*
+ * QEMU PowerPC SPI Controller model
+ *
+ * Copyright (c) 2024, IBM Corporation.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This model Supports a connection to a single SPI responder.
+ * Introduced for P10 to provide access to SPI seeproms, TPM, flash device
+ * and an ADC controller.
+ */
+#include "hw/ssi/ssi.h"
+
+#ifndef PPC_PNV_SPI_CONTROLLER_H
+#define PPC_PNV_SPI_CONTROLLER_H
+
+#define TYPE_PNV_SPI_CONTROLLER "pnv-spi-controller"
+#define PNV_SPICONTROLLER(obj) \
+ OBJECT_CHECK(PnvSpiController, (obj), TYPE_PNV_SPI_CONTROLLER)
+
+#define SPI_CONTROLLER_REG_SIZE 8
+
+typedef struct SSIBus SSIBus;
+
+#define TYPE_PNV_SPI_BUS "pnv-spi-bus"
+OBJECT_DECLARE_SIMPLE_TYPE(PnvSPIBus, PNV_SPI_BUS)
+
+typedef struct PnvSPIBus {
+ SysBusDevice parent_obj;
+
+ SSIBus *ssi_bus;
+ qemu_irq *cs_line;
+ uint32_t id;
+} PnvSPIBus;
+
+typedef struct PnvSpiController {
+ DeviceState parent;
+
+ PnvSPIBus bus;
+ MemoryRegion xscom_spic_regs;
+ /* SPI controller object number */
+ uint32_t spic_num;
+
+ /* SPI Controller registers */
+ uint64_t error_reg;
+ uint64_t counter_config_reg;
+ uint64_t config_reg1;
+ uint64_t clock_config_reset_control;
+ uint64_t memory_mapping_reg;
+ uint64_t transmit_data_reg;
+ uint64_t receive_data_reg;
+ uint8_t sequencer_operation_reg[SPI_CONTROLLER_REG_SIZE];
+ uint64_t status_reg;
+} PnvSpiController;
+#endif /* PPC_PNV_SPI_CONTROLLER_H */
diff --git a/include/hw/ppc/pnv_spi_controller_regs.h b/include/hw/ppc/pnv_spi_controller_regs.h
new file mode 100644
index 0000000000..6f613aca5e
--- /dev/null
+++ b/include/hw/ppc/pnv_spi_controller_regs.h
@@ -0,0 +1,114 @@
+/*
+ * QEMU PowerPC SPI Controller model
+ *
+ * Copyright (c) 2023, IBM Corporation.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef SPI_CONTROLLER_REGS_H
+#define SPI_CONTROLLER_REGS_H
+
+/* Error Register */
+#define ERROR_REG 0x00
+
+/* counter_config_reg */
+#define COUNTER_CONFIG_REG 0x01
+#define COUNTER_CONFIG_REG_SHIFT_COUNT_N1 PPC_BITMASK(0, 7)
+#define COUNTER_CONFIG_REG_SHIFT_COUNT_N2 PPC_BITMASK(8, 15)
+#define COUNTER_CONFIG_REG_COUNT_COMPARE1 PPC_BITMASK(24, 31)
+#define COUNTER_CONFIG_REG_COUNT_COMPARE2 PPC_BITMASK(32, 39)
+#define COUNTER_CONFIG_REG_N1_COUNT_CONTROL PPC_BITMASK(48, 51)
+#define COUNTER_CONFIG_REG_N2_COUNT_CONTROL PPC_BITMASK(52, 55)
+
+/* config_reg */
+#define CONFIG_REG1 0x02
+
+/* clock_config_reset_control_ecc_enable_reg */
+#define CLOCK_CONFIG_REG 0x03
+#define CLOCK_CONFIG_RESET_CONTROL_HARD_RESET 0x0084000000000000;
+#define CLOCK_CONFIG_REG_RESET_CONTROL PPC_BITMASK(24, 27)
+#define CLOCK_CONFIG_REG_ECC_CONTROL PPC_BITMASK(28, 30)
+
+/* memory_mapping_reg */
+#define MEMORY_MAPPING_REG 0x04
+#define MEMORY_MAPPING_REG_MMSPISM_BASE_ADDR PPC_BITMASK(0, 15)
+#define MEMORY_MAPPING_REG_MMSPISM_ADDR_MASK PPC_BITMASK(16, 31)
+#define MEMORY_MAPPING_REG_RDR_MATCH_VAL PPC_BITMASK(32, 47)
+#define MEMORY_MAPPING_REG_RDR_MATCH_MASK PPC_BITMASK(48, 63)
+
+/* transmit_data_reg */
+#define TRANSMIT_DATA_REG 0x05
+
+/* receive_data_reg */
+#define RECEIVE_DATA_REG 0x06
+
+/* sequencer_operation_reg */
+#define SEQUENCER_OPERATION_REG 0x07
+
+/* status_reg */
+#define STATUS_REG 0x08
+#define STATUS_REG_RDR_FULL PPC_BIT(0)
+#define STATUS_REG_RDR_OVERRUN PPC_BIT(1)
+#define STATUS_REG_RDR_UNDERRUN PPC_BIT(2)
+#define STATUS_REG_TDR_FULL PPC_BIT(4)
+#define STATUS_REG_TDR_OVERRUN PPC_BIT(5)
+#define STATUS_REG_TDR_UNDERRUN PPC_BIT(6)
+#define STATUS_REG_SEQUENCER_FSM PPC_BITMASK(8, 15)
+#define STATUS_REG_SHIFTER_FSM PPC_BITMASK(16, 27)
+#define STATUS_REG_SEQUENCER_INDEX PPC_BITMASK(28, 31)
+#define STATUS_REG_GENERAL_SPI_STATUS PPC_BITMASK(32, 63)
+#define STATUS_REG_RDR PPC_BITMASK(1, 3)
+#define STATUS_REG_TDR PPC_BITMASK(5, 7)
+
+/*
+ * Shifter states
+ *
+ * These are the same values defined for the Shifter FSM field of the
+ * status register. It's a 12 bit field so we will represent it as three
+ * nibbles in the constants.
+ *
+ * These are shifter_fsm values
+ *
+ * Status reg bits 16-27 -> field bits 0-11
+ * bits 0,1,2,5 unused/reserved
+ * bit 4 crc shift in (unused)
+ * bit 8 crc shift out (unused)
+ */
+
+#define FSM_DONE 0x100 /* bit 3 */
+#define FSM_SHIFT_N2 0x020 /* bit 6 */
+#define FSM_WAIT 0x010 /* bit 7 */
+#define FSM_SHIFT_N1 0x004 /* bit 9 */
+#define FSM_START 0x002 /* bit 10 */
+#define FSM_IDLE 0x001 /* bit 11 */
+
+/*
+ * Sequencer states
+ *
+ * These are sequencer_fsm values
+ *
+ * Status reg bits 8-15 -> field bits 0-7
+ * bits 0-3 unused/reserved
+ *
+ */
+#define SEQ_STATE_INDEX_INCREMENT 0x08 /* bit 4 */
+#define SEQ_STATE_EXECUTE 0x04 /* bit 5 */
+#define SEQ_STATE_DECODE 0x02 /* bit 6 */
+#define SEQ_STATE_IDLE 0x01 /* bit 7 */
+
+/*
+ * These are the supported sequencer operations.
+ * Only the upper nibble is significant because for many operations
+ * the lower nibble is a variable specific to the operation.
+ */
+#define SEQ_OP_STOP 0x00
+#define SEQ_OP_SELECT_SLAVE 0x10
+#define SEQ_OP_SHIFT_N1 0x30
+#define SEQ_OP_SHIFT_N2 0x40
+#define SEQ_OP_BRANCH_IFNEQ_RDR 0x60
+#define SEQ_OP_TRANSFER_TDR 0xC0
+#define SEQ_OP_BRANCH_IFNEQ_INC_1 0xE0
+#define SEQ_OP_BRANCH_IFNEQ_INC_2 0xF0
+
+#endif
diff --git a/include/hw/ppc/pnv_xscom.h b/include/hw/ppc/pnv_xscom.h
index 6209e18492..a77b97f9b1 100644
--- a/include/hw/ppc/pnv_xscom.h
+++ b/include/hw/ppc/pnv_xscom.h
@@ -194,6 +194,9 @@ struct PnvXScomInterfaceClass {
#define PNV10_XSCOM_PEC_PCI_BASE 0x8010800 /* index goes upwards ... */
#define PNV10_XSCOM_PEC_PCI_SIZE 0x200
+#define PNV10_XSCOM_PIB_SPIC_BASE 0xc0000
+#define PNV10_XSCOM_PIB_SPIC_SIZE 0x20
+
void pnv_xscom_init(PnvChip *chip, uint64_t size, hwaddr addr);
int pnv_dt_xscom(PnvChip *chip, void *fdt, int root_offset,
uint64_t xscom_base, uint64_t xscom_size,
diff --git a/hw/ppc/pnv_spi_controller.c b/hw/ppc/pnv_spi_controller.c
new file mode 100644
index 0000000000..e2478a47f2
--- /dev/null
+++ b/hw/ppc/pnv_spi_controller.c
@@ -0,0 +1,278 @@
+/*
+ * QEMU PowerPC SPI Controller model
+ *
+ * Copyright (c) 2024, IBM Corporation.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "hw/qdev-properties.h"
+#include "hw/ppc/pnv_xscom.h"
+#include "hw/ppc/pnv_spi_controller.h"
+#include "hw/ppc/pnv_spi_controller_regs.h"
+#include "hw/ssi/ssi.h"
+#include "hw/ppc/fdt.h"
+#include <libfdt.h>
+#include <math.h>
+#include "hw/irq.h"
+
+#define SPI_DEBUG(x)
+
+static uint64_t pnv_spi_controller_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ PnvSpiController *sc = PNV_SPICONTROLLER(opaque);
+ uint32_t reg = addr >> 3;
+ uint64_t val = ~0ull;
+
+ switch (reg) {
+ case ERROR_REG:
+ val = sc->error_reg;
+ break;
+ case COUNTER_CONFIG_REG:
+ val = sc->counter_config_reg;
+ break;
+ case CONFIG_REG1:
+ val = sc->config_reg1;
+ break;
+ case CLOCK_CONFIG_REG:
+ val = sc->clock_config_reset_control;
+ break;
+ case MEMORY_MAPPING_REG:
+ val = sc->memory_mapping_reg;
+ break;
+ case TRANSMIT_DATA_REG:
+ val = sc->transmit_data_reg;
+ break;
+ case RECEIVE_DATA_REG:
+ val = sc->receive_data_reg;
+ SPI_DEBUG(qemu_log("RDR being read, data extracted = 0x%16.16lx\n",
+ val));
+ sc->status_reg = SETFIELD(STATUS_REG_RDR_FULL, sc->status_reg, 0);
+ SPI_DEBUG(qemu_log("RDR being read, RDR_full set to 0\n"));
+ break;
+ case SEQUENCER_OPERATION_REG:
+ val = 0;
+ for (int i = 0; i < SPI_CONTROLLER_REG_SIZE; i++) {
+ val = (val << 8) | sc->sequencer_operation_reg[i];
+ }
+ break;
+ case STATUS_REG:
+ val = sc->status_reg;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "spi_controller_regs: Invalid xscom "
+ "read at 0x%08x\n", reg);
+ }
+ return val;
+}
+
+static void pnv_spi_controller_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ PnvSpiController *sc = PNV_SPICONTROLLER(opaque);
+ uint32_t reg = addr >> 3;
+
+ switch (reg) {
+ case ERROR_REG:
+ sc->error_reg = val;
+ break;
+ case COUNTER_CONFIG_REG:
+ sc->counter_config_reg = val;
+ break;
+ case CONFIG_REG1:
+ sc->config_reg1 = val;
+ break;
+ case CLOCK_CONFIG_REG:
+ /*
+ * To reset the SPI controller write the sequence 0x5 0xA to
+ * reset_control field
+ */
+ if (GETFIELD(CLOCK_CONFIG_REG_RESET_CONTROL,
+ sc->clock_config_reset_control) == 0x5) {
+ if (GETFIELD(CLOCK_CONFIG_REG_RESET_CONTROL, val) == 0xA) {
+ SPI_DEBUG(qemu_log("SPI controller reset sequence completed, "
+ "resetting..."));
+ sc->clock_config_reset_control =
+ CLOCK_CONFIG_RESET_CONTROL_HARD_RESET;
+ } else {
+ sc->clock_config_reset_control = val;
+ }
+ } else {
+ sc->clock_config_reset_control = val;
+ }
+ break;
+ case MEMORY_MAPPING_REG:
+ sc->memory_mapping_reg = val;
+ break;
+ case TRANSMIT_DATA_REG:
+ /*
+ * Writing to the transmit data register causes the transmit data
+ * register full status bit in the status register to be set. Writing
+ * when the transmit data register full status bit is already set
+ * causes a "Resource Not Available" condition. This is not possible
+ * in the model since writes to this register are not asynchronous to
+ * the operation sequence like it would be in hardware.
+ */
+ sc->transmit_data_reg = val;
+ SPI_DEBUG(qemu_log("TDR being written, data written = 0x%16.16lx\n",
+ val));
+ sc->status_reg = SETFIELD(STATUS_REG_TDR_FULL, sc->status_reg, 1);
+ SPI_DEBUG(qemu_log("TDR being written, TDR_full set to 1\n"));
+ sc->status_reg = SETFIELD(STATUS_REG_TDR_UNDERRUN, sc->status_reg, 0);
+ SPI_DEBUG(qemu_log("TDR being written, TDR_underrun set to 0\n"));
+ SPI_DEBUG(qemu_log("TDR being written, starting sequencer\n"));
+ break;
+ case RECEIVE_DATA_REG:
+ sc->receive_data_reg = val;
+ break;
+ case SEQUENCER_OPERATION_REG:
+ for (int i = 0; i < SPI_CONTROLLER_REG_SIZE; i++) {
+ sc->sequencer_operation_reg[i] = (val >> (56 - i * 8)) & 0xFF;
+ }
+ break;
+ case STATUS_REG:
+ /* other fields are ignore_write */
+ sc->status_reg = SETFIELD(STATUS_REG_RDR_OVERRUN, sc->status_reg,
+ GETFIELD(STATUS_REG_RDR, val));
+ sc->status_reg = SETFIELD(STATUS_REG_TDR_OVERRUN, sc->status_reg,
+ GETFIELD(STATUS_REG_TDR, val));
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "spi_controller_regs: Invalid xscom "
+ "write at 0x%08x\n", reg);
+ }
+ return;
+}
+
+static const MemoryRegionOps pnv_spi_controller_xscom_ops = {
+ .read = pnv_spi_controller_read,
+ .write = pnv_spi_controller_write,
+ .valid.min_access_size = 8,
+ .valid.max_access_size = 8,
+ .impl.min_access_size = 8,
+ .impl.max_access_size = 8,
+ .endianness = DEVICE_BIG_ENDIAN,
+};
+
+static void pnv_spi_bus_realize(DeviceState *dev, Error **errp)
+{
+ PnvSPIBus *s = PNV_SPI_BUS(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ g_autofree char *name = g_strdup_printf(TYPE_PNV_SPI_BUS ".%d", s->id);
+
+ s->ssi_bus = ssi_create_bus(dev, name);
+ s->cs_line = g_new0(qemu_irq, 1);
+ sysbus_init_irq(sbd, &s->cs_line[0]);
+}
+
+static Property pnv_spi_bus_properties[] = {
+ DEFINE_PROP_UINT32("bus-id", PnvSPIBus, id, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pnv_spi_bus_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->desc = "Pnv SPI Bus";
+ dc->realize = pnv_spi_bus_realize;
+ device_class_set_props(dc, pnv_spi_bus_properties);
+}
+
+static const TypeInfo pnv_spi_bus_info = {
+ .name = TYPE_PNV_SPI_BUS,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PnvSPIBus),
+ .class_init = pnv_spi_bus_class_init,
+};
+
+static Property pnv_spi_controller_properties[] = {
+ DEFINE_PROP_UINT32("spic_num", PnvSpiController, spic_num, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pnv_spi_controller_realize(DeviceState *dev, Error **errp)
+{
+ PnvSpiController *sc = PNV_SPICONTROLLER(dev);
+
+ Object *bus = OBJECT(&sc->bus);
+ if (!object_property_set_int(bus, "bus-id", sc->spic_num, errp)) {
+ return;
+ }
+
+ if (!sysbus_realize(SYS_BUS_DEVICE(bus), errp)) {
+ return;
+ }
+
+ /* spi controller scoms */
+ pnv_xscom_region_init(&sc->xscom_spic_regs, OBJECT(sc),
+ &pnv_spi_controller_xscom_ops, sc,
+ "xscom-spi-controller-regs",
+ PNV10_XSCOM_PIB_SPIC_SIZE);
+}
+
+static int pnv_spi_controller_dt_xscom(PnvXScomInterface *dev, void *fdt,
+ int offset)
+{
+ PnvSpiController *sc = PNV_SPICONTROLLER(dev);
+ g_autofree char *name;
+ int sc_offset;
+ const char compat[] = "ibm,power10-spi_controller";
+ uint32_t spic_pcba = PNV10_XSCOM_PIB_SPIC_BASE +
+ sc->spic_num * PNV10_XSCOM_PIB_SPIC_SIZE;
+ uint32_t reg[] = {
+ cpu_to_be32(spic_pcba),
+ cpu_to_be32(PNV10_XSCOM_PIB_SPIC_SIZE)
+ };
+ name = g_strdup_printf("spi_controller@%x", spic_pcba);
+ sc_offset = fdt_add_subnode(fdt, offset, name);
+ _FDT(sc_offset);
+
+ _FDT(fdt_setprop(fdt, sc_offset, "reg", reg, sizeof(reg)));
+ _FDT(fdt_setprop(fdt, sc_offset, "compatible", compat, sizeof(compat)));
+ _FDT((fdt_setprop_cell(fdt, sc_offset, "spic_num#", sc->spic_num)));
+ return 0;
+}
+
+static void pnv_spi_instance_init(Object *obj)
+{
+ PnvSpiController *sc = PNV_SPICONTROLLER(obj);
+
+ /* Initialise the bus object */
+ object_initialize_child(obj, "bus", &sc->bus, TYPE_PNV_SPI_BUS);
+}
+
+static void pnv_spi_controller_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PnvXScomInterfaceClass *xscomc = PNV_XSCOM_INTERFACE_CLASS(klass);
+
+ xscomc->dt_xscom = pnv_spi_controller_dt_xscom;
+
+ dc->desc = "PowerNV SPI Controller";
+ dc->realize = pnv_spi_controller_realize;
+ device_class_set_props(dc, pnv_spi_controller_properties);
+}
+
+static const TypeInfo pnv_spi_controller_info = {
+ .name = TYPE_PNV_SPI_CONTROLLER,
+ .parent = TYPE_DEVICE,
+ .instance_init = pnv_spi_instance_init,
+ .instance_size = sizeof(PnvSpiController),
+ .class_init = pnv_spi_controller_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_PNV_XSCOM_INTERFACE },
+ { }
+ }
+};
+
+static void pnv_spi_controller_register_types(void)
+{
+ type_register_static(&pnv_spi_bus_info);
+ type_register_static(&pnv_spi_controller_info);
+}
+
+type_init(pnv_spi_controller_register_types);
diff --git a/hw/ppc/Kconfig b/hw/ppc/Kconfig
index 37ccf9cdca..ea1178bd73 100644
--- a/hw/ppc/Kconfig
+++ b/hw/ppc/Kconfig
@@ -35,6 +35,7 @@ config POWERNV
select PCI_POWERNV
select PCA9552
select PCA9554
+ select SSI
config PPC405
bool
diff --git a/hw/ppc/meson.build b/hw/ppc/meson.build
index d096636ee7..68fadbae7b 100644
--- a/hw/ppc/meson.build
+++ b/hw/ppc/meson.build
@@ -56,6 +56,7 @@ ppc_ss.add(when: 'CONFIG_POWERNV', if_true: files(
'pnv_pnor.c',
'pnv_nest_pervasive.c',
'pnv_n1_chiplet.c',
+ 'pnv_spi_controller.c',
))
# PowerPC 4xx boards
ppc_ss.add(when: 'CONFIG_PPC405', if_true: files(
--
2.39.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 3/6] hw/ppc: SPI controller model - sequencer and shifter
2024-04-09 17:56 [PATCH v2 0/6] hw/ppc: SPI model Chalapathi V
2024-04-09 17:56 ` [PATCH v2 1/6] hw/ppc: remove SPI responder model Chalapathi V
2024-04-09 17:56 ` [PATCH v2 2/6] hw/ppc: SPI controller model - registers implementation Chalapathi V
@ 2024-04-09 17:56 ` Chalapathi V
2024-04-16 9:39 ` Cédric Le Goater
2024-04-09 17:56 ` [PATCH v2 4/6] hw/misc: Microchip's 25CSM04 SEEPROM model Chalapathi V
` (2 subsequent siblings)
5 siblings, 1 reply; 16+ messages in thread
From: Chalapathi V @ 2024-04-09 17:56 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-ppc, fbarrat, npiggin, clg, calebs, chalapathi.v,
chalapathi.v, saif.abrar, dantan
In this commit SPI shift engine and sequencer logic is implemented.
Shift engine performs serialization and de-serialization according to the
control by the sequencer and according to the setup defined in the
configuration registers. Sequencer implements the main control logic and
FSM to handle data transmit and data receive control of the shift engine.
Signed-off-by: Chalapathi V <chalapathi.v@linux.ibm.com>
---
include/hw/ppc/pnv_spi_controller.h | 72 ++
hw/ppc/pnv_spi_controller.c | 1311 ++++++++++++++++++++++++++-
2 files changed, 1382 insertions(+), 1 deletion(-)
diff --git a/include/hw/ppc/pnv_spi_controller.h b/include/hw/ppc/pnv_spi_controller.h
index 5ec50fb14c..ee8e7a17da 100644
--- a/include/hw/ppc/pnv_spi_controller.h
+++ b/include/hw/ppc/pnv_spi_controller.h
@@ -8,6 +8,14 @@
* This model Supports a connection to a single SPI responder.
* Introduced for P10 to provide access to SPI seeproms, TPM, flash device
* and an ADC controller.
+ *
+ * All SPI function control is mapped into the SPI register space to enable
+ * full control by firmware.
+ *
+ * SPI Controller has sequencer and shift engine. The SPI shift engine
+ * performs serialization and de-serialization according to the control by
+ * the sequencer and according to the setup defined in the configuration
+ * registers and the SPI sequencer implements the main control logic.
*/
#include "hw/ssi/ssi.h"
@@ -21,6 +29,7 @@
#define SPI_CONTROLLER_REG_SIZE 8
typedef struct SSIBus SSIBus;
+typedef struct xfer_buffer xfer_buffer;
#define TYPE_PNV_SPI_BUS "pnv-spi-bus"
OBJECT_DECLARE_SIMPLE_TYPE(PnvSPIBus, PNV_SPI_BUS)
@@ -33,6 +42,21 @@ typedef struct PnvSPIBus {
uint32_t id;
} PnvSPIBus;
+/* xfer_buffer */
+typedef struct xfer_buffer {
+
+ uint32_t len;
+ uint8_t *data;
+
+} xfer_buffer;
+
+uint8_t *xfer_buffer_write_ptr(xfer_buffer *payload, uint32_t offset,
+ uint32_t length);
+void xfer_buffer_read_ptr(xfer_buffer *payload, uint8_t **read_buf,
+ uint32_t offset, uint32_t length);
+xfer_buffer *xfer_buffer_new(void);
+void xfer_buffer_free(xfer_buffer *payload);
+
typedef struct PnvSpiController {
DeviceState parent;
@@ -40,6 +64,39 @@ typedef struct PnvSpiController {
MemoryRegion xscom_spic_regs;
/* SPI controller object number */
uint32_t spic_num;
+ uint8_t responder_select;
+ /* To verify if shift_n1 happens prior to shift_n2 */
+ bool shift_n1_done;
+ /*
+ * Internal flags for the first and last indicators for the SPI
+ * interface methods
+ */
+ uint8_t first;
+ uint8_t last;
+ /* Loop counter for branch operation opcode Ex/Fx */
+ uint8_t loop_counter_1;
+ uint8_t loop_counter_2;
+ /* N1/N2_bits specifies the size of the N1/N2 segment of a frame in bits.*/
+ uint8_t N1_bits;
+ uint8_t N2_bits;
+ /* Number of bytes in a payload for the N1/N2 frame segment.*/
+ uint8_t N1_bytes;
+ uint8_t N2_bytes;
+ /* Number of N1/N2 bytes marked for transmit */
+ uint8_t N1_tx;
+ uint8_t N2_tx;
+ /* Number of N1/N2 bytes marked for receive */
+ uint8_t N1_rx;
+ uint8_t N2_rx;
+ /*
+ * Setting this attribute to true will cause the engine to reverse the
+ * bit order of each byte it appends to a payload before sending the
+ * payload to a device. There may be cases where an end device expects
+ * a reversed order, like in the case of the Nuvoton TPM device. The
+ * order of bytes in the payload is not reversed, only the order of the
+ * 8 bits in each payload byte.
+ */
+ bool reverse_bits;
/* SPI Controller registers */
uint64_t error_reg;
@@ -52,4 +109,19 @@ typedef struct PnvSpiController {
uint8_t sequencer_operation_reg[SPI_CONTROLLER_REG_SIZE];
uint64_t status_reg;
} PnvSpiController;
+
+void log_all_N_counts(PnvSpiController *spi_controller);
+void spi_response(PnvSpiController *spi_controller, int bits,
+ xfer_buffer *rsp_payload);
+void operation_sequencer(PnvSpiController *spi_controller);
+bool operation_shiftn1(PnvSpiController *spi_controller, uint8_t opcode,
+ xfer_buffer **payload, bool send_n1_alone);
+bool operation_shiftn2(PnvSpiController *spi_controller, uint8_t opcode,
+ xfer_buffer **payload);
+bool does_rdr_match(PnvSpiController *spi_controller);
+uint8_t get_from_offset(PnvSpiController *spi_controller, uint8_t offset);
+void shift_byte_in(PnvSpiController *spi_controller, uint8_t byte);
+void calculate_N1(PnvSpiController *spi_controller, uint8_t opcode);
+void calculate_N2(PnvSpiController *spi_controller, uint8_t opcode);
+uint8_t reverse_bits8(uint8_t x);
#endif /* PPC_PNV_SPI_CONTROLLER_H */
diff --git a/hw/ppc/pnv_spi_controller.c b/hw/ppc/pnv_spi_controller.c
index e2478a47f2..afe7f17565 100644
--- a/hw/ppc/pnv_spi_controller.c
+++ b/hw/ppc/pnv_spi_controller.c
@@ -52,6 +52,11 @@ static uint64_t pnv_spi_controller_read(void *opaque, hwaddr addr,
val));
sc->status_reg = SETFIELD(STATUS_REG_RDR_FULL, sc->status_reg, 0);
SPI_DEBUG(qemu_log("RDR being read, RDR_full set to 0\n"));
+ if (GETFIELD(STATUS_REG_SHIFTER_FSM, sc->status_reg) == FSM_WAIT) {
+ SPI_DEBUG(qemu_log("RDR being read while shifter is waiting, "
+ "starting sequencer\n"));
+ operation_sequencer(sc);
+ }
break;
case SEQUENCER_OPERATION_REG:
val = 0;
@@ -124,13 +129,15 @@ static void pnv_spi_controller_write(void *opaque, hwaddr addr,
sc->status_reg = SETFIELD(STATUS_REG_TDR_UNDERRUN, sc->status_reg, 0);
SPI_DEBUG(qemu_log("TDR being written, TDR_underrun set to 0\n"));
SPI_DEBUG(qemu_log("TDR being written, starting sequencer\n"));
+ operation_sequencer(sc);
+
break;
case RECEIVE_DATA_REG:
sc->receive_data_reg = val;
break;
case SEQUENCER_OPERATION_REG:
for (int i = 0; i < SPI_CONTROLLER_REG_SIZE; i++) {
- sc->sequencer_operation_reg[i] = (val >> (56 - i * 8)) & 0xFF;
+ sc->sequencer_operation_reg[i] = (val >> (56 - i * 8)) & 0xFF;
}
break;
case STATUS_REG:
@@ -157,6 +164,1306 @@ static const MemoryRegionOps pnv_spi_controller_xscom_ops = {
.endianness = DEVICE_BIG_ENDIAN,
};
+/* xfer_buffer_methods */
+xfer_buffer *xfer_buffer_new(void)
+{
+ xfer_buffer *payload = g_malloc0(sizeof(*payload));
+
+ payload->data = g_malloc0(payload->len);
+ return payload;
+}
+
+void xfer_buffer_free(xfer_buffer *payload)
+{
+ free(payload->data);
+ free(payload);
+}
+
+uint8_t *xfer_buffer_write_ptr(xfer_buffer *payload, uint32_t offset,
+ uint32_t length)
+{
+ if (payload->len < (offset + length)) {
+ payload->len = offset + length;
+ payload->data = g_realloc(payload->data, payload->len);
+ }
+ return &payload->data[offset];
+}
+
+void xfer_buffer_read_ptr(xfer_buffer *payload, uint8_t **read_buf,
+ uint32_t offset, uint32_t length)
+{
+ static uint32_t prev_len;
+ if ((prev_len != length) || (*read_buf == NULL)) {
+ *read_buf = g_realloc(*read_buf, length);
+ prev_len = length;
+ }
+ if (offset > payload->len) {
+ /* Reading outside payload, just return */
+ return;
+ }
+ *read_buf = &payload->data[offset];
+}
+
+uint8_t reverse_bits8(uint8_t x)
+{
+ x = (x << 4) | (x >> 4);
+ x = ((x & 0x33) << 2) | ((x & 0xcc) >> 2);
+ x = ((x & 0x55) << 1) | ((x & 0xaa) >> 1);
+ return x;
+}
+
+bool does_rdr_match(PnvSpiController *sc)
+{
+ /*
+ * According to spec, the mask bits that are 0 are compared and the
+ * bits that are 1 are ignored.
+ */
+ uint16_t rdr_match_mask = GETFIELD(MEMORY_MAPPING_REG_RDR_MATCH_MASK,
+ sc->memory_mapping_reg);
+ uint16_t rdr_match_val = GETFIELD(MEMORY_MAPPING_REG_RDR_MATCH_VAL,
+ sc->memory_mapping_reg);
+ if ((~rdr_match_mask & rdr_match_val) == ((~rdr_match_mask) &
+ GETFIELD(PPC_BITMASK(48, 63), sc->receive_data_reg))) {
+ SPI_DEBUG(qemu_log("RDR match successful, match=0x%4.4x, "
+ "mask=0x%4.4x, RDR[48:63]=0x%4.4llx\n",
+ rdr_match_val, rdr_match_mask,
+ GETFIELD(PPC_BITMASK(48, 63),
+ sc->receive_data_reg)));
+ return true;
+ } else {
+ SPI_DEBUG(qemu_log("RDR match failed, match=0x%4.4x, mask=0x%4.4x, "
+ "RDR[48:63]=0x%4.4llx\n", rdr_match_val, rdr_match_mask,
+ GETFIELD(PPC_BITMASK(48, 63), sc->receive_data_reg)));
+ return false;
+ }
+}
+
+uint8_t get_from_offset(PnvSpiController *sc, uint8_t offset)
+{
+ uint8_t byte;
+
+ /*
+ * Offset is an index between 0 and SPI_CONTROLLER_REG_SIZE - 1
+ * Check the offset before using it.
+ */
+ if (offset < SPI_CONTROLLER_REG_SIZE) {
+ byte = (sc->transmit_data_reg >> (56 - offset * 8)) & 0xFF;
+ } else {
+ /*
+ * Log an error and return a 0xFF since we have to assign something
+ * to byte before returning.
+ */
+ qemu_log_mask(LOG_GUEST_ERROR, "Invalid offset = %d used to get byte "
+ "from TDR\n", offset);
+ byte = 0xff;
+ }
+ return byte;
+}
+
+void shift_byte_in(PnvSpiController *sc, uint8_t byte)
+{
+ sc->receive_data_reg = (sc->receive_data_reg << 8) | byte;
+ SPI_DEBUG(qemu_log("0x%2.2x shifted in, RDR now = 0x%16.16lx\n", byte,
+ sc->receive_data_reg));
+}
+
+void spi_response(PnvSpiController *sc, int bits, xfer_buffer *rsp_payload)
+{
+ uint8_t *read_buf = NULL;
+ uint8_t ecc_count;
+ uint8_t shift_in_count;
+
+ /*
+ * Processing here must handle:
+ * - Which bytes in the payload we should move to the RDR
+ * - Explicit mode counter configuration settings
+ * - RDR full and RDR overrun status
+ */
+
+ /*
+ * First check that the response payload is the exact same
+ * number of bytes as the request payload was
+ */
+ if (rsp_payload->len != (sc->N1_bytes + sc->N2_bytes)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Invalid response payload size in "
+ "bytes, expected %d, got %d\n",
+ (sc->N1_bytes + sc->N2_bytes), rsp_payload->len);
+ } else {
+ uint8_t ecc_control;
+ SPI_DEBUG(qemu_log("SPI response received, payload len = %d\n",
+ rsp_payload->len));
+ log_all_N_counts(sc);
+ /*
+ * Adding an ECC count let's us know when we have found a payload byte
+ * that was shifted in but cannot be loaded into RDR. Bits 29-30
+ * equal to either 0b00 or 0b10 indicate that we are taking in data
+ * with ECC and either applying the ECC or discarding it.
+ */
+ ecc_count = 0;
+ ecc_control = GETFIELD(PPC_BITMASK(29, 30),
+ sc->clock_config_reset_control);
+ if (ecc_control == 0 || ecc_control == 2) {
+ ecc_count = 1;
+ }
+ /*
+ * Use the N1_rx and N2_rx counts to control shifting data from the
+ * payload into the RDR. Keep an overall count of the number of bytes
+ * shifted into RDR so we can discard every 9th byte when ECC is
+ * enabled.
+ */
+ shift_in_count = 0;
+ /* Handle the N1 portion of the frame first */
+ if (sc->N1_rx != 0) {
+ uint8_t n1_count = 0;
+ while (n1_count < sc->N1_bytes) {
+ shift_in_count++;
+ xfer_buffer_read_ptr(rsp_payload, &read_buf, n1_count, 1);
+ if ((ecc_count != 0) &&
+ (shift_in_count == (SPI_CONTROLLER_REG_SIZE + ecc_count))) {
+ SPI_DEBUG(qemu_log("Discarding rx N1 ECC byte = 0x%2.2x at "
+ "payload index = %d\n", read_buf[0], n1_count));
+ shift_in_count = 0;
+ } else {
+ uint8_t n1_byte = 0x00;
+ n1_byte = read_buf[0];
+ SPI_DEBUG(qemu_log("Extracting rx n1_byte = 0x%2.2x from "
+ "payload at index = %d\n", n1_byte, n1_count));
+ if (sc->reverse_bits) {
+ SPI_DEBUG(qemu_log("Reversing bit order of rx "
+ "n1_byte\n"));
+ n1_byte = reverse_bits8(n1_byte);
+ }
+ SPI_DEBUG(qemu_log("Shifting rx N1 byte = 0x%2.2x into "
+ "RDR\n", n1_byte));
+ shift_byte_in(sc, n1_byte);
+ }
+ n1_count++;
+ } /* end of while */
+ }
+ /* Handle the N2 portion of the frame */
+ if (sc->N2_rx != 0) {
+ uint8_t n2_count = 0;
+ while (n2_count < sc->N2_bytes) {
+ shift_in_count++;
+ xfer_buffer_read_ptr(rsp_payload, &read_buf,
+ (sc->N1_bytes + n2_count), 1);
+ if ((ecc_count != 0) &&
+ (shift_in_count == (SPI_CONTROLLER_REG_SIZE + ecc_count))) {
+ SPI_DEBUG(qemu_log("Discarding rx N1 ECC byte = 0x%2.2x at "
+ "payload index = %d\n", read_buf[0],
+ (sc->N1_bytes + n2_count)));
+ shift_in_count = 0;
+ } else {
+ /*
+ * The code handles shifting data from the payload received
+ * from the responder into the responder's RDR. Since this
+ * is an N2 frame segment it is safe to assume that there
+ * was a preceding N1 segment which was combined with an N2
+ * segment to create a single frame. The response data will
+ * then have N1_bytes of data in the payload representing a
+ * responder response to the N1 section of the frame. If N2
+ * is set to receive the shifting for N2 data begins after
+ * the N1 bytes regardless of whether or not N1 was marked
+ * for transmit or receive.
+ */
+ uint8_t n2_byte = 0x00;
+ n2_byte = read_buf[0];
+ SPI_DEBUG(qemu_log("Extracting rx n2_byte = 0x%2.2x from "
+ "payload at index = %d\n", n2_byte,
+ (sc->N1_bytes + n2_count)));
+ if (sc->reverse_bits) {
+ SPI_DEBUG(qemu_log("Reversing bit order of rx "
+ "n2_byte\n"));
+ n2_byte = reverse_bits8(n2_byte);
+ }
+ SPI_DEBUG(qemu_log("Shifting rx N2 byte = 0x%2.2x into "
+ "RDR\n", n2_byte));
+ shift_byte_in(sc, n2_byte);
+ }
+ n2_count++;
+ }
+ }
+ if ((sc->N1_rx + sc->N2_rx) > 0) {
+ /*
+ * Data was received so handle RDR status.
+ * It is easier to handle RDR_full and RDR_overrun status here
+ * since the RDR register's shift_byte_in method is called
+ * multiple times in a row. Controlling RDR status is done here
+ * instead of in the RDR scoped methods for that reason.
+ */
+ if (GETFIELD(STATUS_REG_RDR_FULL, sc->status_reg) == 1) {
+ /*
+ * Data was shifted into the RDR before having been read
+ * causing previous data to have been overrun.
+ */
+ sc->status_reg = SETFIELD(STATUS_REG_RDR_OVERRUN,
+ sc->status_reg, 1);
+ } else {
+ /*
+ * Set status to indicate that the received data register is
+ * full. This flag is only cleared once the RDR is unloaded.
+ */
+ sc->status_reg = SETFIELD(STATUS_REG_RDR_FULL,
+ sc->status_reg, 1);
+ SPI_DEBUG(qemu_log("RDR_full set to 1\n"));
+ }
+ }
+ } /* end of else */
+} /* end of spi_response() */
+
+void log_all_N_counts(PnvSpiController *sc)
+{
+ SPI_DEBUG(qemu_log("N1_bits = %d, N1_bytes = %d, N1_tx = %d, N1_rx = %d, "
+ "N2_bits = %d, N2_bytes = %d, N2_tx = %d, N2_rx = %d\n",
+ sc->N1_bits, sc->N1_bytes, sc->N1_tx, sc->N1_rx, sc->N2_bits,
+ sc->N2_bytes, sc->N2_tx, sc->N2_rx));
+}
+
+static inline void next_sequencer_fsm(PnvSpiController *sc)
+{
+ uint8_t seq_index = GETFIELD(STATUS_REG_SEQUENCER_INDEX, sc->status_reg);
+ sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_INDEX, sc->status_reg,
+ (seq_index + 1));
+ sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM, sc->status_reg,
+ SEQ_STATE_INDEX_INCREMENT);
+}
+
+void operation_sequencer(PnvSpiController *sc)
+{
+ /*
+ * Loop through each sequencer operation ID and perform the requested
+ * operations.
+ * Flag for indicating if we should send the N1 frame or wait to combine
+ * it with a preceding N2 frame.
+ */
+ bool send_n1_alone = true;
+ bool stop = false; /* Flag to stop the sequencer */
+ uint8_t opcode = 0;
+ uint8_t masked_opcode = 0;
+
+ /*
+ * xfer_buffer for containing the payload of the SPI frame.
+ * This is a static because there are cases where a sequence has to stop
+ * and wait for the target application to unload the RDR. If this occurs
+ * during a sequence where N1 is not sent alone and instead combined with
+ * N2 since the N1 tx length + the N2 tx length is less than the size of
+ * the TDR.
+ */
+ static xfer_buffer *payload;
+
+ if (payload == NULL) {
+ payload = xfer_buffer_new();
+ }
+ /*
+ * Clear the sequencer FSM error bit - general_SPI_status[3]
+ * before starting a sequence.
+ */
+ sc->status_reg = SETFIELD(PPC_BIT(35), sc->status_reg, 0);
+ /*
+ * If the FSM is idle set the sequencer index to 0
+ * (new/restarted sequence)
+ */
+ if (GETFIELD(STATUS_REG_SEQUENCER_FSM, sc->status_reg) ==
+ SEQ_STATE_IDLE) {
+ sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_INDEX,
+ sc->status_reg, 0);
+ }
+ /*
+ * There are only 8 possible operation IDs to iterate through though
+ * some operations may cause more than one frame to be sequenced.
+ */
+ while (GETFIELD(STATUS_REG_SEQUENCER_INDEX, sc->status_reg) < 8) {
+ opcode = sc->sequencer_operation_reg[GETFIELD(
+ STATUS_REG_SEQUENCER_INDEX, sc->status_reg)];
+ /* Set sequencer state to decode */
+ sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM, sc->status_reg,
+ SEQ_STATE_DECODE);
+ /*
+ * Only the upper nibble of the operation ID is needed to know what
+ * kind of operation is requested.
+ */
+ masked_opcode = opcode & 0xF0;
+ switch (masked_opcode) {
+ /*
+ * Increment the operation index in each case instead of just
+ * once at the end in case an operation like the branch
+ * operation needs to change the index.
+ */
+ case SEQ_OP_STOP:
+ sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
+ sc->status_reg, SEQ_STATE_EXECUTE);
+ /* A stop operation in any position stops the sequencer */
+ SPI_DEBUG(qemu_log("Sequencer STOP at index = 0x%llx, sequencer "
+ "idling\n", GETFIELD(
+ STATUS_REG_SEQUENCER_INDEX,
+ sc->status_reg)));
+ stop = true;
+ sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM, sc->status_reg,
+ FSM_IDLE);
+ sc->loop_counter_1 = 0;
+ sc->loop_counter_2 = 0;
+ sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
+ sc->status_reg, SEQ_STATE_IDLE);
+ break;
+
+ case SEQ_OP_SELECT_SLAVE:
+ sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
+ sc->status_reg, SEQ_STATE_EXECUTE);
+ SPI_DEBUG(qemu_log("Sequencer SELECT_SLAVE at index = 0x%llx\n",
+ GETFIELD(STATUS_REG_SEQUENCER_INDEX, sc->status_reg)));
+ /*
+ * This device currently only supports a single responder
+ * connection at position 0. De-selecting a responder is fine
+ * and expected at the end of a sequence but selecting any
+ * responder other than 0 should cause an error.
+ */
+ sc->responder_select = opcode & 0x0F;
+ if (sc->responder_select == 0) {
+ SPI_DEBUG(qemu_log("Shifter done, pull the CS line high\n"));
+ qemu_set_irq((sc->bus).cs_line[0], 1);
+ sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_INDEX,
+ sc->status_reg,
+ (GETFIELD(
+ STATUS_REG_SEQUENCER_INDEX,
+ sc->status_reg) + 1));
+ sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM,
+ sc->status_reg, FSM_DONE);
+ } else if (sc->responder_select != 1) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Slave selection other than 1 "
+ "not supported, select = 0x%x\n",
+ sc->responder_select);
+ SPI_DEBUG(qemu_log("Sequencer stop requested due to invalid "
+ "responder select at index = 0x%llx, "
+ "shifter idling\n", GETFIELD(
+ STATUS_REG_SEQUENCER_INDEX,
+ sc->status_reg)));
+ sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM,
+ sc->status_reg, FSM_IDLE);
+ stop = true;
+ } else {
+ /*
+ * Only allow an FSM_START state when a responder is
+ * selected
+ */
+ sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM,
+ sc->status_reg, FSM_START);
+ SPI_DEBUG(qemu_log("shifter starting, pull CS line low\n"));
+ qemu_set_irq((sc->bus).cs_line[0], 0);
+ sc->first = 1;
+ sc->last = 0;
+ /*
+ * A Shift_N2 operation is only valid after a Shift_N1
+ * according to the spec. The spec doesn't say if that means
+ * immediately after or just after at any point. We will track
+ * the occurrence of a Shift_N1 to enforce this requirement in
+ * the most generic way possible by assuming that the rule
+ * applies once a valid responder select has occurred.
+ */
+ sc->shift_n1_done = false;
+ next_sequencer_fsm(sc);
+ }
+ break;
+
+ case SEQ_OP_SHIFT_N1:
+ sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
+ sc->status_reg, SEQ_STATE_EXECUTE);
+ SPI_DEBUG(qemu_log("Sequencer SHIFT_N1 at index = 0x%llx\n",
+ GETFIELD(STATUS_REG_SEQUENCER_INDEX, sc->status_reg)));
+ /*
+ * Only allow a shift_n1 when the state is not IDLE or DONE.
+ * In either of those two cases the sequencer is not in a proper
+ * state to perform shift operations because the sequencer has:
+ * - processed a responder deselect (DONE)
+ * - processed a stop opcode (IDLE)
+ * - encountered an error (IDLE)
+ */
+ if ((GETFIELD(STATUS_REG_SHIFTER_FSM,
+ sc->status_reg) == FSM_IDLE) ||
+ (GETFIELD(STATUS_REG_SHIFTER_FSM,
+ sc->status_reg) == FSM_DONE)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Shift_N1 not allowed in "
+ "shifter state = 0x%llx", GETFIELD(
+ STATUS_REG_SHIFTER_FSM, sc->status_reg));
+ /*
+ * Set sequencer FSM error bit 3 (general_SPI_status[3])
+ * in status reg.
+ */
+ sc->status_reg = SETFIELD(PPC_BIT(35), sc->status_reg, 1);
+ SPI_DEBUG(qemu_log("Sequencer stop requested due to invalid "
+ "shifter state at index = 0x%llx\n", GETFIELD(
+ STATUS_REG_SEQUENCER_INDEX, sc->status_reg)));
+ stop = true;
+ } else {
+ /*
+ * Look for the special case where there is a shift_n1 set for
+ * transmit and it is followed by a shift_n2 set for transmit
+ * AND the combined transmit length of the two operations is
+ * less than or equal to the size of the TDR register. In this
+ * case we want to use both this current shift_n1 opcode and the
+ * following shift_n2 opcode to assemble the frame for
+ * transmission to the responder without requiring a refill of
+ * the TDR between the two operations.
+ */
+ if ((sc->sequencer_operation_reg[GETFIELD(
+ STATUS_REG_SEQUENCER_INDEX, sc->status_reg) + 1] & 0xF0)
+ == SEQ_OP_SHIFT_N2) {
+ SPI_DEBUG(qemu_log("Not sending N1 alone\n"));
+ send_n1_alone = false;
+ }
+ /*
+ * If the next opcode is 0x10, which deselects the SPI device
+ * then this is the last shift
+ */
+ if (sc->sequencer_operation_reg[GETFIELD(
+ STATUS_REG_SEQUENCER_INDEX, sc->status_reg) + 1] ==
+ SEQ_OP_SELECT_SLAVE) {
+ sc->last = 1;
+ }
+ sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM,
+ sc->status_reg, FSM_SHIFT_N1);
+ stop = operation_shiftn1(sc, opcode, &payload, send_n1_alone);
+ if (stop) {
+ /*
+ * The operation code says to stop, this can occur if:
+ * (1) RDR is full and the N1 shift is set for receive
+ * (2) TDR was empty at the time of the N1 shift so we need
+ * to wait for data.
+ * (3) Neither 1 nor 2 are occurring and we aren't sending
+ * N1 alone and N2 counter reload is set (bit 0 of the N2
+ * counter reload field). In this case TDR_underrun will
+ * will be set and the Payload has been loaded so it is
+ * ok to advance the sequencer.
+ */
+ if (GETFIELD(STATUS_REG_TDR_UNDERRUN, sc->status_reg)) {
+ SPI_DEBUG(qemu_log("Sequencer stop requested due to N2 "
+ "counter reload active.\n"));
+ sc->shift_n1_done = true;
+ sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM,
+ sc->status_reg,
+ FSM_SHIFT_N2);
+ sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_INDEX,
+ sc->status_reg,
+ (GETFIELD(
+ STATUS_REG_SEQUENCER_INDEX,
+ sc->status_reg) + 1));
+ SPI_DEBUG(qemu_log("Set new sequencer index to = "
+ "0x%llx\n", GETFIELD(
+ STATUS_REG_SEQUENCER_INDEX,
+ sc->status_reg)));
+ } else {
+ /*
+ * This is case (1) or (2) so the sequencer needs to
+ * wait and NOT go to the next sequence yet.
+ */
+ sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM,
+ sc->status_reg, FSM_WAIT);
+ }
+ } else {
+ /* Ok to move on to the next index */
+ sc->shift_n1_done = true;
+ next_sequencer_fsm(sc);
+ }
+ }
+ break;
+
+ case SEQ_OP_SHIFT_N2:
+ sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
+ sc->status_reg, SEQ_STATE_EXECUTE);
+ SPI_DEBUG(qemu_log("Sequencer SHIFT_N2 at index = %lld\n",
+ GETFIELD(STATUS_REG_SEQUENCER_INDEX,
+ sc->status_reg)));
+ if (!sc->shift_n1_done) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Shift_N2 is not allowed if a "
+ "Shift_N1 is not done, shifter state = 0x%llx",
+ GETFIELD(STATUS_REG_SHIFTER_FSM,
+ sc->status_reg));
+ /*
+ * In case the sequencer actually stops if an N2 shift is
+ * requested before any N1 shift is done. Set sequencer FSM
+ * error bit 3 (general_SPI_status[3]) in status reg.
+ */
+ sc->status_reg = SETFIELD(PPC_BIT(35), sc->status_reg, 1);
+ SPI_DEBUG(qemu_log("Sequencer stop requested due to shift_n2 "
+ "w/no shift_n1 done at index = 0x%llx\n",
+ GETFIELD(STATUS_REG_SEQUENCER_INDEX,
+ sc->status_reg)));
+ stop = true;
+ } else {
+ /*
+ * If the next opcode is 0x10, which deselects the SPI device
+ * then this is the last shift
+ */
+ if (sc->sequencer_operation_reg[GETFIELD(
+ STATUS_REG_SEQUENCER_INDEX,
+ sc->status_reg) + 1] == SEQ_OP_SELECT_SLAVE) {
+ sc->last = 1;
+ }
+ /* Ok to do a Shift_N2 */
+ sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM,
+ sc->status_reg, FSM_SHIFT_N2);
+ stop = operation_shiftn2(sc, opcode, &payload);
+ /*
+ * If the operation code says to stop set the shifter state to
+ * wait and stop
+ */
+ if (stop) {
+ sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM,
+ sc->status_reg, FSM_WAIT);
+ } else {
+ /* Ok to move on to the next index */
+ next_sequencer_fsm(sc);
+ }
+ }
+ break;
+
+ case SEQ_OP_BRANCH_IFNEQ_RDR:
+ sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
+ sc->status_reg, SEQ_STATE_EXECUTE);
+ SPI_DEBUG(qemu_log("Sequencer BRANCH_IFNEQ_RDR at "
+ "index = 0x%llx\n", GETFIELD(
+ STATUS_REG_SEQUENCER_INDEX, sc->status_reg)));
+ /*
+ * The memory mapping register RDR match value is compared against
+ * the 16 rightmost bytes of the RDR (potentially with masking).
+ * Since this comparison is performed against the contents of the
+ * RDR then a receive must have previously occurred otherwise
+ * there is no data to compare and the operation cannot be
+ * completed and will stop the sequencer until RDR full is set to
+ * 1.
+ */
+ if (GETFIELD(STATUS_REG_RDR_FULL, sc->status_reg) == 1) {
+ bool rdr_matched = false;
+ rdr_matched = does_rdr_match(sc);
+ if (rdr_matched) {
+ SPI_DEBUG(qemu_log("Proceed to next sequencer index "
+ "(increment on RDR match)\n"));
+ /* A match occurred, increment the sequencer index. */
+ next_sequencer_fsm(sc);
+ } else {
+ SPI_DEBUG(qemu_log("Proceed to sequencer index=0x%x "
+ "(branch on RDR match fail)\n", (opcode & 0x7)));
+ /*
+ * Branch the sequencer to the index coded into the op
+ * code.
+ */
+ sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_INDEX,
+ sc->status_reg, (opcode & 0x7));
+ }
+ /*
+ * Regardless of where the branch ended up we want the
+ * sequencer to continue shifting so we have to clear
+ * RDR_full.
+ */
+ sc->status_reg = SETFIELD(STATUS_REG_RDR_FULL,
+ sc->status_reg, 0);
+ } else {
+ SPI_DEBUG(qemu_log("RDR not full for 0x6x opcode! Stopping "
+ "sequencer.\n"));
+ stop = true;
+ sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM,
+ sc->status_reg, FSM_WAIT);
+ }
+ break;
+
+ case SEQ_OP_TRANSFER_TDR:
+ sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
+ sc->status_reg, SEQ_STATE_EXECUTE);
+ qemu_log_mask(LOG_GUEST_ERROR, "Transfer TDR is not supported\n");
+ next_sequencer_fsm(sc);
+ break;
+
+ case SEQ_OP_BRANCH_IFNEQ_INC_1:
+ sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
+ sc->status_reg, SEQ_STATE_EXECUTE);
+ SPI_DEBUG(qemu_log("Sequencer BRANCH_IFNEQ_INC_1 at index = "
+ "0x%llx, next index = %d, count_compare_1 = "
+ "0x%llx, loop_counter_1 = %d\n", GETFIELD(
+ STATUS_REG_SEQUENCER_INDEX, sc->status_reg),
+ (opcode & 0x07),
+ GETFIELD(COUNTER_CONFIG_REG_COUNT_COMPARE1,
+ sc->status_reg), sc->loop_counter_1));
+ /*
+ * The spec says the loop should execute count compare + 1 times.
+ * However we learned from engineering that we really only loop
+ * count_compare times, count compare = 0 makes this op code a
+ * no-op
+ */
+ if (sc->loop_counter_1 !=
+ GETFIELD(COUNTER_CONFIG_REG_COUNT_COMPARE1,
+ sc->counter_config_reg)) {
+ /*
+ * If the next opcode is 0x10, which deselects the SPI device
+ * and we know that the next opcode is the last one in the
+ * loop then the next shift is the last shift
+ */
+ uint8_t condition1 = sc->sequencer_operation_reg[
+ GETFIELD(STATUS_REG_SEQUENCER_INDEX,
+ sc->status_reg) + 1];
+ uint8_t condition2 = GETFIELD(COUNTER_CONFIG_REG_COUNT_COMPARE1,
+ sc->counter_config_reg);
+
+ if ((condition1 == SEQ_OP_SELECT_SLAVE) &&
+ ((sc->loop_counter_1 + 1) == condition2)) {
+ sc->last = 1;
+ }
+ /*
+ * Next index is the lower nibble of the branch operation ID,
+ * mask off all but the first three bits so we don't try to
+ * access beyond the sequencer_operation_reg boundary.
+ */
+ sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_INDEX,
+ sc->status_reg, (opcode & 0x7));
+ sc->loop_counter_1++;
+ SPI_DEBUG(qemu_log("Branching to index = %d, loop_counter_1 = "
+ "%d\n", (opcode & 0x7), sc->loop_counter_1));
+ } else {
+ /* Continue to next index if loop counter is reached */
+ next_sequencer_fsm(sc);
+ SPI_DEBUG(qemu_log("loop counter 1 achieved, next sequencer "
+ "index = 0x%llx\n", GETFIELD(STATUS_REG_SEQUENCER_INDEX,
+ sc->status_reg)));
+ }
+ break;
+
+ case SEQ_OP_BRANCH_IFNEQ_INC_2:
+ sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
+ sc->status_reg, SEQ_STATE_EXECUTE);
+ SPI_DEBUG(qemu_log("Sequencer BRANCH_IFNEQ_INC_2 at index = "
+ "0x%llx, next index = %d, count_compare_2 = "
+ "0x%llx, loop_counter_2 = %d\n", GETFIELD(
+ STATUS_REG_SEQUENCER_INDEX, sc->status_reg),
+ (opcode & 0x07), GETFIELD(
+ COUNTER_CONFIG_REG_COUNT_COMPARE2,
+ sc->status_reg), sc->loop_counter_2));
+ /*
+ * If the next opcode is 0x10, which deselects the SPI device
+ * and we know that the next opcode is the last one in the
+ * loop then the next shift is the last shift
+ */
+ uint8_t condition1 = sc->sequencer_operation_reg[
+ GETFIELD(STATUS_REG_SEQUENCER_INDEX,
+ sc->status_reg) + 1];
+ uint8_t condition2 = GETFIELD(COUNTER_CONFIG_REG_COUNT_COMPARE2,
+ sc->counter_config_reg);
+
+ if ((condition1 == SEQ_OP_SELECT_SLAVE) &&
+ ((sc->loop_counter_2 + 1) == condition2)) {
+ sc->last = 1;
+ }
+ /*
+ * The spec says the loop should execute count compare + 1 times.
+ * However we learned from engineering that we really only loop
+ * count_compare times, count compare = 0 makes this op code a
+ * no-op
+ */
+ if (sc->loop_counter_2 != condition2) {
+ /*
+ * Next index is the lower nibble of the branch operation ID,
+ * mask off all but the first three bits so we don't try to
+ * access beyond the sequencer_operation_reg boundary.
+ */
+ sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_INDEX,
+ sc->status_reg,
+ (opcode & 0x7));
+ sc->loop_counter_2++;
+ SPI_DEBUG(qemu_log("Branching to index = %d, loop_counter_2 "
+ "= %d", (opcode & 0x7),
+ sc->loop_counter_2));
+ } else {
+ /* Continue to next index if loop counter is reached */
+ next_sequencer_fsm(sc);
+ SPI_DEBUG(qemu_log("loop counter 2 achieved, next sequencer "
+ "index = 0x%llx\n", GETFIELD(STATUS_REG_SEQUENCER_INDEX,
+ sc->status_reg)));
+ }
+ break;
+
+ default:
+ sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
+ sc->status_reg, SEQ_STATE_EXECUTE);
+ qemu_log_mask(LOG_GUEST_ERROR, "Sequencer opcode 0x%x is not "
+ "supported\n", opcode);
+ /* Ignore unsupported operations. */
+ next_sequencer_fsm(sc);
+ break;
+ } /* end of switch */
+ /*
+ * If we used all 8 opcodes without seeing a 00 - STOP in the sequence
+ * we need to go ahead and end things as if there was a STOP at the
+ * end.
+ */
+ if (GETFIELD(STATUS_REG_SEQUENCER_INDEX, sc->status_reg) == 8) {
+ SPI_DEBUG(qemu_log("All 8 opcodes completed, sequencer "
+ "idling\n"));
+ sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM, sc->status_reg,
+ FSM_IDLE);
+ sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_INDEX,
+ sc->status_reg, 0);
+ sc->loop_counter_1 = 0;
+ sc->loop_counter_2 = 0;
+ sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
+ sc->status_reg, SEQ_STATE_IDLE);
+ break;
+ }
+ /* Break the loop if a stop was requested */
+ if (stop) {
+ break;
+ }
+ } /* end of while */
+ return;
+} /* end of operation_sequencer() */
+
+/*
+ * Calculate the N1 counters based on passed in opcode and
+ * internal register values.
+ * The method assumes that the opcode is a Shift_N1 opcode
+ * and doesn't test it.
+ * The counters returned are:
+ * N1 bits: Number of bits in the payload data that are significant
+ * to the responder.
+ * N1_bytes: Total count of payload bytes for the N1 (portion of the) frame.
+ * N1_tx: Total number of bytes taken from TDR for N1
+ * N1_rx: Total number of bytes taken from the payload for N1
+ */
+void calculate_N1(PnvSpiController *sc, uint8_t opcode)
+{
+ /*
+ * Shift_N1 opcode form: 0x3M
+ * Implicit mode:
+ * If M != 0 the shift count is M bytes and M is the number of tx bytes.
+ * Forced Implicit mode:
+ * M is the shift count but tx and rx is determined by the count control
+ * register fields. Note that we only check for forced Implicit mode when
+ * M != 0 since the mode doesn't make sense when M = 0.
+ * Explicit mode:
+ * If M == 0 then shift count is number of bits defined in the
+ * Counter Configuration Register's shift_count_N1 field.
+ */
+ if (GETFIELD(PPC_BITMASK8(4, 7), opcode) == 0) {
+ /* Explicit mode */
+ sc->N1_bits = GETFIELD(COUNTER_CONFIG_REG_SHIFT_COUNT_N1,
+ sc->counter_config_reg);
+ sc->N1_bytes = ceil(sc->N1_bits / 8);
+ sc->N1_tx = 0;
+ sc->N1_rx = 0;
+ /* If tx count control for N1 is set, load the tx value */
+ if (GETFIELD(PPC_BIT(50), sc->counter_config_reg) == 1) {
+ sc->N1_tx = sc->N1_bytes;
+ }
+ /* If rx count control for N1 is set, load the rx value */
+ if (GETFIELD(PPC_BIT(51), sc->counter_config_reg) == 1) {
+ sc->N1_rx = sc->N1_bytes;
+ }
+ } else {
+ /* Implicit mode/Forced Implicit mode, use M field from opcode */
+ sc->N1_bytes = GETFIELD(PPC_BITMASK8(4, 7), opcode);
+ sc->N1_bits = sc->N1_bytes * 8;
+ /*
+ * Assume that we are going to transmit the count
+ * (pure Implicit only)
+ */
+ sc->N1_tx = sc->N1_bytes;
+ sc->N1_rx = 0;
+ /* Let Forced Implicit mode have an effect on the counts */
+ if (GETFIELD(PPC_BIT(49), sc->counter_config_reg) == 1) {
+ /*
+ * If Forced Implicit mode and count control doesn't
+ * indicate transmit then reset the tx count to 0
+ */
+ if (GETFIELD(PPC_BIT(50), sc->counter_config_reg) == 0) {
+ sc->N1_tx = 0;
+ }
+ /* If rx count control for N1 is set, load the rx value */
+ if (GETFIELD(PPC_BIT(51), sc->counter_config_reg) == 1) {
+ sc->N1_rx = sc->N1_bytes;
+ }
+ }
+ }
+ /*
+ * Enforce an upper limit on the size of N1 that is equal to the known size
+ * of the shift register, 64 bits or 72 bits if ECC is enabled.
+ * If the size exceeds 72 bits it is a user error so log an error,
+ * cap the size at a max of 64 bits or 72 bits and set the sequencer FSM
+ * error bit.
+ */
+ uint8_t ecc_control = GETFIELD(PPC_BITMASK(29, 30),
+ sc->clock_config_reset_control);
+ if (ecc_control == 0 || ecc_control == 2) {
+ if (sc->N1_bytes > (SPI_CONTROLLER_REG_SIZE + 1)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Unsupported N1 shift size when "
+ "ECC enabled, bytes = 0x%x, bits = 0x%x\n",
+ sc->N1_bytes, sc->N1_bits);
+ sc->N1_bytes = SPI_CONTROLLER_REG_SIZE + 1;
+ sc->N1_bits = sc->N1_bytes * 8;
+ }
+ } else if (sc->N1_bytes > SPI_CONTROLLER_REG_SIZE) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Unsupported N1 shift size, "
+ "bytes = 0x%x, bits = 0x%x\n",
+ sc->N1_bytes, sc->N1_bits);
+ sc->N1_bytes = SPI_CONTROLLER_REG_SIZE;
+ sc->N1_bits = sc->N1_bytes * 8;
+ }
+} /* end of calculate_N1 */
+
+/*
+ * Shift_N1 operation handler method
+ */
+bool operation_shiftn1(PnvSpiController *sc, uint8_t opcode,
+ xfer_buffer **payload, bool send_n1_alone)
+{
+ uint8_t n1_count;
+ bool stop = false;
+
+ /*
+ * If there isn't a current payload left over from a stopped sequence
+ * create a new one.
+ */
+ if (*payload == NULL) {
+ SPI_DEBUG(qemu_log("Creating new payload xfer_buffer\n"));
+ *payload = xfer_buffer_new();
+ }
+ /*
+ * Use a combination of N1 counters to build the N1 portion of the
+ * transmit payload.
+ * We only care about transmit at this time since the request payload
+ * only represents data going out on the controller output line.
+ * Leave mode specific considerations in the calculate function since
+ * all we really care about are counters that tell use exactly how
+ * many bytes are in the payload and how many of those bytes to
+ * include from the TDR into the payload.
+ */
+ calculate_N1(sc, opcode);
+ SPI_DEBUG(qemu_log("Shift N1 started..\n"));
+ log_all_N_counts(sc);
+ /*
+ * Zero out the N2 counters here in case there is no N2 operation following
+ * the N1 operation in the sequencer. This keeps leftover N2 information
+ * from interfering with spi_response logic.
+ */
+ sc->N2_bits = 0;
+ sc->N2_bytes = 0;
+ sc->N2_tx = 0;
+ sc->N2_rx = 0;
+ /*
+ * N1_bytes is the overall size of the N1 portion of the frame regardless of
+ * whether N1 is used for tx, rx or both. Loop over the size to build a
+ * payload that is N1_bytes long.
+ * N1_tx is the count of bytes to take from the TDR and "shift" into the
+ * frame which means append those bytes to the payload for the N1 portion
+ * of the frame.
+ * If N1_tx is 0 or if the count exceeds the size of the TDR append 0xFF to
+ * the frame until the overall N1 count is reached.
+ */
+ n1_count = 0;
+ while (n1_count < sc->N1_bytes) {
+ /*
+ * Assuming that if N1_tx is not equal to 0 then it is the same as
+ * N1_bytes.
+ */
+ if ((sc->N1_tx != 0) && (n1_count < SPI_CONTROLLER_REG_SIZE)) {
+
+ if (GETFIELD(STATUS_REG_TDR_FULL, sc->status_reg) == 1) {
+ /*
+ * Note that we are only appending to the payload IF the TDR
+ * is full otherwise we don't touch the payload because we are
+ * going to NOT send the payload and instead tell the sequencer
+ * that called us to stop and wait for a TDR write so we have
+ * data to load into the payload.
+ */
+ uint8_t n1_byte = 0x00;
+ n1_byte = get_from_offset(sc, n1_count);
+ SPI_DEBUG(qemu_log("Extracting tx n1_byte = 0x%2.2x at index "
+ "%d from TDR\n", n1_byte, n1_count));
+ if (sc->reverse_bits) {
+ SPI_DEBUG(qemu_log("Reversing bit order of tx n1_byte\n"));
+ n1_byte = reverse_bits8(n1_byte);
+ }
+ SPI_DEBUG(qemu_log("Appending tx n1_byte = 0x%2.2x to "
+ "Payload\n", n1_byte));
+ *(xfer_buffer_write_ptr(*payload, (*payload)->len, 1)) =
+ n1_byte;
+ } else {
+ /*
+ * We hit a shift_n1 opcode TX but the TDR is empty, tell the
+ * sequencer to stop and break this loop.
+ */
+ SPI_DEBUG(qemu_log("Shift N1 set for transmit but TDR is empty,"
+ " requesting sequencer stop\n"));
+ stop = true;
+ break;
+ }
+ } else {
+ /*
+ * Cases here:
+ * - we are receiving during the N1 frame segment and the RDR
+ * is full so we need to stop until the RDR is read
+ * - we are transmitting and we don't care about RDR status
+ * since we won't be loading RDR during the frame segment.
+ * - we are receiving and the RDR is empty so we allow the operation
+ * to proceed.
+ */
+ if ((sc->N1_rx != 0) && (GETFIELD(STATUS_REG_RDR_FULL,
+ sc->status_reg) == 1)) {
+ SPI_DEBUG(qemu_log("Shift N1 set for receive but RDR is full, "
+ "requesting sequencer stop\n"));
+ stop = true;
+ break;
+ } else {
+ SPI_DEBUG(qemu_log("Appending tx n1_byte = 0xFF to Payload\n"));
+ *(xfer_buffer_write_ptr(*payload, (*payload)->len, 1)) = 0xff;
+ }
+ }
+ n1_count++;
+ } /* end of while */
+ /*
+ * If we are not stopping due to an empty TDR and we are doing an N1 TX
+ * and the TDR is full we need to clear the TDR_full status.
+ * Do this here instead of up in the loop above so we don't log the message
+ * in every loop iteration.
+ * Ignore the send_n1_alone flag, all that does is defer the TX until the N2
+ * operation, which was found immediately after the current opcode. The TDR
+ * was unloaded and will be shifted so we have to clear the TDR_full status.
+ */
+ if (!stop && (sc->N1_tx != 0) &&
+ (GETFIELD(STATUS_REG_TDR_FULL, sc->status_reg) == 1)) {
+
+ sc->status_reg = SETFIELD(STATUS_REG_TDR_FULL, sc->status_reg, 0);
+ SPI_DEBUG(qemu_log("TDR_full set to 0\n"));
+ }
+ /*
+ * There are other reasons why the shifter would stop, such as a TDR empty
+ * or RDR full condition with N1 set to receive. If we haven't stopped due
+ * to either one of those conditions then check if the send_n1_alone flag is
+ * equal to False, indicating the next opcode is an N2 operation, AND if
+ * the N2 counter reload switch (bit 0 of the N2 count control field) is
+ * set. This condition requires a pacing write to "kick" off the N2
+ * shift which includes the N1 shift as well when send_n1_alone is False.
+ */
+ if (!stop && !send_n1_alone &&
+ (GETFIELD(PPC_BIT(52), sc->counter_config_reg) == 1)) {
+ SPI_DEBUG(qemu_log("N2 counter reload active, stop N1 shift, "
+ "TDR_underrun set to 1\n"));
+ stop = true;
+ sc->status_reg = SETFIELD(STATUS_REG_TDR_UNDERRUN, sc->status_reg, 1);
+ }
+ /*
+ * If send_n1_alone is set AND we have a full TDR then this is the first and
+ * last payload to send and we don't have an N2 frame segment to add to the
+ * payload.
+ */
+ if (send_n1_alone && !stop) {
+ uint32_t tx;
+ uint32_t rx;
+ uint8_t *read_buf = NULL;
+ xfer_buffer *rsp_payload = NULL;
+
+ /* We have a TX and a full TDR or an RX and an empty RDR */
+ SPI_DEBUG(qemu_log("Shifting N1 frame: first = %d, last = %d, "
+ "n1 bits = %d\n", sc->first, sc->last,
+ sc->N1_bits));
+ rsp_payload = xfer_buffer_new();
+ for (int offset = 0; offset < (*payload)->len; offset = offset + 4) {
+ xfer_buffer_read_ptr(*payload, &read_buf, offset, 4);
+ tx = 0;
+ for (int i = 0; i < 4; i++) {
+ if ((offset + i) >= (*payload)->len) {
+ break;
+ }
+ tx = (tx << 8) | read_buf[i];
+ }
+ rx = ssi_transfer((sc->bus).ssi_bus, tx);
+ for (int i = 0; i < 4; i++) {
+ if ((offset + i) >= (*payload)->len) {
+ break;
+ }
+ *(xfer_buffer_write_ptr(rsp_payload, rsp_payload->len, 1)) =
+ (rx >> (24 - i * 8)) & 0xFF;
+ }
+ }
+ if (rsp_payload != NULL) {
+ spi_response(sc, sc->N1_bits, rsp_payload);
+ }
+ sc->first = 0;
+ sc->last = 0;
+ /* The N1 frame shift is complete so reset the N1 counters */
+ sc->N2_bits = 0;
+ sc->N2_bytes = 0;
+ sc->N2_tx = 0;
+ sc->N2_rx = 0;
+ xfer_buffer_free(*payload);
+ *payload = NULL;
+ SPI_DEBUG(qemu_log("Payload buffer freed\n"));
+ } else {
+ SPI_DEBUG(qemu_log("Not shifting N1, send_n1_alone = %d, stop = %d\n",
+ send_n1_alone, stop));
+ }
+ return stop;
+} /* end of operation_shiftn1() */
+
+/*
+ * Calculate the N2 counters based on passed in opcode and
+ * internal register values.
+ * The method assumes that the opcode is a Shift_N2 opcode
+ * and doesn't test it.
+ * The counters returned are:
+ * N2 bits: Number of bits in the payload data that are significant
+ * to the responder.
+ * N2_bytes: Total count of payload bytes for the N2 frame.
+ * N2_tx: Total number of bytes taken from TDR for N2
+ * N2_rx: Total number of bytes taken from the payload for N2
+ */
+void calculate_N2(PnvSpiController *sc, uint8_t opcode)
+{
+ /*
+ * Shift_N2 opcode form: 0x4M
+ * Implicit mode:
+ * If M!=0 the shift count is M bytes and M is the number of rx bytes.
+ * Forced Implicit mode:
+ * M is the shift count but tx and rx is determined by the count control
+ * register fields. Note that we only check for Forced Implicit mode when
+ * M != 0 since the mode doesn't make sense when M = 0.
+ * Explicit mode:
+ * If M==0 then shift count is number of bits defined in the
+ * Counter Configuration Register's shift_count_N1 field.
+ */
+ if (GETFIELD(PPC_BITMASK8(4, 7), opcode) == 0) {
+ /* Explicit mode */
+ sc->N2_bits = GETFIELD(COUNTER_CONFIG_REG_SHIFT_COUNT_N2,
+ sc->counter_config_reg);
+ sc->N2_bytes = ceil(sc->N2_bits / 8);
+ sc->N2_tx = 0;
+ sc->N2_rx = 0;
+ /* If tx count control for N2 is set, load the tx value */
+ if (GETFIELD(PPC_BIT(54), sc->counter_config_reg) == 1) {
+ sc->N2_tx = sc->N2_bytes;
+ }
+ /* If rx count control for N2 is set, load the rx value */
+ if (GETFIELD(PPC_BIT(55), sc->counter_config_reg) == 1) {
+ sc->N2_rx = sc->N2_bytes;
+ }
+ } else {
+ /* Implicit mode/Forced Implicit mode, use M field from opcode */
+ sc->N2_bytes = GETFIELD(PPC_BITMASK8(4, 7), opcode);
+ sc->N2_bits = sc->N2_bytes * 8;
+ /* Assume that we are going to receive the count */
+ sc->N2_rx = sc->N2_bytes;
+ sc->N2_tx = 0;
+ /* Let Forced Implicit mode have an effect on the counts */
+ if (GETFIELD(PPC_BIT(53), sc->counter_config_reg) == 1) {
+ /*
+ * If Forced Implicit mode and count control doesn't
+ * indicate a receive then reset the rx count to 0
+ */
+ if (GETFIELD(PPC_BIT(55), sc->counter_config_reg) == 0) {
+ sc->N2_rx = 0;
+ }
+ /* If tx count control for N2 is set, load the tx value */
+ if (GETFIELD(PPC_BIT(54), sc->counter_config_reg) == 1) {
+ sc->N2_tx = sc->N2_bytes;
+ }
+ }
+ }
+ /*
+ * Enforce an upper limit on the size of N1 that is equal to the
+ * known size of the shift register, 64 bits or 72 bits if ECC
+ * is enabled.
+ * If the size exceeds 72 bits it is a user error so log an error,
+ * cap the size at a max of 64 bits or 72 bits and set the sequencer FSM
+ * error bit.
+ */
+ uint8_t ecc_control = GETFIELD(PPC_BITMASK(29, 30),
+ sc->clock_config_reset_control);
+ if (ecc_control == 0 || ecc_control == 2) {
+ if (sc->N2_bytes > (SPI_CONTROLLER_REG_SIZE + 1)) {
+ SPI_DEBUG(qemu_log("Unsupported N2 shift size when ECC enabled, "
+ "bytes = 0x%x, bits = 0x%x\n",
+ sc->N2_bytes, sc->N2_bits));
+ sc->N2_bytes = SPI_CONTROLLER_REG_SIZE + 1;
+ sc->N2_bits = sc->N2_bytes * 8;
+ }
+ } else if (sc->N2_bytes > SPI_CONTROLLER_REG_SIZE) {
+ SPI_DEBUG(qemu_log("Unsupported N2 shift size, bytes = 0x%x, "
+ "bits = 0x%x\n", sc->N2_bytes, sc->N2_bits));
+ sc->N2_bytes = SPI_CONTROLLER_REG_SIZE;
+ sc->N2_bits = sc->N2_bytes * 8;
+ }
+} /* end of calculate_N2 */
+
+/*
+ * Shift_N2 operation handler method
+ */
+
+bool operation_shiftn2(PnvSpiController *sc, uint8_t opcode,
+ xfer_buffer **payload)
+{
+ uint8_t n2_count;
+ bool stop = false;
+
+ /*
+ * If there isn't a current payload left over from a stopped sequence
+ * create a new one.
+ */
+ if (*payload == NULL) {
+ SPI_DEBUG(qemu_log("Creating new payload xfer_buffer\n"));
+ *payload = xfer_buffer_new();
+ }
+ /*
+ * Use a combination of N2 counters to build the N2 portion of the
+ * transmit payload.
+ */
+ calculate_N2(sc, opcode);
+ SPI_DEBUG(qemu_log("Shift N2 started\n"));
+ log_all_N_counts(sc);
+ /*
+ * The only difference between this code and the code for shift N1 is
+ * that this code has to account for the possible presence of N1 transmit
+ * bytes already taken from the TDR.
+ * If there are bytes to be transmitted for the N2 portion of the frame
+ * and there are still bytes in TDR that have not been copied into the
+ * TX data of the payload, this code will handle transmitting those
+ * remaining bytes.
+ * If for some reason the transmit count(s) add up to more than the size
+ * of the TDR we will just append 0xFF to the transmit payload data until
+ * the payload is N1 + N2 bytes long.
+ */
+ n2_count = 0;
+ while (n2_count < sc->N2_bytes) {
+ /*
+ * If the RDR is full and we need to RX just bail out, letting the
+ * code continue will end up building the payload twice in the same
+ * buffer since RDR full causes a sequence stop and restart.
+ */
+ if ((sc->N2_rx != 0) &&
+ (GETFIELD(STATUS_REG_RDR_FULL, sc->status_reg) == 1)) {
+ SPI_DEBUG(qemu_log("Shift N2 set for receive but RDR is full, "
+ "requesting sequencer stop\n"));
+ stop = true;
+ break;
+ }
+ if ((sc->N2_tx != 0) && ((sc->N1_tx + n2_count) <
+ SPI_CONTROLLER_REG_SIZE)) {
+ /* Always append data for the N2 segment if it is set for TX */
+ uint8_t n2_byte = 0x00;
+ n2_byte = get_from_offset(sc, (sc->N1_tx + n2_count));
+ SPI_DEBUG(qemu_log("Extracting tx n2_byte = 0x%2.2x at index %d "
+ "from TDR\n", n2_byte, (sc->N1_tx + n2_count)));
+ if (sc->reverse_bits) {
+ SPI_DEBUG(qemu_log("Reversing bit order of tx n2_byte\n"));
+ n2_byte = reverse_bits8(n2_byte);
+ }
+ SPI_DEBUG(qemu_log("Appending tx n2_byte = 0x%2.2x to Payload\n",
+ n2_byte));
+ *(xfer_buffer_write_ptr(*payload, (*payload)->len, 1)) = n2_byte;
+ } else {
+ /*
+ * Regardless of whether or not N2 is set for TX or RX, we need
+ * the number of bytes in the payload to match the overall length
+ * of the operation.
+ */
+ SPI_DEBUG(qemu_log("Appending tx n2_byte = 0xFF to Payload\n"));
+ *(xfer_buffer_write_ptr(*payload, (*payload)->len, 1)) = 0xff;
+ }
+ n2_count++;
+ } /* end of while */
+ if (!stop) {
+ uint32_t tx;
+ uint32_t rx;
+ uint8_t *read_buf = NULL;
+ xfer_buffer *rsp_payload = NULL;
+
+ /* We have a TX and a full TDR or an RX and an empty RDR */
+ SPI_DEBUG(qemu_log("Shifting N2 frame: first = %d, last = %d, "
+ "n1+n2 bits = %d\n", sc->first, sc->last,
+ (sc->N1_bits + sc->N2_bits)));
+ rsp_payload = xfer_buffer_new();
+ for (int offset = 0; offset < (*payload)->len; offset = offset + 4) {
+ xfer_buffer_read_ptr(*payload, &read_buf, offset, 4);
+ tx = 0;
+ for (int i = 0; i < 4; i++) {
+ if ((offset + i) >= (*payload)->len) {
+ break;
+ }
+ tx = (tx << 8) | read_buf[i];
+ }
+ rx = ssi_transfer((sc->bus).ssi_bus, tx);
+ for (int i = 0; i < 4; i++) {
+ if ((offset + i) >= (*payload)->len) {
+ break;
+ }
+ *(xfer_buffer_write_ptr(rsp_payload, rsp_payload->len, 1)) =
+ (rx >> (24 - i * 8)) & 0xFF;
+ }
+ }
+ if (rsp_payload != NULL) {
+ spi_response(sc, (sc->N1_bits + sc->N2_bits), rsp_payload);
+ }
+ sc->first = 0;
+ sc->last = 0;
+ /*
+ * If we are doing an N2 TX and the TDR is full we need to clear the
+ * TDR_full status. Do this here instead of up in the loop above so we
+ * don't log the message in every loop iteration.
+ */
+ if ((sc->N2_tx != 0) &&
+ (GETFIELD(STATUS_REG_TDR_FULL, sc->status_reg) == 1)) {
+ sc->status_reg = SETFIELD(STATUS_REG_TDR_FULL, sc->status_reg, 0);
+ SPI_DEBUG(qemu_log("TDR_full set to 0\n"));
+ }
+ /*
+ * The N2 frame shift is complete so reset the N2 counters.
+ * Reset the N1 counters also in case the frame was a combination of
+ * N1 and N2 segments.
+ */
+ sc->N2_bits = 0;
+ sc->N2_bytes = 0;
+ sc->N2_tx = 0;
+ sc->N2_rx = 0;
+ sc->N1_bits = 0;
+ sc->N1_bytes = 0;
+ sc->N1_tx = 0;
+ sc->N1_rx = 0;
+ xfer_buffer_free(*payload);
+ *payload = NULL;
+ SPI_DEBUG(qemu_log("Payload buffer freed\n"));
+ } else {
+ SPI_DEBUG(qemu_log("Not shifting N2, stop = %d\n", stop));
+ }
+ return stop;
+} /* end of operation_shiftn2()*/
+
+/*
+ * The SPIC engine and its internal sequencer can be interrupted and reset by
+ * a hardware signal, the sbe_spicst_hard_reset bits from Pervasive
+ * Miscellaneous Register of sbe_register_bo device.
+ * Reset immediately aborts any SPI transaction in progress and returns the
+ * sequencer and state machines to idle state.
+ * The configuration register values are not changed. The status register is
+ * not reset. The engine registers are not reset.
+ * The SPIC engine reset does not have any affect on the attached devices.
+ * Reset handling of any attached devices is beyond the scope of the engine.
+ */
+static void do_reset(DeviceState *dev)
+{
+ PnvSpiController *sc = PNV_SPICONTROLLER(dev);
+ SPI_DEBUG(qemu_log("Resetting spic engine sequencer configuration and spi "
+ "communication\n"));
+ /* Reset all N1 and N2 counters, and other constants */
+ sc->first = 0;
+ sc->last = 0;
+ sc->N2_bits = 0;
+ sc->N2_bytes = 0;
+ sc->N2_tx = 0;
+ sc->N2_rx = 0;
+ sc->N1_bits = 0;
+ sc->N1_bytes = 0;
+ sc->N1_tx = 0;
+ sc->N1_rx = 0;
+ sc->loop_counter_1 = 0;
+ sc->loop_counter_2 = 0;
+ SPI_DEBUG(qemu_log("Disconnected from responder\n"));
+ qemu_set_irq((sc->bus).cs_line[0], 1);
+}
+
static void pnv_spi_bus_realize(DeviceState *dev, Error **errp)
{
PnvSPIBus *s = PNV_SPI_BUS(dev);
@@ -191,6 +1498,7 @@ static const TypeInfo pnv_spi_bus_info = {
static Property pnv_spi_controller_properties[] = {
DEFINE_PROP_UINT32("spic_num", PnvSpiController, spic_num, 0),
+ DEFINE_PROP_BOOL("reverse_bits", PnvSpiController, reverse_bits, false),
DEFINE_PROP_END_OF_LIST(),
};
@@ -254,6 +1562,7 @@ static void pnv_spi_controller_class_init(ObjectClass *klass, void *data)
dc->desc = "PowerNV SPI Controller";
dc->realize = pnv_spi_controller_realize;
+ dc->reset = do_reset;
device_class_set_props(dc, pnv_spi_controller_properties);
}
--
2.39.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 4/6] hw/misc: Microchip's 25CSM04 SEEPROM model
2024-04-09 17:56 [PATCH v2 0/6] hw/ppc: SPI model Chalapathi V
` (2 preceding siblings ...)
2024-04-09 17:56 ` [PATCH v2 3/6] hw/ppc: SPI controller model - sequencer and shifter Chalapathi V
@ 2024-04-09 17:56 ` Chalapathi V
2024-04-22 14:44 ` Cédric Le Goater
2024-04-09 17:56 ` [PATCH v2 5/6] hw/ppc: SPI controller wiring to P10 chip and create seeprom device Chalapathi V
2024-04-09 17:57 ` [PATCH v2 6/6] tests/qtest: Add pnv-spi-seeprom qtest Chalapathi V
5 siblings, 1 reply; 16+ messages in thread
From: Chalapathi V @ 2024-04-09 17:56 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-ppc, fbarrat, npiggin, clg, calebs, chalapathi.v,
chalapathi.v, saif.abrar, dantan
This commit implements a Serial EEPROM utilizing the Serial Peripheral
Interface (SPI) compatible bus.
Currently implemented SEEPROM is Microchip's 25CSM04 which provides 4 Mbits
of Serial EEPROM utilizing the Serial Peripheral Interface (SPI) compatible
bus. The device is organized as 524288 bytes of 8 bits each (512Kbyte) and
is optimized for use in consumer and industrial applications where reliable
and dependable nonvolatile memory storage is essential.
This seeprom device is created from a parent "ssi-peripheral".
Signed-off-by: Chalapathi V <chalapathi.v@linux.ibm.com>
---
include/hw/misc/seeprom_25csm04.h | 48 ++
hw/misc/seeprom_25csm04.c | 780 ++++++++++++++++++++++++++++++
hw/misc/Kconfig | 3 +
hw/misc/meson.build | 1 +
hw/ppc/Kconfig | 1 +
5 files changed, 833 insertions(+)
create mode 100644 include/hw/misc/seeprom_25csm04.h
create mode 100644 hw/misc/seeprom_25csm04.c
diff --git a/include/hw/misc/seeprom_25csm04.h b/include/hw/misc/seeprom_25csm04.h
new file mode 100644
index 0000000000..0343530354
--- /dev/null
+++ b/include/hw/misc/seeprom_25csm04.h
@@ -0,0 +1,48 @@
+/*
+ * 25CSM04 Serial EEPROM model
+ *
+ * Copyright (c) 2024, IBM Corporation.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * The Microchip Technology Inc. 25CSM04 provides 4 Mbits of Serial EEPROM
+ * utilizing the Serial Peripheral Interface (SPI) compatible bus. The device
+ * is organized as 524288 bytes of 8 bits each (512Kbyte) and is optimized
+ * for use in consumer and industrial applications where reliable and
+ * dependable nonvolatile memory storage is essential
+ */
+
+#ifndef SEEPROM_25CSM04_H
+#define SEEPROM_25CSM04_H
+
+#include "hw/ssi/ssi.h"
+#include "qom/object.h"
+
+#define TYPE_SEEPROM_25CSM04 "seeprom-25csm04"
+
+OBJECT_DECLARE_SIMPLE_TYPE(SeepromCsm04, SEEPROM_25CSM04)
+
+typedef struct SeepromCsm04 {
+ SSIPeripheral parent_object;
+
+ char *file;
+ char *file_name;
+ uint8_t opcode;
+ uint32_t addr;
+ uint8_t rd_state;
+ bool locked;
+ bool command_byte;
+ /* device registers */
+ uint8_t status0;
+ uint8_t status1;
+ uint8_t dsn[16];
+ uint8_t uplid[256];
+ uint8_t mpr[8];
+ uint8_t idr[5];
+} SeepromCsm04;
+
+uint32_t seeprom_transfer(SSIPeripheral *ss, uint32_t tx);
+void seeprom_realize(SSIPeripheral *dev, Error **errp);
+bool compute_addr(SeepromCsm04 *s, uint32_t tx);
+bool validate_addr(SeepromCsm04 *s);
+#endif /* PPC_PNV_SPI_SEEPROM_H */
diff --git a/hw/misc/seeprom_25csm04.c b/hw/misc/seeprom_25csm04.c
new file mode 100644
index 0000000000..45df66e4b0
--- /dev/null
+++ b/hw/misc/seeprom_25csm04.c
@@ -0,0 +1,780 @@
+/*
+ * 25CSM04 Serial EEPROM model
+ *
+ * Copyright (c) 2024, IBM Corporation.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "hw/misc/seeprom_25csm04.h"
+#include "hw/qdev-properties.h"
+#include "qemu/datadir.h"
+#include <math.h>
+
+#define SPI_DEBUG(x)
+
+/*
+ * 2-byte STATUS register which is a combination of six nonvolatile bits of
+ * EEPROM and five volatile latches.
+ *
+ * status 0:
+ * bit 7 WPEN: Write-Protect Enable bit
+ * 1 = Write-Protect pin is enabled, 0 = Write-Protect pin is ignored
+ *
+ * bit 3-2 BP<1:0>: Block Protection bits
+ * 00 = No array write protection
+ * 01 = Upper quarter memory array protection
+ * 10 = Upper half memory array protection
+ * 11 = Entire memory array protection
+ *
+ * bit 1 WEL: Write Enable Latch bit
+ * 1 = WREN has been executed and device is enabled for writing
+ * 0 = Device is not write-enabled
+ *
+ * bit 0 RDY/BSY: Ready/Busy Status Latch bit
+ * 1 = Device is busy with an internal write cycle
+ * 0 = Device is ready for a new sequence
+ */
+#define STATUS0_WPEN 0x7
+#define STATUS0_BP 0x2
+#define STATUS0_WEL 0x1
+#define STATUS0_BUSY 0x0
+
+/*
+ * status 1:
+ * bit 7 WPM: Write Protection Mode bit(1)
+ * 1 = Enhanced Write Protection mode selected (factory default)
+ * 0 = Legacy Write Protection mode selected
+ *
+ * bit 6 ECS: Error Correction State Latch bit
+ * 1 = The previously executed read sequence did require the ECC
+ * 0 = The previous executed read sequence did not require the ECC
+ *
+ * bit 5 FMPC: Freeze Memory Protection Configuration bit(2)
+ * 1 = Memory Partition registers and write protection mode are permanently
+ * frozen and cannot be modified
+ * 0 = Memory Partition registers and write protection mode are not frozen
+ * and are modifiable
+ *
+ * bit 4 PREL: Partition Register Write Enable Latch bit
+ * 1 = PRWE has been executed and WMPR, FRZR and PPAB instructions are enabled
+ * 0 = WMPR, FRZR and PPAB instructions are disabled
+ *
+ * bit 3 PABP: Partition Address Boundary Protection bit
+ * 1 = Partition Address Endpoints set in Memory Partition registers
+ * cannot be modified
+ * 0 = Partition Address Endpoints set in Memory Partition registers
+ * are modifiable
+ *
+ * bit 0 RDY/BSY: Ready/Busy Status Latch bit
+ * 1 = Device is busy with an internal write cycle
+ * 0 = Device is ready for a new sequence
+ */
+#define STATUS1_WPM 0x7
+#define STATUS1_ECS 0x6
+#define STATUS1_FMPC 0x5
+#define STATUS1_PREL 0x4
+#define STATUS1_PABP 0x3
+#define STATUS1_BUSY 0x0
+
+/*
+ * MEMORY PARTITION REGISTERS
+ * Note 1: The MPR cannot be written if the FMPC bit has been set.
+ * 2: The Partition Endpoint Address bits cannot be written if the PABP
+ * bit has been set.
+ *
+ * bits 7-6 PB<1:0>: Partition Behavior bits(1)
+ * 00 = Partition is open and writing is permitted
+ * factory default is unprotected.
+ * 01 = Partition is always write-protected but can be reversed at a later
+ * time (software write-protected).
+ * 10 = Partition is write-protected only when WP pin is asserted
+ * (hardware write-protected).
+ * 11 = Partition is software write-protected and MPR is permanently locked
+ *
+ * bit 5-0 A<18:13>: Partition Endpoint Address bits(1, 2)
+ * 000000 = Endpoint address of partition is set to 01FFFh.
+ * 000001 = Endpoint address of partition is set to 03FFFh.
+ * ----
+ * 111110 = Endpoint address of partition is set to 7DFFFh.
+ * 111111 = Endpoint address of partition is set to 7FFFFh.
+ */
+#define MPR_PB 0x6
+#define MPR_PEA 0x5
+
+/* INSTRUCTION SET FOR 25CSM04 */
+#define RDSR 0x05
+#define WRBP 0x08
+#define WREN 0x06
+#define WRDI 0x04
+#define WRSR 0x01
+#define READ 0x03
+#define WRITE 0x02
+#define RDEX_CHLK 0x83
+#define WREX_LOCK 0x82
+#define RMPR 0x31
+#define PRWE 0x07
+#define PRWD 0x0A
+#define WMPR 0x32
+#define PPAB 0x34
+#define FRZR 0x37
+#define SPID 0x9F
+#define SRST 0x7C
+
+/* READ FSM state */
+#define ST_IDLE 0
+#define ST_READ 1
+#define ST_SEC_READ 2
+
+#define DATA_LEN 4
+
+uint32_t seeprom_transfer(SSIPeripheral *ss, uint32_t tx)
+{
+ SeepromCsm04 *s = SEEPROM_25CSM04(ss);
+ uint16_t idx;
+ FILE *f;
+ bool status0_busy;
+ bool status1_busy;
+ uint32_t rx = -1;
+ uint32_t *buf = NULL;
+ bool failed = false;
+
+ buf = g_malloc0(sizeof(uint32_t));
+ SPI_DEBUG(qemu_log("Received SPI request, tx = 0x%x\n", tx));
+ if (s->command_byte) {
+ s->opcode = tx >> 24;
+ SPI_DEBUG(qemu_log("Command Opcode (0x%x)\n", s->opcode));
+ /*
+ * Check if device is busy with internal write cycle, During this
+ * time, only the Read STATUS Register (RDSR) and the Write Ready/Busy
+ * Poll (WRBP) instructions will be executed by the device.
+ */
+ status0_busy = extract8(s->status0, STATUS0_BUSY, 1);
+ status1_busy = extract8(s->status1, STATUS1_BUSY, 1);
+ if (((status0_busy == 1) || (status1_busy == 1)) &&
+ ((s->opcode != RDSR) || (s->opcode != WRBP))) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Busy with internal write Cycle, "
+ "opcode(0x%x) not executed\n", s->opcode);
+ return rx;
+ }
+ /* For a new command sequence compute Address */
+ failed = compute_addr(s, tx);
+ /*
+ * Address computation failed, nothing to do further, just send
+ * response and return from here.
+ */
+ if (failed) {
+ return tx;
+ }
+ }
+
+ switch (s->opcode) {
+ case READ:
+ if (!s->command_byte) {
+ SPI_DEBUG(qemu_log("READ(0x%x), addr(0x%x)\n",
+ s->opcode, s->addr));
+ s->rd_state = ST_READ;
+ if (s->file) {
+ f = fopen(s->file, "rb+");
+ if (f) {
+ if (!fseek(f, s->addr, SEEK_SET)) {
+ if (fread(buf, sizeof(uint32_t), 1, f) == 1) {
+ SPI_DEBUG(qemu_log("Read 4 bytes from seeprom\n"));
+ rx = *buf;
+ } else {
+ if (ferror(f)) {
+ SPI_DEBUG(qemu_log("Error reading seeprom\n"));
+ }
+ }
+ }
+ }
+ fclose(f);
+ }
+ s->addr = (s->addr & 0x7FFFF) + DATA_LEN;
+ }
+ break;
+
+ case RDSR:
+ SPI_DEBUG(qemu_log("READ Status Register - RDSR(0x%x)\n",
+ s->opcode));
+ rx = 0;
+ rx = rx | (s->status0 << 24);
+ rx = rx | (s->status1 << 16);
+ break;
+
+ case WRBP:
+ SPI_DEBUG(qemu_log("Write Ready/Busy Poll - WRBP(0x%x)\n",
+ s->opcode));
+ status0_busy = extract8(s->status0, STATUS0_BUSY, 1);
+ status1_busy = extract8(s->status1, STATUS1_BUSY, 1);
+ rx = 0;
+ if ((status0_busy == 1) || (status1_busy == 1)) {
+ rx = rx | (0xFF << 24);
+ }
+ break;
+
+ case WREN:
+ SPI_DEBUG(qemu_log("Set Write Enable Latch (WEL) WREN(0x%x)\n",
+ s->opcode));
+ s->status0 = deposit32(s->status0, STATUS0_WEL, 1, 1);
+ break;
+
+ case WRDI:
+ SPI_DEBUG(qemu_log("Reset Write Enable Latch (WEL) WRDI(0x%x)\n",
+ s->opcode));
+ s->status0 = deposit32(s->status0, STATUS0_WEL, 1, 0);
+ break;
+
+ case WRSR:
+ SPI_DEBUG(qemu_log("Write STATUS Register WRSR(0x%x)\n",
+ s->opcode));
+ s->status0 = deposit32(s->status0, STATUS0_WEL, 1, 1);
+ if (extract8(s->status0, STATUS0_WEL, 1) == 1) {
+ /* Mask and update status0/1 bytes */
+ s->status0 = (tx >> 16) & 0x8C;
+ s->status1 = (tx >> 8) & 0x80;
+ }
+ break;
+
+ case SPID:
+ SPI_DEBUG(qemu_log("READ IDENTIFICATION REGISTER, SPID(0x%x)\n",
+ s->opcode));
+ rx = 0;
+ for (idx = 0; idx < DATA_LEN; idx++) {
+ rx = (rx << 8) | s->idr[idx];
+ }
+ break;
+
+ case SRST:
+ SPI_DEBUG(qemu_log("Software Device Reset, SRST(0x%x)\n",
+ s->opcode));
+ /*
+ * Note: The SRST instruction cannot interrupt the device while it is
+ * in a Busy state (Section 6.1.4 Ready/Busy Status Latch).
+ * This is already taken care of when the command opcode is fetched
+ *
+ * 1.2 Device Default State
+ * 1.2.1 POWER-UP DEFAULT STATE
+ * The 25CSM04 default state upon power-up consists of:
+ * - Standby Power mode (CS = HIGH)
+ * - A high-to-low level transition on CS is required to enter the
+ * active state
+ * - WEL bit in the STATUS register = 0
+ * - ECS bit in the STATUS register = 0
+ * - PREL bit in the STATUS register = 0
+ * - Ready/Busy (RDY/BUSY) bit in the STATUS register = 0, indicating
+ * the device is ready to accept a new instruction.
+ */
+ s->status0 = deposit32(s->status0, STATUS0_WEL, 1, 0);
+ s->status1 = deposit32(s->status1, STATUS1_ECS, 1, 0);
+ s->status1 = deposit32(s->status1, STATUS1_PREL, 1, 0);
+ s->status0 = deposit32(s->status0, STATUS0_BUSY, 1, 0);
+ s->status1 = deposit32(s->status1, STATUS1_BUSY, 1, 0);
+ break;
+
+ case WRITE:
+ if (!s->command_byte) {
+ SPI_DEBUG(qemu_log("WRITE(0x%x), addr(0x%x)\n",
+ s->opcode, s->addr));
+ if (extract8(s->status0, STATUS0_WEL, 1) != 1) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Device is not Write Enabled, "
+ "ignoring WRITE instruction\n");
+ break;
+ }
+ /* Write into SEEPROM Array */
+ SPI_DEBUG(qemu_log("Write sequence\n"));
+ buf = &tx;
+ if (s->file) {
+ f = fopen(s->file, "rb+");
+ if (f) {
+ if (!fseek(f, s->addr, SEEK_SET)) {
+ if (fwrite(buf, sizeof(uint32_t), 1, f) == 1) {
+ SPI_DEBUG(qemu_log("Write 4 bytes to seeprom\n"));
+ } else {
+ SPI_DEBUG(qemu_log("Failed to write seeprom\n"));
+ }
+ }
+ }
+ fclose(f);
+ }
+ /* Increase offset in the page */
+ s->addr += DATA_LEN;
+ }
+ break;
+
+ case RMPR:
+ if (!s->command_byte) {
+ /*
+ * The address for each Memory Partition register is embedded into
+ * the first address byte sent to the device,in bit positions A18
+ * through A16.
+ */
+ SPI_DEBUG(qemu_log("RMPR(0x%x) for MPR[%d]\n", s->opcode,
+ extract8(s->addr, 16, 2)));
+ rx = 0;
+ rx = rx | (s->mpr[extract8(s->addr, 16, 2)] << 24);
+ }
+ break;
+
+ case PRWE:
+ SPI_DEBUG(qemu_log("Set Memory Partition Write Enable Latch "
+ "PRWE(0x%x)\n", s->opcode));
+ s->status1 = deposit32(s->status1, STATUS1_PREL, 1, 1);
+ break;
+
+ case PRWD:
+ SPI_DEBUG(qemu_log("Reset Memory Partition Write Enable Latch "
+ "PRWD(0x%x)\n", s->opcode));
+ s->status1 = deposit32(s->status1, STATUS1_PREL, 1, 0);
+ break;
+
+ case WMPR:
+ if (!s->command_byte) {
+ /*
+ * The address for each Memory Partition register is embedded into
+ * the first address byte sent to the device,in bit positions A18
+ * through A16.
+ */
+ SPI_DEBUG(qemu_log("Write Memory Partition Register[%d] "
+ "WMPR(0x%x)\n",
+ extract8(s->addr, 16, 2), s->opcode));
+ /*
+ * Once the WEL and PREL bits in the STATUS register have been
+ * set to 1, the Memory Partition registers can be programmed
+ * provided that the FMPC bit in the STATUS register has not
+ * already been set to a logic 1.
+ */
+ if ((extract8(s->status0, STATUS0_WEL, 1) != 1) ||
+ (extract8(s->status1, STATUS1_PREL, 1) != 1) ||
+ (extract8(s->status1, STATUS1_FMPC, 1) == 1)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "ignoring write to MPR\n");
+ break;
+ }
+ if (extract8(s->status1, STATUS1_PABP, 1) == 1) {
+ /* Partition Address Boundaries Protected */
+ s->mpr[extract8(s->addr, 16, 2)] =
+ ((tx >> 30) & 0x3);
+ } else {
+ s->mpr[extract8(s->addr, 16, 2)] = (tx >> 24) & 0xFF;
+ }
+ s->status0 = deposit32(s->status0, STATUS0_WEL, 1, 0);
+ s->status1 = deposit32(s->status1, STATUS1_PREL, 1, 0);
+ }
+ break;
+
+ case PPAB:
+ if (!s->command_byte) {
+ SPI_DEBUG(qemu_log("Protect Partition Address Boundaries"
+ "PPAB(0x%x)\n", s->opcode));
+ if ((extract8(s->status0, STATUS0_WEL, 1) != 1) ||
+ (extract8(s->status1, STATUS1_PREL, 1) != 1) ||
+ (extract8(s->status1, STATUS1_FMPC, 1) == 1)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Ignoring PPAB command\n");
+ break;
+ }
+ tx = (tx >> 24) & 0xFF;
+ if (tx == 0xFF) {
+ s->status1 = deposit32(s->status1,
+ STATUS1_PABP, 1, 1);
+ } else if (tx == 0x0) {
+ s->status1 = deposit32(s->status1,
+ STATUS1_PABP, 1, 0);
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR, "Incorrect data byte(0x%x), "
+ "should be 0x0 or 0xFF\n", tx);
+ }
+ s->status0 = deposit32(s->status0, STATUS0_WEL, 1, 0);
+ s->status1 = deposit32(s->status1, STATUS1_PREL, 1, 0);
+ }
+ break;
+
+ case FRZR:
+ if (!s->command_byte) {
+ SPI_DEBUG(qemu_log("Freeze Memory Protection Configuration "
+ "FRZR(0x%x)\n", s->opcode));
+ if ((extract8(s->status0, STATUS0_WEL, 1) != 1) ||
+ (extract8(s->status1, STATUS1_PREL, 1) != 1) ||
+ (extract8(s->status1, STATUS1_FMPC, 1) == 1)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "ignoring FRZR command\n");
+ break;
+ }
+ tx = (tx >> 24) & 0xFF;
+ if (tx == 0xD2) {
+ s->status1 = deposit32(s->status1,
+ STATUS1_FMPC, 1, 1);
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR, "Invalid confirmation data "
+ "byte(0x%x), expecting 0xD2", tx);
+ }
+ s->status0 = deposit32(s->status0, STATUS0_WEL, 1, 0);
+ s->status1 = deposit32(s->status1, STATUS1_PREL, 1, 0);
+ }
+ break;
+
+ case RDEX_CHLK:
+ if (!s->command_byte) {
+ SPI_DEBUG(qemu_log("OPCODE = (0x%x)\n", s->opcode));
+ rx = 0;
+ /* Address bit 10 must be 0 to read security register */
+ if (extract8(s->addr, 10, 1) == 0) {
+ uint16_t sidx;
+ /* RDEX */
+ s->rd_state = ST_SEC_READ;
+ for (idx = 0; idx < DATA_LEN; idx++) {
+ sidx = s->addr & 0x1FF;
+ if (sidx <= 0xFF) {
+ rx = (rx << 8) | s->dsn[sidx];
+ } else {
+ rx = (rx << 8) | s->uplid[sidx & 0xFF];
+ }
+ s->addr = (s->addr & ~0x1FF) | ((s->addr + 1) & 0x1FF);
+ }
+ } else {
+ /* CHLK */
+ if (s->locked) {
+ rx = rx | (0x01 << 24);
+ }
+ }
+ }
+ break;
+
+ case WREX_LOCK:
+ if (!s->command_byte) {
+ SPI_DEBUG(qemu_log("OPCODE = (0x%x)\n", s->opcode));
+ if (s->locked == true) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Device is already Locked, "
+ "command is ignored\n");
+ break;
+ }
+ if (extract8(s->status0, STATUS0_WEL, 1) != 1) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Device is not Write Enabled, "
+ "command is ignored\n");
+ break;
+ }
+ /* Address bit 10 must be 0 to write to security register */
+ if (extract8(s->addr, 10, 1) == 0) {
+ /* WREX */
+ for (idx = 0; idx < DATA_LEN; idx++) {
+ /*
+ * The Device Serial Number is factory programmed and
+ * read-only.
+ */
+ s->uplid[extract8(s->addr, 0, 8)] =
+ (tx >> (24 - idx * 8)) & 0xFF;
+ /* Increase address with the page, and let it rollover*/
+ s->addr = (s->addr & ~0xFF) | ((s->addr + 1) & 0xFF);
+ }
+ } else {
+ /*
+ * LOCK (82h) instruction is clocked in on the SI line,
+ * followed by a fake address where bits A[23:0] are don't
+ * care bits with the exception that bit A10 must be set to 1.
+ * Finally, a confirmation data byte of xxxx_xx1xb is sent
+ */
+ if (((tx >> 24) & 0x02) == 0x2) {
+ s->locked = true;
+ }
+ }
+ }
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "Invalid instruction(0x%x)\n",
+ s->opcode);
+ } /* end of switch */
+ if (s->command_byte) {
+ s->command_byte = false;
+ }
+ return rx;
+}
+
+/*
+ * Method : compute_addr
+ * This method is used to compute address and data offset for supported
+ * opcodes and only invoked when a valid new command sequence starts aka
+ * first is 1.
+ */
+bool compute_addr(SeepromCsm04 *s, uint32_t tx)
+{
+ bool addr_wr_protected = false;
+ bool failed = false;
+
+ switch (s->opcode) {
+ case READ:
+ case WRITE:
+ SPI_DEBUG(qemu_log("Compute address and payload buffer data offset "
+ "for %s\n", (s->opcode == READ) ? "READ" : "WRITE"));
+ /*
+ * Fetch address from size 24 bit from offset 1,2,3 of payload
+ * and mask of higher 5 bits as valid memory array size is 512KB
+ */
+ s->addr = tx & 0x7FFFF;
+ if (s->opcode == WRITE) {
+ addr_wr_protected = validate_addr(s);
+ if (addr_wr_protected) {
+ qemu_log_mask(LOG_GUEST_ERROR, "SEEPROM Address(0x%x) is Write "
+ "protected\n", s->addr);
+ failed = true;
+ }
+ }
+ break;
+ case RMPR:
+ case WMPR:
+ SPI_DEBUG(qemu_log("Compute MPR address for %s MPR\n",
+ (s->opcode == RMPR) ? "READ" : "WRITE"));
+ /*
+ * The address for each Memory Partition register is embedded into
+ * the first address byte sent to the device,in bit positions A18
+ * through A16.
+ */
+ s->addr = tx & 0x70000;
+ break;
+
+ case PPAB:
+ case FRZR:
+ SPI_DEBUG(qemu_log("Validate if addr[15:0] is %s\n",
+ (s->opcode == PPAB) ? "0xCCFF for PPAB" :
+ "0xAA40 for FRZR"));
+ /* Address bits A23-A16 are ignored. */
+ s->addr = tx & 0xFFFF;
+ /* Address bits A15-A0 must be set to CC55h. */
+ if ((s->opcode == PPAB) && s->addr != 0xCC55) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Invalid addr[15:0] = 0x%x sent for "
+ "PPAB\n", s->addr);
+ failed = true;
+ }
+ /* Address bits A15-A0 must be set to AA40h. */
+ if ((s->opcode == FRZR) && s->addr != 0xAA40) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Invalid addr[15:0] = 0x%x sent for "
+ "FRZR\n", s->addr);
+ failed = true;
+ }
+ break;
+
+ case RDEX_CHLK:
+ case WREX_LOCK:
+ SPI_DEBUG(qemu_log("Compute Address for Security reg command\n"));
+ /*
+ * RDEX : A[23:9] are don't care bits, except A10 which must be a
+ * logic 0.
+ * WREX : A[23:9] are don't care bits, except A10 which must be a
+ * logic 0 and A8 which must be a logic 1 to address the
+ * second Security register byte that is user programmable.
+ * CHLK : A[23:0] are don't care bits, except A10 which must be a
+ * logic 1.
+ * LOCK : A[23:0] are don't care bits, except A10 which must be a
+ * logic 1.
+ */
+ s->addr = tx & 0x5FF;
+ SPI_DEBUG(qemu_log("Received Command %s\n",
+ (s->opcode == RDEX_CHLK)
+ ? (extract32(s->addr, 10, 1) ?
+ "CHLK : Check Lock Status of Security Register" :
+ "RDEX : Read from the Security Register")
+ : (extract32(s->addr, 10, 1) ?
+ "LOCK : Lock the Security Register (permanent)" :
+ "WREX : Write to the Security Register")));
+ if ((s->opcode == WREX_LOCK) &&
+ (extract32(s->addr, 10, 1) == 0)) {
+ /*
+ * WREX
+ * In Legacy Write Protection mode, the Security register is
+ * write-protected when the BP <1:0> bits (bits 3-2 byte0) of
+ * the STATUS register = 11.
+ */
+ if (extract8(s->status1, STATUS1_WPM, 1) == 0) {
+ addr_wr_protected = validate_addr(s);
+ } else {
+ if (extract32(s->addr, 0, 9) <= 0xFF) {
+ addr_wr_protected = true;
+ }
+ }
+ if (addr_wr_protected) {
+ qemu_log_mask(LOG_GUEST_ERROR, "SEEPROM Address(0x%x) is "
+ "Write protected\n", s->addr);
+ failed = true;
+ }
+ }
+ break;
+ } /* end of switch */
+ return failed;
+} /* end of method compute_addr */
+
+/*
+ * Method : validate_addr
+ * This method validates whether SEEPROM address is write protected or not
+ */
+
+bool validate_addr(SeepromCsm04 *s)
+{
+ bool addr_wr_protected = false;
+ uint8_t mpr_idx = 0;
+
+ if (extract8(s->status1, STATUS1_WPM, 1) == 1) {
+ /*
+ * enhanced write protection
+ * Memory partition register Bit5 through bit0 contain the Partition
+ * Endpoint Address of A18:A13, where A12:A0 are a logic "1". For
+ * example, if the first partition of the memory array is desired to
+ * stop after 128-Kbit of memory, that end point address is 03FFFh. The
+ * corresponding A18:A13 address bits to be loaded into MPR0 are
+ * therefore 000001b. The eight MPRs are each decoded sequentially by
+ * the device, starting with MPR0. Each MPR should be set to a
+ * Partition Endpoint Address greater than the ending address of the
+ * previous MPR. If a higher order MPR sets a Partition Endpoint Address
+ * less than or equal to the ending address of a lower order MPR, that
+ * higher order MPR is ignored and no protection is set by it's
+ * contents.
+ */
+ for (mpr_idx = 0; mpr_idx < 8; mpr_idx++) {
+ if ((extract32(s->addr, 13, 6)) <=
+ (extract8(s->mpr[mpr_idx], MPR_PEA, 1))) {
+ switch (extract8(s->mpr[mpr_idx], MPR_PB, 2)) {
+ case 0:
+ /*
+ * 0b00 = Partition is open and writing is permitted
+ * (factory default is unprotected).
+ */
+ addr_wr_protected = false;
+ break;
+ case 1:
+ /*
+ * 0b01 = Partition is always write-protected but can be
+ * reversed at a later time (software write-protected).
+ */
+ addr_wr_protected = true;
+ break;
+ case 2:
+ /*
+ * 0b10 = Partition is write-protected only when WP pin is
+ * asserted (hardware write-protected).
+ */
+ addr_wr_protected = false;
+ break;
+ case 3:
+ /*
+ * 0b11 = Partition is software write-protected and Memory
+ * Partition register is permanently locked.
+ */
+ addr_wr_protected = true;
+ break;
+ } /* end of switch */
+ break; /* break from for loop. */
+ }
+ } /* end of for loop */
+ } else {
+ /* Legacy write protection mode */
+ switch (extract8(s->status0, STATUS0_BP, 2)) {
+ case 0:
+ /*
+ * 0b00 = No array write protection
+ * EEPROM None
+ * Security Register 00000h - 000FFh
+ */
+ if ((s->opcode == WREX_LOCK) &&
+ (extract32(s->addr, 0, 9) <= 0xFF)) {
+ addr_wr_protected = true;
+ }
+ break;
+ case 1:
+ /*
+ * 0b01 = Upper quarter memory array protection
+ * EEPROM 60000h - 7FFFFh
+ * Security Register 00000h - 000FFh
+ */
+ if ((s->opcode == WREX_LOCK) &&
+ (extract32(s->addr, 0, 9) <= 0xFF)) {
+ addr_wr_protected = true;
+ } else if ((s->opcode == WRITE) &&
+ (extract32(s->addr, 0, 19) <= 0x60000)) {
+ addr_wr_protected = true;
+ }
+ break;
+ case 2:
+ /*
+ * 0b10 = Upper half memory array protection
+ * EEPROM 40000h - 7FFFFh
+ * Security Register 00000h - 000FFh
+ */
+ if ((s->opcode == WREX_LOCK) &&
+ (extract32(s->addr, 0, 9) <= 0xFF)) {
+ addr_wr_protected = true;
+ } else if ((s->opcode == WRITE) &&
+ (extract32(s->addr, 0, 19) <= 0x40000)) {
+ addr_wr_protected = true;
+ }
+ break;
+ case 3:
+ /*
+ * 0b11 = Entire memory array protection
+ * EEPROM 00000h - 7FFFFh
+ * Security Register 00000h - 001FFh
+ */
+ addr_wr_protected = true;
+ break;
+ } /* end of switch */
+ }
+ return addr_wr_protected;
+} /* end of validate_addr */
+
+
+static int seeprom_cs(SSIPeripheral *ss, bool select)
+{
+ SeepromCsm04 *s = SEEPROM_25CSM04(ss);
+
+ if (select) {
+ s->command_byte = false;
+ s->rd_state = ST_IDLE;
+ s->status0 = deposit32(s->status0, STATUS0_WEL, 1, 0);
+ } else {
+ s->command_byte = true;
+ }
+ return 0;
+}
+
+
+void seeprom_realize(SSIPeripheral *dev, Error **errp)
+{
+ SeepromCsm04 *s = SEEPROM_25CSM04(dev);
+
+ s->command_byte = true;
+ s->rd_state = ST_IDLE;
+ if (s->file_name) {
+ s->file = qemu_find_file(QEMU_FILE_TYPE_BIOS, s->file_name);
+ }
+}
+
+static Property seeprom_props[] = {
+ DEFINE_PROP_STRING("filename", SeepromCsm04, file_name),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void seeprom_25csm04_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SSIPeripheralClass *k = SSI_PERIPHERAL_CLASS(klass);
+
+ k->transfer = seeprom_transfer;
+ k->realize = seeprom_realize;
+ k->set_cs = seeprom_cs;
+ k->cs_polarity = SSI_CS_LOW;
+ device_class_set_props(dc, seeprom_props);
+
+ dc->desc = "PowerNV SPI SEEPROM";
+}
+
+static const TypeInfo seeprom_25csm04_info = {
+ .name = TYPE_SEEPROM_25CSM04,
+ .parent = TYPE_SSI_PERIPHERAL,
+ .instance_size = sizeof(SeepromCsm04),
+ .class_init = seeprom_25csm04_class_init,
+};
+
+static void seeprom_25csm04_register_types(void)
+{
+ type_register_static(&seeprom_25csm04_info);
+}
+
+type_init(seeprom_25csm04_register_types);
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 1e08785b83..9442cc657d 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -38,6 +38,9 @@ config PCA9554
bool
depends on I2C
+config SEEPROM_25CSM04
+ bool
+
config I2C_ECHO
bool
default y if TEST_DEVICES
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 86596a3888..fd4d646f98 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -3,6 +3,7 @@ system_ss.add(when: 'CONFIG_EDU', if_true: files('edu.c'))
system_ss.add(when: 'CONFIG_FW_CFG_DMA', if_true: files('vmcoreinfo.c'))
system_ss.add(when: 'CONFIG_ISA_DEBUG', if_true: files('debugexit.c'))
system_ss.add(when: 'CONFIG_ISA_TESTDEV', if_true: files('pc-testdev.c'))
+system_ss.add(when: 'CONFIG_SEEPROM_25CSM04', if_true: files('seeprom_25csm04.c'))
system_ss.add(when: 'CONFIG_PCI_TESTDEV', if_true: files('pci-testdev.c'))
system_ss.add(when: 'CONFIG_UNIMP', if_true: files('unimp.c'))
system_ss.add(when: 'CONFIG_EMPTY_SLOT', if_true: files('empty_slot.c'))
diff --git a/hw/ppc/Kconfig b/hw/ppc/Kconfig
index ea1178bd73..6a4803d4ec 100644
--- a/hw/ppc/Kconfig
+++ b/hw/ppc/Kconfig
@@ -36,6 +36,7 @@ config POWERNV
select PCA9552
select PCA9554
select SSI
+ select SEEPROM_25CSM04
config PPC405
bool
--
2.39.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 5/6] hw/ppc: SPI controller wiring to P10 chip and create seeprom device
2024-04-09 17:56 [PATCH v2 0/6] hw/ppc: SPI model Chalapathi V
` (3 preceding siblings ...)
2024-04-09 17:56 ` [PATCH v2 4/6] hw/misc: Microchip's 25CSM04 SEEPROM model Chalapathi V
@ 2024-04-09 17:56 ` Chalapathi V
2024-04-22 15:03 ` Cédric Le Goater
2024-04-09 17:57 ` [PATCH v2 6/6] tests/qtest: Add pnv-spi-seeprom qtest Chalapathi V
5 siblings, 1 reply; 16+ messages in thread
From: Chalapathi V @ 2024-04-09 17:56 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-ppc, fbarrat, npiggin, clg, calebs, chalapathi.v,
chalapathi.v, saif.abrar, dantan
In this commit
Creates SPI controller on p10 chip.
Create the keystore seeprom of type "seeprom-25csm04"
Connect the cs of seeprom to PIB_SPIC[2] cs irq.
The QOM tree of spi controller and seeprom are.
/machine (powernv10-machine)
/chip[0] (power10_v2.0-pnv-chip)
/pib_spic[2] (pnv-spi-controller)
/bus (pnv-spi-bus)
/pnv-spi-bus.2 (SSI)
/xscom-spi-controller-regs[0] (memory-region)
/machine (powernv10-machine)
/unattached (container)
/device[7] (seeprom-25csm04)
/ssi-gpio-cs[0] (irq)
(qemu) qom-get /machine/unattached/device[7] "parent_bus"
"/machine/chip[0]/pib_spic[2]/bus/pnv-spi-bus.2"
Signed-off-by: Chalapathi V <chalapathi.v@linux.ibm.com>
---
include/hw/ppc/pnv_chip.h | 3 +++
hw/ppc/pnv.c | 36 +++++++++++++++++++++++++++++++++++-
2 files changed, 38 insertions(+), 1 deletion(-)
diff --git a/include/hw/ppc/pnv_chip.h b/include/hw/ppc/pnv_chip.h
index 8589f3291e..3edf13e8f9 100644
--- a/include/hw/ppc/pnv_chip.h
+++ b/include/hw/ppc/pnv_chip.h
@@ -6,6 +6,7 @@
#include "hw/ppc/pnv_core.h"
#include "hw/ppc/pnv_homer.h"
#include "hw/ppc/pnv_n1_chiplet.h"
+#include "hw/ppc/pnv_spi_controller.h"
#include "hw/ppc/pnv_lpc.h"
#include "hw/ppc/pnv_occ.h"
#include "hw/ppc/pnv_psi.h"
@@ -118,6 +119,8 @@ struct Pnv10Chip {
PnvSBE sbe;
PnvHomer homer;
PnvN1Chiplet n1_chiplet;
+#define PNV10_CHIP_MAX_PIB_SPIC 6
+ PnvSpiController pib_spic[PNV10_CHIP_MAX_PIB_SPIC];
uint32_t nr_quads;
PnvQuad *quads;
diff --git a/hw/ppc/pnv.c b/hw/ppc/pnv.c
index 6e3a5ccdec..eeb2d650bd 100644
--- a/hw/ppc/pnv.c
+++ b/hw/ppc/pnv.c
@@ -46,6 +46,7 @@
#include "hw/pci-host/pnv_phb.h"
#include "hw/pci-host/pnv_phb3.h"
#include "hw/pci-host/pnv_phb4.h"
+#include "hw/ssi/ssi.h"
#include "hw/ppc/xics.h"
#include "hw/qdev-properties.h"
@@ -1829,6 +1830,11 @@ static void pnv_chip_power10_instance_init(Object *obj)
for (i = 0; i < pcc->i2c_num_engines; i++) {
object_initialize_child(obj, "i2c[*]", &chip10->i2c[i], TYPE_PNV_I2C);
}
+
+ for (i = 0; i < PNV10_CHIP_MAX_PIB_SPIC ; i++) {
+ object_initialize_child(obj, "pib_spic[*]", &chip10->pib_spic[i],
+ TYPE_PNV_SPI_CONTROLLER);
+ }
}
static void pnv_chip_power10_quad_realize(Pnv10Chip *chip10, Error **errp)
@@ -2043,7 +2049,35 @@ static void pnv_chip_power10_realize(DeviceState *dev, Error **errp)
qdev_get_gpio_in(DEVICE(&chip10->psi),
PSIHB9_IRQ_SBE_I2C));
}
-
+ /* PIB SPI Controller */
+ for (i = 0; i < PNV10_CHIP_MAX_PIB_SPIC; i++) {
+ object_property_set_int(OBJECT(&chip10->pib_spic[i]), "spic_num",
+ i , &error_fatal);
+ /*
+ * The TPM attached SPIC needs to reverse the bit order in each byte
+ * it sends to the TPM.
+ */
+ if (i == 4) {
+ object_property_set_bool(OBJECT(&chip10->pib_spic[i]),
+ "reverse_bits", true, &error_fatal);
+ }
+ if (!qdev_realize(DEVICE(&chip10->pib_spic[i]), NULL, errp)) {
+ return;
+ }
+ pnv_xscom_add_subregion(chip, PNV10_XSCOM_PIB_SPIC_BASE +
+ i * PNV10_XSCOM_PIB_SPIC_SIZE,
+ &chip10->pib_spic[i].xscom_spic_regs);
+ }
+
+ /* Primary MEAS/MVPD/Keystore SEEPROM connected to pib_spic[2] */
+ DeviceState *seeprom = qdev_new("seeprom-25csm04");
+ qdev_prop_set_string(seeprom, "filename",
+ "sbe_measurement_seeprom.bin.ecc");
+ ssi_realize_and_unref(seeprom, ((&chip10->pib_spic[2])->bus).ssi_bus,
+ &error_fatal);
+ qemu_irq seeprom_cs = qdev_get_gpio_in_named(seeprom, SSI_GPIO_CS, 0);
+ Object *bus = OBJECT(&(&chip10->pib_spic[2])->bus);
+ sysbus_connect_irq(SYS_BUS_DEVICE(bus), 0, seeprom_cs);
}
static void pnv_rainier_i2c_init(PnvMachineState *pnv)
--
2.39.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 6/6] tests/qtest: Add pnv-spi-seeprom qtest
2024-04-09 17:56 [PATCH v2 0/6] hw/ppc: SPI model Chalapathi V
` (4 preceding siblings ...)
2024-04-09 17:56 ` [PATCH v2 5/6] hw/ppc: SPI controller wiring to P10 chip and create seeprom device Chalapathi V
@ 2024-04-09 17:57 ` Chalapathi V
5 siblings, 0 replies; 16+ messages in thread
From: Chalapathi V @ 2024-04-09 17:57 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-ppc, fbarrat, npiggin, clg, calebs, chalapathi.v,
chalapathi.v, saif.abrar, dantan
In this commit Write a qtest pnv-spi-seeprom-test to check the
SPI transactions between spi controller and seeprom device.
Signed-off-by: Chalapathi V <chalapathi.v@linux.ibm.com>
---
tests/qtest/pnv-spi-seeprom-test.c | 126 +++++++++++++++++++++++++++++
tests/qtest/meson.build | 1 +
2 files changed, 127 insertions(+)
create mode 100644 tests/qtest/pnv-spi-seeprom-test.c
diff --git a/tests/qtest/pnv-spi-seeprom-test.c b/tests/qtest/pnv-spi-seeprom-test.c
new file mode 100644
index 0000000000..4f0fcb1ea3
--- /dev/null
+++ b/tests/qtest/pnv-spi-seeprom-test.c
@@ -0,0 +1,126 @@
+/*
+ * QTest testcase for PowerNV 10 Seeprom Communications
+ *
+ * Copyright (c) 2024, IBM Corporation.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include <unistd.h>
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/bswap.h"
+#include "hw/ppc/pnv_spi_controller_regs.h"
+
+#define P10_XSCOM_BASE 0x000603fc00000000ull
+#define SPIC2_XSCOM_BASE 0xc0040
+
+/* To transmit READ opcode and address */
+#define READ_OP_TDR_DATA 0x0300010000000000
+/*
+ * N1 shift - tx 4 bytes (transmit opcode and address)
+ * N2 shift - tx and rx 8 bytes.
+ */
+#define READ_OP_COUNTER_CONFIG 0x2040000000002b00
+/* SEQ_OP_SELECT_RESPONDER - N1 Shift - N2 Shift * 5 - SEQ_OP_STOP */
+#define READ_OP_SEQUENCER 0x1130404040404010
+
+/* To transmit WREN(Set Write Enable Latch in status0 register) opcode */
+#define WRITE_OP_WREN 0x0600000000000000
+/* To transmit WRITE opcode, address and data */
+#define WRITE_OP_TDR_DATA 0x0300010012345678
+/* N1 shift - tx 8 bytes (transmit opcode, address and data) */
+#define WRITE_OP_COUNTER_CONFIG 0x4000000000002000
+/* SEQ_OP_SELECT_RESPONDER - N1 Shift - SEQ_OP_STOP */
+#define WRITE_OP_SEQUENCER 0x1130100000000000
+
+static uint64_t pnv_xscom_addr(uint32_t pcba)
+{
+ return P10_XSCOM_BASE | ((uint64_t) pcba << 3);
+}
+
+static uint64_t pnv_spi_seeprom_xscom_addr(uint32_t reg)
+{
+ return pnv_xscom_addr(SPIC2_XSCOM_BASE + reg);
+}
+
+static void pnv_spi_controller_xscom_write(QTestState *qts, uint32_t reg,
+ uint64_t val)
+{
+ qtest_writeq(qts, pnv_spi_seeprom_xscom_addr(reg), val);
+}
+
+static uint64_t pnv_spi_controller_xscom_read(QTestState *qts, uint32_t reg)
+{
+ return qtest_readq(qts, pnv_spi_seeprom_xscom_addr(reg));
+}
+
+static void spi_seeprom_transaction(QTestState *qts)
+{
+ /* SPI transactions to SEEPROM to read from SEEPROM image */
+ pnv_spi_controller_xscom_write(qts, COUNTER_CONFIG_REG,
+ READ_OP_COUNTER_CONFIG);
+ pnv_spi_controller_xscom_write(qts, SEQUENCER_OPERATION_REG,
+ READ_OP_SEQUENCER);
+ pnv_spi_controller_xscom_write(qts, TRANSMIT_DATA_REG, READ_OP_TDR_DATA);
+ pnv_spi_controller_xscom_write(qts, TRANSMIT_DATA_REG, 0);
+ /* Read 5*8 bytes from SEEPROM at 0x100 */
+ uint64_t rdr_val = pnv_spi_controller_xscom_read(qts, RECEIVE_DATA_REG);
+ printf("RDR READ = 0x%lx\n", rdr_val);
+ rdr_val = pnv_spi_controller_xscom_read(qts, RECEIVE_DATA_REG);
+ rdr_val = pnv_spi_controller_xscom_read(qts, RECEIVE_DATA_REG);
+ rdr_val = pnv_spi_controller_xscom_read(qts, RECEIVE_DATA_REG);
+ rdr_val = pnv_spi_controller_xscom_read(qts, RECEIVE_DATA_REG);
+ printf("RDR READ = 0x%lx\n", rdr_val);
+
+ /* SPI transactions to SEEPROM to write to SEEPROM image */
+ pnv_spi_controller_xscom_write(qts, COUNTER_CONFIG_REG,
+ WRITE_OP_COUNTER_CONFIG);
+ /* Set Write Enable Latch bit of status0 register */
+ pnv_spi_controller_xscom_write(qts, SEQUENCER_OPERATION_REG,
+ WRITE_OP_SEQUENCER);
+ pnv_spi_controller_xscom_write(qts, TRANSMIT_DATA_REG, WRITE_OP_WREN);
+ /* write 8 bytes to SEEPROM at 0x100 */
+ pnv_spi_controller_xscom_write(qts, SEQUENCER_OPERATION_REG,
+ WRITE_OP_SEQUENCER);
+ pnv_spi_controller_xscom_write(qts, TRANSMIT_DATA_REG, WRITE_OP_TDR_DATA);
+}
+
+/* Find complete path of in_file in the current working directory */
+static void find_file(const char *in_file, char *in_path)
+{
+ g_autofree char *cwd = g_get_current_dir();
+ char *filepath = g_build_filename(cwd, in_file, NULL);
+ if (!access(filepath, F_OK)) {
+ strcpy(in_path, filepath);
+ } else {
+ strcpy(in_path, "");
+ printf("File %s not found within %s\n", in_file, cwd);
+ }
+}
+
+static void test_spi_seeprom(void)
+{
+ QTestState *qts = NULL;
+ char seepromfile[500];
+ find_file("sbe_measurement_seeprom.bin.ecc", seepromfile);
+ if (strcmp(seepromfile, "")) {
+ printf("Starting QEMU with seeprom file.\n");
+ qts = qtest_initf("-m 2G -machine powernv10 -smp 2,cores=2,"
+ "threads=1 -accel tcg,thread=single -nographic "
+ "-device loader,file=sbe_measurement_seeprom.bin.ecc"
+ ",addr=0x7000000");
+ } else {
+ printf("Starting QEMU without seeprom file.\n");
+ qts = qtest_initf("-m 2G -machine powernv10 -smp 2,cores=2,"
+ "threads=1 -accel tcg,thread=single -nographic");
+ }
+ spi_seeprom_transaction(qts);
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+ qtest_add_func("spi_seeprom", test_spi_seeprom);
+ return g_test_run();
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 36c5c13a7b..e95453022d 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -167,6 +167,7 @@ qtests_ppc64 = \
qtests_ppc + \
(config_all_devices.has_key('CONFIG_PSERIES') ? ['device-plug-test'] : []) + \
(config_all_devices.has_key('CONFIG_POWERNV') ? ['pnv-xscom-test'] : []) + \
+ (config_all_devices.has_key('CONFIG_POWERNV') ? ['pnv-spi-seeprom-test'] : []) + \
(config_all_devices.has_key('CONFIG_POWERNV') ? ['pnv-host-i2c-test'] : []) + \
(config_all_devices.has_key('CONFIG_PSERIES') ? ['rtas-test'] : []) + \
(slirp.found() ? ['pxe-test'] : []) + \
--
2.39.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH v2 1/6] hw/ppc: remove SPI responder model
2024-04-09 17:56 ` [PATCH v2 1/6] hw/ppc: remove SPI responder model Chalapathi V
@ 2024-04-15 14:46 ` Cédric Le Goater
0 siblings, 0 replies; 16+ messages in thread
From: Cédric Le Goater @ 2024-04-15 14:46 UTC (permalink / raw)
To: Chalapathi V, qemu-devel
Cc: qemu-ppc, fbarrat, npiggin, calebs, chalapathi.v, saif.abrar,
dantan
On 4/9/24 19:56, Chalapathi V wrote:
> -- Empty commit to align the patch numbers between PATCH v1 and PATCH v2.
> SPI responder model is removed as pnv spi controller and seeprom is
> implemented using QEMU SSI framework.
Please drop this empty patch. Patch numbers do not need to be aligned
between respins of the same patchset.
Thanks,
C.
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 2/6] hw/ppc: SPI controller model - registers implementation
2024-04-09 17:56 ` [PATCH v2 2/6] hw/ppc: SPI controller model - registers implementation Chalapathi V
@ 2024-04-15 15:14 ` Cédric Le Goater
2024-04-16 17:02 ` Chalapathi V
0 siblings, 1 reply; 16+ messages in thread
From: Cédric Le Goater @ 2024-04-15 15:14 UTC (permalink / raw)
To: Chalapathi V, qemu-devel
Cc: qemu-ppc, fbarrat, npiggin, calebs, chalapathi.v, saif.abrar,
dantan
Hello Chalapathi
The subject could be rephrased to : "ppc/pnv: Add SPI controller model".
On 4/9/24 19:56, Chalapathi V wrote:
> SPI controller device model supports a connection to a single SPI responder.
> This provide access to SPI seeproms, TPM, flash device and an ADC controller.
>
> All SPI function control is mapped into the SPI register space to enable full
> control by firmware. In this commit SPI configuration component is modelled
> which contains all SPI configuration and status registers as well as the hold
> registers for data to be sent or having been received.
>
> An existing QEMU SSI framework is used and SSI_BUS is created.
>
> Signed-off-by: Chalapathi V <chalapathi.v@linux.ibm.com>
> ---
> include/hw/ppc/pnv_spi_controller.h | 55 +++++
> include/hw/ppc/pnv_spi_controller_regs.h | 114 ++++++++++
These two files should be under hw/ssi/ and include/hw/ssi/. Please
remove '_controller'.
> include/hw/ppc/pnv_xscom.h | 3 +
> hw/ppc/pnv_spi_controller.c | 278 +++++++++++++++++++++++
> hw/ppc/Kconfig | 1 +
> hw/ppc/meson.build | 1 +
> 6 files changed, 452 insertions(+)
> create mode 100644 include/hw/ppc/pnv_spi_controller.h
> create mode 100644 include/hw/ppc/pnv_spi_controller_regs.h
> create mode 100644 hw/ppc/pnv_spi_controller.c
>
> diff --git a/include/hw/ppc/pnv_spi_controller.h b/include/hw/ppc/pnv_spi_controller.h
> new file mode 100644
> index 0000000000..5ec50fb14c
> --- /dev/null
> +++ b/include/hw/ppc/pnv_spi_controller.h
> @@ -0,0 +1,55 @@
> +/*
> + * QEMU PowerPC SPI Controller model
> + *
> + * Copyright (c) 2024, IBM Corporation.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * This model Supports a connection to a single SPI responder.
> + * Introduced for P10 to provide access to SPI seeproms, TPM, flash device
> + * and an ADC controller.
> + */
> +#include "hw/ssi/ssi.h"
> +
> +#ifndef PPC_PNV_SPI_CONTROLLER_H
> +#define PPC_PNV_SPI_CONTROLLER_H
> +
> +#define TYPE_PNV_SPI_CONTROLLER "pnv-spi-controller"
> +#define PNV_SPICONTROLLER(obj) \
> + OBJECT_CHECK(PnvSpiController, (obj), TYPE_PNV_SPI_CONTROLLER)
You could use OBJECT_DECLARE_SIMPLE_TYPE ? Anyhow, I would prefer
naming the macro PNV_SPI_CONTROLLER.
> +#define SPI_CONTROLLER_REG_SIZE 8
> +
> +typedef struct SSIBus SSIBus;
why ?
> +
> +#define TYPE_PNV_SPI_BUS "pnv-spi-bus"
> +OBJECT_DECLARE_SIMPLE_TYPE(PnvSPIBus, PNV_SPI_BUS)
> +
> +typedef struct PnvSPIBus {
I don't think this extra PnvSPIBus model is useful.
> + SysBusDevice parent_obj;
> +
> + SSIBus *ssi_bus;
> + qemu_irq *cs_line;
These two attributes could live under PnvSpiController.
> + uint32_t id;
and this one would become useless.
> +} PnvSPIBus;
>
> +typedef struct PnvSpiController {
> + DeviceState parent;
> +
> + PnvSPIBus bus;
> + MemoryRegion xscom_spic_regs;
> + /* SPI controller object number */
> + uint32_t spic_num;
> +
> + /* SPI Controller registers */
> + uint64_t error_reg;
> + uint64_t counter_config_reg;
> + uint64_t config_reg1;
> + uint64_t clock_config_reset_control;
> + uint64_t memory_mapping_reg;
> + uint64_t transmit_data_reg;
> + uint64_t receive_data_reg;
> + uint8_t sequencer_operation_reg[SPI_CONTROLLER_REG_SIZE];
> + uint64_t status_reg;
You could use an array of uint64_t also.
> +} PnvSpiController;
> +#endif /* PPC_PNV_SPI_CONTROLLER_H */
> diff --git a/include/hw/ppc/pnv_spi_controller_regs.h b/include/hw/ppc/pnv_spi_controller_regs.h
> new file mode 100644
> index 0000000000..6f613aca5e
> --- /dev/null
> +++ b/include/hw/ppc/pnv_spi_controller_regs.h
> @@ -0,0 +1,114 @@
> +/*
> + * QEMU PowerPC SPI Controller model
> + *
> + * Copyright (c) 2023, IBM Corporation.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef SPI_CONTROLLER_REGS_H
> +#define SPI_CONTROLLER_REGS_H
> +
> +/* Error Register */
> +#define ERROR_REG 0x00
> +
> +/* counter_config_reg */
> +#define COUNTER_CONFIG_REG 0x01
> +#define COUNTER_CONFIG_REG_SHIFT_COUNT_N1 PPC_BITMASK(0, 7)
> +#define COUNTER_CONFIG_REG_SHIFT_COUNT_N2 PPC_BITMASK(8, 15)
> +#define COUNTER_CONFIG_REG_COUNT_COMPARE1 PPC_BITMASK(24, 31)
> +#define COUNTER_CONFIG_REG_COUNT_COMPARE2 PPC_BITMASK(32, 39)
> +#define COUNTER_CONFIG_REG_N1_COUNT_CONTROL PPC_BITMASK(48, 51)
> +#define COUNTER_CONFIG_REG_N2_COUNT_CONTROL PPC_BITMASK(52, 55)
> +
> +/* config_reg */
> +#define CONFIG_REG1 0x02
> +
> +/* clock_config_reset_control_ecc_enable_reg */
> +#define CLOCK_CONFIG_REG 0x03
> +#define CLOCK_CONFIG_RESET_CONTROL_HARD_RESET 0x0084000000000000;
> +#define CLOCK_CONFIG_REG_RESET_CONTROL PPC_BITMASK(24, 27)
> +#define CLOCK_CONFIG_REG_ECC_CONTROL PPC_BITMASK(28, 30)
> +
> +/* memory_mapping_reg */
> +#define MEMORY_MAPPING_REG 0x04
> +#define MEMORY_MAPPING_REG_MMSPISM_BASE_ADDR PPC_BITMASK(0, 15)
> +#define MEMORY_MAPPING_REG_MMSPISM_ADDR_MASK PPC_BITMASK(16, 31)
> +#define MEMORY_MAPPING_REG_RDR_MATCH_VAL PPC_BITMASK(32, 47)
> +#define MEMORY_MAPPING_REG_RDR_MATCH_MASK PPC_BITMASK(48, 63)
> +
> +/* transmit_data_reg */
> +#define TRANSMIT_DATA_REG 0x05
> +
> +/* receive_data_reg */
> +#define RECEIVE_DATA_REG 0x06
> +
> +/* sequencer_operation_reg */
> +#define SEQUENCER_OPERATION_REG 0x07
> +
> +/* status_reg */
> +#define STATUS_REG 0x08
> +#define STATUS_REG_RDR_FULL PPC_BIT(0)
> +#define STATUS_REG_RDR_OVERRUN PPC_BIT(1)
> +#define STATUS_REG_RDR_UNDERRUN PPC_BIT(2)
> +#define STATUS_REG_TDR_FULL PPC_BIT(4)
> +#define STATUS_REG_TDR_OVERRUN PPC_BIT(5)
> +#define STATUS_REG_TDR_UNDERRUN PPC_BIT(6)
> +#define STATUS_REG_SEQUENCER_FSM PPC_BITMASK(8, 15)
> +#define STATUS_REG_SHIFTER_FSM PPC_BITMASK(16, 27)
> +#define STATUS_REG_SEQUENCER_INDEX PPC_BITMASK(28, 31)
> +#define STATUS_REG_GENERAL_SPI_STATUS PPC_BITMASK(32, 63)
> +#define STATUS_REG_RDR PPC_BITMASK(1, 3)
> +#define STATUS_REG_TDR PPC_BITMASK(5, 7)
> +
> +/*
> + * Shifter states
> + *
> + * These are the same values defined for the Shifter FSM field of the
> + * status register. It's a 12 bit field so we will represent it as three
> + * nibbles in the constants.
> + *
> + * These are shifter_fsm values
> + *
> + * Status reg bits 16-27 -> field bits 0-11
> + * bits 0,1,2,5 unused/reserved
> + * bit 4 crc shift in (unused)
> + * bit 8 crc shift out (unused)
> + */
> +
> +#define FSM_DONE 0x100 /* bit 3 */
> +#define FSM_SHIFT_N2 0x020 /* bit 6 */
> +#define FSM_WAIT 0x010 /* bit 7 */
> +#define FSM_SHIFT_N1 0x004 /* bit 9 */
> +#define FSM_START 0x002 /* bit 10 */
> +#define FSM_IDLE 0x001 /* bit 11 */
> +
> +/*
> + * Sequencer states
> + *
> + * These are sequencer_fsm values
> + *
> + * Status reg bits 8-15 -> field bits 0-7
> + * bits 0-3 unused/reserved
> + *
> + */
> +#define SEQ_STATE_INDEX_INCREMENT 0x08 /* bit 4 */
> +#define SEQ_STATE_EXECUTE 0x04 /* bit 5 */
> +#define SEQ_STATE_DECODE 0x02 /* bit 6 */
> +#define SEQ_STATE_IDLE 0x01 /* bit 7 */
> +
> +/*
> + * These are the supported sequencer operations.
> + * Only the upper nibble is significant because for many operations
> + * the lower nibble is a variable specific to the operation.
> + */
> +#define SEQ_OP_STOP 0x00
> +#define SEQ_OP_SELECT_SLAVE 0x10
> +#define SEQ_OP_SHIFT_N1 0x30
> +#define SEQ_OP_SHIFT_N2 0x40
> +#define SEQ_OP_BRANCH_IFNEQ_RDR 0x60
> +#define SEQ_OP_TRANSFER_TDR 0xC0
> +#define SEQ_OP_BRANCH_IFNEQ_INC_1 0xE0
> +#define SEQ_OP_BRANCH_IFNEQ_INC_2 0xF0
> +
> +#endif
> diff --git a/include/hw/ppc/pnv_xscom.h b/include/hw/ppc/pnv_xscom.h
> index 6209e18492..a77b97f9b1 100644
> --- a/include/hw/ppc/pnv_xscom.h
> +++ b/include/hw/ppc/pnv_xscom.h
> @@ -194,6 +194,9 @@ struct PnvXScomInterfaceClass {
> #define PNV10_XSCOM_PEC_PCI_BASE 0x8010800 /* index goes upwards ... */
> #define PNV10_XSCOM_PEC_PCI_SIZE 0x200
>
> +#define PNV10_XSCOM_PIB_SPIC_BASE 0xc0000
> +#define PNV10_XSCOM_PIB_SPIC_SIZE 0x20
> +
> void pnv_xscom_init(PnvChip *chip, uint64_t size, hwaddr addr);
> int pnv_dt_xscom(PnvChip *chip, void *fdt, int root_offset,
> uint64_t xscom_base, uint64_t xscom_size,
> diff --git a/hw/ppc/pnv_spi_controller.c b/hw/ppc/pnv_spi_controller.c
> new file mode 100644
> index 0000000000..e2478a47f2
> --- /dev/null
> +++ b/hw/ppc/pnv_spi_controller.c
> @@ -0,0 +1,278 @@
> +/*
> + * QEMU PowerPC SPI Controller model
> + *
> + * Copyright (c) 2024, IBM Corporation.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/log.h"
> +#include "hw/qdev-properties.h"
> +#include "hw/ppc/pnv_xscom.h"
> +#include "hw/ppc/pnv_spi_controller.h"
> +#include "hw/ppc/pnv_spi_controller_regs.h"
> +#include "hw/ssi/ssi.h"
> +#include "hw/ppc/fdt.h"
> +#include <libfdt.h>
> +#include <math.h>
> +#include "hw/irq.h"
> +
> +#define SPI_DEBUG(x)
> +
> +static uint64_t pnv_spi_controller_read(void *opaque, hwaddr addr,
> + unsigned size)
> +{
> + PnvSpiController *sc = PNV_SPICONTROLLER(opaque);
The name 'sc' makes you think of a class. 's' is common in QEMU models.
> + uint32_t reg = addr >> 3;
> + uint64_t val = ~0ull;
> +
> + switch (reg) {
> + case ERROR_REG:
> + val = sc->error_reg;
> + break;
> + case COUNTER_CONFIG_REG:
> + val = sc->counter_config_reg;
> + break;
> + case CONFIG_REG1:
> + val = sc->config_reg1;
> + break;
> + case CLOCK_CONFIG_REG:
> + val = sc->clock_config_reset_control;
> + break;
> + case MEMORY_MAPPING_REG:
> + val = sc->memory_mapping_reg;
> + break;
> + case TRANSMIT_DATA_REG:
> + val = sc->transmit_data_reg;
> + break;
> + case RECEIVE_DATA_REG:
> + val = sc->receive_data_reg;
> + SPI_DEBUG(qemu_log("RDR being read, data extracted = 0x%16.16lx\n",
> + val));
please use trace events instead of the SPI_DEBUG macro.
> + sc->status_reg = SETFIELD(STATUS_REG_RDR_FULL, sc->status_reg, 0);
> + SPI_DEBUG(qemu_log("RDR being read, RDR_full set to 0\n"));
> + break;
> + case SEQUENCER_OPERATION_REG:
> + val = 0;
> + for (int i = 0; i < SPI_CONTROLLER_REG_SIZE; i++) {
> + val = (val << 8) | sc->sequencer_operation_reg[i];
> + }
> + break;
> + case STATUS_REG:
> + val = sc->status_reg;
> + break;
> + default:
> + qemu_log_mask(LOG_GUEST_ERROR, "spi_controller_regs: Invalid xscom "
> + "read at 0x%08x\n", reg);
> + }
> + return val;
> +}
> +
> +static void pnv_spi_controller_write(void *opaque, hwaddr addr,
> + uint64_t val, unsigned size)
> +{
> + PnvSpiController *sc = PNV_SPICONTROLLER(opaque);
> + uint32_t reg = addr >> 3;
> +
> + switch (reg) {
> + case ERROR_REG:
> + sc->error_reg = val;
> + break;
> + case COUNTER_CONFIG_REG:
> + sc->counter_config_reg = val;
> + break;
> + case CONFIG_REG1:
> + sc->config_reg1 = val;
> + break;
> + case CLOCK_CONFIG_REG:
> + /*
> + * To reset the SPI controller write the sequence 0x5 0xA to
> + * reset_control field
> + */
> + if (GETFIELD(CLOCK_CONFIG_REG_RESET_CONTROL,
> + sc->clock_config_reset_control) == 0x5) {
> + if (GETFIELD(CLOCK_CONFIG_REG_RESET_CONTROL, val) == 0xA) {
> + SPI_DEBUG(qemu_log("SPI controller reset sequence completed, "
> + "resetting..."));
> + sc->clock_config_reset_control =
> + CLOCK_CONFIG_RESET_CONTROL_HARD_RESET;
> + } else {
> + sc->clock_config_reset_control = val;
> + }
> + } else {
> + sc->clock_config_reset_control = val;
> + }
> + break;
> + case MEMORY_MAPPING_REG:
> + sc->memory_mapping_reg = val;
> + break;
> + case TRANSMIT_DATA_REG:
> + /*
> + * Writing to the transmit data register causes the transmit data
> + * register full status bit in the status register to be set. Writing
> + * when the transmit data register full status bit is already set
> + * causes a "Resource Not Available" condition. This is not possible
> + * in the model since writes to this register are not asynchronous to
> + * the operation sequence like it would be in hardware.
> + */
> + sc->transmit_data_reg = val;
> + SPI_DEBUG(qemu_log("TDR being written, data written = 0x%16.16lx\n",
> + val));
> + sc->status_reg = SETFIELD(STATUS_REG_TDR_FULL, sc->status_reg, 1);
> + SPI_DEBUG(qemu_log("TDR being written, TDR_full set to 1\n"));
> + sc->status_reg = SETFIELD(STATUS_REG_TDR_UNDERRUN, sc->status_reg, 0);
> + SPI_DEBUG(qemu_log("TDR being written, TDR_underrun set to 0\n"));
> + SPI_DEBUG(qemu_log("TDR being written, starting sequencer\n"));
> + break;
> + case RECEIVE_DATA_REG:
> + sc->receive_data_reg = val;
> + break;
> + case SEQUENCER_OPERATION_REG:
> + for (int i = 0; i < SPI_CONTROLLER_REG_SIZE; i++) {
> + sc->sequencer_operation_reg[i] = (val >> (56 - i * 8)) & 0xFF;
> + }
> + break;
> + case STATUS_REG:
> + /* other fields are ignore_write */
> + sc->status_reg = SETFIELD(STATUS_REG_RDR_OVERRUN, sc->status_reg,
> + GETFIELD(STATUS_REG_RDR, val));
> + sc->status_reg = SETFIELD(STATUS_REG_TDR_OVERRUN, sc->status_reg,
> + GETFIELD(STATUS_REG_TDR, val));
> + break;
> + default:
> + qemu_log_mask(LOG_GUEST_ERROR, "spi_controller_regs: Invalid xscom "
> + "write at 0x%08x\n", reg);
> + }
> + return;
> +}
> +
> +static const MemoryRegionOps pnv_spi_controller_xscom_ops = {
> + .read = pnv_spi_controller_read,
> + .write = pnv_spi_controller_write,
> + .valid.min_access_size = 8,
> + .valid.max_access_size = 8,
> + .impl.min_access_size = 8,
> + .impl.max_access_size = 8,
> + .endianness = DEVICE_BIG_ENDIAN,
> +};
> +
> +static void pnv_spi_bus_realize(DeviceState *dev, Error **errp)
> +{
> + PnvSPIBus *s = PNV_SPI_BUS(dev);
> + SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
> + g_autofree char *name = g_strdup_printf(TYPE_PNV_SPI_BUS ".%d", s->id);
> +
> + s->ssi_bus = ssi_create_bus(dev, name);
> + s->cs_line = g_new0(qemu_irq, 1);
> + sysbus_init_irq(sbd, &s->cs_line[0]);
> +}
> +
> +static Property pnv_spi_bus_properties[] = {
> + DEFINE_PROP_UINT32("bus-id", PnvSPIBus, id, 0),
> + DEFINE_PROP_END_OF_LIST(),
> +};
> +
> +static void pnv_spi_bus_class_init(ObjectClass *klass, void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(klass);
> +
> + dc->desc = "Pnv SPI Bus";
> + dc->realize = pnv_spi_bus_realize;
> + device_class_set_props(dc, pnv_spi_bus_properties);
> +}
> +
> +static const TypeInfo pnv_spi_bus_info = {
> + .name = TYPE_PNV_SPI_BUS,
> + .parent = TYPE_SYS_BUS_DEVICE,
> + .instance_size = sizeof(PnvSPIBus),
> + .class_init = pnv_spi_bus_class_init,
> +};
> +
> +static Property pnv_spi_controller_properties[] = {
> + DEFINE_PROP_UINT32("spic_num", PnvSpiController, spic_num, 0),
> + DEFINE_PROP_END_OF_LIST(),
> +};
> +
> +static void pnv_spi_controller_realize(DeviceState *dev, Error **errp)
> +{
> + PnvSpiController *sc = PNV_SPICONTROLLER(dev);
> +
> + Object *bus = OBJECT(&sc->bus);
> + if (!object_property_set_int(bus, "bus-id", sc->spic_num, errp)) {
> + return;
> + }
> +
> + if (!sysbus_realize(SYS_BUS_DEVICE(bus), errp)) {
> + return;
> + }
> +
> + /* spi controller scoms */
> + pnv_xscom_region_init(&sc->xscom_spic_regs, OBJECT(sc),
> + &pnv_spi_controller_xscom_ops, sc,
> + "xscom-spi-controller-regs",
> + PNV10_XSCOM_PIB_SPIC_SIZE);
> +}
> +
> +static int pnv_spi_controller_dt_xscom(PnvXScomInterface *dev, void *fdt,
> + int offset)
> +{
> + PnvSpiController *sc = PNV_SPICONTROLLER(dev);
> + g_autofree char *name;
> + int sc_offset;
> + const char compat[] = "ibm,power10-spi_controller";
> + uint32_t spic_pcba = PNV10_XSCOM_PIB_SPIC_BASE +
> + sc->spic_num * PNV10_XSCOM_PIB_SPIC_SIZE;
> + uint32_t reg[] = {
> + cpu_to_be32(spic_pcba),
> + cpu_to_be32(PNV10_XSCOM_PIB_SPIC_SIZE)
> + };
> + name = g_strdup_printf("spi_controller@%x", spic_pcba);
> + sc_offset = fdt_add_subnode(fdt, offset, name);
> + _FDT(sc_offset);
> +
> + _FDT(fdt_setprop(fdt, sc_offset, "reg", reg, sizeof(reg)));
> + _FDT(fdt_setprop(fdt, sc_offset, "compatible", compat, sizeof(compat)));
> + _FDT((fdt_setprop_cell(fdt, sc_offset, "spic_num#", sc->spic_num)));
> + return 0;
> +}
> +
> +static void pnv_spi_instance_init(Object *obj)
> +{
> + PnvSpiController *sc = PNV_SPICONTROLLER(obj);
> +
> + /* Initialise the bus object */
> + object_initialize_child(obj, "bus", &sc->bus, TYPE_PNV_SPI_BUS);
> +}
> +
> +static void pnv_spi_controller_class_init(ObjectClass *klass, void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(klass);
> + PnvXScomInterfaceClass *xscomc = PNV_XSCOM_INTERFACE_CLASS(klass);
> +
> + xscomc->dt_xscom = pnv_spi_controller_dt_xscom;
> +
> + dc->desc = "PowerNV SPI Controller";
> + dc->realize = pnv_spi_controller_realize;
> + device_class_set_props(dc, pnv_spi_controller_properties);
> +}
> +
> +static const TypeInfo pnv_spi_controller_info = {
> + .name = TYPE_PNV_SPI_CONTROLLER,
> + .parent = TYPE_DEVICE,
> + .instance_init = pnv_spi_instance_init,
> + .instance_size = sizeof(PnvSpiController),
> + .class_init = pnv_spi_controller_class_init,
> + .interfaces = (InterfaceInfo[]) {
> + { TYPE_PNV_XSCOM_INTERFACE },
> + { }
> + }
> +};
> +
> +static void pnv_spi_controller_register_types(void)
> +{
> + type_register_static(&pnv_spi_bus_info);
> + type_register_static(&pnv_spi_controller_info);
> +}
> +
> +type_init(pnv_spi_controller_register_types);
> diff --git a/hw/ppc/Kconfig b/hw/ppc/Kconfig
> index 37ccf9cdca..ea1178bd73 100644
> --- a/hw/ppc/Kconfig
> +++ b/hw/ppc/Kconfig
> @@ -35,6 +35,7 @@ config POWERNV
> select PCI_POWERNV
> select PCA9552
> select PCA9554
> + select SSI
>
> config PPC405
> bool
> diff --git a/hw/ppc/meson.build b/hw/ppc/meson.build
> index d096636ee7..68fadbae7b 100644
> --- a/hw/ppc/meson.build
> +++ b/hw/ppc/meson.build
> @@ -56,6 +56,7 @@ ppc_ss.add(when: 'CONFIG_POWERNV', if_true: files(
> 'pnv_pnor.c',
> 'pnv_nest_pervasive.c',
> 'pnv_n1_chiplet.c',
> + 'pnv_spi_controller.c',
> ))
> # PowerPC 4xx boards
> ppc_ss.add(when: 'CONFIG_PPC405', if_true: files(
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 3/6] hw/ppc: SPI controller model - sequencer and shifter
2024-04-09 17:56 ` [PATCH v2 3/6] hw/ppc: SPI controller model - sequencer and shifter Chalapathi V
@ 2024-04-16 9:39 ` Cédric Le Goater
2024-04-16 17:08 ` Chalapathi V
0 siblings, 1 reply; 16+ messages in thread
From: Cédric Le Goater @ 2024-04-16 9:39 UTC (permalink / raw)
To: Chalapathi V, qemu-devel
Cc: qemu-ppc, fbarrat, npiggin, calebs, chalapathi.v, saif.abrar,
dantan
Hello,
Please rephrase the subject to something like:
"ppc/pnv: Extend SPI model ..."
Using a verb is preferable.
On 4/9/24 19:56, Chalapathi V wrote:
> In this commit SPI shift engine and sequencer logic is implemented.
> Shift engine performs serialization and de-serialization according to the
> control by the sequencer and according to the setup defined in the
> configuration registers. Sequencer implements the main control logic and
> FSM to handle data transmit and data receive control of the shift engine.
>
> Signed-off-by: Chalapathi V <chalapathi.v@linux.ibm.com>
> ---
> include/hw/ppc/pnv_spi_controller.h | 72 ++
> hw/ppc/pnv_spi_controller.c | 1311 ++++++++++++++++++++++++++-
> 2 files changed, 1382 insertions(+), 1 deletion(-)
>
> diff --git a/include/hw/ppc/pnv_spi_controller.h b/include/hw/ppc/pnv_spi_controller.h
> index 5ec50fb14c..ee8e7a17da 100644
> --- a/include/hw/ppc/pnv_spi_controller.h
> +++ b/include/hw/ppc/pnv_spi_controller.h
> @@ -8,6 +8,14 @@
> * This model Supports a connection to a single SPI responder.
> * Introduced for P10 to provide access to SPI seeproms, TPM, flash device
> * and an ADC controller.
> + *
> + * All SPI function control is mapped into the SPI register space to enable
> + * full control by firmware.
> + *
> + * SPI Controller has sequencer and shift engine. The SPI shift engine
> + * performs serialization and de-serialization according to the control by
> + * the sequencer and according to the setup defined in the configuration
> + * registers and the SPI sequencer implements the main control logic.
> */
> #include "hw/ssi/ssi.h"
>
> @@ -21,6 +29,7 @@
> #define SPI_CONTROLLER_REG_SIZE 8
>
> typedef struct SSIBus SSIBus;
> +typedef struct xfer_buffer xfer_buffer;
Please use CamelCase names for typedef. The forward declaration doesn't
seem useful.
> #define TYPE_PNV_SPI_BUS "pnv-spi-bus"
> OBJECT_DECLARE_SIMPLE_TYPE(PnvSPIBus, PNV_SPI_BUS)
> @@ -33,6 +42,21 @@ typedef struct PnvSPIBus {
> uint32_t id;
> } PnvSPIBus;
>
> +/* xfer_buffer */
> +typedef struct xfer_buffer {
> +
> + uint32_t len;
> + uint8_t *data;
> +
> +} xfer_buffer;
> +
> +uint8_t *xfer_buffer_write_ptr(xfer_buffer *payload, uint32_t offset,
> + uint32_t length);
> +void xfer_buffer_read_ptr(xfer_buffer *payload, uint8_t **read_buf,
> + uint32_t offset, uint32_t length);
> +xfer_buffer *xfer_buffer_new(void);
> +void xfer_buffer_free(xfer_buffer *payload);
> +
I don't think these helper routines need to be defined in the header file
of the PnvPsi model. They look internal to me.
> typedef struct PnvSpiController {
> DeviceState parent;
>
> @@ -40,6 +64,39 @@ typedef struct PnvSpiController {
> MemoryRegion xscom_spic_regs;
> /* SPI controller object number */
> uint32_t spic_num;
> + uint8_t responder_select;
> + /* To verify if shift_n1 happens prior to shift_n2 */
> + bool shift_n1_done;
> + /*
> + * Internal flags for the first and last indicators for the SPI
> + * interface methods
> + */
> + uint8_t first;
> + uint8_t last;
> + /* Loop counter for branch operation opcode Ex/Fx */
> + uint8_t loop_counter_1;
> + uint8_t loop_counter_2;
> + /* N1/N2_bits specifies the size of the N1/N2 segment of a frame in bits.*/
> + uint8_t N1_bits;
> + uint8_t N2_bits;
> + /* Number of bytes in a payload for the N1/N2 frame segment.*/
> + uint8_t N1_bytes;
> + uint8_t N2_bytes;
> + /* Number of N1/N2 bytes marked for transmit */
> + uint8_t N1_tx;
> + uint8_t N2_tx;
> + /* Number of N1/N2 bytes marked for receive */
> + uint8_t N1_rx;
> + uint8_t N2_rx;
> + /*
> + * Setting this attribute to true will cause the engine to reverse the
> + * bit order of each byte it appends to a payload before sending the
> + * payload to a device. There may be cases where an end device expects
> + * a reversed order, like in the case of the Nuvoton TPM device. The
> + * order of bytes in the payload is not reversed, only the order of the
> + * 8 bits in each payload byte.
> + */
> + bool reverse_bits;
>
> /* SPI Controller registers */
> uint64_t error_reg;
> @@ -52,4 +109,19 @@ typedef struct PnvSpiController {
> uint8_t sequencer_operation_reg[SPI_CONTROLLER_REG_SIZE];
> uint64_t status_reg;
> } PnvSpiController;
> +
> +void log_all_N_counts(PnvSpiController *spi_controller);
> +void spi_response(PnvSpiController *spi_controller, int bits,
> + xfer_buffer *rsp_payload);
> +void operation_sequencer(PnvSpiController *spi_controller);
> +bool operation_shiftn1(PnvSpiController *spi_controller, uint8_t opcode,
> + xfer_buffer **payload, bool send_n1_alone);
> +bool operation_shiftn2(PnvSpiController *spi_controller, uint8_t opcode,
> + xfer_buffer **payload);
> +bool does_rdr_match(PnvSpiController *spi_controller);
> +uint8_t get_from_offset(PnvSpiController *spi_controller, uint8_t offset);
> +void shift_byte_in(PnvSpiController *spi_controller, uint8_t byte);
> +void calculate_N1(PnvSpiController *spi_controller, uint8_t opcode);
> +void calculate_N2(PnvSpiController *spi_controller, uint8_t opcode);
> +uint8_t reverse_bits8(uint8_t x);
These routines are internal and belong to hw/ppc/pnv_spi_controller.c. Please
don't export them. Also, adding a pnv_psi_ prefix would be a plus to identify
the sympbols.
> #endif /* PPC_PNV_SPI_CONTROLLER_H */
> diff --git a/hw/ppc/pnv_spi_controller.c b/hw/ppc/pnv_spi_controller.c
> index e2478a47f2..afe7f17565 100644
> --- a/hw/ppc/pnv_spi_controller.c
> +++ b/hw/ppc/pnv_spi_controller.c
> @@ -52,6 +52,11 @@ static uint64_t pnv_spi_controller_read(void *opaque, hwaddr addr,
> val));
> sc->status_reg = SETFIELD(STATUS_REG_RDR_FULL, sc->status_reg, 0);
> SPI_DEBUG(qemu_log("RDR being read, RDR_full set to 0\n"));
> + if (GETFIELD(STATUS_REG_SHIFTER_FSM, sc->status_reg) == FSM_WAIT) {
> + SPI_DEBUG(qemu_log("RDR being read while shifter is waiting, "
> + "starting sequencer\n"));
Please use trace events instead.
> + operation_sequencer(sc);
> + }
> break;
> case SEQUENCER_OPERATION_REG:
> val = 0;
> @@ -124,13 +129,15 @@ static void pnv_spi_controller_write(void *opaque, hwaddr addr,
> sc->status_reg = SETFIELD(STATUS_REG_TDR_UNDERRUN, sc->status_reg, 0);
> SPI_DEBUG(qemu_log("TDR being written, TDR_underrun set to 0\n"));
> SPI_DEBUG(qemu_log("TDR being written, starting sequencer\n"));
> + operation_sequencer(sc);
> +
> break;
> case RECEIVE_DATA_REG:
> sc->receive_data_reg = val;
> break;
> case SEQUENCER_OPERATION_REG:
> for (int i = 0; i < SPI_CONTROLLER_REG_SIZE; i++) {
> - sc->sequencer_operation_reg[i] = (val >> (56 - i * 8)) & 0xFF;
> + sc->sequencer_operation_reg[i] = (val >> (56 - i * 8)) & 0xFF;
This change belongs to the previous patch.
> }
> break;
> case STATUS_REG:
> @@ -157,6 +164,1306 @@ static const MemoryRegionOps pnv_spi_controller_xscom_ops = {
> .endianness = DEVICE_BIG_ENDIAN,
> };
>
> +/* xfer_buffer_methods */
> +xfer_buffer *xfer_buffer_new(void)
> +{
> + xfer_buffer *payload = g_malloc0(sizeof(*payload));
> +
> + payload->data = g_malloc0(payload->len);
euh. payload->len is 0. This allocation is pointless.
> + return payload;
> +}
> +
> +void xfer_buffer_free(xfer_buffer *payload)
> +{
> + free(payload->data);
> + free(payload);
> +}
> +
> +uint8_t *xfer_buffer_write_ptr(xfer_buffer *payload, uint32_t offset,
> + uint32_t length)
> +{
> + if (payload->len < (offset + length)) {
> + payload->len = offset + length;
> + payload->data = g_realloc(payload->data, payload->len);
> + }
> + return &payload->data[offset];
> +}
> +
> +void xfer_buffer_read_ptr(xfer_buffer *payload, uint8_t **read_buf,
> + uint32_t offset, uint32_t length)
> +{
> + static uint32_t prev_len;
> + if ((prev_len != length) || (*read_buf == NULL)) {
> + *read_buf = g_realloc(*read_buf, length);
> + prev_len = length;
> + }
> + if (offset > payload->len) {
> + /* Reading outside payload, just return */
> + return;
> + }
> + *read_buf = &payload->data[offset];
> +}
Isn't there a maximum buffer size ? It would simplify the implementation
and avoid all these alloc/realloc/free calls.
> +uint8_t reverse_bits8(uint8_t x)
> +{
> + x = (x << 4) | (x >> 4);
> + x = ((x & 0x33) << 2) | ((x & 0xcc) >> 2);
> + x = ((x & 0x55) << 1) | ((x & 0xaa) >> 1);
> + return x;
> +}
revbit8() in qemu/host-utils.h should do the same.
The rest is difficult to comment on without an intimitate knowledge of
the controller and the datasheet. Can teammates help ?
Thanks,
C.
> +bool does_rdr_match(PnvSpiController *sc)
> +{
> + /*
> + * According to spec, the mask bits that are 0 are compared and the
> + * bits that are 1 are ignored.
> + */
> + uint16_t rdr_match_mask = GETFIELD(MEMORY_MAPPING_REG_RDR_MATCH_MASK,
> + sc->memory_mapping_reg);
> + uint16_t rdr_match_val = GETFIELD(MEMORY_MAPPING_REG_RDR_MATCH_VAL,
> + sc->memory_mapping_reg);
> + if ((~rdr_match_mask & rdr_match_val) == ((~rdr_match_mask) &
> + GETFIELD(PPC_BITMASK(48, 63), sc->receive_data_reg))) {
> + SPI_DEBUG(qemu_log("RDR match successful, match=0x%4.4x, "
> + "mask=0x%4.4x, RDR[48:63]=0x%4.4llx\n",
> + rdr_match_val, rdr_match_mask,
> + GETFIELD(PPC_BITMASK(48, 63),
> + sc->receive_data_reg)));
> + return true;
> + } else {
> + SPI_DEBUG(qemu_log("RDR match failed, match=0x%4.4x, mask=0x%4.4x, "
> + "RDR[48:63]=0x%4.4llx\n", rdr_match_val, rdr_match_mask,
> + GETFIELD(PPC_BITMASK(48, 63), sc->receive_data_reg)));
> + return false;
> + }
> +}
> +
> +uint8_t get_from_offset(PnvSpiController *sc, uint8_t offset)
> +{
> + uint8_t byte;
> +
> + /*
> + * Offset is an index between 0 and SPI_CONTROLLER_REG_SIZE - 1
> + * Check the offset before using it.
> + */
> + if (offset < SPI_CONTROLLER_REG_SIZE) {
> + byte = (sc->transmit_data_reg >> (56 - offset * 8)) & 0xFF;
> + } else {
> + /*
> + * Log an error and return a 0xFF since we have to assign something
> + * to byte before returning.
> + */
> + qemu_log_mask(LOG_GUEST_ERROR, "Invalid offset = %d used to get byte "
> + "from TDR\n", offset);
> + byte = 0xff;
> + }
> + return byte;
> +}
> +
> +void shift_byte_in(PnvSpiController *sc, uint8_t byte)
> +{
> + sc->receive_data_reg = (sc->receive_data_reg << 8) | byte;
> + SPI_DEBUG(qemu_log("0x%2.2x shifted in, RDR now = 0x%16.16lx\n", byte,
> + sc->receive_data_reg));
> +}
> +
> +void spi_response(PnvSpiController *sc, int bits, xfer_buffer *rsp_payload)
> +{
> + uint8_t *read_buf = NULL;
> + uint8_t ecc_count;
> + uint8_t shift_in_count;
> +
> + /*
> + * Processing here must handle:
> + * - Which bytes in the payload we should move to the RDR
> + * - Explicit mode counter configuration settings
> + * - RDR full and RDR overrun status
> + */
> +
> + /*
> + * First check that the response payload is the exact same
> + * number of bytes as the request payload was
> + */
> + if (rsp_payload->len != (sc->N1_bytes + sc->N2_bytes)) {
> + qemu_log_mask(LOG_GUEST_ERROR, "Invalid response payload size in "
> + "bytes, expected %d, got %d\n",
> + (sc->N1_bytes + sc->N2_bytes), rsp_payload->len);
> + } else {
> + uint8_t ecc_control;
> + SPI_DEBUG(qemu_log("SPI response received, payload len = %d\n",
> + rsp_payload->len));
> + log_all_N_counts(sc);
> + /*
> + * Adding an ECC count let's us know when we have found a payload byte
> + * that was shifted in but cannot be loaded into RDR. Bits 29-30
> + * equal to either 0b00 or 0b10 indicate that we are taking in data
> + * with ECC and either applying the ECC or discarding it.
> + */
> + ecc_count = 0;
> + ecc_control = GETFIELD(PPC_BITMASK(29, 30),
> + sc->clock_config_reset_control);
> + if (ecc_control == 0 || ecc_control == 2) {
> + ecc_count = 1;
> + }
> + /*
> + * Use the N1_rx and N2_rx counts to control shifting data from the
> + * payload into the RDR. Keep an overall count of the number of bytes
> + * shifted into RDR so we can discard every 9th byte when ECC is
> + * enabled.
> + */
> + shift_in_count = 0;
> + /* Handle the N1 portion of the frame first */
> + if (sc->N1_rx != 0) {
> + uint8_t n1_count = 0;
> + while (n1_count < sc->N1_bytes) {
> + shift_in_count++;
> + xfer_buffer_read_ptr(rsp_payload, &read_buf, n1_count, 1);
> + if ((ecc_count != 0) &&
> + (shift_in_count == (SPI_CONTROLLER_REG_SIZE + ecc_count))) {
> + SPI_DEBUG(qemu_log("Discarding rx N1 ECC byte = 0x%2.2x at "
> + "payload index = %d\n", read_buf[0], n1_count));
> + shift_in_count = 0;
> + } else {
> + uint8_t n1_byte = 0x00;
> + n1_byte = read_buf[0];
> + SPI_DEBUG(qemu_log("Extracting rx n1_byte = 0x%2.2x from "
> + "payload at index = %d\n", n1_byte, n1_count));
> + if (sc->reverse_bits) {
> + SPI_DEBUG(qemu_log("Reversing bit order of rx "
> + "n1_byte\n"));
> + n1_byte = reverse_bits8(n1_byte);
> + }
> + SPI_DEBUG(qemu_log("Shifting rx N1 byte = 0x%2.2x into "
> + "RDR\n", n1_byte));
> + shift_byte_in(sc, n1_byte);
> + }
> + n1_count++;
> + } /* end of while */
> + }
> + /* Handle the N2 portion of the frame */
> + if (sc->N2_rx != 0) {
> + uint8_t n2_count = 0;
> + while (n2_count < sc->N2_bytes) {
> + shift_in_count++;
> + xfer_buffer_read_ptr(rsp_payload, &read_buf,
> + (sc->N1_bytes + n2_count), 1);
> + if ((ecc_count != 0) &&
> + (shift_in_count == (SPI_CONTROLLER_REG_SIZE + ecc_count))) {
> + SPI_DEBUG(qemu_log("Discarding rx N1 ECC byte = 0x%2.2x at "
> + "payload index = %d\n", read_buf[0],
> + (sc->N1_bytes + n2_count)));
> + shift_in_count = 0;
> + } else {
> + /*
> + * The code handles shifting data from the payload received
> + * from the responder into the responder's RDR. Since this
> + * is an N2 frame segment it is safe to assume that there
> + * was a preceding N1 segment which was combined with an N2
> + * segment to create a single frame. The response data will
> + * then have N1_bytes of data in the payload representing a
> + * responder response to the N1 section of the frame. If N2
> + * is set to receive the shifting for N2 data begins after
> + * the N1 bytes regardless of whether or not N1 was marked
> + * for transmit or receive.
> + */
> + uint8_t n2_byte = 0x00;
> + n2_byte = read_buf[0];
> + SPI_DEBUG(qemu_log("Extracting rx n2_byte = 0x%2.2x from "
> + "payload at index = %d\n", n2_byte,
> + (sc->N1_bytes + n2_count)));
> + if (sc->reverse_bits) {
> + SPI_DEBUG(qemu_log("Reversing bit order of rx "
> + "n2_byte\n"));
> + n2_byte = reverse_bits8(n2_byte);
> + }
> + SPI_DEBUG(qemu_log("Shifting rx N2 byte = 0x%2.2x into "
> + "RDR\n", n2_byte));
> + shift_byte_in(sc, n2_byte);
> + }
> + n2_count++;
> + }
> + }
> + if ((sc->N1_rx + sc->N2_rx) > 0) {
> + /*
> + * Data was received so handle RDR status.
> + * It is easier to handle RDR_full and RDR_overrun status here
> + * since the RDR register's shift_byte_in method is called
> + * multiple times in a row. Controlling RDR status is done here
> + * instead of in the RDR scoped methods for that reason.
> + */
> + if (GETFIELD(STATUS_REG_RDR_FULL, sc->status_reg) == 1) {
> + /*
> + * Data was shifted into the RDR before having been read
> + * causing previous data to have been overrun.
> + */
> + sc->status_reg = SETFIELD(STATUS_REG_RDR_OVERRUN,
> + sc->status_reg, 1);
> + } else {
> + /*
> + * Set status to indicate that the received data register is
> + * full. This flag is only cleared once the RDR is unloaded.
> + */
> + sc->status_reg = SETFIELD(STATUS_REG_RDR_FULL,
> + sc->status_reg, 1);
> + SPI_DEBUG(qemu_log("RDR_full set to 1\n"));
> + }
> + }
> + } /* end of else */
> +} /* end of spi_response() */
> +
> +void log_all_N_counts(PnvSpiController *sc)
> +{
> + SPI_DEBUG(qemu_log("N1_bits = %d, N1_bytes = %d, N1_tx = %d, N1_rx = %d, "
> + "N2_bits = %d, N2_bytes = %d, N2_tx = %d, N2_rx = %d\n",
> + sc->N1_bits, sc->N1_bytes, sc->N1_tx, sc->N1_rx, sc->N2_bits,
> + sc->N2_bytes, sc->N2_tx, sc->N2_rx));
> +}
> +
> +static inline void next_sequencer_fsm(PnvSpiController *sc)
> +{
> + uint8_t seq_index = GETFIELD(STATUS_REG_SEQUENCER_INDEX, sc->status_reg);
> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_INDEX, sc->status_reg,
> + (seq_index + 1));
> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM, sc->status_reg,
> + SEQ_STATE_INDEX_INCREMENT);
> +}
> +
> +void operation_sequencer(PnvSpiController *sc)
> +{
> + /*
> + * Loop through each sequencer operation ID and perform the requested
> + * operations.
> + * Flag for indicating if we should send the N1 frame or wait to combine
> + * it with a preceding N2 frame.
> + */
> + bool send_n1_alone = true;
> + bool stop = false; /* Flag to stop the sequencer */
> + uint8_t opcode = 0;
> + uint8_t masked_opcode = 0;
> +
> + /*
> + * xfer_buffer for containing the payload of the SPI frame.
> + * This is a static because there are cases where a sequence has to stop
> + * and wait for the target application to unload the RDR. If this occurs
> + * during a sequence where N1 is not sent alone and instead combined with
> + * N2 since the N1 tx length + the N2 tx length is less than the size of
> + * the TDR.
> + */
> + static xfer_buffer *payload;
> +
> + if (payload == NULL) {
> + payload = xfer_buffer_new();
> + }
> + /*
> + * Clear the sequencer FSM error bit - general_SPI_status[3]
> + * before starting a sequence.
> + */
> + sc->status_reg = SETFIELD(PPC_BIT(35), sc->status_reg, 0);
> + /*
> + * If the FSM is idle set the sequencer index to 0
> + * (new/restarted sequence)
> + */
> + if (GETFIELD(STATUS_REG_SEQUENCER_FSM, sc->status_reg) ==
> + SEQ_STATE_IDLE) {
> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_INDEX,
> + sc->status_reg, 0);
> + }
> + /*
> + * There are only 8 possible operation IDs to iterate through though
> + * some operations may cause more than one frame to be sequenced.
> + */
> + while (GETFIELD(STATUS_REG_SEQUENCER_INDEX, sc->status_reg) < 8) {
> + opcode = sc->sequencer_operation_reg[GETFIELD(
> + STATUS_REG_SEQUENCER_INDEX, sc->status_reg)];
> + /* Set sequencer state to decode */
> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM, sc->status_reg,
> + SEQ_STATE_DECODE);
> + /*
> + * Only the upper nibble of the operation ID is needed to know what
> + * kind of operation is requested.
> + */
> + masked_opcode = opcode & 0xF0;
> + switch (masked_opcode) {
> + /*
> + * Increment the operation index in each case instead of just
> + * once at the end in case an operation like the branch
> + * operation needs to change the index.
> + */
> + case SEQ_OP_STOP:
> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
> + sc->status_reg, SEQ_STATE_EXECUTE);
> + /* A stop operation in any position stops the sequencer */
> + SPI_DEBUG(qemu_log("Sequencer STOP at index = 0x%llx, sequencer "
> + "idling\n", GETFIELD(
> + STATUS_REG_SEQUENCER_INDEX,
> + sc->status_reg)));
> + stop = true;
> + sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM, sc->status_reg,
> + FSM_IDLE);
> + sc->loop_counter_1 = 0;
> + sc->loop_counter_2 = 0;
> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
> + sc->status_reg, SEQ_STATE_IDLE);
> + break;
> +
> + case SEQ_OP_SELECT_SLAVE:
> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
> + sc->status_reg, SEQ_STATE_EXECUTE);
> + SPI_DEBUG(qemu_log("Sequencer SELECT_SLAVE at index = 0x%llx\n",
> + GETFIELD(STATUS_REG_SEQUENCER_INDEX, sc->status_reg)));
> + /*
> + * This device currently only supports a single responder
> + * connection at position 0. De-selecting a responder is fine
> + * and expected at the end of a sequence but selecting any
> + * responder other than 0 should cause an error.
> + */
> + sc->responder_select = opcode & 0x0F;
> + if (sc->responder_select == 0) {
> + SPI_DEBUG(qemu_log("Shifter done, pull the CS line high\n"));
> + qemu_set_irq((sc->bus).cs_line[0], 1);
> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_INDEX,
> + sc->status_reg,
> + (GETFIELD(
> + STATUS_REG_SEQUENCER_INDEX,
> + sc->status_reg) + 1));
> + sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM,
> + sc->status_reg, FSM_DONE);
> + } else if (sc->responder_select != 1) {
> + qemu_log_mask(LOG_GUEST_ERROR, "Slave selection other than 1 "
> + "not supported, select = 0x%x\n",
> + sc->responder_select);
> + SPI_DEBUG(qemu_log("Sequencer stop requested due to invalid "
> + "responder select at index = 0x%llx, "
> + "shifter idling\n", GETFIELD(
> + STATUS_REG_SEQUENCER_INDEX,
> + sc->status_reg)));
> + sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM,
> + sc->status_reg, FSM_IDLE);
> + stop = true;
> + } else {
> + /*
> + * Only allow an FSM_START state when a responder is
> + * selected
> + */
> + sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM,
> + sc->status_reg, FSM_START);
> + SPI_DEBUG(qemu_log("shifter starting, pull CS line low\n"));
> + qemu_set_irq((sc->bus).cs_line[0], 0);
> + sc->first = 1;
> + sc->last = 0;
> + /*
> + * A Shift_N2 operation is only valid after a Shift_N1
> + * according to the spec. The spec doesn't say if that means
> + * immediately after or just after at any point. We will track
> + * the occurrence of a Shift_N1 to enforce this requirement in
> + * the most generic way possible by assuming that the rule
> + * applies once a valid responder select has occurred.
> + */
> + sc->shift_n1_done = false;
> + next_sequencer_fsm(sc);
> + }
> + break;
> +
> + case SEQ_OP_SHIFT_N1:
> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
> + sc->status_reg, SEQ_STATE_EXECUTE);
> + SPI_DEBUG(qemu_log("Sequencer SHIFT_N1 at index = 0x%llx\n",
> + GETFIELD(STATUS_REG_SEQUENCER_INDEX, sc->status_reg)));
> + /*
> + * Only allow a shift_n1 when the state is not IDLE or DONE.
> + * In either of those two cases the sequencer is not in a proper
> + * state to perform shift operations because the sequencer has:
> + * - processed a responder deselect (DONE)
> + * - processed a stop opcode (IDLE)
> + * - encountered an error (IDLE)
> + */
> + if ((GETFIELD(STATUS_REG_SHIFTER_FSM,
> + sc->status_reg) == FSM_IDLE) ||
> + (GETFIELD(STATUS_REG_SHIFTER_FSM,
> + sc->status_reg) == FSM_DONE)) {
> + qemu_log_mask(LOG_GUEST_ERROR, "Shift_N1 not allowed in "
> + "shifter state = 0x%llx", GETFIELD(
> + STATUS_REG_SHIFTER_FSM, sc->status_reg));
> + /*
> + * Set sequencer FSM error bit 3 (general_SPI_status[3])
> + * in status reg.
> + */
> + sc->status_reg = SETFIELD(PPC_BIT(35), sc->status_reg, 1);
> + SPI_DEBUG(qemu_log("Sequencer stop requested due to invalid "
> + "shifter state at index = 0x%llx\n", GETFIELD(
> + STATUS_REG_SEQUENCER_INDEX, sc->status_reg)));
> + stop = true;
> + } else {
> + /*
> + * Look for the special case where there is a shift_n1 set for
> + * transmit and it is followed by a shift_n2 set for transmit
> + * AND the combined transmit length of the two operations is
> + * less than or equal to the size of the TDR register. In this
> + * case we want to use both this current shift_n1 opcode and the
> + * following shift_n2 opcode to assemble the frame for
> + * transmission to the responder without requiring a refill of
> + * the TDR between the two operations.
> + */
> + if ((sc->sequencer_operation_reg[GETFIELD(
> + STATUS_REG_SEQUENCER_INDEX, sc->status_reg) + 1] & 0xF0)
> + == SEQ_OP_SHIFT_N2) {
> + SPI_DEBUG(qemu_log("Not sending N1 alone\n"));
> + send_n1_alone = false;
> + }
> + /*
> + * If the next opcode is 0x10, which deselects the SPI device
> + * then this is the last shift
> + */
> + if (sc->sequencer_operation_reg[GETFIELD(
> + STATUS_REG_SEQUENCER_INDEX, sc->status_reg) + 1] ==
> + SEQ_OP_SELECT_SLAVE) {
> + sc->last = 1;
> + }
> + sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM,
> + sc->status_reg, FSM_SHIFT_N1);
> + stop = operation_shiftn1(sc, opcode, &payload, send_n1_alone);
> + if (stop) {
> + /*
> + * The operation code says to stop, this can occur if:
> + * (1) RDR is full and the N1 shift is set for receive
> + * (2) TDR was empty at the time of the N1 shift so we need
> + * to wait for data.
> + * (3) Neither 1 nor 2 are occurring and we aren't sending
> + * N1 alone and N2 counter reload is set (bit 0 of the N2
> + * counter reload field). In this case TDR_underrun will
> + * will be set and the Payload has been loaded so it is
> + * ok to advance the sequencer.
> + */
> + if (GETFIELD(STATUS_REG_TDR_UNDERRUN, sc->status_reg)) {
> + SPI_DEBUG(qemu_log("Sequencer stop requested due to N2 "
> + "counter reload active.\n"));
> + sc->shift_n1_done = true;
> + sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM,
> + sc->status_reg,
> + FSM_SHIFT_N2);
> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_INDEX,
> + sc->status_reg,
> + (GETFIELD(
> + STATUS_REG_SEQUENCER_INDEX,
> + sc->status_reg) + 1));
> + SPI_DEBUG(qemu_log("Set new sequencer index to = "
> + "0x%llx\n", GETFIELD(
> + STATUS_REG_SEQUENCER_INDEX,
> + sc->status_reg)));
> + } else {
> + /*
> + * This is case (1) or (2) so the sequencer needs to
> + * wait and NOT go to the next sequence yet.
> + */
> + sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM,
> + sc->status_reg, FSM_WAIT);
> + }
> + } else {
> + /* Ok to move on to the next index */
> + sc->shift_n1_done = true;
> + next_sequencer_fsm(sc);
> + }
> + }
> + break;
> +
> + case SEQ_OP_SHIFT_N2:
> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
> + sc->status_reg, SEQ_STATE_EXECUTE);
> + SPI_DEBUG(qemu_log("Sequencer SHIFT_N2 at index = %lld\n",
> + GETFIELD(STATUS_REG_SEQUENCER_INDEX,
> + sc->status_reg)));
> + if (!sc->shift_n1_done) {
> + qemu_log_mask(LOG_GUEST_ERROR, "Shift_N2 is not allowed if a "
> + "Shift_N1 is not done, shifter state = 0x%llx",
> + GETFIELD(STATUS_REG_SHIFTER_FSM,
> + sc->status_reg));
> + /*
> + * In case the sequencer actually stops if an N2 shift is
> + * requested before any N1 shift is done. Set sequencer FSM
> + * error bit 3 (general_SPI_status[3]) in status reg.
> + */
> + sc->status_reg = SETFIELD(PPC_BIT(35), sc->status_reg, 1);
> + SPI_DEBUG(qemu_log("Sequencer stop requested due to shift_n2 "
> + "w/no shift_n1 done at index = 0x%llx\n",
> + GETFIELD(STATUS_REG_SEQUENCER_INDEX,
> + sc->status_reg)));
> + stop = true;
> + } else {
> + /*
> + * If the next opcode is 0x10, which deselects the SPI device
> + * then this is the last shift
> + */
> + if (sc->sequencer_operation_reg[GETFIELD(
> + STATUS_REG_SEQUENCER_INDEX,
> + sc->status_reg) + 1] == SEQ_OP_SELECT_SLAVE) {
> + sc->last = 1;
> + }
> + /* Ok to do a Shift_N2 */
> + sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM,
> + sc->status_reg, FSM_SHIFT_N2);
> + stop = operation_shiftn2(sc, opcode, &payload);
> + /*
> + * If the operation code says to stop set the shifter state to
> + * wait and stop
> + */
> + if (stop) {
> + sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM,
> + sc->status_reg, FSM_WAIT);
> + } else {
> + /* Ok to move on to the next index */
> + next_sequencer_fsm(sc);
> + }
> + }
> + break;
> +
> + case SEQ_OP_BRANCH_IFNEQ_RDR:
> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
> + sc->status_reg, SEQ_STATE_EXECUTE);
> + SPI_DEBUG(qemu_log("Sequencer BRANCH_IFNEQ_RDR at "
> + "index = 0x%llx\n", GETFIELD(
> + STATUS_REG_SEQUENCER_INDEX, sc->status_reg)));
> + /*
> + * The memory mapping register RDR match value is compared against
> + * the 16 rightmost bytes of the RDR (potentially with masking).
> + * Since this comparison is performed against the contents of the
> + * RDR then a receive must have previously occurred otherwise
> + * there is no data to compare and the operation cannot be
> + * completed and will stop the sequencer until RDR full is set to
> + * 1.
> + */
> + if (GETFIELD(STATUS_REG_RDR_FULL, sc->status_reg) == 1) {
> + bool rdr_matched = false;
> + rdr_matched = does_rdr_match(sc);
> + if (rdr_matched) {
> + SPI_DEBUG(qemu_log("Proceed to next sequencer index "
> + "(increment on RDR match)\n"));
> + /* A match occurred, increment the sequencer index. */
> + next_sequencer_fsm(sc);
> + } else {
> + SPI_DEBUG(qemu_log("Proceed to sequencer index=0x%x "
> + "(branch on RDR match fail)\n", (opcode & 0x7)));
> + /*
> + * Branch the sequencer to the index coded into the op
> + * code.
> + */
> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_INDEX,
> + sc->status_reg, (opcode & 0x7));
> + }
> + /*
> + * Regardless of where the branch ended up we want the
> + * sequencer to continue shifting so we have to clear
> + * RDR_full.
> + */
> + sc->status_reg = SETFIELD(STATUS_REG_RDR_FULL,
> + sc->status_reg, 0);
> + } else {
> + SPI_DEBUG(qemu_log("RDR not full for 0x6x opcode! Stopping "
> + "sequencer.\n"));
> + stop = true;
> + sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM,
> + sc->status_reg, FSM_WAIT);
> + }
> + break;
> +
> + case SEQ_OP_TRANSFER_TDR:
> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
> + sc->status_reg, SEQ_STATE_EXECUTE);
> + qemu_log_mask(LOG_GUEST_ERROR, "Transfer TDR is not supported\n");
> + next_sequencer_fsm(sc);
> + break;
> +
> + case SEQ_OP_BRANCH_IFNEQ_INC_1:
> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
> + sc->status_reg, SEQ_STATE_EXECUTE);
> + SPI_DEBUG(qemu_log("Sequencer BRANCH_IFNEQ_INC_1 at index = "
> + "0x%llx, next index = %d, count_compare_1 = "
> + "0x%llx, loop_counter_1 = %d\n", GETFIELD(
> + STATUS_REG_SEQUENCER_INDEX, sc->status_reg),
> + (opcode & 0x07),
> + GETFIELD(COUNTER_CONFIG_REG_COUNT_COMPARE1,
> + sc->status_reg), sc->loop_counter_1));
> + /*
> + * The spec says the loop should execute count compare + 1 times.
> + * However we learned from engineering that we really only loop
> + * count_compare times, count compare = 0 makes this op code a
> + * no-op
> + */
> + if (sc->loop_counter_1 !=
> + GETFIELD(COUNTER_CONFIG_REG_COUNT_COMPARE1,
> + sc->counter_config_reg)) {
> + /*
> + * If the next opcode is 0x10, which deselects the SPI device
> + * and we know that the next opcode is the last one in the
> + * loop then the next shift is the last shift
> + */
> + uint8_t condition1 = sc->sequencer_operation_reg[
> + GETFIELD(STATUS_REG_SEQUENCER_INDEX,
> + sc->status_reg) + 1];
> + uint8_t condition2 = GETFIELD(COUNTER_CONFIG_REG_COUNT_COMPARE1,
> + sc->counter_config_reg);
> +
> + if ((condition1 == SEQ_OP_SELECT_SLAVE) &&
> + ((sc->loop_counter_1 + 1) == condition2)) {
> + sc->last = 1;
> + }
> + /*
> + * Next index is the lower nibble of the branch operation ID,
> + * mask off all but the first three bits so we don't try to
> + * access beyond the sequencer_operation_reg boundary.
> + */
> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_INDEX,
> + sc->status_reg, (opcode & 0x7));
> + sc->loop_counter_1++;
> + SPI_DEBUG(qemu_log("Branching to index = %d, loop_counter_1 = "
> + "%d\n", (opcode & 0x7), sc->loop_counter_1));
> + } else {
> + /* Continue to next index if loop counter is reached */
> + next_sequencer_fsm(sc);
> + SPI_DEBUG(qemu_log("loop counter 1 achieved, next sequencer "
> + "index = 0x%llx\n", GETFIELD(STATUS_REG_SEQUENCER_INDEX,
> + sc->status_reg)));
> + }
> + break;
> +
> + case SEQ_OP_BRANCH_IFNEQ_INC_2:
> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
> + sc->status_reg, SEQ_STATE_EXECUTE);
> + SPI_DEBUG(qemu_log("Sequencer BRANCH_IFNEQ_INC_2 at index = "
> + "0x%llx, next index = %d, count_compare_2 = "
> + "0x%llx, loop_counter_2 = %d\n", GETFIELD(
> + STATUS_REG_SEQUENCER_INDEX, sc->status_reg),
> + (opcode & 0x07), GETFIELD(
> + COUNTER_CONFIG_REG_COUNT_COMPARE2,
> + sc->status_reg), sc->loop_counter_2));
> + /*
> + * If the next opcode is 0x10, which deselects the SPI device
> + * and we know that the next opcode is the last one in the
> + * loop then the next shift is the last shift
> + */
> + uint8_t condition1 = sc->sequencer_operation_reg[
> + GETFIELD(STATUS_REG_SEQUENCER_INDEX,
> + sc->status_reg) + 1];
> + uint8_t condition2 = GETFIELD(COUNTER_CONFIG_REG_COUNT_COMPARE2,
> + sc->counter_config_reg);
> +
> + if ((condition1 == SEQ_OP_SELECT_SLAVE) &&
> + ((sc->loop_counter_2 + 1) == condition2)) {
> + sc->last = 1;
> + }
> + /*
> + * The spec says the loop should execute count compare + 1 times.
> + * However we learned from engineering that we really only loop
> + * count_compare times, count compare = 0 makes this op code a
> + * no-op
> + */
> + if (sc->loop_counter_2 != condition2) {
> + /*
> + * Next index is the lower nibble of the branch operation ID,
> + * mask off all but the first three bits so we don't try to
> + * access beyond the sequencer_operation_reg boundary.
> + */
> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_INDEX,
> + sc->status_reg,
> + (opcode & 0x7));
> + sc->loop_counter_2++;
> + SPI_DEBUG(qemu_log("Branching to index = %d, loop_counter_2 "
> + "= %d", (opcode & 0x7),
> + sc->loop_counter_2));
> + } else {
> + /* Continue to next index if loop counter is reached */
> + next_sequencer_fsm(sc);
> + SPI_DEBUG(qemu_log("loop counter 2 achieved, next sequencer "
> + "index = 0x%llx\n", GETFIELD(STATUS_REG_SEQUENCER_INDEX,
> + sc->status_reg)));
> + }
> + break;
> +
> + default:
> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
> + sc->status_reg, SEQ_STATE_EXECUTE);
> + qemu_log_mask(LOG_GUEST_ERROR, "Sequencer opcode 0x%x is not "
> + "supported\n", opcode);
> + /* Ignore unsupported operations. */
> + next_sequencer_fsm(sc);
> + break;
> + } /* end of switch */
> + /*
> + * If we used all 8 opcodes without seeing a 00 - STOP in the sequence
> + * we need to go ahead and end things as if there was a STOP at the
> + * end.
> + */
> + if (GETFIELD(STATUS_REG_SEQUENCER_INDEX, sc->status_reg) == 8) {
> + SPI_DEBUG(qemu_log("All 8 opcodes completed, sequencer "
> + "idling\n"));
> + sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM, sc->status_reg,
> + FSM_IDLE);
> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_INDEX,
> + sc->status_reg, 0);
> + sc->loop_counter_1 = 0;
> + sc->loop_counter_2 = 0;
> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
> + sc->status_reg, SEQ_STATE_IDLE);
> + break;
> + }
> + /* Break the loop if a stop was requested */
> + if (stop) {
> + break;
> + }
> + } /* end of while */
> + return;
> +} /* end of operation_sequencer() */
> +
> +/*
> + * Calculate the N1 counters based on passed in opcode and
> + * internal register values.
> + * The method assumes that the opcode is a Shift_N1 opcode
> + * and doesn't test it.
> + * The counters returned are:
> + * N1 bits: Number of bits in the payload data that are significant
> + * to the responder.
> + * N1_bytes: Total count of payload bytes for the N1 (portion of the) frame.
> + * N1_tx: Total number of bytes taken from TDR for N1
> + * N1_rx: Total number of bytes taken from the payload for N1
> + */
> +void calculate_N1(PnvSpiController *sc, uint8_t opcode)
> +{
> + /*
> + * Shift_N1 opcode form: 0x3M
> + * Implicit mode:
> + * If M != 0 the shift count is M bytes and M is the number of tx bytes.
> + * Forced Implicit mode:
> + * M is the shift count but tx and rx is determined by the count control
> + * register fields. Note that we only check for forced Implicit mode when
> + * M != 0 since the mode doesn't make sense when M = 0.
> + * Explicit mode:
> + * If M == 0 then shift count is number of bits defined in the
> + * Counter Configuration Register's shift_count_N1 field.
> + */
> + if (GETFIELD(PPC_BITMASK8(4, 7), opcode) == 0) {
> + /* Explicit mode */
> + sc->N1_bits = GETFIELD(COUNTER_CONFIG_REG_SHIFT_COUNT_N1,
> + sc->counter_config_reg);
> + sc->N1_bytes = ceil(sc->N1_bits / 8);
> + sc->N1_tx = 0;
> + sc->N1_rx = 0;
> + /* If tx count control for N1 is set, load the tx value */
> + if (GETFIELD(PPC_BIT(50), sc->counter_config_reg) == 1) {
> + sc->N1_tx = sc->N1_bytes;
> + }
> + /* If rx count control for N1 is set, load the rx value */
> + if (GETFIELD(PPC_BIT(51), sc->counter_config_reg) == 1) {
> + sc->N1_rx = sc->N1_bytes;
> + }
> + } else {
> + /* Implicit mode/Forced Implicit mode, use M field from opcode */
> + sc->N1_bytes = GETFIELD(PPC_BITMASK8(4, 7), opcode);
> + sc->N1_bits = sc->N1_bytes * 8;
> + /*
> + * Assume that we are going to transmit the count
> + * (pure Implicit only)
> + */
> + sc->N1_tx = sc->N1_bytes;
> + sc->N1_rx = 0;
> + /* Let Forced Implicit mode have an effect on the counts */
> + if (GETFIELD(PPC_BIT(49), sc->counter_config_reg) == 1) {
> + /*
> + * If Forced Implicit mode and count control doesn't
> + * indicate transmit then reset the tx count to 0
> + */
> + if (GETFIELD(PPC_BIT(50), sc->counter_config_reg) == 0) {
> + sc->N1_tx = 0;
> + }
> + /* If rx count control for N1 is set, load the rx value */
> + if (GETFIELD(PPC_BIT(51), sc->counter_config_reg) == 1) {
> + sc->N1_rx = sc->N1_bytes;
> + }
> + }
> + }
> + /*
> + * Enforce an upper limit on the size of N1 that is equal to the known size
> + * of the shift register, 64 bits or 72 bits if ECC is enabled.
> + * If the size exceeds 72 bits it is a user error so log an error,
> + * cap the size at a max of 64 bits or 72 bits and set the sequencer FSM
> + * error bit.
> + */
> + uint8_t ecc_control = GETFIELD(PPC_BITMASK(29, 30),
> + sc->clock_config_reset_control);
> + if (ecc_control == 0 || ecc_control == 2) {
> + if (sc->N1_bytes > (SPI_CONTROLLER_REG_SIZE + 1)) {
> + qemu_log_mask(LOG_GUEST_ERROR, "Unsupported N1 shift size when "
> + "ECC enabled, bytes = 0x%x, bits = 0x%x\n",
> + sc->N1_bytes, sc->N1_bits);
> + sc->N1_bytes = SPI_CONTROLLER_REG_SIZE + 1;
> + sc->N1_bits = sc->N1_bytes * 8;
> + }
> + } else if (sc->N1_bytes > SPI_CONTROLLER_REG_SIZE) {
> + qemu_log_mask(LOG_GUEST_ERROR, "Unsupported N1 shift size, "
> + "bytes = 0x%x, bits = 0x%x\n",
> + sc->N1_bytes, sc->N1_bits);
> + sc->N1_bytes = SPI_CONTROLLER_REG_SIZE;
> + sc->N1_bits = sc->N1_bytes * 8;
> + }
> +} /* end of calculate_N1 */
> +
> +/*
> + * Shift_N1 operation handler method
> + */
> +bool operation_shiftn1(PnvSpiController *sc, uint8_t opcode,
> + xfer_buffer **payload, bool send_n1_alone)
> +{
> + uint8_t n1_count;
> + bool stop = false;
> +
> + /*
> + * If there isn't a current payload left over from a stopped sequence
> + * create a new one.
> + */
> + if (*payload == NULL) {
> + SPI_DEBUG(qemu_log("Creating new payload xfer_buffer\n"));
> + *payload = xfer_buffer_new();
> + }
> + /*
> + * Use a combination of N1 counters to build the N1 portion of the
> + * transmit payload.
> + * We only care about transmit at this time since the request payload
> + * only represents data going out on the controller output line.
> + * Leave mode specific considerations in the calculate function since
> + * all we really care about are counters that tell use exactly how
> + * many bytes are in the payload and how many of those bytes to
> + * include from the TDR into the payload.
> + */
> + calculate_N1(sc, opcode);
> + SPI_DEBUG(qemu_log("Shift N1 started..\n"));
> + log_all_N_counts(sc);
> + /*
> + * Zero out the N2 counters here in case there is no N2 operation following
> + * the N1 operation in the sequencer. This keeps leftover N2 information
> + * from interfering with spi_response logic.
> + */
> + sc->N2_bits = 0;
> + sc->N2_bytes = 0;
> + sc->N2_tx = 0;
> + sc->N2_rx = 0;
> + /*
> + * N1_bytes is the overall size of the N1 portion of the frame regardless of
> + * whether N1 is used for tx, rx or both. Loop over the size to build a
> + * payload that is N1_bytes long.
> + * N1_tx is the count of bytes to take from the TDR and "shift" into the
> + * frame which means append those bytes to the payload for the N1 portion
> + * of the frame.
> + * If N1_tx is 0 or if the count exceeds the size of the TDR append 0xFF to
> + * the frame until the overall N1 count is reached.
> + */
> + n1_count = 0;
> + while (n1_count < sc->N1_bytes) {
> + /*
> + * Assuming that if N1_tx is not equal to 0 then it is the same as
> + * N1_bytes.
> + */
> + if ((sc->N1_tx != 0) && (n1_count < SPI_CONTROLLER_REG_SIZE)) {
> +
> + if (GETFIELD(STATUS_REG_TDR_FULL, sc->status_reg) == 1) {
> + /*
> + * Note that we are only appending to the payload IF the TDR
> + * is full otherwise we don't touch the payload because we are
> + * going to NOT send the payload and instead tell the sequencer
> + * that called us to stop and wait for a TDR write so we have
> + * data to load into the payload.
> + */
> + uint8_t n1_byte = 0x00;
> + n1_byte = get_from_offset(sc, n1_count);
> + SPI_DEBUG(qemu_log("Extracting tx n1_byte = 0x%2.2x at index "
> + "%d from TDR\n", n1_byte, n1_count));
> + if (sc->reverse_bits) {
> + SPI_DEBUG(qemu_log("Reversing bit order of tx n1_byte\n"));
> + n1_byte = reverse_bits8(n1_byte);
> + }
> + SPI_DEBUG(qemu_log("Appending tx n1_byte = 0x%2.2x to "
> + "Payload\n", n1_byte));
> + *(xfer_buffer_write_ptr(*payload, (*payload)->len, 1)) =
> + n1_byte;
> + } else {
> + /*
> + * We hit a shift_n1 opcode TX but the TDR is empty, tell the
> + * sequencer to stop and break this loop.
> + */
> + SPI_DEBUG(qemu_log("Shift N1 set for transmit but TDR is empty,"
> + " requesting sequencer stop\n"));
> + stop = true;
> + break;
> + }
> + } else {
> + /*
> + * Cases here:
> + * - we are receiving during the N1 frame segment and the RDR
> + * is full so we need to stop until the RDR is read
> + * - we are transmitting and we don't care about RDR status
> + * since we won't be loading RDR during the frame segment.
> + * - we are receiving and the RDR is empty so we allow the operation
> + * to proceed.
> + */
> + if ((sc->N1_rx != 0) && (GETFIELD(STATUS_REG_RDR_FULL,
> + sc->status_reg) == 1)) {
> + SPI_DEBUG(qemu_log("Shift N1 set for receive but RDR is full, "
> + "requesting sequencer stop\n"));
> + stop = true;
> + break;
> + } else {
> + SPI_DEBUG(qemu_log("Appending tx n1_byte = 0xFF to Payload\n"));
> + *(xfer_buffer_write_ptr(*payload, (*payload)->len, 1)) = 0xff;
> + }
> + }
> + n1_count++;
> + } /* end of while */
> + /*
> + * If we are not stopping due to an empty TDR and we are doing an N1 TX
> + * and the TDR is full we need to clear the TDR_full status.
> + * Do this here instead of up in the loop above so we don't log the message
> + * in every loop iteration.
> + * Ignore the send_n1_alone flag, all that does is defer the TX until the N2
> + * operation, which was found immediately after the current opcode. The TDR
> + * was unloaded and will be shifted so we have to clear the TDR_full status.
> + */
> + if (!stop && (sc->N1_tx != 0) &&
> + (GETFIELD(STATUS_REG_TDR_FULL, sc->status_reg) == 1)) {
> +
> + sc->status_reg = SETFIELD(STATUS_REG_TDR_FULL, sc->status_reg, 0);
> + SPI_DEBUG(qemu_log("TDR_full set to 0\n"));
> + }
> + /*
> + * There are other reasons why the shifter would stop, such as a TDR empty
> + * or RDR full condition with N1 set to receive. If we haven't stopped due
> + * to either one of those conditions then check if the send_n1_alone flag is
> + * equal to False, indicating the next opcode is an N2 operation, AND if
> + * the N2 counter reload switch (bit 0 of the N2 count control field) is
> + * set. This condition requires a pacing write to "kick" off the N2
> + * shift which includes the N1 shift as well when send_n1_alone is False.
> + */
> + if (!stop && !send_n1_alone &&
> + (GETFIELD(PPC_BIT(52), sc->counter_config_reg) == 1)) {
> + SPI_DEBUG(qemu_log("N2 counter reload active, stop N1 shift, "
> + "TDR_underrun set to 1\n"));
> + stop = true;
> + sc->status_reg = SETFIELD(STATUS_REG_TDR_UNDERRUN, sc->status_reg, 1);
> + }
> + /*
> + * If send_n1_alone is set AND we have a full TDR then this is the first and
> + * last payload to send and we don't have an N2 frame segment to add to the
> + * payload.
> + */
> + if (send_n1_alone && !stop) {
> + uint32_t tx;
> + uint32_t rx;
> + uint8_t *read_buf = NULL;
> + xfer_buffer *rsp_payload = NULL;
> +
> + /* We have a TX and a full TDR or an RX and an empty RDR */
> + SPI_DEBUG(qemu_log("Shifting N1 frame: first = %d, last = %d, "
> + "n1 bits = %d\n", sc->first, sc->last,
> + sc->N1_bits));
> + rsp_payload = xfer_buffer_new();
> + for (int offset = 0; offset < (*payload)->len; offset = offset + 4) {
> + xfer_buffer_read_ptr(*payload, &read_buf, offset, 4);
> + tx = 0;
> + for (int i = 0; i < 4; i++) {
> + if ((offset + i) >= (*payload)->len) {
> + break;
> + }
> + tx = (tx << 8) | read_buf[i];
> + }
> + rx = ssi_transfer((sc->bus).ssi_bus, tx);
> + for (int i = 0; i < 4; i++) {
> + if ((offset + i) >= (*payload)->len) {
> + break;
> + }
> + *(xfer_buffer_write_ptr(rsp_payload, rsp_payload->len, 1)) =
> + (rx >> (24 - i * 8)) & 0xFF;
> + }
> + }
> + if (rsp_payload != NULL) {
> + spi_response(sc, sc->N1_bits, rsp_payload);
> + }
> + sc->first = 0;
> + sc->last = 0;
> + /* The N1 frame shift is complete so reset the N1 counters */
> + sc->N2_bits = 0;
> + sc->N2_bytes = 0;
> + sc->N2_tx = 0;
> + sc->N2_rx = 0;
> + xfer_buffer_free(*payload);
> + *payload = NULL;
> + SPI_DEBUG(qemu_log("Payload buffer freed\n"));
> + } else {
> + SPI_DEBUG(qemu_log("Not shifting N1, send_n1_alone = %d, stop = %d\n",
> + send_n1_alone, stop));
> + }
> + return stop;
> +} /* end of operation_shiftn1() */
> +
> +/*
> + * Calculate the N2 counters based on passed in opcode and
> + * internal register values.
> + * The method assumes that the opcode is a Shift_N2 opcode
> + * and doesn't test it.
> + * The counters returned are:
> + * N2 bits: Number of bits in the payload data that are significant
> + * to the responder.
> + * N2_bytes: Total count of payload bytes for the N2 frame.
> + * N2_tx: Total number of bytes taken from TDR for N2
> + * N2_rx: Total number of bytes taken from the payload for N2
> + */
> +void calculate_N2(PnvSpiController *sc, uint8_t opcode)
> +{
> + /*
> + * Shift_N2 opcode form: 0x4M
> + * Implicit mode:
> + * If M!=0 the shift count is M bytes and M is the number of rx bytes.
> + * Forced Implicit mode:
> + * M is the shift count but tx and rx is determined by the count control
> + * register fields. Note that we only check for Forced Implicit mode when
> + * M != 0 since the mode doesn't make sense when M = 0.
> + * Explicit mode:
> + * If M==0 then shift count is number of bits defined in the
> + * Counter Configuration Register's shift_count_N1 field.
> + */
> + if (GETFIELD(PPC_BITMASK8(4, 7), opcode) == 0) {
> + /* Explicit mode */
> + sc->N2_bits = GETFIELD(COUNTER_CONFIG_REG_SHIFT_COUNT_N2,
> + sc->counter_config_reg);
> + sc->N2_bytes = ceil(sc->N2_bits / 8);
> + sc->N2_tx = 0;
> + sc->N2_rx = 0;
> + /* If tx count control for N2 is set, load the tx value */
> + if (GETFIELD(PPC_BIT(54), sc->counter_config_reg) == 1) {
> + sc->N2_tx = sc->N2_bytes;
> + }
> + /* If rx count control for N2 is set, load the rx value */
> + if (GETFIELD(PPC_BIT(55), sc->counter_config_reg) == 1) {
> + sc->N2_rx = sc->N2_bytes;
> + }
> + } else {
> + /* Implicit mode/Forced Implicit mode, use M field from opcode */
> + sc->N2_bytes = GETFIELD(PPC_BITMASK8(4, 7), opcode);
> + sc->N2_bits = sc->N2_bytes * 8;
> + /* Assume that we are going to receive the count */
> + sc->N2_rx = sc->N2_bytes;
> + sc->N2_tx = 0;
> + /* Let Forced Implicit mode have an effect on the counts */
> + if (GETFIELD(PPC_BIT(53), sc->counter_config_reg) == 1) {
> + /*
> + * If Forced Implicit mode and count control doesn't
> + * indicate a receive then reset the rx count to 0
> + */
> + if (GETFIELD(PPC_BIT(55), sc->counter_config_reg) == 0) {
> + sc->N2_rx = 0;
> + }
> + /* If tx count control for N2 is set, load the tx value */
> + if (GETFIELD(PPC_BIT(54), sc->counter_config_reg) == 1) {
> + sc->N2_tx = sc->N2_bytes;
> + }
> + }
> + }
> + /*
> + * Enforce an upper limit on the size of N1 that is equal to the
> + * known size of the shift register, 64 bits or 72 bits if ECC
> + * is enabled.
> + * If the size exceeds 72 bits it is a user error so log an error,
> + * cap the size at a max of 64 bits or 72 bits and set the sequencer FSM
> + * error bit.
> + */
> + uint8_t ecc_control = GETFIELD(PPC_BITMASK(29, 30),
> + sc->clock_config_reset_control);
> + if (ecc_control == 0 || ecc_control == 2) {
> + if (sc->N2_bytes > (SPI_CONTROLLER_REG_SIZE + 1)) {
> + SPI_DEBUG(qemu_log("Unsupported N2 shift size when ECC enabled, "
> + "bytes = 0x%x, bits = 0x%x\n",
> + sc->N2_bytes, sc->N2_bits));
> + sc->N2_bytes = SPI_CONTROLLER_REG_SIZE + 1;
> + sc->N2_bits = sc->N2_bytes * 8;
> + }
> + } else if (sc->N2_bytes > SPI_CONTROLLER_REG_SIZE) {
> + SPI_DEBUG(qemu_log("Unsupported N2 shift size, bytes = 0x%x, "
> + "bits = 0x%x\n", sc->N2_bytes, sc->N2_bits));
> + sc->N2_bytes = SPI_CONTROLLER_REG_SIZE;
> + sc->N2_bits = sc->N2_bytes * 8;
> + }
> +} /* end of calculate_N2 */
> +
> +/*
> + * Shift_N2 operation handler method
> + */
> +
> +bool operation_shiftn2(PnvSpiController *sc, uint8_t opcode,
> + xfer_buffer **payload)
> +{
> + uint8_t n2_count;
> + bool stop = false;
> +
> + /*
> + * If there isn't a current payload left over from a stopped sequence
> + * create a new one.
> + */
> + if (*payload == NULL) {
> + SPI_DEBUG(qemu_log("Creating new payload xfer_buffer\n"));
> + *payload = xfer_buffer_new();
> + }
> + /*
> + * Use a combination of N2 counters to build the N2 portion of the
> + * transmit payload.
> + */
> + calculate_N2(sc, opcode);
> + SPI_DEBUG(qemu_log("Shift N2 started\n"));
> + log_all_N_counts(sc);
> + /*
> + * The only difference between this code and the code for shift N1 is
> + * that this code has to account for the possible presence of N1 transmit
> + * bytes already taken from the TDR.
> + * If there are bytes to be transmitted for the N2 portion of the frame
> + * and there are still bytes in TDR that have not been copied into the
> + * TX data of the payload, this code will handle transmitting those
> + * remaining bytes.
> + * If for some reason the transmit count(s) add up to more than the size
> + * of the TDR we will just append 0xFF to the transmit payload data until
> + * the payload is N1 + N2 bytes long.
> + */
> + n2_count = 0;
> + while (n2_count < sc->N2_bytes) {
> + /*
> + * If the RDR is full and we need to RX just bail out, letting the
> + * code continue will end up building the payload twice in the same
> + * buffer since RDR full causes a sequence stop and restart.
> + */
> + if ((sc->N2_rx != 0) &&
> + (GETFIELD(STATUS_REG_RDR_FULL, sc->status_reg) == 1)) {
> + SPI_DEBUG(qemu_log("Shift N2 set for receive but RDR is full, "
> + "requesting sequencer stop\n"));
> + stop = true;
> + break;
> + }
> + if ((sc->N2_tx != 0) && ((sc->N1_tx + n2_count) <
> + SPI_CONTROLLER_REG_SIZE)) {
> + /* Always append data for the N2 segment if it is set for TX */
> + uint8_t n2_byte = 0x00;
> + n2_byte = get_from_offset(sc, (sc->N1_tx + n2_count));
> + SPI_DEBUG(qemu_log("Extracting tx n2_byte = 0x%2.2x at index %d "
> + "from TDR\n", n2_byte, (sc->N1_tx + n2_count)));
> + if (sc->reverse_bits) {
> + SPI_DEBUG(qemu_log("Reversing bit order of tx n2_byte\n"));
> + n2_byte = reverse_bits8(n2_byte);
> + }
> + SPI_DEBUG(qemu_log("Appending tx n2_byte = 0x%2.2x to Payload\n",
> + n2_byte));
> + *(xfer_buffer_write_ptr(*payload, (*payload)->len, 1)) = n2_byte;
> + } else {
> + /*
> + * Regardless of whether or not N2 is set for TX or RX, we need
> + * the number of bytes in the payload to match the overall length
> + * of the operation.
> + */
> + SPI_DEBUG(qemu_log("Appending tx n2_byte = 0xFF to Payload\n"));
> + *(xfer_buffer_write_ptr(*payload, (*payload)->len, 1)) = 0xff;
> + }
> + n2_count++;
> + } /* end of while */
> + if (!stop) {
> + uint32_t tx;
> + uint32_t rx;
> + uint8_t *read_buf = NULL;
> + xfer_buffer *rsp_payload = NULL;
> +
> + /* We have a TX and a full TDR or an RX and an empty RDR */
> + SPI_DEBUG(qemu_log("Shifting N2 frame: first = %d, last = %d, "
> + "n1+n2 bits = %d\n", sc->first, sc->last,
> + (sc->N1_bits + sc->N2_bits)));
> + rsp_payload = xfer_buffer_new();
> + for (int offset = 0; offset < (*payload)->len; offset = offset + 4) {
> + xfer_buffer_read_ptr(*payload, &read_buf, offset, 4);
> + tx = 0;
> + for (int i = 0; i < 4; i++) {
> + if ((offset + i) >= (*payload)->len) {
> + break;
> + }
> + tx = (tx << 8) | read_buf[i];
> + }
> + rx = ssi_transfer((sc->bus).ssi_bus, tx);
> + for (int i = 0; i < 4; i++) {
> + if ((offset + i) >= (*payload)->len) {
> + break;
> + }
> + *(xfer_buffer_write_ptr(rsp_payload, rsp_payload->len, 1)) =
> + (rx >> (24 - i * 8)) & 0xFF;
> + }
> + }
> + if (rsp_payload != NULL) {
> + spi_response(sc, (sc->N1_bits + sc->N2_bits), rsp_payload);
> + }
> + sc->first = 0;
> + sc->last = 0;
> + /*
> + * If we are doing an N2 TX and the TDR is full we need to clear the
> + * TDR_full status. Do this here instead of up in the loop above so we
> + * don't log the message in every loop iteration.
> + */
> + if ((sc->N2_tx != 0) &&
> + (GETFIELD(STATUS_REG_TDR_FULL, sc->status_reg) == 1)) {
> + sc->status_reg = SETFIELD(STATUS_REG_TDR_FULL, sc->status_reg, 0);
> + SPI_DEBUG(qemu_log("TDR_full set to 0\n"));
> + }
> + /*
> + * The N2 frame shift is complete so reset the N2 counters.
> + * Reset the N1 counters also in case the frame was a combination of
> + * N1 and N2 segments.
> + */
> + sc->N2_bits = 0;
> + sc->N2_bytes = 0;
> + sc->N2_tx = 0;
> + sc->N2_rx = 0;
> + sc->N1_bits = 0;
> + sc->N1_bytes = 0;
> + sc->N1_tx = 0;
> + sc->N1_rx = 0;
> + xfer_buffer_free(*payload);
> + *payload = NULL;
> + SPI_DEBUG(qemu_log("Payload buffer freed\n"));
> + } else {
> + SPI_DEBUG(qemu_log("Not shifting N2, stop = %d\n", stop));
> + }
> + return stop;
> +} /* end of operation_shiftn2()*/
> +
> +/*
> + * The SPIC engine and its internal sequencer can be interrupted and reset by
> + * a hardware signal, the sbe_spicst_hard_reset bits from Pervasive
> + * Miscellaneous Register of sbe_register_bo device.
> + * Reset immediately aborts any SPI transaction in progress and returns the
> + * sequencer and state machines to idle state.
> + * The configuration register values are not changed. The status register is
> + * not reset. The engine registers are not reset.
> + * The SPIC engine reset does not have any affect on the attached devices.
> + * Reset handling of any attached devices is beyond the scope of the engine.
> + */
> +static void do_reset(DeviceState *dev)
> +{
> + PnvSpiController *sc = PNV_SPICONTROLLER(dev);
> + SPI_DEBUG(qemu_log("Resetting spic engine sequencer configuration and spi "
> + "communication\n"));
> + /* Reset all N1 and N2 counters, and other constants */
> + sc->first = 0;
> + sc->last = 0;
> + sc->N2_bits = 0;
> + sc->N2_bytes = 0;
> + sc->N2_tx = 0;
> + sc->N2_rx = 0;
> + sc->N1_bits = 0;
> + sc->N1_bytes = 0;
> + sc->N1_tx = 0;
> + sc->N1_rx = 0;
> + sc->loop_counter_1 = 0;
> + sc->loop_counter_2 = 0;
> + SPI_DEBUG(qemu_log("Disconnected from responder\n"));
> + qemu_set_irq((sc->bus).cs_line[0], 1);
> +}
> +
> static void pnv_spi_bus_realize(DeviceState *dev, Error **errp)
> {
> PnvSPIBus *s = PNV_SPI_BUS(dev);
> @@ -191,6 +1498,7 @@ static const TypeInfo pnv_spi_bus_info = {
>
> static Property pnv_spi_controller_properties[] = {
> DEFINE_PROP_UINT32("spic_num", PnvSpiController, spic_num, 0),
> + DEFINE_PROP_BOOL("reverse_bits", PnvSpiController, reverse_bits, false),
> DEFINE_PROP_END_OF_LIST(),
> };
>
> @@ -254,6 +1562,7 @@ static void pnv_spi_controller_class_init(ObjectClass *klass, void *data)
>
> dc->desc = "PowerNV SPI Controller";
> dc->realize = pnv_spi_controller_realize;
> + dc->reset = do_reset;
> device_class_set_props(dc, pnv_spi_controller_properties);
> }
>
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 2/6] hw/ppc: SPI controller model - registers implementation
2024-04-15 15:14 ` Cédric Le Goater
@ 2024-04-16 17:02 ` Chalapathi V
2024-04-22 14:06 ` Cédric Le Goater
0 siblings, 1 reply; 16+ messages in thread
From: Chalapathi V @ 2024-04-16 17:02 UTC (permalink / raw)
To: Cédric Le Goater, qemu-devel
Cc: qemu-ppc, fbarrat, npiggin, calebs, chalapathi.v, saif.abrar,
dantan
On 15-04-2024 20:44, Cédric Le Goater wrote:
> Hello Chalapathi
>
> The subject could be rephrased to : "ppc/pnv: Add SPI controller model".
>
> On 4/9/24 19:56, Chalapathi V wrote:
>> SPI controller device model supports a connection to a single SPI
>> responder.
>> This provide access to SPI seeproms, TPM, flash device and an ADC
>> controller.
>>
>> All SPI function control is mapped into the SPI register space to
>> enable full
>> control by firmware. In this commit SPI configuration component is
>> modelled
>> which contains all SPI configuration and status registers as well as
>> the hold
>> registers for data to be sent or having been received.
>>
>> An existing QEMU SSI framework is used and SSI_BUS is created.
>>
>> Signed-off-by: Chalapathi V <chalapathi.v@linux.ibm.com>
>> ---
>> include/hw/ppc/pnv_spi_controller.h | 55 +++++
>> include/hw/ppc/pnv_spi_controller_regs.h | 114 ++++++++++
>
> These two files should be under hw/ssi/ and include/hw/ssi/. Please
> remove '_controller'.
Sure. Thank You.
>
>> include/hw/ppc/pnv_xscom.h | 3 +
>> hw/ppc/pnv_spi_controller.c | 278 +++++++++++++++++++++++
>> hw/ppc/Kconfig | 1 +
>> hw/ppc/meson.build | 1 +
>> 6 files changed, 452 insertions(+)
>> create mode 100644 include/hw/ppc/pnv_spi_controller.h
>> create mode 100644 include/hw/ppc/pnv_spi_controller_regs.h
>> create mode 100644 hw/ppc/pnv_spi_controller.c
>>
>> diff --git a/include/hw/ppc/pnv_spi_controller.h
>> b/include/hw/ppc/pnv_spi_controller.h
>> new file mode 100644
>> index 0000000000..5ec50fb14c
>> --- /dev/null
>> +++ b/include/hw/ppc/pnv_spi_controller.h
>> @@ -0,0 +1,55 @@
>> +/*
>> + * QEMU PowerPC SPI Controller model
>> + *
>> + * Copyright (c) 2024, IBM Corporation.
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + *
>> + * This model Supports a connection to a single SPI responder.
>> + * Introduced for P10 to provide access to SPI seeproms, TPM, flash
>> device
>> + * and an ADC controller.
>> + */
>> +#include "hw/ssi/ssi.h"
>> +
>> +#ifndef PPC_PNV_SPI_CONTROLLER_H
>> +#define PPC_PNV_SPI_CONTROLLER_H
>> +
>> +#define TYPE_PNV_SPI_CONTROLLER "pnv-spi-controller"
>> +#define PNV_SPICONTROLLER(obj) \
>> + OBJECT_CHECK(PnvSpiController, (obj), TYPE_PNV_SPI_CONTROLLER)
>
> You could use OBJECT_DECLARE_SIMPLE_TYPE ? Anyhow, I would prefer
> naming the macro PNV_SPI_CONTROLLER.
>
>> +#define SPI_CONTROLLER_REG_SIZE 8
>> +
>> +typedef struct SSIBus SSIBus;
>
> why ?
I might have got compile time errors. I will recheck and update. Thank You.
>
>
>> +
>> +#define TYPE_PNV_SPI_BUS "pnv-spi-bus"
>> +OBJECT_DECLARE_SIMPLE_TYPE(PnvSPIBus, PNV_SPI_BUS)
>> +
>> +typedef struct PnvSPIBus {
>
> I don't think this extra PnvSPIBus model is useful.
>
>> + SysBusDevice parent_obj;
>> +
>> + SSIBus *ssi_bus;
>> + qemu_irq *cs_line;
>
> These two attributes could live under PnvSpiController.
This is added to have a SysBusDevice parent so that I can use the
busname in command line for TPM. I will add these in PnvSpiController
with SysBusDevice parent and test.
>
>> + uint32_t id;
>
> and this one would become useless.
>
>> +} PnvSPIBus;
>>
>> +typedef struct PnvSpiController {
>> + DeviceState parent;
>> +
>> + PnvSPIBus bus;
>> + MemoryRegion xscom_spic_regs;
>> + /* SPI controller object number */
>> + uint32_t spic_num;
>> +
>> + /* SPI Controller registers */
>> + uint64_t error_reg;
>> + uint64_t counter_config_reg;
>> + uint64_t config_reg1;
>> + uint64_t clock_config_reset_control;
>> + uint64_t memory_mapping_reg;
>> + uint64_t transmit_data_reg;
>> + uint64_t receive_data_reg;
>> + uint8_t sequencer_operation_reg[SPI_CONTROLLER_REG_SIZE];
>> + uint64_t status_reg;
>
> You could use an array of uint64_t also.
Sure. I will try and check.
>
>
>> +} PnvSpiController;
>> +#endif /* PPC_PNV_SPI_CONTROLLER_H */
>> diff --git a/include/hw/ppc/pnv_spi_controller_regs.h
>> b/include/hw/ppc/pnv_spi_controller_regs.h
>> new file mode 100644
>> index 0000000000..6f613aca5e
>> --- /dev/null
>> +++ b/include/hw/ppc/pnv_spi_controller_regs.h
>> @@ -0,0 +1,114 @@
>> +/*
>> + * QEMU PowerPC SPI Controller model
>> + *
>> + * Copyright (c) 2023, IBM Corporation.
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#ifndef SPI_CONTROLLER_REGS_H
>> +#define SPI_CONTROLLER_REGS_H
>> +
>> +/* Error Register */
>> +#define ERROR_REG 0x00
>> +
>> +/* counter_config_reg */
>> +#define COUNTER_CONFIG_REG 0x01
>> +#define COUNTER_CONFIG_REG_SHIFT_COUNT_N1 PPC_BITMASK(0, 7)
>> +#define COUNTER_CONFIG_REG_SHIFT_COUNT_N2 PPC_BITMASK(8, 15)
>> +#define COUNTER_CONFIG_REG_COUNT_COMPARE1 PPC_BITMASK(24, 31)
>> +#define COUNTER_CONFIG_REG_COUNT_COMPARE2 PPC_BITMASK(32, 39)
>> +#define COUNTER_CONFIG_REG_N1_COUNT_CONTROL PPC_BITMASK(48, 51)
>> +#define COUNTER_CONFIG_REG_N2_COUNT_CONTROL PPC_BITMASK(52, 55)
>> +
>> +/* config_reg */
>> +#define CONFIG_REG1 0x02
>> +
>> +/* clock_config_reset_control_ecc_enable_reg */
>> +#define CLOCK_CONFIG_REG 0x03
>> +#define CLOCK_CONFIG_RESET_CONTROL_HARD_RESET 0x0084000000000000;
>> +#define CLOCK_CONFIG_REG_RESET_CONTROL PPC_BITMASK(24, 27)
>> +#define CLOCK_CONFIG_REG_ECC_CONTROL PPC_BITMASK(28, 30)
>> +
>> +/* memory_mapping_reg */
>> +#define MEMORY_MAPPING_REG 0x04
>> +#define MEMORY_MAPPING_REG_MMSPISM_BASE_ADDR PPC_BITMASK(0, 15)
>> +#define MEMORY_MAPPING_REG_MMSPISM_ADDR_MASK PPC_BITMASK(16, 31)
>> +#define MEMORY_MAPPING_REG_RDR_MATCH_VAL PPC_BITMASK(32, 47)
>> +#define MEMORY_MAPPING_REG_RDR_MATCH_MASK PPC_BITMASK(48, 63)
>> +
>> +/* transmit_data_reg */
>> +#define TRANSMIT_DATA_REG 0x05
>> +
>> +/* receive_data_reg */
>> +#define RECEIVE_DATA_REG 0x06
>> +
>> +/* sequencer_operation_reg */
>> +#define SEQUENCER_OPERATION_REG 0x07
>> +
>> +/* status_reg */
>> +#define STATUS_REG 0x08
>> +#define STATUS_REG_RDR_FULL PPC_BIT(0)
>> +#define STATUS_REG_RDR_OVERRUN PPC_BIT(1)
>> +#define STATUS_REG_RDR_UNDERRUN PPC_BIT(2)
>> +#define STATUS_REG_TDR_FULL PPC_BIT(4)
>> +#define STATUS_REG_TDR_OVERRUN PPC_BIT(5)
>> +#define STATUS_REG_TDR_UNDERRUN PPC_BIT(6)
>> +#define STATUS_REG_SEQUENCER_FSM PPC_BITMASK(8, 15)
>> +#define STATUS_REG_SHIFTER_FSM PPC_BITMASK(16, 27)
>> +#define STATUS_REG_SEQUENCER_INDEX PPC_BITMASK(28, 31)
>> +#define STATUS_REG_GENERAL_SPI_STATUS PPC_BITMASK(32, 63)
>> +#define STATUS_REG_RDR PPC_BITMASK(1, 3)
>> +#define STATUS_REG_TDR PPC_BITMASK(5, 7)
>> +
>> +/*
>> + * Shifter states
>> + *
>> + * These are the same values defined for the Shifter FSM field of the
>> + * status register. It's a 12 bit field so we will represent it as
>> three
>> + * nibbles in the constants.
>> + *
>> + * These are shifter_fsm values
>> + *
>> + * Status reg bits 16-27 -> field bits 0-11
>> + * bits 0,1,2,5 unused/reserved
>> + * bit 4 crc shift in (unused)
>> + * bit 8 crc shift out (unused)
>> + */
>> +
>> +#define FSM_DONE 0x100 /* bit 3 */
>> +#define FSM_SHIFT_N2 0x020 /* bit 6 */
>> +#define FSM_WAIT 0x010 /* bit 7 */
>> +#define FSM_SHIFT_N1 0x004 /* bit 9 */
>> +#define FSM_START 0x002 /* bit 10 */
>> +#define FSM_IDLE 0x001 /* bit 11 */
>> +
>> +/*
>> + * Sequencer states
>> + *
>> + * These are sequencer_fsm values
>> + *
>> + * Status reg bits 8-15 -> field bits 0-7
>> + * bits 0-3 unused/reserved
>> + *
>> + */
>> +#define SEQ_STATE_INDEX_INCREMENT 0x08 /* bit 4 */
>> +#define SEQ_STATE_EXECUTE 0x04 /* bit 5 */
>> +#define SEQ_STATE_DECODE 0x02 /* bit 6 */
>> +#define SEQ_STATE_IDLE 0x01 /* bit 7 */
>> +
>> +/*
>> + * These are the supported sequencer operations.
>> + * Only the upper nibble is significant because for many operations
>> + * the lower nibble is a variable specific to the operation.
>> + */
>> +#define SEQ_OP_STOP 0x00
>> +#define SEQ_OP_SELECT_SLAVE 0x10
>> +#define SEQ_OP_SHIFT_N1 0x30
>> +#define SEQ_OP_SHIFT_N2 0x40
>> +#define SEQ_OP_BRANCH_IFNEQ_RDR 0x60
>> +#define SEQ_OP_TRANSFER_TDR 0xC0
>> +#define SEQ_OP_BRANCH_IFNEQ_INC_1 0xE0
>> +#define SEQ_OP_BRANCH_IFNEQ_INC_2 0xF0
>> +
>> +#endif
>> diff --git a/include/hw/ppc/pnv_xscom.h b/include/hw/ppc/pnv_xscom.h
>> index 6209e18492..a77b97f9b1 100644
>> --- a/include/hw/ppc/pnv_xscom.h
>> +++ b/include/hw/ppc/pnv_xscom.h
>> @@ -194,6 +194,9 @@ struct PnvXScomInterfaceClass {
>> #define PNV10_XSCOM_PEC_PCI_BASE 0x8010800 /* index goes upwards
>> ... */
>> #define PNV10_XSCOM_PEC_PCI_SIZE 0x200
>> +#define PNV10_XSCOM_PIB_SPIC_BASE 0xc0000
>> +#define PNV10_XSCOM_PIB_SPIC_SIZE 0x20
>> +
>> void pnv_xscom_init(PnvChip *chip, uint64_t size, hwaddr addr);
>> int pnv_dt_xscom(PnvChip *chip, void *fdt, int root_offset,
>> uint64_t xscom_base, uint64_t xscom_size,
>> diff --git a/hw/ppc/pnv_spi_controller.c b/hw/ppc/pnv_spi_controller.c
>> new file mode 100644
>> index 0000000000..e2478a47f2
>> --- /dev/null
>> +++ b/hw/ppc/pnv_spi_controller.c
>> @@ -0,0 +1,278 @@
>> +/*
>> + * QEMU PowerPC SPI Controller model
>> + *
>> + * Copyright (c) 2024, IBM Corporation.
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#include "qemu/osdep.h"
>> +#include "qemu/log.h"
>> +#include "hw/qdev-properties.h"
>> +#include "hw/ppc/pnv_xscom.h"
>> +#include "hw/ppc/pnv_spi_controller.h"
>> +#include "hw/ppc/pnv_spi_controller_regs.h"
>> +#include "hw/ssi/ssi.h"
>> +#include "hw/ppc/fdt.h"
>> +#include <libfdt.h>
>> +#include <math.h>
>> +#include "hw/irq.h"
>> +
>> +#define SPI_DEBUG(x)
>> +
>> +static uint64_t pnv_spi_controller_read(void *opaque, hwaddr addr,
>> + unsigned size)
>> +{
>> + PnvSpiController *sc = PNV_SPICONTROLLER(opaque);
>
> The name 'sc' makes you think of a class. 's' is common in QEMU models.
Sure. Will modify.
>
>> + uint32_t reg = addr >> 3;
>> + uint64_t val = ~0ull;
>> +
>> + switch (reg) {
>> + case ERROR_REG:
>> + val = sc->error_reg;
>> + break;
>> + case COUNTER_CONFIG_REG:
>> + val = sc->counter_config_reg;
>> + break;
>> + case CONFIG_REG1:
>> + val = sc->config_reg1;
>> + break;
>> + case CLOCK_CONFIG_REG:
>> + val = sc->clock_config_reset_control;
>> + break;
>> + case MEMORY_MAPPING_REG:
>> + val = sc->memory_mapping_reg;
>> + break;
>> + case TRANSMIT_DATA_REG:
>> + val = sc->transmit_data_reg;
>> + break;
>> + case RECEIVE_DATA_REG:
>> + val = sc->receive_data_reg;
>> + SPI_DEBUG(qemu_log("RDR being read, data extracted =
>> 0x%16.16lx\n",
>> + val));
>
> please use trace events instead of the SPI_DEBUG macro.
Sure. Will replace with trace events wherever necessary.
>
>> + sc->status_reg = SETFIELD(STATUS_REG_RDR_FULL,
>> sc->status_reg, 0);
>> + SPI_DEBUG(qemu_log("RDR being read, RDR_full set to 0\n"));
>> + break;
>> + case SEQUENCER_OPERATION_REG:
>> + val = 0;
>> + for (int i = 0; i < SPI_CONTROLLER_REG_SIZE; i++) {
>> + val = (val << 8) | sc->sequencer_operation_reg[i];
>> + }
>> + break;
>> + case STATUS_REG:
>> + val = sc->status_reg;
>> + break;
>> + default:
>> + qemu_log_mask(LOG_GUEST_ERROR, "spi_controller_regs: Invalid
>> xscom "
>> + "read at 0x%08x\n", reg);
>> + }
>> + return val;
>> +}
>> +
>> +static void pnv_spi_controller_write(void *opaque, hwaddr addr,
>> + uint64_t val, unsigned size)
>> +{
>> + PnvSpiController *sc = PNV_SPICONTROLLER(opaque);
>> + uint32_t reg = addr >> 3;
>> +
>> + switch (reg) {
>> + case ERROR_REG:
>> + sc->error_reg = val;
>> + break;
>> + case COUNTER_CONFIG_REG:
>> + sc->counter_config_reg = val;
>> + break;
>> + case CONFIG_REG1:
>> + sc->config_reg1 = val;
>> + break;
>> + case CLOCK_CONFIG_REG:
>> + /*
>> + * To reset the SPI controller write the sequence 0x5 0xA to
>> + * reset_control field
>> + */
>> + if (GETFIELD(CLOCK_CONFIG_REG_RESET_CONTROL,
>> + sc->clock_config_reset_control) == 0x5) {
>> + if (GETFIELD(CLOCK_CONFIG_REG_RESET_CONTROL, val) == 0xA) {
>> + SPI_DEBUG(qemu_log("SPI controller reset sequence
>> completed, "
>> + "resetting..."));
>> + sc->clock_config_reset_control =
>> + CLOCK_CONFIG_RESET_CONTROL_HARD_RESET;
>> + } else {
>> + sc->clock_config_reset_control = val;
>> + }
>> + } else {
>> + sc->clock_config_reset_control = val;
>> + }
>> + break;
>> + case MEMORY_MAPPING_REG:
>> + sc->memory_mapping_reg = val;
>> + break;
>> + case TRANSMIT_DATA_REG:
>> + /*
>> + * Writing to the transmit data register causes the transmit
>> data
>> + * register full status bit in the status register to be
>> set. Writing
>> + * when the transmit data register full status bit is
>> already set
>> + * causes a "Resource Not Available" condition. This is not
>> possible
>> + * in the model since writes to this register are not
>> asynchronous to
>> + * the operation sequence like it would be in hardware.
>> + */
>> + sc->transmit_data_reg = val;
>> + SPI_DEBUG(qemu_log("TDR being written, data written =
>> 0x%16.16lx\n",
>> + val));
>> + sc->status_reg = SETFIELD(STATUS_REG_TDR_FULL,
>> sc->status_reg, 1);
>> + SPI_DEBUG(qemu_log("TDR being written, TDR_full set to 1\n"));
>> + sc->status_reg = SETFIELD(STATUS_REG_TDR_UNDERRUN,
>> sc->status_reg, 0);
>> + SPI_DEBUG(qemu_log("TDR being written, TDR_underrun set to
>> 0\n"));
>> + SPI_DEBUG(qemu_log("TDR being written, starting sequencer\n"));
>> + break;
>> + case RECEIVE_DATA_REG:
>> + sc->receive_data_reg = val;
>> + break;
>> + case SEQUENCER_OPERATION_REG:
>> + for (int i = 0; i < SPI_CONTROLLER_REG_SIZE; i++) {
>> + sc->sequencer_operation_reg[i] = (val >> (56 - i * 8)) & 0xFF;
>> + }
>> + break;
>> + case STATUS_REG:
>> + /* other fields are ignore_write */
>> + sc->status_reg = SETFIELD(STATUS_REG_RDR_OVERRUN,
>> sc->status_reg,
>> + GETFIELD(STATUS_REG_RDR, val));
>> + sc->status_reg = SETFIELD(STATUS_REG_TDR_OVERRUN,
>> sc->status_reg,
>> + GETFIELD(STATUS_REG_TDR, val));
>> + break;
>> + default:
>> + qemu_log_mask(LOG_GUEST_ERROR, "spi_controller_regs: Invalid
>> xscom "
>> + "write at 0x%08x\n", reg);
>> + }
>> + return;
>> +}
>> +
>> +static const MemoryRegionOps pnv_spi_controller_xscom_ops = {
>> + .read = pnv_spi_controller_read,
>> + .write = pnv_spi_controller_write,
>> + .valid.min_access_size = 8,
>> + .valid.max_access_size = 8,
>> + .impl.min_access_size = 8,
>> + .impl.max_access_size = 8,
>> + .endianness = DEVICE_BIG_ENDIAN,
>> +};
>> +
>> +static void pnv_spi_bus_realize(DeviceState *dev, Error **errp)
>> +{
>> + PnvSPIBus *s = PNV_SPI_BUS(dev);
>> + SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
>> + g_autofree char *name = g_strdup_printf(TYPE_PNV_SPI_BUS ".%d",
>> s->id);
>> +
>> + s->ssi_bus = ssi_create_bus(dev, name);
>> + s->cs_line = g_new0(qemu_irq, 1);
>> + sysbus_init_irq(sbd, &s->cs_line[0]);
>> +}
>> +
>> +static Property pnv_spi_bus_properties[] = {
>> + DEFINE_PROP_UINT32("bus-id", PnvSPIBus, id, 0),
>> + DEFINE_PROP_END_OF_LIST(),
>> +};
>> +
>> +static void pnv_spi_bus_class_init(ObjectClass *klass, void *data)
>> +{
>> + DeviceClass *dc = DEVICE_CLASS(klass);
>> +
>> + dc->desc = "Pnv SPI Bus";
>> + dc->realize = pnv_spi_bus_realize;
>> + device_class_set_props(dc, pnv_spi_bus_properties);
>> +}
>> +
>> +static const TypeInfo pnv_spi_bus_info = {
>> + .name = TYPE_PNV_SPI_BUS,
>> + .parent = TYPE_SYS_BUS_DEVICE,
>> + .instance_size = sizeof(PnvSPIBus),
>> + .class_init = pnv_spi_bus_class_init,
>> +};
>> +
>> +static Property pnv_spi_controller_properties[] = {
>> + DEFINE_PROP_UINT32("spic_num", PnvSpiController, spic_num, 0),
>> + DEFINE_PROP_END_OF_LIST(),
>> +};
>> +
>> +static void pnv_spi_controller_realize(DeviceState *dev, Error **errp)
>> +{
>> + PnvSpiController *sc = PNV_SPICONTROLLER(dev);
>> +
>> + Object *bus = OBJECT(&sc->bus);
>> + if (!object_property_set_int(bus, "bus-id", sc->spic_num, errp)) {
>> + return;
>> + }
>> +
>> + if (!sysbus_realize(SYS_BUS_DEVICE(bus), errp)) {
>> + return;
>> + }
>> +
>> + /* spi controller scoms */
>> + pnv_xscom_region_init(&sc->xscom_spic_regs, OBJECT(sc),
>> + &pnv_spi_controller_xscom_ops, sc,
>> + "xscom-spi-controller-regs",
>> + PNV10_XSCOM_PIB_SPIC_SIZE);
>> +}
>> +
>> +static int pnv_spi_controller_dt_xscom(PnvXScomInterface *dev, void
>> *fdt,
>> + int offset)
>> +{
>> + PnvSpiController *sc = PNV_SPICONTROLLER(dev);
>> + g_autofree char *name;
>> + int sc_offset;
>> + const char compat[] = "ibm,power10-spi_controller";
>> + uint32_t spic_pcba = PNV10_XSCOM_PIB_SPIC_BASE +
>> + sc->spic_num * PNV10_XSCOM_PIB_SPIC_SIZE;
>> + uint32_t reg[] = {
>> + cpu_to_be32(spic_pcba),
>> + cpu_to_be32(PNV10_XSCOM_PIB_SPIC_SIZE)
>> + };
>> + name = g_strdup_printf("spi_controller@%x", spic_pcba);
>> + sc_offset = fdt_add_subnode(fdt, offset, name);
>> + _FDT(sc_offset);
>> +
>> + _FDT(fdt_setprop(fdt, sc_offset, "reg", reg, sizeof(reg)));
>> + _FDT(fdt_setprop(fdt, sc_offset, "compatible", compat,
>> sizeof(compat)));
>> + _FDT((fdt_setprop_cell(fdt, sc_offset, "spic_num#",
>> sc->spic_num)));
>> + return 0;
>> +}
>> +
>> +static void pnv_spi_instance_init(Object *obj)
>> +{
>> + PnvSpiController *sc = PNV_SPICONTROLLER(obj);
>> +
>> + /* Initialise the bus object */
>> + object_initialize_child(obj, "bus", &sc->bus, TYPE_PNV_SPI_BUS);
>> +}
>> +
>> +static void pnv_spi_controller_class_init(ObjectClass *klass, void
>> *data)
>> +{
>> + DeviceClass *dc = DEVICE_CLASS(klass);
>> + PnvXScomInterfaceClass *xscomc = PNV_XSCOM_INTERFACE_CLASS(klass);
>> +
>> + xscomc->dt_xscom = pnv_spi_controller_dt_xscom;
>> +
>> + dc->desc = "PowerNV SPI Controller";
>> + dc->realize = pnv_spi_controller_realize;
>> + device_class_set_props(dc, pnv_spi_controller_properties);
>> +}
>> +
>> +static const TypeInfo pnv_spi_controller_info = {
>> + .name = TYPE_PNV_SPI_CONTROLLER,
>> + .parent = TYPE_DEVICE,
>> + .instance_init = pnv_spi_instance_init,
>> + .instance_size = sizeof(PnvSpiController),
>> + .class_init = pnv_spi_controller_class_init,
>> + .interfaces = (InterfaceInfo[]) {
>> + { TYPE_PNV_XSCOM_INTERFACE },
>> + { }
>> + }
>> +};
>> +
>> +static void pnv_spi_controller_register_types(void)
>> +{
>> + type_register_static(&pnv_spi_bus_info);
>> + type_register_static(&pnv_spi_controller_info);
>> +}
>> +
>> +type_init(pnv_spi_controller_register_types);
>> diff --git a/hw/ppc/Kconfig b/hw/ppc/Kconfig
>> index 37ccf9cdca..ea1178bd73 100644
>> --- a/hw/ppc/Kconfig
>> +++ b/hw/ppc/Kconfig
>> @@ -35,6 +35,7 @@ config POWERNV
>> select PCI_POWERNV
>> select PCA9552
>> select PCA9554
>> + select SSI
>> config PPC405
>> bool
>> diff --git a/hw/ppc/meson.build b/hw/ppc/meson.build
>> index d096636ee7..68fadbae7b 100644
>> --- a/hw/ppc/meson.build
>> +++ b/hw/ppc/meson.build
>> @@ -56,6 +56,7 @@ ppc_ss.add(when: 'CONFIG_POWERNV', if_true: files(
>> 'pnv_pnor.c',
>> 'pnv_nest_pervasive.c',
>> 'pnv_n1_chiplet.c',
>> + 'pnv_spi_controller.c',
>> ))
>> # PowerPC 4xx boards
>> ppc_ss.add(when: 'CONFIG_PPC405', if_true: files(
>
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 3/6] hw/ppc: SPI controller model - sequencer and shifter
2024-04-16 9:39 ` Cédric Le Goater
@ 2024-04-16 17:08 ` Chalapathi V
0 siblings, 0 replies; 16+ messages in thread
From: Chalapathi V @ 2024-04-16 17:08 UTC (permalink / raw)
To: Cédric Le Goater, qemu-devel
Cc: qemu-ppc, fbarrat, npiggin, calebs, chalapathi.v, saif.abrar,
dantan
On 16-04-2024 15:09, Cédric Le Goater wrote:
> Hello,
>
> Please rephrase the subject to something like:
>
> "ppc/pnv: Extend SPI model ..."
>
> Using a verb is preferable.
Sure. Will update. Thank You.
>
> On 4/9/24 19:56, Chalapathi V wrote:
>> In this commit SPI shift engine and sequencer logic is implemented.
>> Shift engine performs serialization and de-serialization according to
>> the
>> control by the sequencer and according to the setup defined in the
>> configuration registers. Sequencer implements the main control logic and
>> FSM to handle data transmit and data receive control of the shift
>> engine.
>>
>> Signed-off-by: Chalapathi V <chalapathi.v@linux.ibm.com>
>> ---
>> include/hw/ppc/pnv_spi_controller.h | 72 ++
>> hw/ppc/pnv_spi_controller.c | 1311 ++++++++++++++++++++++++++-
>> 2 files changed, 1382 insertions(+), 1 deletion(-)
>>
>> diff --git a/include/hw/ppc/pnv_spi_controller.h
>> b/include/hw/ppc/pnv_spi_controller.h
>> index 5ec50fb14c..ee8e7a17da 100644
>> --- a/include/hw/ppc/pnv_spi_controller.h
>> +++ b/include/hw/ppc/pnv_spi_controller.h
>> @@ -8,6 +8,14 @@
>> * This model Supports a connection to a single SPI responder.
>> * Introduced for P10 to provide access to SPI seeproms, TPM, flash
>> device
>> * and an ADC controller.
>> + *
>> + * All SPI function control is mapped into the SPI register space to
>> enable
>> + * full control by firmware.
>> + *
>> + * SPI Controller has sequencer and shift engine. The SPI shift engine
>> + * performs serialization and de-serialization according to the
>> control by
>> + * the sequencer and according to the setup defined in the
>> configuration
>> + * registers and the SPI sequencer implements the main control logic.
>> */
>> #include "hw/ssi/ssi.h"
>> @@ -21,6 +29,7 @@
>> #define SPI_CONTROLLER_REG_SIZE 8
>> typedef struct SSIBus SSIBus;
>> +typedef struct xfer_buffer xfer_buffer;
>
> Please use CamelCase names for typedef. The forward declaration doesn't
> seem useful.
Sure. Will remove and test.
>
>> #define TYPE_PNV_SPI_BUS "pnv-spi-bus"
>> OBJECT_DECLARE_SIMPLE_TYPE(PnvSPIBus, PNV_SPI_BUS)
>> @@ -33,6 +42,21 @@ typedef struct PnvSPIBus {
>> uint32_t id;
>> } PnvSPIBus;
>> +/* xfer_buffer */
>> +typedef struct xfer_buffer {
>> +
>> + uint32_t len;
>> + uint8_t *data;
>> +
>> +} xfer_buffer;
>> +
>> +uint8_t *xfer_buffer_write_ptr(xfer_buffer *payload, uint32_t offset,
>> + uint32_t length);
>> +void xfer_buffer_read_ptr(xfer_buffer *payload, uint8_t **read_buf,
>> + uint32_t offset, uint32_t length);
>> +xfer_buffer *xfer_buffer_new(void);
>> +void xfer_buffer_free(xfer_buffer *payload);
>> +
>
> I don't think these helper routines need to be defined in the header file
> of the PnvPsi model. They look internal to me.
Sure. Will move them.
>
>> typedef struct PnvSpiController {
>> DeviceState parent;
>> @@ -40,6 +64,39 @@ typedef struct PnvSpiController {
>> MemoryRegion xscom_spic_regs;
>> /* SPI controller object number */
>> uint32_t spic_num;
>> + uint8_t responder_select;
>> + /* To verify if shift_n1 happens prior to shift_n2 */
>> + bool shift_n1_done;
>> + /*
>> + * Internal flags for the first and last indicators for the SPI
>> + * interface methods
>> + */
>> + uint8_t first;
>> + uint8_t last;
>> + /* Loop counter for branch operation opcode Ex/Fx */
>> + uint8_t loop_counter_1;
>> + uint8_t loop_counter_2;
>> + /* N1/N2_bits specifies the size of the N1/N2 segment of a frame
>> in bits.*/
>> + uint8_t N1_bits;
>> + uint8_t N2_bits;
>> + /* Number of bytes in a payload for the N1/N2 frame segment.*/
>> + uint8_t N1_bytes;
>> + uint8_t N2_bytes;
>> + /* Number of N1/N2 bytes marked for transmit */
>> + uint8_t N1_tx;
>> + uint8_t N2_tx;
>> + /* Number of N1/N2 bytes marked for receive */
>> + uint8_t N1_rx;
>> + uint8_t N2_rx;
>> + /*
>> + * Setting this attribute to true will cause the engine to
>> reverse the
>> + * bit order of each byte it appends to a payload before sending
>> the
>> + * payload to a device. There may be cases where an end device
>> expects
>> + * a reversed order, like in the case of the Nuvoton TPM device.
>> The
>> + * order of bytes in the payload is not reversed, only the order
>> of the
>> + * 8 bits in each payload byte.
>> + */
>> + bool reverse_bits;
>> /* SPI Controller registers */
>> uint64_t error_reg;
>> @@ -52,4 +109,19 @@ typedef struct PnvSpiController {
>> uint8_t sequencer_operation_reg[SPI_CONTROLLER_REG_SIZE];
>> uint64_t status_reg;
>> } PnvSpiController;
>> +
>> +void log_all_N_counts(PnvSpiController *spi_controller);
>> +void spi_response(PnvSpiController *spi_controller, int bits,
>> + xfer_buffer *rsp_payload);
>> +void operation_sequencer(PnvSpiController *spi_controller);
>> +bool operation_shiftn1(PnvSpiController *spi_controller, uint8_t
>> opcode,
>> + xfer_buffer **payload, bool send_n1_alone);
>> +bool operation_shiftn2(PnvSpiController *spi_controller, uint8_t
>> opcode,
>> + xfer_buffer **payload);
>> +bool does_rdr_match(PnvSpiController *spi_controller);
>> +uint8_t get_from_offset(PnvSpiController *spi_controller, uint8_t
>> offset);
>> +void shift_byte_in(PnvSpiController *spi_controller, uint8_t byte);
>> +void calculate_N1(PnvSpiController *spi_controller, uint8_t opcode);
>> +void calculate_N2(PnvSpiController *spi_controller, uint8_t opcode);
>> +uint8_t reverse_bits8(uint8_t x);
>
> These routines are internal and belong to hw/ppc/pnv_spi_controller.c.
> Please
> don't export them. Also, adding a pnv_psi_ prefix would be a plus to
> identify
> the sympbols.
Will move them to pnv_spi_controller.c. Thank You.
>
>
>> #endif /* PPC_PNV_SPI_CONTROLLER_H */
>> diff --git a/hw/ppc/pnv_spi_controller.c b/hw/ppc/pnv_spi_controller.c
>> index e2478a47f2..afe7f17565 100644
>> --- a/hw/ppc/pnv_spi_controller.c
>> +++ b/hw/ppc/pnv_spi_controller.c
>> @@ -52,6 +52,11 @@ static uint64_t pnv_spi_controller_read(void
>> *opaque, hwaddr addr,
>> val));
>> sc->status_reg = SETFIELD(STATUS_REG_RDR_FULL,
>> sc->status_reg, 0);
>> SPI_DEBUG(qemu_log("RDR being read, RDR_full set to 0\n"));
>> + if (GETFIELD(STATUS_REG_SHIFTER_FSM, sc->status_reg) ==
>> FSM_WAIT) {
>> + SPI_DEBUG(qemu_log("RDR being read while shifter is
>> waiting, "
>> + "starting sequencer\n"));
>
> Please use trace events instead.
Sure, will update.
>
>> + operation_sequencer(sc);
>> + }
>> break;
>> case SEQUENCER_OPERATION_REG:
>> val = 0;
>> @@ -124,13 +129,15 @@ static void pnv_spi_controller_write(void
>> *opaque, hwaddr addr,
>> sc->status_reg = SETFIELD(STATUS_REG_TDR_UNDERRUN,
>> sc->status_reg, 0);
>> SPI_DEBUG(qemu_log("TDR being written, TDR_underrun set to
>> 0\n"));
>> SPI_DEBUG(qemu_log("TDR being written, starting
>> sequencer\n"));
>> + operation_sequencer(sc);
>> +
>> break;
>> case RECEIVE_DATA_REG:
>> sc->receive_data_reg = val;
>> break;
>> case SEQUENCER_OPERATION_REG:
>> for (int i = 0; i < SPI_CONTROLLER_REG_SIZE; i++) {
>> - sc->sequencer_operation_reg[i] = (val >> (56 - i * 8)) & 0xFF;
>> + sc->sequencer_operation_reg[i] = (val >> (56 - i * 8)) &
>> 0xFF;
>
> This change belongs to the previous patch.
Yes, Thank you for catching. will update in v3.
>
>> }
>> break;
>> case STATUS_REG:
>> @@ -157,6 +164,1306 @@ static const MemoryRegionOps
>> pnv_spi_controller_xscom_ops = {
>> .endianness = DEVICE_BIG_ENDIAN,
>> };
>> +/* xfer_buffer_methods */
>> +xfer_buffer *xfer_buffer_new(void)
>> +{
>> + xfer_buffer *payload = g_malloc0(sizeof(*payload));
>> +
>> + payload->data = g_malloc0(payload->len);
>
> euh. payload->len is 0. This allocation is pointless.
>
>> + return payload;
>> +}
>> +
>> +void xfer_buffer_free(xfer_buffer *payload)
>> +{
>> + free(payload->data);
>> + free(payload);
>> +}
>> +
>> +uint8_t *xfer_buffer_write_ptr(xfer_buffer *payload, uint32_t offset,
>> + uint32_t length)
>> +{
>> + if (payload->len < (offset + length)) {
>> + payload->len = offset + length;
>> + payload->data = g_realloc(payload->data, payload->len);
>> + }
>> + return &payload->data[offset];
>> +}
>> +
>> +void xfer_buffer_read_ptr(xfer_buffer *payload, uint8_t **read_buf,
>> + uint32_t offset, uint32_t length)
>> +{
>> + static uint32_t prev_len;
>> + if ((prev_len != length) || (*read_buf == NULL)) {
>> + *read_buf = g_realloc(*read_buf, length);
>> + prev_len = length;
>> + }
>> + if (offset > payload->len) {
>> + /* Reading outside payload, just return */
>> + return;
>> + }
>> + *read_buf = &payload->data[offset];
>> +}
>
> Isn't there a maximum buffer size ? It would simplify the implementation
> and avoid all these alloc/realloc/free calls.
Will check and rework on this. Thank You.
>
>> +uint8_t reverse_bits8(uint8_t x)
>> +{
>> + x = (x << 4) | (x >> 4);
>> + x = ((x & 0x33) << 2) | ((x & 0xcc) >> 2);
>> + x = ((x & 0x55) << 1) | ((x & 0xaa) >> 1);
>> + return x;
>> +}
>
> revbit8() in qemu/host-utils.h should do the same.
>
> The rest is difficult to comment on without an intimitate knowledge of
> the controller and the datasheet. Can teammates help ?
>
> Thanks,
>
> C.
>
>
>
>> +bool does_rdr_match(PnvSpiController *sc)
>> +{
>> + /*
>> + * According to spec, the mask bits that are 0 are compared and the
>> + * bits that are 1 are ignored.
>> + */
>> + uint16_t rdr_match_mask =
>> GETFIELD(MEMORY_MAPPING_REG_RDR_MATCH_MASK,
>> + sc->memory_mapping_reg);
>> + uint16_t rdr_match_val = GETFIELD(MEMORY_MAPPING_REG_RDR_MATCH_VAL,
>> + sc->memory_mapping_reg);
>> + if ((~rdr_match_mask & rdr_match_val) == ((~rdr_match_mask) &
>> + GETFIELD(PPC_BITMASK(48, 63), sc->receive_data_reg))) {
>> + SPI_DEBUG(qemu_log("RDR match successful, match=0x%4.4x, "
>> + "mask=0x%4.4x,
>> RDR[48:63]=0x%4.4llx\n",
>> + rdr_match_val, rdr_match_mask,
>> + GETFIELD(PPC_BITMASK(48, 63),
>> + sc->receive_data_reg)));
>> + return true;
>> + } else {
>> + SPI_DEBUG(qemu_log("RDR match failed, match=0x%4.4x,
>> mask=0x%4.4x, "
>> + "RDR[48:63]=0x%4.4llx\n", rdr_match_val, rdr_match_mask,
>> + GETFIELD(PPC_BITMASK(48, 63), sc->receive_data_reg)));
>> + return false;
>> + }
>> +}
>> +
>> +uint8_t get_from_offset(PnvSpiController *sc, uint8_t offset)
>> +{
>> + uint8_t byte;
>> +
>> + /*
>> + * Offset is an index between 0 and SPI_CONTROLLER_REG_SIZE - 1
>> + * Check the offset before using it.
>> + */
>> + if (offset < SPI_CONTROLLER_REG_SIZE) {
>> + byte = (sc->transmit_data_reg >> (56 - offset * 8)) & 0xFF;
>> + } else {
>> + /*
>> + * Log an error and return a 0xFF since we have to assign
>> something
>> + * to byte before returning.
>> + */
>> + qemu_log_mask(LOG_GUEST_ERROR, "Invalid offset = %d used to
>> get byte "
>> + "from TDR\n", offset);
>> + byte = 0xff;
>> + }
>> + return byte;
>> +}
>> +
>> +void shift_byte_in(PnvSpiController *sc, uint8_t byte)
>> +{
>> + sc->receive_data_reg = (sc->receive_data_reg << 8) | byte;
>> + SPI_DEBUG(qemu_log("0x%2.2x shifted in, RDR now = 0x%16.16lx\n",
>> byte,
>> + sc->receive_data_reg));
>> +}
>> +
>> +void spi_response(PnvSpiController *sc, int bits, xfer_buffer
>> *rsp_payload)
>> +{
>> + uint8_t *read_buf = NULL;
>> + uint8_t ecc_count;
>> + uint8_t shift_in_count;
>> +
>> + /*
>> + * Processing here must handle:
>> + * - Which bytes in the payload we should move to the RDR
>> + * - Explicit mode counter configuration settings
>> + * - RDR full and RDR overrun status
>> + */
>> +
>> + /*
>> + * First check that the response payload is the exact same
>> + * number of bytes as the request payload was
>> + */
>> + if (rsp_payload->len != (sc->N1_bytes + sc->N2_bytes)) {
>> + qemu_log_mask(LOG_GUEST_ERROR, "Invalid response payload
>> size in "
>> + "bytes, expected %d, got %d\n",
>> + (sc->N1_bytes + sc->N2_bytes),
>> rsp_payload->len);
>> + } else {
>> + uint8_t ecc_control;
>> + SPI_DEBUG(qemu_log("SPI response received, payload len = %d\n",
>> + rsp_payload->len));
>> + log_all_N_counts(sc);
>> + /*
>> + * Adding an ECC count let's us know when we have found a
>> payload byte
>> + * that was shifted in but cannot be loaded into RDR. Bits
>> 29-30
>> + * equal to either 0b00 or 0b10 indicate that we are taking
>> in data
>> + * with ECC and either applying the ECC or discarding it.
>> + */
>> + ecc_count = 0;
>> + ecc_control = GETFIELD(PPC_BITMASK(29, 30),
>> + sc->clock_config_reset_control);
>> + if (ecc_control == 0 || ecc_control == 2) {
>> + ecc_count = 1;
>> + }
>> + /*
>> + * Use the N1_rx and N2_rx counts to control shifting data
>> from the
>> + * payload into the RDR. Keep an overall count of the
>> number of bytes
>> + * shifted into RDR so we can discard every 9th byte when
>> ECC is
>> + * enabled.
>> + */
>> + shift_in_count = 0;
>> + /* Handle the N1 portion of the frame first */
>> + if (sc->N1_rx != 0) {
>> + uint8_t n1_count = 0;
>> + while (n1_count < sc->N1_bytes) {
>> + shift_in_count++;
>> + xfer_buffer_read_ptr(rsp_payload, &read_buf,
>> n1_count, 1);
>> + if ((ecc_count != 0) &&
>> + (shift_in_count == (SPI_CONTROLLER_REG_SIZE +
>> ecc_count))) {
>> + SPI_DEBUG(qemu_log("Discarding rx N1 ECC byte =
>> 0x%2.2x at "
>> + "payload index = %d\n", read_buf[0],
>> n1_count));
>> + shift_in_count = 0;
>> + } else {
>> + uint8_t n1_byte = 0x00;
>> + n1_byte = read_buf[0];
>> + SPI_DEBUG(qemu_log("Extracting rx n1_byte =
>> 0x%2.2x from "
>> + "payload at index = %d\n", n1_byte,
>> n1_count));
>> + if (sc->reverse_bits) {
>> + SPI_DEBUG(qemu_log("Reversing bit order of rx "
>> + "n1_byte\n"));
>> + n1_byte = reverse_bits8(n1_byte);
>> + }
>> + SPI_DEBUG(qemu_log("Shifting rx N1 byte =
>> 0x%2.2x into "
>> + "RDR\n", n1_byte));
>> + shift_byte_in(sc, n1_byte);
>> + }
>> + n1_count++;
>> + } /* end of while */
>> + }
>> + /* Handle the N2 portion of the frame */
>> + if (sc->N2_rx != 0) {
>> + uint8_t n2_count = 0;
>> + while (n2_count < sc->N2_bytes) {
>> + shift_in_count++;
>> + xfer_buffer_read_ptr(rsp_payload, &read_buf,
>> + (sc->N1_bytes + n2_count), 1);
>> + if ((ecc_count != 0) &&
>> + (shift_in_count == (SPI_CONTROLLER_REG_SIZE +
>> ecc_count))) {
>> + SPI_DEBUG(qemu_log("Discarding rx N1 ECC byte =
>> 0x%2.2x at "
>> + "payload index = %d\n", read_buf[0],
>> + (sc->N1_bytes + n2_count)));
>> + shift_in_count = 0;
>> + } else {
>> + /*
>> + * The code handles shifting data from the
>> payload received
>> + * from the responder into the responder's RDR.
>> Since this
>> + * is an N2 frame segment it is safe to assume
>> that there
>> + * was a preceding N1 segment which was combined
>> with an N2
>> + * segment to create a single frame. The
>> response data will
>> + * then have N1_bytes of data in the payload
>> representing a
>> + * responder response to the N1 section of the
>> frame. If N2
>> + * is set to receive the shifting for N2 data
>> begins after
>> + * the N1 bytes regardless of whether or not N1
>> was marked
>> + * for transmit or receive.
>> + */
>> + uint8_t n2_byte = 0x00;
>> + n2_byte = read_buf[0];
>> + SPI_DEBUG(qemu_log("Extracting rx n2_byte =
>> 0x%2.2x from "
>> + "payload at index = %d\n", n2_byte,
>> + (sc->N1_bytes + n2_count)));
>> + if (sc->reverse_bits) {
>> + SPI_DEBUG(qemu_log("Reversing bit order of rx "
>> + "n2_byte\n"));
>> + n2_byte = reverse_bits8(n2_byte);
>> + }
>> + SPI_DEBUG(qemu_log("Shifting rx N2 byte =
>> 0x%2.2x into "
>> + "RDR\n", n2_byte));
>> + shift_byte_in(sc, n2_byte);
>> + }
>> + n2_count++;
>> + }
>> + }
>> + if ((sc->N1_rx + sc->N2_rx) > 0) {
>> + /*
>> + * Data was received so handle RDR status.
>> + * It is easier to handle RDR_full and RDR_overrun
>> status here
>> + * since the RDR register's shift_byte_in method is called
>> + * multiple times in a row. Controlling RDR status is
>> done here
>> + * instead of in the RDR scoped methods for that reason.
>> + */
>> + if (GETFIELD(STATUS_REG_RDR_FULL, sc->status_reg) == 1) {
>> + /*
>> + * Data was shifted into the RDR before having been
>> read
>> + * causing previous data to have been overrun.
>> + */
>> + sc->status_reg = SETFIELD(STATUS_REG_RDR_OVERRUN,
>> + sc->status_reg, 1);
>> + } else {
>> + /*
>> + * Set status to indicate that the received data
>> register is
>> + * full. This flag is only cleared once the RDR is
>> unloaded.
>> + */
>> + sc->status_reg = SETFIELD(STATUS_REG_RDR_FULL,
>> + sc->status_reg, 1);
>> + SPI_DEBUG(qemu_log("RDR_full set to 1\n"));
>> + }
>> + }
>> + } /* end of else */
>> +} /* end of spi_response() */
>> +
>> +void log_all_N_counts(PnvSpiController *sc)
>> +{
>> + SPI_DEBUG(qemu_log("N1_bits = %d, N1_bytes = %d, N1_tx = %d,
>> N1_rx = %d, "
>> + "N2_bits = %d, N2_bytes = %d, N2_tx = %d, N2_rx = %d\n",
>> + sc->N1_bits, sc->N1_bytes, sc->N1_tx, sc->N1_rx,
>> sc->N2_bits,
>> + sc->N2_bytes, sc->N2_tx, sc->N2_rx));
>> +}
>> +
>> +static inline void next_sequencer_fsm(PnvSpiController *sc)
>> +{
>> + uint8_t seq_index = GETFIELD(STATUS_REG_SEQUENCER_INDEX,
>> sc->status_reg);
>> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_INDEX,
>> sc->status_reg,
>> + (seq_index + 1));
>> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM, sc->status_reg,
>> + SEQ_STATE_INDEX_INCREMENT);
>> +}
>> +
>> +void operation_sequencer(PnvSpiController *sc)
>> +{
>> + /*
>> + * Loop through each sequencer operation ID and perform the
>> requested
>> + * operations.
>> + * Flag for indicating if we should send the N1 frame or wait to
>> combine
>> + * it with a preceding N2 frame.
>> + */
>> + bool send_n1_alone = true;
>> + bool stop = false; /* Flag to stop the sequencer */
>> + uint8_t opcode = 0;
>> + uint8_t masked_opcode = 0;
>> +
>> + /*
>> + * xfer_buffer for containing the payload of the SPI frame.
>> + * This is a static because there are cases where a sequence has
>> to stop
>> + * and wait for the target application to unload the RDR. If
>> this occurs
>> + * during a sequence where N1 is not sent alone and instead
>> combined with
>> + * N2 since the N1 tx length + the N2 tx length is less than the
>> size of
>> + * the TDR.
>> + */
>> + static xfer_buffer *payload;
>> +
>> + if (payload == NULL) {
>> + payload = xfer_buffer_new();
>> + }
>> + /*
>> + * Clear the sequencer FSM error bit - general_SPI_status[3]
>> + * before starting a sequence.
>> + */
>> + sc->status_reg = SETFIELD(PPC_BIT(35), sc->status_reg, 0);
>> + /*
>> + * If the FSM is idle set the sequencer index to 0
>> + * (new/restarted sequence)
>> + */
>> + if (GETFIELD(STATUS_REG_SEQUENCER_FSM, sc->status_reg) ==
>> + SEQ_STATE_IDLE) {
>> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_INDEX,
>> + sc->status_reg, 0);
>> + }
>> + /*
>> + * There are only 8 possible operation IDs to iterate through
>> though
>> + * some operations may cause more than one frame to be sequenced.
>> + */
>> + while (GETFIELD(STATUS_REG_SEQUENCER_INDEX, sc->status_reg) < 8) {
>> + opcode = sc->sequencer_operation_reg[GETFIELD(
>> + STATUS_REG_SEQUENCER_INDEX, sc->status_reg)];
>> + /* Set sequencer state to decode */
>> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
>> sc->status_reg,
>> + SEQ_STATE_DECODE);
>> + /*
>> + * Only the upper nibble of the operation ID is needed to
>> know what
>> + * kind of operation is requested.
>> + */
>> + masked_opcode = opcode & 0xF0;
>> + switch (masked_opcode) {
>> + /*
>> + * Increment the operation index in each case instead of just
>> + * once at the end in case an operation like the branch
>> + * operation needs to change the index.
>> + */
>> + case SEQ_OP_STOP:
>> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
>> + sc->status_reg,
>> SEQ_STATE_EXECUTE);
>> + /* A stop operation in any position stops the sequencer */
>> + SPI_DEBUG(qemu_log("Sequencer STOP at index = 0x%llx,
>> sequencer "
>> + "idling\n", GETFIELD(
>> + STATUS_REG_SEQUENCER_INDEX,
>> + sc->status_reg)));
>> + stop = true;
>> + sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM,
>> sc->status_reg,
>> + FSM_IDLE);
>> + sc->loop_counter_1 = 0;
>> + sc->loop_counter_2 = 0;
>> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
>> + sc->status_reg, SEQ_STATE_IDLE);
>> + break;
>> +
>> + case SEQ_OP_SELECT_SLAVE:
>> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
>> + sc->status_reg,
>> SEQ_STATE_EXECUTE);
>> + SPI_DEBUG(qemu_log("Sequencer SELECT_SLAVE at index =
>> 0x%llx\n",
>> + GETFIELD(STATUS_REG_SEQUENCER_INDEX, sc->status_reg)));
>> + /*
>> + * This device currently only supports a single responder
>> + * connection at position 0. De-selecting a responder
>> is fine
>> + * and expected at the end of a sequence but selecting any
>> + * responder other than 0 should cause an error.
>> + */
>> + sc->responder_select = opcode & 0x0F;
>> + if (sc->responder_select == 0) {
>> + SPI_DEBUG(qemu_log("Shifter done, pull the CS line
>> high\n"));
>> + qemu_set_irq((sc->bus).cs_line[0], 1);
>> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_INDEX,
>> + sc->status_reg,
>> + (GETFIELD(
>> + STATUS_REG_SEQUENCER_INDEX,
>> + sc->status_reg) + 1));
>> + sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM,
>> + sc->status_reg, FSM_DONE);
>> + } else if (sc->responder_select != 1) {
>> + qemu_log_mask(LOG_GUEST_ERROR, "Slave selection
>> other than 1 "
>> + "not supported, select = 0x%x\n",
>> + sc->responder_select);
>> + SPI_DEBUG(qemu_log("Sequencer stop requested due to
>> invalid "
>> + "responder select at index =
>> 0x%llx, "
>> + "shifter idling\n", GETFIELD(
>> + STATUS_REG_SEQUENCER_INDEX,
>> + sc->status_reg)));
>> + sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM,
>> + sc->status_reg, FSM_IDLE);
>> + stop = true;
>> + } else {
>> + /*
>> + * Only allow an FSM_START state when a responder is
>> + * selected
>> + */
>> + sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM,
>> + sc->status_reg, FSM_START);
>> + SPI_DEBUG(qemu_log("shifter starting, pull CS line
>> low\n"));
>> + qemu_set_irq((sc->bus).cs_line[0], 0);
>> + sc->first = 1;
>> + sc->last = 0;
>> + /*
>> + * A Shift_N2 operation is only valid after a Shift_N1
>> + * according to the spec. The spec doesn't say if
>> that means
>> + * immediately after or just after at any point. We
>> will track
>> + * the occurrence of a Shift_N1 to enforce this
>> requirement in
>> + * the most generic way possible by assuming that
>> the rule
>> + * applies once a valid responder select has occurred.
>> + */
>> + sc->shift_n1_done = false;
>> + next_sequencer_fsm(sc);
>> + }
>> + break;
>> +
>> + case SEQ_OP_SHIFT_N1:
>> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
>> + sc->status_reg,
>> SEQ_STATE_EXECUTE);
>> + SPI_DEBUG(qemu_log("Sequencer SHIFT_N1 at index =
>> 0x%llx\n",
>> + GETFIELD(STATUS_REG_SEQUENCER_INDEX, sc->status_reg)));
>> + /*
>> + * Only allow a shift_n1 when the state is not IDLE or
>> DONE.
>> + * In either of those two cases the sequencer is not in
>> a proper
>> + * state to perform shift operations because the
>> sequencer has:
>> + * - processed a responder deselect (DONE)
>> + * - processed a stop opcode (IDLE)
>> + * - encountered an error (IDLE)
>> + */
>> + if ((GETFIELD(STATUS_REG_SHIFTER_FSM,
>> + sc->status_reg) == FSM_IDLE) ||
>> + (GETFIELD(STATUS_REG_SHIFTER_FSM,
>> + sc->status_reg) == FSM_DONE)) {
>> + qemu_log_mask(LOG_GUEST_ERROR, "Shift_N1 not allowed
>> in "
>> + "shifter state = 0x%llx", GETFIELD(
>> + STATUS_REG_SHIFTER_FSM, sc->status_reg));
>> + /*
>> + * Set sequencer FSM error bit 3
>> (general_SPI_status[3])
>> + * in status reg.
>> + */
>> + sc->status_reg = SETFIELD(PPC_BIT(35),
>> sc->status_reg, 1);
>> + SPI_DEBUG(qemu_log("Sequencer stop requested due to
>> invalid "
>> + "shifter state at index = 0x%llx\n", GETFIELD(
>> + STATUS_REG_SEQUENCER_INDEX, sc->status_reg)));
>> + stop = true;
>> + } else {
>> + /*
>> + * Look for the special case where there is a
>> shift_n1 set for
>> + * transmit and it is followed by a shift_n2 set for
>> transmit
>> + * AND the combined transmit length of the two
>> operations is
>> + * less than or equal to the size of the TDR
>> register. In this
>> + * case we want to use both this current shift_n1
>> opcode and the
>> + * following shift_n2 opcode to assemble the frame for
>> + * transmission to the responder without requiring a
>> refill of
>> + * the TDR between the two operations.
>> + */
>> + if ((sc->sequencer_operation_reg[GETFIELD(
>> + STATUS_REG_SEQUENCER_INDEX, sc->status_reg) +
>> 1] & 0xF0)
>> + == SEQ_OP_SHIFT_N2) {
>> + SPI_DEBUG(qemu_log("Not sending N1 alone\n"));
>> + send_n1_alone = false;
>> + }
>> + /*
>> + * If the next opcode is 0x10, which deselects the
>> SPI device
>> + * then this is the last shift
>> + */
>> + if (sc->sequencer_operation_reg[GETFIELD(
>> + STATUS_REG_SEQUENCER_INDEX, sc->status_reg) + 1] ==
>> + SEQ_OP_SELECT_SLAVE) {
>> + sc->last = 1;
>> + }
>> + sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM,
>> + sc->status_reg, FSM_SHIFT_N1);
>> + stop = operation_shiftn1(sc, opcode, &payload,
>> send_n1_alone);
>> + if (stop) {
>> + /*
>> + * The operation code says to stop, this can
>> occur if:
>> + * (1) RDR is full and the N1 shift is set for
>> receive
>> + * (2) TDR was empty at the time of the N1 shift
>> so we need
>> + * to wait for data.
>> + * (3) Neither 1 nor 2 are occurring and we
>> aren't sending
>> + * N1 alone and N2 counter reload is set (bit 0
>> of the N2
>> + * counter reload field). In this case
>> TDR_underrun will
>> + * will be set and the Payload has been loaded
>> so it is
>> + * ok to advance the sequencer.
>> + */
>> + if (GETFIELD(STATUS_REG_TDR_UNDERRUN,
>> sc->status_reg)) {
>> + SPI_DEBUG(qemu_log("Sequencer stop requested
>> due to N2 "
>> + "counter reload active.\n"));
>> + sc->shift_n1_done = true;
>> + sc->status_reg =
>> SETFIELD(STATUS_REG_SHIFTER_FSM,
>> + sc->status_reg,
>> + FSM_SHIFT_N2);
>> + sc->status_reg =
>> SETFIELD(STATUS_REG_SEQUENCER_INDEX,
>> + sc->status_reg,
>> + (GETFIELD(
>> + STATUS_REG_SEQUENCER_INDEX,
>> + sc->status_reg) + 1));
>> + SPI_DEBUG(qemu_log("Set new sequencer index
>> to = "
>> + "0x%llx\n", GETFIELD(
>> + STATUS_REG_SEQUENCER_INDEX,
>> + sc->status_reg)));
>> + } else {
>> + /*
>> + * This is case (1) or (2) so the sequencer
>> needs to
>> + * wait and NOT go to the next sequence yet.
>> + */
>> + sc->status_reg =
>> SETFIELD(STATUS_REG_SHIFTER_FSM,
>> + sc->status_reg, FSM_WAIT);
>> + }
>> + } else {
>> + /* Ok to move on to the next index */
>> + sc->shift_n1_done = true;
>> + next_sequencer_fsm(sc);
>> + }
>> + }
>> + break;
>> +
>> + case SEQ_OP_SHIFT_N2:
>> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
>> + sc->status_reg,
>> SEQ_STATE_EXECUTE);
>> + SPI_DEBUG(qemu_log("Sequencer SHIFT_N2 at index = %lld\n",
>> + GETFIELD(STATUS_REG_SEQUENCER_INDEX,
>> + sc->status_reg)));
>> + if (!sc->shift_n1_done) {
>> + qemu_log_mask(LOG_GUEST_ERROR, "Shift_N2 is not
>> allowed if a "
>> + "Shift_N1 is not done, shifter state =
>> 0x%llx",
>> + GETFIELD(STATUS_REG_SHIFTER_FSM,
>> + sc->status_reg));
>> + /*
>> + * In case the sequencer actually stops if an N2
>> shift is
>> + * requested before any N1 shift is done. Set
>> sequencer FSM
>> + * error bit 3 (general_SPI_status[3]) in status reg.
>> + */
>> + sc->status_reg = SETFIELD(PPC_BIT(35),
>> sc->status_reg, 1);
>> + SPI_DEBUG(qemu_log("Sequencer stop requested due to
>> shift_n2 "
>> + "w/no shift_n1 done at index =
>> 0x%llx\n",
>> + GETFIELD(STATUS_REG_SEQUENCER_INDEX,
>> + sc->status_reg)));
>> + stop = true;
>> + } else {
>> + /*
>> + * If the next opcode is 0x10, which deselects the
>> SPI device
>> + * then this is the last shift
>> + */
>> + if (sc->sequencer_operation_reg[GETFIELD(
>> + STATUS_REG_SEQUENCER_INDEX,
>> + sc->status_reg) + 1] == SEQ_OP_SELECT_SLAVE) {
>> + sc->last = 1;
>> + }
>> + /* Ok to do a Shift_N2 */
>> + sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM,
>> + sc->status_reg,
>> FSM_SHIFT_N2);
>> + stop = operation_shiftn2(sc, opcode, &payload);
>> + /*
>> + * If the operation code says to stop set the
>> shifter state to
>> + * wait and stop
>> + */
>> + if (stop) {
>> + sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM,
>> + sc->status_reg, FSM_WAIT);
>> + } else {
>> + /* Ok to move on to the next index */
>> + next_sequencer_fsm(sc);
>> + }
>> + }
>> + break;
>> +
>> + case SEQ_OP_BRANCH_IFNEQ_RDR:
>> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
>> + sc->status_reg,
>> SEQ_STATE_EXECUTE);
>> + SPI_DEBUG(qemu_log("Sequencer BRANCH_IFNEQ_RDR at "
>> + "index = 0x%llx\n", GETFIELD(
>> + STATUS_REG_SEQUENCER_INDEX,
>> sc->status_reg)));
>> + /*
>> + * The memory mapping register RDR match value is
>> compared against
>> + * the 16 rightmost bytes of the RDR (potentially with
>> masking).
>> + * Since this comparison is performed against the
>> contents of the
>> + * RDR then a receive must have previously occurred
>> otherwise
>> + * there is no data to compare and the operation cannot be
>> + * completed and will stop the sequencer until RDR full
>> is set to
>> + * 1.
>> + */
>> + if (GETFIELD(STATUS_REG_RDR_FULL, sc->status_reg) == 1) {
>> + bool rdr_matched = false;
>> + rdr_matched = does_rdr_match(sc);
>> + if (rdr_matched) {
>> + SPI_DEBUG(qemu_log("Proceed to next sequencer
>> index "
>> + "(increment on RDR match)\n"));
>> + /* A match occurred, increment the sequencer
>> index. */
>> + next_sequencer_fsm(sc);
>> + } else {
>> + SPI_DEBUG(qemu_log("Proceed to sequencer
>> index=0x%x "
>> + "(branch on RDR match fail)\n", (opcode &
>> 0x7)));
>> + /*
>> + * Branch the sequencer to the index coded into
>> the op
>> + * code.
>> + */
>> + sc->status_reg =
>> SETFIELD(STATUS_REG_SEQUENCER_INDEX,
>> + sc->status_reg, (opcode & 0x7));
>> + }
>> + /*
>> + * Regardless of where the branch ended up we want the
>> + * sequencer to continue shifting so we have to clear
>> + * RDR_full.
>> + */
>> + sc->status_reg = SETFIELD(STATUS_REG_RDR_FULL,
>> + sc->status_reg, 0);
>> + } else {
>> + SPI_DEBUG(qemu_log("RDR not full for 0x6x opcode!
>> Stopping "
>> + "sequencer.\n"));
>> + stop = true;
>> + sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM,
>> + sc->status_reg, FSM_WAIT);
>> + }
>> + break;
>> +
>> + case SEQ_OP_TRANSFER_TDR:
>> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
>> + sc->status_reg,
>> SEQ_STATE_EXECUTE);
>> + qemu_log_mask(LOG_GUEST_ERROR, "Transfer TDR is not
>> supported\n");
>> + next_sequencer_fsm(sc);
>> + break;
>> +
>> + case SEQ_OP_BRANCH_IFNEQ_INC_1:
>> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
>> + sc->status_reg,
>> SEQ_STATE_EXECUTE);
>> + SPI_DEBUG(qemu_log("Sequencer BRANCH_IFNEQ_INC_1 at
>> index = "
>> + "0x%llx, next index = %d,
>> count_compare_1 = "
>> + "0x%llx, loop_counter_1 = %d\n",
>> GETFIELD(
>> + STATUS_REG_SEQUENCER_INDEX,
>> sc->status_reg),
>> + (opcode & 0x07),
>> + GETFIELD(COUNTER_CONFIG_REG_COUNT_COMPARE1,
>> + sc->status_reg), sc->loop_counter_1));
>> + /*
>> + * The spec says the loop should execute count compare +
>> 1 times.
>> + * However we learned from engineering that we really
>> only loop
>> + * count_compare times, count compare = 0 makes this op
>> code a
>> + * no-op
>> + */
>> + if (sc->loop_counter_1 !=
>> + GETFIELD(COUNTER_CONFIG_REG_COUNT_COMPARE1,
>> + sc->counter_config_reg)) {
>> + /*
>> + * If the next opcode is 0x10, which deselects the
>> SPI device
>> + * and we know that the next opcode is the last one
>> in the
>> + * loop then the next shift is the last shift
>> + */
>> + uint8_t condition1 = sc->sequencer_operation_reg[
>> + GETFIELD(STATUS_REG_SEQUENCER_INDEX,
>> + sc->status_reg) + 1];
>> + uint8_t condition2 =
>> GETFIELD(COUNTER_CONFIG_REG_COUNT_COMPARE1,
>> + sc->counter_config_reg);
>> +
>> + if ((condition1 == SEQ_OP_SELECT_SLAVE) &&
>> + ((sc->loop_counter_1 + 1) == condition2)) {
>> + sc->last = 1;
>> + }
>> + /*
>> + * Next index is the lower nibble of the branch
>> operation ID,
>> + * mask off all but the first three bits so we don't
>> try to
>> + * access beyond the sequencer_operation_reg boundary.
>> + */
>> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_INDEX,
>> + sc->status_reg, (opcode &
>> 0x7));
>> + sc->loop_counter_1++;
>> + SPI_DEBUG(qemu_log("Branching to index = %d,
>> loop_counter_1 = "
>> + "%d\n", (opcode & 0x7), sc->loop_counter_1));
>> + } else {
>> + /* Continue to next index if loop counter is reached */
>> + next_sequencer_fsm(sc);
>> + SPI_DEBUG(qemu_log("loop counter 1 achieved, next
>> sequencer "
>> + "index = 0x%llx\n",
>> GETFIELD(STATUS_REG_SEQUENCER_INDEX,
>> + sc->status_reg)));
>> + }
>> + break;
>> +
>> + case SEQ_OP_BRANCH_IFNEQ_INC_2:
>> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
>> + sc->status_reg,
>> SEQ_STATE_EXECUTE);
>> + SPI_DEBUG(qemu_log("Sequencer BRANCH_IFNEQ_INC_2 at
>> index = "
>> + "0x%llx, next index = %d,
>> count_compare_2 = "
>> + "0x%llx, loop_counter_2 = %d\n",
>> GETFIELD(
>> + STATUS_REG_SEQUENCER_INDEX,
>> sc->status_reg),
>> + (opcode & 0x07), GETFIELD(
>> + COUNTER_CONFIG_REG_COUNT_COMPARE2,
>> + sc->status_reg), sc->loop_counter_2));
>> + /*
>> + * If the next opcode is 0x10, which deselects the SPI
>> device
>> + * and we know that the next opcode is the last one in the
>> + * loop then the next shift is the last shift
>> + */
>> + uint8_t condition1 = sc->sequencer_operation_reg[
>> + GETFIELD(STATUS_REG_SEQUENCER_INDEX,
>> + sc->status_reg) + 1];
>> + uint8_t condition2 =
>> GETFIELD(COUNTER_CONFIG_REG_COUNT_COMPARE2,
>> + sc->counter_config_reg);
>> +
>> + if ((condition1 == SEQ_OP_SELECT_SLAVE) &&
>> + ((sc->loop_counter_2 + 1) == condition2)) {
>> + sc->last = 1;
>> + }
>> + /*
>> + * The spec says the loop should execute count compare +
>> 1 times.
>> + * However we learned from engineering that we really
>> only loop
>> + * count_compare times, count compare = 0 makes this op
>> code a
>> + * no-op
>> + */
>> + if (sc->loop_counter_2 != condition2) {
>> + /*
>> + * Next index is the lower nibble of the branch
>> operation ID,
>> + * mask off all but the first three bits so we don't
>> try to
>> + * access beyond the sequencer_operation_reg boundary.
>> + */
>> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_INDEX,
>> + sc->status_reg,
>> + (opcode & 0x7));
>> + sc->loop_counter_2++;
>> + SPI_DEBUG(qemu_log("Branching to index = %d,
>> loop_counter_2 "
>> + "= %d", (opcode & 0x7),
>> + sc->loop_counter_2));
>> + } else {
>> + /* Continue to next index if loop counter is reached */
>> + next_sequencer_fsm(sc);
>> + SPI_DEBUG(qemu_log("loop counter 2 achieved, next
>> sequencer "
>> + "index = 0x%llx\n",
>> GETFIELD(STATUS_REG_SEQUENCER_INDEX,
>> + sc->status_reg)));
>> + }
>> + break;
>> +
>> + default:
>> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
>> + sc->status_reg,
>> SEQ_STATE_EXECUTE);
>> + qemu_log_mask(LOG_GUEST_ERROR, "Sequencer opcode 0x%x is
>> not "
>> + "supported\n", opcode);
>> + /* Ignore unsupported operations. */
>> + next_sequencer_fsm(sc);
>> + break;
>> + } /* end of switch */
>> + /*
>> + * If we used all 8 opcodes without seeing a 00 - STOP in
>> the sequence
>> + * we need to go ahead and end things as if there was a STOP
>> at the
>> + * end.
>> + */
>> + if (GETFIELD(STATUS_REG_SEQUENCER_INDEX, sc->status_reg) ==
>> 8) {
>> + SPI_DEBUG(qemu_log("All 8 opcodes completed, sequencer "
>> + "idling\n"));
>> + sc->status_reg = SETFIELD(STATUS_REG_SHIFTER_FSM,
>> sc->status_reg,
>> + FSM_IDLE);
>> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_INDEX,
>> + sc->status_reg, 0);
>> + sc->loop_counter_1 = 0;
>> + sc->loop_counter_2 = 0;
>> + sc->status_reg = SETFIELD(STATUS_REG_SEQUENCER_FSM,
>> + sc->status_reg, SEQ_STATE_IDLE);
>> + break;
>> + }
>> + /* Break the loop if a stop was requested */
>> + if (stop) {
>> + break;
>> + }
>> + } /* end of while */
>> + return;
>> +} /* end of operation_sequencer() */
>> +
>> +/*
>> + * Calculate the N1 counters based on passed in opcode and
>> + * internal register values.
>> + * The method assumes that the opcode is a Shift_N1 opcode
>> + * and doesn't test it.
>> + * The counters returned are:
>> + * N1 bits: Number of bits in the payload data that are significant
>> + * to the responder.
>> + * N1_bytes: Total count of payload bytes for the N1 (portion of
>> the) frame.
>> + * N1_tx: Total number of bytes taken from TDR for N1
>> + * N1_rx: Total number of bytes taken from the payload for N1
>> + */
>> +void calculate_N1(PnvSpiController *sc, uint8_t opcode)
>> +{
>> + /*
>> + * Shift_N1 opcode form: 0x3M
>> + * Implicit mode:
>> + * If M != 0 the shift count is M bytes and M is the number of
>> tx bytes.
>> + * Forced Implicit mode:
>> + * M is the shift count but tx and rx is determined by the count
>> control
>> + * register fields. Note that we only check for forced Implicit
>> mode when
>> + * M != 0 since the mode doesn't make sense when M = 0.
>> + * Explicit mode:
>> + * If M == 0 then shift count is number of bits defined in the
>> + * Counter Configuration Register's shift_count_N1 field.
>> + */
>> + if (GETFIELD(PPC_BITMASK8(4, 7), opcode) == 0) {
>> + /* Explicit mode */
>> + sc->N1_bits = GETFIELD(COUNTER_CONFIG_REG_SHIFT_COUNT_N1,
>> + sc->counter_config_reg);
>> + sc->N1_bytes = ceil(sc->N1_bits / 8);
>> + sc->N1_tx = 0;
>> + sc->N1_rx = 0;
>> + /* If tx count control for N1 is set, load the tx value */
>> + if (GETFIELD(PPC_BIT(50), sc->counter_config_reg) == 1) {
>> + sc->N1_tx = sc->N1_bytes;
>> + }
>> + /* If rx count control for N1 is set, load the rx value */
>> + if (GETFIELD(PPC_BIT(51), sc->counter_config_reg) == 1) {
>> + sc->N1_rx = sc->N1_bytes;
>> + }
>> + } else {
>> + /* Implicit mode/Forced Implicit mode, use M field from
>> opcode */
>> + sc->N1_bytes = GETFIELD(PPC_BITMASK8(4, 7), opcode);
>> + sc->N1_bits = sc->N1_bytes * 8;
>> + /*
>> + * Assume that we are going to transmit the count
>> + * (pure Implicit only)
>> + */
>> + sc->N1_tx = sc->N1_bytes;
>> + sc->N1_rx = 0;
>> + /* Let Forced Implicit mode have an effect on the counts */
>> + if (GETFIELD(PPC_BIT(49), sc->counter_config_reg) == 1) {
>> + /*
>> + * If Forced Implicit mode and count control doesn't
>> + * indicate transmit then reset the tx count to 0
>> + */
>> + if (GETFIELD(PPC_BIT(50), sc->counter_config_reg) == 0) {
>> + sc->N1_tx = 0;
>> + }
>> + /* If rx count control for N1 is set, load the rx value */
>> + if (GETFIELD(PPC_BIT(51), sc->counter_config_reg) == 1) {
>> + sc->N1_rx = sc->N1_bytes;
>> + }
>> + }
>> + }
>> + /*
>> + * Enforce an upper limit on the size of N1 that is equal to the
>> known size
>> + * of the shift register, 64 bits or 72 bits if ECC is enabled.
>> + * If the size exceeds 72 bits it is a user error so log an error,
>> + * cap the size at a max of 64 bits or 72 bits and set the
>> sequencer FSM
>> + * error bit.
>> + */
>> + uint8_t ecc_control = GETFIELD(PPC_BITMASK(29, 30),
>> + sc->clock_config_reset_control);
>> + if (ecc_control == 0 || ecc_control == 2) {
>> + if (sc->N1_bytes > (SPI_CONTROLLER_REG_SIZE + 1)) {
>> + qemu_log_mask(LOG_GUEST_ERROR, "Unsupported N1 shift
>> size when "
>> + "ECC enabled, bytes = 0x%x, bits = 0x%x\n",
>> + sc->N1_bytes, sc->N1_bits);
>> + sc->N1_bytes = SPI_CONTROLLER_REG_SIZE + 1;
>> + sc->N1_bits = sc->N1_bytes * 8;
>> + }
>> + } else if (sc->N1_bytes > SPI_CONTROLLER_REG_SIZE) {
>> + qemu_log_mask(LOG_GUEST_ERROR, "Unsupported N1 shift size, "
>> + "bytes = 0x%x, bits = 0x%x\n",
>> + sc->N1_bytes, sc->N1_bits);
>> + sc->N1_bytes = SPI_CONTROLLER_REG_SIZE;
>> + sc->N1_bits = sc->N1_bytes * 8;
>> + }
>> +} /* end of calculate_N1 */
>> +
>> +/*
>> + * Shift_N1 operation handler method
>> + */
>> +bool operation_shiftn1(PnvSpiController *sc, uint8_t opcode,
>> + xfer_buffer **payload, bool send_n1_alone)
>> +{
>> + uint8_t n1_count;
>> + bool stop = false;
>> +
>> + /*
>> + * If there isn't a current payload left over from a stopped
>> sequence
>> + * create a new one.
>> + */
>> + if (*payload == NULL) {
>> + SPI_DEBUG(qemu_log("Creating new payload xfer_buffer\n"));
>> + *payload = xfer_buffer_new();
>> + }
>> + /*
>> + * Use a combination of N1 counters to build the N1 portion of the
>> + * transmit payload.
>> + * We only care about transmit at this time since the request
>> payload
>> + * only represents data going out on the controller output line.
>> + * Leave mode specific considerations in the calculate function
>> since
>> + * all we really care about are counters that tell use exactly how
>> + * many bytes are in the payload and how many of those bytes to
>> + * include from the TDR into the payload.
>> + */
>> + calculate_N1(sc, opcode);
>> + SPI_DEBUG(qemu_log("Shift N1 started..\n"));
>> + log_all_N_counts(sc);
>> + /*
>> + * Zero out the N2 counters here in case there is no N2
>> operation following
>> + * the N1 operation in the sequencer. This keeps leftover N2
>> information
>> + * from interfering with spi_response logic.
>> + */
>> + sc->N2_bits = 0;
>> + sc->N2_bytes = 0;
>> + sc->N2_tx = 0;
>> + sc->N2_rx = 0;
>> + /*
>> + * N1_bytes is the overall size of the N1 portion of the frame
>> regardless of
>> + * whether N1 is used for tx, rx or both. Loop over the size to
>> build a
>> + * payload that is N1_bytes long.
>> + * N1_tx is the count of bytes to take from the TDR and "shift"
>> into the
>> + * frame which means append those bytes to the payload for the
>> N1 portion
>> + * of the frame.
>> + * If N1_tx is 0 or if the count exceeds the size of the TDR
>> append 0xFF to
>> + * the frame until the overall N1 count is reached.
>> + */
>> + n1_count = 0;
>> + while (n1_count < sc->N1_bytes) {
>> + /*
>> + * Assuming that if N1_tx is not equal to 0 then it is the
>> same as
>> + * N1_bytes.
>> + */
>> + if ((sc->N1_tx != 0) && (n1_count < SPI_CONTROLLER_REG_SIZE)) {
>> +
>> + if (GETFIELD(STATUS_REG_TDR_FULL, sc->status_reg) == 1) {
>> + /*
>> + * Note that we are only appending to the payload IF
>> the TDR
>> + * is full otherwise we don't touch the payload
>> because we are
>> + * going to NOT send the payload and instead tell
>> the sequencer
>> + * that called us to stop and wait for a TDR write
>> so we have
>> + * data to load into the payload.
>> + */
>> + uint8_t n1_byte = 0x00;
>> + n1_byte = get_from_offset(sc, n1_count);
>> + SPI_DEBUG(qemu_log("Extracting tx n1_byte = 0x%2.2x
>> at index "
>> + "%d from TDR\n", n1_byte,
>> n1_count));
>> + if (sc->reverse_bits) {
>> + SPI_DEBUG(qemu_log("Reversing bit order of tx
>> n1_byte\n"));
>> + n1_byte = reverse_bits8(n1_byte);
>> + }
>> + SPI_DEBUG(qemu_log("Appending tx n1_byte = 0x%2.2x to "
>> + "Payload\n", n1_byte));
>> + *(xfer_buffer_write_ptr(*payload, (*payload)->len,
>> 1)) =
>> + n1_byte;
>> + } else {
>> + /*
>> + * We hit a shift_n1 opcode TX but the TDR is empty,
>> tell the
>> + * sequencer to stop and break this loop.
>> + */
>> + SPI_DEBUG(qemu_log("Shift N1 set for transmit but
>> TDR is empty,"
>> + " requesting sequencer stop\n"));
>> + stop = true;
>> + break;
>> + }
>> + } else {
>> + /*
>> + * Cases here:
>> + * - we are receiving during the N1 frame segment and
>> the RDR
>> + * is full so we need to stop until the RDR is read
>> + * - we are transmitting and we don't care about RDR status
>> + * since we won't be loading RDR during the frame
>> segment.
>> + * - we are receiving and the RDR is empty so we allow
>> the operation
>> + * to proceed.
>> + */
>> + if ((sc->N1_rx != 0) && (GETFIELD(STATUS_REG_RDR_FULL,
>> + sc->status_reg) == 1)) {
>> + SPI_DEBUG(qemu_log("Shift N1 set for receive but RDR
>> is full, "
>> + "requesting sequencer stop\n"));
>> + stop = true;
>> + break;
>> + } else {
>> + SPI_DEBUG(qemu_log("Appending tx n1_byte = 0xFF to
>> Payload\n"));
>> + *(xfer_buffer_write_ptr(*payload, (*payload)->len,
>> 1)) = 0xff;
>> + }
>> + }
>> + n1_count++;
>> + } /* end of while */
>> + /*
>> + * If we are not stopping due to an empty TDR and we are doing
>> an N1 TX
>> + * and the TDR is full we need to clear the TDR_full status.
>> + * Do this here instead of up in the loop above so we don't log
>> the message
>> + * in every loop iteration.
>> + * Ignore the send_n1_alone flag, all that does is defer the TX
>> until the N2
>> + * operation, which was found immediately after the current
>> opcode. The TDR
>> + * was unloaded and will be shifted so we have to clear the
>> TDR_full status.
>> + */
>> + if (!stop && (sc->N1_tx != 0) &&
>> + (GETFIELD(STATUS_REG_TDR_FULL, sc->status_reg) == 1)) {
>> +
>> + sc->status_reg = SETFIELD(STATUS_REG_TDR_FULL,
>> sc->status_reg, 0);
>> + SPI_DEBUG(qemu_log("TDR_full set to 0\n"));
>> + }
>> + /*
>> + * There are other reasons why the shifter would stop, such as a
>> TDR empty
>> + * or RDR full condition with N1 set to receive. If we haven't
>> stopped due
>> + * to either one of those conditions then check if the
>> send_n1_alone flag is
>> + * equal to False, indicating the next opcode is an N2
>> operation, AND if
>> + * the N2 counter reload switch (bit 0 of the N2 count control
>> field) is
>> + * set. This condition requires a pacing write to "kick" off
>> the N2
>> + * shift which includes the N1 shift as well when send_n1_alone
>> is False.
>> + */
>> + if (!stop && !send_n1_alone &&
>> + (GETFIELD(PPC_BIT(52), sc->counter_config_reg) == 1)) {
>> + SPI_DEBUG(qemu_log("N2 counter reload active, stop N1 shift, "
>> + "TDR_underrun set to 1\n"));
>> + stop = true;
>> + sc->status_reg = SETFIELD(STATUS_REG_TDR_UNDERRUN,
>> sc->status_reg, 1);
>> + }
>> + /*
>> + * If send_n1_alone is set AND we have a full TDR then this is
>> the first and
>> + * last payload to send and we don't have an N2 frame segment to
>> add to the
>> + * payload.
>> + */
>> + if (send_n1_alone && !stop) {
>> + uint32_t tx;
>> + uint32_t rx;
>> + uint8_t *read_buf = NULL;
>> + xfer_buffer *rsp_payload = NULL;
>> +
>> + /* We have a TX and a full TDR or an RX and an empty RDR */
>> + SPI_DEBUG(qemu_log("Shifting N1 frame: first = %d, last = %d, "
>> + "n1 bits = %d\n", sc->first, sc->last,
>> + sc->N1_bits));
>> + rsp_payload = xfer_buffer_new();
>> + for (int offset = 0; offset < (*payload)->len; offset =
>> offset + 4) {
>> + xfer_buffer_read_ptr(*payload, &read_buf, offset, 4);
>> + tx = 0;
>> + for (int i = 0; i < 4; i++) {
>> + if ((offset + i) >= (*payload)->len) {
>> + break;
>> + }
>> + tx = (tx << 8) | read_buf[i];
>> + }
>> + rx = ssi_transfer((sc->bus).ssi_bus, tx);
>> + for (int i = 0; i < 4; i++) {
>> + if ((offset + i) >= (*payload)->len) {
>> + break;
>> + }
>> + *(xfer_buffer_write_ptr(rsp_payload,
>> rsp_payload->len, 1)) =
>> + (rx >> (24 - i * 8)) & 0xFF;
>> + }
>> + }
>> + if (rsp_payload != NULL) {
>> + spi_response(sc, sc->N1_bits, rsp_payload);
>> + }
>> + sc->first = 0;
>> + sc->last = 0;
>> + /* The N1 frame shift is complete so reset the N1 counters */
>> + sc->N2_bits = 0;
>> + sc->N2_bytes = 0;
>> + sc->N2_tx = 0;
>> + sc->N2_rx = 0;
>> + xfer_buffer_free(*payload);
>> + *payload = NULL;
>> + SPI_DEBUG(qemu_log("Payload buffer freed\n"));
>> + } else {
>> + SPI_DEBUG(qemu_log("Not shifting N1, send_n1_alone = %d,
>> stop = %d\n",
>> + send_n1_alone, stop));
>> + }
>> + return stop;
>> +} /* end of operation_shiftn1() */
>> +
>> +/*
>> + * Calculate the N2 counters based on passed in opcode and
>> + * internal register values.
>> + * The method assumes that the opcode is a Shift_N2 opcode
>> + * and doesn't test it.
>> + * The counters returned are:
>> + * N2 bits: Number of bits in the payload data that are significant
>> + * to the responder.
>> + * N2_bytes: Total count of payload bytes for the N2 frame.
>> + * N2_tx: Total number of bytes taken from TDR for N2
>> + * N2_rx: Total number of bytes taken from the payload for N2
>> + */
>> +void calculate_N2(PnvSpiController *sc, uint8_t opcode)
>> +{
>> + /*
>> + * Shift_N2 opcode form: 0x4M
>> + * Implicit mode:
>> + * If M!=0 the shift count is M bytes and M is the number of rx
>> bytes.
>> + * Forced Implicit mode:
>> + * M is the shift count but tx and rx is determined by the count
>> control
>> + * register fields. Note that we only check for Forced Implicit
>> mode when
>> + * M != 0 since the mode doesn't make sense when M = 0.
>> + * Explicit mode:
>> + * If M==0 then shift count is number of bits defined in the
>> + * Counter Configuration Register's shift_count_N1 field.
>> + */
>> + if (GETFIELD(PPC_BITMASK8(4, 7), opcode) == 0) {
>> + /* Explicit mode */
>> + sc->N2_bits = GETFIELD(COUNTER_CONFIG_REG_SHIFT_COUNT_N2,
>> + sc->counter_config_reg);
>> + sc->N2_bytes = ceil(sc->N2_bits / 8);
>> + sc->N2_tx = 0;
>> + sc->N2_rx = 0;
>> + /* If tx count control for N2 is set, load the tx value */
>> + if (GETFIELD(PPC_BIT(54), sc->counter_config_reg) == 1) {
>> + sc->N2_tx = sc->N2_bytes;
>> + }
>> + /* If rx count control for N2 is set, load the rx value */
>> + if (GETFIELD(PPC_BIT(55), sc->counter_config_reg) == 1) {
>> + sc->N2_rx = sc->N2_bytes;
>> + }
>> + } else {
>> + /* Implicit mode/Forced Implicit mode, use M field from
>> opcode */
>> + sc->N2_bytes = GETFIELD(PPC_BITMASK8(4, 7), opcode);
>> + sc->N2_bits = sc->N2_bytes * 8;
>> + /* Assume that we are going to receive the count */
>> + sc->N2_rx = sc->N2_bytes;
>> + sc->N2_tx = 0;
>> + /* Let Forced Implicit mode have an effect on the counts */
>> + if (GETFIELD(PPC_BIT(53), sc->counter_config_reg) == 1) {
>> + /*
>> + * If Forced Implicit mode and count control doesn't
>> + * indicate a receive then reset the rx count to 0
>> + */
>> + if (GETFIELD(PPC_BIT(55), sc->counter_config_reg) == 0) {
>> + sc->N2_rx = 0;
>> + }
>> + /* If tx count control for N2 is set, load the tx value */
>> + if (GETFIELD(PPC_BIT(54), sc->counter_config_reg) == 1) {
>> + sc->N2_tx = sc->N2_bytes;
>> + }
>> + }
>> + }
>> + /*
>> + * Enforce an upper limit on the size of N1 that is equal to the
>> + * known size of the shift register, 64 bits or 72 bits if ECC
>> + * is enabled.
>> + * If the size exceeds 72 bits it is a user error so log an error,
>> + * cap the size at a max of 64 bits or 72 bits and set the
>> sequencer FSM
>> + * error bit.
>> + */
>> + uint8_t ecc_control = GETFIELD(PPC_BITMASK(29, 30),
>> + sc->clock_config_reset_control);
>> + if (ecc_control == 0 || ecc_control == 2) {
>> + if (sc->N2_bytes > (SPI_CONTROLLER_REG_SIZE + 1)) {
>> + SPI_DEBUG(qemu_log("Unsupported N2 shift size when ECC
>> enabled, "
>> + "bytes = 0x%x, bits = 0x%x\n",
>> + sc->N2_bytes, sc->N2_bits));
>> + sc->N2_bytes = SPI_CONTROLLER_REG_SIZE + 1;
>> + sc->N2_bits = sc->N2_bytes * 8;
>> + }
>> + } else if (sc->N2_bytes > SPI_CONTROLLER_REG_SIZE) {
>> + SPI_DEBUG(qemu_log("Unsupported N2 shift size, bytes = 0x%x, "
>> + "bits = 0x%x\n", sc->N2_bytes,
>> sc->N2_bits));
>> + sc->N2_bytes = SPI_CONTROLLER_REG_SIZE;
>> + sc->N2_bits = sc->N2_bytes * 8;
>> + }
>> +} /* end of calculate_N2 */
>> +
>> +/*
>> + * Shift_N2 operation handler method
>> + */
>> +
>> +bool operation_shiftn2(PnvSpiController *sc, uint8_t opcode,
>> + xfer_buffer **payload)
>> +{
>> + uint8_t n2_count;
>> + bool stop = false;
>> +
>> + /*
>> + * If there isn't a current payload left over from a stopped
>> sequence
>> + * create a new one.
>> + */
>> + if (*payload == NULL) {
>> + SPI_DEBUG(qemu_log("Creating new payload xfer_buffer\n"));
>> + *payload = xfer_buffer_new();
>> + }
>> + /*
>> + * Use a combination of N2 counters to build the N2 portion of the
>> + * transmit payload.
>> + */
>> + calculate_N2(sc, opcode);
>> + SPI_DEBUG(qemu_log("Shift N2 started\n"));
>> + log_all_N_counts(sc);
>> + /*
>> + * The only difference between this code and the code for shift
>> N1 is
>> + * that this code has to account for the possible presence of N1
>> transmit
>> + * bytes already taken from the TDR.
>> + * If there are bytes to be transmitted for the N2 portion of
>> the frame
>> + * and there are still bytes in TDR that have not been copied
>> into the
>> + * TX data of the payload, this code will handle transmitting those
>> + * remaining bytes.
>> + * If for some reason the transmit count(s) add up to more than
>> the size
>> + * of the TDR we will just append 0xFF to the transmit payload
>> data until
>> + * the payload is N1 + N2 bytes long.
>> + */
>> + n2_count = 0;
>> + while (n2_count < sc->N2_bytes) {
>> + /*
>> + * If the RDR is full and we need to RX just bail out,
>> letting the
>> + * code continue will end up building the payload twice in
>> the same
>> + * buffer since RDR full causes a sequence stop and restart.
>> + */
>> + if ((sc->N2_rx != 0) &&
>> + (GETFIELD(STATUS_REG_RDR_FULL, sc->status_reg) == 1)) {
>> + SPI_DEBUG(qemu_log("Shift N2 set for receive but RDR is
>> full, "
>> + "requesting sequencer stop\n"));
>> + stop = true;
>> + break;
>> + }
>> + if ((sc->N2_tx != 0) && ((sc->N1_tx + n2_count) <
>> + SPI_CONTROLLER_REG_SIZE)) {
>> + /* Always append data for the N2 segment if it is set
>> for TX */
>> + uint8_t n2_byte = 0x00;
>> + n2_byte = get_from_offset(sc, (sc->N1_tx + n2_count));
>> + SPI_DEBUG(qemu_log("Extracting tx n2_byte = 0x%2.2x at
>> index %d "
>> + "from TDR\n", n2_byte, (sc->N1_tx + n2_count)));
>> + if (sc->reverse_bits) {
>> + SPI_DEBUG(qemu_log("Reversing bit order of tx
>> n2_byte\n"));
>> + n2_byte = reverse_bits8(n2_byte);
>> + }
>> + SPI_DEBUG(qemu_log("Appending tx n2_byte = 0x%2.2x to
>> Payload\n",
>> + n2_byte));
>> + *(xfer_buffer_write_ptr(*payload, (*payload)->len, 1)) =
>> n2_byte;
>> + } else {
>> + /*
>> + * Regardless of whether or not N2 is set for TX or RX,
>> we need
>> + * the number of bytes in the payload to match the
>> overall length
>> + * of the operation.
>> + */
>> + SPI_DEBUG(qemu_log("Appending tx n2_byte = 0xFF to
>> Payload\n"));
>> + *(xfer_buffer_write_ptr(*payload, (*payload)->len, 1)) =
>> 0xff;
>> + }
>> + n2_count++;
>> + } /* end of while */
>> + if (!stop) {
>> + uint32_t tx;
>> + uint32_t rx;
>> + uint8_t *read_buf = NULL;
>> + xfer_buffer *rsp_payload = NULL;
>> +
>> + /* We have a TX and a full TDR or an RX and an empty RDR */
>> + SPI_DEBUG(qemu_log("Shifting N2 frame: first = %d, last = %d, "
>> + "n1+n2 bits = %d\n", sc->first, sc->last,
>> + (sc->N1_bits + sc->N2_bits)));
>> + rsp_payload = xfer_buffer_new();
>> + for (int offset = 0; offset < (*payload)->len; offset =
>> offset + 4) {
>> + xfer_buffer_read_ptr(*payload, &read_buf, offset, 4);
>> + tx = 0;
>> + for (int i = 0; i < 4; i++) {
>> + if ((offset + i) >= (*payload)->len) {
>> + break;
>> + }
>> + tx = (tx << 8) | read_buf[i];
>> + }
>> + rx = ssi_transfer((sc->bus).ssi_bus, tx);
>> + for (int i = 0; i < 4; i++) {
>> + if ((offset + i) >= (*payload)->len) {
>> + break;
>> + }
>> + *(xfer_buffer_write_ptr(rsp_payload,
>> rsp_payload->len, 1)) =
>> + (rx >> (24 - i * 8)) & 0xFF;
>> + }
>> + }
>> + if (rsp_payload != NULL) {
>> + spi_response(sc, (sc->N1_bits + sc->N2_bits), rsp_payload);
>> + }
>> + sc->first = 0;
>> + sc->last = 0;
>> + /*
>> + * If we are doing an N2 TX and the TDR is full we need to
>> clear the
>> + * TDR_full status. Do this here instead of up in the loop
>> above so we
>> + * don't log the message in every loop iteration.
>> + */
>> + if ((sc->N2_tx != 0) &&
>> + (GETFIELD(STATUS_REG_TDR_FULL, sc->status_reg) == 1)) {
>> + sc->status_reg = SETFIELD(STATUS_REG_TDR_FULL,
>> sc->status_reg, 0);
>> + SPI_DEBUG(qemu_log("TDR_full set to 0\n"));
>> + }
>> + /*
>> + * The N2 frame shift is complete so reset the N2 counters.
>> + * Reset the N1 counters also in case the frame was a
>> combination of
>> + * N1 and N2 segments.
>> + */
>> + sc->N2_bits = 0;
>> + sc->N2_bytes = 0;
>> + sc->N2_tx = 0;
>> + sc->N2_rx = 0;
>> + sc->N1_bits = 0;
>> + sc->N1_bytes = 0;
>> + sc->N1_tx = 0;
>> + sc->N1_rx = 0;
>> + xfer_buffer_free(*payload);
>> + *payload = NULL;
>> + SPI_DEBUG(qemu_log("Payload buffer freed\n"));
>> + } else {
>> + SPI_DEBUG(qemu_log("Not shifting N2, stop = %d\n", stop));
>> + }
>> + return stop;
>> +} /* end of operation_shiftn2()*/
>> +
>> +/*
>> + * The SPIC engine and its internal sequencer can be interrupted and
>> reset by
>> + * a hardware signal, the sbe_spicst_hard_reset bits from Pervasive
>> + * Miscellaneous Register of sbe_register_bo device.
>> + * Reset immediately aborts any SPI transaction in progress and
>> returns the
>> + * sequencer and state machines to idle state.
>> + * The configuration register values are not changed. The status
>> register is
>> + * not reset. The engine registers are not reset.
>> + * The SPIC engine reset does not have any affect on the attached
>> devices.
>> + * Reset handling of any attached devices is beyond the scope of the
>> engine.
>> + */
>> +static void do_reset(DeviceState *dev)
>> +{
>> + PnvSpiController *sc = PNV_SPICONTROLLER(dev);
>> + SPI_DEBUG(qemu_log("Resetting spic engine sequencer
>> configuration and spi "
>> + "communication\n"));
>> + /* Reset all N1 and N2 counters, and other constants */
>> + sc->first = 0;
>> + sc->last = 0;
>> + sc->N2_bits = 0;
>> + sc->N2_bytes = 0;
>> + sc->N2_tx = 0;
>> + sc->N2_rx = 0;
>> + sc->N1_bits = 0;
>> + sc->N1_bytes = 0;
>> + sc->N1_tx = 0;
>> + sc->N1_rx = 0;
>> + sc->loop_counter_1 = 0;
>> + sc->loop_counter_2 = 0;
>> + SPI_DEBUG(qemu_log("Disconnected from responder\n"));
>> + qemu_set_irq((sc->bus).cs_line[0], 1);
>> +}
>> +
>> static void pnv_spi_bus_realize(DeviceState *dev, Error **errp)
>> {
>> PnvSPIBus *s = PNV_SPI_BUS(dev);
>> @@ -191,6 +1498,7 @@ static const TypeInfo pnv_spi_bus_info = {
>> static Property pnv_spi_controller_properties[] = {
>> DEFINE_PROP_UINT32("spic_num", PnvSpiController, spic_num, 0),
>> + DEFINE_PROP_BOOL("reverse_bits", PnvSpiController, reverse_bits,
>> false),
>> DEFINE_PROP_END_OF_LIST(),
>> };
>> @@ -254,6 +1562,7 @@ static void
>> pnv_spi_controller_class_init(ObjectClass *klass, void *data)
>> dc->desc = "PowerNV SPI Controller";
>> dc->realize = pnv_spi_controller_realize;
>> + dc->reset = do_reset;
>> device_class_set_props(dc, pnv_spi_controller_properties);
>> }
>
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 2/6] hw/ppc: SPI controller model - registers implementation
2024-04-16 17:02 ` Chalapathi V
@ 2024-04-22 14:06 ` Cédric Le Goater
0 siblings, 0 replies; 16+ messages in thread
From: Cédric Le Goater @ 2024-04-22 14:06 UTC (permalink / raw)
To: Chalapathi V, qemu-devel
Cc: qemu-ppc, fbarrat, npiggin, calebs, chalapathi.v, saif.abrar,
dantan
On 4/16/24 19:02, Chalapathi V wrote:
>
> On 15-04-2024 20:44, Cédric Le Goater wrote:
>> Hello Chalapathi
>>
>> The subject could be rephrased to : "ppc/pnv: Add SPI controller model".
>>
>> On 4/9/24 19:56, Chalapathi V wrote:
>>> SPI controller device model supports a connection to a single SPI responder.
>>> This provide access to SPI seeproms, TPM, flash device and an ADC controller.
>>>
>>> All SPI function control is mapped into the SPI register space to enable full
>>> control by firmware. In this commit SPI configuration component is modelled
>>> which contains all SPI configuration and status registers as well as the hold
>>> registers for data to be sent or having been received.
>>>
>>> An existing QEMU SSI framework is used and SSI_BUS is created.
>>>
>>> Signed-off-by: Chalapathi V <chalapathi.v@linux.ibm.com>
>>> ---
>>> include/hw/ppc/pnv_spi_controller.h | 55 +++++
>>> include/hw/ppc/pnv_spi_controller_regs.h | 114 ++++++++++
>>
>> These two files should be under hw/ssi/ and include/hw/ssi/. Please
>> remove '_controller'.
> Sure. Thank You.
>>
>>> include/hw/ppc/pnv_xscom.h | 3 +
>>> hw/ppc/pnv_spi_controller.c | 278 +++++++++++++++++++++++
>>> hw/ppc/Kconfig | 1 +
>>> hw/ppc/meson.build | 1 +
>>> 6 files changed, 452 insertions(+)
>>> create mode 100644 include/hw/ppc/pnv_spi_controller.h
>>> create mode 100644 include/hw/ppc/pnv_spi_controller_regs.h
>>> create mode 100644 hw/ppc/pnv_spi_controller.c
>>>
>>> diff --git a/include/hw/ppc/pnv_spi_controller.h b/include/hw/ppc/pnv_spi_controller.h
>>> new file mode 100644
>>> index 0000000000..5ec50fb14c
>>> --- /dev/null
>>> +++ b/include/hw/ppc/pnv_spi_controller.h
>>> @@ -0,0 +1,55 @@
>>> +/*
>>> + * QEMU PowerPC SPI Controller model
>>> + *
>>> + * Copyright (c) 2024, IBM Corporation.
>>> + *
>>> + * SPDX-License-Identifier: GPL-2.0-or-later
>>> + *
>>> + * This model Supports a connection to a single SPI responder.
>>> + * Introduced for P10 to provide access to SPI seeproms, TPM, flash device
>>> + * and an ADC controller.
>>> + */
>>> +#include "hw/ssi/ssi.h"
>>> +
>>> +#ifndef PPC_PNV_SPI_CONTROLLER_H
>>> +#define PPC_PNV_SPI_CONTROLLER_H
>>> +
>>> +#define TYPE_PNV_SPI_CONTROLLER "pnv-spi-controller"
>>> +#define PNV_SPICONTROLLER(obj) \
>>> + OBJECT_CHECK(PnvSpiController, (obj), TYPE_PNV_SPI_CONTROLLER)
>>
>> You could use OBJECT_DECLARE_SIMPLE_TYPE ? Anyhow, I would prefer
>> naming the macro PNV_SPI_CONTROLLER.
>>
>>> +#define SPI_CONTROLLER_REG_SIZE 8
>>> +
>>> +typedef struct SSIBus SSIBus;
>>
>> why ?
> I might have got compile time errors. I will recheck and update. Thank You.
>>
>>
>>> +
>>> +#define TYPE_PNV_SPI_BUS "pnv-spi-bus"
>>> +OBJECT_DECLARE_SIMPLE_TYPE(PnvSPIBus, PNV_SPI_BUS)
>>> +
>>> +typedef struct PnvSPIBus {
>>
>> I don't think this extra PnvSPIBus model is useful.
>>
>>> + SysBusDevice parent_obj;
>>> +
>>> + SSIBus *ssi_bus;
>>> + qemu_irq *cs_line;
>>
>> These two attributes could live under PnvSpiController.
> This is added to have a SysBusDevice parent so that I can use the busname in command line for TPM. I will add these in PnvSpiController with SysBusDevice parent and test.
You could still compute the bus name from pnv_spi_controller_realize()
and move all PnvSPIBus attributes under PnvSpiController. The PnvSPIBus
is not required.
>>
>>> + uint32_t id;
>>
>> and this one would become useless.
>>
>>> +} PnvSPIBus;
>>>
>>> +typedef struct PnvSpiController {
>>> + DeviceState parent;
>>> +
>>> + PnvSPIBus bus;
>>> + MemoryRegion xscom_spic_regs;
>>> + /* SPI controller object number */
>>> + uint32_t spic_num;
>>> +
>>> + /* SPI Controller registers */
>>> + uint64_t error_reg;
>>> + uint64_t counter_config_reg;
>>> + uint64_t config_reg1;
>>> + uint64_t clock_config_reset_control;
>>> + uint64_t memory_mapping_reg;
>>> + uint64_t transmit_data_reg;
>>> + uint64_t receive_data_reg;
>>> + uint8_t sequencer_operation_reg[SPI_CONTROLLER_REG_SIZE];
>>> + uint64_t status_reg;
>>
>> You could use an array of uint64_t also.
> Sure. I will try and check.
That's not a must have. Both approach work but since the memops use
the MMIO offest to address the register, it is sometime simpler to
use an array of uint64_t.
>>
>>
>>> +} PnvSpiController;
>>> +#endif /* PPC_PNV_SPI_CONTROLLER_H */
>>> diff --git a/include/hw/ppc/pnv_spi_controller_regs.h b/include/hw/ppc/pnv_spi_controller_regs.h
>>> new file mode 100644
>>> index 0000000000..6f613aca5e
>>> --- /dev/null
>>> +++ b/include/hw/ppc/pnv_spi_controller_regs.h
>>> @@ -0,0 +1,114 @@
>>> +/*
>>> + * QEMU PowerPC SPI Controller model
>>> + *
>>> + * Copyright (c) 2023, IBM Corporation.
>>> + *
>>> + * SPDX-License-Identifier: GPL-2.0-or-later
>>> + */
>>> +
>>> +#ifndef SPI_CONTROLLER_REGS_H
>>> +#define SPI_CONTROLLER_REGS_H
>>> +
>>> +/* Error Register */
>>> +#define ERROR_REG 0x00
>>> +
>>> +/* counter_config_reg */
>>> +#define COUNTER_CONFIG_REG 0x01
>>> +#define COUNTER_CONFIG_REG_SHIFT_COUNT_N1 PPC_BITMASK(0, 7)
>>> +#define COUNTER_CONFIG_REG_SHIFT_COUNT_N2 PPC_BITMASK(8, 15)
>>> +#define COUNTER_CONFIG_REG_COUNT_COMPARE1 PPC_BITMASK(24, 31)
>>> +#define COUNTER_CONFIG_REG_COUNT_COMPARE2 PPC_BITMASK(32, 39)
>>> +#define COUNTER_CONFIG_REG_N1_COUNT_CONTROL PPC_BITMASK(48, 51)
>>> +#define COUNTER_CONFIG_REG_N2_COUNT_CONTROL PPC_BITMASK(52, 55)
>>> +
>>> +/* config_reg */
>>> +#define CONFIG_REG1 0x02
>>> +
>>> +/* clock_config_reset_control_ecc_enable_reg */
>>> +#define CLOCK_CONFIG_REG 0x03
>>> +#define CLOCK_CONFIG_RESET_CONTROL_HARD_RESET 0x0084000000000000;
>>> +#define CLOCK_CONFIG_REG_RESET_CONTROL PPC_BITMASK(24, 27)
>>> +#define CLOCK_CONFIG_REG_ECC_CONTROL PPC_BITMASK(28, 30)
>>> +
>>> +/* memory_mapping_reg */
>>> +#define MEMORY_MAPPING_REG 0x04
>>> +#define MEMORY_MAPPING_REG_MMSPISM_BASE_ADDR PPC_BITMASK(0, 15)
>>> +#define MEMORY_MAPPING_REG_MMSPISM_ADDR_MASK PPC_BITMASK(16, 31)
>>> +#define MEMORY_MAPPING_REG_RDR_MATCH_VAL PPC_BITMASK(32, 47)
>>> +#define MEMORY_MAPPING_REG_RDR_MATCH_MASK PPC_BITMASK(48, 63)
>>> +
>>> +/* transmit_data_reg */
>>> +#define TRANSMIT_DATA_REG 0x05
>>> +
>>> +/* receive_data_reg */
>>> +#define RECEIVE_DATA_REG 0x06
>>> +
>>> +/* sequencer_operation_reg */
>>> +#define SEQUENCER_OPERATION_REG 0x07
>>> +
>>> +/* status_reg */
>>> +#define STATUS_REG 0x08
>>> +#define STATUS_REG_RDR_FULL PPC_BIT(0)
>>> +#define STATUS_REG_RDR_OVERRUN PPC_BIT(1)
>>> +#define STATUS_REG_RDR_UNDERRUN PPC_BIT(2)
>>> +#define STATUS_REG_TDR_FULL PPC_BIT(4)
>>> +#define STATUS_REG_TDR_OVERRUN PPC_BIT(5)
>>> +#define STATUS_REG_TDR_UNDERRUN PPC_BIT(6)
>>> +#define STATUS_REG_SEQUENCER_FSM PPC_BITMASK(8, 15)
>>> +#define STATUS_REG_SHIFTER_FSM PPC_BITMASK(16, 27)
>>> +#define STATUS_REG_SEQUENCER_INDEX PPC_BITMASK(28, 31)
>>> +#define STATUS_REG_GENERAL_SPI_STATUS PPC_BITMASK(32, 63)
>>> +#define STATUS_REG_RDR PPC_BITMASK(1, 3)
>>> +#define STATUS_REG_TDR PPC_BITMASK(5, 7)
>>> +
>>> +/*
>>> + * Shifter states
>>> + *
>>> + * These are the same values defined for the Shifter FSM field of the
>>> + * status register. It's a 12 bit field so we will represent it as three
>>> + * nibbles in the constants.
>>> + *
>>> + * These are shifter_fsm values
>>> + *
>>> + * Status reg bits 16-27 -> field bits 0-11
>>> + * bits 0,1,2,5 unused/reserved
>>> + * bit 4 crc shift in (unused)
>>> + * bit 8 crc shift out (unused)
>>> + */
>>> +
>>> +#define FSM_DONE 0x100 /* bit 3 */
>>> +#define FSM_SHIFT_N2 0x020 /* bit 6 */
>>> +#define FSM_WAIT 0x010 /* bit 7 */
>>> +#define FSM_SHIFT_N1 0x004 /* bit 9 */
>>> +#define FSM_START 0x002 /* bit 10 */
>>> +#define FSM_IDLE 0x001 /* bit 11 */
>>> +
>>> +/*
>>> + * Sequencer states
>>> + *
>>> + * These are sequencer_fsm values
>>> + *
>>> + * Status reg bits 8-15 -> field bits 0-7
>>> + * bits 0-3 unused/reserved
>>> + *
>>> + */
>>> +#define SEQ_STATE_INDEX_INCREMENT 0x08 /* bit 4 */
>>> +#define SEQ_STATE_EXECUTE 0x04 /* bit 5 */
>>> +#define SEQ_STATE_DECODE 0x02 /* bit 6 */
>>> +#define SEQ_STATE_IDLE 0x01 /* bit 7 */
>>> +
>>> +/*
>>> + * These are the supported sequencer operations.
>>> + * Only the upper nibble is significant because for many operations
>>> + * the lower nibble is a variable specific to the operation.
>>> + */
>>> +#define SEQ_OP_STOP 0x00
>>> +#define SEQ_OP_SELECT_SLAVE 0x10
>>> +#define SEQ_OP_SHIFT_N1 0x30
>>> +#define SEQ_OP_SHIFT_N2 0x40
>>> +#define SEQ_OP_BRANCH_IFNEQ_RDR 0x60
>>> +#define SEQ_OP_TRANSFER_TDR 0xC0
>>> +#define SEQ_OP_BRANCH_IFNEQ_INC_1 0xE0
>>> +#define SEQ_OP_BRANCH_IFNEQ_INC_2 0xF0
>>> +
>>> +#endif
>>> diff --git a/include/hw/ppc/pnv_xscom.h b/include/hw/ppc/pnv_xscom.h
>>> index 6209e18492..a77b97f9b1 100644
>>> --- a/include/hw/ppc/pnv_xscom.h
>>> +++ b/include/hw/ppc/pnv_xscom.h
>>> @@ -194,6 +194,9 @@ struct PnvXScomInterfaceClass {
>>> #define PNV10_XSCOM_PEC_PCI_BASE 0x8010800 /* index goes upwards ... */
>>> #define PNV10_XSCOM_PEC_PCI_SIZE 0x200
>>> +#define PNV10_XSCOM_PIB_SPIC_BASE 0xc0000
>>> +#define PNV10_XSCOM_PIB_SPIC_SIZE 0x20
>>> +
>>> void pnv_xscom_init(PnvChip *chip, uint64_t size, hwaddr addr);
>>> int pnv_dt_xscom(PnvChip *chip, void *fdt, int root_offset,
>>> uint64_t xscom_base, uint64_t xscom_size,
>>> diff --git a/hw/ppc/pnv_spi_controller.c b/hw/ppc/pnv_spi_controller.c
>>> new file mode 100644
>>> index 0000000000..e2478a47f2
>>> --- /dev/null
>>> +++ b/hw/ppc/pnv_spi_controller.c
>>> @@ -0,0 +1,278 @@
>>> +/*
>>> + * QEMU PowerPC SPI Controller model
>>> + *
>>> + * Copyright (c) 2024, IBM Corporation.
>>> + *
>>> + * SPDX-License-Identifier: GPL-2.0-or-later
>>> + */
>>> +
>>> +#include "qemu/osdep.h"
>>> +#include "qemu/log.h"
>>> +#include "hw/qdev-properties.h"
>>> +#include "hw/ppc/pnv_xscom.h"
>>> +#include "hw/ppc/pnv_spi_controller.h"
>>> +#include "hw/ppc/pnv_spi_controller_regs.h"
>>> +#include "hw/ssi/ssi.h"
>>> +#include "hw/ppc/fdt.h"
>>> +#include <libfdt.h>
>>> +#include <math.h>
>>> +#include "hw/irq.h"
>>> +
>>> +#define SPI_DEBUG(x)
>>> +
>>> +static uint64_t pnv_spi_controller_read(void *opaque, hwaddr addr,
>>> + unsigned size)
>>> +{
>>> + PnvSpiController *sc = PNV_SPICONTROLLER(opaque);
>>
>> The name 'sc' makes you think of a class. 's' is common in QEMU models.
> Sure. Will modify.
>>
>>> + uint32_t reg = addr >> 3;
>>> + uint64_t val = ~0ull;
>>> +
>>> + switch (reg) {
>>> + case ERROR_REG:
>>> + val = sc->error_reg;
>>> + break;
>>> + case COUNTER_CONFIG_REG:
>>> + val = sc->counter_config_reg;
>>> + break;
>>> + case CONFIG_REG1:
>>> + val = sc->config_reg1;
>>> + break;
>>> + case CLOCK_CONFIG_REG:
>>> + val = sc->clock_config_reset_control;
>>> + break;
>>> + case MEMORY_MAPPING_REG:
>>> + val = sc->memory_mapping_reg;
>>> + break;
>>> + case TRANSMIT_DATA_REG:
>>> + val = sc->transmit_data_reg;
>>> + break;
>>> + case RECEIVE_DATA_REG:
>>> + val = sc->receive_data_reg;
>>> + SPI_DEBUG(qemu_log("RDR being read, data extracted = 0x%16.16lx\n",
>>> + val));
>>
>> please use trace events instead of the SPI_DEBUG macro.
> Sure. Will replace with trace events wherever necessary.
then you can use '-trace pnv_spi*' from the command line to activate extra
logging. Very useful.
Thanks,
C.
>>
>>> + sc->status_reg = SETFIELD(STATUS_REG_RDR_FULL, sc->status_reg, 0);
>>> + SPI_DEBUG(qemu_log("RDR being read, RDR_full set to 0\n"));
>>> + break;
>>> + case SEQUENCER_OPERATION_REG:
>>> + val = 0;
>>> + for (int i = 0; i < SPI_CONTROLLER_REG_SIZE; i++) {
>>> + val = (val << 8) | sc->sequencer_operation_reg[i];
>>> + }
>>> + break;
>>> + case STATUS_REG:
>>> + val = sc->status_reg;
>>> + break;
>>> + default:
>>> + qemu_log_mask(LOG_GUEST_ERROR, "spi_controller_regs: Invalid xscom "
>>> + "read at 0x%08x\n", reg);
>>> + }
>>> + return val;
>>> +}
>>> +
>>> +static void pnv_spi_controller_write(void *opaque, hwaddr addr,
>>> + uint64_t val, unsigned size)
>>> +{
>>> + PnvSpiController *sc = PNV_SPICONTROLLER(opaque);
>>> + uint32_t reg = addr >> 3;
>>> +
>>> + switch (reg) {
>>> + case ERROR_REG:
>>> + sc->error_reg = val;
>>> + break;
>>> + case COUNTER_CONFIG_REG:
>>> + sc->counter_config_reg = val;
>>> + break;
>>> + case CONFIG_REG1:
>>> + sc->config_reg1 = val;
>>> + break;
>>> + case CLOCK_CONFIG_REG:
>>> + /*
>>> + * To reset the SPI controller write the sequence 0x5 0xA to
>>> + * reset_control field
>>> + */
>>> + if (GETFIELD(CLOCK_CONFIG_REG_RESET_CONTROL,
>>> + sc->clock_config_reset_control) == 0x5) {
>>> + if (GETFIELD(CLOCK_CONFIG_REG_RESET_CONTROL, val) == 0xA) {
>>> + SPI_DEBUG(qemu_log("SPI controller reset sequence completed, "
>>> + "resetting..."));
>>> + sc->clock_config_reset_control =
>>> + CLOCK_CONFIG_RESET_CONTROL_HARD_RESET;
>>> + } else {
>>> + sc->clock_config_reset_control = val;
>>> + }
>>> + } else {
>>> + sc->clock_config_reset_control = val;
>>> + }
>>> + break;
>>> + case MEMORY_MAPPING_REG:
>>> + sc->memory_mapping_reg = val;
>>> + break;
>>> + case TRANSMIT_DATA_REG:
>>> + /*
>>> + * Writing to the transmit data register causes the transmit data
>>> + * register full status bit in the status register to be set. Writing
>>> + * when the transmit data register full status bit is already set
>>> + * causes a "Resource Not Available" condition. This is not possible
>>> + * in the model since writes to this register are not asynchronous to
>>> + * the operation sequence like it would be in hardware.
>>> + */
>>> + sc->transmit_data_reg = val;
>>> + SPI_DEBUG(qemu_log("TDR being written, data written = 0x%16.16lx\n",
>>> + val));
>>> + sc->status_reg = SETFIELD(STATUS_REG_TDR_FULL, sc->status_reg, 1);
>>> + SPI_DEBUG(qemu_log("TDR being written, TDR_full set to 1\n"));
>>> + sc->status_reg = SETFIELD(STATUS_REG_TDR_UNDERRUN, sc->status_reg, 0);
>>> + SPI_DEBUG(qemu_log("TDR being written, TDR_underrun set to 0\n"));
>>> + SPI_DEBUG(qemu_log("TDR being written, starting sequencer\n"));
>>> + break;
>>> + case RECEIVE_DATA_REG:
>>> + sc->receive_data_reg = val;
>>> + break;
>>> + case SEQUENCER_OPERATION_REG:
>>> + for (int i = 0; i < SPI_CONTROLLER_REG_SIZE; i++) {
>>> + sc->sequencer_operation_reg[i] = (val >> (56 - i * 8)) & 0xFF;
>>> + }
>>> + break;
>>> + case STATUS_REG:
>>> + /* other fields are ignore_write */
>>> + sc->status_reg = SETFIELD(STATUS_REG_RDR_OVERRUN, sc->status_reg,
>>> + GETFIELD(STATUS_REG_RDR, val));
>>> + sc->status_reg = SETFIELD(STATUS_REG_TDR_OVERRUN, sc->status_reg,
>>> + GETFIELD(STATUS_REG_TDR, val));
>>> + break;
>>> + default:
>>> + qemu_log_mask(LOG_GUEST_ERROR, "spi_controller_regs: Invalid xscom "
>>> + "write at 0x%08x\n", reg);
>>> + }
>>> + return;
>>> +}
>>> +
>>> +static const MemoryRegionOps pnv_spi_controller_xscom_ops = {
>>> + .read = pnv_spi_controller_read,
>>> + .write = pnv_spi_controller_write,
>>> + .valid.min_access_size = 8,
>>> + .valid.max_access_size = 8,
>>> + .impl.min_access_size = 8,
>>> + .impl.max_access_size = 8,
>>> + .endianness = DEVICE_BIG_ENDIAN,
>>> +};
>>> +
>>> +static void pnv_spi_bus_realize(DeviceState *dev, Error **errp)
>>> +{
>>> + PnvSPIBus *s = PNV_SPI_BUS(dev);
>>> + SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
>>> + g_autofree char *name = g_strdup_printf(TYPE_PNV_SPI_BUS ".%d", s->id);
>>> +
>>> + s->ssi_bus = ssi_create_bus(dev, name);
>>> + s->cs_line = g_new0(qemu_irq, 1);
>>> + sysbus_init_irq(sbd, &s->cs_line[0]);
>>> +}
>>> +
>>> +static Property pnv_spi_bus_properties[] = {
>>> + DEFINE_PROP_UINT32("bus-id", PnvSPIBus, id, 0),
>>> + DEFINE_PROP_END_OF_LIST(),
>>> +};
>>> +
>>> +static void pnv_spi_bus_class_init(ObjectClass *klass, void *data)
>>> +{
>>> + DeviceClass *dc = DEVICE_CLASS(klass);
>>> +
>>> + dc->desc = "Pnv SPI Bus";
>>> + dc->realize = pnv_spi_bus_realize;
>>> + device_class_set_props(dc, pnv_spi_bus_properties);
>>> +}
>>> +
>>> +static const TypeInfo pnv_spi_bus_info = {
>>> + .name = TYPE_PNV_SPI_BUS,
>>> + .parent = TYPE_SYS_BUS_DEVICE,
>>> + .instance_size = sizeof(PnvSPIBus),
>>> + .class_init = pnv_spi_bus_class_init,
>>> +};
>>> +
>>> +static Property pnv_spi_controller_properties[] = {
>>> + DEFINE_PROP_UINT32("spic_num", PnvSpiController, spic_num, 0),
>>> + DEFINE_PROP_END_OF_LIST(),
>>> +};
>>> +
>>> +static void pnv_spi_controller_realize(DeviceState *dev, Error **errp)
>>> +{
>>> + PnvSpiController *sc = PNV_SPICONTROLLER(dev);
>>> +
>>> + Object *bus = OBJECT(&sc->bus);
>>> + if (!object_property_set_int(bus, "bus-id", sc->spic_num, errp)) {
>>> + return;
>>> + }
>>> +
>>> + if (!sysbus_realize(SYS_BUS_DEVICE(bus), errp)) {
>>> + return;
>>> + }
>>> +
>>> + /* spi controller scoms */
>>> + pnv_xscom_region_init(&sc->xscom_spic_regs, OBJECT(sc),
>>> + &pnv_spi_controller_xscom_ops, sc,
>>> + "xscom-spi-controller-regs",
>>> + PNV10_XSCOM_PIB_SPIC_SIZE);
>>> +}
>>> +
>>> +static int pnv_spi_controller_dt_xscom(PnvXScomInterface *dev, void *fdt,
>>> + int offset)
>>> +{
>>> + PnvSpiController *sc = PNV_SPICONTROLLER(dev);
>>> + g_autofree char *name;
>>> + int sc_offset;
>>> + const char compat[] = "ibm,power10-spi_controller";
>>> + uint32_t spic_pcba = PNV10_XSCOM_PIB_SPIC_BASE +
>>> + sc->spic_num * PNV10_XSCOM_PIB_SPIC_SIZE;
>>> + uint32_t reg[] = {
>>> + cpu_to_be32(spic_pcba),
>>> + cpu_to_be32(PNV10_XSCOM_PIB_SPIC_SIZE)
>>> + };
>>> + name = g_strdup_printf("spi_controller@%x", spic_pcba);
>>> + sc_offset = fdt_add_subnode(fdt, offset, name);
>>> + _FDT(sc_offset);
>>> +
>>> + _FDT(fdt_setprop(fdt, sc_offset, "reg", reg, sizeof(reg)));
>>> + _FDT(fdt_setprop(fdt, sc_offset, "compatible", compat, sizeof(compat)));
>>> + _FDT((fdt_setprop_cell(fdt, sc_offset, "spic_num#", sc->spic_num)));
>>> + return 0;
>>> +}
>>> +
>>> +static void pnv_spi_instance_init(Object *obj)
>>> +{
>>> + PnvSpiController *sc = PNV_SPICONTROLLER(obj);
>>> +
>>> + /* Initialise the bus object */
>>> + object_initialize_child(obj, "bus", &sc->bus, TYPE_PNV_SPI_BUS);
>>> +}
>>> +
>>> +static void pnv_spi_controller_class_init(ObjectClass *klass, void *data)
>>> +{
>>> + DeviceClass *dc = DEVICE_CLASS(klass);
>>> + PnvXScomInterfaceClass *xscomc = PNV_XSCOM_INTERFACE_CLASS(klass);
>>> +
>>> + xscomc->dt_xscom = pnv_spi_controller_dt_xscom;
>>> +
>>> + dc->desc = "PowerNV SPI Controller";
>>> + dc->realize = pnv_spi_controller_realize;
>>> + device_class_set_props(dc, pnv_spi_controller_properties);
>>> +}
>>> +
>>> +static const TypeInfo pnv_spi_controller_info = {
>>> + .name = TYPE_PNV_SPI_CONTROLLER,
>>> + .parent = TYPE_DEVICE,
>>> + .instance_init = pnv_spi_instance_init,
>>> + .instance_size = sizeof(PnvSpiController),
>>> + .class_init = pnv_spi_controller_class_init,
>>> + .interfaces = (InterfaceInfo[]) {
>>> + { TYPE_PNV_XSCOM_INTERFACE },
>>> + { }
>>> + }
>>> +};
>>> +
>>> +static void pnv_spi_controller_register_types(void)
>>> +{
>>> + type_register_static(&pnv_spi_bus_info);
>>> + type_register_static(&pnv_spi_controller_info);
>>> +}
>>> +
>>> +type_init(pnv_spi_controller_register_types);
>>> diff --git a/hw/ppc/Kconfig b/hw/ppc/Kconfig
>>> index 37ccf9cdca..ea1178bd73 100644
>>> --- a/hw/ppc/Kconfig
>>> +++ b/hw/ppc/Kconfig
>>> @@ -35,6 +35,7 @@ config POWERNV
>>> select PCI_POWERNV
>>> select PCA9552
>>> select PCA9554
>>> + select SSI
>>> config PPC405
>>> bool
>>> diff --git a/hw/ppc/meson.build b/hw/ppc/meson.build
>>> index d096636ee7..68fadbae7b 100644
>>> --- a/hw/ppc/meson.build
>>> +++ b/hw/ppc/meson.build
>>> @@ -56,6 +56,7 @@ ppc_ss.add(when: 'CONFIG_POWERNV', if_true: files(
>>> 'pnv_pnor.c',
>>> 'pnv_nest_pervasive.c',
>>> 'pnv_n1_chiplet.c',
>>> + 'pnv_spi_controller.c',
>>> ))
>>> # PowerPC 4xx boards
>>> ppc_ss.add(when: 'CONFIG_PPC405', if_true: files(
>>
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 4/6] hw/misc: Microchip's 25CSM04 SEEPROM model
2024-04-09 17:56 ` [PATCH v2 4/6] hw/misc: Microchip's 25CSM04 SEEPROM model Chalapathi V
@ 2024-04-22 14:44 ` Cédric Le Goater
0 siblings, 0 replies; 16+ messages in thread
From: Cédric Le Goater @ 2024-04-22 14:44 UTC (permalink / raw)
To: Chalapathi V, qemu-devel
Cc: qemu-ppc, fbarrat, npiggin, calebs, chalapathi.v, saif.abrar,
dantan
Hello Chalapathi
On 4/9/24 19:56, Chalapathi V wrote:
> This commit implements a Serial EEPROM utilizing the Serial Peripheral
> Interface (SPI) compatible bus.
> Currently implemented SEEPROM is Microchip's 25CSM04 which provides 4 Mbits
> of Serial EEPROM utilizing the Serial Peripheral Interface (SPI) compatible
> bus. The device is organized as 524288 bytes of 8 bits each (512Kbyte) and
> is optimized for use in consumer and industrial applications where reliable
> and dependable nonvolatile memory storage is essential.
>
> This seeprom device is created from a parent "ssi-peripheral".
Can the hw/block/m25p80c model be extented instead ?
Thanks,
C.
>
> Signed-off-by: Chalapathi V <chalapathi.v@linux.ibm.com>
> ---
> include/hw/misc/seeprom_25csm04.h | 48 ++
> hw/misc/seeprom_25csm04.c | 780 ++++++++++++++++++++++++++++++
> hw/misc/Kconfig | 3 +
> hw/misc/meson.build | 1 +
> hw/ppc/Kconfig | 1 +
> 5 files changed, 833 insertions(+)
> create mode 100644 include/hw/misc/seeprom_25csm04.h
> create mode 100644 hw/misc/seeprom_25csm04.c
>
> diff --git a/include/hw/misc/seeprom_25csm04.h b/include/hw/misc/seeprom_25csm04.h
> new file mode 100644
> index 0000000000..0343530354
> --- /dev/null
> +++ b/include/hw/misc/seeprom_25csm04.h
> @@ -0,0 +1,48 @@
> +/*
> + * 25CSM04 Serial EEPROM model
> + *
> + * Copyright (c) 2024, IBM Corporation.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * The Microchip Technology Inc. 25CSM04 provides 4 Mbits of Serial EEPROM
> + * utilizing the Serial Peripheral Interface (SPI) compatible bus. The device
> + * is organized as 524288 bytes of 8 bits each (512Kbyte) and is optimized
> + * for use in consumer and industrial applications where reliable and
> + * dependable nonvolatile memory storage is essential
> + */
> +
> +#ifndef SEEPROM_25CSM04_H
> +#define SEEPROM_25CSM04_H
> +
> +#include "hw/ssi/ssi.h"
> +#include "qom/object.h"
> +
> +#define TYPE_SEEPROM_25CSM04 "seeprom-25csm04"
> +
> +OBJECT_DECLARE_SIMPLE_TYPE(SeepromCsm04, SEEPROM_25CSM04)
> +
> +typedef struct SeepromCsm04 {
> + SSIPeripheral parent_object;
> +
> + char *file;
> + char *file_name;
> + uint8_t opcode;
> + uint32_t addr;
> + uint8_t rd_state;
> + bool locked;
> + bool command_byte;
> + /* device registers */
> + uint8_t status0;
> + uint8_t status1;
> + uint8_t dsn[16];
> + uint8_t uplid[256];
> + uint8_t mpr[8];
> + uint8_t idr[5];
> +} SeepromCsm04;
> +
> +uint32_t seeprom_transfer(SSIPeripheral *ss, uint32_t tx);
> +void seeprom_realize(SSIPeripheral *dev, Error **errp);
> +bool compute_addr(SeepromCsm04 *s, uint32_t tx);
> +bool validate_addr(SeepromCsm04 *s);
> +#endif /* PPC_PNV_SPI_SEEPROM_H */
> diff --git a/hw/misc/seeprom_25csm04.c b/hw/misc/seeprom_25csm04.c
> new file mode 100644
> index 0000000000..45df66e4b0
> --- /dev/null
> +++ b/hw/misc/seeprom_25csm04.c
> @@ -0,0 +1,780 @@
> +/*
> + * 25CSM04 Serial EEPROM model
> + *
> + * Copyright (c) 2024, IBM Corporation.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/log.h"
> +#include "hw/misc/seeprom_25csm04.h"
> +#include "hw/qdev-properties.h"
> +#include "qemu/datadir.h"
> +#include <math.h>
> +
> +#define SPI_DEBUG(x)
> +
> +/*
> + * 2-byte STATUS register which is a combination of six nonvolatile bits of
> + * EEPROM and five volatile latches.
> + *
> + * status 0:
> + * bit 7 WPEN: Write-Protect Enable bit
> + * 1 = Write-Protect pin is enabled, 0 = Write-Protect pin is ignored
> + *
> + * bit 3-2 BP<1:0>: Block Protection bits
> + * 00 = No array write protection
> + * 01 = Upper quarter memory array protection
> + * 10 = Upper half memory array protection
> + * 11 = Entire memory array protection
> + *
> + * bit 1 WEL: Write Enable Latch bit
> + * 1 = WREN has been executed and device is enabled for writing
> + * 0 = Device is not write-enabled
> + *
> + * bit 0 RDY/BSY: Ready/Busy Status Latch bit
> + * 1 = Device is busy with an internal write cycle
> + * 0 = Device is ready for a new sequence
> + */
> +#define STATUS0_WPEN 0x7
> +#define STATUS0_BP 0x2
> +#define STATUS0_WEL 0x1
> +#define STATUS0_BUSY 0x0
> +
> +/*
> + * status 1:
> + * bit 7 WPM: Write Protection Mode bit(1)
> + * 1 = Enhanced Write Protection mode selected (factory default)
> + * 0 = Legacy Write Protection mode selected
> + *
> + * bit 6 ECS: Error Correction State Latch bit
> + * 1 = The previously executed read sequence did require the ECC
> + * 0 = The previous executed read sequence did not require the ECC
> + *
> + * bit 5 FMPC: Freeze Memory Protection Configuration bit(2)
> + * 1 = Memory Partition registers and write protection mode are permanently
> + * frozen and cannot be modified
> + * 0 = Memory Partition registers and write protection mode are not frozen
> + * and are modifiable
> + *
> + * bit 4 PREL: Partition Register Write Enable Latch bit
> + * 1 = PRWE has been executed and WMPR, FRZR and PPAB instructions are enabled
> + * 0 = WMPR, FRZR and PPAB instructions are disabled
> + *
> + * bit 3 PABP: Partition Address Boundary Protection bit
> + * 1 = Partition Address Endpoints set in Memory Partition registers
> + * cannot be modified
> + * 0 = Partition Address Endpoints set in Memory Partition registers
> + * are modifiable
> + *
> + * bit 0 RDY/BSY: Ready/Busy Status Latch bit
> + * 1 = Device is busy with an internal write cycle
> + * 0 = Device is ready for a new sequence
> + */
> +#define STATUS1_WPM 0x7
> +#define STATUS1_ECS 0x6
> +#define STATUS1_FMPC 0x5
> +#define STATUS1_PREL 0x4
> +#define STATUS1_PABP 0x3
> +#define STATUS1_BUSY 0x0
> +
> +/*
> + * MEMORY PARTITION REGISTERS
> + * Note 1: The MPR cannot be written if the FMPC bit has been set.
> + * 2: The Partition Endpoint Address bits cannot be written if the PABP
> + * bit has been set.
> + *
> + * bits 7-6 PB<1:0>: Partition Behavior bits(1)
> + * 00 = Partition is open and writing is permitted
> + * factory default is unprotected.
> + * 01 = Partition is always write-protected but can be reversed at a later
> + * time (software write-protected).
> + * 10 = Partition is write-protected only when WP pin is asserted
> + * (hardware write-protected).
> + * 11 = Partition is software write-protected and MPR is permanently locked
> + *
> + * bit 5-0 A<18:13>: Partition Endpoint Address bits(1, 2)
> + * 000000 = Endpoint address of partition is set to 01FFFh.
> + * 000001 = Endpoint address of partition is set to 03FFFh.
> + * ----
> + * 111110 = Endpoint address of partition is set to 7DFFFh.
> + * 111111 = Endpoint address of partition is set to 7FFFFh.
> + */
> +#define MPR_PB 0x6
> +#define MPR_PEA 0x5
> +
> +/* INSTRUCTION SET FOR 25CSM04 */
> +#define RDSR 0x05
> +#define WRBP 0x08
> +#define WREN 0x06
> +#define WRDI 0x04
> +#define WRSR 0x01
> +#define READ 0x03
> +#define WRITE 0x02
> +#define RDEX_CHLK 0x83
> +#define WREX_LOCK 0x82
> +#define RMPR 0x31
> +#define PRWE 0x07
> +#define PRWD 0x0A
> +#define WMPR 0x32
> +#define PPAB 0x34
> +#define FRZR 0x37
> +#define SPID 0x9F
> +#define SRST 0x7C
> +
> +/* READ FSM state */
> +#define ST_IDLE 0
> +#define ST_READ 1
> +#define ST_SEC_READ 2
> +
> +#define DATA_LEN 4
> +
> +uint32_t seeprom_transfer(SSIPeripheral *ss, uint32_t tx)
> +{
> + SeepromCsm04 *s = SEEPROM_25CSM04(ss);
> + uint16_t idx;
> + FILE *f;
> + bool status0_busy;
> + bool status1_busy;
> + uint32_t rx = -1;
> + uint32_t *buf = NULL;
> + bool failed = false;
> +
> + buf = g_malloc0(sizeof(uint32_t));
> + SPI_DEBUG(qemu_log("Received SPI request, tx = 0x%x\n", tx));
> + if (s->command_byte) {
> + s->opcode = tx >> 24;
> + SPI_DEBUG(qemu_log("Command Opcode (0x%x)\n", s->opcode));
> + /*
> + * Check if device is busy with internal write cycle, During this
> + * time, only the Read STATUS Register (RDSR) and the Write Ready/Busy
> + * Poll (WRBP) instructions will be executed by the device.
> + */
> + status0_busy = extract8(s->status0, STATUS0_BUSY, 1);
> + status1_busy = extract8(s->status1, STATUS1_BUSY, 1);
> + if (((status0_busy == 1) || (status1_busy == 1)) &&
> + ((s->opcode != RDSR) || (s->opcode != WRBP))) {
> + qemu_log_mask(LOG_GUEST_ERROR, "Busy with internal write Cycle, "
> + "opcode(0x%x) not executed\n", s->opcode);
> + return rx;
> + }
> + /* For a new command sequence compute Address */
> + failed = compute_addr(s, tx);
> + /*
> + * Address computation failed, nothing to do further, just send
> + * response and return from here.
> + */
> + if (failed) {
> + return tx;
> + }
> + }
> +
> + switch (s->opcode) {
> + case READ:
> + if (!s->command_byte) {
> + SPI_DEBUG(qemu_log("READ(0x%x), addr(0x%x)\n",
> + s->opcode, s->addr));
> + s->rd_state = ST_READ;
> + if (s->file) {
> + f = fopen(s->file, "rb+");
> + if (f) {
> + if (!fseek(f, s->addr, SEEK_SET)) {
> + if (fread(buf, sizeof(uint32_t), 1, f) == 1) {
> + SPI_DEBUG(qemu_log("Read 4 bytes from seeprom\n"));
> + rx = *buf;
> + } else {
> + if (ferror(f)) {
> + SPI_DEBUG(qemu_log("Error reading seeprom\n"));
> + }
> + }
> + }
> + }
> + fclose(f);
> + }
> + s->addr = (s->addr & 0x7FFFF) + DATA_LEN;
> + }
> + break;
> +
> + case RDSR:
> + SPI_DEBUG(qemu_log("READ Status Register - RDSR(0x%x)\n",
> + s->opcode));
> + rx = 0;
> + rx = rx | (s->status0 << 24);
> + rx = rx | (s->status1 << 16);
> + break;
> +
> + case WRBP:
> + SPI_DEBUG(qemu_log("Write Ready/Busy Poll - WRBP(0x%x)\n",
> + s->opcode));
> + status0_busy = extract8(s->status0, STATUS0_BUSY, 1);
> + status1_busy = extract8(s->status1, STATUS1_BUSY, 1);
> + rx = 0;
> + if ((status0_busy == 1) || (status1_busy == 1)) {
> + rx = rx | (0xFF << 24);
> + }
> + break;
> +
> + case WREN:
> + SPI_DEBUG(qemu_log("Set Write Enable Latch (WEL) WREN(0x%x)\n",
> + s->opcode));
> + s->status0 = deposit32(s->status0, STATUS0_WEL, 1, 1);
> + break;
> +
> + case WRDI:
> + SPI_DEBUG(qemu_log("Reset Write Enable Latch (WEL) WRDI(0x%x)\n",
> + s->opcode));
> + s->status0 = deposit32(s->status0, STATUS0_WEL, 1, 0);
> + break;
> +
> + case WRSR:
> + SPI_DEBUG(qemu_log("Write STATUS Register WRSR(0x%x)\n",
> + s->opcode));
> + s->status0 = deposit32(s->status0, STATUS0_WEL, 1, 1);
> + if (extract8(s->status0, STATUS0_WEL, 1) == 1) {
> + /* Mask and update status0/1 bytes */
> + s->status0 = (tx >> 16) & 0x8C;
> + s->status1 = (tx >> 8) & 0x80;
> + }
> + break;
> +
> + case SPID:
> + SPI_DEBUG(qemu_log("READ IDENTIFICATION REGISTER, SPID(0x%x)\n",
> + s->opcode));
> + rx = 0;
> + for (idx = 0; idx < DATA_LEN; idx++) {
> + rx = (rx << 8) | s->idr[idx];
> + }
> + break;
> +
> + case SRST:
> + SPI_DEBUG(qemu_log("Software Device Reset, SRST(0x%x)\n",
> + s->opcode));
> + /*
> + * Note: The SRST instruction cannot interrupt the device while it is
> + * in a Busy state (Section 6.1.4 Ready/Busy Status Latch).
> + * This is already taken care of when the command opcode is fetched
> + *
> + * 1.2 Device Default State
> + * 1.2.1 POWER-UP DEFAULT STATE
> + * The 25CSM04 default state upon power-up consists of:
> + * - Standby Power mode (CS = HIGH)
> + * - A high-to-low level transition on CS is required to enter the
> + * active state
> + * - WEL bit in the STATUS register = 0
> + * - ECS bit in the STATUS register = 0
> + * - PREL bit in the STATUS register = 0
> + * - Ready/Busy (RDY/BUSY) bit in the STATUS register = 0, indicating
> + * the device is ready to accept a new instruction.
> + */
> + s->status0 = deposit32(s->status0, STATUS0_WEL, 1, 0);
> + s->status1 = deposit32(s->status1, STATUS1_ECS, 1, 0);
> + s->status1 = deposit32(s->status1, STATUS1_PREL, 1, 0);
> + s->status0 = deposit32(s->status0, STATUS0_BUSY, 1, 0);
> + s->status1 = deposit32(s->status1, STATUS1_BUSY, 1, 0);
> + break;
> +
> + case WRITE:
> + if (!s->command_byte) {
> + SPI_DEBUG(qemu_log("WRITE(0x%x), addr(0x%x)\n",
> + s->opcode, s->addr));
> + if (extract8(s->status0, STATUS0_WEL, 1) != 1) {
> + qemu_log_mask(LOG_GUEST_ERROR, "Device is not Write Enabled, "
> + "ignoring WRITE instruction\n");
> + break;
> + }
> + /* Write into SEEPROM Array */
> + SPI_DEBUG(qemu_log("Write sequence\n"));
> + buf = &tx;
> + if (s->file) {
> + f = fopen(s->file, "rb+");
> + if (f) {
> + if (!fseek(f, s->addr, SEEK_SET)) {
> + if (fwrite(buf, sizeof(uint32_t), 1, f) == 1) {
> + SPI_DEBUG(qemu_log("Write 4 bytes to seeprom\n"));
> + } else {
> + SPI_DEBUG(qemu_log("Failed to write seeprom\n"));
> + }
> + }
> + }
> + fclose(f);
> + }
> + /* Increase offset in the page */
> + s->addr += DATA_LEN;
> + }
> + break;
> +
> + case RMPR:
> + if (!s->command_byte) {
> + /*
> + * The address for each Memory Partition register is embedded into
> + * the first address byte sent to the device,in bit positions A18
> + * through A16.
> + */
> + SPI_DEBUG(qemu_log("RMPR(0x%x) for MPR[%d]\n", s->opcode,
> + extract8(s->addr, 16, 2)));
> + rx = 0;
> + rx = rx | (s->mpr[extract8(s->addr, 16, 2)] << 24);
> + }
> + break;
> +
> + case PRWE:
> + SPI_DEBUG(qemu_log("Set Memory Partition Write Enable Latch "
> + "PRWE(0x%x)\n", s->opcode));
> + s->status1 = deposit32(s->status1, STATUS1_PREL, 1, 1);
> + break;
> +
> + case PRWD:
> + SPI_DEBUG(qemu_log("Reset Memory Partition Write Enable Latch "
> + "PRWD(0x%x)\n", s->opcode));
> + s->status1 = deposit32(s->status1, STATUS1_PREL, 1, 0);
> + break;
> +
> + case WMPR:
> + if (!s->command_byte) {
> + /*
> + * The address for each Memory Partition register is embedded into
> + * the first address byte sent to the device,in bit positions A18
> + * through A16.
> + */
> + SPI_DEBUG(qemu_log("Write Memory Partition Register[%d] "
> + "WMPR(0x%x)\n",
> + extract8(s->addr, 16, 2), s->opcode));
> + /*
> + * Once the WEL and PREL bits in the STATUS register have been
> + * set to 1, the Memory Partition registers can be programmed
> + * provided that the FMPC bit in the STATUS register has not
> + * already been set to a logic 1.
> + */
> + if ((extract8(s->status0, STATUS0_WEL, 1) != 1) ||
> + (extract8(s->status1, STATUS1_PREL, 1) != 1) ||
> + (extract8(s->status1, STATUS1_FMPC, 1) == 1)) {
> + qemu_log_mask(LOG_GUEST_ERROR, "ignoring write to MPR\n");
> + break;
> + }
> + if (extract8(s->status1, STATUS1_PABP, 1) == 1) {
> + /* Partition Address Boundaries Protected */
> + s->mpr[extract8(s->addr, 16, 2)] =
> + ((tx >> 30) & 0x3);
> + } else {
> + s->mpr[extract8(s->addr, 16, 2)] = (tx >> 24) & 0xFF;
> + }
> + s->status0 = deposit32(s->status0, STATUS0_WEL, 1, 0);
> + s->status1 = deposit32(s->status1, STATUS1_PREL, 1, 0);
> + }
> + break;
> +
> + case PPAB:
> + if (!s->command_byte) {
> + SPI_DEBUG(qemu_log("Protect Partition Address Boundaries"
> + "PPAB(0x%x)\n", s->opcode));
> + if ((extract8(s->status0, STATUS0_WEL, 1) != 1) ||
> + (extract8(s->status1, STATUS1_PREL, 1) != 1) ||
> + (extract8(s->status1, STATUS1_FMPC, 1) == 1)) {
> + qemu_log_mask(LOG_GUEST_ERROR, "Ignoring PPAB command\n");
> + break;
> + }
> + tx = (tx >> 24) & 0xFF;
> + if (tx == 0xFF) {
> + s->status1 = deposit32(s->status1,
> + STATUS1_PABP, 1, 1);
> + } else if (tx == 0x0) {
> + s->status1 = deposit32(s->status1,
> + STATUS1_PABP, 1, 0);
> + } else {
> + qemu_log_mask(LOG_GUEST_ERROR, "Incorrect data byte(0x%x), "
> + "should be 0x0 or 0xFF\n", tx);
> + }
> + s->status0 = deposit32(s->status0, STATUS0_WEL, 1, 0);
> + s->status1 = deposit32(s->status1, STATUS1_PREL, 1, 0);
> + }
> + break;
> +
> + case FRZR:
> + if (!s->command_byte) {
> + SPI_DEBUG(qemu_log("Freeze Memory Protection Configuration "
> + "FRZR(0x%x)\n", s->opcode));
> + if ((extract8(s->status0, STATUS0_WEL, 1) != 1) ||
> + (extract8(s->status1, STATUS1_PREL, 1) != 1) ||
> + (extract8(s->status1, STATUS1_FMPC, 1) == 1)) {
> + qemu_log_mask(LOG_GUEST_ERROR, "ignoring FRZR command\n");
> + break;
> + }
> + tx = (tx >> 24) & 0xFF;
> + if (tx == 0xD2) {
> + s->status1 = deposit32(s->status1,
> + STATUS1_FMPC, 1, 1);
> + } else {
> + qemu_log_mask(LOG_GUEST_ERROR, "Invalid confirmation data "
> + "byte(0x%x), expecting 0xD2", tx);
> + }
> + s->status0 = deposit32(s->status0, STATUS0_WEL, 1, 0);
> + s->status1 = deposit32(s->status1, STATUS1_PREL, 1, 0);
> + }
> + break;
> +
> + case RDEX_CHLK:
> + if (!s->command_byte) {
> + SPI_DEBUG(qemu_log("OPCODE = (0x%x)\n", s->opcode));
> + rx = 0;
> + /* Address bit 10 must be 0 to read security register */
> + if (extract8(s->addr, 10, 1) == 0) {
> + uint16_t sidx;
> + /* RDEX */
> + s->rd_state = ST_SEC_READ;
> + for (idx = 0; idx < DATA_LEN; idx++) {
> + sidx = s->addr & 0x1FF;
> + if (sidx <= 0xFF) {
> + rx = (rx << 8) | s->dsn[sidx];
> + } else {
> + rx = (rx << 8) | s->uplid[sidx & 0xFF];
> + }
> + s->addr = (s->addr & ~0x1FF) | ((s->addr + 1) & 0x1FF);
> + }
> + } else {
> + /* CHLK */
> + if (s->locked) {
> + rx = rx | (0x01 << 24);
> + }
> + }
> + }
> + break;
> +
> + case WREX_LOCK:
> + if (!s->command_byte) {
> + SPI_DEBUG(qemu_log("OPCODE = (0x%x)\n", s->opcode));
> + if (s->locked == true) {
> + qemu_log_mask(LOG_GUEST_ERROR, "Device is already Locked, "
> + "command is ignored\n");
> + break;
> + }
> + if (extract8(s->status0, STATUS0_WEL, 1) != 1) {
> + qemu_log_mask(LOG_GUEST_ERROR, "Device is not Write Enabled, "
> + "command is ignored\n");
> + break;
> + }
> + /* Address bit 10 must be 0 to write to security register */
> + if (extract8(s->addr, 10, 1) == 0) {
> + /* WREX */
> + for (idx = 0; idx < DATA_LEN; idx++) {
> + /*
> + * The Device Serial Number is factory programmed and
> + * read-only.
> + */
> + s->uplid[extract8(s->addr, 0, 8)] =
> + (tx >> (24 - idx * 8)) & 0xFF;
> + /* Increase address with the page, and let it rollover*/
> + s->addr = (s->addr & ~0xFF) | ((s->addr + 1) & 0xFF);
> + }
> + } else {
> + /*
> + * LOCK (82h) instruction is clocked in on the SI line,
> + * followed by a fake address where bits A[23:0] are don't
> + * care bits with the exception that bit A10 must be set to 1.
> + * Finally, a confirmation data byte of xxxx_xx1xb is sent
> + */
> + if (((tx >> 24) & 0x02) == 0x2) {
> + s->locked = true;
> + }
> + }
> + }
> + break;
> +
> + default:
> + qemu_log_mask(LOG_GUEST_ERROR, "Invalid instruction(0x%x)\n",
> + s->opcode);
> + } /* end of switch */
> + if (s->command_byte) {
> + s->command_byte = false;
> + }
> + return rx;
> +}
> +
> +/*
> + * Method : compute_addr
> + * This method is used to compute address and data offset for supported
> + * opcodes and only invoked when a valid new command sequence starts aka
> + * first is 1.
> + */
> +bool compute_addr(SeepromCsm04 *s, uint32_t tx)
> +{
> + bool addr_wr_protected = false;
> + bool failed = false;
> +
> + switch (s->opcode) {
> + case READ:
> + case WRITE:
> + SPI_DEBUG(qemu_log("Compute address and payload buffer data offset "
> + "for %s\n", (s->opcode == READ) ? "READ" : "WRITE"));
> + /*
> + * Fetch address from size 24 bit from offset 1,2,3 of payload
> + * and mask of higher 5 bits as valid memory array size is 512KB
> + */
> + s->addr = tx & 0x7FFFF;
> + if (s->opcode == WRITE) {
> + addr_wr_protected = validate_addr(s);
> + if (addr_wr_protected) {
> + qemu_log_mask(LOG_GUEST_ERROR, "SEEPROM Address(0x%x) is Write "
> + "protected\n", s->addr);
> + failed = true;
> + }
> + }
> + break;
> + case RMPR:
> + case WMPR:
> + SPI_DEBUG(qemu_log("Compute MPR address for %s MPR\n",
> + (s->opcode == RMPR) ? "READ" : "WRITE"));
> + /*
> + * The address for each Memory Partition register is embedded into
> + * the first address byte sent to the device,in bit positions A18
> + * through A16.
> + */
> + s->addr = tx & 0x70000;
> + break;
> +
> + case PPAB:
> + case FRZR:
> + SPI_DEBUG(qemu_log("Validate if addr[15:0] is %s\n",
> + (s->opcode == PPAB) ? "0xCCFF for PPAB" :
> + "0xAA40 for FRZR"));
> + /* Address bits A23-A16 are ignored. */
> + s->addr = tx & 0xFFFF;
> + /* Address bits A15-A0 must be set to CC55h. */
> + if ((s->opcode == PPAB) && s->addr != 0xCC55) {
> + qemu_log_mask(LOG_GUEST_ERROR, "Invalid addr[15:0] = 0x%x sent for "
> + "PPAB\n", s->addr);
> + failed = true;
> + }
> + /* Address bits A15-A0 must be set to AA40h. */
> + if ((s->opcode == FRZR) && s->addr != 0xAA40) {
> + qemu_log_mask(LOG_GUEST_ERROR, "Invalid addr[15:0] = 0x%x sent for "
> + "FRZR\n", s->addr);
> + failed = true;
> + }
> + break;
> +
> + case RDEX_CHLK:
> + case WREX_LOCK:
> + SPI_DEBUG(qemu_log("Compute Address for Security reg command\n"));
> + /*
> + * RDEX : A[23:9] are don't care bits, except A10 which must be a
> + * logic 0.
> + * WREX : A[23:9] are don't care bits, except A10 which must be a
> + * logic 0 and A8 which must be a logic 1 to address the
> + * second Security register byte that is user programmable.
> + * CHLK : A[23:0] are don't care bits, except A10 which must be a
> + * logic 1.
> + * LOCK : A[23:0] are don't care bits, except A10 which must be a
> + * logic 1.
> + */
> + s->addr = tx & 0x5FF;
> + SPI_DEBUG(qemu_log("Received Command %s\n",
> + (s->opcode == RDEX_CHLK)
> + ? (extract32(s->addr, 10, 1) ?
> + "CHLK : Check Lock Status of Security Register" :
> + "RDEX : Read from the Security Register")
> + : (extract32(s->addr, 10, 1) ?
> + "LOCK : Lock the Security Register (permanent)" :
> + "WREX : Write to the Security Register")));
> + if ((s->opcode == WREX_LOCK) &&
> + (extract32(s->addr, 10, 1) == 0)) {
> + /*
> + * WREX
> + * In Legacy Write Protection mode, the Security register is
> + * write-protected when the BP <1:0> bits (bits 3-2 byte0) of
> + * the STATUS register = 11.
> + */
> + if (extract8(s->status1, STATUS1_WPM, 1) == 0) {
> + addr_wr_protected = validate_addr(s);
> + } else {
> + if (extract32(s->addr, 0, 9) <= 0xFF) {
> + addr_wr_protected = true;
> + }
> + }
> + if (addr_wr_protected) {
> + qemu_log_mask(LOG_GUEST_ERROR, "SEEPROM Address(0x%x) is "
> + "Write protected\n", s->addr);
> + failed = true;
> + }
> + }
> + break;
> + } /* end of switch */
> + return failed;
> +} /* end of method compute_addr */
> +
> +/*
> + * Method : validate_addr
> + * This method validates whether SEEPROM address is write protected or not
> + */
> +
> +bool validate_addr(SeepromCsm04 *s)
> +{
> + bool addr_wr_protected = false;
> + uint8_t mpr_idx = 0;
> +
> + if (extract8(s->status1, STATUS1_WPM, 1) == 1) {
> + /*
> + * enhanced write protection
> + * Memory partition register Bit5 through bit0 contain the Partition
> + * Endpoint Address of A18:A13, where A12:A0 are a logic "1". For
> + * example, if the first partition of the memory array is desired to
> + * stop after 128-Kbit of memory, that end point address is 03FFFh. The
> + * corresponding A18:A13 address bits to be loaded into MPR0 are
> + * therefore 000001b. The eight MPRs are each decoded sequentially by
> + * the device, starting with MPR0. Each MPR should be set to a
> + * Partition Endpoint Address greater than the ending address of the
> + * previous MPR. If a higher order MPR sets a Partition Endpoint Address
> + * less than or equal to the ending address of a lower order MPR, that
> + * higher order MPR is ignored and no protection is set by it's
> + * contents.
> + */
> + for (mpr_idx = 0; mpr_idx < 8; mpr_idx++) {
> + if ((extract32(s->addr, 13, 6)) <=
> + (extract8(s->mpr[mpr_idx], MPR_PEA, 1))) {
> + switch (extract8(s->mpr[mpr_idx], MPR_PB, 2)) {
> + case 0:
> + /*
> + * 0b00 = Partition is open and writing is permitted
> + * (factory default is unprotected).
> + */
> + addr_wr_protected = false;
> + break;
> + case 1:
> + /*
> + * 0b01 = Partition is always write-protected but can be
> + * reversed at a later time (software write-protected).
> + */
> + addr_wr_protected = true;
> + break;
> + case 2:
> + /*
> + * 0b10 = Partition is write-protected only when WP pin is
> + * asserted (hardware write-protected).
> + */
> + addr_wr_protected = false;
> + break;
> + case 3:
> + /*
> + * 0b11 = Partition is software write-protected and Memory
> + * Partition register is permanently locked.
> + */
> + addr_wr_protected = true;
> + break;
> + } /* end of switch */
> + break; /* break from for loop. */
> + }
> + } /* end of for loop */
> + } else {
> + /* Legacy write protection mode */
> + switch (extract8(s->status0, STATUS0_BP, 2)) {
> + case 0:
> + /*
> + * 0b00 = No array write protection
> + * EEPROM None
> + * Security Register 00000h - 000FFh
> + */
> + if ((s->opcode == WREX_LOCK) &&
> + (extract32(s->addr, 0, 9) <= 0xFF)) {
> + addr_wr_protected = true;
> + }
> + break;
> + case 1:
> + /*
> + * 0b01 = Upper quarter memory array protection
> + * EEPROM 60000h - 7FFFFh
> + * Security Register 00000h - 000FFh
> + */
> + if ((s->opcode == WREX_LOCK) &&
> + (extract32(s->addr, 0, 9) <= 0xFF)) {
> + addr_wr_protected = true;
> + } else if ((s->opcode == WRITE) &&
> + (extract32(s->addr, 0, 19) <= 0x60000)) {
> + addr_wr_protected = true;
> + }
> + break;
> + case 2:
> + /*
> + * 0b10 = Upper half memory array protection
> + * EEPROM 40000h - 7FFFFh
> + * Security Register 00000h - 000FFh
> + */
> + if ((s->opcode == WREX_LOCK) &&
> + (extract32(s->addr, 0, 9) <= 0xFF)) {
> + addr_wr_protected = true;
> + } else if ((s->opcode == WRITE) &&
> + (extract32(s->addr, 0, 19) <= 0x40000)) {
> + addr_wr_protected = true;
> + }
> + break;
> + case 3:
> + /*
> + * 0b11 = Entire memory array protection
> + * EEPROM 00000h - 7FFFFh
> + * Security Register 00000h - 001FFh
> + */
> + addr_wr_protected = true;
> + break;
> + } /* end of switch */
> + }
> + return addr_wr_protected;
> +} /* end of validate_addr */
> +
> +
> +static int seeprom_cs(SSIPeripheral *ss, bool select)
> +{
> + SeepromCsm04 *s = SEEPROM_25CSM04(ss);
> +
> + if (select) {
> + s->command_byte = false;
> + s->rd_state = ST_IDLE;
> + s->status0 = deposit32(s->status0, STATUS0_WEL, 1, 0);
> + } else {
> + s->command_byte = true;
> + }
> + return 0;
> +}
> +
> +
> +void seeprom_realize(SSIPeripheral *dev, Error **errp)
> +{
> + SeepromCsm04 *s = SEEPROM_25CSM04(dev);
> +
> + s->command_byte = true;
> + s->rd_state = ST_IDLE;
> + if (s->file_name) {
> + s->file = qemu_find_file(QEMU_FILE_TYPE_BIOS, s->file_name);
> + }
> +}
> +
> +static Property seeprom_props[] = {
> + DEFINE_PROP_STRING("filename", SeepromCsm04, file_name),
> + DEFINE_PROP_END_OF_LIST()
> +};
> +
> +static void seeprom_25csm04_class_init(ObjectClass *klass, void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(klass);
> + SSIPeripheralClass *k = SSI_PERIPHERAL_CLASS(klass);
> +
> + k->transfer = seeprom_transfer;
> + k->realize = seeprom_realize;
> + k->set_cs = seeprom_cs;
> + k->cs_polarity = SSI_CS_LOW;
> + device_class_set_props(dc, seeprom_props);
> +
> + dc->desc = "PowerNV SPI SEEPROM";
> +}
> +
> +static const TypeInfo seeprom_25csm04_info = {
> + .name = TYPE_SEEPROM_25CSM04,
> + .parent = TYPE_SSI_PERIPHERAL,
> + .instance_size = sizeof(SeepromCsm04),
> + .class_init = seeprom_25csm04_class_init,
> +};
> +
> +static void seeprom_25csm04_register_types(void)
> +{
> + type_register_static(&seeprom_25csm04_info);
> +}
> +
> +type_init(seeprom_25csm04_register_types);
> diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
> index 1e08785b83..9442cc657d 100644
> --- a/hw/misc/Kconfig
> +++ b/hw/misc/Kconfig
> @@ -38,6 +38,9 @@ config PCA9554
> bool
> depends on I2C
>
> +config SEEPROM_25CSM04
> + bool
> +
> config I2C_ECHO
> bool
> default y if TEST_DEVICES
> diff --git a/hw/misc/meson.build b/hw/misc/meson.build
> index 86596a3888..fd4d646f98 100644
> --- a/hw/misc/meson.build
> +++ b/hw/misc/meson.build
> @@ -3,6 +3,7 @@ system_ss.add(when: 'CONFIG_EDU', if_true: files('edu.c'))
> system_ss.add(when: 'CONFIG_FW_CFG_DMA', if_true: files('vmcoreinfo.c'))
> system_ss.add(when: 'CONFIG_ISA_DEBUG', if_true: files('debugexit.c'))
> system_ss.add(when: 'CONFIG_ISA_TESTDEV', if_true: files('pc-testdev.c'))
> +system_ss.add(when: 'CONFIG_SEEPROM_25CSM04', if_true: files('seeprom_25csm04.c'))
> system_ss.add(when: 'CONFIG_PCI_TESTDEV', if_true: files('pci-testdev.c'))
> system_ss.add(when: 'CONFIG_UNIMP', if_true: files('unimp.c'))
> system_ss.add(when: 'CONFIG_EMPTY_SLOT', if_true: files('empty_slot.c'))
> diff --git a/hw/ppc/Kconfig b/hw/ppc/Kconfig
> index ea1178bd73..6a4803d4ec 100644
> --- a/hw/ppc/Kconfig
> +++ b/hw/ppc/Kconfig
> @@ -36,6 +36,7 @@ config POWERNV
> select PCA9552
> select PCA9554
> select SSI
> + select SEEPROM_25CSM04
>
> config PPC405
> bool
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 5/6] hw/ppc: SPI controller wiring to P10 chip and create seeprom device
2024-04-09 17:56 ` [PATCH v2 5/6] hw/ppc: SPI controller wiring to P10 chip and create seeprom device Chalapathi V
@ 2024-04-22 15:03 ` Cédric Le Goater
2024-04-24 17:12 ` Chalapathi V
0 siblings, 1 reply; 16+ messages in thread
From: Cédric Le Goater @ 2024-04-22 15:03 UTC (permalink / raw)
To: Chalapathi V, qemu-devel
Cc: qemu-ppc, fbarrat, npiggin, calebs, chalapathi.v, saif.abrar,
dantan
On 4/9/24 19:56, Chalapathi V wrote:
> In this commit
> Creates SPI controller on p10 chip.
> Create the keystore seeprom of type "seeprom-25csm04"
> Connect the cs of seeprom to PIB_SPIC[2] cs irq.
>
> The QOM tree of spi controller and seeprom are.
> /machine (powernv10-machine)
> /chip[0] (power10_v2.0-pnv-chip)
> /pib_spic[2] (pnv-spi-controller)
> /bus (pnv-spi-bus)
> /pnv-spi-bus.2 (SSI)
> /xscom-spi-controller-regs[0] (memory-region)
>
> /machine (powernv10-machine)
> /unattached (container)
> /device[7] (seeprom-25csm04)
> /ssi-gpio-cs[0] (irq)
>
> (qemu) qom-get /machine/unattached/device[7] "parent_bus"
> "/machine/chip[0]/pib_spic[2]/bus/pnv-spi-bus.2"
>
> Signed-off-by: Chalapathi V <chalapathi.v@linux.ibm.com>
> ---
> include/hw/ppc/pnv_chip.h | 3 +++
> hw/ppc/pnv.c | 36 +++++++++++++++++++++++++++++++++++-
> 2 files changed, 38 insertions(+), 1 deletion(-)
>
> diff --git a/include/hw/ppc/pnv_chip.h b/include/hw/ppc/pnv_chip.h
> index 8589f3291e..3edf13e8f9 100644
> --- a/include/hw/ppc/pnv_chip.h
> +++ b/include/hw/ppc/pnv_chip.h
> @@ -6,6 +6,7 @@
> #include "hw/ppc/pnv_core.h"
> #include "hw/ppc/pnv_homer.h"
> #include "hw/ppc/pnv_n1_chiplet.h"
> +#include "hw/ppc/pnv_spi_controller.h"
> #include "hw/ppc/pnv_lpc.h"
> #include "hw/ppc/pnv_occ.h"
> #include "hw/ppc/pnv_psi.h"
> @@ -118,6 +119,8 @@ struct Pnv10Chip {
> PnvSBE sbe;
> PnvHomer homer;
> PnvN1Chiplet n1_chiplet;
> +#define PNV10_CHIP_MAX_PIB_SPIC 6
> + PnvSpiController pib_spic[PNV10_CHIP_MAX_PIB_SPIC];
>
> uint32_t nr_quads;
> PnvQuad *quads;
> diff --git a/hw/ppc/pnv.c b/hw/ppc/pnv.c
> index 6e3a5ccdec..eeb2d650bd 100644
> --- a/hw/ppc/pnv.c
> +++ b/hw/ppc/pnv.c
> @@ -46,6 +46,7 @@
> #include "hw/pci-host/pnv_phb.h"
> #include "hw/pci-host/pnv_phb3.h"
> #include "hw/pci-host/pnv_phb4.h"
> +#include "hw/ssi/ssi.h"
>
> #include "hw/ppc/xics.h"
> #include "hw/qdev-properties.h"
> @@ -1829,6 +1830,11 @@ static void pnv_chip_power10_instance_init(Object *obj)
> for (i = 0; i < pcc->i2c_num_engines; i++) {
> object_initialize_child(obj, "i2c[*]", &chip10->i2c[i], TYPE_PNV_I2C);
> }
> +
> + for (i = 0; i < PNV10_CHIP_MAX_PIB_SPIC ; i++) {
> + object_initialize_child(obj, "pib_spic[*]", &chip10->pib_spic[i],
> + TYPE_PNV_SPI_CONTROLLER);
> + }
> }
>
> static void pnv_chip_power10_quad_realize(Pnv10Chip *chip10, Error **errp)
> @@ -2043,7 +2049,35 @@ static void pnv_chip_power10_realize(DeviceState *dev, Error **errp)
> qdev_get_gpio_in(DEVICE(&chip10->psi),
> PSIHB9_IRQ_SBE_I2C));
> }
> -
> + /* PIB SPI Controller */
> + for (i = 0; i < PNV10_CHIP_MAX_PIB_SPIC; i++) {
> + object_property_set_int(OBJECT(&chip10->pib_spic[i]), "spic_num",
> + i , &error_fatal);
> + /*
> + * The TPM attached SPIC needs to reverse the bit order in each byte
> + * it sends to the TPM.
> + */
> + if (i == 4) {
> + object_property_set_bool(OBJECT(&chip10->pib_spic[i]),
> + "reverse_bits", true, &error_fatal);
> + }
or
object_property_set_bool(OBJECT(&chip10->pib_spic[i]),
"reverse_bits", (i == 4) , &error_fatal);
That said. This setting looks weird to me.
Why do we need to reverse the bits ? is it an endian issue ?
Are there other SPI devices on the buses ?
> + if (!qdev_realize(DEVICE(&chip10->pib_spic[i]), NULL, errp)) {
> + return;
> + }
> + pnv_xscom_add_subregion(chip, PNV10_XSCOM_PIB_SPIC_BASE +
> + i * PNV10_XSCOM_PIB_SPIC_SIZE,
> + &chip10->pib_spic[i].xscom_spic_regs);
> + }
The devices below belong to the rainer machine it seems. We should introduce
a per-machine handler to create them like it was done for the I2C devices.
For this purpose, the PnvMachineClass::i2c_init) handler could be changed
to create all machine specific devices.
> + /* Primary MEAS/MVPD/Keystore SEEPROM connected to pib_spic[2] */
> + DeviceState *seeprom = qdev_new("seeprom-25csm04");
> + qdev_prop_set_string(seeprom, "filename",
> + "sbe_measurement_seeprom.bin.ecc");
This should be done differently. Here is a command line example :
$ qemu-system-arm -M ast2600-evb \
-blockdev node-name=fmc0,driver=file,filename=/path/to/fmc0.img \
-device mx66u51235f,bus=ssi.0,cs=0x0,drive=fmc0 \
-blockdev node-name=fmc1,driver=file,filename=/path/to/fmc1.img \
-device mx66u51235f,bus=ssi.0,cs=0x1,drive=fmc1 \
-blockdev node-name=spi1,driver=file,filename=/path/to/spi1.img \
-device mx66u51235f,cs=0x0,bus=ssi.1,drive=spi1 \
...
Please try to rework "seeprom-25csm04" on top of "m25p80". It should help.
> + ssi_realize_and_unref(seeprom, ((&chip10->pib_spic[2])->bus).ssi_bus,
> + &error_fatal);
> + qemu_irq seeprom_cs = qdev_get_gpio_in_named(seeprom, SSI_GPIO_CS, 0);
> + Object *bus = OBJECT(&(&chip10->pib_spic[2])->bus);
> + sysbus_connect_irq(SYS_BUS_DEVICE(bus), 0, seeprom_cs);
Could you please slightly change the models to connect the IRQ line using
qdev_connect_gpio_out instead ? See pnv_rainier_i2c_init.
Thanks,
C.
> }
>
> static void pnv_rainier_i2c_init(PnvMachineState *pnv)
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 5/6] hw/ppc: SPI controller wiring to P10 chip and create seeprom device
2024-04-22 15:03 ` Cédric Le Goater
@ 2024-04-24 17:12 ` Chalapathi V
0 siblings, 0 replies; 16+ messages in thread
From: Chalapathi V @ 2024-04-24 17:12 UTC (permalink / raw)
To: Cédric Le Goater, qemu-devel
Cc: qemu-ppc, fbarrat, npiggin, calebs, chalapathi.v, saif.abrar,
dantan
Hello Cedric,
Thank You for reviewing v2 patches.
Regards,
Chalapathi
On 22-04-2024 20:33, Cédric Le Goater wrote:
> On 4/9/24 19:56, Chalapathi V wrote:
>> In this commit
>> Creates SPI controller on p10 chip.
>> Create the keystore seeprom of type "seeprom-25csm04"
>> Connect the cs of seeprom to PIB_SPIC[2] cs irq.
>>
>> The QOM tree of spi controller and seeprom are.
>> /machine (powernv10-machine)
>> /chip[0] (power10_v2.0-pnv-chip)
>> /pib_spic[2] (pnv-spi-controller)
>> /bus (pnv-spi-bus)
>> /pnv-spi-bus.2 (SSI)
>> /xscom-spi-controller-regs[0] (memory-region)
>>
>> /machine (powernv10-machine)
>> /unattached (container)
>> /device[7] (seeprom-25csm04)
>> /ssi-gpio-cs[0] (irq)
>>
>> (qemu) qom-get /machine/unattached/device[7] "parent_bus"
>> "/machine/chip[0]/pib_spic[2]/bus/pnv-spi-bus.2"
>>
>> Signed-off-by: Chalapathi V <chalapathi.v@linux.ibm.com>
>> ---
>> include/hw/ppc/pnv_chip.h | 3 +++
>> hw/ppc/pnv.c | 36 +++++++++++++++++++++++++++++++++++-
>> 2 files changed, 38 insertions(+), 1 deletion(-)
>>
>> diff --git a/include/hw/ppc/pnv_chip.h b/include/hw/ppc/pnv_chip.h
>> index 8589f3291e..3edf13e8f9 100644
>> --- a/include/hw/ppc/pnv_chip.h
>> +++ b/include/hw/ppc/pnv_chip.h
>> @@ -6,6 +6,7 @@
>> #include "hw/ppc/pnv_core.h"
>> #include "hw/ppc/pnv_homer.h"
>> #include "hw/ppc/pnv_n1_chiplet.h"
>> +#include "hw/ppc/pnv_spi_controller.h"
>> #include "hw/ppc/pnv_lpc.h"
>> #include "hw/ppc/pnv_occ.h"
>> #include "hw/ppc/pnv_psi.h"
>> @@ -118,6 +119,8 @@ struct Pnv10Chip {
>> PnvSBE sbe;
>> PnvHomer homer;
>> PnvN1Chiplet n1_chiplet;
>> +#define PNV10_CHIP_MAX_PIB_SPIC 6
>> + PnvSpiController pib_spic[PNV10_CHIP_MAX_PIB_SPIC];
>> uint32_t nr_quads;
>> PnvQuad *quads;
>> diff --git a/hw/ppc/pnv.c b/hw/ppc/pnv.c
>> index 6e3a5ccdec..eeb2d650bd 100644
>> --- a/hw/ppc/pnv.c
>> +++ b/hw/ppc/pnv.c
>> @@ -46,6 +46,7 @@
>> #include "hw/pci-host/pnv_phb.h"
>> #include "hw/pci-host/pnv_phb3.h"
>> #include "hw/pci-host/pnv_phb4.h"
>> +#include "hw/ssi/ssi.h"
>> #include "hw/ppc/xics.h"
>> #include "hw/qdev-properties.h"
>> @@ -1829,6 +1830,11 @@ static void
>> pnv_chip_power10_instance_init(Object *obj)
>> for (i = 0; i < pcc->i2c_num_engines; i++) {
>> object_initialize_child(obj, "i2c[*]", &chip10->i2c[i],
>> TYPE_PNV_I2C);
>> }
>> +
>> + for (i = 0; i < PNV10_CHIP_MAX_PIB_SPIC ; i++) {
>> + object_initialize_child(obj, "pib_spic[*]",
>> &chip10->pib_spic[i],
>> + TYPE_PNV_SPI_CONTROLLER);
>> + }
>> }
>> static void pnv_chip_power10_quad_realize(Pnv10Chip *chip10,
>> Error **errp)
>> @@ -2043,7 +2049,35 @@ static void
>> pnv_chip_power10_realize(DeviceState *dev, Error **errp)
>> qdev_get_gpio_in(DEVICE(&chip10->psi),
>> PSIHB9_IRQ_SBE_I2C));
>> }
>> -
>> + /* PIB SPI Controller */
>> + for (i = 0; i < PNV10_CHIP_MAX_PIB_SPIC; i++) {
>> + object_property_set_int(OBJECT(&chip10->pib_spic[i]), "spic_num",
>> + i , &error_fatal);
>> + /*
>> + * The TPM attached SPIC needs to reverse the bit order in
>> each byte
>> + * it sends to the TPM.
>> + */
>> + if (i == 4) {
>> + object_property_set_bool(OBJECT(&chip10->pib_spic[i]),
>> + "reverse_bits", true, &error_fatal);
>> + }
>
> or
>
> object_property_set_bool(OBJECT(&chip10->pib_spic[i]),
> "reverse_bits", (i == 4) , &error_fatal);
>
>
> That said. This setting looks weird to me.
>
> Why do we need to reverse the bits ? is it an endian issue ?
>
> Are there other SPI devices on the buses ?
There are no other SPI devices attached to this bus.
Checking about reversing the bits that sent to TPM.
>
>> + if (!qdev_realize(DEVICE(&chip10->pib_spic[i]), NULL, errp)) {
>> + return;
>> + }
>> + pnv_xscom_add_subregion(chip, PNV10_XSCOM_PIB_SPIC_BASE +
>> + i * PNV10_XSCOM_PIB_SPIC_SIZE,
>> + &chip10->pib_spic[i].xscom_spic_regs);
>> + }
>
>
> The devices below belong to the rainer machine it seems. We should
> introduce
> a per-machine handler to create them like it was done for the I2C
> devices.
> For this purpose, the PnvMachineClass::i2c_init) handler could be changed
> to create all machine specific devices.
ACK, Thank You.
>
>> + /* Primary MEAS/MVPD/Keystore SEEPROM connected to pib_spic[2] */
>> + DeviceState *seeprom = qdev_new("seeprom-25csm04");
>> + qdev_prop_set_string(seeprom, "filename",
>> + "sbe_measurement_seeprom.bin.ecc");
>
> This should be done differently. Here is a command line example :
>
> $ qemu-system-arm -M ast2600-evb \
> -blockdev node-name=fmc0,driver=file,filename=/path/to/fmc0.img \
> -device mx66u51235f,bus=ssi.0,cs=0x0,drive=fmc0 \
> -blockdev node-name=fmc1,driver=file,filename=/path/to/fmc1.img \
> -device mx66u51235f,bus=ssi.0,cs=0x1,drive=fmc1 \
> -blockdev node-name=spi1,driver=file,filename=/path/to/spi1.img \
> -device mx66u51235f,cs=0x0,bus=ssi.1,drive=spi1 \
> ...
>
> Please try to rework "seeprom-25csm04" on top of "m25p80". It should
> help.
Sure, Will check and do the updates.
>
>
>> + ssi_realize_and_unref(seeprom,
>> ((&chip10->pib_spic[2])->bus).ssi_bus,
>> + &error_fatal);
>> + qemu_irq seeprom_cs = qdev_get_gpio_in_named(seeprom,
>> SSI_GPIO_CS, 0);
>> + Object *bus = OBJECT(&(&chip10->pib_spic[2])->bus);
>> + sysbus_connect_irq(SYS_BUS_DEVICE(bus), 0, seeprom_cs);
>
> Could you please slightly change the models to connect the IRQ line using
> qdev_connect_gpio_out instead ? See pnv_rainier_i2c_init.
>
> Thanks,
>
> C.
Sure, Will check and update. Thank You.
>
>> }
>> static void pnv_rainier_i2c_init(PnvMachineState *pnv)
>
^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2024-04-24 17:13 UTC | newest]
Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-04-09 17:56 [PATCH v2 0/6] hw/ppc: SPI model Chalapathi V
2024-04-09 17:56 ` [PATCH v2 1/6] hw/ppc: remove SPI responder model Chalapathi V
2024-04-15 14:46 ` Cédric Le Goater
2024-04-09 17:56 ` [PATCH v2 2/6] hw/ppc: SPI controller model - registers implementation Chalapathi V
2024-04-15 15:14 ` Cédric Le Goater
2024-04-16 17:02 ` Chalapathi V
2024-04-22 14:06 ` Cédric Le Goater
2024-04-09 17:56 ` [PATCH v2 3/6] hw/ppc: SPI controller model - sequencer and shifter Chalapathi V
2024-04-16 9:39 ` Cédric Le Goater
2024-04-16 17:08 ` Chalapathi V
2024-04-09 17:56 ` [PATCH v2 4/6] hw/misc: Microchip's 25CSM04 SEEPROM model Chalapathi V
2024-04-22 14:44 ` Cédric Le Goater
2024-04-09 17:56 ` [PATCH v2 5/6] hw/ppc: SPI controller wiring to P10 chip and create seeprom device Chalapathi V
2024-04-22 15:03 ` Cédric Le Goater
2024-04-24 17:12 ` Chalapathi V
2024-04-09 17:57 ` [PATCH v2 6/6] tests/qtest: Add pnv-spi-seeprom qtest Chalapathi V
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).