diff mbox

[v4,1/6] ASoC: core: add snd_soc_add_dai_pcm_controls helper

Message ID 1457441641-7501-2-git-send-email-arnaud.pouliquen@st.com (mailing list archive)
State New, archived
Headers show

Commit Message

Arnaud POULIQUEN March 8, 2016, 12:53 p.m. UTC
Add helper function to register DAI controls that need to be 
linked to pcm device.
A list is handled in case controls are created before dai_link probe

Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen@st.com>
---
 include/sound/soc-dai.h |   1 +
 include/sound/soc.h     |   2 +
 sound/soc/soc-core.c    | 164 +++++++++++++++++++++++++++++++++++++++++-------
 3 files changed, 146 insertions(+), 21 deletions(-)

Comments

Vinod Koul March 10, 2016, 5:06 a.m. UTC | #1
On Tue, Mar 08, 2016 at 01:53:56PM +0100, Arnaud Pouliquen wrote:
> Add helper function to register DAI controls that need to be 
> linked to pcm device.
> A list is handled in case controls are created before dai_link probe

Overall this patch looks good to us. But on first read it is not very clear
how PCM and DAIs are inter related and why you need to do this. Since we are
having similar issues we were able to quickly understand this, the
suggestion would be to elborate a bit more in changelog.

Second, why do we need a new API for this. Why not use existing asoc
concepts and add one more field in dai_driver for dai_controls.
Core can automagically create those controls and link to PCM.

Lastly, this doesn't help our usecase of DPCM where the HDMI codec is
connected to a BE, so that rtd cannot be used and we need to link to FE, so
not sure how we can solve that...

