diff mbox

[LTSI-3.14,251/894] spi: Provide core support for full duplex devices

Message ID 1409209620-24487-252-git-send-email-horms+renesas@verge.net.au (mailing list archive)
State New, archived
Headers show

Commit Message

Simon Horman Aug. 28, 2014, 6:56 a.m. UTC
From: Mark Brown <broonie@linaro.org>

It is fairly common for SPI devices to require that one or both transfer
directions is always active. Currently drivers open code this in various
ways with varying degrees of efficiency. Start factoring this out by
providing flags SPI_MASTER_MUST_TX and SPI_MASTER_MUST_RX. These will cause
the core to provide buffers for the requested direction if none are
specified in the underlying transfer.

Currently this is fairly inefficient since we actually allocate a data
buffer which may get large, support for mapping transfers using a
scatterlist will allow us to avoid this for DMA based transfers.

Signed-off-by: Mark Brown <broonie@linaro.org>
(cherry picked from commit 3a2eba9bd0a6447dfbc01635e4cd0689f5f2bdad)
Signed-off-by: Simon Horman <horms+renesas@verge.net.au>
---
 drivers/spi/spi.c       | 47 +++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/spi/spi.h |  6 ++++++
 2 files changed, 53 insertions(+)
diff mbox

Patch

diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index 048eb72..dbab668 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -587,6 +587,49 @@  static int spi_map_msg(struct spi_master *master, struct spi_message *msg)
 	struct device *dev = master->dev.parent;
 	struct device *tx_dev, *rx_dev;
 	struct spi_transfer *xfer;
+	void *tmp;
+	size_t max_tx, max_rx;
+
+	if (master->flags & (SPI_MASTER_MUST_RX | SPI_MASTER_MUST_TX)) {
+		max_tx = 0;
+		max_rx = 0;
+
+		list_for_each_entry(xfer, &msg->transfers, transfer_list) {
+			if ((master->flags & SPI_MASTER_MUST_TX) &&
+			    !xfer->tx_buf)
+				max_tx = max(xfer->len, max_tx);
+			if ((master->flags & SPI_MASTER_MUST_RX) &&
+			    !xfer->rx_buf)
+				max_rx = max(xfer->len, max_rx);
+		}
+
+		if (max_tx) {
+			tmp = krealloc(master->dummy_tx, max_tx,
+				       GFP_KERNEL | GFP_DMA);
+			if (!tmp)
+				return -ENOMEM;
+			master->dummy_tx = tmp;
+			memset(tmp, 0, max_tx);
+		}
+
+		if (max_rx) {
+			tmp = krealloc(master->dummy_rx, max_rx,
+				       GFP_KERNEL | GFP_DMA);
+			if (!tmp)
+				return -ENOMEM;
+			master->dummy_rx = tmp;
+		}
+
+		if (max_tx || max_rx) {
+			list_for_each_entry(xfer, &msg->transfers,
+					    transfer_list) {
+				if (!xfer->tx_buf)
+					xfer->tx_buf = master->dummy_tx;
+				if (!xfer->rx_buf)
+					xfer->rx_buf = master->dummy_rx;
+			}
+		}
+	}
 
 	if (msg->is_dma_mapped || !master->can_dma)
 		return 0;
@@ -759,6 +802,10 @@  static void spi_pump_messages(struct kthread_work *work)
 		}
 		master->busy = false;
 		spin_unlock_irqrestore(&master->queue_lock, flags);
+		kfree(master->dummy_rx);
+		master->dummy_rx = NULL;
+		kfree(master->dummy_tx);
+		master->dummy_tx = NULL;
 		if (master->unprepare_transfer_hardware &&
 		    master->unprepare_transfer_hardware(master))
 			dev_err(&master->dev,
diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index dd8b91d..c4c6809 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -350,6 +350,8 @@  struct spi_master {
 #define SPI_MASTER_HALF_DUPLEX	BIT(0)		/* can't do full duplex */
 #define SPI_MASTER_NO_RX	BIT(1)		/* can't do buffer read */
 #define SPI_MASTER_NO_TX	BIT(2)		/* can't do buffer write */
+#define SPI_MASTER_MUST_RX      BIT(3)		/* requires rx */
+#define SPI_MASTER_MUST_TX      BIT(4)		/* requires tx */
 
 	/* lock and mutex for SPI bus locking */
 	spinlock_t		bus_lock_spinlock;
@@ -446,6 +448,10 @@  struct spi_master {
 	/* DMA channels for use with core dmaengine helpers */
 	struct dma_chan		*dma_tx;
 	struct dma_chan		*dma_rx;
+
+	/* dummy data for full duplex devices */
+	void			*dummy_rx;
+	void			*dummy_tx;
 };
 
 static inline void *spi_master_get_devdata(struct spi_master *master)