| /* |
| * Copyright (c) 2019, Linaro Limited |
| * Copyright (c) 2024, tinyVision.ai Inc. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <string.h> |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/drivers/video.h> |
| |
| #if defined(CONFIG_VIDEO_BUFFER_USE_SHARED_MULTI_HEAP) |
| #include <zephyr/multi_heap/shared_multi_heap.h> |
| |
| #define VIDEO_COMMON_HEAP_ALLOC(align, size, timeout) \ |
| shared_multi_heap_aligned_alloc(CONFIG_VIDEO_BUFFER_SMH_ATTRIBUTE, align, size) |
| #define VIDEO_COMMON_FREE(block) shared_multi_heap_free(block) |
| #else |
| K_HEAP_DEFINE(video_buffer_pool, CONFIG_VIDEO_BUFFER_POOL_SZ_MAX*CONFIG_VIDEO_BUFFER_POOL_NUM_MAX); |
| #define VIDEO_COMMON_HEAP_ALLOC(align, size, timeout) \ |
| k_heap_aligned_alloc(&video_buffer_pool, align, size, timeout); |
| #define VIDEO_COMMON_FREE(block) k_heap_free(&video_buffer_pool, block) |
| #endif |
| |
| static struct video_buffer video_buf[CONFIG_VIDEO_BUFFER_POOL_NUM_MAX]; |
| |
| struct mem_block { |
| void *data; |
| }; |
| |
| 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, k_timeout_t timeout) |
| { |
| struct video_buffer *vbuf = NULL; |
| struct mem_block *block; |
| int i; |
| |
| /* find available video buffer */ |
| for (i = 0; i < ARRAY_SIZE(video_buf); i++) { |
| if (video_buf[i].buffer == NULL) { |
| vbuf = &video_buf[i]; |
| block = &video_block[i]; |
| break; |
| } |
| } |
| |
| if (vbuf == NULL) { |
| return NULL; |
| } |
| |
| /* Alloc buffer memory */ |
| block->data = VIDEO_COMMON_HEAP_ALLOC(align, size, timeout); |
| if (block->data == NULL) { |
| return NULL; |
| } |
| |
| vbuf->buffer = block->data; |
| vbuf->size = size; |
| vbuf->bytesused = 0; |
| |
| return vbuf; |
| } |
| |
| struct video_buffer *video_buffer_alloc(size_t size, k_timeout_t timeout) |
| { |
| return video_buffer_aligned_alloc(size, sizeof(void *), timeout); |
| } |
| |
| void video_buffer_release(struct video_buffer *vbuf) |
| { |
| struct mem_block *block = NULL; |
| int i; |
| |
| /* vbuf to block */ |
| for (i = 0; i < ARRAY_SIZE(video_block); i++) { |
| if (video_block[i].data == vbuf->buffer) { |
| block = &video_block[i]; |
| break; |
| } |
| } |
| |
| vbuf->buffer = NULL; |
| if (block) { |
| VIDEO_COMMON_FREE(block->data); |
| } |
| } |
| |
| int video_format_caps_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; |
| } |
| |
| void video_closest_frmival_stepwise(const struct video_frmival_stepwise *stepwise, |
| const struct video_frmival *desired, |
| struct video_frmival *match) |
| { |
| uint64_t min = stepwise->min.numerator; |
| uint64_t max = stepwise->max.numerator; |
| uint64_t step = stepwise->step.numerator; |
| uint64_t goal = desired->numerator; |
| |
| /* 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; |
| |
| /* Saturate the desired value to the min/max supported */ |
| goal = CLAMP(goal, min, max); |
| |
| /* Compute a numerator and denominator */ |
| match->numerator = min + DIV_ROUND_CLOSEST(goal - min, step) * step; |
| match->denominator = stepwise->min.denominator * stepwise->max.denominator * |
| stepwise->step.denominator * desired->denominator; |
| } |
| |
| void video_closest_frmival(const struct device *dev, enum video_endpoint_id ep, |
| struct video_frmival_enum *match) |
| { |
| uint64_t best_diff_nsec = INT32_MAX; |
| struct video_frmival desired = match->discrete; |
| struct video_frmival_enum fie = {.format = match->format}; |
| |
| __ASSERT(match->type != VIDEO_FRMIVAL_TYPE_STEPWISE, |
| "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}; |
| uint64_t diff_nsec = 0, a, b; |
| |
| 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"); |
| } |
| |
| a = video_frmival_nsec(&desired); |
| b = video_frmival_nsec(&tmp); |
| diff_nsec = a > b ? a - b : b - a; |
| if (diff_nsec < best_diff_nsec) { |
| best_diff_nsec = diff_nsec; |
| memcpy(&match->discrete, &tmp, sizeof(tmp)); |
| |
| /* The video_enum_frmival() function will increment fie.index every time. |
| * Compensate for it to get the current index, not the next index. |
| */ |
| match->index = fie.index - 1; |
| } |
| } |
| } |