From: Nicola Fiorillo <nicfio@gmail.com>
To: Laurent Pinchart <laurent.pinchart@ideasonboard.com>,
Sakari Ailus <sakari.ailus@linux.intel.com>
Cc: "Mauro Carvalho Chehab" <mchehab@kernel.org>,
"Hans Verkuil" <hverkuil@kernel.org>,
"Dan Scally" <dan.scally@ideasonboard.com>,
"Hans de Goede" <hansg@kernel.org>,
"Uwe Kleine-König" <u.kleine-koenig@baylibre.com>,
linux-media@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH v2 5/7] media: i2c: gc8034: Add crop support
Date: Mon, 12 Oct 2026 16:34:41 +0200 [thread overview]
Message-ID: <20261012143443.34897-6-nicfio@gmail.com> (raw)
In-Reply-To: <20261012143443.34897-1-nicfio@gmail.com>
Implement the crop selection on the source pad, as for the GC5035: the
vertical crop is done by the readout window, the horizontal crop by the
sensor output crop, as the column start register only gives valid images
at some values. Crop offsets are even, to keep the RGGB order, and sizes
are multiples of 4. The width ranges from 512 to 3280: up to 384 columns
the CSI-2 receiver gets corrupted frames, 448 worked and 512 is kept as a
conservative minimum; an output as wide as the readout window causes
CSI-2 errors. The height ranges from 64 to 2448: with readout windows
taller than the vendor one (2464 rows) the frames get longer than
computed.
set_fmt and the vertical blanking behave as in the GC5035 driver.
Tested in the same way as the GC5035: v4l2-compliance and crop rectangles
at the bounds, in the corners, at the minimum size and in the centre.
Assisted-by: claude-opus-5-5 coccinelle sparse smatch
Signed-off-by: Nicola Fiorillo <nicfio@gmail.com>
---
drivers/media/i2c/gc8034.c | 168 +++++++++++++++++++++++++++++++++++--
1 file changed, 161 insertions(+), 7 deletions(-)
diff --git a/drivers/media/i2c/gc8034.c b/drivers/media/i2c/gc8034.c
index c21fb8510..732ee6c78 100644
--- a/drivers/media/i2c/gc8034.c
+++ b/drivers/media/i2c/gc8034.c
@@ -8,6 +8,7 @@
* The registers and sequences come from the Rockchip BSP driver,
* rockchip-linux/kernel, branch develop-5.10.
*/
+#include <linux/align.h>
#include <linux/array_size.h>
#include <linux/clk.h>
#include <linux/container_of.h>
@@ -17,6 +18,7 @@
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/math.h>
+#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/property.h>
@@ -89,6 +91,12 @@
*
* The default crop rectangle is the 3264x2448 image of the vendor register
* sequence, at (8, 52) in the area.
+ *
+ * Output widths up to 384 pixels gave corrupted frames on the CSI-2 receiver,
+ * 448 pixels and above worked; 512 is kept as the minimum. With a readout
+ * window taller than the 2464 rows of the vendor sequence, the frames were
+ * longer than the timing model below by the extra rows, so the output height
+ * is limited to 2448.
*/
#define GC8034_AREA_WIDTH 3282
#define GC8034_AREA_HEIGHT 2500
@@ -102,6 +110,10 @@
#define GC8034_CROP_TOP 52
#define GC8034_WIDTH 3264
#define GC8034_HEIGHT 2448
+#define GC8034_MIN_WIDTH 512
+#define GC8034_MIN_HEIGHT 64
+#define GC8034_MAX_WIDTH (GC8034_WIN_WIDTH - 4)
+#define GC8034_MAX_HEIGHT 2448
/*
* The frame is the readout window plus 20 lines plus the blanking register.
@@ -210,6 +222,7 @@ struct gc8034 {
struct v4l2_ctrl_handler ctrls;
struct v4l2_ctrl *exposure;
+ struct v4l2_ctrl *vblank;
struct v4l2_ctrl *hblank;
struct regmap *regmap;
@@ -487,14 +500,55 @@ static int gc8034_enum_frame_size(struct v4l2_subdev *sd,
if (fse->index > 0)
return -EINVAL;
- fse->min_width = GC8034_WIDTH;
- fse->max_width = GC8034_WIDTH;
- fse->min_height = GC8034_HEIGHT;
- fse->max_height = GC8034_HEIGHT;
+ /* The output size is the size of the crop rectangle. */
+ fse->min_width = GC8034_MIN_WIDTH;
+ fse->max_width = GC8034_MAX_WIDTH;
+ fse->min_height = GC8034_MIN_HEIGHT;
+ fse->max_height = GC8034_MAX_HEIGHT;
return 0;
}
+/*
+ * Update the controls that depend on the output size. Called with the state
+ * lock held, which is also the control handler lock.
+ */
+static int gc8034_update_ctrls(struct gc8034 *gc8034,
+ const struct v4l2_mbus_framefmt *format)
+{
+ s64 exposure_max;
+ int ret;
+
+ ret = __v4l2_ctrl_modify_range(gc8034->hblank,
+ GC8034_HTS - format->width,
+ GC8034_HTS - format->width, 1,
+ GC8034_HTS - format->width);
+ if (ret)
+ return ret;
+
+ /*
+ * As in ov01a10, setting the format or the crop rectangle resets the
+ * vertical blanking.
+ */
+ ret = __v4l2_ctrl_modify_range(gc8034->vblank, GC8034_VBLANK_MIN,
+ GC8034_VTS_MAX - format->height, 1,
+ GC8034_VBLANK_MIN);
+ if (ret)
+ return ret;
+
+ ret = __v4l2_ctrl_s_ctrl(gc8034->vblank, GC8034_VBLANK_MIN);
+ if (ret)
+ return ret;
+
+ exposure_max = round_down(format->height + gc8034->vblank->val -
+ GC8034_EXP_MARGIN, GC8034_EXP_STEP);
+
+ return __v4l2_ctrl_modify_range(gc8034->exposure, GC8034_EXP_MIN,
+ exposure_max, GC8034_EXP_STEP,
+ min_t(s64, GC8034_EXP_DEF,
+ exposure_max));
+}
+
static void gc8034_fill_format(struct v4l2_mbus_framefmt *fmt,
const struct v4l2_rect *crop)
{
@@ -537,6 +591,102 @@ static int gc8034_get_selection(struct v4l2_subdev *sd,
return 0;
}
+static int gc8034_set_selection(struct v4l2_subdev *sd,
+ const struct v4l2_subdev_client_info *ci,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_selection *sel)
+{
+ struct gc8034 *gc8034 = to_gc8034(sd);
+ struct v4l2_mbus_framefmt *format;
+ struct v4l2_rect rect;
+ u32 max_width, max_height;
+
+ if (sel->target != V4L2_SEL_TGT_CROP)
+ return -EINVAL;
+
+ if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE &&
+ v4l2_subdev_is_streaming(sd))
+ return -EBUSY;
+
+ /*
+ * Even offsets keep the RGGB order. The width is a multiple of 4 so
+ * that RAW10 lines are a whole number of bytes; only heights that are
+ * multiples of 4 have been tested.
+ */
+ rect.left = ALIGN_DOWN(clamp(sel->r.left, 0,
+ GC8034_AREA_WIDTH - GC8034_MIN_WIDTH), 2);
+ rect.top = ALIGN_DOWN(clamp(sel->r.top, 0,
+ GC8034_AREA_HEIGHT - GC8034_MIN_HEIGHT), 2);
+ max_width = min(GC8034_MAX_WIDTH, GC8034_AREA_WIDTH - rect.left);
+ max_height = min(GC8034_MAX_HEIGHT, GC8034_AREA_HEIGHT - rect.top);
+ rect.width = ALIGN_DOWN(clamp_t(u32, sel->r.width, GC8034_MIN_WIDTH,
+ max_width), 4);
+ rect.height = ALIGN_DOWN(clamp_t(u32, sel->r.height, GC8034_MIN_HEIGHT,
+ max_height), 4);
+
+ *v4l2_subdev_state_get_crop(state, 0) = rect;
+ sel->r = rect;
+
+ /* No binning or scaling: the output size is the crop size. */
+ format = v4l2_subdev_state_get_format(state, 0);
+ gc8034_fill_format(format, &rect);
+
+ if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE)
+ return gc8034_update_ctrls(gc8034, format);
+
+ return 0;
+}
+
+/*
+ * No binning or scaling: the output size is the size of the crop rectangle. A
+ * new width or height centres the crop rectangle on the default crop
+ * rectangle along that axis, within the area, for userspace that does not set
+ * the crop rectangle first. The default size gives the default rectangle back.
+ */
+static int gc8034_set_fmt(struct v4l2_subdev *sd,
+ const struct v4l2_subdev_client_info *ci,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct gc8034 *gc8034 = to_gc8034(sd);
+ struct v4l2_mbus_framefmt *format;
+ struct v4l2_rect *crop;
+ u32 width, height;
+ int pos;
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE &&
+ v4l2_subdev_is_streaming(sd))
+ return -EBUSY;
+
+ width = ALIGN_DOWN(clamp_t(u32, fmt->format.width, GC8034_MIN_WIDTH,
+ GC8034_MAX_WIDTH), 4);
+ height = ALIGN_DOWN(clamp_t(u32, fmt->format.height, GC8034_MIN_HEIGHT,
+ GC8034_MAX_HEIGHT), 4);
+
+ crop = v4l2_subdev_state_get_crop(state, 0);
+ if (width != crop->width) {
+ pos = GC8034_CROP_LEFT + (GC8034_WIDTH - (int)width) / 2;
+ pos = clamp(pos, 0, GC8034_AREA_WIDTH - (int)width);
+ crop->left = ALIGN_DOWN(pos, 2);
+ crop->width = width;
+ }
+ if (height != crop->height) {
+ pos = GC8034_CROP_TOP + (GC8034_HEIGHT - (int)height) / 2;
+ pos = clamp(pos, 0, GC8034_AREA_HEIGHT - (int)height);
+ crop->top = ALIGN_DOWN(pos, 2);
+ crop->height = height;
+ }
+
+ format = v4l2_subdev_state_get_format(state, 0);
+ gc8034_fill_format(format, crop);
+ fmt->format = *format;
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE)
+ return gc8034_update_ctrls(gc8034, format);
+
+ return 0;
+}
+
static int gc8034_init_state(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state)
{
@@ -750,7 +900,9 @@ static const struct v4l2_subdev_pad_ops gc8034_pad_ops = {
.enum_mbus_code = gc8034_enum_mbus_code,
.enum_frame_size = gc8034_enum_frame_size,
.get_fmt = v4l2_subdev_get_fmt,
+ .set_fmt = gc8034_set_fmt,
.get_selection = gc8034_get_selection,
+ .set_selection = gc8034_set_selection,
.get_frame_desc = gc8034_get_frame_desc,
.enable_streams = gc8034_enable_streams,
.disable_streams = gc8034_disable_streams,
@@ -825,9 +977,11 @@ static int gc8034_init_controls(struct gc8034 *gc8034)
GC8034_PIXEL_RATE, GC8034_PIXEL_RATE, 1,
GC8034_PIXEL_RATE);
- v4l2_ctrl_new_std(ctrl_hdlr, &gc8034_ctrl_ops, V4L2_CID_VBLANK,
- GC8034_VBLANK_MIN, GC8034_VTS_MAX - GC8034_HEIGHT, 1,
- GC8034_VBLANK_MIN);
+ /* Ranges for the default crop rectangle, see gc8034_update_ctrls(). */
+ gc8034->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &gc8034_ctrl_ops,
+ V4L2_CID_VBLANK, GC8034_VBLANK_MIN,
+ GC8034_VTS_MAX - GC8034_HEIGHT, 1,
+ GC8034_VBLANK_MIN);
gc8034->hblank = v4l2_ctrl_new_std(ctrl_hdlr, NULL, V4L2_CID_HBLANK,
GC8034_HTS - GC8034_WIDTH,
--
2.47.3
next prev parent reply other threads:[~2026-10-12 14:35 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-09 7:27 [PATCH 0/3] media: Add GalaxyCore GC5035 and GC8034 sensor drivers Nicola Fiorillo
2026-10-09 7:27 ` [PATCH 1/3] media: i2c: Add GC5035 image sensor driver Nicola Fiorillo
2026-10-09 9:48 ` Laurent Pinchart
2026-10-12 11:20 ` Uwe Kleine-König
2026-10-09 7:27 ` [PATCH 2/3] media: i2c: Add GC8034 " Nicola Fiorillo
2026-10-09 7:27 ` [PATCH 3/3] media: ipu-bridge: Add GalaxyCore GC5035 and GC8034 Nicola Fiorillo
2026-10-09 9:24 ` Dan Scally
2026-10-12 14:34 ` [PATCH v2 0/7] media: Add GalaxyCore GC5035 and GC8034 sensor drivers Nicola Fiorillo
2026-10-12 14:34 ` [PATCH v2 1/7] media: i2c: Add GC5035 image sensor driver Nicola Fiorillo
2026-10-12 14:34 ` [PATCH v2 2/7] media: i2c: Add GC8034 " Nicola Fiorillo
2026-10-12 14:34 ` [PATCH v2 3/7] media: ipu-bridge: Add GalaxyCore GC5035 and GC8034 Nicola Fiorillo
2026-10-12 14:34 ` [PATCH v2 4/7] media: i2c: gc5035: Add crop support Nicola Fiorillo
2026-10-12 14:34 ` Nicola Fiorillo [this message]
2026-10-12 14:34 ` [PATCH v2 6/7] media: ipu-bridge: Add the GC5035 binning link frequency Nicola Fiorillo
2026-10-12 14:34 ` [PATCH v2 7/7] media: i2c: gc5035: Add 2x2 binning support Nicola Fiorillo
2026-10-12 22:17 ` [PATCH v2 0/7] media: Add GalaxyCore GC5035 and GC8034 sensor drivers Laurent Pinchart
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261012143443.34897-6-nicfio@gmail.com \
--to=nicfio@gmail.com \
--cc=dan.scally@ideasonboard.com \
--cc=hansg@kernel.org \
--cc=hverkuil@kernel.org \
--cc=laurent.pinchart@ideasonboard.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-media@vger.kernel.org \
--cc=mchehab@kernel.org \
--cc=sakari.ailus@linux.intel.com \
--cc=u.kleine-koenig@baylibre.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox