241 lines
6.7 KiB
Diff
241 lines
6.7 KiB
Diff
|
From 725e81d507b1098cd275d4e3333c77c4b750fa79 Mon Sep 17 00:00:00 2001
|
||
|
From: Jonas Gorski <jogo@openwrt.org>
|
||
|
Date: Sun, 9 Dec 2012 01:53:05 +0100
|
||
|
Subject: [PATCH V2 2/2] spi/bcm63xx: work around inability to keep CS up
|
||
|
|
||
|
This SPI controller does not support keeping CS asserted after sending
|
||
|
a transfer.
|
||
|
Since messages expected on this SPI controller are rather short, we can
|
||
|
work around it for normal use cases by sending all transfers at once in
|
||
|
a big full duplex stream.
|
||
|
|
||
|
This means that we cannot change the speed between transfers if they
|
||
|
require CS to be kept asserted, but these would have been rejected
|
||
|
before anyway because of the inability of keeping CS asserted.
|
||
|
|
||
|
Signed-off-by: Jonas Gorski <jogo@openwrt.org>
|
||
|
---
|
||
|
V1 -> V2:
|
||
|
* split out rejection logic into separate patch
|
||
|
* fixed return type of bcm63xx_txrx_bufs()
|
||
|
* slightly reworked bcm63xx_txrx_bufs, obsoleting one local variable
|
||
|
|
||
|
drivers/spi/spi-bcm63xx.c | 134 +++++++++++++++++++++++++++++++++++----------
|
||
|
1 file changed, 106 insertions(+), 28 deletions(-)
|
||
|
|
||
|
--- a/drivers/spi/spi-bcm63xx.c
|
||
|
+++ b/drivers/spi/spi-bcm63xx.c
|
||
|
@@ -37,6 +37,8 @@
|
||
|
|
||
|
#define PFX KBUILD_MODNAME
|
||
|
|
||
|
+#define BCM63XX_SPI_MAX_PREPEND 15
|
||
|
+
|
||
|
struct bcm63xx_spi {
|
||
|
struct completion done;
|
||
|
|
||
|
@@ -169,13 +171,17 @@ static int bcm63xx_spi_setup(struct spi_
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
-static int bcm63xx_txrx_bufs(struct spi_device *spi, struct spi_transfer *t)
|
||
|
+static int bcm63xx_txrx_bufs(struct spi_device *spi, struct spi_transfer *first,
|
||
|
+ unsigned int num_transfers)
|
||
|
{
|
||
|
struct bcm63xx_spi *bs = spi_master_get_devdata(spi->master);
|
||
|
u16 msg_ctl;
|
||
|
u16 cmd;
|
||
|
u8 rx_tail;
|
||
|
- unsigned int timeout = 0;
|
||
|
+ unsigned int i, timeout = 0, prepend_len = 0, len = 0;
|
||
|
+ struct spi_transfer *t = first;
|
||
|
+ bool do_rx = false;
|
||
|
+ bool do_tx = false;
|
||
|
|
||
|
/* Disable the CMD_DONE interrupt */
|
||
|
bcm_spi_writeb(bs, 0, SPI_INT_MASK);
|
||
|
@@ -183,19 +189,45 @@ static int bcm63xx_txrx_bufs(struct spi_
|
||
|
dev_dbg(&spi->dev, "txrx: tx %p, rx %p, len %d\n",
|
||
|
t->tx_buf, t->rx_buf, t->len);
|
||
|
|
||
|
- if (t->tx_buf)
|
||
|
- memcpy_toio(bs->tx_io, t->tx_buf, t->len);
|
||
|
+ if (num_transfers > 1 && t->tx_buf && t->len <= BCM63XX_SPI_MAX_PREPEND)
|
||
|
+ prepend_len = t->len;
|
||
|
+
|
||
|
+ /* prepare the buffer */
|
||
|
+ for (i = 0; i < num_transfers; i++) {
|
||
|
+ if (t->tx_buf) {
|
||
|
+ do_tx = true;
|
||
|
+ memcpy_toio(bs->tx_io + 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;
|
||
|
+ /* prepend is half-duplex write only */
|
||
|
+ if (t == first)
|
||
|
+ prepend_len = 0;
|
||
|
+ }
|
||
|
+
|
||
|
+ len += t->len;
|
||
|
+
|
||
|
+ t = list_entry(t->transfer_list.next, struct spi_transfer,
|
||
|
+ transfer_list);
|
||
|
+ }
|
||
|
+
|
||
|
+ 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) {
|
||
|
@@ -209,7 +241,7 @@ static int bcm63xx_txrx_bufs(struct spi_
|
||
|
|
||
|
/* 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);
|
||
|
|
||
|
@@ -223,9 +255,25 @@ static int bcm63xx_txrx_bufs(struct spi_
|
||
|
/* read out all data */
|
||
|
rx_tail = bcm_spi_readb(bs, SPI_RX_TAIL);
|
||
|
|
||
|
+ if (do_rx && rx_tail != len)
|
||
|
+ return -EIO;
|
||
|
+
|
||
|
+ if (!rx_tail)
|
||
|
+ return 0;
|
||
|
+
|
||
|
+ len = 0;
|
||
|
+ t = first;
|
||
|
/* Read out all the data */
|
||
|
- if (rx_tail)
|
||
|
- memcpy_fromio(t->rx_ptr, bs->rx_io, rx_tail);
|
||
|
+ for (i = 0; i < num_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 0;
|
||
|
}
|
||
|
@@ -252,46 +300,76 @@ 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 n_transfers = 0, total_len = 0;
|
||
|
+ bool can_use_prepend = false;
|
||
|
|
||
|
+ /*
|
||
|
+ * This SPI controller does not support keeping CS active after a
|
||
|
+ * transfer.
|
||
|
+ * Work around this by merging as many transfers we can into one big
|
||
|
+ * full-duplex transfers.
|
||
|
+ */
|
||
|
list_for_each_entry(t, &m->transfers, transfer_list) {
|
||
|
status = bcm63xx_spi_check_transfer(spi, t);
|
||
|
if (status < 0)
|
||
|
goto exit;
|
||
|
|
||
|
+ if (!first)
|
||
|
+ first = t;
|
||
|
+
|
||
|
+ 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;
|
||
|
+
|
||
|
/* we can only transfer one fifo worth of data */
|
||
|
- if (t->len > bs->fifo_size) {
|
||
|
+ if ((can_use_prepend &&
|
||
|
+ total_len > (bs->fifo_size + BCM63XX_SPI_MAX_PREPEND)) ||
|
||
|
+ (!can_use_prepend && total_len > bs->fifo_size)) {
|
||
|
dev_err(&spi->dev, "unable to do transfers larger than FIFO size (%i > %i)\n",
|
||
|
- t->len, bs->fifo_size);
|
||
|
+ total_len, bs->fifo_size);
|
||
|
status = -EINVAL;
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
- /* CS will be deasserted directly after transfer */
|
||
|
- if (t->delay_usecs) {
|
||
|
- dev_err(&spi->dev, "unable to keep CS asserted after transfer\n");
|
||
|
+ /* all combined transfers have to have the same speed */
|
||
|
+ if (t->speed_hz != first->speed_hz) {
|
||
|
+ dev_err(&spi->dev, "unable to change speed between transfers\n");
|
||
|
status = -EINVAL;
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
- if (!t->cs_change &&
|
||
|
- !list_is_last(&t->transfer_list, &m->transfers)) {
|
||
|
- dev_err(&spi->dev, "unable to keep CS asserted between transfers\n");
|
||
|
+ /* CS will be deasserted directly after transfer */
|
||
|
+ if (t->delay_usecs) {
|
||
|
+ dev_err(&spi->dev, "unable to keep CS asserted after transfer\n");
|
||
|
status = -EINVAL;
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
- /* configure adapter for a new transfer */
|
||
|
- bcm63xx_spi_setup_transfer(spi, t);
|
||
|
-
|
||
|
- /* send the data */
|
||
|
- status = bcm63xx_txrx_bufs(spi, t);
|
||
|
- if (status)
|
||
|
- 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);
|
||
|
+
|
||
|
+ /* send the data */
|
||
|
+ status = bcm63xx_txrx_bufs(spi, first, n_transfers);
|
||
|
+ if (status)
|
||
|
+ goto exit;
|
||
|
+
|
||
|
+ m->actual_length += total_len;
|
||
|
+
|
||
|
+ first = NULL;
|
||
|
+ n_transfers = 0;
|
||
|
+ total_len = 0;
|
||
|
+ can_use_prepend = false;
|
||
|
+ }
|
||
|
}
|
||
|
exit:
|
||
|
m->status = status;
|