From bbbe33900d1f3c4402148ccb85234a741a6606a3 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 13 Aug 2010 08:45:23 +0200 Subject: ALSA: hda - Restrict PCM parameters per ELD information over HDMI When a device is plugged over HDMI, it passes some information in ELD including the supported PCM parameters like formats, rates, channels. This patch adds the check to PCM open callback of HDMI streams so that only valid parameters the device supports are used. When no device is plugged, the parameters the codec supports are used; it's mostly all parameters the hardware can work. This is for apps that are started before device plugging and do probing (e.g. a sound daemon), so that at least, probing would work even before the device plugging. Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_eld.c | 49 +++++++++++++++++++++++++++++++++++++++++ sound/pci/hda/hda_local.h | 2 ++ sound/pci/hda/patch_hdmi.c | 42 +++++++++++++++++++++++++++++++++++ sound/pci/hda/patch_intelhdmi.c | 1 + sound/pci/hda/patch_nvhdmi.c | 4 +--- 5 files changed, 95 insertions(+), 3 deletions(-) (limited to 'sound/pci') diff --git a/sound/pci/hda/hda_eld.c b/sound/pci/hda/hda_eld.c index d8da18a9e98b..803b298f7411 100644 --- a/sound/pci/hda/hda_eld.c +++ b/sound/pci/hda/hda_eld.c @@ -596,4 +596,53 @@ void snd_hda_eld_proc_free(struct hda_codec *codec, struct hdmi_eld *eld) } EXPORT_SYMBOL_HDA(snd_hda_eld_proc_free); +/* update PCM info based on ELD */ +void hdmi_eld_update_pcm_info(struct hdmi_eld *eld, struct hda_pcm_stream *pcm, + struct hda_pcm_stream *codec_pars) +{ + int i; + + pcm->rates = 0; + pcm->formats = 0; + pcm->maxbps = 0; + pcm->channels_min = -1; + pcm->channels_max = 0; + for (i = 0; i < eld->sad_count; i++) { + struct cea_sad *a = &eld->sad[i]; + pcm->rates |= a->rates; + if (a->channels < pcm->channels_min) + pcm->channels_min = a->channels; + if (a->channels > pcm->channels_max) + pcm->channels_max = a->channels; + if (a->format == AUDIO_CODING_TYPE_LPCM) { + if (a->sample_bits & AC_SUPPCM_BITS_16) { + pcm->formats |= SNDRV_PCM_FMTBIT_S16_LE; + if (pcm->maxbps < 16) + pcm->maxbps = 16; + } + if (a->sample_bits & AC_SUPPCM_BITS_20) { + pcm->formats |= SNDRV_PCM_FMTBIT_S32_LE; + if (pcm->maxbps < 20) + pcm->maxbps = 20; + } + if (a->sample_bits & AC_SUPPCM_BITS_24) { + pcm->formats |= SNDRV_PCM_FMTBIT_S32_LE; + if (pcm->maxbps < 24) + pcm->maxbps = 24; + } + } + } + + if (!codec_pars) + return; + + /* restrict the parameters by the values the codec provides */ + pcm->rates &= codec_pars->rates; + pcm->formats &= codec_pars->formats; + pcm->channels_min = max(pcm->channels_min, codec_pars->channels_min); + pcm->channels_max = min(pcm->channels_max, codec_pars->channels_max); + pcm->maxbps = min(pcm->maxbps, codec_pars->maxbps); +} +EXPORT_SYMBOL_HDA(hdmi_eld_update_pcm_info); + #endif /* CONFIG_PROC_FS */ diff --git a/sound/pci/hda/hda_local.h b/sound/pci/hda/hda_local.h index 7a97f126f6f7..28ab4aead48f 100644 --- a/sound/pci/hda/hda_local.h +++ b/sound/pci/hda/hda_local.h @@ -604,6 +604,8 @@ struct hdmi_eld { int snd_hdmi_get_eld_size(struct hda_codec *codec, hda_nid_t nid); int snd_hdmi_get_eld(struct hdmi_eld *, struct hda_codec *, hda_nid_t); void snd_hdmi_show_eld(struct hdmi_eld *eld); +void hdmi_eld_update_pcm_info(struct hdmi_eld *eld, struct hda_pcm_stream *pcm, + struct hda_pcm_stream *codec_pars); #ifdef CONFIG_PROC_FS int snd_hda_eld_proc_new(struct hda_codec *codec, struct hdmi_eld *eld, diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c index 522e0748ee99..2bc0f07cf33f 100644 --- a/sound/pci/hda/patch_hdmi.c +++ b/sound/pci/hda/patch_hdmi.c @@ -46,6 +46,7 @@ struct hdmi_spec { * export one pcm per pipe */ struct hda_pcm pcm_rec[MAX_HDMI_CVTS]; + struct hda_pcm_stream codec_pcm_pars[MAX_HDMI_CVTS]; /* * nvhdmi specific @@ -765,6 +766,47 @@ static int hdmi_setup_stream(struct hda_codec *codec, hda_nid_t nid, return 0; } +/* + * HDA PCM callbacks + */ +static int hdmi_pcm_open(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct hdmi_spec *spec = codec->spec; + struct hdmi_eld *eld; + struct hda_pcm_stream *codec_pars; + unsigned int idx; + + for (idx = 0; idx < spec->num_cvts; idx++) + if (hinfo->nid == spec->cvt[idx]) + break; + if (snd_BUG_ON(idx >= spec->num_cvts) || + snd_BUG_ON(idx >= spec->num_pins)) + return -EINVAL; + + /* save the PCM info the codec provides */ + codec_pars = &spec->codec_pcm_pars[idx]; + if (!codec_pars->rates) + *codec_pars = *hinfo; + + eld = &spec->sink_eld[idx]; + if (eld->sad_count > 0) { + hdmi_eld_update_pcm_info(eld, hinfo, codec_pars); + if (hinfo->channels_min > hinfo->channels_max || + !hinfo->rates || !hinfo->formats) + return -ENODEV; + } else { + /* fallback to the codec default */ + hinfo->channels_min = codec_pars->channels_min; + hinfo->channels_max = codec_pars->channels_max; + hinfo->rates = codec_pars->rates; + hinfo->formats = codec_pars->formats; + hinfo->maxbps = codec_pars->maxbps; + } + return 0; +} + /* * HDA/HDMI auto parsing */ diff --git a/sound/pci/hda/patch_intelhdmi.c b/sound/pci/hda/patch_intelhdmi.c index 5972d5e7d01f..d382d3c81c0f 100644 --- a/sound/pci/hda/patch_intelhdmi.c +++ b/sound/pci/hda/patch_intelhdmi.c @@ -80,6 +80,7 @@ static struct hda_pcm_stream intel_hdmi_pcm_playback = { .substreams = 1, .channels_min = 2, .ops = { + .open = hdmi_pcm_open, .prepare = intel_hdmi_playback_pcm_prepare, .cleanup = intel_hdmi_playback_pcm_cleanup, }, diff --git a/sound/pci/hda/patch_nvhdmi.c b/sound/pci/hda/patch_nvhdmi.c index 77e2b4028b9f..f636870dc718 100644 --- a/sound/pci/hda/patch_nvhdmi.c +++ b/sound/pci/hda/patch_nvhdmi.c @@ -347,10 +347,8 @@ static int nvhdmi_dig_playback_pcm_prepare_2ch(struct hda_pcm_stream *hinfo, static struct hda_pcm_stream nvhdmi_pcm_digital_playback_8ch_89 = { .substreams = 1, .channels_min = 2, - .rates = SUPPORTED_RATES, - .maxbps = SUPPORTED_MAXBPS, - .formats = SUPPORTED_FORMATS, .ops = { + .open = hdmi_pcm_open, .prepare = nvhdmi_dig_playback_pcm_prepare_8ch_89, .cleanup = nvhdmi_playback_pcm_cleanup, }, -- cgit v1.2.3-58-ga151 From f0cea79724f03ee55e7b5933b6a6f6a3fd177710 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 13 Aug 2010 11:56:53 +0200 Subject: ALSA: hda - Fix dynamic ADC change working again The commit eb541337b7a43822fce7d0c9d967ee149b2d9a96 ALSA: hda - Make converter setups sticky changes the semantics of snd_hda_codec_cleanup_stream() not to clean up the stream at that moment but delay the action. This broke the codes expecting that the clean-up is done immediately, such as dynamic ADC changes in some codec drivers. This patch fixes the issue by introducing a lower helper, __snd_hda_codec_cleanup_stream(), to allow the immediate clean up. The original snd_hda_codec_cleanup_stream() is kept as is now. Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_codec.c | 26 ++++++++++++++++++-------- sound/pci/hda/hda_codec.h | 5 ++++- sound/pci/hda/patch_cirrus.c | 2 +- sound/pci/hda/patch_conexant.c | 2 +- sound/pci/hda/patch_realtek.c | 2 +- 5 files changed, 25 insertions(+), 12 deletions(-) (limited to 'sound/pci') diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 720a81d711e3..dd8fb86c842b 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -1261,12 +1261,17 @@ void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid, } EXPORT_SYMBOL_HDA(snd_hda_codec_setup_stream); +static void really_cleanup_stream(struct hda_codec *codec, + struct hda_cvt_setup *q); + /** - * snd_hda_codec_cleanup_stream - clean up the codec for closing + * __snd_hda_codec_cleanup_stream - clean up the codec for closing * @codec: the CODEC to clean up * @nid: the NID to clean up + * @do_now: really clean up the stream instead of clearing the active flag */ -void snd_hda_codec_cleanup_stream(struct hda_codec *codec, hda_nid_t nid) +void __snd_hda_codec_cleanup_stream(struct hda_codec *codec, hda_nid_t nid, + int do_now) { struct hda_cvt_setup *p; @@ -1274,14 +1279,19 @@ void snd_hda_codec_cleanup_stream(struct hda_codec *codec, hda_nid_t nid) return; snd_printdd("hda_codec_cleanup_stream: NID=0x%x\n", nid); - /* here we just clear the active flag; actual clean-ups will be done - * in purify_inactive_streams() - */ p = get_hda_cvt_setup(codec, nid); - if (p) - p->active = 0; + if (p) { + /* here we just clear the active flag when do_now isn't set; + * actual clean-ups will be done later in + * purify_inactive_streams() called from snd_hda_codec_prpapre() + */ + if (do_now) + really_cleanup_stream(codec, p); + else + p->active = 0; + } } -EXPORT_SYMBOL_HDA(snd_hda_codec_cleanup_stream); +EXPORT_SYMBOL_HDA(__snd_hda_codec_cleanup_stream); static void really_cleanup_stream(struct hda_codec *codec, struct hda_cvt_setup *q) diff --git a/sound/pci/hda/hda_codec.h b/sound/pci/hda/hda_codec.h index 3f7a479881e5..4303353feda9 100644 --- a/sound/pci/hda/hda_codec.h +++ b/sound/pci/hda/hda_codec.h @@ -963,7 +963,10 @@ void snd_hda_codec_cleanup(struct hda_codec *codec, void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid, u32 stream_tag, int channel_id, int format); -void snd_hda_codec_cleanup_stream(struct hda_codec *codec, hda_nid_t nid); +void __snd_hda_codec_cleanup_stream(struct hda_codec *codec, hda_nid_t nid, + int do_now); +#define snd_hda_codec_cleanup_stream(codec, nid) \ + __snd_hda_codec_cleanup_stream(codec, nid, 0) unsigned int snd_hda_calc_stream_format(unsigned int rate, unsigned int channels, unsigned int format, diff --git a/sound/pci/hda/patch_cirrus.c b/sound/pci/hda/patch_cirrus.c index 350ee8ac4153..4ef5efaaaef1 100644 --- a/sound/pci/hda/patch_cirrus.c +++ b/sound/pci/hda/patch_cirrus.c @@ -656,7 +656,7 @@ static int change_cur_input(struct hda_codec *codec, unsigned int idx, return 0; if (spec->cur_adc && spec->cur_adc != spec->adc_nid[idx]) { /* stream is running, let's swap the current ADC */ - snd_hda_codec_cleanup_stream(codec, spec->cur_adc); + __snd_hda_codec_cleanup_stream(codec, spec->cur_adc, 1); spec->cur_adc = spec->adc_nid[idx]; snd_hda_codec_setup_stream(codec, spec->cur_adc, spec->cur_adc_stream_tag, 0, diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c index f7e234e5ee96..31b5d9eeba68 100644 --- a/sound/pci/hda/patch_conexant.c +++ b/sound/pci/hda/patch_conexant.c @@ -1733,7 +1733,7 @@ static void cxt5051_portc_automic(struct hda_codec *codec) new_adc = spec->adc_nids[spec->cur_adc_idx]; if (spec->cur_adc && spec->cur_adc != new_adc) { /* stream is running, let's swap the current ADC */ - snd_hda_codec_cleanup_stream(codec, spec->cur_adc); + __snd_hda_codec_cleanup_stream(codec, spec->cur_adc, 1); spec->cur_adc = new_adc; snd_hda_codec_setup_stream(codec, new_adc, spec->cur_adc_stream_tag, 0, diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 55d6e5b6bb7d..2cd1ae809e46 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -1037,7 +1037,7 @@ static void alc_dual_mic_adc_auto_switch(struct hda_codec *codec) new_adc = spec->adc_nids[spec->cur_adc_idx]; if (spec->cur_adc && spec->cur_adc != new_adc) { /* stream is running, let's swap the current ADC */ - snd_hda_codec_cleanup_stream(codec, spec->cur_adc); + __snd_hda_codec_cleanup_stream(codec, spec->cur_adc, 1); spec->cur_adc = new_adc; snd_hda_codec_setup_stream(codec, new_adc, spec->cur_adc_stream_tag, 0, -- cgit v1.2.3-58-ga151