diff options
author | Ned Forrester <nforrester@whoi.edu> | 2008-09-13 05:33:18 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2008-09-13 17:41:52 -0400 |
commit | 7e96445533ac3f4f7964646a202ff3620602fab4 (patch) | |
tree | 8c8dfd18cb0447cabd834e2b68d209c2e870be24 | |
parent | 8423597d676615f3dd2d9ab36f59f147086b90b8 (diff) |
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: Ned Forrester <nforrester@whoi.edu>
Cc: Vernon Sauder <vernoninhand@gmail.com>
Cc: Eric Miao <eric.y.miao@gmail.com>
Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Cc: <stable@kernel.org> [2.6.25.x, 2.6.26.x]
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r-- | drivers/spi/pxa2xx_spi.c | 57 |
1 files changed, 43 insertions, 14 deletions
diff --git a/drivers/spi/pxa2xx_spi.c b/drivers/spi/pxa2xx_spi.c index a7b9070e1b46..0e53354c1cfe 100644 --- a/drivers/spi/pxa2xx_spi.c +++ b/drivers/spi/pxa2xx_spi.c | |||
@@ -47,9 +47,10 @@ MODULE_ALIAS("platform:pxa2xx-spi"); | |||
47 | 47 | ||
48 | #define MAX_BUSES 3 | 48 | #define MAX_BUSES 3 |
49 | 49 | ||
50 | #define DMA_INT_MASK (DCSR_ENDINTR | DCSR_STARTINTR | DCSR_BUSERR) | 50 | #define DMA_INT_MASK (DCSR_ENDINTR | DCSR_STARTINTR | DCSR_BUSERR) |
51 | #define RESET_DMA_CHANNEL (DCSR_NODESC | DMA_INT_MASK) | 51 | #define RESET_DMA_CHANNEL (DCSR_NODESC | DMA_INT_MASK) |
52 | #define IS_DMA_ALIGNED(x) (((u32)(x)&0x07)==0) | 52 | #define IS_DMA_ALIGNED(x) (((x) & 0x07) == 0) |
53 | #define MAX_DMA_LEN 8191 | ||
53 | 54 | ||
54 | /* | 55 | /* |
55 | * for testing SSCR1 changes that require SSP restart, basically | 56 | * for testing SSCR1 changes that require SSP restart, basically |
@@ -887,14 +888,27 @@ static void pump_transfers(unsigned long data) | |||
887 | drv_data->cs_control(PXA2XX_CS_DEASSERT); | 888 | drv_data->cs_control(PXA2XX_CS_DEASSERT); |
888 | } | 889 | } |
889 | 890 | ||
890 | /* Check transfer length */ | 891 | /* Check for transfers that need multiple DMA segments */ |
891 | if (transfer->len > 8191) | 892 | if (transfer->len > MAX_DMA_LEN && chip->enable_dma) { |
892 | { | 893 | |
893 | dev_warn(&drv_data->pdev->dev, "pump_transfers: transfer " | 894 | /* reject already-mapped transfers; PIO won't always work */ |
894 | "length greater than 8191\n"); | 895 | if (message->is_dma_mapped |
895 | message->status = -EINVAL; | 896 | || transfer->rx_dma || transfer->tx_dma) { |
896 | giveback(drv_data); | 897 | dev_err(&drv_data->pdev->dev, |
897 | return; | 898 | "pump_transfers: mapped transfer length " |
899 | "of %lu is greater than %d\n", | ||
900 | transfer->len, MAX_DMA_LEN); | ||
901 | message->status = -EINVAL; | ||
902 | giveback(drv_data); | ||
903 | return; | ||
904 | } | ||
905 | |||
906 | /* warn ... we force this to PIO mode */ | ||
907 | if (printk_ratelimit()) | ||
908 | dev_warn(&message->spi->dev, "pump_transfers: " | ||
909 | "DMA disabled for transfer length %ld " | ||
910 | "greater than %d\n", | ||
911 | (long)drv_data->len, MAX_DMA_LEN); | ||
898 | } | 912 | } |
899 | 913 | ||
900 | /* Setup the transfer state based on the type of transfer */ | 914 | /* Setup the transfer state based on the type of transfer */ |
@@ -962,7 +976,7 @@ static void pump_transfers(unsigned long data) | |||
962 | &dma_thresh)) | 976 | &dma_thresh)) |
963 | if (printk_ratelimit()) | 977 | if (printk_ratelimit()) |
964 | dev_warn(&message->spi->dev, | 978 | dev_warn(&message->spi->dev, |
965 | "pump_transfer: " | 979 | "pump_transfers: " |
966 | "DMA burst size reduced to " | 980 | "DMA burst size reduced to " |
967 | "match bits_per_word\n"); | 981 | "match bits_per_word\n"); |
968 | } | 982 | } |
@@ -976,8 +990,23 @@ static void pump_transfers(unsigned long data) | |||
976 | 990 | ||
977 | message->state = RUNNING_STATE; | 991 | message->state = RUNNING_STATE; |
978 | 992 | ||
979 | /* Try to map dma buffer and do a dma transfer if successful */ | 993 | /* Try to map dma buffer and do a dma transfer if successful, but |
980 | if ((drv_data->dma_mapped = map_dma_buffers(drv_data))) { | 994 | * only if the length is non-zero and less than MAX_DMA_LEN. |
995 | * | ||
996 | * Zero-length non-descriptor DMA is illegal on PXA2xx; force use | ||
997 | * of PIO instead. Care is needed above because the transfer may | ||
998 | * have have been passed with buffers that are already dma mapped. | ||
999 | * A zero-length transfer in PIO mode will not try to write/read | ||
1000 | * to/from the buffers | ||
1001 | * | ||
1002 | * REVISIT large transfers are exactly where we most want to be | ||
1003 | * using DMA. If this happens much, split those transfers into | ||
1004 | * multiple DMA segments rather than forcing PIO. | ||
1005 | */ | ||
1006 | drv_data->dma_mapped = 0; | ||
1007 | if (drv_data->len > 0 && drv_data->len <= MAX_DMA_LEN) | ||
1008 | drv_data->dma_mapped = map_dma_buffers(drv_data); | ||
1009 | if (drv_data->dma_mapped) { | ||
981 | 1010 | ||
982 | /* Ensure we have the correct interrupt handler */ | 1011 | /* Ensure we have the correct interrupt handler */ |
983 | drv_data->transfer_handler = dma_transfer; | 1012 | drv_data->transfer_handler = dma_transfer; |