[v5,1/2] media: ovm6211: Adds support for OVM6211

Message ID 20220309102215.891001-2-petko.manolov@konsulko.com (mailing list archive)
State New
Headers
Series media: ovm6211: Adds support for OVM6211 |

Commit Message

Petko Manolov March 9, 2022, 10:22 a.m. UTC
  OVM6211 is a black and white 160 kpixel (400x400) camera sensor from OmniVision.
It supports 400x400, 200x200 and 100x100 image sizes.  With careful register
programming it is possible to have non-square sizes as well.  The output formats
are 8/10-bit RAW, this driver is only using the former.  OVM6211 supports a
single lane MIPI transmitter interface with up to 600 Mbps data rate.

Signed-off-by: Petko Manolov <petko.manolov@konsulko.com>
---
 drivers/media/i2c/Kconfig   |   10 +
 drivers/media/i2c/Makefile  |    1 +
 drivers/media/i2c/ovm6211.c | 1018 +++++++++++++++++++++++++++++++++++
 3 files changed, 1029 insertions(+)
 create mode 100644 drivers/media/i2c/ovm6211.c
  

Comments

Kieran Bingham March 9, 2022, 11:51 a.m. UTC | #1
Hi Petko,

Quoting Petko Manolov (2022-03-09 10:22:14)
> OVM6211 is a black and white 160 kpixel (400x400) camera sensor from OmniVision.
> It supports 400x400, 200x200 and 100x100 image sizes.  With careful register

Looking below, I see you only have 400x400 and 400x200 modes
implemented. It might be worth stating that here in the commit message.
I started looking through here with the assumption you are supporting
{400x400}, {200x200}, and {100x100}, but can see it's actually {400x400}
and {400x200}.


> programming it is possible to have non-square sizes as well.  The output formats
> are 8/10-bit RAW, this driver is only using the former.  OVM6211 supports a
> single lane MIPI transmitter interface with up to 600 Mbps data rate.

Is it possible to implement the HBLANK/VBLANK controls on this sensor? I
see some settings for frame intervals but not blanking. Perhaps the
blanking is fixed? (I see it's a global shutter sensor, I'm not sure if
that makes a difference here...)

Also I see there is exposure control, but no gain. Should
V4L2_CID_ANALOGUE_GAIN be implemented/supported?

https://www.ovt.com/wp-content/uploads/2022/01/OVM6211-PB-v1.6-WEB.pdf
shows at least that there is a gain control component.


> 
> Signed-off-by: Petko Manolov <petko.manolov@konsulko.com>
> ---
>  drivers/media/i2c/Kconfig   |   10 +
>  drivers/media/i2c/Makefile  |    1 +
>  drivers/media/i2c/ovm6211.c | 1018 +++++++++++++++++++++++++++++++++++
>  3 files changed, 1029 insertions(+)
>  create mode 100644 drivers/media/i2c/ovm6211.c
> 
> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> index fe66093b8849..aebe6a5f237e 100644
> --- a/drivers/media/i2c/Kconfig
> +++ b/drivers/media/i2c/Kconfig
> @@ -1049,6 +1049,16 @@ config VIDEO_OV5648
>           To compile this driver as a module, choose M here: the
>           module will be called ov5648.
>  
> +config VIDEO_OVM6211
> +       tristate "OmniVision OVM6211 sensor support"
> +       depends on I2C && VIDEO_V4L2
> +       help
> +         This is a Video4Linux2 sensor driver for the OmniVision
> +         OVM6211 camera.
> +
> +         To compile this driver as a module, choose M here: the
> +         module will be called ovm6211.
> +
>  config VIDEO_OV6650
>         tristate "OmniVision OV6650 sensor support"
>         depends on I2C && VIDEO_V4L2
> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> index f6b80ef6f41d..9e9998966c18 100644
> --- a/drivers/media/i2c/Makefile
> +++ b/drivers/media/i2c/Makefile
> @@ -78,6 +78,7 @@ obj-$(CONFIG_VIDEO_OV5670) += ov5670.o
>  obj-$(CONFIG_VIDEO_OV5675) += ov5675.o
>  obj-$(CONFIG_VIDEO_OV5693) += ov5693.o
>  obj-$(CONFIG_VIDEO_OV5695) += ov5695.o
> +obj-$(CONFIG_VIDEO_OVM6211) += ovm6211.o
>  obj-$(CONFIG_VIDEO_OV6650) += ov6650.o
>  obj-$(CONFIG_VIDEO_OV7251) += ov7251.o
>  obj-$(CONFIG_VIDEO_OV7640) += ov7640.o
> diff --git a/drivers/media/i2c/ovm6211.c b/drivers/media/i2c/ovm6211.c
> new file mode 100644
> index 000000000000..7cfcc0bf5e48
> --- /dev/null
> +++ b/drivers/media/i2c/ovm6211.c
> @@ -0,0 +1,1018 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2021 Petko Manolov <petko.manolov@konsulko.com>
> + * Copyright (C) 2021 DEKA Research & Development Corp.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/slab.h>
> +#include <linux/types.h>
> +#include <media/v4l2-async.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-subdev.h>
> +
> +/* System Control */
> +#define OVM6211_SC_MODE_SELECT         0x0100
> +#define OVM6211_SC_SOFTWARE_RESET      0x0103
> +#define OVM6211_SC_SCCB_ID1            0x0109
> +#define OVM6211_SC_CHIP_ID_HIGH                0x300a
> +#define OVM6211_SC_CHIP_ID_LOW         0x300b
> +#define OVM6211_SC_REG0C               0x300c
> +#define OVM6211_SC_REG10               0x3010
> +#define OVM6211_SC_MIPI_PHY            0x3012
> +#define OVM6211_SC_MIPI_PHY2           0x3013
> +#define OVM6211_SC_MIPI_SC_CTRL0       0x3014
> +#define OVM6211_SC_MIPI_SC_CTRL1       0x3015
> +#define OVM6211_SC_CLKRST0             0x3016
> +#define OVM6211_SC_CLKRST1             0x3017
> +#define OVM6211_SC_CLKRST2             0x3018
> +#define OVM6211_SC_CLKRST3             0x3019
> +#define OVM6211_SC_CLKRST4             0x301a
> +#define OVM6211_SC_CLKRST5             0x301b
> +#define OVM6211_SC_CLKRST6             0x301c
> +#define OVM6211_SC_CLOCK_SEL           0x301e
> +#define OVM6211_SC_MISC_CTRL           0x301f
> +#define OVM6211_SC_LOW_PWR_CTRL                0x3023
> +#define OVM6211_SC_REG27               0x3027
> +#define OVM6211_SC_GP_IO_IN1           0x3029
> +#define OVM6211_SC_GP_IO_IN2           0x302a
> +#define OVM6211_SC_SCCB_ID2            0x302b
> +#define OVM6211_SC_AUTO_SLEEP_PERIOD0  0x302c
> +#define OVM6211_SC_AUTO_SLEEP_PERIOD1  0x302d
> +#define OVM6211_SC_AUTO_SLEEP_PERIOD2  0x302e
> +#define OVM6211_SC_AUTO_SLEEP_PERIOD3  0x302f
> +#define OVM6211_SC_LP_CTRL0            0x3030
> +#define OVM6211_SC_REG37               0x3037
> +#define OVM6211_SC_REG3B               0x303b
> +/* PLL Control */
> +#define OVM6211_PLL_PLL0               0x3080
> +#define OVM6211_PLL_PLL1               0x3081
> +#define OVM6211_PLL_PLL2               0x3082
> +#define OVM6211_PLL_PLL18              0x3098
> +#define OVM6211_PLL_PLL19              0x3099
> +#define OVM6211_PLL_PLL1A              0x309a
> +#define OVM6211_PLL_PLL1B              0x309b
> +#define OVM6211_PLL_PLL1C              0x309c
> +#define OVM6211_PLL_PLL1D              0x309d
> +#define OVM6211_PLL_PLL1E              0x309e
> +#define OVM6211_PLL_PLL1F              0x309f
> +#define OVM6211_PLL_VT_PIX_CLK_DIV     0x30b0
> +#define OVM6211_PLL_VT_SYS_CLK_DIV     0x30b1
> +#define OVM6211_PLL_MULTIPLIER         0x30b3
> +#define OVM6211_PLL_PLL1_PRE_PLL_DIV   0x30b4
> +#define OVM6211_PLL_PLL1_OP_PIX_CLK_DIV        0x30b5
> +#define OVM6211_PLL_PLL1_OP_SYS_CLK_DIV        0x30b6
> +/* SCCB and group hold */
> +#define OVM6211_SB_SRB_CTRL            0x3106
> +#define OVM6211_SB_SWITCH              0x31ff
> +/* AEC/AGC */
> +#define OVM6211_AEC_EXPO1              0x3500
> +#define OVM6211_AEC_EXPO2              0x3501
> +#define OVM6211_AEC_EXPO3              0x3502
> +#define OVM6211_AEC_MANUAL             0x3503
> +#define OVM6211_AGC_MAN_SNR_GAIN1      0x3504
> +#define OVM6211_AGC_MAN_SNR_GAIN2      0x3505
> +/* timing conrol registers */
> +#define        OVM6211_TVTS_HI                 0x380e
> +#define        OVM6211_TVTS_LO                 0x380f
> +/*Strobe frame span registers*/
> +#define OVM6211_STROBE_SPAN1           0x3b8d
> +#define OVM6211_STROBE_SPAN2           0x3b8e
> +#define OVM6211_STROBE_SPAN3           0x3b8f
> +/* format control */
> +#define OVM6211_DATA_MAX_H             0x4300
> +#define OVM6211_DATA_MIN_H             0x4301
> +#define OVM6211_CLIP_L                 0x4302
> +#define OVM6211_FORMAT_CTRL3           0x4303
> +#define OVM6211_FORMAT_CTRL4           0x4304
> +#define OVM6211_VSYNC_WIDTH_H          0x4311
> +#define OVM6211_VSYNC_WIDTH_L          0x4312
> +#define OVM6211_VSYNC_CTRL             0x4313
> +#define OVM6211_VSYNC_DELAY1           0x4314
> +#define OVM6211_VSYNC_DELAY2           0x4315
> +#define OVM6211_VSYNC_DELAY3           0x4316
> +#define OVM6211_TST_PATTERN_CTRL       0x4320
> +/* MIPI */
> +#define OVM6211_MIPI_CTRL00            0x4800
> +#define OVM6211_MIPI_CTRL01            0x4801
> +#define OVM6211_MIPI_CTRL02            0x4802
> +#define OVM6211_MIPI_CTRL03            0x4803
> +#define OVM6211_MIPI_CTRL04            0x4804
> +#define OVM6211_MIPI_CTRL05            0x4805
> +#define OVM6211_MIPI_CTRL06            0x4806
> +#define OVM6211_MIPI_MAX_FRAME_COUNT_H 0x4810
> +#define OVM6211_MIPI_MAX_FRAME_COUNT_L 0x4811
> +#define OVM6211_MIPI_SHORT_PKT_COUNTER_H       0x4812
> +#define OVM6211_MIPI_SHORT_PKT_COUNTER_L       0x4813
> +#define OVM6211_MIPI_CTRL14            0x4814
> +#define OVM6211_MIPI_DT_SPKT           0x4815
> +#define OVM6211_MIPI_REG_MAX_H         0x4835
> +#define OVM6211_MIPI_REG_MAX_L         0x4836
> +#define OVM6211_PCLK_PERIOD            0x4837
> +#define OVM6211_WKUP_DLY               0x4838
> +#define OVM6211_DIR_DLY                        0x483a
> +#define OVM6211_MIPI_LP_GPIO           0x483b
> +#define OVM6211_MIPI_CTRL3C            0x483c
> +#define OVM6211_T_TA_GO                        0x483d
> +#define OVM6211_T_TA_SURE              0x483e
> +#define OVM6211_T_TA_GET               0x483f
> +#define OVM6211_MIPI_CLIP_MAX_H                0x4846
> +#define OVM6211_MIPI_CLIP_MAX_L                0x4847
> +#define OVM6211_MIPI_CLIP_MIN_H                0x4848
> +#define OVM6211_MIPI_CLIP_MIN_L                0x4848
> +#define OVM6211_REG_INTR_MAN           0x4850
> +#define OVM6211_REG_TX_WR              0x4851
> +/* ISP top */
> +#define OVM6211_ISP_CTRL00             0x5000
> +#define OVM6211_ISP_CTRL01             0x5001
> +#define OVM6211_ISP_CTRL02             0x5002
> +#define OVM6211_ISP_CTRL03             0x5003
> +#define OVM6211_ISP_CTRL04             0x5004
> +#define OVM6211_ISP_CTRL05             0x5005
> +#define OVM6211_ISP_CTRL06             0x5006
> +#define OVM6211_ISP_CTRL07             0x5007
> +#define OVM6211_ISP_CTRL08             0x5008
> +#define OVM6211_ISP_CTRL09             0x5009
> +/* window control */
> +#define OVM6211_MAN_XSTART_OFF_H       0x5a00
> +#define OVM6211_MAN_XSTART_OFF_L       0x5a01
> +#define OVM6211_MAN_YSTART_OFF_H       0x5a02
> +#define OVM6211_MAN_YSTART_OFF_L       0x5a03
> +#define OVM6211_MAN_WIN_WIDTH_H                0x5a04
> +#define OVM6211_MAN_WIN_WIDTH_L                0x5a05
> +#define OVM6211_MAN_WIN_HEIGHT_H       0x5a06
> +#define OVM6211_MAN_WIN_HEIGHT_L       0x5a07
> +#define OVM6211_WIN_MAN                        0x5a08
> +
> +#define        OVM6211_LAST_REG                0x5e08
> +/* end of OVM6211 definitions */
> +
> +#define        DEF_LINK_FREQ                   38400000LL
> +
> +enum ovm6211_mode_id {
> +       OVM6211_MODE_Y8_400_200 = 0,
> +       OVM6211_MODE_Y8_400_400,
> +       OVM6211_NUM_MODES,
> +};
> +
> +struct ovm6211_pixfmt {
> +       u32 code;
> +       u32 colorspace;
> +};
> +
> +static const struct ovm6211_pixfmt ovm6211_formats[] = {
> +       { MEDIA_BUS_FMT_Y8_1X8,   V4L2_COLORSPACE_RAW, },
> +       { MEDIA_BUS_FMT_Y8_1X8,   V4L2_COLORSPACE_RAW, },

Is one entry enough here?

The color space isn't even used is it?
It could probably be hardcoded in ovm6211_enum_mbus_code(), and then get
rid of struct ovm6211_pixfmt too?

Unless you expect other bus formats or colorspace to be supported?


> +};
> +
> +enum ovm6211_framerate_ids {
> +       OVM6211_10_FPS = 0,
> +       OVM6211_15_FPS,
> +       OVM6211_30_FPS,
> +       OVM6211_45_FPS,
> +       OVM6211_60_FPS,
> +       OVM6211_NUM_FRAMERATES,
> +};
> +
> +static const int ovm6211_framerates[] = {
> +       [OVM6211_10_FPS] = 10,
> +       [OVM6211_15_FPS] = 15,
> +       [OVM6211_30_FPS] = 30,
> +       [OVM6211_45_FPS] = 45,
> +       [OVM6211_60_FPS] = 60,
> +};
> +
> +/* regulator supplies */
> +static const char * const ovm6211_supply_name[] = {
> +       "dovdd",
> +       "avdd",
> +};
> +
> +static const struct regmap_config ovm6211_regmap_config = {
> +       .reg_bits       = 16,
> +       .val_bits       = 8,
> +       .max_register   = OVM6211_LAST_REG,
> +       .cache_type     = REGCACHE_NONE,
> +};
> +
> +struct reg_value {
> +       u16 reg_addr;
> +       u8 val;
> +       u8 mask;
> +       u32 delay_ms;
> +};
> +
> +struct ovm6211_mode_info {
> +       enum ovm6211_mode_id id;
> +       u32 width;
> +       u32 height;
> +       const struct reg_value *reg_data;
> +       u32 reg_data_size;
> +       u32 pixel_clock;
> +};
> +
> +struct ovm6211_ctrls {
> +       struct v4l2_ctrl_handler handler;
> +       struct {
> +               struct v4l2_ctrl *auto_exp;
> +               struct v4l2_ctrl *exposure;
> +       };
> +       struct {
> +               struct v4l2_ctrl *auto_gain;
> +               struct v4l2_ctrl *gain;
> +       };
> +       struct v4l2_ctrl *link_freq;
> +};
> +
> +struct ovm6211_dev {
> +       struct i2c_client *i2c_client;
> +       struct regmap *regmap;
> +       struct v4l2_subdev sd;
> +       struct media_pad pad;
> +       struct v4l2_fwnode_endpoint ep; /* the parsed DT endpoint info */
> +
> +       struct regulator_bulk_data supplies[ARRAY_SIZE(ovm6211_supply_name)];
> +       struct gpio_desc *reset_gpio;
> +       struct gpio_desc *pwdn_gpio;
> +
> +       struct mutex lock;
> +
> +       struct v4l2_mbus_framefmt fmt;
> +
> +       const struct ovm6211_mode_info *cur_mode;
> +       enum ovm6211_framerate_ids cur_fr_id;
> +       struct v4l2_fract frame_interval;
> +
> +       struct ovm6211_ctrls ctrls;
> +
> +       u32 exposure;
> +       bool pending_mode_change;
> +       bool pending_fi_change;
> +       bool streaming;
> +};
> +
> +static inline struct ovm6211_dev *to_ovm6211_dev(struct v4l2_subdev *sd)
> +{
> +       return container_of(sd, struct ovm6211_dev, sd);
> +}
> +
> +static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl)
> +{
> +       return &container_of(ctrl->handler, struct ovm6211_dev,
> +                            ctrls.handler)->sd;
> +}
> +
> +static const struct reg_value ovm6211_init_y8_400_400[] = {
> +       {0x0103, 0x01, 0, 0}, {0x0100, 0x00, 0, 0}, {0x3005, 0x08, 0, 0},
> +       {0x3013, 0x12, 0, 0}, {0x3014, 0x04, 0, 0}, {0x3016, 0x10, 0, 0},
> +       {0x3017, 0x00, 0, 0}, {0x3018, 0x00, 0, 0}, {0x301a, 0x00, 0, 0},
> +       {0x301b, 0x00, 0, 0}, {0x301c, 0x00, 0, 0}, {0x3037, 0xf0, 0, 0},
> +       {0x3080, 0x01, 0, 0}, {0x3081, 0x00, 0, 0}, {0x3082, 0x01, 0, 0},
> +       {0x3098, 0x04, 0, 0}, {0x3099, 0x28, 0, 0}, {0x309a, 0x06, 0, 0},
> +       {0x309b, 0x04, 0, 0}, {0x309c, 0x00, 0, 0}, {0x309d, 0x00, 0, 0},
> +       {0x309e, 0x01, 0, 0}, {0x309f, 0x00, 0, 0}, {0x30b0, 0x08, 0, 0},
> +       {0x30b1, 0x02, 0, 0}, {0x30b2, 0x00, 0, 0}, {0x30b3, 0x28, 0, 0},
> +       {0x30b4, 0x02, 0, 0}, {0x30b5, 0x00, 0, 0}, {0x3106, 0xd9, 0, 0},
> +       {0x3500, 0x00, 0, 0}, {0x3501, 0x1b, 0, 0}, {0x3502, 0x20, 0, 0},
> +       {0x3503, 0x07, 0, 0}, {0x3509, 0x10, 0, 0}, {0x350b, 0x10, 0, 0},
> +       {0x3600, 0xfc, 0, 0}, {0x3620, 0xb7, 0, 0}, {0x3621, 0x05, 0, 0},
> +       {0x3626, 0x31, 0, 0}, {0x3627, 0x40, 0, 0}, {0x3632, 0xa3, 0, 0},
> +       {0x3633, 0x34, 0, 0}, {0x3634, 0x40, 0, 0}, {0x3636, 0x00, 0, 0},
> +       {0x3660, 0x80, 0, 0}, {0x3662, 0x03, 0, 0}, {0x3664, 0xf0, 0, 0},
> +       {0x366a, 0x10, 0, 0}, {0x366b, 0x06, 0, 0}, {0x3680, 0xf4, 0, 0},
> +       {0x3681, 0x50, 0, 0}, {0x3682, 0x00, 0, 0}, {0x3708, 0x20, 0, 0},
> +       {0x3709, 0x40, 0, 0}, {0x370d, 0x03, 0, 0}, {0x373b, 0x02, 0, 0},
> +       {0x373c, 0x08, 0, 0}, {0x3742, 0x00, 0, 0}, {0x3744, 0x16, 0, 0},
> +       {0x3745, 0x08, 0, 0}, {0x3781, 0xfc, 0, 0}, {0x3788, 0x00, 0, 0},
> +       {0x3800, 0x00, 0, 0}, {0x3801, 0x04, 0, 0}, {0x3802, 0x00, 0, 0},
> +       {0x3803, 0x04, 0, 0}, {0x3804, 0x01, 0, 0}, {0x3805, 0x9b, 0, 0},
> +       {0x3806, 0x01, 0, 0}, {0x3807, 0x9b, 0, 0}, {0x3808, 0x01, 0, 0},
> +       {0x3809, 0x90, 0, 0}, {0x380a, 0x01, 0, 0}, {0x380b, 0x90, 0, 0},
> +       {0x380c, 0x05, 0, 0}, {0x380d, 0xf2, 0, 0}, {0x380e, 0x03, 0, 0},
> +       {0x380f, 0x6c, 0, 0}, {0x3810, 0x00, 0, 0}, {0x3811, 0x04, 0, 0},
> +       {0x3812, 0x00, 0, 0}, {0x3813, 0x04, 0, 0}, {0x3814, 0x11, 0, 0},
> +       {0x3815, 0x11, 0, 0}, {0x3820, 0x00, 0, 0}, {0x3821, 0x00, 0, 0},
> +       {0x382b, 0xfa, 0, 0}, {0x382f, 0x04, 0, 0}, {0x3832, 0x00, 0, 0},
> +       {0x3833, 0x05, 0, 0}, {0x3834, 0x00, 0, 0}, {0x3835, 0x05, 0, 0},
> +       {0x3882, 0x04, 0, 0}, {0x3883, 0x00, 0, 0}, {0x38a4, 0x10, 0, 0},
> +       {0x38a5, 0x00, 0, 0}, {0x38b1, 0x03, 0, 0}, {0x3b80, 0x00, 0, 0},
> +       {0x3b81, 0xff, 0, 0}, {0x3b82, 0x10, 0, 0}, {0x3b83, 0x00, 0, 0},
> +       {0x3b84, 0x08, 0, 0}, {0x3b85, 0x00, 0, 0}, {0x3b86, 0x01, 0, 0},
> +       {0x3b87, 0x00, 0, 0}, {0x3b88, 0x00, 0, 0}, {0x3b89, 0x00, 0, 0},
> +       {0x3b8a, 0x00, 0, 0}, {0x3b8b, 0x05, 0, 0}, {0x3b8c, 0x00, 0, 0},
> +       {0x3b8d, 0x00, 0, 0}, {0x3b8e, 0x01, 0, 0}, {0x3b8f, 0xb2, 0, 0},
> +       {0x3b94, 0x05, 0, 0}, {0x3b95, 0xf2, 0, 0}, {0x3b96, 0xc0, 0, 0},
> +       {0x4004, 0x04, 0, 0}, {0x404e, 0x01, 0, 0}, {0x4801, 0x0f, 0, 0},
> +       {0x4806, 0x0f, 0, 0}, {0x4837, 0x43, 0, 0}, {0x5a08, 0x00, 0, 0},
> +       {0x5a01, 0x00, 0, 0}, {0x5a03, 0x00, 0, 0}, {0x5a04, 0x10, 0, 0},
> +       {0x5a05, 0xa0, 0, 0}, {0x5a06, 0x0c, 0, 0}, {0x5a07, 0x78, 0, 0},
> +};
> +
> +static const struct reg_value ovm6211_init_y8_400_200[] = {
> +       {0x0103, 0x01, 0, 0}, {0x0100, 0x00, 0, 0}, {0x3005, 0x08, 0, 0},
> +       {0x3013, 0x12, 0, 0}, {0x3014, 0x04, 0, 0}, {0x3016, 0x10, 0, 0},
> +       {0x3017, 0x00, 0, 0}, {0x3018, 0x00, 0, 0}, {0x301a, 0x00, 0, 0},
> +       {0x301b, 0x00, 0, 0}, {0x301c, 0x00, 0, 0}, {0x3037, 0xf0, 0, 0},
> +       {0x3080, 0x01, 0, 0}, {0x3081, 0x00, 0, 0}, {0x3082, 0x01, 0, 0},
> +       {0x3098, 0x04, 0, 0}, {0x3099, 0x28, 0, 0}, {0x309a, 0x06, 0, 0},
> +       {0x309b, 0x04, 0, 0}, {0x309c, 0x00, 0, 0}, {0x309d, 0x00, 0, 0},
> +       {0x309e, 0x01, 0, 0}, {0x309f, 0x00, 0, 0}, {0x30b0, 0x08, 0, 0},
> +       {0x30b1, 0x02, 0, 0}, {0x30b2, 0x00, 0, 0}, {0x30b3, 0x28, 0, 0},
> +       {0x30b4, 0x02, 0, 0}, {0x30b5, 0x00, 0, 0}, {0x3106, 0xd9, 0, 0},
> +       {0x3500, 0x00, 0, 0}, {0x3501, 0x1b, 0, 0}, {0x3502, 0x20, 0, 0},
> +       {0x3503, 0x07, 0, 0}, {0x3509, 0x10, 0, 0}, {0x350b, 0x10, 0, 0},
> +       {0x3600, 0xfc, 0, 0}, {0x3620, 0xb7, 0, 0}, {0x3621, 0x05, 0, 0},
> +       {0x3626, 0x31, 0, 0}, {0x3627, 0x40, 0, 0}, {0x3632, 0xa3, 0, 0},
> +       {0x3633, 0x34, 0, 0}, {0x3634, 0x40, 0, 0}, {0x3636, 0x00, 0, 0},
> +       {0x3660, 0x80, 0, 0}, {0x3662, 0x03, 0, 0}, {0x3664, 0xf0, 0, 0},
> +       {0x366a, 0x10, 0, 0}, {0x366b, 0x06, 0, 0}, {0x3680, 0xf4, 0, 0},
> +       {0x3681, 0x50, 0, 0}, {0x3682, 0x00, 0, 0}, {0x3708, 0x20, 0, 0},
> +       {0x3709, 0x40, 0, 0}, {0x370d, 0x03, 0, 0}, {0x373b, 0x02, 0, 0},
> +       {0x373c, 0x08, 0, 0}, {0x3742, 0x00, 0, 0}, {0x3744, 0x16, 0, 0},
> +       {0x3745, 0x08, 0, 0}, {0x3781, 0xfc, 0, 0}, {0x3788, 0x00, 0, 0},
> +       {0x3800, 0x00, 0, 0}, {0x3801, 0x04, 0, 0}, {0x3802, 0x00, 0, 0},
> +       {0x3803, 0x04, 0, 0}, {0x3804, 0x01, 0, 0}, {0x3805, 0x9b, 0, 0},
> +       {0x3806, 0x01, 0, 0}, {0x3807, 0x9b, 0, 0}, {0x3808, 0x01, 0, 0},
> +       {0x3809, 0x90, 0, 0}, {0x380a, 0x00, 0, 0}, {0x380b, 0xc8, 0, 0},
> +       {0x380c, 0x05, 0, 0}, {0x380d, 0xf2, 0, 0}, {0x380e, 0x0d, 0, 0},
> +       {0x380f, 0xb0, 0, 0}, {0x3810, 0x00, 0, 0}, {0x3811, 0x04, 0, 0},
> +       {0x3812, 0x00, 0, 0}, {0x3813, 0x9a, 0, 0}, {0x3814, 0x11, 0, 0},
> +       {0x3815, 0x11, 0, 0}, {0x3820, 0x00, 0, 0}, {0x3821, 0x00, 0, 0},
> +       {0x382b, 0xfa, 0, 0}, {0x382f, 0x04, 0, 0}, {0x3832, 0x00, 0, 0},
> +       {0x3833, 0x05, 0, 0}, {0x3834, 0x00, 0, 0}, {0x3835, 0x05, 0, 0},
> +       {0x3882, 0x04, 0, 0}, {0x3883, 0x00, 0, 0}, {0x38a4, 0x10, 0, 0},
> +       {0x38a5, 0x00, 0, 0}, {0x38b1, 0x03, 0, 0}, {0x3b80, 0x00, 0, 0},
> +       {0x3b81, 0xff, 0, 0}, {0x3b82, 0x10, 0, 0}, {0x3b83, 0x00, 0, 0},
> +       {0x3b84, 0x08, 0, 0}, {0x3b85, 0x00, 0, 0}, {0x3b86, 0x01, 0, 0},
> +       {0x3b87, 0x00, 0, 0}, {0x3b88, 0x00, 0, 0}, {0x3b89, 0x00, 0, 0},
> +       {0x3b8a, 0x00, 0, 0}, {0x3b8b, 0x05, 0, 0}, {0x3b8c, 0x00, 0, 0},
> +       {0x3b8d, 0x00, 0, 0}, {0x3b8e, 0x01, 0, 0}, {0x3b8f, 0xb2, 0, 0},
> +       {0x3b94, 0x05, 0, 0}, {0x3b95, 0xf2, 0, 0}, {0x3b96, 0xc0, 0, 0},
> +       {0x4004, 0x04, 0, 0}, {0x404e, 0x01, 0, 0}, {0x4801, 0x0f, 0, 0},
> +       {0x4806, 0x0f, 0, 0}, {0x4837, 0x43, 0, 0}, {0x5a08, 0x00, 0, 0},
> +       {0x5a01, 0x00, 0, 0}, {0x5a03, 0x00, 0, 0}, {0x5a04, 0x10, 0, 0},
> +       {0x5a05, 0xa0, 0, 0}, {0x5a06, 0x0c, 0, 0}, {0x5a07, 0x78, 0, 0},
> +};
> +
> +static struct ovm6211_mode_info
> +ovm6211_mode_data[OVM6211_NUM_MODES] = {
> +       {OVM6211_MODE_Y8_400_200,
> +        400, 200,
> +        ovm6211_init_y8_400_200,
> +        ARRAY_SIZE(ovm6211_init_y8_400_200),
> +        400 * 400 * 60 * 2,
> +       },
> +       {OVM6211_MODE_Y8_400_400,
> +        400, 400,
> +        ovm6211_init_y8_400_400,
> +        ARRAY_SIZE(ovm6211_init_y8_400_400),
> +        400 * 400 * 60 * 2,
> +       },
> +};
> +
> +static const s64 link_freq_menu_items[] = {
> +       DEF_LINK_FREQ,
> +};
> +
> +#ifdef CONFIG_VIDEO_ADV_DEBUG
> +static int ovm6211_get_register(struct v4l2_subdev *sd,
> +                               struct v4l2_dbg_register *reg)
> +{
> +       struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
> +       struct regmap *regmap = sensor->regmap;
> +       unsigned int val = 0;
> +       int ret;
> +
> +       ret = regmap_read(regmap, reg->reg, &val);
> +       reg->val = val;
> +       reg->size = 1;
> +
> +       return ret;
> +}
> +
> +static int ovm6211_set_register(struct v4l2_subdev *sd,
> +                               const struct v4l2_dbg_register *reg)
> +{
> +       struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
> +       struct regmap *regmap = sensor->regmap;
> +
> +       return regmap_write(regmap, reg->reg, reg->val & 0xff);
> +}
> +#endif
> +
> +static int ovm6211_write_reg(struct ovm6211_dev *sensor, u16 reg, u8 val)
> +{
> +       struct i2c_client *client = sensor->i2c_client;
> +       struct regmap *regmap = sensor->regmap;
> +       int ret;
> +
> +       ret = regmap_write(regmap, reg, val);
> +       if (ret < 0)
> +               dev_err(&client->dev, "error writing reg %u\n", reg);
> +
> +       return ret;
> +}
> +
> +static int ovm6211_read_reg(struct ovm6211_dev *sensor, u16 reg, u8 *val)
> +{
> +       struct i2c_client *client = sensor->i2c_client;
> +       struct regmap *regmap = sensor->regmap;
> +       unsigned int r;
> +       int ret;
> +
> +       ret = regmap_read(regmap, reg, &r);
> +       if (ret < 0)
> +               dev_err(&client->dev, "error reading reg %u\n", reg);
> +       *val = r & 0xff;
> +
> +       return ret;
> +}
> +
> +static int ovm6211_mod_reg(struct ovm6211_dev *sensor, u16 reg, u8 mask, u8 val)
> +{
> +       u8 readval;
> +       int ret;
> +
> +       ret = ovm6211_read_reg(sensor, reg, &readval);
> +       if (ret)
> +               return ret;
> +
> +       readval &= ~mask;
> +       val &= mask;
> +       val |= readval;
> +
> +       return ovm6211_write_reg(sensor, reg, val);
> +}
> +
> +static int ovm6211_load_regs(struct ovm6211_dev *sensor,
> +                            const struct ovm6211_mode_info *mode)
> +{
> +       const struct reg_value *regs = mode->reg_data;
> +       unsigned int i;
> +       u32 delay_ms;
> +       u16 reg_addr;
> +       u8 mask, val;
> +       int ret = 0;
> +
> +       for (i = 0; i < mode->reg_data_size; ++i, ++regs) {
> +               delay_ms = regs->delay_ms;
> +               reg_addr = regs->reg_addr;
> +               val = regs->val;
> +               mask = regs->mask;
> +
> +               if (mask)
> +                       ret = ovm6211_mod_reg(sensor, reg_addr, mask, val);
> +               else
> +                       ret = ovm6211_write_reg(sensor, reg_addr, val);
> +
> +               if (ret)
> +                       break;
> +
> +               if (delay_ms)
> +                       usleep_range(1000 * delay_ms, 1000 * delay_ms + 100);
> +       }
> +
> +       return ret;
> +}
> +
> +static void ovm6211_soft_reset(struct ovm6211_dev *sensor)
> +{
> +       ovm6211_write_reg(sensor, OVM6211_SC_SOFTWARE_RESET, 0x01);
> +       usleep_range(5000, 9000);
> +       ovm6211_write_reg(sensor, OVM6211_SC_SOFTWARE_RESET, 0x00);
> +}
> +
> +static int ovm6211_set_exposure(struct ovm6211_dev *sensor, u32 exposure)
> +{
> +       u32 ce;
> +       u8 v;
> +
> +       ovm6211_read_reg(sensor, OVM6211_TVTS_HI, &v);
> +       ce = v << 8;
> +       ovm6211_read_reg(sensor, OVM6211_TVTS_LO, &v);
> +       ce |= v;
> +       ce -= 4;
> +
> +       if (ce < exposure)
> +               exposure = ce;
> +
> +       ovm6211_mod_reg(sensor, OVM6211_AEC_MANUAL, 1, 1);
> +
> +       ovm6211_write_reg(sensor, OVM6211_AEC_EXPO1, (exposure >> 12) & 0x0f);
> +       ovm6211_write_reg(sensor, OVM6211_AEC_EXPO2, (exposure >>  4) & 0xff);
> +       ovm6211_write_reg(sensor, OVM6211_AEC_EXPO3, (exposure <<  4) & 0xf0);
> +
> +       /* set strobe width equal to exposure time */
> +       ovm6211_write_reg(sensor, OVM6211_STROBE_SPAN1,
> +                         (exposure >> 16) & 0xff);
> +       ovm6211_write_reg(sensor, OVM6211_STROBE_SPAN2,
> +                         (exposure >> 8) & 0xff);
> +       ovm6211_write_reg(sensor, OVM6211_STROBE_SPAN3, (exposure) & 0xff);
> +
> +       return 0;
> +}
> +
> +static int internal_set_stream(struct ovm6211_dev *sensor, bool on)
> +{
> +       u8 hi, lo;
> +
> +       if (sensor->pending_fi_change == false)
> +               goto stream;
> +
> +       switch (sensor->cur_fr_id) {
> +       case OVM6211_10_FPS:
> +               hi = 0x14;
> +               lo = 0x88;
> +               break;
> +       case OVM6211_15_FPS:
> +               hi = 0x0d;
> +               lo = 0xb0;
> +               break;
> +       case OVM6211_30_FPS:
> +               hi = 0x06;
> +               lo = 0xd8;
> +               break;
> +       case OVM6211_45_FPS:
> +               hi = 0x04;
> +               lo = 0x90;
> +               break;
> +       case OVM6211_60_FPS:
> +               hi = 0x03;
> +               lo = 0x6c;
> +               break;
> +       case OVM6211_NUM_FRAMERATES:
> +               return -EINVAL;
> +       }
> +
> +       sensor->pending_fi_change = false;
> +       ovm6211_write_reg(sensor, OVM6211_TVTS_HI, hi);
> +       ovm6211_write_reg(sensor, OVM6211_TVTS_LO, lo);
> +stream:
> +       ovm6211_write_reg(sensor, OVM6211_SC_MODE_SELECT, 0);
> +       if (on) {
> +               usleep_range(4000, 5000);
> +               if (sensor->exposure)
> +                       ovm6211_set_exposure(sensor, sensor->exposure);
> +               ovm6211_write_reg(sensor, OVM6211_SC_MODE_SELECT, 1);
> +               sensor->streaming = true;
> +       } else {
> +               sensor->streaming = false;
> +       }
> +
> +       return 0;
> +}
> +
> +static struct ovm6211_mode_info *ovm6211_find_mode(int w, int h)
> +{
> +       return v4l2_find_nearest_size(ovm6211_mode_data,
> +                                     ARRAY_SIZE(ovm6211_mode_data),
> +                                     width, height, w, h);
> +}
> +
> +static int ovm6211_set_mode(struct ovm6211_dev *sensor)
> +{
> +       const struct ovm6211_mode_info *mode = sensor->cur_mode;
> +
> +       ovm6211_soft_reset(sensor);
> +       ovm6211_load_regs(sensor, mode);
> +
> +       return 0;
> +}
> +
> +/* --------------- Subdev Operations --------------- */
> +
> +static int ovm6211_get_fmt(struct v4l2_subdev *sd,
> +                          struct v4l2_subdev_state *state,
> +                          struct v4l2_subdev_format *format)
> +{
> +       struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
> +       struct v4l2_mbus_framefmt *fmt;
> +
> +       mutex_lock(&sensor->lock);
> +
> +       if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
> +               fmt = v4l2_subdev_get_try_format(&sensor->sd, state, format->pad);
> +               format->format = *fmt;
> +       } else {
> +               /* these are hardcoded as we don't support anything else */
> +               format->format.colorspace  = V4L2_COLORSPACE_RAW;
> +               format->format.field = V4L2_FIELD_NONE;
> +               format->format.code = MEDIA_BUS_FMT_Y8_1X8;
> +               format->format.width = sensor->cur_mode->width;
> +               format->format.height = sensor->cur_mode->height;
> +       }
> +
> +       mutex_unlock(&sensor->lock);
> +
> +       return 0;
> +}
> +
> +static int ovm6211_set_fmt(struct v4l2_subdev *sd,
> +                          struct v4l2_subdev_state *state,
> +                          struct v4l2_subdev_format *format)
> +{
> +       struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
> +       struct v4l2_mbus_framefmt *fmt = &format->format;
> +       struct ovm6211_mode_info *mode;
> +
> +       mutex_lock(&sensor->lock);
> +
> +       if (sensor->streaming)
> +               return -EBUSY;
> +
> +       /* these are hardcoded as we don't support anything else */
> +       format->format.colorspace  = V4L2_COLORSPACE_RAW;
> +       format->format.field = V4L2_FIELD_NONE;
> +       format->format.code = MEDIA_BUS_FMT_Y8_1X8;
> +       mode = ovm6211_find_mode(format->format.width, format->format.height);
> +       format->format.width = mode->width;
> +       format->format.height = mode->height;
> +
> +       if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
> +               fmt = v4l2_subdev_get_try_format(&sensor->sd, state, format->pad);
> +               *fmt = format->format;
> +               goto out;
> +       }
> +       sensor->cur_mode = mode;
> +out:
> +       mutex_unlock(&sensor->lock);
> +
> +       return 0;
> +}
> +
> +/*
> + * Sensor Controls.
> + */
> +
> +static int ovm6211_set_ctrl_exposure(struct ovm6211_dev *sensor, enum
> +                                    v4l2_exposure_auto_type auto_exposure)
> +{
> +       struct ovm6211_ctrls *ctrls = &sensor->ctrls;
> +
> +       if (auto_exposure == V4L2_EXPOSURE_AUTO) {
> +               sensor->exposure = 0;
> +       } else {
> +               sensor->exposure = ctrls->exposure->val;
> +               ovm6211_set_exposure(sensor, sensor->exposure);
> +       }
> +
> +       return 0;
> +}
> +
> +static int ovm6211_s_ctrl(struct v4l2_ctrl *ctrl)
> +{
> +       struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
> +       struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
> +       int ret;
> +
> +       switch (ctrl->id) {
> +       case V4L2_CID_EXPOSURE_AUTO:
> +               ret = ovm6211_set_ctrl_exposure(sensor, ctrl->val);
> +               break;
> +       case V4L2_CID_LINK_FREQ:

Is this actually adjustable? Or should LINK_FREQ always return DEF_LINK_FREQ?

Aha perhaps it does because it's a menu entry with only one entry ...

> +               return 0;
> +       case V4L2_CID_PIXEL_RATE:

Is it valid to just openly accept any LINK_FREQ/PIXEL_RATE?

> +               return 0;
> +       default:
> +               ret = -EINVAL;
> +               break;
> +       }
> +
> +       return ret;
> +}
> +
> +static const struct v4l2_ctrl_ops ovm6211_ctrl_ops = {
> +       .s_ctrl = ovm6211_s_ctrl,
> +};
> +
> +static int ovm6211_init_controls(struct ovm6211_dev *sensor)
> +{
> +       const struct v4l2_ctrl_ops *ops = &ovm6211_ctrl_ops;
> +       struct ovm6211_ctrls *ctrls = &sensor->ctrls;
> +       struct v4l2_ctrl_handler *hdl = &ctrls->handler;
> +       int ret;
> +
> +       v4l2_ctrl_handler_init(hdl, 16);
> +
> +       hdl->lock = &sensor->lock;
> +
> +       ctrls->auto_exp = v4l2_ctrl_new_std_menu(hdl, ops, V4L2_CID_EXPOSURE_AUTO,
> +                                                V4L2_EXPOSURE_MANUAL, 0,
> +                                                V4L2_EXPOSURE_AUTO);
> +       ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE,
> +                                           0, 65535, 1, 0);
> +       v4l2_ctrl_auto_cluster(3, &ctrls->auto_exp, 0, false);
> +       ctrls->link_freq = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ,
> +                                                 0, 0, link_freq_menu_items);
> +
> +       if (hdl->error) {
> +               ret = hdl->error;
> +               goto free_ctrls;
> +       }
> +
> +       ctrls->exposure->flags |= V4L2_CTRL_FLAG_VOLATILE;
> +       sensor->sd.ctrl_handler = hdl;
> +
> +       return 0;
> +
> +free_ctrls:
> +       v4l2_ctrl_handler_free(hdl);
> +       return ret;
> +}
> +
> +static int ovm6211_g_frame_interval(struct v4l2_subdev *sd,
> +                                   struct v4l2_subdev_frame_interval *fi)
> +{
> +       struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
> +
> +       mutex_lock(&sensor->lock);
> +       fi->interval = sensor->frame_interval;
> +       mutex_unlock(&sensor->lock);
> +
> +       return 0;
> +}
> +
> +static int internal_set_frame_interval(struct ovm6211_dev *sensor, struct
> +                                      v4l2_subdev_frame_interval *fi)
> +{
> +       u32 fr_rate;
> +       int i, ret = -EINVAL;
> +
> +       if (fi->interval.numerator == 0)
> +               goto out;
> +
> +       fr_rate = fi->interval.denominator / fi->interval.numerator;
> +
> +       for (i = 0; i < ARRAY_SIZE(ovm6211_framerates); i++) {
> +               if (ovm6211_framerates[i] == fr_rate)
> +                       break;
> +       }
> +
> +       if (i == ARRAY_SIZE(ovm6211_framerates))
> +               goto out;
> +
> +       sensor->cur_fr_id = i;
> +       sensor->frame_interval = fi->interval;
> +       ret = 0;
> +out:
> +       return ret;
> +}
> +
> +static int ovm6211_s_frame_interval(struct v4l2_subdev *sd,
> +                                   struct v4l2_subdev_frame_interval *fi)
> +{
> +       struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
> +       int ret;
> +
> +       mutex_lock(&sensor->lock);
> +       ret = internal_set_frame_interval(sensor, fi);
> +       sensor->pending_fi_change = true;
> +       mutex_unlock(&sensor->lock);
> +
> +       return ret;
> +}
> +
> +static int ovm6211_enum_mbus_code(struct v4l2_subdev *sd,
> +                                 struct v4l2_subdev_state *state,
> +                                 struct v4l2_subdev_mbus_code_enum *code)
> +{
> +       if (code->index >= ARRAY_SIZE(ovm6211_formats))
> +               return -EINVAL;
> +
> +       code->code = ovm6211_formats[code->index].code;
> +
> +       return 0;
> +}
> +
> +static int ovm6211_s_stream(struct v4l2_subdev *sd, int enable)
> +{
> +       struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
> +       int ret = 0;
> +
> +       mutex_lock(&sensor->lock);
> +
> +       if (enable)
> +               ret = ovm6211_set_mode(sensor);
> +       internal_set_stream(sensor, enable);
> +
> +       mutex_unlock(&sensor->lock);
> +
> +       return ret;
> +}
> +
> +static const struct v4l2_subdev_core_ops ovm6211_core_ops = {
> +#ifdef CONFIG_VIDEO_ADV_DEBUG
> +       .g_register = ovm6211_get_register,
> +       .s_register = ovm6211_set_register,
> +#endif
> +};
> +
> +static const struct v4l2_subdev_video_ops ovm6211_video_ops = {
> +       .g_frame_interval = ovm6211_g_frame_interval,
> +       .s_frame_interval = ovm6211_s_frame_interval,
> +       .s_stream = ovm6211_s_stream,
> +};
> +
> +static const struct v4l2_subdev_pad_ops ovm6211_pad_ops = {
> +       .enum_mbus_code = ovm6211_enum_mbus_code,
> +       .get_fmt = ovm6211_get_fmt,
> +       .set_fmt = ovm6211_set_fmt,
> +};
> +
> +static const struct v4l2_subdev_ops ovm6211_subdev_ops = {
> +       .core = &ovm6211_core_ops,
> +       .video = &ovm6211_video_ops,
> +       .pad = &ovm6211_pad_ops,
> +};
> +
> +static int ovm6211_get_regulators(struct ovm6211_dev *sensor)
> +{
> +       unsigned int i;
> +
> +       for (i = 0; i < ARRAY_SIZE(ovm6211_supply_name); i++)
> +               sensor->supplies[i].supply = ovm6211_supply_name[i];
> +
> +       return devm_regulator_bulk_get(&sensor->i2c_client->dev,
> +                                      ARRAY_SIZE(ovm6211_supply_name),
> +                                      sensor->supplies);
> +}
> +
> +static int ovm6211_check_chip_id(struct ovm6211_dev *sensor)
> +{
> +       struct i2c_client *client = sensor->i2c_client;
> +       struct regmap *regmap = sensor->regmap;
> +       unsigned int cid;
> +       int ret = 0;
> +
> +       ret = regmap_read(regmap, OVM6211_SC_CHIP_ID_HIGH, &cid);
> +       if (ret || cid != 0x67) {
> +               ret = ENXIO;
> +               goto err;
> +       }
> +
> +       ret = regmap_read(regmap, OVM6211_SC_CHIP_ID_LOW, &cid);
> +       if (ret || cid != 0x10) {
> +               ret = -ENXIO;
> +               goto err;
> +       }
> +
> +       ret = regmap_read(regmap, OVM6211_SC_REG0C, &cid);
> +       if (ret)
> +               goto err;
> +
> +       dev_info(&client->dev,"found OVM6211, sub revision: 0x%02X\n", cid);
> +       return 0;
> +err:
> +       dev_err(&client->dev, "failed to detect OVM6211\n");
> +       return ret;
> +}
> +
> +static int ovm6211_probe(struct i2c_client *client)
> +{
> +       struct device *dev = &client->dev;
> +       struct fwnode_handle *endpoint;
> +       struct ovm6211_dev *sensor;
> +       struct v4l2_mbus_framefmt *fmt;
> +       int ret;
> +
> +       sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
> +       if (!sensor)
> +               return -ENOMEM;
> +
> +       sensor->i2c_client = client;
> +
> +       fmt = &sensor->fmt;
> +       fmt->code = MEDIA_BUS_FMT_Y8_1X8;
> +       fmt->colorspace = V4L2_COLORSPACE_RAW;
> +       fmt->field = V4L2_FIELD_NONE;
> +       fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
> +       fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace);
> +       fmt->width = 400;
> +       fmt->height = 200;

