/* * QEMU model of the Xilinx Zynq SPI controller * * Copyright (c) 2012 Peter A. G. Crosthwaite * * 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 "sysemu/sysemu.h" #include "hw/ptimer.h" #include "qemu/log.h" #include "qemu/bitops.h" #include "hw/ssi/xilinx_spips.h" #include "qapi/error.h" #include "hw/register.h" #include "sysemu/dma.h" #include "migration/blocker.h" #ifndef XILINX_SPIPS_ERR_DEBUG #define XILINX_SPIPS_ERR_DEBUG 0 #endif #define DB_PRINT_L(level, ...) do { \ if (XILINX_SPIPS_ERR_DEBUG > (level)) { \ fprintf(stderr, ": %s: ", __func__); \ fprintf(stderr, ## __VA_ARGS__); \ } \ } while (0) /* config register */ #define R_CONFIG (0x00 / 4) #define IFMODE (1U << 31) #define R_CONFIG_ENDIAN (1 << 26) #define MODEFAIL_GEN_EN (1 << 17) #define MAN_START_COM (1 << 16) #define MAN_START_EN (1 << 15) #define MANUAL_CS (1 << 14) #define CS (0xF << 10) #define CS_SHIFT (10) #define PERI_SEL (1 << 9) #define REF_CLK (1 << 8) #define FIFO_WIDTH (3 << 6) #define BAUD_RATE_DIV (7 << 3) #define CLK_PH (1 << 2) #define CLK_POL (1 << 1) #define MODE_SEL (1 << 0) #define R_CONFIG_RSVD (0x7bf40000) /* interrupt mechanism */ #define R_INTR_STATUS (0x04 / 4) #define R_INTR_STATUS_RESET (0x104) #define R_INTR_EN (0x08 / 4) #define R_INTR_DIS (0x0C / 4) #define R_INTR_MASK (0x10 / 4) #define IXR_TX_FIFO_UNDERFLOW (1 << 6) /* Poll timeout not implemented */ #define IXR_RX_FIFO_EMPTY (1 << 11) #define IXR_GENERIC_FIFO_FULL (1 << 10) #define IXR_GENERIC_FIFO_NOT_FULL (1 << 9) #define IXR_TX_FIFO_EMPTY (1 << 8) #define IXR_GENERIC_FIFO_EMPTY (1 << 7) #define IXR_RX_FIFO_FULL (1 << 5) #define IXR_RX_FIFO_NOT_EMPTY (1 << 4) #define IXR_TX_FIFO_FULL (1 << 3) #define IXR_TX_FIFO_NOT_FULL (1 << 2) #define IXR_TX_FIFO_MODE_FAIL (1 << 1) #define IXR_RX_FIFO_OVERFLOW (1 << 0) #define IXR_ALL ((1 << 13) - 1) #define GQSPI_IXR_MASK 0xFBE #define IXR_SELF_CLEAR \ (IXR_GENERIC_FIFO_EMPTY \ | IXR_GENERIC_FIFO_FULL \ | IXR_GENERIC_FIFO_NOT_FULL \ | IXR_TX_FIFO_EMPTY \ | IXR_TX_FIFO_FULL \ | IXR_TX_FIFO_NOT_FULL \ | IXR_RX_FIFO_EMPTY \ | IXR_RX_FIFO_FULL \ | IXR_RX_FIFO_NOT_EMPTY) #define R_EN (0x14 / 4) #define R_DELAY (0x18 / 4) #define R_TX_DATA (0x1C / 4) #define R_RX_DATA (0x20 / 4) #define R_SLAVE_IDLE_COUNT (0x24 / 4) #define R_TX_THRES (0x28 / 4) #define R_RX_THRES (0x2C / 4) #define R_GPIO (0x30 / 4) #define R_LPBK_DLY_ADJ (0x38 / 4) #define R_LPBK_DLY_ADJ_RESET (0x33) #define R_TXD1 (0x80 / 4) #define R_TXD2 (0x84 / 4) #define R_TXD3 (0x88 / 4) #define R_LQSPI_CFG (0xa0 / 4) #define R_LQSPI_CFG_RESET 0x03A002EB #define LQSPI_CFG_LQ_MODE (1U << 31) #define LQSPI_CFG_TWO_MEM (1 << 30) #define LQSPI_CFG_SEP_BUS (1 << 29) #define LQSPI_CFG_U_PAGE (1 << 28) #define LQSPI_CFG_ADDR4 (1 << 27) #define LQSPI_CFG_MODE_EN (1 << 25) #define LQSPI_CFG_MODE_WIDTH 8 #define LQSPI_CFG_MODE_SHIFT 16 #define LQSPI_CFG_DUMMY_WIDTH 3 #define LQSPI_CFG_DUMMY_SHIFT 8 #define LQSPI_CFG_INST_CODE 0xFF #define R_CMND (0xc0 / 4) #define R_CMND_RXFIFO_DRAIN (1 << 19) FIELD(CMND, PARTIAL_BYTE_LEN, 16, 3) #define R_CMND_EXT_ADD (1 << 15) FIELD(CMND, RX_DISCARD, 8, 7) FIELD(CMND, DUMMY_CYCLES, 2, 6) #define R_CMND_DMA_EN (1 << 1) #define R_CMND_PUSH_WAIT (1 << 0) #define R_TRANSFER_SIZE (0xc4 / 4) #define R_LQSPI_STS (0xA4 / 4) #define LQSPI_STS_WR_RECVD (1 << 1) #define R_MOD_ID (0xFC / 4) #define R_GQSPI_SELECT (0x144 / 4) FIELD(GQSPI_SELECT, GENERIC_QSPI_EN, 0, 1) #define R_GQSPI_ISR (0x104 / 4) #define R_GQSPI_IER (0x108 / 4) #define R_GQSPI_IDR (0x10c / 4) #define R_GQSPI_IMR (0x110 / 4) #define R_GQSPI_IMR_RESET (0xfbe) #define R_GQSPI_TX_THRESH (0x128 / 4) #define R_GQSPI_RX_THRESH (0x12c / 4) #define R_GQSPI_GPIO (0x130 / 4) #define R_GQSPI_LPBK_DLY_ADJ (0x138 / 4) #define R_GQSPI_LPBK_DLY_ADJ_RESET (0x33) #define R_GQSPI_CNFG (0x100 / 4) FIELD(GQSPI_CNFG, MODE_EN, 30, 2) FIELD(GQSPI_CNFG, GEN_FIFO_START_MODE, 29, 1) FIELD(GQSPI_CNFG, GEN_FIFO_START, 28, 1) FIELD(GQSPI_CNFG, ENDIAN, 26, 1) /* Poll timeout not implemented */ FIELD(GQSPI_CNFG, EN_POLL_TIMEOUT, 20, 1) /* QEMU doesnt care about any of these last three */ FIELD(GQSPI_CNFG, BR, 3, 3) FIELD(GQSPI_CNFG, CPH, 2, 1) FIELD(GQSPI_CNFG, CPL, 1, 1) #define R_GQSPI_GEN_FIFO (0x140 / 4) #define R_GQSPI_TXD (0x11c / 4) #define R_GQSPI_RXD (0x120 / 4) #define R_GQSPI_FIFO_CTRL (0x14c / 4) FIELD(GQSPI_FIFO_CTRL, RX_FIFO_RESET, 2, 1) FIELD(GQSPI_FIFO_CTRL, TX_FIFO_RESET, 1, 1) FIELD(GQSPI_FIFO_CTRL, GENERIC_FIFO_RESET, 0, 1) #define R_GQSPI_GFIFO_THRESH (0x150 / 4) #define R_GQSPI_DATA_STS (0x15c / 4) /* We use the snapshot register to hold the core state for the currently * or most recently executed command. So the generic fifo format is defined * for the snapshot register */ #define R_GQSPI_GF_SNAPSHOT (0x160 / 4) FIELD(GQSPI_GF_SNAPSHOT, POLL, 19, 1) FIELD(GQSPI_GF_SNAPSHOT, STRIPE, 18, 1) FIELD(GQSPI_GF_SNAPSHOT, RECIEVE, 17, 1) FIELD(GQSPI_GF_SNAPSHOT, TRANSMIT, 16, 1) FIELD(GQSPI_GF_SNAPSHOT, DATA_BUS_SELECT, 14, 2) FIELD(GQSPI_GF_SNAPSHOT, CHIP_SELECT, 12, 2) FIELD(GQSPI_GF_SNAPSHOT, SPI_MODE, 10, 2) FIELD(GQSPI_GF_SNAPSHOT, EXPONENT, 9, 1) FIELD(GQSPI_GF_SNAPSHOT, DATA_XFER, 8, 1) FIELD(GQSPI_GF_SNAPSHOT, IMMEDIATE_DATA, 0, 8) #define R_GQSPI_MOD_ID (0x1fc / 4) #define R_GQSPI_MOD_ID_RESET (0x10a0000) #define R_QSPIDMA_DST_CTRL (0x80c / 4) #define R_QSPIDMA_DST_CTRL_RESET (0x803ffa00) #define R_QSPIDMA_DST_I_MASK (0x820 / 4) #define R_QSPIDMA_DST_I_MASK_RESET (0xfe) #define R_QSPIDMA_DST_CTRL2 (0x824 / 4) #define R_QSPIDMA_DST_CTRL2_RESET (0x081bfff8) /* size of TXRX FIFOs */ #define RXFF_A (128) #define TXFF_A (128) #define RXFF_A_Q (64 * 4) #define TXFF_A_Q (64 * 4) /* 16MB per linear region */ #define LQSPI_ADDRESS_BITS 24 #define SNOOP_CHECKING 0xFF #define SNOOP_ADDR 0xF0 #define SNOOP_NONE 0xEE #define SNOOP_STRIPING 0 #define MIN_NUM_BUSSES 1 #define MAX_NUM_BUSSES 2 static inline int num_effective_busses(XilinxSPIPS *s) { return (s->regs[R_LQSPI_CFG] & LQSPI_CFG_SEP_BUS && s->regs[R_LQSPI_CFG] & LQSPI_CFG_TWO_MEM) ? s->num_busses : 1; } static void xilinx_spips_update_cs(XilinxSPIPS *s, int field) { int i; for (i = 0; i < s->num_cs * s->num_busses; i++) { bool old_state = s->cs_lines_state[i]; bool new_state = field & (1 << i); if (old_state != new_state) { s->cs_lines_state[i] = new_state; s->rx_discard = ARRAY_FIELD_EX32(s->regs, CMND, RX_DISCARD); DB_PRINT_L(1, "%sselecting slave %d\n", new_state ? "" : "de", i); } qemu_set_irq(s->cs_lines[i], !new_state); } if (!(field & ((1 << (s->num_cs * s->num_busses)) - 1))) { s->snoop_state = SNOOP_CHECKING; s->cmd_dummies = 0; s->link_state = 1; s->link_state_next = 1; s->link_state_next_when = 0; DB_PRINT_L(1, "moving to snoop check state\n"); } } static void xlnx_zynqmp_qspips_update_cs_lines(XlnxZynqMPQSPIPS *s) { if (s->regs[R_GQSPI_GF_SNAPSHOT]) { int field = ARRAY_FIELD_EX32(s->regs, GQSPI_GF_SNAPSHOT, CHIP_SELECT); bool upper_cs_sel = field & (1 << 1); bool lower_cs_sel = field & 1; bool bus0_enabled; bool bus1_enabled; uint8_t buses; int cs = 0; buses = ARRAY_FIELD_EX32(s->regs, GQSPI_GF_SNAPSHOT, DATA_BUS_SELECT); bus0_enabled = buses & 1; bus1_enabled = buses & (1 << 1); if (bus0_enabled && bus1_enabled) { if (lower_cs_sel) { cs |= 1; } if (upper_cs_sel) { cs |= 1 << 3; } } else if (bus0_enabled) { if (lower_cs_sel) { cs |= 1; } if (upper_cs_sel) { cs |= 1 << 1; } } else if (bus1_enabled) { if (lower_cs_sel) { cs |= 1 << 2; } if (upper_cs_sel) { cs |= 1 << 3; } } xilinx_spips_update_cs(XILINX_SPIPS(s), cs); } } static void xilinx_spips_update_cs_lines(XilinxSPIPS *s) { int field = ~((s->regs[R_CONFIG] & CS) >> CS_SHIFT); /* In dual parallel, mirror low CS to both */ if (num_effective_busses(s) == 2) { /* Single bit chip-select for qspi */ field &= 0x1; field |= field << 3; /* Dual stack U-Page */ } else if (s->regs[R_LQSPI_CFG] & LQSPI_CFG_TWO_MEM && s->regs[R_LQSPI_STS] & LQSPI_CFG_U_PAGE) { /* Single bit chip-select for qspi */ field &= 0x1; /* change from CS0 to CS1 */ field <<= 1; } /* Auto CS */ if (!(s->regs[R_CONFIG] & MANUAL_CS) && fifo8_is_empty(&s->tx_fifo)) { field = 0; } xilinx_spips_update_cs(s, field); } static void xilinx_spips_update_ixr(XilinxSPIPS *s) { if (!(s->regs[R_LQSPI_CFG] & LQSPI_CFG_LQ_MODE)) { s->regs[R_INTR_STATUS] &= ~IXR_SELF_CLEAR; s->regs[R_INTR_STATUS] |= (fifo8_is_full(&s->rx_fifo) ? IXR_RX_FIFO_FULL : 0) | (s->rx_fifo.num >= s->regs[R_RX_THRES] ? IXR_RX_FIFO_NOT_EMPTY : 0) | (fifo8_is_full(&s->tx_fifo) ? IXR_TX_FIFO_FULL : 0) | (fifo8_is_empty(&s->tx_fifo) ? IXR_TX_FIFO_EMPTY : 0) | (s->tx_fifo.num < s->regs[R_TX_THRES] ? IXR_TX_FIFO_NOT_FULL : 0); } int new_irqline = !!(s->regs[R_INTR_MASK] & s->regs[R_INTR_STATUS] & IXR_ALL); if (new_irqline != s->irqline) { s->irqline = new_irqline; qemu_set_irq(s->irq, s->irqline); } } static void xlnx_zynqmp_qspips_update_ixr(XlnxZynqMPQSPIPS *s) { uint32_t gqspi_int; int new_irqline; s->regs[R_GQSPI_ISR] &= ~IXR_SELF_CLEAR; s->regs[R_GQSPI_ISR] |= (fifo32_is_empty(&s->fifo_g) ? IXR_GENERIC_FIFO_EMPTY : 0) | (fifo32_is_full(&s->fifo_g) ? IXR_GENERIC_FIFO_FULL : 0) | (s->fifo_g.fifo.num < s->regs[R_GQSPI_GFIFO_THRESH] ? IXR_GENERIC_FIFO_NOT_FULL : 0) | (fifo8_is_empty(&s->rx_fifo_g) ? IXR_RX_FIFO_EMPTY : 0) | (fifo8_is_full(&s->rx_fifo_g) ? IXR_RX_FIFO_FULL : 0) | (s->rx_fifo_g.num >= s->regs[R_GQSPI_RX_THRESH] ? IXR_RX_FIFO_NOT_EMPTY : 0) | (fifo8_is_empty(&s->tx_fifo_g) ? IXR_TX_FIFO_EMPTY : 0) | (fifo8_is_full(&s->tx_fifo_g) ? IXR_TX_FIFO_FULL : 0) | (s->tx_fifo_g.num < s->regs[R_GQSPI_TX_THRESH] ? IXR_TX_FIFO_NOT_FULL : 0); /* GQSPI Interrupt Trigger Status */ gqspi_int = (~s->regs[R_GQSPI_IMR]) & s->regs[R_GQSPI_ISR] & GQSPI_IXR_MASK; new_irqline = !!(gqspi_int & IXR_ALL); /* drive external interrupt pin */ if (new_irqline != s->gqspi_irqline) { s->gqspi_irqline = new_irqline; qemu_set_irq(XILINX_SPIPS(s)->irq, s->gqspi_irqline); } } static void xilinx_spips_reset(DeviceState *d) { XilinxSPIPS *s = XILINX_SPIPS(d); memset(s->regs, 0, sizeof(s->regs)); fifo8_reset(&s->rx_fifo); fifo8_reset(&s->rx_fifo); /* non zero resets */ s->regs[R_CONFIG] |= MODEFAIL_GEN_EN; s->regs[R_SLAVE_IDLE_COUNT] = 0xFF; s->regs[R_TX_THRES] = 1; s->regs[R_RX_THRES] = 1; /* FIXME: move magic number definition somewhere sensible */ s->regs[R_MOD_ID] = 0x01090106; s->regs[R_LQSPI_CFG] = R_LQSPI_CFG_RESET; s->link_state = 1; s->link_state_next = 1; s->link_state_next_when = 0; s->snoop_state = SNOOP_CHECKING; s->cmd_dummies = 0; s->man_start_com = false; xilinx_spips_update_ixr(s); xilinx_spips_update_cs_lines(s); } static void xlnx_zynqmp_qspips_reset(DeviceState *d) { XlnxZynqMPQSPIPS *s = XLNX_ZYNQMP_QSPIPS(d); xilinx_spips_reset(d); memset(s->regs, 0, sizeof(s->regs)); fifo8_reset(&s->rx_fifo_g); fifo8_reset(&s->rx_fifo_g); fifo32_reset(&s->fifo_g); s->regs[R_INTR_STATUS] = R_INTR_STATUS_RESET; s->regs[R_GPIO] = 1; s->regs[R_LPBK_DLY_ADJ] = R_LPBK_DLY_ADJ_RESET; s->regs[R_GQSPI_GFIFO_THRESH] = 0x10; s->regs[R_MOD_ID] = 0x01090101; s->regs[R_GQSPI_IMR] = R_GQSPI_IMR_RESET; s->regs[R_GQSPI_TX_THRESH] = 1; s->regs[R_GQSPI_RX_THRESH] = 1; s->regs[R_GQSPI_GPIO] = 1; s->regs[R_GQSPI_LPBK_DLY_ADJ] = R_GQSPI_LPBK_DLY_ADJ_RESET; s->regs[R_GQSPI_MOD_ID] = R_GQSPI_MOD_ID_RESET; s->regs[R_QSPIDMA_DST_CTRL] = R_QSPIDMA_DST_CTRL_RESET; s->regs[R_QSPIDMA_DST_I_MASK] = R_QSPIDMA_DST_I_MASK_RESET; s->regs[R_QSPIDMA_DST_CTRL2] = R_QSPIDMA_DST_CTRL2_RESET; s->man_start_com_g = false; s->gqspi_irqline = 0; xlnx_zynqmp_qspips_update_ixr(s); } /* N way (num) in place bit striper. Lay out row wise bits (MSB to LSB) * column wise (from element 0 to N-1). num is the length of x, and dir * reverses the direction of the transform. Best illustrated by example: * Each digit in the below array is a single bit (num == 3): * * {{ 76543210, } ----- stripe (dir == false) -----> {{ 741gdaFC, } * { hgfedcba, } { 630fcHEB, } * { HGFEDCBA, }} <---- upstripe (dir == true) ----- { 52hebGDA, }} */ static inline void stripe8(uint8_t *x, int num, bool dir) { uint8_t r[num]; memset(r, 0, sizeof(uint8_t) * num); int idx[2] = {0, 0}; int bit[2] = {0, 7}; int d = dir; for (idx[0] = 0; idx[0] < num; ++idx[0]) { for (bit[0] = 7; bit[0] >= 0; bit[0]--) { r[idx[!d]] |= x[idx[d]] & 1 << bit[d] ? 1 << bit[!d] : 0; idx[1] = (idx[1] + 1) % num; if (!idx[1]) { bit[1]--; } } } memcpy(x, r, sizeof(uint8_t) * num); } static void xlnx_zynqmp_qspips_flush_fifo_g(XlnxZynqMPQSPIPS *s) { while (s->regs[R_GQSPI_DATA_STS] || !fifo32_is_empty(&s->fifo_g)) { uint8_t tx_rx[2] = { 0 }; int num_stripes = 1; uint8_t busses; int i; if (!s->regs[R_GQSPI_DATA_STS]) { uint8_t imm; s->regs[R_GQSPI_GF_SNAPSHOT] = fifo32_pop(&s->fifo_g); DB_PRINT_L(0, "GQSPI command: %x\n", s->regs[R_GQSPI_GF_SNAPSHOT]); if (!s->regs[R_GQSPI_GF_SNAPSHOT]) { DB_PRINT_L(0, "Dummy GQSPI Delay Command Entry, Do nothing"); continue; } xlnx_zynqmp_qspips_update_cs_lines(s); imm = ARRAY_FIELD_EX32(s->regs, GQSPI_GF_SNAPSHOT, IMMEDIATE_DATA); if (!ARRAY_FIELD_EX32(s->regs, GQSPI_GF_SNAPSHOT, DATA_XFER)) { /* immedate transfer */ if (ARRAY_FIELD_EX32(s->regs, GQSPI_GF_SNAPSHOT, TRANSMIT) || ARRAY_FIELD_EX32(s->regs, GQSPI_GF_SNAPSHOT, RECIEVE)) { s->regs[R_GQSPI_DATA_STS] = 1; /* CS setup/hold - do nothing */ } else { s->regs[R_GQSPI_DATA_STS] = 0; } } else if (ARRAY_FIELD_EX32(s->regs, GQSPI_GF_SNAPSHOT, EXPONENT)) { if (imm > 31) { qemu_log_mask(LOG_UNIMP, "QSPI exponential transfer too" " long - 2 ^ %" PRId8 " requested\n", imm); } s->regs[R_GQSPI_DATA_STS] = 1ul << imm; } else { s->regs[R_GQSPI_DATA_STS] = imm; } } /* Zero length transfer check */ if (!s->regs[R_GQSPI_DATA_STS]) { continue; } if (ARRAY_FIELD_EX32(s->regs, GQSPI_GF_SNAPSHOT, RECIEVE) && fifo8_is_full(&s->rx_fifo_g)) { /* No space in RX fifo for transfer - try again later */ return; } if (ARRAY_FIELD_EX32(s->regs, GQSPI_GF_SNAPSHOT, STRIPE) && (ARRAY_FIELD_EX32(s->regs, GQSPI_GF_SNAPSHOT, TRANSMIT) || ARRAY_FIELD_EX32(s->regs, GQSPI_GF_SNAPSHOT, RECIEVE))) { num_stripes = 2; } if (!ARRAY_FIELD_EX32(s->regs, GQSPI_GF_SNAPSHOT, DATA_XFER)) { tx_rx[0] = ARRAY_FIELD_EX32(s->regs, GQSPI_GF_SNAPSHOT, IMMEDIATE_DATA); } else if (ARRAY_FIELD_EX32(s->regs, GQSPI_GF_SNAPSHOT, TRANSMIT)) { for (i = 0; i < num_stripes; ++i) { if (!fifo8_is_empty(&s->tx_fifo_g)) { tx_rx[i] = fifo8_pop(&s->tx_fifo_g); s->tx_fifo_g_align++; } else { return; } } } if (num_stripes == 1) { /* mirror */ tx_rx[1] = tx_rx[0]; } busses = ARRAY_FIELD_EX32(s->regs, GQSPI_GF_SNAPSHOT, DATA_BUS_SELECT); for (i = 0; i < 2; ++i) { DB_PRINT_L(1, "bus %d tx = %02x\n", i, tx_rx[i]); tx_rx[i] = ssi_transfer(XILINX_SPIPS(s)->spi[i], tx_rx[i]); DB_PRINT_L(1, "bus %d rx = %02x\n", i, tx_rx[i]); } if (s->regs[R_GQSPI_DATA_STS] > 1 && busses == 0x3 && num_stripes == 2) { s->regs[R_GQSPI_DATA_STS] -= 2; } else if (s->regs[R_GQSPI_DATA_STS] > 0) { s->regs[R_GQSPI_DATA_STS]--; } if (ARRAY_FIELD_EX32(s->regs, GQSPI_GF_SNAPSHOT, RECIEVE)) { for (i = 0; i < 2; ++i) { if (busses & (1 << i)) { DB_PRINT_L(1, "bus %d push_byte = %02x\n", i, tx_rx[i]); fifo8_push(&s->rx_fifo_g, tx_rx[i]); s->rx_fifo_g_align++; } } } if (!s->regs[R_GQSPI_DATA_STS]) { for (; s->tx_fifo_g_align % 4; s->tx_fifo_g_align++) { fifo8_pop(&s->tx_fifo_g); } for (; s->rx_fifo_g_align % 4; s->rx_fifo_g_align++) { fifo8_push(&s->rx_fifo_g, 0); } } } } static int xilinx_spips_num_dummies(XilinxQSPIPS *qs, uint8_t command) { if (!qs) { /* The SPI device is not a QSPI device */ return -1; } switch (command) { /* check for dummies */ case READ: /* no dummy bytes/cycles */ case PP: case DPP: case QPP: case READ_4: case PP_4: case QPP_4: return 0; case FAST_READ: case DOR: case QOR: case DOR_4: case QOR_4: return 1; case DIOR: case FAST_READ_4: case DIOR_4: return 2; case QIOR: case QIOR_4: return 4; default: return -1; } } static inline uint8_t get_addr_length(XilinxSPIPS *s, uint8_t cmd) { switch (cmd) { case PP_4: case QPP_4: case READ_4: case QIOR_4: case FAST_READ_4: case DOR_4: case QOR_4: case DIOR_4: return 4; default: return (s->regs[R_CMND] & R_CMND_EXT_ADD) ? 4 : 3; } } static void xilinx_spips_flush_txfifo(XilinxSPIPS *s) { int debug_level = 0; XilinxQSPIPS *q = (XilinxQSPIPS *) object_dynamic_cast(OBJECT(s), TYPE_XILINX_QSPIPS); for (;;) { int i; uint8_t tx = 0; uint8_t tx_rx[MAX_NUM_BUSSES] = { 0 }; uint8_t dummy_cycles = 0; uint8_t addr_length; if (fifo8_is_empty(&s->tx_fifo)) { xilinx_spips_update_ixr(s); return; } else if (s->snoop_state == SNOOP_STRIPING || s->snoop_state == SNOOP_NONE) { for (i = 0; i < num_effective_busses(s); ++i) { tx_rx[i] = fifo8_pop(&s->tx_fifo); } stripe8(tx_rx, num_effective_busses(s), false); } else if (s->snoop_state >= SNOOP_ADDR) { tx = fifo8_pop(&s->tx_fifo); for (i = 0; i < num_effective_busses(s); ++i) { tx_rx[i] = tx; } } else { /* Extract a dummy byte and generate dummy cycles according to the * link state */ tx = fifo8_pop(&s->tx_fifo); dummy_cycles = 8 / s->link_state; } for (i = 0; i < num_effective_busses(s); ++i) { int bus = num_effective_busses(s) - 1 - i; if (dummy_cycles) { int d; for (d = 0; d < dummy_cycles; ++d) { tx_rx[0] = ssi_transfer(s->spi[bus], (uint32_t)tx_rx[0]); } } else { DB_PRINT_L(debug_level, "tx = %02x\n", tx_rx[i]); tx_rx[i] = ssi_transfer(s->spi[bus], (uint32_t)tx_rx[i]); DB_PRINT_L(debug_level, "rx = %02x\n", tx_rx[i]); } } if (s->regs[R_CMND] & R_CMND_RXFIFO_DRAIN) { DB_PRINT_L(debug_level, "dircarding drained rx byte\n"); /* Do nothing */ } else if (s->rx_discard) { DB_PRINT_L(debug_level, "dircarding discarded rx byte\n"); s->rx_discard -= 8 / s->link_state; } else if (fifo8_is_full(&s->rx_fifo)) { s->regs[R_INTR_STATUS] |= IXR_RX_FIFO_OVERFLOW; DB_PRINT_L(0, "rx FIFO overflow"); } else if (s->snoop_state == SNOOP_STRIPING) { stripe8(tx_rx, num_effective_busses(s), true); for (i = 0; i < num_effective_busses(s); ++i) { fifo8_push(&s->rx_fifo, (uint8_t)tx_rx[i]); DB_PRINT_L(debug_level, "pushing striped rx byte\n"); } } else { DB_PRINT_L(debug_level, "pushing unstriped rx byte\n"); fifo8_push(&s->rx_fifo, (uint8_t)tx_rx[0]); } if (s->link_state_next_when) { s->link_state_next_when--; if (!s->link_state_next_when) { s->link_state = s->link_state_next; } } DB_PRINT_L(debug_level, "initial snoop state: %x\n", (unsigned)s->snoop_state); switch (s->snoop_state) { case (SNOOP_CHECKING): /* Store the count of dummy bytes in the txfifo */ s->cmd_dummies = xilinx_spips_num_dummies(q, tx); addr_length = get_addr_length(s, tx); if (s->cmd_dummies < 0) { s->snoop_state = SNOOP_NONE; } else { s->snoop_state = SNOOP_ADDR + addr_length - 1; } switch (tx) { case DPP: case DOR: case DOR_4: s->link_state_next = 2; s->link_state_next_when = addr_length + s->cmd_dummies; break; case QPP: case QPP_4: case QOR: case QOR_4: s->link_state_next = 4; s->link_state_next_when = addr_length + s->cmd_dummies; break; case DIOR: case DIOR_4: s->link_state = 2; break; case QIOR: case QIOR_4: s->link_state = 4; break; } break; case (SNOOP_ADDR): /* Address has been transmitted, transmit dummy cycles now if * needed */ if (s->cmd_dummies < 0) { s->snoop_state = SNOOP_NONE; } else { s->snoop_state = s->cmd_dummies; } break; case (SNOOP_STRIPING): case (SNOOP_NONE): /* Once we hit the boring stuff - squelch debug noise */ if (!debug_level) { DB_PRINT_L(0, "squelching debug info ....\n"); debug_level = 1; } break; default: s->snoop_state--; } DB_PRINT_L(debug_level, "final snoop state: %x\n", (unsigned)s->snoop_state); } } static inline void tx_data_bytes(Fifo8 *fifo, uint32_t value, int num, bool be) { int i; for (i = 0; i < num && !fifo8_is_full(fifo); ++i) { if (be) { fifo8_push(fifo, (uint8_t)(value >> 24)); value <<= 8; } else { fifo8_push(fifo, (uint8_t)value); value >>= 8; } } } static void xilinx_spips_check_zero_pump(XilinxSPIPS *s) { if (!s->regs[R_TRANSFER_SIZE]) { return; } if (!fifo8_is_empty(&s->tx_fifo) && s->regs[R_CMND] & R_CMND_PUSH_WAIT) { return; } /* * The zero pump must never fill tx fifo such that rx overflow is * possible */ while (s->regs[R_TRANSFER_SIZE] && s->rx_fifo.num + s->tx_fifo.num < RXFF_A_Q - 3) { /* endianess just doesn't matter when zero pumping */ tx_data_bytes(&s->tx_fifo, 0, 4, false); s->regs[R_TRANSFER_SIZE] &= ~0x03ull; s->regs[R_TRANSFER_SIZE] -= 4; } } static void xilinx_spips_check_flush(XilinxSPIPS *s) { if (s->man_start_com || (!fifo8_is_empty(&s->tx_fifo) && !(s->regs[R_CONFIG] & MAN_START_EN))) { xilinx_spips_check_zero_pump(s); xilinx_spips_flush_txfifo(s); } if (fifo8_is_empty(&s->tx_fifo) && !s->regs[R_TRANSFER_SIZE]) { s->man_start_com = false; } xilinx_spips_update_ixr(s); } static void xlnx_zynqmp_qspips_check_flush(XlnxZynqMPQSPIPS *s) { bool gqspi_has_work = s->regs[R_GQSPI_DATA_STS] || !fifo32_is_empty(&s->fifo_g); if (ARRAY_FIELD_EX32(s->regs, GQSPI_SELECT, GENERIC_QSPI_EN)) { if (s->man_start_com_g || (gqspi_has_work && !ARRAY_FIELD_EX32(s->regs, GQSPI_CNFG, GEN_FIFO_START_MODE))) { xlnx_zynqmp_qspips_flush_fifo_g(s); } } else { xilinx_spips_check_flush(XILINX_SPIPS(s)); } if (!gqspi_has_work) { s->man_start_com_g = false; } xlnx_zynqmp_qspips_update_ixr(s); } static inline int rx_data_bytes(Fifo8 *fifo, uint8_t *value, int max) { int i; for (i = 0; i < max && !fifo8_is_empty(fifo); ++i) { value[i] = fifo8_pop(fifo); } return max - i; } static const void *pop_buf(Fifo8 *fifo, uint32_t max, uint32_t *num) { void *ret; if (max == 0 || max > fifo->num) { abort(); } *num = MIN(fifo->capacity - fifo->head, max); ret = &fifo->data[fifo->head]; fifo->head += *num; fifo->head %= fifo->capacity; fifo->num -= *num; return ret; } static void xlnx_zynqmp_qspips_notify(void *opaque) { XlnxZynqMPQSPIPS *rq = XLNX_ZYNQMP_QSPIPS(opaque); XilinxSPIPS *s = XILINX_SPIPS(rq); Fifo8 *recv_fifo; if (ARRAY_FIELD_EX32(rq->regs, GQSPI_SELECT, GENERIC_QSPI_EN)) { if (!(ARRAY_FIELD_EX32(rq->regs, GQSPI_CNFG, MODE_EN) == 2)) { return; } recv_fifo = &rq->rx_fifo_g; } else { if (!(s->regs[R_CMND] & R_CMND_DMA_EN)) { return; } recv_fifo = &s->rx_fifo; } while (recv_fifo->num >= 4 && stream_can_push(rq->dma, xlnx_zynqmp_qspips_notify, rq)) { size_t ret; uint32_t num; const void *rxd = pop_buf(recv_fifo, 4, &num); memcpy(rq->dma_buf, rxd, num); ret = stream_push(rq->dma, rq->dma_buf, 4); assert(ret == 4); xlnx_zynqmp_qspips_check_flush(rq); } } static uint64_t xilinx_spips_read(void *opaque, hwaddr addr, unsigned size) { XilinxSPIPS *s = opaque; uint32_t mask = ~0; uint32_t ret; uint8_t rx_buf[4]; int shortfall; addr >>= 2; switch (addr) { case R_CONFIG: mask = ~(R_CONFIG_RSVD | MAN_START_COM); break; case R_INTR_STATUS: ret = s->regs[addr] & IXR_ALL; s->regs[addr] = 0; DB_PRINT_L(0, "addr=" TARGET_FMT_plx " = %x\n", addr * 4, ret); xilinx_spips_update_ixr(s); return ret; case R_INTR_MASK: mask = IXR_ALL; break; case R_EN: mask = 0x1; break; case R_SLAVE_IDLE_COUNT: mask = 0xFF; break; case R_MOD_ID: mask = 0x01FFFFFF; break; case R_INTR_EN: case R_INTR_DIS: case R_TX_DATA: mask = 0; break; case R_RX_DATA: memset(rx_buf, 0, sizeof(rx_buf)); shortfall = rx_data_bytes(&s->rx_fifo, rx_buf, s->num_txrx_bytes); ret = s->regs[R_CONFIG] & R_CONFIG_ENDIAN ? cpu_to_be32(*(uint32_t *)rx_buf) : cpu_to_le32(*(uint32_t *)rx_buf); if (!(s->regs[R_CONFIG] & R_CONFIG_ENDIAN)) { ret <<= 8 * shortfall; } DB_PRINT_L(0, "addr=" TARGET_FMT_plx " = %x\n", addr * 4, ret); xilinx_spips_check_flush(s); xilinx_spips_update_ixr(s); return ret; } DB_PRINT_L(0, "addr=" TARGET_FMT_plx " = %x\n", addr * 4, s->regs[addr] & mask); return s->regs[addr] & mask; } static uint64_t xlnx_zynqmp_qspips_read(void *opaque, hwaddr addr, unsigned size) { XlnxZynqMPQSPIPS *s = XLNX_ZYNQMP_QSPIPS(opaque); uint32_t reg = addr / 4; uint32_t ret; uint8_t rx_buf[4]; int shortfall; if (reg <= R_MOD_ID) { return xilinx_spips_read(opaque, addr, size); } else { switch (reg) { case R_GQSPI_RXD: if (fifo8_is_empty(&s->rx_fifo_g)) { qemu_log_mask(LOG_GUEST_ERROR, "Read from empty GQSPI RX FIFO\n"); return 0; } memset(rx_buf, 0, sizeof(rx_buf)); shortfall = rx_data_bytes(&s->rx_fifo_g, rx_buf, XILINX_SPIPS(s)->num_txrx_bytes); ret = ARRAY_FIELD_EX32(s->regs, GQSPI_CNFG, ENDIAN) ? cpu_to_be32(*(uint32_t *)rx_buf) : cpu_to_le32(*(uint32_t *)rx_buf); if (!ARRAY_FIELD_EX32(s->regs, GQSPI_CNFG, ENDIAN)) { ret <<= 8 * shortfall; } xlnx_zynqmp_qspips_check_flush(s); xlnx_zynqmp_qspips_update_ixr(s); return ret; default: return s->regs[reg]; } } } static void xilinx_spips_write(void *opaque, hwaddr addr, uint64_t value, unsigned size) { int mask = ~0; XilinxSPIPS *s = opaque; DB_PRINT_L(0, "addr=" TARGET_FMT_plx " = %x\n", addr, (unsigned)value); addr >>= 2; switch (addr) { case R_CONFIG: mask = ~(R_CONFIG_RSVD | MAN_START_COM); if ((value & MAN_START_COM) && (s->regs[R_CONFIG] & MAN_START_EN)) { s->man_start_com = true; } break; case R_INTR_STATUS: mask = IXR_ALL; s->regs[R_INTR_STATUS] &= ~(mask & value); goto no_reg_update; case R_INTR_DIS: mask = IXR_ALL; s->regs[R_INTR_MASK] &= ~(mask & value); goto no_reg_update; case R_INTR_EN: mask = IXR_ALL; s->regs[R_INTR_MASK] |= mask & value; goto no_reg_update; case R_EN: mask = 0x1; break; case R_SLAVE_IDLE_COUNT: mask = 0xFF; break; case R_RX_DATA: case R_INTR_MASK: case R_MOD_ID: mask = 0; break; case R_TX_DATA: tx_data_bytes(&s->tx_fifo, (uint32_t)value, s->num_txrx_bytes, s->regs[R_CONFIG] & R_CONFIG_ENDIAN); goto no_reg_update; case R_TXD1: tx_data_bytes(&s->tx_fifo, (uint32_t)value, 1, s->regs[R_CONFIG] & R_CONFIG_ENDIAN); goto no_reg_update; case R_TXD2: tx_data_bytes(&s->tx_fifo, (uint32_t)value, 2, s->regs[R_CONFIG] & R_CONFIG_ENDIAN); goto no_reg_update; case R_TXD3: tx_data_bytes(&s->tx_fifo, (uint32_t)value, 3, s->regs[R_CONFIG] & R_CONFIG_ENDIAN); goto no_reg_update; } s->regs[addr] = (s->regs[addr] & ~mask) | (value & mask); no_reg_update: xilinx_spips_update_cs_lines(s); xilinx_spips_check_flush(s); xilinx_spips_update_cs_lines(s); xilinx_spips_update_ixr(s); } static const MemoryRegionOps spips_ops = { .read = xilinx_spips_read, .write = xilinx_spips_write, .endianness = DEVICE_LITTLE_ENDIAN, }; static void xilinx_qspips_invalidate_mmio_ptr(XilinxQSPIPS *q) { XilinxSPIPS *s = &q->parent_obj; if ((q->mmio_execution_enabled) && (q->lqspi_cached_addr != ~0ULL)) { /* Invalidate the current mapped mmio */ memory_region_invalidate_mmio_ptr(&s->mmlqspi, q->lqspi_cached_addr, LQSPI_CACHE_SIZE); } q->lqspi_cached_addr = ~0ULL; } static void xilinx_qspips_write(void *opaque, hwaddr addr, uint64_t value, unsigned size) { XilinxQSPIPS *q = XILINX_QSPIPS(opaque); XilinxSPIPS *s = XILINX_SPIPS(opaque); xilinx_spips_write(opaque, addr, value, size); addr >>= 2; if (addr == R_LQSPI_CFG) { xilinx_qspips_invalidate_mmio_ptr(q); } if (s->regs[R_CMND] & R_CMND_RXFIFO_DRAIN) { fifo8_reset(&s->rx_fifo); } } static void xlnx_zynqmp_qspips_write(void *opaque, hwaddr addr, uint64_t value, unsigned size) { XlnxZynqMPQSPIPS *s = XLNX_ZYNQMP_QSPIPS(opaque); uint32_t reg = addr / 4; if (reg <= R_MOD_ID) { xilinx_qspips_write(opaque, addr, value, size); } else { switch (reg) { case R_GQSPI_CNFG: if (FIELD_EX32(value, GQSPI_CNFG, GEN_FIFO_START) && ARRAY_FIELD_EX32(s->regs, GQSPI_CNFG, GEN_FIFO_START_MODE)) { s->man_start_com_g = true; } s->regs[reg] = value & ~(R_GQSPI_CNFG_GEN_FIFO_START_MASK); break; case R_GQSPI_GEN_FIFO: if (!fifo32_is_full(&s->fifo_g)) { fifo32_push(&s->fifo_g, value); } break; case R_GQSPI_TXD: tx_data_bytes(&s->tx_fifo_g, (uint32_t)value, 4, ARRAY_FIELD_EX32(s->regs, GQSPI_CNFG, ENDIAN)); break; case R_GQSPI_FIFO_CTRL: if (FIELD_EX32(value, GQSPI_FIFO_CTRL, GENERIC_FIFO_RESET)) { fifo32_reset(&s->fifo_g); } if (FIELD_EX32(value, GQSPI_FIFO_CTRL, TX_FIFO_RESET)) { fifo8_reset(&s->tx_fifo_g); } if (FIELD_EX32(value, GQSPI_FIFO_CTRL, RX_FIFO_RESET)) { fifo8_reset(&s->rx_fifo_g); } break; case R_GQSPI_IDR: s->regs[R_GQSPI_IMR] |= value; break; case R_GQSPI_IER: s->regs[R_GQSPI_IMR] &= ~value; break; case R_GQSPI_ISR: s->regs[R_GQSPI_ISR] &= ~value; break; case R_GQSPI_IMR: case R_GQSPI_RXD: case R_GQSPI_GF_SNAPSHOT: case R_GQSPI_MOD_ID: break; default: s->regs[reg] = value; break; } xlnx_zynqmp_qspips_update_cs_lines(s); xlnx_zynqmp_qspips_check_flush(s); xlnx_zynqmp_qspips_update_cs_lines(s); xlnx_zynqmp_qspips_update_ixr(s); } xlnx_zynqmp_qspips_notify(s); } static const MemoryRegionOps qspips_ops = { .read = xilinx_spips_read, .write = xilinx_qspips_write, .endianness = DEVICE_LITTLE_ENDIAN, }; static const MemoryRegionOps xlnx_zynqmp_qspips_ops = { .read = xlnx_zynqmp_qspips_read, .write = xlnx_zynqmp_qspips_write, .endianness = DEVICE_LITTLE_ENDIAN, }; #define LQSPI_CACHE_SIZE 1024 static void lqspi_load_cache(void *opaque, hwaddr addr) { XilinxQSPIPS *q = opaque; XilinxSPIPS *s = opaque; int i; int flash_addr = ((addr & ~(LQSPI_CACHE_SIZE - 1)) / num_effective_busses(s)); int slave = flash_addr >> LQSPI_ADDRESS_BITS; int cache_entry = 0; uint32_t u_page_save = s->regs[R_LQSPI_STS] & ~LQSPI_CFG_U_PAGE; if (addr < q->lqspi_cached_addr || addr > q->lqspi_cached_addr + LQSPI_CACHE_SIZE - 4) { xilinx_qspips_invalidate_mmio_ptr(q); s->regs[R_LQSPI_STS] &= ~LQSPI_CFG_U_PAGE; s->regs[R_LQSPI_STS] |= slave ? LQSPI_CFG_U_PAGE : 0; DB_PRINT_L(0, "config reg status: %08x\n", s->regs[R_LQSPI_CFG]); fifo8_reset(&s->tx_fifo); fifo8_reset(&s->rx_fifo); /* instruction */ DB_PRINT_L(0, "pushing read instruction: %02x\n", (unsigned)(uint8_t)(s->regs[R_LQSPI_CFG] & LQSPI_CFG_INST_CODE)); fifo8_push(&s->tx_fifo, s->regs[R_LQSPI_CFG] & LQSPI_CFG_INST_CODE); /* read address */ DB_PRINT_L(0, "pushing read address %06x\n", flash_addr); if (s->regs[R_LQSPI_CFG] & LQSPI_CFG_ADDR4) { fifo8_push(&s->tx_fifo, (uint8_t)(flash_addr >> 24)); } fifo8_push(&s->tx_fifo, (uint8_t)(flash_addr >> 16)); fifo8_push(&s->tx_fifo, (uint8_t)(flash_addr >> 8)); fifo8_push(&s->tx_fifo, (uint8_t)flash_addr); /* mode bits */ if (s->regs[R_LQSPI_CFG] & LQSPI_CFG_MODE_EN) { fifo8_push(&s->tx_fifo, extract32(s->regs[R_LQSPI_CFG], LQSPI_CFG_MODE_SHIFT, LQSPI_CFG_MODE_WIDTH)); } /* dummy bytes */ for (i = 0; i < (extract32(s->regs[R_LQSPI_CFG], LQSPI_CFG_DUMMY_SHIFT, LQSPI_CFG_DUMMY_WIDTH)); ++i) { DB_PRINT_L(0, "pushing dummy byte\n"); fifo8_push(&s->tx_fifo, 0); } xilinx_spips_update_cs_lines(s); xilinx_spips_flush_txfifo(s); fifo8_reset(&s->rx_fifo); DB_PRINT_L(0, "starting QSPI data read\n"); while (cache_entry < LQSPI_CACHE_SIZE) { for (i = 0; i < 64; ++i) { tx_data_bytes(&s->tx_fifo, 0, 1, false); } xilinx_spips_flush_txfifo(s); for (i = 0; i < 64; ++i) { rx_data_bytes(&s->rx_fifo, &q->lqspi_buf[cache_entry++], 1); } } s->regs[R_LQSPI_STS] &= ~LQSPI_CFG_U_PAGE; s->regs[R_LQSPI_STS] |= u_page_save; xilinx_spips_update_cs_lines(s); q->lqspi_cached_addr = flash_addr * num_effective_busses(s); } } static void *lqspi_request_mmio_ptr(void *opaque, hwaddr addr, unsigned *size, unsigned *offset) { XilinxQSPIPS *q = opaque; hwaddr offset_within_the_region; if (!q->mmio_execution_enabled) { return NULL; } offset_within_the_region = addr & ~(LQSPI_CACHE_SIZE - 1); lqspi_load_cache(opaque, offset_within_the_region); *size = LQSPI_CACHE_SIZE; *offset = offset_within_the_region; return q->lqspi_buf; } static uint64_t lqspi_read(void *opaque, hwaddr addr, unsigned int size) { XilinxQSPIPS *q = opaque; uint32_t ret; if (addr >= q->lqspi_cached_addr && addr <= q->lqspi_cached_addr + LQSPI_CACHE_SIZE - 4) { uint8_t *retp = &q->lqspi_buf[addr - q->lqspi_cached_addr]; ret = cpu_to_le32(*(uint32_t *)retp); DB_PRINT_L(1, "addr: %08x, data: %08x\n", (unsigned)addr, (unsigned)ret); return ret; } else { lqspi_load_cache(opaque, addr); return lqspi_read(opaque, addr, size); } } static const MemoryRegionOps lqspi_ops = { .read = lqspi_read, .request_ptr = lqspi_request_mmio_ptr, .endianness = DEVICE_NATIVE_ENDIAN, .valid = { .min_access_size = 1, .max_access_size = 4 } }; static void xilinx_spips_realize(DeviceState *dev, Error **errp) { XilinxSPIPS *s = XILINX_SPIPS(dev); SysBusDevice *sbd = SYS_BUS_DEVICE(dev); XilinxSPIPSClass *xsc = XILINX_SPIPS_GET_CLASS(s); qemu_irq *cs; int i; DB_PRINT_L(0, "realized spips\n"); if (s->num_busses > MAX_NUM_BUSSES) { error_setg(errp, "requested number of SPI busses %u exceeds maximum %d", s->num_busses, MAX_NUM_BUSSES); return; } if (s->num_busses < MIN_NUM_BUSSES) { error_setg(errp, "requested number of SPI busses %u is below minimum %d", s->num_busses, MIN_NUM_BUSSES); return; } s->spi = g_new(SSIBus *, s->num_busses); for (i = 0; i < s->num_busses; ++i) { char bus_name[16]; snprintf(bus_name, 16, "spi%d", i); s->spi[i] = ssi_create_bus(dev, bus_name); } s->cs_lines = g_new0(qemu_irq, s->num_cs * s->num_busses); s->cs_lines_state = g_new0(bool, s->num_cs * s->num_busses); for (i = 0, cs = s->cs_lines; i < s->num_busses; ++i, cs += s->num_cs) { ssi_auto_connect_slaves(DEVICE(s), cs, s->spi[i]); } sysbus_init_irq(sbd, &s->irq); for (i = 0; i < s->num_cs * s->num_busses; ++i) { sysbus_init_irq(sbd, &s->cs_lines[i]); } memory_region_init_io(&s->iomem, OBJECT(s), xsc->reg_ops, s, "spi", XLNX_ZYNQMP_SPIPS_R_MAX * 4); sysbus_init_mmio(sbd, &s->iomem); s->irqline = -1; fifo8_create(&s->rx_fifo, xsc->rx_fifo_size); fifo8_create(&s->tx_fifo, xsc->tx_fifo_size); } static void xilinx_qspips_realize(DeviceState *dev, Error **errp) { XilinxSPIPS *s = XILINX_SPIPS(dev); XilinxQSPIPS *q = XILINX_QSPIPS(dev); SysBusDevice *sbd = SYS_BUS_DEVICE(dev); DB_PRINT_L(0, "realized qspips\n"); s->num_busses = 2; s->num_cs = 2; s->num_txrx_bytes = 4; xilinx_spips_realize(dev, errp); memory_region_init_io(&s->mmlqspi, OBJECT(s), &lqspi_ops, s, "lqspi", (1 << LQSPI_ADDRESS_BITS) * 2); sysbus_init_mmio(sbd, &s->mmlqspi); q->lqspi_cached_addr = ~0ULL; /* mmio_execution breaks migration better aborting than having strange * bugs. */ if (q->mmio_execution_enabled) { error_setg(&q->migration_blocker, "enabling mmio_execution breaks migration"); migrate_add_blocker(q->migration_blocker, &error_fatal); } } static void xlnx_zynqmp_qspips_realize(DeviceState *dev, Error **errp) { XlnxZynqMPQSPIPS *s = XLNX_ZYNQMP_QSPIPS(dev); XilinxSPIPSClass *xsc = XILINX_SPIPS_GET_CLASS(s); xilinx_qspips_realize(dev, errp); fifo8_create(&s->rx_fifo_g, xsc->rx_fifo_size); fifo8_create(&s->tx_fifo_g, xsc->tx_fifo_size); fifo32_create(&s->fifo_g, 32); } static void xlnx_zynqmp_qspips_init(Object *obj) { XlnxZynqMPQSPIPS *rq = XLNX_ZYNQMP_QSPIPS(obj); object_property_add_link(obj, "stream-connected-dma", TYPE_STREAM_SLAVE, (Object **)&rq->dma, object_property_allow_set_link, OBJ_PROP_LINK_STRONG, NULL); } static int xilinx_spips_post_load(void *opaque, int version_id) { xilinx_spips_update_ixr((XilinxSPIPS *)opaque); xilinx_spips_update_cs_lines((XilinxSPIPS *)opaque); return 0; } static const VMStateDescription vmstate_xilinx_spips = { .name = "xilinx_spips", .version_id = 2, .minimum_version_id = 2, .post_load = xilinx_spips_post_load, .fields = (VMStateField[]) { VMSTATE_FIFO8(tx_fifo, XilinxSPIPS), VMSTATE_FIFO8(rx_fifo, XilinxSPIPS), VMSTATE_UINT32_ARRAY(regs, XilinxSPIPS, XLNX_SPIPS_R_MAX), VMSTATE_UINT8(snoop_state, XilinxSPIPS), VMSTATE_END_OF_LIST() } }; static int xlnx_zynqmp_qspips_post_load(void *opaque, int version_id) { XlnxZynqMPQSPIPS *s = (XlnxZynqMPQSPIPS *)opaque; XilinxSPIPS *qs = XILINX_SPIPS(s); if (ARRAY_FIELD_EX32(s->regs, GQSPI_SELECT, GENERIC_QSPI_EN) && fifo8_is_empty(&qs->rx_fifo) && fifo8_is_empty(&qs->tx_fifo)) { xlnx_zynqmp_qspips_update_ixr(s); xlnx_zynqmp_qspips_update_cs_lines(s); } return 0; } static const VMStateDescription vmstate_xilinx_qspips = { .name = "xilinx_qspips", .version_id = 1, .minimum_version_id = 1, .fields = (VMStateField[]) { VMSTATE_STRUCT(parent_obj, XilinxQSPIPS, 0, vmstate_xilinx_spips, XilinxSPIPS), VMSTATE_END_OF_LIST() } }; static const VMStateDescription vmstate_xlnx_zynqmp_qspips = { .name = "xlnx_zynqmp_qspips", .version_id = 1, .minimum_version_id = 1, .post_load = xlnx_zynqmp_qspips_post_load, .fields = (VMStateField[]) { VMSTATE_STRUCT(parent_obj, XlnxZynqMPQSPIPS, 0, vmstate_xilinx_qspips, XilinxQSPIPS), VMSTATE_FIFO8(tx_fifo_g, XlnxZynqMPQSPIPS), VMSTATE_FIFO8(rx_fifo_g, XlnxZynqMPQSPIPS), VMSTATE_FIFO32(fifo_g, XlnxZynqMPQSPIPS), VMSTATE_UINT32_ARRAY(regs, XlnxZynqMPQSPIPS, XLNX_ZYNQMP_SPIPS_R_MAX), VMSTATE_END_OF_LIST() } }; static Property xilinx_qspips_properties[] = { /* We had to turn this off for 2.10 as it is not compatible with migration. * It can be enabled but will prevent the device to be migrated. * This will go aways when a fix will be released. */ DEFINE_PROP_BOOL("x-mmio-exec", XilinxQSPIPS, mmio_execution_enabled, false), DEFINE_PROP_END_OF_LIST(), }; static Property xilinx_spips_properties[] = { DEFINE_PROP_UINT8("num-busses", XilinxSPIPS, num_busses, 1), DEFINE_PROP_UINT8("num-ss-bits", XilinxSPIPS, num_cs, 4), DEFINE_PROP_UINT8("num-txrx-bytes", XilinxSPIPS, num_txrx_bytes, 1), DEFINE_PROP_END_OF_LIST(), }; static void xilinx_qspips_class_init(ObjectClass *klass, void * data) { DeviceClass *dc = DEVICE_CLASS(klass); XilinxSPIPSClass *xsc = XILINX_SPIPS_CLASS(klass); dc->realize = xilinx_qspips_realize; dc->props = xilinx_qspips_properties; xsc->reg_ops = &qspips_ops; xsc->rx_fifo_size = RXFF_A_Q; xsc->tx_fifo_size = TXFF_A_Q; } static void xilinx_spips_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); XilinxSPIPSClass *xsc = XILINX_SPIPS_CLASS(klass); dc->realize = xilinx_spips_realize; dc->reset = xilinx_spips_reset; dc->props = xilinx_spips_properties; dc->vmsd = &vmstate_xilinx_spips; xsc->reg_ops = &spips_ops; xsc->rx_fifo_size = RXFF_A; xsc->tx_fifo_size = TXFF_A; } static void xlnx_zynqmp_qspips_class_init(ObjectClass *klass, void * data) { DeviceClass *dc = DEVICE_CLASS(klass); XilinxSPIPSClass *xsc = XILINX_SPIPS_CLASS(klass); dc->realize = xlnx_zynqmp_qspips_realize; dc->reset = xlnx_zynqmp_qspips_reset; dc->vmsd = &vmstate_xlnx_zynqmp_qspips; xsc->reg_ops = &xlnx_zynqmp_qspips_ops; xsc->rx_fifo_size = RXFF_A_Q; xsc->tx_fifo_size = TXFF_A_Q; } static const TypeInfo xilinx_spips_info = { .name = TYPE_XILINX_SPIPS, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(XilinxSPIPS), .class_init = xilinx_spips_class_init, .class_size = sizeof(XilinxSPIPSClass), }; static const TypeInfo xilinx_qspips_info = { .name = TYPE_XILINX_QSPIPS, .parent = TYPE_XILINX_SPIPS, .instance_size = sizeof(XilinxQSPIPS), .class_init = xilinx_qspips_class_init, }; static const TypeInfo xlnx_zynqmp_qspips_info = { .name = TYPE_XLNX_ZYNQMP_QSPIPS, .parent = TYPE_XILINX_QSPIPS, .instance_size = sizeof(XlnxZynqMPQSPIPS), .instance_init = xlnx_zynqmp_qspips_init, .class_init = xlnx_zynqmp_qspips_class_init, }; static void xilinx_spips_register_types(void) { type_register_static(&xilinx_spips_info); type_register_static(&xilinx_qspips_info); type_register_static(&xlnx_zynqmp_qspips_info); } type_init(xilinx_spips_register_types)