diff options
author | Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> | 2013-07-28 18:58:50 -0700 |
---|---|---|
committer | Mark Brown <broonie@linaro.org> | 2013-08-06 17:56:13 +0100 |
commit | 0a4d94c07ce782e645a8c0484d52221758b4c398 (patch) | |
tree | 06b9b94e4da866f625d5754a94dcd199687798f4 /sound/soc/sh | |
parent | 4b4dab82340d969521f4f86108441cb597c8595d (diff) | |
download | linux-0a4d94c07ce782e645a8c0484d52221758b4c398.tar.bz2 |
ASoC: rsnd: add common DMAEngine method
R-Car Sound driver will support DMA transfer in the future,
then, SSI/SRU/SRC will use it.
Current R-Car can't use soc-dmaengine-pcm.c since its DMAEngine
doesn't support dmaengine_prep_dma_cyclic(),
and SSI needs double plane transfer (which needs special submit) on DMAC.
This patch adds common DMAEngine method for it
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Signed-off-by: Mark Brown <broonie@linaro.org>
Diffstat (limited to 'sound/soc/sh')
-rw-r--r-- | sound/soc/sh/rcar/core.c | 132 | ||||
-rw-r--r-- | sound/soc/sh/rcar/rsnd.h | 32 |
2 files changed, 164 insertions, 0 deletions
diff --git a/sound/soc/sh/rcar/core.c b/sound/soc/sh/rcar/core.c index 420d6df9c3d0..a35706028514 100644 --- a/sound/soc/sh/rcar/core.c +++ b/sound/soc/sh/rcar/core.c @@ -174,6 +174,138 @@ void rsnd_mod_init(struct rsnd_priv *priv, } /* + * rsnd_dma functions + */ +static void rsnd_dma_continue(struct rsnd_dma *dma) +{ + /* push next A or B plane */ + dma->submit_loop = 1; + schedule_work(&dma->work); +} + +void rsnd_dma_start(struct rsnd_dma *dma) +{ + /* push both A and B plane*/ + dma->submit_loop = 2; + schedule_work(&dma->work); +} + +void rsnd_dma_stop(struct rsnd_dma *dma) +{ + dma->submit_loop = 0; + cancel_work_sync(&dma->work); + dmaengine_terminate_all(dma->chan); +} + +static void rsnd_dma_complete(void *data) +{ + struct rsnd_dma *dma = (struct rsnd_dma *)data; + struct rsnd_priv *priv = dma->priv; + unsigned long flags; + + rsnd_lock(priv, flags); + + dma->complete(dma); + + if (dma->submit_loop) + rsnd_dma_continue(dma); + + rsnd_unlock(priv, flags); +} + +static void rsnd_dma_do_work(struct work_struct *work) +{ + struct rsnd_dma *dma = container_of(work, struct rsnd_dma, work); + struct rsnd_priv *priv = dma->priv; + struct device *dev = rsnd_priv_to_dev(priv); + struct dma_async_tx_descriptor *desc; + dma_addr_t buf; + size_t len; + int i; + + for (i = 0; i < dma->submit_loop; i++) { + + if (dma->inquiry(dma, &buf, &len) < 0) + return; + + desc = dmaengine_prep_slave_single( + dma->chan, buf, len, dma->dir, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) { + dev_err(dev, "dmaengine_prep_slave_sg() fail\n"); + return; + } + + desc->callback = rsnd_dma_complete; + desc->callback_param = dma; + + if (dmaengine_submit(desc) < 0) { + dev_err(dev, "dmaengine_submit() fail\n"); + return; + } + + } + + dma_async_issue_pending(dma->chan); +} + +int rsnd_dma_available(struct rsnd_dma *dma) +{ + return !!dma->chan; +} + +static bool rsnd_dma_filter(struct dma_chan *chan, void *param) +{ + chan->private = param; + + return true; +} + +int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, + int is_play, int id, + int (*inquiry)(struct rsnd_dma *dma, + dma_addr_t *buf, int *len), + int (*complete)(struct rsnd_dma *dma)) +{ + struct device *dev = rsnd_priv_to_dev(priv); + dma_cap_mask_t mask; + + if (dma->chan) { + dev_err(dev, "it already has dma channel\n"); + return -EIO; + } + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + dma->slave.shdma_slave.slave_id = id; + + dma->chan = dma_request_channel(mask, rsnd_dma_filter, + &dma->slave.shdma_slave); + if (!dma->chan) { + dev_err(dev, "can't get dma channel\n"); + return -EIO; + } + + dma->dir = is_play ? DMA_TO_DEVICE : DMA_FROM_DEVICE; + dma->priv = priv; + dma->inquiry = inquiry; + dma->complete = complete; + INIT_WORK(&dma->work, rsnd_dma_do_work); + + return 0; +} + +void rsnd_dma_quit(struct rsnd_priv *priv, + struct rsnd_dma *dma) +{ + if (dma->chan) + dma_release_channel(dma->chan); + + dma->chan = NULL; +} + +/* * rsnd_dai functions */ #define rsnd_dai_call(rdai, io, fn) \ diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h index 9243e387104c..15dccd598960 100644 --- a/sound/soc/sh/rcar/rsnd.h +++ b/sound/soc/sh/rcar/rsnd.h @@ -13,9 +13,12 @@ #include <linux/clk.h> #include <linux/device.h> +#include <linux/dma-mapping.h> #include <linux/io.h> #include <linux/list.h> #include <linux/module.h> +#include <linux/sh_dma.h> +#include <linux/workqueue.h> #include <sound/rcar_snd.h> #include <sound/soc.h> #include <sound/pcm_params.h> @@ -79,6 +82,32 @@ void rsnd_bset(struct rsnd_priv *priv, struct rsnd_mod *mod, enum rsnd_reg reg, u32 mask, u32 data); /* + * R-Car DMA + */ +struct rsnd_dma { + struct rsnd_priv *priv; + struct sh_dmae_slave slave; + struct work_struct work; + struct dma_chan *chan; + enum dma_data_direction dir; + int (*inquiry)(struct rsnd_dma *dma, dma_addr_t *buf, int *len); + int (*complete)(struct rsnd_dma *dma); + + int submit_loop; +}; + +void rsnd_dma_start(struct rsnd_dma *dma); +void rsnd_dma_stop(struct rsnd_dma *dma); +int rsnd_dma_available(struct rsnd_dma *dma); +int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, + int is_play, int id, + int (*inquiry)(struct rsnd_dma *dma, dma_addr_t *buf, int *len), + int (*complete)(struct rsnd_dma *dma)); +void rsnd_dma_quit(struct rsnd_priv *priv, + struct rsnd_dma *dma); + + +/* * R-Car sound mod */ @@ -103,9 +132,12 @@ struct rsnd_mod { struct rsnd_priv *priv; struct rsnd_mod_ops *ops; struct list_head list; /* connect to rsnd_dai playback/capture */ + struct rsnd_dma dma; }; #define rsnd_mod_to_priv(mod) ((mod)->priv) +#define rsnd_mod_to_dma(mod) (&(mod)->dma) +#define rsnd_dma_to_mod(_dma) container_of((_dma), struct rsnd_mod, dma) #define rsnd_mod_id(mod) ((mod)->id) #define for_each_rsnd_mod(pos, n, io) \ list_for_each_entry_safe(pos, n, &(io)->head, list) |