diff mbox

[v2,1/3] ALSA: hda - divide controller and codec dependency on i915 gfx power well

Message ID 1430224980-22186-1-git-send-email-mengdong.lin@intel.com (mailing list archive)
State New, archived
Headers show

Commit Message

Lin, Mengdong April 28, 2015, 12:43 p.m. UTC
From: Mengdong Lin <mengdong.lin@intel.com>

This patch can improve power saving for Intel platforms on which only the
display audio codec is in the shared i915 power well:

- divide the controller and codec dependency on i915 power well by adding
  flag "need_i915_power" for the chip (controller) and codec respectively.

- If the controller does not need the power well, the driver will release the
  power after probe, otherwise the power will be held until the controller is
  runtime suspended or S3.

-  And if only the codec needs the power, its runtime PM ops will request/
   release the power.

Background:
- For Haswell/Broadwell, which has a separate HD-A controller for display audio,
  both the controller and the display codec are in the i915 power well.

- For Baytrail/Braswell, the display and analog audio share the same HDA
  controller and link, and only the display codec is in the i915 power well.

- For Skylake, the display and analog audio share the same HDA controller but
  use separate links. Only the display codec is in the i915 power well. And in
  legacy mode we take the two links as one. So it can follow Baytrail/Braswell.

Signed-off-by: Mengdong Lin <mengdong.lin@intel.com>

Comments

Takashi Iwai April 28, 2015, 12:59 p.m. UTC | #1
At Tue, 28 Apr 2015 20:43:00 +0800,
mengdong.lin@intel.com wrote:
> 
> From: Mengdong Lin <mengdong.lin@intel.com>

Please don't continue in the previous thread if you send a new patch
series.  It makes harder to follow.

> This patch can improve power saving for Intel platforms on which only the
> display audio codec is in the shared i915 power well:
> 
> - divide the controller and codec dependency on i915 power well by adding
>   flag "need_i915_power" for the chip (controller) and codec respectively.
> 
> - If the controller does not need the power well, the driver will release the
>   power after probe, otherwise the power will be held until the controller is
>   runtime suspended or S3.
> 
> -  And if only the codec needs the power, its runtime PM ops will request/
>    release the power.
> 
> Background:
> - For Haswell/Broadwell, which has a separate HD-A controller for display audio,
>   both the controller and the display codec are in the i915 power well.
> 
> - For Baytrail/Braswell, the display and analog audio share the same HDA
>   controller and link, and only the display codec is in the i915 power well.
> 
> - For Skylake, the display and analog audio share the same HDA controller but
>   use separate links. Only the display codec is in the i915 power well. And in
>   legacy mode we take the two links as one. So it can follow Baytrail/Braswell.
> 
> Signed-off-by: Mengdong Lin <mengdong.lin@intel.com>
> 
> diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h
> index 6a2e030..4fa2d51 100644
> --- a/include/sound/hdaudio.h
> +++ b/include/sound/hdaudio.h
> @@ -74,6 +74,7 @@ struct hdac_device {
>  
>  	/* misc flags */
>  	atomic_t in_pm;		/* suspend/resume being performed */
> +	unsigned int need_i915_power:1; /* only codec needs i915 power */

Use bool.  A bool can have a bit field, too.

Also, try not to be too specific to certain hardware.
Actually, a generic image about this operation is to control the link
power.  Something like link_power_control would fit better.


>  	/* sysfs */
>  	struct hdac_widget_tree *widgets;
> @@ -184,6 +185,8 @@ struct hdac_bus_ops {
>  	/* get a response from the last command */
>  	int (*get_response)(struct hdac_bus *bus, unsigned int addr,
>  			    unsigned int *res);
> +	/* enable/disable the display power */
> +	int (*display_power)(struct hdac_bus *bus, bool enable);

Also, this can be link_power() so that it can be used even for other
than HDMI/DP, if any, too.


>  };
>  
>  /*
> @@ -308,6 +311,15 @@ static inline void snd_hdac_codec_link_down(struct hdac_device *codec)
>  	clear_bit(codec->addr, &codec->bus->codec_powered);
>  }
>  
> +/*
> + * Enable/disable the display power well, for HDMI/DP audio codecs that can be
> + * in the shared power well with GPU display engine.
> + */
> +static inline int snd_hdac_display_power(struct hdac_device *codec, bool enable)
> +{
> +	return codec->bus->ops->display_power(codec->bus, enable);

Put a NULL check of bus->ops->display_power.  It's not always set.
Also, the check of need_i915_power flag can be moved here.
Then move into hdac_device.c, as it becomes big as an inline function.


> +}
> +
>  int snd_hdac_bus_send_cmd(struct hdac_bus *bus, unsigned int val);
>  int snd_hdac_bus_get_response(struct hdac_bus *bus, unsigned int addr,
>  			      unsigned int *res);
> diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c
> index 2d8883f..2de9e84 100644
> --- a/sound/pci/hda/hda_codec.c
> +++ b/sound/pci/hda/hda_codec.c
> @@ -857,6 +857,10 @@ void snd_hda_codec_register(struct hda_codec *codec)
>  		return;
>  	if (device_is_registered(hda_codec_dev(codec))) {
>  		snd_hda_register_beep_device(codec);
> +
> +		if (codec->core.need_i915_power)
> +			snd_hdac_display_power(&codec->core, true);
> +
>  		pm_runtime_enable(hda_codec_dev(codec));
>  		/* it was powered up in snd_hda_codec_new(), now all done */
>  		snd_hda_power_down(codec);
> @@ -3102,6 +3106,9 @@ static int hda_codec_runtime_suspend(struct device *dev)
>  	if (codec_has_clkstop(codec) && codec_has_epss(codec) &&
>  	    (state & AC_PWRST_CLK_STOP_OK))
>  		snd_hdac_codec_link_down(&codec->core);
> +
> +	if (codec->core.need_i915_power)
> +		snd_hdac_display_power(&codec->core, false);
>  	return 0;
>  }
>  
> @@ -3109,6 +3116,9 @@ static int hda_codec_runtime_resume(struct device *dev)
>  {
>  	struct hda_codec *codec = dev_to_hda_codec(dev);
>  
> +	if (codec->core.need_i915_power)
> +		snd_hdac_display_power(&codec->core, true);
> +
>  	snd_hdac_codec_link_up(&codec->core);
>  	hda_call_codec_resume(codec);
>  	pm_runtime_mark_last_busy(dev);
> diff --git a/sound/pci/hda/hda_controller.c b/sound/pci/hda/hda_controller.c
> index e0bb623..e5bdf31 100644
> --- a/sound/pci/hda/hda_controller.c
> +++ b/sound/pci/hda/hda_controller.c
> @@ -775,9 +775,20 @@ static int azx_get_response(struct hdac_bus *bus, unsigned int addr,
>  		return azx_rirb_get_response(bus, addr, res);
>  }
>  
> +static int azx_display_power(struct hdac_bus *bus, bool enable)
> +{
> +	struct azx *chip = bus_to_azx(bus);
> +
> +	if (chip->ops->display_power)
> +		return chip->ops->display_power(chip, enable);
> +	else
> +		return -EINVAL;
> +}
> +
>  static const struct hdac_bus_ops bus_core_ops = {
>  	.command = azx_send_cmd,
>  	.get_response = azx_get_response,
> +	.display_power = azx_display_power,
>  };
>  
>  #ifdef CONFIG_SND_HDA_DSP_LOADER
> diff --git a/sound/pci/hda/hda_controller.h b/sound/pci/hda/hda_controller.h
> index 173bf7c..e2292e4 100644
> --- a/sound/pci/hda/hda_controller.h
> +++ b/sound/pci/hda/hda_controller.h
> @@ -89,6 +89,8 @@ struct hda_controller_ops {
>  				 struct vm_area_struct *area);
>  	/* Check if current position is acceptable */
>  	int (*position_check)(struct azx *chip, struct azx_dev *azx_dev);
> +	/* enable/disable the shared display power */
> +	int (*display_power)(struct azx *chip, bool enable);
>  };
>  
>  struct azx_pcm {
> @@ -152,6 +154,7 @@ struct azx {
>  	unsigned int align_buffer_size:1;
>  	unsigned int region_requested:1;
>  	unsigned int disabled:1; /* disabled by VGA-switcher */
> +	unsigned int need_i915_power:1; /* both controller & display codec needs i915 power */

This doesn't have to be in hda_controller.h.  hda_controller.c doesn't
refer to this at all.  Better to move it into hda_intel.h.


Takashi

>  #ifdef CONFIG_SND_HDA_DSP_LOADER
>  	struct azx_dev saved_azx_dev;
> diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c
> index e615556..1b688ba 100644
> --- a/sound/pci/hda/hda_intel.c
> +++ b/sound/pci/hda/hda_intel.c
> @@ -543,6 +543,14 @@ static int azx_position_check(struct azx *chip, struct azx_dev *azx_dev)
>  	return 0;
>  }
>  
> +/* Enable/disable display power  */
> +static int azx_intel_display_power(struct azx *chip, bool enable)
> +{
> +	struct hda_intel *hda = container_of(chip, struct hda_intel, chip);
> +
> +	return hda_display_power(hda, enable);
> +}
> +
>  /*
>   * Check whether the current DMA position is acceptable for updating
>   * periods.  Returns non-zero if it's OK.
> @@ -809,7 +817,8 @@ static int azx_suspend(struct device *dev)
>  
>  	if (chip->msi)
>  		pci_disable_msi(chip->pci);
> -	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL)
> +	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL
> +		&& chip->need_i915_power)
>  		hda_display_power(hda, false);
>  	return 0;
>  }
> @@ -829,7 +838,8 @@ static int azx_resume(struct device *dev)
>  	if (chip->disabled || hda->init_failed)
>  		return 0;
>  
> -	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) {
> +	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL
> +		&& chip->need_i915_power) {
>  		hda_display_power(hda, true);
>  		haswell_set_bclk(hda);
>  	}
> @@ -872,7 +882,8 @@ static int azx_runtime_suspend(struct device *dev)
>  	azx_stop_chip(chip);
>  	azx_enter_link_reset(chip);
>  	azx_clear_irq_pending(chip);
> -	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL)
> +	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL
> +		&& chip->need_i915_power)
>  		hda_display_power(hda, false);
>  
>  	return 0;
> @@ -897,7 +908,8 @@ static int azx_runtime_resume(struct device *dev)
>  	if (!azx_has_pm_runtime(chip))
>  		return 0;
>  
> -	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) {
> +	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL
> +		&& chip->need_i915_power) {
>  		hda_display_power(hda, true);
>  		haswell_set_bclk(hda);
>  	}
> @@ -1118,7 +1130,8 @@ static int azx_free(struct azx *chip)
>  	release_firmware(chip->fw);
>  #endif
>  	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) {
> -		hda_display_power(hda, false);
> +		if (chip->need_i915_power)
> +			hda_display_power(hda, false);
>  		hda_i915_exit(hda);
>  	}
>  	kfree(hda);
> @@ -1789,6 +1802,7 @@ static const struct hda_controller_ops pci_hda_ops = {
>  	.substream_free_pages = substream_free_pages,
>  	.pcm_mmap_prepare = pcm_mmap_prepare,
>  	.position_check = azx_position_check,
> +	.display_power = azx_intel_display_power,
>  };
>  
>  static int azx_probe(struct pci_dev *pci,
> @@ -1882,17 +1896,26 @@ static int azx_probe_continue(struct azx *chip)
>  	int err;
>  
>  	hda->probe_continued = 1;
> -	/* Request power well for Haswell HDA controller and codec */
> +
> +	/* Request display power well for the HDA controller or codec. For
> +	 * Haswell/Broadwell, both the display HDA controller and codec need
> +	 * this power. For other platforms, like Baytrail/Braswell, only the
> +	 * display codec needs the power and it can be released after probe.
> +	 */
>  	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) {
> +		/* Assume the controller needs the power by default */
> +		chip->need_i915_power = 1;
> +
>  #ifdef CONFIG_SND_HDA_I915
>  		err = hda_i915_init(hda);
>  		if (err < 0)
> -			goto out_free;
> +			goto i915_power_fail;
> +
>  		err = hda_display_power(hda, true);
>  		if (err < 0) {
>  			dev_err(chip->card->dev,
>  				"Cannot turn on display power on i915\n");
> -			goto out_free;
> +			goto i915_power_fail;
>  		}
>  #endif
>  	}
> @@ -1939,6 +1962,11 @@ static int azx_probe_continue(struct azx *chip)
>  		pm_runtime_put_noidle(&pci->dev);
>  
>  out_free:
> +	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL
> +		&& !chip->need_i915_power)
> +		hda_display_power(hda, false);
> +
> +i915_power_fail:
>  	if (err < 0)
>  		hda->init_failed = 1;
>  	complete_all(&hda->probe_wait);
> -- 
> 1.9.1
>
Lin, Mengdong April 28, 2015, 3:03 p.m. UTC | #2
> -----Original Message-----
> From: Takashi Iwai [mailto:tiwai@suse.de]
> Sent: Tuesday, April 28, 2015 9:00 PM

> Please don't continue in the previous thread if you send a new patch series.
> It makes harder to follow.

Okay.

> > This patch can improve power saving for Intel platforms on which only
> > the display audio codec is in the shared i915 power well:
> >
> > - divide the controller and codec dependency on i915 power well by adding
> >   flag "need_i915_power" for the chip (controller) and codec respectively.
> >
> > - If the controller does not need the power well, the driver will release the
> >   power after probe, otherwise the power will be held until the controller is
> >   runtime suspended or S3.
> >
> > -  And if only the codec needs the power, its runtime PM ops will request/
> >    release the power.
> >
> > Background:
> > - For Haswell/Broadwell, which has a separate HD-A controller for display
> audio,
> >   both the controller and the display codec are in the i915 power well.
> >
> > - For Baytrail/Braswell, the display and analog audio share the same HDA
> >   controller and link, and only the display codec is in the i915 power well.
> >
> > - For Skylake, the display and analog audio share the same HDA controller
> but
> >   use separate links. Only the display codec is in the i915 power well. And in
> >   legacy mode we take the two links as one. So it can follow
> Baytrail/Braswell.
> >
> > Signed-off-by: Mengdong Lin <mengdong.lin@intel.com>
> >
> > diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index
> > 6a2e030..4fa2d51 100644
> > --- a/include/sound/hdaudio.h
> > +++ b/include/sound/hdaudio.h
> > @@ -74,6 +74,7 @@ struct hdac_device {
> >
> >  	/* misc flags */
> >  	atomic_t in_pm;		/* suspend/resume being performed */
> > +	unsigned int need_i915_power:1; /* only codec needs i915 power */
> 
> Use bool.  A bool can have a bit field, too.
> 
> Also, try not to be too specific to certain hardware.
> Actually, a generic image about this operation is to control the link power.
> Something like link_power_control would fit better.

Yes, it should be more generic. I'll change to "bool link_power_control:1".

> >  	/* sysfs */
> >  	struct hdac_widget_tree *widgets;
> > @@ -184,6 +185,8 @@ struct hdac_bus_ops {
> >  	/* get a response from the last command */
> >  	int (*get_response)(struct hdac_bus *bus, unsigned int addr,
> >  			    unsigned int *res);
> > +	/* enable/disable the display power */
> > +	int (*display_power)(struct hdac_bus *bus, bool enable);
> 
> Also, this can be link_power() so that it can be used even for other than
> HDMI/DP, if any, too.

Okay. 

> >  };
> >
> >  /*
> > @@ -308,6 +311,15 @@ static inline void snd_hdac_codec_link_down(struct
> hdac_device *codec)
> >  	clear_bit(codec->addr, &codec->bus->codec_powered);  }
> >
> > +/*
> > + * Enable/disable the display power well, for HDMI/DP audio codecs
> > +that can be
> > + * in the shared power well with GPU display engine.
> > + */
> > +static inline int snd_hdac_display_power(struct hdac_device *codec,
> > +bool enable) {
> > +	return codec->bus->ops->display_power(codec->bus, enable);
> 
> Put a NULL check of bus->ops->display_power.  It's not always set.
> Also, the check of need_i915_power flag can be moved here.
> Then move into hdac_device.c, as it becomes big as an inline function.

Okay.
 
> > +}
> > +
> >  int snd_hdac_bus_send_cmd(struct hdac_bus *bus, unsigned int val);
> > int snd_hdac_bus_get_response(struct hdac_bus *bus, unsigned int addr,
> >  			      unsigned int *res);
> > diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c
> > index 2d8883f..2de9e84 100644
> > --- a/sound/pci/hda/hda_codec.c
> > +++ b/sound/pci/hda/hda_codec.c
> > @@ -857,6 +857,10 @@ void snd_hda_codec_register(struct hda_codec
> *codec)
> >  		return;
> >  	if (device_is_registered(hda_codec_dev(codec))) {
> >  		snd_hda_register_beep_device(codec);
> > +
> > +		if (codec->core.need_i915_power)
> > +			snd_hdac_display_power(&codec->core, true);
> > +
> >  		pm_runtime_enable(hda_codec_dev(codec));
> >  		/* it was powered up in snd_hda_codec_new(), now all done */
> >  		snd_hda_power_down(codec);
> > @@ -3102,6 +3106,9 @@ static int hda_codec_runtime_suspend(struct
> device *dev)
> >  	if (codec_has_clkstop(codec) && codec_has_epss(codec) &&
> >  	    (state & AC_PWRST_CLK_STOP_OK))
> >  		snd_hdac_codec_link_down(&codec->core);
> > +
> > +	if (codec->core.need_i915_power)
> > +		snd_hdac_display_power(&codec->core, false);
> >  	return 0;
> >  }
> >
> > @@ -3109,6 +3116,9 @@ static int hda_codec_runtime_resume(struct
> > device *dev)  {
> >  	struct hda_codec *codec = dev_to_hda_codec(dev);
> >
> > +	if (codec->core.need_i915_power)
> > +		snd_hdac_display_power(&codec->core, true);
> > +
> >  	snd_hdac_codec_link_up(&codec->core);
> >  	hda_call_codec_resume(codec);
> >  	pm_runtime_mark_last_busy(dev);
> > diff --git a/sound/pci/hda/hda_controller.c
> > b/sound/pci/hda/hda_controller.c index e0bb623..e5bdf31 100644
> > --- a/sound/pci/hda/hda_controller.c
> > +++ b/sound/pci/hda/hda_controller.c
> > @@ -775,9 +775,20 @@ static int azx_get_response(struct hdac_bus *bus,
> unsigned int addr,
> >  		return azx_rirb_get_response(bus, addr, res);  }
> >
> > +static int azx_display_power(struct hdac_bus *bus, bool enable) {
> > +	struct azx *chip = bus_to_azx(bus);
> > +
> > +	if (chip->ops->display_power)
> > +		return chip->ops->display_power(chip, enable);
> > +	else
> > +		return -EINVAL;
> > +}
> > +
> >  static const struct hdac_bus_ops bus_core_ops = {
> >  	.command = azx_send_cmd,
> >  	.get_response = azx_get_response,
> > +	.display_power = azx_display_power,
> >  };
> >
> >  #ifdef CONFIG_SND_HDA_DSP_LOADER
> > diff --git a/sound/pci/hda/hda_controller.h
> > b/sound/pci/hda/hda_controller.h index 173bf7c..e2292e4 100644
> > --- a/sound/pci/hda/hda_controller.h
> > +++ b/sound/pci/hda/hda_controller.h
> > @@ -89,6 +89,8 @@ struct hda_controller_ops {
> >  				 struct vm_area_struct *area);
> >  	/* Check if current position is acceptable */
> >  	int (*position_check)(struct azx *chip, struct azx_dev *azx_dev);
> > +	/* enable/disable the shared display power */
> > +	int (*display_power)(struct azx *chip, bool enable);
> >  };
> >
> >  struct azx_pcm {
> > @@ -152,6 +154,7 @@ struct azx {
> >  	unsigned int align_buffer_size:1;
> >  	unsigned int region_requested:1;
> >  	unsigned int disabled:1; /* disabled by VGA-switcher */
> > +	unsigned int need_i915_power:1; /* both controller & display codec
> > +needs i915 power */
> 
> This doesn't have to be in hda_controller.h.  hda_controller.c doesn't refer to
> this at all.  Better to move it into hda_intel.h.

Okay. Only the legacy HD-A driver need this flag to be set for Haswell and Broadwell.
I'll revise the patch tomorrow.

Thanks again!
Mengdong
 
> Takashi
> 
> >  #ifdef CONFIG_SND_HDA_DSP_LOADER
> >  	struct azx_dev saved_azx_dev;
> > diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c
> > index e615556..1b688ba 100644
> > --- a/sound/pci/hda/hda_intel.c
> > +++ b/sound/pci/hda/hda_intel.c
> > @@ -543,6 +543,14 @@ static int azx_position_check(struct azx *chip, struct
> azx_dev *azx_dev)
> >  	return 0;
> >  }
> >
> > +/* Enable/disable display power  */
> > +static int azx_intel_display_power(struct azx *chip, bool enable) {
> > +	struct hda_intel *hda = container_of(chip, struct hda_intel, chip);
> > +
> > +	return hda_display_power(hda, enable); }
> > +
> >  /*
> >   * Check whether the current DMA position is acceptable for updating
> >   * periods.  Returns non-zero if it's OK.
> > @@ -809,7 +817,8 @@ static int azx_suspend(struct device *dev)
> >
> >  	if (chip->msi)
> >  		pci_disable_msi(chip->pci);
> > -	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL)
> > +	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL
> > +		&& chip->need_i915_power)
> >  		hda_display_power(hda, false);
> >  	return 0;
> >  }
> > @@ -829,7 +838,8 @@ static int azx_resume(struct device *dev)
> >  	if (chip->disabled || hda->init_failed)
> >  		return 0;
> >
> > -	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) {
> > +	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL
> > +		&& chip->need_i915_power) {
> >  		hda_display_power(hda, true);
> >  		haswell_set_bclk(hda);
> >  	}
> > @@ -872,7 +882,8 @@ static int azx_runtime_suspend(struct device *dev)
> >  	azx_stop_chip(chip);
> >  	azx_enter_link_reset(chip);
> >  	azx_clear_irq_pending(chip);
> > -	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL)
> > +	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL
> > +		&& chip->need_i915_power)
> >  		hda_display_power(hda, false);
> >
> >  	return 0;
> > @@ -897,7 +908,8 @@ static int azx_runtime_resume(struct device *dev)
> >  	if (!azx_has_pm_runtime(chip))
> >  		return 0;
> >
> > -	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) {
> > +	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL
> > +		&& chip->need_i915_power) {
> >  		hda_display_power(hda, true);
> >  		haswell_set_bclk(hda);
> >  	}
> > @@ -1118,7 +1130,8 @@ static int azx_free(struct azx *chip)
> >  	release_firmware(chip->fw);
> >  #endif
> >  	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) {
> > -		hda_display_power(hda, false);
> > +		if (chip->need_i915_power)
> > +			hda_display_power(hda, false);
> >  		hda_i915_exit(hda);
> >  	}
> >  	kfree(hda);
> > @@ -1789,6 +1802,7 @@ static const struct hda_controller_ops pci_hda_ops
> = {
> >  	.substream_free_pages = substream_free_pages,
> >  	.pcm_mmap_prepare = pcm_mmap_prepare,
> >  	.position_check = azx_position_check,
> > +	.display_power = azx_intel_display_power,
> >  };
> >
> >  static int azx_probe(struct pci_dev *pci, @@ -1882,17 +1896,26 @@
> > static int azx_probe_continue(struct azx *chip)
> >  	int err;
> >
> >  	hda->probe_continued = 1;
> > -	/* Request power well for Haswell HDA controller and codec */
> > +
> > +	/* Request display power well for the HDA controller or codec. For
> > +	 * Haswell/Broadwell, both the display HDA controller and codec need
> > +	 * this power. For other platforms, like Baytrail/Braswell, only the
> > +	 * display codec needs the power and it can be released after probe.
> > +	 */
> >  	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) {
> > +		/* Assume the controller needs the power by default */
> > +		chip->need_i915_power = 1;
> > +
> >  #ifdef CONFIG_SND_HDA_I915
> >  		err = hda_i915_init(hda);
> >  		if (err < 0)
> > -			goto out_free;
> > +			goto i915_power_fail;
> > +
> >  		err = hda_display_power(hda, true);
> >  		if (err < 0) {
> >  			dev_err(chip->card->dev,
> >  				"Cannot turn on display power on i915\n");
> > -			goto out_free;
> > +			goto i915_power_fail;
> >  		}
> >  #endif
> >  	}
> > @@ -1939,6 +1962,11 @@ static int azx_probe_continue(struct azx *chip)
> >  		pm_runtime_put_noidle(&pci->dev);
> >
> >  out_free:
> > +	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL
> > +		&& !chip->need_i915_power)
> > +		hda_display_power(hda, false);
> > +
> > +i915_power_fail:
> >  	if (err < 0)
> >  		hda->init_failed = 1;
> >  	complete_all(&hda->probe_wait);
> > --
> > 1.9.1
> >
diff mbox

