devicetree.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/9] Add Chameleon v3 video support
@ 2024-02-12 13:13 Paweł Anikiel
  2024-02-12 13:13 ` [PATCH 1/9] media: v4l2-subdev: Add a pad variant of .query_dv_timings() Paweł Anikiel
                   ` (8 more replies)
  0 siblings, 9 replies; 19+ messages in thread
From: Paweł Anikiel @ 2024-02-12 13:13 UTC (permalink / raw)
  To: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
	krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
	robh+dt, tzimmermann
  Cc: devicetree, dri-devel, linux-kernel, linux-media,
	chromeos-krk-upstreaming, ribalda, Paweł Anikiel

Google Chameleon v3 is a testing device capable of emulating multiple
DisplayPort monitors, used for testing purposes.  It is based on an Arria
10 SoCFPGA.  This patchset adds V4L2 drivers for two IP blocks used in
the device's FPGA: the Chameleon v3 framebuffer, and the Intel DisplayPort
RX IP.  The former is a video capture device that takes video signal and
writes frames into memory, which can be later processed by userspace.
The latter is a DisplayPort receiver IP from Intel, its datasheet can
be found at:
https://www.intel.com/programmable/technical-pdfs/683273.pdf

The framebuffer driver is a regular v4l2 capture device driver, while
the DP RX driver is a v4l2 subdevice driver. In order to avoid code
duplication, some parts of the DisplayPort code from the DRM subsystem
were put into headers usable by the DP RX driver. Apart from that, the
patchset adds a new callback to the internal v4l2 subdevice API to allow
querying the dv timings of individual video streams of the DP receiver.

Paweł Anikiel (9):
  media: v4l2-subdev: Add a pad variant of .query_dv_timings()
  media: Add Chameleon v3 framebuffer driver
  drm/dp_mst: Move DRM-independent structures to separate header
  lib: Move DisplayPort CRC functions to common lib
  drm/display: Add mask definitions for DP_PAYLOAD_ALLOCATE_* registers
  media: intel: Add Displayport RX IP driver
  media: dt-bindings: Add Chameleon v3 framebuffer
  media: dt-bindings: Add Intel Displayport RX IP
  ARM: dts: chameleonv3: Add video device nodes

 .../bindings/media/google,chv3-fb.yaml        |   77 +
 .../devicetree/bindings/media/intel,dprx.yaml |  125 +
 .../socfpga/socfpga_arria10_chameleonv3.dts   |  130 +
 drivers/gpu/drm/display/Kconfig               |    1 +
 drivers/gpu/drm/display/drm_dp_mst_topology.c |   76 +-
 drivers/media/platform/Kconfig                |    1 +
 drivers/media/platform/Makefile               |    1 +
 drivers/media/platform/google/Kconfig         |    3 +
 drivers/media/platform/google/Makefile        |    2 +
 .../media/platform/google/chameleonv3/Kconfig |   13 +
 .../platform/google/chameleonv3/Makefile      |    3 +
 .../platform/google/chameleonv3/chv3-fb.c     |  897 +++++++
 drivers/media/platform/intel/Kconfig          |   12 +
 drivers/media/platform/intel/Makefile         |    1 +
 drivers/media/platform/intel/intel-dprx.c     | 2171 +++++++++++++++++
 drivers/media/v4l2-core/v4l2-subdev.c         |   11 +
 include/drm/display/drm_dp.h                  |    9 +-
 include/drm/display/drm_dp_mst.h              |  238 ++
 include/drm/display/drm_dp_mst_helper.h       |  232 +-
 include/linux/crc-dp.h                        |   10 +
 include/media/v4l2-subdev.h                   |    5 +
 lib/Kconfig                                   |    8 +
 lib/Makefile                                  |    1 +
 lib/crc-dp.c                                  |   78 +
 24 files changed, 3801 insertions(+), 304 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/media/google,chv3-fb.yaml
 create mode 100644 Documentation/devicetree/bindings/media/intel,dprx.yaml
 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-fb.c
 create mode 100644 drivers/media/platform/intel/intel-dprx.c
 create mode 100644 include/drm/display/drm_dp_mst.h
 create mode 100644 include/linux/crc-dp.h
 create mode 100644 lib/crc-dp.c

-- 
2.43.0.687.g38aa6559b0-goog


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH 1/9] media: v4l2-subdev: Add a pad variant of .query_dv_timings()
  2024-02-12 13:13 [PATCH 0/9] Add Chameleon v3 video support Paweł Anikiel
@ 2024-02-12 13:13 ` Paweł Anikiel
  2024-02-12 13:13 ` [PATCH 2/9] media: Add Chameleon v3 framebuffer driver Paweł Anikiel
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 19+ messages in thread
From: Paweł Anikiel @ 2024-02-12 13:13 UTC (permalink / raw)
  To: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
	krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
	robh+dt, tzimmermann
  Cc: devicetree, dri-devel, linux-kernel, linux-media,
	chromeos-krk-upstreaming, ribalda, Paweł Anikiel

Currently, .query_dv_timings() is defined as a video callback without
a pad argument. This is a problem if the subdevice can have different
dv timings for each pad (e.g. a DisplayPort receiver with multiple
virtual channels).

To solve this, add a pad variant of this callback which includes
the pad number as an argument.

Signed-off-by: Paweł Anikiel <panikiel@google.com>
---
 drivers/media/v4l2-core/v4l2-subdev.c | 11 +++++++++++
 include/media/v4l2-subdev.h           |  5 +++++
 2 files changed, 16 insertions(+)

diff --git a/drivers/media/v4l2-core/v4l2-subdev.c b/drivers/media/v4l2-core/v4l2-subdev.c
index 4c6198c48dd6..11f865dd19b4 100644
--- a/drivers/media/v4l2-core/v4l2-subdev.c
+++ b/drivers/media/v4l2-core/v4l2-subdev.c
@@ -389,6 +389,16 @@ static int call_enum_dv_timings(struct v4l2_subdev *sd,
 	       sd->ops->pad->enum_dv_timings(sd, dvt);
 }
 
