Skip to content

Conversation

@AidenHu
Copy link
Contributor

@AidenHu AidenHu commented Aug 4, 2025

Dependency:
USB Host: integrate class API [4: aggregated] #99775

  1. Add USB host video class driver usbh_uvc.c/h . This class driver is implemented based on UVC 1.5
  2. The USB host video class driver is tightly coupled with the video subsystem. Users still use video API to operate it.
  3. Some key changes for usbh_core.c to support multiple classes that one USB device supports.
  4. Some necessary changes to make video capture example connect with USB device camera based on the new USB host video class driver.

@github-actions
Copy link

github-actions bot commented Aug 4, 2025

Hello @AidenHu, and thank you very much for your first pull request to the Zephyr project!
Our Continuous Integration pipeline will execute a series of checks on your Pull Request commit messages and code, and you are expected to address any failures by updating the PR. Please take a look at our commit message guidelines to find out how to format your commit messages, and at our contribution workflow to understand how to update your Pull Request. If you haven't already, please make sure to review the project's Contributor Expectations and update (by amending and force-pushing the commits) your pull request if necessary.
If you are stuck or need help please join us on Discord and ask your question there. Additionally, you can escalate the review when applicable. 😊

@AidenHu AidenHu force-pushed the enable_usb_host_video_class branch 2 times, most recently from 669c434 to 4ca6d07 Compare August 5, 2025 08:50
@dleach02 dleach02 added the area: USB Universal Serial Bus label Aug 5, 2025
uvc_host: uvc_host {
compatible = "zephyr,uvc-host";
};
}; No newline at end of file
Copy link
Member

@dleach02 dleach02 Aug 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need blank line (repeat this on a couple of the files)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need blank line (repeat this on a couple of the files)

Thanks, done it.

#include <zephyr/kernel.h>
#include <zephyr/device.h>

#include <zephyr/drivers/display.h>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is draft but I think we want to allow this sample to support Video host and video device

@dleach02
Copy link
Member

dleach02 commented Aug 5, 2025

@AidenHu you are going to need to break this up into multiple commits. My suggestion is:

  1. subsys/usb/host, dts binding, and usbh.h changes
  2. RW612 board/soc changes
  3. sample changes

@AidenHu
Copy link
Contributor Author

AidenHu commented Aug 6, 2025

@AidenHu you are going to need to break this up into multiple commits. My suggestion is:

1. subsys/usb/host, dts binding, and usbh.h changes

2. RW612 board/soc changes

3. sample changes

Thanks @dleach02, I will split now.

@AidenHu AidenHu force-pushed the enable_usb_host_video_class branch 2 times, most recently from cb3ab4e to 76bed79 Compare August 6, 2025 02:59
@carlescufi carlescufi requested a review from josuah August 6, 2025 10:33
@AidenHu AidenHu force-pushed the enable_usb_host_video_class branch from 76bed79 to 9192550 Compare August 7, 2025 02:41
@AidenHu AidenHu force-pushed the enable_usb_host_video_class branch from 9192550 to 6e8acc7 Compare August 14, 2025 14:58
@josuah
Copy link
Contributor

josuah commented Aug 14, 2025

Thank you for updating this PR with a more fine-grained commit history.

In parallel, I opened #94504 to add the missing USB Host API bits to enable this class to be implemented, using the content of this current PR:

And here is this current PR nxp-upstream:enable_usb_host_video_class but rebased on top of pr_usb_host_class_api2:

@AidenHu AidenHu closed this Aug 15, 2025
@AidenHu AidenHu reopened this Aug 15, 2025
@AidenHu
Copy link
Contributor Author

AidenHu commented Aug 15, 2025

