diff mbox

[1/2] drm/i915: report Gen5+ CPU and PCH FIFO underruns

Message ID 1365800278-3781-1-git-send-email-przanoni@gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Paulo Zanoni April 12, 2013, 8:57 p.m. UTC
From: Paulo Zanoni <paulo.r.zanoni@intel.com>

In this commit we enable both CPU and PCH FIFO underrun reporting and
start reporting them. We follow a few rules:
  - after we receive one of these errors, we mask the interrupt, so
    we won't get an "interrupt storm" and we also won't flood dmesg;
  - at each mode set we enable the interrupts again, so we'll see each
    message at most once per mode set;
  - in the specific places where we need to ignore the errors, we
    completely mask the interrupts.

The downside of this patch is that since we're completely disabling
(masking) the interrupts instead of just not printing error messages,
we will mask more than just what we want on IVB/HSW CPU interrupts
(due to GEN7_ERR_INT) and on CPT/PPT/LPT PCHs (due to SERR_INT). So
when we decide to mask PCH FIFO underruns for pipe A on CPT, we'll
also be masking PCH FIFO underruns for pipe B, because both are
reported by SERR_INT, which has to be either completely enabled or
completely disabled (in othe words, there's no way to disable/enable
specific bits of GEN7_ERR_INT and SERR_INT).

V2: Rename some functions and variables, downgrade messages to
DRM_DEBUG_DRIVER and rebase.

Signed-off-by: Paulo Zanoni <paulo.r.zanoni@intel.com>
---
 drivers/gpu/drm/i915/i915_irq.c      |  315 +++++++++++++++++++++++++++++++++-
 drivers/gpu/drm/i915/i915_reg.h      |   13 +-
 drivers/gpu/drm/i915/intel_display.c |   14 ++
 drivers/gpu/drm/i915/intel_drv.h     |   11 ++
 4 files changed, 342 insertions(+), 11 deletions(-)

Comments

Imre Deak April 18, 2013, 8:29 p.m. UTC | #1
Hi,

one comment below:

On Fri, 2013-04-12 at 17:57 -0300, Paulo Zanoni wrote:
> From: Paulo Zanoni <paulo.r.zanoni@intel.com>
> 
> In this commit we enable both CPU and PCH FIFO underrun reporting and
> start reporting them. We follow a few rules:
>   - after we receive one of these errors, we mask the interrupt, so
>     we won't get an "interrupt storm" and we also won't flood dmesg;
>   - at each mode set we enable the interrupts again, so we'll see each
>     message at most once per mode set;
>   - in the specific places where we need to ignore the errors, we
>     completely mask the interrupts.
> 
> The downside of this patch is that since we're completely disabling
> (masking) the interrupts instead of just not printing error messages,
> we will mask more than just what we want on IVB/HSW CPU interrupts
> (due to GEN7_ERR_INT) and on CPT/PPT/LPT PCHs (due to SERR_INT). So
> when we decide to mask PCH FIFO underruns for pipe A on CPT, we'll
> also be masking PCH FIFO underruns for pipe B, because both are
> reported by SERR_INT, which has to be either completely enabled or
> completely disabled (in othe words, there's no way to disable/enable
> specific bits of GEN7_ERR_INT and SERR_INT).
> 
> V2: Rename some functions and variables, downgrade messages to
> DRM_DEBUG_DRIVER and rebase.
> 
> Signed-off-by: Paulo Zanoni <paulo.r.zanoni@intel.com>
> ---
>  drivers/gpu/drm/i915/i915_irq.c      |  315 +++++++++++++++++++++++++++++++++-
>  drivers/gpu/drm/i915/i915_reg.h      |   13 +-
>  drivers/gpu/drm/i915/intel_display.c |   14 ++
>  drivers/gpu/drm/i915/intel_drv.h     |   11 ++
>  4 files changed, 342 insertions(+), 11 deletions(-)
> 
> diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c
> index e97bbb2..9aff6ed 100644
> --- a/drivers/gpu/drm/i915/i915_irq.c
> +++ b/drivers/gpu/drm/i915/i915_irq.c
> @@ -111,6 +111,213 @@ ironlake_disable_display_irq(drm_i915_private_t *dev_priv, u32 mask)
>  	}
>  }
>  
> +static bool ivb_can_enable_err_int(struct drm_device *dev)
> +{
> +	struct drm_i915_private *dev_priv = dev->dev_private;
> +	struct intel_crtc *crtc;
> +	enum pipe pipe;
> +
> +	for_each_pipe(pipe) {
> +		crtc = to_intel_crtc(dev_priv->pipe_to_crtc_mapping[pipe]);
> +
> +		if (crtc->cpu_fifo_underrun_disabled)
> +			return false;
> +	}
> +
> +	return true;
> +}
> +
> +static bool cpt_can_enable_serr_int(struct drm_device *dev)
> +{
> +	struct drm_i915_private *dev_priv = dev->dev_private;
> +	enum pipe pipe;
> +	struct intel_crtc *crtc;
> +
> +	for_each_pipe(pipe) {
> +		crtc = to_intel_crtc(dev_priv->pipe_to_crtc_mapping[pipe]);
> +
> +		if (crtc->pch_fifo_underrun_disabled)
> +			return false;
> +	}
> +
> +	return true;
> +}
> +
> +static void ironlake_set_fifo_underrun_reporting(struct drm_device *dev,
> +						 enum pipe pipe, bool enable)
> +{
> +	struct drm_i915_private *dev_priv = dev->dev_private;
> +	uint32_t bit = (pipe == PIPE_A) ? DE_PIPEA_FIFO_UNDERRUN :
> +					  DE_PIPEB_FIFO_UNDERRUN;
> +
> +	if (enable)
> +		ironlake_enable_display_irq(dev_priv, bit);
> +	else
> +		ironlake_disable_display_irq(dev_priv, bit);
> +}
> +
> +static void ivybridge_set_fifo_underrun_reporting(struct drm_device *dev,
> +						  bool enable)
> +{
> +	struct drm_i915_private *dev_priv = dev->dev_private;
> +
> +	if (enable) {
> +		if (!ivb_can_enable_err_int(dev))
> +			return;
> +
> +		I915_WRITE(GEN7_ERR_INT, ERR_INT_FIFO_UNDERRUN_A |
> +					 ERR_INT_FIFO_UNDERRUN_B |
> +					 ERR_INT_FIFO_UNDERRUN_C);
> +
> +		ironlake_enable_display_irq(dev_priv, DE_ERR_INT_IVB);
> +	} else {
> +		ironlake_disable_display_irq(dev_priv, DE_ERR_INT_IVB);
> +	}
> +}
> +
> +static void ibx_set_fifo_underrun_reporting(struct intel_crtc *crtc,
> +					    bool enable)
> +{
> +	struct drm_device *dev = crtc->base.dev;
> +	struct drm_i915_private *dev_priv = dev->dev_private;
> +	uint32_t bit = (crtc->pipe == PIPE_A) ? SDE_TRANSA_FIFO_UNDER :
> +						SDE_TRANSB_FIFO_UNDER;
> +
> +	if (enable)
> +		I915_WRITE(SDEIMR, I915_READ(SDEIMR) & ~bit);
> +	else
> +		I915_WRITE(SDEIMR, I915_READ(SDEIMR) | bit);
> +
> +	POSTING_READ(SDEIMR);
> +}
> +
> +static void cpt_set_fifo_underrun_reporting(struct drm_device *dev,
> +					    enum transcoder pch_transcoder,
> +					    bool enable)
> +{
> +	struct drm_i915_private *dev_priv = dev->dev_private;
> +
> +	if (enable) {
> +		if (!cpt_can_enable_serr_int(dev))
> +			return;
> +
> +		I915_WRITE(SERR_INT, SERR_INT_TRANS_A_FIFO_UNDERRUN |
> +				     SERR_INT_TRANS_B_FIFO_UNDERRUN |
> +				     SERR_INT_TRANS_C_FIFO_UNDERRUN);

We end up here for LPT too, but there only the TRANS_A bit is defined.
Otherwise looks good:
Reviewed-by: Imre Deak <imre.deak@intel.com>

> +
> +		I915_WRITE(SDEIMR, I915_READ(SDEIMR) & ~SDE_ERROR_CPT);
> +	} else {
> +		I915_WRITE(SDEIMR, I915_READ(SDEIMR) | SDE_ERROR_CPT);
> +	}
> +
> +	POSTING_READ(SDEIMR);
> +}
> +
> +/**
> + * intel_set_cpu_fifo_underrun_reporting - enable/disable FIFO underrun messages
> + * @dev: drm device
> + * @pipe: pipe
> + * @enable: true if we want to report FIFO underrun errors, false otherwise
> + *
> + * This function makes us disable or enable CPU fifo underruns for a specific
> + * pipe. Notice that on some Gens (e.g. IVB, HSW), disabling FIFO underrun
> + * reporting for one pipe may also disable all the other CPU error interruts for
> + * the other pipes, due to the fact that there's just one interrupt mask/enable
> + * bit for all the pipes.
> + *
> + * Returns the previous state of underrun reporting.
> + */
> +bool intel_set_cpu_fifo_underrun_reporting(struct drm_device *dev,
> +					   enum pipe pipe, bool enable)
> +{
> +	struct drm_i915_private *dev_priv = dev->dev_private;
> +	struct drm_crtc *crtc = dev_priv->pipe_to_crtc_mapping[pipe];
> +	struct intel_crtc *intel_crtc = to_intel_crtc(crtc);
> +	unsigned long flags;
> +	bool ret;
> +
> +	spin_lock_irqsave(&dev_priv->irq_lock, flags);
> +
> +	ret = !intel_crtc->cpu_fifo_underrun_disabled;
> +
> +	if (enable == ret)
> +		goto done;
> +
> +	intel_crtc->cpu_fifo_underrun_disabled = !enable;
> +
> +	if (IS_GEN5(dev) || IS_GEN6(dev))
> +		ironlake_set_fifo_underrun_reporting(dev, pipe, enable);
> +	else if (IS_GEN7(dev))
> +		ivybridge_set_fifo_underrun_reporting(dev, enable);
> +
> +done:
> +	spin_unlock_irqrestore(&dev_priv->irq_lock, flags);
> +	return ret;
> +}
> +
> +/**
> + * intel_set_pch_fifo_underrun_reporting - enable/disable FIFO underrun messages
> + * @dev: drm device
> + * @pch_transcoder: the PCH transcoder (same as pipe on IVB and older)
> + * @enable: true if we want to report FIFO underrun errors, false otherwise
> + *
> + * This function makes us disable or enable PCH fifo underruns for a specific
> + * PCH transcoder. Notice that on some PCHs (e.g. CPT/PPT), disabling FIFO
> + * underrun reporting for one transcoder may also disable all the other PCH
> + * error interruts for the other transcoders, due to the fact that there's just
> + * one interrupt mask/enable bit for all the transcoders.
> + *
> + * Returns the previous state of underrun reporting.
> + */
> +bool intel_set_pch_fifo_underrun_reporting(struct drm_device *dev,
> +					   enum transcoder pch_transcoder,
> +					   bool enable)
> +{
> +	struct drm_i915_private *dev_priv = dev->dev_private;
> +	enum pipe p;
> +	struct drm_crtc *crtc;
> +	struct intel_crtc *intel_crtc;
> +	unsigned long flags;
> +	bool ret;
> +
> +	if (HAS_PCH_LPT(dev)) {
> +		crtc = NULL;
> +		for_each_pipe(p) {
> +			struct drm_crtc *c = dev_priv->pipe_to_crtc_mapping[p];
> +			if (intel_pipe_has_type(c, INTEL_OUTPUT_ANALOG)) {
> +				crtc = c;
> +				break;
> +			}
> +		}
> +		if (!crtc) {
> +			DRM_ERROR("PCH FIFO underrun, but no CRTC using the PCH found\n");
> +			return false;
> +		}
> +	} else {
> +		crtc = dev_priv->pipe_to_crtc_mapping[pch_transcoder];
> +	}
> +	intel_crtc = to_intel_crtc(crtc);
> +
> +	spin_lock_irqsave(&dev_priv->irq_lock, flags);
> +
> +	ret = !intel_crtc->pch_fifo_underrun_disabled;
> +
> +	if (enable == ret)
> +		goto done;
> +
> +	intel_crtc->pch_fifo_underrun_disabled = !enable;
> +
> +	if (HAS_PCH_IBX(dev))
> +		ibx_set_fifo_underrun_reporting(intel_crtc, enable);
> +	else
> +		cpt_set_fifo_underrun_reporting(dev, pch_transcoder, enable);
> +
> +done:
> +	spin_unlock_irqrestore(&dev_priv->irq_lock, flags);
> +	return ret;
> +}
> +
> +
>  void
>  i915_enable_pipestat(drm_i915_private_t *dev_priv, int pipe, u32 mask)
>  {
> @@ -716,10 +923,58 @@ static void ibx_irq_handler(struct drm_device *dev, u32 pch_iir)
>  	if (pch_iir & (SDE_TRANSB_CRC_ERR | SDE_TRANSA_CRC_ERR))
>  		DRM_DEBUG_DRIVER("PCH transcoder CRC error interrupt\n");
>  
> -	if (pch_iir & SDE_TRANSB_FIFO_UNDER)
> -		DRM_DEBUG_DRIVER("PCH transcoder B underrun interrupt\n");
>  	if (pch_iir & SDE_TRANSA_FIFO_UNDER)
> -		DRM_DEBUG_DRIVER("PCH transcoder A underrun interrupt\n");
> +		if (intel_set_pch_fifo_underrun_reporting(dev, TRANSCODER_A,
> +							  false))
> +			DRM_DEBUG_DRIVER("PCH transcoder A FIFO underrun\n");
> +
> +	if (pch_iir & SDE_TRANSB_FIFO_UNDER)
> +		if (intel_set_pch_fifo_underrun_reporting(dev, TRANSCODER_B,
> +							  false))
> +			DRM_DEBUG_DRIVER("PCH transcoder B FIFO underrun\n");
> +}
> +
> +static void ivb_err_int_handler(struct drm_device *dev)
> +{
> +	struct drm_i915_private *dev_priv = dev->dev_private;
> +	u32 err_int = I915_READ(GEN7_ERR_INT);
> +
> +	if (err_int & ERR_INT_FIFO_UNDERRUN_A)
> +		if (intel_set_cpu_fifo_underrun_reporting(dev, PIPE_A, false))
> +			DRM_DEBUG_DRIVER("Pipe A FIFO underrun\n");
> +
> +	if (err_int & ERR_INT_FIFO_UNDERRUN_B)
> +		if (intel_set_cpu_fifo_underrun_reporting(dev, PIPE_B, false))
> +			DRM_DEBUG_DRIVER("Pipe B FIFO underrun\n");
> +
> +	if (err_int & ERR_INT_FIFO_UNDERRUN_C)
> +		if (intel_set_cpu_fifo_underrun_reporting(dev, PIPE_C, false))
> +			DRM_DEBUG_DRIVER("Pipe C FIFO underrun\n");
> +
> +	I915_WRITE(GEN7_ERR_INT, err_int);
> +}
> +
> +static void cpt_serr_int_handler(struct drm_device *dev)
> +{
> +	struct drm_i915_private *dev_priv = dev->dev_private;
> +	u32 serr_int = I915_READ(SERR_INT);
> +
> +	if (serr_int & SERR_INT_TRANS_A_FIFO_UNDERRUN)
> +		if (intel_set_pch_fifo_underrun_reporting(dev, TRANSCODER_A,
> +							  false))
> +			DRM_DEBUG_DRIVER("PCH transcoder A FIFO underrun\n");
> +
> +	if (serr_int & SERR_INT_TRANS_B_FIFO_UNDERRUN)
> +		if (intel_set_pch_fifo_underrun_reporting(dev, TRANSCODER_B,
> +							  false))
> +			DRM_DEBUG_DRIVER("PCH transcoder B FIFO underrun\n");
> +
> +	if (serr_int & SERR_INT_TRANS_C_FIFO_UNDERRUN)
> +		if (intel_set_pch_fifo_underrun_reporting(dev, TRANSCODER_C,
> +							  false))
> +			DRM_DEBUG_DRIVER("PCH transcoder C FIFO underrun\n");
> +
> +	I915_WRITE(SERR_INT, serr_int);
>  }
>  
>  static void cpt_irq_handler(struct drm_device *dev, u32 pch_iir)
> @@ -752,6 +1007,9 @@ static void cpt_irq_handler(struct drm_device *dev, u32 pch_iir)
>  			DRM_DEBUG_DRIVER("  pipe %c FDI IIR: 0x%08x\n",
>  					 pipe_name(pipe),
>  					 I915_READ(FDI_RX_IIR(pipe)));
> +
> +	if (pch_iir & SDE_ERROR_CPT)
> +		cpt_serr_int_handler(dev);
>  }
>  
>  static irqreturn_t ivybridge_irq_handler(int irq, void *arg)
> @@ -764,6 +1022,14 @@ static irqreturn_t ivybridge_irq_handler(int irq, void *arg)
>  
>  	atomic_inc(&dev_priv->irq_received);
>  
> +	/* We get interrupts on unclaimed registers, so check for this before we
> +	 * do any I915_{READ,WRITE}. */
> +	if (IS_HASWELL(dev) &&
> +	    (I915_READ_NOTRACE(FPGA_DBG) & FPGA_DBG_RM_NOCLAIM)) {
> +		DRM_ERROR("Unclaimed register before interrupt\n");
> +		I915_WRITE_NOTRACE(FPGA_DBG, FPGA_DBG_RM_NOCLAIM);
> +	}
> +
>  	/* disable master interrupt before clearing iir  */
>  	de_ier = I915_READ(DEIER);
>  	I915_WRITE(DEIER, de_ier & ~DE_MASTER_IRQ_CONTROL);
> @@ -779,6 +1045,12 @@ static irqreturn_t ivybridge_irq_handler(int irq, void *arg)
>  		POSTING_READ(SDEIER);
>  	}
>  
> +	/* On Haswell, also mask ERR_INT because we don't want to risk
> +	 * generating "unclaimed register" interrupts from inside the interrupt
> +	 * handler. */
> +	if (IS_HASWELL(dev))
> +		ironlake_disable_display_irq(dev_priv, DE_ERR_INT_IVB);
> +
>  	gt_iir = I915_READ(GTIIR);
>  	if (gt_iir) {
>  		snb_gt_irq_handler(dev, dev_priv, gt_iir);
> @@ -788,6 +1060,9 @@ static irqreturn_t ivybridge_irq_handler(int irq, void *arg)
>  
>  	de_iir = I915_READ(DEIIR);
>  	if (de_iir) {
> +		if (de_iir & DE_ERR_INT_IVB)
> +			ivb_err_int_handler(dev);
> +
>  		if (de_iir & DE_AUX_CHANNEL_A_IVB)
>  			dp_aux_irq_handler(dev);
>  
> @@ -825,6 +1100,9 @@ static irqreturn_t ivybridge_irq_handler(int irq, void *arg)
>  		ret = IRQ_HANDLED;
>  	}
>  
> +	if (IS_HASWELL(dev) && ivb_can_enable_err_int(dev))
> +		ironlake_enable_display_irq(dev_priv, DE_ERR_INT_IVB);
> +
>  	I915_WRITE(DEIER, de_ier);
>  	POSTING_READ(DEIER);
>  	if (!HAS_PCH_NOP(dev)) {
> @@ -894,6 +1172,14 @@ static irqreturn_t ironlake_irq_handler(int irq, void *arg)
>  	if (de_iir & DE_PIPEB_VBLANK)
>  		drm_handle_vblank(dev, 1);
>  
> +	if (de_iir & DE_PIPEA_FIFO_UNDERRUN)
> +		if (intel_set_cpu_fifo_underrun_reporting(dev, PIPE_A, false))
> +			DRM_DEBUG_DRIVER("Pipe A FIFO underrun\n");
> +
> +	if (de_iir & DE_PIPEB_FIFO_UNDERRUN)
> +		if (intel_set_cpu_fifo_underrun_reporting(dev, PIPE_B, false))
> +			DRM_DEBUG_DRIVER("Pipe B FIFO underrun\n");
> +
>  	if (de_iir & DE_PLANEA_FLIP_DONE) {
>  		intel_prepare_page_flip(dev, 0);
>  		intel_finish_page_flip_plane(dev, 0);
> @@ -2115,10 +2401,14 @@ static void ibx_irq_postinstall(struct drm_device *dev)
>  	drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
>  	u32 mask;
>  
> -	if (HAS_PCH_IBX(dev))
> -		mask = SDE_GMBUS | SDE_AUX_MASK;
> -	else
> -		mask = SDE_GMBUS_CPT | SDE_AUX_MASK_CPT;
> +	if (HAS_PCH_IBX(dev)) {
> +		mask = SDE_GMBUS | SDE_AUX_MASK | SDE_TRANSB_FIFO_UNDER |
> +		       SDE_TRANSA_FIFO_UNDER;
> +	} else {
> +		mask = SDE_GMBUS_CPT | SDE_AUX_MASK_CPT | SDE_ERROR_CPT;
> +
> +		I915_WRITE(SERR_INT, I915_READ(SERR_INT));
> +	}
>  
>  	if (HAS_PCH_NOP(dev))
>  		return;
> @@ -2133,7 +2423,8 @@ static int ironlake_irq_postinstall(struct drm_device *dev)
>  	/* enable kind of interrupts always enabled */
>  	u32 display_mask = DE_MASTER_IRQ_CONTROL | DE_GSE | DE_PCH_EVENT |
>  			   DE_PLANEA_FLIP_DONE | DE_PLANEB_FLIP_DONE |
> -			   DE_AUX_CHANNEL_A;
> +			   DE_AUX_CHANNEL_A | DE_PIPEB_FIFO_UNDERRUN |
> +			   DE_PIPEA_FIFO_UNDERRUN;
>  	u32 render_irqs;
>  
>  	dev_priv->irq_mask = ~display_mask;
> @@ -2183,12 +2474,14 @@ static int ivybridge_irq_postinstall(struct drm_device *dev)
>  		DE_PLANEC_FLIP_DONE_IVB |
>  		DE_PLANEB_FLIP_DONE_IVB |
>  		DE_PLANEA_FLIP_DONE_IVB |
> -		DE_AUX_CHANNEL_A_IVB;
> +		DE_AUX_CHANNEL_A_IVB |
> +		DE_ERR_INT_IVB;
>  	u32 render_irqs;
>  
>  	dev_priv->irq_mask = ~display_mask;
>  
>  	/* should always can generate irq */
> +	I915_WRITE(GEN7_ERR_INT, I915_READ(GEN7_ERR_INT));
>  	I915_WRITE(DEIIR, I915_READ(DEIIR));
>  	I915_WRITE(DEIMR, dev_priv->irq_mask);
>  	I915_WRITE(DEIER,
> @@ -2312,6 +2605,8 @@ static void ironlake_irq_uninstall(struct drm_device *dev)
>  	I915_WRITE(DEIMR, 0xffffffff);
>  	I915_WRITE(DEIER, 0x0);
>  	I915_WRITE(DEIIR, I915_READ(DEIIR));
> +	if (IS_GEN7(dev))
> +		I915_WRITE(GEN7_ERR_INT, I915_READ(GEN7_ERR_INT));
>  
>  	I915_WRITE(GTIMR, 0xffffffff);
>  	I915_WRITE(GTIER, 0x0);
> @@ -2323,6 +2618,8 @@ static void ironlake_irq_uninstall(struct drm_device *dev)
>  	I915_WRITE(SDEIMR, 0xffffffff);
>  	I915_WRITE(SDEIER, 0x0);
>  	I915_WRITE(SDEIIR, I915_READ(SDEIIR));
> +	if (HAS_PCH_CPT(dev) || HAS_PCH_LPT(dev))
> +		I915_WRITE(SERR_INT, I915_READ(SERR_INT));
>  }
>  
>  static void i8xx_irq_preinstall(struct drm_device * dev)
> diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h
> index 76d9df1..932b4a0 100644
> --- a/drivers/gpu/drm/i915/i915_reg.h
> +++ b/drivers/gpu/drm/i915/i915_reg.h
> @@ -529,7 +529,10 @@
>  
>  #define ERROR_GEN6	0x040a0
>  #define GEN7_ERR_INT	0x44040
> -#define   ERR_INT_MMIO_UNCLAIMED (1<<13)
> +#define   ERR_INT_MMIO_UNCLAIMED	(1<<13)
> +#define   ERR_INT_FIFO_UNDERRUN_C	(1<<6)
> +#define   ERR_INT_FIFO_UNDERRUN_B	(1<<3)
> +#define   ERR_INT_FIFO_UNDERRUN_A	(1<<0)
>  
>  #define FPGA_DBG		0x42300
>  #define   FPGA_DBG_RM_NOCLAIM	(1<<31)
> @@ -3505,7 +3508,7 @@
>  #define DE_PIPEA_FIFO_UNDERRUN  (1 << 0)
>  
>  /* More Ivybridge lolz */
> -#define DE_ERR_DEBUG_IVB		(1<<30)
> +#define DE_ERR_INT_IVB			(1<<30)
>  #define DE_GSE_IVB			(1<<29)
>  #define DE_PCH_EVENT_IVB		(1<<28)
>  #define DE_DP_A_HOTPLUG_IVB		(1<<27)
> @@ -3664,6 +3667,7 @@
>  				 SDE_PORTC_HOTPLUG_CPT |	\
>  				 SDE_PORTB_HOTPLUG_CPT)
>  #define SDE_GMBUS_CPT		(1 << 17)
> +#define SDE_ERROR_CPT		(1 << 16)
>  #define SDE_AUDIO_CP_REQ_C_CPT	(1 << 10)
>  #define SDE_AUDIO_CP_CHG_C_CPT	(1 << 9)
>  #define SDE_FDI_RXC_CPT		(1 << 8)
> @@ -3688,6 +3692,11 @@
>  #define SDEIIR  0xc4008
>  #define SDEIER  0xc400c
>  
> +#define SERR_INT			0xc4040
> +#define  SERR_INT_TRANS_C_FIFO_UNDERRUN	(1<<6)
> +#define  SERR_INT_TRANS_B_FIFO_UNDERRUN	(1<<3)
> +#define  SERR_INT_TRANS_A_FIFO_UNDERRUN	(1<<0)
> +
>  /* digital port hotplug */
>  #define PCH_PORT_HOTPLUG        0xc4030		/* SHOTPLUG_CTL */
>  #define PORTD_HOTPLUG_ENABLE            (1 << 20)
> diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c
> index 457a0a0..574d68d 100644
> --- a/drivers/gpu/drm/i915/intel_display.c
> +++ b/drivers/gpu/drm/i915/intel_display.c
> @@ -3339,6 +3339,10 @@ static void ironlake_crtc_enable(struct drm_crtc *crtc)
>  		return;
>  
>  	intel_crtc->active = true;
> +
> +	intel_set_cpu_fifo_underrun_reporting(dev, pipe, true);
> +	intel_set_pch_fifo_underrun_reporting(dev, pipe, true);
> +
>  	intel_update_watermarks(dev);
>  
>  	if (intel_pipe_has_type(crtc, INTEL_OUTPUT_LVDS)) {
> @@ -3430,6 +3434,11 @@ static void haswell_crtc_enable(struct drm_crtc *crtc)
>  		return;
>  
>  	intel_crtc->active = true;
> +
> +	intel_set_cpu_fifo_underrun_reporting(dev, pipe, true);
> +	if (intel_crtc->config.has_pch_encoder)
> +		intel_set_pch_fifo_underrun_reporting(dev, TRANSCODER_A, true);
> +
>  	intel_update_watermarks(dev);
>  
>  	if (intel_crtc->config.has_pch_encoder)
> @@ -3516,6 +3525,7 @@ static void ironlake_crtc_disable(struct drm_crtc *crtc)
>  	if (dev_priv->cfb_plane == plane)
>  		intel_disable_fbc(dev);
>  
> +	intel_set_pch_fifo_underrun_reporting(dev, pipe, false);
>  	intel_disable_pipe(dev_priv, pipe);
>  
>  	/* Disable PF */
> @@ -3529,6 +3539,7 @@ static void ironlake_crtc_disable(struct drm_crtc *crtc)
>  	ironlake_fdi_disable(crtc);
>  
>  	ironlake_disable_pch_transcoder(dev_priv, pipe);
> +	intel_set_pch_fifo_underrun_reporting(dev, pipe, true);
>  
>  	if (HAS_PCH_CPT(dev)) {
>  		/* disable TRANS_DP_CTL */
> @@ -3595,6 +3606,8 @@ static void haswell_crtc_disable(struct drm_crtc *crtc)
>  	if (dev_priv->cfb_plane == plane)
>  		intel_disable_fbc(dev);
>  
> +	if (intel_crtc->config.has_pch_encoder)
> +		intel_set_pch_fifo_underrun_reporting(dev, TRANSCODER_A, false);
>  	intel_disable_pipe(dev_priv, pipe);
>  
>  	intel_ddi_disable_transcoder_func(dev_priv, cpu_transcoder);
> @@ -3611,6 +3624,7 @@ static void haswell_crtc_disable(struct drm_crtc *crtc)
>  
>  	if (intel_crtc->config.has_pch_encoder) {
>  		lpt_disable_pch_transcoder(dev_priv);
> +		intel_set_pch_fifo_underrun_reporting(dev, TRANSCODER_A, true);
>  		intel_ddi_fdi_disable(crtc);
>  	}
>  
> diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h
> index d7bd031..fe3566c 100644
> --- a/drivers/gpu/drm/i915/intel_drv.h
> +++ b/drivers/gpu/drm/i915/intel_drv.h
> @@ -258,6 +258,10 @@ struct intel_crtc {
>  
>  	/* reset counter value when the last flip was submitted */
>  	unsigned int reset_counter;
> +
> +	/* Access to these should be protected by dev_priv->irq_lock. */
> +	bool cpu_fifo_underrun_disabled;
> +	bool pch_fifo_underrun_disabled;
>  };
>  
>  struct intel_plane {
> @@ -467,6 +471,7 @@ int intel_ddc_get_modes(struct drm_connector *c, struct i2c_adapter *adapter);
>  extern void intel_attach_force_audio_property(struct drm_connector *connector);
>  extern void intel_attach_broadcast_rgb_property(struct drm_connector *connector);
>  
> +extern bool intel_pipe_has_type(struct drm_crtc *crtc, int type);
>  extern void intel_crt_init(struct drm_device *dev);
>  extern void intel_hdmi_init(struct drm_device *dev,
>  			    int hdmi_reg, enum port port);
> @@ -719,5 +724,11 @@ intel_ddi_connector_get_hw_state(struct intel_connector *intel_connector);
>  extern void intel_ddi_fdi_disable(struct drm_crtc *crtc);
>  
>  extern void intel_display_handle_reset(struct drm_device *dev);
> +extern bool intel_set_cpu_fifo_underrun_reporting(struct drm_device *dev,
> +						  enum pipe pipe,
> +						  bool enable);
> +extern bool intel_set_pch_fifo_underrun_reporting(struct drm_device *dev,
> +						 enum transcoder pch_transcoder,
> +						 bool enable);
>  
>  #endif /* __INTEL_DRV_H__ */
Daniel Vetter April 19, 2013, 7:34 a.m. UTC | #2
On Thu, Apr 18, 2013 at 11:29:17PM +0300, Imre Deak wrote:
> On Fri, 2013-04-12 at 17:57 -0300, Paulo Zanoni wrote:
> > +static void cpt_set_fifo_underrun_reporting(struct drm_device *dev,
> > +					    enum transcoder pch_transcoder,
> > +					    bool enable)
> > +{
> > +	struct drm_i915_private *dev_priv = dev->dev_private;
> > +
> > +	if (enable) {
> > +		if (!cpt_can_enable_serr_int(dev))
> > +			return;
> > +
> > +		I915_WRITE(SERR_INT, SERR_INT_TRANS_A_FIFO_UNDERRUN |
> > +				     SERR_INT_TRANS_B_FIFO_UNDERRUN |
> > +				     SERR_INT_TRANS_C_FIFO_UNDERRUN);
> 
> We end up here for LPT too, but there only the TRANS_A bit is defined.

We have the same issue on cpt where there's not really a pipe C on the
pch. I guess it doesn't really matter.

> Otherwise looks good:
> Reviewed-by: Imre Deak <imre.deak@intel.com>

Queued for -next, thanks for the patch and review.
-Daniel
diff mbox

Patch

diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c
index e97bbb2..9aff6ed 100644
--- a/drivers/gpu/drm/i915/i915_irq.c
+++ b/drivers/gpu/drm/i915/i915_irq.c
@@ -111,6 +111,213 @@  ironlake_disable_display_irq(drm_i915_private_t *dev_priv, u32 mask)
 	}
 }
 
+static bool ivb_can_enable_err_int(struct drm_device *dev)
+{
+	struct drm_i915_private *dev_priv = dev->dev_private;
+	struct intel_crtc *crtc;
+	enum pipe pipe;
+
+	for_each_pipe(pipe) {
+		crtc = to_intel_crtc(dev_priv->pipe_to_crtc_mapping[pipe]);
+
+		if (crtc->cpu_fifo_underrun_disabled)
+			return false;
+	}
+
+	return true;
+}
+
+static bool cpt_can_enable_serr_int(struct drm_device *dev)
+{
+	struct drm_i915_private *dev_priv = dev->dev_private;
+	enum pipe pipe;
+	struct intel_crtc *crtc;
+
+	for_each_pipe(pipe) {
+		crtc = to_intel_crtc(dev_priv->pipe_to_crtc_mapping[pipe]);
+
+		if (crtc->pch_fifo_underrun_disabled)
+			return false;
+	}
+
+	return true;
+}
+
+static void ironlake_set_fifo_underrun_reporting(struct drm_device *dev,
+						 enum pipe pipe, bool enable)
+{
+	struct drm_i915_private *dev_priv = dev->dev_private;
+	uint32_t bit = (pipe == PIPE_A) ? DE_PIPEA_FIFO_UNDERRUN :
+					  DE_PIPEB_FIFO_UNDERRUN;
+
+	if (enable)
+		ironlake_enable_display_irq(dev_priv, bit);
+	else
+		ironlake_disable_display_irq(dev_priv, bit);
+}
+
+static void ivybridge_set_fifo_underrun_reporting(struct drm_device *dev,
+						  bool enable)
+{
+	struct drm_i915_private *dev_priv = dev->dev_private;
+
+	if (enable) {
+		if (!ivb_can_enable_err_int(dev))
+			return;
+
+		I915_WRITE(GEN7_ERR_INT, ERR_INT_FIFO_UNDERRUN_A |
+					 ERR_INT_FIFO_UNDERRUN_B |
+					 ERR_INT_FIFO_UNDERRUN_C);
+
+		ironlake_enable_display_irq(dev_priv, DE_ERR_INT_IVB);
+	} else {
+		ironlake_disable_display_irq(dev_priv, DE_ERR_INT_IVB);
+	}
+}
+
+static void ibx_set_fifo_underrun_reporting(struct intel_crtc *crtc,
+					    bool enable)
+{
+	struct drm_device *dev = crtc->base.dev;
+	struct drm_i915_private *dev_priv = dev->dev_private;
+	uint32_t bit = (crtc->pipe == PIPE_A) ? SDE_TRANSA_FIFO_UNDER :
+						SDE_TRANSB_FIFO_UNDER;
+
+	if (enable)
+		I915_WRITE(SDEIMR, I915_READ(SDEIMR) & ~bit);
+	else
+		I915_WRITE(SDEIMR, I915_READ(SDEIMR) | bit);
+
+	POSTING_READ(SDEIMR);
+}
+
+static void cpt_set_fifo_underrun_reporting(struct drm_device *dev,
+					    enum transcoder pch_transcoder,
+					    bool enable)
+{
+	struct drm_i915_private *dev_priv = dev->dev_private;
+
+	if (enable) {
+		if (!cpt_can_enable_serr_int(dev))
+			return;
+
+		I915_WRITE(SERR_INT, SERR_INT_TRANS_A_FIFO_UNDERRUN |
+				     SERR_INT_TRANS_B_FIFO_UNDERRUN |
+				     SERR_INT_TRANS_C_FIFO_UNDERRUN);
+
+		I915_WRITE(SDEIMR, I915_READ(SDEIMR) & ~SDE_ERROR_CPT);
+	} else {
+		I915_WRITE(SDEIMR, I915_READ(SDEIMR) | SDE_ERROR_CPT);
+	}
+
+	POSTING_READ(SDEIMR);
+}
+
+/**
+ * intel_set_cpu_fifo_underrun_reporting - enable/disable FIFO underrun messages
+ * @dev: drm device
+ * @pipe: pipe
+ * @enable: true if we want to report FIFO underrun errors, false otherwise
+ *
+ * This function makes us disable or enable CPU fifo underruns for a specific
+ * pipe. Notice that on some Gens (e.g. IVB, HSW), disabling FIFO underrun
+ * reporting for one pipe may also disable all the other CPU error interruts for
+ * the other pipes, due to the fact that there's just one interrupt mask/enable
+ * bit for all the pipes.
+ *
+ * Returns the previous state of underrun reporting.
+ */
+bool intel_set_cpu_fifo_underrun_reporting(struct drm_device *dev,
+					   enum pipe pipe, bool enable)
+{
+	struct drm_i915_private *dev_priv = dev->dev_private;
+	struct drm_crtc *crtc = dev_priv->pipe_to_crtc_mapping[pipe];
+	struct intel_crtc *intel_crtc = to_intel_crtc(crtc);
+	unsigned long flags;
+	bool ret;
+
+	spin_lock_irqsave(&dev_priv->irq_lock, flags);
+
+	ret = !intel_crtc->cpu_fifo_underrun_disabled;
+
+	if (enable == ret)
+		goto done;
+
+	intel_crtc->cpu_fifo_underrun_disabled = !enable;
+
+	if (IS_GEN5(dev) || IS_GEN6(dev))
+		ironlake_set_fifo_underrun_reporting(dev, pipe, enable);
+	else if (IS_GEN7(dev))
+		ivybridge_set_fifo_underrun_reporting(dev, enable);
+
+done:
+	spin_unlock_irqrestore(&dev_priv->irq_lock, flags);
+	return ret;
+}
+
+/**
+ * intel_set_pch_fifo_underrun_reporting - enable/disable FIFO underrun messages
+ * @dev: drm device
+ * @pch_transcoder: the PCH transcoder (same as pipe on IVB and older)
+ * @enable: true if we want to report FIFO underrun errors, false otherwise
+ *
+ * This function makes us disable or enable PCH fifo underruns for a specific
+ * PCH transcoder. Notice that on some PCHs (e.g. CPT/PPT), disabling FIFO
+ * underrun reporting for one transcoder may also disable all the other PCH
+ * error interruts for the other transcoders, due to the fact that there's just
+ * one interrupt mask/enable bit for all the transcoders.
+ *
+ * Returns the previous state of underrun reporting.
+ */
+bool intel_set_pch_fifo_underrun_reporting(struct drm_device *dev,
+					   enum transcoder pch_transcoder,
+					   bool enable)
+{
+	struct drm_i915_private *dev_priv = dev->dev_private;
+	enum pipe p;
+	struct drm_crtc *crtc;
+	struct intel_crtc *intel_crtc;
+	unsigned long flags;
+	bool ret;
+
+	if (HAS_PCH_LPT(dev)) {
+		crtc = NULL;
+		for_each_pipe(p) {
+			struct drm_crtc *c = dev_priv->pipe_to_crtc_mapping[p];
+			if (intel_pipe_has_type(c, INTEL_OUTPUT_ANALOG)) {
+				crtc = c;
+				break;
+			}
+		}
+		if (!crtc) {
+			DRM_ERROR("PCH FIFO underrun, but no CRTC using the PCH found\n");
+			return false;
+		}
+	} else {
+		crtc = dev_priv->pipe_to_crtc_mapping[pch_transcoder];
+	}
+	intel_crtc = to_intel_crtc(crtc);
+
+	spin_lock_irqsave(&dev_priv->irq_lock, flags);
+
+	ret = !intel_crtc->pch_fifo_underrun_disabled;
+
+	if (enable == ret)
+		goto done;
+
+	intel_crtc->pch_fifo_underrun_disabled = !enable;
+
+	if (HAS_PCH_IBX(dev))
+		ibx_set_fifo_underrun_reporting(intel_crtc, enable);
+	else
+		cpt_set_fifo_underrun_reporting(dev, pch_transcoder, enable);
+
+done:
+	spin_unlock_irqrestore(&dev_priv->irq_lock, flags);
+	return ret;
+}
+
+
 void
 i915_enable_pipestat(drm_i915_private_t *dev_priv, int pipe, u32 mask)
 {
@@ -716,10 +923,58 @@  static void ibx_irq_handler(struct drm_device *dev, u32 pch_iir)
 	if (pch_iir & (SDE_TRANSB_CRC_ERR | SDE_TRANSA_CRC_ERR))
 		DRM_DEBUG_DRIVER("PCH transcoder CRC error interrupt\n");
 
-	if (pch_iir & SDE_TRANSB_FIFO_UNDER)
-		DRM_DEBUG_DRIVER("PCH transcoder B underrun interrupt\n");
 	if (pch_iir & SDE_TRANSA_FIFO_UNDER)
-		DRM_DEBUG_DRIVER("PCH transcoder A underrun interrupt\n");
+		if (intel_set_pch_fifo_underrun_reporting(dev, TRANSCODER_A,
+							  false))
+			DRM_DEBUG_DRIVER("PCH transcoder A FIFO underrun\n");
+
+	if (pch_iir & SDE_TRANSB_FIFO_UNDER)
+		if (intel_set_pch_fifo_underrun_reporting(dev, TRANSCODER_B,
+							  false))
+			DRM_DEBUG_DRIVER("PCH transcoder B FIFO underrun\n");
+}
+
+static void ivb_err_int_handler(struct drm_device *dev)
+{
+	struct drm_i915_private *dev_priv = dev->dev_private;
+	u32 err_int = I915_READ(GEN7_ERR_INT);
+
+	if (err_int & ERR_INT_FIFO_UNDERRUN_A)
+		if (intel_set_cpu_fifo_underrun_reporting(dev, PIPE_A, false))
+			DRM_DEBUG_DRIVER("Pipe A FIFO underrun\n");
+
+	if (err_int & ERR_INT_FIFO_UNDERRUN_B)
+		if (intel_set_cpu_fifo_underrun_reporting(dev, PIPE_B, false))
+			DRM_DEBUG_DRIVER("Pipe B FIFO underrun\n");
+
+	if (err_int & ERR_INT_FIFO_UNDERRUN_C)
+		if (intel_set_cpu_fifo_underrun_reporting(dev, PIPE_C, false))
+			DRM_DEBUG_DRIVER("Pipe C FIFO underrun\n");
+
+	I915_WRITE(GEN7_ERR_INT, err_int);
+}
+
+static void cpt_serr_int_handler(struct drm_device *dev)
+{
+	struct drm_i915_private *dev_priv = dev->dev_private;
+	u32 serr_int = I915_READ(SERR_INT);
+
+	if (serr_int & SERR_INT_TRANS_A_FIFO_UNDERRUN)
+		if (intel_set_pch_fifo_underrun_reporting(dev, TRANSCODER_A,
+							  false))
+			DRM_DEBUG_DRIVER("PCH transcoder A FIFO underrun\n");
+
+	if (serr_int & SERR_INT_TRANS_B_FIFO_UNDERRUN)
+		if (intel_set_pch_fifo_underrun_reporting(dev, TRANSCODER_B,
+							  false))
+			DRM_DEBUG_DRIVER("PCH transcoder B FIFO underrun\n");
+
+	if (serr_int & SERR_INT_TRANS_C_FIFO_UNDERRUN)
+		if (intel_set_pch_fifo_underrun_reporting(dev, TRANSCODER_C,
+							  false))
+			DRM_DEBUG_DRIVER("PCH transcoder C FIFO underrun\n");
+
+	I915_WRITE(SERR_INT, serr_int);
 }
 
 static void cpt_irq_handler(struct drm_device *dev, u32 pch_iir)
@@ -752,6 +1007,9 @@  static void cpt_irq_handler(struct drm_device *dev, u32 pch_iir)
 			DRM_DEBUG_DRIVER("  pipe %c FDI IIR: 0x%08x\n",
 					 pipe_name(pipe),
 					 I915_READ(FDI_RX_IIR(pipe)));
+
+	if (pch_iir & SDE_ERROR_CPT)
+		cpt_serr_int_handler(dev);
 }
 
 static irqreturn_t ivybridge_irq_handler(int irq, void *arg)
@@ -764,6 +1022,14 @@  static irqreturn_t ivybridge_irq_handler(int irq, void *arg)
 
 	atomic_inc(&dev_priv->irq_received);
 
+	/* We get interrupts on unclaimed registers, so check for this before we
+	 * do any I915_{READ,WRITE}. */
+	if (IS_HASWELL(dev) &&
+	    (I915_READ_NOTRACE(FPGA_DBG) & FPGA_DBG_RM_NOCLAIM)) {
+		DRM_ERROR("Unclaimed register before interrupt\n");
+		I915_WRITE_NOTRACE(FPGA_DBG, FPGA_DBG_RM_NOCLAIM);
+	}
+
 	/* disable master interrupt before clearing iir  */
 	de_ier = I915_READ(DEIER);
 	I915_WRITE(DEIER, de_ier & ~DE_MASTER_IRQ_CONTROL);
@@ -779,6 +1045,12 @@  static irqreturn_t ivybridge_irq_handler(int irq, void *arg)
 		POSTING_READ(SDEIER);
 	}
 
+	/* On Haswell, also mask ERR_INT because we don't want to risk
+	 * generating "unclaimed register" interrupts from inside the interrupt
+	 * handler. */
+	if (IS_HASWELL(dev))
+		ironlake_disable_display_irq(dev_priv, DE_ERR_INT_IVB);
+
 	gt_iir = I915_READ(GTIIR);
 	if (gt_iir) {
 		snb_gt_irq_handler(dev, dev_priv, gt_iir);
@@ -788,6 +1060,9 @@  static irqreturn_t ivybridge_irq_handler(int irq, void *arg)
 
 	de_iir = I915_READ(DEIIR);
 	if (de_iir) {
+		if (de_iir & DE_ERR_INT_IVB)
+			ivb_err_int_handler(dev);
+
 		if (de_iir & DE_AUX_CHANNEL_A_IVB)
 			dp_aux_irq_handler(dev);
 
@@ -825,6 +1100,9 @@  static irqreturn_t ivybridge_irq_handler(int irq, void *arg)
 		ret = IRQ_HANDLED;
 	}
 
+	if (IS_HASWELL(dev) && ivb_can_enable_err_int(dev))
+		ironlake_enable_display_irq(dev_priv, DE_ERR_INT_IVB);
+
 	I915_WRITE(DEIER, de_ier);
 	POSTING_READ(DEIER);
 	if (!HAS_PCH_NOP(dev)) {
@@ -894,6 +1172,14 @@  static irqreturn_t ironlake_irq_handler(int irq, void *arg)
 	if (de_iir & DE_PIPEB_VBLANK)
 		drm_handle_vblank(dev, 1);
 
+	if (de_iir & DE_PIPEA_FIFO_UNDERRUN)
+		if (intel_set_cpu_fifo_underrun_reporting(dev, PIPE_A, false))
+			DRM_DEBUG_DRIVER("Pipe A FIFO underrun\n");
+
+	if (de_iir & DE_PIPEB_FIFO_UNDERRUN)
+		if (intel_set_cpu_fifo_underrun_reporting(dev, PIPE_B, false))
+			DRM_DEBUG_DRIVER("Pipe B FIFO underrun\n");
+
 	if (de_iir & DE_PLANEA_FLIP_DONE) {
 		intel_prepare_page_flip(dev, 0);
 		intel_finish_page_flip_plane(dev, 0);
@@ -2115,10 +2401,14 @@  static void ibx_irq_postinstall(struct drm_device *dev)
 	drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
 	u32 mask;
 
-	if (HAS_PCH_IBX(dev))
-		mask = SDE_GMBUS | SDE_AUX_MASK;
-	else
-		mask = SDE_GMBUS_CPT | SDE_AUX_MASK_CPT;
+	if (HAS_PCH_IBX(dev)) {
+		mask = SDE_GMBUS | SDE_AUX_MASK | SDE_TRANSB_FIFO_UNDER |
+		       SDE_TRANSA_FIFO_UNDER;
+	} else {
+		mask = SDE_GMBUS_CPT | SDE_AUX_MASK_CPT | SDE_ERROR_CPT;
+
+		I915_WRITE(SERR_INT, I915_READ(SERR_INT));
+	}
 
 	if (HAS_PCH_NOP(dev))
 		return;
@@ -2133,7 +2423,8 @@  static int ironlake_irq_postinstall(struct drm_device *dev)
 	/* enable kind of interrupts always enabled */
 	u32 display_mask = DE_MASTER_IRQ_CONTROL | DE_GSE | DE_PCH_EVENT |
 			   DE_PLANEA_FLIP_DONE | DE_PLANEB_FLIP_DONE |
-			   DE_AUX_CHANNEL_A;
+			   DE_AUX_CHANNEL_A | DE_PIPEB_FIFO_UNDERRUN |
+			   DE_PIPEA_FIFO_UNDERRUN;
 	u32 render_irqs;
 
 	dev_priv->irq_mask = ~display_mask;
@@ -2183,12 +2474,14 @@  static int ivybridge_irq_postinstall(struct drm_device *dev)
 		DE_PLANEC_FLIP_DONE_IVB |
 		DE_PLANEB_FLIP_DONE_IVB |
 		DE_PLANEA_FLIP_DONE_IVB |
-		DE_AUX_CHANNEL_A_IVB;
+		DE_AUX_CHANNEL_A_IVB |
+		DE_ERR_INT_IVB;
 	u32 render_irqs;
 
 	dev_priv->irq_mask = ~display_mask;
 
 	/* should always can generate irq */
+	I915_WRITE(GEN7_ERR_INT, I915_READ(GEN7_ERR_INT));
 	I915_WRITE(DEIIR, I915_READ(DEIIR));
 	I915_WRITE(DEIMR, dev_priv->irq_mask);
 	I915_WRITE(DEIER,
@@ -2312,6 +2605,8 @@  static void ironlake_irq_uninstall(struct drm_device *dev)
 	I915_WRITE(DEIMR, 0xffffffff);
 	I915_WRITE(DEIER, 0x0);
 	I915_WRITE(DEIIR, I915_READ(DEIIR));
+	if (IS_GEN7(dev))
+		I915_WRITE(GEN7_ERR_INT, I915_READ(GEN7_ERR_INT));
 
 	I915_WRITE(GTIMR, 0xffffffff);
 	I915_WRITE(GTIER, 0x0);
@@ -2323,6 +2618,8 @@  static void ironlake_irq_uninstall(struct drm_device *dev)
 	I915_WRITE(SDEIMR, 0xffffffff);
 	I915_WRITE(SDEIER, 0x0);
 	I915_WRITE(SDEIIR, I915_READ(SDEIIR));
+	if (HAS_PCH_CPT(dev) || HAS_PCH_LPT(dev))
+		I915_WRITE(SERR_INT, I915_READ(SERR_INT));
 }
 
 static void i8xx_irq_preinstall(struct drm_device * dev)
diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h
index 76d9df1..932b4a0 100644
--- a/drivers/gpu/drm/i915/i915_reg.h
+++ b/drivers/gpu/drm/i915/i915_reg.h
@@ -529,7 +529,10 @@ 
 
 #define ERROR_GEN6	0x040a0
 #define GEN7_ERR_INT	0x44040
-#define   ERR_INT_MMIO_UNCLAIMED (1<<13)
+#define   ERR_INT_MMIO_UNCLAIMED	(1<<13)
+#define   ERR_INT_FIFO_UNDERRUN_C	(1<<6)
+#define   ERR_INT_FIFO_UNDERRUN_B	(1<<3)
+#define   ERR_INT_FIFO_UNDERRUN_A	(1<<0)
 
 #define FPGA_DBG		0x42300
 #define   FPGA_DBG_RM_NOCLAIM	(1<<31)
@@ -3505,7 +3508,7 @@ 
 #define DE_PIPEA_FIFO_UNDERRUN  (1 << 0)
 
 /* More Ivybridge lolz */
-#define DE_ERR_DEBUG_IVB		(1<<30)
+#define DE_ERR_INT_IVB			(1<<30)
 #define DE_GSE_IVB			(1<<29)
 #define DE_PCH_EVENT_IVB		(1<<28)
 #define DE_DP_A_HOTPLUG_IVB		(1<<27)
@@ -3664,6 +3667,7 @@ 
 				 SDE_PORTC_HOTPLUG_CPT |	\
 				 SDE_PORTB_HOTPLUG_CPT)
 #define SDE_GMBUS_CPT		(1 << 17)
+#define SDE_ERROR_CPT		(1 << 16)
 #define SDE_AUDIO_CP_REQ_C_CPT	(1 << 10)
 #define SDE_AUDIO_CP_CHG_C_CPT	(1 << 9)
 #define SDE_FDI_RXC_CPT		(1 << 8)
@@ -3688,6 +3692,11 @@ 
 #define SDEIIR  0xc4008
 #define SDEIER  0xc400c
 
+#define SERR_INT			0xc4040
+#define  SERR_INT_TRANS_C_FIFO_UNDERRUN	(1<<6)
+#define  SERR_INT_TRANS_B_FIFO_UNDERRUN	(1<<3)
+#define  SERR_INT_TRANS_A_FIFO_UNDERRUN	(1<<0)
+
 /* digital port hotplug */
 #define PCH_PORT_HOTPLUG        0xc4030		/* SHOTPLUG_CTL */
 #define PORTD_HOTPLUG_ENABLE            (1 << 20)
diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c
index 457a0a0..574d68d 100644
--- a/drivers/gpu/drm/i915/intel_display.c
+++ b/drivers/gpu/drm/i915/intel_display.c
@@ -3339,6 +3339,10 @@  static void ironlake_crtc_enable(struct drm_crtc *crtc)
 		return;
 
 	intel_crtc->active = true;
+
+	intel_set_cpu_fifo_underrun_reporting(dev, pipe, true);
+	intel_set_pch_fifo_underrun_reporting(dev, pipe, true);
+
 	intel_update_watermarks(dev);
 
 	if (intel_pipe_has_type(crtc, INTEL_OUTPUT_LVDS)) {
@@ -3430,6 +3434,11 @@  static void haswell_crtc_enable(struct drm_crtc *crtc)
 		return;
 
 	intel_crtc->active = true;
+
+	intel_set_cpu_fifo_underrun_reporting(dev, pipe, true);
+	if (intel_crtc->config.has_pch_encoder)
+		intel_set_pch_fifo_underrun_reporting(dev, TRANSCODER_A, true);
+
 	intel_update_watermarks(dev);
 
 	if (intel_crtc->config.has_pch_encoder)
@@ -3516,6 +3525,7 @@  static void ironlake_crtc_disable(struct drm_crtc *crtc)
 	if (dev_priv->cfb_plane == plane)
 		intel_disable_fbc(dev);
 
+	intel_set_pch_fifo_underrun_reporting(dev, pipe, false);
 	intel_disable_pipe(dev_priv, pipe);
 
 	/* Disable PF */
@@ -3529,6 +3539,7 @@  static void ironlake_crtc_disable(struct drm_crtc *crtc)
 	ironlake_fdi_disable(crtc);
 
 	ironlake_disable_pch_transcoder(dev_priv, pipe);
+	intel_set_pch_fifo_underrun_reporting(dev, pipe, true);
 
 	if (HAS_PCH_CPT(dev)) {
 		/* disable TRANS_DP_CTL */
@@ -3595,6 +3606,8 @@  static void haswell_crtc_disable(struct drm_crtc *crtc)
 	if (dev_priv->cfb_plane == plane)
 		intel_disable_fbc(dev);
 
+	if (intel_crtc->config.has_pch_encoder)
+		intel_set_pch_fifo_underrun_reporting(dev, TRANSCODER_A, false);
 	intel_disable_pipe(dev_priv, pipe);
 
 	intel_ddi_disable_transcoder_func(dev_priv, cpu_transcoder);
@@ -3611,6 +3624,7 @@  static void haswell_crtc_disable(struct drm_crtc *crtc)
 
 	if (intel_crtc->config.has_pch_encoder) {
 		lpt_disable_pch_transcoder(dev_priv);
+		intel_set_pch_fifo_underrun_reporting(dev, TRANSCODER_A, true);
 		intel_ddi_fdi_disable(crtc);
 	}
 
diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h
index d7bd031..fe3566c 100644
--- a/drivers/gpu/drm/i915/intel_drv.h
+++ b/drivers/gpu/drm/i915/intel_drv.h
@@ -258,6 +258,10 @@  struct intel_crtc {
 
 	/* reset counter value when the last flip was submitted */
 	unsigned int reset_counter;
+
+	/* Access to these should be protected by dev_priv->irq_lock. */
+	bool cpu_fifo_underrun_disabled;
+	bool pch_fifo_underrun_disabled;
 };
 
 struct intel_plane {
@@ -467,6 +471,7 @@  int intel_ddc_get_modes(struct drm_connector *c, struct i2c_adapter *adapter);
 extern void intel_attach_force_audio_property(struct drm_connector *connector);
 extern void intel_attach_broadcast_rgb_property(struct drm_connector *connector);
 
+extern bool intel_pipe_has_type(struct drm_crtc *crtc, int type);
 extern void intel_crt_init(struct drm_device *dev);
 extern void intel_hdmi_init(struct drm_device *dev,
 			    int hdmi_reg, enum port port);
@@ -719,5 +724,11 @@  intel_ddi_connector_get_hw_state(struct intel_connector *intel_connector);
 extern void intel_ddi_fdi_disable(struct drm_crtc *crtc);
 
 extern void intel_display_handle_reset(struct drm_device *dev);
+extern bool intel_set_cpu_fifo_underrun_reporting(struct drm_device *dev,
+						  enum pipe pipe,
+						  bool enable);
+extern bool intel_set_pch_fifo_underrun_reporting(struct drm_device *dev,
+						 enum transcoder pch_transcoder,
+						 bool enable);
 
 #endif /* __INTEL_DRV_H__ */