All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/2] SPI: spi-sh-msiof: add DMA support
@ 2011-09-02 15:13 ` Guennadi Liakhovetski
  0 siblings, 0 replies; 18+ messages in thread
From: Guennadi Liakhovetski @ 2011-09-02 15:13 UTC (permalink / raw)
  To: spi-devel-general; +Cc: linux-sh, Grant Likely

This patch series adds DMA support for the MSIOF SPI controllers on 
sh-mobile SoCs, e.g., on sh7724.

Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer
http://www.open-technology.de/

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH 0/2] SPI: spi-sh-msiof: add DMA support
@ 2011-09-02 15:13 ` Guennadi Liakhovetski
  0 siblings, 0 replies; 18+ messages in thread
From: Guennadi Liakhovetski @ 2011-09-02 15:13 UTC (permalink / raw)
  To: spi-devel-general; +Cc: linux-sh, Grant Likely

This patch series adds DMA support for the MSIOF SPI controllers on 
sh-mobile SoCs, e.g., on sh7724.

Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer
http://www.open-technology.de/

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH 1/2] SPI: spi_sh_msiof: implement DMA support
  2011-09-02 15:13 ` Guennadi Liakhovetski
@ 2011-09-02 15:13   ` Guennadi Liakhovetski
  -1 siblings, 0 replies; 18+ messages in thread
From: Guennadi Liakhovetski @ 2011-09-02 15:13 UTC (permalink / raw)
  To: spi-devel-general; +Cc: linux-sh, Grant Likely

Use the sh_dma dmaengine driver to support DMA on MSIOF.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
---
 drivers/spi/spi-sh-msiof.c   |  393 ++++++++++++++++++++++++++++++++++++++---
 include/linux/spi/sh_msiof.h |    2 +
 2 files changed, 366 insertions(+), 29 deletions(-)

diff --git a/drivers/spi/spi-sh-msiof.c b/drivers/spi/spi-sh-msiof.c
index e00d94b..8622b32 100644
--- a/drivers/spi/spi-sh-msiof.c
+++ b/drivers/spi/spi-sh-msiof.c
@@ -13,14 +13,18 @@
 #include <linux/clk.h>
 #include <linux/completion.h>
 #include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
 #include <linux/err.h>
 #include <linux/gpio.h>
 #include <linux/init.h>
 #include <linux/interrupt.h>
 #include <linux/io.h>
 #include <linux/kernel.h>
+#include <linux/mm.h>
 #include <linux/platform_device.h>
 #include <linux/pm_runtime.h>
+#include <linux/sh_dma.h>
 
 #include <linux/spi/sh_msiof.h>
 #include <linux/spi/spi.h>
@@ -28,6 +32,13 @@
 
 #include <asm/unaligned.h>
 
+struct sh_msiof_dma {
+	struct sh_dmae_slave dma_slave;
+	struct dma_chan	*chan;
+	struct sg_table	sg;
+	struct dma_async_tx_descriptor *desc;
+};
+
 struct sh_msiof_spi_priv {
 	struct spi_bitbang bitbang; /* must be first for spi_bitbang.c */
 	void __iomem *mapbase;
@@ -38,6 +49,10 @@ struct sh_msiof_spi_priv {
 	unsigned long flags;
 	int tx_fifo_size;
 	int rx_fifo_size;
+	struct sh_msiof_dma dma_tx;
+	struct sh_msiof_dma dma_rx;
+	struct completion dma_done;
+	struct page *dummypage;
 };
 
 #define TMDR1	0x00
@@ -64,8 +79,12 @@ struct sh_msiof_spi_priv {
 #define CTR_TXE   (1 << 9)
 #define CTR_RXE   (1 << 8)
 
-#define STR_TEOF  (1 << 23)
-#define STR_REOF  (1 << 7)
+#define IER_TDMA  (1 << 31)
+#define IER_TDREQ (1 << 28)
+#define IER_TEOF  (1 << 23)
+#define IER_RDMA  (1 << 15)
+#define IER_RDREQ (1 << 12)
+#define IER_REOF  (1 << 7)
 
 static u32 sh_msiof_read(struct sh_msiof_spi_priv *p, int reg_offs)
 {
@@ -208,8 +227,6 @@ static void sh_msiof_spi_set_mode_regs(struct sh_msiof_spi_priv *p,
 
 	if (rx_buf)
 		sh_msiof_write(p, RMDR2, dr2);
-
-	sh_msiof_write(p, IER, STR_TEOF | STR_REOF);
 }
 
 static void sh_msiof_reset_str(struct sh_msiof_spi_priv *p)
@@ -401,9 +418,9 @@ static void sh_msiof_spi_chipselect(struct spi_device *spi, int is_on)
 
 	/* chip select is active low unless SPI_CS_HIGH is set */
 	if (spi->mode & SPI_CS_HIGH)
-		value = (is_on = BITBANG_CS_ACTIVE) ? 1 : 0;
+		value = is_on = BITBANG_CS_ACTIVE;
 	else
-		value = (is_on = BITBANG_CS_ACTIVE) ? 0 : 1;
+		value = is_on != BITBANG_CS_ACTIVE;
 
 	if (is_on = BITBANG_CS_ACTIVE) {
 		if (!test_and_set_bit(0, &p->flags)) {
@@ -429,6 +446,36 @@ static void sh_msiof_spi_chipselect(struct spi_device *spi, int is_on)
 	}
 }
 
+static int sh_msiof_spi_start(struct sh_msiof_spi_priv *p, void *rx_buf)
+{
+	/* setup clock and rx/tx signals */
+	int ret = sh_msiof_modify_ctr_wait(p, 0, CTR_TSCKE);
+	if (rx_buf && !ret)
+		ret = sh_msiof_modify_ctr_wait(p, 0, CTR_RXE);
+	if (!ret)
+		ret = sh_msiof_modify_ctr_wait(p, 0, CTR_TXE);
+
+	/* start by setting frame bit */
+	if (!ret)
+		ret = sh_msiof_modify_ctr_wait(p, 0, CTR_TFSE);
+
+	return ret;
+}
+
+static int sh_msiof_spi_stop(struct sh_msiof_spi_priv *p, void *rx_buf)
+{
+	/* shut down frame, tx/tx and clock signals */
+	int ret = sh_msiof_modify_ctr_wait(p, CTR_TFSE, 0);
+	if (!ret)
+		ret = sh_msiof_modify_ctr_wait(p, CTR_TXE, 0);
+	if (rx_buf && !ret)
+		ret = sh_msiof_modify_ctr_wait(p, CTR_RXE, 0);
+	if (!ret)
+		ret = sh_msiof_modify_ctr_wait(p, CTR_TSCKE, 0);
+
+	return ret;
+}
+
 static int sh_msiof_spi_txrx_once(struct sh_msiof_spi_priv *p,
 				  void (*tx_fifo)(struct sh_msiof_spi_priv *,
 						  const void *, int, int),
@@ -456,16 +503,11 @@ static int sh_msiof_spi_txrx_once(struct sh_msiof_spi_priv *p,
 	if (tx_buf)
 		tx_fifo(p, tx_buf, words, fifo_shift);
 
-	/* setup clock and rx/tx signals */
-	ret = sh_msiof_modify_ctr_wait(p, 0, CTR_TSCKE);
-	if (rx_buf)
-		ret = ret ? ret : sh_msiof_modify_ctr_wait(p, 0, CTR_RXE);
-	ret = ret ? ret : sh_msiof_modify_ctr_wait(p, 0, CTR_TXE);
-
-	/* start by setting frame bit */
 	INIT_COMPLETION(p->done);
-	ret = ret ? ret : sh_msiof_modify_ctr_wait(p, 0, CTR_TFSE);
-	if (ret) {
+	sh_msiof_write(p, IER, IER_TEOF | IER_REOF);
+
+	ret = sh_msiof_spi_start(p, rx_buf);
+	if (ret < 0) {
 		dev_err(&p->pdev->dev, "failed to start hardware\n");
 		goto err;
 	}
@@ -480,13 +522,8 @@ static int sh_msiof_spi_txrx_once(struct sh_msiof_spi_priv *p,
 	/* clear status bits */
 	sh_msiof_reset_str(p);
 
-	/* shut down frame, tx/tx and clock signals */
-	ret = sh_msiof_modify_ctr_wait(p, CTR_TFSE, 0);
-	ret = ret ? ret : sh_msiof_modify_ctr_wait(p, CTR_TXE, 0);
-	if (rx_buf)
-		ret = ret ? ret : sh_msiof_modify_ctr_wait(p, CTR_RXE, 0);
-	ret = ret ? ret : sh_msiof_modify_ctr_wait(p, CTR_TSCKE, 0);
-	if (ret) {
+	ret = sh_msiof_spi_stop(p, rx_buf);
+	if (ret < 0) {
 		dev_err(&p->pdev->dev, "failed to shut down hardware\n");
 		goto err;
 	}
@@ -498,6 +535,220 @@ static int sh_msiof_spi_txrx_once(struct sh_msiof_spi_priv *p,
 	return ret;
 }
 
+static void sh_msiof_setup_sg(struct sh_msiof_spi_priv *p,
+			      const void *buffer,
+			      unsigned int length,
+			      struct sg_table *sgtab)
+{
+	struct scatterlist *sg;
+	int bytesleft = length;
+	const void *bufp = buffer;
+	int mapbytes;
+	int i;
+
+	if (buffer) {
+		for_each_sg(sgtab->sgl, sg, sgtab->nents, i) {
+			/*
+			 * If there are fewer bytes left than what fits
+			 * in the current page (plus page alignment offset)
+			 * we just feed in this, else we stuff in as much
+			 * as we can.
+			 */
+			if (bytesleft < (PAGE_SIZE - offset_in_page(bufp)))
+				mapbytes = bytesleft;
+			else
+				mapbytes = PAGE_SIZE - offset_in_page(bufp);
+			sg_set_page(sg, virt_to_page(bufp),
+				    mapbytes, offset_in_page(bufp));
+			bytesleft -= mapbytes;
+			bufp += mapbytes;
+		}
+	} else {
+		/* Map the dummy buffer on every page */
+		for_each_sg(sgtab->sgl, sg, sgtab->nents, i) {
+			if (bytesleft < PAGE_SIZE)
+				mapbytes = bytesleft;
+			else
+				mapbytes = PAGE_SIZE;
+			sg_set_page(sg, p->dummypage, mapbytes, 0);
+			bytesleft -= mapbytes;
+		}
+	}
+	BUG_ON(bytesleft);
+}
+
+static int sh_msiof_spi_setup_xfer_dma(struct sh_msiof_spi_priv *p, struct sh_msiof_dma *dma,
+				       const void *buffer, size_t len, enum dma_data_direction dir)
+{
+	int ret, sglen;
+	unsigned int pages;
+	unsigned long flags = DMA_CTRL_ACK;
+
+	/* Create sglists for the transfers */
+	pages = (PAGE_ALIGN(len) >> PAGE_SHIFT);
+
+	ret = sg_alloc_table(&dma->sg, pages, GFP_KERNEL);
+	if (ret < 0)
+		return ret;
+
+	sh_msiof_setup_sg(p, buffer, len, &dma->sg);
+
+	/* Map DMA buffers */
+	sglen = dma_map_sg(&p->pdev->dev, dma->sg.sgl,
+			   dma->sg.orig_nents, dir);
+	if (sglen < 0) {
+		ret = sglen;
+		goto emapsg;
+	} else if (!sglen) {
+		ret = -EINVAL;
+		goto emapsg;
+	}
+
+	if (dir = DMA_FROM_DEVICE)
+		flags |= DMA_PREP_INTERRUPT;
+
+	dma->desc = dma->chan->device->device_prep_slave_sg(dma->chan,
+					dma->sg.sgl, sglen, dir, flags);
+	if (!dma->desc) {
+		ret = -ENOMEM;
+		goto edmaprep;
+	}
+
+	dma->sg.nents = sglen;
+
+	return 0;
+
+edmaprep:
+	dma_unmap_sg(&p->pdev->dev, dma->sg.sgl, sglen, dir);
+emapsg:
+	sg_free_table(&dma->sg);
+
+	return ret;
+}
+
+static void sh_msiof_spi_free_xfer_dma(struct sh_msiof_spi_priv *p, struct sh_msiof_dma *dma,
+				       enum dma_data_direction dir)
+{
+	dma_unmap_sg(&p->pdev->dev, dma->sg.sgl, dma->sg.nents, dir);
+	sg_free_table(&dma->sg);
+}
+
+static void sh_msiof_spi_dma_callback(void *arg)
+{
+	struct sh_msiof_spi_priv *p = arg;
+
+	dma_sync_sg_for_device(&p->pdev->dev, p->dma_rx.sg.sgl,
+			       p->dma_rx.sg.nents, DMA_FROM_DEVICE);
+
+	sh_msiof_spi_free_xfer_dma(p, &p->dma_rx, DMA_FROM_DEVICE);
+	sh_msiof_spi_free_xfer_dma(p, &p->dma_tx, DMA_TO_DEVICE);
+
+	complete(&p->dma_done);
+}
+
+static int sh_msiof_spi_txrx_dma_single(struct sh_msiof_spi_priv *p, int bits,
+					const void *tx_buf, const void *rx_buf, size_t len)
+{
+	int ret, cookie, words;
+
+	if (bits <= 8)
+		words = len;
+	else if (bits <= 16)
+		words = len >> 1;
+	else
+		words = len >> 2;
+
+	ret = sh_msiof_spi_setup_xfer_dma(p, &p->dma_tx, tx_buf, len, DMA_TO_DEVICE);
+	if (ret < 0)
+		return ret;
+
+	dma_sync_sg_for_device(&p->pdev->dev, p->dma_tx.sg.sgl,
+			       p->dma_tx.sg.nents, DMA_TO_DEVICE);
+
+	ret = sh_msiof_spi_setup_xfer_dma(p, &p->dma_rx, rx_buf, len, DMA_FROM_DEVICE);
+	if (ret < 0)
+		goto esdmarx;
+
+	/* Put the callback on the RX transfer only, that should finish last */
+	p->dma_rx.desc->callback = sh_msiof_spi_dma_callback;
+	p->dma_rx.desc->callback_param = p;
+
+	/* Submit and fire RX and TX with TX last so we're ready to read! */
+	cookie = p->dma_rx.desc->tx_submit(p->dma_rx.desc);
+	if (dma_submit_error(cookie)) {
+		ret = cookie;
+		goto esubmitrx;
+	}
+	p->dma_rx.chan->device->device_issue_pending(p->dma_rx.chan);
+
+	/* setup msiof transfer mode registers */
+	sh_msiof_spi_set_mode_regs(p, tx_buf, p->dummypage, bits, words);
+
+	cookie = p->dma_tx.desc->tx_submit(p->dma_tx.desc);
+	if (dma_submit_error(cookie)) {
+		ret = cookie;
+		goto esubmittx;
+	}
+	p->dma_tx.chan->device->device_issue_pending(p->dma_tx.chan);
+
+	INIT_COMPLETION(p->dma_done);
+	sh_msiof_write(p, IER, IER_RDREQ | IER_TDREQ | IER_RDMA | IER_TDMA);
+
+	/* As long as there's something to send, we'll also be receiving. */
+	ret = sh_msiof_spi_start(p, p->dummypage);
+	if (ret < 0) {
+		dev_err(&p->pdev->dev, "failed to start hardware: %d\n", ret);
+		goto espistart;
+	}
+
+	wait_for_completion(&p->dma_done);
+
+	/* clear status bits */
+	sh_msiof_reset_str(p);
+
+	ret = sh_msiof_spi_stop(p, p->dummypage);
+	if (ret < 0) {
+		dev_err(&p->pdev->dev, "failed to stop hardware: %d\n", ret);
+		goto espistop;
+	}
+
+	return len;
+
+espistop:
+espistart:
+	dmaengine_device_control(p->dma_tx.chan, DMA_TERMINATE_ALL, 0);
+esubmittx:
+	dmaengine_device_control(p->dma_rx.chan, DMA_TERMINATE_ALL, 0);
+esubmitrx:
+	sh_msiof_spi_free_xfer_dma(p, &p->dma_rx, DMA_FROM_DEVICE);
+esdmarx:
+	sh_msiof_spi_free_xfer_dma(p, &p->dma_tx, DMA_TO_DEVICE);
+
+	return ret;
+}
+
+static int sh_msiof_spi_txrx_dma(struct sh_msiof_spi_priv *p, struct spi_transfer *t, int bits)
+{
+	int i, ret = 0;
+	size_t left = t->len;
+	off_t offs = 0;
+
+	/* Up to 256 words per group, we only use a single group */
+	for (i = 0; i < (t->len + 255) / 256; i++) {
+		const void *rx_buf = t->rx_buf ? t->rx_buf + offs : NULL;
+		const void *tx_buf = t->tx_buf ? t->tx_buf + offs : NULL;
+
+		ret = sh_msiof_spi_txrx_dma_single(p, bits, tx_buf, rx_buf,
+						   min_t(size_t, left, 256));
+		if (ret < 0)
+			return ret;
+		left -= ret;
+		offs += ret;
+	}
+
+	return ret <= 0 ? ret : t->len;
+}
+
 static int sh_msiof_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
 {
 	struct sh_msiof_spi_priv *p = spi_master_get_devdata(spi->master);
@@ -512,6 +763,19 @@ static int sh_msiof_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
 
 	bits = sh_msiof_spi_bits(spi, t);
 
+	/* setup clocks (clock already enabled in chipselect()) */
+	sh_msiof_spi_set_clk_regs(p, clk_get_rate(p->clk),
+				  sh_msiof_spi_hz(spi, t));
+
+	if (p->dma_rx.chan && p->dma_tx.chan && t->len > 15 &&
+	    !(t->len & 3) && !((int)t->rx_buf & 3) && !((int)t->tx_buf & 3)) {
+		int ret = sh_msiof_spi_txrx_dma(p, t, bits);
+		if (ret < 0)
+			dev_warn(&spi->dev, "fall back to PIO: %d\n", ret);
+		else
+			return ret;
+	}
+
 	if (bits <= 8 && t->len > 15 && !(t->len & 3)) {
 		bits = 32;
 		swab = true;
@@ -559,10 +823,6 @@ static int sh_msiof_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
 			rx_fifo = sh_msiof_spi_read_fifo_32;
 	}
 
-	/* setup clocks (clock already enabled in chipselect()) */
-	sh_msiof_spi_set_clk_regs(p, clk_get_rate(p->clk),
-				  sh_msiof_spi_hz(spi, t));
-
 	/* transfer in fifo sized chunks */
 	words = t->len / bytes_per_word;
 	bytes_done = 0;
@@ -591,6 +851,78 @@ static u32 sh_msiof_spi_txrx_word(struct spi_device *spi, unsigned nsecs,
 	return 0;
 }
 
+static bool sh_msiof_filter(struct dma_chan *chan, void *arg)
+{
+	dev_dbg(chan->device->dev, "%s: slave data %p\n", __func__, arg);
+	chan->private = arg;
+	return true;
+}
+
+static void sh_msiof_request_dma(struct sh_msiof_spi_priv *p)
+{
+	struct sh_dmae_slave *tx = &p->dma_tx.dma_slave,
+		*rx = &p->dma_rx.dma_slave;
+	dma_cap_mask_t mask;
+
+	tx->slave_id = p->info->slave_id_tx;
+	rx->slave_id = p->info->slave_id_rx;
+
+	/* We can only either use DMA for both Tx and Rx or not use it at all */
+	if (tx->slave_id <= 0 || rx->slave_id <= 0)
+		return;
+
+	dev_warn(&p->pdev->dev, "Experimental DMA support enabled.\n");
+
+	p->dummypage = alloc_page(GFP_KERNEL);
+	if (!p->dummypage)
+		return;
+
+	dma_cap_zero(mask);
+	dma_cap_set(DMA_SLAVE, mask);
+
+	p->dma_tx.chan = dma_request_channel(mask, sh_msiof_filter, tx);
+	dev_dbg(&p->pdev->dev, "%s: TX: got channel %p\n", __func__,
+		p->dma_tx.chan);
+
+	if (!p->dma_tx.chan)
+		goto echantx;
+
+	p->dma_rx.chan = dma_request_channel(mask, sh_msiof_filter, rx);
+	dev_dbg(&p->pdev->dev, "%s: RX: got channel %p\n", __func__,
+		p->dma_rx.chan);
+
+	if (!p->dma_rx.chan)
+		goto echanrx;
+
+	init_completion(&p->dma_done);
+
+	return;
+
+echanrx:
+	dma_release_channel(p->dma_tx.chan);
+	p->dma_tx.chan = NULL;
+echantx:
+	__free_pages(p->dummypage, 0);
+}
+
+static void sh_msiof_release_dma(struct sh_msiof_spi_priv *p)
+{
+	/* Descriptors are freed automatically */
+	if (p->dma_tx.chan) {
+		struct dma_chan *chan = p->dma_tx.chan;
+		p->dma_tx.chan = NULL;
+		dma_release_channel(chan);
+	}
+	if (p->dma_rx.chan) {
+		struct dma_chan *chan = p->dma_rx.chan;
+		p->dma_rx.chan = NULL;
+		dma_release_channel(chan);
+	}
+
+	if (p->dummypage)
+		__free_pages(p->dummypage, 0);
+}
+
 static int sh_msiof_spi_probe(struct platform_device *pdev)
 {
 	struct resource	*r;
@@ -645,6 +977,8 @@ static int sh_msiof_spi_probe(struct platform_device *pdev)
 	p->pdev = pdev;
 	pm_runtime_enable(&pdev->dev);
 
+	sh_msiof_request_dma(p);
+
 	/* The standard version of MSIOF use 64 word FIFOs */
 	p->tx_fifo_size = 64;
 	p->rx_fifo_size = 64;
@@ -656,8 +990,8 @@ static int sh_msiof_spi_probe(struct platform_device *pdev)
 		p->rx_fifo_size = p->info->rx_fifo_override;
 
 	/* init master and bitbang code */
-	master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
-	master->mode_bits |= SPI_LSB_FIRST | SPI_3WIRE;
+	master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH |
+		SPI_LSB_FIRST | SPI_3WIRE;
 	master->flags = 0;
 	master->bus_num = pdev->id;
 	master->num_chipselect = p->info->num_chipselect;
@@ -677,6 +1011,7 @@ static int sh_msiof_spi_probe(struct platform_device *pdev)
 	if (ret = 0)
 		return 0;
 
+	sh_msiof_release_dma(p);
 	pm_runtime_disable(&pdev->dev);
  err3:
 	iounmap(p->mapbase);
@@ -695,11 +1030,11 @@ static int sh_msiof_spi_remove(struct platform_device *pdev)
 
 	ret = spi_bitbang_stop(&p->bitbang);
 	if (!ret) {
+		sh_msiof_release_dma(p);
 		pm_runtime_disable(&pdev->dev);
 		free_irq(platform_get_irq(pdev, 0), p);
 		iounmap(p->mapbase);
 		clk_put(p->clk);
-		spi_master_put(p->bitbang.master);
 	}
 	return ret;
 }
diff --git a/include/linux/spi/sh_msiof.h b/include/linux/spi/sh_msiof.h
index 2e8db3d..69c0f6e 100644
--- a/include/linux/spi/sh_msiof.h
+++ b/include/linux/spi/sh_msiof.h
@@ -5,6 +5,8 @@ struct sh_msiof_spi_info {
 	int tx_fifo_override;
 	int rx_fifo_override;
 	u16 num_chipselect;
+	unsigned int slave_id_tx;
+	unsigned int slave_id_rx;
 };
 
 #endif /* __SPI_SH_MSIOF_H__ */
-- 
1.7.2.5


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 1/2] SPI: spi_sh_msiof: implement DMA support
@ 2011-09-02 15:13   ` Guennadi Liakhovetski
  0 siblings, 0 replies; 18+ messages in thread
From: Guennadi Liakhovetski @ 2011-09-02 15:13 UTC (permalink / raw)
  To: spi-devel-general; +Cc: linux-sh, Grant Likely

Use the sh_dma dmaengine driver to support DMA on MSIOF.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
---
 drivers/spi/spi-sh-msiof.c   |  393 ++++++++++++++++++++++++++++++++++++++---
 include/linux/spi/sh_msiof.h |    2 +
 2 files changed, 366 insertions(+), 29 deletions(-)

diff --git a/drivers/spi/spi-sh-msiof.c b/drivers/spi/spi-sh-msiof.c
index e00d94b..8622b32 100644
--- a/drivers/spi/spi-sh-msiof.c
+++ b/drivers/spi/spi-sh-msiof.c
@@ -13,14 +13,18 @@
 #include <linux/clk.h>
 #include <linux/completion.h>
 #include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
 #include <linux/err.h>
 #include <linux/gpio.h>
 #include <linux/init.h>
 #include <linux/interrupt.h>
 #include <linux/io.h>
 #include <linux/kernel.h>
+#include <linux/mm.h>
 #include <linux/platform_device.h>
 #include <linux/pm_runtime.h>
+#include <linux/sh_dma.h>
 
 #include <linux/spi/sh_msiof.h>
 #include <linux/spi/spi.h>
@@ -28,6 +32,13 @@
 
 #include <asm/unaligned.h>
 
+struct sh_msiof_dma {
+	struct sh_dmae_slave dma_slave;
+	struct dma_chan	*chan;
+	struct sg_table	sg;
+	struct dma_async_tx_descriptor *desc;
+};
+
 struct sh_msiof_spi_priv {
 	struct spi_bitbang bitbang; /* must be first for spi_bitbang.c */
 	void __iomem *mapbase;
@@ -38,6 +49,10 @@ struct sh_msiof_spi_priv {
 	unsigned long flags;
 	int tx_fifo_size;
 	int rx_fifo_size;
+	struct sh_msiof_dma dma_tx;
+	struct sh_msiof_dma dma_rx;
+	struct completion dma_done;
+	struct page *dummypage;
 };
 
 #define TMDR1	0x00
@@ -64,8 +79,12 @@ struct sh_msiof_spi_priv {
 #define CTR_TXE   (1 << 9)
 #define CTR_RXE   (1 << 8)
 
-#define STR_TEOF  (1 << 23)
-#define STR_REOF  (1 << 7)
+#define IER_TDMA  (1 << 31)
+#define IER_TDREQ (1 << 28)
+#define IER_TEOF  (1 << 23)
+#define IER_RDMA  (1 << 15)
+#define IER_RDREQ (1 << 12)
+#define IER_REOF  (1 << 7)
 
 static u32 sh_msiof_read(struct sh_msiof_spi_priv *p, int reg_offs)
 {
@@ -208,8 +227,6 @@ static void sh_msiof_spi_set_mode_regs(struct sh_msiof_spi_priv *p,
 
 	if (rx_buf)
 		sh_msiof_write(p, RMDR2, dr2);
-
-	sh_msiof_write(p, IER, STR_TEOF | STR_REOF);
 }
 
 static void sh_msiof_reset_str(struct sh_msiof_spi_priv *p)
@@ -401,9 +418,9 @@ static void sh_msiof_spi_chipselect(struct spi_device *spi, int is_on)
 
 	/* chip select is active low unless SPI_CS_HIGH is set */
 	if (spi->mode & SPI_CS_HIGH)
-		value = (is_on == BITBANG_CS_ACTIVE) ? 1 : 0;
+		value = is_on == BITBANG_CS_ACTIVE;
 	else
-		value = (is_on == BITBANG_CS_ACTIVE) ? 0 : 1;
+		value = is_on != BITBANG_CS_ACTIVE;
 
 	if (is_on == BITBANG_CS_ACTIVE) {
 		if (!test_and_set_bit(0, &p->flags)) {
@@ -429,6 +446,36 @@ static void sh_msiof_spi_chipselect(struct spi_device *spi, int is_on)
 	}
 }
 
+static int sh_msiof_spi_start(struct sh_msiof_spi_priv *p, void *rx_buf)
+{
+	/* setup clock and rx/tx signals */
+	int ret = sh_msiof_modify_ctr_wait(p, 0, CTR_TSCKE);
+	if (rx_buf && !ret)
+		ret = sh_msiof_modify_ctr_wait(p, 0, CTR_RXE);
+	if (!ret)
+		ret = sh_msiof_modify_ctr_wait(p, 0, CTR_TXE);
+
+	/* start by setting frame bit */
+	if (!ret)
+		ret = sh_msiof_modify_ctr_wait(p, 0, CTR_TFSE);
+
+	return ret;
+}
+
+static int sh_msiof_spi_stop(struct sh_msiof_spi_priv *p, void *rx_buf)
+{
+	/* shut down frame, tx/tx and clock signals */
+	int ret = sh_msiof_modify_ctr_wait(p, CTR_TFSE, 0);
+	if (!ret)
+		ret = sh_msiof_modify_ctr_wait(p, CTR_TXE, 0);
+	if (rx_buf && !ret)
+		ret = sh_msiof_modify_ctr_wait(p, CTR_RXE, 0);
+	if (!ret)
+		ret = sh_msiof_modify_ctr_wait(p, CTR_TSCKE, 0);
+
+	return ret;
+}
+
 static int sh_msiof_spi_txrx_once(struct sh_msiof_spi_priv *p,
 				  void (*tx_fifo)(struct sh_msiof_spi_priv *,
 						  const void *, int, int),
@@ -456,16 +503,11 @@ static int sh_msiof_spi_txrx_once(struct sh_msiof_spi_priv *p,
 	if (tx_buf)
 		tx_fifo(p, tx_buf, words, fifo_shift);
 
-	/* setup clock and rx/tx signals */
-	ret = sh_msiof_modify_ctr_wait(p, 0, CTR_TSCKE);
-	if (rx_buf)
-		ret = ret ? ret : sh_msiof_modify_ctr_wait(p, 0, CTR_RXE);
-	ret = ret ? ret : sh_msiof_modify_ctr_wait(p, 0, CTR_TXE);
-
-	/* start by setting frame bit */
 	INIT_COMPLETION(p->done);
-	ret = ret ? ret : sh_msiof_modify_ctr_wait(p, 0, CTR_TFSE);
-	if (ret) {
+	sh_msiof_write(p, IER, IER_TEOF | IER_REOF);
+
+	ret = sh_msiof_spi_start(p, rx_buf);
+	if (ret < 0) {
 		dev_err(&p->pdev->dev, "failed to start hardware\n");
 		goto err;
 	}
@@ -480,13 +522,8 @@ static int sh_msiof_spi_txrx_once(struct sh_msiof_spi_priv *p,
 	/* clear status bits */
 	sh_msiof_reset_str(p);
 
-	/* shut down frame, tx/tx and clock signals */
-	ret = sh_msiof_modify_ctr_wait(p, CTR_TFSE, 0);
-	ret = ret ? ret : sh_msiof_modify_ctr_wait(p, CTR_TXE, 0);
-	if (rx_buf)
-		ret = ret ? ret : sh_msiof_modify_ctr_wait(p, CTR_RXE, 0);
-	ret = ret ? ret : sh_msiof_modify_ctr_wait(p, CTR_TSCKE, 0);
-	if (ret) {
+	ret = sh_msiof_spi_stop(p, rx_buf);
+	if (ret < 0) {
 		dev_err(&p->pdev->dev, "failed to shut down hardware\n");
 		goto err;
 	}
@@ -498,6 +535,220 @@ static int sh_msiof_spi_txrx_once(struct sh_msiof_spi_priv *p,
 	return ret;
 }
 
+static void sh_msiof_setup_sg(struct sh_msiof_spi_priv *p,
+			      const void *buffer,
+			      unsigned int length,
+			      struct sg_table *sgtab)
+{
+	struct scatterlist *sg;
+	int bytesleft = length;
+	const void *bufp = buffer;
+	int mapbytes;
+	int i;
+
+	if (buffer) {
+		for_each_sg(sgtab->sgl, sg, sgtab->nents, i) {
+			/*
+			 * If there are fewer bytes left than what fits
+			 * in the current page (plus page alignment offset)
+			 * we just feed in this, else we stuff in as much
+			 * as we can.
+			 */
+			if (bytesleft < (PAGE_SIZE - offset_in_page(bufp)))
+				mapbytes = bytesleft;
+			else
+				mapbytes = PAGE_SIZE - offset_in_page(bufp);
+			sg_set_page(sg, virt_to_page(bufp),
+				    mapbytes, offset_in_page(bufp));
+			bytesleft -= mapbytes;
+			bufp += mapbytes;
+		}
+	} else {
+		/* Map the dummy buffer on every page */
+		for_each_sg(sgtab->sgl, sg, sgtab->nents, i) {
+			if (bytesleft < PAGE_SIZE)
+				mapbytes = bytesleft;
+			else
+				mapbytes = PAGE_SIZE;
+			sg_set_page(sg, p->dummypage, mapbytes, 0);
+			bytesleft -= mapbytes;
+		}
+	}
+	BUG_ON(bytesleft);
+}
+
+static int sh_msiof_spi_setup_xfer_dma(struct sh_msiof_spi_priv *p, struct sh_msiof_dma *dma,
+				       const void *buffer, size_t len, enum dma_data_direction dir)
+{
+	int ret, sglen;
+	unsigned int pages;
+	unsigned long flags = DMA_CTRL_ACK;
+
+	/* Create sglists for the transfers */
+	pages = (PAGE_ALIGN(len) >> PAGE_SHIFT);
+
+	ret = sg_alloc_table(&dma->sg, pages, GFP_KERNEL);
+	if (ret < 0)
+		return ret;
+
+	sh_msiof_setup_sg(p, buffer, len, &dma->sg);
+
+	/* Map DMA buffers */
+	sglen = dma_map_sg(&p->pdev->dev, dma->sg.sgl,
+			   dma->sg.orig_nents, dir);
+	if (sglen < 0) {
+		ret = sglen;
+		goto emapsg;
+	} else if (!sglen) {
+		ret = -EINVAL;
+		goto emapsg;
+	}
+
+	if (dir == DMA_FROM_DEVICE)
+		flags |= DMA_PREP_INTERRUPT;
+
+	dma->desc = dma->chan->device->device_prep_slave_sg(dma->chan,
+					dma->sg.sgl, sglen, dir, flags);
+	if (!dma->desc) {
+		ret = -ENOMEM;
+		goto edmaprep;
+	}
+
+	dma->sg.nents = sglen;
+
+	return 0;
+
+edmaprep:
+	dma_unmap_sg(&p->pdev->dev, dma->sg.sgl, sglen, dir);
+emapsg:
+	sg_free_table(&dma->sg);
+
+	return ret;
+}
+
+static void sh_msiof_spi_free_xfer_dma(struct sh_msiof_spi_priv *p, struct sh_msiof_dma *dma,
+				       enum dma_data_direction dir)
+{
+	dma_unmap_sg(&p->pdev->dev, dma->sg.sgl, dma->sg.nents, dir);
+	sg_free_table(&dma->sg);
+}
+
+static void sh_msiof_spi_dma_callback(void *arg)
+{
+	struct sh_msiof_spi_priv *p = arg;
+
+	dma_sync_sg_for_device(&p->pdev->dev, p->dma_rx.sg.sgl,
+			       p->dma_rx.sg.nents, DMA_FROM_DEVICE);
+
+	sh_msiof_spi_free_xfer_dma(p, &p->dma_rx, DMA_FROM_DEVICE);
+	sh_msiof_spi_free_xfer_dma(p, &p->dma_tx, DMA_TO_DEVICE);
+
+	complete(&p->dma_done);
+}
+
+static int sh_msiof_spi_txrx_dma_single(struct sh_msiof_spi_priv *p, int bits,
+					const void *tx_buf, const void *rx_buf, size_t len)
+{
+	int ret, cookie, words;
+
+	if (bits <= 8)
+		words = len;
+	else if (bits <= 16)
+		words = len >> 1;
+	else
+		words = len >> 2;
+
+	ret = sh_msiof_spi_setup_xfer_dma(p, &p->dma_tx, tx_buf, len, DMA_TO_DEVICE);
+	if (ret < 0)
+		return ret;
+
+	dma_sync_sg_for_device(&p->pdev->dev, p->dma_tx.sg.sgl,
+			       p->dma_tx.sg.nents, DMA_TO_DEVICE);
+
+	ret = sh_msiof_spi_setup_xfer_dma(p, &p->dma_rx, rx_buf, len, DMA_FROM_DEVICE);
+	if (ret < 0)
+		goto esdmarx;
+
+	/* Put the callback on the RX transfer only, that should finish last */
+	p->dma_rx.desc->callback = sh_msiof_spi_dma_callback;
+	p->dma_rx.desc->callback_param = p;
+
+	/* Submit and fire RX and TX with TX last so we're ready to read! */
+	cookie = p->dma_rx.desc->tx_submit(p->dma_rx.desc);
+	if (dma_submit_error(cookie)) {
+		ret = cookie;
+		goto esubmitrx;
+	}
+	p->dma_rx.chan->device->device_issue_pending(p->dma_rx.chan);
+
+	/* setup msiof transfer mode registers */
+	sh_msiof_spi_set_mode_regs(p, tx_buf, p->dummypage, bits, words);
+
+	cookie = p->dma_tx.desc->tx_submit(p->dma_tx.desc);
+	if (dma_submit_error(cookie)) {
+		ret = cookie;
+		goto esubmittx;
+	}
+	p->dma_tx.chan->device->device_issue_pending(p->dma_tx.chan);
+
+	INIT_COMPLETION(p->dma_done);
+	sh_msiof_write(p, IER, IER_RDREQ | IER_TDREQ | IER_RDMA | IER_TDMA);
+
+	/* As long as there's something to send, we'll also be receiving. */
+	ret = sh_msiof_spi_start(p, p->dummypage);
+	if (ret < 0) {
+		dev_err(&p->pdev->dev, "failed to start hardware: %d\n", ret);
+		goto espistart;
+	}
+
+	wait_for_completion(&p->dma_done);
+
+	/* clear status bits */
+	sh_msiof_reset_str(p);
+
+	ret = sh_msiof_spi_stop(p, p->dummypage);
+	if (ret < 0) {
+		dev_err(&p->pdev->dev, "failed to stop hardware: %d\n", ret);
+		goto espistop;
+	}
+
+	return len;
+
+espistop:
+espistart:
+	dmaengine_device_control(p->dma_tx.chan, DMA_TERMINATE_ALL, 0);
+esubmittx:
+	dmaengine_device_control(p->dma_rx.chan, DMA_TERMINATE_ALL, 0);
+esubmitrx:
+	sh_msiof_spi_free_xfer_dma(p, &p->dma_rx, DMA_FROM_DEVICE);
+esdmarx:
+	sh_msiof_spi_free_xfer_dma(p, &p->dma_tx, DMA_TO_DEVICE);
+
+	return ret;
+}
+
+static int sh_msiof_spi_txrx_dma(struct sh_msiof_spi_priv *p, struct spi_transfer *t, int bits)
+{
+	int i, ret = 0;
+	size_t left = t->len;
+	off_t offs = 0;
+
+	/* Up to 256 words per group, we only use a single group */
+	for (i = 0; i < (t->len + 255) / 256; i++) {
+		const void *rx_buf = t->rx_buf ? t->rx_buf + offs : NULL;
+		const void *tx_buf = t->tx_buf ? t->tx_buf + offs : NULL;
+
+		ret = sh_msiof_spi_txrx_dma_single(p, bits, tx_buf, rx_buf,
+						   min_t(size_t, left, 256));
+		if (ret < 0)
+			return ret;
+		left -= ret;
+		offs += ret;
+	}
+
+	return ret <= 0 ? ret : t->len;
+}
+
 static int sh_msiof_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
 {
 	struct sh_msiof_spi_priv *p = spi_master_get_devdata(spi->master);
@@ -512,6 +763,19 @@ static int sh_msiof_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
 
 	bits = sh_msiof_spi_bits(spi, t);
 
+	/* setup clocks (clock already enabled in chipselect()) */
+	sh_msiof_spi_set_clk_regs(p, clk_get_rate(p->clk),
+				  sh_msiof_spi_hz(spi, t));
+
+	if (p->dma_rx.chan && p->dma_tx.chan && t->len > 15 &&
+	    !(t->len & 3) && !((int)t->rx_buf & 3) && !((int)t->tx_buf & 3)) {
+		int ret = sh_msiof_spi_txrx_dma(p, t, bits);
+		if (ret < 0)
+			dev_warn(&spi->dev, "fall back to PIO: %d\n", ret);
+		else
+			return ret;
+	}
+
 	if (bits <= 8 && t->len > 15 && !(t->len & 3)) {
 		bits = 32;
 		swab = true;
@@ -559,10 +823,6 @@ static int sh_msiof_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
 			rx_fifo = sh_msiof_spi_read_fifo_32;
 	}
 
-	/* setup clocks (clock already enabled in chipselect()) */
-	sh_msiof_spi_set_clk_regs(p, clk_get_rate(p->clk),
-				  sh_msiof_spi_hz(spi, t));
-
 	/* transfer in fifo sized chunks */
 	words = t->len / bytes_per_word;
 	bytes_done = 0;
@@ -591,6 +851,78 @@ static u32 sh_msiof_spi_txrx_word(struct spi_device *spi, unsigned nsecs,
 	return 0;
 }
 
+static bool sh_msiof_filter(struct dma_chan *chan, void *arg)
+{
+	dev_dbg(chan->device->dev, "%s: slave data %p\n", __func__, arg);
+	chan->private = arg;
+	return true;
+}
+
+static void sh_msiof_request_dma(struct sh_msiof_spi_priv *p)
+{
+	struct sh_dmae_slave *tx = &p->dma_tx.dma_slave,
+		*rx = &p->dma_rx.dma_slave;
+	dma_cap_mask_t mask;
+
+	tx->slave_id = p->info->slave_id_tx;
+	rx->slave_id = p->info->slave_id_rx;
+
+	/* We can only either use DMA for both Tx and Rx or not use it at all */
+	if (tx->slave_id <= 0 || rx->slave_id <= 0)
+		return;
+
+	dev_warn(&p->pdev->dev, "Experimental DMA support enabled.\n");
+
+	p->dummypage = alloc_page(GFP_KERNEL);
+	if (!p->dummypage)
+		return;
+
+	dma_cap_zero(mask);
+	dma_cap_set(DMA_SLAVE, mask);
+
+	p->dma_tx.chan = dma_request_channel(mask, sh_msiof_filter, tx);
+	dev_dbg(&p->pdev->dev, "%s: TX: got channel %p\n", __func__,
+		p->dma_tx.chan);
+
+	if (!p->dma_tx.chan)
+		goto echantx;
+
+	p->dma_rx.chan = dma_request_channel(mask, sh_msiof_filter, rx);
+	dev_dbg(&p->pdev->dev, "%s: RX: got channel %p\n", __func__,
+		p->dma_rx.chan);
+
+	if (!p->dma_rx.chan)
+		goto echanrx;
+
+	init_completion(&p->dma_done);
+
+	return;
+
+echanrx:
+	dma_release_channel(p->dma_tx.chan);
+	p->dma_tx.chan = NULL;
+echantx:
+	__free_pages(p->dummypage, 0);
+}
+
+static void sh_msiof_release_dma(struct sh_msiof_spi_priv *p)
+{
+	/* Descriptors are freed automatically */
+	if (p->dma_tx.chan) {
+		struct dma_chan *chan = p->dma_tx.chan;
+		p->dma_tx.chan = NULL;
+		dma_release_channel(chan);
+	}
+	if (p->dma_rx.chan) {
+		struct dma_chan *chan = p->dma_rx.chan;
+		p->dma_rx.chan = NULL;
+		dma_release_channel(chan);
+	}
+
+	if (p->dummypage)
+		__free_pages(p->dummypage, 0);
+}
+
 static int sh_msiof_spi_probe(struct platform_device *pdev)
 {
 	struct resource	*r;
@@ -645,6 +977,8 @@ static int sh_msiof_spi_probe(struct platform_device *pdev)
 	p->pdev = pdev;
 	pm_runtime_enable(&pdev->dev);
 
+	sh_msiof_request_dma(p);
+
 	/* The standard version of MSIOF use 64 word FIFOs */
 	p->tx_fifo_size = 64;
 	p->rx_fifo_size = 64;
@@ -656,8 +990,8 @@ static int sh_msiof_spi_probe(struct platform_device *pdev)
 		p->rx_fifo_size = p->info->rx_fifo_override;
 
 	/* init master and bitbang code */
-	master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
-	master->mode_bits |= SPI_LSB_FIRST | SPI_3WIRE;
+	master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH |
+		SPI_LSB_FIRST | SPI_3WIRE;
 	master->flags = 0;
 	master->bus_num = pdev->id;
 	master->num_chipselect = p->info->num_chipselect;
@@ -677,6 +1011,7 @@ static int sh_msiof_spi_probe(struct platform_device *pdev)
 	if (ret == 0)
 		return 0;
 
+	sh_msiof_release_dma(p);
 	pm_runtime_disable(&pdev->dev);
  err3:
 	iounmap(p->mapbase);
@@ -695,11 +1030,11 @@ static int sh_msiof_spi_remove(struct platform_device *pdev)
 
 	ret = spi_bitbang_stop(&p->bitbang);
 	if (!ret) {
+		sh_msiof_release_dma(p);
 		pm_runtime_disable(&pdev->dev);
 		free_irq(platform_get_irq(pdev, 0), p);
 		iounmap(p->mapbase);
 		clk_put(p->clk);
-		spi_master_put(p->bitbang.master);
 	}
 	return ret;
 }
diff --git a/include/linux/spi/sh_msiof.h b/include/linux/spi/sh_msiof.h
index 2e8db3d..69c0f6e 100644
--- a/include/linux/spi/sh_msiof.h
+++ b/include/linux/spi/sh_msiof.h
@@ -5,6 +5,8 @@ struct sh_msiof_spi_info {
 	int tx_fifo_override;
 	int rx_fifo_override;
 	u16 num_chipselect;
+	unsigned int slave_id_tx;
+	unsigned int slave_id_rx;
 };
 
 #endif /* __SPI_SH_MSIOF_H__ */
-- 
1.7.2.5


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 2/2] sh: use DMA with MSIOF SPI on the sh7724 ecovec board
  2011-09-02 15:13 ` Guennadi Liakhovetski
@ 2011-09-02 15:13   ` Guennadi Liakhovetski
  -1 siblings, 0 replies; 18+ messages in thread
From: Guennadi Liakhovetski @ 2011-09-02 15:13 UTC (permalink / raw)
  To: spi-devel-general; +Cc: linux-sh, Grant Likely

On ecovec MSIOF0 is connected to an SD/MMC slot, this patch adds DMA
support on it.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
---
 arch/sh/boards/mach-ecovec24/setup.c   |    8 +++++++-
 arch/sh/include/cpu-sh4/cpu/sh7724.h   |    4 ++++
 arch/sh/kernel/cpu/sh4a/setup-sh7724.c |   20 ++++++++++++++++++++
 3 files changed, 31 insertions(+), 1 deletions(-)

diff --git a/arch/sh/boards/mach-ecovec24/setup.c b/arch/sh/boards/mach-ecovec24/setup.c
index 22faf2a..16747a8 100644
--- a/arch/sh/boards/mach-ecovec24/setup.c
+++ b/arch/sh/boards/mach-ecovec24/setup.c
@@ -10,6 +10,7 @@
 
 #include <linux/init.h>
 #include <linux/device.h>
+#include <linux/dma-mapping.h>
 #include <linux/platform_device.h>
 #include <linux/mmc/host.h>
 #include <linux/mmc/sh_mmcif.h>
@@ -653,6 +654,8 @@ static struct spi_board_info spi_bus[] = {
 /* MSIOF0 */
 static struct sh_msiof_spi_info msiof0_data = {
 	.num_chipselect = 1,
+	.slave_id_tx = SHDMA_SLAVE_MSIOF0_TX,
+	.slave_id_rx = SHDMA_SLAVE_MSIOF0_RX,
 };
 
 static struct resource msiof0_resources[] = {
@@ -668,11 +671,14 @@ static struct resource msiof0_resources[] = {
 	},
 };
 
+u64 msiof0_dmamask = DMA_BIT_MASK(32);
+
 static struct platform_device msiof0_device = {
 	.name		= "spi_sh_msiof",
 	.id		= 0, /* MSIOF0 */
 	.dev = {
-		.platform_data = &msiof0_data,
+		.platform_data	= &msiof0_data,
+		.dma_mask	= &msiof0_dmamask,
 	},
 	.num_resources	= ARRAY_SIZE(msiof0_resources),
 	.resource	= msiof0_resources,
diff --git a/arch/sh/include/cpu-sh4/cpu/sh7724.h b/arch/sh/include/cpu-sh4/cpu/sh7724.h
index cbc47e6..10f9292 100644
--- a/arch/sh/include/cpu-sh4/cpu/sh7724.h
+++ b/arch/sh/include/cpu-sh4/cpu/sh7724.h
@@ -310,6 +310,10 @@ enum {
 	SHDMA_SLAVE_SDHI0_RX,
 	SHDMA_SLAVE_SDHI1_TX,
 	SHDMA_SLAVE_SDHI1_RX,
+	SHDMA_SLAVE_MSIOF0_TX,
+	SHDMA_SLAVE_MSIOF0_RX,
+	SHDMA_SLAVE_MSIOF1_TX,
+	SHDMA_SLAVE_MSIOF1_RX,
 };
 
 extern struct clk sh7724_fsimcka_clk;
diff --git a/arch/sh/kernel/cpu/sh4a/setup-sh7724.c b/arch/sh/kernel/cpu/sh4a/setup-sh7724.c
index a37dd72..d6626fe 100644
--- a/arch/sh/kernel/cpu/sh4a/setup-sh7724.c
+++ b/arch/sh/kernel/cpu/sh4a/setup-sh7724.c
@@ -152,6 +152,26 @@ static const struct sh_dmae_slave_config sh7724_dmae_slaves[] = {
 		.addr		= 0x04cf0030,
 		.chcr		= DM_INC | SM_FIX | 0x800 | TS_INDEX2VAL(XMIT_SZ_16BIT),
 		.mid_rid	= 0xca,
+	}, {
+		.slave_id	= SHDMA_SLAVE_MSIOF0_TX,
+		.addr		= 0xa4c40050,
+		.chcr		= DM_FIX | SM_INC | 0x800 | TS_INDEX2VAL(XMIT_SZ_8BIT),
+		.mid_rid	= 0x51,
+	}, {
+		.slave_id	= SHDMA_SLAVE_MSIOF0_RX,
+		.addr		= 0xa4c40060,
+		.chcr		= DM_INC | SM_FIX | 0x800 | TS_INDEX2VAL(XMIT_SZ_8BIT),
+		.mid_rid	= 0x52,
+	}, {
+		.slave_id	= SHDMA_SLAVE_MSIOF1_TX,
+		.addr		= 0xa4c50050,
+		.chcr		= DM_FIX | SM_INC | 0x800 | TS_INDEX2VAL(XMIT_SZ_8BIT),
+		.mid_rid	= 0x55,
+	}, {
+		.slave_id	= SHDMA_SLAVE_MSIOF1_RX,
+		.addr		= 0xa4c50060,
+		.chcr		= DM_INC | SM_FIX | 0x800 | TS_INDEX2VAL(XMIT_SZ_8BIT),
+		.mid_rid	= 0x56,
 	},
 };
 
-- 
1.7.2.5


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 2/2] sh: use DMA with MSIOF SPI on the sh7724 ecovec board
@ 2011-09-02 15:13   ` Guennadi Liakhovetski
  0 siblings, 0 replies; 18+ messages in thread
From: Guennadi Liakhovetski @ 2011-09-02 15:13 UTC (permalink / raw)
  To: spi-devel-general; +Cc: linux-sh, Grant Likely

On ecovec MSIOF0 is connected to an SD/MMC slot, this patch adds DMA
support on it.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
---
 arch/sh/boards/mach-ecovec24/setup.c   |    8 +++++++-
 arch/sh/include/cpu-sh4/cpu/sh7724.h   |    4 ++++
 arch/sh/kernel/cpu/sh4a/setup-sh7724.c |   20 ++++++++++++++++++++
 3 files changed, 31 insertions(+), 1 deletions(-)

diff --git a/arch/sh/boards/mach-ecovec24/setup.c b/arch/sh/boards/mach-ecovec24/setup.c
index 22faf2a..16747a8 100644
--- a/arch/sh/boards/mach-ecovec24/setup.c
+++ b/arch/sh/boards/mach-ecovec24/setup.c
@@ -10,6 +10,7 @@
 
 #include <linux/init.h>
 #include <linux/device.h>
+#include <linux/dma-mapping.h>
 #include <linux/platform_device.h>
 #include <linux/mmc/host.h>
 #include <linux/mmc/sh_mmcif.h>
@@ -653,6 +654,8 @@ static struct spi_board_info spi_bus[] = {
 /* MSIOF0 */
 static struct sh_msiof_spi_info msiof0_data = {
 	.num_chipselect = 1,
+	.slave_id_tx = SHDMA_SLAVE_MSIOF0_TX,
+	.slave_id_rx = SHDMA_SLAVE_MSIOF0_RX,
 };
 
 static struct resource msiof0_resources[] = {
@@ -668,11 +671,14 @@ static struct resource msiof0_resources[] = {
 	},
 };
 
+u64 msiof0_dmamask = DMA_BIT_MASK(32);
+
 static struct platform_device msiof0_device = {
 	.name		= "spi_sh_msiof",
 	.id		= 0, /* MSIOF0 */
 	.dev = {
-		.platform_data = &msiof0_data,
+		.platform_data	= &msiof0_data,
+		.dma_mask	= &msiof0_dmamask,
 	},
 	.num_resources	= ARRAY_SIZE(msiof0_resources),
 	.resource	= msiof0_resources,
diff --git a/arch/sh/include/cpu-sh4/cpu/sh7724.h b/arch/sh/include/cpu-sh4/cpu/sh7724.h
index cbc47e6..10f9292 100644
--- a/arch/sh/include/cpu-sh4/cpu/sh7724.h
+++ b/arch/sh/include/cpu-sh4/cpu/sh7724.h
@@ -310,6 +310,10 @@ enum {
 	SHDMA_SLAVE_SDHI0_RX,
 	SHDMA_SLAVE_SDHI1_TX,
 	SHDMA_SLAVE_SDHI1_RX,
+	SHDMA_SLAVE_MSIOF0_TX,
+	SHDMA_SLAVE_MSIOF0_RX,
+	SHDMA_SLAVE_MSIOF1_TX,
+	SHDMA_SLAVE_MSIOF1_RX,
 };
 
 extern struct clk sh7724_fsimcka_clk;
diff --git a/arch/sh/kernel/cpu/sh4a/setup-sh7724.c b/arch/sh/kernel/cpu/sh4a/setup-sh7724.c
index a37dd72..d6626fe 100644
--- a/arch/sh/kernel/cpu/sh4a/setup-sh7724.c
+++ b/arch/sh/kernel/cpu/sh4a/setup-sh7724.c
@@ -152,6 +152,26 @@ static const struct sh_dmae_slave_config sh7724_dmae_slaves[] = {
 		.addr		= 0x04cf0030,
 		.chcr		= DM_INC | SM_FIX | 0x800 | TS_INDEX2VAL(XMIT_SZ_16BIT),
 		.mid_rid	= 0xca,
+	}, {
+		.slave_id	= SHDMA_SLAVE_MSIOF0_TX,
+		.addr		= 0xa4c40050,
+		.chcr		= DM_FIX | SM_INC | 0x800 | TS_INDEX2VAL(XMIT_SZ_8BIT),
+		.mid_rid	= 0x51,
+	}, {
+		.slave_id	= SHDMA_SLAVE_MSIOF0_RX,
+		.addr		= 0xa4c40060,
+		.chcr		= DM_INC | SM_FIX | 0x800 | TS_INDEX2VAL(XMIT_SZ_8BIT),
+		.mid_rid	= 0x52,
+	}, {
+		.slave_id	= SHDMA_SLAVE_MSIOF1_TX,
+		.addr		= 0xa4c50050,
+		.chcr		= DM_FIX | SM_INC | 0x800 | TS_INDEX2VAL(XMIT_SZ_8BIT),
+		.mid_rid	= 0x55,
+	}, {
+		.slave_id	= SHDMA_SLAVE_MSIOF1_RX,
+		.addr		= 0xa4c50060,
+		.chcr		= DM_INC | SM_FIX | 0x800 | TS_INDEX2VAL(XMIT_SZ_8BIT),
+		.mid_rid	= 0x56,
 	},
 };
 
-- 
1.7.2.5


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* Re: [PATCH 1/2] SPI: spi_sh_msiof: implement DMA support
  2011-09-02 15:13   ` Guennadi Liakhovetski
@ 2011-09-05  4:59     ` Paul Mundt
  -1 siblings, 0 replies; 18+ messages in thread
From: Paul Mundt @ 2011-09-05  4:59 UTC (permalink / raw)
  To: Guennadi Liakhovetski; +Cc: spi-devel-general, linux-sh, Grant Likely

On Fri, Sep 02, 2011 at 05:13:31PM +0200, Guennadi Liakhovetski wrote:
> Use the sh_dma dmaengine driver to support DMA on MSIOF.
> 
> Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>

No real opinion one way or the other, just a couple observations.

> +static void sh_msiof_request_dma(struct sh_msiof_spi_priv *p)
> +{
..
> +	p->dummypage = alloc_page(GFP_KERNEL);
> +	if (!p->dummypage)
> +		return;
> +
..
> +echantx:
> +	__free_pages(p->dummypage, 0);
> +}
> +
alloc_page() can be balanced out with __free_page().

> @@ -695,11 +1030,11 @@ static int sh_msiof_spi_remove(struct platform_device *pdev)
>  
>  	ret = spi_bitbang_stop(&p->bitbang);
>  	if (!ret) {
> +		sh_msiof_release_dma(p);
>  		pm_runtime_disable(&pdev->dev);
>  		free_irq(platform_get_irq(pdev, 0), p);
>  		iounmap(p->mapbase);
>  		clk_put(p->clk);
> -		spi_master_put(p->bitbang.master);
>  	}
>  	return ret;
>  }

You've also killed off the spi_master_put() here.

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH 1/2] SPI: spi_sh_msiof: implement DMA support
@ 2011-09-05  4:59     ` Paul Mundt
  0 siblings, 0 replies; 18+ messages in thread
From: Paul Mundt @ 2011-09-05  4:59 UTC (permalink / raw)
  To: Guennadi Liakhovetski; +Cc: spi-devel-general, linux-sh, Grant Likely

On Fri, Sep 02, 2011 at 05:13:31PM +0200, Guennadi Liakhovetski wrote:
> Use the sh_dma dmaengine driver to support DMA on MSIOF.
> 
> Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>

No real opinion one way or the other, just a couple observations.

> +static void sh_msiof_request_dma(struct sh_msiof_spi_priv *p)
> +{
..
> +	p->dummypage = alloc_page(GFP_KERNEL);
> +	if (!p->dummypage)
> +		return;
> +
..
> +echantx:
> +	__free_pages(p->dummypage, 0);
> +}
> +
alloc_page() can be balanced out with __free_page().

> @@ -695,11 +1030,11 @@ static int sh_msiof_spi_remove(struct platform_device *pdev)
>  
>  	ret = spi_bitbang_stop(&p->bitbang);
>  	if (!ret) {
> +		sh_msiof_release_dma(p);
>  		pm_runtime_disable(&pdev->dev);
>  		free_irq(platform_get_irq(pdev, 0), p);
>  		iounmap(p->mapbase);
>  		clk_put(p->clk);
> -		spi_master_put(p->bitbang.master);
>  	}
>  	return ret;
>  }

You've also killed off the spi_master_put() here.

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH 1/2] SPI: spi_sh_msiof: implement DMA support
  2011-09-05  4:59     ` Paul Mundt
@ 2011-09-05  7:26       ` Guennadi Liakhovetski
  -1 siblings, 0 replies; 18+ messages in thread
From: Guennadi Liakhovetski @ 2011-09-05  7:26 UTC (permalink / raw)
  To: Paul Mundt; +Cc: spi-devel-general, linux-sh, Grant Likely

Hi Paul

On Mon, 5 Sep 2011, Paul Mundt wrote:

> On Fri, Sep 02, 2011 at 05:13:31PM +0200, Guennadi Liakhovetski wrote:
> > Use the sh_dma dmaengine driver to support DMA on MSIOF.
> > 
> > Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
> 
> No real opinion one way or the other, just a couple observations.
> 
> > +static void sh_msiof_request_dma(struct sh_msiof_spi_priv *p)
> > +{
> ..
> > +	p->dummypage = alloc_page(GFP_KERNEL);
> > +	if (!p->dummypage)
> > +		return;
> > +
> ..
> > +echantx:
> > +	__free_pages(p->dummypage, 0);
> > +}
> > +
> alloc_page() can be balanced out with __free_page().

Hm, indeed, wondering, how I missed it.

> > @@ -695,11 +1030,11 @@ static int sh_msiof_spi_remove(struct platform_device *pdev)
> >  
> >  	ret = spi_bitbang_stop(&p->bitbang);
> >  	if (!ret) {
> > +		sh_msiof_release_dma(p);
> >  		pm_runtime_disable(&pdev->dev);
> >  		free_irq(platform_get_irq(pdev, 0), p);
> >  		iounmap(p->mapbase);
> >  		clk_put(p->clk);
> > -		spi_master_put(p->bitbang.master);
> >  	}
> >  	return ret;
> >  }
> 
> You've also killed off the spi_master_put() here.

Yes, spi_bitbang_stop() does it all already.

Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer
http://www.open-technology.de/

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH 1/2] SPI: spi_sh_msiof: implement DMA support
@ 2011-09-05  7:26       ` Guennadi Liakhovetski
  0 siblings, 0 replies; 18+ messages in thread
From: Guennadi Liakhovetski @ 2011-09-05  7:26 UTC (permalink / raw)
  To: Paul Mundt; +Cc: spi-devel-general, linux-sh, Grant Likely

Hi Paul

On Mon, 5 Sep 2011, Paul Mundt wrote:

> On Fri, Sep 02, 2011 at 05:13:31PM +0200, Guennadi Liakhovetski wrote:
> > Use the sh_dma dmaengine driver to support DMA on MSIOF.
> > 
> > Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
> 
> No real opinion one way or the other, just a couple observations.
> 
> > +static void sh_msiof_request_dma(struct sh_msiof_spi_priv *p)
> > +{
> ..
> > +	p->dummypage = alloc_page(GFP_KERNEL);
> > +	if (!p->dummypage)
> > +		return;
> > +
> ..
> > +echantx:
> > +	__free_pages(p->dummypage, 0);
> > +}
> > +
> alloc_page() can be balanced out with __free_page().

Hm, indeed, wondering, how I missed it.

> > @@ -695,11 +1030,11 @@ static int sh_msiof_spi_remove(struct platform_device *pdev)
> >  
> >  	ret = spi_bitbang_stop(&p->bitbang);
> >  	if (!ret) {
> > +		sh_msiof_release_dma(p);
> >  		pm_runtime_disable(&pdev->dev);
> >  		free_irq(platform_get_irq(pdev, 0), p);
> >  		iounmap(p->mapbase);
> >  		clk_put(p->clk);
> > -		spi_master_put(p->bitbang.master);
> >  	}
> >  	return ret;
> >  }
> 
> You've also killed off the spi_master_put() here.

Yes, spi_bitbang_stop() does it all already.

Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer
http://www.open-technology.de/

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH 1/2] SPI: spi_sh_msiof: implement DMA support
  2011-09-05  7:26       ` Guennadi Liakhovetski
@ 2011-09-06 22:16         ` Magnus Damm
  -1 siblings, 0 replies; 18+ messages in thread
From: Magnus Damm @ 2011-09-06 22:16 UTC (permalink / raw)
  To: Guennadi Liakhovetski; +Cc: Paul Mundt, spi-devel-general, linux-sh

On Mon, Sep 5, 2011 at 4:26 PM, Guennadi Liakhovetski
<g.liakhovetski@gmx.de> wrote:
> On Mon, 5 Sep 2011, Paul Mundt wrote:
>> On Fri, Sep 02, 2011 at 05:13:31PM +0200, Guennadi Liakhovetski wrote:
>> > Use the sh_dma dmaengine driver to support DMA on MSIOF.
>> >
>> > Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>

>> > @@ -695,11 +1030,11 @@ static int sh_msiof_spi_remove(struct platform_device *pdev)
>> >
>> >     ret = spi_bitbang_stop(&p->bitbang);
>> >     if (!ret) {
>> > +           sh_msiof_release_dma(p);
>> >             pm_runtime_disable(&pdev->dev);
>> >             free_irq(platform_get_irq(pdev, 0), p);
>> >             iounmap(p->mapbase);
>> >             clk_put(p->clk);
>> > -           spi_master_put(p->bitbang.master);
>> >     }
>> >     return ret;
>> >  }
>>
>> You've also killed off the spi_master_put() here.
>
> Yes, spi_bitbang_stop() does it all already.

So it's a bug fix built-in to a feature patch? If so, please break out
and submit as a fix.

Thanks,

/ magnus

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH 1/2] SPI: spi_sh_msiof: implement DMA support
@ 2011-09-06 22:16         ` Magnus Damm
  0 siblings, 0 replies; 18+ messages in thread
From: Magnus Damm @ 2011-09-06 22:16 UTC (permalink / raw)
  To: Guennadi Liakhovetski; +Cc: Paul Mundt, spi-devel-general, linux-sh

On Mon, Sep 5, 2011 at 4:26 PM, Guennadi Liakhovetski
<g.liakhovetski@gmx.de> wrote:
> On Mon, 5 Sep 2011, Paul Mundt wrote:
>> On Fri, Sep 02, 2011 at 05:13:31PM +0200, Guennadi Liakhovetski wrote:
>> > Use the sh_dma dmaengine driver to support DMA on MSIOF.
>> >
>> > Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>

>> > @@ -695,11 +1030,11 @@ static int sh_msiof_spi_remove(struct platform_device *pdev)
>> >
>> >     ret = spi_bitbang_stop(&p->bitbang);
>> >     if (!ret) {
>> > +           sh_msiof_release_dma(p);
>> >             pm_runtime_disable(&pdev->dev);
>> >             free_irq(platform_get_irq(pdev, 0), p);
>> >             iounmap(p->mapbase);
>> >             clk_put(p->clk);
>> > -           spi_master_put(p->bitbang.master);
>> >     }
>> >     return ret;
>> >  }
>>
>> You've also killed off the spi_master_put() here.
>
> Yes, spi_bitbang_stop() does it all already.

So it's a bug fix built-in to a feature patch? If so, please break out
and submit as a fix.

Thanks,

/ magnus

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH 0/2 v2] spi: MSIOF: unbalanced spi_master_put() + DMA
  2011-09-02 15:13   ` Guennadi Liakhovetski
@ 2011-09-12  9:27     ` Guennadi Liakhovetski
  -1 siblings, 0 replies; 18+ messages in thread
From: Guennadi Liakhovetski @ 2011-09-12  9:27 UTC (permalink / raw)
  To: spi-devel-general; +Cc: linux-sh, Grant Likely, Magnus Damm

Hi

As requested by Magnus, I'm splitting v1 of the "SPI: spi_sh_msiof: 
implement DMA support" patch

http://marc.info/?l=linux-sh&m\x131497642316782&w=2

in two, separating the "unbalanced spi_master_put()" fix into a separate 
patch. Otherwise no changes. Patch "sh: use DMA with MSIOF SPI on the 
sh7724 ecovec board"

http://marc.info/?l=linux-sh&m\x131497642716790&w=2

is unchanged, so, not re-posted.

Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer
http://www.open-technology.de/

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH 0/2 v2] spi: MSIOF: unbalanced spi_master_put() + DMA
@ 2011-09-12  9:27     ` Guennadi Liakhovetski
  0 siblings, 0 replies; 18+ messages in thread
From: Guennadi Liakhovetski @ 2011-09-12  9:27 UTC (permalink / raw)
  To: spi-devel-general; +Cc: linux-sh, Grant Likely, Magnus Damm

Hi

As requested by Magnus, I'm splitting v1 of the "SPI: spi_sh_msiof: 
implement DMA support" patch

http://marc.info/?l=linux-sh&m=131497642316782&w=2

in two, separating the "unbalanced spi_master_put()" fix into a separate 
patch. Otherwise no changes. Patch "sh: use DMA with MSIOF SPI on the 
sh7724 ecovec board"

http://marc.info/?l=linux-sh&m=131497642716790&w=2

is unchanged, so, not re-posted.

Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer
http://www.open-technology.de/

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH 1/2 v2] SPI: spi-sh-msiof: remove unbalanced spi_master_put()
  2011-09-12  9:27     ` Guennadi Liakhovetski
@ 2011-09-12  9:27       ` Guennadi Liakhovetski
  -1 siblings, 0 replies; 18+ messages in thread
From: Guennadi Liakhovetski @ 2011-09-12  9:27 UTC (permalink / raw)
  To: spi-devel-general; +Cc: linux-sh, Grant Likely, Magnus Damm

The call to spi_bitbang_stop() in sh_msiof_spi_remove() already releases
the master, which can be verified by checking, that the
spi_master_release() function gets called. Therefore the call to
spi_master_put() in sh_msiof_spi_remove() in this driver is superfluous.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
---
 drivers/spi/spi-sh-msiof.c |    1 -
 1 files changed, 0 insertions(+), 1 deletions(-)

diff --git a/drivers/spi/spi-sh-msiof.c b/drivers/spi/spi-sh-msiof.c
index e00d94b..722dad5 100644
--- a/drivers/spi/spi-sh-msiof.c
+++ b/drivers/spi/spi-sh-msiof.c
@@ -699,7 +699,6 @@ static int sh_msiof_spi_remove(struct platform_device *pdev)
 		free_irq(platform_get_irq(pdev, 0), p);
 		iounmap(p->mapbase);
 		clk_put(p->clk);
-		spi_master_put(p->bitbang.master);
 	}
 	return ret;
 }
-- 
1.7.2.5


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 1/2 v2] SPI: spi-sh-msiof: remove unbalanced spi_master_put()
@ 2011-09-12  9:27       ` Guennadi Liakhovetski
  0 siblings, 0 replies; 18+ messages in thread
From: Guennadi Liakhovetski @ 2011-09-12  9:27 UTC (permalink / raw)
  To: spi-devel-general; +Cc: linux-sh, Grant Likely, Magnus Damm

The call to spi_bitbang_stop() in sh_msiof_spi_remove() already releases
the master, which can be verified by checking, that the
spi_master_release() function gets called. Therefore the call to
spi_master_put() in sh_msiof_spi_remove() in this driver is superfluous.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
---
 drivers/spi/spi-sh-msiof.c |    1 -
 1 files changed, 0 insertions(+), 1 deletions(-)

diff --git a/drivers/spi/spi-sh-msiof.c b/drivers/spi/spi-sh-msiof.c
index e00d94b..722dad5 100644
--- a/drivers/spi/spi-sh-msiof.c
+++ b/drivers/spi/spi-sh-msiof.c
@@ -699,7 +699,6 @@ static int sh_msiof_spi_remove(struct platform_device *pdev)
 		free_irq(platform_get_irq(pdev, 0), p);
 		iounmap(p->mapbase);
 		clk_put(p->clk);
-		spi_master_put(p->bitbang.master);
 	}
 	return ret;
 }
-- 
1.7.2.5


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 2/2 v2] SPI: spi_sh_msiof: implement DMA support
  2011-09-12  9:27     ` Guennadi Liakhovetski
@ 2011-09-12  9:27       ` Guennadi Liakhovetski
  -1 siblings, 0 replies; 18+ messages in thread
From: Guennadi Liakhovetski @ 2011-09-12  9:27 UTC (permalink / raw)
  To: spi-devel-general; +Cc: linux-sh, Grant Likely, Magnus Damm

Use the sh_dma dmaengine driver to support DMA on MSIOF.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
---

v2: extracted the "unbalanced spi_master_put()" fix into a separate patch

 drivers/spi/spi-sh-msiof.c   |  392 +++++++++++++++++++++++++++++++++++++++---
 include/linux/spi/sh_msiof.h |    2 +
 2 files changed, 366 insertions(+), 28 deletions(-)

diff --git a/drivers/spi/spi-sh-msiof.c b/drivers/spi/spi-sh-msiof.c
index 722dad5..8622b32 100644
--- a/drivers/spi/spi-sh-msiof.c
+++ b/drivers/spi/spi-sh-msiof.c
@@ -13,14 +13,18 @@
 #include <linux/clk.h>
 #include <linux/completion.h>
 #include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
 #include <linux/err.h>
 #include <linux/gpio.h>
 #include <linux/init.h>
 #include <linux/interrupt.h>
 #include <linux/io.h>
 #include <linux/kernel.h>
+#include <linux/mm.h>
 #include <linux/platform_device.h>
 #include <linux/pm_runtime.h>
+#include <linux/sh_dma.h>
 
 #include <linux/spi/sh_msiof.h>
 #include <linux/spi/spi.h>
@@ -28,6 +32,13 @@
 
 #include <asm/unaligned.h>
 
+struct sh_msiof_dma {
+	struct sh_dmae_slave dma_slave;
+	struct dma_chan	*chan;
+	struct sg_table	sg;
+	struct dma_async_tx_descriptor *desc;
+};
+
 struct sh_msiof_spi_priv {
 	struct spi_bitbang bitbang; /* must be first for spi_bitbang.c */
 	void __iomem *mapbase;
@@ -38,6 +49,10 @@ struct sh_msiof_spi_priv {
 	unsigned long flags;
 	int tx_fifo_size;
 	int rx_fifo_size;
+	struct sh_msiof_dma dma_tx;
+	struct sh_msiof_dma dma_rx;
+	struct completion dma_done;
+	struct page *dummypage;
 };
 
 #define TMDR1	0x00
@@ -64,8 +79,12 @@ struct sh_msiof_spi_priv {
 #define CTR_TXE   (1 << 9)
 #define CTR_RXE   (1 << 8)
 
-#define STR_TEOF  (1 << 23)
-#define STR_REOF  (1 << 7)
+#define IER_TDMA  (1 << 31)
+#define IER_TDREQ (1 << 28)
+#define IER_TEOF  (1 << 23)
+#define IER_RDMA  (1 << 15)
+#define IER_RDREQ (1 << 12)
+#define IER_REOF  (1 << 7)
 
 static u32 sh_msiof_read(struct sh_msiof_spi_priv *p, int reg_offs)
 {
@@ -208,8 +227,6 @@ static void sh_msiof_spi_set_mode_regs(struct sh_msiof_spi_priv *p,
 
 	if (rx_buf)
 		sh_msiof_write(p, RMDR2, dr2);
-
-	sh_msiof_write(p, IER, STR_TEOF | STR_REOF);
 }
 
 static void sh_msiof_reset_str(struct sh_msiof_spi_priv *p)
@@ -401,9 +418,9 @@ static void sh_msiof_spi_chipselect(struct spi_device *spi, int is_on)
 
 	/* chip select is active low unless SPI_CS_HIGH is set */
 	if (spi->mode & SPI_CS_HIGH)
-		value = (is_on = BITBANG_CS_ACTIVE) ? 1 : 0;
+		value = is_on = BITBANG_CS_ACTIVE;
 	else
-		value = (is_on = BITBANG_CS_ACTIVE) ? 0 : 1;
+		value = is_on != BITBANG_CS_ACTIVE;
 
 	if (is_on = BITBANG_CS_ACTIVE) {
 		if (!test_and_set_bit(0, &p->flags)) {
@@ -429,6 +446,36 @@ static void sh_msiof_spi_chipselect(struct spi_device *spi, int is_on)
 	}
 }
 
+static int sh_msiof_spi_start(struct sh_msiof_spi_priv *p, void *rx_buf)
+{
+	/* setup clock and rx/tx signals */
+	int ret = sh_msiof_modify_ctr_wait(p, 0, CTR_TSCKE);
+	if (rx_buf && !ret)
+		ret = sh_msiof_modify_ctr_wait(p, 0, CTR_RXE);
+	if (!ret)
+		ret = sh_msiof_modify_ctr_wait(p, 0, CTR_TXE);
+
+	/* start by setting frame bit */
+	if (!ret)
+		ret = sh_msiof_modify_ctr_wait(p, 0, CTR_TFSE);
+
+	return ret;
+}
+
+static int sh_msiof_spi_stop(struct sh_msiof_spi_priv *p, void *rx_buf)
+{
+	/* shut down frame, tx/tx and clock signals */
+	int ret = sh_msiof_modify_ctr_wait(p, CTR_TFSE, 0);
+	if (!ret)
+		ret = sh_msiof_modify_ctr_wait(p, CTR_TXE, 0);
+	if (rx_buf && !ret)
+		ret = sh_msiof_modify_ctr_wait(p, CTR_RXE, 0);
+	if (!ret)
+		ret = sh_msiof_modify_ctr_wait(p, CTR_TSCKE, 0);
+
+	return ret;
+}
+
 static int sh_msiof_spi_txrx_once(struct sh_msiof_spi_priv *p,
 				  void (*tx_fifo)(struct sh_msiof_spi_priv *,
 						  const void *, int, int),
@@ -456,16 +503,11 @@ static int sh_msiof_spi_txrx_once(struct sh_msiof_spi_priv *p,
 	if (tx_buf)
 		tx_fifo(p, tx_buf, words, fifo_shift);
 
-	/* setup clock and rx/tx signals */
-	ret = sh_msiof_modify_ctr_wait(p, 0, CTR_TSCKE);
-	if (rx_buf)
-		ret = ret ? ret : sh_msiof_modify_ctr_wait(p, 0, CTR_RXE);
-	ret = ret ? ret : sh_msiof_modify_ctr_wait(p, 0, CTR_TXE);
-
-	/* start by setting frame bit */
 	INIT_COMPLETION(p->done);
-	ret = ret ? ret : sh_msiof_modify_ctr_wait(p, 0, CTR_TFSE);
-	if (ret) {
+	sh_msiof_write(p, IER, IER_TEOF | IER_REOF);
+
+	ret = sh_msiof_spi_start(p, rx_buf);
+	if (ret < 0) {
 		dev_err(&p->pdev->dev, "failed to start hardware\n");
 		goto err;
 	}
@@ -480,13 +522,8 @@ static int sh_msiof_spi_txrx_once(struct sh_msiof_spi_priv *p,
 	/* clear status bits */
 	sh_msiof_reset_str(p);
 
-	/* shut down frame, tx/tx and clock signals */
-	ret = sh_msiof_modify_ctr_wait(p, CTR_TFSE, 0);
-	ret = ret ? ret : sh_msiof_modify_ctr_wait(p, CTR_TXE, 0);
-	if (rx_buf)
-		ret = ret ? ret : sh_msiof_modify_ctr_wait(p, CTR_RXE, 0);
-	ret = ret ? ret : sh_msiof_modify_ctr_wait(p, CTR_TSCKE, 0);
-	if (ret) {
+	ret = sh_msiof_spi_stop(p, rx_buf);
+	if (ret < 0) {
 		dev_err(&p->pdev->dev, "failed to shut down hardware\n");
 		goto err;
 	}
@@ -498,6 +535,220 @@ static int sh_msiof_spi_txrx_once(struct sh_msiof_spi_priv *p,
 	return ret;
 }
 
+static void sh_msiof_setup_sg(struct sh_msiof_spi_priv *p,
+			      const void *buffer,
+			      unsigned int length,
+			      struct sg_table *sgtab)
+{
+	struct scatterlist *sg;
+	int bytesleft = length;
+	const void *bufp = buffer;
+	int mapbytes;
+	int i;
+
+	if (buffer) {
+		for_each_sg(sgtab->sgl, sg, sgtab->nents, i) {
+			/*
+			 * If there are fewer bytes left than what fits
+			 * in the current page (plus page alignment offset)
+			 * we just feed in this, else we stuff in as much
+			 * as we can.
+			 */
+			if (bytesleft < (PAGE_SIZE - offset_in_page(bufp)))
+				mapbytes = bytesleft;
+			else
+				mapbytes = PAGE_SIZE - offset_in_page(bufp);
+			sg_set_page(sg, virt_to_page(bufp),
+				    mapbytes, offset_in_page(bufp));
+			bytesleft -= mapbytes;
+			bufp += mapbytes;
+		}
+	} else {
+		/* Map the dummy buffer on every page */
+		for_each_sg(sgtab->sgl, sg, sgtab->nents, i) {
+			if (bytesleft < PAGE_SIZE)
+				mapbytes = bytesleft;
+			else
+				mapbytes = PAGE_SIZE;
+			sg_set_page(sg, p->dummypage, mapbytes, 0);
+			bytesleft -= mapbytes;
+		}
+	}
+	BUG_ON(bytesleft);
+}
+
+static int sh_msiof_spi_setup_xfer_dma(struct sh_msiof_spi_priv *p, struct sh_msiof_dma *dma,
+				       const void *buffer, size_t len, enum dma_data_direction dir)
+{
+	int ret, sglen;
+	unsigned int pages;
+	unsigned long flags = DMA_CTRL_ACK;
+
+	/* Create sglists for the transfers */
+	pages = (PAGE_ALIGN(len) >> PAGE_SHIFT);
+
+	ret = sg_alloc_table(&dma->sg, pages, GFP_KERNEL);
+	if (ret < 0)
+		return ret;
+
+	sh_msiof_setup_sg(p, buffer, len, &dma->sg);
+
+	/* Map DMA buffers */
+	sglen = dma_map_sg(&p->pdev->dev, dma->sg.sgl,
+			   dma->sg.orig_nents, dir);
+	if (sglen < 0) {
+		ret = sglen;
+		goto emapsg;
+	} else if (!sglen) {
+		ret = -EINVAL;
+		goto emapsg;
+	}
+
+	if (dir = DMA_FROM_DEVICE)
+		flags |= DMA_PREP_INTERRUPT;
+
+	dma->desc = dma->chan->device->device_prep_slave_sg(dma->chan,
+					dma->sg.sgl, sglen, dir, flags);
+	if (!dma->desc) {
+		ret = -ENOMEM;
+		goto edmaprep;
+	}
+
+	dma->sg.nents = sglen;
+
+	return 0;
+
+edmaprep:
+	dma_unmap_sg(&p->pdev->dev, dma->sg.sgl, sglen, dir);
+emapsg:
+	sg_free_table(&dma->sg);
+
+	return ret;
+}
+
+static void sh_msiof_spi_free_xfer_dma(struct sh_msiof_spi_priv *p, struct sh_msiof_dma *dma,
+				       enum dma_data_direction dir)
+{
+	dma_unmap_sg(&p->pdev->dev, dma->sg.sgl, dma->sg.nents, dir);
+	sg_free_table(&dma->sg);
+}
+
+static void sh_msiof_spi_dma_callback(void *arg)
+{
+	struct sh_msiof_spi_priv *p = arg;
+
+	dma_sync_sg_for_device(&p->pdev->dev, p->dma_rx.sg.sgl,
+			       p->dma_rx.sg.nents, DMA_FROM_DEVICE);
+
+	sh_msiof_spi_free_xfer_dma(p, &p->dma_rx, DMA_FROM_DEVICE);
+	sh_msiof_spi_free_xfer_dma(p, &p->dma_tx, DMA_TO_DEVICE);
+
+	complete(&p->dma_done);
+}
+
+static int sh_msiof_spi_txrx_dma_single(struct sh_msiof_spi_priv *p, int bits,
+					const void *tx_buf, const void *rx_buf, size_t len)
+{
+	int ret, cookie, words;
+
+	if (bits <= 8)
+		words = len;
+	else if (bits <= 16)
+		words = len >> 1;
+	else
+		words = len >> 2;
+
+	ret = sh_msiof_spi_setup_xfer_dma(p, &p->dma_tx, tx_buf, len, DMA_TO_DEVICE);
+	if (ret < 0)
+		return ret;
+
+	dma_sync_sg_for_device(&p->pdev->dev, p->dma_tx.sg.sgl,
+			       p->dma_tx.sg.nents, DMA_TO_DEVICE);
+
+	ret = sh_msiof_spi_setup_xfer_dma(p, &p->dma_rx, rx_buf, len, DMA_FROM_DEVICE);
+	if (ret < 0)
+		goto esdmarx;
+
+	/* Put the callback on the RX transfer only, that should finish last */
+	p->dma_rx.desc->callback = sh_msiof_spi_dma_callback;
+	p->dma_rx.desc->callback_param = p;
+
+	/* Submit and fire RX and TX with TX last so we're ready to read! */
+	cookie = p->dma_rx.desc->tx_submit(p->dma_rx.desc);
+	if (dma_submit_error(cookie)) {
+		ret = cookie;
+		goto esubmitrx;
+	}
+	p->dma_rx.chan->device->device_issue_pending(p->dma_rx.chan);
+
+	/* setup msiof transfer mode registers */
+	sh_msiof_spi_set_mode_regs(p, tx_buf, p->dummypage, bits, words);
+
+	cookie = p->dma_tx.desc->tx_submit(p->dma_tx.desc);
+	if (dma_submit_error(cookie)) {
+		ret = cookie;
+		goto esubmittx;
+	}
+	p->dma_tx.chan->device->device_issue_pending(p->dma_tx.chan);
+
+	INIT_COMPLETION(p->dma_done);
+	sh_msiof_write(p, IER, IER_RDREQ | IER_TDREQ | IER_RDMA | IER_TDMA);
+
+	/* As long as there's something to send, we'll also be receiving. */
+	ret = sh_msiof_spi_start(p, p->dummypage);
+	if (ret < 0) {
+		dev_err(&p->pdev->dev, "failed to start hardware: %d\n", ret);
+		goto espistart;
+	}
+
+	wait_for_completion(&p->dma_done);
+
+	/* clear status bits */
+	sh_msiof_reset_str(p);
+
+	ret = sh_msiof_spi_stop(p, p->dummypage);
+	if (ret < 0) {
+		dev_err(&p->pdev->dev, "failed to stop hardware: %d\n", ret);
+		goto espistop;
+	}
+
+	return len;
+
+espistop:
+espistart:
+	dmaengine_device_control(p->dma_tx.chan, DMA_TERMINATE_ALL, 0);
+esubmittx:
+	dmaengine_device_control(p->dma_rx.chan, DMA_TERMINATE_ALL, 0);
+esubmitrx:
+	sh_msiof_spi_free_xfer_dma(p, &p->dma_rx, DMA_FROM_DEVICE);
+esdmarx:
+	sh_msiof_spi_free_xfer_dma(p, &p->dma_tx, DMA_TO_DEVICE);
+
+	return ret;
+}
+
+static int sh_msiof_spi_txrx_dma(struct sh_msiof_spi_priv *p, struct spi_transfer *t, int bits)
+{
+	int i, ret = 0;
+	size_t left = t->len;
+	off_t offs = 0;
+
+	/* Up to 256 words per group, we only use a single group */
+	for (i = 0; i < (t->len + 255) / 256; i++) {
+		const void *rx_buf = t->rx_buf ? t->rx_buf + offs : NULL;
+		const void *tx_buf = t->tx_buf ? t->tx_buf + offs : NULL;
+
+		ret = sh_msiof_spi_txrx_dma_single(p, bits, tx_buf, rx_buf,
+						   min_t(size_t, left, 256));
+		if (ret < 0)
+			return ret;
+		left -= ret;
+		offs += ret;
+	}
+
+	return ret <= 0 ? ret : t->len;
+}
+
 static int sh_msiof_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
 {
 	struct sh_msiof_spi_priv *p = spi_master_get_devdata(spi->master);
@@ -512,6 +763,19 @@ static int sh_msiof_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
 
 	bits = sh_msiof_spi_bits(spi, t);
 
+	/* setup clocks (clock already enabled in chipselect()) */
+	sh_msiof_spi_set_clk_regs(p, clk_get_rate(p->clk),
+				  sh_msiof_spi_hz(spi, t));
+
+	if (p->dma_rx.chan && p->dma_tx.chan && t->len > 15 &&
+	    !(t->len & 3) && !((int)t->rx_buf & 3) && !((int)t->tx_buf & 3)) {
+		int ret = sh_msiof_spi_txrx_dma(p, t, bits);
+		if (ret < 0)
+			dev_warn(&spi->dev, "fall back to PIO: %d\n", ret);
+		else
+			return ret;
+	}
+
 	if (bits <= 8 && t->len > 15 && !(t->len & 3)) {
 		bits = 32;
 		swab = true;
@@ -559,10 +823,6 @@ static int sh_msiof_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
 			rx_fifo = sh_msiof_spi_read_fifo_32;
 	}
 
-	/* setup clocks (clock already enabled in chipselect()) */
-	sh_msiof_spi_set_clk_regs(p, clk_get_rate(p->clk),
-				  sh_msiof_spi_hz(spi, t));
-
 	/* transfer in fifo sized chunks */
 	words = t->len / bytes_per_word;
 	bytes_done = 0;
@@ -591,6 +851,78 @@ static u32 sh_msiof_spi_txrx_word(struct spi_device *spi, unsigned nsecs,
 	return 0;
 }
 
+static bool sh_msiof_filter(struct dma_chan *chan, void *arg)
+{
+	dev_dbg(chan->device->dev, "%s: slave data %p\n", __func__, arg);
+	chan->private = arg;
+	return true;
+}
+
+static void sh_msiof_request_dma(struct sh_msiof_spi_priv *p)
+{
+	struct sh_dmae_slave *tx = &p->dma_tx.dma_slave,
+		*rx = &p->dma_rx.dma_slave;
+	dma_cap_mask_t mask;
+
+	tx->slave_id = p->info->slave_id_tx;
+	rx->slave_id = p->info->slave_id_rx;
+
+	/* We can only either use DMA for both Tx and Rx or not use it at all */
+	if (tx->slave_id <= 0 || rx->slave_id <= 0)
+		return;
+
+	dev_warn(&p->pdev->dev, "Experimental DMA support enabled.\n");
+
+	p->dummypage = alloc_page(GFP_KERNEL);
+	if (!p->dummypage)
+		return;
+
+	dma_cap_zero(mask);
+	dma_cap_set(DMA_SLAVE, mask);
+
+	p->dma_tx.chan = dma_request_channel(mask, sh_msiof_filter, tx);
+	dev_dbg(&p->pdev->dev, "%s: TX: got channel %p\n", __func__,
+		p->dma_tx.chan);
+
+	if (!p->dma_tx.chan)
+		goto echantx;
+
+	p->dma_rx.chan = dma_request_channel(mask, sh_msiof_filter, rx);
+	dev_dbg(&p->pdev->dev, "%s: RX: got channel %p\n", __func__,
+		p->dma_rx.chan);
+
+	if (!p->dma_rx.chan)
+		goto echanrx;
+
+	init_completion(&p->dma_done);
+
+	return;
+
+echanrx:
+	dma_release_channel(p->dma_tx.chan);
+	p->dma_tx.chan = NULL;
+echantx:
+	__free_pages(p->dummypage, 0);
+}
+
+static void sh_msiof_release_dma(struct sh_msiof_spi_priv *p)
+{
+	/* Descriptors are freed automatically */
+	if (p->dma_tx.chan) {
+		struct dma_chan *chan = p->dma_tx.chan;
+		p->dma_tx.chan = NULL;
+		dma_release_channel(chan);
+	}
+	if (p->dma_rx.chan) {
+		struct dma_chan *chan = p->dma_rx.chan;
+		p->dma_rx.chan = NULL;
+		dma_release_channel(chan);
+	}
+
+	if (p->dummypage)
+		__free_pages(p->dummypage, 0);
+}
+
 static int sh_msiof_spi_probe(struct platform_device *pdev)
 {
 	struct resource	*r;
@@ -645,6 +977,8 @@ static int sh_msiof_spi_probe(struct platform_device *pdev)
 	p->pdev = pdev;
 	pm_runtime_enable(&pdev->dev);
 
+	sh_msiof_request_dma(p);
+
 	/* The standard version of MSIOF use 64 word FIFOs */
 	p->tx_fifo_size = 64;
 	p->rx_fifo_size = 64;
@@ -656,8 +990,8 @@ static int sh_msiof_spi_probe(struct platform_device *pdev)
 		p->rx_fifo_size = p->info->rx_fifo_override;
 
 	/* init master and bitbang code */
-	master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
-	master->mode_bits |= SPI_LSB_FIRST | SPI_3WIRE;
+	master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH |
+		SPI_LSB_FIRST | SPI_3WIRE;
 	master->flags = 0;
 	master->bus_num = pdev->id;
 	master->num_chipselect = p->info->num_chipselect;
@@ -677,6 +1011,7 @@ static int sh_msiof_spi_probe(struct platform_device *pdev)
 	if (ret = 0)
 		return 0;
 
+	sh_msiof_release_dma(p);
 	pm_runtime_disable(&pdev->dev);
  err3:
 	iounmap(p->mapbase);
@@ -695,6 +1030,7 @@ static int sh_msiof_spi_remove(struct platform_device *pdev)
 
 	ret = spi_bitbang_stop(&p->bitbang);
 	if (!ret) {
+		sh_msiof_release_dma(p);
 		pm_runtime_disable(&pdev->dev);
 		free_irq(platform_get_irq(pdev, 0), p);
 		iounmap(p->mapbase);
diff --git a/include/linux/spi/sh_msiof.h b/include/linux/spi/sh_msiof.h
index 2e8db3d..69c0f6e 100644
--- a/include/linux/spi/sh_msiof.h
+++ b/include/linux/spi/sh_msiof.h
@@ -5,6 +5,8 @@ struct sh_msiof_spi_info {
 	int tx_fifo_override;
 	int rx_fifo_override;
 	u16 num_chipselect;
+	unsigned int slave_id_tx;
+	unsigned int slave_id_rx;
 };
 
 #endif /* __SPI_SH_MSIOF_H__ */
-- 
1.7.2.5


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 2/2 v2] SPI: spi_sh_msiof: implement DMA support
@ 2011-09-12  9:27       ` Guennadi Liakhovetski
  0 siblings, 0 replies; 18+ messages in thread
