mmc: sh_mmcif: add DMA support

The MMCIF controller on sh-mobile platforms can use the DMA controller for data
transfers. Interface to the SH dmaengine driver to enable DMA. We also have to
lower the maximum number of segments to match with the number od DMA
descriptors on SuperH, this doesn't significantly affect driver's PIO
performance.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
Signed-off-by: Paul Mundt <lethal@linux-sh.org>
This commit is contained in:
Guennadi Liakhovetski 2010-11-24 10:05:22 +00:00 committed by Paul Mundt
parent e47bf32aa8
commit a782d688e9
3 changed files with 258 additions and 9 deletions

View File

@ -466,6 +466,12 @@ config MMC_SH_MMCIF
This driver supports MMCIF in sh7724/sh7757/sh7372.
config SH_MMCIF_DMA
bool "Use DMA for MMCIF"
depends on MMC_SH_MMCIF
help
Use SH dma-engine driver for data transfer
config MMC_JZ4740
tristate "JZ4740 SD/Multimedia Card Interface support"
depends on MACH_JZ4740

View File

@ -20,12 +20,14 @@
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/mmc/card.h>
#include <linux/mmc/core.h>
#include <linux/mmc/host.h>
#include <linux/mmc/mmc.h>
#include <linux/mmc/sdio.h>
#include <linux/mmc/sh_mmcif.h>
#include <linux/pagemap.h>
#include <linux/platform_device.h>
#define DRIVER_NAME "sh_mmcif"
@ -162,8 +164,13 @@ struct sh_mmcif_host {
long timeout;
void __iomem *addr;
struct completion intr_wait;
};
/* DMA support */
struct dma_chan *chan_rx;
struct dma_chan *chan_tx;
struct completion dma_complete;
unsigned int dma_sglen;
};
static inline void sh_mmcif_bitset(struct sh_mmcif_host *host,
unsigned int reg, u32 val)
@ -177,6 +184,208 @@ static inline void sh_mmcif_bitclr(struct sh_mmcif_host *host,
writel(~val & readl(host->addr + reg), host->addr + reg);
}
#ifdef CONFIG_SH_MMCIF_DMA
static void mmcif_dma_complete(void *arg)
{
struct sh_mmcif_host *host = arg;
dev_dbg(&host->pd->dev, "Command completed\n");
if (WARN(!host->data, "%s: NULL data in DMA completion!\n",
dev_name(&host->pd->dev)))
return;
if (host->data->flags & MMC_DATA_READ)
dma_unmap_sg(&host->pd->dev, host->data->sg, host->dma_sglen,
DMA_FROM_DEVICE);
else
dma_unmap_sg(&host->pd->dev, host->data->sg, host->dma_sglen,
DMA_TO_DEVICE);
complete(&host->dma_complete);
}
static void sh_mmcif_start_dma_rx(struct sh_mmcif_host *host)
{
struct scatterlist *sg = host->data->sg;
struct dma_async_tx_descriptor *desc = NULL;
struct dma_chan *chan = host->chan_rx;
dma_cookie_t cookie = -EINVAL;
int ret;
ret = dma_map_sg(&host->pd->dev, sg, host->data->sg_len, DMA_FROM_DEVICE);
if (ret > 0) {
host->dma_sglen = ret;
desc = chan->device->device_prep_slave_sg(chan, sg, ret,
DMA_FROM_DEVICE, DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
}
if (desc) {
desc->callback = mmcif_dma_complete;
desc->callback_param = host;
cookie = desc->tx_submit(desc);
if (cookie < 0) {
desc = NULL;
ret = cookie;
} else {
sh_mmcif_bitset(host, MMCIF_CE_BUF_ACC, BUF_ACC_DMAREN);
chan->device->device_issue_pending(chan);
}
}
dev_dbg(&host->pd->dev, "%s(): mapped %d -> %d, cookie %d\n",
__func__, host->data->sg_len, ret, cookie);
if (!desc) {
/* DMA failed, fall back to PIO */
if (ret >= 0)
ret = -EIO;
host->chan_rx = NULL;
host->dma_sglen = 0;
dma_release_channel(chan);
/* Free the Tx channel too */
chan = host->chan_tx;
if (chan) {
host->chan_tx = NULL;
dma_release_channel(chan);
}
dev_warn(&host->pd->dev,
"DMA failed: %d, falling back to PIO\n", ret);
sh_mmcif_bitclr(host, MMCIF_CE_BUF_ACC, BUF_ACC_DMAREN | BUF_ACC_DMAWEN);
}
dev_dbg(&host->pd->dev, "%s(): desc %p, cookie %d, sg[%d]\n", __func__,
desc, cookie, host->data->sg_len);
}
static void sh_mmcif_start_dma_tx(struct sh_mmcif_host *host)
{
struct scatterlist *sg = host->data->sg;
struct dma_async_tx_descriptor *desc = NULL;
struct dma_chan *chan = host->chan_tx;
dma_cookie_t cookie = -EINVAL;
int ret;
ret = dma_map_sg(&host->pd->dev, sg, host->data->sg_len, DMA_TO_DEVICE);
if (ret > 0) {
host->dma_sglen = ret;
desc = chan->device->device_prep_slave_sg(chan, sg, ret,
DMA_TO_DEVICE, DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
}
if (desc) {
desc->callback = mmcif_dma_complete;
desc->callback_param = host;
cookie = desc->tx_submit(desc);
if (cookie < 0) {
desc = NULL;
ret = cookie;
} else {
sh_mmcif_bitset(host, MMCIF_CE_BUF_ACC, BUF_ACC_DMAWEN);
chan->device->device_issue_pending(chan);
}
}
dev_dbg(&host->pd->dev, "%s(): mapped %d -> %d, cookie %d\n",
__func__, host->data->sg_len, ret, cookie);
if (!desc) {
/* DMA failed, fall back to PIO */
if (ret >= 0)
ret = -EIO;
host->chan_tx = NULL;
host->dma_sglen = 0;
dma_release_channel(chan);
/* Free the Rx channel too */
chan = host->chan_rx;
if (chan) {
host->chan_rx = NULL;
dma_release_channel(chan);
}
dev_warn(&host->pd->dev,
"DMA failed: %d, falling back to PIO\n", ret);
sh_mmcif_bitclr(host, MMCIF_CE_BUF_ACC, BUF_ACC_DMAREN | BUF_ACC_DMAWEN);
}
dev_dbg(&host->pd->dev, "%s(): desc %p, cookie %d\n", __func__,
desc, cookie);
}
static bool sh_mmcif_filter(struct dma_chan *chan, void *arg)
{
dev_dbg(chan->device->dev, "%s: slave data %p\n", __func__, arg);
chan->private = arg;
return true;
}
static void sh_mmcif_request_dma(struct sh_mmcif_host *host,
struct sh_mmcif_plat_data *pdata)
{
host->dma_sglen = 0;
/* We can only either use DMA for both Tx and Rx or not use it at all */
if (pdata->dma) {
dma_cap_mask_t mask;
dma_cap_zero(mask);
dma_cap_set(DMA_SLAVE, mask);
host->chan_tx = dma_request_channel(mask, sh_mmcif_filter,
&pdata->dma->chan_priv_tx);
dev_dbg(&host->pd->dev, "%s: TX: got channel %p\n", __func__,
host->chan_tx);
if (!host->chan_tx)
return;
host->chan_rx = dma_request_channel(mask, sh_mmcif_filter,
&pdata->dma->chan_priv_rx);
dev_dbg(&host->pd->dev, "%s: RX: got channel %p\n", __func__,
host->chan_rx);
if (!host->chan_rx) {
dma_release_channel(host->chan_tx);
host->chan_tx = NULL;
return;
}
init_completion(&host->dma_complete);
}
}
static void sh_mmcif_release_dma(struct sh_mmcif_host *host)
{
sh_mmcif_bitclr(host, MMCIF_CE_BUF_ACC, BUF_ACC_DMAREN | BUF_ACC_DMAWEN);
/* Descriptors are freed automatically */
if (host->chan_tx) {
struct dma_chan *chan = host->chan_tx;
host->chan_tx = NULL;
dma_release_channel(chan);
}
if (host->chan_rx) {
struct dma_chan *chan = host->chan_rx;
host->chan_rx = NULL;
dma_release_channel(chan);
}
host->dma_sglen = 0;
}
#else
static void sh_mmcif_start_dma_tx(struct sh_mmcif_host *host)
{
}
static void sh_mmcif_start_dma_rx(struct sh_mmcif_host *host)
{
}
static void sh_mmcif_request_dma(struct sh_mmcif_host *host,
struct sh_mmcif_plat_data *pdata)
{
/* host->chan_tx, host->chan_tx and host->dma_sglen are all zero */
}
static void sh_mmcif_release_dma(struct sh_mmcif_host *host)
{
}
#endif
static void sh_mmcif_clock_control(struct sh_mmcif_host *host, unsigned int clk)
{
@ -564,7 +773,20 @@ static void sh_mmcif_start_cmd(struct sh_mmcif_host *host,
}
sh_mmcif_get_response(host, cmd);
if (host->data) {
ret = sh_mmcif_data_trans(host, mrq, cmd->opcode);
if (!host->dma_sglen) {
ret = sh_mmcif_data_trans(host, mrq, cmd->opcode);
} else {
long time =
wait_for_completion_interruptible_timeout(&host->dma_complete,
host->timeout);
if (!time)
ret = -ETIMEDOUT;
else if (time < 0)
ret = time;
sh_mmcif_bitclr(host, MMCIF_CE_BUF_ACC,
BUF_ACC_DMAREN | BUF_ACC_DMAWEN);
host->dma_sglen = 0;
}
if (ret < 0)
mrq->data->bytes_xfered = 0;
else
@ -622,6 +844,15 @@ static void sh_mmcif_request(struct mmc_host *mmc, struct mmc_request *mrq)
break;
}
host->data = mrq->data;
if (mrq->data) {
if (mrq->data->flags & MMC_DATA_READ) {
if (host->chan_rx)
sh_mmcif_start_dma_rx(host);
} else {
if (host->chan_tx)
sh_mmcif_start_dma_tx(host);
}
}
sh_mmcif_start_cmd(host, mrq, mrq->cmd);
host->data = NULL;
@ -806,14 +1037,18 @@ static int __devinit sh_mmcif_probe(struct platform_device *pdev)
mmc->caps = MMC_CAP_MMC_HIGHSPEED;
if (pd->caps)
mmc->caps |= pd->caps;
mmc->max_segs = 128;
mmc->max_segs = 32;
mmc->max_blk_size = 512;
mmc->max_blk_count = 65535;
mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
mmc->max_req_size = PAGE_CACHE_SIZE * mmc->max_segs;
mmc->max_blk_count = mmc->max_req_size / mmc->max_blk_size;
mmc->max_seg_size = mmc->max_req_size;
sh_mmcif_sync_reset(host);
platform_set_drvdata(pdev, host);
/* See if we also get DMA */
sh_mmcif_request_dma(host, pd);
mmc_add_host(mmc);
ret = request_irq(irq[0], sh_mmcif_intr, 0, "sh_mmc:error", host);
@ -852,6 +1087,7 @@ static int __devexit sh_mmcif_remove(struct platform_device *pdev)
int irq[2];
mmc_remove_host(host->mmc);
sh_mmcif_release_dma(host);
if (host->addr)
iounmap(host->addr);

View File

@ -14,8 +14,9 @@
#ifndef __SH_MMCIF_H__
#define __SH_MMCIF_H__
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/sh_dma.h>
/*
* MMCIF : CE_CLK_CTRL [19:16]
@ -31,13 +32,19 @@
* 1111 : Peripheral clock (sup_pclk set '1')
*/
struct sh_mmcif_dma {
struct sh_dmae_slave chan_priv_tx;
struct sh_dmae_slave chan_priv_rx;
};
struct sh_mmcif_plat_data {
void (*set_pwr)(struct platform_device *pdev, int state);
void (*down_pwr)(struct platform_device *pdev);
int (*get_cd)(struct platform_device *pdef);
u8 sup_pclk; /* 1 :SH7757, 0: SH7724/SH7372 */
unsigned long caps;
u32 ocr;
struct sh_mmcif_dma *dma;
u8 sup_pclk; /* 1 :SH7757, 0: SH7724/SH7372 */
unsigned long caps;
u32 ocr;
};
#define MMCIF_CE_CMD_SET 0x00000000