From 2f74e0d6dd9f545a3b1dcc8d3cdf0ccf55a6f7d0 Mon Sep 17 00:00:00 2001
From: Dave Stevenson <dave.stevenson@raspberrypi.com>
Date: Mon, 7 Sep 2020 17:32:27 +0100
Subject: [PATCH] drm/vc4: Add firmware-kms mode

This is a squash of all firmware-kms related patches from previous
branches, up to and including
"drm/vc4: Set the possible crtcs mask correctly for planes with FKMS"
plus a couple of minor fixups for the 5.9 branch.
Please refer to earlier branches for full history.

This patch includes work by Eric Anholt, James Hughes, Phil Elwell,
Dave Stevenson, Dom Cobley, and Jonathon Bell.

Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>

drm/vc4: Fixup firmware-kms after "drm/atomic: Pass the full state to CRTC atomic enable/disable"

Prototype for those calls changed, so amend fkms (which isn't
upstream) to match.

Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>

drm/vc4: Fixup fkms for API change

Atomic flush and check changed API, so fix up the downstream-only
FKMS driver.

Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>

drm/vc4: Make normalize_zpos conditional on using fkms

Eric's view was that there was no point in having zpos
support on vc4 as all the planes had the same functionality.

Can be later squashed into (and fixes):
drm/vc4: Add firmware-kms mode

Signed-off-by: Dom Cobley <popcornmix@gmail.com>

drm/vc4: FKMS: Change of Broadcast RGB mode needs a mode change

The Broadcast RGB (aka HDMI limited/full range) property is only
notified to the firmware on mode change, so this needs to be
signalled when set.

https://github.com/raspberrypi/firmware/issues/1580

Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>

vc4/drv: Only notify firmware of display done with kms

fkms driver still wants firmware display to be active

Signed-off-by: Dom Cobley <popcornmix@gmail.com>

ydrm/vc4: fkms: Fix margin calculations for the right/bottom edges

The calculations clipped the right/bottom edge of the clipped
range based on the left/top margins.

https://github.com/raspberrypi/linux/issues/4447

Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>

drm/vc4: fkms: Use new devm_rpi_firmware_get api

drm/kms: Add allow_fb_modifiers

Signed-off-by: Dom Cobley <popcornmix@gmail.com>
---
 drivers/gpu/drm/vc4/Makefile           |    1 +
 drivers/gpu/drm/vc4/vc4_debugfs.c      |    3 +-
 drivers/gpu/drm/vc4/vc4_drv.c          |   29 +-
 drivers/gpu/drm/vc4/vc4_drv.h          |    7 +
 drivers/gpu/drm/vc4/vc4_firmware_kms.c | 1997 ++++++++++++++++++++++++
 drivers/gpu/drm/vc4/vc4_kms.c          |   35 +-
 drivers/gpu/drm/vc4/vc_image_types.h   |  175 +++
 7 files changed, 2233 insertions(+), 14 deletions(-)
 create mode 100644 drivers/gpu/drm/vc4/vc4_firmware_kms.c
 create mode 100644 drivers/gpu/drm/vc4/vc_image_types.h

--- a/drivers/gpu/drm/vc4/Makefile
+++ b/drivers/gpu/drm/vc4/Makefile
@@ -9,6 +9,7 @@ vc4-y := \
 	vc4_dpi.o \
 	vc4_dsi.o \
 	vc4_fence.o \
+	vc4_firmware_kms.o \
 	vc4_kms.o \
 	vc4_gem.o \
 	vc4_hdmi.o \
