summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRussell King <rmk+kernel@arm.linux.org.uk>2012-01-18 13:01:26 +0000
committerRussell King (Oracle) <rmk+kernel@armlinux.org.uk>2024-01-08 10:29:13 +0000
commit8123fe54c72e13f6f7d30df23e72158e4612d37f (patch)
tree019dd7cd07fb6080cb699c983b8d982dd14d9ec3
parent7a3f95de4ec072f50b7088e1a52536f8dfd83572 (diff)
ALSA: ASoC: add generic slave and cyclic DMA engine support
Add a generic DMA engine platform driver to ASoC. This allows a range of DMA engines to be used to transfer data to a CPUs audio interface without having to rewrite this code many times. This driver supports both DMA_SLAVE and DMA_CYCLIC channels, preferring DMA_CYCLIC channels over DMA_SLAVE channels. Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
-rw-r--r--include/sound/soc-dmaengine.h26
-rw-r--r--sound/soc/Kconfig6
-rw-r--r--sound/soc/Makefile2
-rw-r--r--sound/soc/soc-dmaengine.c591
4 files changed, 625 insertions, 0 deletions
diff --git a/include/sound/soc-dmaengine.h b/include/sound/soc-dmaengine.h
new file mode 100644
index 000000000000..b435133de04d
--- /dev/null
+++ b/include/sound/soc-dmaengine.h
@@ -0,0 +1,26 @@
+/*
+ * Generic ASoC DMA engine backend
+ *
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _LINUX_SND_SOC_DMAENGINE_H
+#define _LINUX_SND_SOC_DMAENGINE_H
+
+#include <linux/dmaengine.h>
+
+struct soc_dma_config {
+ dma_filter_fn filter;
+ void *data;
+
+ dma_addr_t reg;
+ enum dma_slave_buswidth width;
+ u32 maxburst;
+ size_t align;
+ size_t fifo_size;
+};
+
+#endif
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index 439fa631c342..e2cd4d8b2dcb 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -26,6 +26,9 @@ if SND_SOC
config SND_SOC_AC97_BUS
bool
+config SND_SOC_DMAENGINE_PCM
+ bool
+
config SND_SOC_GENERIC_DMAENGINE_PCM
bool
select SND_DMAENGINE_PCM
@@ -34,6 +37,9 @@ config SND_SOC_COMPRESS
bool
select SND_COMPRESS_OFFLOAD
+config SND_SOC_DMAENGINE
+ tristate
+
config SND_SOC_TOPOLOGY
bool
select SND_DYNAMIC_MINORS
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 8376fdb217ed..0277ea5cc842 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -2,6 +2,7 @@
snd-soc-core-objs := soc-core.o soc-dapm.o soc-jack.o soc-utils.o soc-dai.o soc-component.o
snd-soc-core-objs += soc-pcm.o soc-devres.o soc-ops.o soc-link.o soc-card.o
snd-soc-core-$(CONFIG_SND_SOC_COMPRESS) += soc-compress.o
+snd-soc-dmaengine-objs := soc-dmaengine.o
ifneq ($(CONFIG_SND_SOC_TOPOLOGY),)
snd-soc-core-objs += soc-topology.o
@@ -32,6 +33,7 @@ endif
obj-$(CONFIG_SND_SOC_ACPI) += snd-soc-acpi.o
obj-$(CONFIG_SND_SOC) += snd-soc-core.o
+obj-$(CONFIG_SND_SOC_DMAENGINE) += snd-soc-dmaengine.o
obj-$(CONFIG_SND_SOC) += codecs/
obj-$(CONFIG_SND_SOC) += generic/
obj-$(CONFIG_SND_SOC) += apple/
diff --git a/sound/soc/soc-dmaengine.c b/sound/soc/soc-dmaengine.c
new file mode 100644
index 000000000000..4b1bfce9e641
--- /dev/null
+++ b/sound/soc/soc-dmaengine.c
@@ -0,0 +1,591 @@
+/*
+ * Generic ASoC DMA engine backend
+ *
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * We expect the DMA engine to give accurate residue information,
+ * returning the number of bytes left to complete to the requested
+ * cookie. We also expect the DMA engine to be able to resume
+ * submitted descriptors after a suspend/resume cycle.
+ */
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include <sound/core.h>
+#include <sound/soc-dmaengine.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#define BUFFER_SIZE_MAX 65536
+#define PERIOD_SIZE_MIN 32
+#define PERIOD_SIZE_MAX 16384
+#define PERIODS_MIN 2
+#define PERIODS_MAX 256
+
+struct buf_info {
+ struct scatterlist sg;
+ dma_cookie_t cookie;
+};
+
+struct soc_dma_chan {
+ const struct soc_dma_config *conf;
+ spinlock_t lock;
+ struct dma_chan *chan;
+ struct dma_slave_config cfg;
+ enum dma_transfer_direction dir;
+ unsigned nr_periods;
+ unsigned sg_index;
+ unsigned stopped;
+ unsigned cyclic;
+ dma_cookie_t cyclic_cookie;
+ struct buf_info buf[PERIODS_MAX];
+};
+
+struct soc_dma_info {
+ struct soc_dma_chan *chan[2];
+};
+
+static const struct snd_pcm_hardware soc_dmaengine_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME,
+ .period_bytes_min = PERIOD_SIZE_MIN,
+ .period_bytes_max = PERIOD_SIZE_MAX,
+ .periods_min = PERIODS_MIN,
+ .periods_max = PERIODS_MAX,
+ .buffer_bytes_max = BUFFER_SIZE_MAX,
+};
+
+static int soc_dmaengine_submit(struct snd_pcm_substream *substream,
+ struct soc_dma_chan *dma);
+
+static void soc_dmaengine_callback(void *data)
+{
+ struct snd_pcm_substream *substream = data;
+ struct soc_dma_chan *dma = substream->runtime->private_data;
+ int ret;
+
+ snd_pcm_period_elapsed(substream);
+
+ if (!dma->stopped && !dma->cyclic) {
+ spin_lock(&dma->lock);
+ ret = soc_dmaengine_submit(substream, dma);
+ spin_unlock(&dma->lock);
+
+ if (ret == 0)
+ dma_async_issue_pending(dma->chan);
+ else
+ pr_err("%s: failed to submit next DMA buffer\n", __func__);
+ }
+}
+
+static int soc_dmaengine_submit(struct snd_pcm_substream *substream,
+ struct soc_dma_chan *dma)
+{
+ struct dma_async_tx_descriptor *tx;
+ struct dma_chan *ch = dma->chan;
+ struct buf_info *buf;
+ unsigned sg_index;
+
+ sg_index = dma->sg_index;
+
+ buf = &dma->buf[sg_index];
+
+ tx = dmaengine_prep_slave_sg(ch, &buf->sg, 1, dma->dir,
+ DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+ if (tx) {
+ tx->callback = soc_dmaengine_callback;
+ tx->callback_param = substream;
+
+ buf->cookie = dmaengine_submit(tx);
+
+ sg_index++;
+ if (sg_index >= dma->nr_periods)
+ sg_index = 0;
+ dma->sg_index = sg_index;
+ }
+
+ return tx ? 0 : -ENOMEM;
+}
+
+static int soc_dmaengine_start(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct soc_dma_chan *dma = runtime->private_data;
+ unsigned long flags;
+ unsigned i;
+ int ret = 0;
+
+ if (!dma->cyclic) {
+ spin_lock_irqsave(&dma->lock, flags);
+ for (i = 0; i < dma->nr_periods; i++) {
+ ret = soc_dmaengine_submit(substream, dma);
+ if (ret)
+ break;
+ }
+ spin_unlock_irqrestore(&dma->lock, flags);
+ if (ret == 0) {
+ dma->stopped = 0;
+ dma_async_issue_pending(dma->chan);
+ } else {
+ dma->stopped = 1;
+ dmaengine_terminate_all(dma->chan);
+ }
+ } else {
+ struct dma_async_tx_descriptor *tx;
+
+ tx = dmaengine_prep_dma_cyclic(dma->chan, runtime->dma_addr,
+ snd_pcm_lib_buffer_bytes(substream),
+ snd_pcm_lib_period_bytes(substream), dma->dir,
+ DMA_CTRL_ACK | DMA_PREP_INTERRUPT);
+ if (tx) {
+ tx->callback = soc_dmaengine_callback;
+ tx->callback_param = substream;
+
+ dma->cyclic_cookie = dmaengine_submit(tx);
+ dma_async_issue_pending(dma->chan);
+ }
+ }
+
+ return ret;
+}
+
+static int soc_dmaengine_stop(struct snd_pcm_substream *substream)
+{
+ struct soc_dma_chan *dma = substream->runtime->private_data;
+
+ dma->stopped = 1;
+ dmaengine_terminate_all(dma->chan);
+
+ return 0;
+}
+
+static int soc_dmaengine_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct soc_dma_info *info = snd_soc_platform_get_drvdata(rtd->platform);
+ struct soc_dma_chan *dma = info->chan[substream->stream];
+ int ret = 0;
+
+ if (!dma)
+ return -EINVAL;
+
+ runtime->hw = soc_dmaengine_hardware;
+ runtime->hw.fifo_size = dma->conf->fifo_size;
+
+ if (dma->conf->align) {
+ /*
+ * FIXME: Ideally, there should be some way to query
+ * this from the DMA engine itself.
+ *
+ * It would also be helpful to know the maximum size
+ * a scatterlist entry can be to set the period size.
+ */
+ ret = snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_BYTES, dma->conf->align);
+ if (ret)
+ goto err;
+
+ ret = snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_BUFFER_BYTES, dma->conf->align);
+ if (ret)
+ goto err;
+ }
+
+ runtime->private_data = dma;
+
+ err:
+ return ret;
+}
+
+static int soc_dmaengine_close(struct snd_pcm_substream *substream)
+{
+ return 0;
+}
+
+static int soc_dmaengine_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ int ret = snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(params));
+
+ return ret < 0 ? ret : 0;
+}
+
+static int soc_dmaengine_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct soc_dma_chan *dma = runtime->private_data;
+ size_t buf_size = snd_pcm_lib_buffer_bytes(substream);
+ size_t period = snd_pcm_lib_period_bytes(substream);
+ dma_addr_t addr = runtime->dma_addr;
+ unsigned i;
+
+ /* Create an array of sg entries, one for each period */
+ for (i = 0; i < PERIODS_MAX && buf_size; i++) {
+ BUG_ON(buf_size < period);
+
+ sg_dma_address(&dma->buf[i].sg) = addr;
+ sg_dma_len(&dma->buf[i].sg) = period;
+
+ addr += period;
+ buf_size -= period;
+ }
+
+ if (buf_size) {
+ pr_err("DMA buffer size not a multiple of the period size: residue=%zu\n", buf_size);
+ return -EINVAL;
+ }
+
+ dma->nr_periods = i;
+ dma->sg_index = 0;
+
+ return 0;
+}
+
+static int soc_dmaengine_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct soc_dma_chan *dma = runtime->private_data;
+ int ret;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ ret = soc_dmaengine_start(substream);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ ret = soc_dmaengine_stop(substream);
+ break;
+
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ ret = dmaengine_pause(dma->chan);
+ break;
+
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ ret = dmaengine_resume(dma->chan);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static snd_pcm_uframes_t soc_dmaengine_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct soc_dma_chan *dma = runtime->private_data;
+ struct dma_chan *ch = dma->chan;
+ struct dma_tx_state state;
+ unsigned bytes;
+
+ if (dma->cyclic) {
+ ch->device->device_tx_status(ch, dma->cyclic_cookie, &state);
+
+ bytes = snd_pcm_lib_buffer_bytes(substream) - state.residue;
+ } else {
+ unsigned index, pos;
+ size_t period = snd_pcm_lib_period_bytes(substream);
+ unsigned long flags;
+ enum dma_status status;
+ dma_cookie_t cookie;
+
+ /*
+ * Get the next-to-be-submitted index, and the last submitted
+ * cookie value. We use this to obtain the DMA engine state.
+ */
+ spin_lock_irqsave(&dma->lock, flags);
+ index = dma->sg_index;
+ do {
+ cookie = dma->buf[index].cookie;
+ status = ch->device->device_tx_status(ch, cookie, &state);
+ if (status == DMA_SUCCESS) {
+ index++;
+ if (index > dma->nr_periods)
+ index = 0;
+ }
+ } while (status == DMA_SUCCESS);
+ spin_unlock_irqrestore(&dma->lock, flags);
+
+ /* The end of the current DMA buffer */
+ pos = (index + 1) * period;
+ /* Now take off the residue */
+ bytes = pos - state.residue;
+ /* And correct for wrap-around */
+ if (bytes >= period * dma->nr_periods)
+ bytes -= period * dma->nr_periods;
+ }
+
+ return bytes_to_frames(runtime, bytes);
+}
+
+static int soc_dmaengine_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_dma_buffer *buf = runtime->dma_buffer_p;
+
+ return dma_mmap_writecombine(buf->dev.dev, vma,
+ runtime->dma_area, runtime->dma_addr, runtime->dma_bytes);
+}
+
+static struct snd_pcm_ops soc_dmaengine_ops = {
+ .open = soc_dmaengine_open,
+ .close = soc_dmaengine_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = soc_dmaengine_hw_params,
+ .hw_free = snd_pcm_lib_free_pages,
+ .prepare = soc_dmaengine_prepare,
+ .trigger = soc_dmaengine_trigger,
+ .pointer = soc_dmaengine_pointer,
+ .mmap = soc_dmaengine_mmap,
+};
+
+static struct soc_dma_chan *soc_dmaengine_alloc(void)
+{
+ struct soc_dma_chan *dma = kzalloc(sizeof(*dma), GFP_KERNEL);
+ unsigned i;
+
+ if (dma) {
+ spin_lock_init(&dma->lock);
+ for (i = 0; i < PERIODS_MAX; i++)
+ sg_init_table(&dma->buf[i].sg, 1);
+ }
+ return dma;
+}
+
+static struct dma_chan *soc_dmaengine_get(enum dma_transaction_type type,
+ struct soc_dma_config *cfg)
+{
+ dma_cap_mask_t m;
+
+ dma_cap_zero(m);
+ dma_cap_set(type, m);
+ return dma_request_channel(m, cfg->filter, cfg->data);
+}
+
+static int soc_dmaengine_request(struct soc_dma_chan *dma,
+ struct soc_dma_config *cfg, unsigned stream)
+{
+ int ret;
+
+ dma->conf = cfg;
+ dma->chan = soc_dmaengine_get(DMA_CYCLIC, cfg);
+ if (!dma->chan)
+ dma->chan = soc_dmaengine_get(DMA_SLAVE, cfg);
+ else
+ dma->cyclic = 1;
+ if (!dma->chan) {
+ ret = -ENXIO;
+ goto err_dma_req;
+ }
+
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ dma->dir = DMA_MEM_TO_DEV;
+ dma->cfg.direction = DMA_MEM_TO_DEV;
+ dma->cfg.dst_addr = cfg->reg;
+ dma->cfg.dst_addr_width = cfg->width;
+ dma->cfg.dst_maxburst = cfg->maxburst;
+ } else {
+ dma->dir = DMA_DEV_TO_MEM;
+ dma->cfg.direction = DMA_DEV_TO_MEM;
+ dma->cfg.src_addr = cfg->reg;
+ dma->cfg.src_addr_width = cfg->width;
+ dma->cfg.src_maxburst = cfg->maxburst;
+ }
+
+ ret = dmaengine_slave_config(dma->chan, &dma->cfg);
+ if (ret)
+ goto err_dma_cfg;
+
+ return 0;
+
+ err_dma_cfg:
+ dma_release_channel(dma->chan);
+ dma->chan = NULL;
+ err_dma_req:
+ return ret;
+}
+
+static void soc_dmaengine_release(struct soc_dma_chan *dma)
+{
+ if (dma && dma->chan)
+ dma_release_channel(dma->chan);
+ kfree(dma);
+}
+
+static int soc_dmaengine_preallocate_buffer(struct snd_pcm *pcm,
+ unsigned stream, struct soc_dma_chan *dma)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ int ret = 0;
+
+ if (substream) {
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = dma->chan->device->dev;
+ buf->private_data = NULL;
+ buf->area = dma_alloc_writecombine(buf->dev.dev,
+ BUFFER_SIZE_MAX, &buf->addr, GFP_KERNEL);
+ if (!buf->area)
+ return -ENOMEM;
+
+ buf->bytes = BUFFER_SIZE_MAX;
+ }
+ return ret;
+}
+
+static int soc_dmaengine_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_pcm *pcm = rtd->pcm;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct soc_dma_info *info;
+ unsigned stream;
+ int ret = 0;
+
+ if (!cpu_dai)
+ return -EINVAL;
+
+ if (!cpu_dai->playback_dma_data &&
+ !cpu_dai->capture_dma_data) {
+ pr_err("soc_dmaengine: %s has no cpu_dai DMA data - incorrect probe ordering?\n",
+ rtd->card->name);
+ return -EINVAL;
+ }
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ for (stream = 0; stream < ARRAY_SIZE(pcm->streams); stream++) {
+ struct soc_dma_config *cfg = NULL;
+ struct soc_dma_chan *dma;
+
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+ cfg = cpu_dai->playback_dma_data;
+ else if (stream == SNDRV_PCM_STREAM_CAPTURE)
+ cfg = cpu_dai->capture_dma_data;
+
+ if (!cfg)
+ continue;
+
+ info->chan[stream] = dma = soc_dmaengine_alloc();
+ if (!dma) {
+ ret = -ENOMEM;
+ break;
+ }
+
+ ret = soc_dmaengine_request(dma, cfg, stream);
+ if (ret)
+ return ret;
+
+ ret = soc_dmaengine_preallocate_buffer(pcm, stream, dma);
+ if (ret)
+ break;
+ }
+
+ if (ret) {
+ for (stream = 0; stream < ARRAY_SIZE(info->chan); stream++)
+ soc_dmaengine_release(info->chan[stream]);
+ kfree(info);
+ } else {
+ snd_soc_platform_set_drvdata(rtd->platform, info);
+ }
+
+ return ret;
+}
+
+/*
+ * Use write-combining memory here: the standard ALSA
+ * snd_free_dev_pages() doesn't support this.
+ */
+static void soc_dmaengine_pcm_free(struct snd_pcm *pcm)
+{
+ unsigned stream;
+
+ for (stream = 0; stream < ARRAY_SIZE(pcm->streams); stream++) {
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf;
+
+ if (!substream)
+ continue;
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+
+ if (buf->dev.type == SNDRV_DMA_TYPE_DEV)
+ dma_free_writecombine(buf->dev.dev, buf->bytes,
+ buf->area, buf->addr);
+ else
+ snd_dma_free_pages(buf);
+ buf->area = NULL;
+ }
+}
+
+/*
+ * This is annoying - we can't have symetry with .pcm_new because .pcm_free
+ * is called after the runtime information has been removed. It would be
+ * better if we could find somewhere else to store our soc_dma_info pointer.
+ */
+static int soc_dmaengine_plat_remove(struct snd_soc_platform *platform)
+{
+ struct soc_dma_info *info = snd_soc_platform_get_drvdata(platform);
+ unsigned stream;
+
+ for (stream = 0; stream < ARRAY_SIZE(info->chan); stream++)
+ soc_dmaengine_release(info->chan[stream]);
+ kfree(info);
+
+ return 0;
+}
+
+static struct snd_soc_platform_driver soc_dmaengine_platform = {
+ .remove = soc_dmaengine_plat_remove,
+ .pcm_new = soc_dmaengine_pcm_new,
+ .pcm_free = soc_dmaengine_pcm_free,
+ .ops = &soc_dmaengine_ops,
+ /* Wait until the cpu_dai has been probed */
+ .probe_order = SND_SOC_COMP_ORDER_LATE,
+};
+
+static int soc_dmaengine_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_platform(&pdev->dev, &soc_dmaengine_platform);
+}
+
+static int soc_dmaengine_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver soc_dmaengine_driver = {
+ .driver = {
+ .name = "soc-dmaengine",
+ .owner = THIS_MODULE,
+ },
+ .probe = soc_dmaengine_probe,
+ .remove = soc_dmaengine_remove,
+};
+
+module_platform_driver(soc_dmaengine_driver);
+
+MODULE_AUTHOR("Russell King");
+MODULE_DESCRIPTION("ALSA SoC DMA engine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:soc-dmaengine");