[PATCH v3] ALSA: hda/hdmi: Add Intel silent stream support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



External HDMI receivers have analog circuitry that needs to be powered-on
when exiting standby, and a mechanism to detect PCM v. IEC61937 data.
These two steps take time and up to 2-3 seconds of audio may be muted
when starting playback.

Intel hardware (Haswell and beyond) can keep the link active
with a 'silent stream', so that the receiver does not go through those
two steps when valid audio is transmitted. This mechanism relies
on an setting the channel_id as 0xf, sending info packet and preventing
the codec from going to D3,  which will increase the platform
static power consumption. The info packet assumes a basic 2ch stereo,
and the silent stream is enabled when connecting a monitor.
In case of format changes the detection of PCM v. IEC61937 needs to
be re-run. In this case there is no way to avoid the 2-3s mute.

The silent stream is enabled with a Kconfig option, as well as a kernel
parameter should there be a need to override the build time default.
This approach is used based on the power_save capability as an example,
but in the future, it may be used with a kcontrol,
depending on UCM support for HDaudio legacy.

Signed-off-by: Harsha Priya <harshapriya.n@xxxxxxxxx>
Signed-off-by: Emmanuel Jillela <emmanuel.jillela@xxxxxxxxx>
Reviewed-by: Kai Vehmanen <kai.vehmanen@xxxxxxxxxxxxxxx>
Reported-by: kernel test robot <lkp@xxxxxxxxx>
---
Change History:

v3:
- Removed eld_valid check along side of silent stream enable check
  as its redundant with the monitor_present variable
- Limited the change to take affect only for Haswell and beyond Intel platforms
- As per the spec, setting the channel_id as 0xf before sending info packet
- Fixed issue reported by Kernel Test Robot (tag added)
- Tested on Intel Cometlake based Chromebook connected to few different monitors
  and docks.  

v2:
- Kconfig symbol depends on SND_HDA_INTEL.
- Added valid eld check to along side of silent stream enable check,
  under which silent stream is enabled.
- Error prints added for codec power up, power down functions.
- The silent stream is enabled with a Kconfig/module parameter
  following the power_save capability as an example. But in the future,
  it may be used with a kcontrol, depending on UCM support for HDaudio legacy.

v1:
- Initial version.

 sound/pci/hda/Kconfig      | 14 ++++++++++
 sound/pci/hda/patch_hdmi.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 78 insertions(+)

diff --git a/sound/pci/hda/Kconfig b/sound/pci/hda/Kconfig
index 3f9abda..bf213e5 100644
--- a/sound/pci/hda/Kconfig
+++ b/sound/pci/hda/Kconfig
@@ -239,6 +239,20 @@ config SND_HDA_POWER_SAVE_DEFAULT
 	  The default time-out value in seconds for HD-audio automatic
 	  power-save mode.  0 means to disable the power-save mode.
 
+config SND_HDA_INTEL_HDMI_SILENT_STREAM
+	bool "Enable Silent Stream always for HDMI"
+	depends on SND_HDA_INTEL
+	help
+	  Intel hardware has a feature called 'silent stream', that
+	  keeps external HDMI receiver's analog circuitry powered on
+	  avoiding 2-3 sec silence during playback start. This mechanism
+	  relies on setting channel_id as 0xf, sending info packet and
+	  preventing codec D3 entry (increasing  platform static power
+	  consumption when HDMI receiver is plugged-in). 2-3 sec silence
+	  at the playback start is expected whenever there is format change.
+	  (default is 2 channel format).
+	  Say Y to enable Silent Stream feature.
+
 endif
 
 endmenu
diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c
index e2b21ef..18eabcf 100644
--- a/sound/pci/hda/patch_hdmi.c
+++ b/sound/pci/hda/patch_hdmi.c
@@ -42,6 +42,11 @@ static bool enable_acomp = true;
 module_param(enable_acomp, bool, 0444);
 MODULE_PARM_DESC(enable_acomp, "Enable audio component binding (default=yes)");
 
+static bool enable_silent_stream =
+IS_ENABLED(CONFIG_SND_HDA_INTEL_HDMI_SILENT_STREAM);
+module_param(enable_silent_stream, bool, 0644);
+MODULE_PARM_DESC(enable_silent_stream, "Enable Silent Stream for HDMI devices");
+
 struct hdmi_spec_per_cvt {
 	hda_nid_t cvt_nid;
 	int assigned;
@@ -167,6 +172,7 @@ struct hdmi_spec {
 	hda_nid_t vendor_nid;
 	const int *port_map;
 	int port_num;
+	bool send_silent_stream; /* Flag to enable silent stream feature */
 };
 
 #ifdef CONFIG_SND_HDA_COMPONENT
@@ -1634,21 +1640,72 @@ static void hdmi_present_sense_via_verbs(struct hdmi_spec_per_pin *per_pin,
 	snd_hda_power_down_pm(codec);
 }
 
+static void silent_stream_enable(struct hda_codec *codec,
+				struct hdmi_spec_per_pin *per_pin)
+{
+	unsigned int newval, oldval;
+
+	codec_dbg(codec, "hdmi: enabling silent stream for NID %d\n",
+			per_pin->pin_nid);
+
+	mutex_lock(&per_pin->lock);
+
+	if (!per_pin->channels)
+		per_pin->channels = 2;
+
+	oldval = snd_hda_codec_read(codec, per_pin->pin_nid, 0,
+			AC_VERB_GET_CONV, 0);
+	newval = (oldval & 0xF0) | 0xF;
+	snd_hda_codec_write(codec, per_pin->pin_nid, 0,
+			AC_VERB_SET_CHANNEL_STREAMID, newval);
+
+	hdmi_setup_audio_infoframe(codec, per_pin, per_pin->non_pcm);
+
+	mutex_unlock(&per_pin->lock);
+}
+
 /* update ELD and jack state via audio component */
 static void sync_eld_via_acomp(struct hda_codec *codec,
 			       struct hdmi_spec_per_pin *per_pin)
 {
 	struct hdmi_spec *spec = codec->spec;
 	struct hdmi_eld *eld = &spec->temp_eld;
+	bool monitor_prev, monitor_next;
 
 	mutex_lock(&per_pin->lock);
 	eld->monitor_present = false;
+	monitor_prev = per_pin->sink_eld.monitor_present;
 	eld->eld_size = snd_hdac_acomp_get_eld(&codec->core, per_pin->pin_nid,
 				      per_pin->dev_id, &eld->monitor_present,
 				      eld->eld_buffer, ELD_MAX_SIZE);
 	eld->eld_valid = (eld->eld_size > 0);
 	update_eld(codec, per_pin, eld, 0);
+	monitor_next = per_pin->sink_eld.monitor_present;
 	mutex_unlock(&per_pin->lock);
+
+	/*
+	 * Power-up will call hdmi_present_sense, so the PM calls
+	 * have to be done without mutex held.
+	 */
+
+	if (spec->send_silent_stream) {
+		int pm_ret;
+
+		if (!monitor_prev && monitor_next) {
+			pm_ret = snd_hda_power_up_pm(codec);
+			if (pm_ret < 0)
+				codec_err(codec,
+				"Monitor plugged-in, Failed to power up codec ret=[%d]\n",
+				pm_ret);
+			silent_stream_enable(codec, per_pin);
+		} else if (monitor_prev && !monitor_next) {
+			pm_ret = snd_hda_power_down_pm(codec);
+			if (pm_ret < 0)
+				codec_err(codec,
+				"Monitor plugged-out, Failed to power down codec ret=[%d]\n",
+				pm_ret);
+		}
+	}
 }
 
 static void hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll)
@@ -2791,6 +2848,13 @@ static int intel_hsw_common_init(struct hda_codec *codec, hda_nid_t vendor_nid,
 	spec->ops.setup_stream = i915_hsw_setup_stream;
 	spec->ops.pin_cvt_fixup = i915_pin_cvt_fixup;
 
+	/*
+	 * Enable silent stream feature, if it is enabled via
+	 * module param or Kconfig option
+	 */
+	if (enable_silent_stream)
+		spec->send_silent_stream = true;
+
 	return parse_intel_hdmi(codec);
 }
 
-- 
2.7.4




[Index of Archives]     [ALSA User]     [Linux Audio Users]     [Pulse Audio]     [Kernel Archive]     [Asterisk PBX]     [Photo Sharing]     [Linux Sound]     [Video 4 Linux]     [Gimp]     [Yosemite News]

  Powered by Linux