| From 36996316db8296af016c6636b2fb5d00974fc2de Mon Sep 17 00:00:00 2001 |
| From: Mark Brown <broonie@linaro.org> |
| Date: Tue, 28 Jan 2014 20:17:03 +0000 |
| Subject: 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) |
| 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 --git a/drivers/spi/spi.c b/drivers/spi/spi.c |
| index 048eb72029a4..dbab6681a1a1 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 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) |
| -- |
| 2.1.2 |
| |