diff mbox series

[v3] drm/mediatek: Add AFBC support to Mediatek DRM driver

Message ID 20221012191226.1646315-1-greenjustin@chromium.org (mailing list archive)
State New, archived
Headers show
Series [v3] drm/mediatek: Add AFBC support to Mediatek DRM driver | expand

Commit Message

Justin Green Oct. 12, 2022, 7:12 p.m. UTC
Tested on MT8195 and confirmed both correct video output and improved DRAM
bandwidth performance.

v3:
* Replaced pitch bitshift math with union based approach.
* Refactored overlay register writes to shared code between non-AFBC and
  AFBC.
* Minor code cleanups.

v2:
* Marked mtk_ovl_set_afbc as static.
* Reflowed some lines to fit column limit.

Signed-off-by: Justin Green <greenjustin@chromium.org>
---
 drivers/gpu/drm/mediatek/mtk_disp_ovl.c  | 90 +++++++++++++++++++++++-
 drivers/gpu/drm/mediatek/mtk_drm_plane.c | 37 +++++++++-
 drivers/gpu/drm/mediatek/mtk_drm_plane.h |  8 +++
 3 files changed, 131 insertions(+), 4 deletions(-)

Comments

AngeloGioacchino Del Regno Oct. 13, 2022, 8:04 a.m. UTC | #1
Il 12/10/22 21:12, Justin Green ha scritto:
> Tested on MT8195 and confirmed both correct video output and improved DRAM
> bandwidth performance.
> 
> v3:
> * Replaced pitch bitshift math with union based approach.
> * Refactored overlay register writes to shared code between non-AFBC and
>    AFBC.
> * Minor code cleanups.
> 
> v2:
> * Marked mtk_ovl_set_afbc as static.
> * Reflowed some lines to fit column limit.
> 
> Signed-off-by: Justin Green <greenjustin@chromium.org>
> ---
>   drivers/gpu/drm/mediatek/mtk_disp_ovl.c  | 90 +++++++++++++++++++++++-
>   drivers/gpu/drm/mediatek/mtk_drm_plane.c | 37 +++++++++-
>   drivers/gpu/drm/mediatek/mtk_drm_plane.h |  8 +++
>   3 files changed, 131 insertions(+), 4 deletions(-)
> 
> diff --git a/drivers/gpu/drm/mediatek/mtk_disp_ovl.c b/drivers/gpu/drm/mediatek/mtk_disp_ovl.c
> index 002b0f6cae1a..3f89b5f5064f 100644
> --- a/drivers/gpu/drm/mediatek/mtk_disp_ovl.c
> +++ b/drivers/gpu/drm/mediatek/mtk_disp_ovl.c

..snip..

> @@ -335,9 +396,10 @@ void mtk_ovl_layer_config(struct device *dev, unsigned int idx,
>   		addr += pending->pitch - 1;
>   	}
>   
> +	mtk_ovl_set_afbc(dev, cmdq_pkt, idx, is_afbc);

I'm sorry for not noticing that in the previous review - there's only one
more issue here: I'm not sure that *all* of the MediaTek chips have the
AFBC bits in OVL_DATAPATH_CON... this may be clashing with something else
in the layout of (very?) old chips.

The solution is simple here. Please, guard this call with:

	if (ovl->data->supports_afbc)
		mtk_ovl_set_afbc(dev, cmdq_pkt, idx, is_afbc);

...after which

Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>

Cheers!
Angelo
Justin Green Oct. 13, 2022, 2:55 p.m. UTC | #2
Thanks for the comments everyone! I'll upload a new CL sometime today.

I did want to ask though, I realize I should be using u32/u64 for
kernel code in general, but the rest of this file seems to be written
using unsigned int/unsigned long long. In this circumstance, does
keeping with the style of the original source take precedence over
general style guidelines, or vice versa?
Chun-Kuang Hu Nov. 14, 2022, 3:07 p.m. UTC | #3
* , ,
 Hi, Justin:

