2018-11-26 18:19:40

by Michael Grzeschik

[permalink] [raw]
Subject: [PATCH v2 0/2] media: Startech usb2hdcapm hdmi2usb framegrabber support

This series adds support for the Startech usb2hdcapm framegrabber. The
code is based on the external kernel module code from Steven Toth's
github page:

https://github.com/stoth68000/hdcapm/

We applied checkpatch.pl --strict and cleaned up the 80 character
length, whitespace issues and replaced simple printks with appropriate
v4l2_* or dev_* helpers, used WARN_ON instead of BUG and changed all
errors and warnings checkpatch was complaining about.

Steven Toth (2):
media: mst3367: add support for mstar mst3367 HDMI RX
media: hdcapm: add support for usb2hdcapm hdmi2usb framegrabber from
startech

drivers/media/i2c/Kconfig | 10 +
drivers/media/i2c/Makefile | 1 +
drivers/media/i2c/mst3367.c | 1104 ++++++++++++++++++
drivers/media/usb/Kconfig | 1 +
drivers/media/usb/Makefile | 1 +
drivers/media/usb/hdcapm/Kconfig | 11 +
drivers/media/usb/hdcapm/Makefile | 3 +
drivers/media/usb/hdcapm/hdcapm-buffer.c | 230 ++++
drivers/media/usb/hdcapm/hdcapm-compressor.c | 782 +++++++++++++
drivers/media/usb/hdcapm/hdcapm-core.c | 743 ++++++++++++
drivers/media/usb/hdcapm/hdcapm-i2c.c | 332 ++++++
drivers/media/usb/hdcapm/hdcapm-reg.h | 111 ++
drivers/media/usb/hdcapm/hdcapm-video.c | 665 +++++++++++
drivers/media/usb/hdcapm/hdcapm.h | 283 +++++
include/media/i2c/mst3367.h | 29 +
15 files changed, 4306 insertions(+)
create mode 100644 drivers/media/i2c/mst3367.c
create mode 100644 drivers/media/usb/hdcapm/Kconfig
create mode 100644 drivers/media/usb/hdcapm/Makefile
create mode 100644 drivers/media/usb/hdcapm/hdcapm-buffer.c
create mode 100644 drivers/media/usb/hdcapm/hdcapm-compressor.c
create mode 100644 drivers/media/usb/hdcapm/hdcapm-core.c
create mode 100644 drivers/media/usb/hdcapm/hdcapm-i2c.c
create mode 100644 drivers/media/usb/hdcapm/hdcapm-reg.h
create mode 100644 drivers/media/usb/hdcapm/hdcapm-video.c
create mode 100644 drivers/media/usb/hdcapm/hdcapm.h
create mode 100644 include/media/i2c/mst3367.h

--
2.19.1



2018-11-26 18:19:40

by Michael Grzeschik

[permalink] [raw]
Subject: [PATCH v2 2/2] media: hdcapm: add support for usb2hdcapm hdmi2usb framegrabber from startech

From: Steven Toth <[email protected]>

This patch is based on the work of Steven Toth. He reverse engineered
the driver by tracing the windows driver.

https://github.com/stoth68000/hdcapm/

Signed-off-by: Steven Toth <[email protected]>
Signed-off-by: Michael Grzeschik <[email protected]>
---
drivers/media/usb/Kconfig | 1 +
drivers/media/usb/Makefile | 1 +
drivers/media/usb/hdcapm/Kconfig | 11 +
drivers/media/usb/hdcapm/Makefile | 3 +
drivers/media/usb/hdcapm/hdcapm-buffer.c | 230 ++++++
drivers/media/usb/hdcapm/hdcapm-compressor.c | 782 +++++++++++++++++++
drivers/media/usb/hdcapm/hdcapm-core.c | 743 ++++++++++++++++++
drivers/media/usb/hdcapm/hdcapm-i2c.c | 332 ++++++++
drivers/media/usb/hdcapm/hdcapm-reg.h | 111 +++
drivers/media/usb/hdcapm/hdcapm-video.c | 665 ++++++++++++++++
drivers/media/usb/hdcapm/hdcapm.h | 283 +++++++
11 files changed, 3162 insertions(+)
create mode 100644 drivers/media/usb/hdcapm/Kconfig
create mode 100644 drivers/media/usb/hdcapm/Makefile
create mode 100644 drivers/media/usb/hdcapm/hdcapm-buffer.c
create mode 100644 drivers/media/usb/hdcapm/hdcapm-compressor.c
create mode 100644 drivers/media/usb/hdcapm/hdcapm-core.c
create mode 100644 drivers/media/usb/hdcapm/hdcapm-i2c.c
create mode 100644 drivers/media/usb/hdcapm/hdcapm-reg.h
create mode 100644 drivers/media/usb/hdcapm/hdcapm-video.c
create mode 100644 drivers/media/usb/hdcapm/hdcapm.h

diff --git a/drivers/media/usb/Kconfig b/drivers/media/usb/Kconfig
index b24e753c4766..5da5a849bad7 100644
--- a/drivers/media/usb/Kconfig
+++ b/drivers/media/usb/Kconfig
@@ -41,6 +41,7 @@ if I2C && MEDIA_DIGITAL_TV_SUPPORT
comment "Digital TV USB devices"
source "drivers/media/usb/dvb-usb/Kconfig"
source "drivers/media/usb/dvb-usb-v2/Kconfig"
+source "drivers/media/usb/hdcapm/Kconfig"
source "drivers/media/usb/ttusb-budget/Kconfig"
source "drivers/media/usb/ttusb-dec/Kconfig"
source "drivers/media/usb/siano/Kconfig"
diff --git a/drivers/media/usb/Makefile b/drivers/media/usb/Makefile
index 21e46b10caa5..a729842842fe 100644
--- a/drivers/media/usb/Makefile
+++ b/drivers/media/usb/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_USB_MSI2500) += msi2500/
obj-$(CONFIG_VIDEO_CPIA2) += cpia2/
obj-$(CONFIG_VIDEO_AU0828) += au0828/
obj-$(CONFIG_VIDEO_HDPVR) += hdpvr/
+obj-$(CONFIG_VIDEO_HDCAPM) += hdcapm/
obj-$(CONFIG_VIDEO_PVRUSB2) += pvrusb2/
obj-$(CONFIG_VIDEO_USBVISION) += usbvision/
obj-$(CONFIG_VIDEO_STK1160) += stk1160/
diff --git a/drivers/media/usb/hdcapm/Kconfig b/drivers/media/usb/hdcapm/Kconfig
new file mode 100644
index 000000000000..925e88abe68b
--- /dev/null
+++ b/drivers/media/usb/hdcapm/Kconfig
@@ -0,0 +1,11 @@
+
+config VIDEO_HDCAPM
+ tristate "Startech HDCAPM support"
+ depends on VIDEO_DEV && VIDEO_V4L2
+ select VIDEO_MST3367 if MEDIA_SUBDRV_AUTOSELECT
+ select I2C_ALGOBIT
+ help
+ This is a video4linux driver for Startech's HDCAPM USB device.
+
+ To compile this driver as a module, choose M here: the
+ module will be called hdpvr
diff --git a/drivers/media/usb/hdcapm/Makefile b/drivers/media/usb/hdcapm/Makefile
new file mode 100644
index 000000000000..a0ccdecbd8f7
--- /dev/null
+++ b/drivers/media/usb/hdcapm/Makefile
@@ -0,0 +1,3 @@
+hdcapm-objs := hdcapm-core.o hdcapm-video.o hdcapm-compressor.o hdcapm-buffer.o hdcapm-i2c.o
+
+obj-$(CONFIG_VIDEO_HDCAPM) += hdcapm.o
diff --git a/drivers/media/usb/hdcapm/hdcapm-buffer.c b/drivers/media/usb/hdcapm/hdcapm-buffer.c
new file mode 100644
index 000000000000..e61de3e29d91
--- /dev/null
+++ b/drivers/media/usb/hdcapm/hdcapm-buffer.c
@@ -0,0 +1,230 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for the Startech USB2HDCAPM USB capture device
+ *
+ * Copyright (c) 2017 Steven Toth <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *
+ * GNU General Public License for more details.
+ */
+
+#include "hdcapm.h"
+
+struct hdcapm_buffer *hdcapm_buffer_alloc(struct hdcapm_dev *dev,
+ u32 nr, u32 maxsize)
+{
+ struct hdcapm_buffer *buf;
+
+ buf = kzalloc(sizeof(*buf), GFP_KERNEL);
+ if (!buf)
+ return NULL;
+
+ buf->nr = nr;
+ buf->dev = dev;
+ buf->maxsize = maxsize;
+ buf->ptr = kzalloc(maxsize, GFP_KERNEL);
+ if (!buf->ptr) {
+ kfree(buf);
+ return NULL;
+ }
+
+ return buf;
+}
+
+void hdcapm_buffer_free(struct hdcapm_buffer *buf)
+{
+ kfree(buf->ptr);
+ buf->ptr = NULL;
+
+ usb_free_urb(buf->urb);
+ buf->urb = NULL;
+
+ kfree(buf);
+}
+
+/* Helper macro for moving all buffers to another list.
+ * We WILL take the mutex in this func.
+ */
+void hdcapm_buffers_move_all(struct hdcapm_dev *dev,
+ struct list_head *to, struct list_head *from)
+{
+ struct hdcapm_buffer *buf;
+
+ mutex_lock(&dev->dmaqueue_lock);
+ while (!list_empty(from)) {
+ buf = list_first_entry(from, struct hdcapm_buffer, list);
+ if (buf)
+ list_move_tail(&buf->list, to);
+ }
+ mutex_unlock(&dev->dmaqueue_lock);
+}
+
+/* Helper macro to free all buffers from a list.
+ * We WILL take the mutex in this func.
+ */
+void hdcapm_buffers_free_all(struct hdcapm_dev *dev, struct list_head *head)
+{
+ struct hdcapm_buffer *buf;
+
+ mutex_lock(&dev->dmaqueue_lock);
+ while (!list_empty(head)) {
+ buf = list_first_entry(head, struct hdcapm_buffer, list);
+ if (buf) {
+ list_del(&buf->list);
+ hdcapm_buffer_free(buf);
+ }
+ }
+ mutex_unlock(&dev->dmaqueue_lock);
+}
+
+/* Helper macros for managing the device lists.
+ * We WILL take the mutex in this func.
+ * Return a reference to the top most used buffer, we're going to
+ * read some or all of it (probably). Don't delete it from the list.
+ */
+struct hdcapm_buffer *hdcapm_buffer_peek_used(struct hdcapm_dev *dev)
+{
+ struct hdcapm_buffer *buf = NULL;
+
+ mutex_lock(&dev->dmaqueue_lock);
+ if (!list_empty(&dev->list_buf_used)) {
+ buf = list_first_entry(&dev->list_buf_used,
+ struct hdcapm_buffer, list);
+ }
+ mutex_unlock(&dev->dmaqueue_lock);
+
+ v4l2_dbg(3, hdcapm_debug, dev->sd, "%s() returns %p\n", __func__, buf);
+
+ return buf;
+}
+
+static struct hdcapm_buffer *hdcapm_buffer_next_used(struct hdcapm_dev *dev)
+{
+ struct hdcapm_buffer *buf = NULL;
+
+ mutex_lock(&dev->dmaqueue_lock);
+ if (!list_empty(&dev->list_buf_used)) {
+ buf = list_first_entry(&dev->list_buf_used,
+ struct hdcapm_buffer, list);
+ list_del(&buf->list);
+ }
+ mutex_unlock(&dev->dmaqueue_lock);
+
+ v4l2_dbg(3, hdcapm_debug, dev->sd, "%s() returns %p\n", __func__, buf);
+
+ return buf;
+}
+
+/* Pull the top buffer from the free list, but don't specifically remove
+ * it from the list. If no buffer exists, steal one from the used list.
+ * We WILL take the mutex in this func. Return the buffer at the top of
+ * the free list, delete the list node. We're probably going to fill it
+ * and move it to the used list. IF no free buffers exist, steal one
+ * from the used list and flag an internal data loss statistic.
+ */
+struct hdcapm_buffer *hdcapm_buffer_next_free(struct hdcapm_dev *dev)
+{
+ struct hdcapm_buffer *buf = NULL;
+
+ mutex_lock(&dev->dmaqueue_lock);
+ if (!list_empty(&dev->list_buf_free)) {
+ buf = list_first_entry(&dev->list_buf_free,
+ struct hdcapm_buffer, list);
+ list_del(&buf->list);
+ }
+ mutex_unlock(&dev->dmaqueue_lock);
+
+ if (!buf) {
+ v4l2_err(dev->sd,
+ "%s() No empty buffers. Increase buffer_count.\n",
+ __func__);
+ buf = hdcapm_buffer_next_used(dev);
+ if (!buf)
+ v4l2_err(dev->sd,
+ "%s() No free or empty buffers.\n", __func__);
+ dev->stats->buffer_overrun++;
+ }
+
+ v4l2_dbg(3, hdcapm_debug, dev->sd, "%s() returns %p\n", __func__, buf);
+
+ return buf;
+}
+
+static void hdcapm_buffer_add_to_list(struct hdcapm_dev *dev,
+ struct hdcapm_buffer *buf,
+ struct list_head *list)
+{
+ mutex_lock(&dev->dmaqueue_lock);
+ list_add_tail(&buf->list, list);
+ mutex_unlock(&dev->dmaqueue_lock);
+}
+
+inline void hdcapm_buffer_add_to_free(struct hdcapm_dev *dev,
+ struct hdcapm_buffer *buf)
+{
+ hdcapm_buffer_add_to_list(dev, buf, &dev->list_buf_free);
+}
+
+inline void hdcapm_buffer_add_to_used(struct hdcapm_dev *dev,
+ struct hdcapm_buffer *buf)
+{
+ hdcapm_buffer_add_to_list(dev, buf, &dev->list_buf_used);
+}
+
+static inline void hdcapm_buffer_move(struct hdcapm_dev *dev,
+ struct hdcapm_buffer *buf,
+ struct list_head *list)
+{
+ mutex_lock(&dev->dmaqueue_lock);
+ list_move_tail(&buf->list, list);
+ mutex_unlock(&dev->dmaqueue_lock);
+}
+
+/* Helper macros for moving a buffer to the free list.
+ * We WILL take the mutex in this func.
+ */
+void hdcapm_buffer_move_to_free(struct hdcapm_dev *dev,
+ struct hdcapm_buffer *buf)
+{
+ hdcapm_buffer_move(dev, buf, &dev->list_buf_free);
+}
+
+/* Helper macros for moving a buffer to the used list.
+ * We WILL take the mutex in this func.
+ */
+void hdcapm_buffer_move_to_used(struct hdcapm_dev *dev,
+ struct hdcapm_buffer *buf)
+{
+ hdcapm_buffer_move(dev, buf, &dev->list_buf_used);
+}
+
+/* For debugging. Lock the buffer queue and measure how much data (in bytes)
+ * and how many items are on the list.
+ */
+int hdcapm_buffer_used_queue_stats(struct hdcapm_dev *dev,
+ u64 *bytes, u64 *items)
+{
+ struct hdcapm_buffer *buf = NULL;
+ struct list_head *p = NULL, *q = NULL;
+
+ *bytes = 0;
+ *items = 0;
+
+ mutex_lock(&dev->dmaqueue_lock);
+ list_for_each_safe(p, q, &dev->list_buf_used) {
+ buf = list_entry(p, struct hdcapm_buffer, list);
+ (*bytes) += (buf->actual_size - buf->readpos);
+ (*items)++;
+ }
+ mutex_unlock(&dev->dmaqueue_lock);
+
+ return 0;
+}
diff --git a/drivers/media/usb/hdcapm/hdcapm-compressor.c b/drivers/media/usb/hdcapm/hdcapm-compressor.c
new file mode 100644
index 000000000000..c8795bef4d00
--- /dev/null
+++ b/drivers/media/usb/hdcapm/hdcapm-compressor.c
@@ -0,0 +1,782 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for the Startech USB2HDCAPM USB capture device
+ *
+ * Copyright (c) 2017 Steven Toth <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *
+ * GNU General Public License for more details.
+ */
+
+#include "hdcapm.h"
+
+#define CMD_ARRAY_SIZE(arr) (sizeof((arr)) / sizeof(u32))
+
+static char *cmd_name(u32 id)
+{
+ switch (id) {
+ case 0x01: return "Start Compressor";
+ case 0x02: return "Stop Compressor";
+ case 0x10: return "Configure Compressor Interface";
+ default: return "Undefined";
+ }
+}
+
+/* Wait up to 500ms for the firmware to be ready, or return a timeout.
+ * On idle, value 1 is return else < 0 indicates an error.
+ */
+static int fw_check_idle(struct hdcapm_dev *dev)
+{
+ unsigned long timeout = jiffies + msecs_to_jiffies(500);
+ int ret = -ETIMEDOUT;
+ u32 val;
+
+ while (!time_after(jiffies, timeout)) {
+ if (hdcapm_read32(dev, REG_FW_CMD_BUSY, &val) != 0) {
+ ret = -EINVAL; /* Error trying to read register. */
+ break;
+ }
+
+ if (val == 0) {
+ ret = 1; /* Success - Firmware is idle. */
+ break;
+ }
+
+ usleep_range(10000, 15000);
+ }
+
+ return ret;
+}
+
+/* Send a command to the firmware.
+ *
+ * Firmware commands and arguments are passed to this function for
+ * transmission to the hardware.
+ * An array of u32s, with the first u32 being the command type, followed
+ * by N arguments that are written to ARGS[0-n].
+ * Return 0 on success else < 0.
+ */
+static int execute_cmd(struct hdcapm_dev *dev, const u32 *cmdarr, u32 entries)
+{
+ int ret;
+ int i;
+
+ /* Check hardware is ready */
+ mutex_lock(&dev->lock);
+
+ if (fw_check_idle(dev) > 0) {
+ v4l2_dbg(1, hdcapm_debug, dev->sd,
+ "FIRMWARE CMD = 0x%08x [%s]\n",
+ *cmdarr, cmd_name(*cmdarr));
+
+ /* Send a new command to the hardware/firmware. */
+ /* Write all args into the FW arg registers */
+ for (i = 1; i < entries; i++)
+ v4l2_dbg(1, hdcapm_debug, dev->sd,
+ "%2d: 0x%08x\n", i - 1, *(cmdarr + i));
+
+ for (i = 1; i < entries; i++)
+ hdcapm_write32(dev, REG_FW_CMD_ARG(i - 1),
+ *(cmdarr + i));
+
+ /* Prepare the firmware to execute a command. */
+ hdcapm_write32(dev, REG_FW_CMD_BUSY, 1);
+
+ /* Trigger the command execution. */
+ hdcapm_write32(dev, REG_FW_CMD_EXECUTE, *cmdarr);
+
+ ret = 0; /* Success */
+ } else {
+ ret = -EINVAL;
+ }
+
+ mutex_unlock(&dev->lock);
+
+ return ret;
+}
+
+/* Stop encoder */
+static const u32 cmd_02[] = {
+ 0x00000002,
+ 0x00000000,
+};
+
+/* start encoder */
+static const u32 cmd_0a[] = {
+ 0x0000000a,
+ 0x00000008,
+};
+
+/* 27813 in LGPEncoder/complete-trace.tdc */
+static const u32 cmd_f1[] = {
+ 0x000000f1,
+ 0x80000011,
+};
+
+/* 27873 in LGPEncoder/complete-trace.tdc */
+static const u32 cmd_f2[] = {
+ 0x000000f2,
+ 0x01000100,
+};
+
+/* disable encoder */
+static const u32 cmd_f3[] = {
+ 0x000000f3,
+ 0x00000000,
+};
+
+/* 28548 in LGPEncoder/complete-trace.tdc */
+static const u32 cmd_10_0f[] = {
+ 0x00000010,
+ 0x0000000f,
+ 0x00000000,
+ 0x00000000,
+};
+
+/* 28643 in LGPEncoder/complete-trace.tdc */
+static const u32 cmd_10_10[] = {
+ 0x00000010,
+ 0x00000010,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+};
+
+/* 28743 in LGPEncoder/complete-trace.tdc */
+static const u32 cmd_10_12[] = {
+ 0x00000010,
+ 0x00000012,
+ 0x00000000,
+};
+
+/* 28823 in LGPEncoder/complete-trace.tdc */
+static const u32 cmd_10_13[] = {
+ 0x00000010,
+ 0x00000013,
+ 0x00000050,
+ 0x00000000,
+ 0x0000000a,
+};
+
+/* 29028 in LGPEncoder/complete-trace.tdc */
+static const u32 cmd_10_16[] = {
+ 0x00000010,
+ 0x00000016,
+ 0x00000000,
+ 0x00000000,
+};
+
+/* 29123 in LGPEncoder/complete-trace.tdc */
+static const u32 cmd_10_17[] = {
+ 0x00000010,
+ 0x00000017,
+ 0x00000000,
+};
+
+/* 29203 in LGPEncoder/complete-trace.tdc */
+static const u32 cmd_10_02[] = {
+ 0x00000010,
+ 0x00000002,
+ 0xf1f1f1da,
+ 0xb6f1f1b6,
+};
+
+/* LGPEncoder/complete-trace.tdc
+ * EP4 -> 01 00 07 00 B0 06 00 00
+ * - (query buffer availablility, read 7 words from address 6b0)
+ * EP3 <- 40 00 00 00 83 00 00 00 00 1F 3E 00 00 00
+ * 00 00 3F 5B 00 00 AA AA AA AA 01 00 00 00
+ * EP4 -> 09 00 08 00 00 00 00 00 00 1F 3E 00 3F 5B 00 00
+ * The buffer is transferred via EP1 IN,
+ * note that the ISO13818 DWORDS are byte reversed.....
+ * then this message is sent to the firmware to acknowledge the buffer was read?
+ */
+/* 30739 in LGPEncoder/complete-trace.tdc */
+static const u32 cmd_30[] = {
+ 0x00000030, /* Fixed value */
+ 0x00000083, /* Fixed value */
+ 0x00005b3f, /* Number of DWORDS we previously read. */
+ 0x00010007, /* Fixed value */
+ 0x2aaaaaaa, /* Fixed value */
+ 0x00000000, /* Fixed value */
+ 0x00000000, /* Fixed value */
+};
+
+static int hdcapm_compressor_enable_firmware(struct hdcapm_dev *dev, int val)
+{
+ // 32527
+ /* EP4 Host -> 07 01 00 00 00 00 00 00 */
+ u8 tx[] = {
+ 0x07,
+ val, /* 0 = stop, 1 = start. */
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ };
+
+ v4l2_dbg(2, hdcapm_debug, dev->sd, "%s()\n", __func__);
+
+ /* Flush this to EP4 via a bulk write. */
+ if (hdcapm_core_ep_send(dev, PIPE_EP4, &tx[0], sizeof(tx), 500) < 0)
+ return -1;
+
+ return 0; /* Success */
+}
+
+static int firmware_transition(struct hdcapm_dev *dev, int run,
+ struct v4l2_dv_timings *timings)
+{
+ struct hdcapm_encoder_parameters *p = &dev->encoder_parameters;
+
+ /* 29298 in LGPEncoder/complete-trace.tdc */
+ u32 cfg[12];
+
+ u32 i_width, i_height, i_fps;
+ u32 o_width, o_height, o_fps;
+ u32 min_bitrate_kbps = dev->encoder_parameters.bitrate_bps / 1000;
+ u32 max_bitrate_kbps = dev->encoder_parameters.bitrate_peak_bps / 1000;
+ u32 htotal, vtotal;
+ u32 timing_fpsx100;
+
+ v4l2_dbg(1, hdcapm_debug, dev->sd,
+ "%s(%p, %s)\n", __func__, dev, run == 1 ? "START" : "STOP");
+ if (run) {
+ if (!timings) {
+ v4l2_err(dev->sd, "no timing during firmware transition\n");
+ return -EINVAL;
+ }
+
+ /* Prepare the video/audio compression settings. */
+ i_width = timings->bt.width;
+ i_height = timings->bt.height;
+ htotal = V4L2_DV_BT_FRAME_WIDTH(&timings->bt);
+ vtotal = V4L2_DV_BT_FRAME_HEIGHT(&timings->bt);
+ if (htotal * vtotal) {
+ timing_fpsx100 =
+ div_u64((100 * (u64)timings->bt.pixelclock),
+ (htotal * vtotal));
+ } else {
+ v4l2_err(dev->sd, "no fps calulated\n");
+ return -EINVAL;
+ }
+
+ i_fps = timing_fpsx100 / 100;
+
+ /* If the user has requested a different output
+ * resolution via set/try fmt, obey.
+ */
+ if (p->output_width)
+ o_width = p->output_width;
+ else
+ o_width = i_width;
+
+ if (p->output_width)
+ o_height = p->output_height;
+ else
+ o_height = i_height;
+
+ o_fps = i_fps;
+
+ /* Scaling. Adjust width, height and output fps.
+ * Hardware can't handle anything above p30, drop frames
+ * from p60 to 30, p50 to 25.
+ */
+
+ if (timings->bt.width == 1920 && timings->bt.height == 1080 &&
+ !timings->bt.interlaced && i_fps > 30)
+ o_fps /= 2;
+
+ cfg[0] = 0x00000001;
+ cfg[1] = 0x21010019 |
+ (dev->encoder_parameters.h264_level << 12) |
+ (dev->encoder_parameters.h264_entropy_mode << 26) |
+ (dev->encoder_parameters.h264_profile << 8);
+
+ cfg[2] = i_height << 16 | i_width;
+ cfg[3] = o_fps << 23 | i_fps << 16 | 0x0609;
+ cfg[4] = 0x0050 << 16 | min_bitrate_kbps |
+ (dev->encoder_parameters.h264_mode << 31);
+ cfg[5] = max_bitrate_kbps << 16 | min_bitrate_kbps;
+ cfg[6] = 0x48002000;
+ cfg[7] = 0xe0010000 | dev->encoder_parameters.gop_size;
+ cfg[8] = o_height << 16 | o_width;
+ cfg[9] = 0xc00000d0;
+ cfg[10] = 0x21121080;
+ cfg[11] = 0x465001f2;
+
+ hdcapm_compressor_enable_firmware(dev, 1);
+
+ /* From LGP device dump line 27788 */
+ execute_cmd(dev, cmd_f1, CMD_ARRAY_SIZE(cmd_f1));
+ execute_cmd(dev, cmd_f2, CMD_ARRAY_SIZE(cmd_f2));
+
+ /* Configure the video / audio compressors. */
+ execute_cmd(dev, cmd_10_0f, CMD_ARRAY_SIZE(cmd_10_0f));
+ execute_cmd(dev, cmd_10_10, CMD_ARRAY_SIZE(cmd_10_10));
+ execute_cmd(dev, cmd_10_12, CMD_ARRAY_SIZE(cmd_10_12));
+ execute_cmd(dev, cmd_10_13, CMD_ARRAY_SIZE(cmd_10_13));
+ execute_cmd(dev, cmd_10_16, CMD_ARRAY_SIZE(cmd_10_16));
+ execute_cmd(dev, cmd_10_17, CMD_ARRAY_SIZE(cmd_10_17));
+ execute_cmd(dev, cmd_10_02, CMD_ARRAY_SIZE(cmd_10_02));
+
+ /* Configure and start encoder. */
+ execute_cmd(dev, cfg, CMD_ARRAY_SIZE(cfg));
+ execute_cmd(dev, cmd_0a, CMD_ARRAY_SIZE(cmd_0a));
+ } else {
+ /* Stop and disable encoder. */
+ execute_cmd(dev, cmd_02, CMD_ARRAY_SIZE(cmd_02));
+ execute_cmd(dev, cmd_f3, CMD_ARRAY_SIZE(cmd_f3));
+ }
+
+ return 0;
+}
+
+/* Perform a status read of the compressor. If TS data is available then
+ * query that and push the buffer into a user queue for later processing.
+ */
+static int usb_read(struct hdcapm_dev *dev)
+{
+ struct hdcapm_buffer *buf;
+ u32 val;
+ u32 arr[7];
+ u8 r[4];
+ int ret, i;
+ u32 bytes_to_read;
+
+ /* Query the Compressor regs 0x6b0-0x6c8.
+ * Determine whether a buffer is ready for transfer.
+ * Reg 6b0 (0): Status indicator?
+ * Reg 6b4 (1): F1 always 0x83
+ * Reg 6b8 (2): Transport buffer address (on host h/w)
+ * Reg 6bc (3):
+ * Reg 6c0 (4): Number of dwords
+ * Reg 6c4 (5):
+ * Reg 6c8 (6):
+ * Line 55674 - LGPEncoder/complete-trace.tdc
+ */
+
+ ret = hdcapm_read32_array(dev, REG_06B0, ARRAY_SIZE(arr), &arr[0], 1);
+ if (ret < 0) {
+ /* Failure to read from the device. */
+ return -EINVAL;
+ }
+
+ v4l2_dbg(3, hdcapm_debug, dev->sd,
+ "tsb reply: %08x %08x %08x %08x %08x %08x %08x\n",
+ arr[0], arr[1], arr[2], arr[3], arr[4], arr[5], arr[6]);
+
+ /* Check the reply */
+ if (arr[6] == 0) {
+ /* Buffer not yet ready. */
+ dev->stats->codec_ts_not_yet_ready++;
+ return -ETIMEDOUT;
+ }
+
+ /* Check this is a TS buffer */
+ if ((arr[0] & 0xff) != 0x40) {
+ /* Unexpected, debug this. */
+ v4l2_err(dev->sd,
+ "tsb reply: %08x %08x %08x %08x %08x %08x %08x (No 0x40?)\n",
+ arr[0], arr[1], arr[2], arr[3],
+ arr[4], arr[5], arr[6]);
+ //WARN_ON();
+ }
+
+ /* Check this other fixed value. */
+ if ((arr[1] & 0xff) != 0x83) {
+ /* Unexpected, debug this. */
+ v4l2_err(dev->sd,
+ "tsb reply: %08x %08x %08x %08x %08x %08x %08x (No 0x83?)\n",
+ arr[0], arr[1], arr[2], arr[3],
+ arr[4], arr[5], arr[6]);
+ WARN_ON(1);
+ }
+
+ bytes_to_read = arr[4] * sizeof(u32);
+ if (bytes_to_read > 256000) {
+ /* Unexpected, debug this. */
+ v4l2_err(dev->sd,
+ "tsb reply: %08x %08x %08x %08x %08x %08x %08x (Too many dwords?)\n",
+ arr[0], arr[1], arr[2], arr[3],
+ arr[4], arr[5], arr[6]);
+ WARN_ON(1);
+ }
+
+ /* We need a buffer to transfer the TS into. */
+ buf = hdcapm_buffer_next_free(dev);
+ if (!buf)
+ return -EINVAL;
+
+ /* Transfer buffer from the USB device
+ * (address arr[2]), length arr[4]).
+ */
+ ret = hdcapm_dmaread32(dev, arr[2], (u32 *)buf->ptr, arr[4]);
+ if (ret < 0) {
+ /* Throw the buffer back in the free list. */
+ hdcapm_buffer_add_to_free(dev, buf);
+ return -EINVAL;
+ }
+
+ /* The buffer comes back in DWORD ordering, we need to fixup the
+ * payload to put the TS packet bytes back into the right order.
+ */
+ for (i = 0; i < bytes_to_read; i += 4) {
+ r[0] = *(buf->ptr + i + 3);
+ r[1] = *(buf->ptr + i + 2);
+ r[2] = *(buf->ptr + i + 1);
+ r[3] = *(buf->ptr + i + 0);
+
+ *(buf->ptr + i + 0) = r[0];
+ *(buf->ptr + i + 1) = r[1];
+ *(buf->ptr + i + 2) = r[2];
+ *(buf->ptr + i + 3) = r[3];
+ }
+
+ dev->stats->codec_bytes_received += bytes_to_read;
+ dev->stats->codec_buffers_received++;
+
+ /* Put the buffer on the used list,
+ * the caller will read/dequeue it later.
+ */
+ buf->actual_size = bytes_to_read;
+ buf->readpos = 0;
+ hdcapm_buffer_add_to_used(dev, buf);
+
+ /* Signal to any userland waiters, new buffer available. */
+ wake_up_interruptible(&dev->wait_read);
+
+ /* Acknowledge the buffer back to the firmware. */
+ hdcapm_read32(dev, 0x800, &val);
+ hdcapm_write32(dev, 0x800, val);
+
+ hdcapm_write32(dev, REG_FW_CMD_ARG(0), 0x83);
+ hdcapm_write32(dev, REG_FW_CMD_ARG(1), arr[4]);
+ hdcapm_write32(dev, REG_FW_CMD_ARG(2), 0x2aaaaaaa);
+ hdcapm_write32(dev, REG_FW_CMD_ARG(3), 0);
+ hdcapm_write32(dev, REG_FW_CMD_ARG(5), 0);
+ hdcapm_write32(dev, REG_FW_CMD_BUSY, 1);
+ hdcapm_write32(dev, REG_FW_CMD_EXECUTE, 0x30);
+
+ hdcapm_write32(dev, 0x6c8, 0);
+
+ return 0;
+}
+
+void hdcapm_compressor_init_gpios(struct hdcapm_dev *dev)
+{
+ // 38045 - bit toggling, gpios
+
+ /* Available GPIO's 15-0. */
+
+ /* Configure GPIO's 3-1, 8, 11, 12 as outputs. */
+ hdcapm_set32(dev, REG_GPIO_OE, 0x2);
+ hdcapm_set32(dev, REG_GPIO_OE, 0x4);
+ hdcapm_set32(dev, REG_GPIO_OE, 0x8);
+ hdcapm_set32(dev, REG_GPIO_OE, 0x100);
+ hdcapm_set32(dev, REG_GPIO_OE, 0x800);
+ hdcapm_set32(dev, REG_GPIO_OE, 0x1000);
+ /* Reg should end up at 190E */
+
+ /* Pull all GPIO's high. */
+ hdcapm_clr32(dev, REG_GPIO_DATA_WR, 0xFFFFFFFF);
+
+ /* GPIO #2 is the MST3367 reset, active high, */
+
+ /* TODO: is this register inverted,
+ * meaning writes high result in low?
+ */
+ hdcapm_set32(dev, REG_GPIO_DATA_WR, 0x00000004);
+
+ mdelay(500);
+}
+
+int hdcapm_compressor_register(struct hdcapm_dev *dev)
+{
+ const struct firmware *fw = NULL;
+ const char *fw_video = "v4l-hdcapm-vidfw-01.fw";
+ size_t fw_video_len = 453684;
+ const char *fw_audio = "v4l-hdcapm-audfw-01.fw";
+ size_t fw_audio_len = 363832;
+ u32 val;
+ u32 *dwords;
+ u32 addr;
+ u32 chunk;
+ u32 cpy;
+ u32 offset;
+ int ret;
+
+ hdcapm_compressor_enable_firmware(dev, 0);
+
+// pl330b_lib_reinit
+ hdcapm_write32(dev, REG_081C, 0x00004000);
+ hdcapm_write32(dev, REG_0820, 0x00103FFF);
+ hdcapm_write32(dev, REG_0824, 0x00000000);
+
+ hdcapm_write32(dev, REG_0828, 0x00104000);
+ hdcapm_write32(dev, REG_082C, 0x00203FFF);
+ hdcapm_write32(dev, REG_0830, 0x00100000);
+
+ hdcapm_write32(dev, REG_0834, 0x00204000);
+ hdcapm_write32(dev, REG_0838, 0x00303FFF);
+ hdcapm_write32(dev, REG_083C, 0x00200000);
+
+ hdcapm_write32(dev, REG_0840, 0x70003124);
+ hdcapm_write32(dev, REG_0840, 0x90003124);
+
+ /* Hardware ID? Only every read, never written */
+ if (hdcapm_read32(dev, REG_0038, &val) < 0) {
+ v4l2_err(dev->sd,
+ "USB read failure, chip id check failed, aborting.\n");
+ return -EINVAL;
+ }
+ v4l2_dbg(1, hdcapm_debug, dev->sd,
+ "chiprev? [%08x = %08x]\n", REG_0038, val);
+ WARN_ON(val != 0x00010020);
+
+#if ONETIME_FW_LOAD
+ hdcapm_write32(dev, REG_GPIO_OE, 0x00000000);
+ hdcapm_write32(dev, REG_GPIO_DATA_WR, 0x00000000);
+#endif
+
+ /* Disable audio / video outputs (bits 1/2). */
+ hdcapm_write32(dev, REG_0050, 0x00200400);
+ hdcapm_read32(dev, REG_0050, &val);
+
+ v4l2_dbg(1, hdcapm_debug, dev->sd, "%08x = %08x\n", REG_0050, val);
+ WARN_ON(val != 0x00200400);
+
+ /* Give the device enough time to boot its initial microcode. */
+ msleep(1000);
+
+ hdcapm_compressor_enable_firmware(dev, 0);
+
+ hdcapm_write32(dev, REG_FW_CMD_BUSY, 0x00000000);
+ hdcapm_write32(dev, REG_081C, 0x00004000);
+
+ hdcapm_write32(dev, REG_0820, 0x00103FFF);
+ hdcapm_write32(dev, REG_0824, 0x00000000);
+ hdcapm_write32(dev, REG_0828, 0x00104000);
+ hdcapm_write32(dev, REG_082C, 0x00203FFF);
+ hdcapm_write32(dev, REG_0830, 0x00100000);
+ hdcapm_write32(dev, REG_0834, 0x00204000);
+ hdcapm_write32(dev, REG_0838, 0x00303FFF);
+ hdcapm_write32(dev, REG_083C, 0x00200000);
+ hdcapm_write32(dev, REG_0840, 0x70003124);
+ hdcapm_write32(dev, REG_0840, 0x90003124);
+
+ hdcapm_write32(dev, REG_0050, 0x00200406);
+
+ hdcapm_write32(dev, REG_0050, 0x00200406);
+ hdcapm_read32(dev, REG_0050, &val);
+
+ /* Disable audio and video outputs. */
+ hdcapm_write32(dev, REG_0050, 0x00200406);
+
+#if ONETIME_FW_LOAD
+ hdcapm_write32(dev, REG_GPIO_OE, 0x00000000);
+ hdcapm_write32(dev, REG_GPIO_DATA_WR, 0x00000000);
+#endif
+
+ hdcapm_read32(dev, REG_0000, &val);
+ hdcapm_write32(dev, REG_0000, 0x03FF0300);
+
+ /* Wipe memory at various addresses */
+ dwords = kzalloc(0x2000 * sizeof(u32), GFP_KERNEL);
+ if (!dwords)
+ return -ENOMEM;
+
+ if (hdcapm_dmawrite32(dev, 0x0005634E, dwords, 0x2000) < 0) {
+ v4l2_err(dev->sd, "wipe of addr1 failed\n");
+ return -EINVAL;
+ }
+ if (hdcapm_dmawrite32(dev, 0x0005834E, dwords, 0x2000) < 0) {
+ v4l2_err(dev->sd, "wipe of addr2 failed\n");
+ return -EINVAL;
+ }
+ if (hdcapm_dmawrite32(dev, 0x0005A34E, dwords, 0x1E3B) < 0) {
+ v4l2_err(dev->sd, "wipe of addr3 failed\n");
+ return -EINVAL;
+ }
+ kfree(dwords);
+
+ /* Upload the audio firmware. */
+ ret = request_firmware(&fw, fw_audio, &dev->udev->dev);
+ if (ret) {
+ v4l2_err(dev->sd,
+ "failed to find firmware file %s, aborting upload.\n",
+ fw_video);
+ return -EINVAL;
+ }
+ if (fw->size != fw_audio_len) {
+ v4l2_err(dev->sd, "failed video firmware length check\n");
+ release_firmware(fw);
+ return -EINVAL;
+ }
+ v4l2_info(dev->sd, "loading audio firmware size %zu bytes.\n",
+ fw->size);
+
+ offset = 0;
+ addr = 0x00040000;
+ val = fw_audio_len;
+ chunk = 0x2000 * sizeof(u32);
+ while (val > 0) {
+ if (val > chunk)
+ cpy = chunk;
+ else
+ cpy = val;
+
+ hdcapm_dmawrite32(dev, addr, (const u32 *)fw->data + offset,
+ cpy / sizeof(u32));
+
+ val -= cpy;
+ addr += (cpy / sizeof(u32));
+ offset += (cpy / sizeof(u32));
+ }
+ release_firmware(fw);
+
+ hdcapm_mem_write32(dev, 0x000BC425, 1);
+ hdcapm_mem_write32(dev, 0x000BC424, 0);
+ hdcapm_mem_write32(dev, 0x000BC801, 0);
+
+ hdcapm_write32(dev, REG_0B78, 0x00150000);
+ hdcapm_write32(dev, REG_FW_CMD_BUSY, 0x00000000);
+
+ /* Upload the video firmware. */
+ ret = request_firmware(&fw, fw_video, &dev->udev->dev);
+ if (ret) {
+ v4l2_err(dev->sd,
+ "failed to find firmware file %s, aborting upload.\n",
+ fw_video);
+ return -EINVAL;
+ }
+ if (fw->size != fw_video_len) {
+ v4l2_err(dev->sd,
+ "failed video firmware length check\n");
+ release_firmware(fw);
+ return -EINVAL;
+ }
+ v4l2_info(dev->sd, "loading video firmware size %zu bytes.\n",
+ fw->size);
+
+ /* Load the video firmware */
+ offset = 0;
+ addr = 0x00000000;
+ val = fw_video_len;
+ chunk = 0x2000 * sizeof(u32);
+ while (val > 0) {
+ if (val > chunk)
+ cpy = chunk;
+ else
+ cpy = val;
+
+ hdcapm_dmawrite32(dev, addr, (const u32 *)fw->data + offset,
+ cpy / sizeof(u32));
+
+ val -= cpy;
+ addr += (cpy / sizeof(u32));
+ offset += (cpy / sizeof(u32));
+ }
+ release_firmware(fw);
+
+ hdcapm_compressor_enable_firmware(dev, 1);
+ hdcapm_write32(dev, REG_FW_CMD_BUSY, 0x00000000);
+
+ hdcapm_mem_read32(dev, 0x00000040, &val);
+
+ hdcapm_mem_read32(dev, 0x00000041, &val);
+
+ hdcapm_mem_read32(dev, 0x000Bc804, &val);
+
+ msleep(100);
+
+#if ONETIME_FW_LOAD
+ hdcapm_compressor_init_gpios(dev);
+#endif
+
+ v4l2_info(dev->sd, "Registered compressor\n");
+ return 0;
+}
+
+void hdcapm_compressor_unregister(struct hdcapm_dev *dev)
+{
+ v4l2_dbg(1, hdcapm_debug, dev->sd,
+ "%s() Unregistered compressor\n", __func__);
+}
+
+void hdcapm_compressor_run(struct hdcapm_dev *dev)
+{
+ struct v4l2_dv_timings timings;
+ int ret;
+ int val;
+
+ v4l2_dbg(1, hdcapm_debug, dev->sd, "%s()\n", __func__);
+
+ if (v4l2_subdev_call(dev->sd, video, g_dv_timings, &timings) < 0) {
+ v4l2_err(dev->sd, "%s() subdev call failed\n", __func__);
+ dev->state = STATE_STOPPED;
+ return;
+ }
+
+ /* Make sure all of our buffers are available again. */
+ hdcapm_buffers_move_all(dev, &dev->list_buf_free, &dev->list_buf_used);
+
+#if !(ONETIME_FW_LOAD)
+ /* Register the compression codec (it does both audio and video). */
+ if (hdcapm_compressor_register(dev) < 0) {
+ v4l2_err(dev->sd, "failed to register compressor\n");
+ return;
+ }
+#endif
+
+ /* Enable audio and video outputs. */
+ hdcapm_read32(dev, REG_0050, &val);
+ val &= ~(1 << 1);
+ val &= ~(1 << 2);
+ hdcapm_write32(dev, REG_0050, val);
+
+ ret = firmware_transition(dev, 1, &timings);
+
+ dev->state = STATE_STARTED;
+ while (dev->state == STATE_STARTED) {
+ ret = usb_read(dev);
+ usleep_range(500, 4000);
+ }
+
+ /* Disable audio and video outputs. */
+ hdcapm_read32(dev, REG_0050, &val);
+ val |= (1 << 1);
+ val |= (1 << 2);
+ hdcapm_write32(dev, REG_0050, val);
+
+ /* Give the device enough time to resume its microcode. */
+ msleep(1000);
+
+ ret = firmware_transition(dev, 0, NULL);
+
+#if !(ONETIME_FW_LOAD)
+ hdcapm_compressor_unregister(dev);
+ hdcapm_compressor_init_gpios(dev);
+
+ /* Reloading the firmware disturbs the GPIOs and
+ * causes the MST3367 to go into reset.
+ * Be kind, tell the HDMI receiver to
+ * reconfigure itself.
+ */
+ v4l2_subdev_call(dev->sd, core, s_power, 1);
+#endif
+
+ dev->state = STATE_STOPPED;
+
+ hdcapm_buffers_move_all(dev, &dev->list_buf_free, &dev->list_buf_used);
+}
diff --git a/drivers/media/usb/hdcapm/hdcapm-core.c b/drivers/media/usb/hdcapm/hdcapm-core.c
new file mode 100644
index 000000000000..68836d339b7d
--- /dev/null
+++ b/drivers/media/usb/hdcapm/hdcapm-core.c
@@ -0,0 +1,743 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for the Startech USB2HDCAPM USB capture device
+ *
+ * Copyright (c) 2017 Steven Toth <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *
+ * GNU General Public License for more details.
+ */
+
+#include "hdcapm.h"
+#include <media/i2c/mst3367.h>
+
+int hdcapm_debug;
+module_param_named(debug, hdcapm_debug, int, 0644);
+MODULE_PARM_DESC(debug, "debug bitmask: 1) module");
+
+int hdcapm_i2c_scan;
+module_param_named(i2c_scan, hdcapm_i2c_scan, int, 0644);
+MODULE_PARM_DESC(i2c_scan, "Probe i2c bus for devices");
+
+unsigned int thread_poll_interval = 500;
+module_param(thread_poll_interval, int, 0644);
+MODULE_PARM_DESC(thread_poll_interval,
+ "have the kernel thread poll every N ms (def:500)");
+
+unsigned int buffer_count = 128;
+module_param(buffer_count, int, 0644);
+MODULE_PARM_DESC(buffer_count, "# of buffers the driver should queue");
+
+#define XFERBUF_SIZE (65536 * 4)
+unsigned int buffer_size = XFERBUF_SIZE;
+module_param(buffer_size, int, 0644);
+MODULE_PARM_DESC(buffer_size, "size of each buffer in bytes");
+
+static DEFINE_MUTEX(devlist);
+LIST_HEAD(hdcapm_devlist);
+static unsigned int devlist_count;
+
+/* Copy an on-stack transfer buffer into a device context. Do this
+ * before we pass it to the USB subsystem, else ARM complains (once) in
+ * the USB controller about the location of the transfer. TODO: Review
+ * usage and optimize of the calls so that not all transfers need to be
+ * on stack.
+ */
+int hdcapm_core_ep_send(struct hdcapm_dev *dev, int endpoint,
+ u8 *buf, u32 len, u32 timeout)
+{
+ int writelength;
+
+ if (len > XFERBUF_SIZE) {
+ v4l2_err(dev->sd,
+ "%s() buffer of %d bytes too large for transfer\n",
+ __func__, len);
+ return -1;
+ }
+
+ memcpy(dev->xferbuf, buf, len);
+ dev->xferbuf_len = len;
+
+ /* Flush this to EP4 via a bulk write. */
+ return usb_bulk_msg(dev->udev, usb_sndbulkpipe(dev->udev, endpoint),
+ dev->xferbuf, dev->xferbuf_len,
+ &writelength, timeout);
+}
+
+/* Copy a transfer buffer from the device context back to an onstack location.
+ * TODO: Review usage and optimize of the calls so that not all
+ * transfers need to be on stack.
+ */
+int hdcapm_core_ep_recv(struct hdcapm_dev *dev, int endpoint,
+ u8 *buf, u32 len, u32 *actual, u32 timeout)
+{
+ int ret;
+
+ WARN_ON(len > XFERBUF_SIZE);
+
+ /* Bulk read */
+ ret = usb_bulk_msg(dev->udev, usb_rcvbulkpipe(dev->udev, endpoint),
+ dev->xferbuf, len, &dev->xferbuf_len, timeout);
+
+ memcpy(buf, dev->xferbuf, dev->xferbuf_len);
+ *actual = dev->xferbuf_len;
+
+ return ret;
+}
+
+int hdcapm_mem_write32(struct hdcapm_dev *dev, u32 addr, u32 val)
+{
+ /* EP4 Host -> 02 01 04 00 01 C8 0B 00 01 C8 0B 00 00 00 00 00 */
+ u8 tx[] = {
+ 0x02,
+ 0x01, /* Write */
+ 0x04,
+ 0x00,
+ addr, /* This is really a fill function? */
+ addr >> 8,
+ addr >> 16,
+ addr >> 24,
+ addr,
+ addr >> 8,
+ addr >> 16,
+ addr >> 24,
+ val,
+ val >> 8,
+ val >> 16,
+ val >> 24,
+ };
+
+ v4l2_dbg(2, hdcapm_debug, dev->sd,
+ "%s(0x%08x, 0x%08x)\n", __func__, addr, val);
+
+ if (hdcapm_core_ep_send(dev, PIPE_EP4, &tx[0], sizeof(tx), 500) < 0)
+ return -1;
+
+ return 0;
+}
+
+/* Read a single DWORD from the USB device memory. */
+int hdcapm_mem_read32(struct hdcapm_dev *dev, u32 addr, u32 *val)
+{
+ int len;
+ u8 rx[4];
+
+ /* Read bytes between to addresses
+ * EP4 Host -> 02 00 04 00 01 C8 0B 00 01 C8 0B 00
+ * EP3 Host <- 00 00 00 00
+ */
+ u8 tx[] = {
+ 0x02,
+ 0x00, /* Read */
+ 0x04,
+ 0x00,
+ addr,
+ addr >> 8,
+ addr >> 16,
+ addr >> 24,
+ addr,
+ addr >> 8,
+ addr >> 16,
+ addr >> 24,
+ };
+
+ if (hdcapm_core_ep_send(dev, PIPE_EP4, &tx[0], sizeof(tx), 500) < 0)
+ return -1;
+
+ /* Read 4 bytes from EP 3. */
+ /* TODO: shouldn;t the buffer length be 4? */
+ if (hdcapm_core_ep_recv(dev,
+ PIPE_EP3, &rx[0], sizeof(rx), &len, 500) < 0)
+ return -1;
+
+ *val = rx[0] | (rx[1] << 8) | (rx[2] << 16) | (rx[3] << 24);
+ v4l2_dbg(2, hdcapm_debug, dev->sd,
+ "%s(0x%08x, 0x%08x)\n", __func__, addr, *val);
+
+ return 0;
+}
+
+/* Write a series of DMA DWORDS from the USB device memory. */
+int hdcapm_dmawrite32(struct hdcapm_dev *dev, u32 addr,
+ const u32 *arr, u32 entries)
+{
+ int len;
+ u8 rx;
+
+ /* EP4 Host -> 09 01 08 00 00 00 00 00 4E 63 05 00 00 20 00 00 */
+ u8 tx[] = {
+ 0x09,
+ 0x01, /* Write */
+ 0x08,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ addr,
+ addr >> 8,
+ addr >> 16,
+ addr >> 24,
+ entries,
+ entries >> 8,
+ entries >> 16,
+ entries >> 24,
+ };
+
+ v4l2_dbg(2, hdcapm_debug, dev->sd,
+ "%s(0x%08x, 0x%08x)\n", __func__, addr, entries);
+
+ if (hdcapm_core_ep_send(dev, PIPE_EP4, &tx[0], sizeof(tx), 500) < 0)
+ return -1;
+
+ /* Read 1 byte1 from EP 3. */
+ if (hdcapm_core_ep_recv(dev, PIPE_EP3, &rx, sizeof(rx), &len, 500) < 0)
+ return -1;
+
+ if (rx != 0)
+ return -1;
+
+ /* Flush the buffer to device */
+ if (hdcapm_core_ep_send(dev, PIPE_EP2, (u8 *)arr, entries * sizeof(u32),
+ 5000) < 0)
+ return -1;
+
+ return 0;
+}
+
+/* Read a series of DMA DWORDS from the USB device memory. */
+int hdcapm_dmaread32(struct hdcapm_dev *dev, u32 addr, u32 *arr, u32 entries)
+{
+ int len;
+ u8 rx;
+
+ /* EP4 Host -> 09 00 08 00 00 00 00 00 00 C8 05 00 00 04 00 00 */
+ u8 tx[] = {
+ 0x09,
+ 0x00, /* Read */
+ 0x08,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ addr,
+ addr >> 8,
+ addr >> 16,
+ addr >> 24,
+ entries,
+ entries >> 8,
+ entries >> 16,
+ entries >> 24,
+ };
+
+ v4l2_dbg(2, hdcapm_debug, dev->sd,
+ "%s(0x%08x, 0x%08x)\n", __func__, addr, entries);
+
+ if (hdcapm_core_ep_send(dev, PIPE_EP4, &tx[0], sizeof(tx), 500) < 0)
+ return -1;
+
+ /* Read 1 byte1 from EP 3. */
+ if (hdcapm_core_ep_recv(dev, PIPE_EP3, &rx, sizeof(rx), &len, 500) < 0)
+ return -1;
+
+ if (rx != 0)
+ return -1;
+
+ /* Flush the buffer to device */
+ if (hdcapm_core_ep_recv(dev, PIPE_EP1, (u8 *)arr, entries * sizeof(u32),
+ &len, 5000) < 0)
+ return -1;
+
+ return 0;
+}
+
+/* Write a DWORD to a USB device register. */
+int hdcapm_write32(struct hdcapm_dev *dev, u32 addr, u32 val)
+{
+ /* EP4 Host -> 01 01 01 00 04 05 00 00 55 00 00 00 */
+ u8 tx[] = {
+ 0x01,
+ 0x01, /* Write */
+ 0x01,
+ 0x00,
+ addr,
+ addr >> 8,
+ addr >> 16,
+ addr >> 24,
+ val,
+ val >> 8,
+ val >> 16,
+ val >> 24,
+ };
+
+ v4l2_dbg(2, hdcapm_debug, dev->sd,
+ "%s(0x%08x, 0x%08x)\n", __func__, addr, val);
+
+ if (hdcapm_core_ep_send(dev, PIPE_EP4, &tx[0], sizeof(tx), 500) < 0)
+ return -1;
+
+ return 0;
+}
+
+/* Read a DWORD from a USB device register. */
+int hdcapm_read32(struct hdcapm_dev *dev, u32 addr, u32 *val)
+{
+ int len;
+ u8 rx[4];
+
+ /* EP4 Host -> 01 00 01 00 00 05 00 00 */
+ u8 tx[] = {
+ 0x01,
+ 0x00, /* Read */
+ 0x01,
+ 0x00,
+ addr,
+ addr >> 8,
+ addr >> 16,
+ addr >> 24,
+ };
+
+ /* Flush this to EP4 via a write. */
+ if (hdcapm_core_ep_send(dev, PIPE_EP4, &tx[0], sizeof(tx), 500) < 0)
+ return -1;
+
+ /* Read 4 bytes from EP 3. */
+ if (hdcapm_core_ep_recv(dev, PIPE_EP3, &rx[0],
+ sizeof(rx), &len, 500) < 0)
+ return -1;
+
+ *val = rx[0] | (rx[1] << 8) | (rx[2] << 16) | (rx[3] << 24);
+
+ v4l2_dbg(2, hdcapm_debug, dev->sd,
+ "%s(0x%08x, 0x%08x)\n", __func__, addr, *val);
+
+ return 0;
+}
+
+/* Read (bulk) a number of DWORDS from device registers and endian
+ * convert if requested.
+ */
+int hdcapm_read32_array(struct hdcapm_dev *dev, u32 addr,
+ u32 wordcount, u32 *arr, int le_to_cpu)
+{
+ int len, i, j;
+ int readlenbytes = wordcount * sizeof(u32);
+ u8 *rx;
+
+ /* EP4 Host -> 01 00 07 00 B0 06 00 00 */
+ u8 tx[] = {
+ 0x01,
+ 0x00, /* Read */
+ wordcount,
+ 0x00,
+ addr,
+ addr >> 8,
+ addr >> 16,
+ addr >> 24,
+ };
+
+ rx = kzalloc(readlenbytes, GFP_KERNEL);
+ if (!rx)
+ return -ENOMEM;
+
+ /* Flush this to EP4 via a write. */
+ if (hdcapm_core_ep_send(dev, PIPE_EP4, &tx[0], sizeof(tx), 500) < 0) {
+ kfree(rx);
+ return -1;
+ }
+
+ /* Read 4 bytes from EP 3. */
+ if (hdcapm_core_ep_recv(dev, PIPE_EP3, rx, readlenbytes, &len, 500)
+ < 0) {
+ kfree(rx);
+ return -1;
+ }
+
+ v4l2_dbg(2, hdcapm_debug, dev->sd, "%s(0x%08x) =\n", __func__, addr);
+
+ for (i = 0, j = 0; i < len; i += 4, j++) {
+ *(arr + j) = rx[i + 0] | (rx[i + 1] << 8) |
+ (rx[i + 2] << 16) | (rx[i + 3] << 24);
+
+ if (le_to_cpu)
+ *(arr + j) = le32_to_cpu(*(arr + j));
+ }
+
+ kfree(rx);
+ return 0;
+}
+
+/* Set one or more bits high int a USB device register. */
+void hdcapm_set32(struct hdcapm_dev *dev, u32 addr, u32 mask)
+{
+ u32 val;
+
+ hdcapm_read32(dev, addr, &val);
+ val |= mask;
+ hdcapm_write32(dev, addr, val);
+}
+
+/* Set one or more bits low int a USB device register. */
+void hdcapm_clr32(struct hdcapm_dev *dev, u32 addr, u32 mask)
+{
+ u32 val;
+
+ hdcapm_read32(dev, addr, &val);
+ val &= ~mask;
+ hdcapm_write32(dev, addr, val);
+}
+
+int hdcapm_core_stop_streaming(struct hdcapm_dev *dev)
+{
+ dev->state = STATE_STOP;
+
+ return 0;
+}
+
+int hdcapm_core_start_streaming(struct hdcapm_dev *dev)
+{
+ dev->state = STATE_START;
+
+ return 0;
+}
+
+/* Worker thread to poll the HDMI receiver, and run the USB
+ * transfer mechanism when the encoder starts.
+ */
+static int hdcapm_thread_function(void *data)
+{
+ struct hdcapm_dev *dev = data;
+ struct v4l2_dv_timings timings;
+ int ret;
+
+ dev->thread_active = 1;
+ v4l2_dbg(1, hdcapm_debug, dev->sd, "%s() Started\n", __func__);
+
+ set_freezable();
+
+ while (1) {
+ msleep_interruptible(thread_poll_interval);
+
+ if (kthread_should_stop())
+ break;
+
+ try_to_freeze();
+
+ if (dev->state == STATE_STOPPED)
+ ret = v4l2_subdev_call(dev->sd,
+ video, query_dv_timings,
+ &timings);
+
+ if (dev->state == STATE_START)
+ hdcapm_compressor_run(dev); /* blocking */
+ }
+
+ dev->thread_active = 0;
+ return 0;
+}
+
+static void hdcapm_usb_v4l2_release(struct v4l2_device *v4l2_dev)
+{
+ struct hdcapm_dev *dev =
+ container_of(v4l2_dev, struct hdcapm_dev, v4l2_dev);
+
+ v4l2_device_unregister_subdev(dev->sd);
+
+ // TODO: Do I need this?
+ //v4l2_ctrl_handler_free(&dev->v4l2_ctrl_hdl);
+
+ v4l2_device_unregister(&dev->v4l2_dev);
+}
+
+/* sub-device events are pushed with v4l2_subdev_notify() and
+ * v4l2_subdev_notify_enent(). They eventually make their way here.
+ * The bridge then forwards those events via v4l2_event_queue()
+ * to the v4l2_device, and so eventually they end up in userspace.
+ */
+static void hdcapm_notify(struct v4l2_subdev *sd,
+ unsigned int notification, void *arg)
+{
+ struct hdcapm_dev *dev = container_of(sd->v4l2_dev,
+ struct hdcapm_dev, v4l2_dev);
+ struct mst3367_source_detect *mst3367;
+
+ switch (notification) {
+ case MST3367_SOURCE_DETECT:
+ mst3367 = (struct mst3367_source_detect *)arg;
+ break;
+ case V4L2_DEVICE_NOTIFY_EVENT:
+ /*
+ * Userspace can monitor for these with:
+ * v4l2-ctl -d /dev/video2 --wait-for-event=source_change=0
+ */
+ v4l2_event_queue(dev->v4l_device, arg);
+ break;
+ default:
+ v4l2_err(sd, "unhandled notification = 0x%x\n", notification);
+ break;
+ }
+}
+
+static int hdcapm_usb_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct hdcapm_dev *dev;
+ struct hdcapm_buffer *buf;
+ struct usb_device *udev;
+ struct i2c_board_info mst3367_info;
+ int ret, i;
+
+ udev = interface_to_usbdev(intf);
+
+ if (intf->altsetting->desc.bInterfaceNumber != 0)
+ return -ENODEV;
+
+ dev_dbg(&udev->dev,
+ "%s() vendor id 0x%x device id 0x%x\n", __func__,
+ le16_to_cpu(udev->descriptor.idVendor),
+ le16_to_cpu(udev->descriptor.idProduct));
+
+ /* Ensure the bus speed is 480Mbps. */
+ if (udev->speed != USB_SPEED_HIGH) {
+ dev_err(&intf->dev,
+ "Device must be connected to a USB 2.0 port (480Mbps).\n");
+ return -ENODEV;
+ }
+
+ dev = devm_kzalloc(&udev->dev, sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ dev->xferbuf = devm_kzalloc(&udev->dev, XFERBUF_SIZE, GFP_KERNEL);
+ if (!dev->xferbuf)
+ return -ENOMEM;
+
+ dev->stats = devm_kzalloc(&udev->dev, sizeof(struct hdcapm_statistics),
+ GFP_KERNEL);
+ if (!dev->stats)
+ return -ENOMEM;
+
+ strlcpy(dev->name, "Startech HDCAPM Encoder", sizeof(dev->name));
+ dev->state = STATE_STOPPED;
+ dev->udev = udev;
+
+ mutex_init(&dev->lock);
+ mutex_init(&dev->dmaqueue_lock);
+ INIT_LIST_HEAD(&dev->list_buf_free);
+ INIT_LIST_HEAD(&dev->list_buf_used);
+ init_waitqueue_head(&dev->wait_read);
+ usb_set_intfdata(intf, dev);
+
+ /* Register the I2C buses. */
+ if (hdcapm_i2c_register(dev, &dev->i2cbus[0], 0) < 0) {
+ dev_err(&intf->dev, "failed to register i2cbus 0\n");
+ return -EINVAL;
+ }
+
+ /* We're not using bus#1, it has the eeprom on it. Remove this or leave
+ * for future developers with future products?
+ */
+ if (hdcapm_i2c_register(dev, &dev->i2cbus[1], 1) < 0) {
+ dev_err(&intf->dev, "failed to register i2cbus 1\n");
+ ret = -EINVAL;
+ goto fail3;
+ }
+#if ONETIME_FW_LOAD
+ /* Register the compression codec (it does both audio and video). */
+ if (hdcapm_compressor_register(dev) < 0) {
+ dev_err(&intf->dev, "failed to register compressor\n");
+ ret = -EINVAL;
+ goto fail4;
+ }
+#else
+ hdcapm_compressor_init_gpios(dev);
+#endif
+
+ /* Attach HDMI receiver */
+ ret = v4l2_device_register(&intf->dev, &dev->v4l2_dev);
+ if (ret < 0) {
+ dev_err(&intf->dev, "v4l2_device_register failed\n");
+ ret = -EINVAL;
+ goto fail5;
+ }
+
+ dev->v4l2_dev.release = hdcapm_usb_v4l2_release;
+ dev->v4l2_dev.notify = hdcapm_notify;
+
+ /* Configure a sub-device attachment for the HDMI receiver. */
+ memset(&mst3367_info, 0, sizeof(struct i2c_board_info));
+ strlcpy(mst3367_info.type, "mst3367", I2C_NAME_SIZE);
+
+ mst3367_info.addr = 0x9c >> 1;
+
+ dev->sd = v4l2_i2c_new_subdev_board(&dev->v4l2_dev,
+ &dev->i2cbus[0].i2c_adap,
+ &mst3367_info, NULL);
+ if (!dev->sd) {
+ dev_err(&intf->dev,
+ "failed to find or load a driver for the MST3367\n");
+ ret = -EINVAL;
+ goto fail6;
+ }
+
+ /* Power on the HDMI receiver, assuming it needs it. */
+ v4l2_subdev_call(dev->sd, core, s_power, 1);
+
+ /* We need some buffers to hold user payload. */
+ for (i = 0; i < buffer_count; i++) {
+ buf = hdcapm_buffer_alloc(dev, i, buffer_size);
+ if (!buf) {
+ dev_err(&intf->dev,
+ "failed to allocate a user buffer\n");
+ ret = -ENOMEM;
+ goto fail8;
+ }
+
+ mutex_lock(&dev->dmaqueue_lock);
+ list_add_tail(&buf->list, &dev->list_buf_free);
+ mutex_unlock(&dev->dmaqueue_lock);
+ }
+
+ /* Formally register the V4L2 interfaces. */
+ if (hdcapm_video_register(dev) < 0) {
+ dev_err(&intf->dev, "failed to register video device\n");
+ ret = -EINVAL;
+ goto fail8;
+ }
+
+ /* Bring up a kernel thread to manage the HDMI frontend and run
+ * the data pump.
+ */
+ dev->kthread = kthread_run(hdcapm_thread_function, dev, "hdcapm hdmi");
+ if (!dev->kthread) {
+ dev_err(&intf->dev, "failed to create hdmi kernel thread\n");
+ ret = -EINVAL;
+ goto fail9;
+ }
+
+ /* Finish the rest of the hardware configuration. */
+ mutex_lock(&devlist);
+ list_add_tail(&dev->devlist, &hdcapm_devlist);
+ devlist_count++;
+ mutex_unlock(&devlist);
+
+ dev_info(&intf->dev, "Registered device '%s'\n", dev->name);
+
+ return 0; /* Success */
+
+fail9:
+ hdcapm_video_unregister(dev);
+fail8:
+ /* Put all the buffers back on the free list, then dealloc them. */
+ hdcapm_buffers_move_all(dev, &dev->list_buf_free, &dev->list_buf_used);
+ hdcapm_buffers_free_all(dev, &dev->list_buf_free);
+fail6:
+ v4l2_device_unregister(&dev->v4l2_dev);
+fail5:
+#if ONETIME_FW_LOAD
+ hdcapm_compressor_unregister(dev);
+fail4:
+#endif
+ hdcapm_i2c_unregister(dev, &dev->i2cbus[1]);
+fail3:
+ hdcapm_i2c_unregister(dev, &dev->i2cbus[0]);
+
+ return ret;
+}
+
+static void hdcapm_usb_disconnect(struct usb_interface *intf)
+{
+ struct hdcapm_dev *dev = usb_get_intfdata(intf);
+ int i;
+
+ v4l2_dbg(1, hdcapm_debug, dev->sd, "%s()\n", __func__);
+
+ if (dev->kthread) {
+ kthread_stop(dev->kthread);
+ dev->kthread = NULL;
+
+ i = 0;
+ while (dev->thread_active) {
+ msleep(500);
+ if (i++ > 24)
+ break;
+ }
+ }
+
+ hdcapm_video_unregister(dev);
+
+#if ONETIME_FW_LOAD
+ /* Unregister the compression codec. */
+ hdcapm_compressor_unregister(dev);
+#endif
+
+ /* Unregister any I2C buses. */
+ hdcapm_i2c_unregister(dev, &dev->i2cbus[1]);
+ hdcapm_i2c_unregister(dev, &dev->i2cbus[0]);
+
+ /* Put all the buffers back on the free list, the dealloc them. */
+ hdcapm_buffers_move_all(dev, &dev->list_buf_free, &dev->list_buf_used);
+ hdcapm_buffers_free_all(dev, &dev->list_buf_free);
+
+ mutex_lock(&devlist);
+ list_del(&dev->devlist);
+ mutex_unlock(&devlist);
+}
+
+static int hdcapm_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct hdcapm_dev *dev = usb_get_intfdata(intf);
+
+ if (!dev)
+ return 0;
+
+ /* TODO: Power off the HDMI receiver? */
+
+ pr_info(KBUILD_MODNAME ": USB is suspend\n");
+
+ return 0;
+}
+
+static int hdcapm_resume(struct usb_interface *intf)
+{
+ struct hdcapm_dev *dev = usb_get_intfdata(intf);
+
+ if (!dev)
+ return 0;
+
+ /* TODO: Power on the HDMI receiver? */
+
+ return 0;
+}
+
+struct usb_device_id hdcapm_usb_id_table[] = {
+ { USB_DEVICE(0x1164, 0x75a7), .driver_info = HDCAPM_CARD_REV1 },
+ { /* -- end -- */ },
+};
+MODULE_DEVICE_TABLE(usb, hdcapm_usb_id_table);
+
+static struct usb_driver hdcapm_usb_driver = {
+ .name = KBUILD_MODNAME,
+ .probe = hdcapm_usb_probe,
+ .disconnect = hdcapm_usb_disconnect,
+ .id_table = hdcapm_usb_id_table,
+ .suspend = hdcapm_suspend,
+ .resume = hdcapm_resume,
+ .reset_resume = hdcapm_resume,
+};
+
+module_usb_driver(hdcapm_usb_driver);
+
+MODULE_DESCRIPTION("Driver for StarTech USB2HDCAPM USB capture product");
+MODULE_AUTHOR("Steven Toth <[email protected]>");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("0.0.1");
diff --git a/drivers/media/usb/hdcapm/hdcapm-i2c.c b/drivers/media/usb/hdcapm/hdcapm-i2c.c
new file mode 100644
index 000000000000..6ef74459948b
--- /dev/null
+++ b/drivers/media/usb/hdcapm/hdcapm-i2c.c
@@ -0,0 +1,332 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for the Startech USB2HDCAPM USB capture device
+ *
+ * Copyright (c) 2017 Steven Toth <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *
+ * GNU General Public License for more details.
+ */
+
+#include "hdcapm.h"
+
+#define GPIO_SCL BIT(14)
+#define GPIO_SDA BIT(15)
+
+static unsigned int i2c_udelay = 5;
+module_param(i2c_udelay, int, 0644);
+MODULE_PARM_DESC(i2c_udelay,
+ "i2c delay at insmod time, in usecs (should be 5 or higher). Lower value means higher bus speed.");
+
+/* GPIO bit-banged bus */
+static void hdcapm_bit_setscl(void *data, int state)
+{
+ struct hdcapm_i2c_bus *bus = data;
+ struct hdcapm_dev *dev = bus->dev;
+
+ if (state)
+ hdcapm_clr32(dev, REG_GPIO_OE, GPIO_SCL);
+ else
+ hdcapm_set32(dev, REG_GPIO_OE, GPIO_SCL);
+}
+
+static void hdcapm_bit_setsda(void *data, int state)
+{
+ struct hdcapm_i2c_bus *bus = data;
+ struct hdcapm_dev *dev = bus->dev;
+
+ if (state)
+ hdcapm_clr32(dev, REG_GPIO_OE, GPIO_SDA);
+ else
+ hdcapm_set32(dev, REG_GPIO_OE, GPIO_SDA);
+}
+
+static int hdcapm_bit_getscl(void *data)
+{
+ struct hdcapm_i2c_bus *bus = data;
+ struct hdcapm_dev *dev = bus->dev;
+ u32 val;
+
+ hdcapm_read32(dev, REG_GPIO_DATA_RD, &val);
+
+ return val & GPIO_SCL ? 1 : 0;
+}
+
+static int hdcapm_bit_getsda(void *data)
+{
+ struct hdcapm_i2c_bus *bus = data;
+ struct hdcapm_dev *dev = bus->dev;
+ u32 val;
+
+ hdcapm_read32(dev, REG_GPIO_DATA_RD, &val);
+
+ return val & GPIO_SDA ? 1 : 0;
+}
+
+/* ----------------------------------------------------------------------- */
+
+static const struct i2c_algo_bit_data hdcapm_i2c1_algo_template = {
+ .setsda = hdcapm_bit_setsda,
+ .setscl = hdcapm_bit_setscl,
+ .getsda = hdcapm_bit_getsda,
+ .getscl = hdcapm_bit_getscl,
+ .udelay = 16,
+ .timeout = 200,
+};
+
+/* Internal I2C Bus */
+static int i2c_writeread(struct i2c_adapter *i2c_adap,
+ const struct i2c_msg *msg, int joined_rlen)
+{
+/*
+ * EP4 Host -> 01 01 01 00 04 05 00 00 55 00 00 00
+ * - usbwrite(REG_504, 0x55)
+ * EP4 Host -> 01 01 01 00 00 05 00 00 09 27 00 80
+ * - usbwrite(REG_500, 0x80002709);
+ * EP4 Host -> 01 00 01 00 00 05 00 00
+ * EP3 Host <- 09 27 00 00
+ * - 2709 = usbread(REG_0500);
+ * EP4 Host -> 01 00 01 00 0c 05 00 00
+ * EP3 Host <- 03 00 00 00
+ * - 03 = usbread(REG_050c);
+ */
+ struct hdcapm_i2c_bus *bus = i2c_adap->algo_data;
+ struct hdcapm_dev *dev = bus->dev;
+ struct i2c_msg *nextmsg = (struct i2c_msg *)(msg + 1);
+ u32 val;
+ int ret;
+ int safety = 32;
+
+ v4l2_dbg(2, hdcapm_debug, dev->sd,
+ "%s(addr=0x%x, reg=0x%x, len=%d)\n", __func__,
+ msg->addr, msg->buf[0], msg->len);
+
+ ret = hdcapm_write32(dev, REG_I2C_W_BUF, msg->buf[0]);
+
+ /* Write one and read one byte? */
+ val = (1 << 31);
+ val |= 9;
+ val |= (msg->addr << 7);
+ ret = hdcapm_write32(dev, REG_I2C_XACT, val);
+
+ /* I2C engine busy? */
+ val = (1 << 31);
+ while (val & 0x80000000) {
+ /* Check bit31 has cleared? */
+ ret = hdcapm_read32(dev, REG_I2C_XACT, &val);
+ if (safety-- == 0)
+ break;
+ }
+ if (safety == 0) {
+ v4l2_err(dev->sd, ": stuck i2c bit, aborting.\n");
+ return 0;
+ }
+
+ /* Read i2c result */
+ ret = hdcapm_read32(dev, REG_I2C_R_BUF, &val);
+ nextmsg->buf[0] = val & 0x000000ff;
+
+ v4l2_dbg(2, hdcapm_debug, dev->sd,
+ "%s(addr=0x%x, reg = 0x%x) = 0x%02x\n", __func__,
+ msg->addr, msg->buf[0], nextmsg->buf[0]);
+ return 1;
+}
+
+static int i2c_write(struct i2c_adapter *i2c_adap, const struct i2c_msg *msg,
+ int joined)
+{
+ struct hdcapm_i2c_bus *bus = i2c_adap->algo_data;
+ struct hdcapm_dev *dev = bus->dev;
+ u32 val;
+ int ret, i;
+
+ /* Position each data byte into the u32, for a single strobe
+ * into the write buffer.
+ */
+ val = 0;
+ for (i = msg->len; i > 0; i--) {
+ val <<= 8;
+ val |= msg->buf[i - 1];
+ }
+
+ ret = hdcapm_write32(dev, REG_I2C_W_BUF, val);
+
+ /* Write N bytes, no read */
+ val = (1 << 31);
+ val |= msg->len;
+ val |= (msg->addr << 7);
+ ret = hdcapm_write32(dev, REG_I2C_XACT, val);
+
+ return 1;
+}
+
+static int i2c_xfer(struct i2c_adapter *i2c_adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct hdcapm_i2c_bus *bus = i2c_adap->algo_data;
+ struct hdcapm_dev *dev = bus->dev;
+ int ret = 0;
+ int i;
+
+ v4l2_dbg(2, hdcapm_debug, dev->sd,
+ "%s(num = %d)\n", __func__, num);
+
+ for (i = 0; i < num; i++) {
+ v4l2_dbg(4, hdcapm_debug, dev->sd,
+ "%s(num = %d) addr = 0x%02x len = 0x%x\n",
+ __func__, num, msgs[i].addr, msgs[i].len);
+ if (msgs[i].flags & I2C_M_RD) {
+ /* do nothing */
+ } else if (i + 1 < num && (msgs[i + 1].flags & I2C_M_RD) &&
+ msgs[i].addr == msgs[i + 1].addr) {
+ /* write then read from same address */
+ ret = i2c_writeread(i2c_adap, &msgs[i],
+ msgs[i + 1].len);
+ if (ret < 0)
+ goto error;
+ i++;
+
+ } else {
+ /* Write */
+ ret = i2c_write(i2c_adap, &msgs[i], 0);
+ }
+ if (ret < 0)
+ goto error;
+ }
+ return num;
+
+error:
+ return ret;
+}
+
+static u32 hdcapm0_functionality(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_SMBUS_BYTE_DATA;
+}
+
+static const struct i2c_algorithm hdcapm_i2c0_algo_template = {
+ .master_xfer = i2c_xfer,
+ .functionality = hdcapm0_functionality,
+};
+
+static const struct i2c_adapter hdcapm_i2c0_adap_template = {
+ .name = "hdcapm internal",
+ .owner = THIS_MODULE,
+ .algo = &hdcapm_i2c0_algo_template,
+};
+
+static struct i2c_client hdcapm_i2c0_client_template = {
+ .name = "hdcapm internal",
+};
+
+static int i2c_readreg8(struct hdcapm_i2c_bus *bus, u8 addr, u8 reg, u8 *val)
+{
+ int ret;
+ u8 b0[] = { reg };
+ u8 b1[] = { 0 };
+
+ struct i2c_msg msg[] = {
+ { .addr = addr, .flags = 0, .buf = b0, .len = 1 },
+ { .addr = addr, .flags = I2C_M_RD, .buf = b1, .len = 1 } };
+
+ ret = i2c_transfer(&bus->i2c_adap, msg, 2);
+ if (ret != 2)
+ return 0;
+
+ *val = b1[0];
+
+ return 2;
+}
+
+static char *i2c_devs[128] = {
+ [0x66 >> 1] = "MST3367?",
+ [0x88 >> 1] = "MST3367?",
+ [0x94 >> 1] = "MST3367?",
+ [0x9c >> 1] = "MST3367?",
+ [0xa2 >> 1] = "EEPROM",
+};
+
+static void do_i2c_scan(struct hdcapm_i2c_bus *bus)
+{
+ struct hdcapm_dev *dev = bus->dev;
+ int a, ret;
+ u8 val;
+
+ for (a = 0; a < 128; a++) {
+ ret = i2c_readreg8(bus, a, 0x00, &val);
+ if (ret == 2) {
+ v4l2_info(dev->sd,
+ "%s: i2c scan: found device @ 0x%x [%s]\n",
+ __func__, a << 1,
+ i2c_devs[a] ? i2c_devs[a] : "???");
+ }
+ }
+}
+
+int hdcapm_i2c_register(struct hdcapm_dev *dev,
+ struct hdcapm_i2c_bus *bus, int nr)
+{
+ bus->nr = nr;
+ bus->dev = dev;
+
+ v4l2_dbg(1, hdcapm_debug, dev->sd,
+ "%s() registering I2C Bus#%d\n", __func__, bus->nr);
+
+ if (bus->nr == 0) {
+ bus->i2c_adap = hdcapm_i2c0_adap_template;
+ bus->i2c_client = hdcapm_i2c0_client_template;
+
+ bus->i2c_adap.dev.parent = &dev->udev->dev;
+ strlcpy(bus->i2c_adap.name, KBUILD_MODNAME,
+ sizeof(bus->i2c_adap.name));
+
+ bus->i2c_adap.algo_data = bus;
+ i2c_set_adapdata(&bus->i2c_adap, bus);
+ i2c_add_adapter(&bus->i2c_adap);
+
+ bus->i2c_client.adapter = &bus->i2c_adap;
+
+ } else if (bus->nr == 1) {
+ bus->i2c_algo = hdcapm_i2c1_algo_template;
+
+ bus->i2c_adap.dev.parent = &dev->udev->dev;
+ strlcpy(bus->i2c_adap.name, KBUILD_MODNAME,
+ sizeof(bus->i2c_adap.name));
+ bus->i2c_adap.owner = THIS_MODULE;
+ bus->i2c_algo.udelay = i2c_udelay;
+ bus->i2c_algo.data = bus;
+ i2c_set_adapdata(&bus->i2c_adap, bus);
+ bus->i2c_adap.algo_data = &bus->i2c_algo;
+ bus->i2c_client.adapter = &bus->i2c_adap;
+ strlcpy(bus->i2c_client.name, "hdcapm gpio", I2C_NAME_SIZE);
+
+ hdcapm_bit_setscl(bus, 1);
+ hdcapm_bit_setsda(bus, 1);
+
+ i2c_bit_add_bus(&bus->i2c_adap);
+
+ } else {
+ WARN_ON(1);
+ }
+
+ if (hdcapm_i2c_scan && bus->nr == 1)
+ do_i2c_scan(bus);
+
+ return 0;
+}
+
+void hdcapm_i2c_unregister(struct hdcapm_dev *dev, struct hdcapm_i2c_bus *bus)
+{
+ v4l2_dbg(1, hdcapm_debug, dev->sd,
+ "%s() unregistering I2C Bus#%d\n", __func__, bus->nr);
+
+ i2c_del_adapter(&bus->i2c_adap);
+}
diff --git a/drivers/media/usb/hdcapm/hdcapm-reg.h b/drivers/media/usb/hdcapm/hdcapm-reg.h
new file mode 100644
index 000000000000..8f8487840ac5
--- /dev/null
+++ b/drivers/media/usb/hdcapm/hdcapm-reg.h
@@ -0,0 +1,111 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Driver for the Startech USB2HDCAPM USB capture device
+ *
+ * Copyright (c) 2017 Steven Toth <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *
+ * GNU General Public License for more details.
+ */
+
+/*
+ * idle-no-hdmi-connected.tdc -- Nothing else of consequence in the
+ * file. It's worth noting that when you run the graphedit tool under
+ * windows, open up the Analog Capture property page, switch to the
+ * Driver Properties view, and enable "PRINT DEBUG" option, debug view
+ * shows the following driver activity:
+ * "MST3367_HDMI_MODE_DETECT(0x55 = 0x03)".
+ *
+ * Record 4:
+ *
+ * EP4 Host -> 01 01 01 00 04 05 00 00 55 00 00 00
+ * - usbwrite(REG_504, 0x55)
+ * EP4 Host -> 01 01 01 00 00 05 00 00 09 27 00 80
+ * - usbwrite(REG_500, 0x80002709);
+ * EP4 Host -> 01 00 01 00 00 05 00 00
+ * EP3 Host <- 09 27 00 00
+ * - 2709 = usbread(REG_0500);
+ * EP4 Host -> 01 00 01 00 0c 05 00 00
+ * EP3 Host <- 03 00 00 00
+ * - 03 = usbread(REG_050c);
+ *
+ * In light of the debug view findings, I conclude:
+ * Writes to 504 establist a I2C write to device 0x55.
+ * Writes to 500 are...... what?
+ * reads for 50c are reads from the i2c bus answer.
+ *
+ * 80 00 27 09 =
+ * 1000 0000 | 0000 0000 | 0010 0111 | 0000 1001
+ *
+ * 001 = 1 rx/tx length?
+ * 001 = 1
+ * 01001110 = 0x4e device address or register of MST3367?
+ * 10011100 = 0x9c device address or register of MST3367?
+ * .... tv schematic suggests this is likely correct.
+ *
+ */
+
+/* Bit 13: Cleared during initialization, stall h/w
+ */
+#define REG_0000 0x000
+
+/* Register is read but never written to.
+ * Hardware version / chip id?
+ */
+#define REG_0038 0x038
+
+/* Bit 0,3-7: Unknown
+ * 1: Low when audio output is required, high when disabled.
+ * 2: Low when video output is required, high when disabled.
+ */
+#define REG_0050 0x050
+
+#define REG_I2C_XACT 0x500
+#define REG_I2C_W_BUF 0x504
+#define REG_I2C_R_BUF 0x50c
+
+/* driver-install.csv shows toggling of register between values:
+ * Bits 15.. .. 0
+ * 19 0E -- 0001 1001 0000 1110
+ * 59 0E -- 0101 1001 0000 1110
+ * 99 0E -- 1001 1001 0000 1110
+ * D9 0E -- 1101 1001 0000 1110
+ * Suggesting bits 15/14 are a bitbanged I2C bus.
+ * We'll assume 15: SDA, 14: SCL
+ */
+#define REG_GPIO_OE 0x610
+#define REG_GPIO_DATA_WR 0x614
+#define REG_GPIO_DATA_RD 0x618
+
+#define REG_06B0 0x6b0
+
+#define REG_FW_CMD_BUSY 0x6cc
+
+/* Valid args are 0 - 10 */
+#define REG_FW_CMD_ARG(n) (0x6f8 - ((n) * 4))
+
+/* A command 'type' or identifier is written to this register,
+ * after the type specifics args have already been written.
+ */
+#define REG_FW_CMD_EXECUTE 0x6fc
+
+#define REG_081C 0x81c
+#define REG_0820 0x820
+#define REG_0824 0x824
+#define REG_0828 0x828
+#define REG_082C 0x82c
+#define REG_0830 0x830
+#define REG_0834 0x834
+#define REG_0838 0x838
+#define REG_083C 0x83c
+#define REG_0840 0x840
+
+#define REG_0B78 0xb78
diff --git a/drivers/media/usb/hdcapm/hdcapm-video.c b/drivers/media/usb/hdcapm/hdcapm-video.c
new file mode 100644
index 000000000000..47d19361f54a
--- /dev/null
+++ b/drivers/media/usb/hdcapm/hdcapm-video.c
@@ -0,0 +1,665 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for the Startech USB2HDCAPM USB capture device
+ *
+ * Copyright (c) 2017 Steven Toth <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *
+ * GNU General Public License for more details.
+ */
+
+#include "hdcapm.h"
+
+#define ENCODER_MIN_BITRATE 2000000
+#define ENCODER_MAX_BITRATE 20000000
+#define ENCODER_DEF_BITRATE ENCODER_MAX_BITRATE
+
+#define ENCODER_MIN_GOP_SIZE 1
+#define ENCODER_MAX_GOP_SIZE 60
+#define ENCODER_DEF_GOP_SIZE ENCODER_MAX_GOP_SIZE
+
+static int s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct hdcapm_dev *dev = container_of(ctrl->handler,
+ struct hdcapm_dev, ctrl_handler);
+ struct hdcapm_encoder_parameters *p = &dev->encoder_parameters;
+ int ret = 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_MPEG_AUDIO_MUTE:
+ v4l2_dbg(1, hdcapm_debug, dev->sd,
+ "%s(V4L2_CID_MPEG_AUDIO_MUTE) = %d\n",
+ __func__, ctrl->val);
+ p->audio_mute = ctrl->val;
+ break;
+ case V4L2_CID_BRIGHTNESS:
+ v4l2_dbg(1, hdcapm_debug, dev->sd,
+ "%s(V4L2_CID_BRIGHTNESS) = %d\n", __func__, ctrl->val);
+ p->brightness = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_BITRATE:
+ v4l2_dbg(1, hdcapm_debug, dev->sd,
+ "%s(V4L2_CID_MPEG_VIDEO_BITRATE) = %d\n",
+ __func__, ctrl->val);
+ p->bitrate_bps = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_BITRATE_PEAK:
+ v4l2_dbg(1, hdcapm_debug, dev->sd,
+ "%s(V4L2_CID_MPEG_BITRATE_PEAK) = %d\n",
+ __func__, ctrl->val);
+ p->bitrate_peak_bps = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_BITRATE_MODE:
+ switch (ctrl->val) {
+ case V4L2_MPEG_VIDEO_BITRATE_MODE_VBR:
+ p->h264_mode = 1;
+ break;
+ case V4L2_MPEG_VIDEO_BITRATE_MODE_CBR:
+ p->h264_mode = 0;
+ break;
+ default:
+ v4l2_err(dev->sd,
+ "failed to handle ctrl->id 0x%x, value = %d\n",
+ ctrl->id, ctrl->val);
+ ret = -EINVAL;
+ }
+ v4l2_dbg(1, hdcapm_debug, dev->sd,
+ "%s(V4L2_CID_MPEG_VIDEO_BITRATE_MODE) = %d\n",
+ __func__, ctrl->val);
+ break;
+ case V4L2_CID_MPEG_VIDEO_GOP_SIZE:
+ v4l2_dbg(1, hdcapm_debug, dev->sd,
+ "%s(V4L2_CID_MPEG_VIDEO_GOP_SIZE) = %d\n",
+ __func__, ctrl->val);
+ p->gop_size = ctrl->val;
+
+ /* If we're in VBR mode GOP 1 looks bad,
+ * force a change to CBR.
+ */
+ if (p->gop_size == 1 && p->h264_mode == 1) {
+ v4l2_info(dev->sd,
+ "GOP size 1 produces poor quality, switching from VBR to CBR\n");
+ p->h264_mode = 0;
+ }
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_LEVEL:
+ if (ctrl->val > V4L2_MPEG_VIDEO_H264_LEVEL_5_1) {
+ v4l2_err(dev->sd,
+ "failed to handle ctrl->id 0x%x, value = %d\n",
+ ctrl->id, ctrl->val);
+ ret = -EINVAL;
+ }
+ p->h264_level = ctrl->val;
+ v4l2_dbg(1, hdcapm_debug, dev->sd,
+ "%s(V4L2_CID_MPEG_VIDEO_H264_LEVEL) = %d\n",
+ __func__, ctrl->val);
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE:
+ switch (ctrl->val) {
+ case V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC:
+ p->h264_entropy_mode = 1;
+ break;
+ case V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CAVLC:
+ p->h264_entropy_mode = 0;
+ break;
+ default:
+ v4l2_err(dev->sd,
+ "failed to handle ctrl->id 0x%x, value = %d\n",
+ ctrl->id, ctrl->val);
+ ret = -EINVAL;
+ }
+ v4l2_dbg(1, hdcapm_debug, dev->sd,
+ "%s(V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE) = %d\n",
+ __func__, ctrl->val);
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_PROFILE:
+ switch (ctrl->val) {
+ case V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE:
+ p->h264_profile = 0;
+ break;
+ case V4L2_MPEG_VIDEO_H264_PROFILE_MAIN:
+ p->h264_profile = 1;
+ break;
+ case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH:
+ p->h264_profile = 2;
+ break;
+ default:
+ v4l2_err(dev->sd,
+ "failed to handle ctrl->id 0x%x, value = %d\n",
+ ctrl->id, ctrl->val);
+ ret = -EINVAL;
+ }
+ v4l2_dbg(1, hdcapm_debug, dev->sd,
+ "%s(V4L2_CID_MPEG_VIDEO_H264_PROFILE) = %d\n",
+ __func__, ctrl->val);
+ break;
+ case V4L2_CID_MPEG_STREAM_TYPE:
+ v4l2_dbg(1, hdcapm_debug, dev->sd,
+ "%s(V4L2_CID_MPEG_STREAM_TYPE) = %d\n",
+ __func__, ctrl->val);
+ break;
+ default:
+ v4l2_err(dev->sd,
+ "failed to handle ctrl->id 0x%x, value = %d\n",
+ ctrl->id, ctrl->val);
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static const struct v4l2_ctrl_ops ctrl_ops = {
+ .s_ctrl = s_ctrl,
+};
+
+static int vidioc_enum_input(struct file *file, void *priv_fh,
+ struct v4l2_input *i)
+{
+ struct hdcapm_fh *fh = file->private_data;
+ struct hdcapm_dev *dev = fh->dev;
+
+ if (i->index > 0)
+ return -EINVAL;
+
+ snprintf(i->name, sizeof(i->name), "HDMI / DVI");
+ i->type = V4L2_INPUT_TYPE_CAMERA;
+ i->capabilities = V4L2_IN_CAP_DV_TIMINGS;
+
+ return v4l2_subdev_call(dev->sd, video, g_input_status, &i->status);
+}
+
+static int vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct hdcapm_fh *fh = file->private_data;
+ struct hdcapm_dev *dev = fh->dev;
+
+ strcpy(cap->driver, KBUILD_MODNAME);
+ strlcpy(cap->card, dev->name, sizeof(cap->card));
+ usb_make_path(dev->udev, cap->bus_info, sizeof(cap->bus_info));
+
+ cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE;
+ cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+
+ return 0;
+}
+
+static int vidioc_log_status(struct file *file, void *priv)
+{
+ struct hdcapm_fh *fh = file->private_data;
+ struct hdcapm_dev *dev = fh->dev;
+ struct hdcapm_statistics *s = dev->stats;
+ u64 q_used_bytes, q_used_items;
+ struct hdcapm_encoder_parameters *p = &dev->encoder_parameters;
+
+ v4l2_info(dev->sd, "device_state: %s\n",
+ dev->state == STATE_START ? "START" :
+ dev->state == STATE_STARTED ? "STARTED" :
+ dev->state == STATE_STOP ? "STOP" :
+ dev->state == STATE_STOPPED ? "STOPPED" : "UNDEFINED");
+
+ v4l2_info(dev->sd, "device_context: 0x%p\n", dev);
+ v4l2_info(dev->sd, "codec_buffers_received: %llu\n",
+ s->codec_buffers_received);
+ v4l2_info(dev->sd, "codec_bytes_received: %llu\n",
+ s->codec_bytes_received);
+ v4l2_info(dev->sd, "codec_ts_not_yet_ready: %llu\n",
+ s->codec_ts_not_yet_ready);
+ v4l2_info(dev->sd, "buffer_overrun: %llu\n", s->buffer_overrun);
+
+ if (p->output_width && p->output_height)
+ v4l2_info(dev->sd, "video_scaler_output: %dx%d\n",
+ p->output_width, p->output_height);
+ else
+ v4l2_info(dev->sd, "video_scaler_output: [native 1:1]\n");
+
+ if (hdcapm_buffer_used_queue_stats(dev,
+ &q_used_bytes, &q_used_items) == 0) {
+ v4l2_info(dev->sd, "q_used_bytes: %llu\n",
+ q_used_bytes);
+ v4l2_info(dev->sd, "q_used_items: %llu\n",
+ q_used_items);
+ }
+
+ return v4l2_subdev_call(dev->sd, core, log_status);
+}
+
+static int vidioc_g_input(struct file *file, void *priv, unsigned int *i)
+{
+ *i = 0;
+
+ return 0;
+}
+
+static int vidioc_s_input(struct file *file, void *priv, unsigned int i)
+{
+ if (i > 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int vidioc_g_dv_timings(struct file *file, void *priv,
+ struct v4l2_dv_timings *timings)
+{
+ struct hdcapm_fh *fh = file->private_data;
+ struct hdcapm_dev *dev = fh->dev;
+
+ return v4l2_subdev_call(dev->sd, video, g_dv_timings, timings);
+}
+
+static int vidioc_enum_dv_timings(struct file *file, void *priv,
+ struct v4l2_enum_dv_timings *timings)
+{
+ struct hdcapm_fh *fh = file->private_data;
+ struct hdcapm_dev *dev = fh->dev;
+
+ timings->pad = 0;
+ timings->reserved[0] = 0;
+ timings->reserved[1] = 0;
+
+ return v4l2_subdev_call(dev->sd, pad, enum_dv_timings, timings);
+}
+
+static int vidioc_dv_timings_cap(struct file *file, void *priv,
+ struct v4l2_dv_timings_cap *cap)
+{
+ struct hdcapm_fh *fh = file->private_data;
+ struct hdcapm_dev *dev = fh->dev;
+
+ cap->pad = 0;
+
+ return v4l2_subdev_call(dev->sd, pad, dv_timings_cap, cap);
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ if (f->index != 0)
+ return -EINVAL;
+
+ strlcpy(f->description, "MPEG", sizeof(f->description));
+ f->pixelformat = V4L2_PIX_FMT_MPEG;
+ f->flags = V4L2_FMT_FLAG_COMPRESSED;
+ return 0;
+}
+
+static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct hdcapm_fh *fh = file->private_data;
+ struct hdcapm_dev *dev = fh->dev;
+ struct hdcapm_encoder_parameters *p = &dev->encoder_parameters;
+ struct v4l2_dv_timings timings;
+
+ if (v4l2_subdev_call(dev->sd, video, g_dv_timings, &timings) < 0)
+ return -EINVAL;
+
+ f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG;
+ f->fmt.pix.bytesperline = 0;
+ f->fmt.pix.sizeimage = 188 * 312;
+ f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+
+ if (p->output_width)
+ f->fmt.pix.width = p->output_width;
+ else
+ f->fmt.pix.width = timings.bt.width;
+
+ if (p->output_height)
+ f->fmt.pix.height = p->output_height;
+ else
+ f->fmt.pix.height = timings.bt.width;
+
+ f->fmt.pix.height = timings.bt.height;
+ f->fmt.pix.field = V4L2_FIELD_NONE;
+
+ return 0;
+}
+
+static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct hdcapm_fh *fh = file->private_data;
+ struct hdcapm_dev *dev = fh->dev;
+ struct hdcapm_encoder_parameters *p = &dev->encoder_parameters;
+ struct v4l2_dv_timings timings;
+
+ if (v4l2_subdev_call(dev->sd, video, g_dv_timings, &timings) < 0)
+ return -EINVAL;
+
+ /* Its not clear to me if the input resolution changes, if we're
+ * required to preserve the users requested width and height, or
+ * default it back to 1:1 with the input signal.
+ */
+ p->output_width = f->fmt.pix.width;
+ p->output_height = f->fmt.pix.height;
+
+ f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG;
+ f->fmt.pix.bytesperline = 0;
+ f->fmt.pix.sizeimage = 188 * 312;
+ f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+ f->fmt.pix.width = timings.bt.width;
+ f->fmt.pix.height = timings.bt.height;
+ f->fmt.pix.field = V4L2_FIELD_NONE;
+
+ return 0;
+}
+
+static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ return vidioc_s_fmt_vid_cap(file, priv, f);
+}
+
+static int vidioc_subscribe_event(struct v4l2_fh *fh,
+ const struct v4l2_event_subscription *sub)
+{
+ switch (sub->type) {
+ case V4L2_EVENT_SOURCE_CHANGE:
+ return v4l2_event_subscribe(fh, sub, 16, NULL);
+ default:
+ v4l2_warn(fh->vdev->v4l2_dev,
+ "event sub->type = 0x%x (UNKNOWN)\n", sub->type);
+ }
+ return v4l2_ctrl_subscribe_event(fh, sub);
+}
+
+static int vidioc_query_dv_timings(struct file *file,
+ void *priv_fh,
+ struct v4l2_dv_timings *timings)
+{
+ struct hdcapm_fh *fh = file->private_data;
+ struct hdcapm_dev *dev = fh->dev;
+
+ return v4l2_subdev_call(dev->sd, video, query_dv_timings, timings);
+}
+
+static const struct v4l2_ioctl_ops mpeg_ioctl_ops = {
+ .vidioc_enum_input = vidioc_enum_input,
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_log_status = vidioc_log_status,
+ .vidioc_g_input = vidioc_g_input,
+ .vidioc_s_input = vidioc_s_input,
+ .vidioc_g_dv_timings = vidioc_g_dv_timings,
+ .vidioc_query_dv_timings = vidioc_query_dv_timings,
+ .vidioc_enum_dv_timings = vidioc_enum_dv_timings,
+ .vidioc_dv_timings_cap = vidioc_dv_timings_cap,
+ .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
+ .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
+ .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
+ .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
+ .vidioc_subscribe_event = vidioc_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static int fops_open(struct file *file)
+{
+ struct hdcapm_dev *dev;
+ struct hdcapm_fh *fh;
+
+ dev = (struct hdcapm_dev *)video_get_drvdata(video_devdata(file));
+ if (!dev)
+ return -ENODEV;
+
+ v4l2_dbg(2, hdcapm_debug, dev->sd, "%s()\n", __func__);
+
+ /* allocate + initialize per filehandle data */
+ fh = kzalloc(sizeof(*fh), GFP_KERNEL);
+ if (!fh)
+ return -ENOMEM;
+
+ fh->dev = dev;
+ v4l2_fh_init(&fh->fh, video_devdata(file));
+ file->private_data = &fh->fh;
+ v4l2_fh_add(&fh->fh);
+
+ return 0;
+}
+
+static int fops_release(struct file *file)
+{
+ struct hdcapm_fh *fh = file->private_data;
+ struct hdcapm_dev *dev = fh->dev;
+
+ v4l2_dbg(2, hdcapm_debug, dev->sd, "%s()\n", __func__);
+
+ /* Shut device down on last close */
+ if ((atomic_cmpxchg(&fh->v4l_reading, 1, 0) == 1) &&
+ (atomic_dec_return(&dev->v4l_reader_count) == 0))
+ /* stop mpeg capture then cancel buffers */
+ hdcapm_core_stop_streaming(dev);
+
+ v4l2_fh_del(&fh->fh);
+ v4l2_fh_exit(&fh->fh);
+ kfree(fh);
+
+ return 0;
+}
+
+static ssize_t fops_read(struct file *file, char __user *buffer,
+ size_t count, loff_t *pos)
+{
+ struct hdcapm_fh *fh = file->private_data;
+ struct hdcapm_dev *dev = fh->dev;
+ struct hdcapm_buffer *ubuf = NULL;
+ int ret = 0;
+ int rem, cnt;
+ u8 *p;
+
+ if (*pos) {
+ v4l2_err(dev->sd, "%s() ESPIPE\n", __func__);
+ return -ESPIPE;
+ }
+
+ if ((atomic_cmpxchg(&fh->v4l_reading, 0, 1) == 0) &&
+ (atomic_inc_return(&dev->v4l_reader_count) == 1))
+ hdcapm_core_start_streaming(dev);
+
+ /* blocking wait for buffer */
+ if ((file->f_flags & O_NONBLOCK) == 0) {
+ if (wait_event_interruptible(dev->wait_read,
+ hdcapm_buffer_peek_used(dev))) {
+ v4l2_err(dev->sd,
+ "%s() ERESTARTSYS\n", __func__);
+ //return -ERESTARTSYS;
+ return -EINVAL;
+ }
+ }
+
+ /* Pull the first buffer from the used list */
+ ubuf = hdcapm_buffer_peek_used(dev);
+
+ while ((count > 0) && ubuf) {
+ /* set remaining bytes to copy */
+ rem = ubuf->actual_size - ubuf->readpos;
+ cnt = rem > count ? count : rem;
+
+ p = ubuf->ptr + ubuf->readpos;
+
+ v4l2_dbg(3, hdcapm_debug, dev->sd,
+ "%s() nr=%d count=%d cnt=%d rem=%d buf=%p buf->readpos=%d\n",
+ __func__, ubuf->nr, (int)count,
+ cnt, rem, ubuf, ubuf->readpos);
+
+ if (copy_to_user(buffer, p, cnt)) {
+ v4l2_err(dev->sd,
+ "%s() copy_to_user failed\n", __func__);
+ if (!ret) {
+ v4l2_err(dev->sd, "%s() EFAULT\n", __func__);
+ ret = -EFAULT;
+ }
+ goto err;
+ }
+
+ ubuf->readpos += cnt;
+ count -= cnt;
+ buffer += cnt;
+ ret += cnt;
+
+ if (ubuf->readpos > ubuf->actual_size)
+ v4l2_err(dev->sd, "read() pos > actual, huh?\n");
+
+ if (ubuf->readpos == ubuf->actual_size) {
+ /* finished with current buffer, take next buffer */
+
+ /* Requeue the buffer on the free list */
+ ubuf->readpos = 0;
+
+ hdcapm_buffer_move_to_free(dev, ubuf);
+
+ /* Dequeue next */
+ if ((file->f_flags & O_NONBLOCK) == 0) {
+ if (wait_event_interruptible(dev->wait_read,
+ hdcapm_buffer_peek_used(dev)))
+ break;
+ }
+ ubuf = hdcapm_buffer_peek_used(dev);
+ }
+ }
+err:
+ if (!ret && !ubuf)
+ ret = -EAGAIN;
+
+ return ret;
+}
+
+static unsigned int fops_poll(struct file *file, poll_table *wait)
+{
+ unsigned long req_events = poll_requested_events(wait);
+ struct hdcapm_fh *fh = (struct hdcapm_fh *)file->private_data;
+ struct hdcapm_dev *dev = fh->dev;
+ unsigned int mask = v4l2_ctrl_poll(file, wait);
+
+ if (!(req_events & (POLLIN | POLLRDNORM)))
+ return mask;
+
+ if (atomic_cmpxchg(&fh->v4l_reading, 0, 1) == 0) {
+ if (atomic_inc_return(&dev->v4l_reader_count) == 1)
+ hdcapm_core_start_streaming(dev);
+ }
+
+ /* Pull the first buffer from the used list */
+ if (!list_empty(&dev->list_buf_used))
+ mask |= POLLIN | POLLRDNORM;
+
+ return mask;
+}
+
+static const struct v4l2_file_operations mpeg_fops = {
+ .owner = THIS_MODULE,
+ .open = fops_open,
+ .release = fops_release,
+ .read = fops_read,
+ .poll = fops_poll,
+ .unlocked_ioctl = video_ioctl2,
+};
+
+static struct video_device mpeg_template = {
+ .name = "hdcapm",
+ .fops = &mpeg_fops,
+ .ioctl_ops = &mpeg_ioctl_ops,
+ .minor = -1,
+};
+
+int hdcapm_video_register(struct hdcapm_dev *dev)
+{
+ struct v4l2_ctrl_handler *hdl = &dev->ctrl_handler;
+ int ret;
+
+ v4l2_dbg(1, hdcapm_debug, dev->sd, "%s()\n", __func__);
+
+ /* Any video controls. */
+
+ dev->v4l_device = video_device_alloc();
+ if (!dev->v4l_device)
+ return -EINVAL;
+
+ /* Configure the V4L2 device properties */
+ *dev->v4l_device = mpeg_template;
+ snprintf(dev->v4l_device->name, sizeof(dev->v4l_device->name),
+ "%s %s (%s)", dev->name, "mpeg", dev->name);
+ dev->v4l_device->v4l2_dev = &dev->v4l2_dev;
+ dev->v4l_device->release = video_device_release;
+
+ v4l2_ctrl_handler_init(hdl, 14);
+ dev->v4l_device->ctrl_handler = hdl;
+
+ v4l2_ctrl_new_std(hdl, &ctrl_ops, V4L2_CID_MPEG_AUDIO_MUTE, 0, 1, 1, 0);
+ v4l2_ctrl_new_std(hdl, &ctrl_ops, V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
+ v4l2_ctrl_new_std(hdl, &ctrl_ops, V4L2_CID_MPEG_VIDEO_GOP_SIZE,
+ ENCODER_MIN_GOP_SIZE,
+ ENCODER_MAX_GOP_SIZE, 1, ENCODER_DEF_GOP_SIZE);
+ v4l2_ctrl_new_std(hdl, &ctrl_ops, V4L2_CID_MPEG_VIDEO_BITRATE,
+ ENCODER_MIN_BITRATE,
+ ENCODER_MAX_BITRATE, 100000, ENCODER_DEF_BITRATE);
+ v4l2_ctrl_new_std(hdl, &ctrl_ops, V4L2_CID_MPEG_VIDEO_BITRATE_PEAK,
+ ENCODER_MIN_BITRATE,
+ ENCODER_MAX_BITRATE, 100000, ENCODER_DEF_BITRATE);
+
+ v4l2_ctrl_new_std_menu(hdl, &ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_BITRATE_MODE,
+ V4L2_MPEG_VIDEO_BITRATE_MODE_CBR, 0,
+ V4L2_MPEG_VIDEO_BITRATE_MODE_VBR);
+
+ v4l2_ctrl_new_std_menu(hdl, &ctrl_ops, V4L2_CID_MPEG_VIDEO_H264_LEVEL,
+ V4L2_MPEG_VIDEO_H264_LEVEL_5_1,
+ 0, V4L2_MPEG_VIDEO_H264_LEVEL_4_0);
+
+ v4l2_ctrl_new_std_menu(hdl, &ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE,
+ V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC,
+ 0, V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CAVLC);
+
+ v4l2_ctrl_new_std_menu(hdl, &ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_H264_PROFILE,
+ V4L2_MPEG_VIDEO_H264_PROFILE_HIGH,
+ ~((1 << V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE) |
+ (1 << V4L2_MPEG_VIDEO_H264_PROFILE_MAIN) |
+ (1 << V4L2_MPEG_VIDEO_H264_PROFILE_HIGH)),
+ V4L2_MPEG_VIDEO_H264_PROFILE_HIGH);
+
+ v4l2_ctrl_new_std_menu(hdl, &ctrl_ops,
+ V4L2_CID_MPEG_STREAM_TYPE,
+ V4L2_MPEG_STREAM_TYPE_MPEG2_TS,
+ ~(1 << V4L2_MPEG_STREAM_TYPE_MPEG2_TS),
+ V4L2_MPEG_STREAM_TYPE_MPEG2_TS);
+
+ /* Establish all default control values. */
+ v4l2_ctrl_handler_setup(hdl);
+
+ video_set_drvdata(dev->v4l_device, dev);
+ ret = video_register_device(dev->v4l_device, VFL_TYPE_GRABBER, -1);
+ if (ret < 0)
+ goto fail1;
+
+ v4l2_info(dev->sd,
+ "registered device video%d [mpeg]\n", dev->v4l_device->num);
+
+ ret = 0; /* Success */
+
+fail1:
+ return ret;
+}
+
+void hdcapm_video_unregister(struct hdcapm_dev *dev)
+{
+ v4l2_dbg(1, hdcapm_debug, dev->sd, "%s()\n", __func__);
+
+ if (dev->v4l_device) {
+ if (dev->v4l_device->minor != -1)
+ video_unregister_device(dev->v4l_device);
+ else
+ video_device_release(dev->v4l_device);
+
+ dev->v4l_device = NULL;
+ }
+ v4l2_ctrl_handler_free(&dev->ctrl_handler);
+}
diff --git a/drivers/media/usb/hdcapm/hdcapm.h b/drivers/media/usb/hdcapm/hdcapm.h
new file mode 100644
index 000000000000..d2a0e820c3eb
--- /dev/null
+++ b/drivers/media/usb/hdcapm/hdcapm.h
@@ -0,0 +1,283 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Driver for the Startech USB2HDCAPM USB capture device
+ *
+ * Copyright (c) 2017 Steven Toth <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *
+ * GNU General Public License for more details.
+ */
+
+#ifndef _HDCAPM_H
+#define _HDCAPM_H
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/bitops.h>
+#include <linux/i2c.h>
+#include <linux/i2c-algo-bit.h>
+#include <linux/kdev_t.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+#include <linux/usb.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/firmware.h>
+#include <linux/timer.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fh.h>
+
+#include "hdcapm-reg.h"
+
+extern int hdcapm_i2c_scan;
+extern int hdcapm_debug;
+
+#define HDCAPM_CARD_REV1 1
+
+#define PIPE_EP1 0x01
+#define PIPE_EP2 0x02
+#define PIPE_EP3 0x83
+#define PIPE_EP4 0x04
+
+/* The scheduler on ARM/RDU2 uses a different quanta, probably 20ms.
+ * on X86 its 10. This skews our hard timing when polling the
+ * codec memory levels (and downloading payload).
+ * SOme discussion was had re hires timers for ARM and how they may
+ * be more accurate.
+ * They're not, they just trade CPU cycles for more accurate timing.
+ * Enable this to evaluate ARM hires timers and look for the histogram
+ * results in the --log-status output, they show how accurate the
+ * kernel is for certain requested sleep intervals.
+ */
+#define TIMER_EVAL 0
+
+/* The driver started development by loading the firmware once
+ * during startup, unlike the windows driver that loads the
+ * firmware before every capture session. (ONETIME = 1).
+ * During later development, we found that if we don't load the
+ * firmware before ever capture, we lose audio in the second
+ * and subsequent capture.
+ * With ONETIME = 0 we load the firm whenever a capture starts.
+ */
+#define ONETIME_FW_LOAD 0
+
+extern struct usb_device_id hdcapm_usb_id_table[];
+
+struct hdcapm_dev;
+struct hdcapm_statistics;
+
+enum transition_state_e {
+ STATE_UNDEFINED = 0,
+ STATE_START, /* V4L2 read() or poll() advanced to _START state. */
+
+ STATE_STARTED, /* kernel thread notices _START state, starts
+ * the firmware and moves state to _STARTED.
+ */
+ STATE_STOP, /* V4L2 close advances from _STARTED to _STOP. */
+
+ STATE_STOPPED, /* kernel thread notices _STOPPING, stops
+ * firmware and moves to STOPPED state.
+ */
+};
+
+struct hdcapm_encoder_parameters {
+ /* TODO: Mostly all todo items. */
+ u32 audio_mute;
+ u32 brightness;
+ u32 bitrate_bps;
+ u32 bitrate_peak_bps;
+ u32 bitrate_mode;
+ u32 gop_size;
+
+ u32 h264_profile; /* H264 profile BASELINE etc */
+ u32 h264_level; /* H264 profile 4.1 etc */
+ u32 h264_entropy_mode; /* CABAC = 1 / CAVLC = 0 */
+ u32 h264_mode; /* VBR = 1, CBR = 0 */
+
+ /* Typically these map 1:1 to the detected timing
+ * resolution, but these could be modified bu
+ * s_fmt to invoke the hardware video scaler.
+ */
+ u32 output_width;
+ u32 output_height;
+};
+
+struct hdcapm_fh {
+ struct v4l2_fh fh;
+ struct hdcapm_dev *dev;
+ atomic_t v4l_reading;
+};
+
+struct hdcapm_i2c_bus {
+ struct hdcapm_dev *dev;
+ int nr;
+ struct i2c_adapter i2c_adap;
+ struct i2c_client i2c_client;
+ struct i2c_algo_bit_data i2c_algo;
+};
+
+struct hdcapm_dev {
+ struct list_head devlist;
+
+ char name[32];
+
+ enum transition_state_e state;
+ int thread_active;
+ struct task_struct *kthread;
+
+ struct hdcapm_statistics *stats;
+
+ /* Held by the follow driver features.
+ * 1. During probe and disconnect.
+ * 2. When writing commands to the firmware.
+ */
+ struct mutex lock;
+
+ struct usb_device *udev;
+
+ /* We need to xfer USB buffers off the stack, put them here. */
+ u8 *xferbuf;
+ u32 xferbuf_len;
+
+ /* I2C.
+ * Bus0 - MST3367.
+ * Bus1 - Sonix chip.
+ */
+ struct hdcapm_i2c_bus i2cbus[2];
+ //struct i2c_client *i2c_client_hdmi;
+ struct v4l2_subdev *sd;
+
+ /* V4L2 */
+ struct v4l2_device v4l2_dev;
+ struct video_device *v4l_device;
+ struct v4l2_ctrl_handler ctrl_handler;
+ atomic_t v4l_reader_count;
+ struct hdcapm_encoder_parameters encoder_parameters;
+#if TIMER_EVAL
+ struct timer_list ktimer;
+ struct hrtimer hrtimer;
+#endif
+
+ /* User buffering */
+ struct mutex dmaqueue_lock;
+ struct list_head list_buf_free;
+ struct list_head list_buf_used;
+ wait_queue_head_t wait_read;
+};
+
+struct hdcapm_buffer {
+ struct list_head list;
+
+ int nr;
+ struct hdcapm_dev *dev;
+ struct urb *urb;
+
+ u8 *ptr;
+ u32 maxsize;
+ u32 actual_size;
+ u32 readpos;
+};
+
+struct hdcapm_statistics {
+ /* Number of times the driver stole a used buffer to satisfy a
+ * free buffer streaming request.
+ */
+ u64 buffer_overrun;
+
+ /* The amount of data we've received from the firmware
+ * (video/audio codec data).
+ */
+ u64 codec_bytes_received;
+
+ /* The number of buffers we're received full of codec data
+ * (video/audio codec data).
+ */
+ u64 codec_buffers_received;
+
+ /* Any time we call the codec to check for a TS buffer, and it
+ * replies that it doesn't yet have one.
+ */
+ u64 codec_ts_not_yet_ready;
+};
+
+/* -core.c */
+int hdcapm_write32(struct hdcapm_dev *dev, u32 addr, u32 val);
+int hdcapm_read32(struct hdcapm_dev *dev, u32 addr, u32 *val);
+
+/* Read N DWORDS from the firmware and optionally convert the LE
+ * firmware dwords to platform CPU DWORDS.
+ */
+int hdcapm_read32_array(struct hdcapm_dev *dev, u32 addr, u32 wordcount,
+ u32 *arr, int le_to_cpu);
+
+void hdcapm_set32(struct hdcapm_dev *dev, u32 addr, u32 mask);
+void hdcapm_clr32(struct hdcapm_dev *dev, u32 addr, u32 mask);
+
+int hdcapm_dmawrite32(struct hdcapm_dev *dev, u32 addr, const u32 *arr,
+ u32 entries);
+int hdcapm_dmaread32(struct hdcapm_dev *dev, u32 addr, u32 *arr, u32 entries);
+int hdcapm_mem_write32(struct hdcapm_dev *dev, u32 addr, u32 val);
+int hdcapm_mem_read32(struct hdcapm_dev *dev, u32 addr, u32 *val);
+
+int hdcapm_core_ep_send(struct hdcapm_dev *dev, int endpoint, u8 *buf,
+ u32 len, u32 timeout);
+
+int hdcapm_core_ep_recv(struct hdcapm_dev *dev, int endpoint, u8 *buf,
+ u32 len, u32 *actual, u32 timeout);
+
+int hdcapm_core_stop_streaming(struct hdcapm_dev *dev);
+int hdcapm_core_start_streaming(struct hdcapm_dev *dev);
+
+/* -i2c.c */
+int hdcapm_i2c_register(struct hdcapm_dev *dev,
+ struct hdcapm_i2c_bus *bus, int nr);
+void hdcapm_i2c_unregister(struct hdcapm_dev *dev,
+ struct hdcapm_i2c_bus *bus);
+
+/* -buffer.c */
+struct hdcapm_buffer *hdcapm_buffer_alloc(struct hdcapm_dev *dev,
+ u32 nr, u32 maxsize);
+
+void hdcapm_buffer_free(struct hdcapm_buffer *buf);
+void hdcapm_buffers_move_all(struct hdcapm_dev *dev, struct list_head *to,
+ struct list_head *from);
+void hdcapm_buffers_free_all(struct hdcapm_dev *dev, struct list_head *head);
+struct hdcapm_buffer *hdcapm_buffer_next_free(struct hdcapm_dev *dev);
+struct hdcapm_buffer *hdcapm_buffer_peek_used(struct hdcapm_dev *dev);
+void hdcapm_buffer_move_to_free(struct hdcapm_dev *dev,
+ struct hdcapm_buffer *buf);
+void hdcapm_buffer_move_to_used(struct hdcapm_dev *dev,
+ struct hdcapm_buffer *buf);
+void hdcapm_buffer_add_to_free(struct hdcapm_dev *dev,
+ struct hdcapm_buffer *buf);
+void hdcapm_buffer_add_to_used(struct hdcapm_dev *dev,
+ struct hdcapm_buffer *buf);
+int hdcapm_buffer_used_queue_stats(struct hdcapm_dev *dev,
+ u64 *bytes, u64 *items);
+
+/* -compressor.c */
+int hdcapm_compressor_register(struct hdcapm_dev *dev);
+void hdcapm_compressor_unregister(struct hdcapm_dev *dev);
+void hdcapm_compressor_run(struct hdcapm_dev *dev);
+void hdcapm_compressor_init_gpios(struct hdcapm_dev *dev);
+
+/* -video.c */
+int hdcapm_video_register(struct hdcapm_dev *dev);
+void hdcapm_video_unregister(struct hdcapm_dev *dev);
+
+#endif /* _HDCAPM_H */
--
2.19.1


2018-11-26 18:20:11

by Michael Grzeschik

[permalink] [raw]
Subject: [PATCH v2 1/2] media: mst3367: add support for mstar mst3367 HDMI RX

From: Steven Toth <[email protected]>

This patch is based on the work of Steven Toth. He reverse engineered
the driver by tracing the windows driver.

https://github.com/stoth68000/hdcapm/

Signed-off-by: Steven Toth <[email protected]>
Signed-off-by: Michael Grzeschik <[email protected]>
---
drivers/media/i2c/Kconfig | 10 +
drivers/media/i2c/Makefile | 1 +
drivers/media/i2c/mst3367.c | 1104 +++++++++++++++++++++++++++++++++++
include/media/i2c/mst3367.h | 29 +
4 files changed, 1144 insertions(+)
create mode 100644 drivers/media/i2c/mst3367.c
create mode 100644 include/media/i2c/mst3367.h

diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 704af210e270..10dc0023494b 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -94,6 +94,16 @@ config VIDEO_MSP3400
To compile this driver as a module, choose M here: the
module will be called msp3400.

+config VIDEO_MST3367
+ tristate "Mstar MST3367 video decoders"
+ depends on VIDEO_V4L2 && I2C
+ help
+ Support for the MStar MST3367 HDMI RX / SOC. It is found on
+ the usb2hdcapm hdmi framegrabber from startech.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mst3367.
+
config VIDEO_CS3308
tristate "Cirrus Logic CS3308 audio ADC"
depends on VIDEO_V4L2 && I2C
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index 260d4d9ec2a1..fad65cce90f9 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
msp3400-objs := msp3400-driver.o msp3400-kthreads.o
obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o
+obj-$(CONFIG_VIDEO_MST3367) += mst3367.o

obj-$(CONFIG_VIDEO_SMIAPP) += smiapp/
obj-$(CONFIG_VIDEO_ET8EK8) += et8ek8/
diff --git a/drivers/media/i2c/mst3367.c b/drivers/media/i2c/mst3367.c
new file mode 100644
index 000000000000..7e2f529d96b3
--- /dev/null
+++ b/drivers/media/i2c/mst3367.c
@@ -0,0 +1,1104 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for the MSTAR 3367 HDMI Receiver
+ *
+ * Copyright (c) 2017 Steven Toth <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/videodev2.h>
+#include <linux/workqueue.h>
+#include <linux/v4l2-dv-timings.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-dv-timings.h>
+#include <media/v4l2-ctrls.h>
+#include <media/i2c/mst3367.h>
+
+static int debug;
+module_param_named(debug, debug, int, 0644);
+MODULE_PARM_DESC(debug, "debug level [def: 0]");
+
+MODULE_DESCRIPTION("Driver for MST3367 HDMI receiver");
+MODULE_AUTHOR("Steven Toth <[email protected]>");
+MODULE_LICENSE("GPL");
+
+#define BANK0 0x00
+#define BANK1 0x01
+#define BANK2 0x02
+#define BANK3 0x03
+#define DUMP_SHADOWS 0
+#define DUMP_REGISTERS 0
+
+/*
+ * This is how the register map was modified by the windows
+ * driver during the i2c-trace-driver-init-with-1080-signal.csv
+ * trace.
+ *
+ * BANK0 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ * -----------------------------------------------
+ * 00 : 00
+ * 10 : 11 01
+ * 20 :
+ * 30 :
+ * 40 : 6F
+ * 50 : 89 20
+ * 60 :
+ * 70 : 90
+ * 80 :
+ * 90 : 15 15 62 10 00 00 00 00 00 00 00 10 00 00 00 00
+ * A0 : 00 00 00 10 00 20 00 00 01 20 01 15 95 05 04
+ * B0 : 20 E0 08 00 54 0C 00 00
+ * C0 :
+ * D0 :
+ * E0 : 00
+ * F0 :
+ *
+ * BANK1 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ * -----------------------------------------------
+ * 00 : 01 02
+ * 10 : 30 00 00 00 50
+ * 20 : 40 07
+ * 30 : 80 00 00
+ * 40 :
+ * 50 :
+ * 60 :
+ * 70 :
+ * 80 :
+ * 90 :
+ * A0 :
+ * B0 :
+ * C0 :
+ * D0 :
+ * E0 :
+ * F0 :
+ *
+ * BANK2 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ * -----------------------------------------------
+ * 00 : 02 61 F5 02 01 00 08 04 03 28
+ * 10 : C0 FF FF FC 1A 00 00 00
+ * 20 : 00 00 26 A2 00 A1
+ * 30 :
+ * 40 :
+ * 50 :
+ * 60 :
+ * 70 :
+ * 80 :
+ * 90 :
+ * A0 :
+ * B0 :
+ * C0 :
+ * D0 :
+ * E0 :
+ * F0 :
+ *
+ */
+
+struct mst3367_video_standards_s {
+ struct v4l2_dv_timings timings;
+ u32 htotal_min;
+ u32 htotal_max;
+ u32 vtotal_min;
+ u32 vtotal_max;
+ u32 hperiod_min;
+ u32 hperiod_max;
+ u32 vperiod_min;
+ u32 vperiod_max;
+ u32 interleaved;
+ u32 encoded_fps;
+ u32 hdmi_fpsX100;
+};
+
+struct mst3367_state {
+ struct v4l2_subdev sd;
+ struct v4l2_ctrl_handler hdl;
+
+ /* Is the mst3367 powered on? */
+ bool power_on;
+ bool haveSource;
+
+ /* controls */
+ struct v4l2_ctrl *hotplug_ctrl;
+ struct v4l2_ctrl *rx_sense_ctrl;
+
+ /* i2c */
+ struct i2c_adapter *i2c;
+ u8 i2c_addr;
+ u8 current_bank;
+
+ /* Detection */
+ const struct mst3367_video_standards_s *detected_standard;
+ int detected_signal;
+ struct {
+ u32 htotal;
+ u32 vtotal;
+ u32 hperiod;
+ u32 vperiod;
+ u32 detectdelay;
+ u32 hactive;
+ u32 interleaved;
+ } current_timings;
+
+ /* Shadow regs for monitoring writes. */
+ u8 regs[4][256];
+ u8 regs_updated[4][256];
+
+ u8 regb1r01_cached;
+ u8 regb2r48_cached;
+};
+
+static const struct mst3367_video_standards_s mst3367_video_standards[] = {
+ {V4L2_DV_BT_CEA_720X480P59_94, 845, 865, 520, 525, 310, 320, 595, 605,
+ 0, 60, 5994,},
+
+ // 720p30 - frontend doesn't reliably lock.
+ {V4L2_DV_BT_CEA_1280X720P30, 2300, 2500, 745, 755, 215, 235, 290, 310,
+ 0, 30, 3000,},
+ {V4L2_DV_BT_CEA_1280X720P50, 2965, 2985, 745, 755, 360, 380, 480, 520,
+ 0, 50, 5000,},
+ {V4L2_DV_BT_CEA_1280X720P60, 2470, 2480, 745, 755, 445, 455, 595, 605,
+ 0, 60, 6000,},
+
+ // Tivo
+ {V4L2_DV_BT_CEA_1280X720P60, 1645, 1655, 745, 755, 445, 455, 595, 605,
+ 0, 60, 6000,},
+
+ {V4L2_DV_BT_CEA_1920X1080P24, 4080, 4105, 1120, 1130, 260, 280, 230,
+ 250, 0, 24, 2400,},
+ {V4L2_DV_BT_CEA_1920X1080P25, 3950, 3970, 1120, 1130, 270, 290, 240,
+ 254, 0, 25, 2500,},
+ {V4L2_DV_BT_CEA_1920X1080P30, 2295, 3305, 1120, 1130, 330, 345, 290,
+ 310, 0, 30, 3000,},
+ {V4L2_DV_BT_CEA_1920X1080P50, 3950, 3970, 1120, 1130, 550, 570, 480,
+ 520, 0, 25, 5000,},
+ {V4L2_DV_BT_CEA_1920X1080P60, 3290, 3310, 1120, 1130, 665, 685, 595,
+ 605, 0, 30, 6000,},
+};
+
+static const struct mst3367_video_standards_s *find_video_standard(u32 htotal,
+ u32 vtotal,
+ u32 hperiod,
+ u32 vperiod,
+ u32
+ interleaved)
+{
+ const struct mst3367_video_standards_s *e, *r = NULL;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(mst3367_video_standards); i++) {
+ e = &mst3367_video_standards[i];
+
+ if (htotal < e->htotal_min || htotal > e->htotal_max)
+ continue;
+ if (vtotal < e->vtotal_min || vtotal > e->vtotal_max)
+ continue;
+ if (hperiod < e->hperiod_min || hperiod > e->hperiod_max)
+ continue;
+ if (vperiod < e->vperiod_min || vperiod > e->vperiod_max)
+ continue;
+ if (interleaved != e->interleaved)
+ continue;
+
+ r = e;
+ break;
+ }
+
+ return r;
+}
+
+static inline struct mst3367_state *get_mst3367_state(struct v4l2_subdev *sd)
+{
+ return container_of(sd, struct mst3367_state, sd);
+}
+
+static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl)
+{
+ return &container_of(ctrl->handler, struct mst3367_state, hdl)->sd;
+}
+
+static void mst3367_notify_source_detect(struct v4l2_subdev *sd, int haveSource)
+{
+ struct v4l2_event ev;
+ struct mst3367_source_detect msd;
+
+ msd.present = haveSource;
+
+ /* sub-device events get pushed to the bridge via hdcapm_notify().
+ * The bridge then forwards those events on to the v4l2_device,
+ * and eventually they end up in userspace.
+ */
+ v4l2_subdev_notify(sd, MST3367_SOURCE_DETECT, (void *)&msd);
+
+ memset(&ev, 0, sizeof(ev));
+ ev.type = V4L2_EVENT_SOURCE_CHANGE;
+ ev.u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION;
+ /* Input 0 - This event requires that the id matches the input index
+ * (when used with a video device node)
+ */
+ ev.id = 0;
+ v4l2_subdev_notify_event(sd, &ev);
+}
+
+/* The MST 3367 has multiple I2C register maps, banks 0-3, if the
+ * current bank doesn't match the requested bank, switch banks.
+ */
+static void mst3367_switch_bank(struct v4l2_subdev *sd, u8 bank)
+{
+ struct mst3367_state *state = get_mst3367_state(sd);
+ u8 buf[] = { 0x00, bank };
+
+ struct i2c_msg msg = {.addr = state->i2c_addr >> 1,
+ .flags = 0, .buf = buf, .len = 2
+ };
+
+ if (state->current_bank != bank) {
+ state->current_bank = bank;
+ if (i2c_transfer(state->i2c, &msg, 1) != 1)
+ v4l2_err(sd, "%s: switch bank error\n", __func__);
+ }
+}
+
+static u8 mst3367_rd(struct v4l2_subdev *sd, u8 bank, u8 reg)
+{
+ struct mst3367_state *state = get_mst3367_state(sd);
+ u8 b0 = reg;
+ u8 b1 = 0;
+
+ struct i2c_msg msg[] = {
+ {.addr = state->i2c_addr >> 1,
+ .flags = 0, .buf = &b0, .len = 1},
+ {.addr = state->i2c_addr >> 1,
+ .flags = I2C_M_RD, .buf = &b1, .len = 1}
+ };
+
+ mst3367_switch_bank(sd, bank);
+
+ if (i2c_transfer(state->i2c, msg, 2) != 2)
+ v4l2_err(sd, "%s: readreg error\n", __func__);
+
+ v4l2_dbg(2, debug, sd, "%s(bank=%d, reg=0x%02x) = 0x%02x\n",
+ __func__, bank, reg, b1);
+
+ return b1;
+}
+
+static void mst3367_wr(struct v4l2_subdev *sd, u8 bank, u8 reg, u8 val)
+{
+ struct mst3367_state *state = get_mst3367_state(sd);
+ u8 buf[] = { reg, val };
+
+ struct i2c_msg msg = {.addr = state->i2c_addr >> 1, .flags = 0,
+ .buf = buf, .len = 2
+ };
+
+ v4l2_dbg(2, debug, sd, "%s(bank=%d, reg=0x%02x, value=0x%02x)\n",
+ __func__, bank, reg, val);
+ mst3367_switch_bank(sd, bank);
+
+ state->regs[state->current_bank][reg] = val;
+ state->regs_updated[state->current_bank][reg] = 1;
+
+ if (i2c_transfer(state->i2c, &msg, 1) != 1)
+ v4l2_err(sd, "%s: writereg error\n", __func__);
+}
+
+static inline void mst3367_set(struct v4l2_subdev *sd, u8 bank, u8 reg, u8 mask)
+{
+ u8 val = mst3367_rd(sd, bank, reg);
+
+ val |= mask;
+ mst3367_wr(sd, bank, reg, val);
+}
+
+static inline void mst3367_clr(struct v4l2_subdev *sd, u8 bank, u8 reg, u8 mask)
+{
+ u8 val = mst3367_rd(sd, bank, reg);
+
+ val &= ~mask;
+ mst3367_wr(sd, bank, reg, val);
+}
+
+enum hpt_e {
+ RX_TMDS_HPD_OFF = 0x00,
+ RX_TMDS_A_HPD_ON = 0x01,
+ RX_TMDS_A_LINK_ON = 0x02,
+ RX_TMDS_B_HPD_ON = 0x10,
+ RX_TMDS_B_LINK_ON = 0x20,
+};
+
+static inline void MST3367_TMDS_HOT_PLUG(struct v4l2_subdev *sd, enum hpt_e e)
+{
+ u8 v = mst3367_rd(sd, BANK0, 0xB7);
+
+ v |= 0x02;
+
+ if (e & RX_TMDS_A_LINK_ON)
+ v &= ~0x02;
+ if (e & RX_TMDS_B_LINK_ON)
+ v &= ~0x02;
+
+ mst3367_wr(sd, BANK0, 0xB7, v);
+ msleep(20);
+}
+
+static inline void MST3367_HDMI_INIT(struct v4l2_subdev *sd)
+{
+ /* RxHdmiInit */
+ mst3367_clr(sd, BANK2, 0x01, 0xf0);
+ mst3367_set(sd, BANK2, 0x01, 0x40 | 0x20);
+ mst3367_set(sd, BANK2, 0x04, 0x01);
+ mst3367_wr(sd, BANK2, 0x06, 0x08);
+ mst3367_set(sd, BANK2, 0x09, 0x20);
+ mst3367_clr(sd, BANK0, 0x54, 0x10);
+ mst3367_set(sd, BANK0, 0xac, 0x80);
+
+ mst3367_set(sd, BANK0, 0x00, 0x80);
+ mst3367_set(sd, BANK0, 0xce, 0x80);
+ mst3367_clr(sd, BANK0, 0xcf, 0x07);
+ mst3367_set(sd, BANK0, 0xcf, 0x02);
+ mst3367_clr(sd, BANK0, 0x00, 0x80);
+}
+
+static inline void MST3367_HDCP_RESET(struct v4l2_subdev *sd)
+{
+ mst3367_wr(sd, BANK0, 0xb8, 0x10); /* HDCP RESET */
+ mst3367_wr(sd, BANK0, 0xb8, 0x00);
+ msleep(20);
+}
+
+static inline void MST3367_HDMI_RESET(struct v4l2_subdev *sd)
+{
+ mst3367_wr(sd, BANK2, 0x07, 0xf4);
+ mst3367_wr(sd, BANK2, 0x07, 0x04);
+ msleep(20);
+}
+
+#if DUMP_SHADOWS
+static void dump_shadows(struct v4l2_subdev *sd, int bank)
+{
+ struct mst3367_state *state = get_mst3367_state(sd);
+ int i, j;
+ u8 line[80];
+
+ v4l2_info(sd, "B%d 0 1 2 3 4 5 6 7 8 9 A B C D E F\n",
+ bank);
+ v4l2_info(sd, " -----------------------------------------------\n");
+ for (i = 0; i < 256; i += 16) {
+ sprintf(line, "%02X : ", i);
+ for (j = 0; j < 16; j++) {
+ if (state->regs_updated[bank][i + j])
+ sprintf(line + strlen(line), "%02X ",
+ state->regs[bank][i + j]);
+ else
+ sprintf(line + strlen(line), " ");
+ }
+ sprintf(line + strlen(line), "\n");
+ v4l2_info(sd, line);
+ }
+}
+#endif
+
+#if DUMP_REGISTERS
+static void dump_registers(struct v4l2_subdev *sd, int bank)
+{
+ int i, j;
+ u8 line[80];
+ u8 vals[256];
+
+ for (i = 0; i < sizeof(vals); i++)
+ vals[i] = mst3367_rd(sd, bank, i);
+
+ v4l2_info(sd, "B%d 0 1 2 3 4 5 6 7 8 9 A B C D E F\n",
+ bank);
+ v4l2_info(sd, " -----------------------------------------------\n");
+ for (i = 0; i < 256; i += 16) {
+ sprintf(line, "%02X : ", i);
+ for (j = 0; j < 16; j++)
+ sprintf(line + strlen(line), "%02X ", vals[i + j]);
+ sprintf(line + strlen(line), "\n");
+ v4l2_info(sd, line);
+ }
+}
+#endif
+
+static int MST3367_HDMI_MODE_DETECT(struct v4l2_subdev *sd, int *locked)
+{
+ struct mst3367_state *state = get_mst3367_state(sd);
+ int ret = -ENOLINK;
+ u8 r[0xff];
+ u16 t;
+
+ *locked = 0;
+
+ /* Do we have a signal detect / lock? */
+ if (mst3367_rd(sd, BANK0, 0x55) & 0x3c) {
+ /* We have a signal, extract timing data. */
+ state->current_timings.htotal = (mst3367_rd(sd,
+ BANK0, 0x6a) << 8
+ | mst3367_rd(sd, BANK0, 0x6b))
+ & 0xfff;
+ state->current_timings.vtotal = (mst3367_rd(sd,
+ BANK0, 0x5b) << 8
+ | mst3367_rd(sd, BANK0, 0x5c))
+ & 0x7ff;
+ state->current_timings.hactive =
+ (mst3367_rd(sd, BANK2, 0x29) << 8 |
+ mst3367_rd(sd, BANK2, 0x28))
+ & 0x1fff;
+
+ r[0x57] = mst3367_rd(sd, BANK0, 0x57) & 0x3f;
+ r[0x58] = mst3367_rd(sd, BANK0, 0x58);
+ r[0x59] = mst3367_rd(sd, BANK0, 0x59) & 0x3f;
+ r[0x5a] = mst3367_rd(sd, BANK0, 0x5a);
+ r[0x5f] = mst3367_rd(sd, BANK0, 0x5f) & 0x02;
+
+ t = ((r[0x57] << 8) | r[0x58]);
+ if (t > 0)
+ state->current_timings.hperiod =
+ ((1600000) / ((r[0x57] << 8) | (r[0x58] << 0)));
+
+ t = ((r[0x59] << 8) | r[0x5a]);
+ if (t > 0)
+ state->current_timings.vperiod =
+ ((1250000) / ((r[0x59] << 8) | (r[0x5a] << 0)));
+
+ state->current_timings.interleaved = r[0x5f] >> 1;
+
+ state->current_timings.detectdelay = ((r[0x59] << 8) | r[0x5a]);
+ state->current_timings.detectdelay =
+ ((state->current_timings.detectdelay + 63) * 2) / 125;
+
+ v4l2_dbg(2, debug, sd,
+ "%s() htotal = %d, vtotal = %d, hperiod = %d, vperiod = %d, detectdelay = %d, hactive = %d, interleaved = %d\n",
+ __func__,
+ state->current_timings.htotal,
+ state->current_timings.vtotal,
+ state->current_timings.hperiod,
+ state->current_timings.vperiod,
+ state->current_timings.detectdelay,
+ state->current_timings.hactive,
+ state->current_timings.interleaved);
+
+ /* Looking the signal format. If its somet hing we
+ * support then return lock, else no lock.
+ */
+ state->detected_standard =
+ find_video_standard(state->current_timings.htotal,
+ state->current_timings.vtotal,
+ state->current_timings.hperiod,
+ state->current_timings.vperiod,
+ state->current_timings.interleaved);
+ if (state->detected_standard) {
+ *locked = 1;
+ } else {
+ /* Detected a signal on the wire, but we have no
+ * standard defined for it.
+ */
+ v4l2_dbg(2, debug, sd,
+ "%s() No detected standard for htotal = %d, vtotal = %d, hperiod = %d, vperiod = %d, detectdelay = %d, hactive = %d, interleaved = %d\n",
+ __func__,
+ state->current_timings.htotal,
+ state->current_timings.vtotal,
+ state->current_timings.hperiod,
+ state->current_timings.vperiod,
+ state->current_timings.detectdelay,
+ state->current_timings.hactive,
+ state->current_timings.interleaved);
+ }
+
+ ret = 0;
+
+ /*
+ * printk(KERN_ERR "%s() r01 = 0x%x\n", __func__, r[0x01]);
+ * HDMI_MD 2
+ * HDCP_OP_STS 1
+ * HDCP_MD 0
+ * 0x0: DVI, without HDCP.
+ * 001: DVI OESS* + HDCP, without advance cipher.
+ * 011: DVI EESS** + HDCP, with advance cipher.
+ * 1x0: HDMI EESS, without HDCP.
+ * 101: HDMI EESS + HDCP, without advance cipher.
+ * 111: HDMI + HDCP EESS, with advance cipher.
+ * *OESS: Original Encryption Status Signaling.
+ * **EESS: Enhanced Encryption Status Signaling
+ */
+ }
+
+ state->regb1r01_cached = mst3367_rd(sd, BANK1, 0x01);
+ state->regb2r48_cached = mst3367_rd(sd, BANK2, 0x48);
+
+ if (*locked) {
+ state->detected_signal = 1;
+ if (!state->haveSource) {
+ state->haveSource = 1;
+ mst3367_notify_source_detect(sd, state->haveSource);
+ }
+ } else {
+ memset(&state->current_timings, 0,
+ sizeof(state->current_timings));
+ state->detected_signal = 0;
+ if (state->haveSource) {
+ state->haveSource = 0;
+ mst3367_notify_source_detect(sd, state->haveSource);
+ }
+ }
+
+ return ret;
+}
+
+static inline u32 MST3367_HdmiGetPacketStatus(struct v4l2_subdev *sd)
+{
+ u32 status = 0;
+ u8 r0b, r0c, r0e;
+
+ r0b = mst3367_rd(sd, BANK2, 0x0b) & 0xff;
+ r0c = mst3367_rd(sd, BANK2, 0x0c) & 0x3f;
+ r0e = mst3367_rd(sd, BANK2, 0x0e) & 0x08;
+
+ status = (r0c << 8) | r0b;
+ if (r0e & 0x08)
+ status |= 0x8000;
+
+ v4l2_dbg(1, debug, sd, "%s() status = 0x%08x\n", __func__, status);
+
+ return status;
+}
+
+static inline u32 MST3367_HdmiGetPacketColor(struct v4l2_subdev *sd)
+{
+ u32 color = 0;
+
+ u8 r48 = mst3367_rd(sd, BANK2, 0x48) & 0x60;
+
+ if (r48 == 0x00)
+ color = 0; /* RX_INPUT_RGB */
+ else if (r48 == 0x20)
+ color = 1; /* RX_INPUT_YUV422 */
+ else if (r48 == 0x40)
+ color = 2; /* RX_INPUT_YUV444 */
+
+ return color;
+}
+
+static int mst3367_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct v4l2_subdev *sd = to_sd(ctrl);
+
+ v4l2_dbg(1, debug, sd, "%s: ctrl id: %d, ctrl->val %d\n", __func__,
+ ctrl->id, ctrl->val);
+
+ return -EINVAL;
+}
+
+static const struct v4l2_ctrl_ops mst3367_ctrl_ops = {
+ .s_ctrl = mst3367_s_ctrl,
+};
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+/* Register bits 15-8 represent bank, bits 7-0 register. */
+static int mst3367_g_register(struct v4l2_subdev *sd,
+ struct v4l2_dbg_register *reg)
+{
+ reg->val = mst3367_rd(sd, (reg->reg >> 8) & 0xff, reg->reg & 0xff);
+ reg->size = 1;
+ return 0;
+}
+
+static int mst3367_s_register(struct v4l2_subdev *sd,
+ const struct v4l2_dbg_register *reg)
+{
+ mst3367_wr(sd, (reg->reg >> 8) & 0xff, reg->reg & 0xff,
+ reg->val & 0xff);
+ return 0;
+}
+#endif
+
+static int mst3367_log_status(struct v4l2_subdev *sd)
+{
+ struct mst3367_state *state = get_mst3367_state(sd);
+
+ v4l2_info(sd, "source_connected: %s\n",
+ state->haveSource ? "yes" : "no");
+ v4l2_info(sd, "signal_detected: %s\n",
+ state->detected_signal ? "yes" : "no");
+ v4l2_info(sd, "power: %s\n",
+ state->power_on ? "on" : "off");
+
+ if (state->detected_signal) {
+ v4l2_info(sd, "standard: %dx%dx%d%s\n",
+ state->detected_standard->timings.bt.width,
+ state->detected_standard->timings.bt.height,
+ state->detected_standard->hdmi_fpsX100 / 100,
+ state->detected_standard->timings.bt.interlaced
+ ? "i" : "p");
+ } else {
+ v4l2_info(sd, "standard: n/a\n");
+ }
+
+ v4l2_info(sd,
+ "htotal: %d (horizontal front porch, sync, back porch + active pixels)\n",
+ state->current_timings.htotal);
+ v4l2_info(sd,
+ "vtotal: %d (vertical front porch, sync, back porch + active pixels)\n",
+ state->current_timings.vtotal);
+ v4l2_info(sd, "hperiod: %d (%d.%d KHz)\n",
+ state->current_timings.hperiod,
+ state->current_timings.hperiod / 10,
+ state->current_timings.hperiod % 10);
+
+ v4l2_info(sd, "vperiod: %d (%d.%d Hz)\n",
+ state->current_timings.vperiod,
+ state->current_timings.vperiod / 10,
+ state->current_timings.vperiod % 10);
+
+ v4l2_info(sd, "detectdelay: %d\n",
+ state->current_timings.detectdelay);
+ v4l2_info(sd, "hactive: %d\n",
+ state->current_timings.hactive);
+ v4l2_info(sd, "scanline: %s\n",
+ state->current_timings.interleaved
+ ? "interleaved" : "progressive");
+
+ v4l2_info(sd, "input colorspace: %s\n",
+ (state->regb2r48_cached & 0x60) == 0x00 ? "RX_INPUT_RGB" :
+ (state->regb2r48_cached & 0x60) == 0x20 ? "RX_INPUT_YUV422" :
+ (state->regb2r48_cached & 0x60) ==
+ 0x40 ? "RX_INPUT_YUV444" : "UNDEFINED");
+
+ v4l2_info(sd, "1.01: 0x%02x\n", state->regb1r01_cached);
+ v4l2_info(sd, "1.01.b2: %s\n",
+ state->regb1r01_cached & 0x04 ? "HDMI" : "DVI");
+ v4l2_info(sd, "1.01.b0: %s\n",
+ state->regb1r01_cached & 0x01
+ ? "HDCP active" : "HDCP not present");
+
+ return 0;
+}
+
+static void mst3367_init_setup(struct v4l2_subdev *sd)
+{
+ int i;
+ u8 csctbl[] = {
+ 0x40,
+ 0x08, 0x02, 0x03, 0x65, 0x7E, 0x28, /* M11, M12, M13 */
+ 0x78, 0xB9, 0x0B, 0x65, 0x79, 0xD6, /* M21, M22, M23 */
+ 0x7F, 0x45, 0x01, 0x27, 0x08, 0x02, /* M31, M32, M33 */
+ 0x20, 0x00, 0x02, 0x81, 0x20, 0x01, /* A1, A2, A3 */
+ 0x15, 0x95, 0x05, 0x20, 0xC0, 0x08
+ };
+
+ v4l2_dbg(1, debug, sd, "%s\n", __func__);
+
+ MST3367_TMDS_HOT_PLUG(sd, RX_TMDS_HPD_OFF);
+
+ /* RxGeneralInit */
+ mst3367_wr(sd, BANK0, 0x41, 0x6f);
+ mst3367_wr(sd, BANK0, 0xb8, 0x00);
+
+ /* RxTmdsInit */
+ mst3367_wr(sd, BANK1, 0x0f, 0x02);
+ mst3367_wr(sd, BANK1, 0x16, 0x30);
+ mst3367_wr(sd, BANK1, 0x17, 0x00);
+ mst3367_wr(sd, BANK1, 0x18, 0x00);
+ mst3367_wr(sd, BANK1, 0x19, 0x00);
+ mst3367_wr(sd, BANK1, 0x1a, 0x50);
+ mst3367_clr(sd, BANK1, 0x2a, 0x07);
+ mst3367_set(sd, BANK1, 0x2a, 0x07);
+ mst3367_wr(sd, BANK2, 0x08, 0x03);
+
+ /* RxHdcpInit */
+ /* receive HDCP */
+ mst3367_wr(sd, BANK1, 0x24, 0x40);
+
+ mst3367_wr(sd, BANK1, 0x30, 0x80);
+ mst3367_wr(sd, BANK1, 0x31, 0x00);
+ mst3367_wr(sd, BANK1, 0x32, 0x00);
+
+ /* RxVideoInit */
+ mst3367_wr(sd, BANK0, 0xb0, 0x14);
+ mst3367_set(sd, BANK0, 0xae, 0x04);
+ mst3367_wr(sd, BANK0, 0xad, 0x05); /* ENABLE LOW.PASS FILTER */
+ mst3367_wr(sd, BANK0, 0xb1, 0xe0); /* From windows i2c trace. */
+ mst3367_wr(sd, BANK0, 0xb2, 0x08); /* From windows i2c trace. */
+ mst3367_wr(sd, BANK0, 0xb3, 0x00);
+ mst3367_wr(sd, BANK0, 0xb4, 0x55);
+
+ /* RxAudioInit */
+ mst3367_clr(sd, BANK0, 0xb4, 0x03);
+ mst3367_wr(sd, BANK2, 0x01, 0x61);
+ mst3367_wr(sd, BANK2, 0x02, 0xf5);
+ mst3367_set(sd, BANK2, 0x03, 0x02);
+ mst3367_wr(sd, BANK2, 0x04, 0x01);
+ mst3367_wr(sd, BANK2, 0x05, 0x00);
+ mst3367_wr(sd, BANK2, 0x06, 0x08);
+ mst3367_wr(sd, BANK2, 0x1c, 0x1a);
+ mst3367_wr(sd, BANK2, 0x1d, 0x00);
+ mst3367_wr(sd, BANK2, 0x1e, 0x00);
+ mst3367_wr(sd, BANK2, 0x1f, 0x00);
+ mst3367_clr(sd, BANK2, 0x25, 0xa2);
+ mst3367_set(sd, BANK2, 0x25, 0xa2);
+
+ /* unknown */
+ mst3367_set(sd, BANK2, 0x02, 0x80);
+ mst3367_set(sd, BANK2, 0x07, 0x04);
+ mst3367_wr(sd, BANK2, 0x17, 0xc0);
+ mst3367_wr(sd, BANK2, 0x19, 0xff);
+ mst3367_wr(sd, BANK2, 0x1a, 0xff);
+ mst3367_wr(sd, BANK2, 0x1b, 0xfc);
+ mst3367_wr(sd, BANK2, 0x20, 0x00);
+ mst3367_clr(sd, BANK2, 0x21, 0x03);
+ mst3367_wr(sd, BANK2, 0x22, 0x26);
+ mst3367_wr(sd, BANK2, 0x27, 0x00);
+ mst3367_set(sd, BANK2, 0x2e, 0xa1);
+
+ /* unknown */
+ mst3367_wr(sd, BANK0, 0xab, 0x15); /* [COLOR.RANGE] 0x15 */
+ mst3367_clr(sd, BANK0, 0xac, 0x3f);
+ mst3367_set(sd, BANK0, 0xac, 0x15);
+
+ /* RxSwitchSource - HDMI */
+ MST3367_TMDS_HOT_PLUG(sd, RX_TMDS_HPD_OFF);
+ MST3367_HDCP_RESET(sd);
+ MST3367_HDMI_RESET(sd);
+ mst3367_wr(sd, BANK0, 0x51, 0x89);
+ MST3367_TMDS_HOT_PLUG(sd, RX_TMDS_A_HPD_ON | RX_TMDS_A_LINK_ON);
+ mst3367_wr(sd, BANK0, 0xB7, 0x00);
+
+ /* Patches */
+ mst3367_wr(sd, BANK0, 0xE2, 0x00); /* DISABLE AUTO POSITION */
+ mst3367_wr(sd, BANK0, 0x1e, 0x11);
+ mst3367_wr(sd, BANK0, 0x1f, 0x01);
+ mst3367_wr(sd, BANK0, 0x73, 0x90);
+ mst3367_wr(sd, BANK0, 0xb5, 0x0c);
+
+ /* CSC */
+ mst3367_wr(sd, BANK0, 0x90, 0x15); /* Color Range */
+ mst3367_wr(sd, BANK0, 0x91, 0x15);
+
+ for (i = 0; i < sizeof(csctbl); i++)
+ mst3367_wr(sd, BANK0, 0x92 + i, csctbl[i]);
+
+ /* RX_OUTPUT_YUV422 / 08.BITS / EXTERNAL SYNC */
+ mst3367_wr(sd, BANK0, 0xB0, 0x20);
+
+ MST3367_HDMI_INIT(sd);
+}
+
+static int mst3367_s_power(struct v4l2_subdev *sd, int on)
+{
+ struct mst3367_state *state = get_mst3367_state(sd);
+
+ v4l2_dbg(1, debug, sd, "%s: power %s\n", __func__, on ? "on" : "off");
+
+ /* TODO: Turn on/off the TMDS clocks. */
+
+ state->power_on = on;
+ if (on)
+ mst3367_init_setup(sd);
+
+ return true;
+}
+
+static int mst3367_isr(struct v4l2_subdev *sd, u32 status, bool *handled)
+{
+ v4l2_dbg(0, debug, sd, "%s()\n", __func__);
+ *handled = true;
+ return 0;
+}
+
+static int mst3367_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh,
+ struct v4l2_event_subscription *sub)
+{
+ switch (sub->type) {
+ case V4L2_EVENT_SOURCE_CHANGE:
+ return v4l2_src_change_event_subdev_subscribe(sd, fh, sub);
+ case V4L2_EVENT_CTRL:
+ return v4l2_ctrl_subdev_subscribe_event(sd, fh, sub);
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct v4l2_subdev_core_ops mst3367_core_ops = {
+ .log_status = mst3367_log_status,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+ .g_register = mst3367_g_register,
+ .s_register = mst3367_s_register,
+#endif
+ .s_power = mst3367_s_power,
+ .interrupt_service_routine = mst3367_isr,
+ .subscribe_event = mst3367_subscribe_event,
+ .unsubscribe_event = v4l2_event_subdev_unsubscribe,
+};
+
+static int mst3367_s_stream(struct v4l2_subdev *sd, int enable)
+{
+ v4l2_dbg(1, debug, sd, "%s: %sable\n", __func__,
+ (enable ? "en" : "dis"));
+
+ if (!enable)
+ mst3367_s_power(sd, 0);
+
+ return 0;
+}
+
+static const struct v4l2_dv_timings_cap mst3367_timings_cap = {
+ .type = V4L2_DV_BT_656_1120,
+ /* keep this initialization for compatibility with GCC < 4.4.6 */
+ .reserved = {0},
+ V4L2_INIT_BT_TIMINGS(0, 1920, 0, 1200, 25000000, 170000000,
+ V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT |
+ V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT,
+ V4L2_DV_BT_CAP_PROGRESSIVE |
+ V4L2_DV_BT_CAP_REDUCED_BLANKING |
+ V4L2_DV_BT_CAP_CUSTOM)
+};
+
+static int mst3367_g_dv_timings(struct v4l2_subdev *sd,
+ struct v4l2_dv_timings *timings)
+{
+ struct mst3367_state *state = get_mst3367_state(sd);
+
+ v4l2_dbg(1, debug, sd, "%s:\n", __func__);
+
+ if (!state->detected_signal)
+ return -ENODATA;
+
+ *timings = state->detected_standard->timings;
+
+ return 0;
+}
+
+static int mst3367_enum_dv_timings(struct v4l2_subdev *sd,
+ struct v4l2_enum_dv_timings *timings)
+{
+ return v4l2_enum_dv_timings_cap(timings, &mst3367_timings_cap, NULL,
+ NULL);
+}
+
+static int mst3367_dv_timings_cap(struct v4l2_subdev *sd,
+ struct v4l2_dv_timings_cap *cap)
+{
+ if (cap->pad != 0)
+ return -EINVAL;
+
+ *cap = mst3367_timings_cap;
+
+ return 0;
+}
+
+static int mst3367_video_s_routing(struct v4l2_subdev *sd, u32 input,
+ u32 output, u32 config)
+{
+ v4l2_dbg(1, debug, sd, "%s(input=%d, output=%d, config=0x%x)\n",
+ __func__, input, output, config);
+
+ return 0;
+}
+
+static int mst3367_query_dv_timings(struct v4l2_subdev *sd,
+ struct v4l2_dv_timings *timings)
+{
+ struct mst3367_state *state = get_mst3367_state(sd);
+ int locked;
+ int ret;
+#if DUMP_SHADOWS || DUMP_REGISTERS
+ static int count;
+#endif
+
+ v4l2_dbg(2, debug, sd, "%s()\n", __func__);
+
+ memset(timings, 0, sizeof(struct v4l2_dv_timings));
+
+ /* Perform video standard detection. */
+ ret = MST3367_HDMI_MODE_DETECT(sd, &locked);
+ if (ret < 0 || !locked) {
+ /* No timings could be detected because no signal was found. */
+ return ret;
+ }
+
+ /* We're detected a signal, return formal timing. */
+ *timings = state->detected_standard->timings;
+
+ if (debug > 1) {
+ v4l2_print_dv_timings(sd->name, "timings: ", timings, true);
+ MST3367_HdmiGetPacketColor(sd);
+ }
+#if DUMP_SHADOWS
+ if (count++ > 6) {
+ count = 0;
+ dump_shadows(sd, 0);
+ dump_shadows(sd, 1);
+ dump_shadows(sd, 2);
+ }
+#endif
+
+#if DUMP_REGISTERS
+ if (count++ > 10) {
+ count = 0;
+ dump_registers(sd, BANK0);
+ dump_registers(sd, BANK1);
+ dump_registers(sd, BANK2);
+ }
+#endif
+
+ return 0; /* Success - Signal locked */
+}
+
+static int mst3367_g_input_status(struct v4l2_subdev *sd, u32 *status)
+{
+ struct mst3367_state *state = get_mst3367_state(sd);
+
+ if (state->detected_signal) {
+ /* Clear these failed bits, we have a signal. */
+ *status &= ~V4L2_IN_ST_NO_POWER;
+ *status &= ~V4L2_IN_ST_NO_SIGNAL;
+ } else {
+ /* Establish failed bits. */
+ *status |= V4L2_IN_ST_NO_POWER;
+ *status |= V4L2_IN_ST_NO_SIGNAL;
+ }
+
+ return 0;
+}
+
+static const struct v4l2_subdev_video_ops mst3367_video_ops = {
+ .s_stream = mst3367_s_stream,
+ .g_dv_timings = mst3367_g_dv_timings,
+ .s_routing = mst3367_video_s_routing,
+ .query_dv_timings = mst3367_query_dv_timings,
+ .g_input_status = mst3367_g_input_status,
+};
+
+static const struct v4l2_subdev_pad_ops mst3367_pad_ops = {
+ .enum_dv_timings = mst3367_enum_dv_timings,
+ .dv_timings_cap = mst3367_dv_timings_cap,
+};
+
+static const struct v4l2_subdev_ops mst3367_ops = {
+ .core = &mst3367_core_ops,
+ .video = &mst3367_video_ops,
+ .pad = &mst3367_pad_ops,
+};
+
+static int mst3367_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct mst3367_state *state;
+ struct v4l2_ctrl_handler *hdl;
+ struct v4l2_subdev *sd;
+ int err = -EIO;
+
+ v4l_dbg(1, debug, client, "%s()\n", __func__);
+
+ /* Check if the adapter supports the needed features */
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE_DATA)) {
+ v4l_err(client, "%s() no dice!\n", __func__);
+ return -EIO;
+ }
+
+ v4l_dbg(1, debug, client, "detecting mst3367 client on address 0x%x\n",
+ client->addr << 1);
+
+ state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL);
+ if (!state)
+ return -ENOMEM;
+
+ state->current_bank = 0xff;
+ state->i2c = client->adapter;
+ state->i2c_addr = 0x9c;
+
+ sd = &state->sd;
+ v4l2_i2c_subdev_init(sd, client, &mst3367_ops);
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+ hdl = &state->hdl;
+ v4l2_ctrl_handler_init(hdl, 2);
+
+ state->hotplug_ctrl =
+ v4l2_ctrl_new_std(hdl, NULL, V4L2_CID_DV_TX_HOTPLUG, 0, 1, 0, 0);
+ state->rx_sense_ctrl =
+ v4l2_ctrl_new_std(hdl, NULL, V4L2_CID_DV_TX_RXSENSE, 0, 1, 0, 0);
+ sd->ctrl_handler = hdl;
+ if (hdl->error) {
+ err = hdl->error;
+ goto err_hdl;
+ }
+
+ if (mst3367_rd(sd, BANK0, 0x50) != 1) {
+ v4l2_err(sd, "chip_revision != 1\n");
+ err = -EIO;
+ goto err_hdl;
+ }
+
+ state->detected_signal = 0;
+
+ mst3367_init_setup(sd);
+ v4l2_ctrl_handler_setup(&state->hdl);
+
+ v4l2_info(sd, "%s found and initialized @ addr 0x%x (%s)\n",
+ client->name, client->addr << 1, client->adapter->name);
+
+ if (debug)
+ v4l2_info(sd, "Debugging is enabled\n");
+
+ v4l2_info(sd, "driver loaded\n");
+
+ return 0;
+
+err_hdl:
+ v4l2_ctrl_handler_free(&state->hdl);
+ return err;
+}
+
+static int mst3367_remove(struct i2c_client *client)
+{
+ struct v4l2_subdev *sd = i2c_get_clientdata(client);
+
+ v4l2_dbg(1, debug, sd, "%s()\n", __func__);
+
+ v4l2_dbg(1, debug, sd, "%s removed @ 0x%x (%s)\n", client->name,
+ client->addr << 1, client->adapter->name);
+
+ v4l2_device_unregister_subdev(sd);
+ v4l2_ctrl_handler_free(sd->ctrl_handler);
+
+ v4l_info(client, "driver unloaded\n");
+
+ return 0;
+}
+
+static struct i2c_device_id mst3367_id[] = {
+ {"mst3367", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, mst3367_id);
+
+static struct i2c_driver mst3367_driver = {
+ .driver = {
+ .name = "mst3367",
+ },
+ .probe = mst3367_probe,
+ .remove = mst3367_remove,
+ .id_table = mst3367_id,
+};
+
+module_i2c_driver(mst3367_driver);
diff --git a/include/media/i2c/mst3367.h b/include/media/i2c/mst3367.h
new file mode 100644
index 000000000000..bdbe70258df3
--- /dev/null
+++ b/include/media/i2c/mst3367.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Driver for the MSTAR 3367 HDMI Receiver
+ *
+ * Copyright (c) 2017 Steven Toth <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *
+ * GNU General Public License for more details.
+ */
+
+#ifndef MST3367_H
+#define MST3367_H
+
+/* notify events */
+#define MST3367_SOURCE_DETECT 0
+
+struct mst3367_source_detect {
+ int present;
+};
+
+#endif /* MST3367_H */
--
2.19.1


2018-11-27 07:59:05

by Hans Verkuil

[permalink] [raw]
Subject: Re: [PATCH v2 0/2] media: Startech usb2hdcapm hdmi2usb framegrabber support

Hi Michael,

Apologies for completely missing your v1 post for this driver. It was
posted just before the ELCE and it looks like it just fell through the
cracks.

Anyway, I did a quick scan and found a few high-level things that need
to be addressed before I will start reviewing:

1) Please add the output of 'v4l2-compliance -s' to this cover letter
(test with a valid source connected). Obviously any failures should
be addressed, and if possible also all warnings.

