summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Brown <broonie@linaro.org>2014-01-28 20:17:03 +0000
committerNitin Garg <nitin.garg@freescale.com>2015-01-15 21:18:57 -0600
commit1d94c8e9912277e1e9ca348c10b406f2f74d73d5 (patch)
tree62f21b1371aa984caeb2cc89e3b80a02e63f056b
parent45cbd1f6cfb57a7c410f6d7788c3020b0be6e212 (diff)
spi: Provide core support for full duplex devices
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) (cherry picked from commit d88018dac8450a46cf68d5bd9711bf71cc751e8d)
-rw-r--r--drivers/spi/spi.c47
-rw-r--r--include/linux/spi/spi.h6
2 files changed, 53 insertions, 0 deletions
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index 7e008aa3c529..0189a24f699c 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -592,6 +592,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;
@@ -774,6 +817,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 dd8b91d95110..c4c68093cd7c 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)