On Wed, Sep 6, 2023 at 1:15 PM Hans Verkuil <hverkuil@xxxxxxxxx> 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@xxxxxxxxxxxx> > > --- > > 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