Skip to content

Commit

Permalink
mmc: sh_mmcif: add DMA support
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Guennadi Liakhovetski authored and Paul Mundt committed Nov 25, 2010
1 parent e47bf32 commit a782d68
Show file tree
Hide file tree
Showing 3 changed files with 258 additions and 9 deletions.
6 changes: 6 additions & 0 deletions drivers/mmc/host/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
246 changes: 241 additions & 5 deletions drivers/mmc/host/sh_mmcif.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand All @@ -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)
{
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
15 changes: 11 additions & 4 deletions include/linux/mmc/sh_mmcif.h
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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
Expand Down

0 comments on commit a782d68

Please sign in to comment.