| From 2a9a4fc2e8860de7266d5b5c6624865b7647c17c Mon Sep 17 00:00:00 2001 |
| From: Ludovic Desroches <ludovic.desroches@atmel.com> |
| Date: Wed, 16 May 2012 15:25:58 +0200 |
| Subject: mmc: atmel-mci: the r/w proof capability lack was not well managed |
| |
| commit 7a90dcc2d7ceb64bb37044a8d2ee462b936ddf73 upstream. |
| |
| First mci IPs (mainly on rm9200 and 9261) don't have the r/w proof |
| capability. The driver didn't work correctly without this capability |
| in PDC mode because of the double buffer switch which is too slow |
| even if we stop the transfer to perform this switch. |
| |
| Signed-off-by: Ludovic Desroches <ludovic.desroches@atmel.com> |
| Signed-off-by: Chris Ball <cjb@laptop.org> |
| --- |
| drivers/mmc/host/atmel-mci.c | 92 ++++++++++++++++++++++++++++++++++++------- |
| 1 file changed, 78 insertions(+), 14 deletions(-) |
| |
| --- a/drivers/mmc/host/atmel-mci.c |
| +++ b/drivers/mmc/host/atmel-mci.c |
| @@ -91,6 +91,11 @@ struct atmel_mci_dma { |
| * @regs: Pointer to MMIO registers. |
| * @sg: Scatterlist entry currently being processed by PIO or PDC code. |
| * @pio_offset: Offset into the current scatterlist entry. |
| + * @buffer: Buffer used if we don't have the r/w proof capability. We |
| + * don't have the time to switch pdc buffers so we have to use only |
| + * one buffer for the full transaction. |
| + * @buf_size: size of the buffer. |
| + * @phys_buf_addr: buffer address needed for pdc. |
| * @cur_slot: The slot which is currently using the controller. |
| * @mrq: The request currently being processed on @cur_slot, |
| * or NULL if the controller is idle. |
| @@ -167,6 +172,9 @@ struct atmel_mci { |
| struct scatterlist *sg; |
| unsigned int sg_len; |
| unsigned int pio_offset; |
| + unsigned int *buffer; |
| + unsigned int buf_size; |
| + dma_addr_t buf_phys_addr; |
| |
| struct atmel_mci_slot *cur_slot; |
| struct mmc_request *mrq; |
| @@ -481,6 +489,11 @@ err: |
| dev_err(&mmc->class_dev, "failed to initialize debugfs for slot\n"); |
| } |
| |
| +static inline unsigned int atmci_get_version(struct atmel_mci *host) |
| +{ |
| + return atmci_readl(host, ATMCI_VERSION) & 0x00000fff; |
| +} |
| + |
| static inline unsigned int atmci_ns_to_clocks(struct atmel_mci *host, |
| unsigned int ns) |
| { |
| @@ -604,6 +617,7 @@ static void atmci_pdc_set_single_buf(str |
| enum atmci_xfer_dir dir, enum atmci_pdc_buf buf_nb) |
| { |
| u32 pointer_reg, counter_reg; |
| + unsigned int buf_size; |
| |
| if (dir == XFER_RECEIVE) { |
| pointer_reg = ATMEL_PDC_RPR; |
| @@ -618,8 +632,15 @@ static void atmci_pdc_set_single_buf(str |
| counter_reg += ATMEL_PDC_SCND_BUF_OFF; |
| } |
| |
| - atmci_writel(host, pointer_reg, sg_dma_address(host->sg)); |
| - if (host->data_size <= sg_dma_len(host->sg)) { |
| + if (!host->caps.has_rwproof) { |
| + buf_size = host->buf_size; |
| + atmci_writel(host, pointer_reg, host->buf_phys_addr); |
| + } else { |
| + buf_size = sg_dma_len(host->sg); |
| + atmci_writel(host, pointer_reg, sg_dma_address(host->sg)); |
| + } |
| + |
| + if (host->data_size <= buf_size) { |
| if (host->data_size & 0x3) { |
| /* If size is different from modulo 4, transfer bytes */ |
| atmci_writel(host, counter_reg, host->data_size); |
| @@ -671,7 +692,15 @@ static void atmci_pdc_cleanup(struct atm |
| */ |
| static void atmci_pdc_complete(struct atmel_mci *host) |
| { |
| + int transfer_size = host->data->blocks * host->data->blksz; |
| + |
| atmci_writel(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTDIS | ATMEL_PDC_TXTDIS); |
| + |
| + if ((!host->caps.has_rwproof) |
| + && (host->data->flags & MMC_DATA_READ)) |
| + sg_copy_from_buffer(host->data->sg, host->data->sg_len, |
| + host->buffer, transfer_size); |
| + |
| atmci_pdc_cleanup(host); |
| |
| /* |
| @@ -820,6 +849,12 @@ atmci_prepare_data_pdc(struct atmel_mci |
| /* Configure PDC */ |
| host->data_size = data->blocks * data->blksz; |
| sg_len = dma_map_sg(&host->pdev->dev, data->sg, data->sg_len, dir); |
| + |
| + if ((!host->caps.has_rwproof) |
| + && (host->data->flags & MMC_DATA_WRITE)) |
| + sg_copy_to_buffer(host->data->sg, host->data->sg_len, |
| + host->buffer, host->data_size); |
| + |
| if (host->data_size) |
| atmci_pdc_set_both_buf(host, |
| ((dir == DMA_FROM_DEVICE) ? XFER_RECEIVE : XFER_TRANSMIT)); |
| @@ -1894,13 +1929,26 @@ static int __init atmci_init_slot(struct |
| mmc->caps |= MMC_CAP_SDIO_IRQ; |
| if (host->caps.has_highspeed) |
| mmc->caps |= MMC_CAP_SD_HIGHSPEED; |
| - if (slot_data->bus_width >= 4) |
| + /* |
| + * Without the read/write proof capability, it is strongly suggested to |
| + * use only one bit for data to prevent fifo underruns and overruns |
| + * which will corrupt data. |
| + */ |
| + if ((slot_data->bus_width >= 4) && host->caps.has_rwproof) |
| mmc->caps |= MMC_CAP_4_BIT_DATA; |
| |
| - mmc->max_segs = 64; |
| - mmc->max_req_size = 32768 * 512; |
| - mmc->max_blk_size = 32768; |
| - mmc->max_blk_count = 512; |
| + if (atmci_get_version(host) < 0x200) { |
| + mmc->max_segs = 256; |
| + mmc->max_blk_size = 4095; |
| + mmc->max_blk_count = 256; |
| + mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count; |
| + mmc->max_seg_size = mmc->max_blk_size * mmc->max_segs; |
| + } else { |
| + mmc->max_segs = 64; |
| + mmc->max_req_size = 32768 * 512; |
| + mmc->max_blk_size = 32768; |
| + mmc->max_blk_count = 512; |
| + } |
| |
| /* Assume card is present initially */ |
| set_bit(ATMCI_CARD_PRESENT, &slot->flags); |
| @@ -2024,11 +2072,6 @@ static bool atmci_configure_dma(struct a |
| } |
| } |
| |
| -static inline unsigned int atmci_get_version(struct atmel_mci *host) |
| -{ |
| - return atmci_readl(host, ATMCI_VERSION) & 0x00000fff; |
| -} |
| - |
| /* |
| * HSMCI (High Speed MCI) module is not fully compatible with MCI module. |
| * HSMCI provides DMA support and a new config register but no more supports |
| @@ -2155,14 +2198,20 @@ static int __init atmci_probe(struct pla |
| if (pdata->slot[0].bus_width) { |
| ret = atmci_init_slot(host, &pdata->slot[0], |
| 0, ATMCI_SDCSEL_SLOT_A, ATMCI_SDIOIRQA); |
| - if (!ret) |
| + if (!ret) { |
| nr_slots++; |
| + host->buf_size = host->slot[0]->mmc->max_req_size; |
| + } |
| } |
| if (pdata->slot[1].bus_width) { |
| ret = atmci_init_slot(host, &pdata->slot[1], |
| 1, ATMCI_SDCSEL_SLOT_B, ATMCI_SDIOIRQB); |
| - if (!ret) |
| + if (!ret) { |
| nr_slots++; |
| + if (host->slot[1]->mmc->max_req_size > host->buf_size) |
| + host->buf_size = |
| + host->slot[1]->mmc->max_req_size; |
| + } |
| } |
| |
| if (!nr_slots) { |
| @@ -2170,6 +2219,17 @@ static int __init atmci_probe(struct pla |
| goto err_init_slot; |
| } |
| |
| + if (!host->caps.has_rwproof) { |
| + host->buffer = dma_alloc_coherent(&pdev->dev, host->buf_size, |
| + &host->buf_phys_addr, |
| + GFP_KERNEL); |
| + if (!host->buffer) { |
| + ret = -ENOMEM; |
| + dev_err(&pdev->dev, "buffer allocation failed\n"); |
| + goto err_init_slot; |
| + } |
| + } |
| + |
| dev_info(&pdev->dev, |
| "Atmel MCI controller at 0x%08lx irq %d, %u slots\n", |
| host->mapbase, irq, nr_slots); |
| @@ -2196,6 +2256,10 @@ static int __exit atmci_remove(struct pl |
| |
| platform_set_drvdata(pdev, NULL); |
| |
| + if (host->buffer) |
| + dma_free_coherent(&pdev->dev, host->buf_size, |
| + host->buffer, host->buf_phys_addr); |
| + |
| for (i = 0; i < ATMCI_MAX_NR_SLOTS; i++) { |
| if (host->slot[i]) |
| atmci_cleanup_slot(host->slot[i], i); |