diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
index 97438bbbe3892c5b231df6e457b66509e225f7dc..2705b979ff5dd025c6e77fb33e524032c45df577 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
@@ -188,6 +188,8 @@ void rockchip_drm_mode_config_init(struct drm_device *dev)
 	dev->mode_config.max_width = 4096;
 	dev->mode_config.max_height = 4096;
 
+	dev->mode_config.allow_fb_modifiers = true;
+
 	dev->mode_config.funcs = &rockchip_drm_mode_config_funcs;
 	dev->mode_config.helper_private = &rockchip_mode_config_helpers;
 }
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
index c7d4c6073ea59b70c56559288def3fb7fd6fe215..e91b3f1882eb07589ac93ed13f6d2a00e2fded27 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
@@ -66,6 +66,12 @@
 			vop_reg_set(vop, &win_yuv2yuv->phy->name, win_yuv2yuv->base, ~0, v, #name); \
 	} while (0)
 
+#define VOP_AFBDC_SET(vop, name, v) \
+	do { \
+		if (vop->data->afbdc) \
+			vop_reg_set(vop, &vop->data->afbdc->name, 0, ~0, v, #name); \
+	} while(0)
+
 #define VOP_INTR_SET_MASK(vop, name, mask, v) \
 		vop_reg_set(vop, &vop->data->intr->name, 0, mask, v, #name)
 
@@ -131,6 +137,8 @@ struct vop {
 	struct drm_device *drm_dev;
 	bool is_enabled;
 
+	struct vop_win *afbdc_win;
+
 	struct completion dsp_hold_completion;
 
 	/* protected by dev->event_lock */
@@ -235,6 +243,11 @@ static inline uint32_t vop_get_intr_type(struct vop *vop,
 	return ret;
 }
 
+static int vop_win_id(struct vop *vop, struct vop_win *win)
+{
+	return (win - vop->win) / sizeof(struct vop_win);
+}
+
 static inline void vop_cfg_done(struct vop *vop)
 {
 	VOP_REG_SET(vop, common, cfg_done, 1);
@@ -279,6 +292,26 @@ static enum vop_data_format vop_convert_format(uint32_t format)
 	}
 }
 
+static enum vop_afbc_format vop_convert_afbc_format(uint32_t format)
+{
+	switch (format) {
+	case DRM_FORMAT_XRGB8888:
+	case DRM_FORMAT_ARGB8888:
+	case DRM_FORMAT_XBGR8888:
+	case DRM_FORMAT_ABGR8888:
+		return VOP_AFBC_RGBA8888;
+	case DRM_FORMAT_RGB888:
+	case DRM_FORMAT_BGR888:
+		return VOP_AFBC_RGB888;
+	case DRM_FORMAT_RGB565:
+	case DRM_FORMAT_BGR565:
+		return VOP_AFBC_RGB565;
+	default:
+		DRM_ERROR("unsupported AFBC format[%08x]\n", format);
+		return -EINVAL;
+	}
+}
+
 static uint16_t scl_vop_cal_scale(enum scale_mode mode, uint32_t src,
 				  uint32_t dst, bool is_horizontal,
 				  int vsu_mode, int *vskiplines)
@@ -626,6 +659,9 @@ static void vop_crtc_atomic_disable(struct drm_crtc *crtc,
 	mutex_lock(&vop->vop_lock);
 	drm_crtc_vblank_off(crtc);
 
+	if (vop->data->afbdc)
+		VOP_AFBDC_SET(vop, enable, 0);
+
 	/*
 	 * Vop standby will take effect at end of current frame,
 	 * if dsp hold valid irq happen, it means standby complete.
@@ -720,6 +756,33 @@ static int vop_plane_atomic_check(struct drm_plane *plane,
 		return -EINVAL;
 	}
 
+	/* If AFBC is enabled, some additional restrictions are imposed */
+
+	if (fb->modifier & DRM_FORMAT_MOD_ARM_AFBC(0)) {
+		struct vop *vop = to_vop(crtc);
+		unsigned width = state->src_w >> 16;
+		unsigned height = state->src_h >> 16;
+
+		if (!vop->data->afbdc) {
+			DRM_ERROR("VOP does not support AFBDC\n");
+			return -EINVAL;
+		}
+
+		if (state->src.x1 || state->src.y1) {
+			DRM_ERROR("AFBDC does not support display offsets\n");
+			return -EINVAL;
+		}
+
+		if ((width > 2560) || (height > 1600)) {
+			DRM_ERROR("AFBC framebuffer too big for decoder\n");
+			return -EINVAL;
+		}
+
+		ret = vop_convert_afbc_format(fb->format->format);
+		if (ret < 0)
+			return ret;
+	}
+
 	return 0;
 }
 
@@ -735,6 +798,9 @@ static void vop_plane_atomic_disable(struct drm_plane *plane,
 
 	spin_lock(&vop->reg_lock);
 
+	if (vop->afbdc_win == vop_win)
+		vop->afbdc_win = NULL;
+
 	VOP_WIN_SET(vop, win, enable, 0);
 
 	spin_unlock(&vop->reg_lock);
@@ -775,6 +841,9 @@ static void vop_plane_atomic_update(struct drm_plane *plane,
 	if (WARN_ON(!vop->is_enabled))
 		return;
 
+	if (vop->afbdc_win == vop_win)
+		vop->afbdc_win = NULL;
+
 	if (!state->visible) {
 		vop_plane_atomic_disable(plane, old_state);
 		return;
@@ -809,6 +878,19 @@ static void vop_plane_atomic_update(struct drm_plane *plane,
 
 	spin_lock(&vop->reg_lock);
 
+	if (fb->modifier & DRM_FORMAT_MOD_ARM_AFBC(0)) {
+		enum vop_afbc_format afbc_format =
+			vop_convert_afbc_format(fb->format->format);
+
+		VOP_AFBDC_SET(vop, format, afbc_format | (1 << 4));
+		VOP_AFBDC_SET(vop, hreg_block_split, 0);
+		VOP_AFBDC_SET(vop, win_sel, vop_win_id(vop, vop_win));
+		VOP_AFBDC_SET(vop, hdr_ptr, dma_addr);
+		VOP_AFBDC_SET(vop, pic_size, act_info);
+
+		vop->afbdc_win = vop_win;
+	}
+
 	VOP_WIN_SET(vop, win, format, format);
 	VOP_WIN_SET(vop, win, yrgb_vir, DIV_ROUND_UP(fb->pitches[0], 4));
 	VOP_WIN_SET(vop, win, yrgb_mst, dma_addr);
@@ -1153,6 +1235,8 @@ static void vop_crtc_atomic_flush(struct drm_crtc *crtc,
 
 	spin_lock(&vop->reg_lock);
 
+	VOP_AFBDC_SET(vop, enable, vop->afbdc_win != NULL);
+
 	vop_cfg_done(vop);
 
 	spin_unlock(&vop->reg_lock);
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
index 04ed401d2325e6288225aed9a6bbdba37f2c447d..6b7f8776c46f2815effb161bb303140e48d6cce0 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
@@ -34,6 +34,12 @@ enum vop_data_format {
 	VOP_FMT_YUV444SP,
 };
 
+enum vop_afbc_format {
+	VOP_AFBC_RGB565 = 0,
+	VOP_AFBC_RGB888 = 4,
+	VOP_AFBC_RGBA8888 = 5,
+};
+
 struct vop_reg {
 	uint32_t mask;
 	uint16_t offset;
@@ -66,6 +72,16 @@ struct vop_output {
 	struct vop_reg rgb_en;
 };
 
+struct vop_afbdc {
+	struct vop_reg enable;
+	struct vop_reg win_sel;
+	struct vop_reg format;
+	struct vop_reg hreg_block_split;
+	struct vop_reg pic_size;
+	struct vop_reg hdr_ptr;
+	struct vop_reg rstn;
+};
+
 struct vop_common {
 	struct vop_reg cfg_done;
 	struct vop_reg dsp_blank;
@@ -173,6 +189,7 @@ struct vop_data {
 	const struct vop_misc *misc;
 	const struct vop_modeset *modeset;
 	const struct vop_output *output;
+	const struct vop_afbdc *afbdc;
 	const struct vop_win_yuv2yuv_data *win_yuv2yuv;
 	const struct vop_win_data *win;
 	unsigned int win_size;
diff --git a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
index bd76328c0fdb5f378ac5b2e91f7f7867db24fdcc..24daf01651c32085689781470c0ab27196eaeb2e 100644
--- a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
+++ b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
@@ -795,6 +795,16 @@ static const struct vop_win_yuv2yuv_data rk3399_vop_big_win_yuv2yuv_data[] = {
 	{ .base = 0x120, .phy = &rk3399_yuv2yuv_win23_data },
 };
 
+static const struct vop_afbdc rk3399_vop_afbdc = {
+	.rstn = VOP_REG(RK3399_AFBCD0_CTRL, 0x1, 3),
+	.enable = VOP_REG(RK3399_AFBCD0_CTRL, 0x1, 0),
+	.win_sel = VOP_REG(RK3399_AFBCD0_CTRL, 0x3, 1),
+	.format = VOP_REG(RK3399_AFBCD0_CTRL, 0x1f, 16),
+	.hreg_block_split = VOP_REG(RK3399_AFBCD0_CTRL, 0x1, 21),
+	.hdr_ptr = VOP_REG(RK3399_AFBCD0_HDR_PTR, 0xffffffff, 0),
+	.pic_size = VOP_REG(RK3399_AFBCD0_PIC_SIZE, 0xffffffff, 0),
+};
+
 static const struct vop_data rk3399_vop_big = {
 	.version = VOP_VERSION(3, 5),
 	.feature = VOP_FEATURE_OUTPUT_RGB10,
@@ -803,6 +813,7 @@ static const struct vop_data rk3399_vop_big = {
 	.modeset = &rk3288_modeset,
 	.output = &rk3399_output,
 	.misc = &rk3368_misc,
+	.afbdc = &rk3399_vop_afbdc,
 	.win = rk3368_vop_win_data,
 	.win_size = ARRAY_SIZE(rk3368_vop_win_data),
 	.win_yuv2yuv = rk3399_vop_big_win_yuv2yuv_data,