Thank you for updating this PR with a more fine-grained commit history.
In parallel, I opened #94504 to add the missing USB Host API bits to enable this class to be implemented, using the content of this current PR.
For now pr_usb_host_class_api (#94504), pr_usb_host_class_api2, pr_usb_host_class_api3 etc.
And here is this current PR nxp-upstream:enable_usb_host_video_class but rebased on top of pr_usb_host_class_api2:

* [main...josuah:zephyr:enable_usb_host_video_class](https://github.com/zephyrproject-rtos/zephyr/compare/main...josuah:zephyr:enable_usb_host_video_class)

Thank you for the reply, @josuah

@AidenHu AidenHu force-pushed the enable_usb_host_video_class branch 3 times, most recently from aef5e85 to 629e311 Compare August 16, 2025 14:02
@josuah
Copy link
Contributor

josuah commented Aug 18, 2025

Hello @AidenHu,

Your current PR implements everything at once, which is impressive, but we have no guarantee that the USB host API will look like this in the final version, so maybe we will need to update the current PR many times.

I used some of your code to make 3 small PRs to make an USB host class API. Hopefully these will be merged soon:

Pull request Rebased version of #94085 Summary
API1: #94504 main...josuah:zephyr:enable_usb_host_video_class_api1 Data type changes only
API2: #94590 main...josuah:zephyr:enable_usb_host_video_class_api2 Helper functions
API3: #94591 main...josuah:zephyr:enable_usb_host_video_class_api3 Shared UVC code

They are stacked together: API3 is based on API2 which is based on API1.

I believe, if you rebase the current PR on top of them, the current PR will be easier to review, and require fewer changes in the future.

I already rebased the current PR #94085 on top of them, see the "rebased version" column of the table.
The most complete is enable_usb_host_video_class_api3, which contains all the rebase effort from _api2 and _api1 as well as this current PR.

Would you like to integrate these API changes into this current PR? To illustrate what I mean:

cd ~/zephyrproject/zephyr
git remote add josuah https://github.com/josuah/zephyr
git fetch josuah
git checkout josuah/enable_usb_host_video_class_api3
git cherry-pick <<< extra commits from 2025/08/16, missing from enable_usb_host_video_class_apiX >>>
west build -b rd_rw612_bga samples/drivers/video/capture
west flash
<<< now testing that the USB camera input still works >>>
git checkout enable_usb_host_video_class
git reset --hard enable_usb_host_video_class
git push -f

Thank you again for your progress on this PR!

@josuah
Copy link
Contributor

josuah commented Aug 18, 2025

I just noticed that when I did the rebased versions (enable_usb_host_video_class_api1/2/3), I did not integrated your latest progress, but used the version from 2025/08/14, without the extra change from 2025/08/16.

I updated the snippet above.

Copy link
Contributor

@josuah josuah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is now a comparison between this current PR, and the updated versions of that PR:

The easiest is probably to start from enable_usb_host_video_class_api3 directly (see shell commands above) and test the hardware again.

Comment on lines 46 to 78
/**
* @brief Check if USB device matches class driver
*
* @param cdata Pointer to class driver data
* @param code Pointer to USB class code triple
* @return true if matched, false otherwise
*/
static bool usbh_match_class_driver(struct usbh_class_data *cdata,
struct usbh_code_triple *code)
{
if (!cdata || !code || !cdata->device_code_table) {
return false;
}

/* Traverse device code table (cdata->device_code_table) */
for (int i = 0; i < cdata->table_items_count; i++) {
struct usbh_device_code_table *table_entry = &cdata->device_code_table[i];
/* TODO: match device code */

if (table_entry->match_type & USBH_MATCH_INTFACE) {
/* Match interface class code */
if (table_entry->interface_class_code == code->dclass &&
(table_entry->interface_subclass_code == 0xFF ||
table_entry->interface_subclass_code == code->sub) &&
(table_entry->interface_protocol_code == 0x00 ||
table_entry->interface_protocol_code == code->proto)) {
return true;
}
}
}

return false;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function got integrated in the prosposed API2 #94590:

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;
}

Comment on lines 120 to 144
while (search_ptr < desc_buf_end) {
struct usb_desc_header *header = (struct usb_desc_header *)search_ptr;

if (header->bLength == 0) {
goto exit_loop;
}

if (header->bDescriptorType == USB_DESC_INTERFACE_ASSOC) {
start_addr = search_ptr;
found_iad = true;
iad_desc = search_ptr;
break;
} else if (header->bDescriptorType == USB_DESC_INTERFACE) {
start_addr = search_ptr;
found_interface = true;
/* Save class code for interface */
struct usb_if_descriptor *if_desc = (struct usb_if_descriptor *)search_ptr;
class_code.dclass = if_desc->bInterfaceClass;
class_code.sub = if_desc->bInterfaceSubClass;
class_code.proto = if_desc->bInterfaceProtocol;
break;
}

search_ptr += header->bLength;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part got integrated into the proposed API2 PR #94590 as a helper to search a descriptor:

struct usb_desc_header *usbh_desc_get_by_type(const uint8_t *const start_addr,
const uint8_t *const end_addr,
uint32_t type_mask)
{
const uint8_t *curr_addr = start_addr;
while (curr_addr < end_addr) {
struct usb_desc_header *desc = (void *)curr_addr;
if (desc->bLength == 0) {
break;
}
if ((BIT(desc->bDescriptorType) & type_mask) != 0) {
return desc;
}
}
return NULL;
}

Then this got integrated back into this PR on main...josuah:zephyr:enable_usb_host_video_class_api2 or main...josuah:zephyr:enable_usb_host_video_class_api3

desc = usbh_desc_get_by_type(start_addr, end_addr, mask);
if (desc == NULL) {
LOG_ERR("No IAD or interface descriptor found - error condition");
break;
}
start_addr = (uint8_t *)desc;
if (desc->bDescriptorType == USB_DESC_INTERFACE_ASSOC) {
found_iad = true;
}
if (desc->bDescriptorType == USB_DESC_INTERFACE) {
struct usb_if_descriptor *if_desc = (void *)desc;
device_info.code_triple.dclass = if_desc->bInterfaceClass;
device_info.code_triple.sub = if_desc->bInterfaceSubClass;
device_info.code_triple.proto = if_desc->bInterfaceProtocol;
found_interface = true;
}

Comment on lines 152 to 170
/* Step 2: Continue searching for subsequent descriptors to determine end_addr */
search_ptr = start_addr + ((struct usb_desc_header *)start_addr)->bLength;
uint8_t *next_iad_addr = NULL;

/* Find next IAD */
while (search_ptr < desc_buf_end) {
struct usb_desc_header *header = (struct usb_desc_header *)search_ptr;

if (header->bLength == 0) {
break;
}

if (header->bDescriptorType == USB_DESC_INTERFACE_ASSOC) {
next_iad_addr = search_ptr;
break;
}

search_ptr += header->bLength;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise, this got integrated into the proposed API2 PR #94590 as a helper to search a descriptor:

struct usb_desc_header *usbh_desc_get_by_type(const uint8_t *const start_addr,
const uint8_t *const end_addr,
uint32_t type_mask)
{
const uint8_t *curr_addr = start_addr;
while (curr_addr < end_addr) {
struct usb_desc_header *desc = (void *)curr_addr;
if (desc->bLength == 0) {
break;
}
if ((BIT(desc->bDescriptorType) & type_mask) != 0) {
return desc;
}
}
return NULL;
}

Then this got integrated back into this PR on main...josuah:zephyr:enable_usb_host_video_class_api2 or main...josuah:zephyr:enable_usb_host_video_class_api3

/* Step 2: Continue searching for subsequent descriptors to determine end_addr */
start_addr += ((struct usb_desc_header *)start_addr)->bLength;
/* Find next IAD */
mask = BIT(USB_DESC_INTERFACE_ASSOC);
desc = usbh_desc_get_by_type(start_addr, end_addr, mask);
next_iad_addr = (uint8_t *)desc;

Comment on lines 184 to 144
/* Get class code from first interface after IAD */
search_ptr = start_addr + iad_desc->bLength;
while (search_ptr < end_addr) {
struct usb_desc_header *header = (struct usb_desc_header *)search_ptr;
if (header->bLength == 0) {
break;
}
if (header->bDescriptorType == USB_DESC_INTERFACE) {
struct usb_if_descriptor *if_desc = (struct usb_if_descriptor *)search_ptr;
class_code.dclass = if_desc->bInterfaceClass;
class_code.sub = if_desc->bInterfaceSubClass;
class_code.proto = if_desc->bInterfaceProtocol;
break;
}
search_ptr += header->bLength;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise, this got integrated into the proposed API2 PR #94590 as a helper to search a descriptor:

struct usb_desc_header *usbh_desc_get_by_type(const uint8_t *const start_addr,
const uint8_t *const end_addr,
uint32_t type_mask)
{
const uint8_t *curr_addr = start_addr;
while (curr_addr < end_addr) {
struct usb_desc_header *desc = (void *)curr_addr;
if (desc->bLength == 0) {
break;
}
if ((BIT(desc->bDescriptorType) & type_mask) != 0) {
return desc;
}
}
return NULL;
}

Then this got integrated back into this PR on main...josuah:zephyr:enable_usb_host_video_class_api2 or main...josuah:zephyr:enable_usb_host_video_class_api3

/* Get class code from first interface after IAD */
mask = BIT(USB_DESC_INTERFACE_ASSOC);
desc = usbh_desc_get_by_type(start_addr, end_addr, mask);
if (desc != NULL) {
struct usb_if_descriptor *if_desc = (void *)desc;
device_info.code_triple.dclass = if_desc->bInterfaceClass;
device_info.code_triple.sub = if_desc->bInterfaceSubClass;
device_info.code_triple.proto = if_desc->bInterfaceProtocol;
}

Comment on lines 201 to 217
/* Case 2d: Found IAD in step 1, no new IAD in subsequent descriptors */
/* Get class code from first interface after IAD */
search_ptr = start_addr + iad_desc->bLength;
while (search_ptr < desc_buf_end) {
struct usb_desc_header *header = (struct usb_desc_header *)search_ptr;
if (header->bLength == 0) {
break;
}
if (header->bDescriptorType == USB_DESC_INTERFACE) {
struct usb_if_descriptor *if_desc = (struct usb_if_descriptor *)search_ptr;
class_code.dclass = if_desc->bInterfaceClass;
class_code.sub = if_desc->bInterfaceSubClass;
class_code.proto = if_desc->bInterfaceProtocol;
break;
}
search_ptr += header->bLength;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise, this got integrated into the proposed API2 PR #94590 as a helper to search a descriptor:

struct usb_desc_header *usbh_desc_get_by_type(const uint8_t *const start_addr,
const uint8_t *const end_addr,
uint32_t type_mask)
{
const uint8_t *curr_addr = start_addr;
while (curr_addr < end_addr) {
struct usb_desc_header *desc = (void *)curr_addr;
if (desc->bLength == 0) {
break;
}
if ((BIT(desc->bDescriptorType) & type_mask) != 0) {
return desc;
}
}
return NULL;
}

Then this got integrated back into this PR on main...josuah:zephyr:enable_usb_host_video_class_api2 or main...josuah:zephyr:enable_usb_host_video_class_api3

/* Get class code from first interface after IAD */
start_addr += iad_desc->bLength;
mask = BIT(USB_DESC_INTERFACE);
desc = usbh_desc_get_by_type(start_addr, end_addr, mask);
if (desc != NULL) {
struct usb_if_descriptor *if_desc = (void *)start_addr;
device_info.code_triple.dclass = if_desc->bInterfaceClass;
device_info.code_triple.sub = if_desc->bInterfaceSubClass;
device_info.code_triple.proto = if_desc->bInterfaceProtocol;
}

Comment on lines 496 to 506
/* Initialize registered class drivers */
struct usbh_class_data *cdata;
SYS_SLIST_FOR_EACH_CONTAINER(&uhs_ctx->registered_classes, cdata, node) {
if (cdata->api && cdata->api->init) {
ret = cdata->api->init(uhs_ctx, cdata);
if (ret != 0) {
LOG_WRN("Failed to init class driver %s", cdata->name);
} else {
LOG_INF("Class driver %s initialized", cdata->name);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the proposed API2 PR #94590, this got moved to usbh_class.c:

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;
}

Comment on lines 255 to 257
if (!cdata->api || !cdata->api->connected) {
continue;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the prosposed API2 #94590, there is no more check for cdata->api == NULL (could be an assert instead eventually), and the check for cdata->api->connected == NULL is integrated into an API wrapper, like it is done for USB device class API:

7aa4f8d

/**
 * @brief Device connected handler
 *
 * Called when a device is connected to the bus
 * and it matches the class filters of this instance.
 *
 * @param[in] c_data Pointer to USB host class data
 * @param[in] desc_start_addr Pointer to the start of the descriptor
 * @param[in] desc_end_addr Pointer after the end of the USB descriptor
 *
 * @return 0 on success, negative error code on failure.
 */
static inline int usbh_class_connected(struct usbh_class_data *const c_data,
				       void *const desc_start_addr,
				       void *const desc_end_addr)
{
	const struct usbh_class_api *api = c_data->api;

	if (api->connected != NULL) {
		return api->connected(c_data, desc_start_addr, desc_end_addr);
	}

	return -ENOTSUP;
}

Comment on lines 62 to 101
/**
* @brief UVC GUID to pixel format mapping table
*
* Maps UVC format GUIDs to Zephyr video pixel formats.
* Each entry contains the 16-byte GUID, corresponding pixel format,
* and human-readable format name.
*/
static const struct {
/** UVC format GUID */
uint8_t guid[16];
/** Zephyr pixel format (\ref video_pixel_formats) */
uint32_t pixelformat;
/** Format name string */
const char *name;
} uvc_guid_map[] = {
/** YUY2 format GUID */
{{0x59, 0x55, 0x59, 0x32, 0x00, 0x00, 0x10, 0x00,
0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71},
VIDEO_PIX_FMT_YUYV, "YUYV"},

/** Y800 grayscale format GUID */
{{0x59, 0x38, 0x30, 0x30, 0x00, 0x00, 0x10, 0x00,
0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71},
VIDEO_PIX_FMT_GREY, "GREY"},

/** RGBP format GUID */
{{0x52, 0x47, 0x42, 0x50, 0x00, 0x00, 0x10, 0x00,
0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71},
VIDEO_PIX_FMT_RGB565, "RGB565"},

/** UYVY format GUID (unsupported) */
{{0x55, 0x59, 0x56, 0x59, 0x00, 0x00, 0x10, 0x00,
0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71},
0, "UYVY (unsupported)"},

/** NV12 format GUID (unsupported) */
{{0x4E, 0x56, 0x31, 0x32, 0x00, 0x00, 0x10, 0x00,
0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71},
0, "NV12 (unsupported)"},
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is already present on the UVC device class. In the proposed API3 PR #94590, there is a common definition in usb_common_uvc.c that can be used by both host and device:

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"),
},
};

Comment on lines 213 to 261
/**
* @brief Convert pixel format to UVC GUID
*
* Converts Zephyr video pixel format to corresponding UVC GUID.
*
* @param pixelformat Zephyr pixel format value
* @param guid Output buffer for 16-byte GUID
* @return 0 on success, negative error code if format not supported
*/
int uvc_host_pixelformat_to_guid(uint32_t pixelformat, uint8_t *guid)
{
if (!guid) {
return -EINVAL;
}

for (int i = 0; i < ARRAY_SIZE(uvc_guid_map); i++) {
if (uvc_guid_map[i].pixelformat == pixelformat) {
memcpy(guid, uvc_guid_map[i].guid, 16);
return 0;
}
}

return -ENOTSUP;
}

/**
* @brief Convert UVC format GUID to Zephyr pixel format
*
* This function searches the UVC GUID mapping table to find the corresponding
* Zephyr video pixel format for a given UVC format GUID.
*
* @param guid Pointer to 16-byte UVC format GUID array
* @return Zephyr pixel format constant (VIDEO_PIX_FMT_*) on success,
* 0 if GUID is not found or unsupported
*/
uint32_t uvc_host_guid_to_pixelformat(const uint8_t *guid)
{
if (!guid) {
return 0;
}

for (int i = 0; i < ARRAY_SIZE(uvc_guid_map); i++) {
if (memcmp(guid, uvc_guid_map[i].guid, 16) == 0) {
return uvc_guid_map[i].pixelformat;
}
}

return 0;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is already present on the UVC device class. In the proposed API3 PR #94590, there is a common definition in usb_common_uvc.c that can be used by both host and device:

https://github.com/josuah/zephyr/blob/ca2b214d13ee96dbe65275dcac70dc93738e5873/subsys/usb/common/usb_common_uvc.c#L198-L234

Comment on lines 489 to 512

ret = video_init_ctrl(&ctrls->gain, dev, VIDEO_CID_GAIN,
(struct video_ctrl_range){.min = 0, .max = 255, .step = 1, .def = 0});
if (!ret) {
initialized_count++;
/* Create auto gain cluster if both controls exist */
if (ctrls->auto_gain.id != 0) {
video_auto_cluster_ctrl(&ctrls->auto_gain, 2, true);
}
LOG_DBG("Gain control initialized");
}
}

/* White Balance Temperature control */
if (uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_WHITE_BALANCE_TEMPERATURE)) {
ret = video_init_ctrl(&ctrls->white_balance_temperature, dev, VIDEO_CID_WHITE_BALANCE_TEMPERATURE,
(struct video_ctrl_range){.min = 2800, .max = 6500, .step = 1, .def = 4000});
if (!ret) {
initialized_count++;
LOG_DBG("White balance temperature control initialized");
}
}

/* Auto White Balance control */
if (uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_WHITE_BALANCE_TEMPERATURE_AUTO)) {
ret = video_init_ctrl(&ctrls->auto_white_balance_temperature, dev, VIDEO_CID_AUTO_WHITE_BALANCE,
(struct video_ctrl_range){.min = 0, .max = 1, .step = 1, .def = 1});
if (!ret) {
initialized_count++;
LOG_DBG("Auto white balance control initialized");
}
}

/* Backlight Compensation control */
if (uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_BACKLIGHT_COMPENSATION)) {
ret = video_init_ctrl(&ctrls->backlight_compensation, dev, VIDEO_CID_BACKLIGHT_COMPENSATION,
(struct video_ctrl_range){.min = 0, .max = 2, .step = 1, .def = 1});
if (!ret) {
initialized_count++;
LOG_DBG("Backlight compensation control initialized");
}
}

/* Power line frequency control */
if (uvc_host_pu_supports_control(uvc_dev, UVC_PU_BMCONTROL_POWER_LINE_FREQUENCY)) {
ret = video_init_menu_ctrl(&ctrls->light_freq, dev, VIDEO_CID_POWER_LINE_FREQUENCY,
VIDEO_CID_POWER_LINE_FREQUENCY_AUTO, NULL);
if (!ret) {
initialized_count++;
LOG_DBG("Power line frequency control initialized");
}
}

/* Auto exposure control - Camera Terminal control */
if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_AE_MODE)) {
ret = video_init_ctrl(&ctrls->auto_exposure, dev, VIDEO_CID_EXPOSURE_AUTO,
(struct video_ctrl_range){.min = 0, .max = 1, .step = 1, .def = 1});
if (!ret) {
initialized_count++;
LOG_DBG("Auto exposure control initialized");
}
}

/* Exposure absolute control - Camera Terminal control */
if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_EXPOSURE_TIME_ABSOLUTE)) {
ret = video_init_ctrl(&ctrls->exposure_absolute, dev, VIDEO_CID_EXPOSURE_ABSOLUTE,
(struct video_ctrl_range){
.min = 1, /* Minimum exposure time 1μs */
.max = 10000000, /* Maximum exposure time 10s (10,000,000μs) */
.step = 1,
.def = 33333 /* Default 1/30s ≈ 33.33ms */
});
if (!ret) {
initialized_count++;
/* Create auto exposure cluster if both controls exist */
if (ctrls->auto_exposure.id != 0) {
video_auto_cluster_ctrl(&ctrls->auto_exposure, 2, true);
}
LOG_DBG("Exposure absolute control initialized");
}
}

/* Focus controls - Camera Terminal control */
if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_FOCUS_AUTO)) {
ret = video_init_ctrl(&ctrls->auto_focus, dev, VIDEO_CID_FOCUS_AUTO,
(struct video_ctrl_range){.min = 0, .max = 1, .step = 1, .def = 1});
if (!ret) {
initialized_count++;
LOG_DBG("Auto focus control initialized");
}
}

