From patchwork Wed Dec 18 15:29:56 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jagannadha Sutradharudu Teki X-Patchwork-Id: 302945 X-Patchwork-Delegate: albert.aribaud@free.fr Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from theia.denx.de (theia.denx.de [85.214.87.163]) by ozlabs.org (Postfix) with ESMTP id 61F1C2C00AA for ; Thu, 19 Dec 2013 02:36:11 +1100 (EST) Received: from localhost (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 613514AA04; Wed, 18 Dec 2013 16:36:00 +0100 (CET) X-Virus-Scanned: Debian amavisd-new at theia.denx.de Received: from theia.denx.de ([127.0.0.1]) by localhost (theia.denx.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id n34yImpQR2u7; Wed, 18 Dec 2013 16:36:00 +0100 (CET) Received: from theia.denx.de (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 83FBC4AA2A; Wed, 18 Dec 2013 16:33:11 +0100 (CET) Received: from localhost (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 8D1BD4A9F5 for ; Wed, 18 Dec 2013 16:32:30 +0100 (CET) X-Virus-Scanned: Debian amavisd-new at theia.denx.de Received: from theia.denx.de ([127.0.0.1]) by localhost (theia.denx.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 6lW9Fk28g+I6 for ; Wed, 18 Dec 2013 16:32:24 +0100 (CET) X-policyd-weight: NOT_IN_SBL_XBL_SPAMHAUS=-1.5 NOT_IN_SPAMCOP=-1.5 NOT_IN_BL_NJABL=-1.5 (only DNSBL check requested) Received: from ch1outboundpool.messaging.microsoft.com (ch1ehsobe002.messaging.microsoft.com [216.32.181.182]) by theia.denx.de (Postfix) with ESMTPS id 8AF344A9B4 for ; Wed, 18 Dec 2013 16:31:12 +0100 (CET) Received: from mail97-ch1-R.bigfish.com (10.43.68.225) by CH1EHSOBE001.bigfish.com (10.43.70.51) with Microsoft SMTP Server id 14.1.225.22; Wed, 18 Dec 2013 15:31:05 +0000 Received: from mail97-ch1 (localhost [127.0.0.1]) by mail97-ch1-R.bigfish.com (Postfix) with ESMTP id 46B0040068C; Wed, 18 Dec 2013 15:31:05 +0000 (UTC) X-Forefront-Antispam-Report: CIP:149.199.60.83; KIP:(null); UIP:(null); IPV:NLI; H:xsj-gw1; RD:unknown-60-83.xilinx.com; EFVD:NLI X-SpamScore: 4 X-BigFish: VPS4(zz8b9ckzz1f42h2148h208ch1ee6h1de0h1fdah2073h2146h1202h1e76h2189h1d1ah1d2ah1fc6hzz1de098h8275bh1de097hz2fh95h839hd24hf0ah119dh1288h12a5h12a9h12bdh12e5h137ah139eh13b6h1441h14ddh1504h1537h162dh1631h1758h1898h18e1h1946h19b5h1b0ah224fh1d0ch1d2eh1d3fh1dfeh1dffh1e1dh1e23h1fe8h1ff5h2218h2216h226dh2327h2336h906i1155h) Received-SPF: pass (mail97-ch1: domain of xilinx.com designates 149.199.60.83 as permitted sender) client-ip=149.199.60.83; envelope-from=jagannadha.sutradharudu-teki@xilinx.com; helo=xsj-gw1 ; helo=xsj-gw1 ; Received: from mail97-ch1 (localhost.localdomain [127.0.0.1]) by mail97-ch1 (MessageSwitch) id 1387380663526244_16036; Wed, 18 Dec 2013 15:31:03 +0000 (UTC) Received: from CH1EHSMHS030.bigfish.com (snatpool2.int.messaging.microsoft.com [10.43.68.236]) by mail97-ch1.bigfish.com (Postfix) with ESMTP id 7C26B44004C; Wed, 18 Dec 2013 15:31:03 +0000 (UTC) Received: from xsj-gw1 (149.199.60.83) by CH1EHSMHS030.bigfish.com (10.43.70.30) with Microsoft SMTP Server id 14.16.227.3; Wed, 18 Dec 2013 15:31:03 +0000 Received: from unknown-38-66.xilinx.com ([149.199.38.66] helo=xsj-smtp1) by xsj-gw1 with esmtp (Exim 4.63) (envelope-from ) id 1VtJ5S-0005JU-Sh; Wed, 18 Dec 2013 07:31:02 -0800 From: Jagannadha Sutradharudu Teki To: Date: Wed, 18 Dec 2013 20:59:56 +0530 X-Mailer: git-send-email 1.8.3 In-Reply-To: <1387380620-29228-1-git-send-email-jaganna@xilinx.com> References: <1387380620-29228-1-git-send-email-jaganna@xilinx.com> X-RCIS-Action: ALLOW MIME-Version: 1.0 Message-ID: <5f2721b0-ecf2-4135-802b-57bdb0f7c6f6@CH1EHSMHS030.ehs.local> X-OriginatorOrg: xilinx.com X-FOPE-CONNECTOR: Id%0$Dn%*$RO%0$TLS%0$FQDN%$TlsDn% Cc: Tom Rini , Jagannadha Sutradharudu Teki Subject: [U-Boot] [PATCH v2 11/35] spi: Add zynq qspi controller driver X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.11 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: u-boot-bounces@lists.denx.de Errors-To: u-boot-bounces@lists.denx.de Zynq qspi controller driver supports single bus with singe chipselect. Zynq qspi can be operated in below connection modes - single qspi - dual qspi, with dual stacked - dual qspi, with dual parallel Signed-off-by: Jagannadha Sutradharudu Teki --- V2: Fixed few issues arch/arm/include/asm/arch-zynq/hardware.h | 1 + drivers/spi/Makefile | 1 + drivers/spi/zynq_qspi.c | 449 ++++++++++++++++++++++++++++++ 3 files changed, 451 insertions(+) create mode 100644 drivers/spi/zynq_qspi.c diff --git a/arch/arm/include/asm/arch-zynq/hardware.h b/arch/arm/include/asm/arch-zynq/hardware.h index cd69677..05870ae 100644 --- a/arch/arm/include/asm/arch-zynq/hardware.h +++ b/arch/arm/include/asm/arch-zynq/hardware.h @@ -19,6 +19,7 @@ #define ZYNQ_I2C_BASEADDR1 0xE0005000 #define ZYNQ_SPI_BASEADDR0 0xE0006000 #define ZYNQ_SPI_BASEADDR1 0xE0007000 +#define ZYNQ_QSPI_BASEADDR 0xE000D000 #define ZYNQ_DDRC_BASEADDR 0xF8006000 /* Reflect slcr offsets */ diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index ed4ecd7..8b10730 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -38,3 +38,4 @@ obj-$(CONFIG_TEGRA114_SPI) += tegra114_spi.o obj-$(CONFIG_TI_QSPI) += ti_qspi.o obj-$(CONFIG_XILINX_SPI) += xilinx_spi.o obj-$(CONFIG_ZYNQ_SPI) += zynq_spi.o +obj-$(CONFIG_ZYNQ_QSPI) += zynq_qspi.o diff --git a/drivers/spi/zynq_qspi.c b/drivers/spi/zynq_qspi.c new file mode 100644 index 0000000..48f73c7 --- /dev/null +++ b/drivers/spi/zynq_qspi.c @@ -0,0 +1,449 @@ +/* + * (C) Copyright 2013 Xilinx, Inc. + * + * Zynq PS Quad-SPI(QSPI) controller driver (master mode only) + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include +#include +#include +#include + +/* Zynq qspi register bit masks ZYNQ_QSPI___MASK */ +#define ZYNQ_QSPI_CR_IFMODE_MASK (1 << 31) /* Flash intrface mode*/ +#define ZYNQ_QSPI_CR_MSA_MASK (1 << 15) /* Manual start enb */ +#define ZYNQ_QSPI_CR_MCS_MASK (1 << 14) /* Manual chip select */ +#define ZYNQ_QSPI_CR_PCS_MASK (1 << 10) /* Peri chip select */ +#define ZYNQ_QSPI_CR_FW_MASK (0x3 << 6) /* FIFO width */ +#define ZYNQ_QSPI_CR_BRD_MASK (0x7 << 3) /* Baud rate div */ +#define ZYNQ_QSPI_CR_CPHA_MASK (1 << 2) /* Clock phase */ +#define ZYNQ_QSPI_CR_CPOL_MASK (1 << 1) /* Clock polarity */ +#define ZYNQ_QSPI_CR_MSTREN_MASK (1 << 0) /* Mode select */ +#define ZYNQ_QSPI_IXR_RXNEMPTY_MASK (1 << 4) /* RX_FIFO_not_empty */ +#define ZYNQ_QSPI_IXR_TXOW_MASK (1 << 2) /* TX_FIFO_not_full */ +#define ZYNQ_QSPI_IXR_ALL_MASK 0x7F /* All IXR bits */ +#define ZYNQ_QSPI_ENR_SPI_EN_MASK (1 << 0) /* SPI Enable */ + +/* 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 */ + +/* Definitions of the flash commands - Flash insts in ascending order */ +#define ZYNQ_QSPI_FLASH_INST_WRSR 0x01 /* Write status register */ +#define ZYNQ_QSPI_FLASH_INST_PP 0x02 /* Page program */ +#define ZYNQ_QSPI_FLASH_INST_WRDS 0x04 /* Write disable */ +#define ZYNQ_QSPI_FLASH_INST_RDSR1 0x05 /* Read status register 1 */ +#define ZYNQ_QSPI_FLASH_INST_WREN 0x06 /* Write enable */ +#define ZYNQ_QSPI_FLASH_INST_AFR 0x0B /* Fast read data bytes */ +#define ZYNQ_QSPI_FLASH_INST_BE_4K 0x20 /* Erase 4KiB block */ +#define ZYNQ_QSPI_FLASH_INST_RDSR2 0x35 /* Read status register 2 */ +#define ZYNQ_QSPI_FLASH_INST_BE_32K 0x52 /* Erase 32KiB block */ +#define ZYNQ_QSPI_FLASH_INST_RDID 0x9F /* Read JEDEC ID */ +#define ZYNQ_QSPI_FLASH_INST_SE 0xD8 /* Sector erase (usually 64KB)*/ + +#define ZYNQ_QSPI_FIFO_DEPTH 63 +#define ZYNQ_QSPI_MAX_INPUT_HZ 200000000 +#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 rxdr; /* 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 */ +}; + +/* + * struct zynq_qspi_inst_format - Defines qspi flash instruction format + * @inst: Instruction code + * @inst_size: Size of the instruction including address bytes + * @inst_off: Register address where instruction has to be written + */ +struct zynq_qspi_inst_format { + u8 inst; + u8 inst_size; + u8 inst_off; +}; + +/* FIXME: Must remove - not recommended to use flash cmds + * List of all the QSPI instructions and its format + */ +static struct zynq_qspi_inst_format flash_inst[] = { + {ZYNQ_QSPI_FLASH_INST_WRSR, 1, ZYNQ_QSPI_TXD_00_01_OFFSET}, + {ZYNQ_QSPI_FLASH_INST_PP, 4, ZYNQ_QSPI_TXD_00_00_OFFSET}, + {ZYNQ_QSPI_FLASH_INST_WRDS, 1, ZYNQ_QSPI_TXD_00_01_OFFSET}, + {ZYNQ_QSPI_FLASH_INST_RDSR1, 1, ZYNQ_QSPI_TXD_00_01_OFFSET}, + {ZYNQ_QSPI_FLASH_INST_WREN, 1, ZYNQ_QSPI_TXD_00_01_OFFSET}, + {ZYNQ_QSPI_FLASH_INST_AFR, 1, ZYNQ_QSPI_TXD_00_01_OFFSET}, + {ZYNQ_QSPI_FLASH_INST_BE_4K, 4, ZYNQ_QSPI_TXD_00_00_OFFSET}, + {ZYNQ_QSPI_FLASH_INST_RDSR2, 1, ZYNQ_QSPI_TXD_00_01_OFFSET}, + {ZYNQ_QSPI_FLASH_INST_BE_32K, 4, ZYNQ_QSPI_TXD_00_00_OFFSET}, + {ZYNQ_QSPI_FLASH_INST_RDID, 1, ZYNQ_QSPI_TXD_00_01_OFFSET}, + {ZYNQ_QSPI_FLASH_INST_SE, 4, ZYNQ_QSPI_TXD_00_00_OFFSET}, + /* Add all the instructions supported by the flash device */ +}; + +/* zynq spi slave */ +struct zynq_qspi_slave { + struct spi_slave slave; + struct zynq_qspi_regs *base; + u8 mode; + u8 is_inst; + u8 fifo_depth; + const void *tx_buf; + void *rx_buf; + u32 tx_len; + u32 rx_len; + u32 speed_hz; + u32 input_hz; + u32 req_hz; +}; + +static inline struct zynq_qspi_slave *to_zynq_qspi_slave( + struct spi_slave *slave) +{ + return container_of(slave, struct zynq_qspi_slave, slave); +} + +static void zynq_qspi_init_hw(struct zynq_qspi_slave *zslave) +{ + u32 confr; + + /* Disable SPI */ + writel(~ZYNQ_QSPI_ENR_SPI_EN_MASK, &zslave->base->enr); + + /* Disable Interrupts */ + writel(ZYNQ_QSPI_IXR_ALL_MASK, &zslave->base->idr); + + /* Clear RX FIFO */ + while (readl(&zslave->base->isr) & + ZYNQ_QSPI_IXR_RXNEMPTY_MASK) + readl(&zslave->base->rxdr); + + /* Clear Interrupts */ + writel(ZYNQ_QSPI_IXR_ALL_MASK, &zslave->base->isr); + + /* Manual slave select and Auto start */ + 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; + confr &= ~ZYNQ_QSPI_CR_MSA_MASK; + writel(confr, &zslave->base->cr); + + /* Enable SPI */ + writel(ZYNQ_QSPI_ENR_SPI_EN_MASK, &zslave->base->enr); +} + +/* + * zynq_qspi_read - Copy data to RX buffer + * @zqspi: Pointer to zynq_qspi_slave + * @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(struct zynq_qspi_slave *zslave, u32 data, u8 size) +{ + if (zslave->rx_buf) { + data >>= (4 - size) * 8; + data = le32_to_cpu(data); + memcpy((u8 *)zslave->rx_buf, &data, size); + zslave->rx_buf += size; + } + + zslave->rx_len -= size; +} + +/* + * zynq_qspi_write - Copy data from TX buffer + * @zslave: Pointer to zynq_qspi_slave + * @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(struct zynq_qspi_slave *zslave, u32 *data, u8 size) +{ + if (zslave->tx_buf) { + switch (size) { + case 1: + *data = *((u8 *)zslave->tx_buf); + zslave->tx_buf += 1; + *data |= 0xFFFFFF00; + break; + case 2: + *data = *((u16 *)zslave->tx_buf); + zslave->tx_buf += 2; + *data |= 0xFFFF0000; + break; + case 3: + *data = *((u16 *)zslave->tx_buf); + zslave->tx_buf += 2; + *data |= (*((u8 *)zslave->tx_buf) << 16); + zslave->tx_buf += 1; + *data |= 0xFF000000; + break; + case 4: + /* Can not assume word aligned buffer */ + memcpy(data, zslave->tx_buf, size); + zslave->tx_buf += 4; + break; + default: + /* This will never execute */ + break; + } + } else { + *data = 0; + } + + zslave->tx_len -= size; +} + +static int zynq_qspi_check_txfifo(struct zynq_qspi_slave *zslave) +{ + u32 ts, status; + + ts = get_timer(0); + status = readl(&zslave->base->isr); + while (!(status & ZYNQ_QSPI_IXR_TXOW_MASK)) { + if (get_timer(ts) > CONFIG_SYS_ZYNQ_QSPI_WAIT) { + printf("spi_xfer: Timeout! TX FIFO not full\n"); + return -1; + } + status = readl(&zslave->base->isr); + } + + return 0; +} + +static int zynq_qspi_process_tx(struct zynq_qspi_slave *zslave) +{ + struct zynq_qspi_inst_format *curr_inst; + u8 inst, index; + u32 buf; + + inst = *(u8 *)zslave->tx_buf; + /* instuction */ + if (inst && zslave->is_inst) { + for (index = 0; index < ARRAY_SIZE(flash_inst); index++) + if (inst == flash_inst[index].inst) + break; + + if (index == ARRAY_SIZE(flash_inst)) { + printf("spi_xfer: Unsupported inst %02x\n", inst); + return -1; + } + + curr_inst = &flash_inst[index]; + debug("spi_xfer: inst:%02x inst_size:%d inst_off:%02x\n", + curr_inst->inst, curr_inst->inst_size, + curr_inst->inst_off); + + zynq_qspi_write(zslave, &buf, curr_inst->inst_size); + writel(buf, &zslave->base->cr + (curr_inst->inst_off / 4)); + zslave->is_inst = 0; + } else if (!zslave->is_inst) { /* addr + data */ + if (zslave->tx_len < 4) { + /* Check TXOW for txd1, txd2 and txd3 */ + if (zynq_qspi_check_txfifo(zslave) < 0) + return -1; + + zynq_qspi_write(zslave, &buf, zslave->tx_len); + writel(buf, + &zslave->base->txd1r + (zslave->tx_len - 1)); + } else { + zynq_qspi_write(zslave, &buf, 4); + writel(buf, &zslave->base->txd0r); + } + } + + return 0; +} + +int spi_cs_is_valid(unsigned int bus, unsigned int cs) +{ + /* 1 bus with 1 chipselect */ + return bus < 1 && cs < 1; +} + +void spi_cs_activate(struct spi_slave *slave) +{ + struct zynq_qspi_slave *zslave = to_zynq_qspi_slave(slave); + + debug("spi_cs_activate: 0x%08x\n", (u32)slave); + clrbits_le32(&zslave->base->cr, ZYNQ_QSPI_CR_PCS_MASK); + + zslave->is_inst = 1; +} + +void spi_cs_deactivate(struct spi_slave *slave) +{ + struct zynq_qspi_slave *zslave = to_zynq_qspi_slave(slave); + + debug("spi_cs_deactivate: 0x%08x\n", (u32)slave); + setbits_le32(&zslave->base->cr, ZYNQ_QSPI_CR_PCS_MASK); + + zslave->is_inst = 0; +} + +void spi_init() +{ + /* nothing to do */ +} + +struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs, + unsigned int max_hz, unsigned int mode) +{ + struct zynq_qspi_slave *zslave; + + if (!spi_cs_is_valid(bus, cs)) + return NULL; + + zslave = spi_alloc_slave(struct zynq_qspi_slave, bus, cs); + if (!zslave) { + printf("SPI_error: Fail to allocate zynq_qspi_slave\n"); + return NULL; + } + + zslave->base = (struct zynq_qspi_regs *)ZYNQ_QSPI_BASEADDR; + zslave->mode = mode; + zslave->fifo_depth = ZYNQ_QSPI_FIFO_DEPTH; + zslave->input_hz = ZYNQ_QSPI_MAX_INPUT_HZ; + zslave->speed_hz = zslave->input_hz / 2; + zslave->req_hz = max_hz; + + /* init the zynq spi hw */ + zynq_qspi_init_hw(zslave); + + return &zslave->slave; +} + +void spi_free_slave(struct spi_slave *slave) +{ + struct zynq_qspi_slave *zslave = to_zynq_qspi_slave(slave); + + debug("spi_free_slave: 0x%08x\n", (u32)slave); + free(zslave); +} + +int spi_claim_bus(struct spi_slave *slave) +{ + struct zynq_qspi_slave *zslave = to_zynq_qspi_slave(slave); + u32 confr = 0; + u8 baud_rate_val = 0; + + writel(~ZYNQ_QSPI_ENR_SPI_EN_MASK, &zslave->base->enr); + + /* Set the SPI Clock phase and polarities */ + confr = readl(&zslave->base->cr); + confr &= ~(ZYNQ_QSPI_CR_CPHA_MASK | ZYNQ_QSPI_CR_CPOL_MASK); + if (zslave->mode & SPI_CPHA) + confr |= ZYNQ_QSPI_CR_CPHA_MASK; + if (zslave->mode & SPI_CPOL) + confr |= ZYNQ_QSPI_CR_CPOL_MASK; + + /* Set the clock frequency */ + if (zslave->req_hz == 0) { + /* Set baudrate x8, if the req_hz is 0 */ + baud_rate_val = 0x2; + } else if (zslave->speed_hz != zslave->req_hz) { + while ((baud_rate_val < 8) && + ((zslave->input_hz / + (2 << baud_rate_val)) > zslave->req_hz)) + baud_rate_val++; + zslave->speed_hz = zslave->req_hz / (2 << baud_rate_val); + } + confr &= ~ZYNQ_QSPI_CR_BRD_MASK; + confr |= (baud_rate_val << 3); + writel(confr, &zslave->base->cr); + + writel(ZYNQ_QSPI_ENR_SPI_EN_MASK, &zslave->base->enr); + + return 0; +} + +void spi_release_bus(struct spi_slave *slave) +{ + struct zynq_qspi_slave *zslave = to_zynq_qspi_slave(slave); + + debug("spi_release_bus: 0x%08x\n", (u32)slave); + writel(~ZYNQ_QSPI_ENR_SPI_EN_MASK, &zslave->base->enr); +} + +int spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout, + void *din, unsigned long flags) +{ + struct zynq_qspi_slave *zslave = to_zynq_qspi_slave(slave); + u32 len = bitlen / 8, tx_tvl; + u32 buf, status; + + debug("spi_xfer: bus:%i cs:%i bitlen:%i len:%i flags:%lx\n", + slave->bus, slave->cs, bitlen, len, flags); + + if (bitlen == 0) + return -1; + + if (bitlen % 8) { + debug("spi_xfer: Non byte aligned SPI transfer\n"); + return -1; + } + + if (flags & SPI_XFER_BEGIN) + spi_cs_activate(slave); + + zslave->tx_len = len; + zslave->rx_len = len; + zslave->tx_buf = dout; + zslave->rx_buf = din; + while (zslave->rx_len > 0) { + /* Write the data into TX FIFO - tx threshold is fifo_depth */ + tx_tvl = 0; + while ((tx_tvl < zslave->fifo_depth) && zslave->tx_len) { + if (zynq_qspi_process_tx(zslave) < 0) { + flags |= SPI_XFER_END; + goto out; + } + tx_tvl++; + } + + /* Check TX FIFO completion */ + if (zynq_qspi_check_txfifo(zslave) < 0) { + flags |= SPI_XFER_END; + goto out; + } + + /* Read the data from RX FIFO */ + status = readl(&zslave->base->isr); + while (status & ZYNQ_QSPI_IXR_RXNEMPTY_MASK) { + buf = readl(&zslave->base->rxdr); + if (zslave->rx_len < 4) + zynq_qspi_read(zslave, buf, zslave->rx_len); + else + zynq_qspi_read(zslave, buf, 4); + status = readl(&zslave->base->isr); + } + } + +out: + if (flags & SPI_XFER_END) + spi_cs_deactivate(slave); + + return 0; +}