From 678acffc12ddb472b919820f5fd8a499a21d47f2 Mon Sep 17 00:00:00 2001 From: Kurt Eckhardt Date: Sat, 9 Aug 2025 14:47:43 -0700 Subject: [PATCH 1/3] video: gc2145: support for CROP Implements the set_selection and get_selection APIs, if forwarded to it by a camera controller. It uses the new messages to allow you to set a crop window on top of the current format window. It also then allows you to move this crop window around in the frame window. With this driver I also updated it to allow any resolution from the displays min to max limits. static const struct video_format_cap fmts[] = { GC2145_VIDEO_FORMAT_CAP_HL(128, 1600, 128, 1200, VIDEO_PIX_FMT_RGB565), GC2145_VIDEO_FORMAT_CAP_HL(128, 1600, 128, 1200, VIDEO_PIX_FMT_YUYV), When the resolution is set, it computes the scale factor. Using the set_selection(VIDEO_SEL_TGT_CROP) allows you define a crop window within the format window. It clamps the ratio to a max of 3 as some other drivers limit it saying it helps with frame rates. Signed-off-by: Kurt Eckhardt --- drivers/video/gc2145.c | 228 ++++++++++++++++++++++++++++++----------- 1 file changed, 169 insertions(+), 59 deletions(-) diff --git a/drivers/video/gc2145.c b/drivers/video/gc2145.c index 28bdcdc1f1ce9..ec9dba0c9941b 100644 --- a/drivers/video/gc2145.c +++ b/drivers/video/gc2145.c @@ -768,14 +768,23 @@ struct gc2145_ctrls { struct gc2145_data { struct gc2145_ctrls ctrls; struct video_format fmt; + struct video_rect crop; + uint16_t format_width; + uint16_t format_height; + uint8_t ratio; }; -#define GC2145_VIDEO_FORMAT_CAP(width, height, format) \ - { \ - .pixelformat = format, .width_min = width, .width_max = width, \ - .height_min = height, .height_max = height, .width_step = 0, .height_step = 0, \ +#define GC2145_VIDEO_FORMAT_CAP_HL(width_l, width_h, height_l, height_h, format, step_w, step_h) \ + { \ + .pixelformat = (format), \ + .width_min = (width_l), .width_max = (width_h), \ + .height_min = (height_l), .height_max = (height_h), \ + .width_step = (step_w), .height_step = (step_h), \ } +#define GC2145_VIDEO_FORMAT_CAP(width, height, format) \ + GC2145_VIDEO_FORMAT_CAP_HL((width), (width), (height), (height), (format), 0, 0) + #define RESOLUTION_QVGA_W 320 #define RESOLUTION_QVGA_H 240 @@ -785,6 +794,13 @@ struct gc2145_data { #define RESOLUTION_UXGA_W 1600 #define RESOLUTION_UXGA_H 1200 +#define RESOLUTION_MAX_W RESOLUTION_UXGA_W +#define RESOLUTION_MAX_H RESOLUTION_UXGA_H + +/* Min not defined - smallest seen implementation is for QQVGA */ +#define RESOLUTION_MIN_W 160 +#define RESOLUTION_MIN_H 120 + static const struct video_format_cap fmts[] = { GC2145_VIDEO_FORMAT_CAP(RESOLUTION_QVGA_W, RESOLUTION_QVGA_H, VIDEO_PIX_FMT_RGB565), GC2145_VIDEO_FORMAT_CAP(RESOLUTION_VGA_W, RESOLUTION_VGA_H, VIDEO_PIX_FMT_RGB565), @@ -792,6 +808,11 @@ static const struct video_format_cap fmts[] = { GC2145_VIDEO_FORMAT_CAP(RESOLUTION_QVGA_W, RESOLUTION_QVGA_H, VIDEO_PIX_FMT_YUYV), GC2145_VIDEO_FORMAT_CAP(RESOLUTION_VGA_W, RESOLUTION_VGA_H, VIDEO_PIX_FMT_YUYV), GC2145_VIDEO_FORMAT_CAP(RESOLUTION_UXGA_W, RESOLUTION_UXGA_H, VIDEO_PIX_FMT_YUYV), + /* Add catchall resolution */ + GC2145_VIDEO_FORMAT_CAP_HL(RESOLUTION_MIN_W, RESOLUTION_MAX_W, RESOLUTION_MIN_H, + RESOLUTION_MAX_H, VIDEO_PIX_FMT_RGB565, 1, 1), + GC2145_VIDEO_FORMAT_CAP_HL(RESOLUTION_MIN_W, RESOLUTION_MAX_W, RESOLUTION_MIN_H, + RESOLUTION_MAX_H, VIDEO_PIX_FMT_YUYV, 1, 1), {0}, }; @@ -843,44 +864,70 @@ static int gc2145_set_output_format(const struct device *dev, int output_format) return 0; } +static int gc2145_set_crop_registers(const struct gc2145_config *cfg, uint32_t x, uint32_t y, + uint32_t w, uint32_t h) +{ + int ret; + + ret = video_write_cci_reg(&cfg->i2c, GC2145_REG_OUT_WIN_ROW_START, y); + if (ret < 0) { + return ret; + } + ret = video_write_cci_reg(&cfg->i2c, GC2145_REG_OUT_WIN_COL_START, x); + if (ret < 0) { + return ret; + } + ret = video_write_cci_reg(&cfg->i2c, GC2145_REG_OUT_WIN_HEIGHT, h); + if (ret < 0) { + return ret; + } + ret = video_write_cci_reg(&cfg->i2c, GC2145_REG_OUT_WIN_WIDTH, w); + if (ret < 0) { + return ret; + } + + /* Enable crop */ + return video_write_cci_reg(&cfg->i2c, GC2145_REG_CROP_ENABLE, GC2145_CROP_SET_ENABLE); +} + static int gc2145_set_resolution(const struct device *dev, uint32_t w, uint32_t h) { const struct gc2145_config *cfg = dev->config; + struct gc2145_data *drv_data = dev->data; int ret; uint16_t win_w; uint16_t win_h; - uint16_t c_ratio; - uint16_t r_ratio; - uint16_t x; - uint16_t y; uint16_t win_x; uint16_t win_y; - /* Add the subsampling factor depending on resolution */ - switch (w) { - case RESOLUTION_QVGA_W: - c_ratio = 3; - r_ratio = 3; - break; - case RESOLUTION_VGA_W: - c_ratio = 2; - r_ratio = 2; - break; - case RESOLUTION_UXGA_W: - c_ratio = 1; - r_ratio = 1; - break; - default: - LOG_ERR("Unsupported resolution %d %d", w, h); + /* If we are called from set_format, then we compute ratio and initialize crop */ + drv_data->ratio = MIN(RESOLUTION_UXGA_W / w, RESOLUTION_UXGA_H / h); + + /* make sure we don't end up with ratio of 0 */ + if (drv_data->ratio == 0) { return -EIO; - }; + } + + /* Restrict ratio to 3 for faster refresh ? */ + if (drv_data->ratio > 3) { + drv_data->ratio = 3; + } + + /* remember the width and height passed in */ + drv_data->format_width = w; + drv_data->format_height = h; + + /* Default to crop rectangle being same size as passed in resolution */ + drv_data->crop.left = 0; + drv_data->crop.top = 0; + drv_data->crop.width = w; + drv_data->crop.height = h; /* Calculates the window boundaries to obtain the desired resolution */ - win_w = w * c_ratio; - win_h = h * r_ratio; - x = (((win_w / c_ratio) - w) / 2); - y = (((win_h / r_ratio) - h) / 2); + + win_w = w * drv_data->ratio; + win_h = h * drv_data->ratio; win_x = ((UXGA_HSIZE - win_w) / 2); win_y = ((UXGA_VSIZE - win_h) / 2); @@ -909,31 +956,14 @@ static int gc2145_set_resolution(const struct device *dev, uint32_t w, uint32_t } /* Set cropping window next. */ - ret = video_write_cci_reg(&cfg->i2c, GC2145_REG_OUT_WIN_ROW_START, y); - if (ret < 0) { - return ret; - } - ret = video_write_cci_reg(&cfg->i2c, GC2145_REG_OUT_WIN_COL_START, x); - if (ret < 0) { - return ret; - } - ret = video_write_cci_reg(&cfg->i2c, GC2145_REG_OUT_WIN_HEIGHT, h); - if (ret < 0) { - return ret; - } - ret = video_write_cci_reg(&cfg->i2c, GC2145_REG_OUT_WIN_WIDTH, w); - if (ret < 0) { - return ret; - } - - /* Enable crop */ - ret = video_write_cci_reg(&cfg->i2c, GC2145_REG_CROP_ENABLE, GC2145_CROP_SET_ENABLE); + ret = gc2145_set_crop_registers(cfg, 0, 0, w, h); if (ret < 0) { return ret; } /* Set Sub-sampling ratio and mode */ - ret = video_write_cci_reg(&cfg->i2c, GC2145_REG_SUBSAMPLE, ((r_ratio << 4) | c_ratio)); + ret = video_write_cci_reg(&cfg->i2c, GC2145_REG_SUBSAMPLE, + (drv_data->ratio << 4) | drv_data->ratio); if (ret < 0) { return ret; } @@ -954,6 +984,51 @@ static int gc2145_set_resolution(const struct device *dev, uint32_t w, uint32_t return 0; } +static int gc2145_set_crop(const struct device *dev, struct video_selection *sel) +{ + /* set the crop rectangle */ + int ret; + const struct gc2145_config *cfg = dev->config; + struct gc2145_data *drv_data = dev->data; + + /* Verify the passed in rectangle is valid */ + if (((sel->rect.left + sel->rect.width) > drv_data->format_width) || + ((sel->rect.top + sel->rect.height) > drv_data->format_height)) { + LOG_ERR("Crop rectangle is invalid(%u %u) %ux%u > %ux%u", + sel->rect.left, sel->rect.top, sel->rect.width, sel->rect.height, + drv_data->format_width, drv_data->format_height); + return -EINVAL; + } + + /* if rectangle passed in is same as current, simply return */ + if ((drv_data->crop.left == sel->rect.left) && (drv_data->crop.top == sel->rect.top) && + (drv_data->crop.width == sel->rect.width) && + (drv_data->crop.height == sel->rect.height)) { + return 0; + } + + /* save out the updated crop window registers */ + ret = video_write_cci_reg(&cfg->i2c, GC2145_REG8(GC2145_REG_RESET), + GC2145_REG_RESET_P0_REGS); + if (ret < 0) { + return ret; + } + + ret = gc2145_set_crop_registers(cfg, sel->rect.left, sel->rect.top, + sel->rect.width, sel->rect.height); + if (ret < 0) { + return ret; + } + + drv_data->crop = sel->rect; + + /* enqueue/dequeue depend on this being set as well as the crop */ + drv_data->fmt.width = drv_data->crop.width; + drv_data->fmt.height = drv_data->crop.height; + + return 0; +} + static int gc2145_check_connection(const struct device *dev) { const struct gc2145_config *cfg = dev->config; @@ -1047,7 +1122,7 @@ static int gc2145_set_fmt(const struct device *dev, struct video_format *fmt) { struct gc2145_data *drv_data = dev->data; const struct gc2145_config *cfg = dev->config; - size_t res = ARRAY_SIZE(fmts); + size_t idx; int ret; if (memcmp(&drv_data->fmt, fmt, sizeof(drv_data->fmt)) == 0) { @@ -1056,16 +1131,10 @@ static int gc2145_set_fmt(const struct device *dev, struct video_format *fmt) } /* Check if camera is capable of handling given format */ - for (int i = 0; i < ARRAY_SIZE(fmts) - 1; i++) { - if (fmts[i].width_min == fmt->width && fmts[i].height_min == fmt->height && - fmts[i].pixelformat == fmt->pixelformat) { - res = i; - break; - } - } - if (res == ARRAY_SIZE(fmts)) { + ret = video_format_caps_index(fmts, fmt, &idx); + if (ret < 0) { LOG_ERR("Image format not supported"); - return -ENOTSUP; + return ret; } /* Set output format */ @@ -1171,12 +1240,53 @@ static int gc2145_set_ctrl(const struct device *dev, uint32_t id) } } +static int gc2145_set_selection(const struct device *dev, struct video_selection *sel) +{ + if (sel->type != VIDEO_BUF_TYPE_OUTPUT) { + return -EINVAL; + } + + if (sel->target != VIDEO_SEL_TGT_CROP) { + return -EINVAL; + } + + return gc2145_set_crop(dev, sel); +} + +static int gc2145_get_selection(const struct device *dev, struct video_selection *sel) +{ + struct gc2145_data *drv_data = dev->data; + + if (sel->type != VIDEO_BUF_TYPE_OUTPUT) { + return -EINVAL; + } + + switch (sel->target) { + case VIDEO_SEL_TGT_CROP: + sel->rect = drv_data->crop; + break; + + case VIDEO_SEL_TGT_NATIVE_SIZE: + sel->rect.top = 0; + sel->rect.left = 0; + sel->rect.width = drv_data->format_width; + sel->rect.height = drv_data->format_height; + break; + default: + return -EINVAL; + } + + return 0; +} + static DEVICE_API(video, gc2145_driver_api) = { .set_format = gc2145_set_fmt, .get_format = gc2145_get_fmt, .get_caps = gc2145_get_caps, .set_stream = gc2145_set_stream, .set_ctrl = gc2145_set_ctrl, + .set_selection = gc2145_set_selection, + .get_selection = gc2145_get_selection, }; static int gc2145_init_controls(const struct device *dev) From 6cb14ac62f3d45c39c95340a266eb2bc1fc40fdf Mon Sep 17 00:00:00 2001 From: Kurt Eckhardt Date: Mon, 13 Oct 2025 06:20:21 -0700 Subject: [PATCH 2/3] video: stm32_dcmi: forward selection Forward the get_selection and set_selection APIs to the camera objects, to allow some of the selections to be supported at the camera level. Signed-off-by: Kurt Eckhardt --- drivers/video/video_stm32_dcmi.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/video/video_stm32_dcmi.c b/drivers/video/video_stm32_dcmi.c index 3478d9262fda2..44b1110793d73 100644 --- a/drivers/video/video_stm32_dcmi.c +++ b/drivers/video/video_stm32_dcmi.c @@ -415,6 +415,20 @@ static int video_stm32_dcmi_get_frmival(const struct device *dev, struct video_f return 0; } +static int video_stm32_dcmi_set_selection(const struct device *dev, struct video_selection *sel) +{ + const struct video_stm32_dcmi_config *config = dev->config; + + return video_set_selection(config->sensor_dev, sel); +} + +static int video_stm32_dcmi_get_selection(const struct device *dev, struct video_selection *sel) +{ + const struct video_stm32_dcmi_config *config = dev->config; + + return video_get_selection(config->sensor_dev, sel); +} + static DEVICE_API(video, video_stm32_dcmi_driver_api) = { .set_format = video_stm32_dcmi_set_fmt, .get_format = video_stm32_dcmi_get_fmt, @@ -425,6 +439,8 @@ static DEVICE_API(video, video_stm32_dcmi_driver_api) = { .enum_frmival = video_stm32_dcmi_enum_frmival, .set_frmival = video_stm32_dcmi_set_frmival, .get_frmival = video_stm32_dcmi_get_frmival, + .set_selection = video_stm32_dcmi_set_selection, + .get_selection = video_stm32_dcmi_get_selection, }; static void video_stm32_dcmi_irq_config_func(const struct device *dev) From faba9a3b37e91885e4ff13a4eff31c63c3fa0d38 Mon Sep 17 00:00:00 2001 From: Kurt Eckhardt Date: Sat, 11 Oct 2025 10:00:34 -0700 Subject: [PATCH 3/3] video: stm32_dcmi: DMA Error recovery On several boards, such as the Arduino Giga and Portenta H7, they are often times setup with their camera buffers and potentially video buffers in SDRam. This can lead to a significant number of DMA errors, which currently stops the camera from returning any additional frames. Signed-off-by: Kurt Eckhardt --- drivers/video/video_stm32_dcmi.c | 35 +++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/drivers/video/video_stm32_dcmi.c b/drivers/video/video_stm32_dcmi.c index 44b1110793d73..e390395a1c8a4 100644 --- a/drivers/video/video_stm32_dcmi.c +++ b/drivers/video/video_stm32_dcmi.c @@ -55,9 +55,31 @@ struct video_stm32_dcmi_config { const struct stream dma; }; +static void stm32_dcmi_process_dma_error(DCMI_HandleTypeDef *hdcmi) +{ + struct video_stm32_dcmi_data *dev_data = + CONTAINER_OF(hdcmi, struct video_stm32_dcmi_data, hdcmi); + + LOG_DBG("Restart DMA after Error!"); + + /* Lets try to recover by stopping and restart */ + if (HAL_DCMI_Stop(&dev_data->hdcmi) != HAL_OK) { + LOG_WRN("HAL_DCMI_Stop FAILED!"); + return; + } + + if (HAL_DCMI_Start_DMA(&dev_data->hdcmi, + DCMI_MODE_CONTINUOUS, + (uint32_t)dev_data->vbuf->buffer, + dev_data->vbuf->size / 4) != HAL_OK) { + LOG_WRN("Continuous: HAL_DCMI_Start_DMA FAILED!"); + return; + } +} + void HAL_DCMI_ErrorCallback(DCMI_HandleTypeDef *hdcmi) { - LOG_WRN("%s", __func__); + stm32_dcmi_process_dma_error(hdcmi); } void HAL_DCMI_FrameEventCallback(DCMI_HandleTypeDef *hdcmi) @@ -96,19 +118,12 @@ static void dcmi_dma_callback(const struct device *dev, void *arg, uint32_t chan DMA_HandleTypeDef *hdma = arg; ARG_UNUSED(dev); - - if (status < 0) { - LOG_ERR("DMA callback error with channel %d.", channel); - } + ARG_UNUSED(channel); + ARG_UNUSED(status); HAL_DMA_IRQHandler(hdma); } -void HAL_DMA_ErrorCallback(DMA_HandleTypeDef *hdma) -{ - LOG_WRN("%s", __func__); -} - static int stm32_dma_init(const struct device *dev) { struct video_stm32_dcmi_data *data = dev->data;