[PATCH v5 13/16] media: ov2740: add manual white balance controls
From: Maurizio Casciano
Date: Mon Aug 31 2026 - 17:26:34 EST
The sensor has separate red, green and blue manual white-balance gain
registers, but the driver currently writes the same digital-gain value
to all three channels. This prevents userspace from correcting the
strong color cast of raw Bayer capture.
Expose red- and blue-balance controls relative to the digital gain and
update all three channels under group hold.
Tested on the Yoga Book OV2740 with live gain changes and continuous raw
capture.
Signed-off-by: Maurizio Casciano <mauriziocasciano7@xxxxxxxxx>
Assisted-by: LLM sparse
---
drivers/media/i2c/ov2740.c | 44 +++++++++++++++++++++++++++++++-------
1 file changed, 36 insertions(+), 8 deletions(-)
diff --git a/drivers/media/i2c/ov2740.c b/drivers/media/i2c/ov2740.c
index 5f381b7972bf..4600bbc90293 100644
--- a/drivers/media/i2c/ov2740.c
+++ b/drivers/media/i2c/ov2740.c
@@ -565,6 +565,9 @@ struct ov2740 {
struct v4l2_ctrl *vblank;
struct v4l2_ctrl *hblank;
struct v4l2_ctrl *exposure;
+ struct v4l2_ctrl *digital_gain;
+ struct v4l2_ctrl *red_balance;
+ struct v4l2_ctrl *blue_balance;
/* GPIOs, clocks, regulators */
struct gpio_desc *reset_gpio;
@@ -694,8 +697,10 @@ static int ov2740_identify_module(struct ov2740 *ov2740)
return 0;
}
-static int ov2740_update_digital_gain(struct ov2740 *ov2740, u32 d_gain)
+static int ov2740_update_mwb_gains(struct ov2740 *ov2740)
{
+ u32 d_gain = ov2740->digital_gain->val;
+ u32 blue_gain, red_gain;
int end_ret, launch_ret, ret;
ret = ov2740_write_reg(ov2740, OV2740_REG_GROUP_ACCESS, 1,
@@ -703,7 +708,11 @@ static int ov2740_update_digital_gain(struct ov2740 *ov2740, u32 d_gain)
if (ret)
return ret;
- ret = ov2740_write_reg(ov2740, OV2740_REG_MWB_R_GAIN, 2, d_gain);
+ /* Balance controls use 1024 as unity relative to the digital gain. */
+ red_gain = DIV_ROUND_CLOSEST(d_gain * ov2740->red_balance->val,
+ OV2740_DGTL_GAIN_DEFAULT);
+ red_gain = min(red_gain, OV2740_DGTL_GAIN_MAX);
+ ret = ov2740_write_reg(ov2740, OV2740_REG_MWB_R_GAIN, 2, red_gain);
if (ret)
goto release_group;
@@ -711,7 +720,10 @@ static int ov2740_update_digital_gain(struct ov2740 *ov2740, u32 d_gain)
if (ret)
goto release_group;
- ret = ov2740_write_reg(ov2740, OV2740_REG_MWB_B_GAIN, 2, d_gain);
+ blue_gain = DIV_ROUND_CLOSEST(d_gain * ov2740->blue_balance->val,
+ OV2740_DGTL_GAIN_DEFAULT);
+ blue_gain = min(blue_gain, OV2740_DGTL_GAIN_MAX);
+ ret = ov2740_write_reg(ov2740, OV2740_REG_MWB_B_GAIN, 2, blue_gain);
release_group:
end_ret = ov2740_write_reg(ov2740, OV2740_REG_GROUP_ACCESS, 1,
@@ -760,7 +772,9 @@ static int ov2740_set_ctrl(struct v4l2_ctrl *ctrl)
break;
case V4L2_CID_DIGITAL_GAIN:
- ret = ov2740_update_digital_gain(ov2740, ctrl->val);
+ case V4L2_CID_RED_BALANCE:
+ case V4L2_CID_BLUE_BALANCE:
+ ret = ov2740_update_mwb_gains(ov2740);
break;
case V4L2_CID_EXPOSURE:
@@ -801,7 +815,7 @@ static int ov2740_init_controls(struct ov2740 *ov2740)
int ret;
ctrl_hdlr = &ov2740->ctrl_handler;
- ret = v4l2_ctrl_handler_init(ctrl_hdlr, 10);
+ ret = v4l2_ctrl_handler_init(ctrl_hdlr, 12);
if (ret)
return ret;
@@ -836,9 +850,23 @@ static int ov2740_init_controls(struct ov2740 *ov2740)
v4l2_ctrl_new_std(ctrl_hdlr, &ov2740_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
OV2740_ANAL_GAIN_MIN, OV2740_ANAL_GAIN_MAX,
OV2740_ANAL_GAIN_STEP, OV2740_ANAL_GAIN_MIN);
- v4l2_ctrl_new_std(ctrl_hdlr, &ov2740_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
- OV2740_DGTL_GAIN_MIN, OV2740_DGTL_GAIN_MAX,
- OV2740_DGTL_GAIN_STEP, OV2740_DGTL_GAIN_DEFAULT);
+ ov2740->digital_gain =
+ v4l2_ctrl_new_std(ctrl_hdlr, &ov2740_ctrl_ops,
+ V4L2_CID_DIGITAL_GAIN,
+ OV2740_DGTL_GAIN_MIN, OV2740_DGTL_GAIN_MAX,
+ OV2740_DGTL_GAIN_STEP,
+ OV2740_DGTL_GAIN_DEFAULT);
+ ov2740->red_balance =
+ v4l2_ctrl_new_std(ctrl_hdlr, &ov2740_ctrl_ops,
+ V4L2_CID_RED_BALANCE,
+ 1, OV2740_DGTL_GAIN_MAX, 1,
+ OV2740_DGTL_GAIN_DEFAULT);
+ ov2740->blue_balance =
+ v4l2_ctrl_new_std(ctrl_hdlr, &ov2740_ctrl_ops,
+ V4L2_CID_BLUE_BALANCE,
+ 1, OV2740_DGTL_GAIN_MAX, 1,
+ OV2740_DGTL_GAIN_DEFAULT);
+ v4l2_ctrl_cluster(3, &ov2740->digital_gain);
exposure_max = ov2740->cur_mode->vts_def - OV2740_EXPOSURE_MAX_MARGIN;
ov2740->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov2740_ctrl_ops,
V4L2_CID_EXPOSURE,
--
With Best Regards,
Maurizio Casciano