| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * (C) Copyright 2013 Xilinx, Inc. |
| * (C) Copyright 2015 Jagan Teki <jteki@openedev.com> |
| * |
| * Xilinx Zynq Quad-SPI(QSPI) controller driver (master mode only) |
| */ |
| |
| #include <common.h> |
| #include <dm.h> |
| #include <malloc.h> |
| #include <spi.h> |
| #include <asm/io.h> |
| |
| DECLARE_GLOBAL_DATA_PTR; |
| |
| /* zynq qspi register bit masks ZYNQ_QSPI_<REG>_<BIT>_MASK */ |
| #define ZYNQ_QSPI_CR_IFMODE_MASK BIT(31) /* Flash intrface mode*/ |
| #define ZYNQ_QSPI_CR_MSA_MASK BIT(15) /* Manual start enb */ |
| #define ZYNQ_QSPI_CR_MCS_MASK BIT(14) /* Manual chip select */ |
| #define ZYNQ_QSPI_CR_PCS_MASK BIT(10) /* Peri chip select */ |
| #define ZYNQ_QSPI_CR_FW_MASK GENMASK(7, 6) /* FIFO width */ |
| #define ZYNQ_QSPI_CR_SS_MASK GENMASK(13, 10) /* Slave Select */ |
| #define ZYNQ_QSPI_CR_BAUD_MASK GENMASK(5, 3) /* Baud rate div */ |
| #define ZYNQ_QSPI_CR_CPHA_MASK BIT(2) /* Clock phase */ |
| #define ZYNQ_QSPI_CR_CPOL_MASK BIT(1) /* Clock polarity */ |
| #define ZYNQ_QSPI_CR_MSTREN_MASK BIT(0) /* Mode select */ |
| #define ZYNQ_QSPI_IXR_RXNEMPTY_MASK BIT(4) /* RX_FIFO_not_empty */ |
| #define ZYNQ_QSPI_IXR_TXOW_MASK BIT(2) /* TX_FIFO_not_full */ |
| #define ZYNQ_QSPI_IXR_ALL_MASK GENMASK(6, 0) /* All IXR bits */ |
| #define ZYNQ_QSPI_ENR_SPI_EN_MASK BIT(0) /* SPI Enable */ |
| #define ZYNQ_QSPI_LQSPICFG_LQMODE_MASK BIT(31) /* Linear QSPI Mode */ |
| |
| /* zynq qspi Transmit Data Register */ |
| #define ZYNQ_QSPI_TXD_00_00_OFFSET 0x1C /* Transmit 4-byte inst */ |
| #define ZYNQ_QSPI_TXD_00_01_OFFSET 0x80 /* Transmit 1-byte inst */ |
| #define ZYNQ_QSPI_TXD_00_10_OFFSET 0x84 /* Transmit 2-byte inst */ |
| #define ZYNQ_QSPI_TXD_00_11_OFFSET 0x88 /* Transmit 3-byte inst */ |
| |
| #define ZYNQ_QSPI_TXFIFO_THRESHOLD 1 /* Tx FIFO threshold level*/ |
| #define ZYNQ_QSPI_RXFIFO_THRESHOLD 32 /* Rx FIFO threshold level */ |
| |
| #define ZYNQ_QSPI_CR_BAUD_MAX 8 /* Baud rate divisor max val */ |
| #define ZYNQ_QSPI_CR_BAUD_SHIFT 3 /* Baud rate divisor shift */ |
| #define ZYNQ_QSPI_CR_SS_SHIFT 10 /* Slave select shift */ |
| |
| #define ZYNQ_QSPI_FIFO_DEPTH 63 |
| #ifndef CONFIG_SYS_ZYNQ_QSPI_WAIT |
| #define CONFIG_SYS_ZYNQ_QSPI_WAIT CONFIG_SYS_HZ/100 /* 10 ms */ |
| #endif |
| |
| /* zynq qspi register set */ |
| struct zynq_qspi_regs { |
| u32 cr; /* 0x00 */ |
| u32 isr; /* 0x04 */ |
| u32 ier; /* 0x08 */ |
| u32 idr; /* 0x0C */ |
| u32 imr; /* 0x10 */ |
| u32 enr; /* 0x14 */ |
| u32 dr; /* 0x18 */ |
| u32 txd0r; /* 0x1C */ |
| u32 drxr; /* 0x20 */ |
| u32 sicr; /* 0x24 */ |
| u32 txftr; /* 0x28 */ |
| u32 rxftr; /* 0x2C */ |
| u32 gpior; /* 0x30 */ |
| u32 reserved0[19]; |
| u32 txd1r; /* 0x80 */ |
| u32 txd2r; /* 0x84 */ |
| u32 txd3r; /* 0x88 */ |
| u32 reserved1[5]; |
| u32 lqspicfg; /* 0xA0 */ |
| u32 lqspists; /* 0xA4 */ |
| }; |
| |
| /* zynq qspi platform data */ |
| struct zynq_qspi_platdata { |
| struct zynq_qspi_regs *regs; |
| u32 frequency; /* input frequency */ |
| u32 speed_hz; |
| }; |
| |
| /* zynq qspi priv */ |
| struct zynq_qspi_priv { |
| struct zynq_qspi_regs *regs; |
| u8 cs; |
| u8 mode; |
| u8 fifo_depth; |
| u32 freq; /* required frequency */ |
| const void *tx_buf; |
| void *rx_buf; |
| unsigned len; |
| int bytes_to_transfer; |
| int bytes_to_receive; |
| unsigned int is_inst; |
| unsigned cs_change:1; |
| }; |
| |
| static int zynq_qspi_ofdata_to_platdata(struct udevice *bus) |
| { |
| struct zynq_qspi_platdata *plat = bus->platdata; |
| const void *blob = gd->fdt_blob; |
| int node = dev_of_offset(bus); |
| |
| plat->regs = (struct zynq_qspi_regs *)fdtdec_get_addr(blob, |
| node, "reg"); |
| |
| /* FIXME: Use 166MHz as a suitable default */ |
| plat->frequency = fdtdec_get_int(blob, node, "spi-max-frequency", |
| 166666666); |
| plat->speed_hz = plat->frequency / 2; |
| |
| debug("%s: regs=%p max-frequency=%d\n", __func__, |
| plat->regs, plat->frequency); |
| |
| return 0; |
| } |
| |
| static void zynq_qspi_init_hw(struct zynq_qspi_priv *priv) |
| { |
| struct zynq_qspi_regs *regs = priv->regs; |
| u32 confr; |
| |
| /* Disable QSPI */ |
| writel(~ZYNQ_QSPI_ENR_SPI_EN_MASK, ®s->enr); |
| |
| /* Disable Interrupts */ |
| writel(ZYNQ_QSPI_IXR_ALL_MASK, ®s->idr); |
| |
| /* Clear the TX and RX threshold reg */ |
| writel(ZYNQ_QSPI_TXFIFO_THRESHOLD, ®s->txftr); |
| writel(ZYNQ_QSPI_RXFIFO_THRESHOLD, ®s->rxftr); |
| |
| /* Clear the RX FIFO */ |
| while (readl(®s->isr) & ZYNQ_QSPI_IXR_RXNEMPTY_MASK) |
| readl(®s->drxr); |
| |
| /* Clear Interrupts */ |
| writel(ZYNQ_QSPI_IXR_ALL_MASK, ®s->isr); |
| |
| /* Manual slave select and Auto start */ |
| confr = readl(®s->cr); |
| confr &= ~ZYNQ_QSPI_CR_MSA_MASK; |
| confr |= ZYNQ_QSPI_CR_IFMODE_MASK | ZYNQ_QSPI_CR_MCS_MASK | |
| ZYNQ_QSPI_CR_PCS_MASK | ZYNQ_QSPI_CR_FW_MASK | |
| ZYNQ_QSPI_CR_MSTREN_MASK; |
| writel(confr, ®s->cr); |
| |
| /* Disable the LQSPI feature */ |
| confr = readl(®s->lqspicfg); |
| confr &= ~ZYNQ_QSPI_LQSPICFG_LQMODE_MASK; |
| writel(confr, ®s->lqspicfg); |
| |
| /* Enable SPI */ |
| writel(ZYNQ_QSPI_ENR_SPI_EN_MASK, ®s->enr); |
| } |
| |
| static int zynq_qspi_probe(struct udevice *bus) |
| { |
| struct zynq_qspi_platdata *plat = dev_get_platdata(bus); |
| struct zynq_qspi_priv *priv = dev_get_priv(bus); |
| |
| priv->regs = plat->regs; |
| priv->fifo_depth = ZYNQ_QSPI_FIFO_DEPTH; |
| |
| /* init the zynq spi hw */ |
| zynq_qspi_init_hw(priv); |
| |
| return 0; |
| } |
| |
| /* |
| * zynq_qspi_read_data - Copy data to RX buffer |
| * @zqspi: Pointer to the zynq_qspi structure |
| * @data: The 32 bit variable where data is stored |
| * @size: Number of bytes to be copied from data to RX buffer |
| */ |
| static void zynq_qspi_read_data(struct zynq_qspi_priv *priv, u32 data, u8 size) |
| { |
| u8 byte3; |
| |
| debug("%s: data 0x%04x rx_buf addr: 0x%08x size %d\n", __func__ , |
| data, (unsigned)(priv->rx_buf), size); |
| |
| if (priv->rx_buf) { |
| switch (size) { |
| case 1: |
| *((u8 *)priv->rx_buf) = data; |
| priv->rx_buf += 1; |
| break; |
| case 2: |
| *((u16 *)priv->rx_buf) = data; |
| priv->rx_buf += 2; |
| break; |
| case 3: |
| *((u16 *)priv->rx_buf) = data; |
| priv->rx_buf += 2; |
| byte3 = (u8)(data >> 16); |
| *((u8 *)priv->rx_buf) = byte3; |
| priv->rx_buf += 1; |
| break; |
| case 4: |
| /* Can not assume word aligned buffer */ |
| memcpy(priv->rx_buf, &data, size); |
| priv->rx_buf += 4; |
| break; |
| default: |
| /* This will never execute */ |
| break; |
| } |
| } |
| priv->bytes_to_receive -= size; |
| if (priv->bytes_to_receive < 0) |
| priv->bytes_to_receive = 0; |
| } |
| |
| /* |
| * zynq_qspi_write_data - Copy data from TX buffer |
| * @zqspi: Pointer to the zynq_qspi structure |
| * @data: Pointer to the 32 bit variable where data is to be copied |
| * @size: Number of bytes to be copied from TX buffer to data |
| */ |
| static void zynq_qspi_write_data(struct zynq_qspi_priv *priv, |
| u32 *data, u8 size) |
| { |
| if (priv->tx_buf) { |
| switch (size) { |
| case 1: |
| *data = *((u8 *)priv->tx_buf); |
| priv->tx_buf += 1; |
| *data |= 0xFFFFFF00; |
| break; |
| case 2: |
| *data = *((u16 *)priv->tx_buf); |
| priv->tx_buf += 2; |
| *data |= 0xFFFF0000; |
| break; |
| case 3: |
| *data = *((u16 *)priv->tx_buf); |
| priv->tx_buf += 2; |
| *data |= (*((u8 *)priv->tx_buf) << 16); |
| priv->tx_buf += 1; |
| *data |= 0xFF000000; |
| break; |
| case 4: |
| /* Can not assume word aligned buffer */ |
| memcpy(data, priv->tx_buf, size); |
| priv->tx_buf += 4; |
| break; |
| default: |
| /* This will never execute */ |
| break; |
| } |
| } else { |
| *data = 0; |
| } |
| |
| debug("%s: data 0x%08x tx_buf addr: 0x%08x size %d\n", __func__, |
| *data, (u32)priv->tx_buf, size); |
| |
| priv->bytes_to_transfer -= size; |
| if (priv->bytes_to_transfer < 0) |
| priv->bytes_to_transfer = 0; |
| } |
| |
| static void zynq_qspi_chipselect(struct zynq_qspi_priv *priv, int is_on) |
| { |
| u32 confr; |
| struct zynq_qspi_regs *regs = priv->regs; |
| |
| confr = readl(®s->cr); |
| |
| if (is_on) { |
| /* Select the slave */ |
| confr &= ~ZYNQ_QSPI_CR_SS_MASK; |
| confr |= (~(1 << priv->cs) << ZYNQ_QSPI_CR_SS_SHIFT) & |
| ZYNQ_QSPI_CR_SS_MASK; |
| } else |
| /* Deselect the slave */ |
| confr |= ZYNQ_QSPI_CR_SS_MASK; |
| |
| writel(confr, ®s->cr); |
| } |
| |
| /* |
| * zynq_qspi_fill_tx_fifo - Fills the TX FIFO with as many bytes as possible |
| * @zqspi: Pointer to the zynq_qspi structure |
| */ |
| static void zynq_qspi_fill_tx_fifo(struct zynq_qspi_priv *priv, u32 size) |
| { |
| u32 data = 0; |
| u32 fifocount = 0; |
| unsigned len, offset; |
| struct zynq_qspi_regs *regs = priv->regs; |
| static const unsigned offsets[4] = { |
| ZYNQ_QSPI_TXD_00_00_OFFSET, ZYNQ_QSPI_TXD_00_01_OFFSET, |
| ZYNQ_QSPI_TXD_00_10_OFFSET, ZYNQ_QSPI_TXD_00_11_OFFSET }; |
| |
| while ((fifocount < size) && |
| (priv->bytes_to_transfer > 0)) { |
| if (priv->bytes_to_transfer >= 4) { |
| if (priv->tx_buf) { |
| memcpy(&data, priv->tx_buf, 4); |
| priv->tx_buf += 4; |
| } else { |
| data = 0; |
| } |
| writel(data, ®s->txd0r); |
| priv->bytes_to_transfer -= 4; |
| fifocount++; |
| } else { |
| /* Write TXD1, TXD2, TXD3 only if TxFIFO is empty. */ |
| if (!(readl(®s->isr) |
| & ZYNQ_QSPI_IXR_TXOW_MASK) && |
| !priv->rx_buf) |
| return; |
| len = priv->bytes_to_transfer; |
| zynq_qspi_write_data(priv, &data, len); |
| offset = (priv->rx_buf) ? offsets[0] : offsets[len]; |
| writel(data, ®s->cr + (offset / 4)); |
| } |
| } |
| } |
| |
| /* |
| * zynq_qspi_irq_poll - Interrupt service routine of the QSPI controller |
| * @zqspi: Pointer to the zynq_qspi structure |
| * |
| * This function handles TX empty and Mode Fault interrupts only. |
| * On TX empty interrupt this function reads the received data from RX FIFO and |
| * fills the TX FIFO if there is any data remaining to be transferred. |
| * On Mode Fault interrupt this function indicates that transfer is completed, |
| * the SPI subsystem will identify the error as the remaining bytes to be |
| * transferred is non-zero. |
| * |
| * returns: 0 for poll timeout |
| * 1 transfer operation complete |
| */ |
| static int zynq_qspi_irq_poll(struct zynq_qspi_priv *priv) |
| { |
| struct zynq_qspi_regs *regs = priv->regs; |
| u32 rxindex = 0; |
| u32 rxcount; |
| u32 status, timeout; |
| |
| /* Poll until any of the interrupt status bits are set */ |
| timeout = get_timer(0); |
| do { |
| status = readl(®s->isr); |
| } while ((status == 0) && |
| (get_timer(timeout) < CONFIG_SYS_ZYNQ_QSPI_WAIT)); |
| |
| if (status == 0) { |
| printf("zynq_qspi_irq_poll: Timeout!\n"); |
| return -ETIMEDOUT; |
| } |
| |
| writel(status, ®s->isr); |
| |
| /* Disable all interrupts */ |
| writel(ZYNQ_QSPI_IXR_ALL_MASK, ®s->idr); |
| if ((status & ZYNQ_QSPI_IXR_TXOW_MASK) || |
| (status & ZYNQ_QSPI_IXR_RXNEMPTY_MASK)) { |
| /* |
| * This bit is set when Tx FIFO has < THRESHOLD entries. We have |
| * the THRESHOLD value set to 1, so this bit indicates Tx FIFO |
| * is empty |
| */ |
| rxcount = priv->bytes_to_receive - priv->bytes_to_transfer; |
| rxcount = (rxcount % 4) ? ((rxcount/4)+1) : (rxcount/4); |
| while ((rxindex < rxcount) && |
| (rxindex < ZYNQ_QSPI_RXFIFO_THRESHOLD)) { |
| /* Read out the data from the RX FIFO */ |
| u32 data; |
| data = readl(®s->drxr); |
| |
| if (priv->bytes_to_receive >= 4) { |
| if (priv->rx_buf) { |
| memcpy(priv->rx_buf, &data, 4); |
| priv->rx_buf += 4; |
| } |
| priv->bytes_to_receive -= 4; |
| } else { |
| zynq_qspi_read_data(priv, data, |
| priv->bytes_to_receive); |
| } |
| rxindex++; |
| } |
| |
| if (priv->bytes_to_transfer) { |
| /* There is more data to send */ |
| zynq_qspi_fill_tx_fifo(priv, |
| ZYNQ_QSPI_RXFIFO_THRESHOLD); |
| |
| writel(ZYNQ_QSPI_IXR_ALL_MASK, ®s->ier); |
| } else { |
| /* |
| * If transfer and receive is completed then only send |
| * complete signal |
| */ |
| if (!priv->bytes_to_receive) { |
| /* return operation complete */ |
| writel(ZYNQ_QSPI_IXR_ALL_MASK, |
| ®s->idr); |
| return 1; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * zynq_qspi_start_transfer - Initiates the QSPI transfer |
| * @qspi: Pointer to the spi_device structure |
| * @transfer: Pointer to the spi_transfer structure which provide information |
| * about next transfer parameters |
| * |
| * This function fills the TX FIFO, starts the QSPI transfer, and waits for the |
| * transfer to be completed. |
| * |
| * returns: Number of bytes transferred in the last transfer |
| */ |
| static int zynq_qspi_start_transfer(struct zynq_qspi_priv *priv) |
| { |
| u32 data = 0; |
| struct zynq_qspi_regs *regs = priv->regs; |
| |
| debug("%s: qspi: 0x%08x transfer: 0x%08x len: %d\n", __func__, |
| (u32)priv, (u32)priv, priv->len); |
| |
| priv->bytes_to_transfer = priv->len; |
| priv->bytes_to_receive = priv->len; |
| |
| if (priv->len < 4) |
| zynq_qspi_fill_tx_fifo(priv, priv->len); |
| else |
| zynq_qspi_fill_tx_fifo(priv, priv->fifo_depth); |
| |
| writel(ZYNQ_QSPI_IXR_ALL_MASK, ®s->ier); |
| |
| /* wait for completion */ |
| do { |
| data = zynq_qspi_irq_poll(priv); |
| } while (data == 0); |
| |
| return (priv->len) - (priv->bytes_to_transfer); |
| } |
| |
| static int zynq_qspi_transfer(struct zynq_qspi_priv *priv) |
| { |
| unsigned cs_change = 1; |
| int status = 0; |
| |
| while (1) { |
| /* Select the chip if required */ |
| if (cs_change) |
| zynq_qspi_chipselect(priv, 1); |
| |
| cs_change = priv->cs_change; |
| |
| if (!priv->tx_buf && !priv->rx_buf && priv->len) { |
| status = -1; |
| break; |
| } |
| |
| /* Request the transfer */ |
| if (priv->len) { |
| status = zynq_qspi_start_transfer(priv); |
| priv->is_inst = 0; |
| } |
| |
| if (status != priv->len) { |
| if (status > 0) |
| status = -EMSGSIZE; |
| debug("zynq_qspi_transfer:%d len:%d\n", |
| status, priv->len); |
| break; |
| } |
| status = 0; |
| |
| if (cs_change) |
| /* Deselect the chip */ |
| zynq_qspi_chipselect(priv, 0); |
| |
| break; |
| } |
| |
| return status; |
| } |
| |
| static int zynq_qspi_claim_bus(struct udevice *dev) |
| { |
| struct udevice *bus = dev->parent; |
| struct zynq_qspi_priv *priv = dev_get_priv(bus); |
| struct zynq_qspi_regs *regs = priv->regs; |
| |
| writel(ZYNQ_QSPI_ENR_SPI_EN_MASK, ®s->enr); |
| |
| return 0; |
| } |
| |
| static int zynq_qspi_release_bus(struct udevice *dev) |
| { |
| struct udevice *bus = dev->parent; |
| struct zynq_qspi_priv *priv = dev_get_priv(bus); |
| struct zynq_qspi_regs *regs = priv->regs; |
| |
| writel(~ZYNQ_QSPI_ENR_SPI_EN_MASK, ®s->enr); |
| |
| return 0; |
| } |
| |
| static int zynq_qspi_xfer(struct udevice *dev, unsigned int bitlen, |
| const void *dout, void *din, unsigned long flags) |
| { |
| struct udevice *bus = dev->parent; |
| struct zynq_qspi_priv *priv = dev_get_priv(bus); |
| struct dm_spi_slave_platdata *slave_plat = dev_get_parent_platdata(dev); |
| |
| priv->cs = slave_plat->cs; |
| priv->tx_buf = dout; |
| priv->rx_buf = din; |
| priv->len = bitlen / 8; |
| |
| debug("zynq_qspi_xfer: bus:%i cs:%i bitlen:%i len:%i flags:%lx\n", |
| bus->seq, slave_plat->cs, bitlen, priv->len, flags); |
| |
| /* |
| * Festering sore. |
| * Assume that the beginning of a transfer with bits to |
| * transmit must contain a device command. |
| */ |
| if (dout && flags & SPI_XFER_BEGIN) |
| priv->is_inst = 1; |
| else |
| priv->is_inst = 0; |
| |
| if (flags & SPI_XFER_END) |
| priv->cs_change = 1; |
| else |
| priv->cs_change = 0; |
| |
| zynq_qspi_transfer(priv); |
| |
| return 0; |
| } |
| |
| static int zynq_qspi_set_speed(struct udevice *bus, uint speed) |
| { |
| struct zynq_qspi_platdata *plat = bus->platdata; |
| struct zynq_qspi_priv *priv = dev_get_priv(bus); |
| struct zynq_qspi_regs *regs = priv->regs; |
| uint32_t confr; |
| u8 baud_rate_val = 0; |
| |
| if (speed > plat->frequency) |
| speed = plat->frequency; |
| |
| /* Set the clock frequency */ |
| confr = readl(®s->cr); |
| if (speed == 0) { |
| /* Set baudrate x8, if the freq is 0 */ |
| baud_rate_val = 0x2; |
| } else if (plat->speed_hz != speed) { |
| while ((baud_rate_val < ZYNQ_QSPI_CR_BAUD_MAX) && |
| ((plat->frequency / |
| (2 << baud_rate_val)) > speed)) |
| baud_rate_val++; |
| |
| plat->speed_hz = speed / (2 << baud_rate_val); |
| } |
| confr &= ~ZYNQ_QSPI_CR_BAUD_MASK; |
| confr |= (baud_rate_val << ZYNQ_QSPI_CR_BAUD_SHIFT); |
| |
| writel(confr, ®s->cr); |
| priv->freq = speed; |
| |
| debug("%s: regs=%p, speed=%d\n", __func__, priv->regs, priv->freq); |
| |
| return 0; |
| } |
| |
| static int zynq_qspi_set_mode(struct udevice *bus, uint mode) |
| { |
| struct zynq_qspi_priv *priv = dev_get_priv(bus); |
| struct zynq_qspi_regs *regs = priv->regs; |
| uint32_t confr; |
| |
| /* Set the SPI Clock phase and polarities */ |
| confr = readl(®s->cr); |
| confr &= ~(ZYNQ_QSPI_CR_CPHA_MASK | ZYNQ_QSPI_CR_CPOL_MASK); |
| |
| if (mode & SPI_CPHA) |
| confr |= ZYNQ_QSPI_CR_CPHA_MASK; |
| if (mode & SPI_CPOL) |
| confr |= ZYNQ_QSPI_CR_CPOL_MASK; |
| |
| writel(confr, ®s->cr); |
| priv->mode = mode; |
| |
| debug("%s: regs=%p, mode=%d\n", __func__, priv->regs, priv->mode); |
| |
| return 0; |
| } |
| |
| static const struct dm_spi_ops zynq_qspi_ops = { |
| .claim_bus = zynq_qspi_claim_bus, |
| .release_bus = zynq_qspi_release_bus, |
| .xfer = zynq_qspi_xfer, |
| .set_speed = zynq_qspi_set_speed, |
| .set_mode = zynq_qspi_set_mode, |
| }; |
| |
| static const struct udevice_id zynq_qspi_ids[] = { |
| { .compatible = "xlnx,zynq-qspi-1.0" }, |
| { } |
| }; |
| |
| U_BOOT_DRIVER(zynq_qspi) = { |
| .name = "zynq_qspi", |
| .id = UCLASS_SPI, |
| .of_match = zynq_qspi_ids, |
| .ops = &zynq_qspi_ops, |
| .ofdata_to_platdata = zynq_qspi_ofdata_to_platdata, |
| .platdata_auto_alloc_size = sizeof(struct zynq_qspi_platdata), |
| .priv_auto_alloc_size = sizeof(struct zynq_qspi_priv), |
| .probe = zynq_qspi_probe, |
| }; |