123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- From 969872e334937bd0956887a925f20f7a446af5f6 Mon Sep 17 00:00:00 2001
- From: Alexey Brodkin <Alexey.Brodkin@synopsys.com>
- Date: Thu, 25 Jun 2015 11:25:07 +0300
- Subject: [PATCH] mmc: dw_mmc: handle data blocks > than 4kB if IDMAC is used
- As per DW MobileStorage databook "each descriptor can transfer up to 4kB
- of data in chained mode", moreover buffer size that is put in "des1" is
- limited to 13 bits, i.e. for example on attempt to
- IDMAC_SET_BUFFER1_SIZE(desc, 8192) size value that's effectively written
- will be 0.
- On the platform with 8kB PAGE_SIZE I see dw_mmc gets data blocks in
- SG-list of 8kB size and that leads to unpredictable behavior of the
- SD/MMC controller.
- In particular on write to FAT partition of SD-card the controller will
- stuck in the middle of DMA transaction.
- Solution to the problem is simple - we need to pass large (> 4kB) data
- buffers to the controller via multiple descriptors. And that's what
- that change does.
- What's interesting I did try original driver on same platform but
- configured with 4kB PAGE_SIZE and may confirm that data blocks passed
- in SG-list to dw_mmc never exeed 4kB limit - that explains why nobody
- ever faced a problem I did.
- Signed-off-by: Alexey Brodkin <abrodkin@synopsys.com>
- Cc: Seungwon Jeon <tgih.jun@samsung.com>
- Cc: Jaehoon Chung <jh80.chung@samsung.com>
- Cc: Ulf Hansson <ulf.hansson@linaro.org>
- Cc: arc-linux-dev@synopsys.com
- Cc: linux-kernel@vger.kernel.org
- Signed-off-by: Jaehoon Chung <jh80.chung@samsung.com>
- ---
- drivers/mmc/host/dw_mmc.c | 109 ++++++++++++++++++++++++++++++----------------
- 1 file changed, 71 insertions(+), 38 deletions(-)
- diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c
- index 40e9d8e..e41fb74 100644
- --- a/drivers/mmc/host/dw_mmc.c
- +++ b/drivers/mmc/host/dw_mmc.c
- @@ -99,6 +99,9 @@ struct idmac_desc {
-
- __le32 des3; /* buffer 2 physical address */
- };
- +
- +/* Each descriptor can transfer up to 4KB of data in chained mode */
- +#define DW_MCI_DESC_DATA_LENGTH 0x1000
- #endif /* CONFIG_MMC_DW_IDMAC */
-
- static bool dw_mci_reset(struct dw_mci *host);
- @@ -462,66 +465,96 @@ static void dw_mci_idmac_complete_dma(struct dw_mci *host)
- static void dw_mci_translate_sglist(struct dw_mci *host, struct mmc_data *data,
- unsigned int sg_len)
- {
- + unsigned int desc_len;
- int i;
- if (host->dma_64bit_address == 1) {
- - struct idmac_desc_64addr *desc = host->sg_cpu;
- + struct idmac_desc_64addr *desc_first, *desc_last, *desc;
- +
- + desc_first = desc_last = desc = host->sg_cpu;
-
- - for (i = 0; i < sg_len; i++, desc++) {
- + for (i = 0; i < sg_len; i++) {
- unsigned int length = sg_dma_len(&data->sg[i]);
- u64 mem_addr = sg_dma_address(&data->sg[i]);
-
- - /*
- - * Set the OWN bit and disable interrupts for this
- - * descriptor
- - */
- - desc->des0 = IDMAC_DES0_OWN | IDMAC_DES0_DIC |
- - IDMAC_DES0_CH;
- - /* Buffer length */
- - IDMAC_64ADDR_SET_BUFFER1_SIZE(desc, length);
- -
- - /* Physical address to DMA to/from */
- - desc->des4 = mem_addr & 0xffffffff;
- - desc->des5 = mem_addr >> 32;
- + for ( ; length ; desc++) {
- + desc_len = (length <= DW_MCI_DESC_DATA_LENGTH) ?
- + length : DW_MCI_DESC_DATA_LENGTH;
- +
- + length -= desc_len;
- +
- + /*
- + * Set the OWN bit and disable interrupts
- + * for this descriptor
- + */
- + desc->des0 = IDMAC_DES0_OWN | IDMAC_DES0_DIC |
- + IDMAC_DES0_CH;
- +
- + /* Buffer length */
- + IDMAC_64ADDR_SET_BUFFER1_SIZE(desc, desc_len);
- +
- + /* Physical address to DMA to/from */
- + desc->des4 = mem_addr & 0xffffffff;
- + desc->des5 = mem_addr >> 32;
- +
- + /* Update physical address for the next desc */
- + mem_addr += desc_len;
- +
- + /* Save pointer to the last descriptor */
- + desc_last = desc;
- + }
- }
-
- /* Set first descriptor */
- - desc = host->sg_cpu;
- - desc->des0 |= IDMAC_DES0_FD;
- + desc_first->des0 |= IDMAC_DES0_FD;
-
- /* Set last descriptor */
- - desc = host->sg_cpu + (i - 1) *
- - sizeof(struct idmac_desc_64addr);
- - desc->des0 &= ~(IDMAC_DES0_CH | IDMAC_DES0_DIC);
- - desc->des0 |= IDMAC_DES0_LD;
- + desc_last->des0 &= ~(IDMAC_DES0_CH | IDMAC_DES0_DIC);
- + desc_last->des0 |= IDMAC_DES0_LD;
-
- } else {
- - struct idmac_desc *desc = host->sg_cpu;
- + struct idmac_desc *desc_first, *desc_last, *desc;
- +
- + desc_first = desc_last = desc = host->sg_cpu;
-
- - for (i = 0; i < sg_len; i++, desc++) {
- + for (i = 0; i < sg_len; i++) {
- unsigned int length = sg_dma_len(&data->sg[i]);
- u32 mem_addr = sg_dma_address(&data->sg[i]);
-
- - /*
- - * Set the OWN bit and disable interrupts for this
- - * descriptor
- - */
- - desc->des0 = cpu_to_le32(IDMAC_DES0_OWN |
- - IDMAC_DES0_DIC | IDMAC_DES0_CH);
- - /* Buffer length */
- - IDMAC_SET_BUFFER1_SIZE(desc, length);
- + for ( ; length ; desc++) {
- + desc_len = (length <= DW_MCI_DESC_DATA_LENGTH) ?
- + length : DW_MCI_DESC_DATA_LENGTH;
- +
- + length -= desc_len;
- +
- + /*
- + * Set the OWN bit and disable interrupts
- + * for this descriptor
- + */
- + desc->des0 = cpu_to_le32(IDMAC_DES0_OWN |
- + IDMAC_DES0_DIC |
- + IDMAC_DES0_CH);
- +
- + /* Buffer length */
- + IDMAC_SET_BUFFER1_SIZE(desc, desc_len);
-
- - /* Physical address to DMA to/from */
- - desc->des2 = cpu_to_le32(mem_addr);
- + /* Physical address to DMA to/from */
- + desc->des2 = cpu_to_le32(mem_addr);
- +
- + /* Update physical address for the next desc */
- + mem_addr += desc_len;
- +
- + /* Save pointer to the last descriptor */
- + desc_last = desc;
- + }
- }
-
- /* Set first descriptor */
- - desc = host->sg_cpu;
- - desc->des0 |= cpu_to_le32(IDMAC_DES0_FD);
- + desc_first->des0 |= cpu_to_le32(IDMAC_DES0_FD);
-
- /* Set last descriptor */
- - desc = host->sg_cpu + (i - 1) * sizeof(struct idmac_desc);
- - desc->des0 &= cpu_to_le32(~(IDMAC_DES0_CH | IDMAC_DES0_DIC));
- - desc->des0 |= cpu_to_le32(IDMAC_DES0_LD);
- + desc_last->des0 &= cpu_to_le32(~(IDMAC_DES0_CH |
- + IDMAC_DES0_DIC));
- + desc_last->des0 |= cpu_to_le32(IDMAC_DES0_LD);
- }
-
- wmb();
- @@ -2394,7 +2427,7 @@ static int dw_mci_init_slot(struct dw_mci *host, unsigned int id)
- #ifdef CONFIG_MMC_DW_IDMAC
- mmc->max_segs = host->ring_size;
- mmc->max_blk_size = 65536;
- - mmc->max_seg_size = 0x1000;
- + mmc->max_seg_size = DW_MCI_DESC_DATA_LENGTH;
- mmc->max_req_size = mmc->max_seg_size * host->ring_size;
- mmc->max_blk_count = mmc->max_req_size / 512;
- #else
- --
- 2.4.3
|