> 
> Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen@st.com>
> ---
>  include/sound/soc-dai.h |   1 +
>  include/sound/soc.h     |   2 +
>  sound/soc/soc-core.c    | 164 +++++++++++++++++++++++++++++++++++++++++-------
>  3 files changed, 146 insertions(+), 21 deletions(-)
> 
> diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h
> index 964b7de..6e0fcb0 100644
> --- a/include/sound/soc-dai.h
> +++ b/include/sound/soc-dai.h
> @@ -292,6 +292,7 @@ struct snd_soc_dai {
>  	unsigned int rx_mask;
>  
>  	struct list_head list;
> +	struct list_head list_pcm_ctl;
>  };
>  
>  static inline void *snd_soc_dai_get_dma_data(const struct snd_soc_dai *dai,
> diff --git a/include/sound/soc.h b/include/sound/soc.h
> index 02b4a21..044adcf 100644
> --- a/include/sound/soc.h
> +++ b/include/sound/soc.h
> @@ -598,6 +598,8 @@ int snd_soc_add_card_controls(struct snd_soc_card *soc_card,
>  	const struct snd_kcontrol_new *controls, int num_controls);
>  int snd_soc_add_dai_controls(struct snd_soc_dai *dai,
>  	const struct snd_kcontrol_new *controls, int num_controls);
> +int snd_soc_add_dai_pcm_controls(struct snd_soc_dai *dai,
> +	const struct snd_kcontrol_new *controls, int num_controls);
>  int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol,
>  	struct snd_ctl_elem_info *uinfo);
>  int snd_soc_get_enum_double(struct snd_kcontrol *kcontrol,
> diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
> index 790ee2b..95aae5e 100644
> --- a/sound/soc/soc-core.c
> +++ b/sound/soc/soc-core.c
> @@ -1582,11 +1582,63 @@ static int soc_link_dai_widgets(struct snd_soc_card *card,
>  	return 0;
>  }
>  
> +static int snd_soc_add_controls(struct snd_card *card, struct device *dev,
> +	const struct snd_kcontrol_new *controls, int num_controls,
> +	const char *prefix, void *data)
> +{
> +	int err, i;
> +
> +	for (i = 0; i < num_controls; i++) {
> +		const struct snd_kcontrol_new *control = &controls[i];
> +
> +		err = snd_ctl_add(card, snd_soc_cnew(control, data,
> +						     control->name, prefix));
> +		if (err < 0) {
> +			dev_err(dev, "ASoC: Failed to add %s: %d\n",
> +				control->name, err);
> +			return err;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +struct snd_soc_dai_pcm_ctls {
> +	struct snd_kcontrol_new *controls;
> +	int num_controls;
> +	struct list_head list;
> +};
> +
> +static int soc_link_dai_pcm_controls(struct snd_soc_card *card,
> +				     struct snd_soc_dai *dai,
> +				     struct snd_soc_pcm_runtime *rtd)
> +{
> +	struct snd_soc_dai_pcm_ctls *ctls, *_ctls;
> +	struct snd_kcontrol_new *kctl;
> +	int i, ret;
> +
> +	list_for_each_entry_safe(ctls, _ctls,  &dai->list_pcm_ctl, list) {
> +		kctl = ctls->controls;
> +		for (i = 0; i < ctls->num_controls; i++)
> +			kctl[i].device = rtd->pcm->device;
> +
> +		ret = snd_soc_add_controls(card->snd_card, dai->dev, kctl,
> +					   ctls->num_controls, NULL, dai);
> +		kfree(kctl);
> +		list_del(&ctls->list);
> +		kfree(ctls);
> +		if (ret < 0)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
>  static int soc_probe_link_dais(struct snd_soc_card *card,
>  		struct snd_soc_pcm_runtime *rtd, int order)
>  {
>  	struct snd_soc_dai_link *dai_link = rtd->dai_link;
> -	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
> +	struct snd_soc_dai *dai, *cpu_dai = rtd->cpu_dai;
>  	int i, ret;
>  
>  	dev_dbg(card->dev, "ASoC: probe %s dai link %d late %d\n",
> @@ -1651,6 +1703,35 @@ static int soc_probe_link_dais(struct snd_soc_card *card,
>  				       dai_link->stream_name, ret);
>  				return ret;
>  			}
> +
> +			/* link  CPU DAI pcm controls to pcm device */
> +			if (!list_empty(&cpu_dai->list_pcm_ctl))
> +				ret = soc_link_dai_pcm_controls(card, cpu_dai,
> +								rtd);
> +			if (ret < 0) {
> +				dev_err(card->dev,
> +					"ASoC: Can't link %s control to %s :%d\n",
> +					cpu_dai->name, dai_link->stream_name,
> +					ret);
> +				return ret;
> +			}
> +
> +			/* link CODEC DAI pcm control to pcm device */
> +			for (i = 0; i < rtd->num_codecs; i++) {
> +				dai = rtd->codec_dais[i];
> +				if (!list_empty(&dai->list_pcm_ctl))
> +					ret = soc_link_dai_pcm_controls(card,
> +								      dai, rtd);
> +				if (ret < 0)
> +					break;
> +			}
> +			if (ret < 0) {
> +				dev_err(card->dev,
> +					"ASoC: Can't link %s control to %s :%d\n",
> +					dai->name, dai_link->stream_name, ret);
> +				return ret;
> +			}
> +
>  		} else {
>  			INIT_DELAYED_WORK(&rtd->delayed_work,
>  						codec2codec_close_delayed_work);
> @@ -2187,26 +2268,6 @@ struct snd_kcontrol *snd_soc_cnew(const struct snd_kcontrol_new *_template,
>  }
>  EXPORT_SYMBOL_GPL(snd_soc_cnew);
>  
> -static int snd_soc_add_controls(struct snd_card *card, struct device *dev,
> -	const struct snd_kcontrol_new *controls, int num_controls,
> -	const char *prefix, void *data)
> -{
> -	int err, i;
> -
> -	for (i = 0; i < num_controls; i++) {
> -		const struct snd_kcontrol_new *control = &controls[i];
> -		err = snd_ctl_add(card, snd_soc_cnew(control, data,
> -						     control->name, prefix));
> -		if (err < 0) {
> -			dev_err(dev, "ASoC: Failed to add %s: %d\n",
> -				control->name, err);
> -			return err;
> -		}
> -	}
> -
> -	return 0;
> -}
> -
>  struct snd_kcontrol *snd_soc_card_get_kcontrol(struct snd_soc_card *soc_card,
>  					       const char *name)
>  {
> @@ -2320,6 +2381,65 @@ int snd_soc_add_dai_controls(struct snd_soc_dai *dai,
>  EXPORT_SYMBOL_GPL(snd_soc_add_dai_controls);
>  
>  /**
> + * snd_soc_add_dai_pcm_controls - add an array of pcm controls to a DAI.
> + * Convenience function to add a list of DAI controls linked to the PCM device.
> + *
> + * @dai: DAI to add controls to
> + * @controls: array of controls to add
> + * @num_controls: number of elements in the array
> + *
> + * Return 0 for success, else error.
> + */
> +int snd_soc_add_dai_pcm_controls(struct snd_soc_dai *dai,
> +				 const struct snd_kcontrol_new *controls,
> +				 int num_controls)
> +{
> +	struct snd_soc_card *card = dai->component->card;
> +	struct snd_soc_pcm_runtime *rtd;
> +	struct snd_soc_dai_pcm_ctls *ctls_list;
> +	struct snd_kcontrol_new *kctl;
> +	int i, dai_found = 0;
> +
> +	for (i = 0; i < num_controls; i++) {
> +		if (controls[i].iface != SNDRV_CTL_ELEM_IFACE_PCM) {
> +			dev_err(dai->dev, "%s: not a pcm device control !!!\n",
> +				controls[i].name);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	kctl = kcalloc(num_controls, sizeof(*kctl), GFP_KERNEL);
> +	memcpy(kctl, controls,  sizeof(*kctl) * num_controls);
> +
> +	if (dai->probed) {
> +		/* pcm device exists. Control can be linked to it */
> +		list_for_each_entry(rtd, &card->rtd_list, list) {
> +			if (dai == rtd->cpu_dai) {
> +				dai_found = 1;
> +				break;
> +			}
> +		}
> +		if (!dai_found)
> +			return -EINVAL;
> +
> +		for (i = 0; i < num_controls; i++)
> +			kctl[i].device = rtd->pcm->device;
> +		snd_soc_add_controls(card->snd_card, dai->dev, kctl,
> +				     num_controls, NULL, dai);
> +		kfree(kctl);
> +	} else {
> +		/* pcm device does not exists. Postpone to dai link creation */
> +		ctls_list = kzalloc(sizeof(*ctls_list), GFP_KERNEL);
> +		ctls_list->controls = kctl;
> +		ctls_list->num_controls = num_controls;
> +		list_add(&ctls_list->list, &dai->list_pcm_ctl);
> +	}
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(snd_soc_add_dai_pcm_controls);
> +
> +/**
>   * snd_soc_dai_set_sysclk - configure DAI system or master clock.
>   * @dai: DAI
>   * @clk_id: DAI specific clock ID
> @@ -2795,6 +2915,8 @@ static struct snd_soc_dai *soc_add_dai(struct snd_soc_component *component,
>  	if (!dai->driver->ops)
>  		dai->driver->ops = &null_dai_ops;
>  
> +	INIT_LIST_HEAD(&dai->list_pcm_ctl);
> +
>  	list_add(&dai->list, &component->dai_list);
>  	component->num_dai++;
>  
> -- 
> 1.9.1
> 
> _______________________________________________
> Alsa-devel mailing list
> Alsa-devel@alsa-project.org
> http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
Arnaud POULIQUEN March 10, 2016, 9:08 a.m. UTC | #2
On 03/10/2016 06:06 AM, Vinod Koul wrote:
> On Tue, Mar 08, 2016 at 01:53:56PM +0100, Arnaud Pouliquen wrote:
>> Add helper function to register DAI controls that need to be 
>> linked to pcm device.
>> A list is handled in case controls are created before dai_link probe
> 
> Overall this patch looks good to us. But on first read it is not very clear
> how PCM and DAIs are inter related and why you need to do this. Since we are
> having similar issues we were able to quickly understand this, the
> suggestion would be to elborate a bit more in changelog.
Right, i will provide more details in commit message.
> 
> Second, why do we need a new API for this. Why not use existing asoc
> concepts and add one more field in dai_driver for dai_controls.
> Core can automagically create those controls and link to PCM.
Yes this was my first approach. Finally, i created a separate API, to be
able to support iec generic control in DAI ( patch 3/6 and 4/6).
These patches need possibility to attach private data to control.
If patches 3/6 and 4/6 are rejected, for sure i will rework it to use
existing API.
Today It is more on compromise than an optimized solution...
But, creating a generic iec control also implies a compatibility with
ASoC and none ASoC drivers...

> 
> Lastly, this doesn't help our usecase of DPCM where the HDMI codec is
> connected to a BE, so that rtd cannot be used and we need to link to FE, so
> not sure how we can solve that...
DPCM seems another story... I'm not fully up to date on DPCM concept,
but as i can remember no link between FE and BE except DAPM routing.
Perhaps, for DPCM, a solution should be to use index field for control,
instead of trying to dynamically link the codec control to PCM device?

Thanks
Arnaud
Subhransu S. Prusty March 10, 2016, 12:58 p.m. UTC | #3
On Thu, Mar 10, 2016 at 02:38:01PM +0530, Arnaud Pouliquen wrote:
> 
> 
> On 03/10/2016 06:06 AM, Vinod Koul wrote:
> > On Tue, Mar 08, 2016 at 01:53:56PM +0100, Arnaud Pouliquen wrote:
> >> Add helper function to register DAI controls that need to be 
> >> linked to pcm device.
> >> A list is handled in case controls are created before dai_link probe
> > 
> > Overall this patch looks good to us. But on first read it is not very clear
> > how PCM and DAIs are inter related and why you need to do this. Since we are
> > having similar issues we were able to quickly understand this, the
> > suggestion would be to elborate a bit more in changelog.
> Right, i will provide more details in commit message.
> > 
> > Second, why do we need a new API for this. Why not use existing asoc
> > concepts and add one more field in dai_driver for dai_controls.
> > Core can automagically create those controls and link to PCM.
> Yes this was my first approach. Finally, i created a separate API, to be
> able to support iec generic control in DAI ( patch 3/6 and 4/6).
> These patches need possibility to attach private data to control.
> If patches 3/6 and 4/6 are rejected, for sure i will rework it to use
> existing API.
> Today It is more on compromise than an optimized solution...
> But, creating a generic iec control also implies a compatibility with
> ASoC and none ASoC drivers...

In our view, iec controls should be made generic alsa controls and asoc
should have wrapper over these and create these controls on card enumeration
and use these wrappers.

> 
> > 
> > Lastly, this doesn't help our usecase of DPCM where the HDMI codec is
> > connected to a BE, so that rtd cannot be used and we need to link to FE, so
> > not sure how we can solve that...
> DPCM seems another story... I'm not fully up to date on DPCM concept,
> but as i can remember no link between FE and BE except DAPM routing.
> Perhaps, for DPCM, a solution should be to use index field for control,
> instead of trying to dynamically link the codec control to PCM device?

That calls for a discussion on how the controls should be represented for
DPCM. Takashi/Mark/Liam we need your opinion on how the PCM related controls
should be represented in DPCM concepts.

> 
> Thanks
> Arnaud
> _______________________________________________
> Alsa-devel mailing list
> Alsa-devel@alsa-project.org
> http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
Arnaud POULIQUEN March 10, 2016, 2:03 p.m. UTC | #4
On 03/10/2016 01:58 PM, Subhransu S. Prusty wrote:
> On Thu, Mar 10, 2016 at 02:38:01PM +0530, Arnaud Pouliquen wrote:
>>
>>
>> On 03/10/2016 06:06 AM, Vinod Koul wrote:
>>> On Tue, Mar 08, 2016 at 01:53:56PM +0100, Arnaud Pouliquen wrote:
>>>> Add helper function to register DAI controls that need to be 
>>>> linked to pcm device.
>>>> A list is handled in case controls are created before dai_link probe
>>>
>>> Overall this patch looks good to us. But on first read it is not very clear
>>> how PCM and DAIs are inter related and why you need to do this. Since we are
>>> having similar issues we were able to quickly understand this, the
>>> suggestion would be to elborate a bit more in changelog.
>> Right, i will provide more details in commit message.
>>>
>>> Second, why do we need a new API for this. Why not use existing asoc
>>> concepts and add one more field in dai_driver for dai_controls.
>>> Core can automagically create those controls and link to PCM.
>> Yes this was my first approach. Finally, i created a separate API, to be
>> able to support iec generic control in DAI ( patch 3/6 and 4/6).
>> These patches need possibility to attach private data to control.
>> If patches 3/6 and 4/6 are rejected, for sure i will rework it to use
>> existing API.
>> Today It is more on compromise than an optimized solution...
>> But, creating a generic iec control also implies a compatibility with
>> ASoC and none ASoC drivers...
> 
> In our view, iec controls should be made generic alsa controls and asoc
> should have wrapper over these and create these controls on card enumeration
> and use these wrappers.
Please, could you details what you have in mind please, because I
thought it was exactly what I proposed my patch-set...

My approach is that i would like to take into account the creation of
generic controls in the way of handling pcm control in ASoC.
I tried to treat iec generic control as another PCM control in soc core,
not as an exception. Because this could also be reused in future for
some other generic pcm controls.

> 
>>
>>>
>>> Lastly, this doesn't help our usecase of DPCM where the HDMI codec is
>>> connected to a BE, so that rtd cannot be used and we need to link to FE, so
>>> not sure how we can solve that...
>> DPCM seems another story... I'm not fully up to date on DPCM concept,
>> but as i can remember no link between FE and BE except DAPM routing.
>> Perhaps, for DPCM, a solution should be to use index field for control,
>> instead of trying to dynamically link the codec control to PCM device?
> 
> That calls for a discussion on how the controls should be represented for
> DPCM. Takashi/Mark/Liam we need your opinion on how the PCM related controls
> should be represented in DPCM concepts.
> 
>>
>> Thanks
>> Arnaud
>> _______________________________________________
>> Alsa-devel mailing list
>> Alsa-devel@alsa-project.org
>> http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
>
Mark Brown March 12, 2016, 6:11 a.m. UTC | #5
On Thu, Mar 10, 2016 at 06:28:18PM +0530, Subhransu S. Prusty wrote:

> That calls for a discussion on how the controls should be represented for
> DPCM. Takashi/Mark/Liam we need your opinion on how the PCM related controls
> should be represented in DPCM concepts.

I think we should be moving away from DPCM towards something that scales
to cover off-SoC devices as well.
diff mbox

Patch

diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h
index 964b7de..6e0fcb0 100644
--- a/include/sound/soc-dai.h
+++ b/include/sound/soc-dai.h
@@ -292,6 +292,7 @@  struct snd_soc_dai {
 	unsigned int rx_mask;
 
 	struct list_head list;
+	struct list_head list_pcm_ctl;
 };
 
 static inline void *snd_soc_dai_get_dma_data(const struct snd_soc_dai *dai,
diff --git a/include/sound/soc.h b/include/sound/soc.h
index 02b4a21..044adcf 100644
--- a/include/sound/soc.h
+++ b/include/sound/soc.h
@@ -598,6 +598,8 @@  int snd_soc_add_card_controls(struct snd_soc_card *soc_card,
 	const struct snd_kcontrol_new *controls, int num_controls);
 int snd_soc_add_dai_controls(struct snd_soc_dai *dai,
 	const struct snd_kcontrol_new *controls, int num_controls);
+int snd_soc_add_dai_pcm_controls(struct snd_soc_dai *dai,
+	const struct snd_kcontrol_new *controls, int num_controls);
 int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol,
 	struct snd_ctl_elem_info *uinfo);
 int snd_soc_get_enum_double(struct snd_kcontrol *kcontrol,
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
index 790ee2b..95aae5e 100644
--- a/sound/soc/soc-core.c
+++ b/sound/soc/soc-core.c
@@ -1582,11 +1582,63 @@  static int soc_link_dai_widgets(struct snd_soc_card *card,
 	return 0;
 }
 
+static int snd_soc_add_controls(struct snd_card *card, struct device *dev,
+	const struct snd_kcontrol_new *controls, int num_controls,
+	const char *prefix, void *data)
+{
+	int err, i;
+
+	for (i = 0; i < num_controls; i++) {
+		const struct snd_kcontrol_new *control = &controls[i];
+
+		err = snd_ctl_add(card, snd_soc_cnew(control, data,
+						     control->name, prefix));
+		if (err < 0) {
+			dev_err(dev, "ASoC: Failed to add %s: %d\n",
+				control->name, err);
+			return err;
+		}
+	}
+
+	return 0;
+}
+
+struct snd_soc_dai_pcm_ctls {
+	struct snd_kcontrol_new *controls;
+	int num_controls;
+	struct list_head list;
+};
+
+static int soc_link_dai_pcm_controls(struct snd_soc_card *card,
+				     struct snd_soc_dai *dai,
+				     struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_dai_pcm_ctls *ctls, *_ctls;
+	struct snd_kcontrol_new *kctl;
+	int i, ret;
+
+	list_for_each_entry_safe(ctls, _ctls,  &dai->list_pcm_ctl, list) {
+		kctl = ctls->controls;
+		for (i = 0; i < ctls->num_controls; i++)
+			kctl[i].device = rtd->pcm->device;
+
+		ret = snd_soc_add_controls(card->snd_card, dai->dev, kctl,
+					   ctls->num_controls, NULL, dai);
+		kfree(kctl);
+		list_del(&ctls->list);
+		kfree(ctls);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
 static int soc_probe_link_dais(struct snd_soc_card *card,
 		struct snd_soc_pcm_runtime *rtd, int order)
 {
 	struct snd_soc_dai_link *dai_link = rtd->dai_link;
-	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	struct snd_soc_dai *dai, *cpu_dai = rtd->cpu_dai;
 	int i, ret;
 
 	dev_dbg(card->dev, "ASoC: probe %s dai link %d late %d\n",
@@ -1651,6 +1703,35 @@  static int soc_probe_link_dais(struct snd_soc_card *card,
 				       dai_link->stream_name, ret);
 				return ret;
 			}
+
+			/* link  CPU DAI pcm controls to pcm device */
+			if (!list_empty(&cpu_dai->list_pcm_ctl))
+				ret = soc_link_dai_pcm_controls(card, cpu_dai,
+								rtd);
+			if (ret < 0) {
+				dev_err(card->dev,
+					"ASoC: Can't link %s control to %s :%d\n",
+					cpu_dai->name, dai_link->stream_name,
+					ret);
+				return ret;
+			}
+
+			/* link CODEC DAI pcm control to pcm device */
+			for (i = 0; i < rtd->num_codecs; i++) {
+				dai = rtd->codec_dais[i];
+				if (!list_empty(&dai->list_pcm_ctl))
+					ret = soc_link_dai_pcm_controls(card,
+								      dai, rtd);
+				if (ret < 0)
+					break;
+			}
+			if (ret < 0) {
+				dev_err(card->dev,
+					"ASoC: Can't link %s control to %s :%d\n",
+					dai->name, dai_link->stream_name, ret);
+				return ret;
+			}
+
 		} else {
 			INIT_DELAYED_WORK(&rtd->delayed_work,
 						codec2codec_close_delayed_work);
@@ -2187,26 +2268,6 @@  struct snd_kcontrol *snd_soc_cnew(const struct snd_kcontrol_new *_template,
 }
 EXPORT_SYMBOL_GPL(snd_soc_cnew);
 
-static int snd_soc_add_controls(struct snd_card *card, struct device *dev,
-	const struct snd_kcontrol_new *controls, int num_controls,
-	const char *prefix, void *data)
-{
-	int err, i;
-
-	for (i = 0; i < num_controls; i++) {
-		const struct snd_kcontrol_new *control = &controls[i];
-		err = snd_ctl_add(card, snd_soc_cnew(control, data,
-						     control->name, prefix));
-		if (err < 0) {
-			dev_err(dev, "ASoC: Failed to add %s: %d\n",
-				control->name, err);
-			return err;
-		}
-	}
-
-	return 0;
-}
-
 struct snd_kcontrol *snd_soc_card_get_kcontrol(struct snd_soc_card *soc_card,
 					       const char *name)
 {
@@ -2320,6 +2381,65 @@  int snd_soc_add_dai_controls(struct snd_soc_dai *dai,
 EXPORT_SYMBOL_GPL(snd_soc_add_dai_controls);
 
 /**
+ * snd_soc_add_dai_pcm_controls - add an array of pcm controls to a DAI.
+ * Convenience function to add a list of DAI controls linked to the PCM device.
+ *
+ * @dai: DAI to add controls to
+ * @controls: array of controls to add
+ * @num_controls: number of elements in the array
+ *
+ * Return 0 for success, else error.
+ */
+int snd_soc_add_dai_pcm_controls(struct snd_soc_dai *dai,
+				 const struct snd_kcontrol_new *controls,
+				 int num_controls)
+{
+	struct snd_soc_card *card = dai->component->card;
+	struct snd_soc_pcm_runtime *rtd;
+	struct snd_soc_dai_pcm_ctls *ctls_list;
+	struct snd_kcontrol_new *kctl;
+	int i, dai_found = 0;
+
+	for (i = 0; i < num_controls; i++) {
+		if (controls[i].iface != SNDRV_CTL_ELEM_IFACE_PCM) {
+			dev_err(dai->dev, "%s: not a pcm device control !!!\n",
+				controls[i].name);
+			return -EINVAL;
+		}
+	}
+
+	kctl = kcalloc(num_controls, sizeof(*kctl), GFP_KERNEL);
+	memcpy(kctl, controls,  sizeof(*kctl) * num_controls);
+
+	if (dai->probed) {
+		/* pcm device exists. Control can be linked to it */
+		list_for_each_entry(rtd, &card->rtd_list, list) {
+			if (dai == rtd->cpu_dai) {
+				dai_found = 1;
+				break;
+			}
+		}
+		if (!dai_found)
+			return -EINVAL;
+
+		for (i = 0; i < num_controls; i++)
+			kctl[i].device = rtd->pcm->device;
+		snd_soc_add_controls(card->snd_card, dai->dev, kctl,
+				     num_controls, NULL, dai);
+		kfree(kctl);
+	} else {
+		/* pcm device does not exists. Postpone to dai link creation */
+		ctls_list = kzalloc(sizeof(*ctls_list), GFP_KERNEL);
+		ctls_list->controls = kctl;
+		ctls_list->num_controls = num_controls;
+		list_add(&ctls_list->list, &dai->list_pcm_ctl);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_add_dai_pcm_controls);
+
+/**
  * snd_soc_dai_set_sysclk - configure DAI system or master clock.
  * @dai: DAI
  * @clk_id: DAI specific clock ID
@@ -2795,6 +2915,8 @@  static struct snd_soc_dai *soc_add_dai(struct snd_soc_component *component,
 	if (!dai->driver->ops)
 		dai->driver->ops = &null_dai_ops;
 
+	INIT_LIST_HEAD(&dai->list_pcm_ctl);
+
 	list_add(&dai->list, &component->dai_list);
 	component->num_dai++;