Skip to content

Commit

Permalink
spi: spi-zynqmp-gqspi: Add dual parallel and stacked mode support in …
Browse files Browse the repository at this point in the history
…driver

The qspi controller supports up to two SPI flash memories operating in
parallel or shared bus configuration(i.e., stacked mode). This patch
updates the spi-nor framework to support dual parallel and stacked
connection mode.

Signed-off-by: Amit Kumar Mahapatra <amit.kumar-mahapatra@xilinx.com>
State: pending
  • Loading branch information
Amit Kumar Mahapatra authored and Michal Simek committed Feb 1, 2022
1 parent adf779b commit ad3ea50
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 9 deletions.
22 changes: 22 additions & 0 deletions drivers/spi/spi-mem.c
Expand Up @@ -8,13 +8,33 @@
#include <linux/dmaengine.h>
#include <linux/iopoll.h>
#include <linux/pm_runtime.h>
#include <linux/mtd/spi-nor.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi-mem.h>

#include "internals.h"

#define SPI_MEM_MAX_BUSWIDTH 8

bool update_stripe(const struct spi_mem_op *op)
{
if (op->cmd.opcode == SPINOR_OP_BE_4K ||
op->cmd.opcode == SPINOR_OP_BE_32K ||
op->cmd.opcode == SPINOR_OP_CHIP_ERASE ||
op->cmd.opcode == SPINOR_OP_SE ||
op->cmd.opcode == SPINOR_OP_BE_32K_4B ||
op->cmd.opcode == SPINOR_OP_SE_4B ||
op->cmd.opcode == SPINOR_OP_BE_4K_4B ||
op->cmd.opcode == SPINOR_OP_WRSR ||
op->cmd.opcode == SPINOR_OP_WREAR ||
op->cmd.opcode == SPINOR_OP_BRWR ||
(op->cmd.opcode == SPINOR_OP_WRSR2 && !op->addr.nbytes))
return false;

return true;
}
EXPORT_SYMBOL(update_stripe);

/**
* spi_controller_dma_map_mem_op_data() - DMA-map the buffer attached to a
* memory operation
Expand Down Expand Up @@ -371,6 +391,7 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
xfers[xferpos].tx_buf = tmpbuf + op->addr.nbytes + 1;
xfers[xferpos].len = op->dummy.nbytes;
xfers[xferpos].tx_nbits = op->dummy.buswidth;
xfers[xferpos].dummy = op->dummy.nbytes * 8;
xfers[xferpos].dummy_data = 1;
spi_message_add_tail(&xfers[xferpos], &msg);
xferpos++;
Expand All @@ -386,6 +407,7 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
xfers[xferpos].tx_nbits = op->data.buswidth;
}

xfers[xferpos].stripe = update_stripe(op);
xfers[xferpos].len = op->data.nbytes;
spi_message_add_tail(&xfers[xferpos], &msg);
xferpos++;
Expand Down
61 changes: 52 additions & 9 deletions drivers/spi/spi-zynqmp-gqspi.c
Expand Up @@ -23,6 +23,7 @@
#include <linux/spinlock.h>
#include <linux/workqueue.h>
#include <linux/spi/spi-mem.h>
#include <linux/mtd/spi-nor.h>

/* Generic QSPI register offsets */
#define GQSPI_CONFIG_OFST 0x00000100
Expand Down Expand Up @@ -337,6 +338,16 @@ static void zynqmp_qspi_set_tapdelay(struct zynqmp_qspi *xqspi, u32 baudrateval)
zynqmp_gqspi_write(xqspi, GQSPI_DATA_DLY_ADJ_OFST, datadlyadj);
}

