mmc: at91_mci: fix hanging and rework to match flowcharts
Subsystems (2)
PATCH 1/1 mmc: at91_mci: fix hanging and rework to match flowcharts
Fixes hanging using multi block operations (seen during CMD25). Follows closely the datasheet flowcharts.
This piece of code handles better big file writing. I had to take care of the notbusy signal during write (at91_mci_handle_cmdrdy function) and to rearrange the AT91_MCI_ENDRX and AT91_MCI_RXBUFF flag usage.
---
This patch is applied on top of the "typo" one :
http://kernel.org/pub/linux/kernel/people/akpm/patches/2.6/2.6.22-rc6/2.6.22-rc6-mm1/broken-out/mmc-at91_mci-typo.patch
It is cooked with the help of Wu Xuan and Marc Pignat. Big thanks to them.
It has been tested on several systems and reported to work ok :
at91rm9200 ok w/ 1 wire
at91sam9261 ok w/ 1 wire
at91sam926[03] ok w/ 4 wires
The code takes care of those specificities.
drivers/mmc/host/at91_mci.c | 213 ++++++++++++++++++----------------
1 file changed, 118 insertions(+), 95 deletions(-)
drivers/mmc/host/at91_mci.c +0 -0
@@ -80,8 +78,6 @@
#define DRIVER_NAME "at91_mci"
-#undef SUPPORT_4WIRE
-
#define FL_SENT_COMMAND (1 << 0)
#define FL_SENT_STOP (1 << 1)
@@ -250,28 +246,27 @@ static void at91_mci_pre_dma_read(struct /*
* Handle after a dma read
*/
-static void at91_mci_post_dma_read(struct at91mci_host *host) +static int at91_mci_post_dma_read(struct at91mci_host *host)
{
struct mmc_command *cmd;
struct mmc_data *data;+ int completed = 0;
pr_debug("post dma read\n");
cmd = host->cmd;
if (!cmd) {
pr_debug("no command\n");
- return;
+ return 1;
}
data = cmd->data;
if (!data) {
pr_debug("no data\n");
- return;
+ return 1;
}
while (host->in_use_index < host->transfer_index) {
- unsigned int *buffer;
-
struct scatterlist *sg;
pr_debug("finishing index %d\n", host->in_use_index);@@ -282,20 +277,22 @@ static void at91_mci_post_dma_read(struc
dma_unmap_page(NULL, sg->dma_address, sg->length, DMA_FROM_DEVICE);
- /* Swap the contents of the buffer */
- buffer = kmap_atomic(sg->page, KM_BIO_SRC_IRQ) + sg->offset;
- pr_debug("buffer = %p, length = %d\n", buffer, sg->length);
-
data->bytes_xfered += sg->length;
if (cpu_is_at91rm9200()) { /* AT91RM9200 errata */
+ unsigned int *buffer;
int index;+ /* Swap the contents of the buffer */
+ buffer = kmap_atomic(sg->page, KM_BIO_SRC_IRQ) + sg->offset;
+ pr_debug("buffer = %p, length = %d\n", buffer, sg->length);
+
for (index = 0; index < (sg->length / 4); index++) buffer[index] = swab32(buffer[index]); + + kunmap_atomic(buffer, KM_BIO_SRC_IRQ); }
- kunmap_atomic(buffer, KM_BIO_SRC_IRQ); flush_dcache_page(sg->page); }
@@ -303,11 +300,12 @@ static void at91_mci_post_dma_read(struc
if (host->transfer_index < data->sg_len)
at91_mci_pre_dma_read(host);
else {
+ at91_mci_write(host, AT91_MCI_IDR, AT91_MCI_ENDRX);
at91_mci_write(host, AT91_MCI_IER, AT91_MCI_RXBUFF);
- at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTDIS | ATMEL_PDC_TXTDIS);
}
pr_debug("post dma read done\n");+ return completed;
}
/*
@@ -325,7 +323,6 @@ static void at91_mci_handle_transmitted(
/* Now wait for cmd ready */ at91_mci_write(host, AT91_MCI_IDR, AT91_MCI_TXBUFE);
- at91_mci_write(host, AT91_MCI_IER, AT91_MCI_NOTBUSY);
cmd = host->cmd; if (!cmd) return; @@ -333,18 +330,53 @@ static void at91_mci_handle_transmitted( data = cmd->data; if (!data) return;
+ if (cmd->data->flags & MMC_DATA_MULTI) {
+ pr_debug("multiple write : wait for BLKE...\n");
+ at91_mci_write(host, AT91_MCI_IER, AT91_MCI_BLKE);
+ } else
+ at91_mci_write(host, AT91_MCI_IER, AT91_MCI_NOTBUSY);
+
data->bytes_xfered = host->total_length; }
+/*Handle after command sent ready*/
+static int at91_mci_handle_cmdrdy(struct at91mci_host *host) +{
+ if (!host->cmd)
+ return 1;
+ else if (!host->cmd->data) {
+ if (host->flags & FL_SENT_STOP) {
+ /*After multi block write, we must wait for NOTBUSY*/
+ at91_mci_write(host, AT91_MCI_IER, AT91_MCI_NOTBUSY);
+ } else return 1;
+ } else if (host->cmd->data->flags & MMC_DATA_WRITE) {
+ /*After sendding multi-block-write command, start DMA transfer*/
+ at91_mci_write(host, AT91_MCI_IER, AT91_MCI_TXBUFE);
+ at91_mci_write(host, AT91_MCI_IER, AT91_MCI_BLKE);
+ at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_TXTEN);
+ }
+
+ /* command not completed, have to wait */
+ return 0;
+}
+
+
/*
* Enable the controller
*/
static void at91_mci_enable(struct at91mci_host *host) {
+ unsigned int mr;
+
at91_mci_write(host, AT91_MCI_CR, AT91_MCI_MCIEN); at91_mci_write(host, AT91_MCI_IDR, 0xffffffff); at91_mci_write(host, AT91_MCI_DTOR, AT91_MCI_DTOMUL_1M | AT91_MCI_DTOCYC);
- at91_mci_write(host, AT91_MCI_MR, AT91_MCI_PDCMODE | 0x34a);
+ mr = AT91_MCI_PDCMODE | 0x34a;
+
+ if (cpu_is_at91sam9260() || cpu_is_at91sam9263())
+ mr |= AT91_MCI_RDPROOF | AT91_MCI_WRPROOF;
+
+ at91_mci_write(host, AT91_MCI_MR, mr);
/* use Slot A or B (only one at same time) */ at91_mci_write(host, AT91_MCI_SDCR, host->board->slot_b);
@@ -360,9 +392,8 @@ static void at91_mci_disable(struct at91
/*
* Send a command
- * return the interrupts to enable
*/
-static unsigned int at91_mci_send_command(struct at91mci_host *host, struct mmc_command *cmd) +static void at91_mci_send_command(struct at91mci_host *host, struct mmc_command *cmd)
{
unsigned int cmdr, mr;
unsigned int block_length;@@ -373,8 +404,7 @@ static unsigned int at91_mci_send_comman
host->cmd = cmd;
- /* Not sure if this is needed */
-#if 0
+ /* Needed for leaving busy state before CMD1 */
if ((at91_mci_read(host, AT91_MCI_SR) & AT91_MCI_RTOE) && (cmd->opcode == 1)) {
pr_debug("Clearing timeout\n");
at91_mci_write(host, AT91_MCI_ARGR, 0);
@@ -384,7 +414,7 @@ static unsigned int at91_mci_send_comman
pr_debug("Clearing: SR = %08X\n", at91_mci_read(host, AT91_MCI_SR));
}
}
-#endif
+
cmdr = cmd->opcode;
if (mmc_resp_type(cmd) == MMC_RSP_NONE)
@@ -441,50 +471,48 @@ static unsigned int at91_mci_send_comman
at91_mci_write(host, ATMEL_PDC_TCR, 0);
at91_mci_write(host, ATMEL_PDC_TNPR, 0);
at91_mci_write(host, ATMEL_PDC_TNCR, 0);+ ier = AT91_MCI_CMDRDY;
+ } else {
+ /* zero block length and PDC mode */
+ mr = at91_mci_read(host, AT91_MCI_MR) & 0x7fff;
+ at91_mci_write(host, AT91_MCI_MR, mr | (block_length << 16) | AT91_MCI_PDCMODE);
+
+ /*
+ * Disable the PDC controller
+ */
+ at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTDIS | ATMEL_PDC_TXTDIS);
- at91_mci_write(host, AT91_MCI_ARGR, cmd->arg);
- at91_mci_write(host, AT91_MCI_CMDR, cmdr);
- return AT91_MCI_CMDRDY;
- }
-
- mr = at91_mci_read(host, AT91_MCI_MR) & 0x7fff; /* zero block length and PDC mode */
- at91_mci_write(host, AT91_MCI_MR, mr | (block_length << 16) | AT91_MCI_PDCMODE);
-
- /*
- * Disable the PDC controller
- */
- at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTDIS | ATMEL_PDC_TXTDIS);
-
- if (cmdr & AT91_MCI_TRCMD_START) {
- data->bytes_xfered = 0;
- host->transfer_index = 0;
- host->in_use_index = 0;
- if (cmdr & AT91_MCI_TRDIR) {
- /*
- * Handle a read
- */
- host->buffer = NULL;
- host->total_length = 0;
+ if (cmdr & AT91_MCI_TRCMD_START) {
+ data->bytes_xfered = 0;
+ host->transfer_index = 0;
+ host->in_use_index = 0;
+ if (cmdr & AT91_MCI_TRDIR) {
+ /*
+ * Handle a read
+ */
+ host->buffer = NULL;
+ host->total_length = 0;
- at91_mci_pre_dma_read(host);
- ier = AT91_MCI_ENDRX /* | AT91_MCI_RXBUFF */;
- }
- else {
- /*
- * Handle a write
- */
- host->total_length = block_length * blocks;
- host->buffer = dma_alloc_coherent(NULL,
- host->total_length,
- &host->physical_address, GFP_KERNEL);
-
- at91_mci_sg_to_dma(host, data);
-
- pr_debug("Transmitting %d bytes\n", host->total_length);
-
- at91_mci_write(host, ATMEL_PDC_TPR, host->physical_address);
- at91_mci_write(host, ATMEL_PDC_TCR, host->total_length / 4);
- ier = AT91_MCI_TXBUFE;
+ at91_mci_pre_dma_read(host);
+ ier = AT91_MCI_ENDRX /* | AT91_MCI_RXBUFF */;
+ }
+ else {
+ /*
+ * Handle a write
+ */
+ host->total_length = block_length * blocks;
+ host->buffer = dma_alloc_coherent(NULL,
+ host->total_length,
+ &host->physical_address, GFP_KERNEL);
+
+ at91_mci_sg_to_dma(host, data);
+
+ pr_debug("Transmitting %d bytes\n", host->total_length);
+
+ at91_mci_write(host, ATMEL_PDC_TPR, host->physical_address);
+ at91_mci_write(host, ATMEL_PDC_TCR, host->total_length / 4);
+ ier = AT91_MCI_CMDRDY;
+ }
} }
@@ -499,24 +527,9 @@ static unsigned int at91_mci_send_comman
if (cmdr & AT91_MCI_TRCMD_START) {
if (cmdr & AT91_MCI_TRDIR)
at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTEN);
- else
- at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_TXTEN);
}- return ier;
-}
-/*
- * Wait for a command to complete
- */
-static void at91_mci_process_command(struct at91mci_host *host, struct mmc_command *cmd) -{
- unsigned int ier;
-
- ier = at91_mci_send_command(host, cmd);
-
- pr_debug("setting ier to %08X\n", ier);
-
- /* Stop on errors or the required value */
+ /* Enable selected interrupts */
at91_mci_write(host, AT91_MCI_IER, AT91_MCI_ERRORS | ier); }
@@ -527,11 +540,11 @@ static void at91_mci_process_next(struct
{
if (!(host->flags & FL_SENT_COMMAND)) {
host->flags |= FL_SENT_COMMAND;
- at91_mci_process_command(host, host->request->cmd);
+ at91_mci_send_command(host, host->request->cmd);
}
else if ((!(host->flags & FL_SENT_STOP)) && host->request->stop) {
host->flags |= FL_SENT_STOP;
- at91_mci_process_command(host, host->request->stop);
+ at91_mci_send_command(host, host->request->stop);
}
else
mmc_request_done(host->mmc, host->request);
@@ -698,29 +711,33 @@ static irqreturn_t at91_mci_irq(int irq,
at91_mci_handle_transmitted(host);
}+ if (int_status & AT91_MCI_ENDRX) {
+ pr_debug("ENDRX\n");
+ at91_mci_post_dma_read(host);
+ }
+
if (int_status & AT91_MCI_RXBUFF) {
pr_debug("RX buffer full\n");
- at91_mci_write(host, AT91_MCI_IER, AT91_MCI_CMDRDY);
+ at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTDIS | ATMEL_PDC_TXTDIS);
+ at91_mci_write(host, AT91_MCI_IDR, AT91_MCI_RXBUFF | AT91_MCI_ENDRX);
+ completed = 1;
}
if (int_status & AT91_MCI_ENDTX)
pr_debug("Transmit has ended\n");- if (int_status & AT91_MCI_ENDRX) {
- pr_debug("Receive has ended\n");
- at91_mci_post_dma_read(host);
- }
-
if (int_status & AT91_MCI_NOTBUSY) {
pr_debug("Card is ready\n");
- at91_mci_write(host, AT91_MCI_IER, AT91_MCI_CMDRDY);
+ completed = 1;
}
if (int_status & AT91_MCI_DTIP)
pr_debug("Data transfer in progress\n");- if (int_status & AT91_MCI_BLKE)
+ if (int_status & AT91_MCI_BLKE) {
pr_debug("Block transfer has ended\n");+ completed = 1;
+ }
if (int_status & AT91_MCI_TXRDY)
pr_debug("Ready to transmit\n");@@ -730,7 +747,7 @@ static irqreturn_t at91_mci_irq(int irq,
if (int_status & AT91_MCI_CMDRDY) {
pr_debug("Command ready\n");
- completed = 1;
+ completed = at91_mci_handle_cmdrdy(host);
}
}@@ -817,7 +834,11 @@ static int __init at91_mci_probe(struct
mmc->ops = &at91_mci_ops; mmc->f_min = 375000;
- mmc->f_max = 25000000;
+ if (cpu_is_at91sam9263())
+ mmc->f_max = 50000000;
+ else
+ mmc->f_max = 25000000;
+
mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; mmc->caps = MMC_CAP_BYTEBLOCK | MMC_CAP_MULTIWRITE;
@@ -830,11 +851,11 @@ static int __init at91_mci_probe(struct
host->bus_mode = 0;
host->board = pdev->dev.platform_data;
if (host->board->wire4) {-#ifdef SUPPORT_4WIRE
- mmc->caps |= MMC_CAP_4_BIT_DATA;
-#else
- printk("AT91 MMC: 4 wire bus mode not supported by this driver - using 1 wire\n");
-#endif
+ if (cpu_is_at91sam9260() || cpu_is_at91sam9263())
+ mmc->caps |= MMC_CAP_4_BIT_DATA;
+ else
+ printk("AT91 MMC: 4 wire bus mode not supported"
+ " - using 1 wire\n"); } /*
Quoted 2 lines · Nicolas Ferre
Fixes hanging using multi block operations (seen during CMD25).
Signed-off-by Nicolas Ferre <[email protected]>
Verified working on at91rm9200 using 1 wire mode.
A small remark, only for future improvements:
Review static int __init at91_mci_probe(struct · line 840
Quoted 9 lines
(quoted diff) +4
-1@@ -817,7 +834,11 @@ static int __init at91_mci_probe(struct mmc->ops = &at91_mci_ops; mmc->f_min = 375000; - mmc->f_max = 25000000; + if (cpu_is_at91sam9263()) + mmc->f_max = 50000000; + else + mmc->f_max = 25000000;
for at91rm9200 f_max is MCK/2 why don't use it?
Nice work, thanks
Regards
Marc
Quoted 9 lines · Nicolas Ferre
Fixes hanging using multi block operations (seen during CMD25). Follows closely the datasheet flowcharts.
This piece of code handles better big file writing. I had to take care of the notbusy signal during write (
at91_mci_handle_cmdrdyfunction) and to rearrange theAT91_MCI_ENDRXandAT91_MCI_RXBUFFflag usage.Signed-off-by Nicolas Ferre <[email protected]>---
Most of the patch looks ok. Do you want to wait for some more tests or should I chuck this into the imminent merge window?
Review static int __init at91_mci_probe(struct · line 843
Quoted 12 lines
(quoted diff) +5
-1@@ -817,7 +834,11 @@ static int __init at91_mci_probe(struct mmc->ops = &at91_mci_ops; mmc->f_min = 375000; - mmc->f_max = 25000000; + if (cpu_is_at91sam9263()) + mmc->f_max = 50000000; + else + mmc->f_max = 25000000; + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; mmc->caps = MMC_CAP_BYTEBLOCK | MMC_CAP_MULTIWRITE;
This seems unrelated to the rest of the patch. Also, high-speed won't be enabled unless you set the appropriate caps (which should be checked against timing specifications, not just assigned and hope for the best).
Quoted 9 lines
(quoted diff) +0
-4@@ -830,11 +851,11 @@ static int __init at91_mci_probe(struct host->bus_mode = 0; host->board = pdev->dev.platform_data; if (host->board->wire4) { -#ifdef SUPPORT_4WIRE - mmc->caps |= MMC_CAP_4_BIT_DATA; -#else - printk("AT91 MMC: 4 wire bus mode not supported by this driver
Review static int __init at91_mci_probe(struct
Quoted 10 lines
(quoted diff) +5
-2- using 1 wire\n"); -#endif + if (cpu_is_at91sam9260() || cpu_is_at91sam9263()) + mmc->caps |= MMC_CAP_4_BIT_DATA; + else + printk("AT91 MMC: 4 wire bus mode not supported" + " - using 1 wire\n"); } /*
This also looks unrelated.
Rgds
Signature · 5 lines
-- Pierre Ossman Linux kernel, MMC maintainer http://www.kernel.org PulseAudio, core developer http://pulseaudio.org rdesktop, core developer http://www.rdesktop.org
Quoted 9 lines · On Mon, 02 Jul 2007 15:32:00 +0200 Pierre Ossman <[email protected]> wrote:
Quoted 8 lines · Nicolas Ferre
Fixes hanging using multi block operations (seen during CMD25). Follows closely the datasheet flowcharts.
This piece of code handles better big file writing. I had to take care of the notbusy signal during write (
at91_mci_handle_cmdrdyfunction) and to rearrange theAT91_MCI_ENDRXandAT91_MCI_RXBUFFflag usage.
Quoted 6 lines
Quoted 2 lines
Signed-off-by Nicolas Ferre <[email protected]>---
Most of the patch looks ok. Do you want to wait for some more tests or should I chuck this into the imminent merge window?
*ping*
Signature · 5 lines
-- Pierre Ossman Linux kernel, MMC maintainer http://www.kernel.org PulseAudio, core developer http://pulseaudio.org rdesktop, core developer http://www.rdesktop.org
Pierre Ossman :
Quoted 8 lines
Quoted 7 lines · Nicolas Ferre
Fixes hanging using multi block operations (seen during CMD25). Follows closely the datasheet flowcharts.
This piece of code handles better big file writing. I had to take care of the notbusy signal during write (
at91_mci_handle_cmdrdyfunction) and to rearrange theAT91_MCI_ENDRXandAT91_MCI_RXBUFFflag usage.
Quoted 5 lines
Quoted 2 lines
Signed-off-by Nicolas Ferre <[email protected]>---
Most of the patch looks ok. Do you want to wait for some more tests or should I chuck this into the imminent merge window?
*pong*
I think it is ok to put it into the merge window.
Quoted 8 lines
Quoted 8 lines
(quoted diff) +4
-1@@ -817,7 +834,11 @@ static int __init at91_mci_probe(struct mmc->ops = &at91_mci_ops; mmc->f_min = 375000; - mmc->f_max = 25000000; + if (cpu_is_at91sam9263()) + mmc->f_max = 50000000; + else + mmc->f_max = 25000000;
Quoted 8 lines
Review static int __init at91_mci_probe(struct · line 843
Quoted 4 lines
(quoted diff) +1
-0+ mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; mmc->caps = MMC_CAP_BYTEBLOCK | MMC_CAP_MULTIWRITE;This seems unrelated to the rest of the patch. Also, high-speed won't be enabled unless you set the appropriate caps (which should be checked against timing specifications, not just assigned and hope for the best).
True. I remove this chunk off the patch.
Quoted 17 lines
Quoted 17 lines
(quoted diff) +5
-6@@ -830,11 +851,11 @@ static int __init at91_mci_probe(struct host->bus_mode = 0; host->board = pdev->dev.platform_data; if (host->board->wire4) { -#ifdef SUPPORT_4WIRE - mmc->caps |= MMC_CAP_4_BIT_DATA; -#else - printk("AT91 MMC: 4 wire bus mode not supported by this driver - using 1 wire\n"); -#endif + if (cpu_is_at91sam9260() || cpu_is_at91sam9263()) + mmc->caps |= MMC_CAP_4_BIT_DATA; + else + printk("AT91 MMC: 4 wire bus mode not supported" + " - using 1 wire\n"); }
Quoted 4 lines
Quoted 2 lines
/*This also looks unrelated.
Well, It is related to capacities of newer IPs that allow readproof and writeproof features that I enable here :
+ if (cpu_is_at91sam9260() || cpu_is_at91sam9263())
+ mr |= AT91_MCI_RDPROOF | AT91_MCI_WRPROOF;
This allows to have a smooth data transmission internally (prevent underruns) and so have full 4 wires capacity. So I think this is good for having a full featured driver on all chips.
I resend a corrected patch now.
Regards,
Signature · 1 line
Nicolas Ferre
Hello,
Review static void at91_mci_pre_dma_read(struct · line 253
Quoted 10 lines
(quoted diff) +2
-1@@ -250,28 +246,27 @@ static void at91_mci_pre_dma_read(struct /* * Handle after a dma read */ -static void at91_mci_post_dma_read(struct at91mci_host *host) +static int at91_mci_post_dma_read(struct at91mci_host *host) { struct mmc_command *cmd; struct mmc_data *data; + int completed = 0;
You added 'completed' but you use it only once - as a return value.
Review static void at91_mci_pre_dma_read(struct · line 256
Quoted 3 lines
(quoted diff) +1
-0pr_debug("post dma read done\n"); + return completed; }
What's the point?
Mariusz
PATCH 1/1 mmc: at91_mci: fix hanging and rework to match flowcharts
In reply to Nicolas FerreFixes hanging using multi block operations (seen during CMD25). Follows closely the datasheet flowcharts.
This piece of code handles better big file writing. I had to take care of the notbusy signal during write (at91_mci_handle_cmdrdy function) and to rearrange the AT91_MCI_ENDRX and AT91_MCI_RXBUFF flag usage.
---
This second patch is rebuild following Mariusz and Pierre comments.
This patch is applied on top of the "typo" one :
http://kernel.org/pub/linux/kernel/people/akpm/patches/2.6/2.6.22-rc6/2.6.22-rc6-mm1/broken-out/mmc-at91_mci-typo.patch
It is cooked with the help of Wu Xuan and Marc Pignat. Big thanks to them.
It has been tested on several systems and reported to work ok :
at91rm9200 ok w/ 1 wire
at91sam9261 ok w/ 1 wire
at91sam926[03] ok w/ 4 wires
The code takes care of those specificities.
drivers/mmc/host/at91_mci.c | 199 ++++++++++++++++++----------------
1 file changed, 108 insertions(+), 91 deletions(-)
drivers/mmc/host/at91_mci.c +0 -0
@@ -80,8 +78,6 @@
#define DRIVER_NAME "at91_mci"
-#undef SUPPORT_4WIRE
-
#define FL_SENT_COMMAND (1 << 0)
#define FL_SENT_STOP (1 << 1)
@@ -270,8 +266,6 @@ static void at91_mci_post_dma_read(struc
}
while (host->in_use_index < host->transfer_index) {
- unsigned int *buffer;
-
struct scatterlist *sg;
pr_debug("finishing index %d\n", host->in_use_index);@@ -282,20 +276,22 @@ static void at91_mci_post_dma_read(struc
dma_unmap_page(NULL, sg->dma_address, sg->length, DMA_FROM_DEVICE);
- /* Swap the contents of the buffer */
- buffer = kmap_atomic(sg->page, KM_BIO_SRC_IRQ) + sg->offset;
- pr_debug("buffer = %p, length = %d\n", buffer, sg->length);
-
data->bytes_xfered += sg->length;
if (cpu_is_at91rm9200()) { /* AT91RM9200 errata */
+ unsigned int *buffer;
int index;+ /* Swap the contents of the buffer */
+ buffer = kmap_atomic(sg->page, KM_BIO_SRC_IRQ) + sg->offset;
+ pr_debug("buffer = %p, length = %d\n", buffer, sg->length);
+
for (index = 0; index < (sg->length / 4); index++) buffer[index] = swab32(buffer[index]); + + kunmap_atomic(buffer, KM_BIO_SRC_IRQ); }
- kunmap_atomic(buffer, KM_BIO_SRC_IRQ); flush_dcache_page(sg->page); }
@@ -303,8 +299,8 @@ static void at91_mci_post_dma_read(struc
if (host->transfer_index < data->sg_len)
at91_mci_pre_dma_read(host);
else {
+ at91_mci_write(host, AT91_MCI_IDR, AT91_MCI_ENDRX);
at91_mci_write(host, AT91_MCI_IER, AT91_MCI_RXBUFF);
- at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTDIS | ATMEL_PDC_TXTDIS);
}
pr_debug("post dma read done\n");@@ -325,7 +321,6 @@ static void at91_mci_handle_transmitted(
/* Now wait for cmd ready */ at91_mci_write(host, AT91_MCI_IDR, AT91_MCI_TXBUFE);
- at91_mci_write(host, AT91_MCI_IER, AT91_MCI_NOTBUSY);
cmd = host->cmd; if (!cmd) return; @@ -333,18 +328,53 @@ static void at91_mci_handle_transmitted( data = cmd->data; if (!data) return;
+ if (cmd->data->flags & MMC_DATA_MULTI) {
+ pr_debug("multiple write : wait for BLKE...\n");
+ at91_mci_write(host, AT91_MCI_IER, AT91_MCI_BLKE);
+ } else
+ at91_mci_write(host, AT91_MCI_IER, AT91_MCI_NOTBUSY);
+
data->bytes_xfered = host->total_length; }
+/*Handle after command sent ready*/
+static int at91_mci_handle_cmdrdy(struct at91mci_host *host) +{
+ if (!host->cmd)
+ return 1;
+ else if (!host->cmd->data) {
+ if (host->flags & FL_SENT_STOP) {
+ /*After multi block write, we must wait for NOTBUSY*/
+ at91_mci_write(host, AT91_MCI_IER, AT91_MCI_NOTBUSY);
+ } else return 1;
+ } else if (host->cmd->data->flags & MMC_DATA_WRITE) {
+ /*After sendding multi-block-write command, start DMA transfer*/
+ at91_mci_write(host, AT91_MCI_IER, AT91_MCI_TXBUFE);
+ at91_mci_write(host, AT91_MCI_IER, AT91_MCI_BLKE);
+ at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_TXTEN);
+ }
+
+ /* command not completed, have to wait */
+ return 0;
+}
+
+
/*
* Enable the controller
*/
static void at91_mci_enable(struct at91mci_host *host) {
+ unsigned int mr;
+
at91_mci_write(host, AT91_MCI_CR, AT91_MCI_MCIEN); at91_mci_write(host, AT91_MCI_IDR, 0xffffffff); at91_mci_write(host, AT91_MCI_DTOR, AT91_MCI_DTOMUL_1M | AT91_MCI_DTOCYC);
- at91_mci_write(host, AT91_MCI_MR, AT91_MCI_PDCMODE | 0x34a);
+ mr = AT91_MCI_PDCMODE | 0x34a;
+
+ if (cpu_is_at91sam9260() || cpu_is_at91sam9263())
+ mr |= AT91_MCI_RDPROOF | AT91_MCI_WRPROOF;
+
+ at91_mci_write(host, AT91_MCI_MR, mr);
/* use Slot A or B (only one at same time) */ at91_mci_write(host, AT91_MCI_SDCR, host->board->slot_b);
@@ -360,9 +390,8 @@ static void at91_mci_disable(struct at91
/*
* Send a command
- * return the interrupts to enable
*/
-static unsigned int at91_mci_send_command(struct at91mci_host *host, struct mmc_command *cmd) +static void at91_mci_send_command(struct at91mci_host *host, struct mmc_command *cmd)
{
unsigned int cmdr, mr;
unsigned int block_length;@@ -373,8 +402,7 @@ static unsigned int at91_mci_send_comman
host->cmd = cmd;
- /* Not sure if this is needed */
-#if 0
+ /* Needed for leaving busy state before CMD1 */
if ((at91_mci_read(host, AT91_MCI_SR) & AT91_MCI_RTOE) && (cmd->opcode == 1)) {
pr_debug("Clearing timeout\n");
at91_mci_write(host, AT91_MCI_ARGR, 0);
@@ -384,7 +412,7 @@ static unsigned int at91_mci_send_comman
pr_debug("Clearing: SR = %08X\n", at91_mci_read(host, AT91_MCI_SR));
}
}
-#endif
+
cmdr = cmd->opcode;
if (mmc_resp_type(cmd) == MMC_RSP_NONE)
@@ -441,50 +469,48 @@ static unsigned int at91_mci_send_comman
at91_mci_write(host, ATMEL_PDC_TCR, 0);
at91_mci_write(host, ATMEL_PDC_TNPR, 0);
at91_mci_write(host, ATMEL_PDC_TNCR, 0);+ ier = AT91_MCI_CMDRDY;
+ } else {
+ /* zero block length and PDC mode */
+ mr = at91_mci_read(host, AT91_MCI_MR) & 0x7fff;
+ at91_mci_write(host, AT91_MCI_MR, mr | (block_length << 16) | AT91_MCI_PDCMODE);
+
+ /*
+ * Disable the PDC controller
+ */
+ at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTDIS | ATMEL_PDC_TXTDIS);
- at91_mci_write(host, AT91_MCI_ARGR, cmd->arg);
- at91_mci_write(host, AT91_MCI_CMDR, cmdr);
- return AT91_MCI_CMDRDY;
- }
-
- mr = at91_mci_read(host, AT91_MCI_MR) & 0x7fff; /* zero block length and PDC mode */
- at91_mci_write(host, AT91_MCI_MR, mr | (block_length << 16) | AT91_MCI_PDCMODE);
+ if (cmdr & AT91_MCI_TRCMD_START) {
+ data->bytes_xfered = 0;
+ host->transfer_index = 0;
+ host->in_use_index = 0;
+ if (cmdr & AT91_MCI_TRDIR) {
+ /*
+ * Handle a read
+ */
+ host->buffer = NULL;
+ host->total_length = 0;
- /*
- * Disable the PDC controller
- */
- at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTDIS | ATMEL_PDC_TXTDIS);
-
- if (cmdr & AT91_MCI_TRCMD_START) {
- data->bytes_xfered = 0;
- host->transfer_index = 0;
- host->in_use_index = 0;
- if (cmdr & AT91_MCI_TRDIR) {
- /*
- * Handle a read
- */
- host->buffer = NULL;
- host->total_length = 0;
-
- at91_mci_pre_dma_read(host);
- ier = AT91_MCI_ENDRX /* | AT91_MCI_RXBUFF */;
- }
- else {
- /*
- * Handle a write
- */
- host->total_length = block_length * blocks;
- host->buffer = dma_alloc_coherent(NULL,
- host->total_length,
- &host->physical_address, GFP_KERNEL);
-
- at91_mci_sg_to_dma(host, data);
-
- pr_debug("Transmitting %d bytes\n", host->total_length);
-
- at91_mci_write(host, ATMEL_PDC_TPR, host->physical_address);
- at91_mci_write(host, ATMEL_PDC_TCR, host->total_length / 4);
- ier = AT91_MCI_TXBUFE;
+ at91_mci_pre_dma_read(host);
+ ier = AT91_MCI_ENDRX /* | AT91_MCI_RXBUFF */;
+ }
+ else {
+ /*
+ * Handle a write
+ */
+ host->total_length = block_length * blocks;
+ host->buffer = dma_alloc_coherent(NULL,
+ host->total_length,
+ &host->physical_address, GFP_KERNEL);
+
+ at91_mci_sg_to_dma(host, data);
+
+ pr_debug("Transmitting %d bytes\n", host->total_length);
+
+ at91_mci_write(host, ATMEL_PDC_TPR, host->physical_address);
+ at91_mci_write(host, ATMEL_PDC_TCR, host->total_length / 4);
+ ier = AT91_MCI_CMDRDY;
+ }
} }
@@ -499,24 +525,9 @@ static unsigned int at91_mci_send_comman
if (cmdr & AT91_MCI_TRCMD_START) {
if (cmdr & AT91_MCI_TRDIR)
at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTEN);
- else
- at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_TXTEN);
}- return ier;
-}
-/*
- * Wait for a command to complete
- */
-static void at91_mci_process_command(struct at91mci_host *host, struct mmc_command *cmd) -{
- unsigned int ier;
-
- ier = at91_mci_send_command(host, cmd);
-
- pr_debug("setting ier to %08X\n", ier);
-
- /* Stop on errors or the required value */
+ /* Enable selected interrupts */
at91_mci_write(host, AT91_MCI_IER, AT91_MCI_ERRORS | ier); }
@@ -527,11 +538,11 @@ static void at91_mci_process_next(struct
{
if (!(host->flags & FL_SENT_COMMAND)) {
host->flags |= FL_SENT_COMMAND;
- at91_mci_process_command(host, host->request->cmd);
+ at91_mci_send_command(host, host->request->cmd);
}
else if ((!(host->flags & FL_SENT_STOP)) && host->request->stop) {
host->flags |= FL_SENT_STOP;
- at91_mci_process_command(host, host->request->stop);
+ at91_mci_send_command(host, host->request->stop);
}
else
mmc_request_done(host->mmc, host->request);
@@ -698,29 +709,33 @@ static irqreturn_t at91_mci_irq(int irq,
at91_mci_handle_transmitted(host);
}+ if (int_status & AT91_MCI_ENDRX) {
+ pr_debug("ENDRX\n");
+ at91_mci_post_dma_read(host);
+ }
+
if (int_status & AT91_MCI_RXBUFF) {
pr_debug("RX buffer full\n");
- at91_mci_write(host, AT91_MCI_IER, AT91_MCI_CMDRDY);
+ at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTDIS | ATMEL_PDC_TXTDIS);
+ at91_mci_write(host, AT91_MCI_IDR, AT91_MCI_RXBUFF | AT91_MCI_ENDRX);
+ completed = 1;
}
if (int_status & AT91_MCI_ENDTX)
pr_debug("Transmit has ended\n");- if (int_status & AT91_MCI_ENDRX) {
- pr_debug("Receive has ended\n");
- at91_mci_post_dma_read(host);
- }
-
if (int_status & AT91_MCI_NOTBUSY) {
pr_debug("Card is ready\n");
- at91_mci_write(host, AT91_MCI_IER, AT91_MCI_CMDRDY);
+ completed = 1;
}
if (int_status & AT91_MCI_DTIP)
pr_debug("Data transfer in progress\n");- if (int_status & AT91_MCI_BLKE)
+ if (int_status & AT91_MCI_BLKE) {
pr_debug("Block transfer has ended\n");+ completed = 1;
+ }
if (int_status & AT91_MCI_TXRDY)
pr_debug("Ready to transmit\n");@@ -730,7 +745,7 @@ static irqreturn_t at91_mci_irq(int irq,
if (int_status & AT91_MCI_CMDRDY) {
pr_debug("Command ready\n");
- completed = 1;
+ completed = at91_mci_handle_cmdrdy(host);
}
}@@ -830,11 +845,11 @@ static int __init at91_mci_probe(struct
host->bus_mode = 0;
host->board = pdev->dev.platform_data;
if (host->board->wire4) {-#ifdef SUPPORT_4WIRE
- mmc->caps |= MMC_CAP_4_BIT_DATA;
-#else
- printk("AT91 MMC: 4 wire bus mode not supported by this driver - using 1 wire\n");
-#endif
+ if (cpu_is_at91sam9260() || cpu_is_at91sam9263())
+ mmc->caps |= MMC_CAP_4_BIT_DATA;
+ else
+ printk("AT91 MMC: 4 wire bus mode not supported"
+ " - using 1 wire\n"); } /*
Pierre Ossman
AppliedQuoted 10 lines · On Mon, 09 Jul 2007 14:58:16 +0200 Nicolas Ferre <[email protected]>…
Fixes hanging using multi block operations (seen during CMD25). Follows closely the datasheet flowcharts.
This piece of code handles better big file writing. I had to take care of the notbusy signal during write (
at91_mci_handle_cmdrdyfunction) and to rearrange theAT91_MCI_ENDRXandAT91_MCI_RXBUFFflag usage.Signed-off-by Nicolas Ferre <[email protected]>---
Thanks, applied.
Rgds
Pierre