From 2ff9b90ae77a3fe107de26ad7146506dc60f3f42 Mon Sep 17 00:00:00 2001
From: Jonas Gorski <jogo@openwrt.org>
Date: Sat, 24 Nov 2012 14:19:09 +0000
Subject: bcm63xx: fix spi transfer handling

* Accept transfers without bits_per_word set.

* Work around the inability of the hardware of keeping CS asserted.

Signed-off-by: Jonas Gorski <jogo@openwrt.org>

git-svn-id: svn://svn.openwrt.org/openwrt/trunk@34320 3c298f89-4303-0410-b956-a3cf2f4a3e73
---
 ...bcm63xx-fix-transfer-bits_per_words-check.patch |  29 +++
 ...0-spi-bcm63xx-fix-multi-transfer-messages.patch | 279 +++++++++++++++++++++
 .../patches-3.3/404-bcm963xx_flashmap.patch        |   6 +-
 ...bcm63xx-fix-transfer-bits_per_words-check.patch |  29 +++
 ...7-spi-bcm63xx-fix-multi-transfer-messages.patch | 279 +++++++++++++++++++++
 5 files changed, 619 insertions(+), 3 deletions(-)
 create mode 100644 target/linux/brcm63xx/patches-3.3/109-bcm63xx-fix-transfer-bits_per_words-check.patch
 create mode 100644 target/linux/brcm63xx/patches-3.3/110-spi-bcm63xx-fix-multi-transfer-messages.patch
 create mode 100644 target/linux/brcm63xx/patches-3.6/106-bcm63xx-fix-transfer-bits_per_words-check.patch
 create mode 100644 target/linux/brcm63xx/patches-3.6/107-spi-bcm63xx-fix-multi-transfer-messages.patch

(limited to 'target/linux/brcm63xx')

