Hi Petko, Thanks for the update. Please also cc me on the next time, and add DT bindings to the set. On Sun, Jan 23, 2022 at 04:21:34PM +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@xxxxxxxxxxxx> > --- > drivers/media/i2c/Kconfig | 10 + > drivers/media/i2c/Makefile | 1 + > drivers/media/i2c/ovm6211.c | 1032 +++++++++++++++++++++++++++++++++++ > 3 files changed, 1043 insertions(+) > create mode 100644 drivers/media/i2c/ovm6211.c > > diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig > index 69c56e24a612..336436e2413a 100644 > --- a/drivers/media/i2c/Kconfig > +++ b/drivers/media/i2c/Kconfig > @@ -1023,6 +1023,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 b01f6cd05ee8..385031c87d23 100644 > --- a/drivers/media/i2c/Makefile > +++ b/drivers/media/i2c/Makefile > @@ -77,6 +77,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..8958d7149f15 > --- /dev/null > +++ b/drivers/media/i2c/ovm6211.c > @@ -0,0 +1,1032 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (C) 2021 Petko Manolov <petko.manolov@xxxxxxxxxxxx> > + * 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 */ > + > +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 *brightness; > + struct v4l2_ctrl *light_freq; > + struct v4l2_ctrl *saturation; > + struct v4l2_ctrl *contrast; > + struct v4l2_ctrl *hue; > + struct v4l2_ctrl *test_pattern; > + struct v4l2_ctrl *link_freq; > + struct v4l2_ctrl *pixel_clock; These seem to be still here... > +}; > + > +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[] = { > + 0, This value isn't correct. Please use the actual link frequency here. > +}; > + > +#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); > + } Unnecessary braces. > + } > + > + 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); Please wrap lines over 80, as I've requested before, unless it's really justifiable to have longer lines (that doesn't seem to be the case in this set). This is the practice in the media tree. > + > + 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); > + ctrls->pixel_clock = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, > + 1, INT_MAX, 1, 38400000); > + > + 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, max, ret = -EINVAL; > + > + if (fi->interval.numerator == 0) > + goto out; > + > + fr_rate = fi->interval.denominator / fi->interval.numerator; > + > + max = ARRAY_SIZE(ovm6211_framerates); > + for (i = 0; i < max; i++) { s/max/ARRAY_SIZE(ovm6211_framerates)/ > + if (ovm6211_framerates[i] == fr_rate) > + break; > + } > + > + if (i == max) > + 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 int ovm6211_set_power(struct v4l2_subdev *sd, int on) Please drop the callback, you don't need it. > +{ > + return 0; > +} > + > +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 > + .s_power = ovm6211_set_power, > +}; > + > +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_set_power(&sensor->sd, 1); > + 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@xxxxxxxxxxxx>"); > +MODULE_DESCRIPTION("OVM6211 MIPI Camera Subdev Driver"); > +MODULE_LICENSE("GPL"); -- Kind regards, Sakari Ailus