[PATCH v12 63/86] media: imx219: Allow configuring cropping and binning through CRSM

Sakari Ailus
2026-04-09T20:15:06Z

Enable the configuration of cropping and binning through the Common Raw
Sensor Model. Also change the minimum embedded data line length the same
as that of the output pixel data line.

Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com>
---
drivers/media/i2c/imx219.c | 104 +++++++++++++++++++++++++++++++++----
1 file changed, 95 insertions(+), 9 deletions(-)

diff --git a/drivers/media/i2c/imx219.c b/drivers/media/i2c/imx219.c
index 7ed53810f941..591feb3d539f 100644
--- a/drivers/media/i2c/imx219.c
+++ b/drivers/media/i2c/imx219.c
@@ -154,6 +154,9 @@
#define IMX219_PIXEL_ARRAY_HEIGHT 2480U
#define IMX219_NATIVE_FORMAT MEDIA_BUS_FMT_SRGGB10_1X10

+#define IMX219_OUTPUT_X_SIZE_MIN 0x100
+#define IMX219_OUTPUT_Y_SIZE_MIN 0x100
+
/* Embedded metadata stream height */
#define IMX219_EMBEDDED_DATA_HEIGHT 2U

@@ -479,6 +482,45 @@ static const s64 imx219_binning_factors[] = {
[IMX219_BINNING_22] = V4L2_BINNING_FACTORS_MAKE(2, 1, 2, 1),
};

+static void imx219_apply_binning(struct v4l2_subdev_state *state,
+ struct v4l2_rect *crop, unsigned int index)
+{
+ struct v4l2_rect *compose =
+ v4l2_subdev_state_get_compose(state, IMX219_PAD_IMAGE);
+ struct v4l2_mbus_framefmt *source_format =
+ v4l2_subdev_state_get_format(state, IMX219_PAD_SOURCE,
+ IMX219_STREAM_IMAGE);
+ struct v4l2_mbus_framefmt *embedded_source_format =
+ v4l2_subdev_state_get_format(state, IMX219_PAD_SOURCE,
+ IMX219_STREAM_EDATA);
+ s64 binning = imx219_binning_factors[index];
+
+ crop->width = clamp(crop->width, IMX219_OUTPUT_X_SIZE_MIN *
+ V4L2_BINNING_FACTORS_HNUM(binning),
+ IMX219_VISIBLE_WIDTH) & ~1;
+ crop->height = clamp(crop->height, IMX219_OUTPUT_Y_SIZE_MIN *
+ V4L2_BINNING_FACTORS_VNUM(binning),
+ IMX219_VISIBLE_HEIGHT) & ~1;
+ crop->left = clamp((unsigned int)crop->left, IMX219_VISIBLE_LEFT,
+ IMX219_VISIBLE_LEFT + IMX219_VISIBLE_WIDTH -
+ crop->width);
+ crop->top = clamp((unsigned int)crop->top, IMX219_VISIBLE_TOP,
+ IMX219_VISIBLE_TOP + IMX219_VISIBLE_HEIGHT -
+ crop->height);
+
+ compose->width = crop->width / V4L2_BINNING_FACTORS_HNUM(binning);
+ compose->height = crop->height / V4L2_BINNING_FACTORS_VNUM(binning);
+
+ source_format->width = compose->width;
+ source_format->height = compose->height;
+
+ struct v4l2_mbus_framefmt *embedded_format =
+ v4l2_subdev_state_get_format(state, IMX219_PAD_EDATA);
+
+ embedded_format->width =
+ embedded_source_format->width = source_format->width;
+}
+
static int imx219_set_ctrl(struct v4l2_ctrl *ctrl)
{
struct imx219 *imx219 =
@@ -509,9 +551,24 @@ static int imx219_set_ctrl(struct v4l2_ctrl *ctrl)
return ret;
break;
}
- case V4L2_CID_BINNING_FACTORS:
+ case V4L2_CID_BINNING_FACTORS: {
+ struct v4l2_rect *crop =
+ v4l2_subdev_state_get_crop(state, IMX219_PAD_IMAGE);
+
+ if (imx219->streams_enabled)
+ return -EBUSY;
+
+ cci_write(imx219->regmap, IMX219_REG_BINNING_MODE_H,
+ imx219_binnings[ctrl->val].h, &ret);
+ cci_write(imx219->regmap, IMX219_REG_BINNING_MODE_V,
+ imx219_binnings[ctrl->val].v, &ret);
+
+ if (ctrl->val != ctrl->cur.val)
+ imx219_apply_binning(state, crop, ctrl->val);
+
return 0;
}
+ }

/*
* Applying V4L2 control value only happens
@@ -705,8 +762,6 @@ static int imx219_init_controls(struct imx219 *imx219)
goto error;
}

- imx219->binning->flags = V4L2_CTRL_FLAG_READ_ONLY;
-
ret = v4l2_fwnode_device_parse(&client->dev, &props);
if (ret)
goto error;
@@ -775,11 +830,6 @@ static int imx219_set_framefmt(struct imx219 *imx219,
cci_write(imx219->regmap, IMX219_REG_Y_ADD_END_A,
crop->top - IMX219_VISIBLE_TOP + crop->height - 1, &ret);

- cci_write(imx219->regmap, IMX219_REG_BINNING_MODE_H,
- imx219_binnings[imx219->binning->val].h, &ret);
- cci_write(imx219->regmap, IMX219_REG_BINNING_MODE_V,
- imx219_binnings[imx219->binning->val].v, &ret);
-
cci_write(imx219->regmap, IMX219_REG_X_OUTPUT_SIZE,
format->width, &ret);
cci_write(imx219->regmap, IMX219_REG_Y_OUTPUT_SIZE,
@@ -981,7 +1031,7 @@ static int imx219_enum_frame_size(struct v4l2_subdev *sd,
if (fse->code != MEDIA_BUS_FMT_META_8 || fse->index > 0)
return -EINVAL;

- fse->min_width = IMX219_VISIBLE_WIDTH;
+ fse->min_width = IMX219_OUTPUT_X_SIZE_MIN;
fse->max_width = IMX219_VISIBLE_WIDTH;
fse->min_height = IMX219_EMBEDDED_DATA_HEIGHT;
fse->max_height = IMX219_EMBEDDED_DATA_HEIGHT;
@@ -1223,6 +1273,41 @@ static int imx219_get_selection(struct v4l2_subdev *sd,
}
}

+static int imx219_set_selection(struct v4l2_subdev *sd,
+ const struct v4l2_subdev_client_info *ci,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_selection *sel)
+{
+ if (!(ci && ci->client_caps & V4L2_SUBDEV_CLIENT_CAP_COMMON_RAW_SENSOR))
+ return -EINVAL;
+
+ /*
+ * The embedded data stream doesn't support selection rectangles,
+ * neither on the embedded data pad nor on the source pad.
+ */
+ if (sel->pad != IMX219_PAD_IMAGE || sel->stream != IMX219_STREAM_IMAGE)
+ return -EINVAL;
+
+ switch (sel->target) {
+ case V4L2_SEL_TGT_CROP: {
+ struct imx219 *imx219 = to_imx219(sd);
+ struct v4l2_rect *crop =
+ v4l2_subdev_state_get_crop(state, IMX219_PAD_IMAGE);
+
+ imx219_apply_binning(state, &sel->r, imx219->binning->val);
+
+ *crop = sel->r;
+
+ return 0;
+ }
+ case V4L2_SEL_TGT_COMPOSE:
+ sel->r = *v4l2_subdev_state_get_compose(state, sel->pad);
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
static int imx219_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_mbus_frame_desc *fd)
{
@@ -1347,6 +1432,7 @@ static const struct v4l2_subdev_pad_ops imx219_pad_ops = {
.get_fmt = v4l2_subdev_get_fmt,
.set_fmt = imx219_set_pad_format,
.get_selection = imx219_get_selection,
+ .set_selection = imx219_set_selection,
.enum_frame_size = imx219_enum_frame_size,
.get_frame_desc = imx219_get_frame_desc,
.enable_streams = imx219_enable_streams,
--
2.47.3