Thanks! Reviewed-by: Glenn Miles <mil...@linux.ibm.com>
Glenn On Wed, 2024-06-26 at 04:05 -0500, 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 <chalapath...@linux.ibm.com> > --- > include/hw/ppc/pnv_xscom.h | 3 + > include/hw/ssi/pnv_spi.h | 40 +++++++ > include/hw/ssi/pnv_spi_regs.h | 67 +++++++++++ > hw/ssi/pnv_spi.c | 214 > ++++++++++++++++++++++++++++++++++ > hw/ppc/Kconfig | 3 + > hw/ssi/Kconfig | 4 + > hw/ssi/meson.build | 1 + > hw/ssi/trace-events | 6 + > 8 files changed, 338 insertions(+) > create mode 100644 include/hw/ssi/pnv_spi.h > create mode 100644 include/hw/ssi/pnv_spi_regs.h > create mode 100644 hw/ssi/pnv_spi.c > > diff --git a/include/hw/ppc/pnv_xscom.h b/include/hw/ppc/pnv_xscom.h > index c8dd42bffd..0020dd172f 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/include/hw/ssi/pnv_spi.h b/include/hw/ssi/pnv_spi.h > new file mode 100644 > index 0000000000..833042b74b > --- /dev/null > +++ b/include/hw/ssi/pnv_spi.h > @@ -0,0 +1,40 @@ > +/* > + * QEMU PowerPC SPI 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. > + */ > + > +#ifndef PPC_PNV_SPI_H > +#define PPC_PNV_SPI_H > + > +#include "hw/ssi/ssi.h" > +#include "hw/sysbus.h" > + > +#define TYPE_PNV_SPI "pnv-spi" > +OBJECT_DECLARE_SIMPLE_TYPE(PnvSpi, PNV_SPI) > + > +#define PNV_SPI_REG_SIZE 8 > +#define PNV_SPI_REGS 7 > + > +#define TYPE_PNV_SPI_BUS "pnv-spi-bus" > +typedef struct PnvSpi { > + SysBusDevice parent_obj; > + > + SSIBus *ssi_bus; > + qemu_irq *cs_line; > + MemoryRegion xscom_spic_regs; > + /* SPI object number */ > + uint32_t spic_num; > + > + /* SPI registers */ > + uint64_t regs[PNV_SPI_REGS]; > + uint8_t seq_op[PNV_SPI_REG_SIZE]; > + uint64_t status; > +} PnvSpi; > +#endif /* PPC_PNV_SPI_H */ > diff --git a/include/hw/ssi/pnv_spi_regs.h > b/include/hw/ssi/pnv_spi_regs.h > new file mode 100644 > index 0000000000..5b6ff72d02 > --- /dev/null > +++ b/include/hw/ssi/pnv_spi_regs.h > @@ -0,0 +1,67 @@ > +/* > + * QEMU PowerPC SPI model > + * > + * Copyright (c) 2024, IBM Corporation. > + * > + * SPDX-License-Identifier: GPL-2.0-or-later > + */ > + > +#ifndef PNV_SPI_CONTROLLER_REGS_H > +#define PNV_SPI_CONTROLLER_REGS_H > + > +/* > + * Macros from target/ppc/cpu.h > + * These macros are copied from ppc target specific file > target/ppc/cpu.h > + * as target/ppc/cpu.h cannot be included here. > + */ > +#define PPC_BIT(bit) (0x8000000000000000ULL >> (bit)) > +#define PPC_BIT8(bit) (0x80 >> (bit)) > +#define PPC_BITMASK(bs, be) ((PPC_BIT(bs) - PPC_BIT(be)) | > PPC_BIT(bs)) > +#define PPC_BITMASK8(bs, be) ((PPC_BIT8(bs) - PPC_BIT8(be)) | > PPC_BIT8(bs)) > +#define MASK_TO_LSH(m) (__builtin_ffsll(m) - 1) > +#define GETFIELD(m, v) (((v) & (m)) >> MASK_TO_LSH(m)) > +#define SETFIELD(m, v, val) \ > + (((v) & ~(m)) | ((((typeof(v))(val)) << MASK_TO_LSH(m)) & > (m))) > + > +/* Error Register */ > +#define ERROR_REG 0x00 > + > +/* counter_config_reg */ > +#define SPI_CTR_CFG_REG 0x01 > + > +/* config_reg */ > +#define CONFIG_REG1 0x02 > + > +/* clock_config_reset_control_ecc_enable_reg */ > +#define SPI_CLK_CFG_REG 0x03 > +#define SPI_CLK_CFG_HARD_RST 0x0084000000000000; > +#define SPI_CLK_CFG_RST_CTRL PPC_BITMASK(24, 27) > + > +/* memory_mapping_reg */ > +#define SPI_MM_REG 0x04 > + > +/* transmit_data_reg */ > +#define SPI_XMIT_DATA_REG 0x05 > + > +/* receive_data_reg */ > +#define SPI_RCV_DATA_REG 0x06 > + > +/* sequencer_operation_reg */ > +#define SPI_SEQ_OP_REG 0x07 > + > +/* status_reg */ > +#define SPI_STS_REG 0x08 > +#define SPI_STS_RDR_FULL PPC_BIT(0) > +#define SPI_STS_RDR_OVERRUN PPC_BIT(1) > +#define SPI_STS_RDR_UNDERRUN PPC_BIT(2) > +#define SPI_STS_TDR_FULL PPC_BIT(4) > +#define SPI_STS_TDR_OVERRUN PPC_BIT(5) > +#define SPI_STS_TDR_UNDERRUN PPC_BIT(6) > +#define SPI_STS_SEQ_FSM PPC_BITMASK(8, 15) > +#define SPI_STS_SHIFTER_FSM PPC_BITMASK(16, 27) > +#define SPI_STS_SEQ_INDEX PPC_BITMASK(28, 31) > +#define SPI_STS_GEN_STATUS PPC_BITMASK(32, 63) > +#define SPI_STS_RDR PPC_BITMASK(1, 3) > +#define SPI_STS_TDR PPC_BITMASK(5, 7) > + > +#endif > diff --git a/hw/ssi/pnv_spi.c b/hw/ssi/pnv_spi.c > new file mode 100644 > index 0000000000..de3ffc4e56 > --- /dev/null > +++ b/hw/ssi/pnv_spi.c > @@ -0,0 +1,214 @@ > +/* > + * QEMU PowerPC SPI 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/ssi/pnv_spi.h" > +#include "hw/ssi/pnv_spi_regs.h" > +#include "hw/ssi/ssi.h" > +#include <libfdt.h> > +#include "hw/irq.h" > +#include "trace.h" > + > +/* > + * Macro from include/hw/ppc/fdt.h > + * fdt.h cannot be included here as it contain ppc target specific > dependency. > + */ > +#define _FDT(exp) \ > + do { \ > + int _ret = (exp); \ > + if (_ret < 0) { \ > + error_report("error creating device tree: %s: %s", \ > + #exp, fdt_strerror(_ret)); \ > + exit(1); \ > + } \ > + } while (0) > + > +static uint64_t pnv_spi_xscom_read(void *opaque, hwaddr addr, > unsigned size) > +{ > + PnvSpi *s = PNV_SPI(opaque); > + uint32_t reg = addr >> 3; > + uint64_t val = ~0ull; > + > + switch (reg) { > + case ERROR_REG: > + case SPI_CTR_CFG_REG: > + case CONFIG_REG1: > + case SPI_CLK_CFG_REG: > + case SPI_MM_REG: > + case SPI_XMIT_DATA_REG: > + val = s->regs[reg]; > + break; > + case SPI_RCV_DATA_REG: > + val = s->regs[reg]; > + trace_pnv_spi_read_RDR(val); > + s->status = SETFIELD(SPI_STS_RDR_FULL, s->status, 0); > + break; > + case SPI_SEQ_OP_REG: > + val = 0; > + for (int i = 0; i < PNV_SPI_REG_SIZE; i++) { > + val = (val << 8) | s->seq_op[i]; > + } > + break; > + case SPI_STS_REG: > + val = s->status; > + break; > + default: > + qemu_log_mask(LOG_GUEST_ERROR, "pnv_spi_regs: Invalid xscom > " > + "read at 0x%" PRIx32 "\n", reg); > + } > + > + trace_pnv_spi_read(addr, val); > + return val; > +} > + > +static void pnv_spi_xscom_write(void *opaque, hwaddr addr, > + uint64_t val, unsigned size) > +{ > + PnvSpi *s = PNV_SPI(opaque); > + uint32_t reg = addr >> 3; > + > + trace_pnv_spi_write(addr, val); > + > + switch (reg) { > + case ERROR_REG: > + case SPI_CTR_CFG_REG: > + case CONFIG_REG1: > + case SPI_MM_REG: > + case SPI_RCV_DATA_REG: > + s->regs[reg] = val; > + break; > + case SPI_CLK_CFG_REG: > + /* > + * To reset the SPI controller write the sequence 0x5 0xA to > + * reset_control field > + */ > + if ((GETFIELD(SPI_CLK_CFG_RST_CTRL, s- > >regs[SPI_CLK_CFG_REG]) == 0x5) > + && (GETFIELD(SPI_CLK_CFG_RST_CTRL, val) == 0xA)) { > + /* SPI controller reset sequence completed, > resetting */ > + s->regs[reg] = SPI_CLK_CFG_HARD_RST; > + } else { > + s->regs[reg] = val; > + } > + break; > + case SPI_XMIT_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. > + */ > + s->regs[reg] = val; > + trace_pnv_spi_write_TDR(val); > + s->status = SETFIELD(SPI_STS_TDR_FULL, s->status, 1); > + s->status = SETFIELD(SPI_STS_TDR_UNDERRUN, s->status, 0); > + break; > + case SPI_SEQ_OP_REG: > + for (int i = 0; i < PNV_SPI_REG_SIZE; i++) { > + s->seq_op[i] = (val >> (56 - i * 8)) & 0xFF; > + } > + break; > + case SPI_STS_REG: > + /* other fields are ignore_write */ > + s->status = SETFIELD(SPI_STS_RDR_OVERRUN, s->status, > + GETFIELD(SPI_STS_RDR, val)); > + s->status = SETFIELD(SPI_STS_TDR_OVERRUN, s->status, > + GETFIELD(SPI_STS_TDR, val)); > + break; > + default: > + qemu_log_mask(LOG_GUEST_ERROR, "pnv_spi_regs: Invalid xscom > " > + "write at 0x%" PRIx32 "\n", reg); > + } > + return; > +} > + > +static const MemoryRegionOps pnv_spi_xscom_ops = { > + .read = pnv_spi_xscom_read, > + .write = pnv_spi_xscom_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 Property pnv_spi_properties[] = { > + DEFINE_PROP_UINT32("spic_num", PnvSpi, spic_num, 0), > + DEFINE_PROP_END_OF_LIST(), > +}; > + > +static void pnv_spi_realize(DeviceState *dev, Error **errp) > +{ > + PnvSpi *s = PNV_SPI(dev); > + g_autofree char *name = g_strdup_printf(TYPE_PNV_SPI_BUS ".%d", > + s->spic_num); > + s->ssi_bus = ssi_create_bus(dev, name); > + s->cs_line = g_new0(qemu_irq, 1); > + qdev_init_gpio_out_named(DEVICE(s), s->cs_line, "cs", 1); > + > + /* spi scoms */ > + pnv_xscom_region_init(&s->xscom_spic_regs, OBJECT(s), > &pnv_spi_xscom_ops, > + s, "xscom-spi", > PNV10_XSCOM_PIB_SPIC_SIZE); > +} > + > +static int pnv_spi_dt_xscom(PnvXScomInterface *dev, void *fdt, > + int offset) > +{ > + PnvSpi *s = PNV_SPI(dev); > + g_autofree char *name; > + int s_offset; > + const char compat[] = "ibm,power10-spi"; > + uint32_t spic_pcba = PNV10_XSCOM_PIB_SPIC_BASE + > + s->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("pnv_spi@%x", spic_pcba); > + s_offset = fdt_add_subnode(fdt, offset, name); > + _FDT(s_offset); > + > + _FDT(fdt_setprop(fdt, s_offset, "reg", reg, sizeof(reg))); > + _FDT(fdt_setprop(fdt, s_offset, "compatible", compat, > sizeof(compat))); > + _FDT((fdt_setprop_cell(fdt, s_offset, "spic_num#", s- > >spic_num))); > + return 0; > +} > + > +static void pnv_spi_class_init(ObjectClass *klass, void *data) > +{ > + DeviceClass *dc = DEVICE_CLASS(klass); > + PnvXScomInterfaceClass *xscomc = > PNV_XSCOM_INTERFACE_CLASS(klass); > + > + xscomc->dt_xscom = pnv_spi_dt_xscom; > + > + dc->desc = "PowerNV SPI"; > + dc->realize = pnv_spi_realize; > + device_class_set_props(dc, pnv_spi_properties); > +} > + > +static const TypeInfo pnv_spi_info = { > + .name = TYPE_PNV_SPI, > + .parent = TYPE_SYS_BUS_DEVICE, > + .instance_size = sizeof(PnvSpi), > + .class_init = pnv_spi_class_init, > + .interfaces = (InterfaceInfo[]) { > + { TYPE_PNV_XSCOM_INTERFACE }, > + { } > + } > +}; > + > +static void pnv_spi_register_types(void) > +{ > + type_register_static(&pnv_spi_info); > +} > + > +type_init(pnv_spi_register_types); > diff --git a/hw/ppc/Kconfig b/hw/ppc/Kconfig > index 78f83e78ce..4668d59eab 100644 > --- a/hw/ppc/Kconfig > +++ b/hw/ppc/Kconfig > @@ -39,6 +39,9 @@ config POWERNV > select PCI_POWERNV > select PCA9552 > select PCA9554 > + select SSI > + select SSI_M25P80 > + select PNV_SPI > > config PPC405 > bool > diff --git a/hw/ssi/Kconfig b/hw/ssi/Kconfig > index 83ee53c1d0..8d180de7cf 100644 > --- a/hw/ssi/Kconfig > +++ b/hw/ssi/Kconfig > @@ -24,3 +24,7 @@ config STM32F2XX_SPI > config BCM2835_SPI > bool > select SSI > + > +config PNV_SPI > + bool > + select SSI > diff --git a/hw/ssi/meson.build b/hw/ssi/meson.build > index b999aeb027..b7ad7fca3b 100644 > --- a/hw/ssi/meson.build > +++ b/hw/ssi/meson.build > @@ -12,3 +12,4 @@ system_ss.add(when: 'CONFIG_IMX', if_true: > files('imx_spi.c')) > system_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_spi.c')) > system_ss.add(when: 'CONFIG_IBEX', if_true: > files('ibex_spi_host.c')) > system_ss.add(when: 'CONFIG_BCM2835_SPI', if_true: > files('bcm2835_spi.c')) > +system_ss.add(when: 'CONFIG_PNV_SPI', if_true: files('pnv_spi.c')) > diff --git a/hw/ssi/trace-events b/hw/ssi/trace-events > index 2d5bd2b83d..4388024a05 100644 > --- a/hw/ssi/trace-events > +++ b/hw/ssi/trace-events > @@ -32,3 +32,9 @@ ibex_spi_host_reset(const char *msg) "%s" > ibex_spi_host_transfer(uint32_t tx_data, uint32_t rx_data) "tx_data: > 0x%" PRIx32 " rx_data: @0x%" PRIx32 > ibex_spi_host_write(uint64_t addr, uint32_t size, uint64_t data) > "@0x%" PRIx64 " size %u: 0x%" PRIx64 > ibex_spi_host_read(uint64_t addr, uint32_t size) "@0x%" PRIx64 " > size %u:" > + > +#pnv_spi.c > +pnv_spi_read(uint64_t addr, uint64_t val) "addr 0x%" PRIx64 " val > 0x%" PRIx64 > +pnv_spi_write(uint64_t addr, uint64_t val) "addr 0x%" PRIx64 " val > 0x%" PRIx64 > +pnv_spi_read_RDR(uint64_t val) "data extracted = 0x%" PRIx64 > +pnv_spi_write_TDR(uint64_t val) "being written, data written = 0x%" > PRIx64