Skip to content
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

usb: device_next: new USB Video Class (UVC) implementation #76798

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion boards/weact/mini_stm32h743/mini_stm32h743_defconfig
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ CONFIG_CONSOLE=y
CONFIG_GPIO=y

# Logger cannot use itself to log
CONFIG_USB_CDC_ACM_LOG_LEVEL_OFF=y
#CONFIG_USB_CDC_ACM_LOG_LEVEL_OFF=y

# Enable USB
CONFIG_USB_DEVICE_STACK=y
2 changes: 2 additions & 0 deletions drivers/video/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@ zephyr_library_sources_ifdef(CONFIG_VIDEO_OV5640 ov5640.c)
zephyr_library_sources_ifdef(CONFIG_VIDEO_OV7670 ov7670.c)
zephyr_library_sources_ifdef(CONFIG_VIDEO_ESP32 video_esp32_dvp.c)
zephyr_library_sources_ifdef(CONFIG_VIDEO_MCUX_SDMA video_mcux_smartdma.c)
zephyr_library_sources_ifdef(CONFIG_VIDEO_EMUL_IMAGER video_emul_imager.c)
zephyr_library_sources_ifdef(CONFIG_VIDEO_EMUL_MIPI_RX video_emul_mipi_rx.c)
4 changes: 4 additions & 0 deletions drivers/video/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,8 @@ source "drivers/video/Kconfig.gc2145"

source "drivers/video/Kconfig.mcux_sdma"

source "drivers/video/Kconfig.emul_imager"

source "drivers/video/Kconfig.emul_mipi_rx"

endif # VIDEO
18 changes: 18 additions & 0 deletions drivers/video/Kconfig.emul_imager
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright (c) 2024 tinyVision.ai Inc.
# SPDX-License-Identifier: Apache-2.0

config VIDEO_EMUL_IMAGER
bool "Software implementation of an imager"
depends on DT_HAS_ZEPHYR_EMUL_IMAGER_ENABLED
default y
help
Enable driver for the emulated Imager.

config VIDEO_EMUL_IMAGER_FRAMEBUFFER_SIZE
int "Internal framebuffer size used for link emulation purpose"
default 4096
help
Configure the size of the internal framebuffer the emulated Imager
driver uses to simulate MIPI transfers. This is the first field of
dev->data, and the emulated video MIPI driver will `memcpy()` it
into the video buffer.
10 changes: 10 additions & 0 deletions drivers/video/Kconfig.emul_mipi_rx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Copyright (c) 2024 tinyVision.ai Inc.
# SPDX-License-Identifier: Apache-2.0

config VIDEO_EMUL_MIPI_RX
bool "Software implementation of video frame RX core"
depends on DT_HAS_ZEPHYR_EMUL_MIPI_RX_ENABLED
depends on VIDEO_EMUL_IMAGER
default y
help
Enable driver for the MIPI RX emulated DMA engine.
14 changes: 5 additions & 9 deletions drivers/video/video_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ struct mem_block {

static struct mem_block video_block[CONFIG_VIDEO_BUFFER_POOL_NUM_MAX];

struct video_buffer *video_buffer_aligned_alloc(size_t size, size_t align)
struct video_buffer *z_video_buffer_alloc(size_t header_size, size_t buffer_size, size_t align)
{
struct video_buffer *vbuf = NULL;
struct mem_block *block;
Expand All @@ -48,23 +48,19 @@ struct video_buffer *video_buffer_aligned_alloc(size_t size, size_t align)
}

/* Alloc buffer memory */
block->data = VIDEO_COMMON_HEAP_ALLOC(align, size, K_FOREVER);
block->data = VIDEO_COMMON_HEAP_ALLOC(align, header_size + buffer_size, K_FOREVER);
if (block->data == NULL) {
return NULL;
}

vbuf->buffer = block->data;
vbuf->size = size;
vbuf->header = block->data;
vbuf->buffer = (uint8_t *)block->data + header_size;
vbuf->size = buffer_size;
vbuf->bytesused = 0;

return vbuf;
}

struct video_buffer *video_buffer_alloc(size_t size)
{
return video_buffer_aligned_alloc(size, sizeof(void *));
}