Is the 400x200 mode just subsampling?

Should 400x200 be listed as a supported image size in your commit
message?


> +
> +       sensor->frame_interval.numerator = 1;
> +       sensor->frame_interval.denominator = ovm6211_framerates[OVM6211_45_FPS];
> +       sensor->cur_fr_id = OVM6211_45_FPS;
> +       sensor->cur_mode = &ovm6211_mode_data[OVM6211_MODE_Y8_400_200];
> +
> +       sensor->ep.bus_type = V4L2_MBUS_CSI2_DPHY;
> +       endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(&client->dev), NULL);
> +       if (!endpoint) {
> +               dev_err(dev, "endpoint node not found\n");
> +               return -EINVAL;
> +       }
> +
> +       ret = v4l2_fwnode_endpoint_parse(endpoint, &sensor->ep);
> +       fwnode_handle_put(endpoint);
> +       if (ret) {
> +               dev_err(dev, "Could not parse endpoint\n");
> +               return ret;
> +       }
> +
> +       /* request optional power down pin */
> +       sensor->pwdn_gpio = devm_gpiod_get_optional(dev, "powerdown",
> +                                                   GPIOD_OUT_HIGH);
> +       if (IS_ERR(sensor->pwdn_gpio))
> +               return PTR_ERR(sensor->pwdn_gpio);
> +
> +       /* request optional reset pin */
> +       sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset",
> +                                                    GPIOD_OUT_HIGH);
> +       if (IS_ERR(sensor->reset_gpio))
> +               return PTR_ERR(sensor->reset_gpio);
> +
> +       sensor->regmap = devm_regmap_init_i2c(client, &ovm6211_regmap_config);
> +       if (IS_ERR(sensor->regmap)) {
> +               dev_err(dev, "regmap init failed\n");
> +               return PTR_ERR(sensor->regmap);
> +       }
> +
> +       v4l2_i2c_subdev_init(&sensor->sd, client, &ovm6211_subdev_ops);
> +
> +       mutex_init(&sensor->lock);
> +
> +       sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
> +                           V4L2_SUBDEV_FL_HAS_EVENTS;
> +       sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
> +       sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
> +       ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
> +       if (ret)
> +               goto entity_cleanup;
> +
> +       ret = ovm6211_get_regulators(sensor);
> +       if (ret)
> +               goto entity_cleanup;
> +
> +       ret = ovm6211_check_chip_id(sensor);
> +       if (ret)
> +               goto entity_cleanup;
> +
> +       ret = ovm6211_init_controls(sensor);
> +       if (ret)
> +               goto entity_cleanup;
> +
> +       ovm6211_load_regs(sensor, sensor->cur_mode);
> +
> +       ret = v4l2_async_register_subdev_sensor(&sensor->sd);
> +       if (ret)
> +               goto free_ctrls;
> +
> +       return 0;
> +
> +free_ctrls:
> +       v4l2_ctrl_handler_free(&sensor->ctrls.handler);
> +entity_cleanup:
> +       media_entity_cleanup(&sensor->sd.entity);
> +       mutex_destroy(&sensor->lock);
> +
> +       return ret;
> +}
> +
> +static int ovm6211_remove(struct i2c_client *client)
> +{
> +       struct v4l2_subdev *sd = i2c_get_clientdata(client);
> +       struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
> +
> +       v4l2_async_unregister_subdev(&sensor->sd);
> +       media_entity_cleanup(&sensor->sd.entity);
> +       v4l2_ctrl_handler_free(&sensor->ctrls.handler);
> +       mutex_destroy(&sensor->lock);
> +
> +       return 0;
> +}
> +
> +static const struct i2c_device_id ovm6211_id[] = {
> +       { "ovm6211", 0 },
> +       {},
> +};
> +MODULE_DEVICE_TABLE(i2c, ovm6211_id);
> +
> +static const struct of_device_id ovm6211_dt_ids[] = {
> +       { .compatible = "ovti,ovm6211" },
> +       { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, ovm6211_dt_ids);
> +
> +static struct i2c_driver ovm6211_i2c_driver = {
> +       .driver = {
> +               .name  = "ovm6211",
> +               .of_match_table = ovm6211_dt_ids,
> +       },
> +       .probe_new = ovm6211_probe,
> +       .remove   = ovm6211_remove,
> +};
> +
> +module_i2c_driver(ovm6211_i2c_driver);
> +
> +MODULE_AUTHOR("Petko Manolov <petko.manolov@konsulko.com>");
> +MODULE_DESCRIPTION("OVM6211 MIPI Camera Subdev Driver");
> +MODULE_LICENSE("GPL");
> -- 
> 2.30.2
>
  
Dave Stevenson March 9, 2022, 12:56 p.m. UTC | #2
Hi Petko and Kieran

On Wed, 9 Mar 2022 at 11:51, Kieran Bingham
<kieran.bingham@ideasonboard.com> wrote:
>
> Hi Petko,
>
> Quoting Petko Manolov (2022-03-09 10:22:14)
> > OVM6211 is a black and white 160 kpixel (400x400) camera sensor from OmniVision.
> > It supports 400x400, 200x200 and 100x100 image sizes.  With careful register
>
> Looking below, I see you only have 400x400 and 400x200 modes
> implemented. It might be worth stating that here in the commit message.
> I started looking through here with the assumption you are supporting
> {400x400}, {200x200}, and {100x100}, but can see it's actually {400x400}
> and {400x200}.
>
>
> > programming it is possible to have non-square sizes as well.  The output formats
> > are 8/10-bit RAW, this driver is only using the former.  OVM6211 supports a
> > single lane MIPI transmitter interface with up to 600 Mbps data rate.
>
> Is it possible to implement the HBLANK/VBLANK controls on this sensor? I
> see some settings for frame intervals but not blanking. Perhaps the
> blanking is fixed? (I see it's a global shutter sensor, I'm not sure if
> that makes a difference here...)

The values being poked into OVM6211_TVTS_* in internal_set_stream look
like the normal VTS values of mode->height + V4L2_CID_VBLANK.

HTS is generally registers 0x380c & d on Omnivision sensors, so
V4L2_CID_HBLANK is HTS - mode->width.

> Also I see there is exposure control, but no gain. Should
> V4L2_CID_ANALOGUE_GAIN be implemented/supported?
>
> https://www.ovt.com/wp-content/uploads/2022/01/OVM6211-PB-v1.6-WEB.pdf
> shows at least that there is a gain control component.
>
>
> >
> > Signed-off-by: Petko Manolov <petko.manolov@konsulko.com>
> > ---
> >  drivers/media/i2c/Kconfig   |   10 +
> >  drivers/media/i2c/Makefile  |    1 +
> >  drivers/media/i2c/ovm6211.c | 1018 +++++++++++++++++++++++++++++++++++
> >  3 files changed, 1029 insertions(+)
> >  create mode 100644 drivers/media/i2c/ovm6211.c
> >
> > diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> > index fe66093b8849..aebe6a5f237e 100644
> > --- a/drivers/media/i2c/Kconfig
> > +++ b/drivers/media/i2c/Kconfig
> > @@ -1049,6 +1049,16 @@ config VIDEO_OV5648
> >           To compile this driver as a module, choose M here: the
> >           module will be called ov5648.
> >
> > +config VIDEO_OVM6211
> > +       tristate "OmniVision OVM6211 sensor support"
> > +       depends on I2C && VIDEO_V4L2
> > +       help
> > +         This is a Video4Linux2 sensor driver for the OmniVision
> > +         OVM6211 camera.
> > +
> > +         To compile this driver as a module, choose M here: the
> > +         module will be called ovm6211.
> > +
> >  config VIDEO_OV6650
> >         tristate "OmniVision OV6650 sensor support"
> >         depends on I2C && VIDEO_V4L2
> > diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> > index f6b80ef6f41d..9e9998966c18 100644
> > --- a/drivers/media/i2c/Makefile
> > +++ b/drivers/media/i2c/Makefile
> > @@ -78,6 +78,7 @@ obj-$(CONFIG_VIDEO_OV5670) += ov5670.o
> >  obj-$(CONFIG_VIDEO_OV5675) += ov5675.o
> >  obj-$(CONFIG_VIDEO_OV5693) += ov5693.o
> >  obj-$(CONFIG_VIDEO_OV5695) += ov5695.o
> > +obj-$(CONFIG_VIDEO_OVM6211) += ovm6211.o
> >  obj-$(CONFIG_VIDEO_OV6650) += ov6650.o
> >  obj-$(CONFIG_VIDEO_OV7251) += ov7251.o
> >  obj-$(CONFIG_VIDEO_OV7640) += ov7640.o
> > diff --git a/drivers/media/i2c/ovm6211.c b/drivers/media/i2c/ovm6211.c
> > new file mode 100644
> > index 000000000000..7cfcc0bf5e48
> > --- /dev/null
> > +++ b/drivers/media/i2c/ovm6211.c
> > @@ -0,0 +1,1018 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (C) 2021 Petko Manolov <petko.manolov@konsulko.com>
> > + * Copyright (C) 2021 DEKA Research & Development Corp.
> > + */
> > +
> > +#include <linux/delay.h>
> > +#include <linux/device.h>
> > +#include <linux/gpio/consumer.h>
> > +#include <linux/i2c.h>
> > +#include <linux/module.h>
> > +#include <linux/regmap.h>
> > +#include <linux/regulator/consumer.h>
> > +#include <linux/slab.h>
> > +#include <linux/types.h>
> > +#include <media/v4l2-async.h>
> > +#include <media/v4l2-ctrls.h>
> > +#include <media/v4l2-device.h>
> > +#include <media/v4l2-event.h>
> > +#include <media/v4l2-fwnode.h>
> > +#include <media/v4l2-subdev.h>
> > +
> > +/* System Control */
> > +#define OVM6211_SC_MODE_SELECT         0x0100
> > +#define OVM6211_SC_SOFTWARE_RESET      0x0103
> > +#define OVM6211_SC_SCCB_ID1            0x0109
> > +#define OVM6211_SC_CHIP_ID_HIGH                0x300a
> > +#define OVM6211_SC_CHIP_ID_LOW         0x300b
> > +#define OVM6211_SC_REG0C               0x300c
> > +#define OVM6211_SC_REG10               0x3010
> > +#define OVM6211_SC_MIPI_PHY            0x3012
> > +#define OVM6211_SC_MIPI_PHY2           0x3013
> > +#define OVM6211_SC_MIPI_SC_CTRL0       0x3014
> > +#define OVM6211_SC_MIPI_SC_CTRL1       0x3015
> > +#define OVM6211_SC_CLKRST0             0x3016
> > +#define OVM6211_SC_CLKRST1             0x3017
> > +#define OVM6211_SC_CLKRST2             0x3018
> > +#define OVM6211_SC_CLKRST3             0x3019
> > +#define OVM6211_SC_CLKRST4             0x301a
> > +#define OVM6211_SC_CLKRST5             0x301b
> > +#define OVM6211_SC_CLKRST6             0x301c
> > +#define OVM6211_SC_CLOCK_SEL           0x301e
> > +#define OVM6211_SC_MISC_CTRL           0x301f
> > +#define OVM6211_SC_LOW_PWR_CTRL                0x3023
> > +#define OVM6211_SC_REG27               0x3027
> > +#define OVM6211_SC_GP_IO_IN1           0x3029
> > +#define OVM6211_SC_GP_IO_IN2           0x302a
> > +#define OVM6211_SC_SCCB_ID2            0x302b
> > +#define OVM6211_SC_AUTO_SLEEP_PERIOD0  0x302c
> > +#define OVM6211_SC_AUTO_SLEEP_PERIOD1  0x302d
> > +#define OVM6211_SC_AUTO_SLEEP_PERIOD2  0x302e
> > +#define OVM6211_SC_AUTO_SLEEP_PERIOD3  0x302f
> > +#define OVM6211_SC_LP_CTRL0            0x3030
> > +#define OVM6211_SC_REG37               0x3037
> > +#define OVM6211_SC_REG3B               0x303b
> > +/* PLL Control */
> > +#define OVM6211_PLL_PLL0               0x3080
> > +#define OVM6211_PLL_PLL1               0x3081
> > +#define OVM6211_PLL_PLL2               0x3082
> > +#define OVM6211_PLL_PLL18              0x3098
> > +#define OVM6211_PLL_PLL19              0x3099
> > +#define OVM6211_PLL_PLL1A              0x309a
> > +#define OVM6211_PLL_PLL1B              0x309b
> > +#define OVM6211_PLL_PLL1C              0x309c
> > +#define OVM6211_PLL_PLL1D              0x309d
> > +#define OVM6211_PLL_PLL1E              0x309e
> > +#define OVM6211_PLL_PLL1F              0x309f
> > +#define OVM6211_PLL_VT_PIX_CLK_DIV     0x30b0
> > +#define OVM6211_PLL_VT_SYS_CLK_DIV     0x30b1
> > +#define OVM6211_PLL_MULTIPLIER         0x30b3
> > +#define OVM6211_PLL_PLL1_PRE_PLL_DIV   0x30b4
> > +#define OVM6211_PLL_PLL1_OP_PIX_CLK_DIV        0x30b5
> > +#define OVM6211_PLL_PLL1_OP_SYS_CLK_DIV        0x30b6
> > +/* SCCB and group hold */
> > +#define OVM6211_SB_SRB_CTRL            0x3106
> > +#define OVM6211_SB_SWITCH              0x31ff
> > +/* AEC/AGC */
> > +#define OVM6211_AEC_EXPO1              0x3500
> > +#define OVM6211_AEC_EXPO2              0x3501
> > +#define OVM6211_AEC_EXPO3              0x3502
> > +#define OVM6211_AEC_MANUAL             0x3503
> > +#define OVM6211_AGC_MAN_SNR_GAIN1      0x3504
> > +#define OVM6211_AGC_MAN_SNR_GAIN2      0x3505
> > +/* timing conrol registers */

s/conrol /control

> > +#define        OVM6211_TVTS_HI                 0x380e
> > +#define        OVM6211_TVTS_LO                 0x380f
> > +/*Strobe frame span registers*/
> > +#define OVM6211_STROBE_SPAN1           0x3b8d
> > +#define OVM6211_STROBE_SPAN2           0x3b8e
> > +#define OVM6211_STROBE_SPAN3           0x3b8f
> > +/* format control */
> > +#define OVM6211_DATA_MAX_H             0x4300
> > +#define OVM6211_DATA_MIN_H             0x4301
> > +#define OVM6211_CLIP_L                 0x4302
> > +#define OVM6211_FORMAT_CTRL3           0x4303
> > +#define OVM6211_FORMAT_CTRL4           0x4304
> > +#define OVM6211_VSYNC_WIDTH_H          0x4311
> > +#define OVM6211_VSYNC_WIDTH_L          0x4312
> > +#define OVM6211_VSYNC_CTRL             0x4313
> > +#define OVM6211_VSYNC_DELAY1           0x4314
> > +#define OVM6211_VSYNC_DELAY2           0x4315
> > +#define OVM6211_VSYNC_DELAY3           0x4316
> > +#define OVM6211_TST_PATTERN_CTRL       0x4320
> > +/* MIPI */
> > +#define OVM6211_MIPI_CTRL00            0x4800
> > +#define OVM6211_MIPI_CTRL01            0x4801
> > +#define OVM6211_MIPI_CTRL02            0x4802
> > +#define OVM6211_MIPI_CTRL03            0x4803
> > +#define OVM6211_MIPI_CTRL04            0x4804
> > +#define OVM6211_MIPI_CTRL05            0x4805
> > +#define OVM6211_MIPI_CTRL06            0x4806
> > +#define OVM6211_MIPI_MAX_FRAME_COUNT_H 0x4810
> > +#define OVM6211_MIPI_MAX_FRAME_COUNT_L 0x4811
> > +#define OVM6211_MIPI_SHORT_PKT_COUNTER_H       0x4812
> > +#define OVM6211_MIPI_SHORT_PKT_COUNTER_L       0x4813
> > +#define OVM6211_MIPI_CTRL14            0x4814
> > +#define OVM6211_MIPI_DT_SPKT           0x4815
> > +#define OVM6211_MIPI_REG_MAX_H         0x4835
> > +#define OVM6211_MIPI_REG_MAX_L         0x4836
> > +#define OVM6211_PCLK_PERIOD            0x4837
> > +#define OVM6211_WKUP_DLY               0x4838
> > +#define OVM6211_DIR_DLY                        0x483a
> > +#define OVM6211_MIPI_LP_GPIO           0x483b
> > +#define OVM6211_MIPI_CTRL3C            0x483c
> > +#define OVM6211_T_TA_GO                        0x483d
> > +#define OVM6211_T_TA_SURE              0x483e
> > +#define OVM6211_T_TA_GET               0x483f
> > +#define OVM6211_MIPI_CLIP_MAX_H                0x4846
> > +#define OVM6211_MIPI_CLIP_MAX_L                0x4847
> > +#define OVM6211_MIPI_CLIP_MIN_H                0x4848
> > +#define OVM6211_MIPI_CLIP_MIN_L                0x4848
> > +#define OVM6211_REG_INTR_MAN           0x4850
> > +#define OVM6211_REG_TX_WR              0x4851
> > +/* ISP top */
> > +#define OVM6211_ISP_CTRL00             0x5000
> > +#define OVM6211_ISP_CTRL01             0x5001
> > +#define OVM6211_ISP_CTRL02             0x5002
> > +#define OVM6211_ISP_CTRL03             0x5003
> > +#define OVM6211_ISP_CTRL04             0x5004
> > +#define OVM6211_ISP_CTRL05             0x5005
> > +#define OVM6211_ISP_CTRL06             0x5006
> > +#define OVM6211_ISP_CTRL07             0x5007
> > +#define OVM6211_ISP_CTRL08             0x5008
> > +#define OVM6211_ISP_CTRL09             0x5009
> > +/* window control */
> > +#define OVM6211_MAN_XSTART_OFF_H       0x5a00
> > +#define OVM6211_MAN_XSTART_OFF_L       0x5a01
> > +#define OVM6211_MAN_YSTART_OFF_H       0x5a02
> > +#define OVM6211_MAN_YSTART_OFF_L       0x5a03
> > +#define OVM6211_MAN_WIN_WIDTH_H                0x5a04
> > +#define OVM6211_MAN_WIN_WIDTH_L                0x5a05
> > +#define OVM6211_MAN_WIN_HEIGHT_H       0x5a06
> > +#define OVM6211_MAN_WIN_HEIGHT_L       0x5a07
> > +#define OVM6211_WIN_MAN                        0x5a08
> > +
> > +#define        OVM6211_LAST_REG                0x5e08
> > +/* end of OVM6211 definitions */
> > +
> > +#define        DEF_LINK_FREQ                   38400000LL
> > +
> > +enum ovm6211_mode_id {
> > +       OVM6211_MODE_Y8_400_200 = 0,
> > +       OVM6211_MODE_Y8_400_400,
> > +       OVM6211_NUM_MODES,
> > +};
> > +
> > +struct ovm6211_pixfmt {
> > +       u32 code;
> > +       u32 colorspace;
> > +};
> > +
> > +static const struct ovm6211_pixfmt ovm6211_formats[] = {
> > +       { MEDIA_BUS_FMT_Y8_1X8,   V4L2_COLORSPACE_RAW, },
> > +       { MEDIA_BUS_FMT_Y8_1X8,   V4L2_COLORSPACE_RAW, },
>
> Is one entry enough here?
>
> The color space isn't even used is it?
> It could probably be hardcoded in ovm6211_enum_mbus_code(), and then get
> rid of struct ovm6211_pixfmt too?
>
> Unless you expect other bus formats or colorspace to be supported?
>
>
> > +};
> > +
> > +enum ovm6211_framerate_ids {
> > +       OVM6211_10_FPS = 0,
> > +       OVM6211_15_FPS,
> > +       OVM6211_30_FPS,
> > +       OVM6211_45_FPS,
> > +       OVM6211_60_FPS,
> > +       OVM6211_NUM_FRAMERATES,
> > +};
> > +
> > +static const int ovm6211_framerates[] = {
> > +       [OVM6211_10_FPS] = 10,
> > +       [OVM6211_15_FPS] = 15,
> > +       [OVM6211_30_FPS] = 30,
> > +       [OVM6211_45_FPS] = 45,
> > +       [OVM6211_60_FPS] = 60,
> > +};
> > +
> > +/* regulator supplies */
> > +static const char * const ovm6211_supply_name[] = {
> > +       "dovdd",
> > +       "avdd",
> > +};
> > +
> > +static const struct regmap_config ovm6211_regmap_config = {
> > +       .reg_bits       = 16,
> > +       .val_bits       = 8,
> > +       .max_register   = OVM6211_LAST_REG,
> > +       .cache_type     = REGCACHE_NONE,
> > +};
> > +
> > +struct reg_value {
> > +       u16 reg_addr;
> > +       u8 val;
> > +       u8 mask;
> > +       u32 delay_ms;
> > +};
> > +
> > +struct ovm6211_mode_info {
> > +       enum ovm6211_mode_id id;
> > +       u32 width;
> > +       u32 height;
> > +       const struct reg_value *reg_data;
> > +       u32 reg_data_size;
> > +       u32 pixel_clock;
> > +};
> > +
> > +struct ovm6211_ctrls {
> > +       struct v4l2_ctrl_handler handler;
> > +       struct {
> > +               struct v4l2_ctrl *auto_exp;
> > +               struct v4l2_ctrl *exposure;
> > +       };
> > +       struct {
> > +               struct v4l2_ctrl *auto_gain;
> > +               struct v4l2_ctrl *gain;
> > +       };
> > +       struct v4l2_ctrl *link_freq;
> > +};
> > +
> > +struct ovm6211_dev {
> > +       struct i2c_client *i2c_client;
> > +       struct regmap *regmap;
> > +       struct v4l2_subdev sd;
> > +       struct media_pad pad;
> > +       struct v4l2_fwnode_endpoint ep; /* the parsed DT endpoint info */
> > +
> > +       struct regulator_bulk_data supplies[ARRAY_SIZE(ovm6211_supply_name)];
> > +       struct gpio_desc *reset_gpio;
> > +       struct gpio_desc *pwdn_gpio;
> > +
> > +       struct mutex lock;
> > +
> > +       struct v4l2_mbus_framefmt fmt;
> > +
> > +       const struct ovm6211_mode_info *cur_mode;
> > +       enum ovm6211_framerate_ids cur_fr_id;
> > +       struct v4l2_fract frame_interval;
> > +
> > +       struct ovm6211_ctrls ctrls;
> > +
> > +       u32 exposure;
> > +       bool pending_mode_change;
> > +       bool pending_fi_change;
> > +       bool streaming;
> > +};
> > +
> > +static inline struct ovm6211_dev *to_ovm6211_dev(struct v4l2_subdev *sd)
> > +{
> > +       return container_of(sd, struct ovm6211_dev, sd);
> > +}
> > +
> > +static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl)
> > +{
> > +       return &container_of(ctrl->handler, struct ovm6211_dev,
> > +                            ctrls.handler)->sd;
> > +}
> > +
> > +static const struct reg_value ovm6211_init_y8_400_400[] = {
> > +       {0x0103, 0x01, 0, 0}, {0x0100, 0x00, 0, 0}, {0x3005, 0x08, 0, 0},
> > +       {0x3013, 0x12, 0, 0}, {0x3014, 0x04, 0, 0}, {0x3016, 0x10, 0, 0},
> > +       {0x3017, 0x00, 0, 0}, {0x3018, 0x00, 0, 0}, {0x301a, 0x00, 0, 0},
> > +       {0x301b, 0x00, 0, 0}, {0x301c, 0x00, 0, 0}, {0x3037, 0xf0, 0, 0},
> > +       {0x3080, 0x01, 0, 0}, {0x3081, 0x00, 0, 0}, {0x3082, 0x01, 0, 0},
> > +       {0x3098, 0x04, 0, 0}, {0x3099, 0x28, 0, 0}, {0x309a, 0x06, 0, 0},
> > +       {0x309b, 0x04, 0, 0}, {0x309c, 0x00, 0, 0}, {0x309d, 0x00, 0, 0},
> > +       {0x309e, 0x01, 0, 0}, {0x309f, 0x00, 0, 0}, {0x30b0, 0x08, 0, 0},
> > +       {0x30b1, 0x02, 0, 0}, {0x30b2, 0x00, 0, 0}, {0x30b3, 0x28, 0, 0},
> > +       {0x30b4, 0x02, 0, 0}, {0x30b5, 0x00, 0, 0}, {0x3106, 0xd9, 0, 0},
> > +       {0x3500, 0x00, 0, 0}, {0x3501, 0x1b, 0, 0}, {0x3502, 0x20, 0, 0},
> > +       {0x3503, 0x07, 0, 0}, {0x3509, 0x10, 0, 0}, {0x350b, 0x10, 0, 0},
> > +       {0x3600, 0xfc, 0, 0}, {0x3620, 0xb7, 0, 0}, {0x3621, 0x05, 0, 0},
> > +       {0x3626, 0x31, 0, 0}, {0x3627, 0x40, 0, 0}, {0x3632, 0xa3, 0, 0},
> > +       {0x3633, 0x34, 0, 0}, {0x3634, 0x40, 0, 0}, {0x3636, 0x00, 0, 0},
> > +       {0x3660, 0x80, 0, 0}, {0x3662, 0x03, 0, 0}, {0x3664, 0xf0, 0, 0},
> > +       {0x366a, 0x10, 0, 0}, {0x366b, 0x06, 0, 0}, {0x3680, 0xf4, 0, 0},
> > +       {0x3681, 0x50, 0, 0}, {0x3682, 0x00, 0, 0}, {0x3708, 0x20, 0, 0},
> > +       {0x3709, 0x40, 0, 0}, {0x370d, 0x03, 0, 0}, {0x373b, 0x02, 0, 0},
> > +       {0x373c, 0x08, 0, 0}, {0x3742, 0x00, 0, 0}, {0x3744, 0x16, 0, 0},
> > +       {0x3745, 0x08, 0, 0}, {0x3781, 0xfc, 0, 0}, {0x3788, 0x00, 0, 0},
> > +       {0x3800, 0x00, 0, 0}, {0x3801, 0x04, 0, 0}, {0x3802, 0x00, 0, 0},
> > +       {0x3803, 0x04, 0, 0}, {0x3804, 0x01, 0, 0}, {0x3805, 0x9b, 0, 0},
> > +       {0x3806, 0x01, 0, 0}, {0x3807, 0x9b, 0, 0}, {0x3808, 0x01, 0, 0},
> > +       {0x3809, 0x90, 0, 0}, {0x380a, 0x01, 0, 0}, {0x380b, 0x90, 0, 0},
> > +       {0x380c, 0x05, 0, 0}, {0x380d, 0xf2, 0, 0}, {0x380e, 0x03, 0, 0},
> > +       {0x380f, 0x6c, 0, 0}, {0x3810, 0x00, 0, 0}, {0x3811, 0x04, 0, 0},

No point in having 0x380e & 0x380f in these tables if you're going to
overwrite it based on frame rate.

> > +       {0x3812, 0x00, 0, 0}, {0x3813, 0x04, 0, 0}, {0x3814, 0x11, 0, 0},
> > +       {0x3815, 0x11, 0, 0}, {0x3820, 0x00, 0, 0}, {0x3821, 0x00, 0, 0},
> > +       {0x382b, 0xfa, 0, 0}, {0x382f, 0x04, 0, 0}, {0x3832, 0x00, 0, 0},
> > +       {0x3833, 0x05, 0, 0}, {0x3834, 0x00, 0, 0}, {0x3835, 0x05, 0, 0},
> > +       {0x3882, 0x04, 0, 0}, {0x3883, 0x00, 0, 0}, {0x38a4, 0x10, 0, 0},
> > +       {0x38a5, 0x00, 0, 0}, {0x38b1, 0x03, 0, 0}, {0x3b80, 0x00, 0, 0},
> > +       {0x3b81, 0xff, 0, 0}, {0x3b82, 0x10, 0, 0}, {0x3b83, 0x00, 0, 0},
> > +       {0x3b84, 0x08, 0, 0}, {0x3b85, 0x00, 0, 0}, {0x3b86, 0x01, 0, 0},
> > +       {0x3b87, 0x00, 0, 0}, {0x3b88, 0x00, 0, 0}, {0x3b89, 0x00, 0, 0},
> > +       {0x3b8a, 0x00, 0, 0}, {0x3b8b, 0x05, 0, 0}, {0x3b8c, 0x00, 0, 0},
> > +       {0x3b8d, 0x00, 0, 0}, {0x3b8e, 0x01, 0, 0}, {0x3b8f, 0xb2, 0, 0},
> > +       {0x3b94, 0x05, 0, 0}, {0x3b95, 0xf2, 0, 0}, {0x3b96, 0xc0, 0, 0},
> > +       {0x4004, 0x04, 0, 0}, {0x404e, 0x01, 0, 0}, {0x4801, 0x0f, 0, 0},
> > +       {0x4806, 0x0f, 0, 0}, {0x4837, 0x43, 0, 0}, {0x5a08, 0x00, 0, 0},
> > +       {0x5a01, 0x00, 0, 0}, {0x5a03, 0x00, 0, 0}, {0x5a04, 0x10, 0, 0},
> > +       {0x5a05, 0xa0, 0, 0}, {0x5a06, 0x0c, 0, 0}, {0x5a07, 0x78, 0, 0},
> > +};
> > +
> > +static const struct reg_value ovm6211_init_y8_400_200[] = {
> > +       {0x0103, 0x01, 0, 0}, {0x0100, 0x00, 0, 0}, {0x3005, 0x08, 0, 0},
> > +       {0x3013, 0x12, 0, 0}, {0x3014, 0x04, 0, 0}, {0x3016, 0x10, 0, 0},
> > +       {0x3017, 0x00, 0, 0}, {0x3018, 0x00, 0, 0}, {0x301a, 0x00, 0, 0},
> > +       {0x301b, 0x00, 0, 0}, {0x301c, 0x00, 0, 0}, {0x3037, 0xf0, 0, 0},
> > +       {0x3080, 0x01, 0, 0}, {0x3081, 0x00, 0, 0}, {0x3082, 0x01, 0, 0},
> > +       {0x3098, 0x04, 0, 0}, {0x3099, 0x28, 0, 0}, {0x309a, 0x06, 0, 0},
> > +       {0x309b, 0x04, 0, 0}, {0x309c, 0x00, 0, 0}, {0x309d, 0x00, 0, 0},
> > +       {0x309e, 0x01, 0, 0}, {0x309f, 0x00, 0, 0}, {0x30b0, 0x08, 0, 0},
> > +       {0x30b1, 0x02, 0, 0}, {0x30b2, 0x00, 0, 0}, {0x30b3, 0x28, 0, 0},
> > +       {0x30b4, 0x02, 0, 0}, {0x30b5, 0x00, 0, 0}, {0x3106, 0xd9, 0, 0},
> > +       {0x3500, 0x00, 0, 0}, {0x3501, 0x1b, 0, 0}, {0x3502, 0x20, 0, 0},
> > +       {0x3503, 0x07, 0, 0}, {0x3509, 0x10, 0, 0}, {0x350b, 0x10, 0, 0},
> > +       {0x3600, 0xfc, 0, 0}, {0x3620, 0xb7, 0, 0}, {0x3621, 0x05, 0, 0},
> > +       {0x3626, 0x31, 0, 0}, {0x3627, 0x40, 0, 0}, {0x3632, 0xa3, 0, 0},
> > +       {0x3633, 0x34, 0, 0}, {0x3634, 0x40, 0, 0}, {0x3636, 0x00, 0, 0},
> > +       {0x3660, 0x80, 0, 0}, {0x3662, 0x03, 0, 0}, {0x3664, 0xf0, 0, 0},
> > +       {0x366a, 0x10, 0, 0}, {0x366b, 0x06, 0, 0}, {0x3680, 0xf4, 0, 0},
> > +       {0x3681, 0x50, 0, 0}, {0x3682, 0x00, 0, 0}, {0x3708, 0x20, 0, 0},
> > +       {0x3709, 0x40, 0, 0}, {0x370d, 0x03, 0, 0}, {0x373b, 0x02, 0, 0},
> > +       {0x373c, 0x08, 0, 0}, {0x3742, 0x00, 0, 0}, {0x3744, 0x16, 0, 0},
> > +       {0x3745, 0x08, 0, 0}, {0x3781, 0xfc, 0, 0}, {0x3788, 0x00, 0, 0},
> > +       {0x3800, 0x00, 0, 0}, {0x3801, 0x04, 0, 0}, {0x3802, 0x00, 0, 0},
> > +       {0x3803, 0x04, 0, 0}, {0x3804, 0x01, 0, 0}, {0x3805, 0x9b, 0, 0},
> > +       {0x3806, 0x01, 0, 0}, {0x3807, 0x9b, 0, 0}, {0x3808, 0x01, 0, 0},
> > +       {0x3809, 0x90, 0, 0}, {0x380a, 0x00, 0, 0}, {0x380b, 0xc8, 0, 0},
> > +       {0x380c, 0x05, 0, 0}, {0x380d, 0xf2, 0, 0}, {0x380e, 0x0d, 0, 0},
> > +       {0x380f, 0xb0, 0, 0}, {0x3810, 0x00, 0, 0}, {0x3811, 0x04, 0, 0},
> > +       {0x3812, 0x00, 0, 0}, {0x3813, 0x9a, 0, 0}, {0x3814, 0x11, 0, 0},
> > +       {0x3815, 0x11, 0, 0}, {0x3820, 0x00, 0, 0}, {0x3821, 0x00, 0, 0},
> > +       {0x382b, 0xfa, 0, 0}, {0x382f, 0x04, 0, 0}, {0x3832, 0x00, 0, 0},
> > +       {0x3833, 0x05, 0, 0}, {0x3834, 0x00, 0, 0}, {0x3835, 0x05, 0, 0},
> > +       {0x3882, 0x04, 0, 0}, {0x3883, 0x00, 0, 0}, {0x38a4, 0x10, 0, 0},
> > +       {0x38a5, 0x00, 0, 0}, {0x38b1, 0x03, 0, 0}, {0x3b80, 0x00, 0, 0},
> > +       {0x3b81, 0xff, 0, 0}, {0x3b82, 0x10, 0, 0}, {0x3b83, 0x00, 0, 0},
> > +       {0x3b84, 0x08, 0, 0}, {0x3b85, 0x00, 0, 0}, {0x3b86, 0x01, 0, 0},
> > +       {0x3b87, 0x00, 0, 0}, {0x3b88, 0x00, 0, 0}, {0x3b89, 0x00, 0, 0},
> > +       {0x3b8a, 0x00, 0, 0}, {0x3b8b, 0x05, 0, 0}, {0x3b8c, 0x00, 0, 0},
> > +       {0x3b8d, 0x00, 0, 0}, {0x3b8e, 0x01, 0, 0}, {0x3b8f, 0xb2, 0, 0},
> > +       {0x3b94, 0x05, 0, 0}, {0x3b95, 0xf2, 0, 0}, {0x3b96, 0xc0, 0, 0},
> > +       {0x4004, 0x04, 0, 0}, {0x404e, 0x01, 0, 0}, {0x4801, 0x0f, 0, 0},
> > +       {0x4806, 0x0f, 0, 0}, {0x4837, 0x43, 0, 0}, {0x5a08, 0x00, 0, 0},
> > +       {0x5a01, 0x00, 0, 0}, {0x5a03, 0x00, 0, 0}, {0x5a04, 0x10, 0, 0},
> > +       {0x5a05, 0xa0, 0, 0}, {0x5a06, 0x0c, 0, 0}, {0x5a07, 0x78, 0, 0},
> > +};

These two tables are identical except for registers 0x380a, 0x380b,
0x380f, and 0x3813. More commonly you'd have a common table, and then
the diffs for specific modes.

Also the mask and delay fields are always 0, therefore they seem redundant.

> > +static struct ovm6211_mode_info
> > +ovm6211_mode_data[OVM6211_NUM_MODES] = {
> > +       {OVM6211_MODE_Y8_400_200,
> > +        400, 200,
> > +        ovm6211_init_y8_400_200,
> > +        ARRAY_SIZE(ovm6211_init_y8_400_200),
> > +        400 * 400 * 60 * 2,
> > +       },
> > +       {OVM6211_MODE_Y8_400_400,
> > +        400, 400,
> > +        ovm6211_init_y8_400_400,
> > +        ARRAY_SIZE(ovm6211_init_y8_400_400),
> > +        400 * 400 * 60 * 2,
> > +       },
> > +};
> > +
> > +static const s64 link_freq_menu_items[] = {
> > +       DEF_LINK_FREQ,
> > +};
> > +
> > +#ifdef CONFIG_VIDEO_ADV_DEBUG
> > +static int ovm6211_get_register(struct v4l2_subdev *sd,
> > +                               struct v4l2_dbg_register *reg)
> > +{
> > +       struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
> > +       struct regmap *regmap = sensor->regmap;
> > +       unsigned int val = 0;
> > +       int ret;
> > +
> > +       ret = regmap_read(regmap, reg->reg, &val);
> > +       reg->val = val;
> > +       reg->size = 1;
> > +
> > +       return ret;
> > +}
> > +
> > +static int ovm6211_set_register(struct v4l2_subdev *sd,
> > +                               const struct v4l2_dbg_register *reg)
> > +{
> > +       struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
> > +       struct regmap *regmap = sensor->regmap;
> > +
> > +       return regmap_write(regmap, reg->reg, reg->val & 0xff);
> > +}
> > +#endif
> > +
> > +static int ovm6211_write_reg(struct ovm6211_dev *sensor, u16 reg, u8 val)
> > +{
> > +       struct i2c_client *client = sensor->i2c_client;
> > +       struct regmap *regmap = sensor->regmap;
> > +       int ret;
> > +
> > +       ret = regmap_write(regmap, reg, val);
> > +       if (ret < 0)
> > +               dev_err(&client->dev, "error writing reg %u\n", reg);
> > +
> > +       return ret;
> > +}
> > +
> > +static int ovm6211_read_reg(struct ovm6211_dev *sensor, u16 reg, u8 *val)
> > +{
> > +       struct i2c_client *client = sensor->i2c_client;
> > +       struct regmap *regmap = sensor->regmap;
> > +       unsigned int r;
> > +       int ret;
> > +
> > +       ret = regmap_read(regmap, reg, &r);
> > +       if (ret < 0)
> > +               dev_err(&client->dev, "error reading reg %u\n", reg);
> > +       *val = r & 0xff;
> > +
> > +       return ret;
> > +}
> > +
> > +static int ovm6211_mod_reg(struct ovm6211_dev *sensor, u16 reg, u8 mask, u8 val)
> > +{
> > +       u8 readval;
> > +       int ret;
> > +
> > +       ret = ovm6211_read_reg(sensor, reg, &readval);
> > +       if (ret)
> > +               return ret;
> > +
> > +       readval &= ~mask;
> > +       val &= mask;
> > +       val |= readval;
> > +
> > +       return ovm6211_write_reg(sensor, reg, val);
> > +}
> > +
> > +static int ovm6211_load_regs(struct ovm6211_dev *sensor,
> > +                            const struct ovm6211_mode_info *mode)
> > +{
> > +       const struct reg_value *regs = mode->reg_data;
> > +       unsigned int i;
> > +       u32 delay_ms;
> > +       u16 reg_addr;
> > +       u8 mask, val;
> > +       int ret = 0;
> > +
> > +       for (i = 0; i < mode->reg_data_size; ++i, ++regs) {
> > +               delay_ms = regs->delay_ms;
> > +               reg_addr = regs->reg_addr;
> > +               val = regs->val;
> > +               mask = regs->mask;
> > +
> > +               if (mask)
> > +                       ret = ovm6211_mod_reg(sensor, reg_addr, mask, val);
> > +               else
> > +                       ret = ovm6211_write_reg(sensor, reg_addr, val);
> > +
> > +               if (ret)
> > +                       break;
> > +
> > +               if (delay_ms)
> > +                       usleep_range(1000 * delay_ms, 1000 * delay_ms + 100);
> > +       }
> > +
> > +       return ret;
> > +}
> > +
> > +static void ovm6211_soft_reset(struct ovm6211_dev *sensor)
> > +{
> > +       ovm6211_write_reg(sensor, OVM6211_SC_SOFTWARE_RESET, 0x01);
> > +       usleep_range(5000, 9000);
> > +       ovm6211_write_reg(sensor, OVM6211_SC_SOFTWARE_RESET, 0x00);
> > +}
> > +
> > +static int ovm6211_set_exposure(struct ovm6211_dev *sensor, u32 exposure)
> > +{
> > +       u32 ce;
> > +       u8 v;
> > +
> > +       ovm6211_read_reg(sensor, OVM6211_TVTS_HI, &v);
> > +       ce = v << 8;
> > +       ovm6211_read_reg(sensor, OVM6211_TVTS_LO, &v);
> > +       ce |= v;
> > +       ce -= 4;

This clipping of exposure should be done as part of the support for
VBLANK, setting the range of the exposure control to be between
whatever the minimum is and TVTS - 4.

> > +       if (ce < exposure)
> > +               exposure = ce;
> > +
> > +       ovm6211_mod_reg(sensor, OVM6211_AEC_MANUAL, 1, 1);
> > +
> > +       ovm6211_write_reg(sensor, OVM6211_AEC_EXPO1, (exposure >> 12) & 0x0f);
> > +       ovm6211_write_reg(sensor, OVM6211_AEC_EXPO2, (exposure >>  4) & 0xff);
> > +       ovm6211_write_reg(sensor, OVM6211_AEC_EXPO3, (exposure <<  4) & 0xf0);
> > +
> > +       /* set strobe width equal to exposure time */
> > +       ovm6211_write_reg(sensor, OVM6211_STROBE_SPAN1,
> > +                         (exposure >> 16) & 0xff);
> > +       ovm6211_write_reg(sensor, OVM6211_STROBE_SPAN2,
> > +                         (exposure >> 8) & 0xff);
> > +       ovm6211_write_reg(sensor, OVM6211_STROBE_SPAN3, (exposure) & 0xff);
> > +
> > +       return 0;
> > +}
> > +
> > +static int internal_set_stream(struct ovm6211_dev *sensor, bool on)
> > +{
> > +       u8 hi, lo;
> > +
> > +       if (sensor->pending_fi_change == false)
> > +               goto stream;
> > +
> > +       switch (sensor->cur_fr_id) {
> > +       case OVM6211_10_FPS:
> > +               hi = 0x14;
> > +               lo = 0x88;
> > +               break;
> > +       case OVM6211_15_FPS:
> > +               hi = 0x0d;
> > +               lo = 0xb0;
> > +               break;
> > +       case OVM6211_30_FPS:
> > +               hi = 0x06;
> > +               lo = 0xd8;
> > +               break;
> > +       case OVM6211_45_FPS:
> > +               hi = 0x04;
> > +               lo = 0x90;
> > +               break;
> > +       case OVM6211_60_FPS:
> > +               hi = 0x03;
> > +               lo = 0x6c;
> > +               break;
> > +       case OVM6211_NUM_FRAMERATES:
> > +               return -EINVAL;
> > +       }
> > +
> > +       sensor->pending_fi_change = false;
> > +       ovm6211_write_reg(sensor, OVM6211_TVTS_HI, hi);
> > +       ovm6211_write_reg(sensor, OVM6211_TVTS_LO, lo);
> > +stream:
> > +       ovm6211_write_reg(sensor, OVM6211_SC_MODE_SELECT, 0);
> > +       if (on) {
> > +               usleep_range(4000, 5000);
> > +               if (sensor->exposure)
> > +                       ovm6211_set_exposure(sensor, sensor->exposure);
> > +               ovm6211_write_reg(sensor, OVM6211_SC_MODE_SELECT, 1);
> > +               sensor->streaming = true;
> > +       } else {
> > +               sensor->streaming = false;
> > +       }
> > +
> > +       return 0;
> > +}
> > +
> > +static struct ovm6211_mode_info *ovm6211_find_mode(int w, int h)
> > +{
> > +       return v4l2_find_nearest_size(ovm6211_mode_data,
> > +                                     ARRAY_SIZE(ovm6211_mode_data),
> > +                                     width, height, w, h);
> > +}
> > +
> > +static int ovm6211_set_mode(struct ovm6211_dev *sensor)
> > +{
> > +       const struct ovm6211_mode_info *mode = sensor->cur_mode;
> > +
> > +       ovm6211_soft_reset(sensor);
> > +       ovm6211_load_regs(sensor, mode);

None of your v4l2_ctrl settings will be valid now as you've done a reset.
Use v4l2_ctrl_handler_setup to call s_ctrl on all the defined controls
to restore their current values.

> > +
> > +       return 0;
> > +}
> > +
> > +/* --------------- Subdev Operations --------------- */
> > +
> > +static int ovm6211_get_fmt(struct v4l2_subdev *sd,
> > +                          struct v4l2_subdev_state *state,
> > +                          struct v4l2_subdev_format *format)
> > +{
> > +       struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
> > +       struct v4l2_mbus_framefmt *fmt;
> > +
> > +       mutex_lock(&sensor->lock);
> > +
> > +       if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
> > +               fmt = v4l2_subdev_get_try_format(&sensor->sd, state, format->pad);
> > +               format->format = *fmt;
> > +       } else {
> > +               /* these are hardcoded as we don't support anything else */
> > +               format->format.colorspace  = V4L2_COLORSPACE_RAW;
> > +               format->format.field = V4L2_FIELD_NONE;
> > +               format->format.code = MEDIA_BUS_FMT_Y8_1X8;
> > +               format->format.width = sensor->cur_mode->width;
> > +               format->format.height = sensor->cur_mode->height;
> > +       }
> > +
> > +       mutex_unlock(&sensor->lock);
> > +
> > +       return 0;
> > +}
> > +
> > +static int ovm6211_set_fmt(struct v4l2_subdev *sd,
> > +                          struct v4l2_subdev_state *state,
> > +                          struct v4l2_subdev_format *format)
> > +{
> > +       struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
> > +       struct v4l2_mbus_framefmt *fmt = &format->format;
> > +       struct ovm6211_mode_info *mode;
> > +
> > +       mutex_lock(&sensor->lock);
> > +
> > +       if (sensor->streaming)
> > +               return -EBUSY;
> > +
> > +       /* these are hardcoded as we don't support anything else */
> > +       format->format.colorspace  = V4L2_COLORSPACE_RAW;
> > +       format->format.field = V4L2_FIELD_NONE;
> > +       format->format.code = MEDIA_BUS_FMT_Y8_1X8;
> > +       mode = ovm6211_find_mode(format->format.width, format->format.height);
> > +       format->format.width = mode->width;
> > +       format->format.height = mode->height;
> > +
> > +       if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
> > +               fmt = v4l2_subdev_get_try_format(&sensor->sd, state, format->pad);
> > +               *fmt = format->format;
> > +               goto out;
> > +       }
> > +       sensor->cur_mode = mode;
> > +out:
> > +       mutex_unlock(&sensor->lock);
> > +
> > +       return 0;
> > +}
> > +
> > +/*
> > + * Sensor Controls.
> > + */
> > +
> > +static int ovm6211_set_ctrl_exposure(struct ovm6211_dev *sensor, enum
> > +                                    v4l2_exposure_auto_type auto_exposure)
> > +{
> > +       struct ovm6211_ctrls *ctrls = &sensor->ctrls;
> > +
> > +       if (auto_exposure == V4L2_EXPOSURE_AUTO) {
> > +               sensor->exposure = 0;
> > +       } else {
> > +               sensor->exposure = ctrls->exposure->val;
> > +               ovm6211_set_exposure(sensor, sensor->exposure);
> > +       }
> > +
> > +       return 0;
> > +}
> > +
> > +static int ovm6211_s_ctrl(struct v4l2_ctrl *ctrl)
> > +{
> > +       struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
> > +       struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
> > +       int ret;
> > +
> > +       switch (ctrl->id) {
> > +       case V4L2_CID_EXPOSURE_AUTO:
> > +               ret = ovm6211_set_ctrl_exposure(sensor, ctrl->val);
> > +               break;
> > +       case V4L2_CID_LINK_FREQ:
>
> Is this actually adjustable? Or should LINK_FREQ always return DEF_LINK_FREQ?
>
> Aha perhaps it does because it's a menu entry with only one entry ...

More normally LINK_FREQ is a read only control, in which case there is
no s_ctrl handler required.

> > +               return 0;
> > +       case V4L2_CID_PIXEL_RATE:
>
> Is it valid to just openly accept any LINK_FREQ/PIXEL_RATE?

Seeing as there is no call to v4l2_ctrl_new_std for
V4L2_CID_PIXEL_RATE, this would never be called.

> > +               return 0;
> > +       default:

No case for V4L2_CID_EXPOSURE, so trying to set that would fail.

> > +               ret = -EINVAL;
> > +               break;
> > +       }
> > +
> > +       return ret;
> > +}
> > +
> > +static const struct v4l2_ctrl_ops ovm6211_ctrl_ops = {
> > +       .s_ctrl = ovm6211_s_ctrl,
> > +};
> > +
> > +static int ovm6211_init_controls(struct ovm6211_dev *sensor)
> > +{
> > +       const struct v4l2_ctrl_ops *ops = &ovm6211_ctrl_ops;
> > +       struct ovm6211_ctrls *ctrls = &sensor->ctrls;
> > +       struct v4l2_ctrl_handler *hdl = &ctrls->handler;
> > +       int ret;
> > +
> > +       v4l2_ctrl_handler_init(hdl, 16);
> > +
> > +       hdl->lock = &sensor->lock;
> > +
> > +       ctrls->auto_exp = v4l2_ctrl_new_std_menu(hdl, ops, V4L2_CID_EXPOSURE_AUTO,
> > +                                                V4L2_EXPOSURE_MANUAL, 0,
> > +                                                V4L2_EXPOSURE_AUTO);
> > +       ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE,
> > +                                           0, 65535, 1, 0);
> > +       v4l2_ctrl_auto_cluster(3, &ctrls->auto_exp, 0, false);

v4l2_ctrl_auto_cluster is meant to take an array of controls. You've
passed in a pointer to the first entry in struct ovm6211_ctrls. Whilst
auto_exp and exposure are grouped together under a struct, you've
asked for a cluster of 3 controls, so auto_gain would get added to the
cluster too.
Except you haven't initialised auto_gain (or gain), so that may give
vaguely random results.

> > +       ctrls->link_freq = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ,
> > +                                                 0, 0, link_freq_menu_items);
> > +
> > +       if (hdl->error) {
> > +               ret = hdl->error;
> > +               goto free_ctrls;
> > +       }
> > +
> > +       ctrls->exposure->flags |= V4L2_CTRL_FLAG_VOLATILE;

How can this be volatile if there is no g_volatile_ctrl op in struct
v4l2_ctrl_ops?

> > +       sensor->sd.ctrl_handler = hdl;
> > +
> > +       return 0;
> > +
> > +free_ctrls:
> > +       v4l2_ctrl_handler_free(hdl);
> > +       return ret;
> > +}
> > +
> > +static int ovm6211_g_frame_interval(struct v4l2_subdev *sd,
> > +                                   struct v4l2_subdev_frame_interval *fi)
> > +{
> > +       struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
> > +
> > +       mutex_lock(&sensor->lock);
> > +       fi->interval = sensor->frame_interval;
> > +       mutex_unlock(&sensor->lock);
> > +
> > +       return 0;
> > +}
> > +
> > +static int internal_set_frame_interval(struct ovm6211_dev *sensor, struct
> > +                                      v4l2_subdev_frame_interval *fi)
> > +{
> > +       u32 fr_rate;
> > +       int i, ret = -EINVAL;
> > +
> > +       if (fi->interval.numerator == 0)
> > +               goto out;
> > +
> > +       fr_rate = fi->interval.denominator / fi->interval.numerator;
> > +
> > +       for (i = 0; i < ARRAY_SIZE(ovm6211_framerates); i++) {
> > +               if (ovm6211_framerates[i] == fr_rate)
> > +                       break;
> > +       }
> > +
> > +       if (i == ARRAY_SIZE(ovm6211_framerates))
> > +               goto out;
> > +
> > +       sensor->cur_fr_id = i;
> > +       sensor->frame_interval = fi->interval;
> > +       ret = 0;
> > +out:
> > +       return ret;
> > +}
> > +
> > +static int ovm6211_s_frame_interval(struct v4l2_subdev *sd,
> > +                                   struct v4l2_subdev_frame_interval *fi)
> > +{
> > +       struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
> > +       int ret;
> > +
> > +       mutex_lock(&sensor->lock);
> > +       ret = internal_set_frame_interval(sensor, fi);
> > +       sensor->pending_fi_change = true;
> > +       mutex_unlock(&sensor->lock);
> > +
> > +       return ret;
> > +}
> > +
> > +static int ovm6211_enum_mbus_code(struct v4l2_subdev *sd,
> > +                                 struct v4l2_subdev_state *state,
> > +                                 struct v4l2_subdev_mbus_code_enum *code)
> > +{
> > +       if (code->index >= ARRAY_SIZE(ovm6211_formats))
> > +               return -EINVAL;
> > +
> > +       code->code = ovm6211_formats[code->index].code;
> > +
> > +       return 0;
> > +}
> > +
> > +static int ovm6211_s_stream(struct v4l2_subdev *sd, int enable)
> > +{
> > +       struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
> > +       int ret = 0;
> > +
> > +       mutex_lock(&sensor->lock);
> > +
> > +       if (enable)
> > +               ret = ovm6211_set_mode(sensor);
> > +       internal_set_stream(sensor, enable);
> > +
> > +       mutex_unlock(&sensor->lock);
> > +
> > +       return ret;
> > +}
> > +
> > +static const struct v4l2_subdev_core_ops ovm6211_core_ops = {
> > +#ifdef CONFIG_VIDEO_ADV_DEBUG
> > +       .g_register = ovm6211_get_register,
> > +       .s_register = ovm6211_set_register,
> > +#endif
> > +};
> > +
> > +static const struct v4l2_subdev_video_ops ovm6211_video_ops = {
> > +       .g_frame_interval = ovm6211_g_frame_interval,
> > +       .s_frame_interval = ovm6211_s_frame_interval,
> > +       .s_stream = ovm6211_s_stream,
> > +};
> > +
> > +static const struct v4l2_subdev_pad_ops ovm6211_pad_ops = {
> > +       .enum_mbus_code = ovm6211_enum_mbus_code,
> > +       .get_fmt = ovm6211_get_fmt,
> > +       .set_fmt = ovm6211_set_fmt,
> > +};
> > +
> > +static const struct v4l2_subdev_ops ovm6211_subdev_ops = {
> > +       .core = &ovm6211_core_ops,
> > +       .video = &ovm6211_video_ops,
> > +       .pad = &ovm6211_pad_ops,
> > +};
> > +
> > +static int ovm6211_get_regulators(struct ovm6211_dev *sensor)
> > +{
> > +       unsigned int i;
> > +
> > +       for (i = 0; i < ARRAY_SIZE(ovm6211_supply_name); i++)
> > +               sensor->supplies[i].supply = ovm6211_supply_name[i];
> > +
> > +       return devm_regulator_bulk_get(&sensor->i2c_client->dev,
> > +                                      ARRAY_SIZE(ovm6211_supply_name),
> > +                                      sensor->supplies);
> > +}
> > +
> > +static int ovm6211_check_chip_id(struct ovm6211_dev *sensor)
> > +{
> > +       struct i2c_client *client = sensor->i2c_client;
> > +       struct regmap *regmap = sensor->regmap;
> > +       unsigned int cid;
> > +       int ret = 0;
> > +
> > +       ret = regmap_read(regmap, OVM6211_SC_CHIP_ID_HIGH, &cid);
> > +       if (ret || cid != 0x67) {
> > +               ret = ENXIO;
> > +               goto err;
> > +       }
> > +
> > +       ret = regmap_read(regmap, OVM6211_SC_CHIP_ID_LOW, &cid);
> > +       if (ret || cid != 0x10) {
> > +               ret = -ENXIO;
> > +               goto err;
> > +       }
> > +
> > +       ret = regmap_read(regmap, OVM6211_SC_REG0C, &cid);
> > +       if (ret)
> > +               goto err;
> > +
> > +       dev_info(&client->dev,"found OVM6211, sub revision: 0x%02X\n", cid);
> > +       return 0;
> > +err:
> > +       dev_err(&client->dev, "failed to detect OVM6211\n");
> > +       return ret;
> > +}
> > +
> > +static int ovm6211_probe(struct i2c_client *client)
> > +{
> > +       struct device *dev = &client->dev;
> > +       struct fwnode_handle *endpoint;
> > +       struct ovm6211_dev *sensor;
> > +       struct v4l2_mbus_framefmt *fmt;
> > +       int ret;
> > +
> > +       sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
> > +       if (!sensor)
> > +               return -ENOMEM;
> > +
> > +       sensor->i2c_client = client;
> > +
> > +       fmt = &sensor->fmt;
> > +       fmt->code = MEDIA_BUS_FMT_Y8_1X8;
> > +       fmt->colorspace = V4L2_COLORSPACE_RAW;
> > +       fmt->field = V4L2_FIELD_NONE;
> > +       fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
> > +       fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace);
> > +       fmt->width = 400;
> > +       fmt->height = 200;
>
> Is the 400x200 mode just subsampling?
>
> Should 400x200 be listed as a supported image size in your commit
> message?
>
>
> > +
> > +       sensor->frame_interval.numerator = 1;
> > +       sensor->frame_interval.denominator = ovm6211_framerates[OVM6211_45_FPS];
> > +       sensor->cur_fr_id = OVM6211_45_FPS;
> > +       sensor->cur_mode = &ovm6211_mode_data[OVM6211_MODE_Y8_400_200];
> > +
> > +       sensor->ep.bus_type = V4L2_MBUS_CSI2_DPHY;
> > +       endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(&client->dev), NULL);
> > +       if (!endpoint) {
> > +               dev_err(dev, "endpoint node not found\n");
> > +               return -EINVAL;
> > +       }
> > +
> > +       ret = v4l2_fwnode_endpoint_parse(endpoint, &sensor->ep);
> > +       fwnode_handle_put(endpoint);
> > +       if (ret) {
> > +               dev_err(dev, "Could not parse endpoint\n");
> > +               return ret;
> > +       }
> > +
> > +       /* request optional power down pin */
> > +       sensor->pwdn_gpio = devm_gpiod_get_optional(dev, "powerdown",
> > +                                                   GPIOD_OUT_HIGH);
> > +       if (IS_ERR(sensor->pwdn_gpio))
> > +               return PTR_ERR(sensor->pwdn_gpio);
> > +
> > +       /* request optional reset pin */
> > +       sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset",
> > +                                                    GPIOD_OUT_HIGH);
> > +       if (IS_ERR(sensor->reset_gpio))
> > +               return PTR_ERR(sensor->reset_gpio);

