From 0054ae9fd751ee86bc977de14ac57e4f2b74d7b2 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Sat, 9 Aug 2025 16:24:17 +0000 Subject: [PATCH 01/31] usb: host: rename "struct usbh_contex" to "usbh_context" Make the struct name match the device naming for ease of use, although slightly longer name. Propagate the change to the subsystem, includes, tests and samples. Signed-off-by: Josuah Demangeon --- include/zephyr/usb/usbh.h | 26 +++++++++++++------------- subsys/usb/host/usbh_api.c | 8 ++++---- subsys/usb/host/usbh_core.c | 14 +++++++------- subsys/usb/host/usbh_data.ld | 2 +- subsys/usb/host/usbh_device.c | 12 ++++++------ subsys/usb/host/usbh_device.h | 20 ++++++++++---------- subsys/usb/host/usbh_internal.h | 2 +- subsys/usb/host/usbip.c | 2 +- 8 files changed, 43 insertions(+), 43 deletions(-) diff --git a/include/zephyr/usb/usbh.h b/include/zephyr/usb/usbh.h index 852d8b9ac6986..e2e6f3074000e 100644 --- a/include/zephyr/usb/usbh.h +++ b/include/zephyr/usb/usbh.h @@ -36,7 +36,7 @@ extern "C" { /** * USB host support runtime context */ -struct usbh_contex { +struct usbh_context { /** Name of the USB device */ const char *name; /** Access mutex */ @@ -53,7 +53,7 @@ struct usbh_contex { #define USBH_CONTROLLER_DEFINE(device_name, uhc_dev) \ SYS_BITARRAY_DEFINE_STATIC(ba_##device_name, 128); \ - static STRUCT_SECTION_ITERABLE(usbh_contex, device_name) = { \ + static STRUCT_SECTION_ITERABLE(usbh_context, device_name) = { \ .name = STRINGIFY(device_name), \ .mutex = Z_MUTEX_INITIALIZER(device_name.mutex), \ .dev = uhc_dev, \ @@ -80,20 +80,20 @@ struct usbh_class_data { struct usbh_code_triple code; /** Initialization of the class implementation */ - /* int (*init)(struct usbh_contex *const uhs_ctx); */ + /* int (*init)(struct usbh_context *const uhs_ctx); */ /** Request completion event handler */ - int (*request)(struct usbh_contex *const uhs_ctx, + int (*request)(struct usbh_context *const uhs_ctx, struct uhc_transfer *const xfer, int err); /** Device connected handler */ - int (*connected)(struct usbh_contex *const uhs_ctx); + int (*connected)(struct usbh_context *const uhs_ctx); /** Device removed handler */ - int (*removed)(struct usbh_contex *const uhs_ctx); + int (*removed)(struct usbh_context *const uhs_ctx); /** Bus remote wakeup handler */ - int (*rwup)(struct usbh_contex *const uhs_ctx); + int (*rwup)(struct usbh_context *const uhs_ctx); /** Bus suspended handler */ - int (*suspended)(struct usbh_contex *const uhs_ctx); + int (*suspended)(struct usbh_context *const uhs_ctx); /** Bus resumed handler */ - int (*resumed)(struct usbh_contex *const uhs_ctx); + int (*resumed)(struct usbh_context *const uhs_ctx); }; /** @@ -109,7 +109,7 @@ struct usbh_class_data { * * @return 0 on success, other values on fail. */ -int usbh_init(struct usbh_contex *uhs_ctx); +int usbh_init(struct usbh_context *uhs_ctx); /** * @brief Enable the USB host support and class instances @@ -120,7 +120,7 @@ int usbh_init(struct usbh_contex *uhs_ctx); * * @return 0 on success, other values on fail. */ -int usbh_enable(struct usbh_contex *uhs_ctx); +int usbh_enable(struct usbh_context *uhs_ctx); /** * @brief Disable the USB host support @@ -131,7 +131,7 @@ int usbh_enable(struct usbh_contex *uhs_ctx); * * @return 0 on success, other values on fail. */ -int usbh_disable(struct usbh_contex *uhs_ctx); +int usbh_disable(struct usbh_context *uhs_ctx); /** * @brief Shutdown the USB host support @@ -142,7 +142,7 @@ int usbh_disable(struct usbh_contex *uhs_ctx); * * @return 0 on success, other values on fail. */ -int usbh_shutdown(struct usbh_contex *const uhs_ctx); +int usbh_shutdown(struct usbh_context *const uhs_ctx); /** * @} diff --git a/subsys/usb/host/usbh_api.c b/subsys/usb/host/usbh_api.c index e1aa6e99ac3b9..8fc1a1869deb2 100644 --- a/subsys/usb/host/usbh_api.c +++ b/subsys/usb/host/usbh_api.c @@ -11,7 +11,7 @@ #include LOG_MODULE_REGISTER(uhs_api, CONFIG_USBH_LOG_LEVEL); -int usbh_init(struct usbh_contex *uhs_ctx) +int usbh_init(struct usbh_context *uhs_ctx) { int ret; @@ -36,7 +36,7 @@ int usbh_init(struct usbh_contex *uhs_ctx) return ret; } -int usbh_enable(struct usbh_contex *uhs_ctx) +int usbh_enable(struct usbh_context *uhs_ctx) { int ret; @@ -65,7 +65,7 @@ int usbh_enable(struct usbh_contex *uhs_ctx) return ret; } -int usbh_disable(struct usbh_contex *uhs_ctx) +int usbh_disable(struct usbh_context *uhs_ctx) { int ret; @@ -86,7 +86,7 @@ int usbh_disable(struct usbh_contex *uhs_ctx) return 0; } -int usbh_shutdown(struct usbh_contex *const uhs_ctx) +int usbh_shutdown(struct usbh_context *const uhs_ctx) { int ret; diff --git a/subsys/usb/host/usbh_core.c b/subsys/usb/host/usbh_core.c index c18ef39cb63cf..1b654b5041133 100644 --- a/subsys/usb/host/usbh_core.c +++ b/subsys/usb/host/usbh_core.c @@ -43,7 +43,7 @@ static int usbh_event_carrier(const struct device *dev, return err; } -static void dev_connected_handler(struct usbh_contex *const ctx, +static void dev_connected_handler(struct usbh_context *const ctx, const struct uhc_event *const event) { @@ -73,7 +73,7 @@ static void dev_connected_handler(struct usbh_contex *const ctx, } } -static void dev_removed_handler(struct usbh_contex *const ctx) +static void dev_removed_handler(struct usbh_context *const ctx) { if (ctx->root != NULL) { usbh_device_free(ctx->root); @@ -84,7 +84,7 @@ static void dev_removed_handler(struct usbh_contex *const ctx) } } -static int discard_ep_request(struct usbh_contex *const ctx, +static int discard_ep_request(struct usbh_context *const ctx, struct uhc_transfer *const xfer) { const struct device *dev = ctx->dev; @@ -97,7 +97,7 @@ static int discard_ep_request(struct usbh_contex *const ctx, return uhc_xfer_free(dev, xfer); } -static ALWAYS_INLINE int usbh_event_handler(struct usbh_contex *const ctx, +static ALWAYS_INLINE int usbh_event_handler(struct usbh_context *const ctx, struct uhc_event *const event) { int ret = 0; @@ -141,7 +141,7 @@ static void usbh_bus_thread(void *p1, void *p2, void *p3) ARG_UNUSED(p2); ARG_UNUSED(p3); - struct usbh_contex *uhs_ctx; + struct usbh_context *uhs_ctx; struct uhc_event event; while (true) { @@ -158,7 +158,7 @@ static void usbh_thread(void *p1, void *p2, void *p3) ARG_UNUSED(p2); ARG_UNUSED(p3); - struct usbh_contex *uhs_ctx; + struct usbh_context *uhs_ctx; struct uhc_event event; usbh_udev_cb_t cb; int ret; @@ -182,7 +182,7 @@ static void usbh_thread(void *p1, void *p2, void *p3) } } -int usbh_init_device_intl(struct usbh_contex *const uhs_ctx) +int usbh_init_device_intl(struct usbh_context *const uhs_ctx) { int ret; diff --git a/subsys/usb/host/usbh_data.ld b/subsys/usb/host/usbh_data.ld index 193c63efc0ed2..4363154f29474 100644 --- a/subsys/usb/host/usbh_data.ld +++ b/subsys/usb/host/usbh_data.ld @@ -1,4 +1,4 @@ #include -ITERABLE_SECTION_RAM(usbh_contex, Z_LINK_ITERABLE_SUBALIGN) +ITERABLE_SECTION_RAM(usbh_context, Z_LINK_ITERABLE_SUBALIGN) ITERABLE_SECTION_RAM(usbh_class_data, Z_LINK_ITERABLE_SUBALIGN) diff --git a/subsys/usb/host/usbh_device.c b/subsys/usb/host/usbh_device.c index 13a2a81ccd2c1..99970ffb0c4ed 100644 --- a/subsys/usb/host/usbh_device.c +++ b/subsys/usb/host/usbh_device.c @@ -18,7 +18,7 @@ K_MEM_SLAB_DEFINE_STATIC(usb_device_slab, sizeof(struct usb_device), K_HEAP_DEFINE(usb_device_heap, CONFIG_USBH_USB_DEVICE_HEAP); -struct usb_device *usbh_device_alloc(struct usbh_contex *const uhs_ctx) +struct usb_device *usbh_device_alloc(struct usbh_context *const uhs_ctx) { struct usb_device *udev; @@ -37,7 +37,7 @@ struct usb_device *usbh_device_alloc(struct usbh_contex *const uhs_ctx) void usbh_device_free(struct usb_device *const udev) { - struct usbh_contex *const uhs_ctx = udev->ctx; + struct usbh_context *const uhs_ctx = udev->ctx; sys_bitarray_clear_bit(uhs_ctx->addr_ba, udev->addr); sys_dlist_remove(&udev->node); @@ -48,7 +48,7 @@ void usbh_device_free(struct usb_device *const udev) k_mem_slab_free(&usb_device_slab, (void *)udev); } -struct usb_device *usbh_device_get_any(struct usbh_contex *const uhs_ctx) +struct usb_device *usbh_device_get_any(struct usbh_context *const uhs_ctx) { sys_dnode_t *const node = sys_dlist_peek_head(&uhs_ctx->udevs); struct usb_device *udev; @@ -58,7 +58,7 @@ struct usb_device *usbh_device_get_any(struct usbh_contex *const uhs_ctx) return udev; } -struct usb_device *usbh_device_get(struct usbh_contex *const uhs_ctx, const uint8_t addr) +struct usb_device *usbh_device_get(struct usbh_context *const uhs_ctx, const uint8_t addr) { struct usb_device *udev; @@ -99,7 +99,7 @@ static int validate_device_mps0(const struct usb_device *const udev) static int alloc_device_address(struct usb_device *const udev, uint8_t *const addr) { - struct usbh_contex *const uhs_ctx = udev->ctx; + struct usbh_context *const uhs_ctx = udev->ctx; int val; int err; @@ -449,7 +449,7 @@ int usbh_device_set_configuration(struct usb_device *const udev, const uint8_t n int usbh_device_init(struct usb_device *const udev) { - struct usbh_contex *const uhs_ctx = udev->ctx; + struct usbh_context *const uhs_ctx = udev->ctx; uint8_t new_addr; int err; diff --git a/subsys/usb/host/usbh_device.h b/subsys/usb/host/usbh_device.h index 19613bda3bfa4..5c5637fb0bee8 100644 --- a/subsys/usb/host/usbh_device.h +++ b/subsys/usb/host/usbh_device.h @@ -20,12 +20,12 @@ typedef int (*usbh_udev_cb_t)(struct usb_device *const udev, * connection without hub support, this is the device connected directly to the * host controller. */ -struct usb_device *usbh_device_get_any(struct usbh_contex *const ctx); +struct usb_device *usbh_device_get_any(struct usbh_context *const ctx); -struct usb_device *usbh_device_get(struct usbh_contex *const uhs_ctx, const uint8_t addr); +struct usb_device *usbh_device_get(struct usbh_context *const uhs_ctx, const uint8_t addr); /* Allocate/free USB device */ -struct usb_device *usbh_device_alloc(struct usbh_contex *const uhs_ctx); +struct usb_device *usbh_device_alloc(struct usbh_context *const uhs_ctx); void usbh_device_free(struct usb_device *const udev); /* Reset and configure new USB device */ @@ -42,7 +42,7 @@ static inline struct uhc_transfer *usbh_xfer_alloc(struct usb_device *udev, usbh_udev_cb_t cb, void *const cb_priv) { - struct usbh_contex *const ctx = udev->ctx; + struct usbh_context *const ctx = udev->ctx; return uhc_xfer_alloc(ctx->dev, ep, udev, cb, cb_priv); } @@ -51,7 +51,7 @@ static inline int usbh_xfer_buf_add(const struct usb_device *udev, struct uhc_transfer *const xfer, struct net_buf *buf) { - struct usbh_contex *const ctx = udev->ctx; + struct usbh_context *const ctx = udev->ctx; return uhc_xfer_buf_add(ctx->dev, xfer, buf); } @@ -59,7 +59,7 @@ static inline int usbh_xfer_buf_add(const struct usb_device *udev, static inline struct net_buf *usbh_xfer_buf_alloc(struct usb_device *udev, const size_t size) { - struct usbh_contex *const ctx = udev->ctx; + struct usbh_context *const ctx = udev->ctx; return uhc_xfer_buf_alloc(ctx->dev, size); } @@ -67,7 +67,7 @@ static inline struct net_buf *usbh_xfer_buf_alloc(struct usb_device *udev, static inline int usbh_xfer_free(const struct usb_device *udev, struct uhc_transfer *const xfer) { - struct usbh_contex *const ctx = udev->ctx; + struct usbh_context *const ctx = udev->ctx; return uhc_xfer_free(ctx->dev, xfer); } @@ -75,7 +75,7 @@ static inline int usbh_xfer_free(const struct usb_device *udev, static inline void usbh_xfer_buf_free(const struct usb_device *udev, struct net_buf *const buf) { - struct usbh_contex *const ctx = udev->ctx; + struct usbh_context *const ctx = udev->ctx; uhc_xfer_buf_free(ctx->dev, buf); } @@ -83,7 +83,7 @@ static inline void usbh_xfer_buf_free(const struct usb_device *udev, static inline int usbh_xfer_enqueue(const struct usb_device *udev, struct uhc_transfer *const xfer) { - struct usbh_contex *const ctx = udev->ctx; + struct usbh_context *const ctx = udev->ctx; return uhc_ep_enqueue(ctx->dev, xfer); } @@ -91,7 +91,7 @@ static inline int usbh_xfer_enqueue(const struct usb_device *udev, static inline int usbh_xfer_dequeue(const struct usb_device *udev, struct uhc_transfer *const xfer) { - struct usbh_contex *const ctx = udev->ctx; + struct usbh_context *const ctx = udev->ctx; return uhc_ep_dequeue(ctx->dev, xfer); } diff --git a/subsys/usb/host/usbh_internal.h b/subsys/usb/host/usbh_internal.h index c731520118ed2..8fa8e7d48e19c 100644 --- a/subsys/usb/host/usbh_internal.h +++ b/subsys/usb/host/usbh_internal.h @@ -10,6 +10,6 @@ #include #include -int usbh_init_device_intl(struct usbh_contex *const uhs_ctx); +int usbh_init_device_intl(struct usbh_context *const uhs_ctx); #endif /* ZEPHYR_INCLUDE_USBH_INTERNAL_H */ diff --git a/subsys/usb/host/usbip.c b/subsys/usb/host/usbip.c index 773acbb55d32e..385f763463cdd 100644 --- a/subsys/usb/host/usbip.c +++ b/subsys/usb/host/usbip.c @@ -48,7 +48,7 @@ struct usbip_dev_ctx { /* Context of the exported bus (not really used yet) */ struct usbip_bus_ctx { - struct usbh_contex *uhs_ctx; + struct usbh_context *uhs_ctx; struct usbip_dev_ctx devs[CONFIG_USBIP_DEVICES_COUNT]; uint8_t busnum; }; From 1bad1d091af5740257b5e2d5eb360a636bf440d4 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Sat, 26 Jul 2025 15:42:36 +0000 Subject: [PATCH 02/31] usb: host: ensure device configuration descriptors are nil-terminated Allocate extra room at the end of the USB descriptor buffer to ensure that the device ends with `desc->bLength == 0`. For now, validating `desc->bLength` from the device to ensure no access past the buffer is still not done. Signed-off-by: Josuah Demangeon --- subsys/usb/host/usbh_device.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subsys/usb/host/usbh_device.c b/subsys/usb/host/usbh_device.c index 99970ffb0c4ed..76f70b2734148 100644 --- a/subsys/usb/host/usbh_device.c +++ b/subsys/usb/host/usbh_device.c @@ -397,7 +397,7 @@ int usbh_device_set_configuration(struct usb_device *const udev, const uint8_t n } udev->cfg_desc = k_heap_alloc(&usb_device_heap, - cfg_desc.wTotalLength, + cfg_desc.wTotalLength + sizeof(struct usb_desc_header), K_NO_WAIT); if (udev->cfg_desc == NULL) { LOG_ERR("Failed to allocate memory for configuration descriptor"); From 4202f6e62ba020da767cd222c76fbaa6481090fd Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Fri, 8 Aug 2025 14:13:16 +0000 Subject: [PATCH 03/31] usb: host: add host status to the host context Add a "struct usbh_status" that contains a bitmask of flags to keep track of the global state of the host context, like done for the device_next implementation. Add helpers to check the status. Signed-off-by: Josuah Demangeon --- include/zephyr/usb/usbh.h | 12 ++++++++++++ subsys/usb/host/usbh_host.h | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 subsys/usb/host/usbh_host.h diff --git a/include/zephyr/usb/usbh.h b/include/zephyr/usb/usbh.h index e2e6f3074000e..b6526af828bc9 100644 --- a/include/zephyr/usb/usbh.h +++ b/include/zephyr/usb/usbh.h @@ -33,6 +33,16 @@ extern "C" { * @{ */ +/** + * USB host support status + */ +struct usbh_status { + /** USB host support is initialized */ + unsigned int initialized : 1; + /** USB host support is enabled */ + unsigned int enabled : 1; +}; + /** * USB host support runtime context */ @@ -43,6 +53,8 @@ struct usbh_context { struct k_mutex mutex; /** Pointer to UHC device struct */ const struct device *dev; + /** Status of the USB host support */ + struct usbh_status status; /** USB device list */ sys_dlist_t udevs; /** USB root device */ diff --git a/subsys/usb/host/usbh_host.h b/subsys/usb/host/usbh_host.h new file mode 100644 index 0000000000000..5e16bf7511b9f --- /dev/null +++ b/subsys/usb/host/usbh_host.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_USBH_HOST_H +#define ZEPHYR_INCLUDE_USBH_HOST_H + +#include +#include + +/** + * @brief Check whether USB host is enabled + * + * @param[in] uhs_ctx Pointer to a host context + * + * @return true if USB host is in enabled, false otherwise + */ +static inline bool usbd_is_enabled(const struct usbh_context *const uhs_ctx) +{ + return uhs_ctx->status.enabled; +} + +/** + * @brief Check whether USB host is enabled + * + * @param[in] uhs_ctx Pointer to a host context + * + * @return true if USB host is in enabled, false otherwise + */ +static inline bool usbd_is_initialized(const struct usbh_context *const uhs_ctx) +{ + return uhs_ctx->status.initialized; +} + +#endif /* ZEPHYR_INCLUDE_USBH_HOST_H */ From dedc29ad6d9b8919292f562820aee93be6b82869 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Fri, 8 Aug 2025 21:00:09 +0000 Subject: [PATCH 04/31] usb: host: wrap the mutex lock with a helper Hide the mutex lock details inside a wrapper call, like done for the device stack API. This is not used for the per-device lock of the host stack which use a different mutex lock. Signed-off-by: Josuah Demangeon --- subsys/usb/host/usbh_api.c | 17 +++++++++-------- subsys/usb/host/usbh_host.h | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/subsys/usb/host/usbh_api.c b/subsys/usb/host/usbh_api.c index 8fc1a1869deb2..8501ee044b2ad 100644 --- a/subsys/usb/host/usbh_api.c +++ b/subsys/usb/host/usbh_api.c @@ -6,6 +6,7 @@ #include #include +#include "usbh_host.h" #include "usbh_internal.h" #include @@ -15,7 +16,7 @@ int usbh_init(struct usbh_context *uhs_ctx) { int ret; - k_mutex_lock(&uhs_ctx->mutex, K_FOREVER); + usbh_host_lock(uhs_ctx); if (!device_is_ready(uhs_ctx->dev)) { LOG_ERR("USB host controller is not ready"); @@ -32,7 +33,7 @@ int usbh_init(struct usbh_context *uhs_ctx) ret = usbh_init_device_intl(uhs_ctx); init_exit: - k_mutex_unlock(&uhs_ctx->mutex); + usbh_host_unlock(uhs_ctx); return ret; } @@ -40,7 +41,7 @@ int usbh_enable(struct usbh_context *uhs_ctx) { int ret; - k_mutex_lock(&uhs_ctx->mutex, K_FOREVER); + usbh_host_lock(uhs_ctx); if (!uhc_is_initialized(uhs_ctx->dev)) { LOG_WRN("USB host controller is not initialized"); @@ -61,7 +62,7 @@ int usbh_enable(struct usbh_context *uhs_ctx) } enable_exit: - k_mutex_unlock(&uhs_ctx->mutex); + usbh_host_unlock(uhs_ctx); return ret; } @@ -74,14 +75,14 @@ int usbh_disable(struct usbh_context *uhs_ctx) return 0; } - k_mutex_lock(&uhs_ctx->mutex, K_FOREVER); + usbh_host_lock(uhs_ctx); ret = uhc_disable(uhs_ctx->dev); if (ret) { LOG_ERR("Failed to disable USB controller"); } - k_mutex_unlock(&uhs_ctx->mutex); + usbh_host_unlock(uhs_ctx); return 0; } @@ -90,14 +91,14 @@ int usbh_shutdown(struct usbh_context *const uhs_ctx) { int ret; - k_mutex_lock(&uhs_ctx->mutex, K_FOREVER); + usbh_host_lock(uhs_ctx); ret = uhc_shutdown(uhs_ctx->dev); if (ret) { LOG_ERR("Failed to shutdown USB device"); } - k_mutex_unlock(&uhs_ctx->mutex); + usbh_host_unlock(uhs_ctx); return ret; } diff --git a/subsys/usb/host/usbh_host.h b/subsys/usb/host/usbh_host.h index 5e16bf7511b9f..5dc8e6070d60e 100644 --- a/subsys/usb/host/usbh_host.h +++ b/subsys/usb/host/usbh_host.h @@ -34,4 +34,24 @@ static inline bool usbd_is_initialized(const struct usbh_context *const uhs_ctx) return uhs_ctx->status.initialized; } +/** + * @brief Lock the USB host stack context + * + * @param[in] uhs_ctx Pointer to a host context + */ +static inline void usbh_host_lock(struct usbh_context *const uhs_ctx) +{ + k_mutex_lock(&uhs_ctx->mutex, K_FOREVER); +} + +/** + * @brief Unlock the USB host stack context + * + * @param[in] uhs_ctx Pointer to a host context + */ +static inline void usbh_host_unlock(struct usbh_context *const uhs_ctx) +{ + k_mutex_unlock(&uhs_ctx->mutex); +} + #endif /* ZEPHYR_INCLUDE_USBH_HOST_H */ From 2a7863ad60c2709672480be29c49d9a6e98811dd Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Thu, 14 Aug 2025 09:24:16 +0000 Subject: [PATCH 05/31] samples: usb: shell: update the output in the README Update the USB shell sample to the new syntax. This is based on tests done with native_sim, along with the virtual.conf, virtual.overlay and device_and_host_prj.conf extra config files. Signed-off-by: Josuah Demangeon --- samples/subsys/usb/shell/README.rst | 50 ++++++++++++++--------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/samples/subsys/usb/shell/README.rst b/samples/subsys/usb/shell/README.rst index 5e5437b56dc35..5fb42f2c3095e 100644 --- a/samples/subsys/usb/shell/README.rst +++ b/samples/subsys/usb/shell/README.rst @@ -50,37 +50,37 @@ Sample shell interaction .. code-block:: console - uart:~$ usbd defaults - dev: USB descriptors initialized - uart:~$ usbd config add 1 - uart:~$ usbd class add foobaz 1 - dev: added USB class foobaz to configuration 1 - uart:~$ usbd init + *** Booting Zephyr OS build v4.2.0-1588-g83f1bd7341de *** + uart:~$ usbd defcfg + dev: added default string descriptors + dev: register FS loopback_0 + dev: register HS loopback_0 dev: USB initialized uart:~$ usbh init host: USB host initialized uart:~$ usbh enable host: USB host enabled - [611:00:28.620,000] usbd_core: VBUS detected event uart:~$ usbh bus resume host: USB bus resumed uart:~$ usbd enable - host: USB device connected dev: USB enabled - uart:~$ usbh device descriptor device 0 - host: transfer finished 0x20006250, err 0 - 00000000: 80 06 00 01 00 00 12 00 |........ | - bLength 18 - bDescriptorType 1 - bcdUSB 200 - bDeviceClass 239 - bDeviceSubClass 2 - bDeviceProtocol 1 - bMaxPacketSize0 64 - idVendor 2fe3 - idProduct ffff - bcdDevice 301 - iManufacturer 1 - iProduct 2 - iSerial 3 - bNumConfigurations 1 + [160:04:13.870,000] usb_loopback: Enable loopback_0 + uart:~$ usbh device list + 1 + uart:~$ usbh device descriptor device 1 + host: USB device with address 1 + bLength 18 + bDescriptorType 1 + bcdUSB 200 + bDeviceClass 239 + bDeviceSubClass 2 + bDeviceProtocol 1 + bMaxPacketSize0 64 + idVendor 2fe3 + idProduct ffff + bcdDevice 402 + iManufacturer 1 + iProduct 2 + iSerial 3 + bNumConfigurations 1 + uart:~$ From 1ddca71f6cad63e43b85cc90e4b2ad395c6fd142 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Thu, 14 Aug 2025 14:45:40 +0000 Subject: [PATCH 06/31] usb: host: class: implement more of the class/data struct fields Add a "struct usbh_class_api" for the host implementation, and move all the function poitners to it, as it is done for the device support. Add more fields to the host struct usbh_class_data mirrorring the device side struct, including the API pointer. Signed-off-by: Josuah Demangeon --- include/zephyr/usb/usbh.h | 45 ++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/include/zephyr/usb/usbh.h b/include/zephyr/usb/usbh.h index b6526af828bc9..cd82f18af61d8 100644 --- a/include/zephyr/usb/usbh.h +++ b/include/zephyr/usb/usbh.h @@ -61,6 +61,8 @@ struct usbh_context { struct usb_device *root; /** Allocated device addresses bit array */ struct sys_bitarray *addr_ba; + /** List of registered classes (functions) */ + sys_slist_t class_list; }; #define USBH_CONTROLLER_DEFINE(device_name, uhc_dev) \ @@ -84,28 +86,45 @@ struct usbh_code_triple { uint8_t proto; }; +struct usbh_class_data; + /** - * @brief USB host class data and class instance API + * @brief USB host class instance API */ -struct usbh_class_data { - /** Class code supported by this instance */ - struct usbh_code_triple code; - +struct usbh_class_api { /** Initialization of the class implementation */ - /* int (*init)(struct usbh_context *const uhs_ctx); */ + int (*init)(struct usbh_class_data *cdata); /** Request completion event handler */ - int (*request)(struct usbh_context *const uhs_ctx, - struct uhc_transfer *const xfer, int err); + int (*request)(struct usbh_class_data *cdata, + struct uhc_transfer *const xfer, int err); /** Device connected handler */ - int (*connected)(struct usbh_context *const uhs_ctx); + int (*connected)(struct usbh_class_data *cdata, + void *desc_start_addr, + void *desc_end_addr); /** Device removed handler */ - int (*removed)(struct usbh_context *const uhs_ctx); + int (*removed)(struct usbh_class_data *cdata); /** Bus remote wakeup handler */ - int (*rwup)(struct usbh_context *const uhs_ctx); + int (*rwup)(struct usbh_class_data *cdata); /** Bus suspended handler */ - int (*suspended)(struct usbh_context *const uhs_ctx); + int (*suspended)(struct usbh_class_data *cdata); /** Bus resumed handler */ - int (*resumed)(struct usbh_context *const uhs_ctx); + int (*resumed)(struct usbh_class_data *cdata); +}; + +/** + * @brief USB host class instance data + */ +struct usbh_class_data { + /** Name of the USB host class instance */ + const char *name; + /** Pointer to USB host stack context structure */ + struct usbh_context *uhs_ctx; + /** Class code supported by this instance */ + struct usbh_code_triple code; + /** Pointer to host support class API */ + struct usbh_class_api *api; + /** Pointer to private data */ + void *priv; }; /** From 7aa4f8db5cd363fed291d2888a3b778ba9496148 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Fri, 15 Aug 2025 16:10:46 +0000 Subject: [PATCH 07/31] usb: host: introduce wrappers to access the class function pointers Add API wrappers around the function pointers in struct usbh_class_api, while also documenting the USB host class internal API. Signed-off-by: Josuah Demangeon --- subsys/usb/host/usbh_class_api.h | 173 +++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 subsys/usb/host/usbh_class_api.h diff --git a/subsys/usb/host/usbh_class_api.h b/subsys/usb/host/usbh_class_api.h new file mode 100644 index 0000000000000..d793b6fb78631 --- /dev/null +++ b/subsys/usb/host/usbh_class_api.h @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief USB host stack class instances API + * + * This file contains the USB host stack class instances API. + */ + +#ifndef ZEPHYR_INCLUDE_USBH_CLASS_API_H +#define ZEPHYR_INCLUDE_USBH_CLASS_API_H + +#include + +/** + * @brief Initialization of the class implementation + * + * This is called for each instance during the initialization phase, + * for every registered class. + * It can be used to initialize underlying systems. + * + * @param[in] c_data Pointer to USB host class data + * + * @return 0 on success, negative error code on failure. + */ +static inline int usbh_class_init(struct usbh_class_data *const c_data) +{ + const struct usbh_class_api *api = c_data->api; + + if (api->init != NULL) { + return api->init(c_data); + } + + return -ENOTSUP; +} + +/** + * @brief Request completion event handler + * + * Called upon completion of a request made by the host to this class. + * + * @param[in] c_data Pointer to USB host class data + * @param[in] xfer Completed transfer + * @param[in] err Result of the transfer. 0 if the transfer was successful. + * + * @return 0 on success, negative error code on failure. + */ +static inline int usbh_class_request(struct usbh_class_data *const c_data, + struct uhc_transfer *const xfer, int err) +{ + const struct usbh_class_api *api = c_data->api; + + if (api->request != NULL) { + return api->request(c_data, xfer, err); + } + + return -ENOTSUP; +} + +/** + * @brief Device connected handler + * + * Called when a device is connected to the bus + * and it matches the class filters of this instance. + * + * @param[in] c_data Pointer to USB host class data + * @param[in] desc_start_addr Pointer to the start of the descriptor + * @param[in] desc_end_addr Pointer after the end of the USB descriptor + * + * @return 0 on success, negative error code on failure. + */ +static inline int usbh_class_connected(struct usbh_class_data *const c_data, + void *const desc_start_addr, + void *const desc_end_addr) +{ + const struct usbh_class_api *api = c_data->api; + + if (api->connected != NULL) { + return api->connected(c_data, desc_start_addr, desc_end_addr); + } + + return -ENOTSUP; +} + +/** + * @brief Device removed handler + * + * Called when the device is removed from the bus + * and it matches the class filters of this instance. + * + * @param[in] c_data Pointer to USB host class data + * + * @return 0 on success, negative error code on failure. + */ +static inline int usbh_class_removed(struct usbh_class_data *const c_data) +{ + const struct usbh_class_api *api = c_data->api; + + if (api->removed != NULL) { + return api->removed(c_data); + } + + return -ENOTSUP; +} + +/** + * @brief Bus remote wakeup handler + * + * Called when the device trigger a remote wakeup to the host. + * and it matches the class filters. + * + * @param[in] c_data Pointer to USB host class data + * + * @return 0 on success, negative error code on failure. + */ +static inline int usbh_class_rwup(struct usbh_class_data *const c_data) +{ + const struct usbh_class_api *api = c_data->api; + + if (api->rwup != NULL) { + return api->rwup(c_data); + } + + return -ENOTSUP; +} + +/** + * @brief Bus suspended handler + * + * Called when the host has suspending the bus. + * It can be used to suspend underlying systems. + * + * @param[in] c_data Pointer to USB host class data + * + * @return 0 on success, negative error code on failure. + */ +static inline int usbh_class_suspended(struct usbh_class_data *const c_data) +{ + const struct usbh_class_api *api = c_data->api; + + if (api->suspended != NULL) { + return api->suspended(c_data); + } + + return -ENOTSUP; +} + +/** + * @brief Bus resumed handler + * + * Called when the host resumes the activity on a bus. + * It can be used to wake-up underlying systems. + * + * @param[in] c_data Pointer to USB host class data + * + * @return 0 on success, negative error code on failure. + */ +static inline int usbh_class_resumed(struct usbh_class_data *const c_data) +{ + const struct usbh_class_api *api = c_data->api; + + if (api->resumed != NULL) { + return api->resumed(c_data); + } + + return -ENOTSUP; +} + +#endif /* ZEPHYR_INCLUDE_USBD_CLASS_API_H */ From bdd549fbb7b54cede5ecfd339c13032d67026fe0 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Fri, 15 Aug 2025 19:04:59 +0000 Subject: [PATCH 08/31] usb: host: introduce usbh_class with init functions Add functions to register all classes as part of a new usbh_class.c and a matching usbh_class.h. These functions are called from the function usbh_init_device_intl() in usbh_core.c to initialize every class upon connection of a device. The class instances are registered and connected together as a linked list. Co-authored-by: Aiden Hu Signed-off-by: Josuah Demangeon --- include/zephyr/usb/usbh.h | 2 ++ subsys/usb/host/CMakeLists.txt | 3 +- subsys/usb/host/usbh_class.c | 59 ++++++++++++++++++++++++++++++++++ subsys/usb/host/usbh_class.h | 23 +++++++++++++ subsys/usb/host/usbh_core.c | 19 +++++++---- 5 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 subsys/usb/host/usbh_class.c create mode 100644 subsys/usb/host/usbh_class.h diff --git a/include/zephyr/usb/usbh.h b/include/zephyr/usb/usbh.h index cd82f18af61d8..a4a30d26c7a3c 100644 --- a/include/zephyr/usb/usbh.h +++ b/include/zephyr/usb/usbh.h @@ -119,6 +119,8 @@ struct usbh_class_data { const char *name; /** Pointer to USB host stack context structure */ struct usbh_context *uhs_ctx; + /** System linked list node for registered classes */ + sys_snode_t node; /** Class code supported by this instance */ struct usbh_code_triple code; /** Pointer to host support class API */ diff --git a/subsys/usb/host/CMakeLists.txt b/subsys/usb/host/CMakeLists.txt index f48f5b8c71483..b63af6d6a3473 100644 --- a/subsys/usb/host/CMakeLists.txt +++ b/subsys/usb/host/CMakeLists.txt @@ -5,9 +5,10 @@ zephyr_library() zephyr_library_include_directories(${CMAKE_CURRENT_SOURCE_DIR}) zephyr_library_sources( + usbh_api.c usbh_ch9.c + usbh_class.c usbh_core.c - usbh_api.c usbh_device.c ) diff --git a/subsys/usb/host/usbh_class.c b/subsys/usb/host/usbh_class.c new file mode 100644 index 0000000000000..3543f37922c68 --- /dev/null +++ b/subsys/usb/host/usbh_class.c @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "usbh_class.h" +#include "usbh_class_api.h" + +LOG_MODULE_REGISTER(usbh_class, CONFIG_USBH_LOG_LEVEL); + +int usbh_register_all_classes(struct usbh_context *uhs_ctx) +{ + int registered_count = 0; + + STRUCT_SECTION_FOREACH(usbh_class_data, cdata_existing) { + struct usbh_class_data *cdata_registered; + bool already_registered = false; + + /* Check if already registered */ + SYS_SLIST_FOR_EACH_CONTAINER(&uhs_ctx->class_list, cdata_registered, node) { + if (cdata_existing == cdata_registered) { + already_registered = true; + break; + } + } + + if (!already_registered) { + sys_slist_append(&uhs_ctx->class_list, &cdata_existing->node); + registered_count++; + LOG_DBG("Auto-registered class: %s", cdata_existing->name); + } + } + + LOG_INF("Auto-registered %d classes to controller %s", + registered_count, uhs_ctx->name); + + return 0; +} + +int usbh_init_registered_classes(struct usbh_context *uhs_ctx) +{ + struct usbh_class_data *cdata; + int ret; + + SYS_SLIST_FOR_EACH_CONTAINER(&uhs_ctx->class_list, cdata, node) { + ret = usbh_class_init(cdata); + if (ret != 0) { + LOG_ERR("Failed to initialize class instance"); + return ret; + } + } + + return 0; +} diff --git a/subsys/usb/host/usbh_class.h b/subsys/usb/host/usbh_class.h new file mode 100644 index 0000000000000..d419326dbbe2b --- /dev/null +++ b/subsys/usb/host/usbh_class.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_USBD_CLASS_H +#define ZEPHYR_INCLUDE_USBD_CLASS_H + +#include + +/** + * @brief Auto-register all compile-time defined class drivers + */ +int usbh_register_all_classes(struct usbh_context *uhs_ctx); + +/** + * @brief Initialize registered class drivers + */ +int usbh_init_registered_classes(struct usbh_context *uhs_ctx); + +#endif /* ZEPHYR_INCLUDE_USBD_CLASS_H */ diff --git a/subsys/usb/host/usbh_core.c b/subsys/usb/host/usbh_core.c index 1b654b5041133..d1dc0022dfaaa 100644 --- a/subsys/usb/host/usbh_core.c +++ b/subsys/usb/host/usbh_core.c @@ -10,9 +10,11 @@ #include #include #include +#include #include "usbh_internal.h" #include "usbh_device.h" +#include "usbh_class.h" #include LOG_MODULE_REGISTER(uhs, CONFIG_USBH_LOG_LEVEL); @@ -193,13 +195,18 @@ int usbh_init_device_intl(struct usbh_context *const uhs_ctx) } sys_dlist_init(&uhs_ctx->udevs); + sys_slist_init(&uhs_ctx->class_list); - STRUCT_SECTION_FOREACH(usbh_class_data, cdata) { - /* - * For now, we have not implemented any class drivers, - * so just keep it as placeholder. - */ - break; + ret = usbh_register_all_classes(uhs_ctx); + if (ret != 0) { + LOG_ERR("Failed to auto-register class instances"); + return ret; + } + + ret = usbh_init_registered_classes(uhs_ctx); + if (ret != 0) { + LOG_ERR("Failed to initialize all registered class instances"); + return ret; } return 0; From a40fb36cb20fdb8640c2a14600023f8b4cdcf571 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Sat, 16 Aug 2025 19:34:25 +0000 Subject: [PATCH 09/31] usb: host: implement class filtering and descriptor browsing Add utilities to filter a class and search the next descriptor of a given type. This can be used to in combination to seek device information from the USB descriptors and try to match a host class instance given a set of filters. Co-authored-by: Aiden Hu Signed-off-by: Josuah Demangeon --- include/zephyr/usb/usbh.h | 30 +++++++++++++++++++++++++--- subsys/usb/host/CMakeLists.txt | 1 + subsys/usb/host/usbh_class.c | 36 ++++++++++++++++++++++++++++++++++ subsys/usb/host/usbh_class.h | 11 +++++++++++ subsys/usb/host/usbh_desc.c | 29 +++++++++++++++++++++++++++ subsys/usb/host/usbh_desc.h | 35 +++++++++++++++++++++++++++++++++ 6 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 subsys/usb/host/usbh_desc.c create mode 100644 subsys/usb/host/usbh_desc.h diff --git a/include/zephyr/usb/usbh.h b/include/zephyr/usb/usbh.h index a4a30d26c7a3c..7c94de803f970 100644 --- a/include/zephyr/usb/usbh.h +++ b/include/zephyr/usb/usbh.h @@ -1,5 +1,6 @@ /* - * Copyright (c) 2022 Nordic Semiconductor ASA + * Copyright (c) 2025 Nordic Semiconductor ASA + * Copyright 2025 NXP * * SPDX-License-Identifier: Apache-2.0 */ @@ -111,6 +112,29 @@ struct usbh_class_api { int (*resumed)(struct usbh_class_data *cdata); }; +/** Match a class code triple */ +#define USBH_CLASS_FILTER_CODE_TRIPLE BIT(0) + +/** Match a device's vendor ID */ +#define USBH_CLASS_FILTER_VID BIT(1) + +/** Match a device's product ID */ +#define USBH_CLASS_FILTER_PID BIT(2) + +/** + * @brief Filter rule for matching a host class instance to a device class + */ +struct usbh_class_filter { + /** Mask of match types for selecting which rules to apply */ + uint32_t flags; + /** The device's class code, subclass code, protocol code. */ + struct usbh_code_triple code_triple; + /** Vendor ID */ + uint16_t vid; + /** Product ID */ + uint16_t pid; +}; + /** * @brief USB host class instance data */ @@ -121,8 +145,8 @@ struct usbh_class_data { struct usbh_context *uhs_ctx; /** System linked list node for registered classes */ sys_snode_t node; - /** Class code supported by this instance */ - struct usbh_code_triple code; + /** Table of filter rules used to match device classes */ + const struct usbh_class_filter *filters; /** Pointer to host support class API */ struct usbh_class_api *api; /** Pointer to private data */ diff --git a/subsys/usb/host/CMakeLists.txt b/subsys/usb/host/CMakeLists.txt index b63af6d6a3473..b9afd0a65801d 100644 --- a/subsys/usb/host/CMakeLists.txt +++ b/subsys/usb/host/CMakeLists.txt @@ -9,6 +9,7 @@ zephyr_library_sources( usbh_ch9.c usbh_class.c usbh_core.c + usbh_desc.c usbh_device.c ) diff --git a/subsys/usb/host/usbh_class.c b/subsys/usb/host/usbh_class.c index 3543f37922c68..6794aa1f662ec 100644 --- a/subsys/usb/host/usbh_class.c +++ b/subsys/usb/host/usbh_class.c @@ -57,3 +57,39 @@ int usbh_init_registered_classes(struct usbh_context *uhs_ctx) return 0; } + +bool usbh_class_is_matching(struct usbh_class_data *cdata, + struct usbh_class_filter *device_info) +{ + /* Traverse the filter table until a terminator (empty flags) is found */ + for (int i = 0; cdata->filters[i].flags != 0; i++) { + const struct usbh_class_filter *filter = &cdata->filters[i]; + + if (filter->flags & USBH_CLASS_FILTER_VID) { + if (filter->vid != device_info->vid) { + continue; + } + } + + if (filter->flags & USBH_CLASS_FILTER_VID) { + if (filter->vid == device_info->vid) { + continue; + } + } + + if (filter->flags & USBH_CLASS_FILTER_CODE_TRIPLE) { + if (filter->code_triple.dclass != device_info->code_triple.dclass || + (filter->code_triple.sub != 0xFF && + filter->code_triple.sub != device_info->code_triple.sub) || + (filter->code_triple.proto != 0x00 && + filter->code_triple.proto != device_info->code_triple.proto)) { + continue; + } + } + + /* All the filters enabled did match */ + return true; + } + + return false; +} diff --git a/subsys/usb/host/usbh_class.h b/subsys/usb/host/usbh_class.h index d419326dbbe2b..0f96d17a95f64 100644 --- a/subsys/usb/host/usbh_class.h +++ b/subsys/usb/host/usbh_class.h @@ -20,4 +20,15 @@ int usbh_register_all_classes(struct usbh_context *uhs_ctx); */ int usbh_init_registered_classes(struct usbh_context *uhs_ctx); +/** + * @brief Check if a device's descriptor information matches a host class instance + * + * @param cdata Pointer to class driver data + * @param device_info Information from the device descriptors + * + * @return true if matched, false otherwise + */ +bool usbh_class_is_matching(struct usbh_class_data *cdata, + struct usbh_class_filter *device_info); + #endif /* ZEPHYR_INCLUDE_USBD_CLASS_H */ diff --git a/subsys/usb/host/usbh_desc.c b/subsys/usb/host/usbh_desc.c new file mode 100644 index 0000000000000..e73e12f8dc29a --- /dev/null +++ b/subsys/usb/host/usbh_desc.c @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * Copyright NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "usbh_desc.h" + +struct usb_desc_header *usbh_desc_get_by_type(const uint8_t *const start_addr, + const uint8_t *const end_addr, + uint32_t type_mask) +{ + const uint8_t *curr_addr = start_addr; + + while (curr_addr < end_addr) { + struct usb_desc_header *desc = (void *)curr_addr; + + if (desc->bLength == 0) { + break; + } + + if ((BIT(desc->bDescriptorType) & type_mask) != 0) { + return desc; + } + } + + return NULL; +} diff --git a/subsys/usb/host/usbh_desc.h b/subsys/usb/host/usbh_desc.h new file mode 100644 index 0000000000000..6d3183952d07c --- /dev/null +++ b/subsys/usb/host/usbh_desc.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Descriptor matching and searching utilities + * + * This file contains utilities to browse USB descriptors returned by a device + * according to the USB Specification 2.0 Chapter 9. + */ + +#ifndef ZEPHYR_INCLUDE_USBH_DESC_H +#define ZEPHYR_INCLUDE_USBH_DESC_H + +#include + +#include + +/** + * @brief Search the first descriptor matching the selected type(s). + * + * @param[in] start_addr Pointer to the beginning of the descriptor array; to search + * @param[in] end_addr Pointer after the end of the descriptor array to search + * @param[in] type_mask Bitmask of bDescriptorType values to match + * + * @return A pointer to the first descriptor matching + */ +struct usb_desc_header *usbh_desc_get_by_type(const uint8_t *const start_addr, + const uint8_t *const end_addr, + uint32_t type_mask); + +#endif /* ZEPHYR_INCLUDE_USBH_DESC_H */ From e57477d5dc09c250fb1591f6b423f4757f5586d5 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Fri, 25 Jul 2025 12:09:06 +0000 Subject: [PATCH 10/31] usb: uvc: move the header definition to include/ Move the UVC header with all the definitions from the UVC standard to share it between USB host and device class implementation. Signed-off-by: Josuah Demangeon --- .../class/usbd_uvc.h => include/zephyr/usb/class/usb_uvc.h | 0 subsys/usb/device_next/class/usbd_uvc.c | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename subsys/usb/device_next/class/usbd_uvc.h => include/zephyr/usb/class/usb_uvc.h (100%) diff --git a/subsys/usb/device_next/class/usbd_uvc.h b/include/zephyr/usb/class/usb_uvc.h similarity index 100% rename from subsys/usb/device_next/class/usbd_uvc.h rename to include/zephyr/usb/class/usb_uvc.h diff --git a/subsys/usb/device_next/class/usbd_uvc.c b/subsys/usb/device_next/class/usbd_uvc.c index 9038a8e0d3f15..8b1cd235af844 100644 --- a/subsys/usb/device_next/class/usbd_uvc.c +++ b/subsys/usb/device_next/class/usbd_uvc.c @@ -24,8 +24,8 @@ #include #include #include +#include -#include "usbd_uvc.h" #include "../../../drivers/video/video_ctrls.h" #include "../../../drivers/video/video_device.h" From d6ba92437a983feedeaffedf7349bbcc1f016a08 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Sun, 17 Aug 2025 22:49:44 +0000 Subject: [PATCH 11/31] usb: uvc: prepare the header to be used for both host and device When USB device is not defined, there is no macro for the maximum number of dwFrameInterval[] defined, so default to 1 instead of CONFIG_USBD_VIDEO_MAX_FRMIVAL. Signed-off-by: Josuah Demangeon --- include/zephyr/usb/class/usb_uvc.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/zephyr/usb/class/usb_uvc.h b/include/zephyr/usb/class/usb_uvc.h index 3364f83258d11..b737efc5c7e2d 100644 --- a/include/zephyr/usb/class/usb_uvc.h +++ b/include/zephyr/usb/class/usb_uvc.h @@ -422,7 +422,11 @@ struct uvc_frame_discrete_descriptor { uint32_t dwMaxVideoFrameBufferSize; uint32_t dwDefaultFrameInterval; uint8_t bFrameIntervalType; +#ifdef CONFIG_USBD_VIDEO_MAX_FRMIVAL uint32_t dwFrameInterval[CONFIG_USBD_VIDEO_MAX_FRMIVAL]; +#else + uint32_t dwFrameInterval[1]; +#endif } __packed; struct uvc_color_descriptor { From 94c43f42b4fe716f5e6bc36bf6cdaf154203fc93 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Sun, 17 Aug 2025 19:18:33 +0000 Subject: [PATCH 12/31] uvc: device: move helper functions to a common directory Move UVC helper functions to a file shared between UVC host and device. The arrays are not visible anymore from either USB host or device, but instead accessed through a front-end funciton. Signed-off-by: Josuah Demangeon --- subsys/usb/CMakeLists.txt | 2 + subsys/usb/common/CMakeLists.txt | 8 + subsys/usb/common/usb_common_uvc.c | 234 ++++++++++++++++++++ subsys/usb/common/usb_common_uvc.h | 81 +++++++ subsys/usb/device_next/class/usbd_uvc.c | 278 ++---------------------- 5 files changed, 343 insertions(+), 260 deletions(-) create mode 100644 subsys/usb/common/CMakeLists.txt create mode 100644 subsys/usb/common/usb_common_uvc.c create mode 100644 subsys/usb/common/usb_common_uvc.h diff --git a/subsys/usb/CMakeLists.txt b/subsys/usb/CMakeLists.txt index 6b968a4bf894e..098075eaa4e78 100644 --- a/subsys/usb/CMakeLists.txt +++ b/subsys/usb/CMakeLists.txt @@ -3,5 +3,7 @@ add_subdirectory_ifdef(CONFIG_USB_DEVICE_STACK device) add_subdirectory_ifdef(CONFIG_USB_DEVICE_STACK_NEXT device_next) +add_subdirectory_ifdef(CONFIG_USB_DEVICE_STACK_NEXT common) add_subdirectory_ifdef(CONFIG_USB_HOST_STACK host) +add_subdirectory_ifdef(CONFIG_USB_HOST_STACK common) add_subdirectory_ifdef(CONFIG_USBC_STACK usb_c) diff --git a/subsys/usb/common/CMakeLists.txt b/subsys/usb/common/CMakeLists.txt new file mode 100644 index 0000000000000..dce218cebb564 --- /dev/null +++ b/subsys/usb/common/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright (c) 2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() +zephyr_library_include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +zephyr_library_sources_ifdef(CONFIG_USBD_VIDEO_CLASS usb_common_uvc.c) +zephyr_library_sources_ifdef(CONFIG_USBH_VIDEO_CLASS usb_common_uvc.c) diff --git a/subsys/usb/common/usb_common_uvc.c b/subsys/usb/common/usb_common_uvc.c new file mode 100644 index 0000000000000..afa43c67e2bab --- /dev/null +++ b/subsys/usb/common/usb_common_uvc.c @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include +#include + +#include "usb_common_uvc.h" + +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 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, + }, +}; + +int uvc_get_control_map(uint8_t subtype, const struct uvc_control_map **map, size_t *length) +{ + switch (subtype) { + case UVC_VC_INPUT_TERMINAL: + *map = uvc_control_map_ct; + *length = ARRAY_SIZE(uvc_control_map_ct); + break; + case UVC_VC_SELECTOR_UNIT: + *map = uvc_control_map_su; + *length = ARRAY_SIZE(uvc_control_map_su); + break; + case UVC_VC_PROCESSING_UNIT: + *map = uvc_control_map_pu; + *length = ARRAY_SIZE(uvc_control_map_pu); + break; + case UVC_VC_EXTENSION_UNIT: + *map = uvc_control_map_xu; + *length = ARRAY_SIZE(uvc_control_map_xu); + break; + default: + return -EINVAL; + } + + return 0; +} + +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); +} + +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; +} diff --git a/subsys/usb/common/usb_common_uvc.h b/subsys/usb/common/usb_common_uvc.h new file mode 100644 index 0000000000000..aed1f23fde6dc --- /dev/null +++ b/subsys/usb/common/usb_common_uvc.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_USB_COMMON_UVC_H +#define ZEPHYR_INCLUDE_USB_COMMON_UVC_H + +#include + +/** + * @brief Type of value used by the USB protocol for this control. + */ +enum uvc_control_type { + /** Signed integer control type */ + UVC_CONTROL_SIGNED, + /** Unsigned integer control type */ + UVC_CONTROL_UNSIGNED, +}; + +/** + * @brief 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; +}; + +/** + * @brief Mapping between UVC GUIDs and standard FourCC. + */ +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]; +}; + +/** + * @brief Get a conversion table for a given control unit type + * + * The mappings contains information about how UVC control structures are related to + * video control structures. + * + * @param subtype The field bDescriptorSubType of a descriptor of type USB_DESC_CS_INTERFACE. + * @param map Filled with a pointer to the conversion table + * @param length Filled with the number of elements in that conversion table. + * + * @return 0 on success, negative code on error. + */ +int uvc_get_control_map(uint8_t subtype, const struct uvc_control_map **map, size_t *length); + +/** + * @brief Convert a standard FourCC to an equivalent UVC GUID. + * + * @param guid Array to a GUID, filled in binary format + * @param fourcc Four character code + */ +void uvc_fourcc_to_guid(uint8_t guid[16], const uint32_t fourcc); + +/** + * @brief Convert an UVC GUID to a standard FourCC + * + * @param guid GUID, to convert + * + * @return Four Character Code + */ +uint32_t uvc_guid_to_fourcc(const uint8_t guid[16]); + +#endif /* ZEPHYR_INCLUDE_USB_COMMON_UVC_H */ diff --git a/subsys/usb/device_next/class/usbd_uvc.c b/subsys/usb/device_next/class/usbd_uvc.c index 8b1cd235af844..b13aea88d0b73 100644 --- a/subsys/usb/device_next/class/usbd_uvc.c +++ b/subsys/usb/device_next/class/usbd_uvc.c @@ -26,6 +26,7 @@ #include #include +#include "../../common/usb_common_uvc.h" #include "../../../drivers/video/video_ctrls.h" #include "../../../drivers/video/video_device.h" @@ -62,11 +63,6 @@ enum uvc_unit_id { 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; @@ -149,29 +145,6 @@ struct uvc_buf_info { 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, @@ -188,204 +161,6 @@ void uvc_set_video_dev(const struct device *const dev, const struct device *cons 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, @@ -1159,7 +934,7 @@ static int uvc_get_control_op(const struct device *dev, const struct usb_setup_p uint8_t ifnum = (setup->wIndex >> 0) & 0xff; uint8_t unit_id = setup->wIndex >> 8; uint8_t selector = setup->wValue >> 8; - uint8_t subtype = 0; + int ret; /* VideoStreaming operation */ @@ -1189,49 +964,27 @@ static int uvc_get_control_op(const struct device *dev, const struct usb_setup_p return UVC_OP_GET_ERRNO; } + ret = -ENOTSUP; 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)) { + if (desc->bDescriptorType != USB_DESC_CS_INTERFACE) { break; } + ret = uvc_get_control_map(desc->bDescriptorSubtype, &list, &list_sz); + if (ret != 0) { + goto err; + } + if (unit_id == desc->bUnitID) { - subtype = desc->bDescriptorSubtype; break; } } - - if (subtype == 0) { + if (ret != 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) { @@ -1622,6 +1375,8 @@ static int uvc_init(struct usbd_class_data *const c_data) struct uvc_data *data = dev->data; struct uvc_format_descriptor *format_desc = NULL; struct video_caps caps; + const struct uvc_control_map *map = NULL; + size_t map_sz = 0; uint32_t prev_pixfmt = 0; uint32_t mask = 0; int ret; @@ -1637,17 +1392,20 @@ static int uvc_init(struct usbd_class_data *const c_data) /* Generating VideoControl descriptors (interface 0) */ - mask = uvc_get_mask(data->video_dev, uvc_control_map_ct, ARRAY_SIZE(uvc_control_map_ct)); + uvc_get_control_map(UVC_VC_INPUT_TERMINAL, &map, &map_sz); + mask = uvc_get_mask(data->video_dev, map, map_sz); 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)); + uvc_get_control_map(UVC_VC_PROCESSING_UNIT, &map, &map_sz); + mask = uvc_get_mask(data->video_dev, map, map_sz); 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)); + uvc_get_control_map(UVC_VC_EXTENSION_UNIT, &map, &map_sz); + mask = uvc_get_mask(data->video_dev, map, map_sz); cfg->desc->if0_xu.bmControls[0] = mask >> 0; cfg->desc->if0_xu.bmControls[1] = mask >> 8; cfg->desc->if0_xu.bmControls[2] = mask >> 16; From faec7f0a7f43124f0e3156b247b72c564f3b2522 Mon Sep 17 00:00:00 2001 From: Aiden Hu Date: Mon, 9 Jun 2025 14:21:30 +0800 Subject: [PATCH 13/31] usb: host: class: implement usb host video class Add USB host video class driver. Do some improvements for usbh.h accordingly. Add dts binding for usb host video class. Signed-off-by: Aiden Hu --- dts/bindings/usb/zephyr,uvc-host.yaml | 12 + include/zephyr/usb/usbh.h | 73 +- subsys/usb/host/CMakeLists.txt | 5 + subsys/usb/host/Kconfig | 1 + subsys/usb/host/class/Kconfig | 7 + subsys/usb/host/class/Kconfig.uvc_host | 31 + subsys/usb/host/class/usbh_uvc.c | 3834 ++++++++++++++++++++++++ subsys/usb/host/class/usbh_uvc.h | 610 ++++ 8 files changed, 4571 insertions(+), 2 deletions(-) create mode 100644 dts/bindings/usb/zephyr,uvc-host.yaml create mode 100644 subsys/usb/host/class/Kconfig create mode 100644 subsys/usb/host/class/Kconfig.uvc_host create mode 100644 subsys/usb/host/class/usbh_uvc.c create mode 100644 subsys/usb/host/class/usbh_uvc.h diff --git a/dts/bindings/usb/zephyr,uvc-host.yaml b/dts/bindings/usb/zephyr,uvc-host.yaml new file mode 100644 index 0000000000000..fa77fd48b26c9 --- /dev/null +++ b/dts/bindings/usb/zephyr,uvc-host.yaml @@ -0,0 +1,12 @@ +# Copyright 2025 NXP +# SPDX-License-Identifier: Apache-2.0 + +description: | + USB Video Class (UVC) host instance. + + Each UVC instance added to the USB Host Controller (UHC) node will be visible + as a new camera from the host point of view. + +compatible: "zephyr,uvc-host" + +include: base.yaml diff --git a/include/zephyr/usb/usbh.h b/include/zephyr/usb/usbh.h index 7c94de803f970..c52cfaef1427e 100644 --- a/include/zephyr/usb/usbh.h +++ b/include/zephyr/usb/usbh.h @@ -27,6 +27,23 @@ extern "C" { #endif +/** + * @brief USB HOST Core Layer API + * @defgroup usb_host_core_api USB Host Core API + * @ingroup usb + * @{ + */ + +/** + * @brief Match flags for USB device identification + */ +#define USBH_MATCH_DEVICE (1U << 0) /* Match device class code */ +#define USBH_MATCH_INTFACE (1U << 1) /* Match interface code */ + +/* device signal value definitions */ +#define USBH_DEVICE_CONNECTED 1 +#define USBH_DEVICE_DISCONNECTED 2 + /** * @brief USB HOST Core Layer API * @defgroup usb_host_core_api USB Host Core API @@ -149,15 +166,67 @@ struct usbh_class_data { const struct usbh_class_filter *filters; /** Pointer to host support class API */ struct usbh_class_api *api; + /** Pointer to device code table for class matching */ + const struct usbh_device_code_table *device_code_table; + /** Number of items in device code table */ + uint8_t table_items_count; + /** Flag indicating if class has been matched to a device */ + uint8_t class_matched; /** Pointer to private data */ void *priv; }; /** + * @brief USB device code table for device matching + */ +struct usbh_device_code_table { + /** Match type for device identification */ + uint32_t match_type; + /** Vendor ID */ + uint16_t vid; + /** Product ID */ + uint16_t pid; + /** device's class code, subclass code, protocol code. */ + struct usbh_code_triple device_code; + /** USB interface class code */ + uint8_t interface_class_code; + /** USB interface subclass code */ + uint8_t interface_subclass_code; + /** USB interface protocol code */ + uint8_t interface_protocol_code; +}; + +/** + * @brief USB host speed */ -#define USBH_DEFINE_CLASS(name) \ - static STRUCT_SECTION_ITERABLE(usbh_class_data, name) +enum usbh_speed { + /** Host supports or is connected to a full speed bus */ + USBH_SPEED_FS, + /** Host supports or is connected to a high speed bus */ + USBH_SPEED_HS, + /** Host supports or is connected to a super speed bus */ + USBH_SPEED_SS, +}; +/** + * @brief Define USB host support class data + * + * Macro defines class (function) data, as well as corresponding node + * structures used internally by the stack. + * + * @param class_name Class name + * @param class_api Pointer to struct usbd_class_api + * @param class_priv Class private data + */ +#define USBH_DEFINE_CLASS(class_name, class_api, class_priv, code_table, items_count) \ + static STRUCT_SECTION_ITERABLE(usbh_class_data, class_name) = { \ + .name = STRINGIFY(class_name), \ + .api = class_api, \ + .priv = class_priv, \ + .device_code_table = code_table, \ + .table_items_count = items_count, \ + .class_matched = 0, \ + }; /** * @brief Initialize the USB host support; diff --git a/subsys/usb/host/CMakeLists.txt b/subsys/usb/host/CMakeLists.txt index b9afd0a65801d..47f45352abaa1 100644 --- a/subsys/usb/host/CMakeLists.txt +++ b/subsys/usb/host/CMakeLists.txt @@ -18,6 +18,11 @@ zephyr_library_sources_ifdef( usbh_shell.c ) +zephyr_library_sources_ifdef( + CONFIG_USBH_VIDEO_CLASS + class/usbh_uvc.c +) + zephyr_library_sources_ifdef( CONFIG_USBIP usbip.c diff --git a/subsys/usb/host/Kconfig b/subsys/usb/host/Kconfig index 8da3c95ebf7be..f77d0835be841 100644 --- a/subsys/usb/host/Kconfig +++ b/subsys/usb/host/Kconfig @@ -54,5 +54,6 @@ config USBH_MAX_UHC_MSG Maximum number of USB host controller events that can be queued. rsource "Kconfig.usbip" +rsource "class/Kconfig" endif # USB_HOST_STACK diff --git a/subsys/usb/host/class/Kconfig b/subsys/usb/host/class/Kconfig new file mode 100644 index 0000000000000..1c3b14cc59f28 --- /dev/null +++ b/subsys/usb/host/class/Kconfig @@ -0,0 +1,7 @@ +# +# Copyright 2025 NXP +# +# SPDX-License-Identifier: Apache-2.0 +# + +rsource "Kconfig.uvc_host" diff --git a/subsys/usb/host/class/Kconfig.uvc_host b/subsys/usb/host/class/Kconfig.uvc_host new file mode 100644 index 0000000000000..7314f3351e4b9 --- /dev/null +++ b/subsys/usb/host/class/Kconfig.uvc_host @@ -0,0 +1,31 @@ +# +# Copyright 2025 NXP +# +# SPDX-License-Identifier: Apache-2.0 +# + +config USBH_VIDEO_CLASS + bool "USB Host Video Class implementation" + depends on DT_HAS_ZEPHYR_UVC_HOST_ENABLED + help + USB Host Video Class (UVC) implementation. + This allows the system to act as a USB host and communicate + with USB video devices such as webcams and other UVC-compliant + video capture devices. + +if USBH_VIDEO_CLASS + +config USBH_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 = USBH_VIDEO +module-str = usbh uvc +default-count = 1 +source "subsys/logging/Kconfig.template.log_config" + +endif # USBH_VIDEO_CLASS diff --git a/subsys/usb/host/class/usbh_uvc.c b/subsys/usb/host/class/usbh_uvc.c new file mode 100644 index 0000000000000..4a78c71c09c05 --- /dev/null +++ b/subsys/usb/host/class/usbh_uvc.c @@ -0,0 +1,3834 @@ +/* + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT zephyr_uvc_host + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "usbh_device.h" +#include "usbh_ch9.h" +#include "usbh_uvc.h" + +#include "../../../drivers/video/video_ctrls.h" +#include "../../../drivers/video/video_device.h" + +LOG_MODULE_REGISTER(usbh_uvc, CONFIG_USBH_VIDEO_LOG_LEVEL); + +NET_BUF_POOL_VAR_DEFINE(uvc_host_pool, CONFIG_USBH_VIDEO_NUM_BUFS, 0, 4, NULL); + +/** + * @brief UVC device code table for matching UVC devices + * + * This table defines the device matching criteria for USB Video Class (UVC) devices. + * It includes specific device entries and generic interface matching rules. + */ +static const struct usbh_device_code_table uvc_device_code[] = { + /* Intel D435i depth camera - specific device match */ + { + .match_type = USBH_MATCH_DEVICE, + .vid = 0x8086, + .pid = 0x0b3a, + .interface_class_code = UVC_SC_VIDEOCLASS, + .interface_subclass_code = UVC_SC_VIDEOCONTROL, + .interface_protocol_code = 0, + }, + /* Generic UVC video control interface match */ + { + .match_type = USBH_MATCH_INTFACE, + .interface_class_code = UVC_SC_VIDEOCLASS, + .interface_subclass_code = UVC_SC_VIDEOCONTROL, + .interface_protocol_code = 0, + } +}; + +/** + * @brief UVC GUID to pixel format mapping table + * + * Maps UVC format GUIDs to Zephyr video pixel formats. + * Each entry contains the 16-byte GUID, corresponding pixel format, + * and human-readable format name. + */ +static const struct { + /** UVC format GUID */ + uint8_t guid[16]; + /** Zephyr pixel format (\ref video_pixel_formats) */ + uint32_t pixelformat; + /** Format name string */ + const char *name; +} uvc_guid_map[] = { + /** YUY2 format GUID */ + {{0x59, 0x55, 0x59, 0x32, 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71}, + VIDEO_PIX_FMT_YUYV, "YUYV"}, + + /** Y800 grayscale format GUID */ + {{0x59, 0x38, 0x30, 0x30, 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71}, + VIDEO_PIX_FMT_GREY, "GREY"}, + + /** RGBP format GUID */ + {{0x52, 0x47, 0x42, 0x50, 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71}, + VIDEO_PIX_FMT_RGB565, "RGB565"}, + + /** UYVY format GUID (unsupported) */ + {{0x55, 0x59, 0x56, 0x59, 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71}, + 0, "UYVY (unsupported)"}, + + /** NV12 format GUID (unsupported) */ + {{0x4E, 0x56, 0x31, 0x32, 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71}, + 0, "NV12 (unsupported)"}, +}; + +/** + * @brief USB UVC camera control parameters structure + * + * This structure defines all the video control parameters supported by + * USB UVC (USB Video Class) devices. Each control is represented by + * a video_ctrl structure that contains the control's current value, + * range, and capabilities. + */ +struct usb_camera_ctrls { + /** Automatic gain control enable/disable */ + struct video_ctrl auto_gain; + /** Manual gain value adjustment */ + struct video_ctrl gain; + /** Automatic exposure control mode */ + struct video_ctrl auto_exposure; + /** Manual exposure time in absolute units */ + struct video_ctrl exposure_absolute; + /** Image brightness level adjustment */ + struct video_ctrl brightness; + /** Image contrast level adjustment */ + struct video_ctrl contrast; + /** Color hue adjustment */ + struct video_ctrl hue; + /** Color saturation level adjustment */ + struct video_ctrl saturation; + /** Image sharpness adjustment */ + struct video_ctrl sharpness; + /** Gamma correction value */ + struct video_ctrl gamma; + /** White balance temperature setting */ + struct video_ctrl white_balance_temperature; + /** Automatic white balance enable/disable */ + struct video_ctrl auto_white_balance_temperature; + /** Backlight compensation level */ + struct video_ctrl backlight_compensation; + /** Automatic focus enable/disable */ + struct video_ctrl auto_focus; + /** Manual focus position in absolute units */ + struct video_ctrl focus_absolute; + /** Power line frequency compensation */ + struct video_ctrl light_freq; + /** Test pattern generation control */ + struct video_ctrl test_pattern; + /** Pixel clock rate control */ + struct video_ctrl pixel_rate; +}; + +struct uvc_device { + /** Associated USB device */ + struct usb_device *udev; + /** Start address of descriptors belonging to this uvc class */ + void *desc_start; + /** End address of descriptors belonging to this uvc class */ + void *desc_end; + /** Device access synchronization */ + struct k_mutex lock; + /** 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; + /** Device connection status */ + bool connected; + /** Signal to alert video devices of buffer-related events */ + struct k_poll_signal *sig; + /** Byte offset within the currently transmitted video buffer */ + size_t vbuf_offset; + /** Number of completed transfers for current frame */ + size_t transfer_count; + /** USB camera control parameters */ + struct usb_camera_ctrls ctrls; + /** Collection of all available alternate streaming interfaces */ + struct usb_if_descriptor *stream_ifaces[UVC_STREAM_INTERFACES_MAX_ALT]; + /** Currently active VideoControl interface */ + struct usb_if_descriptor *current_control_interface; + /** Information about current streaming interface */ + struct uvc_stream_iface_info current_stream_iface_info; + + /** Video Control Header descriptor from device */ + struct uvc_vc_header_descriptor *vc_header; + /** Video Control Input Terminal descriptor from device */ + struct uvc_vc_input_terminal_descriptor *vc_itd; + /** Video Control Output Terminal descriptor from device */ + struct uvc_vc_output_terminal_descriptor *vc_otd; + /** Video Control Camera Terminal descriptor from device */ + struct uvc_vc_camera_terminal_descriptor *vc_ctd; + /** Video Control Selector Unit descriptor from device */ + struct uvc_vc_selector_unit_descriptor *vc_sud; + /** Video Control Processing Unit descriptor from device */ + struct uvc_vc_processing_unit_descriptor *vc_pud; + /** Video Control Encoding Unit descriptor from device */ + struct uvc_vc_encoding_unit_descriptor *vc_encoding_unit; + /** Video Control Extension Unit descriptor from device */ + struct uvc_vc_processing_unit_descriptor *vc_extension_unit; + + /** Video Stream Input Header descriptor from device */ + struct uvc_vs_input_header_descriptor *vs_input_header; + /** Video Stream Output Header descriptor from device */ + struct uvc_vs_output_header_descriptor *vs_output_header; + /** Available format groups parsed from descriptors */ + struct uvc_vs_format_info formats; + /** Currently selected video format */ + struct uvc_vs_format current_format; + /** Device-supported format capabilities for video API */ + struct video_format_cap* video_format_caps; + /** UVC probe/commit buffer */ + struct uvc_probe_commit video_probe; +}; + +static void uvc_host_flush_queue(struct uvc_device *uvc_dev, struct video_buffer *vbuf); + +/** + * @brief Convert pixel format to UVC GUID + * + * Converts Zephyr video pixel format to corresponding UVC GUID. + * + * @param pixelformat Zephyr pixel format value + * @param guid Output buffer for 16-byte GUID + * @return 0 on success, negative error code if format not supported + */ +int uvc_host_pixelformat_to_guid(uint32_t pixelformat, uint8_t *guid) +{ + if (!guid) { + return -EINVAL; + } + + for (int i = 0; i < ARRAY_SIZE(uvc_guid_map); i++) { + if (uvc_guid_map[i].pixelformat == pixelformat) { + memcpy(guid, uvc_guid_map[i].guid, 16); + return 0; + } + } + + return -ENOTSUP; +} + +/** + * @brief Convert UVC format GUID to Zephyr pixel format + * + * This function searches the UVC GUID mapping table to find the corresponding + * Zephyr video pixel format for a given UVC format GUID. + * + * @param guid Pointer to 16-byte UVC format GUID array + * @return Zephyr pixel format constant (VIDEO_PIX_FMT_*) on success, + * 0 if GUID is not found or unsupported + */ +uint32_t uvc_host_guid_to_pixelformat(const uint8_t *guid) +{ + if (!guid) { + return 0; + } + + for (int i = 0; i < ARRAY_SIZE(uvc_guid_map); i++) { + if (memcmp(guid, uvc_guid_map[i].guid, 16) == 0) { + return uvc_guid_map[i].pixelformat; + } + } + + return 0; +} + +/** + * @brief Select default video format for UVC device + * + * Attempts to find and configure a default video format by first trying + * uncompressed formats, then falling back to MJPEG if needed. + * + * @param uvc_dev Pointer to UVC device structure + * @return 0 on success, negative error code on failure + */ +static int uvc_host_select_default_format(struct uvc_device *uvc_dev) +{ + if (!uvc_dev) { + return -EINVAL; + } + + struct uvc_vs_format_uncompressed_info *uncompressed_info = &uvc_dev->formats.format_uncompressed; + struct uvc_vs_format_mjpeg_info *mjpeg_info = &uvc_dev->formats.format_mjpeg; + + /* Try uncompressed formats first */ + if (uncompressed_info->num_uncompressed_formats > 0 && + uncompressed_info->uncompressed_format[0]) { + + struct uvc_vs_format_uncompressed *format = uncompressed_info->uncompressed_format[0]; + + /* Get pixel format from GUID */ + uint32_t pixelformat = uvc_host_guid_to_pixelformat(format->guidFormat); + if (pixelformat == 0) { + LOG_WRN("First uncompressed format has unsupported GUID"); + goto try_mjpeg; + } + + /* Find first frame descriptor */ + uint8_t *desc_buf = (uint8_t *)format + format->bLength; + + while (desc_buf) { + struct uvc_frame_header *frame_header = (struct uvc_frame_header *)desc_buf; + + if (frame_header->bLength == 0) break; + + if (frame_header->bDescriptorType == UVC_CS_INTERFACE && + frame_header->bDescriptorSubType == UVC_VS_FRAME_UNCOMPRESSED) { + + if (frame_header->bLength >= sizeof(struct uvc_frame_header)) { + uint16_t width = sys_le16_to_cpu(frame_header->wWidth); + uint16_t height = sys_le16_to_cpu(frame_header->wHeight); + + /* dwFrameInterval is at fixed offset 26 bytes for uncompressed frames */ + uint32_t frame_interval = 0; + if (frame_header->bLength >= 30) { /* Ensure sufficient space for dwFrameInterval */ + uint8_t *interval_ptr = desc_buf + 26; /* dwFrameInterval offset */ + frame_interval = sys_le32_to_cpu(*(uint32_t*)interval_ptr); + } + + /* Configure default format parameters */ + uvc_dev->current_format.pixelformat = pixelformat; + uvc_dev->current_format.width = width; + uvc_dev->current_format.height = height; + uvc_dev->current_format.format_index = format->bFormatIndex; + uvc_dev->current_format.frame_index = frame_header->bFrameIndex; + uvc_dev->current_format.frame_interval = frame_interval; + uvc_dev->current_format.format_ptr = (struct uvc_format_header *)format; + uvc_dev->current_format.frame_ptr = frame_header; + + /* Calculate FPS (frame_interval is in 100ns units) */ + if (frame_interval > 0) { + uvc_dev->current_format.fps = 10000000 / frame_interval; + } else { + uvc_dev->current_format.fps = 30; /* Default 30fps */ + } + + /* Calculate pitch (bytes per line) */ + uvc_dev->current_format.pitch = width * video_bits_per_pixel(pixelformat) / 8; + + LOG_INF("Set default format: %s %ux%u@%ufps (format_idx=%u, frame_idx=%u)", + VIDEO_FOURCC_TO_STR(pixelformat), + width, height, uvc_dev->current_format.fps, + format->bFormatIndex, frame_header->bFrameIndex); + return 0; + } + } + + desc_buf += frame_header->bLength; + } + } + +try_mjpeg: + /* Try MJPEG format if uncompressed format is not available */ + if (mjpeg_info->num_mjpeg_formats > 0 && + mjpeg_info->vs_mjpeg_format[0]) { + + struct uvc_vs_format_mjpeg *format = mjpeg_info->vs_mjpeg_format[0]; + + /* Find first MJPEG frame descriptor */ + uint8_t *desc_buf = (uint8_t *)format + format->bLength; + + while (desc_buf) { + struct uvc_frame_header *frame_header = (struct uvc_frame_header *)desc_buf; + + if (frame_header->bLength == 0) break; + + if (frame_header->bDescriptorType == UVC_CS_INTERFACE && + frame_header->bDescriptorSubType == UVC_VS_FRAME_MJPEG) { + + if (frame_header->bLength >= sizeof(struct uvc_frame_header)) { + uint16_t width = sys_le16_to_cpu(frame_header->wWidth); + uint16_t height = sys_le16_to_cpu(frame_header->wHeight); + + /* dwFrameInterval is also at offset 26 bytes for MJPEG frames */ + uint32_t frame_interval = 0; + if (frame_header->bLength >= 30) { /* Ensure sufficient space for dwFrameInterval */ + uint8_t *interval_ptr = desc_buf + 26; /* dwFrameInterval offset */ + frame_interval = sys_le32_to_cpu(*(uint32_t*)interval_ptr); + } + + /* Configure default MJPEG format */ + uvc_dev->current_format.pixelformat = VIDEO_PIX_FMT_MJPEG; + uvc_dev->current_format.width = width; + uvc_dev->current_format.height = height; + uvc_dev->current_format.format_index = format->bFormatIndex; + uvc_dev->current_format.frame_index = frame_header->bFrameIndex; + uvc_dev->current_format.frame_interval = frame_interval; + uvc_dev->current_format.format_ptr = (struct uvc_format_header *)format; + uvc_dev->current_format.frame_ptr = frame_header; + /* Calculate FPS */ + if (frame_interval > 0) { + uvc_dev->current_format.fps = 10000000 / frame_interval; + } else { + uvc_dev->current_format.fps = 30; /* Default 30fps */ + } + + /* MJPEG pitch calculation (compressed format typically uses width) */ + uvc_dev->current_format.pitch = width; + + + LOG_INF("Set default format: MJPEG %ux%u@%ufps (format_idx=%u, frame_idx=%u)", + width, height, uvc_dev->current_format.fps, + format->bFormatIndex, frame_header->bFrameIndex); + return 0; + } + } + + desc_buf += frame_header->bLength; + } + } + + LOG_ERR("No valid format/frame descriptors found"); + return -ENOTSUP; +} + +/** + * @brief Check if Processing Unit supports specific control + * @param uvc_dev UVC device + * @param bmcontrol_bit Control bit mask to check (UVC_PU_BMCONTROL_*) + * @return true if supported, false otherwise + */ +static bool uvc_host_pu_supports_control(struct uvc_device *uvc_dev, uint32_t bmcontrol_bit) +{ + if (!uvc_dev || !uvc_dev->vc_pud) { + return false; + } + + struct uvc_vc_processing_unit_descriptor *pud = uvc_dev->vc_pud; + + if (pud->bControlSize == 0) { + return false; + } + + /* Convert the bmControls array to a 32-bit value for easier bit checking */ + uint32_t controls = 0; + for (int i = 0; i < pud->bControlSize && i < 4; i++) { + controls |= ((uint32_t)pud->bmControls[i]) << (i * 8); + } + + return (controls & bmcontrol_bit) != 0; +} + +/** + * @brief Check if Camera Terminal supports specific control + * @param uvc_dev UVC device + * @param bmcontrol_bit Control bit mask to check (UVC_CT_BMCONTROL_*) + * @return true if supported, false otherwise + */ +static bool uvc_host_ct_supports_control(struct uvc_device *uvc_dev, uint32_t bmcontrol_bit) +{ + if (!uvc_dev || !uvc_dev->vc_ctd) { + return false; + } + + struct uvc_vc_camera_terminal_descriptor *vc_ctd = uvc_dev->vc_ctd; + + if (vc_ctd->bControlSize == 0) { + return false; + } + + /* Convert the bmControls array to a 32-bit value for easier bit checking */ + uint32_t controls = 0; + for (int i = 0; i < vc_ctd->bControlSize && i < 4; i++) { + controls |= ((uint32_t)vc_ctd->bmControls[i]) << (i * 8); + } + + return (controls & bmcontrol_bit) != 0; +} + +/** + * @brief Initialize USB camera controls based on device capabilities + * + * Initializes video controls supported by the UVC device based on + * Processing Unit and Camera Terminal capabilities. + * + * @param dev Video device pointer + * @return 0 on success, negative error code on failure + */ +static int usb_host_camera_init_controls(const struct device *dev) +{ + int ret; + struct uvc_device *uvc_dev = dev->data; + struct usb_camera_ctrls *ctrls = &uvc_dev->ctrls; + int initialized_count = 0; + + if (!uvc_dev->vc_pud) { + LOG_WRN("No processing unit found, skipping control initialization"); + return 0; + } + + LOG_INF("Initializing controls based on processing unit capabilities"); + + /* Brightness control */ + if (uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_BRIGHTNESS)) { + ret = video_init_ctrl(&ctrls->brightness, dev, VIDEO_CID_BRIGHTNESS, + (struct video_ctrl_range){.min = -128, .max = 127, .step = 1, .def = 0}); + if (!ret) { + initialized_count++; + LOG_DBG("Brightness control initialized"); + } + } + + /* Contrast control */ + if (uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_CONTRAST)) { + ret = video_init_ctrl(&ctrls->contrast, dev, VIDEO_CID_CONTRAST, + (struct video_ctrl_range){.min = 0, .max = 255, .step = 1, .def = 128}); + if (!ret) { + initialized_count++; + LOG_DBG("Contrast control initialized"); + } + } + + /* Hue control */ + if (uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_HUE)) { + ret = video_init_ctrl(&ctrls->hue, dev, VIDEO_CID_HUE, + (struct video_ctrl_range){.min = -180, .max = 180, .step = 1, .def = 0}); + if (!ret) { + initialized_count++; + LOG_DBG("Hue control initialized"); + } + } + + /* Saturation control */ + if (uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_SATURATION)) { + ret = video_init_ctrl(&ctrls->saturation, dev, VIDEO_CID_SATURATION, + (struct video_ctrl_range){.min = 0, .max = 255, .step = 1, .def = 128}); + if (!ret) { + initialized_count++; + LOG_DBG("Saturation control initialized"); + } + } + + /* Sharpness control */ + if (uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_SHARPNESS)) { + ret = video_init_ctrl(&ctrls->sharpness, dev, VIDEO_CID_SHARPNESS, + (struct video_ctrl_range){.min = 0, .max = 255, .step = 1, .def = 128}); + if (!ret) { + initialized_count++; + LOG_DBG("Sharpness control initialized"); + } + } + + /* Gamma control */ + if (uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_GAMMA)) { + ret = video_init_ctrl(&ctrls->gamma, dev, VIDEO_CID_GAMMA, + (struct video_ctrl_range){.min = 100, .max = 300, .step = 1, .def = 100}); + if (!ret) { + initialized_count++; + LOG_DBG("Gamma control initialized"); + } + } + + /* Gain controls */ + if (uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_GAIN)) { + ret = video_init_ctrl(&ctrls->auto_gain, dev, VIDEO_CID_AUTOGAIN, + (struct video_ctrl_range){.min = 0, .max = 1, .step = 1, .def = 1}); + if (!ret) { + initialized_count++; + LOG_DBG("Auto gain control initialized"); + } + + ret = video_init_ctrl(&ctrls->gain, dev, VIDEO_CID_GAIN, + (struct video_ctrl_range){.min = 0, .max = 255, .step = 1, .def = 0}); + if (!ret) { + initialized_count++; + /* Create auto gain cluster if both controls exist */ + if (ctrls->auto_gain.id != 0) { + video_auto_cluster_ctrl(&ctrls->auto_gain, 2, true); + } + LOG_DBG("Gain control initialized"); + } + } + + /* White Balance Temperature control */ + if (uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_WHITE_BALANCE_TEMPERATURE)) { + ret = video_init_ctrl(&ctrls->white_balance_temperature, dev, VIDEO_CID_WHITE_BALANCE_TEMPERATURE, + (struct video_ctrl_range){.min = 2800, .max = 6500, .step = 1, .def = 4000}); + if (!ret) { + initialized_count++; + LOG_DBG("White balance temperature control initialized"); + } + } + + /* Auto White Balance control */ + if (uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_WHITE_BALANCE_TEMPERATURE_AUTO)) { + ret = video_init_ctrl(&ctrls->auto_white_balance_temperature, dev, VIDEO_CID_AUTO_WHITE_BALANCE, + (struct video_ctrl_range){.min = 0, .max = 1, .step = 1, .def = 1}); + if (!ret) { + initialized_count++; + LOG_DBG("Auto white balance control initialized"); + } + } + + /* Backlight Compensation control */ + if (uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_BACKLIGHT_COMPENSATION)) { + ret = video_init_ctrl(&ctrls->backlight_compensation, dev, VIDEO_CID_BACKLIGHT_COMPENSATION, + (struct video_ctrl_range){.min = 0, .max = 2, .step = 1, .def = 1}); + if (!ret) { + initialized_count++; + LOG_DBG("Backlight compensation control initialized"); + } + } + + /* Power line frequency control */ + if (uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_POWER_LINE_FREQUENCY)) { + ret = video_init_menu_ctrl(&ctrls->light_freq, dev, VIDEO_CID_POWER_LINE_FREQUENCY, + VIDEO_CID_POWER_LINE_FREQUENCY_AUTO, NULL); + if (!ret) { + initialized_count++; + LOG_DBG("Power line frequency control initialized"); + } + } + + /* Auto exposure control - Camera Terminal control */ + if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_AE_MODE)) { + ret = video_init_ctrl(&ctrls->auto_exposure, dev, VIDEO_CID_EXPOSURE_AUTO, + (struct video_ctrl_range){.min = 0, .max = 1, .step = 1, .def = 1}); + if (!ret) { + initialized_count++; + LOG_DBG("Auto exposure control initialized"); + } + } + + /* Exposure absolute control - Camera Terminal control */ + if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_EXPOSURE_TIME_ABSOLUTE)) { + ret = video_init_ctrl(&ctrls->exposure_absolute, dev, VIDEO_CID_EXPOSURE_ABSOLUTE, + (struct video_ctrl_range){ + .min = 1, /* Minimum exposure time 1μs */ + .max = 10000000, /* Maximum exposure time 10s (10,000,000μs) */ + .step = 1, + .def = 33333 /* Default 1/30s ≈ 33.33ms */ + }); + if (!ret) { + initialized_count++; + /* Create auto exposure cluster if both controls exist */ + if (ctrls->auto_exposure.id != 0) { + video_auto_cluster_ctrl(&ctrls->auto_exposure, 2, true); + } + LOG_DBG("Exposure absolute control initialized"); + } + } + + /* Focus controls - Camera Terminal control */ + if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_FOCUS_AUTO)) { + ret = video_init_ctrl(&ctrls->auto_focus, dev, VIDEO_CID_FOCUS_AUTO, + (struct video_ctrl_range){.min = 0, .max = 1, .step = 1, .def = 1}); + if (!ret) { + initialized_count++; + LOG_DBG("Auto focus control initialized"); + } + } + + if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_FOCUS_ABSOLUTE)) { + ret = video_init_ctrl(&ctrls->focus_absolute, dev, VIDEO_CID_FOCUS_ABSOLUTE, + (struct video_ctrl_range){.min = 0, .max = 1023, .step = 1, .def = 0}); + if (!ret) { + initialized_count++; + LOG_DBG("Focus absolute control initialized"); + } + } + + LOG_INF("Initialized %d camera controls", initialized_count); + return 0; +} + +/** + * @brief Configure UVC device interfaces + * + * Sets up control and streaming interfaces with proper alternate settings. + * Control interface is set to alternate 0, streaming interface to idle state. + * + * @param uvc_dev Pointer to UVC device structure + * @return 0 on success, negative error code on failure + */ +static int uvc_host_configure_device(struct uvc_device *uvc_dev) +{ + struct usb_device *udev = uvc_dev->udev; + int ret; + + if (!uvc_dev || !udev) { + LOG_ERR("Invalid UVC device or USB device"); + return -EINVAL; + } + + /* Check if required interfaces were found */ + if (!uvc_dev->current_control_interface) { + LOG_ERR("No control interface found"); + return -ENODEV; + } + + if (!uvc_dev->current_stream_iface_info.current_stream_iface) { + LOG_ERR("No streaming interface found"); + return -ENODEV; + } + + /* Set control interface to default alternate setting (0) */ + ret = usbh_device_interface_set(uvc_dev->udev, + uvc_dev->current_control_interface->bInterfaceNumber, + 0, + false); + if (ret) { + LOG_ERR("Failed to set control interface alternate setting: %d", ret); + return ret; + } + + /* Set streaming interface to idle state (alternate 0) */ + ret = usbh_device_interface_set(uvc_dev->udev, + uvc_dev->current_stream_iface_info.current_stream_iface->bInterfaceNumber, + 0, + false); + if (ret) { + LOG_ERR("Failed to set streaming interface alternate setting: %d", ret); + return ret; + } + + LOG_INF("UVC device configured successfully (control: interface %u, streaming: interface %u)", + uvc_dev->current_control_interface->bInterfaceNumber, + uvc_dev->current_stream_iface_info.current_stream_iface->bInterfaceNumber); + + return 0; +} + +/** + * @brief Parse USB interface descriptor + * + * Identifies and processes Video Control and Video Streaming interfaces + * from USB interface descriptors. + * + * @param uvc_dev Pointer to UVC device structure + * @param if_desc Pointer to USB interface descriptor + * @return 0 on success, negative error code on failure + */ +static int uvc_host_parse_interface_descriptor(struct uvc_device *uvc_dev, + struct usb_if_descriptor *if_desc) +{ + /* Only process Video class interfaces */ + if (if_desc->bInterfaceClass != UVC_SC_VIDEOCLASS) { + return 0; /* Not a video class interface, skip */ + } + + switch (if_desc->bInterfaceSubClass) { + case UVC_SC_VIDEOCONTROL: + /* Video Control interface: save only the first one found */ + if (!uvc_dev->current_control_interface) { + uvc_dev->current_control_interface = if_desc; + LOG_INF("Found Video Control interface %u", if_desc->bInterfaceNumber); + } + break; + + case UVC_SC_VIDEOSTREAMING: + /* Video Streaming interface: save to stream_ifaces array for all of alternates including 0 */ + for (int i = 0; i < UVC_STREAM_INTERFACES_MAX_ALT; i++) { + if (uvc_dev->stream_ifaces[i] == NULL) { + /* Found empty slot, save interface */ + uvc_dev->stream_ifaces[i] = if_desc; + /* Save current_stream_iface as alternat 0 interface */ + if (!if_desc->bAlternateSetting) { + uvc_dev->current_stream_iface_info.current_stream_iface = if_desc; + } + break; + } + } + break; + + default: + LOG_DBG("Unknown video interface subclass %u (interface %u)", + if_desc->bInterfaceSubClass, if_desc->bInterfaceNumber); + break; + } + + return 0; +} + +/** + * @brief Parse UVC class-specific control interface descriptor + * + * Parses and processes UVC class-specific interface descriptors including + * format, frame, and unit/terminal descriptors. + * + * @param uvc_dev Pointer to UVC device structure + * @param control_if Pointer to control interface descriptor + * @return 0 on success, negative error code on failure + */ +static int uvc_host_parse_cs_vc_interface_descriptor(struct uvc_device *uvc_dev, struct usb_if_descriptor *control_if) +{ + struct usb_cs_desc_header *header; + + /* Basic validation */ + if (!control_if || !uvc_dev || control_if->bLength < 3) { + LOG_ERR("Invalid parameters or descriptor"); + return -EINVAL; + } + + /* Skip the interface descriptor itself */ + header = (struct usb_cs_desc_header *)((uint8_t *)control_if + control_if->bLength); + + while ((uint8_t *)header < (uint8_t *)uvc_dev->desc_end) { + /* Check for end of descriptors or next interface */ + if (header->bDescriptorType == USB_DESC_INTERFACE || + header->bDescriptorType == USB_DESC_INTERFACE_ASSOC || + header->bLength == 0) { + break; + } + + if (header->bDescriptorType == USB_DESC_CS_INTERFACE) { + switch (header->bDescriptorSubType) { + case UVC_VC_HEADER: { + struct uvc_vc_header_descriptor *header_desc = + (struct uvc_vc_header_descriptor *)header; + + /* Check descriptor length - at least basic fields */ + if (header->bLength < sizeof(struct uvc_vc_header_descriptor)) { + LOG_ERR("Invalid VC header descriptor length: %u", header->bLength); + return -EINVAL; + } + + /* Additional length check for interface collection */ + if (header->bLength < (sizeof(struct uvc_vc_header_descriptor) + + header_desc->bInCollection)) { + LOG_ERR("VC header descriptor too short for interface collection: %u < %u", + header->bLength, + (unsigned)(sizeof(struct uvc_vc_header_descriptor) + header_desc->bInCollection)); + return -EINVAL; + } + + /* Save VideoControl Interface Header descriptor pointer */ + uvc_dev->vc_header = header_desc; + LOG_DBG("Found VideoControl Header: UVC v%u.%u, TotalLength=%u, ClockFreq=%u Hz, Interfaces=%u", + (sys_le16_to_cpu(header_desc->bcdUVC) >> 8) & 0xFF, + sys_le16_to_cpu(header_desc->bcdUVC) & 0xFF, + sys_le16_to_cpu(header_desc->wTotalLength), + sys_le32_to_cpu(header_desc->dwClockFrequency), + header_desc->bInCollection); + + /* Print interface collection for debugging */ + if (header_desc->bInCollection > 0) { + LOG_HEXDUMP_DBG(header_desc->baInterfaceNr, header_desc->bInCollection, + "VideoStreaming Interface Numbers"); + } + + break; + } + + case UVC_VC_INPUT_TERMINAL: { + struct uvc_vc_input_terminal_descriptor *it_desc = + (struct uvc_vc_input_terminal_descriptor *)header; + + /* Check descriptor length - at least basic fields */ + if (header->bLength < sizeof(struct uvc_vc_input_terminal_descriptor)) { + LOG_ERR("Invalid input terminal descriptor length: %u", header->bLength); + return -EINVAL; + } + + /* Check if this is Camera Terminal (wTerminalType = 0x0201) */ + if (sys_le16_to_cpu(it_desc->wTerminalType) == UVC_ITT_CAMERA) { + struct uvc_vc_camera_terminal_descriptor *ct_desc = + (struct uvc_vc_camera_terminal_descriptor *)header; + + /* Check Camera Terminal descriptor length */ + if (header->bLength < (sizeof(struct uvc_vc_input_terminal_descriptor) + 6 + ct_desc->bControlSize)) { + LOG_ERR("Invalid camera terminal descriptor length: %u", header->bLength); + return -EINVAL; + } + + /* Check if Camera Terminal already exists */ + if (uvc_dev->vc_ctd != NULL) { + LOG_WRN("Multiple camera terminals found, replacing previous one"); + } + + /* Save Camera Terminal descriptor pointer */ + uvc_dev->vc_ctd = ct_desc; + + LOG_DBG("Found Camera Terminal: ID=%u, Type=0x%04x, ControlSize=%u", + ct_desc->bTerminalID, + sys_le16_to_cpu(ct_desc->wTerminalType), + ct_desc->bControlSize); + + /* Print control bitmap for debugging */ + if (ct_desc->bControlSize > 0) { + LOG_HEXDUMP_DBG(ct_desc->bmControls, ct_desc->bControlSize, + "Camera Terminal Controls"); + } + } else { + + /* Check if Input Terminal already exists */ + if (uvc_dev->vc_itd != NULL) { + LOG_WRN("Multiple input terminals found, replacing previous one"); + } + + /* Save Input Terminal descriptor pointer */ + uvc_dev->vc_itd = it_desc; + + LOG_DBG("Found Input Terminal: ID=%u, Type=0x%04x", + it_desc->bTerminalID, + sys_le16_to_cpu(it_desc->wTerminalType)); + } + + break; + } + + case UVC_VC_OUTPUT_TERMINAL: { + struct uvc_vc_output_terminal_descriptor *ot_desc = + (struct uvc_vc_output_terminal_descriptor *)header; + + /* Check descriptor length - at least basic fields */ + if (header->bLength < sizeof(struct uvc_vc_output_terminal_descriptor)) { + LOG_ERR("Invalid output terminal descriptor length: %u", header->bLength); + return -EINVAL; + } + + /* Check if Output Terminal already exists */ + if (uvc_dev->vc_otd != NULL) { + LOG_WRN("Multiple output terminals found, replacing previous one"); + } + + /* Save Output Terminal descriptor pointer */ + uvc_dev->vc_otd = ot_desc; + + LOG_DBG("Found Output Terminal: ID=%u, Type=0x%04x, SourceID=%u", + ot_desc->bTerminalID, + sys_le16_to_cpu(ot_desc->wTerminalType), + ot_desc->bSourceID); + + break; + } + + case UVC_VC_SELECTOR_UNIT: { + struct uvc_vc_selector_unit_descriptor *su_desc = + (struct uvc_vc_selector_unit_descriptor *)header; + + /* Check descriptor length - at least basic fields */ + if (header->bLength < 5) { /* bLength + bDescriptorType + bDescriptorSubType + bUnitID + bNrInPins */ + LOG_ERR("Invalid selector unit descriptor length: %u", header->bLength); + return -EINVAL; + } + + /* Check if there's enough space for source ID array */ + if (header->bLength < (5 + su_desc->bNrInPins + 1)) { /* 5 basic bytes + source IDs + iSelector */ + LOG_ERR("Selector unit descriptor too short for source IDs: %u < %u", + header->bLength, 5 + su_desc->bNrInPins + 1); + return -EINVAL; + } + + /* Check if Selector Unit already exists */ + if (uvc_dev->vc_sud != NULL) { + LOG_WRN("Multiple selector units found, replacing previous one"); + } + + /* Save Selector Unit descriptor pointer */ + uvc_dev->vc_sud = su_desc; + + LOG_DBG("Found Selector Unit: ID=%u, InputPins=%u", + su_desc->bUnitID, + su_desc->bNrInPins); + + /* Print source IDs for debugging */ + if (su_desc->bNrInPins > 0) { + LOG_HEXDUMP_DBG(su_desc->baSourceID, su_desc->bNrInPins, + "Selector Unit Source IDs"); + } + + break; + } + + case UVC_VC_PROCESSING_UNIT: { + struct uvc_vc_processing_unit_descriptor *pu_desc = + (struct uvc_vc_processing_unit_descriptor *)header; + + /* Check descriptor length - at least basic fields */ + if (header->bLength < 8) { /* Basic field length */ + LOG_ERR("Invalid processing unit descriptor length: %u", header->bLength); + return -EINVAL; + } + + /* Check if there's enough space for control bitmap */ + if (header->bLength < (8 + pu_desc->bControlSize)) { + LOG_ERR("Processing unit descriptor too short for control bitmap: %u < %u", + header->bLength, 8 + pu_desc->bControlSize); + return -EINVAL; + } + + /* Check if Processing Unit already exists (most devices have only one) */ + if (uvc_dev->vc_pud != NULL) { + LOG_WRN("Multiple processing units found, replacing previous one"); + } + + /* Save Processing Unit descriptor pointer */ + uvc_dev->vc_pud = pu_desc; + + LOG_DBG("Found Processing Unit: ID=%u, SourceID=%u, MaxMultiplier=%u, ControlSize=%u", + pu_desc->bUnitID, + pu_desc->bSourceID, + sys_le16_to_cpu(pu_desc->wMaxMultiplier), + pu_desc->bControlSize); + + /* Print control bitmap for debugging */ + if (pu_desc->bControlSize > 0) { + LOG_HEXDUMP_DBG(pu_desc->bmControls, pu_desc->bControlSize, + "Processing Unit Controls"); + } + + break; + } + + case UVC_VC_ENCODING_UNIT: { + struct uvc_vc_encoding_unit_descriptor *enc_desc = + (struct uvc_vc_encoding_unit_descriptor *)header; + + /* Check descriptor length - at least basic fields */ + if (header->bLength < 8) { /* Basic field length */ + LOG_ERR("Invalid encoding unit descriptor length: %u", header->bLength); + return -EINVAL; + } + + /* Check if there's enough space for control bitmap */ + if (header->bLength < (8 + enc_desc->bControlSize)) { + LOG_ERR("Encoding unit descriptor too short for control bitmap: %u < %u", + header->bLength, 8 + enc_desc->bControlSize); + return -EINVAL; + } + + /* Check if Encoding Unit already exists */ + if (uvc_dev->vc_encoding_unit != NULL) { + LOG_WRN("Multiple encoding units found, replacing previous one"); + } + + /* Save Encoding Unit descriptor pointer */ + uvc_dev->vc_encoding_unit = enc_desc; + + LOG_DBG("Found Encoding Unit: ID=%u, SourceID=%u, ControlSize=%u", + enc_desc->bUnitID, + enc_desc->bSourceID, + enc_desc->bControlSize); + + /* Print control bitmap for debugging */ + if (enc_desc->bControlSize > 0) { + LOG_HEXDUMP_DBG(enc_desc->bmControls, enc_desc->bControlSize, + "Encoding Unit Controls"); + } + + break; + } + + case UVC_VC_EXTENSION_UNIT: { + struct uvc_vc_extension_unit_descriptor *eu_desc = + (struct uvc_vc_extension_unit_descriptor *)header; + + /* Check descriptor length - at least basic fields */ + if (header->bLength < 24) { /* Minimum: 3 + 1 + 16 + 1 + 1 + 1 + 1 = 24 bytes */ + LOG_ERR("Invalid extension unit descriptor length: %u", header->bLength); + return -EINVAL; + } + + /* Check if there's enough space for source ID array */ + uint8_t min_length = 24 + eu_desc->bNrInPins; /* 24 basic bytes + source IDs */ + if (header->bLength < min_length) { + LOG_ERR("Extension unit descriptor too short: %u < %u", + header->bLength, min_length); + return -EINVAL; + } + + /* Check if Extension Unit already exists */ + if (uvc_dev->vc_extension_unit != NULL) { + LOG_WRN("Multiple extension units found, replacing previous one"); + } + + /* Save Extension Unit descriptor pointer */ + uvc_dev->vc_extension_unit = eu_desc; + + LOG_DBG("Found Extension Unit: ID=%u, NumControls=%u, InputPins=%u", + eu_desc->bUnitID, + eu_desc->bNumControls, + eu_desc->bNrInPins); + + /* Print Extension Code GUID for debugging */ + LOG_HEXDUMP_DBG(eu_desc->guidExtensionCode, 16, "Extension Unit GUID"); + + break; + } + + default: + /* Other CS_INTERFACE descriptor types, ignore */ + LOG_DBG("Ignoring CS_INTERFACE subtype: 0x%02x", header->bDescriptorSubType); + break; + + } + } + /* Move to next descriptor */ + header = (struct usb_cs_desc_header *)((uint8_t *)header + header->bLength); + } + + return 0; +} + +/** + * @brief Parse UVC class-specific stream interface descriptor + * + * Parses and processes UVC class-specific interface descriptors including + * format, frame, and unit/terminal descriptors. + * + * @param uvc_dev Pointer to UVC device structure + * @param stream_if Pointer to stream interface descriptor + * @return 0 on success, negative error code on failure + */ +static int uvc_host_parse_cs_vs_interface_descriptor(struct uvc_device *uvc_dev, struct usb_if_descriptor *stream_if) +{ + struct usb_cs_desc_header *header; + + /* Basic validation */ + if (!stream_if || !uvc_dev || stream_if->bLength < 3) { + LOG_ERR("Invalid parameters or descriptor"); + return -EINVAL; + } + + /* Skip the interface descriptor itself */ + header = (struct usb_cs_desc_header *)((uint8_t *)stream_if + stream_if->bLength); + + while ((uint8_t *)header < (uint8_t *)uvc_dev->desc_end) { + /* Check for end of descriptors or next interface */ + if (header->bDescriptorType == USB_DESC_INTERFACE || + header->bDescriptorType == USB_DESC_INTERFACE_ASSOC || + header->bLength == 0) { + break; + } + + if (header->bDescriptorType == USB_DESC_CS_INTERFACE) { + switch (header->bDescriptorSubType) { + case UVC_VS_INPUT_HEADER: { + struct uvc_vs_input_header_descriptor *header_desc = (struct uvc_vs_input_header_descriptor *)header; + + /* Check descriptor length */ + if (header->bLength < sizeof(struct uvc_vs_input_header_descriptor)) { + LOG_ERR("Invalid VS input header descriptor length: %u", header->bLength); + return -EINVAL; + } + + /* Save descriptor pointer */ + uvc_dev->vs_input_header = header_desc; + + LOG_DBG("Added VS input header: formats=%u, total_len=%u, ep=0x%02x, terminal_link=%u", + header_desc->bNumFormats, + header_desc->wTotalLength, + header_desc->bEndpointAddress, + header_desc->bTerminalLink); + + break; + } + + case UVC_VS_OUTPUT_HEADER: { + struct uvc_vs_output_header_descriptor *header_desc = (struct uvc_vs_output_header_descriptor *)header; + + /* Check descriptor length */ + if (header->bLength < sizeof(struct uvc_vs_output_header_descriptor)) { + LOG_ERR("Invalid VS output header descriptor length: %u", header->bLength); + return -EINVAL; + } + + /* Save descriptor pointer */ + uvc_dev->vs_output_header = header_desc; + + LOG_DBG("Added VS output header: formats=%u, total_len=%u, ep=0x%02x, terminal_link=%u", + header_desc->bNumFormats, + header_desc->wTotalLength, + header_desc->bEndpointAddress, + header_desc->bTerminalLink); + + break; + } + + case UVC_VS_FORMAT_UNCOMPRESSED: { + struct uvc_vs_format_uncompressed *format_desc = (struct uvc_vs_format_uncompressed *)header; + struct uvc_vs_format_uncompressed_info *info = &uvc_dev->formats.format_uncompressed; + + /* Check descriptor length */ + if (header->bLength < sizeof(struct uvc_vs_format_uncompressed)) { + LOG_ERR("Invalid uncompressed format descriptor length: %u", header->bLength); + return -EINVAL; + } + + /* Check array space */ + if (info->num_uncompressed_formats >= UVC_MAX_UNCOMPRESSED_FORMAT) { + LOG_WRN("Too many uncompressed formats, ignoring format index %u", + format_desc->bFormatIndex); + return 0; + } + + /* Save descriptor pointer */ + info->uncompressed_format[info->num_uncompressed_formats] = format_desc; + info->num_uncompressed_formats++; + + LOG_DBG("Added uncompressed format[%u]: index=%u, frames=%u, bpp=%u", + info->num_uncompressed_formats - 1, + format_desc->bFormatIndex, + format_desc->bNumFrameDescriptors, + format_desc->bBitsPerPixel); + + break; + } + + case UVC_VS_FORMAT_MJPEG: { + struct uvc_vs_format_mjpeg *format_desc = (struct uvc_vs_format_mjpeg *)header; + struct uvc_vs_format_mjpeg_info *info = &uvc_dev->formats.format_mjpeg; + + /* Check descriptor length */ + if (header->bLength < sizeof(struct uvc_vs_format_mjpeg)) { + LOG_ERR("Invalid MJPEG format descriptor length: %u", header->bLength); + return -EINVAL; + } + + /* Check array space */ + if (info->num_mjpeg_formats >= UVC_MAX_MJPEG_FORMAT) { + LOG_WRN("Too many MJPEG formats, ignoring format index %u", + format_desc->bFormatIndex); + return 0; + } + + /* Save descriptor pointer */ + info->vs_mjpeg_format[info->num_mjpeg_formats] = format_desc; + info->num_mjpeg_formats++; + + LOG_DBG("Added MJPEG format[%u]: index=%u, frames=%u, flags=0x%02x", + info->num_mjpeg_formats - 1, + format_desc->bFormatIndex, + format_desc->bNumFrameDescriptors, + format_desc->bmFlags); + + break; + } + + default: + /* Other CS_INTERFACE descriptor types, ignore */ + LOG_DBG("Ignoring CS_INTERFACE subtype: 0x%02x", header->bDescriptorSubType); + break; + + } + } + /* Move to next descriptor */ + header = (struct usb_cs_desc_header *)((uint8_t *)header + header->bLength); + } + return 0; +} + +/** + * @brief Parse all UVC descriptors from device + * + * Parses UVC descriptors from the descriptor segment between desc_start + * and desc_end which contains all descriptors belonging to this UVC device. + * First pass processes interface descriptors, second pass handles class-specific descriptors. + * + * @param uvc_dev Pointer to UVC device structure + * @return 0 on success, negative error code on failure + */ +static int uvc_host_parse_descriptors(struct uvc_device *uvc_dev) +{ + struct usb_if_descriptor *if_desc; + uint8_t *desc_buf, *desc_end; + int ret = 0; + + /* Validate descriptor buffer pointers */ + if (!uvc_dev->desc_start || !uvc_dev->desc_end) { + LOG_ERR("Invalid descriptor range for UVC device"); + return -EINVAL; + } + + /* Ensure start pointer is before end pointer */ + if (uvc_dev->desc_start >= uvc_dev->desc_end) { + LOG_ERR("Invalid descriptor range: start >= end"); + return -EINVAL; + } + + /* Initialize parsing pointers from device descriptor range */ + desc_buf = (uint8_t *)uvc_dev->desc_start; + desc_end = (uint8_t *)uvc_dev->desc_end; + + LOG_DBG("Parsing UVC descriptors from %p to %p (%zu bytes)", + desc_buf, desc_end, (size_t)(desc_end - desc_buf)); + + /* First pass: Parse all interface descriptors to identify UVC interfaces */ + while (desc_buf < desc_end) { + struct usb_desc_header *header = (struct usb_desc_header *)desc_buf; + + /* Check for malformed descriptor with zero length */ + if (header->bLength == 0) { + LOG_WRN("Zero-length descriptor encountered, stopping parse"); + break; + } + + if (desc_buf + header->bLength > desc_end) { + LOG_ERR("Descriptor extends beyond valid range"); + return -EINVAL; + } + + /* Process USB interface descriptors to categorize UVC interfaces */ + if (USB_DESC_INTERFACE == header->bDescriptorType) { + if_desc = (struct usb_if_descriptor *)desc_buf; + ret = uvc_host_parse_interface_descriptor(uvc_dev, if_desc); + LOG_DBG("Parsed interface descriptor (bInterfaceNumber=%d, class=0x%02x)", + if_desc->bInterfaceNumber, if_desc->bInterfaceClass); + } + + /* Move to next descriptor in the buffer */ + desc_buf += header->bLength; + } + + /* Parse class-specific descriptors for Video Control interface */ + ret = uvc_host_parse_cs_vc_interface_descriptor(uvc_dev, uvc_dev->current_control_interface); + if (ret != 0) { + LOG_ERR("Failed to parse Video Control interface descriptor: %d", ret); + return ret; + } + + /* Parse class-specific descriptors for Video Streaming interface */ + ret = uvc_host_parse_cs_vs_interface_descriptor(uvc_dev, uvc_dev->current_stream_iface_info.current_stream_iface); + if (ret != 0) { + LOG_ERR("Failed to parse Video Streaming interface descriptor: %d", ret); + return ret; + } + + LOG_INF("Successfully parsed UVC descriptors"); + return 0; +} + +/** + * @brief Parse frame intervals from descriptor + * + * Extracts frame interval information from frame descriptor and + * returns the maximum supported interval value. + * + * @param desc_buf Pointer to frame descriptor buffer + * @return Maximum frame interval in 100ns units + */ +static uint32_t uvc_host_parse_frame_intervals(uint8_t *desc_buf) +{ + uint32_t max_interval = 333333; /* Default 30fps */ + uint8_t interval_type = desc_buf[25]; /* bFrameIntervalType */ + uint8_t *interval_data = desc_buf + 26; + + if (interval_type == 0) { + /* Continuous/stepwise frame intervals: take maximum value */ + max_interval = sys_le32_to_cpu(*(uint32_t*)(interval_data + 4)); + } else if (interval_type > 0) { + /* Discrete frame intervals: take last (maximum) value */ + max_interval = sys_le32_to_cpu(*(uint32_t*)(interval_data + (interval_type - 1) * 4)); + } + + return max_interval; +} + +/** + * @brief Find matching frame in specific format type + * + * Searches for frame descriptor with specified dimensions and type + * within a format descriptor. + * + * @param format_header Pointer to format header + * @param target_width Target frame width in pixels + * @param target_height Target frame height in pixels + * @param expected_frame_subtype Expected frame descriptor subtype + * @param found_frame Pointer to store found frame descriptor + * @param found_interval Pointer to store found frame interval + * @return 0 on success, negative error code if not found + */ +static int uvc_host_find_frame_in_format(struct uvc_format_header *format_header, + uint16_t target_width, + uint16_t target_height, + uint8_t expected_frame_subtype, + struct uvc_frame_header **found_frame, + uint32_t *found_interval) +{ + uint8_t *desc_buf = (uint8_t *)format_header + format_header->bLength; + int frames_found = 0; + + /* Iterate through all frame descriptors for this format */ + while (frames_found < format_header->bNumFrameDescriptors) { + struct uvc_frame_header *frame_header = (struct uvc_frame_header *)desc_buf; + + if (frame_header->bLength == 0) break; + + /* Check if this is the expected frame descriptor type */ + if (frame_header->bDescriptorType == UVC_CS_INTERFACE && + frame_header->bDescriptorSubType == expected_frame_subtype) { + + if (frame_header->bLength >= sizeof(struct uvc_frame_header)) { + uint16_t frame_width = sys_le16_to_cpu(frame_header->wWidth); + uint16_t frame_height = sys_le16_to_cpu(frame_header->wHeight); + + /* Check if dimensions match target */ + if (frame_width == target_width && frame_height == target_height) { + *found_frame = frame_header; + /* Parse frame interval from descriptor */ + *found_interval = (frame_header->bLength >= 26) ? + uvc_host_parse_frame_intervals(desc_buf) : 333333; + return 0; + } + frames_found++; + } + } else if (frame_header->bDescriptorType == UVC_CS_INTERFACE && + (desc_buf[2] == UVC_VS_FORMAT_UNCOMPRESSED || desc_buf[2] == UVC_VS_FORMAT_MJPEG)) { + /* Encountered new format descriptor, stop searching */ + break; + } + + desc_buf += frame_header->bLength; + } + + return -ENOTSUP; +} + +/** + * @brief Find format and frame matching specifications + * + * Searches UVC device for format/frame combination matching the specified + * pixel format and dimensions. + * + * @param uvc_dev Pointer to UVC device structure + * @param pixelformat Target pixel format (FOURCC) + * @param width Target frame width in pixels + * @param height Target frame height in pixels + * @param format Pointer to store found format descriptor + * @param frame Pointer to store found frame descriptor + * @param frame_interval Pointer to store frame interval + * @return 0 on success, negative error code if not supported + */ +static int uvc_host_find_format(struct uvc_device *uvc_dev, + uint32_t pixelformat, + uint16_t width, + uint16_t height, + struct uvc_format_header **format, + struct uvc_frame_header **frame, + uint32_t *frame_interval) +{ + if (!uvc_dev || !format || !frame || !frame_interval) { + return -EINVAL; + } + + LOG_DBG("Looking for format: %s %ux%u", + VIDEO_FOURCC_TO_STR(pixelformat), width, height); + + /* Search uncompressed formats */ + struct uvc_vs_format_uncompressed_info *uncompressed_info = &uvc_dev->formats.format_uncompressed; + + for (int i = 0; i < uncompressed_info->num_uncompressed_formats; i++) { + struct uvc_vs_format_uncompressed *format_desc = uncompressed_info->uncompressed_format[i]; + + if (!format_desc) continue; + + /* Convert GUID to pixel format for comparison */ + uint32_t desc_pixelformat = uvc_host_guid_to_pixelformat(format_desc->guidFormat); + + if (desc_pixelformat == pixelformat) { + LOG_DBG("Found matching uncompressed format: index=%u", format_desc->bFormatIndex); + + /* Search for matching frame in this format */ + if (uvc_host_find_frame_in_format((struct uvc_format_header *)format_desc, + width, height, UVC_VS_FRAME_UNCOMPRESSED, + frame, frame_interval) == 0) { + *format = (struct uvc_format_header *)format_desc; + LOG_DBG("Found matching frame: format_addr=%p, frame_addr=%p, interval=%u", + *format, *frame, *frame_interval); + return 0; + } + } + } + + /* Search MJPEG formats */ + if (pixelformat == VIDEO_PIX_FMT_MJPEG) { + struct uvc_vs_format_mjpeg_info *mjpeg_info = &uvc_dev->formats.format_mjpeg; + + for (int i = 0; i < mjpeg_info->num_mjpeg_formats; i++) { + struct uvc_vs_format_mjpeg *format_desc = mjpeg_info->vs_mjpeg_format[i]; + + if (!format_desc) continue; + + LOG_DBG("Checking MJPEG format: index=%u", format_desc->bFormatIndex); + + /* Search for matching frame in MJPEG format */ + if (uvc_host_find_frame_in_format((struct uvc_format_header *)format_desc, + width, height, UVC_VS_FRAME_MJPEG, + frame, frame_interval) == 0) { + *format = (struct uvc_format_header *)format_desc; + LOG_DBG("Found matching MJPEG frame: format_addr=%p, frame_addr=%p, interval=%u", + *format, *frame, *frame_interval); + return 0; + } + } + } + + LOG_ERR("Format %s %ux%u not supported by device", + VIDEO_FOURCC_TO_STR(pixelformat), width, height); + return -ENOTSUP; +} + +#if 0 +/** + * @brief Select streaming alternate setting based on bandwidth + * + * Searches through available streaming interface alternate settings to find + * one with sufficient bandwidth for the required video format. + * + * @param uvc_dev Pointer to UVC device structure + * @param required_bandwidth Required bandwidth in bytes per second + * @return 0 on success, negative error code if no suitable interface found + */ +static int uvc_host_select_streaming_alternate(struct uvc_device *uvc_dev, uint32_t required_bandwidth) +{ + struct usb_if_descriptor *best_interface = NULL; + struct usb_ep_descriptor *best_endpoint = NULL; + uint32_t min_suitable_bandwidth = UINT32_MAX; + uint32_t best_ep_mps_mult = 0; + bool found_suitable = false; + enum usbh_speed device_speed = uvc_dev->udev->speed; + + LOG_DBG("Required bandwidth: %u bytes/sec (device speed: %s)", required_bandwidth, + (device_speed == USB_SPEED_SPEED_HS) ? "High Speed" : "Full Speed"); + + /* Iterate through all alternate setting interfaces */ + for (int i = 0; i < UVC_STREAM_INTERFACES_MAX_ALT && uvc_dev->stream_ifaces[i]; i++) { + struct usb_if_descriptor *if_desc = uvc_dev->stream_ifaces[i]; + + /* Skip Alt 0 (idle state) */ + if (if_desc->bAlternateSetting == 0) { + continue; + } + + LOG_DBG("Checking interface %u alt %u (%u endpoints)", + if_desc->bInterfaceNumber, if_desc->bAlternateSetting, if_desc->bNumEndpoints); + + /* Examine all endpoints in this alternate setting */ + uint8_t *ep_buf = (uint8_t *)if_desc + if_desc->bLength; + + for (int ep = 0; ep < if_desc->bNumEndpoints; ep++) { + struct usb_ep_descriptor *ep_desc = (struct usb_ep_descriptor *)ep_buf; + + /* Check if this is ISO IN endpoint */ + if (ep_desc->bDescriptorType == USB_DESC_ENDPOINT && + (ep_desc->bmAttributes & USB_EP_TRANSFER_TYPE_MASK) == USB_EP_TYPE_ISO && + (ep_desc->bEndpointAddress & USB_EP_DIR_MASK) == USB_EP_DIR_IN) { + + uint16_t max_packet_size = sys_le16_to_cpu(ep_desc->wMaxPacketSize) & 0x07FF; + uint32_t ep_bandwidth; + uint32_t current_ep_mps_mult; + + /* Calculate endpoint bandwidth based on USB speed */ + if (device_speed == USB_SPEED_SPEED_HS) { + uint8_t mult = ((sys_le16_to_cpu(ep_desc->wMaxPacketSize) >> 11) & 0x03) + 1; + uint32_t interval_uframes = 1 << (ep_desc->bInterval - 1); + current_ep_mps_mult = max_packet_size * mult; + ep_bandwidth = (current_ep_mps_mult * 8000) / interval_uframes; + } else { + current_ep_mps_mult = max_packet_size; + ep_bandwidth = (max_packet_size * 1000) / ep_desc->bInterval; + } + + LOG_DBG(" Interface %u Alt %u EP[%d]: addr=0x%02x, maxpkt=%u, mps_mult=%u, bandwidth=%u", + if_desc->bInterfaceNumber, if_desc->bAlternateSetting, ep, + ep_desc->bEndpointAddress, max_packet_size, current_ep_mps_mult, ep_bandwidth); + + /* Check if endpoint satisfies bandwidth requirement and is optimal */ + if (ep_bandwidth >= required_bandwidth && ep_bandwidth < min_suitable_bandwidth) { + min_suitable_bandwidth = ep_bandwidth; + best_interface = if_desc; + best_endpoint = ep_desc; + best_ep_mps_mult = current_ep_mps_mult; + found_suitable = true; + + LOG_DBG("Found better endpoint: interface %u alt %u EP 0x%02x, bandwidth=%u, mps_mult=%u", + if_desc->bInterfaceNumber, if_desc->bAlternateSetting, + ep_desc->bEndpointAddress, ep_bandwidth, current_ep_mps_mult); + } + } + + ep_buf += ep_desc->bLength; + } + } + + if (!found_suitable) { + LOG_ERR("No endpoint can satisfy bandwidth requirement %u", required_bandwidth); + return -ENOTSUP; + } + + /* Update current streaming interface and endpoint */ + uvc_dev->current_stream_iface_info.current_stream_iface = best_interface; + uvc_dev->current_stream_iface_info.current_stream_ep = best_endpoint; + uvc_dev->current_stream_iface_info.cur_ep_mps_mult = best_ep_mps_mult; + + LOG_INF("Selected interface %u alternate %u endpoint 0x%02x with bandwidth %u, mps_mult=%u for requirement %u", + best_interface->bInterfaceNumber, best_interface->bAlternateSetting, + best_endpoint->bEndpointAddress, min_suitable_bandwidth, best_ep_mps_mult, required_bandwidth); + + return 0; +} + +#endif + + +/* TODO: this function forces to use interface 1 and alternate setting 11 to work with one Logith Camera. +* In the future, the above commented implementation should be used for generic device support +*/ +/** + * @brief Select streaming alternate setting 6 on interface 1 + * + * Directly selects interface 1 alternate setting 6 and finds its ISO IN endpoint. + * + * @param uvc_dev Pointer to UVC device structure + * @param required_bandwidth Required bandwidth (unused in this simplified version) + * @return 0 on success, negative error code if interface/endpoint not found + */ +static int uvc_host_select_streaming_alternate(struct uvc_device *uvc_dev, uint32_t required_bandwidth) +{ + struct usb_if_descriptor *target_interface = NULL; + struct usb_ep_descriptor *target_endpoint = NULL; + uint32_t ep_mps_mult = 0; + enum usbh_speed device_speed = uvc_dev->udev->speed; + + ARG_UNUSED(required_bandwidth); /* Not used in direct selection */ + + LOG_INF("Directly selecting interface 1 alternate setting 6 (device speed: %s)", + (device_speed == USB_SPEED_SPEED_HS) ? "High Speed" : "Full Speed"); + + /* Search for interface 1 alternate setting 6 */ + for (int i = 0; i < UVC_STREAM_INTERFACES_MAX_ALT && uvc_dev->stream_ifaces[i]; i++) { + struct usb_if_descriptor *if_desc = uvc_dev->stream_ifaces[i]; + + /* Check if this is interface 1 alternate setting 6 */ + if (if_desc->bInterfaceNumber == 1 && if_desc->bAlternateSetting == 11) { + target_interface = if_desc; + + LOG_INF("Found target interface %u alt %u (%u endpoints)", + if_desc->bInterfaceNumber, if_desc->bAlternateSetting, if_desc->bNumEndpoints); + + /* Find ISO IN endpoint in this interface */ + uint8_t *ep_buf = (uint8_t *)if_desc + if_desc->bLength; + + for (int ep = 0; ep < if_desc->bNumEndpoints; ep++) { + struct usb_ep_descriptor *ep_desc = (struct usb_ep_descriptor *)ep_buf; + + /* Check if this is ISO IN endpoint */ + if (ep_desc->bDescriptorType == USB_DESC_ENDPOINT && + (ep_desc->bmAttributes & USB_EP_TRANSFER_TYPE_MASK) == USB_EP_TYPE_ISO && + (ep_desc->bEndpointAddress & USB_EP_DIR_MASK) == USB_EP_DIR_IN) { + + uint16_t max_packet_size = sys_le16_to_cpu(ep_desc->wMaxPacketSize) & 0x07FF; + uint32_t ep_bandwidth; + + /* Calculate endpoint parameters based on USB speed */ + if (device_speed == USB_SPEED_SPEED_HS) { + uint8_t mult = ((sys_le16_to_cpu(ep_desc->wMaxPacketSize) >> 11) & 0x03) + 1; + uint32_t interval_uframes = 1 << (ep_desc->bInterval - 1); + ep_mps_mult = max_packet_size * mult; + ep_bandwidth = (ep_mps_mult * 8000) / interval_uframes; + } else { + ep_mps_mult = max_packet_size; + ep_bandwidth = (max_packet_size * 1000) / ep_desc->bInterval; + } + + target_endpoint = ep_desc; + + LOG_INF("Selected endpoint: addr=0x%02x, maxpkt=%u, mps_mult=%u, bandwidth=%u", + ep_desc->bEndpointAddress, max_packet_size, ep_mps_mult, ep_bandwidth); + break; + } + + ep_buf += ep_desc->bLength; + } + break; /* Found target interface, stop searching */ + } + } + + if (!target_interface) { + LOG_ERR("Interface 1 alternate setting 6 not found"); + return -ENOENT; + } + + if (!target_endpoint) { + LOG_ERR("No ISO IN endpoint found in interface 1 alternate setting 6"); + return -ENOENT; + } + + /* Update current streaming interface and endpoint */ + uvc_dev->current_stream_iface_info.current_stream_iface = target_interface; + uvc_dev->current_stream_iface_info.current_stream_ep = target_endpoint; + uvc_dev->current_stream_iface_info.cur_ep_mps_mult = ep_mps_mult; + + LOG_INF("Successfully selected interface 1 alternate setting 6 endpoint 0x%02x with mps_mult=%u", + target_endpoint->bEndpointAddress, ep_mps_mult); + + return 0; +} + + +/** + * @brief Calculate required bandwidth for current video format + * + * Calculates the bandwidth needed for streaming based on current format + * resolution, frame rate, and pixel format characteristics. + * + * @param uvc_dev Pointer to UVC device structure + * @return Required bandwidth in bytes per second, 0 on error + */ +static uint32_t uvc_host_calculate_required_bandwidth(struct uvc_device *uvc_dev) +{ + uint32_t width = uvc_dev->current_format.width; + uint32_t height = uvc_dev->current_format.height; + uint32_t fps = uvc_dev->current_format.fps; + uint32_t pixelformat = uvc_dev->current_format.pixelformat; + uint32_t bandwidth; + + if (width == 0 || height == 0 || fps == 0) { + LOG_ERR("Invalid format parameters: %ux%u@%ufps", width, height, fps); + return 0; + } + + /* Calculate bandwidth based on pixel format characteristics */ + switch (pixelformat) { + case VIDEO_PIX_FMT_MJPEG: + /* MJPEG compressed format, use empirical compression ratio */ + /* Assume compression ratio 8:1 to 12:1, use conservative 6:1 here */ + bandwidth = (width * height * fps * 3) / 6; /* RGB24 compressed 6x */ + break; + + case VIDEO_PIX_FMT_YUYV: + /* YUYV format, 2 bytes per pixel */ + bandwidth = width * height * fps * 2; + break; + + case VIDEO_PIX_FMT_RGB565: + /* RGB565 format, 2 bytes per pixel */ + bandwidth = width * height * fps * 2; + break; + + case VIDEO_PIX_FMT_GREY: + /* Grayscale format, 1 byte per pixel */ + bandwidth = width * height * fps * 1; + break; + + default: + /* Unknown format, assume RGB24 */ + bandwidth = width * height * fps * 3; + LOG_WRN("Unknown pixel format 0x%08x, assuming RGB24", pixelformat); + break; + } + + /* Add 10% margin to ensure stable transmission */ + bandwidth = (bandwidth * 110) / 100; + + LOG_DBG("Calculated bandwidth: %u bytes/sec for %s %ux%u@%ufps", + bandwidth, VIDEO_FOURCC_TO_STR(pixelformat), width, height, fps); + + return bandwidth; +} + +/** + * @brief Send UVC streaming interface control request + * + * Sends control requests to UVC streaming interface for format negotiation + * and streaming parameters configuration. + * + * @param uvc_dev Pointer to UVC device structure + * @param request UVC request type (SET_CUR, GET_CUR, etc.) + * @param control_selector UVC control selector + * @param data Data buffer for request payload + * @param data_len Length of data buffer + * @return 0 on success, negative error code on failure + */ +static int uvc_host_stream_interface_control(struct uvc_device *uvc_dev, uint8_t request, + uint8_t control_selector, void *data, uint8_t data_len) { + + uint8_t bmRequestType; + uint16_t wValue, wIndex; + struct net_buf *buf = NULL; + int ret; + + if (!uvc_dev || !uvc_dev->udev) { + LOG_ERR("Invalid UVC device"); + return -EINVAL; + } + + if (data_len == 0) { + LOG_ERR("Invalid data length: %u", data_len); + return -EINVAL; + } + + /* Always allocate transfer buffer for both SET and GET requests */ + buf = usbh_xfer_buf_alloc(uvc_dev->udev, data_len); + if (!buf) { + LOG_ERR("Failed to allocate transfer buffer of size %u", data_len); + return -ENOMEM; + } + + /* Set bmRequestType based on request direction and construct wValue/wIndex */ + switch (request) { + /* SET requests - Host to Device */ + case UVC_SET_CUR: + bmRequestType = (USB_REQTYPE_DIR_TO_DEVICE << 7) | + (USB_REQTYPE_TYPE_CLASS << 5) | + (USB_REQTYPE_RECIPIENT_INTERFACE << 0); + /* Copy data to buffer for SET requests */ + if (data) { + memcpy(buf->data, data, data_len); + } + break; + + /* GET requests - Device to Host */ + case UVC_GET_CUR: + case UVC_GET_MIN: + case UVC_GET_MAX: + case UVC_GET_RES: + case UVC_GET_LEN: + case UVC_GET_INFO: + case UVC_GET_DEF: + bmRequestType = (USB_REQTYPE_DIR_TO_HOST << 7) | + (USB_REQTYPE_TYPE_CLASS << 5) | + (USB_REQTYPE_RECIPIENT_INTERFACE << 0); + /* No need to copy data for GET requests - buffer will be filled by device */ + break; + + default: + LOG_ERR("Unsupported UVC request: 0x%02x", request); + ret = -ENOTSUP; + goto cleanup; + } + + /* Construct control selector and interface values */ + wValue = control_selector << 8; + wIndex = uvc_dev->current_stream_iface_info.current_stream_iface->bInterfaceNumber; + + LOG_DBG("UVC control request: req=0x%02x, cs=0x%02x, len=%u", + request, control_selector, data_len); + + /* Send the control transfer using USB Host API */ + ret = usbh_req_setup(uvc_dev->udev, bmRequestType, request, wValue, wIndex, data_len, buf); + if (ret < 0) { + LOG_ERR("Failed to send UVC control request 0x%02x: %d", request, ret); + goto cleanup; + } + + /* For GET requests (Device to Host), copy received data from buffer to output */ + if ((bmRequestType & 0x80) && data && buf && buf->len > 0) { + size_t copy_len = MIN(buf->len, data_len); + memcpy(data, buf->data, copy_len); + + if (buf->len != data_len) { + LOG_WRN("UVC GET request: expected %u bytes, got %zu bytes", + data_len, buf->len); + } + + LOG_DBG("GET request received %zu bytes", buf->len); + } + + LOG_DBG("Successfully completed UVC control request 0x%02x", request); + ret = 0; + +cleanup: + /* Always free the allocated buffer */ + if (buf) { + usbh_xfer_buf_free(uvc_dev->udev, buf); + } + + return ret; +} + +/** + * @brief Get current UVC format + * + * Retrieves the currently configured video format from UVC device. + * + * @param uvc_dev Pointer to UVC device structure + * @param fmt Pointer to video format structure to fill + * @return 0 on success, negative error code on failure + */ +int uvc_host_get_format(struct uvc_device *uvc_dev, struct video_format *fmt) +{ + if (!uvc_dev || !fmt) { + return -EINVAL; + } + + k_mutex_lock(&uvc_dev->lock, K_FOREVER); + + /* Convert from current_format to video_format */ + fmt->pixelformat = uvc_dev->current_format.pixelformat; + fmt->width = uvc_dev->current_format.width; + fmt->height = uvc_dev->current_format.height; + fmt->pitch = uvc_dev->current_format.pitch; + + k_mutex_unlock(&uvc_dev->lock); + + return 0; +} + +/** + * @brief Set UVC video format and configure streaming + * + * Performs complete UVC format negotiation including probe/commit protocol, + * bandwidth calculation, and streaming interface configuration. + * + * @param uvc_dev Pointer to UVC device structure + * @param pixelformat Target pixel format (FOURCC) + * @param width Target frame width in pixels + * @param height Target frame height in pixels + * @return 0 on success, negative error code on failure + */ +static int uvc_host_set_format(struct uvc_device *uvc_dev, uint32_t pixelformat, + uint32_t width, uint32_t height) +{ + uint32_t frame_interval; + uint32_t required_bandwidth; + struct uvc_format_header *format; + struct uvc_frame_header *frame; + int ret; + + /* 1. Find matching format and frame descriptors */ + ret = uvc_host_find_format(uvc_dev, pixelformat, width, height, + &format, &frame, &frame_interval); + if (ret) { + LOG_ERR("Unsupported format: %s %ux%u", + VIDEO_FOURCC_TO_STR(pixelformat), width, height); + return ret; + } + + /* 2. Prepare probe/commit structure with negotiation parameters */ + memset(&uvc_dev->video_probe, 0, sizeof(uvc_dev->video_probe)); + uvc_dev->video_probe.bmHint = sys_cpu_to_le16(0x0001); /* Frame interval hint */ + uvc_dev->video_probe.bFormatIndex = format->bFormatIndex; /* Format index from descriptor */ + uvc_dev->video_probe.bFrameIndex = frame->bFrameIndex; /* Frame index from descriptor */ + uvc_dev->video_probe.dwFrameInterval = sys_cpu_to_le32(frame_interval); /* Frame interval from descriptor */ + + LOG_INF("Setting format: %s %ux%u (format_index=%u, frame_index=%u, interval=%u)", + VIDEO_FOURCC_TO_STR(pixelformat), width, height, + format->bFormatIndex, frame->bFrameIndex, frame_interval); + + /* 3. Send PROBE request to set desired parameters */ + ret = uvc_host_stream_interface_control(uvc_dev, UVC_SET_CUR, UVC_VS_PROBE_CONTROL, &uvc_dev->video_probe, sizeof(uvc_dev->video_probe)); + if (ret) { + LOG_ERR("PROBE SET request failed: %d", ret); + return ret; + } + + /* 4. Send PROBE GET request to read negotiated parameters */ + memset(&uvc_dev->video_probe, 0, sizeof(uvc_dev->video_probe)); + ret = uvc_host_stream_interface_control(uvc_dev, UVC_GET_CUR, UVC_VS_PROBE_CONTROL, &uvc_dev->video_probe, sizeof(uvc_dev->video_probe)); + if (ret) { + LOG_ERR("PROBE GET request failed: %d", ret); + return ret; + } + + /* TODO: Validate negotiated parameters against requirements */ + + /* 5. Send COMMIT request to finalize negotiated parameters */ + ret = uvc_host_stream_interface_control(uvc_dev, UVC_SET_CUR, UVC_VS_COMMIT_CONTROL, &uvc_dev->video_probe, sizeof(uvc_dev->video_probe)); + if (ret) { + LOG_ERR("COMMIT request failed: %d", ret); + return ret; + } + + /* 6. Update device current format information */ + k_mutex_lock(&uvc_dev->lock, K_FOREVER); + uvc_dev->current_format.pixelformat = pixelformat; + uvc_dev->current_format.width = width; + uvc_dev->current_format.height = height; + uvc_dev->current_format.format_index = format->bFormatIndex; /* Use descriptor index */ + uvc_dev->current_format.frame_index = frame->bFrameIndex; /* Use descriptor index */ + uvc_dev->current_format.frame_interval = frame_interval; + uvc_dev->current_format.format_ptr = format; /* Save format descriptor pointer */ + uvc_dev->current_format.frame_ptr = frame; /* Save frame descriptor pointer */ + + /* 7. Recalculate FPS and pitch based on negotiated parameters */ + if (frame_interval > 0) { + uvc_dev->current_format.fps = 10000000 / frame_interval; + } else { + uvc_dev->current_format.fps = 30; /* Default 30fps */ + } + uvc_dev->current_format.pitch = width * video_bits_per_pixel(pixelformat) / 8; + + k_mutex_unlock(&uvc_dev->lock); + + /* 8. Calculate required bandwidth for streaming */ + required_bandwidth = uvc_host_calculate_required_bandwidth(uvc_dev); + if (required_bandwidth == 0) { + LOG_WRN("Cannot calculate required bandwidth"); + return -EINVAL; + } + + /* 9. Select appropriate streaming interface alternate setting */ + ret = uvc_host_select_streaming_alternate(uvc_dev, required_bandwidth); + if (ret) { + LOG_ERR("Select stream alternate failed: %d", ret); + return ret; + } + + /* 10. Configure streaming interface with selected alternate setting */ + ret = usbh_device_interface_set(uvc_dev->udev, + uvc_dev->current_stream_iface_info.current_stream_iface->bInterfaceNumber, + uvc_dev->current_stream_iface_info.current_stream_iface->bAlternateSetting, + false); + if (ret) { + LOG_ERR("Failed to set streaming interface %u alternate %u: %d", + uvc_dev->current_stream_iface_info.current_stream_iface->bInterfaceNumber, + uvc_dev->current_stream_iface_info.current_stream_iface->bAlternateSetting, + ret); + return ret; + } + + LOG_INF("Set streaming interface %u alternate %u successfully", + uvc_dev->current_stream_iface_info.current_stream_iface->bInterfaceNumber, + uvc_dev->current_stream_iface_info.current_stream_iface->bAlternateSetting); + + LOG_INF("UVC format set successfully: %s %ux%u@%ufps", + VIDEO_FOURCC_TO_STR(pixelformat), width, height, + uvc_dev->current_format.fps); + + return 0; +} + +/** + * @brief Set UVC device frame rate + * + * Updates the frame rate while keeping current format settings. + * Only performs UVC probe/commit if the frame interval needs to be changed. + * + * @param uvc_dev Pointer to UVC device structure + * @param fps Frame rate in frames per second + * @return 0 on success, negative error code on failure + */ +static int uvc_host_set_frame_rate(struct uvc_device *uvc_dev, uint32_t fps) +{ + uint32_t target_frame_interval; + uint32_t best_frame_interval; + uint32_t min_diff = UINT32_MAX; + bool found_exact_match = false; + int ret; + + if (!uvc_dev || fps == 0) { + return -EINVAL; + } + + /* Convert fps to frame interval (units of 100ns) */ + target_frame_interval = 10000000 / fps; + + k_mutex_lock(&uvc_dev->lock, K_FOREVER); + + /* Check if current frame interval is already set to target */ + if (uvc_dev->current_format.frame_interval == target_frame_interval) { + LOG_DBG("Frame rate already set to %u fps", fps); + k_mutex_unlock(&uvc_dev->lock); + return 0; + } + + /* Get current frame descriptor */ + struct uvc_frame_header *frame_ptr = uvc_dev->current_format.frame_ptr; + if (!frame_ptr) { + LOG_ERR("No current frame descriptor available"); + k_mutex_unlock(&uvc_dev->lock); + return -EINVAL; + } + + /* Parse frame intervals from current frame descriptor */ + if (frame_ptr->bDescriptorSubType == UVC_VS_FRAME_UNCOMPRESSED) { + struct uvc_vs_frame_uncompressed *frame_desc = + (struct uvc_vs_frame_uncompressed *)frame_ptr; + + if (frame_desc->bFrameIntervalType == 0) { + /* Continuous frame intervals */ + uint32_t *intervals = (uint32_t *)((uint8_t *)frame_desc + sizeof(*frame_desc)); + uint32_t min_interval = sys_le32_to_cpu(intervals[0]); + uint32_t max_interval = sys_le32_to_cpu(intervals[1]); + uint32_t step_interval = sys_le32_to_cpu(intervals[2]); + + /* Clamp to supported range */ + if (target_frame_interval < min_interval) { + best_frame_interval = min_interval; + } else if (target_frame_interval > max_interval) { + best_frame_interval = max_interval; + } else { + /* Find closest step-aligned interval */ + uint32_t steps = (target_frame_interval - min_interval) / step_interval; + best_frame_interval = min_interval + (steps * step_interval); + found_exact_match = (best_frame_interval == target_frame_interval); + } + } else { + /* Discrete frame intervals */ + uint32_t *intervals = (uint32_t *)((uint8_t *)frame_desc + sizeof(*frame_desc)); + + for (int i = 0; i < frame_desc->bFrameIntervalType; i++) { + uint32_t interval = sys_le32_to_cpu(intervals[i]); + uint32_t diff = (interval > target_frame_interval) ? + (interval - target_frame_interval) : (target_frame_interval - interval); + + if (diff < min_diff) { + min_diff = diff; + best_frame_interval = interval; + found_exact_match = (diff == 0); + } + } + } + } else if (frame_ptr->bDescriptorSubType == UVC_VS_FRAME_MJPEG) { + struct uvc_vs_frame_mjpeg *frame_desc = + (struct uvc_vs_frame_mjpeg *)frame_ptr; + + /* Similar logic for MJPEG frames */ + if (frame_desc->bFrameIntervalType == 0) { + uint32_t *intervals = (uint32_t *)((uint8_t *)frame_desc + sizeof(*frame_desc)); + uint32_t min_interval = sys_le32_to_cpu(intervals[0]); + uint32_t max_interval = sys_le32_to_cpu(intervals[1]); + uint32_t step_interval = sys_le32_to_cpu(intervals[2]); + + if (target_frame_interval < min_interval) { + best_frame_interval = min_interval; + } else if (target_frame_interval > max_interval) { + best_frame_interval = max_interval; + } else { + uint32_t steps = (target_frame_interval - min_interval) / step_interval; + best_frame_interval = min_interval + (steps * step_interval); + found_exact_match = (best_frame_interval == target_frame_interval); + } + } else { + uint32_t *intervals = (uint32_t *)((uint8_t *)frame_desc + sizeof(*frame_desc)); + + for (int i = 0; i < frame_desc->bFrameIntervalType; i++) { + uint32_t interval = sys_le32_to_cpu(intervals[i]); + uint32_t diff = (interval > target_frame_interval) ? + (interval - target_frame_interval) : (target_frame_interval - interval); + + if (diff < min_diff) { + min_diff = diff; + best_frame_interval = interval; + found_exact_match = (diff == 0); + } + } + } + } else { + LOG_ERR("Unsupported frame descriptor type: 0x%02x", frame_ptr->bDescriptorSubType); + k_mutex_unlock(&uvc_dev->lock); + return -ENOTSUP; + } + + /* Initialize probe structure with current format settings */ + memset(&uvc_dev->video_probe, 0, sizeof(uvc_dev->video_probe)); + uvc_dev->video_probe.bmHint = sys_cpu_to_le16(0x0001); + uvc_dev->video_probe.bFormatIndex = uvc_dev->current_format.format_index; + uvc_dev->video_probe.bFrameIndex = uvc_dev->current_format.frame_index; + uvc_dev->video_probe.dwFrameInterval = sys_cpu_to_le32(best_frame_interval); + + k_mutex_unlock(&uvc_dev->lock); + + LOG_INF("Setting frame rate: %u fps -> %u fps (%s match)", + fps, 10000000 / best_frame_interval, + found_exact_match ? "exact" : "closest"); + + /* Send PROBE request */ + ret = uvc_host_stream_interface_control(uvc_dev, UVC_SET_CUR, UVC_VS_PROBE_CONTROL, + &uvc_dev->video_probe, sizeof(uvc_dev->video_probe)); + if (ret) { + LOG_ERR("PROBE SET request failed: %d", ret); + return ret; + } + + /* Send PROBE GET request to read negotiated parameters */ + memset(&uvc_dev->video_probe, 0, sizeof(uvc_dev->video_probe)); + ret = uvc_host_stream_interface_control(uvc_dev, UVC_GET_CUR, UVC_VS_PROBE_CONTROL, + &uvc_dev->video_probe, sizeof(uvc_dev->video_probe)); + if (ret) { + LOG_ERR("PROBE GET request failed: %d", ret); + return ret; + } + + /* Send COMMIT request */ + ret = uvc_host_stream_interface_control(uvc_dev, UVC_SET_CUR, UVC_VS_COMMIT_CONTROL, + &uvc_dev->video_probe, sizeof(uvc_dev->video_probe)); + if (ret) { + LOG_ERR("COMMIT request failed: %d", ret); + return ret; + } + + /* Update current format with new frame interval */ + k_mutex_lock(&uvc_dev->lock, K_FOREVER); + uvc_dev->current_format.frame_interval = best_frame_interval; + uvc_dev->current_format.fps = 10000000 / best_frame_interval; + k_mutex_unlock(&uvc_dev->lock); + + LOG_INF("Frame rate successfully set to %u fps", uvc_dev->current_format.fps); + + return 0; +} + +/** + * @brief Create video format capabilities from UVC descriptors + * + * Parses UVC format and frame descriptors to create an array of + * video_format_cap structures for the video subsystem. + * + * @param uvc_dev Pointer to UVC device structure + * @return Pointer to format capabilities array, NULL on failure + */ +static struct video_format_cap* uvc_host_create_format_caps(struct uvc_device *uvc_dev) +{ + struct video_format_cap *caps_array = NULL; + int total_caps = 0; + int cap_index = 0; + + if (!uvc_dev) { + return NULL; + } + + /* Calculate total number of format capabilities */ + struct uvc_vs_format_uncompressed_info *uncompressed_info = &uvc_dev->formats.format_uncompressed; + struct uvc_vs_format_mjpeg_info *mjpeg_info = &uvc_dev->formats.format_mjpeg; + + /* Count frames in uncompressed formats */ + for (int i = 0; i < uncompressed_info->num_uncompressed_formats; i++) { + struct uvc_vs_format_uncompressed *format = uncompressed_info->uncompressed_format[i]; + if (format) { + total_caps += format->bNumFrameDescriptors; + } + } + + /* Count frames in MJPEG formats */ + for (int i = 0; i < mjpeg_info->num_mjpeg_formats; i++) { + struct uvc_vs_format_mjpeg *format = mjpeg_info->vs_mjpeg_format[i]; + if (format) { + total_caps += format->bNumFrameDescriptors; + } + } + + if (total_caps == 0) { + LOG_WRN("No supported formats found"); + return NULL; + } + + /* Allocate format capabilities array (+1 for zero terminator) */ + caps_array = k_malloc(sizeof(struct video_format_cap) * (total_caps + 1)); + if (!caps_array) { + LOG_ERR("Failed to allocate format caps array"); + return NULL; + } + + memset(caps_array, 0, sizeof(struct video_format_cap) * (total_caps + 1)); + + /* Fill uncompressed formats */ + for (int i = 0; i < uncompressed_info->num_uncompressed_formats; i++) { + struct uvc_vs_format_uncompressed *format = uncompressed_info->uncompressed_format[i]; + if (!format) continue; + + /* Get pixel format from GUID */ + uint32_t pixelformat = uvc_host_guid_to_pixelformat(format->guidFormat); + if (pixelformat == 0) { + LOG_WRN("Unsupported GUID format in format index %u", format->bFormatIndex); + continue; + } + + /* Parse all frames for this format */ + uint8_t *desc_buf = (uint8_t *)format + format->bLength; + int frames_found = 0; + + while (frames_found < format->bNumFrameDescriptors && cap_index < total_caps) { + struct uvc_frame_header *frame_header = (struct uvc_frame_header *)desc_buf; + + if (frame_header->bLength == 0) break; + + if (frame_header->bDescriptorType == UVC_CS_INTERFACE && + frame_header->bDescriptorSubType == UVC_VS_FRAME_UNCOMPRESSED) { + + if (frame_header->bLength >= sizeof(struct uvc_frame_header)) { + uint16_t width = sys_le16_to_cpu(frame_header->wWidth); + uint16_t height = sys_le16_to_cpu(frame_header->wHeight); + + /* Create format capability entry */ + caps_array[cap_index].pixelformat = pixelformat; + caps_array[cap_index].width_min = width; + caps_array[cap_index].width_max = width; + caps_array[cap_index].height_min = height; + caps_array[cap_index].height_max = height; + caps_array[cap_index].width_step = 0; + caps_array[cap_index].height_step = 0; + + LOG_DBG("Added format cap[%d]: %s %ux%u", + cap_index, VIDEO_FOURCC_TO_STR(pixelformat), width, height); + + cap_index++; + frames_found++; + } + } else if (frame_header->bDescriptorType == UVC_CS_INTERFACE && + (desc_buf[2] == UVC_VS_FORMAT_UNCOMPRESSED || desc_buf[2] == UVC_VS_FORMAT_MJPEG)) { + /* Found new format descriptor, stop parsing current format frames */ + break; + } + + desc_buf += frame_header->bLength; + } + } + + /* Fill MJPEG formats */ + for (int i = 0; i < mjpeg_info->num_mjpeg_formats; i++) { + struct uvc_vs_format_mjpeg *format = mjpeg_info->vs_mjpeg_format[i]; + if (!format) continue; + + /* MJPEG format uses fixed VIDEO_PIX_FMT_MJPEG */ + uint32_t pixelformat = VIDEO_PIX_FMT_MJPEG; + + /* Parse all frames for this format */ + uint8_t *desc_buf = (uint8_t *)format + format->bLength; + int frames_found = 0; + + while (frames_found < format->bNumFrameDescriptors && cap_index < total_caps) { + struct uvc_frame_header *frame_header = (struct uvc_frame_header *)desc_buf; + + if (frame_header->bLength == 0) break; + + if (frame_header->bDescriptorType == UVC_CS_INTERFACE && + frame_header->bDescriptorSubType == UVC_VS_FRAME_MJPEG) { + if (frame_header->bLength >= sizeof(struct uvc_frame_header)) { + uint16_t width = sys_le16_to_cpu(frame_header->wWidth); + uint16_t height = sys_le16_to_cpu(frame_header->wHeight); + + /* Create format capability entry */ + caps_array[cap_index].pixelformat = pixelformat; + caps_array[cap_index].width_min = width; + caps_array[cap_index].width_max = width; + caps_array[cap_index].height_min = height; + caps_array[cap_index].height_max = height; + caps_array[cap_index].width_step = 0; + caps_array[cap_index].height_step = 0; + + LOG_DBG("Added format cap[%d]: %s %ux%u", + cap_index, VIDEO_FOURCC_TO_STR(pixelformat), width, height); + + cap_index++; + frames_found++; + } + } else if (frame_header->bDescriptorType == UVC_CS_INTERFACE && + (desc_buf[2] == UVC_VS_FORMAT_UNCOMPRESSED || desc_buf[2] == UVC_VS_FORMAT_MJPEG)) { + /* Found new format descriptor, stop parsing current format frames */ + break; + } + + desc_buf += frame_header->bLength; + } + } + + LOG_INF("Created %d format capabilities from UVC descriptors", cap_index); + return caps_array; +} + +/** + * @brief Get UVC device capabilities + * + * Retrieves device capabilities including supported formats and buffer requirements. + * + * @param uvc_dev Pointer to UVC device structure + * @param caps Pointer to video capabilities structure to fill + * @return 0 on success, negative error code on failure + */ +static int uvc_host_get_device_caps(struct uvc_device *uvc_dev, struct video_caps *caps) +{ + if (!uvc_dev || !caps) { + return -EINVAL; + } + + /* Set basic capabilities */ + caps->min_vbuf_count = 2; /* UVC typically needs 2 buffers */ + caps->min_line_count = LINE_COUNT_HEIGHT; /* Only support complete frames */ + caps->max_line_count = LINE_COUNT_HEIGHT; /* Maximum one complete frame */ + + /* Create format capabilities array from UVC descriptors */ + if (uvc_dev->video_format_caps) { + /* Already created, use existing */ + caps->format_caps = uvc_dev->video_format_caps; + } else { + /* First call, create format capabilities array */ + uvc_dev->video_format_caps = uvc_host_create_format_caps(uvc_dev); + if (!uvc_dev->video_format_caps) { + LOG_ERR("Failed to create format capabilities"); + return -ENOMEM; + } + caps->format_caps = uvc_dev->video_format_caps; + } + + return 0; +} + +/** + * @brief UVC host pre-initialization + * + * Initialize basic data structures for UVC device including FIFOs and mutex. + * Called during device initialization before USB connection. + * + * @param dev Pointer to video device structure + * @return 0 on success + */ +bool uvc_host_preinit(const struct device *dev) +{ + struct uvc_device *uvc_dev = dev->data; + + /* Initialize input and output buffer queues */ + k_fifo_init(&uvc_dev->fifo_in); + k_fifo_init(&uvc_dev->fifo_out); + + /* Initialize device access mutex */ + k_mutex_init(&uvc_dev->lock); + + return 0; +} + +/** + * @brief Remove UVC payload header and extract video data + * + * Processes UVC payload packets by removing the UVC-specific header + * and extracting the actual video data payload. Validates header + * integrity and prevents buffer overflow. + * + * @param buf Pointer to network buffer containing UVC payload packet + * @param vbuf Pointer to video buffer to store extracted payload data + * @return Number of payload bytes extracted on success, negative error code on failure + * @retval -EINVAL Invalid parameters or malformed header + * @retval -ENODATA Packet too short to contain valid header + * @retval -ENOSPC Video buffer would overflow with this payload + */ +static int uvc_host_remove_payload_header(struct net_buf *buf, struct video_buffer *vbuf) +{ + struct uvc_payload_header *header; + uint8_t header_len; + size_t payload_len; + + /* Validate input parameters */ + if (!buf || !vbuf || !buf->data) { + LOG_ERR("Invalid parameters: buf=%p, vbuf=%p", buf, vbuf); + return -EINVAL; + } + + /* Check minimum packet size for UVC header */ + if (buf->len < 2) { + LOG_ERR("Packet too short: %u bytes", buf->len); + return -ENODATA; + } + + /* Extract UVC payload header information */ + header = (struct uvc_payload_header *)buf->data; + header_len = header->bHeaderLength; + + /* Validate header length against packet size */ + if (header_len > buf->len) { + LOG_ERR("Invalid header length: %u > %u", header_len, buf->len); + return -EINVAL; + } + + /* Calculate actual payload data size */ + payload_len = buf->len - header_len; + + /* Prevent video buffer overflow */ + if (vbuf->bytesused + payload_len > vbuf->size) { + LOG_ERR("Buffer overflow: used=%u, payload=%u, capacity=%u", + vbuf->bytesused, payload_len, vbuf->size); + return -ENOSPC; + } + + LOG_DBG("Header: len=%u, payload=%u, bmHeaderInfo=0x%02x", + header_len, payload_len, header->bmHeaderInfo); + + /* Copy payload data to video buffer if present */ + if (payload_len > 0) { + memmove(vbuf->buffer + vbuf->bytesused, buf->data + header_len, payload_len); + } + + /* Return number of payload bytes processed */ + return (int)payload_len; +} + +/** + * @brief ISO transfer completion callback + * + * Handles completion of isochronous video data transfers. Processes + * received video data, removes UVC headers, and manages frame completion. + * + * @param dev Pointer to USB device + * @param xfer Pointer to completed transfer + * @return 0 on success, negative error code on failure + */ +static int uvc_host_stream_iso_req_cb(struct usb_device *const dev, struct uhc_transfer *const xfer) +{ + struct uvc_device *uvc_dev = (struct uvc_device *)xfer->priv; + struct net_buf *buf = xfer->buf; + struct video_buffer *vbuf = *(struct video_buffer **)net_buf_user_data(buf); + struct uvc_payload_header *payload_header; + uint8_t endOfFrame; + uint32_t payload_len; + + /* Validate callback parameters */ + if (!buf || !uvc_dev) { + LOG_ERR("Invalid callback parameters"); + return -EINVAL; + } + + /* Handle transfer completion status */ + if (xfer->err == -ECONNRESET) { + LOG_INF("ISO transfer canceled"); + } else if (xfer->err) { + LOG_WRN("ISO request failed, err %d", xfer->err); + } else { + LOG_DBG("ISO request finished, len=%u", buf->len); + } + + /* Process received video data if present */ + if (buf->len > 0 && vbuf) + { + /* Extract frame end marker from payload header */ + payload_header = (struct uvc_payload_header *)buf->data; + endOfFrame = payload_header->bmHeaderInfo & UVC_BMHEADERINFO_END_OF_FRAME; + /* Remove UVC header and extract payload data */ + payload_len = uvc_host_remove_payload_header(buf, vbuf); + if (payload_len < 0) { + LOG_ERR("Header removal failed: %d", payload_len); + goto cleanup; + } + + /* Update video buffer with processed data */ + vbuf->bytesused += payload_len; + uvc_dev->vbuf_offset = vbuf->bytesused; + + LOG_DBG("Processed %u payload bytes, total: %u, EOF: %u", + payload_len, vbuf->bytesused, endOfFrame); + + /* Handle frame completion */ + if (endOfFrame) { + LOG_INF("Frame completed: %u bytes", vbuf->bytesused); + /* Release network buffer reference */ + net_buf_unref(buf); + /* Move completed buffer from input to output queue */ + k_fifo_get(&uvc_dev->fifo_in, K_NO_WAIT); + k_fifo_put(&uvc_dev->fifo_out, vbuf); + + /* Clean up transfer resources */ + uvc_dev->vbuf_offset = 0; + usbh_xfer_free(dev, xfer); + uvc_dev->transfer_count = 0; + + /* Signal frame completion to application */ + LOG_DBG("Raising VIDEO_BUF_DONE signal"); + k_poll_signal_raise(uvc_dev->sig, VIDEO_BUF_DONE); + return 0; + } + } + +cleanup: + /* Release network buffer reference */ + net_buf_unref(buf); + /* Continue processing pending buffers */ + uvc_host_flush_queue(uvc_dev, vbuf); + return 0; +} + +/** + * @brief Initiate new video transfer + * + * Allocates and initializes a new USB transfer for video data streaming. + * Sets up transfer buffer and associates it with video buffer. + * + * @param uvc_dev Pointer to uvc class + * @param vbuf Pointer to video buffer for data storage + * @return Pointer to allocated transfer on success, NULL on failure + */ +static struct uhc_transfer *uvc_host_initiate_transfer(struct uvc_device *uvc_dev, + struct video_buffer *const vbuf) +{ + struct net_buf *buf; + struct uhc_transfer *xfer; + + /* Validate input parameters */ + if (!vbuf || !uvc_dev || !uvc_dev->current_stream_iface_info.current_stream_ep) { + LOG_ERR("Invalid parameters for transfer initiation"); + return NULL; + } + + LOG_DBG("Initiating transfer: ep=0x%02x, vbuf=%p", + uvc_dev->current_stream_iface_info.current_stream_ep->bEndpointAddress, vbuf); + + /* Allocate USB transfer with callback */ + xfer = usbh_xfer_alloc(uvc_dev->udev, uvc_dev->current_stream_iface_info.current_stream_ep->bEndpointAddress, + uvc_host_stream_iso_req_cb, uvc_dev); + if (!xfer) { + LOG_ERR("Failed to allocate transfer"); + return NULL; + } + + /* Allocate transfer buffer with maximum packet size */ + buf = net_buf_alloc_with_data(&uvc_host_pool, vbuf->buffer, uvc_dev->current_stream_iface_info.cur_ep_mps_mult, K_NO_WAIT); + buf->len = 0; + + if (!buf) { + LOG_ERR("Failed to allocate buffer"); + usbh_xfer_free(uvc_dev->udev, xfer); + return NULL; + } + + /* Reset buffer offset and associate video buffer with transfer */ + uvc_dev->vbuf_offset = 0; + vbuf->bytesused = 0; + memset(vbuf->buffer, 0, vbuf->size); + /* Save video buffer pointer in transfer's user data */ + *(void **)net_buf_user_data(buf) = vbuf; + xfer->buf = buf; + vbuf->driver_data = (void *)xfer; + + LOG_DBG("Transfer allocated: buf=%p, size=%u", buf, uvc_dev->current_stream_iface_info.cur_ep_mps_mult); + return xfer; +} + +/** + * @brief Continue existing video transfer + * + * Allocates continuation buffer for ongoing video transfer when + * multiple packets are needed for a single video buffer. + * + * @param uvc_dev Pointer to uvc class + * @param vbuf Pointer to video buffer being filled + * @return Pointer to allocated buffer on success, NULL on failure + */ +static struct net_buf *uvc_host_continue_transfer(struct uvc_device *uvc_dev, + struct video_buffer *const vbuf) +{ + struct net_buf *buf; + + /* Allocate buffer at offset position for continuation */ + buf = net_buf_alloc_with_data(&uvc_host_pool, vbuf->buffer + vbuf->bytesused, uvc_dev->current_stream_iface_info.cur_ep_mps_mult, K_NO_WAIT); + if (buf == NULL) { + LOG_DBG("Cannot allocate continuation USB buffer for now"); + return NULL; + } + buf->len = 0; + + *(void **)net_buf_user_data(buf) = vbuf; + + return buf; +} + +/** + * @brief Flush video buffer to USB endpoint + * + * Initiates or continues USB transfer for a video buffer, handling both + * initial transfers and continuation transfers for large frames. + * + * @param uvc_dev Pointer to uvc class + * @param vbuf Pointer to video buffer to transfer + * @return 0 on success, negative error code on failure + */ +static int uvc_host_flush_vbuf(struct uvc_device *uvc_dev, struct video_buffer *const vbuf) +{ + struct net_buf *buf; + struct uhc_transfer *xfer; + int ret; + + if (uvc_dev->transfer_count == 0) { + xfer = uvc_host_initiate_transfer(uvc_dev, vbuf); + } else { + buf = uvc_host_continue_transfer(uvc_dev, vbuf); + xfer = (struct uhc_transfer *)vbuf->driver_data; + xfer->buf = buf; + } + + if (xfer == NULL || xfer->buf == NULL) { + return -ENOMEM; + } + + ret = usbh_xfer_enqueue(uvc_dev->udev, xfer); + if (ret != 0) { + LOG_ERR("Enqueue failed: ret=%d", ret); + net_buf_unref(buf); + return ret; + } + + uvc_dev->transfer_count++; + return 0; +} + +/** + * @brief Process all pending video buffers in input queue + * + * Thread-safe processing of video buffers waiting for USB transfer. + * + * @param uvc_dev Pointer to uvc class + */ +static void uvc_host_flush_queue(struct uvc_device *uvc_dev, struct video_buffer *vbuf) +{ + int ret; + + /* Lock to ensure atomic processing of buffer queue */ + LOG_DBG("Locking the UVC stream"); + k_mutex_lock(&uvc_dev->lock, K_FOREVER); + + ret = uvc_host_flush_vbuf(uvc_dev, vbuf); + + LOG_DBG("Unlocking the UVC stream"); + k_mutex_unlock(&uvc_dev->lock); +} + +/** + * @brief Enumerate frame intervals for a given frame + * + * Parses UVC frame descriptor to extract supported frame intervals. + * + * @param frame_ptr Pointer to UVC frame descriptor + * @param fie Pointer to frame interval enumeration structure + * @return 0 on success, negative error code on failure + */ +static int uvc_host_enum_frame_intervals(struct uvc_frame_header *frame_ptr, struct video_frmival_enum *fie) +{ + uint8_t *desc_buf; + + if (!frame_ptr || !fie) { + return -EINVAL; + } + + /* Ensure descriptor contains frame interval data */ + if (frame_ptr->bLength < 26) { + LOG_ERR("Frame descriptor too short for interval data"); + return -EINVAL; + } + + desc_buf = (uint8_t *)frame_ptr; + uint8_t interval_type = desc_buf[25]; /* bFrameIntervalType */ + uint8_t *interval_data = desc_buf + 26; /* dwFrameInterval data */ + + LOG_DBG("Enumerating frame intervals: frame_index=%u, interval_type=%u, fie_index=%u", + frame_ptr->bFrameIndex, interval_type, fie->index); + + if (interval_type == 0) { + /* Continuous/stepwise frame intervals */ + if (fie->index > 0) { + return -EINVAL; /* Only one entry for continuous type */ + } + + if (frame_ptr->bLength < 38) { /* Need 3 32-bit values */ + LOG_ERR("Frame descriptor too short for stepwise intervals"); + return -EINVAL; + } + + fie->type = VIDEO_FRMIVAL_TYPE_STEPWISE; + fie->stepwise.min.numerator = sys_le32_to_cpu(*(uint32_t*)(interval_data + 0)); + fie->stepwise.min.denominator = 10000000; /* 100ns units */ + fie->stepwise.max.numerator = sys_le32_to_cpu(*(uint32_t*)(interval_data + 4)); + fie->stepwise.max.denominator = 10000000; + fie->stepwise.step.numerator = sys_le32_to_cpu(*(uint32_t*)(interval_data + 8)); + fie->stepwise.step.denominator = 10000000; + + LOG_DBG("Stepwise intervals: min=%u, max=%u, step=%u", + fie->stepwise.min.numerator, fie->stepwise.max.numerator, fie->stepwise.step.numerator); + + } else if (interval_type > 0) { + /* Discrete frame intervals */ + uint8_t num_intervals = interval_type; + + if (fie->index >= num_intervals) { + return -EINVAL; /* Index out of range */ + } + + if (frame_ptr->bLength < (26 + num_intervals * 4)) { + LOG_ERR("Frame descriptor too short for %u discrete intervals", num_intervals); + return -EINVAL; + } + + fie->type = VIDEO_FRMIVAL_TYPE_DISCRETE; + fie->discrete.numerator = sys_le32_to_cpu(*(uint32_t*)(interval_data + fie->index * 4)); + fie->discrete.denominator = 10000000; /* 100ns units */ + + LOG_DBG("Discrete interval[%u]: %u/10000000 (%u ns)", + fie->index, fie->discrete.numerator, fie->discrete.numerator * 100); + } else { + LOG_ERR("Invalid frame interval type: %u", interval_type); + return -EINVAL; + } + + return 0; +} + +/** + * @brief Get current gain control value from UVC device + * + * Retrieves the current gain setting from the UVC Processing Unit. + * + * @param uvc_dev Pointer to UVC device structure + * @param gain_val Pointer to store current gain value + * @return 0 on success, negative error code on failure + */ +static int uvc_host_get_current_gain(struct uvc_device *uvc_dev, int32_t *gain_val) +{ + uint16_t current_gain; + struct net_buf *buf; + int ret; + + if (!uvc_dev || !uvc_dev->udev || !gain_val) { + LOG_ERR("Invalid parameters"); + return -EINVAL; + } + + /* Allocate buffer for control request */ + buf = usbh_xfer_buf_alloc(uvc_dev->udev, sizeof(current_gain)); + if (!buf) { + LOG_ERR("Failed to allocate buffer for gain query"); + return -ENOMEM; + } + + /* Send GET_CUR control request to retrieve current gain */ + ret = usbh_req_setup(uvc_dev->udev, + USB_REQTYPE_DIR_TO_HOST | USB_REQTYPE_TYPE_CLASS | + USB_REQTYPE_RECIPIENT_INTERFACE, + UVC_GET_CUR, + (UVC_PU_GAIN_CONTROL << 8), + uvc_dev->current_control_interface->bInterfaceNumber, + sizeof(current_gain), + buf); + + if (ret) { + LOG_ERR("Failed to get current gain value: %d", ret); + goto cleanup; + } + + if (buf->len < sizeof(current_gain)) { + LOG_ERR("Insufficient data received for gain value"); + ret = -EIO; + goto cleanup; + } + + /* Extract gain value and convert byte order */ + current_gain = sys_le16_to_cpu(net_buf_pull_le16(buf)); + *gain_val = (int32_t)current_gain; + + LOG_DBG("Current hardware gain value: %d", *gain_val); + +cleanup: + usbh_xfer_buf_free(uvc_dev->udev, buf); + return ret; +} + +/** + * @brief Send UVC control request to unit or terminal + * @param uvc_dev UVC device + * @param request Request type (SET_CUR, GET_CUR, etc.) + * @param control_selector Control selector (for individual controls, 0 for ALL requests) + * @param entity_id Unit/Terminal ID + * @param data Control data buffer (for SET: input data, for GET: output buffer) + * @param data_len Data size + * @return 0 on success, negative error code on failure + */ +static int uvc_host_control_unit_and_terminal_request(struct uvc_device *uvc_dev, uint8_t request, + uint8_t control_selector, uint8_t entity_id, + void *data, uint8_t data_len) +{ + uint8_t bmRequestType; + uint16_t wValue, wIndex; + struct net_buf *buf; + int ret; + + if (!uvc_dev || !uvc_dev->udev) { + LOG_ERR("Invalid UVC device"); + return -EINVAL; + } + + if (data_len == 0) { + LOG_ERR("Invalid data length: %u", data_len); + return -EINVAL; + } + + /* Always allocate transfer buffer for both SET and GET requests */ + buf = usbh_xfer_buf_alloc(uvc_dev->udev, data_len); + if (!buf) { + LOG_ERR("Failed to allocate transfer buffer of size %u", data_len); + return -ENOMEM; + } + + /* Set bmRequestType based on request direction and construct wValue/wIndex */ + switch (request) { + /* SET requests - Host to Device */ + case UVC_SET_CUR: + bmRequestType = (USB_REQTYPE_DIR_TO_DEVICE << 7) | + (USB_REQTYPE_TYPE_CLASS << 5) | + (USB_REQTYPE_RECIPIENT_INTERFACE << 0); + wValue = control_selector << 8; + wIndex = (entity_id << 8) | uvc_dev->current_control_interface->bInterfaceNumber; + + /* Copy data to buffer for SET requests */ + if (data) { + memcpy(buf->data, data, data_len); + net_buf_add(buf, data_len); + } + break; + + case UVC_SET_CUR_ALL: + bmRequestType = (USB_REQTYPE_DIR_TO_DEVICE << 7) | + (USB_REQTYPE_TYPE_CLASS << 5) | + (USB_REQTYPE_RECIPIENT_INTERFACE << 0); + wValue = 0x0000; /* All controls */ + wIndex = (entity_id << 8) | uvc_dev->current_control_interface->bInterfaceNumber; + + /* Copy data to buffer for SET requests */ + if (data) { + memcpy(buf->data, data, data_len); + net_buf_add(buf, data_len); + } + break; + + /* GET requests - Device to Host */ + case UVC_GET_CUR: + case UVC_GET_MIN: + case UVC_GET_MAX: + case UVC_GET_RES: + case UVC_GET_LEN: + case UVC_GET_INFO: + case UVC_GET_DEF: + bmRequestType = (USB_REQTYPE_DIR_TO_HOST << 7) | + (USB_REQTYPE_TYPE_CLASS << 5) | + (USB_REQTYPE_RECIPIENT_INTERFACE << 0); + wValue = control_selector << 8; + wIndex = (entity_id << 8) | uvc_dev->current_control_interface->bInterfaceNumber; + /* No need to copy data for GET requests - buffer will be filled by device */ + break; + + case UVC_GET_CUR_ALL: + case UVC_GET_MIN_ALL: + case UVC_GET_MAX_ALL: + case UVC_GET_RES_ALL: + case UVC_GET_DEF_ALL: + bmRequestType = (USB_REQTYPE_DIR_TO_HOST << 7) | + (USB_REQTYPE_TYPE_CLASS << 5) | + (USB_REQTYPE_RECIPIENT_INTERFACE << 0); + wValue = 0x0000; /* All controls */ + wIndex = (entity_id << 8) | uvc_dev->current_control_interface->bInterfaceNumber; + /* No need to copy data for GET requests - buffer will be filled by device */ + break; + + default: + LOG_ERR("Unsupported UVC request: 0x%02x", request); + ret = -ENOTSUP; + goto cleanup; + } + + LOG_DBG("UVC control request: req=0x%02x, cs=0x%02x, entity=0x%02x, len=%u", + request, control_selector, entity_id, data_len); + + /* Send the control transfer using USB Host API */ + ret = usbh_req_setup(uvc_dev->udev, bmRequestType, request, wValue, wIndex, data_len, buf); + if (ret < 0) { + LOG_ERR("Failed to send UVC control request 0x%02x to entity %d: %d", + request, entity_id, ret); + goto cleanup; + } + + /* For GET requests (Device to Host), copy received data from buffer to output */ + if ((bmRequestType & 0x80) && data && buf && buf->len > 0) { + size_t copy_len = MIN(buf->len, data_len); + memcpy(data, buf->data, copy_len); + + if (buf->len != data_len) { + LOG_WRN("UVC GET request: expected %u bytes, got %zu bytes", + data_len, buf->len); + } + + LOG_DBG("GET request received %zu bytes", buf->len); + } + + LOG_DBG("Successfully completed UVC control request 0x%02x to entity %d", + request, entity_id); + + ret = 0; + +cleanup: + /* Always free the allocated buffer */ + if (buf) { + usbh_xfer_buf_free(uvc_dev->udev, buf); + } + + return ret; +} + +static int uvc_host_init(struct usbh_context *const uhs_ctx, struct usbh_class_data *cdata) +{ + const struct device *dev = cdata->priv; + struct uvc_device *uvc_dev = (struct uvc_device *)dev->data; + + if (!uhs_ctx || !cdata || !dev || !uvc_dev) { + LOG_ERR("Invalid parameters for UVC host init"); + return -EINVAL; + } + + LOG_INF("Initializing UVC device structure"); + + /** Initialize basic device state */ + uvc_dev->udev = NULL; + uvc_dev->connected = false; + + /** Initialize transfer related parameters */ + uvc_dev->vbuf_offset = 0; + uvc_dev->transfer_count = 0; + + /** Initialize FIFO queues */ + k_fifo_init(&uvc_dev->fifo_in); + k_fifo_init(&uvc_dev->fifo_out); + + /** Initialize mutex lock */ + k_mutex_init(&uvc_dev->lock); + + /** Initialize USB camera control structure */ + memset(&uvc_dev->ctrls, 0, sizeof(struct usb_camera_ctrls)); + + /** Initialize stream interface array */ + for (int i = 0; i < UVC_STREAM_INTERFACES_MAX_ALT; i++) { + uvc_dev->stream_ifaces[i] = NULL; + } + + /** Initialize interface information */ + uvc_dev->current_control_interface = NULL; + memset(&uvc_dev->current_stream_iface_info, 0, sizeof(struct uvc_stream_iface_info)); + + /** Initialize descriptor pointers */ + uvc_dev->vc_header = NULL; + uvc_dev->vc_itd = NULL; + uvc_dev->vc_otd = NULL; + uvc_dev->vc_ctd = NULL; + uvc_dev->vc_sud = NULL; + uvc_dev->vc_pud = NULL; + uvc_dev->vc_encoding_unit = NULL; + uvc_dev->vc_extension_unit = NULL; + uvc_dev->vs_input_header = NULL; + uvc_dev->vs_output_header = NULL; + + + /** Initialize format information */ + memset(&uvc_dev->formats, 0, sizeof(struct uvc_vs_format_info)); + uvc_dev->video_format_caps = NULL; + + /** Initialize current format information */ + memset(&uvc_dev->current_format, 0, sizeof(struct uvc_vs_format)); + + LOG_INF("UVC device structure initialized successfully"); + return 0; +} + +/** + * @brief Handle UVC device connection + * + * Called when a UVC device is connected. Parses descriptors, + * configures the device, and initializes controls. + * + * @param udev USB device + * @param cdata USB host class data + * @return 0 on success, negative error code on failure + */ +static int uvc_host_connected(struct usb_device *udev, void *desc_start_addr, void *desc_end_addr, struct usbh_class_data *cdata) +{ + const struct device *dev = cdata->priv; + struct uvc_device *uvc_dev = (struct uvc_device *)dev->data; + int ret; + + if (cdata->class_matched) + { + return 0; /* Already processed, exit early */ + } + cdata->class_matched = 1; + + LOG_INF("UVC device connected"); + + if (!udev || udev->state != USB_STATE_CONFIGURED) { + LOG_ERR("USB device not properly configured"); + return -ENODEV; + } + + if (!uvc_dev) { + LOG_ERR("No UVC device instance available"); + return -ENODEV; + } + /* Associate USB device with UVC device */ + uvc_dev->udev = udev; + uvc_dev->desc_start = desc_start_addr; + uvc_dev->desc_end = desc_end_addr; + + /* Check if device is already in use */ + k_mutex_lock(&uvc_dev->lock, K_FOREVER); + if (uvc_dev->connected) { + k_mutex_unlock(&uvc_dev->lock); + LOG_WRN("UVC device instance already in use"); + return -EBUSY; + } + + /* Parse UVC-specific descriptors */ + ret = uvc_host_parse_descriptors(uvc_dev); + if (ret) { + LOG_ERR("Failed to parse UVC descriptors: %d", ret); + goto error; + } + + /* Configure UVC device */ + ret = uvc_host_configure_device(uvc_dev); + if (ret) { + LOG_ERR("Failed to configure UVC device: %d", ret); + goto error; + } + + /* Select default format - does not start actual transmission */ + ret = uvc_host_select_default_format(uvc_dev); + if (ret) { + LOG_ERR("Failed to set UVC default format : %d", ret); + goto error; + } + + /* Initialize USB camera controls */ + ret = usb_host_camera_init_controls(dev); + if (ret) { + LOG_ERR("Failed to initialize USB camera controls: %d", ret); + goto error; + } + + /* Mark as connected */ + uvc_dev->connected = true; + + /* Trigger device connection event signal */ +#ifdef CONFIG_POLL + if (uvc_dev->sig) { + k_poll_signal_raise(uvc_dev->sig, USBH_DEVICE_CONNECTED); + LOG_DBG("UVC device connected signal raised"); + } +#endif + k_mutex_unlock(&uvc_dev->lock); + + LOG_INF("UVC device connected successfully"); + return 0; + +error: + uvc_dev->udev = NULL; + k_mutex_unlock(&uvc_dev->lock); + return ret; +} + +/** TODO */ +static int uvc_host_removed(struct usbh_context *const uhs_ctx, + struct usbh_class_data *c_data) +{ + return 0; +} + +/** TODO */ +static int uvc_host_suspended(struct usbh_context *const uhs_ctx) +{ + return 0; +} + +/** TODO */ +static int uvc_host_resumed(struct usbh_context *const uhs_ctx) +{ + return 0; +} + +/** TODO */ +static int uvc_host_rwup(struct usbh_context *const uhs_ctx) +{ + return 0; +} + +/** + * @brief Video API implementation for setting format + * + * Video subsystem callback to set video format. Validates parameters + * and delegates to UVC-specific format setting function. + * + * @param dev Pointer to video device + * @param fmt Pointer to video format structure + * @return 0 on success, negative error code on failure + */ +static int video_usb_uvc_host_set_fmt(const struct device *dev, struct video_format *fmt) +{ + struct uvc_device *uvc_dev = dev->data; /* Get UVC device directly */ + int ret; + + if (!uvc_dev || !uvc_dev->udev) { + LOG_ERR("No UVC device connected"); + return -ENODEV; + } + + k_mutex_lock(&uvc_dev->lock, K_FOREVER); + + /* Set format using UVC protocol */ + ret = uvc_host_set_format(uvc_dev, fmt->pixelformat, fmt->width, fmt->height); + if (ret) { + LOG_ERR("Failed to set UVC format: %d", ret); + goto unlock; + } + +unlock: + k_mutex_unlock(&uvc_dev->lock); + return ret; +} + +/** + * @brief Get current video format + * + * Video API implementation to retrieve active video format configuration. + * + * @param dev Pointer to video device + * @param fmt Pointer to store current format + * @return 0 on success, -ENODEV if device not available + */ +static int video_usb_uvc_host_get_fmt(const struct device *dev, struct video_format *fmt) +{ + struct uvc_device *uvc_dev = dev->data; + + if (!uvc_dev) { + return -ENODEV; + } + + k_mutex_lock(&uvc_dev->lock, K_FOREVER); + fmt->pixelformat = uvc_dev->current_format.pixelformat; + fmt->width = uvc_dev->current_format.width; + fmt->height = uvc_dev->current_format.height; + k_mutex_unlock(&uvc_dev->lock); + + return 0; +} + +/** + * @brief Get UVC device capabilities + * + * @param dev Pointer to device structure + * @param caps Pointer to store device capabilities + * @return 0 on success, negative error code on failure + */ +static int video_usb_uvc_host_get_caps(const struct device *dev, struct video_caps *caps) +{ + struct uvc_device *uvc_dev = dev->data; + + if (!uvc_dev) { + return -ENODEV; + } + + return uvc_host_get_device_caps(uvc_dev, caps); +} + +/** + * @brief Set video frame interval (frame rate) + * + * @param dev Pointer to device structure + * @param frmival Pointer to frame interval structure + * @return 0 on success, negative error code on failure + */ +static int video_usb_uvc_host_set_frmival(const struct device *dev, struct video_frmival *frmival) +{ + struct uvc_device *uvc_dev = dev->data; + uint32_t fps; + int ret; + + if (!uvc_dev->connected || !uvc_dev) { + return -ENODEV; + } + + if (frmival->numerator == 0 || frmival->denominator == 0) { + return -EINVAL; + } + + /* Convert frame interval to frame rate */ + fps = frmival->denominator / frmival->numerator; + + k_mutex_lock(&uvc_dev->lock, K_FOREVER); + + ret = uvc_host_set_frame_rate(uvc_dev, fps); + if (ret) { + LOG_ERR("Failed to set UVC frame rate: %d", ret); + } + + k_mutex_unlock(&uvc_dev->lock); + return ret; +} + +/** + * @brief Get current frame interval of UVC device + * + * This function retrieves the current frame interval from the UVC device. + * The frame interval is expressed as a fraction (numerator/denominator) + * representing the time between consecutive frames. + * + * @param dev Pointer to the video device + * @param frmival Pointer to structure to store frame interval information + * + * @return 0 on success, negative error code on failure + */ +static int video_usb_uvc_host_get_frmival(const struct device *dev, struct video_frmival *frmival) +{ + struct uvc_device *uvc_dev = dev->data; + + if (!uvc_dev || !frmival) { + LOG_ERR("Invalid parameters: dev=%p, frmival=%p", dev, frmival); + return -EINVAL; + } + + if (!uvc_dev->connected) { + LOG_ERR("UVC device not connected"); + return -ENODEV; + } + + k_mutex_lock(&uvc_dev->lock, K_FOREVER); + + /* Check if current format is valid */ + if (uvc_dev->current_format.fps == 0) { + LOG_ERR("Invalid current format fps: %u", uvc_dev->current_format.fps); + k_mutex_unlock(&uvc_dev->lock); + return -EINVAL; + } + /* Convert fps to frame interval fraction + * Frame interval = 1 / fps (in seconds) + * So numerator = 1, denominator = fps + */ + frmival->numerator = 1; + frmival->denominator = uvc_dev->current_format.fps; + + k_mutex_unlock(&uvc_dev->lock); + + LOG_DBG("Current frame interval: %u/%u (fps=%u)", + frmival->numerator, frmival->denominator, uvc_dev->current_format.fps); + + return 0; +} + + +/** + * @brief Enumerate supported frame intervals + * + * Video API implementation to list supported frame intervals for current format. + * + * @param dev Pointer to device structure + * @param fie Pointer to frame interval enumeration structure + * @return 0 on success, negative error code on failure + */ +static int video_usb_uvc_host_enum_frmival(const struct device *dev, struct video_frmival_enum *fie) +{ + struct uvc_device *uvc_dev = dev->data; + int ret; + + if (!uvc_dev->connected || !uvc_dev) { + return -ENODEV; + } + + k_mutex_lock(&uvc_dev->lock, K_FOREVER); + + ret = uvc_host_enum_frame_intervals(uvc_dev->current_format.frame_ptr , fie); + if (ret) { + LOG_DBG("Failed to enumerate frame intervals: %d", ret); + } + + k_mutex_unlock(&uvc_dev->lock); + return ret; +} + +#ifdef CONFIG_POLL +/** + * @brief Set poll signal for UVC device events + * + * Registers a poll signal that will be raised when video events occur, + * such as buffer completion or device state changes. + * + * @param dev Pointer to video device + * @param sig Pointer to poll signal structure + * @return 0 on success, negative error code on failure + */ +static int video_usb_uvc_host_set_signal(const struct device *dev, struct k_poll_signal *sig) +{ + struct uvc_device *uvc_dev = dev->data; + + if (!uvc_dev) { + LOG_ERR("No UVC device data"); + return -ENODEV; + } + + k_mutex_lock(&uvc_dev->lock, K_FOREVER); + uvc_dev->sig = sig; + k_mutex_unlock(&uvc_dev->lock); + + LOG_DBG("Signal set for UVC device %p", uvc_dev); + + return 0; +} +#endif + +/** + * @brief Get volatile control values from UVC device + * + * Retrieves current values for volatile controls (auto-controls) that + * may change dynamically based on device automatic adjustments. + * + * @param dev Pointer to video device + * @param id Control ID to retrieve + * @return 0 on success, negative error code on failure + */ +static int video_usb_uvc_host_get_volatile_ctrl(const struct device *dev, uint32_t id) +{ + struct uvc_device *uvc_dev = dev->data; + struct usb_camera_ctrls *ctrls = &uvc_dev->ctrls; + int ret = 0; + + switch (id) { + case VIDEO_CID_EXPOSURE_AUTO: + /* TODO: Implement exposure value retrieval */ + break; + + case VIDEO_CID_AUTOGAIN: + /* Auto gain mode: read current actual gain value */ + ret = uvc_host_get_current_gain(uvc_dev, &ctrls->gain.val); + if (ret) { + LOG_ERR("Failed to get current gain value: %d", ret); + return ret; + } + LOG_DBG("Updated gain to current value: %d", ctrls->gain.val); + break; + + case VIDEO_CID_AUTO_WHITE_BALANCE: + /* TODO: Implement white balance temperature retrieval */ + break; + + default: + LOG_WRN("Volatile control 0x%08x not supported", id); + return -ENOTSUP; + } + + return 0; +} + +/** + * @brief Set UVC control value + * + * Video API implementation to set various camera control parameters. + * Maps video control IDs to UVC-specific control selectors and sends + * appropriate USB control requests. + * + * @param dev Pointer to video device + * @param ctrl Pointer to video control structure containing ID and value + * @return 0 on success, negative error code on failure + */ +static int video_usb_uvc_host_set_ctrl(const struct device *dev, struct video_control *ctrl) +{ + struct uvc_device *uvc_dev = dev->data; + int ret = 0; + uint8_t entity_id = 0; + uint8_t control_selector = 0; + uint8_t data[4] = {0}; /* Buffer for control data */ + uint8_t data_len = 0; + + if (!uvc_dev || !ctrl) { + return -EINVAL; + } + + /* Map control ID to UVC entity and control selector */ + switch (ctrl->id) { + /* Processing Unit Controls */ + case VIDEO_CID_BRIGHTNESS: + if (!uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_BRIGHTNESS)) { + return -ENOTSUP; + } + entity_id = uvc_dev->vc_pud->bUnitID; + control_selector = UVC_PU_BRIGHTNESS_CONTROL; + sys_put_le16(ctrl->val, data); + data_len = 2; + break; + + case VIDEO_CID_CONTRAST: + if (!uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_CONTRAST)) { + return -ENOTSUP; + } + entity_id = uvc_dev->vc_pud->bUnitID; + control_selector = UVC_PU_CONTRAST_CONTROL; + sys_put_le16(ctrl->val, data); + data_len = 2; + break; + + case VIDEO_CID_HUE: + if (!uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_HUE)) { + return -ENOTSUP; + } + entity_id = uvc_dev->vc_pud->bUnitID; + control_selector = UVC_PU_HUE_CONTROL; + sys_put_le16(ctrl->val, data); + data_len = 2; + break; + + case VIDEO_CID_SATURATION: + if (!uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_SATURATION)) { + return -ENOTSUP; + } + entity_id = uvc_dev->vc_pud->bUnitID; + control_selector = UVC_PU_SATURATION_CONTROL; + sys_put_le16(ctrl->val, data); + data_len = 2; + break; + + case VIDEO_CID_SHARPNESS: + if (!uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_SHARPNESS)) { + return -ENOTSUP; + } + entity_id = uvc_dev->vc_pud->bUnitID; + control_selector = UVC_PU_SHARPNESS_CONTROL; + sys_put_le16(ctrl->val, data); + data_len = 2; + break; + + case VIDEO_CID_GAMMA: + if (!uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_GAMMA)) { + return -ENOTSUP; + } + entity_id = uvc_dev->vc_pud->bUnitID; + control_selector = UVC_PU_GAMMA_CONTROL; + sys_put_le16(ctrl->val, data); + data_len = 2; + break; + + case VIDEO_CID_GAIN: + if (!uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_GAIN)) { + return -ENOTSUP; + } + entity_id = uvc_dev->vc_pud->bUnitID; + control_selector = UVC_PU_GAIN_CONTROL; + sys_put_le16(ctrl->val, data); + data_len = 2; + break; + + case VIDEO_CID_AUTOGAIN: + /* Auto gain implemented through gain control's automatic mode */ + if (!uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_GAIN)) { + return -ENOTSUP; + } + entity_id = uvc_dev->vc_pud->bUnitID; + control_selector = UVC_PU_GAIN_CONTROL; + data[0] = ctrl->val ? 0xFF : 0x00; /* Auto mode flag */ + data_len = 1; + break; + + case VIDEO_CID_POWER_LINE_FREQUENCY: + if (!uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_POWER_LINE_FREQUENCY)) { + return -ENOTSUP; + } + entity_id = uvc_dev->vc_pud->bUnitID; + control_selector = UVC_PU_POWER_LINE_FREQUENCY_CONTROL; + data[0] = ctrl->val; /* 0=Disabled, 1=50Hz, 2=60Hz, 3=Auto */ + data_len = 1; + break; + + case VIDEO_CID_WHITE_BALANCE_TEMPERATURE: + if (!uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_WHITE_BALANCE_TEMPERATURE)) { + return -ENOTSUP; + } + entity_id = uvc_dev->vc_pud->bUnitID; + control_selector = UVC_PU_WHITE_BALANCE_TEMP_CONTROL; + sys_put_le16(ctrl->val, data); + data_len = 2; + break; + + case VIDEO_CID_AUTO_WHITE_BALANCE: + if (!uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_WHITE_BALANCE_TEMPERATURE_AUTO)) { + return -ENOTSUP; + } + entity_id = uvc_dev->vc_pud->bUnitID; + control_selector = UVC_PU_WHITE_BALANCE_TEMP_AUTO_CONTROL; + data[0] = ctrl->val ? 1 : 0; + data_len = 1; + break; + + case VIDEO_CID_BACKLIGHT_COMPENSATION: + if (!uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_BACKLIGHT_COMPENSATION)) { + return -ENOTSUP; + } + entity_id = uvc_dev->vc_pud->bUnitID; + control_selector = UVC_PU_BACKLIGHT_COMPENSATION_CONTROL; + sys_put_le16(ctrl->val, data); + data_len = 2; + break; + + /* Camera Terminal Controls */ + case VIDEO_CID_EXPOSURE_AUTO: + if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_AE_MODE)) { + entity_id = uvc_dev->vc_ctd->bTerminalID; + control_selector = UVC_CT_AE_MODE_CONTROL; + data[0] = ctrl->val; + data_len = 1; + } else { + LOG_WRN("Auto exposure mode control not supported"); + ret = -ENOTSUP; + } + break; + + case VIDEO_CID_EXPOSURE_AUTO_PRIORITY: + if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_AE_PRIORITY)) { + entity_id = uvc_dev->vc_ctd->bTerminalID; + control_selector = UVC_CT_AE_PRIORITY_CONTROL; + data[0] = ctrl->val; + data_len = 1; + } else { + LOG_WRN("Auto exposure priority control not supported"); + ret = -ENOTSUP; + } + break; + + case VIDEO_CID_EXPOSURE_ABSOLUTE: + if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_EXPOSURE_TIME_ABSOLUTE)) { + entity_id = uvc_dev->vc_ctd->bTerminalID; + control_selector = UVC_CT_EXPOSURE_TIME_ABS_CONTROL; + sys_put_le32(ctrl->val, data); + data_len = 4; + } else { + LOG_WRN("Exposure absolute control not supported"); + ret = -ENOTSUP; + } + break; + + case VIDEO_CID_FOCUS_ABSOLUTE: + if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_FOCUS_ABSOLUTE)) { + entity_id = uvc_dev->vc_ctd->bTerminalID; + control_selector = UVC_CT_FOCUS_ABS_CONTROL; + sys_put_le16(ctrl->val, data); + data_len = 2; + } else { + LOG_WRN("Focus absolute control not supported"); + ret = -ENOTSUP; + } + break; + + case VIDEO_CID_FOCUS_AUTO: + if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_FOCUS_AUTO)) { + entity_id = uvc_dev->vc_ctd->bTerminalID; + control_selector = UVC_CT_FOCUS_AUTO_CONTROL; + data[0] = ctrl->val; + data_len = 1; + } else { + LOG_WRN("Auto focus control not supported"); + ret = -ENOTSUP; + } + break; + + case VIDEO_CID_FOCUS_RELATIVE: + if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_FOCUS_RELATIVE)) { + entity_id = uvc_dev->vc_ctd->bTerminalID; + control_selector = UVC_CT_FOCUS_REL_CONTROL; + sys_put_le16(ctrl->val, data); + data_len = 2; + } else { + LOG_WRN("Focus relative control not supported"); + ret = -ENOTSUP; + } + break; + + case VIDEO_CID_ZOOM_ABSOLUTE: + if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_ZOOM_ABSOLUTE)) { + entity_id = uvc_dev->vc_ctd->bTerminalID; + control_selector = UVC_CT_ZOOM_ABS_CONTROL; + sys_put_le16(ctrl->val, data); + data_len = 2; + } else { + LOG_WRN("Zoom absolute control not supported"); + ret = -ENOTSUP; + } + break; + + case VIDEO_CID_ZOOM_RELATIVE: + if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_ZOOM_RELATIVE)) { + entity_id = uvc_dev->vc_ctd->bTerminalID; + control_selector = UVC_CT_ZOOM_REL_CONTROL; + data[0] = ctrl->val; /* zoom value */ + data[1] = 0x00; /* digital zoom (not used) */ + data[2] = 0x01; /* speed */ + data_len = 3; + } else { + LOG_WRN("Zoom relative control not supported"); + ret = -ENOTSUP; + } + break; + + case VIDEO_CID_TILT_RELATIVE: + if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_PAN_TILT_RELATIVE)) { + entity_id = uvc_dev->vc_ctd->bTerminalID; + control_selector = UVC_CT_PANTILT_REL_CONTROL; + data[0] = 0x00; /* pan relative (LSB) */ + data[1] = 0x00; /* pan relative (MSB) */ + sys_put_le16(ctrl->val, &data[2]); /* tilt relative */ + data_len = 4; + } else { + LOG_WRN("Tilt relative control not supported"); + ret = -ENOTSUP; + } + break; + + case VIDEO_CID_IRIS_ABSOLUTE: + if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_IRIS_ABSOLUTE)) { + entity_id = uvc_dev->vc_ctd->bTerminalID; + control_selector = UVC_CT_IRIS_ABS_CONTROL; + sys_put_le16(ctrl->val, data); + data_len = 2; + } else { + LOG_WRN("Iris absolute control not supported"); + ret = -ENOTSUP; + } + break; + + case VIDEO_CID_IRIS_RELATIVE: + if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_IRIS_RELATIVE)) { + entity_id = uvc_dev->vc_ctd->bTerminalID; + control_selector = UVC_CT_IRIS_REL_CONTROL; + data[0] = ctrl->val; /* iris value */ + data_len = 1; + } else { + LOG_WRN("Iris relative control not supported"); + ret = -ENOTSUP; + } + break; + + default: + LOG_ERR("Unknown control ID: %u", ctrl->id); + return -EINVAL; + } + + /* Send control request if parameters are valid and no error occurred */ + if (ret == 0 && entity_id != 0 && control_selector != 0 && data_len > 0) { + ret = uvc_host_control_unit_and_terminal_request(uvc_dev, UVC_SET_CUR, control_selector, + entity_id, data, data_len); + } + + return ret; +} + +/** + * @brief Video API implementation for starting stream + * + * Activates video streaming by setting the streaming interface to + * the previously negotiated alternate setting. + * + * @param dev Pointer to video device + * @param enable Enbale or disable the video stream + * @param type Buffer type (unused) + * @return 0 on success, negative error code on failure + */ +static int video_usb_uvc_host_set_stream(const struct device *dev, bool enable, enum video_buf_type type) +{ + struct uvc_device *uvc_dev = dev->data; + uint8_t alt; + int ret; + + if (enable) { + alt = uvc_dev->current_stream_iface_info.current_stream_iface->bAlternateSetting; + } + else { + alt = 0; + } + /* Activate streaming interface with negotiated alternate setting */ + ret = usbh_device_interface_set(uvc_dev->udev, + uvc_dev->current_stream_iface_info.current_stream_iface->bInterfaceNumber, + alt, + false); + return ret; +} + +/** + * @brief Enqueue video buffer for capture + * + * Video API implementation to queue empty buffer for video capture. + * + * @param dev Pointer to video device + * @param vbuf Pointer to video buffer to enqueue + * @return 0 on success, negative error code on failure + */ +static int video_usb_uvc_host_enqueue(const struct device *dev, struct video_buffer *vbuf) +{ + struct uvc_device *uvc_dev = (struct uvc_device *)dev->data; + + /* Initialize buffer state for new capture */ + vbuf->bytesused = 0; + vbuf->timestamp = 0; + vbuf->line_offset = 0; + + k_fifo_put(&uvc_dev->fifo_in, vbuf); + uvc_host_flush_queue(uvc_dev, vbuf); +} + +/** + * @brief Dequeue completed video buffer + * + * Video API implementation to retrieve filled video buffer. + * + * @param dev Pointer to video device + * @param vbuf Pointer to store dequeued buffer pointer + * @param timeout Maximum wait time for available buffer + * @return 0 on success, -EAGAIN if no buffer available + */ +static int video_usb_uvc_host_dequeue(const struct device *dev, struct video_buffer **vbuf, + k_timeout_t timeout) +{ + struct uvc_device *uvc_dev = dev->data; + + *vbuf = k_fifo_get(&uvc_dev->fifo_out, timeout); + if (*vbuf == NULL) { + return -EAGAIN; + } + + return 0; +} + +static const struct usbh_class_api uvc_host_class_api = { + /** Initialize UVC host class */ + .init = uvc_host_init, + /** Handle UVC device connection */ + .connected = uvc_host_connected, + /** Handle UVC device removal */ + .removed = uvc_host_removed, + /** Handle remote wakeup */ + .rwup = uvc_host_rwup, + /** Handle device suspend */ + .suspended = uvc_host_suspended, + /** Handle device resume */ + .resumed = uvc_host_resumed, +}; + +static DEVICE_API(video, uvc_host_video_api) = { + .set_format = video_usb_uvc_host_set_fmt, + .get_format = video_usb_uvc_host_get_fmt, + .get_caps = video_usb_uvc_host_get_caps, + .set_frmival = video_usb_uvc_host_set_frmival, + .get_frmival = video_usb_uvc_host_get_frmival, + .enum_frmival = video_usb_uvc_host_enum_frmival, +#ifdef CONFIG_POLL + .set_signal = video_usb_uvc_host_set_signal, +#endif + .get_volatile_ctrl = video_usb_uvc_host_get_volatile_ctrl, + .set_ctrl = video_usb_uvc_host_set_ctrl, + .set_stream = video_usb_uvc_host_set_stream, + .enqueue = video_usb_uvc_host_enqueue, + .dequeue = video_usb_uvc_host_dequeue, +}; + +#define USBH_VIDEO_DT_DEVICE_DEFINE(n) \ + static struct uvc_device uvc_device_##n; \ + DEVICE_DT_INST_DEFINE(n, uvc_host_preinit, NULL, \ + &uvc_device_##n, NULL, \ + POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, \ + &uvc_host_video_api); \ + USBH_DEFINE_CLASS(uvc_host_c_data_##n, &uvc_host_class_api, \ + (void *)DEVICE_DT_INST_GET(n), \ + uvc_device_code, 2); \ + VIDEO_DEVICE_DEFINE(usb_camera_##n, (void *)DEVICE_DT_INST_GET(n), NULL); + +DT_INST_FOREACH_STATUS_OKAY(USBH_VIDEO_DT_DEVICE_DEFINE) diff --git a/subsys/usb/host/class/usbh_uvc.h b/subsys/usb/host/class/usbh_uvc.h new file mode 100644 index 0000000000000..f274df94d63b6 --- /dev/null +++ b/subsys/usb/host/class/usbh_uvc.h @@ -0,0 +1,610 @@ +/* + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief USB Video Class host driver header + * + */ + +#ifndef ZEPHYR_INCLUDE_USBH_CLASS_UVC_H_ +#define ZEPHYR_INCLUDE_USBH_CLASS_UVC_H_ + +#include + +/* Video Interface Class Codes */ +#define UVC_SC_VIDEOCLASS 0x0E + +/* Video Interface Subclass Codes */ +#define UVC_SC_VIDEOCONTROL 0x01 +#define UVC_SC_VIDEOSTREAMING 0x02 +#define UVC_SC_VIDEO_INTERFACE_COLLECTION 0x03 + +#define UVC_CS_INTERFACE 0x24 +#define UVC_CS_ENDPOINT 0x25 + +/* 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 Streaming 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_CONTROL_UNDEFINED 0x00 +#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_CONTROL_UNDEFINED 0x00 +#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 + +/* Video Class-Specific Request Code */ +#define UVC_RC_UNDEFINED 0x00 +#define UVC_SET_CUR 0x01 +#define UVC_SET_CUR_ALL 0x11 +#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 +#define UVC_GET_CUR_ALL 0x91 +#define UVC_GET_MIN_ALL 0x92 +#define UVC_GET_MAX_ALL 0x93 +#define UVC_GET_RES_ALL 0x94 +#define UVC_GET_DEF_ALL 0x97 + +/* Processing Unit Control Bit Positions (for bmControls bitmap) */ +#define UVC_PU_BMCONTROL_BRIGHTNESS BIT(0) +#define UVC_PU_BMCONTROL_CONTRAST BIT(1) +#define UVC_PU_BMCONTROL_HUE BIT(2) +#define UVC_PU_BMCONTROL_SATURATION BIT(3) +#define UVC_PU_BMCONTROL_SHARPNESS BIT(4) +#define UVC_PU_BMCONTROL_GAMMA BIT(5) +#define UVC_PU_BMCONTROL_WHITE_BALANCE_TEMPERATURE BIT(6) +#define UVC_PU_BMCONTROL_WHITE_BALANCE_COMPONENT BIT(7) +#define UVC_PU_BMCONTROL_BACKLIGHT_COMPENSATION BIT(8) +#define UVC_PU_BMCONTROL_GAIN BIT(9) +#define UVC_PU_BMCONTROL_POWER_LINE_FREQUENCY BIT(10) +#define UVC_PU_BMCONTROL_HUE_AUTO BIT(11) +#define UVC_PU_BMCONTROL_WHITE_BALANCE_TEMPERATURE_AUTO BIT(12) +#define UVC_PU_BMCONTROL_WHITE_BALANCE_COMPONENT_AUTO BIT(13) +#define UVC_PU_BMCONTROL_DIGITAL_MULTIPLIER BIT(14) +#define UVC_PU_BMCONTROL_DIGITAL_MULTIPLIER_LIMIT BIT(15) +#define UVC_PU_BMCONTROL_ANALOG_VIDEO_STANDARD BIT(16) +#define UVC_PU_BMCONTROL_ANALOG_LOCK_STATUS BIT(17) +#define UVC_PU_BMCONTROL_CONTRAST_AUTO BIT(18) +/* Bits 19-23 are reserved for future use */ + +/* Camera Terminal Control Bit Positions (for bmControls bitmap) */ +#define UVC_CT_BMCONTROL_SCANNING_MODE BIT(0) +#define UVC_CT_BMCONTROL_AE_MODE BIT(1) +#define UVC_CT_BMCONTROL_AE_PRIORITY BIT(2) +#define UVC_CT_BMCONTROL_EXPOSURE_TIME_ABSOLUTE BIT(3) +#define UVC_CT_BMCONTROL_EXPOSURE_TIME_RELATIVE BIT(4) +#define UVC_CT_BMCONTROL_FOCUS_ABSOLUTE BIT(5) +#define UVC_CT_BMCONTROL_FOCUS_RELATIVE BIT(6) +#define UVC_CT_BMCONTROL_IRIS_ABSOLUTE BIT(7) +#define UVC_CT_BMCONTROL_IRIS_RELATIVE BIT(8) +#define UVC_CT_BMCONTROL_ZOOM_ABSOLUTE BIT(9) +#define UVC_CT_BMCONTROL_ZOOM_RELATIVE BIT(10) +#define UVC_CT_BMCONTROL_PAN_TILT_ABSOLUTE BIT(11) +#define UVC_CT_BMCONTROL_PAN_TILT_RELATIVE BIT(12) +#define UVC_CT_BMCONTROL_ROLL_ABSOLUTE BIT(13) +#define UVC_CT_BMCONTROL_ROLL_RELATIVE BIT(14) +/* Bits 15-16 are reserved */ +#define UVC_CT_BMCONTROL_FOCUS_AUTO BIT(17) +#define UVC_CT_BMCONTROL_PRIVACY BIT(18) +#define UVC_CT_BMCONTROL_FOCUS_SIMPLE BIT(19) +#define UVC_CT_BMCONTROL_WINDOW BIT(20) +#define UVC_CT_BMCONTROL_REGION_OF_INTEREST BIT(21) +/* Bits 22-23 are reserved for future use */ + +/* 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) + +/* Maximum number of MJPEG formats */ +#ifndef UVC_MAX_MJPEG_FORMAT +#define UVC_MAX_MJPEG_FORMAT 8 +#endif + +/* Maximum number of uncompressed formats */ +#ifndef UVC_MAX_UNCOMPRESSED_FORMAT +#define UVC_MAX_UNCOMPRESSED_FORMAT 8 +#endif + +/* Maximum number of stream interfaces */ +#define UVC_STREAM_INTERFACES_MAX_ALT 32 + +/* MJPEG alias definition for code consistency */ +#ifndef VIDEO_PIX_FMT_MJPEG +#define VIDEO_PIX_FMT_MJPEG VIDEO_PIX_FMT_JPEG +#endif + +/* Maximum frame intervals configuration */ +#ifndef CONFIG_USBH_UVC_MAX_FRAME_INTERVALS +#define CONFIG_USBH_UVC_MAX_FRAME_INTERVALS 8 +#endif + +/* Standard Video Interface Collection IAD */ +struct usb_interface_association_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; /* INTERFACE_ASSOCIATION (0x0B) */ + uint8_t bFirstInterface; + uint8_t bInterfaceCount; + uint8_t bFunctionClass; /* UVC_SC_VIDEOCLASS (0x0E) */ + uint8_t bFunctionSubClass; /* UVC_SC_VIDEO_INTERFACE_COLLECTION (0x03) */ + uint8_t bFunctionProtocol; + uint8_t iFunction; +} __packed; + +/* UVC VideoStreaming Input Header Descriptor */ +struct uvc_vs_input_header_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; /* CS_INTERFACE (0x24) */ + uint8_t bDescriptorSubType; /* VS_INPUT_HEADER (0x01) */ + 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; + uint8_t bmControls[]; /* Variable length control bitmap */ +} __packed; + +/* UVC VideoStreaming Output Header Descriptor */ +struct uvc_vs_output_header_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; /* CS_INTERFACE (0x24) */ + uint8_t bDescriptorSubType; /* VS_OUTPUT_HEADER (0x02) */ + uint8_t bNumFormats; + uint16_t wTotalLength; + uint8_t bEndpointAddress; + uint8_t bTerminalLink; + uint8_t bControlSize; + uint8_t bmControls[]; /* Variable length control bitmap */ +} __packed; + +/* UVC VideoStreaming Uncompressed Format Descriptor */ +struct uvc_vs_format_uncompressed { + 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; + +/* UVC VideoStreaming MJPEG Format Descriptor */ +struct uvc_vs_format_mjpeg { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bFormatIndex; + uint8_t bNumFrameDescriptors; + uint8_t bmFlags; + uint8_t bDefaultFrameIndex; + uint8_t bAspectRatioX; + uint8_t bAspectRatioY; + uint8_t bmInterlaceFlags; + uint8_t bCopyProtect; +} __packed; + +/* UVC VideoStreaming Uncompressed Frame Descriptor */ +struct uvc_vs_frame_uncompressed { + 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; + /* Followed by frame interval data */ +} __packed; + +/* UVC VideoStreaming MJPEG Frame Descriptor */ +struct uvc_vs_frame_mjpeg { + 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; + /* Followed by frame interval data */ +} __packed; + +/* UVC Format Descriptor Common Header */ +struct uvc_format_header { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bFormatIndex; + uint8_t bNumFrameDescriptors; +} __packed; + +/* UVC Frame Descriptor Common Header */ +struct uvc_frame_header { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bFrameIndex; + uint8_t bmCapabilities; + uint16_t wWidth; + uint16_t wHeight; +} __packed; + +/** Header of an USB CS descriptor */ +struct usb_cs_desc_header { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; +} __packed; + +/* UVC VideoControl Interface Header Descriptor */ +struct uvc_vc_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[]; +} __packed; + +/* UVC VideoControl Input Terminal Descriptor */ +struct uvc_vc_input_terminal_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; /* CS_INTERFACE (0x24) */ + uint8_t bDescriptorSubType; /* VC_INPUT_TERMINAL (0x02) */ + uint8_t bTerminalID; + uint16_t wTerminalType; + uint8_t bAssocTerminal; + uint8_t iTerminal; +} __packed; + +/* UVC VideoControl Output Terminal Descriptor */ +struct uvc_vc_output_terminal_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; /* CS_INTERFACE (0x24) */ + uint8_t bDescriptorSubType; /* VC_OUTPUT_TERMINAL (0x03) */ + uint8_t bTerminalID; + uint16_t wTerminalType; + uint8_t bAssocTerminal; + uint8_t bSourceID; + uint8_t iTerminal; +} __packed; + +/* UVC VideoControl Camera Terminal Descriptor */ +struct uvc_vc_camera_terminal_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; /* CS_INTERFACE (0x24) */ + uint8_t bDescriptorSubType; /* VC_INPUT_TERMINAL (0x02) */ + uint8_t bTerminalID; + uint16_t wTerminalType; /* Should be 0x0201 for Camera Terminal */ + uint8_t bAssocTerminal; + uint8_t iTerminal; + uint16_t wObjectiveFocalLengthMin; + uint16_t wObjectiveFocalLengthMax; + uint16_t wOcularFocalLength; + uint8_t bControlSize; + uint8_t bmControls[]; /* Variable length control bitmap */ +} __packed; + +/* UVC VideoControl Selector Unit Descriptor */ +struct uvc_vc_selector_unit_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; /* CS_INTERFACE (0x24) */ + uint8_t bDescriptorSubType; /* VC_SELECTOR_UNIT (0x04) */ + uint8_t bUnitID; + uint8_t bNrInPins; + uint8_t baSourceID[]; /* Variable length array of source IDs */ + /* Note: iSelector field follows baSourceID but is not included here + * due to variable length baSourceID array */ +} __packed; + +/* UVC VideoControl Processing Unit Descriptor */ +struct uvc_vc_processing_unit_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; /* CS_INTERFACE (0x24) */ + uint8_t bDescriptorSubType; /* VC_PROCESSING_UNIT (0x05) */ + uint8_t bUnitID; + uint8_t bSourceID; + uint16_t wMaxMultiplier; + uint8_t bControlSize; + uint8_t bmControls[]; /* Variable length control bitmap */ + /* Note: iProcessing field follows bmControls but is not included here + * due to variable length bmControls array */ +} __packed; + +/* UVC VideoControl Encoding Unit Descriptor */ +struct uvc_vc_encoding_unit_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; /* CS_INTERFACE (0x24) */ + uint8_t bDescriptorSubType; /* VC_ENCODING_UNIT (0x07) */ + uint8_t bUnitID; + uint8_t bSourceID; + uint8_t iEncoding; + uint8_t bControlSize; + uint8_t bmControls[]; /* Variable length control bitmap */ + /* Note: bmControlsRuntime field may follow bmControls depending on version */ +} __packed; + +/* UVC VideoControl Extension Unit Descriptor */ +struct uvc_vc_extension_unit_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; /* CS_INTERFACE (0x24) */ + uint8_t bDescriptorSubType; /* VC_EXTENSION_UNIT (0x06) */ + uint8_t bUnitID; + uint8_t guidExtensionCode[16]; /* GUID identifying the Extension Unit */ + uint8_t bNumControls; + uint8_t bNrInPins; + uint8_t baSourceID[]; /* Variable length array of source IDs */ + /* Note: bControlSize, bmControls[], and iExtension fields follow baSourceID + * but are not included here due to variable length baSourceID array */ +} __packed; + +/* UVC Payload Header */ +struct uvc_payload_header { + uint8_t bHeaderLength; + uint8_t bmHeaderInfo; + uint32_t dwPresentationTime; + uint8_t scrSourceClock[6]; +} __packed; + +/* Video Probe and Commit Controls */ +struct uvc_probe_commit { + 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; +} __packed; + +/* Video Still Probe Control and Still Commit Control */ +struct uvc_still_probe_commit { + uint8_t bFormatIndex; /* Still image format index */ + uint8_t bFrameIndex; /* Still image frame index */ + uint8_t bCompressionIndex; /* Compression level index */ + uint32_t dwMaxVideoFrameSize; /* Maximum image size */ + uint32_t dwMaxPayloadTransferSize; /* Maximum transfer payload */ +} __packed; + + +/* UVC frame interval types */ +enum uvc_frame_interval_type { + UVC_FRAME_INTERVAL_DISCRETE, + UVC_FRAME_INTERVAL_CONTINUOUS, + UVC_FRAME_INTERVAL_STEPWISE, +}; + +/* UVC frame information structure */ +struct uvc_frame_info { + uint8_t frame_index; + uint16_t width; + uint16_t height; + uint32_t min_bit_rate; + uint32_t max_bit_rate; + uint32_t max_frame_buffer_size; + uint32_t default_frame_interval; + + enum uvc_frame_interval_type interval_type; + uint8_t num_intervals; + union { + uint32_t discrete[CONFIG_USBH_UVC_MAX_FRAME_INTERVALS]; + struct { + uint32_t min; + uint32_t max; + uint32_t step; + } stepwise; + } intervals; +}; + +struct uvc_stream_iface_info { + struct usb_if_descriptor *current_stream_iface; /* Stream interface */ + struct usb_ep_descriptor *current_stream_ep; /* Stream endpoint */ + uint32_t cur_ep_mps_mult; /* Endpoint max packet size including multiple transactions */ +}; + +/* UVC format structure */ +struct uvc_vs_format { + uint32_t pixelformat; + uint16_t width; + uint16_t height; + uint32_t fps; + uint32_t frame_interval; + uint32_t pitch; + uint8_t format_index; + uint8_t frame_index; + struct uvc_format_header *format_ptr; + struct uvc_frame_header *frame_ptr; +}; + +struct uvc_vs_format_mjpeg_info { + struct uvc_vs_format_mjpeg *vs_mjpeg_format[UVC_MAX_MJPEG_FORMAT]; + uint8_t num_mjpeg_formats; +}; + +struct uvc_vs_format_uncompressed_info { + struct uvc_vs_format_uncompressed *uncompressed_format[UVC_MAX_UNCOMPRESSED_FORMAT]; + uint8_t num_uncompressed_formats; +}; + +/* Video stream format information structure */ +struct uvc_vs_format_info { + struct uvc_vs_format_mjpeg_info format_mjpeg; + struct uvc_vs_format_uncompressed_info format_uncompressed; +}; + +#endif /* ZEPHYR_INCLUDE_USBH_CLASS_UVC_H_ */ From 84e7ddb0b71a8ba91c1f2614da50c00755e227fc Mon Sep 17 00:00:00 2001 From: Aiden Hu Date: Wed, 6 Aug 2025 09:16:45 +0800 Subject: [PATCH 14/31] usb: host: update host core to support multi usb classes Register all of usb classes that host needs supports during initialization. Call connection function for all of classes that one attached device has. Signed-off-by: Aiden Hu --- subsys/usb/host/usbh_core.c | 185 +++++++++++++++++++++++++++++++++++- 1 file changed, 183 insertions(+), 2 deletions(-) diff --git a/subsys/usb/host/usbh_core.c b/subsys/usb/host/usbh_core.c index d1dc0022dfaaa..8a7451b98688b 100644 --- a/subsys/usb/host/usbh_core.c +++ b/subsys/usb/host/usbh_core.c @@ -12,9 +12,11 @@ #include #include -#include "usbh_internal.h" -#include "usbh_device.h" #include "usbh_class.h" +#include "usbh_class_api.h" +#include "usbh_desc.h" +#include "usbh_device.h" +#include "usbh_internal.h" #include LOG_MODULE_REGISTER(uhs, CONFIG_USBH_LOG_LEVEL); @@ -45,6 +47,177 @@ static int usbh_event_carrier(const struct device *dev, return err; } +/** + * @brief Enumerate device descriptors and match class drivers + * + * This function traverses the USB device descriptors, identifies class-specific + * descriptor segments, and attempts to match them with registered class drivers. + * + * @param ctx USB host context + * @param udev USB device to process + * @return 0 on success, negative errno on failure + */ +static int usbh_match_classes(struct usbh_context *const ctx, + struct usb_device *udev) +{ + struct usb_cfg_descriptor *cfg_desc = udev->cfg_desc; + + if (!cfg_desc) { + LOG_ERR("No configuration descriptor found"); + return -EINVAL; + } + + uint8_t *desc_buf_base = (uint8_t *)cfg_desc; + uint8_t *desc_buf_end = desc_buf_base + sys_le16_to_cpu(cfg_desc->wTotalLength); + uint8_t *current_desc = desc_buf_base + cfg_desc->bLength; /* Skip config descriptor */ + int matched_count = 0; + + LOG_DBG("Starting class enumeration for device (total desc length: %d)", + sys_le16_to_cpu(cfg_desc->wTotalLength)); + + /* Main descriptor traversal loop - traverse entire descriptor */ + while (current_desc < desc_buf_end) { + uint8_t *start_addr = current_desc; /* Initialize to current descriptor */ + uint8_t *end_addr = current_desc; /* Initialize to current descriptor */ + uint8_t *next_iad_addr = NULL; + struct usbh_class_filter device_info = {0}; + bool found_iad = false; + bool found_interface = false; + struct usb_desc_header *iad_desc = NULL; + struct usb_desc_header *desc; + uint32_t mask; + + /* Step 1: Find first IAD or interface descriptor from start_addr */ + mask = BIT(USB_DESC_INTERFACE) | BIT(USB_DESC_INTERFACE_ASSOC); + + desc = usbh_desc_get_by_type(start_addr, end_addr, mask); + if (desc == NULL) { + LOG_ERR("No IAD or interface descriptor found - error condition"); + break; + } + start_addr = (uint8_t *)desc; + + if (desc->bDescriptorType == USB_DESC_INTERFACE_ASSOC) { + found_iad = true; + } + + if (desc->bDescriptorType == USB_DESC_INTERFACE) { + struct usb_if_descriptor *if_desc = (void *)desc; + + device_info.code_triple.dclass = if_desc->bInterfaceClass; + device_info.code_triple.sub = if_desc->bInterfaceSubClass; + device_info.code_triple.proto = if_desc->bInterfaceProtocol; + found_interface = true; + } + + /* Step 2: Continue searching for subsequent descriptors to determine end_addr */ + start_addr += ((struct usb_desc_header *)start_addr)->bLength; + + /* Find next IAD */ + mask = BIT(USB_DESC_INTERFACE_ASSOC); + desc = usbh_desc_get_by_type(start_addr, end_addr, mask); + next_iad_addr = (uint8_t *)desc; + + /* Handle different cases and determine end_addr and device_info. */ + if (!found_iad && next_iad_addr == NULL) { + /* Case 2a: No IAD in step 1, no IAD in subsequent descriptors */ + end_addr = desc_buf_end; + /* device_info already saved in step 1 */ + } else if (!found_iad && next_iad_addr) { + /* Case 2b: No IAD in step 1, found new IAD in subsequent descriptors */ + end_addr = next_iad_addr; + /* device_info already saved in step 1 */ + } else if (found_iad && next_iad_addr) { + /* Case 2c: Found IAD in step 1, found new IAD in subsequent descriptors */ + end_addr = next_iad_addr; + start_addr += iad_desc->bLength; + + /* Get class code from first interface after IAD */ + mask = BIT(USB_DESC_INTERFACE_ASSOC); + desc = usbh_desc_get_by_type(start_addr, end_addr, mask); + if (desc != NULL) { + struct usb_if_descriptor *if_desc = (void *)desc; + + device_info.code_triple.dclass = if_desc->bInterfaceClass; + device_info.code_triple.sub = if_desc->bInterfaceSubClass; + device_info.code_triple.proto = if_desc->bInterfaceProtocol; + } + } else if (found_iad && !next_iad_addr) { + /* Case 2d: Found IAD in step 1, no new IAD in subsequent descriptors */ + /* Get class code from first interface after IAD */ + start_addr += iad_desc->bLength; + mask = BIT(USB_DESC_INTERFACE); + desc = usbh_desc_get_by_type(start_addr, end_addr, mask); + if (desc != NULL) { + struct usb_if_descriptor *if_desc = (void *)start_addr; + + device_info.code_triple.dclass = if_desc->bInterfaceClass; + device_info.code_triple.sub = if_desc->bInterfaceSubClass; + device_info.code_triple.proto = if_desc->bInterfaceProtocol; + } + + /* Search for interface descriptor with different class code after IAD */ + start_addr += iad_desc->bLength; + mask = BIT(USB_DESC_INTERFACE); + desc = usbh_desc_get_by_type(start_addr, desc_buf_end, mask); + if (desc != NULL) { + struct usb_if_descriptor *if_desc = (void *)desc; + + /* Only compare class code */ + if (if_desc->bInterfaceClass != device_info.code_triple.dclass) { + end_addr = desc_buf_end; + } + } + } + + LOG_DBG("Found class segment: class=0x%02x, sub=0x%02x, proto=0x%02x, start=%p, end=%p", + device_info.code_triple.dclass, device_info.code_triple.sub, + device_info.code_triple.proto, start_addr, end_addr); + + /* Step 3: Loop through registered class drivers and call usbh_match_class_driver */ + struct usbh_class_data *cdata; + bool matched = false; + + SYS_SLIST_FOR_EACH_CONTAINER(&ctx->class_list, cdata, node) { + /* Call usbh_match_class_driver with cdata and code */ + if (usbh_class_is_matching(cdata, &device_info)) { + LOG_INF("Class driver %s matched for class 0x%02x", + cdata->name, device_info.code_triple.dclass); + + /* Step 4: Call connected handler */ + int ret = usbh_class_connected(cdata, start_addr, end_addr); + if (ret == 0) { + LOG_INF("Class driver %s successfully claimed device", cdata->name); + matched = true; + matched_count++; + } else { + LOG_WRN("Class driver %s failed to claim device: %d", + cdata->name, ret); + } + } + } + + if (!matched) { + LOG_DBG("No class driver matched for class 0x%02x", + device_info.code_triple.dclass); + } + + /* Step 4: assign end_addr to current_desc and continue main loop */ + current_desc = end_addr; + + /* Ensure we advance to next valid descriptor */ + if (current_desc < desc_buf_end) { + struct usb_desc_header *header = (struct usb_desc_header *)current_desc; + if (header->bLength == 0) { + break; + } + } + } + + LOG_INF("Class enumeration completed: %d driver(s) matched", matched_count); + return 0; +} + static void dev_connected_handler(struct usbh_context *const ctx, const struct uhc_event *const event) { @@ -73,6 +246,11 @@ static void dev_connected_handler(struct usbh_context *const ctx, if (usbh_device_init(ctx->root)) { LOG_ERR("Failed to reset new USB device"); } + + /* Now only consider about one device connected (root device) */ + if (usbh_match_classes(ctx, ctx->root)) { + LOG_ERR("Failed to match classes"); + } } static void dev_removed_handler(struct usbh_context *const ctx) @@ -184,6 +362,9 @@ static void usbh_thread(void *p1, void *p2, void *p3) } } +/** + * @brief Initialize USB host controller and class drivers + */ int usbh_init_device_intl(struct usbh_context *const uhs_ctx) { int ret; From 48f918f6ada6f035e6bf9504bddc824b0bb1b45e Mon Sep 17 00:00:00 2001 From: Aiden Hu Date: Wed, 6 Aug 2025 09:23:19 +0800 Subject: [PATCH 15/31] boards: nxp: rd_rw612_bga: add missed usb host and lcd node Add zephyr_uhc0 node; Add zephyr_mipi_dbi_spi and nxp_pmod_touch_panel_i2c node Signed-off-by: Aiden Hu --- boards/nxp/rd_rw612_bga/rd_rw612_bga.dtsi | 14 ++++++++++++++ boards/nxp/rd_rw612_bga/rd_rw612_bga.yaml | 2 ++ 2 files changed, 16 insertions(+) diff --git a/boards/nxp/rd_rw612_bga/rd_rw612_bga.dtsi b/boards/nxp/rd_rw612_bga/rd_rw612_bga.dtsi index 005a9fd14ce7d..9bc151b4c9bb6 100644 --- a/boards/nxp/rd_rw612_bga/rd_rw612_bga.dtsi +++ b/boards/nxp/rd_rw612_bga/rd_rw612_bga.dtsi @@ -237,6 +237,10 @@ zephyr_udc0: &usb_otg { status = "okay"; }; +zephyr_uhc0: &usbh { + status = "okay"; +}; + &dma0 { status = "okay"; }; @@ -316,3 +320,13 @@ nxp_8080_touch_panel_i2c: &arduino_i2c { status = "okay"; wakeup-source; }; + +zephyr_mipi_dbi_spi: &lcdic { + status = "okay"; + pinctrl-0 = <&pinmux_lcdic>; + pinctrl-names = "default"; +}; + +nxp_pmod_touch_panel_i2c: &arduino_i2c { + status = "okay"; +}; diff --git a/boards/nxp/rd_rw612_bga/rd_rw612_bga.yaml b/boards/nxp/rd_rw612_bga/rd_rw612_bga.yaml index 7e3402f7fc8a4..2421f84b6fd19 100644 --- a/boards/nxp/rd_rw612_bga/rd_rw612_bga.yaml +++ b/boards/nxp/rd_rw612_bga/rd_rw612_bga.yaml @@ -28,4 +28,6 @@ supported: - pwm - spi - usb_device + - usb_host + - usbh - watchdog From 09c5ac63c7ca8f6d7d0e0c32cfc399e702d5449e Mon Sep 17 00:00:00 2001 From: Aiden Hu Date: Wed, 6 Aug 2025 09:29:13 +0800 Subject: [PATCH 16/31] samples: drivers: video: update video capture sample. Update this sample is dedicated to support usb host video. This sample can enumerate UVC device, configure UVC device and start video stream transfers then display it on LCD. Signed-off-by: Aiden Hu --- samples/drivers/video/capture/Kconfig | 8 + .../video/capture/boards/rd_rw612_bga.conf | 15 + .../video/capture/boards/rd_rw612_bga.overlay | 11 + samples/drivers/video/capture/prj.conf | 10 +- samples/drivers/video/capture/sample.yaml | 9 +- samples/drivers/video/capture/src/main.c | 631 ++++++++++++------ 6 files changed, 460 insertions(+), 224 deletions(-) create mode 100644 samples/drivers/video/capture/boards/rd_rw612_bga.conf create mode 100644 samples/drivers/video/capture/boards/rd_rw612_bga.overlay diff --git a/samples/drivers/video/capture/Kconfig b/samples/drivers/video/capture/Kconfig index 362a6037a6cf1..0887db64412b0 100644 --- a/samples/drivers/video/capture/Kconfig +++ b/samples/drivers/video/capture/Kconfig @@ -43,6 +43,14 @@ config VIDEO_FRAME_WIDTH help Width of the video frame. If set to 0, the default width is used. +config VIDEO_TARGET_FPS + int "Target frame rate for video capture" + default 0 + help + Target frame rate (FPS) for video capture. If set to 0, the device + default frame rate is used. This value will be used to configure + the video device's frame interval settings. + config VIDEO_PIXEL_FORMAT string "Pixel format of the video frame" help diff --git a/samples/drivers/video/capture/boards/rd_rw612_bga.conf b/samples/drivers/video/capture/boards/rd_rw612_bga.conf new file mode 100644 index 0000000000000..55cec9704ae1a --- /dev/null +++ b/samples/drivers/video/capture/boards/rd_rw612_bga.conf @@ -0,0 +1,15 @@ +CONFIG_UHC_DRIVER=y +CONFIG_USB_HOST_STACK=y +CONFIG_USBH_VIDEO_CLASS=y + +# Video buffer configuration +CONFIG_VIDEO_BUFFER_POOL_SZ_MAX=154000 +CONFIG_VIDEO_BUFFER_POOL_NUM_MAX=2 +CONFIG_VIDEO_BUFFER_POOL_ALIGN=32 + + +# Memory configuration for USB transfers +CONFIG_HEAP_MEM_POOL_SIZE=70000 +CONFIG_MAIN_STACK_SIZE=80960 +CONFIG_USBH_USB_DEVICE_HEAP=8912 +CONFIG_UHC_BUF_POOL_SIZE=4096 \ No newline at end of file diff --git a/samples/drivers/video/capture/boards/rd_rw612_bga.overlay b/samples/drivers/video/capture/boards/rd_rw612_bga.overlay new file mode 100644 index 0000000000000..216840fee13a4 --- /dev/null +++ b/samples/drivers/video/capture/boards/rd_rw612_bga.overlay @@ -0,0 +1,11 @@ +/* + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + uvc_host: uvc_host { + compatible = "zephyr,uvc-host"; + }; +}; diff --git a/samples/drivers/video/capture/prj.conf b/samples/drivers/video/capture/prj.conf index 360778bc420f0..69e4b08258b70 100644 --- a/samples/drivers/video/capture/prj.conf +++ b/samples/drivers/video/capture/prj.conf @@ -1,8 +1,14 @@ -CONFIG_VIDEO=y +CONFIG_POLL=y +CONFIG_EVENTS=y CONFIG_SHELL=y CONFIG_DEVICE_SHELL=y +CONFIG_VIDEO=y +CONFIG_USB_HOST_STACK=y CONFIG_PRINTK=y CONFIG_LOG=y CONFIG_DISPLAY=y CONFIG_REQUIRES_FLOAT_PRINTF=y -CONFIG_LOG_MODE_DEFERRED=y +CONFIG_DEBUG_OPTIMIZATIONS=y +CONFIG_USBH_VIDEO_LOG_LEVEL_WRN=y +CONFIG_VIDEO_FRAME_WIDTH=320 +CONFIG_VIDEO_FRAME_HEIGHT=240 \ No newline at end of file diff --git a/samples/drivers/video/capture/sample.yaml b/samples/drivers/video/capture/sample.yaml index 3c4d16010f9db..e6dd19192fcaf 100644 --- a/samples/drivers/video/capture/sample.yaml +++ b/samples/drivers/video/capture/sample.yaml @@ -4,6 +4,7 @@ tests: sample.video.capture: tags: - video + - usb - shield - samples extra_args: @@ -13,9 +14,11 @@ tests: - platform:frdm_mcxn947/mcxn947/cpu0:SHIELD="dvp_20pin_ov7670;lcd_par_s035_8080" - platform:frdm_mcxn236/mcxn236:SHIELD="dvp_20pin_ov7670;lcd_par_s035_8080" - platform:stm32h7b3i_dk:SHIELD="st_b_cams_omv_mb1683" + - platform:rd_rw612_bga/rw612:SHIELD="usb_camera;lcd_par_s035_8080" extra_configs: - CONFIG_TEST=y - CONFIG_FPU=y + - CONFIG_USB_HOST=y harness: console harness_config: fixture: fixture_camera @@ -31,15 +34,19 @@ tests: - mimxrt1064_evk/mimxrt1064 - mimxrt1170_evk/mimxrt1176/cm7 - mimxrt1170_evk@B/mimxrt1176/cm7 + - rd_rw612_bga/rw612 - frdm_mcxn947/mcxn947/cpu0 - frdm_mcxn236/mcxn236 - mm_swiftio - esp32s3_eye/esp32s3/procpu - stm32h7b3i_dk - depends_on: video + depends_on: + - video + - usbh integration_platforms: - mimxrt1064_evk/mimxrt1064 - mimxrt1170_evk/mimxrt1176/cm7 + - rd_rw612_bga/rw612 sample.video.capture.shell: tags: - video diff --git a/samples/drivers/video/capture/src/main.c b/samples/drivers/video/capture/src/main.c index bfb8e051f2993..e348fa9b24411 100644 --- a/samples/drivers/video/capture/src/main.c +++ b/samples/drivers/video/capture/src/main.c @@ -5,13 +5,16 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include #include +#include +#include #include #include #include #include - +#include #include #ifdef CONFIG_TEST @@ -22,10 +25,11 @@ LOG_MODULE_REGISTER(main, LOG_LEVEL_DBG); LOG_MODULE_REGISTER(main, CONFIG_LOG_DEFAULT_LEVEL); #endif -#if !DT_HAS_CHOSEN(zephyr_camera) -#error No camera chosen in devicetree. Missing "--shield" or "--snippet video-sw-generator" flag? -#endif +USBH_CONTROLLER_DEFINE(uhs_ctx, DEVICE_DT_GET(DT_NODELABEL(zephyr_uhc0))); +const struct device *const uvc_host = DEVICE_DT_GET(DT_NODELABEL(uvc_host)); +static uint8_t convert_buffer[CONFIG_VIDEO_BUFFER_POOL_SZ_MAX]; +/* TODO: Need to consider about using pixfmt */ #if DT_HAS_CHOSEN(zephyr_display) static inline int display_setup(const struct device *const display_dev, const uint32_t pixfmt) { @@ -44,19 +48,9 @@ static inline int display_setup(const struct device *const display_dev, const ui capabilities.current_orientation); /* Set display pixel format to match the one in use by the camera */ - switch (pixfmt) { - case VIDEO_PIX_FMT_RGB565: - if (capabilities.current_pixel_format != PIXEL_FORMAT_RGB_565) { - ret = display_set_pixel_format(display_dev, PIXEL_FORMAT_RGB_565); - } - break; - case VIDEO_PIX_FMT_XRGB32: - if (capabilities.current_pixel_format != PIXEL_FORMAT_ARGB_8888) { - ret = display_set_pixel_format(display_dev, PIXEL_FORMAT_ARGB_8888); - } - break; - default: - return -ENOTSUP; + + if (capabilities.current_pixel_format != PIXEL_FORMAT_BGR_565) { + ret = display_set_pixel_format(display_dev, PIXEL_FORMAT_BGR_565); } if (ret) { @@ -74,29 +68,155 @@ static inline int display_setup(const struct device *const display_dev, const ui return ret; } -static inline void video_display_frame(const struct device *const display_dev, - const struct video_buffer *const vbuf, - const struct video_format fmt) +static int yuyv_to_bgr565_convert(const uint8_t *yuyv_data, size_t yuyv_size, + uint8_t **bgr565_data, size_t *bgr565_size, + uint16_t width, uint16_t height) { - struct display_buffer_descriptor buf_desc = { - .buf_size = vbuf->bytesused, - .width = fmt.width, - .pitch = buf_desc.width, - .height = vbuf->bytesused / fmt.pitch, - }; + /* Calculate required buffer size for BGR565 */ + size_t required_size = width * height * 2; + + + uint16_t *bgr565_out = (uint16_t *)convert_buffer; + const uint8_t *yuyv_in = yuyv_data; + + /* Corrected loop: properly handle each pixel pair */ + int output_idx = 0; // BGR565 output index + + for (int pixel_pair = 0; pixel_pair < (width * height) / 2; pixel_pair++) { + /* Extract YUYV components */ + int y0 = yuyv_in[0]; + int u = yuyv_in[1] - 128; + int y1 = yuyv_in[2]; + int v = yuyv_in[3] - 128; + + /* Convert first pixel (Y0UV) */ + int r0 = y0 + ((1436 * v) >> 10); + int g0 = y0 - ((354 * u + 732 * v) >> 10); + int b0 = y0 + ((1814 * u) >> 10); + + /* Clamp to 0-255 range */ + r0 = r0 < 0 ? 0 : (r0 > 255 ? 255 : r0); + g0 = g0 < 0 ? 0 : (g0 > 255 ? 255 : g0); + b0 = b0 < 0 ? 0 : (b0 > 255 ? 255 : b0); - display_write(display_dev, 0, vbuf->line_offset, &buf_desc, vbuf->buffer); + /* Convert second pixel (Y1UV) */ + int r1 = y1 + ((1436 * v) >> 10); + int g1 = y1 - ((354 * u + 732 * v) >> 10); + int b1 = y1 + ((1814 * u) >> 10); + + /* Clamp to 0-255 range */ + r1 = r1 < 0 ? 0 : (r1 > 255 ? 255 : r1); + g1 = g1 < 0 ? 0 : (g1 > 255 ? 255 : g1); + b1 = b1 < 0 ? 0 : (b1 > 255 ? 255 : b1); + + /* Safely store BGR565 pixels */ + if (output_idx < (width * height)) { + bgr565_out[output_idx] = ((b0 >> 3) << 11) | ((g0 >> 2) << 5) | (r0 >> 3); + output_idx++; + } + + if (output_idx < (width * height)) { + bgr565_out[output_idx] = ((b1 >> 3) << 11) | ((g1 >> 2) << 5) | (r1 >> 3); + output_idx++; + } + + yuyv_in += 4; /* Move to next YUYV data group */ + } + + /* Validate output index */ + if (output_idx != width * height) { + LOG_WRN("Output pixel count mismatch: expected=%d, got=%d", width * height, output_idx); + } + + *bgr565_data = convert_buffer; + *bgr565_size = required_size; + + LOG_DBG("Successfully converted YUYV to BGR565: %ux%u (%zu bytes)", + width, height, required_size); + + return 0; +} + +static inline void video_display_frame(const struct device *const display_dev, + const struct video_buffer *const vbuf, + const struct video_format fmt) +{ + if (!vbuf || !vbuf->buffer) { + LOG_ERR("Invalid vbuf or buffer pointer"); + return; + } + + LOG_DBG("Display frame: format=0x%x, size=%u, buffer=%p", + fmt.pixelformat, vbuf->bytesused, vbuf->buffer); + + /* Check if YUYV format requires conversion */ + if (fmt.pixelformat == VIDEO_PIX_FMT_YUYV) { + uint8_t *rgb565_data = NULL; + size_t rgb565_size = 0; + int ret; + + LOG_DBG("Converting YUYV to RGB565: %ux%u", fmt.width, fmt.height); + + /* Perform color space conversion */ + ret = yuyv_to_bgr565_convert(vbuf->buffer, vbuf->bytesused, + &rgb565_data, &rgb565_size, + fmt.width, fmt.height); + if (ret != 0) { + LOG_ERR("Failed to convert YUYV to RGB565: %d", ret); + return; + } + + LOG_DBG("Conversion successful: %zu bytes", rgb565_size); + + /* Display using converted data */ + struct display_buffer_descriptor buf_desc = { + .buf_size = rgb565_size, + .width = fmt.width, + .pitch = fmt.width, + .height = fmt.height, + }; + + int display_ret = display_write(display_dev, 0, 0, &buf_desc, rgb565_data); + if (display_ret != 0) { + LOG_ERR("Failed to write converted frame to display: %d", display_ret); + } else { + LOG_DBG("Successfully displayed converted frame: %ux%u", + fmt.width, fmt.height); + } + + } else { + /* Original display logic for other formats */ + struct display_buffer_descriptor buf_desc = { + .buf_size = vbuf->bytesused, + .width = fmt.width, + .pitch = fmt.width, + .height = vbuf->bytesused / fmt.pitch, + }; + + int display_ret = display_write(display_dev, 0, vbuf->line_offset, + &buf_desc, vbuf->buffer); + if (display_ret != 0) { + LOG_ERR("Failed to write frame to display: %d", display_ret); + } + } } #endif +/* TODO: handle disconnection,etc.. */ +static void video_uvc_device_signal_handler(void) +{ +} + int main(void) { struct video_buffer *vbuf = &(struct video_buffer){}; - const struct device *video_dev; struct video_format fmt; struct video_caps caps; struct video_frmival frmival; struct video_frmival_enum fie; + struct k_poll_signal sig; + struct k_poll_event evt[1]; + k_timeout_t timeout = K_FOREVER; enum video_buf_type type = VIDEO_BUF_TYPE_OUTPUT; #if (CONFIG_VIDEO_SOURCE_CROP_WIDTH && CONFIG_VIDEO_SOURCE_CROP_HEIGHT) || \ CONFIG_VIDEO_FRAME_HEIGHT || CONFIG_VIDEO_FRAME_WIDTH @@ -108,6 +228,13 @@ int main(void) size_t bsize; int i = 0; int err; + int signaled, result; + int tp_set_ret = -ENOTSUP; + +#if DT_HAS_CHOSEN(zephyr_display) + const struct device *const display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display)); + bool display_configured = false; +#endif /* When the video shell is enabled, do not run the capture loop */ if (IS_ENABLED(CONFIG_VIDEO_SHELL)) { @@ -115,232 +242,294 @@ int main(void) return 0; } - video_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_camera)); - if (!device_is_ready(video_dev)) { - LOG_ERR("%s: video device is not ready", video_dev->name); + if (!device_is_ready(uvc_host)) { + LOG_ERR("%s: USB host is not ready", uvc_host->name); return 0; } + LOG_INF("USB host: %s", uvc_host->name); - LOG_INF("Video device: %s", video_dev->name); - - /* Get capabilities */ - caps.type = type; - if (video_get_caps(video_dev, &caps)) { - LOG_ERR("Unable to retrieve video capabilities"); - return 0; + err = usbh_init(&uhs_ctx); + if (err) { + LOG_ERR("Failed to initialize host support"); + return err; } - LOG_INF("- Capabilities:"); - while (caps.format_caps[i].pixelformat) { - const struct video_format_cap *fcap = &caps.format_caps[i]; - /* fourcc to string */ - LOG_INF(" %s width [%u; %u; %u] height [%u; %u; %u]", - VIDEO_FOURCC_TO_STR(fcap->pixelformat), - fcap->width_min, fcap->width_max, fcap->width_step, - fcap->height_min, fcap->height_max, fcap->height_step); - i++; + err = usbh_enable(&uhs_ctx); + if (err) { + LOG_ERR("Failed to enable USB host support"); + return err; } - /* Get default/native format */ - fmt.type = type; - if (video_get_format(video_dev, &fmt)) { - LOG_ERR("Unable to retrieve video format"); - return 0; + k_poll_signal_init(&sig); + k_poll_event_init(&evt[0], K_POLL_TYPE_SIGNAL, K_POLL_MODE_NOTIFY_ONLY, &sig); + + err = video_set_signal(uvc_host, &sig); + if (err != 0) { + LOG_WRN("Failed to setup the signal on %s output endpoint", uvc_host->name); + timeout = K_MSEC(10); } - /* Set the crop setting if necessary */ + while (true) { + err = k_poll(evt, ARRAY_SIZE(evt), timeout); + if (err != 0 && err != -EAGAIN) { + LOG_WRN("Poll failed with error %d, retrying...", err); + continue; + } + + k_poll_signal_check(&sig, &signaled, &result); + + if (signaled) { + k_poll_signal_reset(&sig); + + switch (result) { + case USBH_DEVICE_CONNECTED: + LOG_INF("UVC device connected successfully!"); + + /* Get capabilities */ + caps.type = type; + if (video_get_caps(uvc_host, &caps)) { + LOG_ERR("Unable to retrieve video capabilities"); + break; + } + + LOG_INF("- Capabilities:"); + i = 0; + while (caps.format_caps[i].pixelformat) { + const struct video_format_cap *fcap = &caps.format_caps[i]; + /* fourcc to string */ + LOG_INF(" %s width [%u; %u; %u] height [%u; %u; %u]", + VIDEO_FOURCC_TO_STR(fcap->pixelformat), + fcap->width_min, fcap->width_max, fcap->width_step, + fcap->height_min, fcap->height_max, fcap->height_step); + i++; + } + + /* Get default/native format */ + fmt.type = type; + if (video_get_format(uvc_host, &fmt)) { + LOG_ERR("Unable to retrieve video format"); + break; + } + + /* Set the crop setting if necessary */ #if CONFIG_VIDEO_SOURCE_CROP_WIDTH && CONFIG_VIDEO_SOURCE_CROP_HEIGHT - sel.target = VIDEO_SEL_TGT_CROP; - sel.rect.left = CONFIG_VIDEO_SOURCE_CROP_LEFT; - sel.rect.top = CONFIG_VIDEO_SOURCE_CROP_TOP; - sel.rect.width = CONFIG_VIDEO_SOURCE_CROP_WIDTH; - sel.rect.height = CONFIG_VIDEO_SOURCE_CROP_HEIGHT; - if (video_set_selection(video_dev, &sel)) { - LOG_ERR("Unable to set selection crop"); - return 0; - } - LOG_INF("Selection crop set to (%u,%u)/%ux%u", - sel.rect.left, sel.rect.top, sel.rect.width, sel.rect.height); + sel.target = VIDEO_SEL_TGT_CROP; + sel.rect.left = CONFIG_VIDEO_SOURCE_CROP_LEFT; + sel.rect.top = CONFIG_VIDEO_SOURCE_CROP_TOP; + sel.rect.width = CONFIG_VIDEO_SOURCE_CROP_WIDTH; + sel.rect.height = CONFIG_VIDEO_SOURCE_CROP_HEIGHT; + if (video_set_selection(uvc_host, &sel)) { + LOG_ERR("Unable to set selection crop"); + break; + } + LOG_INF("Selection crop set to (%u,%u)/%ux%u", + sel.rect.left, sel.rect.top, sel.rect.width, sel.rect.height); #endif #if CONFIG_VIDEO_FRAME_HEIGHT || CONFIG_VIDEO_FRAME_WIDTH #if CONFIG_VIDEO_FRAME_HEIGHT - fmt.height = CONFIG_VIDEO_FRAME_HEIGHT; + fmt.height = CONFIG_VIDEO_FRAME_HEIGHT; #endif #if CONFIG_VIDEO_FRAME_WIDTH - fmt.width = CONFIG_VIDEO_FRAME_WIDTH; + fmt.width = CONFIG_VIDEO_FRAME_WIDTH; #endif - /* - * Check (if possible) if targeted size is same as crop - * and if compose is necessary - */ - sel.target = VIDEO_SEL_TGT_CROP; - err = video_get_selection(video_dev, &sel); - if (err < 0 && err != -ENOSYS) { - LOG_ERR("Unable to get selection crop"); - return 0; - } - - if (err == 0 && (sel.rect.width != fmt.width || sel.rect.height != fmt.height)) { - sel.target = VIDEO_SEL_TGT_COMPOSE; - sel.rect.left = 0; - sel.rect.top = 0; - sel.rect.width = fmt.width; - sel.rect.height = fmt.height; - err = video_set_selection(video_dev, &sel); - if (err < 0 && err != -ENOSYS) { - LOG_ERR("Unable to set selection compose"); - return 0; - } - } + /* + * Check (if possible) if targeted size is same as crop + * and if compose is necessary + */ + sel.target = VIDEO_SEL_TGT_CROP; + err = video_get_selection(uvc_host, &sel); + if (err < 0 && err != -ENOSYS) { + LOG_ERR("Unable to get selection crop"); + break; + } + + if (err == 0 && (sel.rect.width != fmt.width || sel.rect.height != fmt.height)) { + sel.target = VIDEO_SEL_TGT_COMPOSE; + sel.rect.left = 0; + sel.rect.top = 0; + sel.rect.width = fmt.width; + sel.rect.height = fmt.height; + err = video_set_selection(uvc_host, &sel); + if (err < 0 && err != -ENOSYS) { + LOG_ERR("Unable to set selection compose"); + break; + } + } #endif - if (strcmp(CONFIG_VIDEO_PIXEL_FORMAT, "")) { - fmt.pixelformat = VIDEO_FOURCC_FROM_STR(CONFIG_VIDEO_PIXEL_FORMAT); - } - - LOG_INF("- Video format: %s %ux%u", - VIDEO_FOURCC_TO_STR(fmt.pixelformat), fmt.width, fmt.height); - - if (video_set_format(video_dev, &fmt)) { - LOG_ERR("Unable to set format"); - return 0; - } - - if (!video_get_frmival(video_dev, &frmival)) { - LOG_INF("- Default frame rate : %f fps", - 1.0 * frmival.denominator / frmival.numerator); - } - - LOG_INF("- Supported frame intervals for the default format:"); - memset(&fie, 0, sizeof(fie)); - fie.format = &fmt; - while (video_enum_frmival(video_dev, &fie) == 0) { - if (fie.type == VIDEO_FRMIVAL_TYPE_DISCRETE) { - LOG_INF(" %u/%u", fie.discrete.numerator, fie.discrete.denominator); - } else { - LOG_INF(" [min = %u/%u; max = %u/%u; step = %u/%u]", - fie.stepwise.min.numerator, fie.stepwise.min.denominator, - fie.stepwise.max.numerator, fie.stepwise.max.denominator, - fie.stepwise.step.numerator, fie.stepwise.step.denominator); - } - fie.index++; - } - - /* Get supported controls */ - LOG_INF("- Supported controls:"); - const struct device *last_dev = NULL; - struct video_ctrl_query cq = {.dev = video_dev, .id = VIDEO_CTRL_FLAG_NEXT_CTRL}; - - while (!video_query_ctrl(&cq)) { - if (cq.dev != last_dev) { - last_dev = cq.dev; - LOG_INF("\t\tdevice: %s", cq.dev->name); - } - video_print_ctrl(&cq); - cq.id |= VIDEO_CTRL_FLAG_NEXT_CTRL; - } - - /* Set controls */ - struct video_control ctrl = {.id = VIDEO_CID_HFLIP, .val = 1}; - int tp_set_ret = -ENOTSUP; - - if (IS_ENABLED(CONFIG_VIDEO_CTRL_HFLIP)) { - video_set_ctrl(video_dev, &ctrl); - } + if (strcmp(CONFIG_VIDEO_PIXEL_FORMAT, "")) { + fmt.pixelformat = VIDEO_FOURCC_FROM_STR(CONFIG_VIDEO_PIXEL_FORMAT); + } +#if CONFIG_VIDEO_FRAME_WIDTH > 0 + fmt.width = CONFIG_VIDEO_FRAME_WIDTH; +#endif - if (IS_ENABLED(CONFIG_VIDEO_CTRL_VFLIP)) { - ctrl.id = VIDEO_CID_VFLIP; - video_set_ctrl(video_dev, &ctrl); - } +#if CONFIG_VIDEO_FRAME_HEIGHT > 0 + fmt.height = CONFIG_VIDEO_FRAME_HEIGHT; +#endif - if (IS_ENABLED(CONFIG_TEST)) { - ctrl.id = VIDEO_CID_TEST_PATTERN; - tp_set_ret = video_set_ctrl(video_dev, &ctrl); - } + LOG_INF("- Video format: %s %ux%u", + VIDEO_FOURCC_TO_STR(fmt.pixelformat), fmt.width, fmt.height); + + if (video_set_format(uvc_host, &fmt)) { + LOG_ERR("Unable to set format"); + break; + } + + if (!video_get_frmival(uvc_host, &frmival)) { + LOG_INF("- Default frame rate : %f fps", + 1.0 * frmival.denominator / frmival.numerator); + } + + LOG_INF("- Supported frame intervals for the default format:"); + memset(&fie, 0, sizeof(fie)); + fie.format = &fmt; + while (video_enum_frmival(uvc_host, &fie) == 0) { + if (fie.type == VIDEO_FRMIVAL_TYPE_DISCRETE) { + LOG_INF(" %u/%u", fie.discrete.numerator, fie.discrete.denominator); + } else { + LOG_INF(" [min = %u/%u; max = %u/%u; step = %u/%u]", + fie.stepwise.min.numerator, fie.stepwise.min.denominator, + fie.stepwise.max.numerator, fie.stepwise.max.denominator, + fie.stepwise.step.numerator, fie.stepwise.step.denominator); + } + fie.index++; + } + + /* Get supported controls */ + LOG_INF("- Supported controls:"); + const struct device *last_dev = NULL; + struct video_ctrl_query cq = {.dev = uvc_host, .id = VIDEO_CTRL_FLAG_NEXT_CTRL}; + + while (!video_query_ctrl(&cq)) { + if (cq.dev != last_dev) { + last_dev = cq.dev; + LOG_INF("\t\tdevice: %s", cq.dev->name); + } + video_print_ctrl(&cq); + cq.id |= VIDEO_CTRL_FLAG_NEXT_CTRL; + } + + /* Set controls */ + struct video_control ctrl = {.id = VIDEO_CID_HFLIP, .val = 1}; + + if (IS_ENABLED(CONFIG_VIDEO_CTRL_HFLIP)) { + video_set_ctrl(uvc_host, &ctrl); + } + + if (IS_ENABLED(CONFIG_VIDEO_CTRL_VFLIP)) { + ctrl.id = VIDEO_CID_VFLIP; + video_set_ctrl(uvc_host, &ctrl); + } + + if (IS_ENABLED(CONFIG_TEST)) { + ctrl.id = VIDEO_CID_TEST_PATTERN; + tp_set_ret = video_set_ctrl(uvc_host, &ctrl); + } #if DT_HAS_CHOSEN(zephyr_display) - const struct device *const display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display)); - - if (!device_is_ready(display_dev)) { - LOG_ERR("%s: display device not ready.", display_dev->name); - return 0; - } - - err = display_setup(display_dev, fmt.pixelformat); - if (err) { - LOG_ERR("Unable to set up display"); - return err; - } + if (!display_configured && device_is_ready(display_dev)) { + err = display_setup(display_dev, fmt.pixelformat); + if (err) { + LOG_ERR("Unable to set up display: %d", err); + } else { + display_configured = true; + LOG_INF("Display configured successfully"); + } + } #endif - /* 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; - } - - /* Alloc video buffers and enqueue for capture */ - if (caps.min_vbuf_count > CONFIG_VIDEO_BUFFER_POOL_NUM_MAX || - bsize > CONFIG_VIDEO_BUFFER_POOL_SZ_MAX) { - LOG_ERR("Not enough buffers or memory to start streaming"); - return 0; - } - - for (i = 0; i < CONFIG_VIDEO_BUFFER_POOL_NUM_MAX; i++) { - /* - * For some hardwares, such as the PxP used on i.MX RT1170 to do image rotation, - * buffer alignment is needed in order to achieve the best performance - */ - vbuf = video_buffer_aligned_alloc(bsize, CONFIG_VIDEO_BUFFER_POOL_ALIGN, - K_FOREVER); - if (vbuf == NULL) { - LOG_ERR("Unable to alloc video buffer"); - return 0; - } - vbuf->type = type; - video_enqueue(video_dev, vbuf); - } - - /* Start video capture */ - if (video_stream_start(video_dev, type)) { - LOG_ERR("Unable to start capture (interface)"); - return 0; - } - - LOG_INF("Capture started"); - - /* Grab video frames */ - vbuf->type = type; - while (1) { - err = video_dequeue(video_dev, &vbuf, K_FOREVER); - if (err) { - LOG_ERR("Unable to dequeue video buf"); - return 0; - } - - LOG_DBG("Got frame %u! size: %u; timestamp %u ms", - frame++, vbuf->bytesused, vbuf->timestamp); + /* Size to allocate for each buffer */ + if (caps.min_line_count == LINE_COUNT_HEIGHT) { + bsize = fmt.width * fmt.height * 2; + } else { + bsize = fmt.pitch * caps.min_line_count; + } + + /* Alloc video buffers and enqueue for capture */ + if (caps.min_vbuf_count > CONFIG_VIDEO_BUFFER_POOL_NUM_MAX || + bsize > CONFIG_VIDEO_BUFFER_POOL_SZ_MAX) { + LOG_ERR("Not enough buffers or memory to start streaming"); + break; + } + + /* TODO: CONFIG_VIDEO_BUFFER_POOL_NUM_MAX is 1 now, consider to use multiple video buffers */ + for (i = 0; i < CONFIG_VIDEO_BUFFER_POOL_NUM_MAX; i++) { + /* + * For some hardwares, such as the PxP used on i.MX RT1170 to do image rotation, + * buffer alignment is needed in order to achieve the best performance + */ + vbuf = video_buffer_aligned_alloc(bsize, CONFIG_VIDEO_BUFFER_POOL_ALIGN, + K_FOREVER); + if (vbuf == NULL) { + LOG_ERR("Unable to alloc video buffer"); + break; + } + vbuf->type = type; + video_enqueue(uvc_host, vbuf); + } + + /* enable UVC device streaming */ + if (video_stream_start(uvc_host, type)) { + LOG_ERR("Unable to start capture (interface)"); + break; + } + + /* Delay to wait UVC device ready */ + k_msleep(500); + + LOG_INF("Capture started"); + break; + + case USBH_DEVICE_DISCONNECTED: + /* TODO: CONFIG_VIDEO_BUFFER_POOL_NUM_MAX is 1 now, consider multiple video buffers */ + for (i = 0; i < CONFIG_VIDEO_BUFFER_POOL_NUM_MAX; i++) { + err = video_dequeue(uvc_host, &vbuf, K_NO_WAIT); + if (!err && vbuf) { + video_buffer_release(vbuf); + } + } + LOG_INF("UVC device disconnected!"); + break; + + case VIDEO_BUF_DONE: + /* Process completed video buffer */ + err = video_dequeue(uvc_host, &vbuf, K_FOREVER); + if (err) { + LOG_ERR("Unable to dequeue video buf"); + break; + } + + LOG_DBG("Got frame %u! size: %u; timestamp %u ms", + frame++, vbuf->bytesused, vbuf->timestamp); #ifdef CONFIG_TEST - if (tp_set_ret < 0) { - LOG_DBG("Test pattern control was not successful. Skip test"); - } else if (is_colorbar_ok(vbuf->buffer, fmt)) { - LOG_DBG("Pattern OK!\n"); - } + if (tp_set_ret < 0) { + LOG_DBG("Test pattern control was not successful. Skip test"); + } else if (is_colorbar_ok(vbuf->buffer, fmt)) { + LOG_DBG("Pattern OK!\n"); + } #endif #if DT_HAS_CHOSEN(zephyr_display) - video_display_frame(display_dev, vbuf, fmt); + if (display_configured) { + video_display_frame(display_dev, vbuf, fmt); + } #endif - - err = video_enqueue(video_dev, vbuf); - if (err) { - LOG_ERR("Unable to requeue video buf"); - return 0; + err = video_enqueue(uvc_host, vbuf); + if (err) { + LOG_ERR("Unable to requeue video buf"); + break; + } + break; + + default: + LOG_WRN("Received unknown signal: %d", result); + break; + } } } } From dc968dd3455b07e9ac351dd562b36008cb7fb51d Mon Sep 17 00:00:00 2001 From: Aiden Hu Date: Wed, 6 Aug 2025 10:57:22 +0800 Subject: [PATCH 17/31] dts: arm: nxp: update nxp_rw6xx_common.dtsi for usb host. Add usbh node. Signed-off-by: Aiden Hu --- dts/arm/nxp/nxp_rw6xx_common.dtsi | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/dts/arm/nxp/nxp_rw6xx_common.dtsi b/dts/arm/nxp/nxp_rw6xx_common.dtsi index f9841f95e928e..53dfabf95a5dd 100644 --- a/dts/arm/nxp/nxp_rw6xx_common.dtsi +++ b/dts/arm/nxp/nxp_rw6xx_common.dtsi @@ -241,6 +241,15 @@ status = "disabled"; }; + usbh: usbh@145000 { + compatible = "nxp,uhc-ehci"; + reg = <0x145000 0x200>; + interrupts = <50 1>; + interrupt-names = "usb_otg"; + power-domains = <&power_mode3_domain>; + status = "disabled"; + }; + flexcomm0: flexcomm@106000 { compatible = "nxp,lpc-flexcomm"; reg = <0x106000 0x1000>; From b66ab3cc26fc025b223fb599e40e7e59575e3f32 Mon Sep 17 00:00:00 2001 From: Aiden Hu Date: Wed, 6 Aug 2025 09:34:39 +0800 Subject: [PATCH 18/31] soc: nxp: rw: update soc.c of rw for usb clock usb clock enablement for UHC NXP EHCI on rw. Signed-off-by: Aiden Hu --- soc/nxp/rw/soc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/soc/nxp/rw/soc.c b/soc/nxp/rw/soc.c index b21c7b0398c15..2486f42f00bd0 100644 --- a/soc/nxp/rw/soc.c +++ b/soc/nxp/rw/soc.c @@ -277,7 +277,8 @@ __weak __ramfunc void clock_init(void) #endif /* CONFIG_COUNTER_MCUX_CTIMER */ #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(usb_otg)) && \ - (CONFIG_USB_DC_NXP_EHCI || CONFIG_UDC_NXP_EHCI) + (CONFIG_USB_DC_NXP_EHCI || CONFIG_UDC_NXP_EHCI) || \ + (CONFIG_UHC_NXP_EHCI) /* Enable system xtal from Analog */ SYSCTL2->ANA_GRP_CTRL |= SYSCTL2_ANA_GRP_CTRL_PU_AG_MASK; /* reset USB */ From bce9f09fb7a52a6efe7c96184f2fffcb7743af66 Mon Sep 17 00:00:00 2001 From: Aiden Hu Date: Thu, 14 Aug 2025 14:30:29 +0800 Subject: [PATCH 19/31] drivers: usb: uhc: update usb host driver 1. Add interval for usb xfer. 2. Set right mps for during pipe init. 3. Set right direction for non-zero endpoint. 4. Check transferBuffer during init transfer. Signed-off-by: Aiden Hu --- drivers/usb/uhc/uhc_common.c | 3 +++ drivers/usb/uhc/uhc_mcux_common.c | 9 +++++++-- drivers/usb/uhc/uhc_mcux_ehci.c | 4 ++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/drivers/usb/uhc/uhc_common.c b/drivers/usb/uhc/uhc_common.c index 097d4a01457e0..5d97267a02064 100644 --- a/drivers/usb/uhc/uhc_common.c +++ b/drivers/usb/uhc/uhc_common.c @@ -101,6 +101,7 @@ struct uhc_transfer *uhc_xfer_alloc(const struct device *dev, const struct uhc_api *api = dev->api; struct uhc_transfer *xfer = NULL; uint16_t mps; + uint8_t interval; api->lock(dev); @@ -125,6 +126,7 @@ struct uhc_transfer *uhc_xfer_alloc(const struct device *dev, } mps = ep_desc->wMaxPacketSize; + interval = ep_desc->bInterval; } LOG_DBG("Allocate xfer, ep 0x%02x mps %u cb %p", ep, mps, cb); @@ -137,6 +139,7 @@ struct uhc_transfer *uhc_xfer_alloc(const struct device *dev, memset(xfer, 0, sizeof(struct uhc_transfer)); xfer->ep = ep; xfer->mps = mps; + xfer->interval = interval; xfer->udev = udev; xfer->cb = cb; xfer->priv = cb_priv; diff --git a/drivers/usb/uhc/uhc_mcux_common.c b/drivers/usb/uhc/uhc_mcux_common.c index 9fcf61c723cfe..d7bd1724b1068 100644 --- a/drivers/usb/uhc/uhc_mcux_common.c +++ b/drivers/usb/uhc/uhc_mcux_common.c @@ -280,7 +280,7 @@ usb_host_pipe_t *uhc_mcux_init_hal_ep(const struct device *dev, struct uhc_trans /* USB_HostHelperGetPeripheralInformation uses this value as first parameter */ pipe_init.devInstance = xfer->udev; pipe_init.nakCount = USB_HOST_CONFIG_MAX_NAK; - pipe_init.maxPacketSize = xfer->mps; + pipe_init.maxPacketSize = USB_MPS_EP_SIZE(xfer->mps); pipe_init.endpointAddress = USB_EP_GET_IDX(xfer->ep); pipe_init.direction = USB_EP_GET_IDX(xfer->ep) == 0 ? USB_OUT : USB_EP_GET_DIR(xfer->ep) ? USB_IN : USB_OUT; @@ -288,12 +288,13 @@ usb_host_pipe_t *uhc_mcux_init_hal_ep(const struct device *dev, struct uhc_trans * 'number per uframe' and the endpoint type cannot be got yet. */ pipe_init.numberPerUframe = 0; /* TODO: need right way to implement it. */ + pipe_init.numberPerUframe = USB_MPS_ADDITIONAL_TRANSACTIONS(xfer->mps); pipe_init.interval = xfer->interval; /* TODO: need right way to implement it. */ if (pipe_init.endpointAddress == 0) { pipe_init.pipeType = USB_ENDPOINT_CONTROL; } else { - pipe_init.pipeType = USB_ENDPOINT_BULK; + pipe_init.pipeType = USB_ENDPOINT_ISOCHRONOUS; } status = priv->mcux_if->controllerOpenPipe(priv->mcux_host.controllerHandle, @@ -353,6 +354,10 @@ int uhc_mcux_hal_init_transfer_common(const struct device *dev, usb_host_transfe ? USB_IN : USB_OUT; } + else + { + mcux_xfer->direction = USB_EP_DIR_IS_IN(xfer->ep) ? USB_IN : USB_OUT; + } return 0; } diff --git a/drivers/usb/uhc/uhc_mcux_ehci.c b/drivers/usb/uhc/uhc_mcux_ehci.c index 1a8021633eb94..615cecde4421d 100644 --- a/drivers/usb/uhc/uhc_mcux_ehci.c +++ b/drivers/usb/uhc/uhc_mcux_ehci.c @@ -202,6 +202,10 @@ static usb_host_transfer_t *uhc_mcux_hal_init_transfer(const struct device *dev, memcpy(mcux_xfer->setupPacket, xfer->setup_pkt, 8u); if (xfer->buf != NULL) { mcux_xfer->transferBuffer = uhc_mcux_nocache_alloc(mcux_xfer->transferLength); + if (mcux_xfer->transferBuffer == NULL) + { + LOG_ERR("Failed to allocate non-cached transfer buffer"); + } } #endif From 5d560bc0b0b25f58164765869d951beba86c1ecb Mon Sep 17 00:00:00 2001 From: Aiden Hu Date: Tue, 19 Aug 2025 12:24:29 +0800 Subject: [PATCH 20/31] subsys: usb: host: correct parameters for the class functions Use right parameters for usbh_class_connected and usbh_class_removed. Signed-off-by: Aiden Hu --- include/zephyr/usb/usbh.h | 4 ++-- subsys/usb/host/usbh_class_api.h | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/zephyr/usb/usbh.h b/include/zephyr/usb/usbh.h index c52cfaef1427e..b1538e7b34bbc 100644 --- a/include/zephyr/usb/usbh.h +++ b/include/zephyr/usb/usbh.h @@ -116,11 +116,11 @@ struct usbh_class_api { int (*request)(struct usbh_class_data *cdata, struct uhc_transfer *const xfer, int err); /** Device connected handler */ - int (*connected)(struct usbh_class_data *cdata, + int (*connected)(struct usb_device *udev, struct usbh_class_data *cdata, void *desc_start_addr, void *desc_end_addr); /** Device removed handler */ - int (*removed)(struct usbh_class_data *cdata); + int (*removed)(struct usb_device *udev, struct usbh_class_data *cdata); /** Bus remote wakeup handler */ int (*rwup)(struct usbh_class_data *cdata); /** Bus suspended handler */ diff --git a/subsys/usb/host/usbh_class_api.h b/subsys/usb/host/usbh_class_api.h index d793b6fb78631..a50473eec03aa 100644 --- a/subsys/usb/host/usbh_class_api.h +++ b/subsys/usb/host/usbh_class_api.h @@ -73,14 +73,14 @@ static inline int usbh_class_request(struct usbh_class_data *const c_data, * * @return 0 on success, negative error code on failure. */ -static inline int usbh_class_connected(struct usbh_class_data *const c_data, +static inline int usbh_class_connected(struct usb_device *udev, struct usbh_class_data *const c_data, void *const desc_start_addr, void *const desc_end_addr) { const struct usbh_class_api *api = c_data->api; if (api->connected != NULL) { - return api->connected(c_data, desc_start_addr, desc_end_addr); + return api->connected(udev, c_data, desc_start_addr, desc_end_addr); } return -ENOTSUP; @@ -96,12 +96,12 @@ static inline int usbh_class_connected(struct usbh_class_data *const c_data, * * @return 0 on success, negative error code on failure. */ -static inline int usbh_class_removed(struct usbh_class_data *const c_data) +static inline int usbh_class_removed(struct usb_device *udev, struct usbh_class_data *const c_data) { const struct usbh_class_api *api = c_data->api; if (api->removed != NULL) { - return api->removed(c_data); + return api->removed(udev, c_data); } return -ENOTSUP; From eea52070dd715c22089f3522f756dcc8356967fc Mon Sep 17 00:00:00 2001 From: Aiden Hu Date: Tue, 19 Aug 2025 17:23:29 +0800 Subject: [PATCH 21/31] subsys: usb: host: fix class match issue and add remove handling Incorrect start address is used for usbh_class_connected for each supported independent class. Implement dev_removed_handler to properly notify class disconnection event. Signed-off-by: Aiden Hu --- subsys/usb/host/usbh_core.c | 61 +++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 20 deletions(-) diff --git a/subsys/usb/host/usbh_core.c b/subsys/usb/host/usbh_core.c index 8a7451b98688b..12c42590a3600 100644 --- a/subsys/usb/host/usbh_core.c +++ b/subsys/usb/host/usbh_core.c @@ -90,7 +90,7 @@ static int usbh_match_classes(struct usbh_context *const ctx, /* Step 1: Find first IAD or interface descriptor from start_addr */ mask = BIT(USB_DESC_INTERFACE) | BIT(USB_DESC_INTERFACE_ASSOC); - desc = usbh_desc_get_by_type(start_addr, end_addr, mask); + desc = usbh_desc_get_by_type(start_addr, desc_buf_end, mask); if (desc == NULL) { LOG_ERR("No IAD or interface descriptor found - error condition"); break; @@ -99,6 +99,7 @@ static int usbh_match_classes(struct usbh_context *const ctx, if (desc->bDescriptorType == USB_DESC_INTERFACE_ASSOC) { found_iad = true; + iad_desc = desc; } if (desc->bDescriptorType == USB_DESC_INTERFACE) { @@ -111,11 +112,11 @@ static int usbh_match_classes(struct usbh_context *const ctx, } /* Step 2: Continue searching for subsequent descriptors to determine end_addr */ - start_addr += ((struct usb_desc_header *)start_addr)->bLength; + uint8_t *search_start = start_addr + ((struct usb_desc_header *)start_addr)->bLength; /* Find next IAD */ mask = BIT(USB_DESC_INTERFACE_ASSOC); - desc = usbh_desc_get_by_type(start_addr, end_addr, mask); + desc = usbh_desc_get_by_type(search_start, desc_buf_end, mask); next_iad_addr = (uint8_t *)desc; /* Handle different cases and determine end_addr and device_info. */ @@ -130,11 +131,10 @@ static int usbh_match_classes(struct usbh_context *const ctx, } else if (found_iad && next_iad_addr) { /* Case 2c: Found IAD in step 1, found new IAD in subsequent descriptors */ end_addr = next_iad_addr; - start_addr += iad_desc->bLength; /* Get class code from first interface after IAD */ - mask = BIT(USB_DESC_INTERFACE_ASSOC); - desc = usbh_desc_get_by_type(start_addr, end_addr, mask); + mask = BIT(USB_DESC_INTERFACE); + desc = usbh_desc_get_by_type(search_start, end_addr, mask); if (desc != NULL) { struct usb_if_descriptor *if_desc = (void *)desc; @@ -145,28 +145,33 @@ static int usbh_match_classes(struct usbh_context *const ctx, } else if (found_iad && !next_iad_addr) { /* Case 2d: Found IAD in step 1, no new IAD in subsequent descriptors */ /* Get class code from first interface after IAD */ - start_addr += iad_desc->bLength; mask = BIT(USB_DESC_INTERFACE); - desc = usbh_desc_get_by_type(start_addr, end_addr, mask); + desc = usbh_desc_get_by_type(search_start, desc_buf_end, mask); if (desc != NULL) { - struct usb_if_descriptor *if_desc = (void *)start_addr; + struct usb_if_descriptor *if_desc = (void *)desc; device_info.code_triple.dclass = if_desc->bInterfaceClass; device_info.code_triple.sub = if_desc->bInterfaceSubClass; device_info.code_triple.proto = if_desc->bInterfaceProtocol; - } - - /* Search for interface descriptor with different class code after IAD */ - start_addr += iad_desc->bLength; - mask = BIT(USB_DESC_INTERFACE); - desc = usbh_desc_get_by_type(start_addr, desc_buf_end, mask); - if (desc != NULL) { - struct usb_if_descriptor *if_desc = (void *)desc; - /* Only compare class code */ - if (if_desc->bInterfaceClass != device_info.code_triple.dclass) { + /* Search for interface descriptor with different class code after IAD */ + uint8_t *next_search = (uint8_t *)desc + desc->bLength; + mask = BIT(USB_DESC_INTERFACE); + desc = usbh_desc_get_by_type(next_search, desc_buf_end, mask); + if (desc != NULL) { + struct usb_if_descriptor *if_desc = (void *)desc; + + /* Only compare class code */ + if (if_desc->bInterfaceClass != device_info.code_triple.dclass) { + end_addr = (uint8_t *)desc; + } else { + end_addr = desc_buf_end; + } + } else { end_addr = desc_buf_end; } + } else { + end_addr = desc_buf_end; } } @@ -185,7 +190,7 @@ static int usbh_match_classes(struct usbh_context *const ctx, cdata->name, device_info.code_triple.dclass); /* Step 4: Call connected handler */ - int ret = usbh_class_connected(cdata, start_addr, end_addr); + int ret = usbh_class_connected(udev, cdata, start_addr, end_addr); if (ret == 0) { LOG_INF("Class driver %s successfully claimed device", cdata->name); matched = true; @@ -256,6 +261,22 @@ static void dev_connected_handler(struct usbh_context *const ctx, static void dev_removed_handler(struct usbh_context *const ctx) { if (ctx->root != NULL) { + LOG_DBG("Device removed - notifying class drivers"); + + /* Notify all relevant class drivers that device is disconnected */ + struct usbh_class_data *cdata; + SYS_SLIST_FOR_EACH_CONTAINER(&ctx->class_list, cdata, node) { + LOG_DBG("Calling disconnected for class driver: %s", cdata->name); + int ret = usbh_class_removed(ctx->root, cdata); + if (ret != 0) { + LOG_WRN("Class driver %s disconnected callback failed: %d", + cdata->name, ret); + } else { + LOG_INF("Class driver %s successfully disconnected", cdata->name); + } + } + + /* Free device resources */ usbh_device_free(ctx->root); ctx->root = NULL; LOG_DBG("Device removed"); From 1362e88cad381e64f02e2e8c98548a6113756a45 Mon Sep 17 00:00:00 2001 From: Aiden Hu Date: Tue, 19 Aug 2025 17:32:06 +0800 Subject: [PATCH 22/31] subsys: usb: host: fix descriptor parsing by updating curr_addr Move the pointer to the next descriptor to prevent incorrect address usage. Signed-off-by: Aiden Hu --- subsys/usb/host/usbh_desc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/subsys/usb/host/usbh_desc.c b/subsys/usb/host/usbh_desc.c index e73e12f8dc29a..c03cb0714d891 100644 --- a/subsys/usb/host/usbh_desc.c +++ b/subsys/usb/host/usbh_desc.c @@ -23,6 +23,7 @@ struct usb_desc_header *usbh_desc_get_by_type(const uint8_t *const start_addr, if ((BIT(desc->bDescriptorType) & type_mask) != 0) { return desc; } + curr_addr += desc->bLength; } return NULL; From 96b6becf9cfec492bcbc28654a49927bdb40b317 Mon Sep 17 00:00:00 2001 From: Aiden Hu Date: Tue, 19 Aug 2025 17:34:54 +0800 Subject: [PATCH 23/31] modules: hal_nxp: mcux: update macros for usb host EHCI Change the default value for QH/QTD/ITD/SITD of usb host ehci. Signed-off-by: Aiden Hu --- .../hal_nxp/mcux/mcux-sdk-ng/middleware/usb_host_config.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/hal_nxp/mcux/mcux-sdk-ng/middleware/usb_host_config.h b/modules/hal_nxp/mcux/mcux-sdk-ng/middleware/usb_host_config.h index bf5f058abebd5..16a5f19beeef1 100644 --- a/modules/hal_nxp/mcux/mcux-sdk-ng/middleware/usb_host_config.h +++ b/modules/hal_nxp/mcux/mcux-sdk-ng/middleware/usb_host_config.h @@ -16,10 +16,10 @@ #ifdef CONFIG_UHC_NXP_EHCI #define USB_HOST_CONFIG_EHCI (2U) #define USB_HOST_CONFIG_EHCI_FRAME_LIST_SIZE (1024U) -#define USB_HOST_CONFIG_EHCI_MAX_QH (8U) -#define USB_HOST_CONFIG_EHCI_MAX_QTD (8U) -#define USB_HOST_CONFIG_EHCI_MAX_ITD (0U) -#define USB_HOST_CONFIG_EHCI_MAX_SITD (0U) +#define USB_HOST_CONFIG_EHCI_MAX_QH (16U) +#define USB_HOST_CONFIG_EHCI_MAX_QTD (16U) +#define USB_HOST_CONFIG_EHCI_MAX_ITD (16U) +#define USB_HOST_CONFIG_EHCI_MAX_SITD (16U) #else #define USB_HOST_CONFIG_EHCI (0U) #endif /* CONFIG_USB_DC_NXP_EHCI */ From 49b6370a028d09c1c21a4c676d0128b5a8c3f092 Mon Sep 17 00:00:00 2001 From: Aiden Hu Date: Tue, 19 Aug 2025 17:49:34 +0800 Subject: [PATCH 24/31] usb: host: class: improve host video class. 1. Delete uvc_guid_map and related functions, use the database and helper functions from usb_common_uvc.c 2. Rename uvc_host_parse_frame_intervals as uvc_host_parse_frame_default_intervals to select default frame interval. 3. Correct uvc_host_select_streaming_alternate to do right alternate selection. 4. Implement uvc_host_removed function. 5. Enhance the video_usb_uvc_host_set_stream function. Signed-off-by: Aiden Hu --- subsys/usb/host/class/usbh_uvc.c | 623 +++++++++++++++++-------------- 1 file changed, 339 insertions(+), 284 deletions(-) diff --git a/subsys/usb/host/class/usbh_uvc.c b/subsys/usb/host/class/usbh_uvc.c index 4a78c71c09c05..e8ae28edb4e27 100644 --- a/subsys/usb/host/class/usbh_uvc.c +++ b/subsys/usb/host/class/usbh_uvc.c @@ -59,47 +59,6 @@ static const struct usbh_device_code_table uvc_device_code[] = { } }; -/** - * @brief UVC GUID to pixel format mapping table - * - * Maps UVC format GUIDs to Zephyr video pixel formats. - * Each entry contains the 16-byte GUID, corresponding pixel format, - * and human-readable format name. - */ -static const struct { - /** UVC format GUID */ - uint8_t guid[16]; - /** Zephyr pixel format (\ref video_pixel_formats) */ - uint32_t pixelformat; - /** Format name string */ - const char *name; -} uvc_guid_map[] = { - /** YUY2 format GUID */ - {{0x59, 0x55, 0x59, 0x32, 0x00, 0x00, 0x10, 0x00, - 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71}, - VIDEO_PIX_FMT_YUYV, "YUYV"}, - - /** Y800 grayscale format GUID */ - {{0x59, 0x38, 0x30, 0x30, 0x00, 0x00, 0x10, 0x00, - 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71}, - VIDEO_PIX_FMT_GREY, "GREY"}, - - /** RGBP format GUID */ - {{0x52, 0x47, 0x42, 0x50, 0x00, 0x00, 0x10, 0x00, - 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71}, - VIDEO_PIX_FMT_RGB565, "RGB565"}, - - /** UYVY format GUID (unsupported) */ - {{0x55, 0x59, 0x56, 0x59, 0x00, 0x00, 0x10, 0x00, - 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71}, - 0, "UYVY (unsupported)"}, - - /** NV12 format GUID (unsupported) */ - {{0x4E, 0x56, 0x31, 0x32, 0x00, 0x00, 0x10, 0x00, - 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71}, - 0, "NV12 (unsupported)"}, -}; - /** * @brief USB UVC camera control parameters structure * @@ -162,6 +121,8 @@ struct uvc_device { struct k_fifo fifo_out; /** Device connection status */ bool connected; + /** Device streaming status */ + bool streaming; /** Signal to alert video devices of buffer-related events */ struct k_poll_signal *sig; /** Byte offset within the currently transmitted video buffer */ @@ -208,57 +169,7 @@ struct uvc_device { struct uvc_probe_commit video_probe; }; -static void uvc_host_flush_queue(struct uvc_device *uvc_dev, struct video_buffer *vbuf); - -/** - * @brief Convert pixel format to UVC GUID - * - * Converts Zephyr video pixel format to corresponding UVC GUID. - * - * @param pixelformat Zephyr pixel format value - * @param guid Output buffer for 16-byte GUID - * @return 0 on success, negative error code if format not supported - */ -int uvc_host_pixelformat_to_guid(uint32_t pixelformat, uint8_t *guid) -{ - if (!guid) { - return -EINVAL; - } - - for (int i = 0; i < ARRAY_SIZE(uvc_guid_map); i++) { - if (uvc_guid_map[i].pixelformat == pixelformat) { - memcpy(guid, uvc_guid_map[i].guid, 16); - return 0; - } - } - - return -ENOTSUP; -} - -/** - * @brief Convert UVC format GUID to Zephyr pixel format - * - * This function searches the UVC GUID mapping table to find the corresponding - * Zephyr video pixel format for a given UVC format GUID. - * - * @param guid Pointer to 16-byte UVC format GUID array - * @return Zephyr pixel format constant (VIDEO_PIX_FMT_*) on success, - * 0 if GUID is not found or unsupported - */ -uint32_t uvc_host_guid_to_pixelformat(const uint8_t *guid) -{ - if (!guid) { - return 0; - } - - for (int i = 0; i < ARRAY_SIZE(uvc_guid_map); i++) { - if (memcmp(guid, uvc_guid_map[i].guid, 16) == 0) { - return uvc_guid_map[i].pixelformat; - } - } - - return 0; -} +static int uvc_host_flush_queue(struct uvc_device *uvc_dev, struct video_buffer *vbuf); /** * @brief Select default video format for UVC device @@ -285,7 +196,7 @@ static int uvc_host_select_default_format(struct uvc_device *uvc_dev) struct uvc_vs_format_uncompressed *format = uncompressed_info->uncompressed_format[0]; /* Get pixel format from GUID */ - uint32_t pixelformat = uvc_host_guid_to_pixelformat(format->guidFormat); + uint32_t pixelformat = uvc_guid_to_fourcc(format->guidFormat); if (pixelformat == 0) { LOG_WRN("First uncompressed format has unsupported GUID"); goto try_mjpeg; @@ -1317,29 +1228,62 @@ static int uvc_host_parse_descriptors(struct uvc_device *uvc_dev) } /** - * @brief Parse frame intervals from descriptor + * @brief Parse default frame interval from descriptor * - * Extracts frame interval information from frame descriptor and - * returns the maximum supported interval value. + * Extracts the default frame interval from frame descriptor. If default + * interval is invalid (0), falls back to maximum supported interval. * * @param desc_buf Pointer to frame descriptor buffer - * @return Maximum frame interval in 100ns units + * @param frame_subtype Frame descriptor subtype (UVC_VS_FRAME_UNCOMPRESSED or UVC_VS_FORMAT_MJPEG) + * @return Default or maximum frame interval in 100ns units */ -static uint32_t uvc_host_parse_frame_intervals(uint8_t *desc_buf) +static uint32_t uvc_host_parse_frame_default_intervals(uint8_t *desc_buf, uint8_t frame_subtype) { - uint32_t max_interval = 333333; /* Default 30fps */ - uint8_t interval_type = desc_buf[25]; /* bFrameIntervalType */ - uint8_t *interval_data = desc_buf + 26; - - if (interval_type == 0) { - /* Continuous/stepwise frame intervals: take maximum value */ - max_interval = sys_le32_to_cpu(*(uint32_t*)(interval_data + 4)); - } else if (interval_type > 0) { - /* Discrete frame intervals: take last (maximum) value */ - max_interval = sys_le32_to_cpu(*(uint32_t*)(interval_data + (interval_type - 1) * 4)); - } - - return max_interval; + uint32_t default_interval = 0; + uint8_t interval_type; + uint8_t *interval_data; + + if (frame_subtype == UVC_VS_FRAME_UNCOMPRESSED) { + struct uvc_vs_frame_uncompressed *frame_desc = + (struct uvc_vs_frame_uncompressed *)desc_buf; + default_interval = sys_le32_to_cpu(frame_desc->dwDefaultFrameInterval); + interval_type = frame_desc->bFrameIntervalType; + /* Interval data follows immediately after bFrameIntervalType field */ + interval_data = &(frame_desc->bFrameIntervalType) + 1; + } else if (frame_subtype == UVC_VS_FORMAT_MJPEG) { + struct uvc_vs_frame_mjpeg *frame_desc = + (struct uvc_vs_frame_mjpeg *)desc_buf; + default_interval = sys_le32_to_cpu(frame_desc->dwDefaultFrameInterval); + interval_type = frame_desc->bFrameIntervalType; + /* Interval data follows immediately after bFrameIntervalType field */ + interval_data = &(frame_desc->bFrameIntervalType) + 1; + } else { + /* Unsupported frame subtype, use hardcoded fallback */ + return 333333; /* 30fps */ + } + + /* If default interval is valid, use it */ + if (default_interval != 0) { + return default_interval; + } + + /* Default interval is invalid, find maximum supported interval */ + uint32_t max_interval = 333333; /* Fallback to 30fps */ + + if (interval_type == 0) { + /* Continuous/stepwise intervals: dwMin, dwMax, dwStep */ + if (interval_data + 8 <= desc_buf + desc_buf[0]) { /* Bounds check */ + max_interval = sys_le32_to_cpu(*(uint32_t*)(interval_data + 4)); + } + } else if (interval_type > 0) { + /* Discrete intervals: take the last (typically maximum) value */ + uint32_t last_interval_offset = (interval_type - 1) * 4; + if (interval_data + last_interval_offset + 4 <= desc_buf + desc_buf[0]) { /* Bounds check */ + max_interval = sys_le32_to_cpu(*(uint32_t*)(interval_data + last_interval_offset)); + } + } + + return max_interval; } /** @@ -1385,7 +1329,7 @@ static int uvc_host_find_frame_in_format(struct uvc_format_header *format_header *found_frame = frame_header; /* Parse frame interval from descriptor */ *found_interval = (frame_header->bLength >= 26) ? - uvc_host_parse_frame_intervals(desc_buf) : 333333; + uvc_host_parse_frame_default_intervals(desc_buf, expected_frame_subtype) : 333333; return 0; } frames_found++; @@ -1441,7 +1385,7 @@ static int uvc_host_find_format(struct uvc_device *uvc_dev, if (!format_desc) continue; /* Convert GUID to pixel format for comparison */ - uint32_t desc_pixelformat = uvc_host_guid_to_pixelformat(format_desc->guidFormat); + uint32_t desc_pixelformat = uvc_guid_to_fourcc(format_desc->guidFormat); if (desc_pixelformat == pixelformat) { LOG_DBG("Found matching uncompressed format: index=%u", format_desc->bFormatIndex); @@ -1486,7 +1430,6 @@ static int uvc_host_find_format(struct uvc_device *uvc_dev, return -ENOTSUP; } -#if 0 /** * @brief Select streaming alternate setting based on bandwidth * @@ -1499,14 +1442,16 @@ static int uvc_host_find_format(struct uvc_device *uvc_dev, */ static int uvc_host_select_streaming_alternate(struct uvc_device *uvc_dev, uint32_t required_bandwidth) { - struct usb_if_descriptor *best_interface = NULL; - struct usb_ep_descriptor *best_endpoint = NULL; - uint32_t min_suitable_bandwidth = UINT32_MAX; - uint32_t best_ep_mps_mult = 0; + struct usb_if_descriptor *selected_interface = NULL; + struct usb_ep_descriptor *selected_endpoint = NULL; + uint32_t optimal_bandwidth = UINT32_MAX; + uint32_t selected_payload_size = 0; bool found_suitable = false; enum usbh_speed device_speed = uvc_dev->udev->speed; - - LOG_DBG("Required bandwidth: %u bytes/sec (device speed: %s)", required_bandwidth, + uint32_t max_payload_transfer_size = sys_le32_to_cpu(uvc_dev->video_probe.dwMaxPayloadTransferSize); + + LOG_DBG("Required bandwidth: %u bytes/sec, Max payload: %u bytes (device speed: %s)", + required_bandwidth, max_payload_transfer_size, (device_speed == USB_SPEED_SPEED_HS) ? "High Speed" : "Full Speed"); /* Iterate through all alternate setting interfaces */ @@ -1534,156 +1479,71 @@ static int uvc_host_select_streaming_alternate(struct uvc_device *uvc_dev, uint3 uint16_t max_packet_size = sys_le16_to_cpu(ep_desc->wMaxPacketSize) & 0x07FF; uint32_t ep_bandwidth; - uint32_t current_ep_mps_mult; + uint32_t ep_payload_size; /* Calculate endpoint bandwidth based on USB speed */ if (device_speed == USB_SPEED_SPEED_HS) { uint8_t mult = ((sys_le16_to_cpu(ep_desc->wMaxPacketSize) >> 11) & 0x03) + 1; uint32_t interval_uframes = 1 << (ep_desc->bInterval - 1); - current_ep_mps_mult = max_packet_size * mult; - ep_bandwidth = (current_ep_mps_mult * 8000) / interval_uframes; + ep_payload_size = max_packet_size * mult; + ep_bandwidth = (ep_payload_size * 8000) / interval_uframes; } else { - current_ep_mps_mult = max_packet_size; + ep_payload_size = max_packet_size; ep_bandwidth = (max_packet_size * 1000) / ep_desc->bInterval; } - LOG_DBG(" Interface %u Alt %u EP[%d]: addr=0x%02x, maxpkt=%u, mps_mult=%u, bandwidth=%u", + LOG_DBG(" Interface %u Alt %u EP[%d]: addr=0x%02x, maxpkt=%u, payload=%u, bandwidth=%u", if_desc->bInterfaceNumber, if_desc->bAlternateSetting, ep, - ep_desc->bEndpointAddress, max_packet_size, current_ep_mps_mult, ep_bandwidth); - - /* Check if endpoint satisfies bandwidth requirement and is optimal */ - if (ep_bandwidth >= required_bandwidth && ep_bandwidth < min_suitable_bandwidth) { - min_suitable_bandwidth = ep_bandwidth; - best_interface = if_desc; - best_endpoint = ep_desc; - best_ep_mps_mult = current_ep_mps_mult; + ep_desc->bEndpointAddress, max_packet_size, ep_payload_size, ep_bandwidth); + + /* Check if endpoint satisfies requirements and is optimal */ + if (ep_bandwidth >= required_bandwidth && + ep_payload_size >= max_payload_transfer_size && + ep_bandwidth < optimal_bandwidth) { + + optimal_bandwidth = ep_bandwidth; + selected_interface = if_desc; + selected_endpoint = ep_desc; + selected_payload_size = ep_payload_size; found_suitable = true; - LOG_DBG("Found better endpoint: interface %u alt %u EP 0x%02x, bandwidth=%u, mps_mult=%u", + LOG_DBG("Selected optimal endpoint: interface %u alt %u EP 0x%02x, bandwidth=%u, payload=%u", if_desc->bInterfaceNumber, if_desc->bAlternateSetting, - ep_desc->bEndpointAddress, ep_bandwidth, current_ep_mps_mult); + ep_desc->bEndpointAddress, ep_bandwidth, ep_payload_size); + } else { + if (ep_bandwidth < required_bandwidth) { + LOG_DBG(" Endpoint rejected: insufficient bandwidth (%u < %u)", + ep_bandwidth, required_bandwidth); + } + if (ep_payload_size < max_payload_transfer_size) { + LOG_DBG(" Endpoint rejected: insufficient payload size (%u < %u)", + ep_payload_size, max_payload_transfer_size); + } } } ep_buf += ep_desc->bLength; } } - + if (!found_suitable) { - LOG_ERR("No endpoint can satisfy bandwidth requirement %u", required_bandwidth); + LOG_ERR("No endpoint satisfies bandwidth requirement %u and payload size %u", + required_bandwidth, max_payload_transfer_size); return -ENOTSUP; } /* Update current streaming interface and endpoint */ - uvc_dev->current_stream_iface_info.current_stream_iface = best_interface; - uvc_dev->current_stream_iface_info.current_stream_ep = best_endpoint; - uvc_dev->current_stream_iface_info.cur_ep_mps_mult = best_ep_mps_mult; + uvc_dev->current_stream_iface_info.current_stream_iface = selected_interface; + uvc_dev->current_stream_iface_info.current_stream_ep = selected_endpoint; + uvc_dev->current_stream_iface_info.cur_ep_mps_mult = selected_payload_size; - LOG_INF("Selected interface %u alternate %u endpoint 0x%02x with bandwidth %u, mps_mult=%u for requirement %u", - best_interface->bInterfaceNumber, best_interface->bAlternateSetting, - best_endpoint->bEndpointAddress, min_suitable_bandwidth, best_ep_mps_mult, required_bandwidth); + LOG_INF("Selected interface %u alternate %u endpoint 0x%02x (bandwidth=%u, payload=%u)", + selected_interface->bInterfaceNumber, selected_interface->bAlternateSetting, + selected_endpoint->bEndpointAddress, optimal_bandwidth, selected_payload_size); return 0; } -#endif - - -/* TODO: this function forces to use interface 1 and alternate setting 11 to work with one Logith Camera. -* In the future, the above commented implementation should be used for generic device support -*/ -/** - * @brief Select streaming alternate setting 6 on interface 1 - * - * Directly selects interface 1 alternate setting 6 and finds its ISO IN endpoint. - * - * @param uvc_dev Pointer to UVC device structure - * @param required_bandwidth Required bandwidth (unused in this simplified version) - * @return 0 on success, negative error code if interface/endpoint not found - */ -static int uvc_host_select_streaming_alternate(struct uvc_device *uvc_dev, uint32_t required_bandwidth) -{ - struct usb_if_descriptor *target_interface = NULL; - struct usb_ep_descriptor *target_endpoint = NULL; - uint32_t ep_mps_mult = 0; - enum usbh_speed device_speed = uvc_dev->udev->speed; - - ARG_UNUSED(required_bandwidth); /* Not used in direct selection */ - - LOG_INF("Directly selecting interface 1 alternate setting 6 (device speed: %s)", - (device_speed == USB_SPEED_SPEED_HS) ? "High Speed" : "Full Speed"); - - /* Search for interface 1 alternate setting 6 */ - for (int i = 0; i < UVC_STREAM_INTERFACES_MAX_ALT && uvc_dev->stream_ifaces[i]; i++) { - struct usb_if_descriptor *if_desc = uvc_dev->stream_ifaces[i]; - - /* Check if this is interface 1 alternate setting 6 */ - if (if_desc->bInterfaceNumber == 1 && if_desc->bAlternateSetting == 11) { - target_interface = if_desc; - - LOG_INF("Found target interface %u alt %u (%u endpoints)", - if_desc->bInterfaceNumber, if_desc->bAlternateSetting, if_desc->bNumEndpoints); - - /* Find ISO IN endpoint in this interface */ - uint8_t *ep_buf = (uint8_t *)if_desc + if_desc->bLength; - - for (int ep = 0; ep < if_desc->bNumEndpoints; ep++) { - struct usb_ep_descriptor *ep_desc = (struct usb_ep_descriptor *)ep_buf; - - /* Check if this is ISO IN endpoint */ - if (ep_desc->bDescriptorType == USB_DESC_ENDPOINT && - (ep_desc->bmAttributes & USB_EP_TRANSFER_TYPE_MASK) == USB_EP_TYPE_ISO && - (ep_desc->bEndpointAddress & USB_EP_DIR_MASK) == USB_EP_DIR_IN) { - - uint16_t max_packet_size = sys_le16_to_cpu(ep_desc->wMaxPacketSize) & 0x07FF; - uint32_t ep_bandwidth; - - /* Calculate endpoint parameters based on USB speed */ - if (device_speed == USB_SPEED_SPEED_HS) { - uint8_t mult = ((sys_le16_to_cpu(ep_desc->wMaxPacketSize) >> 11) & 0x03) + 1; - uint32_t interval_uframes = 1 << (ep_desc->bInterval - 1); - ep_mps_mult = max_packet_size * mult; - ep_bandwidth = (ep_mps_mult * 8000) / interval_uframes; - } else { - ep_mps_mult = max_packet_size; - ep_bandwidth = (max_packet_size * 1000) / ep_desc->bInterval; - } - - target_endpoint = ep_desc; - - LOG_INF("Selected endpoint: addr=0x%02x, maxpkt=%u, mps_mult=%u, bandwidth=%u", - ep_desc->bEndpointAddress, max_packet_size, ep_mps_mult, ep_bandwidth); - break; - } - - ep_buf += ep_desc->bLength; - } - break; /* Found target interface, stop searching */ - } - } - - if (!target_interface) { - LOG_ERR("Interface 1 alternate setting 6 not found"); - return -ENOENT; - } - - if (!target_endpoint) { - LOG_ERR("No ISO IN endpoint found in interface 1 alternate setting 6"); - return -ENOENT; - } - - /* Update current streaming interface and endpoint */ - uvc_dev->current_stream_iface_info.current_stream_iface = target_interface; - uvc_dev->current_stream_iface_info.current_stream_ep = target_endpoint; - uvc_dev->current_stream_iface_info.cur_ep_mps_mult = ep_mps_mult; - - LOG_INF("Successfully selected interface 1 alternate setting 6 endpoint 0x%02x with mps_mult=%u", - target_endpoint->bEndpointAddress, ep_mps_mult); - - return 0; -} - - /** * @brief Calculate required bandwidth for current video format * @@ -1737,7 +1597,7 @@ static uint32_t uvc_host_calculate_required_bandwidth(struct uvc_device *uvc_dev } /* Add 10% margin to ensure stable transmission */ - bandwidth = (bandwidth * 110) / 100; + bandwidth = (bandwidth * 110 + 99) / 100; LOG_DBG("Calculated bandwidth: %u bytes/sec for %s %ux%u@%ufps", bandwidth, VIDEO_FOURCC_TO_STR(pixelformat), width, height, fps); @@ -2022,6 +1882,7 @@ static int uvc_host_set_frame_rate(struct uvc_device *uvc_dev, uint32_t fps) uint32_t target_frame_interval; uint32_t best_frame_interval; uint32_t min_diff = UINT32_MAX; + uint32_t required_bandwidth; bool found_exact_match = false; int ret; @@ -2175,6 +2036,37 @@ static int uvc_host_set_frame_rate(struct uvc_device *uvc_dev, uint32_t fps) LOG_INF("Frame rate successfully set to %u fps", uvc_dev->current_format.fps); + /* Calculate required bandwidth for streaming */ + required_bandwidth = uvc_host_calculate_required_bandwidth(uvc_dev); + if (required_bandwidth == 0) { + LOG_ERR("Cannot calculate required bandwidth"); + return -EINVAL; + } + + /* Select appropriate streaming interface alternate setting */ + ret = uvc_host_select_streaming_alternate(uvc_dev, required_bandwidth); + if (ret) { + LOG_ERR("Failed to select streaming alternate: %d", ret); + return ret; + } + + /* Configure streaming interface with selected alternate setting */ + ret = usbh_device_interface_set(uvc_dev->udev, + uvc_dev->current_stream_iface_info.current_stream_iface->bInterfaceNumber, + uvc_dev->current_stream_iface_info.current_stream_iface->bAlternateSetting, + false); + if (ret) { + LOG_ERR("Failed to set streaming interface %u alternate %u: %d", + uvc_dev->current_stream_iface_info.current_stream_iface->bInterfaceNumber, + uvc_dev->current_stream_iface_info.current_stream_iface->bAlternateSetting, + ret); + return ret; + } + + LOG_INF("Set streaming interface %u alternate %u successfully", + uvc_dev->current_stream_iface_info.current_stream_iface->bInterfaceNumber, + uvc_dev->current_stream_iface_info.current_stream_iface->bAlternateSetting); + return 0; } @@ -2237,7 +2129,7 @@ static struct video_format_cap* uvc_host_create_format_caps(struct uvc_device *u if (!format) continue; /* Get pixel format from GUID */ - uint32_t pixelformat = uvc_host_guid_to_pixelformat(format->guidFormat); + uint32_t pixelformat = uvc_guid_to_fourcc(format->guidFormat); if (pixelformat == 0) { LOG_WRN("Unsupported GUID format in format index %u", format->bFormatIndex); continue; @@ -2352,7 +2244,7 @@ static int uvc_host_get_device_caps(struct uvc_device *uvc_dev, struct video_cap } /* Set basic capabilities */ - caps->min_vbuf_count = 2; /* UVC typically needs 2 buffers */ + caps->min_vbuf_count = 1; /* UVC typically needs 1 buffers */ caps->min_line_count = LINE_COUNT_HEIGHT; /* Only support complete frames */ caps->max_line_count = LINE_COUNT_HEIGHT; /* Maximum one complete frame */ @@ -2453,7 +2345,7 @@ static int uvc_host_remove_payload_header(struct net_buf *buf, struct video_buff /* Copy payload data to video buffer if present */ if (payload_len > 0) { - memmove(vbuf->buffer + vbuf->bytesused, buf->data + header_len, payload_len); + memcpy(vbuf->buffer + vbuf->bytesused, buf->data + header_len, payload_len); } /* Return number of payload bytes processed */ @@ -2577,14 +2469,13 @@ static struct uhc_transfer *uvc_host_initiate_transfer(struct uvc_device *uvc_de } /* Allocate transfer buffer with maximum packet size */ - buf = net_buf_alloc_with_data(&uvc_host_pool, vbuf->buffer, uvc_dev->current_stream_iface_info.cur_ep_mps_mult, K_NO_WAIT); - buf->len = 0; - + buf = usbh_xfer_buf_alloc(&uvc_dev->udev, uvc_dev->current_stream_iface_info.cur_ep_mps_mult); if (!buf) { LOG_ERR("Failed to allocate buffer"); usbh_xfer_free(uvc_dev->udev, xfer); return NULL; - } + } + buf->len = 0; /* Reset buffer offset and associate video buffer with transfer */ uvc_dev->vbuf_offset = 0; @@ -2615,9 +2506,9 @@ static struct net_buf *uvc_host_continue_transfer(struct uvc_device *uvc_dev, struct net_buf *buf; /* Allocate buffer at offset position for continuation */ - buf = net_buf_alloc_with_data(&uvc_host_pool, vbuf->buffer + vbuf->bytesused, uvc_dev->current_stream_iface_info.cur_ep_mps_mult, K_NO_WAIT); + buf = usbh_xfer_buf_alloc(&uvc_dev->udev, uvc_dev->current_stream_iface_info.cur_ep_mps_mult); if (buf == NULL) { - LOG_DBG("Cannot allocate continuation USB buffer for now"); + LOG_ERR("Failed to allocate buffer"); return NULL; } buf->len = 0; @@ -2672,19 +2563,28 @@ static int uvc_host_flush_vbuf(struct uvc_device *uvc_dev, struct video_buffer * * Thread-safe processing of video buffers waiting for USB transfer. * * @param uvc_dev Pointer to uvc class + * @param vbuf Pointer to video buffer to flush + * @return 0 on success, negative error code on failure */ -static void uvc_host_flush_queue(struct uvc_device *uvc_dev, struct video_buffer *vbuf) +static int uvc_host_flush_queue(struct uvc_device *uvc_dev, struct video_buffer *vbuf) { - int ret; + int ret = 0; /* Lock to ensure atomic processing of buffer queue */ LOG_DBG("Locking the UVC stream"); k_mutex_lock(&uvc_dev->lock, K_FOREVER); - ret = uvc_host_flush_vbuf(uvc_dev, vbuf); + if (uvc_dev->streaming) { + ret = uvc_host_flush_vbuf(uvc_dev, vbuf); + if (ret) { + LOG_ERR("Failed to flush video buffer: %d", ret); + } + } LOG_DBG("Unlocking the UVC stream"); k_mutex_unlock(&uvc_dev->lock); + + return ret; } /** @@ -2795,8 +2695,9 @@ static int uvc_host_get_current_gain(struct uvc_device *uvc_dev, int32_t *gain_v /* Send GET_CUR control request to retrieve current gain */ ret = usbh_req_setup(uvc_dev->udev, - USB_REQTYPE_DIR_TO_HOST | USB_REQTYPE_TYPE_CLASS | - USB_REQTYPE_RECIPIENT_INTERFACE, + (USB_REQTYPE_DIR_TO_HOST << 7) | + (USB_REQTYPE_TYPE_CLASS << 5) | + (USB_REQTYPE_RECIPIENT_INTERFACE << 0), UVC_GET_CUR, (UVC_PU_GAIN_CONTROL << 8), uvc_dev->current_control_interface->bInterfaceNumber, @@ -2965,12 +2866,12 @@ static int uvc_host_control_unit_and_terminal_request(struct uvc_device *uvc_dev return ret; } -static int uvc_host_init(struct usbh_context *const uhs_ctx, struct usbh_class_data *cdata) +static int uvc_host_init(struct usbh_class_data *cdata) { const struct device *dev = cdata->priv; struct uvc_device *uvc_dev = (struct uvc_device *)dev->data; - if (!uhs_ctx || !cdata || !dev || !uvc_dev) { + if (!cdata || !dev || !uvc_dev) { LOG_ERR("Invalid parameters for UVC host init"); return -EINVAL; } @@ -3019,7 +2920,10 @@ static int uvc_host_init(struct usbh_context *const uhs_ctx, struct usbh_class_d /** Initialize format information */ memset(&uvc_dev->formats, 0, sizeof(struct uvc_vs_format_info)); - uvc_dev->video_format_caps = NULL; + if (uvc_dev->video_format_caps) { + k_free(uvc_dev->video_format_caps); + uvc_dev->video_format_caps = NULL; + } /** Initialize current format information */ memset(&uvc_dev->current_format, 0, sizeof(struct uvc_vs_format)); @@ -3036,9 +2940,11 @@ static int uvc_host_init(struct usbh_context *const uhs_ctx, struct usbh_class_d * * @param udev USB device * @param cdata USB host class data + * @param desc_start_addr Start of descriptor segment + * @param desc_end_addr End of descriptor segment * @return 0 on success, negative error code on failure */ -static int uvc_host_connected(struct usb_device *udev, void *desc_start_addr, void *desc_end_addr, struct usbh_class_data *cdata) +static int uvc_host_connected(struct usb_device *udev, struct usbh_class_data *cdata, void *desc_start_addr, void *desc_end_addr) { const struct device *dev = cdata->priv; struct uvc_device *uvc_dev = (struct uvc_device *)dev->data; @@ -3123,10 +3029,101 @@ static int uvc_host_connected(struct usb_device *udev, void *desc_start_addr, vo return ret; } -/** TODO */ -static int uvc_host_removed(struct usbh_context *const uhs_ctx, - struct usbh_class_data *c_data) +/** + * @brief Handle UVC device disconnection + * + * Called when a UVC device is disconnected. Stops streaming, + * cleans up resources, and resets device state. + * + * @param udev USB device + * @param cdata USB host class data + * @return 0 on success, negative error code on failure + */ +static int uvc_host_removed(struct usb_device *udev, struct usbh_class_data *cdata) { + const struct device *dev = cdata->priv; + struct uvc_device *uvc_dev = (struct uvc_device *)dev->data; + int ret = 0; + + if (!uvc_dev) { + LOG_ERR("No UVC device instance available"); + return -ENODEV; + } + + k_mutex_lock(&uvc_dev->lock, K_FOREVER); + + /* Check if device was actually connected */ + if (!uvc_dev->connected || uvc_dev->udev != udev) { + k_mutex_unlock(&uvc_dev->lock); + LOG_WRN("UVC device was not connected or different device"); + cdata->class_matched = 0; + return -ENODEV; + } + + /* Reset video buffer state */ + uvc_dev->vbuf_offset = 0; + uvc_dev->transfer_count = 0; + + /* Clean up USB camera controls */ + LOG_DBG("Cleaning up camera controls"); + memset(&uvc_dev->ctrls, 0, sizeof(uvc_dev->ctrls)); + + /* Clear streaming interface information */ + uvc_dev->streaming = false; + memset(uvc_dev->stream_ifaces, 0, sizeof(uvc_dev->stream_ifaces)); + uvc_dev->current_control_interface = NULL; + memset(&uvc_dev->current_stream_iface_info, 0, sizeof(uvc_dev->current_stream_iface_info)); + + /* Clear Video Control descriptors */ + LOG_DBG("Clearing Video Control descriptors"); + uvc_dev->vc_header = NULL; + uvc_dev->vc_itd = NULL; + uvc_dev->vc_otd = NULL; + uvc_dev->vc_ctd = NULL; + uvc_dev->vc_sud = NULL; + uvc_dev->vc_pud = NULL; + uvc_dev->vc_encoding_unit = NULL; + uvc_dev->vc_extension_unit = NULL; + + /* Clear Video Streaming descriptors */ + LOG_DBG("Clearing Video Streaming descriptors"); + uvc_dev->vs_input_header = NULL; + uvc_dev->vs_output_header = NULL; + + /* Clear format information */ + memset(&uvc_dev->formats, 0, sizeof(uvc_dev->formats)); + memset(&uvc_dev->current_format, 0, sizeof(uvc_dev->current_format)); + + /* Free video format capabilities if allocated */ + if (uvc_dev->video_format_caps) { + LOG_DBG("Freeing video format capabilities"); + k_free(uvc_dev->video_format_caps); + uvc_dev->video_format_caps = NULL; + } + + /* Clear probe/commit buffer */ + memset(&uvc_dev->video_probe, 0, sizeof(uvc_dev->video_probe)); + + /* Clear device association */ + uvc_dev->udev = NULL; + uvc_dev->desc_start = NULL; + uvc_dev->desc_end = NULL; + + /* Mark as disconnected */ + uvc_dev->connected = false; + + /* Reset class matched flag */ + cdata->class_matched = 0; + + /* Trigger device disconnection event signal */ +#ifdef CONFIG_POLL + if (uvc_dev->sig) { + k_poll_signal_raise(uvc_dev->sig, USBH_DEVICE_DISCONNECTED); + LOG_DBG("UVC device disconnected signal raised"); + } +#endif + + k_mutex_unlock(&uvc_dev->lock); return 0; } @@ -3310,7 +3307,6 @@ static int video_usb_uvc_host_get_frmival(const struct device *dev, struct video return 0; } - /** * @brief Enumerate supported frame intervals * @@ -3719,28 +3715,55 @@ static int video_usb_uvc_host_set_ctrl(const struct device *dev, struct video_co * the previously negotiated alternate setting. * * @param dev Pointer to video device - * @param enable Enbale or disable the video stream + * @param enable Enable or disable the video stream * @param type Buffer type (unused) * @return 0 on success, negative error code on failure */ static int video_usb_uvc_host_set_stream(const struct device *dev, bool enable, enum video_buf_type type) { - struct uvc_device *uvc_dev = dev->data; - uint8_t alt; - int ret; - - if (enable) { - alt = uvc_dev->current_stream_iface_info.current_stream_iface->bAlternateSetting; - } - else { - alt = 0; - } - /* Activate streaming interface with negotiated alternate setting */ - ret = usbh_device_interface_set(uvc_dev->udev, - uvc_dev->current_stream_iface_info.current_stream_iface->bInterfaceNumber, - alt, - false); - return ret; + struct uvc_device *uvc_dev; + uint8_t alt; + uint8_t interface_num; + int ret; + + if (!dev) { + return -EINVAL; + } + + uvc_dev = dev->data; + if (!uvc_dev || !uvc_dev->connected) { + return -ENODEV; + } + + if (enable) { + if (!uvc_dev->current_stream_iface_info.current_stream_iface) { + LOG_ERR("No streaming interface configured"); + return -EINVAL; + } + alt = uvc_dev->current_stream_iface_info.current_stream_iface->bAlternateSetting; + interface_num = uvc_dev->current_stream_iface_info.current_stream_iface->bInterfaceNumber; + } else { + /* For disable, we need a valid interface to set alt setting to 0 */ + if (!uvc_dev->current_stream_iface_info.current_stream_iface) { + LOG_WRN("No interface configured, cannot disable streaming"); + return -EINVAL; + } + alt = 0; + interface_num = uvc_dev->current_stream_iface_info.current_stream_iface->bInterfaceNumber; + } + + /* Activate streaming interface with negotiated alternate setting */ + ret = usbh_device_interface_set(uvc_dev->udev, interface_num, alt, false); + if (ret) { + LOG_ERR("Failed to set interface %d alt setting %d: %d", interface_num, alt, ret); + return ret; + } + + /* Update streaming state only after successful USB operation */ + uvc_dev->streaming = enable; + + LOG_DBG("UVC streaming %s successfully", enable ? "enabled" : "disabled"); + return 0; } /** @@ -3754,7 +3777,17 @@ static int video_usb_uvc_host_set_stream(const struct device *dev, bool enable, */ static int video_usb_uvc_host_enqueue(const struct device *dev, struct video_buffer *vbuf) { - struct uvc_device *uvc_dev = (struct uvc_device *)dev->data; + struct uvc_device *uvc_dev; + int ret; + + if (!dev) { + return -EINVAL; + } + + uvc_dev = dev->data; + if (!uvc_dev || !uvc_dev->connected) { + return -ENODEV; + } /* Initialize buffer state for new capture */ vbuf->bytesused = 0; @@ -3762,7 +3795,10 @@ static int video_usb_uvc_host_enqueue(const struct device *dev, struct video_buf vbuf->line_offset = 0; k_fifo_put(&uvc_dev->fifo_in, vbuf); - uvc_host_flush_queue(uvc_dev, vbuf); + ret = uvc_host_flush_queue(uvc_dev, vbuf); + if (ret) { + return ret; + } } /** @@ -3778,13 +3814,32 @@ static int video_usb_uvc_host_enqueue(const struct device *dev, struct video_buf static int video_usb_uvc_host_dequeue(const struct device *dev, struct video_buffer **vbuf, k_timeout_t timeout) { - struct uvc_device *uvc_dev = dev->data; + struct uvc_device *uvc_dev; + struct uhc_transfer *xfer; + int ret; + + if (!dev) { + return -EINVAL; + } + + uvc_dev = dev->data; + if (!uvc_dev) { + return -ENODEV; + } *vbuf = k_fifo_get(&uvc_dev->fifo_out, timeout); if (*vbuf == NULL) { return -EAGAIN; } + if (!uvc_dev->connected) { + xfer = (*vbuf)->driver_data; + ret = usbh_xfer_dequeue(uvc_dev->udev, xfer); + if (ret != 0) { + return ret; + } + } + return 0; } From 91fa60622daeeef6c71b3b385a731ce181d5b437 Mon Sep 17 00:00:00 2001 From: Aiden Hu Date: Wed, 20 Aug 2025 19:55:02 +0800 Subject: [PATCH 25/31] usb: host: class: use common usb uvc header for host uvc class 1. Use common usb_uvc.h header for usbh_uvc.c 2. Delete common definitions from usbh_uvc.h 3. Clean up and optimize usbh_uvc.c 4. Add necessary symbols for usb host uvc class. Signed-off-by: Aiden Hu --- subsys/usb/host/class/Kconfig.uvc_host | 31 +- subsys/usb/host/class/usbh_uvc.c | 1151 ++++++++++-------------- subsys/usb/host/class/usbh_uvc.h | 564 +----------- 3 files changed, 524 insertions(+), 1222 deletions(-) diff --git a/subsys/usb/host/class/Kconfig.uvc_host b/subsys/usb/host/class/Kconfig.uvc_host index 7314f3351e4b9..6a540e54e2eed 100644 --- a/subsys/usb/host/class/Kconfig.uvc_host +++ b/subsys/usb/host/class/Kconfig.uvc_host @@ -20,9 +20,38 @@ config USBH_VIDEO_NUM_BUFS default 16 help Control the number of buffer UVC can allocate in parallel. - The default is a compromise to allow enough concurrent buffers + The default is a compromise to allow enough concurrent buffers but not too much memory usage. +config USBH_VIDEO_MAX_FRMIVAL + int "Max number of video input 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 USBH_VIDEO_MAX_STREAM_INTERFACE + int "Max number of USB Video stream interface" + range 1 255 + default 32 + help + Max number of Video stream interface listed on descriptors. The default + value is selected arbitrarily to fit most situations without requiring + too much RAM. + +config USBH_VIDEO_MAX_FORMATS + int "Max number of format descriptors" + range 1 254 + default 8 + 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. TThe maximum count for each format type (e.g., MJPEG, uncompressed), not the + total number of all formats. + module = USBH_VIDEO module-str = usbh uvc default-count = 1 diff --git a/subsys/usb/host/class/usbh_uvc.c b/subsys/usb/host/class/usbh_uvc.c index e8ae28edb4e27..accc181aef80f 100644 --- a/subsys/usb/host/class/usbh_uvc.c +++ b/subsys/usb/host/class/usbh_uvc.c @@ -23,17 +23,18 @@ #include #include +#include + #include "usbh_device.h" #include "usbh_ch9.h" #include "usbh_uvc.h" +#include "../../common/usb_common_uvc.h" #include "../../../drivers/video/video_ctrls.h" #include "../../../drivers/video/video_device.h" LOG_MODULE_REGISTER(usbh_uvc, CONFIG_USBH_VIDEO_LOG_LEVEL); -NET_BUF_POOL_VAR_DEFINE(uvc_host_pool, CONFIG_USBH_VIDEO_NUM_BUFS, 0, 4, NULL); - /** * @brief UVC device code table for matching UVC devices * @@ -111,7 +112,7 @@ struct uvc_device { struct usb_device *udev; /** Start address of descriptors belonging to this uvc class */ void *desc_start; - /** End address of descriptors belonging to this uvc class */ + /** End address of descriptors belonging to this uvc class */ void *desc_end; /** Device access synchronization */ struct k_mutex lock; @@ -132,44 +133,44 @@ struct uvc_device { /** USB camera control parameters */ struct usb_camera_ctrls ctrls; /** Collection of all available alternate streaming interfaces */ - struct usb_if_descriptor *stream_ifaces[UVC_STREAM_INTERFACES_MAX_ALT]; + struct usb_if_descriptor *stream_ifaces[CONFIG_USBH_VIDEO_MAX_STREAM_INTERFACE]; /** Currently active VideoControl interface */ struct usb_if_descriptor *current_control_interface; /** Information about current streaming interface */ struct uvc_stream_iface_info current_stream_iface_info; - /** Video Control Header descriptor from device */ - struct uvc_vc_header_descriptor *vc_header; + /** Video Control Header descriptor from device */ + struct uvc_control_header_descriptor *vc_header; /** Video Control Input Terminal descriptor from device */ - struct uvc_vc_input_terminal_descriptor *vc_itd; + struct uvc_input_terminal_descriptor *vc_itd; /** Video Control Output Terminal descriptor from device */ - struct uvc_vc_output_terminal_descriptor *vc_otd; + struct uvc_output_terminal_descriptor *vc_otd; /** Video Control Camera Terminal descriptor from device */ - struct uvc_vc_camera_terminal_descriptor *vc_ctd; + struct uvc_camera_terminal_descriptor *vc_ctd; /** Video Control Selector Unit descriptor from device */ - struct uvc_vc_selector_unit_descriptor *vc_sud; + struct uvc_selector_unit_descriptor *vc_sud; /** Video Control Processing Unit descriptor from device */ - struct uvc_vc_processing_unit_descriptor *vc_pud; + struct uvc_processing_unit_descriptor *vc_pud; /** Video Control Encoding Unit descriptor from device */ - struct uvc_vc_encoding_unit_descriptor *vc_encoding_unit; + struct uvc_encoding_unit_descriptor *vc_encoding_unit; /** Video Control Extension Unit descriptor from device */ - struct uvc_vc_processing_unit_descriptor *vc_extension_unit; + struct uvc_extension_unit_descriptor *vc_extension_unit; /** Video Stream Input Header descriptor from device */ - struct uvc_vs_input_header_descriptor *vs_input_header; + struct uvc_stream_input_header_descriptor *vs_input_header; /** Video Stream Output Header descriptor from device */ - struct uvc_vs_output_header_descriptor *vs_output_header; + struct uvc_stream_output_header_descriptor *vs_output_header; /** Available format groups parsed from descriptors */ - struct uvc_vs_format_info formats; + struct uvc_format_info_all formats; /** Currently selected video format */ - struct uvc_vs_format current_format; + struct uvc_format_info current_format; /** Device-supported format capabilities for video API */ struct video_format_cap* video_format_caps; /** UVC probe/commit buffer */ - struct uvc_probe_commit video_probe; + struct uvc_probe video_probe; }; -static int uvc_host_flush_queue(struct uvc_device *uvc_dev, struct video_buffer *vbuf); +static int uvc_host_stream_iso_req_cb(struct usb_device *const dev, struct uhc_transfer *const xfer); /** * @brief Select default video format for UVC device @@ -189,131 +190,75 @@ static int uvc_host_select_default_format(struct uvc_device *uvc_dev) struct uvc_vs_format_uncompressed_info *uncompressed_info = &uvc_dev->formats.format_uncompressed; struct uvc_vs_format_mjpeg_info *mjpeg_info = &uvc_dev->formats.format_mjpeg; - /* Try uncompressed formats first */ - if (uncompressed_info->num_uncompressed_formats > 0 && - uncompressed_info->uncompressed_format[0]) { - - struct uvc_vs_format_uncompressed *format = uncompressed_info->uncompressed_format[0]; - - /* Get pixel format from GUID */ - uint32_t pixelformat = uvc_guid_to_fourcc(format->guidFormat); - if (pixelformat == 0) { - LOG_WRN("First uncompressed format has unsupported GUID"); - goto try_mjpeg; - } - - /* Find first frame descriptor */ - uint8_t *desc_buf = (uint8_t *)format + format->bLength; - - while (desc_buf) { - struct uvc_frame_header *frame_header = (struct uvc_frame_header *)desc_buf; + void *format_ptr; + uint8_t format_index, frame_subtype; + uint32_t pixelformat; + bool is_mjpeg; - if (frame_header->bLength == 0) break; - - if (frame_header->bDescriptorType == UVC_CS_INTERFACE && - frame_header->bDescriptorSubType == UVC_VS_FRAME_UNCOMPRESSED) { - - if (frame_header->bLength >= sizeof(struct uvc_frame_header)) { - uint16_t width = sys_le16_to_cpu(frame_header->wWidth); - uint16_t height = sys_le16_to_cpu(frame_header->wHeight); - - /* dwFrameInterval is at fixed offset 26 bytes for uncompressed frames */ - uint32_t frame_interval = 0; - if (frame_header->bLength >= 30) { /* Ensure sufficient space for dwFrameInterval */ - uint8_t *interval_ptr = desc_buf + 26; /* dwFrameInterval offset */ - frame_interval = sys_le32_to_cpu(*(uint32_t*)interval_ptr); - } - - /* Configure default format parameters */ - uvc_dev->current_format.pixelformat = pixelformat; - uvc_dev->current_format.width = width; - uvc_dev->current_format.height = height; - uvc_dev->current_format.format_index = format->bFormatIndex; - uvc_dev->current_format.frame_index = frame_header->bFrameIndex; - uvc_dev->current_format.frame_interval = frame_interval; - uvc_dev->current_format.format_ptr = (struct uvc_format_header *)format; - uvc_dev->current_format.frame_ptr = frame_header; - - /* Calculate FPS (frame_interval is in 100ns units) */ - if (frame_interval > 0) { - uvc_dev->current_format.fps = 10000000 / frame_interval; - } else { - uvc_dev->current_format.fps = 30; /* Default 30fps */ - } - - /* Calculate pitch (bytes per line) */ - uvc_dev->current_format.pitch = width * video_bits_per_pixel(pixelformat) / 8; - - LOG_INF("Set default format: %s %ux%u@%ufps (format_idx=%u, frame_idx=%u)", - VIDEO_FOURCC_TO_STR(pixelformat), - width, height, uvc_dev->current_format.fps, - format->bFormatIndex, frame_header->bFrameIndex); - return 0; - } - } - - desc_buf += frame_header->bLength; - } + /* Try uncompressed formats first */ + if (uncompressed_info->num_uncompressed_formats > 0 && uncompressed_info->uncompressed_format[0]) { + struct uvc_format_uncomp_descriptor *format = uncompressed_info->uncompressed_format[0]; + pixelformat = uvc_guid_to_fourcc(format->guidFormat); + if (pixelformat != 0) { + format_ptr = format; + format_index = format->bFormatIndex; + frame_subtype = UVC_VS_FRAME_UNCOMPRESSED; + is_mjpeg = false; + goto process_frames; + } + LOG_WRN("First uncompressed format has unsupported GUID"); + } + + /* Try MJPEG format */ + if (mjpeg_info->num_mjpeg_formats > 0 && mjpeg_info->vs_mjpeg_format[0]) { + struct uvc_format_mjpeg_descriptor *format = mjpeg_info->vs_mjpeg_format[0]; + format_ptr = format; + format_index = format->bFormatIndex; + frame_subtype = UVC_VS_FRAME_MJPEG; + pixelformat = VIDEO_PIX_FMT_JPEG; + is_mjpeg = true; + goto process_frames; } -try_mjpeg: - /* Try MJPEG format if uncompressed format is not available */ - if (mjpeg_info->num_mjpeg_formats > 0 && - mjpeg_info->vs_mjpeg_format[0]) { - - struct uvc_vs_format_mjpeg *format = mjpeg_info->vs_mjpeg_format[0]; - - /* Find first MJPEG frame descriptor */ - uint8_t *desc_buf = (uint8_t *)format + format->bLength; - - while (desc_buf) { - struct uvc_frame_header *frame_header = (struct uvc_frame_header *)desc_buf; - - if (frame_header->bLength == 0) break; - - if (frame_header->bDescriptorType == UVC_CS_INTERFACE && - frame_header->bDescriptorSubType == UVC_VS_FRAME_MJPEG) { - - if (frame_header->bLength >= sizeof(struct uvc_frame_header)) { - uint16_t width = sys_le16_to_cpu(frame_header->wWidth); - uint16_t height = sys_le16_to_cpu(frame_header->wHeight); - - /* dwFrameInterval is also at offset 26 bytes for MJPEG frames */ - uint32_t frame_interval = 0; - if (frame_header->bLength >= 30) { /* Ensure sufficient space for dwFrameInterval */ - uint8_t *interval_ptr = desc_buf + 26; /* dwFrameInterval offset */ - frame_interval = sys_le32_to_cpu(*(uint32_t*)interval_ptr); - } - - /* Configure default MJPEG format */ - uvc_dev->current_format.pixelformat = VIDEO_PIX_FMT_MJPEG; - uvc_dev->current_format.width = width; - uvc_dev->current_format.height = height; - uvc_dev->current_format.format_index = format->bFormatIndex; - uvc_dev->current_format.frame_index = frame_header->bFrameIndex; - uvc_dev->current_format.frame_interval = frame_interval; - uvc_dev->current_format.format_ptr = (struct uvc_format_header *)format; - uvc_dev->current_format.frame_ptr = frame_header; - /* Calculate FPS */ - if (frame_interval > 0) { - uvc_dev->current_format.fps = 10000000 / frame_interval; - } else { - uvc_dev->current_format.fps = 30; /* Default 30fps */ - } - - /* MJPEG pitch calculation (compressed format typically uses width) */ - uvc_dev->current_format.pitch = width; - - - LOG_INF("Set default format: MJPEG %ux%u@%ufps (format_idx=%u, frame_idx=%u)", - width, height, uvc_dev->current_format.fps, - format->bFormatIndex, frame_header->bFrameIndex); - return 0; - } - } + LOG_ERR("No valid format/frame descriptors found"); + return -ENOTSUP; - desc_buf += frame_header->bLength; +process_frames: + /* Find first frame descriptor */ + uint8_t *desc_buf = (uint8_t *)format_ptr + ((struct uvc_format_descriptor_header *)format_ptr)->bLength; + + while (desc_buf) { + struct uvc_frame_descriptor *frame = (struct uvc_frame_descriptor *)desc_buf; + + if (frame->bLength == 0) break; + + if (frame->bDescriptorType == USB_DESC_CS_INTERFACE && + frame->bDescriptorSubType == frame_subtype && + frame->bLength >= sizeof(struct uvc_frame_descriptor)) { + + uint16_t width = sys_le16_to_cpu(frame->wWidth); + uint16_t height = sys_le16_to_cpu(frame->wHeight); + uint32_t frame_interval = sys_le32_to_cpu(frame->dwDefaultFrameInterval); + + /* Configure format */ + uvc_dev->current_format.pixelformat = pixelformat; + uvc_dev->current_format.width = width; + uvc_dev->current_format.height = height; + uvc_dev->current_format.format_index = format_index; + uvc_dev->current_format.frame_index = frame->bFrameIndex; + uvc_dev->current_format.frame_interval = frame_interval; + uvc_dev->current_format.format_ptr = (struct uvc_format_descriptor_header *)format_ptr; + uvc_dev->current_format.frame_ptr = frame; + uvc_dev->current_format.fps = frame_interval > 0 ? 10000000 / frame_interval : 30; + uvc_dev->current_format.pitch = is_mjpeg ? width : width * video_bits_per_pixel(pixelformat) / 8; + + LOG_INF("Set default format: %s %ux%u@%ufps (format_idx=%u, frame_idx=%u)", + is_mjpeg ? "MJPEG" : VIDEO_FOURCC_TO_STR(pixelformat), + width, height, uvc_dev->current_format.fps, format_index, frame->bFrameIndex); + return 0; } + + desc_buf += frame->bLength; } LOG_ERR("No valid format/frame descriptors found"); @@ -332,7 +277,7 @@ static bool uvc_host_pu_supports_control(struct uvc_device *uvc_dev, uint32_t bm return false; } - struct uvc_vc_processing_unit_descriptor *pud = uvc_dev->vc_pud; + struct uvc_processing_unit_descriptor *pud = uvc_dev->vc_pud; if (pud->bControlSize == 0) { return false; @@ -359,7 +304,7 @@ static bool uvc_host_ct_supports_control(struct uvc_device *uvc_dev, uint32_t bm return false; } - struct uvc_vc_camera_terminal_descriptor *vc_ctd = uvc_dev->vc_ctd; + struct uvc_camera_terminal_descriptor *vc_ctd = uvc_dev->vc_ctd; if (vc_ctd->bControlSize == 0) { return false; @@ -532,7 +477,7 @@ static int usb_host_camera_init_controls(const struct device *dev) if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_EXPOSURE_TIME_ABSOLUTE)) { ret = video_init_ctrl(&ctrls->exposure_absolute, dev, VIDEO_CID_EXPOSURE_ABSOLUTE, (struct video_ctrl_range){ - .min = 1, /* Minimum exposure time 1μs */ + .min = 1, /* Minimum exposure time 1μs */ .max = 10000000, /* Maximum exposure time 10s (10,000,000μs) */ .step = 1, .def = 33333 /* Default 1/30s ≈ 33.33ms */ @@ -656,12 +601,12 @@ static int uvc_host_parse_interface_descriptor(struct uvc_device *uvc_dev, case UVC_SC_VIDEOSTREAMING: /* Video Streaming interface: save to stream_ifaces array for all of alternates including 0 */ - for (int i = 0; i < UVC_STREAM_INTERFACES_MAX_ALT; i++) { + for (int i = 0; i < CONFIG_USBH_VIDEO_MAX_STREAM_INTERFACE; i++) { if (uvc_dev->stream_ifaces[i] == NULL) { /* Found empty slot, save interface */ uvc_dev->stream_ifaces[i] = if_desc; /* Save current_stream_iface as alternat 0 interface */ - if (!if_desc->bAlternateSetting) { + if (!if_desc->bAlternateSetting) { uvc_dev->current_stream_iface_info.current_stream_iface = if_desc; } break; @@ -690,7 +635,7 @@ static int uvc_host_parse_interface_descriptor(struct uvc_device *uvc_dev, */ static int uvc_host_parse_cs_vc_interface_descriptor(struct uvc_device *uvc_dev, struct usb_if_descriptor *control_if) { - struct usb_cs_desc_header *header; + struct uvc_cs_descriptor_header *header; /* Basic validation */ if (!control_if || !uvc_dev || control_if->bLength < 3) { @@ -699,34 +644,34 @@ static int uvc_host_parse_cs_vc_interface_descriptor(struct uvc_device *uvc_dev, } /* Skip the interface descriptor itself */ - header = (struct usb_cs_desc_header *)((uint8_t *)control_if + control_if->bLength); + header = (struct uvc_cs_descriptor_header *)((uint8_t *)control_if + control_if->bLength); while ((uint8_t *)header < (uint8_t *)uvc_dev->desc_end) { /* Check for end of descriptors or next interface */ if (header->bDescriptorType == USB_DESC_INTERFACE || - header->bDescriptorType == USB_DESC_INTERFACE_ASSOC || - header->bLength == 0) { + header->bDescriptorType == USB_DESC_INTERFACE_ASSOC || + header->bLength == 0) { break; } - if (header->bDescriptorType == USB_DESC_CS_INTERFACE) { + if (header->bDescriptorType == USB_DESC_CS_INTERFACE) { switch (header->bDescriptorSubType) { case UVC_VC_HEADER: { - struct uvc_vc_header_descriptor *header_desc = - (struct uvc_vc_header_descriptor *)header; + struct uvc_control_header_descriptor *header_desc = + (struct uvc_control_header_descriptor *)header; /* Check descriptor length - at least basic fields */ - if (header->bLength < sizeof(struct uvc_vc_header_descriptor)) { + if (header->bLength < sizeof(struct uvc_control_header_descriptor)) { LOG_ERR("Invalid VC header descriptor length: %u", header->bLength); return -EINVAL; } /* Additional length check for interface collection */ - if (header->bLength < (sizeof(struct uvc_vc_header_descriptor) + + if (header->bLength < (sizeof(struct uvc_control_header_descriptor) + header_desc->bInCollection)) { LOG_ERR("VC header descriptor too short for interface collection: %u < %u", header->bLength, - (unsigned)(sizeof(struct uvc_vc_header_descriptor) + header_desc->bInCollection)); + (unsigned)(sizeof(struct uvc_control_header_descriptor) + header_desc->bInCollection)); return -EINVAL; } @@ -749,22 +694,22 @@ static int uvc_host_parse_cs_vc_interface_descriptor(struct uvc_device *uvc_dev, } case UVC_VC_INPUT_TERMINAL: { - struct uvc_vc_input_terminal_descriptor *it_desc = - (struct uvc_vc_input_terminal_descriptor *)header; + struct uvc_input_terminal_descriptor *it_desc = + (struct uvc_input_terminal_descriptor *)header; /* Check descriptor length - at least basic fields */ - if (header->bLength < sizeof(struct uvc_vc_input_terminal_descriptor)) { + if (header->bLength < sizeof(struct uvc_input_terminal_descriptor)) { LOG_ERR("Invalid input terminal descriptor length: %u", header->bLength); return -EINVAL; } /* Check if this is Camera Terminal (wTerminalType = 0x0201) */ if (sys_le16_to_cpu(it_desc->wTerminalType) == UVC_ITT_CAMERA) { - struct uvc_vc_camera_terminal_descriptor *ct_desc = - (struct uvc_vc_camera_terminal_descriptor *)header; + struct uvc_camera_terminal_descriptor *ct_desc = + (struct uvc_camera_terminal_descriptor *)header; /* Check Camera Terminal descriptor length */ - if (header->bLength < (sizeof(struct uvc_vc_input_terminal_descriptor) + 6 + ct_desc->bControlSize)) { + if (header->bLength < (sizeof(struct uvc_input_terminal_descriptor) + 6 + ct_desc->bControlSize)) { LOG_ERR("Invalid camera terminal descriptor length: %u", header->bLength); return -EINVAL; } @@ -806,11 +751,11 @@ static int uvc_host_parse_cs_vc_interface_descriptor(struct uvc_device *uvc_dev, } case UVC_VC_OUTPUT_TERMINAL: { - struct uvc_vc_output_terminal_descriptor *ot_desc = - (struct uvc_vc_output_terminal_descriptor *)header; + struct uvc_output_terminal_descriptor *ot_desc = + (struct uvc_output_terminal_descriptor *)header; /* Check descriptor length - at least basic fields */ - if (header->bLength < sizeof(struct uvc_vc_output_terminal_descriptor)) { + if (header->bLength < sizeof(struct uvc_output_terminal_descriptor)) { LOG_ERR("Invalid output terminal descriptor length: %u", header->bLength); return -EINVAL; } @@ -832,8 +777,8 @@ static int uvc_host_parse_cs_vc_interface_descriptor(struct uvc_device *uvc_dev, } case UVC_VC_SELECTOR_UNIT: { - struct uvc_vc_selector_unit_descriptor *su_desc = - (struct uvc_vc_selector_unit_descriptor *)header; + struct uvc_selector_unit_descriptor *su_desc = + (struct uvc_selector_unit_descriptor *)header; /* Check descriptor length - at least basic fields */ if (header->bLength < 5) { /* bLength + bDescriptorType + bDescriptorSubType + bUnitID + bNrInPins */ @@ -870,8 +815,8 @@ static int uvc_host_parse_cs_vc_interface_descriptor(struct uvc_device *uvc_dev, } case UVC_VC_PROCESSING_UNIT: { - struct uvc_vc_processing_unit_descriptor *pu_desc = - (struct uvc_vc_processing_unit_descriptor *)header; + struct uvc_processing_unit_descriptor *pu_desc = + (struct uvc_processing_unit_descriptor *)header; /* Check descriptor length - at least basic fields */ if (header->bLength < 8) { /* Basic field length */ @@ -910,8 +855,8 @@ static int uvc_host_parse_cs_vc_interface_descriptor(struct uvc_device *uvc_dev, } case UVC_VC_ENCODING_UNIT: { - struct uvc_vc_encoding_unit_descriptor *enc_desc = - (struct uvc_vc_encoding_unit_descriptor *)header; + struct uvc_encoding_unit_descriptor *enc_desc = + (struct uvc_encoding_unit_descriptor *)header; /* Check descriptor length - at least basic fields */ if (header->bLength < 8) { /* Basic field length */ @@ -949,8 +894,8 @@ static int uvc_host_parse_cs_vc_interface_descriptor(struct uvc_device *uvc_dev, } case UVC_VC_EXTENSION_UNIT: { - struct uvc_vc_extension_unit_descriptor *eu_desc = - (struct uvc_vc_extension_unit_descriptor *)header; + struct uvc_extension_unit_descriptor *eu_desc = + (struct uvc_extension_unit_descriptor *)header; /* Check descriptor length - at least basic fields */ if (header->bLength < 24) { /* Minimum: 3 + 1 + 16 + 1 + 1 + 1 + 1 = 24 bytes */ @@ -993,7 +938,7 @@ static int uvc_host_parse_cs_vc_interface_descriptor(struct uvc_device *uvc_dev, } } /* Move to next descriptor */ - header = (struct usb_cs_desc_header *)((uint8_t *)header + header->bLength); + header = (struct uvc_cs_descriptor_header *)((uint8_t *)header + header->bLength); } return 0; @@ -1011,7 +956,7 @@ static int uvc_host_parse_cs_vc_interface_descriptor(struct uvc_device *uvc_dev, */ static int uvc_host_parse_cs_vs_interface_descriptor(struct uvc_device *uvc_dev, struct usb_if_descriptor *stream_if) { - struct usb_cs_desc_header *header; + struct uvc_cs_descriptor_header *header; /* Basic validation */ if (!stream_if || !uvc_dev || stream_if->bLength < 3) { @@ -1020,23 +965,23 @@ static int uvc_host_parse_cs_vs_interface_descriptor(struct uvc_device *uvc_dev, } /* Skip the interface descriptor itself */ - header = (struct usb_cs_desc_header *)((uint8_t *)stream_if + stream_if->bLength); + header = (struct uvc_cs_descriptor_header *)((uint8_t *)stream_if + stream_if->bLength); while ((uint8_t *)header < (uint8_t *)uvc_dev->desc_end) { /* Check for end of descriptors or next interface */ if (header->bDescriptorType == USB_DESC_INTERFACE || - header->bDescriptorType == USB_DESC_INTERFACE_ASSOC || - header->bLength == 0) { + header->bDescriptorType == USB_DESC_INTERFACE_ASSOC || + header->bLength == 0) { break; } - if (header->bDescriptorType == USB_DESC_CS_INTERFACE) { + if (header->bDescriptorType == USB_DESC_CS_INTERFACE) { switch (header->bDescriptorSubType) { case UVC_VS_INPUT_HEADER: { - struct uvc_vs_input_header_descriptor *header_desc = (struct uvc_vs_input_header_descriptor *)header; + struct uvc_stream_input_header_descriptor *header_desc = (struct uvc_stream_input_header_descriptor *)header; /* Check descriptor length */ - if (header->bLength < sizeof(struct uvc_vs_input_header_descriptor)) { + if (header->bLength < sizeof(struct uvc_stream_input_header_descriptor)) { LOG_ERR("Invalid VS input header descriptor length: %u", header->bLength); return -EINVAL; } @@ -1054,10 +999,10 @@ static int uvc_host_parse_cs_vs_interface_descriptor(struct uvc_device *uvc_dev, } case UVC_VS_OUTPUT_HEADER: { - struct uvc_vs_output_header_descriptor *header_desc = (struct uvc_vs_output_header_descriptor *)header; + struct uvc_stream_output_header_descriptor *header_desc = (struct uvc_stream_output_header_descriptor *)header; /* Check descriptor length */ - if (header->bLength < sizeof(struct uvc_vs_output_header_descriptor)) { + if (header->bLength < sizeof(struct uvc_stream_output_header_descriptor)) { LOG_ERR("Invalid VS output header descriptor length: %u", header->bLength); return -EINVAL; } @@ -1075,17 +1020,17 @@ static int uvc_host_parse_cs_vs_interface_descriptor(struct uvc_device *uvc_dev, } case UVC_VS_FORMAT_UNCOMPRESSED: { - struct uvc_vs_format_uncompressed *format_desc = (struct uvc_vs_format_uncompressed *)header; + struct uvc_format_uncomp_descriptor *format_desc = (struct uvc_format_uncomp_descriptor *)header; struct uvc_vs_format_uncompressed_info *info = &uvc_dev->formats.format_uncompressed; /* Check descriptor length */ - if (header->bLength < sizeof(struct uvc_vs_format_uncompressed)) { + if (header->bLength < sizeof(struct uvc_format_uncomp_descriptor)) { LOG_ERR("Invalid uncompressed format descriptor length: %u", header->bLength); return -EINVAL; } /* Check array space */ - if (info->num_uncompressed_formats >= UVC_MAX_UNCOMPRESSED_FORMAT) { + if (info->num_uncompressed_formats >= CONFIG_USBH_VIDEO_MAX_FORMATS) { LOG_WRN("Too many uncompressed formats, ignoring format index %u", format_desc->bFormatIndex); return 0; @@ -1105,17 +1050,17 @@ static int uvc_host_parse_cs_vs_interface_descriptor(struct uvc_device *uvc_dev, } case UVC_VS_FORMAT_MJPEG: { - struct uvc_vs_format_mjpeg *format_desc = (struct uvc_vs_format_mjpeg *)header; + struct uvc_format_mjpeg_descriptor *format_desc = (struct uvc_format_mjpeg_descriptor *)header; struct uvc_vs_format_mjpeg_info *info = &uvc_dev->formats.format_mjpeg; /* Check descriptor length */ - if (header->bLength < sizeof(struct uvc_vs_format_mjpeg)) { + if (header->bLength < sizeof(struct uvc_format_mjpeg_descriptor)) { LOG_ERR("Invalid MJPEG format descriptor length: %u", header->bLength); return -EINVAL; } /* Check array space */ - if (info->num_mjpeg_formats >= UVC_MAX_MJPEG_FORMAT) { + if (info->num_mjpeg_formats >= CONFIG_USBH_VIDEO_MAX_FORMATS) { LOG_WRN("Too many MJPEG formats, ignoring format index %u", format_desc->bFormatIndex); return 0; @@ -1142,7 +1087,7 @@ static int uvc_host_parse_cs_vs_interface_descriptor(struct uvc_device *uvc_dev, } } /* Move to next descriptor */ - header = (struct usb_cs_desc_header *)((uint8_t *)header + header->bLength); + header = (struct uvc_cs_descriptor_header *)((uint8_t *)header + header->bLength); } return 0; } @@ -1233,57 +1178,36 @@ static int uvc_host_parse_descriptors(struct uvc_device *uvc_dev) * Extracts the default frame interval from frame descriptor. If default * interval is invalid (0), falls back to maximum supported interval. * - * @param desc_buf Pointer to frame descriptor buffer - * @param frame_subtype Frame descriptor subtype (UVC_VS_FRAME_UNCOMPRESSED or UVC_VS_FORMAT_MJPEG) + * @param frame_desc Pointer to frame descriptor buffer * @return Default or maximum frame interval in 100ns units */ -static uint32_t uvc_host_parse_frame_default_intervals(uint8_t *desc_buf, uint8_t frame_subtype) +static uint32_t uvc_host_parse_frame_default_intervals(struct uvc_frame_descriptor *frame_desc) { - uint32_t default_interval = 0; - uint8_t interval_type; - uint8_t *interval_data; - - if (frame_subtype == UVC_VS_FRAME_UNCOMPRESSED) { - struct uvc_vs_frame_uncompressed *frame_desc = - (struct uvc_vs_frame_uncompressed *)desc_buf; - default_interval = sys_le32_to_cpu(frame_desc->dwDefaultFrameInterval); - interval_type = frame_desc->bFrameIntervalType; - /* Interval data follows immediately after bFrameIntervalType field */ - interval_data = &(frame_desc->bFrameIntervalType) + 1; - } else if (frame_subtype == UVC_VS_FORMAT_MJPEG) { - struct uvc_vs_frame_mjpeg *frame_desc = - (struct uvc_vs_frame_mjpeg *)desc_buf; - default_interval = sys_le32_to_cpu(frame_desc->dwDefaultFrameInterval); - interval_type = frame_desc->bFrameIntervalType; - /* Interval data follows immediately after bFrameIntervalType field */ - interval_data = &(frame_desc->bFrameIntervalType) + 1; - } else { - /* Unsupported frame subtype, use hardcoded fallback */ - return 333333; /* 30fps */ - } + uint32_t default_interval = sys_le32_to_cpu(frame_desc->dwDefaultFrameInterval); + uint8_t interval_type = frame_desc->bFrameIntervalType; - /* If default interval is valid, use it */ - if (default_interval != 0) { - return default_interval; - } + /* If default interval is valid, use it */ + if (default_interval != 0) { + return default_interval; + } - /* Default interval is invalid, find maximum supported interval */ - uint32_t max_interval = 333333; /* Fallback to 30fps */ - - if (interval_type == 0) { - /* Continuous/stepwise intervals: dwMin, dwMax, dwStep */ - if (interval_data + 8 <= desc_buf + desc_buf[0]) { /* Bounds check */ - max_interval = sys_le32_to_cpu(*(uint32_t*)(interval_data + 4)); - } - } else if (interval_type > 0) { - /* Discrete intervals: take the last (typically maximum) value */ - uint32_t last_interval_offset = (interval_type - 1) * 4; - if (interval_data + last_interval_offset + 4 <= desc_buf + desc_buf[0]) { /* Bounds check */ - max_interval = sys_le32_to_cpu(*(uint32_t*)(interval_data + last_interval_offset)); - } - } + /* Default interval is invalid, find maximum supported interval */ + uint32_t max_interval = 333333; /* Fallback to 30fps */ - return max_interval; + if (interval_type == 0) { + /* Continuous frame intervals */ + struct uvc_frame_continuous_descriptor *continuous_desc = + (struct uvc_frame_continuous_descriptor *)frame_desc; + max_interval = sys_le32_to_cpu(continuous_desc->dwMaxFrameInterval); + } else { + /* Discrete frame intervals */ + struct uvc_frame_discrete_descriptor *discrete_desc = + (struct uvc_frame_discrete_descriptor *)frame_desc; + /* Limit interval count to configured maximum */ + max_interval = sys_le32_to_cpu(discrete_desc->dwFrameInterval[discrete_desc->bFrameIntervalType - 1]); + } + + return max_interval; } /** @@ -1300,11 +1224,11 @@ static uint32_t uvc_host_parse_frame_default_intervals(uint8_t *desc_buf, uint8_ * @param found_interval Pointer to store found frame interval * @return 0 on success, negative error code if not found */ -static int uvc_host_find_frame_in_format(struct uvc_format_header *format_header, +static int uvc_host_find_frame_in_format(struct uvc_format_descriptor_header *format_header, uint16_t target_width, uint16_t target_height, uint8_t expected_frame_subtype, - struct uvc_frame_header **found_frame, + struct uvc_frame_descriptor **found_frame, uint32_t *found_interval) { uint8_t *desc_buf = (uint8_t *)format_header + format_header->bLength; @@ -1312,30 +1236,29 @@ static int uvc_host_find_frame_in_format(struct uvc_format_header *format_header /* Iterate through all frame descriptors for this format */ while (frames_found < format_header->bNumFrameDescriptors) { - struct uvc_frame_header *frame_header = (struct uvc_frame_header *)desc_buf; + struct uvc_frame_descriptor *frame_header = (struct uvc_frame_descriptor *)desc_buf; - if (frame_header->bLength == 0) break; + if (frame_header->bLength == 0) { + break; + } /* Check if this is the expected frame descriptor type */ - if (frame_header->bDescriptorType == UVC_CS_INTERFACE && + if (frame_header->bDescriptorType == USB_DESC_CS_INTERFACE && frame_header->bDescriptorSubType == expected_frame_subtype) { - if (frame_header->bLength >= sizeof(struct uvc_frame_header)) { - uint16_t frame_width = sys_le16_to_cpu(frame_header->wWidth); - uint16_t frame_height = sys_le16_to_cpu(frame_header->wHeight); + uint16_t frame_width = sys_le16_to_cpu(frame_header->wWidth); + uint16_t frame_height = sys_le16_to_cpu(frame_header->wHeight); - /* Check if dimensions match target */ - if (frame_width == target_width && frame_height == target_height) { - *found_frame = frame_header; - /* Parse frame interval from descriptor */ - *found_interval = (frame_header->bLength >= 26) ? - uvc_host_parse_frame_default_intervals(desc_buf, expected_frame_subtype) : 333333; - return 0; - } - frames_found++; + /* Check if dimensions match target */ + if (frame_width == target_width && frame_height == target_height) { + *found_frame = frame_header; + /* Parse frame interval from descriptor */ + *found_interval = uvc_host_parse_frame_default_intervals(frame_header); + return 0; } - } else if (frame_header->bDescriptorType == UVC_CS_INTERFACE && - (desc_buf[2] == UVC_VS_FORMAT_UNCOMPRESSED || desc_buf[2] == UVC_VS_FORMAT_MJPEG)) { + frames_found++; + } else if (frame_header->bDescriptorType == USB_DESC_CS_INTERFACE && + (frame_header->bDescriptorSubType == UVC_VS_FORMAT_UNCOMPRESSED || frame_header->bDescriptorSubType == UVC_VS_FORMAT_MJPEG)) { /* Encountered new format descriptor, stop searching */ break; } @@ -1365,8 +1288,8 @@ static int uvc_host_find_format(struct uvc_device *uvc_dev, uint32_t pixelformat, uint16_t width, uint16_t height, - struct uvc_format_header **format, - struct uvc_frame_header **frame, + struct uvc_format_descriptor_header **format, + struct uvc_frame_descriptor **frame, uint32_t *frame_interval) { if (!uvc_dev || !format || !frame || !frame_interval) { @@ -1380,7 +1303,7 @@ static int uvc_host_find_format(struct uvc_device *uvc_dev, struct uvc_vs_format_uncompressed_info *uncompressed_info = &uvc_dev->formats.format_uncompressed; for (int i = 0; i < uncompressed_info->num_uncompressed_formats; i++) { - struct uvc_vs_format_uncompressed *format_desc = uncompressed_info->uncompressed_format[i]; + struct uvc_format_uncomp_descriptor *format_desc = uncompressed_info->uncompressed_format[i]; if (!format_desc) continue; @@ -1391,10 +1314,10 @@ static int uvc_host_find_format(struct uvc_device *uvc_dev, LOG_DBG("Found matching uncompressed format: index=%u", format_desc->bFormatIndex); /* Search for matching frame in this format */ - if (uvc_host_find_frame_in_format((struct uvc_format_header *)format_desc, + if (uvc_host_find_frame_in_format((struct uvc_format_descriptor_header *)format_desc, width, height, UVC_VS_FRAME_UNCOMPRESSED, frame, frame_interval) == 0) { - *format = (struct uvc_format_header *)format_desc; + *format = (struct uvc_format_descriptor_header *)format_desc; LOG_DBG("Found matching frame: format_addr=%p, frame_addr=%p, interval=%u", *format, *frame, *frame_interval); return 0; @@ -1402,22 +1325,22 @@ static int uvc_host_find_format(struct uvc_device *uvc_dev, } } - /* Search MJPEG formats */ - if (pixelformat == VIDEO_PIX_FMT_MJPEG) { + /* Search MJPEG formats */ + if (pixelformat == VIDEO_PIX_FMT_JPEG) { struct uvc_vs_format_mjpeg_info *mjpeg_info = &uvc_dev->formats.format_mjpeg; for (int i = 0; i < mjpeg_info->num_mjpeg_formats; i++) { - struct uvc_vs_format_mjpeg *format_desc = mjpeg_info->vs_mjpeg_format[i]; + struct uvc_format_mjpeg_descriptor *format_desc = mjpeg_info->vs_mjpeg_format[i]; if (!format_desc) continue; LOG_DBG("Checking MJPEG format: index=%u", format_desc->bFormatIndex); /* Search for matching frame in MJPEG format */ - if (uvc_host_find_frame_in_format((struct uvc_format_header *)format_desc, + if (uvc_host_find_frame_in_format((struct uvc_format_descriptor_header *)format_desc, width, height, UVC_VS_FRAME_MJPEG, frame, frame_interval) == 0) { - *format = (struct uvc_format_header *)format_desc; + *format = (struct uvc_format_descriptor_header *)format_desc; LOG_DBG("Found matching MJPEG frame: format_addr=%p, frame_addr=%p, interval=%u", *format, *frame, *frame_interval); return 0; @@ -1455,7 +1378,7 @@ static int uvc_host_select_streaming_alternate(struct uvc_device *uvc_dev, uint3 (device_speed == USB_SPEED_SPEED_HS) ? "High Speed" : "Full Speed"); /* Iterate through all alternate setting interfaces */ - for (int i = 0; i < UVC_STREAM_INTERFACES_MAX_ALT && uvc_dev->stream_ifaces[i]; i++) { + for (int i = 0; i < CONFIG_USBH_VIDEO_MAX_STREAM_INTERFACE && uvc_dev->stream_ifaces[i]; i++) { struct usb_if_descriptor *if_desc = uvc_dev->stream_ifaces[i]; /* Skip Alt 0 (idle state) */ @@ -1492,7 +1415,7 @@ static int uvc_host_select_streaming_alternate(struct uvc_device *uvc_dev, uint3 ep_bandwidth = (max_packet_size * 1000) / ep_desc->bInterval; } - LOG_DBG(" Interface %u Alt %u EP[%d]: addr=0x%02x, maxpkt=%u, payload=%u, bandwidth=%u", + LOG_DBG("Interface %u Alt %u EP[%d]: addr=0x%02x, maxpkt=%u, payload=%u, bandwidth=%u", if_desc->bInterfaceNumber, if_desc->bAlternateSetting, ep, ep_desc->bEndpointAddress, max_packet_size, ep_payload_size, ep_bandwidth); @@ -1512,11 +1435,11 @@ static int uvc_host_select_streaming_alternate(struct uvc_device *uvc_dev, uint3 ep_desc->bEndpointAddress, ep_bandwidth, ep_payload_size); } else { if (ep_bandwidth < required_bandwidth) { - LOG_DBG(" Endpoint rejected: insufficient bandwidth (%u < %u)", + LOG_DBG("Endpoint rejected: insufficient bandwidth (%u < %u)", ep_bandwidth, required_bandwidth); } if (ep_payload_size < max_payload_transfer_size) { - LOG_DBG(" Endpoint rejected: insufficient payload size (%u < %u)", + LOG_DBG("Endpoint rejected: insufficient payload size (%u < %u)", ep_payload_size, max_payload_transfer_size); } } @@ -1527,7 +1450,7 @@ static int uvc_host_select_streaming_alternate(struct uvc_device *uvc_dev, uint3 } if (!found_suitable) { - LOG_ERR("No endpoint satisfies bandwidth requirement %u and payload size %u", + LOG_ERR("No endpoint satisfies bandwidth requirement %u and payload size %u", required_bandwidth, max_payload_transfer_size); return -ENOTSUP; } @@ -1568,10 +1491,11 @@ static uint32_t uvc_host_calculate_required_bandwidth(struct uvc_device *uvc_dev /* Calculate bandwidth based on pixel format characteristics */ switch (pixelformat) { - case VIDEO_PIX_FMT_MJPEG: - /* MJPEG compressed format, use empirical compression ratio */ - /* Assume compression ratio 8:1 to 12:1, use conservative 6:1 here */ - bandwidth = (width * height * fps * 3) / 6; /* RGB24 compressed 6x */ + case VIDEO_PIX_FMT_JPEG: + /* MJPEG compressed format, use empirical compression ratio + * Assume compression ratio 8:1 to 12:1, use conservative 6:1 here + */ + bandwidth = (width * height * fps * 3) / 6; break; case VIDEO_PIX_FMT_YUYV: @@ -1760,8 +1684,8 @@ static int uvc_host_set_format(struct uvc_device *uvc_dev, uint32_t pixelformat, { uint32_t frame_interval; uint32_t required_bandwidth; - struct uvc_format_header *format; - struct uvc_frame_header *frame; + struct uvc_format_descriptor_header *format; + struct uvc_frame_descriptor *frame; int ret; /* 1. Find matching format and frame descriptors */ @@ -1813,11 +1737,11 @@ static int uvc_host_set_format(struct uvc_device *uvc_dev, uint32_t pixelformat, uvc_dev->current_format.pixelformat = pixelformat; uvc_dev->current_format.width = width; uvc_dev->current_format.height = height; - uvc_dev->current_format.format_index = format->bFormatIndex; /* Use descriptor index */ - uvc_dev->current_format.frame_index = frame->bFrameIndex; /* Use descriptor index */ + uvc_dev->current_format.format_index = format->bFormatIndex; + uvc_dev->current_format.frame_index = frame->bFrameIndex; uvc_dev->current_format.frame_interval = frame_interval; - uvc_dev->current_format.format_ptr = format; /* Save format descriptor pointer */ - uvc_dev->current_format.frame_ptr = frame; /* Save frame descriptor pointer */ + uvc_dev->current_format.format_ptr = format; + uvc_dev->current_format.frame_ptr = frame; /* 7. Recalculate FPS and pitch based on negotiated parameters */ if (frame_interval > 0) { @@ -1903,91 +1827,47 @@ static int uvc_host_set_frame_rate(struct uvc_device *uvc_dev, uint32_t fps) } /* Get current frame descriptor */ - struct uvc_frame_header *frame_ptr = uvc_dev->current_format.frame_ptr; - if (!frame_ptr) { + struct uvc_frame_descriptor *frame_desc = uvc_dev->current_format.frame_ptr; + if (!frame_desc) { LOG_ERR("No current frame descriptor available"); k_mutex_unlock(&uvc_dev->lock); return -EINVAL; } /* Parse frame intervals from current frame descriptor */ - if (frame_ptr->bDescriptorSubType == UVC_VS_FRAME_UNCOMPRESSED) { - struct uvc_vs_frame_uncompressed *frame_desc = - (struct uvc_vs_frame_uncompressed *)frame_ptr; - - if (frame_desc->bFrameIntervalType == 0) { - /* Continuous frame intervals */ - uint32_t *intervals = (uint32_t *)((uint8_t *)frame_desc + sizeof(*frame_desc)); - uint32_t min_interval = sys_le32_to_cpu(intervals[0]); - uint32_t max_interval = sys_le32_to_cpu(intervals[1]); - uint32_t step_interval = sys_le32_to_cpu(intervals[2]); - - /* Clamp to supported range */ - if (target_frame_interval < min_interval) { - best_frame_interval = min_interval; - } else if (target_frame_interval > max_interval) { - best_frame_interval = max_interval; - } else { - /* Find closest step-aligned interval */ - uint32_t steps = (target_frame_interval - min_interval) / step_interval; - best_frame_interval = min_interval + (steps * step_interval); - found_exact_match = (best_frame_interval == target_frame_interval); - } + if (frame_desc->bFrameIntervalType == 0) { + /* Continuous frame intervals */ + struct uvc_frame_continuous_descriptor *continuous_desc = (struct uvc_frame_continuous_descriptor *)frame_desc; + uint32_t min_interval = sys_le32_to_cpu(continuous_desc->dwMinFrameInterval); + uint32_t max_interval = sys_le32_to_cpu(continuous_desc->dwMaxFrameInterval); + uint32_t step_interval = sys_le32_to_cpu(continuous_desc->dwFrameIntervalStep); + + /* Clamp to supported range */ + if (target_frame_interval < min_interval) { + best_frame_interval = min_interval; + } else if (target_frame_interval > max_interval) { + best_frame_interval = max_interval; } else { - /* Discrete frame intervals */ - uint32_t *intervals = (uint32_t *)((uint8_t *)frame_desc + sizeof(*frame_desc)); - - for (int i = 0; i < frame_desc->bFrameIntervalType; i++) { - uint32_t interval = sys_le32_to_cpu(intervals[i]); - uint32_t diff = (interval > target_frame_interval) ? - (interval - target_frame_interval) : (target_frame_interval - interval); - - if (diff < min_diff) { - min_diff = diff; - best_frame_interval = interval; - found_exact_match = (diff == 0); - } - } + /* Find closest step-aligned interval */ + uint32_t steps = (target_frame_interval - min_interval) / step_interval; + best_frame_interval = min_interval + (steps * step_interval); + found_exact_match = (best_frame_interval == target_frame_interval); } - } else if (frame_ptr->bDescriptorSubType == UVC_VS_FRAME_MJPEG) { - struct uvc_vs_frame_mjpeg *frame_desc = - (struct uvc_vs_frame_mjpeg *)frame_ptr; - - /* Similar logic for MJPEG frames */ - if (frame_desc->bFrameIntervalType == 0) { - uint32_t *intervals = (uint32_t *)((uint8_t *)frame_desc + sizeof(*frame_desc)); - uint32_t min_interval = sys_le32_to_cpu(intervals[0]); - uint32_t max_interval = sys_le32_to_cpu(intervals[1]); - uint32_t step_interval = sys_le32_to_cpu(intervals[2]); - - if (target_frame_interval < min_interval) { - best_frame_interval = min_interval; - } else if (target_frame_interval > max_interval) { - best_frame_interval = max_interval; - } else { - uint32_t steps = (target_frame_interval - min_interval) / step_interval; - best_frame_interval = min_interval + (steps * step_interval); - found_exact_match = (best_frame_interval == target_frame_interval); - } - } else { - uint32_t *intervals = (uint32_t *)((uint8_t *)frame_desc + sizeof(*frame_desc)); + } else { + /* Discrete frame intervals */ + struct uvc_frame_discrete_descriptor *discrete_desc = (struct uvc_frame_discrete_descriptor *)frame_desc; - for (int i = 0; i < frame_desc->bFrameIntervalType; i++) { - uint32_t interval = sys_le32_to_cpu(intervals[i]); - uint32_t diff = (interval > target_frame_interval) ? - (interval - target_frame_interval) : (target_frame_interval - interval); + for (int i = 0; i < discrete_desc->bFrameIntervalType; i++) { + uint32_t interval = sys_le32_to_cpu(discrete_desc->dwFrameInterval[i]); + uint32_t diff = (interval > target_frame_interval) ? + (interval - target_frame_interval) : (target_frame_interval - interval); - if (diff < min_diff) { - min_diff = diff; - best_frame_interval = interval; - found_exact_match = (diff == 0); - } + if (diff < min_diff) { + min_diff = diff; + best_frame_interval = interval; + found_exact_match = (diff == 0); } } - } else { - LOG_ERR("Unsupported frame descriptor type: 0x%02x", frame_ptr->bDescriptorSubType); - k_mutex_unlock(&uvc_dev->lock); - return -ENOTSUP; } /* Initialize probe structure with current format settings */ @@ -2005,7 +1885,7 @@ static int uvc_host_set_frame_rate(struct uvc_device *uvc_dev, uint32_t fps) /* Send PROBE request */ ret = uvc_host_stream_interface_control(uvc_dev, UVC_SET_CUR, UVC_VS_PROBE_CONTROL, - &uvc_dev->video_probe, sizeof(uvc_dev->video_probe)); + &uvc_dev->video_probe, sizeof(uvc_dev->video_probe)); if (ret) { LOG_ERR("PROBE SET request failed: %d", ret); return ret; @@ -2070,6 +1950,68 @@ static int uvc_host_set_frame_rate(struct uvc_device *uvc_dev, uint32_t fps) return 0; } +/** + * @brief Parse frame descriptors for a specific format + * + * @param format_ptr Pointer to format descriptor + * @param format_length Length of format descriptor + * @param num_frames Number of frame descriptors expected + * @param pixelformat Pixel format value + * @param frame_subtype Expected frame descriptor subtype + * @param caps_array Capabilities array to fill + * @param start_index Starting index in capabilities array + * @param max_caps Maximum number of capabilities + * @return Updated capability index + */ +static int uvc_host_parse_format_frames(uint8_t *format_ptr, uint8_t format_length, + uint8_t num_frames, uint32_t pixelformat, + uint8_t frame_subtype, struct video_format_cap *caps_array, + int start_index, int max_caps) +{ + uint8_t *desc_buf = format_ptr + format_length; + int frames_found = 0; + int cap_index = start_index; + + while (frames_found < num_frames && cap_index < max_caps) { + struct uvc_frame_descriptor *frame_header = (struct uvc_frame_descriptor *)desc_buf; + + if (frame_header->bLength == 0) break; + + if (frame_header->bDescriptorType == USB_DESC_CS_INTERFACE && + frame_header->bDescriptorSubType == frame_subtype) { + + if (frame_header->bLength >= sizeof(struct uvc_frame_descriptor)) { + uint16_t width = sys_le16_to_cpu(frame_header->wWidth); + uint16_t height = sys_le16_to_cpu(frame_header->wHeight); + + /* Create format capability entry */ + caps_array[cap_index].pixelformat = pixelformat; + caps_array[cap_index].width_min = width; + caps_array[cap_index].width_max = width; + caps_array[cap_index].height_min = height; + caps_array[cap_index].height_max = height; + caps_array[cap_index].width_step = 0; + caps_array[cap_index].height_step = 0; + + LOG_DBG("Added format cap[%d]: %s %ux%u", + cap_index, VIDEO_FOURCC_TO_STR(pixelformat), width, height); + + cap_index++; + frames_found++; + } + } else if (frame_header->bDescriptorType == USB_DESC_CS_INTERFACE && + (frame_header->bDescriptorSubType == UVC_VS_FORMAT_UNCOMPRESSED || + frame_header->bDescriptorSubType == UVC_VS_FORMAT_MJPEG)) { + /* Found new format descriptor, stop parsing current format frames */ + break; + } + + desc_buf += frame_header->bLength; + } + + return cap_index; +} + /** * @brief Create video format capabilities from UVC descriptors * @@ -2095,7 +2037,7 @@ static struct video_format_cap* uvc_host_create_format_caps(struct uvc_device *u /* Count frames in uncompressed formats */ for (int i = 0; i < uncompressed_info->num_uncompressed_formats; i++) { - struct uvc_vs_format_uncompressed *format = uncompressed_info->uncompressed_format[i]; + struct uvc_format_uncomp_descriptor *format = uncompressed_info->uncompressed_format[i]; if (format) { total_caps += format->bNumFrameDescriptors; } @@ -2103,7 +2045,7 @@ static struct video_format_cap* uvc_host_create_format_caps(struct uvc_device *u /* Count frames in MJPEG formats */ for (int i = 0; i < mjpeg_info->num_mjpeg_formats; i++) { - struct uvc_vs_format_mjpeg *format = mjpeg_info->vs_mjpeg_format[i]; + struct uvc_format_mjpeg_descriptor *format = mjpeg_info->vs_mjpeg_format[i]; if (format) { total_caps += format->bNumFrameDescriptors; } @@ -2123,9 +2065,9 @@ static struct video_format_cap* uvc_host_create_format_caps(struct uvc_device *u memset(caps_array, 0, sizeof(struct video_format_cap) * (total_caps + 1)); - /* Fill uncompressed formats */ + /* Process uncompressed formats */ for (int i = 0; i < uncompressed_info->num_uncompressed_formats; i++) { - struct uvc_vs_format_uncompressed *format = uncompressed_info->uncompressed_format[i]; + struct uvc_format_uncomp_descriptor *format = uncompressed_info->uncompressed_format[i]; if (!format) continue; /* Get pixel format from GUID */ @@ -2135,93 +2077,23 @@ static struct video_format_cap* uvc_host_create_format_caps(struct uvc_device *u continue; } - /* Parse all frames for this format */ - uint8_t *desc_buf = (uint8_t *)format + format->bLength; - int frames_found = 0; - - while (frames_found < format->bNumFrameDescriptors && cap_index < total_caps) { - struct uvc_frame_header *frame_header = (struct uvc_frame_header *)desc_buf; - - if (frame_header->bLength == 0) break; - - if (frame_header->bDescriptorType == UVC_CS_INTERFACE && - frame_header->bDescriptorSubType == UVC_VS_FRAME_UNCOMPRESSED) { - - if (frame_header->bLength >= sizeof(struct uvc_frame_header)) { - uint16_t width = sys_le16_to_cpu(frame_header->wWidth); - uint16_t height = sys_le16_to_cpu(frame_header->wHeight); - - /* Create format capability entry */ - caps_array[cap_index].pixelformat = pixelformat; - caps_array[cap_index].width_min = width; - caps_array[cap_index].width_max = width; - caps_array[cap_index].height_min = height; - caps_array[cap_index].height_max = height; - caps_array[cap_index].width_step = 0; - caps_array[cap_index].height_step = 0; - - LOG_DBG("Added format cap[%d]: %s %ux%u", - cap_index, VIDEO_FOURCC_TO_STR(pixelformat), width, height); - - cap_index++; - frames_found++; - } - } else if (frame_header->bDescriptorType == UVC_CS_INTERFACE && - (desc_buf[2] == UVC_VS_FORMAT_UNCOMPRESSED || desc_buf[2] == UVC_VS_FORMAT_MJPEG)) { - /* Found new format descriptor, stop parsing current format frames */ - break; - } - - desc_buf += frame_header->bLength; - } + /* Parse frames for this format */ + cap_index = uvc_host_parse_format_frames((uint8_t *)format, format->bLength, + format->bNumFrameDescriptors, pixelformat, + UVC_VS_FRAME_UNCOMPRESSED, caps_array, + cap_index, total_caps); } - /* Fill MJPEG formats */ + /* Process MJPEG formats */ for (int i = 0; i < mjpeg_info->num_mjpeg_formats; i++) { - struct uvc_vs_format_mjpeg *format = mjpeg_info->vs_mjpeg_format[i]; + struct uvc_format_mjpeg_descriptor *format = mjpeg_info->vs_mjpeg_format[i]; if (!format) continue; - /* MJPEG format uses fixed VIDEO_PIX_FMT_MJPEG */ - uint32_t pixelformat = VIDEO_PIX_FMT_MJPEG; - - /* Parse all frames for this format */ - uint8_t *desc_buf = (uint8_t *)format + format->bLength; - int frames_found = 0; - - while (frames_found < format->bNumFrameDescriptors && cap_index < total_caps) { - struct uvc_frame_header *frame_header = (struct uvc_frame_header *)desc_buf; - - if (frame_header->bLength == 0) break; - - if (frame_header->bDescriptorType == UVC_CS_INTERFACE && - frame_header->bDescriptorSubType == UVC_VS_FRAME_MJPEG) { - if (frame_header->bLength >= sizeof(struct uvc_frame_header)) { - uint16_t width = sys_le16_to_cpu(frame_header->wWidth); - uint16_t height = sys_le16_to_cpu(frame_header->wHeight); - - /* Create format capability entry */ - caps_array[cap_index].pixelformat = pixelformat; - caps_array[cap_index].width_min = width; - caps_array[cap_index].width_max = width; - caps_array[cap_index].height_min = height; - caps_array[cap_index].height_max = height; - caps_array[cap_index].width_step = 0; - caps_array[cap_index].height_step = 0; - - LOG_DBG("Added format cap[%d]: %s %ux%u", - cap_index, VIDEO_FOURCC_TO_STR(pixelformat), width, height); - - cap_index++; - frames_found++; - } - } else if (frame_header->bDescriptorType == UVC_CS_INTERFACE && - (desc_buf[2] == UVC_VS_FORMAT_UNCOMPRESSED || desc_buf[2] == UVC_VS_FORMAT_MJPEG)) { - /* Found new format descriptor, stop parsing current format frames */ - break; - } - - desc_buf += frame_header->bLength; - } + /* Parse frames for this format */ + cap_index = uvc_host_parse_format_frames((uint8_t *)format, format->bLength, + format->bNumFrameDescriptors, VIDEO_PIX_FMT_JPEG, + UVC_VS_FRAME_MJPEG, caps_array, + cap_index, total_caps); } LOG_INF("Created %d format capabilities from UVC descriptors", cap_index); @@ -2244,9 +2116,9 @@ static int uvc_host_get_device_caps(struct uvc_device *uvc_dev, struct video_cap } /* Set basic capabilities */ - caps->min_vbuf_count = 1; /* UVC typically needs 1 buffers */ - caps->min_line_count = LINE_COUNT_HEIGHT; /* Only support complete frames */ - caps->max_line_count = LINE_COUNT_HEIGHT; /* Maximum one complete frame */ + caps->min_vbuf_count = 1; + caps->min_line_count = LINE_COUNT_HEIGHT; + caps->max_line_count = LINE_COUNT_HEIGHT; /* Create format capabilities array from UVC descriptors */ if (uvc_dev->video_format_caps) { @@ -2352,6 +2224,107 @@ static int uvc_host_remove_payload_header(struct net_buf *buf, struct video_buff return (int)payload_len; } +/** + * @brief Initiate new video transfer + * + * Allocates and initializes a new USB transfer for video data streaming. + * Sets up transfer buffer and associates it with video buffer. + * + * @param uvc_dev Pointer to uvc class + * @param vbuf Pointer to video buffer for data storage + * @return 0 on success, negative error code on failure + */ +static int uvc_host_initiate_transfer(struct uvc_device *uvc_dev, + struct video_buffer *const vbuf) +{ + struct net_buf *buf; + struct uhc_transfer *xfer; + int ret; + + /* Validate input parameters */ + if (!vbuf || !uvc_dev || !uvc_dev->current_stream_iface_info.current_stream_ep) { + LOG_ERR("Invalid parameters for transfer initiation"); + return -EINVAL; + } + + LOG_DBG("Initiating transfer: ep=0x%02x, vbuf=%p", + uvc_dev->current_stream_iface_info.current_stream_ep->bEndpointAddress, vbuf); + + /* Allocate USB transfer with callback */ + xfer = usbh_xfer_alloc(uvc_dev->udev, uvc_dev->current_stream_iface_info.current_stream_ep->bEndpointAddress, + uvc_host_stream_iso_req_cb, uvc_dev); + if (!xfer) { + LOG_ERR("Failed to allocate transfer"); + return -ENOMEM; + } + + /* Allocate transfer buffer with maximum packet size */ + buf = usbh_xfer_buf_alloc(&uvc_dev->udev, uvc_dev->current_stream_iface_info.cur_ep_mps_mult); + if (!buf) { + LOG_ERR("Failed to allocate buffer"); + usbh_xfer_free(uvc_dev->udev, xfer); + return -ENOMEM; + } + + buf->len = 0; + + /* Reset buffer offset and associate video buffer with transfer */ + uvc_dev->vbuf_offset = 0; + vbuf->bytesused = 0; + memset(vbuf->buffer, 0, vbuf->size); + + /* Save video buffer pointer in transfer's user data */ + *(void **)net_buf_user_data(buf) = vbuf; + xfer->buf = buf; + + ret = usbh_xfer_enqueue(uvc_dev->udev, xfer); + if (ret != 0) { + LOG_ERR("Enqueue failed: ret=%d", ret); + net_buf_unref(buf); + usbh_xfer_free(uvc_dev->udev, xfer); + return ret; + } + + return 0; +} + +/** + * @brief Continue existing video transfer + * @param uvc_dev Pointer to UVC device + * @param xfer Pointer to USB transfer + * @param vbuf Pointer to video buffer + * @return 0 on success, negative on failure + */ +static int uvc_host_continue_transfer(struct uvc_device *uvc_dev, struct uhc_transfer *const xfer, struct video_buffer *vbuf) +{ + struct net_buf *buf; + int ret; + + if (xfer == NULL) { + return -EINVAL; + } + + /* Allocate buffer at offset position for continuation */ + buf = usbh_xfer_buf_alloc(&uvc_dev->udev, uvc_dev->current_stream_iface_info.cur_ep_mps_mult); + if (buf == NULL) { + LOG_ERR("Failed to allocate buffer"); + return -ENOMEM; + } + + buf->len = 0; + *(void **)net_buf_user_data(buf) = vbuf; + xfer->buf = buf; + + ret = usbh_xfer_enqueue(uvc_dev->udev, xfer); + if (ret != 0) { + LOG_ERR("Enqueue failed: ret=%d", ret); + net_buf_unref(buf); + return ret; + } + + return 0; +} + /** * @brief ISO transfer completion callback * @@ -2423,6 +2396,10 @@ static int uvc_host_stream_iso_req_cb(struct usb_device *const dev, struct uhc_t /* Signal frame completion to application */ LOG_DBG("Raising VIDEO_BUF_DONE signal"); k_poll_signal_raise(uvc_dev->sig, VIDEO_BUF_DONE); + if ((vbuf = k_fifo_peek_head(&uvc_dev->fifo_in)) != NULL) { + vbuf->bytesused = 0; + uvc_host_initiate_transfer(uvc_dev, vbuf); + } return 0; } } @@ -2431,162 +2408,10 @@ static int uvc_host_stream_iso_req_cb(struct usb_device *const dev, struct uhc_t /* Release network buffer reference */ net_buf_unref(buf); /* Continue processing pending buffers */ - uvc_host_flush_queue(uvc_dev, vbuf); + uvc_host_continue_transfer(uvc_dev, xfer, vbuf); return 0; } -/** - * @brief Initiate new video transfer - * - * Allocates and initializes a new USB transfer for video data streaming. - * Sets up transfer buffer and associates it with video buffer. - * - * @param uvc_dev Pointer to uvc class - * @param vbuf Pointer to video buffer for data storage - * @return Pointer to allocated transfer on success, NULL on failure - */ -static struct uhc_transfer *uvc_host_initiate_transfer(struct uvc_device *uvc_dev, - struct video_buffer *const vbuf) -{ - struct net_buf *buf; - struct uhc_transfer *xfer; - - /* Validate input parameters */ - if (!vbuf || !uvc_dev || !uvc_dev->current_stream_iface_info.current_stream_ep) { - LOG_ERR("Invalid parameters for transfer initiation"); - return NULL; - } - - LOG_DBG("Initiating transfer: ep=0x%02x, vbuf=%p", - uvc_dev->current_stream_iface_info.current_stream_ep->bEndpointAddress, vbuf); - - /* Allocate USB transfer with callback */ - xfer = usbh_xfer_alloc(uvc_dev->udev, uvc_dev->current_stream_iface_info.current_stream_ep->bEndpointAddress, - uvc_host_stream_iso_req_cb, uvc_dev); - if (!xfer) { - LOG_ERR("Failed to allocate transfer"); - return NULL; - } - - /* Allocate transfer buffer with maximum packet size */ - buf = usbh_xfer_buf_alloc(&uvc_dev->udev, uvc_dev->current_stream_iface_info.cur_ep_mps_mult); - if (!buf) { - LOG_ERR("Failed to allocate buffer"); - usbh_xfer_free(uvc_dev->udev, xfer); - return NULL; - } - buf->len = 0; - - /* Reset buffer offset and associate video buffer with transfer */ - uvc_dev->vbuf_offset = 0; - vbuf->bytesused = 0; - memset(vbuf->buffer, 0, vbuf->size); - /* Save video buffer pointer in transfer's user data */ - *(void **)net_buf_user_data(buf) = vbuf; - xfer->buf = buf; - vbuf->driver_data = (void *)xfer; - - LOG_DBG("Transfer allocated: buf=%p, size=%u", buf, uvc_dev->current_stream_iface_info.cur_ep_mps_mult); - return xfer; -} - -/** - * @brief Continue existing video transfer - * - * Allocates continuation buffer for ongoing video transfer when - * multiple packets are needed for a single video buffer. - * - * @param uvc_dev Pointer to uvc class - * @param vbuf Pointer to video buffer being filled - * @return Pointer to allocated buffer on success, NULL on failure - */ -static struct net_buf *uvc_host_continue_transfer(struct uvc_device *uvc_dev, - struct video_buffer *const vbuf) -{ - struct net_buf *buf; - - /* Allocate buffer at offset position for continuation */ - buf = usbh_xfer_buf_alloc(&uvc_dev->udev, uvc_dev->current_stream_iface_info.cur_ep_mps_mult); - if (buf == NULL) { - LOG_ERR("Failed to allocate buffer"); - return NULL; - } - buf->len = 0; - - *(void **)net_buf_user_data(buf) = vbuf; - - return buf; -} - -/** - * @brief Flush video buffer to USB endpoint - * - * Initiates or continues USB transfer for a video buffer, handling both - * initial transfers and continuation transfers for large frames. - * - * @param uvc_dev Pointer to uvc class - * @param vbuf Pointer to video buffer to transfer - * @return 0 on success, negative error code on failure - */ -static int uvc_host_flush_vbuf(struct uvc_device *uvc_dev, struct video_buffer *const vbuf) -{ - struct net_buf *buf; - struct uhc_transfer *xfer; - int ret; - - if (uvc_dev->transfer_count == 0) { - xfer = uvc_host_initiate_transfer(uvc_dev, vbuf); - } else { - buf = uvc_host_continue_transfer(uvc_dev, vbuf); - xfer = (struct uhc_transfer *)vbuf->driver_data; - xfer->buf = buf; - } - - if (xfer == NULL || xfer->buf == NULL) { - return -ENOMEM; - } - - ret = usbh_xfer_enqueue(uvc_dev->udev, xfer); - if (ret != 0) { - LOG_ERR("Enqueue failed: ret=%d", ret); - net_buf_unref(buf); - return ret; - } - - uvc_dev->transfer_count++; - return 0; -} - -/** - * @brief Process all pending video buffers in input queue - * - * Thread-safe processing of video buffers waiting for USB transfer. - * - * @param uvc_dev Pointer to uvc class - * @param vbuf Pointer to video buffer to flush - * @return 0 on success, negative error code on failure - */ -static int uvc_host_flush_queue(struct uvc_device *uvc_dev, struct video_buffer *vbuf) -{ - int ret = 0; - - /* Lock to ensure atomic processing of buffer queue */ - LOG_DBG("Locking the UVC stream"); - k_mutex_lock(&uvc_dev->lock, K_FOREVER); - - if (uvc_dev->streaming) { - ret = uvc_host_flush_vbuf(uvc_dev, vbuf); - if (ret) { - LOG_ERR("Failed to flush video buffer: %d", ret); - } - } - - LOG_DBG("Unlocking the UVC stream"); - k_mutex_unlock(&uvc_dev->lock); - - return ret; -} - /** * @brief Enumerate frame intervals for a given frame * @@ -2596,7 +2421,7 @@ static int uvc_host_flush_queue(struct uvc_device *uvc_dev, struct video_buffer * @param fie Pointer to frame interval enumeration structure * @return 0 on success, negative error code on failure */ -static int uvc_host_enum_frame_intervals(struct uvc_frame_header *frame_ptr, struct video_frmival_enum *fie) +static int uvc_host_enum_frame_intervals(struct uvc_frame_descriptor *frame_ptr, struct video_frmival_enum *fie) { uint8_t *desc_buf; @@ -2897,7 +2722,7 @@ static int uvc_host_init(struct usbh_class_data *cdata) memset(&uvc_dev->ctrls, 0, sizeof(struct usb_camera_ctrls)); /** Initialize stream interface array */ - for (int i = 0; i < UVC_STREAM_INTERFACES_MAX_ALT; i++) { + for (int i = 0; i < CONFIG_USBH_VIDEO_MAX_STREAM_INTERFACE; i++) { uvc_dev->stream_ifaces[i] = NULL; } @@ -2919,14 +2744,14 @@ static int uvc_host_init(struct usbh_class_data *cdata) /** Initialize format information */ - memset(&uvc_dev->formats, 0, sizeof(struct uvc_vs_format_info)); + memset(&uvc_dev->formats, 0, sizeof(struct uvc_format_info_all)); if (uvc_dev->video_format_caps) { k_free(uvc_dev->video_format_caps); uvc_dev->video_format_caps = NULL; } /** Initialize current format information */ - memset(&uvc_dev->current_format, 0, sizeof(struct uvc_vs_format)); + memset(&uvc_dev->current_format, 0, sizeof(struct uvc_format_info)); LOG_INF("UVC device structure initialized successfully"); return 0; @@ -2941,7 +2766,7 @@ static int uvc_host_init(struct usbh_class_data *cdata) * @param udev USB device * @param cdata USB host class data * @param desc_start_addr Start of descriptor segment - * @param desc_end_addr End of descriptor segment + * @param desc_end_addr End of descriptor segment * @return 0 on success, negative error code on failure */ static int uvc_host_connected(struct usb_device *udev, struct usbh_class_data *cdata, void *desc_start_addr, void *desc_end_addr) @@ -3157,7 +2982,7 @@ static int uvc_host_rwup(struct usbh_context *const uhs_ctx) */ static int video_usb_uvc_host_set_fmt(const struct device *dev, struct video_format *fmt) { - struct uvc_device *uvc_dev = dev->data; /* Get UVC device directly */ + struct uvc_device *uvc_dev = dev->data; int ret; if (!uvc_dev || !uvc_dev->udev) { @@ -3721,49 +3546,57 @@ static int video_usb_uvc_host_set_ctrl(const struct device *dev, struct video_co */ static int video_usb_uvc_host_set_stream(const struct device *dev, bool enable, enum video_buf_type type) { - struct uvc_device *uvc_dev; - uint8_t alt; - uint8_t interface_num; - int ret; + struct uvc_device *uvc_dev; + struct video_buffer *vbuf; + uint8_t alt; + uint8_t interface_num; + int ret; - if (!dev) { - return -EINVAL; - } - - uvc_dev = dev->data; - if (!uvc_dev || !uvc_dev->connected) { - return -ENODEV; - } + if (!dev) { + return -EINVAL; + } - if (enable) { - if (!uvc_dev->current_stream_iface_info.current_stream_iface) { - LOG_ERR("No streaming interface configured"); - return -EINVAL; - } - alt = uvc_dev->current_stream_iface_info.current_stream_iface->bAlternateSetting; - interface_num = uvc_dev->current_stream_iface_info.current_stream_iface->bInterfaceNumber; - } else { - /* For disable, we need a valid interface to set alt setting to 0 */ - if (!uvc_dev->current_stream_iface_info.current_stream_iface) { - LOG_WRN("No interface configured, cannot disable streaming"); - return -EINVAL; - } - alt = 0; - interface_num = uvc_dev->current_stream_iface_info.current_stream_iface->bInterfaceNumber; - } + uvc_dev = dev->data; + if (!uvc_dev || !uvc_dev->connected) { + return -ENODEV; + } - /* Activate streaming interface with negotiated alternate setting */ - ret = usbh_device_interface_set(uvc_dev->udev, interface_num, alt, false); - if (ret) { - LOG_ERR("Failed to set interface %d alt setting %d: %d", interface_num, alt, ret); - return ret; - } + if (enable) { + if (!uvc_dev->current_stream_iface_info.current_stream_iface) { + LOG_ERR("No streaming interface configured"); + return -EINVAL; + } + alt = uvc_dev->current_stream_iface_info.current_stream_iface->bAlternateSetting; + interface_num = uvc_dev->current_stream_iface_info.current_stream_iface->bInterfaceNumber; + } else { + /* For disable, we need a valid interface to set alt setting to 0 */ + if (!uvc_dev->current_stream_iface_info.current_stream_iface) { + LOG_WRN("No interface configured, cannot disable streaming"); + return -EINVAL; + } + alt = 0; + interface_num = uvc_dev->current_stream_iface_info.current_stream_iface->bInterfaceNumber; + } + + /* Activate streaming interface with negotiated alternate setting */ + ret = usbh_device_interface_set(uvc_dev->udev, interface_num, alt, false); + if (ret) { + LOG_ERR("Failed to set interface %d alt setting %d: %d", interface_num, alt, ret); + return ret; + } - /* Update streaming state only after successful USB operation */ - uvc_dev->streaming = enable; + /* Update streaming state only after successful USB operation */ + uvc_dev->streaming = enable; + + if (enable) + { + if ((vbuf = k_fifo_peek_head(&uvc_dev->fifo_in)) != NULL) { + uvc_host_initiate_transfer(uvc_dev, vbuf); + } + } - LOG_DBG("UVC streaming %s successfully", enable ? "enabled" : "disabled"); - return 0; + LOG_DBG("UVC streaming %s successfully", enable ? "enabled" : "disabled"); + return 0; } /** @@ -3778,16 +3611,16 @@ static int video_usb_uvc_host_set_stream(const struct device *dev, bool enable, static int video_usb_uvc_host_enqueue(const struct device *dev, struct video_buffer *vbuf) { struct uvc_device *uvc_dev; - int ret; + int ret; - if (!dev) { - return -EINVAL; - } + if (!dev) { + return -EINVAL; + } - uvc_dev = dev->data; - if (!uvc_dev || !uvc_dev->connected) { - return -ENODEV; - } + uvc_dev = dev->data; + if (!uvc_dev || !uvc_dev->connected) { + return -ENODEV; + } /* Initialize buffer state for new capture */ vbuf->bytesused = 0; @@ -3795,10 +3628,6 @@ static int video_usb_uvc_host_enqueue(const struct device *dev, struct video_buf vbuf->line_offset = 0; k_fifo_put(&uvc_dev->fifo_in, vbuf); - ret = uvc_host_flush_queue(uvc_dev, vbuf); - if (ret) { - return ret; - } } /** @@ -3818,14 +3647,14 @@ static int video_usb_uvc_host_dequeue(const struct device *dev, struct video_buf struct uhc_transfer *xfer; int ret; - if (!dev) { - return -EINVAL; - } + if (!dev) { + return -EINVAL; + } - uvc_dev = dev->data; - if (!uvc_dev) { - return -ENODEV; - } + uvc_dev = dev->data; + if (!uvc_dev) { + return -ENODEV; + } *vbuf = k_fifo_get(&uvc_dev->fifo_out, timeout); if (*vbuf == NULL) { @@ -3833,7 +3662,6 @@ static int video_usb_uvc_host_dequeue(const struct device *dev, struct video_buf } if (!uvc_dev->connected) { - xfer = (*vbuf)->driver_data; ret = usbh_xfer_dequeue(uvc_dev->udev, xfer); if (ret != 0) { return ret; @@ -3877,13 +3705,14 @@ static DEVICE_API(video, uvc_host_video_api) = { #define USBH_VIDEO_DT_DEVICE_DEFINE(n) \ static struct uvc_device uvc_device_##n; \ - DEVICE_DT_INST_DEFINE(n, uvc_host_preinit, NULL, \ - &uvc_device_##n, NULL, \ + DEVICE_DT_INST_DEFINE(n, uvc_host_preinit, NULL, \ + &uvc_device_##n, NULL, \ POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, \ &uvc_host_video_api); \ USBH_DEFINE_CLASS(uvc_host_c_data_##n, &uvc_host_class_api, \ - (void *)DEVICE_DT_INST_GET(n), \ - uvc_device_code, 2); \ + (void *)DEVICE_DT_INST_GET(n), \ + uvc_device_code, 2); \ VIDEO_DEVICE_DEFINE(usb_camera_##n, (void *)DEVICE_DT_INST_GET(n), NULL); DT_INST_FOREACH_STATUS_OKAY(USBH_VIDEO_DT_DEVICE_DEFINE) + diff --git a/subsys/usb/host/class/usbh_uvc.h b/subsys/usb/host/class/usbh_uvc.h index f274df94d63b6..14f2a2cfe9ed2 100644 --- a/subsys/usb/host/class/usbh_uvc.h +++ b/subsys/usb/host/class/usbh_uvc.h @@ -15,562 +15,6 @@ #include -/* Video Interface Class Codes */ -#define UVC_SC_VIDEOCLASS 0x0E - -/* Video Interface Subclass Codes */ -#define UVC_SC_VIDEOCONTROL 0x01 -#define UVC_SC_VIDEOSTREAMING 0x02 -#define UVC_SC_VIDEO_INTERFACE_COLLECTION 0x03 - -#define UVC_CS_INTERFACE 0x24 -#define UVC_CS_ENDPOINT 0x25 - -/* 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 Streaming 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_CONTROL_UNDEFINED 0x00 -#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_CONTROL_UNDEFINED 0x00 -#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 - -/* Video Class-Specific Request Code */ -#define UVC_RC_UNDEFINED 0x00 -#define UVC_SET_CUR 0x01 -#define UVC_SET_CUR_ALL 0x11 -#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 -#define UVC_GET_CUR_ALL 0x91 -#define UVC_GET_MIN_ALL 0x92 -#define UVC_GET_MAX_ALL 0x93 -#define UVC_GET_RES_ALL 0x94 -#define UVC_GET_DEF_ALL 0x97 - -/* Processing Unit Control Bit Positions (for bmControls bitmap) */ -#define UVC_PU_BMCONTROL_BRIGHTNESS BIT(0) -#define UVC_PU_BMCONTROL_CONTRAST BIT(1) -#define UVC_PU_BMCONTROL_HUE BIT(2) -#define UVC_PU_BMCONTROL_SATURATION BIT(3) -#define UVC_PU_BMCONTROL_SHARPNESS BIT(4) -#define UVC_PU_BMCONTROL_GAMMA BIT(5) -#define UVC_PU_BMCONTROL_WHITE_BALANCE_TEMPERATURE BIT(6) -#define UVC_PU_BMCONTROL_WHITE_BALANCE_COMPONENT BIT(7) -#define UVC_PU_BMCONTROL_BACKLIGHT_COMPENSATION BIT(8) -#define UVC_PU_BMCONTROL_GAIN BIT(9) -#define UVC_PU_BMCONTROL_POWER_LINE_FREQUENCY BIT(10) -#define UVC_PU_BMCONTROL_HUE_AUTO BIT(11) -#define UVC_PU_BMCONTROL_WHITE_BALANCE_TEMPERATURE_AUTO BIT(12) -#define UVC_PU_BMCONTROL_WHITE_BALANCE_COMPONENT_AUTO BIT(13) -#define UVC_PU_BMCONTROL_DIGITAL_MULTIPLIER BIT(14) -#define UVC_PU_BMCONTROL_DIGITAL_MULTIPLIER_LIMIT BIT(15) -#define UVC_PU_BMCONTROL_ANALOG_VIDEO_STANDARD BIT(16) -#define UVC_PU_BMCONTROL_ANALOG_LOCK_STATUS BIT(17) -#define UVC_PU_BMCONTROL_CONTRAST_AUTO BIT(18) -/* Bits 19-23 are reserved for future use */ - -/* Camera Terminal Control Bit Positions (for bmControls bitmap) */ -#define UVC_CT_BMCONTROL_SCANNING_MODE BIT(0) -#define UVC_CT_BMCONTROL_AE_MODE BIT(1) -#define UVC_CT_BMCONTROL_AE_PRIORITY BIT(2) -#define UVC_CT_BMCONTROL_EXPOSURE_TIME_ABSOLUTE BIT(3) -#define UVC_CT_BMCONTROL_EXPOSURE_TIME_RELATIVE BIT(4) -#define UVC_CT_BMCONTROL_FOCUS_ABSOLUTE BIT(5) -#define UVC_CT_BMCONTROL_FOCUS_RELATIVE BIT(6) -#define UVC_CT_BMCONTROL_IRIS_ABSOLUTE BIT(7) -#define UVC_CT_BMCONTROL_IRIS_RELATIVE BIT(8) -#define UVC_CT_BMCONTROL_ZOOM_ABSOLUTE BIT(9) -#define UVC_CT_BMCONTROL_ZOOM_RELATIVE BIT(10) -#define UVC_CT_BMCONTROL_PAN_TILT_ABSOLUTE BIT(11) -#define UVC_CT_BMCONTROL_PAN_TILT_RELATIVE BIT(12) -#define UVC_CT_BMCONTROL_ROLL_ABSOLUTE BIT(13) -#define UVC_CT_BMCONTROL_ROLL_RELATIVE BIT(14) -/* Bits 15-16 are reserved */ -#define UVC_CT_BMCONTROL_FOCUS_AUTO BIT(17) -#define UVC_CT_BMCONTROL_PRIVACY BIT(18) -#define UVC_CT_BMCONTROL_FOCUS_SIMPLE BIT(19) -#define UVC_CT_BMCONTROL_WINDOW BIT(20) -#define UVC_CT_BMCONTROL_REGION_OF_INTEREST BIT(21) -/* Bits 22-23 are reserved for future use */ - -/* 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) - -/* Maximum number of MJPEG formats */ -#ifndef UVC_MAX_MJPEG_FORMAT -#define UVC_MAX_MJPEG_FORMAT 8 -#endif - -/* Maximum number of uncompressed formats */ -#ifndef UVC_MAX_UNCOMPRESSED_FORMAT -#define UVC_MAX_UNCOMPRESSED_FORMAT 8 -#endif - -/* Maximum number of stream interfaces */ -#define UVC_STREAM_INTERFACES_MAX_ALT 32 - -/* MJPEG alias definition for code consistency */ -#ifndef VIDEO_PIX_FMT_MJPEG -#define VIDEO_PIX_FMT_MJPEG VIDEO_PIX_FMT_JPEG -#endif - -/* Maximum frame intervals configuration */ -#ifndef CONFIG_USBH_UVC_MAX_FRAME_INTERVALS -#define CONFIG_USBH_UVC_MAX_FRAME_INTERVALS 8 -#endif - -/* Standard Video Interface Collection IAD */ -struct usb_interface_association_descriptor { - uint8_t bLength; - uint8_t bDescriptorType; /* INTERFACE_ASSOCIATION (0x0B) */ - uint8_t bFirstInterface; - uint8_t bInterfaceCount; - uint8_t bFunctionClass; /* UVC_SC_VIDEOCLASS (0x0E) */ - uint8_t bFunctionSubClass; /* UVC_SC_VIDEO_INTERFACE_COLLECTION (0x03) */ - uint8_t bFunctionProtocol; - uint8_t iFunction; -} __packed; - -/* UVC VideoStreaming Input Header Descriptor */ -struct uvc_vs_input_header_descriptor { - uint8_t bLength; - uint8_t bDescriptorType; /* CS_INTERFACE (0x24) */ - uint8_t bDescriptorSubType; /* VS_INPUT_HEADER (0x01) */ - 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; - uint8_t bmControls[]; /* Variable length control bitmap */ -} __packed; - -/* UVC VideoStreaming Output Header Descriptor */ -struct uvc_vs_output_header_descriptor { - uint8_t bLength; - uint8_t bDescriptorType; /* CS_INTERFACE (0x24) */ - uint8_t bDescriptorSubType; /* VS_OUTPUT_HEADER (0x02) */ - uint8_t bNumFormats; - uint16_t wTotalLength; - uint8_t bEndpointAddress; - uint8_t bTerminalLink; - uint8_t bControlSize; - uint8_t bmControls[]; /* Variable length control bitmap */ -} __packed; - -/* UVC VideoStreaming Uncompressed Format Descriptor */ -struct uvc_vs_format_uncompressed { - 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; - -/* UVC VideoStreaming MJPEG Format Descriptor */ -struct uvc_vs_format_mjpeg { - uint8_t bLength; - uint8_t bDescriptorType; - uint8_t bDescriptorSubType; - uint8_t bFormatIndex; - uint8_t bNumFrameDescriptors; - uint8_t bmFlags; - uint8_t bDefaultFrameIndex; - uint8_t bAspectRatioX; - uint8_t bAspectRatioY; - uint8_t bmInterlaceFlags; - uint8_t bCopyProtect; -} __packed; - -/* UVC VideoStreaming Uncompressed Frame Descriptor */ -struct uvc_vs_frame_uncompressed { - 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; - /* Followed by frame interval data */ -} __packed; - -/* UVC VideoStreaming MJPEG Frame Descriptor */ -struct uvc_vs_frame_mjpeg { - 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; - /* Followed by frame interval data */ -} __packed; - -/* UVC Format Descriptor Common Header */ -struct uvc_format_header { - uint8_t bLength; - uint8_t bDescriptorType; - uint8_t bDescriptorSubType; - uint8_t bFormatIndex; - uint8_t bNumFrameDescriptors; -} __packed; - -/* UVC Frame Descriptor Common Header */ -struct uvc_frame_header { - uint8_t bLength; - uint8_t bDescriptorType; - uint8_t bDescriptorSubType; - uint8_t bFrameIndex; - uint8_t bmCapabilities; - uint16_t wWidth; - uint16_t wHeight; -} __packed; - -/** Header of an USB CS descriptor */ -struct usb_cs_desc_header { - uint8_t bLength; - uint8_t bDescriptorType; - uint8_t bDescriptorSubType; -} __packed; - -/* UVC VideoControl Interface Header Descriptor */ -struct uvc_vc_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[]; -} __packed; - -/* UVC VideoControl Input Terminal Descriptor */ -struct uvc_vc_input_terminal_descriptor { - uint8_t bLength; - uint8_t bDescriptorType; /* CS_INTERFACE (0x24) */ - uint8_t bDescriptorSubType; /* VC_INPUT_TERMINAL (0x02) */ - uint8_t bTerminalID; - uint16_t wTerminalType; - uint8_t bAssocTerminal; - uint8_t iTerminal; -} __packed; - -/* UVC VideoControl Output Terminal Descriptor */ -struct uvc_vc_output_terminal_descriptor { - uint8_t bLength; - uint8_t bDescriptorType; /* CS_INTERFACE (0x24) */ - uint8_t bDescriptorSubType; /* VC_OUTPUT_TERMINAL (0x03) */ - uint8_t bTerminalID; - uint16_t wTerminalType; - uint8_t bAssocTerminal; - uint8_t bSourceID; - uint8_t iTerminal; -} __packed; - -/* UVC VideoControl Camera Terminal Descriptor */ -struct uvc_vc_camera_terminal_descriptor { - uint8_t bLength; - uint8_t bDescriptorType; /* CS_INTERFACE (0x24) */ - uint8_t bDescriptorSubType; /* VC_INPUT_TERMINAL (0x02) */ - uint8_t bTerminalID; - uint16_t wTerminalType; /* Should be 0x0201 for Camera Terminal */ - uint8_t bAssocTerminal; - uint8_t iTerminal; - uint16_t wObjectiveFocalLengthMin; - uint16_t wObjectiveFocalLengthMax; - uint16_t wOcularFocalLength; - uint8_t bControlSize; - uint8_t bmControls[]; /* Variable length control bitmap */ -} __packed; - -/* UVC VideoControl Selector Unit Descriptor */ -struct uvc_vc_selector_unit_descriptor { - uint8_t bLength; - uint8_t bDescriptorType; /* CS_INTERFACE (0x24) */ - uint8_t bDescriptorSubType; /* VC_SELECTOR_UNIT (0x04) */ - uint8_t bUnitID; - uint8_t bNrInPins; - uint8_t baSourceID[]; /* Variable length array of source IDs */ - /* Note: iSelector field follows baSourceID but is not included here - * due to variable length baSourceID array */ -} __packed; - -/* UVC VideoControl Processing Unit Descriptor */ -struct uvc_vc_processing_unit_descriptor { - uint8_t bLength; - uint8_t bDescriptorType; /* CS_INTERFACE (0x24) */ - uint8_t bDescriptorSubType; /* VC_PROCESSING_UNIT (0x05) */ - uint8_t bUnitID; - uint8_t bSourceID; - uint16_t wMaxMultiplier; - uint8_t bControlSize; - uint8_t bmControls[]; /* Variable length control bitmap */ - /* Note: iProcessing field follows bmControls but is not included here - * due to variable length bmControls array */ -} __packed; - -/* UVC VideoControl Encoding Unit Descriptor */ -struct uvc_vc_encoding_unit_descriptor { - uint8_t bLength; - uint8_t bDescriptorType; /* CS_INTERFACE (0x24) */ - uint8_t bDescriptorSubType; /* VC_ENCODING_UNIT (0x07) */ - uint8_t bUnitID; - uint8_t bSourceID; - uint8_t iEncoding; - uint8_t bControlSize; - uint8_t bmControls[]; /* Variable length control bitmap */ - /* Note: bmControlsRuntime field may follow bmControls depending on version */ -} __packed; - -/* UVC VideoControl Extension Unit Descriptor */ -struct uvc_vc_extension_unit_descriptor { - uint8_t bLength; - uint8_t bDescriptorType; /* CS_INTERFACE (0x24) */ - uint8_t bDescriptorSubType; /* VC_EXTENSION_UNIT (0x06) */ - uint8_t bUnitID; - uint8_t guidExtensionCode[16]; /* GUID identifying the Extension Unit */ - uint8_t bNumControls; - uint8_t bNrInPins; - uint8_t baSourceID[]; /* Variable length array of source IDs */ - /* Note: bControlSize, bmControls[], and iExtension fields follow baSourceID - * but are not included here due to variable length baSourceID array */ -} __packed; - -/* UVC Payload Header */ -struct uvc_payload_header { - uint8_t bHeaderLength; - uint8_t bmHeaderInfo; - uint32_t dwPresentationTime; - uint8_t scrSourceClock[6]; -} __packed; - -/* Video Probe and Commit Controls */ -struct uvc_probe_commit { - 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; -} __packed; - -/* Video Still Probe Control and Still Commit Control */ -struct uvc_still_probe_commit { - uint8_t bFormatIndex; /* Still image format index */ - uint8_t bFrameIndex; /* Still image frame index */ - uint8_t bCompressionIndex; /* Compression level index */ - uint32_t dwMaxVideoFrameSize; /* Maximum image size */ - uint32_t dwMaxPayloadTransferSize; /* Maximum transfer payload */ -} __packed; - - -/* UVC frame interval types */ -enum uvc_frame_interval_type { - UVC_FRAME_INTERVAL_DISCRETE, - UVC_FRAME_INTERVAL_CONTINUOUS, - UVC_FRAME_INTERVAL_STEPWISE, -}; - -/* UVC frame information structure */ -struct uvc_frame_info { - uint8_t frame_index; - uint16_t width; - uint16_t height; - uint32_t min_bit_rate; - uint32_t max_bit_rate; - uint32_t max_frame_buffer_size; - uint32_t default_frame_interval; - - enum uvc_frame_interval_type interval_type; - uint8_t num_intervals; - union { - uint32_t discrete[CONFIG_USBH_UVC_MAX_FRAME_INTERVALS]; - struct { - uint32_t min; - uint32_t max; - uint32_t step; - } stepwise; - } intervals; -}; - struct uvc_stream_iface_info { struct usb_if_descriptor *current_stream_iface; /* Stream interface */ struct usb_ep_descriptor *current_stream_ep; /* Stream endpoint */ @@ -578,7 +22,7 @@ struct uvc_stream_iface_info { }; /* UVC format structure */ -struct uvc_vs_format { +struct uvc_format_info { uint32_t pixelformat; uint16_t width; uint16_t height; @@ -592,17 +36,17 @@ struct uvc_vs_format { }; struct uvc_vs_format_mjpeg_info { - struct uvc_vs_format_mjpeg *vs_mjpeg_format[UVC_MAX_MJPEG_FORMAT]; + struct uvc_vs_format_mjpeg *vs_mjpeg_format[CONFIG_USBH_VIDEO_MAX_FORMATS]; uint8_t num_mjpeg_formats; }; struct uvc_vs_format_uncompressed_info { - struct uvc_vs_format_uncompressed *uncompressed_format[UVC_MAX_UNCOMPRESSED_FORMAT]; + struct uvc_vs_format_uncompressed *uncompressed_format[CONFIG_USBH_VIDEO_MAX_FORMATS]; uint8_t num_uncompressed_formats; }; /* Video stream format information structure */ -struct uvc_vs_format_info { +struct uvc_format_info_all { struct uvc_vs_format_mjpeg_info format_mjpeg; struct uvc_vs_format_uncompressed_info format_uncompressed; }; From 1ce0e22bc04aacc1b42cb6dc832e31937c7b30ad Mon Sep 17 00:00:00 2001 From: Aiden Hu Date: Wed, 20 Aug 2025 20:07:52 +0800 Subject: [PATCH 26/31] usb: uvc: add more contents for the header used by host and device Some necessary definitions are added. Do some improvements for the existing content based on the UVC spec. Signed-off-by: Aiden Hu --- include/zephyr/usb/class/usb_uvc.h | 116 ++++++++++++++++++++++++++--- 1 file changed, 105 insertions(+), 11 deletions(-) diff --git a/include/zephyr/usb/class/usb_uvc.h b/include/zephyr/usb/class/usb_uvc.h index b737efc5c7e2d..aaa3bc57c26c8 100644 --- a/include/zephyr/usb/class/usb_uvc.h +++ b/include/zephyr/usb/class/usb_uvc.h @@ -16,20 +16,26 @@ * - 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_ +#ifndef ZEPHYR_INCLUDE_USB_CLASS_UVC_H_ +#define ZEPHYR_INCLUDE_USB_CLASS_UVC_H_ #include /* Video Class-Specific Request Codes */ #define UVC_SET_CUR 0x01 #define UVC_GET_CUR 0x81 +#define UVC_SET_CUR_ALL 0x11 #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_INFO 0x86 #define UVC_GET_DEF 0x87 +#define UVC_GET_CUR_ALL 0x91 +#define UVC_GET_MIN_ALL 0x92 +#define UVC_GET_MAX_ALL 0x93 +#define UVC_GET_RES_ALL 0x94 +#define UVC_GET_DEF_ALL 0x97 /* Flags announcing which controls are supported */ #define UVC_INFO_SUPPORTS_GET BIT(0) @@ -55,6 +61,8 @@ #define UVC_BMHEADERINFO_ERROR BIT(6) #define UVC_BMHEADERINFO_END_OF_HEADER BIT(7) +/* Video Interface Class Codes */ +#define UVC_SC_VIDEOCLASS 0x0E /* Video Interface Subclass Codes */ #define UVC_SC_VIDEOCONTROL 0x01 #define UVC_SC_VIDEOSTREAMING 0x02 @@ -119,6 +127,7 @@ #define UVC_EXT_COMPONENT_CONNECTOR 0x0403 /* VideoStreaming Interface Controls */ +#define UVC_VS_CONTROL_UNDEFINED 0x00 #define UVC_VS_PROBE_CONTROL 0x01 #define UVC_VS_COMMIT_CONTROL 0x02 #define UVC_VS_STILL_PROBE_CONTROL 0x03 @@ -138,6 +147,7 @@ #define UVC_SU_INPUT_SELECT_CONTROL 0x01 /* Camera Terminal Controls */ +#define UVC_CT_CONTROL_UNDEFINED 0x00 #define UVC_CT_SCANNING_MODE_CONTROL 0x01 #define UVC_CT_AE_MODE_CONTROL 0x02 #define UVC_CT_AE_PRIORITY_CONTROL 0x03 @@ -202,6 +212,52 @@ #define UVC_EU_START_OR_STOP_LAYER_CONTROL 0x13 #define UVC_EU_ERROR_RESILIENCY_CONTROL 0x14 +/* Processing Unit Control Bit Positions (for bmControls bitmap) */ +#define UVC_PU_BMCONTROL_BRIGHTNESS BIT(0) +#define UVC_PU_BMCONTROL_CONTRAST BIT(1) +#define UVC_PU_BMCONTROL_HUE BIT(2) +#define UVC_PU_BMCONTROL_SATURATION BIT(3) +#define UVC_PU_BMCONTROL_SHARPNESS BIT(4) +#define UVC_PU_BMCONTROL_GAMMA BIT(5) +#define UVC_PU_BMCONTROL_WHITE_BALANCE_TEMPERATURE BIT(6) +#define UVC_PU_BMCONTROL_WHITE_BALANCE_COMPONENT BIT(7) +#define UVC_PU_BMCONTROL_BACKLIGHT_COMPENSATION BIT(8) +#define UVC_PU_BMCONTROL_GAIN BIT(9) +#define UVC_PU_BMCONTROL_POWER_LINE_FREQUENCY BIT(10) +#define UVC_PU_BMCONTROL_HUE_AUTO BIT(11) +#define UVC_PU_BMCONTROL_WHITE_BALANCE_TEMPERATURE_AUTO BIT(12) +#define UVC_PU_BMCONTROL_WHITE_BALANCE_COMPONENT_AUTO BIT(13) +#define UVC_PU_BMCONTROL_DIGITAL_MULTIPLIER BIT(14) +#define UVC_PU_BMCONTROL_DIGITAL_MULTIPLIER_LIMIT BIT(15) +#define UVC_PU_BMCONTROL_ANALOG_VIDEO_STANDARD BIT(16) +#define UVC_PU_BMCONTROL_ANALOG_LOCK_STATUS BIT(17) +#define UVC_PU_BMCONTROL_CONTRAST_AUTO BIT(18) +/* Bits 19-23 are reserved for future use */ + +/* Camera Terminal Control Bit Positions (for bmControls bitmap) */ +#define UVC_CT_BMCONTROL_SCANNING_MODE BIT(0) +#define UVC_CT_BMCONTROL_AE_MODE BIT(1) +#define UVC_CT_BMCONTROL_AE_PRIORITY BIT(2) +#define UVC_CT_BMCONTROL_EXPOSURE_TIME_ABSOLUTE BIT(3) +#define UVC_CT_BMCONTROL_EXPOSURE_TIME_RELATIVE BIT(4) +#define UVC_CT_BMCONTROL_FOCUS_ABSOLUTE BIT(5) +#define UVC_CT_BMCONTROL_FOCUS_RELATIVE BIT(6) +#define UVC_CT_BMCONTROL_IRIS_ABSOLUTE BIT(7) +#define UVC_CT_BMCONTROL_IRIS_RELATIVE BIT(8) +#define UVC_CT_BMCONTROL_ZOOM_ABSOLUTE BIT(9) +#define UVC_CT_BMCONTROL_ZOOM_RELATIVE BIT(10) +#define UVC_CT_BMCONTROL_PAN_TILT_ABSOLUTE BIT(11) +#define UVC_CT_BMCONTROL_PAN_TILT_RELATIVE BIT(12) +#define UVC_CT_BMCONTROL_ROLL_ABSOLUTE BIT(13) +#define UVC_CT_BMCONTROL_ROLL_RELATIVE BIT(14) +/* Bits 15-16 are reserved */ +#define UVC_CT_BMCONTROL_FOCUS_AUTO BIT(17) +#define UVC_CT_BMCONTROL_PRIVACY BIT(18) +#define UVC_CT_BMCONTROL_FOCUS_SIMPLE BIT(19) +#define UVC_CT_BMCONTROL_WINDOW BIT(20) +#define UVC_CT_BMCONTROL_REGION_OF_INTEREST BIT(21) +/* Bits 22-23 are reserved for future use */ + /* Extension Unit Controls */ #define UVC_XU_BASE_CONTROL 0x00 @@ -222,7 +278,7 @@ struct uvc_control_header_descriptor { uint16_t wTotalLength; uint32_t dwClockFrequency; uint8_t bInCollection; - uint8_t baInterfaceNr[1]; + uint8_t baInterfaceNr[]; } __packed; struct uvc_unit_descriptor { @@ -232,6 +288,16 @@ struct uvc_unit_descriptor { uint8_t bUnitID; }; +struct uvc_input_terminal_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bTerminalID; + uint16_t wTerminalType; + uint8_t bAssocTerminal; + uint8_t iTerminal; +} __packed; + struct uvc_output_terminal_descriptor { uint8_t bLength; uint8_t bDescriptorType; @@ -307,10 +373,10 @@ struct uvc_extension_unit_descriptor { uint8_t iExtension; } __packed; -struct uvc_stream_header_descriptor { +struct uvc_stream_input_header_descriptor { uint8_t bLength; uint8_t bDescriptorType; - uint8_t bDescriptorSubtype; + uint8_t bDescriptorSubType; uint8_t bNumFormats; uint16_t wTotalLength; uint8_t bEndpointAddress; @@ -320,6 +386,19 @@ struct uvc_stream_header_descriptor { uint8_t bTriggerSupport; uint8_t bTriggerUsage; uint8_t bControlSize; + uint8_t bmControls[]; +} __packed; + +struct uvc_stream_output_header_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bNumFormats; + uint16_t wTotalLength; + uint8_t bEndpointAddress; + uint8_t bTerminalLink; + uint8_t bControlSize; + uint8_t bmControls[]; } __packed; struct uvc_frame_still_image_descriptor { @@ -348,7 +427,7 @@ struct uvc_format_descriptor { struct uvc_format_uncomp_descriptor { uint8_t bLength; uint8_t bDescriptorType; - uint8_t bDescriptorSubtype; + uint8_t bDescriptorSubType; uint8_t bFormatIndex; uint8_t bNumFrameDescriptors; uint8_t guidFormat[16]; @@ -363,7 +442,7 @@ struct uvc_format_uncomp_descriptor { struct uvc_format_mjpeg_descriptor { uint8_t bLength; uint8_t bDescriptorType; - uint8_t bDescriptorSubtype; + uint8_t bDescriptorSubType; uint8_t bFormatIndex; uint8_t bNumFrameDescriptors; uint8_t bmFlags; @@ -375,10 +454,18 @@ struct uvc_format_mjpeg_descriptor { uint8_t bCopyProtect; } __packed; +struct uvc_format_descriptor_header { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bFormatIndex; + uint8_t bNumFrameDescriptors; +} __packed; + struct uvc_frame_descriptor { uint8_t bLength; uint8_t bDescriptorType; - uint8_t bDescriptorSubtype; + uint8_t bDescriptorSubType; uint8_t bFrameIndex; uint8_t bmCapabilities; uint16_t wWidth; @@ -394,7 +481,7 @@ struct uvc_frame_descriptor { struct uvc_frame_continuous_descriptor { uint8_t bLength; uint8_t bDescriptorType; - uint8_t bDescriptorSubtype; + uint8_t bDescriptorSubType; uint8_t bFrameIndex; uint8_t bmCapabilities; uint16_t wWidth; @@ -422,6 +509,7 @@ struct uvc_frame_discrete_descriptor { uint32_t dwMaxVideoFrameBufferSize; uint32_t dwDefaultFrameInterval; uint8_t bFrameIntervalType; + /* TODO: commonly configurable frame interval ( suggestion: uint32_t dwFrameInterval[]; )*/ #ifdef CONFIG_USBD_VIDEO_MAX_FRMIVAL uint32_t dwFrameInterval[CONFIG_USBD_VIDEO_MAX_FRMIVAL]; #else @@ -429,6 +517,12 @@ struct uvc_frame_discrete_descriptor { #endif } __packed; +struct uvc_cs_descriptor_header { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; +} __packed; + struct uvc_color_descriptor { uint8_t bLength; uint8_t bDescriptorType; @@ -485,4 +579,4 @@ struct uvc_payload_header { uint16_t scrSourceClockSOF; /* optional */ } __packed; -#endif /* ZEPHYR_INCLUDE_USBD_CLASS_UVC_H_ */ +#endif /* ZEPHYR_INCLUDE_USB_CLASS_UVC_H_ */ From d12370e8493a30dd145507f30b584563d05a6419 Mon Sep 17 00:00:00 2001 From: Aiden Hu Date: Thu, 4 Sep 2025 15:08:41 +0800 Subject: [PATCH 27/31] subsys: usb: host: enhance host video stream transfer Adjust video stream callback function to be more robust. Use multiple primes to improve the throughput. Signed-off-by: Aiden Hu --- subsys/usb/host/class/usbh_uvc.c | 287 ++++++++++++++++++++----------- 1 file changed, 187 insertions(+), 100 deletions(-) diff --git a/subsys/usb/host/class/usbh_uvc.c b/subsys/usb/host/class/usbh_uvc.c index accc181aef80f..e617f8490ce12 100644 --- a/subsys/usb/host/class/usbh_uvc.c +++ b/subsys/usb/host/class/usbh_uvc.c @@ -128,6 +128,16 @@ struct uvc_device { struct k_poll_signal *sig; /** Byte offset within the currently transmitted video buffer */ size_t vbuf_offset; + /** Current video buffer being filled */ + struct video_buffer *current_vbuf; + /** Expected frame ID for synchronization */ + uint8_t expect_frame_id; + /** Flag to discard first incomplete frame */ + uint8_t discard_first_frame; + /** Count of discarded frames */ + uint32_t discard_frame_cnt; + /** Number of transfers to prime initially */ + uint8_t multi_prime_cnt; /** Number of completed transfers for current frame */ size_t transfer_count; /** USB camera control parameters */ @@ -2235,57 +2245,52 @@ static int uvc_host_remove_payload_header(struct net_buf *buf, struct video_buff * @return 0 on success, negative error code on failure */ static int uvc_host_initiate_transfer(struct uvc_device *uvc_dev, - struct video_buffer *const vbuf) + struct video_buffer *const vbuf) { - struct net_buf *buf; - struct uhc_transfer *xfer; - int ret; - - /* Validate input parameters */ - if (!vbuf || !uvc_dev || !uvc_dev->current_stream_iface_info.current_stream_ep) { - LOG_ERR("Invalid parameters for transfer initiation"); - return -EINVAL; - } - - LOG_DBG("Initiating transfer: ep=0x%02x, vbuf=%p", - uvc_dev->current_stream_iface_info.current_stream_ep->bEndpointAddress, vbuf); - - /* Allocate USB transfer with callback */ - xfer = usbh_xfer_alloc(uvc_dev->udev, uvc_dev->current_stream_iface_info.current_stream_ep->bEndpointAddress, - uvc_host_stream_iso_req_cb, uvc_dev); - if (!xfer) { - LOG_ERR("Failed to allocate transfer"); - return -ENOMEM; - } - - /* Allocate transfer buffer with maximum packet size */ - buf = usbh_xfer_buf_alloc(&uvc_dev->udev, uvc_dev->current_stream_iface_info.cur_ep_mps_mult); - if (!buf) { - LOG_ERR("Failed to allocate buffer"); - usbh_xfer_free(uvc_dev->udev, xfer); - return -ENOMEM; - } - - buf->len = 0; - - /* Reset buffer offset and associate video buffer with transfer */ - uvc_dev->vbuf_offset = 0; - vbuf->bytesused = 0; - memset(vbuf->buffer, 0, vbuf->size); - - /* Save video buffer pointer in transfer's user data */ - *(void **)net_buf_user_data(buf) = vbuf; - xfer->buf = buf; - - ret = usbh_xfer_enqueue(uvc_dev->udev, xfer); - if (ret != 0) { - LOG_ERR("Enqueue failed: ret=%d", ret); - net_buf_unref(buf); - usbh_xfer_free(uvc_dev->udev, xfer); - return ret; - } - - return 0; + struct net_buf *buf; + struct uhc_transfer *xfer; + int ret; + + /* Validate input parameters */ + if (!vbuf || !uvc_dev || !uvc_dev->current_stream_iface_info.current_stream_ep) { + LOG_ERR("Invalid parameters for transfer initiation"); + return -EINVAL; + } + + LOG_DBG("Initiating transfer: ep=0x%02x, vbuf=%p", + uvc_dev->current_stream_iface_info.current_stream_ep->bEndpointAddress, vbuf); + + /* Allocate USB transfer with callback */ + xfer = usbh_xfer_alloc(uvc_dev->udev, + uvc_dev->current_stream_iface_info.current_stream_ep->bEndpointAddress, + uvc_host_stream_iso_req_cb, uvc_dev); + if (!xfer) { + LOG_ERR("Failed to allocate transfer"); + return -ENOMEM; + } + + /* Allocate transfer buffer with maximum packet size */ + buf = usbh_xfer_buf_alloc(&uvc_dev->udev, + uvc_dev->current_stream_iface_info.cur_ep_mps_mult); + if (!buf) { + LOG_ERR("Failed to allocate buffer"); + usbh_xfer_free(uvc_dev->udev, xfer); + return -ENOMEM; + } + + buf->len = 0; + uvc_dev->vbuf_offset = 0; + xfer->buf = buf; + + ret = usbh_xfer_enqueue(uvc_dev->udev, xfer); + if (ret != 0) { + LOG_ERR("Enqueue failed: ret=%d", ret); + net_buf_unref(buf); + usbh_xfer_free(uvc_dev->udev, xfer); + return ret; + } + + return 0; } /** @@ -2312,7 +2317,6 @@ static int uvc_host_continue_transfer(struct uvc_device *uvc_dev, struct uhc_tra } buf->len = 0; - *(void **)net_buf_user_data(buf) = vbuf; xfer->buf = buf; ret = usbh_xfer_enqueue(uvc_dev->udev, xfer); @@ -2330,6 +2334,7 @@ static int uvc_host_continue_transfer(struct uvc_device *uvc_dev, struct uhc_tra * * Handles completion of isochronous video data transfers. Processes * received video data, removes UVC headers, and manages frame completion. + * Only processes data with matching frame ID to ensure frame integrity. * * @param dev Pointer to USB device * @param xfer Pointer to completed transfer @@ -2339,10 +2344,15 @@ static int uvc_host_stream_iso_req_cb(struct usb_device *const dev, struct uhc_t { struct uvc_device *uvc_dev = (struct uvc_device *)xfer->priv; struct net_buf *buf = xfer->buf; - struct video_buffer *vbuf = *(struct video_buffer **)net_buf_user_data(buf); + struct video_buffer *vbuf = uvc_dev->current_vbuf; struct uvc_payload_header *payload_header; + uint32_t headLength; + uint32_t dataSize; uint8_t endOfFrame; - uint32_t payload_len; + uint8_t frame_id; + uint32_t presentationTime; + static uint8_t s_savePicture = 0; + static uint32_t s_currentFrameTimeStamp = 0; /* Validate callback parameters */ if (!buf || !uvc_dev) { @@ -2353,54 +2363,116 @@ static int uvc_host_stream_iso_req_cb(struct usb_device *const dev, struct uhc_t /* Handle transfer completion status */ if (xfer->err == -ECONNRESET) { LOG_INF("ISO transfer canceled"); + goto cleanup; } else if (xfer->err) { LOG_WRN("ISO request failed, err %d", xfer->err); - } else { - LOG_DBG("ISO request finished, len=%u", buf->len); + goto cleanup; } - /* Process received video data if present */ - if (buf->len > 0 && vbuf) - { - /* Extract frame end marker from payload header */ - payload_header = (struct uvc_payload_header *)buf->data; - endOfFrame = payload_header->bmHeaderInfo & UVC_BMHEADERINFO_END_OF_FRAME; - /* Remove UVC header and extract payload data */ - payload_len = uvc_host_remove_payload_header(buf, vbuf); - if (payload_len < 0) { - LOG_ERR("Header removal failed: %d", payload_len); - goto cleanup; - } - - /* Update video buffer with processed data */ - vbuf->bytesused += payload_len; - uvc_dev->vbuf_offset = vbuf->bytesused; - - LOG_DBG("Processed %u payload bytes, total: %u, EOF: %u", - payload_len, vbuf->bytesused, endOfFrame); - - /* Handle frame completion */ - if (endOfFrame) { - LOG_INF("Frame completed: %u bytes", vbuf->bytesused); - /* Release network buffer reference */ - net_buf_unref(buf); - /* Move completed buffer from input to output queue */ - k_fifo_get(&uvc_dev->fifo_in, K_NO_WAIT); - k_fifo_put(&uvc_dev->fifo_out, vbuf); - - /* Clean up transfer resources */ - uvc_dev->vbuf_offset = 0; - usbh_xfer_free(dev, xfer); - uvc_dev->transfer_count = 0; - - /* Signal frame completion to application */ - LOG_DBG("Raising VIDEO_BUF_DONE signal"); - k_poll_signal_raise(uvc_dev->sig, VIDEO_BUF_DONE); - if ((vbuf = k_fifo_peek_head(&uvc_dev->fifo_in)) != NULL) { - vbuf->bytesused = 0; - uvc_host_initiate_transfer(uvc_dev, vbuf); + payload_header = (struct uvc_payload_header *)buf->data; + endOfFrame = payload_header->bmHeaderInfo & UVC_BMHEADERINFO_END_OF_FRAME; + frame_id = payload_header->bmHeaderInfo & UVC_BMHEADERINFO_FRAMEID; + presentationTime = sys_le32_to_cpu(payload_header->dwPresentationTime); + headLength = payload_header->bHeaderLength; + + if (buf->len > 0) { + /* the standard header is 12 bytes */ + if (buf->len > 11) { + dataSize = buf->len - headLength; + /* there is payload for this transfer */ + if (dataSize) { + if (vbuf->bytesused == 0U) { + if (s_currentFrameTimeStamp != presentationTime) { + s_currentFrameTimeStamp = presentationTime; + } + } + /* presentation time should be the same for the same frame, if not, discard this picture */ + else if (presentationTime != s_currentFrameTimeStamp) { + s_savePicture = 0; + } + + /* the current picture buffers are not available, now discard the receiving picture */ + if ((!vbuf && s_savePicture)) { + s_savePicture = 0; + } else if (s_savePicture) { + if (dataSize > (vbuf->size - vbuf->bytesused)) { /* error here */ + s_savePicture = 0; + vbuf->bytesused = 0U; + uvc_dev->vbuf_offset = 0; + } else { + /* the same frame id indicates they belong to the same frame */ + if (frame_id == uvc_dev->expect_frame_id) { + /* copy data to picture buffer */ + memcpy((void *)(((uint8_t *)vbuf->buffer) + vbuf->bytesused), + (void *)(((uint8_t *)buf->data) + headLength), + dataSize); + vbuf->bytesused += dataSize; + uvc_dev->vbuf_offset = vbuf->bytesused; + + LOG_DBG("Processed %u payload bytes (FID:%u), total: %u, EOF: %u", + dataSize, frame_id, vbuf->bytesused, endOfFrame); + } else { + /* for the payload that has different frame id, discard it */ + s_savePicture = 0; + LOG_DBG("Frame ID mismatch: expected %u, got %u - discarding", + uvc_dev->expect_frame_id, frame_id); + } + } + } else { + /* no action */ + } + } + + if (s_savePicture) { + if (endOfFrame) { + if (vbuf->bytesused != 0) { + LOG_INF("Frame completed: %u bytes (FID: %u)", + vbuf->bytesused, frame_id); + + /* Move completed buffer from input to output queue */ + k_fifo_get(&uvc_dev->fifo_in, K_NO_WAIT); + k_fifo_put(&uvc_dev->fifo_out, vbuf); + + /* toggle the expected frame id */ + uvc_dev->expect_frame_id = uvc_dev->expect_frame_id ^ 1; + s_savePicture = 1; + + /* Clean up transfer resources */ + uvc_dev->vbuf_offset = 0; + uvc_dev->transfer_count = 0; + + /* Signal frame completion to application */ + LOG_DBG("Raising VIDEO_BUF_DONE signal"); + k_poll_signal_raise(uvc_dev->sig, VIDEO_BUF_DONE); + + /* switch to another buffer to save picture frame */ + if ((vbuf = k_fifo_peek_head(&uvc_dev->fifo_in)) != NULL) { + vbuf->bytesused = 0; + memset(vbuf->buffer, 0, vbuf->size); + uvc_dev->current_vbuf = vbuf; + } + } + } + } else { + /* the last frame of one picture */ + if (endOfFrame) { + if (uvc_dev->discard_first_frame) { + uvc_dev->discard_first_frame = 0; + uvc_dev->expect_frame_id = frame_id ^ 1; + } + if (vbuf && vbuf->bytesused != 0) { + uvc_dev->discard_frame_cnt++; + } + if (vbuf) { + vbuf->bytesused = 0U; + uvc_dev->vbuf_offset = 0; + } + if (!uvc_dev->discard_first_frame) { + uvc_dev->expect_frame_id = frame_id ^ 1; + } + s_savePicture = 1; + } } - return 0; } } @@ -2408,7 +2480,9 @@ static int uvc_host_stream_iso_req_cb(struct usb_device *const dev, struct uhc_t /* Release network buffer reference */ net_buf_unref(buf); /* Continue processing pending buffers */ - uvc_host_continue_transfer(uvc_dev, xfer, vbuf); + if (vbuf) { + uvc_host_continue_transfer(uvc_dev, xfer, vbuf); + } return 0; } @@ -2742,7 +2816,6 @@ static int uvc_host_init(struct usbh_class_data *cdata) uvc_dev->vs_input_header = NULL; uvc_dev->vs_output_header = NULL; - /** Initialize format information */ memset(&uvc_dev->formats, 0, sizeof(struct uvc_format_info_all)); if (uvc_dev->video_format_caps) { @@ -2753,6 +2826,12 @@ static int uvc_host_init(struct usbh_class_data *cdata) /** Initialize current format information */ memset(&uvc_dev->current_format, 0, sizeof(struct uvc_format_info)); + uvc_dev->expect_frame_id = 0xFF; + /* dicard the first picture because the first picture may be not complete */ + uvc_dev->discard_first_frame = 1; + uvc_dev->discard_frame_cnt = 0; + uvc_dev->multi_prime_cnt = CONFIG_USBH_VIDEO_MULTIPLE_PRIME_COUNT; + LOG_INF("UVC device structure initialized successfully"); return 0; } @@ -2885,6 +2964,8 @@ static int uvc_host_removed(struct usb_device *udev, struct usbh_class_data *cda return -ENODEV; } + /*TODO: cancel all of transfers and free all of usb xfer */ + /* Reset video buffer state */ uvc_dev->vbuf_offset = 0; uvc_dev->transfer_count = 0; @@ -3591,7 +3672,14 @@ static int video_usb_uvc_host_set_stream(const struct device *dev, bool enable, if (enable) { if ((vbuf = k_fifo_peek_head(&uvc_dev->fifo_in)) != NULL) { - uvc_host_initiate_transfer(uvc_dev, vbuf); + vbuf->bytesused = 0; + memset(vbuf->buffer, 0, vbuf->size); + uvc_dev->current_vbuf = vbuf; + while (uvc_dev->multi_prime_cnt) + { + uvc_host_initiate_transfer(uvc_dev, vbuf); + uvc_dev->multi_prime_cnt--; + } } } @@ -3715,4 +3803,3 @@ static DEVICE_API(video, uvc_host_video_api) = { VIDEO_DEVICE_DEFINE(usb_camera_##n, (void *)DEVICE_DT_INST_GET(n), NULL); DT_INST_FOREACH_STATUS_OKAY(USBH_VIDEO_DT_DEVICE_DEFINE) - From f428572b035e2e80e17387e91c2286e6c819e8a1 Mon Sep 17 00:00:00 2001 From: Aiden Hu Date: Fri, 5 Sep 2025 16:25:20 +0800 Subject: [PATCH 28/31] usb: host: class: add symbol USBH_VIDEO_MULTIPLE_PRIME_COUNT This new symbol is used to set usb transfer prime count. Signed-off-by: Aiden Hu --- subsys/usb/host/class/Kconfig.uvc_host | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/subsys/usb/host/class/Kconfig.uvc_host b/subsys/usb/host/class/Kconfig.uvc_host index 6a540e54e2eed..3c81e45b67e23 100644 --- a/subsys/usb/host/class/Kconfig.uvc_host +++ b/subsys/usb/host/class/Kconfig.uvc_host @@ -52,6 +52,16 @@ config USBH_VIDEO_MAX_FORMATS being used. TThe maximum count for each format type (e.g., MJPEG, uncompressed), not the total number of all formats. +config USBH_VIDEO_MULTIPLE_PRIME_COUNT + int "Number of concurrent USB transfers to initiate when starting stream" + range 1 16 + default 1 + help + Number of concurrent USB isochronous transfers initiated when video + streaming starts. Higher values improve throughput but use more resources. + Lower values (1-2) suit low frame rates, higher values (4-8) suit high + frame rates. + module = USBH_VIDEO module-str = usbh uvc default-count = 1 From 0c21bc2c1f69d8b83119df4aa8131def9a1cb6f9 Mon Sep 17 00:00:00 2001 From: Aiden Hu Date: Fri, 5 Sep 2025 16:28:04 +0800 Subject: [PATCH 29/31] samples: drivers: video: improve the video capture sample Set proper value for some resources. Use configurable fps. Signed-off-by: Aiden Hu --- .../drivers/video/capture/boards/rd_rw612_bga.conf | 10 +++++----- samples/drivers/video/capture/prj.conf | 4 +++- samples/drivers/video/capture/src/main.c | 13 ++++++++++++- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/samples/drivers/video/capture/boards/rd_rw612_bga.conf b/samples/drivers/video/capture/boards/rd_rw612_bga.conf index 55cec9704ae1a..63ec5bd1a4536 100644 --- a/samples/drivers/video/capture/boards/rd_rw612_bga.conf +++ b/samples/drivers/video/capture/boards/rd_rw612_bga.conf @@ -3,13 +3,13 @@ CONFIG_USB_HOST_STACK=y CONFIG_USBH_VIDEO_CLASS=y # Video buffer configuration -CONFIG_VIDEO_BUFFER_POOL_SZ_MAX=154000 -CONFIG_VIDEO_BUFFER_POOL_NUM_MAX=2 +CONFIG_VIDEO_BUFFER_POOL_SZ_MAX=116000 +CONFIG_VIDEO_BUFFER_POOL_NUM_MAX=6 CONFIG_VIDEO_BUFFER_POOL_ALIGN=32 # Memory configuration for USB transfers -CONFIG_HEAP_MEM_POOL_SIZE=70000 -CONFIG_MAIN_STACK_SIZE=80960 +CONFIG_HEAP_MEM_POOL_SIZE=40960 +CONFIG_MAIN_STACK_SIZE=10240 CONFIG_USBH_USB_DEVICE_HEAP=8912 -CONFIG_UHC_BUF_POOL_SIZE=4096 \ No newline at end of file +CONFIG_UHC_BUF_POOL_SIZE=4096 diff --git a/samples/drivers/video/capture/prj.conf b/samples/drivers/video/capture/prj.conf index 69e4b08258b70..cab0b4f4f60db 100644 --- a/samples/drivers/video/capture/prj.conf +++ b/samples/drivers/video/capture/prj.conf @@ -11,4 +11,6 @@ CONFIG_REQUIRES_FLOAT_PRINTF=y CONFIG_DEBUG_OPTIMIZATIONS=y CONFIG_USBH_VIDEO_LOG_LEVEL_WRN=y CONFIG_VIDEO_FRAME_WIDTH=320 -CONFIG_VIDEO_FRAME_HEIGHT=240 \ No newline at end of file +CONFIG_VIDEO_FRAME_HEIGHT=180 +CONFIG_VIDEO_TARGET_FPS=15 +CONFIG_USBH_VIDEO_MULTIPLE_PRIME_COUNT=8 diff --git a/samples/drivers/video/capture/src/main.c b/samples/drivers/video/capture/src/main.c index e348fa9b24411..bd1abc61d6c0a 100644 --- a/samples/drivers/video/capture/src/main.c +++ b/samples/drivers/video/capture/src/main.c @@ -399,6 +399,18 @@ int main(void) fie.index++; } +#if CONFIG_VIDEO_TARGET_FPS > 0 + frmival.denominator = CONFIG_VIDEO_TARGET_FPS; + frmival.numerator = 1; + if (!video_set_frmival(uvc_host, &frmival)) { + /* Get the actual frame rate that was set */ + if (!video_get_frmival(uvc_host, &frmival)) { + LOG_INF("- Target frame rate set to: %f fps", + 1.0 * frmival.denominator / frmival.numerator); + } + } +#endif + /* Get supported controls */ LOG_INF("- Supported controls:"); const struct device *last_dev = NULL; @@ -456,7 +468,6 @@ int main(void) break; } - /* TODO: CONFIG_VIDEO_BUFFER_POOL_NUM_MAX is 1 now, consider to use multiple video buffers */ for (i = 0; i < CONFIG_VIDEO_BUFFER_POOL_NUM_MAX; i++) { /* * For some hardwares, such as the PxP used on i.MX RT1170 to do image rotation, From 9328c37cc9756ce9595f35d57c875b9cb439b58b Mon Sep 17 00:00:00 2001 From: Aiden Hu Date: Tue, 16 Sep 2025 14:42:26 +0800 Subject: [PATCH 30/31] usb: host: class: improve the video control handling 1. Use the uvc_control_map from usb_common_uvc.c. 2. Use GET_MIN/GET_MAX/GET_RES/GET_DEF request to query device. 3. Unify the handling for single video control Signed-off-by: Aiden Hu --- subsys/usb/host/class/usbh_uvc.c | 1358 +++++++++++++++++------------- 1 file changed, 788 insertions(+), 570 deletions(-) diff --git a/subsys/usb/host/class/usbh_uvc.c b/subsys/usb/host/class/usbh_uvc.c index e617f8490ce12..fe7819e3e1be1 100644 --- a/subsys/usb/host/class/usbh_uvc.c +++ b/subsys/usb/host/class/usbh_uvc.c @@ -275,256 +275,6 @@ static int uvc_host_select_default_format(struct uvc_device *uvc_dev) return -ENOTSUP; } -/** - * @brief Check if Processing Unit supports specific control - * @param uvc_dev UVC device - * @param bmcontrol_bit Control bit mask to check (UVC_PU_BMCONTROL_*) - * @return true if supported, false otherwise - */ -static bool uvc_host_pu_supports_control(struct uvc_device *uvc_dev, uint32_t bmcontrol_bit) -{ - if (!uvc_dev || !uvc_dev->vc_pud) { - return false; - } - - struct uvc_processing_unit_descriptor *pud = uvc_dev->vc_pud; - - if (pud->bControlSize == 0) { - return false; - } - - /* Convert the bmControls array to a 32-bit value for easier bit checking */ - uint32_t controls = 0; - for (int i = 0; i < pud->bControlSize && i < 4; i++) { - controls |= ((uint32_t)pud->bmControls[i]) << (i * 8); - } - - return (controls & bmcontrol_bit) != 0; -} - -/** - * @brief Check if Camera Terminal supports specific control - * @param uvc_dev UVC device - * @param bmcontrol_bit Control bit mask to check (UVC_CT_BMCONTROL_*) - * @return true if supported, false otherwise - */ -static bool uvc_host_ct_supports_control(struct uvc_device *uvc_dev, uint32_t bmcontrol_bit) -{ - if (!uvc_dev || !uvc_dev->vc_ctd) { - return false; - } - - struct uvc_camera_terminal_descriptor *vc_ctd = uvc_dev->vc_ctd; - - if (vc_ctd->bControlSize == 0) { - return false; - } - - /* Convert the bmControls array to a 32-bit value for easier bit checking */ - uint32_t controls = 0; - for (int i = 0; i < vc_ctd->bControlSize && i < 4; i++) { - controls |= ((uint32_t)vc_ctd->bmControls[i]) << (i * 8); - } - - return (controls & bmcontrol_bit) != 0; -} - -/** - * @brief Initialize USB camera controls based on device capabilities - * - * Initializes video controls supported by the UVC device based on - * Processing Unit and Camera Terminal capabilities. - * - * @param dev Video device pointer - * @return 0 on success, negative error code on failure - */ -static int usb_host_camera_init_controls(const struct device *dev) -{ - int ret; - struct uvc_device *uvc_dev = dev->data; - struct usb_camera_ctrls *ctrls = &uvc_dev->ctrls; - int initialized_count = 0; - - if (!uvc_dev->vc_pud) { - LOG_WRN("No processing unit found, skipping control initialization"); - return 0; - } - - LOG_INF("Initializing controls based on processing unit capabilities"); - - /* Brightness control */ - if (uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_BRIGHTNESS)) { - ret = video_init_ctrl(&ctrls->brightness, dev, VIDEO_CID_BRIGHTNESS, - (struct video_ctrl_range){.min = -128, .max = 127, .step = 1, .def = 0}); - if (!ret) { - initialized_count++; - LOG_DBG("Brightness control initialized"); - } - } - - /* Contrast control */ - if (uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_CONTRAST)) { - ret = video_init_ctrl(&ctrls->contrast, dev, VIDEO_CID_CONTRAST, - (struct video_ctrl_range){.min = 0, .max = 255, .step = 1, .def = 128}); - if (!ret) { - initialized_count++; - LOG_DBG("Contrast control initialized"); - } - } - - /* Hue control */ - if (uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_HUE)) { - ret = video_init_ctrl(&ctrls->hue, dev, VIDEO_CID_HUE, - (struct video_ctrl_range){.min = -180, .max = 180, .step = 1, .def = 0}); - if (!ret) { - initialized_count++; - LOG_DBG("Hue control initialized"); - } - } - - /* Saturation control */ - if (uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_SATURATION)) { - ret = video_init_ctrl(&ctrls->saturation, dev, VIDEO_CID_SATURATION, - (struct video_ctrl_range){.min = 0, .max = 255, .step = 1, .def = 128}); - if (!ret) { - initialized_count++; - LOG_DBG("Saturation control initialized"); - } - } - - /* Sharpness control */ - if (uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_SHARPNESS)) { - ret = video_init_ctrl(&ctrls->sharpness, dev, VIDEO_CID_SHARPNESS, - (struct video_ctrl_range){.min = 0, .max = 255, .step = 1, .def = 128}); - if (!ret) { - initialized_count++; - LOG_DBG("Sharpness control initialized"); - } - } - - /* Gamma control */ - if (uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_GAMMA)) { - ret = video_init_ctrl(&ctrls->gamma, dev, VIDEO_CID_GAMMA, - (struct video_ctrl_range){.min = 100, .max = 300, .step = 1, .def = 100}); - if (!ret) { - initialized_count++; - LOG_DBG("Gamma control initialized"); - } - } - - /* Gain controls */ - if (uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_GAIN)) { - ret = video_init_ctrl(&ctrls->auto_gain, dev, VIDEO_CID_AUTOGAIN, - (struct video_ctrl_range){.min = 0, .max = 1, .step = 1, .def = 1}); - if (!ret) { - initialized_count++; - LOG_DBG("Auto gain control initialized"); - } - - ret = video_init_ctrl(&ctrls->gain, dev, VIDEO_CID_GAIN, - (struct video_ctrl_range){.min = 0, .max = 255, .step = 1, .def = 0}); - if (!ret) { - initialized_count++; - /* Create auto gain cluster if both controls exist */ - if (ctrls->auto_gain.id != 0) { - video_auto_cluster_ctrl(&ctrls->auto_gain, 2, true); - } - LOG_DBG("Gain control initialized"); - } - } - - /* White Balance Temperature control */ - if (uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_WHITE_BALANCE_TEMPERATURE)) { - ret = video_init_ctrl(&ctrls->white_balance_temperature, dev, VIDEO_CID_WHITE_BALANCE_TEMPERATURE, - (struct video_ctrl_range){.min = 2800, .max = 6500, .step = 1, .def = 4000}); - if (!ret) { - initialized_count++; - LOG_DBG("White balance temperature control initialized"); - } - } - - /* Auto White Balance control */ - if (uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_WHITE_BALANCE_TEMPERATURE_AUTO)) { - ret = video_init_ctrl(&ctrls->auto_white_balance_temperature, dev, VIDEO_CID_AUTO_WHITE_BALANCE, - (struct video_ctrl_range){.min = 0, .max = 1, .step = 1, .def = 1}); - if (!ret) { - initialized_count++; - LOG_DBG("Auto white balance control initialized"); - } - } - - /* Backlight Compensation control */ - if (uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_BACKLIGHT_COMPENSATION)) { - ret = video_init_ctrl(&ctrls->backlight_compensation, dev, VIDEO_CID_BACKLIGHT_COMPENSATION, - (struct video_ctrl_range){.min = 0, .max = 2, .step = 1, .def = 1}); - if (!ret) { - initialized_count++; - LOG_DBG("Backlight compensation control initialized"); - } - } - - /* Power line frequency control */ - if (uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_POWER_LINE_FREQUENCY)) { - ret = video_init_menu_ctrl(&ctrls->light_freq, dev, VIDEO_CID_POWER_LINE_FREQUENCY, - VIDEO_CID_POWER_LINE_FREQUENCY_AUTO, NULL); - if (!ret) { - initialized_count++; - LOG_DBG("Power line frequency control initialized"); - } - } - - /* Auto exposure control - Camera Terminal control */ - if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_AE_MODE)) { - ret = video_init_ctrl(&ctrls->auto_exposure, dev, VIDEO_CID_EXPOSURE_AUTO, - (struct video_ctrl_range){.min = 0, .max = 1, .step = 1, .def = 1}); - if (!ret) { - initialized_count++; - LOG_DBG("Auto exposure control initialized"); - } - } - - /* Exposure absolute control - Camera Terminal control */ - if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_EXPOSURE_TIME_ABSOLUTE)) { - ret = video_init_ctrl(&ctrls->exposure_absolute, dev, VIDEO_CID_EXPOSURE_ABSOLUTE, - (struct video_ctrl_range){ - .min = 1, /* Minimum exposure time 1μs */ - .max = 10000000, /* Maximum exposure time 10s (10,000,000μs) */ - .step = 1, - .def = 33333 /* Default 1/30s ≈ 33.33ms */ - }); - if (!ret) { - initialized_count++; - /* Create auto exposure cluster if both controls exist */ - if (ctrls->auto_exposure.id != 0) { - video_auto_cluster_ctrl(&ctrls->auto_exposure, 2, true); - } - LOG_DBG("Exposure absolute control initialized"); - } - } - - /* Focus controls - Camera Terminal control */ - if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_FOCUS_AUTO)) { - ret = video_init_ctrl(&ctrls->auto_focus, dev, VIDEO_CID_FOCUS_AUTO, - (struct video_ctrl_range){.min = 0, .max = 1, .step = 1, .def = 1}); - if (!ret) { - initialized_count++; - LOG_DBG("Auto focus control initialized"); - } - } - - if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_FOCUS_ABSOLUTE)) { - ret = video_init_ctrl(&ctrls->focus_absolute, dev, VIDEO_CID_FOCUS_ABSOLUTE, - (struct video_ctrl_range){.min = 0, .max = 1023, .step = 1, .def = 0}); - if (!ret) { - initialized_count++; - LOG_DBG("Focus absolute control initialized"); - } - } - - LOG_INF("Initialized %d camera controls", initialized_count); - return 0; -} - /** * @brief Configure UVC device interfaces * @@ -2765,109 +2515,536 @@ static int uvc_host_control_unit_and_terminal_request(struct uvc_device *uvc_dev return ret; } -static int uvc_host_init(struct usbh_class_data *cdata) +/** + * @brief Check if Processing Unit supports specific control + * @param uvc_dev UVC device + * @param bmcontrol_bit Control bit mask to check (UVC_PU_BMCONTROL_*) + * @return true if supported, false otherwise + */ +static bool uvc_host_pu_supports_control(struct uvc_device *uvc_dev, uint32_t bmcontrol_bit) { - const struct device *dev = cdata->priv; - struct uvc_device *uvc_dev = (struct uvc_device *)dev->data; - - if (!cdata || !dev || !uvc_dev) { - LOG_ERR("Invalid parameters for UVC host init"); - return -EINVAL; + if (!uvc_dev || !uvc_dev->vc_pud) { + return false; } - LOG_INF("Initializing UVC device structure"); - - /** Initialize basic device state */ - uvc_dev->udev = NULL; - uvc_dev->connected = false; - - /** Initialize transfer related parameters */ - uvc_dev->vbuf_offset = 0; - uvc_dev->transfer_count = 0; + struct uvc_processing_unit_descriptor *pud = uvc_dev->vc_pud; - /** Initialize FIFO queues */ - k_fifo_init(&uvc_dev->fifo_in); - k_fifo_init(&uvc_dev->fifo_out); + if (pud->bControlSize == 0) { + return false; + } - /** Initialize mutex lock */ - k_mutex_init(&uvc_dev->lock); + /* Convert the bmControls array to a 32-bit value for easier bit checking */ + uint32_t controls = 0; + for (int i = 0; i < pud->bControlSize && i < 4; i++) { + controls |= ((uint32_t)pud->bmControls[i]) << (i * 8); + } - /** Initialize USB camera control structure */ - memset(&uvc_dev->ctrls, 0, sizeof(struct usb_camera_ctrls)); + return (controls & bmcontrol_bit) != 0; +} - /** Initialize stream interface array */ - for (int i = 0; i < CONFIG_USBH_VIDEO_MAX_STREAM_INTERFACE; i++) { - uvc_dev->stream_ifaces[i] = NULL; +/** + * @brief Check if Camera Terminal supports specific control + * @param uvc_dev UVC device + * @param bmcontrol_bit Control bit mask to check (UVC_CT_BMCONTROL_*) + * @return true if supported, false otherwise + */ +static bool uvc_host_ct_supports_control(struct uvc_device *uvc_dev, uint32_t bmcontrol_bit) +{ + if (!uvc_dev || !uvc_dev->vc_ctd) { + return false; } - /** Initialize interface information */ - uvc_dev->current_control_interface = NULL; - memset(&uvc_dev->current_stream_iface_info, 0, sizeof(struct uvc_stream_iface_info)); - - /** Initialize descriptor pointers */ - uvc_dev->vc_header = NULL; - uvc_dev->vc_itd = NULL; - uvc_dev->vc_otd = NULL; - uvc_dev->vc_ctd = NULL; - uvc_dev->vc_sud = NULL; - uvc_dev->vc_pud = NULL; - uvc_dev->vc_encoding_unit = NULL; - uvc_dev->vc_extension_unit = NULL; - uvc_dev->vs_input_header = NULL; - uvc_dev->vs_output_header = NULL; + struct uvc_camera_terminal_descriptor *vc_ctd = uvc_dev->vc_ctd; - /** Initialize format information */ - memset(&uvc_dev->formats, 0, sizeof(struct uvc_format_info_all)); - if (uvc_dev->video_format_caps) { - k_free(uvc_dev->video_format_caps); - uvc_dev->video_format_caps = NULL; + if (vc_ctd->bControlSize == 0) { + return false; } - /** Initialize current format information */ - memset(&uvc_dev->current_format, 0, sizeof(struct uvc_format_info)); - - uvc_dev->expect_frame_id = 0xFF; - /* dicard the first picture because the first picture may be not complete */ - uvc_dev->discard_first_frame = 1; - uvc_dev->discard_frame_cnt = 0; - uvc_dev->multi_prime_cnt = CONFIG_USBH_VIDEO_MULTIPLE_PRIME_COUNT; + /* Convert the bmControls array to a 32-bit value for easier bit checking */ + uint32_t controls = 0; + for (int i = 0; i < vc_ctd->bControlSize && i < 4; i++) { + controls |= ((uint32_t)vc_ctd->bmControls[i]) << (i * 8); + } - LOG_INF("UVC device structure initialized successfully"); - return 0; + return (controls & bmcontrol_bit) != 0; } /** - * @brief Handle UVC device connection - * - * Called when a UVC device is connected. Parses descriptors, - * configures the device, and initializes controls. + * @brief Initialize individual control based on CID * - * @param udev USB device - * @param cdata USB host class data - * @param desc_start_addr Start of descriptor segment - * @param desc_end_addr End of descriptor segment + * @param dev Video device + * @param cid Control ID + * @param range Control range * @return 0 on success, negative error code on failure */ -static int uvc_host_connected(struct usb_device *udev, struct usbh_class_data *cdata, void *desc_start_addr, void *desc_end_addr) +static int uvc_host_init_control_by_cid(const struct device *dev, uint32_t cid, + const struct video_ctrl_range *range) { - const struct device *dev = cdata->priv; - struct uvc_device *uvc_dev = (struct uvc_device *)dev->data; - int ret; + struct uvc_device *uvc_dev = dev->data; + struct usb_camera_ctrls *ctrls = &uvc_dev->ctrls; + int ret = -ENOTSUP; - if (cdata->class_matched) - { - return 0; /* Already processed, exit early */ - } - cdata->class_matched = 1; + switch (cid) { + /* Processing Unit Controls */ + case VIDEO_CID_BRIGHTNESS: + ret = video_init_ctrl(&ctrls->brightness, dev, cid, *range); + break; - LOG_INF("UVC device connected"); + case VIDEO_CID_CONTRAST: + ret = video_init_ctrl(&ctrls->contrast, dev, cid, *range); + break; - if (!udev || udev->state != USB_STATE_CONFIGURED) { - LOG_ERR("USB device not properly configured"); - return -ENODEV; - } + case VIDEO_CID_HUE: + ret = video_init_ctrl(&ctrls->hue, dev, cid, *range); + break; - if (!uvc_dev) { + case VIDEO_CID_SATURATION: + ret = video_init_ctrl(&ctrls->saturation, dev, cid, *range); + break; + + case VIDEO_CID_SHARPNESS: + ret = video_init_ctrl(&ctrls->sharpness, dev, cid, *range); + break; + + case VIDEO_CID_GAMMA: + ret = video_init_ctrl(&ctrls->gamma, dev, cid, *range); + break; + + case VIDEO_CID_GAIN: + ret = video_init_ctrl(&ctrls->gain, dev, cid, *range); + break; + + case VIDEO_CID_AUTOGAIN: + ret = video_init_ctrl(&ctrls->auto_gain, dev, cid, *range); + /* Create auto-gain cluster if manual gain control exists */ + if (ret == 0 && ctrls->gain.id != 0) { + video_auto_cluster_ctrl(&ctrls->auto_gain, 2, true); + LOG_DBG("Created auto-gain cluster"); + } + break; + + case VIDEO_CID_WHITE_BALANCE_TEMPERATURE: + ret = video_init_ctrl(&ctrls->white_balance_temperature, dev, cid, *range); + break; + + case VIDEO_CID_AUTO_WHITE_BALANCE: + ret = video_init_ctrl(&ctrls->auto_white_balance_temperature, dev, cid, *range); + break; + + case VIDEO_CID_BACKLIGHT_COMPENSATION: + ret = video_init_ctrl(&ctrls->backlight_compensation, dev, cid, *range); + break; + + case VIDEO_CID_POWER_LINE_FREQUENCY: + ret = video_init_menu_ctrl(&ctrls->light_freq, dev, cid, + VIDEO_CID_POWER_LINE_FREQUENCY_AUTO, NULL); + break; + + /* Camera Terminal Controls */ + case VIDEO_CID_EXPOSURE_AUTO: + ret = video_init_ctrl(&ctrls->auto_exposure, dev, cid, *range); + break; + + case VIDEO_CID_EXPOSURE_ABSOLUTE: + ret = video_init_ctrl(&ctrls->exposure_absolute, dev, cid, *range); + /* Create auto-exposure cluster if auto-exposure control exists */ + if (ret == 0 && ctrls->auto_exposure.id != 0) { + video_auto_cluster_ctrl(&ctrls->auto_exposure, 2, true); + LOG_DBG("Created auto-exposure cluster"); + } + break; + + case VIDEO_CID_FOCUS_AUTO: + ret = video_init_ctrl(&ctrls->auto_focus, dev, cid, *range); + break; + + case VIDEO_CID_FOCUS_ABSOLUTE: + ret = video_init_ctrl(&ctrls->focus_absolute, dev, cid, *range); + break; + + case VIDEO_CID_TEST_PATTERN: + ret = video_init_ctrl(&ctrls->test_pattern, dev, cid, *range); + break; + + default: + LOG_DBG("Control CID 0x%08x not implemented", cid); + break; + } + + return ret; +} + +/** + * @brief Extract control bitmap from bmControls array + * + * @param bmControls Control bitmap array + * @param control_size Size of the control bitmap array + * @return 32-bit control bitmap + */ +static uint32_t uvc_host_extract_control_bitmap(const uint8_t *bmControls, uint8_t control_size) +{ + uint32_t controls = 0; + + for (int i = 0; i < control_size && i < 4; i++) { + controls |= ((uint32_t)bmControls[i]) << (i * 8); + } + + return controls; +} + +/** + * @brief Extract control value from data buffer based on size and type + * + * @param data Data buffer containing the control value + * @param size Size of the control data (1, 2, or 4 bytes) + * @param type Control type (signed or unsigned) + * @return Extracted control value + */ +static int32_t uvc_host_extract_control_value(const uint8_t *data, uint8_t size, uint8_t type) +{ + switch (size) { + case 1: + return (type == UVC_CONTROL_SIGNED) ? (int8_t)data[0] : (uint8_t)data[0]; + + case 2: + if (type == UVC_CONTROL_SIGNED) { + return (int16_t)sys_le16_to_cpu(*(uint16_t*)data); + } else { + return (uint16_t)sys_le16_to_cpu(*(uint16_t*)data); + } + + case 4: + return (int32_t)sys_le32_to_cpu(*(uint32_t*)data); + + default: + LOG_ERR("Unsupported control data size: %u", size); + return 0; + } +} + +/** + * @brief Query actual control range from UVC device + * + * @param uvc_dev UVC device structure + * @param entity_id Entity ID (unit or terminal) + * @param map Control mapping information + * @param range Output range structure + * @return 0 on success, negative error code on failure + */ +static int uvc_host_query_control_range(struct uvc_device *uvc_dev, + uint8_t entity_id, + const struct uvc_control_map *map, + struct video_ctrl_range *range) +{ + uint8_t data[4]; + int ret; + + /* Query minimum value */ + ret = uvc_host_control_unit_and_terminal_request(uvc_dev, UVC_GET_MIN, + map->selector, entity_id, data, map->size); + if (ret) { + LOG_DBG("Failed to get min value for selector 0x%02x: %d", map->selector, ret); + return ret; + } + range->min = uvc_host_extract_control_value(data, map->size, map->type); + + /* Query maximum value */ + ret = uvc_host_control_unit_and_terminal_request(uvc_dev, UVC_GET_MAX, + map->selector, entity_id, data, map->size); + if (ret) { + LOG_DBG("Failed to get max value for selector 0x%02x: %d", map->selector, ret); + return ret; + } + range->max = uvc_host_extract_control_value(data, map->size, map->type); + + /* Query step/resolution value */ + ret = uvc_host_control_unit_and_terminal_request(uvc_dev, UVC_GET_RES, + map->selector, entity_id, data, map->size); + if (ret) { + range->step = 1; /* Default step */ + LOG_DBG("Using default step=1 for selector 0x%02x", map->selector); + } else { + int32_t step_val = uvc_host_extract_control_value(data, map->size, map->type); + + range->step = (step_val > 0) ? step_val : 1; + } + + /* Query default value */ + ret = uvc_host_control_unit_and_terminal_request(uvc_dev, UVC_GET_DEF, + map->selector, entity_id, data, map->size); + if (ret) { + range->def = (range->min + range->max) / 2; /* Use middle value as default */ + LOG_DBG("Using middle value as default for selector 0x%02x", map->selector); + } else { + range->def = uvc_host_extract_control_value(data, map->size, map->type); + } + + LOG_DBG("Control 0x%02x range: [%d, %d, %d, %d]", map->selector, + range->min, range->max, range->step, range->def); + + return 0; +} + +/** + * @brief Initialize controls for a specific UVC unit/terminal + * + * @param uvc_dev UVC device structure + * @param dev Video device + * @param unit_subtype UVC unit subtype + * @param entity_id Entity ID + * @param supported_controls Bitmap of supported controls + * @param initialized_count Pointer to initialized control counter + * @return 0 on success, negative error code on failure + */ +static int uvc_host_init_unit_controls(struct uvc_device *uvc_dev, + const struct device *dev, + uint8_t unit_subtype, + uint8_t entity_id, + uint32_t supported_controls, + int *initialized_count) +{ + const struct uvc_control_map *map; + size_t map_length; + int ret; + + /* Get control mapping table for this unit type */ + ret = uvc_get_control_map(unit_subtype, &map, &map_length); + if (ret) { + LOG_ERR("Failed to get control map for unit subtype %u: %d", unit_subtype, ret); + return ret; + } + + LOG_DBG("Processing %zu controls for unit subtype %u, entity ID %u", + map_length, unit_subtype, entity_id); + + /* Iterate through all controls in the mapping table */ + for (size_t i = 0; i < map_length; i++) { + const struct uvc_control_map *ctrl_map = &map[i]; + struct video_ctrl_range range; + + /* Check if device supports this control */ + if (!(supported_controls & BIT(ctrl_map->bit))) { + LOG_DBG("Control bit %u not supported by device", ctrl_map->bit); + continue; + } + + /* Query actual control range from device */ + ret = uvc_host_query_control_range(uvc_dev, entity_id, ctrl_map, &range); + if (ret) { + LOG_WRN("Failed to query range for control 0x%02x, skipping", + ctrl_map->selector); + continue; + } + + /* Initialize the control */ + ret = uvc_host_init_control_by_cid(dev, ctrl_map->cid, &range); + if (ret == 0) { + (*initialized_count)++; + LOG_DBG("Successfully initialized control CID 0x%08x", ctrl_map->cid); + } else if (ret != -ENOTSUP) { + LOG_WRN("Failed to initialize control CID 0x%08x: %d", ctrl_map->cid, ret); + } + } + + return 0; +} + +/** + * @brief Initialize USB camera controls based on device capabilities + * + * Initializes video controls supported by the UVC device based on + * Processing Unit and Camera Terminal capabilities. + * + * @param dev Video device pointer + * @return 0 on success, negative error code on failure + */ +static int usb_host_camera_init_controls(const struct device *dev) +{ + int ret; + struct uvc_device *uvc_dev = dev->data; + struct usb_camera_ctrls *ctrls = &uvc_dev->ctrls; + uint32_t control_bitmap; + int initialized_count = 0; + + if (!uvc_dev->vc_pud) { + LOG_WRN("No processing unit found, skipping control initialization"); + return 0; + } + + LOG_INF("Initializing controls based on processing unit capabilities"); + + /* Initialize Processing Unit controls */ + if (uvc_dev->vc_pud) { + LOG_DBG("Found Processing Unit (ID: %u)", uvc_dev->vc_pud->bUnitID); + + control_bitmap = uvc_host_extract_control_bitmap(uvc_dev->vc_pud->bmControls, + uvc_dev->vc_pud->bControlSize); + + ret = uvc_host_init_unit_controls(uvc_dev, dev, UVC_VC_PROCESSING_UNIT, + uvc_dev->vc_pud->bUnitID, control_bitmap, + &initialized_count); + if (ret) { + LOG_ERR("Failed to initialize Processing Unit controls: %d", ret); + } + } else { + LOG_WRN("No Processing Unit found, skipping PU controls"); + } + + /* Initialize Camera Terminal controls */ + if (uvc_dev->vc_ctd) { + LOG_DBG("Found Camera Terminal (ID: %u)", uvc_dev->vc_ctd->bTerminalID); + + control_bitmap = uvc_host_extract_control_bitmap(uvc_dev->vc_ctd->bmControls, + uvc_dev->vc_ctd->bControlSize); + + ret = uvc_host_init_unit_controls(uvc_dev, dev, UVC_VC_INPUT_TERMINAL, + uvc_dev->vc_ctd->bTerminalID, control_bitmap, + &initialized_count); + if (ret) { + LOG_ERR("Failed to initialize Camera Terminal controls: %d", ret); + } + } else { + LOG_DBG("No Camera Terminal found, skipping CT controls"); + } + + /* Initialize Selector Unit controls */ + if (uvc_dev->vc_sud) { + LOG_DBG("Found Selector Unit (ID: %u)", uvc_dev->vc_sud->bUnitID); + + /* Selector Unit typically supports input selection control */ + control_bitmap = BIT(0); + + ret = uvc_host_init_unit_controls(uvc_dev, dev, UVC_VC_SELECTOR_UNIT, + uvc_dev->vc_sud->bUnitID, control_bitmap, + &initialized_count); + if (ret) { + LOG_ERR("Failed to initialize Selector Unit controls: %d", ret); + } + } + + /* Initialize Extension Unit controls */ + if (uvc_dev->vc_extension_unit) { + LOG_DBG("Found Extension Unit (ID: %u)", uvc_dev->vc_extension_unit->bUnitID); + + control_bitmap = uvc_host_extract_control_bitmap( + uvc_dev->vc_extension_unit->bmControls, + uvc_dev->vc_extension_unit->bControlSize); + + ret = uvc_host_init_unit_controls(uvc_dev, dev, UVC_VC_EXTENSION_UNIT, + uvc_dev->vc_extension_unit->bUnitID, control_bitmap, + &initialized_count); + if (ret) { + LOG_ERR("Failed to initialize Extension Unit controls: %d", ret); + } + } + + LOG_INF("Successfully initialized %d camera controls", initialized_count); + return 0; +} + +static int uvc_host_init(struct usbh_class_data *cdata) +{ + const struct device *dev = cdata->priv; + struct uvc_device *uvc_dev = (struct uvc_device *)dev->data; + + if (!cdata || !dev || !uvc_dev) { + LOG_ERR("Invalid parameters for UVC host init"); + return -EINVAL; + } + + LOG_INF("Initializing UVC device structure"); + + /** Initialize basic device state */ + uvc_dev->udev = NULL; + uvc_dev->connected = false; + + /** Initialize transfer related parameters */ + uvc_dev->vbuf_offset = 0; + uvc_dev->transfer_count = 0; + + /** Initialize FIFO queues */ + k_fifo_init(&uvc_dev->fifo_in); + k_fifo_init(&uvc_dev->fifo_out); + + /** Initialize mutex lock */ + k_mutex_init(&uvc_dev->lock); + + /** Initialize USB camera control structure */ + memset(&uvc_dev->ctrls, 0, sizeof(struct usb_camera_ctrls)); + + /** Initialize stream interface array */ + for (int i = 0; i < CONFIG_USBH_VIDEO_MAX_STREAM_INTERFACE; i++) { + uvc_dev->stream_ifaces[i] = NULL; + } + + /** Initialize interface information */ + uvc_dev->current_control_interface = NULL; + memset(&uvc_dev->current_stream_iface_info, 0, sizeof(struct uvc_stream_iface_info)); + + /** Initialize descriptor pointers */ + uvc_dev->vc_header = NULL; + uvc_dev->vc_itd = NULL; + uvc_dev->vc_otd = NULL; + uvc_dev->vc_ctd = NULL; + uvc_dev->vc_sud = NULL; + uvc_dev->vc_pud = NULL; + uvc_dev->vc_encoding_unit = NULL; + uvc_dev->vc_extension_unit = NULL; + uvc_dev->vs_input_header = NULL; + uvc_dev->vs_output_header = NULL; + + /** Initialize format information */ + memset(&uvc_dev->formats, 0, sizeof(struct uvc_format_info_all)); + if (uvc_dev->video_format_caps) { + k_free(uvc_dev->video_format_caps); + uvc_dev->video_format_caps = NULL; + } + + /** Initialize current format information */ + memset(&uvc_dev->current_format, 0, sizeof(struct uvc_format_info)); + + uvc_dev->expect_frame_id = 0xFF; + /* dicard the first picture because the first picture may be not complete */ + uvc_dev->discard_first_frame = 1; + uvc_dev->discard_frame_cnt = 0; + uvc_dev->multi_prime_cnt = CONFIG_USBH_VIDEO_MULTIPLE_PRIME_COUNT; + + LOG_INF("UVC device structure initialized successfully"); + return 0; +} + +/** + * @brief Handle UVC device connection + * + * Called when a UVC device is connected. Parses descriptors, + * configures the device, and initializes controls. + * + * @param udev USB device + * @param cdata USB host class data + * @param desc_start_addr Start of descriptor segment + * @param desc_end_addr End of descriptor segment + * @return 0 on success, negative error code on failure + */ +static int uvc_host_connected(struct usb_device *udev, struct usbh_class_data *cdata, void *desc_start_addr, void *desc_end_addr) +{ + const struct device *dev = cdata->priv; + struct uvc_device *uvc_dev = (struct uvc_device *)dev->data; + int ret; + + if (cdata->class_matched) + { + return 0; /* Already processed, exit early */ + } + cdata->class_matched = 1; + + LOG_INF("UVC device connected"); + + if (!udev || udev->state != USB_STATE_CONFIGURED) { + LOG_ERR("USB device not properly configured"); + return -ENODEV; + } + + if (!uvc_dev) { LOG_ERR("No UVC device instance available"); return -ENODEV; } @@ -3316,302 +3493,343 @@ static int video_usb_uvc_host_get_volatile_ctrl(const struct device *dev, uint32 } /** - * @brief Set UVC control value - * - * Video API implementation to set various camera control parameters. - * Maps video control IDs to UVC-specific control selectors and sends - * appropriate USB control requests. + * @brief Find control mapping by CID based on available units * - * @param dev Pointer to video device - * @param ctrl Pointer to video control structure containing ID and value - * @return 0 on success, negative error code on failure + * @param uvc_dev UVC device structure + * @param cid Control ID to find + * @param unit_subtype Output: unit subtype where control was found + * @param map Output: pointer to control mapping + * @return 0 on success, negative error code if not found */ -static int video_usb_uvc_host_set_ctrl(const struct device *dev, struct video_control *ctrl) +static int uvc_host_find_control_mapping(struct uvc_device *uvc_dev, uint32_t cid, + uint8_t *unit_subtype, + const struct uvc_control_map **map) { - struct uvc_device *uvc_dev = dev->data; - int ret = 0; - uint8_t entity_id = 0; - uint8_t control_selector = 0; - uint8_t data[4] = {0}; /* Buffer for control data */ - uint8_t data_len = 0; + const struct uvc_control_map *map_table; + size_t map_length; + int ret; - if (!uvc_dev || !ctrl) { - return -EINVAL; + /* Search in Processing Unit if it exists */ + if (uvc_dev->vc_pud) { + ret = uvc_get_control_map(UVC_VC_PROCESSING_UNIT, &map_table, &map_length); + if (ret == 0) { + for (size_t i = 0; i < map_length; i++) { + if (map_table[i].cid == cid) { + *unit_subtype = UVC_VC_PROCESSING_UNIT; + *map = &map_table[i]; + LOG_DBG("Found control CID 0x%08x in Processing Unit", cid); + return 0; + } + } + } } - /* Map control ID to UVC entity and control selector */ - switch (ctrl->id) { - /* Processing Unit Controls */ - case VIDEO_CID_BRIGHTNESS: - if (!uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_BRIGHTNESS)) { - return -ENOTSUP; + /* Search in Camera Terminal if it exists */ + if (uvc_dev->vc_ctd) { + ret = uvc_get_control_map(UVC_VC_INPUT_TERMINAL, &map_table, &map_length); + if (ret == 0) { + for (size_t i = 0; i < map_length; i++) { + if (map_table[i].cid == cid) { + *unit_subtype = UVC_VC_INPUT_TERMINAL; + *map = &map_table[i]; + LOG_DBG("Found control CID 0x%08x in Camera Terminal", cid); + return 0; + } + } } - entity_id = uvc_dev->vc_pud->bUnitID; - control_selector = UVC_PU_BRIGHTNESS_CONTROL; - sys_put_le16(ctrl->val, data); - data_len = 2; - break; + } - case VIDEO_CID_CONTRAST: - if (!uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_CONTRAST)) { - return -ENOTSUP; + /* Search in Selector Unit if it exists */ + if (uvc_dev->vc_sud) { + ret = uvc_get_control_map(UVC_VC_SELECTOR_UNIT, &map_table, &map_length); + if (ret == 0) { + for (size_t i = 0; i < map_length; i++) { + if (map_table[i].cid == cid) { + *unit_subtype = UVC_VC_SELECTOR_UNIT; + *map = &map_table[i]; + LOG_DBG("Found control CID 0x%08x in Selector Unit", cid); + return 0; + } + } } - entity_id = uvc_dev->vc_pud->bUnitID; - control_selector = UVC_PU_CONTRAST_CONTROL; - sys_put_le16(ctrl->val, data); - data_len = 2; - break; + } - case VIDEO_CID_HUE: - if (!uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_HUE)) { - return -ENOTSUP; + /* Search in Extension Unit if it exists */ + if (uvc_dev->vc_extension_unit) { + ret = uvc_get_control_map(UVC_VC_EXTENSION_UNIT, &map_table, &map_length); + if (ret == 0) { + for (size_t i = 0; i < map_length; i++) { + if (map_table[i].cid == cid) { + *unit_subtype = UVC_VC_EXTENSION_UNIT; + *map = &map_table[i]; + LOG_DBG("Found control CID 0x%08x in Extension Unit", cid); + return 0; + } + } } - entity_id = uvc_dev->vc_pud->bUnitID; - control_selector = UVC_PU_HUE_CONTROL; - sys_put_le16(ctrl->val, data); - data_len = 2; - break; + } - case VIDEO_CID_SATURATION: - if (!uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_SATURATION)) { - return -ENOTSUP; - } - entity_id = uvc_dev->vc_pud->bUnitID; - control_selector = UVC_PU_SATURATION_CONTROL; - sys_put_le16(ctrl->val, data); - data_len = 2; - break; + LOG_DBG("Control CID 0x%08x not found in any available unit", cid); + return -ENOENT; +} - case VIDEO_CID_SHARPNESS: - if (!uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_SHARPNESS)) { - return -ENOTSUP; - } - entity_id = uvc_dev->vc_pud->bUnitID; - control_selector = UVC_PU_SHARPNESS_CONTROL; - sys_put_le16(ctrl->val, data); - data_len = 2; - break; +/** + * @brief Get entity ID for a specific unit subtype + * + * @param uvc_dev UVC device structure + * @param unit_subtype Unit subtype + * @return Entity ID, or 0 if not found + */ +static uint8_t uvc_host_get_entity_id(struct uvc_device *uvc_dev, uint8_t unit_subtype) +{ + switch (unit_subtype) { + case UVC_VC_PROCESSING_UNIT: + return uvc_dev->vc_pud ? uvc_dev->vc_pud->bUnitID : 0; - case VIDEO_CID_GAMMA: - if (!uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_GAMMA)) { - return -ENOTSUP; - } - entity_id = uvc_dev->vc_pud->bUnitID; - control_selector = UVC_PU_GAMMA_CONTROL; - sys_put_le16(ctrl->val, data); - data_len = 2; - break; + case UVC_VC_INPUT_TERMINAL: + return uvc_dev->vc_ctd ? uvc_dev->vc_ctd->bTerminalID : 0; - case VIDEO_CID_GAIN: - if (!uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_GAIN)) { - return -ENOTSUP; - } - entity_id = uvc_dev->vc_pud->bUnitID; - control_selector = UVC_PU_GAIN_CONTROL; - sys_put_le16(ctrl->val, data); - data_len = 2; - break; + case UVC_VC_SELECTOR_UNIT: + return uvc_dev->vc_sud ? uvc_dev->vc_sud->bUnitID : 0; - case VIDEO_CID_AUTOGAIN: - /* Auto gain implemented through gain control's automatic mode */ - if (!uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_GAIN)) { - return -ENOTSUP; - } - entity_id = uvc_dev->vc_pud->bUnitID; - control_selector = UVC_PU_GAIN_CONTROL; - data[0] = ctrl->val ? 0xFF : 0x00; /* Auto mode flag */ - data_len = 1; - break; + case UVC_VC_EXTENSION_UNIT: + return uvc_dev->vc_extension_unit ? uvc_dev->vc_extension_unit->bUnitID : 0; - case VIDEO_CID_POWER_LINE_FREQUENCY: - if (!uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_POWER_LINE_FREQUENCY)) { - return -ENOTSUP; - } - entity_id = uvc_dev->vc_pud->bUnitID; - control_selector = UVC_PU_POWER_LINE_FREQUENCY_CONTROL; - data[0] = ctrl->val; /* 0=Disabled, 1=50Hz, 2=60Hz, 3=Auto */ - data_len = 1; + default: + LOG_ERR("Unknown unit subtype: %u", unit_subtype); + return 0; + } +} + +/** + * @brief Encode control value into data buffer + * + * @param ctrl Control structure with ID and value + * @param map Control mapping information + * @param data Output data buffer + * @return 0 on success, negative error code on failure + */ +static int uvc_host_encode_control_value(struct video_control *ctrl, + const struct uvc_control_map *map, + uint8_t *data) +{ + switch (ctrl->id) { + /* Special control handling */ + + /* Signed 16-bit controls */ + case VIDEO_CID_BRIGHTNESS: + case VIDEO_CID_HUE: + sys_put_le16((int16_t)ctrl->val, data); break; + /* Unsigned 16-bit controls */ + case VIDEO_CID_CONTRAST: + case VIDEO_CID_SATURATION: + case VIDEO_CID_SHARPNESS: + case VIDEO_CID_GAMMA: + case VIDEO_CID_GAIN: case VIDEO_CID_WHITE_BALANCE_TEMPERATURE: - if (!uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_WHITE_BALANCE_TEMPERATURE)) { - return -ENOTSUP; - } - entity_id = uvc_dev->vc_pud->bUnitID; - control_selector = UVC_PU_WHITE_BALANCE_TEMP_CONTROL; - sys_put_le16(ctrl->val, data); - data_len = 2; + case VIDEO_CID_BACKLIGHT_COMPENSATION: + case VIDEO_CID_FOCUS_ABSOLUTE: + case VIDEO_CID_IRIS_ABSOLUTE: + case VIDEO_CID_ZOOM_ABSOLUTE: + sys_put_le16((uint16_t)ctrl->val, data); break; + /* Boolean controls (1 = enable/auto, 0 = disable/manual) */ case VIDEO_CID_AUTO_WHITE_BALANCE: - if (!uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_WHITE_BALANCE_TEMPERATURE_AUTO)) { - return -ENOTSUP; - } - entity_id = uvc_dev->vc_pud->bUnitID; - control_selector = UVC_PU_WHITE_BALANCE_TEMP_AUTO_CONTROL; + case VIDEO_CID_FOCUS_AUTO: + case VIDEO_CID_EXPOSURE_AUTO_PRIORITY: data[0] = ctrl->val ? 1 : 0; - data_len = 1; break; - case VIDEO_CID_BACKLIGHT_COMPENSATION: - if (!uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_BACKLIGHT_COMPENSATION)) { - return -ENOTSUP; - } - entity_id = uvc_dev->vc_pud->bUnitID; - control_selector = UVC_PU_BACKLIGHT_COMPENSATION_CONTROL; - sys_put_le16(ctrl->val, data); - data_len = 2; - break; - - /* Camera Terminal Controls */ + /* Direct 8-bit controls */ case VIDEO_CID_EXPOSURE_AUTO: - if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_AE_MODE)) { - entity_id = uvc_dev->vc_ctd->bTerminalID; - control_selector = UVC_CT_AE_MODE_CONTROL; - data[0] = ctrl->val; - data_len = 1; - } else { - LOG_WRN("Auto exposure mode control not supported"); - ret = -ENOTSUP; - } - break; - - case VIDEO_CID_EXPOSURE_AUTO_PRIORITY: - if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_AE_PRIORITY)) { - entity_id = uvc_dev->vc_ctd->bTerminalID; - control_selector = UVC_CT_AE_PRIORITY_CONTROL; - data[0] = ctrl->val; - data_len = 1; - } else { - LOG_WRN("Auto exposure priority control not supported"); - ret = -ENOTSUP; - } + case VIDEO_CID_IRIS_RELATIVE: + data[0] = (uint8_t)ctrl->val; break; - case VIDEO_CID_EXPOSURE_ABSOLUTE: - if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_EXPOSURE_TIME_ABSOLUTE)) { - entity_id = uvc_dev->vc_ctd->bTerminalID; - control_selector = UVC_CT_EXPOSURE_TIME_ABS_CONTROL; - sys_put_le32(ctrl->val, data); - data_len = 4; - } else { - LOG_WRN("Exposure absolute control not supported"); - ret = -ENOTSUP; - } + /* Special auto gain (0xFF = auto, 0x00 = manual) */ + case VIDEO_CID_AUTOGAIN: + data[0] = ctrl->val ? 0xFF : 0x00; break; - case VIDEO_CID_FOCUS_ABSOLUTE: - if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_FOCUS_ABSOLUTE)) { - entity_id = uvc_dev->vc_ctd->bTerminalID; - control_selector = UVC_CT_FOCUS_ABS_CONTROL; - sys_put_le16(ctrl->val, data); - data_len = 2; - } else { - LOG_WRN("Focus absolute control not supported"); - ret = -ENOTSUP; + /* Power line frequency with range check */ + case VIDEO_CID_POWER_LINE_FREQUENCY: + if (ctrl->val < 0 || ctrl->val > 3) { + return -EINVAL; } + data[0] = (uint8_t)ctrl->val; break; - case VIDEO_CID_FOCUS_AUTO: - if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_FOCUS_AUTO)) { - entity_id = uvc_dev->vc_ctd->bTerminalID; - control_selector = UVC_CT_FOCUS_AUTO_CONTROL; - data[0] = ctrl->val; - data_len = 1; - } else { - LOG_WRN("Auto focus control not supported"); - ret = -ENOTSUP; - } + /* 32-bit controls */ + case VIDEO_CID_EXPOSURE_ABSOLUTE: + sys_put_le32((uint32_t)ctrl->val, data); break; + /* Multi-byte relative controls */ case VIDEO_CID_FOCUS_RELATIVE: - if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_FOCUS_RELATIVE)) { - entity_id = uvc_dev->vc_ctd->bTerminalID; - control_selector = UVC_CT_FOCUS_REL_CONTROL; - sys_put_le16(ctrl->val, data); - data_len = 2; - } else { - LOG_WRN("Focus relative control not supported"); - ret = -ENOTSUP; - } - break; - - case VIDEO_CID_ZOOM_ABSOLUTE: - if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_ZOOM_ABSOLUTE)) { - entity_id = uvc_dev->vc_ctd->bTerminalID; - control_selector = UVC_CT_ZOOM_ABS_CONTROL; - sys_put_le16(ctrl->val, data); - data_len = 2; - } else { - LOG_WRN("Zoom absolute control not supported"); - ret = -ENOTSUP; - } + data[0] = (int8_t)ctrl->val; /* focus value */ + data[1] = 0x01; /* speed */ break; case VIDEO_CID_ZOOM_RELATIVE: - if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_ZOOM_RELATIVE)) { - entity_id = uvc_dev->vc_ctd->bTerminalID; - control_selector = UVC_CT_ZOOM_REL_CONTROL; - data[0] = ctrl->val; /* zoom value */ - data[1] = 0x00; /* digital zoom (not used) */ - data[2] = 0x01; /* speed */ - data_len = 3; - } else { - LOG_WRN("Zoom relative control not supported"); - ret = -ENOTSUP; - } + data[0] = (int8_t)ctrl->val; /* zoom value */ + data[1] = 0x00; /* digital zoom (not used) */ + data[2] = 0x01; /* speed */ break; case VIDEO_CID_TILT_RELATIVE: - if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_PAN_TILT_RELATIVE)) { - entity_id = uvc_dev->vc_ctd->bTerminalID; - control_selector = UVC_CT_PANTILT_REL_CONTROL; - data[0] = 0x00; /* pan relative (LSB) */ - data[1] = 0x00; /* pan relative (MSB) */ - sys_put_le16(ctrl->val, &data[2]); /* tilt relative */ - data_len = 4; - } else { - LOG_WRN("Tilt relative control not supported"); - ret = -ENOTSUP; - } + data[0] = 0x00; /* pan relative (LSB) */ + data[1] = 0x00; /* pan relative (MSB) */ + sys_put_le16((int16_t)ctrl->val, &data[2]); /* tilt relative */ break; - case VIDEO_CID_IRIS_ABSOLUTE: - if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_IRIS_ABSOLUTE)) { - entity_id = uvc_dev->vc_ctd->bTerminalID; - control_selector = UVC_CT_IRIS_ABS_CONTROL; - sys_put_le16(ctrl->val, data); - data_len = 2; - } else { - LOG_WRN("Iris absolute control not supported"); - ret = -ENOTSUP; - } - break; + default: + /* Standard encoding logic based on mapping table */ + switch (map->size) { + case 1: + data[0] = (uint8_t)ctrl->val; + break; - case VIDEO_CID_IRIS_RELATIVE: - if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_IRIS_RELATIVE)) { - entity_id = uvc_dev->vc_ctd->bTerminalID; - control_selector = UVC_CT_IRIS_REL_CONTROL; - data[0] = ctrl->val; /* iris value */ - data_len = 1; - } else { - LOG_WRN("Iris relative control not supported"); - ret = -ENOTSUP; + case 2: + if (map->type == UVC_CONTROL_SIGNED) { + sys_put_le16((int16_t)ctrl->val, data); + } else { + sys_put_le16((uint16_t)ctrl->val, data); + } + break; + + case 3: + data[0] = (uint8_t)(ctrl->val & 0xFF); + data[1] = (uint8_t)((ctrl->val >> 8) & 0xFF); + data[2] = (uint8_t)((ctrl->val >> 16) & 0xFF); + break; + + case 4: + if (map->type == UVC_CONTROL_SIGNED) { + sys_put_le32((int32_t)ctrl->val, data); + } else { + sys_put_le32((uint32_t)ctrl->val, data); + } + break; + + default: + return -EINVAL; } break; + } + + return 0; +} + +/** + * @brief Check if control is supported by checking the corresponding bit + * + * @param uvc_dev UVC device structure + * @param unit_subtype Unit subtype + * @param entity_id Entity ID + * @param control_bit Control bit to check + * @return true if supported, false otherwise + */ +static bool uvc_host_is_control_supported(struct uvc_device *uvc_dev, uint8_t unit_subtype, + uint8_t entity_id, uint32_t control_bit) +{ + switch (unit_subtype) { + case UVC_VC_PROCESSING_UNIT: + return uvc_host_pu_supports_control(uvc_dev, control_bit); + + case UVC_VC_INPUT_TERMINAL: + return uvc_host_ct_supports_control(uvc_dev, control_bit); + + case UVC_VC_SELECTOR_UNIT: + /* TODO: Check selector unit's bmControls field from descriptor */ + return true; + + case UVC_VC_EXTENSION_UNIT: + /* TODO: Check extension unit's bmControls field from descriptor */ + return true; default: - LOG_ERR("Unknown control ID: %u", ctrl->id); + return false; + } +} + +/** + * @brief Set UVC control value + * + * @param dev Pointer to video device + * @param id Control ID to set + * @return 0 on success, negative error code on failure + */ +static int video_usb_uvc_host_set_ctrl(const struct device *dev, uint32_t id) +{ + struct uvc_device *uvc_dev = dev->data; + const struct uvc_control_map *map; + struct video_ctrl *ctrl = NULL; + uint8_t unit_subtype; + uint8_t entity_id; + uint8_t data[4] = {0}; + int ret; + + if (!uvc_dev || !dev) { + LOG_ERR("Invalid parameters: dev=%p", dev); return -EINVAL; } - /* Send control request if parameters are valid and no error occurred */ - if (ret == 0 && entity_id != 0 && control_selector != 0 && data_len > 0) { - ret = uvc_host_control_unit_and_terminal_request(uvc_dev, UVC_SET_CUR, control_selector, - entity_id, data, data_len); + if (!uvc_dev->connected) { + LOG_ERR("UVC device not connected"); + return -ENODEV; } - return ret; + ret = video_find_ctrl(dev, id, &ctrl); + if (ret) { + LOG_ERR("Control ID 0x%08x not found", id); + return ret; + } + + LOG_DBG("Setting control CID 0x%08x to value %d", id, ctrl->val); + + /* Find control mapping in mapping tables */ + ret = uvc_host_find_control_mapping(uvc_dev, id, &unit_subtype, &map); + if (ret) { + LOG_ERR("Control CID 0x%08x not found in mapping tables", id); + return -EINVAL; + } + + /* Get entity ID for this unit type */ + entity_id = uvc_host_get_entity_id(uvc_dev, unit_subtype); + if (entity_id == 0) { + LOG_ERR("Entity not found for unit subtype %u", unit_subtype); + return -ENODEV; + } + + /* Check if device supports this control */ + uint32_t control_bit = BIT(map->bit); + + if (!uvc_host_is_control_supported(uvc_dev, unit_subtype, entity_id, control_bit)) { + LOG_WRN("Control CID 0x%08x (bit %u) not supported by device", id, map->bit); + return -ENOTSUP; + } + + /* Encode control value (handles both special and standard controls) */ + ret = uvc_host_encode_control_value(ctrl, map, data); + if (ret) { + LOG_ERR("Failed to encode control value %d: %d", ctrl->val, ret); + return ret; + } + + /* Send control request */ + ret = uvc_host_control_unit_and_terminal_request(uvc_dev, UVC_SET_CUR, map->selector, + entity_id, data, map->size); + if (ret) { + LOG_ERR("Failed to set control CID 0x%08x to value %d: %d", + ctrl->id, ctrl->val, ret); + return ret; + } + + LOG_DBG("Successfully set control CID 0x%08x to value %d", ctrl->id, ctrl->val); + return 0; } /** From 7af56c4e87a6bf16582eb247d84606bdbb1e81ec Mon Sep 17 00:00:00 2001 From: Aiden Hu Date: Wed, 17 Sep 2025 16:10:15 +0800 Subject: [PATCH 31/31] drivers: video: expose video_find_ctrl by removing static keyword video_find_ctrl becomes an API. Signed-off-by: Aiden Hu --- drivers/video/video_ctrls.c | 2 +- drivers/video/video_ctrls.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/video/video_ctrls.c b/drivers/video/video_ctrls.c index cba92100967c2..e8014109c0c1c 100644 --- a/drivers/video/video_ctrls.c +++ b/drivers/video/video_ctrls.c @@ -270,7 +270,7 @@ void video_auto_cluster_ctrl(struct video_ctrl *ctrls, uint8_t sz, bool set_vola } } -static int video_find_ctrl(const struct device *dev, uint32_t id, struct video_ctrl **ctrl) +int video_find_ctrl(const struct device *dev, uint32_t id, struct video_ctrl **ctrl) { struct video_device *vdev = video_find_vdev(dev); diff --git a/drivers/video/video_ctrls.h b/drivers/video/video_ctrls.h index e788ff042ca98..e3f87a309368f 100644 --- a/drivers/video/video_ctrls.h +++ b/drivers/video/video_ctrls.h @@ -72,6 +72,8 @@ int video_init_menu_ctrl(struct video_ctrl *ctrl, const struct device *dev, uint int video_init_int_menu_ctrl(struct video_ctrl *ctrl, const struct device *dev, uint32_t id, uint8_t def, const int64_t menu[], size_t menu_len); +int video_find_ctrl(const struct device *dev, uint32_t id, struct video_ctrl **ctrl); + void video_cluster_ctrl(struct video_ctrl *ctrls, uint8_t sz); void video_auto_cluster_ctrl(struct video_ctrl *ctrls, uint8_t sz, bool set_volatile);