diff --git a/boards/arduino/nicla_vision/arduino_nicla_vision_stm32h747xx_m7.yaml b/boards/arduino/nicla_vision/arduino_nicla_vision_stm32h747xx_m7.yaml index 0310b5dce890..90e7c42ca08b 100644 --- a/boards/arduino/nicla_vision/arduino_nicla_vision_stm32h747xx_m7.yaml +++ b/boards/arduino/nicla_vision/arduino_nicla_vision_stm32h747xx_m7.yaml @@ -11,4 +11,5 @@ supported: - gpio - spi - i2c + - usbd vendor: arduino diff --git a/doc/connectivity/usb/device/usb_device.rst b/doc/connectivity/usb/device/usb_device.rst index 37da1f464f61..6b982036beed 100644 --- a/doc/connectivity/usb/device/usb_device.rst +++ b/doc/connectivity/usb/device/usb_device.rst @@ -551,6 +551,8 @@ The following Product IDs are currently used: +----------------------------------------------------+--------+ | :zephyr:code-sample:`uac2-implicit-feedback` | 0x000F | +----------------------------------------------------+--------+ +| :zephyr:code-sample:`uvc` | 0x0011 | ++----------------------------------------------------+--------+ | :zephyr:code-sample:`usb-dfu` (DFU Mode) | 0xFFFF | +----------------------------------------------------+--------+ diff --git a/doc/connectivity/usb/device_next/usb_device.rst b/doc/connectivity/usb/device_next/usb_device.rst index 7e7762997a01..7ab2805dc4fa 100644 --- a/doc/connectivity/usb/device_next/usb_device.rst +++ b/doc/connectivity/usb/device_next/usb_device.rst @@ -32,6 +32,8 @@ Samples * :zephyr:code-sample:`uac2-implicit-feedback` +* :zephyr:code-sample:`uvc` + Samples ported to new USB device support ---------------------------------------- @@ -223,6 +225,8 @@ instance (``n``) and is used as an argument to the :c:func:`usbd_register_class` +-----------------------------------+-------------------------+-------------------------+ | Bluetooth HCI USB transport layer | :ref:`bt_hci_raw` | :samp:`bt_hci_{n}` | +-----------------------------------+-------------------------+-------------------------+ +| USB Video Class (UVC) | Video device | :samp:`uvc_{n}` | ++-----------------------------------+-------------------------+-------------------------+ CDC ACM UART ============ diff --git a/dts/bindings/usb/zephyr,uvc-device.yaml b/dts/bindings/usb/zephyr,uvc-device.yaml new file mode 100644 index 000000000000..b3233bdb618f --- /dev/null +++ b/dts/bindings/usb/zephyr,uvc-device.yaml @@ -0,0 +1,12 @@ +# Copyright (c) 2025 tinyVision.ai Inc. +# SPDX-License-Identifier: Apache-2.0 + +description: | + USB Video Class (UVC) device instance. + + Each UVC instance added to the USB Device Controller (UDC) node will be visible + as a new camera from the host point of view. + +compatible: "zephyr,uvc-device" + +include: base.yaml diff --git a/include/zephyr/usb/class/usbd_uvc.h b/include/zephyr/usb/class/usbd_uvc.h new file mode 100644 index 000000000000..bd5562389206 --- /dev/null +++ b/include/zephyr/usb/class/usbd_uvc.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief USB Video Class (UVC) public header + */ + +#ifndef ZEPHYR_INCLUDE_USB_CLASS_USBD_UVC_H +#define ZEPHYR_INCLUDE_USB_CLASS_USBD_UVC_H + +#include + +/** + * @brief USB Video Class (UVC) device API + * @defgroup usbd_uvc USB Video Class (UVC) device API + * @ingroup usb + * @since 4.2 + * @version 0.1.0 + * @see uvc: "Universal Serial Bus Device Class Definition for Video Devices" + * Document Release 1.5 (August 9, 2012) + * @{ + */ + +/** + * @brief Set the video device that a UVC instance will use. + * + * It will query its supported controls, formats and frame rates, and use this information to + * generate USB descriptors sent to the host. + * + * At runtime, it will forward all USB controls from the host to this device. + * + * @note This function must be called before @ref usbd_enable. + * + * @param uvc_dev The UVC device + * @param video_dev The video device that this UVC instance controls + */ +void uvc_set_video_dev(const struct device *uvc_dev, const struct device *video_dev); + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_USB_CLASS_USBD_UVC_H */ diff --git a/samples/subsys/usb/common/sample_usbd_init.c b/samples/subsys/usb/common/sample_usbd_init.c index eecd38839976..97f4fa9adb13 100644 --- a/samples/subsys/usb/common/sample_usbd_init.c +++ b/samples/subsys/usb/common/sample_usbd_init.c @@ -83,7 +83,8 @@ static void sample_fix_code_triple(struct usbd_context *uds_ctx, IS_ENABLED(CONFIG_USBD_CDC_ECM_CLASS) || IS_ENABLED(CONFIG_USBD_CDC_NCM_CLASS) || IS_ENABLED(CONFIG_USBD_MIDI2_CLASS) || - IS_ENABLED(CONFIG_USBD_AUDIO2_CLASS)) { + IS_ENABLED(CONFIG_USBD_AUDIO2_CLASS) || + IS_ENABLED(CONFIG_USBD_VIDEO_CLASS)) { /* * Class with multiple interfaces have an Interface * Association Descriptor available, use an appropriate triple diff --git a/samples/subsys/usb/uvc/CMakeLists.txt b/samples/subsys/usb/uvc/CMakeLists.txt new file mode 100644 index 000000000000..62b0a45e66a4 --- /dev/null +++ b/samples/subsys/usb/uvc/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(usb_video) + +include(${ZEPHYR_BASE}/samples/subsys/usb/common/common.cmake) +target_sources(app PRIVATE src/main.c) diff --git a/samples/subsys/usb/uvc/Kconfig b/samples/subsys/usb/uvc/Kconfig new file mode 100644 index 000000000000..d1b0c2bec39c --- /dev/null +++ b/samples/subsys/usb/uvc/Kconfig @@ -0,0 +1,9 @@ +# Copyright The Zephyr Project Contributors +# SPDX-License-Identifier: Apache-2.0 + +# Source common USB sample options used to initialize new experimental USB +# device stack. The scope of these options is limited to USB samples in project +# tree, you cannot use them in your own application. +source "samples/subsys/usb/common/Kconfig.sample_usbd" + +source "Kconfig.zephyr" diff --git a/samples/subsys/usb/uvc/README.rst b/samples/subsys/usb/uvc/README.rst new file mode 100644 index 000000000000..6c38f68a64ce --- /dev/null +++ b/samples/subsys/usb/uvc/README.rst @@ -0,0 +1,183 @@ +.. zephyr:code-sample:: uvc + :name: USB Video webcam + :relevant-api: usbd_api usbd_uvc video_interface + + Send video frames over USB. + +Overview +******** + +This sample demonstrates how to use a USB Video Class instance to send video data over USB. + +Upon connection, a video device will show-up on the host, usable like a regular webcam device. + +Any software on the host can then access the video stream as a local video source. + +Requirements +************ + +This sample uses the new USB device stack and requires the USB device +controller ported to the :ref:`udc_api`. + +Building and Running +******************** + +If a board does not have a camera supported, the :ref:`snippet-video-sw-generator` snippet can be +used to test without extra hardware than the USB interface, via a software-generated test pattern: + +.. zephyr-app-commands:: + :zephyr-app: samples/subsys/usb/uvc + :board: frdm_mcxn947/mcxn947/cpu0 + :snippets: video-sw-generator + :goals: build flash + :compact: + +If a board is equipped with a supported image sensor configured as the ``zephyr,camera`` chosen +node, then it will be used as the video source. The sample can then be built as follows: + +.. zephyr-app-commands:: + :zephyr-app: samples/subsys/usb/uvc + :board: arduino_nicla_vision/stm32h747xx/m7 + :goals: build flash + :compact: + +The device is expected to be detected as a webcam device: + +.. tabs:: + + .. group-tab:: Ubuntu + + The ``dmesg`` logs are expected to mention a ``generic UVC device``. + + The ``lsusb`` is expected to show an entry for a Zephyr device. + + Refers to `Ideas on board FAQ `_ + for how to get more debug information. + + .. group-tab:: MacOS + + The ``dmesg`` logs are expected to mention a video device. + + The ``ioreg -p IOUSB`` command list the USB devices including cameras. + + The ``system_profiler SPCameraDataType`` command list video input devices. + + .. group-tab:: Windows + + The Device Manager or USBView utilities permit to list the USB devices. + + The 3rd-party USB Tree View allows to review and debug the descriptors. + + In addition, the `USB3CV `_ tool + from USB-IF can check that the device is compliant with the UVC standard. + +Playing the Stream +================== + +The device is recognized by the system as a native webcam and can be used by any video application. + +For instance with VLC: +:menuselection:`Media --> Open Capture Device --> Capture Device --> Video device name`. + +Or with Gstreamer and FFmpeg: + +.. tabs:: + + .. group-tab:: Ubuntu + + Assuming ``/dev/video0`` is your Zephyr device. + + .. code-block:: console + + ffplay -i /dev/video0 + + .. code-block:: console + + gst-launch-1.0 v4l2src device=/dev/video0 ! videoconvert ! autovideosink + + .. group-tab:: MacOS + + Assuming ``0:0`` is your Zephyr device. + + .. code-block:: console + + ffplay -f avfoundation -i 0:0 + + .. code-block:: console + + gst-launch-1.0 avfvideosrc device-index=0 ! autovideosink + + .. group-tab:: Windows + + Assuming ``UVC sample`` is your Zephyr device. + + .. code-block:: console + + ffplay.exe -f dshow -i video="UVC sample" + + .. code-block:: console + + gst-launch-1.0.exe ksvideosrc device-name="UVC sample" ! videoconvert ! autovideosink + +The video device can also be used by web and video call applications systems. + +Android and iPad (but not yet iOS) are also expected to work via dedicated applications. + +Accessing the Video Controls +============================ + +On the host system, the controls would be available as video source +control through various applications, like any webcam. + +.. tabs:: + + .. group-tab:: Ubuntu + + Assuming ``/dev/video0`` is your Zephyr device. + + .. code-block:: console + + $ v4l2-ctl --device /dev/video0 --list-ctrls + + Camera Controls + + auto_exposure 0x009a0901 (menu) : min=0 max=3 default=1 value=1 (Manual Mode) + exposure_dynamic_framerate 0x009a0903 (bool) : default=0 value=0 + exposure_time_absolute 0x009a0902 (int) : min=10 max=2047 step=1 default=384 value=384 flags=inactive + + $ v4l2-ctl --device /dev/video0 --set-ctrl auto_exposure=1 + $ v4l2-ctl --device /dev/video0 --set-ctrl exposure_time_absolute=1500 + + .. group-tab:: MacOS + + The `VLC `_ client and the system Webcam Settings panel + allows adjustment of the supported video controls. + + .. group-tab:: Windows + + The `VLC `_ client and `Pot Player `_ + client permit to further access the video controls. + +Software Processing +=================== + +Software processing tools can also use the video interface directly. + +Here is an example with OpenCV (``pip install opencv-python``): + +.. code-block:: python + + import cv2 + + # Number of the /dev/video# interface + devnum = 2 + + cv2.namedWindow("preview") + vc = cv2.VideoCapture(devnum) + + while (val := vc.read())[0]: + cv2.waitKey(20) + cv2.imshow("preview", val[1]) + + cv2.destroyWindow("preview") + vc.release() diff --git a/samples/subsys/usb/uvc/app.overlay b/samples/subsys/usb/uvc/app.overlay new file mode 100644 index 000000000000..8f7cc121413c --- /dev/null +++ b/samples/subsys/usb/uvc/app.overlay @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + uvc: uvc { + compatible = "zephyr,uvc-device"; + }; +}; diff --git a/samples/subsys/usb/uvc/boards/arduino_nicla_vision_stm32h747xx_m7.conf b/samples/subsys/usb/uvc/boards/arduino_nicla_vision_stm32h747xx_m7.conf new file mode 100644 index 000000000000..7fcfeea35e66 --- /dev/null +++ b/samples/subsys/usb/uvc/boards/arduino_nicla_vision_stm32h747xx_m7.conf @@ -0,0 +1,2 @@ +# Enough two 320x240 YUYV frames +CONFIG_VIDEO_BUFFER_POOL_SZ_MAX=163840 diff --git a/samples/subsys/usb/uvc/boards/frdm_mcxn947_mcxn947_cpu0.conf b/samples/subsys/usb/uvc/boards/frdm_mcxn947_mcxn947_cpu0.conf new file mode 100644 index 000000000000..2302ba1e88b9 --- /dev/null +++ b/samples/subsys/usb/uvc/boards/frdm_mcxn947_mcxn947_cpu0.conf @@ -0,0 +1,2 @@ +CONFIG_VIDEO_BUFFER_POOL_SZ_MAX=40000 +CONFIG_VIDEO_BUFFER_POOL_NUM_MAX=2 diff --git a/samples/subsys/usb/uvc/prj.conf b/samples/subsys/usb/uvc/prj.conf new file mode 100644 index 000000000000..e6da900d7d1c --- /dev/null +++ b/samples/subsys/usb/uvc/prj.conf @@ -0,0 +1,14 @@ +CONFIG_LOG=y +CONFIG_POLL=y +CONFIG_SAMPLE_USBD_PID=0x0011 +CONFIG_SAMPLE_USBD_PRODUCT="UVC sample" +CONFIG_UDC_BUF_POOL_SIZE=2048 +CONFIG_UDC_DRIVER_LOG_LEVEL_WRN=y +CONFIG_USBD_LOG_LEVEL_WRN=y +CONFIG_USBD_VIDEO_CLASS=y +CONFIG_USBD_VIDEO_LOG_LEVEL_WRN=y +CONFIG_USB_DEVICE_STACK_NEXT=y +CONFIG_VIDEO=y +CONFIG_VIDEO_BUFFER_POOL_NUM_MAX=2 +CONFIG_VIDEO_BUFFER_POOL_SZ_MAX=24576 +CONFIG_VIDEO_LOG_LEVEL_WRN=y diff --git a/samples/subsys/usb/uvc/sample.yaml b/samples/subsys/usb/uvc/sample.yaml new file mode 100644 index 000000000000..6495e7a4bd8a --- /dev/null +++ b/samples/subsys/usb/uvc/sample.yaml @@ -0,0 +1,29 @@ +sample: + name: USB Video sample +common: + harness: console + harness_config: + type: one_line + regex: + - "Waiting the host to select the video format" +tests: + sample.subsys.usb.uvc: + depends_on: + - usbd + tags: usb video + extra_args: SNIPPET=video-sw-generator + integration_platforms: + - nrf52840dk/nrf52840 + - nrf54h20dk/nrf54h20/cpuapp + - frdm_k64f + - stm32f723e_disco + - nucleo_f413zh + - mimxrt685_evk/mimxrt685s/cm33 + - mimxrt1060_evk/mimxrt1062/qspi + sample.subsys.usb.uvc.camera: + depends_on: + - usbd + tags: usb video + filter: dt_chosen_enabled("zephyr,camera") + integration_platforms: + - arduino_nicla_vision/stm32h747xx/m7 diff --git a/samples/subsys/usb/uvc/src/main.c b/samples/subsys/usb/uvc/src/main.c new file mode 100644 index 000000000000..e2520556ba10 --- /dev/null +++ b/samples/subsys/usb/uvc/src/main.c @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include + +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(uvc_sample, LOG_LEVEL_INF); + +const struct device *const uvc_dev = DEVICE_DT_GET(DT_NODELABEL(uvc)); +const struct device *const video_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_camera)); + +int main(void) +{ + struct usbd_context *sample_usbd; + struct video_buffer *vbuf; + struct video_format fmt = {0}; + struct video_caps caps; + struct k_poll_signal sig; + struct k_poll_event evt[1]; + k_timeout_t timeout = K_FOREVER; + size_t bsize; + int ret; + + if (!device_is_ready(video_dev)) { + LOG_ERR("video source %s failed to initialize", video_dev->name); + return -ENODEV; + } + + caps.type = VIDEO_BUF_TYPE_OUTPUT; + + if (video_get_caps(video_dev, &caps)) { + LOG_ERR("Unable to retrieve video capabilities"); + return 0; + } + + /* Must be done before initializing USB */ + uvc_set_video_dev(uvc_dev, video_dev); + + sample_usbd = sample_usbd_init_device(NULL); + if (sample_usbd == NULL) { + return -ENODEV; + } + + ret = usbd_enable(sample_usbd); + if (ret != 0) { + return ret; + } + + LOG_INF("Waiting the host to select the video format"); + + /* Get the video format once it is selected by the host */ + while (true) { + fmt.type = VIDEO_BUF_TYPE_INPUT; + + ret = video_get_format(uvc_dev, &fmt); + if (ret == 0) { + break; + } + if (ret != -EAGAIN) { + LOG_ERR("Failed to get the video format"); + return ret; + } + + k_sleep(K_MSEC(10)); + } + + LOG_INF("The host selected format '%s' %ux%u, preparing %u buffers of %u bytes", + VIDEO_FOURCC_TO_STR(fmt.pixelformat), fmt.width, fmt.height, + CONFIG_VIDEO_BUFFER_POOL_NUM_MAX, fmt.pitch * fmt.height); + + /* Size to allocate for each buffer */ + if (caps.min_line_count == LINE_COUNT_HEIGHT) { + bsize = fmt.pitch * fmt.height; + } else { + bsize = fmt.pitch * caps.min_line_count; + } + + for (int i = 0; i < CONFIG_VIDEO_BUFFER_POOL_NUM_MAX; i++) { + vbuf = video_buffer_alloc(bsize, K_NO_WAIT); + if (vbuf == NULL) { + LOG_ERR("Could not allocate the video buffer"); + return -ENOMEM; + } + + vbuf->type = VIDEO_BUF_TYPE_OUTPUT; + + ret = video_enqueue(video_dev, vbuf); + if (ret != 0) { + LOG_ERR("Could not enqueue video buffer"); + return ret; + } + } + + LOG_DBG("Preparing signaling for %s input/output", video_dev->name); + + k_poll_signal_init(&sig); + k_poll_event_init(&evt[0], K_POLL_TYPE_SIGNAL, K_POLL_MODE_NOTIFY_ONLY, &sig); + + ret = video_set_signal(video_dev, &sig); + if (ret != 0) { + LOG_WRN("Failed to setup the signal on %s output endpoint", video_dev->name); + timeout = K_MSEC(1); + } + + ret = video_set_signal(uvc_dev, &sig); + if (ret != 0) { + LOG_ERR("Failed to setup the signal on %s input endpoint", uvc_dev->name); + return ret; + } + + LOG_INF("Starting the video transfer"); + + ret = video_stream_start(video_dev, VIDEO_BUF_TYPE_OUTPUT); + if (ret != 0) { + LOG_ERR("Failed to start %s", video_dev->name); + return ret; + } + + while (true) { + ret = k_poll(evt, ARRAY_SIZE(evt), timeout); + if (ret != 0 && ret != -EAGAIN) { + LOG_ERR("Poll exited with status %d", ret); + return ret; + } + + vbuf = &(struct video_buffer){.type = VIDEO_BUF_TYPE_OUTPUT}; + + if (video_dequeue(video_dev, &vbuf, K_NO_WAIT) == 0) { + LOG_DBG("Dequeued %p from %s, enqueueing to %s", + (void *)vbuf, video_dev->name, uvc_dev->name); + + vbuf->type = VIDEO_BUF_TYPE_INPUT; + + ret = video_enqueue(uvc_dev, vbuf); + if (ret != 0) { + LOG_ERR("Could not enqueue video buffer to %s", uvc_dev->name); + return ret; + } + } + + vbuf = &(struct video_buffer){.type = VIDEO_BUF_TYPE_INPUT}; + + if (video_dequeue(uvc_dev, &vbuf, K_NO_WAIT) == 0) { + LOG_DBG("Dequeued %p from %s, enqueueing to %s", + (void *)vbuf, uvc_dev->name, video_dev->name); + + vbuf->type = VIDEO_BUF_TYPE_OUTPUT; + + ret = video_enqueue(video_dev, vbuf); + if (ret != 0) { + LOG_ERR("Could not enqueue video buffer to %s", video_dev->name); + return ret; + } + } + + k_poll_signal_reset(&sig); + } + + return 0; +} diff --git a/subsys/usb/device_next/CMakeLists.txt b/subsys/usb/device_next/CMakeLists.txt index e0120dc89a82..717cdb9da37b 100644 --- a/subsys/usb/device_next/CMakeLists.txt +++ b/subsys/usb/device_next/CMakeLists.txt @@ -78,6 +78,11 @@ zephyr_library_sources_ifdef( class/usbd_midi2.c ) +zephyr_library_sources_ifdef( + CONFIG_USBD_VIDEO_CLASS + class/usbd_uvc.c +) + zephyr_library_sources_ifdef( CONFIG_USBD_HID_SUPPORT class/usbd_hid.c diff --git a/subsys/usb/device_next/class/Kconfig b/subsys/usb/device_next/class/Kconfig index fc188653565b..d3d9a946488e 100644 --- a/subsys/usb/device_next/class/Kconfig +++ b/subsys/usb/device_next/class/Kconfig @@ -12,3 +12,4 @@ rsource "Kconfig.uac2" rsource "Kconfig.hid" rsource "Kconfig.midi2" rsource "Kconfig.dfu" +rsource "Kconfig.uvc" diff --git a/subsys/usb/device_next/class/Kconfig.uvc b/subsys/usb/device_next/class/Kconfig.uvc new file mode 100644 index 000000000000..bab393e5d0e3 --- /dev/null +++ b/subsys/usb/device_next/class/Kconfig.uvc @@ -0,0 +1,46 @@ +# Copyright (c) 2025 tinyVision.ai Inc. +# +# SPDX-License-Identifier: Apache-2.0 + +config USBD_VIDEO_CLASS + bool "USB Video Class implementation [EXPERIMENTAL]" + depends on DT_HAS_ZEPHYR_UVC_DEVICE_ENABLED + select EXPERIMENTAL + help + USB Device Video Class (UVC) implementation. + +if USBD_VIDEO_CLASS + +config USBD_VIDEO_MAX_FORMATS + int "Max number of format descriptors" + range 1 254 + default 32 + help + The table of format descriptors are generated at runtime. This options plans the + storage at build time to allow enough descriptors to be generated. The default value + aims a compromise between enough descriptors for most devices, but not too much memory + being used. + +config USBD_VIDEO_MAX_FRMIVAL + int "Max number of video output stream per USB Video interface" + range 1 255 + default 8 + help + Max number of Frame Interval listed on a frame descriptor. The + default value is selected arbitrarily to fit most situations without + requiring too much RAM. + +config USBD_VIDEO_NUM_BUFS + int "Max number of buffers the UVC class can allocate" + default 16 + help + Control the number of buffer UVC can allocate in parallel. + The default is a compromise to allow enough concurrent buffers but not too much + memory usage. + +module = USBD_VIDEO +module-str = usbd uvc +default-count = 1 +source "subsys/logging/Kconfig.template.log_config" + +endif # USBD_VIDEO_CLASS diff --git a/subsys/usb/device_next/class/usbd_uvc.c b/subsys/usb/device_next/class/usbd_uvc.c new file mode 100644 index 000000000000..556627aff854 --- /dev/null +++ b/subsys/usb/device_next/class/usbd_uvc.c @@ -0,0 +1,2356 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT zephyr_uvc_device + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "usbd_uvc.h" +#include "../../../drivers/video/video_ctrls.h" +#include "../../../drivers/video/video_device.h" + +LOG_MODULE_REGISTER(usbd_uvc, CONFIG_USBD_VIDEO_LOG_LEVEL); + +#define UVC_VBUF_DONE 1 +#define UVC_MAX_FS_DESC (CONFIG_USBD_VIDEO_MAX_FORMATS + 13) +#define UVC_MAX_HS_DESC (CONFIG_USBD_VIDEO_MAX_FORMATS + 13) +#define UVC_IDX_VC_UNIT 3 +#define UVC_MAX_HEADER_LENGTH 0xff + +enum uvc_op { + UVC_OP_GET_ERRNO, + UVC_OP_VC_CTRL, + UVC_OP_VS_PROBE, + UVC_OP_VS_COMMIT, + UVC_OP_RETURN_ERROR, + UVC_OP_INVALID, +}; + +enum uvc_class_status { + UVC_STATE_INITIALIZED, + UVC_STATE_ENABLED, + UVC_STATE_STREAM_READY, + UVC_STATE_STREAM_RESTART, + UVC_STATE_PAUSED, +}; + +enum uvc_unit_id { + UVC_UNIT_ID_CT = 1, + UVC_UNIT_ID_SU, + UVC_UNIT_ID_PU, + UVC_UNIT_ID_XU, + UVC_UNIT_ID_OT, +}; + +enum uvc_control_type { + UVC_CONTROL_SIGNED, + UVC_CONTROL_UNSIGNED, +}; + +union uvc_fmt_desc { + struct usb_desc_header hdr; + struct uvc_format_descriptor fmt; + struct uvc_format_uncomp_descriptor fmt_uncomp; + struct uvc_format_mjpeg_descriptor fmt_mjpeg; + struct uvc_frame_descriptor frm; + struct uvc_frame_continuous_descriptor frm_cont; + struct uvc_frame_discrete_descriptor frm_disc; +}; + +struct uvc_desc { + struct usb_association_descriptor iad; + struct usb_if_descriptor if0; + struct uvc_control_header_descriptor if0_hdr; + struct uvc_camera_terminal_descriptor if0_ct; + struct uvc_selector_unit_descriptor if0_su; + struct uvc_processing_unit_descriptor if0_pu; + struct uvc_extension_unit_descriptor if0_xu; + struct uvc_output_terminal_descriptor if0_ot; + struct usb_if_descriptor if1; + struct uvc_stream_header_descriptor if1_hdr; + union uvc_fmt_desc if1_fmts[CONFIG_USBD_VIDEO_MAX_FORMATS]; + struct uvc_color_descriptor if1_color; + struct usb_ep_descriptor if1_ep_fs; + struct usb_ep_descriptor if1_ep_hs; +}; + +struct uvc_data { + /* Input buffers to which enqueued video buffers land */ + struct k_fifo fifo_in; + /* Output buffers from which dequeued buffers are picked */ + struct k_fifo fifo_out; + /* Default video probe stored at boot time and sent back to the host when requested */ + struct uvc_probe default_probe; + /* Video payload header content sent before every frame, updated between every frame */ + struct uvc_payload_header payload_header; + /* Video device that is connected to this UVC stream */ + const struct device *video_dev; + /* Video format cached locally for efficiency */ + struct video_format video_fmt; + /* Current frame interval selected by the host */ + struct video_frmival video_frmival; + /* Signal to alert video devices of buffer-related evenets */ + struct k_poll_signal *video_sig; + /* Makes sure flushing the stream only happens in one context at a time */ + struct k_mutex mutex; + /* Zero Length packet used to reset a stream when restarted */ + struct net_buf zlp; + /* Byte offset within the currently transmitted video buffer */ + size_t vbuf_offset; + /* Let the different parts of the code know of the current state */ + atomic_t state; + /* Index where newly generated descriptors are appened */ + unsigned int fs_desc_idx; + unsigned int hs_desc_idx; + unsigned int fmt_desc_idx; + /* UVC error from latest request */ + uint8_t err; + /* Format currently selected by the host */ + uint8_t format_id; + /* Frame currently selected by the host */ + uint8_t frame_id; +}; + +struct uvc_config { + /* Storage for the various descriptors available */ + struct uvc_desc *desc; + /* Class context used by the USB device stack */ + struct usbd_class_data *c_data; + /* Array of pointers to descriptors sent to the USB device stack and the host */ + struct usb_desc_header **fs_desc; + struct usb_desc_header **hs_desc; +}; + +/* Specialized version of UDC net_buf metadata with extra fields */ +struct uvc_buf_info { + /* Regular UDC buf info so that it can be passed to USBD directly */ + struct udc_buf_info udc; + /* Extra field at the end */ + struct video_buffer *vbuf; +} __packed; + +/* Mapping between UVC controls and Video controls */ +struct uvc_control_map { + /* Video CID to use for this control */ + uint32_t cid; + /* Size to write out */ + uint8_t size; + /* Bit position in the UVC control */ + uint8_t bit; + /* UVC selector identifying this control */ + uint8_t selector; + /* Whether the UVC value is signed, always false for bitmaps and boolean */ + enum uvc_control_type type; +}; + +struct uvc_guid_quirk { + /* A Video API format identifier, for which the UVC format GUID is not standard. */ + uint32_t fourcc; + /* GUIDs are 16-bytes long, with the first four bytes being the Four Character Code of the + * format and the rest constant, except for some exceptions listed in this table. + */ + uint8_t guid[16]; +}; + +#define UVC_TOTAL_BUFS (DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) * CONFIG_USBD_VIDEO_NUM_BUFS) + +UDC_BUF_POOL_VAR_DEFINE(uvc_buf_pool, UVC_TOTAL_BUFS, UVC_TOTAL_BUFS * USBD_MAX_BULK_MPS, + sizeof(struct uvc_buf_info), NULL); + +static void uvc_flush_queue(const struct device *dev); + +/* UVC public API */ + +void uvc_set_video_dev(const struct device *const dev, const struct device *const video_dev) +{ + struct uvc_data *data = dev->data; + + data->video_dev = video_dev; +} + +/* UVC helper functions */ + +static const struct uvc_guid_quirk uvc_guid_quirks[] = { + { + .fourcc = VIDEO_PIX_FMT_YUYV, + .guid = UVC_FORMAT_GUID("YUY2"), + }, + { + .fourcc = VIDEO_PIX_FMT_GREY, + .guid = UVC_FORMAT_GUID("Y800"), + }, +}; + +static void uvc_fourcc_to_guid(uint8_t guid[16], const uint32_t fourcc) +{ + uint32_t fourcc_le; + + /* Lookup in the "quirk table" if the UVC format GUID is custom */ + for (int i = 0; i < ARRAY_SIZE(uvc_guid_quirks); i++) { + if (uvc_guid_quirks[i].fourcc == fourcc) { + memcpy(guid, uvc_guid_quirks[i].guid, 16); + return; + } + } + + /* By default, UVC GUIDs are the four character code followed by a common suffix */ + fourcc_le = sys_cpu_to_le32(fourcc); + /* Copy the common suffix with the GUID set to 'XXXX' */ + memcpy(guid, UVC_FORMAT_GUID("XXXX"), 16); + /* Replace the 'XXXX' by the actual GUID of the format */ + memcpy(guid, &fourcc_le, 4); +} + +static uint32_t uvc_guid_to_fourcc(const uint8_t guid[16]) +{ + uint32_t fourcc; + + /* Lookup in the "quirk table" if the UVC format GUID is custom */ + for (int i = 0; i < ARRAY_SIZE(uvc_guid_quirks); i++) { + if (memcmp(guid, uvc_guid_quirks[i].guid, 16) == 0) { + return uvc_guid_quirks[i].fourcc; + } + } + + /* Extract the four character code out of the leading 4 bytes of the GUID */ + memcpy(&fourcc, guid, 4); + fourcc = sys_le32_to_cpu(fourcc); + + return fourcc; +} + +/* UVC control handling */ + +static const struct uvc_control_map uvc_control_map_ct[] = { + { + .size = 1, + .bit = 1, + .selector = UVC_CT_AE_MODE_CONTROL, + .cid = VIDEO_CID_EXPOSURE_AUTO, + .type = UVC_CONTROL_UNSIGNED, + }, + { + .size = 1, + .bit = 2, + .selector = UVC_CT_AE_PRIORITY_CONTROL, + .cid = VIDEO_CID_EXPOSURE_AUTO_PRIORITY, + .type = UVC_CONTROL_UNSIGNED, + }, + { + .size = 4, + .bit = 3, + .selector = UVC_CT_EXPOSURE_TIME_ABS_CONTROL, + .cid = VIDEO_CID_EXPOSURE, + .type = UVC_CONTROL_UNSIGNED, + }, + { + .size = 2, + .bit = 5, + .selector = UVC_CT_FOCUS_ABS_CONTROL, + .cid = VIDEO_CID_FOCUS_ABSOLUTE, + .type = UVC_CONTROL_UNSIGNED, + }, + { + .size = 2, + .bit = 6, + .selector = UVC_CT_FOCUS_REL_CONTROL, + .cid = VIDEO_CID_FOCUS_RELATIVE, + .type = UVC_CONTROL_SIGNED, + }, + { + .size = 2, + .bit = 7, + .selector = UVC_CT_IRIS_ABS_CONTROL, + .cid = VIDEO_CID_IRIS_ABSOLUTE, + .type = UVC_CONTROL_UNSIGNED, + }, + { + .size = 1, + .bit = 8, + .selector = UVC_CT_IRIS_REL_CONTROL, + .cid = VIDEO_CID_IRIS_RELATIVE, + .type = UVC_CONTROL_SIGNED, + }, + { + .size = 2, + .bit = 9, + .selector = UVC_CT_ZOOM_ABS_CONTROL, + .cid = VIDEO_CID_ZOOM_ABSOLUTE, + .type = UVC_CONTROL_UNSIGNED, + }, + { + .size = 3, + .bit = 10, + .selector = UVC_CT_ZOOM_REL_CONTROL, + .cid = VIDEO_CID_ZOOM_RELATIVE, + .type = UVC_CONTROL_SIGNED, + }, +}; + +static const struct uvc_control_map uvc_control_map_pu[] = { + { + .size = 2, + .bit = 0, + .selector = UVC_PU_BRIGHTNESS_CONTROL, + .cid = VIDEO_CID_BRIGHTNESS, + .type = UVC_CONTROL_SIGNED, + }, + { + .size = 1, + .bit = 1, + .selector = UVC_PU_CONTRAST_CONTROL, + .cid = VIDEO_CID_CONTRAST, + .type = UVC_CONTROL_UNSIGNED, + }, + { + .size = 2, + .bit = 9, + .selector = UVC_PU_GAIN_CONTROL, + .cid = VIDEO_CID_GAIN, + .type = UVC_CONTROL_UNSIGNED, + }, + { + .size = 2, + .bit = 3, + .selector = UVC_PU_SATURATION_CONTROL, + .cid = VIDEO_CID_SATURATION, + .type = UVC_CONTROL_UNSIGNED, + }, + { + .size = 2, + .bit = 6, + .selector = UVC_PU_WHITE_BALANCE_TEMP_CONTROL, + .cid = VIDEO_CID_WHITE_BALANCE_TEMPERATURE, + .type = UVC_CONTROL_UNSIGNED, + }, +}; + +static const struct uvc_control_map uvc_control_map_su[] = { + { + .size = 1, + .bit = 0, + .selector = UVC_SU_INPUT_SELECT_CONTROL, + .cid = VIDEO_CID_TEST_PATTERN, + .type = UVC_CONTROL_UNSIGNED, + }, +}; + +static const struct uvc_control_map uvc_control_map_xu[] = { + { + .size = 4, + .bit = 0, + .selector = UVC_XU_BASE_CONTROL + 0, + .cid = VIDEO_CID_PRIVATE_BASE + 0, + .type = UVC_CONTROL_UNSIGNED, + }, + { + .size = 4, + .bit = 1, + .selector = UVC_XU_BASE_CONTROL + 1, + .cid = VIDEO_CID_PRIVATE_BASE + 1, + .type = UVC_CONTROL_UNSIGNED, + }, + { + .size = 4, + .bit = 2, + .selector = UVC_XU_BASE_CONTROL + 2, + .cid = VIDEO_CID_PRIVATE_BASE + 2, + .type = UVC_CONTROL_UNSIGNED, + }, + { + .size = 4, + .bit = 3, + .selector = UVC_XU_BASE_CONTROL + 3, + .cid = VIDEO_CID_PRIVATE_BASE + 3, + .type = UVC_CONTROL_UNSIGNED, + }, +}; + +/* Get the format and frame descriptors selected for the given VideoStreaming interface. */ +static void uvc_get_vs_fmtfrm_desc(const struct device *dev, + struct uvc_format_descriptor **const format_desc, + struct uvc_frame_discrete_descriptor **const frame_desc) +{ + const struct uvc_config *cfg = dev->config; + struct uvc_data *data = dev->data; + int i; + + *format_desc = NULL; + for (i = 0; i < ARRAY_SIZE(cfg->desc->if1_fmts); i++) { + struct uvc_format_descriptor *desc = &cfg->desc->if1_fmts[i].fmt; + + LOG_DBG("Walking through format %u, subtype %u, index %u, ptr %p", + i, desc->bDescriptorSubtype, desc->bFormatIndex, desc); + + if ((desc->bDescriptorSubtype == UVC_VS_FORMAT_UNCOMPRESSED || + desc->bDescriptorSubtype == UVC_VS_FORMAT_MJPEG) && + desc->bFormatIndex == data->format_id) { + *format_desc = desc; + break; + } + } + + *frame_desc = NULL; + for (i++; i < ARRAY_SIZE(cfg->desc->if1_fmts); i++) { + struct uvc_frame_discrete_descriptor *desc = &cfg->desc->if1_fmts[i].frm_disc; + + LOG_DBG("Walking through frame %u, subtype %u, index %u, ptr %p", + i, desc->bDescriptorSubtype, desc->bFrameIndex, desc); + + if (desc->bDescriptorSubtype != UVC_VS_FRAME_UNCOMPRESSED && + desc->bDescriptorSubtype != UVC_VS_FRAME_MJPEG) { + break; + } + + if (desc->bFrameIndex == data->frame_id) { + *frame_desc = desc; + break; + } + } +} + +static uint8_t uvc_get_bulk_in(const struct device *dev) +{ + const struct uvc_config *cfg = dev->config; + struct usbd_context *uds_ctx = usbd_class_get_ctx(cfg->c_data); + struct uvc_desc *desc = cfg->desc; + + if (USBD_SUPPORTS_HIGH_SPEED && + usbd_bus_speed(uds_ctx) == USBD_SPEED_HS) { + return desc->if1_ep_hs.bEndpointAddress; + } + + return desc->if1_ep_fs.bEndpointAddress; +} + +static size_t uvc_get_bulk_mps(struct usbd_class_data *const c_data) +{ + struct usbd_context *uds_ctx = usbd_class_get_ctx(c_data); + + if (USBD_SUPPORTS_HIGH_SPEED && + usbd_bus_speed(uds_ctx) == USBD_SPEED_HS) { + return 512U; + } + + return 64U; +} + +static int uvc_get_vs_probe_format_index(const struct device *dev, struct uvc_probe *const probe, + const uint8_t request) +{ + const struct uvc_config *cfg = dev->config; + struct uvc_data *data = dev->data; + uint8_t max = 0; + + for (int i = 0; i < ARRAY_SIZE(cfg->desc->if1_fmts); i++) { + struct uvc_format_descriptor *desc = &cfg->desc->if1_fmts[i].fmt; + + max += desc->bDescriptorSubtype == UVC_VS_FORMAT_UNCOMPRESSED || + desc->bDescriptorSubtype == UVC_VS_FORMAT_MJPEG; + } + + switch (request) { + case UVC_GET_RES: + __fallthrough; + case UVC_GET_MIN: + probe->bFormatIndex = 1; + break; + case UVC_GET_MAX: + probe->bFormatIndex = max; + break; + case UVC_GET_CUR: + probe->bFormatIndex = data->format_id; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int uvc_get_vs_probe_frame_index(const struct device *dev, struct uvc_probe *const probe, + const uint8_t request) +{ + const struct uvc_config *cfg = dev->config; + struct uvc_data *data = dev->data; + uint8_t max = 0; + int i; + + /* Search the current format */ + for (i = 0; i < ARRAY_SIZE(cfg->desc->if1_fmts); i++) { + struct uvc_format_descriptor *desc = &cfg->desc->if1_fmts[i].fmt; + + if ((desc->bDescriptorSubtype == UVC_VS_FORMAT_UNCOMPRESSED || + desc->bDescriptorSubtype == UVC_VS_FORMAT_MJPEG) && + desc->bFormatIndex == data->format_id) { + break; + } + } + + /* Seek until the next format */ + for (i++; i < ARRAY_SIZE(cfg->desc->if1_fmts); i++) { + struct uvc_frame_discrete_descriptor *desc = &cfg->desc->if1_fmts[i].frm_disc; + + if (desc->bDescriptorSubtype != UVC_VS_FRAME_UNCOMPRESSED && + desc->bDescriptorSubtype != UVC_VS_FRAME_MJPEG) { + break; + } + max++; + } + + switch (request) { + case UVC_GET_RES: + __fallthrough; + case UVC_GET_MIN: + probe->bFrameIndex = 1; + break; + case UVC_GET_MAX: + probe->bFrameIndex = max; + break; + case UVC_GET_CUR: + probe->bFrameIndex = data->frame_id; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int uvc_get_vs_probe_frame_interval(const struct device *dev, struct uvc_probe *const probe, + const uint8_t request) +{ + struct uvc_data *data = dev->data; + struct uvc_format_descriptor *format_desc; + struct uvc_frame_discrete_descriptor *frame_desc; + int max; + + uvc_get_vs_fmtfrm_desc(dev, &format_desc, &frame_desc); + if (format_desc == NULL || frame_desc == NULL) { + LOG_DBG("Selected format ID or frame ID not found"); + return -EINVAL; + } + + switch (request) { + case UVC_GET_MIN: + probe->dwFrameInterval = sys_cpu_to_le32(frame_desc->dwFrameInterval[0]); + break; + case UVC_GET_MAX: + max = frame_desc->bFrameIntervalType - 1; + probe->dwFrameInterval = sys_cpu_to_le32(frame_desc->dwFrameInterval[max]); + break; + case UVC_GET_RES: + probe->dwFrameInterval = sys_cpu_to_le32(1); + break; + case UVC_GET_CUR: + probe->dwFrameInterval = sys_cpu_to_le32(data->video_frmival.numerator); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int uvc_get_vs_probe_max_size(const struct device *dev, struct uvc_probe *const probe, + const uint8_t request) +{ + struct uvc_data *data = dev->data; + struct video_format *fmt = &data->video_fmt; + uint32_t max_frame_size = MAX(fmt->pitch, fmt->width) * fmt->height; + uint32_t max_payload_size = max_frame_size + UVC_MAX_HEADER_LENGTH; + + switch (request) { + case UVC_GET_MIN: + __fallthrough; + case UVC_GET_MAX: + __fallthrough; + case UVC_GET_CUR: + probe->dwMaxPayloadTransferSize = sys_cpu_to_le32(max_payload_size); + probe->dwMaxVideoFrameSize = sys_cpu_to_le32(max_frame_size); + break; + case UVC_GET_RES: + probe->dwMaxPayloadTransferSize = sys_cpu_to_le32(1); + probe->dwMaxVideoFrameSize = sys_cpu_to_le32(1); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int uvc_get_vs_format_from_desc(const struct device *dev, struct video_format *const fmt) +{ + struct uvc_data *data = dev->data; + struct uvc_format_descriptor *format_desc = NULL; + struct uvc_frame_discrete_descriptor *frame_desc; + + /* Update the format based on the probe message from the host */ + uvc_get_vs_fmtfrm_desc(dev, &format_desc, &frame_desc); + if (format_desc == NULL || frame_desc == NULL) { + LOG_ERR("Invalid format ID (%u) and/or frame ID (%u)", + data->format_id, data->frame_id); + return -EINVAL; + } + + /* Translate between UVC pixel formats and Video pixel formats */ + if (format_desc->bDescriptorSubtype == UVC_VS_FORMAT_MJPEG) { + fmt->pixelformat = VIDEO_PIX_FMT_JPEG; + + LOG_DBG("Found descriptor for format %u, frame %u, MJPEG", + format_desc->bFormatIndex, frame_desc->bFrameIndex); + } else { + struct uvc_format_uncomp_descriptor *format_uncomp_desc = (void *)format_desc; + + fmt->pixelformat = uvc_guid_to_fourcc(format_uncomp_desc->guidFormat); + + LOG_DBG("Found descriptor for format %u, frame %u, GUID '%.4s', pixfmt %04x", + format_uncomp_desc->bFormatIndex, frame_desc->bFrameIndex, + format_uncomp_desc->guidFormat, fmt->pixelformat); + } + + /* Fill the format according to what the host selected */ + fmt->width = frame_desc->wWidth; + fmt->height = frame_desc->wHeight; + fmt->pitch = fmt->width * video_bits_per_pixel(fmt->pixelformat) / BITS_PER_BYTE; + + return 0; +} + +static int uvc_get_vs_probe_struct(const struct device *dev, struct uvc_probe *const probe, + const uint8_t request) +{ + struct uvc_data *data = dev->data; + struct video_format *fmt = &data->video_fmt; + int ret; + + ret = uvc_get_vs_probe_format_index(dev, probe, request); + if (ret != 0) { + return ret; + } + + ret = uvc_get_vs_probe_frame_index(dev, probe, request); + if (ret != 0) { + return ret; + } + + ret = uvc_get_vs_format_from_desc(dev, fmt); + if (ret != 0) { + return ret; + } + + ret = uvc_get_vs_probe_frame_interval(dev, probe, request); + if (ret != 0) { + return ret; + } + + ret = uvc_get_vs_probe_max_size(dev, probe, request); + if (ret != 0) { + return ret; + } + + probe->dwClockFrequency = sys_cpu_to_le32(1); + probe->bmFramingInfo = UVC_BMFRAMING_INFO_FID | UVC_BMFRAMING_INFO_EOF; + probe->bPreferedVersion = 1; + probe->bMinVersion = 1; + probe->bMaxVersion = 1; + probe->bUsage = 0; + probe->bBitDepthLuma = 0; + probe->bmSettings = 0; + probe->bMaxNumberOfRefFramesPlus1 = 1; + probe->bmRateControlModes = 0; + probe->bmLayoutPerStream = 0; + probe->wKeyFrameRate = sys_cpu_to_le16(0); + probe->wPFrameRate = sys_cpu_to_le16(0); + probe->wCompQuality = sys_cpu_to_le16(0); + probe->wCompWindowSize = sys_cpu_to_le16(0); + probe->wDelay = sys_cpu_to_le16(1); + + return 0; +} + +static int uvc_get_vs_probe(const struct device *dev, struct net_buf *const buf, + const struct usb_setup_packet *const setup) +{ + struct uvc_data *data = dev->data; + size_t size = MIN(sizeof(struct uvc_probe), net_buf_tailroom(buf)); + int ret; + + switch (setup->bRequest) { + case UVC_GET_INFO: + if (size < 1) { + return -ENOTSUP; + } + net_buf_add_u8(buf, UVC_INFO_SUPPORTS_GET); + return 0; + case UVC_GET_LEN: + if (size < 2) { + return -ENOTSUP; + } + net_buf_add_le16(buf, sizeof(struct uvc_probe)); + return 0; + case UVC_GET_DEF: + if (size < sizeof(struct uvc_probe)) { + return -ENOTSUP; + } + net_buf_add_mem(buf, &data->default_probe, sizeof(data->default_probe)); + return 0; + case UVC_GET_MIN: + __fallthrough; + case UVC_GET_RES: + __fallthrough; + case UVC_GET_MAX: + __fallthrough; + case UVC_GET_CUR: + if (size < sizeof(struct uvc_probe)) { + return -ENOTSUP; + } + + ret = uvc_get_vs_probe_struct(dev, (struct uvc_probe *)buf->data, setup->bRequest); + if (ret != 0) { + return ret; + } + + net_buf_add(buf, sizeof(struct uvc_probe)); + return 0; + default: + return -EINVAL; + } +} + +static int uvc_set_vs_probe(const struct device *dev, const struct net_buf *const buf) +{ + struct uvc_data *data = dev->data; + struct uvc_probe *probe; + struct uvc_probe max = {0}; + int ret; + + if (buf->len != sizeof(*probe)) { + LOG_ERR("Expected probe message of %u bytes got %u", sizeof(*probe), buf->len); + return -EINVAL; + } + + probe = (struct uvc_probe *)buf->data; + + ret = uvc_get_vs_probe_struct(dev, &max, UVC_GET_MAX); + if (ret != 0) { + return ret; + } + + if (probe->bFrameIndex > max.bFrameIndex) { + LOG_WRN("The bFrameIndex %u requested is beyond the max %u", + probe->bFrameIndex, max.bFrameIndex); + return -ERANGE; + } + + if (probe->bFormatIndex > max.bFormatIndex) { + LOG_WRN("The bFormatIndex %u requested is beyond the max %u", + probe->bFormatIndex, max.bFormatIndex); + return -ERANGE; + } + + if (probe->dwFrameInterval != 0) { + data->video_frmival.numerator = sys_le32_to_cpu(probe->dwFrameInterval); + data->video_frmival.denominator = USEC_PER_SEC * 100; + } + + if (probe->bFrameIndex != 0) { + data->frame_id = probe->bFrameIndex; + } + + if (probe->bFormatIndex != 0) { + data->format_id = probe->bFormatIndex; + } + + return 0; +} + +static int uvc_get_vs_commit(const struct device *dev, struct net_buf *const buf, + const struct usb_setup_packet *const setup) +{ + if (setup->bRequest != UVC_GET_CUR) { + LOG_WRN("Invalid commit bRequest %u", setup->bRequest); + return -EINVAL; + } + + return uvc_get_vs_probe(dev, buf, setup); +} + +static int uvc_set_vs_commit(const struct device *dev, const struct net_buf *const buf) +{ + struct uvc_data *data = dev->data; + struct video_format fmt = data->video_fmt; + struct video_frmival frmival = data->video_frmival; + int ret; + + __ASSERT_NO_MSG(data->video_dev != NULL); + + ret = uvc_set_vs_probe(dev, buf); + if (ret != 0) { + return ret; + } + + LOG_INF("Ready to transfer, setting source format to '%s' %ux%u", + VIDEO_FOURCC_TO_STR(fmt.pixelformat), fmt.width, fmt.height); + + fmt.type = VIDEO_BUF_TYPE_OUTPUT; + + ret = video_set_format(data->video_dev, &fmt); + if (ret != 0) { + LOG_ERR("Could not set the format of %s", data->video_dev->name); + return ret; + } + + LOG_DBG("Setting frame interval of %s to %u/%u", + data->video_dev->name, + data->video_frmival.numerator, data->video_frmival.denominator); + + ret = video_set_frmival(data->video_dev, &frmival); + if (ret != 0) { + LOG_WRN("Could not set the framerate of %s", data->video_dev->name); + } + + LOG_DBG("UVC device ready, %s can now be started", data->video_dev->name); + + if (atomic_test_bit(&data->state, UVC_STATE_STREAM_READY)) { + atomic_set_bit(&data->state, UVC_STATE_STREAM_RESTART); + } + + atomic_set_bit(&data->state, UVC_STATE_STREAM_READY); + uvc_flush_queue(dev); + + return 0; +} + +static void uvc_get_vc_conversion_map(const uint32_t cid, const int **const map, int *const map_sz) +{ + static const int ct_ae_mode[] = { + [0] = VIDEO_EXPOSURE_MANUAL, + [1] = VIDEO_EXPOSURE_AUTO, + [2] = VIDEO_EXPOSURE_SHUTTER_PRIORITY, + [3] = VIDEO_EXPOSURE_APERTURE_PRIORITY, + }; + + switch (cid) { + case VIDEO_CID_EXPOSURE_AUTO: + *map = ct_ae_mode; + *map_sz = ARRAY_SIZE(ct_ae_mode); + break; + default: + *map = NULL; + *map_sz = 0; + break; + } +} + +/* + * Convert the Zephyr Video control IDs (CID) to UVC Video Control (VC) IDs. + */ +static int uvc_convert_cid_to_vc(const uint32_t cid, int64_t *const val64) +{ + const int *map; + int map_sz; + + uvc_get_vc_conversion_map(cid, &map, &map_sz); + if (map == NULL) { + /* No conversion needed */ + return 0; + } + + for (int i = 0; i < map_sz; i++) { + if (map[i] == *val64) { + *val64 = BIT(i); + return 0; + } + } + + return -ENOTSUP; +} + +/* + * Convert the UVC Video Control (VC) IDs to Zephyr Video control IDs (CID). + */ +static int uvc_convert_vc_to_cid(const int32_t cid, int64_t *const val64) +{ + const int *map; + int map_sz; + + uvc_get_vc_conversion_map(cid, &map, &map_sz); + if (map == NULL) { + /* No conversion needed */ + return 0; + } + + for (int i = 0; i < map_sz; i++) { + if (BIT(i) & *val64) { + *val64 = map[i]; + return 0; + } + } + + return -ENOTSUP; +} + +static int uvc_get_vc_ctrl(const struct device *dev, struct net_buf *const buf, + const struct usb_setup_packet *const setup, + const struct uvc_control_map *const map) +{ + struct uvc_data *data = dev->data; + const struct device *video_dev = data->video_dev; + struct video_ctrl_query cq = {.id = map->cid, .dev = video_dev}; + struct video_control ctrl = {.id = map->cid}; + size_t size = MIN(setup->wLength, net_buf_tailroom(buf)); + int64_t val64; + int ret; + + __ASSERT_NO_MSG(video_dev != NULL); + + ret = video_query_ctrl(&cq); + if (ret != 0) { + LOG_ERR("Failed to query %s for control 0x%x", video_dev->name, cq.id); + return ret; + } + + LOG_INF("Responding to GET control '%s', size %u", cq.name, map->size); + + if (cq.type != VIDEO_CTRL_TYPE_BOOLEAN && cq.type != VIDEO_CTRL_TYPE_MENU && + cq.type != VIDEO_CTRL_TYPE_INTEGER && cq.type != VIDEO_CTRL_TYPE_INTEGER64) { + LOG_ERR("Unsupported control type %u", cq.type); + return -ENOTSUP; + } + + switch (setup->bRequest) { + case UVC_GET_INFO: + if (size < 1) { + return -ENOTSUP; + } + net_buf_add_u8(buf, UVC_INFO_SUPPORTS_GET | UVC_INFO_SUPPORTS_SET); + return 0; + case UVC_GET_LEN: + if (size < 2) { + return -ENOTSUP; + } + net_buf_add_le16(buf, map->size); + return 0; + case UVC_GET_CUR: + ret = video_get_ctrl(video_dev, &ctrl); + if (ret != 0) { + LOG_INF("Failed to query %s", video_dev->name); + return ret; + } + + val64 = (cq.type == VIDEO_CTRL_TYPE_INTEGER64) ? ctrl.val64 : ctrl.val; + break; + case UVC_GET_MIN: + val64 = (cq.type == VIDEO_CTRL_TYPE_INTEGER64) ? cq.range.min64 : cq.range.min; + break; + case UVC_GET_MAX: + val64 = (cq.type == VIDEO_CTRL_TYPE_INTEGER64) ? cq.range.max64 : cq.range.max; + break; + case UVC_GET_RES: + val64 = (cq.type == VIDEO_CTRL_TYPE_INTEGER64) ? cq.range.step64 : cq.range.step; + break; + case UVC_GET_DEF: + val64 = (cq.type == VIDEO_CTRL_TYPE_INTEGER64) ? cq.range.def64 : cq.range.def; + break; + default: + LOG_WRN("Unsupported request type %u", setup->bRequest); + return -ENOTSUP; + } + + if (size < map->size) { + LOG_WRN("Buffer too small (%u bytes) or unexpected size requested (%u bytes)", + net_buf_tailroom(buf), setup->wLength); + return -ENOTSUP; + } + + ret = uvc_convert_cid_to_vc(cq.id, &val64); + if (ret != 0) { + return ret; + } + + switch (map->type) { + case UVC_CONTROL_SIGNED: + if (map->size == 1) { + net_buf_add_u8(buf, CLAMP(val64, INT8_MIN, INT8_MAX)); + } else if (map->size == 2) { + net_buf_add_le16(buf, CLAMP(val64, INT16_MIN, INT16_MAX)); + } else if (map->size == 3) { + net_buf_add_le24(buf, CLAMP(val64, -0x800000, 0x7fffff)); + } else if (map->size == 4) { + net_buf_add_le32(buf, CLAMP(val64, INT32_MIN, INT32_MAX)); + } else { + LOG_WRN("Unsupported integer size %u for UVC control value", map->size); + return -ENOTSUP; + } + break; + case UVC_CONTROL_UNSIGNED: + if (map->size == 1) { + net_buf_add_u8(buf, CLAMP(val64, 0, UINT8_MAX)); + } else if (map->size == 2) { + net_buf_add_le16(buf, CLAMP(val64, 0, UINT16_MAX)); + } else if (map->size == 3) { + net_buf_add_le24(buf, CLAMP(val64, 0, 0xffffff)); + } else if (map->size == 4) { + net_buf_add_le32(buf, CLAMP(val64, 0, UINT32_MAX)); + } else { + LOG_WRN("Unsupported integer size %u for UVC control value", map->size); + return -ENOTSUP; + } + break; + } + + return 0; +} + +static int uvc_set_vc_ctrl(const struct device *dev, const struct net_buf *const buf_in, + const struct uvc_control_map *const map) +{ + struct uvc_data *data = dev->data; + const struct device *video_dev = data->video_dev; + struct video_ctrl_query cq = {.id = map->cid, .dev = video_dev}; + struct video_control ctrl = {.id = map->cid}; + struct net_buf buf; + int64_t val64 = 0; + int ret; + + __ASSERT_NO_MSG(video_dev != NULL); + + /* Local copy that can be modified, so that functions can be used */ + memcpy(&buf, buf_in, sizeof(buf)); + + ret = video_query_ctrl(&cq); + if (ret != 0) { + LOG_ERR("Failed to query the video device for control 0x%08x", cq.id); + return ret; + } + + if (cq.type != VIDEO_CTRL_TYPE_BOOLEAN && cq.type != VIDEO_CTRL_TYPE_MENU && + cq.type != VIDEO_CTRL_TYPE_INTEGER && cq.type != VIDEO_CTRL_TYPE_INTEGER64) { + LOG_ERR("Unsupported control type %u", cq.type); + return -ENOTSUP; + } + + if (buf.len < map->size) { + LOG_ERR("USB message size %u too short for control 0x%08x", buf.len, cq.id); + return -ENOTSUP; + } + + switch (map->type) { + case UVC_CONTROL_SIGNED: + if (map->size == 1) { + val64 = (int8_t)net_buf_remove_u8(&buf); + } else if (map->size == 2) { + val64 = (int16_t)net_buf_remove_le16(&buf); + } else if (map->size == 3) { + val64 = (int32_t)net_buf_remove_le24(&buf); + } else if (map->size == 4) { + val64 = (int32_t)net_buf_remove_le32(&buf); + } else { + return -ENOTSUP; + } + break; + case UVC_CONTROL_UNSIGNED: + if (map->size == 1) { + val64 = net_buf_remove_u8(&buf); + } else if (map->size == 2) { + val64 = net_buf_remove_le16(&buf); + } else if (map->size == 3) { + val64 = net_buf_remove_le24(&buf); + } else if (map->size == 4) { + val64 = net_buf_remove_le32(&buf); + } else { + return -ENOTSUP; + } + break; + } + + ret = uvc_convert_vc_to_cid(cq.id, &val64); + if (ret != 0) { + return ret; + } + + if (cq.type == VIDEO_CTRL_TYPE_INTEGER64) { + ctrl.val64 = val64; + } else { + ctrl.val = val64; + } + + LOG_DBG("Setting control 0x%08x to %llu", cq.id, val64); + + ret = video_set_ctrl(video_dev, &ctrl); + if (ret != 0) { + LOG_ERR("Failed to configure target video device"); + return ret; + } + + return 0; +} + +static int uvc_get_errno(const struct device *dev, struct net_buf *const buf, + const struct usb_setup_packet *const setup) +{ + struct uvc_data *data = dev->data; + size_t size = MIN(setup->wLength, net_buf_tailroom(buf)); + + switch (setup->bRequest) { + case UVC_GET_INFO: + if (size < 1) { + return -ENOTSUP; + } + net_buf_add_u8(buf, UVC_INFO_SUPPORTS_GET); + break; + case UVC_GET_CUR: + if (size < 1) { + return -ENOTSUP; + } + net_buf_add_u8(buf, data->err); + break; + default: + LOG_WRN("Unsupported request type %u", setup->bRequest); + return -ENOTSUP; + } + + return 0; +} + +static void uvc_set_errno(const struct device *dev, const int ret) +{ + struct uvc_data *data = dev->data; + + if (ret == 0) { + data->err = 0; + } else if (ret == EBUSY || ret == EAGAIN || ret == EINPROGRESS || ret == EALREADY) { + data->err = UVC_ERR_NOT_READY; + } else if (ret == EOVERFLOW || ret == ERANGE || ret == E2BIG) { + data->err = UVC_ERR_OUT_OF_RANGE; + } else if (ret == EDOM || ret == EINVAL) { + data->err = UVC_ERR_INVALID_VALUE_WITHIN_RANGE; + } else if (ret == ENODEV || ret == ENOTSUP || ret == ENOSYS) { + data->err = UVC_ERR_INVALID_REQUEST; + } else { + data->err = UVC_ERR_UNKNOWN; + } +} + +static int uvc_get_control_op(const struct device *dev, const struct usb_setup_packet *const setup, + const struct uvc_control_map **const map) +{ + const struct uvc_config *cfg = dev->config; + struct uvc_data *data = dev->data; + const struct uvc_control_map *list = NULL; + size_t list_sz; + uint8_t ifnum = (setup->wIndex >> 0) & 0xff; + uint8_t unit_id = setup->wIndex >> 8; + uint8_t selector = setup->wValue >> 8; + uint8_t subtype = 0; + + /* VideoStreaming operation */ + + if (ifnum == cfg->desc->if1.bInterfaceNumber) { + switch (selector) { + case UVC_VS_PROBE_CONTROL: + LOG_INF("Host sent a VideoStreaming PROBE control"); + return UVC_OP_VS_PROBE; + case UVC_VS_COMMIT_CONTROL: + LOG_INF("Host sent a VideoStreaming COMMIT control"); + return UVC_OP_VS_COMMIT; + default: + LOG_ERR("Invalid probe/commit operation for bInterfaceNumber %u", ifnum); + return UVC_OP_INVALID; + } + } + + /* VideoControl operation */ + + if (ifnum != cfg->desc->if0.bInterfaceNumber) { + LOG_WRN("Interface %u not found", ifnum); + data->err = UVC_ERR_INVALID_UNIT; + return UVC_OP_RETURN_ERROR; + } + + if (unit_id == 0) { + return UVC_OP_GET_ERRNO; + } + + for (int i = UVC_IDX_VC_UNIT;; i++) { + struct uvc_unit_descriptor *desc = (void *)cfg->fs_desc[i]; + + if (desc->bDescriptorType != USB_DESC_CS_INTERFACE || + (desc->bDescriptorSubtype != UVC_VC_INPUT_TERMINAL && + desc->bDescriptorSubtype != UVC_VC_ENCODING_UNIT && + desc->bDescriptorSubtype != UVC_VC_SELECTOR_UNIT && + desc->bDescriptorSubtype != UVC_VC_EXTENSION_UNIT && + desc->bDescriptorSubtype != UVC_VC_PROCESSING_UNIT)) { + break; + } + + if (unit_id == desc->bUnitID) { + subtype = desc->bDescriptorSubtype; + break; + } + } + + if (subtype == 0) { + goto err; + } + + switch (subtype) { + case UVC_VC_INPUT_TERMINAL: + list = uvc_control_map_ct; + list_sz = ARRAY_SIZE(uvc_control_map_ct); + break; + case UVC_VC_SELECTOR_UNIT: + list = uvc_control_map_su; + list_sz = ARRAY_SIZE(uvc_control_map_su); + break; + case UVC_VC_PROCESSING_UNIT: + list = uvc_control_map_pu; + list_sz = ARRAY_SIZE(uvc_control_map_pu); + break; + case UVC_VC_EXTENSION_UNIT: + list = uvc_control_map_xu; + list_sz = ARRAY_SIZE(uvc_control_map_xu); + break; + default: + CODE_UNREACHABLE; + } + + *map = NULL; + for (int i = 0; i < list_sz; i++) { + if (list[i].selector == selector) { + *map = &list[i]; + break; + } + } + if (*map == NULL) { + goto err; + } + + return UVC_OP_VC_CTRL; +err: + LOG_WRN("No control matches selector %u and bUnitID %u", selector, unit_id); + data->err = UVC_ERR_INVALID_CONTROL; + return UVC_OP_RETURN_ERROR; +} + +static int uvc_control_to_host(struct usbd_class_data *const c_data, + const struct usb_setup_packet *const setup, + struct net_buf *const buf) +{ + const struct device *dev = usbd_class_get_private(c_data); + const struct uvc_control_map *map = NULL; + uint8_t request = setup->bRequest; + + LOG_INF("Host sent a %s request, wValue 0x%04x, wIndex 0x%04x, wLength %u", + request == UVC_GET_CUR ? "GET_CUR" : request == UVC_GET_MIN ? "GET_MIN" : + request == UVC_GET_MAX ? "GET_MAX" : request == UVC_GET_RES ? "GET_RES" : + request == UVC_GET_LEN ? "GET_LEN" : request == UVC_GET_DEF ? "GET_DEF" : + request == UVC_GET_INFO ? "GET_INFO" : "bad", + setup->wValue, setup->wIndex, setup->wLength); + + switch (uvc_get_control_op(dev, setup, &map)) { + case UVC_OP_VS_PROBE: + errno = -uvc_get_vs_probe(dev, buf, setup); + break; + case UVC_OP_VS_COMMIT: + errno = -uvc_get_vs_commit(dev, buf, setup); + break; + case UVC_OP_VC_CTRL: + errno = -uvc_get_vc_ctrl(dev, buf, setup, map); + break; + case UVC_OP_GET_ERRNO: + errno = -uvc_get_errno(dev, buf, setup); + break; + case UVC_OP_RETURN_ERROR: + errno = EINVAL; + return 0; + default: + LOG_WRN("Unhandled operation, stalling control command"); + errno = EINVAL; + } + + uvc_set_errno(dev, errno); + + return 0; +} + +static int uvc_control_to_dev(struct usbd_class_data *const c_data, + const struct usb_setup_packet *const setup, + const struct net_buf *const buf) +{ + const struct device *dev = usbd_class_get_private(c_data); + const struct uvc_control_map *map = NULL; + + if (setup->bRequest != UVC_SET_CUR) { + LOG_WRN("Host issued a control write message but the bRequest is not SET_CUR"); + errno = ENOMEM; + goto end; + } + + LOG_INF("Host sent a SET_CUR request, wValue 0x%04x, wIndex 0x%04x, wLength %u", + setup->wValue, setup->wIndex, setup->wLength); + + switch (uvc_get_control_op(dev, setup, &map)) { + case UVC_OP_VS_PROBE: + errno = -uvc_set_vs_probe(dev, buf); + break; + case UVC_OP_VS_COMMIT: + errno = -uvc_set_vs_commit(dev, buf); + break; + case UVC_OP_VC_CTRL: + errno = -uvc_set_vc_ctrl(dev, buf, map); + break; + case UVC_OP_RETURN_ERROR: + errno = EINVAL; + return 0; + default: + LOG_WRN("Unhandled operation, stalling control command"); + errno = EINVAL; + } +end: + uvc_set_errno(dev, errno); + + return 0; +} + +/* UVC descriptor handling */ + +static void *uvc_get_desc(struct usbd_class_data *const c_data, const enum usbd_speed speed) +{ + const struct device *dev = usbd_class_get_private(c_data); + const struct uvc_config *const cfg = dev->config; + struct uvc_desc *desc = cfg->desc; + + if (USBD_SUPPORTS_HIGH_SPEED && speed == USBD_SPEED_HS) { + desc->if1_hdr.bEndpointAddress = desc->if1_ep_hs.bEndpointAddress; + return cfg->hs_desc; + } + + desc->if1_hdr.bEndpointAddress = desc->if1_ep_fs.bEndpointAddress; + return cfg->fs_desc; +} + +static int uvc_assign_desc(const struct device *dev, void *const desc, + const bool add_to_fs, const bool add_to_hs) +{ + const struct uvc_config *cfg = dev->config; + struct uvc_data *data = dev->data; + static const struct usb_desc_header nil_desc; + + if (add_to_fs) { + if (data->fs_desc_idx + 1 >= UVC_MAX_FS_DESC) { + goto err; + } + cfg->fs_desc[data->fs_desc_idx] = desc; + data->fs_desc_idx++; + cfg->fs_desc[data->fs_desc_idx] = (struct usb_desc_header *)&nil_desc; + } + + if (USBD_SUPPORTS_HIGH_SPEED && add_to_hs) { + if (data->hs_desc_idx + 1 >= UVC_MAX_HS_DESC) { + goto err; + } + cfg->hs_desc[data->hs_desc_idx] = desc; + data->hs_desc_idx++; + cfg->hs_desc[data->hs_desc_idx] = (struct usb_desc_header *)&nil_desc; + } + + return 0; +err: + LOG_ERR("Out of descriptor pointers, raise CONFIG_USBD_VIDEO_MAX_FORMATS above %u", + CONFIG_USBD_VIDEO_MAX_FORMATS); + return -ENOMEM; +} + +static union uvc_fmt_desc *uvc_new_fmt_desc(const struct device *dev) +{ + const struct uvc_config *cfg = dev->config; + struct uvc_data *data = dev->data; + void *desc; + int ret; + + BUILD_ASSERT(CONFIG_USBD_VIDEO_MAX_FORMATS == ARRAY_SIZE(cfg->desc->if1_fmts)); + + if (data->fmt_desc_idx >= CONFIG_USBD_VIDEO_MAX_FORMATS) { + LOG_ERR("Out of descriptor pointers, raise CONFIG_USBD_VIDEO_MAX_FORMATS above %u", + CONFIG_USBD_VIDEO_MAX_FORMATS); + return NULL; + } + + desc = &cfg->desc->if1_fmts[data->fmt_desc_idx]; + data->fmt_desc_idx++; + + LOG_DBG("Allocated format/frame descriptor %u (%p)", data->fmt_desc_idx, desc); + + ret = uvc_assign_desc(dev, desc, true, true); + if (ret != 0) { + return NULL; + } + + return desc; +} + +static int uvc_add_vs_format_desc(const struct device *dev, + struct uvc_format_descriptor **const format_desc, + const struct video_format_cap *const cap) +{ + const struct uvc_config *cfg = dev->config; + + __ASSERT_NO_MSG(format_desc != NULL); + + if (cap->pixelformat == VIDEO_PIX_FMT_JPEG) { + struct uvc_format_mjpeg_descriptor *desc; + + LOG_INF("Adding format descriptor #%u for MJPEG", + cfg->desc->if1_hdr.bNumFormats + 1); + + desc = &uvc_new_fmt_desc(dev)->fmt_mjpeg; + if (desc == NULL) { + return -ENOMEM; + } + + desc->bDescriptorType = USB_DESC_CS_INTERFACE; + desc->bFormatIndex = cfg->desc->if1_hdr.bNumFormats + 1; + desc->bLength = sizeof(*desc); + desc->bDescriptorSubtype = UVC_VS_FORMAT_MJPEG; + desc->bDefaultFrameIndex = 1; + cfg->desc->if1_hdr.bNumFormats++; + cfg->desc->if1_hdr.wTotalLength += desc->bLength; + *format_desc = (struct uvc_format_descriptor *)desc; + } else { + struct uvc_format_uncomp_descriptor *desc; + + LOG_INF("Adding format descriptor #%u for '%s'", + cfg->desc->if1_hdr.bNumFormats + 1, VIDEO_FOURCC_TO_STR(cap->pixelformat)); + + desc = &uvc_new_fmt_desc(dev)->fmt_uncomp; + if (desc == NULL) { + return -ENOMEM; + } + + desc->bDescriptorType = USB_DESC_CS_INTERFACE; + desc->bFormatIndex = cfg->desc->if1_hdr.bNumFormats + 1; + desc->bLength = sizeof(*desc); + desc->bDescriptorSubtype = UVC_VS_FORMAT_UNCOMPRESSED; + uvc_fourcc_to_guid(desc->guidFormat, cap->pixelformat); + desc->bBitsPerPixel = video_bits_per_pixel(cap->pixelformat); + desc->bDefaultFrameIndex = 1; + cfg->desc->if1_hdr.bNumFormats++; + cfg->desc->if1_hdr.wTotalLength += desc->bLength; + *format_desc = (struct uvc_format_descriptor *)desc; + } + + __ASSERT_NO_MSG(*format_desc != NULL); + + return 0; +} + +static int uvc_compare_frmival_desc(const void *const a, const void *const b) +{ + uint32_t ia, ib; + + /* Copy in case a and b are not 32-bit aligned */ + memcpy(&ia, a, sizeof(uint32_t)); + memcpy(&ib, b, sizeof(uint32_t)); + + return ib - ia; +} + +static void uvc_set_vs_bitrate_range(struct uvc_frame_discrete_descriptor *const desc, + const uint64_t frmival_nsec, struct video_format *const fmt) +{ + uint32_t bitrate_min = sys_le32_to_cpu(desc->dwMinBitRate); + uint32_t bitrate_max = sys_le32_to_cpu(desc->dwMaxBitRate); + uint32_t bitrate; + + /* Multiplication/division in this order to avoid overflow */ + bitrate = MAX(fmt->pitch, fmt->width) * frmival_nsec / (NSEC_PER_SEC / 100) * fmt->height; + + /* Extend the min/max value to include the bitrate of this format */ + bitrate_min = MIN(bitrate_min, bitrate); + bitrate_max = MAX(bitrate_max, bitrate); + + if (bitrate_min > bitrate_max) { + LOG_WRN("The minimum bitrate is above the maximum bitrate"); + } + + if (bitrate_max == 0) { + LOG_WRN("Maximum bitrate is zero"); + } + + desc->dwMinBitRate = sys_cpu_to_le32(bitrate_min); + desc->dwMaxBitRate = sys_cpu_to_le32(bitrate_max); +} + +static int uvc_add_vs_frame_interval(struct uvc_frame_discrete_descriptor *const desc, + const struct video_frmival *const frmival, + struct video_format *const fmt) +{ + int i = desc->bFrameIntervalType; + + if (i >= CONFIG_USBD_VIDEO_MAX_FRMIVAL) { + LOG_WRN("Out of frame interval fields"); + return -ENOSPC; + } + + desc->dwFrameInterval[i] = sys_cpu_to_le32(video_frmival_nsec(frmival) / 100); + desc->bFrameIntervalType++; + desc->bLength += sizeof(uint32_t); + + uvc_set_vs_bitrate_range(desc, video_frmival_nsec(frmival), fmt); + + return 0; +} + +static int uvc_add_vs_frame_desc(const struct device *dev, + struct uvc_format_descriptor *const format_desc, + const struct video_format_cap *const cap, const bool min) +{ + const struct uvc_config *cfg = dev->config; + struct uvc_data *data = dev->data; + struct uvc_frame_discrete_descriptor *desc; + uint16_t w = min ? cap->width_min : cap->width_max; + uint16_t h = min ? cap->height_min : cap->height_max; + uint16_t p = MAX(video_bits_per_pixel(cap->pixelformat), 8) * w / BITS_PER_BYTE; + struct video_format fmt = {.pixelformat = cap->pixelformat, + .width = w, .height = h, .pitch = p}; + struct video_frmival_enum fie = {.format = &fmt}; + uint32_t max_size = MAX(p, w) * h; + + __ASSERT_NO_MSG(data->video_dev != NULL); + __ASSERT_NO_MSG(format_desc != NULL); + + LOG_INF("Adding frame descriptor #%u for %ux%u", + format_desc->bNumFrameDescriptors + 1, w, h); + + desc = &uvc_new_fmt_desc(dev)->frm_disc; + if (desc == NULL) { + return -ENOMEM; + } + + desc->bLength = sizeof(*desc) - CONFIG_USBD_VIDEO_MAX_FRMIVAL * sizeof(uint32_t); + desc->bDescriptorType = USB_DESC_CS_INTERFACE; + desc->bFrameIndex = format_desc->bNumFrameDescriptors + 1; + desc->wWidth = sys_cpu_to_le16(w); + desc->wHeight = sys_cpu_to_le16(h); + desc->dwMaxVideoFrameBufferSize = sys_cpu_to_le32(max_size); + desc->bDescriptorSubtype = (format_desc->bDescriptorSubtype == UVC_VS_FORMAT_UNCOMPRESSED) + ? UVC_VS_FRAME_UNCOMPRESSED : UVC_VS_FRAME_MJPEG; + desc->dwMinBitRate = sys_cpu_to_le32(UINT32_MAX); + desc->dwMaxBitRate = sys_cpu_to_le32(0); + + /* Add the adwFrameInterval fields at the end of this descriptor */ + while (video_enum_frmival(data->video_dev, &fie) == 0) { + switch (fie.type) { + case VIDEO_FRMIVAL_TYPE_DISCRETE: + LOG_DBG("Adding discrete frame interval %u", fie.index); + uvc_add_vs_frame_interval(desc, &fie.discrete, &fmt); + break; + case VIDEO_FRMIVAL_TYPE_STEPWISE: + LOG_DBG("Adding stepwise frame interval %u", fie.index); + uvc_add_vs_frame_interval(desc, &fie.stepwise.min, &fmt); + uvc_add_vs_frame_interval(desc, &fie.stepwise.max, &fmt); + break; + default: + CODE_UNREACHABLE; + } + fie.index++; + } + + /* If no frame intrval supported, default to 30 FPS */ + if (desc->bFrameIntervalType == 0) { + struct video_frmival frmival = {.numerator = 1, .denominator = 30}; + + uvc_add_vs_frame_interval(desc, &frmival, &fmt); + } + + /* UVC requires the frame intervals to be sorted, but not Zephyr */ + qsort(desc->dwFrameInterval, desc->bFrameIntervalType, + sizeof(*desc->dwFrameInterval), uvc_compare_frmival_desc); + + desc->dwDefaultFrameInterval = desc->dwFrameInterval[0]; + format_desc->bNumFrameDescriptors++; + cfg->desc->if1_hdr.wTotalLength += desc->bLength; + + return 0; +} + +static uint32_t uvc_get_mask(const struct device *video_dev, + const struct uvc_control_map *const list, + const size_t list_sz) +{ + uint32_t mask = 0; + uint32_t ok; + + LOG_DBG("Querying which controls are supported:"); + + for (int i = 0; i < list_sz; i++) { + struct video_ctrl_query cq = {.id = list[i].cid, .dev = video_dev}; + + ok = (video_query_ctrl(&cq) == 0); + + LOG_DBG("%s supports control 0x%02x: %s", + video_dev->name, cq.id, ok ? "yes" : "no"); + + mask |= ok << list[i].bit; + } + + return mask; +} + +static int uvc_init(struct usbd_class_data *const c_data) +{ + const struct device *dev = usbd_class_get_private(c_data); + const struct uvc_config *cfg = dev->config; + struct uvc_data *data = dev->data; + struct uvc_format_descriptor *format_desc = NULL; + struct video_caps caps; + uint32_t prev_pixfmt = 0; + uint32_t mask = 0; + int ret; + + __ASSERT_NO_MSG(data->video_dev != NULL); + + if (atomic_test_bit(&data->state, UVC_STATE_INITIALIZED)) { + LOG_DBG("UVC instance '%s' is already initialized", dev->name); + return 0; + } + + cfg->desc->if0_hdr.baInterfaceNr[0] = cfg->desc->if1.bInterfaceNumber; + + /* Generating VideoControl descriptors (interface 0) */ + + mask = uvc_get_mask(data->video_dev, uvc_control_map_ct, ARRAY_SIZE(uvc_control_map_ct)); + cfg->desc->if0_ct.bmControls[0] = mask >> 0; + cfg->desc->if0_ct.bmControls[1] = mask >> 8; + cfg->desc->if0_ct.bmControls[2] = mask >> 16; + + mask = uvc_get_mask(data->video_dev, uvc_control_map_pu, ARRAY_SIZE(uvc_control_map_pu)); + cfg->desc->if0_pu.bmControls[0] = mask >> 0; + cfg->desc->if0_pu.bmControls[1] = mask >> 8; + cfg->desc->if0_pu.bmControls[2] = mask >> 16; + + mask = uvc_get_mask(data->video_dev, uvc_control_map_xu, ARRAY_SIZE(uvc_control_map_xu)); + cfg->desc->if0_xu.bmControls[0] = mask >> 0; + cfg->desc->if0_xu.bmControls[1] = mask >> 8; + cfg->desc->if0_xu.bmControls[2] = mask >> 16; + cfg->desc->if0_xu.bmControls[3] = mask >> 24; + + /* Generating VideoStreaming descriptors (interface 1) */ + + caps.type = VIDEO_BUF_TYPE_OUTPUT; + + ret = video_get_caps(data->video_dev, &caps); + if (ret != 0) { + LOG_ERR("Could not load %s video format list", data->video_dev->name); + return ret; + } + + cfg->desc->if1_hdr.wTotalLength = sys_le16_to_cpu(cfg->desc->if1_hdr.wTotalLength); + + for (int i = 0; caps.format_caps[i].pixelformat != 0; i++) { + const struct video_format_cap *cap = &caps.format_caps[i]; + + if (prev_pixfmt != cap->pixelformat) { + if (prev_pixfmt != 0) { + cfg->desc->if1_hdr.wTotalLength += cfg->desc->if1_color.bLength; + uvc_assign_desc(dev, &cfg->desc->if1_color, true, true); + } + + ret = uvc_add_vs_format_desc(dev, &format_desc, cap); + if (ret != 0) { + return ret; + } + } + + ret = uvc_add_vs_frame_desc(dev, format_desc, cap, true); + if (ret != 0) { + return ret; + } + + if (cap->width_min != cap->width_max || cap->height_min != cap->height_max) { + ret = uvc_add_vs_frame_desc(dev, format_desc, cap, false); + if (ret != 0) { + return ret; + } + } + + prev_pixfmt = cap->pixelformat; + } + + cfg->desc->if1_hdr.wTotalLength += cfg->desc->if1_color.bLength; + uvc_assign_desc(dev, &cfg->desc->if1_color, true, true); + uvc_assign_desc(dev, &cfg->desc->if1_ep_fs, true, false); + uvc_assign_desc(dev, &cfg->desc->if1_ep_hs, false, true); + + cfg->desc->if1_hdr.wTotalLength = sys_cpu_to_le16(cfg->desc->if1_hdr.wTotalLength); + + /* Generating the default probe message now that descriptors are complete */ + + ret = uvc_get_vs_probe_struct(dev, &data->default_probe, UVC_GET_CUR); + if (ret != 0) { + LOG_ERR("init: failed to query the default probe"); + return ret; + } + + atomic_set_bit(&data->state, UVC_STATE_INITIALIZED); + + return 0; +} + +/* UVC data handling */ + +static int uvc_request(struct usbd_class_data *const c_data, struct net_buf *const buf, + const int err) +{ + const struct device *dev = usbd_class_get_private(c_data); + struct uvc_buf_info bi = *(struct uvc_buf_info *)udc_get_buf_info(buf); + struct uvc_data *data = dev->data; + + net_buf_unref(buf); + + if (bi.udc.ep == uvc_get_bulk_in(dev)) { + LOG_DBG("Request completed for USB buffer %p, video buffer %p", buf, bi.vbuf); + if (bi.vbuf != NULL) { + k_fifo_put(&data->fifo_out, bi.vbuf); + + if (IS_ENABLED(CONFIG_POLL) && data->video_sig != NULL) { + LOG_DBG("Raising VIDEO_BUF_DONE signal"); + k_poll_signal_raise(data->video_sig, VIDEO_BUF_DONE); + } + } + + /* There is now one more net_buf buffer available */ + uvc_flush_queue(dev); + } else { + LOG_WRN("Request on unknown endpoint 0x%02x", bi.udc.ep); + } + + return 0; +} + +/* + * Handling the start of USB transfers marked by 'v' below: + * v v + * [hdr:data:::][data::::::::::::::::::::] [hdr:data:::][data::::::::::::::::::::] ... + * [vbuf::::::::::::::::::::::::::::] [vbuf::::::::::::::::::::::::::::] ... + */ +static struct net_buf *uvc_initiate_transfer(const struct device *dev, + struct video_buffer *const vbuf, + size_t *const next_line_offset, + size_t *const next_vbuf_offset) +{ + const struct uvc_config *cfg = dev->config; + struct uvc_data *data = dev->data; + struct video_format *fmt = &data->video_fmt; + size_t mps = uvc_get_bulk_mps(cfg->c_data); + struct net_buf *buf; + + buf = net_buf_alloc_len(&uvc_buf_pool, mps, K_NO_WAIT); + if (buf == NULL) { + LOG_DBG("Cannot allocate first USB buffer for now"); + return NULL; + } + + /* If uncompressed and line-based format, update the next position in the frame */ + if (fmt->pitch > 0) { + *next_line_offset = vbuf->line_offset + vbuf->bytesused / fmt->pitch; + } + + LOG_INF("Start of transfer, bytes used %u, sending lines %u to %u out of %u", + vbuf->bytesused, vbuf->line_offset, vbuf->line_offset, fmt->height); + + /* Copy the header into the buffer */ + net_buf_add_mem(buf, &data->payload_header, data->payload_header.bHeaderLength); + + if (vbuf->bytesused <= net_buf_tailroom(buf)) { + /* Very short video buffer fitting in the first packet */ + *next_vbuf_offset = vbuf->bytesused; + } else { + /* Pad the USB buffer until the next video buffer pointer is aligned for UDC */ + while (!IS_UDC_ALIGNED((uintptr_t)&vbuf->buffer[net_buf_tailroom(buf)])) { + net_buf_add_u8(buf, 0); + ((struct uvc_payload_header *)buf->data)->bHeaderLength++; + } + + *next_vbuf_offset = net_buf_tailroom(buf); + } + + net_buf_add_mem(buf, vbuf->buffer, *next_vbuf_offset); + + /* If this new USB transfer will complete this frame */ + if (fmt->pitch == 0 || *next_line_offset >= fmt->height) { + LOG_DBG("Last USB transfer for this buffer"); + + /* Flag that this current transfer is the last */ + ((struct uvc_payload_header *)buf->data)->bmHeaderInfo |= + UVC_BMHEADERINFO_END_OF_FRAME; + + /* Toggle the Frame ID of the next vbuf */ + data->payload_header.bmHeaderInfo ^= UVC_BMHEADERINFO_FRAMEID; + + *next_line_offset = 0; + } + + return buf; +} + +/* + * Handling the continuation of USB transfers marked by 'v' below: + * v v + * [hdr:data:::][data::::::::::::::::::::] [hdr:data:::][data::::::::::::::::::::] ... + * [vbuf::::::::::::::::::::::::::::] [vbuf::::::::::::::::::::::::::::] ... + */ +static struct net_buf *uvc_continue_transfer(const struct device *dev, + struct video_buffer *const vbuf, + size_t *const next_line_offset, + size_t *const next_vbuf_offset) +{ + struct uvc_data *data = dev->data; + struct video_format *fmt = &data->video_fmt; + struct net_buf *buf; + /* Workaround net_buf that uses uint16_t storage for lengths and offsets */ + const size_t max_len = 0xf000; + const size_t buf_len = MIN(max_len, vbuf->bytesused - data->vbuf_offset); + + /* Directly pass the vbuf content with zero-copy */ + buf = net_buf_alloc_with_data(&uvc_buf_pool, vbuf->buffer + data->vbuf_offset, + buf_len, K_NO_WAIT); + if (buf == NULL) { + LOG_DBG("Cannot allocate continuation USB buffer for now"); + return NULL; + } + + /* If uncompressed and line-based format, update the next line position in the frame */ + if (fmt->pitch > 0) { + *next_line_offset = vbuf->line_offset + buf->len / fmt->pitch; + } + + /* The entire video buffer is now submitted */ + *next_vbuf_offset = data->vbuf_offset + buf_len; + + return buf; +} + +static int uvc_reset_transfer(const struct device *dev) +{ + const struct uvc_config *cfg = dev->config; + struct uvc_data *data = dev->data; + struct uvc_buf_info *bi; + struct net_buf *buf; + int ret; + + LOG_DBG("Stream restarted, terminating the transfer after %u bytes", data->vbuf_offset); + + buf = net_buf_alloc_len(&uvc_buf_pool, 0, K_NO_WAIT); + if (buf == NULL) { + LOG_DBG("Cannot allocate ZLP USB buffer for now"); + return -ENOMEM; + } + + bi = (struct uvc_buf_info *)udc_get_buf_info(buf); + bi->udc.ep = uvc_get_bulk_in(dev); + bi->vbuf = NULL; + data->vbuf_offset = 0; + + ret = usbd_ep_enqueue(cfg->c_data, buf); + if (ret != 0) { + net_buf_unref(buf); + return ret; + } + + atomic_clear_bit(&data->state, UVC_STATE_STREAM_RESTART); + + return 0; +} + +/* + * The queue of video frame fragments (vbuf) is processed, each fragment (data) + * is prepended by the UVC header (h). The result is cut into USB packets (pkt) + * submitted to the USB. One vbuf per USB transfer + * + * [hdr:data:::][data::::::::::::::::::::] [hdr:data:::][data::::::::::::::::::::] ... + * [vbuf::::::::::::::::::::::::::::] [vbuf::::::::::::::::::::::::::::] ... + * + * @retval 0 if vbuf was partially transferred. + * @retval 1 if vbuf was fully transferred and can be released. + * @return Negative error code on failure. + */ +static int uvc_flush_vbuf(const struct device *dev, struct video_buffer *const vbuf) +{ + const struct uvc_config *cfg = dev->config; + struct uvc_data *data = dev->data; + size_t next_vbuf_offset = data->vbuf_offset; + size_t next_line_offset = vbuf->line_offset; + struct net_buf *buf; + struct uvc_buf_info *bi; + int ret; + + if (atomic_test_bit(&data->state, UVC_STATE_STREAM_RESTART)) { + return uvc_reset_transfer(dev); + } + + if (data->vbuf_offset == 0) { + buf = uvc_initiate_transfer(dev, vbuf, &next_line_offset, &next_vbuf_offset); + } else { + buf = uvc_continue_transfer(dev, vbuf, &next_line_offset, &next_vbuf_offset); + } + if (buf == NULL) { + return -ENOMEM; + } + + bi = (struct uvc_buf_info *)udc_get_buf_info(buf); + bi->udc.ep = uvc_get_bulk_in(dev); + + LOG_DBG("Video buffer %p, offset %u/%u, size %u", + vbuf, data->vbuf_offset, vbuf->bytesused, buf->len); + + /* End-of-Transfer condition */ + if (next_vbuf_offset == vbuf->bytesused) { + bi->vbuf = vbuf; + bi->udc.zlp = (buf->len % uvc_get_bulk_mps(cfg->c_data) == 0); + } + + ret = usbd_ep_enqueue(cfg->c_data, buf); + if (ret != 0) { + net_buf_unref(buf); + return ret; + } + + data->vbuf_offset = next_vbuf_offset; + vbuf->line_offset = next_line_offset; + + /* End-of-Transfer condition */ + if (next_vbuf_offset == vbuf->bytesused) { + data->vbuf_offset = 0; + return UVC_VBUF_DONE; + } + + return 0; +} + +static void uvc_flush_queue(const struct device *dev) +{ + struct uvc_data *data = dev->data; + struct video_buffer *vbuf; + int ret; + + __ASSERT_NO_MSG(atomic_test_bit(&data->state, UVC_STATE_INITIALIZED)); + __ASSERT_NO_MSG(!k_is_in_isr()); + + if (!atomic_test_bit(&data->state, UVC_STATE_ENABLED) || + !atomic_test_bit(&data->state, UVC_STATE_STREAM_READY)) { + LOG_DBG("UVC not ready yet"); + return; + } + + /* Lock the access to the FIFO to make sure to only process one buffer at a time. + * K_FOREVER is not expected to take long, as uvc_flush_vbuf() never blocks. + */ + LOG_DBG("Locking the UVC stream"); + k_mutex_lock(&data->mutex, K_FOREVER); + + while ((vbuf = k_fifo_peek_head(&data->fifo_in)) != NULL) { + /* Pausing the UVC driver will accumulate buffers in the input queue */ + if (atomic_test_bit(&data->state, UVC_STATE_PAUSED)) { + break; + } + + ret = uvc_flush_vbuf(dev, vbuf); + if (ret < 0) { + LOG_DBG("Could not transfer video buffer %p for now", vbuf); + break; + } + if (ret == UVC_VBUF_DONE) { + LOG_DBG("Video buffer %p transferred, removing from the queue", vbuf); + k_fifo_get(&data->fifo_in, K_NO_WAIT); + } + } + + /* Now the other contexts calling this function can access the fifo safely. */ + LOG_DBG("Unlocking the UVC stream"); + k_mutex_unlock(&data->mutex); +} + +static void uvc_enable(struct usbd_class_data *const c_data) +{ + const struct device *dev = usbd_class_get_private(c_data); + struct uvc_data *data = dev->data; + + atomic_set_bit(&data->state, UVC_STATE_ENABLED); + + /* Catch-up with buffers that might have been delayed */ + uvc_flush_queue(dev); +} + +static void uvc_disable(struct usbd_class_data *const c_data) +{ + const struct device *dev = usbd_class_get_private(c_data); + struct uvc_data *data = dev->data; + + __ASSERT_NO_MSG(atomic_test_bit(&data->state, UVC_STATE_INITIALIZED)); + + atomic_clear_bit(&data->state, UVC_STATE_ENABLED); +} + +static void uvc_update(struct usbd_class_data *const c_data, const uint8_t iface, + const uint8_t alternate) +{ + LOG_DBG("Select alternate %u for interface %u", alternate, iface); +} + +static const struct usbd_class_api uvc_class_api = { + .enable = uvc_enable, + .disable = uvc_disable, + .request = uvc_request, + .update = uvc_update, + .control_to_host = uvc_control_to_host, + .control_to_dev = uvc_control_to_dev, + .init = uvc_init, + .get_desc = uvc_get_desc, +}; + +/* UVC video API */ + +static int uvc_enqueue(const struct device *dev, struct video_buffer *const vbuf) +{ + struct uvc_data *data = dev->data; + + k_fifo_put(&data->fifo_in, vbuf); + uvc_flush_queue(dev); + + return 0; +} + +static int uvc_dequeue(const struct device *dev, struct video_buffer **const vbuf, + const k_timeout_t timeout) +{ + struct uvc_data *data = dev->data; + + *vbuf = k_fifo_get(&data->fifo_out, timeout); + if (*vbuf == NULL) { + return -EAGAIN; + } + + return 0; +} + +static int uvc_get_format(const struct device *dev, struct video_format *const fmt) +{ + struct uvc_data *data = dev->data; + struct video_format tmp_fmt = {0}; + int ret; + + __ASSERT_NO_MSG(data->video_dev != NULL); + + if (!atomic_test_bit(&data->state, UVC_STATE_ENABLED) || + !atomic_test_bit(&data->state, UVC_STATE_STREAM_READY)) { + return -EAGAIN; + } + + LOG_DBG("Querying the format from %s", data->video_dev->name); + + tmp_fmt.type = VIDEO_BUF_TYPE_OUTPUT; + + ret = video_get_format(data->video_dev, &tmp_fmt); + if (ret != 0) { + return ret; + } + + *fmt = tmp_fmt; + + return 0; +} + +static int uvc_set_stream(const struct device *dev, const bool enable, + const enum video_buf_type type) +{ + struct uvc_data *data = dev->data; + + if (enable) { + atomic_clear_bit(&data->state, UVC_STATE_PAUSED); + uvc_flush_queue(dev); + } else { + atomic_set_bit(&data->state, UVC_STATE_PAUSED); + } + + return 0; +} + +#ifdef CONFIG_POLL +static int uvc_set_signal(const struct device *dev, struct k_poll_signal *const sig) +{ + struct uvc_data *data = dev->data; + + data->video_sig = sig; + + return 0; +} +#endif + +static DEVICE_API(video, uvc_video_api) = { + .get_format = uvc_get_format, + .set_stream = uvc_set_stream, + .enqueue = uvc_enqueue, + .dequeue = uvc_dequeue, +#if CONFIG_POLL + .set_signal = uvc_set_signal, +#endif +}; + +static int uvc_preinit(const struct device *dev) +{ + struct uvc_data *data = dev->data; + + __ASSERT_NO_MSG(dev->config != NULL); + + data->payload_header.bHeaderLength = 2; + data->format_id = 1; + data->frame_id = 1; + + k_fifo_init(&data->fifo_in); + k_fifo_init(&data->fifo_out); + k_mutex_init(&data->mutex); + + return 0; +} + +#define UVC_DEFINE_DESCRIPTOR(n) \ +static struct uvc_desc uvc_desc_##n = { \ + .iad = { \ + .bLength = sizeof(struct usb_association_descriptor), \ + .bDescriptorType = USB_DESC_INTERFACE_ASSOC, \ + .bFirstInterface = 0, \ + .bInterfaceCount = 2, \ + .bFunctionClass = USB_BCC_VIDEO, \ + .bFunctionSubClass = UVC_SC_VIDEO_INTERFACE_COLLECTION, \ + .bFunctionProtocol = 0, \ + .iFunction = 0, \ + }, \ + \ + .if0 = { \ + .bLength = sizeof(struct usb_if_descriptor), \ + .bDescriptorType = USB_DESC_INTERFACE, \ + .bInterfaceNumber = 0, \ + .bAlternateSetting = 0, \ + .bNumEndpoints = 0, \ + .bInterfaceClass = USB_BCC_VIDEO, \ + .bInterfaceSubClass = UVC_SC_VIDEOCONTROL, \ + .bInterfaceProtocol = 0, \ + .iInterface = 0, \ + }, \ + \ + .if0_hdr = { \ + .bLength = sizeof(struct uvc_control_header_descriptor), \ + .bDescriptorType = USB_DESC_CS_INTERFACE, \ + .bDescriptorSubtype = UVC_VC_HEADER, \ + .bcdUVC = sys_cpu_to_le16(0x0150), \ + .wTotalLength = sys_cpu_to_le16( \ + sizeof(struct uvc_control_header_descriptor) + \ + sizeof(struct uvc_camera_terminal_descriptor) + \ + sizeof(struct uvc_selector_unit_descriptor) + \ + sizeof(struct uvc_processing_unit_descriptor) + \ + sizeof(struct uvc_extension_unit_descriptor) + \ + sizeof(struct uvc_output_terminal_descriptor)), \ + .dwClockFrequency = sys_cpu_to_le32(30000000), \ + .bInCollection = 1, \ + .baInterfaceNr = {0}, \ + }, \ + \ + .if0_ct = { \ + .bLength = sizeof(struct uvc_camera_terminal_descriptor), \ + .bDescriptorType = USB_DESC_CS_INTERFACE, \ + .bDescriptorSubtype = UVC_VC_INPUT_TERMINAL, \ + .bTerminalID = UVC_UNIT_ID_CT, \ + .wTerminalType = sys_cpu_to_le16(UVC_ITT_CAMERA), \ + .bAssocTerminal = 0, \ + .iTerminal = 0, \ + .wObjectiveFocalLengthMin = sys_cpu_to_le16(0), \ + .wObjectiveFocalLengthMax = sys_cpu_to_le16(0), \ + .wOcularFocalLength = sys_cpu_to_le16(0), \ + .bControlSize = 3, \ + .bmControls = {0}, \ + }, \ + \ + .if0_su = { \ + .bLength = sizeof(struct uvc_selector_unit_descriptor), \ + .bDescriptorType = USB_DESC_CS_INTERFACE, \ + .bDescriptorSubtype = UVC_VC_SELECTOR_UNIT, \ + .bUnitID = UVC_UNIT_ID_SU, \ + .bNrInPins = 1, \ + .baSourceID = {UVC_UNIT_ID_CT}, \ + .iSelector = 0, \ + }, \ + \ + .if0_pu = { \ + .bLength = sizeof(struct uvc_processing_unit_descriptor), \ + .bDescriptorType = USB_DESC_CS_INTERFACE, \ + .bDescriptorSubtype = UVC_VC_PROCESSING_UNIT, \ + .bUnitID = UVC_UNIT_ID_PU, \ + .bSourceID = UVC_UNIT_ID_SU, \ + .wMaxMultiplier = sys_cpu_to_le16(0), \ + .bControlSize = 3, \ + .bmControls = {0}, \ + .iProcessing = 0, \ + .bmVideoStandards = 0, \ + }, \ + \ + .if0_xu = { \ + .bLength = sizeof(struct uvc_extension_unit_descriptor), \ + .bDescriptorType = USB_DESC_CS_INTERFACE, \ + .bDescriptorSubtype = UVC_VC_EXTENSION_UNIT, \ + .bUnitID = UVC_UNIT_ID_XU, \ + .guidExtensionCode = {0}, \ + .bNumControls = 0, \ + .bNrInPins = 1, \ + .baSourceID = {UVC_UNIT_ID_PU}, \ + .bControlSize = 4, \ + .bmControls = {0}, \ + .iExtension = 0, \ + }, \ + \ + .if0_ot = { \ + .bLength = sizeof(struct uvc_output_terminal_descriptor), \ + .bDescriptorType = USB_DESC_CS_INTERFACE, \ + .bDescriptorSubtype = UVC_VC_OUTPUT_TERMINAL, \ + .bTerminalID = UVC_UNIT_ID_OT, \ + .wTerminalType = sys_cpu_to_le16(UVC_TT_STREAMING), \ + .bAssocTerminal = UVC_UNIT_ID_CT, \ + .bSourceID = UVC_UNIT_ID_XU, \ + .iTerminal = 0, \ + }, \ + \ + .if1 = { \ + .bLength = sizeof(struct usb_if_descriptor), \ + .bDescriptorType = USB_DESC_INTERFACE, \ + .bInterfaceNumber = 1, \ + .bAlternateSetting = 0, \ + .bNumEndpoints = 1, \ + .bInterfaceClass = USB_BCC_VIDEO, \ + .bInterfaceSubClass = UVC_SC_VIDEOSTREAMING, \ + .bInterfaceProtocol = 0, \ + .iInterface = 0, \ + }, \ + \ + .if1_hdr = { \ + .bLength = sizeof(struct uvc_stream_header_descriptor), \ + .bDescriptorType = USB_DESC_CS_INTERFACE, \ + .bDescriptorSubtype = UVC_VS_INPUT_HEADER, \ + .bNumFormats = 0, \ + .wTotalLength = sys_cpu_to_le16( \ + sizeof(struct uvc_stream_header_descriptor)), \ + .bEndpointAddress = 0x81, \ + .bmInfo = 0, \ + .bTerminalLink = UVC_UNIT_ID_OT, \ + .bStillCaptureMethod = 0, \ + .bTriggerSupport = 0, \ + .bTriggerUsage = 0, \ + .bControlSize = 0, \ + }, \ + \ + .if1_color = { \ + .bLength = sizeof(struct uvc_color_descriptor), \ + .bDescriptorType = USB_DESC_CS_INTERFACE, \ + .bDescriptorSubtype = UVC_VS_COLORFORMAT, \ + .bColorPrimaries = UVC_COLOR_BT709, \ + .bTransferCharacteristics = UVC_COLOR_BT709, \ + .bMatrixCoefficients = UVC_COLOR_BT601, \ + }, \ + \ + .if1_ep_fs = { \ + .bLength = sizeof(struct usb_ep_descriptor), \ + .bDescriptorType = USB_DESC_ENDPOINT, \ + .bEndpointAddress = 0x81, \ + .bmAttributes = USB_EP_TYPE_BULK, \ + .wMaxPacketSize = sys_cpu_to_le16(64), \ + .bInterval = 0, \ + }, \ + \ + .if1_ep_hs = { \ + .bLength = sizeof(struct usb_ep_descriptor), \ + .bDescriptorType = USB_DESC_ENDPOINT, \ + .bEndpointAddress = 0x81, \ + .bmAttributes = USB_EP_TYPE_BULK, \ + .wMaxPacketSize = sys_cpu_to_le16(512), \ + .bInterval = 0, \ + }, \ +}; \ + \ +struct usb_desc_header *uvc_fs_desc_##n[UVC_MAX_FS_DESC] = { \ + (struct usb_desc_header *) &uvc_desc_##n.iad, \ + (struct usb_desc_header *) &uvc_desc_##n.if0, \ + (struct usb_desc_header *) &uvc_desc_##n.if0_hdr, \ + (struct usb_desc_header *) &uvc_desc_##n.if0_ct, \ + (struct usb_desc_header *) &uvc_desc_##n.if0_su, \ + (struct usb_desc_header *) &uvc_desc_##n.if0_pu, \ + (struct usb_desc_header *) &uvc_desc_##n.if0_xu, \ + (struct usb_desc_header *) &uvc_desc_##n.if0_ot, \ + (struct usb_desc_header *) &uvc_desc_##n.if1, \ + (struct usb_desc_header *) &uvc_desc_##n.if1_hdr, \ + /* More pointers are generated here at runtime */ \ + (struct usb_desc_header *) &uvc_desc_##n.if1_ep_fs, \ + (struct usb_desc_header *) NULL, \ +}; \ + \ +struct usb_desc_header *uvc_hs_desc_##n[UVC_MAX_HS_DESC] = { \ + (struct usb_desc_header *) &uvc_desc_##n.iad, \ + (struct usb_desc_header *) &uvc_desc_##n.if0, \ + (struct usb_desc_header *) &uvc_desc_##n.if0_hdr, \ + (struct usb_desc_header *) &uvc_desc_##n.if0_ct, \ + (struct usb_desc_header *) &uvc_desc_##n.if0_su, \ + (struct usb_desc_header *) &uvc_desc_##n.if0_pu, \ + (struct usb_desc_header *) &uvc_desc_##n.if0_xu, \ + (struct usb_desc_header *) &uvc_desc_##n.if0_ot, \ + (struct usb_desc_header *) &uvc_desc_##n.if1, \ + (struct usb_desc_header *) &uvc_desc_##n.if1_hdr, \ + /* More pointers are generated here at runtime */ \ + (struct usb_desc_header *) &uvc_desc_##n.if1_ep_hs, \ + (struct usb_desc_header *) NULL, \ +}; + +#define USBD_VIDEO_DT_DEVICE_DEFINE(n) \ + UVC_DEFINE_DESCRIPTOR(n) \ + \ + USBD_DEFINE_CLASS(uvc_c_data_##n, &uvc_class_api, \ + (void *)DEVICE_DT_INST_GET(n), NULL); \ + \ + const struct uvc_config uvc_cfg_##n = { \ + .c_data = &uvc_c_data_##n, \ + .desc = &uvc_desc_##n, \ + .fs_desc = uvc_fs_desc_##n, \ + .hs_desc = uvc_hs_desc_##n, \ + }; \ + \ + struct uvc_data uvc_data_##n = { \ + .fs_desc_idx = 10, \ + .hs_desc_idx = 10, \ + }; \ + \ + DEVICE_DT_INST_DEFINE(n, uvc_preinit, NULL, &uvc_data_##n, &uvc_cfg_##n,\ + POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, &uvc_video_api); \ + \ + VIDEO_DEVICE_DEFINE(uvc##n, DEVICE_DT_INST_GET(n), NULL); + +DT_INST_FOREACH_STATUS_OKAY(USBD_VIDEO_DT_DEVICE_DEFINE) diff --git a/subsys/usb/device_next/class/usbd_uvc.h b/subsys/usb/device_next/class/usbd_uvc.h new file mode 100644 index 000000000000..3364f83258d1 --- /dev/null +++ b/subsys/usb/device_next/class/usbd_uvc.h @@ -0,0 +1,484 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief USB Video Class private header + * + * Header follows below documentation: + * - USB Device Class Definition for Video Devices (Revision 1.5) + * + * Additional documentation considered a part of UVC 1.5: + * - USB Device Class Definition for Video Devices: Uncompressed Payload (Revision 1.5) + * - USB Device Class Definition for Video Devices: Motion-JPEG Payload (Revision 1.5) + */ + +#ifndef ZEPHYR_INCLUDE_USBD_CLASS_UVC_H_ +#define ZEPHYR_INCLUDE_USBD_CLASS_UVC_H_ + +#include + +/* Video Class-Specific Request Codes */ +#define UVC_SET_CUR 0x01 +#define UVC_GET_CUR 0x81 +#define UVC_GET_MIN 0x82 +#define UVC_GET_MAX 0x83 +#define UVC_GET_RES 0x84 +#define UVC_GET_LEN 0x85 +#define UVC_GET_INFO 0x86 +#define UVC_GET_DEF 0x87 + +/* Flags announcing which controls are supported */ +#define UVC_INFO_SUPPORTS_GET BIT(0) +#define UVC_INFO_SUPPORTS_SET BIT(1) + +/* Request Error Code Control */ +#define UVC_ERR_NOT_READY 0x01 +#define UVC_ERR_WRONG_STATE 0x02 +#define UVC_ERR_OUT_OF_RANGE 0x04 +#define UVC_ERR_INVALID_UNIT 0x05 +#define UVC_ERR_INVALID_CONTROL 0x06 +#define UVC_ERR_INVALID_REQUEST 0x07 +#define UVC_ERR_INVALID_VALUE_WITHIN_RANGE 0x08 +#define UVC_ERR_UNKNOWN 0xff + +/* Video and Still Image Payload Headers */ +#define UVC_BMHEADERINFO_FRAMEID BIT(0) +#define UVC_BMHEADERINFO_END_OF_FRAME BIT(1) +#define UVC_BMHEADERINFO_HAS_PRESENTATIONTIME BIT(2) +#define UVC_BMHEADERINFO_HAS_SOURCECLOCK BIT(3) +#define UVC_BMHEADERINFO_PAYLOAD_SPECIFIC_BIT BIT(4) +#define UVC_BMHEADERINFO_STILL_IMAGE BIT(5) +#define UVC_BMHEADERINFO_ERROR BIT(6) +#define UVC_BMHEADERINFO_END_OF_HEADER BIT(7) + +/* Video Interface Subclass Codes */ +#define UVC_SC_VIDEOCONTROL 0x01 +#define UVC_SC_VIDEOSTREAMING 0x02 +#define UVC_SC_VIDEO_INTERFACE_COLLECTION 0x03 + +/* Video Class-Specific Video Control Interface Descriptor Subtypes */ +#define UVC_VC_DESCRIPTOR_UNDEFINED 0x00 +#define UVC_VC_HEADER 0x01 +#define UVC_VC_INPUT_TERMINAL 0x02 +#define UVC_VC_OUTPUT_TERMINAL 0x03 +#define UVC_VC_SELECTOR_UNIT 0x04 +#define UVC_VC_PROCESSING_UNIT 0x05 +#define UVC_VC_EXTENSION_UNIT 0x06 +#define UVC_VC_ENCODING_UNIT 0x07 + +/* Video Class-Specific Video Stream Interface Descriptor Subtypes */ +#define UVC_VS_UNDEFINED 0x00 +#define UVC_VS_INPUT_HEADER 0x01 +#define UVC_VS_OUTPUT_HEADER 0x02 +#define UVC_VS_STILL_IMAGE_FRAME 0x03 +#define UVC_VS_FORMAT_UNCOMPRESSED 0x04 +#define UVC_VS_FRAME_UNCOMPRESSED 0x05 +#define UVC_VS_FORMAT_MJPEG 0x06 +#define UVC_VS_FRAME_MJPEG 0x07 +#define UVC_VS_FORMAT_MPEG2TS 0x0A +#define UVC_VS_FORMAT_DV 0x0C +#define UVC_VS_COLORFORMAT 0x0D +#define UVC_VS_FORMAT_FRAME_BASED 0x10 +#define UVC_VS_FRAME_FRAME_BASED 0x11 +#define UVC_VS_FORMAT_STREAM_BASED 0x12 +#define UVC_VS_FORMAT_H264 0x13 +#define UVC_VS_FRAME_H264 0x14 +#define UVC_VS_FORMAT_H264_SIMULCAST 0x15 +#define UVC_VS_FORMAT_VP8 0x16 +#define UVC_VS_FRAME_VP8 0x17 +#define UVC_VS_FORMAT_VP8_SIMULCAST 0x18 + +/* Video Class-Specific Endpoint Descriptor Subtypes */ +#define UVC_EP_UNDEFINED 0x00 +#define UVC_EP_GENERAL 0x01 +#define UVC_EP_ENDPOINT 0x02 +#define UVC_EP_INTERRUPT 0x03 + +/* USB Terminal Types */ +#define UVC_TT_VENDOR_SPECIFIC 0x0100 +#define UVC_TT_STREAMING 0x0101 + +/* Input Terminal Types */ +#define UVC_ITT_VENDOR_SPECIFIC 0x0200 +#define UVC_ITT_CAMERA 0x0201 +#define UVC_ITT_MEDIA_TRANSPORT_INPUT 0x0202 + +/* Output Terminal Types */ +#define UVC_OTT_VENDOR_SPECIFIC 0x0300 +#define UVC_OTT_DISPLAY 0x0301 +#define UVC_OTT_MEDIA_TRANSPORT_OUTPUT 0x0302 + +/* External Terminal Types */ +#define UVC_EXT_EXTERNAL_VENDOR_SPECIFIC 0x0400 +#define UVC_EXT_COMPOSITE_CONNECTOR 0x0401 +#define UVC_EXT_SVIDEO_CONNECTOR 0x0402 +#define UVC_EXT_COMPONENT_CONNECTOR 0x0403 + +/* VideoStreaming Interface Controls */ +#define UVC_VS_PROBE_CONTROL 0x01 +#define UVC_VS_COMMIT_CONTROL 0x02 +#define UVC_VS_STILL_PROBE_CONTROL 0x03 +#define UVC_VS_STILL_COMMIT_CONTROL 0x04 +#define UVC_VS_STILL_IMAGE_TRIGGER_CONTROL 0x05 +#define UVC_VS_STREAM_ERROR_CODE_CONTROL 0x06 +#define UVC_VS_GENERATE_KEY_FRAME_CONTROL 0x07 +#define UVC_VS_UPDATE_FRAME_SEGMENT_CONTROL 0x08 +#define UVC_VS_SYNCH_DELAY_CONTROL 0x09 + +/* VideoControl Interface Controls */ +#define UVC_VC_CONTROL_UNDEFINED 0x00 +#define UVC_VC_VIDEO_POWER_MODE_CONTROL 0x01 +#define UVC_VC_REQUEST_ERROR_CODE_CONTROL 0x02 + +/* Selector Unit Controls */ +#define UVC_SU_INPUT_SELECT_CONTROL 0x01 + +/* Camera Terminal Controls */ +#define UVC_CT_SCANNING_MODE_CONTROL 0x01 +#define UVC_CT_AE_MODE_CONTROL 0x02 +#define UVC_CT_AE_PRIORITY_CONTROL 0x03 +#define UVC_CT_EXPOSURE_TIME_ABS_CONTROL 0x04 +#define UVC_CT_EXPOSURE_TIME_REL_CONTROL 0x05 +#define UVC_CT_FOCUS_ABS_CONTROL 0x06 +#define UVC_CT_FOCUS_REL_CONTROL 0x07 +#define UVC_CT_FOCUS_AUTO_CONTROL 0x08 +#define UVC_CT_IRIS_ABS_CONTROL 0x09 +#define UVC_CT_IRIS_REL_CONTROL 0x0A +#define UVC_CT_ZOOM_ABS_CONTROL 0x0B +#define UVC_CT_ZOOM_REL_CONTROL 0x0C +#define UVC_CT_PANTILT_ABS_CONTROL 0x0D +#define UVC_CT_PANTILT_REL_CONTROL 0x0E +#define UVC_CT_ROLL_ABS_CONTROL 0x0F +#define UVC_CT_ROLL_REL_CONTROL 0x10 +#define UVC_CT_PRIVACY_CONTROL 0x11 +#define UVC_CT_FOCUS_SIMPLE_CONTROL 0x12 +#define UVC_CT_WINDOW_CONTROL 0x13 +#define UVC_CT_REGION_OF_INTEREST_CONTROL 0x14 + +/* Processing Unit Controls */ +#define UVC_PU_BACKLIGHT_COMPENSATION_CONTROL 0x01 +#define UVC_PU_BRIGHTNESS_CONTROL 0x02 +#define UVC_PU_CONTRAST_CONTROL 0x03 +#define UVC_PU_GAIN_CONTROL 0x04 +#define UVC_PU_POWER_LINE_FREQUENCY_CONTROL 0x05 +#define UVC_PU_HUE_CONTROL 0x06 +#define UVC_PU_SATURATION_CONTROL 0x07 +#define UVC_PU_SHARPNESS_CONTROL 0x08 +#define UVC_PU_GAMMA_CONTROL 0x09 +#define UVC_PU_WHITE_BALANCE_TEMP_CONTROL 0x0A +#define UVC_PU_WHITE_BALANCE_TEMP_AUTO_CONTROL 0x0B +#define UVC_PU_WHITE_BALANCE_COMPONENT_CONTROL 0x0C +#define UVC_PU_WHITE_BALANCE_COMPONENT_AUTO_CONTROL 0x0D +#define UVC_PU_DIGITAL_MULTIPLIER_CONTROL 0x0E +#define UVC_PU_DIGITAL_MULTIPLIER_LIMIT_CONTROL 0x0F +#define UVC_PU_HUE_AUTO_CONTROL 0x10 +#define UVC_PU_ANALOG_VIDEO_STANDARD_CONTROL 0x11 +#define UVC_PU_ANALOG_LOCK_STATUS_CONTROL 0x12 +#define UVC_PU_CONTRAST_AUTO_CONTROL 0x13 + +/* Encoding Unit Controls */ +#define UVC_EU_SELECT_LAYER_CONTROL 0x01 +#define UVC_EU_PROFILE_TOOLSET_CONTROL 0x02 +#define UVC_EU_VIDEO_RESOLUTION_CONTROL 0x03 +#define UVC_EU_MIN_FRAME_INTERVAL_CONTROL 0x04 +#define UVC_EU_SLICE_MODE_CONTROL 0x05 +#define UVC_EU_RATE_CONTROL_MODE_CONTROL 0x06 +#define UVC_EU_AVERAGE_BITRATE_CONTROL 0x07 +#define UVC_EU_CPB_SIZE_CONTROL 0x08 +#define UVC_EU_PEAK_BIT_RATE_CONTROL 0x09 +#define UVC_EU_QUANTIZATION_PARAMS_CONTROL 0x0A +#define UVC_EU_SYNC_REF_FRAME_CONTROL 0x0B +#define UVC_EU_LTR_BUFFER_CONTROL 0x0C +#define UVC_EU_LTR_PICTURE_CONTROL 0x0D +#define UVC_EU_LTR_VALIDATION_CONTROL 0x0E +#define UVC_EU_LEVEL_IDC_LIMIT_CONTROL 0x0F +#define UVC_EU_SEI_PAYLOADTYPE_CONTROL 0x10 +#define UVC_EU_QP_RANGE_CONTROL 0x11 +#define UVC_EU_PRIORITY_CONTROL 0x12 +#define UVC_EU_START_OR_STOP_LAYER_CONTROL 0x13 +#define UVC_EU_ERROR_RESILIENCY_CONTROL 0x14 + +/* Extension Unit Controls */ +#define UVC_XU_BASE_CONTROL 0x00 + +/* Base GUID string present at the end of most GUID formats, preceded by the FourCC code */ +#define UVC_FORMAT_GUID(fourcc) fourcc "\x00\x00\x10\x00\x80\x00\x00\xaa\x00\x38\x9b\x71" + +struct uvc_if_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; +} __packed; + +struct uvc_control_header_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint16_t bcdUVC; + uint16_t wTotalLength; + uint32_t dwClockFrequency; + uint8_t bInCollection; + uint8_t baInterfaceNr[1]; +} __packed; + +struct uvc_unit_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bUnitID; +}; + +struct uvc_output_terminal_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bTerminalID; + uint16_t wTerminalType; + uint8_t bAssocTerminal; + uint8_t bSourceID; + uint8_t iTerminal; +} __packed; + +struct uvc_camera_terminal_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bTerminalID; + uint16_t wTerminalType; + uint8_t bAssocTerminal; + uint8_t iTerminal; + uint16_t wObjectiveFocalLengthMin; + uint16_t wObjectiveFocalLengthMax; + uint16_t wOcularFocalLength; + uint8_t bControlSize; + uint8_t bmControls[3]; +} __packed; + +struct uvc_selector_unit_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bUnitID; + uint8_t bNrInPins; + uint8_t baSourceID[1]; + uint8_t iSelector; +} __packed; + +struct uvc_processing_unit_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bUnitID; + uint8_t bSourceID; + uint16_t wMaxMultiplier; + uint8_t bControlSize; + uint8_t bmControls[3]; + uint8_t iProcessing; + uint8_t bmVideoStandards; +} __packed; + +struct uvc_encoding_unit_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bUnitID; + uint8_t bSourceID; + uint8_t iEncoding; + uint8_t bControlSize; + uint8_t bmControls[3]; + uint8_t bmControlsRuntime[3]; +} __packed; + +struct uvc_extension_unit_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bUnitID; + uint8_t guidExtensionCode[16]; + uint8_t bNumControls; + uint8_t bNrInPins; + uint8_t baSourceID[1]; + uint8_t bControlSize; + uint8_t bmControls[4]; + uint8_t iExtension; +} __packed; + +struct uvc_stream_header_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bNumFormats; + uint16_t wTotalLength; + uint8_t bEndpointAddress; + uint8_t bmInfo; + uint8_t bTerminalLink; + uint8_t bStillCaptureMethod; + uint8_t bTriggerSupport; + uint8_t bTriggerUsage; + uint8_t bControlSize; +} __packed; + +struct uvc_frame_still_image_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bEndpointAddress; + uint8_t bNumImageSizePatterns; + struct { + uint16_t wWidth; + uint16_t wHeight; + } n[1] __packed; + uint8_t bNumCompressionPattern; + uint8_t bCompression[1]; +} __packed; + +struct uvc_format_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bFormatIndex; + uint8_t bNumFrameDescriptors; + /* Other fields depending on bDescriptorSubtype value */ +} __packed; + +struct uvc_format_uncomp_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bFormatIndex; + uint8_t bNumFrameDescriptors; + uint8_t guidFormat[16]; + uint8_t bBitsPerPixel; + uint8_t bDefaultFrameIndex; + uint8_t bAspectRatioX; + uint8_t bAspectRatioY; + uint8_t bmInterlaceFlags; + uint8_t bCopyProtect; +} __packed; + +struct uvc_format_mjpeg_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bFormatIndex; + uint8_t bNumFrameDescriptors; + uint8_t bmFlags; +#define UVC_MJPEG_FLAGS_FIXEDSIZESAMPLES (1 << 0) + uint8_t bDefaultFrameIndex; + uint8_t bAspectRatioX; + uint8_t bAspectRatioY; + uint8_t bmInterlaceFlags; + uint8_t bCopyProtect; +} __packed; + +struct uvc_frame_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bFrameIndex; + uint8_t bmCapabilities; + uint16_t wWidth; + uint16_t wHeight; + uint32_t dwMinBitRate; + uint32_t dwMaxBitRate; + uint32_t dwMaxVideoFrameBufferSize; + uint32_t dwDefaultFrameInterval; + uint8_t bFrameIntervalType; + /* Other fields depending on bFrameIntervalType value */ +} __packed; + +struct uvc_frame_continuous_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bFrameIndex; + uint8_t bmCapabilities; + uint16_t wWidth; + uint16_t wHeight; + uint32_t dwMinBitRate; + uint32_t dwMaxBitRate; + uint32_t dwMaxVideoFrameBufferSize; + uint32_t dwDefaultFrameInterval; + uint8_t bFrameIntervalType; + uint32_t dwMinFrameInterval; + uint32_t dwMaxFrameInterval; + uint32_t dwFrameIntervalStep; +} __packed; + +struct uvc_frame_discrete_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bFrameIndex; + uint8_t bmCapabilities; + uint16_t wWidth; + uint16_t wHeight; + uint32_t dwMinBitRate; + uint32_t dwMaxBitRate; + uint32_t dwMaxVideoFrameBufferSize; + uint32_t dwDefaultFrameInterval; + uint8_t bFrameIntervalType; + uint32_t dwFrameInterval[CONFIG_USBD_VIDEO_MAX_FRMIVAL]; +} __packed; + +struct uvc_color_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bColorPrimaries; + uint8_t bTransferCharacteristics; + uint8_t bMatrixCoefficients; +#define UVC_COLOR_BT709 1 +#define UVC_COLOR_BT470M 2 +#define UVC_COLOR_BT470BG 3 +#define UVC_COLOR_BT601 4 +#define UVC_COLOR_SMPTE170M 4 +#define UVC_COLOR_SMPTE240M 5 +#define UVC_COLOR_LINEAR 6 +#define UVC_COLOR_SRGB 7 +} __packed; + +struct uvc_probe { + uint16_t bmHint; + uint8_t bFormatIndex; + uint8_t bFrameIndex; + uint32_t dwFrameInterval; + uint16_t wKeyFrameRate; + uint16_t wPFrameRate; + uint16_t wCompQuality; + uint16_t wCompWindowSize; + uint16_t wDelay; + uint32_t dwMaxVideoFrameSize; + uint32_t dwMaxPayloadTransferSize; + uint32_t dwClockFrequency; + uint8_t bmFramingInfo; +#define UVC_BMFRAMING_INFO_FID BIT(0) +#define UVC_BMFRAMING_INFO_EOF BIT(1) +#define UVC_BMFRAMING_INFO_EOS BIT(2) + uint8_t bPreferedVersion; + uint8_t bMinVersion; + uint8_t bMaxVersion; + uint8_t bUsage; + uint8_t bBitDepthLuma; + uint8_t bmSettings; + uint8_t bMaxNumberOfRefFramesPlus1; + uint16_t bmRateControlModes; + uint64_t bmLayoutPerStream; +} __packed; + +/* This is a particular variant of this struct that is used by the Zephyr implementation. Other + * organization of the fields are allowed by the standard. + */ +struct uvc_payload_header { + uint8_t bHeaderLength; + uint8_t bmHeaderInfo; + uint32_t dwPresentationTime; /* optional */ + uint32_t scrSourceClockSTC; /* optional */ + uint16_t scrSourceClockSOF; /* optional */ +} __packed; + +#endif /* ZEPHYR_INCLUDE_USBD_CLASS_UVC_H_ */