static u32 zynqmp_disable_intr(struct zynqmp_qspi *xqspi)
{
u32 value;

zynqmp_gqspi_write(xqspi, GQSPI_IDR_OFST, GQSPI_ISR_IDR_MASK);
value = zynqmp_gqspi_read(xqspi, GQSPI_IMASK_OFST);
zynqmp_gqspi_write(xqspi, GQSPI_IDR_OFST, GQSPI_ISR_IDR_MASK);

return value;
}
/**
* zynqmp_qspi_init_hw - Initialize the hardware
* @xqspi: Pointer to the zynqmp_qspi structure
Expand Down Expand Up @@ -364,9 +375,7 @@ static void zynqmp_qspi_init_hw(struct zynqmp_qspi *xqspi)
/* Select the GQSPI mode */
zynqmp_gqspi_write(xqspi, GQSPI_SEL_OFST, GQSPI_SEL_MASK);
/* Clear and disable interrupts */
zynqmp_gqspi_write(xqspi, GQSPI_ISR_OFST,
zynqmp_gqspi_read(xqspi, GQSPI_ISR_OFST) |
GQSPI_ISR_WR_TO_CLR_MASK);
zynqmp_disable_intr(xqspi);
/* Clear the DMA STS */
zynqmp_gqspi_write(xqspi, GQSPI_QSPIDMA_DST_I_STS_OFST,
zynqmp_gqspi_read(xqspi,
Expand Down Expand Up @@ -456,11 +465,22 @@ static void zynqmp_qspi_chipselect(struct spi_device *qspi, bool is_high)
u32 genfifoentry = 0, statusreg;

genfifoentry |= GQSPI_GENFIFO_MODE_SPI;
if (qspi->master->flags & SPI_MASTER_BOTH_CS) {
zynqmp_gqspi_selectslave(xqspi,
GQSPI_SELECT_FLASH_CS_BOTH,
GQSPI_SELECT_FLASH_BUS_BOTH);
} else if (qspi->master->flags & SPI_MASTER_U_PAGE) {
zynqmp_gqspi_selectslave(xqspi,
GQSPI_SELECT_FLASH_CS_UPPER,
GQSPI_SELECT_FLASH_BUS_LOWER);
} else {
zynqmp_gqspi_selectslave(xqspi,
GQSPI_SELECT_FLASH_CS_LOWER,
GQSPI_SELECT_FLASH_BUS_LOWER);
}

genfifoentry |= xqspi->genfifobus;
if (!is_high) {
xqspi->genfifobus = GQSPI_GENFIFO_BUS_LOWER;
xqspi->genfifocs = GQSPI_GENFIFO_CS_LOWER;
genfifoentry |= xqspi->genfifobus;
genfifoentry |= xqspi->genfifocs;
genfifoentry |= GQSPI_GENFIFO_CS_SETUP;
} else {
Expand Down Expand Up @@ -842,7 +862,8 @@ static int zynqmp_qspi_setuprxdma(struct zynqmp_qspi *xqspi)
u64 dma_align = (u64)(uintptr_t)xqspi->rxbuf;

if ((xqspi->bytes_to_receive < 8 || xqspi->io_mode) ||
((dma_align & GQSPI_DMA_UNALIGN) != 0x0)) {
((dma_align & GQSPI_DMA_UNALIGN) != 0x0) ||
is_vmalloc_addr(xqspi->rxbuf)) {
/* Setting to IO mode */
config_reg = zynqmp_gqspi_read(xqspi, GQSPI_CONFIG_OFST);
config_reg &= ~GQSPI_CFG_MODE_EN_MASK;
Expand Down Expand Up @@ -1065,6 +1086,7 @@ static int zynqmp_qspi_exec_op(struct spi_mem *mem,
zynqmp_gqspi_read(xqspi, GQSPI_CONFIG_OFST) |
GQSPI_CFG_START_GEN_FIFO_MASK);
zynqmp_gqspi_write(xqspi, GQSPI_IER_OFST,
GQSPI_IER_TXEMPTY_MASK |
GQSPI_IER_GENFIFOEMPTY_MASK |
GQSPI_IER_TXNOT_FULL_MASK);
if (!wait_for_completion_timeout
Expand Down Expand Up @@ -1122,6 +1144,10 @@ static int zynqmp_qspi_exec_op(struct spi_mem *mem,
}

if (op->data.nbytes) {
if (mem->spi->master->flags & SPI_MASTER_DATA_STRIPE) {
if (update_stripe(op))
genfifoentry |= GQSPI_GENFIFO_STRIPE;
}
reinit_completion(&xqspi->data_completion);
if (op->data.dir == SPI_MEM_DATA_OUT) {
xqspi->txbuf = (u8 *)op->data.buf.out;
Expand Down Expand Up @@ -1176,9 +1202,20 @@ static int zynqmp_qspi_exec_op(struct spi_mem *mem,
return err;
}

static int __maybe_unused zynqmp_runtime_idle(struct device *dev)
{
struct zynqmp_qspi *xqspi = dev_get_drvdata(dev);
u32 value;

value = zynqmp_gqspi_read(xqspi, GQSPI_EN_OFST);
if (value)
return -EBUSY;

return 0;
}
static const struct dev_pm_ops zynqmp_qspi_dev_pm_ops = {
SET_RUNTIME_PM_OPS(zynqmp_runtime_suspend,
zynqmp_runtime_resume, NULL)
zynqmp_runtime_resume, zynqmp_runtime_idle)
SET_SYSTEM_SLEEP_PM_OPS(zynqmp_qspi_suspend, zynqmp_qspi_resume)
};

Expand Down Expand Up @@ -1214,6 +1251,7 @@ static int zynqmp_qspi_probe(struct platform_device *pdev)
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
const struct of_device_id *match;
u32 num_cs;

ctlr = spi_alloc_master(&pdev->dev, sizeof(*xqspi));
if (!ctlr)
Expand Down Expand Up @@ -1290,6 +1328,7 @@ static int zynqmp_qspi_probe(struct platform_device *pdev)
xqspi->irq = platform_get_irq(pdev, 0);
if (xqspi->irq <= 0) {
ret = -ENXIO;
dev_err(dev, "irq resource not found\n");
goto clk_dis_all;
}
ret = devm_request_irq(&pdev->dev, xqspi->irq, zynqmp_qspi_irq,
Expand All @@ -1300,9 +1339,13 @@ static int zynqmp_qspi_probe(struct platform_device *pdev)
goto clk_dis_all;
}

ret = of_property_read_u32(pdev->dev.of_node, "num-cs", &num_cs);
if (ret < 0)
ctlr->num_chipselect = GQSPI_DEFAULT_NUM_CS;
else
ctlr->num_chipselect = num_cs;
dma_set_mask(&pdev->dev, DMA_BIT_MASK(44));
ctlr->bits_per_word_mask = SPI_BPW_MASK(8);
ctlr->num_chipselect = GQSPI_DEFAULT_NUM_CS;
ctlr->mem_ops = &zynqmp_qspi_mem_ops;
ctlr->setup = zynqmp_qspi_setup_op;
ctlr->bits_per_word_mask = SPI_BPW_MASK(8);
Expand Down
3 changes: 3 additions & 0 deletions drivers/spi/spi.c
Expand Up @@ -2064,6 +2064,9 @@ static int of_spi_parse_dt(struct spi_controller *ctlr, struct spi_device *spi,
if (!of_property_read_u32(nc, "spi-max-frequency", &value))
spi->max_speed_hz = value;

/* Multi die flash */
if (of_property_read_bool(nc, "multi-die"))
spi->multi_die = true;
return 0;
}

Expand Down
1 change: 1 addition & 0 deletions include/linux/spi/spi-mem.h
Expand Up @@ -361,6 +361,7 @@ bool spi_mem_dtr_supports_op(struct spi_mem *mem,

int spi_mem_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op);

bool update_stripe(const struct spi_mem_op *op);
bool spi_mem_supports_op(struct spi_mem *mem,
const struct spi_mem_op *op);

Expand Down
4 changes: 4 additions & 0 deletions include/linux/spi/spi.h
Expand Up @@ -844,6 +844,7 @@ extern void spi_res_release(struct spi_controller *ctlr,
* @len: size of rx and tx buffers (in bytes)
* @speed_hz: Select a speed other than the device default for this
* transfer. If 0 the default (from @spi_device) is used.
* @dummy: number of dummy cycles.
* @bits_per_word: select a bits_per_word other than the device default
* for this transfer. If 0 the default (from @spi_device) is used.
* @dummy_data: indicates transfer is dummy bytes transfer.
Expand All @@ -861,6 +862,7 @@ extern void spi_res_release(struct spi_controller *ctlr,
* @transfer_list: transfers are sequenced through @spi_message.transfers
* @tx_sg: Scatterlist for transmit, currently not for client use
* @rx_sg: Scatterlist for receive, currently not for client use
* @stripe: true-> enable stripe, false-> disable stripe.
* @ptp_sts_word_pre: The word (subject to bits_per_word semantics) offset
* within @tx_buf for which the SPI device is requesting that the time
* snapshot for this transfer begins. Upon completing the SPI transfer,
Expand Down Expand Up @@ -968,6 +970,8 @@ struct spi_transfer {
struct spi_delay cs_change_delay;
struct spi_delay word_delay;
u32 speed_hz;
u32 dummy;
bool stripe;

u32 effective_speed_hz;

Expand Down

0 comments on commit ad3ea50

Please sign in to comment.