diff --git a/target/linux/brcm63xx/patches-3.3/109-bcm63xx-fix-transfer-bits_per_words-check.patch b/target/linux/brcm63xx/patches-3.3/109-bcm63xx-fix-transfer-bits_per_words-check.patch
new file mode 100644
index 0000000000..37d9d49d6f
--- /dev/null
+++ b/target/linux/brcm63xx/patches-3.3/109-bcm63xx-fix-transfer-bits_per_words-check.patch
@@ -0,0 +1,29 @@
+From fbef4dff80be6254e36ab5b9c655d248a3991ded Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jonas.gorski@gmail.com>
+Date: Sat, 24 Nov 2012 12:08:22 +0100
+Subject: [PATCH 3.7] spi/bcm63xx: fix transfer bits_per_words check
+
+Transfers often do not have bits_per_words set, so use the spi device's
+bits_per_words in this case.
+
+This fixes the driver rejecting valid transfers e.g. generated by
+spi_write() or spi_read().
+
+Cc: stable@vger.kernel.org
+Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
+---
+ drivers/spi/spi-bcm63xx.c |    3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/drivers/spi/spi-bcm63xx.c
++++ b/drivers/spi/spi-bcm63xx.c
+@@ -103,7 +103,8 @@ static int bcm63xx_spi_check_transfer(st
+ {
+ 	u8 bits_per_word;
+ 
+-	bits_per_word = (t) ? t->bits_per_word : spi->bits_per_word;
++	bits_per_word = (t && t->bits_per_word) ?
++			t->bits_per_word : spi->bits_per_word;
+ 	if (bits_per_word != 8) {
+ 		dev_err(&spi->dev, "%s, unsupported bits_per_word=%d\n",
+ 			__func__, bits_per_word);
diff --git a/target/linux/brcm63xx/patches-3.3/110-spi-bcm63xx-fix-multi-transfer-messages.patch b/target/linux/brcm63xx/patches-3.3/110-spi-bcm63xx-fix-multi-transfer-messages.patch
new file mode 100644
index 0000000000..941de48c3f
--- /dev/null
+++ b/target/linux/brcm63xx/patches-3.3/110-spi-bcm63xx-fix-multi-transfer-messages.patch
@@ -0,0 +1,279 @@
+From 0f2ae1e1282ff64f74a5e36f7da874f94911225e Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jonas.gorski@gmail.com>
+Date: Wed, 14 Nov 2012 22:22:33 +0100
+Subject: [PATCH] spi/bcm63xx: fix multi transfer messages
+
+The BCM63XX SPI controller does not support keeping CS asserted after
+sending its buffer. This breaks common usages like spi_write_then_read,
+where it is expected to be kept active during the whole transfers.
+
+Work around this by combining the transfers into one if the buffer
+allows. For spi_write_then_read, use the prepend byte feature to write
+to "prepend" the write if it is less than 15 bytes, allowing the whole
+fifo size for the read.
+
+Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
+---
+Tested on a SPI conntected switch which required keeping CS active between
+the register read command and reading the register contents.
+
+Based on Mark's spi/next.
+
+Not sure if this is stable material, as it's quite invasive.
+
+ drivers/spi/spi-bcm63xx.c |  172 ++++++++++++++++++++++++++++++---------------
+ 1 file changed, 117 insertions(+), 55 deletions(-)
+
+--- a/drivers/spi/spi-bcm63xx.c
++++ b/drivers/spi/spi-bcm63xx.c
+@@ -38,6 +38,8 @@
+ #define PFX		KBUILD_MODNAME
+ #define DRV_VER		"0.1.2"
+ 
++#define BCM63XX_SPI_MAX_PREPEND		15
++
+ struct bcm63xx_spi {
+ 	struct completion	done;
+ 
+@@ -50,16 +52,10 @@ struct bcm63xx_spi {
+ 	unsigned int		msg_type_shift;
+ 	unsigned int		msg_ctl_width;
+ 
+-	/* Data buffers */
+-	const unsigned char	*tx_ptr;
+-	unsigned char		*rx_ptr;
+-
+ 	/* data iomem */
+ 	u8 __iomem		*tx_io;
+ 	const u8 __iomem	*rx_io;
+ 
+-	int			remaining_bytes;
+-
+ 	struct clk		*clk;
+ 	struct platform_device	*pdev;
+ };
+@@ -184,50 +180,60 @@ static int bcm63xx_spi_setup(struct spi_
+ 	return 0;
+ }
+ 
+-/* Fill the TX FIFO with as many bytes as possible */
+-static void bcm63xx_spi_fill_tx_fifo(struct bcm63xx_spi *bs)
+-{
+-	u8 size;
+-
+-	/* Fill the Tx FIFO with as many bytes as possible */
+-	size = bs->remaining_bytes < bs->fifo_size ? bs->remaining_bytes :
+-		bs->fifo_size;
+-	memcpy_toio(bs->tx_io, bs->tx_ptr, size);
+-	bs->remaining_bytes -= size;
+-}
+-
+ static unsigned int bcm63xx_txrx_bufs(struct spi_device *spi,
+-					struct spi_transfer *t)
++					struct spi_transfer *first,
++					unsigned int n_transfers)
+ {
+ 	struct bcm63xx_spi *bs = spi_master_get_devdata(spi->master);
+ 	u16 msg_ctl;
+ 	u16 cmd;
++	unsigned int i, timeout, total_len = 0, prepend_len = 0, len = 0;
++	struct spi_transfer *t = first;
++	u8 rx_tail;
++	bool do_rx = false;
++	bool do_tx = false;
+ 
+ 	/* Disable the CMD_DONE interrupt */
+ 	bcm_spi_writeb(bs, 0, SPI_INT_MASK);
+ 
+-	dev_dbg(&spi->dev, "txrx: tx %p, rx %p, len %d\n",
+-		t->tx_buf, t->rx_buf, t->len);
++	if (n_transfers > 1 && t->tx_buf && t->len <= BCM63XX_SPI_MAX_PREPEND)
++		prepend_len = t->len;
++
++	/* prepare the buffer */
++	for (i = 0; i < n_transfers; i++) {
++		if (t->tx_buf) {
++			do_tx = true;
++			memcpy_toio(bs->tx_io + total_len, t->tx_buf, t->len);
++
++			/* don't prepend more than one tx */
++			if (t != first)
++				prepend_len = 0;
++		}
++
++		if (t->rx_buf) {
++			do_rx = true;
++			if (t == first)
++				prepend_len = 0;
++		}
+ 
+-	/* Transmitter is inhibited */
+-	bs->tx_ptr = t->tx_buf;
+-	bs->rx_ptr = t->rx_buf;
+-
+-	if (t->tx_buf) {
+-		bs->remaining_bytes = t->len;
+-		bcm63xx_spi_fill_tx_fifo(bs);
++		total_len += t->len;
++
++		t = list_entry(t->transfer_list.next, struct spi_transfer,
++			       transfer_list);
+ 	}
+ 
++	len = total_len - prepend_len;
++
+ 	init_completion(&bs->done);
+ 
+ 	/* Fill in the Message control register */
+-	msg_ctl = (t->len << SPI_BYTE_CNT_SHIFT);
++	msg_ctl = (len << SPI_BYTE_CNT_SHIFT);
+ 
+-	if (t->rx_buf && t->tx_buf)
++	if (do_rx && do_tx && prepend_len == 0)
+ 		msg_ctl |= (SPI_FD_RW << bs->msg_type_shift);
+-	else if (t->rx_buf)
++	else if (do_rx)
+ 		msg_ctl |= (SPI_HD_R << bs->msg_type_shift);
+-	else if (t->tx_buf)
++	else if (do_tx)
+ 		msg_ctl |= (SPI_HD_W << bs->msg_type_shift);
+ 
+ 	switch (bs->msg_ctl_width) {
+@@ -245,14 +251,41 @@ static unsigned int bcm63xx_txrx_bufs(st
+ 
+ 	/* Issue the transfer */
+ 	cmd = SPI_CMD_START_IMMEDIATE;
+-	cmd |= (0 << SPI_CMD_PREPEND_BYTE_CNT_SHIFT);
++	cmd |= (prepend_len << SPI_CMD_PREPEND_BYTE_CNT_SHIFT);
+ 	cmd |= (spi->chip_select << SPI_CMD_DEVICE_ID_SHIFT);
+ 	bcm_spi_writew(bs, cmd, SPI_CMD);
+ 
+ 	/* Enable the CMD_DONE interrupt */
+ 	bcm_spi_writeb(bs, SPI_INTR_CMD_DONE, SPI_INT_MASK);
+ 
+-	return t->len - bs->remaining_bytes;
++	timeout = wait_for_completion_timeout(&bs->done, HZ);
++	if (!timeout)
++		return -ETIMEDOUT;
++
++	/* read out all data */
++	rx_tail = bcm_spi_readb(bs, SPI_RX_TAIL);
++
++	if (do_rx && rx_tail != len)
++		return -EINVAL;
++
++	if (!rx_tail)
++		return total_len;
++
++	len = 0;
++	t = first;
++	/* Read out all the data */
++	for (i = 0; i < n_transfers; i++) {
++		if (t->rx_buf)
++			memcpy_fromio(t->rx_buf, bs->rx_io + len, t->len);
++
++		if (t != first || prepend_len == 0)
++			len += t->len;
++
++		t = list_entry(t->transfer_list.next, struct spi_transfer,
++			       transfer_list);
++	}
++
++	return total_len;
+ }
+ 
+ static int bcm63xx_spi_prepare_transfer(struct spi_master *master)
+@@ -277,42 +310,71 @@ static int bcm63xx_spi_transfer_one(stru
+ 					struct spi_message *m)
+ {
+ 	struct bcm63xx_spi *bs = spi_master_get_devdata(master);
+-	struct spi_transfer *t;
++	struct spi_transfer *t, *first = NULL;
+ 	struct spi_device *spi = m->spi;
+ 	int status = 0;
+-	unsigned int timeout = 0;
++	unsigned int n_transfers = 0, total_len = 0;
++	bool can_use_prepend = false;
+ 
++	/*
++	 * This SPI controller does not support keeping CS active after a
++	 * transfer, so we need to combine the transfers into one until we may
++	 * deassert CS.
++	 */
+ 	list_for_each_entry(t, &m->transfers, transfer_list) {
+-		unsigned int len = t->len;
+-		u8 rx_tail;
+-
+ 		status = bcm63xx_spi_check_transfer(spi, t);
+ 		if (status < 0)
+ 			goto exit;
+ 
+-		/* configure adapter for a new transfer */
+-		bcm63xx_spi_setup_transfer(spi, t);
++		if (!first)
++			first = t;
+ 
+-		while (len) {
+-			/* send the data */
+-			len -= bcm63xx_txrx_bufs(spi, t);
+-
+-			timeout = wait_for_completion_timeout(&bs->done, HZ);
+-			if (!timeout) {
+-				status = -ETIMEDOUT;
+-				goto exit;
+-			}
++		n_transfers++;
++		total_len += t->len;
++
++		if (n_transfers == 2 && !first->rx_buf && !t->tx_buf &&
++		    first->len <= BCM63XX_SPI_MAX_PREPEND)
++			can_use_prepend = true;
++		else if (can_use_prepend && t->tx_buf)
++			can_use_prepend = false;
++
++		if ((can_use_prepend &&
++		     total_len > (bs->fifo_size + BCM63XX_SPI_MAX_PREPEND)) ||
++		    (!can_use_prepend && total_len > bs->fifo_size)) {
++			status = -EINVAL;
++			goto exit;
++		}
+ 
+-			/* read out all data */
+-			rx_tail = bcm_spi_readb(bs, SPI_RX_TAIL);
++		/* all transfers have to be made at the same speed */
++		if (t->speed_hz != first->speed_hz) {
++			status = -EINVAL;
++			goto exit;
++		}
+ 
+-			/* Read out all the data */
+-			if (rx_tail)
+-				memcpy_fromio(bs->rx_ptr, bs->rx_io, rx_tail);
++		/* CS will be deasserted directly after the transfer */
++		if (t->delay_usecs) {
++			status = -EINVAL;
++			goto exit;
+ 		}
+ 
+-		m->actual_length += t->len;
++		if (t->cs_change ||
++		    list_is_last(&t->transfer_list, &m->transfers)) {
++			/* configure adapter for a new transfer */
++			bcm63xx_spi_setup_transfer(spi, first);
++
++			status = bcm63xx_txrx_bufs(spi, first, n_transfers);
++			if (status < 0)
++				goto exit;
++
++			m->actual_length += status;
++			first = NULL;
++			status = 0;
++			n_transfers = 0;
++			total_len = 0;
++			can_use_prepend = false;
++		}
+ 	}
++
+ exit:
+ 	m->status = status;
+ 	spi_finalize_current_message(master);
diff --git a/target/linux/brcm63xx/patches-3.3/404-bcm963xx_flashmap.patch b/target/linux/brcm63xx/patches-3.3/404-bcm963xx_flashmap.patch
index 8338962e5b..8f91623c8a 100644
--- a/target/linux/brcm63xx/patches-3.3/404-bcm963xx_flashmap.patch
+++ b/target/linux/brcm63xx/patches-3.3/404-bcm963xx_flashmap.patch
@@ -23,7 +23,7 @@ Signed-off-by: Axel Gembe <ago@bastart.eu.org>
  	.width			= 2,
 --- a/drivers/mtd/redboot.c
 +++ b/drivers/mtd/redboot.c
-@@ -75,6 +75,7 @@ static int parse_redboot_partitions(stru
+@@ -72,6 +72,7 @@ static int parse_redboot_partitions(stru
  	int nulllen = 0;
  	int numslots;
  	unsigned long offset;
@@ -31,7 +31,7 @@ Signed-off-by: Axel Gembe <ago@bastart.eu.org>
  #ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED
  	static char nullstring[] = "unallocated";
  #endif
-@@ -181,6 +182,16 @@ static int parse_redboot_partitions(stru
+@@ -178,6 +179,16 @@ static int parse_redboot_partitions(stru
  		goto out;
  	}
  
@@ -48,7 +48,7 @@ Signed-off-by: Axel Gembe <ago@bastart.eu.org>
  	for (i = 0; i < numslots; i++) {
  		struct fis_list *new_fl, **prev;
  
-@@ -201,10 +212,10 @@ static int parse_redboot_partitions(stru
+@@ -198,10 +209,10 @@ static int parse_redboot_partitions(stru
  			goto out;
  		}
  		new_fl->img = &buf[i];
diff --git a/target/linux/brcm63xx/patches-3.6/106-bcm63xx-fix-transfer-bits_per_words-check.patch b/target/linux/brcm63xx/patches-3.6/106-bcm63xx-fix-transfer-bits_per_words-check.patch
new file mode 100644
index 0000000000..37d9d49d6f
--- /dev/null
+++ b/target/linux/brcm63xx/patches-3.6/106-bcm63xx-fix-transfer-bits_per_words-check.patch
@@ -0,0 +1,29 @@
+From fbef4dff80be6254e36ab5b9c655d248a3991ded Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jonas.gorski@gmail.com>
+Date: Sat, 24 Nov 2012 12:08:22 +0100
+Subject: [PATCH 3.7] spi/bcm63xx: fix transfer bits_per_words check
+
+Transfers often do not have bits_per_words set, so use the spi device's
+bits_per_words in this case.
+
+This fixes the driver rejecting valid transfers e.g. generated by
+spi_write() or spi_read().
+
+Cc: stable@vger.kernel.org
+Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
+---
+ drivers/spi/spi-bcm63xx.c |    3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/drivers/spi/spi-bcm63xx.c
++++ b/drivers/spi/spi-bcm63xx.c
+@@ -103,7 +103,8 @@ static int bcm63xx_spi_check_transfer(st
+ {
+ 	u8 bits_per_word;
+ 
+-	bits_per_word = (t) ? t->bits_per_word : spi->bits_per_word;
++	bits_per_word = (t && t->bits_per_word) ?
++			t->bits_per_word : spi->bits_per_word;
+ 	if (bits_per_word != 8) {
+ 		dev_err(&spi->dev, "%s, unsupported bits_per_word=%d\n",
+ 			__func__, bits_per_word);
diff --git a/target/linux/brcm63xx/patches-3.6/107-spi-bcm63xx-fix-multi-transfer-messages.patch b/target/linux/brcm63xx/patches-3.6/107-spi-bcm63xx-fix-multi-transfer-messages.patch
new file mode 100644
index 0000000000..2da5044492
--- /dev/null
+++ b/target/linux/brcm63xx/patches-3.6/107-spi-bcm63xx-fix-multi-transfer-messages.patch
@@ -0,0 +1,279 @@
+From 0f2ae1e1282ff64f74a5e36f7da874f94911225e Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jonas.gorski@gmail.com>
+Date: Wed, 14 Nov 2012 22:22:33 +0100
+Subject: [PATCH] spi/bcm63xx: fix multi transfer messages
+
+The BCM63XX SPI controller does not support keeping CS asserted after
+sending its buffer. This breaks common usages like spi_write_then_read,
+where it is expected to be kept active during the whole transfers.
+
+Work around this by combining the transfers into one if the buffer
+allows. For spi_write_then_read, use the prepend byte feature to write
+to "prepend" the write if it is less than 15 bytes, allowing the whole
+fifo size for the read.
+
+Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
+---
+Tested on a SPI conntected switch which required keeping CS active between
+the register read command and reading the register contents.
+
+Based on Mark's spi/next.
+
+Not sure if this is stable material, as it's quite invasive.
+
+ drivers/spi/spi-bcm63xx.c |  172 ++++++++++++++++++++++++++++++---------------
+ 1 file changed, 117 insertions(+), 55 deletions(-)
+
+--- a/drivers/spi/spi-bcm63xx.c
++++ b/drivers/spi/spi-bcm63xx.c
+@@ -38,6 +38,8 @@
+ #define PFX		KBUILD_MODNAME
+ #define DRV_VER		"0.1.2"
+ 
++#define BCM63XX_SPI_MAX_PREPEND		15
++
+ struct bcm63xx_spi {
+ 	struct completion	done;
+ 
+@@ -50,16 +52,10 @@ struct bcm63xx_spi {
+ 	unsigned int		msg_type_shift;
+ 	unsigned int		msg_ctl_width;
+ 
+-	/* Data buffers */
+-	const unsigned char	*tx_ptr;
+-	unsigned char		*rx_ptr;
+-
+ 	/* data iomem */
+ 	u8 __iomem		*tx_io;
+ 	const u8 __iomem	*rx_io;
+ 
+-	int			remaining_bytes;
+-
+ 	struct clk		*clk;
+ 	struct platform_device	*pdev;
+ };
+@@ -184,50 +180,60 @@ static int bcm63xx_spi_setup(struct spi_
+ 	return 0;
+ }
+ 
+-/* Fill the TX FIFO with as many bytes as possible */
+-static void bcm63xx_spi_fill_tx_fifo(struct bcm63xx_spi *bs)
+-{
+-	u8 size;
+-
+-	/* Fill the Tx FIFO with as many bytes as possible */
+-	size = bs->remaining_bytes < bs->fifo_size ? bs->remaining_bytes :
+-		bs->fifo_size;
+-	memcpy_toio(bs->tx_io, bs->tx_ptr, size);
+-	bs->remaining_bytes -= size;
+-}
+-
+ static unsigned int bcm63xx_txrx_bufs(struct spi_device *spi,
+-					struct spi_transfer *t)
++					struct spi_transfer *first,
++					unsigned int n_transfers)
+ {
+ 	struct bcm63xx_spi *bs = spi_master_get_devdata(spi->master);
+ 	u16 msg_ctl;
+ 	u16 cmd;
++	unsigned int i, timeout, total_len = 0, prepend_len = 0, len = 0;
++	struct spi_transfer *t = first;
++	u8 rx_tail;
++	bool do_rx = false;
++	bool do_tx = false;
+ 
+ 	/* Disable the CMD_DONE interrupt */
+ 	bcm_spi_writeb(bs, 0, SPI_INT_MASK);
+ 
+-	dev_dbg(&spi->dev, "txrx: tx %p, rx %p, len %d\n",
+-		t->tx_buf, t->rx_buf, t->len);
++	if (n_transfers > 1 && t->tx_buf && t->len <= BCM63XX_SPI_MAX_PREPEND)
++		prepend_len = t->len;
++
++	/* prepare the buffer */
++	for (i = 0; i < n_transfers; i++) {
++		if (t->tx_buf) {
++			do_tx = true;
++			memcpy_toio(bs->tx_io + total_len, t->tx_buf, t->len);
++
++			/* don't prepend more than one tx */
++			if (t != first)
++				prepend_len = 0;
++		}
++
++		if (t->rx_buf) {
++			do_rx = true;
++			if (t == first)
++				prepend_len = 0;
++		}
+ 
+-	/* Transmitter is inhibited */
+-	bs->tx_ptr = t->tx_buf;
+-	bs->rx_ptr = t->rx_buf;
+-
+-	if (t->tx_buf) {
+-		bs->remaining_bytes = t->len;
+-		bcm63xx_spi_fill_tx_fifo(bs);
++		total_len += t->len;
++
++		t = list_entry(t->transfer_list.next, struct spi_transfer,
++			       transfer_list);
+ 	}
+ 
++	len = total_len - prepend_len;
++
+ 	init_completion(&bs->done);
+ 
+ 	/* Fill in the Message control register */
+-	msg_ctl = (t->len << SPI_BYTE_CNT_SHIFT);
++	msg_ctl = (len << SPI_BYTE_CNT_SHIFT);
+ 
+-	if (t->rx_buf && t->tx_buf)
++	if (do_rx && do_tx && prepend_len == 0)
+ 		msg_ctl |= (SPI_FD_RW << bs->msg_type_shift);
+-	else if (t->rx_buf)
++	else if (do_rx)
+ 		msg_ctl |= (SPI_HD_R << bs->msg_type_shift);
+-	else if (t->tx_buf)
++	else if (do_tx)
+ 		msg_ctl |= (SPI_HD_W << bs->msg_type_shift);
+ 
+ 	switch (bs->msg_ctl_width) {
+@@ -241,14 +247,41 @@ static unsigned int bcm63xx_txrx_bufs(st
+ 
+ 	/* Issue the transfer */
+ 	cmd = SPI_CMD_START_IMMEDIATE;
+-	cmd |= (0 << SPI_CMD_PREPEND_BYTE_CNT_SHIFT);
++	cmd |= (prepend_len << SPI_CMD_PREPEND_BYTE_CNT_SHIFT);
+ 	cmd |= (spi->chip_select << SPI_CMD_DEVICE_ID_SHIFT);
+ 	bcm_spi_writew(bs, cmd, SPI_CMD);
+ 
+ 	/* Enable the CMD_DONE interrupt */
+ 	bcm_spi_writeb(bs, SPI_INTR_CMD_DONE, SPI_INT_MASK);
+ 
+-	return t->len - bs->remaining_bytes;
++	timeout = wait_for_completion_timeout(&bs->done, HZ);
++	if (!timeout)
++		return -ETIMEDOUT;
++
++	/* read out all data */
++	rx_tail = bcm_spi_readb(bs, SPI_RX_TAIL);
++
++	if (do_rx && rx_tail != len)
++		return -EINVAL;
++
++	if (!rx_tail)
++		return total_len;
++
++	len = 0;
++	t = first;
++	/* Read out all the data */
++	for (i = 0; i < n_transfers; i++) {
++		if (t->rx_buf)
++			memcpy_fromio(t->rx_buf, bs->rx_io + len, t->len);
++
++		if (t != first || prepend_len == 0)
++			len += t->len;
++
++		t = list_entry(t->transfer_list.next, struct spi_transfer,
++			       transfer_list);
++	}
++
++	return total_len;
+ }
+ 
+ static int bcm63xx_spi_prepare_transfer(struct spi_master *master)
+@@ -273,42 +306,71 @@ static int bcm63xx_spi_transfer_one(stru
+ 					struct spi_message *m)
+ {
+ 	struct bcm63xx_spi *bs = spi_master_get_devdata(master);
+-	struct spi_transfer *t;
++	struct spi_transfer *t, *first = NULL;
+ 	struct spi_device *spi = m->spi;
+ 	int status = 0;
+-	unsigned int timeout = 0;
++	unsigned int n_transfers = 0, total_len = 0;
++	bool can_use_prepend = false;
+ 
++	/*
++	 * This SPI controller does not support keeping CS active after a
++	 * transfer, so we need to combine the transfers into one until we may
++	 * deassert CS.
++	 */
+ 	list_for_each_entry(t, &m->transfers, transfer_list) {
+-		unsigned int len = t->len;
+-		u8 rx_tail;
+-
+ 		status = bcm63xx_spi_check_transfer(spi, t);
+ 		if (status < 0)
+ 			goto exit;
+ 
+-		/* configure adapter for a new transfer */
+-		bcm63xx_spi_setup_transfer(spi, t);
++		if (!first)
++			first = t;
+ 
+-		while (len) {
+-			/* send the data */
+-			len -= bcm63xx_txrx_bufs(spi, t);
+-
+-			timeout = wait_for_completion_timeout(&bs->done, HZ);
+-			if (!timeout) {
+-				status = -ETIMEDOUT;
+-				goto exit;
+-			}
++		n_transfers++;
++		total_len += t->len;
++
++		if (n_transfers == 2 && !first->rx_buf && !t->tx_buf &&
++		    first->len <= BCM63XX_SPI_MAX_PREPEND)
++			can_use_prepend = true;
++		else if (can_use_prepend && t->tx_buf)
++			can_use_prepend = false;
++
++		if ((can_use_prepend &&
++		     total_len > (bs->fifo_size + BCM63XX_SPI_MAX_PREPEND)) ||
++		    (!can_use_prepend && total_len > bs->fifo_size)) {
++			status = -EINVAL;
++			goto exit;
++		}
+ 
+-			/* read out all data */
+-			rx_tail = bcm_spi_readb(bs, SPI_RX_TAIL);
++		/* all transfers have to be made at the same speed */
++		if (t->speed_hz != first->speed_hz) {
++			status = -EINVAL;
++			goto exit;
++		}
+ 
+-			/* Read out all the data */
+-			if (rx_tail)
+-				memcpy_fromio(bs->rx_ptr, bs->rx_io, rx_tail);
++		/* CS will be deasserted directly after the transfer */
++		if (t->delay_usecs) {
++			status = -EINVAL;
++			goto exit;
+ 		}
+ 
+-		m->actual_length += t->len;
++		if (t->cs_change ||
++		    list_is_last(&t->transfer_list, &m->transfers)) {
++			/* configure adapter for a new transfer */
++			bcm63xx_spi_setup_transfer(spi, first);
++
++			status = bcm63xx_txrx_bufs(spi, first, n_transfers);
++			if (status < 0)
++				goto exit;
++
++			m->actual_length += status;
++			first = NULL;
++			status = 0;
++			n_transfers = 0;
++			total_len = 0;
++			can_use_prepend = false;
++		}
+ 	}
++
+ exit:
+ 	m->status = status;
+ 	spi_finalize_current_message(master);
-- 
cgit v1.2.3