You request these GPIOs and regulators, but then never enable them.
pm_runtime support?

You don't request a clock, but the sensor will take an input clock
between 6 and 27MHz. The register PLL configuration is going to be for
one specific input clock frequency, so that ought to be validated, or
multiple clock frequencies supported.

 Dave

> > +       sensor->regmap = devm_regmap_init_i2c(client, &ovm6211_regmap_config);
> > +       if (IS_ERR(sensor->regmap)) {
> > +               dev_err(dev, "regmap init failed\n");
> > +               return PTR_ERR(sensor->regmap);
> > +       }
> > +
> > +       v4l2_i2c_subdev_init(&sensor->sd, client, &ovm6211_subdev_ops);
> > +
> > +       mutex_init(&sensor->lock);
> > +
> > +       sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
> > +                           V4L2_SUBDEV_FL_HAS_EVENTS;
> > +       sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
> > +       sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
> > +       ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
> > +       if (ret)
> > +               goto entity_cleanup;
> > +
> > +       ret = ovm6211_get_regulators(sensor);
> > +       if (ret)
> > +               goto entity_cleanup;
> > +
> > +       ret = ovm6211_check_chip_id(sensor);
> > +       if (ret)
> > +               goto entity_cleanup;
> > +
> > +       ret = ovm6211_init_controls(sensor);
> > +       if (ret)
> > +               goto entity_cleanup;
> > +
> > +       ovm6211_load_regs(sensor, sensor->cur_mode);
> > +
> > +       ret = v4l2_async_register_subdev_sensor(&sensor->sd);
> > +       if (ret)
> > +               goto free_ctrls;
> > +
> > +       return 0;
> > +
> > +free_ctrls:
> > +       v4l2_ctrl_handler_free(&sensor->ctrls.handler);
> > +entity_cleanup:
> > +       media_entity_cleanup(&sensor->sd.entity);
> > +       mutex_destroy(&sensor->lock);
> > +
> > +       return ret;
> > +}
> > +
> > +static int ovm6211_remove(struct i2c_client *client)
> > +{
> > +       struct v4l2_subdev *sd = i2c_get_clientdata(client);
> > +       struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
> > +
> > +       v4l2_async_unregister_subdev(&sensor->sd);
> > +       media_entity_cleanup(&sensor->sd.entity);
> > +       v4l2_ctrl_handler_free(&sensor->ctrls.handler);
> > +       mutex_destroy(&sensor->lock);
> > +
> > +       return 0;
> > +}
> > +
> > +static const struct i2c_device_id ovm6211_id[] = {
> > +       { "ovm6211", 0 },
> > +       {},
> > +};
> > +MODULE_DEVICE_TABLE(i2c, ovm6211_id);
> > +
> > +static const struct of_device_id ovm6211_dt_ids[] = {
> > +       { .compatible = "ovti,ovm6211" },
> > +       { /* sentinel */ }
> > +};
> > +MODULE_DEVICE_TABLE(of, ovm6211_dt_ids);
> > +
> > +static struct i2c_driver ovm6211_i2c_driver = {
> > +       .driver = {
> > +               .name  = "ovm6211",
> > +               .of_match_table = ovm6211_dt_ids,
> > +       },
> > +       .probe_new = ovm6211_probe,
> > +       .remove   = ovm6211_remove,
> > +};
> > +
> > +module_i2c_driver(ovm6211_i2c_driver);
> > +
> > +MODULE_AUTHOR("Petko Manolov <petko.manolov@konsulko.com>");
> > +MODULE_DESCRIPTION("OVM6211 MIPI Camera Subdev Driver");
> > +MODULE_LICENSE("GPL");
> > --
> > 2.30.2
> >
  
kernel test robot March 10, 2022, 12:19 p.m. UTC | #3
Hi Petko,

I love your patch! Perhaps something to improve:

[auto build test WARNING on 2b891d3980f6c255459d0e1c29ce2152ec7cf678]

