-
Notifications
You must be signed in to change notification settings - Fork 8.4k
usb: host: class: support for usb host video class #94085
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
usb: host: class: support for usb host video class #94085
Conversation
|
Hello @AidenHu, and thank you very much for your first pull request to the Zephyr project! |
669c434 to
4ca6d07
Compare
| uvc_host: uvc_host { | ||
| compatible = "zephyr,uvc-host"; | ||
| }; | ||
| }; No newline at end of file |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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
|
@AidenHu you are going to need to break this up into multiple commits. My suggestion is:
|
cb3ab4e to
76bed79
Compare
76bed79 to
9192550
Compare
9192550 to
6e8acc7
Compare
|
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 |
Thank you for the reply, @josuah |
aef5e85 to
629e311
Compare
|
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:
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. Would you like to integrate these API changes into this current PR? To illustrate what I mean: Thank you again for your progress on this PR! |
|
I just noticed that when I did the rebased versions ( I updated the snippet above. |
josuah
left a comment
There was a problem hiding this 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:
- main...josuah:zephyr:enable_usb_host_video_class_api1
- main...josuah:zephyr:enable_usb_host_video_class_api2
- main...josuah:zephyr:enable_usb_host_video_class_api3
The easiest is probably to start from enable_usb_host_video_class_api3 directly (see shell commands above) and test the hardware again.
subsys/usb/host/usbh_core.c
Outdated
| /** | ||
| * @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; | ||
| } |
There was a problem hiding this comment.
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:
zephyr/subsys/usb/host/usbh_class.c
Lines 61 to 95 in e57477d
| 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; | |
| } |
subsys/usb/host/usbh_core.c
Outdated
| 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; | ||
| } |
There was a problem hiding this comment.
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:
zephyr/subsys/usb/host/usbh_desc.c
Lines 10 to 29 in e57477d
| 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
zephyr/subsys/usb/host/usbh_core.c
Lines 93 to 111 in ca2b214
| 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; | |
| } |
subsys/usb/host/usbh_core.c
Outdated
| /* 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; | ||
| } |
There was a problem hiding this comment.
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:
zephyr/subsys/usb/host/usbh_desc.c
Lines 10 to 29 in e57477d
| 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
zephyr/subsys/usb/host/usbh_core.c
Lines 113 to 119 in ca2b214
| /* 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; |
subsys/usb/host/usbh_core.c
Outdated
| /* 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; | ||
| } |
There was a problem hiding this comment.
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:
zephyr/subsys/usb/host/usbh_desc.c
Lines 10 to 29 in e57477d
| 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
zephyr/subsys/usb/host/usbh_core.c
Lines 135 to 144 in ca2b214
| /* 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; | |
| } |
subsys/usb/host/usbh_core.c
Outdated
| /* 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; | ||
| } |
There was a problem hiding this comment.
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:
zephyr/subsys/usb/host/usbh_desc.c
Lines 10 to 29 in e57477d
| 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
zephyr/subsys/usb/host/usbh_core.c
Lines 147 to 157 in ca2b214
| /* 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; | |
| } |
subsys/usb/host/usbh_core.c
Outdated
| /* 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); | ||
| } | ||
| } |
There was a problem hiding this comment.
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:
zephyr/subsys/usb/host/usbh_class.c
Lines 45 to 59 in ca2b214
| 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; | |
| } |
subsys/usb/host/usbh_core.c
Outdated
| if (!cdata->api || !cdata->api->connected) { | ||
| continue; | ||
| } |
There was a problem hiding this comment.
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:
/**
* @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;
}
subsys/usb/host/class/usbh_uvc.c
Outdated
| /** | ||
| * @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)"}, | ||
| }; |
There was a problem hiding this comment.
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:
zephyr/subsys/usb/common/usb_common_uvc.c
Lines 16 to 25 in ca2b214
| 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"), | |
| }, | |
| }; |
subsys/usb/host/class/usbh_uvc.c
Outdated
| /** | ||
| * @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; | ||
| } |
There was a problem hiding this comment.
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:
subsys/usb/host/class/usbh_uvc.c
Outdated
|
|
||
| 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"); | ||
| } | ||
| } |
There was a problem hiding this comment.
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:
I have not yet integrated this into the current PR #94085 but can be working on it soon.
|
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. |
|
@josuah, Thank you so much for the comments, I will do it soon. |
@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? |
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]>
Signed-off-by: Aiden Hu <[email protected]>
7eaa857 to
8e040d0
Compare
|




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