Эх сурвалжийг харах

Merge tag 'imx-drm-next-2017-03-17' of git://git.pengutronix.de/git/pza/linux into drm-next

imx-drm PRE/PRG support, deferred plane disabling, separate alpha support

- Initial support for the Prefetch Resolve Engine/Gasket on i.MX6QP,
  improving linear scanout buffer memory bandwidth utilization. This
  will in the future grow reordering support and allow direct scanout
  of Vivante tiled renderbuffers from the GPU.
- Deferred plane disabling gets rid of some busy waiting in the atomic
  plane disable and crtc disable paths that lead to wait_for_vblank
  timeouts.
- Add support for RGBA formats with a separate alpha plane, that can
  reduce memory bandwidth utilization for mostly transparent overlay
  planes by skipping color reads for completely transparent regions.
- Allow moving an active overlay plane without enforcing a modeset.
- Add 8-bit and 16-bit bayer formats to ipu_cpmem_set_image.
- Set the base address in ipu_cpmem_set_image even for invalid formats
  to increase robustness against errors.
- Use drm_plane_helper_check_state in plane atomic_check.
- Some cleanup.

* tag 'imx-drm-next-2017-03-17' of git://git.pengutronix.de/git/pza/linux: (22 commits)
  drm/imx: Remove unneeded definition for structure imx_drm_component
  drm/imx: use PRG/PRE when possible
  drm/imx: enable/disable PRG on CRTC enable/disable
  gpu: ipu-v3: only set non-zero AXI ID for IC when PRG is absent
  gpu: ipu-v3: hook up PRG unit
  gpu: ipu-v3: document valid IPUv3 compatibles and extend for i.MX6 QuadPlus
  gpu: ipu-v3: add driver for Prefetch Resolve Gasket
  gpu: ipu-v3: add DT binding for the Prefetch Resolve Gasket
  gpu: ipu-v3: add driver for Prefetch Resolve Engine
  gpu: ipu-v3: add DT binding for the Prefetch Resolve Engine
  drm/imx: ipuv3-plane: add support for separate alpha planes
  drm/imx: extend drm_plane_state_to_eba for separate channel support
  gpu: ipu-v3: add support for separate alpha channels
  drm: add RGB formats with separate alpha plane
  drm/imx: add deferred plane disabling
  drm/imx: don't wait for vblank and stop calling cleanup_planes in commit_tail
  gpu: ipu-v3: add unsynchronised DP channel disabling
  gpu: ipu-v3: remove IRQ dance on DC channel disable
  gpu: ipu-cpmem: add bayer formats to ipu_cpmem_set_image
  gpu: ipu-cpmem: set image base address even for incorrect formats
  ...
Dave Airlie 8 жил өмнө
parent
commit
33d5f513c6

+ 58 - 1
Documentation/devicetree/bindings/display/imx/fsl-imx-drm.txt

@@ -21,13 +21,19 @@ Freescale i.MX IPUv3
 ====================
 
 Required properties:
-- compatible: Should be "fsl,<chip>-ipu"
+- compatible: Should be "fsl,<chip>-ipu" where <chip> is one of
+  - imx51
+  - imx53
+  - imx6q
+  - imx6qp
 - reg: should be register base and length as documented in the
   datasheet
 - interrupts: Should contain sync interrupt and error interrupt,
   in this order.
 - resets: phandle pointing to the system reset controller and
           reset line index, see reset/fsl,imx-src.txt for details
+Additional required properties for fsl,imx6qp-ipu:
+- fsl,prg: phandle to prg node associated with this IPU instance
 Optional properties:
 - port@[0-3]: Port nodes with endpoint definitions as defined in
   Documentation/devicetree/bindings/media/video-interfaces.txt.
@@ -53,6 +59,57 @@ ipu: ipu@18000000 {
 	};
 };
 
+Freescale i.MX PRE (Prefetch Resolve Engine)
+============================================
+
+Required properties:
+- compatible: should be "fsl,imx6qp-pre"
+- reg: should be register base and length as documented in the
+  datasheet
+- clocks : phandle to the PRE axi clock input, as described
+  in Documentation/devicetree/bindings/clock/clock-bindings.txt and
+  Documentation/devicetree/bindings/clock/imx6q-clock.txt.
+- clock-names: should be "axi"
+- interrupts: should contain the PRE interrupt
+- fsl,iram: phandle pointing to the mmio-sram device node, that should be
+  used for the PRE SRAM double buffer.
+
+example:
+
+pre@21c8000 {
+	compatible = "fsl,imx6qp-pre";
+	reg = <0x021c8000 0x1000>;
+	interrupts = <GIC_SPI 90 IRQ_TYPE_EDGE_RISING>;
+	clocks = <&clks IMX6QDL_CLK_PRE0>;
+	clock-names = "axi";
+	fsl,iram = <&ocram2>;
+};
+
+Freescale i.MX PRG (Prefetch Resolve Gasket)
+============================================
+
+Required properties:
+- compatible: should be "fsl,imx6qp-prg"
+- reg: should be register base and length as documented in the
+  datasheet
+- clocks : phandles to the PRG ipg and axi clock inputs, as described
+  in Documentation/devicetree/bindings/clock/clock-bindings.txt and
+  Documentation/devicetree/bindings/clock/imx6q-clock.txt.
+- clock-names: should be "ipg" and "axi"
+- fsl,pres: phandles to the PRE units attached to this PRG, with the fixed
+  PRE as the first entry and the muxable PREs following.
+
+example:
+
+prg@21cc000 {
+	compatible = "fsl,imx6qp-prg";
+	reg = <0x021cc000 0x1000>;
+	clocks = <&clks IMX6QDL_CLK_PRG0_APB>,
+		 <&clks IMX6QDL_CLK_PRG0_AXI>;
+	clock-names = "ipg", "axi";
+	fsl,pres = <&pre1>, <&pre2>, <&pre3>;
+};
+
 Parallel display support
 ========================
 

+ 8 - 0
drivers/gpu/drm/drm_fourcc.c

@@ -132,6 +132,8 @@ const struct drm_format_info *__drm_format_info(u32 format)
 		{ .format = DRM_FORMAT_XBGR8888,	.depth = 24, .num_planes = 1, .cpp = { 4, 0, 0 }, .hsub = 1, .vsub = 1 },
 		{ .format = DRM_FORMAT_RGBX8888,	.depth = 24, .num_planes = 1, .cpp = { 4, 0, 0 }, .hsub = 1, .vsub = 1 },
 		{ .format = DRM_FORMAT_BGRX8888,	.depth = 24, .num_planes = 1, .cpp = { 4, 0, 0 }, .hsub = 1, .vsub = 1 },
+		{ .format = DRM_FORMAT_RGB565_A8,	.depth = 24, .num_planes = 2, .cpp = { 2, 1, 0 }, .hsub = 1, .vsub = 1 },
+		{ .format = DRM_FORMAT_BGR565_A8,	.depth = 24, .num_planes = 2, .cpp = { 2, 1, 0 }, .hsub = 1, .vsub = 1 },
 		{ .format = DRM_FORMAT_XRGB2101010,	.depth = 30, .num_planes = 1, .cpp = { 4, 0, 0 }, .hsub = 1, .vsub = 1 },
 		{ .format = DRM_FORMAT_XBGR2101010,	.depth = 30, .num_planes = 1, .cpp = { 4, 0, 0 }, .hsub = 1, .vsub = 1 },
 		{ .format = DRM_FORMAT_RGBX1010102,	.depth = 30, .num_planes = 1, .cpp = { 4, 0, 0 }, .hsub = 1, .vsub = 1 },
@@ -144,6 +146,12 @@ const struct drm_format_info *__drm_format_info(u32 format)
 		{ .format = DRM_FORMAT_ABGR8888,	.depth = 32, .num_planes = 1, .cpp = { 4, 0, 0 }, .hsub = 1, .vsub = 1 },
 		{ .format = DRM_FORMAT_RGBA8888,	.depth = 32, .num_planes = 1, .cpp = { 4, 0, 0 }, .hsub = 1, .vsub = 1 },
 		{ .format = DRM_FORMAT_BGRA8888,	.depth = 32, .num_planes = 1, .cpp = { 4, 0, 0 }, .hsub = 1, .vsub = 1 },
+		{ .format = DRM_FORMAT_RGB888_A8,	.depth = 32, .num_planes = 2, .cpp = { 3, 1, 0 }, .hsub = 1, .vsub = 1 },
+		{ .format = DRM_FORMAT_BGR888_A8,	.depth = 32, .num_planes = 2, .cpp = { 3, 1, 0 }, .hsub = 1, .vsub = 1 },
+		{ .format = DRM_FORMAT_XRGB8888_A8,	.depth = 32, .num_planes = 2, .cpp = { 4, 1, 0 }, .hsub = 1, .vsub = 1 },
+		{ .format = DRM_FORMAT_XBGR8888_A8,	.depth = 32, .num_planes = 2, .cpp = { 4, 1, 0 }, .hsub = 1, .vsub = 1 },
+		{ .format = DRM_FORMAT_RGBX8888_A8,	.depth = 32, .num_planes = 2, .cpp = { 4, 1, 0 }, .hsub = 1, .vsub = 1 },
+		{ .format = DRM_FORMAT_BGRX8888_A8,	.depth = 32, .num_planes = 2, .cpp = { 4, 1, 0 }, .hsub = 1, .vsub = 1 },
 		{ .format = DRM_FORMAT_YUV410,		.depth = 0,  .num_planes = 3, .cpp = { 1, 1, 1 }, .hsub = 4, .vsub = 4 },
 		{ .format = DRM_FORMAT_YVU410,		.depth = 0,  .num_planes = 3, .cpp = { 1, 1, 1 }, .hsub = 4, .vsub = 4 },
 		{ .format = DRM_FORMAT_YUV411,		.depth = 0,  .num_planes = 3, .cpp = { 1, 1, 1 }, .hsub = 4, .vsub = 1 },

+ 22 - 8
drivers/gpu/drm/imx/imx-drm-core.c

@@ -30,14 +30,10 @@
 #include <video/imx-ipu-v3.h>
 
 #include "imx-drm.h"
+#include "ipuv3-plane.h"
 
 #define MAX_CRTC	4
 
