Primary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

660 lines
16 KiB

/*
* Copyright 2025 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <zephyr/drivers/video.h>
#include <zephyr/drivers/video-controls.h>
#include <zephyr/logging/log.h>
#include "video_ctrls.h"
#include "video_device.h"
LOG_MODULE_REGISTER(video_ctrls, CONFIG_VIDEO_LOG_LEVEL);
static inline const char *const *video_get_std_menu_ctrl(uint32_t id)
{
static char const *const power_line_frequency[] = {
"Disabled", "50 Hz", "60 Hz", "Auto", NULL,
};
static char const *const exposure_auto[] = {
"Auto Mode", "Manual Mode", "Shutter Priority Mode", "Aperture Priority Mode", NULL,
};
static char const *const colorfx[] = {
"None", "Black & White", "Sepia", "Negative", "Emboss", "Sketch", "Sky Blue",
"Grass Green", "Skin Whiten", "Vivid", "Aqua", "Art Freeze", "Silhouette",
"Solarization", "Antique", "Set Cb/Cr", NULL,
};
static char const *const camera_orientation[] = {
"Front", "Back", "External", NULL,
};
switch (id) {
/* User control menus */
case VIDEO_CID_POWER_LINE_FREQUENCY:
return power_line_frequency;
/* Camera control menus */
case VIDEO_CID_EXPOSURE_AUTO:
return exposure_auto;
case VIDEO_CID_COLORFX:
return colorfx;
case VIDEO_CID_CAMERA_ORIENTATION:
return camera_orientation;
default:
return NULL;
}
}
static inline int check_range(enum video_ctrl_type type, struct video_ctrl_range range)
{
switch (type) {
case VIDEO_CTRL_TYPE_BOOLEAN:
if (range.step != 1 || range.max > 1 || range.min < 0) {
return -ERANGE;
}
return 0;
case VIDEO_CTRL_TYPE_INTEGER:
if (range.step == 0 || range.min > range.max ||
!IN_RANGE(range.def, range.min, range.max)) {
return -ERANGE;
}
return 0;
case VIDEO_CTRL_TYPE_INTEGER64:
if (range.step64 == 0 || range.min64 > range.max64 ||
!IN_RANGE(range.def64, range.min64, range.max64)) {
return -ERANGE;
}
return 0;
case VIDEO_CTRL_TYPE_MENU:
case VIDEO_CTRL_TYPE_INTEGER_MENU:
if (!IN_RANGE(range.min, 0, range.max) ||
!IN_RANGE(range.def, range.min, range.max)) {
return -ERANGE;
}
return 0;
default:
return 0;
}
}
static inline void set_type_flag(uint32_t id, enum video_ctrl_type *type, uint32_t *flags)
{
*flags = 0;
switch (id) {
case VIDEO_CID_AUTO_WHITE_BALANCE:
case VIDEO_CID_AUTOGAIN:
case VIDEO_CID_HFLIP:
case VIDEO_CID_VFLIP:
case VIDEO_CID_HUE_AUTO:
case VIDEO_CID_AUTOBRIGHTNESS:
case VIDEO_CID_EXPOSURE_AUTO_PRIORITY:
case VIDEO_CID_FOCUS_AUTO:
case VIDEO_CID_WIDE_DYNAMIC_RANGE:
*type = VIDEO_CTRL_TYPE_BOOLEAN;
break;
case VIDEO_CID_POWER_LINE_FREQUENCY:
case VIDEO_CID_EXPOSURE_AUTO:
case VIDEO_CID_COLORFX:
case VIDEO_CID_TEST_PATTERN:
case VIDEO_CID_CAMERA_ORIENTATION:
*type = VIDEO_CTRL_TYPE_MENU;
break;
case VIDEO_CID_PIXEL_RATE:
*type = VIDEO_CTRL_TYPE_INTEGER64;
*flags |= VIDEO_CTRL_FLAG_READ_ONLY;
break;
case VIDEO_CID_LINK_FREQ:
*type = VIDEO_CTRL_TYPE_INTEGER_MENU;
break;
default:
*type = VIDEO_CTRL_TYPE_INTEGER;
break;
}
}
int video_init_ctrl(struct video_ctrl *ctrl, const struct device *dev, uint32_t id,
struct video_ctrl_range range)
{
int ret;
uint32_t flags;
enum video_ctrl_type type;
struct video_ctrl *vc;
struct video_device *vdev;
__ASSERT_NO_MSG(dev != NULL);
__ASSERT_NO_MSG(ctrl != NULL);
vdev = video_find_vdev(dev);
if (!vdev) {
return -EINVAL;
}
/* Sanity checks */
if (id < VIDEO_CID_BASE) {
return -EINVAL;
}
set_type_flag(id, &type, &flags);
ret = check_range(type, range);
if (ret) {
return ret;
}
ctrl->cluster_sz = 0;
ctrl->cluster = NULL;
ctrl->is_auto = false;
ctrl->has_volatiles = false;
ctrl->menu = NULL;
ctrl->vdev = vdev;
ctrl->id = id;
ctrl->type = type;
ctrl->flags = flags;
ctrl->range = range;
if (type == VIDEO_CTRL_TYPE_INTEGER64) {
ctrl->val64 = range.def64;
} else {
ctrl->val = range.def;
}
/* Insert in an ascending order of ctrl's id */
SYS_DLIST_FOR_EACH_CONTAINER(&vdev->ctrls, vc, node) {
if (vc->id > ctrl->id) {
sys_dlist_insert(&vc->node, &ctrl->node);
return 0;
}
}
sys_dlist_append(&vdev->ctrls, &ctrl->node);
return 0;
}
int video_init_menu_ctrl(struct video_ctrl *ctrl, const struct device *dev, uint32_t id,
uint8_t def, const char *const menu[])
{
int ret;
uint8_t sz = 0;
const char *const *_menu = menu ? menu : video_get_std_menu_ctrl(id);
if (!_menu) {
return -EINVAL;
}
while (_menu[sz]) {
sz++;
}
ret = video_init_ctrl(
ctrl, dev, id,
(struct video_ctrl_range){.min = 0, .max = sz - 1, .step = 1, .def = def});
if (ret) {
return ret;
}
ctrl->menu = _menu;
return 0;
}
int video_init_int_menu_ctrl(struct video_ctrl *ctrl, const struct device *dev, uint32_t id,
uint8_t def, const int64_t menu[], size_t menu_len)
{
int ret;
if (!menu) {
return -EINVAL;
}
ret = video_init_ctrl(
ctrl, dev, id,
(struct video_ctrl_range){.min = 0, .max = menu_len - 1, .step = 1, .def = def});
if (ret) {
return ret;
}
ctrl->int_menu = menu;
return 0;
}
/* By definition, the cluster is in manual mode if the primary control value is 0 */
static inline bool is_cluster_manual(const struct video_ctrl *primary)
{
return primary->type == VIDEO_CTRL_TYPE_INTEGER64 ? primary->val64 == 0 : primary->val == 0;
}
void video_cluster_ctrl(struct video_ctrl *ctrls, uint8_t sz)
{
bool has_volatiles = false;
__ASSERT(!sz && !ctrls, "The 1st control, i.e. the primary control, must not be NULL");
for (uint8_t i = 0; i < sz; i++) {
ctrls[i].cluster_sz = sz;
ctrls[i].cluster = ctrls;
if (ctrls[i].flags & VIDEO_CTRL_FLAG_VOLATILE) {
has_volatiles = true;
}
}
ctrls->has_volatiles = has_volatiles;
}
void video_auto_cluster_ctrl(struct video_ctrl *ctrls, uint8_t sz, bool set_volatile)
{
video_cluster_ctrl(ctrls, sz);
__ASSERT(sz > 1, "Control auto cluster size must be > 1");
__ASSERT(!(set_volatile && !DEVICE_API_GET(video, ctrls->vdev->dev)->get_volatile_ctrl),
"Volatile is set but no ops");
ctrls->is_auto = true;
ctrls->has_volatiles = set_volatile;
ctrls->flags |= VIDEO_CTRL_FLAG_UPDATE;
/* If the cluster is in automatic mode, mark all manual controls inactive and volatile */
for (uint8_t i = 1; i < sz; i++) {
if (!is_cluster_manual(ctrls)) {
ctrls[i].flags |= VIDEO_CTRL_FLAG_INACTIVE |
(set_volatile ? VIDEO_CTRL_FLAG_VOLATILE : 0);
}
}
}
static int video_find_ctrl(const struct device *dev, uint32_t id, struct video_ctrl **ctrl)
{
struct video_device *vdev = video_find_vdev(dev);
while (vdev) {
SYS_DLIST_FOR_EACH_CONTAINER(&vdev->ctrls, *ctrl, node) {
if ((*ctrl)->id == id) {
return 0;
}
}
vdev = video_find_vdev(vdev->src_dev);
}
return -ENOTSUP;
}
int video_get_ctrl(const struct device *dev, struct video_control *control)
{
struct video_ctrl *ctrl = NULL;
__ASSERT_NO_MSG(dev != NULL);
__ASSERT_NO_MSG(control != NULL);
int ret = video_find_ctrl(dev, control->id, &ctrl);
if (ret) {
return ret;
}
if (ctrl->flags & VIDEO_CTRL_FLAG_WRITE_ONLY) {
LOG_ERR("Control id 0x%x is write-only\n", control->id);
return -EACCES;
}
if (ctrl->flags & VIDEO_CTRL_FLAG_VOLATILE) {
if (DEVICE_API_GET(video, ctrl->vdev->dev)->get_volatile_ctrl == NULL) {
return -ENOSYS;
}
/* Call driver's get_volatile_ctrl */
ret = DEVICE_API_GET(video, ctrl->vdev->dev)
->get_volatile_ctrl(ctrl->vdev->dev,
ctrl->cluster ? ctrl->cluster->id : ctrl->id);
if (ret) {
return ret;
}
}
/* Give the control's current value to user */
if (ctrl->type == VIDEO_CTRL_TYPE_INTEGER64) {
control->val64 = ctrl->val64;
} else {
control->val = ctrl->val;
}
return 0;
}
int video_set_ctrl(const struct device *dev, struct video_control *control)
{
struct video_ctrl *ctrl = NULL;
int ret;
uint8_t i = 0;
int32_t val = 0;
int64_t val64 = 0;
__ASSERT_NO_MSG(dev != NULL);
__ASSERT_NO_MSG(control != NULL);
ret = video_find_ctrl(dev, control->id, &ctrl);
if (ret) {
return ret;
}
if (ctrl->flags & VIDEO_CTRL_FLAG_READ_ONLY) {
LOG_ERR("Control id 0x%x is read-only\n", control->id);
return -EACCES;
}
if (ctrl->flags & VIDEO_CTRL_FLAG_INACTIVE) {
LOG_ERR("Control id 0x%x is inactive\n", control->id);
return -EACCES;
}
if (ctrl->type == VIDEO_CTRL_TYPE_INTEGER64
? !IN_RANGE(control->val64, ctrl->range.min64, ctrl->range.max64)
: !IN_RANGE(control->val, ctrl->range.min, ctrl->range.max)) {
LOG_ERR("Control value is invalid\n");
return -EINVAL;
}
/* No new value */
if (ctrl->type == VIDEO_CTRL_TYPE_INTEGER64 ? ctrl->val64 == control->val64
: ctrl->val == control->val) {
return 0;
}
/* Backup the control's value then set it to the new value */
if (ctrl->type == VIDEO_CTRL_TYPE_INTEGER64) {
val64 = ctrl->val64;
ctrl->val64 = control->val64;
} else {
val = ctrl->val;
ctrl->val = control->val;
}
/*
* For auto-clusters having volatiles, before switching to manual mode, get the current
* volatile values since those will become the initial manual values after this switch.
*/
if (ctrl == ctrl->cluster && ctrl->is_auto && ctrl->has_volatiles &&
is_cluster_manual(ctrl)) {
if (DEVICE_API_GET(video, ctrl->vdev->dev)->get_volatile_ctrl == NULL) {
ret = -ENOSYS;
goto restore;
}
ret = DEVICE_API_GET(video, ctrl->vdev->dev)
->get_volatile_ctrl(ctrl->vdev->dev, ctrl->id);
if (ret) {
goto restore;
}
}
/* Call driver's set_ctrl */
if (DEVICE_API_GET(video, ctrl->vdev->dev)->set_ctrl) {
ret = DEVICE_API_GET(video, ctrl->vdev->dev)
->set_ctrl(ctrl->vdev->dev,
ctrl->cluster ? ctrl->cluster->id : ctrl->id);
if (ret) {
goto restore;
}
}
/* Update the manual controls' flags of the cluster */
if (ctrl->cluster && ctrl->cluster->is_auto) {
for (i = 1; i < ctrl->cluster_sz; i++) {
if (!is_cluster_manual(ctrl->cluster)) {
/* Automatic mode: set the inactive and volatile flags of the manual
* controls
*/
ctrl->cluster[i].flags |=
VIDEO_CTRL_FLAG_INACTIVE |
(ctrl->cluster->has_volatiles ? VIDEO_CTRL_FLAG_VOLATILE
: 0);
} else {
/* Manual mode: clear the inactive and volatile flags of the manual
* controls
*/
ctrl->cluster[i].flags &=
~(VIDEO_CTRL_FLAG_INACTIVE | VIDEO_CTRL_FLAG_VOLATILE);
}
}
}
return 0;
restore:
/* Restore the old control's value */
if (ctrl->type == VIDEO_CTRL_TYPE_INTEGER64) {
ctrl->val64 = val64;
} else {
ctrl->val = val;
}
return ret;
}
static inline const char *video_get_ctrl_name(uint32_t id)
{
switch (id) {
/* User controls */
case VIDEO_CID_BRIGHTNESS:
return "Brightness";
case VIDEO_CID_CONTRAST:
return "Contrast";
case VIDEO_CID_SATURATION:
return "Saturation";
case VIDEO_CID_HUE:
return "Hue";
case VIDEO_CID_AUTO_WHITE_BALANCE:
return "White Balance, Automatic";
case VIDEO_CID_RED_BALANCE:
return "Red Balance";
case VIDEO_CID_BLUE_BALANCE:
return "Blue Balance";
case VIDEO_CID_GAMMA:
return "Gamma";
case VIDEO_CID_EXPOSURE:
return "Exposure";
case VIDEO_CID_AUTOGAIN:
return "Gain, Automatic";
case VIDEO_CID_GAIN:
return "Gain";
case VIDEO_CID_ANALOGUE_GAIN:
return "Analogue Gain";
case VIDEO_CID_HFLIP:
return "Horizontal Flip";
case VIDEO_CID_VFLIP:
return "Vertical Flip";
case VIDEO_CID_POWER_LINE_FREQUENCY:
return "Power Line Frequency";
case VIDEO_CID_HUE_AUTO:
return "Hue, Automatic";
case VIDEO_CID_WHITE_BALANCE_TEMPERATURE:
return "White Balance Temperature";
case VIDEO_CID_SHARPNESS:
return "Sharpness";
case VIDEO_CID_BACKLIGHT_COMPENSATION:
return "Backlight Compensation";
case VIDEO_CID_COLORFX:
return "Color Effects";
case VIDEO_CID_AUTOBRIGHTNESS:
return "Brightness, Automatic";
case VIDEO_CID_BAND_STOP_FILTER:
return "Band-Stop Filter";
case VIDEO_CID_ALPHA_COMPONENT:
return "Alpha Component";
/* Camera controls */
case VIDEO_CID_EXPOSURE_AUTO:
return "Auto Exposure";
case VIDEO_CID_EXPOSURE_ABSOLUTE:
return "Exposure Time, Absolute";
case VIDEO_CID_EXPOSURE_AUTO_PRIORITY:
return "Exposure, Dynamic Framerate";
case VIDEO_CID_PAN_RELATIVE:
return "Pan, Relative";
case VIDEO_CID_TILT_RELATIVE:
return "Tilt, Reset";
case VIDEO_CID_PAN_ABSOLUTE:
return "Pan, Absolute";
case VIDEO_CID_TILT_ABSOLUTE:
return "Tilt, Absolute";
case VIDEO_CID_FOCUS_ABSOLUTE:
return "Focus, Absolute";
case VIDEO_CID_FOCUS_RELATIVE:
return "Focus, Relative";
case VIDEO_CID_FOCUS_AUTO:
return "Focus, Automatic Continuous";
case VIDEO_CID_ZOOM_ABSOLUTE:
return "Zoom, Absolute";
case VIDEO_CID_ZOOM_RELATIVE:
return "Zoom, Relative";
case VIDEO_CID_ZOOM_CONTINUOUS:
return "Zoom, Continuous";
case VIDEO_CID_IRIS_ABSOLUTE:
return "Iris, Absolute";
case VIDEO_CID_IRIS_RELATIVE:
return "Iris, Relative";
case VIDEO_CID_WIDE_DYNAMIC_RANGE:
return "Wide Dynamic Range";
case VIDEO_CID_PAN_SPEED:
return "Pan, Speed";
case VIDEO_CID_TILT_SPEED:
return "Tilt, Speed";
case VIDEO_CID_CAMERA_ORIENTATION:
return "Camera Orientation";
case VIDEO_CID_CAMERA_SENSOR_ROTATION:
return "Camera Sensor Rotation";
/* JPEG encoder controls */
case VIDEO_CID_JPEG_COMPRESSION_QUALITY:
return "Compression Quality";
/* Image processing controls */
case VIDEO_CID_PIXEL_RATE:
return "Pixel Rate";
case VIDEO_CID_TEST_PATTERN:
return "Test Pattern";
case VIDEO_CID_LINK_FREQ:
return "Link Frequency";
default:
return NULL;
}
}
int video_query_ctrl(struct video_ctrl_query *cq)
{
int ret;
struct video_device *vdev;
struct video_ctrl *ctrl = NULL;
__ASSERT_NO_MSG(cq != NULL);
__ASSERT_NO_MSG(cq->dev != NULL);
if (cq->id & VIDEO_CTRL_FLAG_NEXT_CTRL) {
cq->id &= ~VIDEO_CTRL_FLAG_NEXT_CTRL;
vdev = video_find_vdev(cq->dev);
while (vdev != NULL) {
SYS_DLIST_FOR_EACH_CONTAINER(&vdev->ctrls, ctrl, node) {
if (ctrl->id > cq->id) {
goto fill_query;
}
}
cq->id = 0;
cq->dev = vdev->src_dev;
vdev = video_find_vdev(cq->dev);
}
return -ENOTSUP;
}
ret = video_find_ctrl(cq->dev, cq->id, &ctrl);
if (ret) {
return ret;
}
fill_query:
cq->id = ctrl->id;
cq->type = ctrl->type;
cq->flags = ctrl->flags;
cq->range = ctrl->range;
if (cq->type == VIDEO_CTRL_TYPE_MENU) {
cq->menu = ctrl->menu;
} else if (cq->type == VIDEO_CTRL_TYPE_INTEGER_MENU) {
cq->int_menu = ctrl->int_menu;
}
cq->name = video_get_ctrl_name(cq->id);
return 0;
}
void video_print_ctrl(const struct video_ctrl_query *const cq)
{
uint8_t i = 0;
const char *type = NULL;
char typebuf[8];
__ASSERT_NO_MSG(cq != NULL);
__ASSERT_NO_MSG(cq->dev != NULL);
/* Get type of the control */
switch (cq->type) {
case VIDEO_CTRL_TYPE_BOOLEAN:
type = "bool";
break;
case VIDEO_CTRL_TYPE_INTEGER:
type = "int";
break;
case VIDEO_CTRL_TYPE_INTEGER64:
type = "int64";
break;
case VIDEO_CTRL_TYPE_MENU:
type = "menu";
break;
case VIDEO_CTRL_TYPE_INTEGER_MENU:
type = "integer menu";
break;
case VIDEO_CTRL_TYPE_STRING:
type = "string";
break;
default:
break;
}
snprintf(typebuf, sizeof(typebuf), "(%s)", type);
/* Get current value of the control */
struct video_control vc = {.id = cq->id};
video_get_ctrl(cq->dev, &vc);
/* Print the control information */
if (cq->type == VIDEO_CTRL_TYPE_INTEGER64) {
LOG_INF("%32s 0x%08x %-8s (flags=0x%02x) : min=%lld max=%lld step=%lld "
"default=%lld value=%lld ",
cq->name, cq->id, typebuf, cq->flags, cq->range.min64, cq->range.max64,
cq->range.step64, cq->range.def64, vc.val64);
} else {
LOG_INF("%32s 0x%08x %-8s (flags=0x%02x) : min=%d max=%d step=%d default=%d "
"value=%d ",
cq->name, cq->id, typebuf, cq->flags, cq->range.min, cq->range.max,
cq->range.step, cq->range.def, vc.val);
}
if (cq->type == VIDEO_CTRL_TYPE_MENU && cq->menu) {
while (cq->menu[i]) {
LOG_INF("%*s %u: %s", 32, "", i, cq->menu[i]);
i++;
}
} else if (cq->type == VIDEO_CTRL_TYPE_INTEGER_MENU && cq->int_menu) {
while (cq->int_menu[i]) {
LOG_INF("%*s %u: %lld", 12, "", i, cq->int_menu[i]);
i++;
}
}
}