diff mbox series

drm/stm: ltdc: add support of horizontal & vertical mirroring

Message ID 20220603134547.593790-1-yannick.fertre@foss.st.com (mailing list archive)
State New, archived
Headers show
Series drm/stm: ltdc: add support of horizontal & vertical mirroring | expand

Commit Message

Yannick Fertre June 3, 2022, 1:45 p.m. UTC
Support of vertical & horizontal mirroring features thanks to
the plane rotation property.

Signed-off-by: Yannick Fertre <yannick.fertre@foss.st.com>
---
 drivers/gpu/drm/stm/ltdc.c | 163 ++++++++++++++++++++++++-------------
 drivers/gpu/drm/stm/ltdc.h |   1 +
 2 files changed, 108 insertions(+), 56 deletions(-)

Comments

Philippe CORNU June 27, 2022, 2:12 p.m. UTC | #1
On 6/3/22 15:45, Yannick Fertre wrote:
> Support of vertical & horizontal mirroring features thanks to
> the plane rotation property.
> 
> Signed-off-by: Yannick Fertre <yannick.fertre@foss.st.com>
> ---
>   drivers/gpu/drm/stm/ltdc.c | 163 ++++++++++++++++++++++++-------------
>   drivers/gpu/drm/stm/ltdc.h |   1 +
>   2 files changed, 108 insertions(+), 56 deletions(-)
> 
> diff --git a/drivers/gpu/drm/stm/ltdc.c b/drivers/gpu/drm/stm/ltdc.c
> index 00a6bc1b1d7c..ff2075dd9474 100644
> --- a/drivers/gpu/drm/stm/ltdc.c
> +++ b/drivers/gpu/drm/stm/ltdc.c
> @@ -180,6 +180,7 @@
>   #define LXCR_LEN	BIT(0)		/* Layer ENable */
>   #define LXCR_COLKEN	BIT(1)		/* Color Keying Enable */
>   #define LXCR_CLUTEN	BIT(4)		/* Color Look-Up Table ENable */
> +#define LXCR_HMEN	BIT(8)		/* Horizontal Mirroring ENable */
>   
>   #define LXWHPCR_WHSTPOS	GENMASK(11, 0)	/* Window Horizontal StarT POSition */
>   #define LXWHPCR_WHSPPOS	GENMASK(27, 16)	/* Window Horizontal StoP POSition */
> @@ -197,7 +198,7 @@
>   #define LXBFCR_BOR	GENMASK(18, 16) /* Blending ORder */
>   
>   #define LXCFBLR_CFBLL	GENMASK(12, 0)	/* Color Frame Buffer Line Length */
> -#define LXCFBLR_CFBP	GENMASK(28, 16)	/* Color Frame Buffer Pitch in bytes */
> +#define LXCFBLR_CFBP	GENMASK(31, 16) /* Color Frame Buffer Pitch in bytes */
>   
>   #define LXCFBLNR_CFBLN	GENMASK(10, 0)	/* Color Frame Buffer Line Number */
>   
> @@ -1237,7 +1238,8 @@ static void ltdc_plane_atomic_update(struct drm_plane *plane,
>   	u32 y0 = newstate->crtc_y;
>   	u32 y1 = newstate->crtc_y + newstate->crtc_h - 1;
>   	u32 src_x, src_y, src_w, src_h;
> -	u32 val, pitch_in_bytes, line_length, line_number, paddr, ahbp, avbp, bpcr;
> +	u32 val, pitch_in_bytes, line_length, line_number, ahbp, avbp, bpcr;
> +	u32 paddr, paddr1, paddr2;
>   	enum ltdc_pix_fmt pf;
>   
>   	if (!newstate->crtc || !fb) {
> @@ -1289,13 +1291,6 @@ static void ltdc_plane_atomic_update(struct drm_plane *plane,
>   	}
>   	regmap_write_bits(ldev->regmap, LTDC_L1PFCR + lofs, LXPFCR_PF, val);
>   
> -	/* Configures the color frame buffer pitch in bytes & line length */
> -	pitch_in_bytes = fb->pitches[0];
> -	line_length = fb->format->cpp[0] *
> -		      (x1 - x0 + 1) + (ldev->caps.bus_width >> 3) - 1;
> -	val = ((pitch_in_bytes << 16) | line_length);
> -	regmap_write_bits(ldev->regmap, LTDC_L1CFBLR + lofs, LXCFBLR_CFBLL | LXCFBLR_CFBP, val);
> -
>   	/* Specifies the constant alpha value */
>   	val = newstate->alpha >> 8;
>   	regmap_write_bits(ldev->regmap, LTDC_L1CACR + lofs, LXCACR_CONSTA, val);
> @@ -1319,76 +1314,115 @@ static void ltdc_plane_atomic_update(struct drm_plane *plane,
>   				  LXBFCR_BF2 | LXBFCR_BF1, val);
>   	}
>   
> -	/* Configures the frame buffer line number */
> -	line_number = y1 - y0 + 1;
> -	regmap_write_bits(ldev->regmap, LTDC_L1CFBLNR + lofs, LXCFBLNR_CFBLN, line_number);
> -
>   	/* Sets the FB address */
>   	paddr = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 0);
>   
> +	if (newstate->rotation & DRM_MODE_REFLECT_X)
> +		paddr += (fb->format->cpp[0] * (x1 - x0 + 1)) - 1;
> +
> +	if (newstate->rotation & DRM_MODE_REFLECT_Y)
> +		paddr += (fb->pitches[0] * (y1 - y0));
> +
>   	DRM_DEBUG_DRIVER("fb: phys 0x%08x", paddr);
>   	regmap_write(ldev->regmap, LTDC_L1CFBAR + lofs, paddr);
>   
> +	/* Configures the color frame buffer pitch in bytes & line length */
> +	line_length = fb->format->cpp[0] *
> +		      (x1 - x0 + 1) + (ldev->caps.bus_width >> 3) - 1;
> +
> +	if (newstate->rotation & DRM_MODE_REFLECT_Y)
> +		/* Compute negative value (signed on 16 bits) for the picth */
> +		pitch_in_bytes = 0x10000 - fb->pitches[0];
> +	else
> +		pitch_in_bytes = fb->pitches[0];
> +
> +	val = (pitch_in_bytes << 16) | line_length;
> +	regmap_write_bits(ldev->regmap, LTDC_L1CFBLR + lofs, LXCFBLR_CFBLL | LXCFBLR_CFBP, val);
> +
> +	/* Configures the frame buffer line number */
> +	line_number = y1 - y0 + 1;
> +	regmap_write_bits(ldev->regmap, LTDC_L1CFBLNR + lofs, LXCFBLNR_CFBLN, line_number);
> +
>   	if (ldev->caps.ycbcr_input) {
>   		if (fb->format->is_yuv) {
>   			switch (fb->format->format) {
>   			case DRM_FORMAT_NV12:
>   			case DRM_FORMAT_NV21:
> -			/* Configure the auxiliary frame buffer address 0 & 1 */
> -			paddr = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 1);
> -			regmap_write(ldev->regmap, LTDC_L1AFBA0R + lofs, paddr);
> -			regmap_write(ldev->regmap, LTDC_L1AFBA1R + lofs, paddr + 1);
> +			/* Configure the auxiliary frame buffer address 0 */
> +			paddr1 = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 1);
> +
> +			if (newstate->rotation & DRM_MODE_REFLECT_X)
> +				paddr1 += ((fb->format->cpp[1] * (x1 - x0 + 1)) >> 1) - 1;
>   
> -			/* Configure the buffer length */
> -			val = ((pitch_in_bytes << 16) | line_length);
> -			regmap_write(ldev->regmap, LTDC_L1AFBLR + lofs, val);
> +			if (newstate->rotation & DRM_MODE_REFLECT_Y)
> +				paddr1 += (fb->pitches[1] * (y1 - y0 - 1)) >> 1;
>   
> -			/* Configure the frame buffer line number */
> -			val = (line_number >> 1);
> -			regmap_write(ldev->regmap, LTDC_L1AFBLNR + lofs, val);
> +			regmap_write(ldev->regmap, LTDC_L1AFBA0R + lofs, paddr1);
>   			break;
>   			case DRM_FORMAT_YUV420:
> -			/* Configure the auxiliary frame buffer address 0 */
> -			paddr = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 1);
> -			regmap_write(ldev->regmap, LTDC_L1AFBA0R + lofs, paddr);
> -
> -			/* Configure the auxiliary frame buffer address 1 */
> -			paddr = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 2);
> -			regmap_write(ldev->regmap, LTDC_L1AFBA1R + lofs, paddr);
> +			/* Configure the auxiliary frame buffer address 0 & 1 */
> +			paddr1 = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 1);
> +			paddr2 = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 2);
>   
> -			line_length = ((fb->format->cpp[0] * (x1 - x0 + 1)) >> 1) +
> -				      (ldev->caps.bus_width >> 3) - 1;
> +			if (newstate->rotation & DRM_MODE_REFLECT_X) {
> +				paddr1 += ((fb->format->cpp[1] * (x1 - x0 + 1)) >> 1) - 1;
> +				paddr2 += ((fb->format->cpp[2] * (x1 - x0 + 1)) >> 1) - 1;
> +			}
>   
> -			/* Configure the buffer length */
> -			val = (((pitch_in_bytes >> 1) << 16) | line_length);
> -			regmap_write(ldev->regmap, LTDC_L1AFBLR + lofs, val);
> +			if (newstate->rotation & DRM_MODE_REFLECT_Y) {
> +				paddr1 += (fb->pitches[1] * (y1 - y0 - 1)) >> 1;
> +				paddr2 += (fb->pitches[2] * (y1 - y0 - 1)) >> 1;
> +			}
>   
> -			/* Configure the frame buffer line number */
> -			val = (line_number >> 1);
> -			regmap_write(ldev->regmap, LTDC_L1AFBLNR + lofs, val);
> +			regmap_write(ldev->regmap, LTDC_L1AFBA0R + lofs, paddr1);
> +			regmap_write(ldev->regmap, LTDC_L1AFBA1R + lofs, paddr2);
>   			break;
>   			case DRM_FORMAT_YVU420:
> -			/* Configure the auxiliary frame buffer address 0 */
> -			paddr = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 2);
> -			regmap_write(ldev->regmap, LTDC_L1AFBA0R + lofs, paddr);
> -
> -			/* Configure the auxiliary frame buffer address 1 */
> -			paddr = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 1);
> -			regmap_write(ldev->regmap, LTDC_L1AFBA1R + lofs, paddr);
> +			/* Configure the auxiliary frame buffer address 0 & 1 */
> +			paddr1 = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 2);
> +			paddr2 = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 1);
>   
> -			line_length = ((fb->format->cpp[0] * (x1 - x0 + 1)) >> 1) +
> -				      (ldev->caps.bus_width >> 3) - 1;
> +			if (newstate->rotation & DRM_MODE_REFLECT_X) {
> +				paddr1 += ((fb->format->cpp[1] * (x1 - x0 + 1)) >> 1) - 1;
> +				paddr2 += ((fb->format->cpp[2] * (x1 - x0 + 1)) >> 1) - 1;
> +			}
>   
> -			/* Configure the buffer length */
> -			val = (((pitch_in_bytes >> 1) << 16) | line_length);
> -			regmap_write(ldev->regmap, LTDC_L1AFBLR + lofs, val);
> +			if (newstate->rotation & DRM_MODE_REFLECT_Y) {
> +				paddr1 += (fb->pitches[1] * (y1 - y0 - 1)) >> 1;
> +				paddr2 += (fb->pitches[2] * (y1 - y0 - 1)) >> 1;
> +			}
>   
> -			/* Configure the frame buffer line number */
> -			val = (line_number >> 1);
> -			regmap_write(ldev->regmap, LTDC_L1AFBLNR + lofs, val);
> +			regmap_write(ldev->regmap, LTDC_L1AFBA0R + lofs, paddr1);
> +			regmap_write(ldev->regmap, LTDC_L1AFBA1R + lofs, paddr2);
>   			break;
>   			}
>   
> +			/*
> +			 * Set the length and the number of lines of the auxiliary
> +			 * buffers if the framebuffer contains more than one plane.
> +			 */
> +			if (fb->format->num_planes > 1) {
> +				if (newstate->rotation & DRM_MODE_REFLECT_Y)
> +					/*
> +					 * Compute negative value (signed on 16 bits)
> +					 * for the picth
> +					 */
> +					pitch_in_bytes = 0x10000 - fb->pitches[1];
> +				else
> +					pitch_in_bytes = fb->pitches[1];
> +
> +				line_length = ((fb->format->cpp[1] * (x1 - x0 + 1)) >> 1) +
> +					      (ldev->caps.bus_width >> 3) - 1;
> +
> +				/* Configure the auxiliary buffer length */
> +				val = (pitch_in_bytes << 16) | line_length;
> +				regmap_write(ldev->regmap, LTDC_L1AFBLR + lofs, val);
> +
> +				/* Configure the auxiliary frame buffer line number */
> +				val = line_number >> 1;
> +				regmap_write(ldev->regmap, LTDC_L1AFBLNR + lofs, val);
> +			}
> +
>   			/* Configure YCbC conversion coefficient */
>   			ltdc_set_ycbcr_coeffs(plane);
>   
> @@ -1403,7 +1437,12 @@ static void ltdc_plane_atomic_update(struct drm_plane *plane,
>   	/* Enable layer and CLUT if needed */
>   	val = fb->format->format == DRM_FORMAT_C8 ? LXCR_CLUTEN : 0;
>   	val |= LXCR_LEN;
> -	regmap_write_bits(ldev->regmap, LTDC_L1CR + lofs, LXCR_LEN | LXCR_CLUTEN, val);
> +
> +	/* Enable horizontal mirroring if requested */
> +	if (newstate->rotation & DRM_MODE_REFLECT_X)
> +		val |= LXCR_HMEN;
> +
> +	regmap_write_bits(ldev->regmap, LTDC_L1CR + lofs, LXCR_LEN | LXCR_CLUTEN | LXCR_HMEN, val);
>   
>   	/* Commit shadow registers = update plane at next vblank */
>   	if (ldev->caps.plane_reg_shadow)
> @@ -1432,8 +1471,8 @@ static void ltdc_plane_atomic_disable(struct drm_plane *plane,
>   	struct ltdc_device *ldev = plane_to_ltdc(plane);
>   	u32 lofs = plane->index * LAY_OFS;
>   
> -	/* disable layer */
> -	regmap_write_bits(ldev->regmap, LTDC_L1CR + lofs, LXCR_LEN, 0);
> +	/* Disable layer */
> +	regmap_write_bits(ldev->regmap, LTDC_L1CR + lofs, LXCR_LEN | LXCR_CLUTEN |  LXCR_HMEN, 0);
>   
>   	/* Commit shadow registers = update plane at next vblank */
>   	if (ldev->caps.plane_reg_shadow)
> @@ -1577,6 +1616,7 @@ static int ltdc_crtc_init(struct drm_device *ddev, struct drm_crtc *crtc)
>   {
>   	struct ltdc_device *ldev = ddev->dev_private;
>   	struct drm_plane *primary, *overlay;
> +	int supported_rotations = DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y;
>   	unsigned int i;
>   	int ret;
>   
> @@ -1591,6 +1631,10 @@ static int ltdc_crtc_init(struct drm_device *ddev, struct drm_crtc *crtc)
>   	else
>   		drm_plane_create_zpos_immutable_property(primary, 0);
>   
> +	if (ldev->caps.plane_rotation)
> +		drm_plane_create_rotation_property(primary, DRM_MODE_ROTATE_0,
> +						   supported_rotations);
> +
>   	/* Init CRTC according to its hardware features */
>   	if (ldev->caps.crc)
>   		ret = drm_crtc_init_with_planes(ddev, crtc, primary, NULL,
> @@ -1622,6 +1666,10 @@ static int ltdc_crtc_init(struct drm_device *ddev, struct drm_crtc *crtc)
>   			drm_plane_create_zpos_property(overlay, i, 0, ldev->caps.nb_layers - 1);
>   		else
>   			drm_plane_create_zpos_immutable_property(overlay, i);
> +
> +		if (ldev->caps.plane_rotation)
> +			drm_plane_create_rotation_property(overlay, DRM_MODE_ROTATE_0,
> +							   supported_rotations);
>   	}
>   
>   	return 0;
> @@ -1752,6 +1800,7 @@ static int ltdc_get_caps(struct drm_device *ddev)
>   		ldev->caps.plane_reg_shadow = false;
>   		ldev->caps.crc = false;
>   		ldev->caps.dynamic_zorder = false;
> +		ldev->caps.plane_rotation = false;
>   		break;
>   	case HWVER_20101:
>   		ldev->caps.layer_ofs = LAY_OFS_0;
> @@ -1768,6 +1817,7 @@ static int ltdc_get_caps(struct drm_device *ddev)
>   		ldev->caps.plane_reg_shadow = false;
>   		ldev->caps.crc = false;
>   		ldev->caps.dynamic_zorder = false;
> +		ldev->caps.plane_rotation = false;
>   		break;
>   	case HWVER_40100:
>   		ldev->caps.layer_ofs = LAY_OFS_1;
> @@ -1784,6 +1834,7 @@ static int ltdc_get_caps(struct drm_device *ddev)
>   		ldev->caps.plane_reg_shadow = true;
>   		ldev->caps.crc = true;
>   		ldev->caps.dynamic_zorder = true;
> +		ldev->caps.plane_rotation = true;
>   		break;
>   	default:
>   		return -ENODEV;
> diff --git a/drivers/gpu/drm/stm/ltdc.h b/drivers/gpu/drm/stm/ltdc.h
> index 4855898bd4c0..15139980d8ea 100644
> --- a/drivers/gpu/drm/stm/ltdc.h
> +++ b/drivers/gpu/drm/stm/ltdc.h
> @@ -29,6 +29,7 @@ struct ltdc_caps {
>   	bool plane_reg_shadow;	/* plane shadow registers ability */
>   	bool crc;		/* cyclic redundancy check supported */
>   	bool dynamic_zorder;	/* dynamic z-order */
> +	bool plane_rotation;	/* plane rotation */
>   };
>   
>   #define LTDC_MAX_LAYER	4

Dear Yannick,
Many thanks for your patch,
Applied on drm-misc-next.
Have a good day
Philippe :-)
diff mbox series

Patch

diff --git a/drivers/gpu/drm/stm/ltdc.c b/drivers/gpu/drm/stm/ltdc.c
index 00a6bc1b1d7c..ff2075dd9474 100644
--- a/drivers/gpu/drm/stm/ltdc.c
+++ b/drivers/gpu/drm/stm/ltdc.c
@@ -180,6 +180,7 @@ 
 #define LXCR_LEN	BIT(0)		/* Layer ENable */
 #define LXCR_COLKEN	BIT(1)		/* Color Keying Enable */
 #define LXCR_CLUTEN	BIT(4)		/* Color Look-Up Table ENable */
+#define LXCR_HMEN	BIT(8)		/* Horizontal Mirroring ENable */
 
 #define LXWHPCR_WHSTPOS	GENMASK(11, 0)	/* Window Horizontal StarT POSition */
 #define LXWHPCR_WHSPPOS	GENMASK(27, 16)	/* Window Horizontal StoP POSition */
@@ -197,7 +198,7 @@ 
 #define LXBFCR_BOR	GENMASK(18, 16) /* Blending ORder */
 
 #define LXCFBLR_CFBLL	GENMASK(12, 0)	/* Color Frame Buffer Line Length */
-#define LXCFBLR_CFBP	GENMASK(28, 16)	/* Color Frame Buffer Pitch in bytes */
+#define LXCFBLR_CFBP	GENMASK(31, 16) /* Color Frame Buffer Pitch in bytes */
 
 #define LXCFBLNR_CFBLN	GENMASK(10, 0)	/* Color Frame Buffer Line Number */
 
@@ -1237,7 +1238,8 @@  static void ltdc_plane_atomic_update(struct drm_plane *plane,
 	u32 y0 = newstate->crtc_y;
 	u32 y1 = newstate->crtc_y + newstate->crtc_h - 1;
 	u32 src_x, src_y, src_w, src_h;
-	u32 val, pitch_in_bytes, line_length, line_number, paddr, ahbp, avbp, bpcr;
+	u32 val, pitch_in_bytes, line_length, line_number, ahbp, avbp, bpcr;
+	u32 paddr, paddr1, paddr2;
 	enum ltdc_pix_fmt pf;
 
 	if (!newstate->crtc || !fb) {
@@ -1289,13 +1291,6 @@  static void ltdc_plane_atomic_update(struct drm_plane *plane,
 	}
 	regmap_write_bits(ldev->regmap, LTDC_L1PFCR + lofs, LXPFCR_PF, val);
 
-	/* Configures the color frame buffer pitch in bytes & line length */
-	pitch_in_bytes = fb->pitches[0];
-	line_length = fb->format->cpp[0] *
-		      (x1 - x0 + 1) + (ldev->caps.bus_width >> 3) - 1;
-	val = ((pitch_in_bytes << 16) | line_length);
-	regmap_write_bits(ldev->regmap, LTDC_L1CFBLR + lofs, LXCFBLR_CFBLL | LXCFBLR_CFBP, val);
-
 	/* Specifies the constant alpha value */
 	val = newstate->alpha >> 8;
 	regmap_write_bits(ldev->regmap, LTDC_L1CACR + lofs, LXCACR_CONSTA, val);
@@ -1319,76 +1314,115 @@  static void ltdc_plane_atomic_update(struct drm_plane *plane,
 				  LXBFCR_BF2 | LXBFCR_BF1, val);
 	}
 
-	/* Configures the frame buffer line number */
-	line_number = y1 - y0 + 1;
-	regmap_write_bits(ldev->regmap, LTDC_L1CFBLNR + lofs, LXCFBLNR_CFBLN, line_number);
-
 	/* Sets the FB address */
 	paddr = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 0);
 
+	if (newstate->rotation & DRM_MODE_REFLECT_X)
+		paddr += (fb->format->cpp[0] * (x1 - x0 + 1)) - 1;
+
+	if (newstate->rotation & DRM_MODE_REFLECT_Y)
+		paddr += (fb->pitches[0] * (y1 - y0));
+
 	DRM_DEBUG_DRIVER("fb: phys 0x%08x", paddr);
 	regmap_write(ldev->regmap, LTDC_L1CFBAR + lofs, paddr);
 
+	/* Configures the color frame buffer pitch in bytes & line length */
+	line_length = fb->format->cpp[0] *
+		      (x1 - x0 + 1) + (ldev->caps.bus_width >> 3) - 1;
+
+	if (newstate->rotation & DRM_MODE_REFLECT_Y)
+		/* Compute negative value (signed on 16 bits) for the picth */
+		pitch_in_bytes = 0x10000 - fb->pitches[0];
+	else
+		pitch_in_bytes = fb->pitches[0];
+
+	val = (pitch_in_bytes << 16) | line_length;
+	regmap_write_bits(ldev->regmap, LTDC_L1CFBLR + lofs, LXCFBLR_CFBLL | LXCFBLR_CFBP, val);
+
+	/* Configures the frame buffer line number */
+	line_number = y1 - y0 + 1;
+	regmap_write_bits(ldev->regmap, LTDC_L1CFBLNR + lofs, LXCFBLNR_CFBLN, line_number);
+
 	if (ldev->caps.ycbcr_input) {
 		if (fb->format->is_yuv) {
 			switch (fb->format->format) {
 			case DRM_FORMAT_NV12:
 			case DRM_FORMAT_NV21:
-			/* Configure the auxiliary frame buffer address 0 & 1 */
-			paddr = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 1);
-			regmap_write(ldev->regmap, LTDC_L1AFBA0R + lofs, paddr);
-			regmap_write(ldev->regmap, LTDC_L1AFBA1R + lofs, paddr + 1);
+			/* Configure the auxiliary frame buffer address 0 */
+			paddr1 = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 1);
+
+			if (newstate->rotation & DRM_MODE_REFLECT_X)
+				paddr1 += ((fb->format->cpp[1] * (x1 - x0 + 1)) >> 1) - 1;
 
-			/* Configure the buffer length */
-			val = ((pitch_in_bytes << 16) | line_length);
-			regmap_write(ldev->regmap, LTDC_L1AFBLR + lofs, val);
+			if (newstate->rotation & DRM_MODE_REFLECT_Y)
+				paddr1 += (fb->pitches[1] * (y1 - y0 - 1)) >> 1;
 
-			/* Configure the frame buffer line number */
-			val = (line_number >> 1);
-			regmap_write(ldev->regmap, LTDC_L1AFBLNR + lofs, val);
+			regmap_write(ldev->regmap, LTDC_L1AFBA0R + lofs, paddr1);
 			break;
 			case DRM_FORMAT_YUV420:
-			/* Configure the auxiliary frame buffer address 0 */
-			paddr = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 1);
-			regmap_write(ldev->regmap, LTDC_L1AFBA0R + lofs, paddr);
-
-			/* Configure the auxiliary frame buffer address 1 */
-			paddr = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 2);
-			regmap_write(ldev->regmap, LTDC_L1AFBA1R + lofs, paddr);
+			/* Configure the auxiliary frame buffer address 0 & 1 */
+			paddr1 = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 1);
+			paddr2 = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 2);
 
-			line_length = ((fb->format->cpp[0] * (x1 - x0 + 1)) >> 1) +
-				      (ldev->caps.bus_width >> 3) - 1;
+			if (newstate->rotation & DRM_MODE_REFLECT_X) {
+				paddr1 += ((fb->format->cpp[1] * (x1 - x0 + 1)) >> 1) - 1;
+				paddr2 += ((fb->format->cpp[2] * (x1 - x0 + 1)) >> 1) - 1;
+			}
 
-			/* Configure the buffer length */
-			val = (((pitch_in_bytes >> 1) << 16) | line_length);
-			regmap_write(ldev->regmap, LTDC_L1AFBLR + lofs, val);
+			if (newstate->rotation & DRM_MODE_REFLECT_Y) {
+				paddr1 += (fb->pitches[1] * (y1 - y0 - 1)) >> 1;
+				paddr2 += (fb->pitches[2] * (y1 - y0 - 1)) >> 1;
+			}
 
-			/* Configure the frame buffer line number */
-			val = (line_number >> 1);
-			regmap_write(ldev->regmap, LTDC_L1AFBLNR + lofs, val);
+			regmap_write(ldev->regmap, LTDC_L1AFBA0R + lofs, paddr1);
+			regmap_write(ldev->regmap, LTDC_L1AFBA1R + lofs, paddr2);
 			break;
 			case DRM_FORMAT_YVU420:
-			/* Configure the auxiliary frame buffer address 0 */
-			paddr = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 2);
-			regmap_write(ldev->regmap, LTDC_L1AFBA0R + lofs, paddr);
-
-			/* Configure the auxiliary frame buffer address 1 */
-			paddr = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 1);
-			regmap_write(ldev->regmap, LTDC_L1AFBA1R + lofs, paddr);
+			/* Configure the auxiliary frame buffer address 0 & 1 */
+			paddr1 = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 2);
+			paddr2 = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 1);
 
-			line_length = ((fb->format->cpp[0] * (x1 - x0 + 1)) >> 1) +
-				      (ldev->caps.bus_width >> 3) - 1;
+			if (newstate->rotation & DRM_MODE_REFLECT_X) {
+				paddr1 += ((fb->format->cpp[1] * (x1 - x0 + 1)) >> 1) - 1;
+				paddr2 += ((fb->format->cpp[2] * (x1 - x0 + 1)) >> 1) - 1;
+			}
 
-			/* Configure the buffer length */
-			val = (((pitch_in_bytes >> 1) << 16) | line_length);
-			regmap_write(ldev->regmap, LTDC_L1AFBLR + lofs, val);
+			if (newstate->rotation & DRM_MODE_REFLECT_Y) {
+				paddr1 += (fb->pitches[1] * (y1 - y0 - 1)) >> 1;
+				paddr2 += (fb->pitches[2] * (y1 - y0 - 1)) >> 1;
+			}
 
-			/* Configure the frame buffer line number */
-			val = (line_number >> 1);
-			regmap_write(ldev->regmap, LTDC_L1AFBLNR + lofs, val);
+			regmap_write(ldev->regmap, LTDC_L1AFBA0R + lofs, paddr1);
+			regmap_write(ldev->regmap, LTDC_L1AFBA1R + lofs, paddr2);
 			break;
 			}
 
+			/*
+			 * Set the length and the number of lines of the auxiliary
+			 * buffers if the framebuffer contains more than one plane.
+			 */
+			if (fb->format->num_planes > 1) {
+				if (newstate->rotation & DRM_MODE_REFLECT_Y)
+					/*
+					 * Compute negative value (signed on 16 bits)
+					 * for the picth
+					 */
+					pitch_in_bytes = 0x10000 - fb->pitches[1];
+				else
+					pitch_in_bytes = fb->pitches[1];
+
+				line_length = ((fb->format->cpp[1] * (x1 - x0 + 1)) >> 1) +
+					      (ldev->caps.bus_width >> 3) - 1;
+
+				/* Configure the auxiliary buffer length */
+				val = (pitch_in_bytes << 16) | line_length;
+				regmap_write(ldev->regmap, LTDC_L1AFBLR + lofs, val);
+
+				/* Configure the auxiliary frame buffer line number */
+				val = line_number >> 1;
+				regmap_write(ldev->regmap, LTDC_L1AFBLNR + lofs, val);
+			}
+
 			/* Configure YCbC conversion coefficient */
 			ltdc_set_ycbcr_coeffs(plane);
 
@@ -1403,7 +1437,12 @@  static void ltdc_plane_atomic_update(struct drm_plane *plane,
 	/* Enable layer and CLUT if needed */
 	val = fb->format->format == DRM_FORMAT_C8 ? LXCR_CLUTEN : 0;
 	val |= LXCR_LEN;
-	regmap_write_bits(ldev->regmap, LTDC_L1CR + lofs, LXCR_LEN | LXCR_CLUTEN, val);
+
+	/* Enable horizontal mirroring if requested */
+	if (newstate->rotation & DRM_MODE_REFLECT_X)
+		val |= LXCR_HMEN;
+
+	regmap_write_bits(ldev->regmap, LTDC_L1CR + lofs, LXCR_LEN | LXCR_CLUTEN | LXCR_HMEN, val);
 
 	/* Commit shadow registers = update plane at next vblank */
 	if (ldev->caps.plane_reg_shadow)
@@ -1432,8 +1471,8 @@  static void ltdc_plane_atomic_disable(struct drm_plane *plane,
 	struct ltdc_device *ldev = plane_to_ltdc(plane);
 	u32 lofs = plane->index * LAY_OFS;
 
-	/* disable layer */
-	regmap_write_bits(ldev->regmap, LTDC_L1CR + lofs, LXCR_LEN, 0);
+	/* Disable layer */
+	regmap_write_bits(ldev->regmap, LTDC_L1CR + lofs, LXCR_LEN | LXCR_CLUTEN |  LXCR_HMEN, 0);
 
 	/* Commit shadow registers = update plane at next vblank */
 	if (ldev->caps.plane_reg_shadow)
@@ -1577,6 +1616,7 @@  static int ltdc_crtc_init(struct drm_device *ddev, struct drm_crtc *crtc)
 {
 	struct ltdc_device *ldev = ddev->dev_private;
 	struct drm_plane *primary, *overlay;
+	int supported_rotations = DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y;
 	unsigned int i;
 	int ret;
 
@@ -1591,6 +1631,10 @@  static int ltdc_crtc_init(struct drm_device *ddev, struct drm_crtc *crtc)
 	else
 		drm_plane_create_zpos_immutable_property(primary, 0);
 
+	if (ldev->caps.plane_rotation)
+		drm_plane_create_rotation_property(primary, DRM_MODE_ROTATE_0,
+						   supported_rotations);
+
 	/* Init CRTC according to its hardware features */
 	if (ldev->caps.crc)
 		ret = drm_crtc_init_with_planes(ddev, crtc, primary, NULL,
@@ -1622,6 +1666,10 @@  static int ltdc_crtc_init(struct drm_device *ddev, struct drm_crtc *crtc)
 			drm_plane_create_zpos_property(overlay, i, 0, ldev->caps.nb_layers - 1);
 		else
 			drm_plane_create_zpos_immutable_property(overlay, i);
+
+		if (ldev->caps.plane_rotation)
+			drm_plane_create_rotation_property(overlay, DRM_MODE_ROTATE_0,
+							   supported_rotations);
 	}
 
 	return 0;
@@ -1752,6 +1800,7 @@  static int ltdc_get_caps(struct drm_device *ddev)
 		ldev->caps.plane_reg_shadow = false;
 		ldev->caps.crc = false;
 		ldev->caps.dynamic_zorder = false;
+		ldev->caps.plane_rotation = false;
 		break;
 	case HWVER_20101:
 		ldev->caps.layer_ofs = LAY_OFS_0;
@@ -1768,6 +1817,7 @@  static int ltdc_get_caps(struct drm_device *ddev)
 		ldev->caps.plane_reg_shadow = false;
 		ldev->caps.crc = false;
 		ldev->caps.dynamic_zorder = false;
+		ldev->caps.plane_rotation = false;
 		break;
 	case HWVER_40100:
 		ldev->caps.layer_ofs = LAY_OFS_1;
@@ -1784,6 +1834,7 @@  static int ltdc_get_caps(struct drm_device *ddev)
 		ldev->caps.plane_reg_shadow = true;
 		ldev->caps.crc = true;
 		ldev->caps.dynamic_zorder = true;
+		ldev->caps.plane_rotation = true;
 		break;
 	default:
 		return -ENODEV;
diff --git a/drivers/gpu/drm/stm/ltdc.h b/drivers/gpu/drm/stm/ltdc.h
index 4855898bd4c0..15139980d8ea 100644
--- a/drivers/gpu/drm/stm/ltdc.h
+++ b/drivers/gpu/drm/stm/ltdc.h
@@ -29,6 +29,7 @@  struct ltdc_caps {
 	bool plane_reg_shadow;	/* plane shadow registers ability */
 	bool crc;		/* cyclic redundancy check supported */
 	bool dynamic_zorder;	/* dynamic z-order */
+	bool plane_rotation;	/* plane rotation */
 };
 
 #define LTDC_MAX_LAYER	4