--- a/drivers/gpu/drm/vc4/vc4_debugfs.c
+++ b/drivers/gpu/drm/vc4/vc4_debugfs.c
@@ -24,7 +24,8 @@ vc4_debugfs_init(struct drm_minor *minor
 	struct vc4_dev *vc4 = to_vc4_dev(minor->dev);
 	struct drm_device *drm = &vc4->base;
 
-	drm_WARN_ON(drm, vc4_hvs_debugfs_init(minor));
+	if (vc4->hvs)
+		drm_WARN_ON(drm, vc4_hvs_debugfs_init(minor));
 
 	if (vc4->v3d) {
 		drm_WARN_ON(drm, vc4_bo_debugfs_init(minor));
--- a/drivers/gpu/drm/vc4/vc4_drv.c
+++ b/drivers/gpu/drm/vc4/vc4_drv.c
@@ -284,6 +284,18 @@ static const struct of_device_id vc4_dma
 	{}
 };
 
+/*
+ * we need this helper function for determining presence of fkms
+ * before it's been bound
+ */
+static bool firmware_kms(void)
+{
+	return of_device_is_available(of_find_compatible_node(NULL, NULL,
+	       "raspberrypi,rpi-firmware-kms")) ||
+	       of_device_is_available(of_find_compatible_node(NULL, NULL,
+	       "raspberrypi,rpi-firmware-kms-2711"));
+}
+
 static int vc4_drm_bind(struct device *dev)
 {
 	struct platform_device *pdev = to_platform_device(dev);
@@ -357,7 +369,7 @@ static int vc4_drm_bind(struct device *d
 	if (ret)
 		return ret;
 
-	if (firmware) {
+	if (firmware && !firmware_kms()) {
 		ret = rpi_firmware_property(firmware,
 					    RPI_FIRMWARE_NOTIFY_DISPLAY_DONE,
 					    NULL, 0);
@@ -375,16 +387,20 @@ static int vc4_drm_bind(struct device *d
 	if (ret)
 		return ret;
 
-	ret = vc4_plane_create_additional_planes(drm);
-	if (ret)
-		goto unbind_all;
+	if (!vc4->firmware_kms) {
+		ret = vc4_plane_create_additional_planes(drm);
+		if (ret)
+			return ret;
+	}
 
 	ret = vc4_kms_load(drm);
 	if (ret < 0)
 		goto unbind_all;
 
-	drm_for_each_crtc(crtc, drm)
-		vc4_crtc_disable_at_boot(crtc);
+	if (!vc4->firmware_kms) {
+		drm_for_each_crtc(crtc, drm)
+			vc4_crtc_disable_at_boot(crtc);
+	}
 
 	ret = drm_dev_register(drm, 0);
 	if (ret < 0)
@@ -428,6 +444,7 @@ static struct platform_driver *const com
 	&vc4_dsi_driver,
 	&vc4_txp_driver,
 	&vc4_crtc_driver,
+	&vc4_firmware_kms_driver,
 	&vc4_v3d_driver,
 };
 
--- a/drivers/gpu/drm/vc4/vc4_drv.h
+++ b/drivers/gpu/drm/vc4/vc4_drv.h
@@ -82,8 +82,12 @@ struct vc4_dev {
 
 	unsigned int irq;
 
+	bool firmware_kms;
+	struct rpi_firmware *firmware;
+
 	struct vc4_hvs *hvs;
 	struct vc4_v3d *v3d;
+	struct vc4_fkms *fkms;
 
 	struct vc4_hang_state *hang_state;
 
@@ -905,6 +909,9 @@ extern struct platform_driver vc4_dsi_dr
 /* vc4_fence.c */
 extern const struct dma_fence_ops vc4_fence_ops;
 
+/* vc4_firmware_kms.c */
+extern struct platform_driver vc4_firmware_kms_driver;
+
 /* vc4_gem.c */
 int vc4_gem_init(struct drm_device *dev);
 int vc4_submit_cl_ioctl(struct drm_device *dev, void *data,
--- /dev/null
+++ b/drivers/gpu/drm/vc4/vc4_firmware_kms.c
@@ -0,0 +1,1997 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2016 Broadcom
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+/**
+ * DOC: VC4 firmware KMS module.
+ *
+ * As a hack to get us from the current closed source driver world
+ * toward a totally open stack, implement KMS on top of the Raspberry
+ * Pi's firmware display stack.
+ */
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_blend.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_fb_dma_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem_atomic_helper.h>
+#include <drm/drm_plane_helper.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
+
+#include <linux/component.h>
+#include <linux/clk.h>
+#include <linux/debugfs.h>
+#include <linux/module.h>
+
+#include <soc/bcm2835/raspberrypi-firmware.h>
+
+#include "vc4_drv.h"
+#include "vc4_regs.h"
+#include "vc_image_types.h"
+
+int fkms_max_refresh_rate = 85;
+module_param(fkms_max_refresh_rate, int, 0644);
+MODULE_PARM_DESC(fkms_max_refresh_rate, "Max supported refresh rate");
+
+struct get_display_cfg {
+	u32  max_pixel_clock[2];  //Max pixel clock for each display
+};
+
+struct vc4_fkms {
+	struct get_display_cfg cfg;
+	bool bcm2711;
+};
+
+#define PLANES_PER_CRTC		8
+
+struct set_plane {
+	u8 display;
+	u8 plane_id;
+	u8 vc_image_type;
+	s8 layer;
+
+	u16 width;
+	u16 height;
+
+	u16 pitch;
+	u16 vpitch;
+
+	u32 src_x;	/* 16p16 */
+	u32 src_y;	/* 16p16 */
+
+	u32 src_w;	/* 16p16 */
+	u32 src_h;	/* 16p16 */
+
+	s16 dst_x;
+	s16 dst_y;
+
+	u16 dst_w;
+	u16 dst_h;
+
+	u8 alpha;
+	u8 num_planes;
+	u8 is_vu;
+	u8 color_encoding;
+
+	u32 planes[4];  /* DMA address of each plane */
+
+	u32 transform;
+};
+
+/* Values for the transform field */
+#define TRANSFORM_NO_ROTATE	0
+#define TRANSFORM_ROTATE_180	BIT(1)
+#define TRANSFORM_FLIP_HRIZ	BIT(16)
+#define TRANSFORM_FLIP_VERT	BIT(17)
+
+struct mailbox_set_plane {
+	struct rpi_firmware_property_tag_header tag;
+	struct set_plane plane;
+};
+
+struct mailbox_blank_display {
+	struct rpi_firmware_property_tag_header tag1;
+	u32 display;
+	struct rpi_firmware_property_tag_header tag2;
+	u32 blank;
+};
+
+struct mailbox_display_pwr {
+	struct rpi_firmware_property_tag_header tag1;
+	u32 display;
+	u32 state;
+};
+
+struct mailbox_get_edid {
+	struct rpi_firmware_property_tag_header tag1;
+	u32 block;
+	u32 display_number;
+	u8 edid[128];
+};
+
+struct set_timings {
+	u8 display;
+	u8 padding;
+	u16 video_id_code;
+
+	u32 clock;		/* in kHz */
+
+	u16 hdisplay;
+	u16 hsync_start;
+
+	u16 hsync_end;
+	u16 htotal;
+
+	u16 hskew;
+	u16 vdisplay;
+
+	u16 vsync_start;
+	u16 vsync_end;
+
+	u16 vtotal;
+	u16 vscan;
+
+	u16 vrefresh;
+	u16 padding2;
+
+	u32 flags;
+#define  TIMINGS_FLAGS_H_SYNC_POS	BIT(0)
+#define  TIMINGS_FLAGS_H_SYNC_NEG	0
+#define  TIMINGS_FLAGS_V_SYNC_POS	BIT(1)
+#define  TIMINGS_FLAGS_V_SYNC_NEG	0
+#define  TIMINGS_FLAGS_INTERLACE	BIT(2)
+
+#define TIMINGS_FLAGS_ASPECT_MASK	GENMASK(7, 4)
+#define TIMINGS_FLAGS_ASPECT_NONE	(0 << 4)
+#define TIMINGS_FLAGS_ASPECT_4_3	(1 << 4)
+#define TIMINGS_FLAGS_ASPECT_16_9	(2 << 4)
+#define TIMINGS_FLAGS_ASPECT_64_27	(3 << 4)
+#define TIMINGS_FLAGS_ASPECT_256_135	(4 << 4)
+
+/* Limited range RGB flag. Not set corresponds to full range. */
+#define TIMINGS_FLAGS_RGB_LIMITED	BIT(8)
+/* DVI monitor, therefore disable infoframes. Not set corresponds to HDMI. */
+#define TIMINGS_FLAGS_DVI		BIT(9)
+/* Double clock */
+#define TIMINGS_FLAGS_DBL_CLK		BIT(10)
+};
+
+struct mailbox_set_mode {
+	struct rpi_firmware_property_tag_header tag1;
+	struct set_timings timings;
+};
+
+static const struct vc_image_format {
+	u32 drm;	/* DRM_FORMAT_* */
+	u32 vc_image;	/* VC_IMAGE_* */
+	u32 is_vu;
+} vc_image_formats[] = {
+	{
+		.drm = DRM_FORMAT_XRGB8888,
+		.vc_image = VC_IMAGE_XRGB8888,
+	},
+	{
+		.drm = DRM_FORMAT_ARGB8888,
+		.vc_image = VC_IMAGE_ARGB8888,
+	},
+/*
+ *	FIXME: Need to resolve which DRM format goes to which vc_image format
+ *	for the remaining RGBA and RGBX formats.
+ *	{
+ *		.drm = DRM_FORMAT_ABGR8888,
+ *		.vc_image = VC_IMAGE_RGBA8888,
+ *	},
+ *	{
+ *		.drm = DRM_FORMAT_XBGR8888,
+ *		.vc_image = VC_IMAGE_RGBA8888,
+ *	},
+ */
+	{
+		.drm = DRM_FORMAT_RGB565,
+		.vc_image = VC_IMAGE_RGB565,
+	},
+	{
+		.drm = DRM_FORMAT_RGB888,
+		.vc_image = VC_IMAGE_BGR888,
+	},
+	{
+		.drm = DRM_FORMAT_BGR888,
+		.vc_image = VC_IMAGE_RGB888,
+	},
+	{
+		.drm = DRM_FORMAT_YUV422,
+		.vc_image = VC_IMAGE_YUV422PLANAR,
+	},
+	{
+		.drm = DRM_FORMAT_YUV420,
+		.vc_image = VC_IMAGE_YUV420,
+	},
+	{
+		.drm = DRM_FORMAT_YVU420,
+		.vc_image = VC_IMAGE_YUV420,
+		.is_vu = 1,
+	},
+	{
+		.drm = DRM_FORMAT_NV12,
+		.vc_image = VC_IMAGE_YUV420SP,
+	},
+	{
+		.drm = DRM_FORMAT_NV21,
+		.vc_image = VC_IMAGE_YUV420SP,
+		.is_vu = 1,
+	},
+	{
+		.drm = DRM_FORMAT_P030,
+		.vc_image = VC_IMAGE_YUV10COL,
+	},
+};
+
+static const struct vc_image_format *vc4_get_vc_image_fmt(u32 drm_format)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(vc_image_formats); i++) {
+		if (vc_image_formats[i].drm == drm_format)
+			return &vc_image_formats[i];
+	}
+
+	return NULL;
+}
+
+/* The firmware delivers a vblank interrupt to us through the SMI
+ * hardware, which has only this one register.
+ */
+#define SMICS 0x0
+#define SMIDSW0 0x14
+#define SMIDSW1 0x1C
+#define SMICS_INTERRUPTS (BIT(9) | BIT(10) | BIT(11))
+
+/* Flag to denote that the firmware is giving multiple display callbacks */
+#define SMI_NEW 0xabcd0000
+
+#define vc4_crtc vc4_kms_crtc
+#define to_vc4_crtc to_vc4_kms_crtc
+struct vc4_crtc {
+	struct drm_crtc base;
+	struct drm_encoder *encoder;
+	struct drm_connector *connector;
+	void __iomem *regs;
+
+	struct drm_pending_vblank_event *event;
+	bool vblank_enabled;
+	u32 display_number;
+	u32 display_type;
+};
+
+static inline struct vc4_crtc *to_vc4_crtc(struct drm_crtc *crtc)
+{
+	return container_of(crtc, struct vc4_crtc, base);
+}
+
+struct vc4_fkms_encoder {
+	struct drm_encoder base;
+	bool hdmi_monitor;
+	bool rgb_range_selectable;
+	int display_num;
+};
+
+static inline struct vc4_fkms_encoder *
+to_vc4_fkms_encoder(struct drm_encoder *encoder)
+{
+	return container_of(encoder, struct vc4_fkms_encoder, base);
+}
+
+/* "Broadcast RGB" property.
+ * Allows overriding of HDMI full or limited range RGB
+ */
+#define VC4_BROADCAST_RGB_AUTO 0
+#define VC4_BROADCAST_RGB_FULL 1
+#define VC4_BROADCAST_RGB_LIMITED 2
+
+/* VC4 FKMS connector KMS struct */
+struct vc4_fkms_connector {
+	struct drm_connector base;
+
+	/* Since the connector is attached to just the one encoder,
+	 * this is the reference to it so we can do the best_encoder()
+	 * hook.
+	 */
+	struct drm_encoder *encoder;
+	struct vc4_dev *vc4_dev;
+	u32 display_number;
+	u32 display_type;
+
+	struct drm_property *broadcast_rgb_property;
+};
+
+static inline struct vc4_fkms_connector *
+to_vc4_fkms_connector(struct drm_connector *connector)
+{
+	return container_of(connector, struct vc4_fkms_connector, base);
+}
+
+/* VC4 FKMS connector state */
+struct vc4_fkms_connector_state {
+	struct drm_connector_state base;
+
+	int broadcast_rgb;
+};
+
+#define to_vc4_fkms_connector_state(x) \
+			container_of(x, struct vc4_fkms_connector_state, base)
+
+static u32 vc4_get_display_type(u32 display_number)
+{
+	const u32 display_types[] = {
+		/* The firmware display (DispmanX) IDs map to specific types in
+		 * a fixed manner.
+		 */
+		DRM_MODE_ENCODER_DSI,	/* MAIN_LCD - DSI or DPI */
+		DRM_MODE_ENCODER_DSI,	/* AUX_LCD */
+		DRM_MODE_ENCODER_TMDS,	/* HDMI0 */
+		DRM_MODE_ENCODER_TVDAC,	/* VEC */
+		DRM_MODE_ENCODER_NONE,	/* FORCE_LCD */
+		DRM_MODE_ENCODER_NONE,	/* FORCE_TV */
+		DRM_MODE_ENCODER_NONE,	/* FORCE_OTHER */
+		DRM_MODE_ENCODER_TMDS,	/* HDMI1 */
+		DRM_MODE_ENCODER_NONE,	/* FORCE_TV2 */
+	};
+	return display_number > ARRAY_SIZE(display_types) - 1 ?
+			DRM_MODE_ENCODER_NONE : display_types[display_number];
+}
+
+/* Firmware's structure for making an FB mbox call. */
+struct fbinfo_s {
+	u32 xres, yres, xres_virtual, yres_virtual;
+	u32 pitch, bpp;
+	u32 xoffset, yoffset;
+	u32 base;
+	u32 screen_size;
+	u16 cmap[256];
+};
+
+struct vc4_fkms_plane {
+	struct drm_plane base;
+	struct fbinfo_s *fbinfo;
+	dma_addr_t fbinfo_bus_addr;
+	u32 pitch;
+	struct mailbox_set_plane mb;
+};
+
+static inline struct vc4_fkms_plane *to_vc4_fkms_plane(struct drm_plane *plane)
+{
+	return (struct vc4_fkms_plane *)plane;
+}
+
+static int vc4_plane_set_blank(struct drm_plane *plane, bool blank)
+{
+	struct vc4_dev *vc4 = to_vc4_dev(plane->dev);
+	struct vc4_fkms_plane *vc4_plane = to_vc4_fkms_plane(plane);
+	struct mailbox_set_plane blank_mb = {
+		.tag = { RPI_FIRMWARE_SET_PLANE, sizeof(struct set_plane), 0 },
+		.plane = {
+			.display = vc4_plane->mb.plane.display,
+			.plane_id = vc4_plane->mb.plane.plane_id,
+		}
+	};
+	static const char * const plane_types[] = {
+							"overlay",
+							"primary",
+							"cursor"
+						  };
+	int ret;
+
+	DRM_DEBUG_ATOMIC("[PLANE:%d:%s] %s plane %s",
+			 plane->base.id, plane->name, plane_types[plane->type],
+			 blank ? "blank" : "unblank");
+
+	if (blank)
+		ret = rpi_firmware_property_list(vc4->firmware, &blank_mb,
+						 sizeof(blank_mb));
+	else
+		ret = rpi_firmware_property_list(vc4->firmware, &vc4_plane->mb,
+						 sizeof(vc4_plane->mb));
+
+	WARN_ONCE(ret, "%s: firmware call failed. Please update your firmware",
+		  __func__);
+	return ret;
+}
+
+static void vc4_fkms_crtc_get_margins(struct drm_crtc_state *state,
+				      unsigned int *left, unsigned int *right,
+				      unsigned int *top, unsigned int *bottom)
+{
+	struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(state);
+	struct drm_connector_state *conn_state;
+	struct drm_connector *conn;
+	int i;
+
+	*left = vc4_state->margins.left;
+	*right = vc4_state->margins.right;
+	*top = vc4_state->margins.top;
+	*bottom = vc4_state->margins.bottom;
+
+	/* We have to interate over all new connector states because
+	 * vc4_fkms_crtc_get_margins() might be called before
+	 * vc4_fkms_crtc_atomic_check() which means margins info in
+	 * vc4_crtc_state might be outdated.
+	 */
+	for_each_new_connector_in_state(state->state, conn, conn_state, i) {
+		if (conn_state->crtc != state->crtc)
+			continue;
+
+		*left = conn_state->tv.margins.left;
+		*right = conn_state->tv.margins.right;
+		*top = conn_state->tv.margins.top;
+		*bottom = conn_state->tv.margins.bottom;
+		break;
+	}
+}
+
+static int vc4_fkms_margins_adj(struct drm_plane_state *pstate,
+				struct set_plane *plane)
+{
+	unsigned int left, right, top, bottom;
+	int adjhdisplay, adjvdisplay;
+	struct drm_crtc_state *crtc_state;
+
+	crtc_state = drm_atomic_get_new_crtc_state(pstate->state,
+						   pstate->crtc);
+
+	vc4_fkms_crtc_get_margins(crtc_state, &left, &right, &top, &bottom);
+
+	if (!left && !right && !top && !bottom)
+		return 0;
+
+	if (left + right >= crtc_state->mode.hdisplay ||
+	    top + bottom >= crtc_state->mode.vdisplay)
+		return -EINVAL;
+
+	adjhdisplay = crtc_state->mode.hdisplay - (left + right);
+	plane->dst_x = DIV_ROUND_CLOSEST(plane->dst_x * adjhdisplay,
+					 (int)crtc_state->mode.hdisplay);
+	plane->dst_x += left;
+	if (plane->dst_x > (int)(crtc_state->mode.hdisplay - right))
+		plane->dst_x = crtc_state->mode.hdisplay - right;
+
+	adjvdisplay = crtc_state->mode.vdisplay - (top + bottom);
+	plane->dst_y = DIV_ROUND_CLOSEST(plane->dst_y * adjvdisplay,
+					 (int)crtc_state->mode.vdisplay);
+	plane->dst_y += top;
+	if (plane->dst_y > (int)(crtc_state->mode.vdisplay - bottom))
+		plane->dst_y = crtc_state->mode.vdisplay - bottom;
+
+	plane->dst_w = DIV_ROUND_CLOSEST(plane->dst_w * adjhdisplay,
+					 crtc_state->mode.hdisplay);
+	plane->dst_h = DIV_ROUND_CLOSEST(plane->dst_h * adjvdisplay,
+					 crtc_state->mode.vdisplay);
+
+	if (!plane->dst_w || !plane->dst_h)
+		return -EINVAL;
+
+	return 0;
+}
+
+static void vc4_plane_atomic_update(struct drm_plane *plane,
+				    struct drm_atomic_state *old_state)
+{
+	struct drm_plane_state *state = plane->state;
+
+	/*
+	 * Do NOT set now, as we haven't checked if the crtc is active or not.
+	 * Set from vc4_plane_set_blank instead.
+	 *
+	 * If the CRTC is on (or going to be on) and we're enabled,
+	 * then unblank.  Otherwise, stay blank until CRTC enable.
+	 */
+	if (state->crtc->state->active)
+		vc4_plane_set_blank(plane, false);
+}
+
+static void vc4_plane_atomic_disable(struct drm_plane *plane,
+				     struct drm_atomic_state *old_state)
+{
+	struct drm_plane_state *state = plane->state;
+	struct vc4_fkms_plane *vc4_plane = to_vc4_fkms_plane(plane);
+
+	DRM_DEBUG_ATOMIC("[PLANE:%d:%s] plane disable %dx%d@%d +%d,%d\n",
+			 plane->base.id, plane->name,
+			 state->crtc_w,
+			 state->crtc_h,
+			 vc4_plane->mb.plane.vc_image_type,
+			 state->crtc_x,
+			 state->crtc_y);
+	vc4_plane_set_blank(plane, true);
+}
+
+static bool plane_enabled(struct drm_plane_state *state)
+{
+	return state->fb && state->crtc;
+}
+
+static int vc4_plane_to_mb(struct drm_plane *plane,
+			   struct mailbox_set_plane *mb,
+			   struct drm_plane_state *state)
+{
+	struct drm_framebuffer *fb = state->fb;
+	struct drm_gem_dma_object *bo = drm_fb_dma_get_gem_obj(fb, 0);
+	const struct drm_format_info *drm_fmt = fb->format;
+	const struct vc_image_format *vc_fmt =
+					vc4_get_vc_image_fmt(drm_fmt->format);
+	int num_planes = fb->format->num_planes;
+	unsigned int rotation;
+
+	mb->plane.vc_image_type = vc_fmt->vc_image;
+	mb->plane.width = fb->width;
+	mb->plane.height = fb->height;
+	mb->plane.pitch = fb->pitches[0];
+	mb->plane.src_w = state->src_w;
+	mb->plane.src_h = state->src_h;
+	mb->plane.src_x = state->src_x;
+	mb->plane.src_y = state->src_y;
+	mb->plane.dst_w = state->crtc_w;
+	mb->plane.dst_h = state->crtc_h;
+	mb->plane.dst_x = state->crtc_x;
+	mb->plane.dst_y = state->crtc_y;
+	mb->plane.alpha = state->alpha >> 8;
+	mb->plane.layer = state->normalized_zpos ?
+					state->normalized_zpos : -127;
+	mb->plane.num_planes = num_planes;
+	mb->plane.is_vu = vc_fmt->is_vu;
+	mb->plane.planes[0] = bo->dma_addr + fb->offsets[0];
+
+	rotation = drm_rotation_simplify(state->rotation,
+					 DRM_MODE_ROTATE_0 |
+					 DRM_MODE_REFLECT_X |
+					 DRM_MODE_REFLECT_Y);
+
+	mb->plane.transform = TRANSFORM_NO_ROTATE;
+	if (rotation & DRM_MODE_REFLECT_X)
+		mb->plane.transform |= TRANSFORM_FLIP_HRIZ;
+	if (rotation & DRM_MODE_REFLECT_Y)
+		mb->plane.transform |= TRANSFORM_FLIP_VERT;
+
+	vc4_fkms_margins_adj(state, &mb->plane);
+
+	if (num_planes > 1) {
+		/* Assume this must be YUV */
+		/* Makes assumptions on the stride for the chroma planes as we
+		 * can't easily plumb in non-standard pitches.
+		 */
+		mb->plane.planes[1] = bo->dma_addr + fb->offsets[1];
+		if (num_planes > 2)
+			mb->plane.planes[2] = bo->dma_addr + fb->offsets[2];
+		else
+			mb->plane.planes[2] = 0;
+
+		/* Special case the YUV420 with U and V as line interleaved
+		 * planes as we have special handling for that case.
+		 */
+		if (num_planes == 3 &&
+		    (fb->offsets[2] - fb->offsets[1]) == fb->pitches[1])
+			mb->plane.vc_image_type = VC_IMAGE_YUV420_S;
+
+		switch (state->color_encoding) {
+		default:
+		case DRM_COLOR_YCBCR_BT601:
+			if (state->color_range == DRM_COLOR_YCBCR_LIMITED_RANGE)
+				mb->plane.color_encoding =
+						VC_IMAGE_YUVINFO_CSC_ITUR_BT601;
+			else
+				mb->plane.color_encoding =
+						VC_IMAGE_YUVINFO_CSC_JPEG_JFIF;
+			break;
+		case DRM_COLOR_YCBCR_BT709:
+			/* Currently no support for a full range BT709 */
+			mb->plane.color_encoding =
+						VC_IMAGE_YUVINFO_CSC_ITUR_BT709;
+			break;
+		case DRM_COLOR_YCBCR_BT2020:
+			/* Currently no support for a full range BT2020 */
+			mb->plane.color_encoding =
+					VC_IMAGE_YUVINFO_CSC_REC_2020;
+			break;
+		}
+	} else {
+		mb->plane.planes[1] = 0;
+		mb->plane.planes[2] = 0;
+	}
+	mb->plane.planes[3] = 0;
+
+	switch (fourcc_mod_broadcom_mod(fb->modifier)) {
+	case DRM_FORMAT_MOD_BROADCOM_VC4_T_TILED:
+		switch (mb->plane.vc_image_type) {
+		case VC_IMAGE_XRGB8888:
+			mb->plane.vc_image_type = VC_IMAGE_TF_RGBX32;
+			break;
+		case VC_IMAGE_ARGB8888:
+			mb->plane.vc_image_type = VC_IMAGE_TF_RGBA32;
+			break;
+		case VC_IMAGE_RGB565:
+			mb->plane.vc_image_type = VC_IMAGE_TF_RGB565;
+			break;
+		}
+		break;
+	case DRM_FORMAT_MOD_BROADCOM_SAND128:
+		switch (mb->plane.vc_image_type) {
+		case VC_IMAGE_YUV420SP:
+			mb->plane.vc_image_type = VC_IMAGE_YUV_UV;
+			break;
+		/* VC_IMAGE_YUV10COL could be included in here, but it is only
+		 * valid as a SAND128 format, so the table at the top will have
+		 * already set the correct format.
+		 */
+		}
+		/* Note that the column pitch is passed across in lines, not
+		 * bytes.
+		 */
+		mb->plane.pitch = fourcc_mod_broadcom_param(fb->modifier);
+		break;
+	}
+
+	DRM_DEBUG_ATOMIC("[PLANE:%d:%s] plane update %dx%d@%d +dst(%d,%d, %d,%d) +src(%d,%d, %d,%d) 0x%08x/%08x/%08x/%d, alpha %u zpos %u\n",
+			 plane->base.id, plane->name,
+			 mb->plane.width,
+			 mb->plane.height,
+			 mb->plane.vc_image_type,
+			 state->crtc_x,
+			 state->crtc_y,
+			 state->crtc_w,
+			 state->crtc_h,
+			 mb->plane.src_x,
+			 mb->plane.src_y,
+			 mb->plane.src_w,
+			 mb->plane.src_h,
+			 mb->plane.planes[0],
+			 mb->plane.planes[1],
+			 mb->plane.planes[2],
+			 fb->pitches[0],
+			 state->alpha,
+			 state->normalized_zpos);
+
+	return 0;
+}
+
+static int vc4_plane_atomic_check(struct drm_plane *plane,
+				  struct drm_atomic_state *state)
+{
+	struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
+										 plane);
+	struct vc4_fkms_plane *vc4_plane = to_vc4_fkms_plane(plane);
+
+	if (!plane_enabled(new_plane_state))
+		return 0;
+
+	return vc4_plane_to_mb(plane, &vc4_plane->mb, new_plane_state);
+}
+
+/* Called during init to allocate the plane's atomic state. */
+static void vc4_plane_reset(struct drm_plane *plane)
+{
+	struct vc4_plane_state *vc4_state;
+
+	WARN_ON(plane->state);
+
+	vc4_state = kzalloc(sizeof(*vc4_state), GFP_KERNEL);
+	if (!vc4_state)
+		return;
+
+	__drm_atomic_helper_plane_reset(plane, &vc4_state->base);
+}
+
+static void vc4_plane_destroy(struct drm_plane *plane)
+{
+	drm_plane_cleanup(plane);
+}
+
+static bool vc4_fkms_format_mod_supported(struct drm_plane *plane,
+					  uint32_t format,
+					  uint64_t modifier)
+{
+	/* Support T_TILING for RGB formats only. */
+	switch (format) {
+	case DRM_FORMAT_XRGB8888:
+	case DRM_FORMAT_ARGB8888:
+	case DRM_FORMAT_RGB565:
+		switch (modifier) {
+		case DRM_FORMAT_MOD_BROADCOM_VC4_T_TILED:
+		case DRM_FORMAT_MOD_LINEAR:
+			return true;
+		default:
+			return false;
+		}
+	case DRM_FORMAT_NV12:
+		switch (fourcc_mod_broadcom_mod(modifier)) {
+		case DRM_FORMAT_MOD_LINEAR:
+		case DRM_FORMAT_MOD_BROADCOM_SAND128:
+			return true;
+		default:
+			return false;
+		}
+	case DRM_FORMAT_P030:
+		switch (fourcc_mod_broadcom_mod(modifier)) {
+		case DRM_FORMAT_MOD_BROADCOM_SAND128:
+			return true;
+		default:
+			return false;
+		}
+	case DRM_FORMAT_NV21:
+	case DRM_FORMAT_RGB888:
+	case DRM_FORMAT_BGR888:
+	case DRM_FORMAT_YUV422:
+	case DRM_FORMAT_YUV420:
+	case DRM_FORMAT_YVU420:
+	default:
+		return (modifier == DRM_FORMAT_MOD_LINEAR);
+	}
+}
+
+static struct drm_plane_state *vc4_plane_duplicate_state(struct drm_plane *plane)
+{
+	struct vc4_plane_state *vc4_state;
+
+	if (WARN_ON(!plane->state))
+		return NULL;
+
+	vc4_state = kzalloc(sizeof(*vc4_state), GFP_KERNEL);
+	if (!vc4_state)
+		return NULL;
+
+	__drm_atomic_helper_plane_duplicate_state(plane, &vc4_state->base);
+
+	return &vc4_state->base;
+}
+
+static const struct drm_plane_funcs vc4_plane_funcs = {
+	.update_plane = drm_atomic_helper_update_plane,
+	.disable_plane = drm_atomic_helper_disable_plane,
+	.destroy = vc4_plane_destroy,
+	.set_property = NULL,
+	.reset = vc4_plane_reset,
+	.atomic_duplicate_state = vc4_plane_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+	.format_mod_supported = vc4_fkms_format_mod_supported,
+};
+
+static const struct drm_plane_helper_funcs vc4_plane_helper_funcs = {
+	.prepare_fb = drm_gem_plane_helper_prepare_fb,
+	.cleanup_fb = NULL,
+	.atomic_check = vc4_plane_atomic_check,
+	.atomic_update = vc4_plane_atomic_update,
+	.atomic_disable = vc4_plane_atomic_disable,
+};
+
+static struct drm_plane *vc4_fkms_plane_init(struct drm_device *dev,
+					     enum drm_plane_type type,
+					     u8 display_num,
+					     u8 plane_id)
+{
+	struct drm_plane *plane = NULL;
+	struct vc4_fkms_plane *vc4_plane;
+	u32 formats[ARRAY_SIZE(vc_image_formats)];
+	unsigned int default_zpos = 0;
+	u32 num_formats = 0;
+	int ret = 0;
+	static const uint64_t modifiers[] = {
+		DRM_FORMAT_MOD_LINEAR,
+		/* VC4_T_TILED should come after linear, because we
+		 * would prefer to scan out linear (less bus traffic).
+		 */
+		DRM_FORMAT_MOD_BROADCOM_VC4_T_TILED,
+		DRM_FORMAT_MOD_BROADCOM_SAND128,
+		DRM_FORMAT_MOD_INVALID,
+	};
+	int i;
+
+	vc4_plane = devm_kzalloc(dev->dev, sizeof(*vc4_plane),
+				 GFP_KERNEL);
+	if (!vc4_plane) {
+		ret = -ENOMEM;
+		goto fail;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(vc_image_formats); i++)
+		formats[num_formats++] = vc_image_formats[i].drm;
+
+	plane = &vc4_plane->base;
+	ret = drm_universal_plane_init(dev, plane, 0,
+				       &vc4_plane_funcs,
+				       formats, num_formats, modifiers,
+				       type, NULL);
+
+	/* FIXME: Do we need to be checking return values from all these calls?
+	 */
+	drm_plane_helper_add(plane, &vc4_plane_helper_funcs);
+
+	drm_plane_create_alpha_property(plane);
+	drm_plane_create_rotation_property(plane, DRM_MODE_ROTATE_0,
+					   DRM_MODE_ROTATE_0 |
+					   DRM_MODE_ROTATE_180 |
+					   DRM_MODE_REFLECT_X |
+					   DRM_MODE_REFLECT_Y);
+	drm_plane_create_color_properties(plane,
+					  BIT(DRM_COLOR_YCBCR_BT601) |
+					  BIT(DRM_COLOR_YCBCR_BT709) |
+					  BIT(DRM_COLOR_YCBCR_BT2020),
+					  BIT(DRM_COLOR_YCBCR_LIMITED_RANGE) |
+					  BIT(DRM_COLOR_YCBCR_FULL_RANGE),
+					  DRM_COLOR_YCBCR_BT709,
+					  DRM_COLOR_YCBCR_LIMITED_RANGE);
+
+	/*
+	 * Default frame buffer setup is with FB on -127, and raspistill etc
+	 * tend to drop overlays on layer 2. Cursor plane was on layer +127.
+	 *
+	 * For F-KMS the mailbox call allows for a s8.
+	 * Remap zpos 0 to -127 for the background layer, but leave all the
+	 * other layers as requested by KMS.
+	 */
+	switch (type) {
+	default:
+	case DRM_PLANE_TYPE_PRIMARY:
+		default_zpos = 0;
+		break;
+	case DRM_PLANE_TYPE_OVERLAY:
+		default_zpos = 1;
+		break;
+	case DRM_PLANE_TYPE_CURSOR:
+		default_zpos = 2;
+		break;
+	}
+	drm_plane_create_zpos_property(plane, default_zpos, 0, 127);
+
+	/* Prepare the static elements of the mailbox structure */
+	vc4_plane->mb.tag.tag = RPI_FIRMWARE_SET_PLANE;
+	vc4_plane->mb.tag.buf_size = sizeof(struct set_plane);
+	vc4_plane->mb.tag.req_resp_size = 0;
+	vc4_plane->mb.plane.display = display_num;
+	vc4_plane->mb.plane.plane_id = plane_id;
+	vc4_plane->mb.plane.layer = default_zpos ? default_zpos : -127;
+
+	return plane;
+fail:
+	if (plane)
+		vc4_plane_destroy(plane);
+
+	return ERR_PTR(ret);
+}
+
+static void vc4_crtc_mode_set_nofb(struct drm_crtc *crtc)
+{
+	struct drm_device *dev = crtc->dev;
+	struct vc4_dev *vc4 = to_vc4_dev(dev);
+	struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
+	struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+	struct vc4_fkms_encoder *vc4_encoder =
+					to_vc4_fkms_encoder(vc4_crtc->encoder);
+	struct mailbox_set_mode mb = {
+		.tag1 = { RPI_FIRMWARE_SET_TIMING,
+			  sizeof(struct set_timings), 0},
+	};
+	union hdmi_infoframe frame;
+	int ret;
+
+	ret = drm_hdmi_avi_infoframe_from_display_mode(&frame.avi, vc4_crtc->connector, mode);
+	if (ret < 0) {
+		DRM_ERROR("couldn't fill AVI infoframe\n");
+		return;
+	}
+
+	DRM_DEBUG_KMS("Setting mode for display num %u mode name %s, clk %d, h(disp %d, start %d, end %d, total %d, skew %d) v(disp %d, start %d, end %d, total %d, scan %d), vrefresh %d, par %u, flags 0x%04x\n",
+		      vc4_crtc->display_number, mode->name, mode->clock,
+		      mode->hdisplay, mode->hsync_start, mode->hsync_end,
+		      mode->htotal, mode->hskew, mode->vdisplay,
+		      mode->vsync_start, mode->vsync_end, mode->vtotal,
+		      mode->vscan, drm_mode_vrefresh(mode),
+		      mode->picture_aspect_ratio, mode->flags);
+	mb.timings.display = vc4_crtc->display_number;
+
+	mb.timings.clock = mode->clock;
+	mb.timings.hdisplay = mode->hdisplay;
+	mb.timings.hsync_start = mode->hsync_start;
+	mb.timings.hsync_end = mode->hsync_end;
+	mb.timings.htotal = mode->htotal;
+	mb.timings.hskew = mode->hskew;
+	mb.timings.vdisplay = mode->vdisplay;
+	mb.timings.vsync_start = mode->vsync_start;
+	mb.timings.vsync_end = mode->vsync_end;
+	mb.timings.vtotal = mode->vtotal;
+	mb.timings.vscan = mode->vscan;
+	mb.timings.vrefresh = drm_mode_vrefresh(mode);
+	mb.timings.flags = 0;
+	if (mode->flags & DRM_MODE_FLAG_PHSYNC)
+		mb.timings.flags |= TIMINGS_FLAGS_H_SYNC_POS;
+	if (mode->flags & DRM_MODE_FLAG_PVSYNC)
+		mb.timings.flags |= TIMINGS_FLAGS_V_SYNC_POS;
+
+	switch (frame.avi.picture_aspect) {
+	default:
+	case HDMI_PICTURE_ASPECT_NONE:
+		mb.timings.flags |= TIMINGS_FLAGS_ASPECT_NONE;
+		break;
+	case HDMI_PICTURE_ASPECT_4_3:
+		mb.timings.flags |= TIMINGS_FLAGS_ASPECT_4_3;
+		break;
+	case HDMI_PICTURE_ASPECT_16_9:
+		mb.timings.flags |= TIMINGS_FLAGS_ASPECT_16_9;
+		break;
+	case HDMI_PICTURE_ASPECT_64_27:
+		mb.timings.flags |= TIMINGS_FLAGS_ASPECT_64_27;
+		break;
+	case HDMI_PICTURE_ASPECT_256_135:
+		mb.timings.flags |= TIMINGS_FLAGS_ASPECT_256_135;
+		break;
+	}
+
+	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+		mb.timings.flags |= TIMINGS_FLAGS_INTERLACE;
+	if (mode->flags & DRM_MODE_FLAG_DBLCLK)
+		mb.timings.flags |= TIMINGS_FLAGS_DBL_CLK;
+
+	mb.timings.video_id_code = frame.avi.video_code;
+
+	if (!vc4_encoder->hdmi_monitor) {
+		mb.timings.flags |= TIMINGS_FLAGS_DVI;
+	} else {
+		struct vc4_fkms_connector_state *conn_state =
+			to_vc4_fkms_connector_state(vc4_crtc->connector->state);
+
+		if (conn_state->broadcast_rgb == VC4_BROADCAST_RGB_AUTO) {
+			/* See CEA-861-E - 5.1 Default Encoding Parameters */
+			if (drm_default_rgb_quant_range(mode) ==
+					HDMI_QUANTIZATION_RANGE_LIMITED)
+				mb.timings.flags |= TIMINGS_FLAGS_RGB_LIMITED;
+		} else {
+			if (conn_state->broadcast_rgb ==
+						VC4_BROADCAST_RGB_LIMITED)
+				mb.timings.flags |= TIMINGS_FLAGS_RGB_LIMITED;
+
+			/* If not using the default range, then do not provide
+			 * a VIC as the HDMI spec requires that we do not
+			 * signal the opposite of the defined range in the AVI
+			 * infoframe.
+			 */
+			if (!!(mb.timings.flags & TIMINGS_FLAGS_RGB_LIMITED) !=
+			    (drm_default_rgb_quant_range(mode) ==
+					HDMI_QUANTIZATION_RANGE_LIMITED))
+				mb.timings.video_id_code = 0;
+		}
+	}
+
+	/*
+	 * FIXME: To implement
+	 * switch(mode->flag & DRM_MODE_FLAG_3D_MASK) {
+	 * case DRM_MODE_FLAG_3D_NONE:
+	 * case DRM_MODE_FLAG_3D_FRAME_PACKING:
+	 * case DRM_MODE_FLAG_3D_FIELD_ALTERNATIVE:
+	 * case DRM_MODE_FLAG_3D_LINE_ALTERNATIVE:
+	 * case DRM_MODE_FLAG_3D_SIDE_BY_SIDE_FULL:
+	 * case DRM_MODE_FLAG_3D_L_DEPTH:
+	 * case DRM_MODE_FLAG_3D_L_DEPTH_GFX_GFX_DEPTH:
+	 * case DRM_MODE_FLAG_3D_TOP_AND_BOTTOM:
+	 * case DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF:
+	 * }
+	 */
+
+	ret = rpi_firmware_property_list(vc4->firmware, &mb, sizeof(mb));
+}
+
+static void vc4_crtc_disable(struct drm_crtc *crtc,
+			     struct drm_atomic_state *state)
+{
+	struct drm_device *dev = crtc->dev;
+	struct drm_plane *plane;
+
+	DRM_DEBUG_KMS("[CRTC:%d] vblanks off.\n",
+		      crtc->base.id);
+	drm_crtc_vblank_off(crtc);
+
+	/* Always turn the planes off on CRTC disable. In DRM, planes
+	 * are enabled/disabled through the update/disable hooks
+	 * above, and the CRTC enable/disable independently controls
+	 * whether anything scans out at all, but the firmware doesn't
+	 * give us a CRTC-level control for that.
+	 */
+
+	drm_atomic_crtc_for_each_plane(plane, crtc)
+		vc4_plane_atomic_disable(plane, state);
+
+	/*
+	 * Make sure we issue a vblank event after disabling the CRTC if
+	 * someone was waiting it.
+	 */
+	if (crtc->state->event) {
+		unsigned long flags;
+
+		spin_lock_irqsave(&dev->event_lock, flags);
+		drm_crtc_send_vblank_event(crtc, crtc->state->event);
+		crtc->state->event = NULL;
+		spin_unlock_irqrestore(&dev->event_lock, flags);
+	}
+}
+
+static void vc4_crtc_consume_event(struct drm_crtc *crtc)
+{
+	struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
+	struct drm_device *dev = crtc->dev;
+	unsigned long flags;
+
+	if (!crtc->state->event)
+		return;
+
+	crtc->state->event->pipe = drm_crtc_index(crtc);
+
+	WARN_ON(drm_crtc_vblank_get(crtc) != 0);
+
+	spin_lock_irqsave(&dev->event_lock, flags);
+	vc4_crtc->event = crtc->state->event;
+	crtc->state->event = NULL;
+	spin_unlock_irqrestore(&dev->event_lock, flags);
+}
+
+static void vc4_crtc_enable(struct drm_crtc *crtc,
+			    struct drm_atomic_state *state)
+{
+	struct drm_plane *plane;
+
+	DRM_DEBUG_KMS("[CRTC:%d] vblanks on.\n",
+		      crtc->base.id);
+	drm_crtc_vblank_on(crtc);
+	vc4_crtc_consume_event(crtc);
+
+	/* Unblank the planes (if they're supposed to be displayed). */
+	drm_atomic_crtc_for_each_plane(plane, crtc)
+		if (plane->state->fb)
+			vc4_plane_set_blank(plane, plane->state->visible);
+}
+
+static enum drm_mode_status
+vc4_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode)
+{
+	struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
+	struct drm_device *dev = crtc->dev;
+	struct vc4_dev *vc4 = to_vc4_dev(dev);
+	struct vc4_fkms *fkms = vc4->fkms;
+
+	/* Do not allow doublescan modes from user space */
+	if (mode->flags & DRM_MODE_FLAG_DBLSCAN) {
+		DRM_DEBUG_KMS("[CRTC:%d] Doublescan mode rejected.\n",
+			      crtc->base.id);
+		return MODE_NO_DBLESCAN;
+	}
+
+	/* Disable refresh rates > defined threshold (default 85Hz) as limited
+	 * gain from them
+	 */
+	if (drm_mode_vrefresh(mode) > fkms_max_refresh_rate)
+		return MODE_BAD_VVALUE;
+
+	/* Limit the pixel clock based on the HDMI clock limits from the
+	 * firmware
+	 */
+	switch (vc4_crtc->display_number) {
+	case 2:	/* HDMI0 */
+		if (fkms->cfg.max_pixel_clock[0] &&
+		    mode->clock > fkms->cfg.max_pixel_clock[0])
+			return MODE_CLOCK_HIGH;
+		break;
+	case 7:	/* HDMI1 */
+		if (fkms->cfg.max_pixel_clock[1] &&
+		    mode->clock > fkms->cfg.max_pixel_clock[1])
+			return MODE_CLOCK_HIGH;
+		break;
+	}
+
+	/* Pi4 can't generate odd horizontal timings on HDMI, so reject modes
+	 * that would set them.
+	 */
+	if (fkms->bcm2711 &&
+	    (vc4_crtc->display_number == 2 || vc4_crtc->display_number == 7) &&
+	    !(mode->flags & DRM_MODE_FLAG_DBLCLK) &&
+	    ((mode->hdisplay |				/* active */
+	      (mode->hsync_start - mode->hdisplay) |	/* front porch */
+	      (mode->hsync_end - mode->hsync_start) |	/* sync pulse */
+	      (mode->htotal - mode->hsync_end)) & 1))	/* back porch */ {
+		DRM_DEBUG_KMS("[CRTC:%d] Odd timing rejected %u %u %u %u.\n",
+			      crtc->base.id, mode->hdisplay, mode->hsync_start,
+			      mode->hsync_end, mode->htotal);
+		return MODE_H_ILLEGAL;
+	}
+
+	return MODE_OK;
+}
+
+static int vc4_crtc_atomic_check(struct drm_crtc *crtc,
+				 struct drm_atomic_state *state)
+{
+	struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state,
+									  crtc);
+	struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc_state);
+	struct drm_connector *conn;
+	struct drm_connector_state *conn_state;
+	int i;
+
+	DRM_DEBUG_KMS("[CRTC:%d] crtc_atomic_check.\n", crtc->base.id);
+
+	for_each_new_connector_in_state(crtc_state->state, conn, conn_state, i) {
+		if (conn_state->crtc != crtc)
+			continue;
+
+		vc4_state->margins.left = conn_state->tv.margins.left;
+		vc4_state->margins.right = conn_state->tv.margins.right;
+		vc4_state->margins.top = conn_state->tv.margins.top;
+		vc4_state->margins.bottom = conn_state->tv.margins.bottom;
+		break;
+	}
+	return 0;
+}
+
+static void vc4_crtc_atomic_flush(struct drm_crtc *crtc,
+				  struct drm_atomic_state *state)
+{
+	struct drm_crtc_state *old_state = drm_atomic_get_old_crtc_state(state,
+									 crtc);
+
+	DRM_DEBUG_KMS("[CRTC:%d] crtc_atomic_flush.\n",
+		      crtc->base.id);
+	if (crtc->state->active && old_state->active && crtc->state->event)
+		vc4_crtc_consume_event(crtc);
+}
+
+static void vc4_crtc_handle_page_flip(struct vc4_crtc *vc4_crtc)
+{
+	struct drm_crtc *crtc = &vc4_crtc->base;
+	struct drm_device *dev = crtc->dev;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->event_lock, flags);
+	if (vc4_crtc->event) {
+		drm_crtc_send_vblank_event(crtc, vc4_crtc->event);
+		vc4_crtc->event = NULL;
+		drm_crtc_vblank_put(crtc);
+	}
+	spin_unlock_irqrestore(&dev->event_lock, flags);
+}
+
+static irqreturn_t vc4_crtc_irq_handler(int irq, void *data)
+{
+	struct vc4_crtc **crtc_list = data;
+	int i;
+	u32 stat = readl(crtc_list[0]->regs + SMICS);
+	irqreturn_t ret = IRQ_NONE;
+	u32 chan;
+
+	if (stat & SMICS_INTERRUPTS) {
+		writel(0, crtc_list[0]->regs + SMICS);
+
+		chan = readl(crtc_list[0]->regs + SMIDSW0);
+
+		if ((chan & 0xFFFF0000) != SMI_NEW) {
+			/* Older firmware. Treat the one interrupt as vblank/
+			 * complete for all crtcs.
+			 */
+			for (i = 0; crtc_list[i]; i++) {
+				if (crtc_list[i]->vblank_enabled)
+					drm_crtc_handle_vblank(&crtc_list[i]->base);
+				vc4_crtc_handle_page_flip(crtc_list[i]);
+			}
+		} else {
+			if (chan & 1) {
+				writel(SMI_NEW, crtc_list[0]->regs + SMIDSW0);
+				if (crtc_list[0]->vblank_enabled)
+					drm_crtc_handle_vblank(&crtc_list[0]->base);
+				vc4_crtc_handle_page_flip(crtc_list[0]);
+			}
+
+			if (crtc_list[1]) {
+				/* Check for the secondary display too */
+				chan = readl(crtc_list[0]->regs + SMIDSW1);
+
+				if (chan & 1) {
+					writel(SMI_NEW, crtc_list[0]->regs + SMIDSW1);
+
+					if (crtc_list[1]->vblank_enabled)
+						drm_crtc_handle_vblank(&crtc_list[1]->base);
+					vc4_crtc_handle_page_flip(crtc_list[1]);
+				}
+			}
+		}
+
+		ret = IRQ_HANDLED;
+	}
+
+	return ret;
+}
+
+static int vc4_fkms_page_flip(struct drm_crtc *crtc,
+			      struct drm_framebuffer *fb,
+			      struct drm_pending_vblank_event *event,
+			      uint32_t flags,
+			      struct drm_modeset_acquire_ctx *ctx)
+{
+	if (flags & DRM_MODE_PAGE_FLIP_ASYNC) {
+		DRM_ERROR("Async flips aren't allowed\n");
+		return -EINVAL;
+	}
+
+	return drm_atomic_helper_page_flip(crtc, fb, event, flags, ctx);
+}
+
+static struct drm_crtc_state *
+vc4_fkms_crtc_duplicate_state(struct drm_crtc *crtc)
+{
+	struct vc4_crtc_state *vc4_state, *old_vc4_state;
+
+	vc4_state = kzalloc(sizeof(*vc4_state), GFP_KERNEL);
+	if (!vc4_state)
+		return NULL;
+
+	old_vc4_state = to_vc4_crtc_state(crtc->state);
+	vc4_state->margins = old_vc4_state->margins;
+
+	__drm_atomic_helper_crtc_duplicate_state(crtc, &vc4_state->base);
+	return &vc4_state->base;
+}
+
+static void
+vc4_fkms_crtc_reset(struct drm_crtc *crtc)
+{
+	if (crtc->state)
+		__drm_atomic_helper_crtc_destroy_state(crtc->state);
+
+	crtc->state = kzalloc(sizeof(*crtc->state), GFP_KERNEL);
+	if (crtc->state)
+		crtc->state->crtc = crtc;
+}
+
+static int vc4_fkms_enable_vblank(struct drm_crtc *crtc)
+{
+	struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
+
+	DRM_DEBUG_KMS("[CRTC:%d] enable_vblank.\n",
+		      crtc->base.id);
+	vc4_crtc->vblank_enabled = true;
+
+	return 0;
+}
+
+static void vc4_fkms_disable_vblank(struct drm_crtc *crtc)
+{
+	struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
+
+	DRM_DEBUG_KMS("[CRTC:%d] disable_vblank.\n",
+		      crtc->base.id);
+	vc4_crtc->vblank_enabled = false;
+}
+
+static const struct drm_crtc_funcs vc4_crtc_funcs = {
+	.set_config = drm_atomic_helper_set_config,
+	.destroy = drm_crtc_cleanup,
+	.page_flip = vc4_fkms_page_flip,
+	.set_property = NULL,
+	.cursor_set = NULL, /* handled by drm_mode_cursor_universal */
+	.cursor_move = NULL, /* handled by drm_mode_cursor_universal */
+	.reset = vc4_fkms_crtc_reset,
+	.atomic_duplicate_state = vc4_fkms_crtc_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+	.enable_vblank = vc4_fkms_enable_vblank,
+	.disable_vblank = vc4_fkms_disable_vblank,
+};
+
+static const struct drm_crtc_helper_funcs vc4_crtc_helper_funcs = {
+	.mode_set_nofb = vc4_crtc_mode_set_nofb,
+	.mode_valid = vc4_crtc_mode_valid,
+	.atomic_check = vc4_crtc_atomic_check,
+	.atomic_flush = vc4_crtc_atomic_flush,
+	.atomic_enable = vc4_crtc_enable,
+	.atomic_disable = vc4_crtc_disable,
+};
+
+static const struct of_device_id vc4_firmware_kms_dt_match[] = {
+	{ .compatible = "raspberrypi,rpi-firmware-kms" },
+	{ .compatible = "raspberrypi,rpi-firmware-kms-2711",
+	  .data = (void *)1 },
+	{}
+};
+
+static enum drm_connector_status
+vc4_fkms_connector_detect(struct drm_connector *connector, bool force)
+{
+	DRM_DEBUG_KMS("connector detect.\n");
+	return connector_status_connected;
+}
+
+/* Queries the firmware to populate a drm_mode structure for this display */
+static int vc4_fkms_get_fw_mode(struct vc4_fkms_connector *fkms_connector,
+				struct drm_display_mode *mode)
+{
+	struct vc4_dev *vc4 = fkms_connector->vc4_dev;
+	struct set_timings timings = { 0 };
+	int ret;
+
+	timings.display = fkms_connector->display_number;
+
+	ret = rpi_firmware_property(vc4->firmware,
+				    RPI_FIRMWARE_GET_DISPLAY_TIMING, &timings,
+				    sizeof(timings));
+	if (ret || !timings.clock)
+		/* No mode returned - abort */
+		return -1;
+
+	/* Equivalent to DRM_MODE macro. */
+	memset(mode, 0, sizeof(*mode));
+	strncpy(mode->name, "FIXED_MODE", sizeof(mode->name));
+	mode->status = 0;
+	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+	mode->clock = timings.clock;
+	mode->hdisplay = timings.hdisplay;
+	mode->hsync_start = timings.hsync_start;
+	mode->hsync_end = timings.hsync_end;
+	mode->htotal = timings.htotal;
+	mode->hskew = 0;
+	mode->vdisplay = timings.vdisplay;
+	mode->vsync_start = timings.vsync_start;
+	mode->vsync_end = timings.vsync_end;
+	mode->vtotal = timings.vtotal;
+	mode->vscan = timings.vscan;
+
+	if (timings.flags & TIMINGS_FLAGS_H_SYNC_POS)
+		mode->flags |= DRM_MODE_FLAG_PHSYNC;
+	else
+		mode->flags |= DRM_MODE_FLAG_NHSYNC;
+
+	if (timings.flags & TIMINGS_FLAGS_V_SYNC_POS)
+		mode->flags |= DRM_MODE_FLAG_PVSYNC;
+	else
+		mode->flags |= DRM_MODE_FLAG_NVSYNC;
+
+	if (timings.flags & TIMINGS_FLAGS_INTERLACE)
+		mode->flags |= DRM_MODE_FLAG_INTERLACE;
+
+	return 0;
+}
+
+static int vc4_fkms_get_edid_block(void *data, u8 *buf, unsigned int block,
+				   size_t len)
+{
+	struct vc4_fkms_connector *fkms_connector =
+					(struct vc4_fkms_connector *)data;
+	struct vc4_dev *vc4 = fkms_connector->vc4_dev;
+	struct mailbox_get_edid mb = {
+		.tag1 = { RPI_FIRMWARE_GET_EDID_BLOCK_DISPLAY,
+			  128 + 8, 0 },
+		.block = block,
+		.display_number = fkms_connector->display_number,
+	};
+	int ret = 0;
+
+	ret = rpi_firmware_property_list(vc4->firmware, &mb, sizeof(mb));
+
+	if (!ret)
+		memcpy(buf, mb.edid, len);
+
+	return ret;
+}
+
+static int vc4_fkms_connector_get_modes(struct drm_connector *connector)
+{
+	struct vc4_fkms_connector *fkms_connector =
+					to_vc4_fkms_connector(connector);
+	struct drm_encoder *encoder = fkms_connector->encoder;
+	struct vc4_fkms_encoder *vc4_encoder = to_vc4_fkms_encoder(encoder);
+	struct drm_display_mode fw_mode;
+	struct drm_display_mode *mode;
+	struct edid *edid;
+	int num_modes;
+
+	if (!vc4_fkms_get_fw_mode(fkms_connector, &fw_mode)) {
+		drm_mode_debug_printmodeline(&fw_mode);
+		mode = drm_mode_duplicate(connector->dev,
+					  &fw_mode);
+		drm_mode_probed_add(connector, mode);
+		num_modes = 1;	/* 1 mode */
+	} else {
+		edid = drm_do_get_edid(connector, vc4_fkms_get_edid_block,
+				       fkms_connector);
+
+		/* FIXME: Can we do CEC?
+		 * cec_s_phys_addr_from_edid(vc4->hdmi->cec_adap, edid);
+		 * if (!edid)
+		 *	return -ENODEV;
+		 */
+
+		vc4_encoder->hdmi_monitor = drm_detect_hdmi_monitor(edid);
+
+		drm_connector_update_edid_property(connector, edid);
+		num_modes = drm_add_edid_modes(connector, edid);
+		kfree(edid);
+	}
+
+	return num_modes;
+}
+
+/* This is the DSI panel resolution. Use this as a default should the firmware
+ * not respond to our request for the timings.
+ */
+static const struct drm_display_mode lcd_mode = {
+	DRM_MODE("800x480", DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
+		 25979400 / 1000,
+		 800, 800 + 1, 800 + 1 + 2, 800 + 1 + 2 + 46, 0,
+		 480, 480 + 7, 480 + 7 + 2, 480 + 7 + 2 + 21, 0,
+		 0)
+};
+
+static int vc4_fkms_lcd_connector_get_modes(struct drm_connector *connector)
+{
+	struct vc4_fkms_connector *fkms_connector =
+					to_vc4_fkms_connector(connector);
+	struct drm_display_mode *mode;
+	struct drm_display_mode fw_mode;
+
+	if (!vc4_fkms_get_fw_mode(fkms_connector, &fw_mode) && fw_mode.clock)
+		mode = drm_mode_duplicate(connector->dev,
+					  &fw_mode);
+	else
+		mode = drm_mode_duplicate(connector->dev,
+					  &lcd_mode);
+
+	if (!mode) {
+		DRM_ERROR("Failed to create a new display mode\n");
+		return -ENOMEM;
+	}
+
+	drm_mode_probed_add(connector, mode);
+
+	/* We have one mode */
+	return 1;
+}
+
+static struct drm_encoder *
+vc4_fkms_connector_best_encoder(struct drm_connector *connector)
+{
+	struct vc4_fkms_connector *fkms_connector =
+		to_vc4_fkms_connector(connector);
+	DRM_DEBUG_KMS("best_connector.\n");
+	return fkms_connector->encoder;
+}
+
+static void vc4_fkms_connector_destroy(struct drm_connector *connector)
+{
+	DRM_DEBUG_KMS("[CONNECTOR:%d] destroy.\n",
+		      connector->base.id);
+	drm_connector_unregister(connector);
+	drm_connector_cleanup(connector);
+}
+
+/**
+ * vc4_connector_duplicate_state - duplicate connector state
+ * @connector: digital connector
+ *
+ * Allocates and returns a copy of the connector state (both common and
+ * digital connector specific) for the specified connector.
+ *
+ * Returns: The newly allocated connector state, or NULL on failure.
+ */
+struct drm_connector_state *
+vc4_connector_duplicate_state(struct drm_connector *connector)
+{
+	struct vc4_fkms_connector_state *state;
+
+	state = kmemdup(connector->state, sizeof(*state), GFP_KERNEL);
+	if (!state)
+		return NULL;
+
+	__drm_atomic_helper_connector_duplicate_state(connector, &state->base);
+	return &state->base;
+}
+
+/**
+ * vc4_connector_atomic_get_property - hook for connector->atomic_get_property.
+ * @connector: Connector to get the property for.
+ * @state: Connector state to retrieve the property from.
+ * @property: Property to retrieve.
+ * @val: Return value for the property.
+ *
+ * Returns the atomic property value for a digital connector.
+ */
+int vc4_connector_atomic_get_property(struct drm_connector *connector,
+				      const struct drm_connector_state *state,
+				      struct drm_property *property,
+				      uint64_t *val)
+{
+	struct vc4_fkms_connector *fkms_connector =
+					to_vc4_fkms_connector(connector);
+	struct vc4_fkms_connector_state *vc4_conn_state =
+					to_vc4_fkms_connector_state(state);
+
+	if (property == fkms_connector->broadcast_rgb_property) {
+		*val = vc4_conn_state->broadcast_rgb;
+	} else {
+		DRM_DEBUG_ATOMIC("Unknown property [PROP:%d:%s]\n",
+				 property->base.id, property->name);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/**
+ * vc4_connector_atomic_set_property - hook for connector->atomic_set_property.
+ * @connector: Connector to set the property for.
+ * @state: Connector state to set the property on.
+ * @property: Property to set.
+ * @val: New value for the property.
+ *
+ * Sets the atomic property value for a digital connector.
+ */
+int vc4_connector_atomic_set_property(struct drm_connector *connector,
+				      struct drm_connector_state *state,
+				      struct drm_property *property,
+				      uint64_t val)
+{
+	struct vc4_fkms_connector *fkms_connector =
+					to_vc4_fkms_connector(connector);
+	struct vc4_fkms_connector_state *vc4_conn_state =
+					to_vc4_fkms_connector_state(state);
+
+	if (property == fkms_connector->broadcast_rgb_property) {
+		vc4_conn_state->broadcast_rgb = val;
+		return 0;
+	}
+
+	DRM_DEBUG_ATOMIC("Unknown property [PROP:%d:%s]\n",
+			 property->base.id, property->name);
+	return -EINVAL;
+}
+
+int vc4_connector_atomic_check(struct drm_connector *connector,
+			       struct drm_atomic_state *state)
+{
+	struct drm_connector_state *old_state =
+		drm_atomic_get_old_connector_state(state, connector);
+	struct vc4_fkms_connector_state *vc4_old_state =
+					to_vc4_fkms_connector_state(old_state);
+	struct drm_connector_state *new_state =
+		drm_atomic_get_new_connector_state(state, connector);
+	struct vc4_fkms_connector_state *vc4_new_state =
+					to_vc4_fkms_connector_state(new_state);
+	struct drm_crtc *crtc = new_state->crtc;
+
+	if (!crtc)
+		return 0;
+
+	if (vc4_old_state->broadcast_rgb != vc4_new_state->broadcast_rgb) {
+		struct drm_crtc_state *crtc_state;
+
+		crtc_state = drm_atomic_get_crtc_state(state, crtc);
+		if (IS_ERR(crtc_state))
+			return PTR_ERR(crtc_state);
+
+		crtc_state->mode_changed = true;
+	}
+	return 0;
+}
+
+static void vc4_hdmi_connector_reset(struct drm_connector *connector)
+{
+	drm_atomic_helper_connector_reset(connector);
+	drm_atomic_helper_connector_tv_reset(connector);
+}
+
+static const struct drm_connector_funcs vc4_fkms_connector_funcs = {
+	.detect = vc4_fkms_connector_detect,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = vc4_fkms_connector_destroy,
+	.reset = vc4_hdmi_connector_reset,
+	.atomic_duplicate_state = vc4_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+	.atomic_get_property = vc4_connector_atomic_get_property,
+	.atomic_set_property = vc4_connector_atomic_set_property,
+};
+
+static const struct drm_connector_helper_funcs vc4_fkms_connector_helper_funcs = {
+	.get_modes = vc4_fkms_connector_get_modes,
+	.best_encoder = vc4_fkms_connector_best_encoder,
+	.atomic_check = vc4_connector_atomic_check,
+};
+
+static const struct drm_connector_helper_funcs vc4_fkms_lcd_conn_helper_funcs = {
+	.get_modes = vc4_fkms_lcd_connector_get_modes,
+	.best_encoder = vc4_fkms_connector_best_encoder,
+};
+
+static const struct drm_prop_enum_list broadcast_rgb_names[] = {
+	{ VC4_BROADCAST_RGB_AUTO, "Automatic" },
+	{ VC4_BROADCAST_RGB_FULL, "Full" },
+	{ VC4_BROADCAST_RGB_LIMITED, "Limited 16:235" },
+};
+
+static void
+vc4_attach_broadcast_rgb_property(struct vc4_fkms_connector *fkms_connector)
+{
+	struct drm_device *dev = fkms_connector->base.dev;
+	struct drm_property *prop;
+
+	prop = fkms_connector->broadcast_rgb_property;
+	if (!prop) {
+		prop = drm_property_create_enum(dev, DRM_MODE_PROP_ENUM,
+						"Broadcast RGB",
+						broadcast_rgb_names,
+						ARRAY_SIZE(broadcast_rgb_names));
+		if (!prop)
+			return;
+
+		fkms_connector->broadcast_rgb_property = prop;
+	}
+
+	drm_object_attach_property(&fkms_connector->base.base, prop, 0);
+}
+
+static struct drm_connector *
+vc4_fkms_connector_init(struct drm_device *dev, struct drm_encoder *encoder,
+			u32 display_num)
+{
+	struct drm_connector *connector = NULL;
+	struct vc4_fkms_connector *fkms_connector;
+	struct vc4_fkms_connector_state *conn_state = NULL;
+	struct vc4_dev *vc4_dev = to_vc4_dev(dev);
+	int ret = 0;
+
+	DRM_DEBUG_KMS("connector_init, display_num %u\n", display_num);
+
+	fkms_connector = devm_kzalloc(dev->dev, sizeof(*fkms_connector),
+				      GFP_KERNEL);
+	if (!fkms_connector)
+		return ERR_PTR(-ENOMEM);
+
+	/*
+	 * Allocate enough memory to hold vc4_fkms_connector_state,
+	 */
+	conn_state = kzalloc(sizeof(*conn_state), GFP_KERNEL);
+	if (!conn_state) {
+		kfree(fkms_connector);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	connector = &fkms_connector->base;
+
+	fkms_connector->encoder = encoder;
+	fkms_connector->display_number = display_num;
+	fkms_connector->display_type = vc4_get_display_type(display_num);
+	fkms_connector->vc4_dev = vc4_dev;
+
+	__drm_atomic_helper_connector_reset(connector,
+					    &conn_state->base);
+
+	if (fkms_connector->display_type == DRM_MODE_ENCODER_DSI) {
+		drm_connector_init(dev, connector, &vc4_fkms_connector_funcs,
+				   DRM_MODE_CONNECTOR_DSI);
+		drm_connector_helper_add(connector,
+					 &vc4_fkms_lcd_conn_helper_funcs);
+		connector->interlace_allowed = 0;
+	} else if (fkms_connector->display_type == DRM_MODE_ENCODER_TVDAC) {
+		drm_connector_init(dev, connector, &vc4_fkms_connector_funcs,
+				   DRM_MODE_CONNECTOR_Composite);
+		drm_connector_helper_add(connector,
+					 &vc4_fkms_lcd_conn_helper_funcs);
+		connector->interlace_allowed = 1;
+	} else {
+		drm_connector_init(dev, connector, &vc4_fkms_connector_funcs,
+				   DRM_MODE_CONNECTOR_HDMIA);
+		drm_connector_helper_add(connector,
+					 &vc4_fkms_connector_helper_funcs);
+		connector->interlace_allowed = 1;
+	}
+
+	ret = drm_mode_create_tv_margin_properties(dev);
+	if (ret)
+		goto fail;
+
+	drm_connector_attach_tv_margin_properties(connector);
+
+	connector->polled = (DRM_CONNECTOR_POLL_CONNECT |
+			     DRM_CONNECTOR_POLL_DISCONNECT);
+
+	connector->doublescan_allowed = 0;
+
+	vc4_attach_broadcast_rgb_property(fkms_connector);
+
+	drm_connector_attach_encoder(connector, encoder);
+
+	return connector;
+
+ fail:
+	if (connector)
+		vc4_fkms_connector_destroy(connector);
+
+	return ERR_PTR(ret);
+}
+
+static void vc4_fkms_encoder_destroy(struct drm_encoder *encoder)
+{
+	DRM_DEBUG_KMS("Encoder_destroy\n");
+	drm_encoder_cleanup(encoder);
+}
+
+static const struct drm_encoder_funcs vc4_fkms_encoder_funcs = {
+	.destroy = vc4_fkms_encoder_destroy,
+};
+
+static void vc4_fkms_display_power(struct drm_encoder *encoder, bool power)
+{
+	struct vc4_fkms_encoder *vc4_encoder = to_vc4_fkms_encoder(encoder);
+	struct vc4_dev *vc4 = to_vc4_dev(encoder->dev);
+
+	struct mailbox_display_pwr pwr = {
+		.tag1 = {RPI_FIRMWARE_SET_DISPLAY_POWER, 8, 0, },
+		.display = vc4_encoder->display_num,
+		.state = power ? 1 : 0,
+	};
+
+	rpi_firmware_property_list(vc4->firmware, &pwr, sizeof(pwr));
+}
+
+static void vc4_fkms_encoder_enable(struct drm_encoder *encoder)
+{
+	vc4_fkms_display_power(encoder, true);
+	DRM_DEBUG_KMS("Encoder_enable\n");
+}
+
+static void vc4_fkms_encoder_disable(struct drm_encoder *encoder)
+{
+	vc4_fkms_display_power(encoder, false);
+	DRM_DEBUG_KMS("Encoder_disable\n");
+}
+
+static const struct drm_encoder_helper_funcs vc4_fkms_encoder_helper_funcs = {
+	.enable = vc4_fkms_encoder_enable,
+	.disable = vc4_fkms_encoder_disable,
+};
+
+static int vc4_fkms_create_screen(struct device *dev, struct drm_device *drm,
+				  int display_idx, int display_ref,
+				  struct vc4_crtc **ret_crtc)
+{
+	struct vc4_dev *vc4 = to_vc4_dev(drm);
+	struct vc4_crtc *vc4_crtc;
+	struct vc4_fkms_encoder *vc4_encoder;
+	struct drm_crtc *crtc;
+	struct drm_plane *destroy_plane, *temp;
+	struct mailbox_blank_display blank = {
+		.tag1 = {RPI_FIRMWARE_FRAMEBUFFER_SET_DISPLAY_NUM, 4, 0, },
+		.display = display_idx,
+		.tag2 = { RPI_FIRMWARE_FRAMEBUFFER_BLANK, 4, 0, },
+		.blank = 1,
+	};
+	struct drm_plane *planes[PLANES_PER_CRTC];
+	int ret, i;
+
+	vc4_crtc = devm_kzalloc(dev, sizeof(*vc4_crtc), GFP_KERNEL);
+	if (!vc4_crtc)
+		return -ENOMEM;
+	crtc = &vc4_crtc->base;
+
+	vc4_crtc->display_number = display_ref;
+	vc4_crtc->display_type = vc4_get_display_type(display_ref);
+
+	/* Blank the firmware provided framebuffer */
+	rpi_firmware_property_list(vc4->firmware, &blank, sizeof(blank));
+
+	for (i = 0; i < PLANES_PER_CRTC; i++) {
+		planes[i] = vc4_fkms_plane_init(drm,
+						(i == 0) ?
+						  DRM_PLANE_TYPE_PRIMARY :
+						  (i == PLANES_PER_CRTC - 1) ?
+							DRM_PLANE_TYPE_CURSOR :
+							DRM_PLANE_TYPE_OVERLAY,
+						display_ref,
+						i + (display_idx * PLANES_PER_CRTC)
+					       );
+		if (IS_ERR(planes[i])) {
+			dev_err(dev, "failed to construct plane %u\n", i);
+			ret = PTR_ERR(planes[i]);
+			goto err;
+		}
+	}
+
+	drm_crtc_init_with_planes(drm, crtc, planes[0],
+				  planes[PLANES_PER_CRTC - 1], &vc4_crtc_funcs,
+				  NULL);
+	drm_crtc_helper_add(crtc, &vc4_crtc_helper_funcs);
+
+	/* Update the possible_crtcs mask for the overlay plane(s) */
+	for (i = 1; i < (PLANES_PER_CRTC - 1); i++)
+		planes[i]->possible_crtcs = drm_crtc_mask(crtc);
+
+	vc4_encoder = devm_kzalloc(dev, sizeof(*vc4_encoder), GFP_KERNEL);
+	if (!vc4_encoder)
+		return -ENOMEM;
+	vc4_crtc->encoder = &vc4_encoder->base;
+
+	vc4_encoder->display_num = display_ref;
+	vc4_encoder->base.possible_crtcs |= drm_crtc_mask(crtc);
+
+	drm_encoder_init(drm, &vc4_encoder->base, &vc4_fkms_encoder_funcs,
+			 vc4_crtc->display_type, NULL);
+	drm_encoder_helper_add(&vc4_encoder->base,
+			       &vc4_fkms_encoder_helper_funcs);
+
+	vc4_crtc->connector = vc4_fkms_connector_init(drm, &vc4_encoder->base,
+						      display_ref);
+	if (IS_ERR(vc4_crtc->connector)) {
+		ret = PTR_ERR(vc4_crtc->connector);
+		goto err_destroy_encoder;
+	}
+
+	*ret_crtc = vc4_crtc;
+
+	return 0;
+
+err_destroy_encoder:
+	vc4_fkms_encoder_destroy(vc4_crtc->encoder);
+	list_for_each_entry_safe(destroy_plane, temp,
+				 &drm->mode_config.plane_list, head) {
+		if (destroy_plane->possible_crtcs == 1 << drm_crtc_index(crtc))
+			destroy_plane->funcs->destroy(destroy_plane);
+	}
+err:
+	return ret;
+}
+
+static int vc4_fkms_bind(struct device *dev, struct device *master, void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct drm_device *drm = dev_get_drvdata(master);
+	struct vc4_dev *vc4 = to_vc4_dev(drm);
+	struct device_node *firmware_node;
+	const struct of_device_id *match;
+	struct vc4_crtc **crtc_list;
+	u32 num_displays, display_num;
+	struct vc4_fkms *fkms;
+	int ret;
+	u32 display_id;
+
+	vc4->firmware_kms = true;
+
+	fkms = devm_kzalloc(dev, sizeof(*fkms), GFP_KERNEL);
+	if (!fkms)
+		return -ENOMEM;
+
+	match = of_match_device(vc4_firmware_kms_dt_match, dev);
+	if (!match)
+		return -ENODEV;
+	if (match->data)
+		fkms->bcm2711 = true;
+
+	firmware_node = of_parse_phandle(dev->of_node, "brcm,firmware", 0);
+	vc4->firmware = devm_rpi_firmware_get(&pdev->dev, firmware_node);
+	if (!vc4->firmware) {
+		DRM_DEBUG("Failed to get Raspberry Pi firmware reference.\n");
+		return -EPROBE_DEFER;
+	}
+	of_node_put(firmware_node);
+
+	ret = rpi_firmware_property(vc4->firmware,
+				    RPI_FIRMWARE_FRAMEBUFFER_GET_NUM_DISPLAYS,
+				    &num_displays, sizeof(u32));
+
+	/* If we fail to get the number of displays, then
+	 * assume old firmware that doesn't have the mailbox call, so just
+	 * set one display
+	 */
+	if (ret) {
+		num_displays = 1;
+		DRM_WARN("Unable to determine number of displays - assuming 1\n");
+		ret = 0;
+	}
+
+	ret = rpi_firmware_property(vc4->firmware,
+				    RPI_FIRMWARE_GET_DISPLAY_CFG,
+				    &fkms->cfg, sizeof(fkms->cfg));
+
+	if (ret)
+		return -EINVAL;
+	/* The firmware works in Hz. This will be compared against kHz, so div
+	 * 1000 now rather than multiple times later.
+	 */
+	fkms->cfg.max_pixel_clock[0] /= 1000;
+	fkms->cfg.max_pixel_clock[1] /= 1000;
+
+	/* Allocate a list, with space for a NULL on the end */
+	crtc_list = devm_kzalloc(dev, sizeof(crtc_list) * (num_displays + 1),
+				 GFP_KERNEL);
+	if (!crtc_list)
+		return -ENOMEM;
+
+	for (display_num = 0; display_num < num_displays; display_num++) {
+		display_id = display_num;
+		ret = rpi_firmware_property(vc4->firmware,
+					    RPI_FIRMWARE_FRAMEBUFFER_GET_DISPLAY_ID,
+					    &display_id, sizeof(display_id));
+		/* FIXME: Determine the correct error handling here.
+		 * Should we fail to create the one "screen" but keep the
+		 * others, or fail the whole thing?
+		 */
+		if (ret)
+			DRM_ERROR("Failed to get display id %u\n", display_num);
+
+		ret = vc4_fkms_create_screen(dev, drm, display_num, display_id,
+					     &crtc_list[display_num]);
+		if (ret)
+			DRM_ERROR("Oh dear, failed to create display %u\n",
+				  display_num);
+	}
+
+	if (num_displays > 0) {
+		/* Map the SMI interrupt reg */
+		crtc_list[0]->regs = vc4_ioremap_regs(pdev, 0);
+		if (IS_ERR(crtc_list[0]->regs))
+			DRM_ERROR("Oh dear, failed to map registers\n");
+
+		writel(0, crtc_list[0]->regs + SMICS);
+		ret = devm_request_irq(dev, platform_get_irq(pdev, 0),
+				       vc4_crtc_irq_handler, 0,
+				       "vc4 firmware kms", crtc_list);
+		if (ret)
+			DRM_ERROR("Oh dear, failed to register IRQ\n");
+	} else {
+		DRM_WARN("No displays found. Consider forcing hotplug if HDMI is attached\n");
+	}
+
+	vc4->fkms = fkms;
+
+	platform_set_drvdata(pdev, crtc_list);
+
+	return 0;
+}
+
+static void vc4_fkms_unbind(struct device *dev, struct device *master,
+			    void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct vc4_crtc **crtc_list = dev_get_drvdata(dev);
+	int i;
+
+	for (i = 0; crtc_list[i]; i++) {
+		vc4_fkms_connector_destroy(crtc_list[i]->connector);
+		vc4_fkms_encoder_destroy(crtc_list[i]->encoder);
+		drm_crtc_cleanup(&crtc_list[i]->base);
+	}
+
+	platform_set_drvdata(pdev, NULL);
+}
+
+static const struct component_ops vc4_fkms_ops = {
+	.bind   = vc4_fkms_bind,
+	.unbind = vc4_fkms_unbind,
+};
+
+static int vc4_fkms_probe(struct platform_device *pdev)
+{
+	return component_add(&pdev->dev, &vc4_fkms_ops);
+}
+
+static int vc4_fkms_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &vc4_fkms_ops);
+	return 0;
+}
+
+struct platform_driver vc4_firmware_kms_driver = {
+	.probe = vc4_fkms_probe,
+	.remove = vc4_fkms_remove,
+	.driver = {
+		.name = "vc4_firmware_kms",
+		.of_match_table = vc4_firmware_kms_dt_match,
+	},
+};
--- a/drivers/gpu/drm/vc4/vc4_kms.c
+++ b/drivers/gpu/drm/vc4/vc4_kms.c
@@ -162,6 +162,9 @@ vc4_ctm_commit(struct vc4_dev *vc4, stru
 	struct vc4_ctm_state *ctm_state = to_vc4_ctm_state(vc4->ctm_manager.state);
 	struct drm_color_ctm *ctm = ctm_state->ctm;
 
+	if (vc4->firmware_kms)
+		return;
+
 	if (ctm_state->fifo) {
 		HVS_WRITE(SCALER_OLEDCOEF2,
 			  VC4_SET_FIELD(vc4_ctm_s31_32_to_s0_9(ctm->matrix[0]),
@@ -367,7 +370,7 @@ static void vc4_atomic_commit_tail(struc
 	for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) {
 		struct vc4_crtc_state *vc4_crtc_state;
 
-		if (!new_crtc_state->commit)
+		if (!new_crtc_state->commit || vc4->firmware_kms)
 			continue;
 
 		vc4_crtc_state = to_vc4_crtc_state(new_crtc_state);
@@ -393,7 +396,7 @@ static void vc4_atomic_commit_tail(struc
 		old_hvs_state->fifo_state[channel].pending_commit = NULL;
 	}
 
-	if (vc4->is_vc5) {
+	if (vc4->is_vc5 && !vc4->firmware_kms) {
 		unsigned long state_rate = max(old_hvs_state->core_clock_rate,
 					       new_hvs_state->core_clock_rate);
 		unsigned long core_rate = max_t(unsigned long,
@@ -412,10 +415,12 @@ static void vc4_atomic_commit_tail(struc
 
 	vc4_ctm_commit(vc4, state);
 
-	if (vc4->is_vc5)
-		vc5_hvs_pv_muxing_commit(vc4, state);
-	else
-		vc4_hvs_pv_muxing_commit(vc4, state);
+	if (!vc4->firmware_kms) {
+		if (vc4->is_vc5)
+			vc5_hvs_pv_muxing_commit(vc4, state);
+		else
+			vc4_hvs_pv_muxing_commit(vc4, state);
+	}
 
 	drm_atomic_helper_commit_planes(dev, state,
 					DRM_PLANE_COMMIT_ACTIVE_ONLY);
@@ -430,7 +435,7 @@ static void vc4_atomic_commit_tail(struc
 
 	drm_atomic_helper_cleanup_planes(dev, state);
 
-	if (vc4->is_vc5) {
+	if (vc4->is_vc5 && !vc4->firmware_kms) {
 		drm_dbg(dev, "Running the core clock at %lu Hz\n",
 			new_hvs_state->core_clock_rate);
 
@@ -447,11 +452,21 @@ static void vc4_atomic_commit_tail(struc
 
 static int vc4_atomic_commit_setup(struct drm_atomic_state *state)
 {
+	struct drm_device *dev = state->dev;
+	struct vc4_dev *vc4 = to_vc4_dev(dev);
 	struct drm_crtc_state *crtc_state;
 	struct vc4_hvs_state *hvs_state;
 	struct drm_crtc *crtc;
 	unsigned int i;
 
+	/* We know for sure we don't want an async update here. Set
+	 * state->legacy_cursor_update to false to prevent
+	 * drm_atomic_helper_setup_commit() from auto-completing
+	 * commit->flip_done.
+	 */
+	if (!vc4->firmware_kms)
+		state->legacy_cursor_update = false;
+
 	hvs_state = vc4_hvs_get_new_global_state(state);
 	if (WARN_ON(IS_ERR(hvs_state)))
 		return PTR_ERR(hvs_state);
@@ -806,6 +821,7 @@ static int vc4_hvs_channels_obj_init(str
 static int vc4_pv_muxing_atomic_check(struct drm_device *dev,
 				      struct drm_atomic_state *state)
 {
+	struct vc4_dev *vc4 = to_vc4_dev(state->dev);
 	struct vc4_hvs_state *hvs_new_state;
 	struct drm_crtc_state *old_crtc_state, *new_crtc_state;
 	struct drm_crtc *crtc;
@@ -829,6 +845,9 @@ static int vc4_pv_muxing_atomic_check(st
 		unsigned int matching_channels;
 		unsigned int channel;
 
+		if (vc4->firmware_kms)
+			continue;
+
 		drm_dbg(dev, "%s: Trying to find a channel.\n", crtc->name);
 
 		/* Nothing to do here, let's skip it */
@@ -1047,6 +1066,8 @@ int vc4_kms_load(struct drm_device *dev)
 	dev->mode_config.helper_private = &vc4_mode_config_helpers;
 	dev->mode_config.preferred_depth = 24;
 	dev->mode_config.async_page_flip = true;
+	if (vc4->firmware_kms)
+		dev->mode_config.normalize_zpos = true;
 
 	ret = vc4_ctm_obj_init(vc4);
 	if (ret)
--- /dev/null
+++ b/drivers/gpu/drm/vc4/vc_image_types.h
@@ -0,0 +1,175 @@
+
+/*
+ * Copyright (c) 2012, Broadcom Europe Ltd
+ *
+ * Values taken from vc_image_types.h released by Broadcom at
+ * https://github.com/raspberrypi/userland/blob/master/interface/vctypes/vc_image_types.h
+ * and vc_image_structs.h at
+ * https://github.com/raspberrypi/userland/blob/master/interface/vctypes/vc_image_structs.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+enum {
+	VC_IMAGE_MIN = 0, //bounds for error checking
+
+	VC_IMAGE_RGB565 = 1,
+	VC_IMAGE_1BPP,
+	VC_IMAGE_YUV420,
+	VC_IMAGE_48BPP,
+	VC_IMAGE_RGB888,
+	VC_IMAGE_8BPP,
+	/* 4bpp palettised image */
+	VC_IMAGE_4BPP,
+	/* A separated format of 16 colour/light shorts followed by 16 z
+	 * values
+	 */
+	VC_IMAGE_3D32,
+	/* 16 colours followed by 16 z values */
+	VC_IMAGE_3D32B,
+	/* A separated format of 16 material/colour/light shorts followed by
+	 * 16 z values
+	 */
+	VC_IMAGE_3D32MAT,
+	/* 32 bit format containing 18 bits of 6.6.6 RGB, 9 bits per short */
+	VC_IMAGE_RGB2X9,
+	/* 32-bit format holding 18 bits of 6.6.6 RGB */
+	VC_IMAGE_RGB666,
+	/* 4bpp palettised image with embedded palette */
+	VC_IMAGE_PAL4_OBSOLETE,
+	/* 8bpp palettised image with embedded palette */
+	VC_IMAGE_PAL8_OBSOLETE,
+	/* RGB888 with an alpha byte after each pixel */
+	VC_IMAGE_RGBA32,
+	/* a line of Y (32-byte padded), a line of U (16-byte padded), and a
+	 * line of V (16-byte padded)
+	 */
+	VC_IMAGE_YUV422,
+	/* RGB565 with a transparent patch */
+	VC_IMAGE_RGBA565,
+	/* Compressed (4444) version of RGBA32 */
+	VC_IMAGE_RGBA16,
+	/* VCIII codec format */
+	VC_IMAGE_YUV_UV,
+	/* VCIII T-format RGBA8888 */
+	VC_IMAGE_TF_RGBA32,
+	/* VCIII T-format RGBx8888 */
+	VC_IMAGE_TF_RGBX32,
+	/* VCIII T-format float */
+	VC_IMAGE_TF_FLOAT,
+	/* VCIII T-format RGBA4444 */
+	VC_IMAGE_TF_RGBA16,
+	/* VCIII T-format RGB5551 */
+	VC_IMAGE_TF_RGBA5551,
+	/* VCIII T-format RGB565 */
+	VC_IMAGE_TF_RGB565,
+	/* VCIII T-format 8-bit luma and 8-bit alpha */
+	VC_IMAGE_TF_YA88,
+	/* VCIII T-format 8 bit generic sample */
+	VC_IMAGE_TF_BYTE,
+	/* VCIII T-format 8-bit palette */
+	VC_IMAGE_TF_PAL8,
+	/* VCIII T-format 4-bit palette */
+	VC_IMAGE_TF_PAL4,
+	/* VCIII T-format Ericsson Texture Compressed */
+	VC_IMAGE_TF_ETC1,
+	/* RGB888 with R & B swapped */
+	VC_IMAGE_BGR888,
+	/* RGB888 with R & B swapped, but with no pitch, i.e. no padding after
+	 * each row of pixels
+	 */
+	VC_IMAGE_BGR888_NP,
+	/* Bayer image, extra defines which variant is being used */
+	VC_IMAGE_BAYER,
+	/* General wrapper for codec images e.g. JPEG from camera */
+	VC_IMAGE_CODEC,
+	/* VCIII codec format */
+	VC_IMAGE_YUV_UV32,
+	/* VCIII T-format 8-bit luma */
+	VC_IMAGE_TF_Y8,
+	/* VCIII T-format 8-bit alpha */
+	VC_IMAGE_TF_A8,
+	/* VCIII T-format 16-bit generic sample */
+	VC_IMAGE_TF_SHORT,
+	/* VCIII T-format 1bpp black/white */
+	VC_IMAGE_TF_1BPP,
+	VC_IMAGE_OPENGL,
+	/* VCIII-B0 HVS YUV 4:4:4 interleaved samples */
+	VC_IMAGE_YUV444I,
+	/* Y, U, & V planes separately (VC_IMAGE_YUV422 has them interleaved on
+	 * a per line basis)
+	 */
+	VC_IMAGE_YUV422PLANAR,
+	/* 32bpp with 8bit alpha at MS byte, with R, G, B (LS byte) */
+	VC_IMAGE_ARGB8888,
+	/* 32bpp with 8bit unused at MS byte, with R, G, B (LS byte) */
+	VC_IMAGE_XRGB8888,
+
+	/* interleaved 8 bit samples of Y, U, Y, V (4 flavours) */
+	VC_IMAGE_YUV422YUYV,
+	VC_IMAGE_YUV422YVYU,
+	VC_IMAGE_YUV422UYVY,
+	VC_IMAGE_YUV422VYUY,
+
+	/* 32bpp like RGBA32 but with unused alpha */
+	VC_IMAGE_RGBX32,
+	/* 32bpp, corresponding to RGBA with unused alpha */
+	VC_IMAGE_RGBX8888,
+	/* 32bpp, corresponding to BGRA with unused alpha */
+	VC_IMAGE_BGRX8888,
+
+	/* Y as a plane, then UV byte interleaved in plane with same pitch,
+	 * half height
+	 */
+	VC_IMAGE_YUV420SP,
+
+	/* Y, U, & V planes separately 4:4:4 */
+	VC_IMAGE_YUV444PLANAR,
+
+	/* T-format 8-bit U - same as TF_Y8 buf from U plane */
+	VC_IMAGE_TF_U8,
+	/* T-format 8-bit U - same as TF_Y8 buf from V plane */
+	VC_IMAGE_TF_V8,
+
+	/* YUV4:2:0 planar, 16bit values */
+	VC_IMAGE_YUV420_16,
+	/* YUV4:2:0 codec format, 16bit values */
+	VC_IMAGE_YUV_UV_16,
+	/* YUV4:2:0 with U,V in side-by-side format */
+	VC_IMAGE_YUV420_S,
+	/* 10-bit YUV 420 column image format */
+	VC_IMAGE_YUV10COL,
+	/* 32-bpp, 10-bit R/G/B, 2-bit Alpha */
+	VC_IMAGE_RGBA1010102,
+
+	VC_IMAGE_MAX,     /* bounds for error checking */
+	VC_IMAGE_FORCE_ENUM_16BIT = 0xffff,
+};
+
+enum {
+	/* Unknown or unset - defaults to BT601 interstitial */
+	VC_IMAGE_YUVINFO_UNSPECIFIED    = 0,
+
+	/* colour-space conversions data [4 bits] */
+
+	/* ITU-R BT.601-5 [SDTV] (compatible with VideoCore-II) */
+	VC_IMAGE_YUVINFO_CSC_ITUR_BT601      = 1,
+	/* ITU-R BT.709-3 [HDTV] */
+	VC_IMAGE_YUVINFO_CSC_ITUR_BT709      = 2,
+	/* JPEG JFIF */
+	VC_IMAGE_YUVINFO_CSC_JPEG_JFIF       = 3,
+	/* Title 47 Code of Federal Regulations (2003) 73.682 (a) (20) */
+	VC_IMAGE_YUVINFO_CSC_FCC             = 4,
+	/* Society of Motion Picture and Television Engineers 240M (1999) */
+	VC_IMAGE_YUVINFO_CSC_SMPTE_240M      = 5,
+	/* ITU-R BT.470-2 System M */
+	VC_IMAGE_YUVINFO_CSC_ITUR_BT470_2_M  = 6,
+	/* ITU-R BT.470-2 System B,G */
+	VC_IMAGE_YUVINFO_CSC_ITUR_BT470_2_BG = 7,
+	/* JPEG JFIF, but with 16..255 luma */
+	VC_IMAGE_YUVINFO_CSC_JPEG_JFIF_Y16_255 = 8,
+	/* Rec 2020 */
+	VC_IMAGE_YUVINFO_CSC_REC_2020        = 9,
+};
