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 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 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); 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>; 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/subsys/usb/device_next/class/usbd_uvc.h b/include/zephyr/usb/class/usb_uvc.h similarity index 77% rename from subsys/usb/device_next/class/usbd_uvc.h rename to include/zephyr/usb/class/usb_uvc.h index 3364f83258d11..aaa3bc57c26c8 100644 --- a/subsys/usb/device_next/class/usbd_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,7 +509,18 @@ 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 + uint32_t dwFrameInterval[1]; +#endif +} __packed; + +struct uvc_cs_descriptor_header { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; } __packed; struct uvc_color_descriptor { @@ -481,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_ */ diff --git a/include/zephyr/usb/usbh.h b/include/zephyr/usb/usbh.h index 852d8b9ac6986..b1538e7b34bbc 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 */ @@ -33,27 +34,58 @@ extern "C" { * @{ */ +/** + * @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 + * @ingroup usb + * @{ + */ + +/** + * 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 */ -struct usbh_contex { +struct usbh_context { /** Name of the USB device */ const char *name; /** Access mutex */ 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 */ 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) \ 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, \ @@ -72,35 +104,129 @@ 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_contex *const uhs_ctx); */ + int (*init)(struct usbh_class_data *cdata); /** Request completion event handler */ - int (*request)(struct usbh_contex *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_contex *const uhs_ctx); + 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_contex *const uhs_ctx); + int (*removed)(struct usb_device *udev, struct usbh_class_data *cdata); /** Bus remote wakeup handler */ - int (*rwup)(struct usbh_contex *const uhs_ctx); + int (*rwup)(struct usbh_class_data *cdata); /** Bus suspended handler */ - int (*suspended)(struct usbh_contex *const uhs_ctx); + int (*suspended)(struct usbh_class_data *cdata); /** Bus resumed handler */ - int (*resumed)(struct usbh_contex *const uhs_ctx); + 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 + */ +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; + /** System linked list node for registered classes */ + sys_snode_t node; + /** 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 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; @@ -109,7 +235,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 +246,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 +257,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 +268,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/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 */ 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..63ec5bd1a4536 --- /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=116000 +CONFIG_VIDEO_BUFFER_POOL_NUM_MAX=6 +CONFIG_VIDEO_BUFFER_POOL_ALIGN=32 + + +# Memory configuration for USB transfers +CONFIG_HEAP_MEM_POOL_SIZE=40960 +CONFIG_MAIN_STACK_SIZE=10240 +CONFIG_USBH_USB_DEVICE_HEAP=8912 +CONFIG_UHC_BUF_POOL_SIZE=4096 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..cab0b4f4f60db 100644 --- a/samples/drivers/video/capture/prj.conf +++ b/samples/drivers/video/capture/prj.conf @@ -1,8 +1,16 @@ -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=180 +CONFIG_VIDEO_TARGET_FPS=15 +CONFIG_USBH_VIDEO_MULTIPLE_PRIME_COUNT=8 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..bd1abc61d6c0a 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,305 @@ 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 (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_HFLIP)) { - video_set_ctrl(video_dev, &ctrl); - } +#if CONFIG_VIDEO_FRAME_HEIGHT > 0 + fmt.height = CONFIG_VIDEO_FRAME_HEIGHT; +#endif - if (IS_ENABLED(CONFIG_VIDEO_CTRL_VFLIP)) { - ctrl.id = VIDEO_CID_VFLIP; - 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++; + } + +#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 - if (IS_ENABLED(CONFIG_TEST)) { - ctrl.id = VIDEO_CID_TEST_PATTERN; - tp_set_ret = video_set_ctrl(video_dev, &ctrl); - } + /* 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; + } + + 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; + } } } } 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:~$ 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 */ 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 9038a8e0d3f15..b13aea88d0b73 100644 --- a/subsys/usb/device_next/class/usbd_uvc.c +++ b/subsys/usb/device_next/class/usbd_uvc.c @@ -24,8 +24,9 @@ #include #include #include +#include -#include "usbd_uvc.h" +#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; diff --git a/subsys/usb/host/CMakeLists.txt b/subsys/usb/host/CMakeLists.txt index f48f5b8c71483..47f45352abaa1 100644 --- a/subsys/usb/host/CMakeLists.txt +++ b/subsys/usb/host/CMakeLists.txt @@ -5,9 +5,11 @@ 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_desc.c usbh_device.c ) @@ -16,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..3c81e45b67e23 --- /dev/null +++ b/subsys/usb/host/class/Kconfig.uvc_host @@ -0,0 +1,70 @@ +# +# 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. + +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. + +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 +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..fe7819e3e1be1 --- /dev/null +++ b/subsys/usb/host/class/usbh_uvc.c @@ -0,0 +1,4023 @@ +/* + * 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 + +#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); + +/** + * @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 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; + /** 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 */ + 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 */ + struct usb_camera_ctrls ctrls; + /** Collection of all available alternate streaming interfaces */ + 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_control_header_descriptor *vc_header; + /** Video Control Input Terminal descriptor from device */ + struct uvc_input_terminal_descriptor *vc_itd; + /** Video Control Output Terminal descriptor from device */ + struct uvc_output_terminal_descriptor *vc_otd; + /** Video Control Camera Terminal descriptor from device */ + struct uvc_camera_terminal_descriptor *vc_ctd; + /** Video Control Selector Unit descriptor from device */ + struct uvc_selector_unit_descriptor *vc_sud; + /** Video Control Processing Unit descriptor from device */ + struct uvc_processing_unit_descriptor *vc_pud; + /** Video Control Encoding Unit descriptor from device */ + struct uvc_encoding_unit_descriptor *vc_encoding_unit; + /** Video Control Extension Unit descriptor from device */ + struct uvc_extension_unit_descriptor *vc_extension_unit; + + /** Video Stream Input Header descriptor from device */ + struct uvc_stream_input_header_descriptor *vs_input_header; + /** Video Stream Output Header descriptor from device */ + struct uvc_stream_output_header_descriptor *vs_output_header; + /** Available format groups parsed from descriptors */ + struct uvc_format_info_all formats; + /** Currently selected video 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 video_probe; +}; + +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 + * + * 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; + + void *format_ptr; + uint8_t format_index, frame_subtype; + uint32_t pixelformat; + bool is_mjpeg; + + /* 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; + } + + LOG_ERR("No valid format/frame descriptors found"); + return -ENOTSUP; + +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"); + return -ENOTSUP; +} + +/** + * @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 < 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) { + 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 uvc_cs_descriptor_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 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) { + break; + } + + if (header->bDescriptorType == USB_DESC_CS_INTERFACE) { + switch (header->bDescriptorSubType) { + case UVC_VC_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_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_control_header_descriptor) + + header_desc->bInCollection)) { + LOG_ERR("VC header descriptor too short for interface collection: %u < %u", + header->bLength, + (unsigned)(sizeof(struct uvc_control_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_input_terminal_descriptor *it_desc = + (struct uvc_input_terminal_descriptor *)header; + + /* Check descriptor length - at least basic fields */ + 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_camera_terminal_descriptor *ct_desc = + (struct uvc_camera_terminal_descriptor *)header; + + /* Check Camera Terminal descriptor length */ + 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; + } + + /* 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_output_terminal_descriptor *ot_desc = + (struct uvc_output_terminal_descriptor *)header; + + /* Check descriptor length - at least basic fields */ + if (header->bLength < sizeof(struct uvc_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_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 */ + 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_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 */ + 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_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 */ + 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_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 */ + 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 uvc_cs_descriptor_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 uvc_cs_descriptor_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 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) { + break; + } + + if (header->bDescriptorType == USB_DESC_CS_INTERFACE) { + switch (header->bDescriptorSubType) { + case UVC_VS_INPUT_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_stream_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_stream_output_header_descriptor *header_desc = (struct uvc_stream_output_header_descriptor *)header; + + /* Check descriptor length */ + if (header->bLength < sizeof(struct uvc_stream_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_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_format_uncomp_descriptor)) { + LOG_ERR("Invalid uncompressed format descriptor length: %u", header->bLength); + return -EINVAL; + } + + /* Check array space */ + 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; + } + + /* 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_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_format_mjpeg_descriptor)) { + LOG_ERR("Invalid MJPEG format descriptor length: %u", header->bLength); + return -EINVAL; + } + + /* Check array space */ + 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; + } + + /* 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 uvc_cs_descriptor_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 default frame interval from descriptor + * + * Extracts the default frame interval from frame descriptor. If default + * interval is invalid (0), falls back to maximum supported interval. + * + * @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(struct uvc_frame_descriptor *frame_desc) +{ + 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; + } + + /* Default interval is invalid, find maximum supported interval */ + uint32_t max_interval = 333333; /* Fallback to 30fps */ + + 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; +} + +/** + * @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_descriptor_header *format_header, + uint16_t target_width, + uint16_t target_height, + uint8_t expected_frame_subtype, + struct uvc_frame_descriptor **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_descriptor *frame_header = (struct uvc_frame_descriptor *)desc_buf; + + if (frame_header->bLength == 0) { + break; + } + + /* Check if this is the expected frame descriptor type */ + if (frame_header->bDescriptorType == USB_DESC_CS_INTERFACE && + frame_header->bDescriptorSubType == expected_frame_subtype) { + + 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 = uvc_host_parse_frame_default_intervals(frame_header); + return 0; + } + 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; + } + + 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_descriptor_header **format, + struct uvc_frame_descriptor **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_format_uncomp_descriptor *format_desc = uncompressed_info->uncompressed_format[i]; + + if (!format_desc) continue; + + /* Convert GUID to pixel format for comparison */ + 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); + + /* Search for matching frame in this format */ + 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_descriptor_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_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_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_descriptor_header *)format_desc, + width, height, UVC_VS_FRAME_MJPEG, + frame, frame_interval) == 0) { + *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; + } + } + } + + LOG_ERR("Format %s %ux%u not supported by device", + VIDEO_FOURCC_TO_STR(pixelformat), width, height); + return -ENOTSUP; +} + +/** + * @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 *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; + 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 */ + 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) */ + 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 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); + ep_payload_size = max_packet_size * mult; + ep_bandwidth = (ep_payload_size * 8000) / interval_uframes; + } else { + 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, payload=%u, bandwidth=%u", + if_desc->bInterfaceNumber, if_desc->bAlternateSetting, ep, + 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("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, 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 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 = 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 (bandwidth=%u, payload=%u)", + selected_interface->bInterfaceNumber, selected_interface->bAlternateSetting, + selected_endpoint->bEndpointAddress, optimal_bandwidth, selected_payload_size); + + 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_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: + /* 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 + 99) / 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_descriptor_header *format; + struct uvc_frame_descriptor *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; + uvc_dev->current_format.frame_index = frame->bFrameIndex; + uvc_dev->current_format.frame_interval = frame_interval; + 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) { + 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; + uint32_t required_bandwidth; + 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_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_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 { + /* 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 */ + struct uvc_frame_discrete_descriptor *discrete_desc = (struct uvc_frame_discrete_descriptor *)frame_desc; + + 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); + } + } + } + + /* 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); + + /* 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; +} + +/** + * @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 + * + * 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_format_uncomp_descriptor *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_format_mjpeg_descriptor *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)); + + /* Process uncompressed formats */ + for (int i = 0; i < uncompressed_info->num_uncompressed_formats; i++) { + struct uvc_format_uncomp_descriptor *format = uncompressed_info->uncompressed_format[i]; + if (!format) continue; + + /* Get pixel format from GUID */ + uint32_t pixelformat = uvc_guid_to_fourcc(format->guidFormat); + if (pixelformat == 0) { + LOG_WRN("Unsupported GUID format in format index %u", format->bFormatIndex); + continue; + } + + /* 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); + } + + /* Process MJPEG formats */ + for (int i = 0; i < mjpeg_info->num_mjpeg_formats; i++) { + struct uvc_format_mjpeg_descriptor *format = mjpeg_info->vs_mjpeg_format[i]; + if (!format) continue; + + /* 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); + 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 = 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) { + /* 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) { + memcpy(vbuf->buffer + vbuf->bytesused, buf->data + header_len, payload_len); + } + + /* Return number of payload bytes processed */ + 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; + 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; +} + +/** + * @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; + 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 + * + * 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 + * @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 = uvc_dev->current_vbuf; + struct uvc_payload_header *payload_header; + uint32_t headLength; + uint32_t dataSize; + uint8_t endOfFrame; + 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) { + LOG_ERR("Invalid callback parameters"); + return -EINVAL; + } + + /* 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); + goto cleanup; + } + + 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; + } + } + } + } + +cleanup: + /* Release network buffer reference */ + net_buf_unref(buf); + /* Continue processing pending buffers */ + if (vbuf) { + uvc_host_continue_transfer(uvc_dev, xfer, vbuf); + } + return 0; +} + +/** + * @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_descriptor *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 << 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, + 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; +} + +/** + * @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 individual control based on CID + * + * @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_init_control_by_cid(const struct device *dev, uint32_t cid, + const struct video_ctrl_range *range) +{ + struct uvc_device *uvc_dev = dev->data; + struct usb_camera_ctrls *ctrls = &uvc_dev->ctrls; + int ret = -ENOTSUP; + + switch (cid) { + /* Processing Unit Controls */ + case VIDEO_CID_BRIGHTNESS: + ret = video_init_ctrl(&ctrls->brightness, dev, cid, *range); + break; + + case VIDEO_CID_CONTRAST: + ret = video_init_ctrl(&ctrls->contrast, dev, cid, *range); + break; + + case VIDEO_CID_HUE: + ret = video_init_ctrl(&ctrls->hue, dev, cid, *range); + break; + + 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; + } + /* 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; +} + +/** + * @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; + } + + /*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; + + /* 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; +} + +/** 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; + 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 Find control mapping by CID based on available units + * + * @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 uvc_host_find_control_mapping(struct uvc_device *uvc_dev, uint32_t cid, + uint8_t *unit_subtype, + const struct uvc_control_map **map) +{ + const struct uvc_control_map *map_table; + size_t map_length; + int ret; + + /* 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; + } + } + } + } + + /* 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; + } + } + } + } + + /* 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; + } + } + } + } + + /* 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; + } + } + } + } + + LOG_DBG("Control CID 0x%08x not found in any available unit", cid); + return -ENOENT; +} + +/** + * @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 UVC_VC_INPUT_TERMINAL: + return uvc_dev->vc_ctd ? uvc_dev->vc_ctd->bTerminalID : 0; + + case UVC_VC_SELECTOR_UNIT: + return uvc_dev->vc_sud ? uvc_dev->vc_sud->bUnitID : 0; + + case UVC_VC_EXTENSION_UNIT: + return uvc_dev->vc_extension_unit ? uvc_dev->vc_extension_unit->bUnitID : 0; + + 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: + 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: + case VIDEO_CID_FOCUS_AUTO: + case VIDEO_CID_EXPOSURE_AUTO_PRIORITY: + data[0] = ctrl->val ? 1 : 0; + break; + + /* Direct 8-bit controls */ + case VIDEO_CID_EXPOSURE_AUTO: + case VIDEO_CID_IRIS_RELATIVE: + data[0] = (uint8_t)ctrl->val; + break; + + /* Special auto gain (0xFF = auto, 0x00 = manual) */ + case VIDEO_CID_AUTOGAIN: + data[0] = ctrl->val ? 0xFF : 0x00; + break; + + /* 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; + + /* 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: + data[0] = (int8_t)ctrl->val; /* focus value */ + data[1] = 0x01; /* speed */ + break; + + case VIDEO_CID_ZOOM_RELATIVE: + 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: + data[0] = 0x00; /* pan relative (LSB) */ + data[1] = 0x00; /* pan relative (MSB) */ + sys_put_le16((int16_t)ctrl->val, &data[2]); /* tilt relative */ + break; + + default: + /* Standard encoding logic based on mapping table */ + switch (map->size) { + case 1: + data[0] = (uint8_t)ctrl->val; + break; + + 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: + 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; + } + + if (!uvc_dev->connected) { + LOG_ERR("UVC device not connected"); + return -ENODEV; + } + + 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; +} + +/** + * @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 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; + 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 (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; + + if (enable) + { + 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; + while (uvc_dev->multi_prime_cnt) + { + uvc_host_initiate_transfer(uvc_dev, vbuf); + uvc_dev->multi_prime_cnt--; + } + } + } + + LOG_DBG("UVC streaming %s successfully", enable ? "enabled" : "disabled"); + return 0; +} + +/** + * @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; + 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; + vbuf->timestamp = 0; + vbuf->line_offset = 0; + + k_fifo_put(&uvc_dev->fifo_in, 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; + 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) { + ret = usbh_xfer_dequeue(uvc_dev->udev, xfer); + if (ret != 0) { + return ret; + } + } + + 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..14f2a2cfe9ed2 --- /dev/null +++ b/subsys/usb/host/class/usbh_uvc.h @@ -0,0 +1,54 @@ +/* + * 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 + +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_format_info { + 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[CONFIG_USBH_VIDEO_MAX_FORMATS]; + uint8_t num_mjpeg_formats; +}; + +struct uvc_vs_format_uncompressed_info { + struct uvc_vs_format_uncompressed *uncompressed_format[CONFIG_USBH_VIDEO_MAX_FORMATS]; + uint8_t num_uncompressed_formats; +}; + +/* Video stream format information structure */ +struct uvc_format_info_all { + struct uvc_vs_format_mjpeg_info format_mjpeg; + struct uvc_vs_format_uncompressed_info format_uncompressed; +}; + +#endif /* ZEPHYR_INCLUDE_USBH_CLASS_UVC_H_ */ diff --git a/subsys/usb/host/usbh_api.c b/subsys/usb/host/usbh_api.c index e1aa6e99ac3b9..8501ee044b2ad 100644 --- a/subsys/usb/host/usbh_api.c +++ b/subsys/usb/host/usbh_api.c @@ -6,16 +6,17 @@ #include #include +#include "usbh_host.h" #include "usbh_internal.h" #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; - 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,15 +33,15 @@ int usbh_init(struct usbh_contex *uhs_ctx) ret = usbh_init_device_intl(uhs_ctx); init_exit: - k_mutex_unlock(&uhs_ctx->mutex); + usbh_host_unlock(uhs_ctx); return ret; } -int usbh_enable(struct usbh_contex *uhs_ctx) +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,11 +62,11 @@ int usbh_enable(struct usbh_contex *uhs_ctx) } enable_exit: - k_mutex_unlock(&uhs_ctx->mutex); + usbh_host_unlock(uhs_ctx); return ret; } -int usbh_disable(struct usbh_contex *uhs_ctx) +int usbh_disable(struct usbh_context *uhs_ctx) { int ret; @@ -74,30 +75,30 @@ int usbh_disable(struct usbh_contex *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; } -int usbh_shutdown(struct usbh_contex *const uhs_ctx) +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_class.c b/subsys/usb/host/usbh_class.c new file mode 100644 index 0000000000000..6794aa1f662ec --- /dev/null +++ b/subsys/usb/host/usbh_class.c @@ -0,0 +1,95 @@ +/* + * 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; +} + +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 new file mode 100644 index 0000000000000..0f96d17a95f64 --- /dev/null +++ b/subsys/usb/host/usbh_class.h @@ -0,0 +1,34 @@ +/* + * 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); + +/** + * @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_class_api.h b/subsys/usb/host/usbh_class_api.h new file mode 100644 index 0000000000000..a50473eec03aa --- /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 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(udev, 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 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(udev, 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 */ diff --git a/subsys/usb/host/usbh_core.c b/subsys/usb/host/usbh_core.c index c18ef39cb63cf..12c42590a3600 100644 --- a/subsys/usb/host/usbh_core.c +++ b/subsys/usb/host/usbh_core.c @@ -10,9 +10,13 @@ #include #include #include +#include -#include "usbh_internal.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); @@ -43,7 +47,183 @@ static int usbh_event_carrier(const struct device *dev, return err; } -static void dev_connected_handler(struct usbh_contex *const ctx, +/** + * @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, desc_buf_end, 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; + iad_desc = desc; + } + + 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 */ + 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(search_start, desc_buf_end, 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; + + /* Get class code from first interface after IAD */ + 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; + + 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 */ + mask = BIT(USB_DESC_INTERFACE); + desc = usbh_desc_get_by_type(search_start, desc_buf_end, 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; + + /* 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; + } + } + + 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(udev, 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) { @@ -71,11 +251,32 @@ static void dev_connected_handler(struct usbh_contex *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_contex *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"); @@ -84,7 +285,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 +298,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 +342,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 +359,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 +383,10 @@ static void usbh_thread(void *p1, void *p2, void *p3) } } -int usbh_init_device_intl(struct usbh_contex *const uhs_ctx) +/** + * @brief Initialize USB host controller and class drivers + */ +int usbh_init_device_intl(struct usbh_context *const uhs_ctx) { int ret; @@ -193,13 +397,18 @@ int usbh_init_device_intl(struct usbh_contex *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; 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_desc.c b/subsys/usb/host/usbh_desc.c new file mode 100644 index 0000000000000..c03cb0714d891 --- /dev/null +++ b/subsys/usb/host/usbh_desc.c @@ -0,0 +1,30 @@ +/* + * 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; + } + curr_addr += desc->bLength; + } + + 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 */ diff --git a/subsys/usb/host/usbh_device.c b/subsys/usb/host/usbh_device.c index 13a2a81ccd2c1..76f70b2734148 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; @@ -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"); @@ -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_host.h b/subsys/usb/host/usbh_host.h new file mode 100644 index 0000000000000..5dc8e6070d60e --- /dev/null +++ b/subsys/usb/host/usbh_host.h @@ -0,0 +1,57 @@ +/* + * 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; +} + +/** + * @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 */ 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; };