openwrtv4/target/linux/ipq806x/patches/001-spi-qup-Add-DMA-capabilities.patch
John Crispin 88ddb37461 ipq806x: update target to v3.18
Patches in the ipq806x/patches folder were out of tree in v3.14. The
newest patch at the time was from June, so we can safely assume that
either the patches have been merged, or they have been rejected for
a good reason. If patches are seen missing, we'll cherry-pick them
on a per-needed basis.

This new kernel have been tested on AP148, which seems to works fine.

Signed-off-by: Mathieu Olivari <mathieu@codeaurora.org>

SVN-Revision: 44386
2015-02-11 10:09:23 +00:00

522 lines
16 KiB
Diff

Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: spi: qup: Add DMA capabilities
From: Andy Gross <agross@codeaurora.org>
X-Patchwork-Id: 4432401
Message-Id: <1403816781-31008-1-git-send-email-agross@codeaurora.org>
To: Mark Brown <broonie@kernel.org>
Cc: linux-spi@vger.kernel.org, Sagar Dharia <sdharia@codeaurora.org>,
Daniel Sneddon <dsneddon@codeaurora.org>,
Bjorn Andersson <bjorn.andersson@sonymobile.com>,
"Ivan T. Ivanov" <iivanov@mm-sol.com>,
linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
linux-arm-msm@vger.kernel.org, Andy Gross <agross@codeaurora.org>
Date: Thu, 26 Jun 2014 16:06:21 -0500
This patch adds DMA capabilities to the spi-qup driver. If DMA channels are
present, the QUP will use DMA instead of block mode for transfers to/from SPI
peripherals for transactions larger than the length of a block.
Signed-off-by: Andy Gross <agross@codeaurora.org>
---
.../devicetree/bindings/spi/qcom,spi-qup.txt | 10 +
drivers/spi/spi-qup.c | 361 ++++++++++++++++++--
2 files changed, 350 insertions(+), 21 deletions(-)
--- a/Documentation/devicetree/bindings/spi/qcom,spi-qup.txt
+++ b/Documentation/devicetree/bindings/spi/qcom,spi-qup.txt
@@ -27,6 +27,11 @@ Optional properties:
- spi-max-frequency: Specifies maximum SPI clock frequency,
Units - Hz. Definition as per
Documentation/devicetree/bindings/spi/spi-bus.txt
+- dmas : Two DMA channel specifiers following the convention outlined
+ in bindings/dma/dma.txt
+- dma-names: Names for the dma channels, if present. There must be at
+ least one channel named "tx" for transmit and named "rx" for
+ receive.
- num-cs: total number of chipselects
- cs-gpios: should specify GPIOs used for chipselects.
The gpios will be referred to as reg = <index> in the SPI child
@@ -51,6 +56,10 @@ Example:
clocks = <&gcc GCC_BLSP2_QUP2_SPI_APPS_CLK>, <&gcc GCC_BLSP2_AHB_CLK>;
clock-names = "core", "iface";
+ dmas = <&blsp2_bam 2>,
+ <&blsp2_bam 3>;
+ dma-names = "rx", "tx";
+
pinctrl-names = "default";
pinctrl-0 = <&spi8_default>;
--- a/drivers/spi/spi-qup.c
+++ b/drivers/spi/spi-qup.c
@@ -22,6 +22,8 @@
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/spi/spi.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
#define QUP_CONFIG 0x0000
#define QUP_STATE 0x0004
@@ -116,6 +118,8 @@
#define SPI_NUM_CHIPSELECTS 4
+#define SPI_MAX_XFER (SZ_64K - 64)
+
/* high speed mode is when bus rate is greater then 26MHz */
#define SPI_HS_MIN_RATE 26000000
#define SPI_MAX_RATE 50000000
@@ -143,6 +147,17 @@ struct spi_qup {
int tx_bytes;
int rx_bytes;
int qup_v1;
+
+ int use_dma;
+
+ struct dma_chan *rx_chan;
+ struct dma_slave_config rx_conf;
+ struct dma_chan *tx_chan;
+ struct dma_slave_config tx_conf;
+ dma_addr_t rx_dma;
+ dma_addr_t tx_dma;
+ void *dummy;
+ atomic_t dma_outstanding;
};
@@ -266,6 +281,221 @@ static void spi_qup_fifo_write(struct sp
}
}
+static void qup_dma_callback(void *data)
+{
+ struct spi_qup *controller = data;
+
+ if (atomic_dec_and_test(&controller->dma_outstanding))
+ complete(&controller->done);
+}
+
+static int spi_qup_do_dma(struct spi_qup *controller, struct spi_transfer *xfer)
+{
+ struct dma_async_tx_descriptor *rxd, *txd;
+ dma_cookie_t rx_cookie, tx_cookie;
+ u32 xfer_len, rx_align = 0, tx_align = 0, n_words;
+ struct scatterlist tx_sg[2], rx_sg[2];
+ int ret = 0;
+ u32 bytes_to_xfer = xfer->len;
+ u32 offset = 0;
+ u32 rx_nents = 0, tx_nents = 0;
+ dma_addr_t rx_dma = 0, tx_dma = 0, rx_dummy_dma = 0, tx_dummy_dma = 0;
+
+
+ if (xfer->rx_buf) {
+ rx_dma = dma_map_single(controller->dev, xfer->rx_buf,
+ xfer->len, DMA_FROM_DEVICE);
+
+ if (dma_mapping_error(controller->dev, rx_dma)) {
+ ret = -ENOMEM;
+ return ret;
+ }
+
+ /* check to see if we need dummy buffer for leftover bytes */
+ rx_align = xfer->len % controller->in_blk_sz;
+ if (rx_align) {
+ rx_dummy_dma = dma_map_single(controller->dev,
+ controller->dummy, controller->in_fifo_sz,
+ DMA_FROM_DEVICE);
+
+ if (dma_mapping_error(controller->dev, rx_dummy_dma)) {
+ ret = -ENOMEM;
+ goto err_map_rx_dummy;
+ }
+ }
+ }
+
+ if (xfer->tx_buf) {
+ tx_dma = dma_map_single(controller->dev,
+ (void *)xfer->tx_buf, xfer->len, DMA_TO_DEVICE);
+
+ if (dma_mapping_error(controller->dev, tx_dma)) {
+ ret = -ENOMEM;
+ goto err_map_tx;
+ }
+
+ /* check to see if we need dummy buffer for leftover bytes */
+ tx_align = xfer->len % controller->out_blk_sz;
+ if (tx_align) {
+ memcpy(controller->dummy + SZ_1K,
+ xfer->tx_buf + xfer->len - tx_align,
+ tx_align);
+ memset(controller->dummy + SZ_1K + tx_align, 0,
+ controller->out_blk_sz - tx_align);
+
+ tx_dummy_dma = dma_map_single(controller->dev,
+ controller->dummy + SZ_1K,
+ controller->out_blk_sz, DMA_TO_DEVICE);
+
+ if (dma_mapping_error(controller->dev, tx_dummy_dma)) {
+ ret = -ENOMEM;
+ goto err_map_tx_dummy;
+ }
+ }
+ }
+
+ atomic_set(&controller->dma_outstanding, 0);
+
+ while (bytes_to_xfer > 0) {
+ xfer_len = min_t(u32, bytes_to_xfer, SPI_MAX_XFER);
+ n_words = DIV_ROUND_UP(xfer_len, controller->w_size);
+
+ /* write out current word count to controller */
+ writel_relaxed(n_words, controller->base + QUP_MX_INPUT_CNT);
+ writel_relaxed(n_words, controller->base + QUP_MX_OUTPUT_CNT);
+
+ reinit_completion(&controller->done);
+
+ if (xfer->tx_buf) {
+ /* recalc align for each transaction */
+ tx_align = xfer_len % controller->out_blk_sz;
+
+ if (tx_align)
+ tx_nents = 2;
+ else
+ tx_nents = 1;
+
+ /* initialize scatterlists */
+ sg_init_table(tx_sg, tx_nents);
+ sg_dma_len(&tx_sg[0]) = xfer_len - tx_align;
+ sg_dma_address(&tx_sg[0]) = tx_dma + offset;
+
+ /* account for non block size transfer */
+ if (tx_align) {
+ sg_dma_len(&tx_sg[1]) = controller->out_blk_sz;
+ sg_dma_address(&tx_sg[1]) = tx_dummy_dma;
+ }
+
+ txd = dmaengine_prep_slave_sg(controller->tx_chan,
+ tx_sg, tx_nents, DMA_MEM_TO_DEV, 0);
+ if (!txd) {
+ ret = -ENOMEM;
+ goto err_unmap;
+ }
+
+ atomic_inc(&controller->dma_outstanding);
+
+ txd->callback = qup_dma_callback;
+ txd->callback_param = controller;
+
+ tx_cookie = dmaengine_submit(txd);
+
+ dma_async_issue_pending(controller->tx_chan);
+ }
+
+ if (xfer->rx_buf) {
+ /* recalc align for each transaction */
+ rx_align = xfer_len % controller->in_blk_sz;
+
+ if (rx_align)
+ rx_nents = 2;
+ else
+ rx_nents = 1;
+
+ /* initialize scatterlists */
+ sg_init_table(rx_sg, rx_nents);
+ sg_dma_address(&rx_sg[0]) = rx_dma + offset;
+ sg_dma_len(&rx_sg[0]) = xfer_len - rx_align;
+
+ /* account for non block size transfer */
+ if (rx_align) {
+ sg_dma_len(&rx_sg[1]) = controller->in_blk_sz;
+ sg_dma_address(&rx_sg[1]) = rx_dummy_dma;
+ }
+
+ rxd = dmaengine_prep_slave_sg(controller->rx_chan,
+ rx_sg, rx_nents, DMA_DEV_TO_MEM, 0);
+ if (!rxd) {
+ ret = -ENOMEM;
+ goto err_unmap;
+ }
+
+ atomic_inc(&controller->dma_outstanding);
+
+ rxd->callback = qup_dma_callback;
+ rxd->callback_param = controller;
+
+ rx_cookie = dmaengine_submit(rxd);
+
+ dma_async_issue_pending(controller->rx_chan);
+ }
+
+ if (spi_qup_set_state(controller, QUP_STATE_RUN)) {
+ dev_warn(controller->dev, "cannot set EXECUTE state\n");
+ goto err_unmap;
+ }
+
+ if (!wait_for_completion_timeout(&controller->done,
+ msecs_to_jiffies(1000))) {
+ ret = -ETIMEDOUT;
+
+ /* clear out all the DMA transactions */
+ if (xfer->tx_buf)
+ dmaengine_terminate_all(controller->tx_chan);
+ if (xfer->rx_buf)
+ dmaengine_terminate_all(controller->rx_chan);
+
+ goto err_unmap;
+ }
+
+ if (rx_align)
+ memcpy(xfer->rx_buf + offset + xfer->len - rx_align,
+ controller->dummy, rx_align);
+
+ /* adjust remaining bytes to transfer */
+ bytes_to_xfer -= xfer_len;
+ offset += xfer_len;
+
+
+ /* reset mini-core state so we can program next transaction */
+ if (spi_qup_set_state(controller, QUP_STATE_RESET)) {
+ dev_err(controller->dev, "cannot set RESET state\n");
+ goto err_unmap;
+ }
+ }
+
+ ret = 0;
+
+err_unmap:
+ if (tx_align)
+ dma_unmap_single(controller->dev, tx_dummy_dma,
+ controller->out_fifo_sz, DMA_TO_DEVICE);
+err_map_tx_dummy:
+ if (xfer->tx_buf)
+ dma_unmap_single(controller->dev, tx_dma, xfer->len,
+ DMA_TO_DEVICE);
+err_map_tx:
+ if (rx_align)
+ dma_unmap_single(controller->dev, rx_dummy_dma,
+ controller->in_fifo_sz, DMA_FROM_DEVICE);
+err_map_rx_dummy:
+ if (xfer->rx_buf)
+ dma_unmap_single(controller->dev, rx_dma, xfer->len,
+ DMA_FROM_DEVICE);
+
+ return ret;
+}
+
static irqreturn_t spi_qup_qup_irq(int irq, void *dev_id)
{
struct spi_qup *controller = dev_id;
@@ -315,11 +545,13 @@ static irqreturn_t spi_qup_qup_irq(int i
error = -EIO;
}
- if (opflags & QUP_OP_IN_SERVICE_FLAG)
- spi_qup_fifo_read(controller, xfer);
+ if (!controller->use_dma) {
+ if (opflags & QUP_OP_IN_SERVICE_FLAG)
+ spi_qup_fifo_read(controller, xfer);
- if (opflags & QUP_OP_OUT_SERVICE_FLAG)
- spi_qup_fifo_write(controller, xfer);
+ if (opflags & QUP_OP_OUT_SERVICE_FLAG)
+ spi_qup_fifo_write(controller, xfer);
+ }
spin_lock_irqsave(&controller->lock, flags);
controller->error = error;
@@ -339,6 +571,8 @@ static int spi_qup_io_config(struct spi_
struct spi_qup *controller = spi_master_get_devdata(spi->master);
u32 config, iomode, mode;
int ret, n_words, w_size;
+ size_t dma_align = dma_get_cache_alignment();
+ u32 dma_available = 0;
if (spi->mode & SPI_LOOP && xfer->len > controller->in_fifo_sz) {
dev_err(controller->dev, "too big size for loopback %d > %d\n",
@@ -367,6 +601,11 @@ static int spi_qup_io_config(struct spi_
n_words = xfer->len / w_size;
controller->w_size = w_size;
+ if (controller->rx_chan &&
+ IS_ALIGNED((size_t)xfer->tx_buf, dma_align) &&
+ IS_ALIGNED((size_t)xfer->rx_buf, dma_align))
+ dma_available = 1;
+
if (n_words <= (controller->in_fifo_sz / sizeof(u32))) {
mode = QUP_IO_M_MODE_FIFO;
writel_relaxed(n_words, controller->base + QUP_MX_READ_CNT);
@@ -374,19 +613,31 @@ static int spi_qup_io_config(struct spi_
/* must be zero for FIFO */
writel_relaxed(0, controller->base + QUP_MX_INPUT_CNT);
writel_relaxed(0, controller->base + QUP_MX_OUTPUT_CNT);
- } else {
+ controller->use_dma = 0;
+ } else if (!dma_available) {
mode = QUP_IO_M_MODE_BLOCK;
writel_relaxed(n_words, controller->base + QUP_MX_INPUT_CNT);
writel_relaxed(n_words, controller->base + QUP_MX_OUTPUT_CNT);
/* must be zero for BLOCK and BAM */
writel_relaxed(0, controller->base + QUP_MX_READ_CNT);
writel_relaxed(0, controller->base + QUP_MX_WRITE_CNT);
+ controller->use_dma = 0;
+ } else {
+ mode = QUP_IO_M_MODE_DMOV;
+ writel_relaxed(0, controller->base + QUP_MX_READ_CNT);
+ writel_relaxed(0, controller->base + QUP_MX_WRITE_CNT);
+ controller->use_dma = 1;
}
iomode = readl_relaxed(controller->base + QUP_IO_M_MODES);
/* Set input and output transfer mode */
iomode &= ~(QUP_IO_M_INPUT_MODE_MASK | QUP_IO_M_OUTPUT_MODE_MASK);
- iomode &= ~(QUP_IO_M_PACK_EN | QUP_IO_M_UNPACK_EN);
+
+ if (!controller->use_dma)
+ iomode &= ~(QUP_IO_M_PACK_EN | QUP_IO_M_UNPACK_EN);
+ else
+ iomode |= QUP_IO_M_PACK_EN | QUP_IO_M_UNPACK_EN;
+
iomode |= (mode << QUP_IO_M_OUTPUT_MODE_MASK_SHIFT);
iomode |= (mode << QUP_IO_M_INPUT_MODE_MASK_SHIFT);
@@ -419,6 +670,14 @@ static int spi_qup_io_config(struct spi_
config &= ~(QUP_CONFIG_NO_INPUT | QUP_CONFIG_NO_OUTPUT | QUP_CONFIG_N);
config |= xfer->bits_per_word - 1;
config |= QUP_CONFIG_SPI_MODE;
+
+ if (controller->use_dma) {
+ if (!xfer->tx_buf)
+ config |= QUP_CONFIG_NO_OUTPUT;
+ if (!xfer->rx_buf)
+ config |= QUP_CONFIG_NO_INPUT;
+ }
+
writel_relaxed(config, controller->base + QUP_CONFIG);
/* only write to OPERATIONAL_MASK when register is present */
@@ -452,25 +711,29 @@ static int spi_qup_transfer_one(struct s
controller->tx_bytes = 0;
spin_unlock_irqrestore(&controller->lock, flags);
- if (spi_qup_set_state(controller, QUP_STATE_RUN)) {
- dev_warn(controller->dev, "cannot set RUN state\n");
- goto exit;
- }
+ if (controller->use_dma) {
+ ret = spi_qup_do_dma(controller, xfer);
+ } else {
+ if (spi_qup_set_state(controller, QUP_STATE_RUN)) {
+ dev_warn(controller->dev, "cannot set RUN state\n");
+ goto exit;
+ }
- if (spi_qup_set_state(controller, QUP_STATE_PAUSE)) {
- dev_warn(controller->dev, "cannot set PAUSE state\n");
- goto exit;
- }
+ if (spi_qup_set_state(controller, QUP_STATE_PAUSE)) {
+ dev_warn(controller->dev, "cannot set PAUSE state\n");
+ goto exit;
+ }
- spi_qup_fifo_write(controller, xfer);
+ spi_qup_fifo_write(controller, xfer);
- if (spi_qup_set_state(controller, QUP_STATE_RUN)) {
- dev_warn(controller->dev, "cannot set EXECUTE state\n");
- goto exit;
- }
+ if (spi_qup_set_state(controller, QUP_STATE_RUN)) {
+ dev_warn(controller->dev, "cannot set EXECUTE state\n");
+ goto exit;
+ }
- if (!wait_for_completion_timeout(&controller->done, timeout))
- ret = -ETIMEDOUT;
+ if (!wait_for_completion_timeout(&controller->done, timeout))
+ ret = -ETIMEDOUT;
+ }
exit:
spi_qup_set_state(controller, QUP_STATE_RESET);
spin_lock_irqsave(&controller->lock, flags);
@@ -553,6 +816,7 @@ static int spi_qup_probe(struct platform
master->transfer_one = spi_qup_transfer_one;
master->dev.of_node = pdev->dev.of_node;
master->auto_runtime_pm = true;
+ master->dma_alignment = dma_get_cache_alignment();
platform_set_drvdata(pdev, master);
@@ -618,6 +882,56 @@ static int spi_qup_probe(struct platform
QUP_ERROR_INPUT_UNDER_RUN | QUP_ERROR_OUTPUT_UNDER_RUN,
base + QUP_ERROR_FLAGS_EN);
+ /* allocate dma resources, if available */
+ controller->rx_chan = dma_request_slave_channel(&pdev->dev, "rx");
+ if (controller->rx_chan) {
+ controller->tx_chan =
+ dma_request_slave_channel(&pdev->dev, "tx");
+
+ if (!controller->tx_chan) {
+ dev_err(&pdev->dev, "Failed to allocate dma tx chan");
+ dma_release_channel(controller->rx_chan);
+ }
+
+ /* set DMA parameters */
+ controller->rx_conf.device_fc = 1;
+ controller->rx_conf.src_addr = res->start + QUP_INPUT_FIFO;
+ controller->rx_conf.src_maxburst = controller->in_blk_sz;
+
+ controller->tx_conf.device_fc = 1;
+ controller->tx_conf.dst_addr = res->start + QUP_OUTPUT_FIFO;
+ controller->tx_conf.dst_maxburst = controller->out_blk_sz;
+
+ if (dmaengine_slave_config(controller->rx_chan,
+ &controller->rx_conf)) {
+ dev_err(&pdev->dev, "failed to configure RX channel\n");
+
+ dma_release_channel(controller->rx_chan);
+ dma_release_channel(controller->tx_chan);
+ controller->tx_chan = NULL;
+ controller->rx_chan = NULL;
+ } else if (dmaengine_slave_config(controller->tx_chan,
+ &controller->tx_conf)) {
+ dev_err(&pdev->dev, "failed to configure TX channel\n");
+
+ dma_release_channel(controller->rx_chan);
+ dma_release_channel(controller->tx_chan);
+ controller->tx_chan = NULL;
+ controller->rx_chan = NULL;
+ }
+
+ controller->dummy = devm_kmalloc(controller->dev, PAGE_SIZE,
+ GFP_KERNEL);
+
+ if (!controller->dummy) {
+ dma_release_channel(controller->rx_chan);
+ dma_release_channel(controller->tx_chan);
+ controller->tx_chan = NULL;
+ controller->rx_chan = NULL;
+ }
+ }
+
+
writel_relaxed(0, base + SPI_CONFIG);
writel_relaxed(SPI_IO_C_NO_TRI_STATE, base + SPI_IO_CONTROL);
@@ -730,6 +1044,11 @@ static int spi_qup_remove(struct platfor
if (ret)
return ret;
+ if (controller->rx_chan)
+ dma_release_channel(controller->rx_chan);
+ if (controller->tx_chan)
+ dma_release_channel(controller->tx_chan);
+
clk_disable_unprepare(controller->cclk);
clk_disable_unprepare(controller->iclk);