gk7205v200: add an OTG profile that makes the camera a USB webcam (#113)
Adds gk7205v200_otg_generic, a firmware profile that puts the single DWC3 controller into dual-role so the board can either consume a USB webcam (existing usbcam path, host role) or present itself to a PC as one (UVC gadget, device role). The roles are mutually exclusive -- xHCI HCSPARAMS1 reports MaxPorts = 1 -- but usb-mode switches between them at runtime, so one image covers both and no reboot is needed. Kernel side, three patches: 0015 sets dr_mode = "otg" in the DTS so dwc3 registers both an xHCI host and a UDC. 0016 makes device mode actually work on a part that has only ever run as a host. Three fixes: re-run dwc3_event_buffers_setup() on gadget start (an xHCI reset wipes GEVNTADR), soft-reset the core before taking the lock so the PHY is re-initialised (without it high-speed chirp fails), and swallow the run_stop timeout on pullup(0), which otherwise permanently poisons cdev->deactivations. 0017 disables the vendor's UVC_SG_REQ path. It hands a whole frame to the UDC as one scatter-gather request; dwc3 chains those sg entries into a single isochronous service interval, which on a full-speed link is one 1023-byte packet, so every frame larger than that reached the host truncated and H.264 decoders reported "bytestream -5" on roughly half the frames. Goke never shipped this path enabled -- CONFIG_USB_CONFIGFS_F_UVC is unset in every vendor defconfig, including gk7605v100's, and the SDK carries no userspace UVC sample -- so it was never exercised. The mainline path (32 plain one-packet requests) is used instead. Userspace side: usb-mode switches roles with the unbind/flip/bind sequencing dwc3_set_mode() does not do on its own, uvc-gadget-setup composes the configfs gadget, and S72usbmode forces a concrete role at boot -- not optional, since dr_mode = "otg" leaves GCTL.PRTCAPDIR in a state this 4.9 dwc3 never moves off by itself. The descriptors advertise exactly the one format, frame size and interval majestic is configured to serve, read from /etc/majestic.yaml. majestic answers every PROBE/COMMIT with its own bFormatIndex/bFrameIndex because it has one stream and cannot follow the host, so anything extra is an invitation to select something that will not arrive: asking for MJPEG got raw H.264 (an SPS where a JPEG SOI belongs, garbage to every consumer), and asking for 1080p got 720p. Profile drops the air-unit packages (wifibroadcast, rtl8812au, mavfwd, mavlink-router, vtund, linux-firmware, wireless-tools) and the whole wireless kernel stack, which pays for usb_f_uvc.ko and for keeping the full sensor list -- the fpv trim to IMX307/IMX335 is keyed on OPENIPC_VARIANT="fpv", and a general-purpose USB webcam should not be sensor-locked. Measured on a gk7205v200 lab board over a full-speed link: H.264 1280x720 at 30 fps with zero decoder errors and no dropped buffers, and MJPEG delivering valid JPEGs at the jpeg channel's own rate. Requires a majestic built with ENABLE_UVC_GADGET to feed the gadget; that is a separate change.
This commit is contained in:
@@ -176,6 +176,9 @@ jobs:
|
||||
- hi3516cv300_mini
|
||||
- hi3518ev200_mini
|
||||
|
||||
# OTG (camera presents itself to a PC as a USB webcam)
|
||||
- gk7205v200_otg_generic
|
||||
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@v4
|
||||
|
||||
+2811
File diff suppressed because it is too large
Load Diff
+55
@@ -0,0 +1,55 @@
|
||||
# Architecture
|
||||
BR2_arm=y
|
||||
BR2_cortex_a7=y
|
||||
BR2_ARM_EABI=y
|
||||
BR2_ARM_FPU_NEON_VFPV4=y
|
||||
BR2_ARM_INSTRUCTIONS_THUMB2=y
|
||||
|
||||
# Toolchain
|
||||
BR2_TOOLCHAIN_EXTERNAL=y
|
||||
BR2_TOOLCHAIN_EXTERNAL_DOWNLOAD=y
|
||||
BR2_TOOLCHAIN_EXTERNAL_URL="https://github.com/openipc/firmware/releases/download/$(OPENIPC_TOOLCHAIN).tgz"
|
||||
BR2_TOOLCHAIN_EXTERNAL_CUSTOM_PREFIX="arm-openipc-linux-musleabi"
|
||||
BR2_TOOLCHAIN_EXTERNAL_HEADERS_4_9=y
|
||||
BR2_TOOLCHAIN_EXTERNAL_CUSTOM_MUSL=y
|
||||
BR2_TOOLCHAIN_EXTERNAL_CXX=y
|
||||
|
||||
# Kernel
|
||||
BR2_LINUX_KERNEL=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_TARBALL=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_TARBALL_LOCATION="https://github.com/openipc/linux/archive/$(OPENIPC_KERNEL).tar.gz"
|
||||
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="$(EXTERNAL_VENDOR)/board/$(OPENIPC_SOC_FAMILY)/gk7205v200.generic-otg.config"
|
||||
BR2_LINUX_KERNEL_UIMAGE=y
|
||||
BR2_LINUX_KERNEL_XZ=y
|
||||
|
||||
# Filesystem
|
||||
BR2_PACKAGE_BUSYBOX_CONFIG="$(BR2_EXTERNAL)/package/busybox/busybox.config"
|
||||
BR2_PACKAGE_UBOOT_TOOLS=y
|
||||
BR2_PACKAGE_LIBZIP=y
|
||||
BR2_PACKAGE_JSON_C=y
|
||||
# BR2_PACKAGE_IFUPDOWN_SCRIPTS is not set
|
||||
BR2_TARGET_ROOTFS_CPIO=y
|
||||
BR2_TARGET_ROOTFS_SQUASHFS=y
|
||||
BR2_TARGET_ROOTFS_SQUASHFS4_XZ=y
|
||||
|
||||
# OpenIPC
|
||||
BR2_OPENIPC_SOC_VENDOR="goke"
|
||||
BR2_OPENIPC_SOC_MODEL="gk7205v200"
|
||||
BR2_OPENIPC_SOC_FAMILY="gk7205v200"
|
||||
BR2_OPENIPC_VARIANT="otg"
|
||||
BR2_OPENIPC_FLASH_SIZE="8"
|
||||
|
||||
# Packages
|
||||
BR2_PACKAGE_DROPBEAR_OPENIPC=y
|
||||
BR2_PACKAGE_GOKE_OSDRV_GK7205V200=y
|
||||
BR2_PACKAGE_HISILICON_OPENSDK=y
|
||||
BR2_PACKAGE_IPCTOOL=y
|
||||
BR2_PACKAGE_LIBCURL_OPENIPC=y
|
||||
BR2_PACKAGE_LIBCURL_OPENIPC_CURL=y
|
||||
# BR2_PACKAGE_LIBCURL_OPENIPC_PROXY_SUPPORT is not set
|
||||
# BR2_PACKAGE_LIBCURL_OPENIPC_COOKIES_SUPPORT is not set
|
||||
# BR2_PACKAGE_LIBCURL_OPENIPC_EXTRA_PROTOCOLS_FEATURES is not set
|
||||
BR2_PACKAGE_MAJESTIC=y
|
||||
BR2_PACKAGE_MBEDTLS_OPENIPC=y
|
||||
BR2_PACKAGE_YAML_CLI=y
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Force a concrete USB role at boot.
|
||||
#
|
||||
# This is NOT optional on this board. The device tree sets dr_mode = "otg" so
|
||||
# that dwc3 registers both an xHCI host and a UDC, which is what makes runtime
|
||||
# role switching possible. But dwc3_core_init() then leaves
|
||||
# GCTL.PRTCAPDIR = OTG(3), and this 4.9 dwc3 has no working OTG role-detection
|
||||
# block -- nothing ever moves it off OTG on its own. Left alone the controller
|
||||
# sits in a role it cannot drive and USB works in neither direction.
|
||||
#
|
||||
# Runs after S70vendor so the vendor modules are up first.
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
mode=host
|
||||
[ -r /etc/usbmode ] && read -r mode < /etc/usbmode
|
||||
case "$mode" in
|
||||
host|device) ;;
|
||||
*) mode=host ;;
|
||||
esac
|
||||
echo "Setting USB role to $mode..."
|
||||
/usr/bin/usb-mode "$mode" || echo "usb-mode $mode failed"
|
||||
;;
|
||||
|
||||
stop)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Usage: $0 {start|stop}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,159 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Switch the single DWC3 controller between USB host and USB device roles.
|
||||
#
|
||||
# host - xHCI drives the port. This is what the usbcam second-camera path
|
||||
# needs (majestic consuming a USB webcam).
|
||||
# device - the UVC gadget drives the port, so a PC sees this camera as a
|
||||
# webcam.
|
||||
#
|
||||
# The two are mutually exclusive: xHCI HCSPARAMS1 reports MaxPorts = 1, so
|
||||
# there is one port and one role at a time. Switching does not need a reboot.
|
||||
#
|
||||
# Why this is more than one echo: dwc3_set_mode() (drivers/usb/dwc3/core.c)
|
||||
# only flips GCTL.PRTCAPDIR. It does not stop the host stack or start the
|
||||
# gadget stack. Writing the debugfs `mode` file on its own leaves xHCI still
|
||||
# bound and its DMA engine still live, pointed at a controller that has been
|
||||
# re-aimed underneath it. Unbind first, then flip, then bring the other side
|
||||
# up.
|
||||
|
||||
set -e
|
||||
|
||||
STATE=/etc/usbmode
|
||||
GADGET=/sys/kernel/config/usb_gadget/uvc0
|
||||
|
||||
die() { echo "usb-mode: $*" >&2; exit 1; }
|
||||
|
||||
dwc3_mode_file() {
|
||||
for f in /sys/kernel/debug/*.dwc3/mode; do
|
||||
[ -e "$f" ] && { echo "$f"; return 0; }
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
xhci_device() {
|
||||
for d in /sys/bus/platform/drivers/xhci-hcd/*.auto; do
|
||||
[ -e "$d" ] && { basename "$d"; return 0; }
|
||||
done
|
||||
# Not currently bound; fall back to the device that exists on the bus.
|
||||
for d in /sys/bus/platform/devices/xhci-hcd.*.auto; do
|
||||
[ -e "$d" ] && { basename "$d"; return 0; }
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
udc_name() {
|
||||
for u in /sys/class/udc/*; do
|
||||
[ -e "$u" ] && { basename "$u"; return 0; }
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
need_debugfs() {
|
||||
mountpoint -q /sys/kernel/debug 2>/dev/null ||
|
||||
mount -t debugfs none /sys/kernel/debug
|
||||
MODE_FILE=$(dwc3_mode_file) ||
|
||||
die "no dwc3 mode file; is CONFIG_USB_DWC3_DUAL_ROLE enabled and debugfs mounted?"
|
||||
}
|
||||
|
||||
teardown_gadget() {
|
||||
[ -d "$GADGET" ] || return 0
|
||||
|
||||
# Unbind from the UDC first: this drops the D+ pullup so the host sees a
|
||||
# disconnect rather than a device that stops answering.
|
||||
if [ -e "$GADGET/UDC" ] && [ -s "$GADGET/UDC" ]; then
|
||||
echo "" > "$GADGET/UDC" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# configfs rmdir is strict about ordering: links before the things they
|
||||
# point at, leaves before their parents. ENOTEMPTY here means something
|
||||
# below was missed.
|
||||
rm -f "$GADGET/configs/c.1/uvc.0"
|
||||
|
||||
F="$GADGET/functions/uvc.0"
|
||||
rm -f "$F/control/class/fs/h" "$F/control/class/ss/h"
|
||||
rm -f "$F/streaming/class/fs/h" "$F/streaming/class/hs/h" "$F/streaming/class/ss/h"
|
||||
# uvc-gadget-setup composes whichever single format majestic is set to, so
|
||||
# tear down both possibilities: only one of them exists on any given boot.
|
||||
rm -f "$F/streaming/header/h/f"
|
||||
rmdir "$F/streaming/header/h" 2>/dev/null || true
|
||||
rmdir "$F/streaming/mjpeg/m/f" 2>/dev/null || true
|
||||
rmdir "$F/streaming/mjpeg/m" 2>/dev/null || true
|
||||
rmdir "$F/streaming/framebased/h/f" 2>/dev/null || true
|
||||
rmdir "$F/streaming/framebased/h" 2>/dev/null || true
|
||||
rmdir "$F/control/header/h" 2>/dev/null || true
|
||||
|
||||
# EBUSY here means a userspace process still holds the gadget's
|
||||
# /dev/videoN open -- almost always majestic. Stop it first.
|
||||
if ! rmdir "$F" 2>/dev/null; then
|
||||
die "cannot remove uvc.0 (EBUSY?) -- is majestic still holding /dev/video*? stop it first"
|
||||
fi
|
||||
|
||||
rmdir "$GADGET/configs/c.1/strings/0x409" 2>/dev/null || true
|
||||
rmdir "$GADGET/configs/c.1" 2>/dev/null || true
|
||||
rmdir "$GADGET/strings/0x409" 2>/dev/null || true
|
||||
rmdir "$GADGET" 2>/dev/null || true
|
||||
}
|
||||
|
||||
to_device() {
|
||||
need_debugfs
|
||||
|
||||
if X=$(xhci_device) && [ -e "/sys/bus/platform/drivers/xhci-hcd/$X" ]; then
|
||||
echo "usb-mode: unbinding host controller $X"
|
||||
echo "$X" > /sys/bus/platform/drivers/xhci-hcd/unbind
|
||||
fi
|
||||
|
||||
echo device > "$MODE_FILE"
|
||||
|
||||
modprobe configfs 2>/dev/null || true
|
||||
modprobe libcomposite 2>/dev/null || true
|
||||
modprobe usb_f_uvc 2>/dev/null || true
|
||||
|
||||
mountpoint -q /sys/kernel/config 2>/dev/null ||
|
||||
mount -t configfs none /sys/kernel/config
|
||||
|
||||
/usr/bin/uvc-gadget-setup
|
||||
|
||||
UDC=$(udc_name) ||
|
||||
die "no UDC registered -- is dr_mode=\"otg\" in the device tree?"
|
||||
echo "$UDC" > "$GADGET/UDC"
|
||||
|
||||
echo device > "$STATE"
|
||||
echo "usb-mode: device (UVC gadget bound to $UDC)"
|
||||
}
|
||||
|
||||
to_host() {
|
||||
need_debugfs
|
||||
|
||||
teardown_gadget
|
||||
|
||||
echo host > "$MODE_FILE"
|
||||
|
||||
if X=$(xhci_device) && [ ! -e "/sys/bus/platform/drivers/xhci-hcd/$X" ]; then
|
||||
echo "usb-mode: rebinding host controller $X"
|
||||
echo "$X" > /sys/bus/platform/drivers/xhci-hcd/bind
|
||||
fi
|
||||
|
||||
echo host > "$STATE"
|
||||
echo "usb-mode: host"
|
||||
}
|
||||
|
||||
status() {
|
||||
need_debugfs
|
||||
echo "dwc3 role : $(cat "$MODE_FILE")"
|
||||
echo "persisted : $([ -r "$STATE" ] && cat "$STATE" || echo '(unset, defaults to host)')"
|
||||
echo "udc : $(udc_name || echo '(none)')"
|
||||
echo "xhci bound: $(xhci_device >/dev/null 2>&1 && \
|
||||
([ -e "/sys/bus/platform/drivers/xhci-hcd/$(xhci_device)" ] && echo yes || echo no) || echo '(no device)')"
|
||||
echo "gadget : $([ -d "$GADGET" ] && echo "composed, UDC='$(cat "$GADGET/UDC" 2>/dev/null)'" || echo 'not composed')"
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
device) to_device ;;
|
||||
host) to_host ;;
|
||||
status) status ;;
|
||||
*)
|
||||
echo "Usage: $0 {host|device|status}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,165 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Compose the UVC gadget in configfs, advertising exactly the one format and
|
||||
# frame size majestic is configured to serve.
|
||||
#
|
||||
# Advertising a menu is worse than useless here. majestic answers every
|
||||
# PROBE/COMMIT with its own bFormatIndex/bFrameIndex, because it has exactly one
|
||||
# stream to give -- it cannot switch encoder geometry or codec to follow the
|
||||
# host. So anything extra in the descriptors is a resolution or a codec the host
|
||||
# is invited to select and will then not receive: ask for MJPEG and H.264
|
||||
# arrives (an SPS where a JPEG SOI belongs, which is garbage to every consumer),
|
||||
# ask for 1080p and 720p arrives (decoders recover from the SPS, anything
|
||||
# trusting the negotiated V4L2 format does not).
|
||||
#
|
||||
# Re-run this after changing uvcgadget.format/size (usb-mode host && usb-mode
|
||||
# device, or a reboot) or the descriptors will describe the previous config.
|
||||
#
|
||||
# Run after the gadget modules are loaded and configfs is mounted, and BEFORE
|
||||
# anything writes the UDC name. `usb-mode device` does both for you.
|
||||
#
|
||||
# Notes that cost real debugging time, do not "fix" these:
|
||||
#
|
||||
# * The streaming group is "framebased", NOT "frame_based". The latter is the
|
||||
# upstream >=5.15 spelling; this 4.9 vendor tree uses framebased. Using the
|
||||
# wrong one fails silently -- mkdir just creates a junk directory that the
|
||||
# gadget ignores, and the host sees no H.264 format.
|
||||
# * control/class has only fs and ss. streaming/class has fs, hs and ss.
|
||||
# Creating control/class/hs fails.
|
||||
# * Attribute file names are camelCase (wWidth, dwFrameInterval, ...), even
|
||||
# though the kernel's C identifiers are snake_case.
|
||||
# * Do NOT try to set streaming_maxpacket / streaming_interval /
|
||||
# streaming_maxburst. uvc_configfs.c's UVCG_OPTS_ATTR macro takes no `aname`
|
||||
# parameter but still calls UVC_ATTR(..., aname), so all three land in
|
||||
# configfs as files literally named "aname" and are unreachable. This is
|
||||
# harmless: uvc_alloc_inst() already defaults them to 1 / 3072 / 15, and
|
||||
# 3072 is the USB 2.0 high-speed maximum (3 transactions x 1024 B per
|
||||
# microframe = ~24.5 MB/s isoc ceiling). There is nothing better to set.
|
||||
# * The framebased format's GUID is hardcoded to H.264 by the kernel, so
|
||||
# there is no guidFormat attribute to write for it.
|
||||
|
||||
set -e
|
||||
|
||||
G=/sys/kernel/config/usb_gadget/uvc0
|
||||
YAML=/etc/majestic.yaml
|
||||
|
||||
# Read majestic's config, falling back to the same defaults uvcgadget_start()
|
||||
# uses so a missing key or a missing `cli` still composes a working gadget.
|
||||
cfg() {
|
||||
_v=$(cli -g ".$1" 2>/dev/null) || _v=""
|
||||
case "$_v" in ""|null|"~") echo "$2" ;; *) echo "$_v" ;; esac
|
||||
}
|
||||
|
||||
FORMAT=$(cfg uvcgadget.format h264)
|
||||
SIZE=$(cfg uvcgadget.size 1280x720)
|
||||
WIDTH=${SIZE%%x*}
|
||||
HEIGHT=${SIZE##*x}
|
||||
case "$WIDTH$HEIGHT" in
|
||||
*[!0-9]*|"") WIDTH=1280; HEIGHT=720 ;;
|
||||
esac
|
||||
|
||||
# MJPEG frames come from the always-on JPEG channel, which runs at jpeg.fps and
|
||||
# not at uvcgadget.fps. Advertise whichever actually feeds this format.
|
||||
if [ "$FORMAT" = "mjpeg" ]; then
|
||||
FPS=$(cfg jpeg.fps 5)
|
||||
else
|
||||
FPS=$(cfg uvcgadget.fps 30)
|
||||
fi
|
||||
[ "$FPS" -gt 0 ] 2>/dev/null || FPS=30
|
||||
|
||||
# Frame intervals are in 100 ns units: 10000000 / fps.
|
||||
INTERVAL=$((10000000 / FPS))
|
||||
|
||||
if [ -d "$G" ]; then
|
||||
echo "uvc-gadget-setup: $G already exists, nothing to do"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mkdir -p "$G"
|
||||
|
||||
# 0x1d6b/0x0102 is the Linux Foundation vendor ID with the "UVC gadget"
|
||||
# product ID used by the in-tree g_webcam example. Fine for a device that is
|
||||
# never going to ship a USB-IF certified VID/PID.
|
||||
echo 0x1d6b > "$G/idVendor"
|
||||
echo 0x0102 > "$G/idProduct"
|
||||
|
||||
mkdir -p "$G/strings/0x409"
|
||||
echo "OpenIPC" > "$G/strings/0x409/manufacturer"
|
||||
echo "gk7205v200 UVC" > "$G/strings/0x409/product"
|
||||
[ -r /sys/class/net/eth0/address ] &&
|
||||
tr -d ':' < /sys/class/net/eth0/address > "$G/strings/0x409/serialnumber" ||
|
||||
echo "0000" > "$G/strings/0x409/serialnumber"
|
||||
|
||||
mkdir -p "$G/configs/c.1/strings/0x409"
|
||||
echo "UVC" > "$G/configs/c.1/strings/0x409/configuration"
|
||||
# Self-powered: the camera runs off its own PSU, it draws nothing from the host.
|
||||
echo 0xc0 > "$G/configs/c.1/bmAttributes"
|
||||
echo 0 > "$G/configs/c.1/MaxPower"
|
||||
|
||||
mkdir -p "$G/functions/uvc.0"
|
||||
F="$G/functions/uvc.0"
|
||||
|
||||
# One interval, the one we can sustain. Hosts pick the fastest entry on offer,
|
||||
# so a rate we cannot feed is not a harmless extra: the host commits to it,
|
||||
# sizes its timing from it, and then reports the shortfall as missing frames.
|
||||
#
|
||||
# This MUST reach the kernel as a single write(): configfs invokes the
|
||||
# attribute's store() once per write() syscall, and each store() replaces the
|
||||
# whole list rather than appending. busybox `printf '%s\n%s\n' a b` emits one
|
||||
# write() per directive, so it stores only the last value and then fails with
|
||||
# EINVAL on the second write. An `echo` of one string is a single write().
|
||||
|
||||
# $1 format dir, $2 min bitrate, $3 max bitrate
|
||||
add_frame() {
|
||||
_d="$1/f"
|
||||
mkdir -p "$_d"
|
||||
echo "$WIDTH" > "$_d/wWidth"
|
||||
echo "$HEIGHT" > "$_d/wHeight"
|
||||
echo "$INTERVAL" > "$_d/dwDefaultFrameInterval"
|
||||
echo "$INTERVAL" > "$_d/dwFrameInterval"
|
||||
echo "$2" > "$_d/dwMinBitRate"
|
||||
echo "$3" > "$_d/dwMaxBitRate"
|
||||
}
|
||||
|
||||
# Bit rates are informational; scale them off the frame area so the numbers stay
|
||||
# sane whatever size was configured.
|
||||
PIXELS=$((WIDTH * HEIGHT))
|
||||
|
||||
if [ "$FORMAT" = "mjpeg" ]; then
|
||||
mkdir -p "$F/streaming/mjpeg/m"
|
||||
add_frame "$F/streaming/mjpeg/m" $((PIXELS * 13)) $((PIXELS * 33))
|
||||
# dwMaxVideoFrameBufferSize is an upper bound the host sizes its buffer
|
||||
# from; w*h*2 is a generous ceiling for JPEG. Undersizing truncates frames.
|
||||
echo $((PIXELS * 2)) > "$F/streaming/mjpeg/m/f/dwMaxVideoFrameBufferSize"
|
||||
STREAM_FORMAT="$F/streaming/mjpeg/m"
|
||||
else
|
||||
mkdir -p "$F/streaming/framebased/h"
|
||||
add_frame "$F/streaming/framebased/h" $((PIXELS * 2)) $((PIXELS * 9))
|
||||
# dwBytesPerLine is meaningless for a compressed format; 0 says "variable".
|
||||
echo 0 > "$F/streaming/framebased/h/f/dwBytesPerLine"
|
||||
STREAM_FORMAT="$F/streaming/framebased/h"
|
||||
fi
|
||||
|
||||
# --- headers and class links -------------------------------------------------
|
||||
# Formats link INTO the streaming header; the header links INTO each speed
|
||||
# class.
|
||||
#
|
||||
# Targets must be ABSOLUTE. configfs_symlink() resolves the target with
|
||||
# kern_path(), which resolves a relative path against the calling process's
|
||||
# CWD -- not against the directory the link lives in. The upstream
|
||||
# gadget-testing.txt recipe gets away with relative targets only because it
|
||||
# cd's into each directory first. Absolute paths do not care where we are.
|
||||
mkdir -p "$F/streaming/header/h"
|
||||
ln -s "$STREAM_FORMAT" "$F/streaming/header/h/f"
|
||||
|
||||
ln -s "$F/streaming/header/h" "$F/streaming/class/fs/h"
|
||||
ln -s "$F/streaming/header/h" "$F/streaming/class/hs/h"
|
||||
ln -s "$F/streaming/header/h" "$F/streaming/class/ss/h"
|
||||
|
||||
mkdir -p "$F/control/header/h"
|
||||
ln -s "$F/control/header/h" "$F/control/class/fs/h"
|
||||
ln -s "$F/control/header/h" "$F/control/class/ss/h"
|
||||
|
||||
ln -s "$F" "$G/configs/c.1/uvc.0"
|
||||
|
||||
echo "uvc-gadget-setup: composed $G ($FORMAT ${WIDTH}x${HEIGHT}@${FPS})"
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
From: OpenIPC
|
||||
Subject: [PATCH] gk7205v200: run the dwc3 controller in dual-role (otg) mode
|
||||
|
||||
The gk7205v200 has a single DWC3 USB controller wired as a host port. This
|
||||
image needs it to work both ways: as a host (the usbcam second-camera path,
|
||||
consuming a USB webcam) and as a device (the UVC gadget path, presenting the
|
||||
camera to a PC as a webcam). They are never active at the same time -- xHCI
|
||||
HCSPARAMS1 reports MaxPorts = 1 -- but switching should not require a reboot.
|
||||
|
||||
With dr_mode = "host", dwc3_core_init_mode() only calls dwc3_host_init(), so
|
||||
no UDC is ever registered and the gadget side is unreachable. With
|
||||
dr_mode = "otg" it calls both dwc3_host_init() and dwc3_gadget_init(), so an
|
||||
xHCI controller and a UDC coexist and userspace can pick a role at runtime via
|
||||
/sys/kernel/debug/<dwc3>/mode.
|
||||
|
||||
The controller already carries Goke's peripheral-side endpoint remap in this
|
||||
same node (eps_directions, eps_map, snps,eps_new_init), which the vendor would
|
||||
only have added if device mode was exercised; and CONFIG_USB_DWC3_DUAL_ROLE is
|
||||
already enabled, so dwc3's gadget.o/ep0.o are already linked in.
|
||||
|
||||
IMPORTANT: dr_mode = "otg" also makes dwc3_core_init() leave
|
||||
GCTL.PRTCAPDIR = OTG(3), and this 4.9 dwc3 has no working OTG role-detection
|
||||
block. A concrete role must be forced early in userspace -- see
|
||||
/etc/init.d/S72usbmode, which writes "host" at boot. Without it the controller
|
||||
sits in a role it cannot drive and USB does not work in either direction.
|
||||
|
||||
This patch ships only in the gk7205v200_otg_generic device profile, so other
|
||||
gk7205v200 variants keep dr_mode = "host".
|
||||
|
||||
--- a/arch/arm/boot/dts/gk7205v200.dtsi
|
||||
+++ b/arch/arm/boot/dts/gk7205v200.dtsi
|
||||
@@ -395,7 +395,7 @@
|
||||
phys = <&usb2_phy0>;
|
||||
phy-names = "usb2-phy";
|
||||
maximum-speed = "high-speed";
|
||||
- dr_mode = "host";
|
||||
+ dr_mode = "otg";
|
||||
eps_directions = <0x6a>;
|
||||
snps,eps_new_init;
|
||||
eps_map=<0x0 0x1 0x2 0x3 0x4 0x5 0x7>;
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
From: OpenIPC
|
||||
Subject: [PATCH] usb: dwc3: make device mode work on a host-first dual-role part
|
||||
|
||||
On gk7205v200 the DTS boots in host mode and the role is switched at
|
||||
runtime, so the gadget always starts from a controller that xHCI has
|
||||
already owned and reset. Three things were missing for that to work; all
|
||||
three are needed, and all three are things upstream's later
|
||||
__dwc3_set_mode() rework does for the DWC3_GCTL_PRTCAP_DEVICE case.
|
||||
|
||||
1. The event buffer was never re-armed. xHCI's controller reset clears
|
||||
GEVNTADRLO/HI and nothing programmed them again, so the core DMA'd its
|
||||
event descriptors to physical address 0 -- not RAM on this SoC, DRAM
|
||||
starts at 0x40000000. No event ever reached the driver: the link
|
||||
chirped and counted SOFs in hardware and the host saw a device, but the
|
||||
gadget never observed the USB reset.
|
||||
|
||||
2. The device side was never reset before being started, so the USB2 PHY
|
||||
was left in whatever state xHCI put it in. The high-speed chirp then
|
||||
failed, DSTS.CONNECTSPD came back full-speed, CONNECT_DONE was never
|
||||
raised and the host timed out with -71.
|
||||
|
||||
3. Re-attach never worked, for two separate reasons -- see the comments in
|
||||
dwc3_gadget_pullup().
|
||||
|
||||
With all three, enumeration is reliable across repeated attach/detach
|
||||
cycles.
|
||||
|
||||
--- a/drivers/usb/dwc3/core.c
|
||||
+++ b/drivers/usb/dwc3/core.c
|
||||
@@ -144,7 +144,7 @@
|
||||
* dwc3_core_soft_reset - Issues core soft reset and PHY reset
|
||||
* @dwc: pointer to our context structure
|
||||
*/
|
||||
-static int dwc3_core_soft_reset(struct dwc3 *dwc)
|
||||
+int dwc3_core_soft_reset(struct dwc3 *dwc)
|
||||
{
|
||||
u32 reg;
|
||||
int retries = 1000;
|
||||
@@ -316,7 +316,7 @@
|
||||
*
|
||||
* Returns 0 on success otherwise negative errno.
|
||||
*/
|
||||
-static int dwc3_event_buffers_setup(struct dwc3 *dwc)
|
||||
+int dwc3_event_buffers_setup(struct dwc3 *dwc)
|
||||
{
|
||||
struct dwc3_event_buffer *evt;
|
||||
|
||||
--- a/drivers/usb/dwc3/core.h
|
||||
+++ b/drivers/usb/dwc3/core.h
|
||||
@@ -1155,6 +1155,8 @@
|
||||
|
||||
/* prototypes */
|
||||
void dwc3_set_mode(struct dwc3 *dwc, u32 mode);
|
||||
+int dwc3_event_buffers_setup(struct dwc3 *dwc);
|
||||
+int dwc3_core_soft_reset(struct dwc3 *dwc);
|
||||
u32 dwc3_core_fifo_space(struct dwc3_ep *dep, u8 type);
|
||||
|
||||
/* check whether we are on the DWC_usb31 core */
|
||||
--- a/drivers/usb/dwc3/gadget.c
|
||||
+++ b/drivers/usb/dwc3/gadget.c
|
||||
@@ -1563,6 +1563,9 @@
|
||||
return 0;
|
||||
}
|
||||
|
||||
+static int __dwc3_gadget_start(struct dwc3 *dwc);
|
||||
+static void __dwc3_gadget_stop(struct dwc3 *dwc);
|
||||
+
|
||||
static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on)
|
||||
{
|
||||
struct dwc3 *dwc = gadget_to_dwc(g);
|
||||
@@ -1571,8 +1574,80 @@
|
||||
|
||||
is_on = !!is_on;
|
||||
|
||||
+ if (!is_on) {
|
||||
+ spin_lock_irqsave(&dwc->lock, flags);
|
||||
+ ret = dwc3_gadget_run_stop(dwc, false, false);
|
||||
+ spin_unlock_irqrestore(&dwc->lock, flags);
|
||||
+ /*
|
||||
+ * Never report a failure on the way down.
|
||||
+ *
|
||||
+ * dwc3_gadget_run_stop(0) polls DSTS.DEVCTRLHLT and returns
|
||||
+ * -ETIMEDOUT if the core does not halt in time, which this one
|
||||
+ * regularly does not. That error is not harmless: it propagates
|
||||
+ * out through usb_gadget_disconnect() into
|
||||
+ * usb_gadget_deactivate(), which then skips "gadget->deactivated
|
||||
+ * = true", and into usb_function_deactivate(), which then skips
|
||||
+ * "cdev->deactivations++". The refcount is left permanently
|
||||
+ * unbalanced, so every later usb_function_activate() trips
|
||||
+ * WARN_ON(cdev->deactivations == 0) and never calls
|
||||
+ * usb_gadget_connect() -- the gadget can be disconnected once
|
||||
+ * and then never comes back.
|
||||
+ *
|
||||
+ * RUN_STOP has been cleared regardless, so the pullup really is
|
||||
+ * down; only the halt handshake was slow. Whatever state that
|
||||
+ * leaves the core in does not matter, because the is_on path
|
||||
+ * below soft-resets and re-initialises it from scratch.
|
||||
+ */
|
||||
+ if (ret) {
|
||||
+ dev_dbg(dwc->dev,
|
||||
+ "pullup(0): run_stop returned %d, ignoring so the deactivation refcount stays balanced\n",
|
||||
+ ret);
|
||||
+ ret = 0;
|
||||
+ }
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ /*
|
||||
+ * Bringing the pullup back up after a disconnect needs a full device-side
|
||||
+ * re-init, not just RUN_STOP.
|
||||
+ *
|
||||
+ * A gadget that has been disconnected once (f_uvc does this every time
|
||||
+ * userspace closes /dev/videoN, via usb_function_deactivate()) leaves the
|
||||
+ * core half torn down: the USB2 PHY keeps whatever state the disconnect
|
||||
+ * left it in and the event buffer registers are stale. Re-asserting
|
||||
+ * RUN_STOP on its own gets the host as far as SET_ADDRESS -- the device
|
||||
+ * reaches "addressed" -- and then control transfers fail, so enumeration
|
||||
+ * never completes. Only the first connect after dwc3_gadget_start() ever
|
||||
+ * worked, because that path does the reset for us.
|
||||
+ *
|
||||
+ * So do here what dwc3_gadget_start() does: quiesce, soft reset (which
|
||||
+ * re-runs usb_phy_init()/phy_init()), then start the device side again.
|
||||
+ * dwc3_event_buffers_setup() is called from __dwc3_gadget_start().
|
||||
+ *
|
||||
+ * Callable only from process context: dwc3_core_soft_reset() sleeps, so it
|
||||
+ * must run outside dwc->lock. That holds for every caller we have --
|
||||
+ * usb_gadget_connect() reaches us from usb_gadget_activate() (userspace
|
||||
+ * opening the video node) and from udc_bind_to_driver(). A platform that
|
||||
+ * called usb_udc_vbus_handler() from hard IRQ would need reworking here.
|
||||
+ */
|
||||
spin_lock_irqsave(&dwc->lock, flags);
|
||||
- ret = dwc3_gadget_run_stop(dwc, is_on, false);
|
||||
+ __dwc3_gadget_stop(dwc);
|
||||
+ spin_unlock_irqrestore(&dwc->lock, flags);
|
||||
+
|
||||
+ ret = dwc3_core_soft_reset(dwc);
|
||||
+ if (ret) {
|
||||
+ dev_err(dwc->dev, "pullup: soft reset failed: %d\n", ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ spin_lock_irqsave(&dwc->lock, flags);
|
||||
+ ret = __dwc3_gadget_start(dwc);
|
||||
+ if (ret) {
|
||||
+ spin_unlock_irqrestore(&dwc->lock, flags);
|
||||
+ dev_err(dwc->dev, "pullup: gadget start failed: %d\n", ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+ ret = dwc3_gadget_run_stop(dwc, true, false);
|
||||
spin_unlock_irqrestore(&dwc->lock, flags);
|
||||
|
||||
return ret;
|
||||
@@ -1652,6 +1727,27 @@
|
||||
int ret = 0;
|
||||
u32 reg;
|
||||
|
||||
+ /*
|
||||
+ * Re-program the event buffer address before starting the device side.
|
||||
+ *
|
||||
+ * dwc3_core_init() sets GEVNTADRLO/HI up once at probe, but on a
|
||||
+ * dual-role part the host is brought up straight afterwards and xHCI's
|
||||
+ * controller reset clears them again. Nothing else re-arms them, so on
|
||||
+ * any host->device transition (and on this SoC, that includes the very
|
||||
+ * first one, because the DTS boots in host mode) the core is left with
|
||||
+ * an event buffer address of 0. It then DMAs its event descriptors to
|
||||
+ * physical address 0 -- which is not RAM here, DRAM starts at
|
||||
+ * 0x40000000 -- so no event ever reaches the driver: the link chirps and
|
||||
+ * counts SOFs in hardware, the host sees a high-speed device, but the
|
||||
+ * gadget never observes the USB reset and enumeration dies with -71.
|
||||
+ *
|
||||
+ * Upstream fixed this by calling dwc3_event_buffers_setup() from
|
||||
+ * __dwc3_set_mode() when switching to DWC3_GCTL_PRTCAP_DEVICE; this
|
||||
+ * tree predates that rework, so re-arm here instead. This covers both
|
||||
+ * the gadget-bind and the resume path, and is idempotent.
|
||||
+ */
|
||||
+ dwc3_event_buffers_setup(dwc);
|
||||
+
|
||||
reg = dwc3_readl(dwc->regs, DWC3_DCFG);
|
||||
reg &= ~(DWC3_DCFG_SPEED_MASK);
|
||||
|
||||
@@ -1751,6 +1847,29 @@
|
||||
int ret = 0;
|
||||
int irq;
|
||||
|
||||
+ /*
|
||||
+ * Re-initialise the controller and its PHYs before handing the port to
|
||||
+ * the device side.
|
||||
+ *
|
||||
+ * On a dual-role part the host is started first, so by the time a gadget
|
||||
+ * driver binds, the USB2 PHY is still in the state xHCI left it in and
|
||||
+ * the device side has never been reset. Symptom: the link comes up but
|
||||
+ * loses the high-speed chirp handshake, so DSTS.CONNECTSPD reads
|
||||
+ * full-speed, DWC3_DEVICE_EVENT_CONNECT_DONE is never raised, and the
|
||||
+ * host -- which is driving high-speed -- times out with -71.
|
||||
+ *
|
||||
+ * dwc3_core_soft_reset() re-runs usb_phy_init()/phy_init() and resets
|
||||
+ * the device side, which is exactly what upstream's __dwc3_set_mode()
|
||||
+ * does for the DWC3_GCTL_PRTCAP_DEVICE case. This tree predates that
|
||||
+ * rework. Must be called outside dwc->lock: it sleeps (udelay loops and
|
||||
+ * PHY init).
|
||||
+ */
|
||||
+ ret = dwc3_core_soft_reset(dwc);
|
||||
+ if (ret) {
|
||||
+ dev_err(dwc->dev, "soft reset before gadget start failed: %d\n", ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
irq = dwc->irq_gadget;
|
||||
ret = request_threaded_irq(irq, dwc3_interrupt, dwc3_thread_interrupt,
|
||||
IRQF_SHARED, "dwc3", dwc->ev_buf);
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
uvc gadget: drop the vendor scatter-gather request path
|
||||
|
||||
Goke's f_uvc carries a private UVC_SG_REQ mode that replaces the mainline
|
||||
request ring with a *single* in-flight usb_request (UVC_NUM_REQUESTS 1)
|
||||
whose scatterlist is sized to hold an entire video frame:
|
||||
|
||||
num_sgs = (video->imagesize / (req_size - 2)) + 1;
|
||||
|
||||
uvc_video_encode_isoc() then fills every sg entry of that one request and
|
||||
hands the whole chain to the UDC in one go.
|
||||
|
||||
dwc3 turns each mapped sg entry into a TRB chained to the next, so the
|
||||
chain describes one isochronous service interval. On a full-speed link an
|
||||
isoc interval carries exactly one 1023-byte packet, so only the first sg
|
||||
entry ever reaches the wire; the rest of the frame is dropped. Because the
|
||||
encoder had already consumed the buffer into the sg list, the buffer is
|
||||
marked done and the FID toggles, so the host sees a *complete* frame that
|
||||
happens to be 1021 bytes long (1023 minus the 2-byte UVC payload header).
|
||||
|
||||
The visible symptom is that every frame larger than one packet arrives
|
||||
truncated. H.264 decoders report a stream of
|
||||
|
||||
error while decoding MB x y, bytestream -5
|
||||
|
||||
and roughly half the frames fail to decode, while frames that happen to
|
||||
fit in a single packet come through intact.
|
||||
|
||||
Fall back to the mainline path: 32 plain one-packet requests kept in
|
||||
flight by uvcg_video_pump(). Measured on gk7205v200 at full speed, this
|
||||
takes a 1280x720 H.264 stream from ~50% corrupt frames to 120/120 frames
|
||||
delivered with zero decoder errors and no gaps in the buffer sequence.
|
||||
|
||||
The SG code is left in place behind the now-undefined macro rather than
|
||||
deleted, so it stays available for anyone who wants to revisit it on a
|
||||
high-speed link where an interval can hold mult*maxpacket bytes.
|
||||
|
||||
diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
|
||||
--- a/drivers/usb/gadget/function/uvc.h
|
||||
+++ b/drivers/usb/gadget/function/uvc.h
|
||||
@@ -17,7 +17,12 @@
|
||||
#include <linux/types.h>
|
||||
#include <linux/usb/ch9.h>
|
||||
|
||||
-#define UVC_SG_REQ
|
||||
+/* UVC_SG_REQ: the vendor scatter-gather path uses a single in-flight request
|
||||
+ * carrying a whole frame as a chained TRB list. On this full-speed link the
|
||||
+ * host only ever receives the first 1023-byte packet of each frame, so every
|
||||
+ * frame larger than one packet arrives truncated (H.264 "bytestream -5").
|
||||
+ * Fall back to the mainline path: 32 plain one-packet requests. */
|
||||
+#undef UVC_SG_REQ
|
||||
#define UVC_EVENT_FIRST (V4L2_EVENT_PRIVATE_START + 0)
|
||||
#define UVC_EVENT_CONNECT (V4L2_EVENT_PRIVATE_START + 0)
|
||||
#define UVC_EVENT_DISCONNECT (V4L2_EVENT_PRIVATE_START + 1)
|
||||
Reference in New Issue
Block a user