diff mbox series

drm/i915/display: compute config for audio

Message ID 20220823222116.3696068-1-chaitanya.kumar.borah@intel.com (mailing list archive)
State New, archived
Headers show
Series drm/i915/display: compute config for audio | expand

Commit Message

Chaitanya Kumar Borah Aug. 23, 2022, 10:21 p.m. UTC
In certain scenarios, we might have to filter out some audio
configuration depending on HW limitation. For example, in
GLK DP port more than 2 channels are not supported for audio.
A monitor provides information of it's supported audio configurations
through SAD (Short Audio Descriptors) which are part of the ELD/EDID.
In this patch we add a bit mask to indicate which SADs are supported.
The bit mask is updated in the function intel_eld_compute_config().
Based on this bit mask, we prune SADs which are not supported in
the function i915_audio_component_get_eld() before sending out the
data to the audio driver.

We use a bit mask instead of operating on the eld data directly as
eld data can get updated after intel_eld_compute_config() and before
i915_audio_component_get_eld() is called

fixes: https://gitlab.freedesktop.org/drm/intel/-/issues/5368

Signed-off-by: Borah, Chaitanya Kumar <chaitanya.kumar.borah@intel.com>
---
 drivers/gpu/drm/drm_edid.c                    | 36 ++++++++++
 drivers/gpu/drm/i915/display/intel_audio.c    | 65 ++++++++++++++++++-
 drivers/gpu/drm/i915/display/intel_audio.h    |  3 +
 drivers/gpu/drm/i915/display/intel_ddi.c      |  2 +
 .../drm/i915/display/intel_display_types.h    |  9 +++
 include/drm/drm_edid.h                        |  9 +++
 6 files changed, 123 insertions(+), 1 deletion(-)

Comments

Jani Nikula Aug. 24, 2022, 1:56 p.m. UTC | #1
On Wed, 24 Aug 2022, "Borah, Chaitanya Kumar" <chaitanya.kumar.borah@intel.com> wrote:
> In certain scenarios, we might have to filter out some audio
> configuration depending on HW limitation. For example, in
> GLK DP port more than 2 channels are not supported for audio.
> A monitor provides information of it's supported audio configurations
> through SAD (Short Audio Descriptors) which are part of the ELD/EDID.
> In this patch we add a bit mask to indicate which SADs are supported.
> The bit mask is updated in the function intel_eld_compute_config().
> Based on this bit mask, we prune SADs which are not supported in
> the function i915_audio_component_get_eld() before sending out the
> data to the audio driver.
>
> We use a bit mask instead of operating on the eld data directly as
> eld data can get updated after intel_eld_compute_config() and before
> i915_audio_component_get_eld() is called
>
> fixes: https://gitlab.freedesktop.org/drm/intel/-/issues/5368
>
> Signed-off-by: Borah, Chaitanya Kumar <chaitanya.kumar.borah@intel.com>
> ---
>  drivers/gpu/drm/drm_edid.c                    | 36 ++++++++++
>  drivers/gpu/drm/i915/display/intel_audio.c    | 65 ++++++++++++++++++-
>  drivers/gpu/drm/i915/display/intel_audio.h    |  3 +
>  drivers/gpu/drm/i915/display/intel_ddi.c      |  2 +
>  .../drm/i915/display/intel_display_types.h    |  9 +++
>  include/drm/drm_edid.h                        |  9 +++

First of all, drm_edid.[ch] and i915 changes need to be separated.

>  6 files changed, 123 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c
> index 90a5e26eafa8..3495af8d8596 100644
> --- a/drivers/gpu/drm/drm_edid.c
> +++ b/drivers/gpu/drm/drm_edid.c
> @@ -6988,3 +6988,39 @@ static void _drm_update_tile_info(struct drm_connector *connector,
>  		connector->tile_group = NULL;
>  	}
>  }
> +
> +/*
> + * drm_sad_to_format - extract format of an SAD
> + * @sad: pointer to an sad
> + *
> + * extract the format of an SAD from byte 0
> + */
> +int drm_sad_to_format(const u8 *sad)
> +{
> +	return (sad[0] & DRM_ELD_SAD_FORMAT_MASK) >> DRM_ELD_SAD_FORMAT_SHIFT;
> +}
> +EXPORT_SYMBOL(drm_sad_to_format);
> +
> +/*
> + * drm_sad_to_format - extract channels of an SAD
> + * @sad: pointer to an sad
> + *
> + * extract number of supported channels from byte 0 of SAD
> + */
> +int drm_sad_to_channels(const u8 *sad)
> +{
> +	return (sad[0] & DRM_ELD_SAD_CHANNELS_MASK) + 1;
> +}
> +EXPORT_SYMBOL(drm_sad_to_channels);
> +
> +/*
> + * drm_sad_to_format - extract supported rates of an SAD
> + * @sad: pointer to an sad
> + *
> + * extract supported rates from byte 1 of SAD
> + */
> +int drm_sad_to_rates(const u8 *sad)
> +{
> +	return sad[1] & DRM_ELD_SAD_RATES_MASK;
> +}
> +EXPORT_SYMBOL(drm_sad_to_rates);