Justin Green <greenjustin@chromium.org> 於 2022年10月13日 週四 凌晨3:12寫道:
>
> Tested on MT8195 and confirmed both correct video output and improved DRAM
> bandwidth performance.
>
> v3:
> * Replaced pitch bitshift math with union based approach.
> * Refactored overlay register writes to shared code between non-AFBC and
>   AFBC.
> * Minor code cleanups.
>
> v2:
> * Marked mtk_ovl_set_afbc as static.
> * Reflowed some lines to fit column limit.
>
> Signed-off-by: Justin Green <greenjustin@chromium.org>
> ---
>  drivers/gpu/drm/mediatek/mtk_disp_ovl.c  | 90 +++++++++++++++++++++++-
>  drivers/gpu/drm/mediatek/mtk_drm_plane.c | 37 +++++++++-
>  drivers/gpu/drm/mediatek/mtk_drm_plane.h |  8 +++
>  3 files changed, 131 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/gpu/drm/mediatek/mtk_disp_ovl.c b/drivers/gpu/drm/mediatek/mtk_disp_ovl.c
> index 002b0f6cae1a..3f89b5f5064f 100644
> --- a/drivers/gpu/drm/mediatek/mtk_disp_ovl.c
> +++ b/drivers/gpu/drm/mediatek/mtk_disp_ovl.c
> @@ -29,17 +29,24 @@
>  #define DISP_REG_OVL_DATAPATH_CON              0x0024
>  #define OVL_LAYER_SMI_ID_EN                            BIT(0)
>  #define OVL_BGCLR_SEL_IN                               BIT(2)
> +#define OVL_LAYER_AFBC_EN(n)                           BIT(4+n)
>  #define DISP_REG_OVL_ROI_BGCLR                 0x0028
>  #define DISP_REG_OVL_SRC_CON                   0x002c
>  #define DISP_REG_OVL_CON(n)                    (0x0030 + 0x20 * (n))
>  #define DISP_REG_OVL_SRC_SIZE(n)               (0x0038 + 0x20 * (n))
>  #define DISP_REG_OVL_OFFSET(n)                 (0x003c + 0x20 * (n))
> +#define DISP_REG_OVL_PITCH_MSB(n)              (0x0040 + 0x20 * (n))
> +#define OVL_PITCH_MSB_2ND_SUBBUF                       BIT(16)
> +#define OVL_PITCH_MSB_YUV_TRANS                        BIT(20)

Useless, so drop it.

>  #define DISP_REG_OVL_PITCH(n)                  (0x0044 + 0x20 * (n))
> +#define DISP_REG_OVL_CLIP(n)                   (0x004c + 0x20 * (n))

Useless, so drop it.

>  #define DISP_REG_OVL_RDMA_CTRL(n)              (0x00c0 + 0x20 * (n))
>  #define DISP_REG_OVL_RDMA_GMC(n)               (0x00c8 + 0x20 * (n))
>  #define DISP_REG_OVL_ADDR_MT2701               0x0040
>  #define DISP_REG_OVL_ADDR_MT8173               0x0f40
>  #define DISP_REG_OVL_ADDR(ovl, n)              ((ovl)->data->addr + 0x20 * (n))
> +#define DISP_REG_OVL_HDR_ADDR(ovl, n)          ((ovl)->data->addr + 0x20 * (n) + 0x04)
> +#define DISP_REG_OVL_HDR_PITCH(ovl, n)         ((ovl)->data->addr + 0x20 * (n) + 0x08)
>
>  #define GMC_THRESHOLD_BITS     16
>  #define GMC_THRESHOLD_HIGH     ((1 << GMC_THRESHOLD_BITS) / 4)
> @@ -67,6 +74,7 @@ struct mtk_disp_ovl_data {
>         unsigned int layer_nr;
>         bool fmt_rgb565_is_0;
>         bool smi_id_en;
> +       bool supports_afbc;
>  };
>
>  /*
> @@ -172,7 +180,22 @@ void mtk_ovl_stop(struct device *dev)
>                 reg = reg & ~OVL_LAYER_SMI_ID_EN;
>                 writel_relaxed(reg, ovl->regs + DISP_REG_OVL_DATAPATH_CON);
>         }
> +}
> +
> +static void mtk_ovl_set_afbc(struct device *dev, struct cmdq_pkt *cmdq_pkt,
> +                            int idx, bool enabled)
> +{
> +       struct mtk_disp_ovl *ovl = dev_get_drvdata(dev);

This is a ovl internal function, so you could pass ovl directly into
this function.

> +       unsigned int reg;
>
> +       reg = readl(ovl->regs + DISP_REG_OVL_DATAPATH_CON);
> +       if (enabled)
> +               reg = reg | OVL_LAYER_AFBC_EN(idx);
> +       else
> +               reg = reg & ~OVL_LAYER_AFBC_EN(idx);
> +
> +       mtk_ddp_write_relaxed(cmdq_pkt, reg, &ovl->cmdq_reg,
> +                             ovl->regs, DISP_REG_OVL_DATAPATH_CON);

In normal case, read/write register by cmdq, so

mtk_ddp_write_mask(cmdq_pkt, enable ? OVL_LAYER_AFBC_EN(idx) : 0,
                               &ovl->cmdq_reg, ovl->regs,
DISP_REG_OVL_DATAPATH_CON,
                               OVL_LAYER_AFBC_EN(idx));



>  }
>
>  void mtk_ovl_config(struct device *dev, unsigned int w,
> @@ -226,6 +249,32 @@ int mtk_ovl_layer_check(struct device *dev, unsigned int idx,
>         if (state->fb->format->is_yuv && rotation != 0)
>                 return -EINVAL;
>
> +       if (state->fb->modifier) {
> +               unsigned long long modifier;
> +               unsigned int fourcc;
> +               struct mtk_disp_ovl *ovl = dev_get_drvdata(dev);
> +
> +               if (!ovl->data->supports_afbc)
> +                       return -EINVAL;
> +
> +               modifier = state->fb->modifier;
> +
> +               if (modifier != DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_32x8 |
> +                                                       AFBC_FORMAT_MOD_SPLIT |
> +                                                       AFBC_FORMAT_MOD_SPARSE))
> +                       return -EINVAL;
> +
> +               fourcc = state->fb->format->format;
> +               if (fourcc != DRM_FORMAT_BGRA8888 &&
> +                   fourcc != DRM_FORMAT_ABGR8888 &&
> +                   fourcc != DRM_FORMAT_ARGB8888 &&
> +                   fourcc != DRM_FORMAT_XRGB8888 &&
> +                   fourcc != DRM_FORMAT_XBGR8888 &&
> +                   fourcc != DRM_FORMAT_RGB888 &&
> +                   fourcc != DRM_FORMAT_BGR888)
> +                       return -EINVAL;
> +       }
> +
>         state->rotation = rotation;
>
>         return 0;
> @@ -310,11 +359,23 @@ void mtk_ovl_layer_config(struct device *dev, unsigned int idx,
>         struct mtk_disp_ovl *ovl = dev_get_drvdata(dev);
>         struct mtk_plane_pending_state *pending = &state->pending;
>         unsigned int addr = pending->addr;
> -       unsigned int pitch = pending->pitch & 0xffff;
> +       unsigned int hdr_addr = pending->hdr_addr;
> +       unsigned int pitch = pending->pitch;
> +       unsigned int hdr_pitch = pending->hdr_pitch;
>         unsigned int fmt = pending->format;
>         unsigned int offset = (pending->y << 16) | pending->x;
>         unsigned int src_size = (pending->height << 16) | pending->width;
>         unsigned int con;
> +       bool is_afbc = pending->modifier;
> +       union overlay_pitch {
> +               struct split_pitch {
> +                       u16 lsb;
> +                       u16 msb;
> +               } split_pitch;
> +               u32 pitch;
> +       } overlay_pitch;
> +
> +       overlay_pitch.pitch = pitch;
>
>         if (!pending->enable) {
>                 mtk_ovl_layer_off(dev, idx, cmdq_pkt);
> @@ -335,9 +396,10 @@ void mtk_ovl_layer_config(struct device *dev, unsigned int idx,
>                 addr += pending->pitch - 1;
>         }
>
> +       mtk_ovl_set_afbc(dev, cmdq_pkt, idx, is_afbc);
>         mtk_ddp_write_relaxed(cmdq_pkt, con, &ovl->cmdq_reg, ovl->regs,
>                               DISP_REG_OVL_CON(idx));
> -       mtk_ddp_write_relaxed(cmdq_pkt, pitch, &ovl->cmdq_reg, ovl->regs,
> +       mtk_ddp_write_relaxed(cmdq_pkt, overlay_pitch.split_pitch.lsb, &ovl->cmdq_reg, ovl->regs,
>                               DISP_REG_OVL_PITCH(idx));

Is this general for all MediaTek SoC? If so, separate this to an
independent patch. Otherwise, use a device variable to separate this
setting.

>         mtk_ddp_write_relaxed(cmdq_pkt, src_size, &ovl->cmdq_reg, ovl->regs,
>                               DISP_REG_OVL_SRC_SIZE(idx));
> @@ -345,6 +407,19 @@ void mtk_ovl_layer_config(struct device *dev, unsigned int idx,
>                               DISP_REG_OVL_OFFSET(idx));
>         mtk_ddp_write_relaxed(cmdq_pkt, addr, &ovl->cmdq_reg, ovl->regs,
>                               DISP_REG_OVL_ADDR(ovl, idx));
> +       if (is_afbc) {
> +               mtk_ddp_write_relaxed(cmdq_pkt, hdr_addr, &ovl->cmdq_reg, ovl->regs,
> +                                     DISP_REG_OVL_HDR_ADDR(ovl, idx));
> +               mtk_ddp_write_relaxed(cmdq_pkt,
> +                                     OVL_PITCH_MSB_2ND_SUBBUF | overlay_pitch.split_pitch.msb,
> +                                     &ovl->cmdq_reg, ovl->regs, DISP_REG_OVL_PITCH_MSB(idx));
> +               mtk_ddp_write_relaxed(cmdq_pkt, hdr_pitch, &ovl->cmdq_reg, ovl->regs,
> +                                     DISP_REG_OVL_HDR_PITCH(ovl, idx));
> +       } else {
> +               mtk_ddp_write_relaxed(cmdq_pkt,
> +                                     overlay_pitch.split_pitch.msb,
> +                                     &ovl->cmdq_reg, ovl->regs, DISP_REG_OVL_PITCH_MSB(idx));
> +       }
>
>         mtk_ovl_layer_on(dev, idx, cmdq_pkt);
>  }
> @@ -492,6 +567,15 @@ static const struct mtk_disp_ovl_data mt8192_ovl_2l_driver_data = {
>         .smi_id_en = true,
>  };
>
> +static const struct mtk_disp_ovl_data mt8195_ovl_driver_data = {

In this binding document, mt8195 ovl is compatible to mt8133 ovl.
Please confirm that mt8195 is not identical with mt8133.

> +       .addr = DISP_REG_OVL_ADDR_MT8173,
> +       .gmc_bits = 10,
> +       .layer_nr = 4,
> +       .fmt_rgb565_is_0 = true,
> +       .smi_id_en = true,
> +       .supports_afbc = true,
> +};
> +
>  static const struct of_device_id mtk_disp_ovl_driver_dt_match[] = {
>         { .compatible = "mediatek,mt2701-disp-ovl",
>           .data = &mt2701_ovl_driver_data},
> @@ -505,6 +589,8 @@ static const struct of_device_id mtk_disp_ovl_driver_dt_match[] = {
>           .data = &mt8192_ovl_driver_data},
>         { .compatible = "mediatek,mt8192-disp-ovl-2l",
>           .data = &mt8192_ovl_2l_driver_data},
> +       { .compatible = "mediatek,mt8195-disp-ovl",
> +         .data = &mt8195_ovl_driver_data},
>         {},
>  };
>  MODULE_DEVICE_TABLE(of, mtk_disp_ovl_driver_dt_match);
> diff --git a/drivers/gpu/drm/mediatek/mtk_drm_plane.c b/drivers/gpu/drm/mediatek/mtk_drm_plane.c
> index 5c0d9ce69931..a285b9ff5081 100644
> --- a/drivers/gpu/drm/mediatek/mtk_drm_plane.c
> +++ b/drivers/gpu/drm/mediatek/mtk_drm_plane.c
> @@ -12,6 +12,7 @@
>  #include <drm/drm_framebuffer.h>
>  #include <drm/drm_gem_atomic_helper.h>
>  #include <drm/drm_plane_helper.h>
> +#include <linux/align.h>
>
>  #include "mtk_drm_crtc.h"
>  #include "mtk_drm_ddp_comp.h"
> @@ -52,6 +53,7 @@ static void mtk_plane_reset(struct drm_plane *plane)
>
>         state->base.plane = plane;
>         state->pending.format = DRM_FORMAT_RGB565;
> +       state->pending.modifier = 0;
>  }
>
>  static struct drm_plane_state *mtk_plane_duplicate_state(struct drm_plane *plane)
> @@ -120,21 +122,52 @@ static void mtk_plane_update_new_state(struct drm_plane_state *new_state,
>         struct drm_gem_object *gem;
>         struct mtk_drm_gem_obj *mtk_gem;
>         unsigned int pitch, format;
> +       unsigned long long modifier;
>         dma_addr_t addr;
> +       dma_addr_t hdr_addr = 0;
> +       unsigned int hdr_pitch = 0;
>
>         gem = fb->obj[0];
>         mtk_gem = to_mtk_gem_obj(gem);
>         addr = mtk_gem->dma_addr;
>         pitch = fb->pitches[0];
>         format = fb->format->format;
> +       modifier = fb->modifier;
>
> -       addr += (new_state->src.x1 >> 16) * fb->format->cpp[0];
> -       addr += (new_state->src.y1 >> 16) * pitch;
> +       if (!modifier) {
> +               addr += (new_state->src.x1 >> 16) * fb->format->cpp[0];
> +               addr += (new_state->src.y1 >> 16) * pitch;
> +       } else {
> +               int width_in_blocks = ALIGN(fb->width, AFBC_DATA_BLOCK_WIDTH)
> +                                     / AFBC_DATA_BLOCK_WIDTH;
> +               int height_in_blocks = ALIGN(fb->height, AFBC_DATA_BLOCK_HEIGHT)
> +                                      / AFBC_DATA_BLOCK_HEIGHT;
> +               int x_offset_in_blocks = (new_state->src.x1 >> 16) / AFBC_DATA_BLOCK_WIDTH;
> +               int y_offset_in_blocks = (new_state->src.y1 >> 16) / AFBC_DATA_BLOCK_HEIGHT;
> +               int hdr_size;
> +
> +               hdr_pitch = width_in_blocks * AFBC_HEADER_BLOCK_SIZE;
> +               pitch = width_in_blocks * AFBC_DATA_BLOCK_WIDTH *
> +                       AFBC_DATA_BLOCK_HEIGHT * fb->format->cpp[0];
> +
> +               hdr_size = ALIGN(hdr_pitch * height_in_blocks, AFBC_HEADER_ALIGNMENT);

Usually the pitch needs alignment. So I guess the formula is

hdr_pitch = ALIGN(width_in_blocks * AFBC_HEADER_BLOCK_SIZE,
AFBC_HEADER_ALIGNMENT);
hdr_size = hdr_pitch * height_in_blocks;

Could you explain the meaning of hdr_pitch?

Regards,
Chun-Kuang.

> +
> +               hdr_addr = addr + hdr_pitch * y_offset_in_blocks +
> +                          AFBC_HEADER_BLOCK_SIZE * x_offset_in_blocks;
> +               /* The data plane is offset by 1 additional block. */
> +               addr = addr + hdr_size +
> +                      pitch * y_offset_in_blocks +
> +                      AFBC_DATA_BLOCK_WIDTH * AFBC_DATA_BLOCK_HEIGHT *
> +                      fb->format->cpp[0] * (x_offset_in_blocks + 1);
> +       }
>
>         mtk_plane_state->pending.enable = true;
>         mtk_plane_state->pending.pitch = pitch;
> +       mtk_plane_state->pending.hdr_pitch = hdr_pitch;
>         mtk_plane_state->pending.format = format;
> +       mtk_plane_state->pending.modifier = modifier;
>         mtk_plane_state->pending.addr = addr;
> +       mtk_plane_state->pending.hdr_addr = hdr_addr;
>         mtk_plane_state->pending.x = new_state->dst.x1;
>         mtk_plane_state->pending.y = new_state->dst.y1;
>         mtk_plane_state->pending.width = drm_rect_width(&new_state->dst);
> diff --git a/drivers/gpu/drm/mediatek/mtk_drm_plane.h b/drivers/gpu/drm/mediatek/mtk_drm_plane.h
> index 2d5ec66e3df1..8f39011cdbfc 100644
> --- a/drivers/gpu/drm/mediatek/mtk_drm_plane.h
> +++ b/drivers/gpu/drm/mediatek/mtk_drm_plane.h
> @@ -10,12 +10,20 @@
>  #include <drm/drm_crtc.h>
>  #include <linux/types.h>
>
> +#define AFBC_DATA_BLOCK_WIDTH 32
> +#define AFBC_DATA_BLOCK_HEIGHT 8
> +#define AFBC_HEADER_BLOCK_SIZE 16
> +#define AFBC_HEADER_ALIGNMENT 1024
> +
>  struct mtk_plane_pending_state {
>         bool                            config;
>         bool                            enable;
>         dma_addr_t                      addr;
> +       dma_addr_t                      hdr_addr;
>         unsigned int                    pitch;
> +       unsigned int                    hdr_pitch;
>         unsigned int                    format;
> +       unsigned long long              modifier;
>         unsigned int                    x;
>         unsigned int                    y;
>         unsigned int                    width;
> --
> 2.38.0.rc1.362.ged0d419d3c-goog
>
Justin Green Nov. 16, 2022, 3:45 p.m. UTC | #4
Hi Chun-Kuang,

> > +       mtk_ovl_set_afbc(dev, cmdq_pkt, idx, is_afbc);
> >         mtk_ddp_write_relaxed(cmdq_pkt, con, &ovl->cmdq_reg, ovl->regs,
> >                               DISP_REG_OVL_CON(idx));
> > -       mtk_ddp_write_relaxed(cmdq_pkt, pitch, &ovl->cmdq_reg, ovl->regs,
> > +       mtk_ddp_write_relaxed(cmdq_pkt, overlay_pitch.split_pitch.lsb, &ovl->cmdq_reg, ovl->regs,
> >                               DISP_REG_OVL_PITCH(idx));
>
> Is this general for all MediaTek SoC? If so, separate this to an
> independent patch. Otherwise, use a device variable to separate this
> setting.

Yes and no. Technically all MediaTek SoCs have two separate registers
for the pitch, each are 16 bits, so this code is technically always
needed. However, because the lsb register is 16 bit, this issue has
never come up, because nobody has tried to display a plane that was
16384 ARGB pixels across. In fact, I think most MediaTek SoCs have a
resolution limit of 4K. The reason this issue comes up now is because
"pitch" is calculated differently for AFBC frames, and actually refers
to the size in bytes of one row of AFBC blocks. Should I still
separate this into an independent patch?

> >  }
> > @@ -492,6 +567,15 @@ static const struct mtk_disp_ovl_data mt8192_ovl_2l_driver_data = {
> >         .smi_id_en = true,
> >  };
> >
> > +static const struct mtk_disp_ovl_data mt8195_ovl_driver_data = {
>
> In this binding document, mt8195 ovl is compatible to mt8133 ovl.
> Please confirm that mt8195 is not identical with mt8133.

Do you mean MT8183? If so, we do not have any documentation indicating
that the MT8183 supports AFBC. Do you have some reason to believe
otherwise?

> Usually the pitch needs alignment. So I guess the formula is
>
> hdr_pitch = ALIGN(width_in_blocks * AFBC_HEADER_BLOCK_SIZE,
> AFBC_HEADER_ALIGNMENT);
> hdr_size = hdr_pitch * height_in_blocks;

The documentation does not indicate that the pitch needs alignment
beyond the AFBC header block size.

> Could you explain the meaning of hdr_pitch?

hdr_pitch refers to the size in bytes of one row of AFBC header
blocks. AFBC is a proprietary compressed frame buffer format, but from
what public information we have, it appears to be block compressed
data stored in 2 contiguous planes. The first is called the "header"
plane, and the second is called the "body" plane. The header plane
contains metadata for each block of pixel data, and the body plane
contains sparse compressed block data.


I'll send another patch with the other changes you mentioned.

Thanks!
Justin
diff mbox series

Patch

diff --git a/drivers/gpu/drm/mediatek/mtk_disp_ovl.c b/drivers/gpu/drm/mediatek/mtk_disp_ovl.c
index 002b0f6cae1a..3f89b5f5064f 100644
--- a/drivers/gpu/drm/mediatek/mtk_disp_ovl.c
+++ b/drivers/gpu/drm/mediatek/mtk_disp_ovl.c
@@ -29,17 +29,24 @@ 
 #define DISP_REG_OVL_DATAPATH_CON		0x0024
 #define OVL_LAYER_SMI_ID_EN				BIT(0)
 #define OVL_BGCLR_SEL_IN				BIT(2)
+#define OVL_LAYER_AFBC_EN(n)				BIT(4+n)
 #define DISP_REG_OVL_ROI_BGCLR			0x0028
 #define DISP_REG_OVL_SRC_CON			0x002c
 #define DISP_REG_OVL_CON(n)			(0x0030 + 0x20 * (n))
 #define DISP_REG_OVL_SRC_SIZE(n)		(0x0038 + 0x20 * (n))
 #define DISP_REG_OVL_OFFSET(n)			(0x003c + 0x20 * (n))
+#define DISP_REG_OVL_PITCH_MSB(n)		(0x0040 + 0x20 * (n))
+#define OVL_PITCH_MSB_2ND_SUBBUF			BIT(16)
+#define OVL_PITCH_MSB_YUV_TRANS			BIT(20)
 #define DISP_REG_OVL_PITCH(n)			(0x0044 + 0x20 * (n))
+#define DISP_REG_OVL_CLIP(n)			(0x004c + 0x20 * (n))
 #define DISP_REG_OVL_RDMA_CTRL(n)		(0x00c0 + 0x20 * (n))
 #define DISP_REG_OVL_RDMA_GMC(n)		(0x00c8 + 0x20 * (n))
 #define DISP_REG_OVL_ADDR_MT2701		0x0040
 #define DISP_REG_OVL_ADDR_MT8173		0x0f40
 #define DISP_REG_OVL_ADDR(ovl, n)		((ovl)->data->addr + 0x20 * (n))
+#define DISP_REG_OVL_HDR_ADDR(ovl, n)		((ovl)->data->addr + 0x20 * (n) + 0x04)
+#define DISP_REG_OVL_HDR_PITCH(ovl, n)		((ovl)->data->addr + 0x20 * (n) + 0x08)
 
 #define GMC_THRESHOLD_BITS	16
 #define GMC_THRESHOLD_HIGH	((1 << GMC_THRESHOLD_BITS) / 4)
@@ -67,6 +74,7 @@  struct mtk_disp_ovl_data {
 	unsigned int layer_nr;
 	bool fmt_rgb565_is_0;
 	bool smi_id_en;
+	bool supports_afbc;
 };
 
 /*
@@ -172,7 +180,22 @@  void mtk_ovl_stop(struct device *dev)
 		reg = reg & ~OVL_LAYER_SMI_ID_EN;
 		writel_relaxed(reg, ovl->regs + DISP_REG_OVL_DATAPATH_CON);
 	}
+}
+
+static void mtk_ovl_set_afbc(struct device *dev, struct cmdq_pkt *cmdq_pkt,
+			     int idx, bool enabled)
+{
+	struct mtk_disp_ovl *ovl = dev_get_drvdata(dev);
+	unsigned int reg;
 
+	reg = readl(ovl->regs + DISP_REG_OVL_DATAPATH_CON);
+	if (enabled)
+		reg = reg | OVL_LAYER_AFBC_EN(idx);
+	else
+		reg = reg & ~OVL_LAYER_AFBC_EN(idx);
+
+	mtk_ddp_write_relaxed(cmdq_pkt, reg, &ovl->cmdq_reg,
+			      ovl->regs, DISP_REG_OVL_DATAPATH_CON);
 }
 
 void mtk_ovl_config(struct device *dev, unsigned int w,
@@ -226,6 +249,32 @@  int mtk_ovl_layer_check(struct device *dev, unsigned int idx,
 	if (state->fb->format->is_yuv && rotation != 0)
 		return -EINVAL;
 
+	if (state->fb->modifier) {
+		unsigned long long modifier;
+		unsigned int fourcc;
+		struct mtk_disp_ovl *ovl = dev_get_drvdata(dev);
+
+		if (!ovl->data->supports_afbc)
+			return -EINVAL;
+
+		modifier = state->fb->modifier;
+
+		if (modifier != DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_32x8 |
+							AFBC_FORMAT_MOD_SPLIT |
+							AFBC_FORMAT_MOD_SPARSE))
+			return -EINVAL;
+
+		fourcc = state->fb->format->format;
+		if (fourcc != DRM_FORMAT_BGRA8888 &&
+		    fourcc != DRM_FORMAT_ABGR8888 &&
+		    fourcc != DRM_FORMAT_ARGB8888 &&
+		    fourcc != DRM_FORMAT_XRGB8888 &&
+		    fourcc != DRM_FORMAT_XBGR8888 &&
+		    fourcc != DRM_FORMAT_RGB888 &&
+		    fourcc != DRM_FORMAT_BGR888)
+			return -EINVAL;
+	}
+
 	state->rotation = rotation;
 
 	return 0;
@@ -310,11 +359,23 @@  void mtk_ovl_layer_config(struct device *dev, unsigned int idx,
 	struct mtk_disp_ovl *ovl = dev_get_drvdata(dev);
 	struct mtk_plane_pending_state *pending = &state->pending;
 	unsigned int addr = pending->addr;
-	unsigned int pitch = pending->pitch & 0xffff;
+	unsigned int hdr_addr = pending->hdr_addr;
+	unsigned int pitch = pending->pitch;
+	unsigned int hdr_pitch = pending->hdr_pitch;
 	unsigned int fmt = pending->format;
 	unsigned int offset = (pending->y << 16) | pending->x;
 	unsigned int src_size = (pending->height << 16) | pending->width;
 	unsigned int con;
+	bool is_afbc = pending->modifier;
+	union overlay_pitch {
+		struct split_pitch {
+			u16 lsb;
+			u16 msb;
+		} split_pitch;
+		u32 pitch;
+	} overlay_pitch;
+
+	overlay_pitch.pitch = pitch;
 
 	if (!pending->enable) {
 		mtk_ovl_layer_off(dev, idx, cmdq_pkt);
@@ -335,9 +396,10 @@  void mtk_ovl_layer_config(struct device *dev, unsigned int idx,
 		addr += pending->pitch - 1;
 	}
 
+	mtk_ovl_set_afbc(dev, cmdq_pkt, idx, is_afbc);
 	mtk_ddp_write_relaxed(cmdq_pkt, con, &ovl->cmdq_reg, ovl->regs,
 			      DISP_REG_OVL_CON(idx));
-	mtk_ddp_write_relaxed(cmdq_pkt, pitch, &ovl->cmdq_reg, ovl->regs,
+	mtk_ddp_write_relaxed(cmdq_pkt, overlay_pitch.split_pitch.lsb, &ovl->cmdq_reg, ovl->regs,
 			      DISP_REG_OVL_PITCH(idx));
 	mtk_ddp_write_relaxed(cmdq_pkt, src_size, &ovl->cmdq_reg, ovl->regs,
 			      DISP_REG_OVL_SRC_SIZE(idx));
@@ -345,6 +407,19 @@  void mtk_ovl_layer_config(struct device *dev, unsigned int idx,
 			      DISP_REG_OVL_OFFSET(idx));
 	mtk_ddp_write_relaxed(cmdq_pkt, addr, &ovl->cmdq_reg, ovl->regs,
 			      DISP_REG_OVL_ADDR(ovl, idx));
+	if (is_afbc) {
+		mtk_ddp_write_relaxed(cmdq_pkt, hdr_addr, &ovl->cmdq_reg, ovl->regs,
+				      DISP_REG_OVL_HDR_ADDR(ovl, idx));
+		mtk_ddp_write_relaxed(cmdq_pkt,
+				      OVL_PITCH_MSB_2ND_SUBBUF | overlay_pitch.split_pitch.msb,
+				      &ovl->cmdq_reg, ovl->regs, DISP_REG_OVL_PITCH_MSB(idx));
+		mtk_ddp_write_relaxed(cmdq_pkt, hdr_pitch, &ovl->cmdq_reg, ovl->regs,
+				      DISP_REG_OVL_HDR_PITCH(ovl, idx));
+	} else {
+		mtk_ddp_write_relaxed(cmdq_pkt,
+				      overlay_pitch.split_pitch.msb,
+				      &ovl->cmdq_reg, ovl->regs, DISP_REG_OVL_PITCH_MSB(idx));
+	}
 
 	mtk_ovl_layer_on(dev, idx, cmdq_pkt);
 }
@@ -492,6 +567,15 @@  static const struct mtk_disp_ovl_data mt8192_ovl_2l_driver_data = {
 	.smi_id_en = true,
 };
 
+static const struct mtk_disp_ovl_data mt8195_ovl_driver_data = {
+	.addr = DISP_REG_OVL_ADDR_MT8173,
+	.gmc_bits = 10,
+	.layer_nr = 4,
+	.fmt_rgb565_is_0 = true,
+	.smi_id_en = true,
+	.supports_afbc = true,
+};
+
 static const struct of_device_id mtk_disp_ovl_driver_dt_match[] = {
 	{ .compatible = "mediatek,mt2701-disp-ovl",
 	  .data = &mt2701_ovl_driver_data},
@@ -505,6 +589,8 @@  static const struct of_device_id mtk_disp_ovl_driver_dt_match[] = {
 	  .data = &mt8192_ovl_driver_data},
 	{ .compatible = "mediatek,mt8192-disp-ovl-2l",
 	  .data = &mt8192_ovl_2l_driver_data},
+	{ .compatible = "mediatek,mt8195-disp-ovl",
+	  .data = &mt8195_ovl_driver_data},
 	{},
 };
 MODULE_DEVICE_TABLE(of, mtk_disp_ovl_driver_dt_match);
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_plane.c b/drivers/gpu/drm/mediatek/mtk_drm_plane.c
index 5c0d9ce69931..a285b9ff5081 100644
--- a/drivers/gpu/drm/mediatek/mtk_drm_plane.c
+++ b/drivers/gpu/drm/mediatek/mtk_drm_plane.c
@@ -12,6 +12,7 @@ 
 #include <drm/drm_framebuffer.h>
 #include <drm/drm_gem_atomic_helper.h>
 #include <drm/drm_plane_helper.h>
+#include <linux/align.h>
 
 #include "mtk_drm_crtc.h"
 #include "mtk_drm_ddp_comp.h"
@@ -52,6 +53,7 @@  static void mtk_plane_reset(struct drm_plane *plane)
 
 	state->base.plane = plane;
 	state->pending.format = DRM_FORMAT_RGB565;
+	state->pending.modifier = 0;
 }
 
 static struct drm_plane_state *mtk_plane_duplicate_state(struct drm_plane *plane)
@@ -120,21 +122,52 @@  static void mtk_plane_update_new_state(struct drm_plane_state *new_state,
 	struct drm_gem_object *gem;
 	struct mtk_drm_gem_obj *mtk_gem;
 	unsigned int pitch, format;
+	unsigned long long modifier;
 	dma_addr_t addr;
+	dma_addr_t hdr_addr = 0;
+	unsigned int hdr_pitch = 0;
 
 	gem = fb->obj[0];
 	mtk_gem = to_mtk_gem_obj(gem);
 	addr = mtk_gem->dma_addr;
 	pitch = fb->pitches[0];
 	format = fb->format->format;
+	modifier = fb->modifier;
 
-	addr += (new_state->src.x1 >> 16) * fb->format->cpp[0];
-	addr += (new_state->src.y1 >> 16) * pitch;
+	if (!modifier) {
+		addr += (new_state->src.x1 >> 16) * fb->format->cpp[0];
+		addr += (new_state->src.y1 >> 16) * pitch;
+	} else {
+		int width_in_blocks = ALIGN(fb->width, AFBC_DATA_BLOCK_WIDTH)
+				      / AFBC_DATA_BLOCK_WIDTH;
+		int height_in_blocks = ALIGN(fb->height, AFBC_DATA_BLOCK_HEIGHT)
+				       / AFBC_DATA_BLOCK_HEIGHT;
+		int x_offset_in_blocks = (new_state->src.x1 >> 16) / AFBC_DATA_BLOCK_WIDTH;
+		int y_offset_in_blocks = (new_state->src.y1 >> 16) / AFBC_DATA_BLOCK_HEIGHT;
+		int hdr_size;
+
+		hdr_pitch = width_in_blocks * AFBC_HEADER_BLOCK_SIZE;
+		pitch = width_in_blocks * AFBC_DATA_BLOCK_WIDTH *
+			AFBC_DATA_BLOCK_HEIGHT * fb->format->cpp[0];
+
+		hdr_size = ALIGN(hdr_pitch * height_in_blocks, AFBC_HEADER_ALIGNMENT);
+
+		hdr_addr = addr + hdr_pitch * y_offset_in_blocks +
+			   AFBC_HEADER_BLOCK_SIZE * x_offset_in_blocks;
+		/* The data plane is offset by 1 additional block. */
+		addr = addr + hdr_size +
+		       pitch * y_offset_in_blocks +
+		       AFBC_DATA_BLOCK_WIDTH * AFBC_DATA_BLOCK_HEIGHT *
+		       fb->format->cpp[0] * (x_offset_in_blocks + 1);
+	}
 
 	mtk_plane_state->pending.enable = true;
 	mtk_plane_state->pending.pitch = pitch;
+	mtk_plane_state->pending.hdr_pitch = hdr_pitch;
 	mtk_plane_state->pending.format = format;
+	mtk_plane_state->pending.modifier = modifier;
 	mtk_plane_state->pending.addr = addr;
+	mtk_plane_state->pending.hdr_addr = hdr_addr;
 	mtk_plane_state->pending.x = new_state->dst.x1;
 	mtk_plane_state->pending.y = new_state->dst.y1;
 	mtk_plane_state->pending.width = drm_rect_width(&new_state->dst);
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_plane.h b/drivers/gpu/drm/mediatek/mtk_drm_plane.h
index 2d5ec66e3df1..8f39011cdbfc 100644
--- a/drivers/gpu/drm/mediatek/mtk_drm_plane.h
+++ b/drivers/gpu/drm/mediatek/mtk_drm_plane.h
@@ -10,12 +10,20 @@ 
 #include <drm/drm_crtc.h>
 #include <linux/types.h>
 
+#define AFBC_DATA_BLOCK_WIDTH 32
+#define AFBC_DATA_BLOCK_HEIGHT 8
+#define AFBC_HEADER_BLOCK_SIZE 16
+#define AFBC_HEADER_ALIGNMENT 1024
+
 struct mtk_plane_pending_state {
 	bool				config;
 	bool				enable;
 	dma_addr_t			addr;
+	dma_addr_t			hdr_addr;
 	unsigned int			pitch;
+	unsigned int			hdr_pitch;
 	unsigned int			format;
+	unsigned long long		modifier;
 	unsigned int			x;
 	unsigned int			y;
 	unsigned int			width;