diff mbox series

[v3,15/15] media: i2c: Add vblank control to ov7251 driver

Message ID 20220504223027.3480287-16-djrscally@gmail.com (mailing list archive)
State New, archived
Headers show
Series Support OVTI7251 on Microsoft Surface line | expand

Commit Message

Daniel Scally May 4, 2022, 10:30 p.m. UTC
Add a vblank control to the ov7251 driver.

Signed-off-by: Daniel Scally <djrscally@gmail.com>
---
Changes in v3:

	- New patch

 drivers/media/i2c/ov7251.c | 53 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 53 insertions(+)

Comments

kernel test robot May 5, 2022, 1:02 a.m. UTC | #1
Hi Daniel,

I love your patch! Perhaps something to improve:

[auto build test WARNING on media-tree/master]
[also build test WARNING on v5.18-rc5 next-20220504]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/intel-lab-lkp/linux/commits/Daniel-Scally/Support-OVTI7251-on-Microsoft-Surface-line/20220505-063608
base:   git://linuxtv.org/media_tree.git master
config: m68k-allyesconfig (https://download.01.org/0day-ci/archive/20220505/202205050844.k1CPWqtV-lkp@intel.com/config)
compiler: m68k-linux-gcc (GCC) 11.3.0
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/intel-lab-lkp/linux/commit/132a5a799bbe214b679bc8e242193c5c1ff1d967
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review Daniel-Scally/Support-OVTI7251-on-Microsoft-Surface-line/20220505-063608
        git checkout 132a5a799bbe214b679bc8e242193c5c1ff1d967
        # save the config file
        mkdir build_dir && cp config build_dir/.config
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-11.3.0 make.cross W=1 O=build_dir ARCH=m68k SHELL=/bin/bash drivers/media/i2c/

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All warnings (new ones prefixed by >>):

   drivers/media/i2c/ov7251.c: In function 'ov7251_set_format':
>> drivers/media/i2c/ov7251.c:1218:25: warning: variable 'vblank_def' set but not used [-Wunused-but-set-variable]
    1218 |         int vblank_max, vblank_def;
         |                         ^~~~~~~~~~
   At top level:
   drivers/media/i2c/ov7251.c:193:37: warning: 'ov7251_pll1_cfg_24_mhz_319_2_mhz' defined but not used [-Wunused-const-variable=]
     193 | static const struct ov7251_pll1_cfg ov7251_pll1_cfg_24_mhz_319_2_mhz = {
         |                                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


vim +/vblank_def +1218 drivers/media/i2c/ov7251.c

  1211	
  1212	static int ov7251_set_format(struct v4l2_subdev *sd,
  1213				     struct v4l2_subdev_state *sd_state,
  1214				     struct v4l2_subdev_format *format)
  1215	{
  1216		struct ov7251 *ov7251 = to_ov7251(sd);
  1217		struct v4l2_mbus_framefmt *__format;
> 1218		int vblank_max, vblank_def;
  1219		struct v4l2_rect *__crop;
  1220		const struct ov7251_mode_info *new_mode;
  1221		int ret = 0;
  1222	
  1223		mutex_lock(&ov7251->lock);
  1224	
  1225		__crop = __ov7251_get_pad_crop(ov7251, sd_state, format->pad,
  1226					       format->which);
  1227	
  1228		new_mode = v4l2_find_nearest_size(ov7251_mode_info_data,
  1229					ARRAY_SIZE(ov7251_mode_info_data),
  1230					width, height,
  1231					format->format.width, format->format.height);
  1232	
  1233		__crop->width = new_mode->width;
  1234		__crop->height = new_mode->height;
  1235	
  1236		if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
  1237			ret = __v4l2_ctrl_modify_range(ov7251->exposure,
  1238						       1, new_mode->exposure_max,
  1239						       1, new_mode->exposure_def);
  1240			if (ret < 0)
  1241				goto exit;
  1242	
  1243			ret = __v4l2_ctrl_s_ctrl(ov7251->exposure,
  1244						 new_mode->exposure_def);
  1245			if (ret < 0)
  1246				goto exit;
  1247	
  1248			ret = __v4l2_ctrl_s_ctrl(ov7251->gain, 16);
  1249			if (ret < 0)
  1250				goto exit;
  1251	
  1252			vblank_max = OV7251_TIMING_MAX_VTS - new_mode->height;
  1253			vblank_def = new_mode->vts - new_mode->height;
  1254			ret = __v4l2_ctrl_modify_range(ov7251->vblank,
  1255						       OV7251_TIMING_MIN_VTS,
  1256						       vblank_max, 1, vblank_max);
  1257			if (ret < 0)
  1258				goto exit;
  1259	
  1260			ov7251->current_mode = new_mode;
  1261		}
  1262	
  1263		__format = __ov7251_get_pad_format(ov7251, sd_state, format->pad,
  1264						   format->which);
  1265		__format->width = __crop->width;
  1266		__format->height = __crop->height;
  1267		__format->code = MEDIA_BUS_FMT_Y10_1X10;
  1268		__format->field = V4L2_FIELD_NONE;
  1269		__format->colorspace = V4L2_COLORSPACE_SRGB;
  1270		__format->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(__format->colorspace);
  1271		__format->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true,
  1272					__format->colorspace, __format->ycbcr_enc);
  1273		__format->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(__format->colorspace);
  1274	
  1275		format->format = *__format;
  1276	
  1277	exit:
  1278		mutex_unlock(&ov7251->lock);
  1279	
  1280		return ret;
  1281	}
  1282
Daniel Scally May 5, 2022, 8:04 a.m. UTC | #2
Argh - I screwed up a rebase here, not sure how I missed that. Sorry
all, let me resend

On 05/05/2022 02:02, kernel test robot wrote:
> Hi Daniel,
>
> I love your patch! Perhaps something to improve:
>
> [auto build test WARNING on media-tree/master]
> [also build test WARNING on v5.18-rc5 next-20220504]
> [If your patch is applied to the wrong git tree, kindly drop us a note.
> And when submitting patch, we suggest to use '--base' as documented in
> https://git-scm.com/docs/git-format-patch]
>
> url:    https://github.com/intel-lab-lkp/linux/commits/Daniel-Scally/Support-OVTI7251-on-Microsoft-Surface-line/20220505-063608
> base:   git://linuxtv.org/media_tree.git master
> config: m68k-allyesconfig (https://download.01.org/0day-ci/archive/20220505/202205050844.k1CPWqtV-lkp@intel.com/config)
> compiler: m68k-linux-gcc (GCC) 11.3.0
> reproduce (this is a W=1 build):
>         wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
>         chmod +x ~/bin/make.cross
>         # https://github.com/intel-lab-lkp/linux/commit/132a5a799bbe214b679bc8e242193c5c1ff1d967
>         git remote add linux-review https://github.com/intel-lab-lkp/linux
>         git fetch --no-tags linux-review Daniel-Scally/Support-OVTI7251-on-Microsoft-Surface-line/20220505-063608
>         git checkout 132a5a799bbe214b679bc8e242193c5c1ff1d967
>         # save the config file
>         mkdir build_dir && cp config build_dir/.config
>         COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-11.3.0 make.cross W=1 O=build_dir ARCH=m68k SHELL=/bin/bash drivers/media/i2c/
>
> If you fix the issue, kindly add following tag as appropriate
> Reported-by: kernel test robot <lkp@intel.com>
>
> All warnings (new ones prefixed by >>):
>
>    drivers/media/i2c/ov7251.c: In function 'ov7251_set_format':
>>> drivers/media/i2c/ov7251.c:1218:25: warning: variable 'vblank_def' set but not used [-Wunused-but-set-variable]
>     1218 |         int vblank_max, vblank_def;
>          |                         ^~~~~~~~~~
>    At top level:
>    drivers/media/i2c/ov7251.c:193:37: warning: 'ov7251_pll1_cfg_24_mhz_319_2_mhz' defined but not used [-Wunused-const-variable=]
>      193 | static const struct ov7251_pll1_cfg ov7251_pll1_cfg_24_mhz_319_2_mhz = {
>          |                                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>
>
> vim +/vblank_def +1218 drivers/media/i2c/ov7251.c
>
>   1211	
>   1212	static int ov7251_set_format(struct v4l2_subdev *sd,
>   1213				     struct v4l2_subdev_state *sd_state,
>   1214				     struct v4l2_subdev_format *format)
>   1215	{
>   1216		struct ov7251 *ov7251 = to_ov7251(sd);
>   1217		struct v4l2_mbus_framefmt *__format;
>> 1218		int vblank_max, vblank_def;
>   1219		struct v4l2_rect *__crop;
>   1220		const struct ov7251_mode_info *new_mode;
>   1221		int ret = 0;
>   1222	
>   1223		mutex_lock(&ov7251->lock);
>   1224	
>   1225		__crop = __ov7251_get_pad_crop(ov7251, sd_state, format->pad,
>   1226					       format->which);
>   1227	
>   1228		new_mode = v4l2_find_nearest_size(ov7251_mode_info_data,
>   1229					ARRAY_SIZE(ov7251_mode_info_data),
>   1230					width, height,
>   1231					format->format.width, format->format.height);
>   1232	
>   1233		__crop->width = new_mode->width;
>   1234		__crop->height = new_mode->height;
>   1235	
>   1236		if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
>   1237			ret = __v4l2_ctrl_modify_range(ov7251->exposure,
>   1238						       1, new_mode->exposure_max,
>   1239						       1, new_mode->exposure_def);
>   1240			if (ret < 0)
>   1241				goto exit;
>   1242	
>   1243			ret = __v4l2_ctrl_s_ctrl(ov7251->exposure,
>   1244						 new_mode->exposure_def);
>   1245			if (ret < 0)
>   1246				goto exit;
>   1247	
>   1248			ret = __v4l2_ctrl_s_ctrl(ov7251->gain, 16);
>   1249			if (ret < 0)
>   1250				goto exit;
>   1251	
>   1252			vblank_max = OV7251_TIMING_MAX_VTS - new_mode->height;
>   1253			vblank_def = new_mode->vts - new_mode->height;
>   1254			ret = __v4l2_ctrl_modify_range(ov7251->vblank,
>   1255						       OV7251_TIMING_MIN_VTS,
>   1256						       vblank_max, 1, vblank_max);
>   1257			if (ret < 0)
>   1258				goto exit;
>   1259	
>   1260			ov7251->current_mode = new_mode;
>   1261		}
>   1262	
>   1263		__format = __ov7251_get_pad_format(ov7251, sd_state, format->pad,
>   1264						   format->which);
>   1265		__format->width = __crop->width;
>   1266		__format->height = __crop->height;
>   1267		__format->code = MEDIA_BUS_FMT_Y10_1X10;
>   1268		__format->field = V4L2_FIELD_NONE;
>   1269		__format->colorspace = V4L2_COLORSPACE_SRGB;
>   1270		__format->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(__format->colorspace);
>   1271		__format->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true,
>   1272					__format->colorspace, __format->ycbcr_enc);
>   1273		__format->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(__format->colorspace);
>   1274	
>   1275		format->format = *__format;
>   1276	
>   1277	exit:
>   1278		mutex_unlock(&ov7251->lock);
>   1279	
>   1280		return ret;
>   1281	}
>   1282	
>
Sakari Ailus May 5, 2022, 8:32 a.m. UTC | #3
On Thu, May 05, 2022 at 09:04:32AM +0100, Daniel Scally wrote:
> Argh - I screwed up a rebase here, not sure how I missed that. Sorry
> all, let me resend

If it's just that, I can fix it. No need to resend.
Daniel Scally May 5, 2022, 9:10 a.m. UTC | #4
On 05/05/2022 09:32, Sakari Ailus wrote:
> On Thu, May 05, 2022 at 09:04:32AM +0100, Daniel Scally wrote:
>> Argh - I screwed up a rebase here, not sure how I missed that. Sorry
>> all, let me resend
> If it's just that, I can fix it. No need to resend.
>
Thanks...there's two problems shown though actually, I used 1 instead of
vblank_def in the __v4l2_ctrl_modify_range() call in
ov7251_set_format(), and the other problem
(ov7251_pll1_cfg_24_mhz_319_2_mhz defined but not used) was actually
introduced in patch #7 (media: i2c: Add support for new frequencies to
ov7251). This change:


|static const struct ov7251_pll_cfgs ov7251_pll_cfgs_24_mhz = { .pll2 =
&ov7251_pll2_cfg_24_mhz, .pll1 = { [OV7251_LINK_FREQ_240_MHZ] =
&ov7251_pll1_cfg_24_mhz_240_mhz, + [OV7251_LINK_FREQ_319_2_MHZ] =
&ov7251_pll1_cfg_19_2_mhz_319_2_mhz, }, }; Should have referred to |ov7251_pll1_cfg_24_mhz_319_2_mhz. I can fix them, but it wouldn't be until tonight (sorry, I expected to have this ready much earlier by the end of the weekend!)
||
Andy Shevchenko May 5, 2022, 10:31 a.m. UTC | #5
On Wed, May 04, 2022 at 11:30:27PM +0100, Daniel Scally wrote:
> Add a vblank control to the ov7251 driver.

> +static int ov7251_vts_configure(struct ov7251 *ov7251, s32 vblank)
> +{
> +	u8 vts[2];
> +
> +	vts[0] = ((ov7251->current_mode->height + vblank) & 0xff00) >> 8;
> +	vts[1] = ((ov7251->current_mode->height + vblank) & 0x00ff);

__be16 vts;

cpu_to_be16();

> +	return ov7251_write_seq_regs(ov7251, OV7251_TIMING_VTS_REG, vts, 2);
> +}
kernel test robot May 5, 2022, 5:11 p.m. UTC | #6
Hi Daniel,

I love your patch! Perhaps something to improve:

[auto build test WARNING on media-tree/master]
[also build test WARNING on v5.18-rc5 next-20220505]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/intel-lab-lkp/linux/commits/Daniel-Scally/Support-OVTI7251-on-Microsoft-Surface-line/20220505-063608
base:   git://linuxtv.org/media_tree.git master
config: x86_64-randconfig-a014 (https://download.01.org/0day-ci/archive/20220506/202205060133.HrXVpuXG-lkp@intel.com/config)
compiler: clang version 15.0.0 (https://github.com/llvm/llvm-project 5e004fb787698440a387750db7f8028e7cb14cfc)
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/intel-lab-lkp/linux/commit/132a5a799bbe214b679bc8e242193c5c1ff1d967
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review Daniel-Scally/Support-OVTI7251-on-Microsoft-Surface-line/20220505-063608
        git checkout 132a5a799bbe214b679bc8e242193c5c1ff1d967
        # save the config file
        mkdir build_dir && cp config build_dir/.config
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=x86_64 SHELL=/bin/bash arch/x86/crypto/ drivers/media/i2c/

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All warnings (new ones prefixed by >>):

>> drivers/media/i2c/ov7251.c:1218:18: warning: variable 'vblank_def' set but not used [-Wunused-but-set-variable]
           int vblank_max, vblank_def;
                           ^
   drivers/media/i2c/ov7251.c:193:37: warning: unused variable 'ov7251_pll1_cfg_24_mhz_319_2_mhz' [-Wunused-const-variable]
   static const struct ov7251_pll1_cfg ov7251_pll1_cfg_24_mhz_319_2_mhz = {
                                       ^
   2 warnings generated.


vim +/vblank_def +1218 drivers/media/i2c/ov7251.c

  1211	
  1212	static int ov7251_set_format(struct v4l2_subdev *sd,
  1213				     struct v4l2_subdev_state *sd_state,
  1214				     struct v4l2_subdev_format *format)
  1215	{
  1216		struct ov7251 *ov7251 = to_ov7251(sd);
  1217		struct v4l2_mbus_framefmt *__format;
> 1218		int vblank_max, vblank_def;
  1219		struct v4l2_rect *__crop;
  1220		const struct ov7251_mode_info *new_mode;
  1221		int ret = 0;
  1222	
  1223		mutex_lock(&ov7251->lock);
  1224	
  1225		__crop = __ov7251_get_pad_crop(ov7251, sd_state, format->pad,
  1226					       format->which);
  1227	
  1228		new_mode = v4l2_find_nearest_size(ov7251_mode_info_data,
  1229					ARRAY_SIZE(ov7251_mode_info_data),
  1230					width, height,
  1231					format->format.width, format->format.height);
  1232	
  1233		__crop->width = new_mode->width;
  1234		__crop->height = new_mode->height;
  1235	
  1236		if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
  1237			ret = __v4l2_ctrl_modify_range(ov7251->exposure,
  1238						       1, new_mode->exposure_max,
  1239						       1, new_mode->exposure_def);
  1240			if (ret < 0)
  1241				goto exit;
  1242	
  1243			ret = __v4l2_ctrl_s_ctrl(ov7251->exposure,
  1244						 new_mode->exposure_def);
  1245			if (ret < 0)
  1246				goto exit;
  1247	
  1248			ret = __v4l2_ctrl_s_ctrl(ov7251->gain, 16);
  1249			if (ret < 0)
  1250				goto exit;
  1251	
  1252			vblank_max = OV7251_TIMING_MAX_VTS - new_mode->height;
  1253			vblank_def = new_mode->vts - new_mode->height;
  1254			ret = __v4l2_ctrl_modify_range(ov7251->vblank,
  1255						       OV7251_TIMING_MIN_VTS,
  1256						       vblank_max, 1, vblank_max);
  1257			if (ret < 0)
  1258				goto exit;
  1259	
  1260			ov7251->current_mode = new_mode;
  1261		}
  1262	
  1263		__format = __ov7251_get_pad_format(ov7251, sd_state, format->pad,
  1264						   format->which);
  1265		__format->width = __crop->width;
  1266		__format->height = __crop->height;
  1267		__format->code = MEDIA_BUS_FMT_Y10_1X10;
  1268		__format->field = V4L2_FIELD_NONE;
  1269		__format->colorspace = V4L2_COLORSPACE_SRGB;
  1270		__format->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(__format->colorspace);
  1271		__format->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true,
  1272					__format->colorspace, __format->ycbcr_enc);
  1273		__format->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(__format->colorspace);
  1274	
  1275		format->format = *__format;
  1276	
  1277	exit:
  1278		mutex_unlock(&ov7251->lock);
  1279	
  1280		return ret;
  1281	}
  1282
Daniel Scally May 5, 2022, 10:04 p.m. UTC | #7
Hi Andy


On 05/05/2022 11:31, Andy Shevchenko wrote:
> On Wed, May 04, 2022 at 11:30:27PM +0100, Daniel Scally wrote:
>> Add a vblank control to the ov7251 driver.
>> +static int ov7251_vts_configure(struct ov7251 *ov7251, s32 vblank)
>> +{
>> +	u8 vts[2];
>> +
>> +	vts[0] = ((ov7251->current_mode->height + vblank) & 0xff00) >> 8;
>> +	vts[1] = ((ov7251->current_mode->height + vblank) & 0x00ff);
> __be16 vts;
>
> cpu_to_be16();
Most places that do this seem to do the conversion in the i2c read/write
functions, so in this case within ov7251_write_seq_regs(). Can I do it
there, as an extra patch? I actually have more changes to make on this
driver but they're not remotely read yet so there'll be another series
in the future
>> +	return ov7251_write_seq_regs(ov7251, OV7251_TIMING_VTS_REG, vts, 2);
>> +}
Andy Shevchenko May 6, 2022, 9:22 p.m. UTC | #8
On Fri, May 6, 2022 at 1:46 PM Daniel Scally <djrscally@gmail.com> wrote:
> On 05/05/2022 11:31, Andy Shevchenko wrote:
> > On Wed, May 04, 2022 at 11:30:27PM +0100, Daniel Scally wrote:

...

> >> +    u8 vts[2];
> >> +
> >> +    vts[0] = ((ov7251->current_mode->height + vblank) & 0xff00) >> 8;
> >> +    vts[1] = ((ov7251->current_mode->height + vblank) & 0x00ff);
> > __be16 vts;
> >
> > cpu_to_be16();

> Most places that do this seem to do the conversion in the i2c read/write
> functions, so in this case within ov7251_write_seq_regs(). Can I do it
> there, as an extra patch? I actually have more changes to make on this
> driver but they're not remotely read yet so there'll be another series
> in the future

Ideally you should first convert them and then add your patch with
this change in mind.

> >> +    return ov7251_write_seq_regs(ov7251, OV7251_TIMING_VTS_REG, vts, 2);
diff mbox series

Patch

diff --git a/drivers/media/i2c/ov7251.c b/drivers/media/i2c/ov7251.c
index 003a7a5ae038..dc9d4e08efae 100644
--- a/drivers/media/i2c/ov7251.c
+++ b/drivers/media/i2c/ov7251.c
@@ -62,6 +62,10 @@ 
 #define OV7251_ACTIVE_HEIGHT		488
 
 #define OV7251_FIXED_PPL		928
+#define OV7251_TIMING_VTS_REG		0x380e
+#define OV7251_TIMING_MIN_VTS		1
+#define OV7251_TIMING_MAX_VTS		0xffff
+#define OV7251_INTEGRATION_MARGIN	20
 
 struct reg_value {
 	u16 reg;
@@ -71,6 +75,7 @@  struct reg_value {
 struct ov7251_mode_info {
 	u32 width;
 	u32 height;
+	u32 vts;
 	const struct reg_value *data;
 	u32 data_size;
 	u32 pixel_clock;
@@ -142,6 +147,7 @@  struct ov7251 {
 	struct v4l2_ctrl *exposure;
 	struct v4l2_ctrl *gain;
 	struct v4l2_ctrl *hblank;
+	struct v4l2_ctrl *vblank;
 
 	/* Cached register values */
 	u8 aec_pk_manual;
@@ -637,6 +643,7 @@  static const struct ov7251_mode_info ov7251_mode_info_data[] = {
 	{
 		.width = 640,
 		.height = 480,
+		.vts = 1724,
 		.data = ov7251_setting_vga_30fps,
 		.data_size = ARRAY_SIZE(ov7251_setting_vga_30fps),
 		.exposure_max = 1704,
@@ -649,6 +656,7 @@  static const struct ov7251_mode_info ov7251_mode_info_data[] = {
 	{
 		.width = 640,
 		.height = 480,
+		.vts = 860,
 		.data = ov7251_setting_vga_60fps,
 		.data_size = ARRAY_SIZE(ov7251_setting_vga_60fps),
 		.exposure_max = 840,
@@ -661,6 +669,7 @@  static const struct ov7251_mode_info ov7251_mode_info_data[] = {
 	{
 		.width = 640,
 		.height = 480,
+		.vts = 572,
 		.data = ov7251_setting_vga_90fps,
 		.data_size = ARRAY_SIZE(ov7251_setting_vga_90fps),
 		.exposure_max = 552,
@@ -1001,12 +1010,36 @@  static const char * const ov7251_test_pattern_menu[] = {
 	"Vertical Pattern Bars",
 };
 
+static int ov7251_vts_configure(struct ov7251 *ov7251, s32 vblank)
+{
+	u8 vts[2];
+
+	vts[0] = ((ov7251->current_mode->height + vblank) & 0xff00) >> 8;
+	vts[1] = ((ov7251->current_mode->height + vblank) & 0x00ff);
+
+	return ov7251_write_seq_regs(ov7251, OV7251_TIMING_VTS_REG, vts, 2);
+}
+
 static int ov7251_s_ctrl(struct v4l2_ctrl *ctrl)
 {
 	struct ov7251 *ov7251 = container_of(ctrl->handler,
 					     struct ov7251, ctrls);
 	int ret;
 
+	/* If VBLANK is altered we need to update exposure to compensate */
+	if (ctrl->id == V4L2_CID_VBLANK) {
+		int exposure_max;
+
+		exposure_max = ov7251->current_mode->height + ctrl->val -
+			       OV7251_INTEGRATION_MARGIN;
+		__v4l2_ctrl_modify_range(ov7251->exposure,
+					 ov7251->exposure->minimum,
+					 exposure_max,
+					 ov7251->exposure->step,
+					 min(ov7251->exposure->val,
+					     exposure_max));
+	}
+
 	/* v4l2_ctrl_lock() locks our mutex */
 
 	if (!pm_runtime_get_if_in_use(ov7251->dev))
@@ -1028,6 +1061,9 @@  static int ov7251_s_ctrl(struct v4l2_ctrl *ctrl)
 	case V4L2_CID_VFLIP:
 		ret = ov7251_set_vflip(ov7251, ctrl->val);
 		break;
+	case V4L2_CID_VBLANK:
+		ret = ov7251_vts_configure(ov7251, ctrl->val);
+		break;
 	default:
 		ret = -EINVAL;
 		break;
@@ -1179,6 +1215,7 @@  static int ov7251_set_format(struct v4l2_subdev *sd,
 {
 	struct ov7251 *ov7251 = to_ov7251(sd);
 	struct v4l2_mbus_framefmt *__format;
+	int vblank_max, vblank_def;
 	struct v4l2_rect *__crop;
 	const struct ov7251_mode_info *new_mode;
 	int ret = 0;
@@ -1212,6 +1249,14 @@  static int ov7251_set_format(struct v4l2_subdev *sd,
 		if (ret < 0)
 			goto exit;
 
+		vblank_max = OV7251_TIMING_MAX_VTS - new_mode->height;
+		vblank_def = new_mode->vts - new_mode->height;
+		ret = __v4l2_ctrl_modify_range(ov7251->vblank,
+					       OV7251_TIMING_MIN_VTS,
+					       vblank_max, 1, vblank_max);
+		if (ret < 0)
+			goto exit;
+
 		ov7251->current_mode = new_mode;
 	}
 
@@ -1492,6 +1537,7 @@  static int ov7251_detect_chip(struct ov7251 *ov7251)
 
 static int ov7251_init_ctrls(struct ov7251 *ov7251)
 {
+	int vblank_max, vblank_def;
 	s64 pixel_rate;
 	int hblank;
 
@@ -1535,6 +1581,13 @@  static int ov7251_init_ctrls(struct ov7251 *ov7251)
 	if (ov7251->hblank)
 		ov7251->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
 
+	vblank_max = OV7251_TIMING_MAX_VTS - ov7251->current_mode->height;
+	vblank_def = ov7251->current_mode->vts - ov7251->current_mode->height;
+	ov7251->vblank = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops,
+					   V4L2_CID_VBLANK,
+					   OV7251_TIMING_MIN_VTS, vblank_max, 1,
+					   vblank_def);
+
 	ov7251->sd.ctrl_handler = &ov7251->ctrls;
 
 	if (ov7251->ctrls.error) {