void video_buffer_release(struct video_buffer *vbuf)
{
struct mem_block *block = NULL;
Expand Down
147 changes: 147 additions & 0 deletions drivers/video/video_common.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* Copyright (c) 2024, tinyVision.ai Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/

#ifndef ZEPHYR_INCLUDE_VIDEO_COMMON_H
#define ZEPHYR_INCLUDE_VIDEO_COMMON_H

#include <zephyr/kernel.h>
#include <zephyr/sys_clock.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/math_extras.h>
#include <zephyr/drivers/video.h>

#ifndef ABS
#define ABS(a) ((a) < 0 ? -(a) : (a))
#endif

/**
* @brief Search for a format that matches in a list of capabilities
*
* @param fmts The format capability list to search.
* @param fmts_num The number of capabilities in that list.
* @param fmt The format to find in the list.
* @param idx The pointer to a number of the first format that matches.
*
* @return 0 when a format is found.
* @return -ENOENT when no matching format is found.
*/
static inline int video_format_index(const struct video_format_cap *fmts,
const struct video_format *fmt, size_t *idx)
{
for (int i = 0; fmts[i].pixelformat != 0; i++) {
if (fmts[i].pixelformat == fmt->pixelformat &&
IN_RANGE(fmt->width, fmts[i].width_min, fmts[i].width_max) &&
IN_RANGE(fmt->height, fmts[i].height_min, fmts[i].height_max)) {
*idx = i;
return 0;
}
}
return -ENOENT;
}

/**
* @brief Compute the difference between two frame intervals
*
* @param ap First frame interval.
* @param bp Second frame interval.
*
* @return The signed difference in microsecond between the two frame intervals.
*/
static inline int64_t video_diff_frmival_usec(const struct video_frmival *ap,
const struct video_frmival *bp)
{
struct video_frmival a = *ap;
struct video_frmival b = *bp;

if (a.denominator != b.denominator) {
a.numerator *= b.denominator;
a.denominator *= b.denominator;
b.numerator *= a.denominator;
b.denominator *= a.denominator;
}

/* Return the difference in microseconds */
return DIV_ROUND_CLOSEST((int64_t)USEC_PER_SEC * a.numerator, a.denominator) -
DIV_ROUND_CLOSEST((int64_t)USEC_PER_SEC * b.numerator, b.denominator);
}

/**
* @brief Find the closest match to a frame interval value within a stepwise frame interval.
*
* @param stepwise The stepwise frame interval range to search
* @param desired The frame interval for which find the closest match
* @param best_match The resulting frame interval closest to @p desired
*/
static inline void video_closest_frmival_stepwise(const struct video_frmival_stepwise *stepwise,
const struct video_frmival *desired,
struct video_frmival *match)
{
uint32_t min = stepwise->min.numerator;
uint32_t max = stepwise->max.numerator;
uint32_t step = stepwise->step.numerator;
uint32_t goal = desired->numerator;
uint32_t z1, z2;

/* Set a common denominator to all values */
min *= stepwise->max.denominator * stepwise->step.denominator * desired->denominator;
max *= stepwise->min.denominator * stepwise->step.denominator * desired->denominator;
step *= stepwise->min.denominator * stepwise->max.denominator * desired->denominator;
goal *= stepwise->min.denominator * stepwise->max.denominator * stepwise->step.denominator;

/* Compute a numerator and denominator */
match->denominator = stepwise->min.denominator * stepwise->max.denominator *
stepwise->step.denominator * desired->denominator;
match->numerator = min + DIV_ROUND_CLOSEST(goal - min, step) * step;

/* To reduce risk of overflow: simplify the fraction by powers of 2 */
z1 = u32_count_trailing_zeros(match->numerator);
z2 = u32_count_trailing_zeros(match->denominator);
match->numerator <<= MIN(z1, z2);
match->denominator <<= MIN(z1, z2);
}

/**
* @brief Find the closest match to a frame interval value within a video device.
*
* @param dev Video device to query.
* @param ep Video endpoint ID to query.
* @param match Frame interval enumerator with the query and loaded with the result.
*/
static inline void video_closest_frmival(const struct device *dev, enum video_endpoint_id ep,
struct video_frmival_enum *match)
{
int32_t best_diff_usec = INT32_MAX;
const struct video_frmival desired = match->discrete;
struct video_frmival_enum fie = {.format = match->format};

__ASSERT(match->type == VIDEO_FRMIVAL_TYPE_DISCRETE,
"cannot find range matching the range, only a value matching the range");

while (video_enum_frmival(dev, ep, &fie) == 0) {
struct video_frmival tmp = {0};
int32_t diff_usec = 0;

switch (fie.type) {
case VIDEO_FRMIVAL_TYPE_DISCRETE:
tmp = fie.discrete;
break;
case VIDEO_FRMIVAL_TYPE_STEPWISE:
video_closest_frmival_stepwise(&fie.stepwise, &desired, &tmp);
break;
default:
__ASSERT(false, "invalid answer from the queried video device");
}

diff_usec = video_diff_frmival_usec(&desired, &tmp);
if (ABS(diff_usec) < best_diff_usec) {
best_diff_usec = diff_usec;
memcpy(&match->discrete, &tmp, sizeof(tmp));
match->index = fie.index;
}
}
}

#endif /* ZEPHYR_INCLUDE_VIDEO_COMMON_H */
Loading
Loading