2) Add entries for the new drivers to the MAINTAINERS file.

3) Since you added SPDX lines you can drop the actual license texts. It's
one or the other, not both.

4) I see references to a firmware, but it appears to be a firmware that's
loaded from an on-board eeprom or something, not an external firmware
file. Correct? If I'm wrong and it is an external fw file, then where
does it come from?

5) s_dv_timings is missing. That can't be right. Drivers shall *never*
change timings automatically when they detect new timings. Instead
they should send the SOURCE_CHANGE event to userspace, userspace will
stop streaming, call QUERY_DV_TIMINGS and, if a valid signal was detected,
call S_DV_TIMINGS with the new timings and restart streaming.

Regards,

Hans

On 11/26/2018 07:09 PM, Michael Grzeschik wrote:
> This series adds support for the Startech usb2hdcapm framegrabber. The
> code is based on the external kernel module code from Steven Toth's
> github page:
>
> https://github.com/stoth68000/hdcapm/
>
> We applied checkpatch.pl --strict and cleaned up the 80 character
> length, whitespace issues and replaced simple printks with appropriate
> v4l2_* or dev_* helpers, used WARN_ON instead of BUG and changed all
> errors and warnings checkpatch was complaining about.
>
> Steven Toth (2):
> media: mst3367: add support for mstar mst3367 HDMI RX
> media: hdcapm: add support for usb2hdcapm hdmi2usb framegrabber from
> startech
>
> drivers/media/i2c/Kconfig | 10 +
> drivers/media/i2c/Makefile | 1 +
> drivers/media/i2c/mst3367.c | 1104 ++++++++++++++++++
> drivers/media/usb/Kconfig | 1 +
> drivers/media/usb/Makefile | 1 +
> drivers/media/usb/hdcapm/Kconfig | 11 +
> drivers/media/usb/hdcapm/Makefile | 3 +
> drivers/media/usb/hdcapm/hdcapm-buffer.c | 230 ++++
> drivers/media/usb/hdcapm/hdcapm-compressor.c | 782 +++++++++++++
> drivers/media/usb/hdcapm/hdcapm-core.c | 743 ++++++++++++
> drivers/media/usb/hdcapm/hdcapm-i2c.c | 332 ++++++
> drivers/media/usb/hdcapm/hdcapm-reg.h | 111 ++
> drivers/media/usb/hdcapm/hdcapm-video.c | 665 +++++++++++
> drivers/media/usb/hdcapm/hdcapm.h | 283 +++++
> include/media/i2c/mst3367.h | 29 +
> 15 files changed, 4306 insertions(+)
> create mode 100644 drivers/media/i2c/mst3367.c
> create mode 100644 drivers/media/usb/hdcapm/Kconfig
> create mode 100644 drivers/media/usb/hdcapm/Makefile
> create mode 100644 drivers/media/usb/hdcapm/hdcapm-buffer.c
> create mode 100644 drivers/media/usb/hdcapm/hdcapm-compressor.c
> create mode 100644 drivers/media/usb/hdcapm/hdcapm-core.c
> create mode 100644 drivers/media/usb/hdcapm/hdcapm-i2c.c
> create mode 100644 drivers/media/usb/hdcapm/hdcapm-reg.h
> create mode 100644 drivers/media/usb/hdcapm/hdcapm-video.c
> create mode 100644 drivers/media/usb/hdcapm/hdcapm.h
> create mode 100644 include/media/i2c/mst3367.h
>