diff mbox

[3/3] drm/i915: New drm crtc property for varying the size of borders

Message ID 1403272554-20383-4-git-send-email-akash.goel@intel.com (mailing list archive)
State New, archived
Headers show

Commit Message

akash.goel@intel.com June 20, 2014, 1:55 p.m. UTC
From: Akash Goel <akash.goel@intel.com>

This patch adds a new drm crtc property for varying the size of
the horizontal & vertical borers of the output/display window.
This will control the output of Panel fitter.
There are actually 4 separate properties so as to allow a
control on the size of each border left/top/bottom/right

Testcase: igt/kms_panel_fitter_test

Signed-off-by: Akash Goel <akash.goel@intel.com>
---
 drivers/gpu/drm/i915/i915_drv.h      |  10 ++
 drivers/gpu/drm/i915/intel_display.c | 118 ++++++++++++++++++-
 drivers/gpu/drm/i915/intel_drv.h     |  12 ++
 drivers/gpu/drm/i915/intel_panel.c   | 219 ++++++++++++++++++++++++++++++++---
 4 files changed, 344 insertions(+), 15 deletions(-)
diff mbox

Patch

diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h
index 0640071..b1d94d3 100644
--- a/drivers/gpu/drm/i915/i915_drv.h
+++ b/drivers/gpu/drm/i915/i915_drv.h
@@ -1515,6 +1515,16 @@  struct drm_i915_private {
 	struct drm_property *broadcast_rgb_property;
 	struct drm_property *force_audio_property;
 
+	/*
+	 * Properties to dynamically vary the size of the
+	 * borders. This will indirectly control the size
+	 * of the display window i.e Panel fitter output
+	 */
+	struct drm_property *left_border_property;
+	struct drm_property *right_border_property;
+	struct drm_property *top_border_property;
+	struct drm_property *bottom_border_property;
+
 	uint32_t hw_context_size;
 	struct list_head context_list;
 
diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c
index d95afb6..cf0b67f 100644
--- a/drivers/gpu/drm/i915/intel_display.c
+++ b/drivers/gpu/drm/i915/intel_display.c
@@ -11064,12 +11064,124 @@  out_config:
 	return ret;
 }
 
+static void intel_create_crtc_properties(struct drm_crtc *crtc)
+{
+	struct drm_device *dev = crtc->dev;
+	struct drm_i915_private *dev_priv = dev->dev_private;
+	struct intel_crtc *intel_crtc = to_intel_crtc(crtc);
+
+	/* Create properties*/
+	if (!dev_priv->left_border_property)
+		dev_priv->left_border_property =
+			drm_property_create_range(dev, 0, "left border",
+						0, (uint64_t)MAX_BORDER_VALUE);
+
+	if (!dev_priv->right_border_property)
+		dev_priv->right_border_property =
+			drm_property_create_range(dev, 0, "right border",
+						0, (uint64_t)MAX_BORDER_VALUE);
+
+	if (!dev_priv->top_border_property)
+		dev_priv->top_border_property =
+			drm_property_create_range(dev, 0, "top border",
+						0, (uint64_t)MAX_BORDER_VALUE);
+
+	if (!dev_priv->bottom_border_property)
+		dev_priv->bottom_border_property =
+			drm_property_create_range(dev, 0, "bottom border",
+						0, (uint64_t)MAX_BORDER_VALUE);
+
+	/* Attach to properties*/
+	if (dev_priv->left_border_property)
+		drm_object_attach_property(&intel_crtc->base.base,
+					   dev_priv->left_border_property,
+					   0);
+
+	if (dev_priv->right_border_property)
+		drm_object_attach_property(&intel_crtc->base.base,
+					   dev_priv->right_border_property,
+					   0);
+
+	if (dev_priv->top_border_property)
+		drm_object_attach_property(&intel_crtc->base.base,
+					   dev_priv->top_border_property,
+					   0);
+
+	if (dev_priv->bottom_border_property)
+		drm_object_attach_property(&intel_crtc->base.base,
+					   dev_priv->bottom_border_property,
+					   0);
+}
+
 static int intel_crtc_set_property(struct drm_crtc *crtc,
 		struct drm_property *property, uint64_t val)
 {
-	int ret = -ENOENT;
+	struct drm_device *dev = crtc->dev;
+	struct drm_i915_private *dev_priv = dev->dev_private;
+	struct intel_crtc *intel_crtc = to_intel_crtc(crtc);
 
-	return ret;
+	if (property == dev_priv->left_border_property) {
+		if (val == (uint64_t)intel_crtc->border[PANEL_BORDER_LEFT])
+			return 0;
+		if (val & 1) {
+			DRM_ERROR("Odd border value not supported\n");
+			return -EINVAL;
+		}
+
+		intel_crtc->border[PANEL_BORDER_LEFT] =	(uint32_t)val;
+		goto done;
+	}
+
+	if (property == dev_priv->right_border_property) {
+		if (val == (uint64_t)intel_crtc->border[PANEL_BORDER_RIGHT])
+			return 0;
+		if (val & 1) {
+			DRM_ERROR("Odd border value not supported\n");
+			return -EINVAL;
+		}
+
+		intel_crtc->border[PANEL_BORDER_RIGHT] = (uint32_t)val;
+		goto done;
+	}
+
+	if (property == dev_priv->top_border_property) {
+		if (val == (uint64_t)intel_crtc->border[PANEL_BORDER_TOP])
+			return 0;
+		if (val & 1) {
+			DRM_ERROR("Odd border value not supported\n");
+			return -EINVAL;
+		}
+
+		intel_crtc->border[PANEL_BORDER_TOP] = (uint32_t)val;
+		goto done;
+	}
+
+	if (property == dev_priv->bottom_border_property) {
+		if (val == (uint64_t)intel_crtc->border[PANEL_BORDER_BOTTOM])
+			return 0;
+		if (val & 1) {
+			DRM_ERROR("Odd border value not supported\n");
+			return -EINVAL;
+		}
+
+		intel_crtc->border[PANEL_BORDER_BOTTOM] = (uint32_t)val;
+		goto done;
+	}
+
+	return -EINVAL;
+
+done:
+	if (crtc) {
+		intel_crtc->apply_borders =
+			((intel_crtc->border[PANEL_BORDER_LEFT] != 0) ||
+			 (intel_crtc->border[PANEL_BORDER_RIGHT] != 0) ||
+			 (intel_crtc->border[PANEL_BORDER_TOP] != 0) ||
+			 (intel_crtc->border[PANEL_BORDER_BOTTOM] != 0));
+
+		return intel_crtc_restore_mode(crtc);
+	}
+
+	return -EINVAL;
 }
 
 static const struct drm_crtc_funcs intel_crtc_funcs = {
@@ -11516,6 +11628,8 @@  static void intel_crtc_init(struct drm_device *dev, int pipe)
 
 	drm_crtc_helper_add(&intel_crtc->base, &intel_helper_funcs);
 
+	intel_create_crtc_properties(&intel_crtc->base);
+
 	WARN_ON(drm_crtc_index(&intel_crtc->base) != intel_crtc->pipe);
 	return;
 
diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h
index 860d531..34b695f 100644
--- a/drivers/gpu/drm/i915/intel_drv.h
+++ b/drivers/gpu/drm/i915/intel_drv.h
@@ -109,6 +109,14 @@ 
 #define INTEL_DSI_VIDEO_MODE	0
 #define INTEL_DSI_COMMAND_MODE	1
 
+#define MAX_BORDER_VALUE 256
+enum panel_border {
+	PANEL_BORDER_LEFT = 0,
+	PANEL_BORDER_RIGHT,
+	PANEL_BORDER_TOP,
+	PANEL_BORDER_BOTTOM,
+};
+
 struct intel_framebuffer {
 	struct drm_framebuffer base;
 	struct drm_i915_gem_object *obj;
@@ -407,6 +415,10 @@  struct intel_crtc {
 	bool cpu_fifo_underrun_disabled;
 	bool pch_fifo_underrun_disabled;
 
+	/* border info for the output/display window */
+	uint32_t border[4];
+	bool apply_borders;
+
 	/* per-pipe watermark state */
 	struct {
 		/* watermarks currently being used  */
diff --git a/drivers/gpu/drm/i915/intel_panel.c b/drivers/gpu/drm/i915/intel_panel.c
index e605006..f459745 100644
--- a/drivers/gpu/drm/i915/intel_panel.c
+++ b/drivers/gpu/drm/i915/intel_panel.c
@@ -29,10 +29,25 @@ 
  */
 
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+/* Max Downscale ratio of 1.125, expressed in 1.12 fixed point format */
+#define MAX_DOWNSCALE_RATIO  (0x9 << 9)
 
 #include <linux/moduleparam.h>
 #include "intel_drv.h"
 
+static inline u32 panel_fitter_scaling(u32 source, u32 target)
+{
+	/*
+	 * Floating point operation is not supported. So the FACTOR
+	 * is defined, which can avoid the floating point computation
+	 * when calculating the panel ratio.
+	 */
+#define ACCURACY 12
+#define FACTOR (1 << ACCURACY)
+	u32 ratio = source * FACTOR / target;
+	return (FACTOR * ratio + FACTOR/2) / FACTOR;
+}
+
 void
 intel_fixed_panel_mode(const struct drm_display_mode *fixed_mode,
 		       struct drm_display_mode *adjusted_mode)
@@ -95,6 +110,61 @@  intel_find_panel_downclock(struct drm_device *dev,
 		return NULL;
 }
 
+bool
+intel_pch_manual_panel_fitting(struct intel_crtc *intel_crtc,
+			struct intel_crtc_config *pipe_config)
+{
+	struct drm_display_mode *adjusted_mode;
+	int x, y;
+	u32 pf_horizontal_ratio, pf_vertical_ratio;
+	u32 tot_width, tot_height;
+	u32 src_width, src_height; /* pipesrc.x, pipesrc.y */
+	u32 dst_width, dst_height;
+
+	adjusted_mode = &pipe_config->adjusted_mode;
+
+	src_width = pipe_config->pipe_src_w;
+	src_height = pipe_config->pipe_src_h;
+
+	tot_width  = adjusted_mode->hdisplay;
+	tot_height = adjusted_mode->vdisplay;
+
+	/*
+	 * Having non zero borders will reduce the size of 'HACTIVE/VACTIVE'
+	 * region. So (HACTIVE - Left border - Right Border) *
+	 * (VACTIVE  - Top Border  - Bottom border) will effectively be the
+	 * output rectangle on screen
+	 */
+	dst_width = tot_width - intel_crtc->border[PANEL_BORDER_LEFT] -
+				intel_crtc->border[PANEL_BORDER_RIGHT];
+	dst_height = tot_height - intel_crtc->border[PANEL_BORDER_TOP] -
+				intel_crtc->border[PANEL_BORDER_BOTTOM];
+
+	if ((dst_width == 0) || (dst_height == 0)) {
+		DRM_ERROR("Invalid border size input\n");
+		return false;
+	}
+
+	pf_horizontal_ratio = panel_fitter_scaling(src_width, dst_width);
+	pf_vertical_ratio   = panel_fitter_scaling(src_height, dst_height);
+
+	if (pf_horizontal_ratio > MAX_DOWNSCALE_RATIO) {
+		DRM_ERROR("width is too small\n");
+		return false;
+	} else if (pf_vertical_ratio > MAX_DOWNSCALE_RATIO) {
+		DRM_ERROR("height is too small\n");
+		return false;
+	}
+
+	x = intel_crtc->border[PANEL_BORDER_LEFT];
+	y = intel_crtc->border[PANEL_BORDER_TOP];
+
+	pipe_config->pch_pfit.pos = (x << 16) | y;
+	pipe_config->pch_pfit.size = (dst_width << 16) | dst_height;
+	pipe_config->pch_pfit.enabled = pipe_config->pch_pfit.size != 0;
+	return true;
+}
+
 /* adjusted_mode has been preset to be the panel's fixed mode */
 bool
 intel_pch_panel_fitting(struct intel_crtc *intel_crtc,
@@ -108,6 +178,12 @@  intel_pch_panel_fitting(struct intel_crtc *intel_crtc,
 
 	x = y = width = height = 0;
 
+	/* check if User wants to apply the borders, otherwise fall
+	   through the regular path */
+	if (intel_crtc->apply_borders)
+		return intel_pch_manual_panel_fitting(intel_crtc,
+						      pipe_config);
+
 	/* Native modes don't need fitting */
 	if (adjusted_mode->hdisplay == pipe_config->pipe_src_w &&
 	    adjusted_mode->vdisplay == pipe_config->pipe_src_h)
@@ -169,6 +245,50 @@  done:
 }
 
 static void
+apply_horizontal_borders(struct drm_display_mode *mode,
+		    int *border)
+{
+	u32 width, sync_pos, blank_width, sync_width;
+
+	/* keep the hsync and hblank widths constant */
+	sync_width = mode->crtc_hsync_end - mode->crtc_hsync_start;
+	blank_width = mode->crtc_hblank_end - mode->crtc_hblank_start;
+	sync_pos = (blank_width - sync_width + 1) / 2;
+
+	width = mode->hdisplay - border[PANEL_BORDER_LEFT] -
+				 border[PANEL_BORDER_RIGHT];
+
+	mode->crtc_hdisplay = width;
+	mode->crtc_hblank_start = width + border[PANEL_BORDER_RIGHT];
+	mode->crtc_hblank_end = mode->crtc_hblank_start + blank_width;
+
+	mode->crtc_hsync_start = mode->crtc_hblank_start + sync_pos;
+	mode->crtc_hsync_end = mode->crtc_hsync_start + sync_width;
+}
+
+static void
+apply_vertical_borders(struct drm_display_mode *mode,
+		    int *border)
+{
+	u32 height, sync_pos, blank_width, sync_width;
+
+	/* keep the vsync and vblank widths constant */
+	sync_width = mode->crtc_vsync_end - mode->crtc_vsync_start;
+	blank_width = mode->crtc_vblank_end - mode->crtc_vblank_start;
+	sync_pos = (blank_width - sync_width + 1) / 2;
+
+	height = mode->vdisplay - border[PANEL_BORDER_TOP] -
+				  border[PANEL_BORDER_BOTTOM];
+
+	mode->crtc_vdisplay = height;
+	mode->crtc_vblank_start = height + border[PANEL_BORDER_BOTTOM];
+	mode->crtc_vblank_end = mode->crtc_vblank_start + blank_width;
+
+	mode->crtc_vsync_start = mode->crtc_vblank_start + sync_pos;
+	mode->crtc_vsync_end = mode->crtc_vsync_start + sync_width;
+}
+
+static void
 centre_horizontally(struct drm_display_mode *mode,
 		    int width)
 {
@@ -211,19 +331,6 @@  centre_vertically(struct drm_display_mode *mode,
 	mode->crtc_vsync_end = mode->crtc_vsync_start + sync_width;
 }
 
-static inline u32 panel_fitter_scaling(u32 source, u32 target)
-{
-	/*
-	 * Floating point operation is not supported. So the FACTOR
-	 * is defined, which can avoid the floating point computation
-	 * when calculating the panel ratio.
-	 */
-#define ACCURACY 12
-#define FACTOR (1 << ACCURACY)
-	u32 ratio = source * FACTOR / target;
-	return (FACTOR * ratio + FACTOR/2) / FACTOR;
-}
-
 static void i965_scale_aspect(struct intel_crtc_config *pipe_config,
 			      u32 *pfit_control)
 {
@@ -301,6 +408,86 @@  static void i9xx_scale_aspect(struct intel_crtc_config *pipe_config,
 	}
 }
 
+bool intel_gmch_manual_panel_fitting(struct intel_crtc *intel_crtc,
+				     struct intel_crtc_config *pipe_config)
+{
+	struct drm_device *dev = intel_crtc->base.dev;
+	u32 pfit_control = 0, border = 0;
+	u32 pf_horizontal_ratio, pf_vertical_ratio;
+	struct drm_display_mode *adjusted_mode;
+	u32 tot_width, tot_height;
+	u32 src_width, src_height; /* pipesrc.x, pipesrc.y */
+	u32 dst_width, dst_height;
+
+	adjusted_mode = &pipe_config->adjusted_mode;
+
+	src_width = pipe_config->pipe_src_w;
+	src_height = pipe_config->pipe_src_h;
+
+	tot_width = adjusted_mode->hdisplay;
+	tot_height = adjusted_mode->vdisplay;
+
+	/*
+	 * Having non zero borders will reduce the size of 'HACTIVE/VACTIVE'
+	 * region. So (HACTIVE - Left border - Right Border) *
+	 * (VACTIVE  - Top Border  - Bottom border) will effectively be the
+	 * output rectangle on screen
+	 */
+	dst_width = tot_width - intel_crtc->border[PANEL_BORDER_LEFT] -
+				intel_crtc->border[PANEL_BORDER_RIGHT];
+	dst_height = tot_height - intel_crtc->border[PANEL_BORDER_TOP] -
+				intel_crtc->border[PANEL_BORDER_BOTTOM];
+
+	if ((dst_width == 0) || (dst_height == 0)) {
+		DRM_ERROR("Invalid border size input\n");
+		return false;
+	}
+
+	pf_horizontal_ratio = panel_fitter_scaling(src_width, dst_width);
+	pf_vertical_ratio   = panel_fitter_scaling(src_height, dst_height);
+
+	if (pf_horizontal_ratio > MAX_DOWNSCALE_RATIO) {
+		DRM_ERROR("width is too small\n");
+		return false;
+	} else if (pf_vertical_ratio > MAX_DOWNSCALE_RATIO) {
+		DRM_ERROR("height is too small\n");
+		return false;
+	}
+
+	if (dst_width != tot_width)
+		apply_horizontal_borders(adjusted_mode, intel_crtc->border);
+	if (dst_height != tot_height)
+		apply_vertical_borders(adjusted_mode, intel_crtc->border);
+
+	/* No scaling needed now, but still enable the panel fitter,
+	   as that will allow the User to subequently do the dynamic
+	   flipping of fbs of different resolutions */
+	if (adjusted_mode->crtc_hdisplay == pipe_config->pipe_src_w &&
+	    adjusted_mode->crtc_vdisplay == pipe_config->pipe_src_h) {
+		DRM_DEBUG_KMS("Forcefully enabling the Panel fitter\n");
+	}
+
+	border = LVDS_BORDER_ENABLE;
+
+	if (INTEL_INFO(dev)->gen >= 4) {
+		/* PFIT_SCALING_PROGRAMMED is de-featured on BYT */
+		pfit_control |= PFIT_ENABLE | PFIT_SCALING_AUTO;
+		pfit_control |= ((intel_crtc->pipe << PFIT_PIPE_SHIFT) | PFIT_FILTER_FUZZY);
+	} else {
+		pfit_control |= (PFIT_ENABLE |
+				 VERT_AUTO_SCALE | HORIZ_AUTO_SCALE |
+				 VERT_INTERP_BILINEAR | HORIZ_INTERP_BILINEAR);
+	}
+
+	/* Make sure pre-965 set dither correctly for 18bpp panels. */
+	if (INTEL_INFO(dev)->gen < 4 && pipe_config->pipe_bpp == 18)
+		pfit_control |= PANEL_8TO6_DITHER_ENABLE;
+
+	pipe_config->gmch_pfit.control = pfit_control;
+	pipe_config->gmch_pfit.lvds_border_bits = border;
+	return true;
+}
+
 bool intel_gmch_panel_fitting(struct intel_crtc *intel_crtc,
 			      struct intel_crtc_config *pipe_config,
 			      int fitting_mode)
@@ -311,6 +498,12 @@  bool intel_gmch_panel_fitting(struct intel_crtc *intel_crtc,
 
 	adjusted_mode = &pipe_config->adjusted_mode;
 
+	/* check if User wants to apply the borders, otherwise fall
+	   through the regular path */
+	if (intel_crtc->apply_borders)
+		return intel_gmch_manual_panel_fitting(intel_crtc,
+						       pipe_config);
+
 	/* Native modes don't need fitting */
 	if (adjusted_mode->hdisplay == pipe_config->pipe_src_w &&
 	    adjusted_mode->vdisplay == pipe_config->pipe_src_h)