if (uvc_host_ct_supports_control(uvc_dev, UVC_CT_BMCONTROL_FOCUS_ABSOLUTE)) {
ret = video_init_ctrl(&ctrls->focus_absolute, dev, VIDEO_CID_FOCUS_ABSOLUTE,
(struct video_ctrl_range){.min = 0, .max = 1023, .step = 1, .def = 0});
if (!ret) {
initialized_count++;
LOG_DBG("Focus absolute control initialized");
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you noticed there is the need to specify the min/max/step/def for every video control.

Using arbitrary values works great for a first implementation, however a complete support would require querying the UVC device for GET_MIN, GET_MAX, GET_DEF... and map the UVC types to the Zephyr Video types.

There is a database that specify how these two are related in the UVC device class, an in the proposed API3 PR #94590, this is extracted into a separate file, along with helper functions to access these tables:

https://github.com/josuah/zephyr/blob/enable_usb_host_video_class_api3/subsys/usb/common/usb_common_uvc.c#L27-L196

I have not yet integrated this into the current PR #94085 but can be working on it soon.

@josuah
Copy link
Contributor

josuah commented Aug 18, 2025

I forgot to say: the comments are only there to give an illustration of how it is possible to adapt this PR to integrate it with the WIP USB Host class API proposed (API1/2/3/...). Maybe a different adaptation is better.

@AidenHu
Copy link
Contributor Author

AidenHu commented Aug 18, 2025

@josuah, Thank you so much for the comments, I will do it soon.

@AidenHu
Copy link
Contributor Author

AidenHu commented Aug 18, 2025

git checkout josuah/enable_usb_host_video_class_api3
git cherry-pick <<< extra commits from 2025/08/16, missing from enable_usb_host_video_class_apiX >>>

@josuah, Consider to speed up to do the merging tomorrow, just want to make sure with you that enable_usb_host_video_class_apiX means enable_usb_host_video_class_api1 and enable_usb_host_video_class_api2 in the condition of using enable_usb_host_video_class_api3 branch directly?

Josuah Demangeon and others added 28 commits December 9, 2025 14:35
Add a "struct usbh_status" that contains a bitmask of flags to keep
track of the global state of the host context, like done for the
device_next implementation.

Signed-off-by: Josuah Demangeon <[email protected]>
Add missing copyright notice for the linker script to help with
check_compliance.py.

Signed-off-by: Josuah Demangeon <[email protected]>
Add a "struct usbh_class_api" for the host implementation, and move all
the function poitners to it. Add more fields to "struct usbh_class_data".

Signed-off-by: Josuah Demangeon <[email protected]>
Add API wrappers around the function pointers in struct usbh_class_api,
while also documenting the USB host class internal API.

Signed-off-by: Josuah Demangeon <[email protected]>
Add functions to probe/remove all classes as part of a new usbh_class.c
and a matching usbh_class.h. These functions are called from the function
usbh_init_device_intl() in usbh_core.c to initialize every class upon
connection of a device. Every class driver provide filters to match the
interfaces of the device.

Co-authored-by: Aiden Hu <[email protected]>
Signed-off-by: Josuah Demangeon <[email protected]>
Move the UVC header with all the definitions from the UVC standard to
share it between USB host and device class implementation.

Signed-off-by: Josuah Demangeon <[email protected]>
Add tests making sure the USB Host class APIs introduced build
and run as expected.

Signed-off-by: Josuah Demangeon <[email protected]>
Switch to the SPDX-FileCopyrightText prefix for all sources in USB Host
class.

Signed-off-by: Josuah Demangeon <[email protected]>
Add tests making sure the USB Host class APIs introduced build
and run as expected.

Signed-off-by: Josuah Demangeon <[email protected]>
Move the UVC header with all the definitions from the UVC standard to
share it between USB host and device class implementation.

Signed-off-by: Josuah Demangeon <[email protected]>
Move UVC helper functions to a file shared between UVC host and device.
The arrays are not visible anymore from either USB host or device, but
instead accessed through a front-end funciton.

Signed-off-by: Josuah Demangeon <[email protected]>
The USB control size field was wrong for UVC_PU_CONTRAST_CONTROL.
Correct it to the correct value from the standard.

Signed-off-by: Josuah Demangeon <[email protected]>
Loop through each of the VideoStreaming and VideoControl descriptor
to parse them. This is meant as a stub for the purpose of testing the
class API.

Signed-off-by: Josuah Demangeon <[email protected]>
Add a test to run the USB Video Class host support by using
the existing Zephyr USB Video Class device support.
This allows running implementing the host side from the device side.
A draft implementation of UVC is added leveraging this test.

Signed-off-by: Josuah Demangeon <[email protected]>
The public API file <zephyr/usb/class/usbd_uvc.h> lacked an include
to <zephyr/drivers/video.h> making it fail depending on the order of
the includes.

Signed-off-by: Josuah Demangeon <[email protected]>
Modify the USB device int sequence to read the device
descriptor only after setting a valid device address.

Signed-off-by: Santhosh Charles <[email protected]>
When hub is used, need to consider about multiple
devices are attached.

Signed-off-by: Aiden Hu <[email protected]>
add usbh_device_get_root and usbh_device_is_root
function to check root device

Signed-off-by: Aiden Hu <[email protected]>
For usb xfer, set endpoint type and interval by the
selected endpoint desc.

Signed-off-by: Aiden Hu <[email protected]>
Convert xfer's interval to actual value because
mcux_ep->interval is already calculated.

Signed-off-by: Aiden Hu <[email protected]>
maxPacketSize and numberPerUframe of pipe should
be set considering additional transactions.

Signed-off-by: Aiden Hu <[email protected]>
Add two functions:
usbh_connect_device() for device connection
usbh_disconnect_device() for device disconnection

These functions centralize the logic for device attach/detach,
including class probe and remove handling. They can be invoked
by the hub class as well as dev_connected_handler and
dev_removed_handler, improving code clarity and reuse.

Signed-off-by: Aiden Hu <[email protected]>
This patch refactors the UVC host implementation from a minimal stub
to a complete USB Video Class (UVC) host driver. Key changes include:

- Introduce `struct uvc_device` to manage device state, FIFOs, mutex,
  descriptors, and video format information.
- Add comprehensive descriptor parsing for VideoControl and
  VideoStreaming interfaces, including format and frame descriptors.
- Implement UVC probe/commit protocol for format negotiation and
  bandwidth calculation.
- Support dynamic frame rate adjustment and alternate interface
  selection based on bandwidth.
- Provide full video API integration:
  - set/get format, capabilities, frame intervals
  - start/stop streaming, buffer enqueue/dequeue
  - control handling (brightness, contrast, exposure, focus, etc.)
- Add ISO transfer handling with UVC payload header parsing and
  frame assembly.
- Enhance device initialization and cleanup routines.
- Update device definition macros and register UVC host class.

Signed-off-by: Aiden Hu <[email protected]>
Introduce a new sample under samples/subsys/usb/host_uvc that uses the
USB host UVC class driver to capture frames from a connected USB camera
and render them to the board LCD in real time.

Key features:
- UVC host enumeration and stream start/stop
- YUV/RGB conversion pipeline for LCD
- Basic controls (resolution, frame interval) via UVC probes

Signed-off-by: Aiden Hu <[email protected]>
- Add hub_host Kconfig and source files for USB hub class
- Update CMakeLists.txt to include hub class sources

Signed-off-by: Aiden Hu <[email protected]>

some necessary change for host uvc.
Introduce USB hub class support in the USB host stack.
This includes:
- Core hub handling logic (usbh_hub.c / usbh_hub.h)
- Hub manager for port status and event handling
  (usbh_hub_mgr.c / usbh_hub_mgr.h)

Signed-off-by: Aiden Hu <[email protected]>
Add new Kconfig parameters for buffer, stream, and
format settings to enhance flexibility and
performance of USB Host UVC.

Signed-off-by: Aiden Hu <[email protected]>
@AidenHu AidenHu force-pushed the enable_usb_host_video_class branch from 7eaa857 to 8e040d0 Compare December 10, 2025 15:46
@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
E Reliability Rating on New Code (required ≥ C)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: USB Universal Serial Bus area: Video Video subsystem

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants