diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index ee579916f874..2f15336cd25e 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -69,6 +69,7 @@ source "drivers/media/platform/aspeed/Kconfig"
 source "drivers/media/platform/atmel/Kconfig"
 source "drivers/media/platform/cadence/Kconfig"
 source "drivers/media/platform/chips-media/Kconfig"
+source "drivers/media/platform/google/Kconfig"
 source "drivers/media/platform/intel/Kconfig"
 source "drivers/media/platform/marvell/Kconfig"
 source "drivers/media/platform/mediatek/Kconfig"
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index 5453bb868e67..db4a0fc7bfd3 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -12,6 +12,7 @@ obj-y += aspeed/
 obj-y += atmel/
 obj-y += cadence/
 obj-y += chips-media/
+obj-y += google/
 obj-y += intel/
 obj-y += marvell/
 obj-y += mediatek/
diff --git a/drivers/media/platform/google/Kconfig b/drivers/media/platform/google/Kconfig
new file mode 100644
index 000000000000..8dd3a955bef8
--- /dev/null
+++ b/drivers/media/platform/google/Kconfig
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+source "drivers/media/platform/google/chameleonv3/Kconfig"
+
diff --git a/drivers/media/platform/google/Makefile b/drivers/media/platform/google/Makefile
new file mode 100644
index 000000000000..c971a09faeb4
--- /dev/null
+++ b/drivers/media/platform/google/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-y += chameleonv3/
diff --git a/drivers/media/platform/google/chameleonv3/Kconfig b/drivers/media/platform/google/chameleonv3/Kconfig
new file mode 100644
index 000000000000..ef5130843301
--- /dev/null
+++ b/drivers/media/platform/google/chameleonv3/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config VIDEO_CHAMELEONV3
+	tristate "Google Chameleon v3 video system driver"
+	depends on V4L_PLATFORM_DRIVERS
+	depends on VIDEO_DEV
+	select VIDEOBUF2_DMA_CONTIG
+	help
+	  Enable support for the Google Chameleon v3 video system driver.
diff --git a/drivers/media/platform/google/chameleonv3/Makefile b/drivers/media/platform/google/chameleonv3/Makefile
new file mode 100644
index 000000000000..d65e3c392127
--- /dev/null
+++ b/drivers/media/platform/google/chameleonv3/Makefile
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+chv3-video-objs := \
+	chv3-core.o \
+	chv3-fb.o \
+	dprx-aux.o \
+	dprx-dp.o \
+	dprx-dpcd.o \
+	dprx-dprx.o \
+	dprx-edid.o \
+	dprx-i2c.o \
+	dprx-mt.o \
+	dprx-sbmsg.o
+
+obj-$(CONFIG_VIDEO_CHAMELEONV3) += chv3-video.o
diff --git a/drivers/media/platform/google/chameleonv3/chv3-core.c b/drivers/media/platform/google/chameleonv3/chv3-core.c
new file mode 100644
index 000000000000..b571c0afb8bd
--- /dev/null
+++ b/drivers/media/platform/google/chameleonv3/chv3-core.c
@@ -0,0 +1,292 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for Chameleon v3 framebuffer
+ *
+ * Copyright 2022 Google LLC.
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+
+#include <linux/videodev2.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "chv3-core.h"
+
+#define MODULE_NAME "chv3-video"
+
+static const struct chv3_fb_cfg fb0_cfg = {
+	.reg_core = "fb0",
+	.reg_irq = "fb0_irq",
+	.irq = "fb0",
+	.index = 0,
+};
+
+static const struct chv3_fb_cfg fb_mst_cfg[4] = {
+{
+	.reg_core = "fb_mst1",
+	.reg_irq = "fb_mst1_irq",
+	.irq = "fb_mst1",
+	.index = 1,
+},
+{
+	.reg_core = "fb_mst2",
+	.reg_irq = "fb_mst2_irq",
+	.irq = "fb_mst2",
+	.index = 2,
+},
+{
+	.reg_core = "fb_mst3",
+	.reg_irq = "fb_mst3_irq",
+	.irq = "fb_mst3",
+	.index = 3,
+},
+{
+	.reg_core = "fb_mst4",
+	.reg_irq = "fb_mst4_irq",
+	.irq = "fb_mst4",
+	.index = 4,
+},
+};
+
+static const struct chv3_fb_cfg fb_sst_cfg = {
+	.reg_core = "fb_sst",
+	.reg_irq = "fb_sst_irq",
+	.irq = "fb_sst",
+	.index = 5,
+};
+
+static const struct dprx_dp_cfg dp_mst_cfg = {
+	.reg_core = "dp_mst",
+	.reg_irq = "dp_mst_irq",
+	.irq = "dp_mst",
+	.has_mst = 1,
+	.sink_count = 4,
+};
+
+static const struct dprx_dp_cfg dp_sst_cfg = {
+	.reg_core = "dp_sst",
+	.reg_irq = "dp_sst_irq",
+	.irq = "dp_sst",
+	.has_mst = 0,
+	.sink_count = 1,
+};
+
+int chv3_g_edid(struct chv3_video *video, int index, struct v4l2_edid *edid)
+{
+	u32 end_block = edid->start_block + edid->blocks;
+	struct sink *sink;
+
+	if (index == 0 || index > 5)
+		return -ENOTTY;
+	if (edid->pad)
+		return -EINVAL;
+
+	if (1 <= index && index <= 4)
+		sink = &video->dp_mst.sinks[index-1];
+	else
+		sink = &video->dp_sst.sinks[0];
+
+	if (edid->start_block == 0 && edid->blocks == 0) {
+		edid->blocks = sink->blocks;
+		return 0;
+	}
+
+	if (edid->start_block > sink->blocks)
+		return -EINVAL;
+	if (end_block > sink->blocks) {
+		end_block = sink->blocks;
+		edid->blocks = end_block - edid->start_block;
+	}
+
+	memcpy(edid->edid, sink->edid + edid->start_block * 128, edid->blocks * 128);
+
+	return 0;
+}
+
+int chv3_s_edid(struct chv3_video *video, int index, struct v4l2_edid *edid)
+{
+	struct sink *sink;
+
+	if (index == 0 || index > 5)
+		return -ENOTTY;
+	if (edid->pad)
+		return -EINVAL;
+
+	if (1 <= index && index <= 4)
+		sink = &video->dp_mst.sinks[index-1];
+	else
+		sink = &video->dp_sst.sinks[0];
+
+	if (edid->start_block != 0)
+		return -EINVAL;
+	if (edid->blocks > DPRX_MAX_EDID_BLOCKS) {
+		edid->blocks = DPRX_MAX_EDID_BLOCKS;
+		return -E2BIG;
+	}
+
+	sink->blocks = edid->blocks;
+	memcpy(sink->edid, edid->edid, edid->blocks * 128);
+
+	return 0;
+}
+
+
+static ssize_t dp_hpd_show(struct device *dev, struct device_attribute *attr,
+			   char *buf);
+static ssize_t dp_hpd_store(struct device *dev, struct device_attribute *attr,
+			    const char *buf, size_t count);
+
+static struct device_attribute dev_attr_dp0_hpd = {
+	.attr = { .name = "hpd", .mode = 0644 },
+	.show = dp_hpd_show,
+	.store = dp_hpd_store,
+};
+
+static struct device_attribute dev_attr_dp1_hpd = {
+	.attr = { .name = "hpd", .mode = 0644 },
+	.show = dp_hpd_show,
+	.store = dp_hpd_store,
+};
+
+static struct attribute *dp0_attrs[] = {
+	&dev_attr_dp0_hpd.attr,
+	NULL,
+};
+
+static struct attribute *dp1_attrs[] = {
+	&dev_attr_dp1_hpd.attr,
+	NULL,
+};
+
+static struct attribute_group dp0_attr_group = {
+	.name = "dp0",
+	.attrs = dp0_attrs,
+};
+
+static struct attribute_group dp1_attr_group = {
+	.name = "dp1",
+	.attrs = dp1_attrs,
+};
+
+static ssize_t dp_hpd_show(struct device *dev, struct device_attribute *attr,
+			   char *buf)
+{
+	struct chv3_video *video = dev_get_drvdata(dev);
+	struct dprx_dp *dp;
+
+	if (attr == &dev_attr_dp0_hpd)
+		dp = &video->dp_mst;
+	else
+		dp = &video->dp_sst;
+
+	return sprintf(buf, "%d\n", dprx_dprx_get_hpd(dp));
+}
+
+static ssize_t dp_hpd_store(struct device *dev, struct device_attribute *attr,
+			    const char *buf, size_t count)
+{
+	struct chv3_video *video = dev_get_drvdata(dev);
+	struct dprx_dp *dp;
+	unsigned long val;
+	int res;
+
+	if (attr == &dev_attr_dp0_hpd)
+		dp = &video->dp_mst;
+	else
+		dp = &video->dp_sst;
+
+	res = kstrtoul(buf, 10, &val);
+	if (res)
+		return res;
+
+	dprx_dprx_set_hpd(dp, val);
+	return count;
+}
+
+static int chv3_video_probe(struct platform_device *pdev)
+{
+	struct chv3_video *video;
+	int res;
+	int i;
+
+	video = devm_kzalloc(&pdev->dev, sizeof(*video), GFP_KERNEL);
+	if (!video)
+		return -ENOMEM;
+	video->dev = &pdev->dev;
+	platform_set_drvdata(pdev, video);
+
+	/* register v4l2_device */
+	res = v4l2_device_register(video->dev, &video->v4l2_dev);
+	if (res)
+		return res;
+
+	/* initialize fb devices */
+	res = chv3_fb_register(&video->fb0, video, &fb0_cfg);
+	if (res)
+		return res;
+
+	for (i = 0; i < 4; i++) {
+		res = chv3_fb_register(&video->fb_mst[i], video, &fb_mst_cfg[i]);
+		if (res)
+			return res;
+	}
+
+	res = chv3_fb_register(&video->fb_sst, video, &fb_sst_cfg);
+	if (res)
+		return res;
+
+	/* initialize dp devices */
+	res = dprx_dp_init(&video->dp_mst, video->dev, &dp_mst_cfg);
+	if (res)
+		return res;
+
+	res = dprx_dp_init(&video->dp_sst, video->dev, &dp_sst_cfg);
+	if (res)
+		return res;
+
+	/* create sysfs files */
+	res = sysfs_create_group(&video->dev->kobj, &dp0_attr_group);
+	if (res)
+		return res;
+
+	res = sysfs_create_group(&video->dev->kobj, &dp1_attr_group);
+	if (res)
+		return res;
+
+	return 0;
+}
+
+static int chv3_video_remove(struct platform_device *pdev)
+{
+	struct chv3_video *video = platform_get_drvdata(pdev);
+
+	v4l2_device_unregister(&video->v4l2_dev);
+
+	return 0;
+}
+
+static const struct of_device_id chv3_video_match_table[] = {
+	{ .compatible = "google,chv3-video" },
+	{ },
+};
+
+static struct platform_driver chv3_video_platform_driver = {
+	.probe = chv3_video_probe,
+	.remove = chv3_video_remove,
+	.driver = {
+		.name = MODULE_NAME,
+		.of_match_table = chv3_video_match_table,
+	},
+};
+
+module_platform_driver(chv3_video_platform_driver);
+
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/media/platform/google/chameleonv3/chv3-core.h b/drivers/media/platform/google/chameleonv3/chv3-core.h
new file mode 100644
index 000000000000..9a435cba25bd
--- /dev/null
+++ b/drivers/media/platform/google/chameleonv3/chv3-core.h
@@ -0,0 +1,17 @@
+#include "chv3-fb.h"
+#include "dprx.h"
+
+struct chv3_video {
+	struct device *dev;
+	struct v4l2_device v4l2_dev;
+
+	struct chv3_fb fb0;
+	struct chv3_fb fb_mst[4];
+	struct chv3_fb fb_sst;
+
+	struct dprx_dp dp_mst;
+	struct dprx_dp dp_sst;
+};
+
+int chv3_g_edid(struct chv3_video *video, int index, struct v4l2_edid *edid);
+int chv3_s_edid(struct chv3_video *video, int index, struct v4l2_edid *edid);
diff --git a/drivers/media/platform/google/chameleonv3/chv3-fb.c b/drivers/media/platform/google/chameleonv3/chv3-fb.c
new file mode 100644
index 000000000000..a9b97d637ed5
--- /dev/null
+++ b/drivers/media/platform/google/chameleonv3/chv3-fb.c
@@ -0,0 +1,539 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for Chameleon v3 framebuffer
+ *
+ * Copyright 2022 Google LLC.
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+
+#include <linux/videodev2.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "chv3-core.h"
+
+#define MODULE_NAME	"chv3-fb"
+
+#define FB_EN		0x00
+#define FB_HEIGHT	0x04
+#define FB_WIDTH	0x08
+#define FB_BUFFERA	0x0c
+#define FB_BUFFERB	0x10
+#define FB_BUFFERSIZE	0x14
+#define FB_RESET	0x18
+#define FB_ERRORSTATUS	0x1c
+#define FB_IOCOLOR	0x20
+#define FB_IODATARATE	0x24
+#define FB_IOPIXELMODE	0x28
+#define FB_SYNCPOLARITY	0x2c
+#define FB_DMAFORMAT	0x30
+#define FB_VERSION	0x34
+#define FB_VERSION_CURRENT	0xc0fb0001
+#define FB_IRQ_MASK	0x8
+#define FB_IRQ_CLR	0xc
+#define FB_IRQ_ALL	0xf
+#define FB_IRQ_BUFF0		(1 << 0)
+#define FB_IRQ_BUFF1		(1 << 1)
+#define FB_IRQ_RESOLUTION	(1 << 2)
+#define FB_IRQ_ERROR		(1 << 3)
+
+struct chv3_fb_buffer {
+	struct vb2_v4l2_buffer vb;
+	struct list_head link;
+};
+
+struct chv3_dma_format {
+	u32 id;
+	u32 pixfmt;
+	int bpp;
+};
+
+struct chv3_dma_format chv3_dma_formats[] = {
+	{ 0, V4L2_PIX_FMT_RGB24 , 3 },
+	{ 1, V4L2_PIX_FMT_RGB30U, 6 },
+	{ 2, V4L2_PIX_FMT_RGB30L, 6 },
+	{ 3, V4L2_PIX_FMT_RGB36U, 6 },
+	{ 4, V4L2_PIX_FMT_RGB36L, 6 },
+	{ 5, V4L2_PIX_FMT_RGB48 , 6 },
+	{ 7, V4L2_PIX_FMT_BGRX32, 4 },
+};
+
+static void fb_set_dma_format(struct chv3_fb *fb, struct chv3_dma_format *dmaf)
+{
+	writel(dmaf->id, fb->iobase + FB_DMAFORMAT);
+	/* we need to wait one frame for the width/height to update */
+	mdelay(50);
+	fb->fmt.width  = readl(fb->iobase + FB_WIDTH);
+	fb->fmt.height = readl(fb->iobase + FB_HEIGHT);
+
+	fb->fmt.pixelformat = dmaf->pixfmt;
+	fb->fmt.field = V4L2_FIELD_NONE;
+	fb->fmt.bytesperline = fb->fmt.width * dmaf->bpp;
+	fb->fmt.sizeimage = fb->fmt.bytesperline * fb->fmt.height;
+	fb->fmt.colorspace = V4L2_COLORSPACE_SRGB;
+
+	writel(fb->fmt.sizeimage, fb->iobase + FB_BUFFERSIZE);
+}
+
+/* v4l2 ioctls */
+
+static int vidioc_querycap(struct file *file, void *data,
+			   struct v4l2_capability *cap)
+{
+	strscpy(cap->driver, MODULE_NAME, sizeof(cap->driver));
+	strscpy(cap->card, "Chameleonv3 framebuffer", sizeof(cap->card));
+	snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
+		 MODULE_NAME);
+
+	return 0;
+}
+
+/*
+ * We can't control the resolution, we can only read what it currently is from
+ * the framebuffer. In order not to confuse the application, the resolution is
+ * saved in fb->fmt, and is only updated when the application calls open() and
+ * there are no other applications that have the file opened.
+ */
+
+static int vidioc_g_fmt_vid_cap(struct file *file, void *data,
+				struct v4l2_format *fmt)
+{
+	struct chv3_fb *fb = video_drvdata(file);
+
+	fmt->fmt.pix = fb->fmt;
+	return 0;
+}
+
+static int vidioc_s_fmt_vid_cap(struct file *file, void *data,
+				struct v4l2_format *fmt)
+{
+	struct chv3_fb *fb = video_drvdata(file);
+
+	if (fmt->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+		struct chv3_dma_format *dmaf;
+
+		for (dmaf = chv3_dma_formats; dmaf < &chv3_dma_formats[ARRAY_SIZE(chv3_dma_formats)]; dmaf++) {
+			if (dmaf->pixfmt == fmt->fmt.pix.pixelformat) {
+				fb_set_dma_format(fb, dmaf);
+				break;
+			}
+		}
+	}
+
+	fmt->fmt.pix = fb->fmt;
+	return 0;
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void *data,
+				   struct v4l2_fmtdesc *fmt)
+{
+	if (fmt->index >= ARRAY_SIZE(chv3_dma_formats))
+		return -EINVAL;
+	fmt->flags = 0;
+	fmt->pixelformat = chv3_dma_formats[fmt->index].pixfmt;
+	return 0;
+}
+
+static int vidioc_enum_framesizes(struct file *file, void *data,
+				  struct v4l2_frmsizeenum *frm)
+{
+	struct chv3_fb *fb = video_drvdata(file);
+	struct chv3_dma_format *dmaf;
+	bool fmt_ok = false;
+
+	if (frm->index != 0)
+		return -EINVAL;
+
+	for (dmaf = chv3_dma_formats; dmaf < &chv3_dma_formats[ARRAY_SIZE(chv3_dma_formats)]; dmaf++) {
+		if (dmaf->pixfmt == frm->pixel_format) {
+			fmt_ok = true;
+			break;
+		}
+	}
+	if (!fmt_ok)
+		return -EINVAL;
+
+	frm->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+	frm->discrete.width  = fb->fmt.width;
+	frm->discrete.height = fb->fmt.height;
+	return 0;
+}
+
+static int vidioc_g_input(struct file *file, void *data, unsigned int *index)
+{
+	*index = 0;
+	return 0;
+}
+
+static int vidioc_s_input(struct file *file, void *data, unsigned int index)
+{
+	if (index != 0)
+		return -EINVAL;
+	return 0;
+}
+
+static int vidioc_enum_input(struct file *file, void *data,
+			     struct v4l2_input *input)
+{
+	if (input->index != 0)
+		return -EINVAL;
+	strcpy(input->name, "input0");
+	input->type = V4L2_INPUT_TYPE_CAMERA;
+	return 0;
+}
+
+static int vidioc_g_edid(struct file *file, void *data,
+			 struct v4l2_edid *edid)
+{
+	struct chv3_fb *fb = video_drvdata(file);
+
+	return chv3_g_edid(fb->parent, fb->index, edid);
+}
+
+static int vidioc_s_edid(struct file *file, void *data,
+			 struct v4l2_edid *edid)
+{
+	struct chv3_fb *fb = video_drvdata(file);
+
+	return chv3_s_edid(fb->parent, fb->index, edid);
+}
+
+static const struct v4l2_ioctl_ops fb_v4l2_ioctl_ops = {
+	.vidioc_querycap = vidioc_querycap,
+
+	.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap = vidioc_g_fmt_vid_cap,
+
+	.vidioc_enum_framesizes = vidioc_enum_framesizes,
+
+	.vidioc_enum_input = vidioc_enum_input,
+	.vidioc_g_input = vidioc_g_input,
+	.vidioc_s_input = vidioc_s_input,
+	.vidioc_g_edid = vidioc_g_edid,
+	.vidioc_s_edid = vidioc_s_edid,
+
+	.vidioc_reqbufs = vb2_ioctl_reqbufs,
+	.vidioc_create_bufs = vb2_ioctl_create_bufs,
+	.vidioc_querybuf = vb2_ioctl_querybuf,
+	.vidioc_qbuf = vb2_ioctl_qbuf,
+	.vidioc_dqbuf = vb2_ioctl_dqbuf,
+	.vidioc_streamon = vb2_ioctl_streamon,
+	.vidioc_streamoff = vb2_ioctl_streamoff,
+};
+
+/* videobuf2 operations */
+
+static int fb_queue_setup(struct vb2_queue *q,
+		       unsigned int *nbuffers, unsigned int *nplanes,
+		       unsigned int sizes[], struct device *alloc_devs[])
+{
+	struct chv3_fb *fb = vb2_get_drv_priv(q);
+
+	if (!fb->fmt.sizeimage)
+		return -EIO;
+
+	if (*nplanes) {
+		if (sizes[0] < fb->fmt.sizeimage)
+			return -EINVAL;
+		return 0;
+	}
+	*nplanes = 1;
+	sizes[0] = fb->fmt.sizeimage;
+	return 0;
+}
+
+/*
+ * There are two address registers: BUFFERA and BUFFERB. The framebuffer
+ * alternates writing between them (i.e. even frames go to BUFFERA, odd
+ * ones to BUFFERB).
+ *
+ *  (buffer queue) >     QUEUED ---> QUEUED ---> QUEUED ---> ...
+ *                       BUFFERA     BUFFERB
+ *  (hw writing to this) ^
+ *                (and then to this) ^
+ *
+ * The buffer swapping happens at irq time. When an irq comes, the next
+ * frame is already assigned an address in the buffer queue. This gives
+ * the irq handler a whole frame's worth of time to update the buffer
+ * address register.
+ */
+
+static dma_addr_t fb_buffer_dma_addr(struct chv3_fb_buffer *buf)
+{
+	return vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
+}
+
+static void fb_start_frame(struct chv3_fb *fb, struct chv3_fb_buffer *buf)
+{
+	fb->writing_to_a = 1;
+	writel(fb_buffer_dma_addr(buf), fb->iobase + FB_BUFFERA);
+	writel(1, fb->iobase + FB_EN);
+}
+
+static void fb_next_frame(struct chv3_fb *fb, struct chv3_fb_buffer *buf)
+{
+	u32 reg = fb->writing_to_a ? FB_BUFFERB : FB_BUFFERA;
+
+	writel(fb_buffer_dma_addr(buf), fb->iobase + reg);
+}
+
+static int fb_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+	struct chv3_fb *fb = vb2_get_drv_priv(q);
+	struct chv3_fb_buffer *buf;
+	unsigned long flags;
+
+	fb->streaming = 1;
+	fb->sequence = 0;
+
+	spin_lock_irqsave(&fb->bufs_lock, flags);
+	buf = list_first_entry_or_null(&fb->bufs, struct chv3_fb_buffer, link);
+	if (buf) {
+		fb_start_frame(fb, buf);
+		if (!list_is_last(&buf->link, &fb->bufs))
+			fb_next_frame(fb, list_next_entry(buf, link));
+	}
+	spin_unlock_irqrestore(&fb->bufs_lock, flags);
+
+	return 0;
+}
+
+static void fb_stop_streaming(struct vb2_queue *q)
+{
+	struct chv3_fb *fb = vb2_get_drv_priv(q);
+	struct chv3_fb_buffer *buf;
+	unsigned long flags;
+
+	fb->streaming = 0;
+	writel(0, fb->iobase + FB_EN);
+
+	spin_lock_irqsave(&fb->bufs_lock, flags);
+	list_for_each_entry(buf, &fb->bufs, link)
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+	INIT_LIST_HEAD(&fb->bufs);
+	spin_unlock_irqrestore(&fb->bufs_lock, flags);
+}
+
+static struct chv3_fb_buffer *to_chv3_fb_buffer(struct vb2_v4l2_buffer *b)
+{
+	return container_of(b, struct chv3_fb_buffer, vb);
+}
+
+static void fb_buf_queue(struct vb2_buffer *vb)
+{
+	struct chv3_fb *fb = vb2_get_drv_priv(vb->vb2_queue);
+	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
+	struct chv3_fb_buffer *buf = to_chv3_fb_buffer(v4l2_buf);
+	bool first, second;
+	unsigned long flags;
+
+	spin_lock_irqsave(&fb->bufs_lock, flags);
+	first = list_empty(&fb->bufs);
+	second = list_is_singular(&fb->bufs);
+	list_add_tail(&buf->link, &fb->bufs);
+	if (fb->streaming) {
+		if (first)
+			fb_start_frame(fb, buf);
+		else if (second)
+			fb_next_frame(fb, buf);
+	}
+	spin_unlock_irqrestore(&fb->bufs_lock, flags);
+}
+
+static const struct vb2_ops fb_vb2_ops = {
+	.queue_setup = fb_queue_setup,
+	.wait_prepare = vb2_ops_wait_prepare,
+	.wait_finish = vb2_ops_wait_finish,
+	.start_streaming = fb_start_streaming,
+	.stop_streaming = fb_stop_streaming,
+	.buf_queue = fb_buf_queue,
+};
+
+/* file operations */
+
+static int fb_open(struct file *file)
+{
+	struct chv3_fb *fb = video_drvdata(file);
+	int res;
+
+	mutex_lock(&fb->fb_lock);
+	res = v4l2_fh_open(file);
+	if (!res) {
+		if (v4l2_fh_is_singular_file(file))
+			fb_set_dma_format(fb, &chv3_dma_formats[0]);
+	}
+	mutex_unlock(&fb->fb_lock);
+
+	return res;
+}
+
+static const struct v4l2_file_operations fb_v4l2_fops = {
+	.owner = THIS_MODULE,
+	.open = fb_open,
+	.release = vb2_fop_release,
+	.unlocked_ioctl = video_ioctl2,
+	.mmap = vb2_fop_mmap,
+	.poll = vb2_fop_poll,
+};
+
+/* irq handling */
+
+static void fb_frame_irq(struct chv3_fb *fb)
+{
+	struct chv3_fb_buffer *buf;
+
+	spin_lock(&fb->bufs_lock);
+
+	buf = list_first_entry_or_null(&fb->bufs, struct chv3_fb_buffer, link);
+	if (!buf)
+		goto empty;
+	list_del(&buf->link);
+
+	vb2_set_plane_payload(&buf->vb.vb2_buf, 0, fb->fmt.sizeimage);
+	buf->vb.vb2_buf.timestamp = ktime_get_ns();
+	buf->vb.sequence = fb->sequence++;
+	buf->vb.field = V4L2_FIELD_NONE;
+	vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
+
+	buf = list_first_entry_or_null(&fb->bufs, struct chv3_fb_buffer, link);
+	if (buf) {
+		fb->writing_to_a = !fb->writing_to_a;
+		if (!list_is_last(&buf->link, &fb->bufs))
+			fb_next_frame(fb, list_next_entry(buf, link));
+	} else {
+		writel(0, fb->iobase + FB_EN);
+	}
+empty:
+	spin_unlock(&fb->bufs_lock);
+}
+
+static irqreturn_t fb_isr(int irq, void *data)
+{
+	struct chv3_fb *fb = data;
+	unsigned int reg;
+
+	reg = readl(fb->iobase_irq + FB_IRQ_CLR);
+	if (!reg)
+		return IRQ_NONE;
+
+	if (reg & (FB_IRQ_BUFF0 | FB_IRQ_BUFF1))
+		fb_frame_irq(fb);
+	if (reg & FB_IRQ_ERROR) {
+		dev_warn(fb->dev, "framebuffer error: 0x%x\n",
+		       readl(fb->iobase + FB_ERRORSTATUS));
+	}
+
+	writel(reg, fb->iobase_irq + FB_IRQ_CLR);
+
+	return IRQ_HANDLED;
+}
+
+/* driver probe & remove */
+
+static int fb_check_version(struct chv3_fb *fb)
+{
+	u32 version;
+
+	version = readl(fb->iobase + FB_VERSION);
+	if (version != FB_VERSION_CURRENT) {
+		dev_warn(fb->dev,
+			 "wrong framebuffer version: expected %x, got %x\n",
+			 FB_VERSION_CURRENT, version);
+		return -1;
+	}
+	return 0;
+}
+
+int chv3_fb_register(struct chv3_fb *fb,
+		     struct chv3_video *video,
+		     const struct chv3_fb_cfg *cfg)
+{
+	struct platform_device *pdev = to_platform_device(video->dev);
+	int res;
+	int irq;
+
+	fb->dev = video->dev;
+	fb->parent = video;
+	fb->index = cfg->index;
+
+	/* map register space */
+	fb->iobase = devm_platform_ioremap_resource_byname(pdev, cfg->reg_core);
+	if (IS_ERR(fb->iobase))
+		return -ENOMEM;
+
+	fb->iobase_irq = devm_platform_ioremap_resource_byname(pdev, cfg->reg_irq);
+	if (IS_ERR(fb->iobase_irq))
+		return -ENOMEM;
+
+	/* check hw version */
+	if (fb_check_version(fb))
+		return -ENODEV;
+
+	/* setup interrupts */
+	irq = platform_get_irq_byname(pdev, cfg->irq);
+	if (irq < 0)
+		return -ENXIO;
+	res = devm_request_irq(fb->dev, irq, fb_isr, 0, cfg->irq, fb);
+	if (res)
+		return res;
+
+	/* setup dma */
+	dma_set_coherent_mask(fb->dev, DMA_BIT_MASK(32));
+
+	/* initialize vb2 queue */
+	fb->queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	fb->queue.io_modes = VB2_MMAP;
+	fb->queue.dev = fb->dev;
+	fb->queue.lock = &fb->fb_lock;
+	fb->queue.ops = &fb_vb2_ops;
+	fb->queue.mem_ops = &vb2_dma_contig_memops;
+	fb->queue.drv_priv = fb;
+	fb->queue.buf_struct_size = sizeof(struct chv3_fb_buffer);
+	fb->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	res = vb2_queue_init(&fb->queue);
+	if (res)
+		return res;
+
+	/* register video_device */
+	strcpy(fb->vdev.name, MODULE_NAME);
+	fb->vdev.fops = &fb_v4l2_fops;
+	fb->vdev.ioctl_ops = &fb_v4l2_ioctl_ops;
+	fb->vdev.lock = &fb->fb_lock;
+	fb->vdev.release = video_device_release_empty;
+	fb->vdev.device_caps =
+		V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
+	fb->vdev.v4l2_dev = &video->v4l2_dev;
+	fb->vdev.queue = &fb->queue;
+	video_set_drvdata(&fb->vdev, fb);
+	res = video_register_device(&fb->vdev, VFL_TYPE_VIDEO, -1);
+	if (res)
+		return res;
+
+	/* initialize rest of driver struct */
+	INIT_LIST_HEAD(&fb->bufs);
+	spin_lock_init(&fb->bufs_lock);
+	mutex_init(&fb->fb_lock);
+
+	/* initialize hw */
+	writel(1, fb->iobase + FB_RESET);
+	writel(1, fb->iobase + FB_IODATARATE);
+	writel(1, fb->iobase + FB_IOPIXELMODE);
+	writel(FB_IRQ_BUFF0 | FB_IRQ_BUFF1 | FB_IRQ_ERROR, fb->iobase_irq + FB_IRQ_MASK);
+
+	return 0;
+}
+
+void chv3_fb_unregister(struct chv3_fb *fb)
+{
+	video_unregister_device(&fb->vdev);
+}
diff --git a/drivers/media/platform/google/chameleonv3/chv3-fb.h b/drivers/media/platform/google/chameleonv3/chv3-fb.h
new file mode 100644
index 000000000000..2ece35a114df
--- /dev/null
+++ b/drivers/media/platform/google/chameleonv3/chv3-fb.h
@@ -0,0 +1,34 @@
+struct chv3_fb {
+	struct device *dev;
+	void __iomem *iobase;
+	void __iomem *iobase_irq;
+	struct chv3_video *parent;
+	int index;
+
+	struct vb2_queue queue;
+	struct video_device vdev;
+	struct v4l2_pix_format fmt;
+
+	u32 sequence;
+	bool streaming;
+	bool writing_to_a;
+
+	struct list_head bufs;
+	spinlock_t bufs_lock;
+
+	struct mutex fb_lock;
+};
+
+struct chv3_fb_cfg {
+	const char *reg_core;
+	const char *reg_irq;
+	const char *irq;
+	int index;
+};
+
+int chv3_fb_register(struct chv3_fb *fb,
+		     struct chv3_video *video,
+		     const struct chv3_fb_cfg *cfg);
+
+
+void chv3_fb_unregister(struct chv3_fb *fb);
diff --git a/drivers/media/platform/google/chameleonv3/dprx-aux.c b/drivers/media/platform/google/chameleonv3/dprx-aux.c
new file mode 100644
index 000000000000..56d82c963b4b
--- /dev/null
+++ b/drivers/media/platform/google/chameleonv3/dprx-aux.c
@@ -0,0 +1,77 @@
+#include <linux/string.h>
+
+#include "dprx.h"
+
+static void handle_i2c_read(struct dprx_dp *dp, struct aux_msg *req,
+			    struct aux_msg *res)
+{
+	int r;
+
+	r = dprx_i2c_read(&dp->sinks[0], req->addr, res->data, req->len);
+	if (!r) {
+		res->cmd = AUX_ACK;
+		res->len = req->len;
+	} else {
+		res->cmd = AUX_I2C_NACK;
+		res->len = 0;
+	}
+}
+
+static void handle_i2c_write(struct dprx_dp *dp, struct aux_msg *req,
+			     struct aux_msg *res)
+{
+	int r;
+
+	r = dprx_i2c_write(&dp->sinks[0], req->addr, req->data, req->len);
+	if (!r)
+		res->cmd = AUX_ACK;
+	else
+		res->cmd = AUX_I2C_NACK;
+	res->len = 0;
+}
+
+void dprx_aux_handle_request(struct dprx_dp *dp, struct aux_msg *req,
+			     struct aux_msg *res)
+{
+	if (req->cmd & 8) {
+		dprx_dpcd_access(dp, req, res);
+	} else {
+		if (req->cmd & 1)
+			handle_i2c_read(dp, req, res);
+		else
+			handle_i2c_write(dp, req, res);
+		if (!(req->cmd & 4))
+			dp->sinks[0].segment = 0;
+	}
+}
+
+int dprx_aux_read_request(struct dprx_dp *dp, struct aux_msg *req)
+{
+	u8 data[20];
+	int len;
+
+	len = dprx_dprx_read_aux(dp, data);
+	if (!len)
+		return 0;
+
+	req->cmd = data[0] >> 4;
+	req->addr = (data[0] & 0xf) << 16 | data[1] << 8 | data[2];
+	if (len < 4) {
+		req->len = 0;
+	} else {
+		req->len = data[3] + 1;
+		memcpy(req->data, &data[4], req->len);
+	}
+
+	return 1;
+}
+
+void dprx_aux_write_response(struct dprx_dp *dp, struct aux_msg *res)
+{
+	u8 data[20];
+
+	data[0] = res->cmd << 4;
+	memcpy(&data[1], res->data, res->len);
+
+	dprx_dprx_write_aux(dp, data, res->len + 1);
+}
diff --git a/drivers/media/platform/google/chameleonv3/dprx-dp.c b/drivers/media/platform/google/chameleonv3/dprx-dp.c
new file mode 100644
index 000000000000..ede98cb610f6
--- /dev/null
+++ b/drivers/media/platform/google/chameleonv3/dprx-dp.c
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2022 Google LLC.
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include "dprx.h"
+
+#define DPRX_IRQ_MASK	0x8
+#define DPRX_IRQ_CLR	0xc
+#define DPRX_IRQ_AUX 	0x1
+
+static irqreturn_t dprx_dp_isr(int irq, void *data)
+{
+	struct dprx_dp *dp = data;
+	unsigned int reg;
+	struct aux_msg request;
+	struct aux_msg response;
+
+	reg = readl(dp->iobase_irq + DPRX_IRQ_CLR);
+	if (!reg)
+		return IRQ_NONE;
+	if (dprx_aux_read_request(dp, &request)) {
+		dprx_aux_handle_request(dp, &request, &response);
+		dprx_aux_write_response(dp, &response);
+	}
+	writel(reg, dp->iobase_irq + DPRX_IRQ_CLR);
+	return IRQ_HANDLED;
+}
+
+static void dprx_sink_init(struct dprx_dp *dp)
+{
+	int i;
+
+	for (i = 0; i < 4; i++) {
+		memcpy(dp->sinks[i].edid, default_edid, 128 * default_edid_blocks);
+		dp->sinks[i].blocks = default_edid_blocks;
+	}
+}
+
+int dprx_dp_init(struct dprx_dp *dp, struct device *dev,
+		 const struct dprx_dp_cfg *cfg)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	int irq;
+	int res;
+
+	dp->dev = &pdev->dev;
+
+	dp->iobase = devm_platform_ioremap_resource_byname(pdev, cfg->reg_core);
+	if (IS_ERR(dp->iobase))
+		return PTR_ERR(dp->iobase);
+
+	dp->iobase_irq = devm_platform_ioremap_resource_byname(pdev, cfg->reg_irq);
+	if (IS_ERR(dp->iobase_irq))
+		return PTR_ERR(dp->iobase_irq);
+
+	irq = platform_get_irq_byname(pdev, cfg->irq);
+	if (irq < 0)
+		return irq;
+
+	res = devm_request_irq(dp->dev, irq, dprx_dp_isr, 0, cfg->irq, dp);
+	if (res)
+		return res;
+
+	writel(DPRX_IRQ_AUX, dp->iobase_irq + DPRX_IRQ_MASK);
+
+	dp->has_mst = cfg->has_mst;
+	dp->sink_count = cfg->sink_count;
+
+	dprx_dprx_init(dp);
+	dprx_dpcd_init(dp);
+	dprx_sink_init(dp);
+
+	dprx_dprx_set_hpd(dp, 1);
+
+	return 0;
+}
diff --git a/drivers/media/platform/google/chameleonv3/dprx-dpcd.c b/drivers/media/platform/google/chameleonv3/dprx-dpcd.c
new file mode 100644
index 000000000000..10110cc69dc5
--- /dev/null
+++ b/drivers/media/platform/google/chameleonv3/dprx-dpcd.c
@@ -0,0 +1,424 @@
+#include <linux/string.h>
+#include "dprx.h"
+
+static void dpcd_clear_vc_payload_table(struct dprx_dp *dp)
+{
+	memset(dp->dpcd.vc_table, 0, 64);
+}
+
+static void dpcd_allocate_vc_payload(struct dprx_dp *dp, int start, int count, u8 id)
+{
+	if (count > 64 - start)
+		count = 64 - start;
+	memset(dp->dpcd.vc_table + start, id, count);
+}
+
+static void dpcd_deallocate_vc_payload(struct dprx_dp *dp, int start, u8 id)
+{
+	int i;
+	int to = start;
+
+	for (i = start; i < 64; i++) {
+		if (dp->dpcd.vc_table[i] == id)
+			dp->dpcd.vc_table[i] = 0;
+		else
+			dp->dpcd.vc_table[to++] = dp->dpcd.vc_table[i];
+	}
+}
+
+static void dpcd_handle_payload_allocate(struct dprx_dp *dp)
+{
+	u8 id = dp->dpcd.vc_alloc[0x0];
+	u8 start = dp->dpcd.vc_alloc[0x1];
+	u8 count = dp->dpcd.vc_alloc[0x2];
+
+	if (id == 0 && start == 0 && count == 0x3f) {
+		dpcd_clear_vc_payload_table(dp);
+		dprx_dprx_clear_vc_payload_table(dp);
+	} else {
+		if (count == 0)
+			dpcd_deallocate_vc_payload(dp, start, id);
+		else
+			dpcd_allocate_vc_payload(dp, start, count, id);
+		dprx_dprx_set_vc_payload_table(dp, dp->dpcd.vc_table, dp->vc_id);
+	}
+	dp->dpcd.vc_table_status |= 1 << 0;
+}
+
+
+
+
+
+/* 100h */
+static void dpcd_write_link_bw_set(struct dprx_dp *dp, u8 val)
+{
+	dp->dpcd.link_conf[0x0] = val;
+	dprx_dprx_set_link_rate(dp, val);
+}
+
+/* 101h */
+static void dpcd_write_lane_count_set(struct dprx_dp *dp, u8 val)
+{
+	dp->dpcd.link_conf[0x1] = val;
+	dprx_dprx_set_lane_count(dp, val & 0x1f);
+}
+
+/* 102h */
+static void dpcd_write_training_pattern_set(struct dprx_dp *dp, u8 val)
+{
+	dp->dpcd.link_conf[0x2] = val;
+	dprx_dprx_set_training_pattern(dp, val & 0xf);
+	dprx_dprx_set_scrambler(dp, !((val >> 5) & 1));
+}
+
+/* 111h */
+static void dpcd_write_mstm_ctrl(struct dprx_dp *dp, u8 *src, u32 offset, u32 count)
+{
+	u8 val = *src;
+	dp->dpcd.mstm_ctrl = val;
+	dprx_dprx_set_mst(dp, val & 1);
+}
+
+/* 1c0h */
+static void dpcd_write_payload_allocate_set(struct dprx_dp *dp, u8 val)
+{
+	dp->dpcd.vc_alloc[0x0] = val & 0x7f;
+}
+
+/* 1c1h */
+static void dpcd_write_payload_allocate_start_time_slot(struct dprx_dp *dp, u8 val)
+{
+	dp->dpcd.vc_alloc[0x1] = val & 0x3f;
+}
+
+/* 1c2h */
+static void dpcd_write_payload_allocate_time_slot_count(struct dprx_dp *dp, u8 val)
+{
+	dp->dpcd.vc_alloc[0x2] = val & 0x3f;
+	dpcd_handle_payload_allocate(dp);
+}
+
+/* 201h */
+static void dpcd_write_device_service_irq_vector(struct dprx_dp *dp, u8 val)
+{
+	dp->dpcd.irq_vector &= ~val;
+
+	if (dprx_sbmsg_pending(dp)) {
+		dp->dpcd.irq_vector |= 1 << 4;
+		dprx_sbmsg_write(dp, dp->dpcd.down_rep, 48);
+		dprx_dprx_pulse_hpd(dp);
+	}
+}
+
+/* 202h */
+static u8 dpcd_read_lane01_status(struct dprx_dp *dp)
+{
+	int cr_lock;
+	int sym_lock;
+	u8 res = 0;
+
+	cr_lock = dprx_dprx_get_cr_lock(dp);
+	sym_lock = dprx_dprx_get_sym_lock(dp);
+	/* lane 0 */
+	if (cr_lock & (1 << 0))
+		res |= 0x1;
+	if (sym_lock & (1 << 0))
+		res |= 0x6;
+	/* lane 1 */
+	if (cr_lock & (1 << 1))
+		res |= 0x10;
+	if (sym_lock & (1 << 1))
+		res |= 0x60;
+
+	return res;
+}
+
+/* 203h */
+static u8 dpcd_read_lane23_status(struct dprx_dp *dp)
+{
+	int cr_lock;
+	int sym_lock;
+	u8 res = 0;
+
+	cr_lock = dprx_dprx_get_cr_lock(dp);
+	sym_lock = dprx_dprx_get_sym_lock(dp);
+	/* lane 2 */
+	if (cr_lock & (1 << 2))
+		res |= 0x1;
+	if (sym_lock & (1 << 2))
+		res |= 0x6;
+	/* lane 3 */
+	if (cr_lock & (1 << 3))
+		res |= 0x10;
+	if (sym_lock & (1 << 3))
+		res |= 0x60;
+
+	return res;
+}
+
+/* 204h */
+static u8 dpcd_read_lane_align_status(struct dprx_dp *dp)
+{
+	return dprx_dprx_get_interlane_align(dp);
+}
+
+/* 205h */
+static u8 dpcd_read_sink_status(struct dprx_dp *dp)
+{
+	return dprx_dprx_get_sink_status(dp);
+}
+
+/* 2c0h */
+static void dpcd_read_payload_table_update_status(struct dprx_dp *dp, u8 *dest, u32 offset, u32 count)
+{
+	*dest = dp->dpcd.vc_table_status;
+	if (dprx_dprx_get_act(dp))
+		*dest |= 1 << 1;
+}
+
+/* 2c0h */
+static void dpcd_write_payload_table_update_status(struct dprx_dp *dp, u8 *src, u32 offset, u32 count)
+{
+	if (*src & 0x1) {
+		dp->dpcd.vc_table_status = 0;
+		dprx_dprx_clear_act(dp);
+	}
+}
+
+
+
+
+
+static void dpcd_read_caps(struct dprx_dp *dp, u8 *dest, u32 offset, u32 count)
+{
+	memcpy(dest, dp->dpcd.caps + offset, count);
+}
+
+static void dpcd_read_mstm_cap(struct dprx_dp *dp, u8 *dest, u32 offset, u32 count)
+{
+	*dest = dp->dpcd.mstm_cap;
+}
+
+static void dpcd_read_guid(struct dprx_dp *dp, u8 *dest, u32 offset, u32 count)
+{
+	memcpy(dest, dp->dpcd.guid + offset, count);
+}
+
+static void dpcd_write_guid(struct dprx_dp *dp, u8 *src, u32 offset, u32 count)
+{
+	memcpy(dp->dpcd.guid + offset, src, count);
+}
+
+static void dpcd_read_link_conf(struct dprx_dp *dp, u8 *dest, u32 offset, u32 count)
+{
+	memcpy(dest, dp->dpcd.link_conf + offset, count);
+}
+
+static void dpcd_write_link_conf(struct dprx_dp *dp, u8 *src, u32 offset, u32 count)
+{
+	if (offset <= 0 && 0 < offset + count)
+		dpcd_write_link_bw_set(dp, src[0 - offset]);
+	if (offset <= 1 && 1 < offset + count)
+		dpcd_write_lane_count_set(dp, src[1 - offset]);
+	if (offset <= 2 && 2 < offset + count)
+		dpcd_write_training_pattern_set(dp, src[2 - offset]);
+
+	while (dprx_dprx_get_rx_busy(dp)) {}
+}
+
+static void dpcd_read_mstm_ctrl(struct dprx_dp *dp, u8 *dest, u32 start, u32 count)
+{
+	*dest = dp->dpcd.mstm_ctrl;
+}
+
+static void dpcd_read_vc_alloc(struct dprx_dp *dp, u8 *dest, u32 offset, u32 count)
+{
+	memcpy(dest, dp->dpcd.vc_alloc + offset, count);
+}
+
+static void dpcd_write_vc_alloc(struct dprx_dp *dp, u8 *src, u32 offset, u32 count)
+{
+	if (offset <= 0 && 0 < offset + count)
+		dpcd_write_payload_allocate_set(dp, src[0 - offset]);
+	if (offset <= 1 && 1 < offset + count)
+		dpcd_write_payload_allocate_start_time_slot(dp, src[1 - offset]);
+	if (offset <= 2 && 2 < offset + count)
+		dpcd_write_payload_allocate_time_slot_count(dp, src[2 - offset]);
+}
+
+static void dpcd_read_sink_stat(struct dprx_dp *dp, u8 *dest, u32 offset, u32 count)
+{
+	if (offset <= 0 && 0 < offset + count)
+		dest[0 - offset] = dp->dpcd.sink_count;
+	if (offset <= 1 && 1 < offset + count)
+		dest[1 - offset] = dp->dpcd.irq_vector;
+}
+
+static void dpcd_write_sink_stat(struct dprx_dp *dp, u8 *src, u32 offset, u32 count)
+{
+	if (offset <= 1 && 1 < offset + count)
+		dpcd_write_device_service_irq_vector(dp, src[1 - offset]);
+}
+
+static void dpcd_read_link_stat(struct dprx_dp *dp, u8 *dest, u32 offset, u32 count)
+{
+	if (offset <= 0 && 0 < offset + count)
+		dest[0 - offset] = dpcd_read_lane01_status(dp);
+	if (offset <= 1 && 1 < offset + count)
+		dest[1 - offset] = dpcd_read_lane23_status(dp);
+	if (offset <= 2 && 2 < offset + count)
+		dest[2 - offset] = dpcd_read_lane_align_status(dp);
+	if (offset <= 3 && 3 < offset + count)
+		dest[3 - offset] = dpcd_read_sink_status(dp);
+	if (offset <= 4 && 4 < offset + count)
+		dest[4 - offset] = 0x55;
+	if (offset <= 5 && 5 < offset + count)
+		dest[5 - offset] = 0x55;
+}
+
+static void dpcd_read_vc_table(struct dprx_dp *dp, u8 *dest, u32 offset, u32 count)
+{
+	memcpy(dest, dp->dpcd.vc_table + offset + 1, count);
+}
+
+static void dpcd_read_sink_spec(struct dprx_dp *dp, u8 *dest, u32 offset, u32 count)
+{
+	memcpy(dest, dp->dpcd.sink_spec + offset, count);
+}
+
+static void dpcd_read_down_req(struct dprx_dp *dp, u8 *dest, u32 offset, u32 count)
+{
+	memcpy(dest, dp->dpcd.down_req + offset, count);
+}
+
+static void dpcd_write_down_req(struct dprx_dp *dp, u8 *src, u32 offset, u32 count)
+{
+	memcpy(dp->dpcd.down_req + offset, src, count);
+	/*
+	 * The sideband message may require multiple AUX transactions to be
+	 * fully written. Normally, the source writes the data in order,
+	 * in blocks of 16. Unfortunately, the spec doesn't say what to
+	 * do if the source behaves differently that that.
+	 *
+	 * Approach taken here: when we get a write, assume all the
+	 * bytes before the starting address are valid, try to parse
+	 * the message up to the last byte written in this transaction
+	 * (if it's incomplete, nothing happens).
+	 */
+	dprx_sbmsg_read(dp, dp->dpcd.down_req, offset + count);
+	if (!(dp->dpcd.irq_vector & (1 << 4)) && dprx_sbmsg_pending(dp)) {
+		dp->dpcd.irq_vector |= 1 << 4;
+		dprx_sbmsg_write(dp, dp->dpcd.down_rep, 48);
+		dprx_dprx_pulse_hpd(dp);
+	}
+}
+
+static void dpcd_read_down_rep(struct dprx_dp *dp, u8 *dest, u32 offset, u32 count)
+{
+	memcpy(dest, dp->dpcd.down_rep + offset, count);
+}
+
+struct dpcd_range {
+	u32 start;
+	u32 end;
+	void (*read) (struct dprx_dp *, u8 *, u32, u32);
+	void (*write)(struct dprx_dp *, u8 *, u32, u32);
+};
+
+struct dpcd_range dpcd_ranges[] = {
+	{ 0x00000, 0x00010, dpcd_read_caps,      NULL },
+	{ 0x00021, 0x00022, dpcd_read_mstm_cap,  NULL },
+	{ 0x00030, 0x00040, dpcd_read_guid,      dpcd_write_guid },
+	{ 0x00100, 0x00103, dpcd_read_link_conf, dpcd_write_link_conf },
+	{ 0x00111, 0x00112, dpcd_read_mstm_ctrl, dpcd_write_mstm_ctrl },
+	{ 0x001c0, 0x001c3, dpcd_read_vc_alloc,  dpcd_write_vc_alloc },
+	{ 0x00200, 0x00202, dpcd_read_sink_stat, dpcd_write_sink_stat },
+	{ 0x00202, 0x00208, dpcd_read_link_stat, NULL },
+	{ 0x002c0, 0x002c1, dpcd_read_payload_table_update_status, dpcd_write_payload_table_update_status },
+	{ 0x002c1, 0x00300, dpcd_read_vc_table,  NULL },
+	{ 0x00400, 0x0040c, dpcd_read_sink_spec, NULL },
+	{ 0x01000, 0x01030, dpcd_read_down_req,  dpcd_write_down_req },
+	{ 0x01400, 0x01430, dpcd_read_down_rep,  NULL },
+	{ 0x02002, 0x02004, dpcd_read_sink_stat, dpcd_write_sink_stat },
+	{ 0x0200c, 0x02010, dpcd_read_link_stat, NULL },
+};
+
+void dprx_dpcd_access(struct dprx_dp *dp, struct aux_msg *req,
+		      struct aux_msg *res)
+{
+	struct dpcd_range *range;
+	struct dpcd_range *range_end = dpcd_ranges + ARRAY_SIZE(dpcd_ranges);
+	bool read = req->cmd & 1;
+	u32 start;
+	u32 end;
+	u8 *buf;
+	u32 offset;
+	u32 count;
+
+	res->cmd = AUX_ACK;
+	if (read) {
+		res->len = req->len;
+		memset(res->data, 0, res->len);
+	} else {
+		res->len = 0;
+	}
+
+	for (range = dpcd_ranges; range < range_end; range++) {
+		if (range->end <= req->addr || req->addr + req->len <= range->start)
+			continue;
+		start = max(range->start, req->addr);
+		end   = min(range->end,   req->addr + req->len);
+		count = end - start;
+		offset = start - range->start;
+		if (read) {
+			buf = res->data + (start - req->addr);
+			range->read(dp, buf, offset, count);
+		} else if (range->write) {
+			buf = req->data + (start - req->addr);
+			range->write(dp, buf, offset, count);
+		}
+	}
+}
+
+void dprx_dpcd_init(struct dprx_dp *dp)
+{
+	struct dpcd_mem *dpcd = &dp->dpcd;
+
+	memset(dpcd, 0, sizeof(struct dpcd_mem));
+
+	dpcd->caps[0x0] = 0x14, // DPCD 1.4
+	dpcd->caps[0x1] = 0x1e, // Max link rate 8.1Gbps
+	dpcd->caps[0x2] = 0xc4, // Max lane count 4, TPS3, Enhanced frame cap
+	dpcd->caps[0x3] = 0x81, // Down-spread, TPS4
+	dpcd->caps[0x4] = 0x01, // 2 Reciever ports for SST (video & audio)
+	dpcd->caps[0x5] = 0x00, // no downstream ports
+	dpcd->caps[0x6] = 0x01, // 8b/10b support
+	dpcd->caps[0x7] = 0x80, // no downstream ports, OUI present
+	dpcd->caps[0x8] = 0x02, // has local EDID
+	dpcd->caps[0x9] = 0x00, // buffer size?
+	dpcd->caps[0xa] = 0x06,
+	dpcd->caps[0xb] = 0x00,
+	dpcd->caps[0xc] = 0x00, // no physical i2c bus
+	dpcd->caps[0xd] = 0x00, // reserved for eDP
+	dpcd->caps[0xe] = 0x00, // no extended receiver capability present
+	dpcd->caps[0xf] = 0x00, // no legacy adaptor caps
+
+	dpcd->mstm_cap = dp->has_mst;
+	dpcd->sink_count = dp->has_mst ? dp->sink_count : 1;
+
+	dpcd->sink_spec[0x0] = 0x12;
+	dpcd->sink_spec[0x1] = 0x34;
+	dpcd->sink_spec[0x2] = 0x56;
+	dpcd->sink_spec[0x3] = 'c';
+	dpcd->sink_spec[0x4] = 'h';
+	dpcd->sink_spec[0x5] = 'a';
+	dpcd->sink_spec[0x6] = 'm';
+	dpcd->sink_spec[0x7] = 'e';
+	dpcd->sink_spec[0x8] = 'l';
+	dpcd->sink_spec[0x9] = 0x30;
+	dpcd->sink_spec[0xa] = 0x00;
+	dpcd->sink_spec[0xb] = 0x00;
+
+	dpcd_write_link_bw_set(dp, 0x1e);
+	dpcd_write_lane_count_set(dp, 0x04);
+};
diff --git a/drivers/media/platform/google/chameleonv3/dprx-dprx.c b/drivers/media/platform/google/chameleonv3/dprx-dprx.c
new file mode 100644
index 000000000000..7c7b196539ed
--- /dev/null
+++ b/drivers/media/platform/google/chameleonv3/dprx-dprx.c
@@ -0,0 +1,262 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2022 Google LLC.
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+
+#include "dprx.h"
+
+#define DPRX_RX_CONTROL   0x000
+#define DPRX_RX_STATUS    0x001
+#define DPRX0_VBID        0x02f
+#define DPRX_MST_CONTROL1 0x0a0
+#define DPRX_MST_STATUS1  0x0a1
+#define DPRX_MST_VCPTAB0  0x0a2
+#define DPRX_AUX_CONTROL  0x100
+#define DPRX_AUX_STATUS   0x101
+#define DPRX_AUX_COMMAND  0x102
+#define DPRX_AUX_HPD      0x119
+
+static void dp_wr(struct dprx_dp *dp, int addr, u32 val)
+{
+	writel(val, dp->iobase + (addr * 4));
+}
+
+static u32 dp_rd(struct dprx_dp *dp, int addr)
+{
+	return readl(dp->iobase + (addr * 4));
+}
+
+/* HPD */
+
+void dprx_dprx_set_hpd(struct dprx_dp *dp, int val)
+{
+	u32 reg;
+
+	reg = dp_rd(dp, DPRX_AUX_HPD);
+	reg &= ~(1 << 11);
+	reg |= (val & 1) << 11;
+	dp_wr(dp, DPRX_AUX_HPD, reg);
+}
+
+int dprx_dprx_get_hpd(struct dprx_dp *dp)
+{
+       return (dp_rd(dp, DPRX_AUX_HPD) >> 11) & 1;
+}
+
+void dprx_dprx_pulse_hpd(struct dprx_dp *dp)
+{
+	u32 reg;
+
+	reg = dp_rd(dp, DPRX_AUX_HPD);
+	reg |= 1 << 12;
+	dp_wr(dp, DPRX_AUX_HPD, reg);
+}
+
+/* Receiver Control */
+
+void dprx_dprx_set_link_rate(struct dprx_dp *dp, int val)
+{
+	u32 reg;
+
+	reg = dp_rd(dp, DPRX_RX_CONTROL);
+	reg &= ~(0xff << 16);
+	reg |= (val & 0xff) << 16;
+	reg |= 1 << 13;
+	dp_wr(dp, DPRX_RX_CONTROL, reg);
+}
+
+void dprx_dprx_set_lane_count(struct dprx_dp *dp, int val)
+{
+	u32 reg;
+
+	reg = dp_rd(dp, DPRX_RX_CONTROL);
+	reg &= ~0x1f;
+	reg |= (val & 0x1f);
+	dp_wr(dp, DPRX_RX_CONTROL, reg);
+}
+
+void dprx_dprx_set_training_pattern(struct dprx_dp *dp, int val)
+{
+	u32 reg;
+
+	reg = dp_rd(dp, DPRX_RX_CONTROL);
+	reg &= ~(0x7 << 8);
+	reg |= (val & 0x7) << 8;
+	dp_wr(dp, DPRX_RX_CONTROL, reg);
+}
+
+void dprx_dprx_set_scrambler(struct dprx_dp *dp, int val)
+{
+	u32 reg;
+
+	reg = dp_rd(dp, DPRX_RX_CONTROL);
+	reg &= ~(1 << 7);
+	reg |= (~val & 1) << 7;
+	dp_wr(dp, DPRX_RX_CONTROL, reg);
+}
+
+/* Receiver Status */
+
+int dprx_dprx_get_cr_lock(struct dprx_dp *dp)
+{
+	return dp_rd(dp, DPRX_RX_STATUS) & 0xf;
+}
+
+int dprx_dprx_get_sym_lock(struct dprx_dp *dp)
+{
+	return (dp_rd(dp, DPRX_RX_STATUS) >> 4) & 0xf;
+}
+
+int dprx_dprx_get_interlane_align(struct dprx_dp *dp)
+{
+	return (dp_rd(dp, DPRX_RX_STATUS) >> 8) & 0x1;
+}
+
+int dprx_dprx_get_sink_status(struct dprx_dp *dp)
+{
+	return (dp_rd(dp, DPRX0_VBID) >> 7) & 0x1;
+}
+
+int dprx_dprx_get_rx_busy(struct dprx_dp *dp)
+{
+	return (dp_rd(dp, DPRX_RX_STATUS) >> 17) & 0x1;
+}
+
+/* MST */
+
+void dprx_dprx_set_mst(struct dprx_dp *dp, int val)
+{
+	u32 reg;
+
+	reg = dp_rd(dp, DPRX_MST_CONTROL1);
+	reg &= ~0x1;
+	reg |= (val & 0x1);
+	dp_wr(dp, DPRX_MST_CONTROL1, reg);
+}
+
+void dprx_dprx_clear_vc_payload_table(struct dprx_dp *dp)
+{
+	u32 reg;
+	int i;
+
+	for (i = 0; i < 8; i++)
+		dp_wr(dp, DPRX_MST_VCPTAB0 + i, 0);
+
+	reg = dp_rd(dp, DPRX_MST_CONTROL1);
+	reg &= ~(0xffff << 4);
+	reg |= 1 << 31;
+	dp_wr(dp, DPRX_MST_CONTROL1, reg);
+}
+
+void dprx_dprx_set_vc_payload_table(struct dprx_dp *dp, u8 *table, u8 *id)
+{
+	u8 map[64];
+	int i, j;
+	u32 reg;
+
+	memset(map, 0, 64);
+	for (i = 0; i < 4; i++) {
+		if (id[i] != 0 && id[i] < 64)
+			map[id[i]] = i + 1;
+	}
+
+	for (i = 0; i < 8; i++) {
+		reg = 0;
+		for (j = 0; j < 8; j++)
+			reg |= map[table[i*8+j]] << (j * 4);
+		dp_wr(dp, DPRX_MST_VCPTAB0 + i, reg);
+	}
+
+	reg = dp_rd(dp, DPRX_MST_CONTROL1);
+	reg &= ~(0xffff << 4);
+	for (i = 0; i < 4; i++)
+		if (id[i] != 0 && id[i] < 64)
+			reg |= (i + 1) << ((i + 1) * 4);
+	reg |= 1 << 30;
+	dp_wr(dp, DPRX_MST_CONTROL1, reg);
+}
+
+int dprx_dprx_get_act(struct dprx_dp *dp)
+{
+	return (dp_rd(dp, DPRX_MST_STATUS1) >> 30) & 1;
+}
+
+void dprx_dprx_clear_act(struct dprx_dp *dp)
+{
+	u32 reg;
+
+	reg = dp_rd(dp, DPRX_MST_CONTROL1);
+	reg &= ~(1 << 30);
+	dp_wr(dp, DPRX_MST_CONTROL1, reg);
+}
+
+/* AUX CH */
+
+int dprx_dprx_read_aux(struct dprx_dp *dp, u8 *data)
+{
+	int length;
+	u32 reg;
+	int i;
+
+	/* check MSG_READY */
+	reg = dp_rd(dp, DPRX_AUX_STATUS);
+	if (!(reg & (1 << 31)))
+		return 0;
+
+	/* read LENGTH */
+	length = dp_rd(dp, DPRX_AUX_CONTROL) & 0x1f;
+	if (length > 20)
+		length = 20;
+
+	/* read request */
+	for (i = 0; i < length; i++)
+		data[i] = dp_rd(dp, DPRX_AUX_COMMAND + i);
+
+	return length;
+}
+
+void dprx_dprx_write_aux(struct dprx_dp *dp, u8 *data, int length)
+{
+	u32 reg;
+	int i;
+
+	/* check READY_TO_TX */
+	reg = dp_rd(dp, DPRX_AUX_STATUS);
+	if (!(reg & (1 << 30)))
+		return;
+
+	/* write request */
+	if (length > 17)
+		length = 17;
+	for (i = 0; i < length; i++)
+		dp_wr(dp, DPRX_AUX_COMMAND + i, data[i]);
+
+	/* write LENGTH and TX_STROBE */
+	reg = dp_rd(dp, DPRX_AUX_CONTROL);
+	reg &= ~0x1f;
+	reg |= length | (1 << 7);
+	dp_wr(dp, DPRX_AUX_CONTROL, reg);
+}
+
+/* Misc */
+
+void dprx_dprx_init(struct dprx_dp *dp)
+{
+	u32 reg;
+
+	/* Enable AUX_IRQ_EN */
+	reg = dp_rd(dp, DPRX_AUX_CONTROL);
+	reg |= 1 << 8;
+	dp_wr(dp, DPRX_AUX_CONTROL, reg);
+
+	/* Set CHANNEL_CODING_SET to 8b/10b */
+	reg = dp_rd(dp, DPRX_RX_CONTROL);
+	reg |= 1 << 5;
+	dp_wr(dp, DPRX_RX_CONTROL, reg);
+}
diff --git a/drivers/media/platform/google/chameleonv3/dprx-edid.c b/drivers/media/platform/google/chameleonv3/dprx-edid.c
new file mode 100644
index 000000000000..19d3a6182eeb
--- /dev/null
+++ b/drivers/media/platform/google/chameleonv3/dprx-edid.c
@@ -0,0 +1,39 @@
+#include <linux/kernel.h>
+
+u8 default_edid[256] = {
+	0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,
+	0x34,0xA9,0x96,0xA2,0x01,0x01,0x01,0x01,
+	0x00,0x17,0x01,0x03,0x80,0x80,0x48,0x78,
+	0x0A,0xDA,0xFF,0xA3,0x58,0x4A,0xA2,0x29,
+	0x17,0x49,0x4B,0x21,0x08,0x00,0x31,0x40,
+	0x45,0x40,0x61,0x40,0x81,0x80,0x01,0x01,
+	0x01,0x01,0x01,0x01,0x01,0x01,0x08,0xE8,
+	0x00,0x30,0xF2,0x70,0x5A,0x80,0xB0,0x58,
+	0x8A,0x00,0xBA,0x88,0x21,0x00,0x00,0x1E,
+	0x02,0x3A,0x80,0x18,0x71,0x38,0x2D,0x40,
+	0x58,0x2C,0x45,0x00,0xBA,0x88,0x21,0x00,
+	0x00,0x1E,0x00,0x00,0x00,0xFC,0x00,0x50,
+	0x61,0x6E,0x61,0x73,0x6F,0x6E,0x69,0x63,
+	0x2D,0x54,0x56,0x0A,0x00,0x00,0x00,0xFD,
+	0x00,0x17,0x3D,0x0F,0x88,0x3C,0x00,0x0A,
+	0x20,0x20,0x20,0x20,0x20,0x20,0x01,0xA0,
+
+	0x02,0x03,0x4F,0xF0,0x57,0x1F,0x10,0x14,
+	0x05,0x20,0x21,0x22,0x13,0x04,0x12,0x03,
+	0x16,0x07,0x60,0x61,0x5D,0x5E,0x5F,0x65,
+	0x66,0x62,0x63,0x64,0x23,0x09,0x07,0x01,
+	0x7E,0x03,0x0C,0x00,0x40,0x00,0xB8,0x3C,
+	0x2F,0xC8,0x90,0x01,0x02,0x03,0x04,0x81,
+	0x41,0x01,0x9C,0x06,0x16,0x08,0x00,0x18,
+	0x00,0x96,0xA6,0x98,0x00,0xA8,0x00,0x67,
+	0xD8,0x5D,0xC4,0x01,0x78,0x80,0x03,0xE2,
+	0x00,0x4B,0xE4,0x0F,0x00,0x60,0x0C,0x56,
+	0x5E,0x00,0xA0,0xA0,0xA0,0x29,0x50,0x30,
+	0x20,0x35,0x00,0xBA,0x88,0x21,0x00,0x00,
+	0x1A,0x66,0x21,0x56,0xAA,0x51,0x00,0x1E,
+	0x30,0x46,0x8F,0x33,0x00,0xBA,0x88,0x21,
+	0x00,0x00,0x1E,0x00,0x00,0x00,0x00,0x00,
+	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5F
+};
+
+u8 default_edid_blocks = 2;
diff --git a/drivers/media/platform/google/chameleonv3/dprx-i2c.c b/drivers/media/platform/google/chameleonv3/dprx-i2c.c
new file mode 100644
index 000000000000..2f0faac7352b
--- /dev/null
+++ b/drivers/media/platform/google/chameleonv3/dprx-i2c.c
@@ -0,0 +1,41 @@
+#include <linux/string.h>
+#include "dprx.h"
+
+int dprx_i2c_read(struct sink *sink, u8 addr, u8 *buf, int len)
+{
+	int offset;
+
+	if (addr == 0x50) {
+		offset = sink->offset + sink->segment * 256;
+		if (len + offset > sink->blocks * 128)
+			return -1;
+		memcpy(buf, sink->edid + offset, len);
+		sink->offset += len;
+	} else if (addr == 0x30) {
+		if (len == 1)
+			buf[0] = sink->segment;
+		else if (len > 1)
+			return -1;
+	}
+
+	return 0;
+}
+
+int dprx_i2c_write(struct sink *sink, u8 addr, u8 *buf, int len)
+{
+	if (addr == 0x50) {
+		if (len == 1)
+			sink->offset = buf[0];
+		else if (len > 1)
+			return -1;
+	} else if (addr == 0x30) {
+		if (len == 1)
+			sink->segment = buf[0];
+		else if (len > 1)
+			return -1;
+	} else {
+		return -1;
+	}
+
+	return 0;
+}
diff --git a/drivers/media/platform/google/chameleonv3/dprx-mt.c b/drivers/media/platform/google/chameleonv3/dprx-mt.c
new file mode 100644
index 000000000000..7b39b2e41d22
--- /dev/null
+++ b/drivers/media/platform/google/chameleonv3/dprx-mt.c
@@ -0,0 +1,184 @@
+#include <linux/string.h>
+#include "dprx.h"
+
+#define MT_GET_MESSAGE_TRANSACTION_VERSION 0x00
+#define MT_LINK_ADDRESS		0x01
+#define MT_CONNECTION_STATUS_NOTIFY 0x02
+#define MT_ENUM_PATH_RESOURCES	0x10
+#define MT_ALLOCATE_PAYLOAD	0x11
+#define MT_QUERY_PAYLOAD	0x12
+#define MT_RESOURCE_STATUS_NOTIFY 0x13
+#define MT_CLEAR_PAYLOAD_ID_TABLE 0x14
+#define MT_REMOTE_DPCD_READ	0x20
+#define MT_REMOTE_DPCD_WRITE	0x21
+#define MT_REMOTE_I2C_READ	0x22
+#define MT_REMOTE_I2C_WRITE	0x23
+#define MT_POWER_UP_PHY		0x24
+#define MT_POWER_DOWN_PHY	0x25
+#define MT_SINK_EVENT_NOTIFY	0x30
+#define MT_QUERY_STREAM_ENCRYPTION_STATUS 0x38
+
+#define MT_NACK 0x80
+#define MT_BAD_PARAM 0x4
+
+static void execute_link_address(struct dprx_dp *dp,
+				 struct msg_transaction *req,
+				 struct msg_transaction *rep)
+{
+	int ports = dp->sink_count + 1;
+	u8 *buf;
+	int i;
+
+	rep->buf[0] = MT_LINK_ADDRESS;
+	memcpy(rep->buf + 1, dp->dpcd.guid, 16);
+	rep->buf[17] = ports;
+	/* port 0 */
+	rep->buf[18] = 0x90; /* input, source device, port 0 */
+	rep->buf[19] = 0x40; /* no msg, connected */
+
+	buf = rep->buf + 20;
+	for (i = 1; i < ports; i++) {
+		buf[0] = 0x30 | i; /* output, sink device, port i */
+		buf[1] = 0x40; /* no msg, connected */
+		buf[2] = 0x00; /* DPCD 0 */
+		memset(buf + 3, 0, 16); /* GUID */
+		buf[19] = 0x00; /* 0 SDP streams, 0 SDP stream sinks */
+		buf += 20;
+	}
+	rep->len = ports * 20;
+}
+
+static void execute_enum_path_resources(struct dprx_dp *dp,
+					struct msg_transaction *req,
+					struct msg_transaction *rep)
+{
+	u8 port;
+
+	port = req->buf[1] >> 4;
+
+	dp->total_pbn = dp->dpcd.link_conf[0] *
+			dp->dpcd.link_conf[1] * 32;
+
+	rep->buf[0] = MT_ENUM_PATH_RESOURCES;
+	rep->buf[1] = port << 4;
+	rep->buf[2] = dp->total_pbn >> 8;
+	rep->buf[3] = dp->total_pbn & 0xff;
+	rep->buf[4] = (dp->total_pbn - dp->sum_pbn) >> 8;
+	rep->buf[5] = (dp->total_pbn - dp->sum_pbn) & 0xff;
+	rep->len = 6;
+}
+
+static void execute_allocate_payload(struct dprx_dp *dp,
+				     struct msg_transaction *req,
+				     struct msg_transaction *rep)
+{
+	u8 port;
+	u8 id;
+	u16 pbn;
+
+	port = req->buf[1] >> 4;
+	id = req->buf[2] & 0x7f;
+	pbn = req->buf[3] << 8 | req->buf[4];
+
+	dp->vc_id[port-1] = id;
+
+	rep->buf[0] = MT_ALLOCATE_PAYLOAD;
+	rep->buf[1] = port << 4;
+	rep->buf[2] = id;
+	rep->buf[3] = pbn >> 8;
+	rep->buf[4] = pbn & 0xff;
+	rep->len = 5;
+}
+
+static void execute_clear_payload_id_table(struct dprx_dp *dp,
+					   struct msg_transaction *req,
+					   struct msg_transaction *rep)
+{
+	dprx_dprx_clear_vc_payload_table(dp);
+
+	rep->buf[0] = MT_CLEAR_PAYLOAD_ID_TABLE;
+	rep->len = 1;
+}
+
+static void execute_remote_i2c_read(struct dprx_dp *dp,
+				    struct msg_transaction *req,
+				    struct msg_transaction *rep)
+{
+	u8 *req_buf = req->buf;
+	struct sink *sink;
+	u8 port;
+	int num_write_transactions;
+	u8 addr;
+	int len;
+	int i;
+
+	port = req_buf[1] >> 4;
+
+	if (port < 1 || port > dp->sink_count) {
+		rep->buf[0] = MT_NACK | MT_REMOTE_I2C_READ;
+		memcpy(&rep->buf[1], dp->dpcd.guid, 16);
+		rep->buf[17] = MT_BAD_PARAM;
+		rep->buf[18] = 0;
+		rep->len = 18;
+		return;
+	}
+
+	sink = &dp->sinks[port-1];
+
+	num_write_transactions = req_buf[1] & 0x3;
+	req_buf += 2;
+	for (i = 0; i < num_write_transactions; i++) {
+		addr = req_buf[0] & 0x7f;
+		len = req_buf[1];
+		dprx_i2c_write(sink, addr, &req_buf[2], len);
+		req_buf += len + 3;
+	}
+	addr = req_buf[0] & 0x7f;
+	len = req_buf[1];
+
+	rep->buf[0] = MT_REMOTE_I2C_READ;
+	rep->buf[1] = port;
+	rep->buf[2] = len;
+	dprx_i2c_read(sink, addr, rep->buf + 3, len);
+	rep->len = len + 3;
+}
+
+static void execute_power_up_phy(struct dprx_dp *dp,
+				 struct msg_transaction *req,
+				 struct msg_transaction *rep)
+{
+	u8 port;
+
+	port = req->buf[1] >> 4;
+
+	rep->buf[0] = MT_POWER_UP_PHY;
+	rep->buf[1] = port << 4;
+	rep->len = 2;
+}
+
+void dprx_mt_execute(struct dprx_dp *dp, struct msg_transaction *req,
+		     struct msg_transaction *rep)
+{
+	switch (req->buf[0] & 0x7f) {
+	case MT_LINK_ADDRESS:
+		execute_link_address(dp, req, rep);
+		break;
+	case MT_ENUM_PATH_RESOURCES:
+		execute_enum_path_resources(dp, req, rep);
+		break;
+	case MT_ALLOCATE_PAYLOAD:
+		execute_allocate_payload(dp, req, rep);
+		break;
+	case MT_CLEAR_PAYLOAD_ID_TABLE:
+		execute_clear_payload_id_table(dp, req, rep);
+		break;
+	case MT_REMOTE_I2C_READ:
+		execute_remote_i2c_read(dp, req, rep);
+		break;
+	case MT_POWER_UP_PHY:
+		execute_power_up_phy(dp, req, rep);
+		break;
+	default:
+		rep->len = 0;
+	}
+}
diff --git a/drivers/media/platform/google/chameleonv3/dprx-sbmsg.c b/drivers/media/platform/google/chameleonv3/dprx-sbmsg.c
new file mode 100644
index 000000000000..ae5db31f225a
--- /dev/null
+++ b/drivers/media/platform/google/chameleonv3/dprx-sbmsg.c
@@ -0,0 +1,162 @@
+#include <linux/string.h>
+#include "dprx.h"
+
+static u8 get_hdr_crc4(const uint8_t *data, size_t num_nibbles)
+{
+	u8 bitmask = 0x80;
+	u8 bitshift = 7;
+	u8 array_index = 0;
+	int number_of_bits = num_nibbles * 4;
+	u8 remainder = 0;
+
+	while (number_of_bits != 0) {
+		number_of_bits--;
+		remainder <<= 1;
+		remainder |= (data[array_index] & bitmask) >> bitshift;
+		bitmask >>= 1;
+		bitshift--;
+		if (bitmask == 0) {
+			bitmask = 0x80;
+			bitshift = 7;
+			array_index++;
+		}
+		if ((remainder & 0x10) == 0x10)
+			remainder ^= 0x13;
+	}
+
+	number_of_bits = 4;
+	while (number_of_bits != 0) {
+		number_of_bits--;
+		remainder <<= 1;
+		if ((remainder & 0x10) != 0)
+			remainder ^= 0x13;
+	}
+
+	return remainder;
+}
+
+static u8 get_body_crc4(const uint8_t *data, u8 number_of_bytes)
+{
+	u8 bitmask = 0x80;
+	u8 bitshift = 7;
+	u8 array_index = 0;
+	int number_of_bits = number_of_bytes * 8;
+	u16 remainder = 0;
+
+	while (number_of_bits != 0) {
+		number_of_bits--;
+		remainder <<= 1;
+		remainder |= (data[array_index] & bitmask) >> bitshift;
+		bitmask >>= 1;
+		bitshift--;
+		if (bitmask == 0) {
+			bitmask = 0x80;
+			bitshift = 7;
+			array_index++;
+		}
+		if ((remainder & 0x100) == 0x100)
+			remainder ^= 0xd5;
+	}
+
+	number_of_bits = 8;
+	while (number_of_bits != 0) {
+		number_of_bits--;
+		remainder <<= 1;
+		if ((remainder & 0x100) != 0)
+			remainder ^= 0xd5;
+	}
+
+	return remainder & 0xff;
+}
+
+
+void dprx_sbmsg_read(struct dprx_dp *dp, u8 *buf, int len)
+{
+	int link_count_total;
+	int rad_len;
+	int hdr_len;
+	int body_len;
+	bool start;
+	bool end;
+	int seq_no;
+	struct msg_transaction *req;
+	struct msg_transaction *rep;
+
+	link_count_total = buf[0] >> 4;
+	rad_len = link_count_total / 2;
+	hdr_len = rad_len + 3;
+	body_len = buf[rad_len + 1] & 0x3f;
+
+	/* If message is incomplete, do nothing */
+	if (hdr_len + body_len > len)
+		return;
+
+	start  = (buf[rad_len + 2] >> 7) & 1;
+	end    = (buf[rad_len + 2] >> 6) & 1;
+	seq_no = (buf[rad_len + 2] >> 4) & 1;
+
+	req = &dp->mt_req[seq_no];
+	rep = &dp->mt_rep[seq_no];
+
+	if (start)
+		req->len = 0;
+	/* TODO: check overflow */
+	memcpy(req->buf + req->len, buf + hdr_len, body_len - 1);
+	req->len += body_len - 1;
+
+	if (end) {
+		rep->written = 0;
+		memcpy(rep->rad, buf + 1, rad_len);
+		rep->link_count_total = link_count_total;
+		dprx_mt_execute(dp, req, rep);
+	}
+}
+
+void dprx_sbmsg_write(struct dprx_dp *dp, u8 *buf, int buf_len)
+{
+	int rad_len;
+	int hdr_len;
+	int body_len;
+	bool start;
+	bool end;
+	u8 hdr_crc4;
+	u8 body_crc4;
+	struct msg_transaction *rep;
+
+	rep = &dp->mt_rep[dp->mt_seq_no];
+	if (rep->len == 0) {
+		dp->mt_seq_no ^= 1;
+		rep = &dp->mt_rep[dp->mt_seq_no];
+		if (rep->len == 0)
+			return;
+	}
+
+	rad_len = rep->link_count_total / 2;
+	hdr_len = rad_len + 3;
+	body_len = min(rep->len - rep->written + 1, buf_len - hdr_len);
+
+	start = (rep->written == 0);
+	end   = (rep->written + body_len - 1 == rep->len);
+
+	buf[0] = rep->link_count_total << 4 | ((rep->link_count_total - 1) & 0xf);
+	memcpy(buf + 1, rep->rad, rad_len);
+	buf[rad_len + 1] = body_len;
+	buf[rad_len + 2] = start << 7 | end << 6 | dp->mt_seq_no << 4;
+	hdr_crc4 = get_hdr_crc4(buf, hdr_len * 2 - 1);
+	buf[rad_len + 2] |= hdr_crc4;
+	memcpy(buf + hdr_len, rep->buf + rep->written, body_len - 1);
+	body_crc4 = get_body_crc4(buf + hdr_len, body_len - 1);
+	buf[hdr_len + body_len - 1] = body_crc4;
+	rep->written += body_len - 1;
+
+	if (end) {
+		rep->len = 0;
+		rep->written = 0;
+		dp->mt_seq_no ^= 1;
+	}
+}
+
+bool dprx_sbmsg_pending(struct dprx_dp *dp)
+{
+	return dp->mt_rep[0].len > 0 || dp->mt_rep[1].len > 0;
+}
diff --git a/drivers/media/platform/google/chameleonv3/dprx.h b/drivers/media/platform/google/chameleonv3/dprx.h
new file mode 100644
index 000000000000..8c48b775c9bd
--- /dev/null
+++ b/drivers/media/platform/google/chameleonv3/dprx.h
@@ -0,0 +1,128 @@
+#include <linux/kernel.h>
+
+struct dprx_dp_cfg {
+	const char *reg_core;
+	const char *reg_irq;
+	const char *irq;
+	int has_mst;
+	int sink_count;
+};
+
+struct msg_transaction {
+       u8 buf[256];
+       int len;
+       int written;
+       u8 rad[16];
+       int link_count_total;
+};
+
+struct dpcd_mem {
+       u8 caps[0x10];        /* 00000 - 0000f */
+       u8 mstm_cap;          /* 00021         */
+       u8 guid[0x10];        /* 00030 - 0003f */
+       u8 link_conf[0x3];    /* 00100 - 00102 */
+       u8 mstm_ctrl;         /* 00111         */
+       u8 vc_alloc[0x3];     /* 001c0 - 001c2 */
+       u8 sink_count;        /* 00200         */
+       u8 irq_vector;        /* 00201         */
+       u8 lane_align_status; /* 00204         */
+       u8 vc_table_status;   /* 0x2c0         */
+       u8 vc_table[0x40];    /* 002c1 - 002ff */
+       u8 sink_spec[0xc];    /* 00400 - 0040b */
+       u8 down_req[0x30];    /* 01000 - 01030 */
+       u8 down_rep[0x30];    /* 01400 - 01430 */
+};
+
+#define DPRX_MAX_EDID_BLOCKS 4
+
+struct sink {
+       u8 edid[128 * DPRX_MAX_EDID_BLOCKS];
+       int blocks;
+       int offset;
+       int segment;
+};
+
+struct dprx_dp {
+       struct device *dev;
+       void __iomem *iobase;
+       void __iomem *iobase_irq;
+
+       struct sink sinks[4];
+       u8 vc_id[4];
+       int sink_count;
+       int has_mst;
+       int total_pbn;
+       int sum_pbn;
+
+       /* dpcd */
+       struct dpcd_mem dpcd;
+
+       /* msg transaction */
+       struct msg_transaction mt_req[2];
+       struct msg_transaction mt_rep[2];
+       bool mt_seq_no;
+};
+
+int dprx_dp_init(struct dprx_dp *dp, struct device *dev,
+		 const struct dprx_dp_cfg *cfg);
+
+#define AUX_ACK 0x0
+#define AUX_I2C_NACK 0x4
+
+struct aux_msg {
+	u8 cmd;
+	u32 addr;
+	u8 len;
+	u8 data[16];
+};
+
+/* dprx-aux.c */
+void dprx_aux_handle_request(struct dprx_dp *dp, struct aux_msg *req,
+			     struct aux_msg *res);
+int dprx_aux_read_request(struct dprx_dp *dp, struct aux_msg *req);
+void dprx_aux_write_response(struct dprx_dp *dp, struct aux_msg *res);
+
+/* dprx-dpcd.c */
+void dprx_dpcd_init(struct dprx_dp *dp);
+void dprx_dpcd_access(struct dprx_dp *dp, struct aux_msg *req,
+		      struct aux_msg *res);
+
+
+/* dprx-dprx.c */
+void dprx_dprx_set_hpd(struct dprx_dp *dp, int val);
+int dprx_dprx_get_hpd(struct dprx_dp *dp);
+void dprx_dprx_pulse_hpd(struct dprx_dp *dp);
+void dprx_dprx_set_link_rate(struct dprx_dp *dp, int val);
+void dprx_dprx_set_lane_count(struct dprx_dp *dp, int val);
+void dprx_dprx_set_training_pattern(struct dprx_dp *dp, int val);
+void dprx_dprx_set_scrambler(struct dprx_dp *dp, int val);
+int dprx_dprx_get_cr_lock(struct dprx_dp *dp);
+int dprx_dprx_get_sym_lock(struct dprx_dp *dp);
+int dprx_dprx_get_interlane_align(struct dprx_dp *dp);
+int dprx_dprx_get_sink_status(struct dprx_dp *dp);
+int dprx_dprx_get_rx_busy(struct dprx_dp *dp);
+void dprx_dprx_set_mst(struct dprx_dp *dp, int val);
+void dprx_dprx_clear_vc_payload_table(struct dprx_dp *dp);
+void dprx_dprx_set_vc_payload_table(struct dprx_dp *dp, u8 *table, u8 *id);
+int dprx_dprx_get_act(struct dprx_dp *dp);
+void dprx_dprx_clear_act(struct dprx_dp *dp);
+int dprx_dprx_read_aux(struct dprx_dp *dp, u8 *data);
+void dprx_dprx_write_aux(struct dprx_dp *dp, u8 *data, int length);
+void dprx_dprx_init(struct dprx_dp *dp);
+
+/* dprx-edid.c */
+extern u8 default_edid[256];
+extern u8 default_edid_blocks;
+
+/* dprx-i2c.c */
+int dprx_i2c_read(struct sink *sink, u8 addr, u8 *buf, int len);
+int dprx_i2c_write(struct sink *sink, u8 addr, u8 *buf, int len);
+
+/* dprx-mt.c */
+void dprx_mt_execute(struct dprx_dp *dp, struct msg_transaction *req,
+		     struct msg_transaction *rep);
+
+/* dprx-sbmsg.c */
+void dprx_sbmsg_read(struct dprx_dp *dp, u8 *buf, int len);
+void dprx_sbmsg_write(struct dprx_dp *dp, u8 *buf, int buf_len);
+bool dprx_sbmsg_pending(struct dprx_dp *dp);
