Commit 7e964455 authored by Ned Forrester's avatar Ned Forrester Committed by Linus Torvalds

pxa2xx_spi: dma bugfixes

Fixes two DMA bugs in the pxa2xx_spi driver.  The first bug is in all
versions of this driver; the second was introduced in the 2.6.20 kernel,
and prevents using the driver with chips like m25p16 flash (which can
issue large DMA reads).

 1. Zero length transfers are permitted for use to insert timing,
    but pxa2xx_spi.c will fail if this is requested in DMA mode.
    Fixed by using programmed I/O (PIO) mode for such transfers.

 2. Transfers larger than 8191 are not permitted in DMA mode.  A
    test for length rejects all large transfers regardless of DMA
    or PIO mode.  Worked around by rejecting only large transfers
    with DMA mapped buffers, and forcing all other transfers
    larger than 8191 to use PIO mode.  A rate limited warning is
    issued for DMA transfers forced to PIO mode.

This patch should apply to all kernels back to and including 2.6.20;
it was test patched against 2.6.20.  An additional patch would be
required for older kernels, but those versions are very buggy anyway.
Signed-off-by: default avatarNed Forrester <nforrester@whoi.edu>
Cc: Vernon Sauder <vernoninhand@gmail.com>
Cc: Eric Miao <eric.y.miao@gmail.com>
Signed-off-by: default avatarDavid Brownell <dbrownell@users.sourceforge.net>
Cc: <stable@kernel.org>		[2.6.25.x, 2.6.26.x]
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 8423597d
......@@ -49,7 +49,8 @@ MODULE_ALIAS("platform:pxa2xx-spi");
#define DMA_INT_MASK (DCSR_ENDINTR | DCSR_STARTINTR | DCSR_BUSERR)
#define RESET_DMA_CHANNEL (DCSR_NODESC | DMA_INT_MASK)
#define IS_DMA_ALIGNED(x) (((u32)(x)&0x07)==0)
#define IS_DMA_ALIGNED(x) (((x) & 0x07) == 0)
#define MAX_DMA_LEN 8191
/*
* for testing SSCR1 changes that require SSP restart, basically
......@@ -887,16 +888,29 @@ static void pump_transfers(unsigned long data)
drv_data->cs_control(PXA2XX_CS_DEASSERT);
}
/* Check transfer length */
if (transfer->len > 8191)
{
dev_warn(&drv_data->pdev->dev, "pump_transfers: transfer "
"length greater than 8191\n");
/* Check for transfers that need multiple DMA segments */
if (transfer->len > MAX_DMA_LEN && chip->enable_dma) {
/* reject already-mapped transfers; PIO won't always work */
if (message->is_dma_mapped
|| transfer->rx_dma || transfer->tx_dma) {
dev_err(&drv_data->pdev->dev,
"pump_transfers: mapped transfer length "
"of %lu is greater than %d\n",
transfer->len, MAX_DMA_LEN);
message->status = -EINVAL;
giveback(drv_data);
return;
}
/* warn ... we force this to PIO mode */
if (printk_ratelimit())
dev_warn(&message->spi->dev, "pump_transfers: "
"DMA disabled for transfer length %ld "
"greater than %d\n",
(long)drv_data->len, MAX_DMA_LEN);
}
/* Setup the transfer state based on the type of transfer */
if (flush(drv_data) == 0) {
dev_err(&drv_data->pdev->dev, "pump_transfers: flush failed\n");
......@@ -962,7 +976,7 @@ static void pump_transfers(unsigned long data)
&dma_thresh))
if (printk_ratelimit())
dev_warn(&message->spi->dev,
"pump_transfer: "
"pump_transfers: "
"DMA burst size reduced to "
"match bits_per_word\n");
}
......@@ -976,8 +990,23 @@ static void pump_transfers(unsigned long data)
message->state = RUNNING_STATE;
/* Try to map dma buffer and do a dma transfer if successful */
if ((drv_data->dma_mapped = map_dma_buffers(drv_data))) {
/* Try to map dma buffer and do a dma transfer if successful, but
* only if the length is non-zero and less than MAX_DMA_LEN.
*
* Zero-length non-descriptor DMA is illegal on PXA2xx; force use
* of PIO instead. Care is needed above because the transfer may
* have have been passed with buffers that are already dma mapped.
* A zero-length transfer in PIO mode will not try to write/read
* to/from the buffers
*
* REVISIT large transfers are exactly where we most want to be
* using DMA. If this happens much, split those transfers into
* multiple DMA segments rather than forcing PIO.
*/
drv_data->dma_mapped = 0;
if (drv_data->len > 0 && drv_data->len <= MAX_DMA_LEN)
drv_data->dma_mapped = map_dma_buffers(drv_data);
if (drv_data->dma_mapped) {
/* Ensure we have the correct interrupt handler */
drv_data->transfer_handler = dma_transfer;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment