[alsa-devel] [RFT v3 0/3] ASoC: core: Add support for DAI multicodec
Hi Mark, Liam and Lars,
Sorry for the delay. Here is the third version of the multicodec series.
I changed the way to store the multiple codec_dai as suggested by Lars and added the missing iterations in all the places pointed by Lars in his comments.
Please note that I have a very simple setup based on BBB to check if the series is working, so it will be good to test it on various platforms to ensure this is not generating any regression. Hence this version is still RFT.
The series is based on asoc/for-next (v3.15-rc2).
Test and comments are welcome.
Thanks, Benoit
v1: http://comments.gmane.org/gmane.linux.alsa.devel/120532
v2: http://comments.gmane.org/gmane.linux.alsa.devel/121024 - Split the first version in several patches. - Remove the TDM fixup
v3: - Fixed all the places that were not iterating over all the codecs. - Change the structure and split it.
Benoit Cousson (2): ASoC: core: Add one dai_get_widget helper instead of two rtd based ones ASoC: core: Add support for DAI multicodec
Misael Lopez Cruz (1): ASoC: core: Add helpers for dai link and aux dev init
include/sound/soc-dai.h | 5 + include/sound/soc.h | 13 ++ sound/soc/soc-compress.c | 83 +++++--- sound/soc/soc-core.c | 401 +++++++++++++++++++++++++---------- sound/soc/soc-dapm.c | 71 ++++--- sound/soc/soc-pcm.c | 529 +++++++++++++++++++++++++++++++---------------- 6 files changed, 763 insertions(+), 339 deletions(-)
From: Misael Lopez Cruz misael.lopez@ti.com
Separate DAI link and aux dev initialization in preparation for DAI multicodec support. Since aux dev will remain using single codecs but DAI links will be able to support multiple codecs.
No functional change.
Signed-off-by: Misael Lopez Cruz misael.lopez@ti.com [fparent@baylibre.com: Adapt to 3.14+] Signed-off-by: Fabien Parent fparent@baylibre.com Signed-off-by: Benoit Cousson bcousson@baylibre.com --- sound/soc/soc-core.c | 67 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 59 insertions(+), 8 deletions(-)
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 980da92..f18112a 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1266,6 +1266,63 @@ static void rtd_release(struct device *dev) kfree(dev); }
+static int soc_aux_dev_init(struct snd_soc_card *card, + struct snd_soc_codec *codec, + int num) +{ + struct snd_soc_aux_dev *aux_dev = &card->aux_dev[num]; + struct snd_soc_pcm_runtime *rtd = &card->rtd_aux[num]; + const char *temp; + int ret; + + rtd->card = card; + + temp = codec->name_prefix; + codec->name_prefix = NULL; + + /* do machine specific initialization */ + if (aux_dev->init) { + ret = aux_dev->init(&codec->dapm); + if (ret < 0) + return ret; + } + + codec->name_prefix = temp; + + rtd->codec = codec; + + return 0; +} + +static int soc_dai_link_init(struct snd_soc_card *card, + struct snd_soc_codec *codec, + int num) +{ + struct snd_soc_dai_link *dai_link = &card->dai_link[num]; + struct snd_soc_pcm_runtime *rtd = &card->rtd[num]; + const char *temp; + int ret; + + rtd->card = card; + + /* machine controls, routes and widgets are not prefixed */ + temp = codec->name_prefix; + codec->name_prefix = NULL; + + /* do machine specific initialization */ + if (dai_link->init) { + ret = dai_link->init(rtd); + if (ret < 0) + return ret; + } + + codec->name_prefix = temp; + + rtd->codec = codec; + + return 0; +} + static int soc_post_component_init(struct snd_soc_card *card, struct snd_soc_codec *codec, int num, int dailess) @@ -1280,26 +1337,20 @@ static int soc_post_component_init(struct snd_soc_card *card, dai_link = &card->dai_link[num]; rtd = &card->rtd[num]; name = dai_link->name; + ret = soc_dai_link_init(card, codec, num); } else { aux_dev = &card->aux_dev[num]; rtd = &card->rtd_aux[num]; name = aux_dev->name; + ret = soc_aux_dev_init(card, codec, num); } - rtd->card = card;
- /* do machine specific initialization */ - if (!dailess && dai_link->init) - ret = dai_link->init(rtd); - else if (dailess && aux_dev->init) - ret = aux_dev->init(&codec->dapm); if (ret < 0) { dev_err(card->dev, "ASoC: failed to init %s: %d\n", name, ret); return ret; }
/* register the rtd device */ - rtd->codec = codec; - rtd->dev = kzalloc(sizeof(struct device), GFP_KERNEL); if (!rtd->dev) return -ENOMEM;
On Thu, Apr 24, 2014 at 02:01:44PM +0200, Benoit Cousson wrote:
From: Misael Lopez Cruz misael.lopez@ti.com
Separate DAI link and aux dev initialization in preparation for DAI multicodec support. Since aux dev will remain using single codecs but DAI links will be able to support multiple codecs.
Applied, thanks.
Replace rtd_get_codec_widget() and rtd_get_cpu_widget() by a simple dai_get_widget() in preparation for DAI-multicodec support, per Lars suggestion.
No functional change.
Signed-off-by: Benoit Cousson bcousson@baylibre.com Cc: Lars-Peter Clausen lars@metafoo.de --- sound/soc/soc-pcm.c | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-)
diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 2cedf09..88230ea 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -1012,21 +1012,12 @@ static struct snd_soc_pcm_runtime *dpcm_get_be(struct snd_soc_card *card, }
static inline struct snd_soc_dapm_widget * - rtd_get_cpu_widget(struct snd_soc_pcm_runtime *rtd, int stream) + dai_get_widget(struct snd_soc_dai *dai, int stream) { if (stream == SNDRV_PCM_STREAM_PLAYBACK) - return rtd->cpu_dai->playback_widget; + return dai->playback_widget; else - return rtd->cpu_dai->capture_widget; -} - -static inline struct snd_soc_dapm_widget * - rtd_get_codec_widget(struct snd_soc_pcm_runtime *rtd, int stream) -{ - if (stream == SNDRV_PCM_STREAM_PLAYBACK) - return rtd->codec_dai->playback_widget; - else - return rtd->codec_dai->capture_widget; + return dai->capture_widget; }
static int widget_in_list(struct snd_soc_dapm_widget_list *list, @@ -1076,14 +1067,14 @@ static int dpcm_prune_paths(struct snd_soc_pcm_runtime *fe, int stream, list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) {
/* is there a valid CPU DAI widget for this BE */ - widget = rtd_get_cpu_widget(dpcm->be, stream); + widget = dai_get_widget(dpcm->be->cpu_dai, stream);
/* prune the BE if it's no longer in our active list */ if (widget && widget_in_list(list, widget)) continue;
/* is there a valid CODEC DAI widget for this BE */ - widget = rtd_get_codec_widget(dpcm->be, stream); + widget = dai_get_widget(dpcm->be->codec_dai, stream);
/* prune the BE if it's no longer in our active list */ if (widget && widget_in_list(list, widget))
On Thu, Apr 24, 2014 at 02:01:45PM +0200, Benoit Cousson wrote:
Replace rtd_get_codec_widget() and rtd_get_cpu_widget() by a simple dai_get_widget() in preparation for DAI-multicodec support, per Lars suggestion.
Applied, thanks.
DAI link assumes a one to one mapping between CPU DAI and CODEC. In some cases, the same CPU DAI can be connected to several codecs. This is the case for example, if you connect two mono codecs to the same I2S link in order to have a stereo card. The current ASoC implementation does not allow such setup.
Add support for DAI links composed of a single CPU DAI and multiple CODECs. Sound cards have to pass the CODECs array in the corresponding DAI link through a new 'snd_soc_dai_link_component' struct. Each CODEC in this array is described in the same manner single CODEC DAIs are (either DT/OF node or codec_name).
Fix a trailing space in the header as well.
Based on an original code done by Misael.
Signed-off-by: Benoit Cousson bcousson@baylibre.com Signed-off-by: Misael Lopez Cruz misael.lopez@ti.com Signed-off-by: Fabien Parent fparent@baylibre.com --- include/sound/soc-dai.h | 5 + include/sound/soc.h | 13 ++ sound/soc/soc-compress.c | 83 +++++--- sound/soc/soc-core.c | 364 ++++++++++++++++++++++----------- sound/soc/soc-dapm.c | 71 ++++--- sound/soc/soc-pcm.c | 512 ++++++++++++++++++++++++++++++++--------------- 6 files changed, 715 insertions(+), 333 deletions(-)
diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index fad7676..6985851 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -274,6 +274,11 @@ struct snd_soc_dai { struct snd_soc_codec *codec; struct snd_soc_component *component;
+ /* CODEC TDM slot masks and params (for fixup) */ + unsigned int tx_mask; + unsigned int rx_mask; + struct snd_pcm_hw_params params[2]; + struct snd_soc_card *card;
struct list_head list; diff --git a/include/sound/soc.h b/include/sound/soc.h index 0fadb3c..7933512 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -821,6 +821,12 @@ struct snd_soc_platform_driver { int (*bespoke_trigger)(struct snd_pcm_substream *, int); };
+struct snd_soc_dai_link_component { + const char *name; + const struct device_node *of_node; + const char *dai_name; +}; + struct snd_soc_platform { const char *name; int id; @@ -870,6 +876,10 @@ struct snd_soc_dai_link { const struct device_node *codec_of_node; /* You MUST specify the DAI name within the codec */ const char *codec_dai_name; + + struct snd_soc_dai_link_component *codecs; + unsigned int num_codecs; + /* * You MAY specify the link's platform/PCM/DMA driver, either by * device name, or by DT/OF node, but not both. Some forms of link @@ -1059,6 +1069,9 @@ struct snd_soc_pcm_runtime { struct snd_soc_dai *codec_dai; struct snd_soc_dai *cpu_dai;
+ struct snd_soc_dai **codec_dais; + unsigned int num_codecs; + struct delayed_work delayed_work; #ifdef CONFIG_DEBUG_FS struct dentry *debugfs_dpcm_root; diff --git a/sound/soc/soc-compress.c b/sound/soc/soc-compress.c index 91083e6..f7a19bd 100644 --- a/sound/soc/soc-compress.c +++ b/sound/soc/soc-compress.c @@ -153,14 +153,17 @@ static void close_delayed_work(struct work_struct *work) { struct snd_soc_pcm_runtime *rtd = container_of(work, struct snd_soc_pcm_runtime, delayed_work.work); - struct snd_soc_dai *codec_dai = rtd->codec_dai; + int i;
mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass);
- dev_dbg(rtd->dev, "ASoC: pop wq checking: %s status: %s waiting: %s\n", - codec_dai->driver->playback.stream_name, - codec_dai->playback_active ? "active" : "inactive", - rtd->pop_wait ? "yes" : "no"); + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; + dev_dbg(rtd->dev, "ASoC: pop wq checking: %s status: %s waiting: %s\n", + codec_dai->driver->playback.stream_name, + codec_dai->playback_active ? "active" : "inactive", + rtd->pop_wait ? "yes" : "no"); + }
/* are we waiting on this codec DAI stream */ if (rtd->pop_wait == 1) { @@ -177,8 +180,7 @@ static int soc_compr_free(struct snd_compr_stream *cstream) struct snd_soc_pcm_runtime *rtd = cstream->private_data; struct snd_soc_platform *platform = rtd->platform; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - int stream; + int stream, i;
mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass);
@@ -189,14 +191,18 @@ static int soc_compr_free(struct snd_compr_stream *cstream)
snd_soc_runtime_deactivate(rtd, stream);
- snd_soc_dai_digital_mute(codec_dai, 1, cstream->direction); + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; + + snd_soc_dai_digital_mute(codec_dai, 1, cstream->direction); + + if (!codec_dai->active) + codec_dai->rate = 0; + }
if (!cpu_dai->active) cpu_dai->rate = 0;
- if (!codec_dai->active) - codec_dai->rate = 0; -
if (rtd->dai_link->compr_ops && rtd->dai_link->compr_ops->shutdown) rtd->dai_link->compr_ops->shutdown(cstream); @@ -282,8 +288,7 @@ static int soc_compr_trigger(struct snd_compr_stream *cstream, int cmd)
struct snd_soc_pcm_runtime *rtd = cstream->private_data; struct snd_soc_platform *platform = rtd->platform; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - int ret = 0; + int i, ret = 0;
mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass);
@@ -293,13 +298,19 @@ static int soc_compr_trigger(struct snd_compr_stream *cstream, int cmd) goto out; }
- switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - snd_soc_dai_digital_mute(codec_dai, 0, cstream->direction); - break; - case SNDRV_PCM_TRIGGER_STOP: - snd_soc_dai_digital_mute(codec_dai, 1, cstream->direction); - break; + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + snd_soc_dai_digital_mute(codec_dai, 0, + cstream->direction); + break; + case SNDRV_PCM_TRIGGER_STOP: + snd_soc_dai_digital_mute(codec_dai, 1, + cstream->direction); + break; + } }
out: @@ -620,23 +631,26 @@ int soc_new_compress(struct snd_soc_pcm_runtime *rtd, int num) { struct snd_soc_codec *codec = rtd->codec; struct snd_soc_platform *platform = rtd->platform; - struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; struct snd_compr *compr; struct snd_pcm *be_pcm; char new_name[64]; - int ret = 0, direction = 0; + int i, ret = 0, direction = 0;
- /* check client and interface hw capabilities */ - snprintf(new_name, sizeof(new_name), "%s %s-%d", - rtd->dai_link->stream_name, codec_dai->name, num);
- if (codec_dai->driver->playback.channels_min) - direction = SND_COMPRESS_PLAYBACK; - else if (codec_dai->driver->capture.channels_min) - direction = SND_COMPRESS_CAPTURE; - else - return -EINVAL; + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; + /* check client and interface hw capabilities */ + snprintf(new_name, sizeof(new_name), "%s %s-%d", + rtd->dai_link->stream_name, codec_dai->name, num); + + if (codec_dai->driver->playback.channels_min) + direction = SND_COMPRESS_PLAYBACK; + else if (codec_dai->driver->capture.channels_min) + direction = SND_COMPRESS_CAPTURE; + else + return -EINVAL; + }
compr = kzalloc(sizeof(*compr), GFP_KERNEL); if (compr == NULL) { @@ -690,8 +704,11 @@ int soc_new_compress(struct snd_soc_pcm_runtime *rtd, int num) rtd->compr = compr; compr->private_data = rtd;
- printk(KERN_INFO "compress asoc: %s <-> %s mapping ok\n", codec_dai->name, - cpu_dai->name); + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; + printk(KERN_INFO "compress asoc: %s <-> %s mapping ok\n", + codec_dai->name, cpu_dai->name); + } return ret;
compr_err: diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index f18112a..7f68492 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -554,7 +554,7 @@ int snd_soc_suspend(struct device *dev) { struct snd_soc_card *card = dev_get_drvdata(dev); struct snd_soc_codec *codec; - int i; + int i, j;
/* If the initialization of this soc device failed, there is no codec * associated with it. Just bail out in this case. @@ -574,14 +574,16 @@ int snd_soc_suspend(struct device *dev)
/* mute any active DACs */ for (i = 0; i < card->num_rtd; i++) { - struct snd_soc_dai *dai = card->rtd[i].codec_dai; - struct snd_soc_dai_driver *drv = dai->driver; + for (j = 0; j < card->rtd[i].num_codecs; j++) { + struct snd_soc_dai *dai = card->rtd[i].codec_dais[j]; + struct snd_soc_dai_driver *drv = dai->driver;
- if (card->rtd[i].dai_link->ignore_suspend) - continue; + if (card->rtd[i].dai_link->ignore_suspend) + continue;
- if (drv->ops->digital_mute && dai->playback_active) - drv->ops->digital_mute(dai, 1); + if (drv->ops->digital_mute && dai->playback_active) + drv->ops->digital_mute(dai, 1); + } }
/* suspend all pcms */ @@ -612,8 +614,12 @@ int snd_soc_suspend(struct device *dev)
/* close any waiting streams and save state */ for (i = 0; i < card->num_rtd; i++) { + struct snd_soc_dai **codec_dais = card->rtd[i].codec_dais; flush_delayed_work(&card->rtd[i].delayed_work); - card->rtd[i].codec->dapm.suspend_bias_level = card->rtd[i].codec->dapm.bias_level; + for (j = 0; j < card->rtd[i].num_codecs; j++) { + codec_dais[j]->codec->dapm.suspend_bias_level = + codec_dais[j]->codec->dapm.bias_level; + } }
for (i = 0; i < card->num_rtd; i++) { @@ -697,7 +703,7 @@ static void soc_resume_deferred(struct work_struct *work) struct snd_soc_card *card = container_of(work, struct snd_soc_card, deferred_resume_work); struct snd_soc_codec *codec; - int i; + int i, j;
/* our power state is still SNDRV_CTL_POWER_D3hot from suspend time, * so userspace apps are blocked from touching us @@ -758,14 +764,17 @@ static void soc_resume_deferred(struct work_struct *work)
/* unmute any active DACs */ for (i = 0; i < card->num_rtd; i++) { - struct snd_soc_dai *dai = card->rtd[i].codec_dai; - struct snd_soc_dai_driver *drv = dai->driver;
- if (card->rtd[i].dai_link->ignore_suspend) - continue; + for (j = 0; j < card->rtd[i].num_codecs; j++) { + struct snd_soc_dai *dai = card->rtd[i].codec_dais[j]; + struct snd_soc_dai_driver *drv = dai->driver; + + if (card->rtd[i].dai_link->ignore_suspend) + continue;
- if (drv->ops->digital_mute && dai->playback_active) - drv->ops->digital_mute(dai, 0); + if (drv->ops->digital_mute && dai->playback_active) + drv->ops->digital_mute(dai, 0); + } }
for (i = 0; i < card->num_rtd; i++) { @@ -810,12 +819,19 @@ int snd_soc_resume(struct device *dev)
/* activate pins from sleep state */ for (i = 0; i < card->num_rtd; i++) { - struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai; - struct snd_soc_dai *codec_dai = card->rtd[i].codec_dai; + struct snd_soc_pcm_runtime *rtd = &card->rtd[i]; + struct snd_soc_dai **codec_dais = rtd->codec_dais; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int j; + if (cpu_dai->active) pinctrl_pm_select_default_state(cpu_dai->dev); - if (codec_dai->active) - pinctrl_pm_select_default_state(codec_dai->dev); + + for (j = 0; j < rtd->num_codecs; j++) { + struct snd_soc_dai *codec_dai = codec_dais[j]; + if (codec_dai->active) + pinctrl_pm_select_default_state(codec_dai->dev); + } }
/* AC97 devices might have other drivers hanging off them so @@ -847,8 +863,9 @@ EXPORT_SYMBOL_GPL(snd_soc_resume); static const struct snd_soc_dai_ops null_dai_ops = { };
-static struct snd_soc_codec *soc_find_codec(const struct device_node *codec_of_node, - const char *codec_name) +static struct snd_soc_codec *soc_find_codec( + const struct device_node *codec_of_node, + const char *codec_name) { struct snd_soc_codec *codec;
@@ -886,9 +903,12 @@ static int soc_bind_dai_link(struct snd_soc_card *card, int num) struct snd_soc_dai_link *dai_link = &card->dai_link[num]; struct snd_soc_pcm_runtime *rtd = &card->rtd[num]; struct snd_soc_component *component; + struct snd_soc_dai_link_component *codecs = dai_link->codecs; + struct snd_soc_dai **codec_dais = rtd->codec_dais; struct snd_soc_platform *platform; struct snd_soc_dai *cpu_dai; const char *platform_name; + int i;
dev_dbg(card->dev, "ASoC: binding %s at idx %d\n", dai_link->name, num);
@@ -915,24 +935,30 @@ static int soc_bind_dai_link(struct snd_soc_card *card, int num) return -EPROBE_DEFER; }
- /* Find CODEC from registered list */ - rtd->codec = soc_find_codec(dai_link->codec_of_node, - dai_link->codec_name); - if (!rtd->codec) { - dev_err(card->dev, "ASoC: CODEC %s not registered\n", - dai_link->codec_name); - return -EPROBE_DEFER; - } + rtd->num_codecs = dai_link->num_codecs;
- /* Find CODEC DAI from registered list */ - rtd->codec_dai = soc_find_codec_dai(rtd->codec, - dai_link->codec_dai_name); - if (!rtd->codec_dai) { - dev_err(card->dev, "ASoC: CODEC DAI %s not registered\n", - dai_link->codec_dai_name); - return -EPROBE_DEFER; + /* Find CODEC from registered CODECs */ + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_codec *codec; + codec = soc_find_codec(codecs[i].of_node, codecs[i].name); + if (!codec) { + dev_err(card->dev, "ASoC: CODEC %s not registered\n", + codecs[i].name); + return -EPROBE_DEFER; + } + + codec_dais[i] = soc_find_codec_dai(codec, codecs[i].dai_name); + if (!codec_dais[i]) { + dev_err(card->dev, "ASoC: CODEC DAI %s not registered\n", + codecs[i].dai_name); + return -EPROBE_DEFER; + } }
+ /* Single codec links expect codec and codec_dai in runtime data */ + rtd->codec_dai = codec_dais[0]; + rtd->codec = rtd->codec_dai->codec; + /* if there's no platform we match on the empty platform */ platform_name = dai_link->platform_name; if (!platform_name && !dai_link->platform_of_node) @@ -1024,8 +1050,8 @@ static void soc_remove_codec_dai(struct snd_soc_dai *codec_dai, int order) static void soc_remove_link_dais(struct snd_soc_card *card, int num, int order) { struct snd_soc_pcm_runtime *rtd = &card->rtd[num]; - struct snd_soc_dai *codec_dai = rtd->codec_dai, *cpu_dai = rtd->cpu_dai; - int err; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int i, err;
/* unregister the rtd device */ if (rtd->dev_registered) { @@ -1036,7 +1062,8 @@ static void soc_remove_link_dais(struct snd_soc_card *card, int num, int order) }
/* remove the CODEC DAI */ - soc_remove_codec_dai(codec_dai, order); + for (i = 0; i < rtd->num_codecs; i++) + soc_remove_codec_dai(rtd->codec_dais[i], order);
/* remove the cpu_dai */ if (cpu_dai && cpu_dai->probed && @@ -1063,9 +1090,9 @@ static void soc_remove_link_components(struct snd_soc_card *card, int num, { struct snd_soc_pcm_runtime *rtd = &card->rtd[num]; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_platform *platform = rtd->platform; struct snd_soc_codec *codec; + int i;
/* remove the platform */ if (platform && platform->probed && @@ -1074,8 +1101,8 @@ static void soc_remove_link_components(struct snd_soc_card *card, int num, }
/* remove the CODEC-side CODEC */ - if (codec_dai) { - codec = codec_dai->codec; + for (i = 0; i < rtd->num_codecs; i++) { + codec = rtd->codec_dais[i]->codec; if (codec && codec->probed && codec->driver->remove_order == order) soc_remove_codec(codec); @@ -1266,17 +1293,20 @@ static void rtd_release(struct device *dev) kfree(dev); }
-static int soc_aux_dev_init(struct snd_soc_card *card, - struct snd_soc_codec *codec, - int num) +static int soc_aux_dev_init(struct snd_soc_card *card, int num) { struct snd_soc_aux_dev *aux_dev = &card->aux_dev[num]; struct snd_soc_pcm_runtime *rtd = &card->rtd_aux[num]; + struct snd_soc_codec *codec; const char *temp; int ret;
rtd->card = card;
+ codec = soc_find_codec(NULL, aux_dev->codec_name); + if (!codec) + return -EPROBE_DEFER; + temp = codec->name_prefix; codec->name_prefix = NULL;
@@ -1294,20 +1324,28 @@ static int soc_aux_dev_init(struct snd_soc_card *card, return 0; }
-static int soc_dai_link_init(struct snd_soc_card *card, - struct snd_soc_codec *codec, - int num) +static int soc_dai_link_init(struct snd_soc_card *card, int num) { struct snd_soc_dai_link *dai_link = &card->dai_link[num]; struct snd_soc_pcm_runtime *rtd = &card->rtd[num]; - const char *temp; - int ret; + const char **temp; + int i, ret;
rtd->card = card;
- /* machine controls, routes and widgets are not prefixed */ - temp = codec->name_prefix; - codec->name_prefix = NULL; + temp = devm_kzalloc(card->dev, rtd->num_codecs * sizeof(char *), + GFP_KERNEL); + if (!temp) + return -ENOMEM; + + for (i = 0; i < rtd->num_codecs; i++) { + /* Make sure all DAPM widgets are instantiated */ + snd_soc_dapm_new_widgets(rtd->codec_dais[i]->codec->dapm.card); + + /* machine controls, routes and widgets are not prefixed */ + temp[i] = rtd->codec_dais[i]->codec->name_prefix; + rtd->codec_dais[i]->codec->name_prefix = NULL; + }
/* do machine specific initialization */ if (dai_link->init) { @@ -1316,15 +1354,15 @@ static int soc_dai_link_init(struct snd_soc_card *card, return ret; }
- codec->name_prefix = temp; + for (i = 0; i < rtd->num_codecs; i++) + rtd->codec_dais[i]->codec->name_prefix = temp[i];
- rtd->codec = codec; + devm_kfree(card->dev, temp);
return 0; }
static int soc_post_component_init(struct snd_soc_card *card, - struct snd_soc_codec *codec, int num, int dailess) { struct snd_soc_dai_link *dai_link = NULL; @@ -1337,12 +1375,12 @@ static int soc_post_component_init(struct snd_soc_card *card, dai_link = &card->dai_link[num]; rtd = &card->rtd[num]; name = dai_link->name; - ret = soc_dai_link_init(card, codec, num); + ret = soc_dai_link_init(card, num); } else { aux_dev = &card->aux_dev[num]; rtd = &card->rtd_aux[num]; name = aux_dev->name; - ret = soc_aux_dev_init(card, codec, num); + ret = soc_aux_dev_init(card, num); }
if (ret < 0) { @@ -1368,7 +1406,7 @@ static int soc_post_component_init(struct snd_soc_card *card, if (ret < 0) { /* calling put_device() here to free the rtd->dev */ put_device(rtd->dev); - dev_err(card->dev, + dev_err(rtd->dev, "ASoC: failed to register runtime device: %d\n", ret); return ret; } @@ -1377,13 +1415,13 @@ static int soc_post_component_init(struct snd_soc_card *card, /* add DAPM sysfs entries for this codec */ ret = snd_soc_dapm_sys_add(rtd->dev); if (ret < 0) - dev_err(codec->dev, + dev_err(rtd->dev, "ASoC: failed to add codec dapm sysfs entries: %d\n", ret);
/* add codec sysfs entries */ ret = device_create_file(rtd->dev, &dev_attr_codec_reg); if (ret < 0) - dev_err(codec->dev, + dev_err(rtd->dev, "ASoC: failed to add codec sysfs files: %d\n", ret);
#ifdef CONFIG_DEBUG_FS @@ -1405,9 +1443,8 @@ static int soc_probe_link_components(struct snd_soc_card *card, int num, { struct snd_soc_pcm_runtime *rtd = &card->rtd[num]; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_platform *platform = rtd->platform; - int ret; + int i, ret;
/* probe the CPU-side component, if it is a CODEC */ if (cpu_dai->codec && @@ -1418,12 +1455,14 @@ static int soc_probe_link_components(struct snd_soc_card *card, int num, return ret; }
- /* probe the CODEC-side component */ - if (!codec_dai->codec->probed && - codec_dai->codec->driver->probe_order == order) { - ret = soc_probe_codec(card, codec_dai->codec); - if (ret < 0) - return ret; + /* probe the CODEC-side components */ + for (i = 0; i < rtd->num_codecs; i++) { + if (!rtd->codec_dais[i]->codec->probed && + rtd->codec_dais[i]->codec->driver->probe_order == order) { + ret = soc_probe_codec(card, rtd->codec_dais[i]->codec); + if (ret < 0) + return ret; + } }
/* probe the platform */ @@ -1502,19 +1541,18 @@ static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order) { struct snd_soc_dai_link *dai_link = &card->dai_link[num]; struct snd_soc_pcm_runtime *rtd = &card->rtd[num]; - struct snd_soc_codec *codec = rtd->codec; struct snd_soc_platform *platform = rtd->platform; - struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - int ret; + int i, ret;
dev_dbg(card->dev, "ASoC: probe %s dai link %d late %d\n", card->name, num, order);
/* config components */ cpu_dai->platform = platform; - codec_dai->card = card; cpu_dai->card = card; + for (i = 0; i < rtd->num_codecs; i++) + rtd->codec_dais[i]->card = card;
/* set default power off timeout */ rtd->pmdown_time = pmdown_time; @@ -1546,15 +1584,17 @@ static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order) }
/* probe the CODEC DAI */ - ret = soc_probe_codec_dai(card, codec_dai, order); - if (ret) - return ret; + for (i = 0; i < rtd->num_codecs; i++) { + ret = soc_probe_codec_dai(card, rtd->codec_dais[i], order); + if (ret) + return ret; + }
/* complete DAI probe during last probe */ if (order != SND_SOC_COMP_ORDER_LAST) return 0;
- ret = soc_post_component_init(card, codec, num, 0); + ret = soc_post_component_init(card, num, 0); if (ret) return ret;
@@ -1586,16 +1626,21 @@ static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order) codec2codec_close_delayed_work);
/* link the DAI widgets */ - ret = soc_link_dai_widgets(card, dai_link, - cpu_dai, codec_dai); - if (ret) - return ret; + for (i = 0; i < rtd->num_codecs; i++) { + ret = soc_link_dai_widgets(card, dai_link, + cpu_dai, rtd->codec_dais[i]); + if (ret) + return ret; + } } }
/* add platform data for AC97 devices */ - if (rtd->codec_dai->driver->ac97_control) - snd_ac97_dev_add_pdata(codec->ac97, rtd->cpu_dai->ac97_pdata); + for (i = 0; i < rtd->num_codecs; i++) { + if (rtd->codec_dais[i]->driver->ac97_control) + snd_ac97_dev_add_pdata(rtd->codec_dais[i]->codec->ac97, + rtd->cpu_dai->ac97_pdata); + }
return 0; } @@ -1635,7 +1680,20 @@ static int soc_register_ac97_codec(struct snd_soc_codec *codec,
static int soc_register_ac97_dai_link(struct snd_soc_pcm_runtime *rtd) { - return soc_register_ac97_codec(rtd->codec, rtd->codec_dai); + int i, ret; + + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; + + ret = soc_register_ac97_codec(codec_dai->codec, codec_dai); + if (ret) { + while (--i >= 0) + soc_unregister_ac97_codec(codec_dai->codec); + return ret; + } + } + + return 0; }
static void soc_unregister_ac97_codec(struct snd_soc_codec *codec) @@ -1648,7 +1706,10 @@ static void soc_unregister_ac97_codec(struct snd_soc_codec *codec)
static void soc_unregister_ac97_dai_link(struct snd_soc_pcm_runtime *rtd) { - soc_unregister_ac97_codec(rtd->codec); + int i; + + for (i = 0; i < rtd->num_codecs; i++) + soc_unregister_ac97_codec(rtd->codec_dais[i]->codec); } #endif
@@ -1695,7 +1756,7 @@ found: if (ret < 0) return ret;
- ret = soc_post_component_init(card, codec, num, 1); + ret = soc_post_component_init(card, num, 1);
out: return ret; @@ -1850,16 +1911,23 @@ static int snd_soc_instantiate_card(struct snd_soc_card *card) card->num_dapm_routes);
for (i = 0; i < card->num_links; i++) { + struct snd_soc_pcm_runtime *rtd = &card->rtd[i]; dai_link = &card->dai_link[i]; dai_fmt = dai_link->dai_fmt;
if (dai_fmt) { - ret = snd_soc_dai_set_fmt(card->rtd[i].codec_dai, - dai_fmt); - if (ret != 0 && ret != -ENOTSUPP) - dev_warn(card->rtd[i].codec_dai->dev, - "ASoC: Failed to set DAI format: %d\n", - ret); + struct snd_soc_dai **codec_dais = rtd->codec_dais; + int j; + + for (j = 0; j < rtd->num_codecs; j++) { + struct snd_soc_dai *codec_dai = codec_dais[j]; + + ret = snd_soc_dai_set_fmt(codec_dai, dai_fmt); + if (ret != 0 && ret != -ENOTSUPP) + dev_warn(codec_dai->dev, + "ASoC: Failed to set DAI format: %d\n", + ret); + } }
/* If this is a regular CPU link there will be a platform */ @@ -2059,10 +2127,15 @@ int snd_soc_poweroff(struct device *dev)
/* deactivate pins to sleep state */ for (i = 0; i < card->num_rtd; i++) { - struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai; - struct snd_soc_dai *codec_dai = card->rtd[i].codec_dai; - pinctrl_pm_select_sleep_state(codec_dai->dev); + struct snd_soc_pcm_runtime *rtd = &card->rtd[i]; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int j; + pinctrl_pm_select_sleep_state(cpu_dai->dev); + for (j = 0; j < rtd->num_codecs; j++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[j]; + pinctrl_pm_select_sleep_state(codec_dai->dev); + } }
return 0; @@ -3624,17 +3697,26 @@ static int snd_soc_xlate_tdm_slot_mask(unsigned int slots, int snd_soc_dai_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) { + int ret = 0; + if (dai->driver && dai->driver->ops->xlate_tdm_slot_mask) dai->driver->ops->xlate_tdm_slot_mask(slots, &tx_mask, &rx_mask); else snd_soc_xlate_tdm_slot_mask(slots, &tx_mask, &rx_mask);
+ if (dai->codec) { + dai->tx_mask = tx_mask; + dai->rx_mask = rx_mask; + } + if (dai->driver && dai->driver->ops->set_tdm_slot) - return dai->driver->ops->set_tdm_slot(dai, tx_mask, rx_mask, + ret = dai->driver->ops->set_tdm_slot(dai, tx_mask, rx_mask, slots, slot_width); else return -ENOTSUPP; + + return ret; } EXPORT_SYMBOL_GPL(snd_soc_dai_set_tdm_slot);
@@ -3702,6 +3784,33 @@ int snd_soc_dai_digital_mute(struct snd_soc_dai *dai, int mute, } EXPORT_SYMBOL_GPL(snd_soc_dai_digital_mute);
+static int snd_soc_init_multicodec(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_link) +{ + /* Legacy codec/codec_dai link is a single entry in multicodec */ + if (dai_link->codec_name || dai_link->codec_of_node || + dai_link->codec_dai_name) { + dai_link->num_codecs = 1; + + dai_link->codecs = devm_kzalloc(card->dev, + sizeof(struct snd_soc_dai_link_component), + GFP_KERNEL); + if (!dai_link->codecs) + return -ENOMEM; + + dai_link->codecs[0].name = dai_link->codec_name; + dai_link->codecs[0].of_node = dai_link->codec_of_node; + dai_link->codecs[0].dai_name = dai_link->codec_dai_name; + } + + if (!dai_link->codecs) { + dev_err(card->dev, "ASoC: DAI link has no CODECs\n"); + return -EINVAL; + } + + return 0; +} + /** * snd_soc_register_card - Register a card with the ASoC core * @@ -3710,7 +3819,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_digital_mute); */ int snd_soc_register_card(struct snd_soc_card *card) { - int i, ret; + int i, j, ret;
if (!card->name || !card->dev) return -EINVAL; @@ -3718,22 +3827,29 @@ int snd_soc_register_card(struct snd_soc_card *card) for (i = 0; i < card->num_links; i++) { struct snd_soc_dai_link *link = &card->dai_link[i];
- /* - * Codec must be specified by 1 of name or OF node, - * not both or neither. - */ - if (!!link->codec_name == !!link->codec_of_node) { - dev_err(card->dev, - "ASoC: Neither/both codec name/of_node are set for %s\n", - link->name); - return -EINVAL; + ret = snd_soc_init_multicodec(card, link); + if (ret) { + dev_err(card->dev, "ASoC: failed to init multicodec\n"); + return ret; } - /* Codec DAI name must be specified */ - if (!link->codec_dai_name) { - dev_err(card->dev, - "ASoC: codec_dai_name not set for %s\n", - link->name); - return -EINVAL; + + for (j = 0; j < link->num_codecs; j++) { + /* + * Codec must be specified by 1 of name or OF node, + * not both or neither. + */ + if (!!link->codecs[j].name == + !!link->codecs[j].of_node) { + dev_err(card->dev, "ASoC: Neither/both codec name/of_node are set for %s\n", + link->name); + return -EINVAL; + } + /* Codec DAI name must be specified */ + if (!link->codecs[j].dai_name) { + dev_err(card->dev, "ASoC: codec_dai_name not set for %s\n", + link->name); + return -EINVAL; + } }
/* @@ -3786,9 +3902,17 @@ int snd_soc_register_card(struct snd_soc_card *card) card->num_rtd = 0; card->rtd_aux = &card->rtd[card->num_links];
- for (i = 0; i < card->num_links; i++) + for (i = 0; i < card->num_links; i++) { card->rtd[i].dai_link = &card->dai_link[i];
+ card->rtd[i].codec_dais = devm_kzalloc(card->dev, + sizeof(struct snd_soc_dai *) * + (card->rtd[i].dai_link->num_codecs), + GFP_KERNEL); + if (card->rtd->codec_dais == NULL) + return -ENOMEM; + } + INIT_LIST_HEAD(&card->list); INIT_LIST_HEAD(&card->dapm_dirty); card->instantiated = 0; @@ -3801,10 +3925,16 @@ int snd_soc_register_card(struct snd_soc_card *card)
/* deactivate pins to sleep state */ for (i = 0; i < card->num_rtd; i++) { - struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai; - struct snd_soc_dai *codec_dai = card->rtd[i].codec_dai; - if (!codec_dai->active) - pinctrl_pm_select_sleep_state(codec_dai->dev); + struct snd_soc_pcm_runtime *rtd = &card->rtd[i]; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int j; + + for (j = 0; j < rtd->num_codecs; j++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[j]; + if (!codec_dai->active) + pinctrl_pm_select_sleep_state(codec_dai->dev); + } + if (!cpu_dai->active) pinctrl_pm_select_sleep_state(cpu_dai->dev); } diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index a2475e1..a322f11 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -2079,12 +2079,8 @@ int snd_soc_dapm_mixer_update_power(struct snd_soc_dapm_context *dapm, } EXPORT_SYMBOL_GPL(snd_soc_dapm_mixer_update_power);
-/* show dapm widget status in sys fs */ -static ssize_t dapm_widget_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t dapm_widget_show_codec(struct snd_soc_codec *codec, char *buf) { - struct snd_soc_pcm_runtime *rtd = dev_get_drvdata(dev); - struct snd_soc_codec *codec =rtd->codec; struct snd_soc_dapm_widget *w; int count = 0; char *state = "not set"; @@ -2137,6 +2133,21 @@ static ssize_t dapm_widget_show(struct device *dev, return count; }
+/* show dapm widget status in sys fs */ +static ssize_t dapm_widget_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_soc_pcm_runtime *rtd = dev_get_drvdata(dev); + int i, count = 0; + + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_codec *codec = rtd->codec_dais[i]->codec; + count += dapm_widget_show_codec(codec, buf + count); + } + + return count; +} + static DEVICE_ATTR(dapm_widget, 0444, dapm_widget_show, NULL);
int snd_soc_dapm_sys_add(struct device *dev) @@ -3397,24 +3408,17 @@ int snd_soc_dapm_link_dai_widgets(struct snd_soc_card *card) return 0; }
-void snd_soc_dapm_connect_dai_link_widgets(struct snd_soc_card *card) +static void dapm_connect_dai_link_widgets(struct snd_soc_card *card, + struct snd_soc_pcm_runtime *rtd) { - struct snd_soc_pcm_runtime *rtd = card->rtd; - struct snd_soc_dai *cpu_dai, *codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; struct snd_soc_dapm_route r; int i;
memset(&r, 0, sizeof(r));
- /* for each BE DAI link... */ - for (i = 0; i < card->num_rtd; i++) { - rtd = &card->rtd[i]; - cpu_dai = rtd->cpu_dai; - codec_dai = rtd->codec_dai; - - /* dynamic FE links have no fixed DAI mapping */ - if (rtd->dai_link->dynamic) - continue; + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i];
/* there is no point in connecting BE DAI links with dummies */ if (snd_soc_dai_is_dummy(codec_dai) || @@ -3442,17 +3446,32 @@ void snd_soc_dapm_connect_dai_link_widgets(struct snd_soc_card *card)
snd_soc_dapm_add_route(&card->dapm, &r, true); } - } }
-static void soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd, int stream, - int event) +void snd_soc_dapm_connect_dai_link_widgets(struct snd_soc_card *card) { + struct snd_soc_pcm_runtime *rtd = card->rtd; + int i; + + /* for each BE DAI link... */ + for (i = 0; i < card->num_rtd; i++) { + rtd = &card->rtd[i]; + + /* dynamic FE links have no fixed DAI mapping */ + if (rtd->dai_link->dynamic) + continue; + + dapm_connect_dai_link_widgets(card, rtd); + } +}
+static void soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_dai *cpu_dai, + struct snd_soc_dai *codec_dai, + int stream, int event) +{ struct snd_soc_dapm_widget *w_cpu, *w_codec; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai;
if (stream == SNDRV_PCM_STREAM_PLAYBACK) { w_cpu = cpu_dai->playback_widget; @@ -3518,9 +3537,15 @@ void snd_soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd, int stream, int event) { struct snd_soc_card *card = rtd->card; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai; + int i;
mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); - soc_dapm_stream_event(rtd, stream, event); + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + soc_dapm_stream_event(rtd, cpu_dai, codec_dai, stream, event); + } mutex_unlock(&card->dapm_mutex); }
diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 88230ea..a56a1d5 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -7,7 +7,7 @@ * Copyright (C) 2010 Texas Instruments Inc. * * Authors: Liam Girdwood lrg@ti.com - * Mark Brown broonie@opensource.wolfsonmicro.com + * Mark Brown broonie@opensource.wolfsonmicro.com * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -47,22 +47,26 @@ void snd_soc_runtime_activate(struct snd_soc_pcm_runtime *rtd, int stream) { struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + int i;
lockdep_assert_held(&rtd->pcm_mutex);
if (stream == SNDRV_PCM_STREAM_PLAYBACK) { cpu_dai->playback_active++; - codec_dai->playback_active++; + for (i = 0; i < rtd->num_codecs; i++) + rtd->codec_dais[i]->playback_active++; } else { cpu_dai->capture_active++; - codec_dai->capture_active++; + for (i = 0; i < rtd->num_codecs; i++) + rtd->codec_dais[i]->capture_active++; }
cpu_dai->active++; - codec_dai->active++; cpu_dai->component->active++; - codec_dai->component->active++; + for (i = 0; i < rtd->num_codecs; i++) { + rtd->codec_dais[i]->active++; + rtd->codec_dais[i]->component->active++; + } }
/** @@ -78,22 +82,26 @@ void snd_soc_runtime_activate(struct snd_soc_pcm_runtime *rtd, int stream) void snd_soc_runtime_deactivate(struct snd_soc_pcm_runtime *rtd, int stream) { struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + int i;
lockdep_assert_held(&rtd->pcm_mutex);
if (stream == SNDRV_PCM_STREAM_PLAYBACK) { cpu_dai->playback_active--; - codec_dai->playback_active--; + for (i = 0; i < rtd->num_codecs; i++) + rtd->codec_dais[i]->playback_active--; } else { cpu_dai->capture_active--; - codec_dai->capture_active--; + for (i = 0; i < rtd->num_codecs; i++) + rtd->codec_dais[i]->capture_active--; }
cpu_dai->active--; - codec_dai->active--; cpu_dai->component->active--; - codec_dai->component->active--; + for (i = 0; i < rtd->num_codecs; i++) { + rtd->codec_dais[i]->component->active--; + rtd->codec_dais[i]->active--; + } }
/** @@ -107,11 +115,15 @@ void snd_soc_runtime_deactivate(struct snd_soc_pcm_runtime *rtd, int stream) */ bool snd_soc_runtime_ignore_pmdown_time(struct snd_soc_pcm_runtime *rtd) { + int i, ignore = 0; + if (!rtd->pmdown_time || rtd->dai_link->ignore_pmdown_time) return true;
- return rtd->cpu_dai->component->ignore_pmdown_time && - rtd->codec_dai->component->ignore_pmdown_time; + for (i = 0; (i < rtd->num_codecs) && !ignore; i++) + ignore |= rtd->codec_dais[i]->component->ignore_pmdown_time; + + return rtd->cpu_dai->component->ignore_pmdown_time && ignore; }
/** @@ -222,8 +234,7 @@ static int soc_pcm_params_symmetry(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - unsigned int rate, channels, sample_bits, symmetry; + unsigned int rate, channels, sample_bits, symmetry, i;
rate = params_rate(params); channels = params_channels(params); @@ -231,8 +242,12 @@ static int soc_pcm_params_symmetry(struct snd_pcm_substream *substream,
/* reject unmatched parameters when applying symmetry */ symmetry = cpu_dai->driver->symmetric_rates || - codec_dai->driver->symmetric_rates || rtd->dai_link->symmetric_rates; + + for (i = 0; i < rtd->num_codecs; i++) + symmetry = rtd->codec_dais[i]->driver->symmetric_rates || + symmetry; + if (symmetry && cpu_dai->rate && cpu_dai->rate != rate) { dev_err(rtd->dev, "ASoC: unmatched rate symmetry: %d - %d\n", cpu_dai->rate, rate); @@ -240,8 +255,12 @@ static int soc_pcm_params_symmetry(struct snd_pcm_substream *substream, }
symmetry = cpu_dai->driver->symmetric_channels || - codec_dai->driver->symmetric_channels || rtd->dai_link->symmetric_channels; + + for (i = 0; i < rtd->num_codecs; i++) + symmetry = rtd->codec_dais[i]->driver->symmetric_channels || + symmetry; + if (symmetry && cpu_dai->channels && cpu_dai->channels != channels) { dev_err(rtd->dev, "ASoC: unmatched channel symmetry: %d - %d\n", cpu_dai->channels, channels); @@ -249,8 +268,12 @@ static int soc_pcm_params_symmetry(struct snd_pcm_substream *substream, }
symmetry = cpu_dai->driver->symmetric_samplebits || - codec_dai->driver->symmetric_samplebits || rtd->dai_link->symmetric_samplebits; + + for (i = 0; i < rtd->num_codecs; i++) + symmetry = rtd->codec_dais[i]->driver->symmetric_samplebits || + symmetry; + if (symmetry && cpu_dai->sample_bits && cpu_dai->sample_bits != sample_bits) { dev_err(rtd->dev, "ASoC: unmatched sample bits symmetry: %d - %d\n", cpu_dai->sample_bits, sample_bits); @@ -264,15 +287,20 @@ static bool soc_pcm_has_symmetry(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai_driver *cpu_driver = rtd->cpu_dai->driver; - struct snd_soc_dai_driver *codec_driver = rtd->codec_dai->driver; struct snd_soc_dai_link *link = rtd->dai_link; + unsigned int symmetry, i; + + symmetry = cpu_driver->symmetric_rates || link->symmetric_rates || + cpu_driver->symmetric_channels || link->symmetric_channels || + cpu_driver->symmetric_samplebits || link->symmetric_samplebits;
- return cpu_driver->symmetric_rates || codec_driver->symmetric_rates || - link->symmetric_rates || cpu_driver->symmetric_channels || - codec_driver->symmetric_channels || link->symmetric_channels || - cpu_driver->symmetric_samplebits || - codec_driver->symmetric_samplebits || - link->symmetric_samplebits; + for (i = 0; i < rtd->num_codecs; i++) + symmetry = symmetry || + rtd->codec_dais[i]->driver->symmetric_rates || + rtd->codec_dais[i]->driver->symmetric_channels || + rtd->codec_dais[i]->driver->symmetric_samplebits; + + return symmetry; }
/* @@ -310,32 +338,65 @@ static void soc_pcm_apply_msb(struct snd_pcm_substream *substream, } }
-static void soc_pcm_init_runtime_hw(struct snd_pcm_runtime *runtime, - struct snd_soc_pcm_stream *codec_stream, - struct snd_soc_pcm_stream *cpu_stream) +static void soc_pcm_init_runtime_hw(struct snd_pcm_substream *substream) { + struct snd_pcm_runtime *runtime = substream->runtime; struct snd_pcm_hardware *hw = &runtime->hw; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai_driver *cpu_dai_drv = rtd->cpu_dai->driver; + struct snd_soc_dai_driver *codec_dai_drv; + struct snd_soc_pcm_stream *codec_stream; + struct snd_soc_pcm_stream *cpu_stream; + unsigned int chan_min = 0, chan_max = UINT_MAX; + unsigned int rate_min = 0, rate_max = UINT_MAX; + unsigned int rates = UINT_MAX; + u64 formats = ULLONG_MAX; + int i;
- hw->channels_min = max(codec_stream->channels_min, - cpu_stream->channels_min); - hw->channels_max = min(codec_stream->channels_max, - cpu_stream->channels_max); - if (hw->formats) - hw->formats &= codec_stream->formats & cpu_stream->formats; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + cpu_stream = &cpu_dai_drv->playback; else - hw->formats = codec_stream->formats & cpu_stream->formats; - hw->rates = snd_pcm_rate_mask_intersect(codec_stream->rates, - cpu_stream->rates); + cpu_stream = &cpu_dai_drv->capture;
- hw->rate_min = 0; - hw->rate_max = UINT_MAX; + /* first calculate min/max only for CODECs in the DAI link */ + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai_drv = rtd->codec_dais[i]->driver; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + codec_stream = &codec_dai_drv->playback; + else + codec_stream = &codec_dai_drv->capture; + chan_min = max(chan_min, codec_stream->channels_min); + chan_max = min(chan_max, codec_stream->channels_max); + rate_min = max(rate_min, codec_stream->rate_min); + rate_max = min_not_zero(rate_max, codec_stream->rate_max); + formats &= codec_stream->formats; + rates = snd_pcm_rate_mask_intersect(codec_stream->rates, rates); + } + + /* + * chan min/max cannot be enforced if there are multiple CODEC DAIs + * connected to a single CPU DAI, use CPU DAI's directly and let + * channel allocation be fixed up later + */ + if (rtd->num_codecs > 1) { + chan_min = cpu_stream->channels_min; + chan_max = cpu_stream->channels_max; + } + + hw->channels_min = max(chan_min, cpu_stream->channels_min); + hw->channels_max = min(chan_max, cpu_stream->channels_max); + if (hw->formats) + hw->formats &= formats & cpu_stream->formats; + else + hw->formats = formats & cpu_stream->formats; + hw->rates = snd_pcm_rate_mask_intersect(rates, cpu_stream->rates);
snd_pcm_limit_hw_rates(runtime);
hw->rate_min = max(hw->rate_min, cpu_stream->rate_min); - hw->rate_min = max(hw->rate_min, codec_stream->rate_min); + hw->rate_min = max(hw->rate_min, rate_min); hw->rate_max = min_not_zero(hw->rate_max, cpu_stream->rate_max); - hw->rate_max = min_not_zero(hw->rate_max, codec_stream->rate_max); + hw->rate_max = min_not_zero(hw->rate_max, rate_max); }
/* @@ -349,15 +410,16 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) struct snd_pcm_runtime *runtime = substream->runtime; struct snd_soc_platform *platform = rtd->platform; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - struct snd_soc_dai_driver *cpu_dai_drv = cpu_dai->driver; - struct snd_soc_dai_driver *codec_dai_drv = codec_dai->driver; - int ret = 0; + struct snd_soc_dai *codec_dai; + const char *codec_dai_name = "multicodec"; + int i, ret = 0;
pinctrl_pm_select_default_state(cpu_dai->dev); - pinctrl_pm_select_default_state(codec_dai->dev); + for (i = 0; i < rtd->num_codecs; i++) + pinctrl_pm_select_default_state(rtd->codec_dais[i]->dev); pm_runtime_get_sync(cpu_dai->dev); - pm_runtime_get_sync(codec_dai->dev); + for (i = 0; i < rtd->num_codecs; i++) + pm_runtime_get_sync(rtd->codec_dais[i]->dev); pm_runtime_get_sync(platform->dev);
mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); @@ -381,13 +443,23 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) } }
- if (codec_dai->driver->ops && codec_dai->driver->ops->startup) { - ret = codec_dai->driver->ops->startup(substream, codec_dai); - if (ret < 0) { - dev_err(codec_dai->dev, "ASoC: can't open codec" - " %s: %d\n", codec_dai->name, ret); - goto codec_dai_err; + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops && codec_dai->driver->ops->startup) { + ret = codec_dai->driver->ops->startup(substream, + codec_dai); + if (ret < 0) { + dev_err(codec_dai->dev, + "ASoC: can't open codec %s: %d\n", + codec_dai->name, ret); + goto codec_dai_err; + } } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + codec_dai->tx_mask = 0; + else + codec_dai->rx_mask = 0; }
if (rtd->dai_link->ops && rtd->dai_link->ops->startup) { @@ -404,13 +476,10 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) goto dynamic;
/* Check that the codec and cpu DAIs are compatible */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - soc_pcm_init_runtime_hw(runtime, &codec_dai_drv->playback, - &cpu_dai_drv->playback); - } else { - soc_pcm_init_runtime_hw(runtime, &codec_dai_drv->capture, - &cpu_dai_drv->capture); - } + soc_pcm_init_runtime_hw(substream); + + if (rtd->num_codecs == 1) + codec_dai_name = rtd->codec_dai->name;
if (soc_pcm_has_symmetry(substream)) runtime->hw.info |= SNDRV_PCM_INFO_JOINT_DUPLEX; @@ -418,22 +487,22 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) ret = -EINVAL; if (!runtime->hw.rates) { printk(KERN_ERR "ASoC: %s <-> %s No matching rates\n", - codec_dai->name, cpu_dai->name); + codec_dai_name, cpu_dai->name); goto config_err; } if (!runtime->hw.formats) { printk(KERN_ERR "ASoC: %s <-> %s No matching formats\n", - codec_dai->name, cpu_dai->name); + codec_dai_name, cpu_dai->name); goto config_err; } if (!runtime->hw.channels_min || !runtime->hw.channels_max || runtime->hw.channels_min > runtime->hw.channels_max) { printk(KERN_ERR "ASoC: %s <-> %s No matching channels\n", - codec_dai->name, cpu_dai->name); + codec_dai_name, cpu_dai->name); goto config_err; }
- soc_pcm_apply_msb(substream, codec_dai); + soc_pcm_apply_msb(substream, rtd->codec_dais[0]); soc_pcm_apply_msb(substream, cpu_dai);
/* Symmetry only applies if we've already got an active stream. */ @@ -443,14 +512,17 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) goto config_err; }
- if (codec_dai->active) { - ret = soc_pcm_apply_symmetry(substream, codec_dai); - if (ret != 0) - goto config_err; + for (i = 0; i < rtd->num_codecs; i++) { + if (rtd->codec_dais[i]->active) { + ret = soc_pcm_apply_symmetry(substream, + rtd->codec_dais[i]); + if (ret != 0) + goto config_err; + } }
pr_debug("ASoC: %s <-> %s info:\n", - codec_dai->name, cpu_dai->name); + codec_dai_name, cpu_dai->name); pr_debug("ASoC: rate mask 0x%x\n", runtime->hw.rates); pr_debug("ASoC: min ch %d max ch %d\n", runtime->hw.channels_min, runtime->hw.channels_max); @@ -469,10 +541,15 @@ config_err: rtd->dai_link->ops->shutdown(substream);
machine_err: - if (codec_dai->driver->ops->shutdown) - codec_dai->driver->ops->shutdown(substream, codec_dai); + i = rtd->num_codecs;
codec_dai_err: + while (--i >= 0) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops->shutdown) + codec_dai->driver->ops->shutdown(substream, codec_dai); + } + if (platform->driver->ops && platform->driver->ops->close) platform->driver->ops->close(substream);
@@ -483,10 +560,13 @@ out: mutex_unlock(&rtd->pcm_mutex);
pm_runtime_put(platform->dev); - pm_runtime_put(codec_dai->dev); + for (i = 0; i < rtd->num_codecs; i++) + pm_runtime_put(rtd->codec_dais[i]->dev); pm_runtime_put(cpu_dai->dev); - if (!codec_dai->active) - pinctrl_pm_select_sleep_state(codec_dai->dev); + for (i = 0; i < rtd->num_codecs; i++) { + if (!rtd->codec_dais[i]->active) + pinctrl_pm_select_sleep_state(rtd->codec_dais[i]->dev); + } if (!cpu_dai->active) pinctrl_pm_select_sleep_state(cpu_dai->dev);
@@ -502,7 +582,7 @@ static void close_delayed_work(struct work_struct *work) { struct snd_soc_pcm_runtime *rtd = container_of(work, struct snd_soc_pcm_runtime, delayed_work.work); - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dais[0];
mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass);
@@ -531,7 +611,8 @@ static int soc_pcm_close(struct snd_pcm_substream *substream) struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_platform *platform = rtd->platform; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *codec_dai; + int i;
mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass);
@@ -541,14 +622,20 @@ static int soc_pcm_close(struct snd_pcm_substream *substream) if (!cpu_dai->active) cpu_dai->rate = 0;
- if (!codec_dai->active) - codec_dai->rate = 0; + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (!codec_dai->active) + codec_dai->rate = 0; + }
if (cpu_dai->driver->ops->shutdown) cpu_dai->driver->ops->shutdown(substream, cpu_dai);
- if (codec_dai->driver->ops->shutdown) - codec_dai->driver->ops->shutdown(substream, codec_dai); + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops->shutdown) + codec_dai->driver->ops->shutdown(substream, codec_dai); + }
if (rtd->dai_link->ops && rtd->dai_link->ops->shutdown) rtd->dai_link->ops->shutdown(substream); @@ -579,10 +666,13 @@ static int soc_pcm_close(struct snd_pcm_substream *substream) mutex_unlock(&rtd->pcm_mutex);
pm_runtime_put(platform->dev); - pm_runtime_put(codec_dai->dev); + for (i = 0; i < rtd->num_codecs; i++) + pm_runtime_put(rtd->codec_dais[i]->dev); pm_runtime_put(cpu_dai->dev); - if (!codec_dai->active) - pinctrl_pm_select_sleep_state(codec_dai->dev); + for (i = 0; i < rtd->num_codecs; i++) { + if (!rtd->codec_dais[i]->active) + pinctrl_pm_select_sleep_state(rtd->codec_dais[i]->dev); + } if (!cpu_dai->active) pinctrl_pm_select_sleep_state(cpu_dai->dev);
@@ -599,8 +689,8 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_platform *platform = rtd->platform; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - int ret = 0; + struct snd_soc_dai *codec_dai; + int i, ret = 0;
mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass);
@@ -622,12 +712,16 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) } }
- if (codec_dai->driver->ops && codec_dai->driver->ops->prepare) { - ret = codec_dai->driver->ops->prepare(substream, codec_dai); - if (ret < 0) { - dev_err(codec_dai->dev, "ASoC: DAI prepare error: %d\n", - ret); - goto out; + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops && codec_dai->driver->ops->prepare) { + ret = codec_dai->driver->ops->prepare(substream, + codec_dai); + if (ret < 0) { + dev_err(codec_dai->dev, + "ASoC: DAI prepare error: %d\n", ret); + goto out; + } } }
@@ -650,13 +744,26 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) snd_soc_dapm_stream_event(rtd, substream->stream, SND_SOC_DAPM_STREAM_START);
- snd_soc_dai_digital_mute(codec_dai, 0, substream->stream); + for (i = 0; i < rtd->num_codecs; i++) + snd_soc_dai_digital_mute(rtd->codec_dais[i], 0, + substream->stream);
out: mutex_unlock(&rtd->pcm_mutex); return ret; }
+static void soc_pcm_codec_params_fixup(struct snd_pcm_hw_params *params, + unsigned int mask) +{ + struct snd_interval *interval; + int channels = hweight_long(mask); + + interval = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + interval->min = channels; + interval->max = channels; +} + /* * Called by ALSA when the hardware params are set by application. This * function can also be called multiple times and can allocate buffers @@ -668,8 +775,7 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_platform *platform = rtd->platform; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - int ret = 0; + int i, ret = 0;
mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass);
@@ -686,13 +792,39 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, } }
- if (codec_dai->driver->ops && codec_dai->driver->ops->hw_params) { - ret = codec_dai->driver->ops->hw_params(substream, params, codec_dai); - if (ret < 0) { - dev_err(codec_dai->dev, "ASoC: can't set %s hw params:" - " %d\n", codec_dai->name, ret); - goto codec_err; + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; + struct snd_pcm_hw_params *codec_params; + + codec_params = &codec_dai->params[substream->stream]; + + /* copy params for each codec */ + memcpy(codec_params, params, sizeof(struct snd_pcm_hw_params)); + + /* fixup params based on TDM slot masks */ + if (codec_dai->tx_mask) + soc_pcm_codec_params_fixup(codec_params, + codec_dai->tx_mask); + if (codec_dai->rx_mask) + soc_pcm_codec_params_fixup(codec_params, + codec_dai->rx_mask); + + if (codec_dai->driver->ops && + codec_dai->driver->ops->hw_params) { + ret = codec_dai->driver->ops->hw_params(substream, + codec_params, codec_dai); + if (ret < 0) { + dev_err(codec_dai->dev, + "ASoC: can't set %s hw params: %d\n", + codec_dai->name, ret); + goto codec_err; + } } + + codec_dai->rate = params_rate(codec_params); + codec_dai->channels = params_channels(codec_params); + codec_dai->sample_bits = snd_pcm_format_physical_width( + params_format(codec_params)); }
if (cpu_dai->driver->ops && cpu_dai->driver->ops->hw_params) { @@ -719,11 +851,6 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, cpu_dai->sample_bits = snd_pcm_format_physical_width(params_format(params));
- codec_dai->rate = params_rate(params); - codec_dai->channels = params_channels(params); - codec_dai->sample_bits = - snd_pcm_format_physical_width(params_format(params)); - out: mutex_unlock(&rtd->pcm_mutex); return ret; @@ -733,10 +860,16 @@ platform_err: cpu_dai->driver->ops->hw_free(substream, cpu_dai);
interface_err: - if (codec_dai->driver->ops && codec_dai->driver->ops->hw_free) - codec_dai->driver->ops->hw_free(substream, codec_dai); + i = rtd->num_codecs;
codec_err: + while (--i >= 0) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops && codec_dai->driver->ops->hw_free) + codec_dai->driver->ops->hw_free(substream, codec_dai); + codec_dai->rate = 0; + } + if (rtd->dai_link->ops && rtd->dai_link->ops->hw_free) rtd->dai_link->ops->hw_free(substream);
@@ -752,8 +885,9 @@ static int soc_pcm_hw_free(struct snd_pcm_substream *substream) struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_platform *platform = rtd->platform; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *codec_dai; bool playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + int i;
mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass);
@@ -764,16 +898,22 @@ static int soc_pcm_hw_free(struct snd_pcm_substream *substream) cpu_dai->sample_bits = 0; }
- if (codec_dai->active == 1) { - codec_dai->rate = 0; - codec_dai->channels = 0; - codec_dai->sample_bits = 0; + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->active == 1) { + codec_dai->rate = 0; + codec_dai->channels = 0; + codec_dai->sample_bits = 0; + } }
/* apply codec digital mute */ - if ((playback && codec_dai->playback_active == 1) || - (!playback && codec_dai->capture_active == 1)) - snd_soc_dai_digital_mute(codec_dai, 1, substream->stream); + for (i = 0; i < rtd->num_codecs; i++) { + if ((playback && rtd->codec_dais[i]->playback_active == 1) || + (!playback && rtd->codec_dais[i]->capture_active == 1)) + snd_soc_dai_digital_mute(rtd->codec_dais[i], 1, + substream->stream); + }
/* free any machine hw params */ if (rtd->dai_link->ops && rtd->dai_link->ops->hw_free) @@ -784,8 +924,11 @@ static int soc_pcm_hw_free(struct snd_pcm_substream *substream) platform->driver->ops->hw_free(substream);
/* now free hw params for the DAIs */ - if (codec_dai->driver->ops && codec_dai->driver->ops->hw_free) - codec_dai->driver->ops->hw_free(substream, codec_dai); + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops && codec_dai->driver->ops->hw_free) + codec_dai->driver->ops->hw_free(substream, codec_dai); + }
if (cpu_dai->driver->ops && cpu_dai->driver->ops->hw_free) cpu_dai->driver->ops->hw_free(substream, cpu_dai); @@ -799,13 +942,17 @@ static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_platform *platform = rtd->platform; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - int ret; - - if (codec_dai->driver->ops && codec_dai->driver->ops->trigger) { - ret = codec_dai->driver->ops->trigger(substream, cmd, codec_dai); - if (ret < 0) - return ret; + struct snd_soc_dai *codec_dai; + int i, ret; + + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops && codec_dai->driver->ops->trigger) { + ret = codec_dai->driver->ops->trigger(substream, + cmd, codec_dai); + if (ret < 0) + return ret; + } }
if (platform->driver->ops && platform->driver->ops->trigger) { @@ -828,14 +975,18 @@ static int soc_pcm_bespoke_trigger(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_platform *platform = rtd->platform; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - int ret; - - if (codec_dai->driver->ops && - codec_dai->driver->ops->bespoke_trigger) { - ret = codec_dai->driver->ops->bespoke_trigger(substream, cmd, codec_dai); - if (ret < 0) - return ret; + struct snd_soc_dai *codec_dai; + int i, ret; + + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops && + codec_dai->driver->ops->bespoke_trigger) { + ret = codec_dai->driver->ops->bespoke_trigger(substream, + cmd, codec_dai); + if (ret < 0) + return ret; + } }
if (platform->driver->bespoke_trigger) { @@ -861,10 +1012,12 @@ static snd_pcm_uframes_t soc_pcm_pointer(struct snd_pcm_substream *substream) struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_platform *platform = rtd->platform; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *codec_dai; struct snd_pcm_runtime *runtime = substream->runtime; snd_pcm_uframes_t offset = 0; snd_pcm_sframes_t delay = 0; + snd_pcm_sframes_t codec_delay = 0; + int i;
if (platform->driver->ops && platform->driver->ops->pointer) offset = platform->driver->ops->pointer(substream); @@ -872,11 +1025,21 @@ static snd_pcm_uframes_t soc_pcm_pointer(struct snd_pcm_substream *substream) if (cpu_dai->driver->ops && cpu_dai->driver->ops->delay) delay += cpu_dai->driver->ops->delay(substream, cpu_dai);
- if (codec_dai->driver->ops && codec_dai->driver->ops->delay) - delay += codec_dai->driver->ops->delay(substream, codec_dai); + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops && codec_dai->driver->ops->delay) + codec_delay = max(codec_delay, + codec_dai->driver->ops->delay(substream, + codec_dai)); + } + delay += codec_delay;
+ /* + * None of the existing platform drivers implement delay(), so + * for now the codec_dai of first multicodec entry is used + */ if (platform->driver->delay) - delay += platform->driver->delay(substream, codec_dai); + delay += platform->driver->delay(substream, rtd->codec_dais[0]);
runtime->delay = delay;
@@ -979,7 +1142,7 @@ static struct snd_soc_pcm_runtime *dpcm_get_be(struct snd_soc_card *card, struct snd_soc_dapm_widget *widget, int stream) { struct snd_soc_pcm_runtime *be; - int i; + int i, j;
if (stream == SNDRV_PCM_STREAM_PLAYBACK) { for (i = 0; i < card->num_links; i++) { @@ -988,9 +1151,14 @@ static struct snd_soc_pcm_runtime *dpcm_get_be(struct snd_soc_card *card, if (!be->dai_link->no_pcm) continue;
- if (be->cpu_dai->playback_widget == widget || - be->codec_dai->playback_widget == widget) + if (be->cpu_dai->playback_widget == widget) return be; + + for (j = 0; j < be->num_codecs; j++) { + struct snd_soc_dai *dai = be->codec_dais[j]; + if (dai->playback_widget == widget) + return be; + } } } else {
@@ -1000,9 +1168,14 @@ static struct snd_soc_pcm_runtime *dpcm_get_be(struct snd_soc_card *card, if (!be->dai_link->no_pcm) continue;
- if (be->cpu_dai->capture_widget == widget || - be->codec_dai->capture_widget == widget) + if (be->cpu_dai->capture_widget == widget) return be; + + for (j = 0; j < be->num_codecs; j++) { + struct snd_soc_dai *dai = be->codec_dais[j]; + if (dai->capture_widget == widget) + return be; + } } }
@@ -1065,6 +1238,7 @@ static int dpcm_prune_paths(struct snd_soc_pcm_runtime *fe, int stream,
/* Destroy any old FE <--> BE connections */ list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + unsigned int i;
/* is there a valid CPU DAI widget for this BE */ widget = dai_get_widget(dpcm->be->cpu_dai, stream); @@ -1074,11 +1248,14 @@ static int dpcm_prune_paths(struct snd_soc_pcm_runtime *fe, int stream, continue;
/* is there a valid CODEC DAI widget for this BE */ - widget = dai_get_widget(dpcm->be->codec_dai, stream); + for (i = 0; i < dpcm->be->num_codecs; i++) { + struct snd_soc_dai *dai = dpcm->be->codec_dais[i]; + widget = dai_get_widget(dai, stream);
- /* prune the BE if it's no longer in our active list */ - if (widget && widget_in_list(list, widget)) - continue; + /* prune the BE if it's no longer in our active list */ + if (widget && widget_in_list(list, widget)) + continue; + }
dev_dbg(fe->dev, "ASoC: pruning %s BE %s for %s\n", stream ? "capture" : "playback", @@ -2107,16 +2284,22 @@ int soc_dpcm_be_digital_mute(struct snd_soc_pcm_runtime *fe, int mute) list_for_each_entry(dpcm, clients, list_be) {
struct snd_soc_pcm_runtime *be = dpcm->be; - struct snd_soc_dai *dai = be->codec_dai; - struct snd_soc_dai_driver *drv = dai->driver; + int i;
if (be->dai_link->ignore_suspend) continue;
- dev_dbg(be->dev, "ASoC: BE digital mute %s\n", be->dai_link->name); + for (i = 0; i < be->num_codecs; i++) { + struct snd_soc_dai *dai = be->codec_dais[i]; + struct snd_soc_dai_driver *drv = dai->driver; + + dev_dbg(be->dev, "ASoC: BE digital mute %s\n", + be->dai_link->name);
- if (drv->ops && drv->ops->digital_mute && dai->playback_active) - drv->ops->digital_mute(dai, mute); + if (drv->ops && drv->ops->digital_mute && + dai->playback_active) + drv->ops->digital_mute(dai, mute); + } }
return 0; @@ -2181,22 +2364,28 @@ static int dpcm_fe_dai_close(struct snd_pcm_substream *fe_substream) int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) { struct snd_soc_platform *platform = rtd->platform; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *codec_dai; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; struct snd_pcm *pcm; char new_name[64]; int ret = 0, playback = 0, capture = 0; + int i;
if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) { playback = rtd->dai_link->dpcm_playback; capture = rtd->dai_link->dpcm_capture; } else { - if (codec_dai->driver->playback.channels_min && - cpu_dai->driver->playback.channels_min) - playback = 1; - if (codec_dai->driver->capture.channels_min && - cpu_dai->driver->capture.channels_min) - capture = 1; + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->playback.channels_min && + cpu_dai->driver->playback.channels_min) + playback++; + if (codec_dai->driver->capture.channels_min && + cpu_dai->driver->capture.channels_min) + capture++; + } + capture = (rtd->num_codecs == capture); + playback = (rtd->num_codecs == playback); }
if (rtd->dai_link->playback_only) { @@ -2222,7 +2411,9 @@ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) rtd->dai_link->stream_name); else snprintf(new_name, sizeof(new_name), "%s %s-%d", - rtd->dai_link->stream_name, codec_dai->name, num); + rtd->dai_link->stream_name, + (rtd->num_codecs > 1) ? + "multicodec" : rtd->codec_dai->name, num);
ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback, capture, &pcm); @@ -2295,8 +2486,9 @@ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
pcm->private_free = platform->driver->pcm_free; out: - dev_info(rtd->card->dev, "%s <-> %s mapping ok\n", codec_dai->name, - cpu_dai->name); + dev_info(rtd->card->dev, "%s <-> %s mapping ok\n", + (rtd->num_codecs > 1) ? "multicodec" : rtd->codec_dai->name, + cpu_dai->name); return ret; }
On 04/24/2014 02:01 PM, Benoit Cousson wrote:
DAI link assumes a one to one mapping between CPU DAI and CODEC. In some cases, the same CPU DAI can be connected to several codecs. This is the case for example, if you connect two mono codecs to the same I2S link in order to have a stereo card. The current ASoC implementation does not allow such setup.
Add support for DAI links composed of a single CPU DAI and multiple CODECs. Sound cards have to pass the CODECs array in the corresponding DAI link through a new 'snd_soc_dai_link_component' struct. Each CODEC in this array is described in the same manner single CODEC DAIs are (either DT/OF node or codec_name).
Fix a trailing space in the header as well.
Based on an original code done by Misael.
Signed-off-by: Benoit Cousson bcousson@baylibre.com Signed-off-by: Misael Lopez Cruz misael.lopez@ti.com Signed-off-by: Fabien Parent fparent@baylibre.com
Looks mostly good. A few bits and pieces here and there, comments inline.
[...]
diff --git a/include/sound/soc.h b/include/sound/soc.h index 0fadb3c..7933512 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -821,6 +821,12 @@ struct snd_soc_platform_driver { int (*bespoke_trigger)(struct snd_pcm_substream *, int); };
+struct snd_soc_dai_link_component {
- const char *name;
- const struct device_node *of_node;
- const char *dai_name;
+};
- struct snd_soc_platform { const char *name; int id;
@@ -870,6 +876,10 @@ struct snd_soc_dai_link { const struct device_node *codec_of_node; /* You MUST specify the DAI name within the codec */ const char *codec_dai_name;
- struct snd_soc_dai_link_component *codecs;
Should probably be const
- unsigned int num_codecs;
- /*
- You MAY specify the link's platform/PCM/DMA driver, either by
- device name, or by DT/OF node, but not both. Some forms of link
[...]
@@ -620,23 +631,26 @@ int soc_new_compress(struct snd_soc_pcm_runtime *rtd, int num) { struct snd_soc_codec *codec = rtd->codec; struct snd_soc_platform *platform = rtd->platform;
- struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; struct snd_compr *compr; struct snd_pcm *be_pcm; char new_name[64];
- int ret = 0, direction = 0;
- int i, ret = 0, direction = 0;
/* check client and interface hw capabilities */
snprintf(new_name, sizeof(new_name), "%s %s-%d",
rtd->dai_link->stream_name, codec_dai->name, num);
if (codec_dai->driver->playback.channels_min)
direction = SND_COMPRESS_PLAYBACK;
else if (codec_dai->driver->capture.channels_min)
direction = SND_COMPRESS_CAPTURE;
else
return -EINVAL;
- for (i = 0; i < rtd->num_codecs; i++) {
struct snd_soc_dai *codec_dai = rtd->codec_dais[i];
/* check client and interface hw capabilities */
snprintf(new_name, sizeof(new_name), "%s %s-%d",
rtd->dai_link->stream_name, codec_dai->name, num);
if (codec_dai->driver->playback.channels_min)
direction = SND_COMPRESS_PLAYBACK;
else if (codec_dai->driver->capture.channels_min)
direction = SND_COMPRESS_CAPTURE;
else
return -EINVAL;
- }
This loop makes no sense, you just end up overwriting new_name and direction with each loop iteration.
compr = kzalloc(sizeof(*compr), GFP_KERNEL); if (compr == NULL) { @@ -690,8 +704,11 @@ int soc_new_compress(struct snd_soc_pcm_runtime *rtd, int num) rtd->compr = compr; compr->private_data = rtd;
- printk(KERN_INFO "compress asoc: %s <-> %s mapping ok\n", codec_dai->name,
cpu_dai->name);
for (i = 0; i < rtd->num_codecs; i++) {
struct snd_soc_dai *codec_dai = rtd->codec_dais[i];
printk(KERN_INFO "compress asoc: %s <-> %s mapping ok\n",
codec_dai->name, cpu_dai->name);
} return ret;
compr_err:
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index f18112a..7f68492 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c
[...]
@@ -1294,20 +1324,28 @@ static int soc_aux_dev_init(struct snd_soc_card *card, return 0; }
-static int soc_dai_link_init(struct snd_soc_card *card,
struct snd_soc_codec *codec,
int num)
+static int soc_dai_link_init(struct snd_soc_card *card, int num) { struct snd_soc_dai_link *dai_link = &card->dai_link[num]; struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
- const char *temp;
- int ret;
const char **temp;
int i, ret;
rtd->card = card;
- /* machine controls, routes and widgets are not prefixed */
- temp = codec->name_prefix;
- codec->name_prefix = NULL;
- temp = devm_kzalloc(card->dev, rtd->num_codecs * sizeof(char *),
GFP_KERNEL);
- if (!temp)
return -ENOMEM;
Setting name_prefix temporarily to NULL is no longer necessary, see the patch I just sent out.
for (i = 0; i < rtd->num_codecs; i++) {
/* Make sure all DAPM widgets are instantiated */
snd_soc_dapm_new_widgets(rtd->codec_dais[i]->codec->dapm.card);
/* machine controls, routes and widgets are not prefixed */
temp[i] = rtd->codec_dais[i]->codec->name_prefix;
rtd->codec_dais[i]->codec->name_prefix = NULL;
}
/* do machine specific initialization */ if (dai_link->init) {
[...]
@@ -1586,16 +1626,21 @@ static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order) codec2codec_close_delayed_work);
/* link the DAI widgets */
ret = soc_link_dai_widgets(card, dai_link,
cpu_dai, codec_dai);
if (ret)
return ret;
for (i = 0; i < rtd->num_codecs; i++) {
ret = soc_link_dai_widgets(card, dai_link,
cpu_dai, rtd->codec_dais[i]);
This will create a DAI link widget for each CODEC DAI. The DAI link widget will configure the CPU and the CODEC DAI that are connected to it. If there is one DAI link widget per CODEC DAI this means that the CPU DAI will be connected to multiple DAI link widgets, which means it will be configured once for each CODEC DAI (with possible conflicting configurations).
So there should only be one DAI link widget per DAI link, with the CPU DAI on one side and the CODEC DAIs on the other side. Note that you'll also need to re-work snd_soc_dai_link_event() to handle multiple inputs/outputs. I'd factor that part out into a separate patch.
if (ret)
return ret;
}
} }
/* add platform data for AC97 devices */
- if (rtd->codec_dai->driver->ac97_control)
snd_ac97_dev_add_pdata(codec->ac97, rtd->cpu_dai->ac97_pdata);
for (i = 0; i < rtd->num_codecs; i++) {
if (rtd->codec_dais[i]->driver->ac97_control)
snd_ac97_dev_add_pdata(rtd->codec_dais[i]->codec->ac97,
rtd->cpu_dai->ac97_pdata);
}
return 0; }
@@ -1635,7 +1680,20 @@ static int soc_register_ac97_codec(struct snd_soc_codec *codec,
static int soc_register_ac97_dai_link(struct snd_soc_pcm_runtime *rtd) {
- return soc_register_ac97_codec(rtd->codec, rtd->codec_dai);
- int i, ret;
- for (i = 0; i < rtd->num_codecs; i++) {
struct snd_soc_dai *codec_dai = rtd->codec_dais[i];
ret = soc_register_ac97_codec(codec_dai->codec, codec_dai);
if (ret) {
while (--i >= 0)
soc_unregister_ac97_codec(codec_dai->codec);
You use soc_unregister_ac97_codec here ...
return ret;
}
}
return 0; }
static void soc_unregister_ac97_codec(struct snd_soc_codec *codec)
@@ -1648,7 +1706,10 @@ static void soc_unregister_ac97_codec(struct snd_soc_codec *codec)
static void soc_unregister_ac97_dai_link(struct snd_soc_pcm_runtime *rtd)
... but only define it here. This causes a compile error. Just move soc_unregister_ac97_dai_link before soc_register_ac97_codec
{
- soc_unregister_ac97_codec(rtd->codec);
- int i;
- for (i = 0; i < rtd->num_codecs; i++)
} #endifsoc_unregister_ac97_codec(rtd->codec_dais[i]->codec);
[...]
+static void soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd,
struct snd_soc_dai *cpu_dai,
struct snd_soc_dai *codec_dai,
int stream, int event)
+{ struct snd_soc_dapm_widget *w_cpu, *w_codec;
- struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
- struct snd_soc_dai *codec_dai = rtd->codec_dai;
The way this should be implemented is: * Update CPU DAI (set active, mark widget dirty) * Update all CODEC DAIs * Run dapm_power_widgets()
if (stream == SNDRV_PCM_STREAM_PLAYBACK) { w_cpu = cpu_dai->playback_widget;
[...]
@@ -107,11 +115,15 @@ void snd_soc_runtime_deactivate(struct snd_soc_pcm_runtime *rtd, int stream) */ bool snd_soc_runtime_ignore_pmdown_time(struct snd_soc_pcm_runtime *rtd) {
- int i, ignore = 0;
- if (!rtd->pmdown_time || rtd->dai_link->ignore_pmdown_time) return true;
- return rtd->cpu_dai->component->ignore_pmdown_time &&
rtd->codec_dai->component->ignore_pmdown_time;
- for (i = 0; (i < rtd->num_codecs) && !ignore; i++)
ignore |= rtd->codec_dais[i]->component->ignore_pmdown_time;
The power-down delay must only be ignored if all components connected to the DAI link agree that it can be ignored.
return rtd->cpu_dai->component->ignore_pmdown_time && ignore; }
/**
@@ -222,8 +234,7 @@ static int soc_pcm_params_symmetry(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
- struct snd_soc_dai *codec_dai = rtd->codec_dai;
- unsigned int rate, channels, sample_bits, symmetry;
unsigned int rate, channels, sample_bits, symmetry, i;
rate = params_rate(params); channels = params_channels(params);
@@ -231,8 +242,12 @@ static int soc_pcm_params_symmetry(struct snd_pcm_substream *substream,
/* reject unmatched parameters when applying symmetry */ symmetry = cpu_dai->driver->symmetric_rates ||
rtd->dai_link->symmetric_rates;codec_dai->driver->symmetric_rates ||
- for (i = 0; i < rtd->num_codecs; i++)
symmetry = rtd->codec_dais[i]->driver->symmetric_rates ||
symmetry;
Just writing symmetery |= ... should be fine, that makes it a bit shorter. Same comment for the same pattern below.
- if (symmetry && cpu_dai->rate && cpu_dai->rate != rate) { dev_err(rtd->dev, "ASoC: unmatched rate symmetry: %d - %d\n", cpu_dai->rate, rate);
@@ -240,8 +255,12 @@ static int soc_pcm_params_symmetry(struct snd_pcm_substream *substream,
[...]
@@ -418,22 +487,22 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) ret = -EINVAL; if (!runtime->hw.rates) { printk(KERN_ERR "ASoC: %s <-> %s No matching rates\n",
codec_dai->name, cpu_dai->name);
goto config_err; } if (!runtime->hw.formats) { printk(KERN_ERR "ASoC: %s <-> %s No matching formats\n",codec_dai_name, cpu_dai->name);
codec_dai->name, cpu_dai->name);
goto config_err; } if (!runtime->hw.channels_min || !runtime->hw.channels_max || runtime->hw.channels_min > runtime->hw.channels_max) { printk(KERN_ERR "ASoC: %s <-> %s No matching channels\n",codec_dai_name, cpu_dai->name);
codec_dai->name, cpu_dai->name);
goto config_err; }codec_dai_name, cpu_dai->name);
- soc_pcm_apply_msb(substream, codec_dai);
- soc_pcm_apply_msb(substream, rtd->codec_dais[0]);
The msb constraint that gets applied for the CODEC side should be the maximum over all the CODECs sig_bits. If one of them is 0 there is no constraint from the CODEC side.
soc_pcm_apply_msb(substream, cpu_dai);
/* Symmetry only applies if we've already got an active stream. */
[...]
@@ -2181,22 +2364,28 @@ static int dpcm_fe_dai_close(struct snd_pcm_substream *fe_substream) int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) { struct snd_soc_platform *platform = rtd->platform;
- struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_dai *codec_dai; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; struct snd_pcm *pcm; char new_name[64]; int ret = 0, playback = 0, capture = 0;
int i;
if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) { playback = rtd->dai_link->dpcm_playback; capture = rtd->dai_link->dpcm_capture; } else {
if (codec_dai->driver->playback.channels_min &&
cpu_dai->driver->playback.channels_min)
playback = 1;
if (codec_dai->driver->capture.channels_min &&
cpu_dai->driver->capture.channels_min)
capture = 1;
for (i = 0; i < rtd->num_codecs; i++) {
codec_dai = rtd->codec_dais[i];
if (codec_dai->driver->playback.channels_min &&
cpu_dai->driver->playback.channels_min)
playback++;
if (codec_dai->driver->capture.channels_min &&
cpu_dai->driver->capture.channels_min)
capture++;
}
capture = (rtd->num_codecs == capture);
playback = (rtd->num_codecs == playback);
I think it should be sufficent if at least one of them has a channels_min != 0 to create the stream. Not all of the CODEC necesarrily do have to have data flowing in both directions. So thing like:
for (i = 0; i < rtd->num_codecs; i++) { codec_dai = rtd->codec_dais[i]; if (codec_dai->driver->playback.channels_min) codec_playback = true; if (codec_dai->driver->capture.channels_min) codec_capture = true; }
if (cpu_dai->driver->playback.channels_min && codec_playback) playback = true; ...
}
[...]
Hi Lars,
On 26/04/2014 14:51, Lars-Peter Clausen wrote:
On 04/24/2014 02:01 PM, Benoit Cousson wrote:
DAI link assumes a one to one mapping between CPU DAI and CODEC. In some cases, the same CPU DAI can be connected to several codecs. This is the case for example, if you connect two mono codecs to the same I2S link in order to have a stereo card. The current ASoC implementation does not allow such setup.
Add support for DAI links composed of a single CPU DAI and multiple CODECs. Sound cards have to pass the CODECs array in the corresponding DAI link through a new 'snd_soc_dai_link_component' struct. Each CODEC in this array is described in the same manner single CODEC DAIs are (either DT/OF node or codec_name).
Fix a trailing space in the header as well.
Based on an original code done by Misael.
Signed-off-by: Benoit Cousson bcousson@baylibre.com Signed-off-by: Misael Lopez Cruz misael.lopez@ti.com Signed-off-by: Fabien Parent fparent@baylibre.com
Looks mostly good. A few bits and pieces here and there, comments inline.
Cool, it is getting closer :-)
[...]
diff --git a/include/sound/soc.h b/include/sound/soc.h index 0fadb3c..7933512 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -821,6 +821,12 @@ struct snd_soc_platform_driver { int (*bespoke_trigger)(struct snd_pcm_substream *, int); };
+struct snd_soc_dai_link_component {
- const char *name;
- const struct device_node *of_node;
- const char *dai_name;
+};
- struct snd_soc_platform { const char *name; int id;
@@ -870,6 +876,10 @@ struct snd_soc_dai_link { const struct device_node *codec_of_node; /* You MUST specify the DAI name within the codec */ const char *codec_dai_name;
- struct snd_soc_dai_link_component *codecs;
Should probably be const
OK.
- unsigned int num_codecs;
/* * You MAY specify the link's platform/PCM/DMA driver, either by * device name, or by DT/OF node, but not both. Some forms of link
[...]
@@ -620,23 +631,26 @@ int soc_new_compress(struct snd_soc_pcm_runtime *rtd, int num) { struct snd_soc_codec *codec = rtd->codec; struct snd_soc_platform *platform = rtd->platform;
- struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; struct snd_compr *compr; struct snd_pcm *be_pcm; char new_name[64];
- int ret = 0, direction = 0;
- int i, ret = 0, direction = 0;
/* check client and interface hw capabilities */
snprintf(new_name, sizeof(new_name), "%s %s-%d",
rtd->dai_link->stream_name, codec_dai->name, num);
if (codec_dai->driver->playback.channels_min)
direction = SND_COMPRESS_PLAYBACK;
else if (codec_dai->driver->capture.channels_min)
direction = SND_COMPRESS_CAPTURE;
else
return -EINVAL;
- for (i = 0; i < rtd->num_codecs; i++) {
struct snd_soc_dai *codec_dai = rtd->codec_dais[i];
/* check client and interface hw capabilities */
snprintf(new_name, sizeof(new_name), "%s %s-%d",
rtd->dai_link->stream_name, codec_dai->name, num);
if (codec_dai->driver->playback.channels_min)
direction = SND_COMPRESS_PLAYBACK;
else if (codec_dai->driver->capture.channels_min)
direction = SND_COMPRESS_CAPTURE;
else
return -EINVAL;
- }
This loop makes no sense, you just end up overwriting new_name and direction with each loop iteration.
Mmm, indeed! I just did a dumb search and replace without understanding what that was doing :-) In that case we could potentially check that everything codecs does have the same direction... hoping it will be the case.
compr = kzalloc(sizeof(*compr), GFP_KERNEL); if (compr == NULL) {
@@ -690,8 +704,11 @@ int soc_new_compress(struct snd_soc_pcm_runtime *rtd, int num) rtd->compr = compr; compr->private_data = rtd;
- printk(KERN_INFO "compress asoc: %s <-> %s mapping ok\n",
codec_dai->name,
cpu_dai->name);
for (i = 0; i < rtd->num_codecs; i++) {
struct snd_soc_dai *codec_dai = rtd->codec_dais[i];
printk(KERN_INFO "compress asoc: %s <-> %s mapping ok\n",
codec_dai->name, cpu_dai->name);
} return ret;
compr_err:
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index f18112a..7f68492 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c
[...]
@@ -1294,20 +1324,28 @@ static int soc_aux_dev_init(struct snd_soc_card *card, return 0; }
-static int soc_dai_link_init(struct snd_soc_card *card,
struct snd_soc_codec *codec,
int num)
+static int soc_dai_link_init(struct snd_soc_card *card, int num) { struct snd_soc_dai_link *dai_link = &card->dai_link[num]; struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
- const char *temp;
- int ret;
const char **temp;
int i, ret;
rtd->card = card;
- /* machine controls, routes and widgets are not prefixed */
- temp = codec->name_prefix;
- codec->name_prefix = NULL;
- temp = devm_kzalloc(card->dev, rtd->num_codecs * sizeof(char *),
GFP_KERNEL);
- if (!temp)
return -ENOMEM;
Setting name_prefix temporarily to NULL is no longer necessary, see the patch I just sent out.
Yep, I saw it. Sorry for pulling some code you've just removed.
for (i = 0; i < rtd->num_codecs; i++) {
/* Make sure all DAPM widgets are instantiated */
snd_soc_dapm_new_widgets(rtd->codec_dais[i]->codec->dapm.card);
/* machine controls, routes and widgets are not prefixed */
temp[i] = rtd->codec_dais[i]->codec->name_prefix;
rtd->codec_dais[i]->codec->name_prefix = NULL;
}
/* do machine specific initialization */ if (dai_link->init) {
[...]
@@ -1586,16 +1626,21 @@ static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order) codec2codec_close_delayed_work);
/* link the DAI widgets */
ret = soc_link_dai_widgets(card, dai_link,
cpu_dai, codec_dai);
if (ret)
return ret;
for (i = 0; i < rtd->num_codecs; i++) {
ret = soc_link_dai_widgets(card, dai_link,
cpu_dai, rtd->codec_dais[i]);
This will create a DAI link widget for each CODEC DAI. The DAI link widget will configure the CPU and the CODEC DAI that are connected to it. If there is one DAI link widget per CODEC DAI this means that the CPU DAI will be connected to multiple DAI link widgets, which means it will be configured once for each CODEC DAI (with possible conflicting configurations).
So there should only be one DAI link widget per DAI link, with the CPU DAI on one side and the CODEC DAIs on the other side. Note that you'll also need to re-work snd_soc_dai_link_event() to handle multiple inputs/outputs. I'd factor that part out into a separate patch.
OK.
if (ret)
return ret;
} } } /* add platform data for AC97 devices */
- if (rtd->codec_dai->driver->ac97_control)
snd_ac97_dev_add_pdata(codec->ac97, rtd->cpu_dai->ac97_pdata);
for (i = 0; i < rtd->num_codecs; i++) {
if (rtd->codec_dais[i]->driver->ac97_control)
snd_ac97_dev_add_pdata(rtd->codec_dais[i]->codec->ac97,
rtd->cpu_dai->ac97_pdata);
}
return 0; }
@@ -1635,7 +1680,20 @@ static int soc_register_ac97_codec(struct snd_soc_codec *codec,
static int soc_register_ac97_dai_link(struct snd_soc_pcm_runtime *rtd) {
- return soc_register_ac97_codec(rtd->codec, rtd->codec_dai);
- int i, ret;
- for (i = 0; i < rtd->num_codecs; i++) {
struct snd_soc_dai *codec_dai = rtd->codec_dais[i];
ret = soc_register_ac97_codec(codec_dai->codec, codec_dai);
if (ret) {
while (--i >= 0)
soc_unregister_ac97_codec(codec_dai->codec);
You use soc_unregister_ac97_codec here ...
return ret;
}
}
return 0; }
static void soc_unregister_ac97_codec(struct snd_soc_codec *codec)
@@ -1648,7 +1706,10 @@ static void soc_unregister_ac97_codec(struct snd_soc_codec *codec)
static void soc_unregister_ac97_dai_link(struct snd_soc_pcm_runtime *rtd)
... but only define it here. This causes a compile error. Just move soc_unregister_ac97_dai_link before soc_register_ac97_codec
Oops, I did not have the CONFIG_SND_SOC_AC97_BUS enabled and missed that issue.
{
- soc_unregister_ac97_codec(rtd->codec);
- int i;
- for (i = 0; i < rtd->num_codecs; i++)
} #endifsoc_unregister_ac97_codec(rtd->codec_dais[i]->codec);
[...]
+static void soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd,
struct snd_soc_dai *cpu_dai,
struct snd_soc_dai *codec_dai,
int stream, int event)
+{ struct snd_soc_dapm_widget *w_cpu, *w_codec;
- struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
- struct snd_soc_dai *codec_dai = rtd->codec_dai;
The way this should be implemented is: * Update CPU DAI (set active, mark widget dirty) * Update all CODEC DAIs * Run dapm_power_widgets()
OK.
if (stream == SNDRV_PCM_STREAM_PLAYBACK) { w_cpu = cpu_dai->playback_widget;
[...]
@@ -107,11 +115,15 @@ void snd_soc_runtime_deactivate(struct snd_soc_pcm_runtime *rtd, int stream) */ bool snd_soc_runtime_ignore_pmdown_time(struct snd_soc_pcm_runtime *rtd) {
- int i, ignore = 0;
if (!rtd->pmdown_time || rtd->dai_link->ignore_pmdown_time) return true;
- return rtd->cpu_dai->component->ignore_pmdown_time &&
rtd->codec_dai->component->ignore_pmdown_time;
- for (i = 0; (i < rtd->num_codecs) && !ignore; i++)
ignore |= rtd->codec_dais[i]->component->ignore_pmdown_time;
The power-down delay must only be ignored if all components connected to the DAI link agree that it can be ignored.
OK, so it should be an "&" then.
return rtd->cpu_dai->component->ignore_pmdown_time && ignore; }
/**
@@ -222,8 +234,7 @@ static int soc_pcm_params_symmetry(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
- struct snd_soc_dai *codec_dai = rtd->codec_dai;
- unsigned int rate, channels, sample_bits, symmetry;
unsigned int rate, channels, sample_bits, symmetry, i;
rate = params_rate(params); channels = params_channels(params);
@@ -231,8 +242,12 @@ static int soc_pcm_params_symmetry(struct snd_pcm_substream *substream,
/* reject unmatched parameters when applying symmetry */ symmetry = cpu_dai->driver->symmetric_rates ||
codec_dai->driver->symmetric_rates || rtd->dai_link->symmetric_rates;
- for (i = 0; i < rtd->num_codecs; i++)
symmetry = rtd->codec_dais[i]->driver->symmetric_rates ||
symmetry;
Just writing symmetery |= ... should be fine, that makes it a bit shorter. Same comment for the same pattern below.
OK.
if (symmetry && cpu_dai->rate && cpu_dai->rate != rate) { dev_err(rtd->dev, "ASoC: unmatched rate symmetry: %d - %d\n", cpu_dai->rate, rate);
@@ -240,8 +255,12 @@ static int soc_pcm_params_symmetry(struct snd_pcm_substream *substream,
[...]
@@ -418,22 +487,22 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) ret = -EINVAL; if (!runtime->hw.rates) { printk(KERN_ERR "ASoC: %s <-> %s No matching rates\n",
codec_dai->name, cpu_dai->name);
codec_dai_name, cpu_dai->name); goto config_err; } if (!runtime->hw.formats) { printk(KERN_ERR "ASoC: %s <-> %s No matching formats\n",
codec_dai->name, cpu_dai->name);
codec_dai_name, cpu_dai->name); goto config_err; } if (!runtime->hw.channels_min || !runtime->hw.channels_max || runtime->hw.channels_min > runtime->hw.channels_max) { printk(KERN_ERR "ASoC: %s <-> %s No matching channels\n",
codec_dai->name, cpu_dai->name);
codec_dai_name, cpu_dai->name); goto config_err; }
- soc_pcm_apply_msb(substream, codec_dai);
- soc_pcm_apply_msb(substream, rtd->codec_dais[0]);
The msb constraint that gets applied for the CODEC side should be the maximum over all the CODECs sig_bits. If one of them is 0 there is no constraint from the CODEC side.
OK.
soc_pcm_apply_msb(substream, cpu_dai); /* Symmetry only applies if we've already got an active stream. */
[...]
@@ -2181,22 +2364,28 @@ static int dpcm_fe_dai_close(struct snd_pcm_substream *fe_substream) int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) { struct snd_soc_platform *platform = rtd->platform;
- struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_dai *codec_dai; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; struct snd_pcm *pcm; char new_name[64]; int ret = 0, playback = 0, capture = 0;
int i;
if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) { playback = rtd->dai_link->dpcm_playback; capture = rtd->dai_link->dpcm_capture; } else {
if (codec_dai->driver->playback.channels_min &&
cpu_dai->driver->playback.channels_min)
playback = 1;
if (codec_dai->driver->capture.channels_min &&
cpu_dai->driver->capture.channels_min)
capture = 1;
for (i = 0; i < rtd->num_codecs; i++) {
codec_dai = rtd->codec_dais[i];
if (codec_dai->driver->playback.channels_min &&
cpu_dai->driver->playback.channels_min)
playback++;
if (codec_dai->driver->capture.channels_min &&
cpu_dai->driver->capture.channels_min)
capture++;
}
capture = (rtd->num_codecs == capture);
playback = (rtd->num_codecs == playback);
I think it should be sufficent if at least one of them has a channels_min != 0 to create the stream. Not all of the CODEC necesarrily do have to have data flowing in both directions. So thing like:
for (i = 0; i < rtd->num_codecs; i++) { codec_dai = rtd->codec_dais[i]; if (codec_dai->driver->playback.channels_min) codec_playback = true; if (codec_dai->driver->capture.channels_min) codec_capture = true; } if (cpu_dai->driver->playback.channels_min && codec_playback) playback = true; ...
OK.
}
[...]
Thanks for exhaustive review and comments.
I'll try to update faster than for the v3, but I'm stuck at ELC this week.
Regards, Benoit
Hi Lars,
I'm struggling a little bit to understand one of your comment :-)
On 26/04/2014 14:51, Lars-Peter Clausen wrote:
On 04/24/2014 02:01 PM, Benoit Cousson wrote:
@@ -870,6 +876,10 @@ struct snd_soc_dai_link { const struct device_node *codec_of_node; /* You MUST specify the DAI name within the codec */ const char *codec_dai_name;
- struct snd_soc_dai_link_component *codecs;
Should probably be const
In fact, I don't think this is not possible, since this struct will be initialized at runtime in the legacy case when the regular codec struct is the one used by the driver.
[...]
@@ -1586,16 +1626,21 @@ static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order) codec2codec_close_delayed_work);
/* link the DAI widgets */
ret = soc_link_dai_widgets(card, dai_link,
cpu_dai, codec_dai);
if (ret)
return ret;
for (i = 0; i < rtd->num_codecs; i++) {
ret = soc_link_dai_widgets(card, dai_link,
cpu_dai, rtd->codec_dais[i]);
This will create a DAI link widget for each CODEC DAI. The DAI link widget will configure the CPU and the CODEC DAI that are connected to it. If there is one DAI link widget per CODEC DAI this means that the CPU DAI will be connected to multiple DAI link widgets, which means it will be configured once for each CODEC DAI (with possible conflicting configurations).
I've got that point, but now I'm wondering what struct should be per codec_dai. Should we consider one source and several sinks to represent the multiple codecs?
So there should only be one DAI link widget per DAI link, with the CPU DAI on one side and the CODEC DAIs on the other side. Note that you'll also need to re-work snd_soc_dai_link_event() to handle multiple inputs/outputs. I'd factor that part out into a separate patch.
Here, I'm lost :-)
What struct should be made multiple in that case?
Should we consider that the following list can handle multiple sink/source?
/* We only support a single source and sink, pick the first */ source_p = list_first_entry(&w->sources, struct snd_soc_dapm_path, list_sink); sink_p = list_first_entry(&w->sinks, struct snd_soc_dapm_path, list_source);
[...]
Thanks, Benoit
On 05/15/2014 05:01 PM, Benoit Cousson wrote:
Hi Lars,
I'm struggling a little bit to understand one of your comment :-)
On 26/04/2014 14:51, Lars-Peter Clausen wrote:
On 04/24/2014 02:01 PM, Benoit Cousson wrote:
@@ -870,6 +876,10 @@ struct snd_soc_dai_link { const struct device_node *codec_of_node; /* You MUST specify the DAI name within the codec */ const char *codec_dai_name;
- struct snd_soc_dai_link_component *codecs;
Should probably be const
In fact, I don't think this is not possible, since this struct will be initialized at runtime in the legacy case when the regular codec struct is the one used by the driver.
That shouldn't be a problem. The const applies to the data the pointer points to, not the pointer itself.
[...]
@@ -1586,16 +1626,21 @@ static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order) codec2codec_close_delayed_work);
/* link the DAI widgets */
ret = soc_link_dai_widgets(card, dai_link,
cpu_dai, codec_dai);
if (ret)
return ret;
for (i = 0; i < rtd->num_codecs; i++) {
ret = soc_link_dai_widgets(card, dai_link,
cpu_dai, rtd->codec_dais[i]);
This will create a DAI link widget for each CODEC DAI. The DAI link widget will configure the CPU and the CODEC DAI that are connected to it. If there is one DAI link widget per CODEC DAI this means that the CPU DAI will be connected to multiple DAI link widgets, which means it will be configured once for each CODEC DAI (with possible conflicting configurations).
I've got that point, but now I'm wondering what struct should be per codec_dai. Should we consider one source and several sinks to represent the multiple codecs?
The soc_link_dai_widgets function should take the rtd as a parameter instead of the CODEC and CPU DAIs. Same goes for snd_soc_dapm_new_pcm() then you create one dai_link widget per stream (i.e. one for capture of there is capture support, one for playback if there is playback support). And then create the paths between the dai_link widgets and the DAI widgets accordingly.
So there should only be one DAI link widget per DAI link, with the CPU DAI on one side and the CODEC DAIs on the other side. Note that you'll also need to re-work snd_soc_dai_link_event() to handle multiple inputs/outputs. I'd factor that part out into a separate patch.
Here, I'm lost :-)
What struct should be made multiple in that case?
Should we consider that the following list can handle multiple sink/source?
/* We only support a single source and sink, pick the first */ source_p = list_first_entry(&w->sources, struct snd_soc_dapm_path, list_sink); sink_p = list_first_entry(&w->sinks, struct snd_soc_dapm_path, list_source);
[...]
See the attached patch.
If you don't want to worry about these things for now it should be fine to add a check in soc_probe_link_dais() and make sure that num_codecs == 1 for the CODEC to CODEC case. This means we'll not support multi-codec links for CODEC to CODEC links, which should in my opinion be fine for now.
On 16/05/2014 12:44, Lars-Peter Clausen wrote:
On 05/15/2014 05:01 PM, Benoit Cousson wrote:
Hi Lars,
I'm struggling a little bit to understand one of your comment :-)
On 26/04/2014 14:51, Lars-Peter Clausen wrote:
On 04/24/2014 02:01 PM, Benoit Cousson wrote:
@@ -870,6 +876,10 @@ struct snd_soc_dai_link { const struct device_node *codec_of_node; /* You MUST specify the DAI name within the codec */ const char *codec_dai_name;
- struct snd_soc_dai_link_component *codecs;
Should probably be const
In fact, I don't think this is not possible, since this struct will be initialized at runtime in the legacy case when the regular codec struct is the one used by the driver.
That shouldn't be a problem. The const applies to the data the pointer points to, not the pointer itself.
Yeah, I know. But in that case, we populate the data based on the ones in the codec struct. We do not just assign the pointer. The fact is the compiler is complaining about that anyway :-)
[...]
@@ -1586,16 +1626,21 @@ static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order) codec2codec_close_delayed_work);
/* link the DAI widgets */
ret = soc_link_dai_widgets(card, dai_link,
cpu_dai, codec_dai);
if (ret)
return ret;
for (i = 0; i < rtd->num_codecs; i++) {
ret = soc_link_dai_widgets(card, dai_link,
cpu_dai, rtd->codec_dais[i]);
This will create a DAI link widget for each CODEC DAI. The DAI link widget will configure the CPU and the CODEC DAI that are connected to it. If there is one DAI link widget per CODEC DAI this means that the CPU DAI will be connected to multiple DAI link widgets, which means it will be configured once for each CODEC DAI (with possible conflicting configurations).
I've got that point, but now I'm wondering what struct should be per codec_dai. Should we consider one source and several sinks to represent the multiple codecs?
The soc_link_dai_widgets function should take the rtd as a parameter instead of the CODEC and CPU DAIs. Same goes for snd_soc_dapm_new_pcm() then you create one dai_link widget per stream (i.e. one for capture of there is capture support, one for playback if there is playback support). And then create the paths between the dai_link widgets and the DAI widgets accordingly.
Yep, I've done that part already, but this is widget -> dai relation that was not clear to me.
So there should only be one DAI link widget per DAI link, with the CPU DAI on one side and the CODEC DAIs on the other side. Note that you'll also need to re-work snd_soc_dai_link_event() to handle multiple inputs/outputs. I'd factor that part out into a separate patch.
Here, I'm lost :-)
What struct should be made multiple in that case?
Should we consider that the following list can handle multiple sink/source?
/* We only support a single source and sink, pick the first */ source_p = list_first_entry(&w->sources, struct snd_soc_dapm_path, list_sink); sink_p = list_first_entry(&w->sinks, struct snd_soc_dapm_path,But
list_source);
[...]
See the attached patch.
Cool, thanks for that.
If you don't want to worry about these things for now it should be fine to add a check in soc_probe_link_dais() and make sure that num_codecs == 1 for the CODEC to CODEC case. This means we'll not support multi-codec links for CODEC to CODEC links, which should in my opinion be fine for now.
That sounds like a good plan to me. The patch is already pretty big, and assumning it will not generate regression in the regular case (single codec), it might be good to start merging the current work in progress and add the full multi codec support later.
I'll repost to fix the current comments and let you decide what you'll do with that code :-)
Thanks for the help, Benoit
Hi Lars,
On 16/05/2014 12:44, Lars-Peter Clausen wrote:
On 05/15/2014 05:01 PM, Benoit Cousson wrote:
[...]
@@ -1586,16 +1626,21 @@ static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order) codec2codec_close_delayed_work);
/* link the DAI widgets */
ret = soc_link_dai_widgets(card, dai_link,
cpu_dai, codec_dai);
if (ret)
return ret;
for (i = 0; i < rtd->num_codecs; i++) {
ret = soc_link_dai_widgets(card, dai_link,
cpu_dai, rtd->codec_dais[i]);
This will create a DAI link widget for each CODEC DAI. The DAI link widget will configure the CPU and the CODEC DAI that are connected to it. If there is one DAI link widget per CODEC DAI this means that the CPU DAI will be connected to multiple DAI link widgets, which means it will be configured once for each CODEC DAI (with possible conflicting configurations).
I've got that point, but now I'm wondering what struct should be per codec_dai. Should we consider one source and several sinks to represent the multiple codecs?
The soc_link_dai_widgets function should take the rtd as a parameter instead of the CODEC and CPU DAIs. Same goes for snd_soc_dapm_new_pcm() then you create one dai_link widget per stream (i.e. one for capture of there is capture support, one for playback if there is playback support). And then create the paths between the dai_link widgets and the DAI widgets accordingly.
I'm still not 100% sure how it should work so here is a tentative patch to try to handle that part. Please note that in my setup none of that code is ever called :-( I'm not sure why!
Please ignore the extra DEBUG code I added and there is probably some way to factorize some code, but I'd like to be sure that this is the proper way to handle the routes in the multicodec case before re-sending that series.
Thanks, Benoit
---
From 565bfa3f31abbe2b677238ed423eb9fa99609086 Mon Sep 17 00:00:00 2001
From: Benoit Cousson bcousson@baylibre.com Date: Thu, 22 May 2014 08:54:54 +0200 Subject: [PATCH] ASoC: Temp: Tentative multicodec adaptation for dapm routes
Not-Signed-off-by: Benoit Cousson bcousson@baylibre.com --- include/sound/soc-dapm.h | 3 +- sound/soc/soc-core.c | 31 +---------- sound/soc/soc-dapm.c | 132 +++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 113 insertions(+), 53 deletions(-)
diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index b041fc6..41336be 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -381,8 +381,7 @@ int snd_soc_dapm_link_dai_widgets(struct snd_soc_card *card); void snd_soc_dapm_connect_dai_link_widgets(struct snd_soc_card *card); int snd_soc_dapm_new_pcm(struct snd_soc_card *card, const struct snd_soc_pcm_stream *params, - struct snd_soc_dapm_widget *source, - struct snd_soc_dapm_widget *sink); + struct snd_soc_pcm_runtime *rtd);
/* dapm path setup */ int snd_soc_dapm_new_widgets(struct snd_soc_card *card); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 077f51a..208df5d 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1483,37 +1483,8 @@ static int soc_link_dai_widgets(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link, struct snd_soc_pcm_runtime *rtd) { - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - struct snd_soc_dapm_widget *play_w, *capture_w; - int ret; - /* link the DAI widgets */ - play_w = codec_dai->playback_widget; - capture_w = cpu_dai->capture_widget; - if (play_w && capture_w) { - ret = snd_soc_dapm_new_pcm(card, dai_link->params, - capture_w, play_w); - if (ret != 0) { - dev_err(card->dev, "ASoC: Can't link %s to %s: %d\n", - play_w->name, capture_w->name, ret); - return ret; - } - } - - play_w = cpu_dai->playback_widget; - capture_w = codec_dai->capture_widget; - if (play_w && capture_w) { - ret = snd_soc_dapm_new_pcm(card, dai_link->params, - capture_w, play_w); - if (ret != 0) { - dev_err(card->dev, "ASoC: Can't link %s to %s: %d\n", - play_w->name, capture_w->name, ret); - return ret; - } - } - - return 0; + return snd_soc_dapm_new_pcm(card, dai_link->params,rtd); }
static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order) diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index f457225..4f21429 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -22,7 +22,7 @@ * device reopen. * */ - +#define DEBUG #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/init.h> @@ -3248,22 +3248,12 @@ out: return ret; }
-int snd_soc_dapm_new_pcm(struct snd_soc_card *card, +int snd_soc_dapm_new_link(struct snd_soc_card *card, const struct snd_soc_pcm_stream *params, - struct snd_soc_dapm_widget *source, - struct snd_soc_dapm_widget *sink) + char *link_name) { - struct snd_soc_dapm_route routes[2]; struct snd_soc_dapm_widget template; struct snd_soc_dapm_widget *w; - size_t len; - char *link_name; - - len = strlen(source->name) + strlen(sink->name) + 2; - link_name = devm_kzalloc(card->dev, len, GFP_KERNEL); - if (!link_name) - return -ENOMEM; - snprintf(link_name, len, "%s-%s", source->name, sink->name);
memset(&template, 0, sizeof(template)); template.reg = SND_SOC_NOPM; @@ -3271,7 +3261,7 @@ int snd_soc_dapm_new_pcm(struct snd_soc_card *card, template.name = link_name; template.event = snd_soc_dai_link_event; template.event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | - SND_SOC_DAPM_PRE_PMD; + SND_SOC_DAPM_PRE_PMD;
dev_dbg(card->dev, "ASoC: adding %s widget\n", link_name);
@@ -3284,15 +3274,115 @@ int snd_soc_dapm_new_pcm(struct snd_soc_card *card,
w->params = params;
- memset(&routes, 0, sizeof(routes)); + return 0; +} + +int snd_soc_dapm_new_pcm(struct snd_soc_card *card, + const struct snd_soc_pcm_stream *params, + struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dapm_route route; + struct snd_soc_dapm_widget *source, *sink; + size_t len; + char *link_name; + const char *codecs_link_name = "multiple"; + unsigned int num_widgets = 0; + int i; + + /* cpu_dai->capture_widget -> codec_dai->playback_widget */ + for (i = 0; i < rtd->num_codecs; i++) + if (rtd->codec_dais[i]->playback_widget) + num_widgets++; + + if (!rtd->cpu_dai->capture_widget || !num_widgets) + return 0; + + + source = rtd->cpu_dai->capture_widget; + + if (num_widgets > 1) { + len = strlen(source->name) + strlen(codecs_link_name) + 2; + } else { + sink = rtd->codec_dais[0]->playback_widget; + len = strlen(source->name) + strlen(sink->name) + 2; + } + + link_name = devm_kzalloc(card->dev, len, GFP_KERNEL); + if (!link_name) + return -ENOMEM; + + if (num_widgets > 1) + snprintf(link_name, len, "%s-%s", source->name, codecs_link_name); + else + snprintf(link_name, len, "%s-%s", source->name, sink->name); + + pr_info("%s(), link_name #1 %s", __func__,link_name); + snd_soc_dapm_new_link(card, params, link_name);
- routes[0].source = source->name; - routes[0].sink = link_name; - routes[1].source = link_name; - routes[1].sink = sink->name;
- return snd_soc_dapm_add_routes(&card->dapm, routes, - ARRAY_SIZE(routes)); + memset(&route, 0, sizeof(route)); + route.source = source->name; + route.sink = link_name; + snd_soc_dapm_add_route(&card->dapm, &route, false); + + route.source = link_name; + for (i = 0; i < rtd->num_codecs; i++) { + if (!rtd->codec_dais[i]->playback_widget) + continue; + + route.sink = rtd->codec_dais[i]->playback_widget->name; + snd_soc_dapm_add_route(&card->dapm, &route, false); + } + + + num_widgets = 0; + + /* codec_dai->capture_widget -> cpu_dai->playback_widget */ + for (i = 0; i < rtd->num_codecs; i++) + if (rtd->codec_dais[i]->capture_widget) + num_widgets++; + + if (!rtd->cpu_dai->playback_widget || !num_widgets) + return 0; + + + sink = rtd->cpu_dai->playback_widget; + + if (num_widgets > 1) { + len = strlen(sink->name) + strlen(codecs_link_name) + 2; + } else { + source = rtd->codec_dais[0]->playback_widget; + len = strlen(source->name) + strlen(sink->name) + 2; + } + + link_name = devm_kzalloc(card->dev, len, GFP_KERNEL); + if (!link_name) + return -ENOMEM; + + if (num_widgets > 1) + snprintf(link_name, len, "%s-%s", codecs_link_name, sink->name); + else + snprintf(link_name, len, "%s-%s", source->name, sink->name); + + pr_info("%s(), link_name #2 %s", __func__,link_name); + snd_soc_dapm_new_link(card, params, link_name); + + + memset(&route, 0, sizeof(route)); + route.sink = link_name; + for (i = 0; i < rtd->num_codecs; i++) { + if (!rtd->codec_dais[i]->capture_widget) + continue; + + route.source = rtd->codec_dais[i]->capture_widget->name; + snd_soc_dapm_add_route(&card->dapm, &route, false); + } + + route.source = link_name; + route.sink = sink->name; + snd_soc_dapm_add_route(&card->dapm, &route, false); + + return 0; }
int snd_soc_dapm_new_dai_widgets(struct snd_soc_dapm_context *dapm,
On 05/22/2014 09:01 AM, Benoit Cousson wrote:
Hi Lars,
On 16/05/2014 12:44, Lars-Peter Clausen wrote:
On 05/15/2014 05:01 PM, Benoit Cousson wrote:
[...]
@@ -1586,16 +1626,21 @@ static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order) codec2codec_close_delayed_work);
/* link the DAI widgets */
ret = soc_link_dai_widgets(card, dai_link,
cpu_dai, codec_dai);
if (ret)
return ret;
for (i = 0; i < rtd->num_codecs; i++) {
ret = soc_link_dai_widgets(card, dai_link,
cpu_dai, rtd->codec_dais[i]);
This will create a DAI link widget for each CODEC DAI. The DAI link widget will configure the CPU and the CODEC DAI that are connected to it. If there is one DAI link widget per CODEC DAI this means that the CPU DAI will be connected to multiple DAI link widgets, which means it will be configured once for each CODEC DAI (with possible conflicting configurations).
I've got that point, but now I'm wondering what struct should be per codec_dai. Should we consider one source and several sinks to represent the multiple codecs?
The soc_link_dai_widgets function should take the rtd as a parameter instead of the CODEC and CPU DAIs. Same goes for snd_soc_dapm_new_pcm() then you create one dai_link widget per stream (i.e. one for capture of there is capture support, one for playback if there is playback support). And then create the paths between the dai_link widgets and the DAI widgets accordingly.
I'm still not 100% sure how it should work so here is a tentative patch to try to handle that part. Please note that in my setup none of that code is ever called :-( I'm not sure why!
Please ignore the extra DEBUG code I added and there is probably some way to factorize some code, but I'd like to be sure that this is the proper way to handle the routes in the multicodec case before re-sending that series.
If we do not want to support CODEC<-->CODEC links with multiple CODECs you do not have to care about this for now. But I think the code looks good. The result should look something like this:
+----------+ | Playback | --> [ CODEC 1 DAI ] [ CPU ] --> | DAI LINK | --> [ CODEC 2 DAI ] | | --> [ CODEC 3 DAI ] +----------+
+----------+ [ CODEC 1 DAI ]-- > | Capture | [ CODEC 2 DAI ] --> | DAI LINK | --> [ CPU DAI ] [ CODEC 3 DAI ] --> | | +----------+
Also note that the code in upstream is now using snd_soc_dapm_add_path() rather than snd_soc_dapm_add_route()
- Lars
On 04/24/2014 02:01 PM, Benoit Cousson wrote:
DAI link assumes a one to one mapping between CPU DAI and CODEC. In some cases, the same CPU DAI can be connected to several codecs. This is the case for example, if you connect two mono codecs to the same I2S link in order to have a stereo card. The current ASoC implementation does not allow such setup.
Add support for DAI links composed of a single CPU DAI and multiple CODECs. Sound cards have to pass the CODECs array in the corresponding DAI link through a new 'snd_soc_dai_link_component' struct. Each CODEC in this array is described in the same manner single CODEC DAIs are (either DT/OF node or codec_name).
Fix a trailing space in the header as well.
Based on an original code done by Misael.
Signed-off-by: Benoit Cousson bcousson@baylibre.com Signed-off-by: Misael Lopez Cruz misael.lopez@ti.com Signed-off-by: Fabien Parent fparent@baylibre.com
Some thoughts on the parameter fixup.
include/sound/soc-dai.h | 5 + include/sound/soc.h | 13 ++ sound/soc/soc-compress.c | 83 +++++--- sound/soc/soc-core.c | 364 ++++++++++++++++++++++----------- sound/soc/soc-dapm.c | 71 ++++--- sound/soc/soc-pcm.c | 512 ++++++++++++++++++++++++++++++++--------------- 6 files changed, 715 insertions(+), 333 deletions(-)
diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index fad7676..6985851 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -274,6 +274,11 @@ struct snd_soc_dai { struct snd_soc_codec *codec; struct snd_soc_component *component;
- /* CODEC TDM slot masks and params (for fixup) */
- unsigned int tx_mask;
- unsigned int rx_mask;
- struct snd_pcm_hw_params params[2];
snd_pcm_hw_params is rather large, given that for a lot of setups we don't need it it might be better to make this a pointer and only allocate the params struct when needed.
[...]
@@ -3624,17 +3697,26 @@ static int snd_soc_xlate_tdm_slot_mask(unsigned int slots, int snd_soc_dai_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) {
int ret = 0;
if (dai->driver && dai->driver->ops->xlate_tdm_slot_mask) dai->driver->ops->xlate_tdm_slot_mask(slots, &tx_mask, &rx_mask); else snd_soc_xlate_tdm_slot_mask(slots, &tx_mask, &rx_mask);
if (dai->codec) {
dai->tx_mask = tx_mask;
dai->rx_mask = rx_mask;
}
I don't think it makes sense to artificially limit this to just CODECs. While we do not support multiple CPU DAIs (yet?) adding the check for CODECs just makes the code more complex, but doesn't gain us anything.
[...]
- for (i = 0; i < rtd->num_codecs; i++) {
struct snd_soc_dai *codec_dai = rtd->codec_dais[i];
struct snd_pcm_hw_params *codec_params;
codec_params = &codec_dai->params[substream->stream];
The fixed up params are only ever used in here. Do we really need to keep two copies per CODEC DAI around?
/* copy params for each codec */
memcpy(codec_params, params, sizeof(struct snd_pcm_hw_params));
/* fixup params based on TDM slot masks */
if (codec_dai->tx_mask)
soc_pcm_codec_params_fixup(codec_params,
codec_dai->tx_mask);
if (codec_dai->rx_mask)
soc_pcm_codec_params_fixup(codec_params,
codec_dai->rx_mask);
If we fix-up the number of channels to be always the number of slots should we also setup a constraint for userspace that the number of channels must match the number of channels specified in the CPU DAI mask? Or should we check how many of the channels in the CODEC mask are actually active and fix-up the the number of channels according to that?
if (codec_dai->driver->ops &&
codec_dai->driver->ops->hw_params) {
ret = codec_dai->driver->ops->hw_params(substream,
codec_params, codec_dai);
if (ret < 0) {
dev_err(codec_dai->dev,
"ASoC: can't set %s hw params: %d\n",
codec_dai->name, ret);
goto codec_err;
}}
I think it makes sense to factor this whole block out into a helper function. E.g. soc_dai_hw_params(struct snd_soc_dai* dai, struct hw_params *params);. This will make it possible to also use it in other places like the dapm_dai_link_event() function.
codec_dai->rate = params_rate(codec_params);
codec_dai->channels = params_channels(codec_params);
codec_dai->sample_bits = snd_pcm_format_physical_width(
}params_format(codec_params));
[...]
On 05/06/2014 07:24 AM, Lars-Peter Clausen wrote:
[...] I don't think it makes sense to artificially limit this to just CODECs. While we do not support multiple CPU DAIs (yet?) adding the check for CODECs just makes the code more complex, but doesn't gain us anything.
I have a related question. I am trying to put together something that would "bridge" a spdif rx into say ssi tx on iMX6. I am just not sure how this would fit into the alsa model. Would multiple CPU DAIs be the recommended model for this. In this case I will follow this thread and try to extend/use that multiple CPU DAIs idea.
I apologise if this is not related enough to the current thread. If it is the case I'll start a new thread.
Thanks
-- sinan
On Fri, May 16, 2014 at 04:06:50PM -0400, Sinan Akman wrote:
I have a related question. I am trying to put together something that would "bridge" a spdif rx into say ssi tx on iMX6. I am just not sure how this would fit into the alsa model. Would multiple CPU DAIs be the recommended model for this. In this case I will follow this thread and try to extend/use that multiple CPU DAIs idea.
Ideally we'd be able to set up a route between the two DAIs and then have the framework arrange for data to go from one to the other. There are hardware limitations in being able to do this - you either need ASRC in the hardware or you need to be able to ensure that all the clocks are synchronous. Right now you have to bounce everything through the application layer.
It is a somewhat different issue to the one Benoit is trying to address though.
Hi Lars,
I finally got some BW to address your second email :-)
On 06/05/2014 13:24, Lars-Peter Clausen wrote:
On 04/24/2014 02:01 PM, Benoit Cousson wrote:
DAI link assumes a one to one mapping between CPU DAI and CODEC. In some cases, the same CPU DAI can be connected to several codecs. This is the case for example, if you connect two mono codecs to the same I2S link in order to have a stereo card. The current ASoC implementation does not allow such setup.
Add support for DAI links composed of a single CPU DAI and multiple CODECs. Sound cards have to pass the CODECs array in the corresponding DAI link through a new 'snd_soc_dai_link_component' struct. Each CODEC in this array is described in the same manner single CODEC DAIs are (either DT/OF node or codec_name).
Fix a trailing space in the header as well.
Based on an original code done by Misael.
Signed-off-by: Benoit Cousson bcousson@baylibre.com Signed-off-by: Misael Lopez Cruz misael.lopez@ti.com Signed-off-by: Fabien Parent fparent@baylibre.com
Some thoughts on the parameter fixup.
include/sound/soc-dai.h | 5 + include/sound/soc.h | 13 ++ sound/soc/soc-compress.c | 83 +++++--- sound/soc/soc-core.c | 364 ++++++++++++++++++++++----------- sound/soc/soc-dapm.c | 71 ++++--- sound/soc/soc-pcm.c | 512 ++++++++++++++++++++++++++++++++--------------- 6 files changed, 715 insertions(+), 333 deletions(-)
diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index fad7676..6985851 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -274,6 +274,11 @@ struct snd_soc_dai { struct snd_soc_codec *codec; struct snd_soc_component *component;
- /* CODEC TDM slot masks and params (for fixup) */
- unsigned int tx_mask;
- unsigned int rx_mask;
- struct snd_pcm_hw_params params[2];
snd_pcm_hw_params is rather large, given that for a lot of setups we don't need it it might be better to make this a pointer and only allocate the params struct when needed.
OK.
[...]
@@ -3624,17 +3697,26 @@ static int snd_soc_xlate_tdm_slot_mask(unsigned int slots, int snd_soc_dai_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) {
int ret = 0;
if (dai->driver && dai->driver->ops->xlate_tdm_slot_mask) dai->driver->ops->xlate_tdm_slot_mask(slots, &tx_mask, &rx_mask); else snd_soc_xlate_tdm_slot_mask(slots, &tx_mask, &rx_mask);
if (dai->codec) {
dai->tx_mask = tx_mask;
dai->rx_mask = rx_mask;
}
I don't think it makes sense to artificially limit this to just CODECs. While we do not support multiple CPU DAIs (yet?) adding the check for CODECs just makes the code more complex, but doesn't gain us anything.
OK, makes sense.
[...]
- for (i = 0; i < rtd->num_codecs; i++) {
struct snd_soc_dai *codec_dai = rtd->codec_dais[i];
struct snd_pcm_hw_params *codec_params;
codec_params = &codec_dai->params[substream->stream];
The fixed up params are only ever used in here. Do we really need to keep two copies per CODEC DAI around?
Well, we can indeed, create a local copy that will be used for the fixup. Moreover, it will avoid allocating the data dynamically.
/* copy params for each codec */
memcpy(codec_params, params, sizeof(struct snd_pcm_hw_params));
/* fixup params based on TDM slot masks */
if (codec_dai->tx_mask)
soc_pcm_codec_params_fixup(codec_params,
codec_dai->tx_mask);
if (codec_dai->rx_mask)
soc_pcm_codec_params_fixup(codec_params,
codec_dai->rx_mask);
If we fix-up the number of channels to be always the number of slots should we also setup a constraint for userspace that the number of channels must match the number of channels specified in the CPU DAI mask? Or should we check how many of the channels in the CODEC mask are actually active and fix-up the the number of channels according to that?
That's a pretty good question.
I've tried to check the current users of the snd_soc_dai_set_tdm_slot to understand how it is used in cpu/codec dais.
There are only ~10 users, and it seems that the definition of the mask is dependent of the user. I don't know if we can really enforce anything because of that :-(
if (codec_dai->driver->ops &&
codec_dai->driver->ops->hw_params) {
ret = codec_dai->driver->ops->hw_params(substream,
codec_params, codec_dai);
if (ret < 0) {
dev_err(codec_dai->dev,
"ASoC: can't set %s hw params: %d\n",
codec_dai->name, ret);
goto codec_err;
} }
I think it makes sense to factor this whole block out into a helper function. E.g. soc_dai_hw_params(struct snd_soc_dai* dai, struct hw_params *params);. This will make it possible to also use it in other places like the dapm_dai_link_event() function.
OK, good point.
Thanks, Benoit
participants (4)
-
Benoit Cousson
-
Lars-Peter Clausen
-
Mark Brown
-
Sinan Akman