+static int call_query_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
+				 struct v4l2_dv_timings *timings)
+{
+	if (!timings)
+		return -EINVAL;
+
+	return check_pad(sd, pad) ? :
+	       sd->ops->pad->query_dv_timings(sd, pad, timings);
+}
+
 static int call_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad,
 				struct v4l2_mbus_config *config)
 {
@@ -489,6 +499,7 @@ static const struct v4l2_subdev_pad_ops v4l2_subdev_call_pad_wrappers = {
 	.set_edid		= call_set_edid,
 	.dv_timings_cap		= call_dv_timings_cap,
 	.enum_dv_timings	= call_enum_dv_timings,
+	.query_dv_timings	= call_query_dv_timings,
 	.get_frame_desc		= call_get_frame_desc,
 	.get_mbus_config	= call_get_mbus_config,
 };
diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h
index a9e6b8146279..dc8963fa5a06 100644
--- a/include/media/v4l2-subdev.h
+++ b/include/media/v4l2-subdev.h
@@ -797,6 +797,9 @@ struct v4l2_subdev_state {
  * @enum_dv_timings: callback for VIDIOC_SUBDEV_ENUM_DV_TIMINGS() ioctl handler
  *		     code.
  *
+ * @query_dv_timings: same as query_dv_timings() from v4l2_subdev_video_ops,
+ *		      but with additional pad argument.
+ *
  * @link_validate: used by the media controller code to check if the links
  *		   that belongs to a pipeline can be used for stream.
  *
@@ -868,6 +871,8 @@ struct v4l2_subdev_pad_ops {
 			      struct v4l2_dv_timings_cap *cap);
 	int (*enum_dv_timings)(struct v4l2_subdev *sd,
 			       struct v4l2_enum_dv_timings *timings);
+	int (*query_dv_timings)(struct v4l2_subdev *sd, unsigned int pad,
+				struct v4l2_dv_timings *timings);
 #ifdef CONFIG_MEDIA_CONTROLLER
 	int (*link_validate)(struct v4l2_subdev *sd, struct media_link *link,
 			     struct v4l2_subdev_format *source_fmt,
-- 
2.43.0.687.g38aa6559b0-goog


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH 2/9] media: Add Chameleon v3 framebuffer driver
  2024-02-12 13:13 [PATCH 0/9] Add Chameleon v3 video support Paweł Anikiel
  2024-02-12 13:13 ` [PATCH 1/9] media: v4l2-subdev: Add a pad variant of .query_dv_timings() Paweł Anikiel
@ 2024-02-12 13:13 ` Paweł Anikiel
  2024-02-12 13:13 ` [PATCH 3/9] drm/dp_mst: Move DRM-independent structures to separate header Paweł Anikiel
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 19+ messages in thread
From: Paweł Anikiel @ 2024-02-12 13:13 UTC (permalink / raw)
  To: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
	krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
	robh+dt, tzimmermann
  Cc: devicetree, dri-devel, linux-kernel, linux-media,
	chromeos-krk-upstreaming, ribalda, Paweł Anikiel

Add v4l2 driver for the Google Chameleon v3 framebuffer device.

Signed-off-by: Paweł Anikiel <panikiel@google.com>
---
 drivers/media/platform/Kconfig                |   1 +
 drivers/media/platform/Makefile               |   1 +
 drivers/media/platform/google/Kconfig         |   3 +
 drivers/media/platform/google/Makefile        |   2 +
 .../media/platform/google/chameleonv3/Kconfig |  13 +
 .../platform/google/chameleonv3/Makefile      |   3 +
 .../platform/google/chameleonv3/chv3-fb.c     | 897 ++++++++++++++++++
 7 files changed, 920 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-fb.c

diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index 91e54215de3a..b82f7b142b85 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 3296ec1ebe16..f7067eb05f76 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..2a5f01034c11
--- /dev/null
+++ b/drivers/media/platform/google/Kconfig
@@ -0,0 +1,3 @@
+# 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..76d0383a8589
--- /dev/null
+++ b/drivers/media/platform/google/chameleonv3/Kconfig
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config VIDEO_CHV3_FB
+	tristate "Google Chameleon v3 framebuffer video driver"
+	depends on V4L_PLATFORM_DRIVERS
+	depends on VIDEO_DEV
+	select VIDEOBUF2_DMA_CONTIG
+	select V4L2_FWNODE
+	help
+	  v4l2 driver for the video interface present on the Google
+	  Chameleon v3. The Chameleon v3 uses the framebuffer IP core
+	  to take the video signal from different sources and directly
+	  write frames into memory.
diff --git a/drivers/media/platform/google/chameleonv3/Makefile b/drivers/media/platform/google/chameleonv3/Makefile
new file mode 100644
index 000000000000..d63727148688
--- /dev/null
+++ b/drivers/media/platform/google/chameleonv3/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-$(CONFIG_VIDEO_CHV3_FB) += chv3-fb.o
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..1bcd7410a743
--- /dev/null
+++ b/drivers/media/platform/google/chameleonv3/chv3-fb.c
@@ -0,0 +1,897 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2023-2024 Google LLC.
+ * Author: Paweł Anikiel <panikiel@google.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-dv-timings.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-dma-contig.h>
+
+#define DEVICE_NAME	"chv3-fb"
+
+/*
+ * The device is expected to report some format even if there's currently no
+ * active video stream. In such case we default to 1080p.
+ */
+#define DEFAULT_WIDTH	1920
+#define DEFAULT_HEIGHT	1080
+
+#define FB_EN				0x00
+#define FB_EN_BIT			BIT(0)
+#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_RESET_BIT			BIT(0)
+#define FB_ERRORSTATUS			0x1c
+#define FB_IOCOLOR			0x20
+#define FB_DATARATE			0x24
+#define FB_DATARATE_SINGLE		0x0
+#define FB_DATARATE_DOUBLE		0x1
+#define FB_PIXELMODE			0x28
+#define FB_PIXELMODE_SINGLE		0x0
+#define FB_PIXELMODE_DOUBLE		0x1
+#define FB_SYNCPOLARITY			0x2c
+#define FB_DMAFORMAT			0x30
+#define FB_DMAFORMAT_8BPC		0x0
+#define FB_DMAFORMAT_10BPC_UPPER	0x1
+#define FB_DMAFORMAT_10BPC_LOWER	0x2
+#define FB_DMAFORMAT_12BPC_UPPER	0x3
+#define FB_DMAFORMAT_12BPC_LOWER	0x4
+#define FB_DMAFORMAT_16BPC		0x5
+#define FB_DMAFORMAT_RAW		0x6
+#define FB_DMAFORMAT_8BPC_LEGACY	0x7
+#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		BIT(0)
+#define FB_IRQ_BUFF1		BIT(1)
+#define FB_IRQ_RESOLUTION	BIT(2)
+#define FB_IRQ_ERROR		BIT(3)
+
+struct chv3_fb {
+	struct device *dev;
+	void __iomem *iobase;
+	void __iomem *iobase_irq;
+
+	struct v4l2_device v4l2_dev;
+	struct vb2_queue queue;
+	struct video_device vdev;
+	struct v4l2_pix_format pix_fmt;
+	struct v4l2_dv_timings timings;
+
+	struct v4l2_async_notifier notifier;
+	struct v4l2_subdev *subdev;
+	int subdev_source_pad;
+
+	u32 sequence;
+	bool writing_to_a;
+
+	struct list_head bufs;
+	spinlock_t bufs_lock;
+
+	struct mutex fb_lock;
+};
+
+struct chv3_fb_buffer {
+	struct vb2_v4l2_buffer vb;
+	struct list_head link;
+};
+
+static void chv3_fb_set_format_resolution(struct chv3_fb *fb, u32 width, u32 height)
+{
+	u32 bytes_per_pixel;
+
+	if (fb->pix_fmt.pixelformat == V4L2_PIX_FMT_RGB24)
+		bytes_per_pixel = 3;
+	else
+		bytes_per_pixel = 4;
+
+	fb->pix_fmt.width = width;
+	fb->pix_fmt.height = height;
+	fb->pix_fmt.bytesperline = width * bytes_per_pixel;
+	fb->pix_fmt.sizeimage = fb->pix_fmt.bytesperline * height;
+}
+
+/*
+ * The video interface has hardware counters which expose the width and
+ * height of the current video stream. It can't reliably detect if the stream
+ * is present or not, so this is only used as a fallback in the case where
+ * we don't have access to the receiver hardware.
+ */
+static int chv3_fb_query_dv_timings_fallback(struct chv3_fb *fb,
+					     struct v4l2_dv_timings *timings)
+{
+	u32 width, height;
+
+	width  = readl(fb->iobase + FB_WIDTH);
+	height = readl(fb->iobase + FB_HEIGHT);
+	if (width == 0 || height == 0)
+		return -ENOLINK;
+
+	memset(timings, 0, sizeof(*timings));
+	timings->type = V4L2_DV_BT_656_1120;
+	timings->bt.width  = width;
+	timings->bt.height = height;
+
+	return 0;
+}
+
+static int chv3_fb_query_dv_timings(struct chv3_fb *fb, struct v4l2_dv_timings *timings)
+{
+	if (fb->subdev) {
+		return v4l2_subdev_call(fb->subdev, pad, query_dv_timings,
+					fb->subdev_source_pad, timings);
+	} else {
+		return chv3_fb_query_dv_timings_fallback(fb, timings);
+	}
+}
+
+static const struct v4l2_dv_timings_cap chv3_fb_fallback_dv_timings_cap = {
+	.type = V4L2_DV_BT_656_1120,
+	.bt = {
+		.min_width = 0,
+		.max_width = 65535,
+		.min_height = 0,
+		.max_height = 65535,
+		.min_pixelclock = 0,
+		.max_pixelclock = 2147483647,
+		.standards = V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT |
+			V4L2_DV_BT_STD_CVT | V4L2_DV_BT_STD_GTF,
+		.capabilities = V4L2_DV_BT_CAP_PROGRESSIVE |
+			V4L2_DV_BT_CAP_REDUCED_BLANKING |
+			V4L2_DV_BT_CAP_CUSTOM,
+	},
+};
+
+static int chv3_fb_enum_dv_timings_fallback(struct chv3_fb *fb,
+					    struct v4l2_enum_dv_timings *timings)
+{
+	return v4l2_enum_dv_timings_cap(timings, &chv3_fb_fallback_dv_timings_cap,
+					NULL, NULL);
+}
+
+static int chv3_fb_dv_timings_cap_fallback(struct chv3_fb *fb,
+					   struct v4l2_dv_timings_cap *cap)
+{
+	*cap = chv3_fb_fallback_dv_timings_cap;
+
+	return 0;
+}
+
+static void chv3_fb_apply_dv_timings(struct chv3_fb *fb)
+{
+	struct v4l2_dv_timings timings;
+	int res;
+
+	res = chv3_fb_query_dv_timings(fb, &timings);
+	if (res)
+		return;
+
+	fb->timings = timings;
+	chv3_fb_set_format_resolution(fb, timings.bt.width, timings.bt.height);
+}
+
+static int chv3_fb_querycap(struct file *file, void *fh,
+			    struct v4l2_capability *cap)
+{
+	strscpy(cap->driver, DEVICE_NAME, sizeof(cap->driver));
+	strscpy(cap->card, "Chameleon v3 video", sizeof(cap->card));
+
+	return 0;
+}
+
+static int chv3_fb_g_fmt_vid_cap(struct file *file, void *fh,
+				 struct v4l2_format *fmt)
+{
+	struct chv3_fb *fb = video_drvdata(file);
+
+	fmt->fmt.pix = fb->pix_fmt;
+
+	return 0;
+}
+
+static int chv3_fb_enum_fmt_vid_cap(struct file *file, void *fh,
+				    struct v4l2_fmtdesc *fmt)
+{
+	struct chv3_fb *fb = video_drvdata(file);
+
+	if (fmt->index != 0)
+		return -EINVAL;
+
+	fmt->flags = 0;
+	fmt->pixelformat = fb->pix_fmt.pixelformat;
+
+	return 0;
+}
+
+static int chv3_fb_enum_framesizes(struct file *file, void *fh,
+				   struct v4l2_frmsizeenum *frm)
+{
+	struct chv3_fb *fb = video_drvdata(file);
+
+	if (frm->index != 0)
+		return -EINVAL;
+
+	if (frm->pixel_format != fb->pix_fmt.pixelformat)
+		return -EINVAL;
+
+	frm->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+	frm->discrete.width  = fb->pix_fmt.width;
+	frm->discrete.height = fb->pix_fmt.height;
+
+	return 0;
+}
+
+static int chv3_fb_g_input(struct file *file, void *fh, unsigned int *index)
+{
+	*index = 0;
+
+	return 0;
+}
+
+static int chv3_fb_s_input(struct file *file, void *fh, unsigned int index)
+{
+	if (index != 0)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int chv3_fb_enum_input(struct file *file, void *fh,
+			      struct v4l2_input *input)
+{
+	if (input->index != 0)
+		return -EINVAL;
+
+	strscpy(input->name, "input0", sizeof(input->name));
+	input->type = V4L2_INPUT_TYPE_CAMERA;
+	input->capabilities = V4L2_IN_CAP_DV_TIMINGS;
+
+	return 0;
+}
+
+static int chv3_fb_g_edid(struct file *file, void *fh,
+			  struct v4l2_edid *edid)
+{
+	struct chv3_fb *fb = video_drvdata(file);
+	int res;
+
+	if (!fb->subdev)
+		return -ENOTTY;
+
+	if (edid->pad != 0)
+		return -EINVAL;
+
+	edid->pad = fb->subdev_source_pad;
+	res = v4l2_subdev_call(fb->subdev, pad, get_edid, edid);
+	edid->pad = 0;
+
+	return res;
+}
+
+static int chv3_fb_s_edid(struct file *file, void *fh,
+			  struct v4l2_edid *edid)
+{
+	struct chv3_fb *fb = video_drvdata(file);
+	int res;
+
+	if (!fb->subdev)
+		return -ENOTTY;
+
+	if (edid->pad != 0)
+		return -EINVAL;
+
+	edid->pad = fb->subdev_source_pad;
+	res = v4l2_subdev_call(fb->subdev, pad, set_edid, edid);
+	edid->pad = 0;
+
+	return res;
+}
+
+static int chv3_fb_s_dv_timings(struct file *file, void *fh,
+				struct v4l2_dv_timings *timings)
+{
+	struct chv3_fb *fb = video_drvdata(file);
+
+	if (timings->bt.width == fb->timings.bt.width &&
+	    timings->bt.height == fb->timings.bt.height)
+		return 0;
+
+	if (vb2_is_busy(&fb->queue))
+		return -EBUSY;
+
+	if (!v4l2_valid_dv_timings(timings, &chv3_fb_fallback_dv_timings_cap, NULL, NULL))
+		return -ERANGE;
+
+	fb->timings = *timings;
+	chv3_fb_set_format_resolution(fb, timings->bt.width, timings->bt.height);
+
+	return 0;
+}
+
+static int chv3_fb_g_dv_timings(struct file *file, void *fh,
+				struct v4l2_dv_timings *timings)
+{
+	struct chv3_fb *fb = video_drvdata(file);
+
+	*timings = fb->timings;
+	return 0;
+}
+
+static int chv3_fb_vidioc_query_dv_timings(struct file *file, void *fh,
+					   struct v4l2_dv_timings *timings)
+{
+	struct chv3_fb *fb = video_drvdata(file);
+
+	return chv3_fb_query_dv_timings(fb, timings);
+}
+
+static int chv3_fb_enum_dv_timings(struct file *file, void *fh,
+				   struct v4l2_enum_dv_timings *timings)
+{
+	struct chv3_fb *fb = video_drvdata(file);
+	int res;
+
+	if (timings->pad != 0)
+		return -EINVAL;
+
+	if (fb->subdev) {
+		timings->pad = fb->subdev_source_pad;
+		res = v4l2_subdev_call(fb->subdev, pad, enum_dv_timings, timings);
+		timings->pad = 0;
+		return res;
+	} else {
+		return chv3_fb_enum_dv_timings_fallback(fb, timings);
+	}
+}
+
+static int chv3_fb_dv_timings_cap(struct file *file, void *fh,
+				  struct v4l2_dv_timings_cap *cap)
+{
+	struct chv3_fb *fb = video_drvdata(file);
+	int res;
+
+	if (cap->pad != 0)
+		return -EINVAL;
+
+	if (fb->subdev) {
+		cap->pad = fb->subdev_source_pad;
+		res = v4l2_subdev_call(fb->subdev, pad, dv_timings_cap, cap);
+		cap->pad = 0;
+		return res;
+	} else {
+		return chv3_fb_dv_timings_cap_fallback(fb, cap);
+	}
+}
+
+static int chv3_fb_subscribe_event(struct v4l2_fh *fh,
+				   const struct v4l2_event_subscription *sub)
+{
+	switch (sub->type) {
+	case V4L2_EVENT_SOURCE_CHANGE:
+		return v4l2_src_change_event_subscribe(fh, sub);
+	}
+
+	return v4l2_ctrl_subscribe_event(fh, sub);
+}
+
+static const struct v4l2_ioctl_ops chv3_fb_v4l2_ioctl_ops = {
+	.vidioc_querycap = chv3_fb_querycap,
+
+	.vidioc_enum_fmt_vid_cap = chv3_fb_enum_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap = chv3_fb_g_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap = chv3_fb_g_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap = chv3_fb_g_fmt_vid_cap,
+
+	.vidioc_enum_framesizes = chv3_fb_enum_framesizes,
+
+	.vidioc_enum_input = chv3_fb_enum_input,
+	.vidioc_g_input = chv3_fb_g_input,
+	.vidioc_s_input = chv3_fb_s_input,
+	.vidioc_g_edid = chv3_fb_g_edid,
+	.vidioc_s_edid = chv3_fb_s_edid,
+
+	.vidioc_reqbufs = vb2_ioctl_reqbufs,
+	.vidioc_create_bufs = vb2_ioctl_create_bufs,
+	.vidioc_querybuf = vb2_ioctl_querybuf,
+	.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+	.vidioc_expbuf = vb2_ioctl_expbuf,
+	.vidioc_qbuf = vb2_ioctl_qbuf,
+	.vidioc_dqbuf = vb2_ioctl_dqbuf,
+	.vidioc_streamon = vb2_ioctl_streamon,
+	.vidioc_streamoff = vb2_ioctl_streamoff,
+
+	.vidioc_s_dv_timings = chv3_fb_s_dv_timings,
+	.vidioc_g_dv_timings = chv3_fb_g_dv_timings,
+	.vidioc_query_dv_timings = chv3_fb_vidioc_query_dv_timings,
+	.vidioc_enum_dv_timings = chv3_fb_enum_dv_timings,
+	.vidioc_dv_timings_cap = chv3_fb_dv_timings_cap,
+
+	.vidioc_subscribe_event = chv3_fb_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static int chv3_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 (*nplanes) {
+		if (sizes[0] < fb->pix_fmt.sizeimage)
+			return -EINVAL;
+		return 0;
+	}
+	*nplanes = 1;
+	sizes[0] = fb->pix_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 chv3_fb_buffer_dma_addr(struct chv3_fb_buffer *buf)
+{
+	return vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
+}
+
+static void chv3_fb_start_frame(struct chv3_fb *fb, struct chv3_fb_buffer *buf)
+{
+	fb->writing_to_a = 1;
+	writel(chv3_fb_buffer_dma_addr(buf), fb->iobase + FB_BUFFERA);
+	writel(FB_EN_BIT, fb->iobase + FB_EN);
+}
+
+static void chv3_fb_next_frame(struct chv3_fb *fb, struct chv3_fb_buffer *buf)
+{
+	u32 reg = fb->writing_to_a ? FB_BUFFERB : FB_BUFFERA;
+
+	writel(chv3_fb_buffer_dma_addr(buf), fb->iobase + reg);
+}
+
+static int chv3_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->sequence = 0;
+	writel(fb->pix_fmt.sizeimage, fb->iobase + FB_BUFFERSIZE);
+
+	spin_lock_irqsave(&fb->bufs_lock, flags);
+	buf = list_first_entry_or_null(&fb->bufs, struct chv3_fb_buffer, link);
+	if (buf) {
+		chv3_fb_start_frame(fb, buf);
+		if (!list_is_last(&buf->link, &fb->bufs))
+			chv3_fb_next_frame(fb, list_next_entry(buf, link));
+	}
+	spin_unlock_irqrestore(&fb->bufs_lock, flags);
+
+	return 0;
+}
+
+static void chv3_fb_stop_streaming(struct vb2_queue *q)
+{
+	struct chv3_fb *fb = vb2_get_drv_priv(q);
+	struct chv3_fb_buffer *buf;
+	unsigned long flags;
+
+	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 void chv3_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 = container_of(v4l2_buf, struct chv3_fb_buffer, vb);
+	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 (vb2_is_streaming(vb->vb2_queue)) {
+		if (first)
+			chv3_fb_start_frame(fb, buf);
+		else if (second)
+			chv3_fb_next_frame(fb, buf);
+	}
+	spin_unlock_irqrestore(&fb->bufs_lock, flags);
+}
+
+static const struct vb2_ops chv3_fb_vb2_ops = {
+	.queue_setup = chv3_fb_queue_setup,
+	.wait_prepare = vb2_ops_wait_prepare,
+	.wait_finish = vb2_ops_wait_finish,
+	.start_streaming = chv3_fb_start_streaming,
+	.stop_streaming = chv3_fb_stop_streaming,
+	.buf_queue = chv3_fb_buf_queue,
+};
+
+static int chv3_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))
+			chv3_fb_apply_dv_timings(fb);
+	}
+	mutex_unlock(&fb->fb_lock);
+
+	return res;
+}
+
+static const struct v4l2_file_operations chv3_fb_v4l2_fops = {
+	.owner = THIS_MODULE,
+	.open = chv3_fb_open,
+	.release = vb2_fop_release,
+	.unlocked_ioctl = video_ioctl2,
+	.mmap = vb2_fop_mmap,
+	.poll = vb2_fop_poll,
+};
+
+static void chv3_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->pix_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))
+			chv3_fb_next_frame(fb, list_next_entry(buf, link));
+	} else {
+		writel(0, fb->iobase + FB_EN);
+	}
+empty:
+	spin_unlock(&fb->bufs_lock);
+}
+
+static void chv3_fb_error_irq(struct chv3_fb *fb)
+{
+	if (vb2_is_streaming(&fb->queue))
+		vb2_queue_error(&fb->queue);
+}
+
+static void chv3_fb_resolution_irq(struct chv3_fb *fb)
+{
+	static const struct v4l2_event event = {
+		.type = V4L2_EVENT_SOURCE_CHANGE,
+		.u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION,
+	};
+
+	v4l2_event_queue(&fb->vdev, &event);
+	chv3_fb_error_irq(fb);
+}
+
+static irqreturn_t chv3_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)
+		chv3_fb_frame_irq(fb);
+	if (reg & FB_IRQ_BUFF1)
+		chv3_fb_frame_irq(fb);
+	if (reg & FB_IRQ_RESOLUTION)
+		chv3_fb_resolution_irq(fb);
+	if (reg & FB_IRQ_ERROR) {
+		dev_warn(fb->dev, "framebuffer error: 0x%x\n",
+			 readl(fb->iobase + FB_ERRORSTATUS));
+		chv3_fb_error_irq(fb);
+	}
+
+	writel(reg, fb->iobase_irq + FB_IRQ_CLR);
+
+	return IRQ_HANDLED;
+}
+
+static int chv3_fb_check_version(struct chv3_fb *fb)
+{
+	u32 version;
+
+	version = readl(fb->iobase + FB_VERSION);
+	if (version != FB_VERSION_CURRENT) {
+		dev_err(fb->dev,
+			"wrong framebuffer version: expected %x, got %x\n",
+			FB_VERSION_CURRENT, version);
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void chv3_fb_set_default_format(struct chv3_fb *fb, bool legacy_format)
+{
+	struct v4l2_pix_format *pix = &fb->pix_fmt;
+
+	if (legacy_format)
+		pix->pixelformat = V4L2_PIX_FMT_BGRX32;
+	else
+		pix->pixelformat = V4L2_PIX_FMT_RGB24;
+	pix->field = V4L2_FIELD_NONE;
+	pix->colorspace = V4L2_COLORSPACE_SRGB;
+
+	chv3_fb_set_format_resolution(fb, DEFAULT_WIDTH, DEFAULT_HEIGHT);
+}
+
+static void chv3_fb_set_default_timings(struct chv3_fb *fb)
+{
+	memset(&fb->timings, 0, sizeof(fb->timings));
+	fb->timings.type = V4L2_DV_BT_656_1120;
+	fb->timings.bt.width  = DEFAULT_WIDTH;
+	fb->timings.bt.height = DEFAULT_HEIGHT;
+}
+
+#define notifier_to_fb(nf) container_of(nf, struct chv3_fb, notifier)
+
+static int chv3_fb_async_notify_bound(struct v4l2_async_notifier *notifier,
+				      struct v4l2_subdev *subdev,
+				      struct v4l2_async_connection *asc)
+{
+	struct chv3_fb *fb = notifier_to_fb(notifier);
+	int pad;
+
+	pad = media_entity_get_fwnode_pad(&subdev->entity, asc->match.fwnode,
+					  MEDIA_PAD_FL_SOURCE);
+	if (pad < 0)
+		return pad;
+
+	fb->subdev = subdev;
+	fb->subdev_source_pad = pad;
+
+	return 0;
+}
+
+static void chv3_fb_async_notify_unbind(struct v4l2_async_notifier *notifier,
+					struct v4l2_subdev *subdev,
+					struct v4l2_async_connection *asc)
+{
+	struct chv3_fb *fb = notifier_to_fb(notifier);
+
+	video_unregister_device(&fb->vdev);
+}
+
+static int chv3_fb_async_notify_complete(struct v4l2_async_notifier *notifier)
+{
+	struct chv3_fb *fb = notifier_to_fb(notifier);
+
+	return video_register_device(&fb->vdev, VFL_TYPE_VIDEO, -1);
+}
+
+static const struct v4l2_async_notifier_operations chv3_fb_async_notify_ops = {
+	.bound = chv3_fb_async_notify_bound,
+	.unbind = chv3_fb_async_notify_unbind,
+	.complete = chv3_fb_async_notify_complete,
+};
+
+static int chv3_fb_fallback_init(struct chv3_fb *fb)
+{
+	fb->subdev = NULL;
+	fb->subdev_source_pad = 0;
+
+	return video_register_device(&fb->vdev, VFL_TYPE_VIDEO, -1);
+}
+
+static int chv3_fb_fwnode_init(struct chv3_fb *fb)
+{
+	struct v4l2_async_connection *asc;
+	struct fwnode_handle *endpoint;
+	int res;
+
+	endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(fb->dev), NULL);
+	if (!endpoint)
+		return -EINVAL;
+
+	v4l2_async_nf_init(&fb->notifier, &fb->v4l2_dev);
+
+	asc = v4l2_async_nf_add_fwnode_remote(&fb->notifier, endpoint,
+					      struct v4l2_async_connection);
+	fwnode_handle_put(endpoint);
+
+	if (IS_ERR(asc))
+		return PTR_ERR(asc);
+
+	fb->notifier.ops = &chv3_fb_async_notify_ops;
+	res = v4l2_async_nf_register(&fb->notifier);
+	if (res) {
+		v4l2_async_nf_cleanup(&fb->notifier);
+		return res;
+	}
+
+	return 0;
+}
+
+static int chv3_fb_probe(struct platform_device *pdev)
+{
+	struct chv3_fb *fb;
+	bool legacy_format;
+	bool no_endpoint;
+	int res;
+	int irq;
+
+	fb = devm_kzalloc(&pdev->dev, sizeof(*fb), GFP_KERNEL);
+	if (!fb)
+		return -ENOMEM;
+	fb->dev = &pdev->dev;
+	platform_set_drvdata(pdev, fb);
+
+	/* map register space */
+	fb->iobase = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(fb->iobase))
+		return PTR_ERR(fb->iobase);
+
+	fb->iobase_irq = devm_platform_ioremap_resource(pdev, 1);
+	if (IS_ERR(fb->iobase_irq))
+		return PTR_ERR(fb->iobase_irq);
+
+	/* check hw version */
+	res = chv3_fb_check_version(fb);
+	if (res)
+		return res;
+
+	/* setup interrupts */
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return -ENXIO;
+	res = devm_request_irq(&pdev->dev, irq, chv3_fb_isr, 0, DEVICE_NAME, fb);
+	if (res)
+		return res;
+
+	/* initialize v4l2_device */
+	res = v4l2_device_register(&pdev->dev, &fb->v4l2_dev);
+	if (res)
+		return res;
+
+	/* initialize vb2 queue */
+	fb->queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	fb->queue.io_modes = VB2_MMAP | VB2_DMABUF;
+	fb->queue.dev = &pdev->dev;
+	fb->queue.lock = &fb->fb_lock;
+	fb->queue.ops = &chv3_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)
+		goto error;
+
+	/* initialize video_device */
+	strscpy(fb->vdev.name, DEVICE_NAME, sizeof(fb->vdev.name));
+	fb->vdev.fops = &chv3_fb_v4l2_fops;
+	fb->vdev.ioctl_ops = &chv3_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 = &fb->v4l2_dev;
+	fb->vdev.queue = &fb->queue;
+	video_set_drvdata(&fb->vdev, fb);
+
+	/* read other DT properties */
+	legacy_format = device_property_read_bool(&pdev->dev, "google,legacy-format");
+	no_endpoint = device_property_read_bool(&pdev->dev, "google,no-endpoint");
+
+	if (no_endpoint)
+		res = chv3_fb_fallback_init(fb);
+	else
+		res = chv3_fb_fwnode_init(fb);
+	if (res)
+		goto error;
+
+	/* initialize rest of driver struct */
+	INIT_LIST_HEAD(&fb->bufs);
+	spin_lock_init(&fb->bufs_lock);
+	mutex_init(&fb->fb_lock);
+
+	chv3_fb_set_default_format(fb, legacy_format);
+	chv3_fb_set_default_timings(fb);
+
+	/* initialize hw */
+	writel(FB_RESET_BIT, fb->iobase + FB_RESET);
+	writel(FB_DATARATE_DOUBLE, fb->iobase + FB_DATARATE);
+	writel(FB_PIXELMODE_DOUBLE, fb->iobase + FB_PIXELMODE);
+	if (legacy_format)
+		writel(FB_DMAFORMAT_8BPC_LEGACY, fb->iobase + FB_DMAFORMAT);
+	else
+		writel(FB_DMAFORMAT_8BPC, fb->iobase + FB_DMAFORMAT);
+
+	writel(FB_IRQ_ALL, fb->iobase_irq + FB_IRQ_MASK);
+
+	return 0;
+
+error:
+	v4l2_device_unregister(&fb->v4l2_dev);
+
+	return res;
+}
+
+static void chv3_fb_remove(struct platform_device *pdev)
+{
+	struct chv3_fb *fb = platform_get_drvdata(pdev);
+
+	/* disable interrupts */
+	writel(0, fb->iobase_irq + FB_IRQ_MASK);
+
+	v4l2_async_nf_unregister(&fb->notifier);
+	v4l2_async_nf_cleanup(&fb->notifier);
+	v4l2_device_unregister(&fb->v4l2_dev);
+}
+
+static const struct of_device_id chv3_fb_match_table[] = {
+	{ .compatible = "google,chv3-fb" },
+	{ },
+};
+
+static struct platform_driver chv3_fb_platform_driver = {
+	.probe = chv3_fb_probe,
+	.remove_new = chv3_fb_remove,
+	.driver = {
+		.name = DEVICE_NAME,
+		.of_match_table = chv3_fb_match_table,
+	},
+};
+
+module_platform_driver(chv3_fb_platform_driver);
+
+MODULE_AUTHOR("Paweł Anikiel <panikiel@google.com>");
+MODULE_DESCRIPTION("Google Chameleon v3 video framebuffer driver");
+MODULE_LICENSE("GPL");
-- 
2.43.0.687.g38aa6559b0-goog


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH 3/9] drm/dp_mst: Move DRM-independent structures to separate header
  2024-02-12 13:13 [PATCH 0/9] Add Chameleon v3 video support Paweł Anikiel
  2024-02-12 13:13 ` [PATCH 1/9] media: v4l2-subdev: Add a pad variant of .query_dv_timings() Paweł Anikiel
  2024-02-12 13:13 ` [PATCH 2/9] media: Add Chameleon v3 framebuffer driver Paweł Anikiel
@ 2024-02-12 13:13 ` Paweł Anikiel
  2024-02-12 13:13 ` [PATCH 4/9] lib: Move DisplayPort CRC functions to common lib Paweł Anikiel
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 19+ messages in thread
From: Paweł Anikiel @ 2024-02-12 13:13 UTC (permalink / raw)
  To: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
	krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
	robh+dt, tzimmermann
  Cc: devicetree, dri-devel, linux-kernel, linux-media,
	chromeos-krk-upstreaming, ribalda, Paweł Anikiel

Move structures describing MST sideband messages into a separate header
so that non-DRM code can use them.

Signed-off-by: Paweł Anikiel <panikiel@google.com>
---
 include/drm/display/drm_dp_mst.h        | 238 ++++++++++++++++++++++++
 include/drm/display/drm_dp_mst_helper.h | 232 +----------------------
 2 files changed, 239 insertions(+), 231 deletions(-)
 create mode 100644 include/drm/display/drm_dp_mst.h

diff --git a/include/drm/display/drm_dp_mst.h b/include/drm/display/drm_dp_mst.h
new file mode 100644
index 000000000000..4e398bfd3ee3
--- /dev/null
+++ b/include/drm/display/drm_dp_mst.h
@@ -0,0 +1,238 @@
+/* SPDX-License-Identifier: MIT */
+
+#ifndef _DRM_DP_MST_H_
+#define _DRM_DP_MST_H_
+
+#include <linux/types.h>
+
+struct drm_dp_nak_reply {
+	u8 guid[16];
+	u8 reason;
+	u8 nak_data;
+};
+
+struct drm_dp_link_address_ack_reply {
+	u8 guid[16];
+	u8 nports;
+	struct drm_dp_link_addr_reply_port {
+		bool input_port;
+		u8 peer_device_type;
+		u8 port_number;
+		bool mcs;
+		bool ddps;
+		bool legacy_device_plug_status;
+		u8 dpcd_revision;
+		u8 peer_guid[16];
+		u8 num_sdp_streams;
+		u8 num_sdp_stream_sinks;
+	} ports[16];
+};
+
+struct drm_dp_remote_dpcd_read_ack_reply {
+	u8 port_number;
+	u8 num_bytes;
+	u8 bytes[255];
+};
+
+struct drm_dp_remote_dpcd_write_ack_reply {
+	u8 port_number;
+};
+
+struct drm_dp_remote_dpcd_write_nak_reply {
+	u8 port_number;
+	u8 reason;
+	u8 bytes_written_before_failure;
+};
+
+struct drm_dp_remote_i2c_read_ack_reply {
+	u8 port_number;
+	u8 num_bytes;
+	u8 bytes[255];
+};
+
+struct drm_dp_remote_i2c_read_nak_reply {
+	u8 port_number;
+	u8 nak_reason;
+	u8 i2c_nak_transaction;
+};
+
+struct drm_dp_remote_i2c_write_ack_reply {
+	u8 port_number;
+};
+
+struct drm_dp_query_stream_enc_status_ack_reply {
+	/* Bit[23:16]- Stream Id */
+	u8 stream_id;
+
+	/* Bit[15]- Signed */
+	bool reply_signed;
+
+	/* Bit[10:8]- Stream Output Sink Type */
+	bool unauthorizable_device_present;
+	bool legacy_device_present;
+	bool query_capable_device_present;
+
+	/* Bit[12:11]- Stream Output CP Type */
+	bool hdcp_1x_device_present;
+	bool hdcp_2x_device_present;
+
+	/* Bit[4]- Stream Authentication */
+	bool auth_completed;
+
+	/* Bit[3]- Stream Encryption */
+	bool encryption_enabled;
+
+	/* Bit[2]- Stream Repeater Function Present */
+	bool repeater_present;
+
+	/* Bit[1:0]- Stream State */
+	u8 state;
+};
+
+#define DRM_DP_MAX_SDP_STREAMS 16
+struct drm_dp_allocate_payload {
+	u8 port_number;
+	u8 number_sdp_streams;
+	u8 vcpi;
+	u16 pbn;
+	u8 sdp_stream_sink[DRM_DP_MAX_SDP_STREAMS];
+};
+
+struct drm_dp_allocate_payload_ack_reply {
+	u8 port_number;
+	u8 vcpi;
+	u16 allocated_pbn;
+};
+
+struct drm_dp_connection_status_notify {
+	u8 guid[16];
+	u8 port_number;
+	bool legacy_device_plug_status;
+	bool displayport_device_plug_status;
+	bool message_capability_status;
+	bool input_port;
+	u8 peer_device_type;
+};
+
+struct drm_dp_remote_dpcd_read {
+	u8 port_number;
+	u32 dpcd_address;
+	u8 num_bytes;
+};
+
+struct drm_dp_remote_dpcd_write {
+	u8 port_number;
+	u32 dpcd_address;
+	u8 num_bytes;
+	u8 *bytes;
+};
+
+#define DP_REMOTE_I2C_READ_MAX_TRANSACTIONS 4
+struct drm_dp_remote_i2c_read {
+	u8 num_transactions;
+	u8 port_number;
+	struct drm_dp_remote_i2c_read_tx {
+		u8 i2c_dev_id;
+		u8 num_bytes;
+		u8 *bytes;
+		u8 no_stop_bit;
+		u8 i2c_transaction_delay;
+	} transactions[DP_REMOTE_I2C_READ_MAX_TRANSACTIONS];
+	u8 read_i2c_device_id;
+	u8 num_bytes_read;
+};
+
+struct drm_dp_remote_i2c_write {
+	u8 port_number;
+	u8 write_i2c_device_id;
+	u8 num_bytes;
+	u8 *bytes;
+};
+
+struct drm_dp_query_stream_enc_status {
+	u8 stream_id;
+	u8 client_id[7];	/* 56-bit nonce */
+	u8 stream_event;
+	bool valid_stream_event;
+	u8 stream_behavior;
+	u8 valid_stream_behavior;
+};
+
+/* this covers ENUM_RESOURCES, POWER_DOWN_PHY, POWER_UP_PHY */
+struct drm_dp_port_number_req {
+	u8 port_number;
+};
+
+struct drm_dp_enum_path_resources_ack_reply {
+	u8 port_number;
+	bool fec_capable;
+	u16 full_payload_bw_number;
+	u16 avail_payload_bw_number;
+};
+
+/* covers POWER_DOWN_PHY, POWER_UP_PHY */
+struct drm_dp_port_number_rep {
+	u8 port_number;
+};
+
+struct drm_dp_query_payload {
+	u8 port_number;
+	u8 vcpi;
+};
+
+struct drm_dp_resource_status_notify {
+	u8 port_number;
+	u8 guid[16];
+	u16 available_pbn;
+};
+
+struct drm_dp_query_payload_ack_reply {
+	u8 port_number;
+	u16 allocated_pbn;
+};
+
+struct drm_dp_sideband_msg_req_body {
+	u8 req_type;
+	union ack_req {
+		struct drm_dp_connection_status_notify conn_stat;
+		struct drm_dp_port_number_req port_num;
+		struct drm_dp_resource_status_notify resource_stat;
+
+		struct drm_dp_query_payload query_payload;
+		struct drm_dp_allocate_payload allocate_payload;
+
+		struct drm_dp_remote_dpcd_read dpcd_read;
+		struct drm_dp_remote_dpcd_write dpcd_write;
+
+		struct drm_dp_remote_i2c_read i2c_read;
+		struct drm_dp_remote_i2c_write i2c_write;
+
+		struct drm_dp_query_stream_enc_status enc_status;
+	} u;
+};
+
+struct drm_dp_sideband_msg_reply_body {
+	u8 reply_type;
+	u8 req_type;
+	union ack_replies {
+		struct drm_dp_nak_reply nak;
+		struct drm_dp_link_address_ack_reply link_addr;
+		struct drm_dp_port_number_rep port_number;
+
+		struct drm_dp_enum_path_resources_ack_reply path_resources;
+		struct drm_dp_allocate_payload_ack_reply allocate_payload;
+		struct drm_dp_query_payload_ack_reply query_payload;
+
+		struct drm_dp_remote_dpcd_read_ack_reply remote_dpcd_read_ack;
+		struct drm_dp_remote_dpcd_write_ack_reply remote_dpcd_write_ack;
+		struct drm_dp_remote_dpcd_write_nak_reply remote_dpcd_write_nack;
+
+		struct drm_dp_remote_i2c_read_ack_reply remote_i2c_read_ack;
+		struct drm_dp_remote_i2c_read_nak_reply remote_i2c_read_nack;
+		struct drm_dp_remote_i2c_write_ack_reply remote_i2c_write_ack;
+
+		struct drm_dp_query_stream_enc_status_ack_reply enc_status;
+	} u;
+};
+
+#endif
diff --git a/include/drm/display/drm_dp_mst_helper.h b/include/drm/display/drm_dp_mst_helper.h
index 9b19d8bd520a..61add6f6accd 100644
--- a/include/drm/display/drm_dp_mst_helper.h
+++ b/include/drm/display/drm_dp_mst_helper.h
@@ -23,6 +23,7 @@
 #define _DRM_DP_MST_HELPER_H_
 
 #include <linux/types.h>
+#include <drm/display/drm_dp_mst.h>
 #include <drm/display/drm_dp_helper.h>
 #include <drm/drm_atomic.h>
 #include <drm/drm_fixed.h>
@@ -248,237 +249,6 @@ struct drm_dp_mst_branch {
 	u8 guid[16];
 };
 
-
-struct drm_dp_nak_reply {
-	u8 guid[16];
-	u8 reason;
-	u8 nak_data;
-};
-
-struct drm_dp_link_address_ack_reply {
-	u8 guid[16];
-	u8 nports;
-	struct drm_dp_link_addr_reply_port {
-		bool input_port;
-		u8 peer_device_type;
-		u8 port_number;
-		bool mcs;
-		bool ddps;
-		bool legacy_device_plug_status;
-		u8 dpcd_revision;
-		u8 peer_guid[16];
-		u8 num_sdp_streams;
-		u8 num_sdp_stream_sinks;
-	} ports[16];
-};
-
-struct drm_dp_remote_dpcd_read_ack_reply {
-	u8 port_number;
-	u8 num_bytes;
-	u8 bytes[255];
-};
-
-struct drm_dp_remote_dpcd_write_ack_reply {
-	u8 port_number;
-};
-
-struct drm_dp_remote_dpcd_write_nak_reply {
-	u8 port_number;
-	u8 reason;
-	u8 bytes_written_before_failure;
-};
-
-struct drm_dp_remote_i2c_read_ack_reply {
-	u8 port_number;
-	u8 num_bytes;
-	u8 bytes[255];
-};
-
-struct drm_dp_remote_i2c_read_nak_reply {
-	u8 port_number;
-	u8 nak_reason;
-	u8 i2c_nak_transaction;
-};
-
-struct drm_dp_remote_i2c_write_ack_reply {
-	u8 port_number;
-};
-
-struct drm_dp_query_stream_enc_status_ack_reply {
-	/* Bit[23:16]- Stream Id */
-	u8 stream_id;
-
-	/* Bit[15]- Signed */
-	bool reply_signed;
-
-	/* Bit[10:8]- Stream Output Sink Type */
-	bool unauthorizable_device_present;
-	bool legacy_device_present;
-	bool query_capable_device_present;
-
-	/* Bit[12:11]- Stream Output CP Type */
-	bool hdcp_1x_device_present;
-	bool hdcp_2x_device_present;
-
-	/* Bit[4]- Stream Authentication */
-	bool auth_completed;
-
-	/* Bit[3]- Stream Encryption */
-	bool encryption_enabled;
-
-	/* Bit[2]- Stream Repeater Function Present */
-	bool repeater_present;
-
-	/* Bit[1:0]- Stream State */
-	u8 state;
-};
-
-#define DRM_DP_MAX_SDP_STREAMS 16
-struct drm_dp_allocate_payload {
-	u8 port_number;
-	u8 number_sdp_streams;
-	u8 vcpi;
-	u16 pbn;
-	u8 sdp_stream_sink[DRM_DP_MAX_SDP_STREAMS];
-};
-
-struct drm_dp_allocate_payload_ack_reply {
-	u8 port_number;
-	u8 vcpi;
-	u16 allocated_pbn;
-};
-
-struct drm_dp_connection_status_notify {
-	u8 guid[16];
-	u8 port_number;
-	bool legacy_device_plug_status;
-	bool displayport_device_plug_status;
-	bool message_capability_status;
-	bool input_port;
-	u8 peer_device_type;
-};
-
-struct drm_dp_remote_dpcd_read {
-	u8 port_number;
-	u32 dpcd_address;
-	u8 num_bytes;
-};
-
-struct drm_dp_remote_dpcd_write {
-	u8 port_number;
-	u32 dpcd_address;
-	u8 num_bytes;
-	u8 *bytes;
-};
-
-#define DP_REMOTE_I2C_READ_MAX_TRANSACTIONS 4
-struct drm_dp_remote_i2c_read {
-	u8 num_transactions;
-	u8 port_number;
-	struct drm_dp_remote_i2c_read_tx {
-		u8 i2c_dev_id;
-		u8 num_bytes;
-		u8 *bytes;
-		u8 no_stop_bit;
-		u8 i2c_transaction_delay;
-	} transactions[DP_REMOTE_I2C_READ_MAX_TRANSACTIONS];
-	u8 read_i2c_device_id;
-	u8 num_bytes_read;
-};
-
-struct drm_dp_remote_i2c_write {
-	u8 port_number;
-	u8 write_i2c_device_id;
-	u8 num_bytes;
-	u8 *bytes;
-};
-
-struct drm_dp_query_stream_enc_status {
-	u8 stream_id;
-	u8 client_id[7];	/* 56-bit nonce */
-	u8 stream_event;
-	bool valid_stream_event;
-	u8 stream_behavior;
-	u8 valid_stream_behavior;
-};
-
-/* this covers ENUM_RESOURCES, POWER_DOWN_PHY, POWER_UP_PHY */
-struct drm_dp_port_number_req {
-	u8 port_number;
-};
-
-struct drm_dp_enum_path_resources_ack_reply {
-	u8 port_number;
-	bool fec_capable;
-	u16 full_payload_bw_number;
-	u16 avail_payload_bw_number;
-};
-
-/* covers POWER_DOWN_PHY, POWER_UP_PHY */
-struct drm_dp_port_number_rep {
-	u8 port_number;
-};
-
-struct drm_dp_query_payload {
-	u8 port_number;
-	u8 vcpi;
-};
-
-struct drm_dp_resource_status_notify {
-	u8 port_number;
-	u8 guid[16];
-	u16 available_pbn;
-};
-
-struct drm_dp_query_payload_ack_reply {
-	u8 port_number;
-	u16 allocated_pbn;
-};
-
-struct drm_dp_sideband_msg_req_body {
-	u8 req_type;
-	union ack_req {
-		struct drm_dp_connection_status_notify conn_stat;
-		struct drm_dp_port_number_req port_num;
-		struct drm_dp_resource_status_notify resource_stat;
-
-		struct drm_dp_query_payload query_payload;
-		struct drm_dp_allocate_payload allocate_payload;
-
-		struct drm_dp_remote_dpcd_read dpcd_read;
-		struct drm_dp_remote_dpcd_write dpcd_write;
-
-		struct drm_dp_remote_i2c_read i2c_read;
-		struct drm_dp_remote_i2c_write i2c_write;
-
-		struct drm_dp_query_stream_enc_status enc_status;
-	} u;
-};
-
-struct drm_dp_sideband_msg_reply_body {
-	u8 reply_type;
-	u8 req_type;
-	union ack_replies {
-		struct drm_dp_nak_reply nak;
-		struct drm_dp_link_address_ack_reply link_addr;
-		struct drm_dp_port_number_rep port_number;
-
-		struct drm_dp_enum_path_resources_ack_reply path_resources;
-		struct drm_dp_allocate_payload_ack_reply allocate_payload;
-		struct drm_dp_query_payload_ack_reply query_payload;
-
-		struct drm_dp_remote_dpcd_read_ack_reply remote_dpcd_read_ack;
-		struct drm_dp_remote_dpcd_write_ack_reply remote_dpcd_write_ack;
-		struct drm_dp_remote_dpcd_write_nak_reply remote_dpcd_write_nack;
-
-		struct drm_dp_remote_i2c_read_ack_reply remote_i2c_read_ack;
-		struct drm_dp_remote_i2c_read_nak_reply remote_i2c_read_nack;
-		struct drm_dp_remote_i2c_write_ack_reply remote_i2c_write_ack;
-
-		struct drm_dp_query_stream_enc_status_ack_reply enc_status;
-	} u;
-};
-
 /* msg is queued to be put into a slot */
 #define DRM_DP_SIDEBAND_TX_QUEUED 0
 /* msg has started transmitting on a slot - still on msgq */
-- 
2.43.0.687.g38aa6559b0-goog


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH 4/9] lib: Move DisplayPort CRC functions to common lib
  2024-02-12 13:13 [PATCH 0/9] Add Chameleon v3 video support Paweł Anikiel
                   ` (2 preceding siblings ...)
  2024-02-12 13:13 ` [PATCH 3/9] drm/dp_mst: Move DRM-independent structures to separate header Paweł Anikiel
@ 2024-02-12 13:13 ` Paweł Anikiel
  2024-02-12 13:13 ` [PATCH 5/9] drm/display: Add mask definitions for DP_PAYLOAD_ALLOCATE_* registers Paweł Anikiel
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 19+ messages in thread
From: Paweł Anikiel @ 2024-02-12 13:13 UTC (permalink / raw)
  To: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
	krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
	robh+dt, tzimmermann
  Cc: devicetree, dri-devel, linux-kernel, linux-media,
	chromeos-krk-upstreaming, ribalda, Paweł Anikiel

The CRC functions found in drivers/gpu/drm/display/drm_dp_mst_topology.c
may be useful for other non-DRM code that deals with DisplayPort, e.g.
v4l2 drivers for DP receivers. Move these functions to /lib.

Signed-off-by: Paweł Anikiel <panikiel@google.com>
---
 drivers/gpu/drm/display/Kconfig               |  1 +
 drivers/gpu/drm/display/drm_dp_mst_topology.c | 76 ++----------------
 include/linux/crc-dp.h                        | 10 +++
 lib/Kconfig                                   |  8 ++
 lib/Makefile                                  |  1 +
 lib/crc-dp.c                                  | 78 +++++++++++++++++++
 6 files changed, 103 insertions(+), 71 deletions(-)
 create mode 100644 include/linux/crc-dp.h
 create mode 100644 lib/crc-dp.c

diff --git a/drivers/gpu/drm/display/Kconfig b/drivers/gpu/drm/display/Kconfig
index 09712b88a5b8..c615f50152f2 100644
--- a/drivers/gpu/drm/display/Kconfig
+++ b/drivers/gpu/drm/display/Kconfig
@@ -14,6 +14,7 @@ config DRM_DISPLAY_HELPER
 config DRM_DISPLAY_DP_HELPER
 	bool
 	depends on DRM_DISPLAY_HELPER
+	select CRC_DP
 	help
 	  DRM display helpers for DisplayPort.
 
diff --git a/drivers/gpu/drm/display/drm_dp_mst_topology.c b/drivers/gpu/drm/display/drm_dp_mst_topology.c
index f7c6b60629c2..ada1f90fa808 100644
--- a/drivers/gpu/drm/display/drm_dp_mst_topology.c
+++ b/drivers/gpu/drm/display/drm_dp_mst_topology.c
@@ -22,6 +22,7 @@
 
 #include <linux/bitfield.h>
 #include <linux/delay.h>
+#include <linux/crc-dp.h>
 #include <linux/errno.h>
 #include <linux/i2c.h>
 #include <linux/init.h>
@@ -195,73 +196,6 @@ drm_dp_mst_rad_to_str(const u8 rad[8], u8 lct, char *out, size_t len)
 }
 
 /* sideband msg handling */
-static u8 drm_dp_msg_header_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 drm_dp_msg_data_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;
-}
 static inline u8 drm_dp_calc_sb_hdr_size(struct drm_dp_sideband_msg_hdr *hdr)
 {
 	u8 size = 3;
@@ -284,7 +218,7 @@ static void drm_dp_encode_sideband_msg_hdr(struct drm_dp_sideband_msg_hdr *hdr,
 		(hdr->msg_len & 0x3f);
 	buf[idx++] = (hdr->somt << 7) | (hdr->eomt << 6) | (hdr->seqno << 4);
 
-	crc4 = drm_dp_msg_header_crc4(buf, (idx * 2) - 1);
+	crc4 = crc_dp_msg_header(buf, (idx * 2) - 1);
 	buf[idx - 1] |= (crc4 & 0xf);
 
 	*len = idx;
@@ -305,7 +239,7 @@ static bool drm_dp_decode_sideband_msg_hdr(const struct drm_dp_mst_topology_mgr
 	len += ((buf[0] & 0xf0) >> 4) / 2;
 	if (len > buflen)
 		return false;
-	crc4 = drm_dp_msg_header_crc4(buf, (len * 2) - 1);
+	crc4 = crc_dp_msg_header(buf, (len * 2) - 1);
 
 	if ((crc4 & 0xf) != (buf[len - 1] & 0xf)) {
 		drm_dbg_kms(mgr->dev, "crc4 mismatch 0x%x 0x%x\n", crc4, buf[len - 1]);
@@ -725,7 +659,7 @@ static void drm_dp_crc_sideband_chunk_req(u8 *msg, u8 len)
 {
 	u8 crc4;
 
-	crc4 = drm_dp_msg_data_crc4(msg, len);
+	crc4 = crc_dp_msg_data(msg, len);
 	msg[len] = crc4;
 }
 
@@ -782,7 +716,7 @@ static bool drm_dp_sideband_append_payload(struct drm_dp_sideband_msg_rx *msg,
 
 	if (msg->curchunk_idx >= msg->curchunk_len) {
 		/* do CRC */
-		crc4 = drm_dp_msg_data_crc4(msg->chunk, msg->curchunk_len - 1);
+		crc4 = crc_dp_msg_data(msg->chunk, msg->curchunk_len - 1);
 		if (crc4 != msg->chunk[msg->curchunk_len - 1])
 			print_hex_dump(KERN_DEBUG, "wrong crc",
 				       DUMP_PREFIX_NONE, 16, 1,
diff --git a/include/linux/crc-dp.h b/include/linux/crc-dp.h
new file mode 100644
index 000000000000..b63435c82b96
--- /dev/null
+++ b/include/linux/crc-dp.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_CRC_DP_H
+#define _LINUX_CRC_DP_H
+
+#include <linux/types.h>
+
+u8 crc_dp_msg_header(const uint8_t *data, size_t num_nibbles);
+u8 crc_dp_msg_data(const uint8_t *data, u8 number_of_bytes);
+
+#endif /* _LINUX_CRC_DP_H */
diff --git a/lib/Kconfig b/lib/Kconfig
index 5ddda7c2ed9b..28f9f6cfec9f 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -171,6 +171,14 @@ config CRC_ITU_T
 	  the kernel tree does. Such modules that use library CRC ITU-T V.41
 	  functions require M here.
 
+config CRC_DP
+	tristate "CRC DisplayPort MST functions"
+	help
+	  This option is provided for the case where no in-kernel-tree
+	  modules require CRC DisplayPort MST functions, but a module built outside
+	  the kernel tree does. Such modules that use library CRC DisplayPort MST
+	  functions require M here.
+
 config CRC32
 	tristate "CRC32/CRC32c functions"
 	default y
diff --git a/lib/Makefile b/lib/Makefile
index 6b09731d8e61..e4d7ffa260b3 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -188,6 +188,7 @@ obj-$(CONFIG_CRC7)	+= crc7.o
 obj-$(CONFIG_LIBCRC32C)	+= libcrc32c.o
 obj-$(CONFIG_CRC8)	+= crc8.o
 obj-$(CONFIG_CRC64_ROCKSOFT) += crc64-rocksoft.o
+obj-$(CONFIG_CRC_DP)	+= crc-dp.o
 obj-$(CONFIG_XXHASH)	+= xxhash.o
 obj-$(CONFIG_GENERIC_ALLOCATOR) += genalloc.o
 
diff --git a/lib/crc-dp.c b/lib/crc-dp.c
new file mode 100644
index 000000000000..95b58bc436d4
--- /dev/null
+++ b/lib/crc-dp.c
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/crc-dp.h>
+
+/*
+ * Sideband MSG Header CRC
+ * Defined in DisplayPort 1.2 spec, section 2.11.3.1.9
+ */
+u8 crc_dp_msg_header(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;
+}
+
+/*
+ * Sideband MSG Data CRC
+ * Defined in DisplayPort 1.2 spec, section 2.11.3.2.2
+ */
+u8 crc_dp_msg_data(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;
+}
-- 
2.43.0.687.g38aa6559b0-goog


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH 5/9] drm/display: Add mask definitions for DP_PAYLOAD_ALLOCATE_* registers
  2024-02-12 13:13 [PATCH 0/9] Add Chameleon v3 video support Paweł Anikiel
                   ` (3 preceding siblings ...)
  2024-02-12 13:13 ` [PATCH 4/9] lib: Move DisplayPort CRC functions to common lib Paweł Anikiel
@ 2024-02-12 13:13 ` Paweł Anikiel
  2024-02-12 13:13 ` [PATCH 6/9] media: intel: Add Displayport RX IP driver Paweł Anikiel
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 19+ messages in thread
From: Paweł Anikiel @ 2024-02-12 13:13 UTC (permalink / raw)
  To: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
	krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
	robh+dt, tzimmermann
  Cc: devicetree, dri-devel, linux-kernel, linux-media,
	chromeos-krk-upstreaming, ribalda, Paweł Anikiel

Each of these registers contains a single value, but not the entire
8 bits:

DP_PAYLOAD_ALLOCATE_SET - Bit 7 Reserved
DP_PAYLOAD_ALLOCATE_START_TIME_SLOT - Bits 7:6 Reserved
DP_PAYLOAD_ALLOCATE_TIME_SLOT_COUNT - Bits 7:6 Reserved

Add definitions to properly mask off values read from these registers.

Signed-off-by: Paweł Anikiel <panikiel@google.com>
---
 include/drm/display/drm_dp.h | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/include/drm/display/drm_dp.h b/include/drm/display/drm_dp.h
index 3731828825bd..9dee30190f14 100644
--- a/include/drm/display/drm_dp.h
+++ b/include/drm/display/drm_dp.h
@@ -733,8 +733,13 @@
 # define DP_PANEL_REPLAY_SU_ENABLE                      (1 << 6)
 
 #define DP_PAYLOAD_ALLOCATE_SET		    0x1c0
-#define DP_PAYLOAD_ALLOCATE_START_TIME_SLOT 0x1c1
-#define DP_PAYLOAD_ALLOCATE_TIME_SLOT_COUNT 0x1c2
+# define DP_PAYLOAD_ALLOCATE_SET_MASK	    0x7f
+
+#define DP_PAYLOAD_ALLOCATE_START_TIME_SLOT		0x1c1
+# define DP_PAYLOAD_ALLOCATE_START_TIME_SLOT_MASK	0x3f
+
+#define DP_PAYLOAD_ALLOCATE_TIME_SLOT_COUNT		0x1c2
+# define DP_PAYLOAD_ALLOCATE_TIME_SLOT_COUNT_MASK	0x3f
 
 /* Link/Sink Device Status */
 #define DP_SINK_COUNT			    0x200
-- 
2.43.0.687.g38aa6559b0-goog


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH 6/9] media: intel: Add Displayport RX IP driver
  2024-02-12 13:13 [PATCH 0/9] Add Chameleon v3 video support Paweł Anikiel
                   ` (4 preceding siblings ...)
  2024-02-12 13:13 ` [PATCH 5/9] drm/display: Add mask definitions for DP_PAYLOAD_ALLOCATE_* registers Paweł Anikiel
@ 2024-02-12 13:13 ` Paweł Anikiel
  2024-02-12 13:13 ` [PATCH 7/9] media: dt-bindings: Add Chameleon v3 framebuffer Paweł Anikiel
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 19+ messages in thread
From: Paweł Anikiel @ 2024-02-12 13:13 UTC (permalink / raw)
  To: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
	krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
	robh+dt, tzimmermann
  Cc: devicetree, dri-devel, linux-kernel, linux-media,
	chromeos-krk-upstreaming, ribalda, Paweł Anikiel

Add driver for the Intel DisplayPort RX FPGA IP

Signed-off-by: Paweł Anikiel <panikiel@google.com>
---
 drivers/media/platform/intel/Kconfig      |   12 +
 drivers/media/platform/intel/Makefile     |    1 +
 drivers/media/platform/intel/intel-dprx.c | 2171 +++++++++++++++++++++
 3 files changed, 2184 insertions(+)
 create mode 100644 drivers/media/platform/intel/intel-dprx.c

diff --git a/drivers/media/platform/intel/Kconfig b/drivers/media/platform/intel/Kconfig
index 724e80a9086d..eafcd47cce68 100644
--- a/drivers/media/platform/intel/Kconfig
+++ b/drivers/media/platform/intel/Kconfig
@@ -12,3 +12,15 @@ config VIDEO_PXA27x
 	select V4L2_FWNODE
 	help
 	  This is a v4l2 driver for the PXA27x Quick Capture Interface
+
+config VIDEO_INTEL_DPRX
+	tristate "Intel DisplayPort RX IP driver"
+	depends on V4L_PLATFORM_DRIVERS
+	depends on VIDEO_DEV
+	select V4L2_FWNODE
+	select CRC_DP
+	help
+	  v4l2 subdev driver for Intel Displayport receiver FPGA IP.
+	  It is a part of the DisplayPort Intel FPGA IP Core.
+	  It implements a DisplayPort 1.4 receiver capable of HBR3
+	  video capture and Multi-Stream Transport.
diff --git a/drivers/media/platform/intel/Makefile b/drivers/media/platform/intel/Makefile
index 7e8889cbd2df..f571399f5aa8 100644
--- a/drivers/media/platform/intel/Makefile
+++ b/drivers/media/platform/intel/Makefile
@@ -1,2 +1,3 @@
 # SPDX-License-Identifier: GPL-2.0-only
 obj-$(CONFIG_VIDEO_PXA27x) += pxa_camera.o
+obj-$(CONFIG_VIDEO_INTEL_DPRX) += intel-dprx.o
diff --git a/drivers/media/platform/intel/intel-dprx.c b/drivers/media/platform/intel/intel-dprx.c
new file mode 100644
index 000000000000..88998dc218c1
--- /dev/null
+++ b/drivers/media/platform/intel/intel-dprx.c
@@ -0,0 +1,2171 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2023-2024 Google LLC.
+ * Author: Paweł Anikiel <panikiel@google.com>
+ */
+
+#include <linux/crc-dp.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <media/v4l2-dv-timings.h>
+#include <media/v4l2-subdev.h>
+#include <drm/display/drm_dp.h>
+#include <drm/display/drm_dp_mst.h>
+
+#define DPRX_MAX_EDID_BLOCKS	4
+#define DPRX_MAX_LANE_COUNT	4
+#define DPRX_MAX_LINK_RATE	DP_LINK_BW_8_1
+
+/* DPRX registers */
+
+#define DPRX_RX_CONTROL				0x000
+#define DPRX_RX_CONTROL_LINK_RATE_SHIFT		16
+#define DPRX_RX_CONTROL_LINK_RATE_MASK		0xff
+#define DPRX_RX_CONTROL_RECONFIG_LINKRATE	13
+#define DPRX_RX_CONTROL_TP_SHIFT		8
+#define DPRX_RX_CONTROL_TP_MASK			0x7
+#define DPRX_RX_CONTROL_SCRAMBLER_DISABLE	7
+#define DPRX_RX_CONTROL_CHANNEL_CODING_SHIFT	5
+#define DPRX_RX_CONTROL_CHANNEL_CODING_8B10B	0x1
+#define DPRX_RX_CONTROL_LANE_COUNT_SHIFT	0
+#define DPRX_RX_CONTROL_LANE_COUNT_MASK		0x1f
+
+#define DPRX_RX_STATUS				0x001
+#define DPRX_RX_STATUS_INTERLANE_ALIGN		8
+#define DPRX_RX_STATUS_SYM_LOCK_SHIFT		4
+#define DPRX_RX_STATUS_SYM_LOCK(i)		(4 + i)
+#define DPRX_RX_STATUS_CR_LOCK_SHIFT		0
+#define DPRX_RX_STATUS_CR_LOCK(i)		(0 + i)
+
+#define DPRX_MSA_HTOTAL(i)			(0x022 + 0x20 * (i))
+#define DPRX_MSA_VTOTAL(i)			(0x023 + 0x20 * (i))
+#define DPRX_MSA_HSP(i)				(0x024 + 0x20 * (i))
+#define DPRX_MSA_HSW(i)				(0x025 + 0x20 * (i))
+#define DPRX_MSA_HSTART(i)			(0x026 + 0x20 * (i))
+#define DPRX_MSA_VSTART(i)			(0x027 + 0x20 * (i))
+#define DPRX_MSA_VSP(i)				(0x028 + 0x20 * (i))
+#define DPRX_MSA_VSW(i)				(0x029 + 0x20 * (i))
+#define DPRX_MSA_HWIDTH(i)			(0x02a + 0x20 * (i))
+#define DPRX_MSA_VHEIGHT(i)			(0x02b + 0x20 * (i))
+#define DPRX_VBID(i)				(0x02f + 0x20 * (i))
+#define DPRX_VBID_MSA_LOCK			7
+
+#define DPRX_MST_CONTROL1			0x0a0
+#define DPRX_MST_CONTROL1_VCPTAB_UPD_FORCE	31
+#define DPRX_MST_CONTROL1_VCPTAB_UPD_REQ	30
+#define DPRX_MST_CONTROL1_VCP_ID_SHIFT(i)	(4 + 4 * (i))
+#define DPRX_MST_CONTROL1_VCP_IDS_SHIFT		4
+#define DPRX_MST_CONTROL1_VCP_IDS_MASK		0xffff
+#define DPRX_MST_CONTROL1_MST_EN		0
+
+#define DPRX_MST_STATUS1			0x0a1
+#define DPRX_MST_STATUS1_VCPTAB_ACT_ACK		30
+
+#define DPRX_MST_VCPTAB(i)			(0x0a2 + i)
+
+#define DPRX_AUX_CONTROL			0x100
+#define DPRX_AUX_CONTROL_IRQ_EN			8
+#define DPRX_AUX_CONTROL_TX_STROBE		7
+#define DPRX_AUX_CONTROL_LENGTH_SHIFT		0
+#define DPRX_AUX_CONTROL_LENGTH_MASK		0x1f
+
+#define DPRX_AUX_STATUS				0x101
+#define DPRX_AUX_STATUS_MSG_READY		31
+#define DPRX_AUX_STATUS_READY_TO_TX		30
+
+#define DPRX_AUX_COMMAND			0x102
+
+#define DPRX_AUX_HPD				0x119
+#define DPRX_AUX_HPD_IRQ			12
+#define DPRX_AUX_HPD_EN				11
+
+/* IRQ registers */
+
+#define DPRX_IRQ_MASK	0x8
+#define DPRX_IRQ_CLR	0xc
+#define DPRX_IRQ_AUX	0x1
+
+/* DDC defines */
+
+#define DDC_EDID_ADDR		0x50
+#define DDC_SEGMENT_ADDR	0x30
+
+struct dprx_training_control {
+	u8 volt_swing;
+	u8 pre_emph;
+	bool max_swing;
+	bool max_pre_emph;
+};
+
+struct dprx_sink {
+	u8 edid[128 * DPRX_MAX_EDID_BLOCKS];
+	int blocks;
+	int offset;
+	int segment;
+};
+
+struct msg_transaction_rxbuf {
+	u8 buf[256];
+	int len;
+};
+
+struct msg_transaction_txbuf {
+	u8 buf[256];
+	int len;
+	int written;
+};
+
+struct msg_transaction_meta {
+	u8 lct;
+	u8 rad[8];
+	bool seqno;
+};
+
+struct dprx {
+	struct device *dev;
+	void __iomem *iobase;
+	void __iomem *iobase_irq;
+
+	struct v4l2_subdev subdev;
+	struct media_pad pads[4];
+
+	struct dprx_sink sinks[4];
+
+	bool has_mst;
+	u8 sink_count;
+
+	u8 guid[16];
+
+	struct dprx_training_control training_control[4];
+
+	u8 payload_allocate_set;
+	u8 payload_allocate_start_time_slot;
+	u8 payload_allocate_time_slot_count;
+	u8 payload_table[64];
+	u8 payload_table_updated;
+
+	u8  payload_id[4];
+	u32 payload_pbn[4];
+	u32 payload_pbn_total;
+
+	u8 irq_vector;
+
+	u8 down_req_buf[48];
+	u8 down_rep_buf[48];
+
+	struct msg_transaction_rxbuf mt_rxbuf[2];
+	struct msg_transaction_txbuf mt_txbuf[2];
+	struct msg_transaction_meta mt_meta[2];
+	bool mt_seqno;
+	bool mt_pending;
+	bool down_rep_pending;
+
+	spinlock_t lock;
+};
+
+struct aux_buf {
+	u8 data[20];
+	int len;
+};
+
+struct aux_msg {
+	u8 cmd;
+	u32 addr;
+	u8 len;
+	u8 data[16];
+};
+
+struct sideband_msg {
+	u8 lct;
+	u8 lcr;
+	u8 rad[8];
+	bool broadcast;
+	bool path_msg;
+	bool somt;
+	bool eomt;
+	bool seqno;
+
+	u8 body[48];
+	u8 body_len;
+};
+
+static void dprx_write(struct dprx *dprx, int addr, u32 val)
+{
+	writel(val, dprx->iobase + (addr * 4));
+}
+
+static u32 dprx_read(struct dprx *dprx, int addr)
+{
+	return readl(dprx->iobase + (addr * 4));
+}
+
+static void dprx_set_irq(struct dprx *dprx, int val)
+{
+	u32 reg;
+
+	reg = dprx_read(dprx, DPRX_AUX_CONTROL);
+	reg |= ~(1 << DPRX_AUX_CONTROL_IRQ_EN);
+	reg |= val << DPRX_AUX_CONTROL_IRQ_EN;
+	dprx_write(dprx, DPRX_AUX_CONTROL, reg);
+}
+
+static void dprx_set_hpd(struct dprx *dprx, int val)
+{
+	u32 reg;
+
+	reg = dprx_read(dprx, DPRX_AUX_HPD);
+	reg &= ~(1 << DPRX_AUX_HPD_EN);
+	reg |= val << DPRX_AUX_HPD_EN;
+	dprx_write(dprx, DPRX_AUX_HPD, reg);
+}
+
+static void dprx_pulse_hpd(struct dprx *dprx)
+{
+	u32 reg;
+
+	reg = dprx_read(dprx, DPRX_AUX_HPD);
+	reg |= 1 << DPRX_AUX_HPD_IRQ;
+	dprx_write(dprx, DPRX_AUX_HPD, reg);
+}
+
+static void dprx_clear_vc_payload_table(struct dprx *dprx)
+{
+	u32 reg;
+	int i;
+
+	memset(dprx->payload_table, 0, sizeof(dprx->payload_table));
+
+	for (i = 0; i < 8; i++)
+		dprx_write(dprx, DPRX_MST_VCPTAB(i), 0);
+
+	reg = dprx_read(dprx, DPRX_MST_CONTROL1);
+	reg &= ~(DPRX_MST_CONTROL1_VCP_IDS_MASK << DPRX_MST_CONTROL1_VCP_IDS_SHIFT);
+	reg |= 1 << DPRX_MST_CONTROL1_VCPTAB_UPD_FORCE;
+	dprx_write(dprx, DPRX_MST_CONTROL1, reg);
+}
+
+static void dprx_set_vc_payload_table(struct dprx *dprx)
+{
+	int i, j;
+	u32 reg;
+	u8 val;
+
+	/*
+	 * The IP core only accepts VC payload IDs of 1-4. Thus, we need to
+	 * remap the 1-63 range allowed by DisplayPort into 1-4. However, some
+	 * hosts first set the VC payload table and then allocate the VC
+	 * payload IDs, which means we can't remap the range immediately.
+	 *
+	 * It is probably possible to force a VC payload table update (without
+	 * waiting for a ACT trigger) when the IDs change, but for now we just
+	 * ignore IDs higher than 4.
+	 */
+	for (i = 0; i < 8; i++) {
+		reg = 0;
+		for (j = 0; j < 8; j++) {
+			val = dprx->payload_table[i*8+j];
+			if (val <= 4)
+				reg |= val << (j * 4);
+		}
+		dprx_write(dprx, DPRX_MST_VCPTAB(i), reg);
+	}
+
+	reg = dprx_read(dprx, DPRX_MST_CONTROL1);
+	reg |= 1 << DPRX_MST_CONTROL1_VCPTAB_UPD_REQ;
+	dprx_write(dprx, DPRX_MST_CONTROL1, reg);
+}
+
+static void dprx_set_vc_ids(struct dprx *dprx)
+{
+	u32 reg;
+	int i;
+
+	reg = dprx_read(dprx, DPRX_MST_CONTROL1);
+	reg &= ~(DPRX_MST_CONTROL1_VCP_IDS_MASK << DPRX_MST_CONTROL1_VCP_IDS_SHIFT);
+	for (i = 0; i < dprx->sink_count; i++) {
+		if (dprx->payload_id[i] <= 4)
+			reg |= dprx->payload_id[i] << DPRX_MST_CONTROL1_VCP_ID_SHIFT(i);
+	}
+	dprx_write(dprx, DPRX_MST_CONTROL1, reg);
+}
+
+static void dprx_allocate_vc_payload(struct dprx *dprx, u8 start, u8 count, u8 id)
+{
+	if (count > sizeof(dprx->payload_table) - start)
+		count = sizeof(dprx->payload_table) - start;
+	memset(dprx->payload_table + start, id, count);
+}
+
+static void dprx_deallocate_vc_payload(struct dprx *dprx, int start, u8 id)
+{
+	u8 to = start;
+	u8 i;
+
+	for (i = start; i < sizeof(dprx->payload_table); i++) {
+		if (dprx->payload_table[i] == id)
+			dprx->payload_table[i] = 0;
+		else
+			dprx->payload_table[to++] = dprx->payload_table[i];
+	}
+}
+
+static u32 dprx_full_pbn(struct dprx *dprx)
+{
+	u32 reg;
+	u32 lane_count;
+	u32 link_rate;
+
+	if ((dprx_read(dprx, DPRX_RX_STATUS) >> DPRX_RX_STATUS_INTERLANE_ALIGN) & 1) {
+		/* link training done - get current bandwidth */
+		reg = dprx_read(dprx, DPRX_RX_CONTROL);
+		lane_count = (reg >> DPRX_RX_CONTROL_LANE_COUNT_SHIFT) &
+			     DPRX_RX_CONTROL_LANE_COUNT_MASK;
+		link_rate  = (reg >> DPRX_RX_CONTROL_LINK_RATE_SHIFT) &
+			     DPRX_RX_CONTROL_LINK_RATE_MASK;
+	} else {
+		/* link training not done - get max bandwidth */
+		lane_count = DPRX_MAX_LANE_COUNT;
+		link_rate  = DPRX_MAX_LINK_RATE;
+	}
+
+	return lane_count * link_rate * 32;
+}
+
+static int dprx_port_number_to_sink_idx(struct dprx *dprx, u8 port_number)
+{
+	/* check if port number is valid */
+	if (port_number < DP_MST_LOGICAL_PORT_0 ||
+	    port_number >= DP_MST_LOGICAL_PORT_0 + dprx->sink_count)
+		return -1;
+
+	return port_number - DP_MST_LOGICAL_PORT_0;
+}
+
+static bool dprx_adjust_needed(struct dprx *dprx)
+{
+	u32 control;
+	u32 status;
+	u32 lane_count;
+	u32 lane_count_mask;
+	u32 pattern;
+
+	control = dprx_read(dprx, DPRX_RX_CONTROL);
+	status = dprx_read(dprx, DPRX_RX_STATUS);
+
+	pattern = (control >> DPRX_RX_CONTROL_TP_SHIFT) & DPRX_RX_CONTROL_TP_MASK;
+	lane_count = (control >> DPRX_RX_CONTROL_LANE_COUNT_SHIFT) &
+		     DPRX_RX_CONTROL_LANE_COUNT_MASK;
+	lane_count_mask = (1 << lane_count) - 1;
+
+	if (pattern == 0) {
+		/* link training not in progress */
+		return false;
+	} else if (pattern == 1) {
+		/* link training CR phase - check CR lock */
+		return (~status) & (lane_count_mask << DPRX_RX_STATUS_CR_LOCK_SHIFT);
+	}
+	/* link training EQ phase - check synbol lock and interlane align */
+	return (~status) & (lane_count_mask << DPRX_RX_STATUS_SYM_LOCK_SHIFT |
+			    1 << DPRX_RX_STATUS_INTERLANE_ALIGN);
+}
+
+/*
+ * Return next allowed voltage swing, and pre-emphasis pair.
+ * DisplayPort 1.2 spec, section 3.1.5.2
+ */
+static void dprx_training_control_next(struct dprx_training_control *ctl,
+				       u8 *next_volt_swing, u8 *next_pre_emph)
+{
+	u8 volt_swing = ctl->volt_swing;
+	u8 pre_emph = ctl->pre_emph;
+
+	pre_emph++;
+	if (pre_emph > 2) {
+		volt_swing++;
+		pre_emph = 0;
+	}
+
+	if (volt_swing > 2 || (volt_swing == 2 && pre_emph == 2)) {
+		volt_swing = 0;
+		pre_emph = 0;
+	}
+
+	*next_volt_swing = volt_swing;
+	*next_pre_emph = pre_emph;
+}
+
+static int dprx_i2c_read(struct dprx_sink *sink, u8 addr, u8 *buf, int len)
+{
+	int offset;
+
+	if (len == 0)
+		return 0;
+
+	switch (addr) {
+	case DDC_EDID_ADDR:
+		offset = sink->offset + sink->segment * 256;
+		if (len + offset > sink->blocks * 128)
+			return -1;
+		memcpy(buf, sink->edid + offset, len);
+		sink->offset += len;
+		break;
+	case DDC_SEGMENT_ADDR:
+		if (len > 1)
+			return -1;
+		buf[0] = sink->segment;
+		break;
+	default:
+		return -1;
+	}
+
+	return 0;
+}
+
+static int dprx_i2c_write(struct dprx_sink *sink, u8 addr, u8 *buf, int len)
+{
+	if (len == 0)
+		return 0;
+	if (len > 1)
+		return -1;
+
+	switch (addr) {
+	case DDC_EDID_ADDR:
+		sink->offset = buf[0];
+		break;
+	case DDC_SEGMENT_ADDR:
+		sink->segment = buf[0];
+		break;
+	default:
+		return -1;
+	}
+
+	return 0;
+}
+
+static void dprx_i2c_stop(struct dprx_sink *sink)
+{
+	sink->segment = 0;
+}
+
+static void dprx_write_nak(struct dprx *dprx,
+			   struct drm_dp_sideband_msg_reply_body *rep,
+			   u8 req_type, u8 reason)
+{
+	rep->reply_type = DP_SIDEBAND_REPLY_NAK;
+	rep->req_type = req_type;
+
+	memcpy(rep->u.nak.guid, dprx->guid, sizeof(dprx->guid));
+	rep->u.nak.reason = reason;
+	rep->u.nak.nak_data = 0;
+}
+
+static void dprx_execute_link_address(struct dprx *dprx,
+				      struct drm_dp_sideband_msg_req_body *req,
+				      struct drm_dp_sideband_msg_reply_body *rep)
+{
+	struct drm_dp_link_address_ack_reply *link_address = &rep->u.link_addr;
+	struct drm_dp_link_addr_reply_port *port = link_address->ports;
+	int i;
+
+	rep->reply_type = DP_SIDEBAND_REPLY_ACK;
+	rep->req_type = DP_LINK_ADDRESS;
+
+	memcpy(link_address->guid, dprx->guid, sizeof(dprx->guid));
+	link_address->nports = dprx->sink_count + 1;
+
+	/* Port 0: input (physical) */
+	port->input_port = true;
+	port->peer_device_type = DP_PEER_DEVICE_SOURCE_OR_SST;
+	port->port_number = 0;
+	port->mcs = false;
+	port->ddps = true;
+	port++;
+
+	for (i = 0; i < dprx->sink_count; i++) {
+		/* Port 8 + n: internal sink number n (logical) */
+		port->input_port = false;
+		port->port_number = DP_MST_LOGICAL_PORT_0 + i;
+		port->mcs = false;
+		if (dprx->sinks[i].blocks > 0) {
+			port->peer_device_type = DP_PEER_DEVICE_SST_SINK;
+			port->ddps = true;
+		} else {
+			port->peer_device_type = DP_PEER_DEVICE_NONE;
+			port->ddps = false;
+		}
+		port->legacy_device_plug_status = false;
+		port->dpcd_revision = 0;
+		memset(port->peer_guid, 0, 16);
+		port->num_sdp_streams = 0;
+		port->num_sdp_stream_sinks = 0;
+		port++;
+	}
+}
+
+static void dprx_execute_connection_status_notify(struct dprx *dprx,
+						  struct drm_dp_sideband_msg_req_body *req,
+						  struct drm_dp_sideband_msg_reply_body *rep)
+{
+	rep->reply_type = DP_SIDEBAND_REPLY_ACK;
+	rep->req_type = DP_CONNECTION_STATUS_NOTIFY;
+}
+
+static void dprx_execute_enum_path_resources(struct dprx *dprx,
+					     struct drm_dp_sideband_msg_req_body *req,
+					     struct drm_dp_sideband_msg_reply_body *rep)
+{
+	u32 full_pbn = dprx_full_pbn(dprx);
+
+	rep->reply_type = DP_SIDEBAND_REPLY_ACK;
+	rep->req_type = DP_ENUM_PATH_RESOURCES;
+
+	rep->u.path_resources.port_number = req->u.port_num.port_number;
+	rep->u.path_resources.fec_capable = false;
+	rep->u.path_resources.full_payload_bw_number = full_pbn;
+	if (dprx->payload_pbn_total > full_pbn)
+		rep->u.path_resources.avail_payload_bw_number = 0;
+	else
+		rep->u.path_resources.avail_payload_bw_number = full_pbn - dprx->payload_pbn_total;
+}
+
+static void dprx_execute_allocate_payload(struct dprx *dprx,
+					  struct drm_dp_sideband_msg_req_body *req,
+					  struct drm_dp_sideband_msg_reply_body *rep)
+{
+	struct drm_dp_allocate_payload *a_req = &req->u.allocate_payload;
+	struct drm_dp_allocate_payload_ack_reply *a_rep = &rep->u.allocate_payload;
+	int sink_idx;
+
+	sink_idx = dprx_port_number_to_sink_idx(dprx, a_req->port_number);
+	if (sink_idx == -1) {
+		dprx_write_nak(dprx, rep, req->req_type, DP_NAK_BAD_PARAM);
+		return;
+	}
+
+	if (a_req->vcpi == 0) {
+		dprx_write_nak(dprx, rep, req->req_type, DP_NAK_BAD_PARAM);
+		return;
+	}
+
+	if (a_req->pbn > 0) {
+		if (dprx->payload_pbn[sink_idx] == 0) {
+			/* New payload ID */
+			dprx->payload_id[sink_idx] = a_req->vcpi;
+		} else if (dprx->payload_id[sink_idx] != a_req->vcpi) {
+			/* At most one payload ID is allowed per sink */
+			dprx_write_nak(dprx, rep, req->req_type, DP_NAK_ALLOCATE_FAIL);
+			return;
+		}
+	}
+	WARN_ON_ONCE(dprx->payload_pbn_total < dprx->payload_pbn[sink_idx]);
+	dprx->payload_pbn_total -= dprx->payload_pbn[sink_idx];
+	dprx->payload_pbn_total += a_req->pbn;
+	dprx->payload_pbn[sink_idx] = a_req->pbn;
+
+	dprx_set_vc_ids(dprx);
+
+	rep->reply_type = DP_SIDEBAND_REPLY_ACK;
+	rep->req_type = DP_ALLOCATE_PAYLOAD;
+
+	a_rep->port_number = a_req->port_number;
+	a_rep->vcpi = a_req->vcpi;
+	a_rep->allocated_pbn = a_req->pbn;
+}
+
+static void dprx_execute_clear_payload_id_table(struct dprx *dprx,
+						struct drm_dp_sideband_msg_req_body *req,
+						struct drm_dp_sideband_msg_reply_body *rep)
+{
+	rep->reply_type = DP_SIDEBAND_REPLY_ACK;
+	rep->req_type = DP_CLEAR_PAYLOAD_ID_TABLE;
+
+	dprx_clear_vc_payload_table(dprx);
+}
+
+static void dprx_execute_remote_dpcd_read(struct dprx *dprx,
+					  struct drm_dp_sideband_msg_req_body *req,
+					  struct drm_dp_sideband_msg_reply_body *rep)
+{
+	struct drm_dp_remote_dpcd_read *read_req = &req->u.dpcd_read;
+	struct drm_dp_remote_dpcd_read_ack_reply *read_rep = &rep->u.remote_dpcd_read_ack;
+
+	rep->reply_type = DP_SIDEBAND_REPLY_ACK;
+	rep->req_type = DP_REMOTE_DPCD_READ;
+
+	read_rep->port_number = read_req->port_number;
+	read_rep->num_bytes = read_req->num_bytes;
+	memset(read_rep->bytes, 0, read_req->num_bytes);
+}
+
+static void dprx_execute_remote_i2c_read(struct dprx *dprx,
+					 struct drm_dp_sideband_msg_req_body *req,
+					 struct drm_dp_sideband_msg_reply_body *rep)
+{
+	struct drm_dp_remote_i2c_read *read_req = &req->u.i2c_read;
+	struct drm_dp_remote_i2c_read_ack_reply *read_rep = &rep->u.remote_i2c_read_ack;
+	struct drm_dp_remote_i2c_read_tx *tx;
+	struct dprx_sink *sink;
+	int sink_idx;
+	int res;
+	int i;
+
+	sink_idx = dprx_port_number_to_sink_idx(dprx, read_req->port_number);
+	if (sink_idx == -1) {
+		dprx_write_nak(dprx, rep, req->req_type, DP_NAK_BAD_PARAM);
+		return;
+	}
+	sink = &dprx->sinks[sink_idx];
+
+	for (i = 0; i < read_req->num_transactions; i++) {
+		tx = &read_req->transactions[i];
+		res = dprx_i2c_write(sink, tx->i2c_dev_id, tx->bytes, tx->num_bytes);
+		if (res)
+			goto i2c_err;
+		if (!tx->no_stop_bit)
+			dprx_i2c_stop(sink);
+	}
+
+	res = dprx_i2c_read(sink, read_req->read_i2c_device_id,
+				  read_rep->bytes, read_req->num_bytes_read);
+	if (res)
+		goto i2c_err;
+	dprx_i2c_stop(sink);
+
+	rep->reply_type = DP_SIDEBAND_REPLY_ACK;
+	rep->req_type = DP_REMOTE_I2C_READ;
+
+	read_rep->port_number = read_req->port_number;
+	read_rep->num_bytes = read_req->num_bytes_read;
+	return;
+
+i2c_err:
+	dprx_i2c_stop(sink);
+	dprx_write_nak(dprx, rep, req->req_type, DP_NAK_I2C_NAK);
+}
+
+static void dprx_execute_remote_i2c_write(struct dprx *dprx,
+					  struct drm_dp_sideband_msg_req_body *req,
+					  struct drm_dp_sideband_msg_reply_body *rep)
+{
+	struct drm_dp_remote_i2c_write *write_req = &req->u.i2c_write;
+	struct drm_dp_remote_i2c_write_ack_reply *write_rep = &rep->u.remote_i2c_write_ack;
+	struct dprx_sink *sink;
+	int sink_idx;
+	int res;
+
+	sink_idx = dprx_port_number_to_sink_idx(dprx, write_req->port_number);
+	if (sink_idx == -1) {
+		dprx_write_nak(dprx, rep, req->req_type, DP_NAK_BAD_PARAM);
+		return;
+	}
+	sink = &dprx->sinks[sink_idx];
+
+	res = dprx_i2c_write(sink, write_req->write_i2c_device_id,
+				   write_req->bytes, write_req->num_bytes);
+	dprx_i2c_stop(sink);
+	if (res) {
+		dprx_write_nak(dprx, rep, req->req_type, DP_NAK_I2C_NAK);
+		return;
+	}
+
+	rep->reply_type = DP_SIDEBAND_REPLY_ACK;
+	rep->req_type = DP_REMOTE_I2C_WRITE;
+	write_rep->port_number = write_req->port_number;
+}
+
+static void dprx_execute_power_up_phy(struct dprx *dprx,
+				      struct drm_dp_sideband_msg_req_body *req,
+				      struct drm_dp_sideband_msg_reply_body *rep)
+{
+	rep->reply_type = DP_SIDEBAND_REPLY_ACK;
+	rep->req_type = DP_POWER_UP_PHY;
+	rep->u.port_number.port_number = req->u.port_num.port_number;
+}
+
+static void dprx_execute_power_down_phy(struct dprx *dprx,
+					struct drm_dp_sideband_msg_req_body *req,
+					struct drm_dp_sideband_msg_reply_body *rep)
+{
+	rep->reply_type = DP_SIDEBAND_REPLY_ACK;
+	rep->req_type = DP_POWER_DOWN_PHY;
+	rep->u.port_number.port_number = req->u.port_num.port_number;
+}
+
+static void dprx_encode_sideband_msg(struct sideband_msg *msg, u8 *buf)
+{
+	int idx = 0;
+	int i;
+	u8 crc4;
+
+	buf[idx++] = ((msg->lct & 0xf) << 4) | (msg->lcr & 0xf);
+	for (i = 0; i < (msg->lct / 2); i++)
+		buf[idx++] = msg->rad[i];
+	buf[idx++] = (msg->broadcast << 7) | (msg->path_msg << 6) |
+		((msg->body_len + 1) & 0x3f);
+	buf[idx++] = (msg->somt << 7) | (msg->eomt << 6) | (msg->seqno << 4);
+
+	crc4 = crc_dp_msg_header(buf, (idx * 2) - 1);
+	buf[idx - 1] |= (crc4 & 0xf);
+
+	memcpy(&buf[idx], msg->body, msg->body_len);
+	idx += msg->body_len;
+	buf[idx] = crc_dp_msg_data(msg->body, msg->body_len);
+}
+
+static bool dprx_decode_sideband_msg(struct sideband_msg *msg, u8 *buf, int buflen)
+{
+	u8 hdr_crc;
+	u8 hdr_len;
+	u8 body_crc;
+	int i;
+	u8 idx;
+
+	if (buf[0] == 0)
+		return false;
+	hdr_len = 3;
+	hdr_len += ((buf[0] & 0xf0) >> 4) / 2;
+	if (hdr_len > buflen)
+		return false;
+	hdr_crc = crc_dp_msg_header(buf, (hdr_len * 2) - 1);
+	if ((hdr_crc & 0xf) != (buf[hdr_len - 1] & 0xf))
+		return false;
+
+	msg->lct = (buf[0] & 0xf0) >> 4;
+	msg->lcr = (buf[0] & 0xf);
+	idx = 1;
+	for (i = 0; i < (msg->lct / 2); i++)
+		msg->rad[i] = buf[idx++];
+	msg->broadcast = (buf[idx] >> 7) & 0x1;
+	msg->path_msg = (buf[idx] >> 6) & 0x1;
+	msg->body_len = (buf[idx] & 0x3f) - 1;
+	idx++;
+	msg->somt = (buf[idx] >> 7) & 0x1;
+	msg->eomt = (buf[idx] >> 6) & 0x1;
+	msg->seqno = (buf[idx] >> 4) & 0x1;
+	idx++;
+
+	if (hdr_len + msg->body_len + 1 != buflen)
+		return false;
+
+	body_crc = crc_dp_msg_data(&buf[idx], msg->body_len);
+	if (body_crc != buf[idx + msg->body_len])
+		return false;
+
+	memcpy(msg->body, &buf[idx], msg->body_len);
+	idx += msg->body_len;
+
+	return true;
+}
+
+static bool dprx_decode_port_number_req(struct drm_dp_port_number_req *port_num, u8 *buf, int len)
+{
+	if (len != 1)
+		return false;
+
+	port_num->port_number = buf[0] >> 4;
+
+	return true;
+}
+
+static bool
+dprx_decode_connection_status_notify_req(struct drm_dp_connection_status_notify *conn_stat,
+					 u8 *buf, int len)
+{
+	int idx = 0;
+
+	if (len != 18)
+		return false;
+
+	conn_stat->port_number = buf[idx++];
+	memcpy(conn_stat->guid, &buf[idx], 16);
+	idx += 16;
+	conn_stat->legacy_device_plug_status = (buf[idx] >> 6) & 1;
+	conn_stat->displayport_device_plug_status = (buf[idx] >> 5) & 1;
+	conn_stat->message_capability_status = (buf[idx] >> 4) & 1;
+	conn_stat->input_port = (buf[idx] >> 3) & 1;
+	conn_stat->peer_device_type = buf[idx] & 0x7;
+
+	return true;
+}
+
+static bool dprx_decode_allocate_payload_req(struct drm_dp_allocate_payload *alloc_payload,
+					     u8 *buf, int len)
+{
+	int idx = 0;
+	int i;
+
+	if (len < 4)
+		return false;
+
+	alloc_payload->port_number = buf[idx] >> 4;
+	alloc_payload->number_sdp_streams = buf[idx++] & 0xf;
+	alloc_payload->vcpi = buf[idx++] & 0x7f;
+	alloc_payload->pbn = buf[idx] << 8 | buf[idx + 1];
+	idx += 2;
+
+	if (len != idx + (alloc_payload->number_sdp_streams + 1) / 2)
+		return false;
+
+	for (i = 0; i < alloc_payload->number_sdp_streams; i++) {
+		if ((i & 1) == 0) {
+			alloc_payload->sdp_stream_sink[i] = buf[idx] >> 4;
+		} else {
+			alloc_payload->sdp_stream_sink[i] = buf[idx] & 0xf;
+			idx++;
+		}
+	}
+
+	return true;
+}
+
+static bool dprx_decode_remote_dpcd_read_req(struct drm_dp_remote_dpcd_read *dpcd_read,
+					     u8 *buf, int len)
+{
+	if (len != 4)
+		return false;
+
+	dpcd_read->port_number = buf[0] >> 4;
+	dpcd_read->dpcd_address = (buf[0] & 0xf) << 16 | buf[1] << 8 | buf[2];
+	dpcd_read->num_bytes = buf[3];
+
+	return true;
+}
+
+static bool dprx_decode_remote_i2c_read_req(struct drm_dp_remote_i2c_read *i2c_read,
+					    u8 *buf, int len)
+{
+	struct drm_dp_remote_i2c_read_tx *tx;
+	int idx = 0;
+	int i;
+
+	if (len < 1)
+		return false;
+
+	i2c_read->port_number = buf[idx] >> 4;
+	i2c_read->num_transactions = buf[idx] & 0x3;
+	idx++;
+
+	for (i = 0; i < i2c_read->num_transactions; i++) {
+		tx = &i2c_read->transactions[i];
+		if (len < idx + 2)
+			return false;
+		tx->i2c_dev_id = buf[idx++] & 0x7f;
+		tx->num_bytes = buf[idx++];
+		if (len < idx + tx->num_bytes + 1)
+			return -1;
+		tx->bytes = &buf[idx];
+		idx += tx->num_bytes;
+		tx->no_stop_bit = (buf[idx] >> 4) & 1;
+		tx->i2c_transaction_delay = buf[idx] & 0xf;
+		idx++;
+	}
+
+	if (len != idx + 2)
+		return false;
+
+	i2c_read->read_i2c_device_id = buf[idx++] & 0x7f;
+	i2c_read->num_bytes_read = buf[idx++];
+
+	return true;
+}
+
+static bool dprx_decode_remote_i2c_write_req(struct drm_dp_remote_i2c_write *i2c_write,
+					     u8 *buf, int len)
+{
+	int idx = 0;
+
+	if (len < 3)
+		return false;
+
+	i2c_write->port_number = buf[idx++] >> 4;
+	i2c_write->write_i2c_device_id = buf[idx++] & 0x7f;
+	i2c_write->num_bytes = buf[idx++];
+
+	if (len != idx + i2c_write->num_bytes)
+		return false;
+
+	i2c_write->bytes = &buf[idx];
+
+	return true;
+}
+
+static bool dprx_decode_sideband_req(struct drm_dp_sideband_msg_req_body *req, u8 *buf, int len)
+{
+	if (len == 0)
+		return false;
+
+	req->req_type = buf[0] & 0x7f;
+	buf++;
+	len--;
+
+	switch (req->req_type) {
+	case DP_LINK_ADDRESS:
+	case DP_CLEAR_PAYLOAD_ID_TABLE:
+		return len == 0; /* no request data */
+	case DP_ENUM_PATH_RESOURCES:
+	case DP_POWER_UP_PHY:
+	case DP_POWER_DOWN_PHY:
+		return dprx_decode_port_number_req(&req->u.port_num, buf, len);
+	case DP_CONNECTION_STATUS_NOTIFY:
+		return dprx_decode_connection_status_notify_req(&req->u.conn_stat, buf, len);
+	case DP_ALLOCATE_PAYLOAD:
+		return dprx_decode_allocate_payload_req(&req->u.allocate_payload, buf, len);
+	case DP_REMOTE_DPCD_READ:
+		return dprx_decode_remote_dpcd_read_req(&req->u.dpcd_read, buf, len);
+	case DP_REMOTE_I2C_READ:
+		return dprx_decode_remote_i2c_read_req(&req->u.i2c_read, buf, len);
+	case DP_REMOTE_I2C_WRITE:
+		return dprx_decode_remote_i2c_write_req(&req->u.i2c_write, buf, len);
+	default:
+		return false;
+	}
+}
+
+static void dprx_encode_sideband_rep(struct drm_dp_sideband_msg_reply_body *rep, u8 *buf, int *len)
+{
+	int idx = 0;
+	int i;
+
+	buf[idx++] = (rep->reply_type & 0x1) << 7 | (rep->req_type & 0x7f);
+
+	if (rep->reply_type) {
+		memcpy(&buf[idx], rep->u.nak.guid, 16);
+		idx += 16;
+		buf[idx++] = rep->u.nak.reason;
+		buf[idx++] = rep->u.nak.nak_data;
+		*len = idx;
+		return;
+	}
+
+	switch (rep->req_type) {
+	case DP_LINK_ADDRESS: {
+		struct drm_dp_link_address_ack_reply *link_addr = &rep->u.link_addr;
+		struct drm_dp_link_addr_reply_port *port;
+
+		memcpy(&buf[idx], link_addr->guid, 16);
+		idx += 16;
+		buf[idx++] = link_addr->nports;
+		for (i = 0; i < link_addr->nports; i++) {
+			port = &link_addr->ports[i];
+			buf[idx++] = port->input_port << 7 | port->peer_device_type << 4 |
+				     port->port_number;
+			if (port->input_port == 0) {
+				buf[idx++] = port->mcs << 7 | port->ddps << 6 |
+					     port->legacy_device_plug_status << 5;
+				buf[idx++] = port->dpcd_revision;
+				memcpy(&buf[idx], port->peer_guid, 16);
+				idx += 16;
+				buf[idx++] = port->num_sdp_streams << 4 |
+					     port->num_sdp_stream_sinks;
+			} else {
+				buf[idx++] = port->mcs << 7 | port->ddps << 6;
+			}
+		}
+		break;
+	}
+	case DP_ENUM_PATH_RESOURCES: {
+		struct drm_dp_enum_path_resources_ack_reply *path_res = &rep->u.path_resources;
+
+		buf[idx++] = path_res->port_number << 4 | path_res->fec_capable;
+		buf[idx++] = path_res->full_payload_bw_number >> 8;
+		buf[idx++] = path_res->full_payload_bw_number & 0xff;
+		buf[idx++] = path_res->avail_payload_bw_number >> 8;
+		buf[idx++] = path_res->avail_payload_bw_number & 0xff;
+		break;
+	}
+	case DP_ALLOCATE_PAYLOAD: {
+		struct drm_dp_allocate_payload_ack_reply *alloc_payload = &rep->u.allocate_payload;
+
+		buf[idx++] = alloc_payload->port_number << 4;
+		buf[idx++] = alloc_payload->vcpi & 0x3f;
+		buf[idx++] = alloc_payload->allocated_pbn >> 8;
+		buf[idx++] = alloc_payload->allocated_pbn & 0xff;
+		break;
+	}
+	case DP_REMOTE_DPCD_READ: {
+		struct drm_dp_remote_dpcd_read_ack_reply *dpcd_read = &rep->u.remote_dpcd_read_ack;
+
+		buf[idx++] = dpcd_read->port_number & 0xf;
+		buf[idx++] = dpcd_read->num_bytes;
+		memcpy(&buf[idx], dpcd_read->bytes, dpcd_read->num_bytes);
+		idx += dpcd_read->num_bytes;
+		break;
+	}
+	case DP_REMOTE_I2C_READ: {
+		struct drm_dp_remote_i2c_read_ack_reply *i2c_read = &rep->u.remote_i2c_read_ack;
+
+		buf[idx++] = i2c_read->port_number & 0xf;
+		buf[idx++] = i2c_read->num_bytes;
+		memcpy(&buf[idx], i2c_read->bytes, i2c_read->num_bytes);
+		idx += i2c_read->num_bytes;
+		break;
+	}
+	case DP_REMOTE_I2C_WRITE:
+		buf[idx++] = rep->u.remote_i2c_write_ack.port_number & 0xf;
+		break;
+	case DP_POWER_UP_PHY:
+	case DP_POWER_DOWN_PHY:
+		buf[idx++] = rep->u.port_number.port_number << 4;
+		break;
+	}
+	*len = idx;
+}
+
+static void dprx_execute_msg_transaction(struct dprx *dprx,
+					 struct drm_dp_sideband_msg_req_body *req,
+					 struct drm_dp_sideband_msg_reply_body *rep)
+{
+	switch (req->req_type) {
+	case DP_LINK_ADDRESS:
+		dprx_execute_link_address(dprx, req, rep);
+		break;
+	case DP_CONNECTION_STATUS_NOTIFY:
+		dprx_execute_connection_status_notify(dprx, req, rep);
+		break;
+	case DP_ENUM_PATH_RESOURCES:
+		dprx_execute_enum_path_resources(dprx, req, rep);
+		break;
+	case DP_ALLOCATE_PAYLOAD:
+		dprx_execute_allocate_payload(dprx, req, rep);
+		break;
+	case DP_CLEAR_PAYLOAD_ID_TABLE:
+		dprx_execute_clear_payload_id_table(dprx, req, rep);
+		break;
+	case DP_REMOTE_DPCD_READ:
+		dprx_execute_remote_dpcd_read(dprx, req, rep);
+		break;
+	case DP_REMOTE_I2C_READ:
+		dprx_execute_remote_i2c_read(dprx, req, rep);
+		break;
+	case DP_REMOTE_I2C_WRITE:
+		dprx_execute_remote_i2c_write(dprx, req, rep);
+		break;
+	case DP_POWER_UP_PHY:
+		dprx_execute_power_up_phy(dprx, req, rep);
+		break;
+	case DP_POWER_DOWN_PHY:
+		dprx_execute_power_down_phy(dprx, req, rep);
+		break;
+	default:
+		dprx_write_nak(dprx, rep, req->req_type, DP_NAK_BAD_PARAM);
+		break;
+	}
+}
+
+static void dprx_handle_msg_transaction(struct dprx *dprx,
+					struct msg_transaction_rxbuf *rxbuf,
+					struct msg_transaction_txbuf *txbuf)
+{
+	bool decoded;
+	struct drm_dp_sideband_msg_req_body req;
+	struct drm_dp_sideband_msg_reply_body rep;
+
+	decoded = dprx_decode_sideband_req(&req, rxbuf->buf, rxbuf->len);
+	if (decoded)
+		dprx_execute_msg_transaction(dprx, &req, &rep);
+	else
+		dprx_write_nak(dprx, &rep, req.req_type, DP_NAK_BAD_PARAM);
+	dprx_encode_sideband_rep(&rep, txbuf->buf, &txbuf->len);
+	txbuf->written = 0;
+}
+
+static void dprx_msg_transaction_append(struct msg_transaction_rxbuf *rxbuf,
+					struct msg_transaction_meta *meta,
+					struct sideband_msg *msg)
+{
+	int append_len;
+
+	append_len = min(msg->body_len, sizeof(rxbuf->buf) - rxbuf->len);
+	memcpy(rxbuf->buf + rxbuf->len, msg->body, append_len);
+	rxbuf->len += append_len;
+
+	if (msg->somt) {
+		meta->lct = msg->lct;
+		memcpy(meta->rad, msg->rad, msg->lct / 2);
+		meta->seqno = msg->seqno;
+	}
+}
+
+static void dprx_msg_transaction_extract(struct msg_transaction_txbuf *txbuf,
+					 struct msg_transaction_meta *meta,
+					 struct sideband_msg *msg)
+{
+	int hdr_len = 3 + meta->lct / 2;
+	int body_len;
+	bool somt;
+	bool eomt;
+
+	body_len = txbuf->len - txbuf->written;
+	/* trim body_len so that the sideband msg fits into 48 bytes */
+	body_len = min(body_len, 48 - 1 - hdr_len);
+
+	somt = (txbuf->written == 0);
+	eomt = (txbuf->written + body_len == txbuf->len);
+
+	msg->lct = meta->lct;
+	msg->lcr = meta->lct - 1;
+	memcpy(msg->rad, meta->rad, meta->lct / 2);
+	msg->broadcast = false;
+	msg->path_msg = false;
+	msg->somt = somt;
+	msg->eomt = eomt;
+	msg->seqno = meta->seqno;
+
+	memcpy(msg->body, txbuf->buf + txbuf->written, body_len);
+	msg->body_len = body_len;
+
+	txbuf->written += body_len;
+}
+
+static void dprx_msg_transaction_clear_rxbuf(struct msg_transaction_rxbuf *rxbuf)
+{
+	rxbuf->len = 0;
+}
+
+static void dprx_msg_transaction_clear_txbuf(struct msg_transaction_txbuf *txbuf)
+{
+	txbuf->len = 0;
+	txbuf->written = 0;
+}
+
+static bool dprx_msg_transaction_txbuf_empty(struct msg_transaction_txbuf *txbuf)
+{
+	return txbuf->written == txbuf->len;
+}
+
+static void dprx_write_pending_sideband_msg(struct dprx *dprx)
+{
+	struct msg_transaction_txbuf *txbuf;
+	struct msg_transaction_meta *meta;
+	struct sideband_msg msg;
+
+	if (WARN_ON_ONCE(!dprx->mt_pending))
+		return;
+
+	txbuf = &dprx->mt_txbuf[dprx->mt_seqno];
+	meta = &dprx->mt_meta[dprx->mt_seqno];
+
+	dprx_msg_transaction_extract(txbuf, meta, &msg);
+	if (dprx_msg_transaction_txbuf_empty(txbuf)) {
+		dprx->mt_seqno = !dprx->mt_seqno;
+		txbuf = &dprx->mt_txbuf[dprx->mt_seqno];
+		if (dprx_msg_transaction_txbuf_empty(txbuf))
+			dprx->mt_pending = false;
+	}
+
+	dprx_encode_sideband_msg(&msg, dprx->down_rep_buf);
+}
+
+static void dprx_signal_irq(struct dprx *dprx, int irq)
+{
+	dprx->irq_vector |= irq;
+	dprx_pulse_hpd(dprx);
+}
+
+static void dprx_handle_sideband_msg(struct dprx *dprx, struct sideband_msg *msg)
+{
+	struct msg_transaction_rxbuf *rxbuf = &dprx->mt_rxbuf[msg->seqno];
+	struct msg_transaction_txbuf *txbuf = &dprx->mt_txbuf[msg->seqno];
+	struct msg_transaction_meta *meta = &dprx->mt_meta[msg->seqno];
+
+	if (msg->somt)
+		dprx_msg_transaction_clear_rxbuf(rxbuf);
+	dprx_msg_transaction_append(rxbuf, meta, msg);
+
+	if (msg->eomt) {
+		/* drop the message if txbuf isn't empty */
+		if (!dprx_msg_transaction_txbuf_empty(txbuf))
+			return;
+		dprx_handle_msg_transaction(dprx, rxbuf, txbuf);
+
+		if (!dprx->mt_pending) {
+			dprx->mt_pending = true;
+			dprx->mt_seqno = msg->seqno;
+			if (!dprx->down_rep_pending) {
+				dprx_write_pending_sideband_msg(dprx);
+				dprx_signal_irq(dprx, DP_DOWN_REP_MSG_RDY);
+				dprx->down_rep_pending = true;
+			}
+		}
+	}
+}
+
+static u8 dprx_caps[16] = {
+	[DP_DPCD_REV]			= DP_DPCD_REV_14,
+	[DP_MAX_LINK_RATE]		= DPRX_MAX_LINK_RATE,
+	[DP_MAX_LANE_COUNT]		= DP_ENHANCED_FRAME_CAP | DP_TPS3_SUPPORTED |
+					  DPRX_MAX_LANE_COUNT,
+	[DP_MAX_DOWNSPREAD]		= DP_TPS4_SUPPORTED | DP_MAX_DOWNSPREAD_0_5,
+	[DP_MAIN_LINK_CHANNEL_CODING]	= DP_CAP_ANSI_8B10B,
+	[DP_RECEIVE_PORT_0_CAP_0]	= DP_LOCAL_EDID_PRESENT,
+};
+
+static u8 dprx_read_caps(struct dprx *dprx, u32 offset)
+{
+	return dprx_caps[offset];
+}
+
+static u8 dprx_read_mstm_cap(struct dprx *dprx)
+{
+	return dprx->has_mst;
+}
+
+static u8 dprx_read_guid(struct dprx *dprx, u32 offset)
+{
+	return dprx->guid[offset];
+}
+
+static void dprx_write_guid(struct dprx *dprx, u32 offset, u8 val)
+{
+	dprx->guid[offset] = val;
+}
+
+static u8 dprx_read_link_bw(struct dprx *dprx)
+{
+	u32 reg = dprx_read(dprx, DPRX_RX_CONTROL);
+
+	return (reg >> DPRX_RX_CONTROL_LINK_RATE_SHIFT) & DPRX_RX_CONTROL_LINK_RATE_MASK;
+}
+
+static void dprx_write_link_bw(struct dprx *dprx, u8 val)
+{
+	u32 reg;
+
+	if (val != DP_LINK_BW_1_62 && val != DP_LINK_BW_2_7 &&
+	    val != DP_LINK_BW_5_4  && val != DP_LINK_BW_8_1)
+		return;
+
+	if (val > DPRX_MAX_LINK_RATE)
+		return;
+
+	reg = dprx_read(dprx, DPRX_RX_CONTROL);
+	reg &= ~(DPRX_RX_CONTROL_LINK_RATE_MASK << DPRX_RX_CONTROL_LINK_RATE_SHIFT);
+	reg |= val << DPRX_RX_CONTROL_LINK_RATE_SHIFT;
+	reg |= 1 << DPRX_RX_CONTROL_RECONFIG_LINKRATE;
+	dprx_write(dprx, DPRX_RX_CONTROL, reg);
+}
+
+static u8 dprx_read_lane_count(struct dprx *dprx)
+{
+	u32 reg = dprx_read(dprx, DPRX_RX_CONTROL);
+
+	return (reg >> DPRX_RX_CONTROL_LANE_COUNT_SHIFT) & DPRX_RX_CONTROL_LANE_COUNT_MASK;
+}
+
+static void dprx_write_lane_count(struct dprx *dprx, u8 val)
+{
+	u32 reg;
+	u8 lane_count;
+
+	lane_count = val & DP_LANE_COUNT_MASK;
+
+	if (lane_count != 1 && lane_count != 2 && lane_count != 4)
+		return;
+
+	reg = dprx_read(dprx, DPRX_RX_CONTROL);
+	reg &= ~(DPRX_RX_CONTROL_LANE_COUNT_MASK << DPRX_RX_CONTROL_LANE_COUNT_SHIFT);
+	reg |= lane_count << DPRX_RX_CONTROL_LANE_COUNT_SHIFT;
+	dprx_write(dprx, DPRX_RX_CONTROL, reg);
+}
+
+static u8 dprx_read_training_pattern(struct dprx *dprx)
+{
+	u32 reg;
+	u32 pattern;
+	u32 scrambler_disable;
+	u8 result = 0;
+
+	reg = dprx_read(dprx, DPRX_RX_CONTROL);
+	pattern = (reg >> DPRX_RX_CONTROL_TP_SHIFT) & DPRX_RX_CONTROL_TP_MASK;
+	scrambler_disable = (reg >> DPRX_RX_CONTROL_SCRAMBLER_DISABLE) & 1;
+
+	if (scrambler_disable)
+		result |= DP_LINK_SCRAMBLING_DISABLE;
+	result |= pattern;
+
+	return result;
+}
+
+static void dprx_write_training_pattern(struct dprx *dprx, u8 val)
+{
+	u8 pattern;
+	u8 scrambler_disable;
+	u32 reg;
+
+	pattern = val & DP_TRAINING_PATTERN_MASK_1_4;
+	scrambler_disable = !!(val & DP_LINK_SCRAMBLING_DISABLE);
+
+	reg = dprx_read(dprx, DPRX_RX_CONTROL);
+	reg &= ~(DPRX_RX_CONTROL_TP_MASK << DPRX_RX_CONTROL_TP_SHIFT);
+	reg |= pattern << DPRX_RX_CONTROL_TP_SHIFT;
+	reg &= ~(1 << DPRX_RX_CONTROL_SCRAMBLER_DISABLE);
+	reg |= scrambler_disable << DPRX_RX_CONTROL_SCRAMBLER_DISABLE;
+	dprx_write(dprx, DPRX_RX_CONTROL, reg);
+}
+
+static u8 dprx_read_training_lane(struct dprx *dprx, u32 offset)
+{
+	struct dprx_training_control *ctl = &dprx->training_control[offset];
+	u8 result = 0;
+
+	result |= ctl->volt_swing << DP_TRAIN_VOLTAGE_SWING_SHIFT;
+	if (ctl->max_swing)
+		result |= DP_TRAIN_MAX_SWING_REACHED;
+	result |= ctl->pre_emph << DP_TRAIN_PRE_EMPHASIS_SHIFT;
+	if (ctl->max_pre_emph)
+		result |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED;
+
+	return result;
+}
+
+static void dprx_write_training_lane(struct dprx *dprx, u32 offset, u8 val)
+{
+	struct dprx_training_control *ctl = &dprx->training_control[offset];
+
+	ctl->volt_swing   = (val & DP_TRAIN_VOLTAGE_SWING_MASK) >> DP_TRAIN_VOLTAGE_SWING_SHIFT;
+	ctl->max_swing    = (val & DP_TRAIN_MAX_SWING_REACHED);
+	ctl->pre_emph     = (val & DP_TRAIN_PRE_EMPHASIS_MASK) >> DP_TRAIN_PRE_EMPHASIS_SHIFT;
+	ctl->max_pre_emph = (val & DP_TRAIN_MAX_PRE_EMPHASIS_REACHED);
+}
+
+static u8 dprx_read_mstm_ctrl(struct dprx *dprx)
+{
+	return (dprx_read(dprx, DPRX_MST_CONTROL1) >> DPRX_MST_CONTROL1_MST_EN) & 1;
+}
+
+static void dprx_write_mstm_ctrl(struct dprx *dprx, u8 val)
+{
+	u8 mst_en = !!(val & DP_MST_EN);
+	u32 reg;
+
+	reg = dprx_read(dprx, DPRX_MST_CONTROL1);
+	reg &= ~(1 << DPRX_MST_CONTROL1_MST_EN);
+	reg |= mst_en << DPRX_MST_CONTROL1_MST_EN;
+	dprx_write(dprx, DPRX_MST_CONTROL1, reg);
+}
+
+static void dprx_handle_payload_allocate(struct dprx *dprx)
+{
+	u8 id = dprx->payload_allocate_set;
+	u8 start = dprx->payload_allocate_start_time_slot;
+	u8 count = dprx->payload_allocate_time_slot_count;
+
+	if (id == 0 && start == 0 && count == 0x3f) {
+		dprx_clear_vc_payload_table(dprx);
+	} else {
+		if (count == 0)
+			dprx_deallocate_vc_payload(dprx, start, id);
+		else
+			dprx_allocate_vc_payload(dprx, start, count, id);
+		dprx_set_vc_payload_table(dprx);
+	}
+	dprx->payload_table_updated = 1;
+}
+
+static u8 dprx_read_payload_allocate_set(struct dprx *dprx)
+{
+	return dprx->payload_allocate_set;
+}
+
+static void dprx_write_payload_allocate_set(struct dprx *dprx, u8 val)
+{
+	dprx->payload_allocate_set = val & DP_PAYLOAD_ALLOCATE_SET_MASK;
+}
+
+static u8 dprx_read_payload_allocate_start_time_slot(struct dprx *dprx)
+{
+	return dprx->payload_allocate_start_time_slot;
+}
+
+static void dprx_write_payload_allocate_start_time_slot(struct dprx *dprx, u8 val)
+{
+	dprx->payload_allocate_start_time_slot = val & DP_PAYLOAD_ALLOCATE_START_TIME_SLOT_MASK;
+}
+
+static u8 dprx_read_payload_allocate_time_slot_count(struct dprx *dprx)
+{
+	return dprx->payload_allocate_time_slot_count;
+}
+
+static void dprx_write_payload_allocate_time_slot_count(struct dprx *dprx, u8 val)
+{
+	dprx->payload_allocate_time_slot_count = val & DP_PAYLOAD_ALLOCATE_TIME_SLOT_COUNT_MASK;
+	dprx_handle_payload_allocate(dprx);
+}
+
+static u8 dprx_read_sink_count(struct dprx *dprx)
+{
+	return dprx->sink_count;
+}
+
+static u8 dprx_read_device_service_irq_vector(struct dprx *dprx)
+{
+	return dprx->irq_vector;
+}
+
+static void dprx_write_device_service_irq_vector(struct dprx *dprx, u8 val)
+{
+	dprx->irq_vector &= ~val;
+
+	if (val & DP_DOWN_REP_MSG_RDY) {
+		if (dprx->mt_pending) {
+			dprx_write_pending_sideband_msg(dprx);
+			dprx_signal_irq(dprx, DP_DOWN_REP_MSG_RDY);
+		} else {
+			dprx->down_rep_pending = false;
+		}
+	}
+}
+
+static u8 dprx_read_lane0_1_status(struct dprx *dprx)
+{
+	u32 reg;
+	u8 res = 0;
+
+	reg = dprx_read(dprx, DPRX_RX_STATUS);
+	if ((reg >> DPRX_RX_STATUS_CR_LOCK(0)) & 1)
+		res |= DP_LANE_CR_DONE;
+	if ((reg >> DPRX_RX_STATUS_CR_LOCK(1)) & 1)
+		res |= DP_LANE_CR_DONE << 4;
+	if ((reg >> DPRX_RX_STATUS_SYM_LOCK(0)) & 1)
+		res |= DP_LANE_CHANNEL_EQ_DONE | DP_LANE_SYMBOL_LOCKED;
+	if ((reg >> DPRX_RX_STATUS_SYM_LOCK(1)) & 1)
+		res |= (DP_LANE_CHANNEL_EQ_DONE | DP_LANE_SYMBOL_LOCKED) << 4;
+
+	return res;
+}
+
+static u8 dprx_read_lane2_3_status(struct dprx *dprx)
+{
+	u32 reg;
+	u8 res = 0;
+
+	reg = dprx_read(dprx, DPRX_RX_STATUS);
+	if ((reg >> DPRX_RX_STATUS_CR_LOCK(2)) & 1)
+		res |= DP_LANE_CR_DONE;
+	if ((reg >> DPRX_RX_STATUS_CR_LOCK(3)) & 1)
+		res |= DP_LANE_CR_DONE << 4;
+	if ((reg >> DPRX_RX_STATUS_SYM_LOCK(2)) & 1)
+		res |= DP_LANE_CHANNEL_EQ_DONE | DP_LANE_SYMBOL_LOCKED;
+	if ((reg >> DPRX_RX_STATUS_SYM_LOCK(3)) & 1)
+		res |= (DP_LANE_CHANNEL_EQ_DONE | DP_LANE_SYMBOL_LOCKED) << 4;
+
+	return res;
+}
+
+static u8 dprx_read_lane_align_status(struct dprx *dprx)
+{
+	return (dprx_read(dprx, DPRX_RX_STATUS) >> DPRX_RX_STATUS_INTERLANE_ALIGN) & 1;
+}
+
+static u8 dprx_read_sink_status(struct dprx *dprx)
+{
+	return (dprx_read(dprx, DPRX_VBID(0)) >> DPRX_VBID_MSA_LOCK) & 1;
+}
+
+static u8 dprx_read_adjust_request(struct dprx *dprx,
+				   struct dprx_training_control *ctl0,
+				   struct dprx_training_control *ctl1)
+{
+	u8 next_volt_swing0;
+	u8 next_pre_emph0;
+	u8 next_volt_swing1;
+	u8 next_pre_emph1;
+
+	if (dprx_adjust_needed(dprx)) {
+		dprx_training_control_next(ctl0, &next_volt_swing0, &next_pre_emph0);
+		dprx_training_control_next(ctl1, &next_volt_swing1, &next_pre_emph1);
+	} else {
+		next_volt_swing0 = ctl0->volt_swing;
+		next_pre_emph0 = ctl0->pre_emph;
+		next_volt_swing1 = ctl1->volt_swing;
+		next_pre_emph1 = ctl1->pre_emph;
+	}
+
+	return next_volt_swing0 << DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT |
+	       next_pre_emph0   << DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT  |
+	       next_volt_swing1 << DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT |
+	       next_pre_emph1   << DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT;
+}
+
+static u8 dprx_read_adjust_request_lane0_1(struct dprx *dprx)
+{
+	return dprx_read_adjust_request(dprx,
+					&dprx->training_control[0],
+					&dprx->training_control[1]);
+}
+
+static u8 dprx_read_adjust_request_lane2_3(struct dprx *dprx)
+{
+	return dprx_read_adjust_request(dprx,
+					&dprx->training_control[2],
+					&dprx->training_control[3]);
+}
+
+static u8 dprx_read_payload_table_update_status(struct dprx *dprx)
+{
+	u32 reg;
+	u32 act_handled;
+	u8 result = 0;
+
+	reg = dprx_read(dprx, DPRX_MST_STATUS1);
+	act_handled = (reg >> DPRX_MST_STATUS1_VCPTAB_ACT_ACK) & 1;
+
+	if (dprx->payload_table_updated)
+		result |= DP_PAYLOAD_TABLE_UPDATED;
+	if (act_handled)
+		result |= DP_PAYLOAD_ACT_HANDLED;
+
+	return result;
+}
+
+static void dprx_write_payload_table_update_status(struct dprx *dprx, u8 val)
+{
+	u32 reg;
+
+	if (val & DP_PAYLOAD_TABLE_UPDATED) {
+		dprx->payload_table_updated = 0;
+		reg = dprx_read(dprx, DPRX_MST_CONTROL1);
+		reg &= ~(1 << DPRX_MST_CONTROL1_VCPTAB_UPD_REQ);
+		dprx_write(dprx, DPRX_MST_CONTROL1, reg);
+	}
+}
+
+static u8 dprx_read_vc_payload_id_slot(struct dprx *dprx, u32 offset)
+{
+	return dprx->payload_table[offset + 1];
+}
+
+static u8 dprx_read_down_req(struct dprx *dprx, u32 offset)
+{
+	return dprx->down_req_buf[offset];
+}
+
+static void dprx_write_down_req(struct dprx *dprx, u32 offset, u8 val)
+{
+	struct sideband_msg msg;
+
+	dprx->down_req_buf[offset] = val;
+	if (dprx_decode_sideband_msg(&msg, dprx->down_req_buf, offset + 1))
+		dprx_handle_sideband_msg(dprx, &msg);
+}
+
+static u8 dprx_read_down_rep(struct dprx *dprx, u32 offset)
+{
+	return dprx->down_rep_buf[offset];
+}
+
+struct dprx_dpcd_handler {
+	u32 addr;
+	u32 range_len;
+	union {
+		u8 (*point)(struct dprx *dprx);
+		u8 (*range)(struct dprx *dprx, u32 offset);
+	} read;
+	union {
+		void (*point)(struct dprx *dprx, u8 val);
+		void (*range)(struct dprx *dprx, u32 offset, u8 val);
+	} write;
+};
+
+static void dprx_write_noop(struct dprx *dprx, u8 val)
+{
+}
+
+static void dprx_write_noop_range(struct dprx *dprx, u32 offset, u8 val)
+{
+}
+
+static struct dprx_dpcd_handler dprx_dpcd_handlers[] = {
+	{ 0x00000, 16,	{ .range = dprx_read_caps },
+			{ .range = dprx_write_noop_range } },
+	{ 0x00021, 0,	{ .point = dprx_read_mstm_cap },
+			{ .point = dprx_write_noop } },
+	{ 0x00030, 16,	{ .range = dprx_read_guid },
+			{ .range = dprx_write_guid } },
+	{ 0x00100, 0,	{ .point = dprx_read_link_bw },
+			{ .point = dprx_write_link_bw } },
+	{ 0x00101, 0,	{ .point = dprx_read_lane_count },
+			{ .point = dprx_write_lane_count } },
+	{ 0x00102, 0,	{ .point = dprx_read_training_pattern },
+			{ .point = dprx_write_training_pattern } },
+	{ 0x00103, 4,	{ .range = dprx_read_training_lane },
+			{ .range = dprx_write_training_lane } },
+	{ 0x00111, 0,	{ .point = dprx_read_mstm_ctrl },
+			{ .point = dprx_write_mstm_ctrl } },
+	{ 0x001c0, 0,	{ .point = dprx_read_payload_allocate_set },
+			{ .point = dprx_write_payload_allocate_set } },
+	{ 0x001c1, 0,	{ .point = dprx_read_payload_allocate_start_time_slot },
+			{ .point = dprx_write_payload_allocate_start_time_slot } },
+	{ 0x001c2, 0,	{ .point = dprx_read_payload_allocate_time_slot_count },
+			{ .point = dprx_write_payload_allocate_time_slot_count } },
+	{ 0x00200, 0,	{ .point = dprx_read_sink_count },
+			{ .point = dprx_write_noop } },
+	{ 0x00201, 0,	{ .point = dprx_read_device_service_irq_vector },
+			{ .point = dprx_write_device_service_irq_vector } },
+	{ 0x00202, 0,	{ .point = dprx_read_lane0_1_status },
+			{ .point = dprx_write_noop } },
+	{ 0x00203, 0,	{ .point = dprx_read_lane2_3_status },
+			{ .point = dprx_write_noop } },
+	{ 0x00204, 0,	{ .point = dprx_read_lane_align_status },
+			{ .point = dprx_write_noop } },
+	{ 0x00205, 0,	{ .point = dprx_read_sink_status },
+			{ .point = dprx_write_noop } },
+	{ 0x00206, 0,	{ .point = dprx_read_adjust_request_lane0_1 },
+			{ .point = dprx_write_noop } },
+	{ 0x00207, 0,	{ .point = dprx_read_adjust_request_lane2_3 },
+			{ .point = dprx_write_noop } },
+	{ 0x002c0, 0,	{ .point = dprx_read_payload_table_update_status },
+			{ .point = dprx_write_payload_table_update_status } },
+	{ 0x002c1, 63,	{ .range = dprx_read_vc_payload_id_slot },
+			{ .range = dprx_write_noop_range } },
+	{ 0x01000, 48,	{ .range = dprx_read_down_req },
+			{ .range = dprx_write_down_req } },
+	{ 0x01400, 48,	{ .range = dprx_read_down_rep },
+			{ .range = dprx_write_noop_range } },
+	/* Event Status Indicator is a copy of 200h - 205h */
+	{ 0x02002, 0,	{ .point = dprx_read_sink_count },
+			{ .point = dprx_write_noop } },
+	{ 0x02003, 0,	{ .point = dprx_read_device_service_irq_vector },
+			{ .point = dprx_write_device_service_irq_vector } },
+	{ 0x0200c, 0,	{ .point = dprx_read_lane0_1_status },
+			{ .point = dprx_write_noop } },
+	{ 0x0200d, 0,	{ .point = dprx_read_lane2_3_status },
+			{ .point = dprx_write_noop } },
+	{ 0x0200e, 0,	{ .point = dprx_read_lane_align_status },
+			{ .point = dprx_write_noop } },
+	{ 0x0200f, 0,	{ .point = dprx_read_sink_status },
+			{ .point = dprx_write_noop } },
+	/* Extended Receiver Capability is a copy of 0h - 0fh */
+	{ 0x02200, 16,	{ .range = dprx_read_caps },
+			{ .range = dprx_write_noop_range } },
+};
+
+static bool dprx_dpcd_handler_match(struct dprx_dpcd_handler *handler, u32 addr)
+{
+	if (handler->range_len == 0)
+		return addr == handler->addr;
+	else
+		return addr >= handler->addr && addr < handler->addr + handler->range_len;
+}
+
+static void dprx_dpcd_handler_run(struct dprx_dpcd_handler *handler,
+				  struct dprx *dprx, u32 addr, u8 *val, bool read)
+{
+	if (read) {
+		if (handler->range_len == 0)
+			*val = handler->read.point(dprx);
+		else
+			*val = handler->read.range(dprx, addr - handler->addr);
+	} else {
+		if (handler->range_len == 0)
+			handler->write.point(dprx, *val);
+		else
+			handler->write.range(dprx, addr - handler->addr, *val);
+	}
+}
+
+static void dprx_dpcd_access(struct dprx *dprx, u32 addr, u8 *val, bool read)
+{
+	struct dprx_dpcd_handler *handler;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(dprx_dpcd_handlers); i++) {
+		handler = &dprx_dpcd_handlers[i];
+		if (dprx_dpcd_handler_match(handler, addr)) {
+			dprx_dpcd_handler_run(handler, dprx, addr, val, read);
+			return;
+		}
+	}
+
+	/* for unsupported registers, writes are ignored and reads return 0. */
+	if (read)
+		*val = 0;
+}
+
+static void dprx_handle_native_aux(struct dprx *dprx, struct aux_msg *req, struct aux_msg *rep)
+{
+	bool read = req->cmd & 1;
+	u8 *data;
+	int i;
+
+	rep->cmd = DP_AUX_NATIVE_REPLY_ACK;
+	if (read) {
+		rep->len = req->len;
+		data = rep->data;
+	} else {
+		rep->len = 0;
+		data = req->data;
+	}
+
+	for (i = 0; i < req->len; i++)
+		dprx_dpcd_access(dprx, req->addr + i, data + i, read);
+}
+
+static void dprx_handle_i2c_read(struct dprx *dprx, struct aux_msg *req, struct aux_msg *rep)
+{
+	int res;
+
+	res = dprx_i2c_read(&dprx->sinks[0], req->addr, rep->data, req->len);
+	if (!res) {
+		rep->cmd = DP_AUX_I2C_REPLY_ACK;
+		rep->len = req->len;
+	} else {
+		rep->cmd = DP_AUX_I2C_REPLY_NACK;
+		rep->len = 0;
+	}
+}
+
+static void dprx_handle_i2c_write(struct dprx *dprx, struct aux_msg *req, struct aux_msg *rep)
+{
+	int res;
+
+	res = dprx_i2c_write(&dprx->sinks[0], req->addr, req->data, req->len);
+	if (!res)
+		rep->cmd = DP_AUX_I2C_REPLY_ACK;
+	else
+		rep->cmd = DP_AUX_I2C_REPLY_NACK;
+	rep->len = 0;
+}
+
+static void dprx_decode_aux_request(struct aux_msg *req, struct aux_buf *buf)
+{
+	req->cmd = buf->data[0] >> 4;
+	req->addr = (buf->data[0] & 0xf) << 16 | buf->data[1] << 8 | buf->data[2];
+	if (buf->len < 4) {
+		req->len = 0;
+	} else {
+		req->len = buf->data[3] + 1;
+		memcpy(req->data, &buf->data[4], req->len);
+	}
+}
+
+static void dprx_encode_aux_reply(struct aux_msg *rep, struct aux_buf *buf)
+{
+	buf->data[0] = rep->cmd << 4;
+	memcpy(&buf->data[1], rep->data, rep->len);
+	buf->len = rep->len + 1;
+}
+
+static void dprx_handle_aux(struct dprx *dprx, struct aux_buf *req_buf, struct aux_buf *rep_buf)
+{
+	struct aux_msg req;
+	struct aux_msg rep;
+
+	dprx_decode_aux_request(&req, req_buf);
+
+	if (req.cmd & 8) {
+		dprx_handle_native_aux(dprx, &req, &rep);
+	} else {
+		if (req.cmd & 1)
+			dprx_handle_i2c_read(dprx, &req, &rep);
+		else
+			dprx_handle_i2c_write(dprx, &req, &rep);
+		if (!(req.cmd & DP_AUX_I2C_MOT))
+			dprx_i2c_stop(&dprx->sinks[0]);
+	}
+
+	dprx_encode_aux_reply(&rep, rep_buf);
+}
+
+static int dprx_read_aux(struct dprx *dprx, struct aux_buf *buf)
+{
+	u32 control = dprx_read(dprx, DPRX_AUX_CONTROL);
+	int i;
+
+	/* check MSG_READY */
+	if (!((dprx_read(dprx, DPRX_AUX_STATUS) >> DPRX_AUX_STATUS_MSG_READY) & 1))
+		return -1;
+
+	/* read LENGTH */
+	buf->len = (control >> DPRX_AUX_CONTROL_LENGTH_SHIFT) & DPRX_AUX_CONTROL_LENGTH_MASK;
+	if (buf->len > 20)
+		buf->len = 20;
+
+	/* read request */
+	for (i = 0; i < buf->len; i++)
+		buf->data[i] = dprx_read(dprx, DPRX_AUX_COMMAND + i);
+
+	return 0;
+}
+
+static void dprx_write_aux(struct dprx *dprx, struct aux_buf *buf)
+{
+	u32 reg;
+	int i;
+
+	if (!((dprx_read(dprx, DPRX_AUX_STATUS) >> DPRX_AUX_STATUS_READY_TO_TX) & 1))
+		return;
+
+	if (buf->len > 17)
+		buf->len = 17;
+	for (i = 0; i < buf->len; i++)
+		dprx_write(dprx, DPRX_AUX_COMMAND + i, buf->data[i]);
+
+	reg = dprx_read(dprx, DPRX_AUX_CONTROL);
+	reg &= ~(DPRX_AUX_CONTROL_LENGTH_MASK << DPRX_AUX_CONTROL_LENGTH_SHIFT);
+	reg |= buf->len << DPRX_AUX_CONTROL_LENGTH_SHIFT;
+	reg |= 1 << DPRX_AUX_CONTROL_TX_STROBE;
+	dprx_write(dprx, DPRX_AUX_CONTROL, reg);
+}
+
+static irqreturn_t dprx_isr(int irq, void *data)
+{
+	struct dprx *dprx = data;
+	struct aux_buf request;
+	struct aux_buf reply;
+	unsigned int reg;
+
+	reg = readl(dprx->iobase_irq + DPRX_IRQ_CLR);
+	if (!reg)
+		return IRQ_NONE;
+
+	if (!dprx_read_aux(dprx, &request)) {
+		spin_lock(&dprx->lock);
+		dprx_handle_aux(dprx, &request, &reply);
+		spin_unlock(&dprx->lock);
+		dprx_write_aux(dprx, &reply);
+	}
+
+	writel(reg, dprx->iobase_irq + DPRX_IRQ_CLR);
+
+	return IRQ_HANDLED;
+}
+
+static void dprx_reset_hw(struct dprx *dprx)
+{
+	int i;
+
+	/* set link rate to 1.62 Gbps and lane count to 1 */
+	dprx_write(dprx, DPRX_RX_CONTROL,
+		   DP_LINK_BW_1_62 << DPRX_RX_CONTROL_LINK_RATE_SHIFT |
+		   1 << DPRX_RX_CONTROL_RECONFIG_LINKRATE |
+		   DPRX_RX_CONTROL_CHANNEL_CODING_8B10B << DPRX_RX_CONTROL_CHANNEL_CODING_SHIFT |
+		   1 << DPRX_RX_CONTROL_LANE_COUNT_SHIFT);
+	/* clear VC payload ID table */
+	for (i = 0; i < 8; i++)
+		dprx_write(dprx, DPRX_MST_VCPTAB(i), 0);
+	dprx_write(dprx, DPRX_MST_CONTROL1, 1 << DPRX_MST_CONTROL1_VCPTAB_UPD_FORCE);
+}
+
+static void dprx_reset(struct dprx *dprx)
+{
+	int i;
+
+	memset(dprx->guid, 0, sizeof(dprx->guid));
+	memset(dprx->training_control, 0, sizeof(dprx->training_control));
+
+	dprx->payload_allocate_set = 0;
+	dprx->payload_allocate_start_time_slot = 0;
+	dprx->payload_allocate_time_slot_count = 0;
+	memset(dprx->payload_table, 0, sizeof(dprx->payload_table));
+	dprx->payload_table_updated = 0;
+
+	memset(dprx->payload_id, 0, sizeof(dprx->payload_id));
+	memset(dprx->payload_pbn, 0, sizeof(dprx->payload_pbn));
+	dprx->payload_pbn_total = 0;
+
+	dprx->irq_vector = 0;
+
+	memset(dprx->down_req_buf, 0, sizeof(dprx->down_req_buf));
+	memset(dprx->down_rep_buf, 0, sizeof(dprx->down_rep_buf));
+
+	for (i = 0; i < 2; i++) {
+		dprx_msg_transaction_clear_rxbuf(&dprx->mt_rxbuf[i]);
+		dprx_msg_transaction_clear_txbuf(&dprx->mt_txbuf[i]);
+	}
+	dprx->mt_seqno = 0;
+	dprx->mt_pending = false;
+	dprx->down_rep_pending = false;
+
+	dprx_reset_hw(dprx);
+}
+
+#define to_dprx(sd) container_of(sd, struct dprx, subdev)
+
+static int dprx_get_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid)
+{
+	struct dprx *dprx = to_dprx(sd);
+	struct dprx_sink *sink;
+	u32 end_block = edid->start_block + edid->blocks;
+	unsigned long flags;
+	int res = 0;
+
+	memset(edid->reserved, 0, sizeof(edid->reserved));
+
+	if (edid->pad >= dprx->sink_count)
+		return -EINVAL;
+
+	spin_lock_irqsave(&dprx->lock, flags);
+
+	sink = &dprx->sinks[edid->pad];
+	if (edid->start_block == 0 && edid->blocks == 0) {
+		edid->blocks = sink->blocks;
+		goto out;
+	}
+	if (sink->blocks == 0) {
+		res = -ENODATA;
+		goto out;
+	}
+	if (edid->start_block >= sink->blocks) {
+		res = -EINVAL;
+		goto out;
+	}
+	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);
+
+out:
+	spin_unlock_irqrestore(&dprx->lock, flags);
+
+	return res;
+}
+
+static int dprx_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid)
+{
+	struct dprx *dprx = to_dprx(sd);
+	struct dprx_sink *sink;
+	bool prev_hpd;
+	bool cur_hpd;
+	unsigned long flags;
+
+	memset(edid->reserved, 0, sizeof(edid->reserved));
+
+	if (edid->pad >= dprx->sink_count)
+		return -EINVAL;
+	if (edid->start_block != 0)
+		return -EINVAL;
+	if (edid->blocks > DPRX_MAX_EDID_BLOCKS) {
+		edid->blocks = DPRX_MAX_EDID_BLOCKS;
+		return -E2BIG;
+	}
+
+	spin_lock_irqsave(&dprx->lock, flags);
+	sink = &dprx->sinks[edid->pad];
+	/*
+	 * This is an MST DisplayPort device, which means that one HPD
+	 * line controls all the video streams. The way this is handled
+	 * in s_edid is that the HPD line is controlled by the presence
+	 * of only the first stream's EDID. This allows, for example, to
+	 * first set the second streams's EDID and then the first one in
+	 * order to reduce the amount of AUX communication.
+	 */
+	prev_hpd = dprx->sinks[0].blocks > 0;
+	sink->blocks = edid->blocks;
+	memcpy(sink->edid, edid->edid, edid->blocks * 128);
+	cur_hpd = dprx->sinks[0].blocks > 0;
+	if (!prev_hpd && cur_hpd)
+		dprx_reset(dprx);
+	spin_unlock_irqrestore(&dprx->lock, flags);
+
+	if (!prev_hpd && cur_hpd) {
+		dprx_set_hpd(dprx, 1);
+	} else if (prev_hpd && !cur_hpd) {
+		dprx_set_hpd(dprx, 0);
+	} else if (prev_hpd && cur_hpd) {
+		/* HPD replug - pulse for >2ms */
+		dprx_set_hpd(dprx, 0);
+		usleep_range(2000, 4000);
+		dprx_set_hpd(dprx, 1);
+	}
+
+	return 0;
+}
+
+static int dprx_query_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
+				 struct v4l2_dv_timings *timings)
+{
+	struct dprx *dprx = to_dprx(sd);
+	u32 htotal, vtotal;
+	u32 hsp, hsw;
+	u32 hstart, vstart;
+	u32 vsp, vsw;
+	u32 hwidth, vheight;
+
+	if (pad >= dprx->sink_count)
+		return -EINVAL;
+
+	if (!((dprx_read(dprx, DPRX_VBID(pad)) >> DPRX_VBID_MSA_LOCK) & 1))
+		return -ENOLINK;
+
+	htotal  = dprx_read(dprx, DPRX_MSA_HTOTAL(pad));
+	vtotal  = dprx_read(dprx, DPRX_MSA_VTOTAL(pad));
+	hsp     = dprx_read(dprx, DPRX_MSA_HSP(pad));
+	hsw     = dprx_read(dprx, DPRX_MSA_HSW(pad));
+	hstart  = dprx_read(dprx, DPRX_MSA_HSTART(pad));
+	vstart  = dprx_read(dprx, DPRX_MSA_VSTART(pad));
+	vsp     = dprx_read(dprx, DPRX_MSA_VSP(pad));
+	vsw     = dprx_read(dprx, DPRX_MSA_VSW(pad));
+	hwidth  = dprx_read(dprx, DPRX_MSA_HWIDTH(pad));
+	vheight = dprx_read(dprx, DPRX_MSA_VHEIGHT(pad));
+
+	memset(timings, 0, sizeof(*timings));
+	timings->type = V4L2_DV_BT_656_1120;
+	timings->bt.width = hwidth;
+	timings->bt.height = vheight;
+	timings->bt.polarities = (!vsp) | (!hsp) << 1;
+	timings->bt.hfrontporch = htotal - hstart - hwidth;
+	timings->bt.hsync = hsw;
+	timings->bt.hbackporch = hstart - hsw;
+	timings->bt.vfrontporch = vtotal - vstart - vheight;
+	timings->bt.vsync = vsw;
+	timings->bt.vbackporch = vstart - vsw;
+
+	return 0;
+}
+
+/* DisplayPort 1.4 capabilities */
+
+static const struct v4l2_dv_timings_cap dprx_timings_cap = {
+	.type = V4L2_DV_BT_656_1120,
+	.bt = {
+		.min_width = 0,
+		.max_width = 7680,
+		.min_height = 0,
+		.max_height = 4320,
+		.min_pixelclock = 0,
+		.max_pixelclock = 1350000000, /* 8.1Gbps * 4lanes / 24bpp */
+		.standards = V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT |
+			V4L2_DV_BT_STD_CVT | V4L2_DV_BT_STD_GTF,
+		.capabilities = V4L2_DV_BT_CAP_PROGRESSIVE |
+			V4L2_DV_BT_CAP_REDUCED_BLANKING |
+			V4L2_DV_BT_CAP_CUSTOM,
+	},
+};
+
+static int dprx_enum_dv_timings(struct v4l2_subdev *sd, struct v4l2_enum_dv_timings *timings)
+{
+	return v4l2_enum_dv_timings_cap(timings, &dprx_timings_cap,
+					NULL, NULL);
+}
+
+static int dprx_dv_timings_cap(struct v4l2_subdev *sd, struct v4l2_dv_timings_cap *cap)
+{
+	*cap = dprx_timings_cap;
+
+	return 0;
+}
+
+static const struct v4l2_subdev_pad_ops dprx_pad_ops = {
+	.get_edid = dprx_get_edid,
+	.set_edid = dprx_set_edid,
+	.dv_timings_cap = dprx_dv_timings_cap,
+	.enum_dv_timings = dprx_enum_dv_timings,
+	.query_dv_timings = dprx_query_dv_timings,
+};
+
+static const struct v4l2_subdev_ops dprx_subdev_ops = {
+	.pad = &dprx_pad_ops,
+};
+
+static const struct media_entity_operations dprx_entity_ops = {
+	.link_validate = v4l2_subdev_link_validate,
+	.get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
+};
+
+static int dprx_init_pads(struct dprx *dprx)
+{
+	int i;
+
+	for (i = 0; i < dprx->sink_count; i++)
+		dprx->pads[i].flags = MEDIA_PAD_FL_SOURCE;
+
+	return media_entity_pads_init(&dprx->subdev.entity, dprx->sink_count, dprx->pads);
+}
+
+static int dprx_probe(struct platform_device *pdev)
+{
+	struct dprx *dprx;
+	int irq;
+	int res;
+
+	dprx = devm_kzalloc(&pdev->dev, sizeof(*dprx), GFP_KERNEL);
+	if (!dprx)
+		return -ENOMEM;
+	dprx->dev = &pdev->dev;
+	platform_set_drvdata(pdev, dprx);
+
+	dprx->iobase = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(dprx->iobase))
+		return PTR_ERR(dprx->iobase);
+
+	dprx->iobase_irq = devm_platform_ioremap_resource(pdev, 1);
+	if (IS_ERR(dprx->iobase_irq))
+		return PTR_ERR(dprx->iobase_irq);
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	res = devm_request_irq(dprx->dev, irq, dprx_isr, 0, "intel-dprx", dprx);
+	if (res)
+		return res;
+
+	dprx->has_mst = device_property_read_bool(&pdev->dev, "intel,has-mst");
+	dprx->sink_count = dprx->has_mst ? 4 : 1;
+
+	dprx->subdev.owner = THIS_MODULE;
+	dprx->subdev.dev = &pdev->dev;
+	v4l2_subdev_init(&dprx->subdev, &dprx_subdev_ops);
+	v4l2_set_subdevdata(&dprx->subdev, &pdev->dev);
+	snprintf(dprx->subdev.name, sizeof(dprx->subdev.name), "%s %s",
+		 KBUILD_MODNAME, dev_name(&pdev->dev));
+	dprx->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+	dprx->subdev.entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
+	dprx->subdev.entity.ops = &dprx_entity_ops;
+
+	res = dprx_init_pads(dprx);
+	if (res)
+		return res;
+
+	res = v4l2_async_register_subdev(&dprx->subdev);
+	if (res)
+		return res;
+
+	dprx_set_hpd(dprx, 0);
+	dprx_reset_hw(dprx);
+
+	dprx_set_irq(dprx, 1);
+	writel(DPRX_IRQ_AUX, dprx->iobase_irq + DPRX_IRQ_MASK);
+
+	return 0;
+}
+
+static void dprx_remove(struct platform_device *pdev)
+{
+	struct dprx *dprx = platform_get_drvdata(pdev);
+
+	/* disable interrupts */
+	writel(0, dprx->iobase_irq + DPRX_IRQ_MASK);
+	dprx_set_irq(dprx, 0);
+
+	v4l2_async_unregister_subdev(&dprx->subdev);
+}
+
+static const struct of_device_id dprx_match_table[] = {
+	{ .compatible = "intel,dprx" },
+	{ },
+};
+
+static struct platform_driver dprx_platform_driver = {
+	.probe = dprx_probe,
+	.remove_new = dprx_remove,
+	.driver = {
+		.name = "intel-dprx",
+		.of_match_table = dprx_match_table,
+	},
+};
+
+module_platform_driver(dprx_platform_driver);
+
+MODULE_AUTHOR("Paweł Anikiel <panikiel@google.com>");
+MODULE_DESCRIPTION("Intel DisplayPort RX IP core driver");
+MODULE_LICENSE("GPL");
-- 
2.43.0.687.g38aa6559b0-goog


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH 7/9] media: dt-bindings: Add Chameleon v3 framebuffer
  2024-02-12 13:13 [PATCH 0/9] Add Chameleon v3 video support Paweł Anikiel
                   ` (5 preceding siblings ...)
  2024-02-12 13:13 ` [PATCH 6/9] media: intel: Add Displayport RX IP driver Paweł Anikiel
@ 2024-02-12 13:13 ` Paweł Anikiel
  2024-02-12 14:35   ` Rob Herring
  2024-02-15 17:29   ` Conor Dooley
  2024-02-12 13:13 ` [PATCH 8/9] media: dt-bindings: Add Intel Displayport RX IP Paweł Anikiel
  2024-02-12 13:13 ` [PATCH 9/9] ARM: dts: chameleonv3: Add video device nodes Paweł Anikiel
  8 siblings, 2 replies; 19+ messages in thread
From: Paweł Anikiel @ 2024-02-12 13:13 UTC (permalink / raw)
  To: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
	krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
	robh+dt, tzimmermann
  Cc: devicetree, dri-devel, linux-kernel, linux-media,
	chromeos-krk-upstreaming, ribalda, Paweł Anikiel

The Chameleon v3 uses the framebuffer IP core to take the video signal
from different sources and directly write frames into memory.

Signed-off-by: Paweł Anikiel <panikiel@google.com>
---
 .../bindings/media/google,chv3-fb.yaml        | 77 +++++++++++++++++++
 1 file changed, 77 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/google,chv3-fb.yaml

diff --git a/Documentation/devicetree/bindings/media/google,chv3-fb.yaml b/Documentation/devicetree/bindings/media/google,chv3-fb.yaml
new file mode 100644
index 000000000000..ba6643cc7232
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/google,chv3-fb.yaml
@@ -0,0 +1,77 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/google,chv3-fb.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Google Chameleon v3 video framebuffer
+
+maintainers:
+  - Paweł Anikiel <panikiel@google.com>
+
+properties:
+  compatible:
+    const: google,chv3-fb
+
+  reg:
+    items:
+      - description: core registers
+      - description: irq registers
+
+  interrupts:
+    maxItems: 1
+
+  google,legacy-format:
+    type: boolean
+    description: The incoming video stream is in 32-bit padded mode.
+
+  google,no-endpoint:
+    type: boolean
+    description:
+      The framebuffer isn't connected to a controllable endpoint.
+      The video interface still works, but EDID control is unavailable
+      and DV timing information only reports the active video width/height.
+
+  port:
+    $ref: /schemas/graph.yaml#/properties/port
+
+required:
+  - compatible
+  - reg
+  - interrupts
+
+allOf:
+  - if:
+      not:
+        required:
+          - google,no-endpoint
+    then:
+      required:
+        - port
+
+additionalProperties: false
+
+examples:
+  - |
+    video@c0060500 {
+        compatible = "google,chv3-fb";
+        reg = <0xc0060500 0x100>,
+              <0xc0060f20 0x10>;
+        interrupts = <GIC_SPI 19 IRQ_TYPE_LEVEL_HIGH>;
+        google,legacy-format;
+        google,no-endpoint;
+    };
+
+  - |
+    video@c0060600 {
+        compatible = "google,chv3-fb";
+        reg = <0xc0060600 0x100>,
+              <0xc0060f30 0x10>;
+        interrupts = <GIC_SPI 22 IRQ_TYPE_LEVEL_HIGH>;
+
+        port {
+            fb_mst0_0: endpoint {
+                remote-endpoint = <&dprx_mst_0>;
+            };
+        };
+    };
-- 
2.43.0.687.g38aa6559b0-goog


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH 8/9] media: dt-bindings: Add Intel Displayport RX IP
  2024-02-12 13:13 [PATCH 0/9] Add Chameleon v3 video support Paweł Anikiel
                   ` (6 preceding siblings ...)
  2024-02-12 13:13 ` [PATCH 7/9] media: dt-bindings: Add Chameleon v3 framebuffer Paweł Anikiel
@ 2024-02-12 13:13 ` Paweł Anikiel
  2024-02-12 14:35   ` Rob Herring
  2024-02-15 17:26   ` Conor Dooley
  2024-02-12 13:13 ` [PATCH 9/9] ARM: dts: chameleonv3: Add video device nodes Paweł Anikiel
  8 siblings, 2 replies; 19+ messages in thread
From: Paweł Anikiel @ 2024-02-12 13:13 UTC (permalink / raw)
  To: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
	krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
	robh+dt, tzimmermann
  Cc: devicetree, dri-devel, linux-kernel, linux-media,
	chromeos-krk-upstreaming, ribalda, Paweł Anikiel

The Intel Displayport RX IP is a part of the DisplayPort Intel FPGA IP
Core. It implements a DisplayPort 1.4 receiver capable of HBR3 video
capture and Multi-Stream Transport. The user guide can be found here:

https://www.intel.com/programmable/technical-pdfs/683273.pdf

Signed-off-by: Paweł Anikiel <panikiel@google.com>
---
 .../devicetree/bindings/media/intel,dprx.yaml | 125 ++++++++++++++++++
 1 file changed, 125 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/intel,dprx.yaml

diff --git a/Documentation/devicetree/bindings/media/intel,dprx.yaml b/Documentation/devicetree/bindings/media/intel,dprx.yaml
new file mode 100644
index 000000000000..3ed37e0a4a94
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/intel,dprx.yaml
@@ -0,0 +1,125 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/intel,dprx.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Intel DisplayPort RX IP
+
+maintainers:
+  - Paweł Anikiel <panikiel@google.com>
+
+properties:
+  compatible:
+    const: intel,dprx
+
+  reg:
+    items:
+      - description: core registers
+      - description: irq registers
+
+  interrupts:
+    maxItems: 1
+
+  intel,has-mst:
+    type: boolean
+    description: The device supports Multi-Stream Transport
+
+  port:
+    $ref: /schemas/graph.yaml#/properties/port
+    description: SST main link
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+
+    properties:
+      port@0:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: MST virtual channel 0 or SST main link
+
+      port@1:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: MST virtual channel 1
+
+      port@2:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: MST virtual channel 2
+
+      port@3:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: MST virtual channel 3
+
+required:
+  - compatible
+  - reg
+  - interrupts
+
+allOf:
+  - if:
+      required:
+        - intel,has-mst
+    then:
+      required:
+        - ports
+    else:
+      required:
+        - port
+
+additionalProperties: false
+
+examples:
+  - |
+    dprx@c0062000 {
+        compatible = "intel,dprx";
+        reg = <0xc0062000 0x800>,
+              <0xc0060f80 0x10>;
+        interrupts = <GIC_SPI 27 IRQ_TYPE_LEVEL_HIGH>;
+        intel,has-mst;
+
+        ports {
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            port@0 {
+                reg = <0>;
+                dprx_mst_0: endpoint {
+                    remote-endpoint = <&fb_mst0_0>;
+                };
+            };
+
+            port@1 {
+                reg = <1>;
+                dprx_mst_1: endpoint {
+                    remote-endpoint = <&fb_mst1_0>;
+                };
+            };
+
+            port@2 {
+                reg = <2>;
+                dprx_mst_2: endpoint {
+                    remote-endpoint = <&fb_mst2_0>;
+                };
+            };
+
+            port@3 {
+                reg = <3>;
+                dprx_mst_3: endpoint {
+                    remote-endpoint = <&fb_mst3_0>;
+                };
+            };
+        };
+    };
+
+  - |
+    dprx@c0064000 {
+        compatible = "intel,dprx";
+        reg = <0xc0064000 0x800>,
+              <0xc0060fe0 0x10>;
+        interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
+
+        port {
+            dprx_sst_0: endpoint {
+                remote-endpoint = <&fb_sst_0>;
+            };
+        };
+    };
-- 
2.43.0.687.g38aa6559b0-goog


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH 9/9] ARM: dts: chameleonv3: Add video device nodes
  2024-02-12 13:13 [PATCH 0/9] Add Chameleon v3 video support Paweł Anikiel
                   ` (7 preceding siblings ...)
  2024-02-12 13:13 ` [PATCH 8/9] media: dt-bindings: Add Intel Displayport RX IP Paweł Anikiel
