Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/zephyr/usb/class/usbd_uvc.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#define ZEPHYR_INCLUDE_USB_CLASS_USBD_UVC_H

#include <zephyr/device.h>
#include <zephyr/drivers/video.h>

/**
* @brief USB Video Class (UVC) device API
Expand Down
5 changes: 4 additions & 1 deletion subsys/usb/common/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# SPDX-FileCopyrightText: Copyright Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0

zephyr_include_directories(include)
if(CONFIG_USBD_VIDEO_CLASS)
zephyr_include_directories(.)
zephyr_sources(uvc.c)
Copy link
Copy Markdown
Contributor

@tmon-nordic tmon-nordic Feb 5, 2026

Choose a reason for hiding this comment

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

You use uvc.c in CMakeLists.txt in commit prior to where uvc.c is introduced.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes my bad, there was some bad rebase happening.
I think I fixed it in API3 without fixing it in uvc_common.

endif()
232 changes: 232 additions & 0 deletions subsys/usb/common/uvc.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
/*
* SPDX-FileCopyrightText: Copyright tinyVision.ai Inc.
* SPDX-License-Identifier: Apache-2.0
*/

#include <stdint.h>

#include <zephyr/drivers/video-controls.h>
#include <zephyr/drivers/video.h>
#include <zephyr/sys/byteorder.h>

#include "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;
}
80 changes: 75 additions & 5 deletions subsys/usb/common/include/usb_uvc.h → subsys/usb/common/uvc.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2025 tinyVision.ai Inc.
*
* SPDX-FileCopyrightText: Copyright tinyVision.ai Inc.
* SPDX-License-Identifier: Apache-2.0
*/

Expand All @@ -16,8 +15,10 @@
* - 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_SUBSYS_USB_COMMON_UVC_H
#define ZEPHYR_SUBSYS_USB_COMMON_UVC_H

#include <stdint.h>

#include <zephyr/usb/usb_ch9.h>

Expand Down Expand Up @@ -526,4 +527,73 @@ struct uvc_payload_header {
uint16_t scrSourceClockSOF; /* optional */
} __packed;

#endif /* ZEPHYR_INCLUDE_USBD_CLASS_UVC_H_ */
/**
* @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_SUBSYS_USB_COMMON_UVC_H */
Loading