linux-spi.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* Fwd: Re: Fwd: [PATCH] atmel_spi: Allow chained transfers
@ 2007-11-14 18:57 David Brownell
  0 siblings, 0 replies; only message in thread
From: David Brownell @ 2007-11-14 18:57 UTC (permalink / raw)
  To: spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f

If this checks out for me too, I'll likely forward it for inclusion
in the MM tree, and with a note to the ARM list.

Silvester, thanks for this patch ... it sounds like it'll be a
winner!


----------  Forwarded Message  ----------

Subject: Re: Fwd: [spi-devel-general] [PATCH] atmel_spi: Allow chained transfers
Date: Wednesday 14 November 2007
From: "Silvester Erdeg" <slipszi-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
To: "Haavard Skinnemoen" <hskinnemoen-AIFe0yeh4nAAvxtiuMwx3w@public.gmane.org>

On Nov 13, 2007 3:04 PM, Haavard Skinnemoen <hskinnemoen-AIFe0yeh4nAAvxtiuMwx3w@public.gmane.org> wrote:
> ...
> The patch looks good, and it works fine on my NGW board with
> mtd_dataflash and jffs2. I'm not sure if it's any faster, but if
> someone with an AT91RM9200 board could verify that it solves the chip
> select issues, I think it should be merged after some extensive testing.

We tested this on an AT91RM9200 board with a fingerprint reader chip
connected to spi.0. It solved our problems with it. But I agree that
it should go through some more testing.

> I can't find anything wrong or even suspicious about the patch, but it
> looks like you're dereferencing as->current_transfer and
> as->current_remaining_bytes quite a lot. I wonder if gcc is smart
> enough to keep it in a register most of the time...? Also,
> atmel_spi_next_xfer_data() takes a "len" pointer which is dereferenced
> many times as well. That might hurt too, unless gcc is smart enough to
> keep it in a register (which it should since the function looks like it
> will be inlined anyway.)

I checked the assembly source and gcc is not smart enough, so I
reworked the patch to address these concerns. The source is perhaps a
bit more difficult to read but it is more compact now.

---------
This patch adds support for chained transfers in the atmel_spi driver.
Almost all of the logic can be found in the reworked
atmel_spi_next_xfer() function. When it is called, the driver is in
one of the following three states:

1. It isn't transferring anything (in which case the first transfer of
the current message is going to be sent)
2. It has finished transfering a non-chainable transfer (in which case
it will go to the next transfer in the message)
3. It has finished transfering a chained transfer (in which case the
next transfer is already queued)

After that it will queue the next transfer if it can be chained.

The patch is made against 2.6.24-rc2.

Signed-off-by: Szilveszter Ordog <slipszi-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>

diff -uNrp linux-2.6.24-rc1-orig/drivers/spi/atmel_spi.c
linux-2.6.24-rc1/drivers/spi/atmel_spi.c
--- linux-2.6.24-rc1-orig/drivers/spi/atmel_spi.c	2007-11-14
17:25:41.000000000 +0100
+++ linux-2.6.24-rc1/drivers/spi/atmel_spi.c	2007-11-14 17:31:25.000000000 +0100
@@ -51,7 +51,9 @@ struct atmel_spi {
 	u8			stopping;
 	struct list_head	queue;
 	struct spi_transfer	*current_transfer;
-	unsigned long		remaining_bytes;
+	unsigned long		current_remaining_bytes;
+	struct spi_transfer	*next_transfer;
+	unsigned long		next_remaining_bytes;

 	void			*buffer;
 	dma_addr_t		buffer_dma;
@@ -121,6 +123,48 @@ static void cs_deactivate(struct atmel_s
 		gpio_set_value(gpio, !active);
 }

+static inline int atmel_spi_xfer_is_last(struct spi_message *msg,
+					struct spi_transfer *xfer)
+{
+	return msg->transfers.prev == &xfer->transfer_list;
+}
+
+static inline int atmel_spi_xfer_can_be_chained(struct spi_transfer *xfer)
+{
+	return xfer->delay_usecs == 0 && !xfer->cs_change;
+}
+
+static void atmel_spi_next_xfer_data(struct spi_master *master,
+				struct spi_transfer *xfer,
+				dma_addr_t *tx_dma,
+				dma_addr_t *rx_dma,
+				u32 *plen)
+{
+	struct atmel_spi	*as = spi_master_get_devdata(master);
+	u32			len = *plen;
+
+	/* use scratch buffer only when rx or tx data is unspecified */
+	if (xfer->rx_buf)
+		*rx_dma = xfer->rx_dma + xfer->len - len;
+	else {
+		*rx_dma = as->buffer_dma;
+		if (len > BUFFER_SIZE)
+			len = BUFFER_SIZE;
+	}
+	if (xfer->tx_buf)
+		*tx_dma = xfer->tx_dma + xfer->len - len;
+	else {
+		*tx_dma = as->buffer_dma;
+		if (len > BUFFER_SIZE)
+			len = BUFFER_SIZE;
+		memset(as->buffer, 0, len);
+		dma_sync_single_for_device(&as->pdev->dev,
+				as->buffer_dma, len, DMA_TO_DEVICE);
+	}
+
+	*plen = len;
+}
+
 /*
  * Submit next transfer for DMA.
  * lock is held, spi irq is blocked
@@ -130,53 +174,68 @@ static void atmel_spi_next_xfer(struct s
 {
 	struct atmel_spi	*as = spi_master_get_devdata(master);
 	struct spi_transfer	*xfer;
-	u32			len;
+	u32			len, remaining, total;
 	dma_addr_t		tx_dma, rx_dma;

-	xfer = as->current_transfer;
-	if (!xfer || as->remaining_bytes == 0) {
-		if (xfer)
-			xfer = list_entry(xfer->transfer_list.next,
-					struct spi_transfer, transfer_list);
-		else
-			xfer = list_entry(msg->transfers.next,
-					struct spi_transfer, transfer_list);
-		as->remaining_bytes = xfer->len;
-		as->current_transfer = xfer;
+	if (!as->current_transfer)
+		xfer = list_entry(msg->transfers.next,
+				struct spi_transfer, transfer_list);
+	else if (!as->next_transfer)
+		xfer = list_entry(as->current_transfer->transfer_list.next,
+				struct spi_transfer, transfer_list);
+	else
+		xfer = NULL;
+
+	if (xfer) {
+		len = xfer->len;
+		atmel_spi_next_xfer_data(master, xfer, &tx_dma, &rx_dma, &len);
+		remaining = xfer->len - len;
+
+		spi_writel(as, TPR, tx_dma);
+		spi_writel(as, RPR, rx_dma);
+
+		if (msg->spi->bits_per_word > 8)
+			len >>= 1;
+		spi_writel(as, TCR, len);
+		spi_writel(as, RCR, len);
+	} else {
+		xfer = as->next_transfer;
+		remaining = as->next_remaining_bytes;
 	}

-	len = as->remaining_bytes;
+	as->current_transfer = xfer;
+	as->current_remaining_bytes = remaining;

-	tx_dma = xfer->tx_dma + xfer->len - len;
-	rx_dma = xfer->rx_dma + xfer->len - len;
+	if (remaining > 0)
+		len = remaining;
+	else if (!atmel_spi_xfer_is_last(msg, xfer) &&
+		atmel_spi_xfer_can_be_chained(xfer)) {
+		xfer = list_entry(xfer->transfer_list.next,
+				struct spi_transfer, transfer_list);
+		len = xfer->len;
+	} else
+		xfer = NULL;

-	/* use scratch buffer only when rx or tx data is unspecified */
-	if (!xfer->rx_buf) {
-		rx_dma = as->buffer_dma;
-		if (len > BUFFER_SIZE)
-			len = BUFFER_SIZE;
-	}
-	if (!xfer->tx_buf) {
-		tx_dma = as->buffer_dma;
-		if (len > BUFFER_SIZE)
-			len = BUFFER_SIZE;
-		memset(as->buffer, 0, len);
-		dma_sync_single_for_device(&as->pdev->dev,
-				as->buffer_dma, len, DMA_TO_DEVICE);
-	}
+	as->next_transfer = xfer;

-	spi_writel(as, RPR, rx_dma);
-	spi_writel(as, TPR, tx_dma);
+	if (xfer) {
+		total = len;
+		atmel_spi_next_xfer_data(master, xfer, &tx_dma, &rx_dma, &len);
+		as->next_remaining_bytes = total - len;
+
+		spi_writel(as, TNPR, tx_dma);
+		spi_writel(as, RNPR, rx_dma);
+
+		if (msg->spi->bits_per_word > 8)
+			len >>= 1;
+		spi_writel(as, TNCR, len);
+		spi_writel(as, RNCR, len);
+	} else {
+		spi_writel(as, TNCR, 0);
+		spi_writel(as, RNCR, 0);
+	}

-	as->remaining_bytes -= len;
-	if (msg->spi->bits_per_word > 8)
-		len >>= 1;
-
-	/* REVISIT: when xfer->delay_usecs == 0, the PDC "next transfer"
-	 * mechanism might help avoid the IRQ latency between transfers
-	 * (and improve the nCS0 errata handling on at91rm9200 chips)
-	 *
-	 * We're also waiting for ENDRX before we start the next
+	/* REVISIT: We're waiting for ENDRX before we start the next
 	 * transfer because we need to handle some difficult timing
 	 * issues otherwise. If we wait for ENDTX in one transfer and
 	 * then starts waiting for ENDRX in the next, it's difficult
@@ -186,8 +245,6 @@ static void atmel_spi_next_xfer(struct s
 	 *
 	 * It should be doable, though. Just not now...
 	 */
-	spi_writel(as, TNCR, 0);
-	spi_writel(as, RNCR, 0);
 	spi_writel(as, IER, SPI_BIT(ENDRX) | SPI_BIT(OVRES));

 	dev_dbg(&msg->spi->dev,
@@ -195,8 +252,6 @@ static void atmel_spi_next_xfer(struct s
 		xfer, xfer->len, xfer->tx_buf, xfer->tx_dma,
 		xfer->rx_buf, xfer->rx_dma, spi_readl(as, IMR));

-	spi_writel(as, TCR, len);
-	spi_writel(as, RCR, len);
 	spi_writel(as, PTCR, SPI_BIT(TXTEN) | SPI_BIT(RXTEN));
 }

@@ -294,6 +349,7 @@ atmel_spi_msg_done(struct spi_master *ma
 	spin_lock(&as->lock);

 	as->current_transfer = NULL;
+	as->next_transfer = NULL;

 	/* continue if needed */
 	if (list_empty(&as->queue) || as->stopping)
@@ -377,7 +433,7 @@ atmel_spi_interrupt(int irq, void *dev_i

 		spi_writel(as, IDR, pending);

-		if (as->remaining_bytes == 0) {
+		if (as->current_remaining_bytes == 0) {
 			msg->actual_length += xfer->len;

 			if (!msg->is_dma_mapped)
@@ -387,7 +443,7 @@ atmel_spi_interrupt(int irq, void *dev_i
 			if (xfer->delay_usecs)
 				udelay(xfer->delay_usecs);

-			if (msg->transfers.prev == &xfer->transfer_list) {
+			if (atmel_spi_xfer_is_last(msg, xfer)) {
 				/* report completed message */
 				atmel_spi_msg_done(master, as, msg, 0,
 						xfer->cs_change);

-------------------------------------------------------

-------------------------------------------------------------------------
This SF.net email is sponsored by: Splunk Inc.
Still grepping through log files to find problems?  Stop.
Now Search log events and configuration files using AJAX and a browser.
Download your FREE copy of Splunk now >> http://get.splunk.com/

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2007-11-14 18:57 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2007-11-14 18:57 Fwd: Re: Fwd: [PATCH] atmel_spi: Allow chained transfers David Brownell

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).