From: Steven Toth <stoth@xxxxxxxxxxxxxx> 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 <stoth@xxxxxxxxxxxxxx> Signed-off-by: Michael Grzeschik <m.grzeschik@xxxxxxxxxxxxxx> --- 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: linux-parisc@xxxxxxxxxxxxxxx S: Maintained F: sound/parisc/harmony.* +HDCAPM USB HDMI FRAME GRABBER DRIVER +M: Michael Grzeschik <m.grzeschik@xxxxxxxxxxxxxx> +L: linux-media@xxxxxxxxxxxxxxx +S: Maintained +F: drivers/media/usb/hdcapm/ + HDPVR USB VIDEO ENCODER DRIVER M: Hans Verkuil <hverkuil@xxxxxxxxx> L: linux-media@xxxxxxxxxxxxxxx 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 <stoth@xxxxxxxxxxxxxx> + * + * 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 <stoth@xxxxxxxxxxxxxx> + * + * 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 <stoth@xxxxxxxxxxxxxx> + * + * 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 <stoth@xxxxxxxxxxxxxx>"); +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 <stoth@xxxxxxxxxxxxxx> + * + * 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 <stoth@xxxxxxxxxxxxxx> + * + * 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 <stoth@xxxxxxxxxxxxxx> + * + * 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 <stoth@xxxxxxxxxxxxxx> + * + * 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