-struct imx_drm_component {
-	struct device_node *of_node;
-	struct list_head list;
-};
-
 struct imx_drm_device {
 	struct drm_device			*drm;
 	unsigned int				pipes;
@@ -109,6 +105,11 @@ static int imx_drm_atomic_check(struct drm_device *dev,
 	if (ret)
 		return ret;
 
+	/* Assign PRG/PRE channels and check if all constrains are satisfied. */
+	ret = ipu_planes_assign_pre(dev, state);
+	if (ret)
+		return ret;
+
 	return ret;
 }
 
@@ -122,6 +123,10 @@ static const struct drm_mode_config_funcs imx_drm_mode_config_funcs = {
 static void imx_drm_atomic_commit_tail(struct drm_atomic_state *state)
 {
 	struct drm_device *dev = state->dev;
+	struct drm_plane *plane;
+	struct drm_plane_state *old_plane_state;
+	bool plane_disabling = false;
+	int i;
 
 	drm_atomic_helper_commit_modeset_disables(dev, state);
 
@@ -131,11 +136,20 @@ static void imx_drm_atomic_commit_tail(struct drm_atomic_state *state)
 
 	drm_atomic_helper_commit_modeset_enables(dev, state);
 
-	drm_atomic_helper_commit_hw_done(state);
+	for_each_plane_in_state(state, plane, old_plane_state, i) {
+		if (drm_atomic_plane_disabling(old_plane_state, plane->state))
+			plane_disabling = true;
+	}
+
+	if (plane_disabling) {
+		drm_atomic_helper_wait_for_vblanks(dev, state);
 
-	drm_atomic_helper_wait_for_vblanks(dev, state);
+		for_each_plane_in_state(state, plane, old_plane_state, i)
+			ipu_plane_disable_deferred(plane);
 
-	drm_atomic_helper_cleanup_planes(dev, state);
+	}
+
+	drm_atomic_helper_commit_hw_done(state);
 }
 
 static const struct drm_mode_config_helper_funcs imx_drm_mode_config_helpers = {

+ 3 - 0
drivers/gpu/drm/imx/imx-drm.h

@@ -39,4 +39,7 @@ int imx_drm_encoder_parse_of(struct drm_device *drm,
 void imx_drm_connector_destroy(struct drm_connector *connector);
 void imx_drm_encoder_destroy(struct drm_encoder *encoder);
 
+int ipu_planes_assign_pre(struct drm_device *dev,
+			  struct drm_atomic_state *state);
+
 #endif /* _IMX_DRM_H_ */

+ 23 - 1
drivers/gpu/drm/imx/ipuv3-crtc.c

@@ -55,11 +55,32 @@ static void ipu_crtc_enable(struct drm_crtc *crtc)
 	struct ipu_crtc *ipu_crtc = to_ipu_crtc(crtc);
 	struct ipu_soc *ipu = dev_get_drvdata(ipu_crtc->dev->parent);
 
+	ipu_prg_enable(ipu);
 	ipu_dc_enable(ipu);
 	ipu_dc_enable_channel(ipu_crtc->dc);
 	ipu_di_enable(ipu_crtc->di);
 }
 
+static void ipu_crtc_disable_planes(struct ipu_crtc *ipu_crtc,
+				    struct drm_crtc_state *old_crtc_state)
+{
+	bool disable_partial = false;
+	bool disable_full = false;
+	struct drm_plane *plane;
+
+	drm_atomic_crtc_state_for_each_plane(plane, old_crtc_state) {
+		if (plane == &ipu_crtc->plane[0]->base)
+			disable_full = true;
+		if (&ipu_crtc->plane[1] && plane == &ipu_crtc->plane[1]->base)
+			disable_partial = true;
+	}
+
+	if (disable_partial)
+		ipu_plane_disable(ipu_crtc->plane[1], true);
+	if (disable_full)
+		ipu_plane_disable(ipu_crtc->plane[0], false);
+}
+
 static void ipu_crtc_atomic_disable(struct drm_crtc *crtc,
 				    struct drm_crtc_state *old_crtc_state)
 {
@@ -73,8 +94,9 @@ static void ipu_crtc_atomic_disable(struct drm_crtc *crtc,
 	 * attached IDMACs will be left in undefined state, possibly hanging
 	 * the IPU or even system.
 	 */
-	drm_atomic_helper_disable_planes_on_crtc(old_crtc_state, false);
+	ipu_crtc_disable_planes(ipu_crtc, old_crtc_state);
 	ipu_dc_disable(ipu);
+	ipu_prg_disable(ipu);
 
 	spin_lock_irq(&crtc->dev->event_lock);
 	if (crtc->state->event) {

+ 283 - 61
drivers/gpu/drm/imx/ipuv3-plane.c

@@ -23,6 +23,17 @@
 #include "video/imx-ipu-v3.h"
 #include "ipuv3-plane.h"
 
+struct ipu_plane_state {
+	struct drm_plane_state base;
+	bool use_pre;
+};
+
+static inline struct ipu_plane_state *
+to_ipu_plane_state(struct drm_plane_state *p)
+{
+	return container_of(p, struct ipu_plane_state, base);
+}
+
 static inline struct ipu_plane *to_ipu_plane(struct drm_plane *p)
 {
 	return container_of(p, struct ipu_plane, base);
@@ -57,6 +68,12 @@ static const uint32_t ipu_plane_formats[] = {
 	DRM_FORMAT_NV12,
 	DRM_FORMAT_NV16,
 	DRM_FORMAT_RGB565,
+	DRM_FORMAT_RGB565_A8,
+	DRM_FORMAT_BGR565_A8,
+	DRM_FORMAT_RGB888_A8,
+	DRM_FORMAT_BGR888_A8,
+	DRM_FORMAT_RGBX8888_A8,
+	DRM_FORMAT_BGRX8888_A8,
 };
 
 int ipu_plane_irq(struct ipu_plane *ipu_plane)
@@ -66,18 +83,18 @@ int ipu_plane_irq(struct ipu_plane *ipu_plane)
 }
 
 static inline unsigned long
-drm_plane_state_to_eba(struct drm_plane_state *state)
+drm_plane_state_to_eba(struct drm_plane_state *state, int plane)
 {
 	struct drm_framebuffer *fb = state->fb;
 	struct drm_gem_cma_object *cma_obj;
-	int x = state->src_x >> 16;
-	int y = state->src_y >> 16;
+	int x = state->src.x1 >> 16;
+	int y = state->src.y1 >> 16;
 
-	cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
+	cma_obj = drm_fb_cma_get_gem_obj(fb, plane);
 	BUG_ON(!cma_obj);
 
-	return cma_obj->paddr + fb->offsets[0] + fb->pitches[0] * y +
-	       fb->format->cpp[0] * x;
+	return cma_obj->paddr + fb->offsets[plane] + fb->pitches[plane] * y +
+	       fb->format->cpp[plane] * x;
 }
 
 static inline unsigned long
@@ -85,9 +102,9 @@ drm_plane_state_to_ubo(struct drm_plane_state *state)
 {
 	struct drm_framebuffer *fb = state->fb;
 	struct drm_gem_cma_object *cma_obj;
-	unsigned long eba = drm_plane_state_to_eba(state);
-	int x = state->src_x >> 16;
-	int y = state->src_y >> 16;
+	unsigned long eba = drm_plane_state_to_eba(state, 0);
+	int x = state->src.x1 >> 16;
+	int y = state->src.y1 >> 16;
 
 	cma_obj = drm_fb_cma_get_gem_obj(fb, 1);
 	BUG_ON(!cma_obj);
@@ -104,9 +121,9 @@ drm_plane_state_to_vbo(struct drm_plane_state *state)
 {
 	struct drm_framebuffer *fb = state->fb;
 	struct drm_gem_cma_object *cma_obj;
-	unsigned long eba = drm_plane_state_to_eba(state);
-	int x = state->src_x >> 16;
-	int y = state->src_y >> 16;
+	unsigned long eba = drm_plane_state_to_eba(state, 0);
+	int x = state->src.x1 >> 16;
+	int y = state->src.y1 >> 16;
 
 	cma_obj = drm_fb_cma_get_gem_obj(fb, 2);
 	BUG_ON(!cma_obj);
@@ -126,11 +143,14 @@ void ipu_plane_put_resources(struct ipu_plane *ipu_plane)
 		ipu_dmfc_put(ipu_plane->dmfc);
 	if (!IS_ERR_OR_NULL(ipu_plane->ipu_ch))
 		ipu_idmac_put(ipu_plane->ipu_ch);
+	if (!IS_ERR_OR_NULL(ipu_plane->alpha_ch))
+		ipu_idmac_put(ipu_plane->alpha_ch);
 }
 
 int ipu_plane_get_resources(struct ipu_plane *ipu_plane)
 {
 	int ret;
+	int alpha_ch;
 
 	ipu_plane->ipu_ch = ipu_idmac_get(ipu_plane->ipu, ipu_plane->dma);
 	if (IS_ERR(ipu_plane->ipu_ch)) {
@@ -139,6 +159,17 @@ int ipu_plane_get_resources(struct ipu_plane *ipu_plane)
 		return ret;
 	}
 
+	alpha_ch = ipu_channel_alpha_channel(ipu_plane->dma);
+	if (alpha_ch >= 0) {
+		ipu_plane->alpha_ch = ipu_idmac_get(ipu_plane->ipu, alpha_ch);
+		if (IS_ERR(ipu_plane->alpha_ch)) {
+			ret = PTR_ERR(ipu_plane->alpha_ch);
+			DRM_ERROR("failed to get alpha idmac channel %d: %d\n",
+				  alpha_ch, ret);
+			return ret;
+		}
+	}
+
 	ipu_plane->dmfc = ipu_dmfc_get(ipu_plane->ipu, ipu_plane->dma);
 	if (IS_ERR(ipu_plane->dmfc)) {
 		ret = PTR_ERR(ipu_plane->dmfc);
@@ -162,33 +193,61 @@ err_out:
 	return ret;
 }
 
+static bool ipu_plane_separate_alpha(struct ipu_plane *ipu_plane)
+{
+	switch (ipu_plane->base.state->fb->format->format) {
+	case DRM_FORMAT_RGB565_A8:
+	case DRM_FORMAT_BGR565_A8:
+	case DRM_FORMAT_RGB888_A8:
+	case DRM_FORMAT_BGR888_A8:
+	case DRM_FORMAT_RGBX8888_A8:
+	case DRM_FORMAT_BGRX8888_A8:
+		return true;
+	default:
+		return false;
+	}
+}
+
 static void ipu_plane_enable(struct ipu_plane *ipu_plane)
 {
 	if (ipu_plane->dp)
 		ipu_dp_enable(ipu_plane->ipu);
 	ipu_dmfc_enable_channel(ipu_plane->dmfc);
 	ipu_idmac_enable_channel(ipu_plane->ipu_ch);
+	if (ipu_plane_separate_alpha(ipu_plane))
+		ipu_idmac_enable_channel(ipu_plane->alpha_ch);
 	if (ipu_plane->dp)
 		ipu_dp_enable_channel(ipu_plane->dp);
 }
 
-static int ipu_disable_plane(struct drm_plane *plane)
+void ipu_plane_disable(struct ipu_plane *ipu_plane, bool disable_dp_channel)
 {
-	struct ipu_plane *ipu_plane = to_ipu_plane(plane);
-
 	DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__);
 
 	ipu_idmac_wait_busy(ipu_plane->ipu_ch, 50);
 
-	if (ipu_plane->dp)
-		ipu_dp_disable_channel(ipu_plane->dp);
+	if (ipu_plane->dp && disable_dp_channel)
+		ipu_dp_disable_channel(ipu_plane->dp, false);
 	ipu_idmac_disable_channel(ipu_plane->ipu_ch);
+	if (ipu_plane->alpha_ch)
+		ipu_idmac_disable_channel(ipu_plane->alpha_ch);
 	ipu_dmfc_disable_channel(ipu_plane->dmfc);
 	if (ipu_plane->dp)
 		ipu_dp_disable(ipu_plane->ipu);
+	if (ipu_prg_present(ipu_plane->ipu))
+		ipu_prg_channel_disable(ipu_plane->ipu_ch);
+}
 
-	return 0;
+void ipu_plane_disable_deferred(struct drm_plane *plane)
+{
+	struct ipu_plane *ipu_plane = to_ipu_plane(plane);
+
+	if (ipu_plane->disabling) {
+		ipu_plane->disabling = false;
+		ipu_plane_disable(ipu_plane, false);
+	}
 }
+EXPORT_SYMBOL_GPL(ipu_plane_disable_deferred);
 
 static void ipu_plane_destroy(struct drm_plane *plane)
 {
@@ -200,13 +259,56 @@ static void ipu_plane_destroy(struct drm_plane *plane)
 	kfree(ipu_plane);
 }
 
+void ipu_plane_state_reset(struct drm_plane *plane)
+{
+	struct ipu_plane_state *ipu_state;
+
+	if (plane->state) {
+		ipu_state = to_ipu_plane_state(plane->state);
+		__drm_atomic_helper_plane_destroy_state(plane->state);
+		kfree(ipu_state);
+	}
+
+	ipu_state = kzalloc(sizeof(*ipu_state), GFP_KERNEL);
+
+	if (ipu_state) {
+		ipu_state->base.plane = plane;
+		ipu_state->base.rotation = DRM_ROTATE_0;
+	}
+
+	plane->state = &ipu_state->base;
+}
+
+struct drm_plane_state *ipu_plane_duplicate_state(struct drm_plane *plane)
+{
+	struct ipu_plane_state *state;
+
+	if (WARN_ON(!plane->state))
+		return NULL;
+
+	state = kmalloc(sizeof(*state), GFP_KERNEL);
+	if (state)
+		__drm_atomic_helper_plane_duplicate_state(plane, &state->base);
+
+	return &state->base;
+}
+
+void ipu_plane_destroy_state(struct drm_plane *plane,
+			     struct drm_plane_state *state)
+{
+	struct ipu_plane_state *ipu_state = to_ipu_plane_state(state);
+
+	__drm_atomic_helper_plane_destroy_state(state);
+	kfree(ipu_state);
+}
+
 static const struct drm_plane_funcs ipu_plane_funcs = {
 	.update_plane	= drm_atomic_helper_update_plane,
 	.disable_plane	= drm_atomic_helper_disable_plane,
 	.destroy	= ipu_plane_destroy,
-	.reset		= drm_atomic_helper_plane_reset,
-	.atomic_duplicate_state	= drm_atomic_helper_plane_duplicate_state,
-	.atomic_destroy_state	= drm_atomic_helper_plane_destroy_state,
+	.reset		= ipu_plane_state_reset,
+	.atomic_duplicate_state	= ipu_plane_duplicate_state,
+	.atomic_destroy_state	= ipu_plane_destroy_state,
 };
 
 static int ipu_plane_atomic_check(struct drm_plane *plane,
@@ -217,8 +319,11 @@ static int ipu_plane_atomic_check(struct drm_plane *plane,
 	struct device *dev = plane->dev->dev;
 	struct drm_framebuffer *fb = state->fb;
 	struct drm_framebuffer *old_fb = old_state->fb;
-	unsigned long eba, ubo, vbo, old_ubo, old_vbo;
+	unsigned long eba, ubo, vbo, old_ubo, old_vbo, alpha_eba;
+	bool can_position = (plane->type == DRM_PLANE_TYPE_OVERLAY);
+	struct drm_rect clip;
 	int hsub, vsub;
+	int ret;
 
 	/* Ok to disable */
 	if (!fb)
@@ -232,44 +337,35 @@ static int ipu_plane_atomic_check(struct drm_plane *plane,
 	if (WARN_ON(!crtc_state))
 		return -EINVAL;
 
+	clip.x1 = 0;
+	clip.y1 = 0;
+	clip.x2 = crtc_state->adjusted_mode.hdisplay;
+	clip.y2 = crtc_state->adjusted_mode.vdisplay;
+	ret = drm_plane_helper_check_state(state, &clip,
+					   DRM_PLANE_HELPER_NO_SCALING,
+					   DRM_PLANE_HELPER_NO_SCALING,
+					   can_position, true);
+	if (ret)
+		return ret;
+
 	/* CRTC should be enabled */
 	if (!crtc_state->enable)
 		return -EINVAL;
 
-	/* no scaling */
-	if (state->src_w >> 16 != state->crtc_w ||
-	    state->src_h >> 16 != state->crtc_h)
-		return -EINVAL;
-
 	switch (plane->type) {
 	case DRM_PLANE_TYPE_PRIMARY:
-		/* full plane doesn't support partial off screen */
-		if (state->crtc_x || state->crtc_y ||
-		    state->crtc_w != crtc_state->adjusted_mode.hdisplay ||
-		    state->crtc_h != crtc_state->adjusted_mode.vdisplay)
-			return -EINVAL;
-
 		/* full plane minimum width is 13 pixels */
-		if (state->crtc_w < 13)
+		if (drm_rect_width(&state->dst) < 13)
 			return -EINVAL;
 		break;
 	case DRM_PLANE_TYPE_OVERLAY:
-		if (state->crtc_x < 0 || state->crtc_y < 0)
-			return -EINVAL;
-
-		if (state->crtc_x + state->crtc_w >
-		    crtc_state->adjusted_mode.hdisplay)
-			return -EINVAL;
-		if (state->crtc_y + state->crtc_h >
-		    crtc_state->adjusted_mode.vdisplay)
-			return -EINVAL;
 		break;
 	default:
-		dev_warn(dev, "Unsupported plane type\n");
+		dev_warn(dev, "Unsupported plane type %d\n", plane->type);
 		return -EINVAL;
 	}
 
-	if (state->crtc_h < 2)
+	if (drm_rect_height(&state->dst) < 2)
 		return -EINVAL;
 
 	/*
@@ -279,12 +375,13 @@ static int ipu_plane_atomic_check(struct drm_plane *plane,
 	 * callback.  The planes will be reenabled in plane's ->atomic_update
 	 * callback.
 	 */
-	if (old_fb && (state->src_w != old_state->src_w ||
-			      state->src_h != old_state->src_h ||
-			      fb->format != old_fb->format))
+	if (old_fb &&
+	    (drm_rect_width(&state->dst) != drm_rect_width(&old_state->dst) ||
+	     drm_rect_height(&state->dst) != drm_rect_height(&old_state->dst) ||
+	     fb->format != old_fb->format))
 		crtc_state->mode_changed = true;
 
-	eba = drm_plane_state_to_eba(state);
+	eba = drm_plane_state_to_eba(state, 0);
 
 	if (eba & 0x7)
 		return -EINVAL;
@@ -350,9 +447,26 @@ static int ipu_plane_atomic_check(struct drm_plane *plane,
 		 */
 		hsub = drm_format_horz_chroma_subsampling(fb->format->format);
 		vsub = drm_format_vert_chroma_subsampling(fb->format->format);
-		if (((state->src_x >> 16) & (hsub - 1)) ||
-		    ((state->src_y >> 16) & (vsub - 1)))
+		if (((state->src.x1 >> 16) & (hsub - 1)) ||
+		    ((state->src.y1 >> 16) & (vsub - 1)))
 			return -EINVAL;
+		break;
+	case DRM_FORMAT_RGB565_A8:
+	case DRM_FORMAT_BGR565_A8:
+	case DRM_FORMAT_RGB888_A8:
+	case DRM_FORMAT_BGR888_A8:
+	case DRM_FORMAT_RGBX8888_A8:
+	case DRM_FORMAT_BGRX8888_A8:
+		alpha_eba = drm_plane_state_to_eba(state, 1);
+		if (alpha_eba & 0x7)
+			return -EINVAL;
+
+		if (fb->pitches[1] < 1 || fb->pitches[1] > 16384)
+			return -EINVAL;
+
+		if (old_fb && old_fb->pitches[1] != fb->pitches[1])
+			crtc_state->mode_changed = true;
+		break;
 	}
 
 	return 0;
@@ -361,7 +475,25 @@ static int ipu_plane_atomic_check(struct drm_plane *plane,
 static void ipu_plane_atomic_disable(struct drm_plane *plane,
 				     struct drm_plane_state *old_state)
 {
-	ipu_disable_plane(plane);
+	struct ipu_plane *ipu_plane = to_ipu_plane(plane);
+
+	if (ipu_plane->dp)
+		ipu_dp_disable_channel(ipu_plane->dp, true);
+	ipu_plane->disabling = true;
+}
+
+static int ipu_chan_assign_axi_id(int ipu_chan)
+{
+	switch (ipu_chan) {
+	case IPUV3_CHANNEL_MEM_BG_SYNC:
+		return 1;
+	case IPUV3_CHANNEL_MEM_FG_SYNC:
+		return 2;
+	case IPUV3_CHANNEL_MEM_DC_SYNC:
+		return 3;
+	default:
+		return 0;
+	}
 }
 
 static void ipu_plane_atomic_update(struct drm_plane *plane,
@@ -369,18 +501,47 @@ static void ipu_plane_atomic_update(struct drm_plane *plane,
 {
 	struct ipu_plane *ipu_plane = to_ipu_plane(plane);
 	struct drm_plane_state *state = plane->state;
+	struct ipu_plane_state *ipu_state = to_ipu_plane_state(state);
 	struct drm_crtc_state *crtc_state = state->crtc->state;
 	struct drm_framebuffer *fb = state->fb;
+	struct drm_rect *dst = &state->dst;
 	unsigned long eba, ubo, vbo;
+	unsigned long alpha_eba = 0;
 	enum ipu_color_space ics;
+	unsigned int axi_id = 0;
 	int active;
 
-	eba = drm_plane_state_to_eba(state);
+	if (ipu_plane->dp_flow == IPU_DP_FLOW_SYNC_FG)
+		ipu_dp_set_window_pos(ipu_plane->dp, dst->x1, dst->y1);
+
+	eba = drm_plane_state_to_eba(state, 0);
+
+	/*
+	 * Configure PRG channel and attached PRE, this changes the EBA to an
+	 * internal SRAM location.
+	 */
+	if (ipu_state->use_pre) {
+		axi_id = ipu_chan_assign_axi_id(ipu_plane->dma);
+		ipu_prg_channel_configure(ipu_plane->ipu_ch, axi_id,
+					  drm_rect_width(&state->src) >> 16,
+					  drm_rect_height(&state->src) >> 16,
+					  state->fb->pitches[0],
+					  state->fb->format->format, &eba);
+	}
 
 	if (old_state->fb && !drm_atomic_crtc_needs_modeset(crtc_state)) {
+		/* nothing to do if PRE is used */
+		if (ipu_state->use_pre)
+			return;
 		active = ipu_idmac_get_current_buffer(ipu_plane->ipu_ch);
 		ipu_cpmem_set_buffer(ipu_plane->ipu_ch, !active, eba);
 		ipu_idmac_select_buffer(ipu_plane->ipu_ch, !active);
+		if (ipu_plane_separate_alpha(ipu_plane)) {
+			active = ipu_idmac_get_current_buffer(ipu_plane->alpha_ch);
+			ipu_cpmem_set_buffer(ipu_plane->alpha_ch, !active,
+					     alpha_eba);
+			ipu_idmac_select_buffer(ipu_plane->alpha_ch, !active);
+		}
 		return;
 	}
 
@@ -395,8 +556,6 @@ static void ipu_plane_atomic_update(struct drm_plane *plane,
 		ics = ipu_drm_fourcc_to_colorspace(state->fb->format->format);
 		ipu_dp_setup_channel(ipu_plane->dp, ics,
 					IPUV3_COLORSPACE_UNKNOWN);
-		ipu_dp_set_window_pos(ipu_plane->dp, state->crtc_x,
-					state->crtc_y);
 		/* Enable local alpha on partial plane */
 		switch (state->fb->format->format) {
 		case DRM_FORMAT_ARGB1555:
@@ -408,6 +567,12 @@ static void ipu_plane_atomic_update(struct drm_plane *plane,
 		case DRM_FORMAT_ABGR8888:
 		case DRM_FORMAT_RGBA8888:
 		case DRM_FORMAT_BGRA8888:
+		case DRM_FORMAT_RGB565_A8:
+		case DRM_FORMAT_BGR565_A8:
+		case DRM_FORMAT_RGB888_A8:
+		case DRM_FORMAT_BGR888_A8:
+		case DRM_FORMAT_RGBX8888_A8:
+		case DRM_FORMAT_BGRX8888_A8:
 			ipu_dp_set_global_alpha(ipu_plane->dp, false, 0, false);
 			break;
 		default:
@@ -416,15 +581,17 @@ static void ipu_plane_atomic_update(struct drm_plane *plane,
 		}
 	}
 
-	ipu_dmfc_config_wait4eot(ipu_plane->dmfc, state->crtc_w);
+	ipu_dmfc_config_wait4eot(ipu_plane->dmfc, drm_rect_width(dst));
 
 	ipu_cpmem_zero(ipu_plane->ipu_ch);
-	ipu_cpmem_set_resolution(ipu_plane->ipu_ch, state->src_w >> 16,
-					state->src_h >> 16);
+	ipu_cpmem_set_resolution(ipu_plane->ipu_ch,
+				 drm_rect_width(&state->src) >> 16,
+				 drm_rect_height(&state->src) >> 16);
 	ipu_cpmem_set_fmt(ipu_plane->ipu_ch, state->fb->format->format);
 	ipu_cpmem_set_high_priority(ipu_plane->ipu_ch);
 	ipu_idmac_set_double_buffer(ipu_plane->ipu_ch, 1);
 	ipu_cpmem_set_stride(ipu_plane->ipu_ch, state->fb->pitches[0]);
+	ipu_cpmem_set_axi_id(ipu_plane->ipu_ch, axi_id);
 	switch (fb->format->format) {
 	case DRM_FORMAT_YUV420:
 	case DRM_FORMAT_YVU420:
@@ -444,7 +611,7 @@ static void ipu_plane_atomic_update(struct drm_plane *plane,
 
 		dev_dbg(ipu_plane->base.dev->dev,
 			"phy = %lu %lu %lu, x = %d, y = %d", eba, ubo, vbo,
-			state->src_x >> 16, state->src_y >> 16);
+			state->src.x1 >> 16, state->src.y1 >> 16);
 		break;
 	case DRM_FORMAT_NV12:
 	case DRM_FORMAT_NV16:
@@ -455,11 +622,37 @@ static void ipu_plane_atomic_update(struct drm_plane *plane,
 
 		dev_dbg(ipu_plane->base.dev->dev,
 			"phy = %lu %lu, x = %d, y = %d", eba, ubo,
-			state->src_x >> 16, state->src_y >> 16);
+			state->src.x1 >> 16, state->src.y1 >> 16);
+		break;
+	case DRM_FORMAT_RGB565_A8:
+	case DRM_FORMAT_BGR565_A8:
+	case DRM_FORMAT_RGB888_A8:
+	case DRM_FORMAT_BGR888_A8:
+	case DRM_FORMAT_RGBX8888_A8:
+	case DRM_FORMAT_BGRX8888_A8:
+		alpha_eba = drm_plane_state_to_eba(state, 1);
+
+		dev_dbg(ipu_plane->base.dev->dev, "phys = %lu %lu, x = %d, y = %d",
+			eba, alpha_eba, state->src.x1 >> 16, state->src.y1 >> 16);
+
+		ipu_cpmem_set_burstsize(ipu_plane->ipu_ch, 16);
+
+		ipu_cpmem_zero(ipu_plane->alpha_ch);
+		ipu_cpmem_set_resolution(ipu_plane->alpha_ch,
+					 drm_rect_width(&state->src) >> 16,
+					 drm_rect_height(&state->src) >> 16);
+		ipu_cpmem_set_format_passthrough(ipu_plane->alpha_ch, 8);
+		ipu_cpmem_set_high_priority(ipu_plane->alpha_ch);
+		ipu_idmac_set_double_buffer(ipu_plane->alpha_ch, 1);
+		ipu_cpmem_set_stride(ipu_plane->alpha_ch,
+				     state->fb->pitches[1]);
+		ipu_cpmem_set_burstsize(ipu_plane->alpha_ch, 16);
+		ipu_cpmem_set_buffer(ipu_plane->alpha_ch, 0, alpha_eba);
+		ipu_cpmem_set_buffer(ipu_plane->alpha_ch, 1, alpha_eba);
 		break;
 	default:
 		dev_dbg(ipu_plane->base.dev->dev, "phys = %lu, x = %d, y = %d",
-			eba, state->src_x >> 16, state->src_y >> 16);
+			eba, state->src.x1 >> 16, state->src.y1 >> 16);
 		break;
 	}
 	ipu_cpmem_set_buffer(ipu_plane->ipu_ch, 0, eba);
@@ -474,6 +667,35 @@ static const struct drm_plane_helper_funcs ipu_plane_helper_funcs = {
 	.atomic_update = ipu_plane_atomic_update,
 };
 
+int ipu_planes_assign_pre(struct drm_device *dev,
+			  struct drm_atomic_state *state)
+{
+	struct drm_plane_state *plane_state;
+	struct drm_plane *plane;
+	int available_pres = ipu_prg_max_active_channels();
+	int i;
+
+	for_each_plane_in_state(state, plane, plane_state, i) {
+		struct ipu_plane_state *ipu_state =
+				to_ipu_plane_state(plane_state);
+		struct ipu_plane *ipu_plane = to_ipu_plane(plane);
+
+		if (ipu_prg_present(ipu_plane->ipu) && available_pres &&
+		    plane_state->fb &&
+		    ipu_prg_format_supported(ipu_plane->ipu,
+					     plane_state->fb->format->format,
+					     plane_state->fb->modifier)) {
+			ipu_state->use_pre = true;
+			available_pres--;
+		} else {
+			ipu_state->use_pre = false;
+		}
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(ipu_planes_assign_pre);
+
 struct ipu_plane *ipu_plane_init(struct drm_device *dev, struct ipu_soc *ipu,
 				 int dma, int dp, unsigned int possible_crtcs,
 				 enum drm_plane_type type)

+ 6 - 0
drivers/gpu/drm/imx/ipuv3-plane.h

@@ -18,11 +18,14 @@ struct ipu_plane {
 
 	struct ipu_soc		*ipu;
 	struct ipuv3_channel	*ipu_ch;
+	struct ipuv3_channel	*alpha_ch;
 	struct dmfc_channel	*dmfc;
 	struct ipu_dp		*dp;
 
 	int			dma;
 	int			dp_flow;
+
+	bool			disabling;
 };
 
 struct ipu_plane *ipu_plane_init(struct drm_device *dev, struct ipu_soc *ipu,
@@ -42,4 +45,7 @@ void ipu_plane_put_resources(struct ipu_plane *plane);
 
 int ipu_plane_irq(struct ipu_plane *plane);
 
+void ipu_plane_disable(struct ipu_plane *ipu_plane, bool disable_dp_channel);
+void ipu_plane_disable_deferred(struct drm_plane *plane);
+
 #endif

+ 1 - 1
drivers/gpu/ipu-v3/Makefile

@@ -2,4 +2,4 @@ obj-$(CONFIG_IMX_IPUV3_CORE) += imx-ipu-v3.o
 
 imx-ipu-v3-objs := ipu-common.o ipu-cpmem.o ipu-csi.o ipu-dc.o ipu-di.o \
 		ipu-dp.o ipu-dmfc.o ipu-ic.o ipu-image-convert.o \
-		ipu-smfc.o ipu-vdi.o
+		ipu-pre.o ipu-prg.o ipu-smfc.o ipu-vdi.o

+ 38 - 5
drivers/gpu/ipu-v3/ipu-common.c

@@ -51,15 +51,17 @@ int ipu_get_num(struct ipu_soc *ipu)
 }
 EXPORT_SYMBOL_GPL(ipu_get_num);
 
-void ipu_srm_dp_sync_update(struct ipu_soc *ipu)
+void ipu_srm_dp_update(struct ipu_soc *ipu, bool sync)
 {
 	u32 val;
 
 	val = ipu_cm_read(ipu, IPU_SRM_PRI2);
-	val |= 0x8;
+	val &= ~DP_S_SRM_MODE_MASK;
+	val |= sync ? DP_S_SRM_MODE_NEXT_FRAME :
+		      DP_S_SRM_MODE_NOW;
 	ipu_cm_write(ipu, val, IPU_SRM_PRI2);
 }
-EXPORT_SYMBOL_GPL(ipu_srm_dp_sync_update);
+EXPORT_SYMBOL_GPL(ipu_srm_dp_update);
 
 enum ipu_color_space ipu_drm_fourcc_to_colorspace(u32 drm_fourcc)
 {
@@ -81,6 +83,12 @@ enum ipu_color_space ipu_drm_fourcc_to_colorspace(u32 drm_fourcc)
 	case DRM_FORMAT_ABGR8888:
 	case DRM_FORMAT_RGBA8888:
 	case DRM_FORMAT_BGRA8888:
+	case DRM_FORMAT_RGB565_A8:
+	case DRM_FORMAT_BGR565_A8:
+	case DRM_FORMAT_RGB888_A8:
+	case DRM_FORMAT_BGR888_A8:
+	case DRM_FORMAT_RGBX8888_A8:
+	case DRM_FORMAT_BGRX8888_A8:
 		return IPUV3_COLORSPACE_RGB;
 	case DRM_FORMAT_YUYV:
 	case DRM_FORMAT_UYVY:
@@ -931,6 +939,7 @@ static const struct of_device_id imx_ipu_dt_ids[] = {
 	{ .compatible = "fsl,imx51-ipu", .data = &ipu_type_imx51, },
 	{ .compatible = "fsl,imx53-ipu", .data = &ipu_type_imx53, },
 	{ .compatible = "fsl,imx6q-ipu", .data = &ipu_type_imx6q, },
+	{ .compatible = "fsl,imx6qp-ipu", .data = &ipu_type_imx6q, },
 	{ /* sentinel */ }
 };
 MODULE_DEVICE_TABLE(of, imx_ipu_dt_ids);
@@ -1390,11 +1399,19 @@ static int ipu_probe(struct platform_device *pdev)
 	if (!ipu)
 		return -ENODEV;
 
+	ipu->id = of_alias_get_id(np, "ipu");
+
+	if (of_device_is_compatible(np, "fsl,imx6qp-ipu")) {
+		ipu->prg_priv = ipu_prg_lookup_by_phandle(&pdev->dev,
+							  "fsl,prg", ipu->id);
+		if (!ipu->prg_priv)
+			return -EPROBE_DEFER;
+	}
+
 	for (i = 0; i < 64; i++)
 		ipu->channel[i].ipu = ipu;
 	ipu->devtype = devtype;
 	ipu->ipu_type = devtype->type;
-	ipu->id = of_alias_get_id(np, "ipu");
 
 	spin_lock_init(&ipu->lock);
 	mutex_init(&ipu->channel_lock);
@@ -1520,7 +1537,23 @@ static struct platform_driver imx_ipu_driver = {
 	.remove = ipu_remove,
 };
 
-module_platform_driver(imx_ipu_driver);
+static struct platform_driver * const drivers[] = {
+	&ipu_pre_drv,
+	&ipu_prg_drv,
+	&imx_ipu_driver,
+};
+
+static int __init imx_ipu_init(void)
+{
+	return platform_register_drivers(drivers, ARRAY_SIZE(drivers));
+}
+module_init(imx_ipu_init);
+
+static void __exit imx_ipu_exit(void)
+{
+	platform_unregister_drivers(drivers, ARRAY_SIZE(drivers));
+}
+module_exit(imx_ipu_exit);
 
 MODULE_ALIAS("platform:imx-ipuv3");
 MODULE_DESCRIPTION("i.MX IPU v3 driver");

+ 76 - 2
drivers/gpu/ipu-v3/ipu-cpmem.c

@@ -537,6 +537,43 @@ static const struct ipu_rgb def_bgra_16 = {
 #define UV2_OFFSET(pix, x, y)	((pix->width * pix->height) +	\
 				 (pix->width * y) + (x))
 
+#define NUM_ALPHA_CHANNELS	7
+
+/* See Table 37-12. Alpha channels mapping. */
+static int ipu_channel_albm(int ch_num)
+{
+	switch (ch_num) {
+	case IPUV3_CHANNEL_G_MEM_IC_PRP_VF:	return 0;
+	case IPUV3_CHANNEL_G_MEM_IC_PP:		return 1;
+	case IPUV3_CHANNEL_MEM_FG_SYNC:		return 2;
+	case IPUV3_CHANNEL_MEM_FG_ASYNC:	return 3;
+	case IPUV3_CHANNEL_MEM_BG_SYNC:		return 4;
+	case IPUV3_CHANNEL_MEM_BG_ASYNC:	return 5;
+	case IPUV3_CHANNEL_MEM_VDI_PLANE1_COMB: return 6;
+	default:
+		return -EINVAL;
+	}
+}
+
+static void ipu_cpmem_set_separate_alpha(struct ipuv3_channel *ch)
+{
+	struct ipu_soc *ipu = ch->ipu;
+	int albm;
+	u32 val;
+
+	albm = ipu_channel_albm(ch->num);
+	if (albm < 0)
+		return;
+
+	ipu_ch_param_write_field(ch, IPU_FIELD_ALU, 1);
+	ipu_ch_param_write_field(ch, IPU_FIELD_ALBM, albm);
+	ipu_ch_param_write_field(ch, IPU_FIELD_CRE, 1);
+
+	val = ipu_idmac_read(ipu, IDMAC_SEP_ALPHA);
+	val |= BIT(ch->num);
+	ipu_idmac_write(ipu, val, IDMAC_SEP_ALPHA);
+}
+
 int ipu_cpmem_set_fmt(struct ipuv3_channel *ch, u32 drm_fourcc)
 {
 	switch (drm_fourcc) {
@@ -599,22 +636,28 @@ int ipu_cpmem_set_fmt(struct ipuv3_channel *ch, u32 drm_fourcc)
 		break;
 	case DRM_FORMAT_RGBA8888:
 	case DRM_FORMAT_RGBX8888:
+	case DRM_FORMAT_RGBX8888_A8:
 		ipu_cpmem_set_format_rgb(ch, &def_rgbx_32);
 		break;
 	case DRM_FORMAT_BGRA8888:
 	case DRM_FORMAT_BGRX8888:
+	case DRM_FORMAT_BGRX8888_A8:
 		ipu_cpmem_set_format_rgb(ch, &def_bgrx_32);
 		break;
 	case DRM_FORMAT_BGR888:
+	case DRM_FORMAT_BGR888_A8:
 		ipu_cpmem_set_format_rgb(ch, &def_bgr_24);
 		break;
 	case DRM_FORMAT_RGB888:
+	case DRM_FORMAT_RGB888_A8:
 		ipu_cpmem_set_format_rgb(ch, &def_rgb_24);
 		break;
 	case DRM_FORMAT_RGB565:
+	case DRM_FORMAT_RGB565_A8:
 		ipu_cpmem_set_format_rgb(ch, &def_rgb_16);
 		break;
 	case DRM_FORMAT_BGR565:
+	case DRM_FORMAT_BGR565_A8:
 		ipu_cpmem_set_format_rgb(ch, &def_bgr_16);
 		break;
 	case DRM_FORMAT_ARGB1555:
@@ -636,6 +679,20 @@ int ipu_cpmem_set_fmt(struct ipuv3_channel *ch, u32 drm_fourcc)
 		return -EINVAL;
 	}
 
+	switch (drm_fourcc) {
+	case DRM_FORMAT_RGB565_A8:
+	case DRM_FORMAT_BGR565_A8:
+	case DRM_FORMAT_RGB888_A8:
+	case DRM_FORMAT_BGR888_A8:
+	case DRM_FORMAT_RGBX8888_A8:
+	case DRM_FORMAT_BGRX8888_A8:
+		ipu_ch_param_write_field(ch, IPU_FIELD_WID3, 7);
+		ipu_cpmem_set_separate_alpha(ch);
+		break;
+	default:
+		break;
+	}
+
 	return 0;
 }
 EXPORT_SYMBOL_GPL(ipu_cpmem_set_fmt);
@@ -644,6 +701,7 @@ int ipu_cpmem_set_image(struct ipuv3_channel *ch, struct ipu_image *image)
 {
 	struct v4l2_pix_format *pix = &image->pix;
 	int offset, u_offset, v_offset;
+	int ret = 0;
 
 	pr_debug("%s: resolution: %dx%d stride: %d\n",
 		 __func__, pix->width, pix->height,
@@ -719,14 +777,30 @@ int ipu_cpmem_set_image(struct ipuv3_channel *ch, struct ipu_image *image)
 		offset = image->rect.left * 3 +
 			image->rect.top * pix->bytesperline;
 		break;
+	case V4L2_PIX_FMT_SBGGR8:
+	case V4L2_PIX_FMT_SGBRG8:
+	case V4L2_PIX_FMT_SGRBG8:
+	case V4L2_PIX_FMT_SRGGB8:
+		offset = image->rect.left + image->rect.top * pix->bytesperline;
+		break;
+	case V4L2_PIX_FMT_SBGGR16:
+	case V4L2_PIX_FMT_SGBRG16:
+	case V4L2_PIX_FMT_SGRBG16:
+	case V4L2_PIX_FMT_SRGGB16:
+		offset = image->rect.left * 2 +
+			 image->rect.top * pix->bytesperline;
+		break;
 	default:
-		return -EINVAL;
+		/* This should not happen */
+		WARN_ON(1);
+		offset = 0;
+		ret = -EINVAL;
 	}
 
 	ipu_cpmem_set_buffer(ch, 0, image->phys0 + offset);
 	ipu_cpmem_set_buffer(ch, 1, image->phys1 + offset);
 
-	return 0;
+	return ret;
 }
 EXPORT_SYMBOL_GPL(ipu_cpmem_set_image);
 

+ 4 - 57
drivers/gpu/ipu-v3/ipu-dc.c

@@ -112,8 +112,6 @@ struct ipu_dc_priv {
 	struct ipu_dc		channels[IPU_DC_NUM_CHANNELS];
 	struct mutex		mutex;
 	struct completion	comp;
-	int			dc_irq;
-	int			dp_irq;
 	int			use_count;
 };
 
@@ -262,47 +260,13 @@ void ipu_dc_enable_channel(struct ipu_dc *dc)
 }
 EXPORT_SYMBOL_GPL(ipu_dc_enable_channel);
 
-static irqreturn_t dc_irq_handler(int irq, void *dev_id)
-{
-	struct ipu_dc *dc = dev_id;
-	u32 reg;
-
-	reg = readl(dc->base + DC_WR_CH_CONF);
-	reg &= ~DC_WR_CH_CONF_PROG_TYPE_MASK;
-	writel(reg, dc->base + DC_WR_CH_CONF);
-
-	/* The Freescale BSP kernel clears DIx_COUNTER_RELEASE here */
-
-	complete(&dc->priv->comp);
-	return IRQ_HANDLED;
-}
-
 void ipu_dc_disable_channel(struct ipu_dc *dc)
 {
-	struct ipu_dc_priv *priv = dc->priv;
-	int irq;
-	unsigned long ret;
 	u32 val;
 
-	/* TODO: Handle MEM_FG_SYNC differently from MEM_BG_SYNC */
-	if (dc->chno == 1)
-		irq = priv->dc_irq;
-	else if (dc->chno == 5)
-		irq = priv->dp_irq;
-	else
-		return;
-
-	init_completion(&priv->comp);
-	enable_irq(irq);
-	ret = wait_for_completion_timeout(&priv->comp, msecs_to_jiffies(50));
-	disable_irq(irq);
-	if (ret == 0) {
-		dev_warn(priv->dev, "DC stop timeout after 50 ms\n");
-
-		val = readl(dc->base + DC_WR_CH_CONF);
-		val &= ~DC_WR_CH_CONF_PROG_TYPE_MASK;
-		writel(val, dc->base + DC_WR_CH_CONF);
-	}
+	val = readl(dc->base + DC_WR_CH_CONF);
+	val &= ~DC_WR_CH_CONF_PROG_TYPE_MASK;
+	writel(val, dc->base + DC_WR_CH_CONF);
 }
 EXPORT_SYMBOL_GPL(ipu_dc_disable_channel);
 
@@ -389,7 +353,7 @@ int ipu_dc_init(struct ipu_soc *ipu, struct device *dev,
 	struct ipu_dc_priv *priv;
 	static int channel_offsets[] = { 0, 0x1c, 0x38, 0x54, 0x58, 0x5c,
 		0x78, 0, 0x94, 0xb4};
-	int i, ret;
+	int i;
 
 	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
 	if (!priv)
@@ -410,23 +374,6 @@ int ipu_dc_init(struct ipu_soc *ipu, struct device *dev,
 		priv->channels[i].base = priv->dc_reg + channel_offsets[i];
 	}
 
-	priv->dc_irq = ipu_map_irq(ipu, IPU_IRQ_DC_FC_1);
-	if (!priv->dc_irq)
-		return -EINVAL;
-	ret = devm_request_irq(dev, priv->dc_irq, dc_irq_handler, 0, NULL,
-			       &priv->channels[1]);
-	if (ret < 0)
-		return ret;
-	disable_irq(priv->dc_irq);
-	priv->dp_irq = ipu_map_irq(ipu, IPU_IRQ_DP_SF_END);
-	if (!priv->dp_irq)
-		return -EINVAL;
-	ret = devm_request_irq(dev, priv->dp_irq, dc_irq_handler, 0, NULL,
-			       &priv->channels[5]);
-	if (ret < 0)
-		return ret;
-	disable_irq(priv->dp_irq);
-
 	writel(DC_WR_CH_CONF_WORD_SIZE_24 | DC_WR_CH_CONF_DISP_ID_PARALLEL(1) |
 			DC_WR_CH_CONF_PROG_DI_ID,
 			priv->channels[1].base + DC_WR_CH_CONF);

+ 6 - 9
drivers/gpu/ipu-v3/ipu-dp.c

@@ -112,7 +112,7 @@ int ipu_dp_set_global_alpha(struct ipu_dp *dp, bool enable,
 		writel(reg & ~DP_COM_CONF_GWAM, flow->base + DP_COM_CONF);
 	}
 
-	ipu_srm_dp_sync_update(priv->ipu);
+	ipu_srm_dp_update(priv->ipu, true);
 
 	mutex_unlock(&priv->mutex);
 
@@ -127,7 +127,7 @@ int ipu_dp_set_window_pos(struct ipu_dp *dp, u16 x_pos, u16 y_pos)
 
 	writel((x_pos << 16) | y_pos, flow->base + DP_FG_POS);
 
-	ipu_srm_dp_sync_update(priv->ipu);
+	ipu_srm_dp_update(priv->ipu, true);
 
 	return 0;
 }
@@ -207,7 +207,7 @@ int ipu_dp_setup_channel(struct ipu_dp *dp,
 					flow->out_cs, DP_COM_CONF_CSC_DEF_FG);
 	}
 
-	ipu_srm_dp_sync_update(priv->ipu);
+	ipu_srm_dp_update(priv->ipu, true);
 
 	mutex_unlock(&priv->mutex);
 
@@ -247,7 +247,7 @@ int ipu_dp_enable_channel(struct ipu_dp *dp)
 	reg |= DP_COM_CONF_FG_EN;
 	writel(reg, flow->base + DP_COM_CONF);
 
-	ipu_srm_dp_sync_update(priv->ipu);
+	ipu_srm_dp_update(priv->ipu, true);
 
 	mutex_unlock(&priv->mutex);
 
@@ -255,7 +255,7 @@ int ipu_dp_enable_channel(struct ipu_dp *dp)
 }
 EXPORT_SYMBOL_GPL(ipu_dp_enable_channel);
 
-void ipu_dp_disable_channel(struct ipu_dp *dp)
+void ipu_dp_disable_channel(struct ipu_dp *dp, bool sync)
 {
 	struct ipu_flow *flow = to_flow(dp);
 	struct ipu_dp_priv *priv = flow->priv;
@@ -275,10 +275,7 @@ void ipu_dp_disable_channel(struct ipu_dp *dp)
 	writel(reg, flow->base + DP_COM_CONF);
 
 	writel(0, flow->base + DP_FG_POS);
-	ipu_srm_dp_sync_update(priv->ipu);
-
-	if (ipu_idmac_channel_busy(priv->ipu, IPUV3_CHANNEL_MEM_BG_SYNC))
-		ipu_wait_interrupt(priv->ipu, IPU_IRQ_DP_SF_END, 50);
+	ipu_srm_dp_update(priv->ipu, sync);
 
 	mutex_unlock(&priv->mutex);
 }

+ 6 - 1
drivers/gpu/ipu-v3/ipu-image-convert.c

@@ -671,7 +671,12 @@ static void init_idmac_channel(struct ipu_image_convert_ctx *ctx,
 	ipu_ic_task_idma_init(chan->ic, channel, width, height,
 			      burst_size, rot_mode);
 
-	ipu_cpmem_set_axi_id(channel, 1);
+	/*
+	 * Setting a non-zero AXI ID collides with the PRG AXI snooping, so
+	 * only do this when there is no PRG present.
+	 */
+	if (!channel->ipu->prg_priv)
+		ipu_cpmem_set_axi_id(channel, 1);
 
 	ipu_idmac_set_double_buffer(channel, ctx->double_buffering);
 }

+ 289 - 0
drivers/gpu/ipu-v3/ipu-pre.c

@@ -0,0 +1,289 @@
+/*
+ * Copyright (c) 2017 Lucas Stach, Pengutronix
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ */
+
+#include <drm/drm_fourcc.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/genalloc.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <video/imx-ipu-v3.h>
+
+#include "ipu-prv.h"
+
+#define IPU_PRE_MAX_WIDTH	2048
+#define IPU_PRE_NUM_SCANLINES	8
+
+#define IPU_PRE_CTRL					0x000
+#define IPU_PRE_CTRL_SET				0x004
+#define  IPU_PRE_CTRL_ENABLE				(1 << 0)
+#define  IPU_PRE_CTRL_BLOCK_EN				(1 << 1)
+#define  IPU_PRE_CTRL_BLOCK_16				(1 << 2)
+#define  IPU_PRE_CTRL_SDW_UPDATE			(1 << 4)
+#define  IPU_PRE_CTRL_VFLIP				(1 << 5)
+#define  IPU_PRE_CTRL_SO				(1 << 6)
+#define  IPU_PRE_CTRL_INTERLACED_FIELD			(1 << 7)
+#define  IPU_PRE_CTRL_HANDSHAKE_EN			(1 << 8)
+#define  IPU_PRE_CTRL_HANDSHAKE_LINE_NUM(v)		((v & 0x3) << 9)
+#define  IPU_PRE_CTRL_HANDSHAKE_ABORT_SKIP_EN		(1 << 11)
+#define  IPU_PRE_CTRL_EN_REPEAT				(1 << 28)
+#define  IPU_PRE_CTRL_TPR_REST_SEL			(1 << 29)
+#define  IPU_PRE_CTRL_CLKGATE				(1 << 30)
+#define  IPU_PRE_CTRL_SFTRST				(1 << 31)
+
+#define IPU_PRE_CUR_BUF					0x030
+
+#define IPU_PRE_NEXT_BUF				0x040
+
+#define IPU_PRE_TPR_CTRL				0x070
+#define  IPU_PRE_TPR_CTRL_TILE_FORMAT(v)		((v & 0xff) << 0)
+#define  IPU_PRE_TPR_CTRL_TILE_FORMAT_MASK		0xff
+
+#define IPU_PRE_PREFETCH_ENG_CTRL			0x080
+#define  IPU_PRE_PREF_ENG_CTRL_PREFETCH_EN		(1 << 0)
+#define  IPU_PRE_PREF_ENG_CTRL_RD_NUM_BYTES(v)		((v & 0x7) << 1)
+#define  IPU_PRE_PREF_ENG_CTRL_INPUT_ACTIVE_BPP(v)	((v & 0x3) << 4)
+#define  IPU_PRE_PREF_ENG_CTRL_INPUT_PIXEL_FORMAT(v)	((v & 0x7) << 8)
+#define  IPU_PRE_PREF_ENG_CTRL_SHIFT_BYPASS		(1 << 11)
+#define  IPU_PRE_PREF_ENG_CTRL_FIELD_INVERSE		(1 << 12)
+#define  IPU_PRE_PREF_ENG_CTRL_PARTIAL_UV_SWAP		(1 << 14)
+#define  IPU_PRE_PREF_ENG_CTRL_TPR_COOR_OFFSET_EN	(1 << 15)
+
+#define IPU_PRE_PREFETCH_ENG_INPUT_SIZE			0x0a0
+#define  IPU_PRE_PREFETCH_ENG_INPUT_SIZE_WIDTH(v)	((v & 0xffff) << 0)
+#define  IPU_PRE_PREFETCH_ENG_INPUT_SIZE_HEIGHT(v)	((v & 0xffff) << 16)
+
+#define IPU_PRE_PREFETCH_ENG_PITCH			0x0d0
+#define  IPU_PRE_PREFETCH_ENG_PITCH_Y(v)		((v & 0xffff) << 0)
+#define  IPU_PRE_PREFETCH_ENG_PITCH_UV(v)		((v & 0xffff) << 16)
+
+#define IPU_PRE_STORE_ENG_CTRL				0x110
+#define  IPU_PRE_STORE_ENG_CTRL_STORE_EN		(1 << 0)
+#define  IPU_PRE_STORE_ENG_CTRL_WR_NUM_BYTES(v)		((v & 0x7) << 1)
+#define  IPU_PRE_STORE_ENG_CTRL_OUTPUT_ACTIVE_BPP(v)	((v & 0x3) << 4)
+
+#define IPU_PRE_STORE_ENG_SIZE				0x130
+#define  IPU_PRE_STORE_ENG_SIZE_INPUT_WIDTH(v)		((v & 0xffff) << 0)
+#define  IPU_PRE_STORE_ENG_SIZE_INPUT_HEIGHT(v)		((v & 0xffff) << 16)
+
+#define IPU_PRE_STORE_ENG_PITCH				0x140
+#define  IPU_PRE_STORE_ENG_PITCH_OUT_PITCH(v)		((v & 0xffff) << 0)
+
+#define IPU_PRE_STORE_ENG_ADDR				0x150
+
+struct ipu_pre {
+	struct list_head	list;
+	struct device		*dev;
+
+	void __iomem		*regs;
+	struct clk		*clk_axi;
+	struct gen_pool		*iram;
+
+	dma_addr_t		buffer_paddr;
+	void			*buffer_virt;
+	bool			in_use;
+};
+
+static DEFINE_MUTEX(ipu_pre_list_mutex);
+static LIST_HEAD(ipu_pre_list);
+static int available_pres;
+
+int ipu_pre_get_available_count(void)
+{
+	return available_pres;
+}
+
+struct ipu_pre *
+ipu_pre_lookup_by_phandle(struct device *dev, const char *name, int index)
+{
+	struct device_node *pre_node = of_parse_phandle(dev->of_node,
+							name, index);
+	struct ipu_pre *pre;
+
+	mutex_lock(&ipu_pre_list_mutex);
+	list_for_each_entry(pre, &ipu_pre_list, list) {
+		if (pre_node == pre->dev->of_node) {
+			mutex_unlock(&ipu_pre_list_mutex);
+			device_link_add(dev, pre->dev, DL_FLAG_AUTOREMOVE);
+			return pre;
+		}
+	}
+	mutex_unlock(&ipu_pre_list_mutex);
+
+	return NULL;
+}
+
+int ipu_pre_get(struct ipu_pre *pre)
+{
+	u32 val;
+
+	if (pre->in_use)
+		return -EBUSY;
+
+	clk_prepare_enable(pre->clk_axi);
+
+	/* first get the engine out of reset and remove clock gating */
+	writel(0, pre->regs + IPU_PRE_CTRL);
+
+	/* init defaults that should be applied to all streams */
+	val = IPU_PRE_CTRL_HANDSHAKE_ABORT_SKIP_EN |
+	      IPU_PRE_CTRL_HANDSHAKE_EN |
+	      IPU_PRE_CTRL_TPR_REST_SEL |
+	      IPU_PRE_CTRL_BLOCK_16 | IPU_PRE_CTRL_SDW_UPDATE;
+	writel(val, pre->regs + IPU_PRE_CTRL);
+
+	pre->in_use = true;
+	return 0;
+}
+
+void ipu_pre_put(struct ipu_pre *pre)
+{
+	u32 val;
+
+	val = IPU_PRE_CTRL_SFTRST | IPU_PRE_CTRL_CLKGATE;
+	writel(val, pre->regs + IPU_PRE_CTRL);
+
+	clk_disable_unprepare(pre->clk_axi);
+
+	pre->in_use = false;
+}
+
+void ipu_pre_configure(struct ipu_pre *pre, unsigned int width,
+		       unsigned int height, unsigned int stride, u32 format,
+		       unsigned int bufaddr)
+{
+	const struct drm_format_info *info = drm_format_info(format);
+	u32 active_bpp = info->cpp[0] >> 1;
+	u32 val;
+
+	writel(bufaddr, pre->regs + IPU_PRE_CUR_BUF);
+	writel(bufaddr, pre->regs + IPU_PRE_NEXT_BUF);
+
+	val = IPU_PRE_PREF_ENG_CTRL_INPUT_PIXEL_FORMAT(0) |
+	      IPU_PRE_PREF_ENG_CTRL_INPUT_ACTIVE_BPP(active_bpp) |
+	      IPU_PRE_PREF_ENG_CTRL_RD_NUM_BYTES(4) |
+	      IPU_PRE_PREF_ENG_CTRL_SHIFT_BYPASS |
+	      IPU_PRE_PREF_ENG_CTRL_PREFETCH_EN;
+	writel(val, pre->regs + IPU_PRE_PREFETCH_ENG_CTRL);
+
+	val = IPU_PRE_PREFETCH_ENG_INPUT_SIZE_WIDTH(width) |
+	      IPU_PRE_PREFETCH_ENG_INPUT_SIZE_HEIGHT(height);
+	writel(val, pre->regs + IPU_PRE_PREFETCH_ENG_INPUT_SIZE);
+
+	val = IPU_PRE_PREFETCH_ENG_PITCH_Y(stride);
+	writel(val, pre->regs + IPU_PRE_PREFETCH_ENG_PITCH);
+
+	val = IPU_PRE_STORE_ENG_CTRL_OUTPUT_ACTIVE_BPP(active_bpp) |
+	      IPU_PRE_STORE_ENG_CTRL_WR_NUM_BYTES(4) |
+	      IPU_PRE_STORE_ENG_CTRL_STORE_EN;
+	writel(val, pre->regs + IPU_PRE_STORE_ENG_CTRL);
+
+	val = IPU_PRE_STORE_ENG_SIZE_INPUT_WIDTH(width) |
+	      IPU_PRE_STORE_ENG_SIZE_INPUT_HEIGHT(height);
+	writel(val, pre->regs + IPU_PRE_STORE_ENG_SIZE);
+
+	val = IPU_PRE_STORE_ENG_PITCH_OUT_PITCH(stride);
+	writel(val, pre->regs + IPU_PRE_STORE_ENG_PITCH);
+
+	writel(pre->buffer_paddr, pre->regs + IPU_PRE_STORE_ENG_ADDR);
+
+	val = readl(pre->regs + IPU_PRE_CTRL);
+	val |= IPU_PRE_CTRL_EN_REPEAT | IPU_PRE_CTRL_ENABLE |
+	       IPU_PRE_CTRL_SDW_UPDATE;
+	writel(val, pre->regs + IPU_PRE_CTRL);
+}
+
+void ipu_pre_update(struct ipu_pre *pre, unsigned int bufaddr)
+{
+	writel(bufaddr, pre->regs + IPU_PRE_NEXT_BUF);
+	writel(IPU_PRE_CTRL_SDW_UPDATE, pre->regs + IPU_PRE_CTRL_SET);
+}
+
+u32 ipu_pre_get_baddr(struct ipu_pre *pre)
+{
+	return (u32)pre->buffer_paddr;
+}
+
+static int ipu_pre_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct resource *res;
+	struct ipu_pre *pre;
+
+	pre = devm_kzalloc(dev, sizeof(*pre), GFP_KERNEL);
+	if (!pre)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	pre->regs = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(pre->regs))
+		return PTR_ERR(pre->regs);
+
+	pre->clk_axi = devm_clk_get(dev, "axi");
+	if (IS_ERR(pre->clk_axi))
+		return PTR_ERR(pre->clk_axi);
+
+	pre->iram = of_gen_pool_get(dev->of_node, "fsl,iram", 0);
+	if (!pre->iram)
+		return -EPROBE_DEFER;
+
+	/*
+	 * Allocate IRAM buffer with maximum size. This could be made dynamic,
+	 * but as there is no other user of this IRAM region and we can fit all
+	 * max sized buffers into it, there is no need yet.
+	 */
+	pre->buffer_virt = gen_pool_dma_alloc(pre->iram, IPU_PRE_MAX_WIDTH *
+					      IPU_PRE_NUM_SCANLINES * 4,
+					      &pre->buffer_paddr);
+	if (!pre->buffer_virt)
+		return -ENOMEM;
+
+	pre->dev = dev;
+	platform_set_drvdata(pdev, pre);
+	mutex_lock(&ipu_pre_list_mutex);
+	list_add(&pre->list, &ipu_pre_list);
+	available_pres++;
+	mutex_unlock(&ipu_pre_list_mutex);
+
+	return 0;
+}
+
+static int ipu_pre_remove(struct platform_device *pdev)
+{
+	struct ipu_pre *pre = platform_get_drvdata(pdev);
+
+	mutex_lock(&ipu_pre_list_mutex);
+	list_del(&pre->list);
+	available_pres--;
+	mutex_unlock(&ipu_pre_list_mutex);
+
+	if (pre->buffer_virt)
+		gen_pool_free(pre->iram, (unsigned long)pre->buffer_virt,
+			      IPU_PRE_MAX_WIDTH * IPU_PRE_NUM_SCANLINES * 4);
+	return 0;
+}
+
+static const struct of_device_id ipu_pre_dt_ids[] = {
+	{ .compatible = "fsl,imx6qp-pre", },
+	{ /* sentinel */ },
+};
+
+struct platform_driver ipu_pre_drv = {
+	.probe		= ipu_pre_probe,
+	.remove		= ipu_pre_remove,
+	.driver		= {
+		.name	= "imx-ipu-pre",
+		.of_match_table = ipu_pre_dt_ids,
+	},
+};

+ 424 - 0
drivers/gpu/ipu-v3/ipu-prg.c

@@ -0,0 +1,424 @@
+/*
+ * Copyright (c) 2016-2017 Lucas Stach, Pengutronix
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ */
+
+#include <drm/drm_fourcc.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <video/imx-ipu-v3.h>
+
+#include "ipu-prv.h"
+
+#define IPU_PRG_CTL				0x00
+#define  IPU_PRG_CTL_BYPASS(i)			(1 << (0 + i))
+#define  IPU_PRG_CTL_SOFT_ARID_MASK		0x3
+#define  IPU_PRG_CTL_SOFT_ARID_SHIFT(i)		(8 + i * 2)
+#define  IPU_PRG_CTL_SOFT_ARID(i, v)		((v & 0x3) << (8 + 2 * i))
+#define  IPU_PRG_CTL_SO(i)			(1 << (16 + i))
+#define  IPU_PRG_CTL_VFLIP(i)			(1 << (19 + i))
+#define  IPU_PRG_CTL_BLOCK_MODE(i)		(1 << (22 + i))
+#define  IPU_PRG_CTL_CNT_LOAD_EN(i)		(1 << (25 + i))
+#define  IPU_PRG_CTL_SOFTRST			(1 << 30)
+#define  IPU_PRG_CTL_SHADOW_EN			(1 << 31)
+
+#define IPU_PRG_STATUS				0x04
+#define  IPU_PRG_STATUS_BUFFER0_READY(i)	(1 << (0 + i * 2))
+#define  IPU_PRG_STATUS_BUFFER1_READY(i)	(1 << (1 + i * 2))
+
+#define IPU_PRG_QOS				0x08
+#define  IPU_PRG_QOS_ARID_MASK			0xf
+#define  IPU_PRG_QOS_ARID_SHIFT(i)		(0 + i * 4)
+
+#define IPU_PRG_REG_UPDATE			0x0c
+#define  IPU_PRG_REG_UPDATE_REG_UPDATE		(1 << 0)
+
+#define IPU_PRG_STRIDE(i)			(0x10 + i * 0x4)
+#define  IPU_PRG_STRIDE_STRIDE_MASK		0x3fff
+
+#define IPU_PRG_CROP_LINE			0x1c
+
+#define IPU_PRG_THD				0x20
+
+#define IPU_PRG_BADDR(i)			(0x24 + i * 0x4)
+
+#define IPU_PRG_OFFSET(i)			(0x30 + i * 0x4)
+
+#define IPU_PRG_ILO(i)				(0x3c + i * 0x4)
+
+#define IPU_PRG_HEIGHT(i)			(0x48 + i * 0x4)
+#define  IPU_PRG_HEIGHT_PRE_HEIGHT_MASK		0xfff
+#define  IPU_PRG_HEIGHT_PRE_HEIGHT_SHIFT	0
+#define  IPU_PRG_HEIGHT_IPU_HEIGHT_MASK		0xfff
+#define  IPU_PRG_HEIGHT_IPU_HEIGHT_SHIFT	16
+
+struct ipu_prg_channel {
+	bool			enabled;
+	int			used_pre;
+};
+
+struct ipu_prg {
+	struct list_head	list;
+	struct device		*dev;
+	int			id;
+
+	void __iomem		*regs;
+	struct clk		*clk_ipg, *clk_axi;
+	struct regmap		*iomuxc_gpr;
+	struct ipu_pre		*pres[3];
+
+	struct ipu_prg_channel	chan[3];
+};
+
+static DEFINE_MUTEX(ipu_prg_list_mutex);
+static LIST_HEAD(ipu_prg_list);
+
+struct ipu_prg *
+ipu_prg_lookup_by_phandle(struct device *dev, const char *name, int ipu_id)
+{
+	struct device_node *prg_node = of_parse_phandle(dev->of_node,
+							name, 0);
+	struct ipu_prg *prg;
+
+	mutex_lock(&ipu_prg_list_mutex);
+	list_for_each_entry(prg, &ipu_prg_list, list) {
+		if (prg_node == prg->dev->of_node) {
+			mutex_unlock(&ipu_prg_list_mutex);
+			device_link_add(dev, prg->dev, DL_FLAG_AUTOREMOVE);
+			prg->id = ipu_id;
+			return prg;
+		}
+	}
+	mutex_unlock(&ipu_prg_list_mutex);
+
+	return NULL;
+}
+
+int ipu_prg_max_active_channels(void)
+{
+	return ipu_pre_get_available_count();
+}
+EXPORT_SYMBOL_GPL(ipu_prg_max_active_channels);
+
+bool ipu_prg_present(struct ipu_soc *ipu)
+{
+	if (ipu->prg_priv)
+		return true;
+
+	return false;
+}
+EXPORT_SYMBOL_GPL(ipu_prg_present);
+
+bool ipu_prg_format_supported(struct ipu_soc *ipu, uint32_t format,
+			      uint64_t modifier)
+{
+	const struct drm_format_info *info = drm_format_info(format);
+
+	if (info->num_planes != 1)
+		return false;
+
+	return true;
+}
+EXPORT_SYMBOL_GPL(ipu_prg_format_supported);
+
+int ipu_prg_enable(struct ipu_soc *ipu)
+{
+	struct ipu_prg *prg = ipu->prg_priv;
+	int ret;
+
+	if (!prg)
+		return 0;
+
+	ret = clk_prepare_enable(prg->clk_axi);
+	if (ret)
+		goto fail_disable_ipg;
+
+	return 0;
+
+fail_disable_ipg:
+	clk_disable_unprepare(prg->clk_ipg);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(ipu_prg_enable);
+
+void ipu_prg_disable(struct ipu_soc *ipu)
+{
+	struct ipu_prg *prg = ipu->prg_priv;
+
+	if (!prg)
+		return;
+
+	clk_disable_unprepare(prg->clk_axi);
+}
+EXPORT_SYMBOL_GPL(ipu_prg_disable);
+
+/*
+ * The channel configuartion functions below are not thread safe, as they
+ * must be only called from the atomic commit path in the DRM driver, which
+ * is properly serialized.
+ */
+static int ipu_prg_ipu_to_prg_chan(int ipu_chan)
+{
+	/*
+	 * This isn't clearly documented in the RM, but IPU to PRG channel
+	 * assignment is fixed, as only with this mapping the control signals
+	 * match up.
+	 */
+	switch (ipu_chan) {
+	case IPUV3_CHANNEL_MEM_BG_SYNC:
+		return 0;
+	case IPUV3_CHANNEL_MEM_FG_SYNC:
+		return 1;
+	case IPUV3_CHANNEL_MEM_DC_SYNC:
+		return 2;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ipu_prg_get_pre(struct ipu_prg *prg, int prg_chan)
+{
+	int i, ret;
+
+	/* channel 0 is special as it is hardwired to one of the PREs */
+	if (prg_chan == 0) {
+		ret = ipu_pre_get(prg->pres[0]);
+		if (ret)
+			goto fail;
+		prg->chan[prg_chan].used_pre = 0;
+		return 0;
+	}
+
+	for (i = 1; i < 3; i++) {
+		ret = ipu_pre_get(prg->pres[i]);
+		if (!ret) {
+			u32 val, mux;
+			int shift;
+
+			prg->chan[prg_chan].used_pre = i;
+
+			/* configure the PRE to PRG channel mux */
+			shift = (i == 1) ? 12 : 14;
+			mux = (prg->id << 1) | (prg_chan - 1);
+			regmap_update_bits(prg->iomuxc_gpr, IOMUXC_GPR5,
+					   0x3 << shift, mux << shift);
+
+			/* check other mux, must not point to same channel */
+			shift = (i == 1) ? 14 : 12;
+			regmap_read(prg->iomuxc_gpr, IOMUXC_GPR5, &val);
+			if (((val >> shift) & 0x3) == mux) {
+				regmap_update_bits(prg->iomuxc_gpr, IOMUXC_GPR5,
+						   0x3 << shift,
+						   (mux ^ 0x1) << shift);
+			}
+
+			return 0;
+		}
+	}
+
+fail:
+	dev_err(prg->dev, "could not get PRE for PRG chan %d", prg_chan);
+	return ret;
+}
+
+static void ipu_prg_put_pre(struct ipu_prg *prg, int prg_chan)
+{
+	struct ipu_prg_channel *chan = &prg->chan[prg_chan];
+
+	ipu_pre_put(prg->pres[chan->used_pre]);
+	chan->used_pre = -1;
+}
+
+void ipu_prg_channel_disable(struct ipuv3_channel *ipu_chan)
+{
+	int prg_chan = ipu_prg_ipu_to_prg_chan(ipu_chan->num);
+	struct ipu_prg *prg = ipu_chan->ipu->prg_priv;
+	struct ipu_prg_channel *chan = &prg->chan[prg_chan];
+	u32 val;
+
+	if (!chan->enabled || prg_chan < 0)
+		return;
+
+	clk_prepare_enable(prg->clk_ipg);
+
+	val = readl(prg->regs + IPU_PRG_CTL);
+	val |= IPU_PRG_CTL_BYPASS(prg_chan);
+	writel(val, prg->regs + IPU_PRG_CTL);
+
+	val = IPU_PRG_REG_UPDATE_REG_UPDATE;
+	writel(val, prg->regs + IPU_PRG_REG_UPDATE);
+
+	clk_disable_unprepare(prg->clk_ipg);
+
+	ipu_prg_put_pre(prg, prg_chan);
+
+	chan->enabled = false;
+}
+EXPORT_SYMBOL_GPL(ipu_prg_channel_disable);
+
+int ipu_prg_channel_configure(struct ipuv3_channel *ipu_chan,
+			      unsigned int axi_id, unsigned int width,
+			      unsigned int height, unsigned int stride,
+			      u32 format, unsigned long *eba)
+{
+	int prg_chan = ipu_prg_ipu_to_prg_chan(ipu_chan->num);
+	struct ipu_prg *prg = ipu_chan->ipu->prg_priv;
+	struct ipu_prg_channel *chan = &prg->chan[prg_chan];
+	u32 val;
+	int ret;
+
+	if (prg_chan < 0)
+		return prg_chan;
+
+	if (chan->enabled) {
+		ipu_pre_update(prg->pres[chan->used_pre], *eba);
+		return 0;
+	}
+
+	ret = ipu_prg_get_pre(prg, prg_chan);
+	if (ret)
+		return ret;
+
+	ipu_pre_configure(prg->pres[chan->used_pre],
+			  width, height, stride, format, *eba);
+
+
+	ret = clk_prepare_enable(prg->clk_ipg);
+	if (ret) {
+		ipu_prg_put_pre(prg, prg_chan);
+		return ret;
+	}
+
+	val = (stride - 1) & IPU_PRG_STRIDE_STRIDE_MASK;
+	writel(val, prg->regs + IPU_PRG_STRIDE(prg_chan));
+
+	val = ((height & IPU_PRG_HEIGHT_PRE_HEIGHT_MASK) <<
+	       IPU_PRG_HEIGHT_PRE_HEIGHT_SHIFT) |
+	      ((height & IPU_PRG_HEIGHT_IPU_HEIGHT_MASK) <<
+	       IPU_PRG_HEIGHT_IPU_HEIGHT_SHIFT);
+	writel(val, prg->regs + IPU_PRG_HEIGHT(prg_chan));
+
+	val = ipu_pre_get_baddr(prg->pres[chan->used_pre]);
+	*eba = val;
+	writel(val, prg->regs + IPU_PRG_BADDR(prg_chan));
+
+	val = readl(prg->regs + IPU_PRG_CTL);
+	/* counter load enable */
+	val |= IPU_PRG_CTL_CNT_LOAD_EN(prg_chan);
+	/* config AXI ID */
+	val &= ~(IPU_PRG_CTL_SOFT_ARID_MASK <<
+		 IPU_PRG_CTL_SOFT_ARID_SHIFT(prg_chan));
+	val |= IPU_PRG_CTL_SOFT_ARID(prg_chan, axi_id);
+	/* enable channel */
+	val &= ~IPU_PRG_CTL_BYPASS(prg_chan);
+	writel(val, prg->regs + IPU_PRG_CTL);
+
+	val = IPU_PRG_REG_UPDATE_REG_UPDATE;
+	writel(val, prg->regs + IPU_PRG_REG_UPDATE);
+
+	clk_disable_unprepare(prg->clk_ipg);
+
+	chan->enabled = true;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(ipu_prg_channel_configure);
+
+static int ipu_prg_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct resource *res;
+	struct ipu_prg *prg;
+	u32 val;
+	int i, ret;
+
+	prg = devm_kzalloc(dev, sizeof(*prg), GFP_KERNEL);
+	if (!prg)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	prg->regs = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(prg->regs))
+		return PTR_ERR(prg->regs);
+
+
+	prg->clk_ipg = devm_clk_get(dev, "ipg");
+	if (IS_ERR(prg->clk_ipg))
+		return PTR_ERR(prg->clk_ipg);
+
+	prg->clk_axi = devm_clk_get(dev, "axi");
+	if (IS_ERR(prg->clk_axi))
+		return PTR_ERR(prg->clk_axi);
+
+	prg->iomuxc_gpr =
+		syscon_regmap_lookup_by_compatible("fsl,imx6q-iomuxc-gpr");
+	if (IS_ERR(prg->iomuxc_gpr))
+		return PTR_ERR(prg->iomuxc_gpr);
+
+	for (i = 0; i < 3; i++) {
+		prg->pres[i] = ipu_pre_lookup_by_phandle(dev, "fsl,pres", i);
+		if (!prg->pres[i])
+			return -EPROBE_DEFER;
+	}
+
+	ret = clk_prepare_enable(prg->clk_ipg);
+	if (ret)
+		return ret;
+
+	/* init to free running mode */
+	val = readl(prg->regs + IPU_PRG_CTL);
+	val |= IPU_PRG_CTL_SHADOW_EN;
+	writel(val, prg->regs + IPU_PRG_CTL);
+
+	/* disable address threshold */
+	writel(0xffffffff, prg->regs + IPU_PRG_THD);
+
+	clk_disable_unprepare(prg->clk_ipg);
+
+	prg->dev = dev;
+	platform_set_drvdata(pdev, prg);
+	mutex_lock(&ipu_prg_list_mutex);
+	list_add(&prg->list, &ipu_prg_list);
+	mutex_unlock(&ipu_prg_list_mutex);
+
+	return 0;
+}
+
+static int ipu_prg_remove(struct platform_device *pdev)
+{
+	struct ipu_prg *prg = platform_get_drvdata(pdev);
+
+	mutex_lock(&ipu_prg_list_mutex);
+	list_del(&prg->list);
+	mutex_unlock(&ipu_prg_list_mutex);
+
+	return 0;
+}
+
+static const struct of_device_id ipu_prg_dt_ids[] = {
+	{ .compatible = "fsl,imx6qp-prg", },
+	{ /* sentinel */ },
+};
+
+struct platform_driver ipu_prg_drv = {
+	.probe		= ipu_prg_probe,
+	.remove		= ipu_prg_remove,
+	.driver		= {
+		.name	= "imx-ipu-prg",
+		.of_match_table = ipu_prg_dt_ids,
+	},
+};

+ 26 - 1
drivers/gpu/ipu-v3/ipu-prv.h

@@ -75,6 +75,11 @@ struct ipu_soc;
 #define IPU_INT_CTRL(n)		IPU_CM_REG(0x003C + 4 * (n))
 #define IPU_INT_STAT(n)		IPU_CM_REG(0x0200 + 4 * (n))
 
+/* SRM_PRI2 */
+#define DP_S_SRM_MODE_MASK		(0x3 << 3)
+#define DP_S_SRM_MODE_NOW		(0x3 << 3)
+#define DP_S_SRM_MODE_NEXT_FRAME	(0x1 << 3)
+
 /* FS_PROC_FLOW1 */
 #define FS_PRPENC_ROT_SRC_SEL_MASK	(0xf << 0)
 #define FS_PRPENC_ROT_SRC_SEL_ENC		(0x7 << 0)
@@ -168,6 +173,8 @@ struct ipu_ic_priv;
 struct ipu_vdi;
 struct ipu_image_convert_priv;
 struct ipu_smfc_priv;
+struct ipu_pre;
+struct ipu_prg;
 
 struct ipu_devtype;
 
@@ -202,6 +209,7 @@ struct ipu_soc {
 	struct ipu_vdi          *vdi_priv;
 	struct ipu_image_convert_priv *image_convert_priv;
 	struct ipu_smfc_priv	*smfc_priv;
+	struct ipu_prg		*prg_priv;
 };
 
 static inline u32 ipu_idmac_read(struct ipu_soc *ipu, unsigned offset)
@@ -215,7 +223,7 @@ static inline void ipu_idmac_write(struct ipu_soc *ipu, u32 value,
 	writel(value, ipu->idmac_reg + offset);
 }
 
-void ipu_srm_dp_sync_update(struct ipu_soc *ipu);
+void ipu_srm_dp_update(struct ipu_soc *ipu, bool sync);
 
 int ipu_module_enable(struct ipu_soc *ipu, u32 mask);
 int ipu_module_disable(struct ipu_soc *ipu, u32 mask);
@@ -259,4 +267,21 @@ void ipu_cpmem_exit(struct ipu_soc *ipu);
 int ipu_smfc_init(struct ipu_soc *ipu, struct device *dev, unsigned long base);
 void ipu_smfc_exit(struct ipu_soc *ipu);
 
+struct ipu_pre *ipu_pre_lookup_by_phandle(struct device *dev, const char *name,
+					  int index);
+int ipu_pre_get_available_count(void);
+int ipu_pre_get(struct ipu_pre *pre);
+void ipu_pre_put(struct ipu_pre *pre);
+u32 ipu_pre_get_baddr(struct ipu_pre *pre);
+void ipu_pre_configure(struct ipu_pre *pre, unsigned int width,
+		       unsigned int height,
+		       unsigned int stride, u32 format, unsigned int bufaddr);
+void ipu_pre_update(struct ipu_pre *pre, unsigned int bufaddr);
+
+struct ipu_prg *ipu_prg_lookup_by_phandle(struct device *dev, const char *name,
+					  int ipu_id);
+
+extern struct platform_driver ipu_pre_drv;
+extern struct platform_driver ipu_prg_drv;
+
 #endif				/* __IPU_PRV_H__ */

+ 14 - 0
include/uapi/drm/drm_fourcc.h

@@ -113,6 +113,20 @@ extern "C" {
 
 #define DRM_FORMAT_AYUV		fourcc_code('A', 'Y', 'U', 'V') /* [31:0] A:Y:Cb:Cr 8:8:8:8 little endian */
 
+/*
+ * 2 plane RGB + A
+ * index 0 = RGB plane, same format as the corresponding non _A8 format has
+ * index 1 = A plane, [7:0] A
+ */
+#define DRM_FORMAT_XRGB8888_A8	fourcc_code('X', 'R', 'A', '8')
+#define DRM_FORMAT_XBGR8888_A8	fourcc_code('X', 'B', 'A', '8')
+#define DRM_FORMAT_RGBX8888_A8	fourcc_code('R', 'X', 'A', '8')
+#define DRM_FORMAT_BGRX8888_A8	fourcc_code('B', 'X', 'A', '8')
+#define DRM_FORMAT_RGB888_A8	fourcc_code('R', '8', 'A', '8')
+#define DRM_FORMAT_BGR888_A8	fourcc_code('B', '8', 'A', '8')
+#define DRM_FORMAT_RGB565_A8	fourcc_code('R', '5', 'A', '8')
+#define DRM_FORMAT_BGR565_A8	fourcc_code('B', '5', 'A', '8')
+
 /*
  * 2 plane YCbCr
  * index 0 = Y plane, [7:0] Y

+ 38 - 1
include/video/imx-ipu-v3.h

@@ -161,6 +161,28 @@ enum ipu_channel_irq {
 #define IPUV3_CHANNEL_MEM_BG_ASYNC_ALPHA	52
 #define IPUV3_NUM_CHANNELS			64
 
+static inline int ipu_channel_alpha_channel(int ch_num)
+{
+	switch (ch_num) {
+	case IPUV3_CHANNEL_G_MEM_IC_PRP_VF:
+		return IPUV3_CHANNEL_G_MEM_IC_PRP_VF_ALPHA;
+	case IPUV3_CHANNEL_G_MEM_IC_PP:
+		return IPUV3_CHANNEL_G_MEM_IC_PP_ALPHA;
+	case IPUV3_CHANNEL_MEM_FG_SYNC:
+		return IPUV3_CHANNEL_MEM_FG_SYNC_ALPHA;
+	case IPUV3_CHANNEL_MEM_FG_ASYNC:
+		return IPUV3_CHANNEL_MEM_FG_ASYNC_ALPHA;
+	case IPUV3_CHANNEL_MEM_BG_SYNC:
+		return IPUV3_CHANNEL_MEM_BG_SYNC_ALPHA;
+	case IPUV3_CHANNEL_MEM_BG_ASYNC:
+		return IPUV3_CHANNEL_MEM_BG_ASYNC_ALPHA;
+	case IPUV3_CHANNEL_MEM_VDI_PLANE1_COMB:
+		return IPUV3_CHANNEL_MEM_VDI_PLANE1_COMB_ALPHA;
+	default:
+		return -EINVAL;
+	}
+}
+
 int ipu_map_irq(struct ipu_soc *ipu, int irq);
 int ipu_idmac_channel_irq(struct ipu_soc *ipu, struct ipuv3_channel *channel,
 		enum ipu_channel_irq irq);
@@ -300,7 +322,7 @@ struct ipu_dp *ipu_dp_get(struct ipu_soc *ipu, unsigned int flow);
 void ipu_dp_put(struct ipu_dp *);
 int ipu_dp_enable(struct ipu_soc *ipu);
 int ipu_dp_enable_channel(struct ipu_dp *dp);
-void ipu_dp_disable_channel(struct ipu_dp *dp);
+void ipu_dp_disable_channel(struct ipu_dp *dp, bool sync);
 void ipu_dp_disable(struct ipu_soc *ipu);
 int ipu_dp_setup_channel(struct ipu_dp *dp,
 		enum ipu_color_space in, enum ipu_color_space out);
@@ -308,6 +330,21 @@ int ipu_dp_set_window_pos(struct ipu_dp *, u16 x_pos, u16 y_pos);
 int ipu_dp_set_global_alpha(struct ipu_dp *dp, bool enable, u8 alpha,
 		bool bg_chan);
 
+/*
+ * IPU Prefetch Resolve Gasket (prg) functions
+ */
+int ipu_prg_max_active_channels(void);
+bool ipu_prg_present(struct ipu_soc *ipu);
+bool ipu_prg_format_supported(struct ipu_soc *ipu, uint32_t format,
+			      uint64_t modifier);
+int ipu_prg_enable(struct ipu_soc *ipu);
+void ipu_prg_disable(struct ipu_soc *ipu);
+void ipu_prg_channel_disable(struct ipuv3_channel *ipu_chan);
+int ipu_prg_channel_configure(struct ipuv3_channel *ipu_chan,
+			      unsigned int axi_id,  unsigned int width,
+			      unsigned int height, unsigned int stride,
+			      u32 format, unsigned long *eba);
+
 /*
  * IPU CMOS Sensor Interface (csi) functions
  */