Patch

diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h
index 6a2e030..4fa2d51 100644
--- a/include/sound/hdaudio.h
+++ b/include/sound/hdaudio.h
@@ -74,6 +74,7 @@  struct hdac_device {
 
 	/* misc flags */
 	atomic_t in_pm;		/* suspend/resume being performed */
+	unsigned int need_i915_power:1; /* only codec needs i915 power */
 
 	/* sysfs */
 	struct hdac_widget_tree *widgets;
@@ -184,6 +185,8 @@  struct hdac_bus_ops {
 	/* get a response from the last command */
 	int (*get_response)(struct hdac_bus *bus, unsigned int addr,
 			    unsigned int *res);
+	/* enable/disable the display power */
+	int (*display_power)(struct hdac_bus *bus, bool enable);
 };
 
 /*
@@ -308,6 +311,15 @@  static inline void snd_hdac_codec_link_down(struct hdac_device *codec)
 	clear_bit(codec->addr, &codec->bus->codec_powered);
 }
 
+/*
+ * Enable/disable the display power well, for HDMI/DP audio codecs that can be
+ * in the shared power well with GPU display engine.
+ */
+static inline int snd_hdac_display_power(struct hdac_device *codec, bool enable)
+{
+	return codec->bus->ops->display_power(codec->bus, enable);
+}
+
 int snd_hdac_bus_send_cmd(struct hdac_bus *bus, unsigned int val);
 int snd_hdac_bus_get_response(struct hdac_bus *bus, unsigned int addr,
 			      unsigned int *res);
diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c
index 2d8883f..2de9e84 100644
--- a/sound/pci/hda/hda_codec.c
+++ b/sound/pci/hda/hda_codec.c
@@ -857,6 +857,10 @@  void snd_hda_codec_register(struct hda_codec *codec)
 		return;
 	if (device_is_registered(hda_codec_dev(codec))) {
 		snd_hda_register_beep_device(codec);
+
+		if (codec->core.need_i915_power)
+			snd_hdac_display_power(&codec->core, true);
+
 		pm_runtime_enable(hda_codec_dev(codec));
 		/* it was powered up in snd_hda_codec_new(), now all done */
 		snd_hda_power_down(codec);
@@ -3102,6 +3106,9 @@  static int hda_codec_runtime_suspend(struct device *dev)
 	if (codec_has_clkstop(codec) && codec_has_epss(codec) &&
 	    (state & AC_PWRST_CLK_STOP_OK))
 		snd_hdac_codec_link_down(&codec->core);
+
+	if (codec->core.need_i915_power)
+		snd_hdac_display_power(&codec->core, false);
 	return 0;
 }
 
@@ -3109,6 +3116,9 @@  static int hda_codec_runtime_resume(struct device *dev)
 {
 	struct hda_codec *codec = dev_to_hda_codec(dev);
 
+	if (codec->core.need_i915_power)
+		snd_hdac_display_power(&codec->core, true);
+
 	snd_hdac_codec_link_up(&codec->core);
 	hda_call_codec_resume(codec);
 	pm_runtime_mark_last_busy(dev);
diff --git a/sound/pci/hda/hda_controller.c b/sound/pci/hda/hda_controller.c
index e0bb623..e5bdf31 100644
--- a/sound/pci/hda/hda_controller.c
+++ b/sound/pci/hda/hda_controller.c
@@ -775,9 +775,20 @@  static int azx_get_response(struct hdac_bus *bus, unsigned int addr,
 		return azx_rirb_get_response(bus, addr, res);
 }
 
+static int azx_display_power(struct hdac_bus *bus, bool enable)
+{
+	struct azx *chip = bus_to_azx(bus);
+
+	if (chip->ops->display_power)
+		return chip->ops->display_power(chip, enable);
+	else
+		return -EINVAL;
+}
+
 static const struct hdac_bus_ops bus_core_ops = {
 	.command = azx_send_cmd,
 	.get_response = azx_get_response,
+	.display_power = azx_display_power,
 };
 
 #ifdef CONFIG_SND_HDA_DSP_LOADER
diff --git a/sound/pci/hda/hda_controller.h b/sound/pci/hda/hda_controller.h
index 173bf7c..e2292e4 100644
--- a/sound/pci/hda/hda_controller.h
+++ b/sound/pci/hda/hda_controller.h
@@ -89,6 +89,8 @@  struct hda_controller_ops {
 				 struct vm_area_struct *area);
 	/* Check if current position is acceptable */
 	int (*position_check)(struct azx *chip, struct azx_dev *azx_dev);
+	/* enable/disable the shared display power */
+	int (*display_power)(struct azx *chip, bool enable);
 };
 
 struct azx_pcm {
@@ -152,6 +154,7 @@  struct azx {
 	unsigned int align_buffer_size:1;
 	unsigned int region_requested:1;
 	unsigned int disabled:1; /* disabled by VGA-switcher */
+	unsigned int need_i915_power:1; /* both controller & display codec needs i915 power */
 
 #ifdef CONFIG_SND_HDA_DSP_LOADER
 	struct azx_dev saved_azx_dev;
diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c
index e615556..1b688ba 100644
--- a/sound/pci/hda/hda_intel.c
+++ b/sound/pci/hda/hda_intel.c
@@ -543,6 +543,14 @@  static int azx_position_check(struct azx *chip, struct azx_dev *azx_dev)
 	return 0;
 }
 
+/* Enable/disable display power  */
+static int azx_intel_display_power(struct azx *chip, bool enable)
+{
+	struct hda_intel *hda = container_of(chip, struct hda_intel, chip);
+
+	return hda_display_power(hda, enable);
+}
+
 /*
  * Check whether the current DMA position is acceptable for updating
  * periods.  Returns non-zero if it's OK.
@@ -809,7 +817,8 @@  static int azx_suspend(struct device *dev)
 
 	if (chip->msi)
 		pci_disable_msi(chip->pci);
-	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL)
+	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL
+		&& chip->need_i915_power)
 		hda_display_power(hda, false);
 	return 0;
 }
@@ -829,7 +838,8 @@  static int azx_resume(struct device *dev)
 	if (chip->disabled || hda->init_failed)
 		return 0;
 
-	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) {
+	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL
+		&& chip->need_i915_power) {
 		hda_display_power(hda, true);
 		haswell_set_bclk(hda);
 	}
@@ -872,7 +882,8 @@  static int azx_runtime_suspend(struct device *dev)
 	azx_stop_chip(chip);
 	azx_enter_link_reset(chip);
 	azx_clear_irq_pending(chip);
-	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL)
+	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL
+		&& chip->need_i915_power)
 		hda_display_power(hda, false);
 
 	return 0;
@@ -897,7 +908,8 @@  static int azx_runtime_resume(struct device *dev)
 	if (!azx_has_pm_runtime(chip))
 		return 0;
 
-	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) {
+	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL
+		&& chip->need_i915_power) {
 		hda_display_power(hda, true);
 		haswell_set_bclk(hda);
 	}
@@ -1118,7 +1130,8 @@  static int azx_free(struct azx *chip)
 	release_firmware(chip->fw);
 #endif
 	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) {
-		hda_display_power(hda, false);
+		if (chip->need_i915_power)
+			hda_display_power(hda, false);
 		hda_i915_exit(hda);
 	}
 	kfree(hda);
@@ -1789,6 +1802,7 @@  static const struct hda_controller_ops pci_hda_ops = {
 	.substream_free_pages = substream_free_pages,
 	.pcm_mmap_prepare = pcm_mmap_prepare,
 	.position_check = azx_position_check,
+	.display_power = azx_intel_display_power,
 };
 
 static int azx_probe(struct pci_dev *pci,
@@ -1882,17 +1896,26 @@  static int azx_probe_continue(struct azx *chip)
 	int err;
 
 	hda->probe_continued = 1;
-	/* Request power well for Haswell HDA controller and codec */
+
+	/* Request display power well for the HDA controller or codec. For
+	 * Haswell/Broadwell, both the display HDA controller and codec need
+	 * this power. For other platforms, like Baytrail/Braswell, only the
+	 * display codec needs the power and it can be released after probe.
+	 */
 	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) {
+		/* Assume the controller needs the power by default */
+		chip->need_i915_power = 1;
+
 #ifdef CONFIG_SND_HDA_I915
 		err = hda_i915_init(hda);
 		if (err < 0)
-			goto out_free;
+			goto i915_power_fail;
+
 		err = hda_display_power(hda, true);
 		if (err < 0) {
 			dev_err(chip->card->dev,
 				"Cannot turn on display power on i915\n");
-			goto out_free;
+			goto i915_power_fail;
 		}
 #endif
 	}
@@ -1939,6 +1962,11 @@  static int azx_probe_continue(struct azx *chip)
 		pm_runtime_put_noidle(&pci->dev);
 
 out_free:
+	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL
+		&& !chip->need_i915_power)
+		hda_display_power(hda, false);
+
+i915_power_fail:
 	if (err < 0)
 		hda->init_failed = 1;
 	complete_all(&hda->probe_wait);