[PATCH v2 06/10] spi/pxa2xx: add support for DMA engine
From: Mika Westerberg
Date: Mon Jan 21 2013 - 04:31:49 EST
To be able to use DMA with this driver on non-PXA platforms we implement
support for the generic DMA engine API. This lets user to use different DMA
engines with little or no modification to the driver.
The DMA engine support will be build to the driver if CONFIG_DMA_ENGINE is
set.
Request lines and channel numbers can be passed to the driver from the
platform specific data.
Signed-off-by: Mika Westerberg <mika.westerberg@xxxxxxxxxxxxxxx>
---
Linus W, there haven't been any comments from Vinod/Dan about the alignment
thing so I added a comment 'REVISIT: ..' so that we remember to change this
once it has been sorted out. I hope that is OK for you.
drivers/spi/Kconfig | 3 +
drivers/spi/Makefile | 1 +
drivers/spi/spi-pxa2xx-dma.c | 388 ++++++++++++++++++++++++++++++++++++++++
drivers/spi/spi-pxa2xx.c | 2 +-
drivers/spi/spi-pxa2xx.h | 32 +++-
include/linux/spi/pxa2xx_spi.h | 6 +
6 files changed, 430 insertions(+), 2 deletions(-)
create mode 100644 drivers/spi/spi-pxa2xx-dma.c
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 30c60813..c3d4555 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -299,12 +299,15 @@ config SPI_PPC4xx
config SPI_PXA2XX_PXADMA
bool
+config SPI_PXA2XX_DMA
+ bool
config SPI_PXA2XX
tristate "PXA2xx SSP SPI master"
depends on ARCH_PXA || PCI
select PXA_SSP if ARCH_PXA
select SPI_PXA2XX_PXADMA if ARCH_PXA
+ select SPI_PXA2XX_DMA if DMA_ENGINE && !ARCH_PXA
help
This enables using a PXA2xx or Sodaville SSP port as a SPI master
controller. The driver can be configured to use any SSP port and
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 2e3cdd3..e53c309 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -49,6 +49,7 @@ obj-$(CONFIG_SPI_PL022) += spi-pl022.o
obj-$(CONFIG_SPI_PPC4xx) += spi-ppc4xx.o
spi-pxa2xx-platform-objs := spi-pxa2xx.o
spi-pxa2xx-platform-$(CONFIG_SPI_PXA2XX_PXADMA) += spi-pxa2xx-pxadma.o
+spi-pxa2xx-platform-$(CONFIG_SPI_PXA2XX_DMA) += spi-pxa2xx-dma.o
obj-$(CONFIG_SPI_PXA2XX) += spi-pxa2xx-platform.o
obj-$(CONFIG_SPI_PXA2XX_PCI) += spi-pxa2xx-pci.o
obj-$(CONFIG_SPI_RSPI) += spi-rspi.o
diff --git a/drivers/spi/spi-pxa2xx-dma.c b/drivers/spi/spi-pxa2xx-dma.c
new file mode 100644
index 0000000..bb49ede
--- /dev/null
+++ b/drivers/spi/spi-pxa2xx-dma.c
@@ -0,0 +1,388 @@
+/*
+ * PXA2xx SPI DMA engine support.
+ *
+ * Copyright (C) 2013, Intel Corporation
+ * Author: Mika Westerberg <mika.westerberg@xxxxxxxxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/pxa2xx_ssp.h>
+#include <linux/scatterlist.h>
+#include <linux/sizes.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/pxa2xx_spi.h>
+
+#include "spi-pxa2xx.h"
+
+static int map_dma_buffer(struct 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;
+
+ /*
+ * Some DMA controllers have problems transferring buffers that are
+ * not multiple of 4 bytes. So we truncate the transfer so that it
+ * is suitable for such controllers, and handle the trailing bytes
+ * manually after the DMA completes.
+ *
+ * REVISIT: It would be better if this information could be
+ * retrieved directly from the DMA device in a similar way than
+ * ->copy_align etc. is done.
+ */
+ len = ALIGN(drv_data->len, 4);
+
+ 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_KERNEL);
+ 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 unmap_dma_buffer(struct 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 unmap_dma_buffers(struct driver_data *drv_data)
+{
+ if (!drv_data->dma_mapped)
+ return;
+
+ unmap_dma_buffer(drv_data, DMA_FROM_DEVICE);
+ unmap_dma_buffer(drv_data, DMA_TO_DEVICE);
+
+ drv_data->dma_mapped = 0;
+}
+
+static void dma_transfer_complete(struct 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)) {
+ void __iomem *reg = drv_data->ioaddr;
+
+ /*
+ * 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 = read_SSSR(reg) & drv_data->mask_sr;
+ error = status & SSSR_ROR;
+ }
+
+ /* Clear status & disable interrupts */
+ write_SSCR1(read_SSCR1(reg) & ~drv_data->dma_cr1, reg);
+ write_SSSR_CS(drv_data, drv_data->clear_sr);
+ if (!pxa25x_ssp_comp(drv_data))
+ write_SSTO(0, reg);
+
+ if (!error) {
+ unmap_dma_buffers(drv_data);
+
+ /* Handle the last bytes of unaligned transfer */
+ drv_data->tx += drv_data->tx_map_len;
+ drv_data->write(drv_data);
+
+ drv_data->rx += drv_data->rx_map_len;
+ drv_data->read(drv_data);
+
+ msg->actual_length += drv_data->len;
+ msg->state = next_transfer(drv_data);
+ } else {
+ /* In case we got an error we disable the SSP now */
+ write_SSCR0(read_SSCR0(reg) & ~SSCR0_SSE, reg);
+
+ msg->state = ERROR_STATE;
+ }
+
+ tasklet_schedule(&drv_data->pump_transfers);
+ }
+}
+
+static void dma_callback(void *data)
+{
+ dma_transfer_complete(data, false);
+}
+
+static struct dma_async_tx_descriptor *
+dma_prepare_one(struct driver_data *drv_data, enum dma_transfer_direction dir)
+{
+ struct pxa2xx_spi_master *pdata = drv_data->master_info;
+ 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;
+ cfg.slave_id = pdata->tx_slave_id;
+
+ 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;
+ cfg.slave_id = pdata->rx_slave_id;
+
+ 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);
+}
+
+static bool dma_filter(struct dma_chan *chan, void *param)
+{
+ const struct pxa2xx_spi_master *pdata = param;
+
+ return chan->chan_id == pdata->tx_chan_id ||
+ chan->chan_id == pdata->rx_chan_id;
+}
+
+bool dma_is_possible(size_t len)
+{
+ return len <= MAX_DMA_LEN;
+}
+
+int map_dma_buffers(struct 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 = 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 = map_dma_buffer(drv_data, DMA_FROM_DEVICE);
+ if (ret <= 0) {
+ 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 dma_transfer(struct driver_data *drv_data)
+{
+ u32 status;
+
+ status = read_SSSR(drv_data->ioaddr) & drv_data->mask_sr;
+ if (status & SSSR_ROR) {
+ dev_err(&drv_data->pdev->dev, "FIFO overrun\n");
+
+ dmaengine_terminate_all(drv_data->rx_chan);
+ dmaengine_terminate_all(drv_data->tx_chan);
+
+ dma_transfer_complete(drv_data, true);
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_NONE;
+}
+
+int dma_prepare(struct driver_data *drv_data, u32 dma_burst)
+{
+ struct dma_async_tx_descriptor *tx_desc, *rx_desc;
+
+ tx_desc = 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 = 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 = dma_callback;
+ rx_desc->callback_param = drv_data;
+
+ dmaengine_submit(rx_desc);
+ dmaengine_submit(tx_desc);
+ return 0;
+}
+
+void dma_start(struct 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 dma_setup(struct driver_data *drv_data)
+{
+ struct pxa2xx_spi_master *pdata = drv_data->master_info;
+ dma_cap_mask_t mask;
+
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+
+ drv_data->dummy = devm_kzalloc(&drv_data->pdev->dev, SZ_2K, GFP_KERNEL);
+ if (!drv_data->dummy)
+ return -ENOMEM;
+
+ drv_data->tx_chan = dma_request_channel(mask, dma_filter, pdata);
+ if (!drv_data->tx_chan)
+ return -ENODEV;
+
+ drv_data->rx_chan = dma_request_channel(mask, dma_filter, pdata);
+ if (!drv_data->rx_chan) {
+ dma_release_channel(drv_data->tx_chan);
+ drv_data->tx_chan = NULL;
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+void dma_release(struct 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;
+ }
+}
+
+void dma_resume(struct driver_data *drv_data)
+{
+}
+
+int set_dma_burst_and_threshold(struct chip_data *chip,
+ struct spi_device *spi,
+ u8 bits_per_word, u32 *burst_code,
+ u32 *threshold)
+{
+ struct pxa2xx_spi_chip *chip_info = spi->controller_data;
+
+ /*
+ * If the DMA burst size is given in chip_info we use that,
+ * otherwise we use the default. Also we use the default FIFO
+ * thresholds for now.
+ */
+ *burst_code = chip_info ? chip_info->dma_burst_size : 16;
+ *threshold = SSCR1_RxTresh(RX_THRESH_DFLT)
+ | SSCR1_TxTresh(TX_THRESH_DFLT);
+
+ return 0;
+}
diff --git a/drivers/spi/spi-pxa2xx.c b/drivers/spi/spi-pxa2xx.c
index 8da07a9..a08df23 100644
--- a/drivers/spi/spi-pxa2xx.c
+++ b/drivers/spi/spi-pxa2xx.c
@@ -936,7 +936,7 @@ static int pxa2xx_spi_probe(struct platform_device *pdev)
drv_data->mask_sr = SSSR_RFS | SSSR_TFS | SSSR_ROR;
} else {
drv_data->int_cr1 = SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE;
- drv_data->dma_cr1 = SSCR1_TSRE | SSCR1_RSRE | SSCR1_TINTE;
+ drv_data->dma_cr1 = DEFAULT_DMA_CR1;
drv_data->clear_sr = SSSR_ROR | SSSR_TINT;
drv_data->mask_sr = SSSR_TINT | SSSR_RFS | SSSR_TFS | SSSR_ROR;
}
diff --git a/drivers/spi/spi-pxa2xx.h b/drivers/spi/spi-pxa2xx.h
index e78830c..771af62 100644
--- a/drivers/spi/spi-pxa2xx.h
+++ b/drivers/spi/spi-pxa2xx.h
@@ -10,11 +10,15 @@
#ifndef SPI_PXA2XX_H
#define SPI_PXA2XX_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/pxa2xx_ssp.h>
+#include <linux/scatterlist.h>
+#include <linux/sizes.h>
#include <linux/spi/spi.h>
#include <linux/spi/pxa2xx_spi.h>
@@ -53,6 +57,16 @@ struct driver_data {
/* Message Transfer pump */
struct tasklet_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;
@@ -116,7 +130,6 @@ DEFINE_SSP_REG(SSPSP, 0x2c)
#define DONE_STATE ((void *)2)
#define ERROR_STATE ((void *)-1)
-#define MAX_DMA_LEN 8191
#define IS_DMA_ALIGNED(x) IS_ALIGNED((unsigned long)x, DMA_ALIGNMENT)
#define DMA_ALIGNMENT 8
@@ -133,7 +146,24 @@ extern void write_SSSR_CS(struct driver_data *drv_data, u32 val);
extern int flush(struct driver_data *drv_data);
extern void *next_transfer(struct driver_data *drv_data);
+/*
+ * Select the right DMA implementation.
+ */
#if defined(CONFIG_SPI_PXA2XX_PXADMA)
+#define SPI_PXA2XX_USE_DMA 1
+#define MAX_DMA_LEN 8191
+#define DEFAULT_DMA_CR1 (SSCR1_TSRE | SSCR1_RSRE | SSCR1_TINTE)
+#elif defined(CONFIG_SPI_PXA2XX_DMA)
+#define SPI_PXA2XX_USE_DMA 1
+#define MAX_DMA_LEN SZ_64K
+#define DEFAULT_DMA_CR1 (SSCR1_TSRE | SSCR1_RSRE | SSCR1_TRAIL)
+#else
+#undef SPI_PXA2XX_USE_DMA
+#define MAX_DMA_LEN 0
+#define DEFAULT_DMA_CR1 0
+#endif
+
+#ifdef SPI_PXA2XX_USE_DMA
extern bool dma_is_possible(size_t len);
extern int map_dma_buffers(struct driver_data *drv_data);
extern irqreturn_t dma_transfer(struct driver_data *drv_data);
diff --git a/include/linux/spi/pxa2xx_spi.h b/include/linux/spi/pxa2xx_spi.h
index d6d2b4d..e5cbbc4 100644
--- a/include/linux/spi/pxa2xx_spi.h
+++ b/include/linux/spi/pxa2xx_spi.h
@@ -29,6 +29,12 @@ struct pxa2xx_spi_master {
u16 num_chipselect;
u8 enable_dma;
+ /* DMA engine specific config */
+ int rx_chan_id;
+ int tx_chan_id;
+ int rx_slave_id;
+ int tx_slave_id;
+
/* For non-PXA arches */
struct ssp_device ssp;
};
--
1.7.10.4
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/