5513 lines
151 KiB
Diff
5513 lines
151 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Patrick Yavitz <pyavitz@armbian.com>
|
|
Date: Fri, 21 Jun 2024 11:54:06 -0400
|
|
Subject: add spacemit patch set
|
|
|
|
source: https://gitee.com/bianbu-linux/linux-6.1
|
|
|
|
Signed-off-by: Patrick Yavitz <pyavitz@armbian.com>
|
|
---
|
|
drivers/spi/Kconfig | 20 +
|
|
drivers/spi/Makefile | 3 +
|
|
drivers/spi/spi-dw-espi.c | 1256 +++++++
|
|
drivers/spi/spi-dw-espi.h | 325 ++
|
|
drivers/spi/spi-dw-mmio-ext.c | 171 +
|
|
drivers/spi/spi-k1x-dma.c | 375 ++
|
|
drivers/spi/spi-k1x-qspi.c | 1722 ++++++++++
|
|
drivers/spi/spi-k1x.c | 1189 +++++++
|
|
drivers/spi/spi-k1x.h | 364 ++
|
|
9 files changed, 5425 insertions(+)
|
|
|
|
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
|
|
index 111111111111..222222222222 100644
|
|
--- a/drivers/spi/Kconfig
|
|
+++ b/drivers/spi/Kconfig
|
|
@@ -272,6 +272,26 @@ config SPI_DAVINCI
|
|
help
|
|
SPI master controller for DaVinci/DA8x/OMAP-L/AM1x SPI modules.
|
|
|
|
+config SPI_DESIGNWARE_EXT
|
|
+ tristate "DesignWare enhanced SPI controller core support"
|
|
+ imply SPI_MEM
|
|
+ help
|
|
+ general driver for enhanced SPI controller core from DesignWare
|
|
+
|
|
+config SPI_K1X
|
|
+ tristate "K1X SPI Controller"
|
|
+ depends on SOC_SPACEMIT_K1X
|
|
+ help
|
|
+ Enable support for the Spacemit K1X SPI controller.
|
|
+
|
|
+config SPI_K1X_QSPI
|
|
+ tristate "K1X QuadSPI Controller"
|
|
+ depends on SPI_MEM
|
|
+ help
|
|
+ This enables support for the Spacemit K1X QuadSPI controller in master mode.
|
|
+ This controller does only support the high-level SPI memory interface
|
|
+ and not support generic SPI messages.
|
|
+
|
|
config SPI_DESIGNWARE
|
|
tristate "DesignWare SPI controller core support"
|
|
imply SPI_MEM
|
|
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
|
|
index 111111111111..222222222222 100644
|
|
--- a/drivers/spi/Makefile
|
|
+++ b/drivers/spi/Makefile
|
|
@@ -40,6 +40,9 @@ obj-$(CONFIG_SPI_CLPS711X) += spi-clps711x.o
|
|
obj-$(CONFIG_SPI_COLDFIRE_QSPI) += spi-coldfire-qspi.o
|
|
obj-$(CONFIG_SPI_DAVINCI) += spi-davinci.o
|
|
obj-$(CONFIG_SPI_DLN2) += spi-dln2.o
|
|
+obj-$(CONFIG_SPI_DESIGNWARE_EXT) += spi-dw-mmio-ext.o spi-dw-espi.o
|
|
+obj-$(CONFIG_SPI_K1X) += spi-k1x.o spi-k1x-dma.o
|
|
+obj-$(CONFIG_SPI_K1X_QSPI) += spi-k1x-qspi.o
|
|
obj-$(CONFIG_SPI_DESIGNWARE) += spi-dw.o
|
|
spi-dw-y := spi-dw-core.o
|
|
spi-dw-$(CONFIG_SPI_DW_DMA) += spi-dw-dma.o
|
|
diff --git a/drivers/spi/spi-dw-espi.c b/drivers/spi/spi-dw-espi.c
|
|
new file mode 100644
|
|
index 000000000000..111111111111
|
|
--- /dev/null
|
|
+++ b/drivers/spi/spi-dw-espi.c
|
|
@@ -0,0 +1,1256 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-only
|
|
+/*
|
|
+ * Designware SPI core controller driver
|
|
+ *
|
|
+ * Copyright (c) 2023, spacemit Corporation.
|
|
+ *
|
|
+ * base on design-ware spi-core driver(spi-dw-xxx.c)
|
|
+ */
|
|
+
|
|
+#include <linux/bitfield.h>
|
|
+#include <linux/dma-mapping.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/preempt.h>
|
|
+#include <linux/highmem.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/spi/spi.h>
|
|
+#include <linux/spi/spi-mem.h>
|
|
+#include <linux/string.h>
|
|
+#include <linux/of.h>
|
|
+
|
|
+#include "spi-dw-espi.h"
|
|
+
|
|
+#ifdef CONFIG_DEBUG_FS
|
|
+#include <linux/debugfs.h>
|
|
+#endif
|
|
+
|
|
+/* Slave spi_device related */
|
|
+struct dw_spi_chip_data {
|
|
+ u32 cr0;
|
|
+ u32 rx_sample_dly; /* RX sample delay */
|
|
+};
|
|
+
|
|
+#ifdef CONFIG_DEBUG_FS
|
|
+
|
|
+#define DW_SPI_DBGFS_REG(_name, _off) \
|
|
+{ \
|
|
+ .name = _name, \
|
|
+ .offset = _off, \
|
|
+}
|
|
+
|
|
+static const struct debugfs_reg32 dw_spi_dbgfs_regs[] = {
|
|
+ DW_SPI_DBGFS_REG("CTRLR0", DW_SPI_CTRLR0),
|
|
+ DW_SPI_DBGFS_REG("CTRLR1", DW_SPI_CTRLR1),
|
|
+ DW_SPI_DBGFS_REG("SSIENR", DW_SPI_SSIENR),
|
|
+ DW_SPI_DBGFS_REG("SER", DW_SPI_SER),
|
|
+ DW_SPI_DBGFS_REG("BAUDR", DW_SPI_BAUDR),
|
|
+ DW_SPI_DBGFS_REG("TXFTLR", DW_SPI_TXFTLR),
|
|
+ DW_SPI_DBGFS_REG("RXFTLR", DW_SPI_RXFTLR),
|
|
+ DW_SPI_DBGFS_REG("TXFLR", DW_SPI_TXFLR),
|
|
+ DW_SPI_DBGFS_REG("RXFLR", DW_SPI_RXFLR),
|
|
+ DW_SPI_DBGFS_REG("SR", DW_SPI_SR),
|
|
+ DW_SPI_DBGFS_REG("IMR", DW_SPI_IMR),
|
|
+ DW_SPI_DBGFS_REG("ISR", DW_SPI_ISR),
|
|
+ DW_SPI_DBGFS_REG("DMACR", DW_SPI_DMACR),
|
|
+ DW_SPI_DBGFS_REG("DMATDLR", DW_SPI_DMATDLR),
|
|
+ DW_SPI_DBGFS_REG("DMARDLR", DW_SPI_DMARDLR),
|
|
+ DW_SPI_DBGFS_REG("RX_SAMPLE_DLY", DW_SPI_RX_SAMPLE_DLY),
|
|
+};
|
|
+
|
|
+static int dw_spi_debugfs_init(struct dw_spi *dws)
|
|
+{
|
|
+ char name[32];
|
|
+
|
|
+ snprintf(name, 32, "dw_spi%d", dws->master->bus_num);
|
|
+ dws->debugfs = debugfs_create_dir(name, NULL);
|
|
+ if (!dws->debugfs)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ dws->regset.regs = dw_spi_dbgfs_regs;
|
|
+ dws->regset.nregs = ARRAY_SIZE(dw_spi_dbgfs_regs);
|
|
+ dws->regset.base = dws->regs;
|
|
+ debugfs_create_regset32("registers", 0400, dws->debugfs, &dws->regset);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void dw_spi_debugfs_remove(struct dw_spi *dws)
|
|
+{
|
|
+ debugfs_remove_recursive(dws->debugfs);
|
|
+}
|
|
+
|
|
+#else
|
|
+static inline int dw_spi_debugfs_init(struct dw_spi *dws)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static inline void dw_spi_debugfs_remove(struct dw_spi *dws)
|
|
+{
|
|
+}
|
|
+#endif /* CONFIG_DEBUG_FS */
|
|
+
|
|
+void dw_spi_ext_set_cs(struct spi_device *spi, bool enable)
|
|
+{
|
|
+ struct dw_spi *dws = spi_controller_get_devdata(spi->controller);
|
|
+ bool cs_high = !!(spi->mode & SPI_CS_HIGH);
|
|
+
|
|
+ /*
|
|
+ * DW SPI controller demands any native CS being set in order to
|
|
+ * proceed with data transfer. So in order to activate the SPI
|
|
+ * communications we must set a corresponding bit in the Slave
|
|
+ * Enable register no matter whether the SPI core is configured to
|
|
+ * support active-high or active-low CS level.
|
|
+ */
|
|
+ if (cs_high == enable)
|
|
+ dw_writel(dws, DW_SPI_SER, BIT(spi->chip_select));
|
|
+ else
|
|
+ dw_writel(dws, DW_SPI_SER, 0);
|
|
+}
|
|
+
|
|
+/* Return the max entries we can fill into tx fifo */
|
|
+static inline u32 dw_spi_tx_max(struct dw_spi *dws)
|
|
+{
|
|
+ u32 tx_room, rxtx_gap;
|
|
+
|
|
+ tx_room = dws->fifo_len - dw_readl(dws, DW_SPI_TXFLR);
|
|
+
|
|
+ /*
|
|
+ * Another concern is about the tx/rx mismatch, we
|
|
+ * though to use (dws->fifo_len - rxflr - txflr) as
|
|
+ * one maximum value for tx, but it doesn't cover the
|
|
+ * data which is out of tx/rx fifo and inside the
|
|
+ * shift registers. So a control from sw point of
|
|
+ * view is taken.
|
|
+ */
|
|
+ rxtx_gap = dws->fifo_len - (dws->rx_len - dws->tx_len);
|
|
+
|
|
+ return min3((u32)dws->tx_len, tx_room, rxtx_gap);
|
|
+}
|
|
+
|
|
+/* Return the max entries we should read out of rx fifo */
|
|
+static inline u32 dw_spi_rx_max(struct dw_spi *dws)
|
|
+{
|
|
+ return min_t(u32, dws->rx_len, dw_readl(dws, DW_SPI_RXFLR));
|
|
+}
|
|
+
|
|
+static void dw_writer(struct dw_spi *dws)
|
|
+{
|
|
+ u32 max = dw_spi_tx_max(dws);
|
|
+ u32 txw = 0;
|
|
+
|
|
+ while (max--) {
|
|
+ if (dws->tx) {
|
|
+ if (dws->n_bytes == 1)
|
|
+ txw = *(u8 *)(dws->tx);
|
|
+ else if (dws->n_bytes == 2)
|
|
+ txw = *(u16 *)(dws->tx);
|
|
+ else
|
|
+ txw = *(u32 *)(dws->tx);
|
|
+
|
|
+ dws->tx += dws->n_bytes;
|
|
+ }
|
|
+ dw_write_io_reg(dws, DW_SPI_DR, txw);
|
|
+ --dws->tx_len;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void dw_reader(struct dw_spi *dws)
|
|
+{
|
|
+ u32 max = dw_spi_rx_max(dws);
|
|
+ u32 rxw;
|
|
+
|
|
+ while (max--) {
|
|
+ rxw = dw_read_io_reg(dws, DW_SPI_DR);
|
|
+ if (dws->rx) {
|
|
+ if (dws->n_bytes == 1)
|
|
+ *(u8 *)(dws->rx) = rxw;
|
|
+ else if (dws->n_bytes == 2)
|
|
+ *(u16 *)(dws->rx) = rxw;
|
|
+ else
|
|
+ *(u32 *)(dws->rx) = rxw;
|
|
+
|
|
+ dws->rx += dws->n_bytes;
|
|
+ }
|
|
+ --dws->rx_len;
|
|
+ }
|
|
+}
|
|
+
|
|
+int dw_spi_ext_check_status(struct dw_spi *dws, bool raw)
|
|
+{
|
|
+ u32 irq_status;
|
|
+ int ret = 0;
|
|
+
|
|
+ if (raw)
|
|
+ irq_status = dw_readl(dws, DW_SPI_RISR);
|
|
+ else
|
|
+ irq_status = dw_readl(dws, DW_SPI_ISR);
|
|
+
|
|
+ if (irq_status & DW_SPI_INT_RXOI) {
|
|
+ dev_err(&dws->master->dev, "RX FIFO overflow detected\n");
|
|
+ ret = -EIO;
|
|
+ }
|
|
+
|
|
+ if (irq_status & DW_SPI_INT_RXUI) {
|
|
+ dev_err(&dws->master->dev, "RX FIFO underflow detected\n");
|
|
+ ret = -EIO;
|
|
+ }
|
|
+
|
|
+ if (irq_status & DW_SPI_INT_TXOI) {
|
|
+ dev_err(&dws->master->dev, "TX FIFO overflow detected\n");
|
|
+ ret = -EIO;
|
|
+ }
|
|
+
|
|
+ /* Generically handle the erroneous situation */
|
|
+ if (ret) {
|
|
+ dw_spi_reset_chip(dws);
|
|
+ if (dws->master->cur_msg)
|
|
+ dws->master->cur_msg->status = ret;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static irqreturn_t dw_spi_transfer_handler(struct dw_spi *dws)
|
|
+{
|
|
+ u16 irq_status = dw_readl(dws, DW_SPI_ISR);
|
|
+
|
|
+ if (dw_spi_ext_check_status(dws, false)) {
|
|
+ spi_finalize_current_transfer(dws->master);
|
|
+ return IRQ_HANDLED;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Read data from the Rx FIFO every time we've got a chance executing
|
|
+ * this method. If there is nothing left to receive, terminate the
|
|
+ * procedure. Otherwise adjust the Rx FIFO Threshold level if it's a
|
|
+ * final stage of the transfer. By doing so we'll get the next IRQ
|
|
+ * right when the leftover incoming data is received.
|
|
+ */
|
|
+ dw_reader(dws);
|
|
+ if (!dws->rx_len) {
|
|
+ dw_spi_mask_intr(dws, 0xff);
|
|
+ spi_finalize_current_transfer(dws->master);
|
|
+ } else if (dws->rx_len <= dw_readl(dws, DW_SPI_RXFTLR)) {
|
|
+ dw_writel(dws, DW_SPI_RXFTLR, dws->rx_len - 1);
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Send data out if Tx FIFO Empty IRQ is received. The IRQ will be
|
|
+ * disabled after the data transmission is finished so not to
|
|
+ * have the TXE IRQ flood at the final stage of the transfer.
|
|
+ */
|
|
+ if (irq_status & DW_SPI_INT_TXEI) {
|
|
+ dw_writer(dws);
|
|
+ if (!dws->tx_len)
|
|
+ dw_spi_mask_intr(dws, DW_SPI_INT_TXEI);
|
|
+ }
|
|
+
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+static irqreturn_t dw_spi_ext_irq(int irq, void *dev_id)
|
|
+{
|
|
+ struct spi_controller *master = dev_id;
|
|
+ struct dw_spi *dws = spi_controller_get_devdata(master);
|
|
+ u16 irq_status = dw_readl(dws, DW_SPI_ISR) & DW_SPI_INT_MASK;
|
|
+
|
|
+ if (!irq_status)
|
|
+ return IRQ_NONE;
|
|
+
|
|
+ if (!master->cur_msg) {
|
|
+ dw_spi_mask_intr(dws, 0xff);
|
|
+ return IRQ_HANDLED;
|
|
+ }
|
|
+
|
|
+ return dws->transfer_handler(dws);
|
|
+}
|
|
+
|
|
+static u32 dw_spi_prepare_cr0(struct dw_spi *dws, struct spi_device *spi)
|
|
+{
|
|
+ u32 cr0 = 0;
|
|
+
|
|
+ if (dw_spi_ip_is(dws, PSSI)) {
|
|
+ /* CTRLR0[ 5: 4] Frame Format */
|
|
+ cr0 |= FIELD_PREP(DW_PSSI_CTRLR0_FRF_MASK, DW_SPI_CTRLR0_FRF_MOTO_SPI);
|
|
+
|
|
+ /*
|
|
+ * SPI mode (SCPOL|SCPH)
|
|
+ * CTRLR0[ 6] Serial Clock Phase
|
|
+ * CTRLR0[ 7] Serial Clock Polarity
|
|
+ */
|
|
+ if (spi->mode & SPI_CPOL)
|
|
+ cr0 |= DW_PSSI_CTRLR0_SCPOL;
|
|
+ if (spi->mode & SPI_CPHA)
|
|
+ cr0 |= DW_PSSI_CTRLR0_SCPHA;
|
|
+
|
|
+ /* CTRLR0[11] Shift Register Loop */
|
|
+ if (spi->mode & SPI_LOOP)
|
|
+ cr0 |= DW_PSSI_CTRLR0_SRL;
|
|
+ } else {
|
|
+ /* CTRLR0[ 7: 6] Frame Format */
|
|
+ cr0 |= FIELD_PREP(DW_HSSI_CTRLR0_FRF_MASK, DW_SPI_CTRLR0_FRF_MOTO_SPI);
|
|
+
|
|
+ /*
|
|
+ * SPI mode (SCPOL|SCPH)
|
|
+ * CTRLR0[ 8] Serial Clock Phase
|
|
+ * CTRLR0[ 9] Serial Clock Polarity
|
|
+ */
|
|
+ if (spi->mode & SPI_CPOL)
|
|
+ cr0 |= DW_HSSI_CTRLR0_SCPOL;
|
|
+ if (spi->mode & SPI_CPHA)
|
|
+ cr0 |= DW_HSSI_CTRLR0_SCPHA;
|
|
+
|
|
+ /* CTRLR0[13] Shift Register Loop */
|
|
+ if (spi->mode & SPI_LOOP)
|
|
+ cr0 |= DW_HSSI_CTRLR0_SRL;
|
|
+
|
|
+ /* CTRLR0[31] MST */
|
|
+ if (dw_spi_ver_is_ge(dws, HSSI, 102A))
|
|
+ cr0 |= DW_HSSI_CTRLR0_MST;
|
|
+ }
|
|
+
|
|
+ return cr0;
|
|
+}
|
|
+
|
|
+void dw_spi_ext_update_config(struct dw_spi *dws, struct spi_device *spi,
|
|
+ struct dw_spi_cfg *cfg)
|
|
+{
|
|
+ struct dw_spi_chip_data *chip = spi_get_ctldata(spi);
|
|
+ u32 cr0 = chip->cr0;
|
|
+ u32 speed_hz;
|
|
+ u16 clk_div;
|
|
+
|
|
+ /* CTRLR0[ 4/3: 0] or CTRLR0[ 20: 16] Data Frame Size */
|
|
+ cr0 |= (cfg->dfs - 1) << dws->dfs_offset;
|
|
+
|
|
+ if (dw_spi_ip_is(dws, PSSI))
|
|
+ /* CTRLR0[ 9:8] Transfer Mode */
|
|
+ cr0 |= FIELD_PREP(DW_PSSI_CTRLR0_TMOD_MASK, cfg->tmode);
|
|
+ else
|
|
+ /* CTRLR0[11:10] Transfer Mode */
|
|
+ cr0 |= FIELD_PREP(DW_HSSI_CTRLR0_TMOD_MASK, cfg->tmode);
|
|
+
|
|
+ if (dws->caps & DW_SPI_CAP_EXT_SPI) {
|
|
+ if (cfg->spi_frf)
|
|
+ cr0 |= FIELD_PREP(DW_HSSI_CTRLR0_SPI_FRF_MASK,
|
|
+ cfg->spi_frf);
|
|
+ else
|
|
+ cr0 &= ~DW_HSSI_CTRLR0_SPI_FRF_MASK;
|
|
+ }
|
|
+
|
|
+ dw_writel(dws, DW_SPI_CTRLR0, cr0);
|
|
+
|
|
+ if (cfg->tmode == DW_SPI_CTRLR0_TMOD_EPROMREAD ||
|
|
+ cfg->tmode == DW_SPI_CTRLR0_TMOD_RO ||
|
|
+ (cfg->tmode == DW_SPI_CTRLR0_TMOD_TO &&
|
|
+ (dws->caps & DW_SPI_CAP_EXT_SPI) && cfg->spi_frf))
|
|
+ dw_writel(dws, DW_SPI_CTRLR1, cfg->ndf ? cfg->ndf - 1 : 0);
|
|
+
|
|
+ /* Note DW APB SSI clock divider doesn't support odd numbers */
|
|
+ clk_div = (DIV_ROUND_UP(dws->max_freq, cfg->freq) + 1) & 0xfffe;
|
|
+ speed_hz = dws->max_freq / clk_div;
|
|
+
|
|
+ if (dws->current_freq != speed_hz) {
|
|
+ dw_spi_set_clk(dws, clk_div);
|
|
+ dws->current_freq = speed_hz;
|
|
+ }
|
|
+
|
|
+ /* Update RX sample delay if required */
|
|
+ if (dws->cur_rx_sample_dly != chip->rx_sample_dly) {
|
|
+ dw_writel(dws, DW_SPI_RX_SAMPLE_DLY, chip->rx_sample_dly);
|
|
+ dws->cur_rx_sample_dly = chip->rx_sample_dly;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void dw_spi_ext_irq_setup(struct dw_spi *dws)
|
|
+{
|
|
+ u16 level;
|
|
+ u8 imask;
|
|
+
|
|
+ /*
|
|
+ * Originally Tx and Rx data lengths match. Rx FIFO Threshold level
|
|
+ * will be adjusted at the final stage of the IRQ-based SPI transfer
|
|
+ * execution so not to lose the leftover of the incoming data.
|
|
+ */
|
|
+ level = min_t(unsigned int, dws->fifo_len / 2, dws->tx_len);
|
|
+ dw_writel(dws, DW_SPI_TXFTLR, level);
|
|
+ dw_writel(dws, DW_SPI_RXFTLR, level - 1);
|
|
+
|
|
+ dws->transfer_handler = dw_spi_transfer_handler;
|
|
+
|
|
+ imask = DW_SPI_INT_TXEI | DW_SPI_INT_TXOI |
|
|
+ DW_SPI_INT_RXUI | DW_SPI_INT_RXOI | DW_SPI_INT_RXFI;
|
|
+ dw_spi_umask_intr(dws, imask);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * The iterative procedure of the poll-based transfer is simple: write as much
|
|
+ * as possible to the Tx FIFO, wait until the pending to receive data is ready
|
|
+ * to be read, read it from the Rx FIFO and check whether the performed
|
|
+ * procedure has been successful.
|
|
+ *
|
|
+ * Note this method the same way as the IRQ-based transfer won't work well for
|
|
+ * the SPI devices connected to the controller with native CS due to the
|
|
+ * automatic CS assertion/de-assertion.
|
|
+ */
|
|
+static int dw_spi_poll_transfer(struct dw_spi *dws,
|
|
+ struct spi_transfer *transfer)
|
|
+{
|
|
+ struct spi_delay delay;
|
|
+ u16 nbits;
|
|
+ int ret;
|
|
+
|
|
+ delay.unit = SPI_DELAY_UNIT_SCK;
|
|
+ nbits = dws->n_bytes * BITS_PER_BYTE;
|
|
+
|
|
+ do {
|
|
+ dw_writer(dws);
|
|
+
|
|
+ delay.value = nbits * (dws->rx_len - dws->tx_len);
|
|
+ spi_delay_exec(&delay, transfer);
|
|
+
|
|
+ dw_reader(dws);
|
|
+
|
|
+ ret = dw_spi_ext_check_status(dws, true);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ } while (dws->rx_len);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dw_spi_transfer_one(struct spi_controller *master,
|
|
+ struct spi_device *spi,
|
|
+ struct spi_transfer *transfer)
|
|
+{
|
|
+ struct dw_spi *dws = spi_controller_get_devdata(master);
|
|
+ struct dw_spi_cfg cfg = {
|
|
+ .tmode = DW_SPI_CTRLR0_TMOD_TR,
|
|
+ .dfs = transfer->bits_per_word,
|
|
+ .freq = transfer->speed_hz,
|
|
+ .spi_frf = 0,
|
|
+ };
|
|
+ int ret;
|
|
+
|
|
+ dws->dma_mapped = 0;
|
|
+ dws->n_bytes = DIV_ROUND_UP(transfer->bits_per_word, BITS_PER_BYTE);
|
|
+ dws->tx = (void *)transfer->tx_buf;
|
|
+ dws->tx_len = transfer->len / dws->n_bytes;
|
|
+ dws->rx = transfer->rx_buf;
|
|
+ dws->rx_len = dws->tx_len;
|
|
+
|
|
+ /* Ensure the data above is visible for all CPUs */
|
|
+ smp_mb();
|
|
+
|
|
+ dw_spi_enable_chip(dws, 0);
|
|
+
|
|
+ dw_spi_ext_update_config(dws, spi, &cfg);
|
|
+
|
|
+ transfer->effective_speed_hz = dws->current_freq;
|
|
+
|
|
+ /* Check if current transfer is a DMA transaction */
|
|
+ if (master->can_dma && master->can_dma(master, spi, transfer))
|
|
+ dws->dma_mapped = master->cur_msg_mapped;
|
|
+
|
|
+ /* For poll mode just disable all interrupts */
|
|
+ dw_spi_mask_intr(dws, 0xff);
|
|
+
|
|
+ if (dws->dma_mapped) {
|
|
+ ret = dws->dma_ops->dma_setup(dws, transfer);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ dw_spi_enable_chip(dws, 1);
|
|
+
|
|
+ if (dws->dma_mapped)
|
|
+ return dws->dma_ops->dma_transfer(dws, transfer);
|
|
+ else if (dws->irq == IRQ_NOTCONNECTED)
|
|
+ return dw_spi_poll_transfer(dws, transfer);
|
|
+
|
|
+ dw_spi_ext_irq_setup(dws);
|
|
+
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+static void dw_spi_handle_err(struct spi_controller *master,
|
|
+ struct spi_message *msg)
|
|
+{
|
|
+ struct dw_spi *dws = spi_controller_get_devdata(master);
|
|
+
|
|
+ if (dws->dma_mapped)
|
|
+ dws->dma_ops->dma_stop(dws);
|
|
+
|
|
+ dw_spi_reset_chip(dws);
|
|
+}
|
|
+
|
|
+static int dw_spi_adjust_mem_op_size(struct spi_mem *mem, struct spi_mem_op *op)
|
|
+{
|
|
+ if (op->data.dir == SPI_MEM_DATA_IN)
|
|
+ op->data.nbytes = clamp_val(op->data.nbytes, 0, DW_SPI_NDF_MASK + 1);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static bool dw_spi_supports_mem_op(struct spi_mem *mem,
|
|
+ const struct spi_mem_op *op)
|
|
+{
|
|
+ struct dw_spi *dws = spi_controller_get_devdata(mem->spi->controller);
|
|
+
|
|
+ /*
|
|
+ * Only support the numbler of io lines used to transfer the cmd
|
|
+ * is 1 in enhanced SPI for now.
|
|
+ */
|
|
+ if (op->addr.buswidth > 1 || op->dummy.buswidth > 1 ||
|
|
+ op->cmd.buswidth > 1)
|
|
+ return false;
|
|
+
|
|
+ /* In enhanced SPI 1, 2, 4, 8 all are valid modes. */
|
|
+ if (op->data.buswidth > 1 && (!(dws->caps & DW_SPI_CAP_EXT_SPI)))
|
|
+ return false;
|
|
+
|
|
+ /* Only support upto 32 bit address in enhanced SPI for now. */
|
|
+ if (op->data.buswidth > 1 && op->addr.nbytes > 4)
|
|
+ return false;
|
|
+
|
|
+ return spi_mem_default_supports_op(mem, op);
|
|
+}
|
|
+
|
|
+static int dw_spi_init_mem_buf(struct dw_spi *dws, const struct spi_mem_op *op,
|
|
+ bool enhanced_spi)
|
|
+{
|
|
+ unsigned int i, j, len;
|
|
+ u8 *out;
|
|
+
|
|
+ /*
|
|
+ * Calculate the total length of the EEPROM command transfer and
|
|
+ * either use the pre-allocated buffer or create a temporary one.
|
|
+ */
|
|
+ len = op->cmd.nbytes + op->addr.nbytes + op->dummy.nbytes;
|
|
+ if (op->data.dir == SPI_MEM_DATA_OUT)
|
|
+ len += op->data.nbytes;
|
|
+
|
|
+ if (len <= DW_SPI_BUF_SIZE) {
|
|
+ out = dws->buf;
|
|
+ } else {
|
|
+ out = kzalloc(len, GFP_KERNEL);
|
|
+ if (!out)
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Collect the operation code, address and dummy bytes into the single
|
|
+ * buffer. If it's a transfer with data to be sent, also copy it into the
|
|
+ * single buffer in order to speed the data transmission up.
|
|
+ */
|
|
+ for (i = 0; i < op->cmd.nbytes; ++i)
|
|
+ out[i] = DW_SPI_GET_BYTE(op->cmd.opcode, op->cmd.nbytes - i - 1);
|
|
+
|
|
+ if (enhanced_spi) {
|
|
+ /*
|
|
+ * Fill the remaining spaces of dws->reg_io_width bytes
|
|
+ * size register with zero for cmd.
|
|
+ */
|
|
+ for (; i < dws->reg_io_width; ++i)
|
|
+ out[i] = 0;
|
|
+ /*
|
|
+ * Copy the address bytes in dws->reg_io_width bytes size
|
|
+ * register and fill remaining spaces with zero.
|
|
+ */
|
|
+ for (j = op->addr.nbytes; j > 0; ++i, --j)
|
|
+ out[i] = DW_SPI_GET_BYTE(op->addr.val, op->addr.nbytes - j);
|
|
+ for (j = op->addr.nbytes; j < dws->reg_io_width; ++i, ++j)
|
|
+ out[i] = 0;
|
|
+ } else {
|
|
+ for (j = 0; j < op->addr.nbytes; ++i, ++j)
|
|
+ out[i] = DW_SPI_GET_BYTE(op->addr.val, op->addr.nbytes - j - 1);
|
|
+ }
|
|
+
|
|
+ if (!enhanced_spi) {
|
|
+ /*
|
|
+ * dummy bytes are not needed in enhanced mode as
|
|
+ * wait_cycles specified as number of SPI clock cycles
|
|
+ * between control frames transmit and data reception
|
|
+ * will be mentioned in enhanced spi mode.
|
|
+ */
|
|
+ for (j = 0; j < op->dummy.nbytes; ++i, ++j)
|
|
+ out[i] = 0x0;
|
|
+ }
|
|
+
|
|
+ if (op->data.dir == SPI_MEM_DATA_OUT)
|
|
+ memcpy(&out[i], op->data.buf.out, op->data.nbytes);
|
|
+
|
|
+ dws->n_bytes = 1;
|
|
+ dws->tx = out;
|
|
+
|
|
+ if (enhanced_spi) {
|
|
+ /*
|
|
+ * In enhanced mode cmd will be one FIFO and address
|
|
+ * will be one more FIFO.
|
|
+ */
|
|
+ dws->tx_len = 1;
|
|
+ if (op->addr.nbytes)
|
|
+ dws->tx_len += 1;
|
|
+ if (op->data.dir == SPI_MEM_DATA_OUT)
|
|
+ dws->tx_len += op->data.nbytes;
|
|
+ } else {
|
|
+ dws->tx_len = len;
|
|
+ }
|
|
+
|
|
+ if (op->data.dir == SPI_MEM_DATA_IN) {
|
|
+ dws->rx = op->data.buf.in;
|
|
+ dws->rx_len = op->data.nbytes;
|
|
+ } else {
|
|
+ dws->rx = NULL;
|
|
+ dws->rx_len = 0;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void dw_spi_free_mem_buf(struct dw_spi *dws)
|
|
+{
|
|
+ if (dws->tx != dws->buf)
|
|
+ kfree(dws->tx);
|
|
+}
|
|
+
|
|
+static int dw_spi_write_then_read(struct dw_spi *dws, struct spi_device *spi)
|
|
+{
|
|
+ u32 room, entries, sts;
|
|
+ unsigned int len;
|
|
+ u8 *buf;
|
|
+
|
|
+ /*
|
|
+ * At initial stage we just pre-fill the Tx FIFO in with no rush,
|
|
+ * since native CS hasn't been enabled yet and the automatic data
|
|
+ * transmission won't start til we do that.
|
|
+ */
|
|
+ len = min(dws->fifo_len, dws->tx_len);
|
|
+ buf = dws->tx;
|
|
+ while (len--)
|
|
+ dw_write_io_reg(dws, DW_SPI_DR, *buf++);
|
|
+
|
|
+ /*
|
|
+ * After setting any bit in the SER register the transmission will
|
|
+ * start automatically. We have to keep up with that procedure
|
|
+ * otherwise the CS de-assertion will happen whereupon the memory
|
|
+ * operation will be pre-terminated.
|
|
+ */
|
|
+ len = dws->tx_len - ((void *)buf - dws->tx);
|
|
+ dw_spi_ext_set_cs(spi, false);
|
|
+ while (len) {
|
|
+ entries = readl_relaxed(dws->regs + DW_SPI_TXFLR);
|
|
+ if (!entries) {
|
|
+ dev_err(&dws->master->dev, "CS de-assertion on Tx\n");
|
|
+ return -EIO;
|
|
+ }
|
|
+ room = min(dws->fifo_len - entries, len);
|
|
+ for (; room; --room, --len)
|
|
+ dw_write_io_reg(dws, DW_SPI_DR, *buf++);
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Data fetching will start automatically if the EEPROM-read mode is
|
|
+ * activated. We have to keep up with the incoming data pace to
|
|
+ * prevent the Rx FIFO overflow causing the inbound data loss.
|
|
+ */
|
|
+ len = dws->rx_len;
|
|
+ buf = dws->rx;
|
|
+ while (len) {
|
|
+ entries = readl_relaxed(dws->regs + DW_SPI_RXFLR);
|
|
+ if (!entries) {
|
|
+ sts = readl_relaxed(dws->regs + DW_SPI_RISR);
|
|
+ if (sts & DW_SPI_INT_RXOI) {
|
|
+ dev_err(&dws->master->dev, "FIFO overflow on Rx\n");
|
|
+ return -EIO;
|
|
+ }
|
|
+ continue;
|
|
+ }
|
|
+ entries = min(entries, len);
|
|
+ for (; entries; --entries, --len)
|
|
+ *buf++ = dw_read_io_reg(dws, DW_SPI_DR);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static inline bool dw_spi_ctlr_busy(struct dw_spi *dws)
|
|
+{
|
|
+ return dw_readl(dws, DW_SPI_SR) & DW_SPI_SR_BUSY;
|
|
+}
|
|
+
|
|
+static int dw_spi_wait_mem_op_done(struct dw_spi *dws)
|
|
+{
|
|
+ int retry = DW_SPI_WAIT_RETRIES;
|
|
+ struct spi_delay delay;
|
|
+ unsigned long ns, us;
|
|
+ u32 nents;
|
|
+
|
|
+ nents = dw_readl(dws, DW_SPI_TXFLR);
|
|
+ ns = NSEC_PER_SEC / dws->current_freq * nents;
|
|
+ ns *= dws->n_bytes * BITS_PER_BYTE;
|
|
+ if (ns <= NSEC_PER_USEC) {
|
|
+ delay.unit = SPI_DELAY_UNIT_NSECS;
|
|
+ delay.value = ns;
|
|
+ } else {
|
|
+ us = DIV_ROUND_UP(ns, NSEC_PER_USEC);
|
|
+ delay.unit = SPI_DELAY_UNIT_USECS;
|
|
+ delay.value = clamp_val(us, 0, USHRT_MAX);
|
|
+ }
|
|
+
|
|
+ while (dw_spi_ctlr_busy(dws) && retry--)
|
|
+ spi_delay_exec(&delay, NULL);
|
|
+
|
|
+ if (retry < 0) {
|
|
+ dev_err(&dws->master->dev, "Mem op hanged up\n");
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void ext_transfer_delay(struct dw_spi *dws)
|
|
+{
|
|
+ struct spi_delay delay;
|
|
+ unsigned long ns, us;
|
|
+ u32 nents;
|
|
+
|
|
+ nents = dw_readl(dws, DW_SPI_TXFLR);
|
|
+ ns = NSEC_PER_SEC / dws->current_freq * nents;
|
|
+ ns *= dws->n_bytes * BITS_PER_BYTE;
|
|
+ if (ns <= NSEC_PER_USEC) {
|
|
+ delay.unit = SPI_DELAY_UNIT_NSECS;
|
|
+ delay.value = ns;
|
|
+ } else {
|
|
+ us = DIV_ROUND_UP(ns, NSEC_PER_USEC);
|
|
+ delay.unit = SPI_DELAY_UNIT_USECS;
|
|
+ delay.value = clamp_val(us, 0, USHRT_MAX);
|
|
+ }
|
|
+ /* wait until there is some space in TX FIFO */
|
|
+ while (!(dw_readl(dws, DW_SPI_SR) & DW_SPI_SR_TF_NOT_FULL))
|
|
+ spi_delay_exec(&delay, NULL);
|
|
+}
|
|
+
|
|
+static void dw_spi_stop_mem_op(struct dw_spi *dws, struct spi_device *spi)
|
|
+{
|
|
+ dw_spi_enable_chip(dws, 0);
|
|
+ dw_spi_ext_set_cs(spi, true);
|
|
+ dw_spi_enable_chip(dws, 1);
|
|
+}
|
|
+
|
|
+static int enhanced_transfer(struct dw_spi *dws, struct spi_device *spi,
|
|
+ const struct spi_mem_op *op)
|
|
+{
|
|
+ u32 max, txw = 0, rxw;
|
|
+ bool cs_done = false;
|
|
+ void *buf = dws->tx;
|
|
+ int ret;
|
|
+
|
|
+ /* Send cmd as 32 bit value */
|
|
+ if (buf) {
|
|
+ txw = *(u32 *)(buf);
|
|
+ dw_write_io_reg(dws, DW_SPI_DR, txw);
|
|
+ buf += 4;
|
|
+ dws->tx_len--;
|
|
+ if (op->addr.nbytes) {
|
|
+ /*
|
|
+ * Send address as 32 bit value if address
|
|
+ * is present in the instruction.
|
|
+ */
|
|
+ txw = *(u32 *)(buf);
|
|
+ dw_write_io_reg(dws, DW_SPI_DR, txw);
|
|
+ buf += 4;
|
|
+ dws->tx_len--;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ do {
|
|
+ max = min_t(u32, dws->tx_len, dws->fifo_len -
|
|
+ dw_readl(dws, DW_SPI_TXFLR));
|
|
+ while (max--) {
|
|
+ if (buf) {
|
|
+ txw = *(u8 *)(buf);
|
|
+ buf += dws->n_bytes;
|
|
+ }
|
|
+ dw_write_io_reg(dws, DW_SPI_DR, txw);
|
|
+ --dws->tx_len;
|
|
+ }
|
|
+ /* Enable CS after filling up FIFO */
|
|
+ if (!cs_done) {
|
|
+ dw_spi_ext_set_cs(spi, false);
|
|
+ cs_done = true;
|
|
+ }
|
|
+ ext_transfer_delay(dws);
|
|
+ if (!dws->tx_len && !dws->rx_len) {
|
|
+ /*
|
|
+ * We only need to wait for done if there is
|
|
+ * nothing to receive and there is nothing more
|
|
+ * to transmit. If we are receiving, then the
|
|
+ * wait cycles will make sure we wait.
|
|
+ */
|
|
+ ret = dw_spi_wait_mem_op_done(dws);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ }
|
|
+ } while (dws->tx_len);
|
|
+
|
|
+ buf = dws->rx;
|
|
+ while (dws->rx_len) {
|
|
+ max = dw_spi_rx_max(dws);
|
|
+
|
|
+ while (max--) {
|
|
+ rxw = dw_read_io_reg(dws, DW_SPI_DR);
|
|
+ if (buf) {
|
|
+ *(u8 *)(buf) = rxw;
|
|
+ buf += dws->n_bytes;
|
|
+ }
|
|
+ --dws->rx_len;
|
|
+ }
|
|
+
|
|
+ ret = dw_spi_ext_check_status(dws, true);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void update_spi_ctrl0(struct dw_spi *dws, const struct spi_mem_op *op, bool enable)
|
|
+{
|
|
+ u32 spi_ctrlr0;
|
|
+ u32 addr_l = 0;
|
|
+
|
|
+ spi_ctrlr0 = dw_readl(dws, DW_SPI_SPI_CTRLR0);
|
|
+ if (enable) {
|
|
+ spi_ctrlr0 |= FIELD_PREP(DW_HSSI_SPI_CTRLR0_WAIT_CYCLE_MASK,
|
|
+ op->dummy.nbytes * BITS_PER_BYTE);
|
|
+ /* 8 bit instruction length */
|
|
+ spi_ctrlr0 |= FIELD_PREP(DW_HSSI_SPI_CTRLR0_INST_L_MASK,
|
|
+ DW_HSSI_SPI_CTRLR0_INST_L8);
|
|
+ /* 32 bit address length */
|
|
+ addr_l = clamp(op->addr.nbytes * 2, 0, 0xf);
|
|
+ spi_ctrlr0 |= FIELD_PREP(DW_HSSI_SPI_CTRLR0_ADDR_L_MASK,
|
|
+ addr_l);
|
|
+ /* Enable clock stretching */
|
|
+ spi_ctrlr0 |= DW_HSSI_SPI_CTRLR0_CLK_STRETCH_EN;
|
|
+ } else {
|
|
+ spi_ctrlr0 &= ~DW_HSSI_SPI_CTRLR0_WAIT_CYCLE_MASK;
|
|
+ spi_ctrlr0 &= ~DW_HSSI_SPI_CTRLR0_INST_L_MASK;
|
|
+ spi_ctrlr0 &= ~DW_HSSI_SPI_CTRLR0_ADDR_L_MASK;
|
|
+ spi_ctrlr0 &= ~DW_HSSI_SPI_CTRLR0_CLK_STRETCH_EN;
|
|
+ }
|
|
+
|
|
+ dw_writel(dws, DW_SPI_SPI_CTRLR0, spi_ctrlr0);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * The SPI memory operation implementation below is the best choice for the
|
|
+ * devices, which are selected by the native chip-select lane. It's
|
|
+ * specifically developed to workaround the problem with automatic chip-select
|
|
+ * lane toggle when there is no data in the Tx FIFO buffer. Luckily the current
|
|
+ * SPI-mem core calls exec_op() callback only if the GPIO-based CS is
|
|
+ * unavailable.
|
|
+ */
|
|
+static int dw_spi_exec_mem_op(struct spi_mem *mem, const struct spi_mem_op *op)
|
|
+{
|
|
+ struct dw_spi *dws = spi_controller_get_devdata(mem->spi->controller);
|
|
+ bool enhanced_spi = false;
|
|
+ struct dw_spi_cfg cfg;
|
|
+ unsigned long flags;
|
|
+ int ret;
|
|
+
|
|
+ if (dws->caps & DW_SPI_CAP_EXT_SPI) {
|
|
+ switch (op->data.buswidth) {
|
|
+ case 2:
|
|
+ cfg.spi_frf = DW_SSI_CTRLR0_SPI_FRF_DUAL_SPI;
|
|
+ enhanced_spi = true;
|
|
+ break;
|
|
+ case 4:
|
|
+ cfg.spi_frf = DW_SSI_CTRLR0_SPI_FRF_QUAD_SPI;
|
|
+ enhanced_spi = true;
|
|
+ break;
|
|
+ case 8:
|
|
+ cfg.spi_frf = DW_SSI_CTRLR0_SPI_FRF_OCT_SPI;
|
|
+ enhanced_spi = true;
|
|
+ break;
|
|
+ default:
|
|
+ cfg.spi_frf = 0;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Collect the outbound data into a single buffer to speed the
|
|
+ * transmission up at least on the initial stage.
|
|
+ */
|
|
+ ret = dw_spi_init_mem_buf(dws, op, enhanced_spi);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /*
|
|
+ * DW SPI EEPROM-read mode is required only for the SPI memory Data-IN
|
|
+ * operation. Transmit-only mode is suitable for the rest of them.
|
|
+ */
|
|
+ cfg.dfs = 8;
|
|
+ cfg.freq = clamp(mem->spi->max_speed_hz, 0U, dws->max_mem_freq);
|
|
+ if (op->data.dir == SPI_MEM_DATA_IN) {
|
|
+ if (enhanced_spi)
|
|
+ cfg.tmode = DW_SPI_CTRLR0_TMOD_RO;
|
|
+ else
|
|
+ cfg.tmode = DW_SPI_CTRLR0_TMOD_EPROMREAD;
|
|
+ cfg.ndf = op->data.nbytes;
|
|
+ } else {
|
|
+ cfg.tmode = DW_SPI_CTRLR0_TMOD_TO;
|
|
+ if (enhanced_spi)
|
|
+ cfg.ndf = op->data.nbytes;
|
|
+ }
|
|
+
|
|
+ dw_spi_enable_chip(dws, 0);
|
|
+
|
|
+ if (enhanced_spi) {
|
|
+ u16 level;
|
|
+
|
|
+ level = min_t(unsigned int, dws->fifo_len / 2, dws->tx_len);
|
|
+ dw_writel(dws, DW_SPI_TXFTLR, level);
|
|
+ /*
|
|
+ * In enhanced mode if we are reading then tx_len is 0 as we
|
|
+ * have nothing to transmit. Calculate DW_SPI_RXFTLR with
|
|
+ * rx_len.
|
|
+ */
|
|
+ if (dws->rx_len != 0) {
|
|
+ level = min_t(u16, dws->fifo_len / 2, dws->rx_len);
|
|
+ }
|
|
+ dw_writel(dws, DW_SPI_RXFTLR, level - 1);
|
|
+ }
|
|
+
|
|
+ if (dws->caps & DW_SPI_CAP_EXT_SPI)
|
|
+ update_spi_ctrl0(dws, op, enhanced_spi);
|
|
+
|
|
+ dw_spi_ext_update_config(dws, mem->spi, &cfg);
|
|
+
|
|
+ dw_spi_mask_intr(dws, 0xff);
|
|
+
|
|
+ dw_spi_enable_chip(dws, 1);
|
|
+
|
|
+ /*
|
|
+ * DW APB SSI controller has very nasty peculiarities. First originally
|
|
+ * (without any vendor-specific modifications) it doesn't provide a
|
|
+ * direct way to set and clear the native chip-select signal. Instead
|
|
+ * the controller asserts the CS lane if Tx FIFO isn't empty and a
|
|
+ * transmission is going on, and automatically de-asserts it back to
|
|
+ * the high level if the Tx FIFO doesn't have anything to be pushed
|
|
+ * out. Due to that a multi-tasking or heavy IRQs activity might be
|
|
+ * fatal, since the transfer procedure preemption may cause the Tx FIFO
|
|
+ * getting empty and sudden CS de-assertion, which in the middle of the
|
|
+ * transfer will most likely cause the data loss. Secondly the
|
|
+ * EEPROM-read or Read-only DW SPI transfer modes imply the incoming
|
|
+ * data being automatically pulled in into the Rx FIFO. So if the
|
|
+ * driver software is late in fetching the data from the FIFO before
|
|
+ * it's overflown, new incoming data will be lost. In order to make
|
|
+ * sure the executed memory operations are CS-atomic and to prevent the
|
|
+ * Rx FIFO overflow we have to disable the local interrupts so to block
|
|
+ * any preemption during the subsequent IO operations.
|
|
+ *
|
|
+ * Note. At some circumstances disabling IRQs may not help to prevent
|
|
+ * the problems described above. The CS de-assertion and Rx FIFO
|
|
+ * overflow may still happen due to the relatively slow system bus or
|
|
+ * CPU not working fast enough, so the write-then-read algo implemented
|
|
+ * here just won't keep up with the SPI bus data transfer. Such
|
|
+ * situation is highly platform specific and is supposed to be fixed by
|
|
+ * manually restricting the SPI bus frequency using the
|
|
+ * dws->max_mem_freq parameter.
|
|
+ */
|
|
+ if (!enhanced_spi) {
|
|
+ local_irq_save(flags);
|
|
+ preempt_disable();
|
|
+
|
|
+ ret = dw_spi_write_then_read(dws, mem->spi);
|
|
+
|
|
+ local_irq_restore(flags);
|
|
+ preempt_enable();
|
|
+
|
|
+ /*
|
|
+ * Wait for the operation being finished and check the controller
|
|
+ * status only if there hasn't been any run-time error detected. In the
|
|
+ * former case it's just pointless. In the later one to prevent an
|
|
+ * additional error message printing since any hw error flag being set
|
|
+ * would be due to an error detected on the data transfer.
|
|
+ */
|
|
+ if (!ret) {
|
|
+ ret = dw_spi_wait_mem_op_done(dws);
|
|
+ if (!ret)
|
|
+ ret = dw_spi_ext_check_status(dws, true);
|
|
+ }
|
|
+ } else {
|
|
+ /*
|
|
+ * We donot need to disable IRQs as clock stretching will
|
|
+ * be enabled in enhanced mode which will prevent CS
|
|
+ * from being de-assert.
|
|
+ */
|
|
+ ret = enhanced_transfer(dws, mem->spi, op);
|
|
+ }
|
|
+
|
|
+ dw_spi_stop_mem_op(dws, mem->spi);
|
|
+
|
|
+ dw_spi_free_mem_buf(dws);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Initialize the default memory operations if a glue layer hasn't specified
|
|
+ * custom ones. Direct mapping operations will be preserved anyway since DW SPI
|
|
+ * controller doesn't have an embedded dirmap interface. Note the memory
|
|
+ * operations implemented in this driver is the best choice only for the DW APB
|
|
+ * SSI controller with standard native CS functionality. If a hardware vendor
|
|
+ * has fixed the automatic CS assertion/de-assertion peculiarity, then it will
|
|
+ * be safer to use the normal SPI-messages-based transfers implementation.
|
|
+ */
|
|
+static void dw_spi_init_mem_ops(struct dw_spi *dws)
|
|
+{
|
|
+ if (!dws->mem_ops.exec_op && !(dws->caps & DW_SPI_CAP_CS_OVERRIDE) &&
|
|
+ !dws->set_cs) {
|
|
+ dws->mem_ops.adjust_op_size = dw_spi_adjust_mem_op_size;
|
|
+ dws->mem_ops.supports_op = dw_spi_supports_mem_op;
|
|
+ dws->mem_ops.exec_op = dw_spi_exec_mem_op;
|
|
+ if (!dws->max_mem_freq)
|
|
+ dws->max_mem_freq = dws->max_freq;
|
|
+ }
|
|
+}
|
|
+
|
|
+/* This may be called twice for each spi dev */
|
|
+static int dw_spi_setup(struct spi_device *spi)
|
|
+{
|
|
+ struct dw_spi *dws = spi_controller_get_devdata(spi->controller);
|
|
+ struct dw_spi_chip_data *chip;
|
|
+
|
|
+ /* Only alloc on first setup */
|
|
+ chip = spi_get_ctldata(spi);
|
|
+ if (!chip) {
|
|
+ struct dw_spi *dws = spi_controller_get_devdata(spi->controller);
|
|
+ u32 rx_sample_dly_ns;
|
|
+
|
|
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
|
|
+ if (!chip)
|
|
+ return -ENOMEM;
|
|
+ spi_set_ctldata(spi, chip);
|
|
+ /* Get specific / default rx-sample-delay */
|
|
+ if (device_property_read_u32(&spi->dev,
|
|
+ "rx-sample-delay-ns",
|
|
+ &rx_sample_dly_ns) != 0)
|
|
+ /* Use default controller value */
|
|
+ rx_sample_dly_ns = dws->def_rx_sample_dly_ns;
|
|
+ chip->rx_sample_dly = DIV_ROUND_CLOSEST(rx_sample_dly_ns,
|
|
+ NSEC_PER_SEC /
|
|
+ dws->max_freq);
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Update CR0 data each time the setup callback is invoked since
|
|
+ * the device parameters could have been changed, for instance, by
|
|
+ * the MMC SPI driver or something else.
|
|
+ */
|
|
+ chip->cr0 = dw_spi_prepare_cr0(dws, spi);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void dw_spi_cleanup(struct spi_device *spi)
|
|
+{
|
|
+ struct dw_spi_chip_data *chip = spi_get_ctldata(spi);
|
|
+
|
|
+ kfree(chip);
|
|
+ spi_set_ctldata(spi, NULL);
|
|
+}
|
|
+
|
|
+/* Restart the controller, disable all interrupts, clean rx fifo */
|
|
+static void dw_spi_hw_init(struct device *dev, struct dw_spi *dws)
|
|
+{
|
|
+ dw_spi_reset_chip(dws);
|
|
+
|
|
+ /*
|
|
+ * Retrieve the Synopsys component version if it hasn't been specified
|
|
+ * by the platform. CoreKit version ID is encoded as a 3-chars ASCII
|
|
+ * code enclosed with '*' (typical for the most of Synopsys IP-cores).
|
|
+ */
|
|
+ if (!dws->ver) {
|
|
+ dws->ver = dw_readl(dws, DW_SPI_VERSION);
|
|
+
|
|
+ dev_dbg(dev, "Synopsys DWC%sSSI v%c.%c%c\n",
|
|
+ dw_spi_ip_is(dws, PSSI) ? " APB " : " ",
|
|
+ DW_SPI_GET_BYTE(dws->ver, 3), DW_SPI_GET_BYTE(dws->ver, 2),
|
|
+ DW_SPI_GET_BYTE(dws->ver, 1));
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Try to detect the FIFO depth if not set by interface driver,
|
|
+ * the depth could be from 2 to 256 from HW spec
|
|
+ */
|
|
+ if (!dws->fifo_len) {
|
|
+ u32 fifo;
|
|
+
|
|
+ for (fifo = 1; fifo < 256; fifo++) {
|
|
+ dw_writel(dws, DW_SPI_TXFTLR, fifo);
|
|
+ if (fifo != dw_readl(dws, DW_SPI_TXFTLR))
|
|
+ break;
|
|
+ }
|
|
+ dw_writel(dws, DW_SPI_TXFTLR, 0);
|
|
+
|
|
+ dws->fifo_len = (fifo == 1) ? 0 : fifo;
|
|
+ dev_dbg(dev, "Detected FIFO size: %u bytes\n", dws->fifo_len);
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Detect CTRLR0.DFS field size and offset by testing the lowest bits
|
|
+ * writability. Note DWC SSI controller also has the extended DFS, but
|
|
+ * with zero offset.
|
|
+ */
|
|
+ if (dw_spi_ip_is(dws, PSSI)) {
|
|
+ u32 cr0, tmp = dw_readl(dws, DW_SPI_CTRLR0);
|
|
+
|
|
+ dw_spi_enable_chip(dws, 0);
|
|
+ dw_writel(dws, DW_SPI_CTRLR0, 0xffffffff);
|
|
+ cr0 = dw_readl(dws, DW_SPI_CTRLR0);
|
|
+ dw_writel(dws, DW_SPI_CTRLR0, tmp);
|
|
+ dw_spi_enable_chip(dws, 1);
|
|
+
|
|
+ if (!(cr0 & DW_PSSI_CTRLR0_DFS_MASK)) {
|
|
+ dws->caps |= DW_SPI_CAP_DFS32;
|
|
+ dws->dfs_offset = __bf_shf(DW_PSSI_CTRLR0_DFS32_MASK);
|
|
+ dev_dbg(dev, "Detected 32-bits max data frame size\n");
|
|
+ }
|
|
+ } else {
|
|
+ dws->caps |= DW_SPI_CAP_DFS32;
|
|
+ }
|
|
+}
|
|
+
|
|
+int dw_spi_ext_add_host(struct device *dev, struct dw_spi *dws)
|
|
+{
|
|
+ struct spi_controller *master;
|
|
+ int ret;
|
|
+
|
|
+ if (!dws)
|
|
+ return -EINVAL;
|
|
+
|
|
+ master = spi_alloc_master(dev, 0);
|
|
+ if (!master)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ device_set_node(&master->dev, dev_fwnode(dev));
|
|
+
|
|
+ dws->master = master;
|
|
+ dws->dma_addr = (dma_addr_t)(dws->paddr + DW_SPI_DR);
|
|
+
|
|
+ spi_controller_set_devdata(master, dws);
|
|
+
|
|
+ /* Basic HW init */
|
|
+ dw_spi_hw_init(dev, dws);
|
|
+
|
|
+ ret = request_irq(dws->irq, dw_spi_ext_irq, IRQF_SHARED, dev_name(dev),
|
|
+ master);
|
|
+ if (ret < 0 && ret != -ENOTCONN) {
|
|
+ dev_err(dev, "can not get IRQ\n");
|
|
+ goto err_free_master;
|
|
+ }
|
|
+
|
|
+ dw_spi_init_mem_ops(dws);
|
|
+
|
|
+ master->use_gpio_descriptors = true;
|
|
+ master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP;
|
|
+ if (dws->caps & DW_SPI_CAP_EXT_SPI)
|
|
+ master->mode_bits |= SPI_TX_DUAL | SPI_RX_DUAL |
|
|
+ SPI_TX_QUAD | SPI_RX_QUAD |
|
|
+ SPI_TX_OCTAL | SPI_RX_OCTAL;
|
|
+ if (dws->caps & DW_SPI_CAP_DFS32)
|
|
+ master->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 32);
|
|
+ else
|
|
+ master->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 16);
|
|
+ master->bus_num = dws->bus_num;
|
|
+ master->num_chipselect = dws->num_cs;
|
|
+ master->setup = dw_spi_setup;
|
|
+ master->cleanup = dw_spi_cleanup;
|
|
+ if (dws->set_cs)
|
|
+ master->set_cs = dws->set_cs;
|
|
+ else
|
|
+ master->set_cs = dw_spi_ext_set_cs;
|
|
+ master->transfer_one = dw_spi_transfer_one;
|
|
+ master->handle_err = dw_spi_handle_err;
|
|
+ if (dws->mem_ops.exec_op)
|
|
+ master->mem_ops = &dws->mem_ops;
|
|
+ master->max_speed_hz = dws->max_freq;
|
|
+ master->flags = SPI_MASTER_GPIO_SS;
|
|
+ master->auto_runtime_pm = true;
|
|
+
|
|
+ /* Get default rx sample delay */
|
|
+ device_property_read_u32(dev, "rx-sample-delay-ns",
|
|
+ &dws->def_rx_sample_dly_ns);
|
|
+
|
|
+ if (dws->dma_ops && dws->dma_ops->dma_init) {
|
|
+ ret = dws->dma_ops->dma_init(dev, dws);
|
|
+ if (ret == -EPROBE_DEFER) {
|
|
+ goto err_free_irq;
|
|
+ } else if (ret) {
|
|
+ dev_warn(dev, "DMA init failed\n");
|
|
+ } else {
|
|
+ master->can_dma = dws->dma_ops->can_dma;
|
|
+ master->flags |= SPI_CONTROLLER_MUST_TX;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ ret = spi_register_controller(master);
|
|
+ if (ret) {
|
|
+ dev_err_probe(dev, ret, "problem registering spi master\n");
|
|
+ goto err_dma_exit;
|
|
+ }
|
|
+
|
|
+ dw_spi_debugfs_init(dws);
|
|
+ return 0;
|
|
+
|
|
+err_dma_exit:
|
|
+ if (dws->dma_ops && dws->dma_ops->dma_exit)
|
|
+ dws->dma_ops->dma_exit(dws);
|
|
+ dw_spi_enable_chip(dws, 0);
|
|
+err_free_irq:
|
|
+ free_irq(dws->irq, master);
|
|
+err_free_master:
|
|
+ spi_controller_put(master);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+void dw_spi_ext_remove_host(struct dw_spi *dws)
|
|
+{
|
|
+ dw_spi_debugfs_remove(dws);
|
|
+
|
|
+ spi_unregister_controller(dws->master);
|
|
+
|
|
+ if (dws->dma_ops && dws->dma_ops->dma_exit)
|
|
+ dws->dma_ops->dma_exit(dws);
|
|
+
|
|
+ dw_spi_shutdown_chip(dws);
|
|
+
|
|
+ free_irq(dws->irq, dws->master);
|
|
+}
|
|
+
|
|
+int dw_spi_ext_suspend_host(struct dw_spi *dws)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = spi_controller_suspend(dws->master);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ dw_spi_shutdown_chip(dws);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int dw_spi_ext_resume_host(struct dw_spi *dws)
|
|
+{
|
|
+ dw_spi_hw_init(&dws->master->dev, dws);
|
|
+ return spi_controller_resume(dws->master);
|
|
+}
|
|
+
|
|
+MODULE_AUTHOR("George hu");
|
|
+MODULE_DESCRIPTION("Spacemit enhance spi driver");
|
|
+MODULE_LICENSE("GPL v2");
|
|
diff --git a/drivers/spi/spi-dw-espi.h b/drivers/spi/spi-dw-espi.h
|
|
new file mode 100644
|
|
index 000000000000..111111111111
|
|
--- /dev/null
|
|
+++ b/drivers/spi/spi-dw-espi.h
|
|
@@ -0,0 +1,325 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-only
|
|
+
|
|
+#ifndef DW_ESPI_HEADER_H
|
|
+#define DW_ESPI_HEADER_H
|
|
+
|
|
+#include <linux/bits.h>
|
|
+#include <linux/completion.h>
|
|
+#include <linux/debugfs.h>
|
|
+#include <linux/irqreturn.h>
|
|
+#include <linux/io.h>
|
|
+#include <linux/scatterlist.h>
|
|
+#include <linux/spi/spi-mem.h>
|
|
+#include <linux/bitfield.h>
|
|
+
|
|
+/* Synopsys DW SSI IP-core virtual IDs */
|
|
+#define DW_PSSI_ID 0
|
|
+#define DW_HSSI_ID 1
|
|
+
|
|
+/* Synopsys DW SSI component versions (FourCC sequence) */
|
|
+#define DW_HSSI_102A 0x3130322a
|
|
+
|
|
+/* DW SSI IP-core ID and version check helpers */
|
|
+#define dw_spi_ip_is(_dws, _ip) \
|
|
+ ((_dws)->ip == DW_ ## _ip ## _ID)
|
|
+
|
|
+#define __dw_spi_ver_cmp(_dws, _ip, _ver, _op) \
|
|
+ (dw_spi_ip_is(_dws, _ip) && (_dws)->ver _op DW_ ## _ip ## _ ## _ver)
|
|
+
|
|
+#define dw_spi_ver_is(_dws, _ip, _ver) __dw_spi_ver_cmp(_dws, _ip, _ver, ==)
|
|
+
|
|
+#define dw_spi_ver_is_ge(_dws, _ip, _ver) __dw_spi_ver_cmp(_dws, _ip, _ver, >=)
|
|
+
|
|
+/* DW SPI controller capabilities */
|
|
+#define DW_SPI_CAP_CS_OVERRIDE BIT(0)
|
|
+#define DW_SPI_CAP_DFS32 BIT(1)
|
|
+#define DW_SPI_CAP_EXT_SPI BIT(2)
|
|
+
|
|
+/* Register offsets (Generic for both DWC APB SSI and DWC SSI IP-cores) */
|
|
+#define DW_SPI_CTRLR0 0x00
|
|
+#define DW_SPI_CTRLR1 0x04
|
|
+#define DW_SPI_SSIENR 0x08
|
|
+#define DW_SPI_MWCR 0x0c
|
|
+#define DW_SPI_SER 0x10
|
|
+#define DW_SPI_BAUDR 0x14
|
|
+#define DW_SPI_TXFTLR 0x18
|
|
+#define DW_SPI_RXFTLR 0x1c
|
|
+#define DW_SPI_TXFLR 0x20
|
|
+#define DW_SPI_RXFLR 0x24
|
|
+#define DW_SPI_SR 0x28
|
|
+#define DW_SPI_IMR 0x2c
|
|
+#define DW_SPI_ISR 0x30
|
|
+#define DW_SPI_RISR 0x34
|
|
+#define DW_SPI_TXOICR 0x38
|
|
+#define DW_SPI_RXOICR 0x3c
|
|
+#define DW_SPI_RXUICR 0x40
|
|
+#define DW_SPI_MSTICR 0x44
|
|
+#define DW_SPI_ICR 0x48
|
|
+#define DW_SPI_DMACR 0x4c
|
|
+#define DW_SPI_DMATDLR 0x50
|
|
+#define DW_SPI_DMARDLR 0x54
|
|
+#define DW_SPI_IDR 0x58
|
|
+#define DW_SPI_VERSION 0x5c
|
|
+#define DW_SPI_DR 0x60
|
|
+#define DW_SPI_RX_SAMPLE_DLY 0xf0
|
|
+#define DW_SPI_SPI_CTRLR0 0xf4
|
|
+#define DW_SPI_DDR_DRV_EDGE 0xf8
|
|
+
|
|
+/* Bit fields in CTRLR0 (DWC APB SSI) */
|
|
+#define DW_PSSI_CTRLR0_DFS_MASK GENMASK(3, 0)
|
|
+#define DW_PSSI_CTRLR0_DFS32_MASK GENMASK(20, 16)
|
|
+
|
|
+#define DW_PSSI_CTRLR0_FRF_MASK GENMASK(5, 4)
|
|
+#define DW_SPI_CTRLR0_FRF_MOTO_SPI 0x0
|
|
+#define DW_SPI_CTRLR0_FRF_TI_SSP 0x1
|
|
+#define DW_SPI_CTRLR0_FRF_NS_MICROWIRE 0x2
|
|
+#define DW_SPI_CTRLR0_FRF_RESV 0x3
|
|
+
|
|
+#define DW_PSSI_CTRLR0_MODE_MASK GENMASK(7, 6)
|
|
+#define DW_PSSI_CTRLR0_SCPHA BIT(6)
|
|
+#define DW_PSSI_CTRLR0_SCPOL BIT(7)
|
|
+
|
|
+#define DW_PSSI_CTRLR0_TMOD_MASK GENMASK(9, 8)
|
|
+#define DW_SPI_CTRLR0_TMOD_TR 0x0 /* xmit & recv */
|
|
+#define DW_SPI_CTRLR0_TMOD_TO 0x1 /* xmit only */
|
|
+#define DW_SPI_CTRLR0_TMOD_RO 0x2 /* recv only */
|
|
+#define DW_SPI_CTRLR0_TMOD_EPROMREAD 0x3 /* eeprom read mode */
|
|
+
|
|
+#define DW_PSSI_CTRLR0_SLV_OE BIT(10)
|
|
+#define DW_PSSI_CTRLR0_SRL BIT(11)
|
|
+#define DW_PSSI_CTRLR0_CFS BIT(12)
|
|
+
|
|
+/* Bit fields in CTRLR0 (DWC SSI with AHB interface) */
|
|
+#define DW_HSSI_CTRLR0_DFS_MASK GENMASK(4, 0)
|
|
+#define DW_HSSI_CTRLR0_FRF_MASK GENMASK(7, 6)
|
|
+#define DW_HSSI_CTRLR0_SCPHA BIT(8)
|
|
+#define DW_HSSI_CTRLR0_SCPOL BIT(9)
|
|
+#define DW_HSSI_CTRLR0_TMOD_MASK GENMASK(11, 10)
|
|
+#define DW_HSSI_CTRLR0_SRL BIT(13)
|
|
+#define DW_HSSI_CTRLR0_MST BIT(31)
|
|
+
|
|
+/* Bit fields in CTRLR0 for enhanced SPI */
|
|
+#define DW_HSSI_CTRLR0_SPI_FRF_MASK GENMASK(23, 22)
|
|
+#define DW_SSI_CTRLR0_SPI_FRF_DUAL_SPI 0x1
|
|
+#define DW_SSI_CTRLR0_SPI_FRF_QUAD_SPI 0x2
|
|
+#define DW_SSI_CTRLR0_SPI_FRF_OCT_SPI 0x3
|
|
+
|
|
+/* Bit fields in CTRLR1 */
|
|
+#define DW_SPI_NDF_MASK GENMASK(15, 0)
|
|
+
|
|
+/* Bit fields in SR, 7 bits */
|
|
+#define DW_SPI_SR_MASK GENMASK(6, 0)
|
|
+#define DW_SPI_SR_BUSY BIT(0)
|
|
+#define DW_SPI_SR_TF_NOT_FULL BIT(1)
|
|
+#define DW_SPI_SR_TF_EMPT BIT(2)
|
|
+#define DW_SPI_SR_RF_NOT_EMPT BIT(3)
|
|
+#define DW_SPI_SR_RF_FULL BIT(4)
|
|
+#define DW_SPI_SR_TX_ERR BIT(5)
|
|
+#define DW_SPI_SR_DCOL BIT(6)
|
|
+
|
|
+/* Bit fields in ISR, IMR, RISR, 7 bits */
|
|
+#define DW_SPI_INT_MASK GENMASK(5, 0)
|
|
+#define DW_SPI_INT_TXEI BIT(0)
|
|
+#define DW_SPI_INT_TXOI BIT(1)
|
|
+#define DW_SPI_INT_RXUI BIT(2)
|
|
+#define DW_SPI_INT_RXOI BIT(3)
|
|
+#define DW_SPI_INT_RXFI BIT(4)
|
|
+#define DW_SPI_INT_MSTI BIT(5)
|
|
+
|
|
+/* Bit fields in DMACR */
|
|
+#define DW_SPI_DMACR_RDMAE BIT(0)
|
|
+#define DW_SPI_DMACR_TDMAE BIT(1)
|
|
+
|
|
+/* Bit fields in SPI_CTRLR0 (Defined in DWC SSI >= 1.02a) */
|
|
+#define DW_HSSI_SPI_CTRLR0_CLK_STRETCH_EN BIT(30)
|
|
+#define DW_HSSI_SPI_CTRLR0_WAIT_CYCLE_MASK GENMASK(15, 11)
|
|
+#define DW_HSSI_SPI_CTRLR0_INST_L_MASK GENMASK(9, 8)
|
|
+#define DW_HSSI_SPI_CTRLR0_INST_L8 0x2
|
|
+#define DW_HSSI_SPI_CTRLR0_ADDR_L_MASK GENMASK(5, 2)
|
|
+#define DW_HSSI_SPI_CTRLR0_ADDR_L32 0x8
|
|
+
|
|
+/* Mem/DMA operations helpers */
|
|
+#define DW_SPI_WAIT_RETRIES 5
|
|
+#define DW_SPI_BUF_SIZE \
|
|
+ (sizeof_field(struct spi_mem_op, cmd.opcode) + \
|
|
+ sizeof_field(struct spi_mem_op, addr.val) + 256)
|
|
+#define DW_SPI_GET_BYTE(_val, _idx) \
|
|
+ ((_val) >> (BITS_PER_BYTE * (_idx)) & 0xff)
|
|
+
|
|
+/* Slave spi_transfer/spi_mem_op related */
|
|
+struct dw_spi_cfg {
|
|
+ u8 tmode;
|
|
+ u8 dfs;
|
|
+ u32 ndf;
|
|
+ u32 freq;
|
|
+ u8 spi_frf;
|
|
+};
|
|
+
|
|
+struct dw_spi;
|
|
+struct dw_spi_dma_ops {
|
|
+ int (*dma_init)(struct device *dev, struct dw_spi *dws);
|
|
+ void (*dma_exit)(struct dw_spi *dws);
|
|
+ int (*dma_setup)(struct dw_spi *dws, struct spi_transfer *xfer);
|
|
+ bool (*can_dma)(struct spi_controller *master, struct spi_device *spi,
|
|
+ struct spi_transfer *xfer);
|
|
+ int (*dma_transfer)(struct dw_spi *dws, struct spi_transfer *xfer);
|
|
+ void (*dma_stop)(struct dw_spi *dws);
|
|
+};
|
|
+
|
|
+struct dw_spi {
|
|
+ struct spi_controller *master;
|
|
+
|
|
+ u32 ip; /* Synopsys DW SSI IP-core ID */
|
|
+ u32 ver; /* Synopsys component version */
|
|
+ u32 caps; /* DW SPI capabilities */
|
|
+
|
|
+ void __iomem *regs;
|
|
+ unsigned long paddr;
|
|
+ int irq;
|
|
+ u32 fifo_len; /* depth of the FIFO buffer */
|
|
+ unsigned int dfs_offset; /* CTRLR0 DFS field offset */
|
|
+ u32 max_mem_freq; /* max mem-ops bus freq */
|
|
+ u32 max_freq; /* max bus freq supported */
|
|
+
|
|
+ u32 reg_io_width; /* DR I/O width in bytes */
|
|
+ u16 bus_num;
|
|
+ u16 num_cs; /* supported slave numbers */
|
|
+ void (*set_cs)(struct spi_device *spi, bool enable);
|
|
+
|
|
+ /* Current message transfer state info */
|
|
+ void *tx;
|
|
+ unsigned int tx_len;
|
|
+ void *rx;
|
|
+ unsigned int rx_len;
|
|
+ u8 buf[DW_SPI_BUF_SIZE];
|
|
+ int dma_mapped;
|
|
+ u8 n_bytes; /* current is a 1/2 bytes op */
|
|
+ irqreturn_t (*transfer_handler)(struct dw_spi *dws);
|
|
+ u32 current_freq; /* frequency in hz */
|
|
+ u32 cur_rx_sample_dly;
|
|
+ u32 def_rx_sample_dly_ns;
|
|
+
|
|
+ /* Custom memory operations */
|
|
+ struct spi_controller_mem_ops mem_ops;
|
|
+
|
|
+ /* DMA info */
|
|
+ struct dma_chan *txchan;
|
|
+ u32 txburst;
|
|
+ struct dma_chan *rxchan;
|
|
+ u32 rxburst;
|
|
+ u32 dma_sg_burst;
|
|
+ unsigned long dma_chan_busy;
|
|
+ dma_addr_t dma_addr; /* phy address of the Data register */
|
|
+ const struct dw_spi_dma_ops *dma_ops;
|
|
+ struct completion dma_completion;
|
|
+
|
|
+#ifdef CONFIG_DEBUG_FS
|
|
+ struct dentry *debugfs;
|
|
+ struct debugfs_regset32 regset;
|
|
+#endif
|
|
+};
|
|
+
|
|
+static inline u32 dw_readl(struct dw_spi *dws, u32 offset)
|
|
+{
|
|
+ return __raw_readl(dws->regs + offset);
|
|
+}
|
|
+
|
|
+static inline void dw_writel(struct dw_spi *dws, u32 offset, u32 val)
|
|
+{
|
|
+ __raw_writel(val, dws->regs + offset);
|
|
+}
|
|
+
|
|
+static inline u32 dw_read_io_reg(struct dw_spi *dws, u32 offset)
|
|
+{
|
|
+ switch (dws->reg_io_width) {
|
|
+ case 2:
|
|
+ return readw_relaxed(dws->regs + offset);
|
|
+ case 4:
|
|
+ default:
|
|
+ return readl_relaxed(dws->regs + offset);
|
|
+ }
|
|
+}
|
|
+
|
|
+static inline void dw_write_io_reg(struct dw_spi *dws, u32 offset, u32 val)
|
|
+{
|
|
+ switch (dws->reg_io_width) {
|
|
+ case 2:
|
|
+ writew_relaxed(val, dws->regs + offset);
|
|
+ break;
|
|
+ case 4:
|
|
+ default:
|
|
+ writel_relaxed(val, dws->regs + offset);
|
|
+ break;
|
|
+ }
|
|
+}
|
|
+
|
|
+static inline void dw_spi_enable_chip(struct dw_spi *dws, int enable)
|
|
+{
|
|
+ dw_writel(dws, DW_SPI_SSIENR, (enable ? 1 : 0));
|
|
+}
|
|
+
|
|
+static inline void dw_spi_set_clk(struct dw_spi *dws, u16 div)
|
|
+{
|
|
+ dw_writel(dws, DW_SPI_BAUDR, div);
|
|
+}
|
|
+
|
|
+/* Disable IRQ bits */
|
|
+static inline void dw_spi_mask_intr(struct dw_spi *dws, u32 mask)
|
|
+{
|
|
+ u32 new_mask;
|
|
+
|
|
+ new_mask = dw_readl(dws, DW_SPI_IMR) & ~mask;
|
|
+ dw_writel(dws, DW_SPI_IMR, new_mask);
|
|
+}
|
|
+
|
|
+/* Enable IRQ bits */
|
|
+static inline void dw_spi_umask_intr(struct dw_spi *dws, u32 mask)
|
|
+{
|
|
+ u32 new_mask;
|
|
+
|
|
+ new_mask = dw_readl(dws, DW_SPI_IMR) | mask;
|
|
+ dw_writel(dws, DW_SPI_IMR, new_mask);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * This disables the SPI controller, interrupts, clears the interrupts status
|
|
+ * and CS, then re-enables the controller back. Transmit and receive FIFO
|
|
+ * buffers are cleared when the device is disabled.
|
|
+ */
|
|
+static inline void dw_spi_reset_chip(struct dw_spi *dws)
|
|
+{
|
|
+ dw_spi_enable_chip(dws, 0);
|
|
+ dw_spi_mask_intr(dws, 0xff);
|
|
+ dw_readl(dws, DW_SPI_ICR);
|
|
+ dw_writel(dws, DW_SPI_SER, 0);
|
|
+ dw_spi_enable_chip(dws, 1);
|
|
+}
|
|
+
|
|
+static inline void dw_spi_shutdown_chip(struct dw_spi *dws)
|
|
+{
|
|
+ dw_spi_enable_chip(dws, 0);
|
|
+ dw_spi_set_clk(dws, 0);
|
|
+}
|
|
+
|
|
+extern void dw_spi_ext_set_cs(struct spi_device *spi, bool enable);
|
|
+extern void dw_spi_ext_update_config(struct dw_spi *dws, struct spi_device *spi,
|
|
+ struct dw_spi_cfg *cfg);
|
|
+extern int dw_spi_ext_check_status(struct dw_spi *dws, bool raw);
|
|
+extern int dw_spi_ext_add_host(struct device *dev, struct dw_spi *dws);
|
|
+extern void dw_spi_ext_remove_host(struct dw_spi *dws);
|
|
+extern int dw_spi_ext_suspend_host(struct dw_spi *dws);
|
|
+extern int dw_spi_ext_resume_host(struct dw_spi *dws);
|
|
+
|
|
+#ifdef CONFIG_SPI_DW_DMA
|
|
+
|
|
+extern void dw_spi_dma_setup_mfld(struct dw_spi *dws);
|
|
+extern void dw_spi_dma_setup_generic(struct dw_spi *dws);
|
|
+
|
|
+#else
|
|
+
|
|
+static inline void dw_spi_dma_setup_mfld(struct dw_spi *dws) {}
|
|
+static inline void dw_spi_dma_setup_generic(struct dw_spi *dws) {}
|
|
+
|
|
+#endif /* !CONFIG_SPI_DW_DMA */
|
|
+
|
|
+#endif /* DW_ESPI_HEADER_H */
|
|
diff --git a/drivers/spi/spi-dw-mmio-ext.c b/drivers/spi/spi-dw-mmio-ext.c
|
|
new file mode 100644
|
|
index 000000000000..111111111111
|
|
--- /dev/null
|
|
+++ b/drivers/spi/spi-dw-mmio-ext.c
|
|
@@ -0,0 +1,171 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-only
|
|
+/*
|
|
+ * Memory-mapped interface driver for DW SPI Core
|
|
+ *
|
|
+ * Copyright (c) 2023, spacemit.
|
|
+ */
|
|
+
|
|
+#include <linux/clk.h>
|
|
+#include <linux/err.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/pm_runtime.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/spi/spi.h>
|
|
+#include <linux/scatterlist.h>
|
|
+#include <linux/mfd/syscon.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/of.h>
|
|
+#include <linux/of_platform.h>
|
|
+#include <linux/acpi.h>
|
|
+#include <linux/property.h>
|
|
+#include <linux/regmap.h>
|
|
+#include <linux/reset.h>
|
|
+#include "spi-dw-espi.h"
|
|
+
|
|
+#define DRIVER_NAME "dw_spi_mmio_ext"
|
|
+
|
|
+struct dw_spi_mmio_ext {
|
|
+ struct dw_spi dws;
|
|
+ struct clk *clk;
|
|
+ struct clk *pclk;
|
|
+ void *priv;
|
|
+ struct reset_control *rstc;
|
|
+};
|
|
+
|
|
+static int dw_spi_hssi_ext_init(struct platform_device *pdev,
|
|
+ struct dw_spi_mmio_ext *dwsmmio)
|
|
+{
|
|
+ dwsmmio->dws.ip = DW_HSSI_ID;
|
|
+ dwsmmio->dws.caps = DW_SPI_CAP_EXT_SPI;
|
|
+
|
|
+ dw_spi_dma_setup_generic(&dwsmmio->dws);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dw_spi_mmio_ext_probe(struct platform_device *pdev)
|
|
+{
|
|
+ int (*init_func)(struct platform_device *pdev,
|
|
+ struct dw_spi_mmio_ext *dwsmmio);
|
|
+ struct dw_spi_mmio_ext *dwsmmio;
|
|
+ struct resource *mem;
|
|
+ struct dw_spi *dws;
|
|
+ int ret;
|
|
+ int num_cs;
|
|
+
|
|
+ dwsmmio = devm_kzalloc(&pdev->dev, sizeof(struct dw_spi_mmio_ext),
|
|
+ GFP_KERNEL);
|
|
+ if (!dwsmmio)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ dws = &dwsmmio->dws;
|
|
+
|
|
+ /* Get basic io resource and map it */
|
|
+ dws->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &mem);
|
|
+ if (IS_ERR(dws->regs))
|
|
+ return PTR_ERR(dws->regs);
|
|
+
|
|
+ dws->paddr = mem->start;
|
|
+
|
|
+ dws->irq = platform_get_irq(pdev, 0);
|
|
+ if (dws->irq < 0)
|
|
+ return dws->irq; /* -ENXIO */
|
|
+
|
|
+ dwsmmio->clk = devm_clk_get(&pdev->dev, NULL);
|
|
+ if (IS_ERR(dwsmmio->clk))
|
|
+ return PTR_ERR(dwsmmio->clk);
|
|
+ ret = clk_prepare_enable(dwsmmio->clk);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /* Optional clock needed to access the registers */
|
|
+ dwsmmio->pclk = devm_clk_get_optional(&pdev->dev, "pclk");
|
|
+ if (IS_ERR(dwsmmio->pclk)) {
|
|
+ ret = PTR_ERR(dwsmmio->pclk);
|
|
+ goto out_clk;
|
|
+ }
|
|
+ ret = clk_prepare_enable(dwsmmio->pclk);
|
|
+ if (ret)
|
|
+ goto out_clk;
|
|
+
|
|
+ /* find an optional reset controller */
|
|
+ dwsmmio->rstc = devm_reset_control_get_optional_exclusive(&pdev->dev, NULL);
|
|
+ if (IS_ERR(dwsmmio->rstc)) {
|
|
+ ret = PTR_ERR(dwsmmio->rstc);
|
|
+ dev_err(&pdev->dev, "failed to get reset.\n");
|
|
+ goto out_clk;
|
|
+ }
|
|
+ reset_control_deassert(dwsmmio->rstc);
|
|
+
|
|
+ /* set bus number */
|
|
+ dws->bus_num = pdev->id;
|
|
+
|
|
+ /* get supported freq in max */
|
|
+ dws->max_freq = clk_get_rate(dwsmmio->clk);
|
|
+
|
|
+ /* get reg width of controler */
|
|
+ device_property_read_u32(&pdev->dev, "reg-io-width", &dws->reg_io_width);
|
|
+
|
|
+ /* get chip select count of controler */
|
|
+ num_cs = 4;
|
|
+ device_property_read_u32(&pdev->dev, "num-cs", &num_cs);
|
|
+ dws->num_cs = num_cs;
|
|
+ init_func = device_get_match_data(&pdev->dev);
|
|
+ if (init_func) {
|
|
+ ret = init_func(pdev, dwsmmio);
|
|
+ if (ret)
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ pm_runtime_enable(&pdev->dev);
|
|
+
|
|
+ ret = dw_spi_ext_add_host(&pdev->dev, dws);
|
|
+ if (ret)
|
|
+ goto out;
|
|
+
|
|
+ platform_set_drvdata(pdev, dwsmmio);
|
|
+ dev_info(&pdev->dev, "dw_spi_ext_probe success.\n");
|
|
+ return 0;
|
|
+
|
|
+out:
|
|
+ pm_runtime_disable(&pdev->dev);
|
|
+ clk_disable_unprepare(dwsmmio->pclk);
|
|
+out_clk:
|
|
+ clk_disable_unprepare(dwsmmio->clk);
|
|
+ reset_control_assert(dwsmmio->rstc);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int dw_spi_mmio_ext_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct dw_spi_mmio_ext *dwsmmio = platform_get_drvdata(pdev);
|
|
+
|
|
+ dw_spi_ext_remove_host(&dwsmmio->dws);
|
|
+ pm_runtime_disable(&pdev->dev);
|
|
+ clk_disable_unprepare(dwsmmio->pclk);
|
|
+ clk_disable_unprepare(dwsmmio->clk);
|
|
+ reset_control_assert(dwsmmio->rstc);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct of_device_id dw_spi_mmio_ext_of_match[] = {
|
|
+ { .compatible = "snps,dwc-ssi-1.02a", .data = dw_spi_hssi_ext_init},
|
|
+ { /* end of table */}
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, dw_spi_mmio_ext_of_match);
|
|
+
|
|
+static struct platform_driver dw_spi_mmio_ext_driver = {
|
|
+ .probe = dw_spi_mmio_ext_probe,
|
|
+ .remove = dw_spi_mmio_ext_remove,
|
|
+ .driver = {
|
|
+ .name = DRIVER_NAME,
|
|
+ .of_match_table = dw_spi_mmio_ext_of_match,
|
|
+ },
|
|
+};
|
|
+module_platform_driver(dw_spi_mmio_ext_driver);
|
|
+
|
|
+MODULE_AUTHOR("George hu");
|
|
+MODULE_DESCRIPTION("Spacemit qspi driver");
|
|
+MODULE_LICENSE("GPL v2");
|
|
diff --git a/drivers/spi/spi-k1x-dma.c b/drivers/spi/spi-k1x-dma.c
|
|
new file mode 100644
|
|
index 000000000000..111111111111
|
|
--- /dev/null
|
|
+++ b/drivers/spi/spi-k1x-dma.c
|
|
@@ -0,0 +1,375 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Support for Spacemit k1x spi controller dma mode
|
|
+ *
|
|
+ * Copyright (c) 2023, spacemit Corporation.
|
|
+ *
|
|
+ */
|
|
+
|
|
+#include <linux/device.h>
|
|
+#include <linux/dma-mapping.h>
|
|
+#include <linux/dmaengine.h>
|
|
+#include <linux/scatterlist.h>
|
|
+#include <linux/sizes.h>
|
|
+#include <linux/spi/spi.h>
|
|
+#include <linux/delay.h>
|
|
+
|
|
+#include "spi-k1x.h"
|
|
+
|
|
+static int k1x_spi_map_dma_buffer(struct spi_driver_data *drv_data,
|
|
+ enum dma_data_direction dir)
|
|
+{
|
|
+ int i, nents, len = drv_data->len;
|
|
+ struct scatterlist *sg;
|
|
+ struct device *dmadev;
|
|
+ struct sg_table *sgt;
|
|
+ void *buf, *pbuf;
|
|
+
|
|
+ if (dir == DMA_TO_DEVICE) {
|
|
+ dmadev = drv_data->tx_chan->device->dev;
|
|
+ sgt = &drv_data->tx_sgt;
|
|
+ buf = drv_data->tx;
|
|
+ drv_data->tx_map_len = len;
|
|
+ } else {
|
|
+ dmadev = drv_data->rx_chan->device->dev;
|
|
+ sgt = &drv_data->rx_sgt;
|
|
+ buf = drv_data->rx;
|
|
+ drv_data->rx_map_len = len;
|
|
+ }
|
|
+
|
|
+ nents = DIV_ROUND_UP(len, SZ_2K);
|
|
+ if (nents != sgt->nents) {
|
|
+ int ret;
|
|
+
|
|
+ sg_free_table(sgt);
|
|
+ ret = sg_alloc_table(sgt, nents, GFP_ATOMIC);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ pbuf = buf;
|
|
+ for_each_sg(sgt->sgl, sg, sgt->nents, i) {
|
|
+ size_t bytes = min_t(size_t, len, SZ_2K);
|
|
+
|
|
+ if (buf)
|
|
+ sg_set_buf(sg, pbuf, bytes);
|
|
+ else
|
|
+ sg_set_buf(sg, drv_data->dummy, bytes);
|
|
+
|
|
+ pbuf += bytes;
|
|
+ len -= bytes;
|
|
+ }
|
|
+
|
|
+ nents = dma_map_sg(dmadev, sgt->sgl, sgt->nents, dir);
|
|
+ if (!nents)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ return nents;
|
|
+}
|
|
+
|
|
+static void k1x_spi_unmap_dma_buffer(struct spi_driver_data *drv_data,
|
|
+ enum dma_data_direction dir)
|
|
+{
|
|
+ struct device *dmadev;
|
|
+ struct sg_table *sgt;
|
|
+
|
|
+ if (dir == DMA_TO_DEVICE) {
|
|
+ dmadev = drv_data->tx_chan->device->dev;
|
|
+ sgt = &drv_data->tx_sgt;
|
|
+ } else {
|
|
+ dmadev = drv_data->rx_chan->device->dev;
|
|
+ sgt = &drv_data->rx_sgt;
|
|
+ }
|
|
+
|
|
+ dma_unmap_sg(dmadev, sgt->sgl, sgt->nents, dir);
|
|
+}
|
|
+
|
|
+static void k1x_spi_unmap_dma_buffers(struct spi_driver_data *drv_data)
|
|
+{
|
|
+ if (!drv_data->dma_mapped)
|
|
+ return;
|
|
+
|
|
+ k1x_spi_unmap_dma_buffer(drv_data, DMA_FROM_DEVICE);
|
|
+ k1x_spi_unmap_dma_buffer(drv_data, DMA_TO_DEVICE);
|
|
+
|
|
+ drv_data->dma_mapped = 0;
|
|
+}
|
|
+
|
|
+static void k1x_spi_dma_transfer_complete(struct spi_driver_data *drv_data,
|
|
+ bool error)
|
|
+{
|
|
+ struct spi_message *msg = drv_data->cur_msg;
|
|
+
|
|
+ /*
|
|
+ * It is possible that one CPU is handling ROR interrupt and other
|
|
+ * just gets DMA completion. Calling pump_transfers() twice for the
|
|
+ * same transfer leads to problems thus we prevent concurrent calls
|
|
+ * by using ->dma_running.
|
|
+ */
|
|
+ if (atomic_dec_and_test(&drv_data->dma_running)) {
|
|
+ /*
|
|
+ * If the other CPU is still handling the ROR interrupt we
|
|
+ * might not know about the error yet. So we re-check the
|
|
+ * ROR bit here before we clear the status register.
|
|
+ */
|
|
+ if (!error) {
|
|
+ u32 status = k1x_spi_read(drv_data, STATUS)
|
|
+ & drv_data->mask_sr;
|
|
+ error = status & STATUS_ROR;
|
|
+ }
|
|
+
|
|
+ /* Clear status & disable interrupts */
|
|
+ k1x_spi_write(drv_data, FIFO_CTRL,
|
|
+ k1x_spi_read(drv_data, FIFO_CTRL)
|
|
+ & ~drv_data->dma_fifo_ctrl);
|
|
+ k1x_spi_write(drv_data, TOP_CTRL,
|
|
+ k1x_spi_read(drv_data, TOP_CTRL)
|
|
+ & ~drv_data->dma_top_ctrl);
|
|
+ k1x_spi_write(drv_data, STATUS, drv_data->clear_sr);
|
|
+ k1x_spi_write(drv_data, TO, 0);
|
|
+
|
|
+ if (!error) {
|
|
+ k1x_spi_unmap_dma_buffers(drv_data);
|
|
+
|
|
+ drv_data->tx += drv_data->tx_map_len;
|
|
+ drv_data->rx += drv_data->rx_map_len;
|
|
+
|
|
+ msg->actual_length += drv_data->len;
|
|
+ msg->state = k1x_spi_next_transfer(drv_data);
|
|
+ } else {
|
|
+ /* In case we got an error we disable the SSP now */
|
|
+ k1x_spi_write(drv_data, TOP_CTRL,
|
|
+ k1x_spi_read(drv_data, TOP_CTRL)
|
|
+ & ~TOP_SSE);
|
|
+
|
|
+ msg->state = ERROR_STATE;
|
|
+ }
|
|
+ queue_work(system_wq, &drv_data->pump_transfers);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void k1x_spi_dma_callback(void *data)
|
|
+{
|
|
+ k1x_spi_dma_transfer_complete(data, false);
|
|
+}
|
|
+
|
|
+static struct dma_async_tx_descriptor *
|
|
+k1x_spi_dma_prepare_one(struct spi_driver_data *drv_data,
|
|
+ enum dma_transfer_direction dir)
|
|
+{
|
|
+ struct chip_data *chip = drv_data->cur_chip;
|
|
+ enum dma_slave_buswidth width;
|
|
+ struct dma_slave_config cfg;
|
|
+ struct dma_chan *chan;
|
|
+ struct sg_table *sgt;
|
|
+ int nents, ret;
|
|
+
|
|
+ switch (drv_data->n_bytes) {
|
|
+ case 1:
|
|
+ width = DMA_SLAVE_BUSWIDTH_1_BYTE;
|
|
+ break;
|
|
+ case 2:
|
|
+ width = DMA_SLAVE_BUSWIDTH_2_BYTES;
|
|
+ break;
|
|
+ default:
|
|
+ width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ memset(&cfg, 0, sizeof(cfg));
|
|
+ cfg.direction = dir;
|
|
+
|
|
+ if (dir == DMA_MEM_TO_DEV) {
|
|
+ cfg.dst_addr = drv_data->ssdr_physical;
|
|
+ cfg.dst_addr_width = width;
|
|
+ cfg.dst_maxburst = chip->dma_burst_size;
|
|
+
|
|
+ sgt = &drv_data->tx_sgt;
|
|
+ nents = drv_data->tx_nents;
|
|
+ chan = drv_data->tx_chan;
|
|
+ } else {
|
|
+ cfg.src_addr = drv_data->ssdr_physical;
|
|
+ cfg.src_addr_width = width;
|
|
+ cfg.src_maxburst = chip->dma_burst_size;
|
|
+
|
|
+ sgt = &drv_data->rx_sgt;
|
|
+ nents = drv_data->rx_nents;
|
|
+ chan = drv_data->rx_chan;
|
|
+ }
|
|
+
|
|
+ ret = dmaengine_slave_config(chan, &cfg);
|
|
+ if (ret) {
|
|
+ dev_warn(&drv_data->pdev->dev, "DMA slave config failed\n");
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ return dmaengine_prep_slave_sg(chan, sgt->sgl, nents, dir,
|
|
+ DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
|
+}
|
|
+
|
|
+bool k1x_spi_dma_is_possible(size_t len)
|
|
+{
|
|
+ return len <= MAX_DMA_LEN;
|
|
+}
|
|
+
|
|
+int k1x_spi_map_dma_buffers(struct spi_driver_data *drv_data)
|
|
+{
|
|
+ const struct chip_data *chip = drv_data->cur_chip;
|
|
+ int ret;
|
|
+
|
|
+ if (!chip->enable_dma)
|
|
+ return 0;
|
|
+
|
|
+ /* Don't bother with DMA if we can't do even a single burst */
|
|
+ if (drv_data->len < chip->dma_burst_size)
|
|
+ return 0;
|
|
+
|
|
+ ret = k1x_spi_map_dma_buffer(drv_data, DMA_TO_DEVICE);
|
|
+ if (ret <= 0) {
|
|
+ dev_warn(&drv_data->pdev->dev, "failed to DMA map TX\n");
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ drv_data->tx_nents = ret;
|
|
+
|
|
+ ret = k1x_spi_map_dma_buffer(drv_data, DMA_FROM_DEVICE);
|
|
+ if (ret <= 0) {
|
|
+ k1x_spi_unmap_dma_buffer(drv_data, DMA_TO_DEVICE);
|
|
+ dev_warn(&drv_data->pdev->dev, "failed to DMA map RX\n");
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ drv_data->rx_nents = ret;
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+irqreturn_t k1x_spi_dma_transfer(struct spi_driver_data *drv_data)
|
|
+{
|
|
+ u32 status;
|
|
+
|
|
+ status = k1x_spi_read(drv_data, STATUS) & drv_data->mask_sr;
|
|
+
|
|
+ if (((drv_data->slave_mode) && (status & STATUS_TINT)) ||
|
|
+ (status & STATUS_ROR)) {
|
|
+ dmaengine_terminate_all(drv_data->rx_chan);
|
|
+ dmaengine_terminate_all(drv_data->tx_chan);
|
|
+ k1x_spi_dma_transfer_complete(drv_data, true);
|
|
+ return IRQ_HANDLED;
|
|
+ }
|
|
+
|
|
+ return IRQ_NONE;
|
|
+}
|
|
+
|
|
+void k1x_spi_slave_sw_timeout_callback(struct spi_driver_data *drv_data)
|
|
+{
|
|
+ dmaengine_terminate_all(drv_data->rx_chan);
|
|
+ dmaengine_terminate_all(drv_data->tx_chan);
|
|
+ k1x_spi_dma_transfer_complete(drv_data, true);
|
|
+}
|
|
+
|
|
+int k1x_spi_dma_prepare(struct spi_driver_data *drv_data, u32 dma_burst)
|
|
+{
|
|
+ struct dma_async_tx_descriptor *tx_desc, *rx_desc;
|
|
+
|
|
+ tx_desc = k1x_spi_dma_prepare_one(drv_data, DMA_MEM_TO_DEV);
|
|
+ if (!tx_desc) {
|
|
+ dev_err(&drv_data->pdev->dev,
|
|
+ "failed to get DMA TX descriptor\n");
|
|
+ return -EBUSY;
|
|
+ }
|
|
+
|
|
+ rx_desc = k1x_spi_dma_prepare_one(drv_data, DMA_DEV_TO_MEM);
|
|
+ if (!rx_desc) {
|
|
+ dev_err(&drv_data->pdev->dev,
|
|
+ "failed to get DMA RX descriptor\n");
|
|
+ return -EBUSY;
|
|
+ }
|
|
+
|
|
+ /* We are ready when RX completes */
|
|
+ rx_desc->callback = k1x_spi_dma_callback;
|
|
+ rx_desc->callback_param = drv_data;
|
|
+
|
|
+ dmaengine_submit(rx_desc);
|
|
+ dmaengine_submit(tx_desc);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void k1x_spi_dma_start(struct spi_driver_data *drv_data)
|
|
+{
|
|
+ dma_async_issue_pending(drv_data->rx_chan);
|
|
+ dma_async_issue_pending(drv_data->tx_chan);
|
|
+
|
|
+ atomic_set(&drv_data->dma_running, 1);
|
|
+}
|
|
+
|
|
+int k1x_spi_dma_setup(struct spi_driver_data *drv_data)
|
|
+{
|
|
+ struct k1x_spi_master *pdata = drv_data->master_info;
|
|
+ struct device *dev = &drv_data->pdev->dev;
|
|
+ dma_cap_mask_t mask;
|
|
+
|
|
+ dma_cap_zero(mask);
|
|
+ dma_cap_set(DMA_SLAVE, mask);
|
|
+
|
|
+ drv_data->dummy = devm_kzalloc(dev, SZ_2K, GFP_KERNEL);
|
|
+ if (!drv_data->dummy)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ drv_data->tx_chan = dma_request_slave_channel_compat(mask,
|
|
+ pdata->dma_filter, pdata->tx_param, dev, "tx");
|
|
+ if (!drv_data->tx_chan)
|
|
+ return -ENODEV;
|
|
+
|
|
+ drv_data->rx_chan = dma_request_slave_channel_compat(mask,
|
|
+ pdata->dma_filter, pdata->rx_param, dev, "rx");
|
|
+ if (!drv_data->rx_chan) {
|
|
+ dma_release_channel(drv_data->tx_chan);
|
|
+ drv_data->tx_chan = NULL;
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void k1x_spi_dma_release(struct spi_driver_data *drv_data)
|
|
+{
|
|
+ if (drv_data->rx_chan) {
|
|
+ dmaengine_terminate_all(drv_data->rx_chan);
|
|
+ dma_release_channel(drv_data->rx_chan);
|
|
+ sg_free_table(&drv_data->rx_sgt);
|
|
+ drv_data->rx_chan = NULL;
|
|
+ }
|
|
+ if (drv_data->tx_chan) {
|
|
+ dmaengine_terminate_all(drv_data->tx_chan);
|
|
+ dma_release_channel(drv_data->tx_chan);
|
|
+ sg_free_table(&drv_data->tx_sgt);
|
|
+ drv_data->tx_chan = NULL;
|
|
+ }
|
|
+}
|
|
+
|
|
+int k1x_spi_set_dma_burst_and_threshold(struct chip_data *chip,
|
|
+ struct spi_device *spi,
|
|
+ u8 bits_per_word, u32 *burst_code,
|
|
+ u32 *threshold)
|
|
+{
|
|
+ /*
|
|
+ * If the DMA burst size is given in chip_info we use
|
|
+ * that, otherwise we set it to half of FIFO size; SPI
|
|
+ * FIFO has 16 entry, so FIFO size = 16*bits_per_word/8;
|
|
+ * Also we use the default FIFO thresholds for now.
|
|
+ */
|
|
+ if (chip && chip->dma_burst_size)
|
|
+ *burst_code = chip->dma_burst_size;
|
|
+ else if (bits_per_word <= 8) {
|
|
+ *burst_code = 8;
|
|
+ }
|
|
+ else if (bits_per_word <= 16)
|
|
+ *burst_code = 16;
|
|
+ else
|
|
+ *burst_code = 32;
|
|
+
|
|
+ *threshold = FIFO_RxTresh(RX_THRESH_DFLT)
|
|
+ | FIFO_TxTresh(TX_THRESH_DFLT);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
diff --git a/drivers/spi/spi-k1x-qspi.c b/drivers/spi/spi-k1x-qspi.c
|
|
new file mode 100644
|
|
index 000000000000..111111111111
|
|
--- /dev/null
|
|
+++ b/drivers/spi/spi-k1x-qspi.c
|
|
@@ -0,0 +1,1722 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-only
|
|
+/*
|
|
+ * Spacemit k1x qspi controller driver
|
|
+ *
|
|
+ * Copyright (c) 2023, spacemit Corporation.
|
|
+ *
|
|
+ */
|
|
+
|
|
+#include <linux/bitops.h>
|
|
+#include <linux/clk.h>
|
|
+#include <linux/completion.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/dmaengine.h>
|
|
+#include <linux/dma-mapping.h>
|
|
+#include <linux/err.h>
|
|
+#include <linux/errno.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/io.h>
|
|
+#include <linux/iopoll.h>
|
|
+#include <linux/jiffies.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/mutex.h>
|
|
+#include <linux/of.h>
|
|
+#include <linux/of_device.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/pm_qos.h>
|
|
+#include <linux/pm_runtime.h>
|
|
+#include <linux/sizes.h>
|
|
+#include <linux/spi/spi.h>
|
|
+#include <linux/spi/spi-mem.h>
|
|
+#include <linux/reset.h>
|
|
+
|
|
+//#define K1X_DUMP_QSPI_REG
|
|
+
|
|
+#define QSPI_WAIT_TIMEOUT (300) /* ms */
|
|
+#define QSPI_AUTOSUSPEND_TIMEOUT 2000
|
|
+#define K1X_MPMU_ACGR 0xd4051024
|
|
+
|
|
+/* QSPI PMUap register */
|
|
+#define PMUA_QSPI_CLK_RES_CTRL 0xd4282860
|
|
+#define QSPI_CLK_SEL(x) ((x) << 6)
|
|
+#define QSPI_CLK_SEL_MASK GENMASK(8, 6)
|
|
+#define QSPI_CLK_EN BIT(4)
|
|
+#define QSPI_BUS_CLK_EN BIT(3)
|
|
+#define QSPI_CLK_RST BIT(1)
|
|
+#define QSPI_BUS_RST BIT(0)
|
|
+
|
|
+/* QSPI memory base */
|
|
+#define QSPI_AMBA_BASE 0xb8000000
|
|
+#define QSPI_FLASH_A1_BASE QSPI_AMBA_BASE
|
|
+#define QSPI_FLASH_A1_TOP (QSPI_FLASH_A1_BASE + 0x4000000)
|
|
+#define QSPI_FLASH_A2_BASE QSPI_FLASH_A1_TOP
|
|
+#define QSPI_FLASH_A2_TOP (QSPI_FLASH_A2_BASE + 0x100000)
|
|
+#define QSPI_FLASH_B1_BASE QSPI_FLASH_A2_TOP
|
|
+#define QSPI_FLASH_B1_TOP (QSPI_FLASH_B1_BASE + 0x100000)
|
|
+#define QSPI_FLASH_B2_BASE QSPI_FLASH_B1_TOP
|
|
+#define QSPI_FLASH_B2_TOP (QSPI_FLASH_B2_BASE + 0x100000)
|
|
+
|
|
+/* TX/RX/ABH buffer max */
|
|
+#define QSPI_RX_BUFF_MAX SZ_128
|
|
+#define QSPI_TX_BUFF_MAX SZ_256
|
|
+#define QSPI_TX_BUFF_POP_MIN 16
|
|
+#define QSPI_AHB_BUFF_MAX_SIZE SZ_512
|
|
+#define QSPI_TX_DMA_BURST SZ_16
|
|
+
|
|
+#define QSPI_WAIT_BIT_CLEAR 0
|
|
+#define QSPI_WAIT_BIT_SET 1
|
|
+
|
|
+/* QSPI Host Registers used by the driver */
|
|
+#define QSPI_MCR 0x00
|
|
+#define QSPI_MCR_ISD_MASK GENMASK(19, 16)
|
|
+#define QSPI_MCR_MDIS_MASK BIT(14)
|
|
+#define QSPI_MCR_CLR_TXF_MASK BIT(11)
|
|
+#define QSPI_MCR_CLR_RXF_MASK BIT(10)
|
|
+#define QSPI_MCR_DDR_EN_MASK BIT(7)
|
|
+#define QSPI_MCR_END_CFG_MASK GENMASK(3, 2)
|
|
+#define QSPI_MCR_SWRSTHD_MASK BIT(1)
|
|
+#define QSPI_MCR_SWRSTSD_MASK BIT(0)
|
|
+
|
|
+#define QSPI_TCR 0x04
|
|
+#define QSPI_IPCR 0x08
|
|
+#define QSPI_IPCR_SEQID(x) ((x) << 24)
|
|
+
|
|
+#define QSPI_FLSHCR 0x0c
|
|
+
|
|
+#define QSPI_BUF0CR 0x10
|
|
+#define QSPI_BUF1CR 0x14
|
|
+#define QSPI_BUF2CR 0x18
|
|
+#define QSPI_BUF3CR 0x1c
|
|
+#define QSPI_BUF3CR_ALLMST_MASK BIT(31)
|
|
+#define QSPI_BUF3CR_ADATSZ(x) ((x) << 8)
|
|
+#define QSPI_BUF3CR_ADATSZ_MASK GENMASK(15, 8)
|
|
+
|
|
+#define QSPI_BFGENCR 0x20
|
|
+#define QSPI_BFGENCR_SEQID(x) ((x) << 12)
|
|
+
|
|
+#define QSPI_SOCCR 0x24
|
|
+
|
|
+#define QSPI_BUF0IND 0x30
|
|
+#define QSPI_BUF1IND 0x34
|
|
+#define QSPI_BUF2IND 0x38
|
|
+
|
|
+#define QSPI_SFAR 0x100
|
|
+#define QSPI_SFACR 0x104
|
|
+
|
|
+#define QSPI_SMPR 0x108
|
|
+#define QSPI_SMPR_DDRSMP_MASK GENMASK(18, 16)
|
|
+#define QSPI_SMPR_FSDLY_MASK BIT(6)
|
|
+#define QSPI_SMPR_FSPHS_MASK BIT(5)
|
|
+#define QSPI_SMPR_FSPHS_CLK (416000000)
|
|
+#define QSPI_SMPR_HSENA_MASK BIT(0)
|
|
+
|
|
+#define QSPI_RBSR 0x10c
|
|
+
|
|
+#define QSPI_RBCT 0x110
|
|
+#define QSPI_RBCT_WMRK_MASK GENMASK(4, 0)
|
|
+#define QSPI_RBCT_RXBRD_MASK BIT(8)
|
|
+
|
|
+#define QSPI_TBSR 0x150
|
|
+#define QSPI_TBDR 0x154
|
|
+#define QSPI_TBCT 0x158
|
|
+#define QSPI_TX_WMRK (QSPI_TX_DMA_BURST / 4 - 1)
|
|
+
|
|
+#define QSPI_SR 0x15c
|
|
+#define QSPI_SR_BUSY BIT(0)
|
|
+#define QSPI_SR_IP_ACC_MASK BIT(1)
|
|
+#define QSPI_SR_AHB_ACC_MASK BIT(2)
|
|
+#define QSPI_SR_TXFULL BIT(27)
|
|
+
|
|
+#define QSPI_FR 0x160
|
|
+#define QSPI_FR_TFF_MASK BIT(0)
|
|
+#define QSPI_FR_IPGEF BIT(4)
|
|
+#define QSPI_FR_IPIEF BIT(6)
|
|
+#define QSPI_FR_IPAEF BIT(7)
|
|
+#define QSPI_FR_IUEF BIT(11)
|
|
+#define QSPI_FR_ABOF BIT(12)
|
|
+#define QSPI_FR_AIBSEF BIT(13)
|
|
+#define QSPI_FR_AITEF BIT(14)
|
|
+#define QSPI_FR_ABSEF BIT(15)
|
|
+#define QSPI_FR_RBDF BIT(16)
|
|
+#define QSPI_FR_RBOF BIT(17)
|
|
+#define QSPI_FR_ILLINE BIT(23)
|
|
+#define QSPI_FR_TBUF BIT(26)
|
|
+#define QSPI_FR_TBFF BIT(27)
|
|
+#define BUFFER_FR_FLAG (QSPI_FR_ABOF| QSPI_FR_RBOF| \
|
|
+ QSPI_FR_TBUF)
|
|
+
|
|
+#define COMMAND_FR_FLAG (QSPI_FR_ABSEF | QSPI_FR_AITEF | \
|
|
+ QSPI_FR_AIBSEF | QSPI_FR_IUEF | \
|
|
+ QSPI_FR_IPAEF |QSPI_FR_IPIEF | \
|
|
+ QSPI_FR_IPGEF)
|
|
+
|
|
+#define QSPI_RSER 0x164
|
|
+#define QSPI_RSER_TFIE BIT(0)
|
|
+#define QSPI_RSER_IPGEIE BIT(4)
|
|
+#define QSPI_RSER_IPIEIE BIT(6)
|
|
+#define QSPI_RSER_IPAEIE BIT(7)
|
|
+#define QSPI_RSER_IUEIE BIT(11)
|
|
+#define QSPI_RSER_ABOIE BIT(12)
|
|
+#define QSPI_RSER_AIBSIE BIT(13)
|
|
+#define QSPI_RSER_AITIE BIT(14)
|
|
+#define QSPI_RSER_ABSEIE BIT(15)
|
|
+#define QSPI_RSER_RBDIE BIT(16)
|
|
+#define QSPI_RSER_RBOIE BIT(17)
|
|
+#define QSPI_RSER_RBDDE BIT(21)
|
|
+#define QSPI_RSER_ILLINIE BIT(23)
|
|
+#define QSPI_RSER_TBFDE BIT(25)
|
|
+#define QSPI_RSER_TBUIE BIT(26)
|
|
+#define QSPI_RSER_TBFIE BIT(27)
|
|
+#define BUFFER_ERROR_INT (QSPI_RSER_ABOIE| QSPI_RSER_RBOIE| \
|
|
+ QSPI_RSER_TBUIE)
|
|
+
|
|
+#define COMMAND_ERROR_INT (QSPI_RSER_ABSEIE | QSPI_RSER_AITIE | \
|
|
+ QSPI_RSER_AIBSIE | QSPI_RSER_IUEIE | \
|
|
+ QSPI_RSER_IPAEIE |QSPI_RSER_IPIEIE | \
|
|
+ QSPI_RSER_IPGEIE)
|
|
+
|
|
+#define QSPI_SPNDST 0x168
|
|
+#define QSPI_SPTRCLR 0x16c
|
|
+#define QSPI_SPTRCLR_IPPTRC BIT(8)
|
|
+#define QSPI_SPTRCLR_BFPTRC BIT(0)
|
|
+
|
|
+#define QSPI_SFA1AD 0x180
|
|
+#define QSPI_SFA2AD 0x184
|
|
+#define QSPI_SFB1AD 0x188
|
|
+#define QSPI_SFB2AD 0x18c
|
|
+#define QSPI_DLPR 0x190
|
|
+#define QSPI_RBDR(x) (0x200 + ((x) * 4))
|
|
+
|
|
+#define QSPI_LUTKEY 0x300
|
|
+#define QSPI_LUTKEY_VALUE 0x5af05af0
|
|
+
|
|
+#define QSPI_LCKCR 0x304
|
|
+#define QSPI_LCKER_LOCK BIT(0)
|
|
+#define QSPI_LCKER_UNLOCK BIT(1)
|
|
+
|
|
+#define QSPI_LUT_BASE 0x310
|
|
+/* 16Bytes per sequence */
|
|
+#define QSPI_LUT_REG(seqid, i) (QSPI_LUT_BASE + (seqid) * 16 + (i) * 4)
|
|
+
|
|
+/*
|
|
+ * QSPI Sequence index.
|
|
+ * index 0 is preset at boot for AHB read,
|
|
+ * index 1 is used for other command.
|
|
+ */
|
|
+#define SEQID_LUT_AHBREAD_ID 0
|
|
+#define SEQID_LUT_SHARED_ID 1
|
|
+
|
|
+/* QSPI Instruction set for the LUT register */
|
|
+#define LUT_INSTR_STOP 0
|
|
+#define LUT_INSTR_CMD 1
|
|
+#define LUT_INSTR_ADDR 2
|
|
+#define LUT_INSTR_DUMMY 3
|
|
+#define LUT_INSTR_MODE 4
|
|
+#define LUT_INSTR_MODE2 5
|
|
+#define LUT_INSTR_MODE4 6
|
|
+#define LUT_INSTR_READ 7
|
|
+#define LUT_INSTR_WRITE 8
|
|
+#define LUT_INSTR_JMP_ON_CS 9
|
|
+#define LUT_INSTR_ADDR_DDR 10
|
|
+#define LUT_INSTR_MODE_DDR 11
|
|
+#define LUT_INSTR_MODE2_DDR 12
|
|
+#define LUT_INSTR_MODE4_DDR 13
|
|
+#define LUT_INSTR_READ_DDR 14
|
|
+#define LUT_INSTR_WRITE_DDR 15
|
|
+#define LUT_INSTR_DATA_LEARN 16
|
|
+
|
|
+/*
|
|
+ * The PAD definitions for LUT register.
|
|
+ *
|
|
+ * The pad stands for the number of IO lines [0:3].
|
|
+ * For example, the quad read needs four IO lines,
|
|
+ * so you should use LUT_PAD(4).
|
|
+ */
|
|
+#define LUT_PAD(x) (fls(x) - 1)
|
|
+
|
|
+/*
|
|
+ * One sequence must be consisted of 4 LUT enteries(16Bytes).
|
|
+ * LUT entries with the following register layout:
|
|
+ * b'31 b'0
|
|
+ * ---------------------------------------------------------------------------
|
|
+ * |INSTR1[15~10]|PAD1[9~8]|OPRND1[7~0] | INSTR0[15~10]|PAD0[9~8]|OPRND0[7~0]|
|
|
+ * ---------------------------------------------------------------------------
|
|
+ */
|
|
+#define LUT_DEF(idx, ins, pad, opr) \
|
|
+ ((((ins) << 10) | ((pad) << 8) | (opr)) << (((idx) & 0x1) * 16))
|
|
+
|
|
+#define READ_FROM_CACHE_OP 0x03
|
|
+#define READ_FROM_CACHE_OP_Fast 0x0b
|
|
+#define READ_FROM_CACHE_OP_X2 0x3b
|
|
+#define READ_FROM_CACHE_OP_X4 0x6b
|
|
+#define READ_FROM_CACHE_OP_DUALIO 0xbb
|
|
+#define READ_FROM_CACHE_OP_QUADIO 0xeb
|
|
+
|
|
+u32 reg_offset_table[] = {
|
|
+ QSPI_MCR, QSPI_TCR, QSPI_IPCR, QSPI_FLSHCR,
|
|
+ QSPI_BUF0CR, QSPI_BUF1CR, QSPI_BUF2CR, QSPI_BUF3CR,
|
|
+ QSPI_BFGENCR, QSPI_SOCCR, QSPI_BUF0IND, QSPI_BUF1IND,
|
|
+ QSPI_BUF2IND, QSPI_SFAR, QSPI_SFACR, QSPI_SMPR,
|
|
+ QSPI_RBSR, QSPI_RBCT, QSPI_TBSR, QSPI_TBDR,
|
|
+ QSPI_TBCT, QSPI_SR, QSPI_FR, QSPI_RSER,
|
|
+ QSPI_SPNDST, QSPI_SPTRCLR, QSPI_SFA1AD, QSPI_SFA2AD,
|
|
+ QSPI_SFB1AD, QSPI_SFB2AD, QSPI_DLPR, QSPI_LUTKEY,
|
|
+ QSPI_LCKCR
|
|
+};
|
|
+
|
|
+/* k1x qspi host priv */
|
|
+struct k1x_qspi {
|
|
+ struct device *dev;
|
|
+ struct spi_controller *ctrl;
|
|
+ void __iomem *io_map;
|
|
+ phys_addr_t io_phys;
|
|
+
|
|
+ void __iomem *ahb_map;
|
|
+ phys_addr_t memmap_base;
|
|
+ u32 memmap_size;
|
|
+
|
|
+ u32 sfa1ad;
|
|
+ u32 sfa2ad;
|
|
+ u32 sfb1ad;
|
|
+ u32 sfb2ad;
|
|
+
|
|
+ u32 pmuap_reg;
|
|
+ void __iomem *pmuap_addr;
|
|
+ u32 mpmu_acgr_reg;
|
|
+ void __iomem *mpmu_acgr;
|
|
+
|
|
+ u32 rx_buf_size;
|
|
+ u32 tx_buf_size;
|
|
+ u32 ahb_buf_size;
|
|
+ u32 ahb_read_enable;
|
|
+ u32 tx_unit_size;
|
|
+ u32 rx_unit_size;
|
|
+
|
|
+ u32 cmd_interrupt;
|
|
+ u32 fr_error_flag;
|
|
+
|
|
+ u32 tx_dma_enable;
|
|
+ u32 tx_wmrk;
|
|
+ struct dma_chan *tx_dma;
|
|
+ struct dma_slave_config tx_dma_cfg;
|
|
+
|
|
+ u32 rx_dma_enable;
|
|
+ struct dma_chan *rx_dma;
|
|
+
|
|
+ struct sg_table sgt;
|
|
+ struct completion dma_completion;
|
|
+
|
|
+ u32 cs_selected;
|
|
+ u32 max_hz;
|
|
+ u32 endian_xchg;
|
|
+ u32 dma_enable;
|
|
+
|
|
+ struct clk *clk, *bus_clk;
|
|
+ struct reset_control *resets;
|
|
+
|
|
+ struct completion cmd_completion;
|
|
+ struct mutex lock;
|
|
+ int selected;
|
|
+
|
|
+ u32 tx_underrun_err;
|
|
+ u32 rx_overflow_err;
|
|
+ u32 ahb_overflow_err;
|
|
+};
|
|
+
|
|
+enum qpsi_cs {
|
|
+ QSPI_CS_A1 = 0,
|
|
+ QSPI_CS_A2,
|
|
+ QSPI_CS_B1,
|
|
+ QSPI_CS_B2,
|
|
+ QSPI_CS_MAX,
|
|
+};
|
|
+#define QSPI_DEFAULT_CS (QSPI_CS_A1)
|
|
+
|
|
+enum qpsi_mode {
|
|
+ QSPI_NORMAL_MODE = 0,
|
|
+ QSPI_DISABLE_MODE,
|
|
+ QSPI_STOP_MODE,
|
|
+};
|
|
+
|
|
+static ssize_t qspi_info_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ struct k1x_qspi *t_qspi = dev_get_drvdata(dev);
|
|
+ return sprintf(buf, "%s: rx_dma_en=%d, rx_buf_size=0x%x, tx_dma_en=%d, tx_buf_size=0x%x,"
|
|
+ "ahb_read_enable=%d, ahb_buf_size=0x%x\n",
|
|
+ dev_name(dev),
|
|
+ t_qspi->rx_dma_enable, t_qspi->rx_buf_size,
|
|
+ t_qspi->tx_dma_enable, t_qspi->tx_buf_size,
|
|
+ t_qspi->ahb_read_enable, t_qspi->ahb_buf_size);
|
|
+}
|
|
+static DEVICE_ATTR_RO(qspi_info);
|
|
+
|
|
+static ssize_t qspi_err_resp_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ struct k1x_qspi *t_qspi = dev_get_drvdata(dev);
|
|
+ return sprintf(buf, "%s: tx_underrun (%d), rx_overflow (%d), ahb_overflow (%d)\n",
|
|
+ dev_name(dev),
|
|
+ t_qspi->tx_underrun_err, t_qspi->rx_overflow_err, t_qspi->ahb_overflow_err);
|
|
+}
|
|
+static DEVICE_ATTR_RO(qspi_err_resp);
|
|
+
|
|
+static struct attribute *qspi_dev_attrs[] = {
|
|
+ &dev_attr_qspi_info.attr,
|
|
+ &dev_attr_qspi_err_resp.attr,
|
|
+ NULL,
|
|
+};
|
|
+
|
|
+static struct attribute_group qspi_dev_group = {
|
|
+ .name = "qspi_dev",
|
|
+ .attrs = qspi_dev_attrs,
|
|
+};
|
|
+
|
|
+static void qspi_writel(struct k1x_qspi *qspi, u32 val, void __iomem *addr)
|
|
+{
|
|
+ if (qspi->endian_xchg)
|
|
+ iowrite32be(val, addr);
|
|
+ else
|
|
+ iowrite32(val, addr);
|
|
+}
|
|
+
|
|
+static u32 qspi_readl(struct k1x_qspi *qspi, void __iomem *addr)
|
|
+{
|
|
+ if (qspi->endian_xchg)
|
|
+ return ioread32be(addr);
|
|
+ else
|
|
+ return ioread32(addr);
|
|
+}
|
|
+
|
|
+static int qspi_set_func_clk(struct k1x_qspi *qspi)
|
|
+{
|
|
+ int ret = 0;
|
|
+
|
|
+ qspi->clk = devm_clk_get(qspi->dev, "qspi_clk");
|
|
+ if (IS_ERR_OR_NULL(qspi->clk)) {
|
|
+ dev_err(qspi->dev, "can not find the clock\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ qspi->bus_clk = devm_clk_get(qspi->dev, "qspi_bus_clk");
|
|
+ if (IS_ERR_OR_NULL(qspi->bus_clk)) {
|
|
+ dev_err(qspi->dev, "can not find the bus clock\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ clk_disable_unprepare(qspi->bus_clk);
|
|
+ clk_disable_unprepare(qspi->clk);
|
|
+
|
|
+ ret = clk_set_rate(qspi->clk, qspi->max_hz);
|
|
+ if (ret) {
|
|
+ dev_err(qspi->dev, "fail to set clk, ret:%d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = clk_prepare_enable(qspi->clk);
|
|
+ if (ret) {
|
|
+ dev_err(qspi->dev, "fail to enable clk, ret:%d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ clk_prepare_enable(qspi->bus_clk);
|
|
+
|
|
+ dev_dbg(qspi->dev, "bus clock %dHz, PMUap reg[0x%08x]:0x%08x\n",
|
|
+ qspi->max_hz, qspi->pmuap_reg, qspi_readl(qspi, qspi->pmuap_addr));
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void qspi_config_mfp(struct k1x_qspi *qspi)
|
|
+{
|
|
+ int cs = qspi->cs_selected;
|
|
+
|
|
+ /* TODO: only for FPGA */
|
|
+#if 0
|
|
+ void * __iomem mfpr_base = ioremap((phys_addr_t)0xd401e000, 0x200);
|
|
+ if (!mfpr_base) {
|
|
+ pr_err("%s: ioremap mfpr reg error.\n", __func__);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (cs == QSPI_CS_A1 || cs == QSPI_CS_A2) {
|
|
+ writel(0xa800, mfpr_base + 0x174); // QSPI_DAT3
|
|
+ writel(0xa800, mfpr_base + 0x170); // QSPI_DAT2
|
|
+ writel(0xa800, mfpr_base + 0x16C); // QSPI_DAT1
|
|
+ writel(0xa800, mfpr_base + 0x168); // QSPI_DAT0
|
|
+ writel(0xa800, mfpr_base + 0x17C); // QSPI_CLK
|
|
+ writel(0xc800, mfpr_base + 0x178); // QSPI_CS1
|
|
+ dev_err(qspi->dev, "config mfp for cs:[%d]\n", cs);
|
|
+ }
|
|
+#endif
|
|
+ dev_info(qspi->dev, "config mfp for cs:[%d]\n", cs);
|
|
+}
|
|
+
|
|
+static int k1x_qspi_readl_poll_tout(struct k1x_qspi *qspi, void __iomem *base,
|
|
+ u32 mask, u32 timeout_us, u8 wait_set)
|
|
+{
|
|
+ u32 reg;
|
|
+
|
|
+ if (qspi->endian_xchg)
|
|
+ mask = swab32(mask);
|
|
+
|
|
+ if (wait_set)
|
|
+ return readl_poll_timeout(base, reg, (reg & mask), 10, timeout_us);
|
|
+ else
|
|
+ return readl_poll_timeout(base, reg, !(reg & mask), 10, timeout_us);
|
|
+}
|
|
+
|
|
+static void qspi_reset(struct k1x_qspi *qspi)
|
|
+{
|
|
+ uint32_t reg;
|
|
+ int err;
|
|
+
|
|
+ /* QSPI_SR[QSPI_SR_BUSY] must be 0 */
|
|
+ err = k1x_qspi_readl_poll_tout(qspi, qspi->io_map + QSPI_SR,
|
|
+ QSPI_SR_BUSY, QSPI_WAIT_TIMEOUT*1000, QSPI_WAIT_BIT_CLEAR);
|
|
+ if (err) {
|
|
+ dev_err(qspi->dev, "failed to reset qspi host.\n");
|
|
+ } else {
|
|
+ /* qspi softreset first */
|
|
+ reg = qspi_readl(qspi, qspi->io_map + QSPI_MCR);
|
|
+ reg |= QSPI_MCR_SWRSTHD_MASK | QSPI_MCR_SWRSTSD_MASK;
|
|
+ qspi_writel(qspi, reg, qspi->io_map + QSPI_MCR);
|
|
+ reg = qspi_readl(qspi, qspi->io_map + QSPI_MCR);
|
|
+ if ((reg & 0x3) != 0x3)
|
|
+ dev_info(qspi->dev, "reset ignored 0x%x.\n", reg);
|
|
+
|
|
+ udelay(1);
|
|
+ reg &= ~(QSPI_MCR_SWRSTHD_MASK | QSPI_MCR_SWRSTSD_MASK);
|
|
+ qspi_writel(qspi, reg, qspi->io_map + QSPI_MCR);
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+static void qspi_enter_mode(struct k1x_qspi *qspi, uint32_t mode)
|
|
+{
|
|
+ uint32_t mcr;
|
|
+
|
|
+ mcr = qspi_readl(qspi, qspi->io_map + QSPI_MCR);
|
|
+ if (mode == QSPI_NORMAL_MODE)
|
|
+ mcr &= ~QSPI_MCR_MDIS_MASK;
|
|
+ else if (mode == QSPI_DISABLE_MODE)
|
|
+ mcr |= QSPI_MCR_MDIS_MASK;
|
|
+ qspi_writel(qspi, mcr, qspi->io_map + QSPI_MCR);
|
|
+}
|
|
+
|
|
+static void qspi_write_sfar(struct k1x_qspi *qspi, uint32_t val)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ /* QSPI_SR[IP_ACC] must be 0 */
|
|
+ err = k1x_qspi_readl_poll_tout(qspi, qspi->io_map + QSPI_SR,
|
|
+ QSPI_SR_IP_ACC_MASK, QSPI_WAIT_TIMEOUT*1000, QSPI_WAIT_BIT_CLEAR);
|
|
+ if (err)
|
|
+ dev_err(qspi->dev, "failed to set QSPI_SFAR.\n");
|
|
+ else
|
|
+ qspi_writel(qspi, val, qspi->io_map + QSPI_SFAR);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * IP Command Trigger could not be executed Error Flag may happen for write
|
|
+ * access to RBCT/SFAR register, need retry for these two register
|
|
+ */
|
|
+static void qspi_write_rbct(struct k1x_qspi *qspi, uint32_t val)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ /* QSPI_SR[IP_ACC] must be 0 */
|
|
+ err = k1x_qspi_readl_poll_tout(qspi, qspi->io_map + QSPI_SR,
|
|
+ QSPI_SR_IP_ACC_MASK, QSPI_WAIT_TIMEOUT*1000, QSPI_WAIT_BIT_CLEAR);
|
|
+ if (err)
|
|
+ dev_err(qspi->dev, "failed to set QSPI_RBCT.\n");
|
|
+ else
|
|
+ qspi_writel(qspi, val, qspi->io_map + QSPI_RBCT);
|
|
+}
|
|
+
|
|
+void qspi_init_ahbread(struct k1x_qspi *qspi, int seq_id)
|
|
+{
|
|
+ u32 buf_cfg = 0;
|
|
+
|
|
+ /* Disable BUF0~BUF1, use BUF3 for all masters */
|
|
+ qspi_writel(qspi, 0, qspi->io_map + QSPI_BUF0IND);
|
|
+ qspi_writel(qspi, 0, qspi->io_map + QSPI_BUF1IND);
|
|
+ qspi_writel(qspi, 0, qspi->io_map + QSPI_BUF2IND);
|
|
+
|
|
+ buf_cfg = QSPI_BUF3CR_ALLMST_MASK |
|
|
+ QSPI_BUF3CR_ADATSZ((qspi->ahb_buf_size / 8));
|
|
+
|
|
+ /* AHB Master port */
|
|
+ qspi_writel(qspi, 0xe, qspi->io_map + QSPI_BUF0CR);
|
|
+ qspi_writel(qspi, 0xe, qspi->io_map + QSPI_BUF1CR);
|
|
+ qspi_writel(qspi, 0xe, qspi->io_map + QSPI_BUF2CR);
|
|
+ qspi_writel(qspi, buf_cfg, qspi->io_map + QSPI_BUF3CR); // other masters
|
|
+
|
|
+ /* set AHB read sequence id */
|
|
+ qspi_writel(qspi, QSPI_BFGENCR_SEQID(seq_id), qspi->io_map + QSPI_BFGENCR);
|
|
+ dev_info(qspi->dev, "AHB buf size: %d\n", qspi->ahb_buf_size);
|
|
+}
|
|
+
|
|
+void qspi_dump_reg(struct k1x_qspi *qspi)
|
|
+{
|
|
+ u32 reg = 0;
|
|
+ void __iomem *base = qspi->io_map;
|
|
+ int i;
|
|
+
|
|
+ dev_notice(qspi->dev, "dump qspi host register:\n");
|
|
+ for (i = 0; i < ARRAY_SIZE(reg_offset_table); i++) {
|
|
+ if (i > 0 && (i % 4 == 0))
|
|
+ dev_notice(qspi->dev, "\n");
|
|
+ reg = qspi_readl(qspi, base + reg_offset_table[i]);
|
|
+ dev_notice(qspi->dev, "offset[0x%03x]:0x%08x\t\t",
|
|
+ reg_offset_table[i], reg);
|
|
+ }
|
|
+
|
|
+ dev_notice(qspi->dev, "\ndump AHB read LUT:\n");
|
|
+ for (i = 0; i < 4; i++) {
|
|
+ reg = qspi_readl(qspi, base + QSPI_LUT_REG(SEQID_LUT_AHBREAD_ID, i));
|
|
+ dev_notice(qspi->dev, "lut_reg[0x%03x]:0x%08x\t\t",
|
|
+ QSPI_LUT_REG(SEQID_LUT_AHBREAD_ID, i), reg);
|
|
+ }
|
|
+
|
|
+ dev_notice(qspi->dev, "\ndump shared LUT:\n");
|
|
+ for (i = 0; i < 4; i++) {
|
|
+ reg = qspi_readl(qspi, base + QSPI_LUT_REG(SEQID_LUT_SHARED_ID, i));
|
|
+ dev_notice(qspi->dev, "lut_reg[0x%03x]:0x%08x\t\t",
|
|
+ QSPI_LUT_REG(SEQID_LUT_SHARED_ID, i), reg);
|
|
+ }
|
|
+ dev_notice(qspi->dev, "\n");
|
|
+}
|
|
+
|
|
+/*
|
|
+ * If the slave device content being changed by Write/Erase, need to
|
|
+ * invalidate the AHB buffer. This can be achieved by doing the reset
|
|
+ * of controller after setting MCR0[SWRESET] bit.
|
|
+ */
|
|
+static inline void k1x_qspi_invalid(struct k1x_qspi *qspi)
|
|
+{
|
|
+ u32 reg;
|
|
+
|
|
+ reg = qspi_readl(qspi, qspi->io_map + QSPI_MCR);
|
|
+ reg |= QSPI_MCR_SWRSTHD_MASK | QSPI_MCR_SWRSTSD_MASK;
|
|
+ qspi_writel(qspi, reg, qspi->io_map + QSPI_MCR);
|
|
+
|
|
+ /*
|
|
+ * The minimum delay : 1 AHB + 2 SFCK clocks.
|
|
+ * Delay 1 us is enough.
|
|
+ */
|
|
+ udelay(1);
|
|
+
|
|
+ reg &= ~(QSPI_MCR_SWRSTHD_MASK | QSPI_MCR_SWRSTSD_MASK);
|
|
+ qspi_writel(qspi, reg, qspi->io_map + QSPI_MCR);
|
|
+}
|
|
+
|
|
+static void k1x_qspi_prepare_lut(struct k1x_qspi *qspi,
|
|
+ const struct spi_mem_op *op, u32 seq_id)
|
|
+{
|
|
+ u32 lutval[4] = {0,};
|
|
+ int lutidx = 0;
|
|
+ int i;
|
|
+
|
|
+ /* qspi cmd */
|
|
+ lutval[0] |= LUT_DEF(lutidx, LUT_INSTR_CMD,
|
|
+ LUT_PAD(op->cmd.buswidth),
|
|
+ op->cmd.opcode);
|
|
+ lutidx++;
|
|
+
|
|
+ /* addr bytes */
|
|
+ if (op->addr.nbytes) {
|
|
+ lutval[lutidx / 2] |= LUT_DEF(lutidx, LUT_INSTR_ADDR,
|
|
+ LUT_PAD(op->addr.buswidth),
|
|
+ op->addr.nbytes * 8);
|
|
+ lutidx++;
|
|
+ }
|
|
+
|
|
+ /* dummy bytes, if needed */
|
|
+ if (op->dummy.nbytes) {
|
|
+ lutval[lutidx / 2] |= LUT_DEF(lutidx, LUT_INSTR_DUMMY,
|
|
+ LUT_PAD(op->dummy.buswidth),
|
|
+ op->dummy.nbytes * 8 /
|
|
+ op->dummy.buswidth);
|
|
+ lutidx++;
|
|
+ }
|
|
+
|
|
+ /* read/write data bytes */
|
|
+ if (op->data.nbytes) {
|
|
+ lutval[lutidx / 2] |= LUT_DEF(lutidx,
|
|
+ op->data.dir == SPI_MEM_DATA_IN ?
|
|
+ LUT_INSTR_READ : LUT_INSTR_WRITE,
|
|
+ LUT_PAD(op->data.buswidth),
|
|
+ 0);
|
|
+ lutidx++;
|
|
+ }
|
|
+
|
|
+ /* stop condition. */
|
|
+ lutval[lutidx / 2] |= LUT_DEF(lutidx, LUT_INSTR_STOP, 0, 0);
|
|
+
|
|
+ /* unlock LUT */
|
|
+ qspi_writel(qspi, QSPI_LUTKEY_VALUE, qspi->io_map + QSPI_LUTKEY);
|
|
+ qspi_writel(qspi, QSPI_LCKER_UNLOCK, qspi->io_map + QSPI_LCKCR);
|
|
+
|
|
+ /* fill LUT register */
|
|
+ for (i = 0; i < ARRAY_SIZE(lutval); i++)
|
|
+ qspi_writel(qspi, lutval[i], qspi->io_map + QSPI_LUT_REG(seq_id, i));
|
|
+
|
|
+ /* lock LUT */
|
|
+ qspi_writel(qspi, QSPI_LUTKEY_VALUE, qspi->io_map + QSPI_LUTKEY);
|
|
+ qspi_writel(qspi, QSPI_LCKER_LOCK, qspi->io_map + QSPI_LCKCR);
|
|
+
|
|
+ dev_dbg(qspi->dev, "opcode:0x%x, lut_reg[0:0x%x, 1:0x%x, 2:0x%x, 3:0x%x]\n",
|
|
+ op->cmd.opcode, lutval[0], lutval[1], lutval[2], lutval[3]);
|
|
+}
|
|
+
|
|
+static void k1x_qspi_enable_interrupt(struct k1x_qspi *qspi, u32 val)
|
|
+{
|
|
+ u32 resr = 0;
|
|
+
|
|
+ resr = qspi_readl(qspi, qspi->io_map + QSPI_RSER);
|
|
+ resr |= val;
|
|
+ qspi_writel(qspi, resr, qspi->io_map + QSPI_RSER);
|
|
+}
|
|
+
|
|
+static void k1x_qspi_disable_interrupt(struct k1x_qspi *qspi, u32 val)
|
|
+{
|
|
+ u32 resr = 0;
|
|
+
|
|
+ resr = qspi_readl(qspi, qspi->io_map + QSPI_RSER);
|
|
+ resr &= ~val;
|
|
+ qspi_writel(qspi, resr, qspi->io_map + QSPI_RSER);
|
|
+}
|
|
+
|
|
+static void k1x_qspi_prepare_dma(struct k1x_qspi *qspi)
|
|
+{
|
|
+ struct dma_slave_config dma_cfg;
|
|
+ struct device *dev = qspi->dev;
|
|
+ dma_cap_mask_t mask;
|
|
+
|
|
+ if (qspi->rx_dma_enable) {
|
|
+ /* RX DMA: DMA_MEMCPY type */
|
|
+ dma_cap_zero(mask);
|
|
+ dma_cap_set(DMA_MEMCPY, mask);
|
|
+ qspi->rx_dma = dma_request_chan_by_mask(&mask);
|
|
+ if (IS_ERR_OR_NULL(qspi->rx_dma)) {
|
|
+ dev_err(dev, "rx dma request channel failed\n");
|
|
+ qspi->rx_dma = NULL;
|
|
+ qspi->rx_dma_enable = 0;
|
|
+ } else {
|
|
+ dev_dbg(dev, "rx dma enable, channel:%d\n", qspi->rx_dma->chan_id);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (qspi->tx_dma_enable) {
|
|
+ /* TX DMA: DMA_SLAVE type */
|
|
+ qspi->tx_dma = dma_request_slave_channel(dev, "tx-dma");
|
|
+ if (qspi->tx_dma) {
|
|
+ memset(&dma_cfg, 0, sizeof(struct dma_slave_config));
|
|
+ dma_cfg.direction = DMA_MEM_TO_DEV;
|
|
+ dma_cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
|
+ dma_cfg.dst_addr = qspi->io_phys + QSPI_TBDR - 4;
|
|
+ dma_cfg.dst_maxburst = QSPI_TX_DMA_BURST;
|
|
+ if (dmaengine_slave_config(qspi->tx_dma, &dma_cfg)) {
|
|
+ dev_err(dev, "tx dma slave config failed\n");
|
|
+ dma_release_channel(qspi->tx_dma);
|
|
+ qspi->tx_dma = NULL;
|
|
+ qspi->tx_dma_enable = 0;
|
|
+ } else {
|
|
+ dev_dbg(dev, "tx dma enable, channel:%d\n", qspi->tx_dma->chan_id);
|
|
+ }
|
|
+ } else {
|
|
+ qspi->tx_dma_enable = 0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (qspi->tx_dma || qspi->rx_dma)
|
|
+ init_completion(&qspi->dma_completion);
|
|
+}
|
|
+
|
|
+static void k1x_qspi_dma_callback(void *arg)
|
|
+{
|
|
+ struct completion *dma_completion = arg;
|
|
+
|
|
+ complete(dma_completion);
|
|
+}
|
|
+
|
|
+int k1x_qspi_tx_dma_exec(struct k1x_qspi *qspi,
|
|
+ const struct spi_mem_op *op)
|
|
+{
|
|
+ struct dma_async_tx_descriptor *desc;
|
|
+ enum dma_transfer_direction dma_dir;
|
|
+ dma_cookie_t cookie;
|
|
+ int err = 0;
|
|
+
|
|
+ if (!virt_addr_valid(op->data.buf.in) ||
|
|
+ spi_controller_dma_map_mem_op_data(qspi->ctrl, op, &qspi->sgt)) {
|
|
+ dev_err(qspi->dev, "tx dma spi_controller_dma_map_mem_op_data error\n");
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ dma_dir = DMA_MEM_TO_DEV;
|
|
+ desc = dmaengine_prep_slave_sg(qspi->tx_dma, qspi->sgt.sgl, qspi->sgt.nents,
|
|
+ dma_dir, DMA_PREP_INTERRUPT);
|
|
+ if (!desc) {
|
|
+ dev_err(qspi->dev, "tx dma dmaengine_prep_slave_sg error\n");
|
|
+ err = -ENOMEM;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ reinit_completion(&qspi->dma_completion);
|
|
+ desc->callback = k1x_qspi_dma_callback;
|
|
+ desc->callback_param = &qspi->dma_completion;
|
|
+
|
|
+ cookie = dmaengine_submit(desc);
|
|
+ err = dma_submit_error(cookie);
|
|
+ if (err) {
|
|
+ dev_err(qspi->dev, "tx dma dmaengine_submit error\n");
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ dma_async_issue_pending(qspi->tx_dma);
|
|
+
|
|
+ return 0;
|
|
+out:
|
|
+ spi_controller_dma_unmap_mem_op_data(qspi->ctrl, op, &qspi->sgt);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int k1x_qspi_rx_dma_exec(struct k1x_qspi *qspi, dma_addr_t dma_dst,
|
|
+ dma_addr_t dma_src, size_t len)
|
|
+{
|
|
+ dma_cookie_t cookie;
|
|
+ enum dma_ctrl_flags flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
|
|
+ struct dma_async_tx_descriptor *desc;
|
|
+ int ret;
|
|
+
|
|
+ desc = dmaengine_prep_dma_memcpy(qspi->rx_dma, dma_dst, dma_src, len, flags);
|
|
+ if (!desc) {
|
|
+ dev_err(qspi->dev, "dmaengine_prep_dma_memcpy error\n");
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ reinit_completion(&qspi->dma_completion);
|
|
+ desc->callback = k1x_qspi_dma_callback;
|
|
+ desc->callback_param = &qspi->dma_completion;
|
|
+ cookie = dmaengine_submit(desc);
|
|
+ ret = dma_submit_error(cookie);
|
|
+ if (ret) {
|
|
+ dev_err(qspi->dev, "dma_submit_error %d\n", cookie);
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ dma_async_issue_pending(qspi->rx_dma);
|
|
+ ret = wait_for_completion_timeout(&qspi->dma_completion,
|
|
+ msecs_to_jiffies(len));
|
|
+ if (ret <= 0) {
|
|
+ dmaengine_terminate_sync(qspi->rx_dma);
|
|
+ dev_err(qspi->dev, "DMA wait_for_completion_timeout\n");
|
|
+ return -ETIMEDOUT;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int k1x_qspi_rx_dma_sg(struct k1x_qspi *qspi, struct sg_table rx_sg,
|
|
+ loff_t from)
|
|
+{
|
|
+ struct scatterlist *sg;
|
|
+ dma_addr_t dma_src = qspi->memmap_base + from;
|
|
+ dma_addr_t dma_dst;
|
|
+ int i, len, ret;
|
|
+
|
|
+ for_each_sg(rx_sg.sgl, sg, rx_sg.nents, i) {
|
|
+ dma_dst = sg_dma_address(sg);
|
|
+ len = sg_dma_len(sg);
|
|
+ dev_dbg(qspi->dev, "rx dma, dst:0x%pad, src:0x%pad, len:%d\n",
|
|
+ &dma_dst, &dma_src, len);
|
|
+ ret = k1x_qspi_rx_dma_exec(qspi, dma_dst, dma_src, len);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ dma_src += len;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int k1x_qspi_ahb_read(struct k1x_qspi *qspi,
|
|
+ const struct spi_mem_op *op)
|
|
+{
|
|
+ int ret = 0;
|
|
+ u32 len = op->data.nbytes;
|
|
+ u32 from = op->addr.val;
|
|
+ struct sg_table sgt;
|
|
+
|
|
+ /* Read out the data directly from the AHB buffer. */
|
|
+ dev_dbg(qspi->dev, "ahb read %d bytes from address:0x%llx\n",
|
|
+ len, (qspi->memmap_base + op->addr.val));
|
|
+ if (from + len > qspi->memmap_size)
|
|
+ return -ENOTSUPP;
|
|
+
|
|
+ /* firstly try the DMA */
|
|
+ if (qspi->rx_dma_enable) {
|
|
+ if (virt_addr_valid(op->data.buf.in) &&
|
|
+ !spi_controller_dma_map_mem_op_data(qspi->ctrl, op, &sgt)) {
|
|
+ ret = k1x_qspi_rx_dma_sg(qspi, sgt, from);
|
|
+ spi_controller_dma_unmap_mem_op_data(qspi->ctrl, op, &sgt);
|
|
+ } else {
|
|
+ ret = -EIO;
|
|
+ dev_err(qspi->dev, "spi_controller_dma_map_mem_op_data error\n");
|
|
+ }
|
|
+
|
|
+ /* DMA completed */
|
|
+ if (!ret)
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ if (qspi->rx_dma_enable && ret) {
|
|
+ dev_dbg(qspi->dev, "rx dma read fallback to memcpy read.\n");
|
|
+ }
|
|
+
|
|
+ if (!qspi->rx_dma_enable || (qspi->rx_dma_enable && ret)) {
|
|
+ memcpy(op->data.buf.in, (qspi->ahb_map + op->addr.val), len);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int k1x_qspi_fill_txfifo(struct k1x_qspi *qspi,
|
|
+ const struct spi_mem_op *op)
|
|
+{
|
|
+ void __iomem *base = qspi->io_map;
|
|
+ int i;
|
|
+ u32 val;
|
|
+ u32 tbsr;
|
|
+ u32 wait_cnt;
|
|
+
|
|
+ if (!qspi->tx_dma_enable || (op->data.nbytes % QSPI_TX_BUFF_POP_MIN)) {
|
|
+ qspi->tx_wmrk = 0;
|
|
+ for (i = 0; i < ALIGN_DOWN(op->data.nbytes, 4); i += 4) {
|
|
+ memcpy(&val, op->data.buf.out + i, 4);
|
|
+ qspi_writel(qspi, val, base + QSPI_TBDR);
|
|
+ }
|
|
+
|
|
+ if (i < op->data.nbytes) {
|
|
+ memcpy(&val, op->data.buf.out + i, op->data.nbytes - i);
|
|
+ qspi_writel(qspi, val, base + QSPI_TBDR);
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * There must be at least 128bit data available in TX FIFO
|
|
+ * for any pop operation otherwise QSPI_FR[TBUF] will be set
|
|
+ */
|
|
+ for (i = op->data.nbytes; i < ALIGN_DOWN(op->data.nbytes + (QSPI_TX_BUFF_POP_MIN - 1), QSPI_TX_BUFF_POP_MIN); i += 4) {
|
|
+ qspi_writel(qspi, 0, base + QSPI_TBDR);
|
|
+ }
|
|
+ } else {
|
|
+ /*
|
|
+ * Note that the number of bytes per DMA loop is determined
|
|
+ * by thee size of the QSPI_TBCT[WMRK].
|
|
+ * bytes per DMA loop = (QSPI_TBCT[WMRK] + 1) * 4.
|
|
+ * set QSPI_TX_WMRK as the TX watermark.
|
|
+ */
|
|
+ qspi->tx_wmrk = QSPI_TX_WMRK;
|
|
+ qspi_writel(qspi, qspi->tx_wmrk, base + QSPI_TBCT);
|
|
+
|
|
+ /* config DMA channel and start */
|
|
+ if (k1x_qspi_tx_dma_exec(qspi, op)) {
|
|
+ qspi->tx_wmrk = 0;
|
|
+ dev_err(qspi->dev, "failed to start tx dma\n");
|
|
+ return -EIO;
|
|
+ }
|
|
+ /* enable DMA request */
|
|
+ k1x_qspi_enable_interrupt(qspi, QSPI_RSER_TBFDE);
|
|
+
|
|
+ /*
|
|
+ * before trigger qspi to send data to external bus, TX bufer
|
|
+ * need to have some data, or underrun error may happen.
|
|
+ * DMA need some time to write data to TX buffer, so add
|
|
+ * a delay here for this requirement.
|
|
+ */
|
|
+ wait_cnt = 0;
|
|
+ tbsr = qspi_readl(qspi, base + QSPI_TBSR);
|
|
+ while (4 * (tbsr >> 16) < min_t(unsigned int, qspi->tx_buf_size, op->data.nbytes)) {
|
|
+ udelay(1);
|
|
+ tbsr = qspi_readl(qspi, base + QSPI_TBSR);
|
|
+ if (wait_cnt++ >= 100) {
|
|
+ msleep(100);
|
|
+ tbsr = qspi_readl(qspi, base + QSPI_TBSR);
|
|
+ if (4 * (tbsr >> 16) < min_t(unsigned int, qspi->tx_buf_size, op->data.nbytes)) {
|
|
+ dev_err(qspi->dev, "tx dma failed to fill txbuf\n");
|
|
+ /* disable all interrupts */
|
|
+ qspi_writel(qspi, 0, qspi->io_map + QSPI_RSER);
|
|
+ dmaengine_terminate_all(qspi->tx_dma);
|
|
+ spi_controller_dma_unmap_mem_op_data(qspi->ctrl, op, &qspi->sgt);
|
|
+ qspi->tx_wmrk = 0;
|
|
+
|
|
+ return -EIO;
|
|
+ } else {
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void k1x_qspi_read_rxfifo(struct k1x_qspi *qspi,
|
|
+ const struct spi_mem_op *op)
|
|
+{
|
|
+ void __iomem *base = qspi->io_map;
|
|
+ int i;
|
|
+ u8 *buf = op->data.buf.in;
|
|
+ u32 val;
|
|
+
|
|
+ dev_dbg(qspi->dev, "ip read %d bytes\n", op->data.nbytes);
|
|
+ for (i = 0; i < ALIGN_DOWN(op->data.nbytes, 4); i += 4) {
|
|
+ val = qspi_readl(qspi, base + QSPI_RBDR(i / 4));
|
|
+ memcpy(buf + i, &val, 4);
|
|
+ }
|
|
+
|
|
+ if (i < op->data.nbytes) {
|
|
+ val = qspi_readl(qspi, base + QSPI_RBDR(i / 4));
|
|
+ memcpy(buf + i, &val, op->data.nbytes - i);
|
|
+ }
|
|
+}
|
|
+
|
|
+static irqreturn_t k1x_qspi_irq_handler(int irq, void *dev_id)
|
|
+{
|
|
+ struct k1x_qspi *qspi = dev_id;
|
|
+ u32 fr;
|
|
+
|
|
+ /* disable all interrupts */
|
|
+ qspi_writel(qspi, 0, qspi->io_map + QSPI_RSER);
|
|
+
|
|
+ fr = qspi_readl(qspi, qspi->io_map + QSPI_FR);
|
|
+ dev_dbg(qspi->dev, "QSPI_FR:0x%08x\n", fr);
|
|
+ /* check QSPI_FR error flag */
|
|
+ if (fr & (COMMAND_FR_FLAG | BUFFER_FR_FLAG)) {
|
|
+ qspi->fr_error_flag = fr & (COMMAND_FR_FLAG | BUFFER_FR_FLAG);
|
|
+
|
|
+ if (fr & QSPI_FR_IPGEF)
|
|
+ dev_err(qspi->dev, "IP command trigger during AHB grant\n");
|
|
+ if (fr & QSPI_FR_IPIEF)
|
|
+ dev_err(qspi->dev, "IP command trigger could not be executed\n");
|
|
+ if (fr & QSPI_FR_IPAEF)
|
|
+ dev_err(qspi->dev, "IP command trigger during AHB access\n");
|
|
+ if (fr & QSPI_FR_IUEF)
|
|
+ dev_err(qspi->dev, "IP command usage error\n");
|
|
+ if (fr & QSPI_FR_AIBSEF)
|
|
+ dev_err(qspi->dev, "AHB illegal burst size error\n");
|
|
+ if (fr & QSPI_FR_AITEF)
|
|
+ dev_err(qspi->dev, "AHB illegal trancaction error\n");
|
|
+ if (fr & QSPI_FR_ABSEF)
|
|
+ dev_err(qspi->dev, "AHB sequence error\n");
|
|
+
|
|
+ if (fr & QSPI_FR_TBUF) {
|
|
+ /* disable TBFDE interrupt */
|
|
+ k1x_qspi_disable_interrupt(qspi, QSPI_RSER_TBFDE);
|
|
+ dev_err_ratelimited(qspi->dev, "TX buffer underrun\n");
|
|
+ qspi->tx_underrun_err++;
|
|
+ }
|
|
+ if (fr & QSPI_FR_RBOF) {
|
|
+ dev_err(qspi->dev, "RX buffer overflow\n");
|
|
+ qspi->rx_overflow_err++;
|
|
+ }
|
|
+ if (fr & QSPI_FR_ABOF) {
|
|
+ dev_err(qspi->dev, "AHB buffer overflow\n");
|
|
+ qspi->ahb_overflow_err++;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (qspi->cmd_interrupt && (fr & (QSPI_FR_TFF_MASK | COMMAND_FR_FLAG | BUFFER_FR_FLAG)))
|
|
+ complete(&qspi->cmd_completion);
|
|
+
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+static int k1x_qspi_do_op(struct k1x_qspi *qspi, const struct spi_mem_op *op)
|
|
+{
|
|
+ void __iomem *base = qspi->io_map;
|
|
+ int err = 0;
|
|
+ u32 mcr;
|
|
+
|
|
+ if (qspi->cmd_interrupt) {
|
|
+ k1x_qspi_enable_interrupt(qspi, QSPI_RSER_TFIE | BUFFER_ERROR_INT | COMMAND_ERROR_INT);
|
|
+ init_completion(&qspi->cmd_completion);
|
|
+ }
|
|
+
|
|
+#ifdef K1X_DUMP_QSPI_REG
|
|
+ /* dump reg if need */
|
|
+ qspi_dump_reg(qspi);
|
|
+#endif
|
|
+ /* trigger LUT */
|
|
+ qspi_writel(qspi, op->data.nbytes | QSPI_IPCR_SEQID(SEQID_LUT_SHARED_ID),
|
|
+ base + QSPI_IPCR);
|
|
+
|
|
+ /* wait for the transaction complete */
|
|
+ if (qspi->cmd_interrupt) {
|
|
+ wait_for_completion(&qspi->cmd_completion);
|
|
+ } else {
|
|
+ err = k1x_qspi_readl_poll_tout(qspi, base + QSPI_FR, QSPI_FR_TFF_MASK,
|
|
+ QSPI_WAIT_TIMEOUT*1000, QSPI_WAIT_BIT_SET);
|
|
+ }
|
|
+ if (err) {
|
|
+ dev_err(qspi->dev, "opcode:0x%x transaction abort, ret:%d, error flag:0x%08x\n",
|
|
+ op->cmd.opcode, err, qspi->fr_error_flag);
|
|
+ dev_err(qspi->dev, "pmuap[0x%08x]:0x%08x\n", qspi->pmuap_reg, qspi_readl(qspi, qspi->pmuap_addr));
|
|
+ dev_err(qspi->dev, "mpmu[0x%08x]:0x%08x\n", K1X_MPMU_ACGR, qspi_readl(qspi, qspi->mpmu_acgr));
|
|
+ qspi_dump_reg(qspi);
|
|
+ goto tx_dma_unmap;
|
|
+ }
|
|
+
|
|
+ err = k1x_qspi_readl_poll_tout(qspi, base + QSPI_SR, QSPI_SR_BUSY,
|
|
+ QSPI_WAIT_TIMEOUT*1000, QSPI_WAIT_BIT_CLEAR);
|
|
+ if (err) {
|
|
+ dev_err(qspi->dev, "opcode:0x%x busy timeout, ret:%d\n", op->cmd.opcode, err);
|
|
+ goto tx_dma_unmap;
|
|
+ }
|
|
+
|
|
+ /* read RX buffer for IP command read */
|
|
+ if (op->data.nbytes && op->data.dir == SPI_MEM_DATA_IN) {
|
|
+#ifdef K1X_DUMP_QSPI_REG
|
|
+ qspi_dump_reg(qspi);
|
|
+#endif
|
|
+ k1x_qspi_read_rxfifo(qspi, op);
|
|
+ }
|
|
+
|
|
+ if (qspi->fr_error_flag & QSPI_FR_TBUF) {
|
|
+ /* abort current dma transfer */
|
|
+ if (qspi->tx_dma_enable)
|
|
+ dmaengine_terminate_all(qspi->tx_dma);
|
|
+
|
|
+ /* clear TX buf */
|
|
+ mcr = qspi_readl(qspi, qspi->io_map + QSPI_MCR);
|
|
+ mcr |= QSPI_MCR_CLR_TXF_MASK ;
|
|
+ qspi_writel(qspi, mcr, qspi->io_map + QSPI_MCR);
|
|
+
|
|
+ /* reduce tx unit size and retry */
|
|
+ if (qspi->tx_dma_enable)
|
|
+ qspi->tx_unit_size = qspi->tx_buf_size;
|
|
+
|
|
+ err = -EAGAIN;
|
|
+ } else {
|
|
+ if (qspi->tx_dma_enable)
|
|
+ qspi->tx_unit_size = qspi->tx_buf_size;
|
|
+ }
|
|
+
|
|
+tx_dma_unmap:
|
|
+ if (qspi->tx_wmrk) {
|
|
+ /* disable TBFDE interrupt and dma unmap */
|
|
+ k1x_qspi_disable_interrupt(qspi, QSPI_RSER_TBFDE);
|
|
+ spi_controller_dma_unmap_mem_op_data(qspi->ctrl, op, &qspi->sgt);
|
|
+ qspi->tx_wmrk = 0;
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void dump_spi_mem_op_info(struct k1x_qspi *qspi,
|
|
+ const struct spi_mem_op *op)
|
|
+{
|
|
+ dev_dbg(qspi->dev, "cmd.opcode:0x%x\n", op->cmd.opcode);
|
|
+ dev_dbg(qspi->dev, "cmd.buswidth:%d\n", op->cmd.buswidth);
|
|
+ dev_dbg(qspi->dev, "addr.nbytes:%d,\n", op->addr.nbytes);
|
|
+ dev_dbg(qspi->dev, "addr.buswidth:%d\n", op->addr.buswidth);
|
|
+ dev_dbg(qspi->dev, "addr.val:0x%llx\n", op->addr.val);
|
|
+ dev_dbg(qspi->dev, "dummy.nbytes:%d\n", op->dummy.nbytes);
|
|
+ dev_dbg(qspi->dev, "dummy.buswidth:%d\n", op->dummy.buswidth);
|
|
+ dev_dbg(qspi->dev, "%s data.nbytes:%d\n",
|
|
+ (op->data.dir == SPI_MEM_DATA_IN) ? "read" :"write",
|
|
+ op->data.nbytes);
|
|
+ dev_dbg(qspi->dev, "data.buswidth:%d\n", op->data.buswidth);
|
|
+ dev_dbg(qspi->dev, "data.buf:0x%p\n", op->data.buf.in);
|
|
+}
|
|
+
|
|
+static int is_read_from_cache_opcode(u8 opcode)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = ((opcode == READ_FROM_CACHE_OP) ||
|
|
+ (opcode == READ_FROM_CACHE_OP_Fast) ||
|
|
+ (opcode == READ_FROM_CACHE_OP_X2) ||
|
|
+ (opcode == READ_FROM_CACHE_OP_X4) ||
|
|
+ (opcode == READ_FROM_CACHE_OP_DUALIO) ||
|
|
+ (opcode == READ_FROM_CACHE_OP_QUADIO));
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int k1x_qspi_check_buswidth(struct k1x_qspi *qspi, u8 width)
|
|
+{
|
|
+ switch (width) {
|
|
+ case 1:
|
|
+ case 2:
|
|
+ case 4:
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ return -ENOTSUPP;
|
|
+}
|
|
+
|
|
+static bool k1x_qspi_supports_op(struct spi_mem *mem,
|
|
+ const struct spi_mem_op *op)
|
|
+{
|
|
+ struct k1x_qspi *qspi = spi_controller_get_devdata(mem->spi->master);
|
|
+ int ret;
|
|
+
|
|
+ mutex_lock(&qspi->lock);
|
|
+ ret = k1x_qspi_check_buswidth(qspi, op->cmd.buswidth);
|
|
+
|
|
+ if (op->addr.nbytes)
|
|
+ ret |= k1x_qspi_check_buswidth(qspi, op->addr.buswidth);
|
|
+
|
|
+ if (op->dummy.nbytes)
|
|
+ ret |= k1x_qspi_check_buswidth(qspi, op->dummy.buswidth);
|
|
+
|
|
+ if (op->data.nbytes)
|
|
+ ret |= k1x_qspi_check_buswidth(qspi, op->data.buswidth);
|
|
+
|
|
+ if (ret) {
|
|
+ mutex_unlock(&qspi->lock);
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ /* address bytes should be equal to or less than 4 bytes */
|
|
+ if (op->addr.nbytes > 4) {
|
|
+ mutex_unlock(&qspi->lock);
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ /* check controller TX/RX buffer limits and alignment */
|
|
+ if (op->data.dir == SPI_MEM_DATA_IN &&
|
|
+ (op->data.nbytes > qspi->rx_unit_size ||
|
|
+ (op->data.nbytes > qspi->rx_buf_size - 4 && !IS_ALIGNED(op->data.nbytes, 4)))) {
|
|
+ mutex_unlock(&qspi->lock);
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ if (op->data.dir == SPI_MEM_DATA_OUT && op->data.nbytes > qspi->tx_unit_size) {
|
|
+ mutex_unlock(&qspi->lock);
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * If requested address value is greater than controller assigned
|
|
+ * memory mapped space, return error as it didn't fit in the range.
|
|
+ */
|
|
+ if (op->addr.val >= qspi->memmap_size) {
|
|
+ pr_err("k1x_qspi_supports_op: addr.val:%lld greater than the map size\n", op->addr.val);
|
|
+ mutex_unlock(&qspi->lock);
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ /* number of dummy clock cycles should be <= 64 cycles */
|
|
+ if (op->dummy.buswidth &&
|
|
+ (op->dummy.nbytes * 8 / op->dummy.buswidth > 64)) {
|
|
+ mutex_unlock(&qspi->lock);
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&qspi->lock);
|
|
+ return true;
|
|
+}
|
|
+
|
|
+static int k1x_qspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
|
|
+{
|
|
+ struct k1x_qspi *qspi = spi_controller_get_devdata(mem->spi->master);
|
|
+ int err = 0;
|
|
+ u32 mask;
|
|
+ u32 reg;
|
|
+ void __iomem *base;
|
|
+
|
|
+ base = qspi->io_map;
|
|
+
|
|
+ mutex_lock(&qspi->lock);
|
|
+
|
|
+ dump_spi_mem_op_info(qspi, op);
|
|
+
|
|
+ /* wait for controller being ready */
|
|
+ mask = QSPI_SR_BUSY | QSPI_SR_IP_ACC_MASK | QSPI_SR_AHB_ACC_MASK;
|
|
+ err = k1x_qspi_readl_poll_tout(qspi, base + QSPI_SR, mask, QSPI_WAIT_TIMEOUT*1000, QSPI_WAIT_BIT_CLEAR);
|
|
+ if (err) {
|
|
+ dev_err(qspi->dev, "controller not ready!\n");
|
|
+ dev_err(qspi->dev, "pmuap[0x%08x]:0x%08x\n", qspi->pmuap_reg, qspi_readl(qspi, qspi->pmuap_addr));
|
|
+ dev_err(qspi->dev, "mpmu[0x%08x]:0x%08x\n", K1X_MPMU_ACGR, qspi_readl(qspi, qspi->mpmu_acgr));
|
|
+ qspi_dump_reg(qspi);
|
|
+ mutex_unlock(&qspi->lock);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ /* clear TX/RX buffer before transaction */
|
|
+ reg = qspi_readl(qspi, base + QSPI_MCR);
|
|
+ reg |= QSPI_MCR_CLR_TXF_MASK | QSPI_MCR_CLR_RXF_MASK;
|
|
+ qspi_writel(qspi, reg, base + QSPI_MCR);
|
|
+
|
|
+ /*
|
|
+ * reset the sequence pointers whenever the sequence ID is changed by
|
|
+ * updating the SEDID filed in QSPI_IPCR OR QSPI_BFGENCR.
|
|
+ */
|
|
+ reg = qspi_readl(qspi, base + QSPI_SPTRCLR);
|
|
+ reg |= (QSPI_SPTRCLR_IPPTRC | QSPI_SPTRCLR_BFPTRC);
|
|
+ qspi_writel(qspi, reg, base + QSPI_SPTRCLR);
|
|
+
|
|
+ /* set the flash address into the QSPI_SFAR */
|
|
+ qspi_write_sfar(qspi, qspi->memmap_base + op->addr.val);
|
|
+
|
|
+ /* clear QSPI_FR before trigger LUT command */
|
|
+ reg = qspi_readl(qspi, base + QSPI_FR);
|
|
+ if (reg)
|
|
+ qspi_writel(qspi, reg, base + QSPI_FR);
|
|
+ qspi->fr_error_flag = 0;
|
|
+
|
|
+ /*
|
|
+ * read page command 13h must be done by IP command.
|
|
+ * read from cache through the AHB bus by accessing the mapped memory.
|
|
+ * In all other cases we use IP commands to access the flash.
|
|
+ */
|
|
+ if (op->data.nbytes > (qspi->rx_buf_size - 4) &&
|
|
+ op->data.dir == SPI_MEM_DATA_IN &&
|
|
+ qspi->ahb_read_enable &&
|
|
+ is_read_from_cache_opcode(op->cmd.opcode)) {
|
|
+ k1x_qspi_prepare_lut(qspi, op, SEQID_LUT_AHBREAD_ID);
|
|
+ err = k1x_qspi_ahb_read(qspi, op);
|
|
+ } else {
|
|
+ /* IP command */
|
|
+ k1x_qspi_prepare_lut(qspi, op, SEQID_LUT_SHARED_ID);
|
|
+ if (op->data.nbytes && op->data.dir == SPI_MEM_DATA_OUT) {
|
|
+ err = k1x_qspi_fill_txfifo(qspi, op);
|
|
+ }
|
|
+ if (!err)
|
|
+ err = k1x_qspi_do_op(qspi, op);
|
|
+ }
|
|
+
|
|
+ /* invalidate the data in the AHB buffer. */
|
|
+ k1x_qspi_invalid(qspi);
|
|
+
|
|
+ mutex_unlock(&qspi->lock);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int k1x_qspi_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op)
|
|
+{
|
|
+ struct k1x_qspi *qspi = spi_controller_get_devdata(mem->spi->master);
|
|
+
|
|
+ mutex_lock(&qspi->lock);
|
|
+ if (op->data.dir == SPI_MEM_DATA_OUT) {
|
|
+ if (op->data.nbytes > qspi->tx_unit_size)
|
|
+ op->data.nbytes = qspi->tx_unit_size;
|
|
+ } else {
|
|
+ if (op->data.nbytes > qspi->rx_unit_size)
|
|
+ op->data.nbytes = qspi->rx_unit_size;
|
|
+ }
|
|
+ mutex_unlock(&qspi->lock);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int k1x_qspi_host_init(struct k1x_qspi *qspi)
|
|
+{
|
|
+ void __iomem *base = qspi->io_map;
|
|
+ u32 reg;
|
|
+
|
|
+ qspi->resets = devm_reset_control_array_get_optional_exclusive(qspi->dev);
|
|
+ if (IS_ERR(qspi->resets)) {
|
|
+ dev_err(qspi->dev, "Failed to get qspi's resets\n");
|
|
+ return PTR_ERR(qspi->resets);
|
|
+ }
|
|
+
|
|
+ /* config mfp */
|
|
+ qspi_config_mfp(qspi);
|
|
+
|
|
+ reset_control_assert(qspi->resets);
|
|
+ /* set PMUap */
|
|
+ qspi_set_func_clk(qspi);
|
|
+ reset_control_deassert(qspi->resets);
|
|
+
|
|
+ /* rest qspi */
|
|
+ qspi_reset(qspi);
|
|
+
|
|
+ /* clock settings */
|
|
+ qspi_enter_mode(qspi, QSPI_DISABLE_MODE);
|
|
+
|
|
+ /* sampled by sfif_clk_b; half cycle delay; */
|
|
+ if (qspi->max_hz < (QSPI_SMPR_FSPHS_CLK >> 2))
|
|
+ qspi_writel(qspi, 0x0, base + QSPI_SMPR);
|
|
+ else
|
|
+ qspi_writel(qspi, QSPI_SMPR_FSPHS_MASK, base + QSPI_SMPR);
|
|
+
|
|
+ /* Fix wirte failure issue*/
|
|
+ qspi_writel(qspi, 0x8, base + QSPI_SOCCR);
|
|
+
|
|
+ /* set the default source address QSPI_AMBA_BASE*/
|
|
+ qspi_write_sfar(qspi, qspi->memmap_base);
|
|
+ qspi_writel(qspi, 0x0, base + QSPI_SFACR);
|
|
+
|
|
+ /* config ahb read */
|
|
+ qspi_init_ahbread(qspi, SEQID_LUT_AHBREAD_ID);
|
|
+
|
|
+ /* set flash memory map */
|
|
+ qspi_writel(qspi, qspi->sfa1ad & 0xfffffc00, base + QSPI_SFA1AD);
|
|
+ qspi_writel(qspi, qspi->sfa2ad & 0xfffffc00, base + QSPI_SFA2AD);
|
|
+ qspi_writel(qspi, qspi->sfb1ad & 0xfffffc00, base + QSPI_SFB1AD);
|
|
+ qspi_writel(qspi, qspi->sfb2ad & 0xfffffc00, base + QSPI_SFB2AD);
|
|
+
|
|
+ /* ISD3FB, ISD2FB, ISD3FA, ISD2FA = 1; END_CFG=0x3 */
|
|
+ reg = qspi_readl(qspi, base + QSPI_MCR);
|
|
+ reg |= QSPI_MCR_END_CFG_MASK | QSPI_MCR_ISD_MASK;
|
|
+ qspi_writel(qspi, reg, base + QSPI_MCR);
|
|
+
|
|
+ /* Module enabled */
|
|
+ qspi_enter_mode(qspi, QSPI_NORMAL_MODE);
|
|
+
|
|
+ /* Read using the IP Bus registers QSPI_RBDR0 to QSPI_RBDR31*/
|
|
+ qspi_write_rbct(qspi, QSPI_RBCT_RXBRD_MASK);
|
|
+
|
|
+ /* clear all interrupt status */
|
|
+ qspi_writel(qspi, 0xffffffff, base + QSPI_FR);
|
|
+
|
|
+ dev_dbg(qspi->dev, "qspi host init done.\n");
|
|
+#ifdef K1X_DUMP_QSPI_REG
|
|
+ qspi_dump_reg(qspi);
|
|
+#endif
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct spi_controller_mem_ops k1x_qspi_mem_ops = {
|
|
+ .adjust_op_size = k1x_qspi_adjust_op_size,
|
|
+ .supports_op = k1x_qspi_supports_op,
|
|
+ .exec_op = k1x_qspi_exec_op,
|
|
+};
|
|
+
|
|
+static int k1x_qspi_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct spi_controller *ctlr;
|
|
+ struct device *dev = &pdev->dev;
|
|
+ struct device_node *np = dev->of_node;
|
|
+ struct k1x_qspi *qspi;
|
|
+ struct resource *res;
|
|
+
|
|
+ int ret = 0;
|
|
+ u32 qspi_bus_num = 0;
|
|
+ int host_irq = 0;
|
|
+
|
|
+ ctlr = spi_alloc_master(&pdev->dev, sizeof(struct k1x_qspi));
|
|
+ if (!ctlr)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ ctlr->mode_bits = SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD ;
|
|
+ qspi = spi_controller_get_devdata(ctlr);
|
|
+ qspi->dev = dev;
|
|
+ qspi->ctrl = ctlr;
|
|
+
|
|
+ platform_set_drvdata(pdev, qspi);
|
|
+
|
|
+ /* get qspi frequency */
|
|
+ if (of_property_read_u32(dev->of_node, "k1x,qspi-freq", &qspi->max_hz)) {
|
|
+ dev_err(dev, "failed to get qspi frequency\n");
|
|
+ goto err_put_ctrl;
|
|
+ }
|
|
+
|
|
+ /* get qspi register base address */
|
|
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi-base");
|
|
+ qspi->io_map = devm_ioremap_resource(dev, res);
|
|
+ if (IS_ERR(qspi->io_map)) {
|
|
+ ret = PTR_ERR(qspi->io_map);
|
|
+ goto err_put_ctrl;
|
|
+ }
|
|
+ qspi->io_phys = res->start;
|
|
+
|
|
+ /* get qspi memory-map address */
|
|
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi-mmap");
|
|
+ qspi->ahb_map = devm_ioremap_resource(dev, res);
|
|
+ if (IS_ERR(qspi->ahb_map)) {
|
|
+ ret = PTR_ERR(qspi->ahb_map);
|
|
+ goto err_put_ctrl;
|
|
+ }
|
|
+
|
|
+ qspi->memmap_base = res->start;
|
|
+ qspi->memmap_size = resource_size(res);
|
|
+
|
|
+ if (of_property_read_u32(dev->of_node, "k1x,qspi-sfa1ad", &qspi->sfa1ad))
|
|
+ qspi->sfa1ad = QSPI_FLASH_A1_TOP;
|
|
+ else
|
|
+ qspi->sfa1ad += qspi->memmap_base;
|
|
+ if (of_property_read_u32(dev->of_node, "k1x,qspi-sfa2ad", &qspi->sfa2ad))
|
|
+ qspi->sfa2ad = QSPI_FLASH_A2_TOP;
|
|
+ else
|
|
+ qspi->sfa2ad += qspi->sfa1ad;
|
|
+ if (of_property_read_u32(dev->of_node, "k1x,qspi-sfb1ad", &qspi->sfb1ad))
|
|
+ qspi->sfb1ad = QSPI_FLASH_B1_TOP;
|
|
+ else
|
|
+ qspi->sfb1ad = qspi->sfa2ad;
|
|
+ if (of_property_read_u32(dev->of_node, "k1x,qspi-sfb2ad", &qspi->sfb2ad))
|
|
+ qspi->sfb2ad = QSPI_FLASH_B2_TOP;
|
|
+ else
|
|
+ qspi->sfb2ad += qspi->sfb1ad;
|
|
+
|
|
+ dev_dbg(dev, "k1x_qspi_probe:memmap base:0x%pa, memmap size:0x%x\n",
|
|
+ &qspi->memmap_base, qspi->memmap_size);
|
|
+
|
|
+ host_irq = platform_get_irq(pdev, 0);
|
|
+ if (host_irq < 0) {
|
|
+ dev_err(dev, "invalid host irq:%d\n", host_irq);
|
|
+ goto err_put_ctrl;
|
|
+ }
|
|
+ ret = devm_request_irq(dev, host_irq, k1x_qspi_irq_handler,
|
|
+ 0, pdev->name, qspi);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "failed to request irq:%d\n", ret);
|
|
+ goto err_put_ctrl;
|
|
+ }
|
|
+ init_completion(&qspi->cmd_completion);
|
|
+ dev_dbg(qspi->dev, "k1x_qspi_probe: host_irq:%d\n", host_irq);
|
|
+
|
|
+ /* map QSPI PMUap register address */
|
|
+ if (of_property_read_u32(dev->of_node, "k1x,qspi-pmuap-reg", &qspi->pmuap_reg)) {
|
|
+ qspi->pmuap_reg = PMUA_QSPI_CLK_RES_CTRL;
|
|
+ }
|
|
+ qspi->pmuap_addr = ioremap(qspi->pmuap_reg, 4);
|
|
+
|
|
+ /* map QSPI MPMU ACGR register address */
|
|
+ if (of_property_read_u32(dev->of_node, "k1x,qspi-mpmu-acgr-reg", &qspi->mpmu_acgr_reg)) {
|
|
+ qspi->mpmu_acgr_reg = K1X_MPMU_ACGR;
|
|
+ }
|
|
+ qspi->mpmu_acgr = ioremap(qspi->mpmu_acgr_reg, 4);
|
|
+
|
|
+ if (of_property_read_u32(dev->of_node, "k1x,qspi-rx-buf", &qspi->rx_buf_size)) {
|
|
+ qspi->rx_buf_size = QSPI_RX_BUFF_MAX;
|
|
+ }
|
|
+
|
|
+ if (of_property_read_u32(dev->of_node, "k1x,qspi-tx-buf", &qspi->tx_buf_size)) {
|
|
+ qspi->tx_buf_size = QSPI_TX_BUFF_MAX;
|
|
+ }
|
|
+
|
|
+ if (of_property_read_u32(dev->of_node, "k1x,qspi-ahb-buf", &qspi->ahb_buf_size)) {
|
|
+ qspi->ahb_buf_size = QSPI_AHB_BUFF_MAX_SIZE;
|
|
+ }
|
|
+
|
|
+ if (of_property_read_u32(dev->of_node, "k1x,qspi-ahb-enable", &qspi->ahb_read_enable)) {
|
|
+ qspi->ahb_read_enable = 1;
|
|
+ }
|
|
+
|
|
+ if (of_property_read_u32(dev->of_node, "k1x,qspi-interrupt", &qspi->cmd_interrupt)) {
|
|
+ qspi->cmd_interrupt = 1;
|
|
+ }
|
|
+
|
|
+ if (of_property_read_u32(dev->of_node, "k1x,qspi-endian-xchg", &qspi->endian_xchg)) {
|
|
+ qspi->endian_xchg = 0;
|
|
+ }
|
|
+
|
|
+ if (of_property_read_u32(dev->of_node, "k1x,qspi-cs", &qspi->cs_selected)) {
|
|
+ qspi->cs_selected = QSPI_DEFAULT_CS;
|
|
+ }
|
|
+
|
|
+ if (of_property_read_u32(dev->of_node, "k1x,qspi-tx-dma", &qspi->tx_dma_enable)) {
|
|
+ qspi->tx_dma_enable = 0;
|
|
+ }
|
|
+
|
|
+ if (of_property_read_u32(dev->of_node, "k1x,qspi-rx-dma", &qspi->rx_dma_enable)) {
|
|
+ qspi->rx_dma_enable = 0;
|
|
+ }
|
|
+
|
|
+ k1x_qspi_prepare_dma(qspi);
|
|
+ mutex_init(&qspi->lock);
|
|
+
|
|
+ /* set the qspi device default index */
|
|
+ if (of_property_read_u32(dev->of_node, "k1x,qspi-id", &qspi_bus_num))
|
|
+ ctlr->bus_num = 0;
|
|
+ else
|
|
+ ctlr->bus_num = qspi_bus_num;
|
|
+ ctlr->num_chipselect = 1;
|
|
+ ctlr->mem_ops = &k1x_qspi_mem_ops;
|
|
+
|
|
+ dev_dbg(dev, "k1x_qspi_probe: rx_buf_size:%d, tx_buf_size:%d\n",
|
|
+ qspi->rx_buf_size, qspi->tx_buf_size);
|
|
+ dev_dbg(dev, "k1x_qspi_probe: ahb_buf_size:%d, ahb_read:%d\n",
|
|
+ qspi->ahb_buf_size, qspi->ahb_read_enable);
|
|
+
|
|
+ if (qspi->tx_dma_enable)
|
|
+ qspi->tx_unit_size = qspi->tx_buf_size;
|
|
+ else
|
|
+ qspi->tx_unit_size = qspi->tx_buf_size;
|
|
+
|
|
+ if (qspi->ahb_read_enable)
|
|
+ qspi->rx_unit_size = SZ_4K;
|
|
+ else
|
|
+ qspi->rx_unit_size = qspi->rx_buf_size;
|
|
+ k1x_qspi_host_init(qspi);
|
|
+
|
|
+ pm_runtime_use_autosuspend(&pdev->dev);
|
|
+ pm_runtime_set_autosuspend_delay(&pdev->dev, QSPI_AUTOSUSPEND_TIMEOUT);
|
|
+ pm_suspend_ignore_children(&pdev->dev, 1);
|
|
+ pm_runtime_enable(&pdev->dev);
|
|
+ pm_runtime_set_active(&pdev->dev);
|
|
+
|
|
+ ctlr->dev.of_node = np;
|
|
+ ctlr->dev.parent = &pdev->dev;
|
|
+ ctlr->use_gpio_descriptors = true;
|
|
+ ctlr->auto_runtime_pm = true;
|
|
+ ret = spi_register_controller(ctlr);
|
|
+ if (ret)
|
|
+ goto err_destroy_mutex;
|
|
+
|
|
+ pm_runtime_put_autosuspend(&pdev->dev);
|
|
+
|
|
+#ifdef CONFIG_SYSFS
|
|
+ ret = sysfs_create_group(&(pdev->dev.kobj),
|
|
+ (const struct attribute_group *)(&qspi_dev_group));
|
|
+ if (ret) {
|
|
+ dev_err(dev,
|
|
+ "failed to create attr group for qspi dev!\n");
|
|
+ goto err_destroy_mutex;
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_destroy_mutex:
|
|
+ pm_runtime_disable(&pdev->dev);
|
|
+ pm_runtime_put_noidle(&pdev->dev);
|
|
+
|
|
+ mutex_destroy(&qspi->lock);
|
|
+ iounmap(qspi->pmuap_addr);
|
|
+
|
|
+err_put_ctrl:
|
|
+ spi_controller_put(ctlr);
|
|
+
|
|
+ dev_err(dev, "K1X QSPI probe failed\n");
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int k1x_qspi_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct k1x_qspi *qspi = platform_get_drvdata(pdev);
|
|
+
|
|
+ pm_runtime_get_sync(&pdev->dev);
|
|
+
|
|
+ /* set disable mode */
|
|
+ qspi_writel(qspi, QSPI_MCR_MDIS_MASK, qspi->io_map + QSPI_MCR);
|
|
+ qspi_writel(qspi, 0x0, qspi->io_map + QSPI_RSER);
|
|
+
|
|
+ pm_runtime_disable(&pdev->dev);
|
|
+ pm_runtime_put_noidle(&pdev->dev);
|
|
+
|
|
+ if (qspi->tx_dma)
|
|
+ dma_release_channel(qspi->tx_dma);
|
|
+ if (qspi->rx_dma)
|
|
+ dma_release_channel(qspi->rx_dma);
|
|
+
|
|
+ mutex_destroy(&qspi->lock);
|
|
+ iounmap(qspi->pmuap_addr);
|
|
+
|
|
+ reset_control_assert(qspi->resets);
|
|
+ clk_disable_unprepare(qspi->clk);
|
|
+ clk_disable_unprepare(qspi->bus_clk);
|
|
+
|
|
+#ifdef CONFIG_SYSFS
|
|
+ sysfs_remove_group(&(pdev->dev.kobj),
|
|
+ (const struct attribute_group *)(&qspi_dev_group));
|
|
+#endif
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_PM_SLEEP
|
|
+static int k1x_qspi_suspend(struct device *dev)
|
|
+{
|
|
+ int ret;
|
|
+ u32 sr;
|
|
+ struct k1x_qspi *qspi = dev_get_drvdata(dev);
|
|
+
|
|
+ pm_runtime_get_sync(qspi->dev);
|
|
+
|
|
+ sr = qspi_readl(qspi, qspi->io_map + QSPI_SR);
|
|
+ if (sr & QSPI_SR_BUSY) {
|
|
+ dev_err(dev, "qspi busy with ongoing cmd\n");
|
|
+ return -EBUSY;
|
|
+ }
|
|
+
|
|
+ ret = pm_runtime_force_suspend(dev);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "failed to suspend(ret:%d)\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int k1x_qspi_resume(struct device *dev)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = pm_runtime_force_resume(dev);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "failed to resume(ret:%d)\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ pm_runtime_mark_last_busy(dev);
|
|
+ pm_runtime_put_autosuspend(dev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+#endif
|
|
+
|
|
+#ifdef CONFIG_PM
|
|
+static int k1x_qspi_runtime_suspend(struct device *dev)
|
|
+{
|
|
+ u32 sr;
|
|
+ struct k1x_qspi *qspi = dev_get_drvdata(dev);
|
|
+
|
|
+ mutex_lock(&qspi->lock);
|
|
+ sr = qspi_readl(qspi, qspi->io_map + QSPI_SR);
|
|
+ if (sr & QSPI_SR_BUSY) {
|
|
+ dev_err(dev, "qspi busy with ongoing cmd\n");
|
|
+ mutex_unlock(&qspi->lock);
|
|
+ return -EBUSY;
|
|
+ }
|
|
+ qspi_enter_mode(qspi, QSPI_DISABLE_MODE);
|
|
+ mutex_unlock(&qspi->lock);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int k1x_qspi_runtime_resume(struct device *dev)
|
|
+{
|
|
+ struct k1x_qspi *qspi = dev_get_drvdata(dev);
|
|
+
|
|
+ qspi_enter_mode(qspi, QSPI_NORMAL_MODE);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct dev_pm_ops k1x_qspi_pmops = {
|
|
+ SET_SYSTEM_SLEEP_PM_OPS(k1x_qspi_suspend, k1x_qspi_resume)
|
|
+ SET_RUNTIME_PM_OPS(k1x_qspi_runtime_suspend,
|
|
+ k1x_qspi_runtime_resume, NULL)
|
|
+};
|
|
+
|
|
+#define K1X_QSPI_PMOPS (&k1x_qspi_pmops)
|
|
+
|
|
+#else
|
|
+#define K1X_QSPI_PMOPS NULL
|
|
+#endif
|
|
+
|
|
+static const struct of_device_id k1x_qspi_dt_ids[] = {
|
|
+ { .compatible = "spacemit,k1x-qspi", },
|
|
+ {}
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, k1x_qspi_dt_ids);
|
|
+
|
|
+static struct platform_driver k1x_qspi_driver = {
|
|
+ .driver = {
|
|
+ .name = "k1x-qspi",
|
|
+ .of_match_table = k1x_qspi_dt_ids,
|
|
+ .pm = K1X_QSPI_PMOPS,
|
|
+ },
|
|
+ .probe = k1x_qspi_probe,
|
|
+ .remove = k1x_qspi_remove,
|
|
+};
|
|
+module_platform_driver(k1x_qspi_driver);
|
|
+
|
|
+MODULE_AUTHOR("Spacemit");
|
|
+MODULE_DESCRIPTION("Spacemit k1x qspi controller driver");
|
|
+MODULE_LICENSE("GPL v2");
|
|
diff --git a/drivers/spi/spi-k1x.c b/drivers/spi/spi-k1x.c
|
|
new file mode 100644
|
|
index 000000000000..111111111111
|
|
--- /dev/null
|
|
+++ b/drivers/spi/spi-k1x.c
|
|
@@ -0,0 +1,1189 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Support for Spacemit k1x spi controller
|
|
+ *
|
|
+ * Copyright (c) 2023, spacemit Corporation.
|
|
+ *
|
|
+ */
|
|
+
|
|
+#include <linux/err.h>
|
|
+#include <linux/bitops.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/device.h>
|
|
+#include <linux/ioport.h>
|
|
+#include <linux/errno.h>
|
|
+#include <linux/err.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/pci.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/spi/spi.h>
|
|
+#include <linux/of_device.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/gpio.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/clk.h>
|
|
+#include <linux/pm_runtime.h>
|
|
+#include <linux/acpi.h>
|
|
+
|
|
+#include "spi-k1x.h"
|
|
+
|
|
+#define TIMOUT_DFLT 3000
|
|
+#define TIMOUT_DFLT_SLAVE 0x40000
|
|
+
|
|
+//#define CONFIG_K1X_SSP_DEBUG 1
|
|
+
|
|
+static bool k1x_spi_txfifo_full(const struct spi_driver_data *drv_data)
|
|
+{
|
|
+ return !(k1x_spi_read(drv_data, STATUS) & STATUS_TNF);
|
|
+}
|
|
+
|
|
+static u32 k1x_configure_topctrl(const struct spi_driver_data *drv_data, u8 bits)
|
|
+{
|
|
+ /*
|
|
+ * set Motorola Frame Format
|
|
+ * set DSS
|
|
+ */
|
|
+ return TOP_FRF_Motorola | TOP_DSS(bits);
|
|
+}
|
|
+
|
|
+static void set_dvfm_constraint(struct spi_driver_data *drv_data)
|
|
+{
|
|
+#if 0
|
|
+ if (drv_data->qos_idle_value != PM_QOS_CPUIDLE_BLOCK_DEFAULT_VALUE)
|
|
+ freq_qos_update_request(&drv_data->qos_idle,
|
|
+ drv_data->qos_idle_value);
|
|
+#endif
|
|
+}
|
|
+
|
|
+static void unset_dvfm_constraint(struct spi_driver_data *drv_data)
|
|
+{
|
|
+#if 0
|
|
+ if (drv_data->qos_idle_value != PM_QOS_CPUIDLE_BLOCK_DEFAULT_VALUE)
|
|
+ freq_qos_update_request(&drv_data->qos_idle,
|
|
+ PM_QOS_CPUIDLE_BLOCK_DEFAULT_VALUE);
|
|
+#endif
|
|
+}
|
|
+
|
|
+static void init_dvfm_constraint(struct spi_driver_data *drv_data)
|
|
+{
|
|
+#if 0
|
|
+#ifdef CONFIG_PM
|
|
+ struct freq_constraints *idle_qos;
|
|
+
|
|
+ idle_qos = cpuidle_get_constraints();
|
|
+
|
|
+ freq_qos_add_request(idle_qos, &drv_data->qos_idle, FREQ_QOS_MAX,
|
|
+ PM_QOS_CPUIDLE_BLOCK_DEFAULT_VALUE);
|
|
+#endif
|
|
+#endif
|
|
+}
|
|
+
|
|
+static void deinit_dvfm_constraint(struct spi_driver_data *drv_data)
|
|
+{
|
|
+#if 0
|
|
+#ifdef CONFIG_PM
|
|
+ freq_qos_remove_request(&drv_data->qos_idle);
|
|
+#endif
|
|
+#endif
|
|
+}
|
|
+
|
|
+static void cs_assert(struct spi_driver_data *drv_data)
|
|
+{
|
|
+ struct chip_data *chip = drv_data->cur_chip;
|
|
+
|
|
+ if (chip->cs_control) {
|
|
+ chip->cs_control(K1X_CS_ASSERT);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (gpio_is_valid(chip->gpio_cs)) {
|
|
+ gpio_set_value(chip->gpio_cs, chip->gpio_cs_inverted);
|
|
+ return;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void cs_deassert(struct spi_driver_data *drv_data)
|
|
+{
|
|
+ struct chip_data *chip = drv_data->cur_chip;
|
|
+
|
|
+ if (chip->cs_control) {
|
|
+ chip->cs_control(K1X_CS_DEASSERT);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (gpio_is_valid(chip->gpio_cs)) {
|
|
+ gpio_set_value(chip->gpio_cs, !chip->gpio_cs_inverted);
|
|
+ return;
|
|
+ }
|
|
+}
|
|
+
|
|
+/* clear all rx fifo useless data */
|
|
+int k1x_spi_flush(struct spi_driver_data *drv_data)
|
|
+{
|
|
+ unsigned long limit = loops_per_jiffy << 1;
|
|
+
|
|
+ do {
|
|
+ while (k1x_spi_read(drv_data, STATUS) & STATUS_RNE)
|
|
+ k1x_spi_read(drv_data, DATAR);
|
|
+ } while ((k1x_spi_read(drv_data, STATUS) & STATUS_BSY) && --limit);
|
|
+ k1x_spi_write(drv_data, STATUS, STATUS_ROR);
|
|
+
|
|
+ return limit;
|
|
+}
|
|
+
|
|
+static int null_writer(struct spi_driver_data *drv_data)
|
|
+{
|
|
+ u8 n_bytes = drv_data->n_bytes;
|
|
+
|
|
+ if (k1x_spi_txfifo_full(drv_data)
|
|
+ || (drv_data->tx == drv_data->tx_end))
|
|
+ return 0;
|
|
+
|
|
+ k1x_spi_write(drv_data, DATAR, 0);
|
|
+ drv_data->tx += n_bytes;
|
|
+
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+static int null_reader(struct spi_driver_data *drv_data)
|
|
+{
|
|
+ u8 n_bytes = drv_data->n_bytes;
|
|
+
|
|
+ while ((k1x_spi_read(drv_data, STATUS) & STATUS_RNE)
|
|
+ && (drv_data->rx < drv_data->rx_end)) {
|
|
+ k1x_spi_read(drv_data, DATAR);
|
|
+ drv_data->rx += n_bytes;
|
|
+ }
|
|
+
|
|
+ return drv_data->rx == drv_data->rx_end;
|
|
+}
|
|
+
|
|
+static int u8_writer(struct spi_driver_data *drv_data)
|
|
+{
|
|
+ if (k1x_spi_txfifo_full(drv_data)
|
|
+ || (drv_data->tx == drv_data->tx_end))
|
|
+ return 0;
|
|
+
|
|
+ k1x_spi_write(drv_data, DATAR, *(u8 *)(drv_data->tx));
|
|
+ ++drv_data->tx;
|
|
+
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+static int u8_reader(struct spi_driver_data *drv_data)
|
|
+{
|
|
+ while ((k1x_spi_read(drv_data, STATUS) & STATUS_RNE)
|
|
+ && (drv_data->rx < drv_data->rx_end)) {
|
|
+ *(u8 *)(drv_data->rx) = k1x_spi_read(drv_data, DATAR);
|
|
+ ++drv_data->rx;
|
|
+ }
|
|
+
|
|
+ return drv_data->rx == drv_data->rx_end;
|
|
+}
|
|
+
|
|
+static int u16_writer(struct spi_driver_data *drv_data)
|
|
+{
|
|
+ if (k1x_spi_txfifo_full(drv_data)
|
|
+ || (drv_data->tx == drv_data->tx_end))
|
|
+ return 0;
|
|
+
|
|
+ k1x_spi_write(drv_data, DATAR, *(u16 *)(drv_data->tx));
|
|
+ drv_data->tx += 2;
|
|
+
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+static int u16_reader(struct spi_driver_data *drv_data)
|
|
+{
|
|
+ while ((k1x_spi_read(drv_data, STATUS) & STATUS_RNE)
|
|
+ && (drv_data->rx < drv_data->rx_end)) {
|
|
+ *(u16 *)(drv_data->rx) = k1x_spi_read(drv_data, DATAR);
|
|
+ drv_data->rx += 2;
|
|
+ }
|
|
+
|
|
+ return drv_data->rx == drv_data->rx_end;
|
|
+}
|
|
+
|
|
+static int u32_writer(struct spi_driver_data *drv_data)
|
|
+{
|
|
+ if (k1x_spi_txfifo_full(drv_data)
|
|
+ || (drv_data->tx == drv_data->tx_end))
|
|
+ return 0;
|
|
+
|
|
+ k1x_spi_write(drv_data, DATAR, *(u32 *)(drv_data->tx));
|
|
+ drv_data->tx += 4;
|
|
+
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+static int u32_reader(struct spi_driver_data *drv_data)
|
|
+{
|
|
+ while ((k1x_spi_read(drv_data, STATUS) & STATUS_RNE)
|
|
+ && (drv_data->rx < drv_data->rx_end)) {
|
|
+ *(u32 *)(drv_data->rx) = k1x_spi_read(drv_data, DATAR);
|
|
+ drv_data->rx += 4;
|
|
+ }
|
|
+
|
|
+ return drv_data->rx == drv_data->rx_end;
|
|
+}
|
|
+
|
|
+void *k1x_spi_next_transfer(struct spi_driver_data *drv_data)
|
|
+{
|
|
+ struct spi_message *msg = drv_data->cur_msg;
|
|
+ struct spi_transfer *trans = drv_data->cur_transfer;
|
|
+
|
|
+ /* Move to next transfer */
|
|
+ if (trans->transfer_list.next != &msg->transfers) {
|
|
+ drv_data->cur_transfer =
|
|
+ list_entry(trans->transfer_list.next,
|
|
+ struct spi_transfer,
|
|
+ transfer_list);
|
|
+ return RUNNING_STATE;
|
|
+ } else
|
|
+ return DONE_STATE;
|
|
+}
|
|
+
|
|
+/* caller already set message->status; dma and pio irqs are blocked */
|
|
+static void giveback(struct spi_driver_data *drv_data)
|
|
+{
|
|
+ struct spi_transfer* last_transfer;
|
|
+ struct spi_message *msg;
|
|
+
|
|
+ msg = drv_data->cur_msg;
|
|
+ drv_data->cur_msg = NULL;
|
|
+ drv_data->cur_transfer = NULL;
|
|
+
|
|
+ last_transfer = list_last_entry(&msg->transfers, struct spi_transfer,
|
|
+ transfer_list);
|
|
+
|
|
+ /* Delay if requested before any change in chip select */
|
|
+ spi_transfer_delay_exec(last_transfer);
|
|
+
|
|
+ /* Drop chip select UNLESS cs_change is true or we are returning
|
|
+ * a message with an error, or next message is for another chip
|
|
+ */
|
|
+ if (!last_transfer->cs_change)
|
|
+ cs_deassert(drv_data);
|
|
+ else {
|
|
+ struct spi_message *next_msg;
|
|
+
|
|
+ /* Holding of cs was hinted, but we need to make sure
|
|
+ * the next message is for the same chip. Don't waste
|
|
+ * time with the following tests unless this was hinted.
|
|
+ *
|
|
+ * We cannot postpone this until pump_messages, because
|
|
+ * after calling msg->complete (below) the driver that
|
|
+ * sent the current message could be unloaded, which
|
|
+ * could invalidate the cs_control() callback...
|
|
+ */
|
|
+
|
|
+ /* get a pointer to the next message, if any */
|
|
+ next_msg = spi_get_next_queued_message(drv_data->master);
|
|
+
|
|
+ /* see if the next and current messages point
|
|
+ * to the same chip
|
|
+ */
|
|
+ if (next_msg && next_msg->spi != msg->spi)
|
|
+ next_msg = NULL;
|
|
+ if (!next_msg || msg->state == ERROR_STATE)
|
|
+ cs_deassert(drv_data);
|
|
+ }
|
|
+
|
|
+ drv_data->cur_chip = NULL;
|
|
+ spi_finalize_current_message(drv_data->master);
|
|
+ unset_dvfm_constraint(drv_data);
|
|
+
|
|
+ if (drv_data->slave_mode)
|
|
+ del_timer(&drv_data->slave_rx_timer);
|
|
+ complete(&drv_data->cur_msg_completion);
|
|
+}
|
|
+
|
|
+static void reset_fifo_ctrl(struct spi_driver_data *drv_data)
|
|
+{
|
|
+ struct chip_data *chip = drv_data->cur_chip;
|
|
+ u32 fifo_ctrl = 0;
|
|
+
|
|
+ fifo_ctrl |= chip->threshold;
|
|
+ k1x_spi_write(drv_data, FIFO_CTRL, fifo_ctrl);
|
|
+}
|
|
+
|
|
+static void reset_int_en(struct spi_driver_data *drv_data)
|
|
+{
|
|
+ u32 int_en = 0;
|
|
+
|
|
+ int_en = k1x_spi_read(drv_data, INT_EN);
|
|
+ int_en &= ~drv_data->int_cr;
|
|
+ k1x_spi_write(drv_data, INT_EN, int_en);
|
|
+}
|
|
+
|
|
+static void int_error_stop(struct spi_driver_data *drv_data, const char* msg)
|
|
+{
|
|
+ /* Stop and reset SSP */
|
|
+ k1x_spi_write(drv_data, STATUS, drv_data->clear_sr);
|
|
+ reset_fifo_ctrl(drv_data);
|
|
+ reset_int_en(drv_data);
|
|
+ k1x_spi_write(drv_data, TO, 0);
|
|
+ k1x_spi_flush(drv_data);
|
|
+ k1x_spi_write(drv_data, TOP_CTRL,
|
|
+ k1x_spi_read(drv_data, TOP_CTRL) & ~(TOP_SSE | TOP_HOLD_FRAME_LOW));
|
|
+ dev_err(&drv_data->pdev->dev, "%s\n", msg);
|
|
+
|
|
+ drv_data->cur_msg->state = ERROR_STATE;
|
|
+ queue_work(system_wq, &drv_data->pump_transfers);
|
|
+}
|
|
+
|
|
+static void int_transfer_complete(struct spi_driver_data *drv_data)
|
|
+{
|
|
+ /* Stop SSP */
|
|
+ k1x_spi_write(drv_data, STATUS, drv_data->clear_sr);
|
|
+ reset_fifo_ctrl(drv_data);
|
|
+ reset_int_en(drv_data);
|
|
+ k1x_spi_write(drv_data, TO, 0);
|
|
+
|
|
+ /* Update total byte transferred return count actual bytes read */
|
|
+ drv_data->cur_msg->actual_length += drv_data->len -
|
|
+ (drv_data->rx_end - drv_data->rx);
|
|
+
|
|
+ /* Transfer delays and chip select release are
|
|
+ * handled in pump_transfers or giveback
|
|
+ */
|
|
+
|
|
+ /* Move to next transfer */
|
|
+ drv_data->cur_msg->state = k1x_spi_next_transfer(drv_data);
|
|
+
|
|
+ /* Schedule transfer tasklet */
|
|
+ queue_work(system_wq, &drv_data->pump_transfers);
|
|
+}
|
|
+
|
|
+static irqreturn_t interrupt_transfer(struct spi_driver_data *drv_data)
|
|
+{
|
|
+ u32 irq_mask = (k1x_spi_read(drv_data, INT_EN) & INT_EN_TIE) ?
|
|
+ drv_data->mask_sr : drv_data->mask_sr & ~STATUS_TFS;
|
|
+
|
|
+ u32 irq_status = k1x_spi_read(drv_data, STATUS) & irq_mask;
|
|
+
|
|
+ if (irq_status & STATUS_ROR) {
|
|
+ int_error_stop(drv_data, "interrupt_transfer: fifo overrun");
|
|
+ return IRQ_HANDLED;
|
|
+ }
|
|
+
|
|
+ if (irq_status & STATUS_TINT) {
|
|
+ k1x_spi_write(drv_data, STATUS, STATUS_TINT);
|
|
+ if (drv_data->read(drv_data)) {
|
|
+ int_transfer_complete(drv_data);
|
|
+ return IRQ_HANDLED;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Drain rx fifo, Fill tx fifo and prevent overruns */
|
|
+ do {
|
|
+ if (drv_data->read(drv_data)) {
|
|
+ int_transfer_complete(drv_data);
|
|
+ return IRQ_HANDLED;
|
|
+ }
|
|
+ } while (drv_data->write(drv_data));
|
|
+
|
|
+ if (drv_data->read(drv_data)) {
|
|
+ int_transfer_complete(drv_data);
|
|
+ return IRQ_HANDLED;
|
|
+ }
|
|
+
|
|
+ if (drv_data->tx == drv_data->tx_end) {
|
|
+ u32 int_en;
|
|
+
|
|
+ int_en = k1x_spi_read(drv_data, INT_EN);
|
|
+ int_en &= ~INT_EN_TIE;
|
|
+
|
|
+ k1x_spi_write(drv_data, INT_EN, int_en);
|
|
+ }
|
|
+
|
|
+ /* We did something */
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+static irqreturn_t ssp_int(int irq, void *dev_id)
|
|
+{
|
|
+ struct spi_driver_data *drv_data = dev_id;
|
|
+ u32 int_en;
|
|
+ u32 mask = drv_data->mask_sr;
|
|
+ u32 int_status;
|
|
+
|
|
+ /*
|
|
+ * The IRQ might be shared with other peripherals so we must first
|
|
+ * check that are we RPM suspended or not. If we are we assume that
|
|
+ * the IRQ was not for us (we shouldn't be RPM suspended when the
|
|
+ * interrupt is enabled).
|
|
+ */
|
|
+ if (pm_runtime_suspended(&drv_data->pdev->dev))
|
|
+ return IRQ_NONE;
|
|
+
|
|
+ /*
|
|
+ * If the device is not yet in RPM suspended state and we get an
|
|
+ * interrupt that is meant for another device, check if status bits
|
|
+ * are all set to one. That means that the device is already
|
|
+ * powered off.
|
|
+ */
|
|
+ int_status = k1x_spi_read(drv_data, STATUS);
|
|
+ if (int_status == ~0)
|
|
+ return IRQ_NONE;
|
|
+
|
|
+ int_en = k1x_spi_read(drv_data, INT_EN);
|
|
+
|
|
+ /* Ignore possible writes if we don't need to write */
|
|
+ if (!(int_en & INT_EN_TIE))
|
|
+ mask &= ~STATUS_TFS;
|
|
+
|
|
+ /* Ignore RX timeout interrupt if it is disabled */
|
|
+ if (!(int_en & INT_EN_TINTE))
|
|
+ mask &= ~STATUS_TINT;
|
|
+
|
|
+ if (!(int_status & mask))
|
|
+ return IRQ_NONE;
|
|
+
|
|
+ if (!drv_data->cur_msg) {
|
|
+
|
|
+ k1x_spi_write(drv_data, TOP_CTRL,
|
|
+ k1x_spi_read(drv_data, TOP_CTRL)
|
|
+ & ~(TOP_SSE | TOP_HOLD_FRAME_LOW));
|
|
+ k1x_spi_write(drv_data, INT_EN,
|
|
+ k1x_spi_read(drv_data, INT_EN)
|
|
+ & ~drv_data->int_cr);
|
|
+ k1x_spi_write(drv_data, TO, 0);
|
|
+ k1x_spi_write(drv_data, STATUS, drv_data->clear_sr);
|
|
+
|
|
+ dev_err(&drv_data->pdev->dev,
|
|
+ "bad message state in interrupt handler\n");
|
|
+
|
|
+ /* Never fail */
|
|
+ return IRQ_HANDLED;
|
|
+ }
|
|
+
|
|
+ return drv_data->transfer_handler(drv_data);
|
|
+}
|
|
+
|
|
+static void slave_rx_timer_expired(struct timer_list *t) {
|
|
+ struct spi_driver_data *drv_data = from_timer(drv_data, t, slave_rx_timer);
|
|
+#ifdef CONFIG_K1X_SSP_DEBUG
|
|
+ pr_err("%s\n", __func__);
|
|
+ pr_err("spi top = 0x%x\n", k1x_spi_read(drv_data, TOP_CTRL));
|
|
+ pr_err("fifo = 0x%x\n", k1x_spi_read(drv_data, FIFO_CTRL));
|
|
+ pr_err("int_en = 0x%x\n", k1x_spi_read(drv_data, INT_EN));
|
|
+ pr_err("to = 0x%x\n", k1x_spi_read(drv_data, TO));
|
|
+#endif
|
|
+ k1x_spi_slave_sw_timeout_callback(drv_data);
|
|
+}
|
|
+
|
|
+static void pump_transfers(struct work_struct *work)
|
|
+{
|
|
+ struct spi_driver_data *drv_data = container_of(work, struct spi_driver_data, pump_transfers);
|
|
+ struct spi_message *message = NULL;
|
|
+ struct spi_transfer *transfer = NULL;
|
|
+ struct spi_transfer *previous = NULL;
|
|
+ struct chip_data *chip = NULL;
|
|
+ u8 bits = 0;
|
|
+ u32 top_ctrl;
|
|
+ u32 fifo_ctrl;
|
|
+ u32 int_en = 0;
|
|
+ u32 dma_thresh = drv_data->cur_chip->dma_threshold;
|
|
+ u32 dma_burst = drv_data->cur_chip->dma_burst_size;
|
|
+
|
|
+ if (drv_data->slave_mode)
|
|
+ mod_timer(&drv_data->slave_rx_timer,
|
|
+ jiffies + msecs_to_jiffies(1000));
|
|
+
|
|
+ /* Get current state information */
|
|
+ message = drv_data->cur_msg;
|
|
+ transfer = drv_data->cur_transfer;
|
|
+ chip = drv_data->cur_chip;
|
|
+
|
|
+ /* Handle for abort */
|
|
+ if (message->state == ERROR_STATE) {
|
|
+ message->status = -EIO;
|
|
+ giveback(drv_data);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* Handle end of message */
|
|
+ if (message->state == DONE_STATE) {
|
|
+ message->status = 0;
|
|
+ giveback(drv_data);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* Delay if requested at end of transfer before CS change */
|
|
+ if (message->state == RUNNING_STATE) {
|
|
+ previous = list_entry(transfer->transfer_list.prev,
|
|
+ struct spi_transfer,
|
|
+ transfer_list);
|
|
+ spi_transfer_delay_exec(previous);
|
|
+
|
|
+ /* Drop chip select only if cs_change is requested */
|
|
+ if (previous->cs_change)
|
|
+ cs_deassert(drv_data);
|
|
+ }
|
|
+
|
|
+ /* Check if we can DMA this transfer */
|
|
+ if (!k1x_spi_dma_is_possible(transfer->len) && chip->enable_dma) {
|
|
+ /* reject already-mapped transfers; PIO won't always work */
|
|
+ if (message->is_dma_mapped
|
|
+ || transfer->rx_dma || transfer->tx_dma) {
|
|
+ dev_err(&drv_data->pdev->dev,
|
|
+ "pump_transfers: mapped transfer length of "
|
|
+ "%u is greater than %d\n",
|
|
+ transfer->len, MAX_DMA_LEN);
|
|
+ message->status = -EINVAL;
|
|
+ giveback(drv_data);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* warn ... we force this to PIO mode */
|
|
+ dev_warn_ratelimited(&message->spi->dev,
|
|
+ "pump_transfers: DMA disabled for transfer length %ld "
|
|
+ "greater than %d\n",
|
|
+ (long)drv_data->len, MAX_DMA_LEN);
|
|
+ }
|
|
+
|
|
+ /* Setup the transfer state based on the type of transfer */
|
|
+ if (k1x_spi_flush(drv_data) == 0) {
|
|
+ dev_err(&drv_data->pdev->dev, "pump_transfers: flush failed\n");
|
|
+ message->status = -EIO;
|
|
+ giveback(drv_data);
|
|
+ return;
|
|
+ }
|
|
+ drv_data->n_bytes = chip->n_bytes;
|
|
+ drv_data->tx = (void *)transfer->tx_buf;
|
|
+ drv_data->tx_end = drv_data->tx + transfer->len;
|
|
+ drv_data->rx = transfer->rx_buf;
|
|
+ drv_data->rx_end = drv_data->rx + transfer->len;
|
|
+ drv_data->rx_dma = transfer->rx_dma;
|
|
+ drv_data->tx_dma = transfer->tx_dma;
|
|
+ drv_data->len = transfer->len;
|
|
+ drv_data->write = drv_data->tx ? chip->write : null_writer;
|
|
+ drv_data->read = drv_data->rx ? chip->read : null_reader;
|
|
+
|
|
+ /* Change speed and bit per word on a per transfer */
|
|
+ bits = transfer->bits_per_word;
|
|
+
|
|
+ if (bits <= 8) {
|
|
+ drv_data->n_bytes = 1;
|
|
+ drv_data->read = drv_data->read != null_reader ?
|
|
+ u8_reader : null_reader;
|
|
+ drv_data->write = drv_data->write != null_writer ?
|
|
+ u8_writer : null_writer;
|
|
+ } else if (bits <= 16) {
|
|
+ drv_data->n_bytes = 2;
|
|
+ drv_data->read = drv_data->read != null_reader ?
|
|
+ u16_reader : null_reader;
|
|
+ drv_data->write = drv_data->write != null_writer ?
|
|
+ u16_writer : null_writer;
|
|
+ } else if (bits <= 32) {
|
|
+ drv_data->n_bytes = 4;
|
|
+ drv_data->read = drv_data->read != null_reader ?
|
|
+ u32_reader : null_reader;
|
|
+ drv_data->write = drv_data->write != null_writer ?
|
|
+ u32_writer : null_writer;
|
|
+ }
|
|
+ /*
|
|
+ * if bits/word is changed in dma mode, then must check the
|
|
+ * thresholds and burst also
|
|
+ */
|
|
+ if (chip->enable_dma) {
|
|
+ if (k1x_spi_set_dma_burst_and_threshold(chip,
|
|
+ message->spi,
|
|
+ bits, &dma_burst,
|
|
+ &dma_thresh))
|
|
+ dev_warn_ratelimited(&message->spi->dev,
|
|
+ "pump_transfers: DMA burst size reduced to match bits_per_word\n");
|
|
+ }
|
|
+
|
|
+ top_ctrl = k1x_configure_topctrl(drv_data, bits);
|
|
+ dev_dbg(&message->spi->dev, "%u Hz, %s\n",
|
|
+ drv_data->master->max_speed_hz,
|
|
+ chip->enable_dma ? "DMA" : "PIO");
|
|
+ top_ctrl |= chip->top_ctrl;
|
|
+ fifo_ctrl = chip->fifo_ctrl;
|
|
+
|
|
+ if (drv_data->ssp_enhancement) {
|
|
+ /*
|
|
+ * If transfer length is times of 4, then use
|
|
+ * 32 bit fifo width with endian swap support
|
|
+ */
|
|
+ if (drv_data->len % 4 == 0 && transfer->bits_per_word <= 16) {
|
|
+ if (transfer->bits_per_word <= 8)
|
|
+ fifo_ctrl |= FIFO_WR_ENDIAN_8BITS |
|
|
+ FIFO_RD_ENDIAN_8BITS;
|
|
+ else if (transfer->bits_per_word <= 16)
|
|
+ fifo_ctrl |= FIFO_WR_ENDIAN_16BITS |
|
|
+ FIFO_RD_ENDIAN_16BITS;
|
|
+ bits = 32;
|
|
+ drv_data->n_bytes = 4;
|
|
+ if(transfer->rx_buf)
|
|
+ drv_data->read = u32_reader;
|
|
+ if(transfer->tx_buf)
|
|
+ drv_data->write = u32_writer;
|
|
+
|
|
+ if (chip->enable_dma) {
|
|
+ if (k1x_spi_set_dma_burst_and_threshold(chip,
|
|
+ message->spi,
|
|
+ bits, &dma_burst,
|
|
+ &dma_thresh))
|
|
+ dev_warn_ratelimited(&message->spi->dev,
|
|
+ "pump_transfers:"
|
|
+ "DMA burst size reduced to"
|
|
+ "match bits_per_word\n");
|
|
+ }
|
|
+
|
|
+ top_ctrl &= ~TOP_DSS_MASK;
|
|
+ top_ctrl |= TOP_DSS(32);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ message->state = RUNNING_STATE;
|
|
+
|
|
+ drv_data->dma_mapped = 0;
|
|
+ if (k1x_spi_dma_is_possible(drv_data->len))
|
|
+ drv_data->dma_mapped = k1x_spi_map_dma_buffers(drv_data);
|
|
+ if (drv_data->dma_mapped) {
|
|
+ /* Ensure we have the correct interrupt handler */
|
|
+ drv_data->transfer_handler = k1x_spi_dma_transfer;
|
|
+
|
|
+ k1x_spi_dma_prepare(drv_data, dma_burst);
|
|
+
|
|
+ /* Clear status and start DMA engine */
|
|
+ fifo_ctrl |= chip->fifo_ctrl | dma_thresh | drv_data->dma_fifo_ctrl;
|
|
+ top_ctrl |= chip->top_ctrl | drv_data->dma_top_ctrl;
|
|
+ k1x_spi_write(drv_data, STATUS, drv_data->clear_sr);
|
|
+ k1x_spi_dma_start(drv_data);
|
|
+ int_en = k1x_spi_read(drv_data, INT_EN) | drv_data->dma_cr;
|
|
+ } else {
|
|
+ /* Ensure we have the correct interrupt handler */
|
|
+ drv_data->transfer_handler = interrupt_transfer;
|
|
+
|
|
+ fifo_ctrl = fifo_ctrl | chip->fifo_ctrl | chip->threshold;
|
|
+ int_en = k1x_spi_read(drv_data, INT_EN) | drv_data->int_cr;
|
|
+ k1x_spi_write(drv_data, STATUS, drv_data->clear_sr);
|
|
+ }
|
|
+
|
|
+ k1x_spi_write(drv_data, TO, chip->timeout);
|
|
+
|
|
+ cs_assert(drv_data);
|
|
+
|
|
+ /*
|
|
+ * TODO: refine these logic
|
|
+ * k1x_spi_get_ssrc1_change_mask
|
|
+ * if ((k1x_spi_read(drv_data, SSCR0) != cr0)
|
|
+ * cs_assert(drv_data);
|
|
+ * k1x_spi_write(drv_data, SSCR1, cr1);
|
|
+ */
|
|
+
|
|
+ set_dvfm_constraint(drv_data); /*disable system to idle while DMA */
|
|
+ if (drv_data->slave_mode)
|
|
+ top_ctrl |= TOP_SSE | TOP_SCLKDIR | TOP_SFRMDIR;
|
|
+ else
|
|
+ top_ctrl |= TOP_HOLD_FRAME_LOW;
|
|
+ /*
|
|
+ * This part changed the logic
|
|
+ * 1. clear SSE
|
|
+ * 2. write TOP_CTRL and other register
|
|
+ * 3. set SSE in the end of this function
|
|
+ */
|
|
+ top_ctrl &= ~TOP_SSE;
|
|
+ k1x_spi_write(drv_data, TOP_CTRL, top_ctrl);
|
|
+ k1x_spi_write(drv_data, FIFO_CTRL, fifo_ctrl);
|
|
+ k1x_spi_write(drv_data, INT_EN, int_en);
|
|
+ top_ctrl |= TOP_SSE;
|
|
+#ifdef CONFIG_K1X_SSP_DEBUG
|
|
+ dev_err(&message->spi->dev, "spi top = 0x%x\n", top_ctrl);
|
|
+ dev_err(&message->spi->dev, "fifo = 0x%x\n", k1x_spi_read(drv_data, FIFO_CTRL));
|
|
+ dev_err(&message->spi->dev, "int_en = 0x%x\n", k1x_spi_read(drv_data, INT_EN));
|
|
+ dev_err(&message->spi->dev, "to = 0x%x\n", k1x_spi_read(drv_data, TO));
|
|
+#endif
|
|
+ k1x_spi_write(drv_data, TOP_CTRL, top_ctrl);
|
|
+}
|
|
+
|
|
+static int k1x_spi_transfer_one_message(struct spi_master *master,
|
|
+ struct spi_message *msg)
|
|
+{
|
|
+ struct spi_driver_data *drv_data = spi_master_get_devdata(master);
|
|
+
|
|
+ drv_data->cur_msg = msg;
|
|
+ /* Initial message state*/
|
|
+ drv_data->cur_msg->state = START_STATE;
|
|
+ drv_data->cur_transfer = list_entry(drv_data->cur_msg->transfers.next,
|
|
+ struct spi_transfer,
|
|
+ transfer_list);
|
|
+
|
|
+ /*
|
|
+ * prepare to setup the SSP, in pump_transfers, using the per
|
|
+ * chip configuration
|
|
+ */
|
|
+ drv_data->cur_chip = spi_get_ctldata(drv_data->cur_msg->spi);
|
|
+
|
|
+ if (master->max_speed_hz != drv_data->cur_transfer->speed_hz) {
|
|
+ master->max_speed_hz = drv_data->cur_transfer->speed_hz;
|
|
+ clk_set_rate(drv_data->clk, master->max_speed_hz);
|
|
+ }
|
|
+
|
|
+ reinit_completion(&drv_data->cur_msg_completion);
|
|
+ /* Mark as busy and launch transfers */
|
|
+ queue_work(system_wq, &drv_data->pump_transfers);
|
|
+ wait_for_completion(&drv_data->cur_msg_completion);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int k1x_spi_unprepare_transfer(struct spi_master *master)
|
|
+{
|
|
+ struct spi_driver_data *drv_data = spi_master_get_devdata(master);
|
|
+
|
|
+ /* Disable the SSP now */
|
|
+ k1x_spi_write(drv_data, TOP_CTRL,
|
|
+ k1x_spi_read(drv_data, TOP_CTRL) & ~(TOP_SSE | TOP_HOLD_FRAME_LOW));
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int setup_cs(struct spi_device *spi, struct chip_data *chip)
|
|
+{
|
|
+ int err = 0;
|
|
+
|
|
+ if (chip == NULL)
|
|
+ return 0;
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int setup(struct spi_device *spi)
|
|
+{
|
|
+ struct chip_data *chip;
|
|
+ struct spi_driver_data *drv_data = spi_master_get_devdata(spi->master);
|
|
+ uint tx_thres, tx_hi_thres, rx_thres;
|
|
+
|
|
+ tx_thres = TX_THRESH_DFLT;
|
|
+ tx_hi_thres = 0;
|
|
+ rx_thres = RX_THRESH_DFLT;
|
|
+
|
|
+ /* Only alloc on first setup */
|
|
+ chip = spi_get_ctldata(spi);
|
|
+ if (!chip) {
|
|
+ chip = devm_kzalloc(&spi->master->dev, sizeof(struct chip_data),
|
|
+ GFP_KERNEL);
|
|
+ if (!chip)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ chip->gpio_cs = -1;
|
|
+ chip->enable_dma = 0;
|
|
+ chip->timeout =
|
|
+ drv_data->slave_mode ? TIMOUT_DFLT_SLAVE : TIMOUT_DFLT;
|
|
+ }
|
|
+
|
|
+ chip->top_ctrl = 0;
|
|
+ chip->fifo_ctrl = 0;
|
|
+
|
|
+ chip->enable_dma = drv_data->master_info->enable_dma;
|
|
+ if (drv_data->slave_mode)
|
|
+ chip->dma_burst_size = 32;
|
|
+
|
|
+ if (chip->enable_dma) {
|
|
+ /* set up legal burst and threshold for dma */
|
|
+ if (k1x_spi_set_dma_burst_and_threshold(chip, spi,
|
|
+ spi->bits_per_word,
|
|
+ &chip->dma_burst_size,
|
|
+ &chip->dma_threshold)) {
|
|
+ dev_warn(&spi->dev,
|
|
+ "in setup: DMA burst size reduced to match bits_per_word\n");
|
|
+ }
|
|
+ }
|
|
+ chip->threshold = (FIFO_RxTresh(rx_thres) & FIFO_RFT) |
|
|
+ (FIFO_TxTresh(tx_thres) & FIFO_TFT);
|
|
+
|
|
+ chip->top_ctrl &= ~(TOP_SPO | TOP_SPH);
|
|
+ chip->top_ctrl |= (((spi->mode & SPI_CPHA) != 0) ? TOP_SPH : 0)
|
|
+ | (((spi->mode & SPI_CPOL) != 0) ? TOP_SPO : 0);
|
|
+
|
|
+ if (spi->mode & SPI_LOOP)
|
|
+ chip->top_ctrl |= TOP_LBM;
|
|
+
|
|
+ /* Enable rx fifo auto full control */
|
|
+ if (drv_data->ssp_enhancement)
|
|
+ chip->fifo_ctrl |= FIFO_RXFIFO_AUTO_FULL_CTRL;
|
|
+
|
|
+ if (spi->bits_per_word <= 8) {
|
|
+ chip->n_bytes = 1;
|
|
+ chip->read = u8_reader;
|
|
+ chip->write = u8_writer;
|
|
+ } else if (spi->bits_per_word <= 16) {
|
|
+ chip->n_bytes = 2;
|
|
+ chip->read = u16_reader;
|
|
+ chip->write = u16_writer;
|
|
+ } else if (spi->bits_per_word <= 32) {
|
|
+ chip->n_bytes = 4;
|
|
+ chip->read = u32_reader;
|
|
+ chip->write = u32_writer;
|
|
+ }
|
|
+
|
|
+ if (spi->master->max_speed_hz != spi->max_speed_hz) {
|
|
+ spi->master->max_speed_hz = spi->max_speed_hz;
|
|
+ clk_set_rate(drv_data->clk, spi->master->max_speed_hz);
|
|
+ }
|
|
+
|
|
+ spi_set_ctldata(spi, chip);
|
|
+
|
|
+ return setup_cs(spi, chip);
|
|
+}
|
|
+
|
|
+static void cleanup(struct spi_device *spi)
|
|
+{
|
|
+ struct chip_data *chip = spi_get_ctldata(spi);
|
|
+
|
|
+ if (!chip)
|
|
+ return;
|
|
+
|
|
+ if (gpio_is_valid(chip->gpio_cs))
|
|
+ gpio_free(chip->gpio_cs);
|
|
+
|
|
+ devm_kfree(&spi->dev, chip);
|
|
+}
|
|
+
|
|
+static const struct of_device_id k1x_spi_dt_ids[] = {
|
|
+ { .compatible = "spacemit,k1x-spi", .data = (void *) K1X_SSP },
|
|
+ {}
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, k1x_spi_dt_ids);
|
|
+
|
|
+static int k1x_spi_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct device *dev = &pdev->dev;
|
|
+ struct k1x_spi_master *platform_info;
|
|
+ struct spi_master *master = NULL;
|
|
+ struct spi_driver_data *drv_data = NULL;
|
|
+ struct device_node *np = dev->of_node;
|
|
+ const struct of_device_id *id =
|
|
+ of_match_device(of_match_ptr(k1x_spi_dt_ids), dev);
|
|
+ struct resource *iores;
|
|
+ u32 bus_num;
|
|
+#if 0
|
|
+ const __be32 *prop;
|
|
+ unsigned int proplen;
|
|
+#endif
|
|
+ int status;
|
|
+ u32 tmp;
|
|
+
|
|
+ platform_info = dev_get_platdata(dev);
|
|
+ if (!platform_info) {
|
|
+ platform_info = devm_kzalloc(dev, sizeof(*platform_info),
|
|
+ GFP_KERNEL);
|
|
+ if (!platform_info)
|
|
+ return -ENOMEM;
|
|
+ platform_info->num_chipselect = 1;
|
|
+ /* TODO: NO DMA on FPGA yet */
|
|
+ if (of_get_property(np, "k1x,ssp-disable-dma", NULL))
|
|
+ platform_info->enable_dma = 0;
|
|
+ else
|
|
+ platform_info->enable_dma = 1;
|
|
+ }
|
|
+
|
|
+ master = spi_alloc_master(dev, sizeof(struct spi_driver_data));
|
|
+ if (!master) {
|
|
+ dev_err(&pdev->dev, "cannot alloc spi_master\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ drv_data = spi_master_get_devdata(master);
|
|
+
|
|
+ iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
+ if (iores == NULL) {
|
|
+ dev_err(dev, "no memory resource defined\n");
|
|
+ status = -ENODEV;
|
|
+ goto out_error_master_alloc;
|
|
+ }
|
|
+
|
|
+ drv_data->ioaddr = devm_ioremap_resource(dev, iores);
|
|
+ if (drv_data->ioaddr == NULL) {
|
|
+ dev_err(dev, "failed to ioremap() registers\n");
|
|
+ status = -ENODEV;
|
|
+ goto out_error_master_alloc;
|
|
+ }
|
|
+
|
|
+ drv_data->irq = platform_get_irq(pdev, 0);
|
|
+ if (drv_data->irq < 0) {
|
|
+ dev_err(dev, "no IRQ resource defined\n");
|
|
+ status = -ENODEV;
|
|
+ goto out_error_master_alloc;
|
|
+ }
|
|
+
|
|
+ /* Receive FIFO auto full ctrl enable */
|
|
+ if (of_get_property(np, "k1x,ssp-enhancement", NULL))
|
|
+ drv_data->ssp_enhancement = 1;
|
|
+
|
|
+ if (of_get_property(np, "k1x,ssp-slave-mode", NULL)) {
|
|
+ drv_data->slave_mode = 1;
|
|
+ dev_warn(&pdev->dev, "slave mode\n");
|
|
+ timer_setup(&drv_data->slave_rx_timer,
|
|
+ slave_rx_timer_expired, 0);
|
|
+ }
|
|
+
|
|
+#if 0
|
|
+ prop = of_get_property(dev->of_node, "k1x,ssp-lpm-qos", &proplen);
|
|
+ if (!prop) {
|
|
+ dev_err(&pdev->dev, "lpm-qos for spi is not defined!\n");
|
|
+ status = -EINVAL;
|
|
+ goto out_error_master_alloc;
|
|
+ } else
|
|
+ drv_data->qos_idle_value = be32_to_cpup(prop);
|
|
+#endif
|
|
+
|
|
+ init_dvfm_constraint(drv_data);
|
|
+
|
|
+ master->dev.of_node = dev->of_node;
|
|
+ drv_data->ssp_type = (uintptr_t) id->data;
|
|
+ if (!of_property_read_u32(np, "k1x,ssp-id", &bus_num))
|
|
+ master->bus_num = bus_num;
|
|
+ drv_data->ssdr_physical = iores->start + DATAR;
|
|
+
|
|
+ drv_data->clk = devm_clk_get(dev, NULL);
|
|
+ if (IS_ERR_OR_NULL(drv_data->clk)) {
|
|
+ dev_err(&pdev->dev, "cannot get clk\n");
|
|
+ status = -ENODEV;
|
|
+ goto out_error_clk_check;
|
|
+ }
|
|
+
|
|
+ drv_data->reset = devm_reset_control_get_optional(dev, NULL);
|
|
+ if (IS_ERR_OR_NULL(drv_data->reset)) {
|
|
+ dev_err(&pdev->dev, "Failed to get spi's reset\n");
|
|
+ goto out_error_clk_check;
|
|
+ }
|
|
+
|
|
+ drv_data->master = master;
|
|
+ drv_data->master_info = platform_info;
|
|
+ drv_data->pdev = pdev;
|
|
+
|
|
+ master->dev.parent = &pdev->dev;
|
|
+ /* the spi->mode bits understood by this driver: */
|
|
+ master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LOOP;
|
|
+
|
|
+ master->dma_alignment = DMA_ALIGNMENT;
|
|
+ master->cleanup = cleanup;
|
|
+ master->setup = setup;
|
|
+ master->transfer_one_message = k1x_spi_transfer_one_message;
|
|
+ master->unprepare_transfer_hardware = k1x_spi_unprepare_transfer;
|
|
+ master->auto_runtime_pm = true;
|
|
+
|
|
+ master->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 32);
|
|
+ drv_data->int_cr = INT_EN_TIE | INT_EN_RIE | INT_EN_TINTE; /* INT_EN */
|
|
+ drv_data->dma_cr = (drv_data->slave_mode) ? INT_EN_TINTE : 0;
|
|
+ drv_data->clear_sr = STATUS_ROR | STATUS_TINT;
|
|
+ drv_data->mask_sr = STATUS_TINT | STATUS_RFS | STATUS_TFS | STATUS_ROR;
|
|
+ drv_data->dma_top_ctrl = DEFAULT_DMA_TOP_CTRL;
|
|
+ drv_data->dma_fifo_ctrl = DEFAULT_DMA_FIFO_CTRL;
|
|
+
|
|
+ status = devm_request_irq(&pdev->dev, drv_data->irq, ssp_int, IRQF_SHARED, dev_name(dev),
|
|
+ drv_data);
|
|
+ if (status < 0) {
|
|
+ dev_err(&pdev->dev, "cannot get IRQ %d\n", drv_data->irq);
|
|
+ goto out_error_master_alloc;
|
|
+ }
|
|
+
|
|
+ /* Setup DMA if requested */
|
|
+ if (platform_info->enable_dma) {
|
|
+ status = k1x_spi_dma_setup(drv_data);
|
|
+ if (status) {
|
|
+ dev_dbg(dev, "no DMA channels available, using PIO\n");
|
|
+ platform_info->enable_dma = false;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ status = of_property_read_u32(np, "k1x,ssp-clock-rate", &master->max_speed_hz);
|
|
+ if (status < 0) {
|
|
+ dev_err(&pdev->dev, "cannot get clock-rate from DT file\n");
|
|
+ goto out_error_master_alloc;
|
|
+ }
|
|
+
|
|
+ clk_set_rate(drv_data->clk, master->max_speed_hz);
|
|
+ master->max_speed_hz = clk_get_rate(drv_data->clk);
|
|
+ clk_prepare_enable(drv_data->clk);
|
|
+ reset_control_deassert(drv_data->reset);
|
|
+
|
|
+ /* Load default SSP configuration */
|
|
+ k1x_spi_write(drv_data, TOP_CTRL, 0);
|
|
+ k1x_spi_write(drv_data, FIFO_CTRL, 0);
|
|
+ tmp = FIFO_RxTresh(RX_THRESH_DFLT) |
|
|
+ FIFO_TxTresh(TX_THRESH_DFLT);
|
|
+ k1x_spi_write(drv_data, FIFO_CTRL, tmp);
|
|
+ tmp = TOP_FRF_Motorola | TOP_DSS(8);
|
|
+ k1x_spi_write(drv_data, TOP_CTRL, tmp);
|
|
+ k1x_spi_write(drv_data, TO, 0);
|
|
+
|
|
+ k1x_spi_write(drv_data, PSP_CTRL, 0);
|
|
+
|
|
+ master->num_chipselect = platform_info->num_chipselect;
|
|
+
|
|
+ INIT_WORK(&drv_data->pump_transfers, pump_transfers);
|
|
+ pm_runtime_set_autosuspend_delay(&pdev->dev, 50);
|
|
+ pm_runtime_use_autosuspend(&pdev->dev);
|
|
+ pm_runtime_set_active(&pdev->dev);
|
|
+ pm_runtime_enable(&pdev->dev);
|
|
+
|
|
+ init_completion(&drv_data->cur_msg_completion);
|
|
+
|
|
+ /* Register with the SPI framework */
|
|
+ platform_set_drvdata(pdev, drv_data);
|
|
+ status = devm_spi_register_master(&pdev->dev, master);
|
|
+ if (status != 0) {
|
|
+ dev_err(&pdev->dev, "problem registering spi master\n");
|
|
+ goto out_error_clock_enabled;
|
|
+ }
|
|
+
|
|
+ return status;
|
|
+
|
|
+out_error_clock_enabled:
|
|
+ reset_control_assert(drv_data->reset);
|
|
+ clk_disable_unprepare(drv_data->clk);
|
|
+ k1x_spi_dma_release(drv_data);
|
|
+ free_irq(drv_data->irq, drv_data);
|
|
+out_error_clk_check:
|
|
+ deinit_dvfm_constraint(drv_data);
|
|
+out_error_master_alloc:
|
|
+ spi_master_put(master);
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static int k1x_spi_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct spi_driver_data *drv_data = platform_get_drvdata(pdev);
|
|
+
|
|
+ if (!drv_data)
|
|
+ return 0;
|
|
+
|
|
+ pm_runtime_get_sync(&pdev->dev);
|
|
+
|
|
+ /* Disable the SSP at the peripheral and SOC level */
|
|
+ k1x_spi_write(drv_data, TOP_CTRL, 0);
|
|
+ k1x_spi_write(drv_data, FIFO_CTRL, 0); /* whether need this line? */
|
|
+
|
|
+ reset_control_assert(drv_data->reset);
|
|
+ clk_disable_unprepare(drv_data->clk);
|
|
+
|
|
+ /* Release DMA */
|
|
+ if (drv_data->master_info->enable_dma)
|
|
+ k1x_spi_dma_release(drv_data);
|
|
+
|
|
+ pm_runtime_put_noidle(&pdev->dev);
|
|
+ pm_runtime_disable(&pdev->dev);
|
|
+
|
|
+ /* Release IRQ */
|
|
+ free_irq(drv_data->irq, drv_data);
|
|
+
|
|
+ deinit_dvfm_constraint(drv_data);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void k1x_spi_shutdown(struct platform_device *pdev)
|
|
+{
|
|
+ int status = 0;
|
|
+
|
|
+ if ((status = k1x_spi_remove(pdev)) != 0)
|
|
+ dev_err(&pdev->dev, "shutdown failed with %d\n", status);
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_PM_SLEEP
|
|
+static int k1x_spi_suspend(struct device *dev)
|
|
+{
|
|
+ struct spi_driver_data *drv_data = dev_get_drvdata(dev);
|
|
+ int status = 0;
|
|
+
|
|
+ pm_runtime_get_sync(dev);
|
|
+ status = spi_master_suspend(drv_data->master);
|
|
+ if (status != 0)
|
|
+ return status;
|
|
+ k1x_spi_write(drv_data, TOP_CTRL, 0);
|
|
+ k1x_spi_write(drv_data, FIFO_CTRL, 0); /* whether need this line? */
|
|
+
|
|
+ status = pm_runtime_force_suspend(dev);
|
|
+
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static int k1x_spi_resume(struct device *dev)
|
|
+{
|
|
+ struct spi_driver_data *drv_data = dev_get_drvdata(dev);
|
|
+ int status = 0;
|
|
+
|
|
+ /* Enable the SSP clock */
|
|
+ status = pm_runtime_force_resume(dev);
|
|
+ if (status) {
|
|
+ dev_err(dev, "failed to resume pm_runtime (%d)\n", status);
|
|
+ return status;
|
|
+ }
|
|
+
|
|
+ /* Start the queue running */
|
|
+ status = spi_master_resume(drv_data->master);
|
|
+ pm_runtime_mark_last_busy(dev);
|
|
+ pm_runtime_put_autosuspend(dev);
|
|
+ if (status != 0) {
|
|
+ dev_err(dev, "problem starting queue (%d)\n", status);
|
|
+ return status;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+#endif
|
|
+
|
|
+#ifdef CONFIG_PM
|
|
+/** static int k1x_spi_runtime_suspend(struct device *dev)
|
|
+ * {
|
|
+ * struct spi_driver_data *drv_data = dev_get_drvdata(dev);
|
|
+ *
|
|
+ * reset_control_assert(drv_data->reset);
|
|
+ * clk_disable_unprepare(drv_data->clk);
|
|
+ *
|
|
+ * return 0;
|
|
+ *}
|
|
+ */
|
|
+
|
|
+/**
|
|
+ * static int k1x_spi_runtime_resume(struct device *dev)
|
|
+ * {
|
|
+ * struct spi_driver_data *drv_data = dev_get_drvdata(dev);
|
|
+ *
|
|
+ * clk_prepare_enable(drv_data->clk);
|
|
+ * reset_control_deassert(drv_data->reset);
|
|
+ * return 0;
|
|
+ *}
|
|
+ */
|
|
+#endif
|
|
+
|
|
+static const struct dev_pm_ops k1x_spi_pm_ops = {
|
|
+ SET_SYSTEM_SLEEP_PM_OPS(k1x_spi_suspend, k1x_spi_resume)
|
|
+ /**
|
|
+ * SET_RUNTIME_PM_OPS(k1x_spi_runtime_suspend,
|
|
+ * k1x_spi_runtime_resume, NULL)
|
|
+ */
|
|
+};
|
|
+
|
|
+static struct platform_driver driver = {
|
|
+ .driver = {
|
|
+ .name = "k1x-spi",
|
|
+ .pm = &k1x_spi_pm_ops,
|
|
+ .of_match_table = k1x_spi_dt_ids,
|
|
+ },
|
|
+ .probe = k1x_spi_probe,
|
|
+ .remove = k1x_spi_remove,
|
|
+ .shutdown = k1x_spi_shutdown,
|
|
+};
|
|
+
|
|
+static int __init k1x_spi_init(void)
|
|
+{
|
|
+ return platform_driver_register(&driver);
|
|
+}
|
|
+module_init(k1x_spi_init);
|
|
+
|
|
+static void __exit k1x_spi_exit(void)
|
|
+{
|
|
+ platform_driver_unregister(&driver);
|
|
+}
|
|
+module_exit(k1x_spi_exit);
|
|
+
|
|
+MODULE_AUTHOR("Spacemit");
|
|
+MODULE_DESCRIPTION("Spacemit k1x spi controller driver");
|
|
+MODULE_LICENSE("GPL v2");
|
|
diff --git a/drivers/spi/spi-k1x.h b/drivers/spi/spi-k1x.h
|
|
new file mode 100644
|
|
index 000000000000..111111111111
|
|
--- /dev/null
|
|
+++ b/drivers/spi/spi-k1x.h
|
|
@@ -0,0 +1,364 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Support for Spacemit k1x spi controller
|
|
+ *
|
|
+ * Copyright (c) 2023, spacemit Corporation.
|
|
+ *
|
|
+ */
|
|
+
|
|
+#ifndef _SPI_K1X_H
|
|
+#define _SPI_K1X_H
|
|
+
|
|
+#include <linux/atomic.h>
|
|
+#include <linux/dmaengine.h>
|
|
+#include <linux/errno.h>
|
|
+#include <linux/io.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/scatterlist.h>
|
|
+#include <linux/sizes.h>
|
|
+#include <linux/spi/spi.h>
|
|
+#include <linux/pm_qos.h>
|
|
+#include <linux/list.h>
|
|
+#include <linux/io.h>
|
|
+#include <linux/of.h>
|
|
+#include <linux/reset.h>
|
|
+
|
|
+/* Spacemit k1x SPI Registers */
|
|
+#define TOP_CTRL 0x00 /* SSP Top Control Register */
|
|
+#define FIFO_CTRL 0x04 /* SSP FIFO Control Register */
|
|
+#define INT_EN 0x08 /* SSP Interrupt Enable Register */
|
|
+#define TO 0x0C /* SSP Time Out Register */
|
|
+#define DATAR 0x10 /* SSP Data Register */
|
|
+#define STATUS 0x14 /* SSP Stauts Register */
|
|
+#define PSP_CTRL 0x18 /* SSP Programmable Serial Protocal Control Register */
|
|
+#define NET_WORK_CTRL 0x1C /* SSP NET Work Control Register */
|
|
+#define NET_WORK_STATUS 0x20 /* SSP Net Work Status Register */
|
|
+#define RWOT_CTRL 0x24 /* SSP RWOT Control Register */
|
|
+#define RWOT_CCM 0x28 /* SSP RWOT Counter Cycles Match Register */
|
|
+#define RWOT_CVWRn 0x2C /* SSP RWOT Counter Value Write for Read Request Register */
|
|
+
|
|
+/* 0x00 TOP_CTRL */
|
|
+#define TOP_TTELP (1 << 18)
|
|
+#define TOP_TTE (1 << 17)
|
|
+#define TOP_SCFR (1 << 16)
|
|
+#define TOP_IFS (1 << 15)
|
|
+#define TOP_HOLD_FRAME_LOW (1 << 14)
|
|
+#define TOP_TRAIL (1 << 13)
|
|
+#define TOP_LBM (1 << 12)
|
|
+#define TOP_SPH (1 << 11)
|
|
+#define TOP_SPO (1 << 10)
|
|
+#define TOP_DSS(x) ((x - 1) << 5)
|
|
+#define TOP_DSS_MASK (0x1F << 5)
|
|
+#define TOP_SFRMDIR (1 << 4)
|
|
+#define TOP_SCLKDIR (1 << 3)
|
|
+#define TOP_FRF_MASK (0x3 << 1)
|
|
+#define TOP_FRF_Motorola (0x0 << 1) /* Motorola's Serial Peripheral Interface (SPI) */
|
|
+#define TOP_FRF_TI (0x1 << 1) /* Texas Instruments' Synchronous Serial Protocol (SSP) */
|
|
+#define TOP_FRF_National (0x2 << 1) /* National Microwire */
|
|
+#define TOP_FRF_PSP (0x3 << 1) /* Programmable Serial Protocol(PSP) */
|
|
+#define TOP_SSE (1 << 0)
|
|
+
|
|
+/* 0x04 FIFO_CTRL */
|
|
+#define FIFO_STRF (1 << 19)
|
|
+#define FIFO_EFWR (1 << 18)
|
|
+#define FIFO_RXFIFO_AUTO_FULL_CTRL (1 << 17)
|
|
+#define FIFO_FPCKE (1 << 16)
|
|
+#define FIFO_TXFIFO_WR_ENDIAN_MASK (0x3 << 14)
|
|
+#define FIFO_RXFIFO_RD_ENDIAN_MASK (0x3 << 12)
|
|
+#define FIFO_WR_ENDIAN_16BITS (1 << 14) /* Swap first 16 bits and last 16 bits */
|
|
+#define FIFO_WR_ENDIAN_8BITS (2 << 14) /* Swap all 4 bytes */
|
|
+#define FIFO_RD_ENDIAN_16BITS (1 << 12) /* Swap first 16 bits and last 16 bits */
|
|
+#define FIFO_RD_ENDIAN_8BITS (2 << 12) /* Swap all 4 bytes */
|
|
+#define FIFO_RSRE (1 << 11)
|
|
+#define FIFO_TSRE (1 << 10)
|
|
+
|
|
+/* 0x08 INT_EN */
|
|
+#define INT_EN_EBCEI (1 << 6)
|
|
+#define INT_EN_TIM (1 << 5)
|
|
+#define INT_EN_RIM (1 << 4)
|
|
+#define INT_EN_TIE (1 << 3)
|
|
+#define INT_EN_RIE (1 << 2)
|
|
+#define INT_EN_TINTE (1 << 1)
|
|
+#define INT_EN_PINTE (1 << 0)
|
|
+
|
|
+/* 0x0C TO */
|
|
+#define TIMEOUT(x) ((x) << 0)
|
|
+
|
|
+/* 0x10 DATAR */
|
|
+#define DATA(x) ((x) << 0)
|
|
+
|
|
+/* 0x14 STATUS */
|
|
+#define STATUS_OSS (1 << 23)
|
|
+#define STATUS_TX_OSS (1 << 22)
|
|
+#define STATUS_BCE (1 << 21)
|
|
+#define STATUS_ROR (1 << 20)
|
|
+#define STATUS_RNE (1 << 14)
|
|
+#define STATUS_RFS (1 << 13)
|
|
+#define STATUS_TUR (1 << 12)
|
|
+#define STATUS_TNF (1 << 6)
|
|
+#define STATUS_TFS (1 << 5)
|
|
+#define STATUS_EOC (1 << 4)
|
|
+#define STATUS_TINT (1 << 3)
|
|
+#define STATUS_PINT (1 << 2)
|
|
+#define STATUS_CSS (1 << 1)
|
|
+#define STATUS_BSY (1 << 0)
|
|
+
|
|
+/* 0x18 PSP_CTRL */
|
|
+#define PSP_EDMYSTOP(x) ((x) << 27)
|
|
+#define PSP_EMYSTOP(x) ((x) << 25)
|
|
+#define PSP_EDMYSTRT(x) ((x) << 23)
|
|
+#define PSP_DMYSTRT(x) ((x) << 21)
|
|
+#define PSP_STRTDLY(x) ((x) << 18)
|
|
+#define PSP_SFRMWDTH(x) ((x) << 12)
|
|
+#define PSP_SFRMDLY(x) ((x) << 5)
|
|
+#define PSP_SFRMP (1 << 4)
|
|
+#define PSP_FSRT (1 << 3)
|
|
+#define PSP_ETDS (1 << 2)
|
|
+#define PSP_SCMODE(x) ((x) << 0)
|
|
+
|
|
+/* 0x1C NET_WORK_CTRL */
|
|
+#define RTSA(x) ((x) << 12)
|
|
+#define RTSA_MASK (0xFF << 12)
|
|
+#define TTSA(x) ((x) << 4)
|
|
+#define TTSA_MASK (0xFF << 4)
|
|
+#define NET_FRDC(x) ((x) << 1)
|
|
+#define NET_WORK_MODE (1 << 0)
|
|
+
|
|
+/* 0x20 NET_WORK_STATUS */
|
|
+#define NET_SATUS_NMBSY (1 << 3)
|
|
+#define NET_STATUS_TSS(x) ((x) << 0)
|
|
+
|
|
+/* 0x24 RWOT_CTRL */
|
|
+#define RWOT_MASK_RWOT_LAST_SAMPLE (1 << 4)
|
|
+#define RWOT_CLR_RWOT_CYCLE (1 << 3)
|
|
+#define RWOT_SET_RWOT_CYCLE (1 << 2)
|
|
+#define RWOT_CYCLE_RWOT_EN (1 << 1)
|
|
+#define RWOT_RWOT (1 << 0)
|
|
+
|
|
+enum k1x_ssp_type {
|
|
+ SSP_UNDEFINED = 0,
|
|
+ K1X_SSP,
|
|
+};
|
|
+
|
|
+struct spi_driver_data {
|
|
+ /* Driver model hookup */
|
|
+ struct platform_device *pdev;
|
|
+
|
|
+ /* SSP Info */
|
|
+ struct ssp_device *ssp;
|
|
+
|
|
+ /* SPI framework hookup */
|
|
+ enum k1x_ssp_type ssp_type;
|
|
+ struct spi_master *master;
|
|
+
|
|
+ /* k1x hookup */
|
|
+ struct k1x_spi_master *master_info;
|
|
+
|
|
+ /* SSP register addresses */
|
|
+ void __iomem *ioaddr;
|
|
+ u32 ssdr_physical;
|
|
+
|
|
+ /* SSP masks*/
|
|
+ u32 dma_fifo_ctrl;
|
|
+ u32 dma_top_ctrl;
|
|
+ u32 int_cr;
|
|
+ u32 dma_cr;
|
|
+ u32 clear_sr;
|
|
+ u32 mask_sr;
|
|
+
|
|
+ /* Message Transfer pump */
|
|
+ struct work_struct pump_transfers;
|
|
+
|
|
+ /* DMA engine support */
|
|
+ struct dma_chan *rx_chan;
|
|
+ struct dma_chan *tx_chan;
|
|
+ struct sg_table rx_sgt;
|
|
+ struct sg_table tx_sgt;
|
|
+ int rx_nents;
|
|
+ int tx_nents;
|
|
+ void *dummy;
|
|
+ atomic_t dma_running;
|
|
+
|
|
+ /* Current message transfer state info */
|
|
+ struct spi_message *cur_msg;
|
|
+ struct spi_transfer *cur_transfer;
|
|
+ struct chip_data *cur_chip;
|
|
+ struct completion cur_msg_completion;
|
|
+ size_t len;
|
|
+ void *tx;
|
|
+ void *tx_end;
|
|
+ void *rx;
|
|
+ void *rx_end;
|
|
+ int dma_mapped;
|
|
+ dma_addr_t rx_dma;
|
|
+ dma_addr_t tx_dma;
|
|
+ size_t rx_map_len;
|
|
+ size_t tx_map_len;
|
|
+ u8 n_bytes;
|
|
+ int (*write)(struct spi_driver_data *drv_data);
|
|
+ int (*read)(struct spi_driver_data *drv_data);
|
|
+ irqreturn_t (*transfer_handler)(struct spi_driver_data *drv_data);
|
|
+ void (*cs_control)(u32 command);
|
|
+ struct freq_qos_request qos_idle;
|
|
+ int qos_idle_value;
|
|
+ struct clk *clk;
|
|
+ struct reset_control *reset;
|
|
+ int irq;
|
|
+ /* Support RX FIFO auto full control and endian swap */
|
|
+ unsigned int ssp_enhancement;
|
|
+ unsigned char slave_mode;
|
|
+ struct timer_list slave_rx_timer;
|
|
+};
|
|
+
|
|
+struct chip_data {
|
|
+ u32 top_ctrl;
|
|
+ u32 fifo_ctrl;
|
|
+ u32 timeout;
|
|
+ u8 n_bytes;
|
|
+ u32 dma_burst_size;
|
|
+ u32 threshold;
|
|
+ u32 dma_threshold;
|
|
+ u8 enable_dma;
|
|
+ union {
|
|
+ int gpio_cs;
|
|
+ unsigned int frm;
|
|
+ };
|
|
+ int gpio_cs_inverted;
|
|
+ int (*write)(struct spi_driver_data *drv_data);
|
|
+ int (*read)(struct spi_driver_data *drv_data);
|
|
+ void (*cs_control)(u32 command);
|
|
+};
|
|
+
|
|
+static inline u32 k1x_spi_read(const struct spi_driver_data *drv_data,
|
|
+ unsigned reg)
|
|
+{
|
|
+ return __raw_readl(drv_data->ioaddr + reg);
|
|
+}
|
|
+
|
|
+static inline void k1x_spi_write(const struct spi_driver_data *drv_data,
|
|
+ unsigned reg, u32 val)
|
|
+{
|
|
+ __raw_writel(val, drv_data->ioaddr + reg);
|
|
+}
|
|
+
|
|
+#define START_STATE ((void *)0)
|
|
+#define RUNNING_STATE ((void *)1)
|
|
+#define DONE_STATE ((void *)2)
|
|
+#define ERROR_STATE ((void *)-1)
|
|
+
|
|
+#define IS_DMA_ALIGNED(x) IS_ALIGNED((unsigned long)(x), DMA_ALIGNMENT)
|
|
+#define DMA_ALIGNMENT 64
|
|
+
|
|
+extern int k1x_spi_flush(struct spi_driver_data *drv_data);
|
|
+extern void *k1x_spi_next_transfer(struct spi_driver_data *drv_data);
|
|
+
|
|
+/*
|
|
+ * Select the right DMA implementation.
|
|
+ */
|
|
+#define MAX_DMA_LEN SZ_512K
|
|
+#define DEFAULT_DMA_FIFO_CTRL (FIFO_TSRE | FIFO_RSRE)
|
|
+#define DEFAULT_DMA_TOP_CTRL (TOP_TRAIL)
|
|
+
|
|
+extern bool k1x_spi_dma_is_possible(size_t len);
|
|
+extern int k1x_spi_map_dma_buffers(struct spi_driver_data *drv_data);
|
|
+extern irqreturn_t k1x_spi_dma_transfer(struct spi_driver_data *drv_data);
|
|
+extern void k1x_spi_slave_sw_timeout_callback(struct spi_driver_data *drv_data);
|
|
+extern int k1x_spi_dma_prepare(struct spi_driver_data *drv_data, u32 dma_burst);
|
|
+extern void k1x_spi_dma_start(struct spi_driver_data *drv_data);
|
|
+extern int k1x_spi_dma_setup(struct spi_driver_data *drv_data);
|
|
+extern void k1x_spi_dma_release(struct spi_driver_data *drv_data);
|
|
+extern int k1x_spi_set_dma_burst_and_threshold(struct chip_data *chip,
|
|
+ struct spi_device *spi,
|
|
+ u8 bits_per_word,
|
|
+ u32 *burst_code,
|
|
+ u32 *threshold);
|
|
+
|
|
+#define RX_THRESH_DFLT 9
|
|
+#define TX_THRESH_DFLT 8
|
|
+/* 0x14 */
|
|
+#define STATUS_TFL_MASK (0x1f << 7) /* Transmit FIFO Level mask */
|
|
+#define STATUS_RFL_MASK (0x1f << 15) /* Receive FIFO Level mask */
|
|
+/* 0x4 */
|
|
+#define FIFO_TFT (0x0000001F) /* Transmit FIFO Threshold (mask) */
|
|
+#define FIFO_TxTresh(x) (((x) - 1) << 0) /* level [1..32] */
|
|
+#define FIFO_RFT (0x000003E0) /* Receive FIFO Threshold (mask) */
|
|
+#define FIFO_RxTresh(x) (((x) - 1) << 5) /* level [1..32] */
|
|
+
|
|
+struct ssp_device {
|
|
+ struct platform_device *pdev;
|
|
+ struct list_head node;
|
|
+
|
|
+ struct clk *clk;
|
|
+ void __iomem *mmio_base;
|
|
+ unsigned long phys_base;
|
|
+
|
|
+ const char *label;
|
|
+ int port_id;
|
|
+ int type;
|
|
+ int use_count;
|
|
+ int irq;
|
|
+ int drcmr_rx;
|
|
+ int drcmr_tx;
|
|
+
|
|
+ struct device_node *of_node;
|
|
+};
|
|
+
|
|
+/**
|
|
+ * k1x_ssp_write_reg - Write to a SSP register
|
|
+ *
|
|
+ * @dev: SSP device to access
|
|
+ * @reg: Register to write to
|
|
+ * @val: Value to be written.
|
|
+ */
|
|
+static inline void k1x_ssp_write_reg(struct ssp_device *dev, u32 reg, u32 val)
|
|
+{
|
|
+ __raw_writel(val, dev->mmio_base + reg);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * k1x_ssp_read_reg - Read from a SSP register
|
|
+ *
|
|
+ * @dev: SSP device to access
|
|
+ * @reg: Register to read from
|
|
+ */
|
|
+static inline u32 k1x_ssp_read_reg(struct ssp_device *dev, u32 reg)
|
|
+{
|
|
+ return __raw_readl(dev->mmio_base + reg);
|
|
+}
|
|
+
|
|
+static inline void k1x_ssp_free(struct ssp_device *ssp) {}
|
|
+#define K1X_CS_ASSERT (0x01)
|
|
+#define K1X_CS_DEASSERT (0x02)
|
|
+
|
|
+struct dma_chan;
|
|
+
|
|
+/* device.platform_data for SSP controller devices */
|
|
+struct k1x_spi_master {
|
|
+ u16 num_chipselect;
|
|
+ u8 enable_dma;
|
|
+
|
|
+ /* DMA engine specific config */
|
|
+ bool (*dma_filter)(struct dma_chan *chan, void *param);
|
|
+ void *tx_param;
|
|
+ void *rx_param;
|
|
+
|
|
+ /* For sound ssp controller */
|
|
+ struct ssp_device ssp;
|
|
+};
|
|
+
|
|
+/* spi_board_info.controller_data for SPI slave devices,
|
|
+ * copied to spi_device.platform_data ... mostly for dma tuning
|
|
+ */
|
|
+struct k1x_spi_chip {
|
|
+ u8 tx_threshold;
|
|
+ u8 tx_hi_threshold;
|
|
+ u8 rx_threshold;
|
|
+ u8 dma_burst_size;
|
|
+ u32 timeout;
|
|
+ u8 enable_loopback;
|
|
+ int gpio_cs;
|
|
+ void (*cs_control)(u32 command);
|
|
+};
|
|
+
|
|
+#endif /* _SPI_K1X_H */
|
|
--
|
|
Armbian
|
|
|