* [PATCH 0/1] Add driver for StarTech USB3HDCAP capture device
@ 2026-03-25 17:06 Matthew Laux
2026-03-25 17:06 ` [PATCH 1/1] staging: media: add driver for StarTech USB3HDCAP Matthew Laux
0 siblings, 1 reply; 2+ messages in thread
From: Matthew Laux @ 2026-03-25 17:06 UTC (permalink / raw)
To: linux-media; +Cc: mchehab, gregkh, Matthew Laux
Add a V4L2/ALSA driver for the StarTech USB3HDCAP (and Micomsoft
XCAPTURE-1 variant), a USB 3.0 video capture device based on the
MST3367 HDMI receiver, TW9900 composite decoder, and CS53L21 audio codec.
The driver supports composite, S-Video, component, and HDMI inputs,
including audio capture. I'm targeting staging because the I2C chip
drivers are currently embedded rather than using proper subdevice drivers,
and some other minor issues remain (see TODO).
Tested on all four inputs at various resolutions. All v4l2-compliance
tests pass, including streaming tests, but I did find what might be a bug
in v4l2-compliance that causes any device that uses V4L2_FIELD_ALTERNATE
to fail the streaming tests - I believe the sequence number checks in
v4l2-test-buffers.cpp are contradictory for V4L2_FIELD_ALTERNATE and
impossible to satisfy. I temporarily changed my driver to report
FIELD_NONE to have the streaming tests run. I'm happy to fix the
v4l2-compliance issue if it is indeed an issue, or fix my driver if
it's not :)
This is my first contribution. I based this driver heavily on the
existing `usbtv` driver and welcome all feedback. I am committed to
eventually getting this out of staging and will stick around to make
any improvements needed so that can happen.
Thanks,
Matthew
$ v4l2-compliance -s -d /dev/video2
v4l2-compliance 1.33.0-5455, 64 bits, 64-bit time_t
v4l2-compliance SHA: 95ad25f6a77a 2026-03-17 13:08:36
Compliance test for usb3hdcap device /dev/video2:
Driver Info:
Driver name : usb3hdcap
Card type : StarTech USB3HDCAP
Bus info : usb-0000:00:14.0-2
Driver version : 6.12.73
Capabilities : 0x85200001
Video Capture
Read/Write
Streaming
Extended Pix Format
Device Capabilities
Device Caps : 0x05200001
Video Capture
Read/Write
Streaming
Extended Pix Format
Required ioctls:
test VIDIOC_QUERYCAP: OK
test invalid ioctls: OK
Allow for multiple opens:
test second /dev/video2 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
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: 4 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
fail: v4l2-test-io-config.cpp(210): field == V4L2_FIELD_NONE
fail: v4l2-test-io-config.cpp(386): Timings check failed for input 2.
test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: FAIL
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: 8 Private Controls: 0
Format ioctls (Input 0):
test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
test VIDIOC_G/S_PARM: OK
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 (Not Supported)
test Requests: OK (Not Supported)
test blocking wait: OK
Control ioctls (Input 1):
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: 8 Private Controls: 0
Format ioctls (Input 1):
test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
test VIDIOC_G/S_PARM: OK
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 1):
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 1):
test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK
test CREATE_BUFS maximum buffers: OK
test VIDIOC_REMOVE_BUFS: OK
test VIDIOC_EXPBUF: OK (Not Supported)
test Requests: OK (Not Supported)
test blocking wait: OK
Control ioctls (Input 2):
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: 8 Private Controls: 0
Format ioctls (Input 2):
test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
test VIDIOC_G/S_PARM: OK
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 2):
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 2):
test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK
test CREATE_BUFS maximum buffers: OK
test VIDIOC_REMOVE_BUFS: OK
test VIDIOC_EXPBUF: OK (Not Supported)
test Requests: OK (Not Supported)
test blocking wait: OK
Control ioctls (Input 3):
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: 8 Private Controls: 0
Format ioctls (Input 3):
test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
test VIDIOC_G/S_PARM: OK
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 3):
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 3):
test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK
test CREATE_BUFS maximum buffers: OK
test VIDIOC_REMOVE_BUFS: OK
test VIDIOC_EXPBUF: OK (Not Supported)
test Requests: OK (Not Supported)
test blocking wait: OK
Test input 0:
Streaming ioctls:
test read/write: OK
test MMAP (no poll, REQBUFS): OK
test MMAP (select, REQBUFS): OK
test MMAP (epoll, REQBUFS): OK
test MMAP (no poll, CREATE_BUFS): OK
test MMAP (select, CREATE_BUFS): OK
test MMAP (epoll, CREATE_BUFS): OK
test USERPTR (no poll): OK
test USERPTR (select): OK
test DMABUF (no poll): OK (Not Supported)
test DMABUF (select): OK (Not Supported)
Total for usb3hdcap device /dev/video2: 134, Succeeded: 133, Failed: 1,
Warnings: 0
Matthew Laux (1):
staging: media: add driver for StarTech USB3HDCAP
MAINTAINERS | 8 +
drivers/staging/media/Kconfig | 2 +
drivers/staging/media/Makefile | 1 +
drivers/staging/media/usb3hdcap/Kconfig | 13 +
drivers/staging/media/usb3hdcap/Makefile | 4 +
drivers/staging/media/usb3hdcap/TODO | 16 +
.../staging/media/usb3hdcap/usb3hdcap-audio.c | 305 +++++
.../media/usb3hdcap/usb3hdcap-composite.c | 182 +++
.../staging/media/usb3hdcap/usb3hdcap-core.c | 1048 +++++++++++++++++
.../staging/media/usb3hdcap/usb3hdcap-hdmi.c | 808 +++++++++++++
.../staging/media/usb3hdcap/usb3hdcap-video.c | 511 ++++++++
drivers/staging/media/usb3hdcap/usb3hdcap.h | 239 ++++
12 files changed, 3137 insertions(+)
create mode 100644 drivers/staging/media/usb3hdcap/Kconfig
create mode 100644 drivers/staging/media/usb3hdcap/Makefile
create mode 100644 drivers/staging/media/usb3hdcap/TODO
create mode 100644 drivers/staging/media/usb3hdcap/usb3hdcap-audio.c
create mode 100644 drivers/staging/media/usb3hdcap/usb3hdcap-composite.c
create mode 100644 drivers/staging/media/usb3hdcap/usb3hdcap-core.c
create mode 100644 drivers/staging/media/usb3hdcap/usb3hdcap-hdmi.c
create mode 100644 drivers/staging/media/usb3hdcap/usb3hdcap-video.c
create mode 100644 drivers/staging/media/usb3hdcap/usb3hdcap.h
base-commit: 0e2c4117c3512cf6b8f54c2c3d37564bfa3ccd67
--
2.47.3
^ permalink raw reply [flat|nested] 2+ messages in thread
* [PATCH 1/1] staging: media: add driver for StarTech USB3HDCAP
2026-03-25 17:06 [PATCH 0/1] Add driver for StarTech USB3HDCAP capture device Matthew Laux
@ 2026-03-25 17:06 ` Matthew Laux
0 siblings, 0 replies; 2+ messages in thread
From: Matthew Laux @ 2026-03-25 17:06 UTC (permalink / raw)
To: linux-media; +Cc: mchehab, gregkh, Matthew Laux
Add a V4L2 driver for the StarTech USB3HDCAP video capture device. This is
a popular capture card based on the MST3367 HDMI receiver and TW9900
composite video decoder. Support is included for the composite, S-Video,
component, and HDMI inputs, and these inputs have been tested at various
resolutions. Audio support is provided by an ALSA device.
Also includes preliminary support for the Micomsoft XCAPTURE-1 hardware
variant. I've provided a TODO containing the remaining items necessary to
get this out of staging.
Signed-off-by: Matthew Laux <matthew.laux@gmail.com>
---
MAINTAINERS | 8 +
drivers/staging/media/Kconfig | 2 +
drivers/staging/media/Makefile | 1 +
drivers/staging/media/usb3hdcap/Kconfig | 13 +
drivers/staging/media/usb3hdcap/Makefile | 4 +
drivers/staging/media/usb3hdcap/TODO | 16 +
.../staging/media/usb3hdcap/usb3hdcap-audio.c | 305 +++++
.../media/usb3hdcap/usb3hdcap-composite.c | 182 +++
.../staging/media/usb3hdcap/usb3hdcap-core.c | 1048 +++++++++++++++++
.../staging/media/usb3hdcap/usb3hdcap-hdmi.c | 808 +++++++++++++
.../staging/media/usb3hdcap/usb3hdcap-video.c | 511 ++++++++
drivers/staging/media/usb3hdcap/usb3hdcap.h | 239 ++++
12 files changed, 3137 insertions(+)
create mode 100644 drivers/staging/media/usb3hdcap/Kconfig
create mode 100644 drivers/staging/media/usb3hdcap/Makefile
create mode 100644 drivers/staging/media/usb3hdcap/TODO
create mode 100644 drivers/staging/media/usb3hdcap/usb3hdcap-audio.c
create mode 100644 drivers/staging/media/usb3hdcap/usb3hdcap-composite.c
create mode 100644 drivers/staging/media/usb3hdcap/usb3hdcap-core.c
create mode 100644 drivers/staging/media/usb3hdcap/usb3hdcap-hdmi.c
create mode 100644 drivers/staging/media/usb3hdcap/usb3hdcap-video.c
create mode 100644 drivers/staging/media/usb3hdcap/usb3hdcap.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 750ac8c4a7b0..bab204eeab56 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -25303,6 +25303,14 @@ S: Supported
F: Documentation/devicetree/bindings/interrupt-controller/starfive,jh8100-intc.yaml
F: drivers/irqchip/irq-starfive-jh8100-intc.c
+STARTECH USB3HDCAP MEDIA DRIVER
+M: Matthew Laux <matthew.laux@gmail.com>
+L: linux-media@vger.kernel.org
+S: Supported
+W: https://linuxtv.org
+Q: http://patchwork.linuxtv.org/project/linux-media/list/
+F: drivers/staging/media/usb3hdcap/
+
STATIC BRANCH/CALL
M: Peter Zijlstra <peterz@infradead.org>
M: Josh Poimboeuf <jpoimboe@kernel.org>
diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig
index 1aa31bddf970..3f4cbc190a7a 100644
--- a/drivers/staging/media/Kconfig
+++ b/drivers/staging/media/Kconfig
@@ -38,6 +38,8 @@ source "drivers/staging/media/sunxi/Kconfig"
source "drivers/staging/media/tegra-video/Kconfig"
+source "drivers/staging/media/usb3hdcap/Kconfig"
+
menuconfig STAGING_MEDIA_DEPRECATED
bool "Media staging drivers (DEPRECATED)"
default n
diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile
index 6f78b0edde1e..e8c5fd2725f1 100644
--- a/drivers/staging/media/Makefile
+++ b/drivers/staging/media/Makefile
@@ -9,3 +9,4 @@ obj-$(CONFIG_VIDEO_TEGRA) += tegra-video/
obj-$(CONFIG_VIDEO_IPU3_IMGU) += ipu3/
obj-$(CONFIG_VIDEO_INTEL_IPU7) += ipu7/
obj-$(CONFIG_DVB_AV7110) += av7110/
+obj-$(CONFIG_VIDEO_USB3HDCAP) += usb3hdcap/
diff --git a/drivers/staging/media/usb3hdcap/Kconfig b/drivers/staging/media/usb3hdcap/Kconfig
new file mode 100644
index 000000000000..edca098bd334
--- /dev/null
+++ b/drivers/staging/media/usb3hdcap/Kconfig
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0
+config VIDEO_USB3HDCAP
+ tristate "USB3HDCAP video capture support"
+ depends on VIDEO_DEV && SND
+ select SND_PCM
+ select VIDEOBUF2_VMALLOC
+
+ help
+ This is a video4linux2 driver for the StarTech USB3HDCAP video/audio
+ capture device.
+
+ To compile this driver as a module, choose M here; the
+ module will be called usb3hdcap.
diff --git a/drivers/staging/media/usb3hdcap/Makefile b/drivers/staging/media/usb3hdcap/Makefile
new file mode 100644
index 000000000000..b72e2464ab1d
--- /dev/null
+++ b/drivers/staging/media/usb3hdcap/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+usb3hdcap-y := usb3hdcap-core.o usb3hdcap-video.o usb3hdcap-composite.o \
+ usb3hdcap-hdmi.o usb3hdcap-audio.o
+obj-$(CONFIG_VIDEO_USB3HDCAP) += usb3hdcap.o
diff --git a/drivers/staging/media/usb3hdcap/TODO b/drivers/staging/media/usb3hdcap/TODO
new file mode 100644
index 000000000000..92412de20e34
--- /dev/null
+++ b/drivers/staging/media/usb3hdcap/TODO
@@ -0,0 +1,16 @@
+* Prerequisite for using subdevices: i2c_algorithm/i2c_adapter that converts
+ to the correct USB vendor requests depending on whether the hardware has the
+ NUC100 or not
+* Use existing tw9900 subdevice
+ * Need to make the regulator optional
+ * Need to only call `v4l2_async_register_subdev` when registering from
+ device tree so this driver can call `v4l2_device_register_subdev`
+ directly
+ * Need to port over my additional functionality to tw9900.c
+* Write new subdevice for MST3367
+ * Won't be much use to any other driver initially
+* Generify CS53L21 support (can probably base it on sound/soc/codecs/cs53l30.c)
+* Use v4l2_fh
+* Fix MST3367 support for XCAPTURE-1
+* Fix HDMI color space issues (pretty sure the values it's using are always the
+ RGB ones)
\ No newline at end of file
diff --git a/drivers/staging/media/usb3hdcap/usb3hdcap-audio.c b/drivers/staging/media/usb3hdcap/usb3hdcap-audio.c
new file mode 100644
index 000000000000..a08855c4f170
--- /dev/null
+++ b/drivers/staging/media/usb3hdcap/usb3hdcap-audio.c
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * USB3HDCAP audio support - CS53L21 init + ALSA capture device
+ *
+ * Audio is embedded in the isochronous video stream as 48kHz stereo
+ * 16-bit LE PCM. Each video line carries 8, 12, or 16 bytes of audio data.
+ */
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "usb3hdcap.h"
+
+/* ALSA buffer sizing */
+#define HDCAP_AUDIO_BUFSZ (64 * 1024)
+
+static const struct snd_pcm_hardware snd_hdcap_hw = {
+ .info = SNDRV_PCM_INFO_BATCH |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .period_bytes_min = 1024,
+ .period_bytes_max = 65536,
+ .periods_min = 2,
+ .periods_max = 128,
+ .buffer_bytes_max = HDCAP_AUDIO_BUFSZ,
+};
+
+/* ------------------------------------------------------------------ */
+/* PCM ops */
+/* ------------------------------------------------------------------ */
+
+static int snd_hdcap_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct usb3hdcap *hdcap = snd_pcm_substream_chip(substream);
+
+ hdcap->snd_substream = substream;
+ substream->runtime->hw = snd_hdcap_hw;
+
+ return 0;
+}
+
+static int snd_hdcap_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct usb3hdcap *hdcap = snd_pcm_substream_chip(substream);
+
+ atomic_set(&hdcap->snd_stream, 0);
+ hdcap->snd_substream = NULL;
+
+ return 0;
+}
+
+static int snd_hdcap_prepare(struct snd_pcm_substream *substream)
+{
+ struct usb3hdcap *hdcap = snd_pcm_substream_chip(substream);
+
+ hdcap->snd_buffer_pos = 0;
+ hdcap->snd_period_pos = 0;
+
+ return 0;
+}
+
+static int snd_hdcap_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct usb3hdcap *hdcap = snd_pcm_substream_chip(substream);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ atomic_set(&hdcap->snd_stream, 1);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ atomic_set(&hdcap->snd_stream, 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t snd_hdcap_pointer(
+ struct snd_pcm_substream *substream)
+{
+ struct usb3hdcap *hdcap = snd_pcm_substream_chip(substream);
+
+ return hdcap->snd_buffer_pos;
+}
+
+static const struct snd_pcm_ops snd_hdcap_pcm_ops = {
+ .open = snd_hdcap_pcm_open,
+ .close = snd_hdcap_pcm_close,
+ .prepare = snd_hdcap_prepare,
+ .trigger = snd_hdcap_trigger,
+ .pointer = snd_hdcap_pointer,
+};
+
+/* ------------------------------------------------------------------ */
+/* Audio data feed (called from ISO URB handler in usb3hdcap-core.c) */
+/* ------------------------------------------------------------------ */
+
+void usb3hdcap_audio_data(struct usb3hdcap *hdcap, const u8 *data, int len)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_pcm_runtime *runtime;
+ size_t frame_bytes, nframes;
+ size_t buffer_pos, period_pos;
+ int period_elapsed = 0;
+ unsigned long flags;
+
+ if (!atomic_read(&hdcap->snd_stream))
+ return;
+
+ substream = hdcap->snd_substream;
+ if (!substream)
+ return;
+
+ runtime = substream->runtime;
+ if (!runtime || !runtime->dma_area)
+ return;
+
+ frame_bytes = runtime->frame_bits >> 3;
+ nframes = len / frame_bytes;
+ if (!nframes)
+ return;
+
+ snd_pcm_stream_lock_irqsave(substream, flags);
+
+ buffer_pos = hdcap->snd_buffer_pos;
+ period_pos = hdcap->snd_period_pos;
+
+ /* Copy into ALSA ring buffer, handling wrap */
+ if (buffer_pos + nframes >= runtime->buffer_size) {
+ size_t cnt = (runtime->buffer_size - buffer_pos) * frame_bytes;
+
+ memcpy(runtime->dma_area + buffer_pos * frame_bytes,
+ data, cnt);
+ memcpy(runtime->dma_area, data + cnt,
+ nframes * frame_bytes - cnt);
+ } else {
+ memcpy(runtime->dma_area + buffer_pos * frame_bytes,
+ data, nframes * frame_bytes);
+ }
+
+ buffer_pos += nframes;
+ period_pos += nframes;
+
+ if (buffer_pos >= runtime->buffer_size)
+ buffer_pos -= runtime->buffer_size;
+
+ if (period_pos >= runtime->period_size) {
+ period_pos -= runtime->period_size;
+ period_elapsed = 1;
+ }
+
+ hdcap->snd_buffer_pos = buffer_pos;
+ hdcap->snd_period_pos = period_pos;
+
+ snd_pcm_stream_unlock_irqrestore(substream, flags);
+
+ if (period_elapsed)
+ snd_pcm_period_elapsed(substream);
+}
+
+/* ------------------------------------------------------------------ */
+/* CS53L21 codec init */
+/* ------------------------------------------------------------------ */
+
+int usb3hdcap_cs53l21_init(struct usb3hdcap *hdcap)
+{
+ int ret;
+
+ /* MIC power control: power down mic B, mic A, and bias */
+ /* SPEED = "10 - Half-Speed Mode (HSM) - 12.5 to 25 kHz sample rates" */
+ ret = u3hc_i2c_write(hdcap, ADDR_CS53L21, CS53L21_MIC_PWR_CTL, 0x4e);
+ if (ret)
+ return ret;
+
+ /* interface: I2S, master */
+ ret = u3hc_i2c_write(hdcap, ADDR_CS53L21, CS53L21_IFACE_CTL, 0x44);
+ if (ret)
+ return ret;
+
+ /* ADC input select: default */
+ ret = u3hc_i2c_write(hdcap, ADDR_CS53L21, CS53L21_ADC_IN_SEL, 0x00);
+ if (ret)
+ return ret;
+
+ /* ALC/PGA: 0 dB */
+ ret = u3hc_i2c_write(hdcap, ADDR_CS53L21, CS53L21_ALC_PGAA, 0x00);
+ if (ret)
+ return ret;
+ ret = u3hc_i2c_write(hdcap, ADDR_CS53L21, CS53L21_ALC_PGAB, 0x00);
+ if (ret)
+ return ret;
+
+ /* ADC attenuators: 0 dB */
+ ret = u3hc_i2c_write(hdcap, ADDR_CS53L21, CS53L21_ADCA_ATT, 0x00);
+ if (ret)
+ return ret;
+ ret = u3hc_i2c_write(hdcap, ADDR_CS53L21, CS53L21_ADCB_ATT, 0x00);
+ if (ret)
+ return ret;
+
+ /* ALC enable and attack rate = fastest attack */
+ ret = u3hc_i2c_write(hdcap, ADDR_CS53L21, CS53L21_ALC_EN_ATK, 0xc0);
+ if (ret)
+ return ret;
+
+ /* interface: "SPE Processed ADC data to ADC serial port, SDOUT data." */
+ ret = u3hc_i2c_write(hdcap, ADDR_CS53L21, CS53L21_IFACE_CTL, 0x46);
+ if (ret)
+ return ret;
+
+ /* SPE control: SPE enable, "Soft Ramp" turned on */
+ ret = u3hc_i2c_write(hdcap, ADDR_CS53L21, CS53L21_SPE_CTL, 0x42);
+ if (ret)
+ return ret;
+
+ /* ADC mixer volume: +2.5 dB */
+ ret = u3hc_i2c_write(hdcap, ADDR_CS53L21, CS53L21_ADCA_MIX_VOL, 0x05);
+ if (ret)
+ return ret;
+ ret = u3hc_i2c_write(hdcap, ADDR_CS53L21, CS53L21_ADCB_MIX_VOL, 0x05);
+ if (ret)
+ return ret;
+
+ /* channel mixer: off */
+ ret = u3hc_i2c_write(hdcap, ADDR_CS53L21, CS53L21_CH_MIXER, 0x00);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------------ */
+/* ALSA card init / teardown */
+/* ------------------------------------------------------------------ */
+
+int usb3hdcap_audio_init(struct usb3hdcap *hdcap)
+{
+ int ret;
+ struct snd_card *card;
+ struct snd_pcm *pcm;
+
+ atomic_set(&hdcap->snd_stream, 0);
+
+ ret = snd_card_new(hdcap->dev, SNDRV_DEFAULT_IDX1, "usb3hdcap",
+ THIS_MODULE, 0, &card);
+ if (ret < 0)
+ return ret;
+
+ strscpy(card->driver, "usb3hdcap", sizeof(card->driver));
+ strscpy(card->shortname, "USB3HDCAP Audio",
+ sizeof(card->shortname));
+ snprintf(card->longname, sizeof(card->longname),
+ "USB3HDCAP Audio at bus %d device %d",
+ hdcap->usb_dev->bus->busnum, hdcap->usb_dev->devnum);
+
+ hdcap->snd = card;
+
+ ret = snd_pcm_new(card, "USB3HDCAP Audio", 0, 0, 1, &pcm);
+ if (ret < 0)
+ goto err;
+
+ strscpy(pcm->name, "USB3HDCAP Audio Input", sizeof(pcm->name));
+ pcm->info_flags = 0;
+ pcm->private_data = hdcap;
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_hdcap_pcm_ops);
+ snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+ NULL, HDCAP_AUDIO_BUFSZ, HDCAP_AUDIO_BUFSZ);
+
+ ret = snd_card_register(card);
+ if (ret)
+ goto err;
+
+ dev_info(hdcap->dev, "ALSA audio device registered\n");
+ return 0;
+
+err:
+ hdcap->snd = NULL;
+ snd_card_free(card);
+ return ret;
+}
+
+void usb3hdcap_audio_free(struct usb3hdcap *hdcap)
+{
+ if (hdcap->snd && hdcap->usb_dev) {
+ snd_card_free_when_closed(hdcap->snd);
+ hdcap->snd = NULL;
+ }
+}
diff --git a/drivers/staging/media/usb3hdcap/usb3hdcap-composite.c b/drivers/staging/media/usb3hdcap/usb3hdcap-composite.c
new file mode 100644
index 000000000000..dab8c6bb6db9
--- /dev/null
+++ b/drivers/staging/media/usb3hdcap/usb3hdcap-composite.c
@@ -0,0 +1,182 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * USB3HDCAP composite video init - CPLD + TW9900 setup, signal lock
+ */
+
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/usb.h>
+#include <media/v4l2-ctrls.h>
+
+#include "usb3hdcap.h"
+
+/* Detect PAL vs NTSC from TW9900 auto-detection result */
+static void usb3hdcap_detect_std(struct usb3hdcap *hdcap)
+{
+ int k, sdt, detected;
+
+ /* 0x80 = start detection */
+ /* 0x03 = enable recognition of PAL and NTSC only */
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_SDTR, 0x83);
+
+ for (k = 0; k < 10; k++) {
+ sdt = u3hc_i2c_read(hdcap, ADDR_TW9900, TW9900_SDT);
+ if (!(sdt & 0x80))
+ break;
+ msleep(100);
+ }
+
+ if (sdt & 0x80) {
+ dev_warn(hdcap->dev, "Detection not done after 1s, using NTSC\n");
+ hdcap->std = V4L2_STD_NTSC;
+ return;
+ }
+
+ detected = (sdt & TW9900_STDNOW_MASK) >> TW9900_STDNOW_SHIFT;
+ switch (detected) {
+ case TW9900_STD_NTSC_M:
+ hdcap->std = V4L2_STD_NTSC;
+ dev_info(hdcap->dev, "NTSC detected (SDT=0x%02x)\n", sdt);
+ break;
+ case TW9900_STD_PAL_BDGHI:
+ hdcap->std = V4L2_STD_PAL;
+ dev_info(hdcap->dev, "PAL detected (SDT=0x%02x)\n", sdt);
+ break;
+ default: /* not sure if this will be hit, because others are disabled */
+ hdcap->std = V4L2_STD_NTSC;
+ dev_info(hdcap->dev, "Non-PAL/NTSC detected (SDT=0x%02x)\n", sdt);
+ break;
+ }
+
+ hdcap->detected_timings_present = 0;
+}
+
+/* Detect 240p/288p (non-interlaced) vs 480i/576i */
+static void usb3hdcap_detect_size(struct usb3hdcap *hdcap)
+{
+ int status;
+ int full_h = (hdcap->std & V4L2_STD_PAL) ? PAL_HEIGHT : NTSC_HEIGHT;
+ int half_h = full_h / 2;
+
+ hdcap->width = SD_WIDTH;
+ hdcap->height = half_h;
+
+ status = u3hc_i2c_read(hdcap, ADDR_TW9900, TW9900_CSTATUS_II);
+ if (status >= 0 && (status & TW9900_NINTL)) {
+ hdcap->interlaced = 0;
+ dev_info(hdcap->dev, "NINTL=1: %dp detected (SDTR=0x%02x)\n",
+ half_h, status);
+ } else {
+ hdcap->interlaced = 1;
+ dev_info(hdcap->dev, "NINTL=0: %di detected (SDTR=0x%02x)\n",
+ full_h, status);
+ }
+}
+
+int usb3hdcap_composite_init(struct usb3hdcap *hdcap)
+{
+ int k, status;
+
+ /* disable MST3367 */
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x00, 0x00); /* bank 0 */
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x51, 0x80);
+
+ /* disable TW9900 */
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_ACNTL_I, 0x0e);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_ACNTL_II, 0x40);
+
+ /* select TW9900 input of cpld? */
+ /* from windows driver */
+ status = u3hc_i2c_read(hdcap, ADDR_CPLD, 0x3b);
+ if (status != 0)
+ u3hc_i2c_write(hdcap, ADDR_CPLD, 0x3b, 0x80);
+ u3hc_i2c_write(hdcap, ADDR_CPLD, 0x20, 0x05);
+ u3hc_i2c_write(hdcap, ADDR_CPLD, 0x00, 0x01);
+ u3hc_i2c_write(hdcap, ADDR_CPLD, 0x10, 0xfe);
+ u3hc_i2c_write(hdcap, ADDR_CPLD, 0x20, 0x75);
+
+ /* Main TW9900 configuration */
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_ACNTL_I, 0x00);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_OPFORM, 0xa2);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_OPCNTL_I, 0x01);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_VDELAY_LO, 0x14);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_VACTIVE_LO, 0xf2);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_HDELAY_LO, 0x0b);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_HACTIVE_LO, 0xd2);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_VBICNTL, 0x57);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_ACNTL_II, 0x0f);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_OPCNTL_II, 0x00);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_SDT, 0x07); /* auto-detect */
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_VCNTL1, 0x0c);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_VCNTL2, 0x03);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_MISC1, 0x07);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_MISC2, 0x06);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_ANAPLLCTL, 0x01);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_VVBI, 0x00);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_HSBEGIN, 0x26);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_HSEND, 0x36);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_OVSDLY, 0xf0);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_OVSEND, 0x28);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_ACNTL_I, 0x80);
+ if (hdcap->input == INPUT_SVIDEO) {
+ /* "Input crystal clock frequency is 27MHz" */
+ /* IFSEL 01 for S-Video, YSEL 01 for Y on Mux1 */
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_INFORM, 0x54);
+ /* "internal current reference 2" + both luma and chroma ADCs on */
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_ACNTL_I, 0x40);
+
+ } else {
+ /* IFSEL 00 for composite, YSEL 00 for Y on Mux0 */
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_INFORM, 0x40);
+ /* "internal current reference 2" + chroma ADC off */
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_ACNTL_I, 0x42);
+ }
+ v4l2_ctrl_handler_setup(&hdcap->ctrl);
+
+ vendor_out(hdcap, REQ_STREAM, 0x0000, 0, NULL, 0);
+
+ for (k = 0; k < 10; k++) {
+ status = u3hc_i2c_read(hdcap, ADDR_TW9900, TW9900_CSTATUS);
+
+ /*
+ * 0x40 = "Horizontal sync PLL is locked to the incoming video source"
+ * 0x08 = "Vertical logic is locked to the incoming video source"
+ */
+ if (status >= 0 && (status & 0x48) == 0x48) {
+ dev_info(hdcap->dev, "Signal locked (%d ms)\n", k * 100);
+ break;
+ }
+ msleep(100);
+ }
+
+ if (k == 10) {
+ dev_err(hdcap->dev, "No signal lock after 1s (last status=0x%02x)\n",
+ status);
+ return -ETIMEDOUT;
+ }
+
+ usb3hdcap_detect_std(hdcap);
+ usb3hdcap_detect_size(hdcap);
+
+ /* Adjust for PAL vs NTSC */
+ if (hdcap->std & V4L2_STD_PAL) {
+ u3hc_i2c_rmw(hdcap, ADDR_TW9900, TW9900_CROP_HI, 0x0f, 0x10);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_VDELAY_LO, 0x19);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_VACTIVE_LO, 0x20);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_HDELAY_LO, 0x0a);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_HACTIVE_LO, 0xd0);
+ u3hc_i2c_rmw(hdcap, ADDR_TW9900, TW9900_VVBI, 0xef, 0x00);
+ } else {
+ u3hc_i2c_rmw(hdcap, ADDR_TW9900, TW9900_CROP_HI, 0x0f, 0x00);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_VDELAY_LO, 0x14);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_VACTIVE_LO, 0xf2);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_HDELAY_LO, 0x10);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_HACTIVE_LO, 0xd0);
+ u3hc_i2c_rmw(hdcap, ADDR_TW9900, TW9900_VVBI, 0xef, 0x10);
+ }
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_CNTRL2, 0x00);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_SDT_NOISE, 0x11);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_LUMA_CTL, 0x00);
+
+ return 0;
+}
diff --git a/drivers/staging/media/usb3hdcap/usb3hdcap-core.c b/drivers/staging/media/usb3hdcap/usb3hdcap-core.c
new file mode 100644
index 000000000000..d04d1c3fac2f
--- /dev/null
+++ b/drivers/staging/media/usb3hdcap/usb3hdcap-core.c
@@ -0,0 +1,1048 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * StarTech USB3HDCAP USB 3.0 HD Video Capture Driver
+ * a.k.a. YUAN High-Tech Development Co., Ltd UB530
+ *
+ * Supported inputs are composite, S-Video, component, and HDMI. I haven't
+ * attempted to support DVI or VGA.
+ *
+ * Tested video modes (all at 60fps):
+ * composite 240p and 480i
+ * S-Video 240p and 480i
+ * component 240p, 480i, 480p, 720p, 1080i
+ * HDMI 1080p
+ *
+ * Other video modes will probably "just work" if the appropriate entries
+ * are added in the mode tables and code is added to disambiguate using
+ * htotal if necessary.
+ *
+ * Audio is supported and should work with all inputs.
+ *
+ * Also partially supports the Micomsoft XCAPTURE-1 for composite and
+ * S-Video only... HDMI is unusable, component is untested.
+ *
+ * Based on USBPcap analysis and reverse engineering of CY3014.X64.SYS
+ * (version 1.1.0.193 of Fri, 04 Jun 2021 09:37:45 UTC)
+ * MST3367 HDMI path based on hdcapm
+ * Driver structure based heavily on usbtv
+ */
+
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kref.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/mutex.h>
+#include <media/v4l2-common.h>
+#include <linux/v4l2-dv-timings.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-vmalloc.h>
+
+#include "usb3hdcap.h"
+
+#define USB3HDCAP_VID 0x1164
+#define USB3HDCAP_PID 0xf533
+#define XCAPTURE1_PID 0xf531
+
+#define V4L2_CID_USB3HDCAP_CSC (V4L2_CID_USER_BASE | 0x1000)
+
+static const char * const csc_menu[] = {
+ "Default (Component)",
+ "Default (HDMI)",
+ "YCbCr (HDMI)",
+ NULL,
+};
+
+/* ------------------------------------------------------------------ */
+/* USB control transfer helpers */
+/* ------------------------------------------------------------------ */
+
+int vendor_out(
+ struct usb3hdcap *hdcap,
+ u8 request,
+ u16 value,
+ u16 index,
+ u8 *data,
+ u16 len)
+{
+ int ret;
+ u8 *buf = NULL;
+
+ if (len > 0) {
+ buf = kmemdup(data, len, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+ }
+
+ ret = usb_control_msg(hdcap->usb_dev,
+ usb_sndctrlpipe(hdcap->usb_dev, 0),
+ request,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ value, index, buf, len, 1000);
+
+ kfree(buf);
+
+ if (ret < 0) {
+ dev_err(hdcap->dev, "%s 0x%02x val=0x%04x idx=0x%04x: %d\n",
+ __func__, request, value, index, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int vendor_in(
+ struct usb3hdcap *hdcap,
+ u8 request,
+ u16 value,
+ u16 index,
+ u8 *data,
+ u16 len)
+{
+ u8 *buf;
+ int ret;
+
+ buf = kmalloc(len, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = usb_control_msg(hdcap->usb_dev,
+ usb_rcvctrlpipe(hdcap->usb_dev, 0),
+ request,
+ USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ value, index, buf, len, 1000);
+
+ if (ret >= 0)
+ memcpy(data, buf, min_t(int, ret, len));
+
+ kfree(buf);
+ return ret;
+}
+
+/* ------------------------------------------------------------------ */
+/* MCU I2C tunnel (XCAPTURE-1) */
+/* */
+/* The Nuvoton NUC100 MCU intercepts I2C traffic. Transactions are */
+/* tunneled through a different USB vendor protocol: */
+/* Write: bRequest=0xC0, wValue=0x5066 or addr|0x5000 */
+/* Read: two-phase write+read, always through wValue=0x5066 */
+/* */
+/* Some addresses need the 0x66 "gateway" (target addr in payload), */
+/* others use the I2C addr directly in wValue with 0x5000 flag. */
+/* ------------------------------------------------------------------ */
+
+static bool mcu_use_gateway(u8 addr)
+{
+ /* Matches Windows driver address routing logic */
+ return ((u8)(addr + 0x78) & 0xeb) || addr == 0x8c;
+}
+
+static int mcu_i2c_write(struct usb3hdcap *hdcap, u8 addr, u8 reg, u8 val)
+{
+ u8 buf[3];
+
+ if (mcu_use_gateway(addr)) {
+ buf[0] = addr;
+ buf[1] = reg;
+ buf[2] = val;
+ return vendor_out(hdcap, REQ_I2C, 0x5066, 0, buf, 3);
+ }
+
+ buf[0] = reg;
+ buf[1] = val;
+ return vendor_out(hdcap, REQ_I2C, addr | 0x5000, 0, buf, 2);
+}
+
+static int mcu_i2c_read(struct usb3hdcap *hdcap, u8 addr, u8 reg)
+{
+ u8 tx[3] = { addr | 1, 1, reg };
+ u8 val;
+ int ret;
+
+ /* send target addr, rx length, and register */
+ ret = vendor_out(hdcap, REQ_I2C, 0x5066, 0, tx, 3);
+ if (ret < 0)
+ return ret;
+
+ /* Retrieve the result */
+ ret = vendor_in(hdcap, REQ_I2C, 0x5066, 0, &val, 1);
+ if (ret < 0) {
+ dev_err(hdcap->dev, "%s 0x%02x reg 0x%02x: %d\n",
+ __func__, addr, reg, ret);
+ return ret;
+ }
+
+ return val;
+}
+
+int u3hc_i2c_write(struct usb3hdcap *hdcap, u8 addr, u8 reg, u8 val)
+{
+ if (hdcap->has_mcu)
+ return mcu_i2c_write(hdcap, addr, reg, val);
+ return vendor_out(hdcap, REQ_I2C, addr, reg, &val, 1);
+}
+
+int u3hc_i2c_read(struct usb3hdcap *hdcap, u8 addr, u8 reg)
+{
+ u8 val;
+ int ret;
+
+ if (hdcap->has_mcu)
+ return mcu_i2c_read(hdcap, addr, reg);
+
+ ret = vendor_in(hdcap, REQ_I2C, addr, reg, &val, 1);
+ if (ret < 0) {
+ dev_err(hdcap->dev, "%s 0x%02x reg 0x%02x: %d\n",
+ __func__, addr, reg, ret);
+ return ret;
+ }
+
+ return val;
+}
+
+int u3hc_i2c_rmw(struct usb3hdcap *hdcap, u8 addr, u8 reg, u8 mask, u8 bits)
+{
+ int val = u3hc_i2c_read(hdcap, addr, reg);
+
+ if (val < 0)
+ return val;
+ return u3hc_i2c_write(hdcap, addr, reg, (val & mask) | bits);
+}
+
+int u3hc_i2c_rmw_get_old(struct usb3hdcap *hdcap, u8 addr, u8 reg, u8 mask, u8 bits, u8 *old)
+{
+ int val = u3hc_i2c_read(hdcap, addr, reg);
+
+ if (val < 0)
+ return val;
+ *old = val;
+ return u3hc_i2c_write(hdcap, addr, reg, (val & mask) | bits);
+}
+
+/* CheckIsMCUExist() */
+static void usb3hdcap_probe_mcu(struct usb3hdcap *hdcap)
+{
+ u8 tx[5] = { 0xab, 0x03, 0x12, 0x34, 0x57 };
+ u8 rx[3] = {};
+ int k, ret;
+
+ hdcap->has_mcu = 0;
+
+ /* Assert CPLD GPIO 0x39 to wake MCU? */
+ vendor_out(hdcap, REQ_GPIO, 0xc039, 0, NULL, 0);
+ vendor_out(hdcap, REQ_GPIO, 0x4134, 0, NULL, 0);
+
+ /* Poll GPIO 0x39 until MCU signals ready */
+ for (k = 0; k < 50; k++) {
+ msleep(20);
+ ret = vendor_in(hdcap, REQ_GPIO, 0x39, 0, rx, 1);
+ if (ret >= 0 && rx[0] == 1)
+ break;
+ }
+
+ if (k == 50) {
+ dev_info(hdcap->dev, "MCU not detected (GPIO timeout)\n");
+ return;
+ }
+
+ dev_dbg(hdcap->dev, "MCU freed, wait count=%d\n", k);
+
+ /* Send probe: magic {0x12, 0x34, 0x57} to MCU at I2C 0xAA */
+ ret = vendor_out(hdcap, REQ_I2C, 0x5066, 0, tx, sizeof(tx));
+ if (ret < 0)
+ return;
+
+ memset(rx, 0, sizeof(rx));
+ ret = vendor_in(hdcap, REQ_I2C, 0x5066, 0, rx, 3);
+ if (ret < 0)
+ return;
+
+ if (rx[0] == 'Q' && rx[1] == 0x10) {
+ hdcap->has_mcu = 1;
+ dev_info(hdcap->dev, "Nuvoton NUC100 MCU detected\n");
+ } else {
+ dev_info(hdcap->dev,
+ "MCU probe response: %02x %02x %02x (not recognized)\n",
+ rx[0], rx[1], rx[2]);
+ }
+}
+
+/* Device-level init + GPIO pin state? shared between all inputs */
+static int usb3hdcap_device_init(struct usb3hdcap *hdcap)
+{
+ vendor_out(hdcap, REQ_INIT, 0x0000, 0, NULL, 0);
+ vendor_out(hdcap, REQ_UNK_C7, 0x0064, 0, NULL, 0);
+ /* Windows driver does this but is always fails? */
+ /* vendor_out(hdcap, 0xc5, 0x0000, 0, NULL, 0); */
+ vendor_out(hdcap, REQ_STREAM, 0x0000, 0, NULL, 0);
+ vendor_out(hdcap, REQ_STREAM, 0x0000, 0, NULL, 0);
+
+ /*
+ * just replay what the windows driver does:
+ * FUN_14022e3f4(param_1,0x402d);
+ * FUN_14022e3f4(param_1,0x12d);
+ * FUN_140001220(0x32);
+ * FUN_14022e3f4(param_1,0x2d);
+ * FUN_140001220(0x32);
+ * FUN_14022e3f4(param_1,0x12d);
+ * FUN_140001220(0x32);
+ * FUN_14022e3f4(param_1,0x802d);
+ */
+
+ vendor_out(hdcap, REQ_GPIO, 0x402d, 0, NULL, 0);
+ vendor_out(hdcap, REQ_GPIO, 0x012d, 0, NULL, 0);
+ msleep(50);
+ vendor_out(hdcap, REQ_GPIO, 0x002d, 0, NULL, 0);
+ msleep(50);
+ vendor_out(hdcap, REQ_GPIO, 0x012d, 0, NULL, 0);
+ msleep(50);
+ vendor_out(hdcap, REQ_GPIO, 0x802d, 0, NULL, 0);
+
+ /* Probe for MCU after GPIO reset (XCAPTURE-1 only) */
+ if (!hdcap->has_mcu &&
+ le16_to_cpu(hdcap->usb_dev->descriptor.idProduct) == XCAPTURE1_PID)
+ usb3hdcap_probe_mcu(hdcap);
+
+ return 0;
+}
+
+int usb3hdcap_hw_init(struct usb3hdcap *hdcap)
+{
+ /* Clear stale detection state from previous input */
+ hdcap->std = 0;
+ hdcap->requested_std = 0;
+ hdcap->detected_timings_present = 0;
+ hdcap->requested_timings_present = 0;
+
+ usb3hdcap_device_init(hdcap);
+ usb3hdcap_cs53l21_init(hdcap);
+
+ switch (hdcap->input) {
+ case INPUT_HDMI:
+ return usb3hdcap_hdmi_init(hdcap);
+ case INPUT_COMPONENT:
+ return usb3hdcap_component_init(hdcap);
+ case INPUT_COMPOSITE:
+ case INPUT_SVIDEO:
+ default:
+ return usb3hdcap_composite_init(hdcap);
+ }
+}
+
+/* ------------------------------------------------------------------ */
+/* V4L2 ioctls */
+/* ------------------------------------------------------------------ */
+
+static int usb3hdcap_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct usb3hdcap *hdcap = video_drvdata(file);
+
+ strscpy(cap->driver, "usb3hdcap", sizeof(cap->driver));
+ strscpy(cap->card, hdcap->has_mcu ?
+ "Micomsoft XCAPTURE-1" : "StarTech USB3HDCAP",
+ sizeof(cap->card));
+ usb_make_path(hdcap->usb_dev, cap->bus_info, sizeof(cap->bus_info));
+ return 0;
+}
+
+static int usb3hdcap_enum_input(struct file *file, void *priv,
+ struct v4l2_input *i)
+{
+ if (i->index > INPUT_HDMI)
+ return -EINVAL;
+
+ i->type = V4L2_INPUT_TYPE_CAMERA;
+ switch (i->index) {
+ case INPUT_COMPOSITE:
+ strscpy(i->name, "Composite", sizeof(i->name));
+ i->std = USB3HDCAP_V4L2_STDS;
+ i->capabilities = V4L2_IN_CAP_STD;
+ break;
+ case INPUT_SVIDEO:
+ strscpy(i->name, "S-Video", sizeof(i->name));
+ i->std = USB3HDCAP_V4L2_STDS;
+ i->capabilities = V4L2_IN_CAP_STD;
+ break;
+ case INPUT_COMPONENT:
+ strscpy(i->name, "Component", sizeof(i->name));
+ i->std = 0;
+ i->capabilities = V4L2_IN_CAP_DV_TIMINGS;
+ break;
+ case INPUT_HDMI:
+ strscpy(i->name, "HDMI", sizeof(i->name));
+ i->std = 0;
+ i->capabilities = V4L2_IN_CAP_DV_TIMINGS;
+ break;
+ }
+ return 0;
+}
+
+static int usb3hdcap_enum_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ if (f->index > 0)
+ return -EINVAL;
+
+ f->pixelformat = V4L2_PIX_FMT_YUYV;
+ return 0;
+}
+
+static int usb3hdcap_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct usb3hdcap *hdcap = video_drvdata(file);
+
+ int width = hdcap->width;
+ int height = hdcap->height;
+ int interlaced = hdcap->interlaced;
+
+ /*
+ * If timings were explicitly set, report format matching those
+ * timings instead of what was detected from the signal
+ */
+ if (hdcap->requested_timings_present) {
+ const struct v4l2_bt_timings *bt = &hdcap->requested_timings.bt;
+
+ width = bt->width;
+ height = bt->interlaced ? bt->height / 2 : bt->height;
+ interlaced = bt->interlaced;
+ }
+
+ f->fmt.pix.width = width;
+ f->fmt.pix.height = height;
+ f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
+ f->fmt.pix.field = interlaced ? V4L2_FIELD_ALTERNATE : V4L2_FIELD_NONE;
+ f->fmt.pix.bytesperline = width * 2;
+ f->fmt.pix.sizeimage = width * 2 * height;
+ f->fmt.pix.colorspace = (hdcap->input == INPUT_HDMI) ?
+ V4L2_COLORSPACE_REC709 : V4L2_COLORSPACE_SMPTE170M;
+
+ return 0;
+}
+
+static int usb3hdcap_g_std(struct file *file, void *priv, v4l2_std_id *norm)
+{
+ struct usb3hdcap *hdcap = video_drvdata(file);
+
+ if (!(hdcap->input == INPUT_COMPOSITE || hdcap->input == INPUT_SVIDEO))
+ return -ENODATA;
+
+ /* try what was explicitly set, if unavailable, use detected */
+ if (hdcap->requested_std)
+ *norm = hdcap->requested_std;
+ else if (hdcap->std)
+ *norm = hdcap->std;
+ else
+ *norm = V4L2_STD_NTSC;
+ return 0;
+}
+
+static int usb3hdcap_s_std(struct file *file, void *priv, v4l2_std_id norm)
+{
+ struct usb3hdcap *hdcap = video_drvdata(file);
+
+ if (!(hdcap->input == INPUT_COMPOSITE || hdcap->input == INPUT_SVIDEO))
+ return -ENODATA;
+
+ if (!(norm & USB3HDCAP_V4L2_STDS))
+ return -EINVAL;
+
+ hdcap->requested_std = norm;
+ return 0;
+}
+
+/* ------------------------------------------------------------------ */
+/* DV timings (HDMI + HD component) */
+/* ------------------------------------------------------------------ */
+
+static const struct v4l2_dv_timings_cap usb3hdcap_timings_cap = {
+ .type = V4L2_DV_BT_656_1120,
+ .bt = {
+ .min_width = 720,
+ .max_width = 1920,
+ .min_height = 480,
+ .max_height = 1080,
+ .min_pixelclock = 13500000, /* 480i */
+ .max_pixelclock = 148500000, /* 1080p60 */
+ .standards = V4L2_DV_BT_STD_CEA861,
+ .capabilities = V4L2_DV_BT_CAP_PROGRESSIVE |
+ V4L2_DV_BT_CAP_INTERLACED,
+ },
+};
+
+const struct v4l2_dv_timings all_supported_dv_timings[] = {
+ /* component only */
+ V4L2_DV_BT_CEA_720X480I59_94,
+ V4L2_DV_BT_CEA_720X576I50,
+ V4L2_DV_BT_CEA_1920X1080I60,
+
+ /* hdmi only */
+ V4L2_DV_BT_CEA_640X480P59_94,
+ V4L2_DV_BT_CEA_1280X720P50,
+
+ /* both */
+ V4L2_DV_BT_CEA_720X480P59_94,
+ V4L2_DV_BT_CEA_720X576P50,
+ V4L2_DV_BT_CEA_1280X720P60,
+ V4L2_DV_BT_CEA_1920X1080P60,
+};
+
+static int usb3hdcap_g_dv_timings(
+ struct file *file,
+ void *priv,
+ struct v4l2_dv_timings *timings)
+{
+ struct usb3hdcap *hdcap = video_drvdata(file);
+
+ /* composite/S-Video -> no DV timings */
+ if (!(hdcap->input == INPUT_COMPONENT || hdcap->input == INPUT_HDMI))
+ return -ENODATA;
+
+ /* nothing set with s_dv_timings and nothing detected? */
+ if (!(hdcap->requested_timings_present || hdcap->detected_timings_present))
+ return -ENODATA;
+
+ if (hdcap->requested_timings_present)
+ *timings = hdcap->requested_timings;
+ else
+ *timings = hdcap->detected_timings;
+ return 0;
+}
+
+static int usb3hdcap_s_dv_timings(
+ struct file *file,
+ void *priv,
+ struct v4l2_dv_timings *timings)
+{
+ struct usb3hdcap *hdcap = video_drvdata(file);
+
+ if (!(hdcap->input == INPUT_COMPONENT || hdcap->input == INPUT_HDMI))
+ return -ENODATA;
+
+ hdcap->requested_timings = *timings;
+ hdcap->requested_timings_present = 1;
+ return 0;
+}
+
+static int usb3hdcap_enum_dv_timings(
+ struct file *file,
+ void *priv,
+ struct v4l2_enum_dv_timings *timings)
+{
+ struct usb3hdcap *hdcap = video_drvdata(file);
+
+ if (timings->index >= ARRAY_SIZE(all_supported_dv_timings))
+ return -EINVAL;
+ if (!(hdcap->input == INPUT_COMPONENT || hdcap->input == INPUT_HDMI))
+ return -ENODATA;
+
+ timings->timings = all_supported_dv_timings[timings->index];
+ return 0;
+}
+
+static int usb3hdcap_dv_timings_cap(
+ struct file *file,
+ void *priv,
+ struct v4l2_dv_timings_cap *cap)
+{
+ struct usb3hdcap *hdcap = video_drvdata(file);
+
+ /* if you're not on HDMI/component, DV timings are not supported */
+ if (!(hdcap->input == INPUT_COMPONENT || hdcap->input == INPUT_HDMI))
+ return -ENODATA;
+
+ *cap = usb3hdcap_timings_cap;
+ return 0;
+}
+
+static int usb3hdcap_query_dv_timings(
+ struct file *file,
+ void *priv,
+ struct v4l2_dv_timings *timings)
+{
+ struct usb3hdcap *hdcap = video_drvdata(file);
+
+ if (!hdcap->detected_timings_present)
+ return -ENODATA;
+
+ *timings = hdcap->detected_timings;
+ return 0;
+}
+
+static void usb3hdcap_activate_ctrls(struct usb3hdcap *hdcap,
+ enum usb3hdcap_input input)
+{
+ bool is_tw9900 = input == INPUT_COMPOSITE || input == INPUT_SVIDEO;
+ bool is_mst3367 = input == INPUT_COMPONENT || input == INPUT_HDMI;
+
+ v4l2_ctrl_activate(hdcap->ctrl_brightness, is_tw9900);
+ v4l2_ctrl_activate(hdcap->ctrl_contrast, is_tw9900);
+ v4l2_ctrl_activate(hdcap->ctrl_saturation, is_tw9900);
+ v4l2_ctrl_activate(hdcap->ctrl_hue, is_tw9900);
+ v4l2_ctrl_activate(hdcap->ctrl_sharpness, is_tw9900);
+
+ v4l2_ctrl_activate(hdcap->ctrl_csc, is_mst3367);
+}
+
+static int usb3hdcap_g_input(struct file *file, void *priv, unsigned int *i)
+{
+ struct usb3hdcap *hdcap = video_drvdata(file);
+
+ *i = hdcap->input;
+ return 0;
+}
+
+static int usb3hdcap_s_input(struct file *file, void *priv, unsigned int i)
+{
+ struct usb3hdcap *hdcap = video_drvdata(file);
+ enum usb3hdcap_input old_input = hdcap->input;
+ int ret;
+
+ if (i > INPUT_HDMI)
+ return -EINVAL;
+ if (vb2_is_busy(&hdcap->vb2q))
+ return -EBUSY;
+
+ hdcap->input = i;
+
+ usb3hdcap_activate_ctrls(hdcap, i);
+
+ ret = usb3hdcap_hw_init(hdcap);
+ if (ret < 0) {
+ hdcap->input = old_input;
+ hdcap->hw_inited = 0;
+ return ret;
+ }
+
+ hdcap->bpl = hdcap->width * 2;
+ hdcap->hw_inited = 1;
+
+ if (i == INPUT_COMPOSITE || i == INPUT_SVIDEO)
+ hdcap->video_dev.tvnorms = USB3HDCAP_V4L2_STDS;
+ else
+ hdcap->video_dev.tvnorms = 0;
+
+ return 0;
+}
+
+static int usb3hdcap_enum_framesizes(
+ struct file *file,
+ void *priv,
+ struct v4l2_frmsizeenum *fsize)
+{
+ struct usb3hdcap *hdcap = video_drvdata(file);
+
+ if (fsize->index > 0)
+ return -EINVAL;
+ if (fsize->pixel_format != V4L2_PIX_FMT_YUYV)
+ return -EINVAL;
+
+ fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+ fsize->discrete.width = hdcap->width;
+ fsize->discrete.height = hdcap->height;
+ return 0;
+}
+
+static void fill_timeperframe(
+ struct usb3hdcap *hdcap,
+ struct v4l2_fract *tf)
+{
+ /*
+ * using the frame rate here even for interlaced, spec isn't super
+ * clear on what to do, but adv7180.c does it this way
+ */
+ if (hdcap->detected_timings.type) {
+ const struct v4l2_bt_timings *bt = &hdcap->detected_timings.bt;
+ u32 htotal = V4L2_DV_BT_FRAME_WIDTH(bt);
+ u32 vtotal = V4L2_DV_BT_FRAME_HEIGHT(bt);
+
+ tf->numerator = htotal * vtotal;
+ tf->denominator = (u32) bt->pixelclock;
+ v4l2_simplify_fraction(&tf->numerator, &tf->denominator, 8, 333);
+ } else if (hdcap->std & V4L2_STD_625_50) {
+ /* PAL: 25fps */
+ tf->numerator = 1;
+ tf->denominator = 25;
+ } else {
+ /* NTSC: 29.97fps */
+ tf->numerator = 1001;
+ tf->denominator = 30000;
+ }
+}
+
+static int usb3hdcap_g_parm(
+ struct file *file,
+ void *priv,
+ struct v4l2_streamparm *sp)
+{
+ struct usb3hdcap *hdcap = video_drvdata(file);
+
+ if (sp->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ sp->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
+ fill_timeperframe(hdcap, &sp->parm.capture.timeperframe);
+ sp->parm.capture.readbuffers = 4;
+ return 0;
+}
+
+static int usb3hdcap_s_parm(
+ struct file *file,
+ void *priv,
+ struct v4l2_streamparm *sp)
+{
+ struct usb3hdcap *hdcap = video_drvdata(file);
+
+ if (sp->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ /* fixed framerate - just report what we have */
+ sp->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
+ fill_timeperframe(hdcap, &sp->parm.capture.timeperframe);
+ sp->parm.capture.readbuffers = 4;
+ return 0;
+}
+
+static int usb3hdcap_enum_frameintervals(
+ struct file *file,
+ void *priv,
+ struct v4l2_frmivalenum *fival)
+{
+ struct usb3hdcap *hdcap = video_drvdata(file);
+
+ if (fival->index > 0)
+ return -EINVAL;
+ if (fival->pixel_format != V4L2_PIX_FMT_YUYV)
+ return -EINVAL;
+ if (fival->width != hdcap->width || fival->height != hdcap->height)
+ return -EINVAL;
+
+ fival->type = V4L2_FRMIVAL_TYPE_DISCRETE;
+ fill_timeperframe(hdcap, &fival->discrete);
+ return 0;
+}
+
+static int usb3hdcap_log_status(struct file *file, void *priv)
+{
+ struct usb3hdcap *hdcap = video_drvdata(file);
+
+ dev_info(hdcap->dev,
+ "status: iso_cbs=%u iso_bytes=%lu markers=%u frames=%u ",
+ hdcap->iso_cb_count, hdcap->iso_bytes,
+ hdcap->markers_found, hdcap->frames_delivered);
+ dev_info(hdcap->dev,
+ "parse_len=%d frame_line=%d synced=%d was_blanking=%d ",
+ hdcap->parse_len, hdcap->frame_line,
+ hdcap->synced, hdcap->was_blanking);
+ dev_info(hdcap->dev, "cur_buf=%p\n", hdcap->cur_buf);
+ return 0;
+}
+
+static int usb3hdcap_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct usb3hdcap *hdcap = container_of(ctrl->handler,
+ struct usb3hdcap, ctrl);
+
+ switch (ctrl->id) {
+ case V4L2_CID_BRIGHTNESS:
+ return u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_BRIGHT, (u8)ctrl->val);
+ case V4L2_CID_CONTRAST:
+ return u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_CONTRAST, ctrl->val);
+ case V4L2_CID_SATURATION:
+ u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_SAT_U, ctrl->val);
+ return u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_SAT_V, ctrl->val);
+ case V4L2_CID_HUE:
+ return u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_HUE, (u8)ctrl->val);
+ case V4L2_CID_SHARPNESS:
+ return u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_SHARPNESS, ctrl->val);
+ case V4L2_CID_USB3HDCAP_CSC:
+ mst3367_write_csc(hdcap);
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int usb3hdcap_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
+{
+ switch (ctrl->id) {
+ case V4L2_CID_DV_RX_POWER_PRESENT:
+ ctrl->val = 1;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct v4l2_ctrl_ops usb3hdcap_ctrl_ops = {
+ .s_ctrl = usb3hdcap_s_ctrl,
+ .g_volatile_ctrl = usb3hdcap_g_volatile_ctrl,
+};
+
+static const struct v4l2_ctrl_config usb3hdcap_csc_ctrl = {
+ .ops = &usb3hdcap_ctrl_ops,
+ .id = V4L2_CID_USB3HDCAP_CSC,
+ .name = "Color Space Conversion",
+ .type = V4L2_CTRL_TYPE_MENU,
+ .min = 0,
+ .max = CSC_YCBCR_HDMI,
+ .def = CSC_DEFAULT_HDMI,
+ .qmenu = csc_menu,
+};
+
+static int usb3hdcap_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);
+ default:
+ return v4l2_ctrl_subscribe_event(fh, sub);
+ }
+}
+
+static const struct v4l2_ioctl_ops usb3hdcap_ioctl_ops = {
+ .vidioc_querycap = usb3hdcap_querycap,
+ .vidioc_enum_input = usb3hdcap_enum_input,
+ .vidioc_enum_fmt_vid_cap = usb3hdcap_enum_fmt_vid_cap,
+ .vidioc_g_fmt_vid_cap = usb3hdcap_fmt_vid_cap,
+ .vidioc_try_fmt_vid_cap = usb3hdcap_fmt_vid_cap,
+ .vidioc_s_fmt_vid_cap = usb3hdcap_fmt_vid_cap,
+ .vidioc_g_std = usb3hdcap_g_std,
+ .vidioc_s_std = usb3hdcap_s_std,
+ .vidioc_g_dv_timings = usb3hdcap_g_dv_timings,
+ .vidioc_s_dv_timings = usb3hdcap_s_dv_timings,
+ .vidioc_enum_dv_timings = usb3hdcap_enum_dv_timings,
+ .vidioc_dv_timings_cap = usb3hdcap_dv_timings_cap,
+ .vidioc_query_dv_timings = usb3hdcap_query_dv_timings,
+ .vidioc_g_input = usb3hdcap_g_input,
+ .vidioc_s_input = usb3hdcap_s_input,
+ .vidioc_g_parm = usb3hdcap_g_parm,
+ .vidioc_s_parm = usb3hdcap_s_parm,
+ .vidioc_enum_framesizes = usb3hdcap_enum_framesizes,
+ .vidioc_enum_frameintervals = usb3hdcap_enum_frameintervals,
+ .vidioc_log_status = usb3hdcap_log_status,
+
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+
+ .vidioc_subscribe_event = usb3hdcap_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static const struct v4l2_file_operations usb3hdcap_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = vb2_fop_mmap,
+ .open = v4l2_fh_open,
+ .release = vb2_fop_release,
+ .read = vb2_fop_read,
+ .poll = vb2_fop_poll,
+};
+
+static void usb3hdcap_release(struct v4l2_device *v4l2_dev)
+{
+ struct usb3hdcap *hdcap = container_of(v4l2_dev,
+ struct usb3hdcap, v4l2_dev);
+
+ v4l2_device_unregister(&hdcap->v4l2_dev);
+ v4l2_ctrl_handler_free(&hdcap->ctrl);
+ kfree(hdcap);
+}
+
+static int video_init(struct usb3hdcap *hdcap)
+{
+ int ret;
+
+ mutex_init(&hdcap->v4l2_lock);
+ mutex_init(&hdcap->vb2q_lock);
+ spin_lock_init(&hdcap->buflock);
+ INIT_LIST_HEAD(&hdcap->bufs);
+
+ hdcap->vb2q.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ hdcap->vb2q.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
+ hdcap->vb2q.drv_priv = hdcap;
+ hdcap->vb2q.buf_struct_size = sizeof(struct hdcap_buf);
+ hdcap->vb2q.ops = &usb3hdcap_vb2_ops;
+ hdcap->vb2q.mem_ops = &vb2_vmalloc_memops;
+ hdcap->vb2q.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ hdcap->vb2q.min_queued_buffers = 2;
+ hdcap->vb2q.lock = &hdcap->vb2q_lock;
+ ret = vb2_queue_init(&hdcap->vb2q);
+ if (ret < 0) {
+ dev_warn(hdcap->dev, "Could not initialize videobuf2 queue\n");
+ return ret;
+ }
+
+ v4l2_ctrl_handler_init(&hdcap->ctrl, 7);
+ hdcap->ctrl_brightness = v4l2_ctrl_new_std(&hdcap->ctrl,
+ &usb3hdcap_ctrl_ops, V4L2_CID_BRIGHTNESS, -128, 127, 1, -9);
+ hdcap->ctrl_contrast = v4l2_ctrl_new_std(&hdcap->ctrl,
+ &usb3hdcap_ctrl_ops, V4L2_CID_CONTRAST, 0, 255, 1, 0x79);
+ hdcap->ctrl_saturation = v4l2_ctrl_new_std(&hdcap->ctrl,
+ &usb3hdcap_ctrl_ops, V4L2_CID_SATURATION, 0, 255, 1, 0x80);
+ hdcap->ctrl_hue = v4l2_ctrl_new_std(&hdcap->ctrl,
+ &usb3hdcap_ctrl_ops, V4L2_CID_HUE, -128, 127, 1, 0);
+ hdcap->ctrl_sharpness = v4l2_ctrl_new_std(&hdcap->ctrl,
+ &usb3hdcap_ctrl_ops, V4L2_CID_SHARPNESS, 0, 255, 1, 0x52);
+ hdcap->ctrl_rx_power = v4l2_ctrl_new_std(&hdcap->ctrl,
+ &usb3hdcap_ctrl_ops, V4L2_CID_DV_RX_POWER_PRESENT, 0, 1, 0, 1);
+ if (hdcap->ctrl_rx_power)
+ hdcap->ctrl_rx_power->flags |= V4L2_CTRL_FLAG_VOLATILE |
+ V4L2_CTRL_FLAG_READ_ONLY;
+ hdcap->ctrl_csc = v4l2_ctrl_new_custom(&hdcap->ctrl,
+ &usb3hdcap_csc_ctrl, NULL);
+ ret = hdcap->ctrl.error;
+ if (ret < 0) {
+ dev_warn(hdcap->dev, "Could not initialize controls\n");
+ goto ctrl_fail;
+ }
+
+ hdcap->v4l2_dev.ctrl_handler = &hdcap->ctrl;
+ hdcap->v4l2_dev.release = usb3hdcap_release;
+ ret = v4l2_device_register(hdcap->dev, &hdcap->v4l2_dev);
+ if (ret < 0) {
+ dev_warn(hdcap->dev, "Could not register v4l2 device\n");
+ goto v4l2_fail;
+ }
+
+ strscpy(hdcap->video_dev.name, "usb3hdcap", sizeof(hdcap->video_dev.name));
+ hdcap->video_dev.v4l2_dev = &hdcap->v4l2_dev;
+ hdcap->video_dev.release = video_device_release_empty;
+ hdcap->video_dev.fops = &usb3hdcap_fops;
+ hdcap->video_dev.ioctl_ops = &usb3hdcap_ioctl_ops;
+ hdcap->video_dev.tvnorms = USB3HDCAP_V4L2_STDS;
+ hdcap->video_dev.queue = &hdcap->vb2q;
+ hdcap->video_dev.lock = &hdcap->v4l2_lock;
+ hdcap->video_dev.device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE |
+ V4L2_CAP_STREAMING;
+ video_set_drvdata(&hdcap->video_dev, hdcap);
+ ret = video_register_device(&hdcap->video_dev, VFL_TYPE_VIDEO, -1);
+ if (ret < 0) {
+ dev_warn(hdcap->dev, "Could not register video device\n");
+ goto vdev_fail;
+ }
+
+ dev_info(hdcap->dev, "%s: registered as %s\n",
+ __func__, video_device_node_name(&hdcap->video_dev));
+
+ return 0;
+
+vdev_fail:
+ v4l2_device_unregister(&hdcap->v4l2_dev);
+
+v4l2_fail:
+ v4l2_ctrl_handler_free(&hdcap->ctrl);
+
+ctrl_fail:
+ return ret;
+}
+
+static int usb3hdcap_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct device *dev;
+ struct usb3hdcap *hdcap;
+ int ret;
+
+ /* not using the interrupt interface, just data */
+ if (intf->cur_altsetting->desc.bInterfaceNumber != 0)
+ return -ENODEV;
+
+ /* none, Mult=0 for SD, Mult=1 for HD */
+ if (intf->num_altsetting != 3)
+ return -ENODEV;
+
+ dev = &intf->dev;
+
+ hdcap = kzalloc_obj(*hdcap, GFP_KERNEL);
+ if (!hdcap)
+ return -ENOMEM;
+
+ hdcap->dev = dev;
+ hdcap->usb_dev = usb_get_dev(interface_to_usbdev(intf));
+ hdcap->input = INPUT_COMPOSITE;
+ hdcap->std = 0;
+ hdcap->width = SD_WIDTH;
+ hdcap->height = NTSC_HEIGHT / 2;
+ hdcap->bpl = DEFAULT_BPL;
+
+ usb_set_intfdata(intf, hdcap);
+
+ ret = video_init(hdcap);
+ if (ret < 0)
+ goto video_fail;
+
+ ret = usb3hdcap_audio_init(hdcap);
+ if (ret < 0)
+ dev_warn(dev, "audio init failed: %d (continuing without audio)\n", ret);
+
+ /*
+ * Take an extra ref so that disconnect's v4l2_device_put doesn't
+ * immediately trigger usb3hdcap_release before cleanup is done - based on
+ * usbtv driver
+ */
+ v4l2_device_get(&hdcap->v4l2_dev);
+
+ dev_info(dev, "%s USB 3.0 HD Video Capture Device",
+ le16_to_cpu(hdcap->usb_dev->descriptor.idProduct) == XCAPTURE1_PID ?
+ "Micomsoft XCAPTURE-1" : "StarTech USB3HDCAP");
+
+ return 0;
+
+video_fail:
+ usb_set_intfdata(intf, NULL);
+ usb_put_dev(hdcap->usb_dev);
+ kfree(hdcap);
+ return ret;
+}
+
+static void usb3hdcap_disconnect(struct usb_interface *intf)
+{
+ struct usb3hdcap *hdcap = usb_get_intfdata(intf);
+
+ usb_set_intfdata(intf, NULL);
+
+ if (!hdcap)
+ return;
+
+ usb3hdcap_audio_free(hdcap);
+ vb2_video_unregister_device(&hdcap->video_dev);
+ v4l2_device_disconnect(&hdcap->v4l2_dev);
+
+ usb_put_dev(hdcap->usb_dev);
+ hdcap->usb_dev = NULL;
+
+ /* hdcap is freed by usb3hdcap_release when last user closes fd */
+ v4l2_device_put(&hdcap->v4l2_dev);
+}
+
+static const struct usb_device_id usb3hdcap_id_table[] = {
+ { USB_DEVICE(USB3HDCAP_VID, USB3HDCAP_PID) },
+ { USB_DEVICE(USB3HDCAP_VID, XCAPTURE1_PID) },
+ { },
+};
+MODULE_DEVICE_TABLE(usb, usb3hdcap_id_table);
+
+MODULE_AUTHOR("Matthew Laux <matthew.laux@gmail.com>");
+MODULE_DESCRIPTION("StarTech USB3HDCAP Driver");
+MODULE_LICENSE("GPL");
+
+static struct usb_driver usb3hdcap_usb_driver = {
+ .name = "usb3hdcap",
+ .id_table = usb3hdcap_id_table,
+ .probe = usb3hdcap_probe,
+ .disconnect = usb3hdcap_disconnect,
+};
+module_usb_driver(usb3hdcap_usb_driver);
diff --git a/drivers/staging/media/usb3hdcap/usb3hdcap-hdmi.c b/drivers/staging/media/usb3hdcap/usb3hdcap-hdmi.c
new file mode 100644
index 000000000000..0ade4de227b9
--- /dev/null
+++ b/drivers/staging/media/usb3hdcap/usb3hdcap-hdmi.c
@@ -0,0 +1,808 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * USB3HDCAP MST3367 support: HDMI and component
+ */
+
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/usb.h>
+#include <linux/v4l2-dv-timings.h>
+#include <media/v4l2-ctrls.h>
+
+#include "usb3hdcap.h"
+
+/* ------------------------------------------------------------------ */
+/* Component scaler mode table */
+/* ------------------------------------------------------------------ */
+
+struct component_mode {
+ int vtotal_min, vtotal_max;
+ int width, height;
+ int refresh_rate;
+ int interlaced;
+ /* MST3367 bank 0 ADC/scaler registers (ref: SetADParameters) */
+ u8 pllgain, clpdly, clpdur, hsopw;
+ u8 adc_bw0, adc_bw1;
+ u16 scaler_htotal;
+ u16 hde_off;
+ u8 vde_off;
+ struct v4l2_dv_timings timings;
+};
+
+/*
+ * Values from Windows driver table $140303810
+ * TODO: add 50Hz variants
+ *
+ * The hardware supports 240p over component (one of the great things about
+ * this card), but the Windows driver doesn't attempt to distinguish. I
+ * won't either
+ */
+static const struct component_mode component_modes[] = {
+ /* pll clp clp hsop adc adc */
+ /* gain dly dur w bw0 bw1 sclht hde vde */
+ /* 480i */
+ { 250, 275, 720, 240, 60, 1, 0x08, 0x08, 0x08, 0x18, 0x44, 0x04, 0x35a, 0x76, 0x12,
+ V4L2_DV_BT_CEA_720X480I59_94 },
+ /* 576i */
+ { 300, 320, 720, 288, 50, 1, 0x08, 0x08, 0x08, 0x18, 0x44, 0x04, 0x360, 0x83, 0x16,
+ V4L2_DV_BT_CEA_720X576I50 },
+ /* 480p */
+ { 515, 535, 720, 480, 60, 0, 0x20, 0x08, 0x08, 0x18, 0x33, 0x03, 0x35a, 0x79, 0x24,
+ V4L2_DV_BT_CEA_720X480P59_94 },
+ /* 1080i */
+ { 555, 585, 1920, 540, 60, 1, 0x58, 0x38, 0x20, 0x30, 0x11, 0x01, 0x898, 0xeb, 0x14,
+ V4L2_DV_BT_CEA_1920X1080I60 },
+ /* 576p */
+ { 615, 635, 720, 576, 50, 0, 0x18, 0x08, 0x08, 0x18, 0x33, 0x03, 0x360, 0x83, 0x2c,
+ V4L2_DV_BT_CEA_720X576P50 },
+ /* 720p */
+ { 740, 760, 1280, 720, 60, 0, 0x58, 0x38, 0x20, 0x30, 0x11, 0x01, 0x672, 0x12b, 0x19,
+ V4L2_DV_BT_CEA_1280X720P60 },
+ /* 1080p */
+ { 1115, 1135, 1920, 1080, 60, 0, 0xe8, 0x38, 0x30, 0xa0, 0x00, 0x00, 0x898, 0xc1, 0x29,
+ V4L2_DV_BT_CEA_1920X1080P60 },
+};
+
+/*
+ * Known CEA timing table - maps (htotal, vtotal, hactive) to full
+ * v4l2_dv_timings. HD htotal = standard * 1.5 (MST3367 counter rate?)
+ */
+struct hdmi_std {
+ int htotal_min, htotal_max;
+ int vtotal_min, vtotal_max;
+ int hactive;
+ struct v4l2_dv_timings timings;
+};
+
+static const struct hdmi_std hdmi_stds[] = {
+ /* htotal vtotal ha */
+ { 790, 810, 520, 530, 640, V4L2_DV_BT_CEA_640X480P59_94 },
+ { 848, 868, 520, 530, 720, V4L2_DV_BT_CEA_720X480P59_94 },
+ { 854, 874, 620, 630, 720, V4L2_DV_BT_CEA_720X576P50 },
+ { 2960, 2980, 745, 755, 1280, V4L2_DV_BT_CEA_1280X720P50 },
+ { 2465, 2485, 745, 755, 1280, V4L2_DV_BT_CEA_1280X720P60 },
+ /*
+ * not sure about this one. it matches the timings for 30, but my laptop
+ * claims to be outputting 60...
+ */
+ { 2740, 2760, 1120, 1130, 1920, V4L2_DV_BT_CEA_1920X1080P30 },
+ { 3290, 3310, 555, 570, 1920, V4L2_DV_BT_CEA_1920X1080I60 },
+ { 3950, 3970, 1120, 1130, 1920, V4L2_DV_BT_CEA_1920X1080P50 },
+ { 2190, 2210, 1120, 1130, 1920, V4L2_DV_BT_CEA_1920X1080P60 },
+ { 3290, 3310, 1120, 1130, 1920, V4L2_DV_BT_CEA_1920X1080P60 },
+};
+
+/* ------------------------------------------------------------------ */
+/* MST3367 bank select helper */
+/* ------------------------------------------------------------------ */
+
+static int mst_bank(struct usb3hdcap *hdcap, int bank)
+{
+ if (hdcap->mst_current_bank == bank)
+ return 0;
+ hdcap->mst_current_bank = bank;
+ return u3hc_i2c_write(hdcap, ADDR_MST3367, 0x00, bank);
+}
+
+/* ------------------------------------------------------------------ */
+/* MST3367 register configuration */
+/* ------------------------------------------------------------------ */
+
+static void mst3367_config(struct usb3hdcap *hdcap)
+{
+ /*
+ * Largely based on mst3367-drv.c by Steven Toth and Ghidra decompilation
+ * of the Windows x64 driver
+ */
+
+ mst_bank(hdcap, 0x00);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x51, 0x80);
+ /* RxTmdsHotPlug: all off */
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0xb7, 0xff, 0x02);
+ /* RxGeneralInit */
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x41, 0x6f);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0xb8, 0x00);
+ /* RxVideoInit */
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0xb0, 0x14);
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0xae, ~0x04, 0x04);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0xad, 0x05); /* low-pass filter */
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0xb1, 0xc0);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0xb2, 0x00);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0xb3, 0x00);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0xb4, 0x55);
+ /* RxAudioInit */
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0xb4, ~0x03, 0x00);
+
+ /* RxTmdsInit */
+ mst_bank(hdcap, 0x01);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x0f, 0x02);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x16, 0x30);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x17, 0x00);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x18, 0x00);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x19, 0x00);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1a, 0x50);
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x2a, ~0x07, 0x07);
+ /* RxHdcpInit */
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x24, 0x40);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x25, 0x00);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x30, 0x80);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x31, 0x00);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x32, 0x00);
+
+ mst_bank(hdcap, 0x02);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x08, 0x03);
+ /* RxAudioInit (cont'd) */
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x01, 0x61);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x02, 0xf5);
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x03, ~0x02, 0x02);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x04, 0x01);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x05, 0x00);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x06, 0x08);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1c, 0x1a);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1d, 0x00);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1e, 0x00);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1f, 0x00);
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x25, (u8)~0xa2, 0xa2);
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x07, ~0x04, 0x04);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x17, 0xc0);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x19, 0xff);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1a, 0xff);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1b, 0xfc);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x20, 0x00);
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x21, ~0x03, 0x00);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x22, 0x26);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x27, 0x00);
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x2e, (u8)~0xa1, 0xa1);
+
+ mst_bank(hdcap, 0x00);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0xab, 0x15); /* COLOR.RANGE */
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0xac, 0x15); /* COLOR.RANGE */
+
+ /* RxHdcpReset */
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0xb8, 0x10); /* HDCP.RESET */
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0xb8, 0x00);
+
+ /* RxHdmiReset */
+ mst_bank(hdcap, 0x02);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x07, 0xf4);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x07, 0x04);
+
+ /* RxSwitchSource: 0x81 = HDMI TMDS.A */
+ mst_bank(hdcap, 0x00);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x51, 0x81);
+ /* RxTmdsHotPlug: link on */
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0xb7, 0x00);
+
+ /* RxHdmiInit */
+ mst_bank(hdcap, 0x02);
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x01, 0x0f, 0x60);
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x04, 0xff, 0x01);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x06, 0x08);
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x09, 0xff, 0x20);
+
+ mst_bank(hdcap, 0x00);
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x54, 0xef, 0x00);
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0xac, 0xff, 0x80);
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0xce, 0xff, 0x80);
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0xcf, 0xfa, 0x02);
+
+ /*
+ * 8c28 is "CustomCompanyEndoCamProperty" (0 in my registry)
+ * 7674 is "CustomAnalogVideoInputBandwidthProperty" (1 on my machine)
+ * if (*(int *)(param_1 + 0x8c28) == 0) {
+ * uVar1 = *(uint *)(param_1 + 0x7674);
+ * local_18 = 0x3040006;
+ * uVar2 = (ulonglong)uVar1 / 6;
+ * local_14 = 0x107;
+ * bVar5 = read_mst(param_1,CONCAT71((int7)(uVar2 >> 8),0x80),0xd0);
+ * bVar4 = read_mst(param_1,0x80,0xcf);
+ * bVar3 = *(byte *)((longlong)&local_18 + (ulonglong)(uVar1
+ * + (int)uVar2 * -6));
+ * bVar4 = bVar3 << 7 | bVar4 & 0x7f;
+ * bVar5 = (bVar3 >> 1 ^ bVar5) & 3 ^ bVar5;
+ * }
+ *
+ * ... which has this effect:
+ */
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0xd0, 0xfc, 0x00);
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0xcf, 0x7f, 0x00);
+
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x00, 0x7f, 0x00);
+ mst_bank(hdcap, 0x00);
+}
+
+/* ------------------------------------------------------------------ */
+/* CPLD init + poll helper */
+/* ------------------------------------------------------------------ */
+
+static void cpld_init(struct usb3hdcap *hdcap, u8 mux_val)
+{
+ int k, status;
+
+ /*
+ * cVar3 = i2c_read(param_1,0x98,0x3b,0);
+ * if (cVar3 != '\0') {
+ * uVar5 = *(uint *)(param_1 + 0x6840);
+ * if ((uVar5 == 2) || (uVar5 == 3)) {
+ * local_res8[0] = -0x80;
+ * i2c_write(param_1,0x98,0x3b,local_res8,1);
+ * uVar5 = *(uint *)(param_1 + 0x6840);
+ * }
+ * if (uVar5 < 2) {
+ * local_res8[0] = '\x02';
+ * i2c_write(param_1,0x98,0x3b,local_res8,1);
+ * }
+ * }
+ */
+
+ u3hc_i2c_write(hdcap, ADDR_CPLD, 0x3b, mux_val);
+
+ /*
+ * local_res8[0] = '\x05';
+ * i2c_write(param_1,0x98,0x20,local_res8,1);
+ * cVar3 = FUN_140226fc4(param_1);
+ * local_res8[0] = '\x01';
+ * i2c_write(param_1,0x98,0,local_res8,1);
+ * local_res8[0] = (-(cVar3 != '\0') & 0xfeU) - 2;
+ * i2c_write(param_1,0x98,0x10,local_res8,1);
+ */
+ u3hc_i2c_write(hdcap, ADDR_CPLD, 0x20, 0x05);
+ u3hc_i2c_write(hdcap, ADDR_CPLD, 0x00, 0x01);
+
+ u3hc_i2c_write(hdcap, ADDR_CPLD, 0x10, hdcap->has_mcu ? 0xfc : 0xfe);
+
+ for (k = 0; k < 50; k++) {
+ status = u3hc_i2c_read(hdcap, ADDR_CPLD, 0x01);
+ if (status >= 0x03)
+ break;
+ msleep(20);
+ }
+
+ u3hc_i2c_write(hdcap, ADDR_CPLD, 0x01, 0x02);
+ u3hc_i2c_write(hdcap, ADDR_CPLD, 0x11, 0xfc);
+}
+
+/* Identity CSC for component YPbPr */
+static const u8 csc_identity_component[] = {
+ 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, /* M11, M12, M13 */
+ 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, /* M21, M22, M23 */
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, /* M31, M32, M33 */
+ 0x20, 0x00, 0x03, 0x80, 0x20, 0x00, /* A1, A2, A3 */
+};
+
+/* Identity CSC for HDMI YCbCr */
+static const u8 csc_identity_hdmi[] = {
+ 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
+ 0x20, 0x00, 0x04, 0x00, 0x20, 0x00,
+};
+
+/* BT.601 for SD (width <= 720) */
+static const u8 csc_r2y_sd[] = {
+ 0x08, 0x02, 0x04, 0xc5, 0xfd, 0x4d,
+ 0xf9, 0x4a, 0x09, 0x5b, 0xfa, 0xb1,
+ 0xfe, 0xb4, 0x01, 0xd2, 0x08, 0x02,
+ 0x20, 0x00, 0x00, 0x00, 0x20, 0x00,
+};
+
+/* BT.709 for HD (width > 720) */
+static const u8 csc_r2y_hd[] = {
+ 0x08, 0x02, 0x03, 0x65, 0xfe, 0x28,
+ 0xf8, 0xb9, 0x0b, 0x65, 0xf9, 0xd6,
+ 0xff, 0x45, 0x01, 0x27, 0x08, 0x02,
+ 0x20, 0x00, 0x00, 0x00, 0x20, 0x00,
+};
+
+void mst3367_write_csc(struct usb3hdcap *hdcap)
+{
+ bool comp = (hdcap->input == INPUT_COMPONENT);
+ const u8 *csc;
+ u8 old_ab, reg_92;
+ int k;
+
+ if (comp) {
+ reg_92 = 0x66;
+ csc = csc_identity_component;
+ } else {
+ /*
+ * Auto-detect HDMI input colorspace:
+ *
+ * RxHdmiPacketStatus (0x02:0x0B/0x0C/0x0E): bit 3 indicates
+ * an AVI InfoFrame has been received?
+ * RxHdmiPacketColor (0x02:0x48 bits 6:5):
+ * 0x00 = RGB, 0x20 = YUV422, 0x40 = YUV444
+ */
+ int pkt_status, color = 0x00;
+
+ mst_bank(hdcap, 0x02);
+ pkt_status = (u3hc_i2c_read(hdcap, ADDR_MST3367, 0x0c) & 0x3f) << 8 |
+ (u3hc_i2c_read(hdcap, ADDR_MST3367, 0x0b) & 0xff);
+ if (u3hc_i2c_read(hdcap, ADDR_MST3367, 0x0e) & 0x08)
+ pkt_status |= 0x8000;
+
+ if (pkt_status & 0x0008)
+ color = u3hc_i2c_read(hdcap, ADDR_MST3367, 0x48) & 0x60;
+
+ dev_info(hdcap->dev,
+ "CSC: pkt_status=0x%04x color=0x%02x\n",
+ pkt_status, color);
+
+ if (color == 0x00) {
+ /* RGB: need R2Y conversion for YUV422 output */
+ reg_92 = 0x40;
+ csc = (hdcap->width <= 720) ? csc_r2y_sd : csc_r2y_hd;
+ dev_info(hdcap->dev, "CSC: HDMI RGB, %s R2Y\n",
+ (hdcap->width <= 720) ? "BT.601" : "BT.709");
+ } else {
+ /* YCbCr input: identity passthrough, pre-offset
+ * subtracts 128 from Cb/Cr before matrix
+ */
+ reg_92 = 0x62;
+ csc = csc_identity_hdmi;
+ dev_info(hdcap->dev, "CSC: HDMI YCbCr (0x%02x), identity\n",
+ color);
+ }
+ }
+
+ mst_bank(hdcap, 0x00);
+
+ /* BLANK.OUTPUT while changing CSC */
+ u3hc_i2c_rmw_get_old(hdcap, ADDR_MST3367, 0xab, 0x7f, 0x80, &old_ab);
+
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x90, 0x15); /* COLOR.RANGE */
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x91, 0x15); /* COLOR.RANGE */
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x92, reg_92);
+
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0xac, ~0x3f, 0x15); /* COLOR.RANGE */
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0xad, 0x05); /* sharpness: low-pass filter */
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1e, 0x11); /* sharpness */
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1f, 0x01); /* sharpness */
+
+ for (k = 0; k < 24; k++)
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x93 + k, csc[k]);
+
+ /* RX_OUTPUT_YUV422 / 08.BITS / EMBEDDED SYNC */
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0xb0, 0xc2, 0x21);
+ /* NORMAL.OUTPUT (un-blank) */
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0xab, old_ab & 0x7f);
+
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0xb3, 0x00);
+
+ mst_bank(hdcap, 0x02);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x27, 0x00);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x20, 0x00);
+}
+
+/* ------------------------------------------------------------------ */
+/* Signal detection polling */
+/* ------------------------------------------------------------------ */
+
+static const struct v4l2_dv_timings *match_hdmi_timing(
+ int htotal,
+ int vtotal,
+ int hactive)
+{
+ int k;
+
+ for (k = 0; k < ARRAY_SIZE(hdmi_stds); k++) {
+ const struct hdmi_std *s = &hdmi_stds[k];
+
+ if (htotal < s->htotal_min || htotal > s->htotal_max)
+ continue;
+ if (vtotal < s->vtotal_min || vtotal > s->vtotal_max)
+ continue;
+ if (hactive != s->hactive)
+ continue;
+ return &s->timings;
+ }
+ return NULL;
+}
+
+static int hdmi_poll_signal(struct usb3hdcap *hdcap)
+{
+ int k, status;
+
+ vendor_out(hdcap, REQ_STREAM, 0x0000, 0, NULL, 0);
+
+ for (k = 0; k < 100; k++) {
+ mst_bank(hdcap, 0x00);
+ status = u3hc_i2c_read(hdcap, ADDR_MST3367, 0x55);
+ if (status < 0) {
+ msleep(100);
+ continue;
+ }
+
+ if (k % 10 == 0)
+ dev_info(hdcap->dev,
+ "HDMI signal poll[%d]: lock=0x%02x\n",
+ k, status);
+
+ if (status & 0x3c) {
+ const struct v4l2_dv_timings *std;
+ int htotal, vtotal, hactive;
+
+ htotal = (u3hc_i2c_read(hdcap, ADDR_MST3367, 0x6a) << 8 |
+ u3hc_i2c_read(hdcap, ADDR_MST3367, 0x6b)) & 0xfff;
+ vtotal = (u3hc_i2c_read(hdcap, ADDR_MST3367, 0x5b) << 8 |
+ u3hc_i2c_read(hdcap, ADDR_MST3367, 0x5c)) & 0x7ff;
+
+ mst_bank(hdcap, 0x02);
+ hactive = (u3hc_i2c_read(hdcap, ADDR_MST3367, 0x29) << 8 |
+ u3hc_i2c_read(hdcap, ADDR_MST3367, 0x28)) & 0x1fff;
+
+ dev_info(hdcap->dev,
+ "HDMI locked: htotal=%d vtotal=%d hactive=%d\n",
+ htotal, vtotal, hactive);
+
+ std = match_hdmi_timing(htotal, vtotal, hactive);
+ if (!std) {
+ dev_err(hdcap->dev,
+ "unsupported HDMI timing: htotal=%d vtotal=%d hactive=%d\n",
+ htotal, vtotal, hactive);
+ return -ERANGE;
+ }
+ hdcap->detected_timings = *std;
+ hdcap->detected_timings_present = 1;
+ hdcap->std = 0; /* no SD standard */
+ hdcap->width = std->bt.width;
+ hdcap->interlaced = std->bt.interlaced;
+ hdcap->height = std->bt.interlaced
+ ? std->bt.height / 2 : std->bt.height;
+ hdcap->bpl = hdcap->width * 2;
+
+ /* DISABLE AUTO POSITION */
+ mst_bank(hdcap, 0x00);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0xe2, 0x00);
+
+ return 0;
+ }
+
+ /* Not locked yet, ENABLE AUTO POSITION */
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0xe2, 0x80);
+ msleep(100);
+ }
+
+ dev_err(hdcap->dev, "No HDMI signal lock after 10s\n");
+ return -ETIMEDOUT;
+}
+
+/* ------------------------------------------------------------------ */
+/* ADC front-end config (component/VGA only) */
+/* ------------------------------------------------------------------ */
+
+static void mst3367_adc_config(struct usb3hdcap *hdcap)
+{
+ /* RxAdcInit */
+ mst_bank(hdcap, 0x00);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x00, 0x80);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x24, 0xc0);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x00, 0x00);
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x21, ~0x07, 0x01);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x04, 0x00); /* ADC phase */
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x10, 0x0f, 0xd0);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x11, 0x2d);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x20, 0xc0);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x21, 0x04);
+ /* bits 5:4 = sync source: 0x30 = SOG for component */
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x0e, 0xcf, 0x30);
+}
+
+static const struct component_mode *match_component_mode(int vtotal)
+{
+ int k;
+
+ for (k = 0; k < ARRAY_SIZE(component_modes); k++) {
+ const struct component_mode *m = &component_modes[k];
+
+ if (vtotal < m->vtotal_min || vtotal > m->vtotal_max)
+ continue;
+ return m;
+ }
+ return NULL;
+}
+
+/* ------------------------------------------------------------------ */
+/* Component scaler register write */
+/* ------------------------------------------------------------------ */
+
+static void component_write_scaler(
+ struct usb3hdcap *hdcap,
+ const struct component_mode *m)
+{
+ int ht = m->scaler_htotal - 1;
+ int is_720_low_rr = m->width == 1280 && m->height == 720
+ && (m->refresh_rate == 24
+ || m->refresh_rate == 25
+ || m->refresh_rate == 30);
+
+ mst_bank(hdcap, 0x00);
+
+ /*
+ * edge case for low frame rate 720p:
+ * if (((*(int *)(uVar30 + 0x30) == 0x500) && (*(int *)(uVar30 + 0x34) == 0x2d0)) &&
+ * ((*(int *)(uVar30 + 0x38) == 0x1e ||
+ * ((*(int *)(uVar30 + 0x38) == 0x19 || (*(int *)(uVar30 + 0x38) == 0x18)))))) {
+ * uVar37 = 0;
+ * } else {
+ * uVar37 = 4;
+ * }
+ * send_mst(param_1,0,0x12,uVar37);
+ */
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x12, is_720_low_rr ? 0x00 : 0x04);
+
+ /* SetADParameters */
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x03, m->pllgain);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x05, m->clpdly);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x06, m->clpdur);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x07, m->hsopw);
+
+ /* ADC gain: Y, Cb, Cr */
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x08, 0xa0);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x09, 0xc0);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x0a, 0xa0);
+
+ /* grade scale (clamp level): Y, Cb, Cr */
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x0b, 0x80);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x0c, 0x60);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x0d, 0x80);
+
+ /* clamp offset: Y, Cb, Cr */
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x18, 0x10);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x19, 0x10);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1a, 0x10);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1b, 0x00);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1c, 0x00);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1d, 0x00);
+
+ /* ADCSetAutoDetectFormat: output htotal */
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x01, ht >> 4);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x02, (ht & 0x0f) << 4);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1e, m->adc_bw0);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1f, m->adc_bw1);
+
+ /* output data enable start pos after sync */
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x80, (m->hde_off >> 8) & 0x0f);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x81, m->hde_off & 0xff);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x82, (m->width >> 8) & 0xff);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x83, m->width & 0xff);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x84, m->vde_off);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x85,
+ ((m->height >> 8) & 0x0f) | (m->interlaced ? 0x20 : 0x00));
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x86, m->height & 0xff);
+
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x2d, 0x11);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x2e, 0x11);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x39, (u8) (m->clpdly + m->clpdur - 0x78));
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x2c, 0x9d);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x3a, 0x0c);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x3b, 0x08);
+
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x10, 0xfa, 0x05);
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x0f, 0xff, 0x20);
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x17, 0xff, 0x02);
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x2f, 0xff, 0x02);
+ u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x60, 0xfb, 0x00);
+}
+
+/* ------------------------------------------------------------------ */
+/* Component ADC signal polling */
+/* ------------------------------------------------------------------ */
+
+static int component_poll_signal(
+ struct usb3hdcap *hdcap,
+ const struct component_mode **detected)
+{
+ const struct component_mode *mode;
+ int k, status, vtotal, htotal;
+ int signal_count = 0;
+
+ vendor_out(hdcap, REQ_STREAM, 0x0000, 0, NULL, 0);
+
+ /*
+ * RxAdcModeDetect()
+ * Signal detection per decompiled Windows driver:
+ * 0x14 & 0x90 == 0x90: H+V locked (status=1)
+ * 0x14 & 0x90 == 0x80: H lock only (status=2)
+ * 0x14 == 0x00 && 0x15 & 0x40: status=3
+ */
+ for (k = 0; k < 100; k++) {
+ int signal_type;
+
+ mst_bank(hdcap, 0x00);
+
+ /* trigger timing measurement? */
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x0e, 0x40);
+
+ status = u3hc_i2c_read(hdcap, ADDR_MST3367, 0x14);
+ u3hc_i2c_read(hdcap, ADDR_MST3367, 0x15);
+
+ /* Check signal presence from 0x14 bits 7,4 */
+ signal_type = status & 0x90;
+ if (signal_type == 0x90 || signal_type == 0x80 ||
+ (signal_type == 0x00 && (status & 0x02)))
+ signal_count++;
+ else
+ signal_count = 0;
+
+ /* Read H period */
+ htotal = ((u3hc_i2c_read(hdcap, ADDR_MST3367, 0x57) & 0x3f) << 8) |
+ u3hc_i2c_read(hdcap, ADDR_MST3367, 0x58);
+
+ /* scale ADC sample rate to H period */
+ if (htotal > 0)
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x11,
+ (htotal < 0x401 ? 0x400 : htotal) >> 7);
+
+ /* V period (timer counts) */
+ u3hc_i2c_read(hdcap, ADDR_MST3367, 0x59);
+ u3hc_i2c_read(hdcap, ADDR_MST3367, 0x5a);
+
+ /* V total (lines) */
+ u3hc_i2c_read(hdcap, ADDR_MST3367, 0x5b);
+ u3hc_i2c_read(hdcap, ADDR_MST3367, 0x5c);
+
+ /* H total (pixels), interlace flag */
+ u3hc_i2c_read(hdcap, ADDR_MST3367, 0x5d);
+ u3hc_i2c_read(hdcap, ADDR_MST3367, 0x5e);
+ u3hc_i2c_read(hdcap, ADDR_MST3367, 0x5f);
+
+ mst_bank(hdcap, 0x02);
+ u3hc_i2c_read(hdcap, ADDR_MST3367, 0x11);
+ u3hc_i2c_read(hdcap, ADDR_MST3367, 0x12);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x20, 0x00);
+
+ if (k % 10 == 0)
+ dev_info(hdcap->dev,
+ "component poll[%d]: 0x14=0x%02x count=%d\n",
+ k, status, signal_count);
+
+ if (signal_count < 3) {
+ msleep(100);
+ continue;
+ }
+
+ /* Signal stable - read vtotal and determine format */
+ mst_bank(hdcap, 0x00);
+ vtotal = ((u3hc_i2c_read(hdcap, ADDR_MST3367, 0x5b) & 0x07) << 8) |
+ u3hc_i2c_read(hdcap, ADDR_MST3367, 0x5c);
+
+ dev_info(hdcap->dev,
+ "component locked: vtotal=%d htotal=%d 0x14=0x%02x\n",
+ vtotal, htotal, status);
+
+ mode = match_component_mode(vtotal);
+ if (!mode) {
+ dev_err(hdcap->dev,
+ "unsupported component timing: vtotal=%d\n",
+ vtotal);
+ return -ERANGE;
+ }
+ hdcap->width = mode->width;
+ hdcap->height = mode->height;
+ hdcap->interlaced = mode->interlaced;
+ hdcap->bpl = hdcap->width * 2;
+ hdcap->std = 0; /* no SD standard */
+ hdcap->detected_timings_present = 1;
+ hdcap->detected_timings = mode->timings;
+ *detected = mode;
+ return 0;
+ }
+
+ dev_err(hdcap->dev, "No component signal lock after 10s\n");
+ return -ETIMEDOUT;
+}
+
+/* ------------------------------------------------------------------ */
+/* Main HDMI init entry point */
+/* ------------------------------------------------------------------ */
+
+int usb3hdcap_hdmi_init(struct usb3hdcap *hdcap)
+{
+ int ret;
+
+ hdcap->mst_current_bank = -1;
+
+ mst3367_config(hdcap);
+
+ /* TW9900 power-down */
+ u3hc_i2c_write(hdcap, ADDR_TW9900, 0x06, 0x0e);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, 0x1a, 0x40);
+
+ /* CPLD init with HDMI input mux */
+ cpld_init(hdcap, 0x02);
+
+ u3hc_i2c_read(hdcap, ADDR_CPLD, 0x20);
+ u3hc_i2c_write(hdcap, ADDR_CPLD, 0x20, 0x05);
+
+ /* probably not needed */
+ u3hc_i2c_read(hdcap, ADDR_CPLD, 0x20);
+ u3hc_i2c_write(hdcap, ADDR_CPLD, 0x20, 0x05);
+
+ ret = hdmi_poll_signal(hdcap);
+ if (ret < 0)
+ return ret;
+
+ v4l2_ctrl_handler_setup(&hdcap->ctrl);
+
+ dev_info(hdcap->dev, "hdmi_init: complete (%dx%d)\n",
+ hdcap->width, hdcap->height);
+ return 0;
+}
+
+/* ------------------------------------------------------------------ */
+/* Main component init entry point */
+/* ------------------------------------------------------------------ */
+
+int usb3hdcap_component_init(struct usb3hdcap *hdcap)
+{
+ const struct component_mode *mode;
+ int ret, use_height;
+
+ hdcap->mst_current_bank = -1;
+
+ mst3367_config(hdcap);
+
+ /* TW9900 power-down */
+ u3hc_i2c_write(hdcap, ADDR_TW9900, 0x06, 0x0e);
+ u3hc_i2c_write(hdcap, ADDR_TW9900, 0x1a, 0x40);
+
+ /* CPLD init with component input mux */
+ cpld_init(hdcap, 0x80);
+
+ /* RxSwitchSource: 0x21 = component ADC (YPbPr) */
+ mst_bank(hdcap, 0x00);
+ u3hc_i2c_write(hdcap, ADDR_MST3367, 0x51, 0x21);
+
+ /* Component CPLD routing, bit 4 = component input? */
+ u3hc_i2c_read(hdcap, ADDR_CPLD, 0x20);
+ u3hc_i2c_write(hdcap, ADDR_CPLD, 0x20, 0x15);
+
+ mst3367_adc_config(hdcap);
+
+ /*
+ * Wait for ADC to stabilize, windows driver does this but might be able
+ * to decrease wait
+ */
+ msleep(1000);
+
+ /* Final CPLD verify */
+ u3hc_i2c_read(hdcap, ADDR_CPLD, 0x20);
+ u3hc_i2c_write(hdcap, ADDR_CPLD, 0x20, 0x15);
+
+ ret = component_poll_signal(hdcap, &mode);
+ if (ret < 0)
+ return ret;
+
+ u3hc_i2c_rmw(hdcap, ADDR_CPLD, 0x00, 0xff, 0x02);
+
+ component_write_scaler(hdcap, mode);
+
+ v4l2_ctrl_handler_setup(&hdcap->ctrl);
+
+ use_height = hdcap->interlaced ? hdcap->height * 2 : hdcap->height;
+ dev_info(hdcap->dev, "component_init: complete (%dx%d%s)\n",
+ hdcap->width, use_height,
+ hdcap->interlaced ? "i" : "p");
+ return 0;
+}
diff --git a/drivers/staging/media/usb3hdcap/usb3hdcap-video.c b/drivers/staging/media/usb3hdcap/usb3hdcap-video.c
new file mode 100644
index 000000000000..b9b36c59f09e
--- /dev/null
+++ b/drivers/staging/media/usb3hdcap/usb3hdcap-video.c
@@ -0,0 +1,511 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * USB3HDCAP video streaming - stream parser, ISO URB handling, vb2 ops
+ */
+
+#include <linux/kernel.h>
+#include <linux/version.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/vmalloc.h>
+#include <media/v4l2-ctrls.h>
+#include <media/videobuf2-v4l2.h>
+
+#include "usb3hdcap.h"
+
+/* ------------------------------------------------------------------ */
+/* Stream parser */
+/* ------------------------------------------------------------------ */
+
+static void vyuy_to_yuyv(u8 *dst, const u8 *src, int len)
+{
+ int k;
+
+ /* VYUY: V Y0 U Y1 -> YUYV: Y0 U Y1 V */
+ for (k = 0; k < len; k += 4) {
+ dst[k + 0] = src[k + 1]; /* Y0 */
+ dst[k + 1] = src[k + 2]; /* U */
+ dst[k + 2] = src[k + 3]; /* Y1 */
+ dst[k + 3] = src[k + 0]; /* V */
+ }
+}
+
+static struct hdcap_buf *get_next_buf(struct usb3hdcap *hdcap)
+{
+ struct hdcap_buf *buf = NULL;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hdcap->buflock, flags);
+ if (!list_empty(&hdcap->bufs)) {
+ buf = list_first_entry(&hdcap->bufs, struct hdcap_buf, list);
+ list_del(&buf->list);
+ }
+ spin_unlock_irqrestore(&hdcap->buflock, flags);
+
+ return buf;
+}
+
+static void deliver_frame(struct usb3hdcap *hdcap)
+{
+ struct hdcap_buf *buf = hdcap->cur_buf;
+ u8 *vaddr;
+ int k;
+
+ if (!buf)
+ return;
+
+ hdcap->frames_delivered++;
+
+ /* Pad remaining lines with black */
+ vaddr = vb2_plane_vaddr(&buf->vb.vb2_buf, 0);
+ while (hdcap->frame_line < hdcap->height) {
+ u8 *row = vaddr + hdcap->frame_line * hdcap->bpl;
+
+ for (k = 0; k < hdcap->bpl; k += 4) {
+ row[k + 0] = 0x10;
+ row[k + 1] = 0x80;
+ row[k + 2] = 0x10;
+ row[k + 3] = 0x80;
+ }
+ hdcap->frame_line++;
+ }
+
+ vb2_set_plane_payload(&buf->vb.vb2_buf, 0,
+ hdcap->bpl * hdcap->height);
+ buf->vb.vb2_buf.timestamp = ktime_get_ns();
+ if (hdcap->interlaced) {
+ buf->vb.field = hdcap->is_second_field ? V4L2_FIELD_BOTTOM : V4L2_FIELD_TOP;
+ buf->vb.sequence = hdcap->sequence;
+ if (hdcap->is_second_field)
+ hdcap->sequence++;
+ hdcap->is_second_field ^= 1;
+ } else {
+ buf->vb.field = V4L2_FIELD_NONE;
+ buf->vb.sequence = hdcap->sequence++;
+ }
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
+
+ hdcap->cur_buf = NULL;
+}
+
+/*
+ * Process one video line (shared between composite and HDMI).
+ * 'video' points to the XY byte followed by pixel data.
+ */
+static void process_video_line(struct usb3hdcap *hdcap, const u8 *video)
+{
+ u8 sav_xy = video[0];
+ int blanking = sav_xy & BT656_V_BIT;
+
+ /* Blanking -> active transition */
+ if (!blanking && hdcap->was_blanking) {
+ if (!hdcap->synced)
+ hdcap->synced = 1;
+ if (hdcap->cur_buf && hdcap->frame_line >= 100)
+ deliver_frame(hdcap);
+ hdcap->frame_line = 0;
+ if (!hdcap->cur_buf)
+ hdcap->cur_buf = get_next_buf(hdcap);
+ }
+ hdcap->was_blanking = blanking;
+
+ if (!hdcap->synced || !hdcap->cur_buf || blanking)
+ return;
+
+ /* Deliver when field is full */
+ if (hdcap->frame_line >= hdcap->height) {
+ deliver_frame(hdcap);
+ hdcap->frame_line = 0;
+ hdcap->cur_buf = get_next_buf(hdcap);
+ if (!hdcap->cur_buf)
+ return;
+ }
+
+ vyuy_to_yuyv(
+ vb2_plane_vaddr(&hdcap->cur_buf->vb.vb2_buf, 0)
+ + hdcap->frame_line * hdcap->bpl,
+ video, hdcap->bpl);
+ hdcap->frame_line++;
+}
+
+static void process_data(struct usb3hdcap *hdcap)
+{
+ u8 *buf = hdcap->parse_buf;
+ int line_size = SAV_LEN + hdcap->bpl;
+ int pos = 0;
+
+ while (pos + 3 <= hdcap->parse_len) {
+ if (buf[pos] != 0xff || buf[pos + 1] != 0x00) {
+ pos++;
+ continue;
+ }
+
+ if (buf[pos + 2] == 0x00) {
+ /* SAV: video line */
+ if (pos + line_size > hdcap->parse_len)
+ break;
+ /*
+ * -1 because on this hardware, XY byte occupies the first pixel's
+ * V position
+ */
+ process_video_line(hdcap, buf + pos + SAV_LEN - 1);
+ pos += line_size;
+ } else if (buf[pos + 2] == 0xff) {
+ u8 type;
+ int alen;
+
+ if (pos + MARKER_LEN > hdcap->parse_len)
+ break;
+ type = buf[pos + 3];
+ if (type < 0x02 || type > 0x04) {
+ pos++;
+ continue;
+ }
+ alen = type * 4;
+ if (pos + MARKER_LEN + alen > hdcap->parse_len)
+ break;
+ hdcap->markers_found++;
+ usb3hdcap_audio_data(hdcap,
+ buf + pos + MARKER_LEN, alen);
+ pos += MARKER_LEN + alen;
+ } else {
+ pos++;
+ }
+ }
+
+ /* Shift unconsumed data to front */
+ if (pos > 0) {
+ hdcap->parse_len -= pos;
+ if (hdcap->parse_len > 0)
+ memmove(buf, buf + pos, hdcap->parse_len);
+ }
+}
+
+/* ------------------------------------------------------------------ */
+/* Isochronous URB handling */
+/* ------------------------------------------------------------------ */
+
+static void usb3hdcap_iso_cb(struct urb *urb)
+{
+ struct usb3hdcap *hdcap = urb->context;
+ int k, ret;
+
+ switch (urb->status) {
+ case 0:
+ break;
+ case -ENODEV:
+ case -ENOENT:
+ case -ECONNRESET:
+ case -ESHUTDOWN:
+ return;
+ default:
+ dev_warn(hdcap->dev, "ISO URB status %d\n", urb->status);
+ goto resubmit;
+ }
+
+ hdcap->iso_cb_count++;
+
+ for (k = 0; k < urb->number_of_packets; k++) {
+ int status = urb->iso_frame_desc[k].status;
+ int len = urb->iso_frame_desc[k].actual_length;
+ u8 *data = urb->transfer_buffer +
+ urb->iso_frame_desc[k].offset;
+
+ if (status != 0 || len == 0)
+ continue;
+
+ if (hdcap->parse_len + len > PARSE_BUF_SIZE) {
+ hdcap->parse_len = 0;
+ continue;
+ }
+
+ memcpy(hdcap->parse_buf + hdcap->parse_len, data, len);
+ hdcap->parse_len += len;
+ hdcap->iso_bytes += len;
+ }
+
+ process_data(hdcap);
+
+resubmit:
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret < 0)
+ dev_warn(hdcap->dev, "ISO URB resubmit: %d\n", ret);
+}
+
+static struct urb *usb3hdcap_alloc_urb(struct usb3hdcap *hdcap)
+{
+ struct urb *urb;
+ int size = hdcap->iso_size;
+ int k;
+
+ urb = usb_alloc_urb(NUM_ISO_PKTS, GFP_KERNEL);
+ if (!urb)
+ return NULL;
+
+ urb->dev = hdcap->usb_dev;
+ urb->context = hdcap;
+ urb->pipe = usb_rcvisocpipe(hdcap->usb_dev, EP_VIDEO);
+ urb->interval = 1;
+ urb->transfer_flags = URB_ISO_ASAP;
+ urb->transfer_buffer = kcalloc(NUM_ISO_PKTS, size, GFP_KERNEL);
+ if (!urb->transfer_buffer) {
+ usb_free_urb(urb);
+ return NULL;
+ }
+ urb->complete = usb3hdcap_iso_cb;
+ urb->number_of_packets = NUM_ISO_PKTS;
+ urb->transfer_buffer_length = size * NUM_ISO_PKTS;
+ for (k = 0; k < NUM_ISO_PKTS; k++) {
+ urb->iso_frame_desc[k].offset = size * k;
+ urb->iso_frame_desc[k].length = size;
+ }
+
+ return urb;
+}
+
+static void usb3hdcap_stop(struct usb3hdcap *hdcap)
+{
+ int k;
+ unsigned long flags;
+
+ for (k = 0; k < NUM_XFERS; k++) {
+ struct urb *urb = hdcap->isoc_urbs[k];
+
+ if (!urb)
+ continue;
+ usb_kill_urb(urb);
+ kfree(urb->transfer_buffer);
+ usb_free_urb(urb);
+ hdcap->isoc_urbs[k] = NULL;
+ }
+
+ /* already NULL if this stop came from a disconnect */
+ if (hdcap->usb_dev) {
+ vendor_out(hdcap, REQ_STREAM, 0x0000, 0, NULL, 0);
+ usb_set_interface(hdcap->usb_dev, 0, 0);
+ }
+
+ /* Return any in-progress buffer */
+ if (hdcap->cur_buf) {
+ vb2_buffer_done(&hdcap->cur_buf->vb.vb2_buf,
+ VB2_BUF_STATE_ERROR);
+ hdcap->cur_buf = NULL;
+ }
+
+ spin_lock_irqsave(&hdcap->buflock, flags);
+ while (!list_empty(&hdcap->bufs)) {
+ struct hdcap_buf *buf = list_first_entry(&hdcap->bufs,
+ struct hdcap_buf, list);
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ list_del(&buf->list);
+ }
+ spin_unlock_irqrestore(&hdcap->buflock, flags);
+
+ vfree(hdcap->parse_buf);
+ hdcap->parse_buf = NULL;
+}
+
+static int usb3hdcap_start(struct usb3hdcap *hdcap)
+{
+ struct usb_host_endpoint *ep;
+ int maxp, burst, mult;
+ int k, ret, alt;
+
+ if (!hdcap->hw_inited) {
+ ret = usb3hdcap_hw_init(hdcap);
+ if (ret < 0) {
+ dev_err(hdcap->dev, "open: hw_init failed: %d\n", ret);
+ return ret;
+ }
+ hdcap->hw_inited = 1;
+ }
+
+ hdcap->parse_buf = vzalloc(PARSE_BUF_SIZE);
+ if (!hdcap->parse_buf)
+ return -ENOMEM;
+ hdcap->parse_len = 0;
+ hdcap->frame_line = 0;
+ hdcap->synced = 0;
+ hdcap->was_blanking = 0;
+ hdcap->is_second_field = 0;
+ hdcap->cur_buf = NULL;
+
+ if (hdcap->input == INPUT_HDMI) {
+ /*
+ * this is a register watchlist, interrupt generated n endpoint 1 when
+ * (value & mask) changes.
+ *
+ * struct reg_watch {
+ * uint8_t reg;
+ * // 0x80-0x82: MST3367 bank n&0x7f, 0x40: look at I2C addr
+ * uint8_t bank_or_source;
+ * uint8_t value_mask;
+ * uint8_t i2c_addr; // for bank_or_source == 0x40
+ * };
+ */
+ u8 stream_payload[] = {
+ 0x55, 0x80, 0x3c, 0x00, 0x6a, 0x80, 0x0f, 0x00,
+ 0x6b, 0x80, 0xfe, 0x00, 0x57, 0x80, 0x3f, 0x00,
+ 0x58, 0x80, 0x80, 0x00, 0x59, 0x80, 0x3f, 0x00,
+ 0x5a, 0x80, 0xf0, 0x00, 0x5b, 0x80, 0x07, 0x00,
+ 0x5c, 0x80, 0xfe, 0x00, 0x5f, 0x80, 0x02, 0x00,
+ 0x01, 0x81, 0x07, 0x00, 0x29, 0x82, 0x1f, 0x00,
+ 0x28, 0x82, 0xff, 0x00, 0x0b, 0x82, 0xdf, 0x00,
+ 0x0c, 0x82, 0x3f, 0x00, 0x0e, 0x82, 0x08, 0x00,
+ 0x48, 0x82, 0x60, 0x00, 0x9b, 0x82, 0xf0, 0x00,
+ 0x11, 0x82, 0xff, 0x00, 0x12, 0x82, 0xff, 0x00,
+ 0x11, 0x82, 0xff, 0x00, 0x12, 0x82, 0xff, 0x00,
+ };
+ ret = vendor_out(hdcap, REQ_STREAM, 0x0032, 1,
+ stream_payload, sizeof(stream_payload));
+ } else if (hdcap->input == INPUT_COMPONENT) {
+ /* Component: monitor MST3367 ADC registers */
+ u8 stream_payload[] = {
+ 0x14, 0x80, 0x90, 0x00, 0x15, 0x80, 0x46, 0x00,
+ 0x5b, 0x80, 0x07, 0x00, 0x5c, 0x80, 0xf8, 0x00,
+ 0x57, 0x80, 0x3f, 0x00, 0x5f, 0x80, 0x12, 0x00,
+ 0x11, 0x82, 0xff, 0x00, 0x12, 0x82, 0xff, 0x00,
+ };
+ ret = vendor_out(hdcap, REQ_STREAM, 0x0032, 1,
+ stream_payload, sizeof(stream_payload));
+ } else {
+ u8 stream_payload[] = {
+ 0x01, 0x40, 0xe9, 0x88,
+ 0x1c, 0x40, 0xf0, 0x88,
+ 0x30, 0x40, 0x0f, 0x88,
+ };
+ v4l2_ctrl_handler_setup(&hdcap->ctrl);
+ ret = vendor_out(hdcap, REQ_STREAM, 0x0032, 1,
+ stream_payload, sizeof(stream_payload));
+ }
+
+ if (ret < 0) {
+ vfree(hdcap->parse_buf);
+ hdcap->parse_buf = NULL;
+ return ret;
+ }
+
+ /*
+ * Select alt setting based on bandwidth:
+ * alt 1: maxp=1024 burst=16 mult=1 -> 16KB/pkt (SD)
+ * alt 2: maxp=1024 burst=16 mult=2 -> 32KB/pkt (HD)
+ */
+ alt = (hdcap->width > 720 || hdcap->height > 576) ? 2 : 1;
+ dev_info(hdcap->dev, "%s: setting alt %d\n", __func__, alt);
+ ret = usb_set_interface(hdcap->usb_dev, 0, alt);
+
+ if (ret < 0) {
+ dev_err(hdcap->dev, "%s: usb_set_interface failed: %d\n",
+ __func__, ret);
+ vfree(hdcap->parse_buf);
+ hdcap->parse_buf = NULL;
+ return ret;
+ }
+
+ /* Compute ISO packet size from SS endpoint companion descriptor */
+ ep = usb_pipe_endpoint(hdcap->usb_dev,
+ usb_rcvisocpipe(hdcap->usb_dev, EP_VIDEO));
+ if (!ep) {
+ dev_err(hdcap->dev, "ISO endpoint 0x%02x not found\n",
+ EP_VIDEO);
+ return -ENODEV;
+ }
+
+ maxp = usb_endpoint_maxp(&ep->desc);
+ burst = ep->ss_ep_comp.bMaxBurst;
+ mult = USB_SS_MULT(ep->ss_ep_comp.bmAttributes);
+ hdcap->iso_size = maxp * (burst + 1) * mult;
+
+ for (k = 0; k < NUM_XFERS; k++) {
+ struct urb *urb = usb3hdcap_alloc_urb(hdcap);
+
+ if (!urb) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+ hdcap->isoc_urbs[k] = urb;
+
+ ret = usb_submit_urb(urb, GFP_KERNEL);
+ if (ret < 0)
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ usb3hdcap_stop(hdcap);
+ return ret;
+}
+
+/* ------------------------------------------------------------------ */
+/* vb2 ops */
+/* ------------------------------------------------------------------ */
+
+static int usb3hdcap_queue_setup(
+ struct vb2_queue *vq,
+ unsigned int *nbuffers,
+ unsigned int *nplanes,
+ unsigned int sizes[],
+ struct device *alloc_devs[])
+{
+ struct usb3hdcap *hdcap = vb2_get_drv_priv(vq);
+ unsigned int img_size = hdcap->bpl * hdcap->height;
+
+ if (*nplanes) {
+ dev_info(hdcap->dev, "queue_setup: recheck sizes[0]=%u vs %u\n",
+ sizes[0], img_size);
+ return sizes[0] < img_size ? -EINVAL : 0;
+ }
+ *nplanes = 1;
+ sizes[0] = img_size;
+
+ return 0;
+}
+
+static void usb3hdcap_buf_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct usb3hdcap *hdcap = vb2_get_drv_priv(vb->vb2_queue);
+ struct hdcap_buf *buf = container_of(vbuf, struct hdcap_buf, vb);
+ unsigned long flags;
+
+ if (!hdcap->usb_dev) {
+ dev_warn(hdcap->dev, "buf_queue: usb_dev is NULL\n");
+ vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
+ return;
+ }
+
+ spin_lock_irqsave(&hdcap->buflock, flags);
+ list_add_tail(&buf->list, &hdcap->bufs);
+ spin_unlock_irqrestore(&hdcap->buflock, flags);
+}
+
+static int usb3hdcap_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+ struct usb3hdcap *hdcap = vb2_get_drv_priv(vq);
+ int ret;
+
+ if (!hdcap->usb_dev) {
+ dev_err(hdcap->dev, "start_streaming: usb_dev is NULL\n");
+ return -ENODEV;
+ }
+
+ hdcap->sequence = 0;
+ ret = usb3hdcap_start(hdcap);
+ return ret;
+}
+
+static void usb3hdcap_stop_streaming(struct vb2_queue *vq)
+{
+ struct usb3hdcap *hdcap = vb2_get_drv_priv(vq);
+
+ if (hdcap->usb_dev)
+ usb3hdcap_stop(hdcap);
+}
+
+const struct vb2_ops usb3hdcap_vb2_ops = {
+ .queue_setup = usb3hdcap_queue_setup,
+ .buf_queue = usb3hdcap_buf_queue,
+ .start_streaming = usb3hdcap_start_streaming,
+ .stop_streaming = usb3hdcap_stop_streaming,
+};
diff --git a/drivers/staging/media/usb3hdcap/usb3hdcap.h b/drivers/staging/media/usb3hdcap/usb3hdcap.h
new file mode 100644
index 000000000000..435b4d6fab1f
--- /dev/null
+++ b/drivers/staging/media/usb3hdcap/usb3hdcap.h
@@ -0,0 +1,239 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef USB3HDCAP_H
+#define USB3HDCAP_H
+
+#include <linux/usb.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/videobuf2-core.h>
+#include <media/videobuf2-v4l2.h>
+
+struct snd_card;
+struct snd_pcm_substream;
+
+#define USB3HDCAP_V4L2_STDS (V4L2_STD_NTSC | V4L2_STD_PAL)
+
+/* NTSC SD frame format */
+#define SD_WIDTH 720
+#define NTSC_HEIGHT 480
+/* YUYV = 4 bytes/2 pixels */
+#define DEFAULT_BPL (SD_WIDTH * 2)
+/* PAL SD frame format */
+#define PAL_HEIGHT 576
+
+enum usb3hdcap_input {
+ INPUT_COMPOSITE = 0,
+ INPUT_SVIDEO = 1,
+ INPUT_COMPONENT = 2,
+ INPUT_HDMI = 3,
+};
+
+enum usb3hdcap_csc {
+ CSC_DEFAULT_COMPONENT = 0,
+ CSC_DEFAULT_HDMI = 1,
+ CSC_YCBCR_HDMI = 2,
+};
+
+/* I2C 8-bit addresses */
+#define ADDR_MST3367 0x9c
+#define ADDR_TW9900 0x88
+#define ADDR_CS53L21 0x94
+#define ADDR_CPLD 0x98
+#define ADDR_MCU 0x64 /* Nuvoton NUC100 on XCAPTURE-1 */
+
+/* TW9900 registers */
+#define TW9900_CSTATUS 0x01
+#define TW9900_INFORM 0x02
+#define TW9900_OPFORM 0x03
+#define TW9900_OPCNTL_I 0x05
+#define TW9900_ACNTL_I 0x06
+#define TW9900_CROP_HI 0x07
+#define TW9900_VDELAY_LO 0x08
+#define TW9900_VACTIVE_LO 0x09
+#define TW9900_HDELAY_LO 0x0a
+#define TW9900_HACTIVE_LO 0x0b
+#define TW9900_CNTRL2 0x0d
+#define TW9900_SDT_NOISE 0x0e
+#define TW9900_LUMA_CTL 0x0f
+#define TW9900_BRIGHT 0x10
+#define TW9900_CONTRAST 0x11
+#define TW9900_SHARPNESS 0x12
+#define TW9900_SAT_U 0x13
+#define TW9900_SAT_V 0x14
+#define TW9900_HUE 0x15
+#define TW9900_VBICNTL 0x19
+#define TW9900_ACNTL_II 0x1a
+#define TW9900_OPCNTL_II 0x1b
+#define TW9900_SDT 0x1c
+#define TW9900_SDTR 0x1d
+#define TW9900_VCNTL1 0x28
+#define TW9900_VCNTL2 0x29
+#define TW9900_MISC1 0x2d
+#define TW9900_MISC2 0x2f
+#define TW9900_CSTATUS_II 0x31
+#define TW9900_ANAPLLCTL 0x4c
+#define TW9900_VVBI 0x55
+#define TW9900_HSBEGIN 0x6b
+#define TW9900_HSEND 0x6c
+#define TW9900_OVSDLY 0x6d
+#define TW9900_OVSEND 0x6e
+
+/* TW9900 bit definitions */
+#define TW9900_NINTL 0x08
+#define TW9900_STDNOW_SHIFT 4
+#define TW9900_STDNOW_MASK 0x70
+#define TW9900_STD_NTSC_M 0
+#define TW9900_STD_PAL_BDGHI 1
+#define TW9900_STD_SECAM 2
+#define TW9900_STD_NTSC_4_43 3
+#define TW9900_STD_PAL_M 4
+#define TW9900_STD_PAL_CN 5
+#define TW9900_STD_PAL_60 6
+
+/* CS53L21 registers */
+#define CS53L21_MIC_PWR_CTL 0x03
+#define CS53L21_IFACE_CTL 0x04
+#define CS53L21_ADC_IN_SEL 0x07
+#define CS53L21_SPE_CTL 0x09
+#define CS53L21_ALC_PGAA 0x0a
+#define CS53L21_ALC_PGAB 0x0b
+#define CS53L21_ADCA_ATT 0x0c
+#define CS53L21_ADCB_ATT 0x0d
+#define CS53L21_ADCA_MIX_VOL 0x0e
+#define CS53L21_ADCB_MIX_VOL 0x0f
+#define CS53L21_CH_MIXER 0x18
+#define CS53L21_ALC_EN_ATK 0x1c
+
+/* Vendor request codes */
+#define REQ_I2C 0xc0
+#define REQ_GPIO 0xc1
+#define REQ_INIT 0xc2
+#define REQ_STREAM 0xc6
+#define REQ_UNK_C7 0xc7
+
+/* Isochronous transfer parameters */
+#define EP_VIDEO 0x83
+#define NUM_XFERS 8
+#define NUM_ISO_PKTS 64
+
+/*
+ * Stream framing: two element types in the byte stream, both
+ * starting with ff 00:
+ * SAV (ff 00 00 XY): bpl-1 pixel bytes, -1 because the XY is actually
+ * in the first pixel's V position... weird but it's correct
+ * Marker (ff 00 ff NN): audio - NN = number of stereo s16le
+ * samples, audio data = NN * 4 bytes
+ */
+#define SAV_LEN 4
+#define MARKER_LEN 4
+#define PARSE_BUF_SIZE (2 * 1024 * 1024)
+
+/* BT.656 SAV XY bits */
+#define BT656_F_BIT 0x40
+#define BT656_V_BIT 0x20
+
+struct hdcap_buf {
+ struct vb2_v4l2_buffer vb;
+ struct list_head list;
+};
+
+struct usb3hdcap {
+ struct device *dev;
+ struct usb_device *usb_dev;
+ struct v4l2_device v4l2_dev;
+ struct video_device video_dev;
+ struct vb2_queue vb2q;
+
+ struct v4l2_ctrl_handler ctrl;
+ struct v4l2_ctrl *ctrl_brightness;
+ struct v4l2_ctrl *ctrl_contrast;
+ struct v4l2_ctrl *ctrl_saturation;
+ struct v4l2_ctrl *ctrl_hue;
+ struct v4l2_ctrl *ctrl_sharpness;
+ struct v4l2_ctrl *ctrl_rx_power;
+
+ struct mutex v4l2_lock;
+ struct mutex vb2q_lock;
+
+ /* buffer list */
+ spinlock_t buflock;
+ struct list_head bufs;
+
+ /* ISO URBs */
+ int iso_size;
+ struct urb *isoc_urbs[NUM_XFERS];
+
+ /* frame tracking */
+ unsigned int sequence;
+
+ /* input selection + format */
+ enum usb3hdcap_input input;
+ v4l2_std_id std;
+ v4l2_std_id requested_std;
+ int width;
+ int height;
+ int interlaced;
+ int bpl; /* width * 2 */
+
+ /* stream parser state */
+ u8 *parse_buf;
+ int parse_len;
+ struct hdcap_buf *cur_buf;
+ int frame_line;
+ int synced;
+ int was_blanking;
+ int is_second_field;
+
+ /* hardware init state */
+ int hw_inited;
+ int has_mcu; /* Nuvoton MCU present (XCAPTURE-1) */
+
+ /* MST3367 state */
+ int mst_current_bank;
+ struct v4l2_ctrl *ctrl_csc;
+ struct v4l2_dv_timings detected_timings;
+ int detected_timings_present;
+ struct v4l2_dv_timings requested_timings;
+ int requested_timings_present;
+
+ /* ALSA audio */
+ struct snd_card *snd;
+ struct snd_pcm_substream *snd_substream;
+ atomic_t snd_stream;
+ size_t snd_buffer_pos;
+ size_t snd_period_pos;
+
+ /* diagnostics */
+ unsigned int iso_cb_count;
+ unsigned long iso_bytes;
+ unsigned int markers_found;
+ unsigned int frames_delivered;
+};
+
+/* USB control helpers (usb3hdcap-core.c) */
+int vendor_out(struct usb3hdcap *hdcap, u8 request, u16 value, u16 index,
+ u8 *data, u16 len);
+int u3hc_i2c_write(struct usb3hdcap *hdcap, u8 addr, u8 reg, u8 val);
+int u3hc_i2c_read(struct usb3hdcap *hdcap, u8 addr, u8 reg);
+int u3hc_i2c_rmw(struct usb3hdcap *hdcap, u8 addr, u8 reg, u8 mask, u8 bits);
+int u3hc_i2c_rmw_get_old(struct usb3hdcap *hdcap, u8 addr, u8 reg, u8 mask, u8 bits, u8 *old);
+int usb3hdcap_hw_init(struct usb3hdcap *hdcap);
+
+/* Composite (usb3hdcap-composite.c) */
+int usb3hdcap_composite_init(struct usb3hdcap *hdcap);
+
+/* HDMI + Component (usb3hdcap-hdmi.c) */
+int usb3hdcap_hdmi_init(struct usb3hdcap *hdcap);
+int usb3hdcap_component_init(struct usb3hdcap *hdcap);
+void mst3367_write_csc(struct usb3hdcap *hdcap);
+
+/* Video/streaming (usb3hdcap-video.c) */
+extern const struct vb2_ops usb3hdcap_vb2_ops;
+
+/* Audio (usb3hdcap-audio.c) */
+int usb3hdcap_cs53l21_init(struct usb3hdcap *hdcap);
+int usb3hdcap_audio_init(struct usb3hdcap *hdcap);
+void usb3hdcap_audio_free(struct usb3hdcap *hdcap);
+void usb3hdcap_audio_data(struct usb3hdcap *hdcap, const u8 *data, int len);
+
+#endif
--
2.47.3
^ permalink raw reply related [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-03-25 17:07 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-25 17:06 [PATCH 0/1] Add driver for StarTech USB3HDCAP capture device Matthew Laux
2026-03-25 17:06 ` [PATCH 1/1] staging: media: add driver for StarTech USB3HDCAP Matthew Laux
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox