Message ID | 20211117141841.4696-7-francisco.iglesias@xilinx.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | Xilinx Versal's PMC SLCR and OSPI support | expand |
On Wed, Nov 17, 2021 at 02:18:38PM +0000, Francisco Iglesias wrote: > Add a model of Xilinx Versal's OSPI flash memory controller. Thanks Francisco, If you haven't already in this series, I think you should also add yourself in MAINTAINERS for this model. Reviewed-by: Edgar E. Iglesias <edgar.iglesias@xilinx.com> Cheers, Edgar > > Signed-off-by: Francisco Iglesias <francisco.iglesias@xilinx.com> > --- > hw/ssi/meson.build | 1 + > hw/ssi/xlnx-versal-ospi.c | 1892 +++++++++++++++++++++++++++++++++++++ > include/hw/ssi/xlnx-versal-ospi.h | 86 ++ > 3 files changed, 1979 insertions(+) > create mode 100644 hw/ssi/xlnx-versal-ospi.c > create mode 100644 include/hw/ssi/xlnx-versal-ospi.h > > diff --git a/hw/ssi/meson.build b/hw/ssi/meson.build > index 3d6bc82ab1..0ded9cd092 100644 > --- a/hw/ssi/meson.build > +++ b/hw/ssi/meson.build > @@ -7,5 +7,6 @@ softmmu_ss.add(when: 'CONFIG_SSI', if_true: files('ssi.c')) > softmmu_ss.add(when: 'CONFIG_STM32F2XX_SPI', if_true: files('stm32f2xx_spi.c')) > softmmu_ss.add(when: 'CONFIG_XILINX_SPI', if_true: files('xilinx_spi.c')) > softmmu_ss.add(when: 'CONFIG_XILINX_SPIPS', if_true: files('xilinx_spips.c')) > +softmmu_ss.add(when: 'CONFIG_XLNX_VERSAL', if_true: files('xlnx-versal-ospi.c')) > softmmu_ss.add(when: 'CONFIG_IMX', if_true: files('imx_spi.c')) > softmmu_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_spi.c')) > diff --git a/hw/ssi/xlnx-versal-ospi.c b/hw/ssi/xlnx-versal-ospi.c > new file mode 100644 > index 0000000000..c02fc143de > --- /dev/null > +++ b/hw/ssi/xlnx-versal-ospi.c > @@ -0,0 +1,1892 @@ > +/* > + * QEMU model of Xilinx Versal's OSPI controller. > + * > + * Copyright (c) 2021 Xilinx Inc. > + * Written by Francisco Iglesias <francisco.iglesias@xilinx.com> > + * > + * Permission is hereby granted, free of charge, to any person obtaining a copy > + * of this software and associated documentation files (the "Software"), to deal > + * in the Software without restriction, including without limitation the rights > + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell > + * copies of the Software, and to permit persons to whom the Software is > + * furnished to do so, subject to the following conditions: > + * > + * The above copyright notice and this permission notice shall be included in > + * all copies or substantial portions of the Software. > + * > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR > + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, > + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL > + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER > + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, > + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN > + * THE SOFTWARE. > + */ > +#include "qemu/osdep.h" > +#include "hw/sysbus.h" > +#include "migration/vmstate.h" > +#include "hw/qdev-properties.h" > +#include "qemu/bitops.h" > +#include "qemu/log.h" > +#include "hw/irq.h" > +#include "hw/ssi/xlnx-versal-ospi.h" > + > +#ifndef XILINX_VERSAL_OSPI_ERR_DEBUG > +#define XILINX_VERSAL_OSPI_ERR_DEBUG 0 > +#endif > + > +REG32(CONFIG_REG, 0x0) > + FIELD(CONFIG_REG, IDLE_FLD, 31, 1) > + FIELD(CONFIG_REG, DUAL_BYTE_OPCODE_EN_FLD, 30, 1) > + FIELD(CONFIG_REG, CRC_ENABLE_FLD, 29, 1) > + FIELD(CONFIG_REG, CONFIG_RESV2_FLD, 26, 3) > + FIELD(CONFIG_REG, PIPELINE_PHY_FLD, 25, 1) > + FIELD(CONFIG_REG, ENABLE_DTR_PROTOCOL_FLD, 24, 1) > + FIELD(CONFIG_REG, ENABLE_AHB_DECODER_FLD, 23, 1) > + FIELD(CONFIG_REG, MSTR_BAUD_DIV_FLD, 19, 4) > + FIELD(CONFIG_REG, ENTER_XIP_MODE_IMM_FLD, 18, 1) > + FIELD(CONFIG_REG, ENTER_XIP_MODE_FLD, 17, 1) > + FIELD(CONFIG_REG, ENB_AHB_ADDR_REMAP_FLD, 16, 1) > + FIELD(CONFIG_REG, ENB_DMA_IF_FLD, 15, 1) > + FIELD(CONFIG_REG, WR_PROT_FLASH_FLD, 14, 1) > + FIELD(CONFIG_REG, PERIPH_CS_LINES_FLD, 10, 4) > + FIELD(CONFIG_REG, PERIPH_SEL_DEC_FLD, 9, 1) > + FIELD(CONFIG_REG, ENB_LEGACY_IP_MODE_FLD, 8, 1) > + FIELD(CONFIG_REG, ENB_DIR_ACC_CTLR_FLD, 7, 1) > + FIELD(CONFIG_REG, RESET_CFG_FLD, 6, 1) > + FIELD(CONFIG_REG, RESET_PIN_FLD, 5, 1) > + FIELD(CONFIG_REG, HOLD_PIN_FLD, 4, 1) > + FIELD(CONFIG_REG, PHY_MODE_ENABLE_FLD, 3, 1) > + FIELD(CONFIG_REG, SEL_CLK_PHASE_FLD, 2, 1) > + FIELD(CONFIG_REG, SEL_CLK_POL_FLD, 1, 1) > + FIELD(CONFIG_REG, ENB_SPI_FLD, 0, 1) > +REG32(DEV_INSTR_RD_CONFIG_REG, 0x4) > + FIELD(DEV_INSTR_RD_CONFIG_REG, RD_INSTR_RESV5_FLD, 29, 3) > + FIELD(DEV_INSTR_RD_CONFIG_REG, DUMMY_RD_CLK_CYCLES_FLD, 24, 5) > + FIELD(DEV_INSTR_RD_CONFIG_REG, RD_INSTR_RESV4_FLD, 21, 3) > + FIELD(DEV_INSTR_RD_CONFIG_REG, MODE_BIT_ENABLE_FLD, 20, 1) > + FIELD(DEV_INSTR_RD_CONFIG_REG, RD_INSTR_RESV3_FLD, 18, 2) > + FIELD(DEV_INSTR_RD_CONFIG_REG, DATA_XFER_TYPE_EXT_MODE_FLD, 16, 2) > + FIELD(DEV_INSTR_RD_CONFIG_REG, RD_INSTR_RESV2_FLD, 14, 2) > + FIELD(DEV_INSTR_RD_CONFIG_REG, ADDR_XFER_TYPE_STD_MODE_FLD, 12, 2) > + FIELD(DEV_INSTR_RD_CONFIG_REG, PRED_DIS_FLD, 11, 1) > + FIELD(DEV_INSTR_RD_CONFIG_REG, DDR_EN_FLD, 10, 1) > + FIELD(DEV_INSTR_RD_CONFIG_REG, INSTR_TYPE_FLD, 8, 2) > + FIELD(DEV_INSTR_RD_CONFIG_REG, RD_OPCODE_NON_XIP_FLD, 0, 8) > +REG32(DEV_INSTR_WR_CONFIG_REG, 0x8) > + FIELD(DEV_INSTR_WR_CONFIG_REG, WR_INSTR_RESV4_FLD, 29, 3) > + FIELD(DEV_INSTR_WR_CONFIG_REG, DUMMY_WR_CLK_CYCLES_FLD, 24, 5) > + FIELD(DEV_INSTR_WR_CONFIG_REG, WR_INSTR_RESV3_FLD, 18, 6) > + FIELD(DEV_INSTR_WR_CONFIG_REG, DATA_XFER_TYPE_EXT_MODE_FLD, 16, 2) > + FIELD(DEV_INSTR_WR_CONFIG_REG, WR_INSTR_RESV2_FLD, 14, 2) > + FIELD(DEV_INSTR_WR_CONFIG_REG, ADDR_XFER_TYPE_STD_MODE_FLD, 12, 2) > + FIELD(DEV_INSTR_WR_CONFIG_REG, WR_INSTR_RESV1_FLD, 9, 3) > + FIELD(DEV_INSTR_WR_CONFIG_REG, WEL_DIS_FLD, 8, 1) > + FIELD(DEV_INSTR_WR_CONFIG_REG, WR_OPCODE_FLD, 0, 8) > +REG32(DEV_DELAY_REG, 0xc) > + FIELD(DEV_DELAY_REG, D_NSS_FLD, 24, 8) > + FIELD(DEV_DELAY_REG, D_BTWN_FLD, 16, 8) > + FIELD(DEV_DELAY_REG, D_AFTER_FLD, 8, 8) > + FIELD(DEV_DELAY_REG, D_INIT_FLD, 0, 8) > +REG32(RD_DATA_CAPTURE_REG, 0x10) > + FIELD(RD_DATA_CAPTURE_REG, RD_DATA_RESV3_FLD, 20, 12) > + FIELD(RD_DATA_CAPTURE_REG, DDR_READ_DELAY_FLD, 16, 4) > + FIELD(RD_DATA_CAPTURE_REG, RD_DATA_RESV2_FLD, 9, 7) > + FIELD(RD_DATA_CAPTURE_REG, DQS_ENABLE_FLD, 8, 1) > + FIELD(RD_DATA_CAPTURE_REG, RD_DATA_RESV1_FLD, 6, 2) > + FIELD(RD_DATA_CAPTURE_REG, SAMPLE_EDGE_SEL_FLD, 5, 1) > + FIELD(RD_DATA_CAPTURE_REG, DELAY_FLD, 1, 4) > + FIELD(RD_DATA_CAPTURE_REG, BYPASS_FLD, 0, 1) > +REG32(DEV_SIZE_CONFIG_REG, 0x14) > + FIELD(DEV_SIZE_CONFIG_REG, DEV_SIZE_RESV_FLD, 29, 3) > + FIELD(DEV_SIZE_CONFIG_REG, MEM_SIZE_ON_CS3_FLD, 27, 2) > + FIELD(DEV_SIZE_CONFIG_REG, MEM_SIZE_ON_CS2_FLD, 25, 2) > + FIELD(DEV_SIZE_CONFIG_REG, MEM_SIZE_ON_CS1_FLD, 23, 2) > + FIELD(DEV_SIZE_CONFIG_REG, MEM_SIZE_ON_CS0_FLD, 21, 2) > + FIELD(DEV_SIZE_CONFIG_REG, BYTES_PER_SUBSECTOR_FLD, 16, 5) > + FIELD(DEV_SIZE_CONFIG_REG, BYTES_PER_DEVICE_PAGE_FLD, 4, 12) > + FIELD(DEV_SIZE_CONFIG_REG, NUM_ADDR_BYTES_FLD, 0, 4) > +REG32(SRAM_PARTITION_CFG_REG, 0x18) > + FIELD(SRAM_PARTITION_CFG_REG, SRAM_PARTITION_RESV_FLD, 8, 24) > + FIELD(SRAM_PARTITION_CFG_REG, ADDR_FLD, 0, 8) > +REG32(IND_AHB_ADDR_TRIGGER_REG, 0x1c) > +REG32(DMA_PERIPH_CONFIG_REG, 0x20) > + FIELD(DMA_PERIPH_CONFIG_REG, DMA_PERIPH_RESV2_FLD, 12, 20) > + FIELD(DMA_PERIPH_CONFIG_REG, NUM_BURST_REQ_BYTES_FLD, 8, 4) > + FIELD(DMA_PERIPH_CONFIG_REG, DMA_PERIPH_RESV1_FLD, 4, 4) > + FIELD(DMA_PERIPH_CONFIG_REG, NUM_SINGLE_REQ_BYTES_FLD, 0, 4) > +REG32(REMAP_ADDR_REG, 0x24) > +REG32(MODE_BIT_CONFIG_REG, 0x28) > + FIELD(MODE_BIT_CONFIG_REG, RX_CRC_DATA_LOW_FLD, 24, 8) > + FIELD(MODE_BIT_CONFIG_REG, RX_CRC_DATA_UP_FLD, 16, 8) > + FIELD(MODE_BIT_CONFIG_REG, CRC_OUT_ENABLE_FLD, 15, 1) > + FIELD(MODE_BIT_CONFIG_REG, MODE_BIT_RESV1_FLD, 11, 4) > + FIELD(MODE_BIT_CONFIG_REG, CHUNK_SIZE_FLD, 8, 3) > + FIELD(MODE_BIT_CONFIG_REG, MODE_FLD, 0, 8) > +REG32(SRAM_FILL_REG, 0x2c) > + FIELD(SRAM_FILL_REG, SRAM_FILL_INDAC_WRITE_FLD, 16, 16) > + FIELD(SRAM_FILL_REG, SRAM_FILL_INDAC_READ_FLD, 0, 16) > +REG32(TX_THRESH_REG, 0x30) > + FIELD(TX_THRESH_REG, TX_THRESH_RESV_FLD, 5, 27) > + FIELD(TX_THRESH_REG, LEVEL_FLD, 0, 5) > +REG32(RX_THRESH_REG, 0x34) > + FIELD(RX_THRESH_REG, RX_THRESH_RESV_FLD, 5, 27) > + FIELD(RX_THRESH_REG, LEVEL_FLD, 0, 5) > +REG32(WRITE_COMPLETION_CTRL_REG, 0x38) > + FIELD(WRITE_COMPLETION_CTRL_REG, POLL_REP_DELAY_FLD, 24, 8) > + FIELD(WRITE_COMPLETION_CTRL_REG, POLL_COUNT_FLD, 16, 8) > + FIELD(WRITE_COMPLETION_CTRL_REG, ENABLE_POLLING_EXP_FLD, 15, 1) > + FIELD(WRITE_COMPLETION_CTRL_REG, DISABLE_POLLING_FLD, 14, 1) > + FIELD(WRITE_COMPLETION_CTRL_REG, POLLING_POLARITY_FLD, 13, 1) > + FIELD(WRITE_COMPLETION_CTRL_REG, WR_COMP_CTRL_RESV1_FLD, 12, 1) > + FIELD(WRITE_COMPLETION_CTRL_REG, POLLING_ADDR_EN_FLD, 11, 1) > + FIELD(WRITE_COMPLETION_CTRL_REG, POLLING_BIT_INDEX_FLD, 8, 3) > + FIELD(WRITE_COMPLETION_CTRL_REG, OPCODE_FLD, 0, 8) > +REG32(NO_OF_POLLS_BEF_EXP_REG, 0x3c) > +REG32(IRQ_STATUS_REG, 0x40) > + FIELD(IRQ_STATUS_REG, IRQ_STAT_RESV_FLD, 20, 12) > + FIELD(IRQ_STATUS_REG, ECC_FAIL_FLD, 19, 1) > + FIELD(IRQ_STATUS_REG, TX_CRC_CHUNK_BRK_FLD, 18, 1) > + FIELD(IRQ_STATUS_REG, RX_CRC_DATA_VAL_FLD, 17, 1) > + FIELD(IRQ_STATUS_REG, RX_CRC_DATA_ERR_FLD, 16, 1) > + FIELD(IRQ_STATUS_REG, IRQ_STAT_RESV1_FLD, 15, 1) > + FIELD(IRQ_STATUS_REG, STIG_REQ_INT_FLD, 14, 1) > + FIELD(IRQ_STATUS_REG, POLL_EXP_INT_FLD, 13, 1) > + FIELD(IRQ_STATUS_REG, INDRD_SRAM_FULL_FLD, 12, 1) > + FIELD(IRQ_STATUS_REG, RX_FIFO_FULL_FLD, 11, 1) > + FIELD(IRQ_STATUS_REG, RX_FIFO_NOT_EMPTY_FLD, 10, 1) > + FIELD(IRQ_STATUS_REG, TX_FIFO_FULL_FLD, 9, 1) > + FIELD(IRQ_STATUS_REG, TX_FIFO_NOT_FULL_FLD, 8, 1) > + FIELD(IRQ_STATUS_REG, RECV_OVERFLOW_FLD, 7, 1) > + FIELD(IRQ_STATUS_REG, INDIRECT_XFER_LEVEL_BREACH_FLD, 6, 1) > + FIELD(IRQ_STATUS_REG, ILLEGAL_ACCESS_DET_FLD, 5, 1) > + FIELD(IRQ_STATUS_REG, PROT_WR_ATTEMPT_FLD, 4, 1) > + FIELD(IRQ_STATUS_REG, INDIRECT_TRANSFER_REJECT_FLD, 3, 1) > + FIELD(IRQ_STATUS_REG, INDIRECT_OP_DONE_FLD, 2, 1) > + FIELD(IRQ_STATUS_REG, UNDERFLOW_DET_FLD, 1, 1) > + FIELD(IRQ_STATUS_REG, MODE_M_FAIL_FLD, 0, 1) > +REG32(IRQ_MASK_REG, 0x44) > + FIELD(IRQ_MASK_REG, IRQ_MASK_RESV_FLD, 20, 12) > + FIELD(IRQ_MASK_REG, ECC_FAIL_MASK_FLD, 19, 1) > + FIELD(IRQ_MASK_REG, TX_CRC_CHUNK_BRK_MASK_FLD, 18, 1) > + FIELD(IRQ_MASK_REG, RX_CRC_DATA_VAL_MASK_FLD, 17, 1) > + FIELD(IRQ_MASK_REG, RX_CRC_DATA_ERR_MASK_FLD, 16, 1) > + FIELD(IRQ_MASK_REG, IRQ_MASK_RESV1_FLD, 15, 1) > + FIELD(IRQ_MASK_REG, STIG_REQ_MASK_FLD, 14, 1) > + FIELD(IRQ_MASK_REG, POLL_EXP_INT_MASK_FLD, 13, 1) > + FIELD(IRQ_MASK_REG, INDRD_SRAM_FULL_MASK_FLD, 12, 1) > + FIELD(IRQ_MASK_REG, RX_FIFO_FULL_MASK_FLD, 11, 1) > + FIELD(IRQ_MASK_REG, RX_FIFO_NOT_EMPTY_MASK_FLD, 10, 1) > + FIELD(IRQ_MASK_REG, TX_FIFO_FULL_MASK_FLD, 9, 1) > + FIELD(IRQ_MASK_REG, TX_FIFO_NOT_FULL_MASK_FLD, 8, 1) > + FIELD(IRQ_MASK_REG, RECV_OVERFLOW_MASK_FLD, 7, 1) > + FIELD(IRQ_MASK_REG, INDIRECT_XFER_LEVEL_BREACH_MASK_FLD, 6, 1) > + FIELD(IRQ_MASK_REG, ILLEGAL_ACCESS_DET_MASK_FLD, 5, 1) > + FIELD(IRQ_MASK_REG, PROT_WR_ATTEMPT_MASK_FLD, 4, 1) > + FIELD(IRQ_MASK_REG, INDIRECT_TRANSFER_REJECT_MASK_FLD, 3, 1) > + FIELD(IRQ_MASK_REG, INDIRECT_OP_DONE_MASK_FLD, 2, 1) > + FIELD(IRQ_MASK_REG, UNDERFLOW_DET_MASK_FLD, 1, 1) > + FIELD(IRQ_MASK_REG, MODE_M_FAIL_MASK_FLD, 0, 1) > +REG32(LOWER_WR_PROT_REG, 0x50) > +REG32(UPPER_WR_PROT_REG, 0x54) > +REG32(WR_PROT_CTRL_REG, 0x58) > + FIELD(WR_PROT_CTRL_REG, WR_PROT_CTRL_RESV_FLD, 2, 30) > + FIELD(WR_PROT_CTRL_REG, ENB_FLD, 1, 1) > + FIELD(WR_PROT_CTRL_REG, INV_FLD, 0, 1) > +REG32(INDIRECT_READ_XFER_CTRL_REG, 0x60) > + FIELD(INDIRECT_READ_XFER_CTRL_REG, INDIR_RD_XFER_RESV_FLD, 8, 24) > + FIELD(INDIRECT_READ_XFER_CTRL_REG, NUM_IND_OPS_DONE_FLD, 6, 2) > + FIELD(INDIRECT_READ_XFER_CTRL_REG, IND_OPS_DONE_STATUS_FLD, 5, 1) > + FIELD(INDIRECT_READ_XFER_CTRL_REG, RD_QUEUED_FLD, 4, 1) > + FIELD(INDIRECT_READ_XFER_CTRL_REG, SRAM_FULL_FLD, 3, 1) > + FIELD(INDIRECT_READ_XFER_CTRL_REG, RD_STATUS_FLD, 2, 1) > + FIELD(INDIRECT_READ_XFER_CTRL_REG, CANCEL_FLD, 1, 1) > + FIELD(INDIRECT_READ_XFER_CTRL_REG, START_FLD, 0, 1) > +REG32(INDIRECT_READ_XFER_WATERMARK_REG, 0x64) > +REG32(INDIRECT_READ_XFER_START_REG, 0x68) > +REG32(INDIRECT_READ_XFER_NUM_BYTES_REG, 0x6c) > +REG32(INDIRECT_WRITE_XFER_CTRL_REG, 0x70) > + FIELD(INDIRECT_WRITE_XFER_CTRL_REG, INDIR_WR_XFER_RESV2_FLD, 8, 24) > + FIELD(INDIRECT_WRITE_XFER_CTRL_REG, NUM_IND_OPS_DONE_FLD, 6, 2) > + FIELD(INDIRECT_WRITE_XFER_CTRL_REG, IND_OPS_DONE_STATUS_FLD, 5, 1) > + FIELD(INDIRECT_WRITE_XFER_CTRL_REG, WR_QUEUED_FLD, 4, 1) > + FIELD(INDIRECT_WRITE_XFER_CTRL_REG, INDIR_WR_XFER_RESV1_FLD, 3, 1) > + FIELD(INDIRECT_WRITE_XFER_CTRL_REG, WR_STATUS_FLD, 2, 1) > + FIELD(INDIRECT_WRITE_XFER_CTRL_REG, CANCEL_FLD, 1, 1) > + FIELD(INDIRECT_WRITE_XFER_CTRL_REG, START_FLD, 0, 1) > +REG32(INDIRECT_WRITE_XFER_WATERMARK_REG, 0x74) > +REG32(INDIRECT_WRITE_XFER_START_REG, 0x78) > +REG32(INDIRECT_WRITE_XFER_NUM_BYTES_REG, 0x7c) > +REG32(INDIRECT_TRIGGER_ADDR_RANGE_REG, 0x80) > + FIELD(INDIRECT_TRIGGER_ADDR_RANGE_REG, IND_RANGE_RESV1_FLD, 4, 28) > + FIELD(INDIRECT_TRIGGER_ADDR_RANGE_REG, IND_RANGE_WIDTH_FLD, 0, 4) > +REG32(FLASH_COMMAND_CTRL_MEM_REG, 0x8c) > + FIELD(FLASH_COMMAND_CTRL_MEM_REG, FLASH_COMMAND_CTRL_MEM_RESV1_FLD, 29, 3) > + FIELD(FLASH_COMMAND_CTRL_MEM_REG, MEM_BANK_ADDR_FLD, 20, 9) > + FIELD(FLASH_COMMAND_CTRL_MEM_REG, FLASH_COMMAND_CTRL_MEM_RESV2_FLD, 19, 1) > + FIELD(FLASH_COMMAND_CTRL_MEM_REG, NB_OF_STIG_READ_BYTES_FLD, 16, 3) > + FIELD(FLASH_COMMAND_CTRL_MEM_REG, MEM_BANK_READ_DATA_FLD, 8, 8) > + FIELD(FLASH_COMMAND_CTRL_MEM_REG, FLASH_COMMAND_CTRL_MEM_RESV3_FLD, 2, 6) > + FIELD(FLASH_COMMAND_CTRL_MEM_REG, MEM_BANK_REQ_IN_PROGRESS_FLD, 1, 1) > + FIELD(FLASH_COMMAND_CTRL_MEM_REG, TRIGGER_MEM_BANK_REQ_FLD, 0, 1) > +REG32(FLASH_CMD_CTRL_REG, 0x90) > + FIELD(FLASH_CMD_CTRL_REG, CMD_OPCODE_FLD, 24, 8) > + FIELD(FLASH_CMD_CTRL_REG, ENB_READ_DATA_FLD, 23, 1) > + FIELD(FLASH_CMD_CTRL_REG, NUM_RD_DATA_BYTES_FLD, 20, 3) > + FIELD(FLASH_CMD_CTRL_REG, ENB_COMD_ADDR_FLD, 19, 1) > + FIELD(FLASH_CMD_CTRL_REG, ENB_MODE_BIT_FLD, 18, 1) > + FIELD(FLASH_CMD_CTRL_REG, NUM_ADDR_BYTES_FLD, 16, 2) > + FIELD(FLASH_CMD_CTRL_REG, ENB_WRITE_DATA_FLD, 15, 1) > + FIELD(FLASH_CMD_CTRL_REG, NUM_WR_DATA_BYTES_FLD, 12, 3) > + FIELD(FLASH_CMD_CTRL_REG, NUM_DUMMY_CYCLES_FLD, 7, 5) > + FIELD(FLASH_CMD_CTRL_REG, FLASH_CMD_CTRL_RESV1_FLD, 3, 4) > + FIELD(FLASH_CMD_CTRL_REG, STIG_MEM_BANK_EN_FLD, 2, 1) > + FIELD(FLASH_CMD_CTRL_REG, CMD_EXEC_STATUS_FLD, 1, 1) > + FIELD(FLASH_CMD_CTRL_REG, CMD_EXEC_FLD, 0, 1) > +REG32(FLASH_CMD_ADDR_REG, 0x94) > +REG32(FLASH_RD_DATA_LOWER_REG, 0xa0) > +REG32(FLASH_RD_DATA_UPPER_REG, 0xa4) > +REG32(FLASH_WR_DATA_LOWER_REG, 0xa8) > +REG32(FLASH_WR_DATA_UPPER_REG, 0xac) > +REG32(POLLING_FLASH_STATUS_REG, 0xb0) > + FIELD(POLLING_FLASH_STATUS_REG, DEVICE_STATUS_RSVD_FLD2, 21, 11) > + FIELD(POLLING_FLASH_STATUS_REG, DEVICE_STATUS_NB_DUMMY, 16, 5) > + FIELD(POLLING_FLASH_STATUS_REG, DEVICE_STATUS_RSVD_FLD1, 9, 7) > + FIELD(POLLING_FLASH_STATUS_REG, DEVICE_STATUS_VALID_FLD, 8, 1) > + FIELD(POLLING_FLASH_STATUS_REG, DEVICE_STATUS_FLD, 0, 8) > +REG32(PHY_CONFIGURATION_REG, 0xb4) > + FIELD(PHY_CONFIGURATION_REG, PHY_CONFIG_RESYNC_FLD, 31, 1) > + FIELD(PHY_CONFIGURATION_REG, PHY_CONFIG_RESET_FLD, 30, 1) > + FIELD(PHY_CONFIGURATION_REG, PHY_CONFIG_RX_DLL_BYPASS_FLD, 29, 1) > + FIELD(PHY_CONFIGURATION_REG, PHY_CONFIG_RESV2_FLD, 23, 6) > + FIELD(PHY_CONFIGURATION_REG, PHY_CONFIG_TX_DLL_DELAY_FLD, 16, 7) > + FIELD(PHY_CONFIGURATION_REG, PHY_CONFIG_RESV1_FLD, 7, 9) > + FIELD(PHY_CONFIGURATION_REG, PHY_CONFIG_RX_DLL_DELAY_FLD, 0, 7) > +REG32(PHY_MASTER_CONTROL_REG, 0xb8) > + FIELD(PHY_MASTER_CONTROL_REG, PHY_MASTER_CONTROL_RESV3_FLD, 25, 7) > + FIELD(PHY_MASTER_CONTROL_REG, PHY_MASTER_LOCK_MODE_FLD, 24, 1) > + FIELD(PHY_MASTER_CONTROL_REG, PHY_MASTER_BYPASS_MODE_FLD, 23, 1) > + FIELD(PHY_MASTER_CONTROL_REG, PHY_MASTER_PHASE_DETECT_SELECTOR_FLD, 20, 3) > + FIELD(PHY_MASTER_CONTROL_REG, PHY_MASTER_CONTROL_RESV2_FLD, 19, 1) > + FIELD(PHY_MASTER_CONTROL_REG, PHY_MASTER_NB_INDICATIONS_FLD, 16, 3) > + FIELD(PHY_MASTER_CONTROL_REG, PHY_MASTER_CONTROL_RESV1_FLD, 7, 9) > + FIELD(PHY_MASTER_CONTROL_REG, PHY_MASTER_INITIAL_DELAY_FLD, 0, 7) > +REG32(DLL_OBSERVABLE_LOWER_REG, 0xbc) > + FIELD(DLL_OBSERVABLE_LOWER_REG, > + DLL_OBSERVABLE_LOWER_DLL_LOCK_INC_FLD, 24, 8) > + FIELD(DLL_OBSERVABLE_LOWER_REG, > + DLL_OBSERVABLE_LOWER_DLL_LOCK_DEC_FLD, 16, 8) > + FIELD(DLL_OBSERVABLE_LOWER_REG, > + DLL_OBSERVABLE_LOWER_LOOPBACK_LOCK_FLD, 15, 1) > + FIELD(DLL_OBSERVABLE_LOWER_REG, > + DLL_OBSERVABLE_LOWER_LOCK_VALUE_FLD, 8, 7) > + FIELD(DLL_OBSERVABLE_LOWER_REG, > + DLL_OBSERVABLE_LOWER_UNLOCK_COUNTER_FLD, 3, 5) > + FIELD(DLL_OBSERVABLE_LOWER_REG, > + DLL_OBSERVABLE_LOWER_LOCK_MODE_FLD, 1, 2) > + FIELD(DLL_OBSERVABLE_LOWER_REG, > + DLL_OBSERVABLE_LOWER_DLL_LOCK_FLD, 0, 1) > +REG32(DLL_OBSERVABLE_UPPER_REG, 0xc0) > + FIELD(DLL_OBSERVABLE_UPPER_REG, > + DLL_OBSERVABLE_UPPER_RESV2_FLD, 23, 9) > + FIELD(DLL_OBSERVABLE_UPPER_REG, > + DLL_OBSERVABLE_UPPER_TX_DECODER_OUTPUT_FLD, 16, 7) > + FIELD(DLL_OBSERVABLE_UPPER_REG, > + DLL_OBSERVABLE_UPPER_RESV1_FLD, 7, 9) > + FIELD(DLL_OBSERVABLE_UPPER_REG, > + DLL_OBSERVABLE__UPPER_RX_DECODER_OUTPUT_FLD, 0, 7) > +REG32(OPCODE_EXT_LOWER_REG, 0xe0) > + FIELD(OPCODE_EXT_LOWER_REG, EXT_READ_OPCODE_FLD, 24, 8) > + FIELD(OPCODE_EXT_LOWER_REG, EXT_WRITE_OPCODE_FLD, 16, 8) > + FIELD(OPCODE_EXT_LOWER_REG, EXT_POLL_OPCODE_FLD, 8, 8) > + FIELD(OPCODE_EXT_LOWER_REG, EXT_STIG_OPCODE_FLD, 0, 8) > +REG32(OPCODE_EXT_UPPER_REG, 0xe4) > + FIELD(OPCODE_EXT_UPPER_REG, WEL_OPCODE_FLD, 24, 8) > + FIELD(OPCODE_EXT_UPPER_REG, EXT_WEL_OPCODE_FLD, 16, 8) > + FIELD(OPCODE_EXT_UPPER_REG, OPCODE_EXT_UPPER_RESV1_FLD, 0, 16) > +REG32(MODULE_ID_REG, 0xfc) > + FIELD(MODULE_ID_REG, FIX_PATCH_FLD, 24, 8) > + FIELD(MODULE_ID_REG, MODULE_ID_FLD, 8, 16) > + FIELD(MODULE_ID_REG, MODULE_ID_RESV_FLD, 2, 6) > + FIELD(MODULE_ID_REG, CONF_FLD, 0, 2) > + > +#define RXFF_SZ 1024 > +#define TXFF_SZ 1024 > + > +#define MAX_RX_DEC_OUT 8 > + > +#define SZ_512MBIT (512 * 1024 * 1024) > +#define SZ_1GBIT (1024 * 1024 * 1024) > +#define SZ_2GBIT (2ULL * SZ_1GBIT) > +#define SZ_4GBIT (4ULL * SZ_1GBIT) > + > +#define IS_IND_DMA_START(op) (op->done_bytes == 0) > +/* > + * Bit field size of R_INDIRECT_WRITE_XFER_CTRL_REG_NUM_IND_OPS_DONE_FLD > + * is 2 bits, which can record max of 3 indac operations. > + */ > +#define IND_OPS_DONE_MAX 3 > + > +typedef enum { > + WREN = 0x6, > +} FlashCMD; > + > +/* Type to avoid cpu endian byte swaps */ > +typedef union { > + uint64_t u64; > + uint8_t u8[8]; > +} OSPIRdData; > + > +static unsigned int ospi_stig_addr_len(XlnxVersalOspi *s) > +{ > + /* Num address bytes is NUM_ADDR_BYTES_FLD + 1 */ > + return ARRAY_FIELD_EX32(s->regs, > + FLASH_CMD_CTRL_REG, NUM_ADDR_BYTES_FLD) + 1; > +} > + > +static unsigned int ospi_stig_wr_data_len(XlnxVersalOspi *s) > +{ > + /* Num write data bytes is NUM_WR_DATA_BYTES_FLD + 1 */ > + return ARRAY_FIELD_EX32(s->regs, > + FLASH_CMD_CTRL_REG, NUM_WR_DATA_BYTES_FLD) + 1; > +} > + > +static unsigned int ospi_stig_rd_data_len(XlnxVersalOspi *s) > +{ > + /* Num read data bytes is NUM_RD_DATA_BYTES_FLD + 1 */ > + return ARRAY_FIELD_EX32(s->regs, > + FLASH_CMD_CTRL_REG, NUM_RD_DATA_BYTES_FLD) + 1; > +} > + > +/* > + * Status bits in R_IRQ_STATUS_REG are set when the event occurs and the > + * interrupt is enabled in the mask register ([1] Section 2.3.17) > + */ > +static void set_irq(XlnxVersalOspi *s, uint32_t set_mask) > +{ > + s->regs[R_IRQ_STATUS_REG] |= s->regs[R_IRQ_MASK_REG] & set_mask; > +} > + > +static void ospi_update_irq_line(XlnxVersalOspi *s) > +{ > + qemu_set_irq(s->irq, !!(s->regs[R_IRQ_STATUS_REG] & > + s->regs[R_IRQ_MASK_REG])); > +} > + > +static uint8_t ospi_get_wr_opcode(XlnxVersalOspi *s) > +{ > + return ARRAY_FIELD_EX32(s->regs, > + DEV_INSTR_WR_CONFIG_REG, WR_OPCODE_FLD); > +} > + > +static uint8_t ospi_get_rd_opcode(XlnxVersalOspi *s) > +{ > + return ARRAY_FIELD_EX32(s->regs, > + DEV_INSTR_RD_CONFIG_REG, RD_OPCODE_NON_XIP_FLD); > +} > + > +static uint32_t ospi_get_num_addr_bytes(XlnxVersalOspi *s) > +{ > + /* Num address bytes is NUM_ADDR_BYTES_FLD + 1 */ > + return ARRAY_FIELD_EX32(s->regs, > + DEV_SIZE_CONFIG_REG, NUM_ADDR_BYTES_FLD) + 1; > +} > + > +static void ospi_stig_membank_req(XlnxVersalOspi *s) > +{ > + int idx = ARRAY_FIELD_EX32(s->regs, > + FLASH_COMMAND_CTRL_MEM_REG, MEM_BANK_ADDR_FLD); > + > + ARRAY_FIELD_DP32(s->regs, FLASH_COMMAND_CTRL_MEM_REG, > + MEM_BANK_READ_DATA_FLD, s->stig_membank[idx]); > +} > + > +static int ospi_stig_membank_rd_bytes(XlnxVersalOspi *s) > +{ > + int rd_data_fld = ARRAY_FIELD_EX32(s->regs, FLASH_COMMAND_CTRL_MEM_REG, > + NB_OF_STIG_READ_BYTES_FLD); > + int sizes[6] = { 16, 32, 64, 128, 256, 512 }; > + return (rd_data_fld < 6) ? sizes[rd_data_fld] : 0; > +} > + > +static uint32_t ospi_get_page_sz(XlnxVersalOspi *s) > +{ > + return ARRAY_FIELD_EX32(s->regs, > + DEV_SIZE_CONFIG_REG, BYTES_PER_DEVICE_PAGE_FLD); > +} > + > +static bool ospi_ind_rd_watermark_enabled(XlnxVersalOspi *s) > +{ > + return s->regs[R_INDIRECT_READ_XFER_WATERMARK_REG]; > +} > + > +static void ind_op_advance(IndOp *op, unsigned int len) > +{ > + op->done_bytes += len; > + assert(op->done_bytes <= op->num_bytes); > + if (op->done_bytes == op->num_bytes) { > + op->completed = true; > + } > +} > + > +static uint32_t ind_op_next_byte(IndOp *op) > +{ > + return op->flash_addr + op->done_bytes; > +} > + > +static uint32_t ind_op_end_byte(IndOp *op) > +{ > + return op->flash_addr + op->num_bytes; > +} > + > +static void ospi_ind_op_next(IndOp *op) > +{ > + op[0] = op[1]; > + op[1].completed = true; > +} > + > +static void ind_op_setup(IndOp *op, uint32_t flash_addr, uint32_t num_bytes) > +{ > + if (num_bytes & 0x3) { > + qemu_log_mask(LOG_GUEST_ERROR, > + "OSPI indirect op num bytes not word aligned\n"); > + } > + op->flash_addr = flash_addr; > + op->num_bytes = num_bytes; > + op->done_bytes = 0; > + op->completed = false; > +} > + > +static bool ospi_ind_op_completed(IndOp *op) > +{ > + return op->completed; > +} > + > +static bool ospi_ind_op_all_completed(XlnxVersalOspi *s) > +{ > + return s->rd_ind_op[0].completed && s->wr_ind_op[0].completed; > +} > + > +static void ospi_ind_op_cancel(IndOp *op) > +{ > + op[0].completed = true; > + op[1].completed = true; > +} > + > +static bool ospi_ind_op_add(IndOp *op, Fifo8 *fifo, > + uint32_t flash_addr, uint32_t num_bytes) > +{ > + /* Check if first indirect op has been completed */ > + if (op->completed) { > + fifo8_reset(fifo); > + ind_op_setup(op, flash_addr, num_bytes); > + return false; > + } > + > + /* Check if second indirect op has been completed */ > + op++; > + if (op->completed) { > + ind_op_setup(op, flash_addr, num_bytes); > + return false; > + } > + return true; > +} > + > +static void ospi_ind_op_queue_up_rd(XlnxVersalOspi *s) > +{ > + uint32_t num_bytes = s->regs[R_INDIRECT_READ_XFER_NUM_BYTES_REG]; > + uint32_t flash_addr = s->regs[R_INDIRECT_READ_XFER_START_REG]; > + bool failed; > + > + failed = ospi_ind_op_add(s->rd_ind_op, &s->rx_sram, flash_addr, num_bytes); > + /* If two already queued set rd reject interrupt */ > + if (failed) { > + set_irq(s, R_IRQ_STATUS_REG_INDIRECT_TRANSFER_REJECT_FLD_MASK); > + } > +} > + > +static void ospi_ind_op_queue_up_wr(XlnxVersalOspi *s) > +{ > + uint32_t num_bytes = s->regs[R_INDIRECT_WRITE_XFER_NUM_BYTES_REG]; > + uint32_t flash_addr = s->regs[R_INDIRECT_WRITE_XFER_START_REG]; > + bool failed; > + > + failed = ospi_ind_op_add(s->wr_ind_op, &s->tx_sram, flash_addr, num_bytes); > + /* If two already queued set rd reject interrupt */ > + if (failed) { > + set_irq(s, R_IRQ_STATUS_REG_INDIRECT_TRANSFER_REJECT_FLD_MASK); > + } > +} > + > +static uint64_t flash_sz(XlnxVersalOspi *s, unsigned int cs) > +{ > + /* Flash sizes in MB */ > + static const uint64_t sizes[4] = { SZ_512MBIT / 8, SZ_1GBIT / 8, > + SZ_2GBIT / 8, SZ_4GBIT / 8 }; > + uint32_t v = s->regs[R_DEV_SIZE_CONFIG_REG]; > + > + v >>= cs * R_DEV_SIZE_CONFIG_REG_MEM_SIZE_ON_CS0_FLD_LENGTH; > + return sizes[FIELD_EX32(v, DEV_SIZE_CONFIG_REG, MEM_SIZE_ON_CS0_FLD)]; > +} > + > +static unsigned int ospi_get_block_sz(XlnxVersalOspi *s) > +{ > + unsigned int block_fld = ARRAY_FIELD_EX32(s->regs, > + DEV_SIZE_CONFIG_REG, > + BYTES_PER_SUBSECTOR_FLD); > + return 1 << block_fld; > +} > + > +static unsigned int flash_blocks(XlnxVersalOspi *s, unsigned int cs) > +{ > + unsigned int b_sz = ospi_get_block_sz(s); > + unsigned int f_sz = flash_sz(s, cs); > + > + return f_sz / b_sz; > +} > + > +static int ospi_ahb_decoder_cs(XlnxVersalOspi *s, hwaddr addr) > +{ > + uint64_t end_addr = 0; > + int cs; > + > + for (cs = 0; cs < s->num_cs; cs++) { > + end_addr += flash_sz(s, cs); > + if (addr < end_addr) { > + break; > + } > + } > + > + if (cs == s->num_cs) { > + /* Address is out of range */ > + qemu_log_mask(LOG_GUEST_ERROR, > + "OSPI flash address does not fit in configuraton\n"); > + return -1; > + } > + return cs; > +} > + > +static void ospi_ahb_decoder_enable_cs(XlnxVersalOspi *s, hwaddr addr) > +{ > + int cs = ospi_ahb_decoder_cs(s, addr); > + > + if (cs >= 0) { > + for (int i = 0; i < s->num_cs; i++) { > + if (cs == i) { > + qemu_set_irq(s->cs_lines[i], 0); > + } else { > + qemu_set_irq(s->cs_lines[i], 1); > + } > + } > + } > +} > + > +static unsigned int single_cs(XlnxVersalOspi *s) > +{ > + unsigned int field = ARRAY_FIELD_EX32(s->regs, > + CONFIG_REG, PERIPH_CS_LINES_FLD); > + int i; > + > + /* > + * 4'bXXX0 -> 4'b1110 > + * 4'bXX0X -> 4'b1101 > + * 4'bX0XX -> 4'b1011 > + * 4'b0XXX -> 4'b0111 > + * 4'b1111 -> 4'b1111 > + */ > + for (i = 0; i < 4; i++) { > + if ((field & (1 << i)) == 0) { > + return (~(1 << i)) & 0xF; > + } > + } > + return 0; > +} > + > +static void ospi_update_cs_lines(XlnxVersalOspi *s) > +{ > + unsigned int all_cs; > + int i; > + > + if (ARRAY_FIELD_EX32(s->regs, CONFIG_REG, PERIPH_SEL_DEC_FLD)) { > + all_cs = ARRAY_FIELD_EX32(s->regs, CONFIG_REG, PERIPH_CS_LINES_FLD); > + } else { > + all_cs = single_cs(s); > + } > + > + for (i = 0; i < s->num_cs; i++) { > + bool cs = (all_cs >> i) & 1; > + > + qemu_set_irq(s->cs_lines[i], cs); > + } > +} > + > +static void ospi_dac_cs(XlnxVersalOspi *s, hwaddr addr) > +{ > + if (ARRAY_FIELD_EX32(s->regs, CONFIG_REG, ENABLE_AHB_DECODER_FLD)) { > + ospi_ahb_decoder_enable_cs(s, addr); > + } else { > + ospi_update_cs_lines(s); > + } > +} > + > +static void ospi_disable_cs(XlnxVersalOspi *s) > +{ > + int i; > + > + for (i = 0; i < s->num_cs; i++) { > + qemu_set_irq(s->cs_lines[i], 1); > + } > +} > + > +static void ospi_flush_txfifo(XlnxVersalOspi *s) > +{ > + while (!fifo8_is_empty(&s->tx_fifo)) { > + uint32_t tx_rx = fifo8_pop(&s->tx_fifo); > + > + tx_rx = ssi_transfer(s->spi, tx_rx); > + fifo8_push(&s->rx_fifo, tx_rx); > + } > +} > + > +static void ospi_tx_fifo_push_address_raw(XlnxVersalOspi *s, > + uint32_t flash_addr, > + unsigned int addr_bytes) > +{ > + /* Push write address */ > + if (addr_bytes == 4) { > + fifo8_push(&s->tx_fifo, flash_addr >> 24); > + } > + if (addr_bytes >= 3) { > + fifo8_push(&s->tx_fifo, flash_addr >> 16); > + } > + if (addr_bytes >= 2) { > + fifo8_push(&s->tx_fifo, flash_addr >> 8); > + } > + fifo8_push(&s->tx_fifo, flash_addr); > +} > + > +static void ospi_tx_fifo_push_address(XlnxVersalOspi *s, uint32_t flash_addr) > +{ > + /* Push write address */ > + int addr_bytes = ospi_get_num_addr_bytes(s); > + > + ospi_tx_fifo_push_address_raw(s, flash_addr, addr_bytes); > +} > + > +static void ospi_tx_fifo_push_stig_addr(XlnxVersalOspi *s) > +{ > + uint32_t flash_addr = s->regs[R_FLASH_CMD_ADDR_REG]; > + unsigned int addr_bytes = ospi_stig_addr_len(s); > + > + ospi_tx_fifo_push_address_raw(s, flash_addr, addr_bytes); > +} > + > +static void ospi_tx_fifo_push_rd_op_addr(XlnxVersalOspi *s, uint32_t flash_addr) > +{ > + uint8_t inst_code = ospi_get_rd_opcode(s); > + > + fifo8_reset(&s->tx_fifo); > + > + /* Push read opcode */ > + fifo8_push(&s->tx_fifo, inst_code); > + > + /* Push read address */ > + ospi_tx_fifo_push_address(s, flash_addr); > +} > + > +static void ospi_tx_fifo_push_stig_wr_data(XlnxVersalOspi *s) > +{ > + uint64_t data = s->regs[R_FLASH_WR_DATA_LOWER_REG]; > + int wr_data_len = ospi_stig_wr_data_len(s); > + int i; > + > + data |= (uint64_t) s->regs[R_FLASH_WR_DATA_UPPER_REG] << 32; > + for (i = 0; i < wr_data_len; i++) { > + int shift = i * 8; > + fifo8_push(&s->tx_fifo, data >> shift); > + } > +} > + > +static void ospi_tx_fifo_push_stig_rd_data(XlnxVersalOspi *s) > +{ > + int rd_data_len; > + int i; > + > + if (ARRAY_FIELD_EX32(s->regs, FLASH_CMD_CTRL_REG, STIG_MEM_BANK_EN_FLD)) { > + rd_data_len = ospi_stig_membank_rd_bytes(s); > + } else { > + rd_data_len = ospi_stig_rd_data_len(s); > + } > + > + /* transmit second part (data) */ > + for (i = 0; i < rd_data_len; ++i) { > + fifo8_push(&s->tx_fifo, 0); > + } > +} > + > +static void ospi_rx_fifo_pop_stig_rd_data(XlnxVersalOspi *s) > +{ > + int size = ospi_stig_rd_data_len(s); > + OSPIRdData res = {}; > + int i; > + > + size = MIN(fifo8_num_used(&s->rx_fifo), size); > + for (i = 0; i < size; i++) { > + res.u8[i] = fifo8_pop(&s->rx_fifo); > + } > + > + s->regs[R_FLASH_RD_DATA_LOWER_REG] = res.u64 & 0xFFFFFFFF; > + s->regs[R_FLASH_RD_DATA_UPPER_REG] = (res.u64 >> 32) & 0xFFFFFFFF; > +} > + > +static void ospi_ind_read(XlnxVersalOspi *s, uint32_t flash_addr, uint32_t len) > +{ > + int i; > + > + /* Create first section of read cmd */ > + ospi_tx_fifo_push_rd_op_addr(s, flash_addr); > + > + /* transmit first part */ > + ospi_update_cs_lines(s); > + ospi_flush_txfifo(s); > + > + fifo8_reset(&s->rx_fifo); > + > + /* transmit second part (data) */ > + for (i = 0; i < len; ++i) { > + fifo8_push(&s->tx_fifo, 0); > + } > + ospi_flush_txfifo(s); > + > + for (i = 0; i < len; ++i) { > + fifo8_push(&s->rx_sram, fifo8_pop(&s->rx_fifo)); > + } > + > + /* done */ > + ospi_disable_cs(s); > +} > + > +static unsigned int ospi_dma_burst_size(XlnxVersalOspi *s) > +{ > + return 1 << ARRAY_FIELD_EX32(s->regs, > + DMA_PERIPH_CONFIG_REG, > + NUM_BURST_REQ_BYTES_FLD); > +} > + > +static unsigned int ospi_dma_single_size(XlnxVersalOspi *s) > +{ > + return 1 << ARRAY_FIELD_EX32(s->regs, > + DMA_PERIPH_CONFIG_REG, > + NUM_SINGLE_REQ_BYTES_FLD); > +} > + > +static void ind_rd_inc_num_done(XlnxVersalOspi *s) > +{ > + unsigned int done = ARRAY_FIELD_EX32(s->regs, > + INDIRECT_READ_XFER_CTRL_REG, > + NUM_IND_OPS_DONE_FLD); > + if (done < IND_OPS_DONE_MAX) { > + done++; > + } > + done &= 0x3; > + ARRAY_FIELD_DP32(s->regs, INDIRECT_READ_XFER_CTRL_REG, > + NUM_IND_OPS_DONE_FLD, done); > +} > + > +static void ospi_ind_rd_completed(XlnxVersalOspi *s) > +{ > + ARRAY_FIELD_DP32(s->regs, INDIRECT_READ_XFER_CTRL_REG, > + IND_OPS_DONE_STATUS_FLD, 1); > + > + ind_rd_inc_num_done(s); > + ospi_ind_op_next(s->rd_ind_op); > + if (ospi_ind_op_all_completed(s)) { > + set_irq(s, R_IRQ_STATUS_REG_INDIRECT_OP_DONE_FLD_MASK); > + } > +} > + > +static uint32_t get_ind_rd_dma_len(XlnxVersalOspi *s, IndOp *op) > +{ > + uint32_t len = 0; > + if (ARRAY_FIELD_EX32(s->regs, CONFIG_REG, ENB_DMA_IF_FLD)) { > + if (fifo8_num_used(&s->rx_sram) < > + ospi_dma_burst_size(s)) { > + len = ospi_dma_single_size(s); > + } else { > + len = ospi_dma_burst_size(s); > + } > + } > + return len; > +} > + > +static void ospi_notify(void *opaque); > + > +static void ospi_dma_read(XlnxVersalOspi *s, bool start_dma) > +{ > + IndOp *op = s->rd_ind_op; > + uint32_t dma_len; > + uint32_t flush_bytes = fifo8_num_used(&s->rx_sram); > + DmaCtrlNotify notify = { .cb = ospi_notify, > + .opaque = (void *)s, > + }; > + > + if (flush_bytes && !s->src_dma_inprog) { > + dma_len = get_ind_rd_dma_len(s, op); > + /* > + * Source dma accesses SRAM at address 0 (at its own addresss space). > + */ > + dma_ctrl_read_with_notify(s->dma_src, 0, dma_len, ¬ify, start_dma); > + > + /* > + * ospi_dma_read is called for every call of ospi_notify > + * in that case, by the time we are here, if flush_bytes is not zero > + * then we have pending dma transactions. > + */ > + flush_bytes = fifo8_num_used(&s->rx_sram); > + if (flush_bytes) { > + s->src_dma_inprog = true; > + } > + } > +} > + > +static void ospi_do_ind_read(XlnxVersalOspi *s) > +{ > + IndOp *op = s->rd_ind_op; > + uint32_t next_b; > + uint32_t end_b; > + uint32_t len; > + bool start_dma = IS_IND_DMA_START(op); > + > + /* Continue to read flash until we run out of space in sram */ > + while (!ospi_ind_op_completed(op) && > + !fifo8_is_full(&s->rx_sram)) { > + /* Read reqested number of bytes, max bytes limited to size of sram */ > + next_b = ind_op_next_byte(op); > + end_b = next_b + fifo8_num_free(&s->rx_sram); > + end_b = MIN(end_b, ind_op_end_byte(op)); > + > + len = end_b - next_b; > + ospi_ind_read(s, next_b, len); > + ind_op_advance(op, len); > + > + if (ospi_ind_rd_watermark_enabled(s)) { > + ARRAY_FIELD_DP32(s->regs, IRQ_STATUS_REG, > + INDIRECT_XFER_LEVEL_BREACH_FLD, 1); > + set_irq(s, > + R_IRQ_STATUS_REG_INDIRECT_XFER_LEVEL_BREACH_FLD_MASK); > + } > + > + if (!s->src_dma_inprog && > + ARRAY_FIELD_EX32(s->regs, CONFIG_REG, ENB_DMA_IF_FLD)) { > + ospi_dma_read(s, start_dma); > + } > + } > + > + /* Set sram full */ > + if (fifo8_num_used(&s->rx_sram) == RXFF_SZ) { > + ARRAY_FIELD_DP32(s->regs, > + INDIRECT_READ_XFER_CTRL_REG, SRAM_FULL_FLD, 1); > + set_irq(s, R_IRQ_STATUS_REG_INDRD_SRAM_FULL_FLD_MASK); > + } > + > + /* Signal completion if done, unless inside recursion via ospi_dma_read */ > + if (!ARRAY_FIELD_EX32(s->regs, CONFIG_REG, ENB_DMA_IF_FLD) || start_dma) { > + if (ospi_ind_op_completed(op)) { > + ospi_ind_rd_completed(s); > + } > + } > +} > + > +static void ospi_notify(void *opaque) > +{ > + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(opaque); > + > + s->src_dma_inprog = false; > + ospi_dma_read(s, false); > + if (!ospi_ind_op_completed(s->rd_ind_op)) { > + ospi_do_ind_read(s); > + } > +} > + > +/* Transmit write enable instruction */ > +static void ospi_transmit_wel(XlnxVersalOspi *s, bool ahb_decoder_cs, > + hwaddr addr) > +{ > + fifo8_reset(&s->tx_fifo); > + fifo8_push(&s->tx_fifo, WREN); > + > + if (ahb_decoder_cs) { > + ospi_ahb_decoder_enable_cs(s, addr); > + } else { > + ospi_update_cs_lines(s); > + } > + > + ospi_flush_txfifo(s); > + ospi_disable_cs(s); > + > + fifo8_reset(&s->rx_fifo); > +} > + > +static void ospi_ind_write(XlnxVersalOspi *s, uint32_t flash_addr, uint32_t len) > +{ > + bool ahb_decoder_cs = false; > + uint8_t inst_code; > + int i; > + > + assert(fifo8_num_used(&s->tx_sram) >= len); > + > + if (!ARRAY_FIELD_EX32(s->regs, DEV_INSTR_WR_CONFIG_REG, WEL_DIS_FLD)) { > + ospi_transmit_wel(s, ahb_decoder_cs, 0); > + } > + > + /* reset fifos */ > + fifo8_reset(&s->tx_fifo); > + fifo8_reset(&s->rx_fifo); > + > + /* Push write opcode */ > + inst_code = ospi_get_wr_opcode(s); > + fifo8_push(&s->tx_fifo, inst_code); > + > + /* Push write address */ > + ospi_tx_fifo_push_address(s, flash_addr); > + > + /* data */ > + for (i = 0; i < len; i++) { > + fifo8_push(&s->tx_fifo, fifo8_pop(&s->tx_sram)); > + } > + > + /* transmit */ > + ospi_update_cs_lines(s); > + ospi_flush_txfifo(s); > + > + /* done */ > + ospi_disable_cs(s); > + fifo8_reset(&s->rx_fifo); > +} > + > +static void ind_wr_inc_num_done(XlnxVersalOspi *s) > +{ > + unsigned int done = ARRAY_FIELD_EX32(s->regs, INDIRECT_WRITE_XFER_CTRL_REG, > + NUM_IND_OPS_DONE_FLD); > + if (done < IND_OPS_DONE_MAX) { > + done++; > + } > + done &= 0x3; > + ARRAY_FIELD_DP32(s->regs, INDIRECT_WRITE_XFER_CTRL_REG, > + NUM_IND_OPS_DONE_FLD, done); > +} > + > +static void ospi_ind_wr_completed(XlnxVersalOspi *s) > +{ > + ARRAY_FIELD_DP32(s->regs, INDIRECT_WRITE_XFER_CTRL_REG, > + IND_OPS_DONE_STATUS_FLD, 1); > + ind_wr_inc_num_done(s); > + ospi_ind_op_next(s->wr_ind_op); > + /* Set indirect op done interrupt if enabled */ > + if (ospi_ind_op_all_completed(s)) { > + set_irq(s, R_IRQ_STATUS_REG_INDIRECT_OP_DONE_FLD_MASK); > + } > +} > + > +static void ospi_do_indirect_write(XlnxVersalOspi *s) > +{ > + uint32_t write_watermark = s->regs[R_INDIRECT_WRITE_XFER_WATERMARK_REG]; > + uint32_t pagesz = ospi_get_page_sz(s); > + uint32_t page_mask = ~(pagesz - 1); > + IndOp *op = s->wr_ind_op; > + uint32_t next_b; > + uint32_t end_b; > + uint32_t len; > + > + /* Write out tx_fifo in maximum page sz chunks */ > + while (!ospi_ind_op_completed(op) && fifo8_num_used(&s->tx_sram) > 0) { > + next_b = ind_op_next_byte(op); > + end_b = next_b + MIN(fifo8_num_used(&s->tx_sram), pagesz); > + > + /* Dont cross page boundery */ > + if ((end_b & page_mask) > next_b) { > + end_b &= page_mask; > + } > + > + len = end_b - next_b; > + len = MIN(len, op->num_bytes - op->done_bytes); > + ospi_ind_write(s, next_b, len); > + ind_op_advance(op, len); > + } > + > + /* > + * Always set indirect transfer level breached interrupt if enabled > + * (write watermark > 0) since the tx_sram always will be emptied > + */ > + if (write_watermark > 0) { > + set_irq(s, R_IRQ_STATUS_REG_INDIRECT_XFER_LEVEL_BREACH_FLD_MASK); > + } > + > + /* Signal completions if done */ > + if (ospi_ind_op_completed(op)) { > + ospi_ind_wr_completed(s); > + } > +} > + > +static void ospi_stig_fill_membank(XlnxVersalOspi *s) > +{ > + int num_rd_bytes = ospi_stig_membank_rd_bytes(s); > + int idx = num_rd_bytes - 8; /* first of last 8 */ > + uint32_t lower = 0; > + uint32_t upper = 0; > + int i; > + > + for (i = 0; i < num_rd_bytes; i++) { > + s->stig_membank[i] = fifo8_pop(&s->rx_fifo); > + } > + > + /* Fill in lower upper regs */ > + for (i = 0; i < 4; i++) { > + lower |= ((uint32_t)s->stig_membank[idx++]) << 8 * i; > + } > + > + for (i = 0; i < 4; i++) { > + upper |= ((uint32_t)s->stig_membank[idx++]) << 8 * i; > + } > + > + s->regs[R_FLASH_RD_DATA_LOWER_REG] = lower; > + s->regs[R_FLASH_RD_DATA_UPPER_REG] = upper; > +} > + > +static void ospi_stig_cmd_exec(XlnxVersalOspi *s) > +{ > + uint8_t inst_code; > + > + /* Reset fifos */ > + fifo8_reset(&s->tx_fifo); > + fifo8_reset(&s->rx_fifo); > + > + /* Push write opcode */ > + inst_code = ARRAY_FIELD_EX32(s->regs, FLASH_CMD_CTRL_REG, CMD_OPCODE_FLD); > + fifo8_push(&s->tx_fifo, inst_code); > + > + /* Push address if enabled */ > + if (ARRAY_FIELD_EX32(s->regs, FLASH_CMD_CTRL_REG, ENB_COMD_ADDR_FLD)) { > + ospi_tx_fifo_push_stig_addr(s); > + } > + > + /* Enable cs */ > + ospi_update_cs_lines(s); > + > + /* Data */ > + if (ARRAY_FIELD_EX32(s->regs, FLASH_CMD_CTRL_REG, ENB_WRITE_DATA_FLD)) { > + ospi_tx_fifo_push_stig_wr_data(s); > + } else if (ARRAY_FIELD_EX32(s->regs, > + FLASH_CMD_CTRL_REG, ENB_READ_DATA_FLD)) { > + /* transmit first part */ > + ospi_flush_txfifo(s); > + fifo8_reset(&s->rx_fifo); > + ospi_tx_fifo_push_stig_rd_data(s); > + } > + > + /* Transmit */ > + ospi_flush_txfifo(s); > + ospi_disable_cs(s); > + > + if (ARRAY_FIELD_EX32(s->regs, FLASH_CMD_CTRL_REG, ENB_READ_DATA_FLD)) { > + if (ARRAY_FIELD_EX32(s->regs, > + FLASH_CMD_CTRL_REG, STIG_MEM_BANK_EN_FLD)) { > + ospi_stig_fill_membank(s); > + } else { > + ospi_rx_fifo_pop_stig_rd_data(s); > + } > + } > +} > + > +static uint32_t ospi_block_address(XlnxVersalOspi *s, unsigned int block) > +{ > + unsigned int block_sz = ospi_get_block_sz(s); > + unsigned int cs = 0; > + uint32_t addr = 0; > + > + while (cs < s->num_cs && block >= flash_blocks(s, cs)) { > + block -= flash_blocks(s, 0); > + addr += flash_sz(s, cs); > + } > + addr += block * block_sz; > + return addr; > +} > + > +static uint32_t ospi_get_wr_prot_addr_low(XlnxVersalOspi *s) > +{ > + unsigned int block = s->regs[R_LOWER_WR_PROT_REG]; > + > + return ospi_block_address(s, block); > +} > + > +static uint32_t ospi_get_wr_prot_addr_upper(XlnxVersalOspi *s) > +{ > + unsigned int block = s->regs[R_UPPER_WR_PROT_REG]; > + > + /* Get address of first block out of defined range */ > + return ospi_block_address(s, block + 1); > +} > + > +static bool ospi_is_write_protected(XlnxVersalOspi *s, hwaddr addr) > +{ > + uint32_t wr_prot_addr_upper = ospi_get_wr_prot_addr_upper(s); > + uint32_t wr_prot_addr_low = ospi_get_wr_prot_addr_low(s); > + bool in_range = false; > + > + if (addr >= wr_prot_addr_low && addr < wr_prot_addr_upper) { > + in_range = true; > + } > + > + if (ARRAY_FIELD_EX32(s->regs, WR_PROT_CTRL_REG, INV_FLD)) { > + in_range = !in_range; > + } > + return in_range; > +} > + > +static uint64_t ospi_rx_sram_read(XlnxVersalOspi *s, unsigned int size) > +{ > + OSPIRdData ret = {}; > + int i; > + > + if (size < 4 && fifo8_num_used(&s->rx_sram) >= 4) { > + qemu_log_mask(LOG_GUEST_ERROR, > + "OSPI only last read of internal " > + "sram is allowed to be < 32 bits\n"); > + } > + > + size = MIN(fifo8_num_used(&s->rx_sram), size); > + for (i = 0; i < size; i++) { > + ret.u8[i] = fifo8_pop(&s->rx_sram); > + } > + return ret.u64; > +} > + > +static void ospi_tx_sram_write(XlnxVersalOspi *s, uint64_t value, > + unsigned int size) > +{ > + int i; > + for (i = 0; i < size; i++) { > + fifo8_push(&s->tx_sram, value >> 8 * i); > + } > +} > + > +static uint64_t ospi_do_dac_read(void *opaque, hwaddr addr, unsigned int size) > +{ > + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(opaque); > + OSPIRdData ret = {}; > + int i; > + > + /* Create first section of read cmd */ > + ospi_tx_fifo_push_rd_op_addr(s, (uint32_t) addr); > + > + /* Enable cs and transmit first part */ > + ospi_dac_cs(s, addr); > + ospi_flush_txfifo(s); > + > + fifo8_reset(&s->rx_fifo); > + > + /* transmit second part (data) */ > + for (i = 0; i < size; ++i) { > + fifo8_push(&s->tx_fifo, 0); > + } > + ospi_flush_txfifo(s); > + > + /* fill in result */ > + size = MIN(fifo8_num_used(&s->rx_fifo), size); > + > + for (i = 0; i < size; i++) { > + ret.u8[i] = fifo8_pop(&s->rx_fifo); > + } > + > + /* done */ > + ospi_disable_cs(s); > + > + return ret.u64; > +} > + > +static void ospi_do_dac_write(void *opaque, > + hwaddr addr, > + uint64_t value, > + unsigned int size) > +{ > + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(opaque); > + bool ahb_decoder_cs = ARRAY_FIELD_EX32(s->regs, CONFIG_REG, > + ENABLE_AHB_DECODER_FLD); > + uint8_t inst_code; > + unsigned int i; > + > + if (!ARRAY_FIELD_EX32(s->regs, DEV_INSTR_WR_CONFIG_REG, WEL_DIS_FLD)) { > + ospi_transmit_wel(s, ahb_decoder_cs, addr); > + } > + > + /* reset fifos */ > + fifo8_reset(&s->tx_fifo); > + fifo8_reset(&s->rx_fifo); > + > + /* Push write opcode */ > + inst_code = ospi_get_wr_opcode(s); > + fifo8_push(&s->tx_fifo, inst_code); > + > + /* Push write address */ > + ospi_tx_fifo_push_address(s, addr); > + > + /* data */ > + for (i = 0; i < size; i++) { > + fifo8_push(&s->tx_fifo, value >> 8 * i); > + } > + > + /* Enable cs and transmit */ > + ospi_dac_cs(s, addr); > + ospi_flush_txfifo(s); > + ospi_disable_cs(s); > + > + fifo8_reset(&s->rx_fifo); > +} > + > +static void flash_cmd_ctrl_mem_reg_post_write(RegisterInfo *reg, > + uint64_t val) > +{ > + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(reg->opaque); > + if (ARRAY_FIELD_EX32(s->regs, CONFIG_REG, ENB_SPI_FLD)) { > + if (ARRAY_FIELD_EX32(s->regs, > + FLASH_COMMAND_CTRL_MEM_REG, > + TRIGGER_MEM_BANK_REQ_FLD)) { > + ospi_stig_membank_req(s); > + ARRAY_FIELD_DP32(s->regs, FLASH_COMMAND_CTRL_MEM_REG, > + TRIGGER_MEM_BANK_REQ_FLD, 0); > + } > + } > +} > + > +static void flash_cmd_ctrl_reg_post_write(RegisterInfo *reg, uint64_t val) > +{ > + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(reg->opaque); > + > + if (ARRAY_FIELD_EX32(s->regs, CONFIG_REG, ENB_SPI_FLD) && > + ARRAY_FIELD_EX32(s->regs, FLASH_CMD_CTRL_REG, CMD_EXEC_FLD)) { > + ospi_stig_cmd_exec(s); > + set_irq(s, R_IRQ_STATUS_REG_STIG_REQ_INT_FLD_MASK); > + ARRAY_FIELD_DP32(s->regs, FLASH_CMD_CTRL_REG, CMD_EXEC_FLD, 0); > + } > +} > + > +static uint64_t ind_wr_dec_num_done(XlnxVersalOspi *s, uint64_t val) > +{ > + unsigned int done = ARRAY_FIELD_EX32(s->regs, INDIRECT_WRITE_XFER_CTRL_REG, > + NUM_IND_OPS_DONE_FLD); > + done--; > + done &= 0x3; > + val = FIELD_DP32(val, INDIRECT_WRITE_XFER_CTRL_REG, > + NUM_IND_OPS_DONE_FLD, done); > + return val; > +} > + > +static bool ind_wr_clearing_op_done(XlnxVersalOspi *s, uint64_t new_val) > +{ > + bool set_in_reg = ARRAY_FIELD_EX32(s->regs, INDIRECT_WRITE_XFER_CTRL_REG, > + IND_OPS_DONE_STATUS_FLD); > + bool set_in_new_val = FIELD_EX32(new_val, INDIRECT_WRITE_XFER_CTRL_REG, > + IND_OPS_DONE_STATUS_FLD); > + /* return true if clearing bit */ > + return set_in_reg && !set_in_new_val; > +} > + > +static uint64_t ind_wr_xfer_ctrl_reg_pre_write(RegisterInfo *reg, > + uint64_t val) > +{ > + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(reg->opaque); > + > + if (ind_wr_clearing_op_done(s, val)) { > + val = ind_wr_dec_num_done(s, val); > + } > + return val; > +} > + > +static void ind_wr_xfer_ctrl_reg_post_write(RegisterInfo *reg, uint64_t val) > +{ > + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(reg->opaque); > + > + if (s->ind_write_disabled) { > + return; > + } > + > + if (ARRAY_FIELD_EX32(s->regs, INDIRECT_WRITE_XFER_CTRL_REG, START_FLD)) { > + ospi_ind_op_queue_up_wr(s); > + ospi_do_indirect_write(s); > + ARRAY_FIELD_DP32(s->regs, INDIRECT_WRITE_XFER_CTRL_REG, START_FLD, 0); > + } > + > + if (ARRAY_FIELD_EX32(s->regs, INDIRECT_WRITE_XFER_CTRL_REG, CANCEL_FLD)) { > + ospi_ind_op_cancel(s->wr_ind_op); > + fifo8_reset(&s->tx_sram); > + ARRAY_FIELD_DP32(s->regs, INDIRECT_WRITE_XFER_CTRL_REG, CANCEL_FLD, 0); > + } > +} > + > +static uint64_t ind_wr_xfer_ctrl_reg_post_read(RegisterInfo *reg, > + uint64_t val) > +{ > + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(reg->opaque); > + IndOp *op = s->wr_ind_op; > + > + /* Check if ind ops is ongoing */ > + if (!ospi_ind_op_completed(&op[0])) { > + /* Check if two ind ops are queued */ > + if (!ospi_ind_op_completed(&op[1])) { > + val = FIELD_DP32(val, INDIRECT_WRITE_XFER_CTRL_REG, > + WR_QUEUED_FLD, 1); > + } > + val = FIELD_DP32(val, INDIRECT_WRITE_XFER_CTRL_REG, WR_STATUS_FLD, 1); > + } > + return val; > +} > + > +static uint64_t ind_rd_dec_num_done(XlnxVersalOspi *s, uint64_t val) > +{ > + unsigned int done = ARRAY_FIELD_EX32(s->regs, INDIRECT_READ_XFER_CTRL_REG, > + NUM_IND_OPS_DONE_FLD); > + done--; > + done &= 0x3; > + val = FIELD_DP32(val, INDIRECT_READ_XFER_CTRL_REG, > + NUM_IND_OPS_DONE_FLD, done); > + return val; > +} > + > +static uint64_t ind_rd_xfer_ctrl_reg_pre_write(RegisterInfo *reg, > + uint64_t val) > +{ > + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(reg->opaque); > + > + if (FIELD_EX32(val, INDIRECT_READ_XFER_CTRL_REG, > + IND_OPS_DONE_STATUS_FLD)) { > + val = ind_rd_dec_num_done(s, val); > + val &= ~R_INDIRECT_READ_XFER_CTRL_REG_IND_OPS_DONE_STATUS_FLD_MASK; > + } > + return val; > +} > + > +static void ind_rd_xfer_ctrl_reg_post_write(RegisterInfo *reg, uint64_t val) > +{ > + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(reg->opaque); > + > + if (ARRAY_FIELD_EX32(s->regs, INDIRECT_READ_XFER_CTRL_REG, START_FLD)) { > + ospi_ind_op_queue_up_rd(s); > + ospi_do_ind_read(s); > + ARRAY_FIELD_DP32(s->regs, INDIRECT_READ_XFER_CTRL_REG, START_FLD, 0); > + } > + > + if (ARRAY_FIELD_EX32(s->regs, INDIRECT_READ_XFER_CTRL_REG, CANCEL_FLD)) { > + ospi_ind_op_cancel(s->rd_ind_op); > + fifo8_reset(&s->rx_sram); > + ARRAY_FIELD_DP32(s->regs, INDIRECT_READ_XFER_CTRL_REG, CANCEL_FLD, 0); > + } > +} > + > +static uint64_t ind_rd_xfer_ctrl_reg_post_read(RegisterInfo *reg, > + uint64_t val) > +{ > + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(reg->opaque); > + IndOp *op = s->rd_ind_op; > + > + /* Check if ind ops is ongoing */ > + if (!ospi_ind_op_completed(&op[0])) { > + /* Check if two ind ops are queued */ > + if (!ospi_ind_op_completed(&op[1])) { > + val = FIELD_DP32(val, INDIRECT_READ_XFER_CTRL_REG, > + RD_QUEUED_FLD, 1); > + } > + val = FIELD_DP32(val, INDIRECT_READ_XFER_CTRL_REG, RD_STATUS_FLD, 1); > + } > + return val; > +} > + > +static uint64_t sram_fill_reg_post_read(RegisterInfo *reg, uint64_t val) > +{ > + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(reg->opaque); > + val = ((fifo8_num_used(&s->tx_sram) & 0xFFFF) << 16) | > + (fifo8_num_used(&s->rx_sram) & 0xFFFF); > + return val; > +} > + > +static uint64_t dll_obs_upper_reg_post_read(RegisterInfo *reg, uint64_t val) > +{ > + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(reg->opaque); > + uint32_t rx_dec_out; > + > + rx_dec_out = FIELD_EX32(val, DLL_OBSERVABLE_UPPER_REG, > + DLL_OBSERVABLE__UPPER_RX_DECODER_OUTPUT_FLD); > + > + if (rx_dec_out < MAX_RX_DEC_OUT) { > + ARRAY_FIELD_DP32(s->regs, DLL_OBSERVABLE_UPPER_REG, > + DLL_OBSERVABLE__UPPER_RX_DECODER_OUTPUT_FLD, > + rx_dec_out + 1); > + } > + > + return val; > +} > + > + > +static void xlnx_versal_ospi_reset(DeviceState *dev) > +{ > + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(dev); > + unsigned int i; > + > + for (i = 0; i < ARRAY_SIZE(s->regs_info); ++i) { > + register_reset(&s->regs_info[i]); > + } > + > + fifo8_reset(&s->rx_fifo); > + fifo8_reset(&s->tx_fifo); > + fifo8_reset(&s->rx_sram); > + fifo8_reset(&s->tx_sram); > + > + s->rd_ind_op[0].completed = true; > + s->rd_ind_op[1].completed = true; > + s->wr_ind_op[0].completed = true; > + s->wr_ind_op[1].completed = true; > + ARRAY_FIELD_DP32(s->regs, DLL_OBSERVABLE_LOWER_REG, > + DLL_OBSERVABLE_LOWER_DLL_LOCK_FLD, 1); > + ARRAY_FIELD_DP32(s->regs, DLL_OBSERVABLE_LOWER_REG, > + DLL_OBSERVABLE_LOWER_LOOPBACK_LOCK_FLD, 1); > +} > + > +static RegisterAccessInfo ospi_regs_info[] = { > + { .name = "CONFIG_REG", > + .addr = A_CONFIG_REG, > + .reset = 0x80780081, > + .ro = 0x9c000000, > + },{ .name = "DEV_INSTR_RD_CONFIG_REG", > + .addr = A_DEV_INSTR_RD_CONFIG_REG, > + .reset = 0x3, > + .ro = 0xe0ecc800, > + },{ .name = "DEV_INSTR_WR_CONFIG_REG", > + .addr = A_DEV_INSTR_WR_CONFIG_REG, > + .reset = 0x2, > + .ro = 0xe0fcce00, > + },{ .name = "DEV_DELAY_REG", > + .addr = A_DEV_DELAY_REG, > + },{ .name = "RD_DATA_CAPTURE_REG", > + .addr = A_RD_DATA_CAPTURE_REG, > + .reset = 0x1, > + .ro = 0xfff0fec0, > + },{ .name = "DEV_SIZE_CONFIG_REG", > + .addr = A_DEV_SIZE_CONFIG_REG, > + .reset = 0x101002, > + .ro = 0xe0000000, > + },{ .name = "SRAM_PARTITION_CFG_REG", > + .addr = A_SRAM_PARTITION_CFG_REG, > + .reset = 0x80, > + .ro = 0xffffff00, > + },{ .name = "IND_AHB_ADDR_TRIGGER_REG", > + .addr = A_IND_AHB_ADDR_TRIGGER_REG, > + },{ .name = "DMA_PERIPH_CONFIG_REG", > + .addr = A_DMA_PERIPH_CONFIG_REG, > + .ro = 0xfffff0f0, > + },{ .name = "REMAP_ADDR_REG", > + .addr = A_REMAP_ADDR_REG, > + },{ .name = "MODE_BIT_CONFIG_REG", > + .addr = A_MODE_BIT_CONFIG_REG, > + .reset = 0x200, > + .ro = 0xffff7800, > + },{ .name = "SRAM_FILL_REG", > + .addr = A_SRAM_FILL_REG, > + .ro = 0xffffffff, > + .post_read = sram_fill_reg_post_read, > + },{ .name = "TX_THRESH_REG", > + .addr = A_TX_THRESH_REG, > + .reset = 0x1, > + .ro = 0xffffffe0, > + },{ .name = "RX_THRESH_REG", > + .addr = A_RX_THRESH_REG, > + .reset = 0x1, > + .ro = 0xffffffe0, > + },{ .name = "WRITE_COMPLETION_CTRL_REG", > + .addr = A_WRITE_COMPLETION_CTRL_REG, > + .reset = 0x10005, > + .ro = 0x1800, > + },{ .name = "NO_OF_POLLS_BEF_EXP_REG", > + .addr = A_NO_OF_POLLS_BEF_EXP_REG, > + .reset = 0xffffffff, > + },{ .name = "IRQ_STATUS_REG", > + .addr = A_IRQ_STATUS_REG, > + .ro = 0xfff08000, > + .w1c = 0xf7fff, > + },{ .name = "IRQ_MASK_REG", > + .addr = A_IRQ_MASK_REG, > + .ro = 0xfff08000, > + },{ .name = "LOWER_WR_PROT_REG", > + .addr = A_LOWER_WR_PROT_REG, > + },{ .name = "UPPER_WR_PROT_REG", > + .addr = A_UPPER_WR_PROT_REG, > + },{ .name = "WR_PROT_CTRL_REG", > + .addr = A_WR_PROT_CTRL_REG, > + .ro = 0xfffffffc, > + },{ .name = "INDIRECT_READ_XFER_CTRL_REG", > + .addr = A_INDIRECT_READ_XFER_CTRL_REG, > + .ro = 0xffffffd4, > + .w1c = 0x08, > + .pre_write = ind_rd_xfer_ctrl_reg_pre_write, > + .post_write = ind_rd_xfer_ctrl_reg_post_write, > + .post_read = ind_rd_xfer_ctrl_reg_post_read, > + },{ .name = "INDIRECT_READ_XFER_WATERMARK_REG", > + .addr = A_INDIRECT_READ_XFER_WATERMARK_REG, > + },{ .name = "INDIRECT_READ_XFER_START_REG", > + .addr = A_INDIRECT_READ_XFER_START_REG, > + },{ .name = "INDIRECT_READ_XFER_NUM_BYTES_REG", > + .addr = A_INDIRECT_READ_XFER_NUM_BYTES_REG, > + },{ .name = "INDIRECT_WRITE_XFER_CTRL_REG", > + .addr = A_INDIRECT_WRITE_XFER_CTRL_REG, > + .ro = 0xffffffdc, > + .w1c = 0x20, > + .pre_write = ind_wr_xfer_ctrl_reg_pre_write, > + .post_write = ind_wr_xfer_ctrl_reg_post_write, > + .post_read = ind_wr_xfer_ctrl_reg_post_read, > + },{ .name = "INDIRECT_WRITE_XFER_WATERMARK_REG", > + .addr = A_INDIRECT_WRITE_XFER_WATERMARK_REG, > + .reset = 0xffffffff, > + },{ .name = "INDIRECT_WRITE_XFER_START_REG", > + .addr = A_INDIRECT_WRITE_XFER_START_REG, > + },{ .name = "INDIRECT_WRITE_XFER_NUM_BYTES_REG", > + .addr = A_INDIRECT_WRITE_XFER_NUM_BYTES_REG, > + },{ .name = "INDIRECT_TRIGGER_ADDR_RANGE_REG", > + .addr = A_INDIRECT_TRIGGER_ADDR_RANGE_REG, > + .reset = 0x4, > + .ro = 0xfffffff0, > + },{ .name = "FLASH_COMMAND_CTRL_MEM_REG", > + .addr = A_FLASH_COMMAND_CTRL_MEM_REG, > + .ro = 0xe008fffe, > + .post_write = flash_cmd_ctrl_mem_reg_post_write, > + },{ .name = "FLASH_CMD_CTRL_REG", > + .addr = A_FLASH_CMD_CTRL_REG, > + .ro = 0x7a, > + .post_write = flash_cmd_ctrl_reg_post_write, > + },{ .name = "FLASH_CMD_ADDR_REG", > + .addr = A_FLASH_CMD_ADDR_REG, > + },{ .name = "FLASH_RD_DATA_LOWER_REG", > + .addr = A_FLASH_RD_DATA_LOWER_REG, > + .ro = 0xffffffff, > + },{ .name = "FLASH_RD_DATA_UPPER_REG", > + .addr = A_FLASH_RD_DATA_UPPER_REG, > + .ro = 0xffffffff, > + },{ .name = "FLASH_WR_DATA_LOWER_REG", > + .addr = A_FLASH_WR_DATA_LOWER_REG, > + },{ .name = "FLASH_WR_DATA_UPPER_REG", > + .addr = A_FLASH_WR_DATA_UPPER_REG, > + },{ .name = "POLLING_FLASH_STATUS_REG", > + .addr = A_POLLING_FLASH_STATUS_REG, > + .ro = 0xfff0ffff, > + },{ .name = "PHY_CONFIGURATION_REG", > + .addr = A_PHY_CONFIGURATION_REG, > + .reset = 0x40000000, > + .ro = 0x1f80ff80, > + },{ .name = "PHY_MASTER_CONTROL_REG", > + .addr = A_PHY_MASTER_CONTROL_REG, > + .reset = 0x800000, > + .ro = 0xfe08ff80, > + },{ .name = "DLL_OBSERVABLE_LOWER_REG", > + .addr = A_DLL_OBSERVABLE_LOWER_REG, > + .ro = 0xffffffff, > + },{ .name = "DLL_OBSERVABLE_UPPER_REG", > + .addr = A_DLL_OBSERVABLE_UPPER_REG, > + .ro = 0xffffffff, > + .post_read = dll_obs_upper_reg_post_read, > + },{ .name = "OPCODE_EXT_LOWER_REG", > + .addr = A_OPCODE_EXT_LOWER_REG, > + .reset = 0x13edfa00, > + },{ .name = "OPCODE_EXT_UPPER_REG", > + .addr = A_OPCODE_EXT_UPPER_REG, > + .reset = 0x6f90000, > + .ro = 0xffff, > + },{ .name = "MODULE_ID_REG", > + .addr = A_MODULE_ID_REG, > + .reset = 0x300, > + .ro = 0xffffffff, > + } > +}; > + > +/* Return dev-obj from reg-region created by register_init_block32 */ > +static XlnxVersalOspi *xilinx_ospi_of_mr(void *mr_accessor) > +{ > + RegisterInfoArray *reg_array = mr_accessor; > + Object *dev; > + > + assert(reg_array != NULL); > + > + dev = reg_array->mem.owner; > + assert(dev); > + > + return XILINX_VERSAL_OSPI(dev); > +} > + > +static void ospi_write(void *opaque, hwaddr addr, uint64_t value, > + unsigned int size) > +{ > + XlnxVersalOspi *s = xilinx_ospi_of_mr(opaque); > + > + register_write_memory(opaque, addr, value, size); > + ospi_update_irq_line(s); > +} > + > +static const MemoryRegionOps ospi_ops = { > + .read = register_read_memory, > + .write = ospi_write, > + .endianness = DEVICE_LITTLE_ENDIAN, > + .valid = { > + .min_access_size = 4, > + .max_access_size = 4, > + }, > +}; > + > +static uint64_t ospi_indac_read(void *opaque, unsigned int size) > +{ > + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(opaque); > + uint64_t ret = ospi_rx_sram_read(s, size); > + > + if (!ARRAY_FIELD_EX32(s->regs, CONFIG_REG, ENB_DMA_IF_FLD) && > + !ospi_ind_op_completed(s->rd_ind_op)) { > + ospi_do_ind_read(s); > + } > + return ret; > +} > + > +static void ospi_indac_write(void *opaque, uint64_t value, unsigned int size) > +{ > + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(opaque); > + > + if (s->ind_write_disabled) { > + g_assert_not_reached(); > + } > + > + if (!ospi_ind_op_completed(s->wr_ind_op)) { > + ospi_tx_sram_write(s, value, size); > + ospi_do_indirect_write(s); > + } else { > + qemu_log_mask(LOG_GUEST_ERROR, > + "OSPI wr into indac area while no ongoing indac wr\n"); > + } > +} > + > +static bool is_inside_indac_range(XlnxVersalOspi *s, hwaddr addr) > +{ > + uint32_t range_start = s->regs[R_IND_AHB_ADDR_TRIGGER_REG]; > + uint32_t range_end = range_start + > + (1 << ARRAY_FIELD_EX32(s->regs, > + INDIRECT_TRIGGER_ADDR_RANGE_REG, > + IND_RANGE_WIDTH_FLD)); > + > + if (ARRAY_FIELD_EX32(s->regs, CONFIG_REG, ENB_DMA_IF_FLD)) { > + addr += s->regs[R_IND_AHB_ADDR_TRIGGER_REG]; > + } else { > + addr += s->regs[R_IND_AHB_ADDR_TRIGGER_REG] & 0xF0000000; > + } > + > + return addr >= range_start && addr < range_end; > +} > + > +static bool ospi_is_indac_active(XlnxVersalOspi *s) > +{ > + /* > + * When dac and indac cannot be active at the same time, > + * return true when dac is disabled. > + */ > + return s->dac_with_indac || !s->dac_enable; > +} > + > +static uint64_t ospi_dac_read(void *opaque, hwaddr addr, unsigned int size) > +{ > + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(opaque); > + > + if (ARRAY_FIELD_EX32(s->regs, CONFIG_REG, ENB_SPI_FLD)) { > + if (ospi_is_indac_active(s) && > + is_inside_indac_range(s, addr)) { > + return ospi_indac_read(s, size); > + } > + if (ARRAY_FIELD_EX32(s->regs, CONFIG_REG, ENB_DIR_ACC_CTLR_FLD) > + && s->dac_enable) { > + if (ARRAY_FIELD_EX32(s->regs, > + CONFIG_REG, ENB_AHB_ADDR_REMAP_FLD)) { > + addr += s->regs[R_REMAP_ADDR_REG]; > + } > + return ospi_do_dac_read(opaque, addr, size); > + } else { > + qemu_log_mask(LOG_GUEST_ERROR, "OSPI AHB rd while DAC disabled\n"); > + } > + } else { > + qemu_log_mask(LOG_GUEST_ERROR, "OSPI AHB rd while OSPI disabled\n"); > + } > + > + return 0; > +} > + > +static void ospi_dac_write(void *opaque, hwaddr addr, uint64_t value, > + unsigned int size) > +{ > + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(opaque); > + > + if (ARRAY_FIELD_EX32(s->regs, CONFIG_REG, ENB_SPI_FLD)) { > + if (ospi_is_indac_active(s) && > + !s->ind_write_disabled && > + is_inside_indac_range(s, addr)) { > + return ospi_indac_write(s, value, size); > + } > + if (ARRAY_FIELD_EX32(s->regs, CONFIG_REG, ENB_DIR_ACC_CTLR_FLD) && > + s->dac_enable) { > + if (ARRAY_FIELD_EX32(s->regs, > + CONFIG_REG, ENB_AHB_ADDR_REMAP_FLD)) { > + addr += s->regs[R_REMAP_ADDR_REG]; > + } > + /* Check if addr is write protected */ > + if (ARRAY_FIELD_EX32(s->regs, WR_PROT_CTRL_REG, ENB_FLD) && > + ospi_is_write_protected(s, addr)) { > + set_irq(s, R_IRQ_STATUS_REG_PROT_WR_ATTEMPT_FLD_MASK); > + ospi_update_irq_line(s); > + qemu_log_mask(LOG_GUEST_ERROR, > + "OSPI writing into write protected area\n"); > + return; > + } > + ospi_do_dac_write(opaque, addr, value, size); > + } else { > + qemu_log_mask(LOG_GUEST_ERROR, "OSPI AHB wr while DAC disabled\n"); > + } > + } else { > + qemu_log_mask(LOG_GUEST_ERROR, "OSPI AHB wr while OSPI disabled\n"); > + } > +} > + > +static const MemoryRegionOps ospi_dac_ops = { > + .read = ospi_dac_read, > + .write = ospi_dac_write, > + .endianness = DEVICE_LITTLE_ENDIAN, > + .valid = { > + .min_access_size = 4, > + .max_access_size = 4, > + }, > +}; > + > +static void ospi_update_dac_status(void *opaque, int n, int level) > +{ > + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(opaque); > + > + s->dac_enable = level; > +} > + > +static void xlnx_versal_ospi_realize(DeviceState *dev, Error **errp) > +{ > + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(dev); > + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); > + > + s->num_cs = 4; > + s->spi = ssi_create_bus(dev, "spi0"); > + s->cs_lines = g_new0(qemu_irq, s->num_cs); > + for (int i = 0; i < s->num_cs; ++i) { > + sysbus_init_irq(sbd, &s->cs_lines[i]); > + } > + > + fifo8_create(&s->rx_fifo, RXFF_SZ); > + fifo8_create(&s->tx_fifo, TXFF_SZ); > + fifo8_create(&s->rx_sram, RXFF_SZ); > + fifo8_create(&s->tx_sram, TXFF_SZ); > +} > + > +static void xlnx_versal_ospi_init(Object *obj) > +{ > + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(obj); > + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); > + DeviceState *dev = DEVICE(obj); > + RegisterInfoArray *reg_array; > + > + memory_region_init(&s->iomem, obj, TYPE_XILINX_VERSAL_OSPI, > + XILINX_VERSAL_OSPI_R_MAX * 4); > + reg_array = > + register_init_block32(DEVICE(obj), ospi_regs_info, > + ARRAY_SIZE(ospi_regs_info), > + s->regs_info, s->regs, > + &ospi_ops, > + XILINX_VERSAL_OSPI_ERR_DEBUG, > + XILINX_VERSAL_OSPI_R_MAX * 4); > + memory_region_add_subregion(&s->iomem, 0x0, ®_array->mem); > + sysbus_init_mmio(sbd, &s->iomem); > + > + memory_region_init_io(&s->iomem_dac, obj, &ospi_dac_ops, s, > + TYPE_XILINX_VERSAL_OSPI "-dac", 0x20000000); > + sysbus_init_mmio(sbd, &s->iomem_dac); > + > + sysbus_init_irq(sbd, &s->irq); > + > + object_property_add_link(obj, "dma-src", TYPE_DMA_CTRL, > + (Object **)&s->dma_src, > + object_property_allow_set_link, > + OBJ_PROP_LINK_STRONG); > + > + qdev_init_gpio_in(dev, ospi_update_dac_status, 1); > +} > + > +static const VMStateDescription vmstate_ind_op = { > + .name = "OSPIIndOp", > + .version_id = 1, > + .minimum_version_id = 1, > + .fields = (VMStateField[]) { > + VMSTATE_UINT32(flash_addr, IndOp), > + VMSTATE_UINT32(num_bytes, IndOp), > + VMSTATE_UINT32(done_bytes, IndOp), > + VMSTATE_BOOL(completed, IndOp), > + VMSTATE_END_OF_LIST() > + } > +}; > + > +static const VMStateDescription vmstate_xlnx_versal_ospi = { > + .name = TYPE_XILINX_VERSAL_OSPI, > + .version_id = 1, > + .minimum_version_id = 1, > + .minimum_version_id_old = 1, > + .fields = (VMStateField[]) { > + VMSTATE_FIFO8(rx_fifo, XlnxVersalOspi), > + VMSTATE_FIFO8(tx_fifo, XlnxVersalOspi), > + VMSTATE_FIFO8(rx_sram, XlnxVersalOspi), > + VMSTATE_FIFO8(tx_sram, XlnxVersalOspi), > + VMSTATE_BOOL(ind_write_disabled, XlnxVersalOspi), > + VMSTATE_BOOL(dac_with_indac, XlnxVersalOspi), > + VMSTATE_BOOL(dac_enable, XlnxVersalOspi), > + VMSTATE_BOOL(src_dma_inprog, XlnxVersalOspi), > + VMSTATE_STRUCT_ARRAY(rd_ind_op, XlnxVersalOspi, 2, 1, > + vmstate_ind_op, IndOp), > + VMSTATE_STRUCT_ARRAY(wr_ind_op, XlnxVersalOspi, 2, 1, > + vmstate_ind_op, IndOp), > + VMSTATE_UINT32_ARRAY(regs, XlnxVersalOspi, XILINX_VERSAL_OSPI_R_MAX), > + VMSTATE_UINT8_ARRAY(stig_membank, XlnxVersalOspi, 512), > + VMSTATE_END_OF_LIST(), > + } > +}; > + > +static Property xlnx_versal_ospi_properties[] = { > + DEFINE_PROP_BOOL("dac-with-indac", XlnxVersalOspi, dac_with_indac, false), > + DEFINE_PROP_BOOL("indac-write-disabled", XlnxVersalOspi, > + ind_write_disabled, true), > + DEFINE_PROP_UINT8("num-cs", XlnxVersalOspi, num_cs, 4), > + DEFINE_PROP_END_OF_LIST(), > +}; > + > +static void xlnx_versal_ospi_class_init(ObjectClass *klass, void *data) > +{ > + DeviceClass *dc = DEVICE_CLASS(klass); > + > + dc->reset = xlnx_versal_ospi_reset; > + dc->realize = xlnx_versal_ospi_realize; > + dc->vmsd = &vmstate_xlnx_versal_ospi; > + device_class_set_props(dc, xlnx_versal_ospi_properties); > +} > + > +static const TypeInfo xlnx_versal_ospi_info = { > + .name = TYPE_XILINX_VERSAL_OSPI, > + .parent = TYPE_SYS_BUS_DEVICE, > + .instance_size = sizeof(XlnxVersalOspi), > + .class_init = xlnx_versal_ospi_class_init, > + .instance_init = xlnx_versal_ospi_init, > +}; > + > +static void xlnx_versal_ospi_register_types(void) > +{ > + type_register_static(&xlnx_versal_ospi_info); > +} > + > +type_init(xlnx_versal_ospi_register_types) > diff --git a/include/hw/ssi/xlnx-versal-ospi.h b/include/hw/ssi/xlnx-versal-ospi.h > new file mode 100644 > index 0000000000..cc2602b300 > --- /dev/null > +++ b/include/hw/ssi/xlnx-versal-ospi.h > @@ -0,0 +1,86 @@ > +/* > + * Header file for the Xilinx Versal's OSPI controller > + * > + * Copyright (C) 2021 Xilinx Inc > + * Written by Francisco Iglesias <francisco.iglesias@xilinx.com> > + * > + * Permission is hereby granted, free of charge, to any person obtaining a copy > + * of this software and associated documentation files (the "Software"), to deal > + * in the Software without restriction, including without limitation the rights > + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell > + * copies of the Software, and to permit persons to whom the Software is > + * furnished to do so, subject to the following conditions: > + * > + * The above copyright notice and this permission notice shall be included in > + * all copies or substantial portions of the Software. > + * > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR > + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, > + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL > + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER > + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, > + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN > + * THE SOFTWARE. > + */ > + > +#ifndef XILINX_VERSAL_OSPI_H > +#define XILINX_VERSAL_OSPI_H > + > +#include "hw/register.h" > +#include "hw/ssi/ssi.h" > +#include "qemu/fifo32.h" > +#include "hw/dma/dma-ctrl.h" > + > +#define TYPE_XILINX_VERSAL_OSPI "xlnx.versal-ospi" > + > +#define XILINX_VERSAL_OSPI(obj) \ > + OBJECT_CHECK(XlnxVersalOspi, (obj), TYPE_XILINX_VERSAL_OSPI) > + > +#define XILINX_VERSAL_OSPI_R_MAX (0xfc / 4 + 1) > + > +/* > + * Indirect operations > + */ > +typedef struct IndOp { > + uint32_t flash_addr; > + uint32_t num_bytes; > + uint32_t done_bytes; > + bool completed; > +} IndOp; > + > +typedef struct XlnxVersalOspi { > + SysBusDevice parent_obj; > + > + MemoryRegion iomem; > + MemoryRegion iomem_dac; > + > + uint8_t num_cs; > + qemu_irq *cs_lines; > + > + SSIBus *spi; > + > + Fifo8 rx_fifo; > + Fifo8 tx_fifo; > + > + Fifo8 rx_sram; > + Fifo8 tx_sram; > + > + qemu_irq irq; > + > + DmaCtrl *dma_src; > + bool ind_write_disabled; > + bool dac_with_indac; > + bool dac_enable; > + bool src_dma_inprog; > + > + IndOp rd_ind_op[2]; > + IndOp wr_ind_op[2]; > + > + uint32_t regs[XILINX_VERSAL_OSPI_R_MAX]; > + RegisterInfo regs_info[XILINX_VERSAL_OSPI_R_MAX]; > + > + /* Maximum inferred membank size is 512 bytes */ > + uint8_t stig_membank[512]; > +} XlnxVersalOspi; > + > +#endif /* XILINX_VERSAL_OSPI_H */ > -- > 2.11.0 >
diff --git a/hw/ssi/meson.build b/hw/ssi/meson.build index 3d6bc82ab1..0ded9cd092 100644 --- a/hw/ssi/meson.build +++ b/hw/ssi/meson.build @@ -7,5 +7,6 @@ softmmu_ss.add(when: 'CONFIG_SSI', if_true: files('ssi.c')) softmmu_ss.add(when: 'CONFIG_STM32F2XX_SPI', if_true: files('stm32f2xx_spi.c')) softmmu_ss.add(when: 'CONFIG_XILINX_SPI', if_true: files('xilinx_spi.c')) softmmu_ss.add(when: 'CONFIG_XILINX_SPIPS', if_true: files('xilinx_spips.c')) +softmmu_ss.add(when: 'CONFIG_XLNX_VERSAL', if_true: files('xlnx-versal-ospi.c')) softmmu_ss.add(when: 'CONFIG_IMX', if_true: files('imx_spi.c')) softmmu_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_spi.c')) diff --git a/hw/ssi/xlnx-versal-ospi.c b/hw/ssi/xlnx-versal-ospi.c new file mode 100644 index 0000000000..c02fc143de --- /dev/null +++ b/hw/ssi/xlnx-versal-ospi.c @@ -0,0 +1,1892 @@ +/* + * QEMU model of Xilinx Versal's OSPI controller. + * + * Copyright (c) 2021 Xilinx Inc. + * Written by Francisco Iglesias <francisco.iglesias@xilinx.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "qemu/osdep.h" +#include "hw/sysbus.h" +#include "migration/vmstate.h" +#include "hw/qdev-properties.h" +#include "qemu/bitops.h" +#include "qemu/log.h" +#include "hw/irq.h" +#include "hw/ssi/xlnx-versal-ospi.h" + +#ifndef XILINX_VERSAL_OSPI_ERR_DEBUG +#define XILINX_VERSAL_OSPI_ERR_DEBUG 0 +#endif + +REG32(CONFIG_REG, 0x0) + FIELD(CONFIG_REG, IDLE_FLD, 31, 1) + FIELD(CONFIG_REG, DUAL_BYTE_OPCODE_EN_FLD, 30, 1) + FIELD(CONFIG_REG, CRC_ENABLE_FLD, 29, 1) + FIELD(CONFIG_REG, CONFIG_RESV2_FLD, 26, 3) + FIELD(CONFIG_REG, PIPELINE_PHY_FLD, 25, 1) + FIELD(CONFIG_REG, ENABLE_DTR_PROTOCOL_FLD, 24, 1) + FIELD(CONFIG_REG, ENABLE_AHB_DECODER_FLD, 23, 1) + FIELD(CONFIG_REG, MSTR_BAUD_DIV_FLD, 19, 4) + FIELD(CONFIG_REG, ENTER_XIP_MODE_IMM_FLD, 18, 1) + FIELD(CONFIG_REG, ENTER_XIP_MODE_FLD, 17, 1) + FIELD(CONFIG_REG, ENB_AHB_ADDR_REMAP_FLD, 16, 1) + FIELD(CONFIG_REG, ENB_DMA_IF_FLD, 15, 1) + FIELD(CONFIG_REG, WR_PROT_FLASH_FLD, 14, 1) + FIELD(CONFIG_REG, PERIPH_CS_LINES_FLD, 10, 4) + FIELD(CONFIG_REG, PERIPH_SEL_DEC_FLD, 9, 1) + FIELD(CONFIG_REG, ENB_LEGACY_IP_MODE_FLD, 8, 1) + FIELD(CONFIG_REG, ENB_DIR_ACC_CTLR_FLD, 7, 1) + FIELD(CONFIG_REG, RESET_CFG_FLD, 6, 1) + FIELD(CONFIG_REG, RESET_PIN_FLD, 5, 1) + FIELD(CONFIG_REG, HOLD_PIN_FLD, 4, 1) + FIELD(CONFIG_REG, PHY_MODE_ENABLE_FLD, 3, 1) + FIELD(CONFIG_REG, SEL_CLK_PHASE_FLD, 2, 1) + FIELD(CONFIG_REG, SEL_CLK_POL_FLD, 1, 1) + FIELD(CONFIG_REG, ENB_SPI_FLD, 0, 1) +REG32(DEV_INSTR_RD_CONFIG_REG, 0x4) + FIELD(DEV_INSTR_RD_CONFIG_REG, RD_INSTR_RESV5_FLD, 29, 3) + FIELD(DEV_INSTR_RD_CONFIG_REG, DUMMY_RD_CLK_CYCLES_FLD, 24, 5) + FIELD(DEV_INSTR_RD_CONFIG_REG, RD_INSTR_RESV4_FLD, 21, 3) + FIELD(DEV_INSTR_RD_CONFIG_REG, MODE_BIT_ENABLE_FLD, 20, 1) + FIELD(DEV_INSTR_RD_CONFIG_REG, RD_INSTR_RESV3_FLD, 18, 2) + FIELD(DEV_INSTR_RD_CONFIG_REG, DATA_XFER_TYPE_EXT_MODE_FLD, 16, 2) + FIELD(DEV_INSTR_RD_CONFIG_REG, RD_INSTR_RESV2_FLD, 14, 2) + FIELD(DEV_INSTR_RD_CONFIG_REG, ADDR_XFER_TYPE_STD_MODE_FLD, 12, 2) + FIELD(DEV_INSTR_RD_CONFIG_REG, PRED_DIS_FLD, 11, 1) + FIELD(DEV_INSTR_RD_CONFIG_REG, DDR_EN_FLD, 10, 1) + FIELD(DEV_INSTR_RD_CONFIG_REG, INSTR_TYPE_FLD, 8, 2) + FIELD(DEV_INSTR_RD_CONFIG_REG, RD_OPCODE_NON_XIP_FLD, 0, 8) +REG32(DEV_INSTR_WR_CONFIG_REG, 0x8) + FIELD(DEV_INSTR_WR_CONFIG_REG, WR_INSTR_RESV4_FLD, 29, 3) + FIELD(DEV_INSTR_WR_CONFIG_REG, DUMMY_WR_CLK_CYCLES_FLD, 24, 5) + FIELD(DEV_INSTR_WR_CONFIG_REG, WR_INSTR_RESV3_FLD, 18, 6) + FIELD(DEV_INSTR_WR_CONFIG_REG, DATA_XFER_TYPE_EXT_MODE_FLD, 16, 2) + FIELD(DEV_INSTR_WR_CONFIG_REG, WR_INSTR_RESV2_FLD, 14, 2) + FIELD(DEV_INSTR_WR_CONFIG_REG, ADDR_XFER_TYPE_STD_MODE_FLD, 12, 2) + FIELD(DEV_INSTR_WR_CONFIG_REG, WR_INSTR_RESV1_FLD, 9, 3) + FIELD(DEV_INSTR_WR_CONFIG_REG, WEL_DIS_FLD, 8, 1) + FIELD(DEV_INSTR_WR_CONFIG_REG, WR_OPCODE_FLD, 0, 8) +REG32(DEV_DELAY_REG, 0xc) + FIELD(DEV_DELAY_REG, D_NSS_FLD, 24, 8) + FIELD(DEV_DELAY_REG, D_BTWN_FLD, 16, 8) + FIELD(DEV_DELAY_REG, D_AFTER_FLD, 8, 8) + FIELD(DEV_DELAY_REG, D_INIT_FLD, 0, 8) +REG32(RD_DATA_CAPTURE_REG, 0x10) + FIELD(RD_DATA_CAPTURE_REG, RD_DATA_RESV3_FLD, 20, 12) + FIELD(RD_DATA_CAPTURE_REG, DDR_READ_DELAY_FLD, 16, 4) + FIELD(RD_DATA_CAPTURE_REG, RD_DATA_RESV2_FLD, 9, 7) + FIELD(RD_DATA_CAPTURE_REG, DQS_ENABLE_FLD, 8, 1) + FIELD(RD_DATA_CAPTURE_REG, RD_DATA_RESV1_FLD, 6, 2) + FIELD(RD_DATA_CAPTURE_REG, SAMPLE_EDGE_SEL_FLD, 5, 1) + FIELD(RD_DATA_CAPTURE_REG, DELAY_FLD, 1, 4) + FIELD(RD_DATA_CAPTURE_REG, BYPASS_FLD, 0, 1) +REG32(DEV_SIZE_CONFIG_REG, 0x14) + FIELD(DEV_SIZE_CONFIG_REG, DEV_SIZE_RESV_FLD, 29, 3) + FIELD(DEV_SIZE_CONFIG_REG, MEM_SIZE_ON_CS3_FLD, 27, 2) + FIELD(DEV_SIZE_CONFIG_REG, MEM_SIZE_ON_CS2_FLD, 25, 2) + FIELD(DEV_SIZE_CONFIG_REG, MEM_SIZE_ON_CS1_FLD, 23, 2) + FIELD(DEV_SIZE_CONFIG_REG, MEM_SIZE_ON_CS0_FLD, 21, 2) + FIELD(DEV_SIZE_CONFIG_REG, BYTES_PER_SUBSECTOR_FLD, 16, 5) + FIELD(DEV_SIZE_CONFIG_REG, BYTES_PER_DEVICE_PAGE_FLD, 4, 12) + FIELD(DEV_SIZE_CONFIG_REG, NUM_ADDR_BYTES_FLD, 0, 4) +REG32(SRAM_PARTITION_CFG_REG, 0x18) + FIELD(SRAM_PARTITION_CFG_REG, SRAM_PARTITION_RESV_FLD, 8, 24) + FIELD(SRAM_PARTITION_CFG_REG, ADDR_FLD, 0, 8) +REG32(IND_AHB_ADDR_TRIGGER_REG, 0x1c) +REG32(DMA_PERIPH_CONFIG_REG, 0x20) + FIELD(DMA_PERIPH_CONFIG_REG, DMA_PERIPH_RESV2_FLD, 12, 20) + FIELD(DMA_PERIPH_CONFIG_REG, NUM_BURST_REQ_BYTES_FLD, 8, 4) + FIELD(DMA_PERIPH_CONFIG_REG, DMA_PERIPH_RESV1_FLD, 4, 4) + FIELD(DMA_PERIPH_CONFIG_REG, NUM_SINGLE_REQ_BYTES_FLD, 0, 4) +REG32(REMAP_ADDR_REG, 0x24) +REG32(MODE_BIT_CONFIG_REG, 0x28) + FIELD(MODE_BIT_CONFIG_REG, RX_CRC_DATA_LOW_FLD, 24, 8) + FIELD(MODE_BIT_CONFIG_REG, RX_CRC_DATA_UP_FLD, 16, 8) + FIELD(MODE_BIT_CONFIG_REG, CRC_OUT_ENABLE_FLD, 15, 1) + FIELD(MODE_BIT_CONFIG_REG, MODE_BIT_RESV1_FLD, 11, 4) + FIELD(MODE_BIT_CONFIG_REG, CHUNK_SIZE_FLD, 8, 3) + FIELD(MODE_BIT_CONFIG_REG, MODE_FLD, 0, 8) +REG32(SRAM_FILL_REG, 0x2c) + FIELD(SRAM_FILL_REG, SRAM_FILL_INDAC_WRITE_FLD, 16, 16) + FIELD(SRAM_FILL_REG, SRAM_FILL_INDAC_READ_FLD, 0, 16) +REG32(TX_THRESH_REG, 0x30) + FIELD(TX_THRESH_REG, TX_THRESH_RESV_FLD, 5, 27) + FIELD(TX_THRESH_REG, LEVEL_FLD, 0, 5) +REG32(RX_THRESH_REG, 0x34) + FIELD(RX_THRESH_REG, RX_THRESH_RESV_FLD, 5, 27) + FIELD(RX_THRESH_REG, LEVEL_FLD, 0, 5) +REG32(WRITE_COMPLETION_CTRL_REG, 0x38) + FIELD(WRITE_COMPLETION_CTRL_REG, POLL_REP_DELAY_FLD, 24, 8) + FIELD(WRITE_COMPLETION_CTRL_REG, POLL_COUNT_FLD, 16, 8) + FIELD(WRITE_COMPLETION_CTRL_REG, ENABLE_POLLING_EXP_FLD, 15, 1) + FIELD(WRITE_COMPLETION_CTRL_REG, DISABLE_POLLING_FLD, 14, 1) + FIELD(WRITE_COMPLETION_CTRL_REG, POLLING_POLARITY_FLD, 13, 1) + FIELD(WRITE_COMPLETION_CTRL_REG, WR_COMP_CTRL_RESV1_FLD, 12, 1) + FIELD(WRITE_COMPLETION_CTRL_REG, POLLING_ADDR_EN_FLD, 11, 1) + FIELD(WRITE_COMPLETION_CTRL_REG, POLLING_BIT_INDEX_FLD, 8, 3) + FIELD(WRITE_COMPLETION_CTRL_REG, OPCODE_FLD, 0, 8) +REG32(NO_OF_POLLS_BEF_EXP_REG, 0x3c) +REG32(IRQ_STATUS_REG, 0x40) + FIELD(IRQ_STATUS_REG, IRQ_STAT_RESV_FLD, 20, 12) + FIELD(IRQ_STATUS_REG, ECC_FAIL_FLD, 19, 1) + FIELD(IRQ_STATUS_REG, TX_CRC_CHUNK_BRK_FLD, 18, 1) + FIELD(IRQ_STATUS_REG, RX_CRC_DATA_VAL_FLD, 17, 1) + FIELD(IRQ_STATUS_REG, RX_CRC_DATA_ERR_FLD, 16, 1) + FIELD(IRQ_STATUS_REG, IRQ_STAT_RESV1_FLD, 15, 1) + FIELD(IRQ_STATUS_REG, STIG_REQ_INT_FLD, 14, 1) + FIELD(IRQ_STATUS_REG, POLL_EXP_INT_FLD, 13, 1) + FIELD(IRQ_STATUS_REG, INDRD_SRAM_FULL_FLD, 12, 1) + FIELD(IRQ_STATUS_REG, RX_FIFO_FULL_FLD, 11, 1) + FIELD(IRQ_STATUS_REG, RX_FIFO_NOT_EMPTY_FLD, 10, 1) + FIELD(IRQ_STATUS_REG, TX_FIFO_FULL_FLD, 9, 1) + FIELD(IRQ_STATUS_REG, TX_FIFO_NOT_FULL_FLD, 8, 1) + FIELD(IRQ_STATUS_REG, RECV_OVERFLOW_FLD, 7, 1) + FIELD(IRQ_STATUS_REG, INDIRECT_XFER_LEVEL_BREACH_FLD, 6, 1) + FIELD(IRQ_STATUS_REG, ILLEGAL_ACCESS_DET_FLD, 5, 1) + FIELD(IRQ_STATUS_REG, PROT_WR_ATTEMPT_FLD, 4, 1) + FIELD(IRQ_STATUS_REG, INDIRECT_TRANSFER_REJECT_FLD, 3, 1) + FIELD(IRQ_STATUS_REG, INDIRECT_OP_DONE_FLD, 2, 1) + FIELD(IRQ_STATUS_REG, UNDERFLOW_DET_FLD, 1, 1) + FIELD(IRQ_STATUS_REG, MODE_M_FAIL_FLD, 0, 1) +REG32(IRQ_MASK_REG, 0x44) + FIELD(IRQ_MASK_REG, IRQ_MASK_RESV_FLD, 20, 12) + FIELD(IRQ_MASK_REG, ECC_FAIL_MASK_FLD, 19, 1) + FIELD(IRQ_MASK_REG, TX_CRC_CHUNK_BRK_MASK_FLD, 18, 1) + FIELD(IRQ_MASK_REG, RX_CRC_DATA_VAL_MASK_FLD, 17, 1) + FIELD(IRQ_MASK_REG, RX_CRC_DATA_ERR_MASK_FLD, 16, 1) + FIELD(IRQ_MASK_REG, IRQ_MASK_RESV1_FLD, 15, 1) + FIELD(IRQ_MASK_REG, STIG_REQ_MASK_FLD, 14, 1) + FIELD(IRQ_MASK_REG, POLL_EXP_INT_MASK_FLD, 13, 1) + FIELD(IRQ_MASK_REG, INDRD_SRAM_FULL_MASK_FLD, 12, 1) + FIELD(IRQ_MASK_REG, RX_FIFO_FULL_MASK_FLD, 11, 1) + FIELD(IRQ_MASK_REG, RX_FIFO_NOT_EMPTY_MASK_FLD, 10, 1) + FIELD(IRQ_MASK_REG, TX_FIFO_FULL_MASK_FLD, 9, 1) + FIELD(IRQ_MASK_REG, TX_FIFO_NOT_FULL_MASK_FLD, 8, 1) + FIELD(IRQ_MASK_REG, RECV_OVERFLOW_MASK_FLD, 7, 1) + FIELD(IRQ_MASK_REG, INDIRECT_XFER_LEVEL_BREACH_MASK_FLD, 6, 1) + FIELD(IRQ_MASK_REG, ILLEGAL_ACCESS_DET_MASK_FLD, 5, 1) + FIELD(IRQ_MASK_REG, PROT_WR_ATTEMPT_MASK_FLD, 4, 1) + FIELD(IRQ_MASK_REG, INDIRECT_TRANSFER_REJECT_MASK_FLD, 3, 1) + FIELD(IRQ_MASK_REG, INDIRECT_OP_DONE_MASK_FLD, 2, 1) + FIELD(IRQ_MASK_REG, UNDERFLOW_DET_MASK_FLD, 1, 1) + FIELD(IRQ_MASK_REG, MODE_M_FAIL_MASK_FLD, 0, 1) +REG32(LOWER_WR_PROT_REG, 0x50) +REG32(UPPER_WR_PROT_REG, 0x54) +REG32(WR_PROT_CTRL_REG, 0x58) + FIELD(WR_PROT_CTRL_REG, WR_PROT_CTRL_RESV_FLD, 2, 30) + FIELD(WR_PROT_CTRL_REG, ENB_FLD, 1, 1) + FIELD(WR_PROT_CTRL_REG, INV_FLD, 0, 1) +REG32(INDIRECT_READ_XFER_CTRL_REG, 0x60) + FIELD(INDIRECT_READ_XFER_CTRL_REG, INDIR_RD_XFER_RESV_FLD, 8, 24) + FIELD(INDIRECT_READ_XFER_CTRL_REG, NUM_IND_OPS_DONE_FLD, 6, 2) + FIELD(INDIRECT_READ_XFER_CTRL_REG, IND_OPS_DONE_STATUS_FLD, 5, 1) + FIELD(INDIRECT_READ_XFER_CTRL_REG, RD_QUEUED_FLD, 4, 1) + FIELD(INDIRECT_READ_XFER_CTRL_REG, SRAM_FULL_FLD, 3, 1) + FIELD(INDIRECT_READ_XFER_CTRL_REG, RD_STATUS_FLD, 2, 1) + FIELD(INDIRECT_READ_XFER_CTRL_REG, CANCEL_FLD, 1, 1) + FIELD(INDIRECT_READ_XFER_CTRL_REG, START_FLD, 0, 1) +REG32(INDIRECT_READ_XFER_WATERMARK_REG, 0x64) +REG32(INDIRECT_READ_XFER_START_REG, 0x68) +REG32(INDIRECT_READ_XFER_NUM_BYTES_REG, 0x6c) +REG32(INDIRECT_WRITE_XFER_CTRL_REG, 0x70) + FIELD(INDIRECT_WRITE_XFER_CTRL_REG, INDIR_WR_XFER_RESV2_FLD, 8, 24) + FIELD(INDIRECT_WRITE_XFER_CTRL_REG, NUM_IND_OPS_DONE_FLD, 6, 2) + FIELD(INDIRECT_WRITE_XFER_CTRL_REG, IND_OPS_DONE_STATUS_FLD, 5, 1) + FIELD(INDIRECT_WRITE_XFER_CTRL_REG, WR_QUEUED_FLD, 4, 1) + FIELD(INDIRECT_WRITE_XFER_CTRL_REG, INDIR_WR_XFER_RESV1_FLD, 3, 1) + FIELD(INDIRECT_WRITE_XFER_CTRL_REG, WR_STATUS_FLD, 2, 1) + FIELD(INDIRECT_WRITE_XFER_CTRL_REG, CANCEL_FLD, 1, 1) + FIELD(INDIRECT_WRITE_XFER_CTRL_REG, START_FLD, 0, 1) +REG32(INDIRECT_WRITE_XFER_WATERMARK_REG, 0x74) +REG32(INDIRECT_WRITE_XFER_START_REG, 0x78) +REG32(INDIRECT_WRITE_XFER_NUM_BYTES_REG, 0x7c) +REG32(INDIRECT_TRIGGER_ADDR_RANGE_REG, 0x80) + FIELD(INDIRECT_TRIGGER_ADDR_RANGE_REG, IND_RANGE_RESV1_FLD, 4, 28) + FIELD(INDIRECT_TRIGGER_ADDR_RANGE_REG, IND_RANGE_WIDTH_FLD, 0, 4) +REG32(FLASH_COMMAND_CTRL_MEM_REG, 0x8c) + FIELD(FLASH_COMMAND_CTRL_MEM_REG, FLASH_COMMAND_CTRL_MEM_RESV1_FLD, 29, 3) + FIELD(FLASH_COMMAND_CTRL_MEM_REG, MEM_BANK_ADDR_FLD, 20, 9) + FIELD(FLASH_COMMAND_CTRL_MEM_REG, FLASH_COMMAND_CTRL_MEM_RESV2_FLD, 19, 1) + FIELD(FLASH_COMMAND_CTRL_MEM_REG, NB_OF_STIG_READ_BYTES_FLD, 16, 3) + FIELD(FLASH_COMMAND_CTRL_MEM_REG, MEM_BANK_READ_DATA_FLD, 8, 8) + FIELD(FLASH_COMMAND_CTRL_MEM_REG, FLASH_COMMAND_CTRL_MEM_RESV3_FLD, 2, 6) + FIELD(FLASH_COMMAND_CTRL_MEM_REG, MEM_BANK_REQ_IN_PROGRESS_FLD, 1, 1) + FIELD(FLASH_COMMAND_CTRL_MEM_REG, TRIGGER_MEM_BANK_REQ_FLD, 0, 1) +REG32(FLASH_CMD_CTRL_REG, 0x90) + FIELD(FLASH_CMD_CTRL_REG, CMD_OPCODE_FLD, 24, 8) + FIELD(FLASH_CMD_CTRL_REG, ENB_READ_DATA_FLD, 23, 1) + FIELD(FLASH_CMD_CTRL_REG, NUM_RD_DATA_BYTES_FLD, 20, 3) + FIELD(FLASH_CMD_CTRL_REG, ENB_COMD_ADDR_FLD, 19, 1) + FIELD(FLASH_CMD_CTRL_REG, ENB_MODE_BIT_FLD, 18, 1) + FIELD(FLASH_CMD_CTRL_REG, NUM_ADDR_BYTES_FLD, 16, 2) + FIELD(FLASH_CMD_CTRL_REG, ENB_WRITE_DATA_FLD, 15, 1) + FIELD(FLASH_CMD_CTRL_REG, NUM_WR_DATA_BYTES_FLD, 12, 3) + FIELD(FLASH_CMD_CTRL_REG, NUM_DUMMY_CYCLES_FLD, 7, 5) + FIELD(FLASH_CMD_CTRL_REG, FLASH_CMD_CTRL_RESV1_FLD, 3, 4) + FIELD(FLASH_CMD_CTRL_REG, STIG_MEM_BANK_EN_FLD, 2, 1) + FIELD(FLASH_CMD_CTRL_REG, CMD_EXEC_STATUS_FLD, 1, 1) + FIELD(FLASH_CMD_CTRL_REG, CMD_EXEC_FLD, 0, 1) +REG32(FLASH_CMD_ADDR_REG, 0x94) +REG32(FLASH_RD_DATA_LOWER_REG, 0xa0) +REG32(FLASH_RD_DATA_UPPER_REG, 0xa4) +REG32(FLASH_WR_DATA_LOWER_REG, 0xa8) +REG32(FLASH_WR_DATA_UPPER_REG, 0xac) +REG32(POLLING_FLASH_STATUS_REG, 0xb0) + FIELD(POLLING_FLASH_STATUS_REG, DEVICE_STATUS_RSVD_FLD2, 21, 11) + FIELD(POLLING_FLASH_STATUS_REG, DEVICE_STATUS_NB_DUMMY, 16, 5) + FIELD(POLLING_FLASH_STATUS_REG, DEVICE_STATUS_RSVD_FLD1, 9, 7) + FIELD(POLLING_FLASH_STATUS_REG, DEVICE_STATUS_VALID_FLD, 8, 1) + FIELD(POLLING_FLASH_STATUS_REG, DEVICE_STATUS_FLD, 0, 8) +REG32(PHY_CONFIGURATION_REG, 0xb4) + FIELD(PHY_CONFIGURATION_REG, PHY_CONFIG_RESYNC_FLD, 31, 1) + FIELD(PHY_CONFIGURATION_REG, PHY_CONFIG_RESET_FLD, 30, 1) + FIELD(PHY_CONFIGURATION_REG, PHY_CONFIG_RX_DLL_BYPASS_FLD, 29, 1) + FIELD(PHY_CONFIGURATION_REG, PHY_CONFIG_RESV2_FLD, 23, 6) + FIELD(PHY_CONFIGURATION_REG, PHY_CONFIG_TX_DLL_DELAY_FLD, 16, 7) + FIELD(PHY_CONFIGURATION_REG, PHY_CONFIG_RESV1_FLD, 7, 9) + FIELD(PHY_CONFIGURATION_REG, PHY_CONFIG_RX_DLL_DELAY_FLD, 0, 7) +REG32(PHY_MASTER_CONTROL_REG, 0xb8) + FIELD(PHY_MASTER_CONTROL_REG, PHY_MASTER_CONTROL_RESV3_FLD, 25, 7) + FIELD(PHY_MASTER_CONTROL_REG, PHY_MASTER_LOCK_MODE_FLD, 24, 1) + FIELD(PHY_MASTER_CONTROL_REG, PHY_MASTER_BYPASS_MODE_FLD, 23, 1) + FIELD(PHY_MASTER_CONTROL_REG, PHY_MASTER_PHASE_DETECT_SELECTOR_FLD, 20, 3) + FIELD(PHY_MASTER_CONTROL_REG, PHY_MASTER_CONTROL_RESV2_FLD, 19, 1) + FIELD(PHY_MASTER_CONTROL_REG, PHY_MASTER_NB_INDICATIONS_FLD, 16, 3) + FIELD(PHY_MASTER_CONTROL_REG, PHY_MASTER_CONTROL_RESV1_FLD, 7, 9) + FIELD(PHY_MASTER_CONTROL_REG, PHY_MASTER_INITIAL_DELAY_FLD, 0, 7) +REG32(DLL_OBSERVABLE_LOWER_REG, 0xbc) + FIELD(DLL_OBSERVABLE_LOWER_REG, + DLL_OBSERVABLE_LOWER_DLL_LOCK_INC_FLD, 24, 8) + FIELD(DLL_OBSERVABLE_LOWER_REG, + DLL_OBSERVABLE_LOWER_DLL_LOCK_DEC_FLD, 16, 8) + FIELD(DLL_OBSERVABLE_LOWER_REG, + DLL_OBSERVABLE_LOWER_LOOPBACK_LOCK_FLD, 15, 1) + FIELD(DLL_OBSERVABLE_LOWER_REG, + DLL_OBSERVABLE_LOWER_LOCK_VALUE_FLD, 8, 7) + FIELD(DLL_OBSERVABLE_LOWER_REG, + DLL_OBSERVABLE_LOWER_UNLOCK_COUNTER_FLD, 3, 5) + FIELD(DLL_OBSERVABLE_LOWER_REG, + DLL_OBSERVABLE_LOWER_LOCK_MODE_FLD, 1, 2) + FIELD(DLL_OBSERVABLE_LOWER_REG, + DLL_OBSERVABLE_LOWER_DLL_LOCK_FLD, 0, 1) +REG32(DLL_OBSERVABLE_UPPER_REG, 0xc0) + FIELD(DLL_OBSERVABLE_UPPER_REG, + DLL_OBSERVABLE_UPPER_RESV2_FLD, 23, 9) + FIELD(DLL_OBSERVABLE_UPPER_REG, + DLL_OBSERVABLE_UPPER_TX_DECODER_OUTPUT_FLD, 16, 7) + FIELD(DLL_OBSERVABLE_UPPER_REG, + DLL_OBSERVABLE_UPPER_RESV1_FLD, 7, 9) + FIELD(DLL_OBSERVABLE_UPPER_REG, + DLL_OBSERVABLE__UPPER_RX_DECODER_OUTPUT_FLD, 0, 7) +REG32(OPCODE_EXT_LOWER_REG, 0xe0) + FIELD(OPCODE_EXT_LOWER_REG, EXT_READ_OPCODE_FLD, 24, 8) + FIELD(OPCODE_EXT_LOWER_REG, EXT_WRITE_OPCODE_FLD, 16, 8) + FIELD(OPCODE_EXT_LOWER_REG, EXT_POLL_OPCODE_FLD, 8, 8) + FIELD(OPCODE_EXT_LOWER_REG, EXT_STIG_OPCODE_FLD, 0, 8) +REG32(OPCODE_EXT_UPPER_REG, 0xe4) + FIELD(OPCODE_EXT_UPPER_REG, WEL_OPCODE_FLD, 24, 8) + FIELD(OPCODE_EXT_UPPER_REG, EXT_WEL_OPCODE_FLD, 16, 8) + FIELD(OPCODE_EXT_UPPER_REG, OPCODE_EXT_UPPER_RESV1_FLD, 0, 16) +REG32(MODULE_ID_REG, 0xfc) + FIELD(MODULE_ID_REG, FIX_PATCH_FLD, 24, 8) + FIELD(MODULE_ID_REG, MODULE_ID_FLD, 8, 16) + FIELD(MODULE_ID_REG, MODULE_ID_RESV_FLD, 2, 6) + FIELD(MODULE_ID_REG, CONF_FLD, 0, 2) + +#define RXFF_SZ 1024 +#define TXFF_SZ 1024 + +#define MAX_RX_DEC_OUT 8 + +#define SZ_512MBIT (512 * 1024 * 1024) +#define SZ_1GBIT (1024 * 1024 * 1024) +#define SZ_2GBIT (2ULL * SZ_1GBIT) +#define SZ_4GBIT (4ULL * SZ_1GBIT) + +#define IS_IND_DMA_START(op) (op->done_bytes == 0) +/* + * Bit field size of R_INDIRECT_WRITE_XFER_CTRL_REG_NUM_IND_OPS_DONE_FLD + * is 2 bits, which can record max of 3 indac operations. + */ +#define IND_OPS_DONE_MAX 3 + +typedef enum { + WREN = 0x6, +} FlashCMD; + +/* Type to avoid cpu endian byte swaps */ +typedef union { + uint64_t u64; + uint8_t u8[8]; +} OSPIRdData; + +static unsigned int ospi_stig_addr_len(XlnxVersalOspi *s) +{ + /* Num address bytes is NUM_ADDR_BYTES_FLD + 1 */ + return ARRAY_FIELD_EX32(s->regs, + FLASH_CMD_CTRL_REG, NUM_ADDR_BYTES_FLD) + 1; +} + +static unsigned int ospi_stig_wr_data_len(XlnxVersalOspi *s) +{ + /* Num write data bytes is NUM_WR_DATA_BYTES_FLD + 1 */ + return ARRAY_FIELD_EX32(s->regs, + FLASH_CMD_CTRL_REG, NUM_WR_DATA_BYTES_FLD) + 1; +} + +static unsigned int ospi_stig_rd_data_len(XlnxVersalOspi *s) +{ + /* Num read data bytes is NUM_RD_DATA_BYTES_FLD + 1 */ + return ARRAY_FIELD_EX32(s->regs, + FLASH_CMD_CTRL_REG, NUM_RD_DATA_BYTES_FLD) + 1; +} + +/* + * Status bits in R_IRQ_STATUS_REG are set when the event occurs and the + * interrupt is enabled in the mask register ([1] Section 2.3.17) + */ +static void set_irq(XlnxVersalOspi *s, uint32_t set_mask) +{ + s->regs[R_IRQ_STATUS_REG] |= s->regs[R_IRQ_MASK_REG] & set_mask; +} + +static void ospi_update_irq_line(XlnxVersalOspi *s) +{ + qemu_set_irq(s->irq, !!(s->regs[R_IRQ_STATUS_REG] & + s->regs[R_IRQ_MASK_REG])); +} + +static uint8_t ospi_get_wr_opcode(XlnxVersalOspi *s) +{ + return ARRAY_FIELD_EX32(s->regs, + DEV_INSTR_WR_CONFIG_REG, WR_OPCODE_FLD); +} + +static uint8_t ospi_get_rd_opcode(XlnxVersalOspi *s) +{ + return ARRAY_FIELD_EX32(s->regs, + DEV_INSTR_RD_CONFIG_REG, RD_OPCODE_NON_XIP_FLD); +} + +static uint32_t ospi_get_num_addr_bytes(XlnxVersalOspi *s) +{ + /* Num address bytes is NUM_ADDR_BYTES_FLD + 1 */ + return ARRAY_FIELD_EX32(s->regs, + DEV_SIZE_CONFIG_REG, NUM_ADDR_BYTES_FLD) + 1; +} + +static void ospi_stig_membank_req(XlnxVersalOspi *s) +{ + int idx = ARRAY_FIELD_EX32(s->regs, + FLASH_COMMAND_CTRL_MEM_REG, MEM_BANK_ADDR_FLD); + + ARRAY_FIELD_DP32(s->regs, FLASH_COMMAND_CTRL_MEM_REG, + MEM_BANK_READ_DATA_FLD, s->stig_membank[idx]); +} + +static int ospi_stig_membank_rd_bytes(XlnxVersalOspi *s) +{ + int rd_data_fld = ARRAY_FIELD_EX32(s->regs, FLASH_COMMAND_CTRL_MEM_REG, + NB_OF_STIG_READ_BYTES_FLD); + int sizes[6] = { 16, 32, 64, 128, 256, 512 }; + return (rd_data_fld < 6) ? sizes[rd_data_fld] : 0; +} + +static uint32_t ospi_get_page_sz(XlnxVersalOspi *s) +{ + return ARRAY_FIELD_EX32(s->regs, + DEV_SIZE_CONFIG_REG, BYTES_PER_DEVICE_PAGE_FLD); +} + +static bool ospi_ind_rd_watermark_enabled(XlnxVersalOspi *s) +{ + return s->regs[R_INDIRECT_READ_XFER_WATERMARK_REG]; +} + +static void ind_op_advance(IndOp *op, unsigned int len) +{ + op->done_bytes += len; + assert(op->done_bytes <= op->num_bytes); + if (op->done_bytes == op->num_bytes) { + op->completed = true; + } +} + +static uint32_t ind_op_next_byte(IndOp *op) +{ + return op->flash_addr + op->done_bytes; +} + +static uint32_t ind_op_end_byte(IndOp *op) +{ + return op->flash_addr + op->num_bytes; +} + +static void ospi_ind_op_next(IndOp *op) +{ + op[0] = op[1]; + op[1].completed = true; +} + +static void ind_op_setup(IndOp *op, uint32_t flash_addr, uint32_t num_bytes) +{ + if (num_bytes & 0x3) { + qemu_log_mask(LOG_GUEST_ERROR, + "OSPI indirect op num bytes not word aligned\n"); + } + op->flash_addr = flash_addr; + op->num_bytes = num_bytes; + op->done_bytes = 0; + op->completed = false; +} + +static bool ospi_ind_op_completed(IndOp *op) +{ + return op->completed; +} + +static bool ospi_ind_op_all_completed(XlnxVersalOspi *s) +{ + return s->rd_ind_op[0].completed && s->wr_ind_op[0].completed; +} + +static void ospi_ind_op_cancel(IndOp *op) +{ + op[0].completed = true; + op[1].completed = true; +} + +static bool ospi_ind_op_add(IndOp *op, Fifo8 *fifo, + uint32_t flash_addr, uint32_t num_bytes) +{ + /* Check if first indirect op has been completed */ + if (op->completed) { + fifo8_reset(fifo); + ind_op_setup(op, flash_addr, num_bytes); + return false; + } + + /* Check if second indirect op has been completed */ + op++; + if (op->completed) { + ind_op_setup(op, flash_addr, num_bytes); + return false; + } + return true; +} + +static void ospi_ind_op_queue_up_rd(XlnxVersalOspi *s) +{ + uint32_t num_bytes = s->regs[R_INDIRECT_READ_XFER_NUM_BYTES_REG]; + uint32_t flash_addr = s->regs[R_INDIRECT_READ_XFER_START_REG]; + bool failed; + + failed = ospi_ind_op_add(s->rd_ind_op, &s->rx_sram, flash_addr, num_bytes); + /* If two already queued set rd reject interrupt */ + if (failed) { + set_irq(s, R_IRQ_STATUS_REG_INDIRECT_TRANSFER_REJECT_FLD_MASK); + } +} + +static void ospi_ind_op_queue_up_wr(XlnxVersalOspi *s) +{ + uint32_t num_bytes = s->regs[R_INDIRECT_WRITE_XFER_NUM_BYTES_REG]; + uint32_t flash_addr = s->regs[R_INDIRECT_WRITE_XFER_START_REG]; + bool failed; + + failed = ospi_ind_op_add(s->wr_ind_op, &s->tx_sram, flash_addr, num_bytes); + /* If two already queued set rd reject interrupt */ + if (failed) { + set_irq(s, R_IRQ_STATUS_REG_INDIRECT_TRANSFER_REJECT_FLD_MASK); + } +} + +static uint64_t flash_sz(XlnxVersalOspi *s, unsigned int cs) +{ + /* Flash sizes in MB */ + static const uint64_t sizes[4] = { SZ_512MBIT / 8, SZ_1GBIT / 8, + SZ_2GBIT / 8, SZ_4GBIT / 8 }; + uint32_t v = s->regs[R_DEV_SIZE_CONFIG_REG]; + + v >>= cs * R_DEV_SIZE_CONFIG_REG_MEM_SIZE_ON_CS0_FLD_LENGTH; + return sizes[FIELD_EX32(v, DEV_SIZE_CONFIG_REG, MEM_SIZE_ON_CS0_FLD)]; +} + +static unsigned int ospi_get_block_sz(XlnxVersalOspi *s) +{ + unsigned int block_fld = ARRAY_FIELD_EX32(s->regs, + DEV_SIZE_CONFIG_REG, + BYTES_PER_SUBSECTOR_FLD); + return 1 << block_fld; +} + +static unsigned int flash_blocks(XlnxVersalOspi *s, unsigned int cs) +{ + unsigned int b_sz = ospi_get_block_sz(s); + unsigned int f_sz = flash_sz(s, cs); + + return f_sz / b_sz; +} + +static int ospi_ahb_decoder_cs(XlnxVersalOspi *s, hwaddr addr) +{ + uint64_t end_addr = 0; + int cs; + + for (cs = 0; cs < s->num_cs; cs++) { + end_addr += flash_sz(s, cs); + if (addr < end_addr) { + break; + } + } + + if (cs == s->num_cs) { + /* Address is out of range */ + qemu_log_mask(LOG_GUEST_ERROR, + "OSPI flash address does not fit in configuraton\n"); + return -1; + } + return cs; +} + +static void ospi_ahb_decoder_enable_cs(XlnxVersalOspi *s, hwaddr addr) +{ + int cs = ospi_ahb_decoder_cs(s, addr); + + if (cs >= 0) { + for (int i = 0; i < s->num_cs; i++) { + if (cs == i) { + qemu_set_irq(s->cs_lines[i], 0); + } else { + qemu_set_irq(s->cs_lines[i], 1); + } + } + } +} + +static unsigned int single_cs(XlnxVersalOspi *s) +{ + unsigned int field = ARRAY_FIELD_EX32(s->regs, + CONFIG_REG, PERIPH_CS_LINES_FLD); + int i; + + /* + * 4'bXXX0 -> 4'b1110 + * 4'bXX0X -> 4'b1101 + * 4'bX0XX -> 4'b1011 + * 4'b0XXX -> 4'b0111 + * 4'b1111 -> 4'b1111 + */ + for (i = 0; i < 4; i++) { + if ((field & (1 << i)) == 0) { + return (~(1 << i)) & 0xF; + } + } + return 0; +} + +static void ospi_update_cs_lines(XlnxVersalOspi *s) +{ + unsigned int all_cs; + int i; + + if (ARRAY_FIELD_EX32(s->regs, CONFIG_REG, PERIPH_SEL_DEC_FLD)) { + all_cs = ARRAY_FIELD_EX32(s->regs, CONFIG_REG, PERIPH_CS_LINES_FLD); + } else { + all_cs = single_cs(s); + } + + for (i = 0; i < s->num_cs; i++) { + bool cs = (all_cs >> i) & 1; + + qemu_set_irq(s->cs_lines[i], cs); + } +} + +static void ospi_dac_cs(XlnxVersalOspi *s, hwaddr addr) +{ + if (ARRAY_FIELD_EX32(s->regs, CONFIG_REG, ENABLE_AHB_DECODER_FLD)) { + ospi_ahb_decoder_enable_cs(s, addr); + } else { + ospi_update_cs_lines(s); + } +} + +static void ospi_disable_cs(XlnxVersalOspi *s) +{ + int i; + + for (i = 0; i < s->num_cs; i++) { + qemu_set_irq(s->cs_lines[i], 1); + } +} + +static void ospi_flush_txfifo(XlnxVersalOspi *s) +{ + while (!fifo8_is_empty(&s->tx_fifo)) { + uint32_t tx_rx = fifo8_pop(&s->tx_fifo); + + tx_rx = ssi_transfer(s->spi, tx_rx); + fifo8_push(&s->rx_fifo, tx_rx); + } +} + +static void ospi_tx_fifo_push_address_raw(XlnxVersalOspi *s, + uint32_t flash_addr, + unsigned int addr_bytes) +{ + /* Push write address */ + if (addr_bytes == 4) { + fifo8_push(&s->tx_fifo, flash_addr >> 24); + } + if (addr_bytes >= 3) { + fifo8_push(&s->tx_fifo, flash_addr >> 16); + } + if (addr_bytes >= 2) { + fifo8_push(&s->tx_fifo, flash_addr >> 8); + } + fifo8_push(&s->tx_fifo, flash_addr); +} + +static void ospi_tx_fifo_push_address(XlnxVersalOspi *s, uint32_t flash_addr) +{ + /* Push write address */ + int addr_bytes = ospi_get_num_addr_bytes(s); + + ospi_tx_fifo_push_address_raw(s, flash_addr, addr_bytes); +} + +static void ospi_tx_fifo_push_stig_addr(XlnxVersalOspi *s) +{ + uint32_t flash_addr = s->regs[R_FLASH_CMD_ADDR_REG]; + unsigned int addr_bytes = ospi_stig_addr_len(s); + + ospi_tx_fifo_push_address_raw(s, flash_addr, addr_bytes); +} + +static void ospi_tx_fifo_push_rd_op_addr(XlnxVersalOspi *s, uint32_t flash_addr) +{ + uint8_t inst_code = ospi_get_rd_opcode(s); + + fifo8_reset(&s->tx_fifo); + + /* Push read opcode */ + fifo8_push(&s->tx_fifo, inst_code); + + /* Push read address */ + ospi_tx_fifo_push_address(s, flash_addr); +} + +static void ospi_tx_fifo_push_stig_wr_data(XlnxVersalOspi *s) +{ + uint64_t data = s->regs[R_FLASH_WR_DATA_LOWER_REG]; + int wr_data_len = ospi_stig_wr_data_len(s); + int i; + + data |= (uint64_t) s->regs[R_FLASH_WR_DATA_UPPER_REG] << 32; + for (i = 0; i < wr_data_len; i++) { + int shift = i * 8; + fifo8_push(&s->tx_fifo, data >> shift); + } +} + +static void ospi_tx_fifo_push_stig_rd_data(XlnxVersalOspi *s) +{ + int rd_data_len; + int i; + + if (ARRAY_FIELD_EX32(s->regs, FLASH_CMD_CTRL_REG, STIG_MEM_BANK_EN_FLD)) { + rd_data_len = ospi_stig_membank_rd_bytes(s); + } else { + rd_data_len = ospi_stig_rd_data_len(s); + } + + /* transmit second part (data) */ + for (i = 0; i < rd_data_len; ++i) { + fifo8_push(&s->tx_fifo, 0); + } +} + +static void ospi_rx_fifo_pop_stig_rd_data(XlnxVersalOspi *s) +{ + int size = ospi_stig_rd_data_len(s); + OSPIRdData res = {}; + int i; + + size = MIN(fifo8_num_used(&s->rx_fifo), size); + for (i = 0; i < size; i++) { + res.u8[i] = fifo8_pop(&s->rx_fifo); + } + + s->regs[R_FLASH_RD_DATA_LOWER_REG] = res.u64 & 0xFFFFFFFF; + s->regs[R_FLASH_RD_DATA_UPPER_REG] = (res.u64 >> 32) & 0xFFFFFFFF; +} + +static void ospi_ind_read(XlnxVersalOspi *s, uint32_t flash_addr, uint32_t len) +{ + int i; + + /* Create first section of read cmd */ + ospi_tx_fifo_push_rd_op_addr(s, flash_addr); + + /* transmit first part */ + ospi_update_cs_lines(s); + ospi_flush_txfifo(s); + + fifo8_reset(&s->rx_fifo); + + /* transmit second part (data) */ + for (i = 0; i < len; ++i) { + fifo8_push(&s->tx_fifo, 0); + } + ospi_flush_txfifo(s); + + for (i = 0; i < len; ++i) { + fifo8_push(&s->rx_sram, fifo8_pop(&s->rx_fifo)); + } + + /* done */ + ospi_disable_cs(s); +} + +static unsigned int ospi_dma_burst_size(XlnxVersalOspi *s) +{ + return 1 << ARRAY_FIELD_EX32(s->regs, + DMA_PERIPH_CONFIG_REG, + NUM_BURST_REQ_BYTES_FLD); +} + +static unsigned int ospi_dma_single_size(XlnxVersalOspi *s) +{ + return 1 << ARRAY_FIELD_EX32(s->regs, + DMA_PERIPH_CONFIG_REG, + NUM_SINGLE_REQ_BYTES_FLD); +} + +static void ind_rd_inc_num_done(XlnxVersalOspi *s) +{ + unsigned int done = ARRAY_FIELD_EX32(s->regs, + INDIRECT_READ_XFER_CTRL_REG, + NUM_IND_OPS_DONE_FLD); + if (done < IND_OPS_DONE_MAX) { + done++; + } + done &= 0x3; + ARRAY_FIELD_DP32(s->regs, INDIRECT_READ_XFER_CTRL_REG, + NUM_IND_OPS_DONE_FLD, done); +} + +static void ospi_ind_rd_completed(XlnxVersalOspi *s) +{ + ARRAY_FIELD_DP32(s->regs, INDIRECT_READ_XFER_CTRL_REG, + IND_OPS_DONE_STATUS_FLD, 1); + + ind_rd_inc_num_done(s); + ospi_ind_op_next(s->rd_ind_op); + if (ospi_ind_op_all_completed(s)) { + set_irq(s, R_IRQ_STATUS_REG_INDIRECT_OP_DONE_FLD_MASK); + } +} + +static uint32_t get_ind_rd_dma_len(XlnxVersalOspi *s, IndOp *op) +{ + uint32_t len = 0; + if (ARRAY_FIELD_EX32(s->regs, CONFIG_REG, ENB_DMA_IF_FLD)) { + if (fifo8_num_used(&s->rx_sram) < + ospi_dma_burst_size(s)) { + len = ospi_dma_single_size(s); + } else { + len = ospi_dma_burst_size(s); + } + } + return len; +} + +static void ospi_notify(void *opaque); + +static void ospi_dma_read(XlnxVersalOspi *s, bool start_dma) +{ + IndOp *op = s->rd_ind_op; + uint32_t dma_len; + uint32_t flush_bytes = fifo8_num_used(&s->rx_sram); + DmaCtrlNotify notify = { .cb = ospi_notify, + .opaque = (void *)s, + }; + + if (flush_bytes && !s->src_dma_inprog) { + dma_len = get_ind_rd_dma_len(s, op); + /* + * Source dma accesses SRAM at address 0 (at its own addresss space). + */ + dma_ctrl_read_with_notify(s->dma_src, 0, dma_len, ¬ify, start_dma); + + /* + * ospi_dma_read is called for every call of ospi_notify + * in that case, by the time we are here, if flush_bytes is not zero + * then we have pending dma transactions. + */ + flush_bytes = fifo8_num_used(&s->rx_sram); + if (flush_bytes) { + s->src_dma_inprog = true; + } + } +} + +static void ospi_do_ind_read(XlnxVersalOspi *s) +{ + IndOp *op = s->rd_ind_op; + uint32_t next_b; + uint32_t end_b; + uint32_t len; + bool start_dma = IS_IND_DMA_START(op); + + /* Continue to read flash until we run out of space in sram */ + while (!ospi_ind_op_completed(op) && + !fifo8_is_full(&s->rx_sram)) { + /* Read reqested number of bytes, max bytes limited to size of sram */ + next_b = ind_op_next_byte(op); + end_b = next_b + fifo8_num_free(&s->rx_sram); + end_b = MIN(end_b, ind_op_end_byte(op)); + + len = end_b - next_b; + ospi_ind_read(s, next_b, len); + ind_op_advance(op, len); + + if (ospi_ind_rd_watermark_enabled(s)) { + ARRAY_FIELD_DP32(s->regs, IRQ_STATUS_REG, + INDIRECT_XFER_LEVEL_BREACH_FLD, 1); + set_irq(s, + R_IRQ_STATUS_REG_INDIRECT_XFER_LEVEL_BREACH_FLD_MASK); + } + + if (!s->src_dma_inprog && + ARRAY_FIELD_EX32(s->regs, CONFIG_REG, ENB_DMA_IF_FLD)) { + ospi_dma_read(s, start_dma); + } + } + + /* Set sram full */ + if (fifo8_num_used(&s->rx_sram) == RXFF_SZ) { + ARRAY_FIELD_DP32(s->regs, + INDIRECT_READ_XFER_CTRL_REG, SRAM_FULL_FLD, 1); + set_irq(s, R_IRQ_STATUS_REG_INDRD_SRAM_FULL_FLD_MASK); + } + + /* Signal completion if done, unless inside recursion via ospi_dma_read */ + if (!ARRAY_FIELD_EX32(s->regs, CONFIG_REG, ENB_DMA_IF_FLD) || start_dma) { + if (ospi_ind_op_completed(op)) { + ospi_ind_rd_completed(s); + } + } +} + +static void ospi_notify(void *opaque) +{ + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(opaque); + + s->src_dma_inprog = false; + ospi_dma_read(s, false); + if (!ospi_ind_op_completed(s->rd_ind_op)) { + ospi_do_ind_read(s); + } +} + +/* Transmit write enable instruction */ +static void ospi_transmit_wel(XlnxVersalOspi *s, bool ahb_decoder_cs, + hwaddr addr) +{ + fifo8_reset(&s->tx_fifo); + fifo8_push(&s->tx_fifo, WREN); + + if (ahb_decoder_cs) { + ospi_ahb_decoder_enable_cs(s, addr); + } else { + ospi_update_cs_lines(s); + } + + ospi_flush_txfifo(s); + ospi_disable_cs(s); + + fifo8_reset(&s->rx_fifo); +} + +static void ospi_ind_write(XlnxVersalOspi *s, uint32_t flash_addr, uint32_t len) +{ + bool ahb_decoder_cs = false; + uint8_t inst_code; + int i; + + assert(fifo8_num_used(&s->tx_sram) >= len); + + if (!ARRAY_FIELD_EX32(s->regs, DEV_INSTR_WR_CONFIG_REG, WEL_DIS_FLD)) { + ospi_transmit_wel(s, ahb_decoder_cs, 0); + } + + /* reset fifos */ + fifo8_reset(&s->tx_fifo); + fifo8_reset(&s->rx_fifo); + + /* Push write opcode */ + inst_code = ospi_get_wr_opcode(s); + fifo8_push(&s->tx_fifo, inst_code); + + /* Push write address */ + ospi_tx_fifo_push_address(s, flash_addr); + + /* data */ + for (i = 0; i < len; i++) { + fifo8_push(&s->tx_fifo, fifo8_pop(&s->tx_sram)); + } + + /* transmit */ + ospi_update_cs_lines(s); + ospi_flush_txfifo(s); + + /* done */ + ospi_disable_cs(s); + fifo8_reset(&s->rx_fifo); +} + +static void ind_wr_inc_num_done(XlnxVersalOspi *s) +{ + unsigned int done = ARRAY_FIELD_EX32(s->regs, INDIRECT_WRITE_XFER_CTRL_REG, + NUM_IND_OPS_DONE_FLD); + if (done < IND_OPS_DONE_MAX) { + done++; + } + done &= 0x3; + ARRAY_FIELD_DP32(s->regs, INDIRECT_WRITE_XFER_CTRL_REG, + NUM_IND_OPS_DONE_FLD, done); +} + +static void ospi_ind_wr_completed(XlnxVersalOspi *s) +{ + ARRAY_FIELD_DP32(s->regs, INDIRECT_WRITE_XFER_CTRL_REG, + IND_OPS_DONE_STATUS_FLD, 1); + ind_wr_inc_num_done(s); + ospi_ind_op_next(s->wr_ind_op); + /* Set indirect op done interrupt if enabled */ + if (ospi_ind_op_all_completed(s)) { + set_irq(s, R_IRQ_STATUS_REG_INDIRECT_OP_DONE_FLD_MASK); + } +} + +static void ospi_do_indirect_write(XlnxVersalOspi *s) +{ + uint32_t write_watermark = s->regs[R_INDIRECT_WRITE_XFER_WATERMARK_REG]; + uint32_t pagesz = ospi_get_page_sz(s); + uint32_t page_mask = ~(pagesz - 1); + IndOp *op = s->wr_ind_op; + uint32_t next_b; + uint32_t end_b; + uint32_t len; + + /* Write out tx_fifo in maximum page sz chunks */ + while (!ospi_ind_op_completed(op) && fifo8_num_used(&s->tx_sram) > 0) { + next_b = ind_op_next_byte(op); + end_b = next_b + MIN(fifo8_num_used(&s->tx_sram), pagesz); + + /* Dont cross page boundery */ + if ((end_b & page_mask) > next_b) { + end_b &= page_mask; + } + + len = end_b - next_b; + len = MIN(len, op->num_bytes - op->done_bytes); + ospi_ind_write(s, next_b, len); + ind_op_advance(op, len); + } + + /* + * Always set indirect transfer level breached interrupt if enabled + * (write watermark > 0) since the tx_sram always will be emptied + */ + if (write_watermark > 0) { + set_irq(s, R_IRQ_STATUS_REG_INDIRECT_XFER_LEVEL_BREACH_FLD_MASK); + } + + /* Signal completions if done */ + if (ospi_ind_op_completed(op)) { + ospi_ind_wr_completed(s); + } +} + +static void ospi_stig_fill_membank(XlnxVersalOspi *s) +{ + int num_rd_bytes = ospi_stig_membank_rd_bytes(s); + int idx = num_rd_bytes - 8; /* first of last 8 */ + uint32_t lower = 0; + uint32_t upper = 0; + int i; + + for (i = 0; i < num_rd_bytes; i++) { + s->stig_membank[i] = fifo8_pop(&s->rx_fifo); + } + + /* Fill in lower upper regs */ + for (i = 0; i < 4; i++) { + lower |= ((uint32_t)s->stig_membank[idx++]) << 8 * i; + } + + for (i = 0; i < 4; i++) { + upper |= ((uint32_t)s->stig_membank[idx++]) << 8 * i; + } + + s->regs[R_FLASH_RD_DATA_LOWER_REG] = lower; + s->regs[R_FLASH_RD_DATA_UPPER_REG] = upper; +} + +static void ospi_stig_cmd_exec(XlnxVersalOspi *s) +{ + uint8_t inst_code; + + /* Reset fifos */ + fifo8_reset(&s->tx_fifo); + fifo8_reset(&s->rx_fifo); + + /* Push write opcode */ + inst_code = ARRAY_FIELD_EX32(s->regs, FLASH_CMD_CTRL_REG, CMD_OPCODE_FLD); + fifo8_push(&s->tx_fifo, inst_code); + + /* Push address if enabled */ + if (ARRAY_FIELD_EX32(s->regs, FLASH_CMD_CTRL_REG, ENB_COMD_ADDR_FLD)) { + ospi_tx_fifo_push_stig_addr(s); + } + + /* Enable cs */ + ospi_update_cs_lines(s); + + /* Data */ + if (ARRAY_FIELD_EX32(s->regs, FLASH_CMD_CTRL_REG, ENB_WRITE_DATA_FLD)) { + ospi_tx_fifo_push_stig_wr_data(s); + } else if (ARRAY_FIELD_EX32(s->regs, + FLASH_CMD_CTRL_REG, ENB_READ_DATA_FLD)) { + /* transmit first part */ + ospi_flush_txfifo(s); + fifo8_reset(&s->rx_fifo); + ospi_tx_fifo_push_stig_rd_data(s); + } + + /* Transmit */ + ospi_flush_txfifo(s); + ospi_disable_cs(s); + + if (ARRAY_FIELD_EX32(s->regs, FLASH_CMD_CTRL_REG, ENB_READ_DATA_FLD)) { + if (ARRAY_FIELD_EX32(s->regs, + FLASH_CMD_CTRL_REG, STIG_MEM_BANK_EN_FLD)) { + ospi_stig_fill_membank(s); + } else { + ospi_rx_fifo_pop_stig_rd_data(s); + } + } +} + +static uint32_t ospi_block_address(XlnxVersalOspi *s, unsigned int block) +{ + unsigned int block_sz = ospi_get_block_sz(s); + unsigned int cs = 0; + uint32_t addr = 0; + + while (cs < s->num_cs && block >= flash_blocks(s, cs)) { + block -= flash_blocks(s, 0); + addr += flash_sz(s, cs); + } + addr += block * block_sz; + return addr; +} + +static uint32_t ospi_get_wr_prot_addr_low(XlnxVersalOspi *s) +{ + unsigned int block = s->regs[R_LOWER_WR_PROT_REG]; + + return ospi_block_address(s, block); +} + +static uint32_t ospi_get_wr_prot_addr_upper(XlnxVersalOspi *s) +{ + unsigned int block = s->regs[R_UPPER_WR_PROT_REG]; + + /* Get address of first block out of defined range */ + return ospi_block_address(s, block + 1); +} + +static bool ospi_is_write_protected(XlnxVersalOspi *s, hwaddr addr) +{ + uint32_t wr_prot_addr_upper = ospi_get_wr_prot_addr_upper(s); + uint32_t wr_prot_addr_low = ospi_get_wr_prot_addr_low(s); + bool in_range = false; + + if (addr >= wr_prot_addr_low && addr < wr_prot_addr_upper) { + in_range = true; + } + + if (ARRAY_FIELD_EX32(s->regs, WR_PROT_CTRL_REG, INV_FLD)) { + in_range = !in_range; + } + return in_range; +} + +static uint64_t ospi_rx_sram_read(XlnxVersalOspi *s, unsigned int size) +{ + OSPIRdData ret = {}; + int i; + + if (size < 4 && fifo8_num_used(&s->rx_sram) >= 4) { + qemu_log_mask(LOG_GUEST_ERROR, + "OSPI only last read of internal " + "sram is allowed to be < 32 bits\n"); + } + + size = MIN(fifo8_num_used(&s->rx_sram), size); + for (i = 0; i < size; i++) { + ret.u8[i] = fifo8_pop(&s->rx_sram); + } + return ret.u64; +} + +static void ospi_tx_sram_write(XlnxVersalOspi *s, uint64_t value, + unsigned int size) +{ + int i; + for (i = 0; i < size; i++) { + fifo8_push(&s->tx_sram, value >> 8 * i); + } +} + +static uint64_t ospi_do_dac_read(void *opaque, hwaddr addr, unsigned int size) +{ + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(opaque); + OSPIRdData ret = {}; + int i; + + /* Create first section of read cmd */ + ospi_tx_fifo_push_rd_op_addr(s, (uint32_t) addr); + + /* Enable cs and transmit first part */ + ospi_dac_cs(s, addr); + ospi_flush_txfifo(s); + + fifo8_reset(&s->rx_fifo); + + /* transmit second part (data) */ + for (i = 0; i < size; ++i) { + fifo8_push(&s->tx_fifo, 0); + } + ospi_flush_txfifo(s); + + /* fill in result */ + size = MIN(fifo8_num_used(&s->rx_fifo), size); + + for (i = 0; i < size; i++) { + ret.u8[i] = fifo8_pop(&s->rx_fifo); + } + + /* done */ + ospi_disable_cs(s); + + return ret.u64; +} + +static void ospi_do_dac_write(void *opaque, + hwaddr addr, + uint64_t value, + unsigned int size) +{ + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(opaque); + bool ahb_decoder_cs = ARRAY_FIELD_EX32(s->regs, CONFIG_REG, + ENABLE_AHB_DECODER_FLD); + uint8_t inst_code; + unsigned int i; + + if (!ARRAY_FIELD_EX32(s->regs, DEV_INSTR_WR_CONFIG_REG, WEL_DIS_FLD)) { + ospi_transmit_wel(s, ahb_decoder_cs, addr); + } + + /* reset fifos */ + fifo8_reset(&s->tx_fifo); + fifo8_reset(&s->rx_fifo); + + /* Push write opcode */ + inst_code = ospi_get_wr_opcode(s); + fifo8_push(&s->tx_fifo, inst_code); + + /* Push write address */ + ospi_tx_fifo_push_address(s, addr); + + /* data */ + for (i = 0; i < size; i++) { + fifo8_push(&s->tx_fifo, value >> 8 * i); + } + + /* Enable cs and transmit */ + ospi_dac_cs(s, addr); + ospi_flush_txfifo(s); + ospi_disable_cs(s); + + fifo8_reset(&s->rx_fifo); +} + +static void flash_cmd_ctrl_mem_reg_post_write(RegisterInfo *reg, + uint64_t val) +{ + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(reg->opaque); + if (ARRAY_FIELD_EX32(s->regs, CONFIG_REG, ENB_SPI_FLD)) { + if (ARRAY_FIELD_EX32(s->regs, + FLASH_COMMAND_CTRL_MEM_REG, + TRIGGER_MEM_BANK_REQ_FLD)) { + ospi_stig_membank_req(s); + ARRAY_FIELD_DP32(s->regs, FLASH_COMMAND_CTRL_MEM_REG, + TRIGGER_MEM_BANK_REQ_FLD, 0); + } + } +} + +static void flash_cmd_ctrl_reg_post_write(RegisterInfo *reg, uint64_t val) +{ + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(reg->opaque); + + if (ARRAY_FIELD_EX32(s->regs, CONFIG_REG, ENB_SPI_FLD) && + ARRAY_FIELD_EX32(s->regs, FLASH_CMD_CTRL_REG, CMD_EXEC_FLD)) { + ospi_stig_cmd_exec(s); + set_irq(s, R_IRQ_STATUS_REG_STIG_REQ_INT_FLD_MASK); + ARRAY_FIELD_DP32(s->regs, FLASH_CMD_CTRL_REG, CMD_EXEC_FLD, 0); + } +} + +static uint64_t ind_wr_dec_num_done(XlnxVersalOspi *s, uint64_t val) +{ + unsigned int done = ARRAY_FIELD_EX32(s->regs, INDIRECT_WRITE_XFER_CTRL_REG, + NUM_IND_OPS_DONE_FLD); + done--; + done &= 0x3; + val = FIELD_DP32(val, INDIRECT_WRITE_XFER_CTRL_REG, + NUM_IND_OPS_DONE_FLD, done); + return val; +} + +static bool ind_wr_clearing_op_done(XlnxVersalOspi *s, uint64_t new_val) +{ + bool set_in_reg = ARRAY_FIELD_EX32(s->regs, INDIRECT_WRITE_XFER_CTRL_REG, + IND_OPS_DONE_STATUS_FLD); + bool set_in_new_val = FIELD_EX32(new_val, INDIRECT_WRITE_XFER_CTRL_REG, + IND_OPS_DONE_STATUS_FLD); + /* return true if clearing bit */ + return set_in_reg && !set_in_new_val; +} + +static uint64_t ind_wr_xfer_ctrl_reg_pre_write(RegisterInfo *reg, + uint64_t val) +{ + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(reg->opaque); + + if (ind_wr_clearing_op_done(s, val)) { + val = ind_wr_dec_num_done(s, val); + } + return val; +} + +static void ind_wr_xfer_ctrl_reg_post_write(RegisterInfo *reg, uint64_t val) +{ + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(reg->opaque); + + if (s->ind_write_disabled) { + return; + } + + if (ARRAY_FIELD_EX32(s->regs, INDIRECT_WRITE_XFER_CTRL_REG, START_FLD)) { + ospi_ind_op_queue_up_wr(s); + ospi_do_indirect_write(s); + ARRAY_FIELD_DP32(s->regs, INDIRECT_WRITE_XFER_CTRL_REG, START_FLD, 0); + } + + if (ARRAY_FIELD_EX32(s->regs, INDIRECT_WRITE_XFER_CTRL_REG, CANCEL_FLD)) { + ospi_ind_op_cancel(s->wr_ind_op); + fifo8_reset(&s->tx_sram); + ARRAY_FIELD_DP32(s->regs, INDIRECT_WRITE_XFER_CTRL_REG, CANCEL_FLD, 0); + } +} + +static uint64_t ind_wr_xfer_ctrl_reg_post_read(RegisterInfo *reg, + uint64_t val) +{ + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(reg->opaque); + IndOp *op = s->wr_ind_op; + + /* Check if ind ops is ongoing */ + if (!ospi_ind_op_completed(&op[0])) { + /* Check if two ind ops are queued */ + if (!ospi_ind_op_completed(&op[1])) { + val = FIELD_DP32(val, INDIRECT_WRITE_XFER_CTRL_REG, + WR_QUEUED_FLD, 1); + } + val = FIELD_DP32(val, INDIRECT_WRITE_XFER_CTRL_REG, WR_STATUS_FLD, 1); + } + return val; +} + +static uint64_t ind_rd_dec_num_done(XlnxVersalOspi *s, uint64_t val) +{ + unsigned int done = ARRAY_FIELD_EX32(s->regs, INDIRECT_READ_XFER_CTRL_REG, + NUM_IND_OPS_DONE_FLD); + done--; + done &= 0x3; + val = FIELD_DP32(val, INDIRECT_READ_XFER_CTRL_REG, + NUM_IND_OPS_DONE_FLD, done); + return val; +} + +static uint64_t ind_rd_xfer_ctrl_reg_pre_write(RegisterInfo *reg, + uint64_t val) +{ + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(reg->opaque); + + if (FIELD_EX32(val, INDIRECT_READ_XFER_CTRL_REG, + IND_OPS_DONE_STATUS_FLD)) { + val = ind_rd_dec_num_done(s, val); + val &= ~R_INDIRECT_READ_XFER_CTRL_REG_IND_OPS_DONE_STATUS_FLD_MASK; + } + return val; +} + +static void ind_rd_xfer_ctrl_reg_post_write(RegisterInfo *reg, uint64_t val) +{ + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(reg->opaque); + + if (ARRAY_FIELD_EX32(s->regs, INDIRECT_READ_XFER_CTRL_REG, START_FLD)) { + ospi_ind_op_queue_up_rd(s); + ospi_do_ind_read(s); + ARRAY_FIELD_DP32(s->regs, INDIRECT_READ_XFER_CTRL_REG, START_FLD, 0); + } + + if (ARRAY_FIELD_EX32(s->regs, INDIRECT_READ_XFER_CTRL_REG, CANCEL_FLD)) { + ospi_ind_op_cancel(s->rd_ind_op); + fifo8_reset(&s->rx_sram); + ARRAY_FIELD_DP32(s->regs, INDIRECT_READ_XFER_CTRL_REG, CANCEL_FLD, 0); + } +} + +static uint64_t ind_rd_xfer_ctrl_reg_post_read(RegisterInfo *reg, + uint64_t val) +{ + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(reg->opaque); + IndOp *op = s->rd_ind_op; + + /* Check if ind ops is ongoing */ + if (!ospi_ind_op_completed(&op[0])) { + /* Check if two ind ops are queued */ + if (!ospi_ind_op_completed(&op[1])) { + val = FIELD_DP32(val, INDIRECT_READ_XFER_CTRL_REG, + RD_QUEUED_FLD, 1); + } + val = FIELD_DP32(val, INDIRECT_READ_XFER_CTRL_REG, RD_STATUS_FLD, 1); + } + return val; +} + +static uint64_t sram_fill_reg_post_read(RegisterInfo *reg, uint64_t val) +{ + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(reg->opaque); + val = ((fifo8_num_used(&s->tx_sram) & 0xFFFF) << 16) | + (fifo8_num_used(&s->rx_sram) & 0xFFFF); + return val; +} + +static uint64_t dll_obs_upper_reg_post_read(RegisterInfo *reg, uint64_t val) +{ + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(reg->opaque); + uint32_t rx_dec_out; + + rx_dec_out = FIELD_EX32(val, DLL_OBSERVABLE_UPPER_REG, + DLL_OBSERVABLE__UPPER_RX_DECODER_OUTPUT_FLD); + + if (rx_dec_out < MAX_RX_DEC_OUT) { + ARRAY_FIELD_DP32(s->regs, DLL_OBSERVABLE_UPPER_REG, + DLL_OBSERVABLE__UPPER_RX_DECODER_OUTPUT_FLD, + rx_dec_out + 1); + } + + return val; +} + + +static void xlnx_versal_ospi_reset(DeviceState *dev) +{ + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(dev); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(s->regs_info); ++i) { + register_reset(&s->regs_info[i]); + } + + fifo8_reset(&s->rx_fifo); + fifo8_reset(&s->tx_fifo); + fifo8_reset(&s->rx_sram); + fifo8_reset(&s->tx_sram); + + s->rd_ind_op[0].completed = true; + s->rd_ind_op[1].completed = true; + s->wr_ind_op[0].completed = true; + s->wr_ind_op[1].completed = true; + ARRAY_FIELD_DP32(s->regs, DLL_OBSERVABLE_LOWER_REG, + DLL_OBSERVABLE_LOWER_DLL_LOCK_FLD, 1); + ARRAY_FIELD_DP32(s->regs, DLL_OBSERVABLE_LOWER_REG, + DLL_OBSERVABLE_LOWER_LOOPBACK_LOCK_FLD, 1); +} + +static RegisterAccessInfo ospi_regs_info[] = { + { .name = "CONFIG_REG", + .addr = A_CONFIG_REG, + .reset = 0x80780081, + .ro = 0x9c000000, + },{ .name = "DEV_INSTR_RD_CONFIG_REG", + .addr = A_DEV_INSTR_RD_CONFIG_REG, + .reset = 0x3, + .ro = 0xe0ecc800, + },{ .name = "DEV_INSTR_WR_CONFIG_REG", + .addr = A_DEV_INSTR_WR_CONFIG_REG, + .reset = 0x2, + .ro = 0xe0fcce00, + },{ .name = "DEV_DELAY_REG", + .addr = A_DEV_DELAY_REG, + },{ .name = "RD_DATA_CAPTURE_REG", + .addr = A_RD_DATA_CAPTURE_REG, + .reset = 0x1, + .ro = 0xfff0fec0, + },{ .name = "DEV_SIZE_CONFIG_REG", + .addr = A_DEV_SIZE_CONFIG_REG, + .reset = 0x101002, + .ro = 0xe0000000, + },{ .name = "SRAM_PARTITION_CFG_REG", + .addr = A_SRAM_PARTITION_CFG_REG, + .reset = 0x80, + .ro = 0xffffff00, + },{ .name = "IND_AHB_ADDR_TRIGGER_REG", + .addr = A_IND_AHB_ADDR_TRIGGER_REG, + },{ .name = "DMA_PERIPH_CONFIG_REG", + .addr = A_DMA_PERIPH_CONFIG_REG, + .ro = 0xfffff0f0, + },{ .name = "REMAP_ADDR_REG", + .addr = A_REMAP_ADDR_REG, + },{ .name = "MODE_BIT_CONFIG_REG", + .addr = A_MODE_BIT_CONFIG_REG, + .reset = 0x200, + .ro = 0xffff7800, + },{ .name = "SRAM_FILL_REG", + .addr = A_SRAM_FILL_REG, + .ro = 0xffffffff, + .post_read = sram_fill_reg_post_read, + },{ .name = "TX_THRESH_REG", + .addr = A_TX_THRESH_REG, + .reset = 0x1, + .ro = 0xffffffe0, + },{ .name = "RX_THRESH_REG", + .addr = A_RX_THRESH_REG, + .reset = 0x1, + .ro = 0xffffffe0, + },{ .name = "WRITE_COMPLETION_CTRL_REG", + .addr = A_WRITE_COMPLETION_CTRL_REG, + .reset = 0x10005, + .ro = 0x1800, + },{ .name = "NO_OF_POLLS_BEF_EXP_REG", + .addr = A_NO_OF_POLLS_BEF_EXP_REG, + .reset = 0xffffffff, + },{ .name = "IRQ_STATUS_REG", + .addr = A_IRQ_STATUS_REG, + .ro = 0xfff08000, + .w1c = 0xf7fff, + },{ .name = "IRQ_MASK_REG", + .addr = A_IRQ_MASK_REG, + .ro = 0xfff08000, + },{ .name = "LOWER_WR_PROT_REG", + .addr = A_LOWER_WR_PROT_REG, + },{ .name = "UPPER_WR_PROT_REG", + .addr = A_UPPER_WR_PROT_REG, + },{ .name = "WR_PROT_CTRL_REG", + .addr = A_WR_PROT_CTRL_REG, + .ro = 0xfffffffc, + },{ .name = "INDIRECT_READ_XFER_CTRL_REG", + .addr = A_INDIRECT_READ_XFER_CTRL_REG, + .ro = 0xffffffd4, + .w1c = 0x08, + .pre_write = ind_rd_xfer_ctrl_reg_pre_write, + .post_write = ind_rd_xfer_ctrl_reg_post_write, + .post_read = ind_rd_xfer_ctrl_reg_post_read, + },{ .name = "INDIRECT_READ_XFER_WATERMARK_REG", + .addr = A_INDIRECT_READ_XFER_WATERMARK_REG, + },{ .name = "INDIRECT_READ_XFER_START_REG", + .addr = A_INDIRECT_READ_XFER_START_REG, + },{ .name = "INDIRECT_READ_XFER_NUM_BYTES_REG", + .addr = A_INDIRECT_READ_XFER_NUM_BYTES_REG, + },{ .name = "INDIRECT_WRITE_XFER_CTRL_REG", + .addr = A_INDIRECT_WRITE_XFER_CTRL_REG, + .ro = 0xffffffdc, + .w1c = 0x20, + .pre_write = ind_wr_xfer_ctrl_reg_pre_write, + .post_write = ind_wr_xfer_ctrl_reg_post_write, + .post_read = ind_wr_xfer_ctrl_reg_post_read, + },{ .name = "INDIRECT_WRITE_XFER_WATERMARK_REG", + .addr = A_INDIRECT_WRITE_XFER_WATERMARK_REG, + .reset = 0xffffffff, + },{ .name = "INDIRECT_WRITE_XFER_START_REG", + .addr = A_INDIRECT_WRITE_XFER_START_REG, + },{ .name = "INDIRECT_WRITE_XFER_NUM_BYTES_REG", + .addr = A_INDIRECT_WRITE_XFER_NUM_BYTES_REG, + },{ .name = "INDIRECT_TRIGGER_ADDR_RANGE_REG", + .addr = A_INDIRECT_TRIGGER_ADDR_RANGE_REG, + .reset = 0x4, + .ro = 0xfffffff0, + },{ .name = "FLASH_COMMAND_CTRL_MEM_REG", + .addr = A_FLASH_COMMAND_CTRL_MEM_REG, + .ro = 0xe008fffe, + .post_write = flash_cmd_ctrl_mem_reg_post_write, + },{ .name = "FLASH_CMD_CTRL_REG", + .addr = A_FLASH_CMD_CTRL_REG, + .ro = 0x7a, + .post_write = flash_cmd_ctrl_reg_post_write, + },{ .name = "FLASH_CMD_ADDR_REG", + .addr = A_FLASH_CMD_ADDR_REG, + },{ .name = "FLASH_RD_DATA_LOWER_REG", + .addr = A_FLASH_RD_DATA_LOWER_REG, + .ro = 0xffffffff, + },{ .name = "FLASH_RD_DATA_UPPER_REG", + .addr = A_FLASH_RD_DATA_UPPER_REG, + .ro = 0xffffffff, + },{ .name = "FLASH_WR_DATA_LOWER_REG", + .addr = A_FLASH_WR_DATA_LOWER_REG, + },{ .name = "FLASH_WR_DATA_UPPER_REG", + .addr = A_FLASH_WR_DATA_UPPER_REG, + },{ .name = "POLLING_FLASH_STATUS_REG", + .addr = A_POLLING_FLASH_STATUS_REG, + .ro = 0xfff0ffff, + },{ .name = "PHY_CONFIGURATION_REG", + .addr = A_PHY_CONFIGURATION_REG, + .reset = 0x40000000, + .ro = 0x1f80ff80, + },{ .name = "PHY_MASTER_CONTROL_REG", + .addr = A_PHY_MASTER_CONTROL_REG, + .reset = 0x800000, + .ro = 0xfe08ff80, + },{ .name = "DLL_OBSERVABLE_LOWER_REG", + .addr = A_DLL_OBSERVABLE_LOWER_REG, + .ro = 0xffffffff, + },{ .name = "DLL_OBSERVABLE_UPPER_REG", + .addr = A_DLL_OBSERVABLE_UPPER_REG, + .ro = 0xffffffff, + .post_read = dll_obs_upper_reg_post_read, + },{ .name = "OPCODE_EXT_LOWER_REG", + .addr = A_OPCODE_EXT_LOWER_REG, + .reset = 0x13edfa00, + },{ .name = "OPCODE_EXT_UPPER_REG", + .addr = A_OPCODE_EXT_UPPER_REG, + .reset = 0x6f90000, + .ro = 0xffff, + },{ .name = "MODULE_ID_REG", + .addr = A_MODULE_ID_REG, + .reset = 0x300, + .ro = 0xffffffff, + } +}; + +/* Return dev-obj from reg-region created by register_init_block32 */ +static XlnxVersalOspi *xilinx_ospi_of_mr(void *mr_accessor) +{ + RegisterInfoArray *reg_array = mr_accessor; + Object *dev; + + assert(reg_array != NULL); + + dev = reg_array->mem.owner; + assert(dev); + + return XILINX_VERSAL_OSPI(dev); +} + +static void ospi_write(void *opaque, hwaddr addr, uint64_t value, + unsigned int size) +{ + XlnxVersalOspi *s = xilinx_ospi_of_mr(opaque); + + register_write_memory(opaque, addr, value, size); + ospi_update_irq_line(s); +} + +static const MemoryRegionOps ospi_ops = { + .read = register_read_memory, + .write = ospi_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static uint64_t ospi_indac_read(void *opaque, unsigned int size) +{ + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(opaque); + uint64_t ret = ospi_rx_sram_read(s, size); + + if (!ARRAY_FIELD_EX32(s->regs, CONFIG_REG, ENB_DMA_IF_FLD) && + !ospi_ind_op_completed(s->rd_ind_op)) { + ospi_do_ind_read(s); + } + return ret; +} + +static void ospi_indac_write(void *opaque, uint64_t value, unsigned int size) +{ + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(opaque); + + if (s->ind_write_disabled) { + g_assert_not_reached(); + } + + if (!ospi_ind_op_completed(s->wr_ind_op)) { + ospi_tx_sram_write(s, value, size); + ospi_do_indirect_write(s); + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "OSPI wr into indac area while no ongoing indac wr\n"); + } +} + +static bool is_inside_indac_range(XlnxVersalOspi *s, hwaddr addr) +{ + uint32_t range_start = s->regs[R_IND_AHB_ADDR_TRIGGER_REG]; + uint32_t range_end = range_start + + (1 << ARRAY_FIELD_EX32(s->regs, + INDIRECT_TRIGGER_ADDR_RANGE_REG, + IND_RANGE_WIDTH_FLD)); + + if (ARRAY_FIELD_EX32(s->regs, CONFIG_REG, ENB_DMA_IF_FLD)) { + addr += s->regs[R_IND_AHB_ADDR_TRIGGER_REG]; + } else { + addr += s->regs[R_IND_AHB_ADDR_TRIGGER_REG] & 0xF0000000; + } + + return addr >= range_start && addr < range_end; +} + +static bool ospi_is_indac_active(XlnxVersalOspi *s) +{ + /* + * When dac and indac cannot be active at the same time, + * return true when dac is disabled. + */ + return s->dac_with_indac || !s->dac_enable; +} + +static uint64_t ospi_dac_read(void *opaque, hwaddr addr, unsigned int size) +{ + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(opaque); + + if (ARRAY_FIELD_EX32(s->regs, CONFIG_REG, ENB_SPI_FLD)) { + if (ospi_is_indac_active(s) && + is_inside_indac_range(s, addr)) { + return ospi_indac_read(s, size); + } + if (ARRAY_FIELD_EX32(s->regs, CONFIG_REG, ENB_DIR_ACC_CTLR_FLD) + && s->dac_enable) { + if (ARRAY_FIELD_EX32(s->regs, + CONFIG_REG, ENB_AHB_ADDR_REMAP_FLD)) { + addr += s->regs[R_REMAP_ADDR_REG]; + } + return ospi_do_dac_read(opaque, addr, size); + } else { + qemu_log_mask(LOG_GUEST_ERROR, "OSPI AHB rd while DAC disabled\n"); + } + } else { + qemu_log_mask(LOG_GUEST_ERROR, "OSPI AHB rd while OSPI disabled\n"); + } + + return 0; +} + +static void ospi_dac_write(void *opaque, hwaddr addr, uint64_t value, + unsigned int size) +{ + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(opaque); + + if (ARRAY_FIELD_EX32(s->regs, CONFIG_REG, ENB_SPI_FLD)) { + if (ospi_is_indac_active(s) && + !s->ind_write_disabled && + is_inside_indac_range(s, addr)) { + return ospi_indac_write(s, value, size); + } + if (ARRAY_FIELD_EX32(s->regs, CONFIG_REG, ENB_DIR_ACC_CTLR_FLD) && + s->dac_enable) { + if (ARRAY_FIELD_EX32(s->regs, + CONFIG_REG, ENB_AHB_ADDR_REMAP_FLD)) { + addr += s->regs[R_REMAP_ADDR_REG]; + } + /* Check if addr is write protected */ + if (ARRAY_FIELD_EX32(s->regs, WR_PROT_CTRL_REG, ENB_FLD) && + ospi_is_write_protected(s, addr)) { + set_irq(s, R_IRQ_STATUS_REG_PROT_WR_ATTEMPT_FLD_MASK); + ospi_update_irq_line(s); + qemu_log_mask(LOG_GUEST_ERROR, + "OSPI writing into write protected area\n"); + return; + } + ospi_do_dac_write(opaque, addr, value, size); + } else { + qemu_log_mask(LOG_GUEST_ERROR, "OSPI AHB wr while DAC disabled\n"); + } + } else { + qemu_log_mask(LOG_GUEST_ERROR, "OSPI AHB wr while OSPI disabled\n"); + } +} + +static const MemoryRegionOps ospi_dac_ops = { + .read = ospi_dac_read, + .write = ospi_dac_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void ospi_update_dac_status(void *opaque, int n, int level) +{ + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(opaque); + + s->dac_enable = level; +} + +static void xlnx_versal_ospi_realize(DeviceState *dev, Error **errp) +{ + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + + s->num_cs = 4; + s->spi = ssi_create_bus(dev, "spi0"); + s->cs_lines = g_new0(qemu_irq, s->num_cs); + for (int i = 0; i < s->num_cs; ++i) { + sysbus_init_irq(sbd, &s->cs_lines[i]); + } + + fifo8_create(&s->rx_fifo, RXFF_SZ); + fifo8_create(&s->tx_fifo, TXFF_SZ); + fifo8_create(&s->rx_sram, RXFF_SZ); + fifo8_create(&s->tx_sram, TXFF_SZ); +} + +static void xlnx_versal_ospi_init(Object *obj) +{ + XlnxVersalOspi *s = XILINX_VERSAL_OSPI(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + DeviceState *dev = DEVICE(obj); + RegisterInfoArray *reg_array; + + memory_region_init(&s->iomem, obj, TYPE_XILINX_VERSAL_OSPI, + XILINX_VERSAL_OSPI_R_MAX * 4); + reg_array = + register_init_block32(DEVICE(obj), ospi_regs_info, + ARRAY_SIZE(ospi_regs_info), + s->regs_info, s->regs, + &ospi_ops, + XILINX_VERSAL_OSPI_ERR_DEBUG, + XILINX_VERSAL_OSPI_R_MAX * 4); + memory_region_add_subregion(&s->iomem, 0x0, ®_array->mem); + sysbus_init_mmio(sbd, &s->iomem); + + memory_region_init_io(&s->iomem_dac, obj, &ospi_dac_ops, s, + TYPE_XILINX_VERSAL_OSPI "-dac", 0x20000000); + sysbus_init_mmio(sbd, &s->iomem_dac); + + sysbus_init_irq(sbd, &s->irq); + + object_property_add_link(obj, "dma-src", TYPE_DMA_CTRL, + (Object **)&s->dma_src, + object_property_allow_set_link, + OBJ_PROP_LINK_STRONG); + + qdev_init_gpio_in(dev, ospi_update_dac_status, 1); +} + +static const VMStateDescription vmstate_ind_op = { + .name = "OSPIIndOp", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(flash_addr, IndOp), + VMSTATE_UINT32(num_bytes, IndOp), + VMSTATE_UINT32(done_bytes, IndOp), + VMSTATE_BOOL(completed, IndOp), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_xlnx_versal_ospi = { + .name = TYPE_XILINX_VERSAL_OSPI, + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_FIFO8(rx_fifo, XlnxVersalOspi), + VMSTATE_FIFO8(tx_fifo, XlnxVersalOspi), + VMSTATE_FIFO8(rx_sram, XlnxVersalOspi), + VMSTATE_FIFO8(tx_sram, XlnxVersalOspi), + VMSTATE_BOOL(ind_write_disabled, XlnxVersalOspi), + VMSTATE_BOOL(dac_with_indac, XlnxVersalOspi), + VMSTATE_BOOL(dac_enable, XlnxVersalOspi), + VMSTATE_BOOL(src_dma_inprog, XlnxVersalOspi), + VMSTATE_STRUCT_ARRAY(rd_ind_op, XlnxVersalOspi, 2, 1, + vmstate_ind_op, IndOp), + VMSTATE_STRUCT_ARRAY(wr_ind_op, XlnxVersalOspi, 2, 1, + vmstate_ind_op, IndOp), + VMSTATE_UINT32_ARRAY(regs, XlnxVersalOspi, XILINX_VERSAL_OSPI_R_MAX), + VMSTATE_UINT8_ARRAY(stig_membank, XlnxVersalOspi, 512), + VMSTATE_END_OF_LIST(), + } +}; + +static Property xlnx_versal_ospi_properties[] = { + DEFINE_PROP_BOOL("dac-with-indac", XlnxVersalOspi, dac_with_indac, false), + DEFINE_PROP_BOOL("indac-write-disabled", XlnxVersalOspi, + ind_write_disabled, true), + DEFINE_PROP_UINT8("num-cs", XlnxVersalOspi, num_cs, 4), + DEFINE_PROP_END_OF_LIST(), +}; + +static void xlnx_versal_ospi_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = xlnx_versal_ospi_reset; + dc->realize = xlnx_versal_ospi_realize; + dc->vmsd = &vmstate_xlnx_versal_ospi; + device_class_set_props(dc, xlnx_versal_ospi_properties); +} + +static const TypeInfo xlnx_versal_ospi_info = { + .name = TYPE_XILINX_VERSAL_OSPI, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(XlnxVersalOspi), + .class_init = xlnx_versal_ospi_class_init, + .instance_init = xlnx_versal_ospi_init, +}; + +static void xlnx_versal_ospi_register_types(void) +{ + type_register_static(&xlnx_versal_ospi_info); +} + +type_init(xlnx_versal_ospi_register_types) diff --git a/include/hw/ssi/xlnx-versal-ospi.h b/include/hw/ssi/xlnx-versal-ospi.h new file mode 100644 index 0000000000..cc2602b300 --- /dev/null +++ b/include/hw/ssi/xlnx-versal-ospi.h @@ -0,0 +1,86 @@ +/* + * Header file for the Xilinx Versal's OSPI controller + * + * Copyright (C) 2021 Xilinx Inc + * Written by Francisco Iglesias <francisco.iglesias@xilinx.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef XILINX_VERSAL_OSPI_H +#define XILINX_VERSAL_OSPI_H + +#include "hw/register.h" +#include "hw/ssi/ssi.h" +#include "qemu/fifo32.h" +#include "hw/dma/dma-ctrl.h" + +#define TYPE_XILINX_VERSAL_OSPI "xlnx.versal-ospi" + +#define XILINX_VERSAL_OSPI(obj) \ + OBJECT_CHECK(XlnxVersalOspi, (obj), TYPE_XILINX_VERSAL_OSPI) + +#define XILINX_VERSAL_OSPI_R_MAX (0xfc / 4 + 1) + +/* + * Indirect operations + */ +typedef struct IndOp { + uint32_t flash_addr; + uint32_t num_bytes; + uint32_t done_bytes; + bool completed; +} IndOp; + +typedef struct XlnxVersalOspi { + SysBusDevice parent_obj; + + MemoryRegion iomem; + MemoryRegion iomem_dac; + + uint8_t num_cs; + qemu_irq *cs_lines; + + SSIBus *spi; + + Fifo8 rx_fifo; + Fifo8 tx_fifo; + + Fifo8 rx_sram; + Fifo8 tx_sram; + + qemu_irq irq; + + DmaCtrl *dma_src; + bool ind_write_disabled; + bool dac_with_indac; + bool dac_enable; + bool src_dma_inprog; + + IndOp rd_ind_op[2]; + IndOp wr_ind_op[2]; + + uint32_t regs[XILINX_VERSAL_OSPI_R_MAX]; + RegisterInfo regs_info[XILINX_VERSAL_OSPI_R_MAX]; + + /* Maximum inferred membank size is 512 bytes */ + uint8_t stig_membank[512]; +} XlnxVersalOspi; + +#endif /* XILINX_VERSAL_OSPI_H */
Add a model of Xilinx Versal's OSPI flash memory controller. Signed-off-by: Francisco Iglesias <francisco.iglesias@xilinx.com> --- hw/ssi/meson.build | 1 + hw/ssi/xlnx-versal-ospi.c | 1892 +++++++++++++++++++++++++++++++++++++ include/hw/ssi/xlnx-versal-ospi.h | 86 ++ 3 files changed, 1979 insertions(+) create mode 100644 hw/ssi/xlnx-versal-ospi.c create mode 100644 include/hw/ssi/xlnx-versal-ospi.h