2024-02-12 13:30:45

by Paweł Anikiel

[permalink] [raw]
Subject: [PATCH 0/9] Add Chameleon v3 video support

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



2024-02-12 13:31:20

by Paweł Anikiel

[permalink] [raw]
Subject: [PATCH 7/9] media: dt-bindings: Add Chameleon v3 framebuffer

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 <[email protected]>
---
.../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 <[email protected]>
+
+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


2024-02-12 13:37:13

by Paweł Anikiel

[permalink] [raw]
Subject: [PATCH 1/9] media: v4l2-subdev: Add a pad variant of .query_dv_timings()

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 <[email protected]>
---
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


2024-02-12 13:40:03

by Paweł Anikiel

[permalink] [raw]
Subject: [PATCH 5/9] drm/display: Add mask definitions for DP_PAYLOAD_ALLOCATE_* registers

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 <[email protected]>
---
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


2024-02-12 13:42:02

by Paweł Anikiel

[permalink] [raw]
Subject: [PATCH 8/9] media: dt-bindings: Add Intel Displayport RX IP

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 <[email protected]>
---
.../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 <[email protected]>
+
+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


2024-02-12 13:44:49

by Paweł Anikiel

[permalink] [raw]
Subject: [PATCH 6/9] media: intel: Add Displayport RX IP driver

Add driver for the Intel DisplayPort RX FPGA IP

Signed-off-by: Paweł Anikiel <[email protected]>
---
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 <[email protected]>
+ */
+
+#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 <[email protected]>");
+MODULE_DESCRIPTION("Intel DisplayPort RX IP core driver");
+MODULE_LICENSE("GPL");
--
2.43.0.687.g38aa6559b0-goog


2024-02-12 14:36:03

by Rob Herring (Arm)

[permalink] [raw]
Subject: Re: [PATCH 7/9] media: dt-bindings: Add Chameleon v3 framebuffer


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 <[email protected]>
> ---
> .../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/[email protected]

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.


2024-02-12 15:00:13

by Rob Herring (Arm)

[permalink] [raw]
Subject: Re: [PATCH 8/9] media: dt-bindings: Add Intel Displayport RX IP


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 <[email protected]>
> ---
> .../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/[email protected]

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.


2024-02-12 15:51:36

by Paweł Anikiel

[permalink] [raw]
Subject: Re: [PATCH 7/9] media: dt-bindings: Add Chameleon v3 framebuffer

On Mon, Feb 12, 2024 at 3:35 PM Rob Herring <[email protected]> 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 <[email protected]>
> > ---
> > .../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/[email protected]
>
> 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.

2024-02-12 15:59:31

by Paweł Anikiel

[permalink] [raw]
Subject: Re: [PATCH 8/9] media: dt-bindings: Add Intel Displayport RX IP

On Mon, Feb 12, 2024 at 3:35 PM Rob Herring <[email protected]> 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 <[email protected]>
> > ---
> > .../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/[email protected]
>
> 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.

2024-02-15 17:27:39

by Conor Dooley

[permalink] [raw]
Subject: Re: [PATCH 8/9] media: dt-bindings: Add Intel Displayport RX IP

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 <[email protected]>
> ---
> .../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 <[email protected]>
> +
> +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
>


Attachments:
(No filename) (4.85 kB)
signature.asc (235.00 B)
Download all attachments

2024-02-15 17:57:36

by Conor Dooley

[permalink] [raw]
Subject: Re: [PATCH 7/9] media: dt-bindings: Add Chameleon v3 framebuffer

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 <[email protected]>
> ---
> .../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 <[email protected]>
> +
> +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
>


Attachments:
(No filename) (2.88 kB)
signature.asc (235.00 B)
Download all attachments

2024-02-16 09:43:26

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [PATCH 7/9] media: dt-bindings: Add Chameleon v3 framebuffer

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


2024-02-21 16:17:15

by Paweł Anikiel

[permalink] [raw]
Subject: Re: [PATCH 7/9] media: dt-bindings: Add Chameleon v3 framebuffer

On Thu, Feb 15, 2024 at 6:29 PM Conor Dooley <[email protected]> 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 <[email protected]>
> > ---
> > .../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 <[email protected]>
> > +
> > +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.

2024-02-21 16:36:31

by Paweł Anikiel

[permalink] [raw]
Subject: Re: [PATCH 8/9] media: dt-bindings: Add Intel Displayport RX IP

On Thu, Feb 15, 2024 at 6:26 PM Conor Dooley <[email protected]> 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 <[email protected]>
> > ---
> > .../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 <[email protected]>
> > +
> > +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.