diff options
author | Ludovic Barre <ludovic.barre@stericsson.com> | 2011-03-25 09:23:57 +0100 |
---|---|---|
committer | Sebastian RASMUSSEN <sebastian.rasmussen@stericsson.com> | 2011-04-07 16:06:16 +0200 |
commit | be851980f0f3170bb73086feb170fe46b5009c6b (patch) | |
tree | cf292266cf30060a693e1173cd490261421e769c /drivers/mmc | |
parent | cc21061ee4be97fec29cba762411d4948fc2f008 (diff) |
mmci: tasklet context, data transfer and finalize
ST-Ericsson Linux next: N/A
ST-Ericsson ID: -
ST-Ericsson FOSS-OUT ID: Trivial
Change-Id: I5b9ed019d249491e6194674ab8396269ebfae8b2
Signed-off-by: Ludovic Barre <ludovic.barre@stericsson.com>
Change-Id: I5b9ed019d249491e6194674ab8396269ebfae8b2
Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/17180
Reviewed-by: Sebastian RASMUSSEN <sebastian.rasmussen@stericsson.com>
Tested-by: Sebastian RASMUSSEN <sebastian.rasmussen@stericsson.com>
Diffstat (limited to 'drivers/mmc')
-rw-r--r-- | drivers/mmc/host/mmci.c | 229 |
1 files changed, 172 insertions, 57 deletions
diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index 08a3de80466..36d1391f891 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -112,6 +112,7 @@ static struct variant_data variant_ux500 = { .signal_direction = true, .non_power_of_2_blksize = true, }; + /* * Debugfs */ @@ -267,29 +268,6 @@ static void mmci_set_clkreg(struct mmci_host *host, unsigned int desired) writel(clk, host->base + MMCICLOCK); } -static void -mmci_request_end(struct mmci_host *host, struct mmc_request *mrq) -{ - writel(0, host->base + MMCICOMMAND); - - BUG_ON(host->data); - - host->mrq = NULL; - host->cmd = NULL; - - if (mrq->data) - mrq->data->bytes_xfered = host->data_xfered; - - /* - * Need to drop the host lock here; mmc_request_done may call - * back into the driver... - */ - spin_unlock(&host->lock); - mmc_request_done(host->mmc, mrq); - spin_lock(&host->lock); -} - - static void mmci_set_mask1(struct mmci_host *host, u32 mask) { if (host->singleirq) { @@ -322,8 +300,7 @@ static inline void clear_imasks(struct mmci_host *host) static void mmci_stop_data(struct mmci_host *host) { - u32 clk; - unsigned int datactrl = 0; + host->mmci_datactrl = 0; /* * The ST Micro variants has a special bit @@ -334,42 +311,14 @@ static void mmci_stop_data(struct mmci_host *host) if (host->variant->sdio && host->mmc->card && mmc_card_sdio(host->mmc->card)) - datactrl |= MCI_ST_DPSM_SDIOEN; + host->mmci_datactrl |= MCI_ST_DPSM_SDIOEN; - writel(datactrl, host->base + MMCIDATACTRL); - mmci_set_mask1(host, 0); + writel(host->mmci_datactrl, host->base + MMCIDATACTRL); /* Needed for DDR */ if (host->mmc->card && mmc_card_ddr_mode(host->mmc->card)) { - clk = readl(host->base + MMCICLOCK); - clk &= ~(MCI_NEG_EDGE); - - writel(clk, (host->base + MMCICLOCK)); - } - - host->data = NULL; -} - -static void -mmci_complete_data_xfer(struct mmci_host *host) -{ - struct mmc_data *data = host->data; - - if ((host->size == 0) || data->error) { - - /* - * Variants with broken blockend flags and as well dma - * transfers handles the end of the entire transfer here. - */ - if (host->last_blockend && !data->error) - host->data_xfered = data->blksz * data->blocks; - - mmci_stop_data(host); - - if (!data->stop) - mmci_request_end(host, data->mrq); - else - mmci_start_command(host, data->stop, 0); + host->mmci_clockctrl &= ~(MCI_NEG_EDGE); + writel(host->mmci_clockctrl, (host->base + MMCICLOCK)); } } @@ -1445,6 +1394,172 @@ static void mmci_enable_sdio_irq(struct mmc_host *mmc, int enable) spin_unlock_irqrestore(&host->lock, flags); } +/* + * Tasklet + */ +static void mmci_pio_xferdata(struct mmci_host *host) +{ + struct sg_mapping_iter *sg_miter = &host->sg_miter; + u32 mmci_status; + u32 remain, len; + char *buffer; + unsigned long flags; + + dev_dbg(mmc_dev(host->mmc), "[%s]\n", __func__); + + local_irq_save(flags); + + do { + mmci_status = readl(host->base + MMCISTATUS); + /* + * For write, we only need to test the half-empty flag + * here - if the FIFO is completely empty, then by + * definition it is more than half empty. + * + * For read, check for data available. + */ + if (!(mmci_status & (MCI_TXFIFOHALFEMPTY|MCI_RXDATAAVLBL))) + break; + + if (!sg_miter_next(sg_miter)) + break; + + buffer = sg_miter->addr; + remain = sg_miter->length; + len = 0; + + if (mmci_status & MCI_RXACTIVE) + len = mmci_pio_read(host, buffer, remain); + if (mmci_status & MCI_TXACTIVE) + len = mmci_pio_write(host, buffer, + remain, mmci_status); + + sg_miter->consumed = len; + host->size -= len; + remain -= len; + + if (remain) + break; + } while (1); + + sg_miter_stop(sg_miter); + + local_irq_restore(flags); + + if (mmci_status & MCI_RXACTIVE) { + host->mmci_mask1 |= MCI_RXFIFOHALFFULLMASK; + /* + * If we're nearing the end of the read, switch to + * "any data available" mode. + */ + if (host->size < host->variant->fifosize) + host->mmci_mask1 |= MCI_RXDATAAVLBLMASK; + } + + if (mmci_status & MCI_TXACTIVE) + host->mmci_mask1 |= MCI_TXFIFOHALFEMPTYMASK; + + /* + * If we run out of data, disable the data IRQs; this + * prevents a race where the FIFO becomes empty before + * the chip itself has disabled the data path, and + * stops us racing with our data end IRQ. + */ + if (host->size == 0) { + host->mmci_mask1 = 0; + host->mmci_mask0 |= MCI_DATAENDMASK; + } +} + +static void mmci_finalize_request(struct mmci_host *host) +{ + struct mmc_request *mrq = host->mrq; + struct mmc_command *cmd; + + if (!mrq) { + dev_err(mmc_dev(host->mmc), "request Missing!\n"); + return; + } + + cmd = host->cmd_is_stop ? mrq->stop : mrq->cmd; + if (cmd->data && !cmd->error && !cmd->data->error) + if (host->dma_enable && + host->dma_complete == COMPLETION_DMA_START) + return; /* wait dma completion or datatimeout */ + + /* Read response from controller. */ + cmd->resp[0] = readl(host->base + MMCIRESPONSE0); + cmd->resp[1] = readl(host->base + MMCIRESPONSE1); + cmd->resp[2] = readl(host->base + MMCIRESPONSE2); + cmd->resp[3] = readl(host->base + MMCIRESPONSE3); + + /* Cleanup controller */ + writel(0, host->base + MMCICOMMAND); + writel(0, host->base + MMCIARGUMENT); + writel(0xFFF, host->base + MMCICLEAR); + clear_imasks(host); + + if (cmd->data && cmd->error) + cmd->data->error = cmd->error; + + /* If we have no data transfer we are finished here */ + if (!mrq->data) + goto request_done; + + mmci_stop_data(host); + + if (cmd->data && cmd->data->stop && (!host->cmd_is_stop)) { + host->cmd_is_stop = 1; + mmci_send_request(host->mmc); + return; + } + + /*if dma used and finish or error*/ + if (host->dma_enable && host->dma_complete != COMPLETION_DMA_NONE) { + if (host->dma_complete == COMPLETION_DMA_START) + mmci_dma_abort(host); /* dma error */ + + dma_unmap_sg(mmc_dev(host->mmc), mrq->data->sg, + mrq->data->sg_len, + (mrq->data->flags & MMC_DATA_WRITE) + ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + host->dma_complete = COMPLETION_DMA_NONE; + } + + /* Calulate the amout of bytes transfer if there was no error */ + if (mrq->data->error == 0) + mrq->data->bytes_xfered = mrq->data->blocks * mrq->data->blksz; + else + mrq->data->bytes_xfered = 0; + +request_done: + host->complete_what = COMPLETION_NONE; + host->mrq = NULL; + mmc_request_done(host->mmc, mrq); + return; + +} + +static void mmci_tasklet(unsigned long data) +{ + struct mmci_host *host = (struct mmci_host *) data; + + dev_dbg(mmc_dev(host->mmc), "[%s]\n", __func__); + + if (host->pio_active != XFER_NONE) { + mmci_pio_xferdata(host); + mmci_set_mask1(host, host->mmci_mask1); + } + + if (host->complete_what == COMPLETION_FINALIZE) + mmci_finalize_request(host); + else + enable_imasks(host); +} + +/* + * Init,register,unregister host functions to core mmc + */ static const struct mmc_host_ops mmci_ops = { .request = mmci_request, .set_ios = mmci_set_ios, |