aboutsummaryrefslogtreecommitdiff
path: root/drivers/spi/zynqmp_gqspi.c
diff options
context:
space:
mode:
authorTom Rini <trini@konsulko.com>2024-05-24 09:10:54 -0600
committerTom Rini <trini@konsulko.com>2024-05-24 09:10:54 -0600
commit5e625dca4ed7fcb8aedb4b6400aeaf8a19bed856 (patch)
tree66215f2b04e24864a1cbe5ab61235913b44640e3 /drivers/spi/zynqmp_gqspi.c
parent377e91c162ab09ec20f96f966f380cb55c590edd (diff)
parent75eab7c61c4ff729f11d5b99bef831b5cd9b7f0c (diff)
downloadu-boot-5e625dca4ed7fcb8aedb4b6400aeaf8a19bed856.zip
u-boot-5e625dca4ed7fcb8aedb4b6400aeaf8a19bed856.tar.gz
u-boot-5e625dca4ed7fcb8aedb4b6400aeaf8a19bed856.tar.bz2
Merge patch series "spi-nor: Add parallel and stacked memories support"WIP/24May2024-next
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/zynqmp_gqspi.c')
-rw-r--r--drivers/spi/zynqmp_gqspi.c141
1 files changed, 125 insertions, 16 deletions
diff --git a/drivers/spi/zynqmp_gqspi.c b/drivers/spi/zynqmp_gqspi.c
index 61349a4..28eadff 100644
--- a/drivers/spi/zynqmp_gqspi.c
+++ b/drivers/spi/zynqmp_gqspi.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0+
/*
- * (C) Copyright 2018 Xilinx
- *
+ * (C) Copyright 2013 - 2022, Xilinx, Inc.
+ * (C) Copyright 2023, Advanced Micro Devices, Inc.
* Xilinx ZynqMP Generic Quad-SPI(QSPI) controller driver(master mode only)
*/
@@ -24,6 +24,8 @@
#include <linux/bitops.h>
#include <linux/err.h>
#include <linux/sizes.h>
+#include <linux/mtd/spi-nor.h>
+#include "../mtd/spi/sf_internal.h"
#include <zynqmp_firmware.h>
#define GQSPI_GFIFO_STRT_MODE_MASK BIT(29)
@@ -87,6 +89,9 @@
#define SPI_XFER_ON_LOWER 1
#define SPI_XFER_ON_UPPER 2
+#define GQSPI_SELECT_LOWER_CS BIT(0)
+#define GQSPI_SELECT_UPPER_CS BIT(1)
+
#define GQSPI_DMA_ALIGN 0x4
#define GQSPI_MAX_BAUD_RATE_VAL 7
#define GQSPI_DFLT_BAUD_RATE_VAL 2
@@ -182,13 +187,14 @@ struct zynqmp_qspi_priv {
int bytes_to_transfer;
int bytes_to_receive;
const struct spi_mem_op *op;
+ unsigned int is_parallel;
+ unsigned int u_page;
+ unsigned int bus;
+ unsigned int stripe;
+ unsigned int flags;
+ u32 max_hz;
};
-__weak int zynqmp_mmio_write(const u32 address, const u32 mask, const u32 value)
-{
- return 0;
-}
-
static int zynqmp_qspi_of_to_plat(struct udevice *bus)
{
struct zynqmp_qspi_plat *plat = dev_get_plat(bus);
@@ -233,8 +239,30 @@ static u32 zynqmp_qspi_bus_select(struct zynqmp_qspi_priv *priv)
{
u32 gqspi_fifo_reg = 0;
- gqspi_fifo_reg = GQSPI_GFIFO_LOW_BUS |
- GQSPI_GFIFO_CS_LOWER;
+ if (priv->is_parallel) {
+ if (priv->bus == SPI_XFER_ON_BOTH)
+ gqspi_fifo_reg = GQSPI_GFIFO_LOW_BUS |
+ GQSPI_GFIFO_UP_BUS |
+ GQSPI_GFIFO_CS_UPPER |
+ GQSPI_GFIFO_CS_LOWER;
+ else if (priv->bus == SPI_XFER_ON_LOWER)
+ gqspi_fifo_reg = GQSPI_GFIFO_LOW_BUS |
+ GQSPI_GFIFO_CS_UPPER |
+ GQSPI_GFIFO_CS_LOWER;
+ else if (priv->bus == SPI_XFER_ON_UPPER)
+ gqspi_fifo_reg = GQSPI_GFIFO_UP_BUS |
+ GQSPI_GFIFO_CS_LOWER |
+ GQSPI_GFIFO_CS_UPPER;
+ else
+ debug("Wrong Bus selection:0x%x\n", priv->bus);
+ } else {
+ if (priv->u_page)
+ gqspi_fifo_reg = GQSPI_GFIFO_LOW_BUS |
+ GQSPI_GFIFO_CS_UPPER;
+ else
+ gqspi_fifo_reg = GQSPI_GFIFO_LOW_BUS |
+ GQSPI_GFIFO_CS_LOWER;
+ }
return gqspi_fifo_reg;
}
@@ -294,8 +322,15 @@ static void zynqmp_qspi_chipselect(struct zynqmp_qspi_priv *priv, int is_on)
gqspi_fifo_reg |= GQSPI_SPI_MODE_SPI |
GQSPI_IMD_DATA_CS_ASSERT;
} else {
- gqspi_fifo_reg = GQSPI_GFIFO_LOW_BUS;
- gqspi_fifo_reg |= GQSPI_IMD_DATA_CS_DEASSERT;
+ if (priv->is_parallel) {
+ gqspi_fifo_reg = GQSPI_GFIFO_UP_BUS |
+ GQSPI_GFIFO_LOW_BUS;
+ } else if (priv->u_page) {
+ gqspi_fifo_reg = GQSPI_GFIFO_UP_BUS;
+ } else {
+ gqspi_fifo_reg = GQSPI_GFIFO_LOW_BUS;
+ gqspi_fifo_reg |= GQSPI_IMD_DATA_CS_DEASSERT;
+ }
}
zynqmp_qspi_fill_gen_fifo(priv, gqspi_fifo_reg);
@@ -365,12 +400,13 @@ static int zynqmp_qspi_set_speed(struct udevice *bus, uint speed)
log_debug("%s, Speed: %d, Max: %d\n", __func__, speed, plat->frequency);
- if (speed > plat->frequency)
- speed = plat->frequency;
+ /*
+ * If speed == 0 or speed > max freq, then set speed to highest
+ */
+ if (!speed || speed > priv->max_hz)
+ speed = priv->max_hz;
if (plat->speed_hz != speed) {
- /* Set the clock frequency */
- /* If speed == 0, default to lowest speed */
while ((baud_rate_val < 8) &&
((plat->frequency /
(2 << baud_rate_val)) > speed))
@@ -392,6 +428,18 @@ static int zynqmp_qspi_set_speed(struct udevice *bus, uint speed)
return 0;
}
+static int zynqmp_qspi_child_pre_probe(struct udevice *bus)
+{
+ struct spi_slave *slave = dev_get_parent_priv(bus);
+ struct zynqmp_qspi_priv *priv = dev_get_priv(bus->parent);
+
+ slave->multi_cs_cap = true;
+ slave->bytemode = SPI_4BYTE_MODE;
+ priv->max_hz = slave->max_hz;
+
+ return 0;
+}
+
static int zynqmp_qspi_probe(struct udevice *bus)
{
struct zynqmp_qspi_plat *plat = dev_get_plat(bus);
@@ -456,12 +504,17 @@ static int zynqmp_qspi_set_mode(struct udevice *bus, uint mode)
static int zynqmp_qspi_fill_tx_fifo(struct zynqmp_qspi_priv *priv, u32 size)
{
- u32 data;
+ u32 data, ier;
int ret = 0;
struct zynqmp_qspi_regs *regs = priv->regs;
u32 *buf = (u32 *)priv->tx_buf;
u32 len = size;
+ /* Enable interrupts */
+ ier = readl(&regs->ier);
+ ier |= GQSPI_IXR_ALL_MASK | GQSPI_IXR_TXFIFOEMPTY_MASK;
+ writel(ier, &regs->ier);
+
while (size) {
ret = wait_for_bit_le32(&regs->isr, GQSPI_IXR_TXNFULL_MASK, 1,
GQSPI_TIMEOUT, 1);
@@ -584,6 +637,9 @@ static int zynqmp_qspi_genfifo_fill_tx(struct zynqmp_qspi_priv *priv)
gen_fifo_cmd |= zynqmp_qspi_genfifo_mode(priv->op->data.buswidth);
gen_fifo_cmd |= GQSPI_GFIFO_TX | GQSPI_GFIFO_DATA_XFR_MASK;
+ if (priv->stripe)
+ gen_fifo_cmd |= GQSPI_GFIFO_STRIPE_MASK;
+
while (priv->len) {
len = zynqmp_qspi_calc_exp(priv, &gen_fifo_cmd);
zynqmp_qspi_fill_gen_fifo(priv, gen_fifo_cmd);
@@ -718,6 +774,9 @@ static int zynqmp_qspi_genfifo_fill_rx(struct zynqmp_qspi_priv *priv)
gen_fifo_cmd |= zynqmp_qspi_genfifo_mode(priv->op->data.buswidth);
gen_fifo_cmd |= GQSPI_GFIFO_RX | GQSPI_GFIFO_DATA_XFR_MASK;
+ if (priv->stripe)
+ gen_fifo_cmd |= GQSPI_GFIFO_STRIPE_MASK;
+
/*
* Check if receive buffer is aligned to 4 byte and length
* is multiples of four byte as we are using dma to receive.
@@ -758,6 +817,33 @@ static int zynqmp_qspi_release_bus(struct udevice *dev)
return 0;
}
+static bool zynqmp_qspi_update_stripe(const struct spi_mem_op *op)
+{
+ /*
+ * This is a list of opcodes for which we must not use striped access
+ * even in dual parallel mode, but instead broadcast the same data to
+ * both chips. This is primarily erase commands and writing some
+ * registers.
+ */
+ switch (op->cmd.opcode) {
+ case SPINOR_OP_BE_4K:
+ case SPINOR_OP_BE_32K:
+ case SPINOR_OP_CHIP_ERASE:
+ case SPINOR_OP_SE:
+ case SPINOR_OP_BE_32K_4B:
+ case SPINOR_OP_SE_4B:
+ case SPINOR_OP_BE_4K_4B:
+ case SPINOR_OP_WRSR:
+ case SPINOR_OP_WREAR:
+ case SPINOR_OP_BRWR:
+ return false;
+ case SPINOR_OP_WRSR2:
+ return op->addr.nbytes != 0;
+ default:
+ return true;
+ }
+}
+
static int zynqmp_qspi_exec_op(struct spi_slave *slave,
const struct spi_mem_op *op)
{
@@ -769,6 +855,25 @@ static int zynqmp_qspi_exec_op(struct spi_slave *slave,
priv->rx_buf = op->data.buf.in;
priv->len = op->data.nbytes;
+ if (slave->flags & SPI_XFER_U_PAGE)
+ priv->u_page = 1;
+ else
+ priv->u_page = 0;
+
+ if ((slave->flags & GQSPI_SELECT_LOWER_CS) &&
+ (slave->flags & GQSPI_SELECT_UPPER_CS))
+ priv->is_parallel = true;
+
+ priv->stripe = 0;
+ priv->bus = 0;
+
+ if (priv->is_parallel) {
+ if (slave->flags & SPI_XFER_MASK)
+ priv->bus = (slave->flags & SPI_XFER_MASK) >> 8;
+ if (zynqmp_qspi_update_stripe(op))
+ priv->stripe = 1;
+ }
+
zynqmp_qspi_chipselect(priv, 1);
/* Send opcode, addr, dummy */
@@ -782,6 +887,9 @@ static int zynqmp_qspi_exec_op(struct spi_slave *slave,
zynqmp_qspi_chipselect(priv, 0);
+ priv->is_parallel = false;
+ slave->flags &= ~SPI_XFER_MASK;
+
return ret;
}
@@ -812,4 +920,5 @@ U_BOOT_DRIVER(zynqmp_qspi) = {
.plat_auto = sizeof(struct zynqmp_qspi_plat),
.priv_auto = sizeof(struct zynqmp_qspi_priv),
.probe = zynqmp_qspi_probe,
+ .child_pre_probe = zynqmp_qspi_child_pre_probe,
};