* [PATCH v3 00/10] Add Chameleon v3 video support
@ 2024-05-07 15:54 Paweł Anikiel
2024-05-07 15:54 ` [PATCH v3 01/10] media: Add Chameleon v3 video interface driver Paweł Anikiel
` (10 more replies)
0 siblings, 11 replies; 26+ messages in thread
From: Paweł Anikiel @ 2024-05-07 15:54 UTC (permalink / raw)
To: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
robh+dt, tzimmermann
Cc: devicetree, dri-devel, linux-kernel, linux-media,
chromeos-krk-upstreaming, Paweł Anikiel
Google Chameleon v3 is a testing device capable of emulating multiple
DisplayPort monitors, used for testing purposes. It is based on an Arria
10 SoCFPGA. This patchset adds V4L2 drivers for two IP blocks used in the
device's FPGA: the Chameleon v3 video interface, 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 video interface 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.
This patchset depends on changes merged into the linux-media tree at:
git://linuxtv.org/hverkuil/media_tree.git tags/br-v6.10d
Here is the output of `v4l2-compliance -s` run on a Chameleon v3 for
/dev/video0 (no attached subdevice):
```
v4l2-compliance 1.27.0-5204, 32 bits, 32-bit time_t
v4l2-compliance SHA: dd049328e528 2024-04-29 13:40:09
Compliance test for chv3-video device /dev/video0:
Driver Info:
Driver name : chv3-video
Card type : Chameleon v3 video
Bus info : platform:c0060500.video
Driver version : 6.9.0
Capabilities : 0x84200001
Video Capture
Streaming
Extended Pix Format
Device Capabilities
Device Caps : 0x04200001
Video Capture
Streaming
Extended Pix Format
Required ioctls:
test VIDIOC_QUERYCAP: OK
test invalid ioctls: OK
Allow for multiple opens:
test second /dev/video0 open: OK
test VIDIOC_QUERYCAP: OK
test VIDIOC_G/S_PRIORITY: OK
test for unlimited opens: OK
Debug ioctls:
test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported)
test VIDIOC_LOG_STATUS: OK (Not Supported)
Input ioctls:
test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
test VIDIOC_ENUMAUDIO: OK (Not Supported)
test VIDIOC_G/S/ENUMINPUT: OK
test VIDIOC_G/S_AUDIO: OK (Not Supported)
Inputs: 1 Audio Inputs: 0 Tuners: 0
Output ioctls:
test VIDIOC_G/S_MODULATOR: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_ENUMAUDOUT: OK (Not Supported)
test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
test VIDIOC_G/S_AUDOUT: OK (Not Supported)
Outputs: 0 Audio Outputs: 0 Modulators: 0
Input/Output configuration ioctls:
test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK
test VIDIOC_DV_TIMINGS_CAP: OK
test VIDIOC_G/S_EDID: OK (Not Supported)
Control ioctls (Input 0):
test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK
test VIDIOC_QUERYCTRL: OK
test VIDIOC_G/S_CTRL: OK
test VIDIOC_G/S/TRY_EXT_CTRLS: OK
test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK
test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
Standard Controls: 2 Private Controls: 0
Format ioctls (Input 0):
test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
test VIDIOC_G/S_PARM: OK (Not Supported)
test VIDIOC_G_FBUF: OK (Not Supported)
test VIDIOC_G_FMT: OK
test VIDIOC_TRY_FMT: OK
test VIDIOC_S_FMT: OK
test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
test Cropping: OK (Not Supported)
test Composing: OK (Not Supported)
test Scaling: OK (Not Supported)
Codec ioctls (Input 0):
test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
test VIDIOC_G_ENC_INDEX: OK (Not Supported)
test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)
Buffer ioctls (Input 0):
test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK
test CREATE_BUFS maximum buffers: OK
test VIDIOC_REMOVE_BUFS: OK
test VIDIOC_EXPBUF: OK
test Requests: OK (Not Supported)
test TIME32/64: OK
Test input 0:
Streaming ioctls:
test read/write: OK (Not Supported)
test blocking wait: OK
test MMAP (no poll): OK
test MMAP (select): OK
test MMAP (epoll): OK
test USERPTR (no poll): OK (Not Supported)
test USERPTR (select): OK (Not Supported)
test DMABUF: Cannot test, specify --expbuf-device
Total for chv3-video device /dev/video0: 55, Succeeded: 55, Failed: 0, Warnings: 0
```
Here is the output of `v4l2-compliance -s` run on a Chameleon v3 for
/dev/video4 (attached subdevice):
```
v4l2-compliance 1.27.0-5204, 32 bits, 32-bit time_t
v4l2-compliance SHA: dd049328e528 2024-04-29 13:40:09
Compliance test for chv3-video device /dev/video4:
Driver Info:
Driver name : chv3-video
Card type : Chameleon v3 video
Bus info : platform:c0060600.video
Driver version : 6.9.0
Capabilities : 0x84200001
Video Capture
Streaming
Extended Pix Format
Device Capabilities
Device Caps : 0x04200001
Video Capture
Streaming
Extended Pix Format
Required ioctls:
test VIDIOC_QUERYCAP: OK
test invalid ioctls: OK
Allow for multiple opens:
test second /dev/video4 open: OK
test VIDIOC_QUERYCAP: OK
test VIDIOC_G/S_PRIORITY: OK
test for unlimited opens: OK
Debug ioctls:
test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported)
test VIDIOC_LOG_STATUS: OK (Not Supported)
Input ioctls:
test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
test VIDIOC_ENUMAUDIO: OK (Not Supported)
test VIDIOC_G/S/ENUMINPUT: OK
test VIDIOC_G/S_AUDIO: OK (Not Supported)
Inputs: 1 Audio Inputs: 0 Tuners: 0
Output ioctls:
test VIDIOC_G/S_MODULATOR: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_ENUMAUDOUT: OK (Not Supported)
test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
test VIDIOC_G/S_AUDOUT: OK (Not Supported)
Outputs: 0 Audio Outputs: 0 Modulators: 0
Input/Output configuration ioctls:
test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK
test VIDIOC_DV_TIMINGS_CAP: OK
test VIDIOC_G/S_EDID: OK
Control ioctls (Input 0):
test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK
test VIDIOC_QUERYCTRL: OK
test VIDIOC_G/S_CTRL: OK
test VIDIOC_G/S/TRY_EXT_CTRLS: OK
test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK
test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
Standard Controls: 2 Private Controls: 0
Format ioctls (Input 0):
test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
test VIDIOC_G/S_PARM: OK (Not Supported)
test VIDIOC_G_FBUF: OK (Not Supported)
test VIDIOC_G_FMT: OK
test VIDIOC_TRY_FMT: OK
test VIDIOC_S_FMT: OK
test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
test Cropping: OK (Not Supported)
test Composing: OK (Not Supported)
test Scaling: OK (Not Supported)
Codec ioctls (Input 0):
test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
test VIDIOC_G_ENC_INDEX: OK (Not Supported)
test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)
Buffer ioctls (Input 0):
test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK
test CREATE_BUFS maximum buffers: OK
test VIDIOC_REMOVE_BUFS: OK
test VIDIOC_EXPBUF: OK
test Requests: OK (Not Supported)
test TIME32/64: OK
Test input 0:
Streaming ioctls:
test read/write: OK (Not Supported)
test blocking wait: OK
test MMAP (no poll): OK
test MMAP (select): OK
test MMAP (epoll): OK
test USERPTR (no poll): OK (Not Supported)
test USERPTR (select): OK (Not Supported)
test DMABUF: Cannot test, specify --expbuf-device
Total for chv3-video device /dev/video4: 55, Succeeded: 55, Failed: 0, Warnings: 0
```
v3 changes:
- Send v4l2-subdev API changes as a separate patchset
- Drop chameleonv3/ directory
- Change capture device name from "framebuffer" to "video interface"
- Set sensible min and max dv timing caps
- Set pixelclock to htotal * vtotal * 24Hz (we can't detect the actual value)
- Remove enum_framesizes
- Use v4l2_match_dv_timings()
- Add V4L2_CID_DV_RX_POWER_PRESENT control
- Use V4L2_DV_BT_CEA_1920X1080P60 as default timing
- Use vb2_video_unregister_device()
- Move subdev pad initialization to probe
- Change subdev entity function to MEDIA_ENT_F_DV_DECODER
- Drop dprx 'port' property and always use 'ports' instead
- Remove legacy-format property and use multiple compats
- Cleanup notifier only in non-fallback mode
- Cleanup subdev entity using media_entity_cleanup()
- Increase HPD pulse length to 500ms (see comment in dprx_set_edid())
- Pull HPD low before updating EDID
- Add a DisplayPort media bus type
- Move receiver properties to port endpoint (data-lanes, link-frequencies)
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 (10):
media: Add Chameleon v3 video interface 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: dt-bindings: video-interfaces: Support DisplayPort MST
media: v4l2-mediabus: Add support for DisplayPort media bus
media: intel: Add Displayport RX IP driver
media: dt-bindings: Add Chameleon v3 video interface
media: dt-bindings: Add Intel Displayport RX IP
ARM: dts: chameleonv3: Add video device nodes
.../bindings/media/google,chv3-video.yaml | 64 +
.../devicetree/bindings/media/intel,dprx.yaml | 172 ++
.../bindings/media/video-interfaces.yaml | 7 +
.../socfpga/socfpga_arria10_chameleonv3.dts | 194 ++
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 | 13 +
drivers/media/platform/google/Makefile | 3 +
drivers/media/platform/google/chv3-video.c | 891 +++++++
drivers/media/platform/intel/Kconfig | 12 +
drivers/media/platform/intel/Makefile | 1 +
drivers/media/platform/intel/intel-dprx.c | 2283 +++++++++++++++++
drivers/media/v4l2-core/v4l2-fwnode.c | 38 +
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/dt-bindings/media/video-interfaces.h | 2 +
include/linux/crc-dp.h | 10 +
include/media/v4l2-fwnode.h | 5 +
include/media/v4l2-mediabus.h | 17 +
lib/Kconfig | 8 +
lib/Makefile | 1 +
lib/crc-dp.c | 78 +
25 files changed, 4053 insertions(+), 304 deletions(-)
create mode 100644 Documentation/devicetree/bindings/media/google,chv3-video.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/chv3-video.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.45.0.rc1.225.g2a3ae87e7f-goog
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v3 01/10] media: Add Chameleon v3 video interface driver
2024-05-07 15:54 [PATCH v3 00/10] Add Chameleon v3 video support Paweł Anikiel
@ 2024-05-07 15:54 ` Paweł Anikiel
2024-06-03 7:57 ` Hans Verkuil
2024-05-07 15:54 ` [PATCH v3 02/10] drm/dp_mst: Move DRM-independent structures to separate header Paweł Anikiel
` (9 subsequent siblings)
10 siblings, 1 reply; 26+ messages in thread
From: Paweł Anikiel @ 2024-05-07 15:54 UTC (permalink / raw)
To: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
robh+dt, tzimmermann
Cc: devicetree, dri-devel, linux-kernel, linux-media,
chromeos-krk-upstreaming, Paweł Anikiel
Add v4l2 driver for the video interface present on the Google
Chameleon v3. The Chameleon v3 uses the video interface to capture
a single video source from a given HDMI or DP connector and write
the resulting frames to memory.
Signed-off-by: Paweł Anikiel <panikiel@google.com>
---
drivers/media/platform/Kconfig | 1 +
drivers/media/platform/Makefile | 1 +
drivers/media/platform/google/Kconfig | 13 +
drivers/media/platform/google/Makefile | 3 +
drivers/media/platform/google/chv3-video.c | 891 +++++++++++++++++++++
5 files changed, 909 insertions(+)
create mode 100644 drivers/media/platform/google/Kconfig
create mode 100644 drivers/media/platform/google/Makefile
create mode 100644 drivers/media/platform/google/chv3-video.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..9674a4c12e2d
--- /dev/null
+++ b/drivers/media/platform/google/Kconfig
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config VIDEO_CHAMELEONV3
+ tristate "Google Chameleon v3 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 video interface to
+ capture a single video source from a given HDMI or DP connector
+ and write the resulting frames to memory.
diff --git a/drivers/media/platform/google/Makefile b/drivers/media/platform/google/Makefile
new file mode 100644
index 000000000000..cff06486244c
--- /dev/null
+++ b/drivers/media/platform/google/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-$(CONFIG_VIDEO_CHAMELEONV3) += chv3-video.o
diff --git a/drivers/media/platform/google/chv3-video.c b/drivers/media/platform/google/chv3-video.c
new file mode 100644
index 000000000000..6e782484abaf
--- /dev/null
+++ b/drivers/media/platform/google/chv3-video.c
@@ -0,0 +1,891 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2023-2024 Google LLC.
+ * Author: Paweł Anikiel <panikiel@google.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/v4l2-dv-timings.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-video"
+
+#define VIDEO_EN 0x00
+#define VIDEO_EN_BIT BIT(0)
+#define VIDEO_HEIGHT 0x04
+#define VIDEO_WIDTH 0x08
+#define VIDEO_BUFFERA 0x0c
+#define VIDEO_BUFFERB 0x10
+#define VIDEO_BUFFERSIZE 0x14
+#define VIDEO_RESET 0x18
+#define VIDEO_RESET_BIT BIT(0)
+#define VIDEO_ERRORSTATUS 0x1c
+#define VIDEO_IOCOLOR 0x20
+#define VIDEO_DATARATE 0x24
+#define VIDEO_DATARATE_SINGLE 0x0
+#define VIDEO_DATARATE_DOUBLE 0x1
+#define VIDEO_PIXELMODE 0x28
+#define VIDEO_PIXELMODE_SINGLE 0x0
+#define VIDEO_PIXELMODE_DOUBLE 0x1
+#define VIDEO_SYNCPOLARITY 0x2c
+#define VIDEO_DMAFORMAT 0x30
+#define VIDEO_DMAFORMAT_8BPC 0x0
+#define VIDEO_DMAFORMAT_10BPC_UPPER 0x1
+#define VIDEO_DMAFORMAT_10BPC_LOWER 0x2
+#define VIDEO_DMAFORMAT_12BPC_UPPER 0x3
+#define VIDEO_DMAFORMAT_12BPC_LOWER 0x4
+#define VIDEO_DMAFORMAT_16BPC 0x5
+#define VIDEO_DMAFORMAT_RAW 0x6
+#define VIDEO_DMAFORMAT_8BPC_PAD 0x7
+#define VIDEO_VERSION 0x34
+#define VIDEO_VERSION_CURRENT 0xc0fb0001
+
+#define VIDEO_IRQ_MASK 0x8
+#define VIDEO_IRQ_CLR 0xc
+#define VIDEO_IRQ_ALL 0xf
+#define VIDEO_IRQ_BUFF0 BIT(0)
+#define VIDEO_IRQ_BUFF1 BIT(1)
+#define VIDEO_IRQ_RESOLUTION BIT(2)
+#define VIDEO_IRQ_ERROR BIT(3)
+
+struct chv3_video {
+ 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;
+ u32 bytes_per_pixel;
+
+ struct v4l2_ctrl_handler ctrl_handler;
+ 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 video_lock;
+};
+
+struct chv3_video_buffer {
+ struct vb2_v4l2_buffer vb;
+ struct list_head link;
+};
+
+struct chv3_video_config {
+ u32 pixelformat;
+ u32 bytes_per_pixel;
+ u32 dmaformat;
+};
+
+static void chv3_video_set_format_resolution(struct chv3_video *video, u32 width, u32 height)
+{
+ video->pix_fmt.width = width;
+ video->pix_fmt.height = height;
+ video->pix_fmt.bytesperline = width * video->bytes_per_pixel;
+ video->pix_fmt.sizeimage = video->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_video_query_dv_timings_fallback(struct chv3_video *video,
+ struct v4l2_dv_timings *timings)
+{
+ u32 width, height;
+
+ width = readl(video->iobase + VIDEO_WIDTH);
+ height = readl(video->iobase + VIDEO_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;
+ timings->bt.pixelclock = width * height * 24;
+
+ return 0;
+}
+
+static int chv3_video_query_dv_timings(struct chv3_video *video, struct v4l2_dv_timings *timings)
+{
+ if (video->subdev) {
+ return v4l2_subdev_call(video->subdev, pad, query_dv_timings,
+ video->subdev_source_pad, timings);
+ } else {
+ return chv3_video_query_dv_timings_fallback(video, timings);
+ }
+}
+
+static const struct v4l2_dv_timings_cap chv3_video_fallback_dv_timings_cap = {
+ .type = V4L2_DV_BT_656_1120,
+ .bt = {
+ .min_width = 640,
+ .max_width = 7680,
+ .min_height = 480,
+ .max_height = 4320,
+ .min_pixelclock = 25000000,
+ .max_pixelclock = 1080000000,
+ .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_video_enum_dv_timings_fallback(struct chv3_video *video,
+ struct v4l2_enum_dv_timings *timings)
+{
+ return v4l2_enum_dv_timings_cap(timings, &chv3_video_fallback_dv_timings_cap,
+ NULL, NULL);
+}
+
+static int chv3_video_dv_timings_cap_fallback(struct chv3_video *video,
+ struct v4l2_dv_timings_cap *cap)
+{
+ *cap = chv3_video_fallback_dv_timings_cap;
+
+ return 0;
+}
+
+static void chv3_video_apply_dv_timings(struct chv3_video *video)
+{
+ struct v4l2_dv_timings timings;
+ int res;
+
+ res = chv3_video_query_dv_timings(video, &timings);
+ if (res)
+ return;
+
+ video->timings = timings;
+ chv3_video_set_format_resolution(video, timings.bt.width, timings.bt.height);
+}
+
+static int chv3_video_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_video_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt)
+{
+ struct chv3_video *video = video_drvdata(file);
+
+ fmt->fmt.pix = video->pix_fmt;
+
+ return 0;
+}
+
+static int chv3_video_enum_fmt_vid_cap(struct file *file, void *fh, struct v4l2_fmtdesc *fmt)
+{
+ struct chv3_video *video = video_drvdata(file);
+
+ if (fmt->index != 0)
+ return -EINVAL;
+
+ fmt->flags = 0;
+ fmt->pixelformat = video->pix_fmt.pixelformat;
+
+ return 0;
+}
+
+static int chv3_video_g_input(struct file *file, void *fh, unsigned int *index)
+{
+ *index = 0;
+
+ return 0;
+}
+
+static int chv3_video_s_input(struct file *file, void *fh, unsigned int index)
+{
+ if (index != 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int chv3_video_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_video_g_edid(struct file *file, void *fh, struct v4l2_edid *edid)
+{
+ struct chv3_video *video = video_drvdata(file);
+ int res;
+
+ if (!video->subdev)
+ return -ENOTTY;
+
+ if (edid->pad != 0)
+ return -EINVAL;
+
+ edid->pad = video->subdev_source_pad;
+ res = v4l2_subdev_call(video->subdev, pad, get_edid, edid);
+ edid->pad = 0;
+
+ return res;
+}
+
+static int chv3_video_s_edid(struct file *file, void *fh, struct v4l2_edid *edid)
+{
+ struct chv3_video *video = video_drvdata(file);
+ int res;
+
+ if (!video->subdev)
+ return -ENOTTY;
+
+ if (edid->pad != 0)
+ return -EINVAL;
+
+ edid->pad = video->subdev_source_pad;
+ res = v4l2_subdev_call(video->subdev, pad, set_edid, edid);
+ edid->pad = 0;
+
+ return res;
+}
+
+static int chv3_video_s_dv_timings(struct file *file, void *fh, struct v4l2_dv_timings *timings)
+{
+ struct chv3_video *video = video_drvdata(file);
+
+ if (v4l2_match_dv_timings(&video->timings, timings, 0, false))
+ return 0;
+
+ if (vb2_is_busy(&video->queue))
+ return -EBUSY;
+
+ if (!v4l2_valid_dv_timings(timings, &chv3_video_fallback_dv_timings_cap, NULL, NULL))
+ return -ERANGE;
+
+ video->timings = *timings;
+ chv3_video_set_format_resolution(video, timings->bt.width, timings->bt.height);
+
+ return 0;
+}
+
+static int chv3_video_g_dv_timings(struct file *file, void *fh, struct v4l2_dv_timings *timings)
+{
+ struct chv3_video *video = video_drvdata(file);
+
+ *timings = video->timings;
+ return 0;
+}
+
+static int chv3_video_vidioc_query_dv_timings(struct file *file, void *fh,
+ struct v4l2_dv_timings *timings)
+{
+ struct chv3_video *video = video_drvdata(file);
+
+ return chv3_video_query_dv_timings(video, timings);
+}
+
+static int chv3_video_enum_dv_timings(struct file *file, void *fh,
+ struct v4l2_enum_dv_timings *timings)
+{
+ struct chv3_video *video = video_drvdata(file);
+ int res;
+
+ if (timings->pad != 0)
+ return -EINVAL;
+
+ if (video->subdev) {
+ timings->pad = video->subdev_source_pad;
+ res = v4l2_subdev_call(video->subdev, pad, enum_dv_timings, timings);
+ timings->pad = 0;
+ return res;
+ } else {
+ return chv3_video_enum_dv_timings_fallback(video, timings);
+ }
+}
+
+static int chv3_video_dv_timings_cap(struct file *file, void *fh, struct v4l2_dv_timings_cap *cap)
+{
+ struct chv3_video *video = video_drvdata(file);
+ int res;
+
+ if (cap->pad != 0)
+ return -EINVAL;
+
+ if (video->subdev) {
+ cap->pad = video->subdev_source_pad;
+ res = v4l2_subdev_call(video->subdev, pad, dv_timings_cap, cap);
+ cap->pad = 0;
+ return res;
+ } else {
+ return chv3_video_dv_timings_cap_fallback(video, cap);
+ }
+}
+
+static int chv3_video_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_video_v4l2_ioctl_ops = {
+ .vidioc_querycap = chv3_video_querycap,
+
+ .vidioc_enum_fmt_vid_cap = chv3_video_enum_fmt_vid_cap,
+ .vidioc_g_fmt_vid_cap = chv3_video_g_fmt_vid_cap,
+ .vidioc_s_fmt_vid_cap = chv3_video_g_fmt_vid_cap,
+ .vidioc_try_fmt_vid_cap = chv3_video_g_fmt_vid_cap,
+
+ .vidioc_enum_input = chv3_video_enum_input,
+ .vidioc_g_input = chv3_video_g_input,
+ .vidioc_s_input = chv3_video_s_input,
+ .vidioc_g_edid = chv3_video_g_edid,
+ .vidioc_s_edid = chv3_video_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_video_s_dv_timings,
+ .vidioc_g_dv_timings = chv3_video_g_dv_timings,
+ .vidioc_query_dv_timings = chv3_video_vidioc_query_dv_timings,
+ .vidioc_enum_dv_timings = chv3_video_enum_dv_timings,
+ .vidioc_dv_timings_cap = chv3_video_dv_timings_cap,
+
+ .vidioc_subscribe_event = chv3_video_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static int chv3_video_queue_setup(struct vb2_queue *q,
+ unsigned int *nbuffers, unsigned int *nplanes,
+ unsigned int sizes[], struct device *alloc_devs[])
+{
+ struct chv3_video *video = vb2_get_drv_priv(q);
+
+ if (*nplanes) {
+ if (sizes[0] < video->pix_fmt.sizeimage)
+ return -EINVAL;
+ return 0;
+ }
+ *nplanes = 1;
+ sizes[0] = video->pix_fmt.sizeimage;
+
+ return 0;
+}
+
+/*
+ * There are two address registers: BUFFERA and BUFFERB. The device
+ * 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_video_buffer_dma_addr(struct chv3_video_buffer *buf)
+{
+ return vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
+}
+
+static void chv3_video_start_frame(struct chv3_video *video, struct chv3_video_buffer *buf)
+{
+ video->writing_to_a = 1;
+ writel(chv3_video_buffer_dma_addr(buf), video->iobase + VIDEO_BUFFERA);
+ writel(VIDEO_EN_BIT, video->iobase + VIDEO_EN);
+}
+
+static void chv3_video_next_frame(struct chv3_video *video, struct chv3_video_buffer *buf)
+{
+ u32 reg = video->writing_to_a ? VIDEO_BUFFERB : VIDEO_BUFFERA;
+
+ writel(chv3_video_buffer_dma_addr(buf), video->iobase + reg);
+}
+
+static int chv3_video_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct chv3_video *video = vb2_get_drv_priv(q);
+ struct chv3_video_buffer *buf;
+ unsigned long flags;
+
+ video->sequence = 0;
+ writel(video->pix_fmt.sizeimage, video->iobase + VIDEO_BUFFERSIZE);
+
+ spin_lock_irqsave(&video->bufs_lock, flags);
+ buf = list_first_entry_or_null(&video->bufs, struct chv3_video_buffer, link);
+ if (buf) {
+ chv3_video_start_frame(video, buf);
+ if (!list_is_last(&buf->link, &video->bufs))
+ chv3_video_next_frame(video, list_next_entry(buf, link));
+ }
+ spin_unlock_irqrestore(&video->bufs_lock, flags);
+
+ return 0;
+}
+
+static void chv3_video_stop_streaming(struct vb2_queue *q)
+{
+ struct chv3_video *video = vb2_get_drv_priv(q);
+ struct chv3_video_buffer *buf;
+ unsigned long flags;
+
+ writel(0, video->iobase + VIDEO_EN);
+
+ spin_lock_irqsave(&video->bufs_lock, flags);
+ list_for_each_entry(buf, &video->bufs, link)
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ INIT_LIST_HEAD(&video->bufs);
+ spin_unlock_irqrestore(&video->bufs_lock, flags);
+}
+
+static void chv3_video_buf_queue(struct vb2_buffer *vb)
+{
+ struct chv3_video *video = vb2_get_drv_priv(vb->vb2_queue);
+ struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
+ struct chv3_video_buffer *buf = container_of(v4l2_buf, struct chv3_video_buffer, vb);
+ bool first, second;
+ unsigned long flags;
+
+ spin_lock_irqsave(&video->bufs_lock, flags);
+ first = list_empty(&video->bufs);
+ second = list_is_singular(&video->bufs);
+ list_add_tail(&buf->link, &video->bufs);
+ if (vb2_is_streaming(vb->vb2_queue)) {
+ if (first)
+ chv3_video_start_frame(video, buf);
+ else if (second)
+ chv3_video_next_frame(video, buf);
+ }
+ spin_unlock_irqrestore(&video->bufs_lock, flags);
+}
+
+static const struct vb2_ops chv3_video_vb2_ops = {
+ .queue_setup = chv3_video_queue_setup,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .start_streaming = chv3_video_start_streaming,
+ .stop_streaming = chv3_video_stop_streaming,
+ .buf_queue = chv3_video_buf_queue,
+};
+
+static int chv3_video_open(struct file *file)
+{
+ struct chv3_video *video = video_drvdata(file);
+ int res;
+
+ mutex_lock(&video->video_lock);
+ res = v4l2_fh_open(file);
+ if (!res) {
+ if (v4l2_fh_is_singular_file(file))
+ chv3_video_apply_dv_timings(video);
+ }
+ mutex_unlock(&video->video_lock);
+
+ return res;
+}
+
+static const struct v4l2_file_operations chv3_video_v4l2_fops = {
+ .owner = THIS_MODULE,
+ .open = chv3_video_open,
+ .release = vb2_fop_release,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = vb2_fop_mmap,
+ .poll = vb2_fop_poll,
+};
+
+static void chv3_video_frame_irq(struct chv3_video *video)
+{
+ struct chv3_video_buffer *buf;
+
+ spin_lock(&video->bufs_lock);
+
+ buf = list_first_entry_or_null(&video->bufs, struct chv3_video_buffer, link);
+ if (!buf)
+ goto empty;
+ list_del(&buf->link);
+
+ vb2_set_plane_payload(&buf->vb.vb2_buf, 0, video->pix_fmt.sizeimage);
+ buf->vb.vb2_buf.timestamp = ktime_get_ns();
+ buf->vb.sequence = video->sequence++;
+ buf->vb.field = V4L2_FIELD_NONE;
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
+
+ buf = list_first_entry_or_null(&video->bufs, struct chv3_video_buffer, link);
+ if (buf) {
+ video->writing_to_a = !video->writing_to_a;
+ if (!list_is_last(&buf->link, &video->bufs))
+ chv3_video_next_frame(video, list_next_entry(buf, link));
+ } else {
+ writel(0, video->iobase + VIDEO_EN);
+ }
+empty:
+ spin_unlock(&video->bufs_lock);
+}
+
+static void chv3_video_error_irq(struct chv3_video *video)
+{
+ if (vb2_is_streaming(&video->queue))
+ vb2_queue_error(&video->queue);
+}
+
+static void chv3_video_resolution_irq(struct chv3_video *video)
+{
+ static const struct v4l2_event event = {
+ .type = V4L2_EVENT_SOURCE_CHANGE,
+ .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION,
+ };
+
+ v4l2_event_queue(&video->vdev, &event);
+ chv3_video_error_irq(video);
+}
+
+static irqreturn_t chv3_video_isr(int irq, void *data)
+{
+ struct chv3_video *video = data;
+ unsigned int reg;
+
+ reg = readl(video->iobase_irq + VIDEO_IRQ_CLR);
+ if (!reg)
+ return IRQ_NONE;
+
+ if (reg & VIDEO_IRQ_BUFF0)
+ chv3_video_frame_irq(video);
+ if (reg & VIDEO_IRQ_BUFF1)
+ chv3_video_frame_irq(video);
+ if (reg & VIDEO_IRQ_RESOLUTION)
+ chv3_video_resolution_irq(video);
+ if (reg & VIDEO_IRQ_ERROR) {
+ dev_warn(video->dev, "error: 0x%x\n",
+ readl(video->iobase + VIDEO_ERRORSTATUS));
+ chv3_video_error_irq(video);
+ }
+
+ writel(reg, video->iobase_irq + VIDEO_IRQ_CLR);
+
+ return IRQ_HANDLED;
+}
+
+static int chv3_video_check_version(struct chv3_video *video)
+{
+ u32 version;
+
+ version = readl(video->iobase + VIDEO_VERSION);
+ if (version != VIDEO_VERSION_CURRENT) {
+ dev_err(video->dev,
+ "wrong hw version: expected %x, got %x\n",
+ VIDEO_VERSION_CURRENT, version);
+ return -ENODEV;
+ }
+ return 0;
+}
+
+static void chv3_video_init_timings_and_format(struct chv3_video *video,
+ const struct chv3_video_config *config)
+{
+ struct v4l2_pix_format *pix = &video->pix_fmt;
+ struct v4l2_dv_timings timings = V4L2_DV_BT_CEA_1920X1080P60;
+
+ video->timings = timings;
+ video->bytes_per_pixel = config->bytes_per_pixel;
+
+ pix->pixelformat = config->pixelformat;
+ pix->field = V4L2_FIELD_NONE;
+ pix->colorspace = V4L2_COLORSPACE_SRGB;
+ chv3_video_set_format_resolution(video, timings.bt.width, timings.bt.height);
+}
+
+#define notifier_to_video(nf) container_of(nf, struct chv3_video, notifier)
+
+static int chv3_video_async_notify_bound(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_connection *asc)
+{
+ struct chv3_video *video = notifier_to_video(notifier);
+ int pad;
+
+ pad = media_entity_get_fwnode_pad(&subdev->entity, asc->match.fwnode,
+ MEDIA_PAD_FL_SOURCE);
+ if (pad < 0)
+ return pad;
+
+ video->subdev = subdev;
+ video->subdev_source_pad = pad;
+
+ video->v4l2_dev.ctrl_handler = subdev->ctrl_handler;
+
+ return 0;
+}
+
+static void chv3_video_async_notify_unbind(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_connection *asc)
+{
+ struct chv3_video *video = notifier_to_video(notifier);
+
+ vb2_video_unregister_device(&video->vdev);
+}
+
+static int chv3_video_async_notify_complete(struct v4l2_async_notifier *notifier)
+{
+ struct chv3_video *video = notifier_to_video(notifier);
+
+ return video_register_device(&video->vdev, VFL_TYPE_VIDEO, -1);
+}
+
+static const struct v4l2_async_notifier_operations chv3_video_async_notify_ops = {
+ .bound = chv3_video_async_notify_bound,
+ .unbind = chv3_video_async_notify_unbind,
+ .complete = chv3_video_async_notify_complete,
+};
+
+static int chv3_video_fallback_init(struct chv3_video *video)
+{
+ int res;
+
+ video->subdev = NULL;
+ video->subdev_source_pad = 0;
+
+ v4l2_ctrl_handler_init(&video->ctrl_handler, 1);
+ v4l2_ctrl_new_std(&video->ctrl_handler, NULL,
+ V4L2_CID_DV_RX_POWER_PRESENT, 0, 1, 0, 0);
+ res = video->ctrl_handler.error;
+ if (res)
+ goto handler_free;
+
+ video->v4l2_dev.ctrl_handler = &video->ctrl_handler;
+
+ res = video_register_device(&video->vdev, VFL_TYPE_VIDEO, -1);
+ if (res)
+ goto handler_free;
+
+ return 0;
+
+handler_free:
+ v4l2_ctrl_handler_free(&video->ctrl_handler);
+
+ return res;
+}
+
+static int chv3_video_fwnode_init(struct chv3_video *video)
+{
+ struct v4l2_async_connection *asc;
+ struct fwnode_handle *endpoint;
+ int res;
+
+ endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(video->dev), NULL);
+ if (!endpoint)
+ return -EINVAL;
+
+ v4l2_async_nf_init(&video->notifier, &video->v4l2_dev);
+
+ asc = v4l2_async_nf_add_fwnode_remote(&video->notifier, endpoint,
+ struct v4l2_async_connection);
+ fwnode_handle_put(endpoint);
+
+ if (IS_ERR(asc))
+ return PTR_ERR(asc);
+
+ video->notifier.ops = &chv3_video_async_notify_ops;
+ res = v4l2_async_nf_register(&video->notifier);
+ if (res) {
+ v4l2_async_nf_cleanup(&video->notifier);
+ return res;
+ }
+
+ return 0;
+}
+
+static int chv3_video_probe(struct platform_device *pdev)
+{
+ struct chv3_video *video;
+ const struct chv3_video_config *config;
+ int res;
+ int irq;
+
+ video = devm_kzalloc(&pdev->dev, sizeof(*video), GFP_KERNEL);
+ if (!video)
+ return -ENOMEM;
+ video->dev = &pdev->dev;
+ platform_set_drvdata(pdev, video);
+
+ config = device_get_match_data(video->dev);
+
+ /* map register space */
+ video->iobase = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(video->iobase))
+ return PTR_ERR(video->iobase);
+
+ video->iobase_irq = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(video->iobase_irq))
+ return PTR_ERR(video->iobase_irq);
+
+ /* check hw version */
+ res = chv3_video_check_version(video);
+ 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_video_isr, 0, DEVICE_NAME, video);
+ if (res)
+ return res;
+
+ /* initialize v4l2_device */
+ res = v4l2_device_register(&pdev->dev, &video->v4l2_dev);
+ if (res)
+ return res;
+
+ /* initialize vb2 queue */
+ video->queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ video->queue.io_modes = VB2_MMAP | VB2_DMABUF;
+ video->queue.dev = &pdev->dev;
+ video->queue.lock = &video->video_lock;
+ video->queue.ops = &chv3_video_vb2_ops;
+ video->queue.mem_ops = &vb2_dma_contig_memops;
+ video->queue.drv_priv = video;
+ video->queue.buf_struct_size = sizeof(struct chv3_video_buffer);
+ video->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ res = vb2_queue_init(&video->queue);
+ if (res)
+ goto error;
+
+ /* initialize video_device */
+ strscpy(video->vdev.name, DEVICE_NAME, sizeof(video->vdev.name));
+ video->vdev.fops = &chv3_video_v4l2_fops;
+ video->vdev.ioctl_ops = &chv3_video_v4l2_ioctl_ops;
+ video->vdev.lock = &video->video_lock;
+ video->vdev.release = video_device_release_empty;
+ video->vdev.device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
+ video->vdev.v4l2_dev = &video->v4l2_dev;
+ video->vdev.queue = &video->queue;
+ video_set_drvdata(&video->vdev, video);
+
+ if (device_get_named_child_node(&pdev->dev, "port"))
+ res = chv3_video_fwnode_init(video);
+ else
+ res = chv3_video_fallback_init(video);
+ if (res)
+ goto error;
+
+ /* initialize rest of driver struct */
+ INIT_LIST_HEAD(&video->bufs);
+ spin_lock_init(&video->bufs_lock);
+ mutex_init(&video->video_lock);
+
+ chv3_video_init_timings_and_format(video, config);
+
+ /* initialize hw */
+ writel(VIDEO_RESET_BIT, video->iobase + VIDEO_RESET);
+ writel(VIDEO_DATARATE_DOUBLE, video->iobase + VIDEO_DATARATE);
+ writel(VIDEO_PIXELMODE_DOUBLE, video->iobase + VIDEO_PIXELMODE);
+ writel(config->dmaformat, video->iobase + VIDEO_DMAFORMAT);
+
+ writel(VIDEO_IRQ_ALL, video->iobase_irq + VIDEO_IRQ_MASK);
+
+ return 0;
+
+error:
+ v4l2_device_unregister(&video->v4l2_dev);
+
+ return res;
+}
+
+static void chv3_video_remove(struct platform_device *pdev)
+{
+ struct chv3_video *video = platform_get_drvdata(pdev);
+
+ /* disable interrupts */
+ writel(0, video->iobase_irq + VIDEO_IRQ_MASK);
+
+ if (video->subdev) {
+ /* notifier is initialized only in non-fallback mode */
+ v4l2_async_nf_unregister(&video->notifier);
+ v4l2_async_nf_cleanup(&video->notifier);
+ } else {
+ /* ctrl handler is initialized only in fallback mode */
+ v4l2_ctrl_handler_free(&video->ctrl_handler);
+ }
+
+ v4l2_device_unregister(&video->v4l2_dev);
+}
+
+static const struct chv3_video_config chv3_video_it = {
+ .pixelformat = V4L2_PIX_FMT_BGRX32,
+ .bytes_per_pixel = 4,
+ .dmaformat = VIDEO_DMAFORMAT_8BPC_PAD,
+};
+
+static const struct chv3_video_config chv3_video_dp = {
+ .pixelformat = V4L2_PIX_FMT_RGB24,
+ .bytes_per_pixel = 3,
+ .dmaformat = VIDEO_DMAFORMAT_8BPC,
+};
+
+static const struct of_device_id chv3_video_match_table[] = {
+ { .compatible = "google,chv3-video-it-1.0", .data = &chv3_video_it },
+ { .compatible = "google,chv3-video-dp-1.0", .data = &chv3_video_dp },
+ { },
+};
+
+static struct platform_driver chv3_video_platform_driver = {
+ .probe = chv3_video_probe,
+ .remove_new = chv3_video_remove,
+ .driver = {
+ .name = DEVICE_NAME,
+ .of_match_table = chv3_video_match_table,
+ },
+};
+
+module_platform_driver(chv3_video_platform_driver);
+
+MODULE_AUTHOR("Paweł Anikiel <panikiel@google.com>");
+MODULE_DESCRIPTION("Google Chameleon v3 video interface driver");
+MODULE_LICENSE("GPL");
--
2.45.0.rc1.225.g2a3ae87e7f-goog
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH v3 02/10] drm/dp_mst: Move DRM-independent structures to separate header
2024-05-07 15:54 [PATCH v3 00/10] Add Chameleon v3 video support Paweł Anikiel
2024-05-07 15:54 ` [PATCH v3 01/10] media: Add Chameleon v3 video interface driver Paweł Anikiel
@ 2024-05-07 15:54 ` Paweł Anikiel
2024-05-07 15:54 ` [PATCH v3 03/10] lib: Move DisplayPort CRC functions to common lib Paweł Anikiel
` (8 subsequent siblings)
10 siblings, 0 replies; 26+ messages in thread
From: Paweł Anikiel @ 2024-05-07 15:54 UTC (permalink / raw)
To: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
robh+dt, tzimmermann
Cc: devicetree, dri-devel, linux-kernel, linux-media,
chromeos-krk-upstreaming, Paweł Anikiel
Move structures describing MST sideband messages into a separate header
so that non-DRM code can use them.
Signed-off-by: Paweł Anikiel <panikiel@google.com>
---
include/drm/display/drm_dp_mst.h | 238 ++++++++++++++++++++++++
include/drm/display/drm_dp_mst_helper.h | 232 +----------------------
2 files changed, 239 insertions(+), 231 deletions(-)
create mode 100644 include/drm/display/drm_dp_mst.h
diff --git a/include/drm/display/drm_dp_mst.h b/include/drm/display/drm_dp_mst.h
new file mode 100644
index 000000000000..4e398bfd3ee3
--- /dev/null
+++ b/include/drm/display/drm_dp_mst.h
@@ -0,0 +1,238 @@
+/* SPDX-License-Identifier: MIT */
+
+#ifndef _DRM_DP_MST_H_
+#define _DRM_DP_MST_H_
+
+#include <linux/types.h>
+
+struct drm_dp_nak_reply {
+ u8 guid[16];
+ u8 reason;
+ u8 nak_data;
+};
+
+struct drm_dp_link_address_ack_reply {
+ u8 guid[16];
+ u8 nports;
+ struct drm_dp_link_addr_reply_port {
+ bool input_port;
+ u8 peer_device_type;
+ u8 port_number;
+ bool mcs;
+ bool ddps;
+ bool legacy_device_plug_status;
+ u8 dpcd_revision;
+ u8 peer_guid[16];
+ u8 num_sdp_streams;
+ u8 num_sdp_stream_sinks;
+ } ports[16];
+};
+
+struct drm_dp_remote_dpcd_read_ack_reply {
+ u8 port_number;
+ u8 num_bytes;
+ u8 bytes[255];
+};
+
+struct drm_dp_remote_dpcd_write_ack_reply {
+ u8 port_number;
+};
+
+struct drm_dp_remote_dpcd_write_nak_reply {
+ u8 port_number;
+ u8 reason;
+ u8 bytes_written_before_failure;
+};
+
+struct drm_dp_remote_i2c_read_ack_reply {
+ u8 port_number;
+ u8 num_bytes;
+ u8 bytes[255];
+};
+
+struct drm_dp_remote_i2c_read_nak_reply {
+ u8 port_number;
+ u8 nak_reason;
+ u8 i2c_nak_transaction;
+};
+
+struct drm_dp_remote_i2c_write_ack_reply {
+ u8 port_number;
+};
+
+struct drm_dp_query_stream_enc_status_ack_reply {
+ /* Bit[23:16]- Stream Id */
+ u8 stream_id;
+
+ /* Bit[15]- Signed */
+ bool reply_signed;
+
+ /* Bit[10:8]- Stream Output Sink Type */
+ bool unauthorizable_device_present;
+ bool legacy_device_present;
+ bool query_capable_device_present;
+
+ /* Bit[12:11]- Stream Output CP Type */
+ bool hdcp_1x_device_present;
+ bool hdcp_2x_device_present;
+
+ /* Bit[4]- Stream Authentication */
+ bool auth_completed;
+
+ /* Bit[3]- Stream Encryption */
+ bool encryption_enabled;
+
+ /* Bit[2]- Stream Repeater Function Present */
+ bool repeater_present;
+
+ /* Bit[1:0]- Stream State */
+ u8 state;
+};
+
+#define DRM_DP_MAX_SDP_STREAMS 16
+struct drm_dp_allocate_payload {
+ u8 port_number;
+ u8 number_sdp_streams;
+ u8 vcpi;
+ u16 pbn;
+ u8 sdp_stream_sink[DRM_DP_MAX_SDP_STREAMS];
+};
+
+struct drm_dp_allocate_payload_ack_reply {
+ u8 port_number;
+ u8 vcpi;
+ u16 allocated_pbn;
+};
+
+struct drm_dp_connection_status_notify {
+ u8 guid[16];
+ u8 port_number;
+ bool legacy_device_plug_status;
+ bool displayport_device_plug_status;
+ bool message_capability_status;
+ bool input_port;
+ u8 peer_device_type;
+};
+
+struct drm_dp_remote_dpcd_read {
+ u8 port_number;
+ u32 dpcd_address;
+ u8 num_bytes;
+};
+
+struct drm_dp_remote_dpcd_write {
+ u8 port_number;
+ u32 dpcd_address;
+ u8 num_bytes;
+ u8 *bytes;
+};
+
+#define DP_REMOTE_I2C_READ_MAX_TRANSACTIONS 4
+struct drm_dp_remote_i2c_read {
+ u8 num_transactions;
+ u8 port_number;
+ struct drm_dp_remote_i2c_read_tx {
+ u8 i2c_dev_id;
+ u8 num_bytes;
+ u8 *bytes;
+ u8 no_stop_bit;
+ u8 i2c_transaction_delay;
+ } transactions[DP_REMOTE_I2C_READ_MAX_TRANSACTIONS];
+ u8 read_i2c_device_id;
+ u8 num_bytes_read;
+};
+
+struct drm_dp_remote_i2c_write {
+ u8 port_number;
+ u8 write_i2c_device_id;
+ u8 num_bytes;
+ u8 *bytes;
+};
+
+struct drm_dp_query_stream_enc_status {
+ u8 stream_id;
+ u8 client_id[7]; /* 56-bit nonce */
+ u8 stream_event;
+ bool valid_stream_event;
+ u8 stream_behavior;
+ u8 valid_stream_behavior;
+};
+
+/* this covers ENUM_RESOURCES, POWER_DOWN_PHY, POWER_UP_PHY */
+struct drm_dp_port_number_req {
+ u8 port_number;
+};
+
+struct drm_dp_enum_path_resources_ack_reply {
+ u8 port_number;
+ bool fec_capable;
+ u16 full_payload_bw_number;
+ u16 avail_payload_bw_number;
+};
+
+/* covers POWER_DOWN_PHY, POWER_UP_PHY */
+struct drm_dp_port_number_rep {
+ u8 port_number;
+};
+
+struct drm_dp_query_payload {
+ u8 port_number;
+ u8 vcpi;
+};
+
+struct drm_dp_resource_status_notify {
+ u8 port_number;
+ u8 guid[16];
+ u16 available_pbn;
+};
+
+struct drm_dp_query_payload_ack_reply {
+ u8 port_number;
+ u16 allocated_pbn;
+};
+
+struct drm_dp_sideband_msg_req_body {
+ u8 req_type;
+ union ack_req {
+ struct drm_dp_connection_status_notify conn_stat;
+ struct drm_dp_port_number_req port_num;
+ struct drm_dp_resource_status_notify resource_stat;
+
+ struct drm_dp_query_payload query_payload;
+ struct drm_dp_allocate_payload allocate_payload;
+
+ struct drm_dp_remote_dpcd_read dpcd_read;
+ struct drm_dp_remote_dpcd_write dpcd_write;
+
+ struct drm_dp_remote_i2c_read i2c_read;
+ struct drm_dp_remote_i2c_write i2c_write;
+
+ struct drm_dp_query_stream_enc_status enc_status;
+ } u;
+};
+
+struct drm_dp_sideband_msg_reply_body {
+ u8 reply_type;
+ u8 req_type;
+ union ack_replies {
+ struct drm_dp_nak_reply nak;
+ struct drm_dp_link_address_ack_reply link_addr;
+ struct drm_dp_port_number_rep port_number;
+
+ struct drm_dp_enum_path_resources_ack_reply path_resources;
+ struct drm_dp_allocate_payload_ack_reply allocate_payload;
+ struct drm_dp_query_payload_ack_reply query_payload;
+
+ struct drm_dp_remote_dpcd_read_ack_reply remote_dpcd_read_ack;
+ struct drm_dp_remote_dpcd_write_ack_reply remote_dpcd_write_ack;
+ struct drm_dp_remote_dpcd_write_nak_reply remote_dpcd_write_nack;
+
+ struct drm_dp_remote_i2c_read_ack_reply remote_i2c_read_ack;
+ struct drm_dp_remote_i2c_read_nak_reply remote_i2c_read_nack;
+ struct drm_dp_remote_i2c_write_ack_reply remote_i2c_write_ack;
+
+ struct drm_dp_query_stream_enc_status_ack_reply enc_status;
+ } u;
+};
+
+#endif
diff --git a/include/drm/display/drm_dp_mst_helper.h b/include/drm/display/drm_dp_mst_helper.h
index 9b19d8bd520a..61add6f6accd 100644
--- a/include/drm/display/drm_dp_mst_helper.h
+++ b/include/drm/display/drm_dp_mst_helper.h
@@ -23,6 +23,7 @@
#define _DRM_DP_MST_HELPER_H_
#include <linux/types.h>
+#include <drm/display/drm_dp_mst.h>
#include <drm/display/drm_dp_helper.h>
#include <drm/drm_atomic.h>
#include <drm/drm_fixed.h>
@@ -248,237 +249,6 @@ struct drm_dp_mst_branch {
u8 guid[16];
};
-
-struct drm_dp_nak_reply {
- u8 guid[16];
- u8 reason;
- u8 nak_data;
-};
-
-struct drm_dp_link_address_ack_reply {
- u8 guid[16];
- u8 nports;
- struct drm_dp_link_addr_reply_port {
- bool input_port;
- u8 peer_device_type;
- u8 port_number;
- bool mcs;
- bool ddps;
- bool legacy_device_plug_status;
- u8 dpcd_revision;
- u8 peer_guid[16];
- u8 num_sdp_streams;
- u8 num_sdp_stream_sinks;
- } ports[16];
-};
-
-struct drm_dp_remote_dpcd_read_ack_reply {
- u8 port_number;
- u8 num_bytes;
- u8 bytes[255];
-};
-
-struct drm_dp_remote_dpcd_write_ack_reply {
- u8 port_number;
-};
-
-struct drm_dp_remote_dpcd_write_nak_reply {
- u8 port_number;
- u8 reason;
- u8 bytes_written_before_failure;
-};
-
-struct drm_dp_remote_i2c_read_ack_reply {
- u8 port_number;
- u8 num_bytes;
- u8 bytes[255];
-};
-
-struct drm_dp_remote_i2c_read_nak_reply {
- u8 port_number;
- u8 nak_reason;
- u8 i2c_nak_transaction;
-};
-
-struct drm_dp_remote_i2c_write_ack_reply {
- u8 port_number;
-};
-
-struct drm_dp_query_stream_enc_status_ack_reply {
- /* Bit[23:16]- Stream Id */
- u8 stream_id;
-
- /* Bit[15]- Signed */
- bool reply_signed;
-
- /* Bit[10:8]- Stream Output Sink Type */
- bool unauthorizable_device_present;
- bool legacy_device_present;
- bool query_capable_device_present;
-
- /* Bit[12:11]- Stream Output CP Type */
- bool hdcp_1x_device_present;
- bool hdcp_2x_device_present;
-
- /* Bit[4]- Stream Authentication */
- bool auth_completed;
-
- /* Bit[3]- Stream Encryption */
- bool encryption_enabled;
-
- /* Bit[2]- Stream Repeater Function Present */
- bool repeater_present;
-
- /* Bit[1:0]- Stream State */
- u8 state;
-};
-
-#define DRM_DP_MAX_SDP_STREAMS 16
-struct drm_dp_allocate_payload {
- u8 port_number;
- u8 number_sdp_streams;
- u8 vcpi;
- u16 pbn;
- u8 sdp_stream_sink[DRM_DP_MAX_SDP_STREAMS];
-};
-
-struct drm_dp_allocate_payload_ack_reply {
- u8 port_number;
- u8 vcpi;
- u16 allocated_pbn;
-};
-
-struct drm_dp_connection_status_notify {
- u8 guid[16];
- u8 port_number;
- bool legacy_device_plug_status;
- bool displayport_device_plug_status;
- bool message_capability_status;
- bool input_port;
- u8 peer_device_type;
-};
-
-struct drm_dp_remote_dpcd_read {
- u8 port_number;
- u32 dpcd_address;
- u8 num_bytes;
-};
-
-struct drm_dp_remote_dpcd_write {
- u8 port_number;
- u32 dpcd_address;
- u8 num_bytes;
- u8 *bytes;
-};
-
-#define DP_REMOTE_I2C_READ_MAX_TRANSACTIONS 4
-struct drm_dp_remote_i2c_read {
- u8 num_transactions;
- u8 port_number;
- struct drm_dp_remote_i2c_read_tx {
- u8 i2c_dev_id;
- u8 num_bytes;
- u8 *bytes;
- u8 no_stop_bit;
- u8 i2c_transaction_delay;
- } transactions[DP_REMOTE_I2C_READ_MAX_TRANSACTIONS];
- u8 read_i2c_device_id;
- u8 num_bytes_read;
-};
-
-struct drm_dp_remote_i2c_write {
- u8 port_number;
- u8 write_i2c_device_id;
- u8 num_bytes;
- u8 *bytes;
-};
-
-struct drm_dp_query_stream_enc_status {
- u8 stream_id;
- u8 client_id[7]; /* 56-bit nonce */
- u8 stream_event;
- bool valid_stream_event;
- u8 stream_behavior;
- u8 valid_stream_behavior;
-};
-
-/* this covers ENUM_RESOURCES, POWER_DOWN_PHY, POWER_UP_PHY */
-struct drm_dp_port_number_req {
- u8 port_number;
-};
-
-struct drm_dp_enum_path_resources_ack_reply {
- u8 port_number;
- bool fec_capable;
- u16 full_payload_bw_number;
- u16 avail_payload_bw_number;
-};
-
-/* covers POWER_DOWN_PHY, POWER_UP_PHY */
-struct drm_dp_port_number_rep {
- u8 port_number;
-};
-
-struct drm_dp_query_payload {
- u8 port_number;
- u8 vcpi;
-};
-
-struct drm_dp_resource_status_notify {
- u8 port_number;
- u8 guid[16];
- u16 available_pbn;
-};
-
-struct drm_dp_query_payload_ack_reply {
- u8 port_number;
- u16 allocated_pbn;
-};
-
-struct drm_dp_sideband_msg_req_body {
- u8 req_type;
- union ack_req {
- struct drm_dp_connection_status_notify conn_stat;
- struct drm_dp_port_number_req port_num;
- struct drm_dp_resource_status_notify resource_stat;
-
- struct drm_dp_query_payload query_payload;
- struct drm_dp_allocate_payload allocate_payload;
-
- struct drm_dp_remote_dpcd_read dpcd_read;
- struct drm_dp_remote_dpcd_write dpcd_write;
-
- struct drm_dp_remote_i2c_read i2c_read;
- struct drm_dp_remote_i2c_write i2c_write;
-
- struct drm_dp_query_stream_enc_status enc_status;
- } u;
-};
-
-struct drm_dp_sideband_msg_reply_body {
- u8 reply_type;
- u8 req_type;
- union ack_replies {
- struct drm_dp_nak_reply nak;
- struct drm_dp_link_address_ack_reply link_addr;
- struct drm_dp_port_number_rep port_number;
-
- struct drm_dp_enum_path_resources_ack_reply path_resources;
- struct drm_dp_allocate_payload_ack_reply allocate_payload;
- struct drm_dp_query_payload_ack_reply query_payload;
-
- struct drm_dp_remote_dpcd_read_ack_reply remote_dpcd_read_ack;
- struct drm_dp_remote_dpcd_write_ack_reply remote_dpcd_write_ack;
- struct drm_dp_remote_dpcd_write_nak_reply remote_dpcd_write_nack;
-
- struct drm_dp_remote_i2c_read_ack_reply remote_i2c_read_ack;
- struct drm_dp_remote_i2c_read_nak_reply remote_i2c_read_nack;
- struct drm_dp_remote_i2c_write_ack_reply remote_i2c_write_ack;
-
- struct drm_dp_query_stream_enc_status_ack_reply enc_status;
- } u;
-};
-
/* msg is queued to be put into a slot */
#define DRM_DP_SIDEBAND_TX_QUEUED 0
/* msg has started transmitting on a slot - still on msgq */
--
2.45.0.rc1.225.g2a3ae87e7f-goog
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH v3 03/10] lib: Move DisplayPort CRC functions to common lib
2024-05-07 15:54 [PATCH v3 00/10] Add Chameleon v3 video support Paweł Anikiel
2024-05-07 15:54 ` [PATCH v3 01/10] media: Add Chameleon v3 video interface driver Paweł Anikiel
2024-05-07 15:54 ` [PATCH v3 02/10] drm/dp_mst: Move DRM-independent structures to separate header Paweł Anikiel
@ 2024-05-07 15:54 ` Paweł Anikiel
2024-05-07 15:54 ` [PATCH v3 04/10] drm/display: Add mask definitions for DP_PAYLOAD_ALLOCATE_* registers Paweł Anikiel
` (7 subsequent siblings)
10 siblings, 0 replies; 26+ messages in thread
From: Paweł Anikiel @ 2024-05-07 15:54 UTC (permalink / raw)
To: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
robh+dt, tzimmermann
Cc: devicetree, dri-devel, linux-kernel, linux-media,
chromeos-krk-upstreaming, Paweł Anikiel
The CRC functions found in drivers/gpu/drm/display/drm_dp_mst_topology.c
may be useful for other non-DRM code that deals with DisplayPort, e.g.
v4l2 drivers for DP receivers. Move these functions to /lib.
Signed-off-by: Paweł Anikiel <panikiel@google.com>
---
drivers/gpu/drm/display/Kconfig | 1 +
drivers/gpu/drm/display/drm_dp_mst_topology.c | 76 ++----------------
include/linux/crc-dp.h | 10 +++
lib/Kconfig | 8 ++
lib/Makefile | 1 +
lib/crc-dp.c | 78 +++++++++++++++++++
6 files changed, 103 insertions(+), 71 deletions(-)
create mode 100644 include/linux/crc-dp.h
create mode 100644 lib/crc-dp.c
diff --git a/drivers/gpu/drm/display/Kconfig b/drivers/gpu/drm/display/Kconfig
index c0f56888c328..eda19645201d 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 03d528209426..54ba98d3bc6f 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 4557bb8a5256..d2836dacf10d 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -168,6 +168,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 ffc6b2341b45..82edf655036b 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -186,6 +186,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.45.0.rc1.225.g2a3ae87e7f-goog
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH v3 04/10] drm/display: Add mask definitions for DP_PAYLOAD_ALLOCATE_* registers
2024-05-07 15:54 [PATCH v3 00/10] Add Chameleon v3 video support Paweł Anikiel
` (2 preceding siblings ...)
2024-05-07 15:54 ` [PATCH v3 03/10] lib: Move DisplayPort CRC functions to common lib Paweł Anikiel
@ 2024-05-07 15:54 ` Paweł Anikiel
2024-05-07 15:54 ` [PATCH v3 05/10] media: dt-bindings: video-interfaces: Support DisplayPort MST Paweł Anikiel
` (6 subsequent siblings)
10 siblings, 0 replies; 26+ messages in thread
From: Paweł Anikiel @ 2024-05-07 15:54 UTC (permalink / raw)
To: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
robh+dt, tzimmermann
Cc: devicetree, dri-devel, linux-kernel, linux-media,
chromeos-krk-upstreaming, Paweł Anikiel
Each of these registers contains a single value, but not the entire
8 bits:
DP_PAYLOAD_ALLOCATE_SET - Bit 7 Reserved
DP_PAYLOAD_ALLOCATE_START_TIME_SLOT - Bits 7:6 Reserved
DP_PAYLOAD_ALLOCATE_TIME_SLOT_COUNT - Bits 7:6 Reserved
Add definitions to properly mask off values read from these registers.
Signed-off-by: Paweł Anikiel <panikiel@google.com>
---
include/drm/display/drm_dp.h | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/include/drm/display/drm_dp.h b/include/drm/display/drm_dp.h
index 4891bd916d26..1c397a5f8fc5 100644
--- a/include/drm/display/drm_dp.h
+++ b/include/drm/display/drm_dp.h
@@ -734,8 +734,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.45.0.rc1.225.g2a3ae87e7f-goog
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH v3 05/10] media: dt-bindings: video-interfaces: Support DisplayPort MST
2024-05-07 15:54 [PATCH v3 00/10] Add Chameleon v3 video support Paweł Anikiel
` (3 preceding siblings ...)
2024-05-07 15:54 ` [PATCH v3 04/10] drm/display: Add mask definitions for DP_PAYLOAD_ALLOCATE_* registers Paweł Anikiel
@ 2024-05-07 15:54 ` Paweł Anikiel
2024-05-10 21:16 ` Rob Herring
2024-05-13 14:56 ` Rob Herring (Arm)
2024-05-07 15:54 ` [PATCH v3 06/10] media: v4l2-mediabus: Add support for DisplayPort media bus Paweł Anikiel
` (5 subsequent siblings)
10 siblings, 2 replies; 26+ messages in thread
From: Paweł Anikiel @ 2024-05-07 15:54 UTC (permalink / raw)
To: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
robh+dt, tzimmermann
Cc: devicetree, dri-devel, linux-kernel, linux-media,
chromeos-krk-upstreaming, Paweł Anikiel
Add a DisplayPort bus type and a multi-stream-support property
indicating whether the interface supports MST.
Signed-off-by: Paweł Anikiel <panikiel@google.com>
---
.../devicetree/bindings/media/video-interfaces.yaml | 7 +++++++
include/dt-bindings/media/video-interfaces.h | 2 ++
2 files changed, 9 insertions(+)
diff --git a/Documentation/devicetree/bindings/media/video-interfaces.yaml b/Documentation/devicetree/bindings/media/video-interfaces.yaml
index 26e3e7d7c67b..7bf3a2c09a5b 100644
--- a/Documentation/devicetree/bindings/media/video-interfaces.yaml
+++ b/Documentation/devicetree/bindings/media/video-interfaces.yaml
@@ -94,6 +94,7 @@ properties:
- 5 # Parallel
- 6 # BT.656
- 7 # DPI
+ - 8 # DisplayPort
description:
Data bus type.
@@ -217,4 +218,10 @@ properties:
Whether the clock signal is used as clock (0) or strobe (1). Used with
CCP2, for instance.
+ multi-stream-support:
+ type: boolean
+ description:
+ Support transport of multiple independent streams. Used for
+ DisplayPort MST-capable interfaces.
+
additionalProperties: true
diff --git a/include/dt-bindings/media/video-interfaces.h b/include/dt-bindings/media/video-interfaces.h
index 68ac4e05e37f..b236806f4482 100644
--- a/include/dt-bindings/media/video-interfaces.h
+++ b/include/dt-bindings/media/video-interfaces.h
@@ -12,5 +12,7 @@
#define MEDIA_BUS_TYPE_CSI2_DPHY 4
#define MEDIA_BUS_TYPE_PARALLEL 5
#define MEDIA_BUS_TYPE_BT656 6
+#define MEDIA_BUS_TYPE_DPI 7
+#define MEDIA_BUS_TYPE_DISPLAYPORT 8
#endif /* __DT_BINDINGS_MEDIA_VIDEO_INTERFACES_H__ */
--
2.45.0.rc1.225.g2a3ae87e7f-goog
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH v3 06/10] media: v4l2-mediabus: Add support for DisplayPort media bus
2024-05-07 15:54 [PATCH v3 00/10] Add Chameleon v3 video support Paweł Anikiel
` (4 preceding siblings ...)
2024-05-07 15:54 ` [PATCH v3 05/10] media: dt-bindings: video-interfaces: Support DisplayPort MST Paweł Anikiel
@ 2024-05-07 15:54 ` Paweł Anikiel
2024-05-07 15:54 ` [PATCH v3 07/10] media: intel: Add Displayport RX IP driver Paweł Anikiel
` (4 subsequent siblings)
10 siblings, 0 replies; 26+ messages in thread
From: Paweł Anikiel @ 2024-05-07 15:54 UTC (permalink / raw)
To: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
robh+dt, tzimmermann
Cc: devicetree, dri-devel, linux-kernel, linux-media,
chromeos-krk-upstreaming, Paweł Anikiel
Add new definitions, a config struct, and a parser for the DisplayPort
media bus.
Signed-off-by: Paweł Anikiel <panikiel@google.com>
---
drivers/media/v4l2-core/v4l2-fwnode.c | 38 +++++++++++++++++++++++++++
include/media/v4l2-fwnode.h | 5 ++++
include/media/v4l2-mediabus.h | 17 ++++++++++++
3 files changed, 60 insertions(+)
diff --git a/drivers/media/v4l2-core/v4l2-fwnode.c b/drivers/media/v4l2-core/v4l2-fwnode.c
index 89c7192148df..49ea4d264eb2 100644
--- a/drivers/media/v4l2-core/v4l2-fwnode.c
+++ b/drivers/media/v4l2-core/v4l2-fwnode.c
@@ -67,6 +67,10 @@ static const struct v4l2_fwnode_bus_conv {
V4L2_FWNODE_BUS_TYPE_DPI,
V4L2_MBUS_DPI,
"DPI",
+ }, {
+ V4L2_FWNODE_BUS_TYPE_DISPLAYPORT,
+ V4L2_MBUS_DISPLAYPORT,
+ "DisplayPort",
}
};
@@ -417,6 +421,33 @@ v4l2_fwnode_endpoint_parse_csi1_bus(struct fwnode_handle *fwnode,
vep->bus_type = V4L2_MBUS_CSI1;
}
+static int
+v4l2_fwnode_endpoint_parse_dp_bus(struct fwnode_handle *fwnode,
+ struct v4l2_fwnode_endpoint *vep,
+ enum v4l2_mbus_type bus_type)
+{
+ struct v4l2_mbus_config_displayport *bus = &vep->bus.displayport;
+ u32 array[4];
+ int count;
+ int i;
+
+ count = fwnode_property_count_u32(fwnode, "data-lanes");
+ if (count < 0)
+ return count;
+ if (!(count == 1 || count == 2 || count == 4))
+ return -EINVAL;
+ fwnode_property_read_u32_array(fwnode, "data-lanes", array, count);
+
+ for (i = 0; i < count; i++)
+ bus->data_lanes[i] = array[i];
+ bus->num_data_lanes = count;
+ bus->multi_stream_support = fwnode_property_present(fwnode, "multi-stream-support");
+
+ vep->bus_type = V4L2_MBUS_DISPLAYPORT;
+
+ return 0;
+}
+
static int __v4l2_fwnode_endpoint_parse(struct fwnode_handle *fwnode,
struct v4l2_fwnode_endpoint *vep)
{
@@ -482,6 +513,13 @@ static int __v4l2_fwnode_endpoint_parse(struct fwnode_handle *fwnode,
v4l2_fwnode_endpoint_parse_parallel_bus(fwnode, vep,
vep->bus_type);
+ break;
+ case V4L2_MBUS_DISPLAYPORT:
+ rval = v4l2_fwnode_endpoint_parse_dp_bus(fwnode, vep,
+ vep->bus_type);
+ if (rval)
+ return rval;
+
break;
default:
pr_warn("unsupported bus type %u\n", mbus_type);
diff --git a/include/media/v4l2-fwnode.h b/include/media/v4l2-fwnode.h
index f7c57c776589..777a61015ca0 100644
--- a/include/media/v4l2-fwnode.h
+++ b/include/media/v4l2-fwnode.h
@@ -36,6 +36,8 @@
* @bus.mipi_csi2: embedded &struct v4l2_mbus_config_mipi_csi2.
* Used if the bus is MIPI Alliance's Camera Serial
* Interface version 2 (MIPI CSI2).
+ * @bus.displayport: embedded &struct v4l2_mbus_config_displayport.
+ * Used if the bus is VESA DisplayPort.
* @link_frequencies: array of supported link frequencies
* @nr_of_link_frequencies: number of elements in link_frequenccies array
*/
@@ -46,6 +48,7 @@ struct v4l2_fwnode_endpoint {
struct v4l2_mbus_config_parallel parallel;
struct v4l2_mbus_config_mipi_csi1 mipi_csi1;
struct v4l2_mbus_config_mipi_csi2 mipi_csi2;
+ struct v4l2_mbus_config_displayport displayport;
} bus;
u64 *link_frequencies;
unsigned int nr_of_link_frequencies;
@@ -166,6 +169,7 @@ struct v4l2_fwnode_connector {
* @V4L2_FWNODE_BUS_TYPE_PARALLEL: Camera Parallel Interface bus
* @V4L2_FWNODE_BUS_TYPE_BT656: BT.656 video format bus-type
* @V4L2_FWNODE_BUS_TYPE_DPI: Video Parallel Interface bus
+ * @V4L2_FWNODE_BUS_TYPE_DISPLAYPORT: DisplayPort bus
* @NR_OF_V4L2_FWNODE_BUS_TYPE: Number of bus-types
*/
enum v4l2_fwnode_bus_type {
@@ -177,6 +181,7 @@ enum v4l2_fwnode_bus_type {
V4L2_FWNODE_BUS_TYPE_PARALLEL,
V4L2_FWNODE_BUS_TYPE_BT656,
V4L2_FWNODE_BUS_TYPE_DPI,
+ V4L2_FWNODE_BUS_TYPE_DISPLAYPORT,
NR_OF_V4L2_FWNODE_BUS_TYPE
};
diff --git a/include/media/v4l2-mediabus.h b/include/media/v4l2-mediabus.h
index 5bce6e423e94..74b5d96f5050 100644
--- a/include/media/v4l2-mediabus.h
+++ b/include/media/v4l2-mediabus.h
@@ -120,6 +120,18 @@ struct v4l2_mbus_config_mipi_csi1 {
unsigned char clock_lane;
};
+/**
+ * struct v4l2_mbus_config_displayport - DisplayPort data bus configuration
+ * @data_lanes: an array of physical data lane indexes
+ * @num_data_lanes: number of data lanes
+ * @multi_stream_support: multi stream transport support
+ */
+struct v4l2_mbus_config_displayport {
+ unsigned char data_lanes[4];
+ unsigned char num_data_lanes;
+ bool multi_stream_support;
+};
+
/**
* enum v4l2_mbus_type - media bus type
* @V4L2_MBUS_UNKNOWN: unknown bus type, no V4L2 mediabus configuration
@@ -131,6 +143,7 @@ struct v4l2_mbus_config_mipi_csi1 {
* @V4L2_MBUS_CSI2_DPHY: MIPI CSI-2 serial interface, with D-PHY
* @V4L2_MBUS_CSI2_CPHY: MIPI CSI-2 serial interface, with C-PHY
* @V4L2_MBUS_DPI: MIPI VIDEO DPI interface
+ * @V4L2_MBUS_DISPLAYPORT: DisplayPort interface
* @V4L2_MBUS_INVALID: invalid bus type (keep as last)
*/
enum v4l2_mbus_type {
@@ -142,6 +155,7 @@ enum v4l2_mbus_type {
V4L2_MBUS_CSI2_DPHY,
V4L2_MBUS_CSI2_CPHY,
V4L2_MBUS_DPI,
+ V4L2_MBUS_DISPLAYPORT,
V4L2_MBUS_INVALID,
};
@@ -159,6 +173,8 @@ enum v4l2_mbus_type {
* @bus.mipi_csi2: embedded &struct v4l2_mbus_config_mipi_csi2.
* Used if the bus is MIPI Alliance's Camera Serial
* Interface version 2 (MIPI CSI2).
+ * @bus.displayport: embedded &struct v4l2_mbus_config_displayport.
+ * Used if the bus is VESA DisplayPort interface.
*/
struct v4l2_mbus_config {
enum v4l2_mbus_type type;
@@ -166,6 +182,7 @@ struct v4l2_mbus_config {
struct v4l2_mbus_config_parallel parallel;
struct v4l2_mbus_config_mipi_csi1 mipi_csi1;
struct v4l2_mbus_config_mipi_csi2 mipi_csi2;
+ struct v4l2_mbus_config_displayport displayport;
} bus;
};
--
2.45.0.rc1.225.g2a3ae87e7f-goog
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH v3 07/10] media: intel: Add Displayport RX IP driver
2024-05-07 15:54 [PATCH v3 00/10] Add Chameleon v3 video support Paweł Anikiel
` (5 preceding siblings ...)
2024-05-07 15:54 ` [PATCH v3 06/10] media: v4l2-mediabus: Add support for DisplayPort media bus Paweł Anikiel
@ 2024-05-07 15:54 ` Paweł Anikiel
2024-06-03 8:37 ` Hans Verkuil
2024-05-07 15:54 ` [PATCH v3 08/10] media: dt-bindings: Add Chameleon v3 video interface Paweł Anikiel
` (3 subsequent siblings)
10 siblings, 1 reply; 26+ messages in thread
From: Paweł Anikiel @ 2024-05-07 15:54 UTC (permalink / raw)
To: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
robh+dt, tzimmermann
Cc: devicetree, dri-devel, linux-kernel, linux-media,
chromeos-krk-upstreaming, Paweł Anikiel
Add v4l2 subdev driver for the Intel Displayport receiver FPGA IP.
It is a part of the DisplayPort Intel FPGA IP Core, and supports
DisplayPort 1.4, HBR3 video capture and Multi-Stream Transport.
Signed-off-by: Paweł Anikiel <panikiel@google.com>
---
drivers/media/platform/intel/Kconfig | 12 +
drivers/media/platform/intel/Makefile | 1 +
drivers/media/platform/intel/intel-dprx.c | 2283 +++++++++++++++++++++
3 files changed, 2296 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..734f6c2395bc
--- /dev/null
+++ b/drivers/media/platform/intel/intel-dprx.c
@@ -0,0 +1,2283 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2023-2024 Google LLC.
+ * Author: Paweł Anikiel <panikiel@google.com>
+ */
+
+#include <linux/crc-dp.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-dv-timings.h>
+#include <media/v4l2-fwnode.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 v4l2_ctrl_handler ctrl_handler;
+ struct media_pad pads[5];
+
+ 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;
+
+ bool hpd_state;
+};
+
+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 int dprx_pad_to_sink_idx(struct dprx *dprx, int pad)
+{
+ int sink_idx = pad - 1;
+
+ if (sink_idx < 0 || sink_idx >= dprx->max_stream_count)
+ return -1;
+ else
+ return sink_idx;
+}
+
+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;
+ int sink_idx;
+ u32 end_block = edid->start_block + edid->blocks;
+ unsigned long flags;
+ int res = 0;
+
+ memset(edid->reserved, 0, sizeof(edid->reserved));
+
+ sink_idx = dprx_pad_to_sink_idx(dprx, edid->pad);
+ if (sink_idx < 0)
+ return -EINVAL;
+ sink = &dprx->sinks[sink_idx];
+
+ spin_lock_irqsave(&dprx->lock, flags);
+
+ 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;
+ int sink_idx;
+ bool prev_hpd;
+ bool cur_hpd;
+ unsigned long flags;
+
+ memset(edid->reserved, 0, sizeof(edid->reserved));
+
+ sink_idx = dprx_pad_to_sink_idx(dprx, edid->pad);
+ if (sink_idx < 0)
+ return -EINVAL;
+ sink = &dprx->sinks[sink_idx];
+
+ if (edid->start_block != 0)
+ return -EINVAL;
+ if (edid->blocks > DPRX_MAX_EDID_BLOCKS) {
+ edid->blocks = DPRX_MAX_EDID_BLOCKS;
+ return -E2BIG;
+ }
+
+ prev_hpd = dprx->hpd_state;
+ /*
+ * 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.
+ */
+ if (sink_idx == 0)
+ dprx->hpd_state = edid->blocks > 0;
+ cur_hpd = dprx->hpd_state;
+
+ if (prev_hpd)
+ dprx_set_hpd(dprx, 0);
+
+ spin_lock_irqsave(&dprx->lock, flags);
+ sink->blocks = edid->blocks;
+ memcpy(sink->edid, edid->edid, edid->blocks * 128);
+ if (cur_hpd)
+ dprx_reset(dprx);
+ spin_unlock_irqrestore(&dprx->lock, flags);
+
+ if (cur_hpd) {
+ if (prev_hpd) {
+ /*
+ * Some DisplayPort sources are not happy with 100ms
+ * HPD pulses. Looking at the AUX message log, it
+ * seemed the source was waiting for values of some
+ * DPCD registers to change when it shouldn't be.
+ *
+ * Not sure whose fault that is, but 500ms appears
+ * to be a safe duration for the source to "forget"
+ * about the sink.
+ */
+ msleep(500);
+ }
+ 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);
+ int sink_idx;
+ u32 htotal, vtotal;
+ u32 hsp, hsw;
+ u32 hstart, vstart;
+ u32 vsp, vsw;
+ u32 hwidth, vheight;
+
+ sink_idx = dprx_pad_to_sink_idx(dprx, pad);
+ if (sink_idx < 0)
+ return -EINVAL;
+
+ if (!((dprx_read(dprx, DPRX_VBID(sink_idx)) >> DPRX_VBID_MSA_LOCK) & 1))
+ return -ENOLINK;
+
+ htotal = dprx_read(dprx, DPRX_MSA_HTOTAL(sink_idx));
+ vtotal = dprx_read(dprx, DPRX_MSA_VTOTAL(sink_idx));
+ hsp = dprx_read(dprx, DPRX_MSA_HSP(sink_idx));
+ hsw = dprx_read(dprx, DPRX_MSA_HSW(sink_idx));
+ hstart = dprx_read(dprx, DPRX_MSA_HSTART(sink_idx));
+ vstart = dprx_read(dprx, DPRX_MSA_VSTART(sink_idx));
+ vsp = dprx_read(dprx, DPRX_MSA_VSP(sink_idx));
+ vsw = dprx_read(dprx, DPRX_MSA_VSW(sink_idx));
+ hwidth = dprx_read(dprx, DPRX_MSA_HWIDTH(sink_idx));
+ vheight = dprx_read(dprx, DPRX_MSA_VHEIGHT(sink_idx));
+
+ 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.pixelclock = htotal * vtotal * 24;
+ 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 = 640,
+ .max_width = 7680,
+ .min_height = 480,
+ .max_height = 4320,
+ .min_pixelclock = 25000000,
+ .max_pixelclock = 1080000000,
+ .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_parse_bus_cfg(struct dprx *dprx, struct v4l2_fwnode_endpoint *bus_cfg)
+{
+ u64 max_freq = 0;
+ int i;
+
+ for (i = 0; i < bus_cfg->nr_of_link_frequencies; i++) {
+ if (max_freq < bus_cfg->link_frequencies[i])
+ max_freq = bus_cfg->link_frequencies[i];
+ }
+
+ switch (max_freq) {
+ case 1620000000:
+ dprx->max_link_rate = 0x06;
+ break;
+ case 2700000000:
+ dprx->max_link_rate = 0x0a;
+ break;
+ case 5400000000:
+ dprx->max_link_rate = 0x14;
+ break;
+ case 8100000000:
+ dprx->max_link_rate = 0x1e;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ dprx->max_lane_count = bus_cfg->bus.displayport.num_data_lanes;
+ dprx->multi_stream_support = bus_cfg->bus.displayport.multi_stream_support;
+
+ return 0;
+}
+
+static int dprx_parse_fwnode(struct dprx *dprx)
+{
+ struct fwnode_handle *fwnode = dev_fwnode(dprx->dev);
+ struct fwnode_handle *endpoint;
+ struct v4l2_fwnode_endpoint bus_cfg = {
+ .bus_type = V4L2_MBUS_DISPLAYPORT
+ };
+ int res;
+
+ /* get input port node */
+ endpoint = fwnode_graph_get_next_endpoint(fwnode, NULL);
+ if (!endpoint)
+ return -EINVAL;
+
+ res = v4l2_fwnode_endpoint_alloc_parse(endpoint, &bus_cfg);
+ if (res) {
+ fwnode_handle_put(endpoint);
+ return res;
+ }
+
+ res = dprx_parse_bus_cfg(dprx, &bus_cfg);
+ if (res) {
+ v4l2_fwnode_endpoint_free(&bus_cfg);
+ fwnode_handle_put(endpoint);
+ return res;
+ }
+
+ v4l2_fwnode_endpoint_free(&bus_cfg);
+
+ /* count the number of output port nodes */
+ endpoint = fwnode_graph_get_next_endpoint(fwnode, endpoint);
+ dprx->max_stream_count = 0;
+ while (endpoint) {
+ endpoint = fwnode_graph_get_next_endpoint(fwnode, endpoint);
+ dprx->max_stream_count++;
+ }
+
+ return 0;
+}
+
+static int dprx_probe(struct platform_device *pdev)
+{
+ struct dprx *dprx;
+ int irq;
+ int res;
+ int i;
+
+ 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 = dprx_parse_fwnode(dprx);
+ if (res)
+ return res;
+
+ 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_DV_DECODER;
+ dprx->subdev.entity.ops = &dprx_entity_ops;
+
+ v4l2_ctrl_handler_init(&dprx->ctrl_handler, 1);
+ v4l2_ctrl_new_std(&dprx->ctrl_handler, NULL,
+ V4L2_CID_DV_RX_POWER_PRESENT, 0, 1, 0, 0);
+ res = dprx->ctrl_handler.error;
+ if (res)
+ goto handler_free;
+
+ dprx->subdev.ctrl_handler = &dprx->ctrl_handler;
+
+ dprx->pads[0].flags = MEDIA_PAD_FL_SINK;
+ for (i = 1; i <= dprx->max_stream_count; i++)
+ dprx->pads[i].flags = MEDIA_PAD_FL_SOURCE;
+
+ res = media_entity_pads_init(&dprx->subdev.entity,
+ dprx->max_stream_count + 1, dprx->pads);
+ if (res)
+ goto handler_free;
+
+ res = v4l2_async_register_subdev(&dprx->subdev);
+ if (res)
+ goto entity_cleanup;
+
+ dprx->hpd_state = 0;
+ dprx_set_hpd(dprx, 0);
+ dprx_reset_hw(dprx);
+
+ dprx_set_irq(dprx, 1);
+
+ return 0;
+
+entity_cleanup:
+ media_entity_cleanup(&dprx->subdev.entity);
+handler_free:
+ v4l2_ctrl_handler_free(&dprx->ctrl_handler);
+
+ return res;
+}
+
+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);
+ media_entity_cleanup(&dprx->subdev.entity);
+ v4l2_ctrl_handler_free(&dprx->ctrl_handler);
+}
+
+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 <panikiel@google.com>");
+MODULE_DESCRIPTION("Intel DisplayPort RX IP core driver");
+MODULE_LICENSE("GPL");
--
2.45.0.rc1.225.g2a3ae87e7f-goog
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH v3 08/10] media: dt-bindings: Add Chameleon v3 video interface
2024-05-07 15:54 [PATCH v3 00/10] Add Chameleon v3 video support Paweł Anikiel
` (6 preceding siblings ...)
2024-05-07 15:54 ` [PATCH v3 07/10] media: intel: Add Displayport RX IP driver Paweł Anikiel
@ 2024-05-07 15:54 ` Paweł Anikiel
2024-05-10 21:25 ` Rob Herring (Arm)
2024-05-07 15:54 ` [PATCH v3 09/10] media: dt-bindings: Add Intel Displayport RX IP Paweł Anikiel
` (2 subsequent siblings)
10 siblings, 1 reply; 26+ messages in thread
From: Paweł Anikiel @ 2024-05-07 15:54 UTC (permalink / raw)
To: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
robh+dt, tzimmermann
Cc: devicetree, dri-devel, linux-kernel, linux-media,
chromeos-krk-upstreaming, Paweł Anikiel
Add dt binding for the video interface present on the Google
Chameleon v3. The Chameleon v3 uses the video interface to capture
a single video source from a given HDMI or DP connector and write
the resulting frames to memory.
Signed-off-by: Paweł Anikiel <panikiel@google.com>
---
.../bindings/media/google,chv3-video.yaml | 64 +++++++++++++++++++
1 file changed, 64 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/google,chv3-video.yaml
diff --git a/Documentation/devicetree/bindings/media/google,chv3-video.yaml b/Documentation/devicetree/bindings/media/google,chv3-video.yaml
new file mode 100644
index 000000000000..b8380021cd23
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/google,chv3-video.yaml
@@ -0,0 +1,64 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/google,chv3-video.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Google Chameleon v3 video interface
+
+maintainers:
+ - Paweł Anikiel <panikiel@google.com>
+
+properties:
+ compatible:
+ enum:
+ - google,chv3-video-it-1.0
+ - google,chv3-video-dp-1.0
+
+ reg:
+ items:
+ - description: core registers
+ - description: irq registers
+
+ interrupts:
+ maxItems: 1
+
+ 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-video-it-1.0";
+ reg = <0xc0060500 0x100>,
+ <0xc0060f20 0x10>;
+ interrupts = <GIC_SPI 19 IRQ_TYPE_LEVEL_HIGH>;
+ };
+
+ - |
+ video@c0060600 {
+ compatible = "google,chv3-video-dp-1.0";
+ reg = <0xc0060600 0x100>,
+ <0xc0060f30 0x10>;
+ interrupts = <GIC_SPI 22 IRQ_TYPE_LEVEL_HIGH>;
+
+ port {
+ video_mst0_0: endpoint {
+ remote-endpoint = <&dprx_mst_0>;
+ };
+ };
+ };
--
2.45.0.rc1.225.g2a3ae87e7f-goog
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH v3 09/10] media: dt-bindings: Add Intel Displayport RX IP
2024-05-07 15:54 [PATCH v3 00/10] Add Chameleon v3 video support Paweł Anikiel
` (7 preceding siblings ...)
2024-05-07 15:54 ` [PATCH v3 08/10] media: dt-bindings: Add Chameleon v3 video interface Paweł Anikiel
@ 2024-05-07 15:54 ` Paweł Anikiel
2024-05-10 21:24 ` Rob Herring
2024-05-07 15:54 ` [PATCH v3 10/10] ARM: dts: chameleonv3: Add video device nodes Paweł Anikiel
2024-06-03 8:44 ` [PATCH v3 00/10] Add Chameleon v3 video support Hans Verkuil
10 siblings, 1 reply; 26+ messages in thread
From: Paweł Anikiel @ 2024-05-07 15:54 UTC (permalink / raw)
To: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
robh+dt, tzimmermann
Cc: devicetree, dri-devel, linux-kernel, linux-media,
chromeos-krk-upstreaming, Paweł Anikiel
Add dt binding for the Intel Displayport receiver FPGA IP.
It is a part of the DisplayPort Intel FPGA IP Core, and supports
DisplayPort 1.4, HBR3 video capture and Multi-Stream Transport.
The user guide can be found here:
https://www.intel.com/programmable/technical-pdfs/683273.pdf
Signed-off-by: Paweł Anikiel <panikiel@google.com>
---
.../devicetree/bindings/media/intel,dprx.yaml | 172 ++++++++++++++++++
1 file changed, 172 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..01bed858f746
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/intel,dprx.yaml
@@ -0,0 +1,172 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/intel,dprx.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Intel DisplayPort RX IP
+
+maintainers:
+ - Paweł Anikiel <panikiel@google.com>
+
+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 have to be set in the devicetree:
+ - RX maximum link rate (using link-frequencies)
+ - Maximum lane count (using data-lanes)
+ - Support MST (using multi-stream-support)
+ - Max stream count (inferred from the number of ports)
+
+properties:
+ compatible:
+ const: intel,dprx-20.0.1
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ ports:
+ $ref: /schemas/graph.yaml#/properties/ports
+
+ properties:
+ port@0:
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ description: MST virtual channel 0 or SST main link
+
+ properties:
+ endpoint:
+ $ref: /schemas/media/video-interfaces.yaml#
+
+ properties:
+ link-frequencies: true
+
+ data-lanes:
+ minItems: 1
+ maxItems: 4
+
+ multi-stream-support: true
+
+ required:
+ - data-lanes
+ - link-frequencies
+
+ port@1:
+ $ref: /schemas/graph.yaml#/properties/port
+ description: MST virtual channel 0 or SST main link
+
+ port@2:
+ $ref: /schemas/graph.yaml#/properties/port
+ description: MST virtual channel 1
+
+ port@3:
+ $ref: /schemas/graph.yaml#/properties/port
+ description: MST virtual channel 2
+
+ port@4:
+ $ref: /schemas/graph.yaml#/properties/port
+ description: MST virtual channel 3
+
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - ports
+
+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>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ dprx_mst_in: endpoint {
+ remote-endpoint = <&dp_input_mst_0>;
+ data-lanes = <0 1 2 3>;
+ link-frequencies = /bits/ 64 <1620000000 2700000000
+ 5400000000 8100000000>;
+ multi-stream-support;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ dprx_mst_0: endpoint {
+ remote-endpoint = <&video_mst0_0>;
+ };
+ };
+
+ port@2 {
+ reg = <2>;
+ dprx_mst_1: endpoint {
+ remote-endpoint = <&video_mst1_0>;
+ };
+ };
+
+ port@3 {
+ reg = <3>;
+ dprx_mst_2: endpoint {
+ remote-endpoint = <&video_mst2_0>;
+ };
+ };
+
+ port@4 {
+ reg = <4>;
+ dprx_mst_3: endpoint {
+ remote-endpoint = <&video_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>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ dprx_sst_in: endpoint {
+ remote-endpoint = <&dp_input_sst_0>;
+ data-lanes = <0 1 2 3>;
+ link-frequencies = /bits/ 64 <1620000000 2700000000
+ 5400000000 8100000000>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ dprx_sst_0: endpoint {
+ remote-endpoint = <&video_sst_0>;
+ };
+ };
+ };
+ };
--
2.45.0.rc1.225.g2a3ae87e7f-goog
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH v3 10/10] ARM: dts: chameleonv3: Add video device nodes
2024-05-07 15:54 [PATCH v3 00/10] Add Chameleon v3 video support Paweł Anikiel
` (8 preceding siblings ...)
2024-05-07 15:54 ` [PATCH v3 09/10] media: dt-bindings: Add Intel Displayport RX IP Paweł Anikiel
@ 2024-05-07 15:54 ` Paweł Anikiel
2024-06-03 8:44 ` [PATCH v3 00/10] Add Chameleon v3 video support Hans Verkuil
10 siblings, 0 replies; 26+ messages in thread
From: Paweł Anikiel @ 2024-05-07 15:54 UTC (permalink / raw)
To: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
robh+dt, tzimmermann
Cc: devicetree, dri-devel, linux-kernel, linux-media,
chromeos-krk-upstreaming, Paweł Anikiel
Add device nodes for the video system present on the Chameleon v3.
It consists of six video interfaces and two Intel DisplayPort receivers.
Signed-off-by: Paweł Anikiel <panikiel@google.com>
---
.../socfpga/socfpga_arria10_chameleonv3.dts | 194 ++++++++++++++++++
1 file changed, 194 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..daafcc14e8cc 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,200 @@ / {
compatible = "google,chameleon-v3", "enclustra,mercury-aa1",
"altr,socfpga-arria10", "altr,socfpga";
+ soc {
+ video0: video@c0060500 {
+ compatible = "google,chv3-video-it-1.0";
+ reg = <0xc0060500 0x100>,
+ <0xc0060f20 0x10>;
+ interrupts = <GIC_SPI 19 IRQ_TYPE_LEVEL_HIGH>;
+ };
+
+ video_mst0: video@c0060600 {
+ compatible = "google,chv3-video-dp-1.0";
+ reg = <0xc0060600 0x100>,
+ <0xc0060f30 0x10>;
+ interrupts = <GIC_SPI 22 IRQ_TYPE_LEVEL_HIGH>;
+
+ port {
+ video_mst0_0: endpoint {
+ remote-endpoint = <&dprx_mst_0>;
+ };
+ };
+ };
+
+ video_mst1: video@c0060700 {
+ compatible = "google,chv3-video-dp-1.0";
+ reg = <0xc0060700 0x100>,
+ <0xc0060f40 0x10>;
+ interrupts = <GIC_SPI 23 IRQ_TYPE_LEVEL_HIGH>;
+
+ port {
+ video_mst1_0: endpoint {
+ remote-endpoint = <&dprx_mst_1>;
+ };
+ };
+ };
+
+ video_mst2: video@c0060800 {
+ compatible = "google,chv3-video-dp-1.0";
+ reg = <0xc0060800 0x100>,
+ <0xc0060f50 0x10>;
+ interrupts = <GIC_SPI 24 IRQ_TYPE_LEVEL_HIGH>;
+
+ port {
+ video_mst2_0: endpoint {
+ remote-endpoint = <&dprx_mst_2>;
+ };
+ };
+ };
+
+ video_mst3: video@c0060900 {
+ compatible = "google,chv3-video-dp-1.0";
+ reg = <0xc0060900 0x100>,
+ <0xc0060f60 0x10>;
+ interrupts = <GIC_SPI 25 IRQ_TYPE_LEVEL_HIGH>;
+
+ port {
+ video_mst3_0: endpoint {
+ remote-endpoint = <&dprx_mst_3>;
+ };
+ };
+ };
+
+ video_sst: video@c0060a00 {
+ compatible = "google,chv3-video-dp-1.0";
+ reg = <0xc0060a00 0x100>,
+ <0xc0060f70 0x10>;
+ interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
+
+ port {
+ video_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>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ dprx_mst_in: endpoint {
+ remote-endpoint = <&dp_input_mst_0>;
+ data-lanes = <0 1 2 3>;
+ link-frequencies = /bits/ 64 <1620000000 2700000000
+ 5400000000 8100000000>;
+ multi-stream-support;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ dprx_mst_0: endpoint {
+ remote-endpoint = <&video_mst0_0>;
+ };
+ };
+
+ port@2 {
+ reg = <2>;
+ dprx_mst_1: endpoint {
+ remote-endpoint = <&video_mst1_0>;
+ };
+ };
+
+ port@3 {
+ reg = <3>;
+ dprx_mst_2: endpoint {
+ remote-endpoint = <&video_mst2_0>;
+ };
+ };
+
+ port@4 {
+ reg = <4>;
+ dprx_mst_3: endpoint {
+ remote-endpoint = <&video_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>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ dprx_sst_in: endpoint {
+ remote-endpoint = <&dp_input_sst_0>;
+ data-lanes = <0 1 2 3>;
+ link-frequencies = /bits/ 64 <1620000000 2700000000
+ 5400000000 8100000000>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ dprx_sst_0: endpoint {
+ remote-endpoint = <&video_sst_0>;
+ };
+ };
+ };
+ };
+ };
+
+ dp-input-mst {
+ compatible = "dp-connector";
+ type = "full-size";
+
+ port {
+ dp_input_mst_0: endpoint {
+ remote-endpoint = <&dprx_mst_in>;
+ };
+ };
+ };
+
+ dp-input-sst {
+ compatible = "dp-connector";
+ type = "full-size";
+
+ port {
+ dp_input_sst_0: endpoint {
+ remote-endpoint = <&dprx_sst_in>;
+ };
+ };
+ };
+
aliases {
serial0 = &uart0;
i2c0 = &i2c0;
--
2.45.0.rc1.225.g2a3ae87e7f-goog
^ permalink raw reply related [flat|nested] 26+ messages in thread
* Re: [PATCH v3 05/10] media: dt-bindings: video-interfaces: Support DisplayPort MST
2024-05-07 15:54 ` [PATCH v3 05/10] media: dt-bindings: video-interfaces: Support DisplayPort MST Paweł Anikiel
@ 2024-05-10 21:16 ` Rob Herring
2024-05-13 11:07 ` Paweł Anikiel
2024-05-13 14:56 ` Rob Herring (Arm)
1 sibling, 1 reply; 26+ messages in thread
From: Rob Herring @ 2024-05-10 21:16 UTC (permalink / raw)
To: Paweł Anikiel
Cc: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
tzimmermann, devicetree, dri-devel, linux-kernel, linux-media,
chromeos-krk-upstreaming
On Tue, May 07, 2024 at 03:54:08PM +0000, Paweł Anikiel wrote:
> Add a DisplayPort bus type and a multi-stream-support property
> indicating whether the interface supports MST.
>
> Signed-off-by: Paweł Anikiel <panikiel@google.com>
> ---
> .../devicetree/bindings/media/video-interfaces.yaml | 7 +++++++
> include/dt-bindings/media/video-interfaces.h | 2 ++
> 2 files changed, 9 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/media/video-interfaces.yaml b/Documentation/devicetree/bindings/media/video-interfaces.yaml
> index 26e3e7d7c67b..7bf3a2c09a5b 100644
> --- a/Documentation/devicetree/bindings/media/video-interfaces.yaml
> +++ b/Documentation/devicetree/bindings/media/video-interfaces.yaml
> @@ -94,6 +94,7 @@ properties:
> - 5 # Parallel
> - 6 # BT.656
> - 7 # DPI
> + - 8 # DisplayPort
> description:
> Data bus type.
>
> @@ -217,4 +218,10 @@ properties:
> Whether the clock signal is used as clock (0) or strobe (1). Used with
> CCP2, for instance.
>
> + multi-stream-support:
If MST is a known term for DP, then perhaps "dp-mst-support" for the
name. In any case, 'dp' should be in there somewhere.
> + type: boolean
> + description:
> + Support transport of multiple independent streams. Used for
> + DisplayPort MST-capable interfaces.
Wouldn't this be implied by the devices at each end of the link? The
drivers for each device should really list out features supported for
the link. The mode used is then the union of those 2 lists with DT
properties only used when the union is not definitive.
> +
> additionalProperties: true
> diff --git a/include/dt-bindings/media/video-interfaces.h b/include/dt-bindings/media/video-interfaces.h
> index 68ac4e05e37f..b236806f4482 100644
> --- a/include/dt-bindings/media/video-interfaces.h
> +++ b/include/dt-bindings/media/video-interfaces.h
> @@ -12,5 +12,7 @@
> #define MEDIA_BUS_TYPE_CSI2_DPHY 4
> #define MEDIA_BUS_TYPE_PARALLEL 5
> #define MEDIA_BUS_TYPE_BT656 6
> +#define MEDIA_BUS_TYPE_DPI 7
> +#define MEDIA_BUS_TYPE_DISPLAYPORT 8
>
> #endif /* __DT_BINDINGS_MEDIA_VIDEO_INTERFACES_H__ */
> --
> 2.45.0.rc1.225.g2a3ae87e7f-goog
>
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3 09/10] media: dt-bindings: Add Intel Displayport RX IP
2024-05-07 15:54 ` [PATCH v3 09/10] media: dt-bindings: Add Intel Displayport RX IP Paweł Anikiel
@ 2024-05-10 21:24 ` Rob Herring
2024-05-13 10:39 ` Paweł Anikiel
0 siblings, 1 reply; 26+ messages in thread
From: Rob Herring @ 2024-05-10 21:24 UTC (permalink / raw)
To: Paweł Anikiel
Cc: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
tzimmermann, devicetree, dri-devel, linux-kernel, linux-media,
chromeos-krk-upstreaming
On Tue, May 07, 2024 at 03:54:12PM +0000, Paweł Anikiel wrote:
> Add dt binding for the Intel Displayport receiver FPGA IP.
> It is a part of the DisplayPort Intel FPGA IP Core, and supports
> DisplayPort 1.4, HBR3 video capture and Multi-Stream Transport.
>
> The user guide can be found here:
> https://www.intel.com/programmable/technical-pdfs/683273.pdf
>
> Signed-off-by: Paweł Anikiel <panikiel@google.com>
> ---
> .../devicetree/bindings/media/intel,dprx.yaml | 172 ++++++++++++++++++
> 1 file changed, 172 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..01bed858f746
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/intel,dprx.yaml
> @@ -0,0 +1,172 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/media/intel,dprx.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Intel DisplayPort RX IP
> +
> +maintainers:
> + - Paweł Anikiel <panikiel@google.com>
> +
> +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 have to be set in the devicetree:
> + - RX maximum link rate (using link-frequencies)
> + - Maximum lane count (using data-lanes)
> + - Support MST (using multi-stream-support)
> + - Max stream count (inferred from the number of ports)
> +
> +properties:
> + compatible:
> + const: intel,dprx-20.0.1
> +
> + reg:
> + maxItems: 1
> +
> + interrupts:
> + maxItems: 1
> +
> + ports:
> + $ref: /schemas/graph.yaml#/properties/ports
> +
> + properties:
> + port@0:
> + $ref: /schemas/graph.yaml#/$defs/port-base
> + description: MST virtual channel 0 or SST main link
> +
> + properties:
> + endpoint:
> + $ref: /schemas/media/video-interfaces.yaml#
> +
> + properties:
> + link-frequencies: true
> +
> + data-lanes:
> + minItems: 1
> + maxItems: 4
> +
> + multi-stream-support: true
> +
> + required:
> + - data-lanes
> + - link-frequencies
> +
> + port@1:
> + $ref: /schemas/graph.yaml#/properties/port
> + description: MST virtual channel 0 or SST main link
How can port@0 also be "MST virtual channel 0 or SST main link"?
> +
> + port@2:
> + $ref: /schemas/graph.yaml#/properties/port
> + description: MST virtual channel 1
> +
> + port@3:
> + $ref: /schemas/graph.yaml#/properties/port
> + description: MST virtual channel 2
> +
> + port@4:
> + $ref: /schemas/graph.yaml#/properties/port
> + description: MST virtual channel 3
> +
> +
> +required:
> + - compatible
> + - reg
> + - interrupts
> + - ports
> +
> +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>;
> +
> + ports {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + port@0 {
> + reg = <0>;
> + dprx_mst_in: endpoint {
> + remote-endpoint = <&dp_input_mst_0>;
> + data-lanes = <0 1 2 3>;
> + link-frequencies = /bits/ 64 <1620000000 2700000000
> + 5400000000 8100000000>;
> + multi-stream-support;
> + };
> + };
> +
> + port@1 {
> + reg = <1>;
> + dprx_mst_0: endpoint {
> + remote-endpoint = <&video_mst0_0>;
> + };
> + };
> +
> + port@2 {
> + reg = <2>;
> + dprx_mst_1: endpoint {
> + remote-endpoint = <&video_mst1_0>;
> + };
> + };
> +
> + port@3 {
> + reg = <3>;
> + dprx_mst_2: endpoint {
> + remote-endpoint = <&video_mst2_0>;
> + };
> + };
> +
> + port@4 {
> + reg = <4>;
> + dprx_mst_3: endpoint {
> + remote-endpoint = <&video_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>;
> +
> + ports {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + port@0 {
> + reg = <0>;
> + dprx_sst_in: endpoint {
> + remote-endpoint = <&dp_input_sst_0>;
> + data-lanes = <0 1 2 3>;
> + link-frequencies = /bits/ 64 <1620000000 2700000000
> + 5400000000 8100000000>;
> + };
> + };
> +
> + port@1 {
> + reg = <1>;
> + dprx_sst_0: endpoint {
> + remote-endpoint = <&video_sst_0>;
> + };
> + };
> + };
> + };
> --
> 2.45.0.rc1.225.g2a3ae87e7f-goog
>
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3 08/10] media: dt-bindings: Add Chameleon v3 video interface
2024-05-07 15:54 ` [PATCH v3 08/10] media: dt-bindings: Add Chameleon v3 video interface Paweł Anikiel
@ 2024-05-10 21:25 ` Rob Herring (Arm)
0 siblings, 0 replies; 26+ messages in thread
From: Rob Herring (Arm) @ 2024-05-10 21:25 UTC (permalink / raw)
To: Paweł Anikiel
Cc: conor+dt, chromeos-krk-upstreaming, robh+dt, daniel,
hverkuil-cisco, linux-kernel, krzysztof.kozlowski+dt, airlied,
mripard, tzimmermann, akpm, devicetree, dri-devel, linux-media,
dinguyen, maarten.lankhorst, mchehab
On Tue, 07 May 2024 15:54:11 +0000, Paweł Anikiel wrote:
> Add dt binding for the video interface present on the Google
> Chameleon v3. The Chameleon v3 uses the video interface to capture
> a single video source from a given HDMI or DP connector and write
> the resulting frames to memory.
>
> Signed-off-by: Paweł Anikiel <panikiel@google.com>
> ---
> .../bindings/media/google,chv3-video.yaml | 64 +++++++++++++++++++
> 1 file changed, 64 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/media/google,chv3-video.yaml
>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3 09/10] media: dt-bindings: Add Intel Displayport RX IP
2024-05-10 21:24 ` Rob Herring
@ 2024-05-13 10:39 ` Paweł Anikiel
0 siblings, 0 replies; 26+ messages in thread
From: Paweł Anikiel @ 2024-05-13 10:39 UTC (permalink / raw)
To: Rob Herring
Cc: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
tzimmermann, devicetree, dri-devel, linux-kernel, linux-media,
chromeos-krk-upstreaming
On Fri, May 10, 2024 at 11:24 PM Rob Herring <robh@kernel.org> wrote:
>
> On Tue, May 07, 2024 at 03:54:12PM +0000, Paweł Anikiel wrote:
> > Add dt binding for the Intel Displayport receiver FPGA IP.
> > It is a part of the DisplayPort Intel FPGA IP Core, and supports
> > DisplayPort 1.4, HBR3 video capture and Multi-Stream Transport.
> >
> > The user guide can be found here:
> > https://www.intel.com/programmable/technical-pdfs/683273.pdf
> >
> > Signed-off-by: Paweł Anikiel <panikiel@google.com>
> > ---
> > .../devicetree/bindings/media/intel,dprx.yaml | 172 ++++++++++++++++++
> > 1 file changed, 172 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..01bed858f746
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/media/intel,dprx.yaml
> > @@ -0,0 +1,172 @@
> > +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/media/intel,dprx.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: Intel DisplayPort RX IP
> > +
> > +maintainers:
> > + - Paweł Anikiel <panikiel@google.com>
> > +
> > +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 have to be set in the devicetree:
> > + - RX maximum link rate (using link-frequencies)
> > + - Maximum lane count (using data-lanes)
> > + - Support MST (using multi-stream-support)
> > + - Max stream count (inferred from the number of ports)
> > +
> > +properties:
> > + compatible:
> > + const: intel,dprx-20.0.1
> > +
> > + reg:
> > + maxItems: 1
> > +
> > + interrupts:
> > + maxItems: 1
> > +
> > + ports:
> > + $ref: /schemas/graph.yaml#/properties/ports
> > +
> > + properties:
> > + port@0:
> > + $ref: /schemas/graph.yaml#/$defs/port-base
> > + description: MST virtual channel 0 or SST main link
> > +
> > + properties:
> > + endpoint:
> > + $ref: /schemas/media/video-interfaces.yaml#
> > +
> > + properties:
> > + link-frequencies: true
> > +
> > + data-lanes:
> > + minItems: 1
> > + maxItems: 4
> > +
> > + multi-stream-support: true
> > +
> > + required:
> > + - data-lanes
> > + - link-frequencies
> > +
> > + port@1:
> > + $ref: /schemas/graph.yaml#/properties/port
> > + description: MST virtual channel 0 or SST main link
>
> How can port@0 also be "MST virtual channel 0 or SST main link"?
Sorry, I made a mistake. port@0 should be something like "Input port".
>
> > +
> > + port@2:
> > + $ref: /schemas/graph.yaml#/properties/port
> > + description: MST virtual channel 1
> > +
> > + port@3:
> > + $ref: /schemas/graph.yaml#/properties/port
> > + description: MST virtual channel 2
> > +
> > + port@4:
> > + $ref: /schemas/graph.yaml#/properties/port
> > + description: MST virtual channel 3
> > +
> > +
> > +required:
> > + - compatible
> > + - reg
> > + - interrupts
> > + - ports
> > +
> > +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>;
> > +
> > + ports {
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > +
> > + port@0 {
> > + reg = <0>;
> > + dprx_mst_in: endpoint {
> > + remote-endpoint = <&dp_input_mst_0>;
> > + data-lanes = <0 1 2 3>;
> > + link-frequencies = /bits/ 64 <1620000000 2700000000
> > + 5400000000 8100000000>;
> > + multi-stream-support;
> > + };
> > + };
> > +
> > + port@1 {
> > + reg = <1>;
> > + dprx_mst_0: endpoint {
> > + remote-endpoint = <&video_mst0_0>;
> > + };
> > + };
> > +
> > + port@2 {
> > + reg = <2>;
> > + dprx_mst_1: endpoint {
> > + remote-endpoint = <&video_mst1_0>;
> > + };
> > + };
> > +
> > + port@3 {
> > + reg = <3>;
> > + dprx_mst_2: endpoint {
> > + remote-endpoint = <&video_mst2_0>;
> > + };
> > + };
> > +
> > + port@4 {
> > + reg = <4>;
> > + dprx_mst_3: endpoint {
> > + remote-endpoint = <&video_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>;
> > +
> > + ports {
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > +
> > + port@0 {
> > + reg = <0>;
> > + dprx_sst_in: endpoint {
> > + remote-endpoint = <&dp_input_sst_0>;
> > + data-lanes = <0 1 2 3>;
> > + link-frequencies = /bits/ 64 <1620000000 2700000000
> > + 5400000000 8100000000>;
> > + };
> > + };
> > +
> > + port@1 {
> > + reg = <1>;
> > + dprx_sst_0: endpoint {
> > + remote-endpoint = <&video_sst_0>;
> > + };
> > + };
> > + };
> > + };
> > --
> > 2.45.0.rc1.225.g2a3ae87e7f-goog
> >
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3 05/10] media: dt-bindings: video-interfaces: Support DisplayPort MST
2024-05-10 21:16 ` Rob Herring
@ 2024-05-13 11:07 ` Paweł Anikiel
0 siblings, 0 replies; 26+ messages in thread
From: Paweł Anikiel @ 2024-05-13 11:07 UTC (permalink / raw)
To: Rob Herring
Cc: airlied, akpm, conor+dt, daniel, dinguyen, hverkuil-cisco,
krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
tzimmermann, devicetree, dri-devel, linux-kernel, linux-media,
chromeos-krk-upstreaming
On Fri, May 10, 2024 at 11:16 PM Rob Herring <robh@kernel.org> wrote:
>
> On Tue, May 07, 2024 at 03:54:08PM +0000, Paweł Anikiel wrote:
> > Add a DisplayPort bus type and a multi-stream-support property
> > indicating whether the interface supports MST.
> >
> > Signed-off-by: Paweł Anikiel <panikiel@google.com>
> > ---
> > .../devicetree/bindings/media/video-interfaces.yaml | 7 +++++++
> > include/dt-bindings/media/video-interfaces.h | 2 ++
> > 2 files changed, 9 insertions(+)
> >
> > diff --git a/Documentation/devicetree/bindings/media/video-interfaces.yaml b/Documentation/devicetree/bindings/media/video-interfaces.yaml
> > index 26e3e7d7c67b..7bf3a2c09a5b 100644
> > --- a/Documentation/devicetree/bindings/media/video-interfaces.yaml
> > +++ b/Documentation/devicetree/bindings/media/video-interfaces.yaml
> > @@ -94,6 +94,7 @@ properties:
> > - 5 # Parallel
> > - 6 # BT.656
> > - 7 # DPI
> > + - 8 # DisplayPort
> > description:
> > Data bus type.
> >
> > @@ -217,4 +218,10 @@ properties:
> > Whether the clock signal is used as clock (0) or strobe (1). Used with
> > CCP2, for instance.
> >
> > + multi-stream-support:
>
> If MST is a known term for DP, then perhaps "dp-mst-support" for the
> name. In any case, 'dp' should be in there somewhere.
I tried to keep the name generic, for the use case of some other bus
with a similar feature, e.g. CSI-2 and virtual channels.
>
> > + type: boolean
> > + description:
> > + Support transport of multiple independent streams. Used for
> > + DisplayPort MST-capable interfaces.
>
> Wouldn't this be implied by the devices at each end of the link?
For the case of the Intel DP receiver, MST support is an IP
configuration option which cannot be determined at probe time, so it
needs to be read from DT. Having learned that the receiver should use
properties from video-interfaces, I decided to put this property here.
Do you think that's a good idea?
> The drivers for each device should really list out features supported for
> the link. The mode used is then the union of those 2 lists with DT
> properties only used when the union is not definitive.
The mode that actually gets used (MST vs non-MST) is negotiated during
link setup as part of the DP protocol - the sink reports to the source
if it supports MST, and it's up to the source's ability to enable MST
or not.
The property I'm adding here is only useful for the driver to know if
the hw supports MST or not (in the case it can't determine it itself).
>
>
> > +
> > additionalProperties: true
> > diff --git a/include/dt-bindings/media/video-interfaces.h b/include/dt-bindings/media/video-interfaces.h
> > index 68ac4e05e37f..b236806f4482 100644
> > --- a/include/dt-bindings/media/video-interfaces.h
> > +++ b/include/dt-bindings/media/video-interfaces.h
> > @@ -12,5 +12,7 @@
> > #define MEDIA_BUS_TYPE_CSI2_DPHY 4
> > #define MEDIA_BUS_TYPE_PARALLEL 5
> > #define MEDIA_BUS_TYPE_BT656 6
> > +#define MEDIA_BUS_TYPE_DPI 7
> > +#define MEDIA_BUS_TYPE_DISPLAYPORT 8
> >
> > #endif /* __DT_BINDINGS_MEDIA_VIDEO_INTERFACES_H__ */
> > --
> > 2.45.0.rc1.225.g2a3ae87e7f-goog
> >
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3 05/10] media: dt-bindings: video-interfaces: Support DisplayPort MST
2024-05-07 15:54 ` [PATCH v3 05/10] media: dt-bindings: video-interfaces: Support DisplayPort MST Paweł Anikiel
2024-05-10 21:16 ` Rob Herring
@ 2024-05-13 14:56 ` Rob Herring (Arm)
1 sibling, 0 replies; 26+ messages in thread
From: Rob Herring (Arm) @ 2024-05-13 14:56 UTC (permalink / raw)
To: Paweł Anikiel
Cc: mchehab, airlied, mripard, devicetree, hverkuil-cisco, daniel,
akpm, conor+dt, robh+dt, dri-devel, linux-media, dinguyen,
chromeos-krk-upstreaming, maarten.lankhorst, linux-kernel,
krzysztof.kozlowski+dt, tzimmermann
On Tue, 07 May 2024 15:54:08 +0000, Paweł Anikiel wrote:
> Add a DisplayPort bus type and a multi-stream-support property
> indicating whether the interface supports MST.
>
> Signed-off-by: Paweł Anikiel <panikiel@google.com>
> ---
> .../devicetree/bindings/media/video-interfaces.yaml | 7 +++++++
> include/dt-bindings/media/video-interfaces.h | 2 ++
> 2 files changed, 9 insertions(+)
>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3 01/10] media: Add Chameleon v3 video interface driver
2024-05-07 15:54 ` [PATCH v3 01/10] media: Add Chameleon v3 video interface driver Paweł Anikiel
@ 2024-06-03 7:57 ` Hans Verkuil
2024-06-03 14:32 ` Paweł Anikiel
0 siblings, 1 reply; 26+ messages in thread
From: Hans Verkuil @ 2024-06-03 7:57 UTC (permalink / raw)
To: Paweł Anikiel, airlied, akpm, conor+dt, daniel, dinguyen,
krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
robh+dt, tzimmermann
Cc: devicetree, dri-devel, linux-kernel, linux-media,
chromeos-krk-upstreaming
On 07/05/2024 17:54, Paweł Anikiel wrote:
> Add v4l2 driver for the video interface present on the Google
> Chameleon v3. The Chameleon v3 uses the video interface to capture
> a single video source from a given HDMI or DP connector and write
> the resulting frames to memory.
>
> Signed-off-by: Paweł Anikiel <panikiel@google.com>
> ---
> drivers/media/platform/Kconfig | 1 +
> drivers/media/platform/Makefile | 1 +
> drivers/media/platform/google/Kconfig | 13 +
> drivers/media/platform/google/Makefile | 3 +
> drivers/media/platform/google/chv3-video.c | 891 +++++++++++++++++++++
> 5 files changed, 909 insertions(+)
> create mode 100644 drivers/media/platform/google/Kconfig
> create mode 100644 drivers/media/platform/google/Makefile
> create mode 100644 drivers/media/platform/google/chv3-video.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..9674a4c12e2d
> --- /dev/null
> +++ b/drivers/media/platform/google/Kconfig
> @@ -0,0 +1,13 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +config VIDEO_CHAMELEONV3
> + tristate "Google Chameleon v3 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 video interface to
> + capture a single video source from a given HDMI or DP connector
> + and write the resulting frames to memory.
> diff --git a/drivers/media/platform/google/Makefile b/drivers/media/platform/google/Makefile
> new file mode 100644
> index 000000000000..cff06486244c
> --- /dev/null
> +++ b/drivers/media/platform/google/Makefile
> @@ -0,0 +1,3 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +obj-$(CONFIG_VIDEO_CHAMELEONV3) += chv3-video.o
> diff --git a/drivers/media/platform/google/chv3-video.c b/drivers/media/platform/google/chv3-video.c
> new file mode 100644
> index 000000000000..6e782484abaf
> --- /dev/null
> +++ b/drivers/media/platform/google/chv3-video.c
> @@ -0,0 +1,891 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2023-2024 Google LLC.
> + * Author: Paweł Anikiel <panikiel@google.com>
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/v4l2-dv-timings.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-video"
> +
> +#define VIDEO_EN 0x00
> +#define VIDEO_EN_BIT BIT(0)
> +#define VIDEO_HEIGHT 0x04
> +#define VIDEO_WIDTH 0x08
> +#define VIDEO_BUFFERA 0x0c
> +#define VIDEO_BUFFERB 0x10
> +#define VIDEO_BUFFERSIZE 0x14
> +#define VIDEO_RESET 0x18
> +#define VIDEO_RESET_BIT BIT(0)
> +#define VIDEO_ERRORSTATUS 0x1c
> +#define VIDEO_IOCOLOR 0x20
> +#define VIDEO_DATARATE 0x24
> +#define VIDEO_DATARATE_SINGLE 0x0
> +#define VIDEO_DATARATE_DOUBLE 0x1
> +#define VIDEO_PIXELMODE 0x28
> +#define VIDEO_PIXELMODE_SINGLE 0x0
> +#define VIDEO_PIXELMODE_DOUBLE 0x1
> +#define VIDEO_SYNCPOLARITY 0x2c
> +#define VIDEO_DMAFORMAT 0x30
> +#define VIDEO_DMAFORMAT_8BPC 0x0
> +#define VIDEO_DMAFORMAT_10BPC_UPPER 0x1
> +#define VIDEO_DMAFORMAT_10BPC_LOWER 0x2
> +#define VIDEO_DMAFORMAT_12BPC_UPPER 0x3
> +#define VIDEO_DMAFORMAT_12BPC_LOWER 0x4
> +#define VIDEO_DMAFORMAT_16BPC 0x5
> +#define VIDEO_DMAFORMAT_RAW 0x6
> +#define VIDEO_DMAFORMAT_8BPC_PAD 0x7
> +#define VIDEO_VERSION 0x34
> +#define VIDEO_VERSION_CURRENT 0xc0fb0001
> +
> +#define VIDEO_IRQ_MASK 0x8
> +#define VIDEO_IRQ_CLR 0xc
> +#define VIDEO_IRQ_ALL 0xf
> +#define VIDEO_IRQ_BUFF0 BIT(0)
> +#define VIDEO_IRQ_BUFF1 BIT(1)
> +#define VIDEO_IRQ_RESOLUTION BIT(2)
> +#define VIDEO_IRQ_ERROR BIT(3)
> +
> +struct chv3_video {
> + 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;
> + u32 bytes_per_pixel;
> +
> + struct v4l2_ctrl_handler ctrl_handler;
> + 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 video_lock;
> +};
> +
> +struct chv3_video_buffer {
> + struct vb2_v4l2_buffer vb;
> + struct list_head link;
> +};
> +
> +struct chv3_video_config {
> + u32 pixelformat;
> + u32 bytes_per_pixel;
> + u32 dmaformat;
> +};
> +
> +static void chv3_video_set_format_resolution(struct chv3_video *video, u32 width, u32 height)
> +{
> + video->pix_fmt.width = width;
> + video->pix_fmt.height = height;
> + video->pix_fmt.bytesperline = width * video->bytes_per_pixel;
> + video->pix_fmt.sizeimage = video->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_video_query_dv_timings_fallback(struct chv3_video *video,
> + struct v4l2_dv_timings *timings)
> +{
> + u32 width, height;
> +
> + width = readl(video->iobase + VIDEO_WIDTH);
> + height = readl(video->iobase + VIDEO_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;
> + timings->bt.pixelclock = width * height * 24;
> +
> + return 0;
> +}
> +
> +static int chv3_video_query_dv_timings(struct chv3_video *video, struct v4l2_dv_timings *timings)
> +{
> + if (video->subdev) {
> + return v4l2_subdev_call(video->subdev, pad, query_dv_timings,
> + video->subdev_source_pad, timings);
> + } else {
> + return chv3_video_query_dv_timings_fallback(video, timings);
> + }
I would move the contents of chv3_video_query_dv_timings_fallback() to this
function and drop the old fallback function. It makes more sense if it is all
in the same function.
> +}
> +
> +static const struct v4l2_dv_timings_cap chv3_video_fallback_dv_timings_cap = {
> + .type = V4L2_DV_BT_656_1120,
> + .bt = {
> + .min_width = 640,
> + .max_width = 7680,
> + .min_height = 480,
> + .max_height = 4320,
> + .min_pixelclock = 25000000,
> + .max_pixelclock = 1080000000,
> + .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_video_enum_dv_timings_fallback(struct chv3_video *video,
> + struct v4l2_enum_dv_timings *timings)
> +{
> + return v4l2_enum_dv_timings_cap(timings, &chv3_video_fallback_dv_timings_cap,
> + NULL, NULL);
> +}
> +
> +static int chv3_video_dv_timings_cap_fallback(struct chv3_video *video,
> + struct v4l2_dv_timings_cap *cap)
> +{
> + *cap = chv3_video_fallback_dv_timings_cap;
> +
> + return 0;
> +}
Same for these two fallback functions: move them to the functions that calls them.
> +
> +static void chv3_video_apply_dv_timings(struct chv3_video *video)
> +{
> + struct v4l2_dv_timings timings;
> + int res;
> +
> + res = chv3_video_query_dv_timings(video, &timings);
> + if (res)
> + return;
> +
> + video->timings = timings;
> + chv3_video_set_format_resolution(video, timings.bt.width, timings.bt.height);
> +}
> +
> +static int chv3_video_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_video_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt)
> +{
> + struct chv3_video *video = video_drvdata(file);
> +
> + fmt->fmt.pix = video->pix_fmt;
> +
> + return 0;
> +}
> +
> +static int chv3_video_enum_fmt_vid_cap(struct file *file, void *fh, struct v4l2_fmtdesc *fmt)
> +{
> + struct chv3_video *video = video_drvdata(file);
> +
> + if (fmt->index != 0)
> + return -EINVAL;
> +
> + fmt->flags = 0;
> + fmt->pixelformat = video->pix_fmt.pixelformat;
> +
> + return 0;
> +}
> +
> +static int chv3_video_g_input(struct file *file, void *fh, unsigned int *index)
> +{
> + *index = 0;
> +
> + return 0;
> +}
> +
> +static int chv3_video_s_input(struct file *file, void *fh, unsigned int index)
> +{
> + if (index != 0)
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static int chv3_video_enum_input(struct file *file, void *fh, struct v4l2_input *input)
> +{
> + if (input->index != 0)
> + return -EINVAL;
> +
> + strscpy(input->name, "input0", sizeof(input->name));
This name is not terribly user friendly. Is it possible to determine a more human
readable name? E.g. "DP1", "DP2", etc. Something that matches labeling on the Chameleon
board.
> + input->type = V4L2_INPUT_TYPE_CAMERA;
> + input->capabilities = V4L2_IN_CAP_DV_TIMINGS;
> +
> + return 0;
> +}
> +
> +static int chv3_video_g_edid(struct file *file, void *fh, struct v4l2_edid *edid)
> +{
> + struct chv3_video *video = video_drvdata(file);
> + int res;
> +
> + if (!video->subdev)
> + return -ENOTTY;
> +
> + if (edid->pad != 0)
> + return -EINVAL;
> +
> + edid->pad = video->subdev_source_pad;
> + res = v4l2_subdev_call(video->subdev, pad, get_edid, edid);
> + edid->pad = 0;
> +
> + return res;
> +}
> +
> +static int chv3_video_s_edid(struct file *file, void *fh, struct v4l2_edid *edid)
> +{
> + struct chv3_video *video = video_drvdata(file);
> + int res;
> +
> + if (!video->subdev)
> + return -ENOTTY;
> +
> + if (edid->pad != 0)
> + return -EINVAL;
> +
> + edid->pad = video->subdev_source_pad;
> + res = v4l2_subdev_call(video->subdev, pad, set_edid, edid);
> + edid->pad = 0;
> +
> + return res;
> +}
> +
> +static int chv3_video_s_dv_timings(struct file *file, void *fh, struct v4l2_dv_timings *timings)
> +{
> + struct chv3_video *video = video_drvdata(file);
> +
> + if (v4l2_match_dv_timings(&video->timings, timings, 0, false))
> + return 0;
> +
> + if (vb2_is_busy(&video->queue))
> + return -EBUSY;
This should be moved to after the next 'if'.
> +
> + if (!v4l2_valid_dv_timings(timings, &chv3_video_fallback_dv_timings_cap, NULL, NULL))
> + return -ERANGE;
> +
> + video->timings = *timings;
> + chv3_video_set_format_resolution(video, timings->bt.width, timings->bt.height);
> +
> + return 0;
> +}
> +
> +static int chv3_video_g_dv_timings(struct file *file, void *fh, struct v4l2_dv_timings *timings)
> +{
> + struct chv3_video *video = video_drvdata(file);
> +
> + *timings = video->timings;
> + return 0;
> +}
> +
> +static int chv3_video_vidioc_query_dv_timings(struct file *file, void *fh,
> + struct v4l2_dv_timings *timings)
> +{
> + struct chv3_video *video = video_drvdata(file);
> +
> + return chv3_video_query_dv_timings(video, timings);
> +}
> +
> +static int chv3_video_enum_dv_timings(struct file *file, void *fh,
> + struct v4l2_enum_dv_timings *timings)
> +{
> + struct chv3_video *video = video_drvdata(file);
> + int res;
> +
> + if (timings->pad != 0)
> + return -EINVAL;
> +
> + if (video->subdev) {
> + timings->pad = video->subdev_source_pad;
> + res = v4l2_subdev_call(video->subdev, pad, enum_dv_timings, timings);
> + timings->pad = 0;
> + return res;
> + } else {
> + return chv3_video_enum_dv_timings_fallback(video, timings);
It is much easier to read if the contents of chv3_video_enum_dv_timings_fallback
is moved here.
> + }
> +}
> +
> +static int chv3_video_dv_timings_cap(struct file *file, void *fh, struct v4l2_dv_timings_cap *cap)
> +{
> + struct chv3_video *video = video_drvdata(file);
> + int res;
> +
> + if (cap->pad != 0)
> + return -EINVAL;
> +
> + if (video->subdev) {
> + cap->pad = video->subdev_source_pad;
> + res = v4l2_subdev_call(video->subdev, pad, dv_timings_cap, cap);
> + cap->pad = 0;
> + return res;
> + } else {
> + return chv3_video_dv_timings_cap_fallback(video, cap);
Ditto.
> + }
> +}
> +
> +static int chv3_video_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_video_v4l2_ioctl_ops = {
> + .vidioc_querycap = chv3_video_querycap,
> +
> + .vidioc_enum_fmt_vid_cap = chv3_video_enum_fmt_vid_cap,
> + .vidioc_g_fmt_vid_cap = chv3_video_g_fmt_vid_cap,
> + .vidioc_s_fmt_vid_cap = chv3_video_g_fmt_vid_cap,
> + .vidioc_try_fmt_vid_cap = chv3_video_g_fmt_vid_cap,
> +
> + .vidioc_enum_input = chv3_video_enum_input,
> + .vidioc_g_input = chv3_video_g_input,
> + .vidioc_s_input = chv3_video_s_input,
> + .vidioc_g_edid = chv3_video_g_edid,
> + .vidioc_s_edid = chv3_video_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_video_s_dv_timings,
> + .vidioc_g_dv_timings = chv3_video_g_dv_timings,
> + .vidioc_query_dv_timings = chv3_video_vidioc_query_dv_timings,
> + .vidioc_enum_dv_timings = chv3_video_enum_dv_timings,
> + .vidioc_dv_timings_cap = chv3_video_dv_timings_cap,
> +
> + .vidioc_subscribe_event = chv3_video_subscribe_event,
> + .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
> +};
> +
> +static int chv3_video_queue_setup(struct vb2_queue *q,
> + unsigned int *nbuffers, unsigned int *nplanes,
> + unsigned int sizes[], struct device *alloc_devs[])
> +{
> + struct chv3_video *video = vb2_get_drv_priv(q);
> +
> + if (*nplanes) {
> + if (sizes[0] < video->pix_fmt.sizeimage)
> + return -EINVAL;
> + return 0;
> + }
> + *nplanes = 1;
> + sizes[0] = video->pix_fmt.sizeimage;
> +
> + return 0;
> +}
> +
> +/*
> + * There are two address registers: BUFFERA and BUFFERB. The device
> + * 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_video_buffer_dma_addr(struct chv3_video_buffer *buf)
> +{
> + return vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
> +}
> +
> +static void chv3_video_start_frame(struct chv3_video *video, struct chv3_video_buffer *buf)
> +{
> + video->writing_to_a = 1;
> + writel(chv3_video_buffer_dma_addr(buf), video->iobase + VIDEO_BUFFERA);
> + writel(VIDEO_EN_BIT, video->iobase + VIDEO_EN);
> +}
> +
> +static void chv3_video_next_frame(struct chv3_video *video, struct chv3_video_buffer *buf)
> +{
> + u32 reg = video->writing_to_a ? VIDEO_BUFFERB : VIDEO_BUFFERA;
> +
> + writel(chv3_video_buffer_dma_addr(buf), video->iobase + reg);
> +}
> +
> +static int chv3_video_start_streaming(struct vb2_queue *q, unsigned int count)
> +{
> + struct chv3_video *video = vb2_get_drv_priv(q);
> + struct chv3_video_buffer *buf;
> + unsigned long flags;
> +
> + video->sequence = 0;
> + writel(video->pix_fmt.sizeimage, video->iobase + VIDEO_BUFFERSIZE);
> +
> + spin_lock_irqsave(&video->bufs_lock, flags);
> + buf = list_first_entry_or_null(&video->bufs, struct chv3_video_buffer, link);
> + if (buf) {
> + chv3_video_start_frame(video, buf);
> + if (!list_is_last(&buf->link, &video->bufs))
> + chv3_video_next_frame(video, list_next_entry(buf, link));
> + }
> + spin_unlock_irqrestore(&video->bufs_lock, flags);
> +
> + return 0;
> +}
> +
> +static void chv3_video_stop_streaming(struct vb2_queue *q)
> +{
> + struct chv3_video *video = vb2_get_drv_priv(q);
> + struct chv3_video_buffer *buf;
> + unsigned long flags;
> +
> + writel(0, video->iobase + VIDEO_EN);
> +
> + spin_lock_irqsave(&video->bufs_lock, flags);
> + list_for_each_entry(buf, &video->bufs, link)
> + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
> + INIT_LIST_HEAD(&video->bufs);
> + spin_unlock_irqrestore(&video->bufs_lock, flags);
> +}
> +
> +static void chv3_video_buf_queue(struct vb2_buffer *vb)
> +{
> + struct chv3_video *video = vb2_get_drv_priv(vb->vb2_queue);
> + struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> + struct chv3_video_buffer *buf = container_of(v4l2_buf, struct chv3_video_buffer, vb);
> + bool first, second;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&video->bufs_lock, flags);
> + first = list_empty(&video->bufs);
> + second = list_is_singular(&video->bufs);
> + list_add_tail(&buf->link, &video->bufs);
> + if (vb2_is_streaming(vb->vb2_queue)) {
This should be vb2_start_streaming_called().
It does not matter all that much in this driver, since VIDIOC_STREAMON will
also call start_streaming, even if there are no buffers queued since the
vb2_queue min_queued_buffers field is 0. But if that ever changes, then
vb2_start_streaming_called() is the right call here.
> + if (first)
> + chv3_video_start_frame(video, buf);
> + else if (second)
> + chv3_video_next_frame(video, buf);
> + }
> + spin_unlock_irqrestore(&video->bufs_lock, flags);
> +}
> +
> +static const struct vb2_ops chv3_video_vb2_ops = {
> + .queue_setup = chv3_video_queue_setup,
> + .wait_prepare = vb2_ops_wait_prepare,
> + .wait_finish = vb2_ops_wait_finish,
> + .start_streaming = chv3_video_start_streaming,
> + .stop_streaming = chv3_video_stop_streaming,
> + .buf_queue = chv3_video_buf_queue,
> +};
> +
> +static int chv3_video_open(struct file *file)
> +{
> + struct chv3_video *video = video_drvdata(file);
> + int res;
> +
> + mutex_lock(&video->video_lock);
> + res = v4l2_fh_open(file);
> + if (!res) {
> + if (v4l2_fh_is_singular_file(file))
> + chv3_video_apply_dv_timings(video);
> + }
> + mutex_unlock(&video->video_lock);
> +
> + return res;
> +}
> +
> +static const struct v4l2_file_operations chv3_video_v4l2_fops = {
> + .owner = THIS_MODULE,
> + .open = chv3_video_open,
> + .release = vb2_fop_release,
> + .unlocked_ioctl = video_ioctl2,
> + .mmap = vb2_fop_mmap,
> + .poll = vb2_fop_poll,
> +};
> +
> +static void chv3_video_frame_irq(struct chv3_video *video)
> +{
> + struct chv3_video_buffer *buf;
> +
> + spin_lock(&video->bufs_lock);
> +
> + buf = list_first_entry_or_null(&video->bufs, struct chv3_video_buffer, link);
> + if (!buf)
> + goto empty;
> + list_del(&buf->link);
> +
> + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, video->pix_fmt.sizeimage);
> + buf->vb.vb2_buf.timestamp = ktime_get_ns();
> + buf->vb.sequence = video->sequence++;
> + buf->vb.field = V4L2_FIELD_NONE;
> + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
> +
> + buf = list_first_entry_or_null(&video->bufs, struct chv3_video_buffer, link);
> + if (buf) {
> + video->writing_to_a = !video->writing_to_a;
> + if (!list_is_last(&buf->link, &video->bufs))
> + chv3_video_next_frame(video, list_next_entry(buf, link));
> + } else {
> + writel(0, video->iobase + VIDEO_EN);
> + }
> +empty:
> + spin_unlock(&video->bufs_lock);
> +}
> +
> +static void chv3_video_error_irq(struct chv3_video *video)
> +{
> + if (vb2_is_streaming(&video->queue))
> + vb2_queue_error(&video->queue);
> +}
> +
> +static void chv3_video_resolution_irq(struct chv3_video *video)
> +{
> + static const struct v4l2_event event = {
> + .type = V4L2_EVENT_SOURCE_CHANGE,
> + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION,
> + };
> +
> + v4l2_event_queue(&video->vdev, &event);
> + chv3_video_error_irq(video);
> +}
> +
> +static irqreturn_t chv3_video_isr(int irq, void *data)
> +{
> + struct chv3_video *video = data;
> + unsigned int reg;
> +
> + reg = readl(video->iobase_irq + VIDEO_IRQ_CLR);
> + if (!reg)
> + return IRQ_NONE;
> +
> + if (reg & VIDEO_IRQ_BUFF0)
> + chv3_video_frame_irq(video);
> + if (reg & VIDEO_IRQ_BUFF1)
> + chv3_video_frame_irq(video);
> + if (reg & VIDEO_IRQ_RESOLUTION)
> + chv3_video_resolution_irq(video);
> + if (reg & VIDEO_IRQ_ERROR) {
> + dev_warn(video->dev, "error: 0x%x\n",
> + readl(video->iobase + VIDEO_ERRORSTATUS));
> + chv3_video_error_irq(video);
> + }
> +
> + writel(reg, video->iobase_irq + VIDEO_IRQ_CLR);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int chv3_video_check_version(struct chv3_video *video)
> +{
> + u32 version;
> +
> + version = readl(video->iobase + VIDEO_VERSION);
> + if (version != VIDEO_VERSION_CURRENT) {
> + dev_err(video->dev,
> + "wrong hw version: expected %x, got %x\n",
> + VIDEO_VERSION_CURRENT, version);
> + return -ENODEV;
> + }
> + return 0;
> +}
> +
> +static void chv3_video_init_timings_and_format(struct chv3_video *video,
> + const struct chv3_video_config *config)
> +{
> + struct v4l2_pix_format *pix = &video->pix_fmt;
> + struct v4l2_dv_timings timings = V4L2_DV_BT_CEA_1920X1080P60;
> +
> + video->timings = timings;
> + video->bytes_per_pixel = config->bytes_per_pixel;
> +
> + pix->pixelformat = config->pixelformat;
> + pix->field = V4L2_FIELD_NONE;
> + pix->colorspace = V4L2_COLORSPACE_SRGB;
> + chv3_video_set_format_resolution(video, timings.bt.width, timings.bt.height);
> +}
> +
> +#define notifier_to_video(nf) container_of(nf, struct chv3_video, notifier)
> +
> +static int chv3_video_async_notify_bound(struct v4l2_async_notifier *notifier,
> + struct v4l2_subdev *subdev,
> + struct v4l2_async_connection *asc)
> +{
> + struct chv3_video *video = notifier_to_video(notifier);
> + int pad;
> +
> + pad = media_entity_get_fwnode_pad(&subdev->entity, asc->match.fwnode,
> + MEDIA_PAD_FL_SOURCE);
> + if (pad < 0)
> + return pad;
> +
> + video->subdev = subdev;
> + video->subdev_source_pad = pad;
> +
> + video->v4l2_dev.ctrl_handler = subdev->ctrl_handler;
> +
> + return 0;
> +}
> +
> +static void chv3_video_async_notify_unbind(struct v4l2_async_notifier *notifier,
> + struct v4l2_subdev *subdev,
> + struct v4l2_async_connection *asc)
> +{
> + struct chv3_video *video = notifier_to_video(notifier);
> +
> + vb2_video_unregister_device(&video->vdev);
> +}
> +
> +static int chv3_video_async_notify_complete(struct v4l2_async_notifier *notifier)
> +{
> + struct chv3_video *video = notifier_to_video(notifier);
> +
> + return video_register_device(&video->vdev, VFL_TYPE_VIDEO, -1);
> +}
> +
> +static const struct v4l2_async_notifier_operations chv3_video_async_notify_ops = {
> + .bound = chv3_video_async_notify_bound,
> + .unbind = chv3_video_async_notify_unbind,
> + .complete = chv3_video_async_notify_complete,
> +};
> +
> +static int chv3_video_fallback_init(struct chv3_video *video)
> +{
> + int res;
> +
> + video->subdev = NULL;
> + video->subdev_source_pad = 0;
> +
> + v4l2_ctrl_handler_init(&video->ctrl_handler, 1);
> + v4l2_ctrl_new_std(&video->ctrl_handler, NULL,
> + V4L2_CID_DV_RX_POWER_PRESENT, 0, 1, 0, 0);
> + res = video->ctrl_handler.error;
> + if (res)
> + goto handler_free;
> +
> + video->v4l2_dev.ctrl_handler = &video->ctrl_handler;
> +
> + res = video_register_device(&video->vdev, VFL_TYPE_VIDEO, -1);
> + if (res)
> + goto handler_free;
> +
> + return 0;
> +
> +handler_free:
> + v4l2_ctrl_handler_free(&video->ctrl_handler);
> +
> + return res;
> +}
> +
> +static int chv3_video_fwnode_init(struct chv3_video *video)
> +{
> + struct v4l2_async_connection *asc;
> + struct fwnode_handle *endpoint;
> + int res;
> +
> + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(video->dev), NULL);
> + if (!endpoint)
> + return -EINVAL;
> +
> + v4l2_async_nf_init(&video->notifier, &video->v4l2_dev);
> +
> + asc = v4l2_async_nf_add_fwnode_remote(&video->notifier, endpoint,
> + struct v4l2_async_connection);
> + fwnode_handle_put(endpoint);
> +
> + if (IS_ERR(asc))
> + return PTR_ERR(asc);
> +
> + video->notifier.ops = &chv3_video_async_notify_ops;
> + res = v4l2_async_nf_register(&video->notifier);
> + if (res) {
> + v4l2_async_nf_cleanup(&video->notifier);
> + return res;
> + }
> +
> + return 0;
> +}
> +
> +static int chv3_video_probe(struct platform_device *pdev)
> +{
> + struct chv3_video *video;
> + const struct chv3_video_config *config;
> + int res;
> + int irq;
> +
> + video = devm_kzalloc(&pdev->dev, sizeof(*video), GFP_KERNEL);
> + if (!video)
> + return -ENOMEM;
> + video->dev = &pdev->dev;
> + platform_set_drvdata(pdev, video);
> +
> + config = device_get_match_data(video->dev);
> +
> + /* map register space */
> + video->iobase = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(video->iobase))
> + return PTR_ERR(video->iobase);
> +
> + video->iobase_irq = devm_platform_ioremap_resource(pdev, 1);
> + if (IS_ERR(video->iobase_irq))
> + return PTR_ERR(video->iobase_irq);
> +
> + /* check hw version */
> + res = chv3_video_check_version(video);
> + 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_video_isr, 0, DEVICE_NAME, video);
> + if (res)
> + return res;
> +
> + /* initialize v4l2_device */
> + res = v4l2_device_register(&pdev->dev, &video->v4l2_dev);
> + if (res)
> + return res;
> +
> + /* initialize vb2 queue */
> + video->queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
> + video->queue.io_modes = VB2_MMAP | VB2_DMABUF;
> + video->queue.dev = &pdev->dev;
> + video->queue.lock = &video->video_lock;
> + video->queue.ops = &chv3_video_vb2_ops;
> + video->queue.mem_ops = &vb2_dma_contig_memops;
> + video->queue.drv_priv = video;
> + video->queue.buf_struct_size = sizeof(struct chv3_video_buffer);
> + video->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> + res = vb2_queue_init(&video->queue);
> + if (res)
> + goto error;
> +
> + /* initialize video_device */
> + strscpy(video->vdev.name, DEVICE_NAME, sizeof(video->vdev.name));
> + video->vdev.fops = &chv3_video_v4l2_fops;
> + video->vdev.ioctl_ops = &chv3_video_v4l2_ioctl_ops;
> + video->vdev.lock = &video->video_lock;
> + video->vdev.release = video_device_release_empty;
> + video->vdev.device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
> + video->vdev.v4l2_dev = &video->v4l2_dev;
> + video->vdev.queue = &video->queue;
> + video_set_drvdata(&video->vdev, video);
> +
> + if (device_get_named_child_node(&pdev->dev, "port"))
> + res = chv3_video_fwnode_init(video);
> + else
> + res = chv3_video_fallback_init(video);
> + if (res)
> + goto error;
> +
> + /* initialize rest of driver struct */
> + INIT_LIST_HEAD(&video->bufs);
> + spin_lock_init(&video->bufs_lock);
> + mutex_init(&video->video_lock);
> +
> + chv3_video_init_timings_and_format(video, config);
> +
> + /* initialize hw */
> + writel(VIDEO_RESET_BIT, video->iobase + VIDEO_RESET);
> + writel(VIDEO_DATARATE_DOUBLE, video->iobase + VIDEO_DATARATE);
> + writel(VIDEO_PIXELMODE_DOUBLE, video->iobase + VIDEO_PIXELMODE);
> + writel(config->dmaformat, video->iobase + VIDEO_DMAFORMAT);
> +
> + writel(VIDEO_IRQ_ALL, video->iobase_irq + VIDEO_IRQ_MASK);
> +
> + return 0;
> +
> +error:
> + v4l2_device_unregister(&video->v4l2_dev);
> +
> + return res;
> +}
> +
> +static void chv3_video_remove(struct platform_device *pdev)
> +{
> + struct chv3_video *video = platform_get_drvdata(pdev);
> +
> + /* disable interrupts */
> + writel(0, video->iobase_irq + VIDEO_IRQ_MASK);
> +
> + if (video->subdev) {
> + /* notifier is initialized only in non-fallback mode */
> + v4l2_async_nf_unregister(&video->notifier);
> + v4l2_async_nf_cleanup(&video->notifier);
> + } else {
> + /* ctrl handler is initialized only in fallback mode */
> + v4l2_ctrl_handler_free(&video->ctrl_handler);
> + }
> +
> + v4l2_device_unregister(&video->v4l2_dev);
> +}
> +
> +static const struct chv3_video_config chv3_video_it = {
> + .pixelformat = V4L2_PIX_FMT_BGRX32,
> + .bytes_per_pixel = 4,
> + .dmaformat = VIDEO_DMAFORMAT_8BPC_PAD,
> +};
> +
> +static const struct chv3_video_config chv3_video_dp = {
> + .pixelformat = V4L2_PIX_FMT_RGB24,
> + .bytes_per_pixel = 3,
> + .dmaformat = VIDEO_DMAFORMAT_8BPC,
> +};
> +
> +static const struct of_device_id chv3_video_match_table[] = {
> + { .compatible = "google,chv3-video-it-1.0", .data = &chv3_video_it },
> + { .compatible = "google,chv3-video-dp-1.0", .data = &chv3_video_dp },
> + { },
> +};
> +
> +static struct platform_driver chv3_video_platform_driver = {
> + .probe = chv3_video_probe,
> + .remove_new = chv3_video_remove,
> + .driver = {
> + .name = DEVICE_NAME,
> + .of_match_table = chv3_video_match_table,
> + },
> +};
> +
> +module_platform_driver(chv3_video_platform_driver);
> +
> +MODULE_AUTHOR("Paweł Anikiel <panikiel@google.com>");
> +MODULE_DESCRIPTION("Google Chameleon v3 video interface driver");
> +MODULE_LICENSE("GPL");
Regards,
Hans
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3 07/10] media: intel: Add Displayport RX IP driver
2024-05-07 15:54 ` [PATCH v3 07/10] media: intel: Add Displayport RX IP driver Paweł Anikiel
@ 2024-06-03 8:37 ` Hans Verkuil
2024-06-04 12:32 ` Paweł Anikiel
0 siblings, 1 reply; 26+ messages in thread
From: Hans Verkuil @ 2024-06-03 8:37 UTC (permalink / raw)
To: Paweł Anikiel, airlied, akpm, conor+dt, daniel, dinguyen,
krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
robh+dt, tzimmermann
Cc: devicetree, dri-devel, linux-kernel, linux-media,
chromeos-krk-upstreaming
On 07/05/2024 17:54, Paweł Anikiel wrote:
> Add v4l2 subdev driver for the Intel Displayport receiver FPGA IP.
> It is a part of the DisplayPort Intel FPGA IP Core, and supports
> DisplayPort 1.4, HBR3 video capture and Multi-Stream Transport.
>
> Signed-off-by: Paweł Anikiel <panikiel@google.com>
> ---
> drivers/media/platform/intel/Kconfig | 12 +
> drivers/media/platform/intel/Makefile | 1 +
> drivers/media/platform/intel/intel-dprx.c | 2283 +++++++++++++++++++++
> 3 files changed, 2296 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..734f6c2395bc
> --- /dev/null
> +++ b/drivers/media/platform/intel/intel-dprx.c
> @@ -0,0 +1,2283 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2023-2024 Google LLC.
> + * Author: Paweł Anikiel <panikiel@google.com>
> + */
> +
> +#include <linux/crc-dp.h>
> +#include <linux/delay.h>
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-dv-timings.h>
> +#include <media/v4l2-fwnode.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 v4l2_ctrl_handler ctrl_handler;
> + struct media_pad pads[5];
> +
> + 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;
> +
> + bool hpd_state;
> +};
> +
> +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 int dprx_pad_to_sink_idx(struct dprx *dprx, int pad)
> +{
> + int sink_idx = pad - 1;
> +
> + if (sink_idx < 0 || sink_idx >= dprx->max_stream_count)
> + return -1;
> + else
> + return sink_idx;
> +}
> +
> +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;
> + int sink_idx;
> + u32 end_block = edid->start_block + edid->blocks;
> + unsigned long flags;
> + int res = 0;
> +
> + memset(edid->reserved, 0, sizeof(edid->reserved));
> +
> + sink_idx = dprx_pad_to_sink_idx(dprx, edid->pad);
> + if (sink_idx < 0)
> + return -EINVAL;
> + sink = &dprx->sinks[sink_idx];
> +
> + spin_lock_irqsave(&dprx->lock, flags);
> +
> + 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;
> + int sink_idx;
> + bool prev_hpd;
> + bool cur_hpd;
> + unsigned long flags;
> +
> + memset(edid->reserved, 0, sizeof(edid->reserved));
> +
> + sink_idx = dprx_pad_to_sink_idx(dprx, edid->pad);
> + if (sink_idx < 0)
> + return -EINVAL;
> + sink = &dprx->sinks[sink_idx];
> +
> + if (edid->start_block != 0)
> + return -EINVAL;
> + if (edid->blocks > DPRX_MAX_EDID_BLOCKS) {
> + edid->blocks = DPRX_MAX_EDID_BLOCKS;
> + return -E2BIG;
> + }
> +
> + prev_hpd = dprx->hpd_state;
> + /*
> + * 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.
> + */
> + if (sink_idx == 0)
> + dprx->hpd_state = edid->blocks > 0;
> + cur_hpd = dprx->hpd_state;
> +
> + if (prev_hpd)
> + dprx_set_hpd(dprx, 0);
> +
> + spin_lock_irqsave(&dprx->lock, flags);
> + sink->blocks = edid->blocks;
> + memcpy(sink->edid, edid->edid, edid->blocks * 128);
> + if (cur_hpd)
> + dprx_reset(dprx);
> + spin_unlock_irqrestore(&dprx->lock, flags);
> +
> + if (cur_hpd) {
> + if (prev_hpd) {
> + /*
> + * Some DisplayPort sources are not happy with 100ms
> + * HPD pulses. Looking at the AUX message log, it
> + * seemed the source was waiting for values of some
> + * DPCD registers to change when it shouldn't be.
> + *
> + * Not sure whose fault that is, but 500ms appears
> + * to be a safe duration for the source to "forget"
> + * about the sink.
> + */
> + msleep(500);
> + }
> + 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);
> + int sink_idx;
> + u32 htotal, vtotal;
> + u32 hsp, hsw;
> + u32 hstart, vstart;
> + u32 vsp, vsw;
> + u32 hwidth, vheight;
> +
> + sink_idx = dprx_pad_to_sink_idx(dprx, pad);
> + if (sink_idx < 0)
> + return -EINVAL;
> +
> + if (!((dprx_read(dprx, DPRX_VBID(sink_idx)) >> DPRX_VBID_MSA_LOCK) & 1))
> + return -ENOLINK;
> +
> + htotal = dprx_read(dprx, DPRX_MSA_HTOTAL(sink_idx));
> + vtotal = dprx_read(dprx, DPRX_MSA_VTOTAL(sink_idx));
> + hsp = dprx_read(dprx, DPRX_MSA_HSP(sink_idx));
> + hsw = dprx_read(dprx, DPRX_MSA_HSW(sink_idx));
> + hstart = dprx_read(dprx, DPRX_MSA_HSTART(sink_idx));
> + vstart = dprx_read(dprx, DPRX_MSA_VSTART(sink_idx));
> + vsp = dprx_read(dprx, DPRX_MSA_VSP(sink_idx));
> + vsw = dprx_read(dprx, DPRX_MSA_VSW(sink_idx));
> + hwidth = dprx_read(dprx, DPRX_MSA_HWIDTH(sink_idx));
> + vheight = dprx_read(dprx, DPRX_MSA_VHEIGHT(sink_idx));
> +
> + 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.pixelclock = htotal * vtotal * 24;
> + 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 = 640,
> + .max_width = 7680,
> + .min_height = 480,
> + .max_height = 4320,
> + .min_pixelclock = 25000000,
> + .max_pixelclock = 1080000000,
> + .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_parse_bus_cfg(struct dprx *dprx, struct v4l2_fwnode_endpoint *bus_cfg)
> +{
> + u64 max_freq = 0;
> + int i;
> +
> + for (i = 0; i < bus_cfg->nr_of_link_frequencies; i++) {
> + if (max_freq < bus_cfg->link_frequencies[i])
> + max_freq = bus_cfg->link_frequencies[i];
> + }
> +
> + switch (max_freq) {
> + case 1620000000:
> + dprx->max_link_rate = 0x06;
> + break;
> + case 2700000000:
> + dprx->max_link_rate = 0x0a;
> + break;
> + case 5400000000:
> + dprx->max_link_rate = 0x14;
> + break;
> + case 8100000000:
> + dprx->max_link_rate = 0x1e;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + dprx->max_lane_count = bus_cfg->bus.displayport.num_data_lanes;
> + dprx->multi_stream_support = bus_cfg->bus.displayport.multi_stream_support;
> +
> + return 0;
> +}
> +
> +static int dprx_parse_fwnode(struct dprx *dprx)
> +{
> + struct fwnode_handle *fwnode = dev_fwnode(dprx->dev);
> + struct fwnode_handle *endpoint;
> + struct v4l2_fwnode_endpoint bus_cfg = {
> + .bus_type = V4L2_MBUS_DISPLAYPORT
> + };
> + int res;
> +
> + /* get input port node */
> + endpoint = fwnode_graph_get_next_endpoint(fwnode, NULL);
> + if (!endpoint)
> + return -EINVAL;
> +
> + res = v4l2_fwnode_endpoint_alloc_parse(endpoint, &bus_cfg);
> + if (res) {
> + fwnode_handle_put(endpoint);
> + return res;
> + }
> +
> + res = dprx_parse_bus_cfg(dprx, &bus_cfg);
> + if (res) {
> + v4l2_fwnode_endpoint_free(&bus_cfg);
> + fwnode_handle_put(endpoint);
> + return res;
> + }
> +
> + v4l2_fwnode_endpoint_free(&bus_cfg);
> +
> + /* count the number of output port nodes */
> + endpoint = fwnode_graph_get_next_endpoint(fwnode, endpoint);
> + dprx->max_stream_count = 0;
> + while (endpoint) {
> + endpoint = fwnode_graph_get_next_endpoint(fwnode, endpoint);
> + dprx->max_stream_count++;
> + }
> +
> + return 0;
> +}
> +
> +static int dprx_probe(struct platform_device *pdev)
> +{
> + struct dprx *dprx;
> + int irq;
> + int res;
> + int i;
> +
> + 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 = dprx_parse_fwnode(dprx);
> + if (res)
> + return res;
> +
> + 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_DV_DECODER;
> + dprx->subdev.entity.ops = &dprx_entity_ops;
> +
> + v4l2_ctrl_handler_init(&dprx->ctrl_handler, 1);
> + v4l2_ctrl_new_std(&dprx->ctrl_handler, NULL,
> + V4L2_CID_DV_RX_POWER_PRESENT, 0, 1, 0, 0);
You are creating this control, but it is never set to 1 when the driver detects
that a source is connected. I am wondering if POWER_PRESENT makes sense for a
DisplayPort connector. Is there a clean way for a sink driver to detect if a
source is connected? For HDMI it detects the 5V pin, but it is not clear if
there is an equivalent to that in the DP spec.
If there is no good way to detect if a source is connected, then it might be
better to drop POWER_PRESENT support.
This control is supposed to signal that a source is connected as early as possible,
ideally before link training etc. starts.
It helps the software detect that there is a source, and report an error if a source
is detected, but you never get a stable signal (e.g. link training fails).
If this control is dropped, then we just accept the v4l2-compliance warning.
Regards,
Hans
> + res = dprx->ctrl_handler.error;
> + if (res)
> + goto handler_free;
> +
> + dprx->subdev.ctrl_handler = &dprx->ctrl_handler;
> +
> + dprx->pads[0].flags = MEDIA_PAD_FL_SINK;
> + for (i = 1; i <= dprx->max_stream_count; i++)
> + dprx->pads[i].flags = MEDIA_PAD_FL_SOURCE;
> +
> + res = media_entity_pads_init(&dprx->subdev.entity,
> + dprx->max_stream_count + 1, dprx->pads);
> + if (res)
> + goto handler_free;
> +
> + res = v4l2_async_register_subdev(&dprx->subdev);
> + if (res)
> + goto entity_cleanup;
> +
> + dprx->hpd_state = 0;
> + dprx_set_hpd(dprx, 0);
> + dprx_reset_hw(dprx);
> +
> + dprx_set_irq(dprx, 1);
> +
> + return 0;
> +
> +entity_cleanup:
> + media_entity_cleanup(&dprx->subdev.entity);
> +handler_free:
> + v4l2_ctrl_handler_free(&dprx->ctrl_handler);
> +
> + return res;
> +}
> +
> +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);
> + media_entity_cleanup(&dprx->subdev.entity);
> + v4l2_ctrl_handler_free(&dprx->ctrl_handler);
> +}
> +
> +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 <panikiel@google.com>");
> +MODULE_DESCRIPTION("Intel DisplayPort RX IP core driver");
> +MODULE_LICENSE("GPL");
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3 00/10] Add Chameleon v3 video support
2024-05-07 15:54 [PATCH v3 00/10] Add Chameleon v3 video support Paweł Anikiel
` (9 preceding siblings ...)
2024-05-07 15:54 ` [PATCH v3 10/10] ARM: dts: chameleonv3: Add video device nodes Paweł Anikiel
@ 2024-06-03 8:44 ` Hans Verkuil
10 siblings, 0 replies; 26+ messages in thread
From: Hans Verkuil @ 2024-06-03 8:44 UTC (permalink / raw)
To: Paweł Anikiel, airlied, akpm, conor+dt, daniel, dinguyen,
krzysztof.kozlowski+dt, maarten.lankhorst, mchehab, mripard,
robh+dt, tzimmermann
Cc: devicetree, dri-devel, linux-kernel, linux-media,
chromeos-krk-upstreaming
Hi Paweł,
On 07/05/2024 17:54, Paweł Anikiel wrote:
> 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 video interface, 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 video interface 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.
>
> This patchset depends on changes merged into the linux-media tree at:
> git://linuxtv.org/hverkuil/media_tree.git tags/br-v6.10d
>
> Here is the output of `v4l2-compliance -s` run on a Chameleon v3 for
> /dev/video0 (no attached subdevice):
This v3 series looks pretty good to me, so from a V4L2 perspective I believe
a v4 should be OK.
But I need Acked-by for the drm and bindings patches before I can
merge a v4.
Regards,
Hans
>
> ```
> v4l2-compliance 1.27.0-5204, 32 bits, 32-bit time_t
> v4l2-compliance SHA: dd049328e528 2024-04-29 13:40:09
>
> Compliance test for chv3-video device /dev/video0:
>
> Driver Info:
> Driver name : chv3-video
> Card type : Chameleon v3 video
> Bus info : platform:c0060500.video
> Driver version : 6.9.0
> Capabilities : 0x84200001
> Video Capture
> Streaming
> Extended Pix Format
> Device Capabilities
> Device Caps : 0x04200001
> Video Capture
> Streaming
> Extended Pix Format
>
> Required ioctls:
> test VIDIOC_QUERYCAP: OK
> test invalid ioctls: OK
>
> Allow for multiple opens:
> test second /dev/video0 open: OK
> test VIDIOC_QUERYCAP: OK
> test VIDIOC_G/S_PRIORITY: OK
> test for unlimited opens: OK
>
> Debug ioctls:
> test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported)
> test VIDIOC_LOG_STATUS: OK (Not Supported)
>
> Input ioctls:
> test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
> test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
> test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
> test VIDIOC_ENUMAUDIO: OK (Not Supported)
> test VIDIOC_G/S/ENUMINPUT: OK
> test VIDIOC_G/S_AUDIO: OK (Not Supported)
> Inputs: 1 Audio Inputs: 0 Tuners: 0
>
> Output ioctls:
> test VIDIOC_G/S_MODULATOR: OK (Not Supported)
> test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
> test VIDIOC_ENUMAUDOUT: OK (Not Supported)
> test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
> test VIDIOC_G/S_AUDOUT: OK (Not Supported)
> Outputs: 0 Audio Outputs: 0 Modulators: 0
>
> Input/Output configuration ioctls:
> test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
> test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK
> test VIDIOC_DV_TIMINGS_CAP: OK
> test VIDIOC_G/S_EDID: OK (Not Supported)
>
> Control ioctls (Input 0):
> test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK
> test VIDIOC_QUERYCTRL: OK
> test VIDIOC_G/S_CTRL: OK
> test VIDIOC_G/S/TRY_EXT_CTRLS: OK
> test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK
> test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
> Standard Controls: 2 Private Controls: 0
>
> Format ioctls (Input 0):
> test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
> test VIDIOC_G/S_PARM: OK (Not Supported)
> test VIDIOC_G_FBUF: OK (Not Supported)
> test VIDIOC_G_FMT: OK
> test VIDIOC_TRY_FMT: OK
> test VIDIOC_S_FMT: OK
> test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
> test Cropping: OK (Not Supported)
> test Composing: OK (Not Supported)
> test Scaling: OK (Not Supported)
>
> Codec ioctls (Input 0):
> test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
> test VIDIOC_G_ENC_INDEX: OK (Not Supported)
> test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)
>
> Buffer ioctls (Input 0):
> test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK
> test CREATE_BUFS maximum buffers: OK
> test VIDIOC_REMOVE_BUFS: OK
> test VIDIOC_EXPBUF: OK
> test Requests: OK (Not Supported)
> test TIME32/64: OK
>
> Test input 0:
>
> Streaming ioctls:
> test read/write: OK (Not Supported)
> test blocking wait: OK
> test MMAP (no poll): OK
> test MMAP (select): OK
> test MMAP (epoll): OK
> test USERPTR (no poll): OK (Not Supported)
> test USERPTR (select): OK (Not Supported)
> test DMABUF: Cannot test, specify --expbuf-device
>
> Total for chv3-video device /dev/video0: 55, Succeeded: 55, Failed: 0, Warnings: 0
> ```
>
> Here is the output of `v4l2-compliance -s` run on a Chameleon v3 for
> /dev/video4 (attached subdevice):
>
> ```
> v4l2-compliance 1.27.0-5204, 32 bits, 32-bit time_t
> v4l2-compliance SHA: dd049328e528 2024-04-29 13:40:09
>
> Compliance test for chv3-video device /dev/video4:
>
> Driver Info:
> Driver name : chv3-video
> Card type : Chameleon v3 video
> Bus info : platform:c0060600.video
> Driver version : 6.9.0
> Capabilities : 0x84200001
> Video Capture
> Streaming
> Extended Pix Format
> Device Capabilities
> Device Caps : 0x04200001
> Video Capture
> Streaming
> Extended Pix Format
>
> Required ioctls:
> test VIDIOC_QUERYCAP: OK
> test invalid ioctls: OK
>
> Allow for multiple opens:
> test second /dev/video4 open: OK
> test VIDIOC_QUERYCAP: OK
> test VIDIOC_G/S_PRIORITY: OK
> test for unlimited opens: OK
>
> Debug ioctls:
> test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported)
> test VIDIOC_LOG_STATUS: OK (Not Supported)
>
> Input ioctls:
> test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
> test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
> test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
> test VIDIOC_ENUMAUDIO: OK (Not Supported)
> test VIDIOC_G/S/ENUMINPUT: OK
> test VIDIOC_G/S_AUDIO: OK (Not Supported)
> Inputs: 1 Audio Inputs: 0 Tuners: 0
>
> Output ioctls:
> test VIDIOC_G/S_MODULATOR: OK (Not Supported)
> test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
> test VIDIOC_ENUMAUDOUT: OK (Not Supported)
> test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
> test VIDIOC_G/S_AUDOUT: OK (Not Supported)
> Outputs: 0 Audio Outputs: 0 Modulators: 0
>
> Input/Output configuration ioctls:
> test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
> test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK
> test VIDIOC_DV_TIMINGS_CAP: OK
> test VIDIOC_G/S_EDID: OK
>
> Control ioctls (Input 0):
> test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK
> test VIDIOC_QUERYCTRL: OK
> test VIDIOC_G/S_CTRL: OK
> test VIDIOC_G/S/TRY_EXT_CTRLS: OK
> test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK
> test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
> Standard Controls: 2 Private Controls: 0
>
> Format ioctls (Input 0):
> test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
> test VIDIOC_G/S_PARM: OK (Not Supported)
> test VIDIOC_G_FBUF: OK (Not Supported)
> test VIDIOC_G_FMT: OK
> test VIDIOC_TRY_FMT: OK
> test VIDIOC_S_FMT: OK
> test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
> test Cropping: OK (Not Supported)
> test Composing: OK (Not Supported)
> test Scaling: OK (Not Supported)
>
> Codec ioctls (Input 0):
> test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
> test VIDIOC_G_ENC_INDEX: OK (Not Supported)
> test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)
>
> Buffer ioctls (Input 0):
> test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK
> test CREATE_BUFS maximum buffers: OK
> test VIDIOC_REMOVE_BUFS: OK
> test VIDIOC_EXPBUF: OK
> test Requests: OK (Not Supported)
> test TIME32/64: OK
>
> Test input 0:
>
> Streaming ioctls:
> test read/write: OK (Not Supported)
> test blocking wait: OK
> test MMAP (no poll): OK
> test MMAP (select): OK
> test MMAP (epoll): OK
> test USERPTR (no poll): OK (Not Supported)
> test USERPTR (select): OK (Not Supported)
> test DMABUF: Cannot test, specify --expbuf-device
>
> Total for chv3-video device /dev/video4: 55, Succeeded: 55, Failed: 0, Warnings: 0
> ```
>
> v3 changes:
> - Send v4l2-subdev API changes as a separate patchset
> - Drop chameleonv3/ directory
> - Change capture device name from "framebuffer" to "video interface"
> - Set sensible min and max dv timing caps
> - Set pixelclock to htotal * vtotal * 24Hz (we can't detect the actual value)
> - Remove enum_framesizes
> - Use v4l2_match_dv_timings()
> - Add V4L2_CID_DV_RX_POWER_PRESENT control
> - Use V4L2_DV_BT_CEA_1920X1080P60 as default timing
> - Use vb2_video_unregister_device()
> - Move subdev pad initialization to probe
> - Change subdev entity function to MEDIA_ENT_F_DV_DECODER
> - Drop dprx 'port' property and always use 'ports' instead
> - Remove legacy-format property and use multiple compats
> - Cleanup notifier only in non-fallback mode
> - Cleanup subdev entity using media_entity_cleanup()
> - Increase HPD pulse length to 500ms (see comment in dprx_set_edid())
> - Pull HPD low before updating EDID
> - Add a DisplayPort media bus type
> - Move receiver properties to port endpoint (data-lanes, link-frequencies)
>
> 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 (10):
> media: Add Chameleon v3 video interface 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: dt-bindings: video-interfaces: Support DisplayPort MST
> media: v4l2-mediabus: Add support for DisplayPort media bus
> media: intel: Add Displayport RX IP driver
> media: dt-bindings: Add Chameleon v3 video interface
> media: dt-bindings: Add Intel Displayport RX IP
> ARM: dts: chameleonv3: Add video device nodes
>
> .../bindings/media/google,chv3-video.yaml | 64 +
> .../devicetree/bindings/media/intel,dprx.yaml | 172 ++
> .../bindings/media/video-interfaces.yaml | 7 +
> .../socfpga/socfpga_arria10_chameleonv3.dts | 194 ++
> 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 | 13 +
> drivers/media/platform/google/Makefile | 3 +
> drivers/media/platform/google/chv3-video.c | 891 +++++++
> drivers/media/platform/intel/Kconfig | 12 +
> drivers/media/platform/intel/Makefile | 1 +
> drivers/media/platform/intel/intel-dprx.c | 2283 +++++++++++++++++
> drivers/media/v4l2-core/v4l2-fwnode.c | 38 +
> 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/dt-bindings/media/video-interfaces.h | 2 +
> include/linux/crc-dp.h | 10 +
> include/media/v4l2-fwnode.h | 5 +
> include/media/v4l2-mediabus.h | 17 +
> lib/Kconfig | 8 +
> lib/Makefile | 1 +
> lib/crc-dp.c | 78 +
> 25 files changed, 4053 insertions(+), 304 deletions(-)
> create mode 100644 Documentation/devicetree/bindings/media/google,chv3-video.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/chv3-video.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
>
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3 01/10] media: Add Chameleon v3 video interface driver
2024-06-03 7:57 ` Hans Verkuil
@ 2024-06-03 14:32 ` Paweł Anikiel
2024-06-03 14:56 ` Hans Verkuil
0 siblings, 1 reply; 26+ messages in thread
From: Paweł Anikiel @ 2024-06-03 14:32 UTC (permalink / raw)
To: Hans Verkuil
Cc: airlied, akpm, conor+dt, daniel, dinguyen, krzysztof.kozlowski+dt,
maarten.lankhorst, mchehab, mripard, robh+dt, tzimmermann,
devicetree, dri-devel, linux-kernel, linux-media,
chromeos-krk-upstreaming
On Mon, Jun 3, 2024 at 9:57 AM Hans Verkuil <hverkuil-cisco@xs4all.nl> wrote:
>
> On 07/05/2024 17:54, Paweł Anikiel wrote:
> > Add v4l2 driver for the video interface present on the Google
> > Chameleon v3. The Chameleon v3 uses the video interface to capture
> > a single video source from a given HDMI or DP connector and write
> > the resulting frames to memory.
> >
> > Signed-off-by: Paweł Anikiel <panikiel@google.com>
> > ---
> > drivers/media/platform/Kconfig | 1 +
> > drivers/media/platform/Makefile | 1 +
> > drivers/media/platform/google/Kconfig | 13 +
> > drivers/media/platform/google/Makefile | 3 +
> > drivers/media/platform/google/chv3-video.c | 891 +++++++++++++++++++++
> > 5 files changed, 909 insertions(+)
> > create mode 100644 drivers/media/platform/google/Kconfig
> > create mode 100644 drivers/media/platform/google/Makefile
> > create mode 100644 drivers/media/platform/google/chv3-video.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..9674a4c12e2d
> > --- /dev/null
> > +++ b/drivers/media/platform/google/Kconfig
> > @@ -0,0 +1,13 @@
> > +# SPDX-License-Identifier: GPL-2.0-only
> > +
> > +config VIDEO_CHAMELEONV3
> > + tristate "Google Chameleon v3 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 video interface to
> > + capture a single video source from a given HDMI or DP connector
> > + and write the resulting frames to memory.
> > diff --git a/drivers/media/platform/google/Makefile b/drivers/media/platform/google/Makefile
> > new file mode 100644
> > index 000000000000..cff06486244c
> > --- /dev/null
> > +++ b/drivers/media/platform/google/Makefile
> > @@ -0,0 +1,3 @@
> > +# SPDX-License-Identifier: GPL-2.0-only
> > +
> > +obj-$(CONFIG_VIDEO_CHAMELEONV3) += chv3-video.o
> > diff --git a/drivers/media/platform/google/chv3-video.c b/drivers/media/platform/google/chv3-video.c
> > new file mode 100644
> > index 000000000000..6e782484abaf
> > --- /dev/null
> > +++ b/drivers/media/platform/google/chv3-video.c
> > @@ -0,0 +1,891 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright 2023-2024 Google LLC.
> > + * Author: Paweł Anikiel <panikiel@google.com>
> > + */
> > +
> > +#include <linux/delay.h>
> > +#include <linux/dma-mapping.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/of.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/v4l2-dv-timings.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-video"
> > +
> > +#define VIDEO_EN 0x00
> > +#define VIDEO_EN_BIT BIT(0)
> > +#define VIDEO_HEIGHT 0x04
> > +#define VIDEO_WIDTH 0x08
> > +#define VIDEO_BUFFERA 0x0c
> > +#define VIDEO_BUFFERB 0x10
> > +#define VIDEO_BUFFERSIZE 0x14
> > +#define VIDEO_RESET 0x18
> > +#define VIDEO_RESET_BIT BIT(0)
> > +#define VIDEO_ERRORSTATUS 0x1c
> > +#define VIDEO_IOCOLOR 0x20
> > +#define VIDEO_DATARATE 0x24
> > +#define VIDEO_DATARATE_SINGLE 0x0
> > +#define VIDEO_DATARATE_DOUBLE 0x1
> > +#define VIDEO_PIXELMODE 0x28
> > +#define VIDEO_PIXELMODE_SINGLE 0x0
> > +#define VIDEO_PIXELMODE_DOUBLE 0x1
> > +#define VIDEO_SYNCPOLARITY 0x2c
> > +#define VIDEO_DMAFORMAT 0x30
> > +#define VIDEO_DMAFORMAT_8BPC 0x0
> > +#define VIDEO_DMAFORMAT_10BPC_UPPER 0x1
> > +#define VIDEO_DMAFORMAT_10BPC_LOWER 0x2
> > +#define VIDEO_DMAFORMAT_12BPC_UPPER 0x3
> > +#define VIDEO_DMAFORMAT_12BPC_LOWER 0x4
> > +#define VIDEO_DMAFORMAT_16BPC 0x5
> > +#define VIDEO_DMAFORMAT_RAW 0x6
> > +#define VIDEO_DMAFORMAT_8BPC_PAD 0x7
> > +#define VIDEO_VERSION 0x34
> > +#define VIDEO_VERSION_CURRENT 0xc0fb0001
> > +
> > +#define VIDEO_IRQ_MASK 0x8
> > +#define VIDEO_IRQ_CLR 0xc
> > +#define VIDEO_IRQ_ALL 0xf
> > +#define VIDEO_IRQ_BUFF0 BIT(0)
> > +#define VIDEO_IRQ_BUFF1 BIT(1)
> > +#define VIDEO_IRQ_RESOLUTION BIT(2)
> > +#define VIDEO_IRQ_ERROR BIT(3)
> > +
> > +struct chv3_video {
> > + 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;
> > + u32 bytes_per_pixel;
> > +
> > + struct v4l2_ctrl_handler ctrl_handler;
> > + 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 video_lock;
> > +};
> > +
> > +struct chv3_video_buffer {
> > + struct vb2_v4l2_buffer vb;
> > + struct list_head link;
> > +};
> > +
> > +struct chv3_video_config {
> > + u32 pixelformat;
> > + u32 bytes_per_pixel;
> > + u32 dmaformat;
> > +};
> > +
> > +static void chv3_video_set_format_resolution(struct chv3_video *video, u32 width, u32 height)
> > +{
> > + video->pix_fmt.width = width;
> > + video->pix_fmt.height = height;
> > + video->pix_fmt.bytesperline = width * video->bytes_per_pixel;
> > + video->pix_fmt.sizeimage = video->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_video_query_dv_timings_fallback(struct chv3_video *video,
> > + struct v4l2_dv_timings *timings)
> > +{
> > + u32 width, height;
> > +
> > + width = readl(video->iobase + VIDEO_WIDTH);
> > + height = readl(video->iobase + VIDEO_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;
> > + timings->bt.pixelclock = width * height * 24;
> > +
> > + return 0;
> > +}
> > +
> > +static int chv3_video_query_dv_timings(struct chv3_video *video, struct v4l2_dv_timings *timings)
> > +{
> > + if (video->subdev) {
> > + return v4l2_subdev_call(video->subdev, pad, query_dv_timings,
> > + video->subdev_source_pad, timings);
> > + } else {
> > + return chv3_video_query_dv_timings_fallback(video, timings);
> > + }
>
> I would move the contents of chv3_video_query_dv_timings_fallback() to this
> function and drop the old fallback function. It makes more sense if it is all
> in the same function.
>
> > +}
> > +
> > +static const struct v4l2_dv_timings_cap chv3_video_fallback_dv_timings_cap = {
> > + .type = V4L2_DV_BT_656_1120,
> > + .bt = {
> > + .min_width = 640,
> > + .max_width = 7680,
> > + .min_height = 480,
> > + .max_height = 4320,
> > + .min_pixelclock = 25000000,
> > + .max_pixelclock = 1080000000,
> > + .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_video_enum_dv_timings_fallback(struct chv3_video *video,
> > + struct v4l2_enum_dv_timings *timings)
> > +{
> > + return v4l2_enum_dv_timings_cap(timings, &chv3_video_fallback_dv_timings_cap,
> > + NULL, NULL);
> > +}
> > +
> > +static int chv3_video_dv_timings_cap_fallback(struct chv3_video *video,
> > + struct v4l2_dv_timings_cap *cap)
> > +{
> > + *cap = chv3_video_fallback_dv_timings_cap;
> > +
> > + return 0;
> > +}
>
> Same for these two fallback functions: move them to the functions that calls them.
>
> > +
> > +static void chv3_video_apply_dv_timings(struct chv3_video *video)
> > +{
> > + struct v4l2_dv_timings timings;
> > + int res;
> > +
> > + res = chv3_video_query_dv_timings(video, &timings);
> > + if (res)
> > + return;
> > +
> > + video->timings = timings;
> > + chv3_video_set_format_resolution(video, timings.bt.width, timings.bt.height);
> > +}
> > +
> > +static int chv3_video_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_video_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt)
> > +{
> > + struct chv3_video *video = video_drvdata(file);
> > +
> > + fmt->fmt.pix = video->pix_fmt;
> > +
> > + return 0;
> > +}
> > +
> > +static int chv3_video_enum_fmt_vid_cap(struct file *file, void *fh, struct v4l2_fmtdesc *fmt)
> > +{
> > + struct chv3_video *video = video_drvdata(file);
> > +
> > + if (fmt->index != 0)
> > + return -EINVAL;
> > +
> > + fmt->flags = 0;
> > + fmt->pixelformat = video->pix_fmt.pixelformat;
> > +
> > + return 0;
> > +}
> > +
> > +static int chv3_video_g_input(struct file *file, void *fh, unsigned int *index)
> > +{
> > + *index = 0;
> > +
> > + return 0;
> > +}
> > +
> > +static int chv3_video_s_input(struct file *file, void *fh, unsigned int index)
> > +{
> > + if (index != 0)
> > + return -EINVAL;
> > +
> > + return 0;
> > +}
> > +
> > +static int chv3_video_enum_input(struct file *file, void *fh, struct v4l2_input *input)
> > +{
> > + if (input->index != 0)
> > + return -EINVAL;
> > +
> > + strscpy(input->name, "input0", sizeof(input->name));
>
> This name is not terribly user friendly. Is it possible to determine a more human
> readable name? E.g. "DP1", "DP2", etc. Something that matches labeling on the Chameleon
> board.
The driver would require some board-specific instance info to
determine if the video interface is connected to DP1, DP2, or the
auxiliary decoder (or something entirely different if this IP was used
on a different board). I don't see an easy way to determine such a
human readable name, unfortunately.
>
> > + input->type = V4L2_INPUT_TYPE_CAMERA;
> > + input->capabilities = V4L2_IN_CAP_DV_TIMINGS;
> > +
> > + return 0;
> > +}
> > +
> > +static int chv3_video_g_edid(struct file *file, void *fh, struct v4l2_edid *edid)
> > +{
> > + struct chv3_video *video = video_drvdata(file);
> > + int res;
> > +
> > + if (!video->subdev)
> > + return -ENOTTY;
> > +
> > + if (edid->pad != 0)
> > + return -EINVAL;
> > +
> > + edid->pad = video->subdev_source_pad;
> > + res = v4l2_subdev_call(video->subdev, pad, get_edid, edid);
> > + edid->pad = 0;
> > +
> > + return res;
> > +}
> > +
> > +static int chv3_video_s_edid(struct file *file, void *fh, struct v4l2_edid *edid)
> > +{
> > + struct chv3_video *video = video_drvdata(file);
> > + int res;
> > +
> > + if (!video->subdev)
> > + return -ENOTTY;
> > +
> > + if (edid->pad != 0)
> > + return -EINVAL;
> > +
> > + edid->pad = video->subdev_source_pad;
> > + res = v4l2_subdev_call(video->subdev, pad, set_edid, edid);
> > + edid->pad = 0;
> > +
> > + return res;
> > +}
> > +
> > +static int chv3_video_s_dv_timings(struct file *file, void *fh, struct v4l2_dv_timings *timings)
> > +{
> > + struct chv3_video *video = video_drvdata(file);
> > +
> > + if (v4l2_match_dv_timings(&video->timings, timings, 0, false))
> > + return 0;
> > +
> > + if (vb2_is_busy(&video->queue))
> > + return -EBUSY;
>
> This should be moved to after the next 'if'.
>
> > +
> > + if (!v4l2_valid_dv_timings(timings, &chv3_video_fallback_dv_timings_cap, NULL, NULL))
> > + return -ERANGE;
> > +
> > + video->timings = *timings;
> > + chv3_video_set_format_resolution(video, timings->bt.width, timings->bt.height);
> > +
> > + return 0;
> > +}
> > +
> > +static int chv3_video_g_dv_timings(struct file *file, void *fh, struct v4l2_dv_timings *timings)
> > +{
> > + struct chv3_video *video = video_drvdata(file);
> > +
> > + *timings = video->timings;
> > + return 0;
> > +}
> > +
> > +static int chv3_video_vidioc_query_dv_timings(struct file *file, void *fh,
> > + struct v4l2_dv_timings *timings)
> > +{
> > + struct chv3_video *video = video_drvdata(file);
> > +
> > + return chv3_video_query_dv_timings(video, timings);
> > +}
> > +
> > +static int chv3_video_enum_dv_timings(struct file *file, void *fh,
> > + struct v4l2_enum_dv_timings *timings)
> > +{
> > + struct chv3_video *video = video_drvdata(file);
> > + int res;
> > +
> > + if (timings->pad != 0)
> > + return -EINVAL;
> > +
> > + if (video->subdev) {
> > + timings->pad = video->subdev_source_pad;
> > + res = v4l2_subdev_call(video->subdev, pad, enum_dv_timings, timings);
> > + timings->pad = 0;
> > + return res;
> > + } else {
> > + return chv3_video_enum_dv_timings_fallback(video, timings);
>
> It is much easier to read if the contents of chv3_video_enum_dv_timings_fallback
> is moved here.
>
> > + }
> > +}
> > +
> > +static int chv3_video_dv_timings_cap(struct file *file, void *fh, struct v4l2_dv_timings_cap *cap)
> > +{
> > + struct chv3_video *video = video_drvdata(file);
> > + int res;
> > +
> > + if (cap->pad != 0)
> > + return -EINVAL;
> > +
> > + if (video->subdev) {
> > + cap->pad = video->subdev_source_pad;
> > + res = v4l2_subdev_call(video->subdev, pad, dv_timings_cap, cap);
> > + cap->pad = 0;
> > + return res;
> > + } else {
> > + return chv3_video_dv_timings_cap_fallback(video, cap);
>
> Ditto.
>
> > + }
> > +}
> > +
> > +static int chv3_video_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_video_v4l2_ioctl_ops = {
> > + .vidioc_querycap = chv3_video_querycap,
> > +
> > + .vidioc_enum_fmt_vid_cap = chv3_video_enum_fmt_vid_cap,
> > + .vidioc_g_fmt_vid_cap = chv3_video_g_fmt_vid_cap,
> > + .vidioc_s_fmt_vid_cap = chv3_video_g_fmt_vid_cap,
> > + .vidioc_try_fmt_vid_cap = chv3_video_g_fmt_vid_cap,
> > +
> > + .vidioc_enum_input = chv3_video_enum_input,
> > + .vidioc_g_input = chv3_video_g_input,
> > + .vidioc_s_input = chv3_video_s_input,
> > + .vidioc_g_edid = chv3_video_g_edid,
> > + .vidioc_s_edid = chv3_video_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_video_s_dv_timings,
> > + .vidioc_g_dv_timings = chv3_video_g_dv_timings,
> > + .vidioc_query_dv_timings = chv3_video_vidioc_query_dv_timings,
> > + .vidioc_enum_dv_timings = chv3_video_enum_dv_timings,
> > + .vidioc_dv_timings_cap = chv3_video_dv_timings_cap,
> > +
> > + .vidioc_subscribe_event = chv3_video_subscribe_event,
> > + .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
> > +};
> > +
> > +static int chv3_video_queue_setup(struct vb2_queue *q,
> > + unsigned int *nbuffers, unsigned int *nplanes,
> > + unsigned int sizes[], struct device *alloc_devs[])
> > +{
> > + struct chv3_video *video = vb2_get_drv_priv(q);
> > +
> > + if (*nplanes) {
> > + if (sizes[0] < video->pix_fmt.sizeimage)
> > + return -EINVAL;
> > + return 0;
> > + }
> > + *nplanes = 1;
> > + sizes[0] = video->pix_fmt.sizeimage;
> > +
> > + return 0;
> > +}
> > +
> > +/*
> > + * There are two address registers: BUFFERA and BUFFERB. The device
> > + * 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_video_buffer_dma_addr(struct chv3_video_buffer *buf)
> > +{
> > + return vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
> > +}
> > +
> > +static void chv3_video_start_frame(struct chv3_video *video, struct chv3_video_buffer *buf)
> > +{
> > + video->writing_to_a = 1;
> > + writel(chv3_video_buffer_dma_addr(buf), video->iobase + VIDEO_BUFFERA);
> > + writel(VIDEO_EN_BIT, video->iobase + VIDEO_EN);
> > +}
> > +
> > +static void chv3_video_next_frame(struct chv3_video *video, struct chv3_video_buffer *buf)
> > +{
> > + u32 reg = video->writing_to_a ? VIDEO_BUFFERB : VIDEO_BUFFERA;
> > +
> > + writel(chv3_video_buffer_dma_addr(buf), video->iobase + reg);
> > +}
> > +
> > +static int chv3_video_start_streaming(struct vb2_queue *q, unsigned int count)
> > +{
> > + struct chv3_video *video = vb2_get_drv_priv(q);
> > + struct chv3_video_buffer *buf;
> > + unsigned long flags;
> > +
> > + video->sequence = 0;
> > + writel(video->pix_fmt.sizeimage, video->iobase + VIDEO_BUFFERSIZE);
> > +
> > + spin_lock_irqsave(&video->bufs_lock, flags);
> > + buf = list_first_entry_or_null(&video->bufs, struct chv3_video_buffer, link);
> > + if (buf) {
> > + chv3_video_start_frame(video, buf);
> > + if (!list_is_last(&buf->link, &video->bufs))
> > + chv3_video_next_frame(video, list_next_entry(buf, link));
> > + }
> > + spin_unlock_irqrestore(&video->bufs_lock, flags);
> > +
> > + return 0;
> > +}
> > +
> > +static void chv3_video_stop_streaming(struct vb2_queue *q)
> > +{
> > + struct chv3_video *video = vb2_get_drv_priv(q);
> > + struct chv3_video_buffer *buf;
> > + unsigned long flags;
> > +
> > + writel(0, video->iobase + VIDEO_EN);
> > +
> > + spin_lock_irqsave(&video->bufs_lock, flags);
> > + list_for_each_entry(buf, &video->bufs, link)
> > + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
> > + INIT_LIST_HEAD(&video->bufs);
> > + spin_unlock_irqrestore(&video->bufs_lock, flags);
> > +}
> > +
> > +static void chv3_video_buf_queue(struct vb2_buffer *vb)
> > +{
> > + struct chv3_video *video = vb2_get_drv_priv(vb->vb2_queue);
> > + struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> > + struct chv3_video_buffer *buf = container_of(v4l2_buf, struct chv3_video_buffer, vb);
> > + bool first, second;
> > + unsigned long flags;
> > +
> > + spin_lock_irqsave(&video->bufs_lock, flags);
> > + first = list_empty(&video->bufs);
> > + second = list_is_singular(&video->bufs);
> > + list_add_tail(&buf->link, &video->bufs);
> > + if (vb2_is_streaming(vb->vb2_queue)) {
>
> This should be vb2_start_streaming_called().
>
> It does not matter all that much in this driver, since VIDIOC_STREAMON will
> also call start_streaming, even if there are no buffers queued since the
> vb2_queue min_queued_buffers field is 0. But if that ever changes, then
> vb2_start_streaming_called() is the right call here.
Okay, I see. Should the other use of vb2_is_streaming() within this
file be replaced as well?
>
> > + if (first)
> > + chv3_video_start_frame(video, buf);
> > + else if (second)
> > + chv3_video_next_frame(video, buf);
> > + }
> > + spin_unlock_irqrestore(&video->bufs_lock, flags);
> > +}
> > +
> > +static const struct vb2_ops chv3_video_vb2_ops = {
> > + .queue_setup = chv3_video_queue_setup,
> > + .wait_prepare = vb2_ops_wait_prepare,
> > + .wait_finish = vb2_ops_wait_finish,
> > + .start_streaming = chv3_video_start_streaming,
> > + .stop_streaming = chv3_video_stop_streaming,
> > + .buf_queue = chv3_video_buf_queue,
> > +};
> > +
> > +static int chv3_video_open(struct file *file)
> > +{
> > + struct chv3_video *video = video_drvdata(file);
> > + int res;
> > +
> > + mutex_lock(&video->video_lock);
> > + res = v4l2_fh_open(file);
> > + if (!res) {
> > + if (v4l2_fh_is_singular_file(file))
> > + chv3_video_apply_dv_timings(video);
> > + }
> > + mutex_unlock(&video->video_lock);
> > +
> > + return res;
> > +}
> > +
> > +static const struct v4l2_file_operations chv3_video_v4l2_fops = {
> > + .owner = THIS_MODULE,
> > + .open = chv3_video_open,
> > + .release = vb2_fop_release,
> > + .unlocked_ioctl = video_ioctl2,
> > + .mmap = vb2_fop_mmap,
> > + .poll = vb2_fop_poll,
> > +};
> > +
> > +static void chv3_video_frame_irq(struct chv3_video *video)
> > +{
> > + struct chv3_video_buffer *buf;
> > +
> > + spin_lock(&video->bufs_lock);
> > +
> > + buf = list_first_entry_or_null(&video->bufs, struct chv3_video_buffer, link);
> > + if (!buf)
> > + goto empty;
> > + list_del(&buf->link);
> > +
> > + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, video->pix_fmt.sizeimage);
> > + buf->vb.vb2_buf.timestamp = ktime_get_ns();
> > + buf->vb.sequence = video->sequence++;
> > + buf->vb.field = V4L2_FIELD_NONE;
> > + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
> > +
> > + buf = list_first_entry_or_null(&video->bufs, struct chv3_video_buffer, link);
> > + if (buf) {
> > + video->writing_to_a = !video->writing_to_a;
> > + if (!list_is_last(&buf->link, &video->bufs))
> > + chv3_video_next_frame(video, list_next_entry(buf, link));
> > + } else {
> > + writel(0, video->iobase + VIDEO_EN);
> > + }
> > +empty:
> > + spin_unlock(&video->bufs_lock);
> > +}
> > +
> > +static void chv3_video_error_irq(struct chv3_video *video)
> > +{
> > + if (vb2_is_streaming(&video->queue))
> > + vb2_queue_error(&video->queue);
> > +}
> > +
> > +static void chv3_video_resolution_irq(struct chv3_video *video)
> > +{
> > + static const struct v4l2_event event = {
> > + .type = V4L2_EVENT_SOURCE_CHANGE,
> > + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION,
> > + };
> > +
> > + v4l2_event_queue(&video->vdev, &event);
> > + chv3_video_error_irq(video);
> > +}
> > +
> > +static irqreturn_t chv3_video_isr(int irq, void *data)
> > +{
> > + struct chv3_video *video = data;
> > + unsigned int reg;
> > +
> > + reg = readl(video->iobase_irq + VIDEO_IRQ_CLR);
> > + if (!reg)
> > + return IRQ_NONE;
> > +
> > + if (reg & VIDEO_IRQ_BUFF0)
> > + chv3_video_frame_irq(video);
> > + if (reg & VIDEO_IRQ_BUFF1)
> > + chv3_video_frame_irq(video);
> > + if (reg & VIDEO_IRQ_RESOLUTION)
> > + chv3_video_resolution_irq(video);
> > + if (reg & VIDEO_IRQ_ERROR) {
> > + dev_warn(video->dev, "error: 0x%x\n",
> > + readl(video->iobase + VIDEO_ERRORSTATUS));
> > + chv3_video_error_irq(video);
> > + }
> > +
> > + writel(reg, video->iobase_irq + VIDEO_IRQ_CLR);
> > +
> > + return IRQ_HANDLED;
> > +}
> > +
> > +static int chv3_video_check_version(struct chv3_video *video)
> > +{
> > + u32 version;
> > +
> > + version = readl(video->iobase + VIDEO_VERSION);
> > + if (version != VIDEO_VERSION_CURRENT) {
> > + dev_err(video->dev,
> > + "wrong hw version: expected %x, got %x\n",
> > + VIDEO_VERSION_CURRENT, version);
> > + return -ENODEV;
> > + }
> > + return 0;
> > +}
> > +
> > +static void chv3_video_init_timings_and_format(struct chv3_video *video,
> > + const struct chv3_video_config *config)
> > +{
> > + struct v4l2_pix_format *pix = &video->pix_fmt;
> > + struct v4l2_dv_timings timings = V4L2_DV_BT_CEA_1920X1080P60;
> > +
> > + video->timings = timings;
> > + video->bytes_per_pixel = config->bytes_per_pixel;
> > +
> > + pix->pixelformat = config->pixelformat;
> > + pix->field = V4L2_FIELD_NONE;
> > + pix->colorspace = V4L2_COLORSPACE_SRGB;
> > + chv3_video_set_format_resolution(video, timings.bt.width, timings.bt.height);
> > +}
> > +
> > +#define notifier_to_video(nf) container_of(nf, struct chv3_video, notifier)
> > +
> > +static int chv3_video_async_notify_bound(struct v4l2_async_notifier *notifier,
> > + struct v4l2_subdev *subdev,
> > + struct v4l2_async_connection *asc)
> > +{
> > + struct chv3_video *video = notifier_to_video(notifier);
> > + int pad;
> > +
> > + pad = media_entity_get_fwnode_pad(&subdev->entity, asc->match.fwnode,
> > + MEDIA_PAD_FL_SOURCE);
> > + if (pad < 0)
> > + return pad;
> > +
> > + video->subdev = subdev;
> > + video->subdev_source_pad = pad;
> > +
> > + video->v4l2_dev.ctrl_handler = subdev->ctrl_handler;
> > +
> > + return 0;
> > +}
> > +
> > +static void chv3_video_async_notify_unbind(struct v4l2_async_notifier *notifier,
> > + struct v4l2_subdev *subdev,
> > + struct v4l2_async_connection *asc)
> > +{
> > + struct chv3_video *video = notifier_to_video(notifier);
> > +
> > + vb2_video_unregister_device(&video->vdev);
> > +}
> > +
> > +static int chv3_video_async_notify_complete(struct v4l2_async_notifier *notifier)
> > +{
> > + struct chv3_video *video = notifier_to_video(notifier);
> > +
> > + return video_register_device(&video->vdev, VFL_TYPE_VIDEO, -1);
> > +}
> > +
> > +static const struct v4l2_async_notifier_operations chv3_video_async_notify_ops = {
> > + .bound = chv3_video_async_notify_bound,
> > + .unbind = chv3_video_async_notify_unbind,
> > + .complete = chv3_video_async_notify_complete,
> > +};
> > +
> > +static int chv3_video_fallback_init(struct chv3_video *video)
> > +{
> > + int res;
> > +
> > + video->subdev = NULL;
> > + video->subdev_source_pad = 0;
> > +
> > + v4l2_ctrl_handler_init(&video->ctrl_handler, 1);
> > + v4l2_ctrl_new_std(&video->ctrl_handler, NULL,
> > + V4L2_CID_DV_RX_POWER_PRESENT, 0, 1, 0, 0);
> > + res = video->ctrl_handler.error;
> > + if (res)
> > + goto handler_free;
> > +
> > + video->v4l2_dev.ctrl_handler = &video->ctrl_handler;
> > +
> > + res = video_register_device(&video->vdev, VFL_TYPE_VIDEO, -1);
> > + if (res)
> > + goto handler_free;
> > +
> > + return 0;
> > +
> > +handler_free:
> > + v4l2_ctrl_handler_free(&video->ctrl_handler);
> > +
> > + return res;
> > +}
> > +
> > +static int chv3_video_fwnode_init(struct chv3_video *video)
> > +{
> > + struct v4l2_async_connection *asc;
> > + struct fwnode_handle *endpoint;
> > + int res;
> > +
> > + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(video->dev), NULL);
> > + if (!endpoint)
> > + return -EINVAL;
> > +
> > + v4l2_async_nf_init(&video->notifier, &video->v4l2_dev);
> > +
> > + asc = v4l2_async_nf_add_fwnode_remote(&video->notifier, endpoint,
> > + struct v4l2_async_connection);
> > + fwnode_handle_put(endpoint);
> > +
> > + if (IS_ERR(asc))
> > + return PTR_ERR(asc);
> > +
> > + video->notifier.ops = &chv3_video_async_notify_ops;
> > + res = v4l2_async_nf_register(&video->notifier);
> > + if (res) {
> > + v4l2_async_nf_cleanup(&video->notifier);
> > + return res;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int chv3_video_probe(struct platform_device *pdev)
> > +{
> > + struct chv3_video *video;
> > + const struct chv3_video_config *config;
> > + int res;
> > + int irq;
> > +
> > + video = devm_kzalloc(&pdev->dev, sizeof(*video), GFP_KERNEL);
> > + if (!video)
> > + return -ENOMEM;
> > + video->dev = &pdev->dev;
> > + platform_set_drvdata(pdev, video);
> > +
> > + config = device_get_match_data(video->dev);
> > +
> > + /* map register space */
> > + video->iobase = devm_platform_ioremap_resource(pdev, 0);
> > + if (IS_ERR(video->iobase))
> > + return PTR_ERR(video->iobase);
> > +
> > + video->iobase_irq = devm_platform_ioremap_resource(pdev, 1);
> > + if (IS_ERR(video->iobase_irq))
> > + return PTR_ERR(video->iobase_irq);
> > +
> > + /* check hw version */
> > + res = chv3_video_check_version(video);
> > + 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_video_isr, 0, DEVICE_NAME, video);
> > + if (res)
> > + return res;
> > +
> > + /* initialize v4l2_device */
> > + res = v4l2_device_register(&pdev->dev, &video->v4l2_dev);
> > + if (res)
> > + return res;
> > +
> > + /* initialize vb2 queue */
> > + video->queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
> > + video->queue.io_modes = VB2_MMAP | VB2_DMABUF;
> > + video->queue.dev = &pdev->dev;
> > + video->queue.lock = &video->video_lock;
> > + video->queue.ops = &chv3_video_vb2_ops;
> > + video->queue.mem_ops = &vb2_dma_contig_memops;
> > + video->queue.drv_priv = video;
> > + video->queue.buf_struct_size = sizeof(struct chv3_video_buffer);
> > + video->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> > + res = vb2_queue_init(&video->queue);
> > + if (res)
> > + goto error;
> > +
> > + /* initialize video_device */
> > + strscpy(video->vdev.name, DEVICE_NAME, sizeof(video->vdev.name));
> > + video->vdev.fops = &chv3_video_v4l2_fops;
> > + video->vdev.ioctl_ops = &chv3_video_v4l2_ioctl_ops;
> > + video->vdev.lock = &video->video_lock;
> > + video->vdev.release = video_device_release_empty;
> > + video->vdev.device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
> > + video->vdev.v4l2_dev = &video->v4l2_dev;
> > + video->vdev.queue = &video->queue;
> > + video_set_drvdata(&video->vdev, video);
> > +
> > + if (device_get_named_child_node(&pdev->dev, "port"))
> > + res = chv3_video_fwnode_init(video);
> > + else
> > + res = chv3_video_fallback_init(video);
> > + if (res)
> > + goto error;
> > +
> > + /* initialize rest of driver struct */
> > + INIT_LIST_HEAD(&video->bufs);
> > + spin_lock_init(&video->bufs_lock);
> > + mutex_init(&video->video_lock);
> > +
> > + chv3_video_init_timings_and_format(video, config);
> > +
> > + /* initialize hw */
> > + writel(VIDEO_RESET_BIT, video->iobase + VIDEO_RESET);
> > + writel(VIDEO_DATARATE_DOUBLE, video->iobase + VIDEO_DATARATE);
> > + writel(VIDEO_PIXELMODE_DOUBLE, video->iobase + VIDEO_PIXELMODE);
> > + writel(config->dmaformat, video->iobase + VIDEO_DMAFORMAT);
> > +
> > + writel(VIDEO_IRQ_ALL, video->iobase_irq + VIDEO_IRQ_MASK);
> > +
> > + return 0;
> > +
> > +error:
> > + v4l2_device_unregister(&video->v4l2_dev);
> > +
> > + return res;
> > +}
> > +
> > +static void chv3_video_remove(struct platform_device *pdev)
> > +{
> > + struct chv3_video *video = platform_get_drvdata(pdev);
> > +
> > + /* disable interrupts */
> > + writel(0, video->iobase_irq + VIDEO_IRQ_MASK);
> > +
> > + if (video->subdev) {
> > + /* notifier is initialized only in non-fallback mode */
> > + v4l2_async_nf_unregister(&video->notifier);
> > + v4l2_async_nf_cleanup(&video->notifier);
> > + } else {
> > + /* ctrl handler is initialized only in fallback mode */
> > + v4l2_ctrl_handler_free(&video->ctrl_handler);
> > + }
> > +
> > + v4l2_device_unregister(&video->v4l2_dev);
> > +}
> > +
> > +static const struct chv3_video_config chv3_video_it = {
> > + .pixelformat = V4L2_PIX_FMT_BGRX32,
> > + .bytes_per_pixel = 4,
> > + .dmaformat = VIDEO_DMAFORMAT_8BPC_PAD,
> > +};
> > +
> > +static const struct chv3_video_config chv3_video_dp = {
> > + .pixelformat = V4L2_PIX_FMT_RGB24,
> > + .bytes_per_pixel = 3,
> > + .dmaformat = VIDEO_DMAFORMAT_8BPC,
> > +};
> > +
> > +static const struct of_device_id chv3_video_match_table[] = {
> > + { .compatible = "google,chv3-video-it-1.0", .data = &chv3_video_it },
> > + { .compatible = "google,chv3-video-dp-1.0", .data = &chv3_video_dp },
> > + { },
> > +};
> > +
> > +static struct platform_driver chv3_video_platform_driver = {
> > + .probe = chv3_video_probe,
> > + .remove_new = chv3_video_remove,
> > + .driver = {
> > + .name = DEVICE_NAME,
> > + .of_match_table = chv3_video_match_table,
> > + },
> > +};
> > +
> > +module_platform_driver(chv3_video_platform_driver);
> > +
> > +MODULE_AUTHOR("Paweł Anikiel <panikiel@google.com>");
> > +MODULE_DESCRIPTION("Google Chameleon v3 video interface driver");
> > +MODULE_LICENSE("GPL");
>
> Regards,
>
> Hans
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3 01/10] media: Add Chameleon v3 video interface driver
2024-06-03 14:32 ` Paweł Anikiel
@ 2024-06-03 14:56 ` Hans Verkuil
2024-06-04 12:03 ` Paweł Anikiel
0 siblings, 1 reply; 26+ messages in thread
From: Hans Verkuil @ 2024-06-03 14:56 UTC (permalink / raw)
To: Paweł Anikiel
Cc: airlied, akpm, conor+dt, daniel, dinguyen, krzysztof.kozlowski+dt,
maarten.lankhorst, mchehab, mripard, robh+dt, tzimmermann,
devicetree, dri-devel, linux-kernel, linux-media,
chromeos-krk-upstreaming
On 03/06/2024 16:32, Paweł Anikiel wrote:
> On Mon, Jun 3, 2024 at 9:57 AM Hans Verkuil <hverkuil-cisco@xs4all.nl> wrote:
>>
>> On 07/05/2024 17:54, Paweł Anikiel wrote:
>>> Add v4l2 driver for the video interface present on the Google
>>> Chameleon v3. The Chameleon v3 uses the video interface to capture
>>> a single video source from a given HDMI or DP connector and write
>>> the resulting frames to memory.
>>>
>>> Signed-off-by: Paweł Anikiel <panikiel@google.com>
>>> ---
>>> drivers/media/platform/Kconfig | 1 +
>>> drivers/media/platform/Makefile | 1 +
>>> drivers/media/platform/google/Kconfig | 13 +
>>> drivers/media/platform/google/Makefile | 3 +
>>> drivers/media/platform/google/chv3-video.c | 891 +++++++++++++++++++++
>>> 5 files changed, 909 insertions(+)
>>> create mode 100644 drivers/media/platform/google/Kconfig
>>> create mode 100644 drivers/media/platform/google/Makefile
>>> create mode 100644 drivers/media/platform/google/chv3-video.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..9674a4c12e2d
>>> --- /dev/null
>>> +++ b/drivers/media/platform/google/Kconfig
>>> @@ -0,0 +1,13 @@
>>> +# SPDX-License-Identifier: GPL-2.0-only
>>> +
>>> +config VIDEO_CHAMELEONV3
>>> + tristate "Google Chameleon v3 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 video interface to
>>> + capture a single video source from a given HDMI or DP connector
>>> + and write the resulting frames to memory.
>>> diff --git a/drivers/media/platform/google/Makefile b/drivers/media/platform/google/Makefile
>>> new file mode 100644
>>> index 000000000000..cff06486244c
>>> --- /dev/null
>>> +++ b/drivers/media/platform/google/Makefile
>>> @@ -0,0 +1,3 @@
>>> +# SPDX-License-Identifier: GPL-2.0-only
>>> +
>>> +obj-$(CONFIG_VIDEO_CHAMELEONV3) += chv3-video.o
>>> diff --git a/drivers/media/platform/google/chv3-video.c b/drivers/media/platform/google/chv3-video.c
>>> new file mode 100644
>>> index 000000000000..6e782484abaf
>>> --- /dev/null
>>> +++ b/drivers/media/platform/google/chv3-video.c
>>> @@ -0,0 +1,891 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +/*
>>> + * Copyright 2023-2024 Google LLC.
>>> + * Author: Paweł Anikiel <panikiel@google.com>
>>> + */
>>> +
>>> +#include <linux/delay.h>
>>> +#include <linux/dma-mapping.h>
>>> +#include <linux/interrupt.h>
>>> +#include <linux/kernel.h>
>>> +#include <linux/module.h>
>>> +#include <linux/of.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/v4l2-dv-timings.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-video"
>>> +
>>> +#define VIDEO_EN 0x00
>>> +#define VIDEO_EN_BIT BIT(0)
>>> +#define VIDEO_HEIGHT 0x04
>>> +#define VIDEO_WIDTH 0x08
>>> +#define VIDEO_BUFFERA 0x0c
>>> +#define VIDEO_BUFFERB 0x10
>>> +#define VIDEO_BUFFERSIZE 0x14
>>> +#define VIDEO_RESET 0x18
>>> +#define VIDEO_RESET_BIT BIT(0)
>>> +#define VIDEO_ERRORSTATUS 0x1c
>>> +#define VIDEO_IOCOLOR 0x20
>>> +#define VIDEO_DATARATE 0x24
>>> +#define VIDEO_DATARATE_SINGLE 0x0
>>> +#define VIDEO_DATARATE_DOUBLE 0x1
>>> +#define VIDEO_PIXELMODE 0x28
>>> +#define VIDEO_PIXELMODE_SINGLE 0x0
>>> +#define VIDEO_PIXELMODE_DOUBLE 0x1
>>> +#define VIDEO_SYNCPOLARITY 0x2c
>>> +#define VIDEO_DMAFORMAT 0x30
>>> +#define VIDEO_DMAFORMAT_8BPC 0x0
>>> +#define VIDEO_DMAFORMAT_10BPC_UPPER 0x1
>>> +#define VIDEO_DMAFORMAT_10BPC_LOWER 0x2
>>> +#define VIDEO_DMAFORMAT_12BPC_UPPER 0x3
>>> +#define VIDEO_DMAFORMAT_12BPC_LOWER 0x4
>>> +#define VIDEO_DMAFORMAT_16BPC 0x5
>>> +#define VIDEO_DMAFORMAT_RAW 0x6
>>> +#define VIDEO_DMAFORMAT_8BPC_PAD 0x7
>>> +#define VIDEO_VERSION 0x34
>>> +#define VIDEO_VERSION_CURRENT 0xc0fb0001
>>> +
>>> +#define VIDEO_IRQ_MASK 0x8
>>> +#define VIDEO_IRQ_CLR 0xc
>>> +#define VIDEO_IRQ_ALL 0xf
>>> +#define VIDEO_IRQ_BUFF0 BIT(0)
>>> +#define VIDEO_IRQ_BUFF1 BIT(1)
>>> +#define VIDEO_IRQ_RESOLUTION BIT(2)
>>> +#define VIDEO_IRQ_ERROR BIT(3)
>>> +
>>> +struct chv3_video {
>>> + 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;
>>> + u32 bytes_per_pixel;
>>> +
>>> + struct v4l2_ctrl_handler ctrl_handler;
>>> + 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 video_lock;
>>> +};
>>> +
>>> +struct chv3_video_buffer {
>>> + struct vb2_v4l2_buffer vb;
>>> + struct list_head link;
>>> +};
>>> +
>>> +struct chv3_video_config {
>>> + u32 pixelformat;
>>> + u32 bytes_per_pixel;
>>> + u32 dmaformat;
>>> +};
>>> +
>>> +static void chv3_video_set_format_resolution(struct chv3_video *video, u32 width, u32 height)
>>> +{
>>> + video->pix_fmt.width = width;
>>> + video->pix_fmt.height = height;
>>> + video->pix_fmt.bytesperline = width * video->bytes_per_pixel;
>>> + video->pix_fmt.sizeimage = video->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_video_query_dv_timings_fallback(struct chv3_video *video,
>>> + struct v4l2_dv_timings *timings)
>>> +{
>>> + u32 width, height;
>>> +
>>> + width = readl(video->iobase + VIDEO_WIDTH);
>>> + height = readl(video->iobase + VIDEO_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;
>>> + timings->bt.pixelclock = width * height * 24;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int chv3_video_query_dv_timings(struct chv3_video *video, struct v4l2_dv_timings *timings)
>>> +{
>>> + if (video->subdev) {
>>> + return v4l2_subdev_call(video->subdev, pad, query_dv_timings,
>>> + video->subdev_source_pad, timings);
>>> + } else {
>>> + return chv3_video_query_dv_timings_fallback(video, timings);
>>> + }
>>
>> I would move the contents of chv3_video_query_dv_timings_fallback() to this
>> function and drop the old fallback function. It makes more sense if it is all
>> in the same function.
>>
>>> +}
>>> +
>>> +static const struct v4l2_dv_timings_cap chv3_video_fallback_dv_timings_cap = {
>>> + .type = V4L2_DV_BT_656_1120,
>>> + .bt = {
>>> + .min_width = 640,
>>> + .max_width = 7680,
>>> + .min_height = 480,
>>> + .max_height = 4320,
>>> + .min_pixelclock = 25000000,
>>> + .max_pixelclock = 1080000000,
>>> + .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_video_enum_dv_timings_fallback(struct chv3_video *video,
>>> + struct v4l2_enum_dv_timings *timings)
>>> +{
>>> + return v4l2_enum_dv_timings_cap(timings, &chv3_video_fallback_dv_timings_cap,
>>> + NULL, NULL);
>>> +}
>>> +
>>> +static int chv3_video_dv_timings_cap_fallback(struct chv3_video *video,
>>> + struct v4l2_dv_timings_cap *cap)
>>> +{
>>> + *cap = chv3_video_fallback_dv_timings_cap;
>>> +
>>> + return 0;
>>> +}
>>
>> Same for these two fallback functions: move them to the functions that calls them.
>>
>>> +
>>> +static void chv3_video_apply_dv_timings(struct chv3_video *video)
>>> +{
>>> + struct v4l2_dv_timings timings;
>>> + int res;
>>> +
>>> + res = chv3_video_query_dv_timings(video, &timings);
>>> + if (res)
>>> + return;
>>> +
>>> + video->timings = timings;
>>> + chv3_video_set_format_resolution(video, timings.bt.width, timings.bt.height);
>>> +}
>>> +
>>> +static int chv3_video_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_video_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt)
>>> +{
>>> + struct chv3_video *video = video_drvdata(file);
>>> +
>>> + fmt->fmt.pix = video->pix_fmt;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int chv3_video_enum_fmt_vid_cap(struct file *file, void *fh, struct v4l2_fmtdesc *fmt)
>>> +{
>>> + struct chv3_video *video = video_drvdata(file);
>>> +
>>> + if (fmt->index != 0)
>>> + return -EINVAL;
>>> +
>>> + fmt->flags = 0;
>>> + fmt->pixelformat = video->pix_fmt.pixelformat;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int chv3_video_g_input(struct file *file, void *fh, unsigned int *index)
>>> +{
>>> + *index = 0;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int chv3_video_s_input(struct file *file, void *fh, unsigned int index)
>>> +{
>>> + if (index != 0)
>>> + return -EINVAL;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int chv3_video_enum_input(struct file *file, void *fh, struct v4l2_input *input)
>>> +{
>>> + if (input->index != 0)
>>> + return -EINVAL;
>>> +
>>> + strscpy(input->name, "input0", sizeof(input->name));
>>
>> This name is not terribly user friendly. Is it possible to determine a more human
>> readable name? E.g. "DP1", "DP2", etc. Something that matches labeling on the Chameleon
>> board.
>
> The driver would require some board-specific instance info to
> determine if the video interface is connected to DP1, DP2, or the
> auxiliary decoder (or something entirely different if this IP was used
> on a different board). I don't see an easy way to determine such a
> human readable name, unfortunately.
It is possible, but it requires adding a connector to video pipeline in the device tree.
See e.g. Documentation/devicetree/bindings/display/connector/dp-connector.yaml and
Documentation/devicetree/bindings/media/i2c/tvp5150.txt.
While connectors are used in drm, in the media subsytem only the tvp5150 driver ever
used it for analog video inputs.
The connectors have a label, and that can be used to fill in the input name.
It is worth checking if this would work without too much effort, but if not, then
at least change the "input0" string to something like "Video Input".
>
>>
>>> + input->type = V4L2_INPUT_TYPE_CAMERA;
>>> + input->capabilities = V4L2_IN_CAP_DV_TIMINGS;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int chv3_video_g_edid(struct file *file, void *fh, struct v4l2_edid *edid)
>>> +{
>>> + struct chv3_video *video = video_drvdata(file);
>>> + int res;
>>> +
>>> + if (!video->subdev)
>>> + return -ENOTTY;
>>> +
>>> + if (edid->pad != 0)
>>> + return -EINVAL;
>>> +
>>> + edid->pad = video->subdev_source_pad;
>>> + res = v4l2_subdev_call(video->subdev, pad, get_edid, edid);
>>> + edid->pad = 0;
>>> +
>>> + return res;
>>> +}
>>> +
>>> +static int chv3_video_s_edid(struct file *file, void *fh, struct v4l2_edid *edid)
>>> +{
>>> + struct chv3_video *video = video_drvdata(file);
>>> + int res;
>>> +
>>> + if (!video->subdev)
>>> + return -ENOTTY;
>>> +
>>> + if (edid->pad != 0)
>>> + return -EINVAL;
>>> +
>>> + edid->pad = video->subdev_source_pad;
>>> + res = v4l2_subdev_call(video->subdev, pad, set_edid, edid);
>>> + edid->pad = 0;
>>> +
>>> + return res;
>>> +}
>>> +
>>> +static int chv3_video_s_dv_timings(struct file *file, void *fh, struct v4l2_dv_timings *timings)
>>> +{
>>> + struct chv3_video *video = video_drvdata(file);
>>> +
>>> + if (v4l2_match_dv_timings(&video->timings, timings, 0, false))
>>> + return 0;
>>> +
>>> + if (vb2_is_busy(&video->queue))
>>> + return -EBUSY;
>>
>> This should be moved to after the next 'if'.
>>
>>> +
>>> + if (!v4l2_valid_dv_timings(timings, &chv3_video_fallback_dv_timings_cap, NULL, NULL))
>>> + return -ERANGE;
>>> +
>>> + video->timings = *timings;
>>> + chv3_video_set_format_resolution(video, timings->bt.width, timings->bt.height);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int chv3_video_g_dv_timings(struct file *file, void *fh, struct v4l2_dv_timings *timings)
>>> +{
>>> + struct chv3_video *video = video_drvdata(file);
>>> +
>>> + *timings = video->timings;
>>> + return 0;
>>> +}
>>> +
>>> +static int chv3_video_vidioc_query_dv_timings(struct file *file, void *fh,
>>> + struct v4l2_dv_timings *timings)
>>> +{
>>> + struct chv3_video *video = video_drvdata(file);
>>> +
>>> + return chv3_video_query_dv_timings(video, timings);
>>> +}
>>> +
>>> +static int chv3_video_enum_dv_timings(struct file *file, void *fh,
>>> + struct v4l2_enum_dv_timings *timings)
>>> +{
>>> + struct chv3_video *video = video_drvdata(file);
>>> + int res;
>>> +
>>> + if (timings->pad != 0)
>>> + return -EINVAL;
>>> +
>>> + if (video->subdev) {
>>> + timings->pad = video->subdev_source_pad;
>>> + res = v4l2_subdev_call(video->subdev, pad, enum_dv_timings, timings);
>>> + timings->pad = 0;
>>> + return res;
>>> + } else {
>>> + return chv3_video_enum_dv_timings_fallback(video, timings);
>>
>> It is much easier to read if the contents of chv3_video_enum_dv_timings_fallback
>> is moved here.
>>
>>> + }
>>> +}
>>> +
>>> +static int chv3_video_dv_timings_cap(struct file *file, void *fh, struct v4l2_dv_timings_cap *cap)
>>> +{
>>> + struct chv3_video *video = video_drvdata(file);
>>> + int res;
>>> +
>>> + if (cap->pad != 0)
>>> + return -EINVAL;
>>> +
>>> + if (video->subdev) {
>>> + cap->pad = video->subdev_source_pad;
>>> + res = v4l2_subdev_call(video->subdev, pad, dv_timings_cap, cap);
>>> + cap->pad = 0;
>>> + return res;
>>> + } else {
>>> + return chv3_video_dv_timings_cap_fallback(video, cap);
>>
>> Ditto.
>>
>>> + }
>>> +}
>>> +
>>> +static int chv3_video_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_video_v4l2_ioctl_ops = {
>>> + .vidioc_querycap = chv3_video_querycap,
>>> +
>>> + .vidioc_enum_fmt_vid_cap = chv3_video_enum_fmt_vid_cap,
>>> + .vidioc_g_fmt_vid_cap = chv3_video_g_fmt_vid_cap,
>>> + .vidioc_s_fmt_vid_cap = chv3_video_g_fmt_vid_cap,
>>> + .vidioc_try_fmt_vid_cap = chv3_video_g_fmt_vid_cap,
>>> +
>>> + .vidioc_enum_input = chv3_video_enum_input,
>>> + .vidioc_g_input = chv3_video_g_input,
>>> + .vidioc_s_input = chv3_video_s_input,
>>> + .vidioc_g_edid = chv3_video_g_edid,
>>> + .vidioc_s_edid = chv3_video_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_video_s_dv_timings,
>>> + .vidioc_g_dv_timings = chv3_video_g_dv_timings,
>>> + .vidioc_query_dv_timings = chv3_video_vidioc_query_dv_timings,
>>> + .vidioc_enum_dv_timings = chv3_video_enum_dv_timings,
>>> + .vidioc_dv_timings_cap = chv3_video_dv_timings_cap,
>>> +
>>> + .vidioc_subscribe_event = chv3_video_subscribe_event,
>>> + .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
>>> +};
>>> +
>>> +static int chv3_video_queue_setup(struct vb2_queue *q,
>>> + unsigned int *nbuffers, unsigned int *nplanes,
>>> + unsigned int sizes[], struct device *alloc_devs[])
>>> +{
>>> + struct chv3_video *video = vb2_get_drv_priv(q);
>>> +
>>> + if (*nplanes) {
>>> + if (sizes[0] < video->pix_fmt.sizeimage)
>>> + return -EINVAL;
>>> + return 0;
>>> + }
>>> + *nplanes = 1;
>>> + sizes[0] = video->pix_fmt.sizeimage;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +/*
>>> + * There are two address registers: BUFFERA and BUFFERB. The device
>>> + * 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_video_buffer_dma_addr(struct chv3_video_buffer *buf)
>>> +{
>>> + return vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
>>> +}
>>> +
>>> +static void chv3_video_start_frame(struct chv3_video *video, struct chv3_video_buffer *buf)
>>> +{
>>> + video->writing_to_a = 1;
>>> + writel(chv3_video_buffer_dma_addr(buf), video->iobase + VIDEO_BUFFERA);
>>> + writel(VIDEO_EN_BIT, video->iobase + VIDEO_EN);
>>> +}
>>> +
>>> +static void chv3_video_next_frame(struct chv3_video *video, struct chv3_video_buffer *buf)
>>> +{
>>> + u32 reg = video->writing_to_a ? VIDEO_BUFFERB : VIDEO_BUFFERA;
>>> +
>>> + writel(chv3_video_buffer_dma_addr(buf), video->iobase + reg);
>>> +}
>>> +
>>> +static int chv3_video_start_streaming(struct vb2_queue *q, unsigned int count)
>>> +{
>>> + struct chv3_video *video = vb2_get_drv_priv(q);
>>> + struct chv3_video_buffer *buf;
>>> + unsigned long flags;
>>> +
>>> + video->sequence = 0;
>>> + writel(video->pix_fmt.sizeimage, video->iobase + VIDEO_BUFFERSIZE);
>>> +
>>> + spin_lock_irqsave(&video->bufs_lock, flags);
>>> + buf = list_first_entry_or_null(&video->bufs, struct chv3_video_buffer, link);
>>> + if (buf) {
>>> + chv3_video_start_frame(video, buf);
>>> + if (!list_is_last(&buf->link, &video->bufs))
>>> + chv3_video_next_frame(video, list_next_entry(buf, link));
>>> + }
>>> + spin_unlock_irqrestore(&video->bufs_lock, flags);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static void chv3_video_stop_streaming(struct vb2_queue *q)
>>> +{
>>> + struct chv3_video *video = vb2_get_drv_priv(q);
>>> + struct chv3_video_buffer *buf;
>>> + unsigned long flags;
>>> +
>>> + writel(0, video->iobase + VIDEO_EN);
>>> +
>>> + spin_lock_irqsave(&video->bufs_lock, flags);
>>> + list_for_each_entry(buf, &video->bufs, link)
>>> + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
>>> + INIT_LIST_HEAD(&video->bufs);
>>> + spin_unlock_irqrestore(&video->bufs_lock, flags);
>>> +}
>>> +
>>> +static void chv3_video_buf_queue(struct vb2_buffer *vb)
>>> +{
>>> + struct chv3_video *video = vb2_get_drv_priv(vb->vb2_queue);
>>> + struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>>> + struct chv3_video_buffer *buf = container_of(v4l2_buf, struct chv3_video_buffer, vb);
>>> + bool first, second;
>>> + unsigned long flags;
>>> +
>>> + spin_lock_irqsave(&video->bufs_lock, flags);
>>> + first = list_empty(&video->bufs);
>>> + second = list_is_singular(&video->bufs);
>>> + list_add_tail(&buf->link, &video->bufs);
>>> + if (vb2_is_streaming(vb->vb2_queue)) {
>>
>> This should be vb2_start_streaming_called().
>>
>> It does not matter all that much in this driver, since VIDIOC_STREAMON will
>> also call start_streaming, even if there are no buffers queued since the
>> vb2_queue min_queued_buffers field is 0. But if that ever changes, then
>> vb2_start_streaming_called() is the right call here.
>
> Okay, I see. Should the other use of vb2_is_streaming() within this
> file be replaced as well?
No, the other one is OK.
>
>>
>>> + if (first)
>>> + chv3_video_start_frame(video, buf);
>>> + else if (second)
>>> + chv3_video_next_frame(video, buf);
>>> + }
>>> + spin_unlock_irqrestore(&video->bufs_lock, flags);
>>> +}
>>> +
>>> +static const struct vb2_ops chv3_video_vb2_ops = {
>>> + .queue_setup = chv3_video_queue_setup,
>>> + .wait_prepare = vb2_ops_wait_prepare,
>>> + .wait_finish = vb2_ops_wait_finish,
>>> + .start_streaming = chv3_video_start_streaming,
>>> + .stop_streaming = chv3_video_stop_streaming,
>>> + .buf_queue = chv3_video_buf_queue,
>>> +};
>>> +
>>> +static int chv3_video_open(struct file *file)
>>> +{
>>> + struct chv3_video *video = video_drvdata(file);
>>> + int res;
>>> +
>>> + mutex_lock(&video->video_lock);
>>> + res = v4l2_fh_open(file);
>>> + if (!res) {
>>> + if (v4l2_fh_is_singular_file(file))
>>> + chv3_video_apply_dv_timings(video);
>>> + }
>>> + mutex_unlock(&video->video_lock);
>>> +
>>> + return res;
>>> +}
>>> +
>>> +static const struct v4l2_file_operations chv3_video_v4l2_fops = {
>>> + .owner = THIS_MODULE,
>>> + .open = chv3_video_open,
>>> + .release = vb2_fop_release,
>>> + .unlocked_ioctl = video_ioctl2,
>>> + .mmap = vb2_fop_mmap,
>>> + .poll = vb2_fop_poll,
>>> +};
>>> +
>>> +static void chv3_video_frame_irq(struct chv3_video *video)
>>> +{
>>> + struct chv3_video_buffer *buf;
>>> +
>>> + spin_lock(&video->bufs_lock);
>>> +
>>> + buf = list_first_entry_or_null(&video->bufs, struct chv3_video_buffer, link);
>>> + if (!buf)
>>> + goto empty;
>>> + list_del(&buf->link);
>>> +
>>> + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, video->pix_fmt.sizeimage);
>>> + buf->vb.vb2_buf.timestamp = ktime_get_ns();
>>> + buf->vb.sequence = video->sequence++;
>>> + buf->vb.field = V4L2_FIELD_NONE;
>>> + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
>>> +
>>> + buf = list_first_entry_or_null(&video->bufs, struct chv3_video_buffer, link);
>>> + if (buf) {
>>> + video->writing_to_a = !video->writing_to_a;
>>> + if (!list_is_last(&buf->link, &video->bufs))
>>> + chv3_video_next_frame(video, list_next_entry(buf, link));
>>> + } else {
>>> + writel(0, video->iobase + VIDEO_EN);
>>> + }
>>> +empty:
>>> + spin_unlock(&video->bufs_lock);
>>> +}
>>> +
>>> +static void chv3_video_error_irq(struct chv3_video *video)
>>> +{
>>> + if (vb2_is_streaming(&video->queue))
>>> + vb2_queue_error(&video->queue);
>>> +}
>>> +
>>> +static void chv3_video_resolution_irq(struct chv3_video *video)
>>> +{
>>> + static const struct v4l2_event event = {
>>> + .type = V4L2_EVENT_SOURCE_CHANGE,
>>> + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION,
>>> + };
>>> +
>>> + v4l2_event_queue(&video->vdev, &event);
>>> + chv3_video_error_irq(video);
>>> +}
>>> +
>>> +static irqreturn_t chv3_video_isr(int irq, void *data)
>>> +{
>>> + struct chv3_video *video = data;
>>> + unsigned int reg;
>>> +
>>> + reg = readl(video->iobase_irq + VIDEO_IRQ_CLR);
>>> + if (!reg)
>>> + return IRQ_NONE;
>>> +
>>> + if (reg & VIDEO_IRQ_BUFF0)
>>> + chv3_video_frame_irq(video);
>>> + if (reg & VIDEO_IRQ_BUFF1)
>>> + chv3_video_frame_irq(video);
>>> + if (reg & VIDEO_IRQ_RESOLUTION)
>>> + chv3_video_resolution_irq(video);
>>> + if (reg & VIDEO_IRQ_ERROR) {
>>> + dev_warn(video->dev, "error: 0x%x\n",
>>> + readl(video->iobase + VIDEO_ERRORSTATUS));
>>> + chv3_video_error_irq(video);
>>> + }
>>> +
>>> + writel(reg, video->iobase_irq + VIDEO_IRQ_CLR);
>>> +
>>> + return IRQ_HANDLED;
>>> +}
>>> +
>>> +static int chv3_video_check_version(struct chv3_video *video)
>>> +{
>>> + u32 version;
>>> +
>>> + version = readl(video->iobase + VIDEO_VERSION);
>>> + if (version != VIDEO_VERSION_CURRENT) {
>>> + dev_err(video->dev,
>>> + "wrong hw version: expected %x, got %x\n",
>>> + VIDEO_VERSION_CURRENT, version);
>>> + return -ENODEV;
>>> + }
>>> + return 0;
>>> +}
>>> +
>>> +static void chv3_video_init_timings_and_format(struct chv3_video *video,
>>> + const struct chv3_video_config *config)
>>> +{
>>> + struct v4l2_pix_format *pix = &video->pix_fmt;
>>> + struct v4l2_dv_timings timings = V4L2_DV_BT_CEA_1920X1080P60;
>>> +
>>> + video->timings = timings;
>>> + video->bytes_per_pixel = config->bytes_per_pixel;
>>> +
>>> + pix->pixelformat = config->pixelformat;
>>> + pix->field = V4L2_FIELD_NONE;
>>> + pix->colorspace = V4L2_COLORSPACE_SRGB;
>>> + chv3_video_set_format_resolution(video, timings.bt.width, timings.bt.height);
>>> +}
>>> +
>>> +#define notifier_to_video(nf) container_of(nf, struct chv3_video, notifier)
>>> +
>>> +static int chv3_video_async_notify_bound(struct v4l2_async_notifier *notifier,
>>> + struct v4l2_subdev *subdev,
>>> + struct v4l2_async_connection *asc)
>>> +{
>>> + struct chv3_video *video = notifier_to_video(notifier);
>>> + int pad;
>>> +
>>> + pad = media_entity_get_fwnode_pad(&subdev->entity, asc->match.fwnode,
>>> + MEDIA_PAD_FL_SOURCE);
>>> + if (pad < 0)
>>> + return pad;
>>> +
>>> + video->subdev = subdev;
>>> + video->subdev_source_pad = pad;
>>> +
>>> + video->v4l2_dev.ctrl_handler = subdev->ctrl_handler;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static void chv3_video_async_notify_unbind(struct v4l2_async_notifier *notifier,
>>> + struct v4l2_subdev *subdev,
>>> + struct v4l2_async_connection *asc)
>>> +{
>>> + struct chv3_video *video = notifier_to_video(notifier);
>>> +
>>> + vb2_video_unregister_device(&video->vdev);
>>> +}
>>> +
>>> +static int chv3_video_async_notify_complete(struct v4l2_async_notifier *notifier)
>>> +{
>>> + struct chv3_video *video = notifier_to_video(notifier);
>>> +
>>> + return video_register_device(&video->vdev, VFL_TYPE_VIDEO, -1);
>>> +}
>>> +
>>> +static const struct v4l2_async_notifier_operations chv3_video_async_notify_ops = {
>>> + .bound = chv3_video_async_notify_bound,
>>> + .unbind = chv3_video_async_notify_unbind,
>>> + .complete = chv3_video_async_notify_complete,
>>> +};
>>> +
>>> +static int chv3_video_fallback_init(struct chv3_video *video)
>>> +{
>>> + int res;
>>> +
>>> + video->subdev = NULL;
>>> + video->subdev_source_pad = 0;
>>> +
>>> + v4l2_ctrl_handler_init(&video->ctrl_handler, 1);
>>> + v4l2_ctrl_new_std(&video->ctrl_handler, NULL,
>>> + V4L2_CID_DV_RX_POWER_PRESENT, 0, 1, 0, 0);
>>> + res = video->ctrl_handler.error;
>>> + if (res)
>>> + goto handler_free;
>>> +
>>> + video->v4l2_dev.ctrl_handler = &video->ctrl_handler;
>>> +
>>> + res = video_register_device(&video->vdev, VFL_TYPE_VIDEO, -1);
>>> + if (res)
>>> + goto handler_free;
>>> +
>>> + return 0;
>>> +
>>> +handler_free:
>>> + v4l2_ctrl_handler_free(&video->ctrl_handler);
>>> +
>>> + return res;
>>> +}
>>> +
>>> +static int chv3_video_fwnode_init(struct chv3_video *video)
>>> +{
>>> + struct v4l2_async_connection *asc;
>>> + struct fwnode_handle *endpoint;
>>> + int res;
>>> +
>>> + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(video->dev), NULL);
>>> + if (!endpoint)
>>> + return -EINVAL;
>>> +
>>> + v4l2_async_nf_init(&video->notifier, &video->v4l2_dev);
>>> +
>>> + asc = v4l2_async_nf_add_fwnode_remote(&video->notifier, endpoint,
>>> + struct v4l2_async_connection);
>>> + fwnode_handle_put(endpoint);
>>> +
>>> + if (IS_ERR(asc))
>>> + return PTR_ERR(asc);
>>> +
>>> + video->notifier.ops = &chv3_video_async_notify_ops;
>>> + res = v4l2_async_nf_register(&video->notifier);
>>> + if (res) {
>>> + v4l2_async_nf_cleanup(&video->notifier);
>>> + return res;
>>> + }
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int chv3_video_probe(struct platform_device *pdev)
>>> +{
>>> + struct chv3_video *video;
>>> + const struct chv3_video_config *config;
>>> + int res;
>>> + int irq;
>>> +
>>> + video = devm_kzalloc(&pdev->dev, sizeof(*video), GFP_KERNEL);
>>> + if (!video)
>>> + return -ENOMEM;
>>> + video->dev = &pdev->dev;
>>> + platform_set_drvdata(pdev, video);
>>> +
>>> + config = device_get_match_data(video->dev);
>>> +
>>> + /* map register space */
>>> + video->iobase = devm_platform_ioremap_resource(pdev, 0);
>>> + if (IS_ERR(video->iobase))
>>> + return PTR_ERR(video->iobase);
>>> +
>>> + video->iobase_irq = devm_platform_ioremap_resource(pdev, 1);
>>> + if (IS_ERR(video->iobase_irq))
>>> + return PTR_ERR(video->iobase_irq);
>>> +
>>> + /* check hw version */
>>> + res = chv3_video_check_version(video);
>>> + 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_video_isr, 0, DEVICE_NAME, video);
>>> + if (res)
>>> + return res;
>>> +
>>> + /* initialize v4l2_device */
>>> + res = v4l2_device_register(&pdev->dev, &video->v4l2_dev);
>>> + if (res)
>>> + return res;
>>> +
>>> + /* initialize vb2 queue */
>>> + video->queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
>>> + video->queue.io_modes = VB2_MMAP | VB2_DMABUF;
>>> + video->queue.dev = &pdev->dev;
>>> + video->queue.lock = &video->video_lock;
>>> + video->queue.ops = &chv3_video_vb2_ops;
>>> + video->queue.mem_ops = &vb2_dma_contig_memops;
>>> + video->queue.drv_priv = video;
>>> + video->queue.buf_struct_size = sizeof(struct chv3_video_buffer);
>>> + video->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
>>> + res = vb2_queue_init(&video->queue);
>>> + if (res)
>>> + goto error;
>>> +
>>> + /* initialize video_device */
>>> + strscpy(video->vdev.name, DEVICE_NAME, sizeof(video->vdev.name));
>>> + video->vdev.fops = &chv3_video_v4l2_fops;
>>> + video->vdev.ioctl_ops = &chv3_video_v4l2_ioctl_ops;
>>> + video->vdev.lock = &video->video_lock;
>>> + video->vdev.release = video_device_release_empty;
>>> + video->vdev.device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
>>> + video->vdev.v4l2_dev = &video->v4l2_dev;
>>> + video->vdev.queue = &video->queue;
>>> + video_set_drvdata(&video->vdev, video);
>>> +
>>> + if (device_get_named_child_node(&pdev->dev, "port"))
>>> + res = chv3_video_fwnode_init(video);
>>> + else
>>> + res = chv3_video_fallback_init(video);
>>> + if (res)
>>> + goto error;
>>> +
>>> + /* initialize rest of driver struct */
>>> + INIT_LIST_HEAD(&video->bufs);
>>> + spin_lock_init(&video->bufs_lock);
>>> + mutex_init(&video->video_lock);
>>> +
>>> + chv3_video_init_timings_and_format(video, config);
>>> +
>>> + /* initialize hw */
>>> + writel(VIDEO_RESET_BIT, video->iobase + VIDEO_RESET);
>>> + writel(VIDEO_DATARATE_DOUBLE, video->iobase + VIDEO_DATARATE);
>>> + writel(VIDEO_PIXELMODE_DOUBLE, video->iobase + VIDEO_PIXELMODE);
>>> + writel(config->dmaformat, video->iobase + VIDEO_DMAFORMAT);
>>> +
>>> + writel(VIDEO_IRQ_ALL, video->iobase_irq + VIDEO_IRQ_MASK);
>>> +
>>> + return 0;
>>> +
>>> +error:
>>> + v4l2_device_unregister(&video->v4l2_dev);
>>> +
>>> + return res;
>>> +}
>>> +
>>> +static void chv3_video_remove(struct platform_device *pdev)
>>> +{
>>> + struct chv3_video *video = platform_get_drvdata(pdev);
>>> +
>>> + /* disable interrupts */
>>> + writel(0, video->iobase_irq + VIDEO_IRQ_MASK);
>>> +
>>> + if (video->subdev) {
>>> + /* notifier is initialized only in non-fallback mode */
>>> + v4l2_async_nf_unregister(&video->notifier);
>>> + v4l2_async_nf_cleanup(&video->notifier);
>>> + } else {
>>> + /* ctrl handler is initialized only in fallback mode */
>>> + v4l2_ctrl_handler_free(&video->ctrl_handler);
>>> + }
>>> +
>>> + v4l2_device_unregister(&video->v4l2_dev);
>>> +}
>>> +
>>> +static const struct chv3_video_config chv3_video_it = {
>>> + .pixelformat = V4L2_PIX_FMT_BGRX32,
>>> + .bytes_per_pixel = 4,
>>> + .dmaformat = VIDEO_DMAFORMAT_8BPC_PAD,
>>> +};
>>> +
>>> +static const struct chv3_video_config chv3_video_dp = {
>>> + .pixelformat = V4L2_PIX_FMT_RGB24,
>>> + .bytes_per_pixel = 3,
>>> + .dmaformat = VIDEO_DMAFORMAT_8BPC,
>>> +};
>>> +
>>> +static const struct of_device_id chv3_video_match_table[] = {
>>> + { .compatible = "google,chv3-video-it-1.0", .data = &chv3_video_it },
>>> + { .compatible = "google,chv3-video-dp-1.0", .data = &chv3_video_dp },
>>> + { },
>>> +};
>>> +
>>> +static struct platform_driver chv3_video_platform_driver = {
>>> + .probe = chv3_video_probe,
>>> + .remove_new = chv3_video_remove,
>>> + .driver = {
>>> + .name = DEVICE_NAME,
>>> + .of_match_table = chv3_video_match_table,
>>> + },
>>> +};
>>> +
>>> +module_platform_driver(chv3_video_platform_driver);
>>> +
>>> +MODULE_AUTHOR("Paweł Anikiel <panikiel@google.com>");
>>> +MODULE_DESCRIPTION("Google Chameleon v3 video interface driver");
>>> +MODULE_LICENSE("GPL");
>>
>> Regards,
>>
>> Hans
Regards,
Hans
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3 01/10] media: Add Chameleon v3 video interface driver
2024-06-03 14:56 ` Hans Verkuil
@ 2024-06-04 12:03 ` Paweł Anikiel
2024-06-04 12:46 ` Hans Verkuil
0 siblings, 1 reply; 26+ messages in thread
From: Paweł Anikiel @ 2024-06-04 12:03 UTC (permalink / raw)
To: Hans Verkuil
Cc: airlied, akpm, conor+dt, daniel, dinguyen, krzysztof.kozlowski+dt,
maarten.lankhorst, mchehab, mripard, robh+dt, tzimmermann,
devicetree, dri-devel, linux-kernel, linux-media,
chromeos-krk-upstreaming
On Mon, Jun 3, 2024 at 4:56 PM Hans Verkuil <hverkuil-cisco@xs4all.nl> wrote:
>
> On 03/06/2024 16:32, Paweł Anikiel wrote:
> > On Mon, Jun 3, 2024 at 9:57 AM Hans Verkuil <hverkuil-cisco@xs4all.nl> wrote:
> >>
> >> On 07/05/2024 17:54, Paweł Anikiel wrote:
> >>> Add v4l2 driver for the video interface present on the Google
> >>> Chameleon v3. The Chameleon v3 uses the video interface to capture
> >>> a single video source from a given HDMI or DP connector and write
> >>> the resulting frames to memory.
> >>>
> >>> Signed-off-by: Paweł Anikiel <panikiel@google.com>
> >>> ---
> >>> drivers/media/platform/Kconfig | 1 +
> >>> drivers/media/platform/Makefile | 1 +
> >>> drivers/media/platform/google/Kconfig | 13 +
> >>> drivers/media/platform/google/Makefile | 3 +
> >>> drivers/media/platform/google/chv3-video.c | 891 +++++++++++++++++++++
> >>> 5 files changed, 909 insertions(+)
> >>> create mode 100644 drivers/media/platform/google/Kconfig
> >>> create mode 100644 drivers/media/platform/google/Makefile
> >>> create mode 100644 drivers/media/platform/google/chv3-video.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..9674a4c12e2d
> >>> --- /dev/null
> >>> +++ b/drivers/media/platform/google/Kconfig
> >>> @@ -0,0 +1,13 @@
> >>> +# SPDX-License-Identifier: GPL-2.0-only
> >>> +
> >>> +config VIDEO_CHAMELEONV3
> >>> + tristate "Google Chameleon v3 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 video interface to
> >>> + capture a single video source from a given HDMI or DP connector
> >>> + and write the resulting frames to memory.
> >>> diff --git a/drivers/media/platform/google/Makefile b/drivers/media/platform/google/Makefile
> >>> new file mode 100644
> >>> index 000000000000..cff06486244c
> >>> --- /dev/null
> >>> +++ b/drivers/media/platform/google/Makefile
> >>> @@ -0,0 +1,3 @@
> >>> +# SPDX-License-Identifier: GPL-2.0-only
> >>> +
> >>> +obj-$(CONFIG_VIDEO_CHAMELEONV3) += chv3-video.o
> >>> diff --git a/drivers/media/platform/google/chv3-video.c b/drivers/media/platform/google/chv3-video.c
> >>> new file mode 100644
> >>> index 000000000000..6e782484abaf
> >>> --- /dev/null
> >>> +++ b/drivers/media/platform/google/chv3-video.c
> >>> @@ -0,0 +1,891 @@
> >>> +// SPDX-License-Identifier: GPL-2.0
> >>> +/*
> >>> + * Copyright 2023-2024 Google LLC.
> >>> + * Author: Paweł Anikiel <panikiel@google.com>
> >>> + */
> >>> +
> >>> +#include <linux/delay.h>
> >>> +#include <linux/dma-mapping.h>
> >>> +#include <linux/interrupt.h>
> >>> +#include <linux/kernel.h>
> >>> +#include <linux/module.h>
> >>> +#include <linux/of.h>
> >>> +#include <linux/platform_device.h>
> >>> +#include <linux/v4l2-dv-timings.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-video"
> >>> +
> >>> +#define VIDEO_EN 0x00
> >>> +#define VIDEO_EN_BIT BIT(0)
> >>> +#define VIDEO_HEIGHT 0x04
> >>> +#define VIDEO_WIDTH 0x08
> >>> +#define VIDEO_BUFFERA 0x0c
> >>> +#define VIDEO_BUFFERB 0x10
> >>> +#define VIDEO_BUFFERSIZE 0x14
> >>> +#define VIDEO_RESET 0x18
> >>> +#define VIDEO_RESET_BIT BIT(0)
> >>> +#define VIDEO_ERRORSTATUS 0x1c
> >>> +#define VIDEO_IOCOLOR 0x20
> >>> +#define VIDEO_DATARATE 0x24
> >>> +#define VIDEO_DATARATE_SINGLE 0x0
> >>> +#define VIDEO_DATARATE_DOUBLE 0x1
> >>> +#define VIDEO_PIXELMODE 0x28
> >>> +#define VIDEO_PIXELMODE_SINGLE 0x0
> >>> +#define VIDEO_PIXELMODE_DOUBLE 0x1
> >>> +#define VIDEO_SYNCPOLARITY 0x2c
> >>> +#define VIDEO_DMAFORMAT 0x30
> >>> +#define VIDEO_DMAFORMAT_8BPC 0x0
> >>> +#define VIDEO_DMAFORMAT_10BPC_UPPER 0x1
> >>> +#define VIDEO_DMAFORMAT_10BPC_LOWER 0x2
> >>> +#define VIDEO_DMAFORMAT_12BPC_UPPER 0x3
> >>> +#define VIDEO_DMAFORMAT_12BPC_LOWER 0x4
> >>> +#define VIDEO_DMAFORMAT_16BPC 0x5
> >>> +#define VIDEO_DMAFORMAT_RAW 0x6
> >>> +#define VIDEO_DMAFORMAT_8BPC_PAD 0x7
> >>> +#define VIDEO_VERSION 0x34
> >>> +#define VIDEO_VERSION_CURRENT 0xc0fb0001
> >>> +
> >>> +#define VIDEO_IRQ_MASK 0x8
> >>> +#define VIDEO_IRQ_CLR 0xc
> >>> +#define VIDEO_IRQ_ALL 0xf
> >>> +#define VIDEO_IRQ_BUFF0 BIT(0)
> >>> +#define VIDEO_IRQ_BUFF1 BIT(1)
> >>> +#define VIDEO_IRQ_RESOLUTION BIT(2)
> >>> +#define VIDEO_IRQ_ERROR BIT(3)
> >>> +
> >>> +struct chv3_video {
> >>> + 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;
> >>> + u32 bytes_per_pixel;
> >>> +
> >>> + struct v4l2_ctrl_handler ctrl_handler;
> >>> + 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 video_lock;
> >>> +};
> >>> +
> >>> +struct chv3_video_buffer {
> >>> + struct vb2_v4l2_buffer vb;
> >>> + struct list_head link;
> >>> +};
> >>> +
> >>> +struct chv3_video_config {
> >>> + u32 pixelformat;
> >>> + u32 bytes_per_pixel;
> >>> + u32 dmaformat;
> >>> +};
> >>> +
> >>> +static void chv3_video_set_format_resolution(struct chv3_video *video, u32 width, u32 height)
> >>> +{
> >>> + video->pix_fmt.width = width;
> >>> + video->pix_fmt.height = height;
> >>> + video->pix_fmt.bytesperline = width * video->bytes_per_pixel;
> >>> + video->pix_fmt.sizeimage = video->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_video_query_dv_timings_fallback(struct chv3_video *video,
> >>> + struct v4l2_dv_timings *timings)
> >>> +{
> >>> + u32 width, height;
> >>> +
> >>> + width = readl(video->iobase + VIDEO_WIDTH);
> >>> + height = readl(video->iobase + VIDEO_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;
> >>> + timings->bt.pixelclock = width * height * 24;
> >>> +
> >>> + return 0;
> >>> +}
> >>> +
> >>> +static int chv3_video_query_dv_timings(struct chv3_video *video, struct v4l2_dv_timings *timings)
> >>> +{
> >>> + if (video->subdev) {
> >>> + return v4l2_subdev_call(video->subdev, pad, query_dv_timings,
> >>> + video->subdev_source_pad, timings);
> >>> + } else {
> >>> + return chv3_video_query_dv_timings_fallback(video, timings);
> >>> + }
> >>
> >> I would move the contents of chv3_video_query_dv_timings_fallback() to this
> >> function and drop the old fallback function. It makes more sense if it is all
> >> in the same function.
> >>
> >>> +}
> >>> +
> >>> +static const struct v4l2_dv_timings_cap chv3_video_fallback_dv_timings_cap = {
> >>> + .type = V4L2_DV_BT_656_1120,
> >>> + .bt = {
> >>> + .min_width = 640,
> >>> + .max_width = 7680,
> >>> + .min_height = 480,
> >>> + .max_height = 4320,
> >>> + .min_pixelclock = 25000000,
> >>> + .max_pixelclock = 1080000000,
> >>> + .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_video_enum_dv_timings_fallback(struct chv3_video *video,
> >>> + struct v4l2_enum_dv_timings *timings)
> >>> +{
> >>> + return v4l2_enum_dv_timings_cap(timings, &chv3_video_fallback_dv_timings_cap,
> >>> + NULL, NULL);
> >>> +}
> >>> +
> >>> +static int chv3_video_dv_timings_cap_fallback(struct chv3_video *video,
> >>> + struct v4l2_dv_timings_cap *cap)
> >>> +{
> >>> + *cap = chv3_video_fallback_dv_timings_cap;
> >>> +
> >>> + return 0;
> >>> +}
> >>
> >> Same for these two fallback functions: move them to the functions that calls them.
> >>
> >>> +
> >>> +static void chv3_video_apply_dv_timings(struct chv3_video *video)
> >>> +{
> >>> + struct v4l2_dv_timings timings;
> >>> + int res;
> >>> +
> >>> + res = chv3_video_query_dv_timings(video, &timings);
> >>> + if (res)
> >>> + return;
> >>> +
> >>> + video->timings = timings;
> >>> + chv3_video_set_format_resolution(video, timings.bt.width, timings.bt.height);
> >>> +}
> >>> +
> >>> +static int chv3_video_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_video_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt)
> >>> +{
> >>> + struct chv3_video *video = video_drvdata(file);
> >>> +
> >>> + fmt->fmt.pix = video->pix_fmt;
> >>> +
> >>> + return 0;
> >>> +}
> >>> +
> >>> +static int chv3_video_enum_fmt_vid_cap(struct file *file, void *fh, struct v4l2_fmtdesc *fmt)
> >>> +{
> >>> + struct chv3_video *video = video_drvdata(file);
> >>> +
> >>> + if (fmt->index != 0)
> >>> + return -EINVAL;
> >>> +
> >>> + fmt->flags = 0;
> >>> + fmt->pixelformat = video->pix_fmt.pixelformat;
> >>> +
> >>> + return 0;
> >>> +}
> >>> +
> >>> +static int chv3_video_g_input(struct file *file, void *fh, unsigned int *index)
> >>> +{
> >>> + *index = 0;
> >>> +
> >>> + return 0;
> >>> +}
> >>> +
> >>> +static int chv3_video_s_input(struct file *file, void *fh, unsigned int index)
> >>> +{
> >>> + if (index != 0)
> >>> + return -EINVAL;
> >>> +
> >>> + return 0;
> >>> +}
> >>> +
> >>> +static int chv3_video_enum_input(struct file *file, void *fh, struct v4l2_input *input)
> >>> +{
> >>> + if (input->index != 0)
> >>> + return -EINVAL;
> >>> +
> >>> + strscpy(input->name, "input0", sizeof(input->name));
> >>
> >> This name is not terribly user friendly. Is it possible to determine a more human
> >> readable name? E.g. "DP1", "DP2", etc. Something that matches labeling on the Chameleon
> >> board.
> >
> > The driver would require some board-specific instance info to
> > determine if the video interface is connected to DP1, DP2, or the
> > auxiliary decoder (or something entirely different if this IP was used
> > on a different board). I don't see an easy way to determine such a
> > human readable name, unfortunately.
>
> It is possible, but it requires adding a connector to video pipeline in the device tree.
> See e.g. Documentation/devicetree/bindings/display/connector/dp-connector.yaml and
> Documentation/devicetree/bindings/media/i2c/tvp5150.txt.
I am using connectors in the device tree, actually. See the last
commit of this patchset. However, it's not connected directly - the
video interface is connected to the DP receiver which is then
connected to the connector.
>
> While connectors are used in drm, in the media subsytem only the tvp5150 driver ever
> used it for analog video inputs.
>
> The connectors have a label, and that can be used to fill in the input name.
>
> It is worth checking if this would work without too much effort, but if not, then
> at least change the "input0" string to something like "Video Input".
In order to read the connector label, the video interface driver would
have to make some assumptions about the incoming pipeline, e.g. figure
out which port of the decoder dt node is the input (how? just assume
it's port 0?). Do you see a good way to deal with that?
>
> >
> >>
> >>> + input->type = V4L2_INPUT_TYPE_CAMERA;
> >>> + input->capabilities = V4L2_IN_CAP_DV_TIMINGS;
> >>> +
> >>> + return 0;
> >>> +}
> >>> +
> >>> +static int chv3_video_g_edid(struct file *file, void *fh, struct v4l2_edid *edid)
> >>> +{
> >>> + struct chv3_video *video = video_drvdata(file);
> >>> + int res;
> >>> +
> >>> + if (!video->subdev)
> >>> + return -ENOTTY;
> >>> +
> >>> + if (edid->pad != 0)
> >>> + return -EINVAL;
> >>> +
> >>> + edid->pad = video->subdev_source_pad;
> >>> + res = v4l2_subdev_call(video->subdev, pad, get_edid, edid);
> >>> + edid->pad = 0;
> >>> +
> >>> + return res;
> >>> +}
> >>> +
> >>> +static int chv3_video_s_edid(struct file *file, void *fh, struct v4l2_edid *edid)
> >>> +{
> >>> + struct chv3_video *video = video_drvdata(file);
> >>> + int res;
> >>> +
> >>> + if (!video->subdev)
> >>> + return -ENOTTY;
> >>> +
> >>> + if (edid->pad != 0)
> >>> + return -EINVAL;
> >>> +
> >>> + edid->pad = video->subdev_source_pad;
> >>> + res = v4l2_subdev_call(video->subdev, pad, set_edid, edid);
> >>> + edid->pad = 0;
> >>> +
> >>> + return res;
> >>> +}
> >>> +
> >>> +static int chv3_video_s_dv_timings(struct file *file, void *fh, struct v4l2_dv_timings *timings)
> >>> +{
> >>> + struct chv3_video *video = video_drvdata(file);
> >>> +
> >>> + if (v4l2_match_dv_timings(&video->timings, timings, 0, false))
> >>> + return 0;
> >>> +
> >>> + if (vb2_is_busy(&video->queue))
> >>> + return -EBUSY;
> >>
> >> This should be moved to after the next 'if'.
> >>
> >>> +
> >>> + if (!v4l2_valid_dv_timings(timings, &chv3_video_fallback_dv_timings_cap, NULL, NULL))
> >>> + return -ERANGE;
> >>> +
> >>> + video->timings = *timings;
> >>> + chv3_video_set_format_resolution(video, timings->bt.width, timings->bt.height);
> >>> +
> >>> + return 0;
> >>> +}
> >>> +
> >>> +static int chv3_video_g_dv_timings(struct file *file, void *fh, struct v4l2_dv_timings *timings)
> >>> +{
> >>> + struct chv3_video *video = video_drvdata(file);
> >>> +
> >>> + *timings = video->timings;
> >>> + return 0;
> >>> +}
> >>> +
> >>> +static int chv3_video_vidioc_query_dv_timings(struct file *file, void *fh,
> >>> + struct v4l2_dv_timings *timings)
> >>> +{
> >>> + struct chv3_video *video = video_drvdata(file);
> >>> +
> >>> + return chv3_video_query_dv_timings(video, timings);
> >>> +}
> >>> +
> >>> +static int chv3_video_enum_dv_timings(struct file *file, void *fh,
> >>> + struct v4l2_enum_dv_timings *timings)
> >>> +{
> >>> + struct chv3_video *video = video_drvdata(file);
> >>> + int res;
> >>> +
> >>> + if (timings->pad != 0)
> >>> + return -EINVAL;
> >>> +
> >>> + if (video->subdev) {
> >>> + timings->pad = video->subdev_source_pad;
> >>> + res = v4l2_subdev_call(video->subdev, pad, enum_dv_timings, timings);
> >>> + timings->pad = 0;
> >>> + return res;
> >>> + } else {
> >>> + return chv3_video_enum_dv_timings_fallback(video, timings);
> >>
> >> It is much easier to read if the contents of chv3_video_enum_dv_timings_fallback
> >> is moved here.
> >>
> >>> + }
> >>> +}
> >>> +
> >>> +static int chv3_video_dv_timings_cap(struct file *file, void *fh, struct v4l2_dv_timings_cap *cap)
> >>> +{
> >>> + struct chv3_video *video = video_drvdata(file);
> >>> + int res;
> >>> +
> >>> + if (cap->pad != 0)
> >>> + return -EINVAL;
> >>> +
> >>> + if (video->subdev) {
> >>> + cap->pad = video->subdev_source_pad;
> >>> + res = v4l2_subdev_call(video->subdev, pad, dv_timings_cap, cap);
> >>> + cap->pad = 0;
> >>> + return res;
> >>> + } else {
> >>> + return chv3_video_dv_timings_cap_fallback(video, cap);
> >>
> >> Ditto.
> >>
> >>> + }
> >>> +}
> >>> +
> >>> +static int chv3_video_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_video_v4l2_ioctl_ops = {
> >>> + .vidioc_querycap = chv3_video_querycap,
> >>> +
> >>> + .vidioc_enum_fmt_vid_cap = chv3_video_enum_fmt_vid_cap,
> >>> + .vidioc_g_fmt_vid_cap = chv3_video_g_fmt_vid_cap,
> >>> + .vidioc_s_fmt_vid_cap = chv3_video_g_fmt_vid_cap,
> >>> + .vidioc_try_fmt_vid_cap = chv3_video_g_fmt_vid_cap,
> >>> +
> >>> + .vidioc_enum_input = chv3_video_enum_input,
> >>> + .vidioc_g_input = chv3_video_g_input,
> >>> + .vidioc_s_input = chv3_video_s_input,
> >>> + .vidioc_g_edid = chv3_video_g_edid,
> >>> + .vidioc_s_edid = chv3_video_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_video_s_dv_timings,
> >>> + .vidioc_g_dv_timings = chv3_video_g_dv_timings,
> >>> + .vidioc_query_dv_timings = chv3_video_vidioc_query_dv_timings,
> >>> + .vidioc_enum_dv_timings = chv3_video_enum_dv_timings,
> >>> + .vidioc_dv_timings_cap = chv3_video_dv_timings_cap,
> >>> +
> >>> + .vidioc_subscribe_event = chv3_video_subscribe_event,
> >>> + .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
> >>> +};
> >>> +
> >>> +static int chv3_video_queue_setup(struct vb2_queue *q,
> >>> + unsigned int *nbuffers, unsigned int *nplanes,
> >>> + unsigned int sizes[], struct device *alloc_devs[])
> >>> +{
> >>> + struct chv3_video *video = vb2_get_drv_priv(q);
> >>> +
> >>> + if (*nplanes) {
> >>> + if (sizes[0] < video->pix_fmt.sizeimage)
> >>> + return -EINVAL;
> >>> + return 0;
> >>> + }
> >>> + *nplanes = 1;
> >>> + sizes[0] = video->pix_fmt.sizeimage;
> >>> +
> >>> + return 0;
> >>> +}
> >>> +
> >>> +/*
> >>> + * There are two address registers: BUFFERA and BUFFERB. The device
> >>> + * 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_video_buffer_dma_addr(struct chv3_video_buffer *buf)
> >>> +{
> >>> + return vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
> >>> +}
> >>> +
> >>> +static void chv3_video_start_frame(struct chv3_video *video, struct chv3_video_buffer *buf)
> >>> +{
> >>> + video->writing_to_a = 1;
> >>> + writel(chv3_video_buffer_dma_addr(buf), video->iobase + VIDEO_BUFFERA);
> >>> + writel(VIDEO_EN_BIT, video->iobase + VIDEO_EN);
> >>> +}
> >>> +
> >>> +static void chv3_video_next_frame(struct chv3_video *video, struct chv3_video_buffer *buf)
> >>> +{
> >>> + u32 reg = video->writing_to_a ? VIDEO_BUFFERB : VIDEO_BUFFERA;
> >>> +
> >>> + writel(chv3_video_buffer_dma_addr(buf), video->iobase + reg);
> >>> +}
> >>> +
> >>> +static int chv3_video_start_streaming(struct vb2_queue *q, unsigned int count)
> >>> +{
> >>> + struct chv3_video *video = vb2_get_drv_priv(q);
> >>> + struct chv3_video_buffer *buf;
> >>> + unsigned long flags;
> >>> +
> >>> + video->sequence = 0;
> >>> + writel(video->pix_fmt.sizeimage, video->iobase + VIDEO_BUFFERSIZE);
> >>> +
> >>> + spin_lock_irqsave(&video->bufs_lock, flags);
> >>> + buf = list_first_entry_or_null(&video->bufs, struct chv3_video_buffer, link);
> >>> + if (buf) {
> >>> + chv3_video_start_frame(video, buf);
> >>> + if (!list_is_last(&buf->link, &video->bufs))
> >>> + chv3_video_next_frame(video, list_next_entry(buf, link));
> >>> + }
> >>> + spin_unlock_irqrestore(&video->bufs_lock, flags);
> >>> +
> >>> + return 0;
> >>> +}
> >>> +
> >>> +static void chv3_video_stop_streaming(struct vb2_queue *q)
> >>> +{
> >>> + struct chv3_video *video = vb2_get_drv_priv(q);
> >>> + struct chv3_video_buffer *buf;
> >>> + unsigned long flags;
> >>> +
> >>> + writel(0, video->iobase + VIDEO_EN);
> >>> +
> >>> + spin_lock_irqsave(&video->bufs_lock, flags);
> >>> + list_for_each_entry(buf, &video->bufs, link)
> >>> + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
> >>> + INIT_LIST_HEAD(&video->bufs);
> >>> + spin_unlock_irqrestore(&video->bufs_lock, flags);
> >>> +}
> >>> +
> >>> +static void chv3_video_buf_queue(struct vb2_buffer *vb)
> >>> +{
> >>> + struct chv3_video *video = vb2_get_drv_priv(vb->vb2_queue);
> >>> + struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> >>> + struct chv3_video_buffer *buf = container_of(v4l2_buf, struct chv3_video_buffer, vb);
> >>> + bool first, second;
> >>> + unsigned long flags;
> >>> +
> >>> + spin_lock_irqsave(&video->bufs_lock, flags);
> >>> + first = list_empty(&video->bufs);
> >>> + second = list_is_singular(&video->bufs);
> >>> + list_add_tail(&buf->link, &video->bufs);
> >>> + if (vb2_is_streaming(vb->vb2_queue)) {
> >>
> >> This should be vb2_start_streaming_called().
> >>
> >> It does not matter all that much in this driver, since VIDIOC_STREAMON will
> >> also call start_streaming, even if there are no buffers queued since the
> >> vb2_queue min_queued_buffers field is 0. But if that ever changes, then
> >> vb2_start_streaming_called() is the right call here.
> >
> > Okay, I see. Should the other use of vb2_is_streaming() within this
> > file be replaced as well?
>
> No, the other one is OK.
>
> >
> >>
> >>> + if (first)
> >>> + chv3_video_start_frame(video, buf);
> >>> + else if (second)
> >>> + chv3_video_next_frame(video, buf);
> >>> + }
> >>> + spin_unlock_irqrestore(&video->bufs_lock, flags);
> >>> +}
> >>> +
> >>> +static const struct vb2_ops chv3_video_vb2_ops = {
> >>> + .queue_setup = chv3_video_queue_setup,
> >>> + .wait_prepare = vb2_ops_wait_prepare,
> >>> + .wait_finish = vb2_ops_wait_finish,
> >>> + .start_streaming = chv3_video_start_streaming,
> >>> + .stop_streaming = chv3_video_stop_streaming,
> >>> + .buf_queue = chv3_video_buf_queue,
> >>> +};
> >>> +
> >>> +static int chv3_video_open(struct file *file)
> >>> +{
> >>> + struct chv3_video *video = video_drvdata(file);
> >>> + int res;
> >>> +
> >>> + mutex_lock(&video->video_lock);
> >>> + res = v4l2_fh_open(file);
> >>> + if (!res) {
> >>> + if (v4l2_fh_is_singular_file(file))
> >>> + chv3_video_apply_dv_timings(video);
> >>> + }
> >>> + mutex_unlock(&video->video_lock);
> >>> +
> >>> + return res;
> >>> +}
> >>> +
> >>> +static const struct v4l2_file_operations chv3_video_v4l2_fops = {
> >>> + .owner = THIS_MODULE,
> >>> + .open = chv3_video_open,
> >>> + .release = vb2_fop_release,
> >>> + .unlocked_ioctl = video_ioctl2,
> >>> + .mmap = vb2_fop_mmap,
> >>> + .poll = vb2_fop_poll,
> >>> +};
> >>> +
> >>> +static void chv3_video_frame_irq(struct chv3_video *video)
> >>> +{
> >>> + struct chv3_video_buffer *buf;
> >>> +
> >>> + spin_lock(&video->bufs_lock);
> >>> +
> >>> + buf = list_first_entry_or_null(&video->bufs, struct chv3_video_buffer, link);
> >>> + if (!buf)
> >>> + goto empty;
> >>> + list_del(&buf->link);
> >>> +
> >>> + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, video->pix_fmt.sizeimage);
> >>> + buf->vb.vb2_buf.timestamp = ktime_get_ns();
> >>> + buf->vb.sequence = video->sequence++;
> >>> + buf->vb.field = V4L2_FIELD_NONE;
> >>> + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
> >>> +
> >>> + buf = list_first_entry_or_null(&video->bufs, struct chv3_video_buffer, link);
> >>> + if (buf) {
> >>> + video->writing_to_a = !video->writing_to_a;
> >>> + if (!list_is_last(&buf->link, &video->bufs))
> >>> + chv3_video_next_frame(video, list_next_entry(buf, link));
> >>> + } else {
> >>> + writel(0, video->iobase + VIDEO_EN);
> >>> + }
> >>> +empty:
> >>> + spin_unlock(&video->bufs_lock);
> >>> +}
> >>> +
> >>> +static void chv3_video_error_irq(struct chv3_video *video)
> >>> +{
> >>> + if (vb2_is_streaming(&video->queue))
> >>> + vb2_queue_error(&video->queue);
> >>> +}
> >>> +
> >>> +static void chv3_video_resolution_irq(struct chv3_video *video)
> >>> +{
> >>> + static const struct v4l2_event event = {
> >>> + .type = V4L2_EVENT_SOURCE_CHANGE,
> >>> + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION,
> >>> + };
> >>> +
> >>> + v4l2_event_queue(&video->vdev, &event);
> >>> + chv3_video_error_irq(video);
> >>> +}
> >>> +
> >>> +static irqreturn_t chv3_video_isr(int irq, void *data)
> >>> +{
> >>> + struct chv3_video *video = data;
> >>> + unsigned int reg;
> >>> +
> >>> + reg = readl(video->iobase_irq + VIDEO_IRQ_CLR);
> >>> + if (!reg)
> >>> + return IRQ_NONE;
> >>> +
> >>> + if (reg & VIDEO_IRQ_BUFF0)
> >>> + chv3_video_frame_irq(video);
> >>> + if (reg & VIDEO_IRQ_BUFF1)
> >>> + chv3_video_frame_irq(video);
> >>> + if (reg & VIDEO_IRQ_RESOLUTION)
> >>> + chv3_video_resolution_irq(video);
> >>> + if (reg & VIDEO_IRQ_ERROR) {
> >>> + dev_warn(video->dev, "error: 0x%x\n",
> >>> + readl(video->iobase + VIDEO_ERRORSTATUS));
> >>> + chv3_video_error_irq(video);
> >>> + }
> >>> +
> >>> + writel(reg, video->iobase_irq + VIDEO_IRQ_CLR);
> >>> +
> >>> + return IRQ_HANDLED;
> >>> +}
> >>> +
> >>> +static int chv3_video_check_version(struct chv3_video *video)
> >>> +{
> >>> + u32 version;
> >>> +
> >>> + version = readl(video->iobase + VIDEO_VERSION);
> >>> + if (version != VIDEO_VERSION_CURRENT) {
> >>> + dev_err(video->dev,
> >>> + "wrong hw version: expected %x, got %x\n",
> >>> + VIDEO_VERSION_CURRENT, version);
> >>> + return -ENODEV;
> >>> + }
> >>> + return 0;
> >>> +}
> >>> +
> >>> +static void chv3_video_init_timings_and_format(struct chv3_video *video,
> >>> + const struct chv3_video_config *config)
> >>> +{
> >>> + struct v4l2_pix_format *pix = &video->pix_fmt;
> >>> + struct v4l2_dv_timings timings = V4L2_DV_BT_CEA_1920X1080P60;
> >>> +
> >>> + video->timings = timings;
> >>> + video->bytes_per_pixel = config->bytes_per_pixel;
> >>> +
> >>> + pix->pixelformat = config->pixelformat;
> >>> + pix->field = V4L2_FIELD_NONE;
> >>> + pix->colorspace = V4L2_COLORSPACE_SRGB;
> >>> + chv3_video_set_format_resolution(video, timings.bt.width, timings.bt.height);
> >>> +}
> >>> +
> >>> +#define notifier_to_video(nf) container_of(nf, struct chv3_video, notifier)
> >>> +
> >>> +static int chv3_video_async_notify_bound(struct v4l2_async_notifier *notifier,
> >>> + struct v4l2_subdev *subdev,
> >>> + struct v4l2_async_connection *asc)
> >>> +{
> >>> + struct chv3_video *video = notifier_to_video(notifier);
> >>> + int pad;
> >>> +
> >>> + pad = media_entity_get_fwnode_pad(&subdev->entity, asc->match.fwnode,
> >>> + MEDIA_PAD_FL_SOURCE);
> >>> + if (pad < 0)
> >>> + return pad;
> >>> +
> >>> + video->subdev = subdev;
> >>> + video->subdev_source_pad = pad;
> >>> +
> >>> + video->v4l2_dev.ctrl_handler = subdev->ctrl_handler;
> >>> +
> >>> + return 0;
> >>> +}
> >>> +
> >>> +static void chv3_video_async_notify_unbind(struct v4l2_async_notifier *notifier,
> >>> + struct v4l2_subdev *subdev,
> >>> + struct v4l2_async_connection *asc)
> >>> +{
> >>> + struct chv3_video *video = notifier_to_video(notifier);
> >>> +
> >>> + vb2_video_unregister_device(&video->vdev);
> >>> +}
> >>> +
> >>> +static int chv3_video_async_notify_complete(struct v4l2_async_notifier *notifier)
> >>> +{
> >>> + struct chv3_video *video = notifier_to_video(notifier);
> >>> +
> >>> + return video_register_device(&video->vdev, VFL_TYPE_VIDEO, -1);
> >>> +}
> >>> +
> >>> +static const struct v4l2_async_notifier_operations chv3_video_async_notify_ops = {
> >>> + .bound = chv3_video_async_notify_bound,
> >>> + .unbind = chv3_video_async_notify_unbind,
> >>> + .complete = chv3_video_async_notify_complete,
> >>> +};
> >>> +
> >>> +static int chv3_video_fallback_init(struct chv3_video *video)
> >>> +{
> >>> + int res;
> >>> +
> >>> + video->subdev = NULL;
> >>> + video->subdev_source_pad = 0;
> >>> +
> >>> + v4l2_ctrl_handler_init(&video->ctrl_handler, 1);
> >>> + v4l2_ctrl_new_std(&video->ctrl_handler, NULL,
> >>> + V4L2_CID_DV_RX_POWER_PRESENT, 0, 1, 0, 0);
> >>> + res = video->ctrl_handler.error;
> >>> + if (res)
> >>> + goto handler_free;
> >>> +
> >>> + video->v4l2_dev.ctrl_handler = &video->ctrl_handler;
> >>> +
> >>> + res = video_register_device(&video->vdev, VFL_TYPE_VIDEO, -1);
> >>> + if (res)
> >>> + goto handler_free;
> >>> +
> >>> + return 0;
> >>> +
> >>> +handler_free:
> >>> + v4l2_ctrl_handler_free(&video->ctrl_handler);
> >>> +
> >>> + return res;
> >>> +}
> >>> +
> >>> +static int chv3_video_fwnode_init(struct chv3_video *video)
> >>> +{
> >>> + struct v4l2_async_connection *asc;
> >>> + struct fwnode_handle *endpoint;
> >>> + int res;
> >>> +
> >>> + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(video->dev), NULL);
> >>> + if (!endpoint)
> >>> + return -EINVAL;
> >>> +
> >>> + v4l2_async_nf_init(&video->notifier, &video->v4l2_dev);
> >>> +
> >>> + asc = v4l2_async_nf_add_fwnode_remote(&video->notifier, endpoint,
> >>> + struct v4l2_async_connection);
> >>> + fwnode_handle_put(endpoint);
> >>> +
> >>> + if (IS_ERR(asc))
> >>> + return PTR_ERR(asc);
> >>> +
> >>> + video->notifier.ops = &chv3_video_async_notify_ops;
> >>> + res = v4l2_async_nf_register(&video->notifier);
> >>> + if (res) {
> >>> + v4l2_async_nf_cleanup(&video->notifier);
> >>> + return res;
> >>> + }
> >>> +
> >>> + return 0;
> >>> +}
> >>> +
> >>> +static int chv3_video_probe(struct platform_device *pdev)
> >>> +{
> >>> + struct chv3_video *video;
> >>> + const struct chv3_video_config *config;
> >>> + int res;
> >>> + int irq;
> >>> +
> >>> + video = devm_kzalloc(&pdev->dev, sizeof(*video), GFP_KERNEL);
> >>> + if (!video)
> >>> + return -ENOMEM;
> >>> + video->dev = &pdev->dev;
> >>> + platform_set_drvdata(pdev, video);
> >>> +
> >>> + config = device_get_match_data(video->dev);
> >>> +
> >>> + /* map register space */
> >>> + video->iobase = devm_platform_ioremap_resource(pdev, 0);
> >>> + if (IS_ERR(video->iobase))
> >>> + return PTR_ERR(video->iobase);
> >>> +
> >>> + video->iobase_irq = devm_platform_ioremap_resource(pdev, 1);
> >>> + if (IS_ERR(video->iobase_irq))
> >>> + return PTR_ERR(video->iobase_irq);
> >>> +
> >>> + /* check hw version */
> >>> + res = chv3_video_check_version(video);
> >>> + 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_video_isr, 0, DEVICE_NAME, video);
> >>> + if (res)
> >>> + return res;
> >>> +
> >>> + /* initialize v4l2_device */
> >>> + res = v4l2_device_register(&pdev->dev, &video->v4l2_dev);
> >>> + if (res)
> >>> + return res;
> >>> +
> >>> + /* initialize vb2 queue */
> >>> + video->queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
> >>> + video->queue.io_modes = VB2_MMAP | VB2_DMABUF;
> >>> + video->queue.dev = &pdev->dev;
> >>> + video->queue.lock = &video->video_lock;
> >>> + video->queue.ops = &chv3_video_vb2_ops;
> >>> + video->queue.mem_ops = &vb2_dma_contig_memops;
> >>> + video->queue.drv_priv = video;
> >>> + video->queue.buf_struct_size = sizeof(struct chv3_video_buffer);
> >>> + video->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> >>> + res = vb2_queue_init(&video->queue);
> >>> + if (res)
> >>> + goto error;
> >>> +
> >>> + /* initialize video_device */
> >>> + strscpy(video->vdev.name, DEVICE_NAME, sizeof(video->vdev.name));
> >>> + video->vdev.fops = &chv3_video_v4l2_fops;
> >>> + video->vdev.ioctl_ops = &chv3_video_v4l2_ioctl_ops;
> >>> + video->vdev.lock = &video->video_lock;
> >>> + video->vdev.release = video_device_release_empty;
> >>> + video->vdev.device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
> >>> + video->vdev.v4l2_dev = &video->v4l2_dev;
> >>> + video->vdev.queue = &video->queue;
> >>> + video_set_drvdata(&video->vdev, video);
> >>> +
> >>> + if (device_get_named_child_node(&pdev->dev, "port"))
> >>> + res = chv3_video_fwnode_init(video);
> >>> + else
> >>> + res = chv3_video_fallback_init(video);
> >>> + if (res)
> >>> + goto error;
> >>> +
> >>> + /* initialize rest of driver struct */
> >>> + INIT_LIST_HEAD(&video->bufs);
> >>> + spin_lock_init(&video->bufs_lock);
> >>> + mutex_init(&video->video_lock);
> >>> +
> >>> + chv3_video_init_timings_and_format(video, config);
> >>> +
> >>> + /* initialize hw */
> >>> + writel(VIDEO_RESET_BIT, video->iobase + VIDEO_RESET);
> >>> + writel(VIDEO_DATARATE_DOUBLE, video->iobase + VIDEO_DATARATE);
> >>> + writel(VIDEO_PIXELMODE_DOUBLE, video->iobase + VIDEO_PIXELMODE);
> >>> + writel(config->dmaformat, video->iobase + VIDEO_DMAFORMAT);
> >>> +
> >>> + writel(VIDEO_IRQ_ALL, video->iobase_irq + VIDEO_IRQ_MASK);
> >>> +
> >>> + return 0;
> >>> +
> >>> +error:
> >>> + v4l2_device_unregister(&video->v4l2_dev);
> >>> +
> >>> + return res;
> >>> +}
> >>> +
> >>> +static void chv3_video_remove(struct platform_device *pdev)
> >>> +{
> >>> + struct chv3_video *video = platform_get_drvdata(pdev);
> >>> +
> >>> + /* disable interrupts */
> >>> + writel(0, video->iobase_irq + VIDEO_IRQ_MASK);
> >>> +
> >>> + if (video->subdev) {
> >>> + /* notifier is initialized only in non-fallback mode */
> >>> + v4l2_async_nf_unregister(&video->notifier);
> >>> + v4l2_async_nf_cleanup(&video->notifier);
> >>> + } else {
> >>> + /* ctrl handler is initialized only in fallback mode */
> >>> + v4l2_ctrl_handler_free(&video->ctrl_handler);
> >>> + }
> >>> +
> >>> + v4l2_device_unregister(&video->v4l2_dev);
> >>> +}
> >>> +
> >>> +static const struct chv3_video_config chv3_video_it = {
> >>> + .pixelformat = V4L2_PIX_FMT_BGRX32,
> >>> + .bytes_per_pixel = 4,
> >>> + .dmaformat = VIDEO_DMAFORMAT_8BPC_PAD,
> >>> +};
> >>> +
> >>> +static const struct chv3_video_config chv3_video_dp = {
> >>> + .pixelformat = V4L2_PIX_FMT_RGB24,
> >>> + .bytes_per_pixel = 3,
> >>> + .dmaformat = VIDEO_DMAFORMAT_8BPC,
> >>> +};
> >>> +
> >>> +static const struct of_device_id chv3_video_match_table[] = {
> >>> + { .compatible = "google,chv3-video-it-1.0", .data = &chv3_video_it },
> >>> + { .compatible = "google,chv3-video-dp-1.0", .data = &chv3_video_dp },
> >>> + { },
> >>> +};
> >>> +
> >>> +static struct platform_driver chv3_video_platform_driver = {
> >>> + .probe = chv3_video_probe,
> >>> + .remove_new = chv3_video_remove,
> >>> + .driver = {
> >>> + .name = DEVICE_NAME,
> >>> + .of_match_table = chv3_video_match_table,
> >>> + },
> >>> +};
> >>> +
> >>> +module_platform_driver(chv3_video_platform_driver);
> >>> +
> >>> +MODULE_AUTHOR("Paweł Anikiel <panikiel@google.com>");
> >>> +MODULE_DESCRIPTION("Google Chameleon v3 video interface driver");
> >>> +MODULE_LICENSE("GPL");
> >>
> >> Regards,
> >>
> >> Hans
>
> Regards,
>
> Hans
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3 07/10] media: intel: Add Displayport RX IP driver
2024-06-03 8:37 ` Hans Verkuil
@ 2024-06-04 12:32 ` Paweł Anikiel
2024-06-07 12:04 ` Hans Verkuil
0 siblings, 1 reply; 26+ messages in thread
From: Paweł Anikiel @ 2024-06-04 12:32 UTC (permalink / raw)
To: Hans Verkuil
Cc: airlied, akpm, conor+dt, daniel, dinguyen, krzysztof.kozlowski+dt,
maarten.lankhorst, mchehab, mripard, robh+dt, tzimmermann,
devicetree, dri-devel, linux-kernel, linux-media,
chromeos-krk-upstreaming
On Mon, Jun 3, 2024 at 10:37 AM Hans Verkuil <hverkuil-cisco@xs4all.nl> wrote:
>
> On 07/05/2024 17:54, Paweł Anikiel wrote:
> > Add v4l2 subdev driver for the Intel Displayport receiver FPGA IP.
> > It is a part of the DisplayPort Intel FPGA IP Core, and supports
> > DisplayPort 1.4, HBR3 video capture and Multi-Stream Transport.
> >
> > Signed-off-by: Paweł Anikiel <panikiel@google.com>
> > ---
> > drivers/media/platform/intel/Kconfig | 12 +
> > drivers/media/platform/intel/Makefile | 1 +
> > drivers/media/platform/intel/intel-dprx.c | 2283 +++++++++++++++++++++
> > 3 files changed, 2296 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..734f6c2395bc
> > --- /dev/null
> > +++ b/drivers/media/platform/intel/intel-dprx.c
> > @@ -0,0 +1,2283 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright 2023-2024 Google LLC.
> > + * Author: Paweł Anikiel <panikiel@google.com>
> > + */
> > +
> > +#include <linux/crc-dp.h>
> > +#include <linux/delay.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/of.h>
> > +#include <linux/platform_device.h>
> > +#include <media/v4l2-ctrls.h>
> > +#include <media/v4l2-dv-timings.h>
> > +#include <media/v4l2-fwnode.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 v4l2_ctrl_handler ctrl_handler;
> > + struct media_pad pads[5];
> > +
> > + 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;
> > +
> > + bool hpd_state;
> > +};
> > +
> > +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 int dprx_pad_to_sink_idx(struct dprx *dprx, int pad)
> > +{
> > + int sink_idx = pad - 1;
> > +
> > + if (sink_idx < 0 || sink_idx >= dprx->max_stream_count)
> > + return -1;
> > + else
> > + return sink_idx;
> > +}
> > +
> > +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;
> > + int sink_idx;
> > + u32 end_block = edid->start_block + edid->blocks;
> > + unsigned long flags;
> > + int res = 0;
> > +
> > + memset(edid->reserved, 0, sizeof(edid->reserved));
> > +
> > + sink_idx = dprx_pad_to_sink_idx(dprx, edid->pad);
> > + if (sink_idx < 0)
> > + return -EINVAL;
> > + sink = &dprx->sinks[sink_idx];
> > +
> > + spin_lock_irqsave(&dprx->lock, flags);
> > +
> > + 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;
> > + int sink_idx;
> > + bool prev_hpd;
> > + bool cur_hpd;
> > + unsigned long flags;
> > +
> > + memset(edid->reserved, 0, sizeof(edid->reserved));
> > +
> > + sink_idx = dprx_pad_to_sink_idx(dprx, edid->pad);
> > + if (sink_idx < 0)
> > + return -EINVAL;
> > + sink = &dprx->sinks[sink_idx];
> > +
> > + if (edid->start_block != 0)
> > + return -EINVAL;
> > + if (edid->blocks > DPRX_MAX_EDID_BLOCKS) {
> > + edid->blocks = DPRX_MAX_EDID_BLOCKS;
> > + return -E2BIG;
> > + }
> > +
> > + prev_hpd = dprx->hpd_state;
> > + /*
> > + * 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.
> > + */
> > + if (sink_idx == 0)
> > + dprx->hpd_state = edid->blocks > 0;
> > + cur_hpd = dprx->hpd_state;
> > +
> > + if (prev_hpd)
> > + dprx_set_hpd(dprx, 0);
> > +
> > + spin_lock_irqsave(&dprx->lock, flags);
> > + sink->blocks = edid->blocks;
> > + memcpy(sink->edid, edid->edid, edid->blocks * 128);
> > + if (cur_hpd)
> > + dprx_reset(dprx);
> > + spin_unlock_irqrestore(&dprx->lock, flags);
> > +
> > + if (cur_hpd) {
> > + if (prev_hpd) {
> > + /*
> > + * Some DisplayPort sources are not happy with 100ms
> > + * HPD pulses. Looking at the AUX message log, it
> > + * seemed the source was waiting for values of some
> > + * DPCD registers to change when it shouldn't be.
> > + *
> > + * Not sure whose fault that is, but 500ms appears
> > + * to be a safe duration for the source to "forget"
> > + * about the sink.
> > + */
> > + msleep(500);
> > + }
> > + 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);
> > + int sink_idx;
> > + u32 htotal, vtotal;
> > + u32 hsp, hsw;
> > + u32 hstart, vstart;
> > + u32 vsp, vsw;
> > + u32 hwidth, vheight;
> > +
> > + sink_idx = dprx_pad_to_sink_idx(dprx, pad);
> > + if (sink_idx < 0)
> > + return -EINVAL;
> > +
> > + if (!((dprx_read(dprx, DPRX_VBID(sink_idx)) >> DPRX_VBID_MSA_LOCK) & 1))
> > + return -ENOLINK;
> > +
> > + htotal = dprx_read(dprx, DPRX_MSA_HTOTAL(sink_idx));
> > + vtotal = dprx_read(dprx, DPRX_MSA_VTOTAL(sink_idx));
> > + hsp = dprx_read(dprx, DPRX_MSA_HSP(sink_idx));
> > + hsw = dprx_read(dprx, DPRX_MSA_HSW(sink_idx));
> > + hstart = dprx_read(dprx, DPRX_MSA_HSTART(sink_idx));
> > + vstart = dprx_read(dprx, DPRX_MSA_VSTART(sink_idx));
> > + vsp = dprx_read(dprx, DPRX_MSA_VSP(sink_idx));
> > + vsw = dprx_read(dprx, DPRX_MSA_VSW(sink_idx));
> > + hwidth = dprx_read(dprx, DPRX_MSA_HWIDTH(sink_idx));
> > + vheight = dprx_read(dprx, DPRX_MSA_VHEIGHT(sink_idx));
> > +
> > + 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.pixelclock = htotal * vtotal * 24;
> > + 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 = 640,
> > + .max_width = 7680,
> > + .min_height = 480,
> > + .max_height = 4320,
> > + .min_pixelclock = 25000000,
> > + .max_pixelclock = 1080000000,
> > + .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_parse_bus_cfg(struct dprx *dprx, struct v4l2_fwnode_endpoint *bus_cfg)
> > +{
> > + u64 max_freq = 0;
> > + int i;
> > +
> > + for (i = 0; i < bus_cfg->nr_of_link_frequencies; i++) {
> > + if (max_freq < bus_cfg->link_frequencies[i])
> > + max_freq = bus_cfg->link_frequencies[i];
> > + }
> > +
> > + switch (max_freq) {
> > + case 1620000000:
> > + dprx->max_link_rate = 0x06;
> > + break;
> > + case 2700000000:
> > + dprx->max_link_rate = 0x0a;
> > + break;
> > + case 5400000000:
> > + dprx->max_link_rate = 0x14;
> > + break;
> > + case 8100000000:
> > + dprx->max_link_rate = 0x1e;
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
> > +
> > + dprx->max_lane_count = bus_cfg->bus.displayport.num_data_lanes;
> > + dprx->multi_stream_support = bus_cfg->bus.displayport.multi_stream_support;
> > +
> > + return 0;
> > +}
> > +
> > +static int dprx_parse_fwnode(struct dprx *dprx)
> > +{
> > + struct fwnode_handle *fwnode = dev_fwnode(dprx->dev);
> > + struct fwnode_handle *endpoint;
> > + struct v4l2_fwnode_endpoint bus_cfg = {
> > + .bus_type = V4L2_MBUS_DISPLAYPORT
> > + };
> > + int res;
> > +
> > + /* get input port node */
> > + endpoint = fwnode_graph_get_next_endpoint(fwnode, NULL);
> > + if (!endpoint)
> > + return -EINVAL;
> > +
> > + res = v4l2_fwnode_endpoint_alloc_parse(endpoint, &bus_cfg);
> > + if (res) {
> > + fwnode_handle_put(endpoint);
> > + return res;
> > + }
> > +
> > + res = dprx_parse_bus_cfg(dprx, &bus_cfg);
> > + if (res) {
> > + v4l2_fwnode_endpoint_free(&bus_cfg);
> > + fwnode_handle_put(endpoint);
> > + return res;
> > + }
> > +
> > + v4l2_fwnode_endpoint_free(&bus_cfg);
> > +
> > + /* count the number of output port nodes */
> > + endpoint = fwnode_graph_get_next_endpoint(fwnode, endpoint);
> > + dprx->max_stream_count = 0;
> > + while (endpoint) {
> > + endpoint = fwnode_graph_get_next_endpoint(fwnode, endpoint);
> > + dprx->max_stream_count++;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int dprx_probe(struct platform_device *pdev)
> > +{
> > + struct dprx *dprx;
> > + int irq;
> > + int res;
> > + int i;
> > +
> > + 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 = dprx_parse_fwnode(dprx);
> > + if (res)
> > + return res;
> > +
> > + 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_DV_DECODER;
> > + dprx->subdev.entity.ops = &dprx_entity_ops;
> > +
> > + v4l2_ctrl_handler_init(&dprx->ctrl_handler, 1);
> > + v4l2_ctrl_new_std(&dprx->ctrl_handler, NULL,
> > + V4L2_CID_DV_RX_POWER_PRESENT, 0, 1, 0, 0);
>
> You are creating this control, but it is never set to 1 when the driver detects
> that a source is connected. I am wondering if POWER_PRESENT makes sense for a
> DisplayPort connector. Is there a clean way for a sink driver to detect if a
> source is connected? For HDMI it detects the 5V pin, but it is not clear if
> there is an equivalent to that in the DP spec.
The DP spec says the source can be detected using the AUX lines:
"The Downstream devices must very weakly pull up AUX+ line and very
weakly pull down AUX- line with 1MΩ (+/-5%) resistors between the
Downstream device Connector and the AC-coupling capacitors. When AUX+
line DC voltage is L level, it means a DisplayPort Upstream device is
connected. When AUX- line DC voltage is H level, it means that a
powered DisplayPort Upstream device is connected."
This exact IP has two input signals: rx_cable_detect, and
rx_pwr_detect, which are meant to be connected to the AUX+/AUX- lines
via 10k resistors (or rather that's what the reference design does).
They're exposed to software via status registers, but there's no way
to get interrupts from them, so it wouldn't be possible to set the
control exactly when a source gets plugged in.
>
> If there is no good way to detect if a source is connected, then it might be
> better to drop POWER_PRESENT support.
>
> This control is supposed to signal that a source is connected as early as possible,
> ideally before link training etc. starts.
>
> It helps the software detect that there is a source, and report an error if a source
> is detected, but you never get a stable signal (e.g. link training fails).
This poses another problem, because the chameleon board doesn't have
this detection circuitry, and instead sets the rx_cable_detect and
rx_pwr_detect signals to always logical high. That would make the
control read "always plugged in", which IIUC is not desired.
>
> If this control is dropped, then we just accept the v4l2-compliance warning.
>
> Regards,
>
> Hans
>
> > + res = dprx->ctrl_handler.error;
> > + if (res)
> > + goto handler_free;
> > +
> > + dprx->subdev.ctrl_handler = &dprx->ctrl_handler;
> > +
> > + dprx->pads[0].flags = MEDIA_PAD_FL_SINK;
> > + for (i = 1; i <= dprx->max_stream_count; i++)
> > + dprx->pads[i].flags = MEDIA_PAD_FL_SOURCE;
> > +
> > + res = media_entity_pads_init(&dprx->subdev.entity,
> > + dprx->max_stream_count + 1, dprx->pads);
> > + if (res)
> > + goto handler_free;
> > +
> > + res = v4l2_async_register_subdev(&dprx->subdev);
> > + if (res)
> > + goto entity_cleanup;
> > +
> > + dprx->hpd_state = 0;
> > + dprx_set_hpd(dprx, 0);
> > + dprx_reset_hw(dprx);
> > +
> > + dprx_set_irq(dprx, 1);
> > +
> > + return 0;
> > +
> > +entity_cleanup:
> > + media_entity_cleanup(&dprx->subdev.entity);
> > +handler_free:
> > + v4l2_ctrl_handler_free(&dprx->ctrl_handler);
> > +
> > + return res;
> > +}
> > +
> > +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);
> > + media_entity_cleanup(&dprx->subdev.entity);
> > + v4l2_ctrl_handler_free(&dprx->ctrl_handler);
> > +}
> > +
> > +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 <panikiel@google.com>");
> > +MODULE_DESCRIPTION("Intel DisplayPort RX IP core driver");
> > +MODULE_LICENSE("GPL");
>
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3 01/10] media: Add Chameleon v3 video interface driver
2024-06-04 12:03 ` Paweł Anikiel
@ 2024-06-04 12:46 ` Hans Verkuil
0 siblings, 0 replies; 26+ messages in thread
From: Hans Verkuil @ 2024-06-04 12:46 UTC (permalink / raw)
To: Paweł Anikiel
Cc: airlied, akpm, conor+dt, daniel, dinguyen, krzysztof.kozlowski+dt,
maarten.lankhorst, mchehab, mripard, robh+dt, tzimmermann,
devicetree, dri-devel, linux-kernel, linux-media,
chromeos-krk-upstreaming
On 04/06/2024 14:03, Paweł Anikiel wrote:
> On Mon, Jun 3, 2024 at 4:56 PM Hans Verkuil <hverkuil-cisco@xs4all.nl> wrote:
>>
>> On 03/06/2024 16:32, Paweł Anikiel wrote:
>>> On Mon, Jun 3, 2024 at 9:57 AM Hans Verkuil <hverkuil-cisco@xs4all.nl> wrote:
>>>>
>>>> On 07/05/2024 17:54, Paweł Anikiel wrote:
>>>>> Add v4l2 driver for the video interface present on the Google
>>>>> Chameleon v3. The Chameleon v3 uses the video interface to capture
>>>>> a single video source from a given HDMI or DP connector and write
>>>>> the resulting frames to memory.
>>>>>
>>>>> Signed-off-by: Paweł Anikiel <panikiel@google.com>
>>>>> ---
>>>>> drivers/media/platform/Kconfig | 1 +
>>>>> drivers/media/platform/Makefile | 1 +
>>>>> drivers/media/platform/google/Kconfig | 13 +
>>>>> drivers/media/platform/google/Makefile | 3 +
>>>>> drivers/media/platform/google/chv3-video.c | 891 +++++++++++++++++++++
>>>>> 5 files changed, 909 insertions(+)
>>>>> create mode 100644 drivers/media/platform/google/Kconfig
>>>>> create mode 100644 drivers/media/platform/google/Makefile
>>>>> create mode 100644 drivers/media/platform/google/chv3-video.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..9674a4c12e2d
>>>>> --- /dev/null
>>>>> +++ b/drivers/media/platform/google/Kconfig
>>>>> @@ -0,0 +1,13 @@
>>>>> +# SPDX-License-Identifier: GPL-2.0-only
>>>>> +
>>>>> +config VIDEO_CHAMELEONV3
>>>>> + tristate "Google Chameleon v3 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 video interface to
>>>>> + capture a single video source from a given HDMI or DP connector
>>>>> + and write the resulting frames to memory.
>>>>> diff --git a/drivers/media/platform/google/Makefile b/drivers/media/platform/google/Makefile
>>>>> new file mode 100644
>>>>> index 000000000000..cff06486244c
>>>>> --- /dev/null
>>>>> +++ b/drivers/media/platform/google/Makefile
>>>>> @@ -0,0 +1,3 @@
>>>>> +# SPDX-License-Identifier: GPL-2.0-only
>>>>> +
>>>>> +obj-$(CONFIG_VIDEO_CHAMELEONV3) += chv3-video.o
>>>>> diff --git a/drivers/media/platform/google/chv3-video.c b/drivers/media/platform/google/chv3-video.c
>>>>> new file mode 100644
>>>>> index 000000000000..6e782484abaf
>>>>> --- /dev/null
>>>>> +++ b/drivers/media/platform/google/chv3-video.c
>>>>> @@ -0,0 +1,891 @@
>>>>> +// SPDX-License-Identifier: GPL-2.0
>>>>> +/*
>>>>> + * Copyright 2023-2024 Google LLC.
>>>>> + * Author: Paweł Anikiel <panikiel@google.com>
>>>>> + */
>>>>> +
>>>>> +#include <linux/delay.h>
>>>>> +#include <linux/dma-mapping.h>
>>>>> +#include <linux/interrupt.h>
>>>>> +#include <linux/kernel.h>
>>>>> +#include <linux/module.h>
>>>>> +#include <linux/of.h>
>>>>> +#include <linux/platform_device.h>
>>>>> +#include <linux/v4l2-dv-timings.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-video"
>>>>> +
>>>>> +#define VIDEO_EN 0x00
>>>>> +#define VIDEO_EN_BIT BIT(0)
>>>>> +#define VIDEO_HEIGHT 0x04
>>>>> +#define VIDEO_WIDTH 0x08
>>>>> +#define VIDEO_BUFFERA 0x0c
>>>>> +#define VIDEO_BUFFERB 0x10
>>>>> +#define VIDEO_BUFFERSIZE 0x14
>>>>> +#define VIDEO_RESET 0x18
>>>>> +#define VIDEO_RESET_BIT BIT(0)
>>>>> +#define VIDEO_ERRORSTATUS 0x1c
>>>>> +#define VIDEO_IOCOLOR 0x20
>>>>> +#define VIDEO_DATARATE 0x24
>>>>> +#define VIDEO_DATARATE_SINGLE 0x0
>>>>> +#define VIDEO_DATARATE_DOUBLE 0x1
>>>>> +#define VIDEO_PIXELMODE 0x28
>>>>> +#define VIDEO_PIXELMODE_SINGLE 0x0
>>>>> +#define VIDEO_PIXELMODE_DOUBLE 0x1
>>>>> +#define VIDEO_SYNCPOLARITY 0x2c
>>>>> +#define VIDEO_DMAFORMAT 0x30
>>>>> +#define VIDEO_DMAFORMAT_8BPC 0x0
>>>>> +#define VIDEO_DMAFORMAT_10BPC_UPPER 0x1
>>>>> +#define VIDEO_DMAFORMAT_10BPC_LOWER 0x2
>>>>> +#define VIDEO_DMAFORMAT_12BPC_UPPER 0x3
>>>>> +#define VIDEO_DMAFORMAT_12BPC_LOWER 0x4
>>>>> +#define VIDEO_DMAFORMAT_16BPC 0x5
>>>>> +#define VIDEO_DMAFORMAT_RAW 0x6
>>>>> +#define VIDEO_DMAFORMAT_8BPC_PAD 0x7
>>>>> +#define VIDEO_VERSION 0x34
>>>>> +#define VIDEO_VERSION_CURRENT 0xc0fb0001
>>>>> +
>>>>> +#define VIDEO_IRQ_MASK 0x8
>>>>> +#define VIDEO_IRQ_CLR 0xc
>>>>> +#define VIDEO_IRQ_ALL 0xf
>>>>> +#define VIDEO_IRQ_BUFF0 BIT(0)
>>>>> +#define VIDEO_IRQ_BUFF1 BIT(1)
>>>>> +#define VIDEO_IRQ_RESOLUTION BIT(2)
>>>>> +#define VIDEO_IRQ_ERROR BIT(3)
>>>>> +
>>>>> +struct chv3_video {
>>>>> + 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;
>>>>> + u32 bytes_per_pixel;
>>>>> +
>>>>> + struct v4l2_ctrl_handler ctrl_handler;
>>>>> + 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 video_lock;
>>>>> +};
>>>>> +
>>>>> +struct chv3_video_buffer {
>>>>> + struct vb2_v4l2_buffer vb;
>>>>> + struct list_head link;
>>>>> +};
>>>>> +
>>>>> +struct chv3_video_config {
>>>>> + u32 pixelformat;
>>>>> + u32 bytes_per_pixel;
>>>>> + u32 dmaformat;
>>>>> +};
>>>>> +
>>>>> +static void chv3_video_set_format_resolution(struct chv3_video *video, u32 width, u32 height)
>>>>> +{
>>>>> + video->pix_fmt.width = width;
>>>>> + video->pix_fmt.height = height;
>>>>> + video->pix_fmt.bytesperline = width * video->bytes_per_pixel;
>>>>> + video->pix_fmt.sizeimage = video->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_video_query_dv_timings_fallback(struct chv3_video *video,
>>>>> + struct v4l2_dv_timings *timings)
>>>>> +{
>>>>> + u32 width, height;
>>>>> +
>>>>> + width = readl(video->iobase + VIDEO_WIDTH);
>>>>> + height = readl(video->iobase + VIDEO_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;
>>>>> + timings->bt.pixelclock = width * height * 24;
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static int chv3_video_query_dv_timings(struct chv3_video *video, struct v4l2_dv_timings *timings)
>>>>> +{
>>>>> + if (video->subdev) {
>>>>> + return v4l2_subdev_call(video->subdev, pad, query_dv_timings,
>>>>> + video->subdev_source_pad, timings);
>>>>> + } else {
>>>>> + return chv3_video_query_dv_timings_fallback(video, timings);
>>>>> + }
>>>>
>>>> I would move the contents of chv3_video_query_dv_timings_fallback() to this
>>>> function and drop the old fallback function. It makes more sense if it is all
>>>> in the same function.
>>>>
>>>>> +}
>>>>> +
>>>>> +static const struct v4l2_dv_timings_cap chv3_video_fallback_dv_timings_cap = {
>>>>> + .type = V4L2_DV_BT_656_1120,
>>>>> + .bt = {
>>>>> + .min_width = 640,
>>>>> + .max_width = 7680,
>>>>> + .min_height = 480,
>>>>> + .max_height = 4320,
>>>>> + .min_pixelclock = 25000000,
>>>>> + .max_pixelclock = 1080000000,
>>>>> + .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_video_enum_dv_timings_fallback(struct chv3_video *video,
>>>>> + struct v4l2_enum_dv_timings *timings)
>>>>> +{
>>>>> + return v4l2_enum_dv_timings_cap(timings, &chv3_video_fallback_dv_timings_cap,
>>>>> + NULL, NULL);
>>>>> +}
>>>>> +
>>>>> +static int chv3_video_dv_timings_cap_fallback(struct chv3_video *video,
>>>>> + struct v4l2_dv_timings_cap *cap)
>>>>> +{
>>>>> + *cap = chv3_video_fallback_dv_timings_cap;
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>
>>>> Same for these two fallback functions: move them to the functions that calls them.
>>>>
>>>>> +
>>>>> +static void chv3_video_apply_dv_timings(struct chv3_video *video)
>>>>> +{
>>>>> + struct v4l2_dv_timings timings;
>>>>> + int res;
>>>>> +
>>>>> + res = chv3_video_query_dv_timings(video, &timings);
>>>>> + if (res)
>>>>> + return;
>>>>> +
>>>>> + video->timings = timings;
>>>>> + chv3_video_set_format_resolution(video, timings.bt.width, timings.bt.height);
>>>>> +}
>>>>> +
>>>>> +static int chv3_video_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_video_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt)
>>>>> +{
>>>>> + struct chv3_video *video = video_drvdata(file);
>>>>> +
>>>>> + fmt->fmt.pix = video->pix_fmt;
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static int chv3_video_enum_fmt_vid_cap(struct file *file, void *fh, struct v4l2_fmtdesc *fmt)
>>>>> +{
>>>>> + struct chv3_video *video = video_drvdata(file);
>>>>> +
>>>>> + if (fmt->index != 0)
>>>>> + return -EINVAL;
>>>>> +
>>>>> + fmt->flags = 0;
>>>>> + fmt->pixelformat = video->pix_fmt.pixelformat;
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static int chv3_video_g_input(struct file *file, void *fh, unsigned int *index)
>>>>> +{
>>>>> + *index = 0;
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static int chv3_video_s_input(struct file *file, void *fh, unsigned int index)
>>>>> +{
>>>>> + if (index != 0)
>>>>> + return -EINVAL;
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static int chv3_video_enum_input(struct file *file, void *fh, struct v4l2_input *input)
>>>>> +{
>>>>> + if (input->index != 0)
>>>>> + return -EINVAL;
>>>>> +
>>>>> + strscpy(input->name, "input0", sizeof(input->name));
>>>>
>>>> This name is not terribly user friendly. Is it possible to determine a more human
>>>> readable name? E.g. "DP1", "DP2", etc. Something that matches labeling on the Chameleon
>>>> board.
>>>
>>> The driver would require some board-specific instance info to
>>> determine if the video interface is connected to DP1, DP2, or the
>>> auxiliary decoder (or something entirely different if this IP was used
>>> on a different board). I don't see an easy way to determine such a
>>> human readable name, unfortunately.
>>
>> It is possible, but it requires adding a connector to video pipeline in the device tree.
>> See e.g. Documentation/devicetree/bindings/display/connector/dp-connector.yaml and
>> Documentation/devicetree/bindings/media/i2c/tvp5150.txt.
>
> I am using connectors in the device tree, actually. See the last
> commit of this patchset. However, it's not connected directly - the
> video interface is connected to the DP receiver which is then
> connected to the connector.
>
>>
>> While connectors are used in drm, in the media subsytem only the tvp5150 driver ever
>> used it for analog video inputs.
>>
>> The connectors have a label, and that can be used to fill in the input name.
>>
>> It is worth checking if this would work without too much effort, but if not, then
>> at least change the "input0" string to something like "Video Input".
>
> In order to read the connector label, the video interface driver would
> have to make some assumptions about the incoming pipeline, e.g. figure
> out which port of the decoder dt node is the input (how? just assume
> it's port 0?). Do you see a good way to deal with that?
It is the Displayport RX IP driver that has to parse the connector data
and create connector entities in the media topology. The video interface
driver would have to walk the graph to find those connector entities and
the entity name would contains the input name.
'git grep MEDIA_ENT_FL_CONNECTOR' gives a good idea where this is used.
Note: connectors are currently only used for S-Video and Composite inputs,
so some infrastructure would need to be added for HDMI/DP inputs.
I have never done this, so you may well encounter unexpected issues.
That said, having support for this would be really nice.
Regards,
Hans
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3 07/10] media: intel: Add Displayport RX IP driver
2024-06-04 12:32 ` Paweł Anikiel
@ 2024-06-07 12:04 ` Hans Verkuil
0 siblings, 0 replies; 26+ messages in thread
From: Hans Verkuil @ 2024-06-07 12:04 UTC (permalink / raw)
To: Paweł Anikiel
Cc: airlied, akpm, conor+dt, daniel, dinguyen, krzysztof.kozlowski+dt,
maarten.lankhorst, mchehab, mripard, robh+dt, tzimmermann,
devicetree, dri-devel, linux-kernel, linux-media,
chromeos-krk-upstreaming
On 04/06/2024 14:32, Paweł Anikiel wrote:
> On Mon, Jun 3, 2024 at 10:37 AM Hans Verkuil <hverkuil-cisco@xs4all.nl> wrote:
>>
>> On 07/05/2024 17:54, Paweł Anikiel wrote:
>>> Add v4l2 subdev driver for the Intel Displayport receiver FPGA IP.
>>> It is a part of the DisplayPort Intel FPGA IP Core, and supports
>>> DisplayPort 1.4, HBR3 video capture and Multi-Stream Transport.
>>>
>>> Signed-off-by: Paweł Anikiel <panikiel@google.com>
>>> ---
>>> drivers/media/platform/intel/Kconfig | 12 +
>>> drivers/media/platform/intel/Makefile | 1 +
>>> drivers/media/platform/intel/intel-dprx.c | 2283 +++++++++++++++++++++
>>> 3 files changed, 2296 insertions(+)
>>> create mode 100644 drivers/media/platform/intel/intel-dprx.c
>>>
<snip>
>>> +static int dprx_probe(struct platform_device *pdev)
>>> +{
>>> + struct dprx *dprx;
>>> + int irq;
>>> + int res;
>>> + int i;
>>> +
>>> + 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 = dprx_parse_fwnode(dprx);
>>> + if (res)
>>> + return res;
>>> +
>>> + 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_DV_DECODER;
>>> + dprx->subdev.entity.ops = &dprx_entity_ops;
>>> +
>>> + v4l2_ctrl_handler_init(&dprx->ctrl_handler, 1);
>>> + v4l2_ctrl_new_std(&dprx->ctrl_handler, NULL,
>>> + V4L2_CID_DV_RX_POWER_PRESENT, 0, 1, 0, 0);
>>
>> You are creating this control, but it is never set to 1 when the driver detects
>> that a source is connected. I am wondering if POWER_PRESENT makes sense for a
>> DisplayPort connector. Is there a clean way for a sink driver to detect if a
>> source is connected? For HDMI it detects the 5V pin, but it is not clear if
>> there is an equivalent to that in the DP spec.
>
> The DP spec says the source can be detected using the AUX lines:
>
> "The Downstream devices must very weakly pull up AUX+ line and very
> weakly pull down AUX- line with 1MΩ (+/-5%) resistors between the
> Downstream device Connector and the AC-coupling capacitors. When AUX+
> line DC voltage is L level, it means a DisplayPort Upstream device is
> connected. When AUX- line DC voltage is H level, it means that a
> powered DisplayPort Upstream device is connected."
>
> This exact IP has two input signals: rx_cable_detect, and
> rx_pwr_detect, which are meant to be connected to the AUX+/AUX- lines
> via 10k resistors (or rather that's what the reference design does).
> They're exposed to software via status registers, but there's no way
> to get interrupts from them, so it wouldn't be possible to set the
> control exactly when a source gets plugged in.
>
>>
>> If there is no good way to detect if a source is connected, then it might be
>> better to drop POWER_PRESENT support.
>>
>> This control is supposed to signal that a source is connected as early as possible,
>> ideally before link training etc. starts.
>>
>> It helps the software detect that there is a source, and report an error if a source
>> is detected, but you never get a stable signal (e.g. link training fails).
>
> This poses another problem, because the chameleon board doesn't have
> this detection circuitry, and instead sets the rx_cable_detect and
> rx_pwr_detect signals to always logical high. That would make the
> control read "always plugged in", which IIUC is not desired.
OK, so it is best to drop support for this control.
I recommend adding a comment in the source code explaining why it is not supported.
And in the cover letter you can mention this as well as an explanation of why
there is a v4l2-compliance warning.
Regards,
Hans
^ permalink raw reply [flat|nested] 26+ messages in thread
end of thread, other threads:[~2024-06-07 12:04 UTC | newest]
Thread overview: 26+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-05-07 15:54 [PATCH v3 00/10] Add Chameleon v3 video support Paweł Anikiel
2024-05-07 15:54 ` [PATCH v3 01/10] media: Add Chameleon v3 video interface driver Paweł Anikiel
2024-06-03 7:57 ` Hans Verkuil
2024-06-03 14:32 ` Paweł Anikiel
2024-06-03 14:56 ` Hans Verkuil
2024-06-04 12:03 ` Paweł Anikiel
2024-06-04 12:46 ` Hans Verkuil
2024-05-07 15:54 ` [PATCH v3 02/10] drm/dp_mst: Move DRM-independent structures to separate header Paweł Anikiel
2024-05-07 15:54 ` [PATCH v3 03/10] lib: Move DisplayPort CRC functions to common lib Paweł Anikiel
2024-05-07 15:54 ` [PATCH v3 04/10] drm/display: Add mask definitions for DP_PAYLOAD_ALLOCATE_* registers Paweł Anikiel
2024-05-07 15:54 ` [PATCH v3 05/10] media: dt-bindings: video-interfaces: Support DisplayPort MST Paweł Anikiel
2024-05-10 21:16 ` Rob Herring
2024-05-13 11:07 ` Paweł Anikiel
2024-05-13 14:56 ` Rob Herring (Arm)
2024-05-07 15:54 ` [PATCH v3 06/10] media: v4l2-mediabus: Add support for DisplayPort media bus Paweł Anikiel
2024-05-07 15:54 ` [PATCH v3 07/10] media: intel: Add Displayport RX IP driver Paweł Anikiel
2024-06-03 8:37 ` Hans Verkuil
2024-06-04 12:32 ` Paweł Anikiel
2024-06-07 12:04 ` Hans Verkuil
2024-05-07 15:54 ` [PATCH v3 08/10] media: dt-bindings: Add Chameleon v3 video interface Paweł Anikiel
2024-05-10 21:25 ` Rob Herring (Arm)
2024-05-07 15:54 ` [PATCH v3 09/10] media: dt-bindings: Add Intel Displayport RX IP Paweł Anikiel
2024-05-10 21:24 ` Rob Herring
2024-05-13 10:39 ` Paweł Anikiel
2024-05-07 15:54 ` [PATCH v3 10/10] ARM: dts: chameleonv3: Add video device nodes Paweł Anikiel
2024-06-03 8:44 ` [PATCH v3 00/10] Add Chameleon v3 video support Hans Verkuil
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).