diff options
author | Tom Rini <trini@konsulko.com> | 2024-10-09 09:02:22 -0600 |
---|---|---|
committer | Tom Rini <trini@konsulko.com> | 2024-10-09 09:02:22 -0600 |
commit | f8efc68b30e29360f7a419fe4968432179d7368b (patch) | |
tree | f7e4ed2b5d65eff2c81a16bf937eba4900f40543 /drivers/spi/zynq_qspi.c | |
parent | fbe16bc28014dc1ed957f5fee7e53d6eee781aa9 (diff) | |
parent | 8be3beef44c29c70a9ec447a4f232f987e1b4c64 (diff) |
Merge patch series "spi-nor: Add parallel and stacked memories support"
Venkatesh Yadav Abbarapu <venkatesh.abbarapu@amd.com> says:
This series adds support for Xilinx qspi parallel and
stacked memeories.
In parallel mode, the current implementation assumes that a maximum
of two flashes are connected. The QSPI controller splits the data
evenly between both the flashes so, both the flashes that are connected
in parallel mode should be identical.
During each operation SPI-NOR sets 0th bit for CS0 & 1st bit for CS1 in
nor->flags.
In stacked mode the current implementation assumes that a maximum of two
flashes are connected and both the flashes are of same make but can differ
in sizes. So, except the sizes all other flash parameters of both the flashes
are identical.
Spi-nor will pass on the appropriate flash select flag to low level driver,
and it will select pass all the data to that particular flash.
Write operation in parallel mode are performed in page size * 2 chunks as
each write operation results in writing both the flashes. For doubling the
address space each operation is performed at addr/2 flash offset, where addr
is the address specified by the user.
Similarly for read and erase operations it will read from both flashes, so
size and offset are divided by 2 and send to flash.
Diffstat (limited to 'drivers/spi/zynq_qspi.c')
-rw-r--r-- | drivers/spi/zynq_qspi.c | 121 |
1 files changed, 107 insertions, 14 deletions
diff --git a/drivers/spi/zynq_qspi.c b/drivers/spi/zynq_qspi.c index b71b9a6fd6c..f5b3fb5c125 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("zynq_qspi_xfer: bus:%i cs[0]:%i bitlen:%i len:%i flags:%lx\n", + 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,35 @@ 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 || + op->cmd.opcode == SPINOR_OP_WRSR + ) + 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 +787,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 +800,9 @@ static int zynq_qspi_exec_op(struct spi_slave *slave, if (ret) return ret; + if (priv->is_parallel) + 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 +811,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; |