@ 2024-02-12 13:13 ` Paweł Anikiel
  8 siblings, 0 replies; 19+ messages in thread
From: Paweł Anikiel @ 2024-02-12 13:13 UTC (permalink / raw)
  To: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
	krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
	robh+dt, tzimmermann
  Cc: devicetree, dri-devel, linux-kernel, linux-media,
	chromeos-krk-upstreaming, ribalda, Paweł Anikiel

Add device nodes for the video system present on the Chameleon v3.
It consists of six framebuffers and two Intel Displayport receivers.

Signed-off-by: Paweł Anikiel <panikiel@google.com>
---
 .../socfpga/socfpga_arria10_chameleonv3.dts   | 130 ++++++++++++++++++
 1 file changed, 130 insertions(+)

diff --git a/arch/arm/boot/dts/intel/socfpga/socfpga_arria10_chameleonv3.dts b/arch/arm/boot/dts/intel/socfpga/socfpga_arria10_chameleonv3.dts
index 422d00cd4c74..9bc0c1672b18 100644
--- a/arch/arm/boot/dts/intel/socfpga/socfpga_arria10_chameleonv3.dts
+++ b/arch/arm/boot/dts/intel/socfpga/socfpga_arria10_chameleonv3.dts
@@ -10,6 +10,136 @@ / {
 	compatible = "google,chameleon-v3", "enclustra,mercury-aa1",
 		     "altr,socfpga-arria10", "altr,socfpga";
 
+	soc {
+		fb0: video@c0060500 {
+			compatible = "google,chv3-fb";
+			reg = <0xc0060500 0x100>,
+			      <0xc0060f20 0x10>;
+			interrupts = <GIC_SPI 19 IRQ_TYPE_LEVEL_HIGH>;
+			google,legacy-format;
+			google,no-endpoint;
+		};
+
+		fb_mst0: video@c0060600 {
+			compatible = "google,chv3-fb";
+			reg = <0xc0060600 0x100>,
+			      <0xc0060f30 0x10>;
+			interrupts = <GIC_SPI 22 IRQ_TYPE_LEVEL_HIGH>;
+
+			port {
+				fb_mst0_0: endpoint {
+					remote-endpoint = <&dprx_mst_0>;
+				};
+			};
+		};
+
+		fb_mst1: video@c0060700 {
+			compatible = "google,chv3-fb";
+			reg = <0xc0060700 0x100>,
+			      <0xc0060f40 0x10>;
+			interrupts = <GIC_SPI 23 IRQ_TYPE_LEVEL_HIGH>;
+
+			port {
+				fb_mst1_0: endpoint {
+					remote-endpoint = <&dprx_mst_1>;
+				};
+			};
+		};
+
+		fb_mst2: video@c0060800 {
+			compatible = "google,chv3-fb";
+			reg = <0xc0060800 0x100>,
+			      <0xc0060f50 0x10>;
+			interrupts = <GIC_SPI 24 IRQ_TYPE_LEVEL_HIGH>;
+
+			port {
+				fb_mst2_0: endpoint {
+					remote-endpoint = <&dprx_mst_2>;
+				};
+			};
+		};
+
+		fb_mst3: video@c0060900 {
+			compatible = "google,chv3-fb";
+			reg = <0xc0060900 0x100>,
+			      <0xc0060f60 0x10>;
+			interrupts = <GIC_SPI 25 IRQ_TYPE_LEVEL_HIGH>;
+
+			port {
+				fb_mst3_0: endpoint {
+					remote-endpoint = <&dprx_mst_3>;
+				};
+			};
+		};
+
+		fb_sst: video@c0060a00 {
+			compatible = "google,chv3-fb";
+			reg = <0xc0060a00 0x100>,
+			      <0xc0060f70 0x10>;
+			interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
+
+			port {
+				fb_sst_0: endpoint {
+					remote-endpoint = <&dprx_sst_0>;
+				};
+			};
+		};
+
+		dprx_mst: dprx@c0062000 {
+			compatible = "intel,dprx";
+			reg = <0xc0062000 0x800>,
+			      <0xc0060f80 0x10>;
+			interrupts = <GIC_SPI 27 IRQ_TYPE_LEVEL_HIGH>;
+			intel,has-mst;
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+
+				port@0 {
+					reg = <0>;
+					dprx_mst_0: endpoint {
+						remote-endpoint = <&fb_mst0_0>;
+					};
+				};
+
+				port@1 {
+					reg = <1>;
+					dprx_mst_1: endpoint {
+						remote-endpoint = <&fb_mst1_0>;
+					};
+				};
+
+				port@2 {
+					reg = <2>;
+					dprx_mst_2: endpoint {
+						remote-endpoint = <&fb_mst2_0>;
+					};
+				};
+
+				port@3 {
+					reg = <3>;
+					dprx_mst_3: endpoint {
+						remote-endpoint = <&fb_mst3_0>;
+					};
+				};
+			};
+		};
+
+		dprx_sst: dprx@c0064000 {
+			compatible = "intel,dprx";
+			reg = <0xc0064000 0x800>,
+			      <0xc0060fe0 0x10>;
+			interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
+
+			port {
+				dprx_sst_0: endpoint {
+					remote-endpoint = <&fb_sst_0>;
+				};
+			};
+		};
+	};
+
 	aliases {
 		serial0 = &uart0;
 		i2c0 = &i2c0;
-- 
2.43.0.687.g38aa6559b0-goog


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* Re: [PATCH 7/9] media: dt-bindings: Add Chameleon v3 framebuffer
  2024-02-12 13:13 ` [PATCH 7/9] media: dt-bindings: Add Chameleon v3 framebuffer Paweł Anikiel
@ 2024-02-12 14:35   ` Rob Herring
  2024-02-12 15:51     ` Paweł Anikiel
  2024-02-15 17:29   ` Conor Dooley
  1 sibling, 1 reply; 19+ messages in thread
From: Rob Herring @ 2024-02-12 14:35 UTC (permalink / raw)
  To: Paweł Anikiel
  Cc: chromeos-krk-upstreaming, tzimmermann, maarten.lankhorst,
	hverkuil-cisco, linux-kernel, dri-devel, mchehab, ribalda,
	robh+dt, mripard, airlied, linux-media, akpm, dinguyen,
	devicetree, daniel, krzysztof.kozlowski+dt, conor+dt


On Mon, 12 Feb 2024 13:13:21 +0000, Paweł Anikiel wrote:
> The Chameleon v3 uses the framebuffer IP core to take the video signal
> from different sources and directly write frames into memory.
> 
> Signed-off-by: Paweł Anikiel <panikiel@google.com>
> ---
>  .../bindings/media/google,chv3-fb.yaml        | 77 +++++++++++++++++++
>  1 file changed, 77 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/media/google,chv3-fb.yaml
> 

My bot found errors running 'make DT_CHECKER_FLAGS=-m dt_binding_check'
on your patch (DT_CHECKER_FLAGS is new in v5.13):

yamllint warnings/errors:

dtschema/dtc warnings/errors:
Error: Documentation/devicetree/bindings/media/google,chv3-fb.example.dts:28.27-28 syntax error
FATAL ERROR: Unable to parse input tree
make[2]: *** [scripts/Makefile.lib:419: Documentation/devicetree/bindings/media/google,chv3-fb.example.dtb] Error 1
make[2]: *** Waiting for unfinished jobs....
make[1]: *** [/builds/robherring/dt-review-ci/linux/Makefile:1428: dt_binding_check] Error 2
make: *** [Makefile:240: __sub-make] Error 2

doc reference errors (make refcheckdocs):

See https://patchwork.ozlabs.org/project/devicetree-bindings/patch/20240212131323.2162161-8-panikiel@google.com

The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH 8/9] media: dt-bindings: Add Intel Displayport RX IP
  2024-02-12 13:13 ` [PATCH 8/9] media: dt-bindings: Add Intel Displayport RX IP Paweł Anikiel
@ 2024-02-12 14:35   ` Rob Herring
  2024-02-12 15:52     ` Paweł Anikiel
  2024-02-15 17:26   ` Conor Dooley
  1 sibling, 1 reply; 19+ messages in thread
From: Rob Herring @ 2024-02-12 14:35 UTC (permalink / raw)
  To: Paweł Anikiel
  Cc: linux-kernel, linux-media, conor+dt, airlied, mripard, dinguyen,
	maarten.lankhorst, tzimmermann, robh+dt, hverkuil-cisco,
	devicetree, ribalda, daniel, chromeos-krk-upstreaming, mchehab,
	akpm, dri-devel, krzysztof.kozlowski+dt


On Mon, 12 Feb 2024 13:13:22 +0000, Paweł Anikiel wrote:
> The Intel Displayport RX IP is a part of the DisplayPort Intel FPGA IP
> Core. It implements a DisplayPort 1.4 receiver capable of HBR3 video
> capture and Multi-Stream Transport. The user guide can be found here:
> 
> https://www.intel.com/programmable/technical-pdfs/683273.pdf
> 
> Signed-off-by: Paweł Anikiel <panikiel@google.com>
> ---
>  .../devicetree/bindings/media/intel,dprx.yaml | 125 ++++++++++++++++++
>  1 file changed, 125 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/media/intel,dprx.yaml
> 

My bot found errors running 'make DT_CHECKER_FLAGS=-m dt_binding_check'
on your patch (DT_CHECKER_FLAGS is new in v5.13):

yamllint warnings/errors:

dtschema/dtc warnings/errors:
Error: Documentation/devicetree/bindings/media/intel,dprx.example.dts:28.27-28 syntax error
make[2]: *** [scripts/Makefile.lib:419: Documentation/devicetree/bindings/media/intel,dprx.example.dtb] Error 1

doc reference errors (make refcheckdocs):

See https://patchwork.ozlabs.org/project/devicetree-bindings/patch/20240212131323.2162161-9-panikiel@google.com

The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH 7/9] media: dt-bindings: Add Chameleon v3 framebuffer
  2024-02-12 14:35   ` Rob Herring
@ 2024-02-12 15:51     ` Paweł Anikiel
  2024-02-16  9:43       ` Krzysztof Kozlowski
  0 siblings, 1 reply; 19+ messages in thread
From: Paweł Anikiel @ 2024-02-12 15:51 UTC (permalink / raw)
  To: Rob Herring
  Cc: chromeos-krk-upstreaming, tzimmermann, maarten.lankhorst,
	hverkuil-cisco, linux-kernel, dri-devel, mchehab, ribalda,
	robh+dt, mripard, airlied, linux-media, akpm, dinguyen,
	devicetree, daniel, krzysztof.kozlowski+dt, conor+dt

On Mon, Feb 12, 2024 at 3:35 PM Rob Herring <robh@kernel.org> wrote:
>
>
> On Mon, 12 Feb 2024 13:13:21 +0000, Paweł Anikiel wrote:
> > The Chameleon v3 uses the framebuffer IP core to take the video signal
> > from different sources and directly write frames into memory.
> >
> > Signed-off-by: Paweł Anikiel <panikiel@google.com>
> > ---
> >  .../bindings/media/google,chv3-fb.yaml        | 77 +++++++++++++++++++
> >  1 file changed, 77 insertions(+)
> >  create mode 100644 Documentation/devicetree/bindings/media/google,chv3-fb.yaml
> >
>
> My bot found errors running 'make DT_CHECKER_FLAGS=-m dt_binding_check'
> on your patch (DT_CHECKER_FLAGS is new in v5.13):
>
> yamllint warnings/errors:
>
> dtschema/dtc warnings/errors:
> Error: Documentation/devicetree/bindings/media/google,chv3-fb.example.dts:28.27-28 syntax error
> FATAL ERROR: Unable to parse input tree
> make[2]: *** [scripts/Makefile.lib:419: Documentation/devicetree/bindings/media/google,chv3-fb.example.dtb] Error 1
> make[2]: *** Waiting for unfinished jobs....
> make[1]: *** [/builds/robherring/dt-review-ci/linux/Makefile:1428: dt_binding_check] Error 2
> make: *** [Makefile:240: __sub-make] Error 2
>
> doc reference errors (make refcheckdocs):
>
> See https://patchwork.ozlabs.org/project/devicetree-bindings/patch/20240212131323.2162161-8-panikiel@google.com
>
> The base for the series is generally the latest rc1. A different dependency
> should be noted in *this* patch.
>
> If you already ran 'make dt_binding_check' and didn't see the above
> error(s), then make sure 'yamllint' is installed and dt-schema is up to
> date:
>
> pip3 install dtschema --upgrade
>
> Please check and re-submit after running the above command yourself. Note
> that DT_SCHEMA_FILES can be set to your schema file to speed up checking
> your schema. However, it must be unset to test all examples with your schema.
>

I was missing a '#include
<dt-bindings/interrupt-controller/arm-gic.h>' in the dt binding
example. I ran dt_binding_check after adding it and it reports no
errors. I will include the fix in v2.

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH 8/9] media: dt-bindings: Add Intel Displayport RX IP
  2024-02-12 14:35   ` Rob Herring
@ 2024-02-12 15:52     ` Paweł Anikiel
  0 siblings, 0 replies; 19+ messages in thread
From: Paweł Anikiel @ 2024-02-12 15:52 UTC (permalink / raw)
  To: Rob Herring
  Cc: linux-kernel, linux-media, conor+dt, airlied, mripard, dinguyen,
	maarten.lankhorst, tzimmermann, robh+dt, hverkuil-cisco,
	devicetree, ribalda, daniel, chromeos-krk-upstreaming, mchehab,
	akpm, dri-devel, krzysztof.kozlowski+dt

On Mon, Feb 12, 2024 at 3:35 PM Rob Herring <robh@kernel.org> wrote:
>
>
> On Mon, 12 Feb 2024 13:13:22 +0000, Paweł Anikiel wrote:
> > The Intel Displayport RX IP is a part of the DisplayPort Intel FPGA IP
> > Core. It implements a DisplayPort 1.4 receiver capable of HBR3 video
> > capture and Multi-Stream Transport. The user guide can be found here:
> >
> > https://www.intel.com/programmable/technical-pdfs/683273.pdf
> >
> > Signed-off-by: Paweł Anikiel <panikiel@google.com>
> > ---
> >  .../devicetree/bindings/media/intel,dprx.yaml | 125 ++++++++++++++++++
> >  1 file changed, 125 insertions(+)
> >  create mode 100644 Documentation/devicetree/bindings/media/intel,dprx.yaml
> >
>
> My bot found errors running 'make DT_CHECKER_FLAGS=-m dt_binding_check'
> on your patch (DT_CHECKER_FLAGS is new in v5.13):
>
> yamllint warnings/errors:
>
> dtschema/dtc warnings/errors:
> Error: Documentation/devicetree/bindings/media/intel,dprx.example.dts:28.27-28 syntax error
> make[2]: *** [scripts/Makefile.lib:419: Documentation/devicetree/bindings/media/intel,dprx.example.dtb] Error 1
>
> doc reference errors (make refcheckdocs):
>
> See https://patchwork.ozlabs.org/project/devicetree-bindings/patch/20240212131323.2162161-9-panikiel@google.com
>
> The base for the series is generally the latest rc1. A different dependency
> should be noted in *this* patch.
>
> If you already ran 'make dt_binding_check' and didn't see the above
> error(s), then make sure 'yamllint' is installed and dt-schema is up to
> date:
>
> pip3 install dtschema --upgrade
>
> Please check and re-submit after running the above command yourself. Note
> that DT_SCHEMA_FILES can be set to your schema file to speed up checking
> your schema. However, it must be unset to test all examples with your schema.
>

