summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/mmc/host/jz4740_mmc.c138
1 files changed, 116 insertions, 22 deletions
diff --git a/drivers/mmc/host/jz4740_mmc.c b/drivers/mmc/host/jz4740_mmc.c
index 049b13353917..14738cddcadb 100644
--- a/drivers/mmc/host/jz4740_mmc.c
+++ b/drivers/mmc/host/jz4740_mmc.c
@@ -114,6 +114,11 @@ enum jz4740_mmc_state {
JZ4740_MMC_STATE_DONE,
};
+struct jz4740_mmc_host_next {
+ int sg_len;
+ s32 cookie;
+};
+
struct jz4740_mmc_host {
struct mmc_host *mmc;
struct platform_device *pdev;
@@ -143,6 +148,7 @@ struct jz4740_mmc_host {
/* DMA support */
struct dma_chan *dma_rx;
struct dma_chan *dma_tx;
+ struct jz4740_mmc_host_next next_data;
bool use_dma;
int sg_len;
@@ -184,6 +190,9 @@ static int jz4740_mmc_acquire_dma_channels(struct jz4740_mmc_host *host)
goto free_master_write;
}
+ /* Initialize DMA pre request cookie */
+ host->next_data.cookie = 1;
+
return 0;
free_master_write:
@@ -196,23 +205,72 @@ static inline int jz4740_mmc_get_dma_dir(struct mmc_data *data)
return (data->flags & MMC_DATA_READ) ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
}
+static inline struct dma_chan *jz4740_mmc_get_dma_chan(struct jz4740_mmc_host *host,
+ struct mmc_data *data)
+{
+ return (data->flags & MMC_DATA_READ) ? host->dma_rx : host->dma_tx;
+}
+
static void jz4740_mmc_dma_unmap(struct jz4740_mmc_host *host,
struct mmc_data *data)
{
- struct dma_chan *chan;
+ struct dma_chan *chan = jz4740_mmc_get_dma_chan(host, data);
enum dma_data_direction dir = jz4740_mmc_get_dma_dir(data);
- if (dir == DMA_TO_DEVICE)
- chan = host->dma_tx;
- else
- chan = host->dma_rx;
-
dma_unmap_sg(chan->device->dev, data->sg, data->sg_len, dir);
}
+/* Prepares DMA data for current/next transfer, returns non-zero on failure */
+static int jz4740_mmc_prepare_dma_data(struct jz4740_mmc_host *host,
+ struct mmc_data *data,
+ struct jz4740_mmc_host_next *next,
+ struct dma_chan *chan)
+{
+ struct jz4740_mmc_host_next *next_data = &host->next_data;
+ enum dma_data_direction dir = jz4740_mmc_get_dma_dir(data);
+ int sg_len;
+
+ if (!next && data->host_cookie &&
+ data->host_cookie != host->next_data.cookie) {
+ dev_warn(mmc_dev(host->mmc),
+ "[%s] invalid cookie: data->host_cookie %d host->next_data.cookie %d\n",
+ __func__,
+ data->host_cookie,
+ host->next_data.cookie);
+ data->host_cookie = 0;
+ }
+
+ /* Check if next job is already prepared */
+ if (next || data->host_cookie != host->next_data.cookie) {
+ sg_len = dma_map_sg(chan->device->dev,
+ data->sg,
+ data->sg_len,
+ dir);
+
+ } else {
+ sg_len = next_data->sg_len;
+ next_data->sg_len = 0;
+ }
+
+ if (sg_len <= 0) {
+ dev_err(mmc_dev(host->mmc),
+ "Failed to map scatterlist for DMA operation\n");
+ return -EINVAL;
+ }
+
+ if (next) {
+ next->sg_len = sg_len;
+ data->host_cookie = ++next->cookie < 0 ? 1 : next->cookie;
+ } else
+ host->sg_len = sg_len;
+
+ return 0;
+}
+
static int jz4740_mmc_start_dma_transfer(struct jz4740_mmc_host *host,
struct mmc_data *data)
{
+ int ret;
struct dma_chan *chan;
struct dma_async_tx_descriptor *desc;
struct dma_slave_config conf = {
@@ -221,9 +279,8 @@ static int jz4740_mmc_start_dma_transfer(struct jz4740_mmc_host *host,
.src_maxburst = JZ4740_MMC_FIFO_HALF_SIZE,
.dst_maxburst = JZ4740_MMC_FIFO_HALF_SIZE,
};
- enum dma_data_direction dir = jz4740_mmc_get_dma_dir(data);
- if (dir == DMA_TO_DEVICE) {
+ if (data->flags & MMC_DATA_WRITE) {
conf.direction = DMA_MEM_TO_DEV;
conf.dst_addr = host->mem_res->start + JZ_REG_MMC_TXFIFO;
conf.slave_id = JZ4740_DMA_TYPE_MMC_TRANSMIT;
@@ -235,16 +292,9 @@ static int jz4740_mmc_start_dma_transfer(struct jz4740_mmc_host *host,
chan = host->dma_rx;
}
- host->sg_len = dma_map_sg(chan->device->dev,
- data->sg,
- data->sg_len,
- dir);
-
- if (host->sg_len == 0) {
- dev_err(mmc_dev(host->mmc),
- "Failed to map scatterlist for DMA operation\n");
- return -EINVAL;
- }
+ ret = jz4740_mmc_prepare_dma_data(host, data, NULL, chan);
+ if (ret)
+ return ret;
dmaengine_slave_config(chan, &conf);
desc = dmaengine_prep_slave_sg(chan,
@@ -269,6 +319,43 @@ dma_unmap:
return -ENOMEM;
}
+static void jz4740_mmc_pre_request(struct mmc_host *mmc,
+ struct mmc_request *mrq,
+ bool is_first_req)
+{
+ struct jz4740_mmc_host *host = mmc_priv(mmc);
+ struct mmc_data *data = mrq->data;
+ struct jz4740_mmc_host_next *next_data = &host->next_data;
+
+ BUG_ON(data->host_cookie);
+
+ if (host->use_dma) {
+ struct dma_chan *chan = jz4740_mmc_get_dma_chan(host, data);
+
+ if (jz4740_mmc_prepare_dma_data(host, data, next_data, chan))
+ data->host_cookie = 0;
+ }
+}
+
+static void jz4740_mmc_post_request(struct mmc_host *mmc,
+ struct mmc_request *mrq,
+ int err)
+{
+ struct jz4740_mmc_host *host = mmc_priv(mmc);
+ struct mmc_data *data = mrq->data;
+
+ if (host->use_dma && data->host_cookie) {
+ jz4740_mmc_dma_unmap(host, data);
+ data->host_cookie = 0;
+ }
+
+ if (err) {
+ struct dma_chan *chan = jz4740_mmc_get_dma_chan(host, data);
+
+ dmaengine_terminate_all(chan);
+ }
+}
+
/*----------------------------------------------------------------------------*/
static void jz4740_mmc_set_irq_enabled(struct jz4740_mmc_host *host,
@@ -627,14 +714,19 @@ static irqreturn_t jz_mmc_irq_worker(int irq, void *devid)
case JZ4740_MMC_STATE_TRANSFER_DATA:
if (host->use_dma) {
- /* Use DMA if enabled, data transfer direction was
- * defined before in jz_mmc_prepare_data_transfer().
+ /* Use DMA if enabled.
+ * Data transfer direction is defined later by
+ * relying on data flags in
+ * jz4740_mmc_prepare_dma_data() and
+ * jz4740_mmc_start_dma_transfer().
*/
timeout = jz4740_mmc_start_dma_transfer(host, data);
data->bytes_xfered = data->blocks * data->blksz;
} else if (data->flags & MMC_DATA_READ)
- /* If DMA is not enabled, rely on data flags
- * to establish data transfer direction.
+ /* Use PIO if DMA is not enabled.
+ * Data transfer direction was defined before
+ * by relying on data flags in
+ * jz_mmc_prepare_data_transfer().
*/
timeout = jz4740_mmc_read_data(host, data);
else
@@ -809,6 +901,8 @@ static void jz4740_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable)
static const struct mmc_host_ops jz4740_mmc_ops = {
.request = jz4740_mmc_request,
+ .pre_req = jz4740_mmc_pre_request,
+ .post_req = jz4740_mmc_post_request,
.set_ios = jz4740_mmc_set_ios,
.get_ro = mmc_gpio_get_ro,
.get_cd = mmc_gpio_get_cd,