From: Guennadi Liakhovetski @ 2011-09-12  9:27 UTC (permalink / raw)
  To: spi-devel-general; +Cc: linux-sh, Grant Likely, Magnus Damm

Use the sh_dma dmaengine driver to support DMA on MSIOF.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
---

v2: extracted the "unbalanced spi_master_put()" fix into a separate patch

 drivers/spi/spi-sh-msiof.c   |  392 +++++++++++++++++++++++++++++++++++++++---
 include/linux/spi/sh_msiof.h |    2 +
 2 files changed, 366 insertions(+), 28 deletions(-)

diff --git a/drivers/spi/spi-sh-msiof.c b/drivers/spi/spi-sh-msiof.c
index 722dad5..8622b32 100644
--- a/drivers/spi/spi-sh-msiof.c
+++ b/drivers/spi/spi-sh-msiof.c
@@ -13,14 +13,18 @@
 #include <linux/clk.h>
 #include <linux/completion.h>
 #include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
 #include <linux/err.h>
 #include <linux/gpio.h>
 #include <linux/init.h>
 #include <linux/interrupt.h>
 #include <linux/io.h>
 #include <linux/kernel.h>
+#include <linux/mm.h>
 #include <linux/platform_device.h>
 #include <linux/pm_runtime.h>
+#include <linux/sh_dma.h>
 
 #include <linux/spi/sh_msiof.h>
 #include <linux/spi/spi.h>
@@ -28,6 +32,13 @@
 
 #include <asm/unaligned.h>
 
+struct sh_msiof_dma {
+	struct sh_dmae_slave dma_slave;
+	struct dma_chan	*chan;
+	struct sg_table	sg;
+	struct dma_async_tx_descriptor *desc;
+};
+
 struct sh_msiof_spi_priv {
 	struct spi_bitbang bitbang; /* must be first for spi_bitbang.c */
 	void __iomem *mapbase;
@@ -38,6 +49,10 @@ struct sh_msiof_spi_priv {
 	unsigned long flags;
 	int tx_fifo_size;
 	int rx_fifo_size;
+	struct sh_msiof_dma dma_tx;
+	struct sh_msiof_dma dma_rx;
+	struct completion dma_done;
+	struct page *dummypage;
 };
 
 #define TMDR1	0x00
@@ -64,8 +79,12 @@ struct sh_msiof_spi_priv {
 #define CTR_TXE   (1 << 9)
 #define CTR_RXE   (1 << 8)
 
-#define STR_TEOF  (1 << 23)
-#define STR_REOF  (1 << 7)
+#define IER_TDMA  (1 << 31)
+#define IER_TDREQ (1 << 28)
+#define IER_TEOF  (1 << 23)
+#define IER_RDMA  (1 << 15)
+#define IER_RDREQ (1 << 12)
+#define IER_REOF  (1 << 7)
 
 static u32 sh_msiof_read(struct sh_msiof_spi_priv *p, int reg_offs)
 {
@@ -208,8 +227,6 @@ static void sh_msiof_spi_set_mode_regs(struct sh_msiof_spi_priv *p,
 
 	if (rx_buf)
 		sh_msiof_write(p, RMDR2, dr2);
-
-	sh_msiof_write(p, IER, STR_TEOF | STR_REOF);
 }
 
 static void sh_msiof_reset_str(struct sh_msiof_spi_priv *p)
@@ -401,9 +418,9 @@ static void sh_msiof_spi_chipselect(struct spi_device *spi, int is_on)
 
 	/* chip select is active low unless SPI_CS_HIGH is set */
 	if (spi->mode & SPI_CS_HIGH)
-		value = (is_on == BITBANG_CS_ACTIVE) ? 1 : 0;
+		value = is_on == BITBANG_CS_ACTIVE;
 	else
-		value = (is_on == BITBANG_CS_ACTIVE) ? 0 : 1;
+		value = is_on != BITBANG_CS_ACTIVE;
 
 	if (is_on == BITBANG_CS_ACTIVE) {
 		if (!test_and_set_bit(0, &p->flags)) {
@@ -429,6 +446,36 @@ static void sh_msiof_spi_chipselect(struct spi_device *spi, int is_on)
 	}
 }
 
+static int sh_msiof_spi_start(struct sh_msiof_spi_priv *p, void *rx_buf)
+{
+	/* setup clock and rx/tx signals */
+	int ret = sh_msiof_modify_ctr_wait(p, 0, CTR_TSCKE);
+	if (rx_buf && !ret)
+		ret = sh_msiof_modify_ctr_wait(p, 0, CTR_RXE);
+	if (!ret)
+		ret = sh_msiof_modify_ctr_wait(p, 0, CTR_TXE);
+
+	/* start by setting frame bit */
+	if (!ret)
+		ret = sh_msiof_modify_ctr_wait(p, 0, CTR_TFSE);
+
+	return ret;
+}
+
+static int sh_msiof_spi_stop(struct sh_msiof_spi_priv *p, void *rx_buf)
+{
+	/* shut down frame, tx/tx and clock signals */
+	int ret = sh_msiof_modify_ctr_wait(p, CTR_TFSE, 0);
+	if (!ret)
+		ret = sh_msiof_modify_ctr_wait(p, CTR_TXE, 0);
+	if (rx_buf && !ret)
+		ret = sh_msiof_modify_ctr_wait(p, CTR_RXE, 0);
+	if (!ret)
+		ret = sh_msiof_modify_ctr_wait(p, CTR_TSCKE, 0);
+
+	return ret;
+}
+
 static int sh_msiof_spi_txrx_once(struct sh_msiof_spi_priv *p,
 				  void (*tx_fifo)(struct sh_msiof_spi_priv *,
 						  const void *, int, int),
@@ -456,16 +503,11 @@ static int sh_msiof_spi_txrx_once(struct sh_msiof_spi_priv *p,
 	if (tx_buf)
 		tx_fifo(p, tx_buf, words, fifo_shift);
 
-	/* setup clock and rx/tx signals */
-	ret = sh_msiof_modify_ctr_wait(p, 0, CTR_TSCKE);
-	if (rx_buf)
-		ret = ret ? ret : sh_msiof_modify_ctr_wait(p, 0, CTR_RXE);
-	ret = ret ? ret : sh_msiof_modify_ctr_wait(p, 0, CTR_TXE);
-
-	/* start by setting frame bit */
 	INIT_COMPLETION(p->done);
-	ret = ret ? ret : sh_msiof_modify_ctr_wait(p, 0, CTR_TFSE);
-	if (ret) {
+	sh_msiof_write(p, IER, IER_TEOF | IER_REOF);
+
+	ret = sh_msiof_spi_start(p, rx_buf);
+	if (ret < 0) {
 		dev_err(&p->pdev->dev, "failed to start hardware\n");
 		goto err;
 	}
@@ -480,13 +522,8 @@ static int sh_msiof_spi_txrx_once(struct sh_msiof_spi_priv *p,
 	/* clear status bits */
 	sh_msiof_reset_str(p);
 
-	/* shut down frame, tx/tx and clock signals */
-	ret = sh_msiof_modify_ctr_wait(p, CTR_TFSE, 0);
-	ret = ret ? ret : sh_msiof_modify_ctr_wait(p, CTR_TXE, 0);
-	if (rx_buf)
-		ret = ret ? ret : sh_msiof_modify_ctr_wait(p, CTR_RXE, 0);
-	ret = ret ? ret : sh_msiof_modify_ctr_wait(p, CTR_TSCKE, 0);
-	if (ret) {
+	ret = sh_msiof_spi_stop(p, rx_buf);
+	if (ret < 0) {
 		dev_err(&p->pdev->dev, "failed to shut down hardware\n");
 		goto err;
 	}
@@ -498,6 +535,220 @@ static int sh_msiof_spi_txrx_once(struct sh_msiof_spi_priv *p,
 	return ret;
 }
 
+static void sh_msiof_setup_sg(struct sh_msiof_spi_priv *p,
+			      const void *buffer,
+			      unsigned int length,
+			      struct sg_table *sgtab)
+{
+	struct scatterlist *sg;
+	int bytesleft = length;
+	const void *bufp = buffer;
+	int mapbytes;
+	int i;
+
+	if (buffer) {
+		for_each_sg(sgtab->sgl, sg, sgtab->nents, i) {
+			/*
+			 * If there are fewer bytes left than what fits
+			 * in the current page (plus page alignment offset)
+			 * we just feed in this, else we stuff in as much
+			 * as we can.
+			 */
+			if (bytesleft < (PAGE_SIZE - offset_in_page(bufp)))
+				mapbytes = bytesleft;
+			else
+				mapbytes = PAGE_SIZE - offset_in_page(bufp);
+			sg_set_page(sg, virt_to_page(bufp),
+				    mapbytes, offset_in_page(bufp));
+			bytesleft -= mapbytes;
+			bufp += mapbytes;
+		}
+	} else {
+		/* Map the dummy buffer on every page */
+		for_each_sg(sgtab->sgl, sg, sgtab->nents, i) {
+			if (bytesleft < PAGE_SIZE)
+				mapbytes = bytesleft;
+			else
+				mapbytes = PAGE_SIZE;
+			sg_set_page(sg, p->dummypage, mapbytes, 0);
+			bytesleft -= mapbytes;
+		}
+	}
+	BUG_ON(bytesleft);
+}
+
+static int sh_msiof_spi_setup_xfer_dma(struct sh_msiof_spi_priv *p, struct sh_msiof_dma *dma,
+				       const void *buffer, size_t len, enum dma_data_direction dir)
+{
+	int ret, sglen;
+	unsigned int pages;
+	unsigned long flags = DMA_CTRL_ACK;
+
+	/* Create sglists for the transfers */
+	pages = (PAGE_ALIGN(len) >> PAGE_SHIFT);
+
+	ret = sg_alloc_table(&dma->sg, pages, GFP_KERNEL);
+	if (ret < 0)
+		return ret;
+
+	sh_msiof_setup_sg(p, buffer, len, &dma->sg);
+
+	/* Map DMA buffers */
+	sglen = dma_map_sg(&p->pdev->dev, dma->sg.sgl,
+			   dma->sg.orig_nents, dir);
+	if (sglen < 0) {
+		ret = sglen;
+		goto emapsg;
+	} else if (!sglen) {
+		ret = -EINVAL;
+		goto emapsg;
+	}
+
+	if (dir == DMA_FROM_DEVICE)
+		flags |= DMA_PREP_INTERRUPT;
+
+	dma->desc = dma->chan->device->device_prep_slave_sg(dma->chan,
+					dma->sg.sgl, sglen, dir, flags);
+	if (!dma->desc) {
+		ret = -ENOMEM;
+		goto edmaprep;
+	}
+
+	dma->sg.nents = sglen;
+
+	return 0;
+
+edmaprep:
+	dma_unmap_sg(&p->pdev->dev, dma->sg.sgl, sglen, dir);
+emapsg:
+	sg_free_table(&dma->sg);
+
+	return ret;
+}
+
+static void sh_msiof_spi_free_xfer_dma(struct sh_msiof_spi_priv *p, struct sh_msiof_dma *dma,
+				       enum dma_data_direction dir)
+{
+	dma_unmap_sg(&p->pdev->dev, dma->sg.sgl, dma->sg.nents, dir);
+	sg_free_table(&dma->sg);
+}
+
+static void sh_msiof_spi_dma_callback(void *arg)
+{
+	struct sh_msiof_spi_priv *p = arg;
+
+	dma_sync_sg_for_device(&p->pdev->dev, p->dma_rx.sg.sgl,
+			       p->dma_rx.sg.nents, DMA_FROM_DEVICE);
+
+	sh_msiof_spi_free_xfer_dma(p, &p->dma_rx, DMA_FROM_DEVICE);
+	sh_msiof_spi_free_xfer_dma(p, &p->dma_tx, DMA_TO_DEVICE);
+
+	complete(&p->dma_done);
+}
+
+static int sh_msiof_spi_txrx_dma_single(struct sh_msiof_spi_priv *p, int bits,
+					const void *tx_buf, const void *rx_buf, size_t len)
+{
+	int ret, cookie, words;
+
+	if (bits <= 8)
+		words = len;
+	else if (bits <= 16)
+		words = len >> 1;
+	else
+		words = len >> 2;
+
+	ret = sh_msiof_spi_setup_xfer_dma(p, &p->dma_tx, tx_buf, len, DMA_TO_DEVICE);
+	if (ret < 0)
+		return ret;
+
+	dma_sync_sg_for_device(&p->pdev->dev, p->dma_tx.sg.sgl,
+			       p->dma_tx.sg.nents, DMA_TO_DEVICE);
+
+	ret = sh_msiof_spi_setup_xfer_dma(p, &p->dma_rx, rx_buf, len, DMA_FROM_DEVICE);
+	if (ret < 0)
+		goto esdmarx;
+
+	/* Put the callback on the RX transfer only, that should finish last */
+	p->dma_rx.desc->callback = sh_msiof_spi_dma_callback;
+	p->dma_rx.desc->callback_param = p;
+
+	/* Submit and fire RX and TX with TX last so we're ready to read! */
+	cookie = p->dma_rx.desc->tx_submit(p->dma_rx.desc);
+	if (dma_submit_error(cookie)) {
+		ret = cookie;
+		goto esubmitrx;
+	}
+	p->dma_rx.chan->device->device_issue_pending(p->dma_rx.chan);
+
+	/* setup msiof transfer mode registers */
+	sh_msiof_spi_set_mode_regs(p, tx_buf, p->dummypage, bits, words);
+
+	cookie = p->dma_tx.desc->tx_submit(p->dma_tx.desc);
+	if (dma_submit_error(cookie)) {
+		ret = cookie;
+		goto esubmittx;
+	}
+	p->dma_tx.chan->device->device_issue_pending(p->dma_tx.chan);
+
+	INIT_COMPLETION(p->dma_done);
+	sh_msiof_write(p, IER, IER_RDREQ | IER_TDREQ | IER_RDMA | IER_TDMA);
+
+	/* As long as there's something to send, we'll also be receiving. */
+	ret = sh_msiof_spi_start(p, p->dummypage);
+	if (ret < 0) {
+		dev_err(&p->pdev->dev, "failed to start hardware: %d\n", ret);
+		goto espistart;
+	}
+
+	wait_for_completion(&p->dma_done);
+
+	/* clear status bits */
+	sh_msiof_reset_str(p);
+
+	ret = sh_msiof_spi_stop(p, p->dummypage);
+	if (ret < 0) {
+		dev_err(&p->pdev->dev, "failed to stop hardware: %d\n", ret);
+		goto espistop;
+	}
+
+	return len;
+
+espistop:
+espistart:
+	dmaengine_device_control(p->dma_tx.chan, DMA_TERMINATE_ALL, 0);
+esubmittx:
+	dmaengine_device_control(p->dma_rx.chan, DMA_TERMINATE_ALL, 0);
+esubmitrx:
+	sh_msiof_spi_free_xfer_dma(p, &p->dma_rx, DMA_FROM_DEVICE);
+esdmarx:
+	sh_msiof_spi_free_xfer_dma(p, &p->dma_tx, DMA_TO_DEVICE);
+
+	return ret;
+}
+
+static int sh_msiof_spi_txrx_dma(struct sh_msiof_spi_priv *p, struct spi_transfer *t, int bits)
+{
+	int i, ret = 0;
+	size_t left = t->len;
+	off_t offs = 0;
+
+	/* Up to 256 words per group, we only use a single group */
+	for (i = 0; i < (t->len + 255) / 256; i++) {
+		const void *rx_buf = t->rx_buf ? t->rx_buf + offs : NULL;
+		const void *tx_buf = t->tx_buf ? t->tx_buf + offs : NULL;
+
+		ret = sh_msiof_spi_txrx_dma_single(p, bits, tx_buf, rx_buf,
+						   min_t(size_t, left, 256));
+		if (ret < 0)
+			return ret;
+		left -= ret;
+		offs += ret;
+	}
+
+	return ret <= 0 ? ret : t->len;
+}
+
 static int sh_msiof_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
 {
 	struct sh_msiof_spi_priv *p = spi_master_get_devdata(spi->master);
@@ -512,6 +763,19 @@ static int sh_msiof_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
 
 	bits = sh_msiof_spi_bits(spi, t);
 
+	/* setup clocks (clock already enabled in chipselect()) */
+	sh_msiof_spi_set_clk_regs(p, clk_get_rate(p->clk),
+				  sh_msiof_spi_hz(spi, t));
+
+	if (p->dma_rx.chan && p->dma_tx.chan && t->len > 15 &&
+	    !(t->len & 3) && !((int)t->rx_buf & 3) && !((int)t->tx_buf & 3)) {
+		int ret = sh_msiof_spi_txrx_dma(p, t, bits);
+		if (ret < 0)
+			dev_warn(&spi->dev, "fall back to PIO: %d\n", ret);
+		else
+			return ret;
+	}
+
 	if (bits <= 8 && t->len > 15 && !(t->len & 3)) {
 		bits = 32;
 		swab = true;
@@ -559,10 +823,6 @@ static int sh_msiof_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
 			rx_fifo = sh_msiof_spi_read_fifo_32;
 	}
 
-	/* setup clocks (clock already enabled in chipselect()) */
-	sh_msiof_spi_set_clk_regs(p, clk_get_rate(p->clk),
-				  sh_msiof_spi_hz(spi, t));
-
 	/* transfer in fifo sized chunks */
 	words = t->len / bytes_per_word;
 	bytes_done = 0;
@@ -591,6 +851,78 @@ static u32 sh_msiof_spi_txrx_word(struct spi_device *spi, unsigned nsecs,
 	return 0;
 }
 
+static bool sh_msiof_filter(struct dma_chan *chan, void *arg)
+{
+	dev_dbg(chan->device->dev, "%s: slave data %p\n", __func__, arg);
+	chan->private = arg;
+	return true;
+}
+
+static void sh_msiof_request_dma(struct sh_msiof_spi_priv *p)
+{
+	struct sh_dmae_slave *tx = &p->dma_tx.dma_slave,
+		*rx = &p->dma_rx.dma_slave;
+	dma_cap_mask_t mask;
+
+	tx->slave_id = p->info->slave_id_tx;
+	rx->slave_id = p->info->slave_id_rx;
+
+	/* We can only either use DMA for both Tx and Rx or not use it at all */
+	if (tx->slave_id <= 0 || rx->slave_id <= 0)
+		return;
+
+	dev_warn(&p->pdev->dev, "Experimental DMA support enabled.\n");
+
+	p->dummypage = alloc_page(GFP_KERNEL);
+	if (!p->dummypage)
+		return;
+
+	dma_cap_zero(mask);
+	dma_cap_set(DMA_SLAVE, mask);
+
+	p->dma_tx.chan = dma_request_channel(mask, sh_msiof_filter, tx);
+	dev_dbg(&p->pdev->dev, "%s: TX: got channel %p\n", __func__,
+		p->dma_tx.chan);
+
+	if (!p->dma_tx.chan)
+		goto echantx;
+
+	p->dma_rx.chan = dma_request_channel(mask, sh_msiof_filter, rx);
+	dev_dbg(&p->pdev->dev, "%s: RX: got channel %p\n", __func__,
+		p->dma_rx.chan);
+
+	if (!p->dma_rx.chan)
+		goto echanrx;
+
+	init_completion(&p->dma_done);
+
+	return;
+
+echanrx:
+	dma_release_channel(p->dma_tx.chan);
+	p->dma_tx.chan = NULL;
+echantx:
+	__free_pages(p->dummypage, 0);
+}
+
+static void sh_msiof_release_dma(struct sh_msiof_spi_priv *p)
+{
+	/* Descriptors are freed automatically */
+	if (p->dma_tx.chan) {
+		struct dma_chan *chan = p->dma_tx.chan;
+		p->dma_tx.chan = NULL;
+		dma_release_channel(chan);
+	}
+	if (p->dma_rx.chan) {
+		struct dma_chan *chan = p->dma_rx.chan;
+		p->dma_rx.chan = NULL;
+		dma_release_channel(chan);
+	}
+
+	if (p->dummypage)
+		__free_pages(p->dummypage, 0);
+}
+
 static int sh_msiof_spi_probe(struct platform_device *pdev)
 {
 	struct resource	*r;
@@ -645,6 +977,8 @@ static int sh_msiof_spi_probe(struct platform_device *pdev)
 	p->pdev = pdev;
 	pm_runtime_enable(&pdev->dev);
 
+	sh_msiof_request_dma(p);
+
 	/* The standard version of MSIOF use 64 word FIFOs */
 	p->tx_fifo_size = 64;
 	p->rx_fifo_size = 64;
@@ -656,8 +990,8 @@ static int sh_msiof_spi_probe(struct platform_device *pdev)
 		p->rx_fifo_size = p->info->rx_fifo_override;
 
 	/* init master and bitbang code */
-	master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
-	master->mode_bits |= SPI_LSB_FIRST | SPI_3WIRE;
+	master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH |
+		SPI_LSB_FIRST | SPI_3WIRE;
 	master->flags = 0;
 	master->bus_num = pdev->id;
 	master->num_chipselect = p->info->num_chipselect;
@@ -677,6 +1011,7 @@ static int sh_msiof_spi_probe(struct platform_device *pdev)
 	if (ret == 0)
 		return 0;
 
+	sh_msiof_release_dma(p);
 	pm_runtime_disable(&pdev->dev);
  err3:
 	iounmap(p->mapbase);
@@ -695,6 +1030,7 @@ static int sh_msiof_spi_remove(struct platform_device *pdev)
 
 	ret = spi_bitbang_stop(&p->bitbang);
 	if (!ret) {
+		sh_msiof_release_dma(p);
 		pm_runtime_disable(&pdev->dev);
 		free_irq(platform_get_irq(pdev, 0), p);
 		iounmap(p->mapbase);
diff --git a/include/linux/spi/sh_msiof.h b/include/linux/spi/sh_msiof.h
index 2e8db3d..69c0f6e 100644
--- a/include/linux/spi/sh_msiof.h
+++ b/include/linux/spi/sh_msiof.h
@@ -5,6 +5,8 @@ struct sh_msiof_spi_info {
 	int tx_fifo_override;
 	int rx_fifo_override;
 	u16 num_chipselect;
+	unsigned int slave_id_tx;
+	unsigned int slave_id_rx;
 };
 
 #endif /* __SPI_SH_MSIOF_H__ */
-- 
1.7.2.5


^ permalink raw reply related	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2011-09-12  9:27 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-09-02 15:13 [PATCH 0/2] SPI: spi-sh-msiof: add DMA support Guennadi Liakhovetski
2011-09-02 15:13 ` Guennadi Liakhovetski
2011-09-02 15:13 ` [PATCH 1/2] SPI: spi_sh_msiof: implement " Guennadi Liakhovetski
2011-09-02 15:13   ` Guennadi Liakhovetski
2011-09-05  4:59   ` Paul Mundt
2011-09-05  4:59     ` Paul Mundt
2011-09-05  7:26     ` Guennadi Liakhovetski
2011-09-05  7:26       ` Guennadi Liakhovetski
2011-09-06 22:16       ` Magnus Damm
2011-09-06 22:16         ` Magnus Damm
2011-09-12  9:27   ` [PATCH 0/2 v2] spi: MSIOF: unbalanced spi_master_put() + DMA Guennadi Liakhovetski
2011-09-12  9:27     ` Guennadi Liakhovetski
2011-09-12  9:27     ` [PATCH 1/2 v2] SPI: spi-sh-msiof: remove unbalanced spi_master_put() Guennadi Liakhovetski
2011-09-12  9:27       ` Guennadi Liakhovetski
2011-09-12  9:27     ` [PATCH 2/2 v2] SPI: spi_sh_msiof: implement DMA support Guennadi Liakhovetski
2011-09-12  9:27       ` Guennadi Liakhovetski
2011-09-02 15:13 ` [PATCH 2/2] sh: use DMA with MSIOF SPI on the sh7724 ecovec board Guennadi Liakhovetski
2011-09-02 15:13   ` Guennadi Liakhovetski

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.