summaryrefslogtreecommitdiff
path: root/drivers/spi/spi.c
diff options
context:
space:
mode:
authorMark Brown <broonie@linaro.org>2014-01-29 00:17:03 +0400
committerMark Brown <broonie@linaro.org>2014-02-05 00:31:33 +0400
commit3a2eba9bd0a6447dfbc01635e4cd0689f5f2bdad (patch)
tree642ccd6b3b125435d4e6efa2dce51e2ccb34c485 /drivers/spi/spi.c
parent3700c6eb1e41a898c35c8dd4d5b10dc65fdaf486 (diff)
downloadlinux-3a2eba9bd0a6447dfbc01635e4cd0689f5f2bdad.tar.xz
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>
Diffstat (limited to 'drivers/spi/spi.c')
-rw-r--r--drivers/spi/spi.c47
1 files changed, 47 insertions, 0 deletions
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index bcdaa74f1c8e..bb7cf561c311 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,