diff --git a/Documentation/applications/examples/uvc_cam/index.rst b/Documentation/applications/examples/uvc_cam/index.rst new file mode 100644 index 0000000000000..a6d4eba6eb40b --- /dev/null +++ b/Documentation/applications/examples/uvc_cam/index.rst @@ -0,0 +1,72 @@ +============================================= +``uvc_cam`` UVC Camera streaming application +============================================= + +Captures frames from a V4L2 camera sensor and streams them to a USB host +via the UVC (USB Video Class) gadget driver (``/dev/uvc0``). + +The application queries the sensor's native pixel format, resolution and +frame rate via V4L2, then continuously captures frames and writes them to +the UVC device node. + +Dependencies +========================== + +- :code:`CONFIG_USBUVC=y` – UVC gadget driver +- :code:`CONFIG_VIDEO=y` – Video subsystem +- :code:`CONFIG_BOARDCTL_USBDEVCTRL=y` – USB device control via boardctl + +Configuration +========================== + +- :code:`CONFIG_EXAMPLES_UVC_CAM=y` – Enable the UVC camera example +- :code:`CONFIG_EXAMPLES_UVC_CAM_NFRAMES` – Number of frames to stream (0 = infinite, default) +- :code:`CONFIG_EXAMPLES_UVC_CAM_PRIORITY` – Task priority (default 100) +- :code:`CONFIG_EXAMPLES_UVC_CAM_STACKSIZE` – Stack size (default 4096) + +Supported Pixel Formats +========================== + +- ``YUYV`` (2 bytes per pixel) +- ``RGB565`` (2 bytes per pixel) +- ``RGB24`` (3 bytes per pixel) + +Usage +========================== + +.. code-block:: bash + + uvc_cam [nframes] [video_dev] [uvc_dev] + +- ``nframes`` – Number of frames to capture (default from Kconfig, 0 = infinite) +- ``video_dev`` – V4L2 camera device path (default ``/dev/video0``) +- ``uvc_dev`` – UVC gadget device path (default ``/dev/uvc0``) + +Examples +========================== + +- Stream indefinitely with defaults: :code:`uvc_cam` +- Stream 100 frames: :code:`uvc_cam 100` +- Use a different camera device: :code:`uvc_cam 0 /dev/video1` +- Specify both camera and UVC device: :code:`uvc_cam 0 /dev/video0 /dev/uvc1` + +Board Support +========================== + +The ``lckfb-szpi-esp32s3`` board provides a ``uvc`` configuration with +camera and UVC gadget driver pre-enabled:: + + $ ./tools/configure.sh lckfb-szpi-esp32s3:uvc + +This configuration enables the ESP32-S3 camera interface (GC0308 sensor), +USB OTG and the UVC gadget driver. The ``uvc_cam`` example application is +not enabled by default and must be turned on manually:: + + $ kconfig-tweak --enable CONFIG_EXAMPLES_UVC_CAM + +Or via ``make menuconfig``: +:menuselection:`Application Configuration --> Examples --> UVC Camera streaming example` + +After flashing, run the application manually from the NSH shell:: + + nsh> uvc_cam diff --git a/Documentation/components/drivers/special/index.rst b/Documentation/components/drivers/special/index.rst index 919362b895033..1444d74eb9b04 100644 --- a/Documentation/components/drivers/special/index.rst +++ b/Documentation/components/drivers/special/index.rst @@ -44,6 +44,7 @@ following section. syslog.rst sdio.rst usbdev.rst + uvc.rst usbhost.rst usbmisc.rst usbmonitor.rst diff --git a/Documentation/components/drivers/special/uvc.rst b/Documentation/components/drivers/special/uvc.rst new file mode 100644 index 0000000000000..9c39a1aad9d39 --- /dev/null +++ b/Documentation/components/drivers/special/uvc.rst @@ -0,0 +1,127 @@ +====================================== +USB Video Class (UVC) Gadget Driver +====================================== + +Overview +======== + +The UVC gadget driver (``drivers/usbdev/uvc.c``) implements a USB Video Class +1.1 device that makes NuttX appear as a USB webcam to the host. The driver +exposes a character device (``/dev/uvc0``) that applications write video frames +to. It handles all UVC class-specific control requests (PROBE / COMMIT) +internally and uses bulk transfers for video data. + +The driver supports two modes: + +- **Standalone** – the UVC function is the only USB class on the bus. +- **Composite** – the UVC function is combined with other USB class drivers + (e.g. CDC/ACM) via the NuttX composite device framework. + +Features +======== + +- UVC 1.1 compliant (uncompressed YUY2 format) +- Bulk transfer mode for video data +- Automatic PROBE / COMMIT negotiation with the host +- ``poll()`` support – applications can wait for the host to start streaming + (``POLLOUT``) and detect disconnection (``POLLHUP``) +- Runtime video parameters – resolution and frame rate are passed at + initialization time, so USB descriptors always match the actual sensor +- ``boardctl()`` integration for easy application-level bring-up + +Configuration +============= + +The driver is enabled through the following Kconfig options: + +.. code-block:: kconfig + + CONFIG_USBUVC=y # Enable UVC gadget support + CONFIG_USBUVC_COMPOSITE=n # Set y for composite device mode + CONFIG_USBUVC_EPBULKIN=1 # Bulk IN endpoint number (standalone) + CONFIG_USBUVC_EP0MAXPACKET=64 # EP0 max packet size (standalone) + CONFIG_USBUVC_EPBULKIN_FSSIZE=64 # Bulk IN full-speed max packet size + CONFIG_USBUVC_NWRREQS=4 # Number of pre-allocated write requests + CONFIG_USBUVC_NPOLLWAITERS=2 # Number of poll waiters + +Header Files +============ + +- ``include/nuttx/usb/uvc.h`` – Public API, UVC descriptor constants, and + data structures. + +Data Structures +=============== + +``struct uvc_params_s`` +----------------------- + +Passed to the initialization function so that USB descriptors reflect the +actual sensor capabilities:: + + struct uvc_params_s + { + uint16_t width; /* Frame width in pixels */ + uint16_t height; /* Frame height in pixels */ + uint8_t fps; /* Frames per second */ + }; + +Public Interfaces +================= + +Standalone Mode +--------------- + +``usbdev_uvc_initialize()`` + Initialize the UVC gadget and register ``/dev/uvc0``. *params* may be + ``NULL`` to use defaults (320 × 240 @ 5 fps). Returns a handle for later + ``usbdev_uvc_uninitialize()``. + +``usbdev_uvc_uninitialize()`` + Tear down the UVC gadget and unregister the character device. + +Composite Mode +-------------- + +``usbdev_uvc_classobject()`` + Create a UVC class driver instance for use inside a composite device. + +``usbdev_uvc_classuninitialize()`` + Destroy a class driver instance created by ``usbdev_uvc_classobject()``. + +``usbdev_uvc_get_composite_devdesc()`` + Fill a ``composite_devdesc_s`` for the composite framework. + +boardctl Integration +-------------------- + +Applications can manage the UVC gadget through ``boardctl()``:: + + struct boardioc_usbdev_ctrl_s ctrl; + struct uvc_params_s params = { .width = 320, .height = 240, .fps = 15 }; + FAR void *handle = (FAR void *)¶ms; + + ctrl.usbdev = BOARDIOC_USBDEV_UVC; + ctrl.action = BOARDIOC_USBDEV_CONNECT; + ctrl.instance = 0; + ctrl.config = 0; + ctrl.handle = &handle; + + boardctl(BOARDIOC_USBDEV_CONTROL, (uintptr_t)&ctrl); + + /* ... use /dev/uvc0 ... */ + + ctrl.action = BOARDIOC_USBDEV_DISCONNECT; + boardctl(BOARDIOC_USBDEV_CONTROL, (uintptr_t)&ctrl); + +Device Operation +================ + +1. The application opens ``/dev/uvc0`` for writing. +2. Use ``poll()`` with ``POLLOUT`` to wait for the USB host to start streaming + (i.e. the host sends a VS_COMMIT_CONTROL SET_CUR request). +3. Write complete video frames via ``write()``. The driver prepends a 2-byte + UVC payload header (with FID / EOF bits) automatically. +4. When the host stops streaming, ``write()`` returns ``-EAGAIN``. The + application can ``poll()`` again to wait for the host to restart. +5. ``POLLHUP`` is reported when the host explicitly stops streaming. diff --git a/Documentation/platforms/xtensa/esp32s3/boards/lckfb-szpi-esp32s3/index.rst b/Documentation/platforms/xtensa/esp32s3/boards/lckfb-szpi-esp32s3/index.rst index 3a28c65433bc2..70a492c12866c 100644 --- a/Documentation/platforms/xtensa/esp32s3/boards/lckfb-szpi-esp32s3/index.rst +++ b/Documentation/platforms/xtensa/esp32s3/boards/lckfb-szpi-esp32s3/index.rst @@ -576,3 +576,50 @@ Then use the camera example to capture a frame:: $ adb -s 1234 shell nsh> camera + +uvc +--- + +USB Video Class (UVC) webcam configuration. Streams YUYV frames from the +GC0308 DVP camera to a USB host via the UVC gadget driver (Bulk transport). +The application queries the sensor resolution at runtime and configures the +UVC descriptors accordingly. Console is accessible over UART0 (serial). + +The UVC driver also supports composite USB device mode +(``CONFIG_USBUVC_COMPOSITE``), allowing it to be combined with other USB +class drivers (e.g., CDC/ACM) in a single composite device. + +You can run the configuration and compilation procedure:: + + $ ./tools/configure.sh lckfb-szpi-esp32s3:uvc + $ make flash -j$(nproc) ESPTOOL_PORT=/dev/ttyUSB0 + +The application should work as follows: + +1. Create ``/dev/video0`` via ``capture_initialize()`` and open it. +2. Query the sensor's native resolution with ``VIDIOC_ENUM_FRAMESIZES`` + (pixel format ``V4L2_PIX_FMT_YUYV``), then ``VIDIOC_S_FMT`` to configure. +3. Fill ``struct uvc_params_s`` with the queried width, height and fps, + pass it to ``boardctl(BOARDIOC_USBDEV_CONTROL)`` via ``ctrl.handle`` + so the UVC gadget builds USB descriptors matching the actual sensor. +4. Open ``/dev/uvc0``, use ``poll()`` with ``POLLOUT`` to wait for the + USB host to start streaming. +5. Loop: ``VIDIOC_QBUF`` / ``VIDIOC_DQBUF`` to capture a YUYV frame, + then ``write()`` to ``/dev/uvc0``. + +On the host side, verify the device is recognized:: + + $ sudo dmesg + [32982831.662622] usb 1-9.3.3: new full-speed USB device number 72 using xhci_hcd + [32982831.752856] usb 1-9.3.3: New USB device found, idVendor=1d6b, idProduct=0102, bcdDevice= 1.00 + [32982831.752860] usb 1-9.3.3: New USB device strings: Mfr=1, Product=2, SerialNumber=3 + [32982831.752861] usb 1-9.3.3: Product: NuttX UVC Camera + [32982831.752862] usb 1-9.3.3: Manufacturer: NuttX + [32982831.752863] usb 1-9.3.3: SerialNumber: 0001 + [32982831.756625] usb 1-9.3.3: Found UVC 1.10 device NuttX UVC Camera (1d6b:0102) + +Then open the webcam with any UVC viewer (e.g. ``cheese``, ``guvcview``, +or ``ffplay``):: + + $ cheese + diff --git a/arch/xtensa/src/esp32s3/esp32s3_cam.c b/arch/xtensa/src/esp32s3/esp32s3_cam.c index f89fbb19de815..81caa2c6011c5 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_cam.c +++ b/arch/xtensa/src/esp32s3/esp32s3_cam.c @@ -90,7 +90,7 @@ struct esp32s3_cam_s uint8_t cpu; int32_t dma_channel; - struct esp32s3_dmadesc_s dmadesc[ESP32S3_CAM_DMADESC_NUM]; + struct esp32s3_dmadesc_s *dmadesc; /* Heap-allocated DMA descriptors */ uint8_t *fb; /* Frame buffer */ uint32_t fb_size; /* Frame buffer size */ @@ -196,7 +196,13 @@ static int IRAM_ATTR cam_interrupt(int irq, void *context, void *arg) struct timeval ts; uint32_t regval; - /* Stop capture */ + /* Stop capture and DMA before invoking callback. + * The callback may call set_buf() which rewrites DMA + * descriptors. If DMA is still draining the CAM AFIFO + * it could read half-written descriptors and follow a + * corrupted pbuf pointer, writing pixel data over + * unrelated memory (e.g. g_cam_priv.data.ops). + */ regval = getreg32(LCD_CAM_CAM_CTRL1_REG); regval &= ~LCD_CAM_CAM_START_M; @@ -206,54 +212,72 @@ static int IRAM_ATTR cam_interrupt(int irq, void *context, void *arg) regval |= LCD_CAM_CAM_UPDATE_REG_M; putreg32(regval, LCD_CAM_CAM_CTRL_REG); + /* Stop DMA channel before callback to prevent race */ + + esp32s3_dma_reset_channel(priv->dma_channel, false); + gettimeofday(&ts, NULL); /* Notify frame complete */ priv->cb(0, priv->fb_size, &ts, priv->cb_arg); - /* Restart capture for next frame */ + /* Check if callback called stop_capture. With a + * single-buffer FIFO the V4L2 layer stops capture + * inside the callback; restarting DMA after that + * would run unsynchronized and corrupt memory. + */ - priv->vsync_cnt = 0; + if (!priv->capturing) + { + priv->vsync_cnt = 0; + } + else + { + /* Restart capture for next frame */ - /* Reset DMA */ + priv->vsync_cnt = 0; - esp32s3_dma_reset_channel(priv->dma_channel, false); + /* DMA channel was already reset before the + * callback above. Reset CAM + AFIFO now. + */ - /* Reset CAM + AFIFO */ + /* Reset CAM + AFIFO */ - regval = getreg32(LCD_CAM_CAM_CTRL1_REG); - regval |= LCD_CAM_CAM_RESET_M; - putreg32(regval, LCD_CAM_CAM_CTRL1_REG); - regval &= ~LCD_CAM_CAM_RESET_M; - putreg32(regval, LCD_CAM_CAM_CTRL1_REG); + regval = getreg32(LCD_CAM_CAM_CTRL1_REG); + regval |= LCD_CAM_CAM_RESET_M; + putreg32(regval, LCD_CAM_CAM_CTRL1_REG); + regval &= ~LCD_CAM_CAM_RESET_M; + putreg32(regval, LCD_CAM_CAM_CTRL1_REG); - regval |= LCD_CAM_CAM_AFIFO_RESET_M; - putreg32(regval, LCD_CAM_CAM_CTRL1_REG); - regval &= ~LCD_CAM_CAM_AFIFO_RESET_M; - putreg32(regval, LCD_CAM_CAM_CTRL1_REG); + regval |= LCD_CAM_CAM_AFIFO_RESET_M; + putreg32(regval, LCD_CAM_CAM_CTRL1_REG); + regval &= ~LCD_CAM_CAM_AFIFO_RESET_M; + putreg32(regval, LCD_CAM_CAM_CTRL1_REG); - /* Re-set REC_DATA_BYTELEN after reset */ + /* Re-set REC_DATA_BYTELEN after reset */ - regval &= ~LCD_CAM_CAM_REC_DATA_BYTELEN_M; - regval |= ((ESP32S3_CAM_DMA_BUFLEN - 1) - << LCD_CAM_CAM_REC_DATA_BYTELEN_S); - putreg32(regval, LCD_CAM_CAM_CTRL1_REG); + regval &= ~LCD_CAM_CAM_REC_DATA_BYTELEN_M; + regval |= ((ESP32S3_CAM_DMA_BUFLEN - 1) + << LCD_CAM_CAM_REC_DATA_BYTELEN_S); + putreg32(regval, LCD_CAM_CAM_CTRL1_REG); - /* Reload DMA descriptors */ + /* Reload DMA descriptors */ - esp32s3_dma_load(priv->dmadesc, priv->dma_channel, false); - esp32s3_dma_enable(priv->dma_channel, false); + esp32s3_dma_load(priv->dmadesc, priv->dma_channel, + false); + esp32s3_dma_enable(priv->dma_channel, false); - /* Restart */ + /* Restart */ - regval = getreg32(LCD_CAM_CAM_CTRL1_REG); - regval |= LCD_CAM_CAM_START_M; - putreg32(regval, LCD_CAM_CAM_CTRL1_REG); + regval = getreg32(LCD_CAM_CAM_CTRL1_REG); + regval |= LCD_CAM_CAM_START_M; + putreg32(regval, LCD_CAM_CAM_CTRL1_REG); - regval = getreg32(LCD_CAM_CAM_CTRL_REG); - regval |= LCD_CAM_CAM_UPDATE_REG_M; - putreg32(regval, LCD_CAM_CAM_CTRL_REG); + regval = getreg32(LCD_CAM_CAM_CTRL_REG); + regval |= LCD_CAM_CAM_UPDATE_REG_M; + putreg32(regval, LCD_CAM_CAM_CTRL_REG); + } } } } @@ -512,6 +536,23 @@ static int esp32s3_cam_init(struct imgdata_s *data) spin_lock_init(&priv->lock); + /* Allocate DMA descriptors from heap so they are isolated from + * the driver struct. If GDMA ever follows a stale/corrupted + * descriptor it will scribble on heap, not on g_cam_priv. + */ + + if (priv->dmadesc == NULL) + { + priv->dmadesc = kmm_memalign(4, + sizeof(struct esp32s3_dmadesc_s) * + ESP32S3_CAM_DMADESC_NUM); + if (priv->dmadesc == NULL) + { + snerr("ERROR: Failed to allocate DMA descriptors\n"); + return -ENOMEM; + } + } + /* Configure GPIO pins */ esp32s3_cam_gpio_config(); @@ -554,6 +595,14 @@ static int esp32s3_cam_uninit(struct imgdata_s *data) priv->dma_channel = -1; } + /* Free DMA descriptors */ + + if (priv->dmadesc) + { + kmm_free(priv->dmadesc); + priv->dmadesc = NULL; + } + /* Free frame buffer */ if (priv->fb) @@ -723,31 +772,6 @@ static int esp32s3_cam_start_capture(struct imgdata_s *data, priv->capturing = true; - syslog(LOG_INFO, "CAM start: CTRL=0x%08lx CTRL1=0x%08lx ENA=0x%08lx\n", - (unsigned long)getreg32(LCD_CAM_CAM_CTRL_REG), - (unsigned long)getreg32(LCD_CAM_CAM_CTRL1_REG), - (unsigned long)getreg32(LCD_CAM_LC_DMA_INT_ENA_REG)); - - /* Debug: poll RAW register to see if VSYNC appears */ - - for (int i = 0; i < 50; i++) - { - up_mdelay(20); - uint32_t raw = getreg32(LCD_CAM_LC_DMA_INT_RAW_REG); - uint32_t st = getreg32(LCD_CAM_LC_DMA_INT_ST_REG); - if (raw || st) - { - syslog(LOG_INFO, "CAM poll[%d]: raw=0x%08lx st=0x%08lx\n", - i, (unsigned long)raw, (unsigned long)st); - break; - } - - if (i == 49) - { - syslog(LOG_INFO, "CAM poll: no VSYNC after 1s\n"); - } - } - return OK; } diff --git a/boards/boardctl.c b/boards/boardctl.c index 12743473edbeb..258ee99aa22d2 100644 --- a/boards/boardctl.c +++ b/boards/boardctl.c @@ -56,6 +56,7 @@ # include # include # include +# include # include #endif @@ -206,6 +207,44 @@ static inline int break; #endif +#if defined(CONFIG_USBUVC) && !defined(CONFIG_USBUVC_COMPOSITE) + case BOARDIOC_USBDEV_UVC: /* UVC class */ + switch (ctrl->action) + { + case BOARDIOC_USBDEV_INITIALIZE: /* Initialize UVC device */ + break; + + case BOARDIOC_USBDEV_CONNECT: /* Connect the UVC device */ + { + FAR const struct uvc_params_s *params; + + DEBUGASSERT(ctrl->handle != NULL); + + /* Application passes video params via *handle */ + + params = (FAR const struct uvc_params_s *)*ctrl->handle; + *ctrl->handle = usbdev_uvc_initialize(params); + if (*ctrl->handle == NULL) + { + ret = -EIO; + } + } + break; + + case BOARDIOC_USBDEV_DISCONNECT: /* Disconnect the UVC device */ + { + DEBUGASSERT(ctrl->handle != NULL && *ctrl->handle != NULL); + usbdev_uvc_uninitialize(*ctrl->handle); + } + break; + + default: + ret = -EINVAL; + break; + } + break; +#endif + #ifdef CONFIG_USBDEV_COMPOSITE case BOARDIOC_USBDEV_COMPOSITE: /* Composite device */ switch (ctrl->action) diff --git a/boards/xtensa/esp32s3/lckfb-szpi-esp32s3/configs/uvc/defconfig b/boards/xtensa/esp32s3/lckfb-szpi-esp32s3/configs/uvc/defconfig new file mode 100644 index 0000000000000..398f99986c651 --- /dev/null +++ b/boards/xtensa/esp32s3/lckfb-szpi-esp32s3/configs/uvc/defconfig @@ -0,0 +1,66 @@ +# +# This file is autogenerated: PLEASE DO NOT EDIT IT. +# +# You can use "make menuconfig" to make any modifications to the installed .config file. +# You can then do "make savedefconfig" to generate a new defconfig file that includes your +# modifications. +# +# CONFIG_ARCH_LEDS is not set +# CONFIG_NSH_ARGCAT is not set +# CONFIG_NSH_CMDOPT_HEXDUMP is not set +CONFIG_ARCH="xtensa" +CONFIG_ARCH_BOARD="lckfb-szpi-esp32s3" +CONFIG_ARCH_BOARD_COMMON=y +CONFIG_ARCH_BOARD_ESP32S3_LCKFB_SZPI=y +CONFIG_ARCH_CHIP="esp32s3" +CONFIG_ARCH_CHIP_ESP32S3=y +CONFIG_ARCH_CHIP_ESP32S3WROOM1N16R8=y +CONFIG_ARCH_INTERRUPTSTACK=2048 +CONFIG_ARCH_STACKDUMP=y +CONFIG_ARCH_XTENSA=y +CONFIG_BOARDCTL_USBDEVCTRL=y +CONFIG_BOARD_LOOPSPERMSEC=16717 +CONFIG_BUILTIN=y +CONFIG_DEBUG_FULLOPT=y +CONFIG_DEBUG_SYMBOLS=y +CONFIG_DEV_GPIO=y +CONFIG_ESP32S3_CAM=y +CONFIG_ESP32S3_GPIO_IRQ=y +CONFIG_ESP32S3_I2C0=y +CONFIG_ESP32S3_OTG=y +CONFIG_ESP32S3_OTG_ENDPOINT_NUM=2 +CONFIG_ESP32S3_UART0=y +CONFIG_ESPRESSIF_LEDC=y +CONFIG_ESPRESSIF_LEDC_CHANNEL0_PIN=42 +CONFIG_ESPRESSIF_LEDC_TIMER0=y +CONFIG_FS_PROCFS=y +CONFIG_FS_TMPFS=y +CONFIG_GPIO_LOWER_HALF=y +CONFIG_HAVE_CXX=y +CONFIG_HAVE_CXXINITIALIZE=y +CONFIG_IDLETHREAD_STACKSIZE=3072 +CONFIG_INIT_ENTRYPOINT="nsh_main" +CONFIG_INIT_STACKSIZE=4096 +CONFIG_INTELHEX_BINARY=y +CONFIG_IOEXPANDER=y +CONFIG_IOEXPANDER_PCA9557=y +CONFIG_LINE_MAX=64 +CONFIG_NSH_ARCHINIT=y +CONFIG_NSH_BUILTIN_APPS=y +CONFIG_NSH_FILEIOSIZE=512 +CONFIG_NSH_READLINE=y +CONFIG_PREALLOC_TIMERS=4 +CONFIG_RAM_SIZE=114688 +CONFIG_RAM_START=0x20000000 +CONFIG_RR_INTERVAL=200 +CONFIG_SCHED_WAITPID=y +CONFIG_START_DAY=6 +CONFIG_START_MONTH=12 +CONFIG_START_YEAR=2011 +CONFIG_SYSLOG_BUFFER=y +CONFIG_SYSTEM_NSH=y +CONFIG_UART0_SERIAL_CONSOLE=y +CONFIG_USBUVC=y +CONFIG_VIDEO=y +CONFIG_VIDEO_GC0308=y +CONFIG_VIDEO_STREAM=y diff --git a/drivers/usbdev/CMakeLists.txt b/drivers/usbdev/CMakeLists.txt index cae55d63e39bf..261c90ce0c604 100644 --- a/drivers/usbdev/CMakeLists.txt +++ b/drivers/usbdev/CMakeLists.txt @@ -61,6 +61,10 @@ if(CONFIG_USBDEV) list(APPEND SRCS mtp.c) endif() + if(CONFIG_USBUVC) + list(APPEND SRCS uvc.c) + endif() + if(CONFIG_NET_CDCECM) list(APPEND SRCS cdcecm.c) endif() diff --git a/drivers/usbdev/Kconfig b/drivers/usbdev/Kconfig index e1ad9ddde96ec..6a369baf9892a 100644 --- a/drivers/usbdev/Kconfig +++ b/drivers/usbdev/Kconfig @@ -948,6 +948,51 @@ config USBADB_INTERFACESTR endif # USBADB +menuconfig USBUVC + bool "USB Video Class (UVC) gadget support" + default n + depends on USBDEV + ---help--- + Enables USB Video Class (UVC) gadget device support. + The device appears as a USB webcam to the host. + +if USBUVC + +config USBUVC_COMPOSITE + bool "UVC composite support" + default n + depends on USBDEV_COMPOSITE + ---help--- + Configure the UVC driver as part of a composite USB device. + When enabled, the UVC gadget can be combined with other USB + class drivers (e.g., CDC/ACM) in a single composite device. + +if !USBUVC_COMPOSITE + +config USBUVC_EPBULKIN + int "Bulk IN endpoint number" + default 1 + +config USBUVC_EP0MAXPACKET + int "Max packet size for EP0" + default 64 + +endif # !USBUVC_COMPOSITE + +config USBUVC_EPBULKIN_FSSIZE + int "Bulk IN full-speed max packet size" + default 64 + +config USBUVC_NWRREQS + int "Number of write requests" + default 4 + +config USBUVC_NPOLLWAITERS + int "Number of poll waiters" + default 2 + +endif # USBUVC + menuconfig USBMSC bool "USB Mass storage class device" default n diff --git a/drivers/usbdev/Make.defs b/drivers/usbdev/Make.defs index 8532b800f389e..bf426bbc2a4c5 100644 --- a/drivers/usbdev/Make.defs +++ b/drivers/usbdev/Make.defs @@ -56,6 +56,10 @@ ifeq ($(CONFIG_USBADB),y) CSRCS += adb.c endif +ifeq ($(CONFIG_USBUVC),y) + CSRCS += uvc.c +endif + ifeq ($(CONFIG_USBMTP),y) CSRCS += mtp.c endif diff --git a/drivers/usbdev/uvc.c b/drivers/usbdev/uvc.c new file mode 100644 index 0000000000000..5b90cacf0164c --- /dev/null +++ b/drivers/usbdev/uvc.c @@ -0,0 +1,1665 @@ +/**************************************************************************** + * drivers/usbdev/uvc.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * USB Video Class (UVC) 1.1 Gadget Driver for NuttX + * Bulk transport, uncompressed YUY2, QVGA (320x240) + * + * Architecture: modeled after drivers/usbdev/adb.c (usbdev_fs pattern) + * but with custom class-specific setup handling for UVC PROBE/COMMIT. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_USBDEV_COMPOSITE +# include +#endif +#include +#include +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define USBUVC_CHARDEV_PATH "/dev/uvc0" + +#ifdef CONFIG_USBDEV_SELFPOWERED +# define USBUVC_SELFPOWERED USB_CONFIG_ATTR_SELFPOWER +#else +# define USBUVC_SELFPOWERED (0) +#endif + +#ifdef CONFIG_USBDEV_REMOTEWAKEUP +# define USBUVC_REMOTEWAKEUP USB_CONFIG_ATTR_WAKEUP +#else +# define USBUVC_REMOTEWAKEUP (0) +#endif + +#define USBUVC_MXDESCLEN (64) +#define USBUVC_MAXSTRLEN (USBUVC_MXDESCLEN - 2) + +#define USBUVC_VERSIONNO (0x0100) +#define USBUVC_STR_LANGUAGE (0x0409) + +/* String descriptor IDs — standalone mode uses absolute IDs; + * composite mode uses relative IDs offset by devinfo.strbase. + */ + +#ifndef CONFIG_USBUVC_COMPOSITE +# define USBUVC_MANUFACTURERSTRID (1) +# define USBUVC_PRODUCTSTRID (2) +# define USBUVC_SERIALSTRID (3) +# define USBUVC_CONFIGSTRID (4) +# define USBUVC_VCIFSTRID (5) +# define USBUVC_VSIFSTRID (6) +# define USBUVC_NSTDSTRIDS (6) /* Total standalone string IDs */ +#endif + +/* In composite mode, we only contribute 2 interface strings. + * The composite framework assigns strbase; our strings are + * strbase+1 (VC) and strbase+2 (VS). + */ + +#define USBUVC_VCIFSTRID_REL (1) /* Relative: VC interface string */ +#define USBUVC_VSIFSTRID_REL (2) /* Relative: VS interface string */ + +/* Video parameters — defaults when no runtime params supplied */ + +#define USBUVC_DEF_WIDTH 320 +#define USBUVC_DEF_HEIGHT 240 +#define USBUVC_DEF_FPS 5 +#define USBUVC_BPP 2 /* YUY2 = 2 bytes/pixel */ + +/* Number of poll waiters */ + +#ifndef CONFIG_USBUVC_NPOLLWAITERS +# define CONFIG_USBUVC_NPOLLWAITERS 2 +#endif + +/* Number of write requests */ + +#ifndef CONFIG_USBUVC_NWRREQS +# define CONFIG_USBUVC_NWRREQS 4 +#endif + +/* Bulk IN EP max packet size (Full-Speed) */ + +#ifndef CONFIG_USBUVC_EPBULKIN_FSSIZE +# define CONFIG_USBUVC_EPBULKIN_FSSIZE 64 +#endif + +#ifndef CONFIG_USBUVC_EP0MAXPACKET +# define CONFIG_USBUVC_EP0MAXPACKET 64 +#endif + +/* UVC descriptor unit/terminal IDs */ + +#define USBUVC_IT_ID 1 /* Input Terminal (Camera) */ +#define USBUVC_OT_ID 2 /* Output Terminal (USB Streaming) */ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* Container for write requests — flink must be first for sq_entry_t cast */ + +struct uvc_wrreq_s +{ + FAR struct uvc_wrreq_s *flink; /* Singly linked list link */ + FAR struct usbdev_req_s *req; /* The contained USB request */ +}; + +struct uvc_dev_s +{ + FAR struct usbdev_s *usbdev; + FAR struct usbdev_ep_s *epbulkin; + FAR struct usbdev_req_s *ctrlreq; + FAR struct usbdev_ep_s *ep0; + + struct uvc_streaming_control_s probe; + struct uvc_streaming_control_s commit; + struct usbdev_devinfo_s devinfo; + + /* Runtime video parameters from application */ + + uint16_t width; + uint16_t height; + uint8_t fps; + uint32_t frame_size; + uint32_t frame_interval; /* in 100ns units */ + uint32_t bitrate; + + mutex_t lock; + sem_t wrsem; /* Signaled when wrreq available */ + bool streaming; + FAR struct pollfd *fds[CONFIG_USBUVC_NPOLLWAITERS]; + uint8_t fid; /* Frame ID toggle bit */ + uint8_t config; + + /* Write request pool */ + + struct sq_queue_s wrfree; /* Available write request containers */ + struct uvc_wrreq_s wrreqs[CONFIG_USBUVC_NWRREQS]; + int nwralloc; +}; + +/* Forward declarations */ + +/* EP0 submit helper — in composite mode, route through composite layer */ + +#ifdef CONFIG_USBUVC_COMPOSITE +# define UVC_EP0SUBMIT(driver, dev, ctrlreq, ctrl) \ + composite_ep0submit(driver, dev, ctrlreq, ctrl) +#else +# define UVC_EP0SUBMIT(driver, dev, ctrlreq, ctrl) \ + EP_SUBMIT(dev->ep0, ctrlreq) +#endif + +static int uvc_bind(FAR struct usbdevclass_driver_s *driver, + FAR struct usbdev_s *dev); +static void uvc_unbind(FAR struct usbdevclass_driver_s *driver, + FAR struct usbdev_s *dev); +static int uvc_setup(FAR struct usbdevclass_driver_s *driver, + FAR struct usbdev_s *dev, + FAR const struct usb_ctrlreq_s *ctrl, + FAR uint8_t *dataout, size_t outlen); +static void uvc_disconnect(FAR struct usbdevclass_driver_s *driver, + FAR struct usbdev_s *dev); +static void uvc_streaming_stop(FAR struct uvc_dev_s *priv); + +static const struct usbdevclass_driverops_s g_uvc_driverops = +{ + .bind = uvc_bind, + .unbind = uvc_unbind, + .setup = uvc_setup, + .disconnect = uvc_disconnect, +}; + +/* File operations for /dev/uvc0 */ + +static ssize_t uvc_write(FAR struct file *filep, + FAR const char *buffer, size_t buflen); +static int uvc_open(FAR struct file *filep); +static int uvc_close(FAR struct file *filep); +static int uvc_poll(FAR struct file *filep, + FAR struct pollfd *fds, bool setup); + +static const struct file_operations g_uvc_fops = +{ + .open = uvc_open, + .close = uvc_close, + .write = uvc_write, + .poll = uvc_poll, +}; + +/**************************************************************************** + * Private Data — USB Descriptors + ****************************************************************************/ + +#ifndef CONFIG_USBUVC_COMPOSITE + +/* Device Descriptor — standalone mode only */ + +static const struct usb_devdesc_s g_uvc_devdesc = +{ + .len = USB_SIZEOF_DEVDESC, + .type = USB_DESC_TYPE_DEVICE, + .usb = + { + LSBYTE(0x0200), MSBYTE(0x0200) + }, + .classid = USB_CLASS_MISC, /* 0xEF - IAD device */ + .subclass = 0x02, /* Common Class */ + .protocol = 0x01, /* IAD */ + .mxpacketsize = CONFIG_USBUVC_EP0MAXPACKET, + .vendor = + { + LSBYTE(0x1d6b), MSBYTE(0x1d6b) /* Linux Foundation */ + }, + .product = + { + LSBYTE(0x0102), MSBYTE(0x0102) /* Webcam gadget */ + }, + .device = + { + LSBYTE(USBUVC_VERSIONNO), + MSBYTE(USBUVC_VERSIONNO) + }, + .imfgr = USBUVC_MANUFACTURERSTRID, + .iproduct = USBUVC_PRODUCTSTRID, + .serno = USBUVC_SERIALSTRID, + .nconfigs = USBUVC_NCONFIGS, +}; + +static const struct usb_cfgdesc_s g_uvc_cfgdesc = +{ + .len = USB_SIZEOF_CFGDESC, + .type = USB_DESC_TYPE_CONFIG, + .ninterfaces = USBUVC_NINTERFACES, + .cfgvalue = 1, + .icfg = USBUVC_CONFIGSTRID, + .attr = USB_CONFIG_ATTR_ONE | USBUVC_SELFPOWERED | + USBUVC_REMOTEWAKEUP, + .mxpower = (CONFIG_USBDEV_MAXPOWER + 1) / 2, +}; + +#endif /* !CONFIG_USBUVC_COMPOSITE */ + +/* Interface Association Descriptor (IAD) for UVC + * Note: bFirstInterface and iFunction are patched at runtime. + */ + +static const uint8_t g_uvc_iad[] = +{ + 0x08, /* bLength */ + USB_DESC_TYPE_INTERFACEASSOCIATION, /* bDescriptorType */ + 0x00, /* bFirstInterface: patched */ + 0x02, /* bInterfaceCount (VC + VS) */ + USB_CLASS_VIDEO, /* bFunctionClass */ + UVC_SC_VIDEO_INTERFACE_COLLECTION, /* bFunctionSubClass */ + UVC_PC_PROTOCOL_UNDEFINED, /* bFunctionProtocol */ + 0x00, /* iFunction: patched */ +}; + +/* VideoControl Interface Descriptor + * Note: ifno and iif are patched at runtime. + */ + +static const struct usb_ifdesc_s g_uvc_vc_ifdesc = +{ + .len = USB_SIZEOF_IFDESC, + .type = USB_DESC_TYPE_INTERFACE, + .ifno = 0, /* patched: ifnobase */ + .alt = 0, + .neps = 0, /* No interrupt EP for now */ + .classid = USB_CLASS_VIDEO, + .subclass = UVC_SC_VIDEOCONTROL, + .protocol = UVC_PC_PROTOCOL_UNDEFINED, + .iif = 0, /* patched: strbase + VCIFSTRID_REL */ +}; + +/* VC Header Descriptor (12 + 1*n bytes, n=1 VS interface) + * Note: baInterfaceNr is patched at runtime to ifnobase + 1. + */ + +static const uint8_t g_uvc_vc_header[] = +{ + 0x0d, /* bLength (13) */ + UVC_CS_INTERFACE, /* bDescriptorType */ + UVC_VC_HEADER, /* bDescriptorSubtype */ + LSBYTE(0x0110), /* bcdUVC: 1.10 */ + MSBYTE(0x0110), + LSBYTE(0x0042), /* wTotalLength: will be patched */ + MSBYTE(0x0042), + (0x005b8d80 >> 0) & 0xff, /* dwClockFrequency: 6MHz */ + (0x005b8d80 >> 8) & 0xff, + (0x005b8d80 >> 16) & 0xff, + (0x005b8d80 >> 24) & 0xff, + 0x01, /* bInCollection: 1 VS interface */ + 0x00, /* baInterfaceNr(1): patched */ +}; + +/* Input Terminal Descriptor (Camera) — 17 bytes */ + +static const uint8_t g_uvc_input_terminal[] = +{ + 0x11, /* bLength (17) */ + UVC_CS_INTERFACE, /* bDescriptorType */ + UVC_VC_INPUT_TERMINAL, /* bDescriptorSubtype */ + USBUVC_IT_ID, /* bTerminalID */ + LSBYTE(UVC_ITT_CAMERA), /* wTerminalType */ + MSBYTE(UVC_ITT_CAMERA), + 0x00, /* bAssocTerminal */ + 0x00, /* iTerminal */ + 0x00, 0x00, /* wObjectiveFocalLengthMin */ + 0x00, 0x00, /* wObjectiveFocalLengthMax */ + 0x00, 0x00, /* wOcularFocalLength */ + 0x02, /* bControlSize */ + 0x00, 0x00, /* bmControls (none) */ +}; + +/* Output Terminal Descriptor — 9 bytes */ + +static const uint8_t g_uvc_output_terminal[] = +{ + 0x09, /* bLength */ + UVC_CS_INTERFACE, /* bDescriptorType */ + UVC_VC_OUTPUT_TERMINAL, /* bDescriptorSubtype */ + USBUVC_OT_ID, /* bTerminalID */ + LSBYTE(UVC_TT_STREAMING), /* wTerminalType */ + MSBYTE(UVC_TT_STREAMING), + 0x00, /* bAssocTerminal */ + USBUVC_IT_ID, /* bSourceID */ + 0x00, /* iTerminal */ +}; + +/* VideoStreaming Interface Descriptor + * Note: ifno and iif are patched at runtime. + */ + +static const struct usb_ifdesc_s g_uvc_vs_ifdesc = +{ + .len = USB_SIZEOF_IFDESC, + .type = USB_DESC_TYPE_INTERFACE, + .ifno = 0, /* patched: ifnobase + 1 */ + .alt = 0, + .neps = 1, /* 1 Bulk IN EP */ + .classid = USB_CLASS_VIDEO, + .subclass = UVC_SC_VIDEOSTREAMING, + .protocol = UVC_PC_PROTOCOL_UNDEFINED, + .iif = 0, /* patched: strbase + VSIFSTRID_REL */ +}; + +/* VS Input Header — 14 bytes (1 format, bControlSize=1) */ + +static const uint8_t g_uvc_vs_input_header[] = +{ + 0x0e, /* bLength (14) */ + UVC_CS_INTERFACE, /* bDescriptorType */ + UVC_VS_INPUT_HEADER, /* bDescriptorSubtype */ + 0x01, /* bNumFormats: 1 */ + 0x00, 0x00, /* wTotalLength: patched later */ + 0x00, /* bEndpointAddress: patched later */ + 0x00, /* bmInfo: no dynamic format change */ + USBUVC_OT_ID, /* bTerminalLink */ + 0x00, /* bStillCaptureMethod: none */ + 0x00, /* bTriggerSupport */ + 0x00, /* bTriggerUsage */ + 0x01, /* bControlSize: 1 byte */ + 0x00, /* bmaControls(1): no controls */ +}; + +/* VS Uncompressed Format Descriptor — 27 bytes */ + +static const uint8_t g_uvc_vs_format_uncomp[] = +{ + 0x1b, /* bLength (27) */ + UVC_CS_INTERFACE, /* bDescriptorType */ + UVC_VS_FORMAT_UNCOMPRESSED, /* bDescriptorSubtype */ + 0x01, /* bFormatIndex: 1 */ + 0x01, /* bNumFrameDescriptors: 1 */ + + /* guidFormat: YUY2 */ + + 'Y', 'U', 'Y', '2', 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71, + + 0x10, /* bBitsPerPixel: 16 */ + 0x01, /* bDefaultFrameIndex: 1 */ + 0x00, /* bAspectRatioX */ + 0x00, /* bAspectRatioY */ + 0x00, /* bmInterlaceFlags */ + 0x00, /* bCopyProtect */ +}; + +/* VS Uncompressed Frame Descriptor length - 30 bytes (1 discrete interval) */ + +#define USBUVC_VS_FRAME_DESC_LEN 30 + +/* VS Color Matching Descriptor — 6 bytes */ + +static const uint8_t g_uvc_vs_color[] = +{ + 0x06, /* bLength */ + UVC_CS_INTERFACE, /* bDescriptorType */ + UVC_VS_COLOR_FORMAT, /* bDescriptorSubtype */ + 0x01, /* bColorPrimaries: BT.709 */ + 0x01, /* bTransferCharacteristics: BT.709 */ + 0x04, /* bMatrixCoefficients: SMPTE 170M */ +}; + +/* Bulk IN Endpoint info */ + +static const struct usbdev_epinfo_s g_uvc_epbulkin = +{ + .desc = + { + .len = USB_SIZEOF_EPDESC, + .type = USB_DESC_TYPE_ENDPOINT, + .addr = USB_DIR_IN, + .attr = USB_EP_ATTR_XFER_BULK | + USB_EP_ATTR_NO_SYNC | + USB_EP_ATTR_USAGE_DATA, + .interval = 0, + }, + .reqnum = CONFIG_USBUVC_NWRREQS, + .fssize = CONFIG_USBUVC_EPBULKIN_FSSIZE, +}; + +/* Endpoint info array for composite framework */ + +#ifdef CONFIG_USBDEV_COMPOSITE +static FAR const struct usbdev_epinfo_s *g_uvc_epinfos[USBUVC_NUM_EPS] = +{ + &g_uvc_epbulkin, +}; +#endif + +/* Global device instance (singleton) */ + +static struct uvc_dev_s *g_uvc_dev; + +/**************************************************************************** + * Private Functions — Helpers + ****************************************************************************/ + +static void uvc_default_streaming_ctrl( + FAR struct uvc_dev_s *priv, + FAR struct uvc_streaming_control_s *ctrl) +{ + memset(ctrl, 0, sizeof(*ctrl)); + ctrl->bmhint = 1; + ctrl->bformatindex = 1; + ctrl->bframeindex = 1; + ctrl->dwframeinterval = priv->frame_interval; + ctrl->dwmaxvideoframesize = priv->frame_size; + ctrl->dwmaxpayloadtransfersize = priv->frame_size + + UVC_PAYLOAD_HEADER_LEN; + ctrl->dwclockfrequency = 6000000; /* 6 MHz */ + ctrl->bmframinginfo = 0x03; /* FID + EOF required */ + ctrl->bpreferedversion = 1; + ctrl->bminversion = 1; + ctrl->bmaxversion = 1; +} + +static void uvc_ep0incomplete(FAR struct usbdev_ep_s *ep, + FAR struct usbdev_req_s *req) +{ + if (req->result || req->xfrd != req->len) + { + uerr("EP0 complete: result=%d xfrd=%d len=%d\n", + req->result, req->xfrd, req->len); + } +} + +static void uvc_wrreq_callback(FAR struct usbdev_ep_s *ep, + FAR struct usbdev_req_s *req) +{ + FAR struct uvc_dev_s *priv = g_uvc_dev; + + if (priv) + { + FAR struct uvc_wrreq_s *wrcontainer; + irqstate_t flags; + + if (req->result != OK) + { + uerr("UVC bulk IN xfer failed: %d\n", req->result); + } + + wrcontainer = (FAR struct uvc_wrreq_s *)req->priv; + flags = enter_critical_section(); + sq_addlast((FAR sq_entry_t *)wrcontainer, &priv->wrfree); + leave_critical_section(flags); + + nxsem_post(&priv->wrsem); + } +} + +/**************************************************************************** + * Private Functions — Configuration Descriptor Builder + ****************************************************************************/ + +static int16_t uvc_mkcfgdesc(FAR uint8_t *buf, + FAR struct usbdev_devinfo_s *devinfo, + uint8_t speed, uint8_t type) +{ + FAR struct uvc_dev_s *priv = g_uvc_dev; + FAR uint8_t *p = buf; + uint16_t vs_total; + uint16_t vc_total; + int16_t totallen; + uint8_t vc_ifno; + uint8_t vs_ifno; + uint8_t vc_strid; + uint8_t vs_strid; + + UNUSED(speed); + UNUSED(type); + + /* Compute interface numbers and string IDs from devinfo */ + + vc_ifno = devinfo->ifnobase; + vs_ifno = devinfo->ifnobase + 1; + vc_strid = devinfo->strbase + USBUVC_VCIFSTRID_REL; + vs_strid = devinfo->strbase + USBUVC_VSIFSTRID_REL; + + /* Calculate total descriptor size (without config header for composite) */ + + totallen = (int16_t)(sizeof(g_uvc_iad) + + sizeof(g_uvc_vc_ifdesc) + + sizeof(g_uvc_vc_header) + + sizeof(g_uvc_input_terminal) + + sizeof(g_uvc_output_terminal) + + sizeof(g_uvc_vs_ifdesc) + + sizeof(g_uvc_vs_input_header) + + sizeof(g_uvc_vs_format_uncomp) + + USBUVC_VS_FRAME_DESC_LEN + + sizeof(g_uvc_vs_color) + + USB_SIZEOF_EPDESC); + +#ifndef CONFIG_USBUVC_COMPOSITE + totallen += sizeof(g_uvc_cfgdesc); +#endif + + if (!buf) + { + return totallen; + } + +#ifndef CONFIG_USBUVC_COMPOSITE + /* Configuration descriptor header — standalone only */ + + { + FAR struct usb_cfgdesc_s *dest = (FAR struct usb_cfgdesc_s *)p; + memcpy(p, &g_uvc_cfgdesc, sizeof(g_uvc_cfgdesc)); + dest->type = type; + dest->totallen[0] = LSBYTE(totallen); + dest->totallen[1] = MSBYTE(totallen); + } + + p += sizeof(g_uvc_cfgdesc); +#endif + + /* IAD — patch bFirstInterface and iFunction */ + + memcpy(p, g_uvc_iad, sizeof(g_uvc_iad)); + p[2] = vc_ifno; /* bFirstInterface */ + p[7] = vc_strid; /* iFunction */ + p += sizeof(g_uvc_iad); + + /* VC Interface — patch ifno and iif */ + + memcpy(p, &g_uvc_vc_ifdesc, sizeof(g_uvc_vc_ifdesc)); + ((FAR struct usb_ifdesc_s *)p)->ifno = vc_ifno; + ((FAR struct usb_ifdesc_s *)p)->iif = vc_strid; + p += sizeof(g_uvc_vc_ifdesc); + + /* VC Header — patch wTotalLength and baInterfaceNr */ + + vc_total = sizeof(g_uvc_vc_header) + + sizeof(g_uvc_input_terminal) + + sizeof(g_uvc_output_terminal); + + memcpy(p, g_uvc_vc_header, sizeof(g_uvc_vc_header)); + p[5] = LSBYTE(vc_total); + p[6] = MSBYTE(vc_total); + p[12] = vs_ifno; /* baInterfaceNr(1) */ + p += sizeof(g_uvc_vc_header); + + /* Input Terminal */ + + memcpy(p, g_uvc_input_terminal, sizeof(g_uvc_input_terminal)); + p += sizeof(g_uvc_input_terminal); + + /* Output Terminal */ + + memcpy(p, g_uvc_output_terminal, sizeof(g_uvc_output_terminal)); + p += sizeof(g_uvc_output_terminal); + + /* VS Interface — patch ifno and iif */ + + memcpy(p, &g_uvc_vs_ifdesc, sizeof(g_uvc_vs_ifdesc)); + ((FAR struct usb_ifdesc_s *)p)->ifno = vs_ifno; + ((FAR struct usb_ifdesc_s *)p)->iif = vs_strid; + p += sizeof(g_uvc_vs_ifdesc); + + /* VS Input Header — patch wTotalLength and bEndpointAddress */ + + vs_total = sizeof(g_uvc_vs_input_header) + + sizeof(g_uvc_vs_format_uncomp) + + USBUVC_VS_FRAME_DESC_LEN + + sizeof(g_uvc_vs_color); + + memcpy(p, g_uvc_vs_input_header, sizeof(g_uvc_vs_input_header)); + p[4] = LSBYTE(vs_total); + p[5] = MSBYTE(vs_total); + p[6] = USB_EPIN(devinfo->epno[USBUVC_EP_BULKIN_IDX]); + p += sizeof(g_uvc_vs_input_header); + + /* VS Format */ + + memcpy(p, g_uvc_vs_format_uncomp, sizeof(g_uvc_vs_format_uncomp)); + p += sizeof(g_uvc_vs_format_uncomp); + + /* VS Frame - built at runtime from video params */ + + /* bLength (30) */ + + p[0] = USBUVC_VS_FRAME_DESC_LEN; + + /* bDescriptorType */ + + p[1] = UVC_CS_INTERFACE; + + /* bDescriptorSubtype */ + + p[2] = UVC_VS_FRAME_UNCOMPRESSED; + + /* bFrameIndex: 1 */ + + p[3] = 0x01; + + /* bmCapabilities */ + + p[4] = 0x00; + + /* wWidth */ + + p[5] = LSBYTE(priv->width); + p[6] = MSBYTE(priv->width); + + /* Height (2 bytes LE) */ + + p[7] = LSBYTE(priv->height); + p[8] = MSBYTE(priv->height); + + /* dwMinBitRate */ + + p[9] = (priv->bitrate >> 0) & 0xff; + p[10] = (priv->bitrate >> 8) & 0xff; + p[11] = (priv->bitrate >> 16) & 0xff; + p[12] = (priv->bitrate >> 24) & 0xff; + + /* dwMaxBitRate */ + + p[13] = (priv->bitrate >> 0) & 0xff; + p[14] = (priv->bitrate >> 8) & 0xff; + p[15] = (priv->bitrate >> 16) & 0xff; + p[16] = (priv->bitrate >> 24) & 0xff; + + /* dwMaxVideoFrameBufferSize */ + + p[17] = (priv->frame_size >> 0) & 0xff; + p[18] = (priv->frame_size >> 8) & 0xff; + p[19] = (priv->frame_size >> 16) & 0xff; + p[20] = (priv->frame_size >> 24) & 0xff; + + /* dwDefaultFrameInterval */ + + p[21] = (priv->frame_interval >> 0) & 0xff; + p[22] = (priv->frame_interval >> 8) & 0xff; + p[23] = (priv->frame_interval >> 16) & 0xff; + p[24] = (priv->frame_interval >> 24) & 0xff; + + /* bFrameIntervalType: 1 discrete */ + + p[25] = 0x01; + + /* dwFrameInterval(1) */ + + p[26] = (priv->frame_interval >> 0) & 0xff; + p[27] = (priv->frame_interval >> 8) & 0xff; + p[28] = (priv->frame_interval >> 16) & 0xff; + p[29] = (priv->frame_interval >> 24) & 0xff; + p += USBUVC_VS_FRAME_DESC_LEN; + + /* VS Color Matching */ + + memcpy(p, g_uvc_vs_color, sizeof(g_uvc_vs_color)); + p += sizeof(g_uvc_vs_color); + + /* Bulk IN EP descriptor */ + + usbdev_copy_epdesc((FAR struct usb_epdesc_s *)p, + devinfo->epno[USBUVC_EP_BULKIN_IDX], + speed, &g_uvc_epbulkin); + p += USB_SIZEOF_EPDESC; + + return (int16_t)(p - buf); +} + +/**************************************************************************** + * Private Functions — String Descriptor + ****************************************************************************/ + +static int uvc_mkstrdesc(uint8_t id, FAR struct usb_strdesc_s *strdesc) +{ + FAR uint8_t *data = (FAR uint8_t *)(strdesc + 1); + FAR const char *str; + int len; + int ndata; + int i; + + switch (id) + { +#ifndef CONFIG_USBUVC_COMPOSITE + case 0: + { + /* Descriptor 0 is the language id */ + + strdesc->len = 4; + strdesc->type = USB_DESC_TYPE_STRING; + data[0] = LSBYTE(USBUVC_STR_LANGUAGE); + data[1] = MSBYTE(USBUVC_STR_LANGUAGE); + return 4; + } + + case USBUVC_MANUFACTURERSTRID: + str = "NuttX"; + break; + case USBUVC_PRODUCTSTRID: + str = "NuttX UVC Camera"; + break; + case USBUVC_SERIALSTRID: + str = "0001"; + break; + case USBUVC_CONFIGSTRID: + str = "UVC Config"; + break; +#endif /* !CONFIG_USBUVC_COMPOSITE */ + + /* Interface strings — used in both standalone and composite. + * In standalone: absolute IDs (USBUVC_VCIFSTRID/VSIFSTRID). + * In composite: relative IDs (USBUVC_VCIFSTRID_REL/VSIFSTRID_REL), + * the composite framework adds strbase. + */ + +#ifdef CONFIG_USBUVC_COMPOSITE + case USBUVC_VCIFSTRID_REL: +#else + case USBUVC_VCIFSTRID: +#endif + str = "Video Control"; + break; + +#ifdef CONFIG_USBUVC_COMPOSITE + case USBUVC_VSIFSTRID_REL: +#else + case USBUVC_VSIFSTRID: +#endif + str = "Video Streaming"; + break; + default: + return -EINVAL; + } + + len = strlen(str); + if (len > (USBUVC_MAXSTRLEN / 2)) + { + len = (USBUVC_MAXSTRLEN / 2); + } + + for (i = 0, ndata = 0; i < len; i++, ndata += 2) + { + data[ndata] = str[i]; + data[ndata + 1] = 0; + } + + strdesc->len = ndata + 2; + strdesc->type = USB_DESC_TYPE_STRING; + return strdesc->len; +} + +/**************************************************************************** + * Private Functions — UVC Class-Specific Setup (EP0) + ****************************************************************************/ + +static int uvc_class_setup(FAR struct uvc_dev_s *priv, + FAR struct usbdev_s *dev, + FAR const struct usb_ctrlreq_s *ctrl, + FAR struct usbdev_req_s *ctrlreq) +{ + uint8_t req = ctrl->req; + uint8_t cs = ctrl->value[1]; /* Control selector */ + uint16_t len = GETUINT16(ctrl->len); + int ret = -EOPNOTSUPP; + + uinfo("UVC class setup: req=0x%02x cs=0x%02x len=%d\n", req, cs, len); + + if (cs == UVC_VS_PROBE_CONTROL || cs == UVC_VS_COMMIT_CONTROL) + { + FAR struct uvc_streaming_control_s *target; + + target = (cs == UVC_VS_PROBE_CONTROL) ? + &priv->probe : &priv->commit; + + switch (req) + { + case UVC_SET_CUR: + /* Host sends probe/commit data — we accept it. + * The data arrives in dataout phase, handled below. + * For now just return 0 to ACK the setup. + */ + + if (len >= UVC_PROBE_COMMIT_SIZE) + { + len = UVC_PROBE_COMMIT_SIZE; + } + + ret = 0; + break; + + case UVC_GET_CUR: + case UVC_GET_MIN: + case UVC_GET_MAX: + case UVC_GET_DEF: + if (len > UVC_PROBE_COMMIT_SIZE) + { + len = UVC_PROBE_COMMIT_SIZE; + } + + memcpy(ctrlreq->buf, target, len); + ctrlreq->len = len; + ret = len; + break; + + default: + break; + } + } + + return ret; +} + +/**************************************************************************** + * Private Functions — USB Class Driver Operations + ****************************************************************************/ + +static int uvc_bind(FAR struct usbdevclass_driver_s *driver, + FAR struct usbdev_s *dev) +{ + FAR struct uvc_dev_s *priv = g_uvc_dev; + FAR struct usbdev_req_s *req; + int i; + + uinfo("UVC bind\n"); + + priv->usbdev = dev; + priv->ep0 = dev->ep0; + + /* Allocate control request */ + + priv->ctrlreq = usbdev_allocreq(dev->ep0, 256); + if (!priv->ctrlreq) + { + return -ENOMEM; + } + + priv->ctrlreq->callback = uvc_ep0incomplete; + + /* Allocate Bulk IN endpoint — use devinfo.epno[] set by + * standalone init or composite framework. + */ + + priv->epbulkin = DEV_ALLOCEP(dev, + USB_EPIN(priv->devinfo.epno[ + USBUVC_EP_BULKIN_IDX]), + true, + USB_EP_ATTR_XFER_BULK); + if (!priv->epbulkin) + { + uerr("Failed to allocate bulk IN EP\n"); + return -ENODEV; + } + + priv->epbulkin->priv = priv; + priv->devinfo.epno[USBUVC_EP_BULKIN_IDX] = + USB_EPNO(priv->epbulkin->eplog); + + /* Allocate write requests */ + + sq_init(&priv->wrfree); + for (i = 0; i < CONFIG_USBUVC_NWRREQS; i++) + { + req = usbdev_allocreq(priv->epbulkin, + CONFIG_USBUVC_EPBULKIN_FSSIZE); + if (!req) + { + break; + } + + req->callback = uvc_wrreq_callback; + req->priv = &priv->wrreqs[i]; + priv->wrreqs[i].req = req; + sq_addlast((FAR sq_entry_t *)&priv->wrreqs[i], &priv->wrfree); + priv->nwralloc++; + } + + uvc_default_streaming_ctrl(priv, &priv->probe); + uvc_default_streaming_ctrl(priv, &priv->commit); + + DEV_CONNECT(dev); + return OK; +} + +static void uvc_unbind(FAR struct usbdevclass_driver_s *driver, + FAR struct usbdev_s *dev) +{ + FAR struct uvc_dev_s *priv = g_uvc_dev; + FAR struct uvc_wrreq_s *wrcontainer; + + uinfo("UVC unbind\n"); + + /* Free write requests */ + + while ((wrcontainer = (FAR struct uvc_wrreq_s *) + sq_remfirst(&priv->wrfree))) + { + usbdev_freereq(priv->epbulkin, wrcontainer->req); + wrcontainer->req = NULL; + } + + if (priv->epbulkin) + { + DEV_FREEEP(dev, priv->epbulkin); + priv->epbulkin = NULL; + } + + if (priv->ctrlreq) + { + usbdev_freereq(dev->ep0, priv->ctrlreq); + priv->ctrlreq = NULL; + } + + DEV_DISCONNECT(dev); +} + +static int uvc_setup(FAR struct usbdevclass_driver_s *driver, + FAR struct usbdev_s *dev, + FAR const struct usb_ctrlreq_s *ctrl, + FAR uint8_t *dataout, size_t outlen) +{ + FAR struct uvc_dev_s *priv = g_uvc_dev; + FAR struct usbdev_req_s *ctrlreq = priv->ctrlreq; + uint16_t value = GETUINT16(ctrl->value); + uint16_t index = GETUINT16(ctrl->index); + uint16_t len = GETUINT16(ctrl->len); + int ret = -EOPNOTSUPP; + + UNUSED(index); + + uinfo("UVC setup: type=0x%02x req=0x%02x value=0x%04x len=%d\n", + ctrl->type, ctrl->req, value, len); + + /* Handle class-specific requests */ + + if ((ctrl->type & USB_REQ_TYPE_MASK) == USB_REQ_TYPE_CLASS) + { + /* SET_CUR with data phase — copy incoming data */ + + if (ctrl->req == UVC_SET_CUR && dataout && outlen > 0) + { + uint8_t cs = ctrl->value[1]; + if (cs == UVC_VS_PROBE_CONTROL) + { + memcpy(&priv->probe, dataout, + outlen < UVC_PROBE_COMMIT_SIZE ? + outlen : UVC_PROBE_COMMIT_SIZE); + + /* Clamp to our only supported format */ + + priv->probe.bformatindex = 1; + priv->probe.bframeindex = 1; + priv->probe.dwframeinterval = priv->frame_interval; + priv->probe.dwmaxvideoframesize = priv->frame_size; + priv->probe.dwmaxpayloadtransfersize = + priv->frame_size + UVC_PAYLOAD_HEADER_LEN; + + /* Send ZLP status stage for OUT control transfer */ + + ctrlreq->len = 0; + ctrlreq->flags = USBDEV_REQFLAGS_NULLPKT; + UVC_EP0SUBMIT(driver, dev, ctrlreq, ctrl); + return 0; + } + else if (cs == UVC_VS_COMMIT_CONTROL) + { + memcpy(&priv->commit, dataout, + outlen < UVC_PROBE_COMMIT_SIZE ? + outlen : UVC_PROBE_COMMIT_SIZE); + + /* Reset wrsem to 0 before starting new stream. + * Stale counts from previous EP_CANCEL callbacks + * would cause NULL wrcontainer dereferences. + */ + + while (nxsem_trywait(&priv->wrsem) == OK); + + priv->streaming = true; + poll_notify(priv->fds, CONFIG_USBUVC_NPOLLWAITERS, + POLLOUT); + uinfo("UVC streaming committed\n"); + + /* Send ZLP status stage for OUT control transfer */ + + ctrlreq->len = 0; + ctrlreq->flags = USBDEV_REQFLAGS_NULLPKT; + UVC_EP0SUBMIT(driver, dev, ctrlreq, ctrl); + return 0; + } + } + + ret = uvc_class_setup(priv, dev, ctrl, ctrlreq); + if (ret > 0) + { + ctrlreq->len = ret; + ret = UVC_EP0SUBMIT(driver, dev, ctrlreq, ctrl); + if (ret < 0) + { + uerr("EP_SUBMIT failed: %d\n", ret); + } + + return ret; + } + + /* ret == 0 means SET_CUR accepted, waiting for data phase. + * ret < 0 means unsupported, will fall through. + */ + + if (ret == 0) + { + return ret; + } + } + + /* Handle standard requests — descriptor requests are standalone only; + * SET_CONFIGURATION / SET_INTERFACE are needed in both modes. + */ + + if ((ctrl->type & USB_REQ_TYPE_MASK) == USB_REQ_TYPE_STANDARD) + { + switch (ctrl->req) + { +#ifndef CONFIG_USBUVC_COMPOSITE + case USB_REQ_GETDESCRIPTOR: + { + switch (ctrl->value[1]) + { + case USB_DESC_TYPE_DEVICE: + { + ret = usbdev_copy_devdesc(ctrlreq->buf, + &g_uvc_devdesc, + dev->speed); + } + break; + + case USB_DESC_TYPE_CONFIG: + { + ret = uvc_mkcfgdesc(ctrlreq->buf, + &priv->devinfo, + dev->speed, + ctrl->value[1]); + } + break; + + case USB_DESC_TYPE_STRING: + { + ret = uvc_mkstrdesc(ctrl->value[0], + (FAR struct usb_strdesc_s *)ctrlreq->buf); + } + break; + + default: + break; + } + } + break; +#endif /* !CONFIG_USBUVC_COMPOSITE */ + + case USB_REQ_SETCONFIGURATION: + if (value == 1) + { + /* Configure the bulk IN endpoint */ + + struct usb_epdesc_s epdesc; + epdesc.len = USB_SIZEOF_EPDESC; + epdesc.type = USB_DESC_TYPE_ENDPOINT; + epdesc.addr = USB_EPIN( + USB_EPNO(priv->epbulkin->eplog)); + epdesc.attr = USB_EP_ATTR_XFER_BULK; + epdesc.mxpacketsize[0] = + LSBYTE(CONFIG_USBUVC_EPBULKIN_FSSIZE); + epdesc.mxpacketsize[1] = + MSBYTE(CONFIG_USBUVC_EPBULKIN_FSSIZE); + epdesc.interval = 0; + + EP_CONFIGURE(priv->epbulkin, &epdesc, true); + priv->config = value; + ret = 0; + } + else if (value == 0) + { + uvc_streaming_stop(priv); + EP_DISABLE(priv->epbulkin); + priv->config = 0; + ret = 0; + } + break; + + case USB_REQ_SETINTERFACE: + + /* alt=0 means host is stopping the stream */ + + if ((value & 0xff) == 0 && priv->streaming) + { + uvc_streaming_stop(priv); + } + + ret = 0; + break; + + case USB_REQ_GETINTERFACE: + ctrlreq->buf[0] = 0; + ctrlreq->len = 1; + ret = UVC_EP0SUBMIT(driver, dev, ctrlreq, ctrl); + return ret; + + default: + break; + } + } + + /* Respond to the setup command if data was returned. On an error + * return value (ret < 0), the USB driver will stall EP0. + */ + + if (ret >= 0) + { + ctrlreq->len = len < ret ? len : ret; + ctrlreq->flags = USBDEV_REQFLAGS_NULLPKT; + ret = UVC_EP0SUBMIT(driver, dev, ctrlreq, ctrl); + if (ret < 0) + { + uerr("EP_SUBMIT failed: %d\n", ret); + } + } + + return ret; +} + +static void uvc_streaming_stop(FAR struct uvc_dev_s *priv) +{ + /* Called from interrupt context (disconnect/setup) or from + * uvc_write timeout — no mutex! + * Cancel all pending bulk IN requests so callbacks fire immediately. + * EP_CANCEL callbacks will post wrsem for each cancelled request, + * which unblocks uvc_write(). + * + * Guard against double-cancel: uvc_write timeout may call this, + * then the USB disconnect ISR calls it again. Calling EP_CANCEL + * on an already-cancelled endpoint can corrupt the request list. + */ + + if (!priv->streaming) + { + return; + } + + priv->streaming = false; + poll_notify(priv->fds, CONFIG_USBUVC_NPOLLWAITERS, POLLHUP); + + if (priv->epbulkin) + { + EP_CANCEL(priv->epbulkin, NULL); + } +} + +static void uvc_disconnect(FAR struct usbdevclass_driver_s *driver, + FAR struct usbdev_s *dev) +{ + FAR struct uvc_dev_s *priv = g_uvc_dev; + + uinfo("UVC disconnect\n"); + + /* Called from USB interrupt context — do NOT use mutex */ + + priv->config = 0; + uvc_streaming_stop(priv); +} + +/**************************************************************************** + * Private Functions — File Operations (/dev/uvc0) + ****************************************************************************/ + +static int uvc_open(FAR struct file *filep) +{ + return OK; +} + +static int uvc_close(FAR struct file *filep) +{ + return OK; +} + +/**************************************************************************** + * Name: uvc_poll + * + * Description: + * Poll for POLLOUT — reports writable when host is streaming. + * + ****************************************************************************/ + +static int uvc_poll(FAR struct file *filep, FAR struct pollfd *fds, + bool setup) +{ + FAR struct uvc_dev_s *priv = g_uvc_dev; + irqstate_t flags; + int ret = OK; + int i; + + if (!setup) + { + /* Teardown — clear the slot */ + + FAR struct pollfd **slot = (FAR struct pollfd **)fds->priv; + if (slot) + { + *slot = NULL; + } + + fds->priv = NULL; + return OK; + } + + /* Setup — find an available slot */ + + nxmutex_lock(&priv->lock); + flags = enter_critical_section(); + + for (i = 0; i < CONFIG_USBUVC_NPOLLWAITERS; i++) + { + if (!priv->fds[i]) + { + priv->fds[i] = fds; + fds->priv = &priv->fds[i]; + break; + } + } + + if (i >= CONFIG_USBUVC_NPOLLWAITERS) + { + ret = -EBUSY; + } + else if (priv->config && priv->streaming) + { + poll_notify(&priv->fds[i], 1, POLLOUT); + } + + leave_critical_section(flags); + nxmutex_unlock(&priv->lock); + return ret; +} + +/**************************************************************************** + * Name: uvc_write + * + * Description: + * Write a complete video frame to the UVC bulk endpoint. + * The driver prepends UVC Payload Headers and splits the frame + * into max-packet-sized USB transfers. + * + * Expected input: raw YUY2 frame data + * + ****************************************************************************/ + +static ssize_t uvc_write(FAR struct file *filep, + FAR const char *buffer, size_t buflen) +{ + FAR struct uvc_dev_s *priv = g_uvc_dev; + FAR struct uvc_wrreq_s *wrcontainer; + FAR struct usbdev_req_s *req; + irqstate_t flags; + size_t remaining = buflen; + size_t offset = 0; + size_t payload_max; + size_t chunk; + int ret; + + nxmutex_lock(&priv->lock); + + if (!priv->epbulkin || !priv->config || !priv->streaming) + { + nxmutex_unlock(&priv->lock); + return -EAGAIN; + } + + /* Zero-length write is used to probe readiness */ + + if (buflen == 0) + { + nxmutex_unlock(&priv->lock); + return 0; + } + + payload_max = CONFIG_USBUVC_EPBULKIN_FSSIZE - UVC_PAYLOAD_HEADER_LEN; + + while (remaining > 0) + { + /* Get a write request from the pool */ + + flags = enter_critical_section(); + wrcontainer = (FAR struct uvc_wrreq_s *)sq_remfirst(&priv->wrfree); + leave_critical_section(flags); + + if (!wrcontainer) + { + /* No request available — wait with timeout. + * If host stops reading (e.g. cheese closed), bulk IN + * requests never complete. Timeout and treat as host-gone. + * + * NOTE: When the host truly disconnects or closes the + * video device, the USB disconnect ISR or a CLEAR_FEATURE + * control transfer will call uvc_streaming_stop() + * immediately — the timeout is only a safety net. + * + * It must be long enough to cover the worst case: host + * sends COMMIT, then spends many seconds on REQBUFS / + * MMAP / QBUF / STREAMON before reading bulk data. + * After an unclean close (e.g. v4l2-ctl killed by + * SIGTERM), the host UVC driver reset can add 5-10s. + * 30 seconds is generous enough to never false-trigger. + */ + + nxmutex_unlock(&priv->lock); + ret = nxsem_tickwait(&priv->wrsem, 30 * TICK_PER_SEC); + nxmutex_lock(&priv->lock); + + if (ret == -ETIMEDOUT) + { + uwarn("UVC write timeout — host not reading\n"); + uvc_streaming_stop(priv); + nxmutex_unlock(&priv->lock); + return -EAGAIN; + } + + /* Re-check state after re-acquiring lock. + * streaming_stop() cancels EP requests which unblocks us. + */ + + if (!priv->streaming || !priv->config || !priv->epbulkin) + { + nxmutex_unlock(&priv->lock); + return -EAGAIN; + } + + /* wrcontainer might still be NULL if wrsem had stale count */ + + continue; + } + + req = wrcontainer->req; + + /* Build UVC Payload Header */ + + chunk = remaining > payload_max ? payload_max : remaining; + + req->buf[0] = UVC_PAYLOAD_HEADER_LEN; /* bHeaderLength */ + req->buf[1] = priv->fid & UVC_STREAM_FID; + + if (remaining <= payload_max) + { + /* Last packet of this frame */ + + req->buf[1] |= UVC_STREAM_EOF; + } + + /* Copy video data after header */ + + memcpy(req->buf + UVC_PAYLOAD_HEADER_LEN, buffer + offset, chunk); + req->len = chunk + UVC_PAYLOAD_HEADER_LEN; + + ret = EP_SUBMIT(priv->epbulkin, req); + if (ret < 0) + { + /* Return request to pool */ + + flags = enter_critical_section(); + sq_addlast((FAR sq_entry_t *)wrcontainer, &priv->wrfree); + leave_critical_section(flags); + + if (ret == -ESHUTDOWN) + { + uwarn("UVC EP shutdown — stopping stream\n"); + priv->streaming = false; + poll_notify(priv->fds, CONFIG_USBUVC_NPOLLWAITERS, + POLLHUP); + nxmutex_unlock(&priv->lock); + return -EAGAIN; + } + + uerr("EP_SUBMIT failed: %d\n", ret); + nxmutex_unlock(&priv->lock); + return ret; + } + + offset += chunk; + remaining -= chunk; + } + + /* Toggle FID for next frame */ + + priv->fid ^= 1; + + nxmutex_unlock(&priv->lock); + return buflen; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: usbdev_uvc_classobject + * + * Description: + * Create a UVC class driver instance. Used by both standalone init + * and composite framework. + * + ****************************************************************************/ + +int usbdev_uvc_classobject(int minor, + FAR struct usbdev_devinfo_s *devinfo, + FAR const struct uvc_params_s *params, + FAR struct usbdevclass_driver_s **classdev) +{ + FAR struct uvc_dev_s *priv; + FAR struct usbdevclass_driver_s *drvr; + int ret; + + /* Allocate device struct + class driver struct */ + + priv = kmm_zalloc(sizeof(struct uvc_dev_s) + + sizeof(struct usbdevclass_driver_s)); + if (!priv) + { + return -ENOMEM; + } + + /* Store runtime video parameters */ + + if (params) + { + priv->width = params->width; + priv->height = params->height; + priv->fps = params->fps; + } + else + { + priv->width = USBUVC_DEF_WIDTH; + priv->height = USBUVC_DEF_HEIGHT; + priv->fps = USBUVC_DEF_FPS; + } + + priv->frame_size = (uint32_t)priv->width * priv->height * USBUVC_BPP; + priv->frame_interval = 10000000 / priv->fps; /* 100ns units */ + priv->bitrate = priv->frame_size * 8 * priv->fps; + + /* Save devinfo — interface/string/endpoint bases from caller */ + + memcpy(&priv->devinfo, devinfo, sizeof(struct usbdev_devinfo_s)); + + g_uvc_dev = priv; + nxmutex_init(&priv->lock); + nxsem_init(&priv->wrsem, 0, 0); + + /* Set up the class driver structure right after priv */ + + drvr = (FAR struct usbdevclass_driver_s *)(priv + 1); + drvr->ops = &g_uvc_driverops; + drvr->speed = USB_SPEED_FULL; + + /* Register the character device */ + + ret = register_driver(USBUVC_CHARDEV_PATH, &g_uvc_fops, 0666, priv); + if (ret < 0) + { + uerr("register_driver failed: %d\n", ret); + kmm_free(priv); + g_uvc_dev = NULL; + return ret; + } + + uinfo("UVC gadget initialized: %s\n", USBUVC_CHARDEV_PATH); + *classdev = drvr; + return OK; +} + +/**************************************************************************** + * Name: usbdev_uvc_classuninitialize + * + * Description: + * Uninitialize a UVC class driver instance. + * + ****************************************************************************/ + +void usbdev_uvc_classuninitialize( + FAR struct usbdevclass_driver_s *classdev) +{ + FAR struct uvc_dev_s *priv; + + if (!classdev) + { + return; + } + + /* priv is right before classdev in the allocation */ + + priv = (FAR struct uvc_dev_s *)classdev - 1; + + unregister_driver(USBUVC_CHARDEV_PATH); + nxmutex_destroy(&priv->lock); + nxsem_destroy(&priv->wrsem); + kmm_free(priv); + + if (g_uvc_dev == priv) + { + g_uvc_dev = NULL; + } +} + +/**************************************************************************** + * Name: usbdev_uvc_initialize + * + * Description: + * Standalone mode initialization — creates the class driver and + * registers it directly with the USB device controller. + * + ****************************************************************************/ + +#ifndef CONFIG_USBUVC_COMPOSITE +FAR void *usbdev_uvc_initialize(FAR const struct uvc_params_s *params) +{ + FAR struct usbdevclass_driver_s *classdev = NULL; + struct usbdev_devinfo_s devinfo; + int ret; + + memset(&devinfo, 0, sizeof(devinfo)); + devinfo.ninterfaces = USBUVC_NINTERFACES; + devinfo.nstrings = USBUVC_NSTDSTRIDS; + devinfo.nendpoints = USBUVC_NUM_EPS; + devinfo.epno[USBUVC_EP_BULKIN_IDX] = CONFIG_USBUVC_EPBULKIN; + + ret = usbdev_uvc_classobject(0, &devinfo, params, &classdev); + if (ret < 0) + { + return NULL; + } + + ret = usbdev_register(classdev); + if (ret < 0) + { + uerr("usbdev_register failed: %d\n", ret); + usbdev_uvc_classuninitialize(classdev); + return NULL; + } + + /* Return priv as handle (priv is right before classdev) */ + + return (FAR void *)((FAR struct uvc_dev_s *)classdev - 1); +} + +/**************************************************************************** + * Name: usbdev_uvc_uninitialize + ****************************************************************************/ + +void usbdev_uvc_uninitialize(FAR void *handle) +{ + FAR struct uvc_dev_s *priv = handle; + FAR struct usbdevclass_driver_s *drvr; + + if (!priv) + { + return; + } + + drvr = (FAR struct usbdevclass_driver_s *)(priv + 1); + usbdev_unregister(drvr); + usbdev_uvc_classuninitialize(drvr); +} +#endif /* !CONFIG_USBUVC_COMPOSITE */ + +/**************************************************************************** + * Name: usbdev_uvc_get_composite_devdesc + * + * Description: + * Fill in a composite_devdesc_s for the UVC gadget. + * Board code must set classobject/uninitialize, ifnobase, strbase, + * and epno[] before passing to composite_initialize(). + * + ****************************************************************************/ + +#ifdef CONFIG_USBDEV_COMPOSITE +void usbdev_uvc_get_composite_devdesc( + FAR struct composite_devdesc_s *dev) +{ + memset(dev, 0, sizeof(struct composite_devdesc_s)); + + dev->mkconfdesc = uvc_mkcfgdesc; + dev->mkstrdesc = uvc_mkstrdesc; + + /* classobject/uninitialize are left NULL — board code must wrap + * usbdev_uvc_classobject() to pass uvc_params_s. + */ + + dev->nconfigs = USBUVC_NCONFIGS; + dev->configid = 1; + dev->cfgdescsize = uvc_mkcfgdesc(NULL, NULL, + USB_SPEED_UNKNOWN, 0); + dev->devinfo.ninterfaces = USBUVC_NINTERFACES; + dev->devinfo.nstrings = USBUVC_NSTRIDS; + dev->devinfo.nendpoints = USBUVC_NUM_EPS; + dev->devinfo.epinfos = g_uvc_epinfos; + dev->devinfo.name = USBUVC_CHARDEV_PATH; +} +#endif diff --git a/drivers/video/gc0308.c b/drivers/video/gc0308.c index 339940e5a69bc..e6a98753c6def 100644 --- a/drivers/video/gc0308.c +++ b/drivers/video/gc0308.c @@ -154,6 +154,7 @@ struct gc0308_dev_s struct i2c_master_s *i2c; uint16_t width; uint16_t height; + uint32_t pixelformat; struct v4l2_frmsizeenum frmsizes; bool streaming; }; @@ -455,6 +456,10 @@ static const struct imgsensor_ops_s g_gc0308_ops = static const struct v4l2_fmtdesc g_gc0308_fmtdescs[] = { + { + .pixelformat = V4L2_PIX_FMT_YUYV, + .description = "YUV 4:2:2 (YUYV)", + }, { .pixelformat = V4L2_PIX_FMT_RGB565, .description = "RGB565", @@ -648,7 +653,7 @@ static int gc0308_init(struct imgsensor_s *sensor) up_mdelay(80); - /* Set RGB565 output format: reg 0x24 bits[3:0] = 6 */ + /* Set default RGB565 output format: reg 0x24 bits[3:0] = 6 */ ret = gc0308_putreg(priv->i2c, GC0308_REG_RESET, 0x00); if (ret < 0) @@ -657,6 +662,7 @@ static int gc0308_init(struct imgsensor_s *sensor) } ret = gc0308_modreg(priv->i2c, GC0308_REG_OUTPUT_FMT, 0x0f, 0x06); + priv->pixelformat = IMGSENSOR_PIX_FMT_RGB565; if (ret < 0) { return ret; @@ -752,6 +758,8 @@ static int gc0308_validate_frame_setting(struct imgsensor_s *sensor, } if (datafmts[IMGSENSOR_FMT_MAIN].pixelformat != + IMGSENSOR_PIX_FMT_YUYV && + datafmts[IMGSENSOR_FMT_MAIN].pixelformat != IMGSENSOR_PIX_FMT_RGB565) { return -EINVAL; @@ -777,12 +785,32 @@ static int gc0308_start_capture(struct imgsensor_s *sensor, imgsensor_interval_t *interval) { struct gc0308_dev_s *priv = (struct gc0308_dev_s *)sensor; + uint8_t fmtval; + int ret; if (priv->streaming) { return -EBUSY; } + /* Configure output format register based on requested pixel format */ + + if (datafmts[IMGSENSOR_FMT_MAIN].pixelformat == IMGSENSOR_PIX_FMT_RGB565) + { + fmtval = 0x06; /* RGB565 */ + } + else + { + fmtval = 0x02; /* YCbCr422 */ + } + + ret = gc0308_modreg(priv->i2c, GC0308_REG_OUTPUT_FMT, 0x0f, fmtval); + if (ret < 0) + { + return ret; + } + + priv->pixelformat = datafmts[IMGSENSOR_FMT_MAIN].pixelformat; priv->streaming = true; return OK; } diff --git a/include/nuttx/usb/uvc.h b/include/nuttx/usb/uvc.h new file mode 100644 index 0000000000000..cbbcfffd749d0 --- /dev/null +++ b/include/nuttx/usb/uvc.h @@ -0,0 +1,282 @@ +/**************************************************************************** + * include/nuttx/usb/uvc.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_USB_UVC_H +#define __INCLUDE_NUTTX_USB_UVC_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* UVC device descriptor counts - used by both standalone and composite */ + +#define USBUVC_NINTERFACES 2 /* VC + VS */ +#define USBUVC_NUM_EPS 1 /* Bulk IN only */ +#define USBUVC_EP_BULKIN_IDX 0 +#define USBUVC_NSTRIDS 2 /* VC iInterface + VS iInterface */ +#define USBUVC_NCONFIGS 1 + +/* UVC Class-Specific Descriptor Types (USB Video Class 1.1, Table A-4) */ + +#define UVC_CS_UNDEFINED 0x20 +#define UVC_CS_DEVICE 0x21 +#define UVC_CS_CONFIGURATION 0x22 +#define UVC_CS_STRING 0x23 +#define UVC_CS_INTERFACE 0x24 +#define UVC_CS_ENDPOINT 0x25 + +/* UVC Interface Subclass Codes (Table A-2) */ + +#define UVC_SC_UNDEFINED 0x00 +#define UVC_SC_VIDEOCONTROL 0x01 +#define UVC_SC_VIDEOSTREAMING 0x02 +#define UVC_SC_VIDEO_INTERFACE_COLLECTION 0x03 + +/* UVC Interface Protocol Codes (Table A-3) */ + +#define UVC_PC_PROTOCOL_UNDEFINED 0x00 +#define UVC_PC_PROTOCOL_15 0x01 + +/* UVC VideoControl Interface Descriptor Subtypes (Table A-5) */ + +#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 + +/* UVC VideoStreaming Interface Descriptor Subtypes (Table A-6) */ + +#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_COLOR_FORMAT 0x0d + +/* UVC Terminal Types (Table B-1, B-2) */ + +#define UVC_TT_VENDOR_SPECIFIC 0x0100 +#define UVC_TT_STREAMING 0x0101 +#define UVC_ITT_VENDOR_SPECIFIC 0x0200 +#define UVC_ITT_CAMERA 0x0201 +#define UVC_ITT_MEDIA_TRANSPORT_INPUT 0x0202 +#define UVC_OTT_VENDOR_SPECIFIC 0x0300 +#define UVC_OTT_DISPLAY 0x0301 +#define UVC_OTT_MEDIA_TRANSPORT_OUTPUT 0x0302 + +/* UVC Class-Specific Request Codes (Table A-8) */ + +#define UVC_RC_UNDEFINED 0x00 +#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 + +/* UVC VideoStreaming Interface Control Selectors (Table A-11) */ + +#define UVC_VS_CONTROL_UNDEFINED 0x00 +#define UVC_VS_PROBE_CONTROL 0x01 +#define UVC_VS_COMMIT_CONTROL 0x02 + +/* UVC Payload Header bits (Table 2-6) */ + +#define UVC_STREAM_FID (1 << 0) +#define UVC_STREAM_EOF (1 << 1) +#define UVC_STREAM_PTS (1 << 2) +#define UVC_STREAM_SCR (1 << 3) +#define UVC_STREAM_RES (1 << 4) +#define UVC_STREAM_STI (1 << 5) +#define UVC_STREAM_ERR (1 << 6) +#define UVC_STREAM_EOH (1 << 7) + +/* UVC Payload Header Length (minimum, no PTS/SCR) */ + +#define UVC_PAYLOAD_HEADER_LEN 2 + +/* GUID for YUY2 (MEDIASUBTYPE_YUY2) — we use this for uncompressed + * Note: For RGB565 there is no standard UVC GUID. We advertise YUY2 + * and do a trivial RGB565→YUY2 conversion in the app, OR we define + * a custom GUID. For Phase 1, we use YUY2 which is universally supported. + */ + +#define UVC_GUID_FORMAT_YUY2 \ + { 'Y', 'U', 'Y', '2', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/* UVC Video Probe and Commit Controls (Table 4-75) */ + +begin_packed_struct struct uvc_streaming_control_s +{ + 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; + uint8_t bpreferedversion; + uint8_t bminversion; + uint8_t bmaxversion; +} end_packed_struct; + +#define UVC_PROBE_COMMIT_SIZE sizeof(struct uvc_streaming_control_s) + +/* UVC gadget initialization parameters. + * The application queries the video device at runtime and passes + * these to usbdev_uvc_initialize() so that USB descriptors match + * the actual sensor capabilities. + */ + +struct uvc_params_s +{ + uint16_t width; /* Frame width in pixels */ + uint16_t height; /* Frame height in pixels */ + uint8_t fps; /* Frames per second */ +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#undef EXTERN +#if defined(__cplusplus) +# define EXTERN extern "C" +extern "C" +{ +#else +# define EXTERN extern +#endif + +/**************************************************************************** + * Name: usbdev_uvc_initialize + * + * Description: + * Initialize the USB Video Class device driver. + * + * Input Parameters: + * params - Video parameters (width, height, fps) queried from the + * camera sensor. If NULL, defaults to 320x240 @ 5fps. + * + * Returned Value: + * A non-NULL "handle" is returned on success. + * + ****************************************************************************/ + +FAR void *usbdev_uvc_initialize(FAR const struct uvc_params_s *params); + +/**************************************************************************** + * Name: usbdev_uvc_uninitialize + * + * Description: + * Uninitialize the USB Video Class device driver. + * + ****************************************************************************/ + +void usbdev_uvc_uninitialize(FAR void *handle); + +/**************************************************************************** + * Name: usbdev_uvc_classobject + * + * Description: + * Create a UVC class driver instance for composite device usage. + * Called by the composite framework (or board code) to instantiate + * the UVC class driver with assigned interface/string/endpoint bases. + * + * Input Parameters: + * minor - Device minor number (unused, pass 0). + * devinfo - USB device info with ifnobase, strbase, epno[] assigned + * by the composite framework. + * params - Video parameters (width, height, fps). NULL = defaults. + * classdev - Location to return the class driver instance. + * + * Returned Value: + * OK on success; negative errno on failure. + * + ****************************************************************************/ + +int usbdev_uvc_classobject(int minor, + FAR struct usbdev_devinfo_s *devinfo, + FAR const struct uvc_params_s *params, + FAR struct usbdevclass_driver_s **classdev); + +/**************************************************************************** + * Name: usbdev_uvc_classuninitialize + * + * Description: + * Uninitialize a UVC class driver instance created by classobject. + * + ****************************************************************************/ + +void usbdev_uvc_classuninitialize( + FAR struct usbdevclass_driver_s *classdev); + +/**************************************************************************** + * Name: usbdev_uvc_get_composite_devdesc + * + * Description: + * Fill in a composite_devdesc_s structure for the UVC gadget. + * Board code calls this, then sets ifnobase/strbase/epno[] before + * passing to composite_initialize(). + * + * Note: classobject/uninitialize are left NULL because UVC needs + * extra params (uvc_params_s). Board code must set them. + * + ****************************************************************************/ + +#ifdef CONFIG_USBDEV_COMPOSITE +void usbdev_uvc_get_composite_devdesc( + FAR struct composite_devdesc_s *dev); +#endif + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* __INCLUDE_NUTTX_USB_UVC_H */ diff --git a/include/sys/boardctl.h b/include/sys/boardctl.h index d0508efef7cd5..23cd8f61e3087 100644 --- a/include/sys/boardctl.h +++ b/include/sys/boardctl.h @@ -373,6 +373,9 @@ enum boardioc_usbdev_identifier_e #ifdef CONFIG_USBMSC , BOARDIOC_USBDEV_MSC /* Mass storage class */ #endif +#ifdef CONFIG_USBUVC + , BOARDIOC_USBDEV_UVC /* USB Video Class */ +#endif #ifdef CONFIG_USBDEV_COMPOSITE , BOARDIOC_USBDEV_COMPOSITE /* Composite device */ #endif