From c85fc00fc90da27fd11cad036167d636b312d44f Mon Sep 17 00:00:00 2001 From: Benjamin Mugnier Date: Tue, 19 Aug 2025 16:47:41 +0200 Subject: [PATCH 1/7] media: dt-bindings: vd55g1: Add vd65g4 compatible Commit 5489abf66338c7890edc1a4ababedcb48ec3e996 upstream Switch compatible from a const to an enum to accommodate both the vd55g1 and the vd65g4, which is the color variant. Signed-off-by: Benjamin Mugnier Acked-by: Conor Dooley Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- Documentation/devicetree/bindings/media/i2c/st,vd55g1.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/media/i2c/st,vd55g1.yaml b/Documentation/devicetree/bindings/media/i2c/st,vd55g1.yaml index 3c071e6fbea613..060ac6829b661d 100644 --- a/Documentation/devicetree/bindings/media/i2c/st,vd55g1.yaml +++ b/Documentation/devicetree/bindings/media/i2c/st,vd55g1.yaml @@ -25,7 +25,11 @@ allOf: properties: compatible: - const: st,vd55g1 + enum: + - st,vd55g1 + - st,vd65g4 + description: + VD55G1 is the monochrome variant, while VD65G4 is the color one. reg: maxItems: 1 From 60ab641f2c2c92bb356345c6063f9d512595611f Mon Sep 17 00:00:00 2001 From: Benjamin Mugnier Date: Tue, 19 Aug 2025 16:47:42 +0200 Subject: [PATCH 2/7] media: i2c: vd55g1: Add support for vd65g4 RGB variant Commit e138e7f000424ee3bfef5fd38055d4c3ed5933b9 upstream The vd65g4 is the bayer version of the vd55g1. As opposed to the vd55g1, the vd65g4 does not need any patch. Check the sensor id at probe and choose to patch or not on power_on() according to it. It's bayer matrix's order is RGGB. This commit handles hflip and vflip by switching the bayer pattern accordingly. Signed-off-by: Benjamin Mugnier Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/vd55g1.c | 234 ++++++++++++++++++++++++++----------- 1 file changed, 167 insertions(+), 67 deletions(-) diff --git a/drivers/media/i2c/vd55g1.c b/drivers/media/i2c/vd55g1.c index f09d6bf3264135..78d18c0281541b 100644 --- a/drivers/media/i2c/vd55g1.c +++ b/drivers/media/i2c/vd55g1.c @@ -29,9 +29,11 @@ /* Register Map */ #define VD55G1_REG_MODEL_ID CCI_REG32_LE(0x0000) -#define VD55G1_MODEL_ID 0x53354731 +#define VD55G1_MODEL_ID_VD55G1 0x53354731 /* Mono */ +#define VD55G1_MODEL_ID_VD65G4 0x53354733 /* RGB */ #define VD55G1_REG_REVISION CCI_REG16_LE(0x0004) #define VD55G1_REVISION_CCB 0x2020 +#define VD55G1_REVISION_BAYER 0x3030 #define VD55G1_REG_FWPATCH_REVISION CCI_REG16_LE(0x0012) #define VD55G1_REG_FWPATCH_START_ADDR CCI_REG8(0x2000) #define VD55G1_REG_SYSTEM_FSM CCI_REG8(0x001c) @@ -39,7 +41,8 @@ #define VD55G1_SYSTEM_FSM_SW_STBY 0x02 #define VD55G1_SYSTEM_FSM_STREAMING 0x03 #define VD55G1_REG_BOOT CCI_REG8(0x0200) -#define VD55G1_BOOT_PATCH_SETUP 2 +#define VD55G1_BOOT_BOOT 1 +#define VD55G1_BOOT_PATCH_AND_BOOT 2 #define VD55G1_REG_STBY CCI_REG8(0x0201) #define VD55G1_STBY_START_STREAM 1 #define VD55G1_REG_STREAMING CCI_REG8(0x0202) @@ -132,7 +135,10 @@ #define VD55G1_MIPI_RATE_MIN (250 * MEGA) #define VD55G1_MIPI_RATE_MAX (1200 * MEGA) -static const u8 patch_array[] = { +#define VD55G1_MODEL_ID_NAME(id) \ + ((id) == VD55G1_MODEL_ID_VD55G1 ? "vd55g1" : "vd65g4") + +static const u8 vd55g1_patch_array[] = { 0x44, 0x03, 0x09, 0x02, 0xe6, 0x01, 0x42, 0x00, 0xea, 0x01, 0x42, 0x00, 0xf0, 0x01, 0x42, 0x00, 0xe6, 0x01, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -466,22 +472,24 @@ struct vd55g1_mode { u32 height; }; -struct vd55g1_fmt_desc { - u32 code; - u8 bpp; - u8 data_type; +static const u32 vd55g1_mbus_formats_mono[] = { + MEDIA_BUS_FMT_Y8_1X8, + MEDIA_BUS_FMT_Y10_1X10, }; -static const struct vd55g1_fmt_desc vd55g1_mbus_codes[] = { +/* Format order is : no flip, hflip, vflip, both */ +static const u32 vd55g1_mbus_formats_bayer[][4] = { { - .code = MEDIA_BUS_FMT_Y8_1X8, - .bpp = 8, - .data_type = MIPI_CSI2_DT_RAW8, + MEDIA_BUS_FMT_SRGGB8_1X8, + MEDIA_BUS_FMT_SGRBG8_1X8, + MEDIA_BUS_FMT_SGBRG8_1X8, + MEDIA_BUS_FMT_SBGGR8_1X8, }, { - .code = MEDIA_BUS_FMT_Y10_1X10, - .bpp = 10, - .data_type = MIPI_CSI2_DT_RAW10, + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SBGGR10_1X10, }, }; @@ -524,6 +532,7 @@ struct vd55g1_vblank_limits { struct vd55g1 { struct device *dev; + unsigned int id; struct v4l2_subdev sd; struct media_pad pad; struct regulator_bulk_data supplies[ARRAY_SIZE(vd55g1_supply_name)]; @@ -572,27 +581,78 @@ static inline struct vd55g1 *ctrl_to_vd55g1(struct v4l2_ctrl *ctrl) return to_vd55g1(sd); } -static const struct vd55g1_fmt_desc *vd55g1_get_fmt_desc(struct vd55g1 *sensor, - u32 code) +static unsigned int vd55g1_get_fmt_bpp(u32 code) { - unsigned int i; + switch (code) { + case MEDIA_BUS_FMT_Y8_1X8: + case MEDIA_BUS_FMT_SRGGB8_1X8: + case MEDIA_BUS_FMT_SGRBG8_1X8: + case MEDIA_BUS_FMT_SGBRG8_1X8: + case MEDIA_BUS_FMT_SBGGR8_1X8: + default: + return 8; + + case MEDIA_BUS_FMT_Y10_1X10: + case MEDIA_BUS_FMT_SRGGB10_1X10: + case MEDIA_BUS_FMT_SGRBG10_1X10: + case MEDIA_BUS_FMT_SGBRG10_1X10: + case MEDIA_BUS_FMT_SBGGR10_1X10: + return 10; + } +} + +static unsigned int vd55g1_get_fmt_data_type(u32 code) +{ + switch (code) { + case MEDIA_BUS_FMT_Y8_1X8: + case MEDIA_BUS_FMT_SRGGB8_1X8: + case MEDIA_BUS_FMT_SGRBG8_1X8: + case MEDIA_BUS_FMT_SGBRG8_1X8: + case MEDIA_BUS_FMT_SBGGR8_1X8: + default: + return MIPI_CSI2_DT_RAW8; + + case MEDIA_BUS_FMT_Y10_1X10: + case MEDIA_BUS_FMT_SRGGB10_1X10: + case MEDIA_BUS_FMT_SGRBG10_1X10: + case MEDIA_BUS_FMT_SGBRG10_1X10: + case MEDIA_BUS_FMT_SBGGR10_1X10: + return MIPI_CSI2_DT_RAW10; + } +} + +static u32 vd55g1_get_fmt_code(struct vd55g1 *sensor, u32 code) +{ + unsigned int i, j; - for (i = 0; i < ARRAY_SIZE(vd55g1_mbus_codes); i++) { - if (vd55g1_mbus_codes[i].code == code) - return &vd55g1_mbus_codes[i]; + if (sensor->id == VD55G1_MODEL_ID_VD55G1) + return code; + + for (i = 0; i < ARRAY_SIZE(vd55g1_mbus_formats_bayer); i++) { + for (j = 0; j < ARRAY_SIZE(vd55g1_mbus_formats_bayer[i]); j++) { + if (vd55g1_mbus_formats_bayer[i][j] == code) + goto adapt_bayer_pattern; + } } + dev_warn(sensor->dev, "Unsupported mbus format\n"); - /* Should never happen */ - dev_warn(sensor->dev, "Unsupported code %d. default to 8 bpp\n", code); + return code; + +adapt_bayer_pattern: + j = 0; + /* In first init_state() call, controls might not be initialized yet */ + if (sensor->hflip_ctrl && sensor->vflip_ctrl) { + j = (sensor->hflip_ctrl->val ? 1 : 0) + + (sensor->vflip_ctrl->val ? 2 : 0); + } - return &vd55g1_mbus_codes[0]; + return vd55g1_mbus_formats_bayer[i][j]; } static s32 vd55g1_get_pixel_rate(struct vd55g1 *sensor, struct v4l2_mbus_framefmt *format) { - return sensor->mipi_rate / - vd55g1_get_fmt_desc(sensor, format->code)->bpp; + return sensor->mipi_rate / vd55g1_get_fmt_bpp(format->code); } static unsigned int vd55g1_get_hblank_min(struct vd55g1 *sensor, @@ -605,7 +665,7 @@ static unsigned int vd55g1_get_hblank_min(struct vd55g1 *sensor, /* MIPI required time */ mipi_req_line_time = (crop->width * - vd55g1_get_fmt_desc(sensor, format->code)->bpp + + vd55g1_get_fmt_bpp(format->code) + VD55G1_MIPI_MARGIN) / (sensor->mipi_rate / MEGA); mipi_req_line_length = mipi_req_line_time * sensor->pixel_clock / @@ -887,7 +947,7 @@ static void vd55g1_update_pad_fmt(struct vd55g1 *sensor, const struct vd55g1_mode *mode, u32 code, struct v4l2_mbus_framefmt *fmt) { - fmt->code = code; + fmt->code = vd55g1_get_fmt_code(sensor, code); fmt->width = mode->width; fmt->height = mode->height; fmt->colorspace = V4L2_COLORSPACE_RAW; @@ -951,10 +1011,9 @@ static int vd55g1_set_framefmt(struct vd55g1 *sensor, int ret = 0; vd55g1_write(sensor, VD55G1_REG_FORMAT_CTRL, - vd55g1_get_fmt_desc(sensor, format->code)->bpp, &ret); + vd55g1_get_fmt_bpp(format->code), &ret); vd55g1_write(sensor, VD55G1_REG_OIF_IMG_CTRL, - vd55g1_get_fmt_desc(sensor, format->code)->data_type, - &ret); + vd55g1_get_fmt_data_type(format->code), &ret); switch (crop->width / format->width) { case 1: @@ -1114,26 +1173,45 @@ static int vd55g1_patch(struct vd55g1 *sensor) u64 patch; int ret = 0; - vd55g1_write_array(sensor, VD55G1_REG_FWPATCH_START_ADDR, - sizeof(patch_array), patch_array, &ret); - vd55g1_write(sensor, VD55G1_REG_BOOT, VD55G1_BOOT_PATCH_SETUP, &ret); - vd55g1_poll_reg(sensor, VD55G1_REG_BOOT, 0, &ret); - if (ret) { - dev_err(sensor->dev, "Failed to apply patch\n"); - return ret; - } + /* vd55g1 needs a patch while vd65g4 does not */ + if (sensor->id == VD55G1_MODEL_ID_VD55G1) { + vd55g1_write_array(sensor, VD55G1_REG_FWPATCH_START_ADDR, + sizeof(vd55g1_patch_array), + vd55g1_patch_array, &ret); + vd55g1_write(sensor, VD55G1_REG_BOOT, + VD55G1_BOOT_PATCH_AND_BOOT, &ret); + vd55g1_poll_reg(sensor, VD55G1_REG_BOOT, 0, &ret); + if (ret) { + dev_err(sensor->dev, "Failed to apply patch\n"); + return ret; + } - vd55g1_read(sensor, VD55G1_REG_FWPATCH_REVISION, &patch, &ret); - if (patch != (VD55G1_FWPATCH_REVISION_MAJOR << 8) + - VD55G1_FWPATCH_REVISION_MINOR) { - dev_err(sensor->dev, "Bad patch version expected %d.%d got %d.%d\n", - VD55G1_FWPATCH_REVISION_MAJOR, - VD55G1_FWPATCH_REVISION_MINOR, + vd55g1_read(sensor, VD55G1_REG_FWPATCH_REVISION, &patch, &ret); + if (patch != (VD55G1_FWPATCH_REVISION_MAJOR << 8) + + VD55G1_FWPATCH_REVISION_MINOR) { + dev_err(sensor->dev, "Bad patch version expected %d.%d got %d.%d\n", + VD55G1_FWPATCH_REVISION_MAJOR, + VD55G1_FWPATCH_REVISION_MINOR, + (u8)(patch >> 8), (u8)(patch & 0xff)); + return -ENODEV; + } + dev_dbg(sensor->dev, "patch %d.%d applied\n", (u8)(patch >> 8), (u8)(patch & 0xff)); - return -ENODEV; + + } else { + vd55g1_write(sensor, VD55G1_REG_BOOT, VD55G1_BOOT_BOOT, &ret); + vd55g1_poll_reg(sensor, VD55G1_REG_BOOT, 0, &ret); + if (ret) { + dev_err(sensor->dev, "Failed to boot\n"); + return ret; + } + } + + ret = vd55g1_wait_state(sensor, VD55G1_SYSTEM_FSM_SW_STBY, NULL); + if (ret) { + dev_err(sensor->dev, "Sensor waiting after boot failed\n"); + return ret; } - dev_dbg(sensor->dev, "patch %d.%d applied\n", - (u8)(patch >> 8), (u8)(patch & 0xff)); return 0; } @@ -1165,10 +1243,19 @@ static int vd55g1_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_mbus_code_enum *code) { - if (code->index >= ARRAY_SIZE(vd55g1_mbus_codes)) - return -EINVAL; + struct vd55g1 *sensor = to_vd55g1(sd); + u32 base_code; - code->code = vd55g1_mbus_codes[code->index].code; + if (sensor->id == VD55G1_MODEL_ID_VD55G1) { + if (code->index >= ARRAY_SIZE(vd55g1_mbus_formats_mono)) + return -EINVAL; + base_code = vd55g1_mbus_formats_mono[code->index]; + } else { + if (code->index >= ARRAY_SIZE(vd55g1_mbus_formats_bayer)) + return -EINVAL; + base_code = vd55g1_mbus_formats_bayer[code->index][0]; + } + code->code = vd55g1_get_fmt_code(sensor, base_code); return 0; } @@ -1275,7 +1362,7 @@ static int vd55g1_init_state(struct v4l2_subdev *sd, return ret; vd55g1_update_pad_fmt(sensor, &vd55g1_supported_modes[VD55G1_MODE_DEF], - vd55g1_mbus_codes[VD55G1_MBUS_CODE_DEF].code, + vd55g1_get_fmt_code(sensor, VD55G1_MBUS_CODE_DEF), &fmt.format); return vd55g1_set_pad_fmt(sd, sd_state, &fmt); @@ -1285,9 +1372,16 @@ static int vd55g1_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_frame_size_enum *fse) { + struct vd55g1 *sensor = to_vd55g1(sd); + u32 code; + if (fse->index >= ARRAY_SIZE(vd55g1_supported_modes)) return -EINVAL; + code = vd55g1_get_fmt_code(sensor, fse->code); + if (fse->code != code) + return -EINVAL; + fse->min_width = vd55g1_supported_modes[fse->index].width; fse->max_width = fse->min_width; fse->min_height = vd55g1_supported_modes[fse->index].height; @@ -1463,8 +1557,12 @@ static int vd55g1_init_ctrls(struct vd55g1 *sensor) /* Flip cluster */ sensor->hflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, 0); + if (sensor->hflip_ctrl) + sensor->hflip_ctrl->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; sensor->vflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, 0); + if (sensor->vflip_ctrl) + sensor->vflip_ctrl->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; v4l2_ctrl_cluster(2, &sensor->hflip_ctrl); /* Exposition cluster */ @@ -1548,26 +1646,34 @@ static int vd55g1_init_ctrls(struct vd55g1 *sensor) static int vd55g1_detect(struct vd55g1 *sensor) { - u64 device_rev; - u64 id; + unsigned int dt_id = (uintptr_t)device_get_match_data(sensor->dev); + u64 rev, id; int ret; ret = vd55g1_read(sensor, VD55G1_REG_MODEL_ID, &id, NULL); if (ret) return ret; - if (id != VD55G1_MODEL_ID) { - dev_warn(sensor->dev, "Unsupported sensor id %x\n", (u32)id); + if (id != VD55G1_MODEL_ID_VD55G1 && id != VD55G1_MODEL_ID_VD65G4) { + dev_warn(sensor->dev, "Unsupported sensor id 0x%x\n", + (u32)id); + return -ENODEV; + } + if (id != dt_id) { + dev_err(sensor->dev, "Probed sensor %s and device tree definition (%s) mismatch", + VD55G1_MODEL_ID_NAME(id), VD55G1_MODEL_ID_NAME(dt_id)); return -ENODEV; } + sensor->id = id; - ret = vd55g1_read(sensor, VD55G1_REG_REVISION, &device_rev, NULL); + ret = vd55g1_read(sensor, VD55G1_REG_REVISION, &rev, NULL); if (ret) return ret; - if (device_rev != VD55G1_REVISION_CCB) { - dev_err(sensor->dev, "Unsupported sensor revision (0x%x)\n", - (u16)device_rev); + if ((id == VD55G1_MODEL_ID_VD55G1 && rev != VD55G1_REVISION_CCB) && + (id == VD55G1_MODEL_ID_VD65G4 && rev != VD55G1_REVISION_BAYER)) { + dev_err(sensor->dev, "Unsupported sensor revision 0x%x for sensor %s\n", + (u16)rev, VD55G1_MODEL_ID_NAME(id)); return -ENODEV; } @@ -1616,13 +1722,6 @@ static int vd55g1_power_on(struct device *dev) goto disable_clock; } - ret = vd55g1_wait_state(sensor, VD55G1_SYSTEM_FSM_SW_STBY, NULL); - if (ret) { - dev_err(dev, "Sensor waiting after patch failed %d\n", - ret); - goto disable_clock; - } - return 0; disable_clock: @@ -1934,7 +2033,8 @@ static void vd55g1_remove(struct i2c_client *client) } static const struct of_device_id vd55g1_dt_ids[] = { - { .compatible = "st,vd55g1" }, + { .compatible = "st,vd55g1", .data = (void *)VD55G1_MODEL_ID_VD55G1 }, + { .compatible = "st,vd65g4", .data = (void *)VD55G1_MODEL_ID_VD65G4 }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, vd55g1_dt_ids); From 6bd4b6d453999b75caac9c391326374ec009ef98 Mon Sep 17 00:00:00 2001 From: Benjamin Mugnier Date: Mon, 29 Jun 2026 12:51:46 +0200 Subject: [PATCH 3/7] media: i2c: vd55g1: Fix media bus code initialization In the driver initialization, the index of the default media bus code from the supported media bus code array is passed directly to the vd55g1_get_fmt_code() function instead of the proper media bus code. This works correctly as a proper media bus code is set after initialization but could not have been the case. This also resulted in mutliple "Unsupported mbus format" error messages. Retrieve the media bus code from the media bus code array, and pass this media bus code to vd55g1_get_fmt_code() instead of the code index. Rename VD55G1_MBUS_CODE_DEF to VD55G1_MBUS_CODE_IDX_DEF and VD55G1_MODE_DEF to VD55G1_MODE_IDX_DEF while at it to avoid future confusions. Display the guilty error code in warning message. Cc: stable@vger.kernel.org Fixes: e138e7f00042 ("media: i2c: vd55g1: Add support for vd65g4 RGB variant") Signed-off-by: Benjamin Mugnier --- drivers/media/i2c/vd55g1.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/media/i2c/vd55g1.c b/drivers/media/i2c/vd55g1.c index 78d18c0281541b..fceb437e19becf 100644 --- a/drivers/media/i2c/vd55g1.c +++ b/drivers/media/i2c/vd55g1.c @@ -114,9 +114,8 @@ #define VD55G1_WIDTH 804 #define VD55G1_HEIGHT 704 -#define VD55G1_MODE_DEF 0 +#define VD55G1_MODE_IDX_DEF 0 #define VD55G1_NB_GPIOS 4 -#define VD55G1_MBUS_CODE_DEF 0 #define VD55G1_DGAIN_DEF 256 #define VD55G1_AGAIN_DEF 19 #define VD55G1_EXPO_MAX_TERM 64 @@ -634,7 +633,7 @@ static u32 vd55g1_get_fmt_code(struct vd55g1 *sensor, u32 code) goto adapt_bayer_pattern; } } - dev_warn(sensor->dev, "Unsupported mbus format\n"); + dev_warn(sensor->dev, "Unsupported mbus format: 0x%x\n", code); return code; @@ -1347,6 +1346,7 @@ static int vd55g1_init_state(struct v4l2_subdev *sd, { struct vd55g1 *sensor = to_vd55g1(sd); struct v4l2_subdev_format fmt = { 0 }; + int code; struct v4l2_subdev_route routes[] = { { .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE } }; @@ -1361,9 +1361,13 @@ static int vd55g1_init_state(struct v4l2_subdev *sd, if (ret) return ret; - vd55g1_update_pad_fmt(sensor, &vd55g1_supported_modes[VD55G1_MODE_DEF], - vd55g1_get_fmt_code(sensor, VD55G1_MBUS_CODE_DEF), - &fmt.format); + if (sensor->id == VD55G1_MODEL_ID_VD55G1) + code = vd55g1_mbus_formats_mono[0]; + else + code = vd55g1_mbus_formats_bayer[0][0]; + vd55g1_update_pad_fmt(sensor, + &vd55g1_supported_modes[VD55G1_MODE_IDX_DEF], + vd55g1_get_fmt_code(sensor, code), &fmt.format); return vd55g1_set_pad_fmt(sd, sd_state, &fmt); } From 1210ed166b99197936fbcf15fc8f454350dd039b Mon Sep 17 00:00:00 2001 From: Benjamin Mugnier Date: Mon, 29 Jun 2026 12:51:47 +0200 Subject: [PATCH 4/7] media: i2c: vd55g1: Remove spurious pad format update on init_state() vd55g1_update_pad_fmt() is called in vd55g1_init_state(). But vd55g1_set_pad_fmt(), called at the end of vd55g1_init_state(), also calls vd55g1_update_pad_fmt() itself. Enhance readability and clear confusion by only preparing the format in vd55g1_init_state() and let vd55g1_set_pad_fmt() update it instead, effectively calling it only 1 time instead of 2. Reviewed-by: Jacopo Mondi Signed-off-by: Benjamin Mugnier --- drivers/media/i2c/vd55g1.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/media/i2c/vd55g1.c b/drivers/media/i2c/vd55g1.c index fceb437e19becf..22464fe315624f 100644 --- a/drivers/media/i2c/vd55g1.c +++ b/drivers/media/i2c/vd55g1.c @@ -1365,9 +1365,9 @@ static int vd55g1_init_state(struct v4l2_subdev *sd, code = vd55g1_mbus_formats_mono[0]; else code = vd55g1_mbus_formats_bayer[0][0]; - vd55g1_update_pad_fmt(sensor, - &vd55g1_supported_modes[VD55G1_MODE_IDX_DEF], - vd55g1_get_fmt_code(sensor, code), &fmt.format); + fmt.format.code = vd55g1_get_fmt_code(sensor, code); + fmt.format.width = vd55g1_supported_modes[VD55G1_MODE_IDX_DEF].width; + fmt.format.height = vd55g1_supported_modes[VD55G1_MODE_IDX_DEF].height; return vd55g1_set_pad_fmt(sd, sd_state, &fmt); } From 8ec082b083bb2024780fa7e13686da1d73d0eb2d Mon Sep 17 00:00:00 2001 From: Benjamin Mugnier Date: Mon, 29 Jun 2026 12:51:48 +0200 Subject: [PATCH 5/7] media: i2c: vd55g1: Fix manual digital gain on color variant Apply digital gain to all channels, each channel representing a color. Cc: stable@vger.kernel.org Fixes: e138e7f00042 ("media: i2c: vd55g1: Add support for vd65g4 RGB variant") Reviewed-by: Jacopo Mondi Signed-off-by: Benjamin Mugnier --- drivers/media/i2c/vd55g1.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/drivers/media/i2c/vd55g1.c b/drivers/media/i2c/vd55g1.c index 22464fe315624f..37d44abd1435ce 100644 --- a/drivers/media/i2c/vd55g1.c +++ b/drivers/media/i2c/vd55g1.c @@ -60,7 +60,10 @@ #define VD55G1_PATGEN_ENABLE BIT(0) #define VD55G1_REG_MANUAL_ANALOG_GAIN CCI_REG8(0x0501) #define VD55G1_REG_MANUAL_COARSE_EXPOSURE CCI_REG16_LE(0x0502) -#define VD55G1_REG_MANUAL_DIGITAL_GAIN CCI_REG16_LE(0x0504) +#define VD55G1_REG_MANUAL_DIGITAL_GAIN_CH0 CCI_REG16_LE(0x0504) +#define VD55G1_REG_MANUAL_DIGITAL_GAIN_CH1 CCI_REG16_LE(0x0506) +#define VD55G1_REG_MANUAL_DIGITAL_GAIN_CH2 CCI_REG16_LE(0x0508) +#define VD55G1_REG_MANUAL_DIGITAL_GAIN_CH3 CCI_REG16_LE(0x050a) #define VD55G1_REG_APPLIED_COARSE_EXPOSURE CCI_REG16_LE(0x00e8) #define VD55G1_REG_APPLIED_ANALOG_GAIN CCI_REG16_LE(0x00ea) #define VD55G1_REG_APPLIED_DIGITAL_GAIN CCI_REG16_LE(0x00ec) @@ -849,9 +852,16 @@ static int vd55g1_update_expo_cluster(struct vd55g1 *sensor, bool is_auto) vd55g1_write(sensor, VD55G1_REG_MANUAL_ANALOG_GAIN, sensor->again_ctrl->val, &ret); - if (!is_auto && sensor->dgain_ctrl->is_new) - vd55g1_write(sensor, VD55G1_REG_MANUAL_DIGITAL_GAIN, + if (!is_auto && sensor->dgain_ctrl->is_new) { + vd55g1_write(sensor, VD55G1_REG_MANUAL_DIGITAL_GAIN_CH0, sensor->dgain_ctrl->val, &ret); + vd55g1_write(sensor, VD55G1_REG_MANUAL_DIGITAL_GAIN_CH1, + sensor->dgain_ctrl->val, &ret); + vd55g1_write(sensor, VD55G1_REG_MANUAL_DIGITAL_GAIN_CH2, + sensor->dgain_ctrl->val, &ret); + vd55g1_write(sensor, VD55G1_REG_MANUAL_DIGITAL_GAIN_CH3, + sensor->dgain_ctrl->val, &ret); + } return ret; } From ec48e511cab4dd662cb2af4a1eefc18609fb1708 Mon Sep 17 00:00:00 2001 From: Benjamin Mugnier Date: Mon, 29 Jun 2026 12:51:49 +0200 Subject: [PATCH 6/7] media: dt-bindings: vd55g1: Add vd55g4 compatible Define it as a new monochrome variant of vd65g4. Acked-by: Krzysztof Kozlowski Signed-off-by: Benjamin Mugnier --- Documentation/devicetree/bindings/media/i2c/st,vd55g1.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/media/i2c/st,vd55g1.yaml b/Documentation/devicetree/bindings/media/i2c/st,vd55g1.yaml index 060ac6829b661d..58b1f9e85a9d1c 100644 --- a/Documentation/devicetree/bindings/media/i2c/st,vd55g1.yaml +++ b/Documentation/devicetree/bindings/media/i2c/st,vd55g1.yaml @@ -27,9 +27,10 @@ properties: compatible: enum: - st,vd55g1 + - st,vd55g4 - st,vd65g4 description: - VD55G1 is the monochrome variant, while VD65G4 is the color one. + VD55G1 and VD55G4 are monochrome variants, while VD65G4 is a color one. reg: maxItems: 1 From 74e2031122c563417dd2124e2d8c6ccb91907a33 Mon Sep 17 00:00:00 2001 From: Benjamin Mugnier Date: Mon, 29 Jun 2026 12:51:50 +0200 Subject: [PATCH 7/7] media: i2c: vd55g1: Add support for vd55g4 vd55g4 is the same device as vd65g4 but outputs in monochrome instead of RGB. Adapt the driver structure according to this new variant, and add its support. Reviewed-by: Jacopo Mondi Signed-off-by: Benjamin Mugnier --- drivers/media/i2c/vd55g1.c | 130 +++++++++++++++++++++++++------------ 1 file changed, 87 insertions(+), 43 deletions(-) diff --git a/drivers/media/i2c/vd55g1.c b/drivers/media/i2c/vd55g1.c index 37d44abd1435ce..c4142d771e7c80 100644 --- a/drivers/media/i2c/vd55g1.c +++ b/drivers/media/i2c/vd55g1.c @@ -29,11 +29,7 @@ /* Register Map */ #define VD55G1_REG_MODEL_ID CCI_REG32_LE(0x0000) -#define VD55G1_MODEL_ID_VD55G1 0x53354731 /* Mono */ -#define VD55G1_MODEL_ID_VD65G4 0x53354733 /* RGB */ -#define VD55G1_REG_REVISION CCI_REG16_LE(0x0004) -#define VD55G1_REVISION_CCB 0x2020 -#define VD55G1_REVISION_BAYER 0x3030 +#define VD55G1_REG_COLOR_VERSION CCI_REG32_LE(0x0670) #define VD55G1_REG_FWPATCH_REVISION CCI_REG16_LE(0x0012) #define VD55G1_REG_FWPATCH_START_ADDR CCI_REG8(0x2000) #define VD55G1_REG_SYSTEM_FSM CCI_REG8(0x001c) @@ -137,8 +133,39 @@ #define VD55G1_MIPI_RATE_MIN (250 * MEGA) #define VD55G1_MIPI_RATE_MAX (1200 * MEGA) -#define VD55G1_MODEL_ID_NAME(id) \ - ((id) == VD55G1_MODEL_ID_VD55G1 ? "vd55g1" : "vd65g4") +enum vd55g1_model_id { + VD55G1_MODEL_ID_2 = 0x53354731, + VD55G1_MODEL_ID_3 = 0x53354733, +}; + +enum vd55g1_color_version { + VD55G1_COLOR_VERSION_MONO, + VD55G1_COLOR_VERSION_BAYER, +}; + +struct vd55g1_version { + char *name; + enum vd55g1_model_id id; + enum vd55g1_color_version color; +}; + +static const struct vd55g1_version vd55g1_versions[] = { + { + .name = "vd55g1", + .id = VD55G1_MODEL_ID_2, + .color = VD55G1_COLOR_VERSION_MONO, + }, + { + .name = "vd55g4", + .id = VD55G1_MODEL_ID_3, + .color = VD55G1_COLOR_VERSION_MONO, + }, + { + .name = "vd65g4", + .id = VD55G1_MODEL_ID_3, + .color = VD55G1_COLOR_VERSION_BAYER, + }, +}; static const u8 vd55g1_patch_array[] = { 0x44, 0x03, 0x09, 0x02, 0xe6, 0x01, 0x42, 0x00, 0xea, 0x01, 0x42, 0x00, @@ -534,7 +561,7 @@ struct vd55g1_vblank_limits { struct vd55g1 { struct device *dev; - unsigned int id; + const struct vd55g1_version *version; struct v4l2_subdev sd; struct media_pad pad; struct regulator_bulk_data supplies[ARRAY_SIZE(vd55g1_supply_name)]; @@ -625,20 +652,28 @@ static unsigned int vd55g1_get_fmt_data_type(u32 code) static u32 vd55g1_get_fmt_code(struct vd55g1 *sensor, u32 code) { + u32 fallback_code; unsigned int i, j; - if (sensor->id == VD55G1_MODEL_ID_VD55G1) - return code; - - for (i = 0; i < ARRAY_SIZE(vd55g1_mbus_formats_bayer); i++) { - for (j = 0; j < ARRAY_SIZE(vd55g1_mbus_formats_bayer[i]); j++) { - if (vd55g1_mbus_formats_bayer[i][j] == code) - goto adapt_bayer_pattern; + if (sensor->version->color == VD55G1_COLOR_VERSION_MONO) { + fallback_code = vd55g1_mbus_formats_mono[0]; + for (i = 0; i < ARRAY_SIZE(vd55g1_mbus_formats_mono); i++) + if (vd55g1_mbus_formats_mono[i] == code) + return code; + } else { + fallback_code = vd55g1_mbus_formats_bayer[0][0]; + for (i = 0; i < ARRAY_SIZE(vd55g1_mbus_formats_bayer); i++) { + for (j = 0; + j < ARRAY_SIZE(vd55g1_mbus_formats_bayer[i]); + j++) { + if (vd55g1_mbus_formats_bayer[i][j] == code) + goto adapt_bayer_pattern; + } } } dev_warn(sensor->dev, "Unsupported mbus format: 0x%x\n", code); - return code; + return fallback_code; adapt_bayer_pattern: j = 0; @@ -1182,8 +1217,8 @@ static int vd55g1_patch(struct vd55g1 *sensor) u64 patch; int ret = 0; - /* vd55g1 needs a patch while vd65g4 does not */ - if (sensor->id == VD55G1_MODEL_ID_VD55G1) { + /* Version 2 needs a patch while version 3 does not */ + if (sensor->version->id == VD55G1_MODEL_ID_2) { vd55g1_write_array(sensor, VD55G1_REG_FWPATCH_START_ADDR, sizeof(vd55g1_patch_array), vd55g1_patch_array, &ret); @@ -1255,7 +1290,7 @@ static int vd55g1_enum_mbus_code(struct v4l2_subdev *sd, struct vd55g1 *sensor = to_vd55g1(sd); u32 base_code; - if (sensor->id == VD55G1_MODEL_ID_VD55G1) { + if (sensor->version->color != VD55G1_COLOR_VERSION_BAYER) { if (code->index >= ARRAY_SIZE(vd55g1_mbus_formats_mono)) return -EINVAL; base_code = vd55g1_mbus_formats_mono[code->index]; @@ -1371,7 +1406,7 @@ static int vd55g1_init_state(struct v4l2_subdev *sd, if (ret) return ret; - if (sensor->id == VD55G1_MODEL_ID_VD55G1) + if (sensor->version->color != VD55G1_COLOR_VERSION_BAYER) code = vd55g1_mbus_formats_mono[0]; else code = vd55g1_mbus_formats_bayer[0][0]; @@ -1658,38 +1693,46 @@ static int vd55g1_init_ctrls(struct vd55g1 *sensor) return ret; } +static const struct vd55g1_version * +vd55g1_get_version(enum vd55g1_model_id id, + enum vd55g1_color_version color) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(vd55g1_versions); i++) { + if (vd55g1_versions[i].id == id && + vd55g1_versions[i].color == color) + return &vd55g1_versions[i]; + } + + return NULL; +} + static int vd55g1_detect(struct vd55g1 *sensor) { - unsigned int dt_id = (uintptr_t)device_get_match_data(sensor->dev); - u64 rev, id; - int ret; + const struct vd55g1_version *dt_version = + device_get_match_data(sensor->dev); + const struct vd55g1_version *version; + u64 color, id; + int ret = 0; - ret = vd55g1_read(sensor, VD55G1_REG_MODEL_ID, &id, NULL); + vd55g1_read(sensor, VD55G1_REG_MODEL_ID, &id, &ret); + vd55g1_read(sensor, VD55G1_REG_COLOR_VERSION, &color, &ret); if (ret) return ret; - if (id != VD55G1_MODEL_ID_VD55G1 && id != VD55G1_MODEL_ID_VD65G4) { - dev_warn(sensor->dev, "Unsupported sensor id 0x%x\n", - (u32)id); + version = vd55g1_get_version(id, color); + if (!version) { + dev_warn(sensor->dev, "Unsupported sensor version, expected %s\n", + dt_version->name); return -ENODEV; } - if (id != dt_id) { - dev_err(sensor->dev, "Probed sensor %s and device tree definition (%s) mismatch", - VD55G1_MODEL_ID_NAME(id), VD55G1_MODEL_ID_NAME(dt_id)); + if (version->id != dt_version->id || + version->color != dt_version->color) { + dev_err(sensor->dev, "Probed sensor version %s and device tree definition %s mismatch", + version->name, dt_version->name); return -ENODEV; } - sensor->id = id; - ret = vd55g1_read(sensor, VD55G1_REG_REVISION, &rev, NULL); - if (ret) - return ret; - - if ((id == VD55G1_MODEL_ID_VD55G1 && rev != VD55G1_REVISION_CCB) && - (id == VD55G1_MODEL_ID_VD65G4 && rev != VD55G1_REVISION_BAYER)) { - dev_err(sensor->dev, "Unsupported sensor revision 0x%x for sensor %s\n", - (u16)rev, VD55G1_MODEL_ID_NAME(id)); - return -ENODEV; - } + sensor->version = version; return 0; } @@ -2047,8 +2090,9 @@ static void vd55g1_remove(struct i2c_client *client) } static const struct of_device_id vd55g1_dt_ids[] = { - { .compatible = "st,vd55g1", .data = (void *)VD55G1_MODEL_ID_VD55G1 }, - { .compatible = "st,vd65g4", .data = (void *)VD55G1_MODEL_ID_VD65G4 }, + { .compatible = "st,vd55g1", .data = (void *)&vd55g1_versions[0] }, + { .compatible = "st,vd55g4", .data = (void *)&vd55g1_versions[1] }, + { .compatible = "st,vd65g4", .data = (void *)&vd55g1_versions[2] }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, vd55g1_dt_ids);