struct timer_list timer;
};
+/*
+ * Reset the controller and restore most of the state
+ */
+static void at91_reset_host(struct at91mci_host *host)
+{
+ unsigned long flags;
+ u32 mr;
+ u32 sdcr;
+ u32 dtor;
+ u32 imr;
+
+ local_irq_save(flags);
+ imr = at91_mci_read(host, AT91_MCI_IMR);
+
+ at91_mci_write(host, AT91_MCI_IDR, 0xffffffff);
+
+ /* save current state */
+ mr = at91_mci_read(host, AT91_MCI_MR) & 0x7fff;
+ sdcr = at91_mci_read(host, AT91_MCI_SDCR);
+ dtor = at91_mci_read(host, AT91_MCI_DTOR);
+
+ /* reset the controller */
+ at91_mci_write(host, AT91_MCI_CR, AT91_MCI_MCIDIS | AT91_MCI_SWRST);
+
+ /* restore state */
+ at91_mci_write(host, AT91_MCI_CR, AT91_MCI_MCIEN);
+ at91_mci_write(host, AT91_MCI_MR, mr);
+ at91_mci_write(host, AT91_MCI_SDCR, sdcr);
+ at91_mci_write(host, AT91_MCI_DTOR, dtor);
+ at91_mci_write(host, AT91_MCI_IER, imr);
+
+ /* make sure sdio interrupts will fire */
+ at91_mci_read(host, AT91_MCI_SR);
+
+ local_irq_restore(flags);
+}
+
static void at91_timeout_timer(unsigned long data)
{
struct at91mci_host *host;
host->request->cmd->error = -ETIMEDOUT;
}
+ at91_reset_host(host);
mmc_request_done(host->mmc, host->request);
}
}
unsigned int len, i, size;
unsigned *dmabuf = host->buffer;
- size = host->total_length;
+ size = data->blksz * data->blocks;
len = data->sg_len;
+ /* AT91SAM926[0/3] Data Write Operation and number of bytes erratum */
+ if (cpu_is_at91sam9260() || cpu_is_at91sam9263())
+ if (host->total_length == 12)
+ memset(dmabuf, 0, 12);
+
/*
* Just loop through all entries. Size might not
* be the entire list though so make sure that
for (index = 0; index < (amount / 4); index++)
*dmabuf++ = swab32(sgbuffer[index]);
- }
- else
+ } else {
memcpy(dmabuf, sgbuffer, amount);
+ dmabuf += amount;
+ }
kunmap_atomic(sgbuffer, KM_BIO_SRC_IRQ);
dma_unmap_page(NULL, sg->dma_address, sg->length, DMA_FROM_DEVICE);
- data->bytes_xfered += sg->length;
-
if (cpu_is_at91rm9200()) { /* AT91RM9200 errata */
unsigned int *buffer;
int index;
}
flush_dcache_page(sg_page(sg));
+
+ data->bytes_xfered += sg->length;
}
/* Is there another transfer to trigger? */
at91_mci_write(host, AT91_MCI_IER, AT91_MCI_BLKE);
} else
at91_mci_write(host, AT91_MCI_IER, AT91_MCI_NOTBUSY);
+}
+
+/*
+ * Update bytes tranfered count during a write operation
+ */
+static void at91_mci_update_bytes_xfered(struct at91mci_host *host)
+{
+ struct mmc_data *data;
+
+ /* always deal with the effective request (and not the current cmd) */
+
+ if (host->request->cmd && host->request->cmd->error != 0)
+ return;
- data->bytes_xfered = host->total_length;
+ if (host->request->data) {
+ data = host->request->data;
+ if (data->flags & MMC_DATA_WRITE) {
+ /* card is in IDLE mode now */
+ pr_debug("-> bytes_xfered %d, total_length = %d\n",
+ data->bytes_xfered, host->total_length);
+ data->bytes_xfered = data->blksz * data->blocks;
+ }
+ }
}
+
/*Handle after command sent ready*/
static int at91_mci_handle_cmdrdy(struct at91mci_host *host)
{
} 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, AT91_MCI_IER, AT91_MCI_TXBUFE | AT91_MCI_BLKE);
at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_TXTEN);
}
if (data) {
- if ( cpu_is_at91rm9200() && (data->blksz & 0x3) ) {
- pr_debug("Unsupported block size\n");
- cmd->error = -EINVAL;
- mmc_request_done(host->mmc, host->request);
- return;
+ if (cpu_is_at91rm9200() || cpu_is_at91sam9261()) {
+ if (data->blksz & 0x3) {
+ pr_debug("Unsupported block size\n");
+ cmd->error = -EINVAL;
+ mmc_request_done(host->mmc, host->request);
+ return;
+ }
+ if (data->flags & MMC_DATA_STREAM) {
+ pr_debug("Stream commands not supported\n");
+ cmd->error = -EINVAL;
+ mmc_request_done(host->mmc, host->request);
+ return;
+ }
}
block_length = data->blksz;
ier = AT91_MCI_CMDRDY;
} else {
/* zero block length and PDC mode */
- mr = at91_mci_read(host, AT91_MCI_MR) & 0x7fff;
+ mr = at91_mci_read(host, AT91_MCI_MR) & 0x5fff;
mr |= (data->blksz & 0x3) ? AT91_MCI_PDCFBYTE : 0;
mr |= (block_length << 16);
mr |= AT91_MCI_PDCMODE;
at91_mci_write(host, AT91_MCI_MR, mr);
+ if (!(cpu_is_at91rm9200() || cpu_is_at91sam9261()))
+ at91_mci_write(host, AT91_MCI_BLKR,
+ AT91_MCI_BLKR_BCNT(blocks) |
+ AT91_MCI_BLKR_BLKLEN(block_length));
+
/*
* Disable the PDC controller
*/
* Handle a write
*/
host->total_length = block_length * blocks;
+ /*
+ * AT91SAM926[0/3] Data Write Operation and
+ * number of bytes erratum
+ */
+ if (cpu_is_at91sam9260 () || cpu_is_at91sam9263())
+ if (host->total_length < 12)
+ host->total_length = 12;
host->buffer = dma_alloc_coherent(NULL,
host->total_length,
&host->physical_address, GFP_KERNEL);
at91_mci_send_command(host, host->request->stop);
} else {
del_timer(&host->timer);
+ /* the at91rm9200 mci controller hangs after some transfers,
+ * and the workaround is to reset it after each transfer.
+ */
+ if (cpu_is_at91rm9200())
+ at91_reset_host(host);
mmc_request_done(host->mmc, host->request);
}
}
/*
* Handle a command that has been completed
*/
-static void at91_mci_completed_command(struct at91mci_host *host)
+static void at91_mci_completed_command(struct at91mci_host *host, unsigned int status)
{
struct mmc_command *cmd = host->cmd;
- unsigned int status;
+ struct mmc_data *data = cmd->data;
- at91_mci_write(host, AT91_MCI_IDR, 0xffffffff);
+ at91_mci_write(host, AT91_MCI_IDR, 0xffffffff & ~(AT91_MCI_SDIOIRQA | AT91_MCI_SDIOIRQB));
cmd->resp[0] = at91_mci_read(host, AT91_MCI_RSPR(0));
cmd->resp[1] = at91_mci_read(host, AT91_MCI_RSPR(1));
host->buffer = NULL;
}
- status = at91_mci_read(host, AT91_MCI_SR);
-
- pr_debug("Status = %08X [%08X %08X %08X %08X]\n",
- status, cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3]);
+ pr_debug("Status = %08X/%08x [%08X %08X %08X %08X]\n",
+ status, at91_mci_read(host, AT91_MCI_SR),
+ cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3]);
if (status & AT91_MCI_ERRORS) {
if ((status & AT91_MCI_RCRCE) && !(mmc_resp_type(cmd) & MMC_RSP_CRC)) {
cmd->error = 0;
}
else {
- if (status & (AT91_MCI_RTOE | AT91_MCI_DTOE))
- cmd->error = -ETIMEDOUT;
- else if (status & (AT91_MCI_RCRCE | AT91_MCI_DCRCE))
- cmd->error = -EILSEQ;
- else
- cmd->error = -EIO;
+ if (status & (AT91_MCI_DTOE | AT91_MCI_DCRCE)) {
+ if (data) {
+ if (status & AT91_MCI_DTOE)
+ data->error = -ETIMEDOUT;
+ else if (status & AT91_MCI_DCRCE)
+ data->error = -EILSEQ;
+ }
+ } else {
+ if (status & AT91_MCI_RTOE)
+ cmd->error = -ETIMEDOUT;
+ else if (status & AT91_MCI_RCRCE)
+ cmd->error = -EILSEQ;
+ else
+ cmd->error = -EIO;
+ }
- pr_debug("Error detected and set to %d (cmd = %d, retries = %d)\n",
- cmd->error, cmd->opcode, cmd->retries);
+ pr_debug("Error detected and set to %d/%d (cmd = %d, retries = %d)\n",
+ cmd->error, data ? data->error : 0,
+ cmd->opcode, cmd->retries);
}
}
else
if (int_status & AT91_MCI_NOTBUSY) {
pr_debug("Card is ready\n");
+ at91_mci_update_bytes_xfered(host);
completed = 1;
}
if (int_status & AT91_MCI_BLKE) {
pr_debug("Block transfer has ended\n");
- completed = 1;
+ if (host->request->data && host->request->data->blocks > 1) {
+ /* multi block write : complete multi write
+ * command and send stop */
+ completed = 1;
+ } else {
+ at91_mci_write(host, AT91_MCI_IER, AT91_MCI_NOTBUSY);
+ }
}
+ if (int_status & AT91_MCI_SDIOIRQA)
+ mmc_signal_sdio_irq(host->mmc);
+
+ if (int_status & AT91_MCI_SDIOIRQB)
+ mmc_signal_sdio_irq(host->mmc);
+
if (int_status & AT91_MCI_TXRDY)
pr_debug("Ready to transmit\n");
if (completed) {
pr_debug("Completed command\n");
- at91_mci_write(host, AT91_MCI_IDR, 0xffffffff);
- at91_mci_completed_command(host);
+ at91_mci_write(host, AT91_MCI_IDR, 0xffffffff & ~(AT91_MCI_SDIOIRQA | AT91_MCI_SDIOIRQB));
+ at91_mci_completed_command(host, int_status);
} else
- at91_mci_write(host, AT91_MCI_IDR, int_status);
+ at91_mci_write(host, AT91_MCI_IDR, int_status & ~(AT91_MCI_SDIOIRQA | AT91_MCI_SDIOIRQB));
return IRQ_HANDLED;
}
return -ENOSYS;
}
+static void at91_mci_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+ struct at91mci_host *host = mmc_priv(mmc);
+
+ pr_debug("%s: sdio_irq %c : %s\n", mmc_hostname(host->mmc),
+ host->board->slot_b ? 'B':'A', enable ? "enable" : "disable");
+ at91_mci_write(host, enable ? AT91_MCI_IER : AT91_MCI_IDR,
+ host->board->slot_b ? AT91_MCI_SDIOIRQB : AT91_MCI_SDIOIRQA);
+
+}
+
static const struct mmc_host_ops at91_mci_ops = {
.request = at91_mci_request,
.set_ios = at91_mci_set_ios,
.get_ro = at91_mci_get_ro,
+ .enable_sdio_irq = at91_mci_enable_sdio_irq,
};
/*
mmc->f_min = 375000;
mmc->f_max = 25000000;
mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+ mmc->caps = MMC_CAP_SDIO_IRQ;
mmc->max_blk_size = 4095;
mmc->max_blk_count = mmc->max_req_size;