diff mbox series

[v4,1/2] drm/format-helper: Add conversion from XRGB8888 to BGR888

Message ID 03FA573F-6D01-40E8-A666-CEA17A917036@live.com (mailing list archive)
State New
Headers show
Series Touch Bar DRM driver for x86 Macs | expand

Commit Message

Aditya Garg Feb. 24, 2025, 1:38 p.m. UTC
From: Kerem Karabay <kekrby@gmail.com>

Add XRGB8888 emulation helper for devices that only support BGR888.

Signed-off-by: Kerem Karabay <kekrby@gmail.com>
Signed-off-by: Aditya Garg <gargaditya08@live.com>
Reviewed-by: Thomas Zimmermann <tzimmermann@suse.de>
---
v2 -> Fix incorrect description
v3 -> No change in this patch
v4 -> No change in this patch

 drivers/gpu/drm/drm_format_helper.c           | 54 +++++++++++++
 .../gpu/drm/tests/drm_format_helper_test.c    | 81 +++++++++++++++++++
 include/drm/drm_format_helper.h               |  3 +
 3 files changed, 138 insertions(+)

Comments

andriy.shevchenko@linux.intel.com Feb. 24, 2025, 2:29 p.m. UTC | #1
On Mon, Feb 24, 2025 at 01:38:32PM +0000, Aditya Garg wrote:
> From: Kerem Karabay <kekrby@gmail.com>
> 
> Add XRGB8888 emulation helper for devices that only support BGR888.

...

> +static void drm_fb_xrgb8888_to_bgr888_line(void *dbuf, const void *sbuf, unsigned int pixels)

Okay the xrgb8888 is the actual pixel format independently on
the CPU endianess.

> +{
> +	u8 *dbuf8 = dbuf;
> +	const __le32 *sbuf32 = sbuf;

But here we assume that sbuf is __le32.
And I think we may benefit from the __be32 there.

> +	unsigned int x;
> +	u32 pix;
> +
> +	for (x = 0; x < pixels; x++) {
> +		pix = le32_to_cpu(sbuf32[x]);
> +		/* write red-green-blue to output in little endianness */
> +		*dbuf8++ = (pix & 0x00ff0000) >> 16;
> +		*dbuf8++ = (pix & 0x0000ff00) >> 8;
> +		*dbuf8++ = (pix & 0x000000ff) >> 0;

		pix = be32_to_cpu(sbuf[4 * x]) >> 8;
		put_unaligned_le24(pix, &dbuf[3 * x]);

> +	}

Or, after all, this __le32 magic might be not needed at all. Wouldn't the below
be the equivalent

static void drm_fb_xrgb8888_to_bgr888_line(void *dbuf, const void *sbuf, unsigned int pixels)
{
	unsigned int x;
	u32 pix;

	for (x = 0; x < pixels; x++) {
		/* Read red-green-blue from input in big endianess and... */
		pix = get_unaligned_be24(sbuf + x * 4 + 1);
		/* ...write it to output in little endianness. */
		put_unaligned_le24(pix, dbuf + x * 3);
	}
}

The comments can even be dropped as the code quite clear about what's going on.

> +}

But it's up to you. I don't know which solution gives better code generation
either.
Aditya Garg Feb. 24, 2025, 2:54 p.m. UTC | #2
This conversion helper mimics the existing drm_fb_xrgb8888_to_rgb888 helper

> On 24 Feb 2025, at 7:59 PM, andriy.shevchenko@linux.intel.com wrote:
> 
> On Mon, Feb 24, 2025 at 01:38:32PM +0000, Aditya Garg wrote:
>> From: Kerem Karabay <kekrby@gmail.com>
>> 
>> Add XRGB8888 emulation helper for devices that only support BGR888.
> 
> ...
> 
>> +static void drm_fb_xrgb8888_to_bgr888_line(void *dbuf, const void *sbuf, unsigned int pixels)
> 
> Okay the xrgb8888 is the actual pixel format independently on
> the CPU endianess.
> 
>> +{
>> + u8 *dbuf8 = dbuf;
>> + const __le32 *sbuf32 = sbuf;
> 
> But here we assume that sbuf is __le32.
> And I think we may benefit from the __be32 there.

So, like drm_fb_xrgb8888_to_rgb888, we are using __le32

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/gpu/drm/drm_format_helper.c?h=v6.14-rc4#n657


> 
>> + unsigned int x;
>> + u32 pix;
>> +
>> + for (x = 0; x < pixels; x++) {
>> + pix = le32_to_cpu(sbuf32[x]);
>> + /* write red-green-blue to output in little endianness */
>> + *dbuf8++ = (pix & 0x00ff0000) >> 16;
>> + *dbuf8++ = (pix & 0x0000ff00) >> 8;
>> + *dbuf8++ = (pix & 0x000000ff) >> 0;
> 
> pix = be32_to_cpu(sbuf[4 * x]) >> 8;
> put_unaligned_le24(pix, &dbuf[3 * x]);

Again, 

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/gpu/drm/drm_format_helper.c?h=v6.14-rc4#n664
> 
>> + }
> 
> Or, after all, this __le32 magic might be not needed at all. Wouldn't the below
> be the equivalent
> 
> static void drm_fb_xrgb8888_to_bgr888_line(void *dbuf, const void *sbuf, unsigned int pixels)
> {
> unsigned int x;
> u32 pix;
> 
> for (x = 0; x < pixels; x++) {
> /* Read red-green-blue from input in big endianess and... */
> pix = get_unaligned_be24(sbuf + x * 4 + 1);
> /* ...write it to output in little endianness. */
> put_unaligned_le24(pix, dbuf + x * 3);
> }
> }
> 
> The comments can even be dropped as the code quite clear about what's going on.

These comments are literally rewritten :

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/gpu/drm/drm_format_helper.c?h=v6.14-rc4#n663

> 
>> +}
> 
> But it's up to you. I don't know which solution gives better code generation
> either.

I don't really mind any code change tbh, but I’d prefer that as an improvement to existing code, and not a part of this patchset.
andriy.shevchenko@linux.intel.com Feb. 24, 2025, 3 p.m. UTC | #3
On Mon, Feb 24, 2025 at 02:54:07PM +0000, Aditya Garg wrote:
> This conversion helper mimics the existing drm_fb_xrgb8888_to_rgb888 helper

Not really. See below.

> > On 24 Feb 2025, at 7:59 PM, andriy.shevchenko@linux.intel.com wrote:
> > On Mon, Feb 24, 2025 at 01:38:32PM +0000, Aditya Garg wrote:

...

> >> +static void drm_fb_xrgb8888_to_bgr888_line(void *dbuf, const void *sbuf, unsigned int pixels)
> > 
> > Okay the xrgb8888 is the actual pixel format independently on
> > the CPU endianess.
> > 
> >> +{
> >> + u8 *dbuf8 = dbuf;
> >> + const __le32 *sbuf32 = sbuf;
> > 
> > But here we assume that sbuf is __le32.
> > And I think we may benefit from the __be32 there.
> 
> So, like drm_fb_xrgb8888_to_rgb888, we are using __le32
> 
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/gpu/drm/drm_format_helper.c?h=v6.14-rc4#n657

The rgb888 != bgr888, that's where the byte swapping happens. So, one should
use __be32 if the other has already been using __le32.

> >> + unsigned int x;
> >> + u32 pix;
> >> +
> >> + for (x = 0; x < pixels; x++) {
> >> + pix = le32_to_cpu(sbuf32[x]);
> >> + /* write red-green-blue to output in little endianness */
> >> + *dbuf8++ = (pix & 0x00ff0000) >> 16;
> >> + *dbuf8++ = (pix & 0x0000ff00) >> 8;
> >> + *dbuf8++ = (pix & 0x000000ff) >> 0;
> > 
> > pix = be32_to_cpu(sbuf[4 * x]) >> 8;
> > put_unaligned_le24(pix, &dbuf[3 * x]);
> 
> Again, 
> 
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/gpu/drm/drm_format_helper.c?h=v6.14-rc4#n664

As per above.

> >> + }
> > 
> > Or, after all, this __le32 magic might be not needed at all. Wouldn't the below
> > be the equivalent
> > 
> > static void drm_fb_xrgb8888_to_bgr888_line(void *dbuf, const void *sbuf, unsigned int pixels)
> > {
> > unsigned int x;
> > u32 pix;
> > 
> > for (x = 0; x < pixels; x++) {
> > /* Read red-green-blue from input in big endianess and... */
> > pix = get_unaligned_be24(sbuf + x * 4 + 1);
> > /* ...write it to output in little endianness. */
> > put_unaligned_le24(pix, dbuf + x * 3);
> > }
> > }
> > 
> > The comments can even be dropped as the code quite clear about what's going on.
> 
> These comments are literally rewritten :
> 
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/gpu/drm/drm_format_helper.c?h=v6.14-rc4#n663
> 
> >> +}
> > 
> > But it's up to you. I don't know which solution gives better code generation
> > either.
> 
> I don't really mind any code change tbh, but I’d prefer that as an
> improvement to existing code, and not a part of this patchset.