As with the previous patch, I was missing a #include in the example. I
will include the fix in v2.

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH 8/9] media: dt-bindings: Add Intel Displayport RX IP
  2024-02-12 13:13 ` [PATCH 8/9] media: dt-bindings: Add Intel Displayport RX IP Paweł Anikiel
  2024-02-12 14:35   ` Rob Herring
@ 2024-02-15 17:26   ` Conor Dooley
  2024-02-21 16:30     ` Paweł Anikiel
  1 sibling, 1 reply; 19+ messages in thread
From: Conor Dooley @ 2024-02-15 17:26 UTC (permalink / raw)
  To: Paweł Anikiel
  Cc: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
	krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
	robh+dt, tzimmermann, devicetree, dri-devel, linux-kernel,
	linux-media, chromeos-krk-upstreaming, ribalda

[-- Attachment #1: Type: text/plain, Size: 4962 bytes --]

Yo,

On Mon, Feb 12, 2024 at 01:13:22PM +0000, Paweł Anikiel wrote:
> The Intel Displayport RX IP is a part of the DisplayPort Intel FPGA IP
> Core. It implements a DisplayPort 1.4 receiver capable of HBR3 video
> capture and Multi-Stream Transport. The user guide can be found here:
> 
> https://www.intel.com/programmable/technical-pdfs/683273.pdf
> 
> Signed-off-by: Paweł Anikiel <panikiel@google.com>
> ---
>  .../devicetree/bindings/media/intel,dprx.yaml | 125 ++++++++++++++++++
>  1 file changed, 125 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/media/intel,dprx.yaml
> 
> diff --git a/Documentation/devicetree/bindings/media/intel,dprx.yaml b/Documentation/devicetree/bindings/media/intel,dprx.yaml
> new file mode 100644
> index 000000000000..3ed37e0a4a94
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/intel,dprx.yaml
> @@ -0,0 +1,125 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/media/intel,dprx.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Intel DisplayPort RX IP
> +
> +maintainers:
> +  - Paweł Anikiel <panikiel@google.com>
> +
> +properties:
> +  compatible:
> +    const: intel,dprx

Please version this compatible, given that is it for an FPGA IP.
I could not find an example of another intel IP that had versioning, but
there's plenty of xilinx stuff you can get inspiration from.

> +  reg:
> +    items:
> +      - description: core registers
> +      - description: irq registers
> +
> +  interrupts:
> +    maxItems: 1
> +
> +  intel,has-mst:

Mostly this looks fine, but this property drew my eye.
Firstly, I'd probably call this "intel,multi-stream-support" rather than
"intel,has-mst".

> +    type: boolean
> +    description: The device supports Multi-Stream Transport

Secondly, there are many many configuration parameters for this IP,
but you have chosen to document just one.
Are all other configuration parameters currently in their default
states or ignored by the driver? If not, please at least document all
configuration settings that you rely on - for example the max stream
count or audio packet encoding.

> +
> +  port:
> +    $ref: /schemas/graph.yaml#/properties/port
> +    description: SST main link
> +
> +  ports:
> +    $ref: /schemas/graph.yaml#/properties/ports
> +
> +    properties:
> +      port@0:
> +        $ref: /schemas/graph.yaml#/properties/port
> +        description: MST virtual channel 0 or SST main link
> +
> +      port@1:
> +        $ref: /schemas/graph.yaml#/properties/port
> +        description: MST virtual channel 1
> +
> +      port@2:
> +        $ref: /schemas/graph.yaml#/properties/port
> +        description: MST virtual channel 2
> +
> +      port@3:
> +        $ref: /schemas/graph.yaml#/properties/port
> +        description: MST virtual channel 3
> +
> +required:
> +  - compatible
> +  - reg
> +  - interrupts
> +
> +allOf:
> +  - if:
> +      required:
> +        - intel,has-mst
> +    then:
> +      required:
> +        - ports
> +    else:
> +      required:
> +        - port
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    dprx@c0062000 {

"dprx" isn't a class of device, please try to use a generic node name
here.

Thanks,
Conor.

> +        compatible = "intel,dprx";
> +        reg = <0xc0062000 0x800>,
> +              <0xc0060f80 0x10>;
> +        interrupts = <GIC_SPI 27 IRQ_TYPE_LEVEL_HIGH>;
> +        intel,has-mst;
> +
> +        ports {
> +            #address-cells = <1>;
> +            #size-cells = <0>;
> +
> +            port@0 {
> +                reg = <0>;
> +                dprx_mst_0: endpoint {
> +                    remote-endpoint = <&fb_mst0_0>;
> +                };
> +            };
> +
> +            port@1 {
> +                reg = <1>;
> +                dprx_mst_1: endpoint {
> +                    remote-endpoint = <&fb_mst1_0>;
> +                };
> +            };
> +
> +            port@2 {
> +                reg = <2>;
> +                dprx_mst_2: endpoint {
> +                    remote-endpoint = <&fb_mst2_0>;
> +                };
> +            };
> +
> +            port@3 {
> +                reg = <3>;
> +                dprx_mst_3: endpoint {
> +                    remote-endpoint = <&fb_mst3_0>;
> +                };
> +            };
> +        };
> +    };
> +
> +  - |
> +    dprx@c0064000 {
> +        compatible = "intel,dprx";
> +        reg = <0xc0064000 0x800>,
> +              <0xc0060fe0 0x10>;
> +        interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
> +
> +        port {
> +            dprx_sst_0: endpoint {
> +                remote-endpoint = <&fb_sst_0>;
> +            };
> +        };
> +    };
> -- 
> 2.43.0.687.g38aa6559b0-goog
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH 7/9] media: dt-bindings: Add Chameleon v3 framebuffer
  2024-02-12 13:13 ` [PATCH 7/9] media: dt-bindings: Add Chameleon v3 framebuffer Paweł Anikiel
  2024-02-12 14:35   ` Rob Herring
@ 2024-02-15 17:29   ` Conor Dooley
  2024-02-21 16:16     ` Paweł Anikiel
  1 sibling, 1 reply; 19+ messages in thread
From: Conor Dooley @ 2024-02-15 17:29 UTC (permalink / raw)
  To: Paweł Anikiel
  Cc: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
	krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
	robh+dt, tzimmermann, devicetree, dri-devel, linux-kernel,
	linux-media, chromeos-krk-upstreaming, ribalda

[-- Attachment #1: Type: text/plain, Size: 2944 bytes --]

On Mon, Feb 12, 2024 at 01:13:21PM +0000, Paweł Anikiel wrote:
> The Chameleon v3 uses the framebuffer IP core to take the video signal
> from different sources and directly write frames into memory.
> 
> Signed-off-by: Paweł Anikiel <panikiel@google.com>
> ---
>  .../bindings/media/google,chv3-fb.yaml        | 77 +++++++++++++++++++
>  1 file changed, 77 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/media/google,chv3-fb.yaml
> 
> diff --git a/Documentation/devicetree/bindings/media/google,chv3-fb.yaml b/Documentation/devicetree/bindings/media/google,chv3-fb.yaml
> new file mode 100644
> index 000000000000..ba6643cc7232
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/google,chv3-fb.yaml
> @@ -0,0 +1,77 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/media/google,chv3-fb.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Google Chameleon v3 video framebuffer
> +
> +maintainers:
> +  - Paweł Anikiel <panikiel@google.com>
> +
> +properties:
> +  compatible:
> +    const: google,chv3-fb
> +
> +  reg:
> +    items:
> +      - description: core registers
> +      - description: irq registers
> +
> +  interrupts:
> +    maxItems: 1
> +
> +  google,legacy-format:
> +    type: boolean
> +    description: The incoming video stream is in 32-bit padded mode.
> +
> +  google,no-endpoint:
> +    type: boolean
> +    description:
> +      The framebuffer isn't connected to a controllable endpoint.
> +      The video interface still works, but EDID control is unavailable
> +      and DV timing information only reports the active video width/height.

Why does this need a dedicated property? Is it not sufficient to check
that there are no endpoints in the devicetree?

Cheers,
Conor.

> +
> +  port:
> +    $ref: /schemas/graph.yaml#/properties/port
> +
> +required:
> +  - compatible
> +  - reg
> +  - interrupts
> +
> +allOf:
> +  - if:
> +      not:
> +        required:
> +          - google,no-endpoint
> +    then:
> +      required:
> +        - port
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    video@c0060500 {
> +        compatible = "google,chv3-fb";
> +        reg = <0xc0060500 0x100>,
> +              <0xc0060f20 0x10>;
> +        interrupts = <GIC_SPI 19 IRQ_TYPE_LEVEL_HIGH>;
> +        google,legacy-format;
> +        google,no-endpoint;
> +    };
> +
> +  - |
> +    video@c0060600 {
> +        compatible = "google,chv3-fb";
> +        reg = <0xc0060600 0x100>,
> +              <0xc0060f30 0x10>;
> +        interrupts = <GIC_SPI 22 IRQ_TYPE_LEVEL_HIGH>;
> +
> +        port {
> +            fb_mst0_0: endpoint {
> +                remote-endpoint = <&dprx_mst_0>;
> +            };
> +        };
> +    };
> -- 
> 2.43.0.687.g38aa6559b0-goog
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH 7/9] media: dt-bindings: Add Chameleon v3 framebuffer
  2024-02-12 15:51     ` Paweł Anikiel
@ 2024-02-16  9:43       ` Krzysztof Kozlowski
  0 siblings, 0 replies; 19+ messages in thread
From: Krzysztof Kozlowski @ 2024-02-16  9:43 UTC (permalink / raw)
  To: Paweł Anikiel, Rob Herring
  Cc: chromeos-krk-upstreaming, tzimmermann, maarten.lankhorst,
	hverkuil-cisco, linux-kernel, dri-devel, mchehab, ribalda,
	robh+dt, mripard, airlied, linux-media, akpm, dinguyen,
	devicetree, daniel, krzysztof.kozlowski+dt, conor+dt

On 12/02/2024 16:51, Paweł Anikiel wrote:
>>
>> If you already ran 'make dt_binding_check' and didn't see the above
>> error(s), then make sure 'yamllint' is installed and dt-schema is up to
>> date:
>>
>> pip3 install dtschema --upgrade
>>
>> Please check and re-submit after running the above command yourself. Note
>> that DT_SCHEMA_FILES can be set to your schema file to speed up checking
>> your schema. However, it must be unset to test all examples with your schema.
>>
> 
> I was missing a '#include
> <dt-bindings/interrupt-controller/arm-gic.h>' in the dt binding
> example. I ran dt_binding_check after adding it and it reports no
> errors. I will include the fix in v2.

Please test the patch before sending...

Best regards,
Krzysztof


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH 7/9] media: dt-bindings: Add Chameleon v3 framebuffer
  2024-02-15 17:29   ` Conor Dooley
@ 2024-02-21 16:16     ` Paweł Anikiel
  0 siblings, 0 replies; 19+ messages in thread
From: Paweł Anikiel @ 2024-02-21 16:16 UTC (permalink / raw)
  To: Conor Dooley
  Cc: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
	krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
	robh+dt, tzimmermann, devicetree, dri-devel, linux-kernel,
	linux-media, chromeos-krk-upstreaming, ribalda

On Thu, Feb 15, 2024 at 6:29 PM Conor Dooley <conor@kernel.org> wrote:
>
> On Mon, Feb 12, 2024 at 01:13:21PM +0000, Paweł Anikiel wrote:
> > The Chameleon v3 uses the framebuffer IP core to take the video signal
> > from different sources and directly write frames into memory.
> >
> > Signed-off-by: Paweł Anikiel <panikiel@google.com>
> > ---
> >  .../bindings/media/google,chv3-fb.yaml        | 77 +++++++++++++++++++
> >  1 file changed, 77 insertions(+)
> >  create mode 100644 Documentation/devicetree/bindings/media/google,chv3-fb.yaml
> >
> > diff --git a/Documentation/devicetree/bindings/media/google,chv3-fb.yaml b/Documentation/devicetree/bindings/media/google,chv3-fb.yaml
> > new file mode 100644
> > index 000000000000..ba6643cc7232
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/media/google,chv3-fb.yaml
> > @@ -0,0 +1,77 @@
> > +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/media/google,chv3-fb.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: Google Chameleon v3 video framebuffer
> > +
> > +maintainers:
> > +  - Paweł Anikiel <panikiel@google.com>
> > +
> > +properties:
> > +  compatible:
> > +    const: google,chv3-fb
> > +
> > +  reg:
> > +    items:
> > +      - description: core registers
> > +      - description: irq registers
> > +
> > +  interrupts:
> > +    maxItems: 1
> > +
> > +  google,legacy-format:
> > +    type: boolean
> > +    description: The incoming video stream is in 32-bit padded mode.
> > +
> > +  google,no-endpoint:
> > +    type: boolean
> > +    description:
> > +      The framebuffer isn't connected to a controllable endpoint.
> > +      The video interface still works, but EDID control is unavailable
> > +      and DV timing information only reports the active video width/height.
>
> Why does this need a dedicated property? Is it not sufficient to check
> that there are no endpoints in the devicetree?

Yes, I think it is sufficient. I removed the property and added a
check in the driver in v2.

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH 8/9] media: dt-bindings: Add Intel Displayport RX IP
  2024-02-15 17:26   ` Conor Dooley
@ 2024-02-21 16:30     ` Paweł Anikiel
  0 siblings, 0 replies; 19+ messages in thread
From: Paweł Anikiel @ 2024-02-21 16:30 UTC (permalink / raw)
  To: Conor Dooley
  Cc: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
	krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
	robh+dt, tzimmermann, devicetree, dri-devel, linux-kernel,
	linux-media, chromeos-krk-upstreaming, ribalda

On Thu, Feb 15, 2024 at 6:26 PM Conor Dooley <conor@kernel.org> wrote:
>
> Yo,
>
> On Mon, Feb 12, 2024 at 01:13:22PM +0000, Paweł Anikiel wrote:
> > The Intel Displayport RX IP is a part of the DisplayPort Intel FPGA IP
> > Core. It implements a DisplayPort 1.4 receiver capable of HBR3 video
> > capture and Multi-Stream Transport. The user guide can be found here:
> >
> > https://www.intel.com/programmable/technical-pdfs/683273.pdf
> >
> > Signed-off-by: Paweł Anikiel <panikiel@google.com>
> > ---
> >  .../devicetree/bindings/media/intel,dprx.yaml | 125 ++++++++++++++++++
> >  1 file changed, 125 insertions(+)
> >  create mode 100644 Documentation/devicetree/bindings/media/intel,dprx.yaml
> >
> > diff --git a/Documentation/devicetree/bindings/media/intel,dprx.yaml b/Documentation/devicetree/bindings/media/intel,dprx.yaml
> > new file mode 100644
> > index 000000000000..3ed37e0a4a94
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/media/intel,dprx.yaml
> > @@ -0,0 +1,125 @@
> > +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/media/intel,dprx.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: Intel DisplayPort RX IP
> > +
> > +maintainers:
> > +  - Paweł Anikiel <panikiel@google.com>
> > +
> > +properties:
> > +  compatible:
> > +    const: intel,dprx
>
> Please version this compatible, given that is it for an FPGA IP.
> I could not find an example of another intel IP that had versioning, but
> there's plenty of xilinx stuff you can get inspiration from.

The IP has had a few different revisions, so I decided to just use the
"IP version" which is 20.0.1 for the version this driver is targeting.

>
> > +  reg:
> > +    items:
> > +      - description: core registers
> > +      - description: irq registers
> > +
> > +  interrupts:
> > +    maxItems: 1
> > +
> > +  intel,has-mst:
>
> Mostly this looks fine, but this property drew my eye.
> Firstly, I'd probably call this "intel,multi-stream-support" rather than
> "intel,has-mst".

Sure,

>
> > +    type: boolean
> > +    description: The device supports Multi-Stream Transport
>
> Secondly, there are many many configuration parameters for this IP,
> but you have chosen to document just one.
> Are all other configuration parameters currently in their default
> states or ignored by the driver? If not, please at least document all
> configuration settings that you rely on - for example the max stream
> count or audio packet encoding.

I looked at all the parameters listed in the user guide, and most of
them don't affect the driver. I listed the ones which are required,
and added support for the remaining ones in v2.

>
> > +
> > +  port:
> > +    $ref: /schemas/graph.yaml#/properties/port
> > +    description: SST main link
> > +
> > +  ports:
> > +    $ref: /schemas/graph.yaml#/properties/ports
> > +
> > +    properties:
> > +      port@0:
> > +        $ref: /schemas/graph.yaml#/properties/port
> > +        description: MST virtual channel 0 or SST main link
> > +
> > +      port@1:
> > +        $ref: /schemas/graph.yaml#/properties/port
> > +        description: MST virtual channel 1
> > +
> > +      port@2:
> > +        $ref: /schemas/graph.yaml#/properties/port
> > +        description: MST virtual channel 2
> > +
> > +      port@3:
> > +        $ref: /schemas/graph.yaml#/properties/port
> > +        description: MST virtual channel 3
> > +
> > +required:
> > +  - compatible
> > +  - reg
> > +  - interrupts
> > +
> > +allOf:
> > +  - if:
> > +      required:
> > +        - intel,has-mst
> > +    then:
> > +      required:
> > +        - ports
> > +    else:
> > +      required:
> > +        - port
> > +
> > +additionalProperties: false
> > +
> > +examples:
> > +  - |
> > +    dprx@c0062000 {
>
> "dprx" isn't a class of device, please try to use a generic node name
> here.

I couldn't find anything in the DT spec or any other similar examples,
so I chose dp-receiver.

^ permalink raw reply	[flat|nested] 19+ messages in thread

end of thread, other threads:[~2024-02-21 16:31 UTC | newest]

Thread overview: 19+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-02-12 13:13 [PATCH 0/9] Add Chameleon v3 video support Paweł Anikiel
2024-02-12 13:13 ` [PATCH 1/9] media: v4l2-subdev: Add a pad variant of .query_dv_timings() Paweł Anikiel
2024-02-12 13:13 ` [PATCH 2/9] media: Add Chameleon v3 framebuffer driver Paweł Anikiel
2024-02-12 13:13 ` [PATCH 3/9] drm/dp_mst: Move DRM-independent structures to separate header Paweł Anikiel
2024-02-12 13:13 ` [PATCH 4/9] lib: Move DisplayPort CRC functions to common lib Paweł Anikiel
2024-02-12 13:13 ` [PATCH 5/9] drm/display: Add mask definitions for DP_PAYLOAD_ALLOCATE_* registers Paweł Anikiel
2024-02-12 13:13 ` [PATCH 6/9] media: intel: Add Displayport RX IP driver Paweł Anikiel
2024-02-12 13:13 ` [PATCH 7/9] media: dt-bindings: Add Chameleon v3 framebuffer Paweł Anikiel
2024-02-12 14:35   ` Rob Herring
2024-02-12 15:51     ` Paweł Anikiel
2024-02-16  9:43       ` Krzysztof Kozlowski
2024-02-15 17:29   ` Conor Dooley
2024-02-21 16:16     ` Paweł Anikiel
2024-02-12 13:13 ` [PATCH 8/9] media: dt-bindings: Add Intel Displayport RX IP Paweł Anikiel
2024-02-12 14:35   ` Rob Herring
2024-02-12 15:52     ` Paweł Anikiel
2024-02-15 17:26   ` Conor Dooley
2024-02-21 16:30     ` Paweł Anikiel
2024-02-12 13:13 ` [PATCH 9/9] ARM: dts: chameleonv3: Add video device nodes Paweł Anikiel

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).