Not enthusiastic about adding a bunch of new functions to inspect the
sad here. Makes me think this should be parsed and stored in
drm_display_info in drm_edid_to_eld() instead if it's generally useful
data.

> diff --git a/drivers/gpu/drm/i915/display/intel_audio.c b/drivers/gpu/drm/i915/display/intel_audio.c
> index 6c9ee905f132..ac425b652331 100644
> --- a/drivers/gpu/drm/i915/display/intel_audio.c
> +++ b/drivers/gpu/drm/i915/display/intel_audio.c
> @@ -1235,7 +1235,33 @@ static int i915_audio_component_get_eld(struct device *kdev, int port,
>  	if (*enabled) {
>  		eld = intel_encoder->audio_connector->eld;
>  		ret = drm_eld_size(eld);
> -		memcpy(buf, eld, min(max_bytes, ret));
> +
> +		if (intel_encoder->sad_mask_valid) {
> +			int i;
> +			u8 *temp_buf;
> +			u8 *sad;
> +
> +			temp_buf = kzalloc(ret, GFP_KERNEL);
> +
> +			if (!temp_buf) {
> +				mutex_unlock(&dev_priv->audio.mutex);
> +				return -ENOMEM;

Please no error returns like this; use goto and common exit handling
with the unlock.

> +			}
> +
> +			memcpy(temp_buf, eld, ret);
> +
> +			sad = (u8 *)drm_eld_sad(temp_buf);

This may return NULL. And cringe about casting const away.

> +
> +			for (i = 0; i < drm_eld_sad_count(temp_buf); i++, sad += 3) {
> +				if (!(intel_encoder->supported_sads & (1 << i)))
> +					memset(&sad[0], 0, 3);

Here's the main question I have about the change, really. The ELD
(literally EDID-like-data) and SAD are supposed to describe the *sink*
capabilities. Why are we filtering the data here, telling the audio
controller driver this is what the *sink* supports, when the limitation
comes from the *source*?

I could just be clueless about how audio works, but semantically this
feels the same as modifying the EDID based on what the *source*
supports.

> +			}
> +
> +			memcpy(buf, temp_buf, min(max_bytes, ret));
> +			kfree(temp_buf);
> +		} else {
> +			memcpy(buf, eld, min(max_bytes, ret));
> +		}
>  	}
>  
>  	mutex_unlock(&dev_priv->audio.mutex);
> @@ -1408,3 +1434,40 @@ void intel_audio_deinit(struct drm_i915_private *dev_priv)
>  	else
>  		i915_audio_component_cleanup(dev_priv);
>  }
> +
> +void intel_eld_compute_config(struct intel_encoder *encoder,
> +			      const struct intel_crtc_state *pipe_config,
> +			      const struct drm_connector_state *conn_state)

It should probably be named intel_audio_compute_config().

