| /* |
| * Copyright 2025 NXP |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <inttypes.h> |
| #include <stdio.h> |
| |
| #include <zephyr/drivers/video.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/video/video.h> |
| |
| LOG_MODULE_REGISTER(video_controls, CONFIG_VIDEO_LOG_LEVEL); |
| |
| /* 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; |
| } |
| |
| int video_cluster_ctrl(struct video_ctrl *ctrls, uint8_t sz) |
| { |
| bool has_volatiles = false; |
| |
| if (sz == 0 || ctrls == NULL) { |
| return -EINVAL; |
| } |
| |
| 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; |
| |
| return 0; |
| } |
| |
| int video_auto_cluster_ctrl(struct video_ctrl *ctrls, uint8_t sz, bool set_volatile) |
| { |
| int ret; |
| |
| if (sz <= 1 || |
| (set_volatile && !DEVICE_API_GET(video, ctrls->vdev->dev)->get_volatile_ctrl)) { |
| return -EINVAL; |
| } |
| |
| ret = video_cluster_ctrl(ctrls, sz); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| 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); |
| } |
| } |
| |
| return 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; |
| |
| if (dev == NULL || control == NULL) { |
| return -EINVAL; |
| } |
| |
| 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) { |
| /* Call driver's get_volatile_ctrl */ |
| ret = video_driver_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; |
| |
| if (dev == NULL || control == NULL) { |
| return -EINVAL; |
| } |
| |
| 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)) { |
| ret = video_driver_get_volatile_ctrl(ctrl->vdev->dev, ctrl->id); |
| if (ret) { |
| goto restore; |
| } |
| } |
| |
| /* Call driver's set_ctrl */ |
| ret = video_driver_set_ctrl(ctrl->vdev->dev, |
| ctrl->cluster ? ctrl->cluster->id : ctrl->id); |
| if (ret && ret != -ENOSYS) { |
| 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_ROTATE: |
| return "Rotate"; |
| 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"; |
| case VIDEO_CID_DIGITAL_GAIN: |
| return "Digital Gain"; |
| default: |
| return NULL; |
| } |
| } |
| |
| int video_query_ctrl(struct video_ctrl_query *cq) |
| { |
| int ret; |
| struct video_device *vdev; |
| struct video_ctrl *ctrl = NULL; |
| |
| if (cq == NULL || cq->dev == NULL) { |
| return -EINVAL; |
| } |
| |
| 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) |
| { |
| const char *type = NULL; |
| char buf[11]; |
| |
| if (cq == NULL || cq->dev == NULL) { |
| LOG_ERR("%s - Invalid parameter given", __func__); |
| return; |
| } |
| |
| /* 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 = "int menu"; |
| break; |
| case VIDEO_CTRL_TYPE_STRING: |
| type = "string"; |
| break; |
| default: |
| break; |
| } |
| snprintf(buf, sizeof(buf), "(%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 %-10s (flags=0x%02x) : min=%lld max=%lld step=%lld " |
| "default=%lld value=%lld ", |
| cq->name, cq->id, buf, cq->flags, cq->range.min64, cq->range.max64, |
| cq->range.step64, cq->range.def64, vc.val64); |
| } else { |
| LOG_INF("%32s 0x%08x %-10s (flags=0x%02x) : min=%d max=%d step=%d default=%d " |
| "value=%d ", |
| cq->name, cq->id, buf, 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) || |
| (cq->type == VIDEO_CTRL_TYPE_INTEGER_MENU && cq->int_menu)) { |
| for (uint8_t i = 0; i <= cq->range.max; i++) { |
| if (cq->type == VIDEO_CTRL_TYPE_INTEGER_MENU) { |
| snprintf(buf, sizeof(buf), "%" PRId64, cq->int_menu[i]); |
| } |
| |
| LOG_INF("%*s %u: %s", 32, "", i, |
| (cq->type == VIDEO_CTRL_TYPE_MENU) ? cq->menu[i] : buf); |
| } |
| } |
| } |
| |
| int64_t video_get_csi_link_freq(const struct device *dev, uint8_t bpp, uint8_t lane_nb) |
| { |
| struct video_control ctrl = { |
| .id = VIDEO_CID_LINK_FREQ, |
| }; |
| struct video_ctrl_query ctrl_query = { |
| .dev = dev, |
| .id = VIDEO_CID_LINK_FREQ, |
| }; |
| int ret; |
| |
| /* Try to get the LINK_FREQ value from the source device */ |
| ret = video_get_ctrl(dev, &ctrl); |
| if (ret < 0) { |
| goto fallback; |
| } |
| |
| ret = video_query_ctrl(&ctrl_query); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| if (!IN_RANGE(ctrl.val, ctrl_query.range.min, ctrl_query.range.max)) { |
| return -ERANGE; |
| } |
| |
| if (ctrl_query.int_menu == NULL) { |
| return -EINVAL; |
| } |
| |
| return (int64_t)ctrl_query.int_menu[ctrl.val]; |
| |
| fallback: |
| /* If VIDEO_CID_LINK_FREQ is not available, approximate from VIDEO_CID_PIXEL_RATE */ |
| ctrl.id = VIDEO_CID_PIXEL_RATE; |
| ret = video_get_ctrl(dev, &ctrl); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| /* CSI D-PHY is using a DDR data bus so bitrate is twice the frequency */ |
| return ctrl.val64 * bpp / (2 * lane_nb); |
| } |