2018-10-19 10:57:54

by Michael Grzeschik

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

MAINTAINERS | 12 +
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 +
16 files changed, 4318 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.0



2018-10-19 10:56:23

by Michael Grzeschik

[permalink] [raw]
Subject: [PATCH 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]>
---
MAINTAINERS | 6 +
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 +++++++
12 files changed, 3168 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/MAINTAINERS b/MAINTAINERS
index 9c69b7f9b2f9..20c04092d3c5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6486,6 +6486,12 @@ L: [email protected]
S: Maintained
F: sound/parisc/harmony.*

+HDCAPM USB HDMI FRAME GRABBER DRIVER
+M: Michael Grzeschik <[email protected]>
+L: [email protected]
+S: Maintained
+F: drivers/media/usb/hdcapm/
+
HDPVR USB VIDEO ENCODER DRIVER
M: Hans Verkuil <[email protected]>
L: [email protected]
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.0


2018-10-19 10:56:36

by Michael Grzeschik

[permalink] [raw]
Subject: [PATCH 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]>
---
MAINTAINERS | 6 +
drivers/media/i2c/Kconfig | 10 +
drivers/media/i2c/Makefile | 1 +
drivers/media/i2c/mst3367.c | 1104 +++++++++++++++++++++++++++++++++++
include/media/i2c/mst3367.h | 29 +
5 files changed, 1150 insertions(+)
create mode 100644 drivers/media/i2c/mst3367.c
create mode 100644 include/media/i2c/mst3367.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 556f902b3766..9c69b7f9b2f9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9787,6 +9787,12 @@ L: [email protected]
S: Maintained
F: drivers/mtd/devices/docg3*

+MT9M032 APTINA SENSOR DRIVER
+M: Michael Grzeschik <[email protected]>
+S: Maintained
+F: drivers/media/i2c/mst3367.c
+F: include/media/i2c/mst3367.h
+
MT9M032 APTINA SENSOR DRIVER
M: Laurent Pinchart <[email protected]>
L: [email protected]
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 82af97430e5b..3de53a09d88a 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 a94eb03d10d4..f3e7a35018f8 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.0


2018-10-19 11:03:02

by Lucas Stach

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

Am Freitag, den 19.10.2018, 12:54 +0200 schrieb Michael Grzeschik:
> > 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]>
> ---
>  MAINTAINERS                 |    6 +
>  drivers/media/i2c/Kconfig   |   10 +
>  drivers/media/i2c/Makefile  |    1 +
>  drivers/media/i2c/mst3367.c | 1104 +++++++++++++++++++++++++++++++++++
>  include/media/i2c/mst3367.h |   29 +
>  5 files changed, 1150 insertions(+)
>  create mode 100644 drivers/media/i2c/mst3367.c
>  create mode 100644 include/media/i2c/mst3367.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 556f902b3766..9c69b7f9b2f9 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -9787,6 +9787,12 @@ L: [email protected]
> >  S: Maintained
> >  F: drivers/mtd/devices/docg3*
>  
> +MT9M032 APTINA SENSOR DRIVER
> > > +M: Michael Grzeschik <[email protected]>
> > +S: Maintained
> > +F: drivers/media/i2c/mst3367.c
> > +F: include/media/i2c/mst3367.h

Das sollte nicht in diesem Patch landen, oder?

Gruß
Lucas

2018-11-07 13:01:51

by Michael Grzeschik

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

On Fri, Oct 19, 2018 at 01:02:19PM +0200, Lucas Stach wrote:
> Am Freitag, den 19.10.2018, 12:54 +0200 schrieb Michael Grzeschik:
> > > 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]>
> > ---
> > ?MAINTAINERS?????????????????|????6 +
> > ?drivers/media/i2c/Kconfig???|???10 +
> > ?drivers/media/i2c/Makefile??|????1 +
> > ?drivers/media/i2c/mst3367.c | 1104 +++++++++++++++++++++++++++++++++++
> > ?include/media/i2c/mst3367.h |???29 +
> > ?5 files changed, 1150 insertions(+)
> > ?create mode 100644 drivers/media/i2c/mst3367.c
> > ?create mode 100644 include/media/i2c/mst3367.h
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 556f902b3766..9c69b7f9b2f9 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -9787,6 +9787,12 @@ L: [email protected]
> > > ?S: Maintained
> > > ?F: drivers/mtd/devices/docg3*
> > ?
> > +MT9M032 APTINA SENSOR DRIVER
> > > > +M: Michael Grzeschik <[email protected]>
> > > +S: Maintained
> > > +F: drivers/media/i2c/mst3367.c
> > > +F: include/media/i2c/mst3367.h
>
> Das sollte nicht in diesem Patch landen, oder?

Yes, I will remove it from the patch and put this into a separate one.

Are there any more suggestions for this series?

Regards,
Michael

--
Pengutronix e.K. | |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |


Attachments:
(No filename) (1.83 kB)
signature.asc (849.00 B)
Download all attachments