Right, but see my argumentation above.
Aditya Garg Feb. 24, 2025, 3:03 p.m. UTC | #4
> On 24 Feb 2025, at 8:30 PM, andriy.shevchenko@linux.intel.com wrote:
> 
> On Mon, Feb 24, 2025 at 02:54:07PM +0000, Aditya Garg wrote:
>> This conversion helper mimics the existing drm_fb_xrgb8888_to_rgb888 helper
> 
> Not really. See below.
> 
>>> On 24 Feb 2025, at 7:59 PM, andriy.shevchenko@linux.intel.com wrote:
>>> On Mon, Feb 24, 2025 at 01:38:32PM +0000, Aditya Garg wrote:
> 
> ...
> 
>>>> +static void drm_fb_xrgb8888_to_bgr888_line(void *dbuf, const void *sbuf, unsigned int pixels)
>>> 
>>> Okay the xrgb8888 is the actual pixel format independently on
>>> the CPU endianess.
>>> 
>>>> +{
>>>> + u8 *dbuf8 = dbuf;
>>>> + const __le32 *sbuf32 = sbuf;
>>> 
>>> But here we assume that sbuf is __le32.
>>> And I think we may benefit from the __be32 there.
>> 
>> So, like drm_fb_xrgb8888_to_rgb888, we are using __le32
>> 
>> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/gpu/drm/drm_format_helper.c?h=v6.14-rc4#n657
> 
> The rgb888 != bgr888, that's where the byte swapping happens. So, one should
> use __be32 if the other has already been using __le32.
> 
>>>> + unsigned int x;
>>>> + u32 pix;
>>>> +
>>>> + for (x = 0; x < pixels; x++) {
>>>> + pix = le32_to_cpu(sbuf32[x]);
>>>> + /* write red-green-blue to output in little endianness */
>>>> + *dbuf8++ = (pix & 0x00ff0000) >> 16;
>>>> + *dbuf8++ = (pix & 0x0000ff00) >> 8;
>>>> + *dbuf8++ = (pix & 0x000000ff) >> 0;
>>> 
>>> pix = be32_to_cpu(sbuf[4 * x]) >> 8;
>>> put_unaligned_le24(pix, &dbuf[3 * x]);
>> 
>> Again, 
>> 
>> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/gpu/drm/drm_format_helper.c?h=v6.14-rc4#n664
> 
> As per above.
> 
>>>> + }
>>> 
>>> Or, after all, this __le32 magic might be not needed at all. Wouldn't the below
>>> be the equivalent
>>> 
>>> static void drm_fb_xrgb8888_to_bgr888_line(void *dbuf, const void *sbuf, unsigned int pixels)
>>> {
>>> unsigned int x;
>>> u32 pix;
>>> 
>>> for (x = 0; x < pixels; x++) {
>>> /* Read red-green-blue from input in big endianess and... */
>>> pix = get_unaligned_be24(sbuf + x * 4 + 1);
>>> /* ...write it to output in little endianness. */
>>> put_unaligned_le24(pix, dbuf + x * 3);
>>> }
>>> }
>>> 
>>> The comments can even be dropped as the code quite clear about what's going on.
>> 
>> These comments are literally rewritten :
>> 
>> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/gpu/drm/drm_format_helper.c?h=v6.14-rc4#n663
>> 
>>>> +}
>>> 
>>> But it's up to you. I don't know which solution gives better code generation
>>> either.
>> 
>> I don't really mind any code change tbh, but I’d prefer that as an
>> improvement to existing code, and not a part of this patchset.
> 
> Right, but see my argumentation above.

Guess what, I’ll just wait for Thomas to reply here. He knows more DRM than me.
> 
> -- 
> With Best Regards,
> Andy Shevchenko
andriy.shevchenko@linux.intel.com Feb. 24, 2025, 3:03 p.m. UTC | #5
On Mon, Feb 24, 2025 at 05:00:50PM +0200, andriy.shevchenko@linux.intel.com wrote:
> On Mon, Feb 24, 2025 at 02:54:07PM +0000, Aditya Garg wrote:
> > This conversion helper mimics the existing drm_fb_xrgb8888_to_rgb888 helper
> 
> Not really. See below.
> 
> > > On 24 Feb 2025, at 7:59 PM, andriy.shevchenko@linux.intel.com wrote:
> > > On Mon, Feb 24, 2025 at 01:38:32PM +0000, Aditya Garg wrote:

...

> > >> +static void drm_fb_xrgb8888_to_bgr888_line(void *dbuf, const void *sbuf, unsigned int pixels)
> > > 
> > > Okay the xrgb8888 is the actual pixel format independently on
> > > the CPU endianess.
> > > 
> > >> +{
> > >> + u8 *dbuf8 = dbuf;
> > >> + const __le32 *sbuf32 = sbuf;
> > > 
> > > But here we assume that sbuf is __le32.
> > > And I think we may benefit from the __be32 there.
> > 
> > So, like drm_fb_xrgb8888_to_rgb888, we are using __le32
> > 
> > https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/gpu/drm/drm_format_helper.c?h=v6.14-rc4#n657
> 
> The rgb888 != bgr888, that's where the byte swapping happens. So, one should
> use __be32 if the other has already been using __le32.

But in both cases it's actually the 24-bit format in 4-byte packets.

I would rewrite both to remove these types as they are just confusing. But it's
probably not your call. So, if you want to stick with __le32, fine, not my call
either :-)

> > >> + unsigned int x;
> > >> + u32 pix;
> > >> +
> > >> + for (x = 0; x < pixels; x++) {
> > >> + pix = le32_to_cpu(sbuf32[x]);
> > >> + /* write red-green-blue to output in little endianness */
> > >> + *dbuf8++ = (pix & 0x00ff0000) >> 16;
> > >> + *dbuf8++ = (pix & 0x0000ff00) >> 8;
> > >> + *dbuf8++ = (pix & 0x000000ff) >> 0;
> > > 
> > > pix = be32_to_cpu(sbuf[4 * x]) >> 8;
> > > put_unaligned_le24(pix, &dbuf[3 * x]);
> > 
> > Again, 
> > 
> > https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/gpu/drm/drm_format_helper.c?h=v6.14-rc4#n664
> 
> As per above.
> 
> > >> + }
> > > 
> > > Or, after all, this __le32 magic might be not needed at all. Wouldn't the below
> > > be the equivalent
> > > 
> > > static void drm_fb_xrgb8888_to_bgr888_line(void *dbuf, const void *sbuf, unsigned int pixels)
> > > {
> > > unsigned int x;
> > > u32 pix;
> > > 
> > > for (x = 0; x < pixels; x++) {
> > > /* Read red-green-blue from input in big endianess and... */
> > > pix = get_unaligned_be24(sbuf + x * 4 + 1);
> > > /* ...write it to output in little endianness. */
> > > put_unaligned_le24(pix, dbuf + x * 3);
> > > }
> > > }
> > > 
> > > The comments can even be dropped as the code quite clear about what's going on.
diff mbox series

Patch

diff --git a/drivers/gpu/drm/drm_format_helper.c b/drivers/gpu/drm/drm_format_helper.c
index b1be458ed..4f60c8d8f 100644
--- a/drivers/gpu/drm/drm_format_helper.c
+++ b/drivers/gpu/drm/drm_format_helper.c
@@ -702,6 +702,57 @@  void drm_fb_xrgb8888_to_rgb888(struct iosys_map *dst, const unsigned int *dst_pi
 }
 EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb888);
 
+static void drm_fb_xrgb8888_to_bgr888_line(void *dbuf, const void *sbuf, unsigned int pixels)
+{
+	u8 *dbuf8 = dbuf;
+	const __le32 *sbuf32 = sbuf;
+	unsigned int x;
+	u32 pix;
+
+	for (x = 0; x < pixels; x++) {
+		pix = le32_to_cpu(sbuf32[x]);
+		/* write red-green-blue to output in little endianness */
+		*dbuf8++ = (pix & 0x00ff0000) >> 16;
+		*dbuf8++ = (pix & 0x0000ff00) >> 8;
+		*dbuf8++ = (pix & 0x000000ff) >> 0;
+	}
+}
+
+/**
+ * drm_fb_xrgb8888_to_bgr888 - Convert XRGB8888 to BGR888 clip buffer
+ * @dst: Array of BGR888 destination buffers
+ * @dst_pitch: Array of numbers of bytes between the start of two consecutive scanlines
+ *             within @dst; can be NULL if scanlines are stored next to each other.
+ * @src: Array of XRGB8888 source buffers
+ * @fb: DRM framebuffer
+ * @clip: Clip rectangle area to copy
+ * @state: Transform and conversion state
+ *
+ * This function copies parts of a framebuffer to display memory and converts the
+ * color format during the process. Destination and framebuffer formats must match. The
+ * parameters @dst, @dst_pitch and @src refer to arrays. Each array must have at
+ * least as many entries as there are planes in @fb's format. Each entry stores the
+ * value for the format's respective color plane at the same index.
+ *
+ * This function does not apply clipping on @dst (i.e. the destination is at the
+ * top-left corner).
+ *
+ * Drivers can use this function for BGR888 devices that don't natively
+ * support XRGB8888.
+ */
+void drm_fb_xrgb8888_to_bgr888(struct iosys_map *dst, const unsigned int *dst_pitch,
+			       const struct iosys_map *src, const struct drm_framebuffer *fb,
+			       const struct drm_rect *clip, struct drm_format_conv_state *state)
+{
+	static const u8 dst_pixsize[DRM_FORMAT_MAX_PLANES] = {
+		3,
+	};
+
+	drm_fb_xfrm(dst, dst_pitch, dst_pixsize, src, fb, clip, false, state,
+		    drm_fb_xrgb8888_to_bgr888_line);
+}
+EXPORT_SYMBOL(drm_fb_xrgb8888_to_bgr888);
+
 static void drm_fb_xrgb8888_to_argb8888_line(void *dbuf, const void *sbuf, unsigned int pixels)
 {
 	__le32 *dbuf32 = dbuf;
@@ -1035,6 +1086,9 @@  int drm_fb_blit(struct iosys_map *dst, const unsigned int *dst_pitch, uint32_t d
 		} else if (dst_format == DRM_FORMAT_RGB888) {
 			drm_fb_xrgb8888_to_rgb888(dst, dst_pitch, src, fb, clip, state);
 			return 0;
+		} else if (dst_format == DRM_FORMAT_BGR888) {
+			drm_fb_xrgb8888_to_bgr888(dst, dst_pitch, src, fb, clip, state);
+			return 0;
 		} else if (dst_format == DRM_FORMAT_ARGB8888) {
 			drm_fb_xrgb8888_to_argb8888(dst, dst_pitch, src, fb, clip, state);
 			return 0;
diff --git a/drivers/gpu/drm/tests/drm_format_helper_test.c b/drivers/gpu/drm/tests/drm_format_helper_test.c
index 08992636e..35cd3405d 100644
--- a/drivers/gpu/drm/tests/drm_format_helper_test.c
+++ b/drivers/gpu/drm/tests/drm_format_helper_test.c
@@ -60,6 +60,11 @@  struct convert_to_rgb888_result {
 	const u8 expected[TEST_BUF_SIZE];
 };
 
+struct convert_to_bgr888_result {
+	unsigned int dst_pitch;
+	const u8 expected[TEST_BUF_SIZE];
+};
+
 struct convert_to_argb8888_result {
 	unsigned int dst_pitch;
 	const u32 expected[TEST_BUF_SIZE];
@@ -107,6 +112,7 @@  struct convert_xrgb8888_case {
 	struct convert_to_argb1555_result argb1555_result;
 	struct convert_to_rgba5551_result rgba5551_result;
 	struct convert_to_rgb888_result rgb888_result;
+	struct convert_to_bgr888_result bgr888_result;
 	struct convert_to_argb8888_result argb8888_result;
 	struct convert_to_xrgb2101010_result xrgb2101010_result;
 	struct convert_to_argb2101010_result argb2101010_result;
@@ -151,6 +157,10 @@  static struct convert_xrgb8888_case convert_xrgb8888_cases[] = {
 			.dst_pitch = TEST_USE_DEFAULT_PITCH,
 			.expected = { 0x00, 0x00, 0xFF },
 		},
+		.bgr888_result = {
+			.dst_pitch = TEST_USE_DEFAULT_PITCH,
+			.expected = { 0xFF, 0x00, 0x00 },
+		},
 		.argb8888_result = {
 			.dst_pitch = TEST_USE_DEFAULT_PITCH,
 			.expected = { 0xFFFF0000 },
@@ -217,6 +227,10 @@  static struct convert_xrgb8888_case convert_xrgb8888_cases[] = {
 			.dst_pitch = TEST_USE_DEFAULT_PITCH,
 			.expected = { 0x00, 0x00, 0xFF },
 		},
+		.bgr888_result = {
+			.dst_pitch = TEST_USE_DEFAULT_PITCH,
+			.expected = { 0xFF, 0x00, 0x00 },
+		},
 		.argb8888_result = {
 			.dst_pitch = TEST_USE_DEFAULT_PITCH,
 			.expected = { 0xFFFF0000 },
@@ -330,6 +344,15 @@  static struct convert_xrgb8888_case convert_xrgb8888_cases[] = {
 				0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
 			},
 		},
+		.bgr888_result = {
+			.dst_pitch = TEST_USE_DEFAULT_PITCH,
+			.expected = {
+				0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00,
+				0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00,
+				0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF,
+				0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF,
+			},
+		},
 		.argb8888_result = {
 			.dst_pitch = TEST_USE_DEFAULT_PITCH,
 			.expected = {
@@ -468,6 +491,17 @@  static struct convert_xrgb8888_case convert_xrgb8888_cases[] = {
 				0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 			},
 		},
+		.bgr888_result = {
+			.dst_pitch = 15,
+			.expected = {
+				0x0E, 0x44, 0x9C, 0x11, 0x4D, 0x05, 0xA8, 0xF3, 0x03,
+				0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+				0x6C, 0xF0, 0x73, 0x0E, 0x44, 0x9C, 0x11, 0x4D, 0x05,
+				0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+				0xA8, 0x03, 0x03, 0x6C, 0xF0, 0x73, 0x0E, 0x44, 0x9C,
+				0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			},
+		},
 		.argb8888_result = {
 			.dst_pitch = 20,
 			.expected = {
@@ -914,6 +948,52 @@  static void drm_test_fb_xrgb8888_to_rgb888(struct kunit *test)
 	KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size);
 }
 
+static void drm_test_fb_xrgb8888_to_bgr888(struct kunit *test)
+{
+	const struct convert_xrgb8888_case *params = test->param_value;
+	const struct convert_to_bgr888_result *result = &params->bgr888_result;
+	size_t dst_size;
+	u8 *buf = NULL;
+	__le32 *xrgb8888 = NULL;
+	struct iosys_map dst, src;
+
+	struct drm_framebuffer fb = {
+		.format = drm_format_info(DRM_FORMAT_XRGB8888),
+		.pitches = { params->pitch, 0, 0 },
+	};
+
+	dst_size = conversion_buf_size(DRM_FORMAT_BGR888, result->dst_pitch,
+				       &params->clip, 0);
+	KUNIT_ASSERT_GT(test, dst_size, 0);
+
+	buf = kunit_kzalloc(test, dst_size, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf);
+	iosys_map_set_vaddr(&dst, buf);
+
+	xrgb8888 = cpubuf_to_le32(test, params->xrgb8888, TEST_BUF_SIZE);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, xrgb8888);
+	iosys_map_set_vaddr(&src, xrgb8888);
+
+	/*
+	 * BGR888 expected results are already in little-endian
+	 * order, so there's no need to convert the test output.
+	 */
+	drm_fb_xrgb8888_to_bgr888(&dst, &result->dst_pitch, &src, &fb, &params->clip,
+				  &fmtcnv_state);
+	KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size);
+
+	buf = dst.vaddr; /* restore original value of buf */
+	memset(buf, 0, dst_size);
+
+	int blit_result = 0;
+
+	blit_result = drm_fb_blit(&dst, &result->dst_pitch, DRM_FORMAT_BGR888, &src, &fb, &params->clip,
+				  &fmtcnv_state);
+
+	KUNIT_EXPECT_FALSE(test, blit_result);
+	KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size);
+}
+
 static void drm_test_fb_xrgb8888_to_argb8888(struct kunit *test)
 {
 	const struct convert_xrgb8888_case *params = test->param_value;
@@ -1851,6 +1931,7 @@  static struct kunit_case drm_format_helper_test_cases[] = {
 	KUNIT_CASE_PARAM(drm_test_fb_xrgb8888_to_argb1555, convert_xrgb8888_gen_params),
 	KUNIT_CASE_PARAM(drm_test_fb_xrgb8888_to_rgba5551, convert_xrgb8888_gen_params),
 	KUNIT_CASE_PARAM(drm_test_fb_xrgb8888_to_rgb888, convert_xrgb8888_gen_params),
+	KUNIT_CASE_PARAM(drm_test_fb_xrgb8888_to_bgr888, convert_xrgb8888_gen_params),
 	KUNIT_CASE_PARAM(drm_test_fb_xrgb8888_to_argb8888, convert_xrgb8888_gen_params),
 	KUNIT_CASE_PARAM(drm_test_fb_xrgb8888_to_xrgb2101010, convert_xrgb8888_gen_params),
 	KUNIT_CASE_PARAM(drm_test_fb_xrgb8888_to_argb2101010, convert_xrgb8888_gen_params),
diff --git a/include/drm/drm_format_helper.h b/include/drm/drm_format_helper.h
index 428d81afe..aa1604d92 100644
--- a/include/drm/drm_format_helper.h
+++ b/include/drm/drm_format_helper.h
@@ -96,6 +96,9 @@  void drm_fb_xrgb8888_to_rgba5551(struct iosys_map *dst, const unsigned int *dst_
 void drm_fb_xrgb8888_to_rgb888(struct iosys_map *dst, const unsigned int *dst_pitch,
 			       const struct iosys_map *src, const struct drm_framebuffer *fb,
 			       const struct drm_rect *clip, struct drm_format_conv_state *state);
+void drm_fb_xrgb8888_to_bgr888(struct iosys_map *dst, const unsigned int *dst_pitch,
+			       const struct iosys_map *src, const struct drm_framebuffer *fb,
+			       const struct drm_rect *clip, struct drm_format_conv_state *state);
 void drm_fb_xrgb8888_to_argb8888(struct iosys_map *dst, const unsigned int *dst_pitch,
 				 const struct iosys_map *src, const struct drm_framebuffer *fb,
 				 const struct drm_rect *clip, struct drm_format_conv_state *state);