diff mbox series

[RFC,2/3] media: Add Google Chameleon v3 video driver

Message ID 20230630144006.1513270-3-pan@semihalf.com
State New
Headers show
Series Google Chameleon v3 video driver | expand

Commit Message

Paweł Anikiel June 30, 2023, 2:40 p.m. UTC
Add driver for the video system present on the Chameleon v3. It
consists of two Intel DisplayPort DPRX IP cores and six video
interfaces (here called "framebuffers").

Signed-off-by: Paweł Anikiel <pan@semihalf.com>
---
 drivers/media/platform/Kconfig                |   1 +
 drivers/media/platform/Makefile               |   1 +
 drivers/media/platform/google/Kconfig         |   4 +
 drivers/media/platform/google/Makefile        |   2 +
 .../media/platform/google/chameleonv3/Kconfig |   9 +
 .../platform/google/chameleonv3/Makefile      |  15 +
 .../platform/google/chameleonv3/chv3-core.c   | 292 ++++++++++
 .../platform/google/chameleonv3/chv3-core.h   |  17 +
 .../platform/google/chameleonv3/chv3-fb.c     | 539 ++++++++++++++++++
 .../platform/google/chameleonv3/chv3-fb.h     |  34 ++
 .../platform/google/chameleonv3/dprx-aux.c    |  77 +++
 .../platform/google/chameleonv3/dprx-dp.c     |  82 +++
 .../platform/google/chameleonv3/dprx-dpcd.c   | 424 ++++++++++++++
 .../platform/google/chameleonv3/dprx-dprx.c   | 262 +++++++++
 .../platform/google/chameleonv3/dprx-edid.c   |  39 ++
 .../platform/google/chameleonv3/dprx-i2c.c    |  41 ++
 .../platform/google/chameleonv3/dprx-mt.c     | 184 ++++++
 .../platform/google/chameleonv3/dprx-sbmsg.c  | 162 ++++++
 .../media/platform/google/chameleonv3/dprx.h  | 128 +++++
 19 files changed, 2313 insertions(+)
 create mode 100644 drivers/media/platform/google/Kconfig
 create mode 100644 drivers/media/platform/google/Makefile
 create mode 100644 drivers/media/platform/google/chameleonv3/Kconfig
 create mode 100644 drivers/media/platform/google/chameleonv3/Makefile
 create mode 100644 drivers/media/platform/google/chameleonv3/chv3-core.c
 create mode 100644 drivers/media/platform/google/chameleonv3/chv3-core.h
 create mode 100644 drivers/media/platform/google/chameleonv3/chv3-fb.c
 create mode 100644 drivers/media/platform/google/chameleonv3/chv3-fb.h
 create mode 100644 drivers/media/platform/google/chameleonv3/dprx-aux.c
 create mode 100644 drivers/media/platform/google/chameleonv3/dprx-dp.c
 create mode 100644 drivers/media/platform/google/chameleonv3/dprx-dpcd.c
 create mode 100644 drivers/media/platform/google/chameleonv3/dprx-dprx.c
 create mode 100644 drivers/media/platform/google/chameleonv3/dprx-edid.c
 create mode 100644 drivers/media/platform/google/chameleonv3/dprx-i2c.c
 create mode 100644 drivers/media/platform/google/chameleonv3/dprx-mt.c
 create mode 100644 drivers/media/platform/google/chameleonv3/dprx-sbmsg.c
 create mode 100644 drivers/media/platform/google/chameleonv3/dprx.h

Comments

Paweł Anikiel Sept. 6, 2023, 4:27 p.m. UTC | #1
On Wed, Sep 6, 2023 at 1:15 PM Hans Verkuil <hverkuil@xs4all.nl> wrote:
>
> On 30/06/2023 16:40, Paweł Anikiel wrote:
> > Add driver for the video system present on the Chameleon v3. It
> > consists of two Intel DisplayPort DPRX IP cores and six video
> > interfaces (here called "framebuffers").
> >
> > Signed-off-by: Paweł Anikiel <pan@semihalf.com>
> > ---
> >  drivers/media/platform/Kconfig                |   1 +
> >  drivers/media/platform/Makefile               |   1 +
> >  drivers/media/platform/google/Kconfig         |   4 +
> >  drivers/media/platform/google/Makefile        |   2 +
> >  .../media/platform/google/chameleonv3/Kconfig |   9 +
> >  .../platform/google/chameleonv3/Makefile      |  15 +
> >  .../platform/google/chameleonv3/chv3-core.c   | 292 ++++++++++
> >  .../platform/google/chameleonv3/chv3-core.h   |  17 +
> >  .../platform/google/chameleonv3/chv3-fb.c     | 539 ++++++++++++++++++
> >  .../platform/google/chameleonv3/chv3-fb.h     |  34 ++
> >  .../platform/google/chameleonv3/dprx-aux.c    |  77 +++
> >  .../platform/google/chameleonv3/dprx-dp.c     |  82 +++
> >  .../platform/google/chameleonv3/dprx-dpcd.c   | 424 ++++++++++++++
> >  .../platform/google/chameleonv3/dprx-dprx.c   | 262 +++++++++
> >  .../platform/google/chameleonv3/dprx-edid.c   |  39 ++
> >  .../platform/google/chameleonv3/dprx-i2c.c    |  41 ++
> >  .../platform/google/chameleonv3/dprx-mt.c     | 184 ++++++
> >  .../platform/google/chameleonv3/dprx-sbmsg.c  | 162 ++++++
> >  .../media/platform/google/chameleonv3/dprx.h  | 128 +++++
> >  19 files changed, 2313 insertions(+)
> >  create mode 100644 drivers/media/platform/google/Kconfig
> >  create mode 100644 drivers/media/platform/google/Makefile
> >  create mode 100644 drivers/media/platform/google/chameleonv3/Kconfig
> >  create mode 100644 drivers/media/platform/google/chameleonv3/Makefile
> >  create mode 100644 drivers/media/platform/google/chameleonv3/chv3-core.c
> >  create mode 100644 drivers/media/platform/google/chameleonv3/chv3-core.h
> >  create mode 100644 drivers/media/platform/google/chameleonv3/chv3-fb.c
> >  create mode 100644 drivers/media/platform/google/chameleonv3/chv3-fb.h
> >  create mode 100644 drivers/media/platform/google/chameleonv3/dprx-aux.c
> >  create mode 100644 drivers/media/platform/google/chameleonv3/dprx-dp.c
> >  create mode 100644 drivers/media/platform/google/chameleonv3/dprx-dpcd.c
> >  create mode 100644 drivers/media/platform/google/chameleonv3/dprx-dprx.c
> >  create mode 100644 drivers/media/platform/google/chameleonv3/dprx-edid.c
> >  create mode 100644 drivers/media/platform/google/chameleonv3/dprx-i2c.c
> >  create mode 100644 drivers/media/platform/google/chameleonv3/dprx-mt.c
> >  create mode 100644 drivers/media/platform/google/chameleonv3/dprx-sbmsg.c
> >  create mode 100644 drivers/media/platform/google/chameleonv3/dprx.h
> >
> > 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.
>
> This help text could use a bit more work...
>
> > 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.
>
> 2023? Or 2022-2023?

Yes, the latter one. I'll update it.

>
> > + */
> > +#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"
>
> Hmm, that's a bit obscure. How about chameleonv3-video?

There's already a chv3-audio driver upstream. Should I change this one anyway?

>
> > +
> > +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)
>
> You should probably use a define instead of '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;
> > +}
>
> My guess is that this is added to allow userspace to toggle the HPD when it
> sets the EDID. But this is something the s_edid should do. Typically it will
> pull the HPD low, then update the EDID and start delayed work to pull the HPD
> high again after 100ms.
>
> You might still want to keep this to do debugging, but you don't need this
> for dealing with EDID changes, the driver should take care of that.

Thanks for the explanation, I'll add it to s_edid.

>
> > +
> > +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" },
>
> I would lean towards writing "google,chameleonv3-video" here as well.

As above, there's already a google,chv3-audio binding.

