diff mbox series

[11/13] media: i2c: imx214: Add analogue/digital gain control

Message ID 20240902-imx214-v1-11-c96cba989315@apitzsch.eu (mailing list archive)
State New
Headers show
Series media: i2c: imx214: Miscellaneous cleanups and improvements | expand

Commit Message

André Apitzsch via B4 Relay Sept. 2, 2024, 9:54 p.m. UTC
From: André Apitzsch <git@apitzsch.eu>

The imx214 sensor supports analogue gain up to 8x and digital gain up to
16x. Implement the corresponding controls in the driver. Default gain
values are not modified by this patch.

Signed-off-by: André Apitzsch <git@apitzsch.eu>

---

With the analogue gain control added by this patch, the kernel log shows
the following message when closing megapixels and a similar one when
closing qcam (from libcamera):

[  100.042856] ------------[ cut here ]------------
[  100.042886] WARNING: CPU: 4 PID: 3444 at drivers/media/common/videobuf2/videobuf2-core.c:2182 __vb2_queue_cancel+0x228/0x2c0 [videobuf2_common]
[  100.042948] Modules linked in: rfcomm zstd zstd_compress zram zsmalloc rpmsg_wwan_ctrl q6voice_dai q6asm_dai q6voice q6afe_dai q6routing q6cvs q6adm q6asm q6cvp q6mvm snd_q6dsp_common q6voice_common q6afe q6core apr pdr_interface nft_reject_inet nf_reject_ipv4 nf_reject_ipv6 nft_reject nft_ct nf_conntrack nf_defrag_ipv6 nf_defrag_ipv4 nf_tables nfnetlink algif_hash algif_skcipher af_alg bnep qcom_pd_mapper qcom_pdr_msg wcn36xx btqcomsmd btqca ipv6 wcnss_ctrl qcom_bam_dmux imx214 v4l2_cci ledtrig_pattern bmi160_i2c ak8975 leds_ktd202x bmi160_core ltr501 leds_sy7802 qcom_wcnss_pil qcom_camss qcom_q6v5_mss snd_soc_msm8916_digital snd_soc_msm8916_analog pm8xxx_vibrator v4l2_fwnode qcom_pil_info videobuf2_dma_sg qcom_common qcom_q6v5 videobuf2_memops videobuf2_v4l2 videobuf2_common i2c_qcom_cci leds_sgm3140 v4l2_flash_led_class led_class_flash v4l2_async videodev qcom_memshare mc usb_f_ncm u_ether panel_longcheer_truly_nt35695 atmel_mxt_ts smb1360 msm mdt_loader drm_exec drm_display_he
 lper gpu_sched libcomposite
[  100.043688] CPU: 4 UID: 10000 PID: 3444 Comm: megapixels Not tainted 6.11.0-rc3-msm8916 #312
[  100.043716] Hardware name: BQ Aquaris M5 (Longcheer L9100) (DT)
[  100.043730] pstate: 20000005 (nzCv daif -PAN -UAO -TCO -DIT -SSBS BTYPE=--)
[  100.043756] pc : __vb2_queue_cancel+0x228/0x2c0 [videobuf2_common]
[  100.043787] lr : __vb2_queue_cancel+0x28/0x2c0 [videobuf2_common]
[  100.043815] sp : ffff80008450ba50
[  100.043826] x29: ffff80008450ba50 x28: 0000000000000001 x27: ffff00000f946180
[  100.043867] x26: 0000000000000000 x25: ffff00000f946780 x24: ffff00000f946910
[  100.043906] x23: ffff00000175a620 x22: ffff0000036ddc90 x21: ffff0000036e4410
[  100.043946] x20: ffff0000036e44b8 x19: ffff0000036e4410 x18: ffff80008450ba98
[  100.043985] x17: ffffffffffffffff x16: 0000000000000000 x15: 0000000000000040
[  100.044023] x14: 0256932925642338 x13: ffff00000f946200 x12: 0000000000000001
[  100.044062] x11: ffff00000f946200 x10: 0000000000000a20 x9 : 0000000000000000
[  100.044100] x8 : ffff0000bf964880 x7 : 0000000000000020 x6 : 0000000000000100
[  100.044138] x5 : ffff0000036e4b68 x4 : 0000000000000000 x3 : ffff00000f946180
[  100.044176] x2 : 0000000000000000 x1 : 0000000000000000 x0 : 000000000000000f
[  100.044214] Call trace:
[  100.044226]  __vb2_queue_cancel+0x228/0x2c0 [videobuf2_common]
[  100.044257]  vb2_core_queue_release+0x20/0x74 [videobuf2_common]
[  100.044285]  _vb2_fop_release+0x68/0xb0 [videobuf2_v4l2]
[  100.044314]  vb2_fop_release+0x28/0x50 [videobuf2_v4l2]
[  100.044341]  video_release+0x20/0x40 [qcom_camss]
[  100.044406]  v4l2_release+0xb4/0xf4 [videodev]
[  100.044481]  __fput+0xbc/0x274
[  100.044510]  ____fput+0xc/0x14
[  100.044533]  task_work_run+0x78/0xc0
[  100.044563]  do_exit+0x2dc/0x8b0
[  100.044590]  do_group_exit+0x30/0x8c
[  100.044615]  get_signal+0x7b4/0x8a0
[  100.044643]  do_signal+0x94/0xd14
[  100.044672]  do_notify_resume+0xd0/0x120
[  100.044697]  el0_svc+0x44/0x60
[  100.044730]  el0t_64_sync_handler+0x118/0x124
[  100.044753]  el0t_64_sync+0x14c/0x150
[  100.044775] ---[ end trace 0000000000000000 ]---
[  100.044793] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 0 in active state
[  100.045722] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 1 in active state
[  100.046587] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 2 in active state
[  100.047439] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 3 in active state
[  100.048288] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 4 in active state
[  100.049242] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 5 in active state
[  100.050098] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 6 in active state
[  100.050945] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 7 in active state
[  100.051793] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 8 in active state
[  100.052692] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 14 in active state
[  100.053548] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 15 in active state
[  100.054396] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 16 in active state
[  100.055244] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 17 in active state
[  100.056137] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 18 in active state
[  100.056988] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 19 in active state

From the log it looks like the cause is in some other module and not in
the imx214 driver, that's why the patch wasn't dropped from this series.
---
 drivers/media/i2c/imx214.c | 53 +++++++++++++++++++++++++++++++++-------------
 1 file changed, 38 insertions(+), 15 deletions(-)

Comments

Ricardo Ribalda Delgado Sept. 12, 2024, 1:39 p.m. UTC | #1
On Mon, Sep 2, 2024 at 11:53 PM André Apitzsch via B4 Relay
<devnull+git.apitzsch.eu@kernel.org> wrote:
>
> From: André Apitzsch <git@apitzsch.eu>
>
> The imx214 sensor supports analogue gain up to 8x and digital gain up to
> 16x. Implement the corresponding controls in the driver. Default gain
> values are not modified by this patch.
>
Acked-by: Ricardo Ribalda <ribalda@chromium.org>

> Signed-off-by: André Apitzsch <git@apitzsch.eu>
>
> ---
>
> With the analogue gain control added by this patch, the kernel log shows
> the following message when closing megapixels and a similar one when
> closing qcam (from libcamera):
>
> [  100.042856] ------------[ cut here ]------------
> [  100.042886] WARNING: CPU: 4 PID: 3444 at drivers/media/common/videobuf2/videobuf2-core.c:2182 __vb2_queue_cancel+0x228/0x2c0 [videobuf2_common]
> [  100.042948] Modules linked in: rfcomm zstd zstd_compress zram zsmalloc rpmsg_wwan_ctrl q6voice_dai q6asm_dai q6voice q6afe_dai q6routing q6cvs q6adm q6asm q6cvp q6mvm snd_q6dsp_common q6voice_common q6afe q6core apr pdr_interface nft_reject_inet nf_reject_ipv4 nf_reject_ipv6 nft_reject nft_ct nf_conntrack nf_defrag_ipv6 nf_defrag_ipv4 nf_tables nfnetlink algif_hash algif_skcipher af_alg bnep qcom_pd_mapper qcom_pdr_msg wcn36xx btqcomsmd btqca ipv6 wcnss_ctrl qcom_bam_dmux imx214 v4l2_cci ledtrig_pattern bmi160_i2c ak8975 leds_ktd202x bmi160_core ltr501 leds_sy7802 qcom_wcnss_pil qcom_camss qcom_q6v5_mss snd_soc_msm8916_digital snd_soc_msm8916_analog pm8xxx_vibrator v4l2_fwnode qcom_pil_info videobuf2_dma_sg qcom_common qcom_q6v5 videobuf2_memops videobuf2_v4l2 videobuf2_common i2c_qcom_cci leds_sgm3140 v4l2_flash_led_class led_class_flash v4l2_async videodev qcom_memshare mc usb_f_ncm u_ether panel_longcheer_truly_nt35695 atmel_mxt_ts smb1360 msm mdt_loader drm_exec drm_display_he
>  lper gpu_sched libcomposite
> [  100.043688] CPU: 4 UID: 10000 PID: 3444 Comm: megapixels Not tainted 6.11.0-rc3-msm8916 #312
> [  100.043716] Hardware name: BQ Aquaris M5 (Longcheer L9100) (DT)
> [  100.043730] pstate: 20000005 (nzCv daif -PAN -UAO -TCO -DIT -SSBS BTYPE=--)
> [  100.043756] pc : __vb2_queue_cancel+0x228/0x2c0 [videobuf2_common]
> [  100.043787] lr : __vb2_queue_cancel+0x28/0x2c0 [videobuf2_common]
> [  100.043815] sp : ffff80008450ba50
> [  100.043826] x29: ffff80008450ba50 x28: 0000000000000001 x27: ffff00000f946180
> [  100.043867] x26: 0000000000000000 x25: ffff00000f946780 x24: ffff00000f946910
> [  100.043906] x23: ffff00000175a620 x22: ffff0000036ddc90 x21: ffff0000036e4410
> [  100.043946] x20: ffff0000036e44b8 x19: ffff0000036e4410 x18: ffff80008450ba98
> [  100.043985] x17: ffffffffffffffff x16: 0000000000000000 x15: 0000000000000040
> [  100.044023] x14: 0256932925642338 x13: ffff00000f946200 x12: 0000000000000001
> [  100.044062] x11: ffff00000f946200 x10: 0000000000000a20 x9 : 0000000000000000
> [  100.044100] x8 : ffff0000bf964880 x7 : 0000000000000020 x6 : 0000000000000100
> [  100.044138] x5 : ffff0000036e4b68 x4 : 0000000000000000 x3 : ffff00000f946180
> [  100.044176] x2 : 0000000000000000 x1 : 0000000000000000 x0 : 000000000000000f
> [  100.044214] Call trace:
> [  100.044226]  __vb2_queue_cancel+0x228/0x2c0 [videobuf2_common]
> [  100.044257]  vb2_core_queue_release+0x20/0x74 [videobuf2_common]
> [  100.044285]  _vb2_fop_release+0x68/0xb0 [videobuf2_v4l2]
> [  100.044314]  vb2_fop_release+0x28/0x50 [videobuf2_v4l2]
> [  100.044341]  video_release+0x20/0x40 [qcom_camss]
> [  100.044406]  v4l2_release+0xb4/0xf4 [videodev]
> [  100.044481]  __fput+0xbc/0x274
> [  100.044510]  ____fput+0xc/0x14
> [  100.044533]  task_work_run+0x78/0xc0
> [  100.044563]  do_exit+0x2dc/0x8b0
> [  100.044590]  do_group_exit+0x30/0x8c
> [  100.044615]  get_signal+0x7b4/0x8a0
> [  100.044643]  do_signal+0x94/0xd14
> [  100.044672]  do_notify_resume+0xd0/0x120
> [  100.044697]  el0_svc+0x44/0x60
> [  100.044730]  el0t_64_sync_handler+0x118/0x124
> [  100.044753]  el0t_64_sync+0x14c/0x150
> [  100.044775] ---[ end trace 0000000000000000 ]---
> [  100.044793] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 0 in active state
> [  100.045722] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 1 in active state
> [  100.046587] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 2 in active state
> [  100.047439] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 3 in active state
> [  100.048288] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 4 in active state
> [  100.049242] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 5 in active state
> [  100.050098] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 6 in active state
> [  100.050945] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 7 in active state
> [  100.051793] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 8 in active state
> [  100.052692] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 14 in active state
> [  100.053548] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 15 in active state
> [  100.054396] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 16 in active state
> [  100.055244] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 17 in active state
> [  100.056137] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 18 in active state
> [  100.056988] videobuf2_common: driver bug: stop_streaming operation is leaving buffer 19 in active state
>
> From the log it looks like the cause is in some other module and not in
> the imx214 driver, that's why the patch wasn't dropped from this series.
> ---
>  drivers/media/i2c/imx214.c | 53 +++++++++++++++++++++++++++++++++-------------
>  1 file changed, 38 insertions(+), 15 deletions(-)
>
> diff --git a/drivers/media/i2c/imx214.c b/drivers/media/i2c/imx214.c
> index 4a1433728cd5..ce6d8a90f4a1 100644
> --- a/drivers/media/i2c/imx214.c
> +++ b/drivers/media/i2c/imx214.c
> @@ -53,12 +53,20 @@
>  /* Analog gain control */
>  #define IMX214_REG_ANALOG_GAIN         CCI_REG16(0x0204)
>  #define IMX214_REG_SHORT_ANALOG_GAIN   CCI_REG16(0x0216)
> +#define IMX214_ANA_GAIN_MIN            0
> +#define IMX214_ANA_GAIN_MAX            448
> +#define IMX214_ANA_GAIN_STEP           1
> +#define IMX214_ANA_GAIN_DEFAULT                0x0
>
>  /* Digital gain control */
>  #define IMX214_REG_DIG_GAIN_GREENR     CCI_REG16(0x020e)
>  #define IMX214_REG_DIG_GAIN_RED                CCI_REG16(0x0210)
>  #define IMX214_REG_DIG_GAIN_BLUE       CCI_REG16(0x0212)
>  #define IMX214_REG_DIG_GAIN_GREENB     CCI_REG16(0x0214)
> +#define IMX214_DGTL_GAIN_MIN           0x0100
> +#define IMX214_DGTL_GAIN_MAX           0x0fff
> +#define IMX214_DGTL_GAIN_DEFAULT       0x0100
> +#define IMX214_DGTL_GAIN_STEP          1
>
>  #define IMX214_REG_ORIENTATION         CCI_REG8(0x0101)
>
> @@ -271,13 +279,6 @@ static const struct cci_reg_sequence mode_4096x2304[] = {
>
>         { IMX214_REG_SHORT_EXPOSURE, 500 },
>
> -       { IMX214_REG_ANALOG_GAIN, 0 },
> -       { IMX214_REG_DIG_GAIN_GREENR, 256 },
> -       { IMX214_REG_DIG_GAIN_RED, 256 },
> -       { IMX214_REG_DIG_GAIN_BLUE, 256 },
> -       { IMX214_REG_DIG_GAIN_GREENB, 256 },
> -       { IMX214_REG_SHORT_ANALOG_GAIN, 0 },
> -
>         { CCI_REG8(0x4170), 0x00 },
>         { CCI_REG8(0x4171), 0x10 },
>         { CCI_REG8(0x4176), 0x00 },
> @@ -327,13 +328,6 @@ static const struct cci_reg_sequence mode_1920x1080[] = {
>
>         { IMX214_REG_SHORT_EXPOSURE, 500 },
>
> -       { IMX214_REG_ANALOG_GAIN, 0 },
> -       { IMX214_REG_DIG_GAIN_GREENR, 256 },
> -       { IMX214_REG_DIG_GAIN_RED, 256 },
> -       { IMX214_REG_DIG_GAIN_BLUE, 256 },
> -       { IMX214_REG_DIG_GAIN_GREENB, 256 },
> -       { IMX214_REG_SHORT_ANALOG_GAIN, 0 },
> -
>         { CCI_REG8(0x4170), 0x00 },
>         { CCI_REG8(0x4171), 0x10 },
>         { CCI_REG8(0x4176), 0x00 },
> @@ -757,6 +751,18 @@ static int imx214_entity_init_state(struct v4l2_subdev *subdev,
>         return 0;
>  }
>
> +static int imx214_update_digital_gain(struct imx214 *imx214, u32 val)
> +{
> +       int ret = 0;
> +
> +       cci_write(imx214->regmap, IMX214_REG_DIG_GAIN_GREENR, val, &ret);
> +       cci_write(imx214->regmap, IMX214_REG_DIG_GAIN_RED, val, &ret);
> +       cci_write(imx214->regmap, IMX214_REG_DIG_GAIN_BLUE, val, &ret);
> +       cci_write(imx214->regmap, IMX214_REG_DIG_GAIN_GREENB, val, &ret);
> +
> +       return ret;
> +}
> +
>  static int imx214_set_ctrl(struct v4l2_ctrl *ctrl)
>  {
>         struct imx214 *imx214 = container_of(ctrl->handler,
> @@ -788,6 +794,15 @@ static int imx214_set_ctrl(struct v4l2_ctrl *ctrl)
>                 return 0;
>
>         switch (ctrl->id) {
> +       case V4L2_CID_ANALOGUE_GAIN:
> +               cci_write(imx214->regmap, IMX214_REG_ANALOG_GAIN,
> +                         ctrl->val, &ret);
> +               cci_write(imx214->regmap, IMX214_REG_SHORT_ANALOG_GAIN,
> +                         ctrl->val, &ret);
> +               break;
> +       case V4L2_CID_DIGITAL_GAIN:
> +               ret = imx214_update_digital_gain(imx214, ctrl->val);
> +               break;
>         case V4L2_CID_EXPOSURE:
>                 cci_write(imx214->regmap, IMX214_REG_EXPOSURE, ctrl->val, &ret);
>                 break;
> @@ -834,7 +849,7 @@ static int imx214_ctrls_init(struct imx214 *imx214)
>                 return ret;
>
>         ctrl_hdlr = &imx214->ctrls;
> -       ret = v4l2_ctrl_handler_init(&imx214->ctrls, 10);
> +       ret = v4l2_ctrl_handler_init(&imx214->ctrls, 12);
>         if (ret)
>                 return ret;
>
> @@ -871,6 +886,14 @@ static int imx214_ctrls_init(struct imx214 *imx214)
>                                              IMX214_EXPOSURE_STEP,
>                                              exposure_def);
>
> +       v4l2_ctrl_new_std(ctrl_hdlr, &imx214_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
> +                         IMX214_ANA_GAIN_MIN, IMX214_ANA_GAIN_MAX,
> +                         IMX214_ANA_GAIN_STEP, IMX214_ANA_GAIN_DEFAULT);
> +
> +       v4l2_ctrl_new_std(ctrl_hdlr, &imx214_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
> +                         IMX214_DGTL_GAIN_MIN, IMX214_DGTL_GAIN_MAX,
> +                         IMX214_DGTL_GAIN_STEP, IMX214_DGTL_GAIN_DEFAULT);
> +
>         imx214->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx214_ctrl_ops,
>                                           V4L2_CID_HFLIP, 0, 1, 1, 0);
>         if (imx214->hflip)
>
> --
> 2.46.0
>
>
diff mbox series

Patch

diff --git a/drivers/media/i2c/imx214.c b/drivers/media/i2c/imx214.c
index 4a1433728cd5..ce6d8a90f4a1 100644
--- a/drivers/media/i2c/imx214.c
+++ b/drivers/media/i2c/imx214.c
@@ -53,12 +53,20 @@ 
 /* Analog gain control */
 #define IMX214_REG_ANALOG_GAIN		CCI_REG16(0x0204)
 #define IMX214_REG_SHORT_ANALOG_GAIN	CCI_REG16(0x0216)
+#define IMX214_ANA_GAIN_MIN		0
+#define IMX214_ANA_GAIN_MAX		448
+#define IMX214_ANA_GAIN_STEP		1
+#define IMX214_ANA_GAIN_DEFAULT		0x0
 
 /* Digital gain control */
 #define IMX214_REG_DIG_GAIN_GREENR	CCI_REG16(0x020e)
 #define IMX214_REG_DIG_GAIN_RED		CCI_REG16(0x0210)
 #define IMX214_REG_DIG_GAIN_BLUE	CCI_REG16(0x0212)
 #define IMX214_REG_DIG_GAIN_GREENB	CCI_REG16(0x0214)
+#define IMX214_DGTL_GAIN_MIN		0x0100
+#define IMX214_DGTL_GAIN_MAX		0x0fff
+#define IMX214_DGTL_GAIN_DEFAULT	0x0100
+#define IMX214_DGTL_GAIN_STEP		1
 
 #define IMX214_REG_ORIENTATION		CCI_REG8(0x0101)
 
@@ -271,13 +279,6 @@  static const struct cci_reg_sequence mode_4096x2304[] = {
 
 	{ IMX214_REG_SHORT_EXPOSURE, 500 },
 
-	{ IMX214_REG_ANALOG_GAIN, 0 },
-	{ IMX214_REG_DIG_GAIN_GREENR, 256 },
-	{ IMX214_REG_DIG_GAIN_RED, 256 },
-	{ IMX214_REG_DIG_GAIN_BLUE, 256 },
-	{ IMX214_REG_DIG_GAIN_GREENB, 256 },
-	{ IMX214_REG_SHORT_ANALOG_GAIN, 0 },
-
 	{ CCI_REG8(0x4170), 0x00 },
 	{ CCI_REG8(0x4171), 0x10 },
 	{ CCI_REG8(0x4176), 0x00 },
@@ -327,13 +328,6 @@  static const struct cci_reg_sequence mode_1920x1080[] = {
 
 	{ IMX214_REG_SHORT_EXPOSURE, 500 },
 
-	{ IMX214_REG_ANALOG_GAIN, 0 },
-	{ IMX214_REG_DIG_GAIN_GREENR, 256 },
-	{ IMX214_REG_DIG_GAIN_RED, 256 },
-	{ IMX214_REG_DIG_GAIN_BLUE, 256 },
-	{ IMX214_REG_DIG_GAIN_GREENB, 256 },
-	{ IMX214_REG_SHORT_ANALOG_GAIN, 0 },
-
 	{ CCI_REG8(0x4170), 0x00 },
 	{ CCI_REG8(0x4171), 0x10 },
 	{ CCI_REG8(0x4176), 0x00 },
@@ -757,6 +751,18 @@  static int imx214_entity_init_state(struct v4l2_subdev *subdev,
 	return 0;
 }
 
+static int imx214_update_digital_gain(struct imx214 *imx214, u32 val)
+{
+	int ret = 0;
+
+	cci_write(imx214->regmap, IMX214_REG_DIG_GAIN_GREENR, val, &ret);
+	cci_write(imx214->regmap, IMX214_REG_DIG_GAIN_RED, val, &ret);
+	cci_write(imx214->regmap, IMX214_REG_DIG_GAIN_BLUE, val, &ret);
+	cci_write(imx214->regmap, IMX214_REG_DIG_GAIN_GREENB, val, &ret);
+
+	return ret;
+}
+
 static int imx214_set_ctrl(struct v4l2_ctrl *ctrl)
 {
 	struct imx214 *imx214 = container_of(ctrl->handler,
@@ -788,6 +794,15 @@  static int imx214_set_ctrl(struct v4l2_ctrl *ctrl)
 		return 0;
 
 	switch (ctrl->id) {
+	case V4L2_CID_ANALOGUE_GAIN:
+		cci_write(imx214->regmap, IMX214_REG_ANALOG_GAIN,
+			  ctrl->val, &ret);
+		cci_write(imx214->regmap, IMX214_REG_SHORT_ANALOG_GAIN,
+			  ctrl->val, &ret);
+		break;
+	case V4L2_CID_DIGITAL_GAIN:
+		ret = imx214_update_digital_gain(imx214, ctrl->val);
+		break;
 	case V4L2_CID_EXPOSURE:
 		cci_write(imx214->regmap, IMX214_REG_EXPOSURE, ctrl->val, &ret);
 		break;
@@ -834,7 +849,7 @@  static int imx214_ctrls_init(struct imx214 *imx214)
 		return ret;
 
 	ctrl_hdlr = &imx214->ctrls;
-	ret = v4l2_ctrl_handler_init(&imx214->ctrls, 10);
+	ret = v4l2_ctrl_handler_init(&imx214->ctrls, 12);
 	if (ret)
 		return ret;
 
@@ -871,6 +886,14 @@  static int imx214_ctrls_init(struct imx214 *imx214)
 					     IMX214_EXPOSURE_STEP,
 					     exposure_def);
 
+	v4l2_ctrl_new_std(ctrl_hdlr, &imx214_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
+			  IMX214_ANA_GAIN_MIN, IMX214_ANA_GAIN_MAX,
+			  IMX214_ANA_GAIN_STEP, IMX214_ANA_GAIN_DEFAULT);
+
+	v4l2_ctrl_new_std(ctrl_hdlr, &imx214_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
+			  IMX214_DGTL_GAIN_MIN, IMX214_DGTL_GAIN_MAX,
+			  IMX214_DGTL_GAIN_STEP, IMX214_DGTL_GAIN_DEFAULT);
+
 	imx214->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx214_ctrl_ops,
 					  V4L2_CID_HFLIP, 0, 1, 1, 0);
 	if (imx214->hflip)