[CFT 07/11] spi: omap2-mcspi: add DMA engine support
Russell King
rmk+kernel at arm.linux.org.uk
Thu Jun 7 07:08:35 EDT 2012
Add DMA engine support to the OMAP SPI driver. This supplements the
private DMA API implementation contained within this driver, and the
driver can be independently switched at build time between using DMA
engine and the private DMA API for the transmit and receive sides.
Tested-by: Shubhrajyoti <shubhrajyoti at ti.com>
Acked-by: Grant Likely <grant.likely at secretlab.ca>
Signed-off-by: Russell King <rmk+kernel at arm.linux.org.uk>
---
drivers/spi/spi-omap2-mcspi.c | 183 +++++++++++++++++++++++++++++++++-------
1 files changed, 151 insertions(+), 32 deletions(-)
diff --git a/drivers/spi/spi-omap2-mcspi.c b/drivers/spi/spi-omap2-mcspi.c
index 46ef5fe..ca016df 100644
--- a/drivers/spi/spi-omap2-mcspi.c
+++ b/drivers/spi/spi-omap2-mcspi.c
@@ -20,6 +20,8 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
+#define USE_DMA_ENGINE_RX
+#define USE_DMA_ENGINE_TX
#include <linux/kernel.h>
#include <linux/init.h>
@@ -28,6 +30,8 @@
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/omap-dma.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/clk.h>
@@ -93,6 +97,8 @@
/* We have 2 DMA channels per CS, one for RX and one for TX */
struct omap2_mcspi_dma {
+ struct dma_chan *dma_tx;
+ struct dma_chan *dma_rx;
int dma_tx_channel;
int dma_rx_channel;
@@ -300,6 +306,30 @@ static int mcspi_wait_for_reg_bit(void __iomem *reg, unsigned long bit)
return 0;
}
+static void omap2_mcspi_rx_callback(void *data)
+{
+ struct spi_device *spi = data;
+ struct omap2_mcspi *mcspi = spi_master_get_devdata(spi->master);
+ struct omap2_mcspi_dma *mcspi_dma = &mcspi->dma_channels[spi->chip_select];
+
+ complete(&mcspi_dma->dma_rx_completion);
+
+ /* We must disable the DMA RX request */
+ omap2_mcspi_set_dma_req(spi, 1, 0);
+}
+
+static void omap2_mcspi_tx_callback(void *data)
+{
+ struct spi_device *spi = data;
+ struct omap2_mcspi *mcspi = spi_master_get_devdata(spi->master);
+ struct omap2_mcspi_dma *mcspi_dma = &mcspi->dma_channels[spi->chip_select];
+
+ complete(&mcspi_dma->dma_tx_completion);
+
+ /* We must disable the DMA TX request */
+ omap2_mcspi_set_dma_req(spi, 0, 0);
+}
+
static unsigned
omap2_mcspi_txrx_dma(struct spi_device *spi, struct spi_transfer *xfer)
{
@@ -314,6 +344,9 @@ omap2_mcspi_txrx_dma(struct spi_device *spi, struct spi_transfer *xfer)
u8 * rx;
const u8 * tx;
void __iomem *chstat_reg;
+ struct dma_slave_config cfg;
+ enum dma_slave_buswidth width;
+ unsigned es;
mcspi = spi_master_get_devdata(spi->master);
mcspi_dma = &mcspi->dma_channels[spi->chip_select];
@@ -321,6 +354,71 @@ omap2_mcspi_txrx_dma(struct spi_device *spi, struct spi_transfer *xfer)
chstat_reg = cs->base + OMAP2_MCSPI_CHSTAT0;
+ if (cs->word_len <= 8) {
+ width = DMA_SLAVE_BUSWIDTH_1_BYTE;
+ es = 1;
+ } else if (cs->word_len <= 16) {
+ width = DMA_SLAVE_BUSWIDTH_2_BYTES;
+ es = 2;
+ } else {
+ width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ es = 4;
+ }
+
+ memset(&cfg, 0, sizeof(cfg));
+ cfg.src_addr = cs->phys + OMAP2_MCSPI_RX0;
+ cfg.dst_addr = cs->phys + OMAP2_MCSPI_TX0;
+ cfg.src_addr_width = width;
+ cfg.dst_addr_width = width;
+ cfg.src_maxburst = 1;
+ cfg.dst_maxburst = 1;
+
+ if (xfer->tx_buf && mcspi_dma->dma_tx) {
+ struct dma_async_tx_descriptor *tx;
+ struct scatterlist sg;
+
+ dmaengine_slave_config(mcspi_dma->dma_tx, &cfg);
+
+ sg_init_table(&sg, 1);
+ sg_dma_address(&sg) = xfer->tx_dma;
+ sg_dma_len(&sg) = xfer->len;
+
+ tx = dmaengine_prep_slave_sg(mcspi_dma->dma_tx, &sg, 1,
+ DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+ if (tx) {
+ tx->callback = omap2_mcspi_tx_callback;
+ tx->callback_param = spi;
+ dmaengine_submit(tx);
+ } else {
+ /* FIXME: fall back to PIO? */
+ }
+ }
+
+ if (xfer->rx_buf && mcspi_dma->dma_rx) {
+ struct dma_async_tx_descriptor *tx;
+ struct scatterlist sg;
+ size_t len = xfer->len - es;
+
+ dmaengine_slave_config(mcspi_dma->dma_rx, &cfg);
+
+ if (l & OMAP2_MCSPI_CHCONF_TURBO)
+ len -= es;
+
+ sg_init_table(&sg, 1);
+ sg_dma_address(&sg) = xfer->rx_dma;
+ sg_dma_len(&sg) = len;
+
+ tx = dmaengine_prep_slave_sg(mcspi_dma->dma_rx, &sg, 1,
+ DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+ if (tx) {
+ tx->callback = omap2_mcspi_rx_callback;
+ tx->callback_param = spi;
+ dmaengine_submit(tx);
+ } else {
+ /* FIXME: fall back to PIO? */
+ }
+ }
+
count = xfer->len;
c = count;
word_len = cs->word_len;
@@ -342,7 +440,7 @@ omap2_mcspi_txrx_dma(struct spi_device *spi, struct spi_transfer *xfer)
element_count = count >> 2;
}
- if (tx != NULL) {
+ if (tx != NULL && mcspi_dma->dma_tx_channel != -1) {
omap_set_dma_transfer_params(mcspi_dma->dma_tx_channel,
data_type, element_count, 1,
OMAP_DMA_SYNC_ELEMENT,
@@ -357,7 +455,7 @@ omap2_mcspi_txrx_dma(struct spi_device *spi, struct spi_transfer *xfer)
xfer->tx_dma, 0, 0);
}
- if (rx != NULL) {
+ if (rx != NULL && mcspi_dma->dma_rx_channel != -1) {
elements = element_count - 1;
if (l & OMAP2_MCSPI_CHCONF_TURBO)
elements--;
@@ -377,12 +475,18 @@ omap2_mcspi_txrx_dma(struct spi_device *spi, struct spi_transfer *xfer)
}
if (tx != NULL) {
- omap_start_dma(mcspi_dma->dma_tx_channel);
+ if (mcspi_dma->dma_tx)
+ dma_async_issue_pending(mcspi_dma->dma_tx);
+ else
+ omap_start_dma(mcspi_dma->dma_tx_channel);
omap2_mcspi_set_dma_req(spi, 0, 1);
}
if (rx != NULL) {
- omap_start_dma(mcspi_dma->dma_rx_channel);
+ if (mcspi_dma->dma_rx)
+ dma_async_issue_pending(mcspi_dma->dma_rx);
+ else
+ omap_start_dma(mcspi_dma->dma_rx_channel);
omap2_mcspi_set_dma_req(spi, 1, 1);
}
@@ -406,7 +510,10 @@ omap2_mcspi_txrx_dma(struct spi_device *spi, struct spi_transfer *xfer)
dma_unmap_single(&spi->dev, xfer->rx_dma, count, DMA_FROM_DEVICE);
omap2_mcspi_set_enable(spi, 0);
+ elements = element_count - 1;
+
if (l & OMAP2_MCSPI_CHCONF_TURBO) {
+ elements--;
if (likely(mcspi_read_cs_reg(spi, OMAP2_MCSPI_CHSTAT0)
& OMAP2_MCSPI_CHSTAT_RXS)) {
@@ -725,32 +832,12 @@ static int omap2_mcspi_setup_transfer(struct spi_device *spi,
static void omap2_mcspi_dma_rx_callback(int lch, u16 ch_status, void *data)
{
- struct spi_device *spi = data;
- struct omap2_mcspi *mcspi;
- struct omap2_mcspi_dma *mcspi_dma;
-
- mcspi = spi_master_get_devdata(spi->master);
- mcspi_dma = &(mcspi->dma_channels[spi->chip_select]);
-
- complete(&mcspi_dma->dma_rx_completion);
-
- /* We must disable the DMA RX request */
- omap2_mcspi_set_dma_req(spi, 1, 0);
+ omap2_mcspi_rx_callback(data);
}
static void omap2_mcspi_dma_tx_callback(int lch, u16 ch_status, void *data)
{
- struct spi_device *spi = data;
- struct omap2_mcspi *mcspi;
- struct omap2_mcspi_dma *mcspi_dma;
-
- mcspi = spi_master_get_devdata(spi->master);
- mcspi_dma = &(mcspi->dma_channels[spi->chip_select]);
-
- complete(&mcspi_dma->dma_tx_completion);
-
- /* We must disable the DMA TX request */
- omap2_mcspi_set_dma_req(spi, 0, 0);
+ omap2_mcspi_tx_callback(data);
}
static int omap2_mcspi_request_dma(struct spi_device *spi)
@@ -758,17 +845,43 @@ static int omap2_mcspi_request_dma(struct spi_device *spi)
struct spi_master *master = spi->master;
struct omap2_mcspi *mcspi;
struct omap2_mcspi_dma *mcspi_dma;
+ dma_cap_mask_t mask;
+ unsigned sig;
mcspi = spi_master_get_devdata(master);
mcspi_dma = mcspi->dma_channels + spi->chip_select;
+ init_completion(&mcspi_dma->dma_rx_completion);
+ init_completion(&mcspi_dma->dma_tx_completion);
+
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+#ifdef USE_DMA_ENGINE_RX
+ sig = mcspi_dma->dma_rx_sync_dev;
+ mcspi_dma->dma_rx = dma_request_channel(mask, omap_dma_filter_fn, &sig);
+ if (!mcspi_dma->dma_rx) {
+ dev_err(&spi->dev, "no RX DMA engine channel for McSPI\n");
+ return -EAGAIN;
+ }
+#else
if (omap_request_dma(mcspi_dma->dma_rx_sync_dev, "McSPI RX",
omap2_mcspi_dma_rx_callback, spi,
&mcspi_dma->dma_rx_channel)) {
dev_err(&spi->dev, "no RX DMA channel for McSPI\n");
return -EAGAIN;
}
+#endif
+#ifdef USE_DMA_ENGINE_TX
+ sig = mcspi_dma->dma_tx_sync_dev;
+ mcspi_dma->dma_tx = dma_request_channel(mask, omap_dma_filter_fn, &sig);
+ if (!mcspi_dma->dma_tx) {
+ dev_err(&spi->dev, "no TX DMA engine channel for McSPI\n");
+ dma_release_channel(mcspi_dma->dma_rx);
+ mcspi_dma->dma_rx = NULL;
+ return -EAGAIN;
+ }
+#else
if (omap_request_dma(mcspi_dma->dma_tx_sync_dev, "McSPI TX",
omap2_mcspi_dma_tx_callback, spi,
&mcspi_dma->dma_tx_channel)) {
@@ -777,9 +890,7 @@ static int omap2_mcspi_request_dma(struct spi_device *spi)
dev_err(&spi->dev, "no TX DMA channel for McSPI\n");
return -EAGAIN;
}
-
- init_completion(&mcspi_dma->dma_rx_completion);
- init_completion(&mcspi_dma->dma_tx_completion);
+#endif
return 0;
}
@@ -812,8 +923,8 @@ static int omap2_mcspi_setup(struct spi_device *spi)
list_add_tail(&cs->node, &ctx->cs);
}
- if (mcspi_dma->dma_rx_channel == -1
- || mcspi_dma->dma_tx_channel == -1) {
+ if ((!mcspi_dma->dma_rx && mcspi_dma->dma_rx_channel == -1) ||
+ (!mcspi_dma->dma_tx && mcspi_dma->dma_tx_channel == -1)) {
ret = omap2_mcspi_request_dma(spi);
if (ret < 0)
return ret;
@@ -847,6 +958,14 @@ static void omap2_mcspi_cleanup(struct spi_device *spi)
if (spi->chip_select < spi->master->num_chipselect) {
mcspi_dma = &mcspi->dma_channels[spi->chip_select];
+ if (mcspi_dma->dma_rx) {
+ dma_release_channel(mcspi_dma->dma_rx);
+ mcspi_dma->dma_rx = NULL;
+ }
+ if (mcspi_dma->dma_tx) {
+ dma_release_channel(mcspi_dma->dma_tx);
+ mcspi_dma->dma_tx = NULL;
+ }
if (mcspi_dma->dma_rx_channel != -1) {
omap_free_dma(mcspi_dma->dma_rx_channel);
mcspi_dma->dma_rx_channel = -1;
--
1.7.4.4
More information about the linux-arm-kernel
mailing list