>
> > +     { },
> > +};
> > +
> > +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);
>
> bus_info is set automatically for platform devices, so you can drop that.
>
> > +
> > +     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.
> > + */
>
> Why not use the DV_TIMINGS API?
> https://linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/dv-timings.html
>
> That's meant for e.g. HDMI capture where the resolution can change. It seems much
> more appropriate for this.

I have a few questions:
1. Could you clarify how this API helps with resolution changes? I
couldn't find anything in the docs about this. When looking at drivers
using DV_TIMINGS, they send a V4L2_EVENT_SOURCE_CHANGE event when a
resolution change happens, which is a different API if I understand
correctly.
2. How should the S_DV_TIMINGS ioctls be handled if the timings can't
be changed? In the intel DPRX IP, the timings are only available as
read-only MSA registers:
https://www.intel.com/content/www/us/en/docs/programmable/683273/23-1-20-0-1/sink-msa-registers.html
3. The G_FMT ioctl still needs to be handled. Is my approach of
delaying the resolution change appropriate?

>
> > +
> > +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,
>
> Add prepare_buf and expbuf as well. No need to leave those out.
>
> > +     .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;
>
> Hmm, V4L2 expects that there is always a valid video format, so this should
> never happen. If it doesn't know the correct video format (typical for e.g.
> HDMI/DP inputs), then it just defaults to something reasonable (1920x1080 or so).
>
> > +
> > +     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;
>
> You can let vb2_queue keep track of the streaming state: vb2_is_streaming(q).
>
> > +     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;
>
> I strongly suggest adding VB2_DMABUF.
>
> > +     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)
>
> Reciever -> Receiver
>
> > +     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
> > +};
>
> Running 'edid-decode -c' (git://linuxtv.org/edid-decode.git) results in a number
> of warnings and failures:
>
> ----------------
>
> edid-decode SHA: 2d44e1b01c7e 2023-03-11 18:21:51
>
> Warnings:
>
> Block 1, CTA-861 Extension Block:
>   Video Data Block: VIC 31 is the preferred timing, overriding the first detailed timings. Is this intended?
>   Video Capability Data Block: Set Selectable YCbCr Quantization to avoid interop issues.
>   Add a Colorimetry Data Block with the sRGB colorimetry bit set to avoid interop issues.
>
> Failures:
>
> Block 0, Base EDID:
>   Detailed Timing Descriptor #1: Mismatch of image size 698x392 mm vs display size 1280x720 mm.
>   Detailed Timing Descriptor #2: Mismatch of image size 698x392 mm vs display size 1280x720 mm.
> Block 1, CTA-861 Extension Block:
>   Detailed Timing Descriptor #3: Mismatch of image size 698x392 mm vs display size 1280x720 mm.
>   Detailed Timing Descriptor #4: Mismatch of image size 698x392 mm vs display size 1280x720 mm.
>
> EDID conformity: FAIL

To be honest, I just put in a random EDID so that the user can start
grabbing frames without having to set one (it was useful for testing).
It's not significant in any way. Should I remove it?

>
> > +
> > +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);
>
> Regards,
>
>         Hans
Hans Verkuil Sept. 7, 2023, 7:47 a.m. UTC | #2
On 06/09/2023 18:27, Paweł Anikiel wrote:
> On Wed, Sep 6, 2023 at 1:15 PM Hans Verkuil <hverkuil@xs4all.nl> wrote:
>>
>> On 30/06/2023 16:40, Paweł Anikiel wrote:
>>> Add driver for the video system present on the Chameleon v3. It
>>> consists of two Intel DisplayPort DPRX IP cores and six video
>>> interfaces (here called "framebuffers").
>>>
>>> Signed-off-by: Paweł Anikiel <pan@semihalf.com>
>>> ---
>>>  drivers/media/platform/Kconfig                |   1 +
>>>  drivers/media/platform/Makefile               |   1 +
>>>  drivers/media/platform/google/Kconfig         |   4 +
>>>  drivers/media/platform/google/Makefile        |   2 +
>>>  .../media/platform/google/chameleonv3/Kconfig |   9 +
>>>  .../platform/google/chameleonv3/Makefile      |  15 +
>>>  .../platform/google/chameleonv3/chv3-core.c   | 292 ++++++++++
>>>  .../platform/google/chameleonv3/chv3-core.h   |  17 +
>>>  .../platform/google/chameleonv3/chv3-fb.c     | 539 ++++++++++++++++++
>>>  .../platform/google/chameleonv3/chv3-fb.h     |  34 ++
>>>  .../platform/google/chameleonv3/dprx-aux.c    |  77 +++
>>>  .../platform/google/chameleonv3/dprx-dp.c     |  82 +++
>>>  .../platform/google/chameleonv3/dprx-dpcd.c   | 424 ++++++++++++++
>>>  .../platform/google/chameleonv3/dprx-dprx.c   | 262 +++++++++
>>>  .../platform/google/chameleonv3/dprx-edid.c   |  39 ++
>>>  .../platform/google/chameleonv3/dprx-i2c.c    |  41 ++
>>>  .../platform/google/chameleonv3/dprx-mt.c     | 184 ++++++
>>>  .../platform/google/chameleonv3/dprx-sbmsg.c  | 162 ++++++
>>>  .../media/platform/google/chameleonv3/dprx.h  | 128 +++++
>>>  19 files changed, 2313 insertions(+)
>>>  create mode 100644 drivers/media/platform/google/Kconfig
>>>  create mode 100644 drivers/media/platform/google/Makefile
>>>  create mode 100644 drivers/media/platform/google/chameleonv3/Kconfig
>>>  create mode 100644 drivers/media/platform/google/chameleonv3/Makefile
>>>  create mode 100644 drivers/media/platform/google/chameleonv3/chv3-core.c
>>>  create mode 100644 drivers/media/platform/google/chameleonv3/chv3-core.h
>>>  create mode 100644 drivers/media/platform/google/chameleonv3/chv3-fb.c
>>>  create mode 100644 drivers/media/platform/google/chameleonv3/chv3-fb.h
>>>  create mode 100644 drivers/media/platform/google/chameleonv3/dprx-aux.c
>>>  create mode 100644 drivers/media/platform/google/chameleonv3/dprx-dp.c
>>>  create mode 100644 drivers/media/platform/google/chameleonv3/dprx-dpcd.c
>>>  create mode 100644 drivers/media/platform/google/chameleonv3/dprx-dprx.c
>>>  create mode 100644 drivers/media/platform/google/chameleonv3/dprx-edid.c
>>>  create mode 100644 drivers/media/platform/google/chameleonv3/dprx-i2c.c
>>>  create mode 100644 drivers/media/platform/google/chameleonv3/dprx-mt.c
>>>  create mode 100644 drivers/media/platform/google/chameleonv3/dprx-sbmsg.c
>>>  create mode 100644 drivers/media/platform/google/chameleonv3/dprx.h
>>>

<snip>

>>> +#define MODULE_NAME "chv3-video"
>>
>> Hmm, that's a bit obscure. How about chameleonv3-video?
> 
> There's already a chv3-audio driver upstream. Should I change this one anyway?

Ah, I was not aware of that. In that case leave it as-is. Same for the compatible
string. Personally I think it is a bit too vague, but that ship has sailed...

<snip>

>>> +/*
>>> + * 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.
>>> + */
>>
>> Why not use the DV_TIMINGS API?
>> https://linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/dv-timings.html
>>
>> That's meant for e.g. HDMI capture where the resolution can change. It seems much
>> more appropriate for this.
> 
> I have a few questions:
> 1. Could you clarify how this API helps with resolution changes? I
> couldn't find anything in the docs about this. When looking at drivers
> using DV_TIMINGS, they send a V4L2_EVENT_SOURCE_CHANGE event when a
> resolution change happens, which is a different API if I understand
> correctly.

Perhaps I phrased it badly. The DV Timings API isn't a separate API, it is just a
set of V4L2 ioctls specifically created to handle digital video inputs (HDMI, DP,
VGA, DVI, etc.) where the video signal can go away (unplugged), and come back, and
change resolutions on the fly. It relies on the V4L2_EVENT_SOURCE_CHANGE to signal
to userspace when the resolution changes.

> 2. How should the S_DV_TIMINGS ioctls be handled if the timings can't
> be changed? In the intel DPRX IP, the timings are only available as
> read-only MSA registers:
> https://www.intel.com/content/www/us/en/docs/programmable/683273/23-1-20-0-1/sink-msa-registers.html
> 3. The G_FMT ioctl still needs to be handled. Is my approach of
> delaying the resolution change appropriate?

The basic operation is that the hardware detects the timings (and also whether there
is a video signal at all), and if the video signal/timings change it will raise the
event and (if streaming is in progress) call vb2_queue_error() to stop further streaming.

Userspace sees the event, it will stop streaming and call QUERY_DV_TIMINGS to get the new
timings (if any). When it gets the new timings it will call S_DV_TIMINGS. S_DV_TIMINGS will
also update the format, so afterwards G_FMT will return an updated format using the new
width and height.

The reason for all this is that you cannot change resolutions on the fly since there is
a whole pipeline that depends on the resolution: buffer sizes (of course), but also
how the captured frames are used in userspace, etc. So a resolution change will typically
require a complete rebuild of the video pipeline.

Whether S_DV_TIMINGS needs to change anything in the hardware depends on the hardware
itself. It will always update the current format. In the case of this driver that is likely
the only thing it needs to do.

Note that the driver should always have some initial default timings. Typically 1080p60
is chosen. The internal timings structure is just what was last set (or the initial default),
and S_DV_TIMINGS updates it. It does not need to match what the hardware detects.

Only QUERY_DV_TIMINGS reports what the hardware sees, but that ioctl will never change
the driver state, it just reports what is detected.

There are also a number of controls that relate to DV_TIMINGS API, see the V4L2_CID_DV_TX_*
controls: https://hverkuil.home.xs4all.nl/spec/userspace-api/v4l/ext-ctrls-dv.html

You will likely want to support V4L2_CID_DV_RX_POWER_PRESENT and V4L2_CID_DV_RX_RGB_RANGE,
assuming the hardware can report this.

I really need to start documenting this properly in our spec. Let me see if I can come
up with an initial patch for this.

<snip>

>>> 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
>>> +};
>>
>> Running 'edid-decode -c' (git://linuxtv.org/edid-decode.git) results in a number
>> of warnings and failures:
>>
>> ----------------
>>
>> edid-decode SHA: 2d44e1b01c7e 2023-03-11 18:21:51
>>
>> Warnings:
>>
>> Block 1, CTA-861 Extension Block:
>>   Video Data Block: VIC 31 is the preferred timing, overriding the first detailed timings. Is this intended?
>>   Video Capability Data Block: Set Selectable YCbCr Quantization to avoid interop issues.
>>   Add a Colorimetry Data Block with the sRGB colorimetry bit set to avoid interop issues.
>>
>> Failures:
>>
>> Block 0, Base EDID:
>>   Detailed Timing Descriptor #1: Mismatch of image size 698x392 mm vs display size 1280x720 mm.
>>   Detailed Timing Descriptor #2: Mismatch of image size 698x392 mm vs display size 1280x720 mm.
>> Block 1, CTA-861 Extension Block:
>>   Detailed Timing Descriptor #3: Mismatch of image size 698x392 mm vs display size 1280x720 mm.
>>   Detailed Timing Descriptor #4: Mismatch of image size 698x392 mm vs display size 1280x720 mm.
>>
>> EDID conformity: FAIL
> 
> To be honest, I just put in a random EDID so that the user can start
> grabbing frames without having to set one (it was useful for testing).
> It's not significant in any way. Should I remove it?

This driver is used for a specific use-case, and it is one for which it is fine
to put in a default EDID. But, if you do so, then you should make sure you use
an EDID that passes the edid-decode checks.

It also has to match the hardware capabilities.

Regards,

	Hans
diff mbox series

Patch

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);