Re: [RFC] media: Add AFBC pixel formats
From: Nicolas Dufresne
Date: Thu Apr 16 2026 - 11:25:44 EST
Le jeudi 16 avril 2026 à 14:41 +0200, Benjamin Gaignard a écrit :
> Add 8-bit and 10-bit YUV420 Arm Frame Buffer Compression (AFBC)
> pixel formats.
>
> AFBC stride and image size computation needed to be done by
> specific helpers functions which are also exported to be used
> by drivers.
>
> Add documentation for each of the formats.
>
> Signed-off-by: Benjamin Gaignard <benjamin.gaignard@xxxxxxxxxxxxx>
> ---
> .../userspace-api/media/v4l/pixfmt-afbc.rst | 64 +++++++++++
> .../userspace-api/media/v4l/pixfmt.rst | 1 +
> drivers/media/v4l2-core/v4l2-common.c | 104 ++++++++++++++++++
> drivers/media/v4l2-core/v4l2-ioctl.c | 4 +
> include/media/v4l2-common.h | 4 +
> include/uapi/linux/videodev2.h | 6 +
> 6 files changed, 183 insertions(+)
> create mode 100644 Documentation/userspace-api/media/v4l/pixfmt-afbc.rst
>
> diff --git a/Documentation/userspace-api/media/v4l/pixfmt-afbc.rst b/Documentation/userspace-api/media/v4l/pixfmt-afbc.rst
> new file mode 100644
> index 000000000000..2867e5d45810
> --- /dev/null
> +++ b/Documentation/userspace-api/media/v4l/pixfmt-afbc.rst
> @@ -0,0 +1,64 @@
> +.. SPDX-License-Identifier: GFDL-1.1-no-invariants-or-later
> +
> +.. afbc:
> +
> +*******************************************
> +ARM Frame Buffer Compression formats (AFBC)
> +*******************************************
> +
> +The AFBC format is a lossless compression format which can support
> +up to four components. It could compress 8 bits to 64 bits per pixel.
> +The internal superblock size could be:
> +
> +- 16x16 pixels
> +
> +- 32x8 pixels
> +
> +- 64x4 pixels.
> +
> +The memory layout is composed of a header block followed by payload data.
Because these format are so tied with the Graphics, I would avoid past mistakes
with QC formats, and up-front document their matching DRM format and modifiers.
While its documented in ./Documentation/userspace-api/dma-buf-alloc-exchange.rst
I would still document a bit the way we calculate a stride for this formats, or
cross-reference that documentation, but being explicit for this specific format
may avoid a lot of issues.
> +
> +AFBC Formats
> +============
> +
> +.. tabularcolumns:: |p{5.2cm}|p{1.0cm}|p{1.5cm}|p{1.9cm}|p{1.2cm}|p{1.8cm}|
> +
> +.. flat-table:: Overview of AFBC formats
> + :header-rows: 1
> + :stub-columns: 0
> +
> + * - Identifier
> + - Code
> + - Colorspace
> + - Bits per component
> + - Superblock size
> + - Compression parameters
> + * - V4L2_PIX_FMT_AFBC_YUV420_16x16
> + - 'A168'
> + - YUV420
> + - 8 bits
> + - 16x16
> + - Sparse, Split
From my accumulate knowledge, RK35xx RKVDEC produce non-split, and this one is
split. To make it easy for naming in the future, I'd rename this one.
V4L2_PIX_FMT_AFBC_YUV420_16x16_SPLIT
Or
V4L2_PIX_FMT_AFBC_YUV420_16x16_S
V4L2_PIX_FMT_AFBC_YUV420_16x16S
Other ideas, but it would be really hard to come up with a name for "not slip".
While non-sparse is very special, and not commonly used.
> + * - V4L2_PIX_FMT_AFBC_YUV420_32x8
> + - 'A328'
> + - YUV420
> + - 8 bits
> + - 32x8
> + - Sparse
> + * - V4L2_PIX_FMT_AFBC_YUV420_16x16_10
> + - 'A16a'
> + - YUV420
> + - 10 bits
> + - 16x16
> + - Sparse, Split
> + * - V4L2_PIX_FMT_AFBC_YUV420_32x8_10
> + - 'A32a'
> + - YUV420
> + - 10 bits
> + - 32x8
> + - Sparse
> +
> +.. _V4L2-PIX-FMT-AFBC-YUV420-16x16:
> +.. _V4L2-PIX-FMT-AFBC-YUV420-32x8:
> +.. _V4L2-PIX-FMT-AFBC-YUV420-16x16-10:
> +.. _V4L2-PIX-FMT-AFBC-YUV420-32x8-10:
> diff --git a/Documentation/userspace-api/media/v4l/pixfmt.rst b/Documentation/userspace-api/media/v4l/pixfmt.rst
> index 71b29267488f..c6728b91b74f 100644
> --- a/Documentation/userspace-api/media/v4l/pixfmt.rst
> +++ b/Documentation/userspace-api/media/v4l/pixfmt.rst
> @@ -26,6 +26,7 @@ see also :ref:`VIDIOC_G_FBUF <VIDIOC_G_FBUF>`.)
> pixfmt-indexed
> pixfmt-rgb
> pixfmt-bayer
> + pixfmt-afbc
> yuv-formats
> hsv-formats
> depth-formats
> diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c
> index 554c591e1113..9187cb18a4ef 100644
> --- a/drivers/media/v4l2-core/v4l2-common.c
> +++ b/drivers/media/v4l2-core/v4l2-common.c
> @@ -332,6 +332,16 @@ const struct v4l2_format_info *v4l2_format_info(u32 format)
> { .format = V4L2_PIX_FMT_NV12MT_16X16, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 2, .comp_planes = 2, .bpp = { 1, 2, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2,
> .block_w = { 16, 8, 0, 0 }, .block_h = { 16, 8, 0, 0 }},
>
> + /* AFBC formats */
> + { .format = V4L2_PIX_FMT_AFBC_YUV420_16x16, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 6, 0, 0, 0 }, .bpp_div = { 4, 1, 1, 1 }, .hdiv = 1, .vdiv = 1,
> + .block_w = { 16, 0, 0, 0 }, .block_h = { 16, 0, 0, 0 }},
> + { .format = V4L2_PIX_FMT_AFBC_YUV420_32x8, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 6, 0, 0, 0 }, .bpp_div = { 4, 1, 1, 1 }, .hdiv = 1, .vdiv = 1,
> + .block_w = { 32, 0, 0, 0 }, .block_h = { 8, 0, 0, 0 }},
> + { .format = V4L2_PIX_FMT_AFBC_YUV420_16x16_10, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 15, 0, 0, 0 }, .bpp_div = { 8, 1, 1, 1 }, .hdiv = 1, .vdiv = 1,
> + .block_w = { 16, 0, 0, 0 }, .block_h = { 16, 0, 0, 0 }},
> + { .format = V4L2_PIX_FMT_AFBC_YUV420_32x8_10, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 15, 0, 0, 0 }, .bpp_div = { 8, 1, 1, 1 }, .hdiv = 1, .vdiv = 1,
> + .block_w = { 32, 0, 0, 0 }, .block_h = { 8, 0, 0, 0 }},
> +
> /* Bayer RGB formats */
> { .format = V4L2_PIX_FMT_SBGGR8, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
> { .format = V4L2_PIX_FMT_SGBRG8, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
> @@ -448,6 +458,97 @@ void v4l2_apply_frmsize_constraints(u32 *width, u32 *height,
> }
> EXPORT_SYMBOL_GPL(v4l2_apply_frmsize_constraints);
>
> +size_t v4l2_pixfmt_afbc_header_size(int fourcc, int width, int height)
> +{
> + int width_in_block, height_in_block;
> +
> + if (!v4l2_is_format_afbc(fourcc))
> + return 0;
> +
> + switch (fourcc) {
> + case V4L2_PIX_FMT_AFBC_YUV420_16x16:
> + case V4L2_PIX_FMT_AFBC_YUV420_16x16_10:
> + width_in_block = ALIGN(width, 16) >> 4;
> + height_in_block = ALIGN(height, 16) >> 4;
> + break;
> + case V4L2_PIX_FMT_AFBC_YUV420_32x8:
> + case V4L2_PIX_FMT_AFBC_YUV420_32x8_10:
> + width_in_block = ALIGN(width, 32) >> 5;
> + height_in_block = ALIGN(height, 8) >> 3;
> + break;
> + }
> +
> + return ALIGN(width_in_block * 16 * height_in_block, 128);
> +}
> +EXPORT_SYMBOL_GPL(v4l2_pixfmt_afbc_header_size);
> +
> +size_t v4l2_pixfmt_afbc_payload_size(int fourcc, int width, int height)
> +{
> + int width_in_block, height_in_block, block_payload_size;
> +
> + if (!v4l2_is_format_afbc(fourcc))
> + return 0;
> +
> + switch (fourcc) {
> + case V4L2_PIX_FMT_AFBC_YUV420_16x16:
> + case V4L2_PIX_FMT_AFBC_YUV420_16x16_10:
> + width_in_block = ALIGN(width, 16) >> 4;
> + height_in_block = ALIGN(height, 16) >> 4;
> + break;
> + case V4L2_PIX_FMT_AFBC_YUV420_32x8:
> + case V4L2_PIX_FMT_AFBC_YUV420_32x8_10:
> + width_in_block = ALIGN(width, 32) >> 5;
> + height_in_block = ALIGN(height, 8) >> 3;
> + break;
> + }
> +
> + switch (fourcc) {
> + case V4L2_PIX_FMT_AFBC_YUV420_16x16:
> + case V4L2_PIX_FMT_AFBC_YUV420_32x8:
> + block_payload_size = 384;
> + break;
> + case V4L2_PIX_FMT_AFBC_YUV420_16x16_10:
> + case V4L2_PIX_FMT_AFBC_YUV420_32x8_10:
> + block_payload_size = 512;
> + break;
> + }
> +
> + return ALIGN(block_payload_size * width_in_block * height_in_block, 128);
> +}
> +EXPORT_SYMBOL_GPL(v4l2_pixfmt_afbc_payload_size);
> +
> +static int v4l2_fill_pixfmt_afbc(struct v4l2_pix_format_mplane *pixfmt,
> + const struct v4l2_format_info *info)
> +{
> + struct v4l2_plane_pix_format *plane = &pixfmt->plane_fmt[0];
> + unsigned int width = pixfmt->width;
> + unsigned int height = pixfmt->height;
> + unsigned int aligned_width = ALIGN(width, v4l2_format_block_width(info, 0));
> + unsigned int stride = DIV_ROUND_UP(aligned_width, info->hdiv) *
> + info->bpp[0] / info->bpp_div[0];
> + size_t header_size = v4l2_pixfmt_afbc_header_size(info->format, width, height);
> + size_t payload_size = v4l2_pixfmt_afbc_payload_size(info->format, width, height);
> +
> + plane->bytesperline = stride;
> + plane->sizeimage = header_size + payload_size;
I want to give some more thought on this, its literally by-pass
v4l2_apply_frmsize_constraints(), and that I don't like much.
> +
> + return 0;
> +}
> +
> +bool v4l2_is_format_afbc(int fourcc)
> +{
> + switch (fourcc) {
> + case V4L2_PIX_FMT_AFBC_YUV420_16x16:
> + case V4L2_PIX_FMT_AFBC_YUV420_32x8:
> + case V4L2_PIX_FMT_AFBC_YUV420_16x16_10:
> + case V4L2_PIX_FMT_AFBC_YUV420_32x8_10:
> + return true;
> + }
> +
> + return false;
> +}
> +EXPORT_SYMBOL_GPL(v4l2_is_format_afbc);
Does that really need to be exported ?
> +
> int v4l2_fill_pixfmt_mp(struct v4l2_pix_format_mplane *pixfmt,
> u32 pixelformat, u32 width, u32 height)
> {
> @@ -464,6 +565,9 @@ int v4l2_fill_pixfmt_mp(struct v4l2_pix_format_mplane *pixfmt,
> pixfmt->pixelformat = pixelformat;
> pixfmt->num_planes = info->mem_planes;
>
> + if (v4l2_is_format_afbc(info->format))
> + return v4l2_fill_pixfmt_afbc(pixfmt, info);
> +
> if (info->mem_planes == 1) {
> plane = &pixfmt->plane_fmt[0];
> plane->bytesperline = v4l2_format_plane_stride(info, 0, width);
> diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c
> index a2b650f4ec3c..016d4244c9ee 100644
> --- a/drivers/media/v4l2-core/v4l2-ioctl.c
> +++ b/drivers/media/v4l2-core/v4l2-ioctl.c
> @@ -1387,6 +1387,10 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt)
> case V4L2_PIX_FMT_YVU422M: descr = "Planar YVU 4:2:2 (N-C)"; break;
> case V4L2_PIX_FMT_YUV444M: descr = "Planar YUV 4:4:4 (N-C)"; break;
> case V4L2_PIX_FMT_YVU444M: descr = "Planar YVU 4:4:4 (N-C)"; break;
> + case V4L2_PIX_FMT_AFBC_YUV420_16x16: descr = "AFBC 8-bit YUV420 16x16"; break;
Missing "split" in the descr, which will cause confusion in the future.
Nicolas
> + case V4L2_PIX_FMT_AFBC_YUV420_32x8: descr = "AFBC 8-bit YUV420 32x8"; break;
> + case V4L2_PIX_FMT_AFBC_YUV420_16x16_10: descr = "AFBC 10-bit YUV420 16x16"; break;
> + case V4L2_PIX_FMT_AFBC_YUV420_32x8_10: descr = "AFBC 10-bit YUV420 32x8"; break;
> case V4L2_PIX_FMT_SBGGR8: descr = "8-bit Bayer BGBG/GRGR"; break;
> case V4L2_PIX_FMT_SGBRG8: descr = "8-bit Bayer GBGB/RGRG"; break;
> case V4L2_PIX_FMT_SGRBG8: descr = "8-bit Bayer GRGR/BGBG"; break;
> diff --git a/include/media/v4l2-common.h b/include/media/v4l2-common.h
> index f8b1faced79c..d1a75e6a7d4c 100644
> --- a/include/media/v4l2-common.h
> +++ b/include/media/v4l2-common.h
> @@ -549,6 +549,10 @@ static inline bool v4l2_is_format_bayer(const struct v4l2_format_info *f)
> return f && f->pixel_enc == V4L2_PIXEL_ENC_BAYER;
> }
>
> +bool v4l2_is_format_afbc(int fourcc);
> +size_t v4l2_pixfmt_afbc_header_size(int fourcc, int width, int height);
> +size_t v4l2_pixfmt_afbc_payload_size(int fourcc, int width, int height);
> +
> const struct v4l2_format_info *v4l2_format_info(u32 format);
> void v4l2_apply_frmsize_constraints(u32 *width, u32 *height,
> const struct v4l2_frmsize_stepwise *frmsize);
> diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h
> index eda4492e40dc..88fafadbe13c 100644
> --- a/include/uapi/linux/videodev2.h
> +++ b/include/uapi/linux/videodev2.h
> @@ -698,6 +698,12 @@ struct v4l2_pix_format {
> #define V4L2_PIX_FMT_NV12M_8L128 v4l2_fourcc('N', 'A', '1', '2') /* Y/CbCr 4:2:0 8x128 tiles */
> #define V4L2_PIX_FMT_NV12M_10BE_8L128 v4l2_fourcc_be('N', 'T', '1', '2') /* Y/CbCr 4:2:0 10-bit 8x128 tiles */
>
> +/* AFBC formats */
> +#define V4L2_PIX_FMT_AFBC_YUV420_16x16 v4l2_fourcc('A', '1', '6', '8') /* AFBC containing 8-bit YUV420 in 16x16 blocks, sparse, split */
> +#define V4L2_PIX_FMT_AFBC_YUV420_32x8 v4l2_fourcc('A', '3', '2', '8') /* AFBC containing 8-bit YUV420 in 32x8 blocks, sparse */
> +#define V4L2_PIX_FMT_AFBC_YUV420_16x16_10 v4l2_fourcc('A', '1', '6', 'a') /* AFBC containing 10-bit YUV420 in 16x16 blocks, sparse, split */
> +#define V4L2_PIX_FMT_AFBC_YUV420_32x8_10 v4l2_fourcc('A', '3', '2', 'a') /* AFBC containing 10-bit YUV420 in 32x8 blocks, sparse */
> +
> /* Bayer formats - see http://www.siliconimaging.com/RGB%20Bayer.htm */
> #define V4L2_PIX_FMT_SBGGR8 v4l2_fourcc('B', 'A', '8', '1') /* 8 BGBG.. GRGR.. */
> #define V4L2_PIX_FMT_SGBRG8 v4l2_fourcc('G', 'B', 'R', 'G') /* 8 GBGB.. RGRG.. */
Attachment:
signature.asc
Description: This is a digitally signed message part