url:    https://github.com/0day-ci/linux/commits/Petko-Manolov/media-ovm6211-Adds-support-for-OVM6211/20220309-182333
base:   2b891d3980f6c255459d0e1c29ce2152ec7cf678
config: hexagon-allyesconfig (https://download.01.org/0day-ci/archive/20220310/202203102030.LBUi85M1-lkp@intel.com/config)
compiler: clang version 15.0.0 (https://github.com/llvm/llvm-project 276ca87382b8f16a65bddac700202924228982f6)
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/0day-ci/linux/commit/4894c38df725fcab9dc9ac03d8cffa97971279b7
        git remote add linux-review https://github.com/0day-ci/linux
        git fetch --no-tags linux-review Petko-Manolov/media-ovm6211-Adds-support-for-OVM6211/20220309-182333
        git checkout 4894c38df725fcab9dc9ac03d8cffa97971279b7
        # save the config file to linux build tree
        mkdir build_dir
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=hexagon 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/ovm6211.c:993:35: warning: unused variable 'ovm6211_id' [-Wunused-const-variable]
   static const struct i2c_device_id ovm6211_id[] = {
                                     ^
   1 warning generated.


vim +/ovm6211_id +993 drivers/media/i2c/ovm6211.c

   992	
 > 993	static const struct i2c_device_id ovm6211_id[] = {
   994		{ "ovm6211", 0 },
   995		{},
   996	};
   997	MODULE_DEVICE_TABLE(i2c, ovm6211_id);
   998	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
  
Sakari Ailus April 19, 2022, 9:42 a.m. UTC | #4
Hi Petko,

Thanks for the update. A few small matters below.

On Wed, Mar 09, 2022 at 12:22:14PM +0200, Petko Manolov wrote:
> OVM6211 is a black and white 160 kpixel (400x400) camera sensor from OmniVision.
> It supports 400x400, 200x200 and 100x100 image sizes.  With careful register
> programming it is possible to have non-square sizes as well.  The output formats
> are 8/10-bit RAW, this driver is only using the former.  OVM6211 supports a
> single lane MIPI transmitter interface with up to 600 Mbps data rate.
> 
> Signed-off-by: Petko Manolov <petko.manolov@konsulko.com>
> ---
>  drivers/media/i2c/Kconfig   |   10 +
>  drivers/media/i2c/Makefile  |    1 +
>  drivers/media/i2c/ovm6211.c | 1018 +++++++++++++++++++++++++++++++++++
>  3 files changed, 1029 insertions(+)
>  create mode 100644 drivers/media/i2c/ovm6211.c
> 
> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> index fe66093b8849..aebe6a5f237e 100644
> --- a/drivers/media/i2c/Kconfig
> +++ b/drivers/media/i2c/Kconfig
> @@ -1049,6 +1049,16 @@ config VIDEO_OV5648
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called ov5648.
>  
> +config VIDEO_OVM6211
> +       tristate "OmniVision OVM6211 sensor support"
> +       depends on I2C && VIDEO_V4L2
> +       help
> +         This is a Video4Linux2 sensor driver for the OmniVision
> +         OVM6211 camera.
> +
> +         To compile this driver as a module, choose M here: the
> +         module will be called ovm6211.
> +
>  config VIDEO_OV6650
>  	tristate "OmniVision OV6650 sensor support"
>  	depends on I2C && VIDEO_V4L2
> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> index f6b80ef6f41d..9e9998966c18 100644
> --- a/drivers/media/i2c/Makefile
> +++ b/drivers/media/i2c/Makefile
> @@ -78,6 +78,7 @@ obj-$(CONFIG_VIDEO_OV5670) += ov5670.o
>  obj-$(CONFIG_VIDEO_OV5675) += ov5675.o
>  obj-$(CONFIG_VIDEO_OV5693) += ov5693.o
>  obj-$(CONFIG_VIDEO_OV5695) += ov5695.o
> +obj-$(CONFIG_VIDEO_OVM6211) += ovm6211.o
>  obj-$(CONFIG_VIDEO_OV6650) += ov6650.o
>  obj-$(CONFIG_VIDEO_OV7251) += ov7251.o
>  obj-$(CONFIG_VIDEO_OV7640) += ov7640.o
> diff --git a/drivers/media/i2c/ovm6211.c b/drivers/media/i2c/ovm6211.c
> new file mode 100644
> index 000000000000..7cfcc0bf5e48
> --- /dev/null
> +++ b/drivers/media/i2c/ovm6211.c
> @@ -0,0 +1,1018 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2021 Petko Manolov <petko.manolov@konsulko.com>
> + * Copyright (C) 2021 DEKA Research & Development Corp.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/slab.h>
> +#include <linux/types.h>
> +#include <media/v4l2-async.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-subdev.h>
> +
> +/* System Control */
> +#define OVM6211_SC_MODE_SELECT		0x0100
> +#define OVM6211_SC_SOFTWARE_RESET	0x0103
> +#define OVM6211_SC_SCCB_ID1		0x0109
> +#define OVM6211_SC_CHIP_ID_HIGH		0x300a
> +#define OVM6211_SC_CHIP_ID_LOW		0x300b
> +#define OVM6211_SC_REG0C		0x300c
> +#define OVM6211_SC_REG10		0x3010
> +#define OVM6211_SC_MIPI_PHY		0x3012
> +#define OVM6211_SC_MIPI_PHY2		0x3013
> +#define OVM6211_SC_MIPI_SC_CTRL0	0x3014
> +#define OVM6211_SC_MIPI_SC_CTRL1	0x3015
> +#define OVM6211_SC_CLKRST0		0x3016
> +#define OVM6211_SC_CLKRST1		0x3017
> +#define OVM6211_SC_CLKRST2		0x3018
> +#define OVM6211_SC_CLKRST3		0x3019
> +#define OVM6211_SC_CLKRST4		0x301a
> +#define OVM6211_SC_CLKRST5		0x301b
> +#define OVM6211_SC_CLKRST6		0x301c
> +#define OVM6211_SC_CLOCK_SEL		0x301e
> +#define OVM6211_SC_MISC_CTRL		0x301f
> +#define OVM6211_SC_LOW_PWR_CTRL		0x3023
> +#define OVM6211_SC_REG27		0x3027
> +#define OVM6211_SC_GP_IO_IN1		0x3029
> +#define OVM6211_SC_GP_IO_IN2		0x302a
> +#define OVM6211_SC_SCCB_ID2		0x302b
> +#define OVM6211_SC_AUTO_SLEEP_PERIOD0	0x302c
> +#define OVM6211_SC_AUTO_SLEEP_PERIOD1	0x302d
> +#define OVM6211_SC_AUTO_SLEEP_PERIOD2	0x302e
> +#define OVM6211_SC_AUTO_SLEEP_PERIOD3	0x302f
> +#define OVM6211_SC_LP_CTRL0		0x3030
> +#define OVM6211_SC_REG37		0x3037
> +#define OVM6211_SC_REG3B		0x303b
> +/* PLL Control */
> +#define OVM6211_PLL_PLL0		0x3080
> +#define OVM6211_PLL_PLL1		0x3081
> +#define OVM6211_PLL_PLL2		0x3082
> +#define OVM6211_PLL_PLL18		0x3098
> +#define OVM6211_PLL_PLL19		0x3099
> +#define OVM6211_PLL_PLL1A		0x309a
> +#define OVM6211_PLL_PLL1B		0x309b
> +#define OVM6211_PLL_PLL1C		0x309c
> +#define OVM6211_PLL_PLL1D		0x309d
> +#define OVM6211_PLL_PLL1E		0x309e
> +#define OVM6211_PLL_PLL1F		0x309f
> +#define OVM6211_PLL_VT_PIX_CLK_DIV	0x30b0
> +#define OVM6211_PLL_VT_SYS_CLK_DIV	0x30b1
> +#define OVM6211_PLL_MULTIPLIER		0x30b3
> +#define OVM6211_PLL_PLL1_PRE_PLL_DIV	0x30b4
> +#define OVM6211_PLL_PLL1_OP_PIX_CLK_DIV	0x30b5
> +#define OVM6211_PLL_PLL1_OP_SYS_CLK_DIV	0x30b6
> +/* SCCB and group hold */
> +#define OVM6211_SB_SRB_CTRL		0x3106
> +#define OVM6211_SB_SWITCH		0x31ff
> +/* AEC/AGC */
> +#define OVM6211_AEC_EXPO1		0x3500
> +#define OVM6211_AEC_EXPO2		0x3501
> +#define OVM6211_AEC_EXPO3		0x3502
> +#define OVM6211_AEC_MANUAL		0x3503
> +#define OVM6211_AGC_MAN_SNR_GAIN1	0x3504
> +#define OVM6211_AGC_MAN_SNR_GAIN2	0x3505
> +/* timing conrol registers */
> +#define	OVM6211_TVTS_HI			0x380e
> +#define	OVM6211_TVTS_LO			0x380f
> +/*Strobe frame span registers*/
> +#define OVM6211_STROBE_SPAN1		0x3b8d
> +#define OVM6211_STROBE_SPAN2		0x3b8e
> +#define OVM6211_STROBE_SPAN3		0x3b8f
> +/* format control */
> +#define OVM6211_DATA_MAX_H		0x4300
> +#define OVM6211_DATA_MIN_H		0x4301
> +#define OVM6211_CLIP_L			0x4302
> +#define OVM6211_FORMAT_CTRL3		0x4303
> +#define OVM6211_FORMAT_CTRL4		0x4304
> +#define OVM6211_VSYNC_WIDTH_H		0x4311
> +#define OVM6211_VSYNC_WIDTH_L		0x4312
> +#define OVM6211_VSYNC_CTRL		0x4313
> +#define OVM6211_VSYNC_DELAY1		0x4314
> +#define OVM6211_VSYNC_DELAY2		0x4315
> +#define OVM6211_VSYNC_DELAY3		0x4316
> +#define OVM6211_TST_PATTERN_CTRL	0x4320
> +/* MIPI */
> +#define OVM6211_MIPI_CTRL00		0x4800
> +#define OVM6211_MIPI_CTRL01		0x4801
> +#define OVM6211_MIPI_CTRL02		0x4802
> +#define OVM6211_MIPI_CTRL03		0x4803
> +#define OVM6211_MIPI_CTRL04		0x4804
> +#define OVM6211_MIPI_CTRL05		0x4805
> +#define OVM6211_MIPI_CTRL06		0x4806
> +#define OVM6211_MIPI_MAX_FRAME_COUNT_H	0x4810
> +#define OVM6211_MIPI_MAX_FRAME_COUNT_L	0x4811
> +#define OVM6211_MIPI_SHORT_PKT_COUNTER_H	0x4812
> +#define OVM6211_MIPI_SHORT_PKT_COUNTER_L	0x4813
> +#define OVM6211_MIPI_CTRL14		0x4814
> +#define OVM6211_MIPI_DT_SPKT		0x4815
> +#define OVM6211_MIPI_REG_MAX_H		0x4835
> +#define OVM6211_MIPI_REG_MAX_L		0x4836
> +#define OVM6211_PCLK_PERIOD		0x4837
> +#define OVM6211_WKUP_DLY		0x4838
> +#define OVM6211_DIR_DLY			0x483a
> +#define OVM6211_MIPI_LP_GPIO		0x483b
> +#define OVM6211_MIPI_CTRL3C		0x483c
> +#define OVM6211_T_TA_GO			0x483d
> +#define OVM6211_T_TA_SURE		0x483e
> +#define OVM6211_T_TA_GET		0x483f
> +#define OVM6211_MIPI_CLIP_MAX_H		0x4846
> +#define OVM6211_MIPI_CLIP_MAX_L		0x4847
> +#define OVM6211_MIPI_CLIP_MIN_H		0x4848
> +#define OVM6211_MIPI_CLIP_MIN_L		0x4848
> +#define OVM6211_REG_INTR_MAN		0x4850
> +#define OVM6211_REG_TX_WR		0x4851
> +/* ISP top */
> +#define OVM6211_ISP_CTRL00		0x5000
> +#define OVM6211_ISP_CTRL01		0x5001
> +#define OVM6211_ISP_CTRL02		0x5002
> +#define OVM6211_ISP_CTRL03		0x5003
> +#define OVM6211_ISP_CTRL04		0x5004
> +#define OVM6211_ISP_CTRL05		0x5005
> +#define OVM6211_ISP_CTRL06		0x5006
> +#define OVM6211_ISP_CTRL07		0x5007
> +#define OVM6211_ISP_CTRL08		0x5008
> +#define OVM6211_ISP_CTRL09		0x5009
> +/* window control */
> +#define OVM6211_MAN_XSTART_OFF_H	0x5a00
> +#define OVM6211_MAN_XSTART_OFF_L	0x5a01
> +#define OVM6211_MAN_YSTART_OFF_H	0x5a02
> +#define OVM6211_MAN_YSTART_OFF_L	0x5a03
> +#define OVM6211_MAN_WIN_WIDTH_H		0x5a04
> +#define OVM6211_MAN_WIN_WIDTH_L		0x5a05
> +#define OVM6211_MAN_WIN_HEIGHT_H	0x5a06
> +#define OVM6211_MAN_WIN_HEIGHT_L	0x5a07
> +#define OVM6211_WIN_MAN			0x5a08
> +
> +#define	OVM6211_LAST_REG		0x5e08
> +/* end of OVM6211 definitions */
> +
> +#define	DEF_LINK_FREQ			38400000LL
> +
> +enum ovm6211_mode_id {
> +	OVM6211_MODE_Y8_400_200 = 0,
> +	OVM6211_MODE_Y8_400_400,
> +	OVM6211_NUM_MODES,
> +};
> +
> +struct ovm6211_pixfmt {
> +	u32 code;
> +	u32 colorspace;
> +};
> +
> +static const struct ovm6211_pixfmt ovm6211_formats[] = {
> +	{ MEDIA_BUS_FMT_Y8_1X8,   V4L2_COLORSPACE_RAW, },
> +	{ MEDIA_BUS_FMT_Y8_1X8,   V4L2_COLORSPACE_RAW, },
> +};
> +
> +enum ovm6211_framerate_ids {
> +	OVM6211_10_FPS = 0,
> +	OVM6211_15_FPS,
> +	OVM6211_30_FPS,
> +	OVM6211_45_FPS,
> +	OVM6211_60_FPS,
> +	OVM6211_NUM_FRAMERATES,
> +};
> +
> +static const int ovm6211_framerates[] = {
> +	[OVM6211_10_FPS] = 10,
> +	[OVM6211_15_FPS] = 15,
> +	[OVM6211_30_FPS] = 30,
> +	[OVM6211_45_FPS] = 45,
> +	[OVM6211_60_FPS] = 60,
> +};
> +
> +/* regulator supplies */
> +static const char * const ovm6211_supply_name[] = {
> +	"dovdd",
> +	"avdd",
> +};
> +
> +static const struct regmap_config ovm6211_regmap_config = {
> +	.reg_bits       = 16,
> +	.val_bits       = 8,
> +	.max_register   = OVM6211_LAST_REG,
> +	.cache_type	= REGCACHE_NONE,
> +};
> +
> +struct reg_value {
> +	u16 reg_addr;
> +	u8 val;
> +	u8 mask;
> +	u32 delay_ms;
> +};
> +
> +struct ovm6211_mode_info {
> +	enum ovm6211_mode_id id;
> +	u32 width;
> +	u32 height;
> +	const struct reg_value *reg_data;
> +	u32 reg_data_size;
> +	u32 pixel_clock;
> +};
> +
> +struct ovm6211_ctrls {
> +	struct v4l2_ctrl_handler handler;
> +	struct {
> +		struct v4l2_ctrl *auto_exp;
> +		struct v4l2_ctrl *exposure;
> +	};
> +	struct {
> +		struct v4l2_ctrl *auto_gain;
> +		struct v4l2_ctrl *gain;
> +	};
> +	struct v4l2_ctrl *link_freq;
> +};
> +
> +struct ovm6211_dev {
> +	struct i2c_client *i2c_client;
> +	struct regmap *regmap;
> +	struct v4l2_subdev sd;
> +	struct media_pad pad;
> +	struct v4l2_fwnode_endpoint ep; /* the parsed DT endpoint info */
> +
> +	struct regulator_bulk_data supplies[ARRAY_SIZE(ovm6211_supply_name)];
> +	struct gpio_desc *reset_gpio;
> +	struct gpio_desc *pwdn_gpio;
> +
> +	struct mutex lock;
> +
> +	struct v4l2_mbus_framefmt fmt;
> +
> +	const struct ovm6211_mode_info *cur_mode;
> +	enum ovm6211_framerate_ids cur_fr_id;
> +	struct v4l2_fract frame_interval;
> +
> +	struct ovm6211_ctrls ctrls;
> +
> +	u32 exposure;
> +	bool pending_mode_change;
> +	bool pending_fi_change;
> +	bool streaming;
> +};
> +
> +static inline struct ovm6211_dev *to_ovm6211_dev(struct v4l2_subdev *sd)
> +{
> +	return container_of(sd, struct ovm6211_dev, sd);
> +}
> +
> +static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl)
> +{
> +	return &container_of(ctrl->handler, struct ovm6211_dev,
> +			     ctrls.handler)->sd;
> +}
> +
> +static const struct reg_value ovm6211_init_y8_400_400[] = {
> +	{0x0103, 0x01, 0, 0}, {0x0100, 0x00, 0, 0}, {0x3005, 0x08, 0, 0},
> +	{0x3013, 0x12, 0, 0}, {0x3014, 0x04, 0, 0}, {0x3016, 0x10, 0, 0},
> +	{0x3017, 0x00, 0, 0}, {0x3018, 0x00, 0, 0}, {0x301a, 0x00, 0, 0},
> +	{0x301b, 0x00, 0, 0}, {0x301c, 0x00, 0, 0}, {0x3037, 0xf0, 0, 0},
> +	{0x3080, 0x01, 0, 0}, {0x3081, 0x00, 0, 0}, {0x3082, 0x01, 0, 0},
> +	{0x3098, 0x04, 0, 0}, {0x3099, 0x28, 0, 0}, {0x309a, 0x06, 0, 0},
> +	{0x309b, 0x04, 0, 0}, {0x309c, 0x00, 0, 0}, {0x309d, 0x00, 0, 0},
> +	{0x309e, 0x01, 0, 0}, {0x309f, 0x00, 0, 0}, {0x30b0, 0x08, 0, 0},
> +	{0x30b1, 0x02, 0, 0}, {0x30b2, 0x00, 0, 0}, {0x30b3, 0x28, 0, 0},
> +	{0x30b4, 0x02, 0, 0}, {0x30b5, 0x00, 0, 0}, {0x3106, 0xd9, 0, 0},
> +	{0x3500, 0x00, 0, 0}, {0x3501, 0x1b, 0, 0}, {0x3502, 0x20, 0, 0},
> +	{0x3503, 0x07, 0, 0}, {0x3509, 0x10, 0, 0}, {0x350b, 0x10, 0, 0},
> +	{0x3600, 0xfc, 0, 0}, {0x3620, 0xb7, 0, 0}, {0x3621, 0x05, 0, 0},
> +	{0x3626, 0x31, 0, 0}, {0x3627, 0x40, 0, 0}, {0x3632, 0xa3, 0, 0},
> +	{0x3633, 0x34, 0, 0}, {0x3634, 0x40, 0, 0}, {0x3636, 0x00, 0, 0},
> +	{0x3660, 0x80, 0, 0}, {0x3662, 0x03, 0, 0}, {0x3664, 0xf0, 0, 0},
> +	{0x366a, 0x10, 0, 0}, {0x366b, 0x06, 0, 0}, {0x3680, 0xf4, 0, 0},
> +	{0x3681, 0x50, 0, 0}, {0x3682, 0x00, 0, 0}, {0x3708, 0x20, 0, 0},
> +	{0x3709, 0x40, 0, 0}, {0x370d, 0x03, 0, 0}, {0x373b, 0x02, 0, 0},
> +	{0x373c, 0x08, 0, 0}, {0x3742, 0x00, 0, 0}, {0x3744, 0x16, 0, 0},
> +	{0x3745, 0x08, 0, 0}, {0x3781, 0xfc, 0, 0}, {0x3788, 0x00, 0, 0},
> +	{0x3800, 0x00, 0, 0}, {0x3801, 0x04, 0, 0}, {0x3802, 0x00, 0, 0},
> +	{0x3803, 0x04, 0, 0}, {0x3804, 0x01, 0, 0}, {0x3805, 0x9b, 0, 0},
> +	{0x3806, 0x01, 0, 0}, {0x3807, 0x9b, 0, 0}, {0x3808, 0x01, 0, 0},
> +	{0x3809, 0x90, 0, 0}, {0x380a, 0x01, 0, 0}, {0x380b, 0x90, 0, 0},
> +	{0x380c, 0x05, 0, 0}, {0x380d, 0xf2, 0, 0}, {0x380e, 0x03, 0, 0},
> +	{0x380f, 0x6c, 0, 0}, {0x3810, 0x00, 0, 0}, {0x3811, 0x04, 0, 0},
> +	{0x3812, 0x00, 0, 0}, {0x3813, 0x04, 0, 0}, {0x3814, 0x11, 0, 0},
> +	{0x3815, 0x11, 0, 0}, {0x3820, 0x00, 0, 0}, {0x3821, 0x00, 0, 0},
> +	{0x382b, 0xfa, 0, 0}, {0x382f, 0x04, 0, 0}, {0x3832, 0x00, 0, 0},
> +	{0x3833, 0x05, 0, 0}, {0x3834, 0x00, 0, 0}, {0x3835, 0x05, 0, 0},
> +	{0x3882, 0x04, 0, 0}, {0x3883, 0x00, 0, 0}, {0x38a4, 0x10, 0, 0},
> +	{0x38a5, 0x00, 0, 0}, {0x38b1, 0x03, 0, 0}, {0x3b80, 0x00, 0, 0},
> +	{0x3b81, 0xff, 0, 0}, {0x3b82, 0x10, 0, 0}, {0x3b83, 0x00, 0, 0},
> +	{0x3b84, 0x08, 0, 0}, {0x3b85, 0x00, 0, 0}, {0x3b86, 0x01, 0, 0},
> +	{0x3b87, 0x00, 0, 0}, {0x3b88, 0x00, 0, 0}, {0x3b89, 0x00, 0, 0},
> +	{0x3b8a, 0x00, 0, 0}, {0x3b8b, 0x05, 0, 0}, {0x3b8c, 0x00, 0, 0},
> +	{0x3b8d, 0x00, 0, 0}, {0x3b8e, 0x01, 0, 0}, {0x3b8f, 0xb2, 0, 0},
> +	{0x3b94, 0x05, 0, 0}, {0x3b95, 0xf2, 0, 0}, {0x3b96, 0xc0, 0, 0},
> +	{0x4004, 0x04, 0, 0}, {0x404e, 0x01, 0, 0}, {0x4801, 0x0f, 0, 0},
> +	{0x4806, 0x0f, 0, 0}, {0x4837, 0x43, 0, 0}, {0x5a08, 0x00, 0, 0},
> +	{0x5a01, 0x00, 0, 0}, {0x5a03, 0x00, 0, 0}, {0x5a04, 0x10, 0, 0},
> +	{0x5a05, 0xa0, 0, 0}, {0x5a06, 0x0c, 0, 0}, {0x5a07, 0x78, 0, 0},
> +};
> +
> +static const struct reg_value ovm6211_init_y8_400_200[] = {
> +	{0x0103, 0x01, 0, 0}, {0x0100, 0x00, 0, 0}, {0x3005, 0x08, 0, 0},
> +	{0x3013, 0x12, 0, 0}, {0x3014, 0x04, 0, 0}, {0x3016, 0x10, 0, 0},
> +	{0x3017, 0x00, 0, 0}, {0x3018, 0x00, 0, 0}, {0x301a, 0x00, 0, 0},
> +	{0x301b, 0x00, 0, 0}, {0x301c, 0x00, 0, 0}, {0x3037, 0xf0, 0, 0},
> +	{0x3080, 0x01, 0, 0}, {0x3081, 0x00, 0, 0}, {0x3082, 0x01, 0, 0},
> +	{0x3098, 0x04, 0, 0}, {0x3099, 0x28, 0, 0}, {0x309a, 0x06, 0, 0},
> +	{0x309b, 0x04, 0, 0}, {0x309c, 0x00, 0, 0}, {0x309d, 0x00, 0, 0},
> +	{0x309e, 0x01, 0, 0}, {0x309f, 0x00, 0, 0}, {0x30b0, 0x08, 0, 0},
> +	{0x30b1, 0x02, 0, 0}, {0x30b2, 0x00, 0, 0}, {0x30b3, 0x28, 0, 0},
> +	{0x30b4, 0x02, 0, 0}, {0x30b5, 0x00, 0, 0}, {0x3106, 0xd9, 0, 0},
> +	{0x3500, 0x00, 0, 0}, {0x3501, 0x1b, 0, 0}, {0x3502, 0x20, 0, 0},
> +	{0x3503, 0x07, 0, 0}, {0x3509, 0x10, 0, 0}, {0x350b, 0x10, 0, 0},
> +	{0x3600, 0xfc, 0, 0}, {0x3620, 0xb7, 0, 0}, {0x3621, 0x05, 0, 0},
> +	{0x3626, 0x31, 0, 0}, {0x3627, 0x40, 0, 0}, {0x3632, 0xa3, 0, 0},
> +	{0x3633, 0x34, 0, 0}, {0x3634, 0x40, 0, 0}, {0x3636, 0x00, 0, 0},
> +	{0x3660, 0x80, 0, 0}, {0x3662, 0x03, 0, 0}, {0x3664, 0xf0, 0, 0},
> +	{0x366a, 0x10, 0, 0}, {0x366b, 0x06, 0, 0}, {0x3680, 0xf4, 0, 0},
> +	{0x3681, 0x50, 0, 0}, {0x3682, 0x00, 0, 0}, {0x3708, 0x20, 0, 0},
> +	{0x3709, 0x40, 0, 0}, {0x370d, 0x03, 0, 0}, {0x373b, 0x02, 0, 0},
> +	{0x373c, 0x08, 0, 0}, {0x3742, 0x00, 0, 0}, {0x3744, 0x16, 0, 0},
> +	{0x3745, 0x08, 0, 0}, {0x3781, 0xfc, 0, 0}, {0x3788, 0x00, 0, 0},
> +	{0x3800, 0x00, 0, 0}, {0x3801, 0x04, 0, 0}, {0x3802, 0x00, 0, 0},
> +	{0x3803, 0x04, 0, 0}, {0x3804, 0x01, 0, 0}, {0x3805, 0x9b, 0, 0},
> +	{0x3806, 0x01, 0, 0}, {0x3807, 0x9b, 0, 0}, {0x3808, 0x01, 0, 0},
> +	{0x3809, 0x90, 0, 0}, {0x380a, 0x00, 0, 0}, {0x380b, 0xc8, 0, 0},
> +	{0x380c, 0x05, 0, 0}, {0x380d, 0xf2, 0, 0}, {0x380e, 0x0d, 0, 0},
> +	{0x380f, 0xb0, 0, 0}, {0x3810, 0x00, 0, 0}, {0x3811, 0x04, 0, 0},
> +	{0x3812, 0x00, 0, 0}, {0x3813, 0x9a, 0, 0}, {0x3814, 0x11, 0, 0},
> +	{0x3815, 0x11, 0, 0}, {0x3820, 0x00, 0, 0}, {0x3821, 0x00, 0, 0},
> +	{0x382b, 0xfa, 0, 0}, {0x382f, 0x04, 0, 0}, {0x3832, 0x00, 0, 0},
> +	{0x3833, 0x05, 0, 0}, {0x3834, 0x00, 0, 0}, {0x3835, 0x05, 0, 0},
> +	{0x3882, 0x04, 0, 0}, {0x3883, 0x00, 0, 0}, {0x38a4, 0x10, 0, 0},
> +	{0x38a5, 0x00, 0, 0}, {0x38b1, 0x03, 0, 0}, {0x3b80, 0x00, 0, 0},
> +	{0x3b81, 0xff, 0, 0}, {0x3b82, 0x10, 0, 0}, {0x3b83, 0x00, 0, 0},
> +	{0x3b84, 0x08, 0, 0}, {0x3b85, 0x00, 0, 0}, {0x3b86, 0x01, 0, 0},
> +	{0x3b87, 0x00, 0, 0}, {0x3b88, 0x00, 0, 0}, {0x3b89, 0x00, 0, 0},
> +	{0x3b8a, 0x00, 0, 0}, {0x3b8b, 0x05, 0, 0}, {0x3b8c, 0x00, 0, 0},
> +	{0x3b8d, 0x00, 0, 0}, {0x3b8e, 0x01, 0, 0}, {0x3b8f, 0xb2, 0, 0},
> +	{0x3b94, 0x05, 0, 0}, {0x3b95, 0xf2, 0, 0}, {0x3b96, 0xc0, 0, 0},
> +	{0x4004, 0x04, 0, 0}, {0x404e, 0x01, 0, 0}, {0x4801, 0x0f, 0, 0},
> +	{0x4806, 0x0f, 0, 0}, {0x4837, 0x43, 0, 0}, {0x5a08, 0x00, 0, 0},
> +	{0x5a01, 0x00, 0, 0}, {0x5a03, 0x00, 0, 0}, {0x5a04, 0x10, 0, 0},
> +	{0x5a05, 0xa0, 0, 0}, {0x5a06, 0x0c, 0, 0}, {0x5a07, 0x78, 0, 0},
> +};
> +
> +static struct ovm6211_mode_info
> +ovm6211_mode_data[OVM6211_NUM_MODES] = {
> +	{OVM6211_MODE_Y8_400_200,
> +	 400, 200,
> +	 ovm6211_init_y8_400_200,
> +	 ARRAY_SIZE(ovm6211_init_y8_400_200),
> +	 400 * 400 * 60 * 2,
> +	},
> +	{OVM6211_MODE_Y8_400_400,
> +	 400, 400,
> +	 ovm6211_init_y8_400_400,
> +	 ARRAY_SIZE(ovm6211_init_y8_400_400),
> +	 400 * 400 * 60 * 2,
> +	},
> +};
> +
> +static const s64 link_freq_menu_items[] = {
> +	DEF_LINK_FREQ,
> +};
> +
> +#ifdef CONFIG_VIDEO_ADV_DEBUG
> +static int ovm6211_get_register(struct v4l2_subdev *sd,
> +				struct v4l2_dbg_register *reg)
> +{
> +	struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
> +	struct regmap *regmap = sensor->regmap;
> +	unsigned int val = 0;
> +	int ret;
> +
> +	ret = regmap_read(regmap, reg->reg, &val);
> +	reg->val = val;
> +	reg->size = 1;
> +
> +	return ret;
> +}
> +
> +static int ovm6211_set_register(struct v4l2_subdev *sd,
> +				const struct v4l2_dbg_register *reg)
> +{
> +	struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
> +	struct regmap *regmap = sensor->regmap;
> +
> +	return regmap_write(regmap, reg->reg, reg->val & 0xff);
> +}
> +#endif
> +
> +static int ovm6211_write_reg(struct ovm6211_dev *sensor, u16 reg, u8 val)
> +{
> +	struct i2c_client *client = sensor->i2c_client;
> +	struct regmap *regmap = sensor->regmap;
> +	int ret;
> +
> +	ret = regmap_write(regmap, reg, val);
> +	if (ret < 0)
> +		dev_err(&client->dev, "error writing reg %u\n", reg);
> +
> +	return ret;
> +}
> +
> +static int ovm6211_read_reg(struct ovm6211_dev *sensor, u16 reg, u8 *val)
> +{
> +	struct i2c_client *client = sensor->i2c_client;
> +	struct regmap *regmap = sensor->regmap;
> +	unsigned int r;
> +	int ret;
> +
> +	ret = regmap_read(regmap, reg, &r);
> +	if (ret < 0)
> +		dev_err(&client->dev, "error reading reg %u\n", reg);
> +	*val = r & 0xff;
> +
> +	return ret;
> +}
> +
> +static int ovm6211_mod_reg(struct ovm6211_dev *sensor, u16 reg, u8 mask, u8 val)
> +{
> +	u8 readval;
> +	int ret;
> +
> +	ret = ovm6211_read_reg(sensor, reg, &readval);
> +	if (ret)
> +		return ret;
> +
> +	readval &= ~mask;
> +	val &= mask;
> +	val |= readval;
> +
> +	return ovm6211_write_reg(sensor, reg, val);
> +}
> +
> +static int ovm6211_load_regs(struct ovm6211_dev *sensor,
> +			     const struct ovm6211_mode_info *mode)
> +{
> +	const struct reg_value *regs = mode->reg_data;
> +	unsigned int i;
> +	u32 delay_ms;
> +	u16 reg_addr;
> +	u8 mask, val;
> +	int ret = 0;
> +
> +	for (i = 0; i < mode->reg_data_size; ++i, ++regs) {
> +		delay_ms = regs->delay_ms;
> +		reg_addr = regs->reg_addr;
> +		val = regs->val;
> +		mask = regs->mask;
> +
> +		if (mask)
> +			ret = ovm6211_mod_reg(sensor, reg_addr, mask, val);
> +		else
> +			ret = ovm6211_write_reg(sensor, reg_addr, val);
> +
> +		if (ret)
> +			break;
> +
> +		if (delay_ms)
> +			usleep_range(1000 * delay_ms, 1000 * delay_ms + 100);
> +	}
> +
> +	return ret;
> +}
> +
> +static void ovm6211_soft_reset(struct ovm6211_dev *sensor)
> +{
> +	ovm6211_write_reg(sensor, OVM6211_SC_SOFTWARE_RESET, 0x01);
> +	usleep_range(5000, 9000);
> +	ovm6211_write_reg(sensor, OVM6211_SC_SOFTWARE_RESET, 0x00);
> +}
> +
> +static int ovm6211_set_exposure(struct ovm6211_dev *sensor, u32 exposure)
> +{
> +	u32 ce;
> +	u8 v;
> +
> +	ovm6211_read_reg(sensor, OVM6211_TVTS_HI, &v);
> +	ce = v << 8;
> +	ovm6211_read_reg(sensor, OVM6211_TVTS_LO, &v);
> +	ce |= v;
> +	ce -= 4;
> +
> +	if (ce < exposure)
> +		exposure = ce;
> +
> +	ovm6211_mod_reg(sensor, OVM6211_AEC_MANUAL, 1, 1);
> +
> +	ovm6211_write_reg(sensor, OVM6211_AEC_EXPO1, (exposure >> 12) & 0x0f);
> +	ovm6211_write_reg(sensor, OVM6211_AEC_EXPO2, (exposure >>  4) & 0xff);
> +	ovm6211_write_reg(sensor, OVM6211_AEC_EXPO3, (exposure <<  4) & 0xf0);
> +
> +	/* set strobe width equal to exposure time */

What's the purpose of this? Does the sensor have an external flash strobe
pin?

That should be present in DT as well. I don't think we have an example
though, could be omitted now of course.

> +	ovm6211_write_reg(sensor, OVM6211_STROBE_SPAN1,
> +			  (exposure >> 16) & 0xff);
> +	ovm6211_write_reg(sensor, OVM6211_STROBE_SPAN2,
> +			  (exposure >> 8) & 0xff);
> +	ovm6211_write_reg(sensor, OVM6211_STROBE_SPAN3, (exposure) & 0xff);
> +
> +	return 0;
> +}
> +
> +static int internal_set_stream(struct ovm6211_dev *sensor, bool on)
> +{
> +	u8 hi, lo;
> +
> +	if (sensor->pending_fi_change == false)
> +		goto stream;
> +
> +	switch (sensor->cur_fr_id) {
> +	case OVM6211_10_FPS:
> +		hi = 0x14;
> +		lo = 0x88;
> +		break;
> +	case OVM6211_15_FPS:
> +		hi = 0x0d;
> +		lo = 0xb0;
> +		break;
> +	case OVM6211_30_FPS:
> +		hi = 0x06;
> +		lo = 0xd8;
> +		break;
> +	case OVM6211_45_FPS:
> +		hi = 0x04;
> +		lo = 0x90;
> +		break;
> +	case OVM6211_60_FPS:
> +		hi = 0x03;
> +		lo = 0x6c;
> +		break;
> +	case OVM6211_NUM_FRAMERATES:
> +		return -EINVAL;
> +	}
> +
> +	sensor->pending_fi_change = false;
> +	ovm6211_write_reg(sensor, OVM6211_TVTS_HI, hi);
> +	ovm6211_write_reg(sensor, OVM6211_TVTS_LO, lo);
> +stream:
> +	ovm6211_write_reg(sensor, OVM6211_SC_MODE_SELECT, 0);
> +	if (on) {
> +		usleep_range(4000, 5000);
> +		if (sensor->exposure)
> +			ovm6211_set_exposure(sensor, sensor->exposure);
> +		ovm6211_write_reg(sensor, OVM6211_SC_MODE_SELECT, 1);
> +		sensor->streaming = true;
> +	} else {
> +		sensor->streaming = false;
> +	}
> +
> +	return 0;
> +}
> +
> +static struct ovm6211_mode_info *ovm6211_find_mode(int w, int h)
> +{
> +	return v4l2_find_nearest_size(ovm6211_mode_data,
> +				      ARRAY_SIZE(ovm6211_mode_data),
> +				      width, height, w, h);
> +}
> +
> +static int ovm6211_set_mode(struct ovm6211_dev *sensor)
> +{
> +	const struct ovm6211_mode_info *mode = sensor->cur_mode;
> +
> +	ovm6211_soft_reset(sensor);
> +	ovm6211_load_regs(sensor, mode);
> +
> +	return 0;
> +}
> +
> +/* --------------- Subdev Operations --------------- */
> +
> +static int ovm6211_get_fmt(struct v4l2_subdev *sd,
> +			   struct v4l2_subdev_state *state,
> +			   struct v4l2_subdev_format *format)
> +{
> +	struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
> +	struct v4l2_mbus_framefmt *fmt;
> +
> +	mutex_lock(&sensor->lock);
> +
> +	if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
> +		fmt = v4l2_subdev_get_try_format(&sensor->sd, state, format->pad);
> +		format->format = *fmt;
> +	} else {
> +		/* these are hardcoded as we don't support anything else */
> +		format->format.colorspace  = V4L2_COLORSPACE_RAW;
> +		format->format.field = V4L2_FIELD_NONE;
> +		format->format.code = MEDIA_BUS_FMT_Y8_1X8;
> +		format->format.width = sensor->cur_mode->width;
> +		format->format.height = sensor->cur_mode->height;
> +	}
> +
> +	mutex_unlock(&sensor->lock);
> +
> +	return 0;
> +}
> +
> +static int ovm6211_set_fmt(struct v4l2_subdev *sd,
> +			   struct v4l2_subdev_state *state,
> +			   struct v4l2_subdev_format *format)
> +{
> +	struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
> +	struct v4l2_mbus_framefmt *fmt = &format->format;
> +	struct ovm6211_mode_info *mode;
> +
> +	mutex_lock(&sensor->lock);
> +
> +	if (sensor->streaming)
> +		return -EBUSY;
> +
> +	/* these are hardcoded as we don't support anything else */
> +	format->format.colorspace  = V4L2_COLORSPACE_RAW;
> +	format->format.field = V4L2_FIELD_NONE;
> +	format->format.code = MEDIA_BUS_FMT_Y8_1X8;
> +	mode = ovm6211_find_mode(format->format.width, format->format.height);
> +	format->format.width = mode->width;
> +	format->format.height = mode->height;
> +
> +	if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
> +		fmt = v4l2_subdev_get_try_format(&sensor->sd, state, format->pad);
> +		*fmt = format->format;
> +		goto out;
> +	}
> +	sensor->cur_mode = mode;
> +out:
> +	mutex_unlock(&sensor->lock);
> +
> +	return 0;
> +}
> +
> +/*
> + * Sensor Controls.
> + */
> +
> +static int ovm6211_set_ctrl_exposure(struct ovm6211_dev *sensor, enum
> +				     v4l2_exposure_auto_type auto_exposure)
> +{
> +	struct ovm6211_ctrls *ctrls = &sensor->ctrls;
> +
> +	if (auto_exposure == V4L2_EXPOSURE_AUTO) {
> +		sensor->exposure = 0;
> +	} else {
> +		sensor->exposure = ctrls->exposure->val;
> +		ovm6211_set_exposure(sensor, sensor->exposure);
> +	}
> +
> +	return 0;
> +}
> +
> +static int ovm6211_s_ctrl(struct v4l2_ctrl *ctrl)
> +{
> +	struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
> +	struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
> +	int ret;
> +
> +	switch (ctrl->id) {
> +	case V4L2_CID_EXPOSURE_AUTO:
> +		ret = ovm6211_set_ctrl_exposure(sensor, ctrl->val);
> +		break;
> +	case V4L2_CID_LINK_FREQ:
> +		return 0;
> +	case V4L2_CID_PIXEL_RATE:
> +		return 0;
> +	default:
> +		ret = -EINVAL;
> +		break;
> +	}
> +
> +	return ret;
> +}
> +
> +static const struct v4l2_ctrl_ops ovm6211_ctrl_ops = {
> +	.s_ctrl = ovm6211_s_ctrl,
> +};
> +
> +static int ovm6211_init_controls(struct ovm6211_dev *sensor)
> +{
> +	const struct v4l2_ctrl_ops *ops = &ovm6211_ctrl_ops;
> +	struct ovm6211_ctrls *ctrls = &sensor->ctrls;
> +	struct v4l2_ctrl_handler *hdl = &ctrls->handler;
> +	int ret;
> +
> +	v4l2_ctrl_handler_init(hdl, 16);
> +
> +	hdl->lock = &sensor->lock;
> +
> +	ctrls->auto_exp = v4l2_ctrl_new_std_menu(hdl, ops, V4L2_CID_EXPOSURE_AUTO,
> +						 V4L2_EXPOSURE_MANUAL, 0,
> +						 V4L2_EXPOSURE_AUTO);
> +	ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE,
> +					    0, 65535, 1, 0);
> +	v4l2_ctrl_auto_cluster(3, &ctrls->auto_exp, 0, false);
> +	ctrls->link_freq = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ,
> +						  0, 0, link_freq_menu_items);
> +
> +	if (hdl->error) {
> +		ret = hdl->error;
> +		goto free_ctrls;
> +	}
> +
> +	ctrls->exposure->flags |= V4L2_CTRL_FLAG_VOLATILE;
> +	sensor->sd.ctrl_handler = hdl;
> +
> +	return 0;
> +
> +free_ctrls:
> +	v4l2_ctrl_handler_free(hdl);
> +	return ret;
> +}
> +
> +static int ovm6211_g_frame_interval(struct v4l2_subdev *sd,
> +				    struct v4l2_subdev_frame_interval *fi)
> +{
> +	struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
> +
> +	mutex_lock(&sensor->lock);
> +	fi->interval = sensor->frame_interval;
> +	mutex_unlock(&sensor->lock);
> +
> +	return 0;
> +}
> +
> +static int internal_set_frame_interval(struct ovm6211_dev *sensor, struct
> +				       v4l2_subdev_frame_interval *fi)
> +{
> +	u32 fr_rate;
> +	int i, ret = -EINVAL;
> +
> +	if (fi->interval.numerator == 0)
> +		goto out;
> +
> +	fr_rate = fi->interval.denominator / fi->interval.numerator;
> +
> +	for (i = 0; i < ARRAY_SIZE(ovm6211_framerates); i++) {
> +		if (ovm6211_framerates[i] == fr_rate)
> +			break;
> +	}
> +
> +	if (i == ARRAY_SIZE(ovm6211_framerates))
> +		goto out;
> +
> +	sensor->cur_fr_id = i;
> +	sensor->frame_interval = fi->interval;
> +	ret = 0;
> +out:
> +	return ret;
> +}
> +
> +static int ovm6211_s_frame_interval(struct v4l2_subdev *sd,
> +				    struct v4l2_subdev_frame_interval *fi)
> +{
> +	struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
> +	int ret;
> +
> +	mutex_lock(&sensor->lock);
> +	ret = internal_set_frame_interval(sensor, fi);
> +	sensor->pending_fi_change = true;
> +	mutex_unlock(&sensor->lock);
> +
> +	return ret;
> +}
> +
> +static int ovm6211_enum_mbus_code(struct v4l2_subdev *sd,
> +				  struct v4l2_subdev_state *state,
> +				  struct v4l2_subdev_mbus_code_enum *code)
> +{
> +	if (code->index >= ARRAY_SIZE(ovm6211_formats))
> +		return -EINVAL;
> +
> +	code->code = ovm6211_formats[code->index].code;
> +
> +	return 0;
> +}
> +
> +static int ovm6211_s_stream(struct v4l2_subdev *sd, int enable)
> +{
> +	struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
> +	int ret = 0;
> +
> +	mutex_lock(&sensor->lock);
> +
> +	if (enable)
> +		ret = ovm6211_set_mode(sensor);
> +	internal_set_stream(sensor, enable);
> +
> +	mutex_unlock(&sensor->lock);
> +
> +	return ret;
> +}
> +
> +static const struct v4l2_subdev_core_ops ovm6211_core_ops = {
> +#ifdef CONFIG_VIDEO_ADV_DEBUG
> +	.g_register = ovm6211_get_register,
> +	.s_register = ovm6211_set_register,
> +#endif
> +};
> +
> +static const struct v4l2_subdev_video_ops ovm6211_video_ops = {
> +	.g_frame_interval = ovm6211_g_frame_interval,
> +	.s_frame_interval = ovm6211_s_frame_interval,
> +	.s_stream = ovm6211_s_stream,
> +};
> +
> +static const struct v4l2_subdev_pad_ops ovm6211_pad_ops = {
> +	.enum_mbus_code = ovm6211_enum_mbus_code,
> +	.get_fmt = ovm6211_get_fmt,
> +	.set_fmt = ovm6211_set_fmt,
> +};
> +
> +static const struct v4l2_subdev_ops ovm6211_subdev_ops = {
> +	.core = &ovm6211_core_ops,
> +	.video = &ovm6211_video_ops,
> +	.pad = &ovm6211_pad_ops,
> +};
> +
> +static int ovm6211_get_regulators(struct ovm6211_dev *sensor)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(ovm6211_supply_name); i++)
> +		sensor->supplies[i].supply = ovm6211_supply_name[i];
> +
> +	return devm_regulator_bulk_get(&sensor->i2c_client->dev,
> +				       ARRAY_SIZE(ovm6211_supply_name),
> +				       sensor->supplies);
> +}
> +
> +static int ovm6211_check_chip_id(struct ovm6211_dev *sensor)
> +{
> +	struct i2c_client *client = sensor->i2c_client;
> +	struct regmap *regmap = sensor->regmap;
> +	unsigned int cid;
> +	int ret = 0;
> +
> +	ret = regmap_read(regmap, OVM6211_SC_CHIP_ID_HIGH, &cid);
> +	if (ret || cid != 0x67) {
> +		ret = ENXIO;
> +		goto err;
> +	}
> +
> +	ret = regmap_read(regmap, OVM6211_SC_CHIP_ID_LOW, &cid);
> +	if (ret || cid != 0x10) {
> +		ret = -ENXIO;
> +		goto err;
> +	}
> +
> +	ret = regmap_read(regmap, OVM6211_SC_REG0C, &cid);
> +	if (ret)
> +		goto err;
> +
> +	dev_info(&client->dev,"found OVM6211, sub revision: 0x%02X\n", cid);
> +	return 0;
> +err:
> +	dev_err(&client->dev, "failed to detect OVM6211\n");
> +	return ret;
> +}
> +
> +static int ovm6211_probe(struct i2c_client *client)
> +{
> +	struct device *dev = &client->dev;
> +	struct fwnode_handle *endpoint;
> +	struct ovm6211_dev *sensor;
> +	struct v4l2_mbus_framefmt *fmt;
> +	int ret;
> +
> +	sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
> +	if (!sensor)
> +		return -ENOMEM;
> +
> +	sensor->i2c_client = client;
> +
> +	fmt = &sensor->fmt;
> +	fmt->code = MEDIA_BUS_FMT_Y8_1X8;
> +	fmt->colorspace = V4L2_COLORSPACE_RAW;
> +	fmt->field = V4L2_FIELD_NONE;
> +	fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
> +	fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace);
> +	fmt->width = 400;
> +	fmt->height = 200;
> +
> +	sensor->frame_interval.numerator = 1;
> +	sensor->frame_interval.denominator = ovm6211_framerates[OVM6211_45_FPS];
> +	sensor->cur_fr_id = OVM6211_45_FPS;
> +	sensor->cur_mode = &ovm6211_mode_data[OVM6211_MODE_Y8_400_200];
> +
> +	sensor->ep.bus_type = V4L2_MBUS_CSI2_DPHY;

If you don't use any of the information in the endpoint, please do allocate
it statically (i.e. a local variable).

> +	endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(&client->dev), NULL);
> +	if (!endpoint) {
> +		dev_err(dev, "endpoint node not found\n");
> +		return -EINVAL;
> +	}
> +
> +	ret = v4l2_fwnode_endpoint_parse(endpoint, &sensor->ep);
> +	fwnode_handle_put(endpoint);
> +	if (ret) {
> +		dev_err(dev, "Could not parse endpoint\n");
> +		return ret;
> +	}
> +
> +	/* request optional power down pin */
> +	sensor->pwdn_gpio = devm_gpiod_get_optional(dev, "powerdown",
> +						    GPIOD_OUT_HIGH);
> +	if (IS_ERR(sensor->pwdn_gpio))
> +		return PTR_ERR(sensor->pwdn_gpio);
> +
> +	/* request optional reset pin */
> +	sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset",
> +						     GPIOD_OUT_HIGH);
> +	if (IS_ERR(sensor->reset_gpio))
> +		return PTR_ERR(sensor->reset_gpio);
> +
> +	sensor->regmap = devm_regmap_init_i2c(client, &ovm6211_regmap_config);
> +	if (IS_ERR(sensor->regmap)) {
> +		dev_err(dev, "regmap init failed\n");
> +		return PTR_ERR(sensor->regmap);
> +	}
> +
> +	v4l2_i2c_subdev_init(&sensor->sd, client, &ovm6211_subdev_ops);
> +
> +	mutex_init(&sensor->lock);
> +
> +	sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
> +			    V4L2_SUBDEV_FL_HAS_EVENTS;
> +	sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
> +	sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
> +	ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
> +	if (ret)
> +		goto entity_cleanup;
> +
> +	ret = ovm6211_get_regulators(sensor);
> +	if (ret)
> +		goto entity_cleanup;
> +
> +	ret = ovm6211_check_chip_id(sensor);
> +	if (ret)
> +		goto entity_cleanup;
> +
> +	ret = ovm6211_init_controls(sensor);
> +	if (ret)
> +		goto entity_cleanup;
> +
> +	ovm6211_load_regs(sensor, sensor->cur_mode);
> +
> +	ret = v4l2_async_register_subdev_sensor(&sensor->sd);
> +	if (ret)
> +		goto free_ctrls;
> +
> +	return 0;
> +
> +free_ctrls:
> +	v4l2_ctrl_handler_free(&sensor->ctrls.handler);
> +entity_cleanup:
> +	media_entity_cleanup(&sensor->sd.entity);
> +	mutex_destroy(&sensor->lock);
> +
> +	return ret;
> +}
> +
> +static int ovm6211_remove(struct i2c_client *client)
> +{
> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> +	struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
> +
> +	v4l2_async_unregister_subdev(&sensor->sd);
> +	media_entity_cleanup(&sensor->sd.entity);
> +	v4l2_ctrl_handler_free(&sensor->ctrls.handler);
> +	mutex_destroy(&sensor->lock);
> +
> +	return 0;
> +}
> +
> +static const struct i2c_device_id ovm6211_id[] = {
> +	{ "ovm6211", 0 },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(i2c, ovm6211_id);

It seems you can omit this.

> +
> +static const struct of_device_id ovm6211_dt_ids[] = {
> +	{ .compatible = "ovti,ovm6211" },
> +	{ /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, ovm6211_dt_ids);
> +
> +static struct i2c_driver ovm6211_i2c_driver = {
> +	.driver = {
> +		.name  = "ovm6211",
> +		.of_match_table	= ovm6211_dt_ids,
> +	},
> +	.probe_new = ovm6211_probe,
> +	.remove   = ovm6211_remove,
> +};
> +
> +module_i2c_driver(ovm6211_i2c_driver);
> +
> +MODULE_AUTHOR("Petko Manolov <petko.manolov@konsulko.com>");
> +MODULE_DESCRIPTION("OVM6211 MIPI Camera Subdev Driver");
> +MODULE_LICENSE("GPL");
  

Patch

diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index fe66093b8849..aebe6a5f237e 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -1049,6 +1049,16 @@  config VIDEO_OV5648
 	  To compile this driver as a module, choose M here: the
 	  module will be called ov5648.
 
+config VIDEO_OVM6211
+       tristate "OmniVision OVM6211 sensor support"
+       depends on I2C && VIDEO_V4L2
+       help
+         This is a Video4Linux2 sensor driver for the OmniVision
+         OVM6211 camera.
+
+         To compile this driver as a module, choose M here: the
+         module will be called ovm6211.
+
 config VIDEO_OV6650
 	tristate "OmniVision OV6650 sensor support"
 	depends on I2C && VIDEO_V4L2
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index f6b80ef6f41d..9e9998966c18 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -78,6 +78,7 @@  obj-$(CONFIG_VIDEO_OV5670) += ov5670.o
 obj-$(CONFIG_VIDEO_OV5675) += ov5675.o
 obj-$(CONFIG_VIDEO_OV5693) += ov5693.o
 obj-$(CONFIG_VIDEO_OV5695) += ov5695.o
+obj-$(CONFIG_VIDEO_OVM6211) += ovm6211.o
 obj-$(CONFIG_VIDEO_OV6650) += ov6650.o
 obj-$(CONFIG_VIDEO_OV7251) += ov7251.o
 obj-$(CONFIG_VIDEO_OV7640) += ov7640.o
diff --git a/drivers/media/i2c/ovm6211.c b/drivers/media/i2c/ovm6211.c
new file mode 100644
index 000000000000..7cfcc0bf5e48
--- /dev/null
+++ b/drivers/media/i2c/ovm6211.c
@@ -0,0 +1,1018 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 Petko Manolov <petko.manolov@konsulko.com>
+ * Copyright (C) 2021 DEKA Research & Development Corp.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <media/v4l2-async.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-subdev.h>
+
+/* System Control */
+#define OVM6211_SC_MODE_SELECT		0x0100
+#define OVM6211_SC_SOFTWARE_RESET	0x0103
+#define OVM6211_SC_SCCB_ID1		0x0109
+#define OVM6211_SC_CHIP_ID_HIGH		0x300a
+#define OVM6211_SC_CHIP_ID_LOW		0x300b
+#define OVM6211_SC_REG0C		0x300c
+#define OVM6211_SC_REG10		0x3010
+#define OVM6211_SC_MIPI_PHY		0x3012
+#define OVM6211_SC_MIPI_PHY2		0x3013
+#define OVM6211_SC_MIPI_SC_CTRL0	0x3014
+#define OVM6211_SC_MIPI_SC_CTRL1	0x3015
+#define OVM6211_SC_CLKRST0		0x3016
+#define OVM6211_SC_CLKRST1		0x3017
+#define OVM6211_SC_CLKRST2		0x3018
+#define OVM6211_SC_CLKRST3		0x3019
+#define OVM6211_SC_CLKRST4		0x301a
+#define OVM6211_SC_CLKRST5		0x301b
+#define OVM6211_SC_CLKRST6		0x301c
+#define OVM6211_SC_CLOCK_SEL		0x301e
+#define OVM6211_SC_MISC_CTRL		0x301f
+#define OVM6211_SC_LOW_PWR_CTRL		0x3023
+#define OVM6211_SC_REG27		0x3027
+#define OVM6211_SC_GP_IO_IN1		0x3029
+#define OVM6211_SC_GP_IO_IN2		0x302a
+#define OVM6211_SC_SCCB_ID2		0x302b
+#define OVM6211_SC_AUTO_SLEEP_PERIOD0	0x302c
+#define OVM6211_SC_AUTO_SLEEP_PERIOD1	0x302d
+#define OVM6211_SC_AUTO_SLEEP_PERIOD2	0x302e
+#define OVM6211_SC_AUTO_SLEEP_PERIOD3	0x302f
+#define OVM6211_SC_LP_CTRL0		0x3030
+#define OVM6211_SC_REG37		0x3037
+#define OVM6211_SC_REG3B		0x303b
+/* PLL Control */
+#define OVM6211_PLL_PLL0		0x3080
+#define OVM6211_PLL_PLL1		0x3081
+#define OVM6211_PLL_PLL2		0x3082
+#define OVM6211_PLL_PLL18		0x3098
+#define OVM6211_PLL_PLL19		0x3099
+#define OVM6211_PLL_PLL1A		0x309a
+#define OVM6211_PLL_PLL1B		0x309b
+#define OVM6211_PLL_PLL1C		0x309c
+#define OVM6211_PLL_PLL1D		0x309d
+#define OVM6211_PLL_PLL1E		0x309e
+#define OVM6211_PLL_PLL1F		0x309f
+#define OVM6211_PLL_VT_PIX_CLK_DIV	0x30b0
+#define OVM6211_PLL_VT_SYS_CLK_DIV	0x30b1
+#define OVM6211_PLL_MULTIPLIER		0x30b3
+#define OVM6211_PLL_PLL1_PRE_PLL_DIV	0x30b4
+#define OVM6211_PLL_PLL1_OP_PIX_CLK_DIV	0x30b5
+#define OVM6211_PLL_PLL1_OP_SYS_CLK_DIV	0x30b6
+/* SCCB and group hold */
+#define OVM6211_SB_SRB_CTRL		0x3106
+#define OVM6211_SB_SWITCH		0x31ff
+/* AEC/AGC */
+#define OVM6211_AEC_EXPO1		0x3500
+#define OVM6211_AEC_EXPO2		0x3501
+#define OVM6211_AEC_EXPO3		0x3502
+#define OVM6211_AEC_MANUAL		0x3503
+#define OVM6211_AGC_MAN_SNR_GAIN1	0x3504
+#define OVM6211_AGC_MAN_SNR_GAIN2	0x3505
+/* timing conrol registers */
+#define	OVM6211_TVTS_HI			0x380e
+#define	OVM6211_TVTS_LO			0x380f
+/*Strobe frame span registers*/
+#define OVM6211_STROBE_SPAN1		0x3b8d
+#define OVM6211_STROBE_SPAN2		0x3b8e
+#define OVM6211_STROBE_SPAN3		0x3b8f
+/* format control */
+#define OVM6211_DATA_MAX_H		0x4300
+#define OVM6211_DATA_MIN_H		0x4301
+#define OVM6211_CLIP_L			0x4302
+#define OVM6211_FORMAT_CTRL3		0x4303
+#define OVM6211_FORMAT_CTRL4		0x4304
+#define OVM6211_VSYNC_WIDTH_H		0x4311
+#define OVM6211_VSYNC_WIDTH_L		0x4312
+#define OVM6211_VSYNC_CTRL		0x4313
+#define OVM6211_VSYNC_DELAY1		0x4314
+#define OVM6211_VSYNC_DELAY2		0x4315
+#define OVM6211_VSYNC_DELAY3		0x4316
+#define OVM6211_TST_PATTERN_CTRL	0x4320
+/* MIPI */
+#define OVM6211_MIPI_CTRL00		0x4800
+#define OVM6211_MIPI_CTRL01		0x4801
+#define OVM6211_MIPI_CTRL02		0x4802
+#define OVM6211_MIPI_CTRL03		0x4803
+#define OVM6211_MIPI_CTRL04		0x4804
+#define OVM6211_MIPI_CTRL05		0x4805
+#define OVM6211_MIPI_CTRL06		0x4806
+#define OVM6211_MIPI_MAX_FRAME_COUNT_H	0x4810
+#define OVM6211_MIPI_MAX_FRAME_COUNT_L	0x4811
+#define OVM6211_MIPI_SHORT_PKT_COUNTER_H	0x4812
+#define OVM6211_MIPI_SHORT_PKT_COUNTER_L	0x4813
+#define OVM6211_MIPI_CTRL14		0x4814
+#define OVM6211_MIPI_DT_SPKT		0x4815
+#define OVM6211_MIPI_REG_MAX_H		0x4835
+#define OVM6211_MIPI_REG_MAX_L		0x4836
+#define OVM6211_PCLK_PERIOD		0x4837
+#define OVM6211_WKUP_DLY		0x4838
+#define OVM6211_DIR_DLY			0x483a
+#define OVM6211_MIPI_LP_GPIO		0x483b
+#define OVM6211_MIPI_CTRL3C		0x483c
+#define OVM6211_T_TA_GO			0x483d
+#define OVM6211_T_TA_SURE		0x483e
+#define OVM6211_T_TA_GET		0x483f
+#define OVM6211_MIPI_CLIP_MAX_H		0x4846
+#define OVM6211_MIPI_CLIP_MAX_L		0x4847
+#define OVM6211_MIPI_CLIP_MIN_H		0x4848
+#define OVM6211_MIPI_CLIP_MIN_L		0x4848
+#define OVM6211_REG_INTR_MAN		0x4850
+#define OVM6211_REG_TX_WR		0x4851
+/* ISP top */
+#define OVM6211_ISP_CTRL00		0x5000
+#define OVM6211_ISP_CTRL01		0x5001
+#define OVM6211_ISP_CTRL02		0x5002
+#define OVM6211_ISP_CTRL03		0x5003
+#define OVM6211_ISP_CTRL04		0x5004
+#define OVM6211_ISP_CTRL05		0x5005
+#define OVM6211_ISP_CTRL06		0x5006
+#define OVM6211_ISP_CTRL07		0x5007
+#define OVM6211_ISP_CTRL08		0x5008
+#define OVM6211_ISP_CTRL09		0x5009
+/* window control */
+#define OVM6211_MAN_XSTART_OFF_H	0x5a00
+#define OVM6211_MAN_XSTART_OFF_L	0x5a01
+#define OVM6211_MAN_YSTART_OFF_H	0x5a02
+#define OVM6211_MAN_YSTART_OFF_L	0x5a03
+#define OVM6211_MAN_WIN_WIDTH_H		0x5a04
+#define OVM6211_MAN_WIN_WIDTH_L		0x5a05
+#define OVM6211_MAN_WIN_HEIGHT_H	0x5a06
+#define OVM6211_MAN_WIN_HEIGHT_L	0x5a07
+#define OVM6211_WIN_MAN			0x5a08
+
+#define	OVM6211_LAST_REG		0x5e08
+/* end of OVM6211 definitions */
+
+#define	DEF_LINK_FREQ			38400000LL
+
+enum ovm6211_mode_id {
+	OVM6211_MODE_Y8_400_200 = 0,
+	OVM6211_MODE_Y8_400_400,
+	OVM6211_NUM_MODES,
+};
+
+struct ovm6211_pixfmt {
+	u32 code;
+	u32 colorspace;
+};
+
+static const struct ovm6211_pixfmt ovm6211_formats[] = {
+	{ MEDIA_BUS_FMT_Y8_1X8,   V4L2_COLORSPACE_RAW, },
+	{ MEDIA_BUS_FMT_Y8_1X8,   V4L2_COLORSPACE_RAW, },
+};
+
+enum ovm6211_framerate_ids {
+	OVM6211_10_FPS = 0,
+	OVM6211_15_FPS,
+	OVM6211_30_FPS,
+	OVM6211_45_FPS,
+	OVM6211_60_FPS,
+	OVM6211_NUM_FRAMERATES,
+};
+
+static const int ovm6211_framerates[] = {
+	[OVM6211_10_FPS] = 10,
+	[OVM6211_15_FPS] = 15,
+	[OVM6211_30_FPS] = 30,
+	[OVM6211_45_FPS] = 45,
+	[OVM6211_60_FPS] = 60,
+};
+
+/* regulator supplies */
+static const char * const ovm6211_supply_name[] = {
+	"dovdd",
+	"avdd",
+};
+
+static const struct regmap_config ovm6211_regmap_config = {
+	.reg_bits       = 16,
+	.val_bits       = 8,
+	.max_register   = OVM6211_LAST_REG,
+	.cache_type	= REGCACHE_NONE,
+};
+
+struct reg_value {
+	u16 reg_addr;
+	u8 val;
+	u8 mask;
+	u32 delay_ms;
+};
+
+struct ovm6211_mode_info {
+	enum ovm6211_mode_id id;
+	u32 width;
+	u32 height;
+	const struct reg_value *reg_data;
+	u32 reg_data_size;
+	u32 pixel_clock;
+};
+
+struct ovm6211_ctrls {
+	struct v4l2_ctrl_handler handler;
+	struct {
+		struct v4l2_ctrl *auto_exp;
+		struct v4l2_ctrl *exposure;
+	};
+	struct {
+		struct v4l2_ctrl *auto_gain;
+		struct v4l2_ctrl *gain;
+	};
+	struct v4l2_ctrl *link_freq;
+};
+
+struct ovm6211_dev {
+	struct i2c_client *i2c_client;
+	struct regmap *regmap;
+	struct v4l2_subdev sd;
+	struct media_pad pad;
+	struct v4l2_fwnode_endpoint ep; /* the parsed DT endpoint info */
+
+	struct regulator_bulk_data supplies[ARRAY_SIZE(ovm6211_supply_name)];
+	struct gpio_desc *reset_gpio;
+	struct gpio_desc *pwdn_gpio;
+
+	struct mutex lock;
+
+	struct v4l2_mbus_framefmt fmt;
+
+	const struct ovm6211_mode_info *cur_mode;
+	enum ovm6211_framerate_ids cur_fr_id;
+	struct v4l2_fract frame_interval;
+
+	struct ovm6211_ctrls ctrls;
+
+	u32 exposure;
+	bool pending_mode_change;
+	bool pending_fi_change;
+	bool streaming;
+};
+
+static inline struct ovm6211_dev *to_ovm6211_dev(struct v4l2_subdev *sd)
+{
+	return container_of(sd, struct ovm6211_dev, sd);
+}
+
+static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl)
+{
+	return &container_of(ctrl->handler, struct ovm6211_dev,
+			     ctrls.handler)->sd;
+}
+
+static const struct reg_value ovm6211_init_y8_400_400[] = {
+	{0x0103, 0x01, 0, 0}, {0x0100, 0x00, 0, 0}, {0x3005, 0x08, 0, 0},
+	{0x3013, 0x12, 0, 0}, {0x3014, 0x04, 0, 0}, {0x3016, 0x10, 0, 0},
+	{0x3017, 0x00, 0, 0}, {0x3018, 0x00, 0, 0}, {0x301a, 0x00, 0, 0},
+	{0x301b, 0x00, 0, 0}, {0x301c, 0x00, 0, 0}, {0x3037, 0xf0, 0, 0},
+	{0x3080, 0x01, 0, 0}, {0x3081, 0x00, 0, 0}, {0x3082, 0x01, 0, 0},
+	{0x3098, 0x04, 0, 0}, {0x3099, 0x28, 0, 0}, {0x309a, 0x06, 0, 0},
+	{0x309b, 0x04, 0, 0}, {0x309c, 0x00, 0, 0}, {0x309d, 0x00, 0, 0},
+	{0x309e, 0x01, 0, 0}, {0x309f, 0x00, 0, 0}, {0x30b0, 0x08, 0, 0},
+	{0x30b1, 0x02, 0, 0}, {0x30b2, 0x00, 0, 0}, {0x30b3, 0x28, 0, 0},
+	{0x30b4, 0x02, 0, 0}, {0x30b5, 0x00, 0, 0}, {0x3106, 0xd9, 0, 0},
+	{0x3500, 0x00, 0, 0}, {0x3501, 0x1b, 0, 0}, {0x3502, 0x20, 0, 0},
+	{0x3503, 0x07, 0, 0}, {0x3509, 0x10, 0, 0}, {0x350b, 0x10, 0, 0},
+	{0x3600, 0xfc, 0, 0}, {0x3620, 0xb7, 0, 0}, {0x3621, 0x05, 0, 0},
+	{0x3626, 0x31, 0, 0}, {0x3627, 0x40, 0, 0}, {0x3632, 0xa3, 0, 0},
+	{0x3633, 0x34, 0, 0}, {0x3634, 0x40, 0, 0}, {0x3636, 0x00, 0, 0},
+	{0x3660, 0x80, 0, 0}, {0x3662, 0x03, 0, 0}, {0x3664, 0xf0, 0, 0},
+	{0x366a, 0x10, 0, 0}, {0x366b, 0x06, 0, 0}, {0x3680, 0xf4, 0, 0},
+	{0x3681, 0x50, 0, 0}, {0x3682, 0x00, 0, 0}, {0x3708, 0x20, 0, 0},
+	{0x3709, 0x40, 0, 0}, {0x370d, 0x03, 0, 0}, {0x373b, 0x02, 0, 0},
+	{0x373c, 0x08, 0, 0}, {0x3742, 0x00, 0, 0}, {0x3744, 0x16, 0, 0},
+	{0x3745, 0x08, 0, 0}, {0x3781, 0xfc, 0, 0}, {0x3788, 0x00, 0, 0},
+	{0x3800, 0x00, 0, 0}, {0x3801, 0x04, 0, 0}, {0x3802, 0x00, 0, 0},
+	{0x3803, 0x04, 0, 0}, {0x3804, 0x01, 0, 0}, {0x3805, 0x9b, 0, 0},
+	{0x3806, 0x01, 0, 0}, {0x3807, 0x9b, 0, 0}, {0x3808, 0x01, 0, 0},
+	{0x3809, 0x90, 0, 0}, {0x380a, 0x01, 0, 0}, {0x380b, 0x90, 0, 0},
+	{0x380c, 0x05, 0, 0}, {0x380d, 0xf2, 0, 0}, {0x380e, 0x03, 0, 0},
+	{0x380f, 0x6c, 0, 0}, {0x3810, 0x00, 0, 0}, {0x3811, 0x04, 0, 0},
+	{0x3812, 0x00, 0, 0}, {0x3813, 0x04, 0, 0}, {0x3814, 0x11, 0, 0},
+	{0x3815, 0x11, 0, 0}, {0x3820, 0x00, 0, 0}, {0x3821, 0x00, 0, 0},
+	{0x382b, 0xfa, 0, 0}, {0x382f, 0x04, 0, 0}, {0x3832, 0x00, 0, 0},
+	{0x3833, 0x05, 0, 0}, {0x3834, 0x00, 0, 0}, {0x3835, 0x05, 0, 0},
+	{0x3882, 0x04, 0, 0}, {0x3883, 0x00, 0, 0}, {0x38a4, 0x10, 0, 0},
+	{0x38a5, 0x00, 0, 0}, {0x38b1, 0x03, 0, 0}, {0x3b80, 0x00, 0, 0},
+	{0x3b81, 0xff, 0, 0}, {0x3b82, 0x10, 0, 0}, {0x3b83, 0x00, 0, 0},
+	{0x3b84, 0x08, 0, 0}, {0x3b85, 0x00, 0, 0}, {0x3b86, 0x01, 0, 0},
+	{0x3b87, 0x00, 0, 0}, {0x3b88, 0x00, 0, 0}, {0x3b89, 0x00, 0, 0},
+	{0x3b8a, 0x00, 0, 0}, {0x3b8b, 0x05, 0, 0}, {0x3b8c, 0x00, 0, 0},
+	{0x3b8d, 0x00, 0, 0}, {0x3b8e, 0x01, 0, 0}, {0x3b8f, 0xb2, 0, 0},
+	{0x3b94, 0x05, 0, 0}, {0x3b95, 0xf2, 0, 0}, {0x3b96, 0xc0, 0, 0},
+	{0x4004, 0x04, 0, 0}, {0x404e, 0x01, 0, 0}, {0x4801, 0x0f, 0, 0},
+	{0x4806, 0x0f, 0, 0}, {0x4837, 0x43, 0, 0}, {0x5a08, 0x00, 0, 0},
+	{0x5a01, 0x00, 0, 0}, {0x5a03, 0x00, 0, 0}, {0x5a04, 0x10, 0, 0},
+	{0x5a05, 0xa0, 0, 0}, {0x5a06, 0x0c, 0, 0}, {0x5a07, 0x78, 0, 0},
+};
+
+static const struct reg_value ovm6211_init_y8_400_200[] = {
+	{0x0103, 0x01, 0, 0}, {0x0100, 0x00, 0, 0}, {0x3005, 0x08, 0, 0},
+	{0x3013, 0x12, 0, 0}, {0x3014, 0x04, 0, 0}, {0x3016, 0x10, 0, 0},
+	{0x3017, 0x00, 0, 0}, {0x3018, 0x00, 0, 0}, {0x301a, 0x00, 0, 0},
+	{0x301b, 0x00, 0, 0}, {0x301c, 0x00, 0, 0}, {0x3037, 0xf0, 0, 0},
+	{0x3080, 0x01, 0, 0}, {0x3081, 0x00, 0, 0}, {0x3082, 0x01, 0, 0},
+	{0x3098, 0x04, 0, 0}, {0x3099, 0x28, 0, 0}, {0x309a, 0x06, 0, 0},
+	{0x309b, 0x04, 0, 0}, {0x309c, 0x00, 0, 0}, {0x309d, 0x00, 0, 0},
+	{0x309e, 0x01, 0, 0}, {0x309f, 0x00, 0, 0}, {0x30b0, 0x08, 0, 0},
+	{0x30b1, 0x02, 0, 0}, {0x30b2, 0x00, 0, 0}, {0x30b3, 0x28, 0, 0},
+	{0x30b4, 0x02, 0, 0}, {0x30b5, 0x00, 0, 0}, {0x3106, 0xd9, 0, 0},
+	{0x3500, 0x00, 0, 0}, {0x3501, 0x1b, 0, 0}, {0x3502, 0x20, 0, 0},
+	{0x3503, 0x07, 0, 0}, {0x3509, 0x10, 0, 0}, {0x350b, 0x10, 0, 0},
+	{0x3600, 0xfc, 0, 0}, {0x3620, 0xb7, 0, 0}, {0x3621, 0x05, 0, 0},
+	{0x3626, 0x31, 0, 0}, {0x3627, 0x40, 0, 0}, {0x3632, 0xa3, 0, 0},
+	{0x3633, 0x34, 0, 0}, {0x3634, 0x40, 0, 0}, {0x3636, 0x00, 0, 0},
+	{0x3660, 0x80, 0, 0}, {0x3662, 0x03, 0, 0}, {0x3664, 0xf0, 0, 0},
+	{0x366a, 0x10, 0, 0}, {0x366b, 0x06, 0, 0}, {0x3680, 0xf4, 0, 0},
+	{0x3681, 0x50, 0, 0}, {0x3682, 0x00, 0, 0}, {0x3708, 0x20, 0, 0},
+	{0x3709, 0x40, 0, 0}, {0x370d, 0x03, 0, 0}, {0x373b, 0x02, 0, 0},
+	{0x373c, 0x08, 0, 0}, {0x3742, 0x00, 0, 0}, {0x3744, 0x16, 0, 0},
+	{0x3745, 0x08, 0, 0}, {0x3781, 0xfc, 0, 0}, {0x3788, 0x00, 0, 0},
+	{0x3800, 0x00, 0, 0}, {0x3801, 0x04, 0, 0}, {0x3802, 0x00, 0, 0},
+	{0x3803, 0x04, 0, 0}, {0x3804, 0x01, 0, 0}, {0x3805, 0x9b, 0, 0},
+	{0x3806, 0x01, 0, 0}, {0x3807, 0x9b, 0, 0}, {0x3808, 0x01, 0, 0},
+	{0x3809, 0x90, 0, 0}, {0x380a, 0x00, 0, 0}, {0x380b, 0xc8, 0, 0},
+	{0x380c, 0x05, 0, 0}, {0x380d, 0xf2, 0, 0}, {0x380e, 0x0d, 0, 0},
+	{0x380f, 0xb0, 0, 0}, {0x3810, 0x00, 0, 0}, {0x3811, 0x04, 0, 0},
+	{0x3812, 0x00, 0, 0}, {0x3813, 0x9a, 0, 0}, {0x3814, 0x11, 0, 0},
+	{0x3815, 0x11, 0, 0}, {0x3820, 0x00, 0, 0}, {0x3821, 0x00, 0, 0},
+	{0x382b, 0xfa, 0, 0}, {0x382f, 0x04, 0, 0}, {0x3832, 0x00, 0, 0},
+	{0x3833, 0x05, 0, 0}, {0x3834, 0x00, 0, 0}, {0x3835, 0x05, 0, 0},
+	{0x3882, 0x04, 0, 0}, {0x3883, 0x00, 0, 0}, {0x38a4, 0x10, 0, 0},
+	{0x38a5, 0x00, 0, 0}, {0x38b1, 0x03, 0, 0}, {0x3b80, 0x00, 0, 0},
+	{0x3b81, 0xff, 0, 0}, {0x3b82, 0x10, 0, 0}, {0x3b83, 0x00, 0, 0},
+	{0x3b84, 0x08, 0, 0}, {0x3b85, 0x00, 0, 0}, {0x3b86, 0x01, 0, 0},
+	{0x3b87, 0x00, 0, 0}, {0x3b88, 0x00, 0, 0}, {0x3b89, 0x00, 0, 0},
+	{0x3b8a, 0x00, 0, 0}, {0x3b8b, 0x05, 0, 0}, {0x3b8c, 0x00, 0, 0},
+	{0x3b8d, 0x00, 0, 0}, {0x3b8e, 0x01, 0, 0}, {0x3b8f, 0xb2, 0, 0},
+	{0x3b94, 0x05, 0, 0}, {0x3b95, 0xf2, 0, 0}, {0x3b96, 0xc0, 0, 0},
+	{0x4004, 0x04, 0, 0}, {0x404e, 0x01, 0, 0}, {0x4801, 0x0f, 0, 0},
+	{0x4806, 0x0f, 0, 0}, {0x4837, 0x43, 0, 0}, {0x5a08, 0x00, 0, 0},
+	{0x5a01, 0x00, 0, 0}, {0x5a03, 0x00, 0, 0}, {0x5a04, 0x10, 0, 0},
+	{0x5a05, 0xa0, 0, 0}, {0x5a06, 0x0c, 0, 0}, {0x5a07, 0x78, 0, 0},
+};
+
+static struct ovm6211_mode_info
+ovm6211_mode_data[OVM6211_NUM_MODES] = {
+	{OVM6211_MODE_Y8_400_200,
+	 400, 200,
+	 ovm6211_init_y8_400_200,
+	 ARRAY_SIZE(ovm6211_init_y8_400_200),
+	 400 * 400 * 60 * 2,
+	},
+	{OVM6211_MODE_Y8_400_400,
+	 400, 400,
+	 ovm6211_init_y8_400_400,
+	 ARRAY_SIZE(ovm6211_init_y8_400_400),
+	 400 * 400 * 60 * 2,
+	},
+};
+
+static const s64 link_freq_menu_items[] = {
+	DEF_LINK_FREQ,
+};
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int ovm6211_get_register(struct v4l2_subdev *sd,
+				struct v4l2_dbg_register *reg)
+{
+	struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
+	struct regmap *regmap = sensor->regmap;
+	unsigned int val = 0;
+	int ret;
+
+	ret = regmap_read(regmap, reg->reg, &val);
+	reg->val = val;
+	reg->size = 1;
+
+	return ret;
+}
+
+static int ovm6211_set_register(struct v4l2_subdev *sd,
+				const struct v4l2_dbg_register *reg)
+{
+	struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
+	struct regmap *regmap = sensor->regmap;
+
+	return regmap_write(regmap, reg->reg, reg->val & 0xff);
+}
+#endif
+
+static int ovm6211_write_reg(struct ovm6211_dev *sensor, u16 reg, u8 val)
+{
+	struct i2c_client *client = sensor->i2c_client;
+	struct regmap *regmap = sensor->regmap;
+	int ret;
+
+	ret = regmap_write(regmap, reg, val);
+	if (ret < 0)
+		dev_err(&client->dev, "error writing reg %u\n", reg);
+
+	return ret;
+}
+
+static int ovm6211_read_reg(struct ovm6211_dev *sensor, u16 reg, u8 *val)
+{
+	struct i2c_client *client = sensor->i2c_client;
+	struct regmap *regmap = sensor->regmap;
+	unsigned int r;
+	int ret;
+
+	ret = regmap_read(regmap, reg, &r);
+	if (ret < 0)
+		dev_err(&client->dev, "error reading reg %u\n", reg);
+	*val = r & 0xff;
+
+	return ret;
+}
+
+static int ovm6211_mod_reg(struct ovm6211_dev *sensor, u16 reg, u8 mask, u8 val)
+{
+	u8 readval;
+	int ret;
+
+	ret = ovm6211_read_reg(sensor, reg, &readval);
+	if (ret)
+		return ret;
+
+	readval &= ~mask;
+	val &= mask;
+	val |= readval;
+
+	return ovm6211_write_reg(sensor, reg, val);
+}
+
+static int ovm6211_load_regs(struct ovm6211_dev *sensor,
+			     const struct ovm6211_mode_info *mode)
+{
+	const struct reg_value *regs = mode->reg_data;
+	unsigned int i;
+	u32 delay_ms;
+	u16 reg_addr;
+	u8 mask, val;
+	int ret = 0;
+
+	for (i = 0; i < mode->reg_data_size; ++i, ++regs) {
+		delay_ms = regs->delay_ms;
+		reg_addr = regs->reg_addr;
+		val = regs->val;
+		mask = regs->mask;
+
+		if (mask)
+			ret = ovm6211_mod_reg(sensor, reg_addr, mask, val);
+		else
+			ret = ovm6211_write_reg(sensor, reg_addr, val);
+
+		if (ret)
+			break;
+
+		if (delay_ms)
+			usleep_range(1000 * delay_ms, 1000 * delay_ms + 100);
+	}
+
+	return ret;
+}
+
+static void ovm6211_soft_reset(struct ovm6211_dev *sensor)
+{
+	ovm6211_write_reg(sensor, OVM6211_SC_SOFTWARE_RESET, 0x01);
+	usleep_range(5000, 9000);
+	ovm6211_write_reg(sensor, OVM6211_SC_SOFTWARE_RESET, 0x00);
+}
+
+static int ovm6211_set_exposure(struct ovm6211_dev *sensor, u32 exposure)
+{
+	u32 ce;
+	u8 v;
+
+	ovm6211_read_reg(sensor, OVM6211_TVTS_HI, &v);
+	ce = v << 8;
+	ovm6211_read_reg(sensor, OVM6211_TVTS_LO, &v);
+	ce |= v;
+	ce -= 4;
+
+	if (ce < exposure)
+		exposure = ce;
+
+	ovm6211_mod_reg(sensor, OVM6211_AEC_MANUAL, 1, 1);
+
+	ovm6211_write_reg(sensor, OVM6211_AEC_EXPO1, (exposure >> 12) & 0x0f);
+	ovm6211_write_reg(sensor, OVM6211_AEC_EXPO2, (exposure >>  4) & 0xff);
+	ovm6211_write_reg(sensor, OVM6211_AEC_EXPO3, (exposure <<  4) & 0xf0);
+
+	/* set strobe width equal to exposure time */
+	ovm6211_write_reg(sensor, OVM6211_STROBE_SPAN1,
+			  (exposure >> 16) & 0xff);
+	ovm6211_write_reg(sensor, OVM6211_STROBE_SPAN2,
+			  (exposure >> 8) & 0xff);
+	ovm6211_write_reg(sensor, OVM6211_STROBE_SPAN3, (exposure) & 0xff);
+
+	return 0;
+}
+
+static int internal_set_stream(struct ovm6211_dev *sensor, bool on)
+{
+	u8 hi, lo;
+
+	if (sensor->pending_fi_change == false)
+		goto stream;
+
+	switch (sensor->cur_fr_id) {
+	case OVM6211_10_FPS:
+		hi = 0x14;
+		lo = 0x88;
+		break;
+	case OVM6211_15_FPS:
+		hi = 0x0d;
+		lo = 0xb0;
+		break;
+	case OVM6211_30_FPS:
+		hi = 0x06;
+		lo = 0xd8;
+		break;
+	case OVM6211_45_FPS:
+		hi = 0x04;
+		lo = 0x90;
+		break;
+	case OVM6211_60_FPS:
+		hi = 0x03;
+		lo = 0x6c;
+		break;
+	case OVM6211_NUM_FRAMERATES:
+		return -EINVAL;
+	}
+
+	sensor->pending_fi_change = false;
+	ovm6211_write_reg(sensor, OVM6211_TVTS_HI, hi);
+	ovm6211_write_reg(sensor, OVM6211_TVTS_LO, lo);
+stream:
+	ovm6211_write_reg(sensor, OVM6211_SC_MODE_SELECT, 0);
+	if (on) {
+		usleep_range(4000, 5000);
+		if (sensor->exposure)
+			ovm6211_set_exposure(sensor, sensor->exposure);
+		ovm6211_write_reg(sensor, OVM6211_SC_MODE_SELECT, 1);
+		sensor->streaming = true;
+	} else {
+		sensor->streaming = false;
+	}
+
+	return 0;
+}
+
+static struct ovm6211_mode_info *ovm6211_find_mode(int w, int h)
+{
+	return v4l2_find_nearest_size(ovm6211_mode_data,
+				      ARRAY_SIZE(ovm6211_mode_data),
+				      width, height, w, h);
+}
+
+static int ovm6211_set_mode(struct ovm6211_dev *sensor)
+{
+	const struct ovm6211_mode_info *mode = sensor->cur_mode;
+
+	ovm6211_soft_reset(sensor);
+	ovm6211_load_regs(sensor, mode);
+
+	return 0;
+}
+
+/* --------------- Subdev Operations --------------- */
+
+static int ovm6211_get_fmt(struct v4l2_subdev *sd,
+			   struct v4l2_subdev_state *state,
+			   struct v4l2_subdev_format *format)
+{
+	struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
+	struct v4l2_mbus_framefmt *fmt;
+
+	mutex_lock(&sensor->lock);
+
+	if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
+		fmt = v4l2_subdev_get_try_format(&sensor->sd, state, format->pad);
+		format->format = *fmt;
+	} else {
+		/* these are hardcoded as we don't support anything else */
+		format->format.colorspace  = V4L2_COLORSPACE_RAW;
+		format->format.field = V4L2_FIELD_NONE;
+		format->format.code = MEDIA_BUS_FMT_Y8_1X8;
+		format->format.width = sensor->cur_mode->width;
+		format->format.height = sensor->cur_mode->height;
+	}
+
+	mutex_unlock(&sensor->lock);
+
+	return 0;
+}
+
+static int ovm6211_set_fmt(struct v4l2_subdev *sd,
+			   struct v4l2_subdev_state *state,
+			   struct v4l2_subdev_format *format)
+{
+	struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
+	struct v4l2_mbus_framefmt *fmt = &format->format;
+	struct ovm6211_mode_info *mode;
+
+	mutex_lock(&sensor->lock);
+
+	if (sensor->streaming)
+		return -EBUSY;
+
+	/* these are hardcoded as we don't support anything else */
+	format->format.colorspace  = V4L2_COLORSPACE_RAW;
+	format->format.field = V4L2_FIELD_NONE;
+	format->format.code = MEDIA_BUS_FMT_Y8_1X8;
+	mode = ovm6211_find_mode(format->format.width, format->format.height);
+	format->format.width = mode->width;
+	format->format.height = mode->height;
+
+	if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
+		fmt = v4l2_subdev_get_try_format(&sensor->sd, state, format->pad);
+		*fmt = format->format;
+		goto out;
+	}
+	sensor->cur_mode = mode;
+out:
+	mutex_unlock(&sensor->lock);
+
+	return 0;
+}
+
+/*
+ * Sensor Controls.
+ */
+
+static int ovm6211_set_ctrl_exposure(struct ovm6211_dev *sensor, enum
+				     v4l2_exposure_auto_type auto_exposure)
+{
+	struct ovm6211_ctrls *ctrls = &sensor->ctrls;
+
+	if (auto_exposure == V4L2_EXPOSURE_AUTO) {
+		sensor->exposure = 0;
+	} else {
+		sensor->exposure = ctrls->exposure->val;
+		ovm6211_set_exposure(sensor, sensor->exposure);
+	}
+
+	return 0;
+}
+
+static int ovm6211_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
+	struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
+	int ret;
+
+	switch (ctrl->id) {
+	case V4L2_CID_EXPOSURE_AUTO:
+		ret = ovm6211_set_ctrl_exposure(sensor, ctrl->val);
+		break;
+	case V4L2_CID_LINK_FREQ:
+		return 0;
+	case V4L2_CID_PIXEL_RATE:
+		return 0;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static const struct v4l2_ctrl_ops ovm6211_ctrl_ops = {
+	.s_ctrl = ovm6211_s_ctrl,
+};
+
+static int ovm6211_init_controls(struct ovm6211_dev *sensor)
+{
+	const struct v4l2_ctrl_ops *ops = &ovm6211_ctrl_ops;
+	struct ovm6211_ctrls *ctrls = &sensor->ctrls;
+	struct v4l2_ctrl_handler *hdl = &ctrls->handler;
+	int ret;
+
+	v4l2_ctrl_handler_init(hdl, 16);
+
+	hdl->lock = &sensor->lock;
+
+	ctrls->auto_exp = v4l2_ctrl_new_std_menu(hdl, ops, V4L2_CID_EXPOSURE_AUTO,
+						 V4L2_EXPOSURE_MANUAL, 0,
+						 V4L2_EXPOSURE_AUTO);
+	ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE,
+					    0, 65535, 1, 0);
+	v4l2_ctrl_auto_cluster(3, &ctrls->auto_exp, 0, false);
+	ctrls->link_freq = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ,
+						  0, 0, link_freq_menu_items);
+
+	if (hdl->error) {
+		ret = hdl->error;
+		goto free_ctrls;
+	}
+
+	ctrls->exposure->flags |= V4L2_CTRL_FLAG_VOLATILE;
+	sensor->sd.ctrl_handler = hdl;
+
+	return 0;
+
+free_ctrls:
+	v4l2_ctrl_handler_free(hdl);
+	return ret;
+}
+
+static int ovm6211_g_frame_interval(struct v4l2_subdev *sd,
+				    struct v4l2_subdev_frame_interval *fi)
+{
+	struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
+
+	mutex_lock(&sensor->lock);
+	fi->interval = sensor->frame_interval;
+	mutex_unlock(&sensor->lock);
+
+	return 0;
+}
+
+static int internal_set_frame_interval(struct ovm6211_dev *sensor, struct
+				       v4l2_subdev_frame_interval *fi)
+{
+	u32 fr_rate;
+	int i, ret = -EINVAL;
+
+	if (fi->interval.numerator == 0)
+		goto out;
+
+	fr_rate = fi->interval.denominator / fi->interval.numerator;
+
+	for (i = 0; i < ARRAY_SIZE(ovm6211_framerates); i++) {
+		if (ovm6211_framerates[i] == fr_rate)
+			break;
+	}
+
+	if (i == ARRAY_SIZE(ovm6211_framerates))
+		goto out;
+
+	sensor->cur_fr_id = i;
+	sensor->frame_interval = fi->interval;
+	ret = 0;
+out:
+	return ret;
+}
+
+static int ovm6211_s_frame_interval(struct v4l2_subdev *sd,
+				    struct v4l2_subdev_frame_interval *fi)
+{
+	struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
+	int ret;
+
+	mutex_lock(&sensor->lock);
+	ret = internal_set_frame_interval(sensor, fi);
+	sensor->pending_fi_change = true;
+	mutex_unlock(&sensor->lock);
+
+	return ret;
+}
+
+static int ovm6211_enum_mbus_code(struct v4l2_subdev *sd,
+				  struct v4l2_subdev_state *state,
+				  struct v4l2_subdev_mbus_code_enum *code)
+{
+	if (code->index >= ARRAY_SIZE(ovm6211_formats))
+		return -EINVAL;
+
+	code->code = ovm6211_formats[code->index].code;
+
+	return 0;
+}
+
+static int ovm6211_s_stream(struct v4l2_subdev *sd, int enable)
+{
+	struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
+	int ret = 0;
+
+	mutex_lock(&sensor->lock);
+
+	if (enable)
+		ret = ovm6211_set_mode(sensor);
+	internal_set_stream(sensor, enable);
+
+	mutex_unlock(&sensor->lock);
+
+	return ret;
+}
+
+static const struct v4l2_subdev_core_ops ovm6211_core_ops = {
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.g_register = ovm6211_get_register,
+	.s_register = ovm6211_set_register,
+#endif
+};
+
+static const struct v4l2_subdev_video_ops ovm6211_video_ops = {
+	.g_frame_interval = ovm6211_g_frame_interval,
+	.s_frame_interval = ovm6211_s_frame_interval,
+	.s_stream = ovm6211_s_stream,
+};
+
+static const struct v4l2_subdev_pad_ops ovm6211_pad_ops = {
+	.enum_mbus_code = ovm6211_enum_mbus_code,
+	.get_fmt = ovm6211_get_fmt,
+	.set_fmt = ovm6211_set_fmt,
+};
+
+static const struct v4l2_subdev_ops ovm6211_subdev_ops = {
+	.core = &ovm6211_core_ops,
+	.video = &ovm6211_video_ops,
+	.pad = &ovm6211_pad_ops,
+};
+
+static int ovm6211_get_regulators(struct ovm6211_dev *sensor)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(ovm6211_supply_name); i++)
+		sensor->supplies[i].supply = ovm6211_supply_name[i];
+
+	return devm_regulator_bulk_get(&sensor->i2c_client->dev,
+				       ARRAY_SIZE(ovm6211_supply_name),
+				       sensor->supplies);
+}
+
+static int ovm6211_check_chip_id(struct ovm6211_dev *sensor)
+{
+	struct i2c_client *client = sensor->i2c_client;
+	struct regmap *regmap = sensor->regmap;
+	unsigned int cid;
+	int ret = 0;
+
+	ret = regmap_read(regmap, OVM6211_SC_CHIP_ID_HIGH, &cid);
+	if (ret || cid != 0x67) {
+		ret = ENXIO;
+		goto err;
+	}
+
+	ret = regmap_read(regmap, OVM6211_SC_CHIP_ID_LOW, &cid);
+	if (ret || cid != 0x10) {
+		ret = -ENXIO;
+		goto err;
+	}
+
+	ret = regmap_read(regmap, OVM6211_SC_REG0C, &cid);
+	if (ret)
+		goto err;
+
+	dev_info(&client->dev,"found OVM6211, sub revision: 0x%02X\n", cid);
+	return 0;
+err:
+	dev_err(&client->dev, "failed to detect OVM6211\n");
+	return ret;
+}
+
+static int ovm6211_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct fwnode_handle *endpoint;
+	struct ovm6211_dev *sensor;
+	struct v4l2_mbus_framefmt *fmt;
+	int ret;
+
+	sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
+	if (!sensor)
+		return -ENOMEM;
+
+	sensor->i2c_client = client;
+
+	fmt = &sensor->fmt;
+	fmt->code = MEDIA_BUS_FMT_Y8_1X8;
+	fmt->colorspace = V4L2_COLORSPACE_RAW;
+	fmt->field = V4L2_FIELD_NONE;
+	fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
+	fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace);
+	fmt->width = 400;
+	fmt->height = 200;
+
+	sensor->frame_interval.numerator = 1;
+	sensor->frame_interval.denominator = ovm6211_framerates[OVM6211_45_FPS];
+	sensor->cur_fr_id = OVM6211_45_FPS;
+	sensor->cur_mode = &ovm6211_mode_data[OVM6211_MODE_Y8_400_200];
+
+	sensor->ep.bus_type = V4L2_MBUS_CSI2_DPHY;
+	endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(&client->dev), NULL);
+	if (!endpoint) {
+		dev_err(dev, "endpoint node not found\n");
+		return -EINVAL;
+	}
+
+	ret = v4l2_fwnode_endpoint_parse(endpoint, &sensor->ep);
+	fwnode_handle_put(endpoint);
+	if (ret) {
+		dev_err(dev, "Could not parse endpoint\n");
+		return ret;
+	}
+
+	/* request optional power down pin */
+	sensor->pwdn_gpio = devm_gpiod_get_optional(dev, "powerdown",
+						    GPIOD_OUT_HIGH);
+	if (IS_ERR(sensor->pwdn_gpio))
+		return PTR_ERR(sensor->pwdn_gpio);
+
+	/* request optional reset pin */
+	sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+						     GPIOD_OUT_HIGH);
+	if (IS_ERR(sensor->reset_gpio))
+		return PTR_ERR(sensor->reset_gpio);
+
+	sensor->regmap = devm_regmap_init_i2c(client, &ovm6211_regmap_config);
+	if (IS_ERR(sensor->regmap)) {
+		dev_err(dev, "regmap init failed\n");
+		return PTR_ERR(sensor->regmap);
+	}
+
+	v4l2_i2c_subdev_init(&sensor->sd, client, &ovm6211_subdev_ops);
+
+	mutex_init(&sensor->lock);
+
+	sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
+			    V4L2_SUBDEV_FL_HAS_EVENTS;
+	sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
+	sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
+	ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
+	if (ret)
+		goto entity_cleanup;
+
+	ret = ovm6211_get_regulators(sensor);
+	if (ret)
+		goto entity_cleanup;
+
+	ret = ovm6211_check_chip_id(sensor);
+	if (ret)
+		goto entity_cleanup;
+
+	ret = ovm6211_init_controls(sensor);
+	if (ret)
+		goto entity_cleanup;
+
+	ovm6211_load_regs(sensor, sensor->cur_mode);
+
+	ret = v4l2_async_register_subdev_sensor(&sensor->sd);
+	if (ret)
+		goto free_ctrls;
+
+	return 0;
+
+free_ctrls:
+	v4l2_ctrl_handler_free(&sensor->ctrls.handler);
+entity_cleanup:
+	media_entity_cleanup(&sensor->sd.entity);
+	mutex_destroy(&sensor->lock);
+
+	return ret;
+}
+
+static int ovm6211_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct ovm6211_dev *sensor = to_ovm6211_dev(sd);
+
+	v4l2_async_unregister_subdev(&sensor->sd);
+	media_entity_cleanup(&sensor->sd.entity);
+	v4l2_ctrl_handler_free(&sensor->ctrls.handler);
+	mutex_destroy(&sensor->lock);
+
+	return 0;
+}
+
+static const struct i2c_device_id ovm6211_id[] = {
+	{ "ovm6211", 0 },
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, ovm6211_id);
+
+static const struct of_device_id ovm6211_dt_ids[] = {
+	{ .compatible = "ovti,ovm6211" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ovm6211_dt_ids);
+
+static struct i2c_driver ovm6211_i2c_driver = {
+	.driver = {
+		.name  = "ovm6211",
+		.of_match_table	= ovm6211_dt_ids,
+	},
+	.probe_new = ovm6211_probe,
+	.remove   = ovm6211_remove,
+};
+
+module_i2c_driver(ovm6211_i2c_driver);
+
+MODULE_AUTHOR("Petko Manolov <petko.manolov@konsulko.com>");
+MODULE_DESCRIPTION("OVM6211 MIPI Camera Subdev Driver");
+MODULE_LICENSE("GPL");