From nobody Thu Apr 2 14:06:32 2026 Received: from mail-oo1-f47.google.com (mail-oo1-f47.google.com [209.85.161.47]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 128242566F7 for ; Sat, 28 Mar 2026 03:15:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.161.47 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774667716; cv=none; b=LjeC1PRHpMWeC2OWD9DCqk9u3KAK4/ZiqESNCgoTtpaXuGxfql8wc3tWuGx2liI0/yYU1VJpwMZUx9VVdJJDKgEr2hGGbNo+LjW1x0aw8Mvw/fFRjKddUsE2keGQ3eo+t8yIJ1dpNGh0yEuBeNe1NGJZn9TsmduGE9VqiWBppAE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774667716; c=relaxed/simple; bh=/LFXMK0n/bqBjrwX5x4/VKz00/qbZaOE1rEhO8kOp04=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=AZyD4jpRBSk9Cdt3Qrv5wvdgXFwKenYgBPnfhQ/Sp3Tw8aJydIlLHnZQWV7TvwBOZa49VK8AATs7ztAUEEGMi+W5DDV46oc+WNPGBV6X9U2IRMLMad+/wEqTcFgBl0LTm3PY3B38lH027gO5he29YzvWDGPMkd+twJiF9KmFsWc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=K2AWAo+K; arc=none smtp.client-ip=209.85.161.47 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="K2AWAo+K" Received: by mail-oo1-f47.google.com with SMTP id 006d021491bc7-67e0ac36d87so1107658eaf.1 for ; Fri, 27 Mar 2026 20:15:09 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1774667709; x=1775272509; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=o2rOZhfdE38JHPwKo9/bc2t+JJCFVgZzJT8oWO5wHLI=; b=K2AWAo+KjSMoPYmp+cRzWrHiIDaSKUp5jRIwa6Z3m6M6Kwcg0+yYg4vmmWlyzy7DW6 QuL8xNWubEjUa2NGZ/BNl/5Hv7754eMeXwYaD/2TdjtZKDDpR18d/jBFMHBH3xfNgvd+ Etqf3qMMgZb/mLKhnfB7qo03Q1vB7xh6s0dRvQrdFh1JesdcDWyhlhzB109QdBa6E9aG HZjkO8Dx/BeLvpLQAHLngQKsL/rkTiGEq/woI+XtJqOkUPjHksyn6KyuQ71FWz7ZAMwi cnjI2qQqqseVxs2q5sM8wdk/qViWGjxsECjF4S4d+5X/CIe4enEnzmeiCfV9Ho+Y9Hsv 5FyQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774667709; x=1775272509; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=o2rOZhfdE38JHPwKo9/bc2t+JJCFVgZzJT8oWO5wHLI=; b=ibmOAEt/tPtIcGDnVyDRiOt1R0xwE8PAX35lC4p/7j5gygUNxS+e5DaQ4LQTGW7hY3 XVfeKLk73dvuK6EDZkHGjrn8D6h7v8dY5OUdCXExv1WQIjJscO9N5rCyjjq82VyTOqx9 Rcrggt/y5TYiwrVRX1yDzhg2lZxIQwzWzLDAMOmC5m3isM4/6bJzdv06HBYB64X4lNMp pfYfu9gfNKgEAwVWPqPTvV/D7Ft+4IsgGvobIXXVcgOuTCDOIXQ71pEXehoPTGHSgwNN TeFBjvQ0VSH0HHl08jjBH7G5H+kRrCaAzncIczuw9ZJmfRgj8cR+/dePqekUE9wuIbTN Stbg== X-Gm-Message-State: AOJu0YxjlMG8fhfQtFfSLrekWWraYkXN23OHIPMcWrCK79ClY/8dmg6H 8DdYx0vQpMBEXUgyvoGljamnTkLDvEbrH9n1iWQTecgoGQaEBLYcWiU= X-Gm-Gg: ATEYQzxm3lVLNgVVLfXcD2yoEjWddktfycC8Yl6xpnbwLyKsMBH/5zRzjbAyc2IFn3G HgBaqlCbe38r2djmRyTjBU0EuGBaKnRr2uzY6aJW7b7yV5yF80qpGAEz7B7gmBJ+04wEXkG5Uet y1v10+nrGknVwHxoB11f7lWDmHmpCNamTrxqpsO4sJtNGBp21gczu67wQEHnO3aEk4doTDNILBM LyVJo/JzYj9tONinZ4ImMMLQq8UCJJbp6hlX4CEJffGgZCKo5iSLTrxmnFIbfd3D9tNt57FYA9F PMpUehucSAGYUHCc1DBXwsQfXKjrJUMNSuryKv+slFFUrZKabUGSf2mgP2DYaOh7unatbBkWJDX ZeiVdENyt9NxSemyQdZCKJLuFW0Mx1vF/Ce7yaHvAZYzd9/MTfOlNmn3g5/WcqIGDZMouRrZRLc xfCUUPTFUe0Jg+eEezmuZ1fdZaGVyuiVYtcl6XsyxfibIKUNOjjI8= X-Received: by 2002:a05:6820:f09:b0:67e:cf9:ea2a with SMTP id 006d021491bc7-67e18629f9emr2628076eaf.17.1774667708239; Fri, 27 Mar 2026 20:15:08 -0700 (PDT) Received: from miso.lan ([136.62.61.36]) by smtp.gmail.com with ESMTPSA id 006d021491bc7-67e231ad680sm726309eaf.10.2026.03.27.20.15.05 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 27 Mar 2026 20:15:07 -0700 (PDT) From: Matthew Laux To: Mauro Carvalho Chehab , Greg Kroah-Hartman , Hans Verkuil Cc: linux-kernel@vger.kernel.org, linux-media@vger.kernel.org, linux-staging@lists.linux.dev, Matthew Laux Subject: [PATCH v2 1/1] staging: media: add driver for StarTech USB3HDCAP Date: Fri, 27 Mar 2026 22:14:18 -0500 Message-ID: <20260328031418.10459-2-matthew.laux@gmail.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260328031418.10459-1-matthew.laux@gmail.com> References: <20260328031418.10459-1-matthew.laux@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add a V4L2 driver for the StarTech USB3HDCAP video capture device. This is a popular capture card based on the MST3367 HDMI receiver and TW9900 composite video decoder. Support is included for the composite, S-Video, component, and HDMI inputs, and these inputs have been tested at various resolutions. Audio support is provided by an ALSA device. Also includes preliminary support for the Micomsoft XCAPTURE-1 hardware variant. I've provided a TODO containing the remaining items necessary to get this out of staging. Signed-off-by: Matthew Laux --- v2: remove unused CSC matrix control, remove version.h include, fix all remaining `check`-level checkpatch issues, update TODO MAINTAINERS | 8 + drivers/staging/media/Kconfig | 2 + drivers/staging/media/Makefile | 1 + drivers/staging/media/usb3hdcap/Kconfig | 13 + drivers/staging/media/usb3hdcap/Makefile | 4 + drivers/staging/media/usb3hdcap/TODO | 15 + .../staging/media/usb3hdcap/usb3hdcap-audio.c | 301 +++++ .../media/usb3hdcap/usb3hdcap-composite.c | 182 +++ .../staging/media/usb3hdcap/usb3hdcap-core.c | 1002 +++++++++++++++++ .../staging/media/usb3hdcap/usb3hdcap-hdmi.c | 802 +++++++++++++ .../staging/media/usb3hdcap/usb3hdcap-video.c | 505 +++++++++ drivers/staging/media/usb3hdcap/usb3hdcap.h | 234 ++++ 12 files changed, 3069 insertions(+) create mode 100644 drivers/staging/media/usb3hdcap/Kconfig create mode 100644 drivers/staging/media/usb3hdcap/Makefile create mode 100644 drivers/staging/media/usb3hdcap/TODO create mode 100644 drivers/staging/media/usb3hdcap/usb3hdcap-audio.c create mode 100644 drivers/staging/media/usb3hdcap/usb3hdcap-composite.c create mode 100644 drivers/staging/media/usb3hdcap/usb3hdcap-core.c create mode 100644 drivers/staging/media/usb3hdcap/usb3hdcap-hdmi.c create mode 100644 drivers/staging/media/usb3hdcap/usb3hdcap-video.c create mode 100644 drivers/staging/media/usb3hdcap/usb3hdcap.h diff --git a/MAINTAINERS b/MAINTAINERS index 750ac8c4a7b0..bab204eeab56 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25303,6 +25303,14 @@ S: Supported F: Documentation/devicetree/bindings/interrupt-controller/starfive,jh8100-= intc.yaml F: drivers/irqchip/irq-starfive-jh8100-intc.c =20 +STARTECH USB3HDCAP MEDIA DRIVER +M: Matthew Laux +L: linux-media@vger.kernel.org +S: Supported +W: https://linuxtv.org +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +F: drivers/staging/media/usb3hdcap/ + STATIC BRANCH/CALL M: Peter Zijlstra M: Josh Poimboeuf diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig index 1aa31bddf970..3f4cbc190a7a 100644 --- a/drivers/staging/media/Kconfig +++ b/drivers/staging/media/Kconfig @@ -38,6 +38,8 @@ source "drivers/staging/media/sunxi/Kconfig" =20 source "drivers/staging/media/tegra-video/Kconfig" =20 +source "drivers/staging/media/usb3hdcap/Kconfig" + menuconfig STAGING_MEDIA_DEPRECATED bool "Media staging drivers (DEPRECATED)" default n diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile index 6f78b0edde1e..e8c5fd2725f1 100644 --- a/drivers/staging/media/Makefile +++ b/drivers/staging/media/Makefile @@ -9,3 +9,4 @@ obj-$(CONFIG_VIDEO_TEGRA) +=3D tegra-video/ obj-$(CONFIG_VIDEO_IPU3_IMGU) +=3D ipu3/ obj-$(CONFIG_VIDEO_INTEL_IPU7) +=3D ipu7/ obj-$(CONFIG_DVB_AV7110) +=3D av7110/ +obj-$(CONFIG_VIDEO_USB3HDCAP) +=3D usb3hdcap/ diff --git a/drivers/staging/media/usb3hdcap/Kconfig b/drivers/staging/medi= a/usb3hdcap/Kconfig new file mode 100644 index 000000000000..edca098bd334 --- /dev/null +++ b/drivers/staging/media/usb3hdcap/Kconfig @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 +config VIDEO_USB3HDCAP + tristate "USB3HDCAP video capture support" + depends on VIDEO_DEV && SND + select SND_PCM + select VIDEOBUF2_VMALLOC + + help + This is a video4linux2 driver for the StarTech USB3HDCAP video/audio + capture device. + + To compile this driver as a module, choose M here; the + module will be called usb3hdcap. diff --git a/drivers/staging/media/usb3hdcap/Makefile b/drivers/staging/med= ia/usb3hdcap/Makefile new file mode 100644 index 000000000000..b72e2464ab1d --- /dev/null +++ b/drivers/staging/media/usb3hdcap/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 +usb3hdcap-y :=3D usb3hdcap-core.o usb3hdcap-video.o usb3hdcap-composite.o \ + usb3hdcap-hdmi.o usb3hdcap-audio.o +obj-$(CONFIG_VIDEO_USB3HDCAP) +=3D usb3hdcap.o diff --git a/drivers/staging/media/usb3hdcap/TODO b/drivers/staging/media/u= sb3hdcap/TODO new file mode 100644 index 000000000000..92a35a3a0388 --- /dev/null +++ b/drivers/staging/media/usb3hdcap/TODO @@ -0,0 +1,15 @@ +* Prerequisite for using subdevices: i2c_algorithm/i2c_adapter that conver= ts + to the correct USB vendor requests depending on whether the hardware has= the + NUC100 or not +* Use existing tw9900 subdevice + * Need to make the regulator optional + * Need to only call `v4l2_async_register_subdev` when registering from + device tree so this driver can call `v4l2_device_register_subdev` + directly + * Need to port over my additional functionality to tw9900.c +* Write new subdevice for MST3367 + * Won't be much use to any other driver initially +* Generify CS53L21 support (can probably base it on sound/soc/codecs/cs53l= 30.c) +* Use v4l2_fh +* Investigate MST3367 situation with XCAPTURE-1 - hdmi signal disappears a= fter + a frame or two - HDCP related? diff --git a/drivers/staging/media/usb3hdcap/usb3hdcap-audio.c b/drivers/st= aging/media/usb3hdcap/usb3hdcap-audio.c new file mode 100644 index 000000000000..42bd1dcd70d8 --- /dev/null +++ b/drivers/staging/media/usb3hdcap/usb3hdcap-audio.c @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB3HDCAP audio support - CS53L21 init + ALSA capture device + * + * Audio is embedded in the isochronous video stream as 48kHz stereo + * 16-bit LE PCM. Each video line carries 8, 12, or 16 bytes of audio data. + */ + +#include +#include +#include +#include + +#include "usb3hdcap.h" + +/* ALSA buffer sizing */ +#define HDCAP_AUDIO_BUFSZ (64 * 1024) + +static const struct snd_pcm_hardware snd_hdcap_hw =3D { + .info =3D SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID, + .formats =3D SNDRV_PCM_FMTBIT_S16_LE, + .rates =3D SNDRV_PCM_RATE_48000, + .rate_min =3D 48000, + .rate_max =3D 48000, + .channels_min =3D 2, + .channels_max =3D 2, + .period_bytes_min =3D 1024, + .period_bytes_max =3D 65536, + .periods_min =3D 2, + .periods_max =3D 128, + .buffer_bytes_max =3D HDCAP_AUDIO_BUFSZ, +}; + +/* ------------------------------------------------------------------ */ +/* PCM ops */ +/* ------------------------------------------------------------------ */ + +static int snd_hdcap_pcm_open(struct snd_pcm_substream *substream) +{ + struct usb3hdcap *hdcap =3D snd_pcm_substream_chip(substream); + + hdcap->snd_substream =3D substream; + substream->runtime->hw =3D snd_hdcap_hw; + + return 0; +} + +static int snd_hdcap_pcm_close(struct snd_pcm_substream *substream) +{ + struct usb3hdcap *hdcap =3D snd_pcm_substream_chip(substream); + + atomic_set(&hdcap->snd_stream, 0); + hdcap->snd_substream =3D NULL; + + return 0; +} + +static int snd_hdcap_prepare(struct snd_pcm_substream *substream) +{ + struct usb3hdcap *hdcap =3D snd_pcm_substream_chip(substream); + + hdcap->snd_buffer_pos =3D 0; + hdcap->snd_period_pos =3D 0; + + return 0; +} + +static int snd_hdcap_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct usb3hdcap *hdcap =3D snd_pcm_substream_chip(substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + atomic_set(&hdcap->snd_stream, 1); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + atomic_set(&hdcap->snd_stream, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t snd_hdcap_pointer(struct snd_pcm_substream *subst= ream) +{ + struct usb3hdcap *hdcap =3D snd_pcm_substream_chip(substream); + + return hdcap->snd_buffer_pos; +} + +static const struct snd_pcm_ops snd_hdcap_pcm_ops =3D { + .open =3D snd_hdcap_pcm_open, + .close =3D snd_hdcap_pcm_close, + .prepare =3D snd_hdcap_prepare, + .trigger =3D snd_hdcap_trigger, + .pointer =3D snd_hdcap_pointer, +}; + +/* ------------------------------------------------------------------ */ +/* Audio data feed (called from ISO URB handler in usb3hdcap-core.c) */ +/* ------------------------------------------------------------------ */ + +void usb3hdcap_audio_data(struct usb3hdcap *hdcap, const u8 *data, int len) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + size_t frame_bytes, nframes; + size_t buffer_pos, period_pos; + int period_elapsed =3D 0; + unsigned long flags; + + if (!atomic_read(&hdcap->snd_stream)) + return; + + substream =3D hdcap->snd_substream; + if (!substream) + return; + + runtime =3D substream->runtime; + if (!runtime || !runtime->dma_area) + return; + + frame_bytes =3D runtime->frame_bits >> 3; + nframes =3D len / frame_bytes; + if (!nframes) + return; + + snd_pcm_stream_lock_irqsave(substream, flags); + + buffer_pos =3D hdcap->snd_buffer_pos; + period_pos =3D hdcap->snd_period_pos; + + /* Copy into ALSA ring buffer, handling wrap */ + if (buffer_pos + nframes >=3D runtime->buffer_size) { + size_t cnt =3D (runtime->buffer_size - buffer_pos) * frame_bytes; + + memcpy(runtime->dma_area + buffer_pos * frame_bytes, data, cnt); + memcpy(runtime->dma_area, data + cnt, nframes * frame_bytes - cnt); + } else { + memcpy(runtime->dma_area + buffer_pos * frame_bytes, data, + nframes * frame_bytes); + } + + buffer_pos +=3D nframes; + period_pos +=3D nframes; + + if (buffer_pos >=3D runtime->buffer_size) + buffer_pos -=3D runtime->buffer_size; + + if (period_pos >=3D runtime->period_size) { + period_pos -=3D runtime->period_size; + period_elapsed =3D 1; + } + + hdcap->snd_buffer_pos =3D buffer_pos; + hdcap->snd_period_pos =3D period_pos; + + snd_pcm_stream_unlock_irqrestore(substream, flags); + + if (period_elapsed) + snd_pcm_period_elapsed(substream); +} + +/* ------------------------------------------------------------------ */ +/* CS53L21 codec init */ +/* ------------------------------------------------------------------ */ + +int usb3hdcap_cs53l21_init(struct usb3hdcap *hdcap) +{ + int ret; + + /* MIC power control: power down mic B, mic A, and bias */ + /* SPEED =3D "10 - Half-Speed Mode (HSM) - 12.5 to 25 kHz sample rates" */ + ret =3D u3hc_i2c_write(hdcap, ADDR_CS53L21, CS53L21_MIC_PWR_CTL, 0x4e); + if (ret) + return ret; + + /* interface: I2S, master */ + ret =3D u3hc_i2c_write(hdcap, ADDR_CS53L21, CS53L21_IFACE_CTL, 0x44); + if (ret) + return ret; + + /* ADC input select: default */ + ret =3D u3hc_i2c_write(hdcap, ADDR_CS53L21, CS53L21_ADC_IN_SEL, 0x00); + if (ret) + return ret; + + /* ALC/PGA: 0 dB */ + ret =3D u3hc_i2c_write(hdcap, ADDR_CS53L21, CS53L21_ALC_PGAA, 0x00); + if (ret) + return ret; + ret =3D u3hc_i2c_write(hdcap, ADDR_CS53L21, CS53L21_ALC_PGAB, 0x00); + if (ret) + return ret; + + /* ADC attenuators: 0 dB */ + ret =3D u3hc_i2c_write(hdcap, ADDR_CS53L21, CS53L21_ADCA_ATT, 0x00); + if (ret) + return ret; + ret =3D u3hc_i2c_write(hdcap, ADDR_CS53L21, CS53L21_ADCB_ATT, 0x00); + if (ret) + return ret; + + /* ALC enable and attack rate =3D fastest attack */ + ret =3D u3hc_i2c_write(hdcap, ADDR_CS53L21, CS53L21_ALC_EN_ATK, 0xc0); + if (ret) + return ret; + + /* interface: "SPE Processed ADC data to ADC serial port, SDOUT data." */ + ret =3D u3hc_i2c_write(hdcap, ADDR_CS53L21, CS53L21_IFACE_CTL, 0x46); + if (ret) + return ret; + + /* SPE control: SPE enable, "Soft Ramp" turned on */ + ret =3D u3hc_i2c_write(hdcap, ADDR_CS53L21, CS53L21_SPE_CTL, 0x42); + if (ret) + return ret; + + /* ADC mixer volume: +2.5 dB */ + ret =3D u3hc_i2c_write(hdcap, ADDR_CS53L21, CS53L21_ADCA_MIX_VOL, 0x05); + if (ret) + return ret; + ret =3D u3hc_i2c_write(hdcap, ADDR_CS53L21, CS53L21_ADCB_MIX_VOL, 0x05); + if (ret) + return ret; + + /* channel mixer: off */ + ret =3D u3hc_i2c_write(hdcap, ADDR_CS53L21, CS53L21_CH_MIXER, 0x00); + if (ret) + return ret; + + return 0; +} + +/* ------------------------------------------------------------------ */ +/* ALSA card init / teardown */ +/* ------------------------------------------------------------------ */ + +int usb3hdcap_audio_init(struct usb3hdcap *hdcap) +{ + int ret; + struct snd_card *card; + struct snd_pcm *pcm; + + atomic_set(&hdcap->snd_stream, 0); + + ret =3D snd_card_new(hdcap->dev, SNDRV_DEFAULT_IDX1, "usb3hdcap", + THIS_MODULE, 0, &card); + if (ret < 0) + return ret; + + strscpy(card->driver, "usb3hdcap", sizeof(card->driver)); + strscpy(card->shortname, "USB3HDCAP Audio", sizeof(card->shortname)); + snprintf(card->longname, sizeof(card->longname), + "USB3HDCAP Audio at bus %d device %d", + hdcap->usb_dev->bus->busnum, hdcap->usb_dev->devnum); + + hdcap->snd =3D card; + + ret =3D snd_pcm_new(card, "USB3HDCAP Audio", 0, 0, 1, &pcm); + if (ret < 0) + goto err; + + strscpy(pcm->name, "USB3HDCAP Audio Input", sizeof(pcm->name)); + pcm->info_flags =3D 0; + pcm->private_data =3D hdcap; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_hdcap_pcm_ops); + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, NULL, + HDCAP_AUDIO_BUFSZ, HDCAP_AUDIO_BUFSZ); + + ret =3D snd_card_register(card); + if (ret) + goto err; + + dev_info(hdcap->dev, "ALSA audio device registered\n"); + return 0; + +err: + hdcap->snd =3D NULL; + snd_card_free(card); + return ret; +} + +void usb3hdcap_audio_free(struct usb3hdcap *hdcap) +{ + if (hdcap->snd && hdcap->usb_dev) { + snd_card_free_when_closed(hdcap->snd); + hdcap->snd =3D NULL; + } +} diff --git a/drivers/staging/media/usb3hdcap/usb3hdcap-composite.c b/driver= s/staging/media/usb3hdcap/usb3hdcap-composite.c new file mode 100644 index 000000000000..0e7bc4c430b7 --- /dev/null +++ b/drivers/staging/media/usb3hdcap/usb3hdcap-composite.c @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB3HDCAP composite video init - CPLD + TW9900 setup, signal lock + */ + +#include +#include +#include +#include + +#include "usb3hdcap.h" + +/* Detect PAL vs NTSC from TW9900 auto-detection result */ +static void usb3hdcap_detect_std(struct usb3hdcap *hdcap) +{ + int k, sdt, detected; + + /* 0x80 =3D start detection */ + /* 0x03 =3D enable recognition of PAL and NTSC only */ + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_SDTR, 0x83); + + for (k =3D 0; k < 10; k++) { + sdt =3D u3hc_i2c_read(hdcap, ADDR_TW9900, TW9900_SDT); + if (!(sdt & 0x80)) + break; + msleep(100); + } + + if (sdt & 0x80) { + dev_warn(hdcap->dev, "Detection not done after 1s, using NTSC\n"); + hdcap->std =3D V4L2_STD_NTSC; + return; + } + + detected =3D (sdt & TW9900_STDNOW_MASK) >> TW9900_STDNOW_SHIFT; + switch (detected) { + case TW9900_STD_NTSC_M: + hdcap->std =3D V4L2_STD_NTSC; + dev_info(hdcap->dev, "NTSC detected (SDT=3D0x%02x)\n", sdt); + break; + case TW9900_STD_PAL_BDGHI: + hdcap->std =3D V4L2_STD_PAL; + dev_info(hdcap->dev, "PAL detected (SDT=3D0x%02x)\n", sdt); + break; + default: /* not sure if this will be hit, because others are disabled */ + hdcap->std =3D V4L2_STD_NTSC; + dev_info(hdcap->dev, "Non-PAL/NTSC detected (SDT=3D0x%02x)\n", sdt); + break; + } + + hdcap->detected_timings_present =3D 0; +} + +/* Detect 240p/288p (non-interlaced) vs 480i/576i */ +static void usb3hdcap_detect_size(struct usb3hdcap *hdcap) +{ + int status; + int full_h =3D (hdcap->std & V4L2_STD_PAL) ? PAL_HEIGHT : NTSC_HEIGHT; + int half_h =3D full_h / 2; + + hdcap->width =3D SD_WIDTH; + hdcap->height =3D half_h; + + status =3D u3hc_i2c_read(hdcap, ADDR_TW9900, TW9900_CSTATUS_II); + if (status >=3D 0 && (status & TW9900_NINTL)) { + hdcap->interlaced =3D 0; + dev_info(hdcap->dev, "NINTL=3D1: %dp detected (SDTR=3D0x%02x)\n", + half_h, status); + } else { + hdcap->interlaced =3D 1; + dev_info(hdcap->dev, "NINTL=3D0: %di detected (SDTR=3D0x%02x)\n", + full_h, status); + } +} + +int usb3hdcap_composite_init(struct usb3hdcap *hdcap) +{ + int k, status; + + /* disable MST3367 */ + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x00, 0x00); /* bank 0 */ + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x51, 0x80); + + /* disable TW9900 */ + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_ACNTL_I, 0x0e); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_ACNTL_II, 0x40); + + /* select TW9900 input of cpld? */ + /* from windows driver */ + status =3D u3hc_i2c_read(hdcap, ADDR_CPLD, 0x3b); + if (status !=3D 0) + u3hc_i2c_write(hdcap, ADDR_CPLD, 0x3b, 0x80); + u3hc_i2c_write(hdcap, ADDR_CPLD, 0x20, 0x05); + u3hc_i2c_write(hdcap, ADDR_CPLD, 0x00, 0x01); + u3hc_i2c_write(hdcap, ADDR_CPLD, 0x10, 0xfe); + u3hc_i2c_write(hdcap, ADDR_CPLD, 0x20, 0x75); + + /* Main TW9900 configuration */ + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_ACNTL_I, 0x00); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_OPFORM, 0xa2); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_OPCNTL_I, 0x01); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_VDELAY_LO, 0x14); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_VACTIVE_LO, 0xf2); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_HDELAY_LO, 0x0b); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_HACTIVE_LO, 0xd2); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_VBICNTL, 0x57); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_ACNTL_II, 0x0f); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_OPCNTL_II, 0x00); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_SDT, 0x07); /* auto-detect */ + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_VCNTL1, 0x0c); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_VCNTL2, 0x03); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_MISC1, 0x07); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_MISC2, 0x06); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_ANAPLLCTL, 0x01); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_VVBI, 0x00); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_HSBEGIN, 0x26); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_HSEND, 0x36); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_OVSDLY, 0xf0); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_OVSEND, 0x28); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_ACNTL_I, 0x80); + if (hdcap->input =3D=3D INPUT_SVIDEO) { + /* "Input crystal clock frequency is 27MHz" */ + /* IFSEL 01 for S-Video, YSEL 01 for Y on Mux1 */ + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_INFORM, 0x54); + /* "internal current reference 2" + both luma and chroma ADCs on */ + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_ACNTL_I, 0x40); + + } else { + /* IFSEL 00 for composite, YSEL 00 for Y on Mux0 */ + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_INFORM, 0x40); + /* "internal current reference 2" + chroma ADC off */ + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_ACNTL_I, 0x42); + } + v4l2_ctrl_handler_setup(&hdcap->ctrl); + + vendor_out(hdcap, REQ_STREAM, 0x0000, 0, NULL, 0); + + for (k =3D 0; k < 10; k++) { + status =3D u3hc_i2c_read(hdcap, ADDR_TW9900, TW9900_CSTATUS); + + /* + * 0x40 =3D "Horizontal sync PLL is locked to the incoming video source" + * 0x08 =3D "Vertical logic is locked to the incoming video source" + */ + if (status >=3D 0 && (status & 0x48) =3D=3D 0x48) { + dev_info(hdcap->dev, "Signal locked (%d ms)\n", k * 100); + break; + } + msleep(100); + } + + if (k =3D=3D 10) { + dev_err(hdcap->dev, "No signal lock after 1s (last status=3D0x%02x)\n", + status); + return -ETIMEDOUT; + } + + usb3hdcap_detect_std(hdcap); + usb3hdcap_detect_size(hdcap); + + /* Adjust for PAL vs NTSC */ + if (hdcap->std & V4L2_STD_PAL) { + u3hc_i2c_rmw(hdcap, ADDR_TW9900, TW9900_CROP_HI, 0x0f, 0x10); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_VDELAY_LO, 0x19); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_VACTIVE_LO, 0x20); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_HDELAY_LO, 0x0a); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_HACTIVE_LO, 0xd0); + u3hc_i2c_rmw(hdcap, ADDR_TW9900, TW9900_VVBI, 0xef, 0x00); + } else { + u3hc_i2c_rmw(hdcap, ADDR_TW9900, TW9900_CROP_HI, 0x0f, 0x00); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_VDELAY_LO, 0x14); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_VACTIVE_LO, 0xf2); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_HDELAY_LO, 0x10); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_HACTIVE_LO, 0xd0); + u3hc_i2c_rmw(hdcap, ADDR_TW9900, TW9900_VVBI, 0xef, 0x10); + } + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_CNTRL2, 0x00); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_SDT_NOISE, 0x11); + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_LUMA_CTL, 0x00); + + return 0; +} diff --git a/drivers/staging/media/usb3hdcap/usb3hdcap-core.c b/drivers/sta= ging/media/usb3hdcap/usb3hdcap-core.c new file mode 100644 index 000000000000..134a4cc7f574 --- /dev/null +++ b/drivers/staging/media/usb3hdcap/usb3hdcap-core.c @@ -0,0 +1,1002 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * StarTech USB3HDCAP USB 3.0 HD Video Capture Driver + * a.k.a. YUAN High-Tech Development Co., Ltd UB530 + * + * Supported inputs are composite, S-Video, component, and HDMI. I haven't + * attempted to support DVI or VGA. + * + * Tested video modes (all at 60fps): + * composite 240p and 480i + * S-Video 240p and 480i + * component 240p, 480i, 480p, 720p, 1080i + * HDMI 1080p + * + * Other video modes will probably "just work" if the appropriate entries + * are added in the mode tables and code is added to disambiguate using + * htotal if necessary. + * + * Audio is supported and should work with all inputs. + * + * Also partially supports the Micomsoft XCAPTURE-1 for composite and + * S-Video only... HDMI is unusable, component is untested. + * + * Based on USBPcap analysis and reverse engineering of CY3014.X64.SYS + * (version 1.1.0.193 of Fri, 04 Jun 2021 09:37:45 UTC) + * MST3367 HDMI path based on hdcapm + * Driver structure based heavily on usbtv + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "usb3hdcap.h" + +#define USB3HDCAP_VID 0x1164 +#define USB3HDCAP_PID 0xf533 +#define XCAPTURE1_PID 0xf531 + +/* ------------------------------------------------------------------ */ +/* USB control transfer helpers */ +/* ------------------------------------------------------------------ */ + +int vendor_out(struct usb3hdcap *hdcap, u8 request, u16 value, u16 index, + u8 *data, u16 len) +{ + int ret; + u8 *buf =3D NULL; + + if (len > 0) { + buf =3D kmemdup(data, len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + } + + ret =3D usb_control_msg(hdcap->usb_dev, + usb_sndctrlpipe(hdcap->usb_dev, 0), + request, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, index, buf, len, 1000); + + kfree(buf); + + if (ret < 0) { + dev_err(hdcap->dev, "%s 0x%02x val=3D0x%04x idx=3D0x%04x: %d\n", + __func__, request, value, index, ret); + return ret; + } + + return 0; +} + +static int vendor_in(struct usb3hdcap *hdcap, u8 request, u16 value, u16 i= ndex, + u8 *data, u16 len) +{ + u8 *buf; + int ret; + + buf =3D kmalloc(len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret =3D usb_control_msg(hdcap->usb_dev, + usb_rcvctrlpipe(hdcap->usb_dev, 0), + request, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, index, buf, len, 1000); + + if (ret >=3D 0) + memcpy(data, buf, min_t(int, ret, len)); + + kfree(buf); + return ret; +} + +/* ------------------------------------------------------------------ */ +/* MCU I2C tunnel (XCAPTURE-1) */ +/* */ +/* The Nuvoton NUC100 MCU intercepts I2C traffic. Transactions are */ +/* tunneled through a different USB vendor protocol: */ +/* Write: bRequest=3D0xC0, wValue=3D0x5066 or addr|0x5000 = */ +/* Read: two-phase write+read, always through wValue=3D0x5066 */ +/* */ +/* Some addresses need the 0x66 "gateway" (target addr in payload), */ +/* others use the I2C addr directly in wValue with 0x5000 flag. */ +/* ------------------------------------------------------------------ */ + +static bool mcu_use_gateway(u8 addr) +{ + /* Matches Windows driver address routing logic */ + return ((u8)(addr + 0x78) & 0xeb) || addr =3D=3D 0x8c; +} + +static int mcu_i2c_write(struct usb3hdcap *hdcap, u8 addr, u8 reg, u8 val) +{ + u8 buf[3]; + + if (mcu_use_gateway(addr)) { + buf[0] =3D addr; + buf[1] =3D reg; + buf[2] =3D val; + return vendor_out(hdcap, REQ_I2C, 0x5066, 0, buf, 3); + } + + buf[0] =3D reg; + buf[1] =3D val; + return vendor_out(hdcap, REQ_I2C, addr | 0x5000, 0, buf, 2); +} + +static int mcu_i2c_read(struct usb3hdcap *hdcap, u8 addr, u8 reg) +{ + u8 tx[3] =3D { addr | 1, 1, reg }; + u8 val; + int ret; + + /* send target addr, rx length, and register */ + ret =3D vendor_out(hdcap, REQ_I2C, 0x5066, 0, tx, 3); + if (ret < 0) + return ret; + + /* Retrieve the result */ + ret =3D vendor_in(hdcap, REQ_I2C, 0x5066, 0, &val, 1); + if (ret < 0) { + dev_err(hdcap->dev, "%s 0x%02x reg 0x%02x: %d\n", + __func__, addr, reg, ret); + return ret; + } + + return val; +} + +int u3hc_i2c_write(struct usb3hdcap *hdcap, u8 addr, u8 reg, u8 val) +{ + if (hdcap->has_mcu) + return mcu_i2c_write(hdcap, addr, reg, val); + return vendor_out(hdcap, REQ_I2C, addr, reg, &val, 1); +} + +int u3hc_i2c_read(struct usb3hdcap *hdcap, u8 addr, u8 reg) +{ + u8 val; + int ret; + + if (hdcap->has_mcu) + return mcu_i2c_read(hdcap, addr, reg); + + ret =3D vendor_in(hdcap, REQ_I2C, addr, reg, &val, 1); + if (ret < 0) { + dev_err(hdcap->dev, "%s 0x%02x reg 0x%02x: %d\n", + __func__, addr, reg, ret); + return ret; + } + + return val; +} + +int u3hc_i2c_rmw(struct usb3hdcap *hdcap, u8 addr, u8 reg, u8 mask, u8 bit= s) +{ + int val =3D u3hc_i2c_read(hdcap, addr, reg); + + if (val < 0) + return val; + return u3hc_i2c_write(hdcap, addr, reg, (val & mask) | bits); +} + +int u3hc_i2c_rmw_get_old(struct usb3hdcap *hdcap, u8 addr, u8 reg, u8 mask= , u8 bits, u8 *old) +{ + int val =3D u3hc_i2c_read(hdcap, addr, reg); + + if (val < 0) + return val; + *old =3D val; + return u3hc_i2c_write(hdcap, addr, reg, (val & mask) | bits); +} + +/* CheckIsMCUExist() */ +static void usb3hdcap_probe_mcu(struct usb3hdcap *hdcap) +{ + u8 tx[5] =3D { 0xab, 0x03, 0x12, 0x34, 0x57 }; + u8 rx[3] =3D {}; + int k, ret; + + hdcap->has_mcu =3D 0; + + /* Assert CPLD GPIO 0x39 to wake MCU? */ + vendor_out(hdcap, REQ_GPIO, 0xc039, 0, NULL, 0); + vendor_out(hdcap, REQ_GPIO, 0x4134, 0, NULL, 0); + + /* Poll GPIO 0x39 until MCU signals ready */ + for (k =3D 0; k < 50; k++) { + msleep(20); + ret =3D vendor_in(hdcap, REQ_GPIO, 0x39, 0, rx, 1); + if (ret >=3D 0 && rx[0] =3D=3D 1) + break; + } + + if (k =3D=3D 50) { + dev_info(hdcap->dev, "MCU not detected (GPIO timeout)\n"); + return; + } + + dev_dbg(hdcap->dev, "MCU freed, wait count=3D%d\n", k); + + /* Send probe: magic {0x12, 0x34, 0x57} to MCU at I2C 0xAA */ + ret =3D vendor_out(hdcap, REQ_I2C, 0x5066, 0, tx, sizeof(tx)); + if (ret < 0) + return; + + memset(rx, 0, sizeof(rx)); + ret =3D vendor_in(hdcap, REQ_I2C, 0x5066, 0, rx, 3); + if (ret < 0) + return; + + if (rx[0] =3D=3D 'Q' && rx[1] =3D=3D 0x10) { + hdcap->has_mcu =3D 1; + dev_info(hdcap->dev, "Nuvoton NUC100 MCU detected\n"); + } else { + dev_info(hdcap->dev, + "Unrecognized MCU probe response: %02x %02x %02x\n", + rx[0], rx[1], rx[2]); + } +} + +/* Device-level init + GPIO pin state? shared between all inputs */ +static int usb3hdcap_device_init(struct usb3hdcap *hdcap) +{ + vendor_out(hdcap, REQ_INIT, 0x0000, 0, NULL, 0); + vendor_out(hdcap, REQ_UNK_C7, 0x0064, 0, NULL, 0); + /* Windows driver does this but is always fails? */ + /* vendor_out(hdcap, 0xc5, 0x0000, 0, NULL, 0); */ + vendor_out(hdcap, REQ_STREAM, 0x0000, 0, NULL, 0); + vendor_out(hdcap, REQ_STREAM, 0x0000, 0, NULL, 0); + + /* + * just replay what the windows driver does: + * FUN_14022e3f4(param_1,0x402d); + * FUN_14022e3f4(param_1,0x12d); + * FUN_140001220(0x32); + * FUN_14022e3f4(param_1,0x2d); + * FUN_140001220(0x32); + * FUN_14022e3f4(param_1,0x12d); + * FUN_140001220(0x32); + * FUN_14022e3f4(param_1,0x802d); + */ + + vendor_out(hdcap, REQ_GPIO, 0x402d, 0, NULL, 0); + vendor_out(hdcap, REQ_GPIO, 0x012d, 0, NULL, 0); + msleep(50); + vendor_out(hdcap, REQ_GPIO, 0x002d, 0, NULL, 0); + msleep(50); + vendor_out(hdcap, REQ_GPIO, 0x012d, 0, NULL, 0); + msleep(50); + vendor_out(hdcap, REQ_GPIO, 0x802d, 0, NULL, 0); + + /* Probe for MCU after GPIO reset (XCAPTURE-1 only) */ + if (!hdcap->has_mcu && + le16_to_cpu(hdcap->usb_dev->descriptor.idProduct) =3D=3D XCAPTURE1_PI= D) + usb3hdcap_probe_mcu(hdcap); + + return 0; +} + +int usb3hdcap_hw_init(struct usb3hdcap *hdcap) +{ + /* Clear stale detection state from previous input */ + hdcap->std =3D 0; + hdcap->requested_std =3D 0; + hdcap->detected_timings_present =3D 0; + hdcap->requested_timings_present =3D 0; + + usb3hdcap_device_init(hdcap); + usb3hdcap_cs53l21_init(hdcap); + + switch (hdcap->input) { + case INPUT_HDMI: + return usb3hdcap_hdmi_init(hdcap); + case INPUT_COMPONENT: + return usb3hdcap_component_init(hdcap); + case INPUT_COMPOSITE: + case INPUT_SVIDEO: + default: + return usb3hdcap_composite_init(hdcap); + } +} + +/* ------------------------------------------------------------------ */ +/* V4L2 ioctls */ +/* ------------------------------------------------------------------ */ + +static int usb3hdcap_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct usb3hdcap *hdcap =3D video_drvdata(file); + + strscpy(cap->driver, "usb3hdcap", sizeof(cap->driver)); + strscpy(cap->card, hdcap->has_mcu ? + "Micomsoft XCAPTURE-1" : "StarTech USB3HDCAP", + sizeof(cap->card)); + usb_make_path(hdcap->usb_dev, cap->bus_info, sizeof(cap->bus_info)); + return 0; +} + +static int usb3hdcap_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + if (i->index > INPUT_HDMI) + return -EINVAL; + + i->type =3D V4L2_INPUT_TYPE_CAMERA; + switch (i->index) { + case INPUT_COMPOSITE: + strscpy(i->name, "Composite", sizeof(i->name)); + i->std =3D USB3HDCAP_V4L2_STDS; + i->capabilities =3D V4L2_IN_CAP_STD; + break; + case INPUT_SVIDEO: + strscpy(i->name, "S-Video", sizeof(i->name)); + i->std =3D USB3HDCAP_V4L2_STDS; + i->capabilities =3D V4L2_IN_CAP_STD; + break; + case INPUT_COMPONENT: + strscpy(i->name, "Component", sizeof(i->name)); + i->std =3D 0; + i->capabilities =3D V4L2_IN_CAP_DV_TIMINGS; + break; + case INPUT_HDMI: + strscpy(i->name, "HDMI", sizeof(i->name)); + i->std =3D 0; + i->capabilities =3D V4L2_IN_CAP_DV_TIMINGS; + break; + } + return 0; +} + +static int usb3hdcap_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index > 0) + return -EINVAL; + + f->pixelformat =3D V4L2_PIX_FMT_YUYV; + return 0; +} + +static int usb3hdcap_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct usb3hdcap *hdcap =3D video_drvdata(file); + + int width =3D hdcap->width; + int height =3D hdcap->height; + int interlaced =3D hdcap->interlaced; + + /* + * If timings were explicitly set, report format matching those + * timings instead of what was detected from the signal + */ + if (hdcap->requested_timings_present) { + const struct v4l2_bt_timings *bt =3D &hdcap->requested_timings.bt; + + width =3D bt->width; + height =3D bt->interlaced ? bt->height / 2 : bt->height; + interlaced =3D bt->interlaced; + } + + f->fmt.pix.width =3D width; + f->fmt.pix.height =3D height; + f->fmt.pix.pixelformat =3D V4L2_PIX_FMT_YUYV; + f->fmt.pix.field =3D interlaced ? V4L2_FIELD_ALTERNATE : V4L2_FIELD_NONE; + f->fmt.pix.bytesperline =3D width * 2; + f->fmt.pix.sizeimage =3D width * 2 * height; + f->fmt.pix.colorspace =3D (hdcap->input =3D=3D INPUT_HDMI) ? + V4L2_COLORSPACE_REC709 : V4L2_COLORSPACE_SMPTE170M; + + return 0; +} + +static int usb3hdcap_g_std(struct file *file, void *priv, v4l2_std_id *nor= m) +{ + struct usb3hdcap *hdcap =3D video_drvdata(file); + + if (!(hdcap->input =3D=3D INPUT_COMPOSITE || hdcap->input =3D=3D INPUT_SV= IDEO)) + return -ENODATA; + + /* try what was explicitly set, if unavailable, use detected */ + if (hdcap->requested_std) + *norm =3D hdcap->requested_std; + else if (hdcap->std) + *norm =3D hdcap->std; + else + *norm =3D V4L2_STD_NTSC; + return 0; +} + +static int usb3hdcap_s_std(struct file *file, void *priv, v4l2_std_id norm) +{ + struct usb3hdcap *hdcap =3D video_drvdata(file); + + if (!(hdcap->input =3D=3D INPUT_COMPOSITE || hdcap->input =3D=3D INPUT_SV= IDEO)) + return -ENODATA; + + if (!(norm & USB3HDCAP_V4L2_STDS)) + return -EINVAL; + + hdcap->requested_std =3D norm; + return 0; +} + +/* ------------------------------------------------------------------ */ +/* DV timings (HDMI + HD component) */ +/* ------------------------------------------------------------------ */ + +static const struct v4l2_dv_timings_cap usb3hdcap_timings_cap =3D { + .type =3D V4L2_DV_BT_656_1120, + .bt =3D { + .min_width =3D 720, + .max_width =3D 1920, + .min_height =3D 480, + .max_height =3D 1080, + .min_pixelclock =3D 13500000, /* 480i */ + .max_pixelclock =3D 148500000, /* 1080p60 */ + .standards =3D V4L2_DV_BT_STD_CEA861, + .capabilities =3D V4L2_DV_BT_CAP_PROGRESSIVE | + V4L2_DV_BT_CAP_INTERLACED, + }, +}; + +const struct v4l2_dv_timings all_supported_dv_timings[] =3D { + /* component only */ + V4L2_DV_BT_CEA_720X480I59_94, + V4L2_DV_BT_CEA_720X576I50, + V4L2_DV_BT_CEA_1920X1080I60, + + /* hdmi only */ + V4L2_DV_BT_CEA_640X480P59_94, + V4L2_DV_BT_CEA_1280X720P50, + + /* both */ + V4L2_DV_BT_CEA_720X480P59_94, + V4L2_DV_BT_CEA_720X576P50, + V4L2_DV_BT_CEA_1280X720P60, + V4L2_DV_BT_CEA_1920X1080P60, +}; + +static int usb3hdcap_g_dv_timings(struct file *file, void *priv, + struct v4l2_dv_timings *timings) +{ + struct usb3hdcap *hdcap =3D video_drvdata(file); + + /* composite/S-Video -> no DV timings */ + if (!(hdcap->input =3D=3D INPUT_COMPONENT || hdcap->input =3D=3D INPUT_HD= MI)) + return -ENODATA; + + /* nothing set with s_dv_timings and nothing detected? */ + if (!(hdcap->requested_timings_present || hdcap->detected_timings_present= )) + return -ENODATA; + + if (hdcap->requested_timings_present) + *timings =3D hdcap->requested_timings; + else + *timings =3D hdcap->detected_timings; + return 0; +} + +static int usb3hdcap_s_dv_timings(struct file *file, void *priv, + struct v4l2_dv_timings *timings) +{ + struct usb3hdcap *hdcap =3D video_drvdata(file); + + if (!(hdcap->input =3D=3D INPUT_COMPONENT || hdcap->input =3D=3D INPUT_HD= MI)) + return -ENODATA; + + hdcap->requested_timings =3D *timings; + hdcap->requested_timings_present =3D 1; + return 0; +} + +static int usb3hdcap_enum_dv_timings(struct file *file, void *priv, + struct v4l2_enum_dv_timings *timings) +{ + struct usb3hdcap *hdcap =3D video_drvdata(file); + + if (timings->index >=3D ARRAY_SIZE(all_supported_dv_timings)) + return -EINVAL; + if (!(hdcap->input =3D=3D INPUT_COMPONENT || hdcap->input =3D=3D INPUT_HD= MI)) + return -ENODATA; + + timings->timings =3D all_supported_dv_timings[timings->index]; + return 0; +} + +static int usb3hdcap_dv_timings_cap(struct file *file, void *priv, + struct v4l2_dv_timings_cap *cap) +{ + struct usb3hdcap *hdcap =3D video_drvdata(file); + + /* if you're not on HDMI/component, DV timings are not supported */ + if (!(hdcap->input =3D=3D INPUT_COMPONENT || hdcap->input =3D=3D INPUT_HD= MI)) + return -ENODATA; + + *cap =3D usb3hdcap_timings_cap; + return 0; +} + +static int usb3hdcap_query_dv_timings(struct file *file, void *priv, + struct v4l2_dv_timings *timings) +{ + struct usb3hdcap *hdcap =3D video_drvdata(file); + + if (!hdcap->detected_timings_present) + return -ENODATA; + + *timings =3D hdcap->detected_timings; + return 0; +} + +static void usb3hdcap_activate_ctrls(struct usb3hdcap *hdcap, + enum usb3hdcap_input input) +{ + bool is_tw9900 =3D input =3D=3D INPUT_COMPOSITE || input =3D=3D INPUT_SVI= DEO; + + v4l2_ctrl_activate(hdcap->ctrl_brightness, is_tw9900); + v4l2_ctrl_activate(hdcap->ctrl_contrast, is_tw9900); + v4l2_ctrl_activate(hdcap->ctrl_saturation, is_tw9900); + v4l2_ctrl_activate(hdcap->ctrl_hue, is_tw9900); + v4l2_ctrl_activate(hdcap->ctrl_sharpness, is_tw9900); +} + +static int usb3hdcap_g_input(struct file *file, void *priv, unsigned int *= i) +{ + struct usb3hdcap *hdcap =3D video_drvdata(file); + + *i =3D hdcap->input; + return 0; +} + +static int usb3hdcap_s_input(struct file *file, void *priv, unsigned int i) +{ + struct usb3hdcap *hdcap =3D video_drvdata(file); + enum usb3hdcap_input old_input =3D hdcap->input; + int ret; + + if (i > INPUT_HDMI) + return -EINVAL; + if (vb2_is_busy(&hdcap->vb2q)) + return -EBUSY; + + hdcap->input =3D i; + + usb3hdcap_activate_ctrls(hdcap, i); + + ret =3D usb3hdcap_hw_init(hdcap); + if (ret < 0) { + hdcap->input =3D old_input; + hdcap->hw_inited =3D 0; + return ret; + } + + hdcap->bpl =3D hdcap->width * 2; + hdcap->hw_inited =3D 1; + + if (i =3D=3D INPUT_COMPOSITE || i =3D=3D INPUT_SVIDEO) + hdcap->video_dev.tvnorms =3D USB3HDCAP_V4L2_STDS; + else + hdcap->video_dev.tvnorms =3D 0; + + return 0; +} + +static int usb3hdcap_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + struct usb3hdcap *hdcap =3D video_drvdata(file); + + if (fsize->index > 0) + return -EINVAL; + if (fsize->pixel_format !=3D V4L2_PIX_FMT_YUYV) + return -EINVAL; + + fsize->type =3D V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width =3D hdcap->width; + fsize->discrete.height =3D hdcap->height; + return 0; +} + +static void fill_timeperframe(struct usb3hdcap *hdcap, struct v4l2_fract *= tf) +{ + /* + * using the frame rate here even for interlaced, spec isn't super + * clear on what to do, but adv7180.c does it this way + */ + if (hdcap->detected_timings.type) { + const struct v4l2_bt_timings *bt =3D &hdcap->detected_timings.bt; + u32 htotal =3D V4L2_DV_BT_FRAME_WIDTH(bt); + u32 vtotal =3D V4L2_DV_BT_FRAME_HEIGHT(bt); + + tf->numerator =3D htotal * vtotal; + tf->denominator =3D (u32)bt->pixelclock; + v4l2_simplify_fraction(&tf->numerator, &tf->denominator, 8, 333); + } else if (hdcap->std & V4L2_STD_625_50) { + /* PAL: 25fps */ + tf->numerator =3D 1; + tf->denominator =3D 25; + } else { + /* NTSC: 29.97fps */ + tf->numerator =3D 1001; + tf->denominator =3D 30000; + } +} + +static int usb3hdcap_g_parm(struct file *file, void *priv, + struct v4l2_streamparm *sp) +{ + struct usb3hdcap *hdcap =3D video_drvdata(file); + + if (sp->type !=3D V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + sp->parm.capture.capability =3D V4L2_CAP_TIMEPERFRAME; + fill_timeperframe(hdcap, &sp->parm.capture.timeperframe); + sp->parm.capture.readbuffers =3D 4; + return 0; +} + +static int usb3hdcap_s_parm(struct file *file, void *priv, + struct v4l2_streamparm *sp) +{ + struct usb3hdcap *hdcap =3D video_drvdata(file); + + if (sp->type !=3D V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + /* fixed framerate - just report what we have */ + sp->parm.capture.capability =3D V4L2_CAP_TIMEPERFRAME; + fill_timeperframe(hdcap, &sp->parm.capture.timeperframe); + sp->parm.capture.readbuffers =3D 4; + return 0; +} + +static int usb3hdcap_enum_frameintervals(struct file *file, void *priv, + struct v4l2_frmivalenum *fival) +{ + struct usb3hdcap *hdcap =3D video_drvdata(file); + + if (fival->index > 0) + return -EINVAL; + if (fival->pixel_format !=3D V4L2_PIX_FMT_YUYV) + return -EINVAL; + if (fival->width !=3D hdcap->width || fival->height !=3D hdcap->height) + return -EINVAL; + + fival->type =3D V4L2_FRMIVAL_TYPE_DISCRETE; + fill_timeperframe(hdcap, &fival->discrete); + return 0; +} + +static int usb3hdcap_log_status(struct file *file, void *priv) +{ + struct usb3hdcap *hdcap =3D video_drvdata(file); + + dev_info(hdcap->dev, + "status: iso_cbs=3D%u iso_bytes=3D%lu markers=3D%u frames=3D%u ", + hdcap->iso_cb_count, hdcap->iso_bytes, + hdcap->markers_found, hdcap->frames_delivered); + dev_info(hdcap->dev, + "parse_len=3D%d frame_line=3D%d synced=3D%d was_blanking=3D%d ", + hdcap->parse_len, hdcap->frame_line, + hdcap->synced, hdcap->was_blanking); + dev_info(hdcap->dev, "cur_buf=3D%p\n", hdcap->cur_buf); + return 0; +} + +static int usb3hdcap_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct usb3hdcap *hdcap =3D container_of(ctrl->handler, + struct usb3hdcap, ctrl); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + return u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_BRIGHT, (u8)ctrl->val); + case V4L2_CID_CONTRAST: + return u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_CONTRAST, ctrl->val); + case V4L2_CID_SATURATION: + u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_SAT_U, ctrl->val); + return u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_SAT_V, ctrl->val); + case V4L2_CID_HUE: + return u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_HUE, (u8)ctrl->val); + case V4L2_CID_SHARPNESS: + return u3hc_i2c_write(hdcap, ADDR_TW9900, TW9900_SHARPNESS, ctrl->val); + default: + return -EINVAL; + } +} + +static int usb3hdcap_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + switch (ctrl->id) { + case V4L2_CID_DV_RX_POWER_PRESENT: + ctrl->val =3D 1; + return 0; + default: + return -EINVAL; + } +} + +static const struct v4l2_ctrl_ops usb3hdcap_ctrl_ops =3D { + .s_ctrl =3D usb3hdcap_s_ctrl, + .g_volatile_ctrl =3D usb3hdcap_g_volatile_ctrl, +}; + +static int usb3hdcap_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subscribe(fh, sub); + default: + return v4l2_ctrl_subscribe_event(fh, sub); + } +} + +static const struct v4l2_ioctl_ops usb3hdcap_ioctl_ops =3D { + .vidioc_querycap =3D usb3hdcap_querycap, + .vidioc_enum_input =3D usb3hdcap_enum_input, + .vidioc_enum_fmt_vid_cap =3D usb3hdcap_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap =3D usb3hdcap_fmt_vid_cap, + .vidioc_try_fmt_vid_cap =3D usb3hdcap_fmt_vid_cap, + .vidioc_s_fmt_vid_cap =3D usb3hdcap_fmt_vid_cap, + .vidioc_g_std =3D usb3hdcap_g_std, + .vidioc_s_std =3D usb3hdcap_s_std, + .vidioc_g_dv_timings =3D usb3hdcap_g_dv_timings, + .vidioc_s_dv_timings =3D usb3hdcap_s_dv_timings, + .vidioc_enum_dv_timings =3D usb3hdcap_enum_dv_timings, + .vidioc_dv_timings_cap =3D usb3hdcap_dv_timings_cap, + .vidioc_query_dv_timings =3D usb3hdcap_query_dv_timings, + .vidioc_g_input =3D usb3hdcap_g_input, + .vidioc_s_input =3D usb3hdcap_s_input, + .vidioc_g_parm =3D usb3hdcap_g_parm, + .vidioc_s_parm =3D usb3hdcap_s_parm, + .vidioc_enum_framesizes =3D usb3hdcap_enum_framesizes, + .vidioc_enum_frameintervals =3D usb3hdcap_enum_frameintervals, + .vidioc_log_status =3D usb3hdcap_log_status, + + .vidioc_reqbufs =3D vb2_ioctl_reqbufs, + .vidioc_prepare_buf =3D vb2_ioctl_prepare_buf, + .vidioc_querybuf =3D vb2_ioctl_querybuf, + .vidioc_create_bufs =3D vb2_ioctl_create_bufs, + .vidioc_qbuf =3D vb2_ioctl_qbuf, + .vidioc_dqbuf =3D vb2_ioctl_dqbuf, + .vidioc_streamon =3D vb2_ioctl_streamon, + .vidioc_streamoff =3D vb2_ioctl_streamoff, + + .vidioc_subscribe_event =3D usb3hdcap_subscribe_event, + .vidioc_unsubscribe_event =3D v4l2_event_unsubscribe, +}; + +static const struct v4l2_file_operations usb3hdcap_fops =3D { + .owner =3D THIS_MODULE, + .unlocked_ioctl =3D video_ioctl2, + .mmap =3D vb2_fop_mmap, + .open =3D v4l2_fh_open, + .release =3D vb2_fop_release, + .read =3D vb2_fop_read, + .poll =3D vb2_fop_poll, +}; + +static void usb3hdcap_release(struct v4l2_device *v4l2_dev) +{ + struct usb3hdcap *hdcap =3D container_of(v4l2_dev, + struct usb3hdcap, v4l2_dev); + + v4l2_device_unregister(&hdcap->v4l2_dev); + v4l2_ctrl_handler_free(&hdcap->ctrl); + kfree(hdcap); +} + +static int video_init(struct usb3hdcap *hdcap) +{ + int ret; + + mutex_init(&hdcap->v4l2_lock); + mutex_init(&hdcap->vb2q_lock); + spin_lock_init(&hdcap->buflock); + INIT_LIST_HEAD(&hdcap->bufs); + + hdcap->vb2q.type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE; + hdcap->vb2q.io_modes =3D VB2_MMAP | VB2_USERPTR | VB2_READ; + hdcap->vb2q.drv_priv =3D hdcap; + hdcap->vb2q.buf_struct_size =3D sizeof(struct hdcap_buf); + hdcap->vb2q.ops =3D &usb3hdcap_vb2_ops; + hdcap->vb2q.mem_ops =3D &vb2_vmalloc_memops; + hdcap->vb2q.timestamp_flags =3D V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + hdcap->vb2q.min_queued_buffers =3D 2; + hdcap->vb2q.lock =3D &hdcap->vb2q_lock; + ret =3D vb2_queue_init(&hdcap->vb2q); + if (ret < 0) { + dev_warn(hdcap->dev, "Could not initialize videobuf2 queue\n"); + return ret; + } + + v4l2_ctrl_handler_init(&hdcap->ctrl, 7); + hdcap->ctrl_brightness =3D v4l2_ctrl_new_std(&hdcap->ctrl, + &usb3hdcap_ctrl_ops, + V4L2_CID_BRIGHTNESS, + -128, 127, 1, -9); + hdcap->ctrl_contrast =3D v4l2_ctrl_new_std(&hdcap->ctrl, + &usb3hdcap_ctrl_ops, + V4L2_CID_CONTRAST, + 0, 255, 1, 0x79); + hdcap->ctrl_saturation =3D v4l2_ctrl_new_std(&hdcap->ctrl, + &usb3hdcap_ctrl_ops, + V4L2_CID_SATURATION, + 0, 255, 1, 0x80); + hdcap->ctrl_hue =3D v4l2_ctrl_new_std(&hdcap->ctrl, + &usb3hdcap_ctrl_ops, + V4L2_CID_HUE, + -128, 127, 1, 0); + hdcap->ctrl_sharpness =3D v4l2_ctrl_new_std(&hdcap->ctrl, + &usb3hdcap_ctrl_ops, + V4L2_CID_SHARPNESS, + 0, 255, 1, 0x52); + hdcap->ctrl_rx_power =3D v4l2_ctrl_new_std(&hdcap->ctrl, + &usb3hdcap_ctrl_ops, + V4L2_CID_DV_RX_POWER_PRESENT, + 0, 1, 0, 1); + if (hdcap->ctrl_rx_power) + hdcap->ctrl_rx_power->flags |=3D V4L2_CTRL_FLAG_VOLATILE | + V4L2_CTRL_FLAG_READ_ONLY; + ret =3D hdcap->ctrl.error; + if (ret < 0) { + dev_warn(hdcap->dev, "Could not initialize controls\n"); + goto ctrl_fail; + } + + hdcap->v4l2_dev.ctrl_handler =3D &hdcap->ctrl; + hdcap->v4l2_dev.release =3D usb3hdcap_release; + ret =3D v4l2_device_register(hdcap->dev, &hdcap->v4l2_dev); + if (ret < 0) { + dev_warn(hdcap->dev, "Could not register v4l2 device\n"); + goto v4l2_fail; + } + + strscpy(hdcap->video_dev.name, "usb3hdcap", sizeof(hdcap->video_dev.name)= ); + hdcap->video_dev.v4l2_dev =3D &hdcap->v4l2_dev; + hdcap->video_dev.release =3D video_device_release_empty; + hdcap->video_dev.fops =3D &usb3hdcap_fops; + hdcap->video_dev.ioctl_ops =3D &usb3hdcap_ioctl_ops; + hdcap->video_dev.tvnorms =3D USB3HDCAP_V4L2_STDS; + hdcap->video_dev.queue =3D &hdcap->vb2q; + hdcap->video_dev.lock =3D &hdcap->v4l2_lock; + hdcap->video_dev.device_caps =3D V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWR= ITE | + V4L2_CAP_STREAMING; + video_set_drvdata(&hdcap->video_dev, hdcap); + ret =3D video_register_device(&hdcap->video_dev, VFL_TYPE_VIDEO, -1); + if (ret < 0) { + dev_warn(hdcap->dev, "Could not register video device\n"); + goto vdev_fail; + } + + dev_info(hdcap->dev, "%s: registered as %s\n", + __func__, video_device_node_name(&hdcap->video_dev)); + + return 0; + +vdev_fail: + v4l2_device_unregister(&hdcap->v4l2_dev); + +v4l2_fail: + v4l2_ctrl_handler_free(&hdcap->ctrl); + +ctrl_fail: + return ret; +} + +static int usb3hdcap_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct device *dev; + struct usb3hdcap *hdcap; + int ret, is_xcapture; + + /* not using the interrupt interface, just data */ + if (intf->cur_altsetting->desc.bInterfaceNumber !=3D 0) + return -ENODEV; + + /* none, Mult=3D0 for SD, Mult=3D1 for HD */ + if (intf->num_altsetting !=3D 3) + return -ENODEV; + + dev =3D &intf->dev; + + hdcap =3D kzalloc_obj(*hdcap, GFP_KERNEL); + if (!hdcap) + return -ENOMEM; + + hdcap->dev =3D dev; + hdcap->usb_dev =3D usb_get_dev(interface_to_usbdev(intf)); + hdcap->input =3D INPUT_COMPOSITE; + hdcap->std =3D 0; + hdcap->width =3D SD_WIDTH; + hdcap->height =3D NTSC_HEIGHT / 2; + hdcap->bpl =3D DEFAULT_BPL; + + usb_set_intfdata(intf, hdcap); + + ret =3D video_init(hdcap); + if (ret < 0) + goto video_fail; + + ret =3D usb3hdcap_audio_init(hdcap); + if (ret < 0) + dev_warn(dev, "audio init failed: %d (continuing without audio)\n", ret); + + /* + * Take an extra ref so that disconnect's v4l2_device_put doesn't + * immediately trigger usb3hdcap_release before cleanup is done - based on + * usbtv driver + */ + v4l2_device_get(&hdcap->v4l2_dev); + + is_xcapture =3D le16_to_cpu(hdcap->usb_dev->descriptor.idProduct) =3D=3D = XCAPTURE1_PID; + dev_info(dev, "%s USB 3.0 HD Video Capture Device", + is_xcapture ? "Micomsoft XCAPTURE-1" : "StarTech USB3HDCAP"); + + return 0; + +video_fail: + usb_set_intfdata(intf, NULL); + usb_put_dev(hdcap->usb_dev); + kfree(hdcap); + return ret; +} + +static void usb3hdcap_disconnect(struct usb_interface *intf) +{ + struct usb3hdcap *hdcap =3D usb_get_intfdata(intf); + + usb_set_intfdata(intf, NULL); + + if (!hdcap) + return; + + usb3hdcap_audio_free(hdcap); + vb2_video_unregister_device(&hdcap->video_dev); + v4l2_device_disconnect(&hdcap->v4l2_dev); + + usb_put_dev(hdcap->usb_dev); + hdcap->usb_dev =3D NULL; + + /* hdcap is freed by usb3hdcap_release when last user closes fd */ + v4l2_device_put(&hdcap->v4l2_dev); +} + +static const struct usb_device_id usb3hdcap_id_table[] =3D { + { USB_DEVICE(USB3HDCAP_VID, USB3HDCAP_PID) }, + { USB_DEVICE(USB3HDCAP_VID, XCAPTURE1_PID) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, usb3hdcap_id_table); + +MODULE_AUTHOR("Matthew Laux "); +MODULE_DESCRIPTION("StarTech USB3HDCAP Driver"); +MODULE_LICENSE("GPL"); + +static struct usb_driver usb3hdcap_usb_driver =3D { + .name =3D "usb3hdcap", + .id_table =3D usb3hdcap_id_table, + .probe =3D usb3hdcap_probe, + .disconnect =3D usb3hdcap_disconnect, +}; +module_usb_driver(usb3hdcap_usb_driver); diff --git a/drivers/staging/media/usb3hdcap/usb3hdcap-hdmi.c b/drivers/sta= ging/media/usb3hdcap/usb3hdcap-hdmi.c new file mode 100644 index 000000000000..776ee7602ec2 --- /dev/null +++ b/drivers/staging/media/usb3hdcap/usb3hdcap-hdmi.c @@ -0,0 +1,802 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB3HDCAP MST3367 support: HDMI and component + */ + +#include +#include +#include +#include +#include + +#include "usb3hdcap.h" + +/* ------------------------------------------------------------------ */ +/* Component scaler mode table */ +/* ------------------------------------------------------------------ */ + +struct component_mode { + int vtotal_min, vtotal_max; + int width, height; + int refresh_rate; + int interlaced; + /* MST3367 bank 0 ADC/scaler registers (ref: SetADParameters) */ + u8 pllgain, clpdly, clpdur, hsopw; + u8 adc_bw0, adc_bw1; + u16 scaler_htotal; + u16 hde_off; + u8 vde_off; + struct v4l2_dv_timings timings; +}; + +/* + * Values from Windows driver table $140303810 + * TODO: add 50Hz variants + * + * The hardware supports 240p over component (one of the great things about + * this card), but the Windows driver doesn't attempt to distinguish. I + * won't either + */ +static const struct component_mode component_modes[] =3D { + /* pll clp clp hsop adc adc = */ + /* gain dly dur w bw0 bw1 scl= ht hde vde */ + /* 480i */ + { 250, 275, 720, 240, 60, 1, 0x08, 0x08, 0x08, 0x18, 0x44, 0x04, 0x3= 5a, 0x76, 0x12, + V4L2_DV_BT_CEA_720X480I59_94 }, + /* 576i */ + { 300, 320, 720, 288, 50, 1, 0x08, 0x08, 0x08, 0x18, 0x44, 0x04, 0x3= 60, 0x83, 0x16, + V4L2_DV_BT_CEA_720X576I50 }, + /* 480p */ + { 515, 535, 720, 480, 60, 0, 0x20, 0x08, 0x08, 0x18, 0x33, 0x03, 0x3= 5a, 0x79, 0x24, + V4L2_DV_BT_CEA_720X480P59_94 }, + /* 1080i */ + { 555, 585, 1920, 540, 60, 1, 0x58, 0x38, 0x20, 0x30, 0x11, 0x01, 0x8= 98, 0xeb, 0x14, + V4L2_DV_BT_CEA_1920X1080I60 }, + /* 576p */ + { 615, 635, 720, 576, 50, 0, 0x18, 0x08, 0x08, 0x18, 0x33, 0x03, 0x3= 60, 0x83, 0x2c, + V4L2_DV_BT_CEA_720X576P50 }, + /* 720p */ + { 740, 760, 1280, 720, 60, 0, 0x58, 0x38, 0x20, 0x30, 0x11, 0x01, 0x6= 72, 0x12b, 0x19, + V4L2_DV_BT_CEA_1280X720P60 }, + /* 1080p */ + { 1115, 1135, 1920, 1080, 60, 0, 0xe8, 0x38, 0x30, 0xa0, 0x00, 0x00, 0x8= 98, 0xc1, 0x29, + V4L2_DV_BT_CEA_1920X1080P60 }, +}; + +/* + * Known CEA timing table - maps (htotal, vtotal, hactive) to full + * v4l2_dv_timings. HD htotal =3D standard * 1.5 (MST3367 counter rate?) + */ +struct hdmi_std { + int htotal_min, htotal_max; + int vtotal_min, vtotal_max; + int hactive; + struct v4l2_dv_timings timings; +}; + +static const struct hdmi_std hdmi_stds[] =3D { + /* htotal vtotal ha */ + { 790, 810, 520, 530, 640, V4L2_DV_BT_CEA_640X480P59_94 }, + { 848, 868, 520, 530, 720, V4L2_DV_BT_CEA_720X480P59_94 }, + { 854, 874, 620, 630, 720, V4L2_DV_BT_CEA_720X576P50 }, + { 2960, 2980, 745, 755, 1280, V4L2_DV_BT_CEA_1280X720P50 }, + { 2465, 2485, 745, 755, 1280, V4L2_DV_BT_CEA_1280X720P60 }, + /* + * not sure about this one. it matches the timings for 30, but my laptop + * claims to be outputting 60... + */ + { 2740, 2760, 1120, 1130, 1920, V4L2_DV_BT_CEA_1920X1080P30 }, + { 3290, 3310, 555, 570, 1920, V4L2_DV_BT_CEA_1920X1080I60 }, + { 3950, 3970, 1120, 1130, 1920, V4L2_DV_BT_CEA_1920X1080P50 }, + { 2190, 2210, 1120, 1130, 1920, V4L2_DV_BT_CEA_1920X1080P60 }, + { 3290, 3310, 1120, 1130, 1920, V4L2_DV_BT_CEA_1920X1080P60 }, +}; + +/* ------------------------------------------------------------------ */ +/* MST3367 bank select helper */ +/* ------------------------------------------------------------------ */ + +static int mst_bank(struct usb3hdcap *hdcap, int bank) +{ + if (hdcap->mst_current_bank =3D=3D bank) + return 0; + hdcap->mst_current_bank =3D bank; + return u3hc_i2c_write(hdcap, ADDR_MST3367, 0x00, bank); +} + +/* ------------------------------------------------------------------ */ +/* MST3367 register configuration */ +/* ------------------------------------------------------------------ */ + +static void mst3367_config(struct usb3hdcap *hdcap) +{ + /* + * Largely based on mst3367-drv.c by Steven Toth and Ghidra decompilation + * of the Windows x64 driver + */ + + mst_bank(hdcap, 0x00); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x51, 0x80); + /* RxTmdsHotPlug: all off */ + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0xb7, 0xff, 0x02); + /* RxGeneralInit */ + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x41, 0x6f); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0xb8, 0x00); + /* RxVideoInit */ + u3hc_i2c_write(hdcap, ADDR_MST3367, 0xb0, 0x14); + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0xae, ~0x04, 0x04); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0xad, 0x05); /* low-pass filter */ + u3hc_i2c_write(hdcap, ADDR_MST3367, 0xb1, 0xc0); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0xb2, 0x00); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0xb3, 0x00); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0xb4, 0x55); + /* RxAudioInit */ + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0xb4, ~0x03, 0x00); + + /* RxTmdsInit */ + mst_bank(hdcap, 0x01); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x0f, 0x02); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x16, 0x30); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x17, 0x00); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x18, 0x00); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x19, 0x00); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1a, 0x50); + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x2a, ~0x07, 0x07); + /* RxHdcpInit */ + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x24, 0x40); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x25, 0x00); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x30, 0x80); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x31, 0x00); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x32, 0x00); + + mst_bank(hdcap, 0x02); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x08, 0x03); + /* RxAudioInit (cont'd) */ + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x01, 0x61); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x02, 0xf5); + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x03, ~0x02, 0x02); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x04, 0x01); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x05, 0x00); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x06, 0x08); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1c, 0x1a); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1d, 0x00); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1e, 0x00); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1f, 0x00); + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x25, (u8)~0xa2, 0xa2); + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x07, ~0x04, 0x04); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x17, 0xc0); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x19, 0xff); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1a, 0xff); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1b, 0xfc); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x20, 0x00); + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x21, ~0x03, 0x00); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x22, 0x26); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x27, 0x00); + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x2e, (u8)~0xa1, 0xa1); + + mst_bank(hdcap, 0x00); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0xab, 0x15); /* COLOR.RANGE */ + u3hc_i2c_write(hdcap, ADDR_MST3367, 0xac, 0x15); /* COLOR.RANGE */ + + /* RxHdcpReset */ + u3hc_i2c_write(hdcap, ADDR_MST3367, 0xb8, 0x10); /* HDCP.RESET */ + u3hc_i2c_write(hdcap, ADDR_MST3367, 0xb8, 0x00); + + /* RxHdmiReset */ + mst_bank(hdcap, 0x02); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x07, 0xf4); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x07, 0x04); + + /* RxSwitchSource: 0x81 =3D HDMI TMDS.A */ + mst_bank(hdcap, 0x00); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x51, 0x81); + /* RxTmdsHotPlug: link on */ + u3hc_i2c_write(hdcap, ADDR_MST3367, 0xb7, 0x00); + + /* RxHdmiInit */ + mst_bank(hdcap, 0x02); + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x01, 0x0f, 0x60); + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x04, 0xff, 0x01); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x06, 0x08); + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x09, 0xff, 0x20); + + mst_bank(hdcap, 0x00); + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x54, 0xef, 0x00); + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0xac, 0xff, 0x80); + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0xce, 0xff, 0x80); + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0xcf, 0xfa, 0x02); + + /* + * 8c28 is "CustomCompanyEndoCamProperty" (0 in my registry) + * 7674 is "CustomAnalogVideoInputBandwidthProperty" (1 on my machine) + * if (*(int *)(param_1 + 0x8c28) =3D=3D 0) { + * uVar1 =3D *(uint *)(param_1 + 0x7674); + * local_18 =3D 0x3040006; + * uVar2 =3D (ulonglong)uVar1 / 6; + * local_14 =3D 0x107; + * bVar5 =3D read_mst(param_1,CONCAT71((int7)(uVar2 >> 8),0x80),0xd0); + * bVar4 =3D read_mst(param_1,0x80,0xcf); + * bVar3 =3D *(byte *)((longlong)&local_18 + (ulonglong)(uVar1 + * + (int)uVar2 * -6)); + * bVar4 =3D bVar3 << 7 | bVar4 & 0x7f; + * bVar5 =3D (bVar3 >> 1 ^ bVar5) & 3 ^ bVar5; + * } + * + * ... which has this effect: + */ + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0xd0, 0xfc, 0x00); + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0xcf, 0x7f, 0x00); + + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x00, 0x7f, 0x00); + mst_bank(hdcap, 0x00); +} + +/* ------------------------------------------------------------------ */ +/* CPLD init + poll helper */ +/* ------------------------------------------------------------------ */ + +static void cpld_init(struct usb3hdcap *hdcap, u8 mux_val) +{ + int k, status; + + /* + * cVar3 =3D i2c_read(param_1,0x98,0x3b,0); + * if (cVar3 !=3D '\0') { + * uVar5 =3D *(uint *)(param_1 + 0x6840); + * if ((uVar5 =3D=3D 2) || (uVar5 =3D=3D 3)) { + * local_res8[0] =3D -0x80; + * i2c_write(param_1,0x98,0x3b,local_res8,1); + * uVar5 =3D *(uint *)(param_1 + 0x6840); + * } + * if (uVar5 < 2) { + * local_res8[0] =3D '\x02'; + * i2c_write(param_1,0x98,0x3b,local_res8,1); + * } + * } + */ + + u3hc_i2c_write(hdcap, ADDR_CPLD, 0x3b, mux_val); + + /* + * local_res8[0] =3D '\x05'; + * i2c_write(param_1,0x98,0x20,local_res8,1); + * cVar3 =3D FUN_140226fc4(param_1); + * local_res8[0] =3D '\x01'; + * i2c_write(param_1,0x98,0,local_res8,1); + * local_res8[0] =3D (-(cVar3 !=3D '\0') & 0xfeU) - 2; + * i2c_write(param_1,0x98,0x10,local_res8,1); + */ + u3hc_i2c_write(hdcap, ADDR_CPLD, 0x20, 0x05); + u3hc_i2c_write(hdcap, ADDR_CPLD, 0x00, 0x01); + + u3hc_i2c_write(hdcap, ADDR_CPLD, 0x10, hdcap->has_mcu ? 0xfc : 0xfe); + + for (k =3D 0; k < 50; k++) { + status =3D u3hc_i2c_read(hdcap, ADDR_CPLD, 0x01); + if (status >=3D 0x03) + break; + msleep(20); + } + + u3hc_i2c_write(hdcap, ADDR_CPLD, 0x01, 0x02); + u3hc_i2c_write(hdcap, ADDR_CPLD, 0x11, 0xfc); +} + +/* Identity CSC for component YPbPr */ +static const u8 csc_identity_component[] =3D { + 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, /* M11, M12, M13 */ + 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, /* M21, M22, M23 */ + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, /* M31, M32, M33 */ + 0x20, 0x00, 0x03, 0x80, 0x20, 0x00, /* A1, A2, A3 */ +}; + +/* Identity CSC for HDMI YCbCr */ +static const u8 csc_identity_hdmi[] =3D { + 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x20, 0x00, 0x04, 0x00, 0x20, 0x00, +}; + +/* BT.601 for SD (width <=3D 720) */ +static const u8 csc_r2y_sd[] =3D { + 0x08, 0x02, 0x04, 0xc5, 0xfd, 0x4d, + 0xf9, 0x4a, 0x09, 0x5b, 0xfa, 0xb1, + 0xfe, 0xb4, 0x01, 0xd2, 0x08, 0x02, + 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, +}; + +/* BT.709 for HD (width > 720) */ +static const u8 csc_r2y_hd[] =3D { + 0x08, 0x02, 0x03, 0x65, 0xfe, 0x28, + 0xf8, 0xb9, 0x0b, 0x65, 0xf9, 0xd6, + 0xff, 0x45, 0x01, 0x27, 0x08, 0x02, + 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, +}; + +void mst3367_write_csc(struct usb3hdcap *hdcap) +{ + bool comp =3D (hdcap->input =3D=3D INPUT_COMPONENT); + const u8 *csc; + u8 old_ab, reg_92; + int k; + + if (comp) { + reg_92 =3D 0x66; + csc =3D csc_identity_component; + } else { + /* + * Auto-detect HDMI input colorspace: + * + * RxHdmiPacketStatus (0x02:0x0B/0x0C/0x0E): bit 3 indicates + * an AVI InfoFrame has been received? + * RxHdmiPacketColor (0x02:0x48 bits 6:5): + * 0x00 =3D RGB, 0x20 =3D YUV422, 0x40 =3D YUV444 + */ + int pkt_status, color =3D 0x00; + + mst_bank(hdcap, 0x02); + pkt_status =3D (u3hc_i2c_read(hdcap, ADDR_MST3367, 0x0c) & 0x3f) << 8 | + (u3hc_i2c_read(hdcap, ADDR_MST3367, 0x0b) & 0xff); + if (u3hc_i2c_read(hdcap, ADDR_MST3367, 0x0e) & 0x08) + pkt_status |=3D 0x8000; + + if (pkt_status & 0x0008) + color =3D u3hc_i2c_read(hdcap, ADDR_MST3367, 0x48) & 0x60; + + dev_info(hdcap->dev, + "CSC: pkt_status=3D0x%04x color=3D0x%02x\n", + pkt_status, color); + + if (color =3D=3D 0x00) { + /* RGB: need R2Y conversion for YUV422 output */ + reg_92 =3D 0x40; + csc =3D (hdcap->width <=3D 720) ? csc_r2y_sd : csc_r2y_hd; + dev_info(hdcap->dev, "CSC: HDMI RGB, %s R2Y\n", + (hdcap->width <=3D 720) ? "BT.601" : "BT.709"); + } else { + /* YCbCr input: identity passthrough */ + reg_92 =3D 0x62; + csc =3D csc_identity_hdmi; + dev_info(hdcap->dev, "CSC: HDMI YCbCr (0x%02x), identity\n", + color); + } + } + + mst_bank(hdcap, 0x00); + + /* BLANK.OUTPUT while changing CSC */ + u3hc_i2c_rmw_get_old(hdcap, ADDR_MST3367, 0xab, 0x7f, 0x80, &old_ab); + + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x90, 0x15); /* COLOR.RANGE */ + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x91, 0x15); /* COLOR.RANGE */ + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x92, reg_92); + + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0xac, ~0x3f, 0x15); /* COLOR.RANGE */ + u3hc_i2c_write(hdcap, ADDR_MST3367, 0xad, 0x05); /* sharpness: low-pass f= ilter */ + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1e, 0x11); /* sharpness */ + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1f, 0x01); /* sharpness */ + + for (k =3D 0; k < 24; k++) + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x93 + k, csc[k]); + + /* RX_OUTPUT_YUV422 / 08.BITS / EMBEDDED SYNC */ + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0xb0, 0xc2, 0x21); + /* NORMAL.OUTPUT (un-blank) */ + u3hc_i2c_write(hdcap, ADDR_MST3367, 0xab, old_ab & 0x7f); + + u3hc_i2c_write(hdcap, ADDR_MST3367, 0xb3, 0x00); + + mst_bank(hdcap, 0x02); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x27, 0x00); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x20, 0x00); +} + +/* ------------------------------------------------------------------ */ +/* Signal detection polling */ +/* ------------------------------------------------------------------ */ + +static const struct v4l2_dv_timings *match_hdmi_timing(int htotal, int vto= tal, + int hactive) +{ + int k; + + for (k =3D 0; k < ARRAY_SIZE(hdmi_stds); k++) { + const struct hdmi_std *s =3D &hdmi_stds[k]; + + if (htotal < s->htotal_min || htotal > s->htotal_max) + continue; + if (vtotal < s->vtotal_min || vtotal > s->vtotal_max) + continue; + if (hactive !=3D s->hactive) + continue; + return &s->timings; + } + return NULL; +} + +static int hdmi_poll_signal(struct usb3hdcap *hdcap) +{ + int k, status; + + vendor_out(hdcap, REQ_STREAM, 0x0000, 0, NULL, 0); + + for (k =3D 0; k < 100; k++) { + mst_bank(hdcap, 0x00); + status =3D u3hc_i2c_read(hdcap, ADDR_MST3367, 0x55); + if (status < 0) { + msleep(100); + continue; + } + + if (k % 10 =3D=3D 0) + dev_info(hdcap->dev, + "HDMI signal poll[%d]: lock=3D0x%02x\n", + k, status); + + if (status & 0x3c) { + const struct v4l2_dv_timings *std; + int htotal, vtotal, hactive; + + htotal =3D (u3hc_i2c_read(hdcap, ADDR_MST3367, 0x6a) << 8 | + u3hc_i2c_read(hdcap, ADDR_MST3367, 0x6b)) & 0xfff; + vtotal =3D (u3hc_i2c_read(hdcap, ADDR_MST3367, 0x5b) << 8 | + u3hc_i2c_read(hdcap, ADDR_MST3367, 0x5c)) & 0x7ff; + + mst_bank(hdcap, 0x02); + hactive =3D (u3hc_i2c_read(hdcap, ADDR_MST3367, 0x29) << 8 | + u3hc_i2c_read(hdcap, ADDR_MST3367, 0x28)) & 0x1fff; + + dev_info(hdcap->dev, + "HDMI locked: htotal=3D%d vtotal=3D%d hactive=3D%d\n", + htotal, vtotal, hactive); + + std =3D match_hdmi_timing(htotal, vtotal, hactive); + if (!std) { + dev_err(hdcap->dev, + "unsupported HDMI timing: htotal=3D%d vtotal=3D%d hactive=3D%d\n", + htotal, vtotal, hactive); + return -ERANGE; + } + hdcap->detected_timings =3D *std; + hdcap->detected_timings_present =3D 1; + hdcap->std =3D 0; /* no SD standard */ + hdcap->width =3D std->bt.width; + hdcap->interlaced =3D std->bt.interlaced; + hdcap->height =3D std->bt.interlaced + ? std->bt.height / 2 : std->bt.height; + hdcap->bpl =3D hdcap->width * 2; + + /* DISABLE AUTO POSITION */ + mst_bank(hdcap, 0x00); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0xe2, 0x00); + + return 0; + } + + /* Not locked yet, ENABLE AUTO POSITION */ + u3hc_i2c_write(hdcap, ADDR_MST3367, 0xe2, 0x80); + msleep(100); + } + + dev_err(hdcap->dev, "No HDMI signal lock after 10s\n"); + return -ETIMEDOUT; +} + +/* ------------------------------------------------------------------ */ +/* ADC front-end config (component/VGA only) */ +/* ------------------------------------------------------------------ */ + +static void mst3367_adc_config(struct usb3hdcap *hdcap) +{ + /* RxAdcInit */ + mst_bank(hdcap, 0x00); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x00, 0x80); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x24, 0xc0); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x00, 0x00); + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x21, ~0x07, 0x01); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x04, 0x00); /* ADC phase */ + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x10, 0x0f, 0xd0); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x11, 0x2d); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x20, 0xc0); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x21, 0x04); + /* bits 5:4 =3D sync source: 0x30 =3D SOG for component */ + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x0e, 0xcf, 0x30); +} + +static const struct component_mode *match_component_mode(int vtotal) +{ + int k; + + for (k =3D 0; k < ARRAY_SIZE(component_modes); k++) { + const struct component_mode *m =3D &component_modes[k]; + + if (vtotal < m->vtotal_min || vtotal > m->vtotal_max) + continue; + return m; + } + return NULL; +} + +/* ------------------------------------------------------------------ */ +/* Component scaler register write */ +/* ------------------------------------------------------------------ */ + +static void component_write_scaler(struct usb3hdcap *hdcap, + const struct component_mode *m) +{ + int ht =3D m->scaler_htotal - 1; + int is_720_low_rr =3D m->width =3D=3D 1280 && m->height =3D=3D 720 && + (m->refresh_rate =3D=3D 24 || + m->refresh_rate =3D=3D 25 || + m->refresh_rate =3D=3D 30); + + mst_bank(hdcap, 0x00); + + /* + * edge case for low frame rate 720p: + * if (((*(int *)(uVar30 + 0x30) =3D=3D 0x500) && (*(int *)(uVar30 + 0x34= ) =3D=3D 0x2d0)) && + * ((*(int *)(uVar30 + 0x38) =3D=3D 0x1e || + * ((*(int *)(uVar30 + 0x38) =3D=3D 0x19 || (*(int *)(uVar30 + 0x38) = =3D=3D 0x18)))))) { + * uVar37 =3D 0; + * } else { + * uVar37 =3D 4; + * } + * send_mst(param_1,0,0x12,uVar37); + */ + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x12, is_720_low_rr ? 0x00 : 0x04); + + /* SetADParameters */ + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x03, m->pllgain); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x05, m->clpdly); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x06, m->clpdur); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x07, m->hsopw); + + /* ADC gain: Y, Cb, Cr */ + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x08, 0xa0); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x09, 0xc0); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x0a, 0xa0); + + /* grade scale (clamp level): Y, Cb, Cr */ + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x0b, 0x80); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x0c, 0x60); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x0d, 0x80); + + /* clamp offset: Y, Cb, Cr */ + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x18, 0x10); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x19, 0x10); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1a, 0x10); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1b, 0x00); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1c, 0x00); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1d, 0x00); + + /* ADCSetAutoDetectFormat: output htotal */ + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x01, ht >> 4); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x02, (ht & 0x0f) << 4); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1e, m->adc_bw0); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x1f, m->adc_bw1); + + /* output data enable start pos after sync */ + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x80, (m->hde_off >> 8) & 0x0f); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x81, m->hde_off & 0xff); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x82, (m->width >> 8) & 0xff); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x83, m->width & 0xff); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x84, m->vde_off); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x85, + ((m->height >> 8) & 0x0f) | (m->interlaced ? 0x20 : 0x00)); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x86, m->height & 0xff); + + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x2d, 0x11); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x2e, 0x11); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x39, (u8)(m->clpdly + m->clpdur - 0x= 78)); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x2c, 0x9d); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x3a, 0x0c); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x3b, 0x08); + + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x10, 0xfa, 0x05); + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x0f, 0xff, 0x20); + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x17, 0xff, 0x02); + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x2f, 0xff, 0x02); + u3hc_i2c_rmw(hdcap, ADDR_MST3367, 0x60, 0xfb, 0x00); +} + +/* ------------------------------------------------------------------ */ +/* Component ADC signal polling */ +/* ------------------------------------------------------------------ */ + +static int component_poll_signal(struct usb3hdcap *hdcap, + const struct component_mode **detected) +{ + const struct component_mode *mode; + int k, status, vtotal, htotal; + int signal_count =3D 0; + + vendor_out(hdcap, REQ_STREAM, 0x0000, 0, NULL, 0); + + /* + * RxAdcModeDetect() + * Signal detection per decompiled Windows driver: + * 0x14 & 0x90 =3D=3D 0x90: H+V locked (status=3D1) + * 0x14 & 0x90 =3D=3D 0x80: H lock only (status=3D2) + * 0x14 =3D=3D 0x00 && 0x15 & 0x40: status=3D3 + */ + for (k =3D 0; k < 100; k++) { + int signal_type; + + mst_bank(hdcap, 0x00); + + /* trigger timing measurement? */ + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x0e, 0x40); + + status =3D u3hc_i2c_read(hdcap, ADDR_MST3367, 0x14); + u3hc_i2c_read(hdcap, ADDR_MST3367, 0x15); + + /* Check signal presence from 0x14 bits 7,4 */ + signal_type =3D status & 0x90; + if (signal_type =3D=3D 0x90 || signal_type =3D=3D 0x80 || + (signal_type =3D=3D 0x00 && (status & 0x02))) + signal_count++; + else + signal_count =3D 0; + + /* Read H period */ + htotal =3D ((u3hc_i2c_read(hdcap, ADDR_MST3367, 0x57) & 0x3f) << 8) | + u3hc_i2c_read(hdcap, ADDR_MST3367, 0x58); + + /* scale ADC sample rate to H period */ + if (htotal > 0) + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x11, + (htotal < 0x401 ? 0x400 : htotal) >> 7); + + /* V period (timer counts) */ + u3hc_i2c_read(hdcap, ADDR_MST3367, 0x59); + u3hc_i2c_read(hdcap, ADDR_MST3367, 0x5a); + + /* V total (lines) */ + u3hc_i2c_read(hdcap, ADDR_MST3367, 0x5b); + u3hc_i2c_read(hdcap, ADDR_MST3367, 0x5c); + + /* H total (pixels), interlace flag */ + u3hc_i2c_read(hdcap, ADDR_MST3367, 0x5d); + u3hc_i2c_read(hdcap, ADDR_MST3367, 0x5e); + u3hc_i2c_read(hdcap, ADDR_MST3367, 0x5f); + + mst_bank(hdcap, 0x02); + u3hc_i2c_read(hdcap, ADDR_MST3367, 0x11); + u3hc_i2c_read(hdcap, ADDR_MST3367, 0x12); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x20, 0x00); + + if (k % 10 =3D=3D 0) + dev_info(hdcap->dev, + "component poll[%d]: 0x14=3D0x%02x count=3D%d\n", + k, status, signal_count); + + if (signal_count < 3) { + msleep(100); + continue; + } + + /* Signal stable - read vtotal and determine format */ + mst_bank(hdcap, 0x00); + vtotal =3D ((u3hc_i2c_read(hdcap, ADDR_MST3367, 0x5b) & 0x07) << 8) | + u3hc_i2c_read(hdcap, ADDR_MST3367, 0x5c); + + dev_info(hdcap->dev, + "component locked: vtotal=3D%d htotal=3D%d 0x14=3D0x%02x\n", + vtotal, htotal, status); + + mode =3D match_component_mode(vtotal); + if (!mode) { + dev_err(hdcap->dev, + "unsupported component timing: vtotal=3D%d\n", + vtotal); + return -ERANGE; + } + hdcap->width =3D mode->width; + hdcap->height =3D mode->height; + hdcap->interlaced =3D mode->interlaced; + hdcap->bpl =3D hdcap->width * 2; + hdcap->std =3D 0; /* no SD standard */ + hdcap->detected_timings_present =3D 1; + hdcap->detected_timings =3D mode->timings; + *detected =3D mode; + return 0; + } + + dev_err(hdcap->dev, "No component signal lock after 10s\n"); + return -ETIMEDOUT; +} + +/* ------------------------------------------------------------------ */ +/* Main HDMI init entry point */ +/* ------------------------------------------------------------------ */ + +int usb3hdcap_hdmi_init(struct usb3hdcap *hdcap) +{ + int ret; + + hdcap->mst_current_bank =3D -1; + + mst3367_config(hdcap); + + /* TW9900 power-down */ + u3hc_i2c_write(hdcap, ADDR_TW9900, 0x06, 0x0e); + u3hc_i2c_write(hdcap, ADDR_TW9900, 0x1a, 0x40); + + /* CPLD init with HDMI input mux */ + cpld_init(hdcap, 0x02); + + u3hc_i2c_read(hdcap, ADDR_CPLD, 0x20); + u3hc_i2c_write(hdcap, ADDR_CPLD, 0x20, 0x05); + + /* probably not needed */ + u3hc_i2c_read(hdcap, ADDR_CPLD, 0x20); + u3hc_i2c_write(hdcap, ADDR_CPLD, 0x20, 0x05); + + ret =3D hdmi_poll_signal(hdcap); + if (ret < 0) + return ret; + + v4l2_ctrl_handler_setup(&hdcap->ctrl); + + dev_info(hdcap->dev, "hdmi_init: complete (%dx%d)\n", + hdcap->width, hdcap->height); + return 0; +} + +/* ------------------------------------------------------------------ */ +/* Main component init entry point */ +/* ------------------------------------------------------------------ */ + +int usb3hdcap_component_init(struct usb3hdcap *hdcap) +{ + const struct component_mode *mode; + int ret, use_height; + + hdcap->mst_current_bank =3D -1; + + mst3367_config(hdcap); + + /* TW9900 power-down */ + u3hc_i2c_write(hdcap, ADDR_TW9900, 0x06, 0x0e); + u3hc_i2c_write(hdcap, ADDR_TW9900, 0x1a, 0x40); + + /* CPLD init with component input mux */ + cpld_init(hdcap, 0x80); + + /* RxSwitchSource: 0x21 =3D component ADC (YPbPr) */ + mst_bank(hdcap, 0x00); + u3hc_i2c_write(hdcap, ADDR_MST3367, 0x51, 0x21); + + /* Component CPLD routing, bit 4 =3D component input? */ + u3hc_i2c_read(hdcap, ADDR_CPLD, 0x20); + u3hc_i2c_write(hdcap, ADDR_CPLD, 0x20, 0x15); + + mst3367_adc_config(hdcap); + + /* + * Wait for ADC to stabilize, windows driver does this but might be able + * to decrease wait + */ + msleep(1000); + + /* Final CPLD verify */ + u3hc_i2c_read(hdcap, ADDR_CPLD, 0x20); + u3hc_i2c_write(hdcap, ADDR_CPLD, 0x20, 0x15); + + ret =3D component_poll_signal(hdcap, &mode); + if (ret < 0) + return ret; + + u3hc_i2c_rmw(hdcap, ADDR_CPLD, 0x00, 0xff, 0x02); + + component_write_scaler(hdcap, mode); + + v4l2_ctrl_handler_setup(&hdcap->ctrl); + + use_height =3D hdcap->interlaced ? hdcap->height * 2 : hdcap->height; + dev_info(hdcap->dev, "component_init: complete (%dx%d%s)\n", + hdcap->width, use_height, + hdcap->interlaced ? "i" : "p"); + return 0; +} diff --git a/drivers/staging/media/usb3hdcap/usb3hdcap-video.c b/drivers/st= aging/media/usb3hdcap/usb3hdcap-video.c new file mode 100644 index 000000000000..bcd61cea17f0 --- /dev/null +++ b/drivers/staging/media/usb3hdcap/usb3hdcap-video.c @@ -0,0 +1,505 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB3HDCAP video streaming - stream parser, ISO URB handling, vb2 ops + */ + +#include +#include +#include +#include +#include +#include + +#include "usb3hdcap.h" + +/* ------------------------------------------------------------------ */ +/* Stream parser */ +/* ------------------------------------------------------------------ */ + +static void vyuy_to_yuyv(u8 *dst, const u8 *src, int len) +{ + int k; + + /* VYUY: V Y0 U Y1 -> YUYV: Y0 U Y1 V */ + for (k =3D 0; k < len; k +=3D 4) { + dst[k + 0] =3D src[k + 1]; /* Y0 */ + dst[k + 1] =3D src[k + 2]; /* U */ + dst[k + 2] =3D src[k + 3]; /* Y1 */ + dst[k + 3] =3D src[k + 0]; /* V */ + } +} + +static struct hdcap_buf *get_next_buf(struct usb3hdcap *hdcap) +{ + struct hdcap_buf *buf =3D NULL; + unsigned long flags; + + spin_lock_irqsave(&hdcap->buflock, flags); + if (!list_empty(&hdcap->bufs)) { + buf =3D list_first_entry(&hdcap->bufs, struct hdcap_buf, list); + list_del(&buf->list); + } + spin_unlock_irqrestore(&hdcap->buflock, flags); + + return buf; +} + +static void deliver_frame(struct usb3hdcap *hdcap) +{ + struct hdcap_buf *buf =3D hdcap->cur_buf; + u8 *vaddr; + int k; + + if (!buf) + return; + + hdcap->frames_delivered++; + + /* Pad remaining lines with black */ + vaddr =3D vb2_plane_vaddr(&buf->vb.vb2_buf, 0); + while (hdcap->frame_line < hdcap->height) { + u8 *row =3D vaddr + hdcap->frame_line * hdcap->bpl; + + for (k =3D 0; k < hdcap->bpl; k +=3D 4) { + row[k + 0] =3D 0x10; + row[k + 1] =3D 0x80; + row[k + 2] =3D 0x10; + row[k + 3] =3D 0x80; + } + hdcap->frame_line++; + } + + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, + hdcap->bpl * hdcap->height); + buf->vb.vb2_buf.timestamp =3D ktime_get_ns(); + if (hdcap->interlaced) { + buf->vb.field =3D hdcap->is_second_field ? V4L2_FIELD_BOTTOM : V4L2_FIEL= D_TOP; + buf->vb.sequence =3D hdcap->sequence; + if (hdcap->is_second_field) + hdcap->sequence++; + hdcap->is_second_field ^=3D 1; + } else { + buf->vb.field =3D V4L2_FIELD_NONE; + buf->vb.sequence =3D hdcap->sequence++; + } + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + + hdcap->cur_buf =3D NULL; +} + +/* + * Process one video line (shared between composite and HDMI). + * 'video' points to the XY byte followed by pixel data. + */ +static void process_video_line(struct usb3hdcap *hdcap, const u8 *video) +{ + u8 sav_xy =3D video[0]; + int blanking =3D sav_xy & BT656_V_BIT; + + /* Blanking -> active transition */ + if (!blanking && hdcap->was_blanking) { + if (!hdcap->synced) + hdcap->synced =3D 1; + if (hdcap->cur_buf && hdcap->frame_line >=3D 100) + deliver_frame(hdcap); + hdcap->frame_line =3D 0; + if (!hdcap->cur_buf) + hdcap->cur_buf =3D get_next_buf(hdcap); + } + hdcap->was_blanking =3D blanking; + + if (!hdcap->synced || !hdcap->cur_buf || blanking) + return; + + /* Deliver when field is full */ + if (hdcap->frame_line >=3D hdcap->height) { + deliver_frame(hdcap); + hdcap->frame_line =3D 0; + hdcap->cur_buf =3D get_next_buf(hdcap); + if (!hdcap->cur_buf) + return; + } + + vyuy_to_yuyv(vb2_plane_vaddr(&hdcap->cur_buf->vb.vb2_buf, 0) + + hdcap->frame_line * hdcap->bpl, + video, hdcap->bpl); + hdcap->frame_line++; +} + +static void process_data(struct usb3hdcap *hdcap) +{ + u8 *buf =3D hdcap->parse_buf; + int line_size =3D SAV_LEN + hdcap->bpl; + int pos =3D 0; + + while (pos + 3 <=3D hdcap->parse_len) { + if (buf[pos] !=3D 0xff || buf[pos + 1] !=3D 0x00) { + pos++; + continue; + } + + if (buf[pos + 2] =3D=3D 0x00) { + /* SAV: video line */ + if (pos + line_size > hdcap->parse_len) + break; + /* + * -1 because on this hardware, XY byte occupies the first pixel's + * V position + */ + process_video_line(hdcap, buf + pos + SAV_LEN - 1); + pos +=3D line_size; + } else if (buf[pos + 2] =3D=3D 0xff) { + u8 type; + int alen; + + if (pos + MARKER_LEN > hdcap->parse_len) + break; + type =3D buf[pos + 3]; + if (type < 0x02 || type > 0x04) { + pos++; + continue; + } + alen =3D type * 4; + if (pos + MARKER_LEN + alen > hdcap->parse_len) + break; + hdcap->markers_found++; + usb3hdcap_audio_data(hdcap, buf + pos + MARKER_LEN, alen); + pos +=3D MARKER_LEN + alen; + } else { + pos++; + } + } + + /* Shift unconsumed data to front */ + if (pos > 0) { + hdcap->parse_len -=3D pos; + if (hdcap->parse_len > 0) + memmove(buf, buf + pos, hdcap->parse_len); + } +} + +/* ------------------------------------------------------------------ */ +/* Isochronous URB handling */ +/* ------------------------------------------------------------------ */ + +static void usb3hdcap_iso_cb(struct urb *urb) +{ + struct usb3hdcap *hdcap =3D urb->context; + int k, ret; + + switch (urb->status) { + case 0: + break; + case -ENODEV: + case -ENOENT: + case -ECONNRESET: + case -ESHUTDOWN: + return; + default: + dev_warn(hdcap->dev, "ISO URB status %d\n", urb->status); + goto resubmit; + } + + hdcap->iso_cb_count++; + + for (k =3D 0; k < urb->number_of_packets; k++) { + int status =3D urb->iso_frame_desc[k].status; + int len =3D urb->iso_frame_desc[k].actual_length; + u8 *data =3D urb->transfer_buffer + + urb->iso_frame_desc[k].offset; + + if (status !=3D 0 || len =3D=3D 0) + continue; + + if (hdcap->parse_len + len > PARSE_BUF_SIZE) { + hdcap->parse_len =3D 0; + continue; + } + + memcpy(hdcap->parse_buf + hdcap->parse_len, data, len); + hdcap->parse_len +=3D len; + hdcap->iso_bytes +=3D len; + } + + process_data(hdcap); + +resubmit: + ret =3D usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) + dev_warn(hdcap->dev, "ISO URB resubmit: %d\n", ret); +} + +static struct urb *usb3hdcap_alloc_urb(struct usb3hdcap *hdcap) +{ + struct urb *urb; + int size =3D hdcap->iso_size; + int k; + + urb =3D usb_alloc_urb(NUM_ISO_PKTS, GFP_KERNEL); + if (!urb) + return NULL; + + urb->dev =3D hdcap->usb_dev; + urb->context =3D hdcap; + urb->pipe =3D usb_rcvisocpipe(hdcap->usb_dev, EP_VIDEO); + urb->interval =3D 1; + urb->transfer_flags =3D URB_ISO_ASAP; + urb->transfer_buffer =3D kcalloc(NUM_ISO_PKTS, size, GFP_KERNEL); + if (!urb->transfer_buffer) { + usb_free_urb(urb); + return NULL; + } + urb->complete =3D usb3hdcap_iso_cb; + urb->number_of_packets =3D NUM_ISO_PKTS; + urb->transfer_buffer_length =3D size * NUM_ISO_PKTS; + for (k =3D 0; k < NUM_ISO_PKTS; k++) { + urb->iso_frame_desc[k].offset =3D size * k; + urb->iso_frame_desc[k].length =3D size; + } + + return urb; +} + +static void usb3hdcap_stop(struct usb3hdcap *hdcap) +{ + int k; + unsigned long flags; + + for (k =3D 0; k < NUM_XFERS; k++) { + struct urb *urb =3D hdcap->isoc_urbs[k]; + + if (!urb) + continue; + usb_kill_urb(urb); + kfree(urb->transfer_buffer); + usb_free_urb(urb); + hdcap->isoc_urbs[k] =3D NULL; + } + + /* already NULL if this stop came from a disconnect */ + if (hdcap->usb_dev) { + vendor_out(hdcap, REQ_STREAM, 0x0000, 0, NULL, 0); + usb_set_interface(hdcap->usb_dev, 0, 0); + } + + /* Return any in-progress buffer */ + if (hdcap->cur_buf) { + vb2_buffer_done(&hdcap->cur_buf->vb.vb2_buf, + VB2_BUF_STATE_ERROR); + hdcap->cur_buf =3D NULL; + } + + spin_lock_irqsave(&hdcap->buflock, flags); + while (!list_empty(&hdcap->bufs)) { + struct hdcap_buf *buf =3D list_first_entry(&hdcap->bufs, + struct hdcap_buf, list); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + list_del(&buf->list); + } + spin_unlock_irqrestore(&hdcap->buflock, flags); + + vfree(hdcap->parse_buf); + hdcap->parse_buf =3D NULL; +} + +static int usb3hdcap_start(struct usb3hdcap *hdcap) +{ + struct usb_host_endpoint *ep; + int maxp, burst, mult; + int k, ret, alt; + + if (!hdcap->hw_inited) { + ret =3D usb3hdcap_hw_init(hdcap); + if (ret < 0) { + dev_err(hdcap->dev, "open: hw_init failed: %d\n", ret); + return ret; + } + hdcap->hw_inited =3D 1; + } + + hdcap->parse_buf =3D vzalloc(PARSE_BUF_SIZE); + if (!hdcap->parse_buf) + return -ENOMEM; + hdcap->parse_len =3D 0; + hdcap->frame_line =3D 0; + hdcap->synced =3D 0; + hdcap->was_blanking =3D 0; + hdcap->is_second_field =3D 0; + hdcap->cur_buf =3D NULL; + + if (hdcap->input =3D=3D INPUT_HDMI) { + /* + * this is a register watchlist, interrupt generated n endpoint 1 when + * (value & mask) changes. + * + * struct reg_watch { + * uint8_t reg; + * // 0x80-0x82: MST3367 bank n&0x7f, 0x40: look at I2C addr + * uint8_t bank_or_source; + * uint8_t value_mask; + * uint8_t i2c_addr; // for bank_or_source =3D=3D 0x40 + * }; + */ + u8 stream_payload[] =3D { + 0x55, 0x80, 0x3c, 0x00, 0x6a, 0x80, 0x0f, 0x00, + 0x6b, 0x80, 0xfe, 0x00, 0x57, 0x80, 0x3f, 0x00, + 0x58, 0x80, 0x80, 0x00, 0x59, 0x80, 0x3f, 0x00, + 0x5a, 0x80, 0xf0, 0x00, 0x5b, 0x80, 0x07, 0x00, + 0x5c, 0x80, 0xfe, 0x00, 0x5f, 0x80, 0x02, 0x00, + 0x01, 0x81, 0x07, 0x00, 0x29, 0x82, 0x1f, 0x00, + 0x28, 0x82, 0xff, 0x00, 0x0b, 0x82, 0xdf, 0x00, + 0x0c, 0x82, 0x3f, 0x00, 0x0e, 0x82, 0x08, 0x00, + 0x48, 0x82, 0x60, 0x00, 0x9b, 0x82, 0xf0, 0x00, + 0x11, 0x82, 0xff, 0x00, 0x12, 0x82, 0xff, 0x00, + 0x11, 0x82, 0xff, 0x00, 0x12, 0x82, 0xff, 0x00, + }; + ret =3D vendor_out(hdcap, REQ_STREAM, 0x0032, 1, + stream_payload, sizeof(stream_payload)); + } else if (hdcap->input =3D=3D INPUT_COMPONENT) { + /* Component: monitor MST3367 ADC registers */ + u8 stream_payload[] =3D { + 0x14, 0x80, 0x90, 0x00, 0x15, 0x80, 0x46, 0x00, + 0x5b, 0x80, 0x07, 0x00, 0x5c, 0x80, 0xf8, 0x00, + 0x57, 0x80, 0x3f, 0x00, 0x5f, 0x80, 0x12, 0x00, + 0x11, 0x82, 0xff, 0x00, 0x12, 0x82, 0xff, 0x00, + }; + ret =3D vendor_out(hdcap, REQ_STREAM, 0x0032, 1, + stream_payload, sizeof(stream_payload)); + } else { + u8 stream_payload[] =3D { + 0x01, 0x40, 0xe9, 0x88, + 0x1c, 0x40, 0xf0, 0x88, + 0x30, 0x40, 0x0f, 0x88, + }; + v4l2_ctrl_handler_setup(&hdcap->ctrl); + ret =3D vendor_out(hdcap, REQ_STREAM, 0x0032, 1, + stream_payload, sizeof(stream_payload)); + } + + if (ret < 0) { + vfree(hdcap->parse_buf); + hdcap->parse_buf =3D NULL; + return ret; + } + + /* + * Select alt setting based on bandwidth: + * alt 1: maxp=3D1024 burst=3D16 mult=3D1 -> 16KB/pkt (SD) + * alt 2: maxp=3D1024 burst=3D16 mult=3D2 -> 32KB/pkt (HD) + */ + alt =3D (hdcap->width > 720 || hdcap->height > 576) ? 2 : 1; + dev_info(hdcap->dev, "%s: setting alt %d\n", __func__, alt); + ret =3D usb_set_interface(hdcap->usb_dev, 0, alt); + + if (ret < 0) { + dev_err(hdcap->dev, "%s: usb_set_interface failed: %d\n", + __func__, ret); + vfree(hdcap->parse_buf); + hdcap->parse_buf =3D NULL; + return ret; + } + + /* Compute ISO packet size from SS endpoint companion descriptor */ + ep =3D usb_pipe_endpoint(hdcap->usb_dev, + usb_rcvisocpipe(hdcap->usb_dev, EP_VIDEO)); + if (!ep) { + dev_err(hdcap->dev, "ISO endpoint 0x%02x not found\n", + EP_VIDEO); + return -ENODEV; + } + + maxp =3D usb_endpoint_maxp(&ep->desc); + burst =3D ep->ss_ep_comp.bMaxBurst; + mult =3D USB_SS_MULT(ep->ss_ep_comp.bmAttributes); + hdcap->iso_size =3D maxp * (burst + 1) * mult; + + for (k =3D 0; k < NUM_XFERS; k++) { + struct urb *urb =3D usb3hdcap_alloc_urb(hdcap); + + if (!urb) { + ret =3D -ENOMEM; + goto fail; + } + hdcap->isoc_urbs[k] =3D urb; + + ret =3D usb_submit_urb(urb, GFP_KERNEL); + if (ret < 0) + goto fail; + } + + return 0; + +fail: + usb3hdcap_stop(hdcap); + return ret; +} + +/* ------------------------------------------------------------------ */ +/* vb2 ops */ +/* ------------------------------------------------------------------ */ + +static int usb3hdcap_queue_setup(struct vb2_queue *vq, unsigned int *nbuff= ers, + unsigned int *nplanes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct usb3hdcap *hdcap =3D vb2_get_drv_priv(vq); + unsigned int img_size =3D hdcap->bpl * hdcap->height; + + if (*nplanes) { + dev_info(hdcap->dev, "queue_setup: recheck sizes[0]=3D%u vs %u\n", + sizes[0], img_size); + return sizes[0] < img_size ? -EINVAL : 0; + } + *nplanes =3D 1; + sizes[0] =3D img_size; + + return 0; +} + +static void usb3hdcap_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf =3D to_vb2_v4l2_buffer(vb); + struct usb3hdcap *hdcap =3D vb2_get_drv_priv(vb->vb2_queue); + struct hdcap_buf *buf =3D container_of(vbuf, struct hdcap_buf, vb); + unsigned long flags; + + if (!hdcap->usb_dev) { + dev_warn(hdcap->dev, "buf_queue: usb_dev is NULL\n"); + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); + return; + } + + spin_lock_irqsave(&hdcap->buflock, flags); + list_add_tail(&buf->list, &hdcap->bufs); + spin_unlock_irqrestore(&hdcap->buflock, flags); +} + +static int usb3hdcap_start_streaming(struct vb2_queue *vq, unsigned int co= unt) +{ + struct usb3hdcap *hdcap =3D vb2_get_drv_priv(vq); + int ret; + + if (!hdcap->usb_dev) { + dev_err(hdcap->dev, "start_streaming: usb_dev is NULL\n"); + return -ENODEV; + } + + hdcap->sequence =3D 0; + ret =3D usb3hdcap_start(hdcap); + return ret; +} + +static void usb3hdcap_stop_streaming(struct vb2_queue *vq) +{ + struct usb3hdcap *hdcap =3D vb2_get_drv_priv(vq); + + if (hdcap->usb_dev) + usb3hdcap_stop(hdcap); +} + +const struct vb2_ops usb3hdcap_vb2_ops =3D { + .queue_setup =3D usb3hdcap_queue_setup, + .buf_queue =3D usb3hdcap_buf_queue, + .start_streaming =3D usb3hdcap_start_streaming, + .stop_streaming =3D usb3hdcap_stop_streaming, +}; diff --git a/drivers/staging/media/usb3hdcap/usb3hdcap.h b/drivers/staging/= media/usb3hdcap/usb3hdcap.h new file mode 100644 index 000000000000..2ebe91cd6673 --- /dev/null +++ b/drivers/staging/media/usb3hdcap/usb3hdcap.h @@ -0,0 +1,234 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef USB3HDCAP_H +#define USB3HDCAP_H + +#include +#include +#include +#include +#include + +struct snd_card; +struct snd_pcm_substream; + +#define USB3HDCAP_V4L2_STDS (V4L2_STD_NTSC | V4L2_STD_PAL) + +/* NTSC SD frame format */ +#define SD_WIDTH 720 +#define NTSC_HEIGHT 480 +/* YUYV =3D 4 bytes/2 pixels */ +#define DEFAULT_BPL (SD_WIDTH * 2) +/* PAL SD frame format */ +#define PAL_HEIGHT 576 + +enum usb3hdcap_input { + INPUT_COMPOSITE =3D 0, + INPUT_SVIDEO =3D 1, + INPUT_COMPONENT =3D 2, + INPUT_HDMI =3D 3, +}; + +/* I2C 8-bit addresses */ +#define ADDR_MST3367 0x9c +#define ADDR_TW9900 0x88 +#define ADDR_CS53L21 0x94 +#define ADDR_CPLD 0x98 +#define ADDR_MCU 0x64 /* Nuvoton NUC100 on XCAPTURE-1 */ + +/* TW9900 registers */ +#define TW9900_CSTATUS 0x01 +#define TW9900_INFORM 0x02 +#define TW9900_OPFORM 0x03 +#define TW9900_OPCNTL_I 0x05 +#define TW9900_ACNTL_I 0x06 +#define TW9900_CROP_HI 0x07 +#define TW9900_VDELAY_LO 0x08 +#define TW9900_VACTIVE_LO 0x09 +#define TW9900_HDELAY_LO 0x0a +#define TW9900_HACTIVE_LO 0x0b +#define TW9900_CNTRL2 0x0d +#define TW9900_SDT_NOISE 0x0e +#define TW9900_LUMA_CTL 0x0f +#define TW9900_BRIGHT 0x10 +#define TW9900_CONTRAST 0x11 +#define TW9900_SHARPNESS 0x12 +#define TW9900_SAT_U 0x13 +#define TW9900_SAT_V 0x14 +#define TW9900_HUE 0x15 +#define TW9900_VBICNTL 0x19 +#define TW9900_ACNTL_II 0x1a +#define TW9900_OPCNTL_II 0x1b +#define TW9900_SDT 0x1c +#define TW9900_SDTR 0x1d +#define TW9900_VCNTL1 0x28 +#define TW9900_VCNTL2 0x29 +#define TW9900_MISC1 0x2d +#define TW9900_MISC2 0x2f +#define TW9900_CSTATUS_II 0x31 +#define TW9900_ANAPLLCTL 0x4c +#define TW9900_VVBI 0x55 +#define TW9900_HSBEGIN 0x6b +#define TW9900_HSEND 0x6c +#define TW9900_OVSDLY 0x6d +#define TW9900_OVSEND 0x6e + +/* TW9900 bit definitions */ +#define TW9900_NINTL 0x08 +#define TW9900_STDNOW_SHIFT 4 +#define TW9900_STDNOW_MASK 0x70 +#define TW9900_STD_NTSC_M 0 +#define TW9900_STD_PAL_BDGHI 1 +#define TW9900_STD_SECAM 2 +#define TW9900_STD_NTSC_4_43 3 +#define TW9900_STD_PAL_M 4 +#define TW9900_STD_PAL_CN 5 +#define TW9900_STD_PAL_60 6 + +/* CS53L21 registers */ +#define CS53L21_MIC_PWR_CTL 0x03 +#define CS53L21_IFACE_CTL 0x04 +#define CS53L21_ADC_IN_SEL 0x07 +#define CS53L21_SPE_CTL 0x09 +#define CS53L21_ALC_PGAA 0x0a +#define CS53L21_ALC_PGAB 0x0b +#define CS53L21_ADCA_ATT 0x0c +#define CS53L21_ADCB_ATT 0x0d +#define CS53L21_ADCA_MIX_VOL 0x0e +#define CS53L21_ADCB_MIX_VOL 0x0f +#define CS53L21_CH_MIXER 0x18 +#define CS53L21_ALC_EN_ATK 0x1c + +/* Vendor request codes */ +#define REQ_I2C 0xc0 +#define REQ_GPIO 0xc1 +#define REQ_INIT 0xc2 +#define REQ_STREAM 0xc6 +#define REQ_UNK_C7 0xc7 + +/* Isochronous transfer parameters */ +#define EP_VIDEO 0x83 +#define NUM_XFERS 8 +#define NUM_ISO_PKTS 64 + +/* + * Stream framing: two element types in the byte stream, both + * starting with ff 00: + * SAV (ff 00 00 XY): bpl-1 pixel bytes, -1 because the XY is actually + * in the first pixel's V position... weird but it's correct + * Marker (ff 00 ff NN): audio - NN =3D number of stereo s16le + * samples, audio data =3D NN * 4 bytes + */ +#define SAV_LEN 4 +#define MARKER_LEN 4 +#define PARSE_BUF_SIZE (2 * 1024 * 1024) + +/* BT.656 SAV XY bits */ +#define BT656_F_BIT 0x40 +#define BT656_V_BIT 0x20 + +struct hdcap_buf { + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +struct usb3hdcap { + struct device *dev; + struct usb_device *usb_dev; + struct v4l2_device v4l2_dev; + struct video_device video_dev; + struct vb2_queue vb2q; + + struct v4l2_ctrl_handler ctrl; + struct v4l2_ctrl *ctrl_brightness; + struct v4l2_ctrl *ctrl_contrast; + struct v4l2_ctrl *ctrl_saturation; + struct v4l2_ctrl *ctrl_hue; + struct v4l2_ctrl *ctrl_sharpness; + struct v4l2_ctrl *ctrl_rx_power; + + /* protects V4L2 ioctls and device state */ + struct mutex v4l2_lock; + /* protects videobuf2 queue operations */ + struct mutex vb2q_lock; + + /* buffer list */ + spinlock_t buflock; + struct list_head bufs; + + /* ISO URBs */ + int iso_size; + struct urb *isoc_urbs[NUM_XFERS]; + + /* frame tracking */ + unsigned int sequence; + + /* input selection + format */ + enum usb3hdcap_input input; + v4l2_std_id std; + v4l2_std_id requested_std; + int width; + int height; + int interlaced; + int bpl; /* width * 2 */ + + /* stream parser state */ + u8 *parse_buf; + int parse_len; + struct hdcap_buf *cur_buf; + int frame_line; + int synced; + int was_blanking; + int is_second_field; + + /* hardware init state */ + int hw_inited; + int has_mcu; /* Nuvoton MCU present (XCAPTURE-1) */ + + /* MST3367 state */ + int mst_current_bank; + struct v4l2_dv_timings detected_timings; + int detected_timings_present; + struct v4l2_dv_timings requested_timings; + int requested_timings_present; + + /* ALSA audio */ + struct snd_card *snd; + struct snd_pcm_substream *snd_substream; + atomic_t snd_stream; + size_t snd_buffer_pos; + size_t snd_period_pos; + + /* diagnostics */ + unsigned int iso_cb_count; + unsigned long iso_bytes; + unsigned int markers_found; + unsigned int frames_delivered; +}; + +/* USB control helpers (usb3hdcap-core.c) */ +int vendor_out(struct usb3hdcap *hdcap, u8 request, u16 value, u16 index, + u8 *data, u16 len); +int u3hc_i2c_write(struct usb3hdcap *hdcap, u8 addr, u8 reg, u8 val); +int u3hc_i2c_read(struct usb3hdcap *hdcap, u8 addr, u8 reg); +int u3hc_i2c_rmw(struct usb3hdcap *hdcap, u8 addr, u8 reg, u8 mask, u8 bit= s); +int u3hc_i2c_rmw_get_old(struct usb3hdcap *hdcap, u8 addr, u8 reg, u8 mask= , u8 bits, u8 *old); +int usb3hdcap_hw_init(struct usb3hdcap *hdcap); + +/* Composite (usb3hdcap-composite.c) */ +int usb3hdcap_composite_init(struct usb3hdcap *hdcap); + +/* HDMI + Component (usb3hdcap-hdmi.c) */ +int usb3hdcap_hdmi_init(struct usb3hdcap *hdcap); +int usb3hdcap_component_init(struct usb3hdcap *hdcap); +void mst3367_write_csc(struct usb3hdcap *hdcap); + +/* Video/streaming (usb3hdcap-video.c) */ +extern const struct vb2_ops usb3hdcap_vb2_ops; + +/* Audio (usb3hdcap-audio.c) */ +int usb3hdcap_cs53l21_init(struct usb3hdcap *hdcap); +int usb3hdcap_audio_init(struct usb3hdcap *hdcap); +void usb3hdcap_audio_free(struct usb3hdcap *hdcap); +void usb3hdcap_audio_data(struct usb3hdcap *hdcap, const u8 *data, int len= ); + +#endif --=20 2.47.3