blob: 7732083c712d326eeebc1484bae76bc31c270301 [file] [log] [blame]
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);