diff mbox

[v2,5/9] ASoC: sti: Add platform driver

Message ID 1431951176-24670-6-git-send-email-arnaud.pouliquen@st.com (mailing list archive)
State New, archived
Headers show

Commit Message

Arnaud POULIQUEN May 18, 2015, 12:12 p.m. UTC
Asoc Platform driver that manages uniperipheral DAIs and associated
PCM stream.

Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen@st.com>
---
 sound/soc/sti/sti_platform.c | 642 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 642 insertions(+)
 create mode 100644 sound/soc/sti/sti_platform.c

Comments

Mark Brown May 25, 2015, 12:50 p.m. UTC | #1
On Mon, May 18, 2015 at 02:12:52PM +0200, Arnaud Pouliquen wrote:

> Asoc Platform driver that manages uniperipheral DAIs and associated

ASoC.

> +struct sti_platform_dai {
> +	int stream;
> +	int (*init)(struct platform_device *pdev, struct device_node *node,
> +		    struct uniperif **uni, int idx);
> +	int (*remove)(struct platform_device *pdev);
> +	struct uniperif *uni;
> +	struct snd_dmaengine_dai_dma_data dma_data;
> +};

Again a driver internal abstraction layer...

> +	switch (cmd) {
> +	case SNDRV_PCM_TRIGGER_START:
> +	case SNDRV_PCM_TRIGGER_RESUME:
> +	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
> +	if (uni->ops->trigger)
> +		return uni->ops->trigger(uni, SNDRV_PCM_TRIGGER_START);

Coding style.

> +	return snd_dmaengine_pcm_prepare_slave_config(substream, params,
> +						     &dma_params->slave_config);

You're using dmaengine but at least one of the earlier patches supplied
a snd_pcm_hardware - the dmaengine code should be able to figure out
constraints from the dmaengine driver in current code, is that causing
you problems?

> +static int sti_pcm_trigger(struct snd_pcm_substream *substream,
> +			   int cmd)
> +{
> +	struct snd_soc_pcm_runtime *rtd = substream->private_data;
> +	struct snd_pcm_runtime *runtime = substream->runtime;
> +	struct sti_pcm_dma_params *params = runtime->private_data;
> +	enum dma_transfer_direction direction;
> +	struct dma_async_tx_descriptor *desc;
> +	unsigned long flags = DMA_CTRL_ACK;
> +	int ret = 0;
> +
> +	switch (cmd) {
> +	case SNDRV_PCM_TRIGGER_START:
> +	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
> +	case SNDRV_PCM_TRIGGER_RESUME:
> +		direction = snd_pcm_substream_to_dma_direction(substream);
> +
> +		desc = dmaengine_prep_dma_cyclic(
> +				params->dma_channel,	runtime->dma_addr,
> +				snd_pcm_lib_buffer_bytes(substream),
> +				snd_pcm_lib_period_bytes(substream),
> +				direction, flags);

Can you use snd_dmaengine_pcm_trigger?

> +static snd_pcm_uframes_t sti_pcm_pointer(struct snd_pcm_substream *substream)
> +{

snd_dmaengine_pcm_pointer()?

> +static int sti_platform_engine_probe(struct platform_device *pdev)
> +{
> +	/*
> +	 * Driver can not use snd_dmaengine_pcm_register.
> +	 * Reason is that DMA needs to load a firmware.
> +	 * This firmware is loaded on request_channel from file system.
> +	 * So can not be done in probe while alsa card enumerated before
> +	 * file system is mounted
> +	 */

This is not the only driver with similar needs, the Intel drivers use
firmwares as well (all the DMA is done by the DSP which needs firmware)
and even if it were we should be able to arrange for this to work rather
than having to open code things.  That might mean having the dmaengine
driver requesting firmware with _nowait() and then waiting until the
firmware appears before registering as a DMA controller for example.  We
may also be able to have the DMA engine only load the firmware when it's
used rather than at probe (that might allow us to keep the DMA
controller powered off for longer which would be a power win if possible).

It's not just restricted to audio either.

> +static const struct of_device_id snd_soc_sti_match[] = {
> +	{ .compatible = "st,sti-audio-platform", },
> +	{},
> +};

This doesn't seem to represent actual hardware, it seems to be an
abstraction layer between dmaengine and your DAI drivers.  This is fine
and normal but it means the driver shouldn't appear in DT.  Take a look
at how other drivers like Tegra or the Freescale ones handle this.
Arnaud POULIQUEN May 27, 2015, 8:48 a.m. UTC | #2
On 05/25/2015 02:50 PM, Mark Brown wrote:
> On Mon, May 18, 2015 at 02:12:52PM +0200, Arnaud Pouliquen wrote:
>> +	return snd_dmaengine_pcm_prepare_slave_config(substream, params,
>> +						     &dma_params->slave_config);
>
> You're using dmaengine but at least one of the earlier patches supplied
> a snd_pcm_hardware - the dmaengine code should be able to figure out
> constraints from the dmaengine driver in current code, is that causing
> you problems?
>
>> +static int sti_pcm_trigger(struct snd_pcm_substream *substream,
>> +			   int cmd)
>> +{
>> +	struct snd_soc_pcm_runtime *rtd = substream->private_data;
>> +	struct snd_pcm_runtime *runtime = substream->runtime;
>> +	struct sti_pcm_dma_params *params = runtime->private_data;
>> +	enum dma_transfer_direction direction;
>> +	struct dma_async_tx_descriptor *desc;
>> +	unsigned long flags = DMA_CTRL_ACK;
>> +	int ret = 0;
>> +
>> +	switch (cmd) {
>> +	case SNDRV_PCM_TRIGGER_START:
>> +	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
>> +	case SNDRV_PCM_TRIGGER_RESUME:
>> +		direction = snd_pcm_substream_to_dma_direction(substream);
>> +
>> +		desc = dmaengine_prep_dma_cyclic(
>> +				params->dma_channel,	runtime->dma_addr,
>> +				snd_pcm_lib_buffer_bytes(substream),
>> +				snd_pcm_lib_period_bytes(substream),
>> +				direction, flags);
>
> Can you use snd_dmaengine_pcm_trigger?
>
>> +static snd_pcm_uframes_t sti_pcm_pointer(struct snd_pcm_substream *substream)
>> +{
>
> snd_dmaengine_pcm_pointer()?
>
As dmaengine_pcm_runtime_data is static, in this case i need to use also 
all the other ops function. i tried this before without success.
But as i don't remember the reason , I will test it again...

>> +static int sti_platform_engine_probe(struct platform_device *pdev)
>> +{
>> +	/*
>> +	 * Driver can not use snd_dmaengine_pcm_register.
>> +	 * Reason is that DMA needs to load a firmware.
>> +	 * This firmware is loaded on request_channel from file system.
>> +	 * So can not be done in probe while alsa card enumerated before
>> +	 * file system is mounted
>> +	 */
>
> This is not the only driver with similar needs, the Intel drivers use
> firmwares as well (all the DMA is done by the DSP which needs firmware)
> and even if it were we should be able to arrange for this to work rather
> than having to open code things.  That might mean having the dmaengine
> driver requesting firmware with _nowait() and then waiting until the
> firmware appears before registering as a DMA controller for example.  We
> may also be able to have the DMA engine only load the firmware when it's
> used rather than at probe (that might allow us to keep the DMA
> controller powered off for longer which would be a power win if possible).
>
> It's not just restricted to audio either.
>

Move dmaengine_pcm_request_chan from register to open could be a good 
compromise with (i hope) no impact on drivers that already use 
soc_generic_dma_engine. This is the implementation already proposed in 
of pcm_dmaengine.c
Another issue i'm facing, is that i have one DMA channel per CPU_DAI 
instance. As simple card imposes to define CPU_DAIs in one platform 
device, i need to define specific DMA name per CPU_DAI.
What is your recommendation on this point:
Do you want that i use snd_dmaengine_pcm_register and propose 
soc_gebneric_dmaengine driver patches?
or simply reuse pcm_dmaengine helper functions as much as possible.

>> +static const struct of_device_id snd_soc_sti_match[] = {
>> +	{ .compatible = "st,sti-audio-platform", },
>> +	{},
>> +};
>
> This doesn't seem to represent actual hardware, it seems to be an
> abstraction layer between dmaengine and your DAI drivers.  This is fine
> and normal but it means the driver shouldn't appear in DT.  Take a look
> at how other drivers like Tegra or the Freescale ones handle this.
>
No sure to full understand your comment...
I checked some Tegra and Freescale drivers,look similar...with 
declaration in DT.
Should i rename file and compatible name associated to DAI name
as example sti_uniperif.c  and .compatible = "st,sti-uniperif"
Lars-Peter Clausen May 27, 2015, 12:06 p.m. UTC | #3
On 05/27/2015 10:48 AM, Arnaud Pouliquen wrote:
[...]
>>> +static int sti_platform_engine_probe(struct platform_device *pdev)
>>> +{
>>> +    /*
>>> +     * Driver can not use snd_dmaengine_pcm_register.
>>> +     * Reason is that DMA needs to load a firmware.
>>> +     * This firmware is loaded on request_channel from file system.
>>> +     * So can not be done in probe while alsa card enumerated before
>>> +     * file system is mounted
>>> +     */
>>
>> This is not the only driver with similar needs, the Intel drivers use
>> firmwares as well (all the DMA is done by the DSP which needs firmware)
>> and even if it were we should be able to arrange for this to work rather
>> than having to open code things.  That might mean having the dmaengine
>> driver requesting firmware with _nowait() and then waiting until the
>> firmware appears before registering as a DMA controller for example.  We
>> may also be able to have the DMA engine only load the firmware when it's
>> used rather than at probe (that might allow us to keep the DMA
>> controller powered off for longer which would be a power win if possible).
>>
>> It's not just restricted to audio either.
>>
>
> Move dmaengine_pcm_request_chan from register to open could be a good
> compromise with (i hope) no impact on drivers that already use
> soc_generic_dma_engine. This is the implementation already proposed in of
> pcm_dmaengine.c
> Another issue i'm facing, is that i have one DMA channel per CPU_DAI
> instance. As simple card imposes to define CPU_DAIs in one platform device,
> i need to define specific DMA name per CPU_DAI.
> What is your recommendation on this point:
> Do you want that i use snd_dmaengine_pcm_register and propose
> soc_gebneric_dmaengine driver patches?
> or simply reuse pcm_dmaengine helper functions as much as possible.

You really want to request the DMA channels in the probe callback of the 
driver (and also use the the generic-dmaengine-pcm driver). This makes sure 
that you have proper support for probe deferral, etc. The DMA driver should 
probably not request the firmware inside the channel_request() callback. As 
Mark sad this will not only affect audio, but all usecases that involve 
DMAengine.

Btw. what is the DMA driver you are using, is it already upstream?

- Lars
diff mbox

Patch

diff --git a/sound/soc/sti/sti_platform.c b/sound/soc/sti/sti_platform.c
new file mode 100644
index 0000000..66466e8
--- /dev/null
+++ b/sound/soc/sti/sti_platform.c
@@ -0,0 +1,642 @@ 
+/*
+ * Copyright (C) STMicroelectronics SA 2015
+ * Authors: Arnaud Pouliquen <arnaud.pouliquen@st.com>
+ *          for STMicroelectronics.
+ * License terms:  GNU General Public License (GPL), version 2
+ */
+
+#include <linux/module.h>
+
+#include <sound/dmaengine_pcm.h>
+
+#include "uniperif.h"
+
+/*
+ * Max buffering use case identified:
+ * 3 periods of 2048 frames @ 192kHz, 32 bits, 10 ch
+ */
+#define STI_PLATFORM_PERIODS_BYTES_MAX	196608
+#define STI_PLATFORM_PERIODS_MAX	3
+#define STI_PLATFORM_BUFFER_BYTES_MAX	(STI_PLATFORM_PERIODS_BYTES_MAX * \
+					 STI_PLATFORM_PERIODS_MAX)
+
+#define priv_to_dai_data(priv, i) ((priv)->dai_data + i)
+
+struct sti_platform_dai {
+	int stream;
+	int (*init)(struct platform_device *pdev, struct device_node *node,
+		    struct uniperif **uni, int idx);
+	int (*remove)(struct platform_device *pdev);
+	struct uniperif *uni;
+	struct snd_dmaengine_dai_dma_data dma_data;
+};
+
+struct sti_platform_dai uni_player = {
+	.stream = SNDRV_PCM_STREAM_PLAYBACK,
+	.init = uni_player_init,
+	.remove = uni_player_remove,
+};
+
+struct sti_platform_dai uni_reader = {
+	.stream = SNDRV_PCM_STREAM_CAPTURE,
+	.init = uni_reader_init,
+	.remove = uni_reader_remove,
+};
+
+struct sti_platform_data {
+	struct platform_device *pdev;
+	struct snd_soc_dai_driver *dai;
+	struct sti_platform_dai dai_data[]; /* dynamically allocated */
+};
+
+struct sti_pcm_dma_params {
+	struct dma_chan *dma_channel;
+	dma_cookie_t dma_cookie;
+	struct dma_slave_config slave_config;
+};
+
+/*
+ * sti_platform_dai_create_ctrl
+ * This function is used to create Ctrl associated to DAI but also pcm device.
+ * Request is done by front end to associate ctrl with pcm device id
+ */
+int sti_platform_dai_create_ctrl(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_dai *dai = rtd->cpu_dai;
+	struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai);
+	struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id);
+	struct uniperif *uni = dai_data->uni;
+	struct snd_kcontrol_new *ctrl;
+	int i, ret = 0;
+
+	for (i = 0; i < uni->num_ctrls; i++) {
+		ctrl = &uni->snd_ctrls[i];
+		ctrl->index = rtd->pcm->device;
+		ctrl->device = rtd->pcm->device;
+
+		ret = snd_ctl_add(dai->component->card->snd_card,
+				  snd_ctl_new1(ctrl, uni));
+		if (ret < 0) {
+			dev_err(dai->dev, "%s: Failed to add %s: %d\n",
+				__func__, ctrl->name, ret);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * DAI
+ */
+
+static int sti_platform_dai_startup(struct snd_pcm_substream *substream,
+				    struct snd_soc_dai *dai)
+{
+	struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai);
+	struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id);
+	struct uniperif *uni = dai_data->uni;
+
+	if (uni->ops->open)
+		return uni->ops->open(uni);
+	return 0;
+}
+
+static void sti_platform_dai_shutdown(struct snd_pcm_substream *substream,
+				      struct snd_soc_dai *dai)
+{
+	struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai);
+	struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id);
+	struct uniperif *uni = dai_data->uni;
+
+	if (uni->ops->close)
+		uni->ops->close(uni);
+}
+
+static int sti_platform_dai_prepare(struct snd_pcm_substream *substream,
+				    struct snd_soc_dai *dai)
+{
+	struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai);
+	struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id);
+	struct uniperif *uni = dai_data->uni;
+
+	if (uni->ops->prepare)
+		return uni->ops->prepare(uni, substream->runtime);
+
+	return 0;
+}
+
+static int sti_platform_dai_trigger(struct snd_pcm_substream *substream,
+				    int cmd, struct snd_soc_dai *dai)
+{
+	struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai);
+	struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id);
+	struct uniperif *uni = dai_data->uni;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+	if (uni->ops->trigger)
+		return uni->ops->trigger(uni, SNDRV_PCM_TRIGGER_START);
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	if (uni->ops->trigger)
+		return uni->ops->trigger(uni, SNDRV_PCM_TRIGGER_STOP);
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int sti_platform_dai_hw_params(struct snd_pcm_substream *substream,
+				      struct snd_pcm_hw_params *params,
+				      struct snd_soc_dai *dai)
+{
+	struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai);
+	struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id);
+	struct snd_dmaengine_dai_dma_data *dma_data = &dai_data->dma_data;
+	int transfer_size;
+
+	transfer_size = params_channels(params) * UNIPERIF_FIFO_FRAMES;
+
+	dma_data = snd_soc_dai_get_dma_data(dai, substream);
+	dma_data->maxburst = transfer_size;
+
+	return 0;
+}
+
+static int sti_platform_dai_set_fmt(struct snd_soc_dai *dai,
+				    unsigned int fmt)
+{
+	struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai);
+	struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id);
+
+	dai_data->uni->daifmt = fmt;
+	return 0;
+}
+
+static int sti_platform_dai_set_clkdiv(struct snd_soc_dai *dai, int div_id,
+				       int div)
+{
+	struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai);
+	struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id);
+
+	dai_data->uni->clk_div = div;
+
+	return 0;
+}
+
+static int sti_platform_dai_suspend(struct snd_soc_dai *dai)
+{
+	struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai);
+	struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id);
+	struct uniperif *uni = dai_data->uni;
+
+	if (uni->ops->trigger)
+		return uni->ops->trigger(uni, SNDRV_PCM_TRIGGER_SUSPEND);
+
+	return 0;
+}
+
+static int sti_platform_dai_resume(struct snd_soc_dai *dai)
+{
+	struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai);
+	struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id);
+	struct uniperif *uni = dai_data->uni;
+
+	if (uni->ops->trigger)
+		return uni->ops->trigger(uni, SNDRV_PCM_TRIGGER_RESUME);
+
+	return 0;
+}
+
+static int sti_platform_dai_probe(struct snd_soc_dai *dai)
+{
+	struct sti_platform_data *ptf_data = snd_soc_dai_get_drvdata(dai);
+
+	if (ptf_data->dai_data[dai->id].stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dai->playback_dma_data = &ptf_data->dai_data[dai->id].dma_data;
+	else
+		dai->capture_dma_data = &ptf_data->dai_data[dai->id].dma_data;
+
+	return 0;
+}
+
+static struct snd_soc_dai_ops sti_platform_dai_ops[] = {
+	{
+		.startup = sti_platform_dai_startup,
+		.shutdown = sti_platform_dai_shutdown,
+		.prepare = sti_platform_dai_prepare,
+		.trigger = sti_platform_dai_trigger,
+		.hw_params = sti_platform_dai_hw_params,
+		.set_fmt = sti_platform_dai_set_fmt,
+		.set_clkdiv = sti_platform_dai_set_clkdiv,
+	}
+};
+
+static const struct snd_soc_dai_driver sti_platform_dai_template = {
+	.probe = sti_platform_dai_probe,
+	.ops = sti_platform_dai_ops,
+	.suspend = sti_platform_dai_suspend,
+	.resume = sti_platform_dai_resume
+};
+
+static const struct snd_soc_component_driver sti_platform_dai_component = {
+	.name = "sti_cpu_dai",
+};
+
+/*
+ * PCM
+ */
+
+static void sti_dma_complete(void *arg)
+{
+	struct snd_pcm_substream *substream = arg;
+
+	snd_pcm_period_elapsed(substream);
+}
+
+static int sti_pcm_open(struct snd_pcm_substream *substream)
+{
+	int ret = 0;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct sti_pcm_dma_params *params;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct sti_platform_data *priv = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	struct sti_platform_dai *dai_data = priv_to_dai_data(priv,
+							     rtd->cpu_dai->id);
+
+	params = kzalloc(sizeof(*params), GFP_KERNEL);
+	if (!params)
+		return -ENOMEM;
+
+	ret = snd_soc_set_runtime_hwparams(substream, dai_data->uni->hw);
+	if (ret < 0) {
+		dev_err(rtd->dev, "error on FE hw_constraint\n");
+		return ret;
+	}
+
+	/* Ensure that buffer size is a multiple of period size */
+	ret = snd_pcm_hw_constraint_integer(runtime,
+					    SNDRV_PCM_HW_PARAM_PERIODS);
+	if (ret < 0) {
+		dev_err(rtd->dev, "Error: pcm hw constraints failed (%d)\n",
+			ret);
+		return ret;
+	}
+
+	/* update private data */
+	runtime->private_data = params;
+
+	return ret;
+}
+
+static int sti_pcm_hw_params(struct snd_pcm_substream *substream,
+			     struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct sti_pcm_dma_params *dma_params = runtime->private_data;
+	int size, ret = 0;
+
+	size = params_buffer_bytes(params);
+
+	/* Use PCM Lib */
+	ret = snd_pcm_lib_malloc_pages(substream, size);
+	if (ret < 0) {
+		dev_err(rtd->dev, "Can't allocate pages!\n");
+		return -ENOMEM;
+	}
+
+	return snd_dmaengine_pcm_prepare_slave_config(substream, params,
+						     &dma_params->slave_config);
+}
+
+static int sti_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct sti_pcm_dma_params *params = runtime->private_data;
+	struct sti_platform_data *priv = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	struct sti_platform_dai *dai_data = priv_to_dai_data(priv,
+							     rtd->cpu_dai->id);
+	int ret;
+	char prop[5];
+
+	if (!params->dma_channel) {
+		if (dai_data->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			snprintf(prop, sizeof(prop), "tx-%d", rtd->cpu_dai->id);
+		else
+			snprintf(prop, sizeof(prop), "rx-%d", rtd->cpu_dai->id);
+
+		params->dma_channel =
+			dma_request_slave_channel(rtd->platform->dev, prop);
+		if (!params->dma_channel) {
+			dev_err(rtd->dev, "Failed to request DMA channel");
+			return -ENODEV;
+		}
+	}
+	ret = dmaengine_slave_config(params->dma_channel,
+				     &params->slave_config);
+	if (ret)
+		dev_err(rtd->dev, "Failed to configure DMA channel");
+
+	return ret;
+}
+
+static int sti_pcm_trigger(struct snd_pcm_substream *substream,
+			   int cmd)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct sti_pcm_dma_params *params = runtime->private_data;
+	enum dma_transfer_direction direction;
+	struct dma_async_tx_descriptor *desc;
+	unsigned long flags = DMA_CTRL_ACK;
+	int ret = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		direction = snd_pcm_substream_to_dma_direction(substream);
+
+		desc = dmaengine_prep_dma_cyclic(
+				params->dma_channel,	runtime->dma_addr,
+				snd_pcm_lib_buffer_bytes(substream),
+				snd_pcm_lib_period_bytes(substream),
+				direction, flags);
+		if (!desc) {
+			dev_err(rtd->dev, "Failed to prepare DMA descriptor");
+			return -ENOMEM;
+		}
+
+		/* Set the dma callback */
+		desc->callback = sti_dma_complete;
+		desc->callback_param = substream;
+
+		/* Submit dma descriptor */
+		params->dma_cookie = dmaengine_submit(desc);
+		dma_async_issue_pending(params->dma_channel);
+		break;
+
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		if (params->dma_channel)
+			ret = dmaengine_terminate_all(params->dma_channel);
+		break;
+	default:
+		dev_err(rtd->dev, "%s: ERROR: Invalid command in pcm trigger!\n",
+			__func__);
+		return -EINVAL;
+	}
+
+	return ret;
+}
+
+static snd_pcm_uframes_t sti_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct sti_pcm_dma_params *prtd = runtime->private_data;
+	struct dma_tx_state state;
+	enum dma_status status;
+	unsigned int buf_size;
+	unsigned int pos = 0;
+
+	status = dmaengine_tx_status(prtd->dma_channel, prtd->dma_cookie,
+				     &state);
+	if (status == DMA_IN_PROGRESS || status == DMA_PAUSED) {
+		buf_size = snd_pcm_lib_buffer_bytes(substream);
+		if (state.residue > 0 && state.residue <= buf_size)
+			pos = buf_size - state.residue;
+	}
+
+	return bytes_to_frames(substream->runtime, pos);
+}
+
+static int sti_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct sti_pcm_dma_params *params;
+
+	params = substream->runtime->private_data;
+	if (params->dma_channel) {
+		dma_release_channel(params->dma_channel);
+		params->dma_channel = NULL;
+	}
+	kfree(params);
+
+	return 0;
+}
+
+int sti_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_pcm *pcm = rtd->pcm;
+	struct snd_card *card = rtd->card->snd_card;
+	int ret = 0;
+
+	ret = sti_platform_dai_create_ctrl(rtd);
+	if (ret < 0)
+		return ret;
+	else
+		return snd_pcm_lib_preallocate_pages_for_all(
+			pcm, SNDRV_DMA_TYPE_DEV, card->dev,
+			STI_PLATFORM_BUFFER_BYTES_MAX,
+			STI_PLATFORM_BUFFER_BYTES_MAX);
+}
+
+static struct snd_pcm_ops sti_pcm_ops = {
+	.open		= sti_pcm_open,
+	.close		= sti_pcm_close,
+	.hw_params	= sti_pcm_hw_params,
+	.prepare	= sti_pcm_prepare,
+	.hw_free	= snd_pcm_lib_free_pages,
+	.trigger	= sti_pcm_trigger,
+	.pointer	= sti_pcm_pointer
+};
+
+static struct snd_soc_platform_driver sti_platform_platform = {
+	.ops		= &sti_pcm_ops,
+	.pcm_new	= sti_pcm_new,
+};
+
+static int sti_platform_cpu_dai_of(struct device_node *node,
+				   struct sti_platform_data *priv, int idx)
+{
+	const char *str;
+	int ret, i;
+	struct device *dev = &priv->pdev->dev;
+	struct sti_platform_dai *dai_data = &priv->dai_data[idx];
+	struct snd_soc_dai_driver *dai = &priv->dai[idx];
+	struct snd_soc_pcm_stream *stream;
+	struct uniperif *uni;
+	struct {
+		char *name;
+		struct sti_platform_dai *val;
+	} of_fmt_table[] = {
+		{ "rx", &uni_reader },
+		{ "tx",	&uni_player },
+	};
+
+	*dai = sti_platform_dai_template;
+	ret = of_property_read_string(node, "dai-name", &str);
+	if (ret < 0) {
+		dev_err(dev, "%s: dai name missing.\n", __func__);
+		return -EINVAL;
+	}
+	dai->name = str;
+
+	ret = of_property_read_string_index(dev->of_node, "dma-names",
+					    idx, &str);
+	if (ret == 0) {
+		ret = -EINVAL;
+		for (i = 0; i < ARRAY_SIZE(of_fmt_table); i++)
+			if (strncmp(str, of_fmt_table[i].name, 2) == 0) {
+				*dai_data = *of_fmt_table[i].val;
+				ret = 0;
+				break;
+			}
+	}
+	if (ret < 0) {
+		dev_err(dev, "%s: invalid dai-type.\n", __func__);
+		return -EINVAL;
+	}
+
+	/* Initialise uniperif */
+	if (!dai_data->init) {
+		dev_err(dev, "%s: context data missing.\n", __func__);
+		return -EINVAL;
+	}
+
+	ret = dai_data->init(priv->pdev, node,
+			     &dai_data->uni, idx);
+	if (ret < 0) {
+		dev_err(dev, "%s: ERROR: Fail to init uniperif ( %d)!\n",
+			__func__, ret);
+		return ret;
+	}
+
+	uni = dai_data->uni;
+
+	if (dai_data->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		stream = &dai->playback;
+	else
+		stream = &dai->capture;
+
+	stream->stream_name = dai->name;
+	stream->channels_min = uni->hw->channels_min;
+	stream->channels_max = uni->hw->channels_max;
+	stream->rates = uni->hw->rates;
+	stream->formats = uni->hw->formats;
+
+	/* DMA settings*/
+	dai_data->dma_data.addr = uni->fifo_phys_address;
+	dai_data->dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+
+	return 0;
+}
+
+static int sti_platform_engine_probe(struct platform_device *pdev)
+{
+	/*
+	 * Driver can not use snd_dmaengine_pcm_register.
+	 * Reason is that DMA needs to load a firmware.
+	 * This firmware is loaded on request_channel from file system.
+	 * So can not be done in probe while alsa card enumerated before
+	 * file system is mounted
+	 */
+
+	struct sti_platform_data *priv;
+	struct device_node *node = pdev->dev.of_node;
+	int num_dais, ret;
+
+	/* Get the number of DAI links */
+	if (node && of_get_child_by_name(node, "cpu-dai"))
+		num_dais = of_get_child_count(node);
+	else
+		num_dais = 1;
+
+	/* Allocate the private data and the CPU_DAI array */
+	priv = devm_kzalloc(&pdev->dev,
+			    sizeof(*priv) +
+			    sizeof(struct sti_platform_dai) * num_dais,
+			    GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+	priv->dai = devm_kzalloc(&pdev->dev, sizeof(*priv->dai) * num_dais,
+				 GFP_KERNEL);
+	if (!priv->dai)
+		return -ENOMEM;
+
+	priv->pdev = pdev;
+
+	if (of_get_child_by_name(node, "cpu-dai")) {
+		struct device_node *np = NULL;
+		int i = 0;
+
+		for_each_child_of_node(node, np) {
+			ret = sti_platform_cpu_dai_of(np, priv, i);
+			if (ret < 0) {
+				of_node_put(np);
+				return ret;
+			}
+			i++;
+		}
+	}
+
+	dev_set_drvdata(&pdev->dev, priv);
+
+	ret = snd_soc_register_component(&pdev->dev,
+					 &sti_platform_dai_component,
+					 priv->dai, num_dais);
+	if (ret < 0)
+		return ret;
+
+	return snd_soc_register_platform(&pdev->dev, &sti_platform_platform);
+}
+
+static int sti_platform_engine_remove(struct platform_device *pdev)
+{
+	struct sti_platform_data *priv = dev_get_drvdata(&pdev->dev);
+	struct sti_platform_dai *dai_data = priv->dai_data;
+	struct device_node *node = pdev->dev.of_node;
+	int num_dais, idx;
+
+	/* Get the number of DAI links */
+	if (node && of_get_child_by_name(node, "cpu-dai"))
+		num_dais = of_get_child_count(node);
+	else
+		num_dais = 1;
+
+	for (idx = 0; idx < num_dais; idx++) {
+		dai_data->remove(pdev);
+		dai_data++;
+	}
+
+	snd_soc_unregister_platform(&pdev->dev);
+
+	return 0;
+}
+
+static const struct of_device_id snd_soc_sti_match[] = {
+	{ .compatible = "st,sti-audio-platform", },
+	{},
+};
+
+static struct platform_driver sti_platform_driver = {
+	.driver = {
+		.name = "sti-audio-platform",
+		.owner = THIS_MODULE,
+		.of_match_table = snd_soc_sti_match,
+	},
+	.probe = sti_platform_engine_probe,
+	.remove = sti_platform_engine_remove
+};
+module_platform_driver(sti_platform_driver);
+
+MODULE_DESCRIPTION("audio soc platform driver for STI platforms");
+MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@st.com>");
+MODULE_LICENSE("GPL v2");