From fa9a15badd061956c4a75f1e492ca721b4647615 Mon Sep 17 00:00:00 2001 From: Ravindra Lokhande Date: Thu, 26 Jun 2014 20:31:22 +0530 Subject: ASoC: Tegra: Refactor offload to handle multiple BE - Added virtual mixer and switch to support multiple BE - One FE can be connected to multiple BE, mixer control can be used to select the path - Fix crash if no path is selected Change-Id: Ibbf03ef1e1826acd92402b8275d72a580e643e66 Signed-off-by: Ravindra Lokhande Reviewed-on: http://git-master/r/428950 GVS: Gerrit_Virtual_Submit Reviewed-by: Sumit Bhattacharya --- sound/soc/tegra/tegra30_i2s.c | 60 ++++++++++++++-- sound/soc/tegra/tegra_max98090.c | 5 +- sound/soc/tegra/tegra_offload.c | 145 ++++++++++++++++++++++++++++++++++----- sound/soc/tegra/tegra_rt5639.c | 5 +- 4 files changed, 184 insertions(+), 31 deletions(-) (limited to 'sound') diff --git a/sound/soc/tegra/tegra30_i2s.c b/sound/soc/tegra/tegra30_i2s.c index c54e078014a0..d81b9b4dd64e 100644 --- a/sound/soc/tegra/tegra30_i2s.c +++ b/sound/soc/tegra/tegra30_i2s.c @@ -50,6 +50,10 @@ #define RETRY_CNT 10 +static struct snd_soc_pcm_runtime *allocated_fe; +static int apbif_ref_cnt; +static DEFINE_MUTEX(apbif_mutex); + extern int tegra_i2sloopback_func; static struct tegra30_i2s *i2scont[TEGRA30_NR_I2S_IFC]; #if defined(CONFIG_ARCH_TEGRA_14x_SOC) @@ -91,17 +95,57 @@ int tegra30_i2s_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai); - int ret; + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai_link *dai_link = rtd->dai_link; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + int allocate_fifo = 1; /* increment the playback ref count */ i2s->playback_ref_count++; - ret = tegra30_ahub_allocate_tx_fifo(&i2s->playback_fifo_cif, + mutex_lock(&apbif_mutex); + if (dai_link->no_pcm) { + struct snd_soc_dpcm *dpcm; + + list_for_each_entry(dpcm, + &rtd->dpcm[substream->stream].fe_clients, + list_fe) { + struct snd_soc_pcm_runtime *fe = dpcm->fe; + + if (allocated_fe == fe) { + allocate_fifo = 0; + break; + } + + if (allocated_fe == NULL) { + allocated_fe = fe; + snd_soc_pcm_set_drvdata(allocated_fe, + i2s); + } + } + } + + if (allocate_fifo) { + ret = tegra30_ahub_allocate_tx_fifo( + &i2s->playback_fifo_cif, &i2s->playback_dma_data.addr, &i2s->playback_dma_data.req_sel); - i2s->playback_dma_data.wrap = 4; - i2s->playback_dma_data.width = 32; + i2s->playback_dma_data.wrap = 4; + i2s->playback_dma_data.width = 32; + } else { + struct tegra30_i2s *allocated_be = + snd_soc_pcm_get_drvdata(allocated_fe); + if (allocated_be) { + memcpy(&i2s->playback_dma_data, + &allocated_be->playback_dma_data, + sizeof(struct tegra_pcm_dma_params)); + i2s->playback_fifo_cif = + allocated_be->playback_fifo_cif; + } + } + apbif_ref_cnt++; + mutex_unlock(&apbif_mutex); if (!i2s->is_dam_used) tegra30_ahub_set_rx_cif_source( @@ -131,8 +175,14 @@ void tegra30_i2s_shutdown(struct snd_pcm_substream *substream, tegra30_ahub_unset_rx_cif_source( i2s->playback_i2s_cif); + mutex_lock(&apbif_mutex); + apbif_ref_cnt--; /* free the apbif dma channel*/ - tegra30_ahub_free_tx_fifo(i2s->playback_fifo_cif); + if (!apbif_ref_cnt) { + tegra30_ahub_free_tx_fifo(i2s->playback_fifo_cif); + allocated_fe = NULL; + } + mutex_unlock(&apbif_mutex); i2s->playback_fifo_cif = -1; /* decrement the playback ref count */ diff --git a/sound/soc/tegra/tegra_max98090.c b/sound/soc/tegra/tegra_max98090.c index 4cf15c4098a4..d5a280cce764 100644 --- a/sound/soc/tegra/tegra_max98090.c +++ b/sound/soc/tegra/tegra_max98090.c @@ -1012,9 +1012,6 @@ static const struct snd_soc_dapm_route tegra_max98090_audio_map[] = { /* AHUB BE connections */ {"HiFi Playback", NULL, "I2S1_OUT"}, - - {"I2S1_OUT", NULL, "offload-pcm-playback"}, - {"I2S1_OUT", NULL, "offload-compr-playback"}, }; static const struct snd_kcontrol_new tegra_max98090_controls[] = { @@ -1160,7 +1157,7 @@ static struct snd_soc_dai_link tegra_max98090_dai[] = { .dynamic = 1, }, [DAI_LINK_I2S_OFFLOAD_BE] = { - .name = "offload-audio", + .name = "offload-audio-codec", .stream_name = "offload-audio-pcm", .codec_name = "max98090.0-0010", .codec_dai_name = "HiFi", diff --git a/sound/soc/tegra/tegra_offload.c b/sound/soc/tegra/tegra_offload.c index fbe766e4342c..bdfe94e30add 100644 --- a/sound/soc/tegra/tegra_offload.c +++ b/sound/soc/tegra/tegra_offload.c @@ -59,6 +59,8 @@ static struct tegra_offload_ops offload_ops; static int tegra_offload_init_done; static DEFINE_MUTEX(tegra_offload_lock); +static int codec, spk; + static const struct snd_pcm_hardware tegra_offload_pcm_hardware = { .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | @@ -247,32 +249,54 @@ static int tegra_offload_compr_set_params(struct snd_compr_stream *stream, struct snd_soc_pcm_runtime *rtd = stream->device->private_data; struct tegra_pcm_dma_params *dmap; struct tegra_offload_compr_params offl_params; + int dir; int ret = 0; dev_vdbg(dev, "%s", __func__); + if (stream->direction == SND_COMPRESS_PLAYBACK) + dir = SNDRV_PCM_STREAM_PLAYBACK; + else + dir = SNDRV_PCM_STREAM_CAPTURE; + dmap = rtd->cpu_dai->playback_dma_data; if (!dmap) { struct snd_soc_dpcm *dpcm; + if (list_empty(&rtd->dpcm[dir].be_clients)) { + dev_err(dev, "No backend DAIs enabled for %s\n", + rtd->dai_link->name); + return -EINVAL; + } + list_for_each_entry(dpcm, - &rtd->dpcm[SNDRV_PCM_STREAM_PLAYBACK].be_clients, - list_be) { + &rtd->dpcm[dir].be_clients, list_be) { struct snd_soc_pcm_runtime *be = dpcm->be; struct snd_pcm_substream *be_substream = - snd_soc_dpcm_get_substream(be, - SNDRV_PCM_STREAM_PLAYBACK); + snd_soc_dpcm_get_substream(be, dir); + struct snd_soc_dai_link *dai_link = be->dai_link; dmap = snd_soc_dai_get_dma_data(be->cpu_dai, - be_substream); - if (!dmap) { - dev_err(dev, "Failed to get DMA params."); - return -ENODEV; + be_substream); + + if (spk && strstr(dai_link->name, "speaker")) { + dmap = snd_soc_dai_get_dma_data(be->cpu_dai, + be_substream); + break; + } + if (codec && strstr(dai_link->name, "codec")) { + dmap = snd_soc_dai_get_dma_data(be->cpu_dai, + be_substream); + break; } /* TODO : Multiple BE to single FE not yet supported */ - break; } } + if (!dmap) { + dev_err(dev, "Failed to get DMA params."); + return -ENODEV; + } + offl_params.codec_type = params->codec.id; offl_params.bits_per_sample = 16; offl_params.rate = snd_pcm_rate_bit_to_rate(params->codec.sample_rate); @@ -472,23 +496,40 @@ static int tegra_offload_pcm_hw_params(struct snd_pcm_substream *substream, if (!dmap) { struct snd_soc_dpcm *dpcm; + if (list_empty(&rtd->dpcm[substream->stream].be_clients)) { + dev_err(dev, "No backend DAIs enabled for %s\n", + rtd->dai_link->name); + return -EINVAL; + } + list_for_each_entry(dpcm, &rtd->dpcm[substream->stream].be_clients, list_be) { struct snd_soc_pcm_runtime *be = dpcm->be; struct snd_pcm_substream *be_substream = snd_soc_dpcm_get_substream(be, substream->stream); + struct snd_soc_dai_link *dai_link = be->dai_link; dmap = snd_soc_dai_get_dma_data(be->cpu_dai, - be_substream); - if (!dmap) { - dev_err(dev, "Failed to get DMA params."); - return -ENODEV; + be_substream); + + if (spk && strstr(dai_link->name, "speaker")) { + dmap = snd_soc_dai_get_dma_data(be->cpu_dai, + be_substream); + break; + } + if (codec && strstr(dai_link->name, "codec")) { + dmap = snd_soc_dai_get_dma_data(be->cpu_dai, + be_substream); + break; } /* TODO : Multiple BE to single FE not yet supported */ - break; } } + if (!dmap) { + dev_err(dev, "Failed to get DMA params."); + return -ENODEV; + } offl_params.bits_per_sample = snd_pcm_format_width(params_format(params)); @@ -577,6 +618,59 @@ static int tegra_offload_pcm_ack(struct snd_pcm_substream *substream) return 0; } +static int codec_get_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = codec; + return 0; +} + +static int codec_put_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + + if (ucontrol->value.integer.value[0]) { + codec = ucontrol->value.integer.value[0]; + snd_soc_dapm_mixer_update_power(widget, kcontrol, 1); + } else { + codec = ucontrol->value.integer.value[0]; + snd_soc_dapm_mixer_update_power(widget, kcontrol, 0); + } + return 1; +} + +static int spk_get_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = spk; + return 0; +} + +static int spk_put_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + + if (ucontrol->value.integer.value[0]) { + spk = ucontrol->value.integer.value[0]; + snd_soc_dapm_mixer_update_power(widget, kcontrol, 1); + } else { + spk = ucontrol->value.integer.value[0]; + snd_soc_dapm_mixer_update_power(widget, kcontrol, 0); + } + return 1; +} + +static const struct snd_kcontrol_new codec_control = + SOC_SINGLE_EXT("Codec Switch", SND_SOC_NOPM, 0, 1, 0, + codec_get_mixer, codec_put_switch); + +static const struct snd_kcontrol_new spk_control = + SOC_SINGLE_EXT("SPK Switch", SND_SOC_NOPM, 1, 1, 0, + spk_get_mixer, spk_put_switch); static const struct snd_soc_dapm_widget tegra_offload_widgets[] = { /* BackEnd DAIs */ @@ -591,6 +685,20 @@ static const struct snd_soc_dapm_widget tegra_offload_widgets[] = { SND_SOC_DAPM_AIF_OUT("I2S4_OUT", "tegra30-i2s.4 Playback", 0, 0/*wreg*/, 0/*wshift*/, 0/*winvert*/), + SND_SOC_DAPM_MIXER("Codec VMixer", SND_SOC_NOPM, 0, 0, + &codec_control, 1), + SND_SOC_DAPM_MIXER("SPK VMixer", SND_SOC_NOPM, 0, 0, + &spk_control, 1), +}; + +static const struct snd_soc_dapm_route graph[] = { + {"Codec VMixer", "Codec Switch", "offload-pcm-playback"}, + {"Codec VMixer", "Codec Switch", "offload-compr-playback"}, + {"I2S1_OUT", NULL, "Codec VMixer"}, + + {"SPK VMixer", "SPK Switch", "offload-pcm-playback"}, + {"SPK VMixer", "SPK Switch", "offload-compr-playback"}, + {"I2S2_OUT", NULL, "SPK VMixer"}, }; static struct snd_pcm_ops tegra_pcm_ops = { @@ -677,10 +785,6 @@ static int tegra_offload_pcm_probe(struct snd_soc_platform *platform) { pr_debug("%s", __func__); - snd_soc_dapm_new_controls(&platform->dapm, tegra_offload_widgets, - ARRAY_SIZE(tegra_offload_widgets)); - - snd_soc_dapm_new_widgets(&platform->dapm); platform->dapm.idle_bias_off = 1; return 0; } @@ -705,6 +809,11 @@ static struct snd_soc_platform_driver tegra_offload_platform = { .probe = tegra_offload_pcm_probe, .read = tegra_offload_pcm_read, .write = tegra_offload_pcm_write, + + .dapm_widgets = tegra_offload_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra_offload_widgets), + .dapm_routes = graph, + .num_dapm_routes = ARRAY_SIZE(graph), }; static struct snd_soc_dai_driver tegra_offload_dai[] = { diff --git a/sound/soc/tegra/tegra_rt5639.c b/sound/soc/tegra/tegra_rt5639.c index df9161c84868..467375e15770 100644 --- a/sound/soc/tegra/tegra_rt5639.c +++ b/sound/soc/tegra/tegra_rt5639.c @@ -929,9 +929,6 @@ static const struct snd_soc_dapm_route ardbeg_audio_map[] = { /*{"IN1P", NULL, "micbias1"},*/ /*{"IN1N", NULL, "micbias1"},*/ /* AHUB BE connections */ - {"I2S1_OUT", NULL, "offload-pcm-playback"}, - {"I2S1_OUT", NULL, "offload-compr-playback"}, - {"AIF1 Playback", NULL, "I2S1_OUT"}, }; @@ -1066,7 +1063,7 @@ static struct snd_soc_dai_link tegra_rt5639_dai[NUM_DAI_LINKS] = { .dynamic = 1, }, [DAI_LINK_I2S_OFFLOAD_BE] = { - .name = "offload-audio", + .name = "offload-audio-codec", .stream_name = "offload-audio-pcm", .codec_name = "rt5639.0-001a", .platform_name = "tegra30-i2s.1", -- cgit v1.2.3