[Intel-gfx] [PATCH v6 2/4] drm/i915: implement sync_audio_rate callback

Jani Nikula jani.nikula at linux.intel.com
Wed Sep 2 00:52:24 PDT 2015


On Wed, 02 Sep 2015, libin.yang at intel.com wrote:
> From: Libin Yang <libin.yang at intel.com>
>
> HDMI audio may not work at some frequencies
> with the HW provided N/CTS.
>
> This patch sets the proper N value for the
> given audio sample rate at the impacted frequencies.
> At other frequencies, it will use the N/CTS value
> which HW provides.
>
> Signed-off-by: Libin Yang <libin.yang at intel.com>

Okay, so there are a number of questions and comments this patch still
raises. Please find them inline.

*However*, we should really get this finally moving forward, and I don't
think the issues are or need to be blockers, so this is

Reviewed-by: Jani Nikula <jani.nikula at intel.com>

I think we (as in i915 folks) can clean the rest of the bikeshedding up,
and I don't think we should be NAKing this ad infinitum to teach you to
become an i915 developer... unless you want to be one. ;)

> ---
>  drivers/gpu/drm/i915/i915_dma.c    |   1 +
>  drivers/gpu/drm/i915/i915_drv.h    |   5 ++
>  drivers/gpu/drm/i915/intel_audio.c | 138 +++++++++++++++++++++++++++++++++++++
>  3 files changed, 144 insertions(+)
>
> diff --git a/drivers/gpu/drm/i915/i915_dma.c b/drivers/gpu/drm/i915/i915_dma.c
> index 097d4ba..8ffb5dc 100644
> --- a/drivers/gpu/drm/i915/i915_dma.c
> +++ b/drivers/gpu/drm/i915/i915_dma.c
> @@ -858,6 +858,7 @@ int i915_driver_load(struct drm_device *dev, unsigned long flags)
>  	mutex_init(&dev_priv->sb_lock);
>  	mutex_init(&dev_priv->modeset_restore_lock);
>  	mutex_init(&dev_priv->csr_lock);
> +	mutex_init(&dev_priv->av_mutex);
>  
>  	intel_pm_setup(dev);
>  
> diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h
> index 05ffd5a..2c6b76f 100644
> --- a/drivers/gpu/drm/i915/i915_drv.h
> +++ b/drivers/gpu/drm/i915/i915_drv.h
> @@ -1882,6 +1882,11 @@ struct drm_i915_private {
>  
>  	/* hda/i915 audio component */
>  	bool audio_component_registered;
> +	/**
> +	 * av_mutex - mutex for audio/video sync
> +	 *
> +	 */
> +	struct mutex av_mutex;
>  
>  	uint32_t hw_context_size;
>  	struct list_head context_list;
> diff --git a/drivers/gpu/drm/i915/intel_audio.c b/drivers/gpu/drm/i915/intel_audio.c
> index dc32cf4..a021720 100644
> --- a/drivers/gpu/drm/i915/intel_audio.c
> +++ b/drivers/gpu/drm/i915/intel_audio.c
> @@ -68,6 +68,31 @@ static const struct {
>  	{ 148500, AUD_CONFIG_PIXEL_CLOCK_HDMI_148500 },
>  };
>  
> +/* HDMI N/CTS table */
> +#define TMDS_297M 297000
> +#define TMDS_296M DIV_ROUND_UP(297000 * 1000, 1001)
> +static const struct {
> +	int sample_rate;
> +	int clock;
> +	int n;
> +	int cts;
> +} aud_ncts[] = {
> +	{ 44100, TMDS_296M, 4459, 234375 },
> +	{ 44100, TMDS_297M, 4704, 247500 },
> +	{ 48000, TMDS_296M, 5824, 281250 },
> +	{ 48000, TMDS_297M, 5120, 247500 },
> +	{ 32000, TMDS_296M, 5824, 421875 },
> +	{ 32000, TMDS_297M, 3072, 222750 },
> +	{ 88200, TMDS_296M, 8918, 234375 },
> +	{ 88200, TMDS_297M, 9408, 247500 },
> +	{ 96000, TMDS_296M, 11648, 281250 },
> +	{ 96000, TMDS_297M, 10240, 247500 },
> +	{ 176400, TMDS_296M, 17836, 234375 },
> +	{ 176400, TMDS_297M, 18816, 247500 },
> +	{ 192000, TMDS_296M, 23296, 281250 },
> +	{ 192000, TMDS_297M, 20480, 247500 },
> +};

Okay, I admit, I did not look these up in the spec. *blush*. I'm sure
Ville has/will. ;)

> +
>  /* get AUD_CONFIG_PIXEL_CLOCK_HDMI_* value for mode */
>  static u32 audio_config_hdmi_pixel_clock(struct drm_display_mode *mode)
>  {
> @@ -90,6 +115,31 @@ static u32 audio_config_hdmi_pixel_clock(struct drm_display_mode *mode)
>  	return hdmi_audio_clock[i].config;
>  }
>  
> +static int audio_config_get_n(const struct drm_display_mode *mode, int rate)
> +{
> +	int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(aud_ncts); i++) {
> +		if ((rate == aud_ncts[i].sample_rate) &&
> +			(mode->clock == aud_ncts[i].clock)) {
> +			return aud_ncts[i].n;
> +		}
> +	}
> +	return 0;
> +}
> +
> +/* check whether N/CTS/M need be set manually */
> +static bool audio_rate_need_prog(struct intel_crtc *crtc,
> +					struct drm_display_mode *mode)
> +{
> +	if (((mode->clock == TMDS_297M) ||
> +		 (mode->clock == TMDS_296M)) &&
> +		intel_pipe_has_type(crtc, INTEL_OUTPUT_HDMI))
> +		return true;
> +	else
> +		return false;
> +}
> +
>  static bool intel_eld_uptodate(struct drm_connector *connector,
>  			       int reg_eldv, uint32_t bits_eldv,
>  			       int reg_elda, uint32_t bits_elda,
> @@ -184,6 +234,8 @@ static void hsw_audio_codec_disable(struct intel_encoder *encoder)
>  
>  	DRM_DEBUG_KMS("Disable audio codec on pipe %c\n", pipe_name(pipe));
>  
> +	mutex_lock(&dev_priv->av_mutex);
> +

Bikeshed. We should think about moving the locking to
intel_audio_codec_enable and intel_audio_codec_disable for
consistency. We'll be adding new platform hooks eventually anyway, no
need to duplicate the locking everywhere.

>  	/* Disable timestamps */
>  	tmp = I915_READ(HSW_AUD_CFG(pipe));
>  	tmp &= ~AUD_CONFIG_N_VALUE_INDEX;
> @@ -199,6 +251,8 @@ static void hsw_audio_codec_disable(struct intel_encoder *encoder)
>  	tmp &= ~AUDIO_ELD_VALID(pipe);
>  	tmp &= ~AUDIO_OUTPUT_ENABLE(pipe);
>  	I915_WRITE(HSW_AUD_PIN_ELD_CP_VLD, tmp);
> +
> +	mutex_unlock(&dev_priv->av_mutex);
>  }
>  
>  static void hsw_audio_codec_enable(struct drm_connector *connector,
> @@ -215,6 +269,8 @@ static void hsw_audio_codec_enable(struct drm_connector *connector,
>  	DRM_DEBUG_KMS("Enable audio codec on pipe %c, %u bytes ELD\n",
>  		      pipe_name(pipe), drm_eld_size(eld));
>  
> +	mutex_lock(&dev_priv->av_mutex);
> +
>  	/* Enable audio presence detect, invalidate ELD */
>  	tmp = I915_READ(HSW_AUD_PIN_ELD_CP_VLD);
>  	tmp |= AUDIO_OUTPUT_ENABLE(pipe);
> @@ -253,6 +309,8 @@ static void hsw_audio_codec_enable(struct drm_connector *connector,
>  	else
>  		tmp |= audio_config_hdmi_pixel_clock(mode);
>  	I915_WRITE(HSW_AUD_CFG(pipe), tmp);
> +
> +	mutex_unlock(&dev_priv->av_mutex);
>  }
>  
>  static void ilk_audio_codec_disable(struct intel_encoder *encoder)
> @@ -514,12 +572,92 @@ static int i915_audio_component_get_cdclk_freq(struct device *dev)
>  	return ret;
>  }
>  
> +static int i915_audio_component_sync_audio_rate(struct device *dev,
> +						int port, int rate)
> +{
> +	struct drm_i915_private *dev_priv = dev_to_i915(dev);
> +	struct drm_device *drm_dev = dev_priv->dev;
> +	struct intel_encoder *intel_encoder;
> +	struct intel_digital_port *intel_dig_port;
> +	struct intel_crtc *crtc;
> +	struct drm_display_mode *mode;
> +	enum pipe pipe = -1;

Nitpick. Initialize to INVALID_PIPE.

> +	u32 tmp;
> +	int n_low, n_up, n;
> +
> +	/* HSW, BDW SKL need this fix */
> +	if (!IS_SKYLAKE(dev_priv) &&
> +		!IS_BROADWELL(dev_priv) &&
> +		!IS_HASWELL(dev_priv))
> +		return 0;

Nitpick. We should elaborate that a bit more. Is it basically that only
the above platforms support HDMI mode and audio sample rate combinations
that the automatic mode in hardware can't support? In other words, would
audio_rate_need_prog() always return false on other platforms than
hsw/bdw/skl? And if so, why do we have this check? Perhaps we should
take care to make audio_rate_need_prog() do the right thing, and do that
as the first thing here.

> +
> +	mutex_lock(&dev_priv->av_mutex);
> +	/* 1. get the pipe */
> +	for_each_intel_encoder(drm_dev, intel_encoder) {
> +		if (intel_encoder->type != INTEL_OUTPUT_HDMI)
> +			continue;
> +		intel_dig_port = enc_to_dig_port(&intel_encoder->base);
> +		if (port == intel_dig_port->port) {
> +			crtc = to_intel_crtc(intel_encoder->base.crtc);
> +			if (!crtc) {
> +				DRM_DEBUG_KMS("%s: crtc is NULL\n", __func__);

DRM_DEBUG_KMS includes __func__ already.

> +				continue;
> +			}
> +			pipe = crtc->pipe;
> +			break;
> +		}
> +	}
> +
> +	if (pipe == INVALID_PIPE) {
> +		DRM_DEBUG_KMS("no pipe for the port %c\n", port_name(port));
> +		mutex_unlock(&dev_priv->av_mutex);
> +		return -ENODEV;

Nitpick. I am not fond of early returns when cleanup like mutex unlock
is required. The function should have goto labels at the end with
cleanup. (OTOH if we move locking to higher level this problem goes
away.)

> +	}
> +	DRM_DEBUG_KMS("pipe %c connects port %c\n",
> +				  pipe_name(pipe), port_name(port));
> +	mode = &crtc->config->base.adjusted_mode;
> +
> +	/* 2. check whether to set the N/CTS/M manually or not */
> +	if (!audio_rate_need_prog(crtc, mode)) {
> +		tmp = I915_READ(HSW_AUD_CFG(pipe));
> +		tmp &= ~AUD_CONFIG_N_PROG_ENABLE;
> +		I915_WRITE(HSW_AUD_CFG(pipe), tmp);
> +		mutex_unlock(&dev_priv->av_mutex);
> +		return 0;

Nitpick. Same here, you could have an auto: label at the end with the
auto programming...

> +	}
> +
> +	n = audio_config_get_n(mode, rate);
> +	if (n == 0) {
> +		DRM_DEBUG_KMS("Using automatic mode for N value on port %c\n",
> +					  port_name(port));
> +		tmp = I915_READ(HSW_AUD_CFG(pipe));
> +		tmp &= ~AUD_CONFIG_N_PROG_ENABLE;
> +		I915_WRITE(HSW_AUD_CFG(pipe), tmp);
> +		mutex_unlock(&dev_priv->av_mutex);
> +		return 0;

...especially because this bit is duplicated for no good reason.

Fallback to auto mode if the mode isn't in the list is, I think, a good
thing. Maybe it would deserve a DRM_ERROR, because clearly that
shouldn't happen.

But it's actually the !audio_rate_need_prog case that makes me wonder. I
think we've confirmed with the silicon folks that we can change N on the
fly... but I'm actually not sure if we've confirmed that we can switch
between automatic and manual modes on the fly. I think I've said it
before, but I think I'd prefer it if we always used the manual mode
anyway to reduce the complexity and have only one code path to debug,
and that would also cover the somewhat of a corner case (but still real
case) of switching between automatic/manual modes on the fly. And of
course, this conflicts with what I said about the platform support check
above, so needs thought.

> +	}
> +	n_low = n & 0xfff;
> +	n_up = (n >> 12) & 0xff;
> +
> +	/* 4. set the N/CTS/M */
> +	tmp = I915_READ(HSW_AUD_CFG(pipe));
> +	tmp &= ~(AUD_CONFIG_UPPER_N_MASK | AUD_CONFIG_LOWER_N_MASK);
> +	tmp |= ((n_up << AUD_CONFIG_UPPER_N_SHIFT) |
> +			(n_low << AUD_CONFIG_LOWER_N_SHIFT) |
> +			AUD_CONFIG_N_PROG_ENABLE);
> +	I915_WRITE(HSW_AUD_CFG(pipe), tmp);
> +
> +	mutex_unlock(&dev_priv->av_mutex);
> +	return 0;
> +}
> +
>  static const struct i915_audio_component_ops i915_audio_component_ops = {
>  	.owner		= THIS_MODULE,
>  	.get_power	= i915_audio_component_get_power,
>  	.put_power	= i915_audio_component_put_power,
>  	.codec_wake_override = i915_audio_component_codec_wake_override,
>  	.get_cdclk_freq	= i915_audio_component_get_cdclk_freq,
> +	.sync_audio_rate = i915_audio_component_sync_audio_rate,
>  };
>  
>  static int i915_audio_component_bind(struct device *i915_dev,
> -- 
> 1.9.1
>

-- 
Jani Nikula, Intel Open Source Technology Center


More information about the Intel-gfx mailing list