> +{
> +		struct drm_connector *connector = conn_state->connector;
> +		u8 *eld = connector->eld;
> +		const u8 *sad = drm_eld_sad(eld);
> +		struct drm_i915_private *dev_priv = to_i915(encoder->base.dev);

Should be named i915 for all new code.

> +		u32 *supported_sads = &encoder->supported_sads;
> +		int i, channels;
> +
> +		mutex_lock(&dev_priv->audio.mutex);
> +
> +		/* reset information about supported sads to default */
> +		*supported_sads = 0;
> +		encoder->sad_mask_valid = false;
> +
> +		/* Currently filtering SADs is necessary only for GLK due to it's
> +		 * hardware limitations. However, this function can be scaled
> +		 * to any scenario where display driver has to filter out certain
> +		 * SADs before exposing them to the audio driver.
> +		 */
> +		if (IS_GEMINILAKE(dev_priv) &&
> +		    intel_crtc_has_type(pipe_config, INTEL_OUTPUT_DP)) {
> +			encoder->sad_mask_valid = true;
> +
> +			for (i = 0; i < drm_eld_sad_count(eld); i++, sad += 3) {
> +				channels = drm_sad_to_channels(sad);
> +
> +				if (channels <= 2)
> +					(*supported_sads) |= 1 << i;
> +			}
> +		}

It's wrong to modify the encoder members in compute config. Just imagine
compute config failing after this; you've already changed the encoder
and you can't get it back.

> +		drm_dbg_kms(&dev_priv->drm, "supported_sads 0x%x\n", *supported_sads);
> +		mutex_unlock(&dev_priv->audio.mutex);

The indentation is off in the function.


BR,
Jani.

> +}
> diff --git a/drivers/gpu/drm/i915/display/intel_audio.h b/drivers/gpu/drm/i915/display/intel_audio.h
> index 63b22131dc45..17c468a9e07e 100644
> --- a/drivers/gpu/drm/i915/display/intel_audio.h
> +++ b/drivers/gpu/drm/i915/display/intel_audio.h
> @@ -22,5 +22,8 @@ void intel_audio_cdclk_change_pre(struct drm_i915_private *dev_priv);
>  void intel_audio_cdclk_change_post(struct drm_i915_private *dev_priv);
>  void intel_audio_init(struct drm_i915_private *dev_priv);
>  void intel_audio_deinit(struct drm_i915_private *dev_priv);
> +void intel_eld_compute_config(struct intel_encoder *encoder,
> +			      const struct intel_crtc_state *pipe_config,
> +			      const struct drm_connector_state *conn_state);
>  
>  #endif /* __INTEL_AUDIO_H__ */
> diff --git a/drivers/gpu/drm/i915/display/intel_ddi.c b/drivers/gpu/drm/i915/display/intel_ddi.c
> index 6c43a5124cb8..e7807500c88d 100644
> --- a/drivers/gpu/drm/i915/display/intel_ddi.c
> +++ b/drivers/gpu/drm/i915/display/intel_ddi.c
> @@ -3678,6 +3678,8 @@ static int intel_ddi_compute_config(struct intel_encoder *encoder,
>  
>  	intel_ddi_compute_min_voltage_level(dev_priv, pipe_config);
>  
> +	intel_eld_compute_config(encoder, pipe_config, conn_state);
> +
>  	return 0;
>  }
>  
> diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h
> index 0da9b208d56e..5b6a694ff0cc 100644
> --- a/drivers/gpu/drm/i915/display/intel_display_types.h
> +++ b/drivers/gpu/drm/i915/display/intel_display_types.h
> @@ -265,6 +265,15 @@ struct intel_encoder {
>  	/* for communication with audio component; protected by av_mutex */
>  	const struct drm_connector *audio_connector;
>  
> +	/* bitmask to track supported SADs for current audio connector.
> +	 * According to HDA spec Rev 1.0a, ELD SAD limit is 15. Using
> +	 * a 32 bit mask for future proofing; protected by av_mutex
> +	 */
> +	u32 supported_sads;
> +
> +	/* indicates if the supported_sads is a valid bitmask; protected by av_mutex */
> +	bool sad_mask_valid;
> +
>  	/* VBT information for this encoder (may be NULL for older platforms) */
>  	const struct intel_bios_encoder_data *devdata;
>  };
> diff --git a/include/drm/drm_edid.h b/include/drm/drm_edid.h
> index 2181977ae683..363f4487cdd6 100644
> --- a/include/drm/drm_edid.h
> +++ b/include/drm/drm_edid.h
> @@ -318,6 +318,11 @@ struct detailed_timing {
>  
>  #define DRM_ELD_CEA_SAD(mnl, sad)	(20 + (mnl) + 3 * (sad))
>  
> +#define DRM_ELD_SAD_FORMAT_MASK 0x78
> +#define DRM_ELD_SAD_FORMAT_SHIFT 3
> +#define DRM_ELD_SAD_CHANNELS_MASK 0x7
> +#define DRM_ELD_SAD_RATES_MASK 0x7f
> +
>  struct edid {
>  	u8 header[8];
>  	/* Vendor & product info */
> @@ -378,6 +383,10 @@ int drm_edid_to_speaker_allocation(const struct edid *edid, u8 **sadb);
>  int drm_av_sync_delay(struct drm_connector *connector,
>  		      const struct drm_display_mode *mode);
>  
> +int drm_sad_to_format(const u8 *sad);
> +int drm_sad_to_channels(const u8 *sad);
> +int drm_sad_to_rates(const u8 *sad);
> +
>  #ifdef CONFIG_DRM_LOAD_EDID_FIRMWARE
>  struct edid *drm_load_edid_firmware(struct drm_connector *connector);
>  int __drm_set_edid_firmware_path(const char *path);
Kai Vehmanen Aug. 24, 2022, 3:57 p.m. UTC | #2
Hi,

On Wed, 24 Aug 2022, Jani Nikula wrote:

> On Wed, 24 Aug 2022, "Borah, Chaitanya Kumar" <chaitanya.kumar.borah@intel.com> wrote:
> > In certain scenarios, we might have to filter out some audio
> > configuration depending on HW limitation. For example, in
> > GLK DP port more than 2 channels are not supported for audio.
[...]
> > +			for (i = 0; i < drm_eld_sad_count(temp_buf); i++, sad += 3) {
> > +				if (!(intel_encoder->supported_sads & (1 << i)))
> > +					memset(&sad[0], 0, 3);
> 
> Here's the main question I have about the change, really. The ELD
> (literally EDID-like-data) and SAD are supposed to describe the *sink*
> capabilities. Why are we filtering the data here, telling the audio
> controller driver this is what the *sink* supports, when the limitation
> comes from the *source*?
> 
> I could just be clueless about how audio works, but semantically this
> feels the same as modifying the EDID based on what the *source*
> supports.

I provided early input to this patchset and I think this is a pragmatic 
approach to take. What the audio controller really wants is intersection 
of capabilities supported both by source and the sink. E.g. no need to 
advertise to the audio user-space about an audio format xyz, if the full 
pipeline, including source and sink, cannot support it.

And in practise, as the constraints depend on active display 
configuration, this is the only interface where we can pass such 
information to ALSA.

Br, Kai
Ville Syrjälä Oct. 3, 2022, 9:43 a.m. UTC | #3
On Wed, Aug 24, 2022 at 06:57:04PM +0300, Kai Vehmanen wrote:
> Hi,
> 
> On Wed, 24 Aug 2022, Jani Nikula wrote:
> 
> > On Wed, 24 Aug 2022, "Borah, Chaitanya Kumar" <chaitanya.kumar.borah@intel.com> wrote:
> > > In certain scenarios, we might have to filter out some audio
> > > configuration depending on HW limitation. For example, in
> > > GLK DP port more than 2 channels are not supported for audio.
> [...]
> > > +			for (i = 0; i < drm_eld_sad_count(temp_buf); i++, sad += 3) {
> > > +				if (!(intel_encoder->supported_sads & (1 << i)))
> > > +					memset(&sad[0], 0, 3);
> > 
> > Here's the main question I have about the change, really. The ELD
> > (literally EDID-like-data) and SAD are supposed to describe the *sink*
> > capabilities. Why are we filtering the data here, telling the audio
> > controller driver this is what the *sink* supports, when the limitation
> > comes from the *source*?
> > 
> > I could just be clueless about how audio works, but semantically this
> > feels the same as modifying the EDID based on what the *source*
> > supports.
> 
> I provided early input to this patchset and I think this is a pragmatic 
> approach to take. What the audio controller really wants is intersection 
> of capabilities supported both by source and the sink. E.g. no need to 
> advertise to the audio user-space about an audio format xyz, if the full 
> pipeline, including source and sink, cannot support it.

Yeah, I think filtering the SADs would probably be the right thing
to do, eg. depending on the available bandwidth given the current
display timings/etc.

However what this patch implements is some fairy tale constraint
of (GLK && DP && channels>2). I'm fairly sure no such limitation
exists in the hardware.
diff mbox series

Patch

diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c
index 90a5e26eafa8..3495af8d8596 100644
--- a/drivers/gpu/drm/drm_edid.c
+++ b/drivers/gpu/drm/drm_edid.c
@@ -6988,3 +6988,39 @@  static void _drm_update_tile_info(struct drm_connector *connector,
 		connector->tile_group = NULL;
 	}
 }
+
+/*
+ * drm_sad_to_format - extract format of an SAD
+ * @sad: pointer to an sad
+ *
+ * extract the format of an SAD from byte 0
+ */
+int drm_sad_to_format(const u8 *sad)
+{
+	return (sad[0] & DRM_ELD_SAD_FORMAT_MASK) >> DRM_ELD_SAD_FORMAT_SHIFT;
+}
+EXPORT_SYMBOL(drm_sad_to_format);
+
+/*
+ * drm_sad_to_format - extract channels of an SAD
+ * @sad: pointer to an sad
+ *
+ * extract number of supported channels from byte 0 of SAD
+ */
+int drm_sad_to_channels(const u8 *sad)
+{
+	return (sad[0] & DRM_ELD_SAD_CHANNELS_MASK) + 1;
+}
+EXPORT_SYMBOL(drm_sad_to_channels);
+
+/*
+ * drm_sad_to_format - extract supported rates of an SAD
+ * @sad: pointer to an sad
+ *
+ * extract supported rates from byte 1 of SAD
+ */
+int drm_sad_to_rates(const u8 *sad)
+{
+	return sad[1] & DRM_ELD_SAD_RATES_MASK;
+}
+EXPORT_SYMBOL(drm_sad_to_rates);
diff --git a/drivers/gpu/drm/i915/display/intel_audio.c b/drivers/gpu/drm/i915/display/intel_audio.c
index 6c9ee905f132..ac425b652331 100644
--- a/drivers/gpu/drm/i915/display/intel_audio.c
+++ b/drivers/gpu/drm/i915/display/intel_audio.c
@@ -1235,7 +1235,33 @@  static int i915_audio_component_get_eld(struct device *kdev, int port,
 	if (*enabled) {
 		eld = intel_encoder->audio_connector->eld;
 		ret = drm_eld_size(eld);
-		memcpy(buf, eld, min(max_bytes, ret));
+
+		if (intel_encoder->sad_mask_valid) {
+			int i;
+			u8 *temp_buf;
+			u8 *sad;
+
+			temp_buf = kzalloc(ret, GFP_KERNEL);
+
+			if (!temp_buf) {
+				mutex_unlock(&dev_priv->audio.mutex);
+				return -ENOMEM;
+			}
+
+			memcpy(temp_buf, eld, ret);
+
+			sad = (u8 *)drm_eld_sad(temp_buf);
+
+			for (i = 0; i < drm_eld_sad_count(temp_buf); i++, sad += 3) {
+				if (!(intel_encoder->supported_sads & (1 << i)))
+					memset(&sad[0], 0, 3);
+			}
+
+			memcpy(buf, temp_buf, min(max_bytes, ret));
+			kfree(temp_buf);
+		} else {
+			memcpy(buf, eld, min(max_bytes, ret));
+		}
 	}
 
 	mutex_unlock(&dev_priv->audio.mutex);
@@ -1408,3 +1434,40 @@  void intel_audio_deinit(struct drm_i915_private *dev_priv)
 	else
 		i915_audio_component_cleanup(dev_priv);
 }
+
+void intel_eld_compute_config(struct intel_encoder *encoder,
+			      const struct intel_crtc_state *pipe_config,
+			      const struct drm_connector_state *conn_state)
+{
+		struct drm_connector *connector = conn_state->connector;
+		u8 *eld = connector->eld;
+		const u8 *sad = drm_eld_sad(eld);
+		struct drm_i915_private *dev_priv = to_i915(encoder->base.dev);
+		u32 *supported_sads = &encoder->supported_sads;
+		int i, channels;
+
+		mutex_lock(&dev_priv->audio.mutex);
+
+		/* reset information about supported sads to default */
+		*supported_sads = 0;
+		encoder->sad_mask_valid = false;
+
+		/* Currently filtering SADs is necessary only for GLK due to it's
+		 * hardware limitations. However, this function can be scaled
+		 * to any scenario where display driver has to filter out certain
+		 * SADs before exposing them to the audio driver.
+		 */
+		if (IS_GEMINILAKE(dev_priv) &&
+		    intel_crtc_has_type(pipe_config, INTEL_OUTPUT_DP)) {
+			encoder->sad_mask_valid = true;
+
+			for (i = 0; i < drm_eld_sad_count(eld); i++, sad += 3) {
+				channels = drm_sad_to_channels(sad);
+
+				if (channels <= 2)
+					(*supported_sads) |= 1 << i;
+			}
+		}
+		drm_dbg_kms(&dev_priv->drm, "supported_sads 0x%x\n", *supported_sads);
+		mutex_unlock(&dev_priv->audio.mutex);
+}
diff --git a/drivers/gpu/drm/i915/display/intel_audio.h b/drivers/gpu/drm/i915/display/intel_audio.h
index 63b22131dc45..17c468a9e07e 100644
--- a/drivers/gpu/drm/i915/display/intel_audio.h
+++ b/drivers/gpu/drm/i915/display/intel_audio.h
@@ -22,5 +22,8 @@  void intel_audio_cdclk_change_pre(struct drm_i915_private *dev_priv);
 void intel_audio_cdclk_change_post(struct drm_i915_private *dev_priv);
 void intel_audio_init(struct drm_i915_private *dev_priv);
 void intel_audio_deinit(struct drm_i915_private *dev_priv);
+void intel_eld_compute_config(struct intel_encoder *encoder,
+			      const struct intel_crtc_state *pipe_config,
+			      const struct drm_connector_state *conn_state);
 
 #endif /* __INTEL_AUDIO_H__ */
diff --git a/drivers/gpu/drm/i915/display/intel_ddi.c b/drivers/gpu/drm/i915/display/intel_ddi.c
index 6c43a5124cb8..e7807500c88d 100644
--- a/drivers/gpu/drm/i915/display/intel_ddi.c
+++ b/drivers/gpu/drm/i915/display/intel_ddi.c
@@ -3678,6 +3678,8 @@  static int intel_ddi_compute_config(struct intel_encoder *encoder,
 
 	intel_ddi_compute_min_voltage_level(dev_priv, pipe_config);
 
+	intel_eld_compute_config(encoder, pipe_config, conn_state);
+
 	return 0;
 }
 
diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h
index 0da9b208d56e..5b6a694ff0cc 100644
--- a/drivers/gpu/drm/i915/display/intel_display_types.h
+++ b/drivers/gpu/drm/i915/display/intel_display_types.h
@@ -265,6 +265,15 @@  struct intel_encoder {
 	/* for communication with audio component; protected by av_mutex */
 	const struct drm_connector *audio_connector;
 
+	/* bitmask to track supported SADs for current audio connector.
+	 * According to HDA spec Rev 1.0a, ELD SAD limit is 15. Using
+	 * a 32 bit mask for future proofing; protected by av_mutex
+	 */
+	u32 supported_sads;
+
+	/* indicates if the supported_sads is a valid bitmask; protected by av_mutex */
+	bool sad_mask_valid;
+
 	/* VBT information for this encoder (may be NULL for older platforms) */
 	const struct intel_bios_encoder_data *devdata;
 };
diff --git a/include/drm/drm_edid.h b/include/drm/drm_edid.h
index 2181977ae683..363f4487cdd6 100644
--- a/include/drm/drm_edid.h
+++ b/include/drm/drm_edid.h
@@ -318,6 +318,11 @@  struct detailed_timing {
 
 #define DRM_ELD_CEA_SAD(mnl, sad)	(20 + (mnl) + 3 * (sad))
 
+#define DRM_ELD_SAD_FORMAT_MASK 0x78
+#define DRM_ELD_SAD_FORMAT_SHIFT 3
+#define DRM_ELD_SAD_CHANNELS_MASK 0x7
+#define DRM_ELD_SAD_RATES_MASK 0x7f
+
 struct edid {
 	u8 header[8];
 	/* Vendor & product info */
@@ -378,6 +383,10 @@  int drm_edid_to_speaker_allocation(const struct edid *edid, u8 **sadb);
 int drm_av_sync_delay(struct drm_connector *connector,
 		      const struct drm_display_mode *mode);
 
+int drm_sad_to_format(const u8 *sad);
+int drm_sad_to_channels(const u8 *sad);
+int drm_sad_to_rates(const u8 *sad);
+
 #ifdef CONFIG_DRM_LOAD_EDID_FIRMWARE
 struct edid *drm_load_edid_firmware(struct drm_connector *connector);
 int __drm_set_edid_firmware_path(const char *path);