linux-media.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v5 0/2] media: ovm6211: Adds support for OVM6211
@ 2022-03-09 10:22 Petko Manolov
  2022-03-09 10:22 ` [PATCH v5 1/2] " Petko Manolov
  2022-03-09 10:22 ` [PATCH v5 2/2] dt-bindings: media: add ovti,ovm6211 bindings Petko Manolov
  0 siblings, 2 replies; 9+ messages in thread
From: Petko Manolov @ 2022-03-09 10:22 UTC (permalink / raw)
  To: linux-media; +Cc: sakari.ailus, devicetree, Petko Manolov

v5: Moar cleanupm, adds the ovm6211 bindings.

v4: More unused header files removed, use media_entity_cleanup() where necessary
and some other minor cleanups.

v3: As usual, not everything is perfect, hence v3.  The patch set is now merged
into one big patch, redundant code was removed and the end result tested on the
board.  DT bindings are coming some time later.

v2: Removes an unused function (ovm6211_set_pix_clk) and this patch series is
now based on media/master; Didn't receive any comments about the RFC version,
thus i assume everything is perfect... :P

This patch adds ovm6211 driver into the staging directory.  It also creates
media/i2c entry, where ovm6211.c lives for now, to mimic the generic media
source tree.

Petko Manolov (2):
  media: ovm6211: Adds support for OVM6211
  media: add ovti,ovm6211 bindings

 .../devicetree/bindings/media/i2c/ovm6211.txt |   49 +
 drivers/media/i2c/Kconfig                     |   10 +
 drivers/media/i2c/Makefile                    |    1 +
 drivers/media/i2c/ovm6211.c                   | 1018 +++++++++++++++++
 4 files changed, 1078 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/i2c/ovm6211.txt
 create mode 100644 drivers/media/i2c/ovm6211.c


base-commit: 2b891d3980f6c255459d0e1c29ce2152ec7cf678
-- 
2.30.2


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH v5 1/2] media: ovm6211: Adds support for OVM6211
  2022-03-09 10:22 [PATCH v5 0/2] media: ovm6211: Adds support for OVM6211 Petko Manolov
@ 2022-03-09 10:22 ` Petko Manolov
  2022-03-09 11:51   ` Kieran Bingham
                     ` (2 more replies)
  2022-03-09 10:22 ` [PATCH v5 2/2] dt-bindings: media: add ovti,ovm6211 bindings Petko Manolov
  1 sibling, 3 replies; 9+ messages in thread
From: Petko Manolov @ 2022-03-09 10:22 UTC (permalink / raw)
  To: linux-media; +Cc: sakari.ailus, devicetree, Petko Manolov

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 */
+	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");
-- 
2.30.2


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH v5 2/2] dt-bindings: media: add ovti,ovm6211 bindings
  2022-03-09 10:22 [PATCH v5 0/2] media: ovm6211: Adds support for OVM6211 Petko Manolov
  2022-03-09 10:22 ` [PATCH v5 1/2] " Petko Manolov
@ 2022-03-09 10:22 ` Petko Manolov
  2022-03-09 13:59   ` Rob Herring
  1 sibling, 1 reply; 9+ messages in thread
From: Petko Manolov @ 2022-03-09 10:22 UTC (permalink / raw)
  To: linux-media; +Cc: sakari.ailus, devicetree, Petko Manolov

Omnivision OVM6211 MIPI CSI-2 sensor bindings.

Signed-off-by: Petko Manolov <petko.manolov@konsulko.com>
---
 .../devicetree/bindings/media/i2c/ovm6211.txt | 49 +++++++++++++++++++
 1 file changed, 49 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/i2c/ovm6211.txt

diff --git a/Documentation/devicetree/bindings/media/i2c/ovm6211.txt b/Documentation/devicetree/bindings/media/i2c/ovm6211.txt
new file mode 100644
index 000000000000..6883c34106be
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/ovm6211.txt
@@ -0,0 +1,49 @@
+* Omnivision OVM6211 MIPI CSI-2 sensor
+
+The OVM6211 has an image array capable of operating at up to 120 frames per
+second in 400x400 resolution. All required image processing functions including
+exposure control are programmable through the SCCB interface.
+
+
+Required Properties:
+- compatible: shall be "ovti,ovm6211"
+- clocks: reference to the xvclk input clock
+- clock-names: shall be "xvclk"
+- avdd-supply: Analog voltage supply, 2.9 volts
+- dovdd-supply: Digital I/O voltage supply, 1.8 volts
+- dvdd-supply: Digital core voltage supply, 1.8 volts
+- reset-gpios: Low active reset gpio
+- enable-gpios: High active chip enable gpio
+
+The device node shall contain one 'port' child node with an
+'endpoint' subnode for its digital output video port,
+in accordance with the video interface bindings defined in
+Documentation/devicetree/bindings/media/video-interfaces.txt.
+The endpoint optional property 'data-lanes' shall be "<1>".
+
+
+Example:
+
+	&i2c1 {
+		...
+        
+		camera@60 {
+			compatible = "ovti,ovm6211";
+			reg = <0x60>;
+			clocks = <&clk_cam>;
+			clock-names = "xvclk";
+			dovdd-supply = <&v1v8>;
+			avdd-supply = <&vdda>;
+			reset-gpios = <&gpiof 14 GPIO_ACTIVE_LOW>;
+			enable-gpios = <&gpiof 15 GPIO_ACTIVE_HIGH>;
+
+			status = "okay";
+
+			port {
+				ovm6211_0: endpoint {
+					data-lanes = <1>;
+					clock-lanes = <0>;
+					pclk-max-frequency = <72000000>;
+			};
+		};
+	};
-- 
2.30.2


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH v5 1/2] media: ovm6211: Adds support for OVM6211
  2022-03-09 10:22 ` [PATCH v5 1/2] " Petko Manolov
@ 2022-03-09 11:51   ` Kieran Bingham
  2022-03-09 12:56     ` Dave Stevenson
  2022-03-10 12:19   ` kernel test robot
  2022-04-19  9:42   ` Sakari Ailus
  2 siblings, 1 reply; 9+ messages in thread
From: Kieran Bingham @ 2022-03-09 11:51 UTC (permalink / raw)
  To: Petko Manolov, linux-media; +Cc: sakari.ailus, devicetree, Petko Manolov

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
>

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v5 1/2] media: ovm6211: Adds support for OVM6211
  2022-03-09 11:51   ` Kieran Bingham
@ 2022-03-09 12:56     ` Dave Stevenson
  0 siblings, 0 replies; 9+ messages in thread
From: Dave Stevenson @ 2022-03-09 12:56 UTC (permalink / raw)
  To: Kieran Bingham
  Cc: Petko Manolov, Linux Media Mailing List, Sakari Ailus, devicetree

 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
> >

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v5 2/2] dt-bindings: media: add ovti,ovm6211 bindings
  2022-03-09 10:22 ` [PATCH v5 2/2] dt-bindings: media: add ovti,ovm6211 bindings Petko Manolov
@ 2022-03-09 13:59   ` Rob Herring
  2022-03-09 16:20     ` Petko Manolov
  0 siblings, 1 reply; 9+ messages in thread
From: Rob Herring @ 2022-03-09 13:59 UTC (permalink / raw)
  To: Petko Manolov; +Cc: linux-media, sakari.ailus, devicetree

On Wed, Mar 09, 2022 at 12:22:15PM +0200, Petko Manolov wrote:
> Omnivision OVM6211 MIPI CSI-2 sensor bindings.
> 
> Signed-off-by: Petko Manolov <petko.manolov@konsulko.com>
> ---
>  .../devicetree/bindings/media/i2c/ovm6211.txt | 49 +++++++++++++++++++

Not sure how you got to v5 already, but bindings must be in DT schema 
format now.

Rob

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v5 2/2] dt-bindings: media: add ovti,ovm6211 bindings
  2022-03-09 13:59   ` Rob Herring
@ 2022-03-09 16:20     ` Petko Manolov
  0 siblings, 0 replies; 9+ messages in thread
From: Petko Manolov @ 2022-03-09 16:20 UTC (permalink / raw)
  To: Rob Herring; +Cc: linux-media, sakari.ailus, devicetree

On 22-03-09 06:59:04, Rob Herring wrote:
> On Wed, Mar 09, 2022 at 12:22:15PM +0200, Petko Manolov wrote:
> > Omnivision OVM6211 MIPI CSI-2 sensor bindings.
> > 
> > Signed-off-by: Petko Manolov <petko.manolov@konsulko.com>
> > ---
> >  .../devicetree/bindings/media/i2c/ovm6211.txt | 49 +++++++++++++++++++
> 
> Not sure how you got to v5 already, but bindings must be in DT schema format
> now.

Easy, the bindings got included to the patch in v5.  Looks like it's going to be
yaml all the way... :)


		Petko

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v5 1/2] media: ovm6211: Adds support for OVM6211
  2022-03-09 10:22 ` [PATCH v5 1/2] " Petko Manolov
  2022-03-09 11:51   ` Kieran Bingham
@ 2022-03-10 12:19   ` kernel test robot
  2022-04-19  9:42   ` Sakari Ailus
  2 siblings, 0 replies; 9+ messages in thread
From: kernel test robot @ 2022-03-10 12:19 UTC (permalink / raw)
  To: Petko Manolov, linux-media
  Cc: llvm, kbuild-all, sakari.ailus, devicetree, Petko Manolov

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

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v5 1/2] media: ovm6211: Adds support for OVM6211
  2022-03-09 10:22 ` [PATCH v5 1/2] " Petko Manolov
  2022-03-09 11:51   ` Kieran Bingham
  2022-03-10 12:19   ` kernel test robot
@ 2022-04-19  9:42   ` Sakari Ailus
  2 siblings, 0 replies; 9+ messages in thread
From: Sakari Ailus @ 2022-04-19  9:42 UTC (permalink / raw)
  To: Petko Manolov; +Cc: linux-media, devicetree

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");

-- 
Kind regards,

Sakari Ailus

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2022-04-19  9:42 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-03-09 10:22 [PATCH v5 0/2] media: ovm6211: Adds support for OVM6211 Petko Manolov
2022-03-09 10:22 ` [PATCH v5 1/2] " Petko Manolov
2022-03-09 11:51   ` Kieran Bingham
2022-03-09 12:56     ` Dave Stevenson
2022-03-10 12:19   ` kernel test robot
2022-04-19  9:42   ` Sakari Ailus
2022-03-09 10:22 ` [PATCH v5 2/2] dt-bindings: media: add ovti,ovm6211 bindings Petko Manolov
2022-03-09 13:59   ` Rob Herring
2022-03-09 16:20     ` Petko Manolov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).