2024-02-21 16:03:24

by Paweł Anikiel

[permalink] [raw]
Subject: [PATCH v2 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.

v2 changes:
- Add missing includes in dt binding examples
- Add version number to intel,dprx compatible
- Use generic node names in dts
- Add and document IP configuration parameters
- Remove IRQ registers from intel-dprx (they're not a part of the IP)
- Remove no-endpoint property and check for "port" node instead

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 | 67 +
.../devicetree/bindings/media/intel,dprx.yaml | 160 ++
.../socfpga/socfpga_arria10_chameleonv3.dts | 152 ++
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 | 895 +++++++
drivers/media/platform/intel/Kconfig | 12 +
drivers/media/platform/intel/Makefile | 1 +
drivers/media/platform/intel/intel-dprx.c | 2176 +++++++++++++++++
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, 3851 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.44.0.rc0.258.g7320e95886-goog



2024-02-21 16:04:02

by Paweł Anikiel

[permalink] [raw]
Subject: [PATCH v2 3/9] drm/dp_mst: Move DRM-independent structures to separate header

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

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

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

#include <linux/types.h>
+#include <drm/display/drm_dp_mst.h>
#include <drm/display/drm_dp_helper.h>
#include <drm/drm_atomic.h>
#include <drm/drm_fixed.h>
@@ -248,237 +249,6 @@ struct drm_dp_mst_branch {
u8 guid[16];
};

-
-struct drm_dp_nak_reply {
- u8 guid[16];
- u8 reason;
- u8 nak_data;
-};
-
-struct drm_dp_link_address_ack_reply {
- u8 guid[16];
- u8 nports;
- struct drm_dp_link_addr_reply_port {
- bool input_port;
- u8 peer_device_type;
- u8 port_number;
- bool mcs;
- bool ddps;
- bool legacy_device_plug_status;
- u8 dpcd_revision;
- u8 peer_guid[16];
- u8 num_sdp_streams;
- u8 num_sdp_stream_sinks;
- } ports[16];
-};
-
-struct drm_dp_remote_dpcd_read_ack_reply {
- u8 port_number;
- u8 num_bytes;
- u8 bytes[255];
-};
-
-struct drm_dp_remote_dpcd_write_ack_reply {
- u8 port_number;
-};
-
-struct drm_dp_remote_dpcd_write_nak_reply {
- u8 port_number;
- u8 reason;
- u8 bytes_written_before_failure;
-};
-
-struct drm_dp_remote_i2c_read_ack_reply {
- u8 port_number;
- u8 num_bytes;
- u8 bytes[255];
-};
-
-struct drm_dp_remote_i2c_read_nak_reply {
- u8 port_number;
- u8 nak_reason;
- u8 i2c_nak_transaction;
-};
-
-struct drm_dp_remote_i2c_write_ack_reply {
- u8 port_number;
-};
-
-struct drm_dp_query_stream_enc_status_ack_reply {
- /* Bit[23:16]- Stream Id */
- u8 stream_id;
-
- /* Bit[15]- Signed */
- bool reply_signed;
-
- /* Bit[10:8]- Stream Output Sink Type */
- bool unauthorizable_device_present;
- bool legacy_device_present;
- bool query_capable_device_present;
-
- /* Bit[12:11]- Stream Output CP Type */
- bool hdcp_1x_device_present;
- bool hdcp_2x_device_present;
-
- /* Bit[4]- Stream Authentication */
- bool auth_completed;
-
- /* Bit[3]- Stream Encryption */
- bool encryption_enabled;
-
- /* Bit[2]- Stream Repeater Function Present */
- bool repeater_present;
-
- /* Bit[1:0]- Stream State */
- u8 state;
-};
-
-#define DRM_DP_MAX_SDP_STREAMS 16
-struct drm_dp_allocate_payload {
- u8 port_number;
- u8 number_sdp_streams;
- u8 vcpi;
- u16 pbn;
- u8 sdp_stream_sink[DRM_DP_MAX_SDP_STREAMS];
-};
-
-struct drm_dp_allocate_payload_ack_reply {
- u8 port_number;
- u8 vcpi;
- u16 allocated_pbn;
-};
-
-struct drm_dp_connection_status_notify {
- u8 guid[16];
- u8 port_number;
- bool legacy_device_plug_status;
- bool displayport_device_plug_status;
- bool message_capability_status;
- bool input_port;
- u8 peer_device_type;
-};
-
-struct drm_dp_remote_dpcd_read {
- u8 port_number;
- u32 dpcd_address;
- u8 num_bytes;
-};
-
-struct drm_dp_remote_dpcd_write {
- u8 port_number;
- u32 dpcd_address;
- u8 num_bytes;
- u8 *bytes;
-};
-
-#define DP_REMOTE_I2C_READ_MAX_TRANSACTIONS 4
-struct drm_dp_remote_i2c_read {
- u8 num_transactions;
- u8 port_number;
- struct drm_dp_remote_i2c_read_tx {
- u8 i2c_dev_id;
- u8 num_bytes;
- u8 *bytes;
- u8 no_stop_bit;
- u8 i2c_transaction_delay;
- } transactions[DP_REMOTE_I2C_READ_MAX_TRANSACTIONS];
- u8 read_i2c_device_id;
- u8 num_bytes_read;
-};
-
-struct drm_dp_remote_i2c_write {
- u8 port_number;
- u8 write_i2c_device_id;
- u8 num_bytes;
- u8 *bytes;
-};
-
-struct drm_dp_query_stream_enc_status {
- u8 stream_id;
- u8 client_id[7]; /* 56-bit nonce */
- u8 stream_event;
- bool valid_stream_event;
- u8 stream_behavior;
- u8 valid_stream_behavior;
-};
-
-/* this covers ENUM_RESOURCES, POWER_DOWN_PHY, POWER_UP_PHY */
-struct drm_dp_port_number_req {
- u8 port_number;
-};
-
-struct drm_dp_enum_path_resources_ack_reply {
- u8 port_number;
- bool fec_capable;
- u16 full_payload_bw_number;
- u16 avail_payload_bw_number;
-};
-
-/* covers POWER_DOWN_PHY, POWER_UP_PHY */
-struct drm_dp_port_number_rep {
- u8 port_number;
-};
-
-struct drm_dp_query_payload {
- u8 port_number;
- u8 vcpi;
-};
-
-struct drm_dp_resource_status_notify {
- u8 port_number;
- u8 guid[16];
- u16 available_pbn;
-};
-
-struct drm_dp_query_payload_ack_reply {
- u8 port_number;
- u16 allocated_pbn;
-};
-
-struct drm_dp_sideband_msg_req_body {
- u8 req_type;
- union ack_req {
- struct drm_dp_connection_status_notify conn_stat;
- struct drm_dp_port_number_req port_num;
- struct drm_dp_resource_status_notify resource_stat;
-
- struct drm_dp_query_payload query_payload;
- struct drm_dp_allocate_payload allocate_payload;
-
- struct drm_dp_remote_dpcd_read dpcd_read;
- struct drm_dp_remote_dpcd_write dpcd_write;
-
- struct drm_dp_remote_i2c_read i2c_read;
- struct drm_dp_remote_i2c_write i2c_write;
-
- struct drm_dp_query_stream_enc_status enc_status;
- } u;
-};
-
-struct drm_dp_sideband_msg_reply_body {
- u8 reply_type;
- u8 req_type;
- union ack_replies {
- struct drm_dp_nak_reply nak;
- struct drm_dp_link_address_ack_reply link_addr;
- struct drm_dp_port_number_rep port_number;
-
- struct drm_dp_enum_path_resources_ack_reply path_resources;
- struct drm_dp_allocate_payload_ack_reply allocate_payload;
- struct drm_dp_query_payload_ack_reply query_payload;
-
- struct drm_dp_remote_dpcd_read_ack_reply remote_dpcd_read_ack;
- struct drm_dp_remote_dpcd_write_ack_reply remote_dpcd_write_ack;
- struct drm_dp_remote_dpcd_write_nak_reply remote_dpcd_write_nack;
-
- struct drm_dp_remote_i2c_read_ack_reply remote_i2c_read_ack;
- struct drm_dp_remote_i2c_read_nak_reply remote_i2c_read_nack;
- struct drm_dp_remote_i2c_write_ack_reply remote_i2c_write_ack;
-
- struct drm_dp_query_stream_enc_status_ack_reply enc_status;
- } u;
-};
-
/* msg is queued to be put into a slot */
#define DRM_DP_SIDEBAND_TX_QUEUED 0
/* msg has started transmitting on a slot - still on msgq */
--
2.44.0.rc0.258.g7320e95886-goog


2024-02-21 16:04:13

by Paweł Anikiel

[permalink] [raw]
Subject: [PATCH v2 2/9] media: Add Chameleon v3 framebuffer driver

Add v4l2 driver for the Google Chameleon v3 framebuffer device.

Signed-off-by: Paweł Anikiel <[email protected]>
---
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 | 895 ++++++++++++++++++
7 files changed, 918 insertions(+)
create mode 100644 drivers/media/platform/google/Kconfig
create mode 100644 drivers/media/platform/google/Makefile
create mode 100644 drivers/media/platform/google/chameleonv3/Kconfig
create mode 100644 drivers/media/platform/google/chameleonv3/Makefile
create mode 100644 drivers/media/platform/google/chameleonv3/chv3-fb.c

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


2024-02-21 16:04:25

by Paweł Anikiel

[permalink] [raw]
Subject: [PATCH v2 4/9] lib: Move DisplayPort CRC functions to common lib

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

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

diff --git a/drivers/gpu/drm/display/Kconfig b/drivers/gpu/drm/display/Kconfig
index 09712b88a5b8..c615f50152f2 100644
--- a/drivers/gpu/drm/display/Kconfig
+++ b/drivers/gpu/drm/display/Kconfig
@@ -14,6 +14,7 @@ config DRM_DISPLAY_HELPER
config DRM_DISPLAY_DP_HELPER
bool
depends on DRM_DISPLAY_HELPER
+ select CRC_DP
help
DRM display helpers for DisplayPort.

diff --git a/drivers/gpu/drm/display/drm_dp_mst_topology.c b/drivers/gpu/drm/display/drm_dp_mst_topology.c
index f7c6b60629c2..ada1f90fa808 100644
--- a/drivers/gpu/drm/display/drm_dp_mst_topology.c
+++ b/drivers/gpu/drm/display/drm_dp_mst_topology.c
@@ -22,6 +22,7 @@

#include <linux/bitfield.h>
#include <linux/delay.h>
+#include <linux/crc-dp.h>
#include <linux/errno.h>
#include <linux/i2c.h>
#include <linux/init.h>
@@ -195,73 +196,6 @@ drm_dp_mst_rad_to_str(const u8 rad[8], u8 lct, char *out, size_t len)
}

/* sideband msg handling */
-static u8 drm_dp_msg_header_crc4(const uint8_t *data, size_t num_nibbles)
-{
- u8 bitmask = 0x80;
- u8 bitshift = 7;
- u8 array_index = 0;
- int number_of_bits = num_nibbles * 4;
- u8 remainder = 0;
-
- while (number_of_bits != 0) {
- number_of_bits--;
- remainder <<= 1;
- remainder |= (data[array_index] & bitmask) >> bitshift;
- bitmask >>= 1;
- bitshift--;
- if (bitmask == 0) {
- bitmask = 0x80;
- bitshift = 7;
- array_index++;
- }
- if ((remainder & 0x10) == 0x10)
- remainder ^= 0x13;
- }
-
- number_of_bits = 4;
- while (number_of_bits != 0) {
- number_of_bits--;
- remainder <<= 1;
- if ((remainder & 0x10) != 0)
- remainder ^= 0x13;
- }
-
- return remainder;
-}
-
-static u8 drm_dp_msg_data_crc4(const uint8_t *data, u8 number_of_bytes)
-{
- u8 bitmask = 0x80;
- u8 bitshift = 7;
- u8 array_index = 0;
- int number_of_bits = number_of_bytes * 8;
- u16 remainder = 0;
-
- while (number_of_bits != 0) {
- number_of_bits--;
- remainder <<= 1;
- remainder |= (data[array_index] & bitmask) >> bitshift;
- bitmask >>= 1;
- bitshift--;
- if (bitmask == 0) {
- bitmask = 0x80;
- bitshift = 7;
- array_index++;
- }
- if ((remainder & 0x100) == 0x100)
- remainder ^= 0xd5;
- }
-
- number_of_bits = 8;
- while (number_of_bits != 0) {
- number_of_bits--;
- remainder <<= 1;
- if ((remainder & 0x100) != 0)
- remainder ^= 0xd5;
- }
-
- return remainder & 0xff;
-}
static inline u8 drm_dp_calc_sb_hdr_size(struct drm_dp_sideband_msg_hdr *hdr)
{
u8 size = 3;
@@ -284,7 +218,7 @@ static void drm_dp_encode_sideband_msg_hdr(struct drm_dp_sideband_msg_hdr *hdr,
(hdr->msg_len & 0x3f);
buf[idx++] = (hdr->somt << 7) | (hdr->eomt << 6) | (hdr->seqno << 4);

- crc4 = drm_dp_msg_header_crc4(buf, (idx * 2) - 1);
+ crc4 = crc_dp_msg_header(buf, (idx * 2) - 1);
buf[idx - 1] |= (crc4 & 0xf);

*len = idx;
@@ -305,7 +239,7 @@ static bool drm_dp_decode_sideband_msg_hdr(const struct drm_dp_mst_topology_mgr
len += ((buf[0] & 0xf0) >> 4) / 2;
if (len > buflen)
return false;
- crc4 = drm_dp_msg_header_crc4(buf, (len * 2) - 1);
+ crc4 = crc_dp_msg_header(buf, (len * 2) - 1);

if ((crc4 & 0xf) != (buf[len - 1] & 0xf)) {
drm_dbg_kms(mgr->dev, "crc4 mismatch 0x%x 0x%x\n", crc4, buf[len - 1]);
@@ -725,7 +659,7 @@ static void drm_dp_crc_sideband_chunk_req(u8 *msg, u8 len)
{
u8 crc4;

- crc4 = drm_dp_msg_data_crc4(msg, len);
+ crc4 = crc_dp_msg_data(msg, len);
msg[len] = crc4;
}

@@ -782,7 +716,7 @@ static bool drm_dp_sideband_append_payload(struct drm_dp_sideband_msg_rx *msg,

if (msg->curchunk_idx >= msg->curchunk_len) {
/* do CRC */
- crc4 = drm_dp_msg_data_crc4(msg->chunk, msg->curchunk_len - 1);
+ crc4 = crc_dp_msg_data(msg->chunk, msg->curchunk_len - 1);
if (crc4 != msg->chunk[msg->curchunk_len - 1])
print_hex_dump(KERN_DEBUG, "wrong crc",
DUMP_PREFIX_NONE, 16, 1,
diff --git a/include/linux/crc-dp.h b/include/linux/crc-dp.h
new file mode 100644
index 000000000000..b63435c82b96
--- /dev/null
+++ b/include/linux/crc-dp.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_CRC_DP_H
+#define _LINUX_CRC_DP_H
+
+#include <linux/types.h>
+
+u8 crc_dp_msg_header(const uint8_t *data, size_t num_nibbles);
+u8 crc_dp_msg_data(const uint8_t *data, u8 number_of_bytes);
+
+#endif /* _LINUX_CRC_DP_H */
diff --git a/lib/Kconfig b/lib/Kconfig
index 5ddda7c2ed9b..28f9f6cfec9f 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -171,6 +171,14 @@ config CRC_ITU_T
the kernel tree does. Such modules that use library CRC ITU-T V.41
functions require M here.

+config CRC_DP
+ tristate "CRC DisplayPort MST functions"
+ help
+ This option is provided for the case where no in-kernel-tree
+ modules require CRC DisplayPort MST functions, but a module built outside
+ the kernel tree does. Such modules that use library CRC DisplayPort MST
+ functions require M here.
+
config CRC32
tristate "CRC32/CRC32c functions"
default y
diff --git a/lib/Makefile b/lib/Makefile
index 6b09731d8e61..e4d7ffa260b3 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -188,6 +188,7 @@ obj-$(CONFIG_CRC7) += crc7.o
obj-$(CONFIG_LIBCRC32C) += libcrc32c.o
obj-$(CONFIG_CRC8) += crc8.o
obj-$(CONFIG_CRC64_ROCKSOFT) += crc64-rocksoft.o
+obj-$(CONFIG_CRC_DP) += crc-dp.o
obj-$(CONFIG_XXHASH) += xxhash.o
obj-$(CONFIG_GENERIC_ALLOCATOR) += genalloc.o

diff --git a/lib/crc-dp.c b/lib/crc-dp.c
new file mode 100644
index 000000000000..95b58bc436d4
--- /dev/null
+++ b/lib/crc-dp.c
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/crc-dp.h>
+
+/*
+ * Sideband MSG Header CRC
+ * Defined in DisplayPort 1.2 spec, section 2.11.3.1.9
+ */
+u8 crc_dp_msg_header(const uint8_t *data, size_t num_nibbles)
+{
+ u8 bitmask = 0x80;
+ u8 bitshift = 7;
+ u8 array_index = 0;
+ int number_of_bits = num_nibbles * 4;
+ u8 remainder = 0;
+
+ while (number_of_bits != 0) {
+ number_of_bits--;
+ remainder <<= 1;
+ remainder |= (data[array_index] & bitmask) >> bitshift;
+ bitmask >>= 1;
+ bitshift--;
+ if (bitmask == 0) {
+ bitmask = 0x80;
+ bitshift = 7;
+ array_index++;
+ }
+ if ((remainder & 0x10) == 0x10)
+ remainder ^= 0x13;
+ }
+
+ number_of_bits = 4;
+ while (number_of_bits != 0) {
+ number_of_bits--;
+ remainder <<= 1;
+ if ((remainder & 0x10) != 0)
+ remainder ^= 0x13;
+ }
+
+ return remainder;
+}
+
+/*
+ * Sideband MSG Data CRC
+ * Defined in DisplayPort 1.2 spec, section 2.11.3.2.2
+ */
+u8 crc_dp_msg_data(const uint8_t *data, u8 number_of_bytes)
+{
+ u8 bitmask = 0x80;
+ u8 bitshift = 7;
+ u8 array_index = 0;
+ int number_of_bits = number_of_bytes * 8;
+ u16 remainder = 0;
+
+ while (number_of_bits != 0) {
+ number_of_bits--;
+ remainder <<= 1;
+ remainder |= (data[array_index] & bitmask) >> bitshift;
+ bitmask >>= 1;
+ bitshift--;
+ if (bitmask == 0) {
+ bitmask = 0x80;
+ bitshift = 7;
+ array_index++;
+ }
+ if ((remainder & 0x100) == 0x100)
+ remainder ^= 0xd5;
+ }
+
+ number_of_bits = 8;
+ while (number_of_bits != 0) {
+ number_of_bits--;
+ remainder <<= 1;
+ if ((remainder & 0x100) != 0)
+ remainder ^= 0xd5;
+ }
+
+ return remainder & 0xff;
+}
--
2.44.0.rc0.258.g7320e95886-goog


2024-02-21 16:04:39

by Paweł Anikiel

[permalink] [raw]
Subject: [PATCH v2 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.44.0.rc0.258.g7320e95886-goog


2024-02-21 16:05:43

by Paweł Anikiel

[permalink] [raw]
Subject: [PATCH v2 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 | 2176 +++++++++++++++++++++
3 files changed, 2189 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..d0c60e29e51d
--- /dev/null
+++ b/drivers/media/platform/intel/intel-dprx.c
@@ -0,0 +1,2176 @@
+// 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
+
+/* 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
+
+/* 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;
+
+ struct v4l2_subdev subdev;
+ struct media_pad pads[4];
+
+ struct dprx_sink sinks[4];
+
+ int max_link_rate;
+ int max_lane_count;
+ bool multi_stream_support;
+ int max_stream_count;
+
+ u8 caps[16];
+ 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->max_stream_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->max_stream_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->max_stream_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->max_stream_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 void dprx_init_caps(struct dprx *dprx)
+{
+ memset(dprx->caps, 0, sizeof(dprx->caps));
+ dprx->caps[DP_DPCD_REV] = DP_DPCD_REV_14;
+ dprx->caps[DP_MAX_LINK_RATE] = dprx->max_link_rate;
+ dprx->caps[DP_MAX_LANE_COUNT] = DP_ENHANCED_FRAME_CAP | DP_TPS3_SUPPORTED |
+ dprx->max_lane_count;
+ dprx->caps[DP_MAX_DOWNSPREAD] = DP_TPS4_SUPPORTED | DP_MAX_DOWNSPREAD_0_5;
+ dprx->caps[DP_MAIN_LINK_CHANNEL_CODING] = DP_CAP_ANSI_8B10B;
+ dprx->caps[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->multi_stream_support;
+}
+
+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;
+
+ if (lane_count > dprx->max_lane_count)
+ 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->max_stream_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;
+
+ 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);
+ }
+
+ 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->max_stream_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->max_stream_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->max_stream_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->max_stream_count; i++)
+ dprx->pads[i].flags = MEDIA_PAD_FL_SOURCE;
+
+ return media_entity_pads_init(&dprx->subdev.entity, dprx->max_stream_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);
+
+ 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;
+
+ res = device_property_read_u32(&pdev->dev, "intel,max-link-rate", &dprx->max_link_rate);
+ if (res)
+ return res;
+
+ res = device_property_read_u32(&pdev->dev, "intel,max-lane-count", &dprx->max_lane_count);
+ if (res)
+ return res;
+
+ dprx->multi_stream_support = device_property_read_bool(&pdev->dev,
+ "intel,multi-stream-support");
+
+ if (dprx->multi_stream_support) {
+ res = device_property_read_u32(&pdev->dev, "intel,max-stream-count",
+ &dprx->max_stream_count);
+ if (res)
+ return res;
+ } else {
+ dprx->max_stream_count = 1;
+ }
+
+ dprx_init_caps(dprx);
+
+ 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);
+
+ return 0;
+}
+
+static void dprx_remove(struct platform_device *pdev)
+{
+ struct dprx *dprx = platform_get_drvdata(pdev);
+
+ /* disable interrupts */
+ dprx_set_irq(dprx, 0);
+
+ v4l2_async_unregister_subdev(&dprx->subdev);
+}
+
+static const struct of_device_id dprx_match_table[] = {
+ { .compatible = "intel,dprx-20.0.1" },
+ { },
+};
+
+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.44.0.rc0.258.g7320e95886-goog


2024-02-21 16:05:54

by Paweł Anikiel

[permalink] [raw]
Subject: [PATCH v2 9/9] ARM: dts: chameleonv3: Add video device nodes

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

Signed-off-by: Paweł Anikiel <[email protected]>
---
.../socfpga/socfpga_arria10_chameleonv3.dts | 152 ++++++++++++++++++
1 file changed, 152 insertions(+)

diff --git a/arch/arm/boot/dts/intel/socfpga/socfpga_arria10_chameleonv3.dts b/arch/arm/boot/dts/intel/socfpga/socfpga_arria10_chameleonv3.dts
index 422d00cd4c74..2f48f30cb538 100644
--- a/arch/arm/boot/dts/intel/socfpga/socfpga_arria10_chameleonv3.dts
+++ b/arch/arm/boot/dts/intel/socfpga/socfpga_arria10_chameleonv3.dts
@@ -10,6 +10,158 @@ / {
compatible = "google,chameleon-v3", "enclustra,mercury-aa1",
"altr,socfpga-arria10", "altr,socfpga";

+ soc {
+ fb0: video@c0060500 {
+ compatible = "google,chv3-fb";
+ reg = <0xc0060500 0x100>,
+ <0xc0060f20 0x10>;
+ interrupts = <GIC_SPI 19 IRQ_TYPE_LEVEL_HIGH>;
+ google,legacy-format;
+ };
+
+ fb_mst0: video@c0060600 {
+ compatible = "google,chv3-fb";
+ reg = <0xc0060600 0x100>,
+ <0xc0060f30 0x10>;
+ interrupts = <GIC_SPI 22 IRQ_TYPE_LEVEL_HIGH>;
+
+ port {
+ fb_mst0_0: endpoint {
+ remote-endpoint = <&dprx_mst_0>;
+ };
+ };
+ };
+
+ fb_mst1: video@c0060700 {
+ compatible = "google,chv3-fb";
+ reg = <0xc0060700 0x100>,
+ <0xc0060f40 0x10>;
+ interrupts = <GIC_SPI 23 IRQ_TYPE_LEVEL_HIGH>;
+
+ port {
+ fb_mst1_0: endpoint {
+ remote-endpoint = <&dprx_mst_1>;
+ };
+ };
+ };
+
+ fb_mst2: video@c0060800 {
+ compatible = "google,chv3-fb";
+ reg = <0xc0060800 0x100>,
+ <0xc0060f50 0x10>;
+ interrupts = <GIC_SPI 24 IRQ_TYPE_LEVEL_HIGH>;
+
+ port {
+ fb_mst2_0: endpoint {
+ remote-endpoint = <&dprx_mst_2>;
+ };
+ };
+ };
+
+ fb_mst3: video@c0060900 {
+ compatible = "google,chv3-fb";
+ reg = <0xc0060900 0x100>,
+ <0xc0060f60 0x10>;
+ interrupts = <GIC_SPI 25 IRQ_TYPE_LEVEL_HIGH>;
+
+ port {
+ fb_mst3_0: endpoint {
+ remote-endpoint = <&dprx_mst_3>;
+ };
+ };
+ };
+
+ fb_sst: video@c0060a00 {
+ compatible = "google,chv3-fb";
+ reg = <0xc0060a00 0x100>,
+ <0xc0060f70 0x10>;
+ interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
+
+ port {
+ fb_sst_0: endpoint {
+ remote-endpoint = <&dprx_sst_0>;
+ };
+ };
+ };
+
+ dprx_mst_irq: intc@c0060f80 {
+ compatible = "altr,pio-1.0";
+ reg = <0xc0060f80 0x10>;
+ interrupts = <GIC_SPI 27 IRQ_TYPE_LEVEL_HIGH>;
+ altr,interrupt-type = <IRQ_TYPE_EDGE_RISING>;
+ #interrupt-cells = <2>;
+ interrupt-controller;
+ };
+
+ dprx_sst_irq: intc@c0060fe0 {
+ compatible = "altr,pio-1.0";
+ reg = <0xc0060fe0 0x10>;
+ interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
+ altr,interrupt-type = <IRQ_TYPE_EDGE_RISING>;
+ #interrupt-cells = <2>;
+ interrupt-controller;
+ };
+
+ dprx_mst: dp-receiver@c0062000 {
+ compatible = "intel,dprx-20.0.1";
+ reg = <0xc0062000 0x800>;
+ interrupt-parent = <&dprx_mst_irq>;
+ interrupts = <0 IRQ_TYPE_EDGE_RISING>;
+ intel,max-link-rate = <0x1e>;
+ intel,max-lane-count = <4>;
+ intel,multi-stream-support;
+ intel,max-stream-count = <4>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ dprx_mst_0: endpoint {
+ remote-endpoint = <&fb_mst0_0>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ dprx_mst_1: endpoint {
+ remote-endpoint = <&fb_mst1_0>;
+ };
+ };
+
+ port@2 {
+ reg = <2>;
+ dprx_mst_2: endpoint {
+ remote-endpoint = <&fb_mst2_0>;
+ };
+ };
+
+ port@3 {
+ reg = <3>;
+ dprx_mst_3: endpoint {
+ remote-endpoint = <&fb_mst3_0>;
+ };
+ };
+ };
+ };
+
+ dprx_sst: dp-receiver@c0064000 {
+ compatible = "intel,dprx-20.0.1";
+ reg = <0xc0064000 0x800>;
+ interrupt-parent = <&dprx_sst_irq>;
+ interrupts = <0 IRQ_TYPE_EDGE_RISING>;
+ intel,max-link-rate = <0x1e>;
+ intel,max-lane-count = <4>;
+
+ port {
+ dprx_sst_0: endpoint {
+ remote-endpoint = <&fb_sst_0>;
+ };
+ };
+ };
+ };
+
aliases {
serial0 = &uart0;
i2c0 = &i2c0;
--
2.44.0.rc0.258.g7320e95886-goog


2024-02-21 16:06:01

by Paweł Anikiel

[permalink] [raw]
Subject: [PATCH v2 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 | 67 +++++++++++++++++++
1 file changed, 67 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..7961c0ab66ec
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/google,chv3-fb.yaml
@@ -0,0 +1,67 @@
+# 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.
+
+ port:
+ $ref: /schemas/graph.yaml#/properties/port
+ description:
+ Connection to the video receiver - optional. If this isn't present,
+ the video interface still works on its own, but EDID control is
+ unavailable and DV timing information only reports the active
+ video width/height.
+
+required:
+ - compatible
+ - reg
+ - interrupts
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+ video@c0060500 {
+ compatible = "google,chv3-fb";
+ reg = <0xc0060500 0x100>,
+ <0xc0060f20 0x10>;
+ interrupts = <GIC_SPI 19 IRQ_TYPE_LEVEL_HIGH>;
+ google,legacy-format;
+ };
+
+ - |
+ 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.44.0.rc0.258.g7320e95886-goog


2024-02-21 16:14:13

by Paweł Anikiel

[permalink] [raw]
Subject: [PATCH v2 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.44.0.rc0.258.g7320e95886-goog


2024-02-21 16:18:19

by Paweł Anikiel

[permalink] [raw]
Subject: [PATCH v2 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 | 160 ++++++++++++++++++
1 file changed, 160 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..31025f2d5dcd
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/intel,dprx.yaml
@@ -0,0 +1,160 @@
+# 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]>
+
+description: |
+ 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 IP features a large number of configuration parameters, found at:
+ https://www.intel.com/content/www/us/en/docs/programmable/683273/23-3-20-0-1/sink-parameters.html
+
+ The following parameters have to be enabled:
+ - Support DisplayPort sink
+ - Enable GPU control
+ The following parameters' values have to be set in the devicetree:
+ - RX maximum link rate
+ - Maximum lane count
+ - Support MST
+ - Max stream count (only if Support MST is enabled)
+
+properties:
+ compatible:
+ const: intel,dprx-20.0.1
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ intel,max-link-rate:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: Max link rate configuration parameter
+
+ intel,max-lane-count:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: Max lane count configuration parameter
+
+ intel,multi-stream-support:
+ type: boolean
+ description: Multi-Stream Transport support configuration parameter
+
+ intel,max-stream-count:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: Max stream count configuration parameter
+
+ 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,multi-stream-support
+ then:
+ required:
+ - intel,max-stream-count
+ - ports
+ else:
+ required:
+ - port
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+ dp-receiver@c0062000 {
+ compatible = "intel,dprx-20.0.1";
+ reg = <0xc0062000 0x800>;
+ interrupt-parent = <&dprx_mst_irq>;
+ interrupts = <0 IRQ_TYPE_EDGE_RISING>;
+ intel,max-link-rate = <0x1e>;
+ intel,max-lane-count = <4>;
+ intel,multi-stream-support;
+ intel,max-stream-count = <4>;
+
+ 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>;
+ };
+ };
+ };
+ };
+
+ - |
+ dp-receiver@c0064000 {
+ compatible = "intel,dprx-20.0.1";
+ reg = <0xc0064000 0x800>;
+ interrupt-parent = <&dprx_sst_irq>;
+ interrupts = <0 IRQ_TYPE_EDGE_RISING>;
+ intel,max-link-rate = <0x1e>;
+ intel,max-lane-count = <4>;
+
+ port {
+ dprx_sst_0: endpoint {
+ remote-endpoint = <&fb_sst_0>;
+ };
+ };
+ };
--
2.44.0.rc0.258.g7320e95886-goog


2024-02-23 19:04:48

by Conor Dooley

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

Hey,

On Wed, Feb 21, 2024 at 04:02:06PM +0000, Paweł Anikiel wrote:
> media: dt-bindings: Add Chameleon v3 framebuffer
> media: dt-bindings: Add Intel Displayport RX IP

I'm happy with both of these patches, but I would like others to look,
so I'll hold off leaving R-b tags until someone else has at least
looked.

Cheers,
Conor.


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

2024-02-26 09:46:10

by Krzysztof Kozlowski

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

On 21/02/2024 17:02, 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]>

..

> +
> + 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.

Why is this a property of board DTS? Can't the input streams change
depending on the usage? Who defines the incoming stream format?

> +

Best regards,
Krzysztof


2024-02-26 09:47:45

by Krzysztof Kozlowski

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

On 21/02/2024 17:02, 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 | 160 ++++++++++++++++++
> 1 file changed, 160 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..31025f2d5dcd
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/intel,dprx.yaml
> @@ -0,0 +1,160 @@
> +# 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]>
> +
> +description: |
> + 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 IP features a large number of configuration parameters, found at:
> + https://www.intel.com/content/www/us/en/docs/programmable/683273/23-3-20-0-1/sink-parameters.html
> +
> + The following parameters have to be enabled:
> + - Support DisplayPort sink
> + - Enable GPU control
> + The following parameters' values have to be set in the devicetree:
> + - RX maximum link rate
> + - Maximum lane count
> + - Support MST
> + - Max stream count (only if Support MST is enabled)
> +
> +properties:
> + compatible:
> + const: intel,dprx-20.0.1
> +
> + reg:
> + maxItems: 1
> +
> + interrupts:
> + maxItems: 1
> +
> + intel,max-link-rate:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + description: Max link rate configuration parameter

Please do not duplicate property name in description. It's useless.
Instead explain what is this responsible for.

Why max-link-rate would differ for the same dprx-20.0.1? And why
standard properties cannot be used?

Same for all questions below.

> +
> + intel,max-lane-count:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + description: Max lane count configuration parameter
> +
> + intel,multi-stream-support:
> + type: boolean
> + description: Multi-Stream Transport support configuration parameter
> +
> + intel,max-stream-count:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + description: Max stream count configuration parameter
> +
> + port:
> + $ref: /schemas/graph.yaml#/properties/port
> + description: SST main link

I don't understand why you have both port and ports. Shouldn't this be
under ports?

> +
> + 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,multi-stream-support
> + then:
> + required:
> + - intel,max-stream-count
> + - ports
> + else:
> + required:
> + - port


Best regards,
Krzysztof


2024-02-26 10:18:52

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [PATCH v2 9/9] ARM: dts: chameleonv3: Add video device nodes

On 21/02/2024 17:02, Paweł Anikiel wrote:
> Add device nodes for the video system present on the Chameleon v3.
> It consists of six framebuffers and two Intel Displayport receivers.
>
> Signed-off-by: Paweł Anikiel <[email protected]>
> ---

..

> + dprx_sst: dp-receiver@c0064000 {
> + compatible = "intel,dprx-20.0.1";
> + reg = <0xc0064000 0x800>;
> + interrupt-parent = <&dprx_sst_irq>;
> + interrupts = <0 IRQ_TYPE_EDGE_RISING>;
> + intel,max-link-rate = <0x1e>;

Rate is not in hex! Rate is in Hz, at least usually...

Fix your bindings...

Best regards,
Krzysztof


2024-02-26 11:02:36

by Paweł Anikiel

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

On Mon, Feb 26, 2024 at 10:13 AM Krzysztof Kozlowski
<[email protected]> wrote:
>
> On 21/02/2024 17:02, 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 | 160 ++++++++++++++++++
> > 1 file changed, 160 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..31025f2d5dcd
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/media/intel,dprx.yaml
> > @@ -0,0 +1,160 @@
> > +# 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]>
> > +
> > +description: |
> > + 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 IP features a large number of configuration parameters, found at:
> > + https://www.intel.com/content/www/us/en/docs/programmable/683273/23-3-20-0-1/sink-parameters.html
> > +
> > + The following parameters have to be enabled:
> > + - Support DisplayPort sink
> > + - Enable GPU control
> > + The following parameters' values have to be set in the devicetree:
> > + - RX maximum link rate
> > + - Maximum lane count
> > + - Support MST
> > + - Max stream count (only if Support MST is enabled)
> > +
> > +properties:
> > + compatible:
> > + const: intel,dprx-20.0.1
> > +
> > + reg:
> > + maxItems: 1
> > +
> > + interrupts:
> > + maxItems: 1
> > +
> > + intel,max-link-rate:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + description: Max link rate configuration parameter
>
> Please do not duplicate property name in description. It's useless.
> Instead explain what is this responsible for.
>
> Why max-link-rate would differ for the same dprx-20.0.1? And why
> standard properties cannot be used?
>
> Same for all questions below.

These four properties are the IP configuration parameters mentioned in
the device description. When generating the IP core you can set these
parameters, which could make them differ for the same dprx-20.0.1.
They are documented in the user guide, for which I also put a link in
the description. Is that enough? Or should I also document these
parameters here?

>
> > +
> > + intel,max-lane-count:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + description: Max lane count configuration parameter
> > +
> > + intel,multi-stream-support:
> > + type: boolean
> > + description: Multi-Stream Transport support configuration parameter
> > +
> > + intel,max-stream-count:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + description: Max stream count configuration parameter
> > +
> > + port:
> > + $ref: /schemas/graph.yaml#/properties/port
> > + description: SST main link
>
> I don't understand why you have both port and ports. Shouldn't this be
> under ports?

I put both so that you can use the shorter port property when the
device only has one port (i.e. no MST support). It would work fine
without it. If you think that's unnecessary, I can remove it (and use
the ports property even if there is only one).

2024-02-26 11:10:06

by Paweł Anikiel

[permalink] [raw]
Subject: Re: [PATCH v2 9/9] ARM: dts: chameleonv3: Add video device nodes

On Mon, Feb 26, 2024 at 10:15 AM Krzysztof Kozlowski
<[email protected]> wrote:
>
> On 21/02/2024 17:02, Paweł Anikiel wrote:
> > Add device nodes for the video system present on the Chameleon v3.
> > It consists of six framebuffers and two Intel Displayport receivers.
> >
> > Signed-off-by: Paweł Anikiel <[email protected]>
> > ---
>
> ...
>
> > + dprx_sst: dp-receiver@c0064000 {
> > + compatible = "intel,dprx-20.0.1";
> > + reg = <0xc0064000 0x800>;
> > + interrupt-parent = <&dprx_sst_irq>;
> > + interrupts = <0 IRQ_TYPE_EDGE_RISING>;
> > + intel,max-link-rate = <0x1e>;
>
> Rate is not in hex! Rate is in Hz, at least usually...
>
> Fix your bindings...

This is the DisplayPort link rate, for which the allowed values are
8.1 Gbps, 5.4 Gbps, 2.7 Gbps, or 1.62 Gbps. The standard way to encode
them (used in the DisplayPort DPCD registers and this device's
configuration) is by multiples of 0.27Gbps. This value (AFAIK) is
usually represented in hex, so 8.1Gbps would be 0x1e.

2024-02-26 12:06:47

by Krzysztof Kozlowski

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

On 26/02/2024 11:59, Paweł Anikiel wrote:
>>> +properties:
>>> + compatible:
>>> + const: intel,dprx-20.0.1
>>> +
>>> + reg:
>>> + maxItems: 1
>>> +
>>> + interrupts:
>>> + maxItems: 1
>>> +
>>> + intel,max-link-rate:
>>> + $ref: /schemas/types.yaml#/definitions/uint32
>>> + description: Max link rate configuration parameter
>>
>> Please do not duplicate property name in description. It's useless.
>> Instead explain what is this responsible for.
>>
>> Why max-link-rate would differ for the same dprx-20.0.1? And why
>> standard properties cannot be used?
>>
>> Same for all questions below.
>
> These four properties are the IP configuration parameters mentioned in
> the device description. When generating the IP core you can set these
> parameters, which could make them differ for the same dprx-20.0.1.
> They are documented in the user guide, for which I also put a link in
> the description. Is that enough? Or should I also document these
> parameters here?

user-guide is something for users, like user-space programmers or
end-users. I would never open it to look for any information related to
hardware.

Anyway, external resources are a no-go. We have it clearly in submitting
patches:

https://elixir.bootlin.com/linux/v6.8-rc6/source/Documentation/process/submitting-patches.rst#L130

>
>>
>>> +
>>> + intel,max-lane-count:
>>> + $ref: /schemas/types.yaml#/definitions/uint32
>>> + description: Max lane count configuration parameter
>>> +
>>> + intel,multi-stream-support:
>>> + type: boolean
>>> + description: Multi-Stream Transport support configuration parameter
>>> +
>>> + intel,max-stream-count:
>>> + $ref: /schemas/types.yaml#/definitions/uint32
>>> + description: Max stream count configuration parameter
>>> +
>>> + port:
>>> + $ref: /schemas/graph.yaml#/properties/port
>>> + description: SST main link
>>
>> I don't understand why you have both port and ports. Shouldn't this be
>> under ports?
>
> I put both so that you can use the shorter port property when the
> device only has one port (i.e. no MST support). It would work fine
> without it. If you think that's unnecessary, I can remove it (and use
> the ports property even if there is only one).

No, it is fine, but then you need allOf: which will restrict to only one
of them: either port or ports.

Best regards,
Krzysztof


2024-02-26 12:10:30

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [PATCH v2 9/9] ARM: dts: chameleonv3: Add video device nodes

On 26/02/2024 12:09, Paweł Anikiel wrote:
> On Mon, Feb 26, 2024 at 10:15 AM Krzysztof Kozlowski
> <[email protected]> wrote:
>>
>> On 21/02/2024 17:02, Paweł Anikiel wrote:
>>> Add device nodes for the video system present on the Chameleon v3.
>>> It consists of six framebuffers and two Intel Displayport receivers.
>>>
>>> Signed-off-by: Paweł Anikiel <[email protected]>
>>> ---
>>
>> ...
>>
>>> + dprx_sst: dp-receiver@c0064000 {
>>> + compatible = "intel,dprx-20.0.1";
>>> + reg = <0xc0064000 0x800>;
>>> + interrupt-parent = <&dprx_sst_irq>;
>>> + interrupts = <0 IRQ_TYPE_EDGE_RISING>;
>>> + intel,max-link-rate = <0x1e>;
>>
>> Rate is not in hex! Rate is in Hz, at least usually...
>>
>> Fix your bindings...
>
> This is the DisplayPort link rate, for which the allowed values are
> 8.1 Gbps, 5.4 Gbps, 2.7 Gbps, or 1.62 Gbps. The standard way to encode
> them (used in the DisplayPort DPCD registers and this device's

Then it is in bps or some other units:

https://github.com/devicetree-org/dt-schema/blob/main/dtschema/schemas/property-units.yaml

> configuration) is by multiples of 0.27Gbps. This value (AFAIK) is
> usually represented in hex, so 8.1Gbps would be 0x1e.

No, the value is represented in logical units. Frequency in Hz. Rate in
bps/kbps/etc. Voltage in volts.

Best regards,
Krzysztof


2024-02-26 12:28:11

by Paweł Anikiel

[permalink] [raw]
Subject: Re: [PATCH v2 9/9] ARM: dts: chameleonv3: Add video device nodes

On Mon, Feb 26, 2024 at 1:07 PM Krzysztof Kozlowski
<[email protected]> wrote:
>
> On 26/02/2024 12:09, Paweł Anikiel wrote:
> > On Mon, Feb 26, 2024 at 10:15 AM Krzysztof Kozlowski
> > <[email protected]> wrote:
> >>
> >> On 21/02/2024 17:02, Paweł Anikiel wrote:
> >>> Add device nodes for the video system present on the Chameleon v3.
> >>> It consists of six framebuffers and two Intel Displayport receivers.
> >>>
> >>> Signed-off-by: Paweł Anikiel <[email protected]>
> >>> ---
> >>
> >> ...
> >>
> >>> + dprx_sst: dp-receiver@c0064000 {
> >>> + compatible = "intel,dprx-20.0.1";
> >>> + reg = <0xc0064000 0x800>;
> >>> + interrupt-parent = <&dprx_sst_irq>;
> >>> + interrupts = <0 IRQ_TYPE_EDGE_RISING>;
> >>> + intel,max-link-rate = <0x1e>;
> >>
> >> Rate is not in hex! Rate is in Hz, at least usually...
> >>
> >> Fix your bindings...
> >
> > This is the DisplayPort link rate, for which the allowed values are
> > 8.1 Gbps, 5.4 Gbps, 2.7 Gbps, or 1.62 Gbps. The standard way to encode
> > them (used in the DisplayPort DPCD registers and this device's
>
> Then it is in bps or some other units:
>
> https://github.com/devicetree-org/dt-schema/blob/main/dtschema/schemas/property-units.yaml
>
> > configuration) is by multiples of 0.27Gbps. This value (AFAIK) is
> > usually represented in hex, so 8.1Gbps would be 0x1e.
>
> No, the value is represented in logical units. Frequency in Hz. Rate in
> bps/kbps/etc. Voltage in volts.

Okay, thanks for the info. So if I understand correctly, the max link
rate should be represented in bps in the devicetree, and then be
converted to the per 0.27Gbps value by the driver?

One problem is that the values here are too large to be represented in
bps (since the datatype is uint32). Can the property be in Mbps
instead?

2024-02-26 12:44:19

by Paweł Anikiel

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

On Mon, Feb 26, 2024 at 1:06 PM Krzysztof Kozlowski
<[email protected]> wrote:
>
> On 26/02/2024 11:59, Paweł Anikiel wrote:
> >>> +properties:
> >>> + compatible:
> >>> + const: intel,dprx-20.0.1
> >>> +
> >>> + reg:
> >>> + maxItems: 1
> >>> +
> >>> + interrupts:
> >>> + maxItems: 1
> >>> +
> >>> + intel,max-link-rate:
> >>> + $ref: /schemas/types.yaml#/definitions/uint32
> >>> + description: Max link rate configuration parameter
> >>
> >> Please do not duplicate property name in description. It's useless.
> >> Instead explain what is this responsible for.
> >>
> >> Why max-link-rate would differ for the same dprx-20.0.1? And why
> >> standard properties cannot be used?
> >>
> >> Same for all questions below.
> >
> > These four properties are the IP configuration parameters mentioned in
> > the device description. When generating the IP core you can set these
> > parameters, which could make them differ for the same dprx-20.0.1.
> > They are documented in the user guide, for which I also put a link in
> > the description. Is that enough? Or should I also document these
> > parameters here?
>
> user-guide is something for users, like user-space programmers or
> end-users. I would never open it to look for any information related to
> hardware.
>
> Anyway, external resources are a no-go. We have it clearly in submitting
> patches:
>
> https://elixir.bootlin.com/linux/v6.8-rc6/source/Documentation/process/submitting-patches.rst#L130

Okay, I will describe these properties in the bindings as well.

>
> >
> >>
> >>> +
> >>> + intel,max-lane-count:
> >>> + $ref: /schemas/types.yaml#/definitions/uint32
> >>> + description: Max lane count configuration parameter
> >>> +
> >>> + intel,multi-stream-support:
> >>> + type: boolean
> >>> + description: Multi-Stream Transport support configuration parameter
> >>> +
> >>> + intel,max-stream-count:
> >>> + $ref: /schemas/types.yaml#/definitions/uint32
> >>> + description: Max stream count configuration parameter
> >>> +
> >>> + port:
> >>> + $ref: /schemas/graph.yaml#/properties/port
> >>> + description: SST main link
> >>
> >> I don't understand why you have both port and ports. Shouldn't this be
> >> under ports?
> >
> > I put both so that you can use the shorter port property when the
> > device only has one port (i.e. no MST support). It would work fine
> > without it. If you think that's unnecessary, I can remove it (and use
> > the ports property even if there is only one).
>
> No, it is fine, but then you need allOf: which will restrict to only one
> of them: either port or ports.

There already is an allOf below that says that ports is required for
MST support and port is required otherwise. Isn't this enough?

2024-02-26 17:30:59

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [PATCH v2 9/9] ARM: dts: chameleonv3: Add video device nodes

On 26/02/2024 13:27, Paweł Anikiel wrote:
> On Mon, Feb 26, 2024 at 1:07 PM Krzysztof Kozlowski
> <[email protected]> wrote:
>>
>> On 26/02/2024 12:09, Paweł Anikiel wrote:
>>> On Mon, Feb 26, 2024 at 10:15 AM Krzysztof Kozlowski
>>> <[email protected]> wrote:
>>>>
>>>> On 21/02/2024 17:02, Paweł Anikiel wrote:
>>>>> Add device nodes for the video system present on the Chameleon v3.
>>>>> It consists of six framebuffers and two Intel Displayport receivers.
>>>>>
>>>>> Signed-off-by: Paweł Anikiel <[email protected]>
>>>>> ---
>>>>
>>>> ...
>>>>
>>>>> + dprx_sst: dp-receiver@c0064000 {
>>>>> + compatible = "intel,dprx-20.0.1";
>>>>> + reg = <0xc0064000 0x800>;
>>>>> + interrupt-parent = <&dprx_sst_irq>;
>>>>> + interrupts = <0 IRQ_TYPE_EDGE_RISING>;
>>>>> + intel,max-link-rate = <0x1e>;
>>>>
>>>> Rate is not in hex! Rate is in Hz, at least usually...
>>>>
>>>> Fix your bindings...
>>>
>>> This is the DisplayPort link rate, for which the allowed values are
>>> 8.1 Gbps, 5.4 Gbps, 2.7 Gbps, or 1.62 Gbps. The standard way to encode
>>> them (used in the DisplayPort DPCD registers and this device's
>>
>> Then it is in bps or some other units:
>>
>> https://github.com/devicetree-org/dt-schema/blob/main/dtschema/schemas/property-units.yaml
>>
>>> configuration) is by multiples of 0.27Gbps. This value (AFAIK) is
>>> usually represented in hex, so 8.1Gbps would be 0x1e.
>>
>> No, the value is represented in logical units. Frequency in Hz. Rate in
>> bps/kbps/etc. Voltage in volts.
>
> Okay, thanks for the info. So if I understand correctly, the max link
> rate should be represented in bps in the devicetree, and then be

or kbps

> converted to the per 0.27Gbps value by the driver?

If driver needs some register-based value, then yes.

>
> One problem is that the values here are too large to be represented in
> bps (since the datatype is uint32). Can the property be in Mbps
> instead?

Can be. You can submit a patch to dtschema (patch to DT spec list or
github pull request) adding '-mbps' as well.

Best regards,
Krzysztof


2024-02-26 17:36:53

by Krzysztof Kozlowski

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

On 26/02/2024 13:43, Paweł Anikiel wrote:
>>>>> + intel,max-stream-count:
>>>>> + $ref: /schemas/types.yaml#/definitions/uint32
>>>>> + description: Max stream count configuration parameter
>>>>> +
>>>>> + port:
>>>>> + $ref: /schemas/graph.yaml#/properties/port
>>>>> + description: SST main link
>>>>
>>>> I don't understand why you have both port and ports. Shouldn't this be
>>>> under ports?
>>>
>>> I put both so that you can use the shorter port property when the
>>> device only has one port (i.e. no MST support). It would work fine
>>> without it. If you think that's unnecessary, I can remove it (and use
>>> the ports property even if there is only one).
>>
>> No, it is fine, but then you need allOf: which will restrict to only one
>> of them: either port or ports.
>
> There already is an allOf below that says that ports is required for
> MST support and port is required otherwise. Isn't this enough?

Add both port and ports and see if it is enough.

Best regards,
Krzysztof


2024-02-27 11:28:42

by Paweł Anikiel

[permalink] [raw]
Subject: Re: [PATCH v2 9/9] ARM: dts: chameleonv3: Add video device nodes

On Mon, Feb 26, 2024 at 6:30 PM Krzysztof Kozlowski
<[email protected]> wrote:
>
> On 26/02/2024 13:27, Paweł Anikiel wrote:
> > On Mon, Feb 26, 2024 at 1:07 PM Krzysztof Kozlowski
> > <[email protected]> wrote:
> >>
> >> On 26/02/2024 12:09, Paweł Anikiel wrote:
> >>> On Mon, Feb 26, 2024 at 10:15 AM Krzysztof Kozlowski
> >>> <[email protected]> wrote:
> >>>>
> >>>> On 21/02/2024 17:02, Paweł Anikiel wrote:
> >>>>> Add device nodes for the video system present on the Chameleon v3.
> >>>>> It consists of six framebuffers and two Intel Displayport receivers.
> >>>>>
> >>>>> Signed-off-by: Paweł Anikiel <[email protected]>
> >>>>> ---
> >>>>
> >>>> ...
> >>>>
> >>>>> + dprx_sst: dp-receiver@c0064000 {
> >>>>> + compatible = "intel,dprx-20.0.1";
> >>>>> + reg = <0xc0064000 0x800>;
> >>>>> + interrupt-parent = <&dprx_sst_irq>;
> >>>>> + interrupts = <0 IRQ_TYPE_EDGE_RISING>;
> >>>>> + intel,max-link-rate = <0x1e>;
> >>>>
> >>>> Rate is not in hex! Rate is in Hz, at least usually...
> >>>>
> >>>> Fix your bindings...
> >>>
> >>> This is the DisplayPort link rate, for which the allowed values are
> >>> 8.1 Gbps, 5.4 Gbps, 2.7 Gbps, or 1.62 Gbps. The standard way to encode
> >>> them (used in the DisplayPort DPCD registers and this device's
> >>
> >> Then it is in bps or some other units:
> >>
> >> https://github.com/devicetree-org/dt-schema/blob/main/dtschema/schemas/property-units.yaml
> >>
> >>> configuration) is by multiples of 0.27Gbps. This value (AFAIK) is
> >>> usually represented in hex, so 8.1Gbps would be 0x1e.
> >>
> >> No, the value is represented in logical units. Frequency in Hz. Rate in
> >> bps/kbps/etc. Voltage in volts.
> >
> > Okay, thanks for the info. So if I understand correctly, the max link
> > rate should be represented in bps in the devicetree, and then be
>
> or kbps

The one that's already present in dtschema is kBps (kilobytes per
second) which isn't right for this case IMO.

>
> > converted to the per 0.27Gbps value by the driver?
>
> If driver needs some register-based value, then yes.
>
> >
> > One problem is that the values here are too large to be represented in
> > bps (since the datatype is uint32). Can the property be in Mbps
> > instead?
>
> Can be. You can submit a patch to dtschema (patch to DT spec list or
> github pull request) adding '-mbps' as well.

I sent a PR with both kbps and mbps.

2024-02-27 13:11:57

by Paweł Anikiel

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

On Mon, Feb 26, 2024 at 6:29 PM Krzysztof Kozlowski
<[email protected]> wrote:
>
> On 26/02/2024 13:43, Paweł Anikiel wrote:
> >>>>> + intel,max-stream-count:
> >>>>> + $ref: /schemas/types.yaml#/definitions/uint32
> >>>>> + description: Max stream count configuration parameter
> >>>>> +
> >>>>> + port:
> >>>>> + $ref: /schemas/graph.yaml#/properties/port
> >>>>> + description: SST main link
> >>>>
> >>>> I don't understand why you have both port and ports. Shouldn't this be
> >>>> under ports?
> >>>
> >>> I put both so that you can use the shorter port property when the
> >>> device only has one port (i.e. no MST support). It would work fine
> >>> without it. If you think that's unnecessary, I can remove it (and use
> >>> the ports property even if there is only one).
> >>
> >> No, it is fine, but then you need allOf: which will restrict to only one
> >> of them: either port or ports.
> >
> > There already is an allOf below that says that ports is required for
> > MST support and port is required otherwise. Isn't this enough?
>
> Add both port and ports and see if it is enough.

Ok, I see. I tried and this seems to work:

oneOf:
- required:
- port
- required:
- ports

And that would make the if/else with port and ports below not needed.
What do you think?

2024-02-27 14:30:59

by Rob Herring (Arm)

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

On Mon, Feb 26, 2024 at 11:59:42AM +0100, Paweł Anikiel wrote:
> On Mon, Feb 26, 2024 at 10:13 AM Krzysztof Kozlowski
> <[email protected]> wrote:
> >
> > On 21/02/2024 17:02, 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 | 160 ++++++++++++++++++
> > > 1 file changed, 160 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..31025f2d5dcd
> > > --- /dev/null
> > > +++ b/Documentation/devicetree/bindings/media/intel,dprx.yaml
> > > @@ -0,0 +1,160 @@
> > > +# 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]>
> > > +
> > > +description: |
> > > + 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 IP features a large number of configuration parameters, found at:
> > > + https://www.intel.com/content/www/us/en/docs/programmable/683273/23-3-20-0-1/sink-parameters.html
> > > +
> > > + The following parameters have to be enabled:
> > > + - Support DisplayPort sink
> > > + - Enable GPU control
> > > + The following parameters' values have to be set in the devicetree:
> > > + - RX maximum link rate
> > > + - Maximum lane count
> > > + - Support MST
> > > + - Max stream count (only if Support MST is enabled)
> > > +
> > > +properties:
> > > + compatible:
> > > + const: intel,dprx-20.0.1
> > > +
> > > + reg:
> > > + maxItems: 1
> > > +
> > > + interrupts:
> > > + maxItems: 1
> > > +
> > > + intel,max-link-rate:
> > > + $ref: /schemas/types.yaml#/definitions/uint32
> > > + description: Max link rate configuration parameter
> >
> > Please do not duplicate property name in description. It's useless.
> > Instead explain what is this responsible for.
> >
> > Why max-link-rate would differ for the same dprx-20.0.1? And why
> > standard properties cannot be used?
> >
> > Same for all questions below.
>
> These four properties are the IP configuration parameters mentioned in
> the device description. When generating the IP core you can set these
> parameters, which could make them differ for the same dprx-20.0.1.
> They are documented in the user guide, for which I also put a link in
> the description. Is that enough? Or should I also document these
> parameters here?

Use the standard properties: link-frequencies and data-lanes. Those go
under the port(s) because they are inheritly per logical link.

Rob

2024-02-27 14:33:26

by Rob Herring (Arm)

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

On Tue, Feb 27, 2024 at 02:11:27PM +0100, Paweł Anikiel wrote:
> On Mon, Feb 26, 2024 at 6:29 PM Krzysztof Kozlowski
> <[email protected]> wrote:
> >
> > On 26/02/2024 13:43, Paweł Anikiel wrote:
> > >>>>> + intel,max-stream-count:
> > >>>>> + $ref: /schemas/types.yaml#/definitions/uint32
> > >>>>> + description: Max stream count configuration parameter
> > >>>>> +
> > >>>>> + port:
> > >>>>> + $ref: /schemas/graph.yaml#/properties/port
> > >>>>> + description: SST main link
> > >>>>
> > >>>> I don't understand why you have both port and ports. Shouldn't this be
> > >>>> under ports?
> > >>>
> > >>> I put both so that you can use the shorter port property when the
> > >>> device only has one port (i.e. no MST support). It would work fine
> > >>> without it. If you think that's unnecessary, I can remove it (and use
> > >>> the ports property even if there is only one).
> > >>
> > >> No, it is fine, but then you need allOf: which will restrict to only one
> > >> of them: either port or ports.
> > >
> > > There already is an allOf below that says that ports is required for
> > > MST support and port is required otherwise. Isn't this enough?
> >
> > Add both port and ports and see if it is enough.
>
> Ok, I see. I tried and this seems to work:
>
> oneOf:
> - required:
> - port
> - required:
> - ports
>
> And that would make the if/else with port and ports below not needed.
> What do you think?

Just always use 'ports' rather than complicate things.

Rob

2024-02-28 11:06:09

by Paweł Anikiel

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

On Tue, Feb 27, 2024 at 3:29 PM Rob Herring <[email protected]> wrote:
>
> On Mon, Feb 26, 2024 at 11:59:42AM +0100, Paweł Anikiel wrote:
> > On Mon, Feb 26, 2024 at 10:13 AM Krzysztof Kozlowski
> > <[email protected]> wrote:
> > >
> > > On 21/02/2024 17:02, 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 | 160 ++++++++++++++++++
> > > > 1 file changed, 160 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..31025f2d5dcd
> > > > --- /dev/null
> > > > +++ b/Documentation/devicetree/bindings/media/intel,dprx.yaml
> > > > @@ -0,0 +1,160 @@
> > > > +# 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]>
> > > > +
> > > > +description: |
> > > > + 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 IP features a large number of configuration parameters, found at:
> > > > + https://www.intel.com/content/www/us/en/docs/programmable/683273/23-3-20-0-1/sink-parameters.html
> > > > +
> > > > + The following parameters have to be enabled:
> > > > + - Support DisplayPort sink
> > > > + - Enable GPU control
> > > > + The following parameters' values have to be set in the devicetree:
> > > > + - RX maximum link rate
> > > > + - Maximum lane count
> > > > + - Support MST
> > > > + - Max stream count (only if Support MST is enabled)
> > > > +
> > > > +properties:
> > > > + compatible:
> > > > + const: intel,dprx-20.0.1
> > > > +
> > > > + reg:
> > > > + maxItems: 1
> > > > +
> > > > + interrupts:
> > > > + maxItems: 1
> > > > +
> > > > + intel,max-link-rate:
> > > > + $ref: /schemas/types.yaml#/definitions/uint32
> > > > + description: Max link rate configuration parameter
> > >
> > > Please do not duplicate property name in description. It's useless.
> > > Instead explain what is this responsible for.
> > >
> > > Why max-link-rate would differ for the same dprx-20.0.1? And why
> > > standard properties cannot be used?
> > >
> > > Same for all questions below.
> >
> > These four properties are the IP configuration parameters mentioned in
> > the device description. When generating the IP core you can set these
> > parameters, which could make them differ for the same dprx-20.0.1.
> > They are documented in the user guide, for which I also put a link in
> > the description. Is that enough? Or should I also document these
> > parameters here?
>
> Use the standard properties: link-frequencies and data-lanes. Those go
> under the port(s) because they are inheritly per logical link.

The DP receiver has one input interface (a deserialized DP stream),
and up to four output interfaces (the decoded video streams). The "max
link rate" and "max lane count" parameters only describe the input
interface to the receiver. However, the port(s) I am using here are
for the output streams. They are not affected by those parameters, so
I don't think these properties should go under the output port(s).

The receiver doesn't have an input port in the DT, because there isn't
any controllable entity on the other side - the deserializer doesn't
have any software interface. Since these standard properties
(link-frequencies and data-lanes) are only defined in
video-interfaces.yaml (which IIUC describes a graph endpoint), I can't
use them directly in the device node.

Do you see a way to use these standard properties here?

2024-02-28 11:24:25

by Hans Verkuil

[permalink] [raw]
Subject: Re: [PATCH v2 2/9] media: Add Chameleon v3 framebuffer driver

Hi Paweł,

On 21/02/2024 17:02, Paweł Anikiel wrote:
> Add v4l2 driver for the Google Chameleon v3 framebuffer device.

This is just a video capture device, right? A framebuffer device is something
that lives in drivers/video/fbdev.

It is *very* confusing to see the term 'framebuffer' used in a video
capture context.

This commit log should also give a better description of the hardware.
Just a single one-liner is a bit on the short side :-)

>
> Signed-off-by: Paweł Anikiel <[email protected]>
> ---
> 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 | 895 ++++++++++++++++++

chv3-video.c would be a much better name for chv3-fb.c.

That's a commonly used filename for video capture drivers.

> 7 files changed, 918 insertions(+)
> create mode 100644 drivers/media/platform/google/Kconfig
> create mode 100644 drivers/media/platform/google/Makefile
> create mode 100644 drivers/media/platform/google/chameleonv3/Kconfig
> create mode 100644 drivers/media/platform/google/chameleonv3/Makefile
> create mode 100644 drivers/media/platform/google/chameleonv3/chv3-fb.c
>
> diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
> index 91e54215de3a..b82f7b142b85 100644
> --- a/drivers/media/platform/Kconfig
> +++ b/drivers/media/platform/Kconfig
> @@ -69,6 +69,7 @@ source "drivers/media/platform/aspeed/Kconfig"
> source "drivers/media/platform/atmel/Kconfig"
> source "drivers/media/platform/cadence/Kconfig"
> source "drivers/media/platform/chips-media/Kconfig"
> +source "drivers/media/platform/google/Kconfig"
> source "drivers/media/platform/intel/Kconfig"
> source "drivers/media/platform/marvell/Kconfig"
> source "drivers/media/platform/mediatek/Kconfig"
> diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
> index 3296ec1ebe16..f7067eb05f76 100644
> --- a/drivers/media/platform/Makefile
> +++ b/drivers/media/platform/Makefile
> @@ -12,6 +12,7 @@ obj-y += aspeed/
> obj-y += atmel/
> obj-y += cadence/
> obj-y += chips-media/
> +obj-y += google/
> obj-y += intel/
> obj-y += marvell/
> obj-y += mediatek/
> diff --git a/drivers/media/platform/google/Kconfig b/drivers/media/platform/google/Kconfig
> new file mode 100644
> index 000000000000..2a5f01034c11
> --- /dev/null
> +++ b/drivers/media/platform/google/Kconfig
> @@ -0,0 +1,3 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +source "drivers/media/platform/google/chameleonv3/Kconfig"
> diff --git a/drivers/media/platform/google/Makefile b/drivers/media/platform/google/Makefile
> new file mode 100644
> index 000000000000..c971a09faeb4
> --- /dev/null
> +++ b/drivers/media/platform/google/Makefile
> @@ -0,0 +1,2 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +obj-y += chameleonv3/
> diff --git a/drivers/media/platform/google/chameleonv3/Kconfig b/drivers/media/platform/google/chameleonv3/Kconfig
> new file mode 100644
> index 000000000000..76d0383a8589
> --- /dev/null
> +++ b/drivers/media/platform/google/chameleonv3/Kconfig
> @@ -0,0 +1,13 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +config VIDEO_CHV3_FB
> + tristate "Google Chameleon v3 framebuffer video driver"
> + depends on V4L_PLATFORM_DRIVERS
> + depends on VIDEO_DEV
> + select VIDEOBUF2_DMA_CONTIG
> + select V4L2_FWNODE
> + help
> + v4l2 driver for the video interface present on the Google
> + Chameleon v3. The Chameleon v3 uses the framebuffer IP core
> + to take the video signal from different sources and directly
> + write frames into memory.

So it is composing different video streams into buffers? Or does it
capture from a single source at a time? The text is rather ambiguous.

> diff --git a/drivers/media/platform/google/chameleonv3/Makefile b/drivers/media/platform/google/chameleonv3/Makefile
> new file mode 100644
> index 000000000000..d63727148688
> --- /dev/null
> +++ b/drivers/media/platform/google/chameleonv3/Makefile
> @@ -0,0 +1,3 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +obj-$(CONFIG_VIDEO_CHV3_FB) += chv3-fb.o
> diff --git a/drivers/media/platform/google/chameleonv3/chv3-fb.c b/drivers/media/platform/google/chameleonv3/chv3-fb.c
> new file mode 100644
> index 000000000000..35a44365eba0
> --- /dev/null
> +++ b/drivers/media/platform/google/chameleonv3/chv3-fb.c
> @@ -0,0 +1,895 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2023-2024 Google LLC.
> + * Author: Paweł Anikiel <[email protected]>
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/videodev2.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-dv-timings.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/videobuf2-dma-contig.h>
> +
> +#define DEVICE_NAME "chv3-fb"
> +
> +/*
> + * The device is expected to report some format even if there's currently no
> + * active video stream. In such case we default to 1080p.
> + */
> +#define DEFAULT_WIDTH 1920
> +#define DEFAULT_HEIGHT 1080
> +
> +#define FB_EN 0x00
> +#define FB_EN_BIT BIT(0)
> +#define FB_HEIGHT 0x04
> +#define FB_WIDTH 0x08
> +#define FB_BUFFERA 0x0c
> +#define FB_BUFFERB 0x10
> +#define FB_BUFFERSIZE 0x14
> +#define FB_RESET 0x18
> +#define FB_RESET_BIT BIT(0)
> +#define FB_ERRORSTATUS 0x1c
> +#define FB_IOCOLOR 0x20
> +#define FB_DATARATE 0x24
> +#define FB_DATARATE_SINGLE 0x0
> +#define FB_DATARATE_DOUBLE 0x1
> +#define FB_PIXELMODE 0x28
> +#define FB_PIXELMODE_SINGLE 0x0
> +#define FB_PIXELMODE_DOUBLE 0x1
> +#define FB_SYNCPOLARITY 0x2c
> +#define FB_DMAFORMAT 0x30
> +#define FB_DMAFORMAT_8BPC 0x0
> +#define FB_DMAFORMAT_10BPC_UPPER 0x1
> +#define FB_DMAFORMAT_10BPC_LOWER 0x2
> +#define FB_DMAFORMAT_12BPC_UPPER 0x3
> +#define FB_DMAFORMAT_12BPC_LOWER 0x4
> +#define FB_DMAFORMAT_16BPC 0x5
> +#define FB_DMAFORMAT_RAW 0x6
> +#define FB_DMAFORMAT_8BPC_LEGACY 0x7
> +#define FB_VERSION 0x34
> +#define FB_VERSION_CURRENT 0xc0fb0001
> +
> +#define FB_IRQ_MASK 0x8
> +#define FB_IRQ_CLR 0xc
> +#define FB_IRQ_ALL 0xf
> +#define FB_IRQ_BUFF0 BIT(0)
> +#define FB_IRQ_BUFF1 BIT(1)
> +#define FB_IRQ_RESOLUTION BIT(2)
> +#define FB_IRQ_ERROR BIT(3)
> +
> +struct chv3_fb {
> + struct device *dev;
> + void __iomem *iobase;
> + void __iomem *iobase_irq;
> +
> + struct v4l2_device v4l2_dev;
> + struct vb2_queue queue;
> + struct video_device vdev;
> + struct v4l2_pix_format pix_fmt;
> + struct v4l2_dv_timings timings;
> +
> + struct v4l2_async_notifier notifier;
> + struct v4l2_subdev *subdev;
> + int subdev_source_pad;
> +
> + u32 sequence;
> + bool writing_to_a;
> +
> + struct list_head bufs;
> + spinlock_t bufs_lock;
> +
> + struct mutex fb_lock;
> +};
> +
> +struct chv3_fb_buffer {
> + struct vb2_v4l2_buffer vb;
> + struct list_head link;
> +};
> +
> +static void chv3_fb_set_format_resolution(struct chv3_fb *fb, u32 width, u32 height)
> +{
> + u32 bytes_per_pixel;
> +
> + if (fb->pix_fmt.pixelformat == V4L2_PIX_FMT_RGB24)
> + bytes_per_pixel = 3;
> + else
> + bytes_per_pixel = 4;
> +
> + fb->pix_fmt.width = width;
> + fb->pix_fmt.height = height;
> + fb->pix_fmt.bytesperline = width * bytes_per_pixel;
> + fb->pix_fmt.sizeimage = fb->pix_fmt.bytesperline * height;
> +}
> +
> +/*
> + * The video interface has hardware counters which expose the width and
> + * height of the current video stream. It can't reliably detect if the stream
> + * is present or not, so this is only used as a fallback in the case where
> + * we don't have access to the receiver hardware.
> + */
> +static int chv3_fb_query_dv_timings_fallback(struct chv3_fb *fb,
> + struct v4l2_dv_timings *timings)
> +{
> + u32 width, height;
> +
> + width = readl(fb->iobase + FB_WIDTH);
> + height = readl(fb->iobase + FB_HEIGHT);
> + if (width == 0 || height == 0)
> + return -ENOLINK;
> +
> + memset(timings, 0, sizeof(*timings));
> + timings->type = V4L2_DV_BT_656_1120;
> + timings->bt.width = width;
> + timings->bt.height = height;
> +
> + return 0;
> +}
> +
> +static int chv3_fb_query_dv_timings(struct chv3_fb *fb, struct v4l2_dv_timings *timings)
> +{
> + if (fb->subdev) {
> + return v4l2_subdev_call(fb->subdev, pad, query_dv_timings,
> + fb->subdev_source_pad, timings);
> + } else {
> + return chv3_fb_query_dv_timings_fallback(fb, timings);
> + }
> +}
> +
> +static const struct v4l2_dv_timings_cap chv3_fb_fallback_dv_timings_cap = {
> + .type = V4L2_DV_BT_656_1120,
> + .bt = {
> + .min_width = 0,

This is an unlikely minimum width (ditto for height below).

> + .max_width = 65535,

The max is suspect as well: I would expect it to be a multiple of 2/4/8/16.

> + .min_height = 0,
> + .max_height = 65535,
> + .min_pixelclock = 0,
> + .max_pixelclock = 2147483647,

Ditto for these.

> + .standards = V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT |
> + V4L2_DV_BT_STD_CVT | V4L2_DV_BT_STD_GTF,
> + .capabilities = V4L2_DV_BT_CAP_PROGRESSIVE |
> + V4L2_DV_BT_CAP_REDUCED_BLANKING |
> + V4L2_DV_BT_CAP_CUSTOM,
> + },
> +};
> +
> +static int chv3_fb_enum_dv_timings_fallback(struct chv3_fb *fb,
> + struct v4l2_enum_dv_timings *timings)
> +{
> + return v4l2_enum_dv_timings_cap(timings, &chv3_fb_fallback_dv_timings_cap,
> + NULL, NULL);
> +}
> +
> +static int chv3_fb_dv_timings_cap_fallback(struct chv3_fb *fb,
> + struct v4l2_dv_timings_cap *cap)
> +{
> + *cap = chv3_fb_fallback_dv_timings_cap;
> +
> + return 0;
> +}
> +
> +static void chv3_fb_apply_dv_timings(struct chv3_fb *fb)
> +{
> + struct v4l2_dv_timings timings;
> + int res;
> +
> + res = chv3_fb_query_dv_timings(fb, &timings);
> + if (res)
> + return;
> +
> + fb->timings = timings;
> + chv3_fb_set_format_resolution(fb, timings.bt.width, timings.bt.height);
> +}
> +
> +static int chv3_fb_querycap(struct file *file, void *fh,
> + struct v4l2_capability *cap)
> +{
> + strscpy(cap->driver, DEVICE_NAME, sizeof(cap->driver));
> + strscpy(cap->card, "Chameleon v3 video", sizeof(cap->card));
> +
> + return 0;
> +}
> +
> +static int chv3_fb_g_fmt_vid_cap(struct file *file, void *fh,
> + struct v4l2_format *fmt)
> +{
> + struct chv3_fb *fb = video_drvdata(file);
> +
> + fmt->fmt.pix = fb->pix_fmt;
> +
> + return 0;
> +}
> +
> +static int chv3_fb_enum_fmt_vid_cap(struct file *file, void *fh,
> + struct v4l2_fmtdesc *fmt)
> +{
> + struct chv3_fb *fb = video_drvdata(file);
> +
> + if (fmt->index != 0)
> + return -EINVAL;
> +
> + fmt->flags = 0;
> + fmt->pixelformat = fb->pix_fmt.pixelformat;
> +
> + return 0;
> +}
> +
> +static int chv3_fb_enum_framesizes(struct file *file, void *fh,
> + struct v4l2_frmsizeenum *frm)
> +{
> + struct chv3_fb *fb = video_drvdata(file);
> +
> + if (frm->index != 0)
> + return -EINVAL;
> +
> + if (frm->pixel_format != fb->pix_fmt.pixelformat)
> + return -EINVAL;
> +
> + frm->type = V4L2_FRMSIZE_TYPE_DISCRETE;
> + frm->discrete.width = fb->pix_fmt.width;
> + frm->discrete.height = fb->pix_fmt.height;

This is not something you would expect to see supported for a
video receiver input. This is something you use for camera sensors.

This should almost certainly be dropped.

> +
> + return 0;
> +}
> +
> +static int chv3_fb_g_input(struct file *file, void *fh, unsigned int *index)
> +{
> + *index = 0;
> +
> + return 0;
> +}
> +
> +static int chv3_fb_s_input(struct file *file, void *fh, unsigned int index)
> +{
> + if (index != 0)
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static int chv3_fb_enum_input(struct file *file, void *fh,
> + struct v4l2_input *input)
> +{
> + if (input->index != 0)
> + return -EINVAL;
> +
> + strscpy(input->name, "input0", sizeof(input->name));
> + input->type = V4L2_INPUT_TYPE_CAMERA;
> + input->capabilities = V4L2_IN_CAP_DV_TIMINGS;
> +
> + return 0;
> +}
> +
> +static int chv3_fb_g_edid(struct file *file, void *fh,
> + struct v4l2_edid *edid)
> +{
> + struct chv3_fb *fb = video_drvdata(file);
> + int res;
> +
> + if (!fb->subdev)
> + return -ENOTTY;
> +
> + if (edid->pad != 0)
> + return -EINVAL;
> +
> + edid->pad = fb->subdev_source_pad;
> + res = v4l2_subdev_call(fb->subdev, pad, get_edid, edid);
> + edid->pad = 0;
> +
> + return res;
> +}
> +
> +static int chv3_fb_s_edid(struct file *file, void *fh,
> + struct v4l2_edid *edid)
> +{
> + struct chv3_fb *fb = video_drvdata(file);
> + int res;
> +
> + if (!fb->subdev)
> + return -ENOTTY;
> +
> + if (edid->pad != 0)
> + return -EINVAL;
> +
> + edid->pad = fb->subdev_source_pad;
> + res = v4l2_subdev_call(fb->subdev, pad, set_edid, edid);
> + edid->pad = 0;
> +
> + return res;
> +}
> +
> +static int chv3_fb_s_dv_timings(struct file *file, void *fh,
> + struct v4l2_dv_timings *timings)
> +{
> + struct chv3_fb *fb = video_drvdata(file);
> +
> + if (timings->bt.width == fb->timings.bt.width &&
> + timings->bt.height == fb->timings.bt.height)

You would typically use v4l2_match_dv_timings() for this. The check above
is insufficient since it does not even check for different framerates or
interlaced vs progressive.

> + return 0;
> +
> + if (vb2_is_busy(&fb->queue))
> + return -EBUSY;
> +
> + if (!v4l2_valid_dv_timings(timings, &chv3_fb_fallback_dv_timings_cap, NULL, NULL))
> + return -ERANGE;
> +
> + fb->timings = *timings;
> + chv3_fb_set_format_resolution(fb, timings->bt.width, timings->bt.height);
> +
> + return 0;
> +}
> +
> +static int chv3_fb_g_dv_timings(struct file *file, void *fh,
> + struct v4l2_dv_timings *timings)
> +{
> + struct chv3_fb *fb = video_drvdata(file);
> +
> + *timings = fb->timings;
> + return 0;
> +}
> +
> +static int chv3_fb_vidioc_query_dv_timings(struct file *file, void *fh,
> + struct v4l2_dv_timings *timings)
> +{
> + struct chv3_fb *fb = video_drvdata(file);
> +
> + return chv3_fb_query_dv_timings(fb, timings);
> +}
> +
> +static int chv3_fb_enum_dv_timings(struct file *file, void *fh,
> + struct v4l2_enum_dv_timings *timings)
> +{
> + struct chv3_fb *fb = video_drvdata(file);
> + int res;
> +
> + if (timings->pad != 0)
> + return -EINVAL;
> +
> + if (fb->subdev) {
> + timings->pad = fb->subdev_source_pad;
> + res = v4l2_subdev_call(fb->subdev, pad, enum_dv_timings, timings);
> + timings->pad = 0;
> + return res;
> + } else {
> + return chv3_fb_enum_dv_timings_fallback(fb, timings);
> + }
> +}
> +
> +static int chv3_fb_dv_timings_cap(struct file *file, void *fh,
> + struct v4l2_dv_timings_cap *cap)
> +{
> + struct chv3_fb *fb = video_drvdata(file);
> + int res;
> +
> + if (cap->pad != 0)
> + return -EINVAL;
> +
> + if (fb->subdev) {
> + cap->pad = fb->subdev_source_pad;
> + res = v4l2_subdev_call(fb->subdev, pad, dv_timings_cap, cap);
> + cap->pad = 0;
> + return res;
> + } else {
> + return chv3_fb_dv_timings_cap_fallback(fb, cap);
> + }
> +}
> +
> +static int chv3_fb_subscribe_event(struct v4l2_fh *fh,
> + const struct v4l2_event_subscription *sub)
> +{
> + switch (sub->type) {
> + case V4L2_EVENT_SOURCE_CHANGE:
> + return v4l2_src_change_event_subscribe(fh, sub);
> + }
> +
> + return v4l2_ctrl_subscribe_event(fh, sub);
> +}

I am missing support for the V4L2_CID_DV_RX_POWER_PRESENT control:

https://hverkuil.home.xs4all.nl/spec/userspace-api/v4l/ext-ctrls-dv.html

> +
> +static const struct v4l2_ioctl_ops chv3_fb_v4l2_ioctl_ops = {
> + .vidioc_querycap = chv3_fb_querycap,
> +
> + .vidioc_enum_fmt_vid_cap = chv3_fb_enum_fmt_vid_cap,
> + .vidioc_g_fmt_vid_cap = chv3_fb_g_fmt_vid_cap,
> + .vidioc_s_fmt_vid_cap = chv3_fb_g_fmt_vid_cap,
> + .vidioc_try_fmt_vid_cap = chv3_fb_g_fmt_vid_cap,
> +
> + .vidioc_enum_framesizes = chv3_fb_enum_framesizes,
> +
> + .vidioc_enum_input = chv3_fb_enum_input,
> + .vidioc_g_input = chv3_fb_g_input,
> + .vidioc_s_input = chv3_fb_s_input,
> + .vidioc_g_edid = chv3_fb_g_edid,
> + .vidioc_s_edid = chv3_fb_s_edid,
> +
> + .vidioc_reqbufs = vb2_ioctl_reqbufs,
> + .vidioc_create_bufs = vb2_ioctl_create_bufs,
> + .vidioc_querybuf = vb2_ioctl_querybuf,
> + .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
> + .vidioc_expbuf = vb2_ioctl_expbuf,
> + .vidioc_qbuf = vb2_ioctl_qbuf,
> + .vidioc_dqbuf = vb2_ioctl_dqbuf,
> + .vidioc_streamon = vb2_ioctl_streamon,
> + .vidioc_streamoff = vb2_ioctl_streamoff,
> +
> + .vidioc_s_dv_timings = chv3_fb_s_dv_timings,
> + .vidioc_g_dv_timings = chv3_fb_g_dv_timings,
> + .vidioc_query_dv_timings = chv3_fb_vidioc_query_dv_timings,
> + .vidioc_enum_dv_timings = chv3_fb_enum_dv_timings,
> + .vidioc_dv_timings_cap = chv3_fb_dv_timings_cap,
> +
> + .vidioc_subscribe_event = chv3_fb_subscribe_event,
> + .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
> +};
> +
> +static int chv3_fb_queue_setup(struct vb2_queue *q,
> + unsigned int *nbuffers, unsigned int *nplanes,
> + unsigned int sizes[], struct device *alloc_devs[])
> +{
> + struct chv3_fb *fb = vb2_get_drv_priv(q);
> +
> + if (*nplanes) {
> + if (sizes[0] < fb->pix_fmt.sizeimage)
> + return -EINVAL;
> + return 0;
> + }
> + *nplanes = 1;
> + sizes[0] = fb->pix_fmt.sizeimage;
> +
> + return 0;
> +}
> +
> +/*
> + * There are two address registers: BUFFERA and BUFFERB. The framebuffer
> + * alternates writing between them (i.e. even frames go to BUFFERA, odd
> + * ones to BUFFERB).
> + *
> + * (buffer queue) > QUEUED ---> QUEUED ---> QUEUED ---> ...
> + * BUFFERA BUFFERB
> + * (hw writing to this) ^
> + * (and then to this) ^
> + *
> + * The buffer swapping happens at irq time. When an irq comes, the next
> + * frame is already assigned an address in the buffer queue. This gives
> + * the irq handler a whole frame's worth of time to update the buffer
> + * address register.
> + */
> +
> +static dma_addr_t chv3_fb_buffer_dma_addr(struct chv3_fb_buffer *buf)
> +{
> + return vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
> +}
> +
> +static void chv3_fb_start_frame(struct chv3_fb *fb, struct chv3_fb_buffer *buf)
> +{
> + fb->writing_to_a = 1;
> + writel(chv3_fb_buffer_dma_addr(buf), fb->iobase + FB_BUFFERA);
> + writel(FB_EN_BIT, fb->iobase + FB_EN);
> +}
> +
> +static void chv3_fb_next_frame(struct chv3_fb *fb, struct chv3_fb_buffer *buf)
> +{
> + u32 reg = fb->writing_to_a ? FB_BUFFERB : FB_BUFFERA;
> +
> + writel(chv3_fb_buffer_dma_addr(buf), fb->iobase + reg);
> +}
> +
> +static int chv3_fb_start_streaming(struct vb2_queue *q, unsigned int count)
> +{
> + struct chv3_fb *fb = vb2_get_drv_priv(q);
> + struct chv3_fb_buffer *buf;
> + unsigned long flags;
> +
> + fb->sequence = 0;
> + writel(fb->pix_fmt.sizeimage, fb->iobase + FB_BUFFERSIZE);
> +
> + spin_lock_irqsave(&fb->bufs_lock, flags);
> + buf = list_first_entry_or_null(&fb->bufs, struct chv3_fb_buffer, link);
> + if (buf) {
> + chv3_fb_start_frame(fb, buf);
> + if (!list_is_last(&buf->link, &fb->bufs))
> + chv3_fb_next_frame(fb, list_next_entry(buf, link));
> + }
> + spin_unlock_irqrestore(&fb->bufs_lock, flags);
> +
> + return 0;
> +}
> +
> +static void chv3_fb_stop_streaming(struct vb2_queue *q)
> +{
> + struct chv3_fb *fb = vb2_get_drv_priv(q);
> + struct chv3_fb_buffer *buf;
> + unsigned long flags;
> +
> + writel(0, fb->iobase + FB_EN);
> +
> + spin_lock_irqsave(&fb->bufs_lock, flags);
> + list_for_each_entry(buf, &fb->bufs, link)
> + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
> + INIT_LIST_HEAD(&fb->bufs);
> + spin_unlock_irqrestore(&fb->bufs_lock, flags);
> +}
> +
> +static void chv3_fb_buf_queue(struct vb2_buffer *vb)
> +{
> + struct chv3_fb *fb = vb2_get_drv_priv(vb->vb2_queue);
> + struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> + struct chv3_fb_buffer *buf = container_of(v4l2_buf, struct chv3_fb_buffer, vb);
> + bool first, second;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&fb->bufs_lock, flags);
> + first = list_empty(&fb->bufs);
> + second = list_is_singular(&fb->bufs);
> + list_add_tail(&buf->link, &fb->bufs);
> + if (vb2_is_streaming(vb->vb2_queue)) {
> + if (first)
> + chv3_fb_start_frame(fb, buf);
> + else if (second)
> + chv3_fb_next_frame(fb, buf);
> + }
> + spin_unlock_irqrestore(&fb->bufs_lock, flags);
> +}
> +
> +static const struct vb2_ops chv3_fb_vb2_ops = {
> + .queue_setup = chv3_fb_queue_setup,
> + .wait_prepare = vb2_ops_wait_prepare,
> + .wait_finish = vb2_ops_wait_finish,
> + .start_streaming = chv3_fb_start_streaming,
> + .stop_streaming = chv3_fb_stop_streaming,
> + .buf_queue = chv3_fb_buf_queue,
> +};
> +
> +static int chv3_fb_open(struct file *file)
> +{
> + struct chv3_fb *fb = video_drvdata(file);
> + int res;
> +
> + mutex_lock(&fb->fb_lock);
> + res = v4l2_fh_open(file);
> + if (!res) {
> + if (v4l2_fh_is_singular_file(file))
> + chv3_fb_apply_dv_timings(fb);
> + }
> + mutex_unlock(&fb->fb_lock);
> +
> + return res;
> +}
> +
> +static const struct v4l2_file_operations chv3_fb_v4l2_fops = {
> + .owner = THIS_MODULE,
> + .open = chv3_fb_open,
> + .release = vb2_fop_release,
> + .unlocked_ioctl = video_ioctl2,
> + .mmap = vb2_fop_mmap,
> + .poll = vb2_fop_poll,
> +};
> +
> +static void chv3_fb_frame_irq(struct chv3_fb *fb)
> +{
> + struct chv3_fb_buffer *buf;
> +
> + spin_lock(&fb->bufs_lock);
> +
> + buf = list_first_entry_or_null(&fb->bufs, struct chv3_fb_buffer, link);
> + if (!buf)
> + goto empty;
> + list_del(&buf->link);
> +
> + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, fb->pix_fmt.sizeimage);
> + buf->vb.vb2_buf.timestamp = ktime_get_ns();
> + buf->vb.sequence = fb->sequence++;
> + buf->vb.field = V4L2_FIELD_NONE;
> + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
> +
> + buf = list_first_entry_or_null(&fb->bufs, struct chv3_fb_buffer, link);
> + if (buf) {
> + fb->writing_to_a = !fb->writing_to_a;
> + if (!list_is_last(&buf->link, &fb->bufs))
> + chv3_fb_next_frame(fb, list_next_entry(buf, link));
> + } else {
> + writel(0, fb->iobase + FB_EN);
> + }
> +empty:
> + spin_unlock(&fb->bufs_lock);
> +}
> +
> +static void chv3_fb_error_irq(struct chv3_fb *fb)
> +{
> + if (vb2_is_streaming(&fb->queue))
> + vb2_queue_error(&fb->queue);
> +}
> +
> +static void chv3_fb_resolution_irq(struct chv3_fb *fb)
> +{
> + static const struct v4l2_event event = {
> + .type = V4L2_EVENT_SOURCE_CHANGE,
> + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION,
> + };
> +
> + v4l2_event_queue(&fb->vdev, &event);
> + chv3_fb_error_irq(fb);
> +}
> +
> +static irqreturn_t chv3_fb_isr(int irq, void *data)
> +{
> + struct chv3_fb *fb = data;
> + unsigned int reg;
> +
> + reg = readl(fb->iobase_irq + FB_IRQ_CLR);
> + if (!reg)
> + return IRQ_NONE;
> +
> + if (reg & FB_IRQ_BUFF0)
> + chv3_fb_frame_irq(fb);
> + if (reg & FB_IRQ_BUFF1)
> + chv3_fb_frame_irq(fb);
> + if (reg & FB_IRQ_RESOLUTION)
> + chv3_fb_resolution_irq(fb);
> + if (reg & FB_IRQ_ERROR) {
> + dev_warn(fb->dev, "framebuffer error: 0x%x\n",
> + readl(fb->iobase + FB_ERRORSTATUS));
> + chv3_fb_error_irq(fb);
> + }
> +
> + writel(reg, fb->iobase_irq + FB_IRQ_CLR);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int chv3_fb_check_version(struct chv3_fb *fb)
> +{
> + u32 version;
> +
> + version = readl(fb->iobase + FB_VERSION);
> + if (version != FB_VERSION_CURRENT) {
> + dev_err(fb->dev,
> + "wrong framebuffer version: expected %x, got %x\n",
> + FB_VERSION_CURRENT, version);
> + return -ENODEV;
> + }
> + return 0;
> +}
> +
> +static void chv3_fb_set_default_format(struct chv3_fb *fb, bool legacy_format)
> +{
> + struct v4l2_pix_format *pix = &fb->pix_fmt;
> +
> + if (legacy_format)
> + pix->pixelformat = V4L2_PIX_FMT_BGRX32;
> + else
> + pix->pixelformat = V4L2_PIX_FMT_RGB24;
> + pix->field = V4L2_FIELD_NONE;
> + pix->colorspace = V4L2_COLORSPACE_SRGB;
> +
> + chv3_fb_set_format_resolution(fb, DEFAULT_WIDTH, DEFAULT_HEIGHT);
> +}
> +
> +static void chv3_fb_set_default_timings(struct chv3_fb *fb)
> +{
> + memset(&fb->timings, 0, sizeof(fb->timings));
> + fb->timings.type = V4L2_DV_BT_656_1120;
> + fb->timings.bt.width = DEFAULT_WIDTH;
> + fb->timings.bt.height = DEFAULT_HEIGHT;

Wouldn't it be better to say: fb->timings = V4L2_DV_BT_CEA_1920X1080P60;

> +}
> +
> +#define notifier_to_fb(nf) container_of(nf, struct chv3_fb, notifier)
> +
> +static int chv3_fb_async_notify_bound(struct v4l2_async_notifier *notifier,
> + struct v4l2_subdev *subdev,
> + struct v4l2_async_connection *asc)
> +{
> + struct chv3_fb *fb = notifier_to_fb(notifier);
> + int pad;
> +
> + pad = media_entity_get_fwnode_pad(&subdev->entity, asc->match.fwnode,
> + MEDIA_PAD_FL_SOURCE);
> + if (pad < 0)
> + return pad;
> +
> + fb->subdev = subdev;
> + fb->subdev_source_pad = pad;
> +
> + return 0;
> +}
> +
> +static void chv3_fb_async_notify_unbind(struct v4l2_async_notifier *notifier,
> + struct v4l2_subdev *subdev,
> + struct v4l2_async_connection *asc)
> +{
> + struct chv3_fb *fb = notifier_to_fb(notifier);
> +
> + video_unregister_device(&fb->vdev);

Use vb2_video_unregister_device. See documentation in include/media/videobuf2-v4l2.h.

> +}
> +
> +static int chv3_fb_async_notify_complete(struct v4l2_async_notifier *notifier)
> +{
> + struct chv3_fb *fb = notifier_to_fb(notifier);
> +
> + return video_register_device(&fb->vdev, VFL_TYPE_VIDEO, -1);
> +}
> +
> +static const struct v4l2_async_notifier_operations chv3_fb_async_notify_ops = {
> + .bound = chv3_fb_async_notify_bound,
> + .unbind = chv3_fb_async_notify_unbind,
> + .complete = chv3_fb_async_notify_complete,
> +};
> +
> +static int chv3_fb_fallback_init(struct chv3_fb *fb)
> +{
> + fb->subdev = NULL;
> + fb->subdev_source_pad = 0;
> +
> + return video_register_device(&fb->vdev, VFL_TYPE_VIDEO, -1);
> +}
> +
> +static int chv3_fb_fwnode_init(struct chv3_fb *fb)
> +{
> + struct v4l2_async_connection *asc;
> + struct fwnode_handle *endpoint;
> + int res;
> +
> + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(fb->dev), NULL);
> + if (!endpoint)
> + return -EINVAL;
> +
> + v4l2_async_nf_init(&fb->notifier, &fb->v4l2_dev);
> +
> + asc = v4l2_async_nf_add_fwnode_remote(&fb->notifier, endpoint,
> + struct v4l2_async_connection);
> + fwnode_handle_put(endpoint);
> +
> + if (IS_ERR(asc))
> + return PTR_ERR(asc);
> +
> + fb->notifier.ops = &chv3_fb_async_notify_ops;
> + res = v4l2_async_nf_register(&fb->notifier);
> + if (res) {
> + v4l2_async_nf_cleanup(&fb->notifier);
> + return res;
> + }
> +
> + return 0;
> +}
> +
> +static int chv3_fb_probe(struct platform_device *pdev)
> +{
> + struct chv3_fb *fb;
> + bool legacy_format;
> + int res;
> + int irq;
> +
> + fb = devm_kzalloc(&pdev->dev, sizeof(*fb), GFP_KERNEL);
> + if (!fb)
> + return -ENOMEM;
> + fb->dev = &pdev->dev;
> + platform_set_drvdata(pdev, fb);
> +
> + /* map register space */
> + fb->iobase = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(fb->iobase))
> + return PTR_ERR(fb->iobase);
> +
> + fb->iobase_irq = devm_platform_ioremap_resource(pdev, 1);
> + if (IS_ERR(fb->iobase_irq))
> + return PTR_ERR(fb->iobase_irq);
> +
> + /* check hw version */
> + res = chv3_fb_check_version(fb);
> + if (res)
> + return res;
> +
> + /* setup interrupts */
> + irq = platform_get_irq(pdev, 0);
> + if (irq < 0)
> + return -ENXIO;
> + res = devm_request_irq(&pdev->dev, irq, chv3_fb_isr, 0, DEVICE_NAME, fb);
> + if (res)
> + return res;
> +
> + /* initialize v4l2_device */
> + res = v4l2_device_register(&pdev->dev, &fb->v4l2_dev);
> + if (res)
> + return res;
> +
> + /* initialize vb2 queue */
> + fb->queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
> + fb->queue.io_modes = VB2_MMAP | VB2_DMABUF;
> + fb->queue.dev = &pdev->dev;
> + fb->queue.lock = &fb->fb_lock;
> + fb->queue.ops = &chv3_fb_vb2_ops;
> + fb->queue.mem_ops = &vb2_dma_contig_memops;
> + fb->queue.drv_priv = fb;
> + fb->queue.buf_struct_size = sizeof(struct chv3_fb_buffer);
> + fb->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> + res = vb2_queue_init(&fb->queue);
> + if (res)
> + goto error;
> +
> + /* initialize video_device */
> + strscpy(fb->vdev.name, DEVICE_NAME, sizeof(fb->vdev.name));
> + fb->vdev.fops = &chv3_fb_v4l2_fops;
> + fb->vdev.ioctl_ops = &chv3_fb_v4l2_ioctl_ops;
> + fb->vdev.lock = &fb->fb_lock;
> + fb->vdev.release = video_device_release_empty;
> + fb->vdev.device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
> + fb->vdev.v4l2_dev = &fb->v4l2_dev;
> + fb->vdev.queue = &fb->queue;
> + video_set_drvdata(&fb->vdev, fb);
> +
> + /* read other DT properties */
> + legacy_format = device_property_read_bool(&pdev->dev, "google,legacy-format");
> +
> + if (device_get_named_child_node(&pdev->dev, "port"))
> + res = chv3_fb_fwnode_init(fb);
> + else
> + res = chv3_fb_fallback_init(fb);
> + if (res)
> + goto error;
> +
> + /* initialize rest of driver struct */
> + INIT_LIST_HEAD(&fb->bufs);
> + spin_lock_init(&fb->bufs_lock);
> + mutex_init(&fb->fb_lock);
> +
> + chv3_fb_set_default_format(fb, legacy_format);
> + chv3_fb_set_default_timings(fb);
> +
> + /* initialize hw */
> + writel(FB_RESET_BIT, fb->iobase + FB_RESET);
> + writel(FB_DATARATE_DOUBLE, fb->iobase + FB_DATARATE);
> + writel(FB_PIXELMODE_DOUBLE, fb->iobase + FB_PIXELMODE);
> + if (legacy_format)
> + writel(FB_DMAFORMAT_8BPC_LEGACY, fb->iobase + FB_DMAFORMAT);
> + else
> + writel(FB_DMAFORMAT_8BPC, fb->iobase + FB_DMAFORMAT);
> +
> + writel(FB_IRQ_ALL, fb->iobase_irq + FB_IRQ_MASK);
> +
> + return 0;
> +
> +error:
> + v4l2_device_unregister(&fb->v4l2_dev);
> +
> + return res;
> +}
> +
> +static void chv3_fb_remove(struct platform_device *pdev)
> +{
> + struct chv3_fb *fb = platform_get_drvdata(pdev);
> +
> + /* disable interrupts */
> + writel(0, fb->iobase_irq + FB_IRQ_MASK);
> +
> + v4l2_async_nf_unregister(&fb->notifier);
> + v4l2_async_nf_cleanup(&fb->notifier);
> + v4l2_device_unregister(&fb->v4l2_dev);
> +}
> +
> +static const struct of_device_id chv3_fb_match_table[] = {
> + { .compatible = "google,chv3-fb" },
> + { },
> +};
> +
> +static struct platform_driver chv3_fb_platform_driver = {
> + .probe = chv3_fb_probe,
> + .remove_new = chv3_fb_remove,
> + .driver = {
> + .name = DEVICE_NAME,
> + .of_match_table = chv3_fb_match_table,
> + },
> +};
> +
> +module_platform_driver(chv3_fb_platform_driver);
> +
> +MODULE_AUTHOR("Paweł Anikiel <[email protected]>");
> +MODULE_DESCRIPTION("Google Chameleon v3 video framebuffer driver");
> +MODULE_LICENSE("GPL");

For the v3, can you also provide the output of 'v4l2-compliance -s'
in the cover letter? Make sure you compile v4l2-compliance directly
from the git repo https://git.linuxtv.org/v4l-utils.git/ since the version
from distros is usually too old.

Regards,

Hans

2024-02-28 11:26:00

by Hans Verkuil

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

Hi Paweł,

On 21/02/2024 17:02, Paweł Anikiel wrote:
> 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.

So now we have two query_dv_timings ops: one for video ops, and one
for pad ops. That's not very maintainable. I would suggest switching
all current users of the video op over to the pad op.

Regards,

Hans

>
> 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,


2024-02-28 12:13:31

by Hans Verkuil

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

On 21/02/2024 17:02, Paweł Anikiel wrote:
> 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 | 2176 +++++++++++++++++++++
> 3 files changed, 2189 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..d0c60e29e51d
> --- /dev/null
> +++ b/drivers/media/platform/intel/intel-dprx.c
> @@ -0,0 +1,2176 @@
> +// 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
> +
> +/* 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
> +
> +/* 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;
> +
> + struct v4l2_subdev subdev;
> + struct media_pad pads[4];
> +
> + struct dprx_sink sinks[4];
> +
> + int max_link_rate;
> + int max_lane_count;
> + bool multi_stream_support;
> + int max_stream_count;
> +
> + u8 caps[16];
> + 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->max_stream_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->max_stream_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->max_stream_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->max_stream_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 void dprx_init_caps(struct dprx *dprx)
> +{
> + memset(dprx->caps, 0, sizeof(dprx->caps));
> + dprx->caps[DP_DPCD_REV] = DP_DPCD_REV_14;
> + dprx->caps[DP_MAX_LINK_RATE] = dprx->max_link_rate;
> + dprx->caps[DP_MAX_LANE_COUNT] = DP_ENHANCED_FRAME_CAP | DP_TPS3_SUPPORTED |
> + dprx->max_lane_count;
> + dprx->caps[DP_MAX_DOWNSPREAD] = DP_TPS4_SUPPORTED | DP_MAX_DOWNSPREAD_0_5;
> + dprx->caps[DP_MAIN_LINK_CHANNEL_CODING] = DP_CAP_ANSI_8B10B;
> + dprx->caps[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->multi_stream_support;
> +}
> +
> +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;
> +
> + if (lane_count > dprx->max_lane_count)
> + 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->max_stream_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;
> +
> + 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);
> + }
> +
> + 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->max_stream_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->max_stream_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;

The HPD needs to be pulled low before you start updating the EDID.

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

The threshold for a short HPD pulse is <= 2ms. An EDID change requires a long
HPD pulse, so I would start with a lower limit in usleep_range of 4000.

Unfortunately I was not able to find a recommended length for a long HPD
pulse other than that 2 ms threshold. HDMI uses 100 ms, but that's probably
overkill for DisplayPort.

> + 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->max_stream_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,

Same comment as for the main driver: minimum values of 0 really make no sense.

> + .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->max_stream_count; i++)
> + dprx->pads[i].flags = MEDIA_PAD_FL_SOURCE;
> +
> + return media_entity_pads_init(&dprx->subdev.entity, dprx->max_stream_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);
> +
> + 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;
> +
> + res = device_property_read_u32(&pdev->dev, "intel,max-link-rate", &dprx->max_link_rate);
> + if (res)
> + return res;
> +
> + res = device_property_read_u32(&pdev->dev, "intel,max-lane-count", &dprx->max_lane_count);
> + if (res)
> + return res;
> +
> + dprx->multi_stream_support = device_property_read_bool(&pdev->dev,
> + "intel,multi-stream-support");
> +
> + if (dprx->multi_stream_support) {
> + res = device_property_read_u32(&pdev->dev, "intel,max-stream-count",
> + &dprx->max_stream_count);
> + if (res)
> + return res;
> + } else {
> + dprx->max_stream_count = 1;
> + }
> +
> + dprx_init_caps(dprx);
> +
> + 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;

I would expect this to be a MEDIA_ENT_F_DV_DECODER.

> + 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);
> +
> + return 0;
> +}
> +
> +static void dprx_remove(struct platform_device *pdev)
> +{
> + struct dprx *dprx = platform_get_drvdata(pdev);
> +
> + /* disable interrupts */
> + dprx_set_irq(dprx, 0);
> +
> + v4l2_async_unregister_subdev(&dprx->subdev);
> +}
> +
> +static const struct of_device_id dprx_match_table[] = {
> + { .compatible = "intel,dprx-20.0.1" },
> + { },
> +};
> +
> +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");

Regards,

Hans


2024-02-28 12:18:45

by Krzysztof Kozlowski

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

On 28/02/2024 12:05, Paweł Anikiel wrote:
> On Tue, Feb 27, 2024 at 3:29 PM Rob Herring <[email protected]> wrote:
>>
>> On Mon, Feb 26, 2024 at 11:59:42AM +0100, Paweł Anikiel wrote:
>>> On Mon, Feb 26, 2024 at 10:13 AM Krzysztof Kozlowski
>>> <[email protected]> wrote:
>>>>
>>>> On 21/02/2024 17:02, 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 | 160 ++++++++++++++++++
>>>>> 1 file changed, 160 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..31025f2d5dcd
>>>>> --- /dev/null
>>>>> +++ b/Documentation/devicetree/bindings/media/intel,dprx.yaml
>>>>> @@ -0,0 +1,160 @@
>>>>> +# 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]>
>>>>> +
>>>>> +description: |
>>>>> + 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 IP features a large number of configuration parameters, found at:
>>>>> + https://www.intel.com/content/www/us/en/docs/programmable/683273/23-3-20-0-1/sink-parameters.html
>>>>> +
>>>>> + The following parameters have to be enabled:
>>>>> + - Support DisplayPort sink
>>>>> + - Enable GPU control
>>>>> + The following parameters' values have to be set in the devicetree:
>>>>> + - RX maximum link rate
>>>>> + - Maximum lane count
>>>>> + - Support MST
>>>>> + - Max stream count (only if Support MST is enabled)
>>>>> +
>>>>> +properties:
>>>>> + compatible:
>>>>> + const: intel,dprx-20.0.1
>>>>> +
>>>>> + reg:
>>>>> + maxItems: 1
>>>>> +
>>>>> + interrupts:
>>>>> + maxItems: 1
>>>>> +
>>>>> + intel,max-link-rate:
>>>>> + $ref: /schemas/types.yaml#/definitions/uint32
>>>>> + description: Max link rate configuration parameter
>>>>
>>>> Please do not duplicate property name in description. It's useless.
>>>> Instead explain what is this responsible for.
>>>>
>>>> Why max-link-rate would differ for the same dprx-20.0.1? And why
>>>> standard properties cannot be used?
>>>>
>>>> Same for all questions below.
>>>
>>> These four properties are the IP configuration parameters mentioned in
>>> the device description. When generating the IP core you can set these
>>> parameters, which could make them differ for the same dprx-20.0.1.
>>> They are documented in the user guide, for which I also put a link in
>>> the description. Is that enough? Or should I also document these
>>> parameters here?
>>
>> Use the standard properties: link-frequencies and data-lanes. Those go
>> under the port(s) because they are inheritly per logical link.
>
> The DP receiver has one input interface (a deserialized DP stream),
> and up to four output interfaces (the decoded video streams). The "max
> link rate" and "max lane count" parameters only describe the input
> interface to the receiver. However, the port(s) I am using here are
> for the output streams. They are not affected by those parameters, so
> I don't think these properties should go under the output port(s).
>
> The receiver doesn't have an input port in the DT, because there isn't
> any controllable entity on the other side - the deserializer doesn't
> have any software interface. Since these standard properties
> (link-frequencies and data-lanes) are only defined in
> video-interfaces.yaml (which IIUC describes a graph endpoint), I can't
> use them directly in the device node.

DT describes the hardware, so where does the input come? From something,
right? Regardless if you have a driver or not. There is dp-connector
binding, if this is physical port.

>
> Do you see a way to use these standard properties here?

Best regards,
Krzysztof


2024-02-28 13:10:08

by Paweł Anikiel

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

On Wed, Feb 28, 2024 at 1:18 PM Krzysztof Kozlowski
<[email protected]> wrote:
>
> On 28/02/2024 12:05, Paweł Anikiel wrote:
> > On Tue, Feb 27, 2024 at 3:29 PM Rob Herring <[email protected]> wrote:
> >>
> >> On Mon, Feb 26, 2024 at 11:59:42AM +0100, Paweł Anikiel wrote:
> >>> On Mon, Feb 26, 2024 at 10:13 AM Krzysztof Kozlowski
> >>> <[email protected]> wrote:
> >>>>
> >>>> On 21/02/2024 17:02, 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 | 160 ++++++++++++++++++
> >>>>> 1 file changed, 160 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..31025f2d5dcd
> >>>>> --- /dev/null
> >>>>> +++ b/Documentation/devicetree/bindings/media/intel,dprx.yaml
> >>>>> @@ -0,0 +1,160 @@
> >>>>> +# 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]>
> >>>>> +
> >>>>> +description: |
> >>>>> + 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 IP features a large number of configuration parameters, found at:
> >>>>> + https://www.intel.com/content/www/us/en/docs/programmable/683273/23-3-20-0-1/sink-parameters.html
> >>>>> +
> >>>>> + The following parameters have to be enabled:
> >>>>> + - Support DisplayPort sink
> >>>>> + - Enable GPU control
> >>>>> + The following parameters' values have to be set in the devicetree:
> >>>>> + - RX maximum link rate
> >>>>> + - Maximum lane count
> >>>>> + - Support MST
> >>>>> + - Max stream count (only if Support MST is enabled)
> >>>>> +
> >>>>> +properties:
> >>>>> + compatible:
> >>>>> + const: intel,dprx-20.0.1
> >>>>> +
> >>>>> + reg:
> >>>>> + maxItems: 1
> >>>>> +
> >>>>> + interrupts:
> >>>>> + maxItems: 1
> >>>>> +
> >>>>> + intel,max-link-rate:
> >>>>> + $ref: /schemas/types.yaml#/definitions/uint32
> >>>>> + description: Max link rate configuration parameter
> >>>>
> >>>> Please do not duplicate property name in description. It's useless.
> >>>> Instead explain what is this responsible for.
> >>>>
> >>>> Why max-link-rate would differ for the same dprx-20.0.1? And why
> >>>> standard properties cannot be used?
> >>>>
> >>>> Same for all questions below.
> >>>
> >>> These four properties are the IP configuration parameters mentioned in
> >>> the device description. When generating the IP core you can set these
> >>> parameters, which could make them differ for the same dprx-20.0.1.
> >>> They are documented in the user guide, for which I also put a link in
> >>> the description. Is that enough? Or should I also document these
> >>> parameters here?
> >>
> >> Use the standard properties: link-frequencies and data-lanes. Those go
> >> under the port(s) because they are inheritly per logical link.
> >
> > The DP receiver has one input interface (a deserialized DP stream),
> > and up to four output interfaces (the decoded video streams). The "max
> > link rate" and "max lane count" parameters only describe the input
> > interface to the receiver. However, the port(s) I am using here are
> > for the output streams. They are not affected by those parameters, so
> > I don't think these properties should go under the output port(s).
> >
> > The receiver doesn't have an input port in the DT, because there isn't
> > any controllable entity on the other side - the deserializer doesn't
> > have any software interface. Since these standard properties
> > (link-frequencies and data-lanes) are only defined in
> > video-interfaces.yaml (which IIUC describes a graph endpoint), I can't
> > use them directly in the device node.
>
> DT describes the hardware, so where does the input come? From something,
> right? Regardless if you have a driver or not. There is dp-connector
> binding, if this is physical port.

Yes, it is a physical port. I agree adding a DT node for the physical
DP input connector would let us add link-frequencies to the input port
of the receiver.

However, dp-connector seems to be a binding for an output port - it's
under schemas/display/connector, and DP_PWR can be a power supply only
for an output port (looking at the dp-pwr-supply property). Also, the
driver for this binding is a DRM bridge driver (display-connector.c)
which would not be compatible with a v4l2 (sub)device.

2024-02-28 15:09:23

by Paweł Anikiel

[permalink] [raw]
Subject: Re: [PATCH v2 2/9] media: Add Chameleon v3 framebuffer driver

Hi Hans, thanks for the review!

On Wed, Feb 28, 2024 at 12:24 PM Hans Verkuil <[email protected]> wrote:
>
> Hi Paweł,
>
> On 21/02/2024 17:02, Paweł Anikiel wrote:
> > Add v4l2 driver for the Google Chameleon v3 framebuffer device.
>
> This is just a video capture device, right? A framebuffer device is something
> that lives in drivers/video/fbdev.

Yes, it is just a capture device.

>
> It is *very* confusing to see the term 'framebuffer' used in a video
> capture context.

I agree the name is confusing. I think it started out as something
else and unfortunately stuck around. I think it's possible to change
it, though.

>
> This commit log should also give a better description of the hardware.
> Just a single one-liner is a bit on the short side :-)

Would it be fine to just put the Kconfig help text there?

>
> >
> > Signed-off-by: Paweł Anikiel <[email protected]>
> > ---
> > 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 | 895 ++++++++++++++++++
>
> chv3-video.c would be a much better name for chv3-fb.c.
>
> That's a commonly used filename for video capture drivers.

I'm guessing all the instances of fb or framebuffer in the driver
itself should be changed as well in that case?

>
> > 7 files changed, 918 insertions(+)
> > create mode 100644 drivers/media/platform/google/Kconfig
> > create mode 100644 drivers/media/platform/google/Makefile
> > create mode 100644 drivers/media/platform/google/chameleonv3/Kconfig
> > create mode 100644 drivers/media/platform/google/chameleonv3/Makefile
> > create mode 100644 drivers/media/platform/google/chameleonv3/chv3-fb.c
> >
> > diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
> > index 91e54215de3a..b82f7b142b85 100644
> > --- a/drivers/media/platform/Kconfig
> > +++ b/drivers/media/platform/Kconfig
> > @@ -69,6 +69,7 @@ source "drivers/media/platform/aspeed/Kconfig"
> > source "drivers/media/platform/atmel/Kconfig"
> > source "drivers/media/platform/cadence/Kconfig"
> > source "drivers/media/platform/chips-media/Kconfig"
> > +source "drivers/media/platform/google/Kconfig"
> > source "drivers/media/platform/intel/Kconfig"
> > source "drivers/media/platform/marvell/Kconfig"
> > source "drivers/media/platform/mediatek/Kconfig"
> > diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
> > index 3296ec1ebe16..f7067eb05f76 100644
> > --- a/drivers/media/platform/Makefile
> > +++ b/drivers/media/platform/Makefile
> > @@ -12,6 +12,7 @@ obj-y += aspeed/
> > obj-y += atmel/
> > obj-y += cadence/
> > obj-y += chips-media/
> > +obj-y += google/
> > obj-y += intel/
> > obj-y += marvell/
> > obj-y += mediatek/
> > diff --git a/drivers/media/platform/google/Kconfig b/drivers/media/platform/google/Kconfig
> > new file mode 100644
> > index 000000000000..2a5f01034c11
> > --- /dev/null
> > +++ b/drivers/media/platform/google/Kconfig
> > @@ -0,0 +1,3 @@
> > +# SPDX-License-Identifier: GPL-2.0-only
> > +
> > +source "drivers/media/platform/google/chameleonv3/Kconfig"
> > diff --git a/drivers/media/platform/google/Makefile b/drivers/media/platform/google/Makefile
> > new file mode 100644
> > index 000000000000..c971a09faeb4
> > --- /dev/null
> > +++ b/drivers/media/platform/google/Makefile
> > @@ -0,0 +1,2 @@
> > +# SPDX-License-Identifier: GPL-2.0-only
> > +obj-y += chameleonv3/
> > diff --git a/drivers/media/platform/google/chameleonv3/Kconfig b/drivers/media/platform/google/chameleonv3/Kconfig
> > new file mode 100644
> > index 000000000000..76d0383a8589
> > --- /dev/null
> > +++ b/drivers/media/platform/google/chameleonv3/Kconfig
> > @@ -0,0 +1,13 @@
> > +# SPDX-License-Identifier: GPL-2.0-only
> > +
> > +config VIDEO_CHV3_FB
> > + tristate "Google Chameleon v3 framebuffer video driver"
> > + depends on V4L_PLATFORM_DRIVERS
> > + depends on VIDEO_DEV
> > + select VIDEOBUF2_DMA_CONTIG
> > + select V4L2_FWNODE
> > + help
> > + v4l2 driver for the video interface present on the Google
> > + Chameleon v3. The Chameleon v3 uses the framebuffer IP core
> > + to take the video signal from different sources and directly
> > + write frames into memory.
>
> So it is composing different video streams into buffers? Or does it
> capture from a single source at a time? The text is rather ambiguous.

You're right, I'll write a more precise description.

>
> > diff --git a/drivers/media/platform/google/chameleonv3/Makefile b/drivers/media/platform/google/chameleonv3/Makefile
> > new file mode 100644
> > index 000000000000..d63727148688
> > --- /dev/null
> > +++ b/drivers/media/platform/google/chameleonv3/Makefile
> > @@ -0,0 +1,3 @@
> > +# SPDX-License-Identifier: GPL-2.0-only
> > +
> > +obj-$(CONFIG_VIDEO_CHV3_FB) += chv3-fb.o
> > diff --git a/drivers/media/platform/google/chameleonv3/chv3-fb.c b/drivers/media/platform/google/chameleonv3/chv3-fb.c
> > new file mode 100644
> > index 000000000000..35a44365eba0
> > --- /dev/null
> > +++ b/drivers/media/platform/google/chameleonv3/chv3-fb.c
> > @@ -0,0 +1,895 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright 2023-2024 Google LLC.
> > + * Author: Paweł Anikiel <[email protected]>
> > + */
> > +
> > +#include <linux/delay.h>
> > +#include <linux/dma-mapping.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/of.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/videodev2.h>
> > +#include <media/v4l2-ctrls.h>
> > +#include <media/v4l2-device.h>
> > +#include <media/v4l2-dv-timings.h>
> > +#include <media/v4l2-event.h>
> > +#include <media/v4l2-fwnode.h>
> > +#include <media/v4l2-ioctl.h>
> > +#include <media/videobuf2-dma-contig.h>
> > +
> > +#define DEVICE_NAME "chv3-fb"
> > +
> > +/*
> > + * The device is expected to report some format even if there's currently no
> > + * active video stream. In such case we default to 1080p.
> > + */
> > +#define DEFAULT_WIDTH 1920
> > +#define DEFAULT_HEIGHT 1080
> > +
> > +#define FB_EN 0x00
> > +#define FB_EN_BIT BIT(0)
> > +#define FB_HEIGHT 0x04
> > +#define FB_WIDTH 0x08
> > +#define FB_BUFFERA 0x0c
> > +#define FB_BUFFERB 0x10
> > +#define FB_BUFFERSIZE 0x14
> > +#define FB_RESET 0x18
> > +#define FB_RESET_BIT BIT(0)
> > +#define FB_ERRORSTATUS 0x1c
> > +#define FB_IOCOLOR 0x20
> > +#define FB_DATARATE 0x24
> > +#define FB_DATARATE_SINGLE 0x0
> > +#define FB_DATARATE_DOUBLE 0x1
> > +#define FB_PIXELMODE 0x28
> > +#define FB_PIXELMODE_SINGLE 0x0
> > +#define FB_PIXELMODE_DOUBLE 0x1
> > +#define FB_SYNCPOLARITY 0x2c
> > +#define FB_DMAFORMAT 0x30
> > +#define FB_DMAFORMAT_8BPC 0x0
> > +#define FB_DMAFORMAT_10BPC_UPPER 0x1
> > +#define FB_DMAFORMAT_10BPC_LOWER 0x2
> > +#define FB_DMAFORMAT_12BPC_UPPER 0x3
> > +#define FB_DMAFORMAT_12BPC_LOWER 0x4
> > +#define FB_DMAFORMAT_16BPC 0x5
> > +#define FB_DMAFORMAT_RAW 0x6
> > +#define FB_DMAFORMAT_8BPC_LEGACY 0x7
> > +#define FB_VERSION 0x34
> > +#define FB_VERSION_CURRENT 0xc0fb0001
> > +
> > +#define FB_IRQ_MASK 0x8
> > +#define FB_IRQ_CLR 0xc
> > +#define FB_IRQ_ALL 0xf
> > +#define FB_IRQ_BUFF0 BIT(0)
> > +#define FB_IRQ_BUFF1 BIT(1)
> > +#define FB_IRQ_RESOLUTION BIT(2)
> > +#define FB_IRQ_ERROR BIT(3)
> > +
> > +struct chv3_fb {
> > + struct device *dev;
> > + void __iomem *iobase;
> > + void __iomem *iobase_irq;
> > +
> > + struct v4l2_device v4l2_dev;
> > + struct vb2_queue queue;
> > + struct video_device vdev;
> > + struct v4l2_pix_format pix_fmt;
> > + struct v4l2_dv_timings timings;
> > +
> > + struct v4l2_async_notifier notifier;
> > + struct v4l2_subdev *subdev;
> > + int subdev_source_pad;
> > +
> > + u32 sequence;
> > + bool writing_to_a;
> > +
> > + struct list_head bufs;
> > + spinlock_t bufs_lock;
> > +
> > + struct mutex fb_lock;
> > +};
> > +
> > +struct chv3_fb_buffer {
> > + struct vb2_v4l2_buffer vb;
> > + struct list_head link;
> > +};
> > +
> > +static void chv3_fb_set_format_resolution(struct chv3_fb *fb, u32 width, u32 height)
> > +{
> > + u32 bytes_per_pixel;
> > +
> > + if (fb->pix_fmt.pixelformat == V4L2_PIX_FMT_RGB24)
> > + bytes_per_pixel = 3;
> > + else
> > + bytes_per_pixel = 4;
> > +
> > + fb->pix_fmt.width = width;
> > + fb->pix_fmt.height = height;
> > + fb->pix_fmt.bytesperline = width * bytes_per_pixel;
> > + fb->pix_fmt.sizeimage = fb->pix_fmt.bytesperline * height;
> > +}
> > +
> > +/*
> > + * The video interface has hardware counters which expose the width and
> > + * height of the current video stream. It can't reliably detect if the stream
> > + * is present or not, so this is only used as a fallback in the case where
> > + * we don't have access to the receiver hardware.
> > + */
> > +static int chv3_fb_query_dv_timings_fallback(struct chv3_fb *fb,
> > + struct v4l2_dv_timings *timings)
> > +{
> > + u32 width, height;
> > +
> > + width = readl(fb->iobase + FB_WIDTH);
> > + height = readl(fb->iobase + FB_HEIGHT);
> > + if (width == 0 || height == 0)
> > + return -ENOLINK;
> > +
> > + memset(timings, 0, sizeof(*timings));
> > + timings->type = V4L2_DV_BT_656_1120;
> > + timings->bt.width = width;
> > + timings->bt.height = height;
> > +
> > + return 0;
> > +}
> > +
> > +static int chv3_fb_query_dv_timings(struct chv3_fb *fb, struct v4l2_dv_timings *timings)
> > +{
> > + if (fb->subdev) {
> > + return v4l2_subdev_call(fb->subdev, pad, query_dv_timings,
> > + fb->subdev_source_pad, timings);
> > + } else {
> > + return chv3_fb_query_dv_timings_fallback(fb, timings);
> > + }
> > +}
> > +
> > +static const struct v4l2_dv_timings_cap chv3_fb_fallback_dv_timings_cap = {
> > + .type = V4L2_DV_BT_656_1120,
> > + .bt = {
> > + .min_width = 0,
>
> This is an unlikely minimum width (ditto for height below).
>
> > + .max_width = 65535,
>
> The max is suspect as well: I would expect it to be a multiple of 2/4/8/16.
>
> > + .min_height = 0,
> > + .max_height = 65535,
> > + .min_pixelclock = 0,
> > + .max_pixelclock = 2147483647,
>
> Ditto for these.

Note: these are used only when there is no controllable endpoint
attached to the video interface, and we don't know what the timing
caps are.

Should I then just pick something like 640x480p24 as min and 8Kp120 as max?

>
> > + .standards = V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT |
> > + V4L2_DV_BT_STD_CVT | V4L2_DV_BT_STD_GTF,
> > + .capabilities = V4L2_DV_BT_CAP_PROGRESSIVE |
> > + V4L2_DV_BT_CAP_REDUCED_BLANKING |
> > + V4L2_DV_BT_CAP_CUSTOM,
> > + },
> > +};
> > +
> > +static int chv3_fb_enum_dv_timings_fallback(struct chv3_fb *fb,
> > + struct v4l2_enum_dv_timings *timings)
> > +{
> > + return v4l2_enum_dv_timings_cap(timings, &chv3_fb_fallback_dv_timings_cap,
> > + NULL, NULL);
> > +}
> > +
> > +static int chv3_fb_dv_timings_cap_fallback(struct chv3_fb *fb,
> > + struct v4l2_dv_timings_cap *cap)
> > +{
> > + *cap = chv3_fb_fallback_dv_timings_cap;
> > +
> > + return 0;
> > +}
> > +
> > +static void chv3_fb_apply_dv_timings(struct chv3_fb *fb)
> > +{
> > + struct v4l2_dv_timings timings;
> > + int res;
> > +
> > + res = chv3_fb_query_dv_timings(fb, &timings);
> > + if (res)
> > + return;
> > +
> > + fb->timings = timings;
> > + chv3_fb_set_format_resolution(fb, timings.bt.width, timings.bt.height);
> > +}
> > +
> > +static int chv3_fb_querycap(struct file *file, void *fh,
> > + struct v4l2_capability *cap)
> > +{
> > + strscpy(cap->driver, DEVICE_NAME, sizeof(cap->driver));
> > + strscpy(cap->card, "Chameleon v3 video", sizeof(cap->card));
> > +
> > + return 0;
> > +}
> > +
> > +static int chv3_fb_g_fmt_vid_cap(struct file *file, void *fh,
> > + struct v4l2_format *fmt)
> > +{
> > + struct chv3_fb *fb = video_drvdata(file);
> > +
> > + fmt->fmt.pix = fb->pix_fmt;
> > +
> > + return 0;
> > +}
> > +
> > +static int chv3_fb_enum_fmt_vid_cap(struct file *file, void *fh,
> > + struct v4l2_fmtdesc *fmt)
> > +{
> > + struct chv3_fb *fb = video_drvdata(file);
> > +
> > + if (fmt->index != 0)
> > + return -EINVAL;
> > +
> > + fmt->flags = 0;
> > + fmt->pixelformat = fb->pix_fmt.pixelformat;
> > +
> > + return 0;
> > +}
> > +
> > +static int chv3_fb_enum_framesizes(struct file *file, void *fh,
> > + struct v4l2_frmsizeenum *frm)
> > +{
> > + struct chv3_fb *fb = video_drvdata(file);
> > +
> > + if (frm->index != 0)
> > + return -EINVAL;
> > +
> > + if (frm->pixel_format != fb->pix_fmt.pixelformat)
> > + return -EINVAL;
> > +
> > + frm->type = V4L2_FRMSIZE_TYPE_DISCRETE;
> > + frm->discrete.width = fb->pix_fmt.width;
> > + frm->discrete.height = fb->pix_fmt.height;
>
> This is not something you would expect to see supported for a
> video receiver input. This is something you use for camera sensors.
>
> This should almost certainly be dropped.

Thanks for explaining, I'll remove it in v3.

>
> > +
> > + return 0;
> > +}
> > +
> > +static int chv3_fb_g_input(struct file *file, void *fh, unsigned int *index)
> > +{
> > + *index = 0;
> > +
> > + return 0;
> > +}
> > +
> > +static int chv3_fb_s_input(struct file *file, void *fh, unsigned int index)
> > +{
> > + if (index != 0)
> > + return -EINVAL;
> > +
> > + return 0;
> > +}
> > +
> > +static int chv3_fb_enum_input(struct file *file, void *fh,
> > + struct v4l2_input *input)
> > +{
> > + if (input->index != 0)
> > + return -EINVAL;
> > +
> > + strscpy(input->name, "input0", sizeof(input->name));
> > + input->type = V4L2_INPUT_TYPE_CAMERA;
> > + input->capabilities = V4L2_IN_CAP_DV_TIMINGS;
> > +
> > + return 0;
> > +}
> > +
> > +static int chv3_fb_g_edid(struct file *file, void *fh,
> > + struct v4l2_edid *edid)
> > +{
> > + struct chv3_fb *fb = video_drvdata(file);
> > + int res;
> > +
> > + if (!fb->subdev)
> > + return -ENOTTY;
> > +
> > + if (edid->pad != 0)
> > + return -EINVAL;
> > +
> > + edid->pad = fb->subdev_source_pad;
> > + res = v4l2_subdev_call(fb->subdev, pad, get_edid, edid);
> > + edid->pad = 0;
> > +
> > + return res;
> > +}
> > +
> > +static int chv3_fb_s_edid(struct file *file, void *fh,
> > + struct v4l2_edid *edid)
> > +{
> > + struct chv3_fb *fb = video_drvdata(file);
> > + int res;
> > +
> > + if (!fb->subdev)
> > + return -ENOTTY;
> > +
> > + if (edid->pad != 0)
> > + return -EINVAL;
> > +
> > + edid->pad = fb->subdev_source_pad;
> > + res = v4l2_subdev_call(fb->subdev, pad, set_edid, edid);
> > + edid->pad = 0;
> > +
> > + return res;
> > +}
> > +
> > +static int chv3_fb_s_dv_timings(struct file *file, void *fh,
> > + struct v4l2_dv_timings *timings)
> > +{
> > + struct chv3_fb *fb = video_drvdata(file);
> > +
> > + if (timings->bt.width == fb->timings.bt.width &&
> > + timings->bt.height == fb->timings.bt.height)
>
> You would typically use v4l2_match_dv_timings() for this. The check above
> is insufficient since it does not even check for different framerates or
> interlaced vs progressive.

Okay, I changed it to use v4l2_match_dv_timings().

>
> > + return 0;
> > +
> > + if (vb2_is_busy(&fb->queue))
> > + return -EBUSY;
> > +
> > + if (!v4l2_valid_dv_timings(timings, &chv3_fb_fallback_dv_timings_cap, NULL, NULL))
> > + return -ERANGE;
> > +
> > + fb->timings = *timings;
> > + chv3_fb_set_format_resolution(fb, timings->bt.width, timings->bt.height);
> > +
> > + return 0;
> > +}
> > +
> > +static int chv3_fb_g_dv_timings(struct file *file, void *fh,
> > + struct v4l2_dv_timings *timings)
> > +{
> > + struct chv3_fb *fb = video_drvdata(file);
> > +
> > + *timings = fb->timings;
> > + return 0;
> > +}
> > +
> > +static int chv3_fb_vidioc_query_dv_timings(struct file *file, void *fh,
> > + struct v4l2_dv_timings *timings)
> > +{
> > + struct chv3_fb *fb = video_drvdata(file);
> > +
> > + return chv3_fb_query_dv_timings(fb, timings);
> > +}
> > +
> > +static int chv3_fb_enum_dv_timings(struct file *file, void *fh,
> > + struct v4l2_enum_dv_timings *timings)
> > +{
> > + struct chv3_fb *fb = video_drvdata(file);
> > + int res;
> > +
> > + if (timings->pad != 0)
> > + return -EINVAL;
> > +
> > + if (fb->subdev) {
> > + timings->pad = fb->subdev_source_pad;
> > + res = v4l2_subdev_call(fb->subdev, pad, enum_dv_timings, timings);
> > + timings->pad = 0;
> > + return res;
> > + } else {
> > + return chv3_fb_enum_dv_timings_fallback(fb, timings);
> > + }
> > +}
> > +
> > +static int chv3_fb_dv_timings_cap(struct file *file, void *fh,
> > + struct v4l2_dv_timings_cap *cap)
> > +{
> > + struct chv3_fb *fb = video_drvdata(file);
> > + int res;
> > +
> > + if (cap->pad != 0)
> > + return -EINVAL;
> > +
> > + if (fb->subdev) {
> > + cap->pad = fb->subdev_source_pad;
> > + res = v4l2_subdev_call(fb->subdev, pad, dv_timings_cap, cap);
> > + cap->pad = 0;
> > + return res;
> > + } else {
> > + return chv3_fb_dv_timings_cap_fallback(fb, cap);
> > + }
> > +}
> > +
> > +static int chv3_fb_subscribe_event(struct v4l2_fh *fh,
> > + const struct v4l2_event_subscription *sub)
> > +{
> > + switch (sub->type) {
> > + case V4L2_EVENT_SOURCE_CHANGE:
> > + return v4l2_src_change_event_subscribe(fh, sub);
> > + }
> > +
> > + return v4l2_ctrl_subscribe_event(fh, sub);
> > +}
>
> I am missing support for the V4L2_CID_DV_RX_POWER_PRESENT control:
>
> https://hverkuil.home.xs4all.nl/spec/userspace-api/v4l/ext-ctrls-dv.html

I'll try to implement it.

>
> > +
> > +static const struct v4l2_ioctl_ops chv3_fb_v4l2_ioctl_ops = {
> > + .vidioc_querycap = chv3_fb_querycap,
> > +
> > + .vidioc_enum_fmt_vid_cap = chv3_fb_enum_fmt_vid_cap,
> > + .vidioc_g_fmt_vid_cap = chv3_fb_g_fmt_vid_cap,
> > + .vidioc_s_fmt_vid_cap = chv3_fb_g_fmt_vid_cap,
> > + .vidioc_try_fmt_vid_cap = chv3_fb_g_fmt_vid_cap,
> > +
> > + .vidioc_enum_framesizes = chv3_fb_enum_framesizes,
> > +
> > + .vidioc_enum_input = chv3_fb_enum_input,
> > + .vidioc_g_input = chv3_fb_g_input,
> > + .vidioc_s_input = chv3_fb_s_input,
> > + .vidioc_g_edid = chv3_fb_g_edid,
> > + .vidioc_s_edid = chv3_fb_s_edid,
> > +
> > + .vidioc_reqbufs = vb2_ioctl_reqbufs,
> > + .vidioc_create_bufs = vb2_ioctl_create_bufs,
> > + .vidioc_querybuf = vb2_ioctl_querybuf,
> > + .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
> > + .vidioc_expbuf = vb2_ioctl_expbuf,
> > + .vidioc_qbuf = vb2_ioctl_qbuf,
> > + .vidioc_dqbuf = vb2_ioctl_dqbuf,
> > + .vidioc_streamon = vb2_ioctl_streamon,
> > + .vidioc_streamoff = vb2_ioctl_streamoff,
> > +
> > + .vidioc_s_dv_timings = chv3_fb_s_dv_timings,
> > + .vidioc_g_dv_timings = chv3_fb_g_dv_timings,
> > + .vidioc_query_dv_timings = chv3_fb_vidioc_query_dv_timings,
> > + .vidioc_enum_dv_timings = chv3_fb_enum_dv_timings,
> > + .vidioc_dv_timings_cap = chv3_fb_dv_timings_cap,
> > +
> > + .vidioc_subscribe_event = chv3_fb_subscribe_event,
> > + .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
> > +};
> > +
> > +static int chv3_fb_queue_setup(struct vb2_queue *q,
> > + unsigned int *nbuffers, unsigned int *nplanes,
> > + unsigned int sizes[], struct device *alloc_devs[])
> > +{
> > + struct chv3_fb *fb = vb2_get_drv_priv(q);
> > +
> > + if (*nplanes) {
> > + if (sizes[0] < fb->pix_fmt.sizeimage)
> > + return -EINVAL;
> > + return 0;
> > + }
> > + *nplanes = 1;
> > + sizes[0] = fb->pix_fmt.sizeimage;
> > +
> > + return 0;
> > +}
> > +
> > +/*
> > + * There are two address registers: BUFFERA and BUFFERB. The framebuffer
> > + * alternates writing between them (i.e. even frames go to BUFFERA, odd
> > + * ones to BUFFERB).
> > + *
> > + * (buffer queue) > QUEUED ---> QUEUED ---> QUEUED ---> ...
> > + * BUFFERA BUFFERB
> > + * (hw writing to this) ^
> > + * (and then to this) ^
> > + *
> > + * The buffer swapping happens at irq time. When an irq comes, the next
> > + * frame is already assigned an address in the buffer queue. This gives
> > + * the irq handler a whole frame's worth of time to update the buffer
> > + * address register.
> > + */
> > +
> > +static dma_addr_t chv3_fb_buffer_dma_addr(struct chv3_fb_buffer *buf)
> > +{
> > + return vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
> > +}
> > +
> > +static void chv3_fb_start_frame(struct chv3_fb *fb, struct chv3_fb_buffer *buf)
> > +{
> > + fb->writing_to_a = 1;
> > + writel(chv3_fb_buffer_dma_addr(buf), fb->iobase + FB_BUFFERA);
> > + writel(FB_EN_BIT, fb->iobase + FB_EN);
> > +}
> > +
> > +static void chv3_fb_next_frame(struct chv3_fb *fb, struct chv3_fb_buffer *buf)
> > +{
> > + u32 reg = fb->writing_to_a ? FB_BUFFERB : FB_BUFFERA;
> > +
> > + writel(chv3_fb_buffer_dma_addr(buf), fb->iobase + reg);
> > +}
> > +
> > +static int chv3_fb_start_streaming(struct vb2_queue *q, unsigned int count)
> > +{
> > + struct chv3_fb *fb = vb2_get_drv_priv(q);
> > + struct chv3_fb_buffer *buf;
> > + unsigned long flags;
> > +
> > + fb->sequence = 0;
> > + writel(fb->pix_fmt.sizeimage, fb->iobase + FB_BUFFERSIZE);
> > +
> > + spin_lock_irqsave(&fb->bufs_lock, flags);
> > + buf = list_first_entry_or_null(&fb->bufs, struct chv3_fb_buffer, link);
> > + if (buf) {
> > + chv3_fb_start_frame(fb, buf);
> > + if (!list_is_last(&buf->link, &fb->bufs))
> > + chv3_fb_next_frame(fb, list_next_entry(buf, link));
> > + }
> > + spin_unlock_irqrestore(&fb->bufs_lock, flags);
> > +
> > + return 0;
> > +}
> > +
> > +static void chv3_fb_stop_streaming(struct vb2_queue *q)
> > +{
> > + struct chv3_fb *fb = vb2_get_drv_priv(q);
> > + struct chv3_fb_buffer *buf;
> > + unsigned long flags;
> > +
> > + writel(0, fb->iobase + FB_EN);
> > +
> > + spin_lock_irqsave(&fb->bufs_lock, flags);
> > + list_for_each_entry(buf, &fb->bufs, link)
> > + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
> > + INIT_LIST_HEAD(&fb->bufs);
> > + spin_unlock_irqrestore(&fb->bufs_lock, flags);
> > +}
> > +
> > +static void chv3_fb_buf_queue(struct vb2_buffer *vb)
> > +{
> > + struct chv3_fb *fb = vb2_get_drv_priv(vb->vb2_queue);
> > + struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> > + struct chv3_fb_buffer *buf = container_of(v4l2_buf, struct chv3_fb_buffer, vb);
> > + bool first, second;
> > + unsigned long flags;
> > +
> > + spin_lock_irqsave(&fb->bufs_lock, flags);
> > + first = list_empty(&fb->bufs);
> > + second = list_is_singular(&fb->bufs);
> > + list_add_tail(&buf->link, &fb->bufs);
> > + if (vb2_is_streaming(vb->vb2_queue)) {
> > + if (first)
> > + chv3_fb_start_frame(fb, buf);
> > + else if (second)
> > + chv3_fb_next_frame(fb, buf);
> > + }
> > + spin_unlock_irqrestore(&fb->bufs_lock, flags);
> > +}
> > +
> > +static const struct vb2_ops chv3_fb_vb2_ops = {
> > + .queue_setup = chv3_fb_queue_setup,
> > + .wait_prepare = vb2_ops_wait_prepare,
> > + .wait_finish = vb2_ops_wait_finish,
> > + .start_streaming = chv3_fb_start_streaming,
> > + .stop_streaming = chv3_fb_stop_streaming,
> > + .buf_queue = chv3_fb_buf_queue,
> > +};
> > +
> > +static int chv3_fb_open(struct file *file)
> > +{
> > + struct chv3_fb *fb = video_drvdata(file);
> > + int res;
> > +
> > + mutex_lock(&fb->fb_lock);
> > + res = v4l2_fh_open(file);
> > + if (!res) {
> > + if (v4l2_fh_is_singular_file(file))
> > + chv3_fb_apply_dv_timings(fb);
> > + }
> > + mutex_unlock(&fb->fb_lock);
> > +
> > + return res;
> > +}
> > +
> > +static const struct v4l2_file_operations chv3_fb_v4l2_fops = {
> > + .owner = THIS_MODULE,
> > + .open = chv3_fb_open,
> > + .release = vb2_fop_release,
> > + .unlocked_ioctl = video_ioctl2,
> > + .mmap = vb2_fop_mmap,
> > + .poll = vb2_fop_poll,
> > +};
> > +
> > +static void chv3_fb_frame_irq(struct chv3_fb *fb)
> > +{
> > + struct chv3_fb_buffer *buf;
> > +
> > + spin_lock(&fb->bufs_lock);
> > +
> > + buf = list_first_entry_or_null(&fb->bufs, struct chv3_fb_buffer, link);
> > + if (!buf)
> > + goto empty;
> > + list_del(&buf->link);
> > +
> > + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, fb->pix_fmt.sizeimage);
> > + buf->vb.vb2_buf.timestamp = ktime_get_ns();
> > + buf->vb.sequence = fb->sequence++;
> > + buf->vb.field = V4L2_FIELD_NONE;
> > + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
> > +
> > + buf = list_first_entry_or_null(&fb->bufs, struct chv3_fb_buffer, link);
> > + if (buf) {
> > + fb->writing_to_a = !fb->writing_to_a;
> > + if (!list_is_last(&buf->link, &fb->bufs))
> > + chv3_fb_next_frame(fb, list_next_entry(buf, link));
> > + } else {
> > + writel(0, fb->iobase + FB_EN);
> > + }
> > +empty:
> > + spin_unlock(&fb->bufs_lock);
> > +}
> > +
> > +static void chv3_fb_error_irq(struct chv3_fb *fb)
> > +{
> > + if (vb2_is_streaming(&fb->queue))
> > + vb2_queue_error(&fb->queue);
> > +}
> > +
> > +static void chv3_fb_resolution_irq(struct chv3_fb *fb)
> > +{
> > + static const struct v4l2_event event = {
> > + .type = V4L2_EVENT_SOURCE_CHANGE,
> > + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION,
> > + };
> > +
> > + v4l2_event_queue(&fb->vdev, &event);
> > + chv3_fb_error_irq(fb);
> > +}
> > +
> > +static irqreturn_t chv3_fb_isr(int irq, void *data)
> > +{
> > + struct chv3_fb *fb = data;
> > + unsigned int reg;
> > +
> > + reg = readl(fb->iobase_irq + FB_IRQ_CLR);
> > + if (!reg)
> > + return IRQ_NONE;
> > +
> > + if (reg & FB_IRQ_BUFF0)
> > + chv3_fb_frame_irq(fb);
> > + if (reg & FB_IRQ_BUFF1)
> > + chv3_fb_frame_irq(fb);
> > + if (reg & FB_IRQ_RESOLUTION)
> > + chv3_fb_resolution_irq(fb);
> > + if (reg & FB_IRQ_ERROR) {
> > + dev_warn(fb->dev, "framebuffer error: 0x%x\n",
> > + readl(fb->iobase + FB_ERRORSTATUS));
> > + chv3_fb_error_irq(fb);
> > + }
> > +
> > + writel(reg, fb->iobase_irq + FB_IRQ_CLR);
> > +
> > + return IRQ_HANDLED;
> > +}
> > +
> > +static int chv3_fb_check_version(struct chv3_fb *fb)
> > +{
> > + u32 version;
> > +
> > + version = readl(fb->iobase + FB_VERSION);
> > + if (version != FB_VERSION_CURRENT) {
> > + dev_err(fb->dev,
> > + "wrong framebuffer version: expected %x, got %x\n",
> > + FB_VERSION_CURRENT, version);
> > + return -ENODEV;
> > + }
> > + return 0;
> > +}
> > +
> > +static void chv3_fb_set_default_format(struct chv3_fb *fb, bool legacy_format)
> > +{
> > + struct v4l2_pix_format *pix = &fb->pix_fmt;
> > +
> > + if (legacy_format)
> > + pix->pixelformat = V4L2_PIX_FMT_BGRX32;
> > + else
> > + pix->pixelformat = V4L2_PIX_FMT_RGB24;
> > + pix->field = V4L2_FIELD_NONE;
> > + pix->colorspace = V4L2_COLORSPACE_SRGB;
> > +
> > + chv3_fb_set_format_resolution(fb, DEFAULT_WIDTH, DEFAULT_HEIGHT);
> > +}
> > +
> > +static void chv3_fb_set_default_timings(struct chv3_fb *fb)
> > +{
> > + memset(&fb->timings, 0, sizeof(fb->timings));
> > + fb->timings.type = V4L2_DV_BT_656_1120;
> > + fb->timings.bt.width = DEFAULT_WIDTH;
> > + fb->timings.bt.height = DEFAULT_HEIGHT;
>
> Wouldn't it be better to say: fb->timings = V4L2_DV_BT_CEA_1920X1080P60;

Yes, that would work.

>
> > +}
> > +
> > +#define notifier_to_fb(nf) container_of(nf, struct chv3_fb, notifier)
> > +
> > +static int chv3_fb_async_notify_bound(struct v4l2_async_notifier *notifier,
> > + struct v4l2_subdev *subdev,
> > + struct v4l2_async_connection *asc)
> > +{
> > + struct chv3_fb *fb = notifier_to_fb(notifier);
> > + int pad;
> > +
> > + pad = media_entity_get_fwnode_pad(&subdev->entity, asc->match.fwnode,
> > + MEDIA_PAD_FL_SOURCE);
> > + if (pad < 0)
> > + return pad;
> > +
> > + fb->subdev = subdev;
> > + fb->subdev_source_pad = pad;
> > +
> > + return 0;
> > +}
> > +
> > +static void chv3_fb_async_notify_unbind(struct v4l2_async_notifier *notifier,
> > + struct v4l2_subdev *subdev,
> > + struct v4l2_async_connection *asc)
> > +{
> > + struct chv3_fb *fb = notifier_to_fb(notifier);
> > +
> > + video_unregister_device(&fb->vdev);
>
> Use vb2_video_unregister_device. See documentation in include/media/videobuf2-v4l2.h.

So if I understand the docs correctly, I should just change this line to
'vb2_video_unregister_device(&fb->vdev);'?

>
> > +}
> > +
> > +static int chv3_fb_async_notify_complete(struct v4l2_async_notifier *notifier)
> > +{
> > + struct chv3_fb *fb = notifier_to_fb(notifier);
> > +
> > + return video_register_device(&fb->vdev, VFL_TYPE_VIDEO, -1);
> > +}
> > +
> > +static const struct v4l2_async_notifier_operations chv3_fb_async_notify_ops = {
> > + .bound = chv3_fb_async_notify_bound,
> > + .unbind = chv3_fb_async_notify_unbind,
> > + .complete = chv3_fb_async_notify_complete,
> > +};
> > +
> > +static int chv3_fb_fallback_init(struct chv3_fb *fb)
> > +{
> > + fb->subdev = NULL;
> > + fb->subdev_source_pad = 0;
> > +
> > + return video_register_device(&fb->vdev, VFL_TYPE_VIDEO, -1);
> > +}
> > +
> > +static int chv3_fb_fwnode_init(struct chv3_fb *fb)
> > +{
> > + struct v4l2_async_connection *asc;
> > + struct fwnode_handle *endpoint;
> > + int res;
> > +
> > + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(fb->dev), NULL);
> > + if (!endpoint)
> > + return -EINVAL;
> > +
> > + v4l2_async_nf_init(&fb->notifier, &fb->v4l2_dev);
> > +
> > + asc = v4l2_async_nf_add_fwnode_remote(&fb->notifier, endpoint,
> > + struct v4l2_async_connection);
> > + fwnode_handle_put(endpoint);
> > +
> > + if (IS_ERR(asc))
> > + return PTR_ERR(asc);
> > +
> > + fb->notifier.ops = &chv3_fb_async_notify_ops;
> > + res = v4l2_async_nf_register(&fb->notifier);
> > + if (res) {
> > + v4l2_async_nf_cleanup(&fb->notifier);
> > + return res;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int chv3_fb_probe(struct platform_device *pdev)
> > +{
> > + struct chv3_fb *fb;
> > + bool legacy_format;
> > + int res;
> > + int irq;
> > +
> > + fb = devm_kzalloc(&pdev->dev, sizeof(*fb), GFP_KERNEL);
> > + if (!fb)
> > + return -ENOMEM;
> > + fb->dev = &pdev->dev;
> > + platform_set_drvdata(pdev, fb);
> > +
> > + /* map register space */
> > + fb->iobase = devm_platform_ioremap_resource(pdev, 0);
> > + if (IS_ERR(fb->iobase))
> > + return PTR_ERR(fb->iobase);
> > +
> > + fb->iobase_irq = devm_platform_ioremap_resource(pdev, 1);
> > + if (IS_ERR(fb->iobase_irq))
> > + return PTR_ERR(fb->iobase_irq);
> > +
> > + /* check hw version */
> > + res = chv3_fb_check_version(fb);
> > + if (res)
> > + return res;
> > +
> > + /* setup interrupts */
> > + irq = platform_get_irq(pdev, 0);
> > + if (irq < 0)
> > + return -ENXIO;
> > + res = devm_request_irq(&pdev->dev, irq, chv3_fb_isr, 0, DEVICE_NAME, fb);
> > + if (res)
> > + return res;
> > +
> > + /* initialize v4l2_device */
> > + res = v4l2_device_register(&pdev->dev, &fb->v4l2_dev);
> > + if (res)
> > + return res;
> > +
> > + /* initialize vb2 queue */
> > + fb->queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
> > + fb->queue.io_modes = VB2_MMAP | VB2_DMABUF;
> > + fb->queue.dev = &pdev->dev;
> > + fb->queue.lock = &fb->fb_lock;
> > + fb->queue.ops = &chv3_fb_vb2_ops;
> > + fb->queue.mem_ops = &vb2_dma_contig_memops;
> > + fb->queue.drv_priv = fb;
> > + fb->queue.buf_struct_size = sizeof(struct chv3_fb_buffer);
> > + fb->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> > + res = vb2_queue_init(&fb->queue);
> > + if (res)
> > + goto error;
> > +
> > + /* initialize video_device */
> > + strscpy(fb->vdev.name, DEVICE_NAME, sizeof(fb->vdev.name));
> > + fb->vdev.fops = &chv3_fb_v4l2_fops;
> > + fb->vdev.ioctl_ops = &chv3_fb_v4l2_ioctl_ops;
> > + fb->vdev.lock = &fb->fb_lock;
> > + fb->vdev.release = video_device_release_empty;
> > + fb->vdev.device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
> > + fb->vdev.v4l2_dev = &fb->v4l2_dev;
> > + fb->vdev.queue = &fb->queue;
> > + video_set_drvdata(&fb->vdev, fb);
> > +
> > + /* read other DT properties */
> > + legacy_format = device_property_read_bool(&pdev->dev, "google,legacy-format");
> > +
> > + if (device_get_named_child_node(&pdev->dev, "port"))
> > + res = chv3_fb_fwnode_init(fb);
> > + else
> > + res = chv3_fb_fallback_init(fb);
> > + if (res)
> > + goto error;
> > +
> > + /* initialize rest of driver struct */
> > + INIT_LIST_HEAD(&fb->bufs);
> > + spin_lock_init(&fb->bufs_lock);
> > + mutex_init(&fb->fb_lock);
> > +
> > + chv3_fb_set_default_format(fb, legacy_format);
> > + chv3_fb_set_default_timings(fb);
> > +
> > + /* initialize hw */
> > + writel(FB_RESET_BIT, fb->iobase + FB_RESET);
> > + writel(FB_DATARATE_DOUBLE, fb->iobase + FB_DATARATE);
> > + writel(FB_PIXELMODE_DOUBLE, fb->iobase + FB_PIXELMODE);
> > + if (legacy_format)
> > + writel(FB_DMAFORMAT_8BPC_LEGACY, fb->iobase + FB_DMAFORMAT);
> > + else
> > + writel(FB_DMAFORMAT_8BPC, fb->iobase + FB_DMAFORMAT);
> > +
> > + writel(FB_IRQ_ALL, fb->iobase_irq + FB_IRQ_MASK);
> > +
> > + return 0;
> > +
> > +error:
> > + v4l2_device_unregister(&fb->v4l2_dev);
> > +
> > + return res;
> > +}
> > +
> > +static void chv3_fb_remove(struct platform_device *pdev)
> > +{
> > + struct chv3_fb *fb = platform_get_drvdata(pdev);
> > +
> > + /* disable interrupts */
> > + writel(0, fb->iobase_irq + FB_IRQ_MASK);
> > +
> > + v4l2_async_nf_unregister(&fb->notifier);
> > + v4l2_async_nf_cleanup(&fb->notifier);
> > + v4l2_device_unregister(&fb->v4l2_dev);
> > +}
> > +
> > +static const struct of_device_id chv3_fb_match_table[] = {
> > + { .compatible = "google,chv3-fb" },
> > + { },
> > +};
> > +
> > +static struct platform_driver chv3_fb_platform_driver = {
> > + .probe = chv3_fb_probe,
> > + .remove_new = chv3_fb_remove,
> > + .driver = {
> > + .name = DEVICE_NAME,
> > + .of_match_table = chv3_fb_match_table,
> > + },
> > +};
> > +
> > +module_platform_driver(chv3_fb_platform_driver);
> > +
> > +MODULE_AUTHOR("Paweł Anikiel <[email protected]>");
> > +MODULE_DESCRIPTION("Google Chameleon v3 video framebuffer driver");
> > +MODULE_LICENSE("GPL");
>
> For the v3, can you also provide the output of 'v4l2-compliance -s'
> in the cover letter? Make sure you compile v4l2-compliance directly
> from the git repo https://git.linuxtv.org/v4l-utils.git/ since the version
> from distros is usually too old.

Okay, I'll do that.

2024-02-28 15:14:57

by Hans Verkuil

[permalink] [raw]
Subject: Re: [PATCH v2 2/9] media: Add Chameleon v3 framebuffer driver

On 28/02/2024 16:08, Paweł Anikiel wrote:
> Hi Hans, thanks for the review!
>
> On Wed, Feb 28, 2024 at 12:24 PM Hans Verkuil <[email protected]> wrote:
>>
>> Hi Paweł,
>>
>> On 21/02/2024 17:02, Paweł Anikiel wrote:
>>> Add v4l2 driver for the Google Chameleon v3 framebuffer device.
>>
>> This is just a video capture device, right? A framebuffer device is something
>> that lives in drivers/video/fbdev.
>
> Yes, it is just a capture device.
>
>>
>> It is *very* confusing to see the term 'framebuffer' used in a video
>> capture context.
>
> I agree the name is confusing. I think it started out as something
> else and unfortunately stuck around. I think it's possible to change
> it, though.

That would be very helpful.

>
>>
>> This commit log should also give a better description of the hardware.
>> Just a single one-liner is a bit on the short side :-)
>
> Would it be fine to just put the Kconfig help text there?

Yes.

>
>>
>>>
>>> Signed-off-by: Paweł Anikiel <[email protected]>
>>> ---
>>> 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 | 895 ++++++++++++++++++
>>
>> chv3-video.c would be a much better name for chv3-fb.c.
>>
>> That's a commonly used filename for video capture drivers.
>
> I'm guessing all the instances of fb or framebuffer in the driver
> itself should be changed as well in that case?

Yes, please. I wouldn't normally ask for such major renaming, but
in this case that name is really confusing.

>
>>
>>> 7 files changed, 918 insertions(+)
>>> create mode 100644 drivers/media/platform/google/Kconfig
>>> create mode 100644 drivers/media/platform/google/Makefile
>>> create mode 100644 drivers/media/platform/google/chameleonv3/Kconfig
>>> create mode 100644 drivers/media/platform/google/chameleonv3/Makefile
>>> create mode 100644 drivers/media/platform/google/chameleonv3/chv3-fb.c
>>>
>>> diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
>>> index 91e54215de3a..b82f7b142b85 100644
>>> --- a/drivers/media/platform/Kconfig
>>> +++ b/drivers/media/platform/Kconfig
>>> @@ -69,6 +69,7 @@ source "drivers/media/platform/aspeed/Kconfig"
>>> source "drivers/media/platform/atmel/Kconfig"
>>> source "drivers/media/platform/cadence/Kconfig"
>>> source "drivers/media/platform/chips-media/Kconfig"
>>> +source "drivers/media/platform/google/Kconfig"
>>> source "drivers/media/platform/intel/Kconfig"
>>> source "drivers/media/platform/marvell/Kconfig"
>>> source "drivers/media/platform/mediatek/Kconfig"
>>> diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
>>> index 3296ec1ebe16..f7067eb05f76 100644
>>> --- a/drivers/media/platform/Makefile
>>> +++ b/drivers/media/platform/Makefile
>>> @@ -12,6 +12,7 @@ obj-y += aspeed/
>>> obj-y += atmel/
>>> obj-y += cadence/
>>> obj-y += chips-media/
>>> +obj-y += google/
>>> obj-y += intel/
>>> obj-y += marvell/
>>> obj-y += mediatek/
>>> diff --git a/drivers/media/platform/google/Kconfig b/drivers/media/platform/google/Kconfig
>>> new file mode 100644
>>> index 000000000000..2a5f01034c11
>>> --- /dev/null
>>> +++ b/drivers/media/platform/google/Kconfig
>>> @@ -0,0 +1,3 @@
>>> +# SPDX-License-Identifier: GPL-2.0-only
>>> +
>>> +source "drivers/media/platform/google/chameleonv3/Kconfig"
>>> diff --git a/drivers/media/platform/google/Makefile b/drivers/media/platform/google/Makefile
>>> new file mode 100644
>>> index 000000000000..c971a09faeb4
>>> --- /dev/null
>>> +++ b/drivers/media/platform/google/Makefile
>>> @@ -0,0 +1,2 @@
>>> +# SPDX-License-Identifier: GPL-2.0-only
>>> +obj-y += chameleonv3/
>>> diff --git a/drivers/media/platform/google/chameleonv3/Kconfig b/drivers/media/platform/google/chameleonv3/Kconfig
>>> new file mode 100644
>>> index 000000000000..76d0383a8589
>>> --- /dev/null
>>> +++ b/drivers/media/platform/google/chameleonv3/Kconfig
>>> @@ -0,0 +1,13 @@
>>> +# SPDX-License-Identifier: GPL-2.0-only
>>> +
>>> +config VIDEO_CHV3_FB
>>> + tristate "Google Chameleon v3 framebuffer video driver"
>>> + depends on V4L_PLATFORM_DRIVERS
>>> + depends on VIDEO_DEV
>>> + select VIDEOBUF2_DMA_CONTIG
>>> + select V4L2_FWNODE
>>> + help
>>> + v4l2 driver for the video interface present on the Google
>>> + Chameleon v3. The Chameleon v3 uses the framebuffer IP core
>>> + to take the video signal from different sources and directly
>>> + write frames into memory.
>>
>> So it is composing different video streams into buffers? Or does it
>> capture from a single source at a time? The text is rather ambiguous.
>
> You're right, I'll write a more precise description.
>
>>
>>> diff --git a/drivers/media/platform/google/chameleonv3/Makefile b/drivers/media/platform/google/chameleonv3/Makefile
>>> new file mode 100644
>>> index 000000000000..d63727148688
>>> --- /dev/null
>>> +++ b/drivers/media/platform/google/chameleonv3/Makefile
>>> @@ -0,0 +1,3 @@
>>> +# SPDX-License-Identifier: GPL-2.0-only
>>> +
>>> +obj-$(CONFIG_VIDEO_CHV3_FB) += chv3-fb.o
>>> diff --git a/drivers/media/platform/google/chameleonv3/chv3-fb.c b/drivers/media/platform/google/chameleonv3/chv3-fb.c
>>> new file mode 100644
>>> index 000000000000..35a44365eba0
>>> --- /dev/null
>>> +++ b/drivers/media/platform/google/chameleonv3/chv3-fb.c
>>> @@ -0,0 +1,895 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +/*
>>> + * Copyright 2023-2024 Google LLC.
>>> + * Author: Paweł Anikiel <[email protected]>
>>> + */
>>> +
>>> +#include <linux/delay.h>
>>> +#include <linux/dma-mapping.h>
>>> +#include <linux/interrupt.h>
>>> +#include <linux/kernel.h>
>>> +#include <linux/module.h>
>>> +#include <linux/of.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/videodev2.h>
>>> +#include <media/v4l2-ctrls.h>
>>> +#include <media/v4l2-device.h>
>>> +#include <media/v4l2-dv-timings.h>
>>> +#include <media/v4l2-event.h>
>>> +#include <media/v4l2-fwnode.h>
>>> +#include <media/v4l2-ioctl.h>
>>> +#include <media/videobuf2-dma-contig.h>
>>> +
>>> +#define DEVICE_NAME "chv3-fb"
>>> +
>>> +/*
>>> + * The device is expected to report some format even if there's currently no
>>> + * active video stream. In such case we default to 1080p.
>>> + */
>>> +#define DEFAULT_WIDTH 1920
>>> +#define DEFAULT_HEIGHT 1080
>>> +
>>> +#define FB_EN 0x00
>>> +#define FB_EN_BIT BIT(0)
>>> +#define FB_HEIGHT 0x04
>>> +#define FB_WIDTH 0x08
>>> +#define FB_BUFFERA 0x0c
>>> +#define FB_BUFFERB 0x10
>>> +#define FB_BUFFERSIZE 0x14
>>> +#define FB_RESET 0x18
>>> +#define FB_RESET_BIT BIT(0)
>>> +#define FB_ERRORSTATUS 0x1c
>>> +#define FB_IOCOLOR 0x20
>>> +#define FB_DATARATE 0x24
>>> +#define FB_DATARATE_SINGLE 0x0
>>> +#define FB_DATARATE_DOUBLE 0x1
>>> +#define FB_PIXELMODE 0x28
>>> +#define FB_PIXELMODE_SINGLE 0x0
>>> +#define FB_PIXELMODE_DOUBLE 0x1
>>> +#define FB_SYNCPOLARITY 0x2c
>>> +#define FB_DMAFORMAT 0x30
>>> +#define FB_DMAFORMAT_8BPC 0x0
>>> +#define FB_DMAFORMAT_10BPC_UPPER 0x1
>>> +#define FB_DMAFORMAT_10BPC_LOWER 0x2
>>> +#define FB_DMAFORMAT_12BPC_UPPER 0x3
>>> +#define FB_DMAFORMAT_12BPC_LOWER 0x4
>>> +#define FB_DMAFORMAT_16BPC 0x5
>>> +#define FB_DMAFORMAT_RAW 0x6
>>> +#define FB_DMAFORMAT_8BPC_LEGACY 0x7
>>> +#define FB_VERSION 0x34
>>> +#define FB_VERSION_CURRENT 0xc0fb0001
>>> +
>>> +#define FB_IRQ_MASK 0x8
>>> +#define FB_IRQ_CLR 0xc
>>> +#define FB_IRQ_ALL 0xf
>>> +#define FB_IRQ_BUFF0 BIT(0)
>>> +#define FB_IRQ_BUFF1 BIT(1)
>>> +#define FB_IRQ_RESOLUTION BIT(2)
>>> +#define FB_IRQ_ERROR BIT(3)
>>> +
>>> +struct chv3_fb {
>>> + struct device *dev;
>>> + void __iomem *iobase;
>>> + void __iomem *iobase_irq;
>>> +
>>> + struct v4l2_device v4l2_dev;
>>> + struct vb2_queue queue;
>>> + struct video_device vdev;
>>> + struct v4l2_pix_format pix_fmt;
>>> + struct v4l2_dv_timings timings;
>>> +
>>> + struct v4l2_async_notifier notifier;
>>> + struct v4l2_subdev *subdev;
>>> + int subdev_source_pad;
>>> +
>>> + u32 sequence;
>>> + bool writing_to_a;
>>> +
>>> + struct list_head bufs;
>>> + spinlock_t bufs_lock;
>>> +
>>> + struct mutex fb_lock;
>>> +};
>>> +
>>> +struct chv3_fb_buffer {
>>> + struct vb2_v4l2_buffer vb;
>>> + struct list_head link;
>>> +};
>>> +
>>> +static void chv3_fb_set_format_resolution(struct chv3_fb *fb, u32 width, u32 height)
>>> +{
>>> + u32 bytes_per_pixel;
>>> +
>>> + if (fb->pix_fmt.pixelformat == V4L2_PIX_FMT_RGB24)
>>> + bytes_per_pixel = 3;
>>> + else
>>> + bytes_per_pixel = 4;
>>> +
>>> + fb->pix_fmt.width = width;
>>> + fb->pix_fmt.height = height;
>>> + fb->pix_fmt.bytesperline = width * bytes_per_pixel;
>>> + fb->pix_fmt.sizeimage = fb->pix_fmt.bytesperline * height;
>>> +}
>>> +
>>> +/*
>>> + * The video interface has hardware counters which expose the width and
>>> + * height of the current video stream. It can't reliably detect if the stream
>>> + * is present or not, so this is only used as a fallback in the case where
>>> + * we don't have access to the receiver hardware.
>>> + */
>>> +static int chv3_fb_query_dv_timings_fallback(struct chv3_fb *fb,
>>> + struct v4l2_dv_timings *timings)
>>> +{
>>> + u32 width, height;
>>> +
>>> + width = readl(fb->iobase + FB_WIDTH);
>>> + height = readl(fb->iobase + FB_HEIGHT);
>>> + if (width == 0 || height == 0)
>>> + return -ENOLINK;
>>> +
>>> + memset(timings, 0, sizeof(*timings));
>>> + timings->type = V4L2_DV_BT_656_1120;
>>> + timings->bt.width = width;
>>> + timings->bt.height = height;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int chv3_fb_query_dv_timings(struct chv3_fb *fb, struct v4l2_dv_timings *timings)
>>> +{
>>> + if (fb->subdev) {
>>> + return v4l2_subdev_call(fb->subdev, pad, query_dv_timings,
>>> + fb->subdev_source_pad, timings);
>>> + } else {
>>> + return chv3_fb_query_dv_timings_fallback(fb, timings);
>>> + }
>>> +}
>>> +
>>> +static const struct v4l2_dv_timings_cap chv3_fb_fallback_dv_timings_cap = {
>>> + .type = V4L2_DV_BT_656_1120,
>>> + .bt = {
>>> + .min_width = 0,
>>
>> This is an unlikely minimum width (ditto for height below).
>>
>>> + .max_width = 65535,
>>
>> The max is suspect as well: I would expect it to be a multiple of 2/4/8/16.
>>
>>> + .min_height = 0,
>>> + .max_height = 65535,
>>> + .min_pixelclock = 0,
>>> + .max_pixelclock = 2147483647,
>>
>> Ditto for these.
>
> Note: these are used only when there is no controllable endpoint
> attached to the video interface, and we don't know what the timing
> caps are.
>
> Should I then just pick something like 640x480p24 as min and 8Kp120 as max?

Yes, that would be a better.

>
>>
>>> + .standards = V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT |
>>> + V4L2_DV_BT_STD_CVT | V4L2_DV_BT_STD_GTF,
>>> + .capabilities = V4L2_DV_BT_CAP_PROGRESSIVE |
>>> + V4L2_DV_BT_CAP_REDUCED_BLANKING |
>>> + V4L2_DV_BT_CAP_CUSTOM,
>>> + },
>>> +};
>>> +
>>> +static int chv3_fb_enum_dv_timings_fallback(struct chv3_fb *fb,
>>> + struct v4l2_enum_dv_timings *timings)
>>> +{
>>> + return v4l2_enum_dv_timings_cap(timings, &chv3_fb_fallback_dv_timings_cap,
>>> + NULL, NULL);
>>> +}
>>> +
>>> +static int chv3_fb_dv_timings_cap_fallback(struct chv3_fb *fb,
>>> + struct v4l2_dv_timings_cap *cap)
>>> +{
>>> + *cap = chv3_fb_fallback_dv_timings_cap;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static void chv3_fb_apply_dv_timings(struct chv3_fb *fb)
>>> +{
>>> + struct v4l2_dv_timings timings;
>>> + int res;
>>> +
>>> + res = chv3_fb_query_dv_timings(fb, &timings);
>>> + if (res)
>>> + return;
>>> +
>>> + fb->timings = timings;
>>> + chv3_fb_set_format_resolution(fb, timings.bt.width, timings.bt.height);
>>> +}
>>> +
>>> +static int chv3_fb_querycap(struct file *file, void *fh,
>>> + struct v4l2_capability *cap)
>>> +{
>>> + strscpy(cap->driver, DEVICE_NAME, sizeof(cap->driver));
>>> + strscpy(cap->card, "Chameleon v3 video", sizeof(cap->card));
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int chv3_fb_g_fmt_vid_cap(struct file *file, void *fh,
>>> + struct v4l2_format *fmt)
>>> +{
>>> + struct chv3_fb *fb = video_drvdata(file);
>>> +
>>> + fmt->fmt.pix = fb->pix_fmt;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int chv3_fb_enum_fmt_vid_cap(struct file *file, void *fh,
>>> + struct v4l2_fmtdesc *fmt)
>>> +{
>>> + struct chv3_fb *fb = video_drvdata(file);
>>> +
>>> + if (fmt->index != 0)
>>> + return -EINVAL;
>>> +
>>> + fmt->flags = 0;
>>> + fmt->pixelformat = fb->pix_fmt.pixelformat;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int chv3_fb_enum_framesizes(struct file *file, void *fh,
>>> + struct v4l2_frmsizeenum *frm)
>>> +{
>>> + struct chv3_fb *fb = video_drvdata(file);
>>> +
>>> + if (frm->index != 0)
>>> + return -EINVAL;
>>> +
>>> + if (frm->pixel_format != fb->pix_fmt.pixelformat)
>>> + return -EINVAL;
>>> +
>>> + frm->type = V4L2_FRMSIZE_TYPE_DISCRETE;
>>> + frm->discrete.width = fb->pix_fmt.width;
>>> + frm->discrete.height = fb->pix_fmt.height;
>>
>> This is not something you would expect to see supported for a
>> video receiver input. This is something you use for camera sensors.
>>
>> This should almost certainly be dropped.
>
> Thanks for explaining, I'll remove it in v3.
>
>>
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int chv3_fb_g_input(struct file *file, void *fh, unsigned int *index)
>>> +{
>>> + *index = 0;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int chv3_fb_s_input(struct file *file, void *fh, unsigned int index)
>>> +{
>>> + if (index != 0)
>>> + return -EINVAL;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int chv3_fb_enum_input(struct file *file, void *fh,
>>> + struct v4l2_input *input)
>>> +{
>>> + if (input->index != 0)
>>> + return -EINVAL;
>>> +
>>> + strscpy(input->name, "input0", sizeof(input->name));
>>> + input->type = V4L2_INPUT_TYPE_CAMERA;
>>> + input->capabilities = V4L2_IN_CAP_DV_TIMINGS;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int chv3_fb_g_edid(struct file *file, void *fh,
>>> + struct v4l2_edid *edid)
>>> +{
>>> + struct chv3_fb *fb = video_drvdata(file);
>>> + int res;
>>> +
>>> + if (!fb->subdev)
>>> + return -ENOTTY;
>>> +
>>> + if (edid->pad != 0)
>>> + return -EINVAL;
>>> +
>>> + edid->pad = fb->subdev_source_pad;
>>> + res = v4l2_subdev_call(fb->subdev, pad, get_edid, edid);
>>> + edid->pad = 0;
>>> +
>>> + return res;
>>> +}
>>> +
>>> +static int chv3_fb_s_edid(struct file *file, void *fh,
>>> + struct v4l2_edid *edid)
>>> +{
>>> + struct chv3_fb *fb = video_drvdata(file);
>>> + int res;
>>> +
>>> + if (!fb->subdev)
>>> + return -ENOTTY;
>>> +
>>> + if (edid->pad != 0)
>>> + return -EINVAL;
>>> +
>>> + edid->pad = fb->subdev_source_pad;
>>> + res = v4l2_subdev_call(fb->subdev, pad, set_edid, edid);
>>> + edid->pad = 0;
>>> +
>>> + return res;
>>> +}
>>> +
>>> +static int chv3_fb_s_dv_timings(struct file *file, void *fh,
>>> + struct v4l2_dv_timings *timings)
>>> +{
>>> + struct chv3_fb *fb = video_drvdata(file);
>>> +
>>> + if (timings->bt.width == fb->timings.bt.width &&
>>> + timings->bt.height == fb->timings.bt.height)
>>
>> You would typically use v4l2_match_dv_timings() for this. The check above
>> is insufficient since it does not even check for different framerates or
>> interlaced vs progressive.
>
> Okay, I changed it to use v4l2_match_dv_timings().
>
>>
>>> + return 0;
>>> +
>>> + if (vb2_is_busy(&fb->queue))
>>> + return -EBUSY;
>>> +
>>> + if (!v4l2_valid_dv_timings(timings, &chv3_fb_fallback_dv_timings_cap, NULL, NULL))
>>> + return -ERANGE;
>>> +
>>> + fb->timings = *timings;
>>> + chv3_fb_set_format_resolution(fb, timings->bt.width, timings->bt.height);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int chv3_fb_g_dv_timings(struct file *file, void *fh,
>>> + struct v4l2_dv_timings *timings)
>>> +{
>>> + struct chv3_fb *fb = video_drvdata(file);
>>> +
>>> + *timings = fb->timings;
>>> + return 0;
>>> +}
>>> +
>>> +static int chv3_fb_vidioc_query_dv_timings(struct file *file, void *fh,
>>> + struct v4l2_dv_timings *timings)
>>> +{
>>> + struct chv3_fb *fb = video_drvdata(file);
>>> +
>>> + return chv3_fb_query_dv_timings(fb, timings);
>>> +}
>>> +
>>> +static int chv3_fb_enum_dv_timings(struct file *file, void *fh,
>>> + struct v4l2_enum_dv_timings *timings)
>>> +{
>>> + struct chv3_fb *fb = video_drvdata(file);
>>> + int res;
>>> +
>>> + if (timings->pad != 0)
>>> + return -EINVAL;
>>> +
>>> + if (fb->subdev) {
>>> + timings->pad = fb->subdev_source_pad;
>>> + res = v4l2_subdev_call(fb->subdev, pad, enum_dv_timings, timings);
>>> + timings->pad = 0;
>>> + return res;
>>> + } else {
>>> + return chv3_fb_enum_dv_timings_fallback(fb, timings);
>>> + }
>>> +}
>>> +
>>> +static int chv3_fb_dv_timings_cap(struct file *file, void *fh,
>>> + struct v4l2_dv_timings_cap *cap)
>>> +{
>>> + struct chv3_fb *fb = video_drvdata(file);
>>> + int res;
>>> +
>>> + if (cap->pad != 0)
>>> + return -EINVAL;
>>> +
>>> + if (fb->subdev) {
>>> + cap->pad = fb->subdev_source_pad;
>>> + res = v4l2_subdev_call(fb->subdev, pad, dv_timings_cap, cap);
>>> + cap->pad = 0;
>>> + return res;
>>> + } else {
>>> + return chv3_fb_dv_timings_cap_fallback(fb, cap);
>>> + }
>>> +}
>>> +
>>> +static int chv3_fb_subscribe_event(struct v4l2_fh *fh,
>>> + const struct v4l2_event_subscription *sub)
>>> +{
>>> + switch (sub->type) {
>>> + case V4L2_EVENT_SOURCE_CHANGE:
>>> + return v4l2_src_change_event_subscribe(fh, sub);
>>> + }
>>> +
>>> + return v4l2_ctrl_subscribe_event(fh, sub);
>>> +}
>>
>> I am missing support for the V4L2_CID_DV_RX_POWER_PRESENT control:
>>
>> https://hverkuil.home.xs4all.nl/spec/userspace-api/v4l/ext-ctrls-dv.html
>
> I'll try to implement it.
>
>>
>>> +
>>> +static const struct v4l2_ioctl_ops chv3_fb_v4l2_ioctl_ops = {
>>> + .vidioc_querycap = chv3_fb_querycap,
>>> +
>>> + .vidioc_enum_fmt_vid_cap = chv3_fb_enum_fmt_vid_cap,
>>> + .vidioc_g_fmt_vid_cap = chv3_fb_g_fmt_vid_cap,
>>> + .vidioc_s_fmt_vid_cap = chv3_fb_g_fmt_vid_cap,
>>> + .vidioc_try_fmt_vid_cap = chv3_fb_g_fmt_vid_cap,
>>> +
>>> + .vidioc_enum_framesizes = chv3_fb_enum_framesizes,
>>> +
>>> + .vidioc_enum_input = chv3_fb_enum_input,
>>> + .vidioc_g_input = chv3_fb_g_input,
>>> + .vidioc_s_input = chv3_fb_s_input,
>>> + .vidioc_g_edid = chv3_fb_g_edid,
>>> + .vidioc_s_edid = chv3_fb_s_edid,
>>> +
>>> + .vidioc_reqbufs = vb2_ioctl_reqbufs,
>>> + .vidioc_create_bufs = vb2_ioctl_create_bufs,
>>> + .vidioc_querybuf = vb2_ioctl_querybuf,
>>> + .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
>>> + .vidioc_expbuf = vb2_ioctl_expbuf,
>>> + .vidioc_qbuf = vb2_ioctl_qbuf,
>>> + .vidioc_dqbuf = vb2_ioctl_dqbuf,
>>> + .vidioc_streamon = vb2_ioctl_streamon,
>>> + .vidioc_streamoff = vb2_ioctl_streamoff,
>>> +
>>> + .vidioc_s_dv_timings = chv3_fb_s_dv_timings,
>>> + .vidioc_g_dv_timings = chv3_fb_g_dv_timings,
>>> + .vidioc_query_dv_timings = chv3_fb_vidioc_query_dv_timings,
>>> + .vidioc_enum_dv_timings = chv3_fb_enum_dv_timings,
>>> + .vidioc_dv_timings_cap = chv3_fb_dv_timings_cap,
>>> +
>>> + .vidioc_subscribe_event = chv3_fb_subscribe_event,
>>> + .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
>>> +};
>>> +
>>> +static int chv3_fb_queue_setup(struct vb2_queue *q,
>>> + unsigned int *nbuffers, unsigned int *nplanes,
>>> + unsigned int sizes[], struct device *alloc_devs[])
>>> +{
>>> + struct chv3_fb *fb = vb2_get_drv_priv(q);
>>> +
>>> + if (*nplanes) {
>>> + if (sizes[0] < fb->pix_fmt.sizeimage)
>>> + return -EINVAL;
>>> + return 0;
>>> + }
>>> + *nplanes = 1;
>>> + sizes[0] = fb->pix_fmt.sizeimage;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +/*
>>> + * There are two address registers: BUFFERA and BUFFERB. The framebuffer
>>> + * alternates writing between them (i.e. even frames go to BUFFERA, odd
>>> + * ones to BUFFERB).
>>> + *
>>> + * (buffer queue) > QUEUED ---> QUEUED ---> QUEUED ---> ...
>>> + * BUFFERA BUFFERB
>>> + * (hw writing to this) ^
>>> + * (and then to this) ^
>>> + *
>>> + * The buffer swapping happens at irq time. When an irq comes, the next
>>> + * frame is already assigned an address in the buffer queue. This gives
>>> + * the irq handler a whole frame's worth of time to update the buffer
>>> + * address register.
>>> + */
>>> +
>>> +static dma_addr_t chv3_fb_buffer_dma_addr(struct chv3_fb_buffer *buf)
>>> +{
>>> + return vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
>>> +}
>>> +
>>> +static void chv3_fb_start_frame(struct chv3_fb *fb, struct chv3_fb_buffer *buf)
>>> +{
>>> + fb->writing_to_a = 1;
>>> + writel(chv3_fb_buffer_dma_addr(buf), fb->iobase + FB_BUFFERA);
>>> + writel(FB_EN_BIT, fb->iobase + FB_EN);
>>> +}
>>> +
>>> +static void chv3_fb_next_frame(struct chv3_fb *fb, struct chv3_fb_buffer *buf)
>>> +{
>>> + u32 reg = fb->writing_to_a ? FB_BUFFERB : FB_BUFFERA;
>>> +
>>> + writel(chv3_fb_buffer_dma_addr(buf), fb->iobase + reg);
>>> +}
>>> +
>>> +static int chv3_fb_start_streaming(struct vb2_queue *q, unsigned int count)
>>> +{
>>> + struct chv3_fb *fb = vb2_get_drv_priv(q);
>>> + struct chv3_fb_buffer *buf;
>>> + unsigned long flags;
>>> +
>>> + fb->sequence = 0;
>>> + writel(fb->pix_fmt.sizeimage, fb->iobase + FB_BUFFERSIZE);
>>> +
>>> + spin_lock_irqsave(&fb->bufs_lock, flags);
>>> + buf = list_first_entry_or_null(&fb->bufs, struct chv3_fb_buffer, link);
>>> + if (buf) {
>>> + chv3_fb_start_frame(fb, buf);
>>> + if (!list_is_last(&buf->link, &fb->bufs))
>>> + chv3_fb_next_frame(fb, list_next_entry(buf, link));
>>> + }
>>> + spin_unlock_irqrestore(&fb->bufs_lock, flags);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static void chv3_fb_stop_streaming(struct vb2_queue *q)
>>> +{
>>> + struct chv3_fb *fb = vb2_get_drv_priv(q);
>>> + struct chv3_fb_buffer *buf;
>>> + unsigned long flags;
>>> +
>>> + writel(0, fb->iobase + FB_EN);
>>> +
>>> + spin_lock_irqsave(&fb->bufs_lock, flags);
>>> + list_for_each_entry(buf, &fb->bufs, link)
>>> + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
>>> + INIT_LIST_HEAD(&fb->bufs);
>>> + spin_unlock_irqrestore(&fb->bufs_lock, flags);
>>> +}
>>> +
>>> +static void chv3_fb_buf_queue(struct vb2_buffer *vb)
>>> +{
>>> + struct chv3_fb *fb = vb2_get_drv_priv(vb->vb2_queue);
>>> + struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>>> + struct chv3_fb_buffer *buf = container_of(v4l2_buf, struct chv3_fb_buffer, vb);
>>> + bool first, second;
>>> + unsigned long flags;
>>> +
>>> + spin_lock_irqsave(&fb->bufs_lock, flags);
>>> + first = list_empty(&fb->bufs);
>>> + second = list_is_singular(&fb->bufs);
>>> + list_add_tail(&buf->link, &fb->bufs);
>>> + if (vb2_is_streaming(vb->vb2_queue)) {
>>> + if (first)
>>> + chv3_fb_start_frame(fb, buf);
>>> + else if (second)
>>> + chv3_fb_next_frame(fb, buf);
>>> + }
>>> + spin_unlock_irqrestore(&fb->bufs_lock, flags);
>>> +}
>>> +
>>> +static const struct vb2_ops chv3_fb_vb2_ops = {
>>> + .queue_setup = chv3_fb_queue_setup,
>>> + .wait_prepare = vb2_ops_wait_prepare,
>>> + .wait_finish = vb2_ops_wait_finish,
>>> + .start_streaming = chv3_fb_start_streaming,
>>> + .stop_streaming = chv3_fb_stop_streaming,
>>> + .buf_queue = chv3_fb_buf_queue,
>>> +};
>>> +
>>> +static int chv3_fb_open(struct file *file)
>>> +{
>>> + struct chv3_fb *fb = video_drvdata(file);
>>> + int res;
>>> +
>>> + mutex_lock(&fb->fb_lock);
>>> + res = v4l2_fh_open(file);
>>> + if (!res) {
>>> + if (v4l2_fh_is_singular_file(file))
>>> + chv3_fb_apply_dv_timings(fb);
>>> + }
>>> + mutex_unlock(&fb->fb_lock);
>>> +
>>> + return res;
>>> +}
>>> +
>>> +static const struct v4l2_file_operations chv3_fb_v4l2_fops = {
>>> + .owner = THIS_MODULE,
>>> + .open = chv3_fb_open,
>>> + .release = vb2_fop_release,
>>> + .unlocked_ioctl = video_ioctl2,
>>> + .mmap = vb2_fop_mmap,
>>> + .poll = vb2_fop_poll,
>>> +};
>>> +
>>> +static void chv3_fb_frame_irq(struct chv3_fb *fb)
>>> +{
>>> + struct chv3_fb_buffer *buf;
>>> +
>>> + spin_lock(&fb->bufs_lock);
>>> +
>>> + buf = list_first_entry_or_null(&fb->bufs, struct chv3_fb_buffer, link);
>>> + if (!buf)
>>> + goto empty;
>>> + list_del(&buf->link);
>>> +
>>> + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, fb->pix_fmt.sizeimage);
>>> + buf->vb.vb2_buf.timestamp = ktime_get_ns();
>>> + buf->vb.sequence = fb->sequence++;
>>> + buf->vb.field = V4L2_FIELD_NONE;
>>> + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
>>> +
>>> + buf = list_first_entry_or_null(&fb->bufs, struct chv3_fb_buffer, link);
>>> + if (buf) {
>>> + fb->writing_to_a = !fb->writing_to_a;
>>> + if (!list_is_last(&buf->link, &fb->bufs))
>>> + chv3_fb_next_frame(fb, list_next_entry(buf, link));
>>> + } else {
>>> + writel(0, fb->iobase + FB_EN);
>>> + }
>>> +empty:
>>> + spin_unlock(&fb->bufs_lock);
>>> +}
>>> +
>>> +static void chv3_fb_error_irq(struct chv3_fb *fb)
>>> +{
>>> + if (vb2_is_streaming(&fb->queue))
>>> + vb2_queue_error(&fb->queue);
>>> +}
>>> +
>>> +static void chv3_fb_resolution_irq(struct chv3_fb *fb)
>>> +{
>>> + static const struct v4l2_event event = {
>>> + .type = V4L2_EVENT_SOURCE_CHANGE,
>>> + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION,
>>> + };
>>> +
>>> + v4l2_event_queue(&fb->vdev, &event);
>>> + chv3_fb_error_irq(fb);
>>> +}
>>> +
>>> +static irqreturn_t chv3_fb_isr(int irq, void *data)
>>> +{
>>> + struct chv3_fb *fb = data;
>>> + unsigned int reg;
>>> +
>>> + reg = readl(fb->iobase_irq + FB_IRQ_CLR);
>>> + if (!reg)
>>> + return IRQ_NONE;
>>> +
>>> + if (reg & FB_IRQ_BUFF0)
>>> + chv3_fb_frame_irq(fb);
>>> + if (reg & FB_IRQ_BUFF1)
>>> + chv3_fb_frame_irq(fb);
>>> + if (reg & FB_IRQ_RESOLUTION)
>>> + chv3_fb_resolution_irq(fb);
>>> + if (reg & FB_IRQ_ERROR) {
>>> + dev_warn(fb->dev, "framebuffer error: 0x%x\n",
>>> + readl(fb->iobase + FB_ERRORSTATUS));
>>> + chv3_fb_error_irq(fb);
>>> + }
>>> +
>>> + writel(reg, fb->iobase_irq + FB_IRQ_CLR);
>>> +
>>> + return IRQ_HANDLED;
>>> +}
>>> +
>>> +static int chv3_fb_check_version(struct chv3_fb *fb)
>>> +{
>>> + u32 version;
>>> +
>>> + version = readl(fb->iobase + FB_VERSION);
>>> + if (version != FB_VERSION_CURRENT) {
>>> + dev_err(fb->dev,
>>> + "wrong framebuffer version: expected %x, got %x\n",
>>> + FB_VERSION_CURRENT, version);
>>> + return -ENODEV;
>>> + }
>>> + return 0;
>>> +}
>>> +
>>> +static void chv3_fb_set_default_format(struct chv3_fb *fb, bool legacy_format)
>>> +{
>>> + struct v4l2_pix_format *pix = &fb->pix_fmt;
>>> +
>>> + if (legacy_format)
>>> + pix->pixelformat = V4L2_PIX_FMT_BGRX32;
>>> + else
>>> + pix->pixelformat = V4L2_PIX_FMT_RGB24;
>>> + pix->field = V4L2_FIELD_NONE;
>>> + pix->colorspace = V4L2_COLORSPACE_SRGB;
>>> +
>>> + chv3_fb_set_format_resolution(fb, DEFAULT_WIDTH, DEFAULT_HEIGHT);
>>> +}
>>> +
>>> +static void chv3_fb_set_default_timings(struct chv3_fb *fb)
>>> +{
>>> + memset(&fb->timings, 0, sizeof(fb->timings));
>>> + fb->timings.type = V4L2_DV_BT_656_1120;
>>> + fb->timings.bt.width = DEFAULT_WIDTH;
>>> + fb->timings.bt.height = DEFAULT_HEIGHT;
>>
>> Wouldn't it be better to say: fb->timings = V4L2_DV_BT_CEA_1920X1080P60;
>
> Yes, that would work.
>
>>
>>> +}
>>> +
>>> +#define notifier_to_fb(nf) container_of(nf, struct chv3_fb, notifier)
>>> +
>>> +static int chv3_fb_async_notify_bound(struct v4l2_async_notifier *notifier,
>>> + struct v4l2_subdev *subdev,
>>> + struct v4l2_async_connection *asc)
>>> +{
>>> + struct chv3_fb *fb = notifier_to_fb(notifier);
>>> + int pad;
>>> +
>>> + pad = media_entity_get_fwnode_pad(&subdev->entity, asc->match.fwnode,
>>> + MEDIA_PAD_FL_SOURCE);
>>> + if (pad < 0)
>>> + return pad;
>>> +
>>> + fb->subdev = subdev;
>>> + fb->subdev_source_pad = pad;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static void chv3_fb_async_notify_unbind(struct v4l2_async_notifier *notifier,
>>> + struct v4l2_subdev *subdev,
>>> + struct v4l2_async_connection *asc)
>>> +{
>>> + struct chv3_fb *fb = notifier_to_fb(notifier);
>>> +
>>> + video_unregister_device(&fb->vdev);
>>
>> Use vb2_video_unregister_device. See documentation in include/media/videobuf2-v4l2.h.
>
> So if I understand the docs correctly, I should just change this line to
> 'vb2_video_unregister_device(&fb->vdev);'?

Yes.

>
>>
>>> +}
>>> +
>>> +static int chv3_fb_async_notify_complete(struct v4l2_async_notifier *notifier)
>>> +{
>>> + struct chv3_fb *fb = notifier_to_fb(notifier);
>>> +
>>> + return video_register_device(&fb->vdev, VFL_TYPE_VIDEO, -1);
>>> +}
>>> +
>>> +static const struct v4l2_async_notifier_operations chv3_fb_async_notify_ops = {
>>> + .bound = chv3_fb_async_notify_bound,
>>> + .unbind = chv3_fb_async_notify_unbind,
>>> + .complete = chv3_fb_async_notify_complete,
>>> +};
>>> +
>>> +static int chv3_fb_fallback_init(struct chv3_fb *fb)
>>> +{
>>> + fb->subdev = NULL;
>>> + fb->subdev_source_pad = 0;
>>> +
>>> + return video_register_device(&fb->vdev, VFL_TYPE_VIDEO, -1);
>>> +}
>>> +
>>> +static int chv3_fb_fwnode_init(struct chv3_fb *fb)
>>> +{
>>> + struct v4l2_async_connection *asc;
>>> + struct fwnode_handle *endpoint;
>>> + int res;
>>> +
>>> + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(fb->dev), NULL);
>>> + if (!endpoint)
>>> + return -EINVAL;
>>> +
>>> + v4l2_async_nf_init(&fb->notifier, &fb->v4l2_dev);
>>> +
>>> + asc = v4l2_async_nf_add_fwnode_remote(&fb->notifier, endpoint,
>>> + struct v4l2_async_connection);
>>> + fwnode_handle_put(endpoint);
>>> +
>>> + if (IS_ERR(asc))
>>> + return PTR_ERR(asc);
>>> +
>>> + fb->notifier.ops = &chv3_fb_async_notify_ops;
>>> + res = v4l2_async_nf_register(&fb->notifier);
>>> + if (res) {
>>> + v4l2_async_nf_cleanup(&fb->notifier);
>>> + return res;
>>> + }
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int chv3_fb_probe(struct platform_device *pdev)
>>> +{
>>> + struct chv3_fb *fb;
>>> + bool legacy_format;
>>> + int res;
>>> + int irq;
>>> +
>>> + fb = devm_kzalloc(&pdev->dev, sizeof(*fb), GFP_KERNEL);
>>> + if (!fb)
>>> + return -ENOMEM;
>>> + fb->dev = &pdev->dev;
>>> + platform_set_drvdata(pdev, fb);
>>> +
>>> + /* map register space */
>>> + fb->iobase = devm_platform_ioremap_resource(pdev, 0);
>>> + if (IS_ERR(fb->iobase))
>>> + return PTR_ERR(fb->iobase);
>>> +
>>> + fb->iobase_irq = devm_platform_ioremap_resource(pdev, 1);
>>> + if (IS_ERR(fb->iobase_irq))
>>> + return PTR_ERR(fb->iobase_irq);
>>> +
>>> + /* check hw version */
>>> + res = chv3_fb_check_version(fb);
>>> + if (res)
>>> + return res;
>>> +
>>> + /* setup interrupts */
>>> + irq = platform_get_irq(pdev, 0);
>>> + if (irq < 0)
>>> + return -ENXIO;
>>> + res = devm_request_irq(&pdev->dev, irq, chv3_fb_isr, 0, DEVICE_NAME, fb);
>>> + if (res)
>>> + return res;
>>> +
>>> + /* initialize v4l2_device */
>>> + res = v4l2_device_register(&pdev->dev, &fb->v4l2_dev);
>>> + if (res)
>>> + return res;
>>> +
>>> + /* initialize vb2 queue */
>>> + fb->queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
>>> + fb->queue.io_modes = VB2_MMAP | VB2_DMABUF;
>>> + fb->queue.dev = &pdev->dev;
>>> + fb->queue.lock = &fb->fb_lock;
>>> + fb->queue.ops = &chv3_fb_vb2_ops;
>>> + fb->queue.mem_ops = &vb2_dma_contig_memops;
>>> + fb->queue.drv_priv = fb;
>>> + fb->queue.buf_struct_size = sizeof(struct chv3_fb_buffer);
>>> + fb->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
>>> + res = vb2_queue_init(&fb->queue);
>>> + if (res)
>>> + goto error;
>>> +
>>> + /* initialize video_device */
>>> + strscpy(fb->vdev.name, DEVICE_NAME, sizeof(fb->vdev.name));
>>> + fb->vdev.fops = &chv3_fb_v4l2_fops;
>>> + fb->vdev.ioctl_ops = &chv3_fb_v4l2_ioctl_ops;
>>> + fb->vdev.lock = &fb->fb_lock;
>>> + fb->vdev.release = video_device_release_empty;
>>> + fb->vdev.device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
>>> + fb->vdev.v4l2_dev = &fb->v4l2_dev;
>>> + fb->vdev.queue = &fb->queue;
>>> + video_set_drvdata(&fb->vdev, fb);
>>> +
>>> + /* read other DT properties */
>>> + legacy_format = device_property_read_bool(&pdev->dev, "google,legacy-format");
>>> +
>>> + if (device_get_named_child_node(&pdev->dev, "port"))
>>> + res = chv3_fb_fwnode_init(fb);
>>> + else
>>> + res = chv3_fb_fallback_init(fb);
>>> + if (res)
>>> + goto error;
>>> +
>>> + /* initialize rest of driver struct */
>>> + INIT_LIST_HEAD(&fb->bufs);
>>> + spin_lock_init(&fb->bufs_lock);
>>> + mutex_init(&fb->fb_lock);
>>> +
>>> + chv3_fb_set_default_format(fb, legacy_format);
>>> + chv3_fb_set_default_timings(fb);
>>> +
>>> + /* initialize hw */
>>> + writel(FB_RESET_BIT, fb->iobase + FB_RESET);
>>> + writel(FB_DATARATE_DOUBLE, fb->iobase + FB_DATARATE);
>>> + writel(FB_PIXELMODE_DOUBLE, fb->iobase + FB_PIXELMODE);
>>> + if (legacy_format)
>>> + writel(FB_DMAFORMAT_8BPC_LEGACY, fb->iobase + FB_DMAFORMAT);
>>> + else
>>> + writel(FB_DMAFORMAT_8BPC, fb->iobase + FB_DMAFORMAT);
>>> +
>>> + writel(FB_IRQ_ALL, fb->iobase_irq + FB_IRQ_MASK);
>>> +
>>> + return 0;
>>> +
>>> +error:
>>> + v4l2_device_unregister(&fb->v4l2_dev);
>>> +
>>> + return res;
>>> +}
>>> +
>>> +static void chv3_fb_remove(struct platform_device *pdev)
>>> +{
>>> + struct chv3_fb *fb = platform_get_drvdata(pdev);
>>> +
>>> + /* disable interrupts */
>>> + writel(0, fb->iobase_irq + FB_IRQ_MASK);
>>> +
>>> + v4l2_async_nf_unregister(&fb->notifier);
>>> + v4l2_async_nf_cleanup(&fb->notifier);
>>> + v4l2_device_unregister(&fb->v4l2_dev);
>>> +}
>>> +
>>> +static const struct of_device_id chv3_fb_match_table[] = {
>>> + { .compatible = "google,chv3-fb" },
>>> + { },
>>> +};
>>> +
>>> +static struct platform_driver chv3_fb_platform_driver = {
>>> + .probe = chv3_fb_probe,
>>> + .remove_new = chv3_fb_remove,
>>> + .driver = {
>>> + .name = DEVICE_NAME,
>>> + .of_match_table = chv3_fb_match_table,
>>> + },
>>> +};
>>> +
>>> +module_platform_driver(chv3_fb_platform_driver);
>>> +
>>> +MODULE_AUTHOR("Paweł Anikiel <[email protected]>");
>>> +MODULE_DESCRIPTION("Google Chameleon v3 video framebuffer driver");
>>> +MODULE_LICENSE("GPL");
>>
>> For the v3, can you also provide the output of 'v4l2-compliance -s'
>> in the cover letter? Make sure you compile v4l2-compliance directly
>> from the git repo https://git.linuxtv.org/v4l-utils.git/ since the version
>> from distros is usually too old.
>
> Okay, I'll do that.

Regards,

Hans

2024-02-28 15:35:12

by Paweł Anikiel

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

On Wed, Feb 28, 2024 at 12:25 PM Hans Verkuil <[email protected]> wrote:
>
> Hi Paweł,
>
> On 21/02/2024 17:02, Paweł Anikiel wrote:
> > 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.
>
> So now we have two query_dv_timings ops: one for video ops, and one
> for pad ops. That's not very maintainable. I would suggest switching
> all current users of the video op over to the pad op.

I agree it would be better if there was only one. However, I have some concerns:
1. Isn't there a problem with backwards compatibility? For example, an
out-of-tree driver is likely to use this callback, which would break.
I'm asking because I'm not familiar with how such API changes are
handled.
2. If I do switch all current users to the pad op, I can't test those
changes. Isn't that a problem?
3. Would I need to get ACK from all the driver maintainers?

2024-02-28 18:10:10

by Rob Herring (Arm)

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

On Wed, Feb 28, 2024 at 02:09:33PM +0100, Paweł Anikiel wrote:
> On Wed, Feb 28, 2024 at 1:18 PM Krzysztof Kozlowski
> <[email protected]> wrote:
> >
> > On 28/02/2024 12:05, Paweł Anikiel wrote:
> > > On Tue, Feb 27, 2024 at 3:29 PM Rob Herring <[email protected]> wrote:
> > >>
> > >> On Mon, Feb 26, 2024 at 11:59:42AM +0100, Paweł Anikiel wrote:
> > >>> On Mon, Feb 26, 2024 at 10:13 AM Krzysztof Kozlowski
> > >>> <[email protected]> wrote:
> > >>>>
> > >>>> On 21/02/2024 17:02, 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 | 160 ++++++++++++++++++
> > >>>>> 1 file changed, 160 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..31025f2d5dcd
> > >>>>> --- /dev/null
> > >>>>> +++ b/Documentation/devicetree/bindings/media/intel,dprx.yaml
> > >>>>> @@ -0,0 +1,160 @@
> > >>>>> +# 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]>
> > >>>>> +
> > >>>>> +description: |
> > >>>>> + 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 IP features a large number of configuration parameters, found at:
> > >>>>> + https://www.intel.com/content/www/us/en/docs/programmable/683273/23-3-20-0-1/sink-parameters.html
> > >>>>> +
> > >>>>> + The following parameters have to be enabled:
> > >>>>> + - Support DisplayPort sink
> > >>>>> + - Enable GPU control
> > >>>>> + The following parameters' values have to be set in the devicetree:
> > >>>>> + - RX maximum link rate
> > >>>>> + - Maximum lane count
> > >>>>> + - Support MST
> > >>>>> + - Max stream count (only if Support MST is enabled)
> > >>>>> +
> > >>>>> +properties:
> > >>>>> + compatible:
> > >>>>> + const: intel,dprx-20.0.1
> > >>>>> +
> > >>>>> + reg:
> > >>>>> + maxItems: 1
> > >>>>> +
> > >>>>> + interrupts:
> > >>>>> + maxItems: 1
> > >>>>> +
> > >>>>> + intel,max-link-rate:
> > >>>>> + $ref: /schemas/types.yaml#/definitions/uint32
> > >>>>> + description: Max link rate configuration parameter
> > >>>>
> > >>>> Please do not duplicate property name in description. It's useless.
> > >>>> Instead explain what is this responsible for.
> > >>>>
> > >>>> Why max-link-rate would differ for the same dprx-20.0.1? And why
> > >>>> standard properties cannot be used?
> > >>>>
> > >>>> Same for all questions below.
> > >>>
> > >>> These four properties are the IP configuration parameters mentioned in
> > >>> the device description. When generating the IP core you can set these
> > >>> parameters, which could make them differ for the same dprx-20.0.1.
> > >>> They are documented in the user guide, for which I also put a link in
> > >>> the description. Is that enough? Or should I also document these
> > >>> parameters here?
> > >>
> > >> Use the standard properties: link-frequencies and data-lanes. Those go
> > >> under the port(s) because they are inheritly per logical link.
> > >
> > > The DP receiver has one input interface (a deserialized DP stream),
> > > and up to four output interfaces (the decoded video streams). The "max
> > > link rate" and "max lane count" parameters only describe the input
> > > interface to the receiver. However, the port(s) I am using here are
> > > for the output streams. They are not affected by those parameters, so
> > > I don't think these properties should go under the output port(s).
> > >
> > > The receiver doesn't have an input port in the DT, because there isn't
> > > any controllable entity on the other side - the deserializer doesn't
> > > have any software interface. Since these standard properties
> > > (link-frequencies and data-lanes) are only defined in
> > > video-interfaces.yaml (which IIUC describes a graph endpoint), I can't
> > > use them directly in the device node.
> >
> > DT describes the hardware, so where does the input come? From something,
> > right? Regardless if you have a driver or not. There is dp-connector
> > binding, if this is physical port.
>
> Yes, it is a physical port. I agree adding a DT node for the physical
> DP input connector would let us add link-frequencies to the input port
> of the receiver.
>
> However, dp-connector seems to be a binding for an output port - it's
> under schemas/display/connector, and DP_PWR can be a power supply only
> for an output port (looking at the dp-pwr-supply property). Also, the
> driver for this binding is a DRM bridge driver (display-connector.c)
> which would not be compatible with a v4l2 (sub)device.

So then we should add 'dp-input-connector' because they are different.
When we haven't defined connectors, properties of the connector have
been shoved in whatever node is associated with a connector like you
have done. That works for a while, but then becomes unmanageable. DP on
USB-C connectors for example.

OTOH, maybe your use here is niche enough to not be worth the trouble.
Depends if we see the need for video input connectors in general.

Rob

2024-02-29 08:03:06

by Hans Verkuil

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

On 28/02/2024 16:34, Paweł Anikiel wrote:
> On Wed, Feb 28, 2024 at 12:25 PM Hans Verkuil <[email protected]> wrote:
>>
>> Hi Paweł,
>>
>> On 21/02/2024 17:02, Paweł Anikiel wrote:
>>> 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.
>>
>> So now we have two query_dv_timings ops: one for video ops, and one
>> for pad ops. That's not very maintainable. I would suggest switching
>> all current users of the video op over to the pad op.
>
> I agree it would be better if there was only one. However, I have some concerns:
> 1. Isn't there a problem with backwards compatibility? For example, an
> out-of-tree driver is likely to use this callback, which would break.
> I'm asking because I'm not familiar with how such API changes are
> handled.

It's out of tree, so they will just have to adapt. That's how life is if
you are not part of the mainline kernel.

> 2. If I do switch all current users to the pad op, I can't test those
> changes. Isn't that a problem?

I can test one or two drivers, but in general I don't expect this to be
a problem.

> 3. Would I need to get ACK from all the driver maintainers?

CC the patches to the maintainers. Generally you will get back Acks from
some but not all maintainers, but that's OK. For changes affecting multiple
drivers you never reach 100% on that. I can review the remainder. The DV
Timings API is my expert area, so that shouldn't be a problem.

A quick grep gives me these subdev drivers that implement it:

adv748x, adv7604, adv7842, tc358743, tda1997x, tvp7002, gs1662.

And these bridge drivers that call the subdevs:

cobalt, rcar-vin, vpif_capture.

The bridge drivers can use the following pad when calling query_dv_timings:

cobalt: ADV76XX_PAD_HDMI_PORT_A
rcar_vin: vin->parallel.sink_pad
vpif_capture: 0

The converted subdev drivers should check if the pad is an input pad.
Ideally it should check if the pad is equal to the current input pad
since most devices can only query the timings for the currently selected
input pad. But some older drivers predate the pad concept and they
ignore the pad value.

Regards,

Hans

2024-02-29 12:08:07

by Hans Verkuil

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

On 2/29/24 12:33, Paweł Anikiel wrote:
> On Thu, Feb 29, 2024 at 9:02 AM Hans Verkuil <[email protected]> wrote:
>>
>> On 28/02/2024 16:34, Paweł Anikiel wrote:
>>> On Wed, Feb 28, 2024 at 12:25 PM Hans Verkuil <[email protected]> wrote:
>>>>
>>>> Hi Paweł,
>>>>
>>>> On 21/02/2024 17:02, Paweł Anikiel wrote:
>>>>> 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.
>>>>
>>>> So now we have two query_dv_timings ops: one for video ops, and one
>>>> for pad ops. That's not very maintainable. I would suggest switching
>>>> all current users of the video op over to the pad op.
>>>
>>> I agree it would be better if there was only one. However, I have some concerns:
>>> 1. Isn't there a problem with backwards compatibility? For example, an
>>> out-of-tree driver is likely to use this callback, which would break.
>>> I'm asking because I'm not familiar with how such API changes are
>>> handled.
>>
>> It's out of tree, so they will just have to adapt. That's how life is if
>> you are not part of the mainline kernel.
>>
>>> 2. If I do switch all current users to the pad op, I can't test those
>>> changes. Isn't that a problem?
>>
>> I can test one or two drivers, but in general I don't expect this to be
>> a problem.
>>
>>> 3. Would I need to get ACK from all the driver maintainers?
>>
>> CC the patches to the maintainers. Generally you will get back Acks from
>> some but not all maintainers, but that's OK. For changes affecting multiple
>> drivers you never reach 100% on that. I can review the remainder. The DV
>> Timings API is my expert area, so that shouldn't be a problem.
>>
>> A quick grep gives me these subdev drivers that implement it:
>>
>> adv748x, adv7604, adv7842, tc358743, tda1997x, tvp7002, gs1662.
>>
>> And these bridge drivers that call the subdevs:
>>
>> cobalt, rcar-vin, vpif_capture.
>>
>> The bridge drivers can use the following pad when calling query_dv_timings:
>>
>> cobalt: ADV76XX_PAD_HDMI_PORT_A
>> rcar_vin: vin->parallel.sink_pad
>> vpif_capture: 0
>>
>> The converted subdev drivers should check if the pad is an input pad.
>> Ideally it should check if the pad is equal to the current input pad
>> since most devices can only query the timings for the currently selected
>> input pad. But some older drivers predate the pad concept and they
>> ignore the pad value.
>
> Thank you for the helpful info. I will switch all these drivers to the
> pad op, then. Would you like me to prepare a separate patchset, or
> should I include the changes in this one?

I think I prefer a separate patchset for this.

Regards,

Hans

2024-02-29 10:26:25

by Paweł Anikiel

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

On Wed, Feb 28, 2024 at 7:10 PM Rob Herring <[email protected]> wrote:
>
> On Wed, Feb 28, 2024 at 02:09:33PM +0100, Paweł Anikiel wrote:
> > On Wed, Feb 28, 2024 at 1:18 PM Krzysztof Kozlowski
> > <[email protected]> wrote:
> > >
> > > On 28/02/2024 12:05, Paweł Anikiel wrote:
> > > > On Tue, Feb 27, 2024 at 3:29 PM Rob Herring <[email protected]> wrote:
> > > >>
> > > >> On Mon, Feb 26, 2024 at 11:59:42AM +0100, Paweł Anikiel wrote:
> > > >>> On Mon, Feb 26, 2024 at 10:13 AM Krzysztof Kozlowski
> > > >>> <[email protected]> wrote:
> > > >>>>
> > > >>>> On 21/02/2024 17:02, 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 | 160 ++++++++++++++++++
> > > >>>>> 1 file changed, 160 insertions(+)
> > > >>>>> create mode 100644 Documentation/devicetree/bindings/media/intel,dprx.yaml
> > > >>>>>
> > > >>>>> diff --git a/Documentation/devicetree/bindings/media/intel,dprxyaml b/Documentation/devicetree/bindings/media/intel,dprx.yaml
> > > >>>>> new file mode 100644
> > > >>>>> index 000000000000..31025f2d5dcd
> > > >>>>> --- /dev/null
> > > >>>>> +++ b/Documentation/devicetree/bindings/media/intel,dprx.yaml
> > > >>>>> @@ -0,0 +1,160 @@
> > > >>>>> +# 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]>
> > > >>>>> +
> > > >>>>> +description: |
> > > >>>>> + 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 IP features a large number of configuration parameters, found at:
> > > >>>>> + https://www.intel.com/content/www/us/en/docs/programmable/683273/23-3-20-0-1/sink-parameters.html
> > > >>>>> +
> > > >>>>> + The following parameters have to be enabled:
> > > >>>>> + - Support DisplayPort sink
> > > >>>>> + - Enable GPU control
> > > >>>>> + The following parameters' values have to be set in the devicetree:
> > > >>>>> + - RX maximum link rate
> > > >>>>> + - Maximum lane count
> > > >>>>> + - Support MST
> > > >>>>> + - Max stream count (only if Support MST is enabled)
> > > >>>>> +
> > > >>>>> +properties:
> > > >>>>> + compatible:
> > > >>>>> + const: intel,dprx-20.0.1
> > > >>>>> +
> > > >>>>> + reg:
> > > >>>>> + maxItems: 1
> > > >>>>> +
> > > >>>>> + interrupts:
> > > >>>>> + maxItems: 1
> > > >>>>> +
> > > >>>>> + intel,max-link-rate:
> > > >>>>> + $ref: /schemas/types.yaml#/definitions/uint32
> > > >>>>> + description: Max link rate configuration parameter
> > > >>>>
> > > >>>> Please do not duplicate property name in description. It's useless.
> > > >>>> Instead explain what is this responsible for.
> > > >>>>
> > > >>>> Why max-link-rate would differ for the same dprx-20.0.1? And why
> > > >>>> standard properties cannot be used?
> > > >>>>
> > > >>>> Same for all questions below.
> > > >>>
> > > >>> These four properties are the IP configuration parameters mentioned in
> > > >>> the device description. When generating the IP core you can set these
> > > >>> parameters, which could make them differ for the same dprx-20.0.1.
> > > >>> They are documented in the user guide, for which I also put a link in
> > > >>> the description. Is that enough? Or should I also document these
> > > >>> parameters here?
> > > >>
> > > >> Use the standard properties: link-frequencies and data-lanes. Those go
> > > >> under the port(s) because they are inheritly per logical link.
> > > >
> > > > The DP receiver has one input interface (a deserialized DP stream),
> > > > and up to four output interfaces (the decoded video streams). The "max
> > > > link rate" and "max lane count" parameters only describe the input
> > > > interface to the receiver. However, the port(s) I am using here are
> > > > for the output streams. They are not affected by those parameters, so
> > > > I don't think these properties should go under the output port(s).
> > > >
> > > > The receiver doesn't have an input port in the DT, because there isn't
> > > > any controllable entity on the other side - the deserializer doesn't
> > > > have any software interface. Since these standard properties
> > > > (link-frequencies and data-lanes) are only defined in
> > > > video-interfaces.yaml (which IIUC describes a graph endpoint), I can't
> > > > use them directly in the device node.
> > >
> > > DT describes the hardware, so where does the input come? From something,
> > > right? Regardless if you have a driver or not. There is dp-connector
> > > binding, if this is physical port.
> >
> > Yes, it is a physical port. I agree adding a DT node for the physical
> > DP input connector would let us add link-frequencies to the input port
> > of the receiver.
> >
> > However, dp-connector seems to be a binding for an output port - it's
> > under schemas/display/connector, and DP_PWR can be a power supply only
> > for an output port (looking at the dp-pwr-supply property). Also, the
> > driver for this binding is a DRM bridge driver (display-connector.c)
> > which would not be compatible with a v4l2 (sub)device.
>
> So then we should add 'dp-input-connector' because they are different.
> When we haven't defined connectors, properties of the connector have
> been shoved in whatever node is associated with a connector like you
> have done. That works for a while, but then becomes unmanageable. DP on
> USB-C connectors for example.
>
> OTOH, maybe your use here is niche enough to not be worth the trouble.
> Depends if we see the need for video input connectors in general.

My use case is a dedicated hardware that runs DP tests of an external
DUT. I can't think of another scenario where we'd need an input DP
port. IMO this is pretty niche, but I'll leave the decision to you

2024-02-29 11:38:33

by Paweł Anikiel

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

On Thu, Feb 29, 2024 at 9:02 AM Hans Verkuil <hverkuil-cisco@xs4allnl> wrote:
>
> On 28/02/2024 16:34, Paweł Anikiel wrote:
> > On Wed, Feb 28, 2024 at 12:25 PM Hans Verkuil <[email protected]> wrote:
> >>
> >> Hi Paweł,
> >>
> >> On 21/02/2024 17:02, Paweł Anikiel wrote:
> >>> 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.
> >>
> >> So now we have two query_dv_timings ops: one for video ops, and one
> >> for pad ops. That's not very maintainable. I would suggest switching
> >> all current users of the video op over to the pad op.
> >
> > I agree it would be better if there was only one. However, I have some concerns:
> > 1. Isn't there a problem with backwards compatibility? For example, an
> > out-of-tree driver is likely to use this callback, which would break.
> > I'm asking because I'm not familiar with how such API changes are
> > handled.
>
> It's out of tree, so they will just have to adapt. That's how life is if
> you are not part of the mainline kernel.
>
> > 2. If I do switch all current users to the pad op, I can't test those
> > changes. Isn't that a problem?
>
> I can test one or two drivers, but in general I don't expect this to be
> a problem.
>
> > 3. Would I need to get ACK from all the driver maintainers?
>
> CC the patches to the maintainers. Generally you will get back Acks from
> some but not all maintainers, but that's OK. For changes affecting multiple
> drivers you never reach 100% on that. I can review the remainder. The DV
> Timings API is my expert area, so that shouldn't be a problem.
>
> A quick grep gives me these subdev drivers that implement it:
>
> adv748x, adv7604, adv7842, tc358743, tda1997x, tvp7002, gs1662.
>
> And these bridge drivers that call the subdevs:
>
> cobalt, rcar-vin, vpif_capture.
>
> The bridge drivers can use the following pad when calling query_dv_timings:
>
> cobalt: ADV76XX_PAD_HDMI_PORT_A
> rcar_vin: vin->parallel.sink_pad
> vpif_capture: 0
>
> The converted subdev drivers should check if the pad is an input pad.
> Ideally it should check if the pad is equal to the current input pad
> since most devices can only query the timings for the currently selected
> input pad. But some older drivers predate the pad concept and they
> ignore the pad value.

Thank you for the helpful info. I will switch all these drivers to the
pad op, then. Would you like me to prepare a separate patchset, or
should I include the changes in this one?

2024-03-01 16:31:43

by Rob Herring (Arm)

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

On Thu, Feb 29, 2024 at 11:25:41AM +0100, Paweł Anikiel wrote:
> On Wed, Feb 28, 2024 at 7:10 PM Rob Herring <[email protected]> wrote:
> >
> > On Wed, Feb 28, 2024 at 02:09:33PM +0100, Paweł Anikiel wrote:
> > > On Wed, Feb 28, 2024 at 1:18 PM Krzysztof Kozlowski
> > > <[email protected]> wrote:
> > > >
> > > > On 28/02/2024 12:05, Paweł Anikiel wrote:
> > > > > On Tue, Feb 27, 2024 at 3:29 PM Rob Herring <[email protected]> wrote:
> > > > >>
> > > > >> On Mon, Feb 26, 2024 at 11:59:42AM +0100, Paweł Anikiel wrote:
> > > > >>> On Mon, Feb 26, 2024 at 10:13 AM Krzysztof Kozlowski
> > > > >>> <[email protected]> wrote:
> > > > >>>>
> > > > >>>> On 21/02/2024 17:02, 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 | 160 ++++++++++++++++++
> > > > >>>>> 1 file changed, 160 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..31025f2d5dcd
> > > > >>>>> --- /dev/null
> > > > >>>>> +++ b/Documentation/devicetree/bindings/media/intel,dprx.yaml
> > > > >>>>> @@ -0,0 +1,160 @@
> > > > >>>>> +# 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]>
> > > > >>>>> +
> > > > >>>>> +description: |
> > > > >>>>> + 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 IP features a large number of configuration parameters, found at:
> > > > >>>>> + https://www.intel.com/content/www/us/en/docs/programmable/683273/23-3-20-0-1/sink-parameters.html
> > > > >>>>> +
> > > > >>>>> + The following parameters have to be enabled:
> > > > >>>>> + - Support DisplayPort sink
> > > > >>>>> + - Enable GPU control
> > > > >>>>> + The following parameters' values have to be set in the devicetree:
> > > > >>>>> + - RX maximum link rate
> > > > >>>>> + - Maximum lane count
> > > > >>>>> + - Support MST
> > > > >>>>> + - Max stream count (only if Support MST is enabled)
> > > > >>>>> +
> > > > >>>>> +properties:
> > > > >>>>> + compatible:
> > > > >>>>> + const: intel,dprx-20.0.1
> > > > >>>>> +
> > > > >>>>> + reg:
> > > > >>>>> + maxItems: 1
> > > > >>>>> +
> > > > >>>>> + interrupts:
> > > > >>>>> + maxItems: 1
> > > > >>>>> +
> > > > >>>>> + intel,max-link-rate:
> > > > >>>>> + $ref: /schemas/types.yaml#/definitions/uint32
> > > > >>>>> + description: Max link rate configuration parameter
> > > > >>>>
> > > > >>>> Please do not duplicate property name in description. It's useless.
> > > > >>>> Instead explain what is this responsible for.
> > > > >>>>
> > > > >>>> Why max-link-rate would differ for the same dprx-20.0.1? And why
> > > > >>>> standard properties cannot be used?
> > > > >>>>
> > > > >>>> Same for all questions below.
> > > > >>>
> > > > >>> These four properties are the IP configuration parameters mentioned in
> > > > >>> the device description. When generating the IP core you can set these
> > > > >>> parameters, which could make them differ for the same dprx-20.0.1.
> > > > >>> They are documented in the user guide, for which I also put a link in
> > > > >>> the description. Is that enough? Or should I also document these
> > > > >>> parameters here?
> > > > >>
> > > > >> Use the standard properties: link-frequencies and data-lanes. Those go
> > > > >> under the port(s) because they are inheritly per logical link.
> > > > >
> > > > > The DP receiver has one input interface (a deserialized DP stream),
> > > > > and up to four output interfaces (the decoded video streams). The "max
> > > > > link rate" and "max lane count" parameters only describe the input
> > > > > interface to the receiver. However, the port(s) I am using here are
> > > > > for the output streams. They are not affected by those parameters, so
> > > > > I don't think these properties should go under the output port(s).
> > > > >
> > > > > The receiver doesn't have an input port in the DT, because there isn't
> > > > > any controllable entity on the other side - the deserializer doesn't
> > > > > have any software interface. Since these standard properties
> > > > > (link-frequencies and data-lanes) are only defined in
> > > > > video-interfaces.yaml (which IIUC describes a graph endpoint), I can't
> > > > > use them directly in the device node.
> > > >
> > > > DT describes the hardware, so where does the input come? From something,
> > > > right? Regardless if you have a driver or not. There is dp-connector
> > > > binding, if this is physical port.
> > >
> > > Yes, it is a physical port. I agree adding a DT node for the physical
> > > DP input connector would let us add link-frequencies to the input port
> > > of the receiver.
> > >
> > > However, dp-connector seems to be a binding for an output port - it's
> > > under schemas/display/connector, and DP_PWR can be a power supply only
> > > for an output port (looking at the dp-pwr-supply property). Also, the
> > > driver for this binding is a DRM bridge driver (display-connector.c)
> > > which would not be compatible with a v4l2 (sub)device.
> >
> > So then we should add 'dp-input-connector' because they are different.
> > When we haven't defined connectors, properties of the connector have
> > been shoved in whatever node is associated with a connector like you
> > have done. That works for a while, but then becomes unmanageable. DP on
> > USB-C connectors for example.
> >
> > OTOH, maybe your use here is niche enough to not be worth the trouble.
> > Depends if we see the need for video input connectors in general.
>
> My use case is a dedicated hardware that runs DP tests of an external
> DUT. I can't think of another scenario where we'd need an input DP
> port. IMO this is pretty niche, but I'll leave the decision to you

Your device is niche, but a video capture/in device is not that niche.
After all, Smart TVs run Linux. They have video in connectors. Maybe not
DP though. It's conceivable someone could make a "Smart Monitor" I
suppose.

Rob



2024-04-23 17:01:18

by Paweł Anikiel

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

Hi, sorry for the long delay

On Mon, Feb 26, 2024 at 10:10 AM Krzysztof Kozlowski
<[email protected]> wrote:
>
> On 21/02/2024 17:02, 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]>
>
> ...
>
> > +
> > + 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.
>
> Why is this a property of board DTS? Can't the input streams change
> depending on the usage? Who defines the incoming stream format?

The input streams are defined by this hardware module - there are
specific input lines for red, green, blue channels, h/v sync, etc.
However, there are two variants of this module which behave
differently - one has 60 input lines and outputs BGRX32, and the other
one has 192 input lines and outputs RGB24.

The "incoming 32-bit padded mode" referred to an internal bus inside
of the module, which I realize isn't very meaningful. Instead, I think
it would be better to have two compatibles, one for each variant of
the module.