diff mbox

[RFC,V2,1/5] ALSA: hda - hdmi playback without monitor in dynamic pcm bind mode

Message ID 1448287774-146673-3-git-send-email-libin.yang@linux.intel.com (mailing list archive)
State New, archived
Headers show

Commit Message

libin.yang@linux.intel.com Nov. 23, 2015, 2:09 p.m. UTC
From: Libin Yang <libin.yang@linux.intel.com>

Pulseaudio requires open pcm successfully when probing.

This patch handles playback without monitor in dynamic pcm assignment
mode. It tries to open/prepare/close pcm successfully even there is
no pin bound to the PCM. On the meantime, it will try to find a proper
converter for the PCM.

Signed-off-by: Libin Yang <libin.yang@linux.intel.com>
---
 sound/pci/hda/patch_hdmi.c | 171 +++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 157 insertions(+), 14 deletions(-)

Comments

Takashi Iwai Nov. 23, 2015, 4:01 p.m. UTC | #1
On Mon, 23 Nov 2015 15:09:30 +0100,
libin.yang@linux.intel.com wrote:
> 
> From: Libin Yang <libin.yang@linux.intel.com>
> 
> Pulseaudio requires open pcm successfully when probing.
> 
> This patch handles playback without monitor in dynamic pcm assignment
> mode. It tries to open/prepare/close pcm successfully even there is
> no pin bound to the PCM. On the meantime, it will try to find a proper
> converter for the PCM.

The explanation why you added pcm_lock is missing.


Takashi

> 
> Signed-off-by: Libin Yang <libin.yang@linux.intel.com>
> ---
>  sound/pci/hda/patch_hdmi.c | 171 +++++++++++++++++++++++++++++++++++++++++----
>  1 file changed, 157 insertions(+), 14 deletions(-)
> 
> diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c
> index 92e05fb..d0294bf 100644
> --- a/sound/pci/hda/patch_hdmi.c
> +++ b/sound/pci/hda/patch_hdmi.c
> @@ -135,6 +135,7 @@ struct hdmi_spec {
>  	int num_pins;
>  	struct snd_array pins; /* struct hdmi_spec_per_pin */
>  	struct hda_pcm *pcm_rec[16];
> +	struct mutex pcm_lock;
>  	unsigned int channels_max; /* max over all cvts */
>  
>  	struct hdmi_eld temp_eld;
> @@ -1331,6 +1332,11 @@ static int hdmi_setup_stream(struct hda_codec *codec, hda_nid_t cvt_nid,
>  	return 0;
>  }
>  
> +/* Try to find an available converter
> + * If pin_idx is less then zero, just try to find an available converter.
> + * Otherwise, try to find an available converter and get the cvt mux index
> + * of the pin.
> + */
>  static int hdmi_choose_cvt(struct hda_codec *codec,
>  			int pin_idx, int *cvt_id, int *mux_id)
>  {
> @@ -1339,7 +1345,11 @@ static int hdmi_choose_cvt(struct hda_codec *codec,
>  	struct hdmi_spec_per_cvt *per_cvt = NULL;
>  	int cvt_idx, mux_idx = 0;
>  
> -	per_pin = get_pin(spec, pin_idx);
> +	/* pin_idx < 0 means no pin will be bound to the converter */
> +	if (pin_idx < 0)
> +		per_pin = NULL;
> +	else
> +		per_pin = get_pin(spec, pin_idx);
>  
>  	/* Dynamically assign converter to stream */
>  	for (cvt_idx = 0; cvt_idx < spec->num_cvts; cvt_idx++) {
> @@ -1348,6 +1358,8 @@ static int hdmi_choose_cvt(struct hda_codec *codec,
>  		/* Must not already be assigned */
>  		if (per_cvt->assigned)
>  			continue;
> +		if (per_pin == NULL)
> +			break;
>  		/* Must be in pin's mux's list of converters */
>  		for (mux_idx = 0; mux_idx < per_pin->num_mux_nids; mux_idx++)
>  			if (per_pin->mux_nids[mux_idx] == per_cvt->cvt_nid)
> @@ -1360,9 +1372,10 @@ static int hdmi_choose_cvt(struct hda_codec *codec,
>  
>  	/* No free converters */
>  	if (cvt_idx == spec->num_cvts)
> -		return -ENODEV;
> +		return -EBUSY;
>  
> -	per_pin->mux_idx = mux_idx;
> +	if (per_pin != NULL)
> +		per_pin->mux_idx = mux_idx;
>  
>  	if (cvt_id)
>  		*cvt_id = cvt_idx;
> @@ -1388,6 +1401,20 @@ static void intel_verify_pin_cvt_connect(struct hda_codec *codec,
>  					    mux_idx);
>  }
>  
> +/* get the mux index for the converter of the pins
> + * converter's mux index is the same for all pins on Intel platform
> + */
> +static int intel_cvt_id_to_mux_idx(struct hdmi_spec *spec,
> +			hda_nid_t cvt_nid)
> +{
> +	int i;
> +
> +	for (i = 0; i < spec->num_cvts; i++)
> +		if (spec->cvt_nids[i] == cvt_nid)
> +			return i;
> +	return -EINVAL;
> +}
> +
>  /* Intel HDMI workaround to fix audio routing issue:
>   * For some Intel display codecs, pins share the same connection list.
>   * So a conveter can be selected by multiple pins and playback on any of these
> @@ -1439,6 +1466,69 @@ static void intel_not_share_assigned_cvt(struct hda_codec *codec,
>  	}
>  }
>  
> +/* A wrapper of intel_not_share_asigned_cvt() */
> +static void intel_not_share_assigned_cvt_nid(struct hda_codec *codec,
> +			hda_nid_t pin_nid, hda_nid_t cvt_nid)
> +{
> +	int mux_idx;
> +	struct hdmi_spec *spec = codec->spec;
> +
> +	if (!is_haswell_plus(codec) && !is_valleyview_plus(codec))
> +		return;
> +
> +	/* On Intel platform, the mapping of converter nid to
> +	 * mux index of the pins are always the same.
> +	 * The pin nid may be 0, this means all pins will not
> +	 * share the converter.
> +	 */
> +	mux_idx = intel_cvt_id_to_mux_idx(spec, cvt_nid);
> +	if (mux_idx > 0)
> +		intel_not_share_assigned_cvt(codec, pin_nid, mux_idx);
> +}
> +
> +/* called in hdmi_pcm_open when no pin is assigned to the PCM
> + * in dyn_pcm_assign mode.
> + */
> +static int hdmi_pcm_open_no_pin(struct hda_pcm_stream *hinfo,
> +			 struct hda_codec *codec,
> +			 struct snd_pcm_substream *substream)
> +{
> +	struct hdmi_spec *spec = codec->spec;
> +	struct snd_pcm_runtime *runtime = substream->runtime;
> +	int cvt_idx;
> +	struct hdmi_spec_per_cvt *per_cvt = NULL;
> +	int err;
> +
> +	err = hdmi_choose_cvt(codec, -1, &cvt_idx, NULL);
> +	if (err)
> +		return err;
> +
> +	per_cvt = get_cvt(spec, cvt_idx);
> +	per_cvt->assigned = 1;
> +	hinfo->nid = per_cvt->cvt_nid;
> +
> +	intel_not_share_assigned_cvt_nid(codec, 0, per_cvt->cvt_nid);
> +
> +	/* todo: setup spdif ctls assign */
> +
> +	/* Initially set the converter's capabilities */
> +	hinfo->channels_min = per_cvt->channels_min;
> +	hinfo->channels_max = per_cvt->channels_max;
> +	hinfo->rates = per_cvt->rates;
> +	hinfo->formats = per_cvt->formats;
> +	hinfo->maxbps = per_cvt->maxbps;
> +
> +	/* Store the updated parameters */
> +	runtime->hw.channels_min = hinfo->channels_min;
> +	runtime->hw.channels_max = hinfo->channels_max;
> +	runtime->hw.formats = hinfo->formats;
> +	runtime->hw.rates = hinfo->rates;
> +
> +	snd_pcm_hw_constraint_step(substream->runtime, 0,
> +				   SNDRV_PCM_HW_PARAM_CHANNELS, 2);
> +	return 0;
> +}
> +
>  /*
>   * HDA PCM callbacks
>   */
> @@ -1455,19 +1545,36 @@ static int hdmi_pcm_open(struct hda_pcm_stream *hinfo,
>  	int err;
>  
>  	/* Validate hinfo */
> +	mutex_lock(&spec->pcm_lock);
>  	pin_idx = hinfo_to_pin_index(codec, hinfo);
> -	if (snd_BUG_ON(pin_idx < 0))
> -		return -EINVAL;
> -	per_pin = get_pin(spec, pin_idx);
> -	eld = &per_pin->sink_eld;
> +	if (!spec->dyn_pcm_assign) {
> +		if (snd_BUG_ON(pin_idx < 0)) {
> +			mutex_unlock(&spec->pcm_lock);
> +			return -EINVAL;
> +		}
> +	} else {
> +		/* no pin is assigned to the PCM
> +		 * PA need pcm open successfully when probe
> +		 */
> +		if (pin_idx < 0) {
> +			err = hdmi_pcm_open_no_pin(hinfo, codec, substream);
> +			mutex_unlock(&spec->pcm_lock);
> +			return err;
> +		}
> +	}
>  
>  	err = hdmi_choose_cvt(codec, pin_idx, &cvt_idx, &mux_idx);
> -	if (err < 0)
> +	if (err < 0) {
> +		mutex_unlock(&spec->pcm_lock);
>  		return err;
> +	}
>  
>  	per_cvt = get_cvt(spec, cvt_idx);
>  	/* Claim converter */
>  	per_cvt->assigned = 1;
> +
> +
> +	per_pin = get_pin(spec, pin_idx);
>  	per_pin->cvt_nid = per_cvt->cvt_nid;
>  	hinfo->nid = per_cvt->cvt_nid;
>  
> @@ -1488,6 +1595,7 @@ static int hdmi_pcm_open(struct hda_pcm_stream *hinfo,
>  	hinfo->formats = per_cvt->formats;
>  	hinfo->maxbps = per_cvt->maxbps;
>  
> +	eld = &per_pin->sink_eld;
>  	/* Restrict capabilities by ELD if this isn't disabled */
>  	if (!static_hdmi_pcm && eld->eld_valid) {
>  		snd_hdmi_eld_update_pcm_info(&eld->info, hinfo);
> @@ -1496,10 +1604,12 @@ static int hdmi_pcm_open(struct hda_pcm_stream *hinfo,
>  			per_cvt->assigned = 0;
>  			hinfo->nid = 0;
>  			snd_hda_spdif_ctls_unassign(codec, pin_idx);
> +			mutex_unlock(&spec->pcm_lock);
>  			return -ENODEV;
>  		}
>  	}
>  
> +	mutex_unlock(&spec->pcm_lock);
>  	/* Store the updated parameters */
>  	runtime->hw.channels_min = hinfo->channels_min;
>  	runtime->hw.channels_max = hinfo->channels_max;
> @@ -1803,13 +1913,33 @@ static int generic_hdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
>  {
>  	hda_nid_t cvt_nid = hinfo->nid;
>  	struct hdmi_spec *spec = codec->spec;
> -	int pin_idx = hinfo_to_pin_index(codec, hinfo);
> -	struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx);
> -	hda_nid_t pin_nid = per_pin->pin_nid;
> +	int pin_idx;
> +	struct hdmi_spec_per_pin *per_pin;
> +	hda_nid_t pin_nid;
>  	struct snd_pcm_runtime *runtime = substream->runtime;
>  	struct i915_audio_component *acomp = codec->bus->core.audio_component;
>  	bool non_pcm;
>  	int pinctl;
> +	int err;
> +
> +	mutex_lock(&spec->pcm_lock);
> +	pin_idx = hinfo_to_pin_index(codec, hinfo);
> +	if (spec->dyn_pcm_assign && pin_idx < 0) {
> +		/* when dyn_pcm_assign and pcm is not bound to a pin
> +		 * skip pin setup and return 0 to make audio playback
> +		 * be ongoing
> +		 */
> +		intel_not_share_assigned_cvt_nid(codec, 0, cvt_nid);
> +		snd_hda_codec_setup_stream(codec, cvt_nid,
> +					stream_tag, 0, format);
> +		mutex_unlock(&spec->pcm_lock);
> +		return 0;
> +	}
> +
> +	if (snd_BUG_ON(pin_idx < 0))
> +		return -EINVAL;
> +	per_pin = get_pin(spec, pin_idx);
> +	pin_nid = per_pin->pin_nid;
>  
>  	if (is_haswell_plus(codec) || is_valleyview_plus(codec)) {
>  		/* Verify pin:cvt selections to avoid silent audio after S3.
> @@ -1838,7 +1968,7 @@ static int generic_hdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
>  
>  	hdmi_setup_audio_infoframe(codec, per_pin, non_pcm);
>  	mutex_unlock(&per_pin->lock);
> -
> +	mutex_unlock(&spec->pcm_lock);
>  	if (spec->dyn_pin_out) {
>  		pinctl = snd_hda_codec_read(codec, pin_nid, 0,
>  					    AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
> @@ -1847,7 +1977,10 @@ static int generic_hdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
>  				    pinctl | PIN_OUT);
>  	}
>  
> -	return spec->ops.setup_stream(codec, cvt_nid, pin_nid, stream_tag, format);
> +	err = spec->ops.setup_stream(codec, cvt_nid, pin_nid,
> +				 stream_tag, format);
> +	mutex_unlock(&spec->pcm_lock);
> +	return err;
>  }
>  
>  static int generic_hdmi_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
> @@ -1878,9 +2011,17 @@ static int hdmi_pcm_close(struct hda_pcm_stream *hinfo,
>  		per_cvt->assigned = 0;
>  		hinfo->nid = 0;
>  
> +		mutex_lock(&spec->pcm_lock);
>  		pin_idx = hinfo_to_pin_index(codec, hinfo);
> -		if (snd_BUG_ON(pin_idx < 0))
> +		if (spec->dyn_pcm_assign && pin_idx < 0) {
> +			mutex_unlock(&spec->pcm_lock);
> +			return 0;
> +		}
> +
> +		if (snd_BUG_ON(pin_idx < 0)) {
> +			mutex_unlock(&spec->pcm_lock);
>  			return -EINVAL;
> +		}
>  		per_pin = get_pin(spec, pin_idx);
>  
>  		if (spec->dyn_pin_out) {
> @@ -1900,6 +2041,7 @@ static int hdmi_pcm_close(struct hda_pcm_stream *hinfo,
>  		per_pin->setup = false;
>  		per_pin->channels = 0;
>  		mutex_unlock(&per_pin->lock);
> +		mutex_unlock(&spec->pcm_lock);
>  	}
>  
>  	return 0;
> @@ -2370,6 +2512,7 @@ static int patch_generic_hdmi(struct hda_codec *codec)
>  		return -ENOMEM;
>  
>  	spec->ops = generic_standard_hdmi_ops;
> +	mutex_init(&spec->pcm_lock);
>  	codec->spec = spec;
>  	hdmi_array_init(spec, 4);
>  
> -- 
> 1.9.1
>
Yang, Libin Nov. 25, 2015, 6:50 a.m. UTC | #2
> -----Original Message-----
> From: Takashi Iwai [mailto:tiwai@suse.de]
> Sent: Tuesday, November 24, 2015 12:01 AM
> To: libin.yang@linux.intel.com
> Cc: alsa-devel@alsa-project.org; mengdong.lin@linux.intel.com; Yang,
> Libin
> Subject: Re: [alsa-devel] [RFC V2 PATCH 1/5] ALSA: hda - hdmi playback
> without monitor in dynamic pcm bind mode
> 
> On Mon, 23 Nov 2015 15:09:30 +0100,
> libin.yang@linux.intel.com wrote:
> >
> > From: Libin Yang <libin.yang@linux.intel.com>
> >
> > Pulseaudio requires open pcm successfully when probing.
> >
> > This patch handles playback without monitor in dynamic pcm
> assignment
> > mode. It tries to open/prepare/close pcm successfully even there is
> > no pin bound to the PCM. On the meantime, it will try to find a proper
> > converter for the PCM.
> 
> The explanation why you added pcm_lock is missing.

Get it. I will add the explanation for pcm_lock.

Regards,
Libin

> 
> 
> Takashi
> 
> >
> > Signed-off-by: Libin Yang <libin.yang@linux.intel.com>
> > ---
> >  sound/pci/hda/patch_hdmi.c | 171
> +++++++++++++++++++++++++++++++++++++++++----
> >  1 file changed, 157 insertions(+), 14 deletions(-)
> >
> > diff --git a/sound/pci/hda/patch_hdmi.c
> b/sound/pci/hda/patch_hdmi.c
> > index 92e05fb..d0294bf 100644
> > --- a/sound/pci/hda/patch_hdmi.c
> > +++ b/sound/pci/hda/patch_hdmi.c
> > @@ -135,6 +135,7 @@ struct hdmi_spec {
> >  	int num_pins;
> >  	struct snd_array pins; /* struct hdmi_spec_per_pin */
> >  	struct hda_pcm *pcm_rec[16];
> > +	struct mutex pcm_lock;
> >  	unsigned int channels_max; /* max over all cvts */
> >
> >  	struct hdmi_eld temp_eld;
> > @@ -1331,6 +1332,11 @@ static int hdmi_setup_stream(struct
> hda_codec *codec, hda_nid_t cvt_nid,
> >  	return 0;
> >  }
> >
> > +/* Try to find an available converter
> > + * If pin_idx is less then zero, just try to find an available converter.
> > + * Otherwise, try to find an available converter and get the cvt mux
> index
> > + * of the pin.
> > + */
> >  static int hdmi_choose_cvt(struct hda_codec *codec,
> >  			int pin_idx, int *cvt_id, int *mux_id)
> >  {
> > @@ -1339,7 +1345,11 @@ static int hdmi_choose_cvt(struct
> hda_codec *codec,
> >  	struct hdmi_spec_per_cvt *per_cvt = NULL;
> >  	int cvt_idx, mux_idx = 0;
> >
> > -	per_pin = get_pin(spec, pin_idx);
> > +	/* pin_idx < 0 means no pin will be bound to the converter */
> > +	if (pin_idx < 0)
> > +		per_pin = NULL;
> > +	else
> > +		per_pin = get_pin(spec, pin_idx);
> >
> >  	/* Dynamically assign converter to stream */
> >  	for (cvt_idx = 0; cvt_idx < spec->num_cvts; cvt_idx++) {
> > @@ -1348,6 +1358,8 @@ static int hdmi_choose_cvt(struct
> hda_codec *codec,
> >  		/* Must not already be assigned */
> >  		if (per_cvt->assigned)
> >  			continue;
> > +		if (per_pin == NULL)
> > +			break;
> >  		/* Must be in pin's mux's list of converters */
> >  		for (mux_idx = 0; mux_idx < per_pin->num_mux_nids;
> mux_idx++)
> >  			if (per_pin->mux_nids[mux_idx] == per_cvt-
> >cvt_nid)
> > @@ -1360,9 +1372,10 @@ static int hdmi_choose_cvt(struct
> hda_codec *codec,
> >
> >  	/* No free converters */
> >  	if (cvt_idx == spec->num_cvts)
> > -		return -ENODEV;
> > +		return -EBUSY;
> >
> > -	per_pin->mux_idx = mux_idx;
> > +	if (per_pin != NULL)
> > +		per_pin->mux_idx = mux_idx;
> >
> >  	if (cvt_id)
> >  		*cvt_id = cvt_idx;
> > @@ -1388,6 +1401,20 @@ static void
> intel_verify_pin_cvt_connect(struct hda_codec *codec,
> >  					    mux_idx);
> >  }
> >
> > +/* get the mux index for the converter of the pins
> > + * converter's mux index is the same for all pins on Intel platform
> > + */
> > +static int intel_cvt_id_to_mux_idx(struct hdmi_spec *spec,
> > +			hda_nid_t cvt_nid)
> > +{
> > +	int i;
> > +
> > +	for (i = 0; i < spec->num_cvts; i++)
> > +		if (spec->cvt_nids[i] == cvt_nid)
> > +			return i;
> > +	return -EINVAL;
> > +}
> > +
> >  /* Intel HDMI workaround to fix audio routing issue:
> >   * For some Intel display codecs, pins share the same connection list.
> >   * So a conveter can be selected by multiple pins and playback on any
> of these
> > @@ -1439,6 +1466,69 @@ static void
> intel_not_share_assigned_cvt(struct hda_codec *codec,
> >  	}
> >  }
> >
> > +/* A wrapper of intel_not_share_asigned_cvt() */
> > +static void intel_not_share_assigned_cvt_nid(struct hda_codec
> *codec,
> > +			hda_nid_t pin_nid, hda_nid_t cvt_nid)
> > +{
> > +	int mux_idx;
> > +	struct hdmi_spec *spec = codec->spec;
> > +
> > +	if (!is_haswell_plus(codec) && !is_valleyview_plus(codec))
> > +		return;
> > +
> > +	/* On Intel platform, the mapping of converter nid to
> > +	 * mux index of the pins are always the same.
> > +	 * The pin nid may be 0, this means all pins will not
> > +	 * share the converter.
> > +	 */
> > +	mux_idx = intel_cvt_id_to_mux_idx(spec, cvt_nid);
> > +	if (mux_idx > 0)
> > +		intel_not_share_assigned_cvt(codec, pin_nid, mux_idx);
> > +}
> > +
> > +/* called in hdmi_pcm_open when no pin is assigned to the PCM
> > + * in dyn_pcm_assign mode.
> > + */
> > +static int hdmi_pcm_open_no_pin(struct hda_pcm_stream *hinfo,
> > +			 struct hda_codec *codec,
> > +			 struct snd_pcm_substream *substream)
> > +{
> > +	struct hdmi_spec *spec = codec->spec;
> > +	struct snd_pcm_runtime *runtime = substream->runtime;
> > +	int cvt_idx;
> > +	struct hdmi_spec_per_cvt *per_cvt = NULL;
> > +	int err;
> > +
> > +	err = hdmi_choose_cvt(codec, -1, &cvt_idx, NULL);
> > +	if (err)
> > +		return err;
> > +
> > +	per_cvt = get_cvt(spec, cvt_idx);
> > +	per_cvt->assigned = 1;
> > +	hinfo->nid = per_cvt->cvt_nid;
> > +
> > +	intel_not_share_assigned_cvt_nid(codec, 0, per_cvt->cvt_nid);
> > +
> > +	/* todo: setup spdif ctls assign */
> > +
> > +	/* Initially set the converter's capabilities */
> > +	hinfo->channels_min = per_cvt->channels_min;
> > +	hinfo->channels_max = per_cvt->channels_max;
> > +	hinfo->rates = per_cvt->rates;
> > +	hinfo->formats = per_cvt->formats;
> > +	hinfo->maxbps = per_cvt->maxbps;
> > +
> > +	/* Store the updated parameters */
> > +	runtime->hw.channels_min = hinfo->channels_min;
> > +	runtime->hw.channels_max = hinfo->channels_max;
> > +	runtime->hw.formats = hinfo->formats;
> > +	runtime->hw.rates = hinfo->rates;
> > +
> > +	snd_pcm_hw_constraint_step(substream->runtime, 0,
> > +
> SNDRV_PCM_HW_PARAM_CHANNELS, 2);
> > +	return 0;
> > +}
> > +
> >  /*
> >   * HDA PCM callbacks
> >   */
> > @@ -1455,19 +1545,36 @@ static int hdmi_pcm_open(struct
> hda_pcm_stream *hinfo,
> >  	int err;
> >
> >  	/* Validate hinfo */
> > +	mutex_lock(&spec->pcm_lock);
> >  	pin_idx = hinfo_to_pin_index(codec, hinfo);
> > -	if (snd_BUG_ON(pin_idx < 0))
> > -		return -EINVAL;
> > -	per_pin = get_pin(spec, pin_idx);
> > -	eld = &per_pin->sink_eld;
> > +	if (!spec->dyn_pcm_assign) {
> > +		if (snd_BUG_ON(pin_idx < 0)) {
> > +			mutex_unlock(&spec->pcm_lock);
> > +			return -EINVAL;
> > +		}
> > +	} else {
> > +		/* no pin is assigned to the PCM
> > +		 * PA need pcm open successfully when probe
> > +		 */
> > +		if (pin_idx < 0) {
> > +			err = hdmi_pcm_open_no_pin(hinfo, codec,
> substream);
> > +			mutex_unlock(&spec->pcm_lock);
> > +			return err;
> > +		}
> > +	}
> >
> >  	err = hdmi_choose_cvt(codec, pin_idx, &cvt_idx, &mux_idx);
> > -	if (err < 0)
> > +	if (err < 0) {
> > +		mutex_unlock(&spec->pcm_lock);
> >  		return err;
> > +	}
> >
> >  	per_cvt = get_cvt(spec, cvt_idx);
> >  	/* Claim converter */
> >  	per_cvt->assigned = 1;
> > +
> > +
> > +	per_pin = get_pin(spec, pin_idx);
> >  	per_pin->cvt_nid = per_cvt->cvt_nid;
> >  	hinfo->nid = per_cvt->cvt_nid;
> >
> > @@ -1488,6 +1595,7 @@ static int hdmi_pcm_open(struct
> hda_pcm_stream *hinfo,
> >  	hinfo->formats = per_cvt->formats;
> >  	hinfo->maxbps = per_cvt->maxbps;
> >
> > +	eld = &per_pin->sink_eld;
> >  	/* Restrict capabilities by ELD if this isn't disabled */
> >  	if (!static_hdmi_pcm && eld->eld_valid) {
> >  		snd_hdmi_eld_update_pcm_info(&eld->info, hinfo);
> > @@ -1496,10 +1604,12 @@ static int hdmi_pcm_open(struct
> hda_pcm_stream *hinfo,
> >  			per_cvt->assigned = 0;
> >  			hinfo->nid = 0;
> >  			snd_hda_spdif_ctls_unassign(codec, pin_idx);
> > +			mutex_unlock(&spec->pcm_lock);
> >  			return -ENODEV;
> >  		}
> >  	}
> >
> > +	mutex_unlock(&spec->pcm_lock);
> >  	/* Store the updated parameters */
> >  	runtime->hw.channels_min = hinfo->channels_min;
> >  	runtime->hw.channels_max = hinfo->channels_max;
> > @@ -1803,13 +1913,33 @@ static int
> generic_hdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
> >  {
> >  	hda_nid_t cvt_nid = hinfo->nid;
> >  	struct hdmi_spec *spec = codec->spec;
> > -	int pin_idx = hinfo_to_pin_index(codec, hinfo);
> > -	struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx);
> > -	hda_nid_t pin_nid = per_pin->pin_nid;
> > +	int pin_idx;
> > +	struct hdmi_spec_per_pin *per_pin;
> > +	hda_nid_t pin_nid;
> >  	struct snd_pcm_runtime *runtime = substream->runtime;
> >  	struct i915_audio_component *acomp = codec->bus-
> >core.audio_component;
> >  	bool non_pcm;
> >  	int pinctl;
> > +	int err;
> > +
> > +	mutex_lock(&spec->pcm_lock);
> > +	pin_idx = hinfo_to_pin_index(codec, hinfo);
> > +	if (spec->dyn_pcm_assign && pin_idx < 0) {
> > +		/* when dyn_pcm_assign and pcm is not bound to a pin
> > +		 * skip pin setup and return 0 to make audio playback
> > +		 * be ongoing
> > +		 */
> > +		intel_not_share_assigned_cvt_nid(codec, 0, cvt_nid);
> > +		snd_hda_codec_setup_stream(codec, cvt_nid,
> > +					stream_tag, 0, format);
> > +		mutex_unlock(&spec->pcm_lock);
> > +		return 0;
> > +	}
> > +
> > +	if (snd_BUG_ON(pin_idx < 0))
> > +		return -EINVAL;
> > +	per_pin = get_pin(spec, pin_idx);
> > +	pin_nid = per_pin->pin_nid;
> >
> >  	if (is_haswell_plus(codec) || is_valleyview_plus(codec)) {
> >  		/* Verify pin:cvt selections to avoid silent audio after S3.
> > @@ -1838,7 +1968,7 @@ static int
> generic_hdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
> >
> >  	hdmi_setup_audio_infoframe(codec, per_pin, non_pcm);
> >  	mutex_unlock(&per_pin->lock);
> > -
> > +	mutex_unlock(&spec->pcm_lock);
> >  	if (spec->dyn_pin_out) {
> >  		pinctl = snd_hda_codec_read(codec, pin_nid, 0,
> >
> AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
> > @@ -1847,7 +1977,10 @@ static int
> generic_hdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
> >  				    pinctl | PIN_OUT);
> >  	}
> >
> > -	return spec->ops.setup_stream(codec, cvt_nid, pin_nid,
> stream_tag, format);
> > +	err = spec->ops.setup_stream(codec, cvt_nid, pin_nid,
> > +				 stream_tag, format);
> > +	mutex_unlock(&spec->pcm_lock);
> > +	return err;
> >  }
> >
> >  static int generic_hdmi_playback_pcm_cleanup(struct
> hda_pcm_stream *hinfo,
> > @@ -1878,9 +2011,17 @@ static int hdmi_pcm_close(struct
> hda_pcm_stream *hinfo,
> >  		per_cvt->assigned = 0;
> >  		hinfo->nid = 0;
> >
> > +		mutex_lock(&spec->pcm_lock);
> >  		pin_idx = hinfo_to_pin_index(codec, hinfo);
> > -		if (snd_BUG_ON(pin_idx < 0))
> > +		if (spec->dyn_pcm_assign && pin_idx < 0) {
> > +			mutex_unlock(&spec->pcm_lock);
> > +			return 0;
> > +		}
> > +
> > +		if (snd_BUG_ON(pin_idx < 0)) {
> > +			mutex_unlock(&spec->pcm_lock);
> >  			return -EINVAL;
> > +		}
> >  		per_pin = get_pin(spec, pin_idx);
> >
> >  		if (spec->dyn_pin_out) {
> > @@ -1900,6 +2041,7 @@ static int hdmi_pcm_close(struct
> hda_pcm_stream *hinfo,
> >  		per_pin->setup = false;
> >  		per_pin->channels = 0;
> >  		mutex_unlock(&per_pin->lock);
> > +		mutex_unlock(&spec->pcm_lock);
> >  	}
> >
> >  	return 0;
> > @@ -2370,6 +2512,7 @@ static int patch_generic_hdmi(struct
> hda_codec *codec)
> >  		return -ENOMEM;
> >
> >  	spec->ops = generic_standard_hdmi_ops;
> > +	mutex_init(&spec->pcm_lock);
> >  	codec->spec = spec;
> >  	hdmi_array_init(spec, 4);
> >
> > --
> > 1.9.1
> >
diff mbox

Patch

diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c
index 92e05fb..d0294bf 100644
--- a/sound/pci/hda/patch_hdmi.c
+++ b/sound/pci/hda/patch_hdmi.c
@@ -135,6 +135,7 @@  struct hdmi_spec {
 	int num_pins;
 	struct snd_array pins; /* struct hdmi_spec_per_pin */
 	struct hda_pcm *pcm_rec[16];
+	struct mutex pcm_lock;
 	unsigned int channels_max; /* max over all cvts */
 
 	struct hdmi_eld temp_eld;
@@ -1331,6 +1332,11 @@  static int hdmi_setup_stream(struct hda_codec *codec, hda_nid_t cvt_nid,
 	return 0;
 }
 
+/* Try to find an available converter
+ * If pin_idx is less then zero, just try to find an available converter.
+ * Otherwise, try to find an available converter and get the cvt mux index
+ * of the pin.
+ */
 static int hdmi_choose_cvt(struct hda_codec *codec,
 			int pin_idx, int *cvt_id, int *mux_id)
 {
@@ -1339,7 +1345,11 @@  static int hdmi_choose_cvt(struct hda_codec *codec,
 	struct hdmi_spec_per_cvt *per_cvt = NULL;
 	int cvt_idx, mux_idx = 0;
 
-	per_pin = get_pin(spec, pin_idx);
+	/* pin_idx < 0 means no pin will be bound to the converter */
+	if (pin_idx < 0)
+		per_pin = NULL;
+	else
+		per_pin = get_pin(spec, pin_idx);
 
 	/* Dynamically assign converter to stream */
 	for (cvt_idx = 0; cvt_idx < spec->num_cvts; cvt_idx++) {
@@ -1348,6 +1358,8 @@  static int hdmi_choose_cvt(struct hda_codec *codec,
 		/* Must not already be assigned */
 		if (per_cvt->assigned)
 			continue;
+		if (per_pin == NULL)
+			break;
 		/* Must be in pin's mux's list of converters */
 		for (mux_idx = 0; mux_idx < per_pin->num_mux_nids; mux_idx++)
 			if (per_pin->mux_nids[mux_idx] == per_cvt->cvt_nid)
@@ -1360,9 +1372,10 @@  static int hdmi_choose_cvt(struct hda_codec *codec,
 
 	/* No free converters */
 	if (cvt_idx == spec->num_cvts)
-		return -ENODEV;
+		return -EBUSY;
 
-	per_pin->mux_idx = mux_idx;
+	if (per_pin != NULL)
+		per_pin->mux_idx = mux_idx;
 
 	if (cvt_id)
 		*cvt_id = cvt_idx;
@@ -1388,6 +1401,20 @@  static void intel_verify_pin_cvt_connect(struct hda_codec *codec,
 					    mux_idx);
 }
 
+/* get the mux index for the converter of the pins
+ * converter's mux index is the same for all pins on Intel platform
+ */
+static int intel_cvt_id_to_mux_idx(struct hdmi_spec *spec,
+			hda_nid_t cvt_nid)
+{
+	int i;
+
+	for (i = 0; i < spec->num_cvts; i++)
+		if (spec->cvt_nids[i] == cvt_nid)
+			return i;
+	return -EINVAL;
+}
+
 /* Intel HDMI workaround to fix audio routing issue:
  * For some Intel display codecs, pins share the same connection list.
  * So a conveter can be selected by multiple pins and playback on any of these
@@ -1439,6 +1466,69 @@  static void intel_not_share_assigned_cvt(struct hda_codec *codec,
 	}
 }
 
+/* A wrapper of intel_not_share_asigned_cvt() */
+static void intel_not_share_assigned_cvt_nid(struct hda_codec *codec,
+			hda_nid_t pin_nid, hda_nid_t cvt_nid)
+{
+	int mux_idx;
+	struct hdmi_spec *spec = codec->spec;
+
+	if (!is_haswell_plus(codec) && !is_valleyview_plus(codec))
+		return;
+
+	/* On Intel platform, the mapping of converter nid to
+	 * mux index of the pins are always the same.
+	 * The pin nid may be 0, this means all pins will not
+	 * share the converter.
+	 */
+	mux_idx = intel_cvt_id_to_mux_idx(spec, cvt_nid);
+	if (mux_idx > 0)
+		intel_not_share_assigned_cvt(codec, pin_nid, mux_idx);
+}
+
+/* called in hdmi_pcm_open when no pin is assigned to the PCM
+ * in dyn_pcm_assign mode.
+ */
+static int hdmi_pcm_open_no_pin(struct hda_pcm_stream *hinfo,
+			 struct hda_codec *codec,
+			 struct snd_pcm_substream *substream)
+{
+	struct hdmi_spec *spec = codec->spec;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int cvt_idx;
+	struct hdmi_spec_per_cvt *per_cvt = NULL;
+	int err;
+
+	err = hdmi_choose_cvt(codec, -1, &cvt_idx, NULL);
+	if (err)
+		return err;
+
+	per_cvt = get_cvt(spec, cvt_idx);
+	per_cvt->assigned = 1;
+	hinfo->nid = per_cvt->cvt_nid;
+
+	intel_not_share_assigned_cvt_nid(codec, 0, per_cvt->cvt_nid);
+
+	/* todo: setup spdif ctls assign */
+
+	/* Initially set the converter's capabilities */
+	hinfo->channels_min = per_cvt->channels_min;
+	hinfo->channels_max = per_cvt->channels_max;
+	hinfo->rates = per_cvt->rates;
+	hinfo->formats = per_cvt->formats;
+	hinfo->maxbps = per_cvt->maxbps;
+
+	/* Store the updated parameters */
+	runtime->hw.channels_min = hinfo->channels_min;
+	runtime->hw.channels_max = hinfo->channels_max;
+	runtime->hw.formats = hinfo->formats;
+	runtime->hw.rates = hinfo->rates;
+
+	snd_pcm_hw_constraint_step(substream->runtime, 0,
+				   SNDRV_PCM_HW_PARAM_CHANNELS, 2);
+	return 0;
+}
+
 /*
  * HDA PCM callbacks
  */
@@ -1455,19 +1545,36 @@  static int hdmi_pcm_open(struct hda_pcm_stream *hinfo,
 	int err;
 
 	/* Validate hinfo */
+	mutex_lock(&spec->pcm_lock);
 	pin_idx = hinfo_to_pin_index(codec, hinfo);
-	if (snd_BUG_ON(pin_idx < 0))
-		return -EINVAL;
-	per_pin = get_pin(spec, pin_idx);
-	eld = &per_pin->sink_eld;
+	if (!spec->dyn_pcm_assign) {
+		if (snd_BUG_ON(pin_idx < 0)) {
+			mutex_unlock(&spec->pcm_lock);
+			return -EINVAL;
+		}
+	} else {
+		/* no pin is assigned to the PCM
+		 * PA need pcm open successfully when probe
+		 */
+		if (pin_idx < 0) {
+			err = hdmi_pcm_open_no_pin(hinfo, codec, substream);
+			mutex_unlock(&spec->pcm_lock);
+			return err;
+		}
+	}
 
 	err = hdmi_choose_cvt(codec, pin_idx, &cvt_idx, &mux_idx);
-	if (err < 0)
+	if (err < 0) {
+		mutex_unlock(&spec->pcm_lock);
 		return err;
+	}
 
 	per_cvt = get_cvt(spec, cvt_idx);
 	/* Claim converter */
 	per_cvt->assigned = 1;
+
+
+	per_pin = get_pin(spec, pin_idx);
 	per_pin->cvt_nid = per_cvt->cvt_nid;
 	hinfo->nid = per_cvt->cvt_nid;
 
@@ -1488,6 +1595,7 @@  static int hdmi_pcm_open(struct hda_pcm_stream *hinfo,
 	hinfo->formats = per_cvt->formats;
 	hinfo->maxbps = per_cvt->maxbps;
 
+	eld = &per_pin->sink_eld;
 	/* Restrict capabilities by ELD if this isn't disabled */
 	if (!static_hdmi_pcm && eld->eld_valid) {
 		snd_hdmi_eld_update_pcm_info(&eld->info, hinfo);
@@ -1496,10 +1604,12 @@  static int hdmi_pcm_open(struct hda_pcm_stream *hinfo,
 			per_cvt->assigned = 0;
 			hinfo->nid = 0;
 			snd_hda_spdif_ctls_unassign(codec, pin_idx);
+			mutex_unlock(&spec->pcm_lock);
 			return -ENODEV;
 		}
 	}
 
+	mutex_unlock(&spec->pcm_lock);
 	/* Store the updated parameters */
 	runtime->hw.channels_min = hinfo->channels_min;
 	runtime->hw.channels_max = hinfo->channels_max;
@@ -1803,13 +1913,33 @@  static int generic_hdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
 {
 	hda_nid_t cvt_nid = hinfo->nid;
 	struct hdmi_spec *spec = codec->spec;
-	int pin_idx = hinfo_to_pin_index(codec, hinfo);
-	struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx);
-	hda_nid_t pin_nid = per_pin->pin_nid;
+	int pin_idx;
+	struct hdmi_spec_per_pin *per_pin;
+	hda_nid_t pin_nid;
 	struct snd_pcm_runtime *runtime = substream->runtime;
 	struct i915_audio_component *acomp = codec->bus->core.audio_component;
 	bool non_pcm;
 	int pinctl;
+	int err;
+
+	mutex_lock(&spec->pcm_lock);
+	pin_idx = hinfo_to_pin_index(codec, hinfo);
+	if (spec->dyn_pcm_assign && pin_idx < 0) {
+		/* when dyn_pcm_assign and pcm is not bound to a pin
+		 * skip pin setup and return 0 to make audio playback
+		 * be ongoing
+		 */
+		intel_not_share_assigned_cvt_nid(codec, 0, cvt_nid);
+		snd_hda_codec_setup_stream(codec, cvt_nid,
+					stream_tag, 0, format);
+		mutex_unlock(&spec->pcm_lock);
+		return 0;
+	}
+
+	if (snd_BUG_ON(pin_idx < 0))
+		return -EINVAL;
+	per_pin = get_pin(spec, pin_idx);
+	pin_nid = per_pin->pin_nid;
 
 	if (is_haswell_plus(codec) || is_valleyview_plus(codec)) {
 		/* Verify pin:cvt selections to avoid silent audio after S3.
@@ -1838,7 +1968,7 @@  static int generic_hdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
 
 	hdmi_setup_audio_infoframe(codec, per_pin, non_pcm);
 	mutex_unlock(&per_pin->lock);
-
+	mutex_unlock(&spec->pcm_lock);
 	if (spec->dyn_pin_out) {
 		pinctl = snd_hda_codec_read(codec, pin_nid, 0,
 					    AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
@@ -1847,7 +1977,10 @@  static int generic_hdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
 				    pinctl | PIN_OUT);
 	}
 
-	return spec->ops.setup_stream(codec, cvt_nid, pin_nid, stream_tag, format);
+	err = spec->ops.setup_stream(codec, cvt_nid, pin_nid,
+				 stream_tag, format);
+	mutex_unlock(&spec->pcm_lock);
+	return err;
 }
 
 static int generic_hdmi_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
@@ -1878,9 +2011,17 @@  static int hdmi_pcm_close(struct hda_pcm_stream *hinfo,
 		per_cvt->assigned = 0;
 		hinfo->nid = 0;
 
+		mutex_lock(&spec->pcm_lock);
 		pin_idx = hinfo_to_pin_index(codec, hinfo);
-		if (snd_BUG_ON(pin_idx < 0))
+		if (spec->dyn_pcm_assign && pin_idx < 0) {
+			mutex_unlock(&spec->pcm_lock);
+			return 0;
+		}
+
+		if (snd_BUG_ON(pin_idx < 0)) {
+			mutex_unlock(&spec->pcm_lock);
 			return -EINVAL;
+		}
 		per_pin = get_pin(spec, pin_idx);
 
 		if (spec->dyn_pin_out) {
@@ -1900,6 +2041,7 @@  static int hdmi_pcm_close(struct hda_pcm_stream *hinfo,
 		per_pin->setup = false;
 		per_pin->channels = 0;
 		mutex_unlock(&per_pin->lock);
+		mutex_unlock(&spec->pcm_lock);
 	}
 
 	return 0;
@@ -2370,6 +2512,7 @@  static int patch_generic_hdmi(struct hda_codec *codec)
 		return -ENOMEM;
 
 	spec->ops = generic_standard_hdmi_ops;
+	mutex_init(&spec->pcm_lock);
 	codec->spec = spec;
 	hdmi_array_init(spec, 4);