diff options
Diffstat (limited to 'drivers/spi/zynq_qspi.c')
-rw-r--r-- | drivers/spi/zynq_qspi.c | 119 |
1 files changed, 105 insertions, 14 deletions
diff --git a/drivers/spi/zynq_qspi.c b/drivers/spi/zynq_qspi.c index b71b9a6..281a367 100644 --- a/drivers/spi/zynq_qspi.c +++ b/drivers/spi/zynq_qspi.c @@ -1,7 +1,8 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * (C) Copyright 2013 Xilinx, Inc. + * (C) Copyright 2013 - 2022, Xilinx, Inc. * (C) Copyright 2015 Jagan Teki <jteki@openedev.com> + * (C) Copyright 2023, Advanced Micro Devices, Inc. * * Xilinx Zynq Quad-SPI(QSPI) controller driver (master mode only) */ @@ -12,10 +13,12 @@ #include <log.h> #include <malloc.h> #include <spi.h> +#include <spi_flash.h> #include <asm/global_data.h> #include <asm/io.h> #include <linux/bitops.h> #include <spi-mem.h> +#include "../mtd/spi/sf_internal.h" DECLARE_GLOBAL_DATA_PTR; @@ -41,6 +44,21 @@ DECLARE_GLOBAL_DATA_PTR; #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_FR_QOUT_CODE 0x6B /* read instruction code */ + +#define QSPI_SELECT_LOWER_CS BIT(0) +#define QSPI_SELECT_UPPER_CS BIT(1) + +/* + * QSPI Linear Configuration Register + * + * It is named Linear Configuration but it controls other modes when not in + * linear mode also. + */ +#define ZYNQ_QSPI_LCFG_TWO_MEM_MASK 0x40000000 /* QSPI Enable Bit Mask */ +#define ZYNQ_QSPI_LCFG_SEP_BUS_MASK 0x20000000 /* QSPI Enable Bit Mask */ +#define ZYNQ_QSPI_LCFG_U_PAGE 0x10000000 /* QSPI Upper memory set */ +#define ZYNQ_QSPI_LCFG_DUMMY_SHIFT 8 #define ZYNQ_QSPI_TXFIFO_THRESHOLD 1 /* Tx FIFO threshold level*/ #define ZYNQ_QSPI_RXFIFO_THRESHOLD 32 /* Rx FIFO threshold level */ @@ -100,7 +118,11 @@ struct zynq_qspi_priv { int bytes_to_transfer; int bytes_to_receive; unsigned int is_inst; + unsigned int is_parallel; + unsigned int is_stacked; + unsigned int u_page; unsigned cs_change:1; + unsigned is_strip:1; }; static int zynq_qspi_of_to_plat(struct udevice *bus) @@ -111,7 +133,6 @@ static int zynq_qspi_of_to_plat(struct udevice *bus) plat->regs = (struct zynq_qspi_regs *)fdtdec_get_addr(blob, node, "reg"); - return 0; } @@ -146,6 +167,9 @@ static void zynq_qspi_init_hw(struct zynq_qspi_priv *priv) /* Disable Interrupts */ writel(ZYNQ_QSPI_IXR_ALL_MASK, ®s->idr); + /* Disable linear mode as the boot loader may have used it */ + writel(0x0, ®s->lqspicfg); + /* Clear the TX and RX threshold reg */ writel(ZYNQ_QSPI_TXFIFO_THRESHOLD, ®s->txftr); writel(ZYNQ_QSPI_RXFIFO_THRESHOLD, ®s->rxftr); @@ -163,12 +187,11 @@ static void zynq_qspi_init_hw(struct zynq_qspi_priv *priv) 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); + if (priv->is_stacked) + confr |= 0x10; + + writel(confr, ®s->cr); /* Enable SPI */ writel(ZYNQ_QSPI_ENR_SPI_EN_MASK, ®s->enr); @@ -180,6 +203,7 @@ static int zynq_qspi_child_pre_probe(struct udevice *bus) struct zynq_qspi_priv *priv = dev_get_priv(bus->parent); priv->max_hz = slave->max_hz; + slave->multi_cs_cap = true; return 0; } @@ -362,8 +386,8 @@ static void zynq_qspi_fill_tx_fifo(struct zynq_qspi_priv *priv, u32 size) 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 }; + ZYNQ_QSPI_TXD_00_01_OFFSET, ZYNQ_QSPI_TXD_00_10_OFFSET, + ZYNQ_QSPI_TXD_00_11_OFFSET, ZYNQ_QSPI_TXD_00_00_OFFSET }; while ((fifocount < size) && (priv->bytes_to_transfer > 0)) { @@ -385,7 +409,11 @@ static void zynq_qspi_fill_tx_fifo(struct zynq_qspi_priv *priv, u32 size) return; len = priv->bytes_to_transfer; zynq_qspi_write_data(priv, &data, len); - offset = (priv->rx_buf) ? offsets[0] : offsets[len]; + if ((priv->is_parallel || priv->is_stacked) && + !priv->is_inst && (len % 2)) + len++; + offset = (priv->rx_buf) ? + offsets[3] : offsets[len - 1]; writel(data, ®s->cr + (offset / 4)); } } @@ -490,6 +518,7 @@ static int zynq_qspi_irq_poll(struct zynq_qspi_priv *priv) */ static int zynq_qspi_start_transfer(struct zynq_qspi_priv *priv) { + static u8 current_u_page; u32 data = 0; struct zynq_qspi_regs *regs = priv->regs; @@ -499,6 +528,34 @@ static int zynq_qspi_start_transfer(struct zynq_qspi_priv *priv) priv->bytes_to_transfer = priv->len; priv->bytes_to_receive = priv->len; + if (priv->is_parallel) + writel((ZYNQ_QSPI_LCFG_TWO_MEM_MASK | + ZYNQ_QSPI_LCFG_SEP_BUS_MASK | + (1 << ZYNQ_QSPI_LCFG_DUMMY_SHIFT) | + ZYNQ_QSPI_FR_QOUT_CODE), ®s->lqspicfg); + + if (priv->is_inst && priv->is_stacked && current_u_page != priv->u_page) { + if (priv->u_page) { + /* Configure two memories on shared bus + * by enabling upper mem + */ + writel((ZYNQ_QSPI_LCFG_TWO_MEM_MASK | + ZYNQ_QSPI_LCFG_U_PAGE | + (1 << ZYNQ_QSPI_LCFG_DUMMY_SHIFT) | + ZYNQ_QSPI_FR_QOUT_CODE), + ®s->lqspicfg); + } else { + /* Configure two memories on shared bus + * by enabling lower mem + */ + writel((ZYNQ_QSPI_LCFG_TWO_MEM_MASK | + (1 << ZYNQ_QSPI_LCFG_DUMMY_SHIFT) | + ZYNQ_QSPI_FR_QOUT_CODE), + ®s->lqspicfg); + } + current_u_page = priv->u_page; + } + if (priv->len < 4) zynq_qspi_fill_tx_fifo(priv, priv->len); else @@ -585,20 +642,21 @@ static int zynq_qspi_xfer(struct udevice *dev, unsigned int bitlen, struct zynq_qspi_priv *priv = dev_get_priv(bus); struct dm_spi_slave_plat *slave_plat = dev_get_parent_plat(dev); - priv->cs = slave_plat->cs; + priv->cs = slave_plat->cs[0]; 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", - dev_seq(bus), slave_plat->cs, bitlen, priv->len, flags); + debug("%s: bus:%i cs[0]:%i bitlen:%i len:%i flags:%lx\n", __func__, + dev_seq(bus), slave_plat->cs[0], 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) + if ((dout && flags & SPI_XFER_BEGIN) || + (flags & SPI_XFER_END && !priv->is_strip)) priv->is_inst = 1; else priv->is_inst = 0; @@ -608,6 +666,11 @@ static int zynq_qspi_xfer(struct udevice *dev, unsigned int bitlen, else priv->cs_change = 0; + if (flags & SPI_XFER_U_PAGE) + priv->u_page = 1; + else + priv->u_page = 0; + zynq_qspi_transfer(priv); return 0; @@ -671,14 +734,34 @@ static int zynq_qspi_set_mode(struct udevice *bus, uint mode) return 0; } +bool update_stripe(const struct spi_mem_op *op) +{ + if (op->cmd.opcode == SPINOR_OP_BE_4K || + op->cmd.opcode == SPINOR_OP_CHIP_ERASE || + op->cmd.opcode == SPINOR_OP_SE || + op->cmd.opcode == SPINOR_OP_WREAR + ) + return false; + + return true; +} + static int zynq_qspi_exec_op(struct spi_slave *slave, const struct spi_mem_op *op) { + struct udevice *bus = slave->dev->parent; + struct zynq_qspi_priv *priv = dev_get_priv(bus); int op_len, pos = 0, ret, i; unsigned int flag = 0; const u8 *tx_buf = NULL; u8 *rx_buf = NULL; + if ((slave->flags & QSPI_SELECT_LOWER_CS) && + (slave->flags & QSPI_SELECT_UPPER_CS)) + priv->is_parallel = true; + if (slave->flags & SPI_XFER_STACKED) + priv->is_stacked = true; + if (op->data.nbytes) { if (op->data.dir == SPI_MEM_DATA_IN) rx_buf = op->data.buf.in; @@ -703,6 +786,9 @@ static int zynq_qspi_exec_op(struct spi_slave *slave, if (op->dummy.nbytes) memset(op_buf + pos, 0xff, op->dummy.nbytes); + if (slave->flags & SPI_XFER_U_PAGE) + flag |= SPI_XFER_U_PAGE; + /* 1st transfer: opcode + address + dummy cycles */ /* Make sure to set END bit if no tx or rx data messages follow */ if (!tx_buf && !rx_buf) @@ -713,6 +799,8 @@ static int zynq_qspi_exec_op(struct spi_slave *slave, if (ret) return ret; + priv->is_strip = update_stripe(op); + /* 2nd transfer: rx or tx data path */ if (tx_buf || rx_buf) { ret = zynq_qspi_xfer(slave->dev, op->data.nbytes * 8, tx_buf, @@ -721,6 +809,9 @@ static int zynq_qspi_exec_op(struct spi_slave *slave, return ret; } + priv->is_parallel = false; + priv->is_stacked = false; + slave->flags &= ~SPI_XFER_MASK; spi_release_bus(slave); return 0; |