All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/2] media: i2c: Add driver for OmniVision OV8858
@ 2023-01-06 20:39 Jacopo Mondi
  2023-01-06 20:39 ` [PATCH v2 1/2] dt-bindings: media: Add " Jacopo Mondi
  2023-01-06 20:39 ` [PATCH v2 2/2] media: i2c: Add driver for " Jacopo Mondi
  0 siblings, 2 replies; 9+ messages in thread
From: Jacopo Mondi @ 2023-01-06 20:39 UTC (permalink / raw)
  To: Nicholas Roth, Robert Mader
  Cc: Jacopo Mondi, Laurent Pinchart, Sakari Ailus, linux-media

Hello,
  this is a new version of Nicholas' first submission available at
https://lore.kernel.org/all/20221106171129.166892-2-nicholas@rothemail.net/

This is a re-write of the previous version so I've restarted numeration from 0
even if in this version I have not changed the register tables.

Functionally the most notable difference is the addition of support for binned
mode in 4 data lanes mode.

There is more space for optimization on top of this series, in example the
register tables can be reworked to share more common settings between modes. I
have started the effort, but as it is a tedious and error-prone work I would
rather have this first version merged and the iterate on top. Programming of the
analog crop rectangle and output size could also be made parametric, but the
current modes have a few undocumented registers and I didn't feel like biting
the bullet and see if the can be safely removed or not.

Also, the BSP driver mentions a "not well supported" R1A version of the chip.
I would be in favour of removing it if no one oppose.

Images are still rather "dark" when tested with libcamera but preview is working
as expected in both modes (full res and half-res binned mode) and with 2 and 4
data lanes.

Nicholas could you let me know if things are fine with you here ?

v1 -> v2:
- Unified and simplified modes
- Adjust blanking values to match FPS
- Address Laurent's comment:
  - rework reg_write() function
  - move to subdev state
  - cosmetic change


Jacopo Mondi (1):
  dt-bindings: media: Add OmniVision OV8858

Nicholas Roth (1):
  media: i2c: Add driver for OmniVision OV8858

 .../bindings/media/i2c/ovti,ov8858.yaml       |  105 +
 MAINTAINERS                                   |    9 +
 drivers/media/i2c/Kconfig                     |   13 +
 drivers/media/i2c/Makefile                    |    1 +
 drivers/media/i2c/ov8858.c                    | 1952 +++++++++++++++++
 5 files changed, 2080 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/i2c/ovti,ov8858.yaml
 create mode 100644 drivers/media/i2c/ov8858.c

--
2.38.1


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

* [PATCH v2 1/2] dt-bindings: media: Add OmniVision OV8858
  2023-01-06 20:39 [PATCH v2 0/2] media: i2c: Add driver for OmniVision OV8858 Jacopo Mondi
@ 2023-01-06 20:39 ` Jacopo Mondi
  2023-01-07 13:09   ` Krzysztof Kozlowski
  2023-01-06 20:39 ` [PATCH v2 2/2] media: i2c: Add driver for " Jacopo Mondi
  1 sibling, 1 reply; 9+ messages in thread
From: Jacopo Mondi @ 2023-01-06 20:39 UTC (permalink / raw)
  To: Nicholas Roth, Robert Mader, robh+dt, krzysztof.kozlowski+dt
  Cc: Jacopo Mondi, Laurent Pinchart, Sakari Ailus, linux-media,
	Jacopo Mondi, devicetree

From: Jacopo Mondi <jacopo.mondi@ideasonboard.com>

Add binding schema for the OmniVision OV8858 8 Megapixels camera sensor.

Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 .../bindings/media/i2c/ovti,ov8858.yaml       | 105 ++++++++++++++++++
 1 file changed, 105 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/i2c/ovti,ov8858.yaml

diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,ov8858.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,ov8858.yaml
new file mode 100644
index 000000000000..002461a974f8
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/ovti,ov8858.yaml
@@ -0,0 +1,105 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/i2c/ovti,ov8858.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: OmniVision OV8858 Image Sensor
+
+maintainers:
+  - Jacopo Mondi <jacopo.mondi@ideasonboard.com>
+  - Nicholas Roth <nicholas@rothemail.net>
+
+description: |
+  The OmniVision OV8858 is a color CMOS 8 Megapixels (3264x2448) image sensor
+  controlled through an I2C-compatible SCCB bus. The sensor transmits images
+  on a MIPI CSI-2 output interface with up to 4 data lanes.
+
+properties:
+  compatible:
+    const: ovti,ov8858
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+    description: XVCLK external clock
+
+  clock-names:
+    const: xvclk
+
+  dvdd-supply:
+    description: Digital Domain Power Supply
+
+  avdd-supply:
+    description: Analog Domain Power Supply
+
+  dovdd-supply:
+    description: I/O Domain Power Supply
+
+  powerdown-gpios:
+    description: PWDNB powerdown GPIO (active low)
+
+  reset-gpios:
+    description: XSHUTDN reset GPIO (active low)
+
+  port:
+    description: MIPI CSI-2 transmitter port
+    $ref: /schemas/graph.yaml#/$defs/port-base
+    additionalProperties: false
+
+    properties:
+      endpoint:
+        $ref: /schemas/media/video-interfaces.yaml#
+        unevaluatedProperties: false
+
+        properties:
+          data-lanes:
+            minItems: 1
+            maxItems: 4
+
+        required:
+          - data-lanes
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - port
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/pinctrl/rockchip.h>
+    #include <dt-bindings/clock/rk3399-cru.h>
+    #include <dt-bindings/gpio/gpio.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        ov8858: camera@36 {
+            compatible = "ovti,ov8858";
+            reg = <0x36>;
+
+            clocks = <&cru SCLK_CIF_OUT>;
+            clock-names = "xvclk";
+            assigned-clocks = <&cru SCLK_CIF_OUT>;
+            assigned-clock-rates = <24000000>;
+
+            dovdd-supply = <&vcc1v8_dvp>;
+
+            reset-gpios = <&gpio1 RK_PA4 GPIO_ACTIVE_LOW>;
+            powerdown-gpios = <&gpio2 RK_PB4 GPIO_ACTIVE_LOW>;
+
+            port {
+                ucam_out: endpoint {
+                    remote-endpoint = <&mipi_in_ucam>;
+                    data-lanes = <1 2 3 4>;
+                };
+            };
+        };
+    };
+...
--
2.38.1


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

* [PATCH v2 2/2] media: i2c: Add driver for OmniVision OV8858
  2023-01-06 20:39 [PATCH v2 0/2] media: i2c: Add driver for OmniVision OV8858 Jacopo Mondi
  2023-01-06 20:39 ` [PATCH v2 1/2] dt-bindings: media: Add " Jacopo Mondi
@ 2023-01-06 20:39 ` Jacopo Mondi
  2023-01-06 23:07   ` Laurent Pinchart
  1 sibling, 1 reply; 9+ messages in thread
From: Jacopo Mondi @ 2023-01-06 20:39 UTC (permalink / raw)
  To: Nicholas Roth, Robert Mader
  Cc: Jacopo Mondi, Laurent Pinchart, Sakari Ailus, linux-media, Jacopo Mondi

From: Nicholas Roth <nicholas@rothemail.net>

Add a driver for OmniVision OV8858 image sensor.

The driver currently supports operations with 2 and 4 data lanes, in
full resolution and half-binned resolution modes.

The driver has been upported from the PinephonePro BSP available at
https://gitlab.com/pine64-org/linux.git
at commit 8c4a90c12dc2 ("media: i2c: ov8858: Use default subdev
name").

Signed-off-by: Nicholas Roth <nicholas@rothemail.net>
Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
---
 MAINTAINERS                |    9 +
 drivers/media/i2c/Kconfig  |   13 +
 drivers/media/i2c/Makefile |    1 +
 drivers/media/i2c/ov8858.c | 1952 ++++++++++++++++++++++++++++++++++++
 4 files changed, 1975 insertions(+)
 create mode 100644 drivers/media/i2c/ov8858.c

diff --git a/MAINTAINERS b/MAINTAINERS
index f61eb221415b..fafb0a01be93 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15524,6 +15524,15 @@ T:	git git://linuxtv.org/media_tree.git
 F:	Documentation/devicetree/bindings/media/i2c/ov8856.yaml
 F:	drivers/media/i2c/ov8856.c
 
+OMNIVISION OV8858 SENSOR DRIVER
+M:	Jacopo Mondi <jacopo.mondi@ideasonboard.com>
+M:	Nicholas Roth <nicholas@rothemail.net>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+T:	git git://linuxtv.org/media_tree.git
+F:	Documentation/devicetree/bindings/media/i2c/ovti,ov8858.yaml
+F:	drivers/media/i2c/ov8858.c
+
 OMNIVISION OV9282 SENSOR DRIVER
 M:	Paul J. Murphy <paul.j.murphy@intel.com>
 M:	Daniele Alessandrelli <daniele.alessandrelli@intel.com>
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 833241897d63..12ba8542778f 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -645,6 +645,19 @@ config VIDEO_OV8856
 	  To compile this driver as a module, choose M here: the
 	  module will be called ov8856.
 
+config VIDEO_OV8858
+	tristate "OmniVision OV8858 sensor support"
+	depends on I2C && PM && VIDEO_DEV
+	select MEDIA_CONTROLLER
+	select VIDEO_V4L2_SUBDEV_API
+	select V4L2_FWNODE
+	help
+	  This is a Video4Linux2 sensor driver for OmniVision
+	  OV8858 camera sensor.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called ov8858.
+
 config VIDEO_OV8865
 	tristate "OmniVision OV8865 sensor support"
 	depends on I2C && PM && VIDEO_DEV
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index 4d6c052bb5a7..b611a8277d57 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -96,6 +96,7 @@ obj-$(CONFIG_VIDEO_OV7670) += ov7670.o
 obj-$(CONFIG_VIDEO_OV772X) += ov772x.o
 obj-$(CONFIG_VIDEO_OV7740) += ov7740.o
 obj-$(CONFIG_VIDEO_OV8856) += ov8856.o
+obj-$(CONFIG_VIDEO_OV8858) += ov8858.o
 obj-$(CONFIG_VIDEO_OV8865) += ov8865.o
 obj-$(CONFIG_VIDEO_OV9282) += ov9282.o
 obj-$(CONFIG_VIDEO_OV9640) += ov9640.o
diff --git a/drivers/media/i2c/ov8858.c b/drivers/media/i2c/ov8858.c
new file mode 100644
index 000000000000..bfdfac49caa9
--- /dev/null
+++ b/drivers/media/i2c/ov8858.c
@@ -0,0 +1,1952 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2023 Jacopo Mondi <jacopo.mondi@ideasonboard.com>
+ * Copyright (C) 2022 Nicholas Roth <nicholas@rothemail.net>
+ * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd.
+ */
+
+#include <asm/unaligned.h>
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+#include <media/media-entity.h>
+#include <media/v4l2-async.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-mediabus.h>
+#include <media/v4l2-subdev.h>
+
+#define OV8858_LINK_FREQ		360000000U
+#define OV8858_XVCLK_FREQ		24000000
+
+#define OV8858_REG_SIZE_SHIFT		16
+#define OV8858_REG_ADDR_MASK		0xffff
+#define OV8858_REG_8BIT(n)		((1U << OV8858_REG_SIZE_SHIFT) | (n))
+#define OV8858_REG_16BIT(n)		((2U << OV8858_REG_SIZE_SHIFT) | (n))
+#define OV8858_REG_24BIT(n)		((3U << OV8858_REG_SIZE_SHIFT) | (n))
+
+#define OV8858_CHIP_ID			0x008858
+#define OV8858_REG_CHIP_ID		OV8858_REG_24BIT(0x300a)
+
+#define OV8858_REG_CTRL_MODE		OV8858_REG_8BIT(0x0100)
+#define OV8858_MODE_SW_STANDBY		0x0
+#define OV8858_MODE_STREAMING		0x1
+
+#define OV8858_REG_EXPOSURE		OV8858_REG_24BIT(0x3500)
+#define	OV8858_EXPOSURE_MIN		4
+#define	OV8858_EXPOSURE_STEP		1
+#define OV8858_VTS_MAX			0x7fff
+
+#define OV8858_REG_GAIN_H		OV8858_REG_8BIT(0x3508)
+#define OV8858_REG_GAIN_L		OV8858_REG_8BIT(0x3509)
+#define OV8858_GAIN_H_MASK		0x07
+#define OV8858_GAIN_H_SHIFT		8
+#define OV8858_GAIN_L_MASK		0xff
+#define OV8858_GAIN_MIN			0x0
+#define OV8858_GAIN_MAX			0x7ff
+#define OV8858_GAIN_STEP		1
+#define OV8858_GAIN_DEFAULT		0x80
+
+#define OV8858_REG_DGAIN_H		OV8858_REG_8BIT(0x350a)
+#define OV8858_REG_DGAIN_L		OV8858_REG_8BIT(0x350b)
+#define OV8858_DGAIN_H_MASK		0xff
+#define OV8858_DGAIN_H_SHIFT		6
+#define OV8858_DGAIN_L_MASK		0x3f
+#define OV8858_DGAIN_MIN		0x0
+#define OV8858_DGAIN_MAX		0x3fff
+#define OV8858_DGAIN_STEP		1
+#define OV8858_DGAIN_DEFAULT		0x200
+
+#define OV8858_REG_TEST_PATTERN		OV8858_REG_8BIT(0x5e00)
+#define	OV8858_TEST_PATTERN_ENABLE	0x80
+#define	OV8858_TEST_PATTERN_DISABLE	0x0
+
+#define OV8858_REG_VTS			OV8858_REG_16BIT(0x380e)
+
+#define REG_NULL			0xffff
+
+#define OV8858_CHIP_REVISION_REG	OV8858_REG_8BIT(0x302a)
+#define OV8858_R1A			0xb0
+#define OV8858_R2A			0xb2
+
+static const char * const ov8858_supply_names[] = {
+	"avdd",		/* Analog power */
+	"dovdd",	/* Digital I/O power */
+	"dvdd",		/* Digital core power */
+};
+
+#define OV8858_NUM_SUPPLIES ARRAY_SIZE(ov8858_supply_names)
+
+struct regval {
+	u16 addr;
+	u8 val;
+};
+
+struct regval_modes {
+	const struct regval *mode_2lanes;
+	const struct regval *mode_4lanes;
+};
+
+struct ov8858_mode {
+	u32 width;
+	u32 height;
+	u32 hts_def;
+	u32 vts_def;
+	u32 exp_def;
+	const struct regval_modes reg_modes;
+};
+
+struct ov8858 {
+	struct i2c_client	*client;
+	struct clk		*xvclk;
+	struct gpio_desc	*reset_gpio;
+	struct gpio_desc	*pwdn_gpio;
+	struct regulator_bulk_data supplies[OV8858_NUM_SUPPLIES];
+
+	struct v4l2_subdev	subdev;
+	struct media_pad	pad;
+
+	/* Protect controls and image format. */
+	struct mutex		mutex;
+
+	struct v4l2_ctrl_handler ctrl_handler;
+	struct v4l2_ctrl	*exposure;
+	struct v4l2_ctrl	*hblank;
+	struct v4l2_ctrl	*vblank;
+
+	const struct regval	*global_regs;
+	const struct ov8858_mode *cur_mode;
+
+	bool			streaming;
+	unsigned int		num_lanes;
+	unsigned int		pixel_rate;
+};
+
+#define to_ov8858(sd) container_of(sd, struct ov8858, subdev)
+
+static const struct regval ov8858_global_regs_r1a[] = {
+	{0x0100, 0x00},
+	{0x0100, 0x00},
+	{0x0100, 0x00},
+	{0x0100, 0x00},
+	{0x0302, 0x1e},
+	{0x0303, 0x00},
+	{0x0304, 0x03},
+	{0x030e, 0x00},
+	{0x030f, 0x09},
+	{0x0312, 0x01},
+	{0x031e, 0x0c},
+	{0x3600, 0x00},
+	{0x3601, 0x00},
+	{0x3602, 0x00},
+	{0x3603, 0x00},
+	{0x3604, 0x22},
+	{0x3605, 0x30},
+	{0x3606, 0x00},
+	{0x3607, 0x20},
+	{0x3608, 0x11},
+	{0x3609, 0x28},
+	{0x360a, 0x00},
+	{0x360b, 0x06},
+	{0x360c, 0xdc},
+	{0x360d, 0x40},
+	{0x360e, 0x0c},
+	{0x360f, 0x20},
+	{0x3610, 0x07},
+	{0x3611, 0x20},
+	{0x3612, 0x88},
+	{0x3613, 0x80},
+	{0x3614, 0x58},
+	{0x3615, 0x00},
+	{0x3616, 0x4a},
+	{0x3617, 0xb0},
+	{0x3618, 0x56},
+	{0x3619, 0x70},
+	{0x361a, 0x99},
+	{0x361b, 0x00},
+	{0x361c, 0x07},
+	{0x361d, 0x00},
+	{0x361e, 0x00},
+	{0x361f, 0x00},
+	{0x3638, 0xff},
+	{0x3633, 0x0c},
+	{0x3634, 0x0c},
+	{0x3635, 0x0c},
+	{0x3636, 0x0c},
+	{0x3645, 0x13},
+	{0x3646, 0x83},
+	{0x364a, 0x07},
+	{0x3015, 0x01},
+	{0x3018, 0x32},
+	{0x3020, 0x93},
+	{0x3022, 0x01},
+	{0x3031, 0x0a},
+	{0x3034, 0x00},
+	{0x3106, 0x01},
+	{0x3305, 0xf1},
+	{0x3308, 0x00},
+	{0x3309, 0x28},
+	{0x330a, 0x00},
+	{0x330b, 0x20},
+	{0x330c, 0x00},
+	{0x330d, 0x00},
+	{0x330e, 0x00},
+	{0x330f, 0x40},
+	{0x3307, 0x04},
+	{0x3500, 0x00},
+	{0x3501, 0x4d},
+	{0x3502, 0x40},
+	{0x3503, 0x00},
+	{0x3505, 0x80},
+	{0x3508, 0x04},
+	{0x3509, 0x00},
+	{0x350c, 0x00},
+	{0x350d, 0x80},
+	{0x3510, 0x00},
+	{0x3511, 0x02},
+	{0x3512, 0x00},
+	{0x3700, 0x18},
+	{0x3701, 0x0c},
+	{0x3702, 0x28},
+	{0x3703, 0x19},
+	{0x3704, 0x14},
+	{0x3705, 0x00},
+	{0x3706, 0x35},
+	{0x3707, 0x04},
+	{0x3708, 0x24},
+	{0x3709, 0x33},
+	{0x370a, 0x00},
+	{0x370b, 0xb5},
+	{0x370c, 0x04},
+	{0x3718, 0x12},
+	{0x3719, 0x31},
+	{0x3712, 0x42},
+	{0x3714, 0x24},
+	{0x371e, 0x19},
+	{0x371f, 0x40},
+	{0x3720, 0x05},
+	{0x3721, 0x05},
+	{0x3724, 0x06},
+	{0x3725, 0x01},
+	{0x3726, 0x06},
+	{0x3728, 0x05},
+	{0x3729, 0x02},
+	{0x372a, 0x03},
+	{0x372b, 0x53},
+	{0x372c, 0xa3},
+	{0x372d, 0x53},
+	{0x372e, 0x06},
+	{0x372f, 0x10},
+	{0x3730, 0x01},
+	{0x3731, 0x06},
+	{0x3732, 0x14},
+	{0x3733, 0x10},
+	{0x3734, 0x40},
+	{0x3736, 0x20},
+	{0x373a, 0x05},
+	{0x373b, 0x06},
+	{0x373c, 0x0a},
+	{0x373e, 0x03},
+	{0x3755, 0x10},
+	{0x3758, 0x00},
+	{0x3759, 0x4c},
+	{0x375a, 0x06},
+	{0x375b, 0x13},
+	{0x375c, 0x20},
+	{0x375d, 0x02},
+	{0x375e, 0x00},
+	{0x375f, 0x14},
+	{0x3768, 0x22},
+	{0x3769, 0x44},
+	{0x376a, 0x44},
+	{0x3761, 0x00},
+	{0x3762, 0x00},
+	{0x3763, 0x00},
+	{0x3766, 0xff},
+	{0x376b, 0x00},
+	{0x3772, 0x23},
+	{0x3773, 0x02},
+	{0x3774, 0x16},
+	{0x3775, 0x12},
+	{0x3776, 0x04},
+	{0x3777, 0x00},
+	{0x3778, 0x1b},
+	{0x37a0, 0x44},
+	{0x37a1, 0x3d},
+	{0x37a2, 0x3d},
+	{0x37a3, 0x00},
+	{0x37a4, 0x00},
+	{0x37a5, 0x00},
+	{0x37a6, 0x00},
+	{0x37a7, 0x44},
+	{0x37a8, 0x4c},
+	{0x37a9, 0x4c},
+	{0x3760, 0x00},
+	{0x376f, 0x01},
+	{0x37aa, 0x44},
+	{0x37ab, 0x2e},
+	{0x37ac, 0x2e},
+	{0x37ad, 0x33},
+	{0x37ae, 0x0d},
+	{0x37af, 0x0d},
+	{0x37b0, 0x00},
+	{0x37b1, 0x00},
+	{0x37b2, 0x00},
+	{0x37b3, 0x42},
+	{0x37b4, 0x42},
+	{0x37b5, 0x33},
+	{0x37b6, 0x00},
+	{0x37b7, 0x00},
+	{0x37b8, 0x00},
+	{0x37b9, 0xff},
+	{0x3800, 0x00},
+	{0x3801, 0x0c},
+	{0x3802, 0x00},
+	{0x3803, 0x0c},
+	{0x3804, 0x0c},
+	{0x3805, 0xd3},
+	{0x3806, 0x09},
+	{0x3807, 0xa3},
+	{0x3808, 0x06},
+	{0x3809, 0x60},
+	{0x380a, 0x04},
+	{0x380b, 0xc8},
+	{0x380c, 0x07},
+	{0x380d, 0x88},
+	{0x380e, 0x04},
+	{0x380f, 0xdc},
+	{0x3810, 0x00},
+	{0x3811, 0x04},
+	{0x3813, 0x02},
+	{0x3814, 0x03},
+	{0x3815, 0x01},
+	{0x3820, 0x00},
+	{0x3821, 0x67},
+	{0x382a, 0x03},
+	{0x382b, 0x01},
+	{0x3830, 0x08},
+	{0x3836, 0x02},
+	{0x3837, 0x18},
+	{0x3841, 0xff},
+	{0x3846, 0x48},
+	{0x3d85, 0x14},
+	{0x3f08, 0x08},
+	{0x3f0a, 0x80},
+	{0x4000, 0xf1},
+	{0x4001, 0x10},
+	{0x4005, 0x10},
+	{0x4002, 0x27},
+	{0x4009, 0x81},
+	{0x400b, 0x0c},
+	{0x401b, 0x00},
+	{0x401d, 0x00},
+	{0x4020, 0x00},
+	{0x4021, 0x04},
+	{0x4022, 0x04},
+	{0x4023, 0xb9},
+	{0x4024, 0x05},
+	{0x4025, 0x2a},
+	{0x4026, 0x05},
+	{0x4027, 0x2b},
+	{0x4028, 0x00},
+	{0x4029, 0x02},
+	{0x402a, 0x04},
+	{0x402b, 0x04},
+	{0x402c, 0x02},
+	{0x402d, 0x02},
+	{0x402e, 0x08},
+	{0x402f, 0x02},
+	{0x401f, 0x00},
+	{0x4034, 0x3f},
+	{0x403d, 0x04},
+	{0x4300, 0xff},
+	{0x4301, 0x00},
+	{0x4302, 0x0f},
+	{0x4316, 0x00},
+	{0x4500, 0x38},
+	{0x4503, 0x18},
+	{0x4600, 0x00},
+	{0x4601, 0xcb},
+	{0x481f, 0x32},
+	{0x4837, 0x16},
+	{0x4850, 0x10},
+	{0x4851, 0x32},
+	{0x4b00, 0x2a},
+	{0x4b0d, 0x00},
+	{0x4d00, 0x04},
+	{0x4d01, 0x18},
+	{0x4d02, 0xc3},
+	{0x4d03, 0xff},
+	{0x4d04, 0xff},
+	{0x4d05, 0xff},
+	{0x5000, 0x7e},
+	{0x5001, 0x01},
+	{0x5002, 0x08},
+	{0x5003, 0x20},
+	{0x5046, 0x12},
+	{0x5901, 0x00},
+	{0x5e00, 0x00},
+	{0x5e01, 0x41},
+	{0x382d, 0x7f},
+	{0x4825, 0x3a},
+	{0x4826, 0x40},
+	{0x4808, 0x25},
+	//{0x0100, 0x01},
+	{REG_NULL, 0x00},
+};
+
+static const struct regval ov8858_global_regs_r2a_2lane[] = {
+	/*
+	 * MIPI=720Mbps, SysClk=144Mhz,Dac Clock=360Mhz.
+	 * v00_01_00 (05/29/2014) : initial setting
+	 * AM19 : 3617 <- 0xC0
+	 * AM20 : change FWC_6K_EN to be default 0x3618=0x5a
+	 */
+	{0x0103, 0x01},// software reset for OVTATool only
+	{0x0103, 0x01},// software reset
+	{0x0100, 0x00},// software standby
+	{0x0302, 0x1e},// pll1_multi
+	{0x0303, 0x00},// pll1_divm
+	{0x0304, 0x03},// pll1_div_mipi
+	{0x030e, 0x02},// pll2_rdiv
+	{0x030f, 0x04},// pll2_divsp
+	{0x0312, 0x03},// pll2_pre_div0, pll2_r_divdac
+	{0x031e, 0x0c},// pll1_no_lat
+	{0x3600, 0x00},
+	{0x3601, 0x00},
+	{0x3602, 0x00},
+	{0x3603, 0x00},
+	{0x3604, 0x22},
+	{0x3605, 0x20},
+	{0x3606, 0x00},
+	{0x3607, 0x20},
+	{0x3608, 0x11},
+	{0x3609, 0x28},
+	{0x360a, 0x00},
+	{0x360b, 0x05},
+	{0x360c, 0xd4},
+	{0x360d, 0x40},
+	{0x360e, 0x0c},
+	{0x360f, 0x20},
+	{0x3610, 0x07},
+	{0x3611, 0x20},
+	{0x3612, 0x88},
+	{0x3613, 0x80},
+	{0x3614, 0x58},
+	{0x3615, 0x00},
+	{0x3616, 0x4a},
+	{0x3617, 0x90},
+	{0x3618, 0x5a},
+	{0x3619, 0x70},
+	{0x361a, 0x99},
+	{0x361b, 0x0a},
+	{0x361c, 0x07},
+	{0x361d, 0x00},
+	{0x361e, 0x00},
+	{0x361f, 0x00},
+	{0x3638, 0xff},
+	{0x3633, 0x0f},
+	{0x3634, 0x0f},
+	{0x3635, 0x0f},
+	{0x3636, 0x12},
+	{0x3645, 0x13},
+	{0x3646, 0x83},
+	{0x364a, 0x07},
+	{0x3015, 0x00},
+	{0x3018, 0x32}, // MIPI 2 lane
+	{0x3020, 0x93}, // Clock switch output normal, pclk_div =/1
+	{0x3022, 0x01}, // pd_mipi enable when rst_sync
+	{0x3031, 0x0a}, // MIPI 10-bit mode
+	{0x3034, 0x00}, //
+	{0x3106, 0x01}, // sclk_div, sclk_pre_div
+	{0x3305, 0xf1},
+	{0x3308, 0x00},
+	{0x3309, 0x28},
+	{0x330a, 0x00},
+	{0x330b, 0x20},
+	{0x330c, 0x00},
+	{0x330d, 0x00},
+	{0x330e, 0x00},
+	{0x330f, 0x40},
+	{0x3307, 0x04},
+	{0x3500, 0x00}, // exposure H
+	{0x3501, 0x4d}, // exposure M
+	{0x3502, 0x40}, // exposure L
+	{0x3503, 0x80}, // gain delay ?, exposure delay 1 frame, real gain
+	{0x3505, 0x80}, // gain option
+	{0x3508, 0x02}, // gain H
+	{0x3509, 0x00}, // gain L
+	{0x350c, 0x00}, // short gain H
+	{0x350d, 0x80}, // short gain L
+	{0x3510, 0x00}, // short exposure H
+	{0x3511, 0x02}, // short exposure M
+	{0x3512, 0x00}, // short exposure L
+	{0x3700, 0x18},
+	{0x3701, 0x0c},
+	{0x3702, 0x28},
+	{0x3703, 0x19},
+	{0x3704, 0x14},
+	{0x3705, 0x00},
+	{0x3706, 0x82},
+	{0x3707, 0x04},
+	{0x3708, 0x24},
+	{0x3709, 0x33},
+	{0x370a, 0x01},
+	{0x370b, 0x82},
+	{0x370c, 0x04},
+	{0x3718, 0x12},
+	{0x3719, 0x31},
+	{0x3712, 0x42},
+	{0x3714, 0x24},
+	{0x371e, 0x19},
+	{0x371f, 0x40},
+	{0x3720, 0x05},
+	{0x3721, 0x05},
+	{0x3724, 0x06},
+	{0x3725, 0x01},
+	{0x3726, 0x06},
+	{0x3728, 0x05},
+	{0x3729, 0x02},
+	{0x372a, 0x03},
+	{0x372b, 0x53},
+	{0x372c, 0xa3},
+	{0x372d, 0x53},
+	{0x372e, 0x06},
+	{0x372f, 0x10},
+	{0x3730, 0x01},
+	{0x3731, 0x06},
+	{0x3732, 0x14},
+	{0x3733, 0x10},
+	{0x3734, 0x40},
+	{0x3736, 0x20},
+	{0x373a, 0x05},
+	{0x373b, 0x06},
+	{0x373c, 0x0a},
+	{0x373e, 0x03},
+	{0x3750, 0x0a},
+	{0x3751, 0x0e},
+	{0x3755, 0x10},
+	{0x3758, 0x00},
+	{0x3759, 0x4c},
+	{0x375a, 0x06},
+	{0x375b, 0x13},
+	{0x375c, 0x20},
+	{0x375d, 0x02},
+	{0x375e, 0x00},
+	{0x375f, 0x14},
+	{0x3768, 0x22},
+	{0x3769, 0x44},
+	{0x376a, 0x44},
+	{0x3761, 0x00},
+	{0x3762, 0x00},
+	{0x3763, 0x00},
+	{0x3766, 0xff},
+	{0x376b, 0x00},
+	{0x3772, 0x23},
+	{0x3773, 0x02},
+	{0x3774, 0x16},
+	{0x3775, 0x12},
+	{0x3776, 0x04},
+	{0x3777, 0x00},
+	{0x3778, 0x17},
+	{0x37a0, 0x44},
+	{0x37a1, 0x3d},
+	{0x37a2, 0x3d},
+	{0x37a3, 0x00},
+	{0x37a4, 0x00},
+	{0x37a5, 0x00},
+	{0x37a6, 0x00},
+	{0x37a7, 0x44},
+	{0x37a8, 0x4c},
+	{0x37a9, 0x4c},
+	{0x3760, 0x00},
+	{0x376f, 0x01},
+	{0x37aa, 0x44},
+	{0x37ab, 0x2e},
+	{0x37ac, 0x2e},
+	{0x37ad, 0x33},
+	{0x37ae, 0x0d},
+	{0x37af, 0x0d},
+	{0x37b0, 0x00},
+	{0x37b1, 0x00},
+	{0x37b2, 0x00},
+	{0x37b3, 0x42},
+	{0x37b4, 0x42},
+	{0x37b5, 0x31},
+	{0x37b6, 0x00},
+	{0x37b7, 0x00},
+	{0x37b8, 0x00},
+	{0x37b9, 0xff},
+	{0x3800, 0x00}, // x start H
+	{0x3801, 0x0c}, // x start L
+	{0x3802, 0x00}, // y start H
+	{0x3803, 0x0c}, // y start L
+	{0x3804, 0x0c}, // x end H
+	{0x3805, 0xd3}, // x end L
+	{0x3806, 0x09}, // y end H
+	{0x3807, 0xa3}, // y end L
+	{0x3808, 0x06}, // x output size H
+	{0x3809, 0x60}, // x output size L
+	{0x380a, 0x04}, // y output size H
+	{0x380b, 0xc8}, // y output size L
+	{0x380c, 0x07}, // HTS H
+	{0x380d, 0x88}, // HTS L
+	{0x380e, 0x04}, // VTS H
+	{0x380f, 0xdc}, // VTS L
+	{0x3810, 0x00}, // ISP x win H
+	{0x3811, 0x04}, // ISP x win L
+	{0x3813, 0x02}, // ISP y win L
+	{0x3814, 0x03}, // x odd inc
+	{0x3815, 0x01}, // x even inc
+	{0x3820, 0x00}, // vflip off
+	{0x3821, 0x67}, // mirror on, bin on
+	{0x382a, 0x03}, // y odd inc
+	{0x382b, 0x01}, // y even inc
+	{0x3830, 0x08}, //
+	{0x3836, 0x02}, //
+	{0x3837, 0x18}, //
+	{0x3841, 0xff}, // window auto size enable
+	{0x3846, 0x48}, //
+	{0x3d85, 0x16}, // OTP power up load data enable with BIST
+	{0x3d8c, 0x73}, // OTP setting start High
+	{0x3d8d, 0xde}, // OTP setting start Low
+	{0x3f08, 0x08}, //
+	{0x3f0a, 0x00}, //
+	{0x4000, 0xf1}, // out_range_trig, format_chg_trig
+	{0x4001, 0x10}, // total 128 black column
+	{0x4005, 0x10}, // BLC target L
+	{0x4002, 0x27}, // value used to limit BLC offset
+	{0x4009, 0x81}, // final BLC offset limitation enable
+	{0x400b, 0x0c}, // DCBLC on, DCBLC manual mode on
+	{0x401b, 0x00}, // zero line R coefficient
+	{0x401d, 0x00}, // zoro line T coefficient
+	{0x4020, 0x00}, // Anchor left start H
+	{0x4021, 0x04}, // Anchor left start L
+	{0x4022, 0x06}, // Anchor left end H
+	{0x4023, 0x00}, // Anchor left end L
+	{0x4024, 0x0f}, // Anchor right start H
+	{0x4025, 0x2a}, // Anchor right start L
+	{0x4026, 0x0f}, // Anchor right end H
+	{0x4027, 0x2b}, // Anchor right end L
+	{0x4028, 0x00}, // top zero line start
+	{0x4029, 0x02}, // top zero line number
+	{0x402a, 0x04}, // top black line start
+	{0x402b, 0x04}, // top black line number
+	{0x402c, 0x00}, // bottom zero line start
+	{0x402d, 0x02}, // bottom zoro line number
+	{0x402e, 0x04}, // bottom black line start
+	{0x402f, 0x04}, // bottom black line number
+	{0x401f, 0x00}, // interpolation x/y disable, Anchor one disable
+	{0x4034, 0x3f}, //
+	{0x403d, 0x04}, // md_precision_en
+	{0x4300, 0xff}, // clip max H
+	{0x4301, 0x00}, // clip min H
+	{0x4302, 0x0f}, // clip min L, clip max L
+	{0x4316, 0x00}, //
+	{0x4500, 0x58}, //
+	{0x4503, 0x18}, //
+	{0x4600, 0x00}, //
+	{0x4601, 0xcb}, //
+	{0x481f, 0x32}, // clk prepare min
+	{0x4837, 0x16}, // global timing
+	{0x4850, 0x10}, // lane 1 = 1, lane 0 = 0
+	{0x4851, 0x32}, // lane 3 = 3, lane 2 = 2
+	{0x4b00, 0x2a}, //
+	{0x4b0d, 0x00}, //
+	{0x4d00, 0x04}, // temperature sensor
+	{0x4d01, 0x18}, //
+	{0x4d02, 0xc3}, //
+	{0x4d03, 0xff}, //
+	{0x4d04, 0xff}, //
+	{0x4d05, 0xff}, // temperature sensor
+	{0x5000, 0xfe}, // lenc on, slave/master AWB gain/statistics enable
+	{0x5001, 0x01}, // BLC on
+	{0x5002, 0x08}, // H scale off, WBMATCH off, OTP_DPC
+	{0x5003, 0x20}, // DPC_DBC buffer control enable, WB
+	{0x501e, 0x93}, // enable digital gain
+	{0x5046, 0x12}, //
+	{0x5780, 0x3e}, // DPC
+	{0x5781, 0x0f}, //
+	{0x5782, 0x44}, //
+	{0x5783, 0x02}, //
+	{0x5784, 0x01}, //
+	{0x5785, 0x00}, //
+	{0x5786, 0x00}, //
+	{0x5787, 0x04}, //
+	{0x5788, 0x02}, //
+	{0x5789, 0x0f}, //
+	{0x578a, 0xfd}, //
+	{0x578b, 0xf5}, //
+	{0x578c, 0xf5}, //
+	{0x578d, 0x03}, //
+	{0x578e, 0x08}, //
+	{0x578f, 0x0c}, //
+	{0x5790, 0x08}, //
+	{0x5791, 0x04}, //
+	{0x5792, 0x00}, //
+	{0x5793, 0x52}, //
+	{0x5794, 0xa3}, // DPC
+	{0x5871, 0x0d}, // Lenc
+	{0x5870, 0x18}, //
+	{0x586e, 0x10}, //
+	{0x586f, 0x08}, //
+	{0x58f7, 0x01}, //
+	{0x58f8, 0x3d}, // Lenc
+	{0x5901, 0x00}, // H skip off, V skip off
+	{0x5b00, 0x02}, // OTP DPC start address
+	{0x5b01, 0x10}, // OTP DPC start address
+	{0x5b02, 0x03}, // OTP DPC end address
+	{0x5b03, 0xcf}, // OTP DPC end address
+	{0x5b05, 0x6c}, // recover method = 2b11,
+	{0x5e00, 0x00}, // use 0x3ff to test pattern off
+	{0x5e01, 0x41}, // window cut enable
+	{0x382d, 0x7f}, //
+	{0x4825, 0x3a}, // lpx_p_min
+	{0x4826, 0x40}, // hs_prepare_min
+	{0x4808, 0x25}, // wake up delay in 1/1024 s
+	{0x3763, 0x18}, //
+	{0x3768, 0xcc}, //
+	{0x470b, 0x28}, //
+	{0x4202, 0x00}, //
+	{0x400d, 0x10}, // BLC offset trigger L
+	{0x4040, 0x04}, // BLC gain th2
+	{0x403e, 0x04}, // BLC gain th1
+	{0x4041, 0xc6}, // BLC
+	{0x3007, 0x80},
+	{0x400a, 0x01},
+	{REG_NULL, 0x00},
+};
+
+/*
+ * Xclk 24Mhz
+ * max_framerate 30fps
+ * mipi_datarate per lane 720Mbps
+ */
+static const struct regval ov8858_1632x1224_regs_2lane[] = {
+	/*
+	 * MIPI=720Mbps, SysClk=144Mhz,Dac Clock=360Mhz.
+	 * v00_01_00 (05/29/2014) : initial setting
+	 * AM19 : 3617 <- 0xC0
+	 * AM20 : change FWC_6K_EN to be default 0x3618=0x5a
+	 */
+	{0x0100, 0x00},
+	{0x3501, 0x4d}, // exposure M
+	{0x3502, 0x40}, // exposure L
+	{0x3778, 0x17}, //
+	{0x3808, 0x06}, // x output size H
+	{0x3809, 0x60}, // x output size L
+	{0x380a, 0x04}, // y output size H
+	{0x380b, 0xc8}, // y output size L
+	{0x380c, 0x07}, // HTS H
+	{0x380d, 0x88}, // HTS L
+	{0x380e, 0x04}, // VTS H
+	{0x380f, 0xdc}, // VTS L
+	{0x3814, 0x03}, // x odd inc
+	{0x3821, 0x67}, // mirror on, bin on
+	{0x382a, 0x03}, // y odd inc
+	{0x3830, 0x08},
+	{0x3836, 0x02},
+	{0x3f0a, 0x00},
+	{0x4001, 0x10}, // total 128 black column
+	{0x4022, 0x06}, // Anchor left end H
+	{0x4023, 0x00}, // Anchor left end L
+	{0x4025, 0x2a}, // Anchor right start L
+	{0x4027, 0x2b}, // Anchor right end L
+	{0x402b, 0x04}, // top black line number
+	{0x402f, 0x04}, // bottom black line number
+	{0x4500, 0x58},
+	{0x4600, 0x00},
+	{0x4601, 0xcb},
+	{0x382d, 0x7f},
+	{0x0100, 0x01},
+	{REG_NULL, 0x00},
+};
+
+/*
+ * Xclk 24Mhz
+ * max_framerate 15fps
+ * mipi_datarate per lane 720Mbps
+ */
+static const struct regval ov8858_3264x2448_regs_2lane[] = {
+	{0x0100, 0x00},
+	{0x3501, 0x9a},// exposure M
+	{0x3502, 0x20},// exposure L
+	{0x3778, 0x1a},//
+	{0x3808, 0x0c},// x output size H
+	{0x3809, 0xc0},// x output size L
+	{0x380a, 0x09},// y output size H
+	{0x380b, 0x90},// y output size L
+	{0x380c, 0x07},// HTS H
+	{0x380d, 0x94},// HTS L
+	{0x380e, 0x09},// VTS H
+	{0x380f, 0xaa},// VTS L
+	{0x3814, 0x01},// x odd inc
+	{0x3821, 0x46},// mirror on, bin off
+	{0x382a, 0x01},// y odd inc
+	{0x3830, 0x06},
+	{0x3836, 0x01},
+	{0x3f0a, 0x00},
+	{0x4001, 0x00},// total 256 black column
+	{0x4022, 0x0c},// Anchor left end H
+	{0x4023, 0x60},// Anchor left end L
+	{0x4025, 0x36},// Anchor right start L
+	{0x4027, 0x37},// Anchor right end L
+	{0x402b, 0x08},// top black line number
+	{0x402f, 0x08},// bottom black line number
+	{0x4500, 0x58},
+	{0x4600, 0x01},
+	{0x4601, 0x97},
+	{0x382d, 0xff},
+	{REG_NULL, 0x00},
+};
+
+static const struct regval ov8858_global_regs_r2a_4lane[] = {
+	/*
+	 * MIPI=720Mbps, SysClk=144Mhz,Dac Clock=360Mhz.
+	 * v00_01_00 (05/29/2014) : initial setting
+	 * AM19 : 3617 <- 0xC0
+	 * AM20 : change FWC_6K_EN to be default 0x3618=0x5a
+	 */
+	{0x0103, 0x01}, // software reset for OVTATool only
+	{0x0103, 0x01}, // software reset
+	{0x0100, 0x00}, // software standby
+	{0x0302, 0x1e}, // pll1_multi
+	{0x0303, 0x00}, // pll1_divm
+	{0x0304, 0x03}, // pll1_div_mipi
+	{0x030e, 0x00}, // pll2_rdiv
+	{0x030f, 0x04}, // pll2_divsp
+	{0x0312, 0x01}, // pll2_pre_div0, pll2_r_divdac
+	{0x031e, 0x0c}, // pll1_no_lat
+	{0x3600, 0x00},
+	{0x3601, 0x00},
+	{0x3602, 0x00},
+	{0x3603, 0x00},
+	{0x3604, 0x22},
+	{0x3605, 0x20},
+	{0x3606, 0x00},
+	{0x3607, 0x20},
+	{0x3608, 0x11},
+	{0x3609, 0x28},
+	{0x360a, 0x00},
+	{0x360b, 0x05},
+	{0x360c, 0xd4},
+	{0x360d, 0x40},
+	{0x360e, 0x0c},
+	{0x360f, 0x20},
+	{0x3610, 0x07},
+	{0x3611, 0x20},
+	{0x3612, 0x88},
+	{0x3613, 0x80},
+	{0x3614, 0x58},
+	{0x3615, 0x00},
+	{0x3616, 0x4a},
+	{0x3617, 0x90},
+	{0x3618, 0x5a},
+	{0x3619, 0x70},
+	{0x361a, 0x99},
+	{0x361b, 0x0a},
+	{0x361c, 0x07},
+	{0x361d, 0x00},
+	{0x361e, 0x00},
+	{0x361f, 0x00},
+	{0x3638, 0xff},
+	{0x3633, 0x0f},
+	{0x3634, 0x0f},
+	{0x3635, 0x0f},
+	{0x3636, 0x12},
+	{0x3645, 0x13},
+	{0x3646, 0x83},
+	{0x364a, 0x07},
+	{0x3015, 0x01}, //
+	{0x3018, 0x72}, // MIPI 4 lane
+	{0x3020, 0x93}, // Clock switch output normal, pclk_div =/1
+	{0x3022, 0x01}, // pd_mipi enable when rst_sync
+	{0x3031, 0x0a}, // MIPI 10-bit mode
+	{0x3034, 0x00}, //
+	{0x3106, 0x01}, // sclk_div, sclk_pre_div
+	{0x3305, 0xf1},
+	{0x3308, 0x00},
+	{0x3309, 0x28},
+	{0x330a, 0x00},
+	{0x330b, 0x20},
+	{0x330c, 0x00},
+	{0x330d, 0x00},
+	{0x330e, 0x00},
+	{0x330f, 0x40},
+	{0x3307, 0x04},
+	{0x3500, 0x00}, // exposure H
+	{0x3501, 0x4d}, // exposure M
+	{0x3502, 0x40}, // exposure L
+	{0x3503, 0x80}, // gain delay ?, exposure delay 1 frame, real gain
+	{0x3505, 0x80}, // gain option
+	{0x3508, 0x02}, // gain H
+	{0x3509, 0x00}, // gain L
+	{0x350c, 0x00}, // short gain H
+	{0x350d, 0x80}, // short gain L
+	{0x3510, 0x00}, // short exposure H
+	{0x3511, 0x02}, // short exposure M
+	{0x3512, 0x00}, // short exposure L
+	{0x3700, 0x30},
+	{0x3701, 0x18},
+	{0x3702, 0x50},
+	{0x3703, 0x32},
+	{0x3704, 0x28},
+	{0x3705, 0x00},
+	{0x3706, 0x82},
+	{0x3707, 0x08},
+	{0x3708, 0x48},
+	{0x3709, 0x66},
+	{0x370a, 0x01},
+	{0x370b, 0x82},
+	{0x370c, 0x07},
+	{0x3718, 0x14},
+	{0x3719, 0x31},
+	{0x3712, 0x44},
+	{0x3714, 0x24},
+	{0x371e, 0x31},
+	{0x371f, 0x7f},
+	{0x3720, 0x0a},
+	{0x3721, 0x0a},
+	{0x3724, 0x0c},
+	{0x3725, 0x02},
+	{0x3726, 0x0c},
+	{0x3728, 0x0a},
+	{0x3729, 0x03},
+	{0x372a, 0x06},
+	{0x372b, 0xa6},
+	{0x372c, 0xa6},
+	{0x372d, 0xa6},
+	{0x372e, 0x0c},
+	{0x372f, 0x20},
+	{0x3730, 0x02},
+	{0x3731, 0x0c},
+	{0x3732, 0x28},
+	{0x3733, 0x10},
+	{0x3734, 0x40},
+	{0x3736, 0x30},
+	{0x373a, 0x0a},
+	{0x373b, 0x0b},
+	{0x373c, 0x14},
+	{0x373e, 0x06},
+	{0x3750, 0x0a},
+	{0x3751, 0x0e},
+	{0x3755, 0x10},
+	{0x3758, 0x00},
+	{0x3759, 0x4c},
+	{0x375a, 0x0c},
+	{0x375b, 0x26},
+	{0x375c, 0x20},
+	{0x375d, 0x04},
+	{0x375e, 0x00},
+	{0x375f, 0x28},
+	{0x3768, 0x22},
+	{0x3769, 0x44},
+	{0x376a, 0x44},
+	{0x3761, 0x00},
+	{0x3762, 0x00},
+	{0x3763, 0x00},
+	{0x3766, 0xff},
+	{0x376b, 0x00},
+	{0x3772, 0x46},
+	{0x3773, 0x04},
+	{0x3774, 0x2c},
+	{0x3775, 0x13},
+	{0x3776, 0x08},
+	{0x3777, 0x00},
+	{0x3778, 0x17},
+	{0x37a0, 0x88},
+	{0x37a1, 0x7a},
+	{0x37a2, 0x7a},
+	{0x37a3, 0x00},
+	{0x37a4, 0x00},
+	{0x37a5, 0x00},
+	{0x37a6, 0x00},
+	{0x37a7, 0x88},
+	{0x37a8, 0x98},
+	{0x37a9, 0x98},
+	{0x3760, 0x00},
+	{0x376f, 0x01},
+	{0x37aa, 0x88},
+	{0x37ab, 0x5c},
+	{0x37ac, 0x5c},
+	{0x37ad, 0x55},
+	{0x37ae, 0x19},
+	{0x37af, 0x19},
+	{0x37b0, 0x00},
+	{0x37b1, 0x00},
+	{0x37b2, 0x00},
+	{0x37b3, 0x84},
+	{0x37b4, 0x84},
+	{0x37b5, 0x60},
+	{0x37b6, 0x00},
+	{0x37b7, 0x00},
+	{0x37b8, 0x00},
+	{0x37b9, 0xff},
+	{0x3800, 0x00}, // x start H
+	{0x3801, 0x0c}, // x start L
+	{0x3802, 0x00}, // y start H
+	{0x3803, 0x0c}, // y start L
+	{0x3804, 0x0c}, // x end H
+	{0x3805, 0xd3}, // x end L
+	{0x3806, 0x09}, // y end H
+	{0x3807, 0xa3}, // y end L
+	{0x3808, 0x06}, // x output size H
+	{0x3809, 0x60}, // x output size L
+	{0x380a, 0x04}, // y output size H
+	{0x380b, 0xc8}, // y output size L
+	{0x380c, 0x07}, // HTS H
+	{0x380d, 0x88}, // HTS L
+	{0x380e, 0x04}, // VTS H
+	{0x380f, 0xdc}, // VTS L
+	{0x3810, 0x00}, // ISP x win H
+	{0x3811, 0x04}, // ISP x win L
+	{0x3813, 0x02}, // ISP y win L
+	{0x3814, 0x03}, // x odd inc
+	{0x3815, 0x01}, // x even inc
+	{0x3820, 0x00}, // vflip off
+	{0x3821, 0x67}, // mirror on, bin o
+	{0x382a, 0x03}, // y odd inc
+	{0x382b, 0x01}, // y even inc
+	{0x3830, 0x08},
+	{0x3836, 0x02},
+	{0x3837, 0x18},
+	{0x3841, 0xff}, // window auto size enable
+	{0x3846, 0x48}, //
+	{0x3d85, 0x16}, // OTP power up load data/setting enable
+	{0x3d8c, 0x73}, // OTP setting start High
+	{0x3d8d, 0xde}, // OTP setting start Low
+	{0x3f08, 0x10}, //
+	{0x3f0a, 0x00}, //
+	{0x4000, 0xf1}, // out_range/format_chg/gain/exp_chg trig enable
+	{0x4001, 0x10}, // total 128 black column
+	{0x4005, 0x10}, // BLC target L
+	{0x4002, 0x27}, // value used to limit BLC offset
+	{0x4009, 0x81}, // final BLC offset limitation enable
+	{0x400b, 0x0c}, // DCBLC on, DCBLC manual mode on
+	{0x401b, 0x00}, // zero line R coefficient
+	{0x401d, 0x00}, // zoro line T coefficient
+	{0x4020, 0x00}, // Anchor left start H
+	{0x4021, 0x04}, // Anchor left start L
+	{0x4022, 0x06}, // Anchor left end H
+	{0x4023, 0x00}, // Anchor left end L
+	{0x4024, 0x0f}, // Anchor right start H
+	{0x4025, 0x2a}, // Anchor right start L
+	{0x4026, 0x0f}, // Anchor right end H
+	{0x4027, 0x2b}, // Anchor right end L
+	{0x4028, 0x00}, // top zero line start
+	{0x4029, 0x02}, // top zero line number
+	{0x402a, 0x04}, // top black line start
+	{0x402b, 0x04}, // top black line number
+	{0x402c, 0x00}, // bottom zero line start
+	{0x402d, 0x02}, // bottom zoro line number
+	{0x402e, 0x04}, // bottom black line start
+	{0x402f, 0x04}, // bottom black line number
+	{0x401f, 0x00}, // interpolation x/y disable, Anchor one disable
+	{0x4034, 0x3f},
+	{0x403d, 0x04}, // md_precision_en
+	{0x4300, 0xff}, // clip max H
+	{0x4301, 0x00}, // clip min H
+	{0x4302, 0x0f}, // clip min L, clip max L
+	{0x4316, 0x00},
+	{0x4500, 0x58},
+	{0x4503, 0x18},
+	{0x4600, 0x00},
+	{0x4601, 0xcb},
+	{0x481f, 0x32}, // clk prepare min
+	{0x4837, 0x16}, // global timing
+	{0x4850, 0x10}, // lane 1 = 1, lane 0 = 0
+	{0x4851, 0x32}, // lane 3 = 3, lane 2 = 2
+	{0x4b00, 0x2a},
+	{0x4b0d, 0x00},
+	{0x4d00, 0x04}, // temperature sensor
+	{0x4d01, 0x18}, //
+	{0x4d02, 0xc3}, //
+	{0x4d03, 0xff}, //
+	{0x4d04, 0xff}, //
+	{0x4d05, 0xff}, // temperature sensor
+	{0x5000, 0xfe}, // lenc on, slave/master AWB gain/statistics enable
+	{0x5001, 0x01}, // BLC on
+	{0x5002, 0x08}, // WBMATCH sensor's gain, H scale/WBMATCH/OTP_DPC off
+	{0x5003, 0x20}, // DPC_DBC buffer control enable, WB
+	{0x501e, 0x93}, // enable digital gain
+	{0x5046, 0x12}, //
+	{0x5780, 0x3e}, // DPC
+	{0x5781, 0x0f}, //
+	{0x5782, 0x44}, //
+	{0x5783, 0x02}, //
+	{0x5784, 0x01}, //
+	{0x5785, 0x00}, //
+	{0x5786, 0x00}, //
+	{0x5787, 0x04}, //
+	{0x5788, 0x02}, //
+	{0x5789, 0x0f}, //
+	{0x578a, 0xfd}, //
+	{0x578b, 0xf5}, //
+	{0x578c, 0xf5}, //
+	{0x578d, 0x03}, //
+	{0x578e, 0x08}, //
+	{0x578f, 0x0c}, //
+	{0x5790, 0x08}, //
+	{0x5791, 0x04}, //
+	{0x5792, 0x00}, //
+	{0x5793, 0x52}, //
+	{0x5794, 0xa3}, // DPC
+	{0x5871, 0x0d}, // Lenc
+	{0x5870, 0x18}, //
+	{0x586e, 0x10}, //
+	{0x586f, 0x08}, //
+	{0x58f7, 0x01}, //
+	{0x58f8, 0x3d}, // Lenc
+	{0x5901, 0x00}, // H skip off, V skip off
+	{0x5b00, 0x02}, // OTP DPC start address
+	{0x5b01, 0x10}, // OTP DPC start address
+	{0x5b02, 0x03}, // OTP DPC end address
+	{0x5b03, 0xcf}, // OTP DPC end address
+	{0x5b05, 0x6c}, // recover method = 2b11
+	{0x5e00, 0x00}, // use 0x3ff to test pattern off
+	{0x5e01, 0x41}, // window cut enable
+	{0x382d, 0x7f}, //
+	{0x4825, 0x3a}, // lpx_p_min
+	{0x4826, 0x40}, // hs_prepare_min
+	{0x4808, 0x25}, // wake up delay in 1/1024 s
+	{0x3763, 0x18},
+	{0x3768, 0xcc},
+	{0x470b, 0x28},
+	{0x4202, 0x00},
+	{0x400d, 0x10}, // BLC offset trigger L
+	{0x4040, 0x04}, // BLC gain th2
+	{0x403e, 0x04}, // BLC gain th1
+	{0x4041, 0xc6}, // BLC
+	{0x3007, 0x80},
+	{0x400a, 0x01},
+	{REG_NULL, 0x00},
+};
+
+/*
+ * Xclk 24Mhz
+ * max_framerate 60fps
+ * mipi_datarate per lane 720Mbps
+ */
+static const struct regval ov8858_1632x1224_regs_4lane[] = {
+	{0x0100, 0x00},
+	{0x3501, 0x4d}, // exposure M
+	{0x3502, 0x40}, // exposure L
+	{0x3808, 0x06}, // x output size H
+	{0x3809, 0x60}, // x output size L
+	{0x380a, 0x04}, // y output size H
+	{0x380b, 0xc8}, // y output size L
+	{0x380c, 0x07}, // HTS H
+	{0x380d, 0x88}, // HTS L
+	{0x380e, 0x04}, // VTS H
+	{0x380f, 0xdc}, // VTS L
+	{0x3814, 0x03}, // x odd inc
+	{0x3821, 0x67}, // mirror on, bin on
+	{0x382a, 0x03}, // y odd inc
+	{0x3830, 0x08},
+	{0x3836, 0x02},
+	{0x3f0a, 0x00},
+	{0x4001, 0x10}, // total 128 black column
+	{0x4022, 0x06}, // Anchor left end H
+	{0x4023, 0x00}, // Anchor left end L
+	{0x4025, 0x2a}, // Anchor right start L
+	{0x4027, 0x2b}, // Anchor right end L
+	{0x402b, 0x04}, // top black line number
+	{0x402f, 0x04}, // bottom black line number
+	{0x4500, 0x58},
+	{0x4600, 0x00},
+	{0x4601, 0xcb},
+	{0x382d, 0x7f},
+	{0x0100, 0x01},
+	{REG_NULL, 0x00},
+};
+
+/*
+ * Xclk 24Mhz
+ * max_framerate 30fps
+ * mipi_datarate per lane 720Mbps
+ */
+static const struct regval ov8858_3264x2448_regs_4lane[] = {
+	{0x0100, 0x00},
+	{0x3501, 0x9a}, // exposure M
+	{0x3502, 0x20}, // exposure L
+	{0x3808, 0x0c}, // x output size H
+	{0x3809, 0xc0}, // x output size L
+	{0x380a, 0x09}, // y output size H
+	{0x380b, 0x90}, // y output size L
+	{0x380c, 0x07}, // HTS H
+	{0x380d, 0x94}, // HTS L
+	{0x380e, 0x09}, // VTS H
+	{0x380f, 0xaa}, // VTS L
+	{0x3814, 0x01}, // x odd inc
+	{0x3821, 0x46}, // mirror on, bin off
+	{0x382a, 0x01}, // y odd inc
+	{0x3830, 0x06},
+	{0x3836, 0x01},
+	{0x3f0a, 0x00},
+	{0x4001, 0x00}, // total 256 black column
+	{0x4022, 0x0c}, // Anchor left end H
+	{0x4023, 0x60}, // Anchor left end L
+	{0x4025, 0x36}, // Anchor right start L
+	{0x4027, 0x37}, // Anchor right end L
+	{0x402b, 0x08}, // top black line number
+	{0x402f, 0x08}, // interpolation x/y disable, Anchor one disable
+	{0x4500, 0x58},
+	{0x4600, 0x01},
+	{0x4601, 0x97},
+	{0x382d, 0xff},
+	{REG_NULL, 0x00},
+};
+
+static const struct ov8858_mode ov8858_modes[] = {
+	{
+		.width = 3264,
+		.height = 2448,
+		.exp_def = 2464,
+		.hts_def = 1940 * 2,
+		.vts_def = 2472,
+		.reg_modes = {
+			.mode_2lanes = ov8858_3264x2448_regs_2lane,
+			.mode_4lanes = ov8858_3264x2448_regs_4lane,
+		},
+	},
+	{
+		.width = 1632,
+		.height = 1224,
+		.exp_def = 1232,
+		.hts_def = 1928 * 2,
+		.vts_def = 1244,
+		.reg_modes = {
+			.mode_2lanes = ov8858_1632x1224_regs_2lane,
+			.mode_4lanes = ov8858_1632x1224_regs_4lane,
+		},
+	},
+};
+#define OV8858_NUM_MODES ARRAY_SIZE(ov8858_modes)
+
+static const s64 link_freq_menu_items[] = {
+	OV8858_LINK_FREQ
+};
+
+static const char * const ov8858_test_pattern_menu[] = {
+	"Disabled",
+	"Vertical Color Bar Type 1",
+	"Vertical Color Bar Type 2",
+	"Vertical Color Bar Type 3",
+	"Vertical Color Bar Type 4"
+};
+
+static int ov8858_write_reg(struct i2c_client *client, u32 reg, u32 val,
+			    int *err)
+{
+	unsigned int len = (reg >> OV8858_REG_SIZE_SHIFT) & 3;
+	u16 addr = reg & OV8858_REG_ADDR_MASK;
+	u8 buf[6];
+	int ret;
+
+	if (err && *err)
+		return *err;
+
+	put_unaligned_be16(addr, buf);
+	put_unaligned_be32(val << (8 * (4 - len)), buf + 2);
+
+	ret = i2c_master_send(client, buf, len + 2);
+	if (ret != len + 2) {
+		if (err)
+			*err = ret < 0 ? ret : -EIO;
+		return ret < 0 ? ret : -EIO;
+	}
+
+	return 0;
+}
+
+static int ov8858_write_array(struct i2c_client *client, const struct regval *regs)
+{
+	unsigned int i;
+	int ret = 0;
+
+	for (i = 0; ret == 0 && regs[i].addr != REG_NULL; ++i)
+		ov8858_write_reg(client, OV8858_REG_8BIT(regs[i].addr),
+				 regs[i].val, &ret);
+
+	return ret;
+}
+
+static int ov8858_read_reg(struct i2c_client *client, u32 reg, u32 *val)
+{
+	__be16 reg_addr_be = cpu_to_be16(reg & OV8858_REG_ADDR_MASK);
+	unsigned int len = (reg >> OV8858_REG_SIZE_SHIFT) & 3;
+	struct i2c_msg msgs[2];
+	__be32 data_be = 0;
+	u8 *data_be_p;
+	int ret;
+
+	data_be_p = (u8 *)&data_be;
+	/* Write register address */
+	msgs[0].addr = client->addr;
+	msgs[0].flags = 0;
+	msgs[0].len = 2;
+	msgs[0].buf = (u8 *)&reg_addr_be;
+
+	/* Read data from register */
+	msgs[1].addr = client->addr;
+	msgs[1].flags = I2C_M_RD;
+	msgs[1].len = len;
+	msgs[1].buf = &data_be_p[4 - len];
+
+	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+	if (ret != ARRAY_SIZE(msgs))
+		return -EIO;
+
+	*val = be32_to_cpu(data_be);
+
+	return 0;
+}
+
+static int ov8858_set_fmt(struct v4l2_subdev *sd,
+			  struct v4l2_subdev_state *state,
+			  struct v4l2_subdev_format *fmt)
+{
+	struct ov8858 *ov8858 = to_ov8858(sd);
+	struct v4l2_mbus_framefmt *format;
+	const struct ov8858_mode *mode;
+	s64 h_blank, vblank_def;
+
+	mode = v4l2_find_nearest_size(ov8858_modes, OV8858_NUM_MODES,
+				      width, height, fmt->format.width,
+				      fmt->format.height);
+
+	fmt->format.code = MEDIA_BUS_FMT_SBGGR10_1X10;
+	fmt->format.width = mode->width;
+	fmt->format.height = mode->height;
+	fmt->format.field = V4L2_FIELD_NONE;
+
+	format = v4l2_subdev_get_pad_format(sd, state, 0);
+
+	if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
+		ov8858->cur_mode = mode;
+		h_blank = mode->hts_def - mode->width;
+		__v4l2_ctrl_modify_range(ov8858->hblank, h_blank, h_blank, 1,
+					 h_blank);
+
+		vblank_def = mode->vts_def - mode->height;
+		__v4l2_ctrl_modify_range(ov8858->vblank, vblank_def,
+					 OV8858_VTS_MAX - mode->height, 1,
+					 vblank_def);
+	}
+
+	*format = fmt->format;
+
+	return 0;
+}
+
+static int ov8858_enum_mbus_code(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *state,
+				 struct v4l2_subdev_mbus_code_enum *code)
+{
+	if (code->index != 0)
+		return -EINVAL;
+
+	code->code = MEDIA_BUS_FMT_SBGGR10_1X10;
+
+	return 0;
+}
+
+static int ov8858_enum_frame_sizes(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_state *state,
+				   struct v4l2_subdev_frame_size_enum *fse)
+{
+	if (fse->index >= OV8858_NUM_MODES)
+		return -EINVAL;
+
+	if (fse->code != MEDIA_BUS_FMT_SBGGR10_1X10)
+		return -EINVAL;
+
+	fse->min_width  = ov8858_modes[fse->index].width;
+	fse->max_width  = ov8858_modes[fse->index].width;
+	fse->max_height = ov8858_modes[fse->index].height;
+	fse->min_height = ov8858_modes[fse->index].height;
+
+	return 0;
+}
+
+static int ov8858_enable_test_pattern(struct ov8858 *ov8858, u32 pattern)
+{
+	u32 val;
+
+	if (pattern)
+		val = (pattern - 1) | OV8858_TEST_PATTERN_ENABLE;
+	else
+		val = OV8858_TEST_PATTERN_DISABLE;
+
+	return ov8858_write_reg(ov8858->client, OV8858_REG_TEST_PATTERN,
+				val, NULL);
+}
+
+static int ov8858_start_stream(struct ov8858 *ov8858)
+{
+	const struct regval *reg_list;
+	int ret;
+
+	ret = ov8858_write_array(ov8858->client, ov8858->global_regs);
+	if (ret)
+		return ret;
+
+	if (ov8858->num_lanes == 4)
+		reg_list = ov8858->cur_mode->reg_modes.mode_4lanes;
+	else
+		reg_list = ov8858->cur_mode->reg_modes.mode_2lanes;
+
+	ret = ov8858_write_array(ov8858->client, reg_list);
+	if (ret)
+		return ret;
+
+	/* 200 usec max to have PLL stabilize. */
+	fsleep(200);
+
+	ret = __v4l2_ctrl_handler_setup(&ov8858->ctrl_handler);
+	if (ret)
+		return ret;
+
+	ret = ov8858_write_reg(ov8858->client, OV8858_REG_CTRL_MODE,
+			       OV8858_MODE_STREAMING, NULL);
+	if (ret)
+		return ret;
+
+	/* t5 (fixed) = 10msec before entering streaming state */
+	fsleep(10000);
+
+	return 0;
+}
+
+static int ov8858_stop_stream(struct ov8858 *ov8858)
+{
+	return ov8858_write_reg(ov8858->client, OV8858_REG_CTRL_MODE,
+				OV8858_MODE_SW_STANDBY, NULL);
+}
+
+static int ov8858_s_stream(struct v4l2_subdev *sd, int on)
+{
+	struct ov8858 *ov8858 = to_ov8858(sd);
+	struct i2c_client *client = ov8858->client;
+	struct v4l2_subdev_state *state;
+	int ret = 0;
+
+	/* Only for locking purpose. */
+	state = v4l2_subdev_lock_and_get_active_state(sd);
+
+	if (!!on == ov8858->streaming)
+		goto unlock_and_return;
+
+	if (on) {
+		ret = pm_runtime_resume_and_get(&client->dev);
+		if (ret < 0)
+			goto unlock_and_return;
+
+		ret = ov8858_start_stream(ov8858);
+		if (ret) {
+			dev_err(&client->dev, "Failed to start streaming\n");
+			pm_runtime_put(&client->dev);
+			goto unlock_and_return;
+		}
+	} else {
+		ov8858_stop_stream(ov8858);
+		pm_runtime_put(&client->dev);
+	}
+
+	ov8858->streaming = !!on;
+
+unlock_and_return:
+	v4l2_subdev_unlock_state(state);
+
+	return ret;
+}
+
+static int ov8858_power_on(struct ov8858 *ov8858)
+{
+	struct device *dev = &ov8858->client->dev;
+	unsigned long delay_us;
+	int ret;
+
+	ret = clk_set_rate(ov8858->xvclk, OV8858_XVCLK_FREQ);
+	if (ret < 0)
+		dev_warn(dev, "Failed to set xvclk rate (24MHz)\n");
+	if (clk_get_rate(ov8858->xvclk) != OV8858_XVCLK_FREQ)
+		dev_warn(dev, "xvclk mismatched, modes are based on 24MHz\n");
+
+	ret = clk_prepare_enable(ov8858->xvclk);
+	if (ret < 0) {
+		dev_err(dev, "Failed to enable xvclk\n");
+		return ret;
+	}
+
+	ret = regulator_bulk_enable(OV8858_NUM_SUPPLIES, ov8858->supplies);
+	if (ret < 0) {
+		dev_err(dev, "Failed to enable regulators\n");
+		goto disable_clk;
+	}
+
+	/*
+	 * The chip manual only suggests 8192 cycles prior to first SCCB
+	 * transaction, but a double sleep between the release of gpios
+	 * helps with sporadic failures observed at probe time.
+	 */
+	delay_us = DIV_ROUND_UP(8192, OV8858_XVCLK_FREQ / 1000 / 1000);
+
+	gpiod_set_value_cansleep(ov8858->reset_gpio, 0);
+	fsleep(delay_us);
+	gpiod_set_value_cansleep(ov8858->pwdn_gpio, 0);
+	fsleep(delay_us);
+
+	return 0;
+
+disable_clk:
+	clk_disable_unprepare(ov8858->xvclk);
+
+	return ret;
+}
+
+static void ov8858_power_off(struct ov8858 *ov8858)
+{
+	gpiod_set_value_cansleep(ov8858->pwdn_gpio, 1);
+	clk_disable_unprepare(ov8858->xvclk);
+	gpiod_set_value_cansleep(ov8858->reset_gpio, 1);
+
+	regulator_bulk_disable(OV8858_NUM_SUPPLIES, ov8858->supplies);
+}
+
+static int ov8858_runtime_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct ov8858 *ov8858 = to_ov8858(sd);
+
+	return ov8858_power_on(ov8858);
+}
+
+static int ov8858_runtime_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct ov8858 *ov8858 = to_ov8858(sd);
+
+	ov8858_power_off(ov8858);
+
+	return 0;
+}
+
+static int ov8858_init_cfg(struct v4l2_subdev *sd,
+			   struct v4l2_subdev_state *sd_state)
+{
+	struct ov8858 *ov8858 = to_ov8858(sd);
+	const struct ov8858_mode *def_mode = ov8858->cur_mode;
+	struct v4l2_subdev_format fmt = {
+		.which = V4L2_SUBDEV_FORMAT_TRY,
+		.format = {
+			.width = def_mode->width,
+			.height = def_mode->height,
+		},
+	};
+
+	ov8858_set_fmt(sd, sd_state, &fmt);
+
+	return 0;
+}
+
+static const struct v4l2_subdev_video_ops ov8858_video_ops = {
+	.s_stream = ov8858_s_stream,
+};
+
+static const struct v4l2_subdev_pad_ops ov8858_pad_ops = {
+	.init_cfg = ov8858_init_cfg,
+	.enum_mbus_code = ov8858_enum_mbus_code,
+	.enum_frame_size = ov8858_enum_frame_sizes,
+	.get_fmt = v4l2_subdev_get_fmt,
+	.set_fmt = ov8858_set_fmt,
+};
+
+static const struct v4l2_subdev_ops ov8858_subdev_ops = {
+	.video	= &ov8858_video_ops,
+	.pad	= &ov8858_pad_ops,
+};
+
+static int ov8858_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct ov8858 *ov8858 = container_of(ctrl->handler,
+					     struct ov8858, ctrl_handler);
+	struct i2c_client *client = ov8858->client;
+	s64 max_exp;
+	int ret;
+
+	/* Propagate change of current control to all related controls */
+	switch (ctrl->id) {
+	case V4L2_CID_VBLANK:
+		/* Update max exposure while meeting expected vblanking */
+		max_exp = ov8858->cur_mode->height + ctrl->val - 4;
+		__v4l2_ctrl_modify_range(ov8858->exposure,
+					 ov8858->exposure->minimum, max_exp,
+					 ov8858->exposure->step,
+					 ov8858->exposure->default_value);
+		break;
+	}
+
+	if (!pm_runtime_get_if_in_use(&client->dev))
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_EXPOSURE:
+		/* 4 least significant bits of exposure are fractional part */
+		ret = ov8858_write_reg(ov8858->client, OV8858_REG_EXPOSURE,
+				       ctrl->val << 4, NULL);
+		break;
+	case V4L2_CID_ANALOGUE_GAIN:
+		ret = 0;
+		ov8858_write_reg(ov8858->client, OV8858_REG_GAIN_H,
+				 (ctrl->val >> OV8858_GAIN_H_SHIFT) &
+				 OV8858_GAIN_H_MASK, &ret);
+		ov8858_write_reg(ov8858->client,
+				 OV8858_REG_GAIN_L,
+				 ctrl->val & OV8858_GAIN_L_MASK, &ret);
+		break;
+	case V4L2_CID_DIGITAL_GAIN:
+		ret = 0;
+		ov8858_write_reg(ov8858->client, OV8858_REG_DGAIN_H,
+				 (ctrl->val >> OV8858_DGAIN_H_SHIFT) &
+				 OV8858_DGAIN_H_MASK, &ret);
+		ov8858_write_reg(ov8858->client, OV8858_REG_DGAIN_L,
+				 ctrl->val & OV8858_DGAIN_L_MASK, &ret);
+		break;
+	case V4L2_CID_VBLANK:
+		ret = ov8858_write_reg(ov8858->client, OV8858_REG_VTS,
+				       ctrl->val + ov8858->cur_mode->height,
+				       NULL);
+		break;
+	case V4L2_CID_TEST_PATTERN:
+		ret = ov8858_enable_test_pattern(ov8858, ctrl->val);
+		break;
+	default:
+		ret = -EINVAL;
+		dev_warn(&client->dev, "%s Unhandled id: 0x%x\n",
+			 __func__, ctrl->id);
+		break;
+	}
+
+	return ret;
+}
+
+static const struct v4l2_ctrl_ops ov8858_ctrl_ops = {
+	.s_ctrl = ov8858_set_ctrl,
+};
+
+static int ov8858_initialize_controls(struct ov8858 *ov8858)
+{
+	struct v4l2_fwnode_device_properties props;
+	struct v4l2_ctrl_handler *handler;
+	const struct ov8858_mode *mode;
+	s64 exposure_max, vblank_def;
+	struct v4l2_ctrl *ctrl;
+	u32 h_blank;
+	int ret;
+
+	handler = &ov8858->ctrl_handler;
+	mode = ov8858->cur_mode;
+	ret = v4l2_ctrl_handler_init(handler, 10);
+	if (ret)
+		return ret;
+	handler->lock = &ov8858->mutex;
+
+	ctrl = v4l2_ctrl_new_int_menu(handler, NULL, V4L2_CID_LINK_FREQ,
+				      0, 0, link_freq_menu_items);
+	if (ctrl)
+		ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+	v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE,
+			  0, ov8858->pixel_rate, 1, ov8858->pixel_rate);
+
+	h_blank = mode->hts_def - mode->width;
+	ov8858->hblank = v4l2_ctrl_new_std(handler, NULL, V4L2_CID_HBLANK,
+					   h_blank, h_blank, 1, h_blank);
+	if (ov8858->hblank)
+		ov8858->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+	vblank_def = mode->vts_def - mode->height;
+	ov8858->vblank = v4l2_ctrl_new_std(handler, &ov8858_ctrl_ops,
+					   V4L2_CID_VBLANK, vblank_def,
+					   OV8858_VTS_MAX - mode->height,
+					   1, vblank_def);
+
+	exposure_max = mode->vts_def - 4;
+	ov8858->exposure = v4l2_ctrl_new_std(handler, &ov8858_ctrl_ops,
+					     V4L2_CID_EXPOSURE,
+					     OV8858_EXPOSURE_MIN,
+					     exposure_max, OV8858_EXPOSURE_STEP,
+					     mode->exp_def);
+
+	v4l2_ctrl_new_std(handler, &ov8858_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
+			  OV8858_GAIN_MIN, OV8858_GAIN_MAX, OV8858_GAIN_STEP,
+			  OV8858_GAIN_DEFAULT);
+
+	v4l2_ctrl_new_std(handler, &ov8858_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
+			  OV8858_DGAIN_MIN, OV8858_DGAIN_MAX, OV8858_DGAIN_STEP,
+			  OV8858_DGAIN_DEFAULT);
+
+	v4l2_ctrl_new_std_menu_items(handler, &ov8858_ctrl_ops,
+				     V4L2_CID_TEST_PATTERN,
+				     ARRAY_SIZE(ov8858_test_pattern_menu) - 1,
+				     0, 0, ov8858_test_pattern_menu);
+
+	if (handler->error) {
+		ret = handler->error;
+		goto err_free_handler;
+	}
+
+	ret = v4l2_fwnode_device_parse(&ov8858->client->dev, &props);
+	if (ret)
+		goto err_free_handler;
+
+	ret = v4l2_ctrl_new_fwnode_properties(handler, &ov8858_ctrl_ops,
+					      &props);
+	if (ret)
+		goto err_free_handler;
+
+	ov8858->subdev.ctrl_handler = handler;
+
+	return 0;
+
+err_free_handler:
+	dev_err(&ov8858->client->dev, "Failed to init controls (%d)\n", ret);
+	v4l2_ctrl_handler_free(handler);
+
+	return ret;
+}
+
+static int ov8858_check_sensor_id(struct ov8858 *ov8858,
+				  struct i2c_client *client)
+{
+	struct device *dev = &ov8858->client->dev;
+	u32 id = 0;
+	int ret;
+
+	ret = ov8858_read_reg(client, OV8858_REG_CHIP_ID, &id);
+	if (id != OV8858_CHIP_ID) {
+		dev_err(dev, "Unexpected sensor id(%06x), ret(%d)\n", id, ret);
+		return ret;
+	}
+
+	ret = ov8858_read_reg(client, OV8858_CHIP_REVISION_REG, &id);
+	if (ret) {
+		dev_err(dev, "Read chip revision register error\n");
+		return ret;
+	}
+
+	dev_info(dev, "Detected OV%06x sensor, REVISION 0x%x\n",
+		 OV8858_CHIP_ID, id);
+
+	if (id == OV8858_R2A) {
+		ov8858->global_regs = ov8858->num_lanes == 4
+				    ? ov8858_global_regs_r2a_4lane
+				    : ov8858_global_regs_r2a_2lane;
+	} else {
+		ov8858->global_regs = ov8858_global_regs_r1a;
+		dev_warn(dev, "R1A may not work well!\n");
+	}
+
+	return 0;
+}
+
+static int ov8858_configure_regulators(struct ov8858 *ov8858)
+{
+	unsigned int i;
+
+	for (i = 0; i < OV8858_NUM_SUPPLIES; i++)
+		ov8858->supplies[i].supply = ov8858_supply_names[i];
+
+	return devm_regulator_bulk_get(&ov8858->client->dev,
+				       OV8858_NUM_SUPPLIES,
+				       ov8858->supplies);
+}
+
+static int ov8858_parse_of(struct ov8858 *ov8858)
+{
+	struct v4l2_fwnode_endpoint vep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
+	struct device *dev = &ov8858->client->dev;
+	struct fwnode_handle *endpoint;
+	int ret;
+
+	endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL);
+	if (!endpoint) {
+		dev_err(dev, "Failed to get endpoint\n");
+		return -EINVAL;
+	}
+
+	ret = v4l2_fwnode_endpoint_parse(endpoint, &vep);
+	if (ret) {
+		dev_err(dev, "Failed to parse endpoint: %d\n", ret);
+		fwnode_handle_put(endpoint);
+		return ret;
+	}
+
+	ov8858->num_lanes = vep.bus.mipi_csi2.num_data_lanes;
+	switch (ov8858->num_lanes) {
+	case 4:
+		fallthrough;
+	case 2:
+		break;
+	default:
+		dev_err(dev, "Unsupported number of data lanes %u\n",
+			ov8858->num_lanes);
+		fwnode_handle_put(endpoint);
+		return -EINVAL;
+	}
+
+	/* pixel rate = link frequency * 2 * lanes / bpp */
+	ov8858->cur_mode = &ov8858_modes[0];
+	ov8858->pixel_rate = OV8858_LINK_FREQ * 2U * ov8858->num_lanes / 10U;
+	ov8858->subdev.fwnode = endpoint;
+
+	dev_dbg(dev, "num_lanes = %u - pixel_rate = %u\n",
+		ov8858->num_lanes, ov8858->pixel_rate);
+
+	return 0;
+}
+
+static int ov8858_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct v4l2_subdev *sd;
+	struct ov8858 *ov8858;
+	int ret;
+
+	ov8858 = devm_kzalloc(dev, sizeof(*ov8858), GFP_KERNEL);
+	if (!ov8858)
+		return -ENOMEM;
+
+	ov8858->client = client;
+
+	ov8858->xvclk = devm_clk_get(dev, "xvclk");
+	if (IS_ERR(ov8858->xvclk))
+		return dev_err_probe(dev, PTR_ERR(ov8858->xvclk),
+				     "failed to get xvclk\n");
+
+	ov8858->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+						     GPIOD_OUT_HIGH);
+	if (IS_ERR(ov8858->reset_gpio))
+		return dev_err_probe(dev, PTR_ERR(ov8858->reset_gpio),
+				     "failed to get reset gpio\n");
+
+	ov8858->pwdn_gpio = devm_gpiod_get_optional(dev, "powerdown",
+						    GPIOD_OUT_HIGH);
+	if (IS_ERR(ov8858->pwdn_gpio))
+		return dev_err_probe(dev, PTR_ERR(ov8858->pwdn_gpio),
+				     "failed to get powerdown gpio\n");
+
+	ret = ov8858_configure_regulators(ov8858);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to get regulators\n");
+
+	sd = &ov8858->subdev;
+	v4l2_i2c_subdev_init(sd, client, &ov8858_subdev_ops);
+
+	ret = ov8858_parse_of(ov8858);
+	if (ret != 0)
+		return ret;
+
+	ret = ov8858_initialize_controls(ov8858);
+	if (ret)
+		return ret;
+
+	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+	ov8858->pad.flags = MEDIA_PAD_FL_SOURCE;
+	sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
+	ret = media_entity_pads_init(&sd->entity, 1, &ov8858->pad);
+	if (ret < 0)
+		goto err_free_handler;
+
+	sd->state_lock = &ov8858->mutex;
+	ret = v4l2_subdev_init_finalize(sd);
+	if (ret < 0) {
+		dev_err(&client->dev, "subdev initialization error %d\n", ret);
+		goto err_clean_entity;
+	}
+
+	ret = ov8858_power_on(ov8858);
+	if (ret)
+		goto err_clean_entity;
+
+	ret = ov8858_check_sensor_id(ov8858, client);
+	if (ret)
+		goto err_power_off;
+
+	ret = v4l2_async_register_subdev_sensor(sd);
+	if (ret) {
+		dev_err(dev, "v4l2 async register subdev failed\n");
+		goto err_power_off;
+	}
+
+	pm_runtime_set_active(dev);
+	pm_runtime_enable(dev);
+	pm_runtime_idle(dev);
+
+	return 0;
+
+err_power_off:
+	ov8858_power_off(ov8858);
+err_clean_entity:
+	media_entity_cleanup(&sd->entity);
+err_free_handler:
+	v4l2_ctrl_handler_free(&ov8858->ctrl_handler);
+
+	return ret;
+}
+
+static void ov8858_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct ov8858 *ov8858 = to_ov8858(sd);
+
+	v4l2_async_unregister_subdev(sd);
+	media_entity_cleanup(&sd->entity);
+	v4l2_ctrl_handler_free(&ov8858->ctrl_handler);
+
+	pm_runtime_disable(&client->dev);
+	if (!pm_runtime_status_suspended(&client->dev))
+		ov8858_power_off(ov8858);
+	pm_runtime_set_suspended(&client->dev);
+}
+
+static const struct of_device_id ov8858_of_match[] = {
+	{ .compatible = "ovti,ov8858" },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, ov8858_of_match);
+
+static const struct dev_pm_ops ov8858_pm_ops = {
+	SET_RUNTIME_PM_OPS(ov8858_runtime_suspend,
+			   ov8858_runtime_resume, NULL)
+};
+
+static struct i2c_driver ov8858_i2c_driver = {
+	.driver = {
+		.name = "ov8858",
+		.pm = &ov8858_pm_ops,
+		.of_match_table = of_match_ptr(ov8858_of_match),
+	},
+	.probe_new	= &ov8858_probe,
+	.remove		= &ov8858_remove,
+};
+
+module_i2c_driver(ov8858_i2c_driver);
+
+MODULE_DESCRIPTION("OmniVision ov8858 sensor driver");
+MODULE_LICENSE("GPL");
-- 
2.38.1


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

* Re: [PATCH v2 2/2] media: i2c: Add driver for OmniVision OV8858
  2023-01-06 20:39 ` [PATCH v2 2/2] media: i2c: Add driver for " Jacopo Mondi
@ 2023-01-06 23:07   ` Laurent Pinchart
  2023-01-07 15:17     ` Jacopo Mondi
  0 siblings, 1 reply; 9+ messages in thread
From: Laurent Pinchart @ 2023-01-06 23:07 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: Nicholas Roth, Robert Mader, Sakari Ailus, linux-media, Jacopo Mondi

Hi Jacopo and Nicholas,

Thank you for the patch.

On Fri, Jan 06, 2023 at 09:39:09PM +0100, Jacopo Mondi wrote:
> From: Nicholas Roth <nicholas@rothemail.net>
> 
> Add a driver for OmniVision OV8858 image sensor.
> 
> The driver currently supports operations with 2 and 4 data lanes, in
> full resolution and half-binned resolution modes.
> 
> The driver has been upported from the PinephonePro BSP available at
> https://gitlab.com/pine64-org/linux.git
> at commit 8c4a90c12dc2 ("media: i2c: ov8858: Use default subdev
> name").
> 
> Signed-off-by: Nicholas Roth <nicholas@rothemail.net>
> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> ---
>  MAINTAINERS                |    9 +
>  drivers/media/i2c/Kconfig  |   13 +
>  drivers/media/i2c/Makefile |    1 +
>  drivers/media/i2c/ov8858.c | 1952 ++++++++++++++++++++++++++++++++++++
>  4 files changed, 1975 insertions(+)
>  create mode 100644 drivers/media/i2c/ov8858.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index f61eb221415b..fafb0a01be93 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -15524,6 +15524,15 @@ T:	git git://linuxtv.org/media_tree.git
>  F:	Documentation/devicetree/bindings/media/i2c/ov8856.yaml
>  F:	drivers/media/i2c/ov8856.c
>  
> +OMNIVISION OV8858 SENSOR DRIVER
> +M:	Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> +M:	Nicholas Roth <nicholas@rothemail.net>
> +L:	linux-media@vger.kernel.org
> +S:	Maintained
> +T:	git git://linuxtv.org/media_tree.git
> +F:	Documentation/devicetree/bindings/media/i2c/ovti,ov8858.yaml
> +F:	drivers/media/i2c/ov8858.c
> +
>  OMNIVISION OV9282 SENSOR DRIVER
>  M:	Paul J. Murphy <paul.j.murphy@intel.com>
>  M:	Daniele Alessandrelli <daniele.alessandrelli@intel.com>
> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> index 833241897d63..12ba8542778f 100644
> --- a/drivers/media/i2c/Kconfig
> +++ b/drivers/media/i2c/Kconfig
> @@ -645,6 +645,19 @@ config VIDEO_OV8856
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called ov8856.
>  
> +config VIDEO_OV8858
> +	tristate "OmniVision OV8858 sensor support"
> +	depends on I2C && PM && VIDEO_DEV
> +	select MEDIA_CONTROLLER
> +	select VIDEO_V4L2_SUBDEV_API
> +	select V4L2_FWNODE
> +	help
> +	  This is a Video4Linux2 sensor driver for OmniVision
> +	  OV8858 camera sensor.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called ov8858.
> +
>  config VIDEO_OV8865
>  	tristate "OmniVision OV8865 sensor support"
>  	depends on I2C && PM && VIDEO_DEV
> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> index 4d6c052bb5a7..b611a8277d57 100644
> --- a/drivers/media/i2c/Makefile
> +++ b/drivers/media/i2c/Makefile
> @@ -96,6 +96,7 @@ obj-$(CONFIG_VIDEO_OV7670) += ov7670.o
>  obj-$(CONFIG_VIDEO_OV772X) += ov772x.o
>  obj-$(CONFIG_VIDEO_OV7740) += ov7740.o
>  obj-$(CONFIG_VIDEO_OV8856) += ov8856.o
> +obj-$(CONFIG_VIDEO_OV8858) += ov8858.o
>  obj-$(CONFIG_VIDEO_OV8865) += ov8865.o
>  obj-$(CONFIG_VIDEO_OV9282) += ov9282.o
>  obj-$(CONFIG_VIDEO_OV9640) += ov9640.o
> diff --git a/drivers/media/i2c/ov8858.c b/drivers/media/i2c/ov8858.c
> new file mode 100644
> index 000000000000..bfdfac49caa9
> --- /dev/null
> +++ b/drivers/media/i2c/ov8858.c
> @@ -0,0 +1,1952 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2023 Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> + * Copyright (C) 2022 Nicholas Roth <nicholas@rothemail.net>
> + * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd.
> + */
> +
> +#include <asm/unaligned.h>
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_graph.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/slab.h>
> +
> +#include <media/media-entity.h>
> +#include <media/v4l2-async.h>
> +#include <media/v4l2-common.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-mediabus.h>
> +#include <media/v4l2-subdev.h>
> +
> +#define OV8858_LINK_FREQ		360000000U
> +#define OV8858_XVCLK_FREQ		24000000
> +
> +#define OV8858_REG_SIZE_SHIFT		16
> +#define OV8858_REG_ADDR_MASK		0xffff
> +#define OV8858_REG_8BIT(n)		((1U << OV8858_REG_SIZE_SHIFT) | (n))
> +#define OV8858_REG_16BIT(n)		((2U << OV8858_REG_SIZE_SHIFT) | (n))
> +#define OV8858_REG_24BIT(n)		((3U << OV8858_REG_SIZE_SHIFT) | (n))

/me likes this :-)

> +
> +#define OV8858_CHIP_ID			0x008858
> +#define OV8858_REG_CHIP_ID		OV8858_REG_24BIT(0x300a)
> +
> +#define OV8858_REG_CTRL_MODE		OV8858_REG_8BIT(0x0100)
> +#define OV8858_MODE_SW_STANDBY		0x0
> +#define OV8858_MODE_STREAMING		0x1

Please move this up to sort registers by address. Same thing in several
locations below.

> +
> +#define OV8858_REG_EXPOSURE		OV8858_REG_24BIT(0x3500)
> +#define	OV8858_EXPOSURE_MIN		4
> +#define	OV8858_EXPOSURE_STEP		1
> +#define OV8858_VTS_MAX			0x7fff
> +
> +#define OV8858_REG_GAIN_H		OV8858_REG_8BIT(0x3508)
> +#define OV8858_REG_GAIN_L		OV8858_REG_8BIT(0x3509)

This is a single 16-bit register. You will be able to drop the mask and
shift macros.

> +#define OV8858_GAIN_H_MASK		0x07
> +#define OV8858_GAIN_H_SHIFT		8
> +#define OV8858_GAIN_L_MASK		0xff
> +#define OV8858_GAIN_MIN			0x0
> +#define OV8858_GAIN_MAX			0x7ff

According to the documentation, the gain is a 13-bit value ([12:0]),
should this be 0x1fff ? I'm not sure we can trust the documentation
though, the description of register 0x3509 is clearly wrong.

> +#define OV8858_GAIN_STEP		1
> +#define OV8858_GAIN_DEFAULT		0x80
> +
> +#define OV8858_REG_DGAIN_H		OV8858_REG_8BIT(0x350a)
> +#define OV8858_REG_DGAIN_L		OV8858_REG_8BIT(0x350b)

Same here.

> +#define OV8858_DGAIN_H_MASK		0xff
> +#define OV8858_DGAIN_H_SHIFT		6
> +#define OV8858_DGAIN_L_MASK		0x3f
> +#define OV8858_DGAIN_MIN		0x0
> +#define OV8858_DGAIN_MAX		0x3fff
> +#define OV8858_DGAIN_STEP		1
> +#define OV8858_DGAIN_DEFAULT		0x200
> +
> +#define OV8858_REG_TEST_PATTERN		OV8858_REG_8BIT(0x5e00)
> +#define	OV8858_TEST_PATTERN_ENABLE	0x80
> +#define	OV8858_TEST_PATTERN_DISABLE	0x0
> +
> +#define OV8858_REG_VTS			OV8858_REG_16BIT(0x380e)
> +
> +#define REG_NULL			0xffff
> +
> +#define OV8858_CHIP_REVISION_REG	OV8858_REG_8BIT(0x302a)

It's called "SUB ID" in the documentation, how about sticking to that
name ? Actually this applies to the other registers above, should we
keep them as close as possible to the datasheet ?

> +#define OV8858_R1A			0xb0
> +#define OV8858_R2A			0xb2
> +
> +static const char * const ov8858_supply_names[] = {
> +	"avdd",		/* Analog power */
> +	"dovdd",	/* Digital I/O power */
> +	"dvdd",		/* Digital core power */
> +};
> +
> +#define OV8858_NUM_SUPPLIES ARRAY_SIZE(ov8858_supply_names)
> +
> +struct regval {
> +	u16 addr;
> +	u8 val;
> +};
> +
> +struct regval_modes {
> +	const struct regval *mode_2lanes;
> +	const struct regval *mode_4lanes;
> +};
> +
> +struct ov8858_mode {
> +	u32 width;
> +	u32 height;
> +	u32 hts_def;
> +	u32 vts_def;
> +	u32 exp_def;
> +	const struct regval_modes reg_modes;
> +};
> +
> +struct ov8858 {
> +	struct i2c_client	*client;
> +	struct clk		*xvclk;
> +	struct gpio_desc	*reset_gpio;
> +	struct gpio_desc	*pwdn_gpio;
> +	struct regulator_bulk_data supplies[OV8858_NUM_SUPPLIES];
> +
> +	struct v4l2_subdev	subdev;
> +	struct media_pad	pad;
> +
> +	/* Protect controls and image format. */
> +	struct mutex		mutex;

V4L2 provides a default lock in the control handler. You could use it
and drop this, using the control handler lock as the active state lock
as well.  No functional difference (the important part is to use the
same lock for the control handler and the active state), but one less
field in this structure.

If you want to keep this field, you're missing mutex_init() and
mutex_destroy().

> +
> +	struct v4l2_ctrl_handler ctrl_handler;
> +	struct v4l2_ctrl	*exposure;
> +	struct v4l2_ctrl	*hblank;
> +	struct v4l2_ctrl	*vblank;
> +
> +	const struct regval	*global_regs;
> +	const struct ov8858_mode *cur_mode;

It would be nice to drop the cur_mode field, that would simplify
set_fmt(), but it's not a hard requirement. Let's minimize its usage
though.

> +
> +	bool			streaming;
> +	unsigned int		num_lanes;
> +	unsigned int		pixel_rate;
> +};
> +
> +#define to_ov8858(sd) container_of(sd, struct ov8858, subdev)

Maybe a static inline function to provide type safety ?

> +
> +static const struct regval ov8858_global_regs_r1a[] = {
> +	{0x0100, 0x00},
> +	{0x0100, 0x00},
> +	{0x0100, 0x00},
> +	{0x0100, 0x00},
> +	{0x0302, 0x1e},
> +	{0x0303, 0x00},
> +	{0x0304, 0x03},
> +	{0x030e, 0x00},
> +	{0x030f, 0x09},
> +	{0x0312, 0x01},
> +	{0x031e, 0x0c},
> +	{0x3600, 0x00},
> +	{0x3601, 0x00},
> +	{0x3602, 0x00},
> +	{0x3603, 0x00},
> +	{0x3604, 0x22},
> +	{0x3605, 0x30},
> +	{0x3606, 0x00},
> +	{0x3607, 0x20},
> +	{0x3608, 0x11},
> +	{0x3609, 0x28},
> +	{0x360a, 0x00},
> +	{0x360b, 0x06},
> +	{0x360c, 0xdc},
> +	{0x360d, 0x40},
> +	{0x360e, 0x0c},
> +	{0x360f, 0x20},
> +	{0x3610, 0x07},
> +	{0x3611, 0x20},
> +	{0x3612, 0x88},
> +	{0x3613, 0x80},
> +	{0x3614, 0x58},
> +	{0x3615, 0x00},
> +	{0x3616, 0x4a},
> +	{0x3617, 0xb0},
> +	{0x3618, 0x56},
> +	{0x3619, 0x70},
> +	{0x361a, 0x99},
> +	{0x361b, 0x00},
> +	{0x361c, 0x07},
> +	{0x361d, 0x00},
> +	{0x361e, 0x00},
> +	{0x361f, 0x00},
> +	{0x3638, 0xff},
> +	{0x3633, 0x0c},
> +	{0x3634, 0x0c},
> +	{0x3635, 0x0c},
> +	{0x3636, 0x0c},
> +	{0x3645, 0x13},
> +	{0x3646, 0x83},
> +	{0x364a, 0x07},
> +	{0x3015, 0x01},
> +	{0x3018, 0x32},
> +	{0x3020, 0x93},
> +	{0x3022, 0x01},
> +	{0x3031, 0x0a},
> +	{0x3034, 0x00},
> +	{0x3106, 0x01},
> +	{0x3305, 0xf1},
> +	{0x3308, 0x00},
> +	{0x3309, 0x28},
> +	{0x330a, 0x00},
> +	{0x330b, 0x20},
> +	{0x330c, 0x00},
> +	{0x330d, 0x00},
> +	{0x330e, 0x00},
> +	{0x330f, 0x40},
> +	{0x3307, 0x04},
> +	{0x3500, 0x00},
> +	{0x3501, 0x4d},
> +	{0x3502, 0x40},
> +	{0x3503, 0x00},
> +	{0x3505, 0x80},
> +	{0x3508, 0x04},
> +	{0x3509, 0x00},
> +	{0x350c, 0x00},
> +	{0x350d, 0x80},
> +	{0x3510, 0x00},
> +	{0x3511, 0x02},
> +	{0x3512, 0x00},
> +	{0x3700, 0x18},
> +	{0x3701, 0x0c},
> +	{0x3702, 0x28},
> +	{0x3703, 0x19},
> +	{0x3704, 0x14},
> +	{0x3705, 0x00},
> +	{0x3706, 0x35},
> +	{0x3707, 0x04},
> +	{0x3708, 0x24},
> +	{0x3709, 0x33},
> +	{0x370a, 0x00},
> +	{0x370b, 0xb5},
> +	{0x370c, 0x04},
> +	{0x3718, 0x12},
> +	{0x3719, 0x31},
> +	{0x3712, 0x42},
> +	{0x3714, 0x24},
> +	{0x371e, 0x19},
> +	{0x371f, 0x40},
> +	{0x3720, 0x05},
> +	{0x3721, 0x05},
> +	{0x3724, 0x06},
> +	{0x3725, 0x01},
> +	{0x3726, 0x06},
> +	{0x3728, 0x05},
> +	{0x3729, 0x02},
> +	{0x372a, 0x03},
> +	{0x372b, 0x53},
> +	{0x372c, 0xa3},
> +	{0x372d, 0x53},
> +	{0x372e, 0x06},
> +	{0x372f, 0x10},
> +	{0x3730, 0x01},
> +	{0x3731, 0x06},
> +	{0x3732, 0x14},
> +	{0x3733, 0x10},
> +	{0x3734, 0x40},
> +	{0x3736, 0x20},
> +	{0x373a, 0x05},
> +	{0x373b, 0x06},
> +	{0x373c, 0x0a},
> +	{0x373e, 0x03},
> +	{0x3755, 0x10},
> +	{0x3758, 0x00},
> +	{0x3759, 0x4c},
> +	{0x375a, 0x06},
> +	{0x375b, 0x13},
> +	{0x375c, 0x20},
> +	{0x375d, 0x02},
> +	{0x375e, 0x00},
> +	{0x375f, 0x14},
> +	{0x3768, 0x22},
> +	{0x3769, 0x44},
> +	{0x376a, 0x44},
> +	{0x3761, 0x00},
> +	{0x3762, 0x00},
> +	{0x3763, 0x00},
> +	{0x3766, 0xff},
> +	{0x376b, 0x00},
> +	{0x3772, 0x23},
> +	{0x3773, 0x02},
> +	{0x3774, 0x16},
> +	{0x3775, 0x12},
> +	{0x3776, 0x04},
> +	{0x3777, 0x00},
> +	{0x3778, 0x1b},
> +	{0x37a0, 0x44},
> +	{0x37a1, 0x3d},
> +	{0x37a2, 0x3d},
> +	{0x37a3, 0x00},
> +	{0x37a4, 0x00},
> +	{0x37a5, 0x00},
> +	{0x37a6, 0x00},
> +	{0x37a7, 0x44},
> +	{0x37a8, 0x4c},
> +	{0x37a9, 0x4c},
> +	{0x3760, 0x00},
> +	{0x376f, 0x01},
> +	{0x37aa, 0x44},
> +	{0x37ab, 0x2e},
> +	{0x37ac, 0x2e},
> +	{0x37ad, 0x33},
> +	{0x37ae, 0x0d},
> +	{0x37af, 0x0d},
> +	{0x37b0, 0x00},
> +	{0x37b1, 0x00},
> +	{0x37b2, 0x00},
> +	{0x37b3, 0x42},
> +	{0x37b4, 0x42},
> +	{0x37b5, 0x33},
> +	{0x37b6, 0x00},
> +	{0x37b7, 0x00},
> +	{0x37b8, 0x00},
> +	{0x37b9, 0xff},
> +	{0x3800, 0x00},
> +	{0x3801, 0x0c},
> +	{0x3802, 0x00},
> +	{0x3803, 0x0c},
> +	{0x3804, 0x0c},
> +	{0x3805, 0xd3},
> +	{0x3806, 0x09},
> +	{0x3807, 0xa3},
> +	{0x3808, 0x06},
> +	{0x3809, 0x60},
> +	{0x380a, 0x04},
> +	{0x380b, 0xc8},
> +	{0x380c, 0x07},
> +	{0x380d, 0x88},
> +	{0x380e, 0x04},
> +	{0x380f, 0xdc},
> +	{0x3810, 0x00},
> +	{0x3811, 0x04},
> +	{0x3813, 0x02},
> +	{0x3814, 0x03},
> +	{0x3815, 0x01},
> +	{0x3820, 0x00},
> +	{0x3821, 0x67},
> +	{0x382a, 0x03},
> +	{0x382b, 0x01},
> +	{0x3830, 0x08},
> +	{0x3836, 0x02},
> +	{0x3837, 0x18},
> +	{0x3841, 0xff},
> +	{0x3846, 0x48},
> +	{0x3d85, 0x14},
> +	{0x3f08, 0x08},
> +	{0x3f0a, 0x80},
> +	{0x4000, 0xf1},
> +	{0x4001, 0x10},
> +	{0x4005, 0x10},
> +	{0x4002, 0x27},
> +	{0x4009, 0x81},
> +	{0x400b, 0x0c},
> +	{0x401b, 0x00},
> +	{0x401d, 0x00},
> +	{0x4020, 0x00},
> +	{0x4021, 0x04},
> +	{0x4022, 0x04},
> +	{0x4023, 0xb9},
> +	{0x4024, 0x05},
> +	{0x4025, 0x2a},
> +	{0x4026, 0x05},
> +	{0x4027, 0x2b},
> +	{0x4028, 0x00},
> +	{0x4029, 0x02},
> +	{0x402a, 0x04},
> +	{0x402b, 0x04},
> +	{0x402c, 0x02},
> +	{0x402d, 0x02},
> +	{0x402e, 0x08},
> +	{0x402f, 0x02},
> +	{0x401f, 0x00},
> +	{0x4034, 0x3f},
> +	{0x403d, 0x04},
> +	{0x4300, 0xff},
> +	{0x4301, 0x00},
> +	{0x4302, 0x0f},
> +	{0x4316, 0x00},
> +	{0x4500, 0x38},
> +	{0x4503, 0x18},
> +	{0x4600, 0x00},
> +	{0x4601, 0xcb},
> +	{0x481f, 0x32},
> +	{0x4837, 0x16},
> +	{0x4850, 0x10},
> +	{0x4851, 0x32},
> +	{0x4b00, 0x2a},
> +	{0x4b0d, 0x00},
> +	{0x4d00, 0x04},
> +	{0x4d01, 0x18},
> +	{0x4d02, 0xc3},
> +	{0x4d03, 0xff},
> +	{0x4d04, 0xff},
> +	{0x4d05, 0xff},
> +	{0x5000, 0x7e},
> +	{0x5001, 0x01},
> +	{0x5002, 0x08},
> +	{0x5003, 0x20},
> +	{0x5046, 0x12},
> +	{0x5901, 0x00},
> +	{0x5e00, 0x00},
> +	{0x5e01, 0x41},
> +	{0x382d, 0x7f},
> +	{0x4825, 0x3a},
> +	{0x4826, 0x40},
> +	{0x4808, 0x25},
> +	//{0x0100, 0x01},
> +	{REG_NULL, 0x00},
> +};
> +
> +static const struct regval ov8858_global_regs_r2a_2lane[] = {
> +	/*
> +	 * MIPI=720Mbps, SysClk=144Mhz,Dac Clock=360Mhz.
> +	 * v00_01_00 (05/29/2014) : initial setting
> +	 * AM19 : 3617 <- 0xC0
> +	 * AM20 : change FWC_6K_EN to be default 0x3618=0x5a
> +	 */
> +	{0x0103, 0x01},// software reset for OVTATool only
> +	{0x0103, 0x01},// software reset
> +	{0x0100, 0x00},// software standby
> +	{0x0302, 0x1e},// pll1_multi
> +	{0x0303, 0x00},// pll1_divm
> +	{0x0304, 0x03},// pll1_div_mipi
> +	{0x030e, 0x02},// pll2_rdiv
> +	{0x030f, 0x04},// pll2_divsp
> +	{0x0312, 0x03},// pll2_pre_div0, pll2_r_divdac
> +	{0x031e, 0x0c},// pll1_no_lat

I'll pretend to ignore the C++-style comments, but could you add a space
before // ?

> +	{0x3600, 0x00},
> +	{0x3601, 0x00},
> +	{0x3602, 0x00},
> +	{0x3603, 0x00},
> +	{0x3604, 0x22},
> +	{0x3605, 0x20},
> +	{0x3606, 0x00},
> +	{0x3607, 0x20},
> +	{0x3608, 0x11},
> +	{0x3609, 0x28},
> +	{0x360a, 0x00},
> +	{0x360b, 0x05},
> +	{0x360c, 0xd4},
> +	{0x360d, 0x40},
> +	{0x360e, 0x0c},
> +	{0x360f, 0x20},
> +	{0x3610, 0x07},
> +	{0x3611, 0x20},
> +	{0x3612, 0x88},
> +	{0x3613, 0x80},
> +	{0x3614, 0x58},
> +	{0x3615, 0x00},
> +	{0x3616, 0x4a},
> +	{0x3617, 0x90},
> +	{0x3618, 0x5a},
> +	{0x3619, 0x70},
> +	{0x361a, 0x99},
> +	{0x361b, 0x0a},
> +	{0x361c, 0x07},
> +	{0x361d, 0x00},
> +	{0x361e, 0x00},
> +	{0x361f, 0x00},
> +	{0x3638, 0xff},
> +	{0x3633, 0x0f},
> +	{0x3634, 0x0f},
> +	{0x3635, 0x0f},
> +	{0x3636, 0x12},
> +	{0x3645, 0x13},
> +	{0x3646, 0x83},
> +	{0x364a, 0x07},
> +	{0x3015, 0x00},
> +	{0x3018, 0x32}, // MIPI 2 lane
> +	{0x3020, 0x93}, // Clock switch output normal, pclk_div =/1
> +	{0x3022, 0x01}, // pd_mipi enable when rst_sync
> +	{0x3031, 0x0a}, // MIPI 10-bit mode
> +	{0x3034, 0x00}, //
> +	{0x3106, 0x01}, // sclk_div, sclk_pre_div
> +	{0x3305, 0xf1},
> +	{0x3308, 0x00},
> +	{0x3309, 0x28},
> +	{0x330a, 0x00},
> +	{0x330b, 0x20},
> +	{0x330c, 0x00},
> +	{0x330d, 0x00},
> +	{0x330e, 0x00},
> +	{0x330f, 0x40},
> +	{0x3307, 0x04},
> +	{0x3500, 0x00}, // exposure H
> +	{0x3501, 0x4d}, // exposure M
> +	{0x3502, 0x40}, // exposure L
> +	{0x3503, 0x80}, // gain delay ?, exposure delay 1 frame, real gain
> +	{0x3505, 0x80}, // gain option
> +	{0x3508, 0x02}, // gain H
> +	{0x3509, 0x00}, // gain L
> +	{0x350c, 0x00}, // short gain H
> +	{0x350d, 0x80}, // short gain L
> +	{0x3510, 0x00}, // short exposure H
> +	{0x3511, 0x02}, // short exposure M
> +	{0x3512, 0x00}, // short exposure L
> +	{0x3700, 0x18},
> +	{0x3701, 0x0c},
> +	{0x3702, 0x28},
> +	{0x3703, 0x19},
> +	{0x3704, 0x14},
> +	{0x3705, 0x00},
> +	{0x3706, 0x82},
> +	{0x3707, 0x04},
> +	{0x3708, 0x24},
> +	{0x3709, 0x33},
> +	{0x370a, 0x01},
> +	{0x370b, 0x82},
> +	{0x370c, 0x04},
> +	{0x3718, 0x12},
> +	{0x3719, 0x31},
> +	{0x3712, 0x42},
> +	{0x3714, 0x24},
> +	{0x371e, 0x19},
> +	{0x371f, 0x40},
> +	{0x3720, 0x05},
> +	{0x3721, 0x05},
> +	{0x3724, 0x06},
> +	{0x3725, 0x01},
> +	{0x3726, 0x06},
> +	{0x3728, 0x05},
> +	{0x3729, 0x02},
> +	{0x372a, 0x03},
> +	{0x372b, 0x53},
> +	{0x372c, 0xa3},
> +	{0x372d, 0x53},
> +	{0x372e, 0x06},
> +	{0x372f, 0x10},
> +	{0x3730, 0x01},
> +	{0x3731, 0x06},
> +	{0x3732, 0x14},
> +	{0x3733, 0x10},
> +	{0x3734, 0x40},
> +	{0x3736, 0x20},
> +	{0x373a, 0x05},
> +	{0x373b, 0x06},
> +	{0x373c, 0x0a},
> +	{0x373e, 0x03},
> +	{0x3750, 0x0a},
> +	{0x3751, 0x0e},
> +	{0x3755, 0x10},
> +	{0x3758, 0x00},
> +	{0x3759, 0x4c},
> +	{0x375a, 0x06},
> +	{0x375b, 0x13},
> +	{0x375c, 0x20},
> +	{0x375d, 0x02},
> +	{0x375e, 0x00},
> +	{0x375f, 0x14},
> +	{0x3768, 0x22},
> +	{0x3769, 0x44},
> +	{0x376a, 0x44},
> +	{0x3761, 0x00},
> +	{0x3762, 0x00},
> +	{0x3763, 0x00},
> +	{0x3766, 0xff},
> +	{0x376b, 0x00},
> +	{0x3772, 0x23},
> +	{0x3773, 0x02},
> +	{0x3774, 0x16},
> +	{0x3775, 0x12},
> +	{0x3776, 0x04},
> +	{0x3777, 0x00},
> +	{0x3778, 0x17},
> +	{0x37a0, 0x44},
> +	{0x37a1, 0x3d},
> +	{0x37a2, 0x3d},
> +	{0x37a3, 0x00},
> +	{0x37a4, 0x00},
> +	{0x37a5, 0x00},
> +	{0x37a6, 0x00},
> +	{0x37a7, 0x44},
> +	{0x37a8, 0x4c},
> +	{0x37a9, 0x4c},
> +	{0x3760, 0x00},
> +	{0x376f, 0x01},
> +	{0x37aa, 0x44},
> +	{0x37ab, 0x2e},
> +	{0x37ac, 0x2e},
> +	{0x37ad, 0x33},
> +	{0x37ae, 0x0d},
> +	{0x37af, 0x0d},
> +	{0x37b0, 0x00},
> +	{0x37b1, 0x00},
> +	{0x37b2, 0x00},
> +	{0x37b3, 0x42},
> +	{0x37b4, 0x42},
> +	{0x37b5, 0x31},
> +	{0x37b6, 0x00},
> +	{0x37b7, 0x00},
> +	{0x37b8, 0x00},
> +	{0x37b9, 0xff},
> +	{0x3800, 0x00}, // x start H
> +	{0x3801, 0x0c}, // x start L
> +	{0x3802, 0x00}, // y start H
> +	{0x3803, 0x0c}, // y start L

We have documentation for lots of those registers, it would be nice to
give them names, but that's not a hard requirement either.

> +	{0x3804, 0x0c}, // x end H
> +	{0x3805, 0xd3}, // x end L
> +	{0x3806, 0x09}, // y end H
> +	{0x3807, 0xa3}, // y end L
> +	{0x3808, 0x06}, // x output size H
> +	{0x3809, 0x60}, // x output size L
> +	{0x380a, 0x04}, // y output size H
> +	{0x380b, 0xc8}, // y output size L
> +	{0x380c, 0x07}, // HTS H
> +	{0x380d, 0x88}, // HTS L
> +	{0x380e, 0x04}, // VTS H
> +	{0x380f, 0xdc}, // VTS L
> +	{0x3810, 0x00}, // ISP x win H
> +	{0x3811, 0x04}, // ISP x win L
> +	{0x3813, 0x02}, // ISP y win L
> +	{0x3814, 0x03}, // x odd inc
> +	{0x3815, 0x01}, // x even inc
> +	{0x3820, 0x00}, // vflip off
> +	{0x3821, 0x67}, // mirror on, bin on
> +	{0x382a, 0x03}, // y odd inc
> +	{0x382b, 0x01}, // y even inc
> +	{0x3830, 0x08}, //
> +	{0x3836, 0x02}, //
> +	{0x3837, 0x18}, //
> +	{0x3841, 0xff}, // window auto size enable
> +	{0x3846, 0x48}, //
> +	{0x3d85, 0x16}, // OTP power up load data enable with BIST
> +	{0x3d8c, 0x73}, // OTP setting start High
> +	{0x3d8d, 0xde}, // OTP setting start Low
> +	{0x3f08, 0x08}, //
> +	{0x3f0a, 0x00}, //
> +	{0x4000, 0xf1}, // out_range_trig, format_chg_trig
> +	{0x4001, 0x10}, // total 128 black column
> +	{0x4005, 0x10}, // BLC target L
> +	{0x4002, 0x27}, // value used to limit BLC offset
> +	{0x4009, 0x81}, // final BLC offset limitation enable
> +	{0x400b, 0x0c}, // DCBLC on, DCBLC manual mode on
> +	{0x401b, 0x00}, // zero line R coefficient
> +	{0x401d, 0x00}, // zoro line T coefficient
> +	{0x4020, 0x00}, // Anchor left start H
> +	{0x4021, 0x04}, // Anchor left start L
> +	{0x4022, 0x06}, // Anchor left end H
> +	{0x4023, 0x00}, // Anchor left end L
> +	{0x4024, 0x0f}, // Anchor right start H
> +	{0x4025, 0x2a}, // Anchor right start L
> +	{0x4026, 0x0f}, // Anchor right end H
> +	{0x4027, 0x2b}, // Anchor right end L
> +	{0x4028, 0x00}, // top zero line start
> +	{0x4029, 0x02}, // top zero line number
> +	{0x402a, 0x04}, // top black line start
> +	{0x402b, 0x04}, // top black line number
> +	{0x402c, 0x00}, // bottom zero line start
> +	{0x402d, 0x02}, // bottom zoro line number
> +	{0x402e, 0x04}, // bottom black line start
> +	{0x402f, 0x04}, // bottom black line number
> +	{0x401f, 0x00}, // interpolation x/y disable, Anchor one disable
> +	{0x4034, 0x3f}, //
> +	{0x403d, 0x04}, // md_precision_en
> +	{0x4300, 0xff}, // clip max H
> +	{0x4301, 0x00}, // clip min H
> +	{0x4302, 0x0f}, // clip min L, clip max L
> +	{0x4316, 0x00}, //
> +	{0x4500, 0x58}, //
> +	{0x4503, 0x18}, //
> +	{0x4600, 0x00}, //
> +	{0x4601, 0xcb}, //
> +	{0x481f, 0x32}, // clk prepare min
> +	{0x4837, 0x16}, // global timing
> +	{0x4850, 0x10}, // lane 1 = 1, lane 0 = 0
> +	{0x4851, 0x32}, // lane 3 = 3, lane 2 = 2
> +	{0x4b00, 0x2a}, //
> +	{0x4b0d, 0x00}, //
> +	{0x4d00, 0x04}, // temperature sensor
> +	{0x4d01, 0x18}, //
> +	{0x4d02, 0xc3}, //
> +	{0x4d03, 0xff}, //
> +	{0x4d04, 0xff}, //
> +	{0x4d05, 0xff}, // temperature sensor
> +	{0x5000, 0xfe}, // lenc on, slave/master AWB gain/statistics enable
> +	{0x5001, 0x01}, // BLC on
> +	{0x5002, 0x08}, // H scale off, WBMATCH off, OTP_DPC
> +	{0x5003, 0x20}, // DPC_DBC buffer control enable, WB
> +	{0x501e, 0x93}, // enable digital gain
> +	{0x5046, 0x12}, //
> +	{0x5780, 0x3e}, // DPC
> +	{0x5781, 0x0f}, //
> +	{0x5782, 0x44}, //
> +	{0x5783, 0x02}, //
> +	{0x5784, 0x01}, //
> +	{0x5785, 0x00}, //
> +	{0x5786, 0x00}, //
> +	{0x5787, 0x04}, //
> +	{0x5788, 0x02}, //
> +	{0x5789, 0x0f}, //
> +	{0x578a, 0xfd}, //
> +	{0x578b, 0xf5}, //
> +	{0x578c, 0xf5}, //
> +	{0x578d, 0x03}, //
> +	{0x578e, 0x08}, //
> +	{0x578f, 0x0c}, //
> +	{0x5790, 0x08}, //
> +	{0x5791, 0x04}, //
> +	{0x5792, 0x00}, //
> +	{0x5793, 0x52}, //
> +	{0x5794, 0xa3}, // DPC
> +	{0x5871, 0x0d}, // Lenc
> +	{0x5870, 0x18}, //
> +	{0x586e, 0x10}, //
> +	{0x586f, 0x08}, //
> +	{0x58f7, 0x01}, //
> +	{0x58f8, 0x3d}, // Lenc
> +	{0x5901, 0x00}, // H skip off, V skip off
> +	{0x5b00, 0x02}, // OTP DPC start address
> +	{0x5b01, 0x10}, // OTP DPC start address
> +	{0x5b02, 0x03}, // OTP DPC end address
> +	{0x5b03, 0xcf}, // OTP DPC end address
> +	{0x5b05, 0x6c}, // recover method = 2b11,
> +	{0x5e00, 0x00}, // use 0x3ff to test pattern off
> +	{0x5e01, 0x41}, // window cut enable
> +	{0x382d, 0x7f}, //
> +	{0x4825, 0x3a}, // lpx_p_min
> +	{0x4826, 0x40}, // hs_prepare_min
> +	{0x4808, 0x25}, // wake up delay in 1/1024 s
> +	{0x3763, 0x18}, //
> +	{0x3768, 0xcc}, //
> +	{0x470b, 0x28}, //
> +	{0x4202, 0x00}, //
> +	{0x400d, 0x10}, // BLC offset trigger L
> +	{0x4040, 0x04}, // BLC gain th2
> +	{0x403e, 0x04}, // BLC gain th1
> +	{0x4041, 0xc6}, // BLC
> +	{0x3007, 0x80},
> +	{0x400a, 0x01},
> +	{REG_NULL, 0x00},
> +};
> +
> +/*
> + * Xclk 24Mhz
> + * max_framerate 30fps
> + * mipi_datarate per lane 720Mbps
> + */
> +static const struct regval ov8858_1632x1224_regs_2lane[] = {
> +	/*
> +	 * MIPI=720Mbps, SysClk=144Mhz,Dac Clock=360Mhz.
> +	 * v00_01_00 (05/29/2014) : initial setting
> +	 * AM19 : 3617 <- 0xC0
> +	 * AM20 : change FWC_6K_EN to be default 0x3618=0x5a
> +	 */
> +	{0x0100, 0x00},
> +	{0x3501, 0x4d}, // exposure M
> +	{0x3502, 0x40}, // exposure L
> +	{0x3778, 0x17}, //
> +	{0x3808, 0x06}, // x output size H
> +	{0x3809, 0x60}, // x output size L
> +	{0x380a, 0x04}, // y output size H
> +	{0x380b, 0xc8}, // y output size L
> +	{0x380c, 0x07}, // HTS H
> +	{0x380d, 0x88}, // HTS L
> +	{0x380e, 0x04}, // VTS H
> +	{0x380f, 0xdc}, // VTS L
> +	{0x3814, 0x03}, // x odd inc
> +	{0x3821, 0x67}, // mirror on, bin on
> +	{0x382a, 0x03}, // y odd inc
> +	{0x3830, 0x08},
> +	{0x3836, 0x02},
> +	{0x3f0a, 0x00},
> +	{0x4001, 0x10}, // total 128 black column
> +	{0x4022, 0x06}, // Anchor left end H
> +	{0x4023, 0x00}, // Anchor left end L
> +	{0x4025, 0x2a}, // Anchor right start L
> +	{0x4027, 0x2b}, // Anchor right end L
> +	{0x402b, 0x04}, // top black line number
> +	{0x402f, 0x04}, // bottom black line number
> +	{0x4500, 0x58},
> +	{0x4600, 0x00},
> +	{0x4601, 0xcb},
> +	{0x382d, 0x7f},
> +	{0x0100, 0x01},
> +	{REG_NULL, 0x00},
> +};
> +
> +/*
> + * Xclk 24Mhz
> + * max_framerate 15fps
> + * mipi_datarate per lane 720Mbps
> + */
> +static const struct regval ov8858_3264x2448_regs_2lane[] = {
> +	{0x0100, 0x00},
> +	{0x3501, 0x9a},// exposure M
> +	{0x3502, 0x20},// exposure L
> +	{0x3778, 0x1a},//
> +	{0x3808, 0x0c},// x output size H
> +	{0x3809, 0xc0},// x output size L
> +	{0x380a, 0x09},// y output size H
> +	{0x380b, 0x90},// y output size L
> +	{0x380c, 0x07},// HTS H
> +	{0x380d, 0x94},// HTS L
> +	{0x380e, 0x09},// VTS H
> +	{0x380f, 0xaa},// VTS L
> +	{0x3814, 0x01},// x odd inc
> +	{0x3821, 0x46},// mirror on, bin off
> +	{0x382a, 0x01},// y odd inc
> +	{0x3830, 0x06},
> +	{0x3836, 0x01},
> +	{0x3f0a, 0x00},
> +	{0x4001, 0x00},// total 256 black column
> +	{0x4022, 0x0c},// Anchor left end H
> +	{0x4023, 0x60},// Anchor left end L
> +	{0x4025, 0x36},// Anchor right start L
> +	{0x4027, 0x37},// Anchor right end L
> +	{0x402b, 0x08},// top black line number
> +	{0x402f, 0x08},// bottom black line number
> +	{0x4500, 0x58},
> +	{0x4600, 0x01},
> +	{0x4601, 0x97},
> +	{0x382d, 0xff},
> +	{REG_NULL, 0x00},
> +};
> +
> +static const struct regval ov8858_global_regs_r2a_4lane[] = {
> +	/*
> +	 * MIPI=720Mbps, SysClk=144Mhz,Dac Clock=360Mhz.
> +	 * v00_01_00 (05/29/2014) : initial setting
> +	 * AM19 : 3617 <- 0xC0
> +	 * AM20 : change FWC_6K_EN to be default 0x3618=0x5a
> +	 */
> +	{0x0103, 0x01}, // software reset for OVTATool only
> +	{0x0103, 0x01}, // software reset
> +	{0x0100, 0x00}, // software standby
> +	{0x0302, 0x1e}, // pll1_multi
> +	{0x0303, 0x00}, // pll1_divm
> +	{0x0304, 0x03}, // pll1_div_mipi
> +	{0x030e, 0x00}, // pll2_rdiv
> +	{0x030f, 0x04}, // pll2_divsp
> +	{0x0312, 0x01}, // pll2_pre_div0, pll2_r_divdac
> +	{0x031e, 0x0c}, // pll1_no_lat
> +	{0x3600, 0x00},
> +	{0x3601, 0x00},
> +	{0x3602, 0x00},
> +	{0x3603, 0x00},
> +	{0x3604, 0x22},
> +	{0x3605, 0x20},
> +	{0x3606, 0x00},
> +	{0x3607, 0x20},
> +	{0x3608, 0x11},
> +	{0x3609, 0x28},
> +	{0x360a, 0x00},
> +	{0x360b, 0x05},
> +	{0x360c, 0xd4},
> +	{0x360d, 0x40},
> +	{0x360e, 0x0c},
> +	{0x360f, 0x20},
> +	{0x3610, 0x07},
> +	{0x3611, 0x20},
> +	{0x3612, 0x88},
> +	{0x3613, 0x80},
> +	{0x3614, 0x58},
> +	{0x3615, 0x00},
> +	{0x3616, 0x4a},
> +	{0x3617, 0x90},
> +	{0x3618, 0x5a},
> +	{0x3619, 0x70},
> +	{0x361a, 0x99},
> +	{0x361b, 0x0a},
> +	{0x361c, 0x07},
> +	{0x361d, 0x00},
> +	{0x361e, 0x00},
> +	{0x361f, 0x00},
> +	{0x3638, 0xff},
> +	{0x3633, 0x0f},
> +	{0x3634, 0x0f},
> +	{0x3635, 0x0f},
> +	{0x3636, 0x12},
> +	{0x3645, 0x13},
> +	{0x3646, 0x83},
> +	{0x364a, 0x07},
> +	{0x3015, 0x01}, //
> +	{0x3018, 0x72}, // MIPI 4 lane
> +	{0x3020, 0x93}, // Clock switch output normal, pclk_div =/1
> +	{0x3022, 0x01}, // pd_mipi enable when rst_sync
> +	{0x3031, 0x0a}, // MIPI 10-bit mode
> +	{0x3034, 0x00}, //
> +	{0x3106, 0x01}, // sclk_div, sclk_pre_div
> +	{0x3305, 0xf1},
> +	{0x3308, 0x00},
> +	{0x3309, 0x28},
> +	{0x330a, 0x00},
> +	{0x330b, 0x20},
> +	{0x330c, 0x00},
> +	{0x330d, 0x00},
> +	{0x330e, 0x00},
> +	{0x330f, 0x40},
> +	{0x3307, 0x04},
> +	{0x3500, 0x00}, // exposure H
> +	{0x3501, 0x4d}, // exposure M
> +	{0x3502, 0x40}, // exposure L
> +	{0x3503, 0x80}, // gain delay ?, exposure delay 1 frame, real gain
> +	{0x3505, 0x80}, // gain option
> +	{0x3508, 0x02}, // gain H
> +	{0x3509, 0x00}, // gain L
> +	{0x350c, 0x00}, // short gain H
> +	{0x350d, 0x80}, // short gain L
> +	{0x3510, 0x00}, // short exposure H
> +	{0x3511, 0x02}, // short exposure M
> +	{0x3512, 0x00}, // short exposure L
> +	{0x3700, 0x30},
> +	{0x3701, 0x18},
> +	{0x3702, 0x50},
> +	{0x3703, 0x32},
> +	{0x3704, 0x28},
> +	{0x3705, 0x00},
> +	{0x3706, 0x82},
> +	{0x3707, 0x08},
> +	{0x3708, 0x48},
> +	{0x3709, 0x66},
> +	{0x370a, 0x01},
> +	{0x370b, 0x82},
> +	{0x370c, 0x07},
> +	{0x3718, 0x14},
> +	{0x3719, 0x31},
> +	{0x3712, 0x44},
> +	{0x3714, 0x24},
> +	{0x371e, 0x31},
> +	{0x371f, 0x7f},
> +	{0x3720, 0x0a},
> +	{0x3721, 0x0a},
> +	{0x3724, 0x0c},
> +	{0x3725, 0x02},
> +	{0x3726, 0x0c},
> +	{0x3728, 0x0a},
> +	{0x3729, 0x03},
> +	{0x372a, 0x06},
> +	{0x372b, 0xa6},
> +	{0x372c, 0xa6},
> +	{0x372d, 0xa6},
> +	{0x372e, 0x0c},
> +	{0x372f, 0x20},
> +	{0x3730, 0x02},
> +	{0x3731, 0x0c},
> +	{0x3732, 0x28},
> +	{0x3733, 0x10},
> +	{0x3734, 0x40},
> +	{0x3736, 0x30},
> +	{0x373a, 0x0a},
> +	{0x373b, 0x0b},
> +	{0x373c, 0x14},
> +	{0x373e, 0x06},
> +	{0x3750, 0x0a},
> +	{0x3751, 0x0e},
> +	{0x3755, 0x10},
> +	{0x3758, 0x00},
> +	{0x3759, 0x4c},
> +	{0x375a, 0x0c},
> +	{0x375b, 0x26},
> +	{0x375c, 0x20},
> +	{0x375d, 0x04},
> +	{0x375e, 0x00},
> +	{0x375f, 0x28},
> +	{0x3768, 0x22},
> +	{0x3769, 0x44},
> +	{0x376a, 0x44},
> +	{0x3761, 0x00},
> +	{0x3762, 0x00},
> +	{0x3763, 0x00},
> +	{0x3766, 0xff},
> +	{0x376b, 0x00},
> +	{0x3772, 0x46},
> +	{0x3773, 0x04},
> +	{0x3774, 0x2c},
> +	{0x3775, 0x13},
> +	{0x3776, 0x08},
> +	{0x3777, 0x00},
> +	{0x3778, 0x17},
> +	{0x37a0, 0x88},
> +	{0x37a1, 0x7a},
> +	{0x37a2, 0x7a},
> +	{0x37a3, 0x00},
> +	{0x37a4, 0x00},
> +	{0x37a5, 0x00},
> +	{0x37a6, 0x00},
> +	{0x37a7, 0x88},
> +	{0x37a8, 0x98},
> +	{0x37a9, 0x98},
> +	{0x3760, 0x00},
> +	{0x376f, 0x01},
> +	{0x37aa, 0x88},
> +	{0x37ab, 0x5c},
> +	{0x37ac, 0x5c},
> +	{0x37ad, 0x55},
> +	{0x37ae, 0x19},
> +	{0x37af, 0x19},
> +	{0x37b0, 0x00},
> +	{0x37b1, 0x00},
> +	{0x37b2, 0x00},
> +	{0x37b3, 0x84},
> +	{0x37b4, 0x84},
> +	{0x37b5, 0x60},
> +	{0x37b6, 0x00},
> +	{0x37b7, 0x00},
> +	{0x37b8, 0x00},
> +	{0x37b9, 0xff},
> +	{0x3800, 0x00}, // x start H
> +	{0x3801, 0x0c}, // x start L
> +	{0x3802, 0x00}, // y start H
> +	{0x3803, 0x0c}, // y start L
> +	{0x3804, 0x0c}, // x end H
> +	{0x3805, 0xd3}, // x end L
> +	{0x3806, 0x09}, // y end H
> +	{0x3807, 0xa3}, // y end L
> +	{0x3808, 0x06}, // x output size H
> +	{0x3809, 0x60}, // x output size L
> +	{0x380a, 0x04}, // y output size H
> +	{0x380b, 0xc8}, // y output size L
> +	{0x380c, 0x07}, // HTS H
> +	{0x380d, 0x88}, // HTS L
> +	{0x380e, 0x04}, // VTS H
> +	{0x380f, 0xdc}, // VTS L
> +	{0x3810, 0x00}, // ISP x win H
> +	{0x3811, 0x04}, // ISP x win L
> +	{0x3813, 0x02}, // ISP y win L
> +	{0x3814, 0x03}, // x odd inc
> +	{0x3815, 0x01}, // x even inc
> +	{0x3820, 0x00}, // vflip off
> +	{0x3821, 0x67}, // mirror on, bin o
> +	{0x382a, 0x03}, // y odd inc
> +	{0x382b, 0x01}, // y even inc
> +	{0x3830, 0x08},
> +	{0x3836, 0x02},
> +	{0x3837, 0x18},
> +	{0x3841, 0xff}, // window auto size enable
> +	{0x3846, 0x48}, //
> +	{0x3d85, 0x16}, // OTP power up load data/setting enable
> +	{0x3d8c, 0x73}, // OTP setting start High
> +	{0x3d8d, 0xde}, // OTP setting start Low
> +	{0x3f08, 0x10}, //
> +	{0x3f0a, 0x00}, //
> +	{0x4000, 0xf1}, // out_range/format_chg/gain/exp_chg trig enable
> +	{0x4001, 0x10}, // total 128 black column
> +	{0x4005, 0x10}, // BLC target L
> +	{0x4002, 0x27}, // value used to limit BLC offset
> +	{0x4009, 0x81}, // final BLC offset limitation enable
> +	{0x400b, 0x0c}, // DCBLC on, DCBLC manual mode on
> +	{0x401b, 0x00}, // zero line R coefficient
> +	{0x401d, 0x00}, // zoro line T coefficient
> +	{0x4020, 0x00}, // Anchor left start H
> +	{0x4021, 0x04}, // Anchor left start L
> +	{0x4022, 0x06}, // Anchor left end H
> +	{0x4023, 0x00}, // Anchor left end L
> +	{0x4024, 0x0f}, // Anchor right start H
> +	{0x4025, 0x2a}, // Anchor right start L
> +	{0x4026, 0x0f}, // Anchor right end H
> +	{0x4027, 0x2b}, // Anchor right end L
> +	{0x4028, 0x00}, // top zero line start
> +	{0x4029, 0x02}, // top zero line number
> +	{0x402a, 0x04}, // top black line start
> +	{0x402b, 0x04}, // top black line number
> +	{0x402c, 0x00}, // bottom zero line start
> +	{0x402d, 0x02}, // bottom zoro line number
> +	{0x402e, 0x04}, // bottom black line start
> +	{0x402f, 0x04}, // bottom black line number
> +	{0x401f, 0x00}, // interpolation x/y disable, Anchor one disable
> +	{0x4034, 0x3f},
> +	{0x403d, 0x04}, // md_precision_en
> +	{0x4300, 0xff}, // clip max H
> +	{0x4301, 0x00}, // clip min H
> +	{0x4302, 0x0f}, // clip min L, clip max L
> +	{0x4316, 0x00},
> +	{0x4500, 0x58},
> +	{0x4503, 0x18},
> +	{0x4600, 0x00},
> +	{0x4601, 0xcb},
> +	{0x481f, 0x32}, // clk prepare min
> +	{0x4837, 0x16}, // global timing
> +	{0x4850, 0x10}, // lane 1 = 1, lane 0 = 0
> +	{0x4851, 0x32}, // lane 3 = 3, lane 2 = 2
> +	{0x4b00, 0x2a},
> +	{0x4b0d, 0x00},
> +	{0x4d00, 0x04}, // temperature sensor
> +	{0x4d01, 0x18}, //
> +	{0x4d02, 0xc3}, //
> +	{0x4d03, 0xff}, //
> +	{0x4d04, 0xff}, //
> +	{0x4d05, 0xff}, // temperature sensor
> +	{0x5000, 0xfe}, // lenc on, slave/master AWB gain/statistics enable
> +	{0x5001, 0x01}, // BLC on
> +	{0x5002, 0x08}, // WBMATCH sensor's gain, H scale/WBMATCH/OTP_DPC off
> +	{0x5003, 0x20}, // DPC_DBC buffer control enable, WB
> +	{0x501e, 0x93}, // enable digital gain
> +	{0x5046, 0x12}, //
> +	{0x5780, 0x3e}, // DPC
> +	{0x5781, 0x0f}, //
> +	{0x5782, 0x44}, //
> +	{0x5783, 0x02}, //
> +	{0x5784, 0x01}, //
> +	{0x5785, 0x00}, //
> +	{0x5786, 0x00}, //
> +	{0x5787, 0x04}, //
> +	{0x5788, 0x02}, //
> +	{0x5789, 0x0f}, //
> +	{0x578a, 0xfd}, //
> +	{0x578b, 0xf5}, //
> +	{0x578c, 0xf5}, //
> +	{0x578d, 0x03}, //
> +	{0x578e, 0x08}, //
> +	{0x578f, 0x0c}, //
> +	{0x5790, 0x08}, //
> +	{0x5791, 0x04}, //
> +	{0x5792, 0x00}, //
> +	{0x5793, 0x52}, //
> +	{0x5794, 0xa3}, // DPC
> +	{0x5871, 0x0d}, // Lenc
> +	{0x5870, 0x18}, //
> +	{0x586e, 0x10}, //
> +	{0x586f, 0x08}, //
> +	{0x58f7, 0x01}, //
> +	{0x58f8, 0x3d}, // Lenc
> +	{0x5901, 0x00}, // H skip off, V skip off
> +	{0x5b00, 0x02}, // OTP DPC start address
> +	{0x5b01, 0x10}, // OTP DPC start address
> +	{0x5b02, 0x03}, // OTP DPC end address
> +	{0x5b03, 0xcf}, // OTP DPC end address
> +	{0x5b05, 0x6c}, // recover method = 2b11
> +	{0x5e00, 0x00}, // use 0x3ff to test pattern off
> +	{0x5e01, 0x41}, // window cut enable
> +	{0x382d, 0x7f}, //
> +	{0x4825, 0x3a}, // lpx_p_min
> +	{0x4826, 0x40}, // hs_prepare_min
> +	{0x4808, 0x25}, // wake up delay in 1/1024 s
> +	{0x3763, 0x18},
> +	{0x3768, 0xcc},
> +	{0x470b, 0x28},
> +	{0x4202, 0x00},
> +	{0x400d, 0x10}, // BLC offset trigger L
> +	{0x4040, 0x04}, // BLC gain th2
> +	{0x403e, 0x04}, // BLC gain th1
> +	{0x4041, 0xc6}, // BLC
> +	{0x3007, 0x80},
> +	{0x400a, 0x01},
> +	{REG_NULL, 0x00},
> +};
> +
> +/*
> + * Xclk 24Mhz
> + * max_framerate 60fps
> + * mipi_datarate per lane 720Mbps
> + */
> +static const struct regval ov8858_1632x1224_regs_4lane[] = {
> +	{0x0100, 0x00},
> +	{0x3501, 0x4d}, // exposure M
> +	{0x3502, 0x40}, // exposure L
> +	{0x3808, 0x06}, // x output size H
> +	{0x3809, 0x60}, // x output size L
> +	{0x380a, 0x04}, // y output size H
> +	{0x380b, 0xc8}, // y output size L
> +	{0x380c, 0x07}, // HTS H
> +	{0x380d, 0x88}, // HTS L
> +	{0x380e, 0x04}, // VTS H
> +	{0x380f, 0xdc}, // VTS L
> +	{0x3814, 0x03}, // x odd inc
> +	{0x3821, 0x67}, // mirror on, bin on
> +	{0x382a, 0x03}, // y odd inc
> +	{0x3830, 0x08},
> +	{0x3836, 0x02},
> +	{0x3f0a, 0x00},
> +	{0x4001, 0x10}, // total 128 black column
> +	{0x4022, 0x06}, // Anchor left end H
> +	{0x4023, 0x00}, // Anchor left end L
> +	{0x4025, 0x2a}, // Anchor right start L
> +	{0x4027, 0x2b}, // Anchor right end L
> +	{0x402b, 0x04}, // top black line number
> +	{0x402f, 0x04}, // bottom black line number
> +	{0x4500, 0x58},
> +	{0x4600, 0x00},
> +	{0x4601, 0xcb},
> +	{0x382d, 0x7f},
> +	{0x0100, 0x01},
> +	{REG_NULL, 0x00},
> +};
> +
> +/*
> + * Xclk 24Mhz
> + * max_framerate 30fps
> + * mipi_datarate per lane 720Mbps
> + */
> +static const struct regval ov8858_3264x2448_regs_4lane[] = {
> +	{0x0100, 0x00},
> +	{0x3501, 0x9a}, // exposure M
> +	{0x3502, 0x20}, // exposure L
> +	{0x3808, 0x0c}, // x output size H
> +	{0x3809, 0xc0}, // x output size L
> +	{0x380a, 0x09}, // y output size H
> +	{0x380b, 0x90}, // y output size L
> +	{0x380c, 0x07}, // HTS H
> +	{0x380d, 0x94}, // HTS L
> +	{0x380e, 0x09}, // VTS H
> +	{0x380f, 0xaa}, // VTS L
> +	{0x3814, 0x01}, // x odd inc
> +	{0x3821, 0x46}, // mirror on, bin off
> +	{0x382a, 0x01}, // y odd inc
> +	{0x3830, 0x06},
> +	{0x3836, 0x01},
> +	{0x3f0a, 0x00},
> +	{0x4001, 0x00}, // total 256 black column
> +	{0x4022, 0x0c}, // Anchor left end H
> +	{0x4023, 0x60}, // Anchor left end L
> +	{0x4025, 0x36}, // Anchor right start L
> +	{0x4027, 0x37}, // Anchor right end L
> +	{0x402b, 0x08}, // top black line number
> +	{0x402f, 0x08}, // interpolation x/y disable, Anchor one disable
> +	{0x4500, 0x58},
> +	{0x4600, 0x01},
> +	{0x4601, 0x97},
> +	{0x382d, 0xff},
> +	{REG_NULL, 0x00},
> +};
> +
> +static const struct ov8858_mode ov8858_modes[] = {
> +	{
> +		.width = 3264,
> +		.height = 2448,
> +		.exp_def = 2464,
> +		.hts_def = 1940 * 2,
> +		.vts_def = 2472,
> +		.reg_modes = {
> +			.mode_2lanes = ov8858_3264x2448_regs_2lane,
> +			.mode_4lanes = ov8858_3264x2448_regs_4lane,
> +		},
> +	},
> +	{
> +		.width = 1632,
> +		.height = 1224,
> +		.exp_def = 1232,
> +		.hts_def = 1928 * 2,

There was no * 2 in v1, is this correct ?

> +		.vts_def = 1244,
> +		.reg_modes = {
> +			.mode_2lanes = ov8858_1632x1224_regs_2lane,
> +			.mode_4lanes = ov8858_1632x1224_regs_4lane,
> +		},
> +	},
> +};

Blank line.

> +#define OV8858_NUM_MODES ARRAY_SIZE(ov8858_modes)
> +
> +static const s64 link_freq_menu_items[] = {
> +	OV8858_LINK_FREQ
> +};
> +
> +static const char * const ov8858_test_pattern_menu[] = {
> +	"Disabled",
> +	"Vertical Color Bar Type 1",
> +	"Vertical Color Bar Type 2",
> +	"Vertical Color Bar Type 3",
> +	"Vertical Color Bar Type 4"
> +};
> +
> +static int ov8858_write_reg(struct i2c_client *client, u32 reg, u32 val,
> +			    int *err)

I'd name this function ov8858_write() (same for the read) to make line
shorter, I think it's equally readable.

> +{
> +	unsigned int len = (reg >> OV8858_REG_SIZE_SHIFT) & 3;
> +	u16 addr = reg & OV8858_REG_ADDR_MASK;
> +	u8 buf[6];
> +	int ret;
> +
> +	if (err && *err)
> +		return *err;
> +
> +	put_unaligned_be16(addr, buf);
> +	put_unaligned_be32(val << (8 * (4 - len)), buf + 2);
> +
> +	ret = i2c_master_send(client, buf, len + 2);
> +	if (ret != len + 2) {
> +		if (err)
> +			*err = ret < 0 ? ret : -EIO;
> +		return ret < 0 ? ret : -EIO;

Maybe

		ret = ret < 0 ? ret : -EIO;
		if (err)
			*err = ret;
		return ret;

to avoid repeating the same code.

I would also print an error message here, it's good to be informed of
errors. Same for reads.

> +	}
> +
> +	return 0;
> +}
> +
> +static int ov8858_write_array(struct i2c_client *client, const struct regval *regs)
> +{
> +	unsigned int i;
> +	int ret = 0;
> +
> +	for (i = 0; ret == 0 && regs[i].addr != REG_NULL; ++i)

Curly braces.

> +		ov8858_write_reg(client, OV8858_REG_8BIT(regs[i].addr),
> +				 regs[i].val, &ret);
> +
> +	return ret;
> +}
> +
> +static int ov8858_read_reg(struct i2c_client *client, u32 reg, u32 *val)
> +{
> +	__be16 reg_addr_be = cpu_to_be16(reg & OV8858_REG_ADDR_MASK);
> +	unsigned int len = (reg >> OV8858_REG_SIZE_SHIFT) & 3;
> +	struct i2c_msg msgs[2];
> +	__be32 data_be = 0;
> +	u8 *data_be_p;

	u8 *data_be_p = (u8 *)&data_be;

> +	int ret;
> +
> +	data_be_p = (u8 *)&data_be;

and drop this line.

> +	/* Write register address */
> +	msgs[0].addr = client->addr;
> +	msgs[0].flags = 0;
> +	msgs[0].len = 2;
> +	msgs[0].buf = (u8 *)&reg_addr_be;
> +
> +	/* Read data from register */
> +	msgs[1].addr = client->addr;
> +	msgs[1].flags = I2C_M_RD;
> +	msgs[1].len = len;
> +	msgs[1].buf = &data_be_p[4 - len];
> +
> +	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
> +	if (ret != ARRAY_SIZE(msgs))
> +		return -EIO;

		return ret < 0 ? ret : -EIO;

to preserve the error.

> +
> +	*val = be32_to_cpu(data_be);
> +
> +	return 0;
> +}
> +
> +static int ov8858_set_fmt(struct v4l2_subdev *sd,
> +			  struct v4l2_subdev_state *state,
> +			  struct v4l2_subdev_format *fmt)

Could you move this function down after ov8858_enum_frame_sizes() to
have the same ordering as in ov8858_pad_ops ?

> +{
> +	struct ov8858 *ov8858 = to_ov8858(sd);
> +	struct v4l2_mbus_framefmt *format;
> +	const struct ov8858_mode *mode;
> +	s64 h_blank, vblank_def;
> +
> +	mode = v4l2_find_nearest_size(ov8858_modes, OV8858_NUM_MODES,
> +				      width, height, fmt->format.width,
> +				      fmt->format.height);
> +
> +	fmt->format.code = MEDIA_BUS_FMT_SBGGR10_1X10;
> +	fmt->format.width = mode->width;
> +	fmt->format.height = mode->height;
> +	fmt->format.field = V4L2_FIELD_NONE;
> +
> +	format = v4l2_subdev_get_pad_format(sd, state, 0);
> +
> +	if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
> +		ov8858->cur_mode = mode;
> +		h_blank = mode->hts_def - mode->width;
> +		__v4l2_ctrl_modify_range(ov8858->hblank, h_blank, h_blank, 1,
> +					 h_blank);
> +
> +		vblank_def = mode->vts_def - mode->height;
> +		__v4l2_ctrl_modify_range(ov8858->vblank, vblank_def,
> +					 OV8858_VTS_MAX - mode->height, 1,
> +					 vblank_def);
> +	}
> +
> +	*format = fmt->format;
> +
> +	return 0;
> +}
> +
> +static int ov8858_enum_mbus_code(struct v4l2_subdev *sd,
> +				 struct v4l2_subdev_state *state,
> +				 struct v4l2_subdev_mbus_code_enum *code)
> +{
> +	if (code->index != 0)
> +		return -EINVAL;
> +
> +	code->code = MEDIA_BUS_FMT_SBGGR10_1X10;
> +
> +	return 0;
> +}
> +
> +static int ov8858_enum_frame_sizes(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *state,
> +				   struct v4l2_subdev_frame_size_enum *fse)
> +{
> +	if (fse->index >= OV8858_NUM_MODES)
> +		return -EINVAL;
> +
> +	if (fse->code != MEDIA_BUS_FMT_SBGGR10_1X10)
> +		return -EINVAL;
> +
> +	fse->min_width  = ov8858_modes[fse->index].width;
> +	fse->max_width  = ov8858_modes[fse->index].width;
> +	fse->max_height = ov8858_modes[fse->index].height;
> +	fse->min_height = ov8858_modes[fse->index].height;
> +
> +	return 0;
> +}
> +
> +static int ov8858_enable_test_pattern(struct ov8858 *ov8858, u32 pattern)

Move this down just before ov8858_set_ctrl() to keep functions related
to controls together.

> +{
> +	u32 val;
> +
> +	if (pattern)
> +		val = (pattern - 1) | OV8858_TEST_PATTERN_ENABLE;
> +	else
> +		val = OV8858_TEST_PATTERN_DISABLE;
> +
> +	return ov8858_write_reg(ov8858->client, OV8858_REG_TEST_PATTERN,
> +				val, NULL);
> +}
> +
> +static int ov8858_start_stream(struct ov8858 *ov8858)
> +{
> +	const struct regval *reg_list;
> +	int ret;
> +
> +	ret = ov8858_write_array(ov8858->client, ov8858->global_regs);
> +	if (ret)
> +		return ret;
> +
> +	if (ov8858->num_lanes == 4)
> +		reg_list = ov8858->cur_mode->reg_modes.mode_4lanes;
> +	else
> +		reg_list = ov8858->cur_mode->reg_modes.mode_2lanes;
> +
> +	ret = ov8858_write_array(ov8858->client, reg_list);
> +	if (ret)
> +		return ret;
> +
> +	/* 200 usec max to have PLL stabilize. */
> +	fsleep(200);
> +
> +	ret = __v4l2_ctrl_handler_setup(&ov8858->ctrl_handler);
> +	if (ret)
> +		return ret;
> +
> +	ret = ov8858_write_reg(ov8858->client, OV8858_REG_CTRL_MODE,
> +			       OV8858_MODE_STREAMING, NULL);
> +	if (ret)
> +		return ret;
> +
> +	/* t5 (fixed) = 10msec before entering streaming state */
> +	fsleep(10000);
> +
> +	return 0;
> +}
> +
> +static int ov8858_stop_stream(struct ov8858 *ov8858)
> +{
> +	return ov8858_write_reg(ov8858->client, OV8858_REG_CTRL_MODE,
> +				OV8858_MODE_SW_STANDBY, NULL);
> +}
> +
> +static int ov8858_s_stream(struct v4l2_subdev *sd, int on)
> +{
> +	struct ov8858 *ov8858 = to_ov8858(sd);
> +	struct i2c_client *client = ov8858->client;
> +	struct v4l2_subdev_state *state;
> +	int ret = 0;
> +
> +	/* Only for locking purpose. */
> +	state = v4l2_subdev_lock_and_get_active_state(sd);

You could also find the mode here from the width and height stored in
the state, to avoid storing cur_mode in the ov8858 structure, but that's
not required.

> +
> +	if (!!on == ov8858->streaming)
> +		goto unlock_and_return;

This can't happen, so you can drop this and the streaming flag, as well
as the goto below and the error label.

> +
> +	if (on) {
> +		ret = pm_runtime_resume_and_get(&client->dev);
> +		if (ret < 0)
> +			goto unlock_and_return;
> +
> +		ret = ov8858_start_stream(ov8858);
> +		if (ret) {
> +			dev_err(&client->dev, "Failed to start streaming\n");
> +			pm_runtime_put(&client->dev);

Please use runtime PM autosuspend to avoid costly power down/up
sequences when reconfiguring the sensor. See
https://git.linuxtv.org/pinchartl/media.git/commit/?h=imx290&id=4da70926308ffa71c059fe06027962463e54b930.

> +			goto unlock_and_return;
> +		}
> +	} else {
> +		ov8858_stop_stream(ov8858);
> +		pm_runtime_put(&client->dev);
> +	}
> +
> +	ov8858->streaming = !!on;
> +
> +unlock_and_return:
> +	v4l2_subdev_unlock_state(state);
> +
> +	return ret;
> +}
> +
> +static int ov8858_power_on(struct ov8858 *ov8858)
> +{
> +	struct device *dev = &ov8858->client->dev;
> +	unsigned long delay_us;
> +	int ret;
> +
> +	ret = clk_set_rate(ov8858->xvclk, OV8858_XVCLK_FREQ);
> +	if (ret < 0)
> +		dev_warn(dev, "Failed to set xvclk rate (24MHz)\n");
> +	if (clk_get_rate(ov8858->xvclk) != OV8858_XVCLK_FREQ)
> +		dev_warn(dev, "xvclk mismatched, modes are based on 24MHz\n");
> +
> +	ret = clk_prepare_enable(ov8858->xvclk);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to enable xvclk\n");
> +		return ret;
> +	}
> +
> +	ret = regulator_bulk_enable(OV8858_NUM_SUPPLIES, ov8858->supplies);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to enable regulators\n");
> +		goto disable_clk;
> +	}
> +
> +	/*
> +	 * The chip manual only suggests 8192 cycles prior to first SCCB
> +	 * transaction, but a double sleep between the release of gpios
> +	 * helps with sporadic failures observed at probe time.
> +	 */
> +	delay_us = DIV_ROUND_UP(8192, OV8858_XVCLK_FREQ / 1000 / 1000);
> +
> +	gpiod_set_value_cansleep(ov8858->reset_gpio, 0);
> +	fsleep(delay_us);
> +	gpiod_set_value_cansleep(ov8858->pwdn_gpio, 0);
> +	fsleep(delay_us);
> +
> +	return 0;
> +
> +disable_clk:
> +	clk_disable_unprepare(ov8858->xvclk);
> +
> +	return ret;
> +}
> +
> +static void ov8858_power_off(struct ov8858 *ov8858)
> +{
> +	gpiod_set_value_cansleep(ov8858->pwdn_gpio, 1);
> +	clk_disable_unprepare(ov8858->xvclk);
> +	gpiod_set_value_cansleep(ov8858->reset_gpio, 1);
> +
> +	regulator_bulk_disable(OV8858_NUM_SUPPLIES, ov8858->supplies);
> +}
> +
> +static int ov8858_runtime_resume(struct device *dev)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> +	struct ov8858 *ov8858 = to_ov8858(sd);
> +
> +	return ov8858_power_on(ov8858);
> +}
> +
> +static int ov8858_runtime_suspend(struct device *dev)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> +	struct ov8858 *ov8858 = to_ov8858(sd);
> +
> +	ov8858_power_off(ov8858);
> +
> +	return 0;
> +}

Could you move those four functions in a separate section ? They're in
the middle of the subdev operations, but they're not related. I like
section delimiters, for instance

/* -----------------------------------------------------------------------------
 * Power Management
 */

as it makes it easier to navigate the code in my opinion, but that's not
a hard requirement.

> +
> +static int ov8858_init_cfg(struct v4l2_subdev *sd,
> +			   struct v4l2_subdev_state *sd_state)
> +{
> +	struct ov8858 *ov8858 = to_ov8858(sd);
> +	const struct ov8858_mode *def_mode = ov8858->cur_mode;
> +	struct v4l2_subdev_format fmt = {
> +		.which = V4L2_SUBDEV_FORMAT_TRY,
> +		.format = {
> +			.width = def_mode->width,
> +			.height = def_mode->height,
> +		},
> +	};
> +
> +	ov8858_set_fmt(sd, sd_state, &fmt);
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_subdev_video_ops ov8858_video_ops = {
> +	.s_stream = ov8858_s_stream,
> +};
> +
> +static const struct v4l2_subdev_pad_ops ov8858_pad_ops = {
> +	.init_cfg = ov8858_init_cfg,
> +	.enum_mbus_code = ov8858_enum_mbus_code,
> +	.enum_frame_size = ov8858_enum_frame_sizes,
> +	.get_fmt = v4l2_subdev_get_fmt,
> +	.set_fmt = ov8858_set_fmt,
> +};
> +
> +static const struct v4l2_subdev_ops ov8858_subdev_ops = {
> +	.video	= &ov8858_video_ops,
> +	.pad	= &ov8858_pad_ops,
> +};
> +
> +static int ov8858_set_ctrl(struct v4l2_ctrl *ctrl)
> +{
> +	struct ov8858 *ov8858 = container_of(ctrl->handler,
> +					     struct ov8858, ctrl_handler);
> +	struct i2c_client *client = ov8858->client;
> +	s64 max_exp;
> +	int ret;
> +
> +	/* Propagate change of current control to all related controls */
> +	switch (ctrl->id) {
> +	case V4L2_CID_VBLANK:
> +		/* Update max exposure while meeting expected vblanking */
> +		max_exp = ov8858->cur_mode->height + ctrl->val - 4;

A macro for the offset (4) would be useful, as you also use it in
ov8858_initialize_controls().

If you decide to drop cur_mode (or even if you keep it, to minimize
dependencies on it), I would instead retrieve the height from the active
state.

One idea I'm toying with is to store control values in the state, which
would make it possibly to use TRY states to try combinations of formats,
selection rectangles and controls on subdevs. Using the state here
already would help with that in the future.

> +		__v4l2_ctrl_modify_range(ov8858->exposure,
> +					 ov8858->exposure->minimum, max_exp,
> +					 ov8858->exposure->step,
> +					 ov8858->exposure->default_value);
> +		break;
> +	}
> +
> +	if (!pm_runtime_get_if_in_use(&client->dev))
> +		return 0;
> +
> +	switch (ctrl->id) {
> +	case V4L2_CID_EXPOSURE:
> +		/* 4 least significant bits of exposure are fractional part */
> +		ret = ov8858_write_reg(ov8858->client, OV8858_REG_EXPOSURE,
> +				       ctrl->val << 4, NULL);
> +		break;
> +	case V4L2_CID_ANALOGUE_GAIN:
> +		ret = 0;
> +		ov8858_write_reg(ov8858->client, OV8858_REG_GAIN_H,
> +				 (ctrl->val >> OV8858_GAIN_H_SHIFT) &
> +				 OV8858_GAIN_H_MASK, &ret);
> +		ov8858_write_reg(ov8858->client,
> +				 OV8858_REG_GAIN_L,
> +				 ctrl->val & OV8858_GAIN_L_MASK, &ret);
> +		break;
> +	case V4L2_CID_DIGITAL_GAIN:
> +		ret = 0;
> +		ov8858_write_reg(ov8858->client, OV8858_REG_DGAIN_H,
> +				 (ctrl->val >> OV8858_DGAIN_H_SHIFT) &
> +				 OV8858_DGAIN_H_MASK, &ret);
> +		ov8858_write_reg(ov8858->client, OV8858_REG_DGAIN_L,
> +				 ctrl->val & OV8858_DGAIN_L_MASK, &ret);
> +		break;
> +	case V4L2_CID_VBLANK:
> +		ret = ov8858_write_reg(ov8858->client, OV8858_REG_VTS,
> +				       ctrl->val + ov8858->cur_mode->height,
> +				       NULL);
> +		break;
> +	case V4L2_CID_TEST_PATTERN:
> +		ret = ov8858_enable_test_pattern(ov8858, ctrl->val);
> +		break;
> +	default:
> +		ret = -EINVAL;
> +		dev_warn(&client->dev, "%s Unhandled id: 0x%x\n",
> +			 __func__, ctrl->id);
> +		break;
> +	}
> +
> +	return ret;
> +}
> +
> +static const struct v4l2_ctrl_ops ov8858_ctrl_ops = {
> +	.s_ctrl = ov8858_set_ctrl,
> +};
> +
> +static int ov8858_initialize_controls(struct ov8858 *ov8858)

Other functions or structures use ctrl, maybe ov8858_init_ctrls() ?

> +{
> +	struct v4l2_fwnode_device_properties props;
> +	struct v4l2_ctrl_handler *handler;
> +	const struct ov8858_mode *mode;
> +	s64 exposure_max, vblank_def;
> +	struct v4l2_ctrl *ctrl;
> +	u32 h_blank;
> +	int ret;
> +
> +	handler = &ov8858->ctrl_handler;
> +	mode = ov8858->cur_mode;

You can initialize those two variables when declaring them.

I'd use &ov8858_modes[0] here, again to avoid depending on cur_mode.

> +	ret = v4l2_ctrl_handler_init(handler, 10);
> +	if (ret)
> +		return ret;
> +	handler->lock = &ov8858->mutex;
> +
> +	ctrl = v4l2_ctrl_new_int_menu(handler, NULL, V4L2_CID_LINK_FREQ,
> +				      0, 0, link_freq_menu_items);
> +	if (ctrl)
> +		ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> +
> +	v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE,
> +			  0, ov8858->pixel_rate, 1, ov8858->pixel_rate);
> +
> +	h_blank = mode->hts_def - mode->width;
> +	ov8858->hblank = v4l2_ctrl_new_std(handler, NULL, V4L2_CID_HBLANK,
> +					   h_blank, h_blank, 1, h_blank);
> +	if (ov8858->hblank)
> +		ov8858->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> +
> +	vblank_def = mode->vts_def - mode->height;
> +	ov8858->vblank = v4l2_ctrl_new_std(handler, &ov8858_ctrl_ops,
> +					   V4L2_CID_VBLANK, vblank_def,
> +					   OV8858_VTS_MAX - mode->height,
> +					   1, vblank_def);
> +
> +	exposure_max = mode->vts_def - 4;
> +	ov8858->exposure = v4l2_ctrl_new_std(handler, &ov8858_ctrl_ops,
> +					     V4L2_CID_EXPOSURE,
> +					     OV8858_EXPOSURE_MIN,
> +					     exposure_max, OV8858_EXPOSURE_STEP,
> +					     mode->exp_def);
> +
> +	v4l2_ctrl_new_std(handler, &ov8858_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
> +			  OV8858_GAIN_MIN, OV8858_GAIN_MAX, OV8858_GAIN_STEP,
> +			  OV8858_GAIN_DEFAULT);
> +
> +	v4l2_ctrl_new_std(handler, &ov8858_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
> +			  OV8858_DGAIN_MIN, OV8858_DGAIN_MAX, OV8858_DGAIN_STEP,
> +			  OV8858_DGAIN_DEFAULT);
> +
> +	v4l2_ctrl_new_std_menu_items(handler, &ov8858_ctrl_ops,
> +				     V4L2_CID_TEST_PATTERN,
> +				     ARRAY_SIZE(ov8858_test_pattern_menu) - 1,
> +				     0, 0, ov8858_test_pattern_menu);
> +
> +	if (handler->error) {
> +		ret = handler->error;
> +		goto err_free_handler;
> +	}
> +
> +	ret = v4l2_fwnode_device_parse(&ov8858->client->dev, &props);
> +	if (ret)
> +		goto err_free_handler;
> +
> +	ret = v4l2_ctrl_new_fwnode_properties(handler, &ov8858_ctrl_ops,
> +					      &props);
> +	if (ret)
> +		goto err_free_handler;
> +
> +	ov8858->subdev.ctrl_handler = handler;
> +
> +	return 0;
> +
> +err_free_handler:
> +	dev_err(&ov8858->client->dev, "Failed to init controls (%d)\n", ret);
> +	v4l2_ctrl_handler_free(handler);
> +
> +	return ret;
> +}
> +
> +static int ov8858_check_sensor_id(struct ov8858 *ov8858,
> +				  struct i2c_client *client)
> +{
> +	struct device *dev = &ov8858->client->dev;
> +	u32 id = 0;
> +	int ret;
> +
> +	ret = ov8858_read_reg(client, OV8858_REG_CHIP_ID, &id);

	if (ret)
		return ret;

No need for an error message if you add one to ov8858_read_reg().

> +	if (id != OV8858_CHIP_ID) {
> +		dev_err(dev, "Unexpected sensor id(%06x), ret(%d)\n", id, ret);

		dev_err(dev, "Unexpected sensor ID %06x\n", id);

> +		return ret;
> +	}
> +
> +	ret = ov8858_read_reg(client, OV8858_CHIP_REVISION_REG, &id);
> +	if (ret) {
> +		dev_err(dev, "Read chip revision register error\n");

You can drop this if you add an error message in ov8858_read_reg().

> +		return ret;
> +	}
> +
> +	dev_info(dev, "Detected OV%06x sensor, REVISION 0x%x\n",
> +		 OV8858_CHIP_ID, id);

	dev_info(dev, "Detected OV8858 sensor, REVISION 0x%x\n", id);

as OV8858_CHIP_ID is hardcoded. I think I'd make this a debug message
though, to avoid too many messages in the kernel log at boot time. Up to
you.

> +
> +	if (id == OV8858_R2A) {
> +		ov8858->global_regs = ov8858->num_lanes == 4
> +				    ? ov8858_global_regs_r2a_4lane
> +				    : ov8858_global_regs_r2a_2lane;
> +	} else {
> +		ov8858->global_regs = ov8858_global_regs_r1a;
> +		dev_warn(dev, "R1A may not work well!\n");

As you mention in your cover letter I may be tempted to drop this too,
but I have no idea if there are such sensors in the wild.

> +	}
> +
> +	return 0;
> +}
> +
> +static int ov8858_configure_regulators(struct ov8858 *ov8858)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < OV8858_NUM_SUPPLIES; i++)
> +		ov8858->supplies[i].supply = ov8858_supply_names[i];
> +
> +	return devm_regulator_bulk_get(&ov8858->client->dev,
> +				       OV8858_NUM_SUPPLIES,
> +				       ov8858->supplies);
> +}
> +
> +static int ov8858_parse_of(struct ov8858 *ov8858)
> +{
> +	struct v4l2_fwnode_endpoint vep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
> +	struct device *dev = &ov8858->client->dev;
> +	struct fwnode_handle *endpoint;
> +	int ret;
> +
> +	endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL);
> +	if (!endpoint) {
> +		dev_err(dev, "Failed to get endpoint\n");
> +		return -EINVAL;
> +	}
> +
> +	ret = v4l2_fwnode_endpoint_parse(endpoint, &vep);
> +	if (ret) {
> +		dev_err(dev, "Failed to parse endpoint: %d\n", ret);
> +		fwnode_handle_put(endpoint);
> +		return ret;
> +	}
> +
> +	ov8858->num_lanes = vep.bus.mipi_csi2.num_data_lanes;
> +	switch (ov8858->num_lanes) {
> +	case 4:
> +		fallthrough;

Not needed.

> +	case 2:
> +		break;
> +	default:
> +		dev_err(dev, "Unsupported number of data lanes %u\n",
> +			ov8858->num_lanes);
> +		fwnode_handle_put(endpoint);
> +		return -EINVAL;
> +	}
> +
> +	/* pixel rate = link frequency * 2 * lanes / bpp */
> +	ov8858->cur_mode = &ov8858_modes[0];
> +	ov8858->pixel_rate = OV8858_LINK_FREQ * 2U * ov8858->num_lanes / 10U;

2 and 10 ?

> +	ov8858->subdev.fwnode = endpoint;
> +
> +	dev_dbg(dev, "num_lanes = %u - pixel_rate = %u\n",

s/ - /, /

> +		ov8858->num_lanes, ov8858->pixel_rate);
> +
> +	return 0;
> +}
> +
> +static int ov8858_probe(struct i2c_client *client)
> +{
> +	struct device *dev = &client->dev;
> +	struct v4l2_subdev *sd;
> +	struct ov8858 *ov8858;
> +	int ret;
> +
> +	ov8858 = devm_kzalloc(dev, sizeof(*ov8858), GFP_KERNEL);
> +	if (!ov8858)
> +		return -ENOMEM;
> +
> +	ov8858->client = client;
> +
> +	ov8858->xvclk = devm_clk_get(dev, "xvclk");
> +	if (IS_ERR(ov8858->xvclk))
> +		return dev_err_probe(dev, PTR_ERR(ov8858->xvclk),
> +				     "failed to get xvclk\n");
> +
> +	ov8858->reset_gpio = devm_gpiod_get_optional(dev, "reset",
> +						     GPIOD_OUT_HIGH);
> +	if (IS_ERR(ov8858->reset_gpio))
> +		return dev_err_probe(dev, PTR_ERR(ov8858->reset_gpio),
> +				     "failed to get reset gpio\n");
> +
> +	ov8858->pwdn_gpio = devm_gpiod_get_optional(dev, "powerdown",
> +						    GPIOD_OUT_HIGH);
> +	if (IS_ERR(ov8858->pwdn_gpio))
> +		return dev_err_probe(dev, PTR_ERR(ov8858->pwdn_gpio),
> +				     "failed to get powerdown gpio\n");
> +
> +	ret = ov8858_configure_regulators(ov8858);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "failed to get regulators\n");
> +
> +	sd = &ov8858->subdev;
> +	v4l2_i2c_subdev_init(sd, client, &ov8858_subdev_ops);
> +
> +	ret = ov8858_parse_of(ov8858);
> +	if (ret != 0)

	if (ret)

> +		return ret;
> +
> +	ret = ov8858_initialize_controls(ov8858);
> +	if (ret)

You're leaking ov8858->subdev.fwnode here. In the remove handler too
actually.

> +		return ret;
> +
> +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> +	ov8858->pad.flags = MEDIA_PAD_FL_SOURCE;
> +	sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
> +	ret = media_entity_pads_init(&sd->entity, 1, &ov8858->pad);
> +	if (ret < 0)
> +		goto err_free_handler;
> +
> +	sd->state_lock = &ov8858->mutex;
> +	ret = v4l2_subdev_init_finalize(sd);
> +	if (ret < 0) {
> +		dev_err(&client->dev, "subdev initialization error %d\n", ret);
> +		goto err_clean_entity;
> +	}
> +
> +	ret = ov8858_power_on(ov8858);
> +	if (ret)
> +		goto err_clean_entity;
> +
> +	ret = ov8858_check_sensor_id(ov8858, client);
> +	if (ret)
> +		goto err_power_off;
> +
> +	ret = v4l2_async_register_subdev_sensor(sd);
> +	if (ret) {
> +		dev_err(dev, "v4l2 async register subdev failed\n");
> +		goto err_power_off;
> +	}
> +
> +	pm_runtime_set_active(dev);
> +	pm_runtime_enable(dev);
> +	pm_runtime_idle(dev);

This needs to go before v4l2_async_register_subdev_sensor() or there's a
risk someone will start using the sensor before runtime PM is enabled.
See
https://git.linuxtv.org/pinchartl/media.git/commit/?h=imx290&id=4da70926308ffa71c059fe06027962463e54b930
for the required ordering.

> +
> +	return 0;
> +
> +err_power_off:
> +	ov8858_power_off(ov8858);
> +err_clean_entity:
> +	media_entity_cleanup(&sd->entity);
> +err_free_handler:
> +	v4l2_ctrl_handler_free(&ov8858->ctrl_handler);
> +
> +	return ret;
> +}
> +
> +static void ov8858_remove(struct i2c_client *client)
> +{
> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> +	struct ov8858 *ov8858 = to_ov8858(sd);
> +
> +	v4l2_async_unregister_subdev(sd);
> +	media_entity_cleanup(&sd->entity);
> +	v4l2_ctrl_handler_free(&ov8858->ctrl_handler);
> +
> +	pm_runtime_disable(&client->dev);
> +	if (!pm_runtime_status_suspended(&client->dev))
> +		ov8858_power_off(ov8858);
> +	pm_runtime_set_suspended(&client->dev);
> +}
> +
> +static const struct of_device_id ov8858_of_match[] = {
> +	{ .compatible = "ovti,ov8858" },
> +	{ /* sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(of, ov8858_of_match);
> +
> +static const struct dev_pm_ops ov8858_pm_ops = {
> +	SET_RUNTIME_PM_OPS(ov8858_runtime_suspend,
> +			   ov8858_runtime_resume, NULL)
> +};
> +
> +static struct i2c_driver ov8858_i2c_driver = {
> +	.driver = {
> +		.name = "ov8858",
> +		.pm = &ov8858_pm_ops,
> +		.of_match_table = of_match_ptr(ov8858_of_match),
> +	},
> +	.probe_new	= &ov8858_probe,
> +	.remove		= &ov8858_remove,
> +};
> +
> +module_i2c_driver(ov8858_i2c_driver);
> +
> +MODULE_DESCRIPTION("OmniVision ov8858 sensor driver");

s/ov8858/OV8858/

> +MODULE_LICENSE("GPL");

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v2 1/2] dt-bindings: media: Add OmniVision OV8858
  2023-01-06 20:39 ` [PATCH v2 1/2] dt-bindings: media: Add " Jacopo Mondi
@ 2023-01-07 13:09   ` Krzysztof Kozlowski
  2023-01-07 15:18     ` Jacopo Mondi
  0 siblings, 1 reply; 9+ messages in thread
From: Krzysztof Kozlowski @ 2023-01-07 13:09 UTC (permalink / raw)
  To: Jacopo Mondi, Nicholas Roth, Robert Mader, robh+dt,
	krzysztof.kozlowski+dt
  Cc: Laurent Pinchart, Sakari Ailus, linux-media, Jacopo Mondi, devicetree

On 06/01/2023 21:39, Jacopo Mondi wrote:
> From: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> 
> Add binding schema for the OmniVision OV8858 8 Megapixels camera sensor.
> 
> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>  .../bindings/media/i2c/ovti,ov8858.yaml       | 105 ++++++++++++++++++
>  1 file changed, 105 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/media/i2c/ovti,ov8858.yaml
> 
> diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,ov8858.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,ov8858.yaml
> new file mode 100644
> index 000000000000..002461a974f8
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/i2c/ovti,ov8858.yaml
> @@ -0,0 +1,105 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/media/i2c/ovti,ov8858.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: OmniVision OV8858 Image Sensor
> +
> +maintainers:
> +  - Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> +  - Nicholas Roth <nicholas@rothemail.net>
> +
> +description: |
> +  The OmniVision OV8858 is a color CMOS 8 Megapixels (3264x2448) image sensor
> +  controlled through an I2C-compatible SCCB bus. The sensor transmits images
> +  on a MIPI CSI-2 output interface with up to 4 data lanes.
> +
> +properties:
> +  compatible:
> +    const: ovti,ov8858
> +
> +  reg:
> +    maxItems: 1
> +
> +  clocks:
> +    maxItems: 1
> +    description: XVCLK external clock
> +
> +  clock-names:
> +    const: xvclk
> +
> +  dvdd-supply:
> +    description: Digital Domain Power Supply
> +
> +  avdd-supply:
> +    description: Analog Domain Power Supply
> +
> +  dovdd-supply:
> +    description: I/O Domain Power Supply
> +
> +  powerdown-gpios:
> +    description: PWDNB powerdown GPIO (active low)
> +
> +  reset-gpios:
> +    description: XSHUTDN reset GPIO (active low)

Here you need maxItems. I did not propose to remove it here.


Best regards,
Krzysztof


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

* Re: [PATCH v2 2/2] media: i2c: Add driver for OmniVision OV8858
  2023-01-06 23:07   ` Laurent Pinchart
@ 2023-01-07 15:17     ` Jacopo Mondi
  2023-01-08  2:07       ` Laurent Pinchart
  0 siblings, 1 reply; 9+ messages in thread
From: Jacopo Mondi @ 2023-01-07 15:17 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Nicholas Roth, Robert Mader, Sakari Ailus, linux-media, Jacopo Mondi

Hi Laurent, thanks for review

On Sat, Jan 07, 2023 at 01:07:52AM +0200, Laurent Pinchart wrote:
> Hi Jacopo and Nicholas,
>
> Thank you for the patch.
>
> On Fri, Jan 06, 2023 at 09:39:09PM +0100, Jacopo Mondi wrote:
> > From: Nicholas Roth <nicholas@rothemail.net>
> >
> > Add a driver for OmniVision OV8858 image sensor.
> >
> > The driver currently supports operations with 2 and 4 data lanes, in
> > full resolution and half-binned resolution modes.
> >
> > The driver has been upported from the PinephonePro BSP available at
> > https://gitlab.com/pine64-org/linux.git
> > at commit 8c4a90c12dc2 ("media: i2c: ov8858: Use default subdev
> > name").
> >
> > Signed-off-by: Nicholas Roth <nicholas@rothemail.net>
> > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> > ---
> >  MAINTAINERS                |    9 +
> >  drivers/media/i2c/Kconfig  |   13 +
> >  drivers/media/i2c/Makefile |    1 +
> >  drivers/media/i2c/ov8858.c | 1952 ++++++++++++++++++++++++++++++++++++
> >  4 files changed, 1975 insertions(+)
> >  create mode 100644 drivers/media/i2c/ov8858.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index f61eb221415b..fafb0a01be93 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -15524,6 +15524,15 @@ T:	git git://linuxtv.org/media_tree.git
> >  F:	Documentation/devicetree/bindings/media/i2c/ov8856.yaml
> >  F:	drivers/media/i2c/ov8856.c
> >
> > +OMNIVISION OV8858 SENSOR DRIVER
> > +M:	Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> > +M:	Nicholas Roth <nicholas@rothemail.net>
> > +L:	linux-media@vger.kernel.org
> > +S:	Maintained
> > +T:	git git://linuxtv.org/media_tree.git
> > +F:	Documentation/devicetree/bindings/media/i2c/ovti,ov8858.yaml
> > +F:	drivers/media/i2c/ov8858.c
> > +
> >  OMNIVISION OV9282 SENSOR DRIVER
> >  M:	Paul J. Murphy <paul.j.murphy@intel.com>
> >  M:	Daniele Alessandrelli <daniele.alessandrelli@intel.com>
> > diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> > index 833241897d63..12ba8542778f 100644
> > --- a/drivers/media/i2c/Kconfig
> > +++ b/drivers/media/i2c/Kconfig
> > @@ -645,6 +645,19 @@ config VIDEO_OV8856
> >  	  To compile this driver as a module, choose M here: the
> >  	  module will be called ov8856.
> >
> > +config VIDEO_OV8858
> > +	tristate "OmniVision OV8858 sensor support"
> > +	depends on I2C && PM && VIDEO_DEV
> > +	select MEDIA_CONTROLLER
> > +	select VIDEO_V4L2_SUBDEV_API
> > +	select V4L2_FWNODE
> > +	help
> > +	  This is a Video4Linux2 sensor driver for OmniVision
> > +	  OV8858 camera sensor.
> > +
> > +	  To compile this driver as a module, choose M here: the
> > +	  module will be called ov8858.
> > +
> >  config VIDEO_OV8865
> >  	tristate "OmniVision OV8865 sensor support"
> >  	depends on I2C && PM && VIDEO_DEV
> > diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> > index 4d6c052bb5a7..b611a8277d57 100644
> > --- a/drivers/media/i2c/Makefile
> > +++ b/drivers/media/i2c/Makefile
> > @@ -96,6 +96,7 @@ obj-$(CONFIG_VIDEO_OV7670) += ov7670.o
> >  obj-$(CONFIG_VIDEO_OV772X) += ov772x.o
> >  obj-$(CONFIG_VIDEO_OV7740) += ov7740.o
> >  obj-$(CONFIG_VIDEO_OV8856) += ov8856.o
> > +obj-$(CONFIG_VIDEO_OV8858) += ov8858.o
> >  obj-$(CONFIG_VIDEO_OV8865) += ov8865.o
> >  obj-$(CONFIG_VIDEO_OV9282) += ov9282.o
> >  obj-$(CONFIG_VIDEO_OV9640) += ov9640.o
> > diff --git a/drivers/media/i2c/ov8858.c b/drivers/media/i2c/ov8858.c
> > new file mode 100644
> > index 000000000000..bfdfac49caa9
> > --- /dev/null
> > +++ b/drivers/media/i2c/ov8858.c
> > @@ -0,0 +1,1952 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (C) 2023 Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> > + * Copyright (C) 2022 Nicholas Roth <nicholas@rothemail.net>
> > + * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd.
> > + */
> > +
> > +#include <asm/unaligned.h>
> > +
> > +#include <linux/clk.h>
> > +#include <linux/delay.h>
> > +#include <linux/device.h>
> > +#include <linux/gpio/consumer.h>
> > +#include <linux/i2c.h>
> > +#include <linux/module.h>
> > +#include <linux/of.h>
> > +#include <linux/of_graph.h>
> > +#include <linux/pm_runtime.h>
> > +#include <linux/regulator/consumer.h>
> > +#include <linux/slab.h>
> > +
> > +#include <media/media-entity.h>
> > +#include <media/v4l2-async.h>
> > +#include <media/v4l2-common.h>
> > +#include <media/v4l2-ctrls.h>
> > +#include <media/v4l2-device.h>
> > +#include <media/v4l2-fwnode.h>
> > +#include <media/v4l2-mediabus.h>
> > +#include <media/v4l2-subdev.h>
> > +
> > +#define OV8858_LINK_FREQ		360000000U
> > +#define OV8858_XVCLK_FREQ		24000000
> > +
> > +#define OV8858_REG_SIZE_SHIFT		16
> > +#define OV8858_REG_ADDR_MASK		0xffff
> > +#define OV8858_REG_8BIT(n)		((1U << OV8858_REG_SIZE_SHIFT) | (n))
> > +#define OV8858_REG_16BIT(n)		((2U << OV8858_REG_SIZE_SHIFT) | (n))
> > +#define OV8858_REG_24BIT(n)		((3U << OV8858_REG_SIZE_SHIFT) | (n))
>
> /me likes this :-)
>
> > +
> > +#define OV8858_CHIP_ID			0x008858
> > +#define OV8858_REG_CHIP_ID		OV8858_REG_24BIT(0x300a)
> > +
> > +#define OV8858_REG_CTRL_MODE		OV8858_REG_8BIT(0x0100)
> > +#define OV8858_MODE_SW_STANDBY		0x0
> > +#define OV8858_MODE_STREAMING		0x1
>
> Please move this up to sort registers by address. Same thing in several
> locations below.
>

ack

> > +
> > +#define OV8858_REG_EXPOSURE		OV8858_REG_24BIT(0x3500)
> > +#define	OV8858_EXPOSURE_MIN		4
> > +#define	OV8858_EXPOSURE_STEP		1
> > +#define OV8858_VTS_MAX			0x7fff
> > +
> > +#define OV8858_REG_GAIN_H		OV8858_REG_8BIT(0x3508)
> > +#define OV8858_REG_GAIN_L		OV8858_REG_8BIT(0x3509)
>
> This is a single 16-bit register. You will be able to drop the mask and
> shift macros.
>

You're right, I can write 16 bits in one go

> > +#define OV8858_GAIN_H_MASK		0x07
> > +#define OV8858_GAIN_H_SHIFT		8
> > +#define OV8858_GAIN_L_MASK		0xff
> > +#define OV8858_GAIN_MIN			0x0
> > +#define OV8858_GAIN_MAX			0x7ff
>
> According to the documentation, the gain is a 13-bit value ([12:0]),
> should this be 0x1fff ? I'm not sure we can trust the documentation
> though, the description of register 0x3509 is clearly wrong.
>

I know...
Please see
https://patchwork.kernel.org/project/linux-media/patch/20221106171129.166892-2-nicholas@rothemail.net/#25112139
for a little backgound

> > +#define OV8858_GAIN_STEP		1
> > +#define OV8858_GAIN_DEFAULT		0x80
> > +
> > +#define OV8858_REG_DGAIN_H		OV8858_REG_8BIT(0x350a)
> > +#define OV8858_REG_DGAIN_L		OV8858_REG_8BIT(0x350b)
>
> Same here.
>
> > +#define OV8858_DGAIN_H_MASK		0xff
> > +#define OV8858_DGAIN_H_SHIFT		6
> > +#define OV8858_DGAIN_L_MASK		0x3f
> > +#define OV8858_DGAIN_MIN		0x0
> > +#define OV8858_DGAIN_MAX		0x3fff
> > +#define OV8858_DGAIN_STEP		1
> > +#define OV8858_DGAIN_DEFAULT		0x200
> > +
> > +#define OV8858_REG_TEST_PATTERN		OV8858_REG_8BIT(0x5e00)
> > +#define	OV8858_TEST_PATTERN_ENABLE	0x80
> > +#define	OV8858_TEST_PATTERN_DISABLE	0x0
> > +
> > +#define OV8858_REG_VTS			OV8858_REG_16BIT(0x380e)
> > +
> > +#define REG_NULL			0xffff
> > +
> > +#define OV8858_CHIP_REVISION_REG	OV8858_REG_8BIT(0x302a)
>
> It's called "SUB ID" in the documentation, how about sticking to that
> name ? Actually this applies to the other registers above, should we
> keep them as close as possible to the datasheet ?

I'll try to

>
> > +#define OV8858_R1A			0xb0
> > +#define OV8858_R2A			0xb2
> > +
> > +static const char * const ov8858_supply_names[] = {
> > +	"avdd",		/* Analog power */
> > +	"dovdd",	/* Digital I/O power */
> > +	"dvdd",		/* Digital core power */
> > +};
> > +
> > +#define OV8858_NUM_SUPPLIES ARRAY_SIZE(ov8858_supply_names)
> > +
> > +struct regval {
> > +	u16 addr;
> > +	u8 val;
> > +};
> > +
> > +struct regval_modes {
> > +	const struct regval *mode_2lanes;
> > +	const struct regval *mode_4lanes;
> > +};
> > +
> > +struct ov8858_mode {
> > +	u32 width;
> > +	u32 height;
> > +	u32 hts_def;
> > +	u32 vts_def;
> > +	u32 exp_def;
> > +	const struct regval_modes reg_modes;
> > +};
> > +
> > +struct ov8858 {
> > +	struct i2c_client	*client;
> > +	struct clk		*xvclk;
> > +	struct gpio_desc	*reset_gpio;
> > +	struct gpio_desc	*pwdn_gpio;
> > +	struct regulator_bulk_data supplies[OV8858_NUM_SUPPLIES];
> > +
> > +	struct v4l2_subdev	subdev;
> > +	struct media_pad	pad;
> > +
> > +	/* Protect controls and image format. */
> > +	struct mutex		mutex;
>
> V4L2 provides a default lock in the control handler. You could use it
> and drop this, using the control handler lock as the active state lock
> as well.  No functional difference (the important part is to use the
> same lock for the control handler and the active state), but one less
> field in this structure.
>

I wasn't sure about the control handler lock, as it is usually pointed
to a driver-specific mutex.

In example (imx219)

	ctrl_hdlr->lock = &imx219->mutex;

I've seen your imx290 patch re-using it but I didn't do the same. I'll
change it.

> If you want to keep this field, you're missing mutex_init() and
> mutex_destroy().
>

Got fooled by
	__mutex_init(&state->_lock, lock_name, lock_key);

in __v4l2_subdev_state_alloc()

But that's actually a different mutex


> > +
> > +	struct v4l2_ctrl_handler ctrl_handler;
> > +	struct v4l2_ctrl	*exposure;
> > +	struct v4l2_ctrl	*hblank;
> > +	struct v4l2_ctrl	*vblank;
> > +
> > +	const struct regval	*global_regs;
> > +	const struct ov8858_mode *cur_mode;
>
> It would be nice to drop the cur_mode field, that would simplify
> set_fmt(), but it's not a hard requirement. Let's minimize its usage
> though.
>
> > +
> > +	bool			streaming;
> > +	unsigned int		num_lanes;
> > +	unsigned int		pixel_rate;
> > +};
> > +
> > +#define to_ov8858(sd) container_of(sd, struct ov8858, subdev)
>
> Maybe a static inline function to provide type safety ?
>

Better, yes

> > +
> > +static const struct regval ov8858_global_regs_r1a[] = {
> > +	{0x0100, 0x00},
> > +	{0x0100, 0x00},
> > +	{0x0100, 0x00},
> > +	{0x0100, 0x00},
> > +	{0x0302, 0x1e},
> > +	{0x0303, 0x00},
> > +	{0x0304, 0x03},
> > +	{0x030e, 0x00},
> > +	{0x030f, 0x09},
> > +	{0x0312, 0x01},
> > +	{0x031e, 0x0c},
> > +	{0x3600, 0x00},
> > +	{0x3601, 0x00},
> > +	{0x3602, 0x00},
> > +	{0x3603, 0x00},
> > +	{0x3604, 0x22},
> > +	{0x3605, 0x30},
> > +	{0x3606, 0x00},
> > +	{0x3607, 0x20},
> > +	{0x3608, 0x11},
> > +	{0x3609, 0x28},
> > +	{0x360a, 0x00},
> > +	{0x360b, 0x06},
> > +	{0x360c, 0xdc},
> > +	{0x360d, 0x40},
> > +	{0x360e, 0x0c},
> > +	{0x360f, 0x20},
> > +	{0x3610, 0x07},
> > +	{0x3611, 0x20},
> > +	{0x3612, 0x88},
> > +	{0x3613, 0x80},
> > +	{0x3614, 0x58},
> > +	{0x3615, 0x00},
> > +	{0x3616, 0x4a},
> > +	{0x3617, 0xb0},
> > +	{0x3618, 0x56},
> > +	{0x3619, 0x70},
> > +	{0x361a, 0x99},
> > +	{0x361b, 0x00},
> > +	{0x361c, 0x07},
> > +	{0x361d, 0x00},
> > +	{0x361e, 0x00},
> > +	{0x361f, 0x00},
> > +	{0x3638, 0xff},
> > +	{0x3633, 0x0c},
> > +	{0x3634, 0x0c},
> > +	{0x3635, 0x0c},
> > +	{0x3636, 0x0c},
> > +	{0x3645, 0x13},
> > +	{0x3646, 0x83},
> > +	{0x364a, 0x07},
> > +	{0x3015, 0x01},
> > +	{0x3018, 0x32},
> > +	{0x3020, 0x93},
> > +	{0x3022, 0x01},
> > +	{0x3031, 0x0a},
> > +	{0x3034, 0x00},
> > +	{0x3106, 0x01},
> > +	{0x3305, 0xf1},
> > +	{0x3308, 0x00},
> > +	{0x3309, 0x28},
> > +	{0x330a, 0x00},
> > +	{0x330b, 0x20},
> > +	{0x330c, 0x00},
> > +	{0x330d, 0x00},
> > +	{0x330e, 0x00},
> > +	{0x330f, 0x40},
> > +	{0x3307, 0x04},
> > +	{0x3500, 0x00},
> > +	{0x3501, 0x4d},
> > +	{0x3502, 0x40},
> > +	{0x3503, 0x00},
> > +	{0x3505, 0x80},
> > +	{0x3508, 0x04},
> > +	{0x3509, 0x00},
> > +	{0x350c, 0x00},
> > +	{0x350d, 0x80},
> > +	{0x3510, 0x00},
> > +	{0x3511, 0x02},
> > +	{0x3512, 0x00},
> > +	{0x3700, 0x18},
> > +	{0x3701, 0x0c},
> > +	{0x3702, 0x28},
> > +	{0x3703, 0x19},
> > +	{0x3704, 0x14},
> > +	{0x3705, 0x00},
> > +	{0x3706, 0x35},
> > +	{0x3707, 0x04},
> > +	{0x3708, 0x24},
> > +	{0x3709, 0x33},
> > +	{0x370a, 0x00},
> > +	{0x370b, 0xb5},
> > +	{0x370c, 0x04},
> > +	{0x3718, 0x12},
> > +	{0x3719, 0x31},
> > +	{0x3712, 0x42},
> > +	{0x3714, 0x24},
> > +	{0x371e, 0x19},
> > +	{0x371f, 0x40},
> > +	{0x3720, 0x05},
> > +	{0x3721, 0x05},
> > +	{0x3724, 0x06},
> > +	{0x3725, 0x01},
> > +	{0x3726, 0x06},
> > +	{0x3728, 0x05},
> > +	{0x3729, 0x02},
> > +	{0x372a, 0x03},
> > +	{0x372b, 0x53},
> > +	{0x372c, 0xa3},
> > +	{0x372d, 0x53},
> > +	{0x372e, 0x06},
> > +	{0x372f, 0x10},
> > +	{0x3730, 0x01},
> > +	{0x3731, 0x06},
> > +	{0x3732, 0x14},
> > +	{0x3733, 0x10},
> > +	{0x3734, 0x40},
> > +	{0x3736, 0x20},
> > +	{0x373a, 0x05},
> > +	{0x373b, 0x06},
> > +	{0x373c, 0x0a},
> > +	{0x373e, 0x03},
> > +	{0x3755, 0x10},
> > +	{0x3758, 0x00},
> > +	{0x3759, 0x4c},
> > +	{0x375a, 0x06},
> > +	{0x375b, 0x13},
> > +	{0x375c, 0x20},
> > +	{0x375d, 0x02},
> > +	{0x375e, 0x00},
> > +	{0x375f, 0x14},
> > +	{0x3768, 0x22},
> > +	{0x3769, 0x44},
> > +	{0x376a, 0x44},
> > +	{0x3761, 0x00},
> > +	{0x3762, 0x00},
> > +	{0x3763, 0x00},
> > +	{0x3766, 0xff},
> > +	{0x376b, 0x00},
> > +	{0x3772, 0x23},
> > +	{0x3773, 0x02},
> > +	{0x3774, 0x16},
> > +	{0x3775, 0x12},
> > +	{0x3776, 0x04},
> > +	{0x3777, 0x00},
> > +	{0x3778, 0x1b},
> > +	{0x37a0, 0x44},
> > +	{0x37a1, 0x3d},
> > +	{0x37a2, 0x3d},
> > +	{0x37a3, 0x00},
> > +	{0x37a4, 0x00},
> > +	{0x37a5, 0x00},
> > +	{0x37a6, 0x00},
> > +	{0x37a7, 0x44},
> > +	{0x37a8, 0x4c},
> > +	{0x37a9, 0x4c},
> > +	{0x3760, 0x00},
> > +	{0x376f, 0x01},
> > +	{0x37aa, 0x44},
> > +	{0x37ab, 0x2e},
> > +	{0x37ac, 0x2e},
> > +	{0x37ad, 0x33},
> > +	{0x37ae, 0x0d},
> > +	{0x37af, 0x0d},
> > +	{0x37b0, 0x00},
> > +	{0x37b1, 0x00},
> > +	{0x37b2, 0x00},
> > +	{0x37b3, 0x42},
> > +	{0x37b4, 0x42},
> > +	{0x37b5, 0x33},
> > +	{0x37b6, 0x00},
> > +	{0x37b7, 0x00},
> > +	{0x37b8, 0x00},
> > +	{0x37b9, 0xff},
> > +	{0x3800, 0x00},
> > +	{0x3801, 0x0c},
> > +	{0x3802, 0x00},
> > +	{0x3803, 0x0c},
> > +	{0x3804, 0x0c},
> > +	{0x3805, 0xd3},
> > +	{0x3806, 0x09},
> > +	{0x3807, 0xa3},
> > +	{0x3808, 0x06},
> > +	{0x3809, 0x60},
> > +	{0x380a, 0x04},
> > +	{0x380b, 0xc8},
> > +	{0x380c, 0x07},
> > +	{0x380d, 0x88},
> > +	{0x380e, 0x04},
> > +	{0x380f, 0xdc},
> > +	{0x3810, 0x00},
> > +	{0x3811, 0x04},
> > +	{0x3813, 0x02},
> > +	{0x3814, 0x03},
> > +	{0x3815, 0x01},
> > +	{0x3820, 0x00},
> > +	{0x3821, 0x67},
> > +	{0x382a, 0x03},
> > +	{0x382b, 0x01},
> > +	{0x3830, 0x08},
> > +	{0x3836, 0x02},
> > +	{0x3837, 0x18},
> > +	{0x3841, 0xff},
> > +	{0x3846, 0x48},
> > +	{0x3d85, 0x14},
> > +	{0x3f08, 0x08},
> > +	{0x3f0a, 0x80},
> > +	{0x4000, 0xf1},
> > +	{0x4001, 0x10},
> > +	{0x4005, 0x10},
> > +	{0x4002, 0x27},
> > +	{0x4009, 0x81},
> > +	{0x400b, 0x0c},
> > +	{0x401b, 0x00},
> > +	{0x401d, 0x00},
> > +	{0x4020, 0x00},
> > +	{0x4021, 0x04},
> > +	{0x4022, 0x04},
> > +	{0x4023, 0xb9},
> > +	{0x4024, 0x05},
> > +	{0x4025, 0x2a},
> > +	{0x4026, 0x05},
> > +	{0x4027, 0x2b},
> > +	{0x4028, 0x00},
> > +	{0x4029, 0x02},
> > +	{0x402a, 0x04},
> > +	{0x402b, 0x04},
> > +	{0x402c, 0x02},
> > +	{0x402d, 0x02},
> > +	{0x402e, 0x08},
> > +	{0x402f, 0x02},
> > +	{0x401f, 0x00},
> > +	{0x4034, 0x3f},
> > +	{0x403d, 0x04},
> > +	{0x4300, 0xff},
> > +	{0x4301, 0x00},
> > +	{0x4302, 0x0f},
> > +	{0x4316, 0x00},
> > +	{0x4500, 0x38},
> > +	{0x4503, 0x18},
> > +	{0x4600, 0x00},
> > +	{0x4601, 0xcb},
> > +	{0x481f, 0x32},
> > +	{0x4837, 0x16},
> > +	{0x4850, 0x10},
> > +	{0x4851, 0x32},
> > +	{0x4b00, 0x2a},
> > +	{0x4b0d, 0x00},
> > +	{0x4d00, 0x04},
> > +	{0x4d01, 0x18},
> > +	{0x4d02, 0xc3},
> > +	{0x4d03, 0xff},
> > +	{0x4d04, 0xff},
> > +	{0x4d05, 0xff},
> > +	{0x5000, 0x7e},
> > +	{0x5001, 0x01},
> > +	{0x5002, 0x08},
> > +	{0x5003, 0x20},
> > +	{0x5046, 0x12},
> > +	{0x5901, 0x00},
> > +	{0x5e00, 0x00},
> > +	{0x5e01, 0x41},
> > +	{0x382d, 0x7f},
> > +	{0x4825, 0x3a},
> > +	{0x4826, 0x40},
> > +	{0x4808, 0x25},
> > +	//{0x0100, 0x01},
> > +	{REG_NULL, 0x00},
> > +};
> > +
> > +static const struct regval ov8858_global_regs_r2a_2lane[] = {
> > +	/*
> > +	 * MIPI=720Mbps, SysClk=144Mhz,Dac Clock=360Mhz.
> > +	 * v00_01_00 (05/29/2014) : initial setting
> > +	 * AM19 : 3617 <- 0xC0
> > +	 * AM20 : change FWC_6K_EN to be default 0x3618=0x5a
> > +	 */
> > +	{0x0103, 0x01},// software reset for OVTATool only
> > +	{0x0103, 0x01},// software reset
> > +	{0x0100, 0x00},// software standby
> > +	{0x0302, 0x1e},// pll1_multi
> > +	{0x0303, 0x00},// pll1_divm
> > +	{0x0304, 0x03},// pll1_div_mipi
> > +	{0x030e, 0x02},// pll2_rdiv
> > +	{0x030f, 0x04},// pll2_divsp
> > +	{0x0312, 0x03},// pll2_pre_div0, pll2_r_divdac
> > +	{0x031e, 0x0c},// pll1_no_lat
>
> I'll pretend to ignore the C++-style comments, but could you add a space
> before // ?
>
> > +	{0x3600, 0x00},
> > +	{0x3601, 0x00},
> > +	{0x3602, 0x00},
> > +	{0x3603, 0x00},
> > +	{0x3604, 0x22},
> > +	{0x3605, 0x20},
> > +	{0x3606, 0x00},
> > +	{0x3607, 0x20},
> > +	{0x3608, 0x11},
> > +	{0x3609, 0x28},
> > +	{0x360a, 0x00},
> > +	{0x360b, 0x05},
> > +	{0x360c, 0xd4},
> > +	{0x360d, 0x40},
> > +	{0x360e, 0x0c},
> > +	{0x360f, 0x20},
> > +	{0x3610, 0x07},
> > +	{0x3611, 0x20},
> > +	{0x3612, 0x88},
> > +	{0x3613, 0x80},
> > +	{0x3614, 0x58},
> > +	{0x3615, 0x00},
> > +	{0x3616, 0x4a},
> > +	{0x3617, 0x90},
> > +	{0x3618, 0x5a},
> > +	{0x3619, 0x70},
> > +	{0x361a, 0x99},
> > +	{0x361b, 0x0a},
> > +	{0x361c, 0x07},
> > +	{0x361d, 0x00},
> > +	{0x361e, 0x00},
> > +	{0x361f, 0x00},
> > +	{0x3638, 0xff},
> > +	{0x3633, 0x0f},
> > +	{0x3634, 0x0f},
> > +	{0x3635, 0x0f},
> > +	{0x3636, 0x12},
> > +	{0x3645, 0x13},
> > +	{0x3646, 0x83},
> > +	{0x364a, 0x07},
> > +	{0x3015, 0x00},
> > +	{0x3018, 0x32}, // MIPI 2 lane
> > +	{0x3020, 0x93}, // Clock switch output normal, pclk_div =/1
> > +	{0x3022, 0x01}, // pd_mipi enable when rst_sync
> > +	{0x3031, 0x0a}, // MIPI 10-bit mode
> > +	{0x3034, 0x00}, //
> > +	{0x3106, 0x01}, // sclk_div, sclk_pre_div
> > +	{0x3305, 0xf1},
> > +	{0x3308, 0x00},
> > +	{0x3309, 0x28},
> > +	{0x330a, 0x00},
> > +	{0x330b, 0x20},
> > +	{0x330c, 0x00},
> > +	{0x330d, 0x00},
> > +	{0x330e, 0x00},
> > +	{0x330f, 0x40},
> > +	{0x3307, 0x04},
> > +	{0x3500, 0x00}, // exposure H
> > +	{0x3501, 0x4d}, // exposure M
> > +	{0x3502, 0x40}, // exposure L
> > +	{0x3503, 0x80}, // gain delay ?, exposure delay 1 frame, real gain
> > +	{0x3505, 0x80}, // gain option
> > +	{0x3508, 0x02}, // gain H
> > +	{0x3509, 0x00}, // gain L
> > +	{0x350c, 0x00}, // short gain H
> > +	{0x350d, 0x80}, // short gain L
> > +	{0x3510, 0x00}, // short exposure H
> > +	{0x3511, 0x02}, // short exposure M
> > +	{0x3512, 0x00}, // short exposure L
> > +	{0x3700, 0x18},
> > +	{0x3701, 0x0c},
> > +	{0x3702, 0x28},
> > +	{0x3703, 0x19},
> > +	{0x3704, 0x14},
> > +	{0x3705, 0x00},
> > +	{0x3706, 0x82},
> > +	{0x3707, 0x04},
> > +	{0x3708, 0x24},
> > +	{0x3709, 0x33},
> > +	{0x370a, 0x01},
> > +	{0x370b, 0x82},
> > +	{0x370c, 0x04},
> > +	{0x3718, 0x12},
> > +	{0x3719, 0x31},
> > +	{0x3712, 0x42},
> > +	{0x3714, 0x24},
> > +	{0x371e, 0x19},
> > +	{0x371f, 0x40},
> > +	{0x3720, 0x05},
> > +	{0x3721, 0x05},
> > +	{0x3724, 0x06},
> > +	{0x3725, 0x01},
> > +	{0x3726, 0x06},
> > +	{0x3728, 0x05},
> > +	{0x3729, 0x02},
> > +	{0x372a, 0x03},
> > +	{0x372b, 0x53},
> > +	{0x372c, 0xa3},
> > +	{0x372d, 0x53},
> > +	{0x372e, 0x06},
> > +	{0x372f, 0x10},
> > +	{0x3730, 0x01},
> > +	{0x3731, 0x06},
> > +	{0x3732, 0x14},
> > +	{0x3733, 0x10},
> > +	{0x3734, 0x40},
> > +	{0x3736, 0x20},
> > +	{0x373a, 0x05},
> > +	{0x373b, 0x06},
> > +	{0x373c, 0x0a},
> > +	{0x373e, 0x03},
> > +	{0x3750, 0x0a},
> > +	{0x3751, 0x0e},
> > +	{0x3755, 0x10},
> > +	{0x3758, 0x00},
> > +	{0x3759, 0x4c},
> > +	{0x375a, 0x06},
> > +	{0x375b, 0x13},
> > +	{0x375c, 0x20},
> > +	{0x375d, 0x02},
> > +	{0x375e, 0x00},
> > +	{0x375f, 0x14},
> > +	{0x3768, 0x22},
> > +	{0x3769, 0x44},
> > +	{0x376a, 0x44},
> > +	{0x3761, 0x00},
> > +	{0x3762, 0x00},
> > +	{0x3763, 0x00},
> > +	{0x3766, 0xff},
> > +	{0x376b, 0x00},
> > +	{0x3772, 0x23},
> > +	{0x3773, 0x02},
> > +	{0x3774, 0x16},
> > +	{0x3775, 0x12},
> > +	{0x3776, 0x04},
> > +	{0x3777, 0x00},
> > +	{0x3778, 0x17},
> > +	{0x37a0, 0x44},
> > +	{0x37a1, 0x3d},
> > +	{0x37a2, 0x3d},
> > +	{0x37a3, 0x00},
> > +	{0x37a4, 0x00},
> > +	{0x37a5, 0x00},
> > +	{0x37a6, 0x00},
> > +	{0x37a7, 0x44},
> > +	{0x37a8, 0x4c},
> > +	{0x37a9, 0x4c},
> > +	{0x3760, 0x00},
> > +	{0x376f, 0x01},
> > +	{0x37aa, 0x44},
> > +	{0x37ab, 0x2e},
> > +	{0x37ac, 0x2e},
> > +	{0x37ad, 0x33},
> > +	{0x37ae, 0x0d},
> > +	{0x37af, 0x0d},
> > +	{0x37b0, 0x00},
> > +	{0x37b1, 0x00},
> > +	{0x37b2, 0x00},
> > +	{0x37b3, 0x42},
> > +	{0x37b4, 0x42},
> > +	{0x37b5, 0x31},
> > +	{0x37b6, 0x00},
> > +	{0x37b7, 0x00},
> > +	{0x37b8, 0x00},
> > +	{0x37b9, 0xff},
> > +	{0x3800, 0x00}, // x start H
> > +	{0x3801, 0x0c}, // x start L
> > +	{0x3802, 0x00}, // y start H
> > +	{0x3803, 0x0c}, // y start L
>
> We have documentation for lots of those registers, it would be nice to
> give them names, but that's not a hard requirement either.
>

Ideally we could be able to split the registers table in 3:
- global init
- 4/2 lanes sequences
- geometry programmed using the actual sizes and not registers table

I started the effort, but it's rather delicate and error-prone, as in
example changing the order of registers writes leads to funny results.

I would prefer doing it on top not to delay upstreaming of the driver

> > +	{0x3804, 0x0c}, // x end H
> > +	{0x3805, 0xd3}, // x end L
> > +	{0x3806, 0x09}, // y end H
> > +	{0x3807, 0xa3}, // y end L
> > +	{0x3808, 0x06}, // x output size H
> > +	{0x3809, 0x60}, // x output size L
> > +	{0x380a, 0x04}, // y output size H
> > +	{0x380b, 0xc8}, // y output size L
> > +	{0x380c, 0x07}, // HTS H
> > +	{0x380d, 0x88}, // HTS L
> > +	{0x380e, 0x04}, // VTS H
> > +	{0x380f, 0xdc}, // VTS L
> > +	{0x3810, 0x00}, // ISP x win H
> > +	{0x3811, 0x04}, // ISP x win L
> > +	{0x3813, 0x02}, // ISP y win L
> > +	{0x3814, 0x03}, // x odd inc
> > +	{0x3815, 0x01}, // x even inc
> > +	{0x3820, 0x00}, // vflip off
> > +	{0x3821, 0x67}, // mirror on, bin on
> > +	{0x382a, 0x03}, // y odd inc
> > +	{0x382b, 0x01}, // y even inc
> > +	{0x3830, 0x08}, //
> > +	{0x3836, 0x02}, //
> > +	{0x3837, 0x18}, //
> > +	{0x3841, 0xff}, // window auto size enable
> > +	{0x3846, 0x48}, //
> > +	{0x3d85, 0x16}, // OTP power up load data enable with BIST
> > +	{0x3d8c, 0x73}, // OTP setting start High
> > +	{0x3d8d, 0xde}, // OTP setting start Low
> > +	{0x3f08, 0x08}, //
> > +	{0x3f0a, 0x00}, //
> > +	{0x4000, 0xf1}, // out_range_trig, format_chg_trig
> > +	{0x4001, 0x10}, // total 128 black column
> > +	{0x4005, 0x10}, // BLC target L
> > +	{0x4002, 0x27}, // value used to limit BLC offset
> > +	{0x4009, 0x81}, // final BLC offset limitation enable
> > +	{0x400b, 0x0c}, // DCBLC on, DCBLC manual mode on
> > +	{0x401b, 0x00}, // zero line R coefficient
> > +	{0x401d, 0x00}, // zoro line T coefficient
> > +	{0x4020, 0x00}, // Anchor left start H
> > +	{0x4021, 0x04}, // Anchor left start L
> > +	{0x4022, 0x06}, // Anchor left end H
> > +	{0x4023, 0x00}, // Anchor left end L
> > +	{0x4024, 0x0f}, // Anchor right start H
> > +	{0x4025, 0x2a}, // Anchor right start L
> > +	{0x4026, 0x0f}, // Anchor right end H
> > +	{0x4027, 0x2b}, // Anchor right end L
> > +	{0x4028, 0x00}, // top zero line start
> > +	{0x4029, 0x02}, // top zero line number
> > +	{0x402a, 0x04}, // top black line start
> > +	{0x402b, 0x04}, // top black line number
> > +	{0x402c, 0x00}, // bottom zero line start
> > +	{0x402d, 0x02}, // bottom zoro line number
> > +	{0x402e, 0x04}, // bottom black line start
> > +	{0x402f, 0x04}, // bottom black line number
> > +	{0x401f, 0x00}, // interpolation x/y disable, Anchor one disable
> > +	{0x4034, 0x3f}, //
> > +	{0x403d, 0x04}, // md_precision_en
> > +	{0x4300, 0xff}, // clip max H
> > +	{0x4301, 0x00}, // clip min H
> > +	{0x4302, 0x0f}, // clip min L, clip max L
> > +	{0x4316, 0x00}, //
> > +	{0x4500, 0x58}, //
> > +	{0x4503, 0x18}, //
> > +	{0x4600, 0x00}, //
> > +	{0x4601, 0xcb}, //
> > +	{0x481f, 0x32}, // clk prepare min
> > +	{0x4837, 0x16}, // global timing
> > +	{0x4850, 0x10}, // lane 1 = 1, lane 0 = 0
> > +	{0x4851, 0x32}, // lane 3 = 3, lane 2 = 2
> > +	{0x4b00, 0x2a}, //
> > +	{0x4b0d, 0x00}, //
> > +	{0x4d00, 0x04}, // temperature sensor
> > +	{0x4d01, 0x18}, //
> > +	{0x4d02, 0xc3}, //
> > +	{0x4d03, 0xff}, //
> > +	{0x4d04, 0xff}, //
> > +	{0x4d05, 0xff}, // temperature sensor
> > +	{0x5000, 0xfe}, // lenc on, slave/master AWB gain/statistics enable
> > +	{0x5001, 0x01}, // BLC on
> > +	{0x5002, 0x08}, // H scale off, WBMATCH off, OTP_DPC
> > +	{0x5003, 0x20}, // DPC_DBC buffer control enable, WB
> > +	{0x501e, 0x93}, // enable digital gain
> > +	{0x5046, 0x12}, //
> > +	{0x5780, 0x3e}, // DPC
> > +	{0x5781, 0x0f}, //
> > +	{0x5782, 0x44}, //
> > +	{0x5783, 0x02}, //
> > +	{0x5784, 0x01}, //
> > +	{0x5785, 0x00}, //
> > +	{0x5786, 0x00}, //
> > +	{0x5787, 0x04}, //
> > +	{0x5788, 0x02}, //
> > +	{0x5789, 0x0f}, //
> > +	{0x578a, 0xfd}, //
> > +	{0x578b, 0xf5}, //
> > +	{0x578c, 0xf5}, //
> > +	{0x578d, 0x03}, //
> > +	{0x578e, 0x08}, //
> > +	{0x578f, 0x0c}, //
> > +	{0x5790, 0x08}, //
> > +	{0x5791, 0x04}, //
> > +	{0x5792, 0x00}, //
> > +	{0x5793, 0x52}, //
> > +	{0x5794, 0xa3}, // DPC
> > +	{0x5871, 0x0d}, // Lenc
> > +	{0x5870, 0x18}, //
> > +	{0x586e, 0x10}, //
> > +	{0x586f, 0x08}, //
> > +	{0x58f7, 0x01}, //
> > +	{0x58f8, 0x3d}, // Lenc
> > +	{0x5901, 0x00}, // H skip off, V skip off
> > +	{0x5b00, 0x02}, // OTP DPC start address
> > +	{0x5b01, 0x10}, // OTP DPC start address
> > +	{0x5b02, 0x03}, // OTP DPC end address
> > +	{0x5b03, 0xcf}, // OTP DPC end address
> > +	{0x5b05, 0x6c}, // recover method = 2b11,
> > +	{0x5e00, 0x00}, // use 0x3ff to test pattern off
> > +	{0x5e01, 0x41}, // window cut enable
> > +	{0x382d, 0x7f}, //
> > +	{0x4825, 0x3a}, // lpx_p_min
> > +	{0x4826, 0x40}, // hs_prepare_min
> > +	{0x4808, 0x25}, // wake up delay in 1/1024 s
> > +	{0x3763, 0x18}, //
> > +	{0x3768, 0xcc}, //
> > +	{0x470b, 0x28}, //
> > +	{0x4202, 0x00}, //
> > +	{0x400d, 0x10}, // BLC offset trigger L
> > +	{0x4040, 0x04}, // BLC gain th2
> > +	{0x403e, 0x04}, // BLC gain th1
> > +	{0x4041, 0xc6}, // BLC
> > +	{0x3007, 0x80},
> > +	{0x400a, 0x01},
> > +	{REG_NULL, 0x00},
> > +};
> > +
> > +/*
> > + * Xclk 24Mhz
> > + * max_framerate 30fps
> > + * mipi_datarate per lane 720Mbps
> > + */
> > +static const struct regval ov8858_1632x1224_regs_2lane[] = {
> > +	/*
> > +	 * MIPI=720Mbps, SysClk=144Mhz,Dac Clock=360Mhz.
> > +	 * v00_01_00 (05/29/2014) : initial setting
> > +	 * AM19 : 3617 <- 0xC0
> > +	 * AM20 : change FWC_6K_EN to be default 0x3618=0x5a
> > +	 */
> > +	{0x0100, 0x00},
> > +	{0x3501, 0x4d}, // exposure M
> > +	{0x3502, 0x40}, // exposure L
> > +	{0x3778, 0x17}, //
> > +	{0x3808, 0x06}, // x output size H
> > +	{0x3809, 0x60}, // x output size L
> > +	{0x380a, 0x04}, // y output size H
> > +	{0x380b, 0xc8}, // y output size L
> > +	{0x380c, 0x07}, // HTS H
> > +	{0x380d, 0x88}, // HTS L
> > +	{0x380e, 0x04}, // VTS H
> > +	{0x380f, 0xdc}, // VTS L
> > +	{0x3814, 0x03}, // x odd inc
> > +	{0x3821, 0x67}, // mirror on, bin on
> > +	{0x382a, 0x03}, // y odd inc
> > +	{0x3830, 0x08},
> > +	{0x3836, 0x02},
> > +	{0x3f0a, 0x00},
> > +	{0x4001, 0x10}, // total 128 black column
> > +	{0x4022, 0x06}, // Anchor left end H
> > +	{0x4023, 0x00}, // Anchor left end L
> > +	{0x4025, 0x2a}, // Anchor right start L
> > +	{0x4027, 0x2b}, // Anchor right end L
> > +	{0x402b, 0x04}, // top black line number
> > +	{0x402f, 0x04}, // bottom black line number
> > +	{0x4500, 0x58},
> > +	{0x4600, 0x00},
> > +	{0x4601, 0xcb},
> > +	{0x382d, 0x7f},
> > +	{0x0100, 0x01},
> > +	{REG_NULL, 0x00},
> > +};
> > +
> > +/*
> > + * Xclk 24Mhz
> > + * max_framerate 15fps
> > + * mipi_datarate per lane 720Mbps
> > + */
> > +static const struct regval ov8858_3264x2448_regs_2lane[] = {
> > +	{0x0100, 0x00},
> > +	{0x3501, 0x9a},// exposure M
> > +	{0x3502, 0x20},// exposure L
> > +	{0x3778, 0x1a},//
> > +	{0x3808, 0x0c},// x output size H
> > +	{0x3809, 0xc0},// x output size L
> > +	{0x380a, 0x09},// y output size H
> > +	{0x380b, 0x90},// y output size L
> > +	{0x380c, 0x07},// HTS H
> > +	{0x380d, 0x94},// HTS L
> > +	{0x380e, 0x09},// VTS H
> > +	{0x380f, 0xaa},// VTS L
> > +	{0x3814, 0x01},// x odd inc
> > +	{0x3821, 0x46},// mirror on, bin off
> > +	{0x382a, 0x01},// y odd inc
> > +	{0x3830, 0x06},
> > +	{0x3836, 0x01},
> > +	{0x3f0a, 0x00},
> > +	{0x4001, 0x00},// total 256 black column
> > +	{0x4022, 0x0c},// Anchor left end H
> > +	{0x4023, 0x60},// Anchor left end L
> > +	{0x4025, 0x36},// Anchor right start L
> > +	{0x4027, 0x37},// Anchor right end L
> > +	{0x402b, 0x08},// top black line number
> > +	{0x402f, 0x08},// bottom black line number
> > +	{0x4500, 0x58},
> > +	{0x4600, 0x01},
> > +	{0x4601, 0x97},
> > +	{0x382d, 0xff},
> > +	{REG_NULL, 0x00},
> > +};
> > +
> > +static const struct regval ov8858_global_regs_r2a_4lane[] = {
> > +	/*
> > +	 * MIPI=720Mbps, SysClk=144Mhz,Dac Clock=360Mhz.
> > +	 * v00_01_00 (05/29/2014) : initial setting
> > +	 * AM19 : 3617 <- 0xC0
> > +	 * AM20 : change FWC_6K_EN to be default 0x3618=0x5a
> > +	 */
> > +	{0x0103, 0x01}, // software reset for OVTATool only
> > +	{0x0103, 0x01}, // software reset
> > +	{0x0100, 0x00}, // software standby
> > +	{0x0302, 0x1e}, // pll1_multi
> > +	{0x0303, 0x00}, // pll1_divm
> > +	{0x0304, 0x03}, // pll1_div_mipi
> > +	{0x030e, 0x00}, // pll2_rdiv
> > +	{0x030f, 0x04}, // pll2_divsp
> > +	{0x0312, 0x01}, // pll2_pre_div0, pll2_r_divdac
> > +	{0x031e, 0x0c}, // pll1_no_lat
> > +	{0x3600, 0x00},
> > +	{0x3601, 0x00},
> > +	{0x3602, 0x00},
> > +	{0x3603, 0x00},
> > +	{0x3604, 0x22},
> > +	{0x3605, 0x20},
> > +	{0x3606, 0x00},
> > +	{0x3607, 0x20},
> > +	{0x3608, 0x11},
> > +	{0x3609, 0x28},
> > +	{0x360a, 0x00},
> > +	{0x360b, 0x05},
> > +	{0x360c, 0xd4},
> > +	{0x360d, 0x40},
> > +	{0x360e, 0x0c},
> > +	{0x360f, 0x20},
> > +	{0x3610, 0x07},
> > +	{0x3611, 0x20},
> > +	{0x3612, 0x88},
> > +	{0x3613, 0x80},
> > +	{0x3614, 0x58},
> > +	{0x3615, 0x00},
> > +	{0x3616, 0x4a},
> > +	{0x3617, 0x90},
> > +	{0x3618, 0x5a},
> > +	{0x3619, 0x70},
> > +	{0x361a, 0x99},
> > +	{0x361b, 0x0a},
> > +	{0x361c, 0x07},
> > +	{0x361d, 0x00},
> > +	{0x361e, 0x00},
> > +	{0x361f, 0x00},
> > +	{0x3638, 0xff},
> > +	{0x3633, 0x0f},
> > +	{0x3634, 0x0f},
> > +	{0x3635, 0x0f},
> > +	{0x3636, 0x12},
> > +	{0x3645, 0x13},
> > +	{0x3646, 0x83},
> > +	{0x364a, 0x07},
> > +	{0x3015, 0x01}, //
> > +	{0x3018, 0x72}, // MIPI 4 lane
> > +	{0x3020, 0x93}, // Clock switch output normal, pclk_div =/1
> > +	{0x3022, 0x01}, // pd_mipi enable when rst_sync
> > +	{0x3031, 0x0a}, // MIPI 10-bit mode
> > +	{0x3034, 0x00}, //
> > +	{0x3106, 0x01}, // sclk_div, sclk_pre_div
> > +	{0x3305, 0xf1},
> > +	{0x3308, 0x00},
> > +	{0x3309, 0x28},
> > +	{0x330a, 0x00},
> > +	{0x330b, 0x20},
> > +	{0x330c, 0x00},
> > +	{0x330d, 0x00},
> > +	{0x330e, 0x00},
> > +	{0x330f, 0x40},
> > +	{0x3307, 0x04},
> > +	{0x3500, 0x00}, // exposure H
> > +	{0x3501, 0x4d}, // exposure M
> > +	{0x3502, 0x40}, // exposure L
> > +	{0x3503, 0x80}, // gain delay ?, exposure delay 1 frame, real gain
> > +	{0x3505, 0x80}, // gain option
> > +	{0x3508, 0x02}, // gain H
> > +	{0x3509, 0x00}, // gain L
> > +	{0x350c, 0x00}, // short gain H
> > +	{0x350d, 0x80}, // short gain L
> > +	{0x3510, 0x00}, // short exposure H
> > +	{0x3511, 0x02}, // short exposure M
> > +	{0x3512, 0x00}, // short exposure L
> > +	{0x3700, 0x30},
> > +	{0x3701, 0x18},
> > +	{0x3702, 0x50},
> > +	{0x3703, 0x32},
> > +	{0x3704, 0x28},
> > +	{0x3705, 0x00},
> > +	{0x3706, 0x82},
> > +	{0x3707, 0x08},
> > +	{0x3708, 0x48},
> > +	{0x3709, 0x66},
> > +	{0x370a, 0x01},
> > +	{0x370b, 0x82},
> > +	{0x370c, 0x07},
> > +	{0x3718, 0x14},
> > +	{0x3719, 0x31},
> > +	{0x3712, 0x44},
> > +	{0x3714, 0x24},
> > +	{0x371e, 0x31},
> > +	{0x371f, 0x7f},
> > +	{0x3720, 0x0a},
> > +	{0x3721, 0x0a},
> > +	{0x3724, 0x0c},
> > +	{0x3725, 0x02},
> > +	{0x3726, 0x0c},
> > +	{0x3728, 0x0a},
> > +	{0x3729, 0x03},
> > +	{0x372a, 0x06},
> > +	{0x372b, 0xa6},
> > +	{0x372c, 0xa6},
> > +	{0x372d, 0xa6},
> > +	{0x372e, 0x0c},
> > +	{0x372f, 0x20},
> > +	{0x3730, 0x02},
> > +	{0x3731, 0x0c},
> > +	{0x3732, 0x28},
> > +	{0x3733, 0x10},
> > +	{0x3734, 0x40},
> > +	{0x3736, 0x30},
> > +	{0x373a, 0x0a},
> > +	{0x373b, 0x0b},
> > +	{0x373c, 0x14},
> > +	{0x373e, 0x06},
> > +	{0x3750, 0x0a},
> > +	{0x3751, 0x0e},
> > +	{0x3755, 0x10},
> > +	{0x3758, 0x00},
> > +	{0x3759, 0x4c},
> > +	{0x375a, 0x0c},
> > +	{0x375b, 0x26},
> > +	{0x375c, 0x20},
> > +	{0x375d, 0x04},
> > +	{0x375e, 0x00},
> > +	{0x375f, 0x28},
> > +	{0x3768, 0x22},
> > +	{0x3769, 0x44},
> > +	{0x376a, 0x44},
> > +	{0x3761, 0x00},
> > +	{0x3762, 0x00},
> > +	{0x3763, 0x00},
> > +	{0x3766, 0xff},
> > +	{0x376b, 0x00},
> > +	{0x3772, 0x46},
> > +	{0x3773, 0x04},
> > +	{0x3774, 0x2c},
> > +	{0x3775, 0x13},
> > +	{0x3776, 0x08},
> > +	{0x3777, 0x00},
> > +	{0x3778, 0x17},
> > +	{0x37a0, 0x88},
> > +	{0x37a1, 0x7a},
> > +	{0x37a2, 0x7a},
> > +	{0x37a3, 0x00},
> > +	{0x37a4, 0x00},
> > +	{0x37a5, 0x00},
> > +	{0x37a6, 0x00},
> > +	{0x37a7, 0x88},
> > +	{0x37a8, 0x98},
> > +	{0x37a9, 0x98},
> > +	{0x3760, 0x00},
> > +	{0x376f, 0x01},
> > +	{0x37aa, 0x88},
> > +	{0x37ab, 0x5c},
> > +	{0x37ac, 0x5c},
> > +	{0x37ad, 0x55},
> > +	{0x37ae, 0x19},
> > +	{0x37af, 0x19},
> > +	{0x37b0, 0x00},
> > +	{0x37b1, 0x00},
> > +	{0x37b2, 0x00},
> > +	{0x37b3, 0x84},
> > +	{0x37b4, 0x84},
> > +	{0x37b5, 0x60},
> > +	{0x37b6, 0x00},
> > +	{0x37b7, 0x00},
> > +	{0x37b8, 0x00},
> > +	{0x37b9, 0xff},
> > +	{0x3800, 0x00}, // x start H
> > +	{0x3801, 0x0c}, // x start L
> > +	{0x3802, 0x00}, // y start H
> > +	{0x3803, 0x0c}, // y start L
> > +	{0x3804, 0x0c}, // x end H
> > +	{0x3805, 0xd3}, // x end L
> > +	{0x3806, 0x09}, // y end H
> > +	{0x3807, 0xa3}, // y end L
> > +	{0x3808, 0x06}, // x output size H
> > +	{0x3809, 0x60}, // x output size L
> > +	{0x380a, 0x04}, // y output size H
> > +	{0x380b, 0xc8}, // y output size L
> > +	{0x380c, 0x07}, // HTS H
> > +	{0x380d, 0x88}, // HTS L
> > +	{0x380e, 0x04}, // VTS H
> > +	{0x380f, 0xdc}, // VTS L
> > +	{0x3810, 0x00}, // ISP x win H
> > +	{0x3811, 0x04}, // ISP x win L
> > +	{0x3813, 0x02}, // ISP y win L
> > +	{0x3814, 0x03}, // x odd inc
> > +	{0x3815, 0x01}, // x even inc
> > +	{0x3820, 0x00}, // vflip off
> > +	{0x3821, 0x67}, // mirror on, bin o
> > +	{0x382a, 0x03}, // y odd inc
> > +	{0x382b, 0x01}, // y even inc
> > +	{0x3830, 0x08},
> > +	{0x3836, 0x02},
> > +	{0x3837, 0x18},
> > +	{0x3841, 0xff}, // window auto size enable
> > +	{0x3846, 0x48}, //
> > +	{0x3d85, 0x16}, // OTP power up load data/setting enable
> > +	{0x3d8c, 0x73}, // OTP setting start High
> > +	{0x3d8d, 0xde}, // OTP setting start Low
> > +	{0x3f08, 0x10}, //
> > +	{0x3f0a, 0x00}, //
> > +	{0x4000, 0xf1}, // out_range/format_chg/gain/exp_chg trig enable
> > +	{0x4001, 0x10}, // total 128 black column
> > +	{0x4005, 0x10}, // BLC target L
> > +	{0x4002, 0x27}, // value used to limit BLC offset
> > +	{0x4009, 0x81}, // final BLC offset limitation enable
> > +	{0x400b, 0x0c}, // DCBLC on, DCBLC manual mode on
> > +	{0x401b, 0x00}, // zero line R coefficient
> > +	{0x401d, 0x00}, // zoro line T coefficient
> > +	{0x4020, 0x00}, // Anchor left start H
> > +	{0x4021, 0x04}, // Anchor left start L
> > +	{0x4022, 0x06}, // Anchor left end H
> > +	{0x4023, 0x00}, // Anchor left end L
> > +	{0x4024, 0x0f}, // Anchor right start H
> > +	{0x4025, 0x2a}, // Anchor right start L
> > +	{0x4026, 0x0f}, // Anchor right end H
> > +	{0x4027, 0x2b}, // Anchor right end L
> > +	{0x4028, 0x00}, // top zero line start
> > +	{0x4029, 0x02}, // top zero line number
> > +	{0x402a, 0x04}, // top black line start
> > +	{0x402b, 0x04}, // top black line number
> > +	{0x402c, 0x00}, // bottom zero line start
> > +	{0x402d, 0x02}, // bottom zoro line number
> > +	{0x402e, 0x04}, // bottom black line start
> > +	{0x402f, 0x04}, // bottom black line number
> > +	{0x401f, 0x00}, // interpolation x/y disable, Anchor one disable
> > +	{0x4034, 0x3f},
> > +	{0x403d, 0x04}, // md_precision_en
> > +	{0x4300, 0xff}, // clip max H
> > +	{0x4301, 0x00}, // clip min H
> > +	{0x4302, 0x0f}, // clip min L, clip max L
> > +	{0x4316, 0x00},
> > +	{0x4500, 0x58},
> > +	{0x4503, 0x18},
> > +	{0x4600, 0x00},
> > +	{0x4601, 0xcb},
> > +	{0x481f, 0x32}, // clk prepare min
> > +	{0x4837, 0x16}, // global timing
> > +	{0x4850, 0x10}, // lane 1 = 1, lane 0 = 0
> > +	{0x4851, 0x32}, // lane 3 = 3, lane 2 = 2
> > +	{0x4b00, 0x2a},
> > +	{0x4b0d, 0x00},
> > +	{0x4d00, 0x04}, // temperature sensor
> > +	{0x4d01, 0x18}, //
> > +	{0x4d02, 0xc3}, //
> > +	{0x4d03, 0xff}, //
> > +	{0x4d04, 0xff}, //
> > +	{0x4d05, 0xff}, // temperature sensor
> > +	{0x5000, 0xfe}, // lenc on, slave/master AWB gain/statistics enable
> > +	{0x5001, 0x01}, // BLC on
> > +	{0x5002, 0x08}, // WBMATCH sensor's gain, H scale/WBMATCH/OTP_DPC off
> > +	{0x5003, 0x20}, // DPC_DBC buffer control enable, WB
> > +	{0x501e, 0x93}, // enable digital gain
> > +	{0x5046, 0x12}, //
> > +	{0x5780, 0x3e}, // DPC
> > +	{0x5781, 0x0f}, //
> > +	{0x5782, 0x44}, //
> > +	{0x5783, 0x02}, //
> > +	{0x5784, 0x01}, //
> > +	{0x5785, 0x00}, //
> > +	{0x5786, 0x00}, //
> > +	{0x5787, 0x04}, //
> > +	{0x5788, 0x02}, //
> > +	{0x5789, 0x0f}, //
> > +	{0x578a, 0xfd}, //
> > +	{0x578b, 0xf5}, //
> > +	{0x578c, 0xf5}, //
> > +	{0x578d, 0x03}, //
> > +	{0x578e, 0x08}, //
> > +	{0x578f, 0x0c}, //
> > +	{0x5790, 0x08}, //
> > +	{0x5791, 0x04}, //
> > +	{0x5792, 0x00}, //
> > +	{0x5793, 0x52}, //
> > +	{0x5794, 0xa3}, // DPC
> > +	{0x5871, 0x0d}, // Lenc
> > +	{0x5870, 0x18}, //
> > +	{0x586e, 0x10}, //
> > +	{0x586f, 0x08}, //
> > +	{0x58f7, 0x01}, //
> > +	{0x58f8, 0x3d}, // Lenc
> > +	{0x5901, 0x00}, // H skip off, V skip off
> > +	{0x5b00, 0x02}, // OTP DPC start address
> > +	{0x5b01, 0x10}, // OTP DPC start address
> > +	{0x5b02, 0x03}, // OTP DPC end address
> > +	{0x5b03, 0xcf}, // OTP DPC end address
> > +	{0x5b05, 0x6c}, // recover method = 2b11
> > +	{0x5e00, 0x00}, // use 0x3ff to test pattern off
> > +	{0x5e01, 0x41}, // window cut enable
> > +	{0x382d, 0x7f}, //
> > +	{0x4825, 0x3a}, // lpx_p_min
> > +	{0x4826, 0x40}, // hs_prepare_min
> > +	{0x4808, 0x25}, // wake up delay in 1/1024 s
> > +	{0x3763, 0x18},
> > +	{0x3768, 0xcc},
> > +	{0x470b, 0x28},
> > +	{0x4202, 0x00},
> > +	{0x400d, 0x10}, // BLC offset trigger L
> > +	{0x4040, 0x04}, // BLC gain th2
> > +	{0x403e, 0x04}, // BLC gain th1
> > +	{0x4041, 0xc6}, // BLC
> > +	{0x3007, 0x80},
> > +	{0x400a, 0x01},
> > +	{REG_NULL, 0x00},
> > +};
> > +
> > +/*
> > + * Xclk 24Mhz
> > + * max_framerate 60fps
> > + * mipi_datarate per lane 720Mbps
> > + */
> > +static const struct regval ov8858_1632x1224_regs_4lane[] = {
> > +	{0x0100, 0x00},
> > +	{0x3501, 0x4d}, // exposure M
> > +	{0x3502, 0x40}, // exposure L
> > +	{0x3808, 0x06}, // x output size H
> > +	{0x3809, 0x60}, // x output size L
> > +	{0x380a, 0x04}, // y output size H
> > +	{0x380b, 0xc8}, // y output size L
> > +	{0x380c, 0x07}, // HTS H
> > +	{0x380d, 0x88}, // HTS L
> > +	{0x380e, 0x04}, // VTS H
> > +	{0x380f, 0xdc}, // VTS L
> > +	{0x3814, 0x03}, // x odd inc
> > +	{0x3821, 0x67}, // mirror on, bin on
> > +	{0x382a, 0x03}, // y odd inc
> > +	{0x3830, 0x08},
> > +	{0x3836, 0x02},
> > +	{0x3f0a, 0x00},
> > +	{0x4001, 0x10}, // total 128 black column
> > +	{0x4022, 0x06}, // Anchor left end H
> > +	{0x4023, 0x00}, // Anchor left end L
> > +	{0x4025, 0x2a}, // Anchor right start L
> > +	{0x4027, 0x2b}, // Anchor right end L
> > +	{0x402b, 0x04}, // top black line number
> > +	{0x402f, 0x04}, // bottom black line number
> > +	{0x4500, 0x58},
> > +	{0x4600, 0x00},
> > +	{0x4601, 0xcb},
> > +	{0x382d, 0x7f},
> > +	{0x0100, 0x01},
> > +	{REG_NULL, 0x00},
> > +};
> > +
> > +/*
> > + * Xclk 24Mhz
> > + * max_framerate 30fps
> > + * mipi_datarate per lane 720Mbps
> > + */
> > +static const struct regval ov8858_3264x2448_regs_4lane[] = {
> > +	{0x0100, 0x00},
> > +	{0x3501, 0x9a}, // exposure M
> > +	{0x3502, 0x20}, // exposure L
> > +	{0x3808, 0x0c}, // x output size H
> > +	{0x3809, 0xc0}, // x output size L
> > +	{0x380a, 0x09}, // y output size H
> > +	{0x380b, 0x90}, // y output size L
> > +	{0x380c, 0x07}, // HTS H
> > +	{0x380d, 0x94}, // HTS L
> > +	{0x380e, 0x09}, // VTS H
> > +	{0x380f, 0xaa}, // VTS L
> > +	{0x3814, 0x01}, // x odd inc
> > +	{0x3821, 0x46}, // mirror on, bin off
> > +	{0x382a, 0x01}, // y odd inc
> > +	{0x3830, 0x06},
> > +	{0x3836, 0x01},
> > +	{0x3f0a, 0x00},
> > +	{0x4001, 0x00}, // total 256 black column
> > +	{0x4022, 0x0c}, // Anchor left end H
> > +	{0x4023, 0x60}, // Anchor left end L
> > +	{0x4025, 0x36}, // Anchor right start L
> > +	{0x4027, 0x37}, // Anchor right end L
> > +	{0x402b, 0x08}, // top black line number
> > +	{0x402f, 0x08}, // interpolation x/y disable, Anchor one disable
> > +	{0x4500, 0x58},
> > +	{0x4600, 0x01},
> > +	{0x4601, 0x97},
> > +	{0x382d, 0xff},
> > +	{REG_NULL, 0x00},
> > +};
> > +
> > +static const struct ov8858_mode ov8858_modes[] = {
> > +	{
> > +		.width = 3264,
> > +		.height = 2448,
> > +		.exp_def = 2464,
> > +		.hts_def = 1940 * 2,
> > +		.vts_def = 2472,
> > +		.reg_modes = {
> > +			.mode_2lanes = ov8858_3264x2448_regs_2lane,
> > +			.mode_4lanes = ov8858_3264x2448_regs_4lane,
> > +		},
> > +	},
> > +	{
> > +		.width = 1632,
> > +		.height = 1224,
> > +		.exp_def = 1232,
> > +		.hts_def = 1928 * 2,
>
> There was no * 2 in v1, is this correct ?
>

No there was not, not in the 1632x1224 mode.
However it is needed to have the frame duration calculations correct:

                pixel_rate = 288Mhz (derived from link_freq)
                frame duration = hts_df * vts_def / 288
                               = 1928 * 2 * 1244 / 288 = 16.6 msec

and the mode actually runs at 60FPS in 4 lanes mode and 30 FPS in 2
lanes mode where the pixel rate is halved to 144Mhz.

I decided to keep the * 2 as it make it easier to locate the HTS value
(1928(10) = 788(16)) in the registers tables

	{0x380c, 0x07}, // HTS H
	{0x380d, 0x88}, // HTS L

The register documentation does not report the HTS register unit, so I
decided to belive it has to be set to twice the number of pclk cycles
as I've seen other sensors doing the same

> > +		.vts_def = 1244,
> > +		.reg_modes = {
> > +			.mode_2lanes = ov8858_1632x1224_regs_2lane,
> > +			.mode_4lanes = ov8858_1632x1224_regs_4lane,
> > +		},
> > +	},
> > +};
>
> Blank line.
>
> > +#define OV8858_NUM_MODES ARRAY_SIZE(ov8858_modes)
> > +
> > +static const s64 link_freq_menu_items[] = {
> > +	OV8858_LINK_FREQ
> > +};
> > +
> > +static const char * const ov8858_test_pattern_menu[] = {
> > +	"Disabled",
> > +	"Vertical Color Bar Type 1",
> > +	"Vertical Color Bar Type 2",
> > +	"Vertical Color Bar Type 3",
> > +	"Vertical Color Bar Type 4"
> > +};
> > +
> > +static int ov8858_write_reg(struct i2c_client *client, u32 reg, u32 val,
> > +			    int *err)
>
> I'd name this function ov8858_write() (same for the read) to make line
> shorter, I think it's equally readable.
>

ack

> > +{
> > +	unsigned int len = (reg >> OV8858_REG_SIZE_SHIFT) & 3;
> > +	u16 addr = reg & OV8858_REG_ADDR_MASK;
> > +	u8 buf[6];
> > +	int ret;
> > +
> > +	if (err && *err)
> > +		return *err;
> > +
> > +	put_unaligned_be16(addr, buf);
> > +	put_unaligned_be32(val << (8 * (4 - len)), buf + 2);
> > +
> > +	ret = i2c_master_send(client, buf, len + 2);
> > +	if (ret != len + 2) {
> > +		if (err)
> > +			*err = ret < 0 ? ret : -EIO;
> > +		return ret < 0 ? ret : -EIO;
>
> Maybe
>
> 		ret = ret < 0 ? ret : -EIO;
> 		if (err)
> 			*err = ret;
> 		return ret;
>
> to avoid repeating the same code.
>
> I would also print an error message here, it's good to be informed of
> errors. Same for reads.
>

ack

> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int ov8858_write_array(struct i2c_client *client, const struct regval *regs)
> > +{
> > +	unsigned int i;
> > +	int ret = 0;
> > +
> > +	for (i = 0; ret == 0 && regs[i].addr != REG_NULL; ++i)
>
> Curly braces.
>
> > +		ov8858_write_reg(client, OV8858_REG_8BIT(regs[i].addr),
> > +				 regs[i].val, &ret);
> > +
> > +	return ret;
> > +}
> > +
> > +static int ov8858_read_reg(struct i2c_client *client, u32 reg, u32 *val)
> > +{
> > +	__be16 reg_addr_be = cpu_to_be16(reg & OV8858_REG_ADDR_MASK);
> > +	unsigned int len = (reg >> OV8858_REG_SIZE_SHIFT) & 3;
> > +	struct i2c_msg msgs[2];
> > +	__be32 data_be = 0;
> > +	u8 *data_be_p;
>
> 	u8 *data_be_p = (u8 *)&data_be;
>
> > +	int ret;
> > +
> > +	data_be_p = (u8 *)&data_be;
>
> and drop this line.
>
> > +	/* Write register address */
> > +	msgs[0].addr = client->addr;
> > +	msgs[0].flags = 0;
> > +	msgs[0].len = 2;
> > +	msgs[0].buf = (u8 *)&reg_addr_be;
> > +
> > +	/* Read data from register */
> > +	msgs[1].addr = client->addr;
> > +	msgs[1].flags = I2C_M_RD;
> > +	msgs[1].len = len;
> > +	msgs[1].buf = &data_be_p[4 - len];
> > +
> > +	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
> > +	if (ret != ARRAY_SIZE(msgs))
> > +		return -EIO;
>
> 		return ret < 0 ? ret : -EIO;
>
> to preserve the error.
>
> > +
> > +	*val = be32_to_cpu(data_be);
> > +
> > +	return 0;
> > +}
> > +
> > +static int ov8858_set_fmt(struct v4l2_subdev *sd,
> > +			  struct v4l2_subdev_state *state,
> > +			  struct v4l2_subdev_format *fmt)
>
> Could you move this function down after ov8858_enum_frame_sizes() to
> have the same ordering as in ov8858_pad_ops ?
>

I think I asked the same to Nicholas in his previous version then I
forgot to actually do so :)

> > +{
> > +	struct ov8858 *ov8858 = to_ov8858(sd);
> > +	struct v4l2_mbus_framefmt *format;
> > +	const struct ov8858_mode *mode;
> > +	s64 h_blank, vblank_def;
> > +
> > +	mode = v4l2_find_nearest_size(ov8858_modes, OV8858_NUM_MODES,
> > +				      width, height, fmt->format.width,
> > +				      fmt->format.height);
> > +
> > +	fmt->format.code = MEDIA_BUS_FMT_SBGGR10_1X10;
> > +	fmt->format.width = mode->width;
> > +	fmt->format.height = mode->height;
> > +	fmt->format.field = V4L2_FIELD_NONE;
> > +
> > +	format = v4l2_subdev_get_pad_format(sd, state, 0);
> > +
> > +	if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
> > +		ov8858->cur_mode = mode;
> > +		h_blank = mode->hts_def - mode->width;
> > +		__v4l2_ctrl_modify_range(ov8858->hblank, h_blank, h_blank, 1,
> > +					 h_blank);
> > +
> > +		vblank_def = mode->vts_def - mode->height;
> > +		__v4l2_ctrl_modify_range(ov8858->vblank, vblank_def,
> > +					 OV8858_VTS_MAX - mode->height, 1,
> > +					 vblank_def);
> > +	}
> > +
> > +	*format = fmt->format;
> > +
> > +	return 0;
> > +}
> > +
> > +static int ov8858_enum_mbus_code(struct v4l2_subdev *sd,
> > +				 struct v4l2_subdev_state *state,
> > +				 struct v4l2_subdev_mbus_code_enum *code)
> > +{
> > +	if (code->index != 0)
> > +		return -EINVAL;
> > +
> > +	code->code = MEDIA_BUS_FMT_SBGGR10_1X10;
> > +
> > +	return 0;
> > +}
> > +
> > +static int ov8858_enum_frame_sizes(struct v4l2_subdev *sd,
> > +				   struct v4l2_subdev_state *state,
> > +				   struct v4l2_subdev_frame_size_enum *fse)
> > +{
> > +	if (fse->index >= OV8858_NUM_MODES)
> > +		return -EINVAL;
> > +
> > +	if (fse->code != MEDIA_BUS_FMT_SBGGR10_1X10)
> > +		return -EINVAL;
> > +
> > +	fse->min_width  = ov8858_modes[fse->index].width;
> > +	fse->max_width  = ov8858_modes[fse->index].width;
> > +	fse->max_height = ov8858_modes[fse->index].height;
> > +	fse->min_height = ov8858_modes[fse->index].height;
> > +
> > +	return 0;
> > +}
> > +
> > +static int ov8858_enable_test_pattern(struct ov8858 *ov8858, u32 pattern)
>
> Move this down just before ov8858_set_ctrl() to keep functions related
> to controls together.
>
> > +{
> > +	u32 val;
> > +
> > +	if (pattern)
> > +		val = (pattern - 1) | OV8858_TEST_PATTERN_ENABLE;
> > +	else
> > +		val = OV8858_TEST_PATTERN_DISABLE;
> > +
> > +	return ov8858_write_reg(ov8858->client, OV8858_REG_TEST_PATTERN,
> > +				val, NULL);
> > +}
> > +
> > +static int ov8858_start_stream(struct ov8858 *ov8858)
> > +{
> > +	const struct regval *reg_list;
> > +	int ret;
> > +
> > +	ret = ov8858_write_array(ov8858->client, ov8858->global_regs);
> > +	if (ret)
> > +		return ret;
> > +
> > +	if (ov8858->num_lanes == 4)
> > +		reg_list = ov8858->cur_mode->reg_modes.mode_4lanes;
> > +	else
> > +		reg_list = ov8858->cur_mode->reg_modes.mode_2lanes;
> > +
> > +	ret = ov8858_write_array(ov8858->client, reg_list);
> > +	if (ret)
> > +		return ret;
> > +
> > +	/* 200 usec max to have PLL stabilize. */
> > +	fsleep(200);
> > +
> > +	ret = __v4l2_ctrl_handler_setup(&ov8858->ctrl_handler);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = ov8858_write_reg(ov8858->client, OV8858_REG_CTRL_MODE,
> > +			       OV8858_MODE_STREAMING, NULL);
> > +	if (ret)
> > +		return ret;
> > +
> > +	/* t5 (fixed) = 10msec before entering streaming state */
> > +	fsleep(10000);
> > +
> > +	return 0;
> > +}
> > +
> > +static int ov8858_stop_stream(struct ov8858 *ov8858)
> > +{
> > +	return ov8858_write_reg(ov8858->client, OV8858_REG_CTRL_MODE,
> > +				OV8858_MODE_SW_STANDBY, NULL);
> > +}
> > +
> > +static int ov8858_s_stream(struct v4l2_subdev *sd, int on)
> > +{
> > +	struct ov8858 *ov8858 = to_ov8858(sd);
> > +	struct i2c_client *client = ov8858->client;
> > +	struct v4l2_subdev_state *state;
> > +	int ret = 0;
> > +
> > +	/* Only for locking purpose. */
> > +	state = v4l2_subdev_lock_and_get_active_state(sd);
>
> You could also find the mode here from the width and height stored in
> the state, to avoid storing cur_mode in the ov8858 structure, but that's
> not required.
>

I'll see how complex is to get rid of cur_mode

> > +
> > +	if (!!on == ov8858->streaming)
> > +		goto unlock_and_return;
>
> This can't happen, so you can drop this and the streaming flag, as well
> as the goto below and the error label.
>

That's something that always puzzles me: is s_stream() refcounted by
the core ?

> > +
> > +	if (on) {
> > +		ret = pm_runtime_resume_and_get(&client->dev);
> > +		if (ret < 0)
> > +			goto unlock_and_return;
> > +
> > +		ret = ov8858_start_stream(ov8858);
> > +		if (ret) {
> > +			dev_err(&client->dev, "Failed to start streaming\n");
> > +			pm_runtime_put(&client->dev);
>
> Please use runtime PM autosuspend to avoid costly power down/up
> sequences when reconfiguring the sensor. See
> https://git.linuxtv.org/pinchartl/media.git/commit/?h=imx290&id=4da70926308ffa71c059fe06027962463e54b930.
>

ah nicer

> > +			goto unlock_and_return;
> > +		}
> > +	} else {
> > +		ov8858_stop_stream(ov8858);
> > +		pm_runtime_put(&client->dev);
> > +	}
> > +
> > +	ov8858->streaming = !!on;
> > +
> > +unlock_and_return:
> > +	v4l2_subdev_unlock_state(state);
> > +
> > +	return ret;
> > +}
> > +
> > +static int ov8858_power_on(struct ov8858 *ov8858)
> > +{
> > +	struct device *dev = &ov8858->client->dev;
> > +	unsigned long delay_us;
> > +	int ret;
> > +
> > +	ret = clk_set_rate(ov8858->xvclk, OV8858_XVCLK_FREQ);
> > +	if (ret < 0)
> > +		dev_warn(dev, "Failed to set xvclk rate (24MHz)\n");
> > +	if (clk_get_rate(ov8858->xvclk) != OV8858_XVCLK_FREQ)
> > +		dev_warn(dev, "xvclk mismatched, modes are based on 24MHz\n");
> > +
> > +	ret = clk_prepare_enable(ov8858->xvclk);
> > +	if (ret < 0) {
> > +		dev_err(dev, "Failed to enable xvclk\n");
> > +		return ret;
> > +	}
> > +
> > +	ret = regulator_bulk_enable(OV8858_NUM_SUPPLIES, ov8858->supplies);
> > +	if (ret < 0) {
> > +		dev_err(dev, "Failed to enable regulators\n");
> > +		goto disable_clk;
> > +	}
> > +
> > +	/*
> > +	 * The chip manual only suggests 8192 cycles prior to first SCCB
> > +	 * transaction, but a double sleep between the release of gpios
> > +	 * helps with sporadic failures observed at probe time.
> > +	 */
> > +	delay_us = DIV_ROUND_UP(8192, OV8858_XVCLK_FREQ / 1000 / 1000);
> > +
> > +	gpiod_set_value_cansleep(ov8858->reset_gpio, 0);
> > +	fsleep(delay_us);
> > +	gpiod_set_value_cansleep(ov8858->pwdn_gpio, 0);
> > +	fsleep(delay_us);
> > +
> > +	return 0;
> > +
> > +disable_clk:
> > +	clk_disable_unprepare(ov8858->xvclk);
> > +
> > +	return ret;
> > +}
> > +
> > +static void ov8858_power_off(struct ov8858 *ov8858)
> > +{
> > +	gpiod_set_value_cansleep(ov8858->pwdn_gpio, 1);
> > +	clk_disable_unprepare(ov8858->xvclk);
> > +	gpiod_set_value_cansleep(ov8858->reset_gpio, 1);
> > +
> > +	regulator_bulk_disable(OV8858_NUM_SUPPLIES, ov8858->supplies);
> > +}
> > +
> > +static int ov8858_runtime_resume(struct device *dev)
> > +{
> > +	struct i2c_client *client = to_i2c_client(dev);
> > +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> > +	struct ov8858 *ov8858 = to_ov8858(sd);
> > +
> > +	return ov8858_power_on(ov8858);
> > +}
> > +
> > +static int ov8858_runtime_suspend(struct device *dev)
> > +{
> > +	struct i2c_client *client = to_i2c_client(dev);
> > +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> > +	struct ov8858 *ov8858 = to_ov8858(sd);
> > +
> > +	ov8858_power_off(ov8858);
> > +
> > +	return 0;
> > +}
>
> Could you move those four functions in a separate section ? They're in
> the middle of the subdev operations, but they're not related. I like
> section delimiters, for instance
>
> /* -----------------------------------------------------------------------------
>  * Power Management
>  */
>
> as it makes it easier to navigate the code in my opinion, but that's not
> a hard requirement.
>
> > +
> > +static int ov8858_init_cfg(struct v4l2_subdev *sd,
> > +			   struct v4l2_subdev_state *sd_state)
> > +{
> > +	struct ov8858 *ov8858 = to_ov8858(sd);
> > +	const struct ov8858_mode *def_mode = ov8858->cur_mode;
> > +	struct v4l2_subdev_format fmt = {
> > +		.which = V4L2_SUBDEV_FORMAT_TRY,
> > +		.format = {
> > +			.width = def_mode->width,
> > +			.height = def_mode->height,
> > +		},
> > +	};
> > +
> > +	ov8858_set_fmt(sd, sd_state, &fmt);
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct v4l2_subdev_video_ops ov8858_video_ops = {
> > +	.s_stream = ov8858_s_stream,
> > +};
> > +
> > +static const struct v4l2_subdev_pad_ops ov8858_pad_ops = {
> > +	.init_cfg = ov8858_init_cfg,
> > +	.enum_mbus_code = ov8858_enum_mbus_code,
> > +	.enum_frame_size = ov8858_enum_frame_sizes,
> > +	.get_fmt = v4l2_subdev_get_fmt,
> > +	.set_fmt = ov8858_set_fmt,
> > +};
> > +
> > +static const struct v4l2_subdev_ops ov8858_subdev_ops = {
> > +	.video	= &ov8858_video_ops,
> > +	.pad	= &ov8858_pad_ops,
> > +};
> > +
> > +static int ov8858_set_ctrl(struct v4l2_ctrl *ctrl)
> > +{
> > +	struct ov8858 *ov8858 = container_of(ctrl->handler,
> > +					     struct ov8858, ctrl_handler);
> > +	struct i2c_client *client = ov8858->client;
> > +	s64 max_exp;
> > +	int ret;
> > +
> > +	/* Propagate change of current control to all related controls */
> > +	switch (ctrl->id) {
> > +	case V4L2_CID_VBLANK:
> > +		/* Update max exposure while meeting expected vblanking */
> > +		max_exp = ov8858->cur_mode->height + ctrl->val - 4;
>
> A macro for the offset (4) would be useful, as you also use it in
> ov8858_initialize_controls().
>
> If you decide to drop cur_mode (or even if you keep it, to minimize
> dependencies on it), I would instead retrieve the height from the active
> state.
>
> One idea I'm toying with is to store control values in the state, which
> would make it possibly to use TRY states to try combinations of formats,
> selection rectangles and controls on subdevs. Using the state here
> already would help with that in the future.
>
> > +		__v4l2_ctrl_modify_range(ov8858->exposure,
> > +					 ov8858->exposure->minimum, max_exp,
> > +					 ov8858->exposure->step,
> > +					 ov8858->exposure->default_value);
> > +		break;
> > +	}
> > +
> > +	if (!pm_runtime_get_if_in_use(&client->dev))
> > +		return 0;
> > +
> > +	switch (ctrl->id) {
> > +	case V4L2_CID_EXPOSURE:
> > +		/* 4 least significant bits of exposure are fractional part */
> > +		ret = ov8858_write_reg(ov8858->client, OV8858_REG_EXPOSURE,
> > +				       ctrl->val << 4, NULL);
> > +		break;
> > +	case V4L2_CID_ANALOGUE_GAIN:
> > +		ret = 0;
> > +		ov8858_write_reg(ov8858->client, OV8858_REG_GAIN_H,
> > +				 (ctrl->val >> OV8858_GAIN_H_SHIFT) &
> > +				 OV8858_GAIN_H_MASK, &ret);
> > +		ov8858_write_reg(ov8858->client,
> > +				 OV8858_REG_GAIN_L,
> > +				 ctrl->val & OV8858_GAIN_L_MASK, &ret);
> > +		break;
> > +	case V4L2_CID_DIGITAL_GAIN:
> > +		ret = 0;
> > +		ov8858_write_reg(ov8858->client, OV8858_REG_DGAIN_H,
> > +				 (ctrl->val >> OV8858_DGAIN_H_SHIFT) &
> > +				 OV8858_DGAIN_H_MASK, &ret);
> > +		ov8858_write_reg(ov8858->client, OV8858_REG_DGAIN_L,
> > +				 ctrl->val & OV8858_DGAIN_L_MASK, &ret);
> > +		break;
> > +	case V4L2_CID_VBLANK:
> > +		ret = ov8858_write_reg(ov8858->client, OV8858_REG_VTS,
> > +				       ctrl->val + ov8858->cur_mode->height,
> > +				       NULL);
> > +		break;
> > +	case V4L2_CID_TEST_PATTERN:
> > +		ret = ov8858_enable_test_pattern(ov8858, ctrl->val);
> > +		break;
> > +	default:
> > +		ret = -EINVAL;
> > +		dev_warn(&client->dev, "%s Unhandled id: 0x%x\n",
> > +			 __func__, ctrl->id);
> > +		break;
> > +	}
> > +
> > +	return ret;
> > +}
> > +
> > +static const struct v4l2_ctrl_ops ov8858_ctrl_ops = {
> > +	.s_ctrl = ov8858_set_ctrl,
> > +};
> > +
> > +static int ov8858_initialize_controls(struct ov8858 *ov8858)
>
> Other functions or structures use ctrl, maybe ov8858_init_ctrls() ?
>
> > +{
> > +	struct v4l2_fwnode_device_properties props;
> > +	struct v4l2_ctrl_handler *handler;
> > +	const struct ov8858_mode *mode;
> > +	s64 exposure_max, vblank_def;
> > +	struct v4l2_ctrl *ctrl;
> > +	u32 h_blank;
> > +	int ret;
> > +
> > +	handler = &ov8858->ctrl_handler;
> > +	mode = ov8858->cur_mode;
>
> You can initialize those two variables when declaring them.
>
> I'd use &ov8858_modes[0] here, again to avoid depending on cur_mode.
>
> > +	ret = v4l2_ctrl_handler_init(handler, 10);
> > +	if (ret)
> > +		return ret;
> > +	handler->lock = &ov8858->mutex;
> > +
> > +	ctrl = v4l2_ctrl_new_int_menu(handler, NULL, V4L2_CID_LINK_FREQ,
> > +				      0, 0, link_freq_menu_items);
> > +	if (ctrl)
> > +		ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> > +
> > +	v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE,
> > +			  0, ov8858->pixel_rate, 1, ov8858->pixel_rate);
> > +
> > +	h_blank = mode->hts_def - mode->width;
> > +	ov8858->hblank = v4l2_ctrl_new_std(handler, NULL, V4L2_CID_HBLANK,
> > +					   h_blank, h_blank, 1, h_blank);
> > +	if (ov8858->hblank)
> > +		ov8858->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> > +
> > +	vblank_def = mode->vts_def - mode->height;
> > +	ov8858->vblank = v4l2_ctrl_new_std(handler, &ov8858_ctrl_ops,
> > +					   V4L2_CID_VBLANK, vblank_def,
> > +					   OV8858_VTS_MAX - mode->height,
> > +					   1, vblank_def);
> > +
> > +	exposure_max = mode->vts_def - 4;
> > +	ov8858->exposure = v4l2_ctrl_new_std(handler, &ov8858_ctrl_ops,
> > +					     V4L2_CID_EXPOSURE,
> > +					     OV8858_EXPOSURE_MIN,
> > +					     exposure_max, OV8858_EXPOSURE_STEP,
> > +					     mode->exp_def);
> > +
> > +	v4l2_ctrl_new_std(handler, &ov8858_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
> > +			  OV8858_GAIN_MIN, OV8858_GAIN_MAX, OV8858_GAIN_STEP,
> > +			  OV8858_GAIN_DEFAULT);
> > +
> > +	v4l2_ctrl_new_std(handler, &ov8858_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
> > +			  OV8858_DGAIN_MIN, OV8858_DGAIN_MAX, OV8858_DGAIN_STEP,
> > +			  OV8858_DGAIN_DEFAULT);
> > +
> > +	v4l2_ctrl_new_std_menu_items(handler, &ov8858_ctrl_ops,
> > +				     V4L2_CID_TEST_PATTERN,
> > +				     ARRAY_SIZE(ov8858_test_pattern_menu) - 1,
> > +				     0, 0, ov8858_test_pattern_menu);
> > +
> > +	if (handler->error) {
> > +		ret = handler->error;
> > +		goto err_free_handler;
> > +	}
> > +
> > +	ret = v4l2_fwnode_device_parse(&ov8858->client->dev, &props);
> > +	if (ret)
> > +		goto err_free_handler;
> > +
> > +	ret = v4l2_ctrl_new_fwnode_properties(handler, &ov8858_ctrl_ops,
> > +					      &props);
> > +	if (ret)
> > +		goto err_free_handler;
> > +
> > +	ov8858->subdev.ctrl_handler = handler;
> > +
> > +	return 0;
> > +
> > +err_free_handler:
> > +	dev_err(&ov8858->client->dev, "Failed to init controls (%d)\n", ret);
> > +	v4l2_ctrl_handler_free(handler);
> > +
> > +	return ret;
> > +}
> > +
> > +static int ov8858_check_sensor_id(struct ov8858 *ov8858,
> > +				  struct i2c_client *client)
> > +{
> > +	struct device *dev = &ov8858->client->dev;
> > +	u32 id = 0;
> > +	int ret;
> > +
> > +	ret = ov8858_read_reg(client, OV8858_REG_CHIP_ID, &id);
>
> 	if (ret)
> 		return ret;
>
> No need for an error message if you add one to ov8858_read_reg().
>
> > +	if (id != OV8858_CHIP_ID) {
> > +		dev_err(dev, "Unexpected sensor id(%06x), ret(%d)\n", id, ret);
>
> 		dev_err(dev, "Unexpected sensor ID %06x\n", id);
>
> > +		return ret;
> > +	}
> > +
> > +	ret = ov8858_read_reg(client, OV8858_CHIP_REVISION_REG, &id);
> > +	if (ret) {
> > +		dev_err(dev, "Read chip revision register error\n");
>
> You can drop this if you add an error message in ov8858_read_reg().
>
> > +		return ret;
> > +	}
> > +
> > +	dev_info(dev, "Detected OV%06x sensor, REVISION 0x%x\n",
> > +		 OV8858_CHIP_ID, id);
>
> 	dev_info(dev, "Detected OV8858 sensor, REVISION 0x%x\n", id);
>
> as OV8858_CHIP_ID is hardcoded. I think I'd make this a debug message
> though, to avoid too many messages in the kernel log at boot time. Up to
> you.
>
> > +
> > +	if (id == OV8858_R2A) {
> > +		ov8858->global_regs = ov8858->num_lanes == 4
> > +				    ? ov8858_global_regs_r2a_4lane
> > +				    : ov8858_global_regs_r2a_2lane;
> > +	} else {
> > +		ov8858->global_regs = ov8858_global_regs_r1a;
> > +		dev_warn(dev, "R1A may not work well!\n");
>
> As you mention in your cover letter I may be tempted to drop this too,
> but I have no idea if there are such sensors in the wild.
>

I have no idea, that's why I refrained from removing it.

> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int ov8858_configure_regulators(struct ov8858 *ov8858)
> > +{
> > +	unsigned int i;
> > +
> > +	for (i = 0; i < OV8858_NUM_SUPPLIES; i++)
> > +		ov8858->supplies[i].supply = ov8858_supply_names[i];
> > +
> > +	return devm_regulator_bulk_get(&ov8858->client->dev,
> > +				       OV8858_NUM_SUPPLIES,
> > +				       ov8858->supplies);
> > +}
> > +
> > +static int ov8858_parse_of(struct ov8858 *ov8858)
> > +{
> > +	struct v4l2_fwnode_endpoint vep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
> > +	struct device *dev = &ov8858->client->dev;
> > +	struct fwnode_handle *endpoint;
> > +	int ret;
> > +
> > +	endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL);
> > +	if (!endpoint) {
> > +		dev_err(dev, "Failed to get endpoint\n");
> > +		return -EINVAL;
> > +	}
> > +
> > +	ret = v4l2_fwnode_endpoint_parse(endpoint, &vep);
> > +	if (ret) {
> > +		dev_err(dev, "Failed to parse endpoint: %d\n", ret);
> > +		fwnode_handle_put(endpoint);
> > +		return ret;
> > +	}
> > +
> > +	ov8858->num_lanes = vep.bus.mipi_csi2.num_data_lanes;
> > +	switch (ov8858->num_lanes) {
> > +	case 4:
> > +		fallthrough;
>
> Not needed.
>

Isn't it ?

> > +	case 2:
> > +		break;
> > +	default:
> > +		dev_err(dev, "Unsupported number of data lanes %u\n",
> > +			ov8858->num_lanes);
> > +		fwnode_handle_put(endpoint);
> > +		return -EINVAL;
> > +	}
> > +
> > +	/* pixel rate = link frequency * 2 * lanes / bpp */
> > +	ov8858->cur_mode = &ov8858_modes[0];
> > +	ov8858->pixel_rate = OV8858_LINK_FREQ * 2U * ov8858->num_lanes / 10U;
>
> 2 and 10 ?
>

2 is CSI-2 DDR mode
10 is the single supported format bpp

> > +	ov8858->subdev.fwnode = endpoint;
> > +
> > +	dev_dbg(dev, "num_lanes = %u - pixel_rate = %u\n",
>
> s/ - /, /
>
> > +		ov8858->num_lanes, ov8858->pixel_rate);
> > +
> > +	return 0;
> > +}
> > +
> > +static int ov8858_probe(struct i2c_client *client)
> > +{
> > +	struct device *dev = &client->dev;
> > +	struct v4l2_subdev *sd;
> > +	struct ov8858 *ov8858;
> > +	int ret;
> > +
> > +	ov8858 = devm_kzalloc(dev, sizeof(*ov8858), GFP_KERNEL);
> > +	if (!ov8858)
> > +		return -ENOMEM;
> > +
> > +	ov8858->client = client;
> > +
> > +	ov8858->xvclk = devm_clk_get(dev, "xvclk");
> > +	if (IS_ERR(ov8858->xvclk))
> > +		return dev_err_probe(dev, PTR_ERR(ov8858->xvclk),
> > +				     "failed to get xvclk\n");
> > +
> > +	ov8858->reset_gpio = devm_gpiod_get_optional(dev, "reset",
> > +						     GPIOD_OUT_HIGH);
> > +	if (IS_ERR(ov8858->reset_gpio))
> > +		return dev_err_probe(dev, PTR_ERR(ov8858->reset_gpio),
> > +				     "failed to get reset gpio\n");
> > +
> > +	ov8858->pwdn_gpio = devm_gpiod_get_optional(dev, "powerdown",
> > +						    GPIOD_OUT_HIGH);
> > +	if (IS_ERR(ov8858->pwdn_gpio))
> > +		return dev_err_probe(dev, PTR_ERR(ov8858->pwdn_gpio),
> > +				     "failed to get powerdown gpio\n");
> > +
> > +	ret = ov8858_configure_regulators(ov8858);
> > +	if (ret)
> > +		return dev_err_probe(dev, ret, "failed to get regulators\n");
> > +
> > +	sd = &ov8858->subdev;
> > +	v4l2_i2c_subdev_init(sd, client, &ov8858_subdev_ops);
> > +
> > +	ret = ov8858_parse_of(ov8858);
> > +	if (ret != 0)
>
> 	if (ret)
>
> > +		return ret;
> > +
> > +	ret = ov8858_initialize_controls(ov8858);
> > +	if (ret)
>
> You're leaking ov8858->subdev.fwnode here. In the remove handler too
> actually.
>

Ah yes, I'll make sure I'll put the node even if I really dislike that
it is get in one function and put in another

> > +		return ret;
> > +
> > +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> > +	ov8858->pad.flags = MEDIA_PAD_FL_SOURCE;
> > +	sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
> > +	ret = media_entity_pads_init(&sd->entity, 1, &ov8858->pad);
> > +	if (ret < 0)
> > +		goto err_free_handler;
> > +
> > +	sd->state_lock = &ov8858->mutex;
> > +	ret = v4l2_subdev_init_finalize(sd);
> > +	if (ret < 0) {
> > +		dev_err(&client->dev, "subdev initialization error %d\n", ret);
> > +		goto err_clean_entity;
> > +	}
> > +
> > +	ret = ov8858_power_on(ov8858);
> > +	if (ret)
> > +		goto err_clean_entity;
> > +
> > +	ret = ov8858_check_sensor_id(ov8858, client);
> > +	if (ret)
> > +		goto err_power_off;
> > +
> > +	ret = v4l2_async_register_subdev_sensor(sd);
> > +	if (ret) {
> > +		dev_err(dev, "v4l2 async register subdev failed\n");
> > +		goto err_power_off;
> > +	}
> > +
> > +	pm_runtime_set_active(dev);
> > +	pm_runtime_enable(dev);
> > +	pm_runtime_idle(dev);
>
> This needs to go before v4l2_async_register_subdev_sensor() or there's a
> risk someone will start using the sensor before runtime PM is enabled.
> See
> https://git.linuxtv.org/pinchartl/media.git/commit/?h=imx290&id=4da70926308ffa71c059fe06027962463e54b930
> for the required ordering.
>

Thanks, it's indeed more correct

> > +
> > +	return 0;
> > +
> > +err_power_off:
> > +	ov8858_power_off(ov8858);
> > +err_clean_entity:
> > +	media_entity_cleanup(&sd->entity);
> > +err_free_handler:
> > +	v4l2_ctrl_handler_free(&ov8858->ctrl_handler);
> > +
> > +	return ret;
> > +}
> > +
> > +static void ov8858_remove(struct i2c_client *client)
> > +{
> > +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> > +	struct ov8858 *ov8858 = to_ov8858(sd);
> > +
> > +	v4l2_async_unregister_subdev(sd);
> > +	media_entity_cleanup(&sd->entity);
> > +	v4l2_ctrl_handler_free(&ov8858->ctrl_handler);
> > +
> > +	pm_runtime_disable(&client->dev);
> > +	if (!pm_runtime_status_suspended(&client->dev))
> > +		ov8858_power_off(ov8858);
> > +	pm_runtime_set_suspended(&client->dev);
> > +}
> > +
> > +static const struct of_device_id ov8858_of_match[] = {
> > +	{ .compatible = "ovti,ov8858" },
> > +	{ /* sentinel */ },
> > +};
> > +MODULE_DEVICE_TABLE(of, ov8858_of_match);
> > +
> > +static const struct dev_pm_ops ov8858_pm_ops = {
> > +	SET_RUNTIME_PM_OPS(ov8858_runtime_suspend,
> > +			   ov8858_runtime_resume, NULL)
> > +};
> > +
> > +static struct i2c_driver ov8858_i2c_driver = {
> > +	.driver = {
> > +		.name = "ov8858",
> > +		.pm = &ov8858_pm_ops,
> > +		.of_match_table = of_match_ptr(ov8858_of_match),
> > +	},
> > +	.probe_new	= &ov8858_probe,
> > +	.remove		= &ov8858_remove,
> > +};
> > +
> > +module_i2c_driver(ov8858_i2c_driver);
> > +
> > +MODULE_DESCRIPTION("OmniVision ov8858 sensor driver");
>
> s/ov8858/OV8858/
>

Ack

Thanks for review
   j

> > +MODULE_LICENSE("GPL");
>
> --
> Regards,
>
> Laurent Pinchart

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

* Re: [PATCH v2 1/2] dt-bindings: media: Add OmniVision OV8858
  2023-01-07 13:09   ` Krzysztof Kozlowski
@ 2023-01-07 15:18     ` Jacopo Mondi
  2023-01-07 15:30       ` Krzysztof Kozlowski
  0 siblings, 1 reply; 9+ messages in thread
From: Jacopo Mondi @ 2023-01-07 15:18 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Nicholas Roth, Robert Mader, robh+dt, krzysztof.kozlowski+dt,
	Laurent Pinchart, Sakari Ailus, linux-media, Jacopo Mondi,
	devicetree

Hi Krzysztof

On Sat, Jan 07, 2023 at 02:09:54PM +0100, Krzysztof Kozlowski wrote:
> On 06/01/2023 21:39, Jacopo Mondi wrote:
> > From: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> >
> > Add binding schema for the OmniVision OV8858 8 Megapixels camera sensor.
> >
> > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > ---
> >  .../bindings/media/i2c/ovti,ov8858.yaml       | 105 ++++++++++++++++++
> >  1 file changed, 105 insertions(+)
> >  create mode 100644 Documentation/devicetree/bindings/media/i2c/ovti,ov8858.yaml
> >
> > diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,ov8858.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,ov8858.yaml
> > new file mode 100644
> > index 000000000000..002461a974f8
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/media/i2c/ovti,ov8858.yaml
> > @@ -0,0 +1,105 @@
> > +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/media/i2c/ovti,ov8858.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: OmniVision OV8858 Image Sensor
> > +
> > +maintainers:
> > +  - Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> > +  - Nicholas Roth <nicholas@rothemail.net>
> > +
> > +description: |
> > +  The OmniVision OV8858 is a color CMOS 8 Megapixels (3264x2448) image sensor
> > +  controlled through an I2C-compatible SCCB bus. The sensor transmits images
> > +  on a MIPI CSI-2 output interface with up to 4 data lanes.
> > +
> > +properties:
> > +  compatible:
> > +    const: ovti,ov8858
> > +
> > +  reg:
> > +    maxItems: 1
> > +
> > +  clocks:
> > +    maxItems: 1
> > +    description: XVCLK external clock
> > +
> > +  clock-names:
> > +    const: xvclk
> > +
> > +  dvdd-supply:
> > +    description: Digital Domain Power Supply
> > +
> > +  avdd-supply:
> > +    description: Analog Domain Power Supply
> > +
> > +  dovdd-supply:
> > +    description: I/O Domain Power Supply
> > +
> > +  powerdown-gpios:
> > +    description: PWDNB powerdown GPIO (active low)
> > +
> > +  reset-gpios:
> > +    description: XSHUTDN reset GPIO (active low)
>
> Here you need maxItems. I did not propose to remove it here.
>

I'm wondering why you can have multiple resets but not multiple
powerdowns.

Anyway, how is one supposed to know when maxItems is required or not,
where should I look ?

Thanks
   j

>
> Best regards,
> Krzysztof
>

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

* Re: [PATCH v2 1/2] dt-bindings: media: Add OmniVision OV8858
  2023-01-07 15:18     ` Jacopo Mondi
@ 2023-01-07 15:30       ` Krzysztof Kozlowski
  0 siblings, 0 replies; 9+ messages in thread
From: Krzysztof Kozlowski @ 2023-01-07 15:30 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: Nicholas Roth, Robert Mader, robh+dt, krzysztof.kozlowski+dt,
	Laurent Pinchart, Sakari Ailus, linux-media, devicetree

On 07/01/2023 16:18, Jacopo Mondi wrote:
> Hi Krzysztof
> 
> On Sat, Jan 07, 2023 at 02:09:54PM +0100, Krzysztof Kozlowski wrote:
>> On 06/01/2023 21:39, Jacopo Mondi wrote:
>>> From: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
>>>
>>> Add binding schema for the OmniVision OV8858 8 Megapixels camera sensor.
>>>
>>> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
>>> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
>>> ---
>>>  .../bindings/media/i2c/ovti,ov8858.yaml       | 105 ++++++++++++++++++
>>>  1 file changed, 105 insertions(+)
>>>  create mode 100644 Documentation/devicetree/bindings/media/i2c/ovti,ov8858.yaml
>>>
>>> diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,ov8858.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,ov8858.yaml
>>> new file mode 100644
>>> index 000000000000..002461a974f8
>>> --- /dev/null
>>> +++ b/Documentation/devicetree/bindings/media/i2c/ovti,ov8858.yaml
>>> @@ -0,0 +1,105 @@
>>> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
>>> +%YAML 1.2
>>> +---
>>> +$id: http://devicetree.org/schemas/media/i2c/ovti,ov8858.yaml#
>>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>>> +
>>> +title: OmniVision OV8858 Image Sensor
>>> +
>>> +maintainers:
>>> +  - Jacopo Mondi <jacopo.mondi@ideasonboard.com>
>>> +  - Nicholas Roth <nicholas@rothemail.net>
>>> +
>>> +description: |
>>> +  The OmniVision OV8858 is a color CMOS 8 Megapixels (3264x2448) image sensor
>>> +  controlled through an I2C-compatible SCCB bus. The sensor transmits images
>>> +  on a MIPI CSI-2 output interface with up to 4 data lanes.
>>> +
>>> +properties:
>>> +  compatible:
>>> +    const: ovti,ov8858
>>> +
>>> +  reg:
>>> +    maxItems: 1
>>> +
>>> +  clocks:
>>> +    maxItems: 1
>>> +    description: XVCLK external clock
>>> +
>>> +  clock-names:
>>> +    const: xvclk
>>> +
>>> +  dvdd-supply:
>>> +    description: Digital Domain Power Supply
>>> +
>>> +  avdd-supply:
>>> +    description: Analog Domain Power Supply
>>> +
>>> +  dovdd-supply:
>>> +    description: I/O Domain Power Supply
>>> +
>>> +  powerdown-gpios:
>>> +    description: PWDNB powerdown GPIO (active low)
>>> +
>>> +  reset-gpios:
>>> +    description: XSHUTDN reset GPIO (active low)
>>
>> Here you need maxItems. I did not propose to remove it here.
>>
> 
> I'm wondering why you can have multiple resets but not multiple
> powerdowns.
> 
> Anyway, how is one supposed to know when maxItems is required or not,
> where should I look ?

gpio-consumer-common.yaml

Best regards,
Krzysztof


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

* Re: [PATCH v2 2/2] media: i2c: Add driver for OmniVision OV8858
  2023-01-07 15:17     ` Jacopo Mondi
@ 2023-01-08  2:07       ` Laurent Pinchart
  0 siblings, 0 replies; 9+ messages in thread
From: Laurent Pinchart @ 2023-01-08  2:07 UTC (permalink / raw)
  To: Jacopo Mondi; +Cc: Nicholas Roth, Robert Mader, Sakari Ailus, linux-media

Hi Jacopo,

On Sat, Jan 07, 2023 at 04:17:03PM +0100, Jacopo Mondi wrote:
> On Sat, Jan 07, 2023 at 01:07:52AM +0200, Laurent Pinchart wrote:
> > On Fri, Jan 06, 2023 at 09:39:09PM +0100, Jacopo Mondi wrote:
> > > From: Nicholas Roth <nicholas@rothemail.net>
> > >
> > > Add a driver for OmniVision OV8858 image sensor.
> > >
> > > The driver currently supports operations with 2 and 4 data lanes, in
> > > full resolution and half-binned resolution modes.
> > >
> > > The driver has been upported from the PinephonePro BSP available at
> > > https://gitlab.com/pine64-org/linux.git
> > > at commit 8c4a90c12dc2 ("media: i2c: ov8858: Use default subdev
> > > name").
> > >
> > > Signed-off-by: Nicholas Roth <nicholas@rothemail.net>
> > > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> > > ---
> > >  MAINTAINERS                |    9 +
> > >  drivers/media/i2c/Kconfig  |   13 +
> > >  drivers/media/i2c/Makefile |    1 +
> > >  drivers/media/i2c/ov8858.c | 1952 ++++++++++++++++++++++++++++++++++++
> > >  4 files changed, 1975 insertions(+)
> > >  create mode 100644 drivers/media/i2c/ov8858.c
> > >
> > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > index f61eb221415b..fafb0a01be93 100644
> > > --- a/MAINTAINERS
> > > +++ b/MAINTAINERS
> > > @@ -15524,6 +15524,15 @@ T:	git git://linuxtv.org/media_tree.git
> > >  F:	Documentation/devicetree/bindings/media/i2c/ov8856.yaml
> > >  F:	drivers/media/i2c/ov8856.c
> > >
> > > +OMNIVISION OV8858 SENSOR DRIVER
> > > +M:	Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> > > +M:	Nicholas Roth <nicholas@rothemail.net>
> > > +L:	linux-media@vger.kernel.org
> > > +S:	Maintained
> > > +T:	git git://linuxtv.org/media_tree.git
> > > +F:	Documentation/devicetree/bindings/media/i2c/ovti,ov8858.yaml
> > > +F:	drivers/media/i2c/ov8858.c
> > > +
> > >  OMNIVISION OV9282 SENSOR DRIVER
> > >  M:	Paul J. Murphy <paul.j.murphy@intel.com>
> > >  M:	Daniele Alessandrelli <daniele.alessandrelli@intel.com>
> > > diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> > > index 833241897d63..12ba8542778f 100644
> > > --- a/drivers/media/i2c/Kconfig
> > > +++ b/drivers/media/i2c/Kconfig
> > > @@ -645,6 +645,19 @@ config VIDEO_OV8856
> > >  	  To compile this driver as a module, choose M here: the
> > >  	  module will be called ov8856.
> > >
> > > +config VIDEO_OV8858
> > > +	tristate "OmniVision OV8858 sensor support"
> > > +	depends on I2C && PM && VIDEO_DEV
> > > +	select MEDIA_CONTROLLER
> > > +	select VIDEO_V4L2_SUBDEV_API
> > > +	select V4L2_FWNODE
> > > +	help
> > > +	  This is a Video4Linux2 sensor driver for OmniVision
> > > +	  OV8858 camera sensor.
> > > +
> > > +	  To compile this driver as a module, choose M here: the
> > > +	  module will be called ov8858.
> > > +
> > >  config VIDEO_OV8865
> > >  	tristate "OmniVision OV8865 sensor support"
> > >  	depends on I2C && PM && VIDEO_DEV
> > > diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> > > index 4d6c052bb5a7..b611a8277d57 100644
> > > --- a/drivers/media/i2c/Makefile
> > > +++ b/drivers/media/i2c/Makefile
> > > @@ -96,6 +96,7 @@ obj-$(CONFIG_VIDEO_OV7670) += ov7670.o
> > >  obj-$(CONFIG_VIDEO_OV772X) += ov772x.o
> > >  obj-$(CONFIG_VIDEO_OV7740) += ov7740.o
> > >  obj-$(CONFIG_VIDEO_OV8856) += ov8856.o
> > > +obj-$(CONFIG_VIDEO_OV8858) += ov8858.o
> > >  obj-$(CONFIG_VIDEO_OV8865) += ov8865.o
> > >  obj-$(CONFIG_VIDEO_OV9282) += ov9282.o
> > >  obj-$(CONFIG_VIDEO_OV9640) += ov9640.o
> > > diff --git a/drivers/media/i2c/ov8858.c b/drivers/media/i2c/ov8858.c
> > > new file mode 100644
> > > index 000000000000..bfdfac49caa9
> > > --- /dev/null
> > > +++ b/drivers/media/i2c/ov8858.c
> > > @@ -0,0 +1,1952 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +/*
> > > + * Copyright (C) 2023 Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> > > + * Copyright (C) 2022 Nicholas Roth <nicholas@rothemail.net>
> > > + * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd.
> > > + */
> > > +
> > > +#include <asm/unaligned.h>
> > > +
> > > +#include <linux/clk.h>
> > > +#include <linux/delay.h>
> > > +#include <linux/device.h>
> > > +#include <linux/gpio/consumer.h>
> > > +#include <linux/i2c.h>
> > > +#include <linux/module.h>
> > > +#include <linux/of.h>
> > > +#include <linux/of_graph.h>
> > > +#include <linux/pm_runtime.h>
> > > +#include <linux/regulator/consumer.h>
> > > +#include <linux/slab.h>
> > > +
> > > +#include <media/media-entity.h>
> > > +#include <media/v4l2-async.h>
> > > +#include <media/v4l2-common.h>
> > > +#include <media/v4l2-ctrls.h>
> > > +#include <media/v4l2-device.h>
> > > +#include <media/v4l2-fwnode.h>
> > > +#include <media/v4l2-mediabus.h>
> > > +#include <media/v4l2-subdev.h>
> > > +
> > > +#define OV8858_LINK_FREQ		360000000U
> > > +#define OV8858_XVCLK_FREQ		24000000
> > > +
> > > +#define OV8858_REG_SIZE_SHIFT		16
> > > +#define OV8858_REG_ADDR_MASK		0xffff
> > > +#define OV8858_REG_8BIT(n)		((1U << OV8858_REG_SIZE_SHIFT) | (n))
> > > +#define OV8858_REG_16BIT(n)		((2U << OV8858_REG_SIZE_SHIFT) | (n))
> > > +#define OV8858_REG_24BIT(n)		((3U << OV8858_REG_SIZE_SHIFT) | (n))
> >
> > /me likes this :-)
> >
> > > +
> > > +#define OV8858_CHIP_ID			0x008858
> > > +#define OV8858_REG_CHIP_ID		OV8858_REG_24BIT(0x300a)
> > > +
> > > +#define OV8858_REG_CTRL_MODE		OV8858_REG_8BIT(0x0100)
> > > +#define OV8858_MODE_SW_STANDBY		0x0
> > > +#define OV8858_MODE_STREAMING		0x1
> >
> > Please move this up to sort registers by address. Same thing in several
> > locations below.
> 
> ack
> 
> > > +
> > > +#define OV8858_REG_EXPOSURE		OV8858_REG_24BIT(0x3500)
> > > +#define	OV8858_EXPOSURE_MIN		4
> > > +#define	OV8858_EXPOSURE_STEP		1
> > > +#define OV8858_VTS_MAX			0x7fff
> > > +
> > > +#define OV8858_REG_GAIN_H		OV8858_REG_8BIT(0x3508)
> > > +#define OV8858_REG_GAIN_L		OV8858_REG_8BIT(0x3509)
> >
> > This is a single 16-bit register. You will be able to drop the mask and
> > shift macros.
> 
> You're right, I can write 16 bits in one go
> 
> > > +#define OV8858_GAIN_H_MASK		0x07
> > > +#define OV8858_GAIN_H_SHIFT		8
> > > +#define OV8858_GAIN_L_MASK		0xff
> > > +#define OV8858_GAIN_MIN			0x0
> > > +#define OV8858_GAIN_MAX			0x7ff
> >
> > According to the documentation, the gain is a 13-bit value ([12:0]),
> > should this be 0x1fff ? I'm not sure we can trust the documentation
> > though, the description of register 0x3509 is clearly wrong.
> 
> I know...
> Please see
> https://patchwork.kernel.org/project/linux-media/patch/20221106171129.166892-2-nicholas@rothemail.net/#25112139
> for a little backgound

One day we'll have good documentation. One day...

> > > +#define OV8858_GAIN_STEP		1
> > > +#define OV8858_GAIN_DEFAULT		0x80
> > > +
> > > +#define OV8858_REG_DGAIN_H		OV8858_REG_8BIT(0x350a)
> > > +#define OV8858_REG_DGAIN_L		OV8858_REG_8BIT(0x350b)
> >
> > Same here.
> >
> > > +#define OV8858_DGAIN_H_MASK		0xff
> > > +#define OV8858_DGAIN_H_SHIFT		6
> > > +#define OV8858_DGAIN_L_MASK		0x3f
> > > +#define OV8858_DGAIN_MIN		0x0
> > > +#define OV8858_DGAIN_MAX		0x3fff
> > > +#define OV8858_DGAIN_STEP		1
> > > +#define OV8858_DGAIN_DEFAULT		0x200
> > > +
> > > +#define OV8858_REG_TEST_PATTERN		OV8858_REG_8BIT(0x5e00)
> > > +#define	OV8858_TEST_PATTERN_ENABLE	0x80
> > > +#define	OV8858_TEST_PATTERN_DISABLE	0x0
> > > +
> > > +#define OV8858_REG_VTS			OV8858_REG_16BIT(0x380e)
> > > +
> > > +#define REG_NULL			0xffff
> > > +
> > > +#define OV8858_CHIP_REVISION_REG	OV8858_REG_8BIT(0x302a)
> >
> > It's called "SUB ID" in the documentation, how about sticking to that
> > name ? Actually this applies to the other registers above, should we
> > keep them as close as possible to the datasheet ?
> 
> I'll try to
> 
> > > +#define OV8858_R1A			0xb0
> > > +#define OV8858_R2A			0xb2
> > > +
> > > +static const char * const ov8858_supply_names[] = {
> > > +	"avdd",		/* Analog power */
> > > +	"dovdd",	/* Digital I/O power */
> > > +	"dvdd",		/* Digital core power */
> > > +};
> > > +
> > > +#define OV8858_NUM_SUPPLIES ARRAY_SIZE(ov8858_supply_names)
> > > +
> > > +struct regval {
> > > +	u16 addr;
> > > +	u8 val;
> > > +};
> > > +
> > > +struct regval_modes {
> > > +	const struct regval *mode_2lanes;
> > > +	const struct regval *mode_4lanes;
> > > +};
> > > +
> > > +struct ov8858_mode {
> > > +	u32 width;
> > > +	u32 height;
> > > +	u32 hts_def;
> > > +	u32 vts_def;
> > > +	u32 exp_def;
> > > +	const struct regval_modes reg_modes;
> > > +};
> > > +
> > > +struct ov8858 {
> > > +	struct i2c_client	*client;
> > > +	struct clk		*xvclk;
> > > +	struct gpio_desc	*reset_gpio;
> > > +	struct gpio_desc	*pwdn_gpio;
> > > +	struct regulator_bulk_data supplies[OV8858_NUM_SUPPLIES];
> > > +
> > > +	struct v4l2_subdev	subdev;
> > > +	struct media_pad	pad;
> > > +
> > > +	/* Protect controls and image format. */
> > > +	struct mutex		mutex;
> >
> > V4L2 provides a default lock in the control handler. You could use it
> > and drop this, using the control handler lock as the active state lock
> > as well.  No functional difference (the important part is to use the
> > same lock for the control handler and the active state), but one less
> > field in this structure.
> 
> I wasn't sure about the control handler lock, as it is usually pointed
> to a driver-specific mutex.
> 
> In example (imx219)
> 
> 	ctrl_hdlr->lock = &imx219->mutex;
> 
> I've seen your imx290 patch re-using it but I didn't do the same. I'll
> change it.

It can be done that way too, there's nothing wrong with what you've done
in this patch as such, but then we waste a bit of memory as the mutex in
the control handler structure (v4l2_ctrl_handler._lock) is unused.

> > If you want to keep this field, you're missing mutex_init() and
> > mutex_destroy().
> 
> Got fooled by
> 	__mutex_init(&state->_lock, lock_name, lock_key);
> 
> in __v4l2_subdev_state_alloc()
> 
> But that's actually a different mutex
> 
> > > +
> > > +	struct v4l2_ctrl_handler ctrl_handler;
> > > +	struct v4l2_ctrl	*exposure;
> > > +	struct v4l2_ctrl	*hblank;
> > > +	struct v4l2_ctrl	*vblank;
> > > +
> > > +	const struct regval	*global_regs;
> > > +	const struct ov8858_mode *cur_mode;
> >
> > It would be nice to drop the cur_mode field, that would simplify
> > set_fmt(), but it's not a hard requirement. Let's minimize its usage
> > though.
> >
> > > +
> > > +	bool			streaming;
> > > +	unsigned int		num_lanes;
> > > +	unsigned int		pixel_rate;
> > > +};
> > > +
> > > +#define to_ov8858(sd) container_of(sd, struct ov8858, subdev)
> >
> > Maybe a static inline function to provide type safety ?
> 
> Better, yes
> 
> > > +
> > > +static const struct regval ov8858_global_regs_r1a[] = {
> > > +	{0x0100, 0x00},
> > > +	{0x0100, 0x00},
> > > +	{0x0100, 0x00},
> > > +	{0x0100, 0x00},
> > > +	{0x0302, 0x1e},
> > > +	{0x0303, 0x00},
> > > +	{0x0304, 0x03},
> > > +	{0x030e, 0x00},
> > > +	{0x030f, 0x09},
> > > +	{0x0312, 0x01},
> > > +	{0x031e, 0x0c},
> > > +	{0x3600, 0x00},
> > > +	{0x3601, 0x00},
> > > +	{0x3602, 0x00},
> > > +	{0x3603, 0x00},
> > > +	{0x3604, 0x22},
> > > +	{0x3605, 0x30},
> > > +	{0x3606, 0x00},
> > > +	{0x3607, 0x20},
> > > +	{0x3608, 0x11},
> > > +	{0x3609, 0x28},
> > > +	{0x360a, 0x00},
> > > +	{0x360b, 0x06},
> > > +	{0x360c, 0xdc},
> > > +	{0x360d, 0x40},
> > > +	{0x360e, 0x0c},
> > > +	{0x360f, 0x20},
> > > +	{0x3610, 0x07},
> > > +	{0x3611, 0x20},
> > > +	{0x3612, 0x88},
> > > +	{0x3613, 0x80},
> > > +	{0x3614, 0x58},
> > > +	{0x3615, 0x00},
> > > +	{0x3616, 0x4a},
> > > +	{0x3617, 0xb0},
> > > +	{0x3618, 0x56},
> > > +	{0x3619, 0x70},
> > > +	{0x361a, 0x99},
> > > +	{0x361b, 0x00},
> > > +	{0x361c, 0x07},
> > > +	{0x361d, 0x00},
> > > +	{0x361e, 0x00},
> > > +	{0x361f, 0x00},
> > > +	{0x3638, 0xff},
> > > +	{0x3633, 0x0c},
> > > +	{0x3634, 0x0c},
> > > +	{0x3635, 0x0c},
> > > +	{0x3636, 0x0c},
> > > +	{0x3645, 0x13},
> > > +	{0x3646, 0x83},
> > > +	{0x364a, 0x07},
> > > +	{0x3015, 0x01},
> > > +	{0x3018, 0x32},
> > > +	{0x3020, 0x93},
> > > +	{0x3022, 0x01},
> > > +	{0x3031, 0x0a},
> > > +	{0x3034, 0x00},
> > > +	{0x3106, 0x01},
> > > +	{0x3305, 0xf1},
> > > +	{0x3308, 0x00},
> > > +	{0x3309, 0x28},
> > > +	{0x330a, 0x00},
> > > +	{0x330b, 0x20},
> > > +	{0x330c, 0x00},
> > > +	{0x330d, 0x00},
> > > +	{0x330e, 0x00},
> > > +	{0x330f, 0x40},
> > > +	{0x3307, 0x04},
> > > +	{0x3500, 0x00},
> > > +	{0x3501, 0x4d},
> > > +	{0x3502, 0x40},
> > > +	{0x3503, 0x00},
> > > +	{0x3505, 0x80},
> > > +	{0x3508, 0x04},
> > > +	{0x3509, 0x00},
> > > +	{0x350c, 0x00},
> > > +	{0x350d, 0x80},
> > > +	{0x3510, 0x00},
> > > +	{0x3511, 0x02},
> > > +	{0x3512, 0x00},
> > > +	{0x3700, 0x18},
> > > +	{0x3701, 0x0c},
> > > +	{0x3702, 0x28},
> > > +	{0x3703, 0x19},
> > > +	{0x3704, 0x14},
> > > +	{0x3705, 0x00},
> > > +	{0x3706, 0x35},
> > > +	{0x3707, 0x04},
> > > +	{0x3708, 0x24},
> > > +	{0x3709, 0x33},
> > > +	{0x370a, 0x00},
> > > +	{0x370b, 0xb5},
> > > +	{0x370c, 0x04},
> > > +	{0x3718, 0x12},
> > > +	{0x3719, 0x31},
> > > +	{0x3712, 0x42},
> > > +	{0x3714, 0x24},
> > > +	{0x371e, 0x19},
> > > +	{0x371f, 0x40},
> > > +	{0x3720, 0x05},
> > > +	{0x3721, 0x05},
> > > +	{0x3724, 0x06},
> > > +	{0x3725, 0x01},
> > > +	{0x3726, 0x06},
> > > +	{0x3728, 0x05},
> > > +	{0x3729, 0x02},
> > > +	{0x372a, 0x03},
> > > +	{0x372b, 0x53},
> > > +	{0x372c, 0xa3},
> > > +	{0x372d, 0x53},
> > > +	{0x372e, 0x06},
> > > +	{0x372f, 0x10},
> > > +	{0x3730, 0x01},
> > > +	{0x3731, 0x06},
> > > +	{0x3732, 0x14},
> > > +	{0x3733, 0x10},
> > > +	{0x3734, 0x40},
> > > +	{0x3736, 0x20},
> > > +	{0x373a, 0x05},
> > > +	{0x373b, 0x06},
> > > +	{0x373c, 0x0a},
> > > +	{0x373e, 0x03},
> > > +	{0x3755, 0x10},
> > > +	{0x3758, 0x00},
> > > +	{0x3759, 0x4c},
> > > +	{0x375a, 0x06},
> > > +	{0x375b, 0x13},
> > > +	{0x375c, 0x20},
> > > +	{0x375d, 0x02},
> > > +	{0x375e, 0x00},
> > > +	{0x375f, 0x14},
> > > +	{0x3768, 0x22},
> > > +	{0x3769, 0x44},
> > > +	{0x376a, 0x44},
> > > +	{0x3761, 0x00},
> > > +	{0x3762, 0x00},
> > > +	{0x3763, 0x00},
> > > +	{0x3766, 0xff},
> > > +	{0x376b, 0x00},
> > > +	{0x3772, 0x23},
> > > +	{0x3773, 0x02},
> > > +	{0x3774, 0x16},
> > > +	{0x3775, 0x12},
> > > +	{0x3776, 0x04},
> > > +	{0x3777, 0x00},
> > > +	{0x3778, 0x1b},
> > > +	{0x37a0, 0x44},
> > > +	{0x37a1, 0x3d},
> > > +	{0x37a2, 0x3d},
> > > +	{0x37a3, 0x00},
> > > +	{0x37a4, 0x00},
> > > +	{0x37a5, 0x00},
> > > +	{0x37a6, 0x00},
> > > +	{0x37a7, 0x44},
> > > +	{0x37a8, 0x4c},
> > > +	{0x37a9, 0x4c},
> > > +	{0x3760, 0x00},
> > > +	{0x376f, 0x01},
> > > +	{0x37aa, 0x44},
> > > +	{0x37ab, 0x2e},
> > > +	{0x37ac, 0x2e},
> > > +	{0x37ad, 0x33},
> > > +	{0x37ae, 0x0d},
> > > +	{0x37af, 0x0d},
> > > +	{0x37b0, 0x00},
> > > +	{0x37b1, 0x00},
> > > +	{0x37b2, 0x00},
> > > +	{0x37b3, 0x42},
> > > +	{0x37b4, 0x42},
> > > +	{0x37b5, 0x33},
> > > +	{0x37b6, 0x00},
> > > +	{0x37b7, 0x00},
> > > +	{0x37b8, 0x00},
> > > +	{0x37b9, 0xff},
> > > +	{0x3800, 0x00},
> > > +	{0x3801, 0x0c},
> > > +	{0x3802, 0x00},
> > > +	{0x3803, 0x0c},
> > > +	{0x3804, 0x0c},
> > > +	{0x3805, 0xd3},
> > > +	{0x3806, 0x09},
> > > +	{0x3807, 0xa3},
> > > +	{0x3808, 0x06},
> > > +	{0x3809, 0x60},
> > > +	{0x380a, 0x04},
> > > +	{0x380b, 0xc8},
> > > +	{0x380c, 0x07},
> > > +	{0x380d, 0x88},
> > > +	{0x380e, 0x04},
> > > +	{0x380f, 0xdc},
> > > +	{0x3810, 0x00},
> > > +	{0x3811, 0x04},
> > > +	{0x3813, 0x02},
> > > +	{0x3814, 0x03},
> > > +	{0x3815, 0x01},
> > > +	{0x3820, 0x00},
> > > +	{0x3821, 0x67},
> > > +	{0x382a, 0x03},
> > > +	{0x382b, 0x01},
> > > +	{0x3830, 0x08},
> > > +	{0x3836, 0x02},
> > > +	{0x3837, 0x18},
> > > +	{0x3841, 0xff},
> > > +	{0x3846, 0x48},
> > > +	{0x3d85, 0x14},
> > > +	{0x3f08, 0x08},
> > > +	{0x3f0a, 0x80},
> > > +	{0x4000, 0xf1},
> > > +	{0x4001, 0x10},
> > > +	{0x4005, 0x10},
> > > +	{0x4002, 0x27},
> > > +	{0x4009, 0x81},
> > > +	{0x400b, 0x0c},
> > > +	{0x401b, 0x00},
> > > +	{0x401d, 0x00},
> > > +	{0x4020, 0x00},
> > > +	{0x4021, 0x04},
> > > +	{0x4022, 0x04},
> > > +	{0x4023, 0xb9},
> > > +	{0x4024, 0x05},
> > > +	{0x4025, 0x2a},
> > > +	{0x4026, 0x05},
> > > +	{0x4027, 0x2b},
> > > +	{0x4028, 0x00},
> > > +	{0x4029, 0x02},
> > > +	{0x402a, 0x04},
> > > +	{0x402b, 0x04},
> > > +	{0x402c, 0x02},
> > > +	{0x402d, 0x02},
> > > +	{0x402e, 0x08},
> > > +	{0x402f, 0x02},
> > > +	{0x401f, 0x00},
> > > +	{0x4034, 0x3f},
> > > +	{0x403d, 0x04},
> > > +	{0x4300, 0xff},
> > > +	{0x4301, 0x00},
> > > +	{0x4302, 0x0f},
> > > +	{0x4316, 0x00},
> > > +	{0x4500, 0x38},
> > > +	{0x4503, 0x18},
> > > +	{0x4600, 0x00},
> > > +	{0x4601, 0xcb},
> > > +	{0x481f, 0x32},
> > > +	{0x4837, 0x16},
> > > +	{0x4850, 0x10},
> > > +	{0x4851, 0x32},
> > > +	{0x4b00, 0x2a},
> > > +	{0x4b0d, 0x00},
> > > +	{0x4d00, 0x04},
> > > +	{0x4d01, 0x18},
> > > +	{0x4d02, 0xc3},
> > > +	{0x4d03, 0xff},
> > > +	{0x4d04, 0xff},
> > > +	{0x4d05, 0xff},
> > > +	{0x5000, 0x7e},
> > > +	{0x5001, 0x01},
> > > +	{0x5002, 0x08},
> > > +	{0x5003, 0x20},
> > > +	{0x5046, 0x12},
> > > +	{0x5901, 0x00},
> > > +	{0x5e00, 0x00},
> > > +	{0x5e01, 0x41},
> > > +	{0x382d, 0x7f},
> > > +	{0x4825, 0x3a},
> > > +	{0x4826, 0x40},
> > > +	{0x4808, 0x25},
> > > +	//{0x0100, 0x01},
> > > +	{REG_NULL, 0x00},
> > > +};
> > > +
> > > +static const struct regval ov8858_global_regs_r2a_2lane[] = {
> > > +	/*
> > > +	 * MIPI=720Mbps, SysClk=144Mhz,Dac Clock=360Mhz.
> > > +	 * v00_01_00 (05/29/2014) : initial setting
> > > +	 * AM19 : 3617 <- 0xC0
> > > +	 * AM20 : change FWC_6K_EN to be default 0x3618=0x5a
> > > +	 */
> > > +	{0x0103, 0x01},// software reset for OVTATool only
> > > +	{0x0103, 0x01},// software reset
> > > +	{0x0100, 0x00},// software standby
> > > +	{0x0302, 0x1e},// pll1_multi
> > > +	{0x0303, 0x00},// pll1_divm
> > > +	{0x0304, 0x03},// pll1_div_mipi
> > > +	{0x030e, 0x02},// pll2_rdiv
> > > +	{0x030f, 0x04},// pll2_divsp
> > > +	{0x0312, 0x03},// pll2_pre_div0, pll2_r_divdac
> > > +	{0x031e, 0x0c},// pll1_no_lat
> >
> > I'll pretend to ignore the C++-style comments, but could you add a space
> > before // ?
> >
> > > +	{0x3600, 0x00},
> > > +	{0x3601, 0x00},
> > > +	{0x3602, 0x00},
> > > +	{0x3603, 0x00},
> > > +	{0x3604, 0x22},
> > > +	{0x3605, 0x20},
> > > +	{0x3606, 0x00},
> > > +	{0x3607, 0x20},
> > > +	{0x3608, 0x11},
> > > +	{0x3609, 0x28},
> > > +	{0x360a, 0x00},
> > > +	{0x360b, 0x05},
> > > +	{0x360c, 0xd4},
> > > +	{0x360d, 0x40},
> > > +	{0x360e, 0x0c},
> > > +	{0x360f, 0x20},
> > > +	{0x3610, 0x07},
> > > +	{0x3611, 0x20},
> > > +	{0x3612, 0x88},
> > > +	{0x3613, 0x80},
> > > +	{0x3614, 0x58},
> > > +	{0x3615, 0x00},
> > > +	{0x3616, 0x4a},
> > > +	{0x3617, 0x90},
> > > +	{0x3618, 0x5a},
> > > +	{0x3619, 0x70},
> > > +	{0x361a, 0x99},
> > > +	{0x361b, 0x0a},
> > > +	{0x361c, 0x07},
> > > +	{0x361d, 0x00},
> > > +	{0x361e, 0x00},
> > > +	{0x361f, 0x00},
> > > +	{0x3638, 0xff},
> > > +	{0x3633, 0x0f},
> > > +	{0x3634, 0x0f},
> > > +	{0x3635, 0x0f},
> > > +	{0x3636, 0x12},
> > > +	{0x3645, 0x13},
> > > +	{0x3646, 0x83},
> > > +	{0x364a, 0x07},
> > > +	{0x3015, 0x00},
> > > +	{0x3018, 0x32}, // MIPI 2 lane
> > > +	{0x3020, 0x93}, // Clock switch output normal, pclk_div =/1
> > > +	{0x3022, 0x01}, // pd_mipi enable when rst_sync
> > > +	{0x3031, 0x0a}, // MIPI 10-bit mode
> > > +	{0x3034, 0x00}, //
> > > +	{0x3106, 0x01}, // sclk_div, sclk_pre_div
> > > +	{0x3305, 0xf1},
> > > +	{0x3308, 0x00},
> > > +	{0x3309, 0x28},
> > > +	{0x330a, 0x00},
> > > +	{0x330b, 0x20},
> > > +	{0x330c, 0x00},
> > > +	{0x330d, 0x00},
> > > +	{0x330e, 0x00},
> > > +	{0x330f, 0x40},
> > > +	{0x3307, 0x04},
> > > +	{0x3500, 0x00}, // exposure H
> > > +	{0x3501, 0x4d}, // exposure M
> > > +	{0x3502, 0x40}, // exposure L
> > > +	{0x3503, 0x80}, // gain delay ?, exposure delay 1 frame, real gain
> > > +	{0x3505, 0x80}, // gain option
> > > +	{0x3508, 0x02}, // gain H
> > > +	{0x3509, 0x00}, // gain L
> > > +	{0x350c, 0x00}, // short gain H
> > > +	{0x350d, 0x80}, // short gain L
> > > +	{0x3510, 0x00}, // short exposure H
> > > +	{0x3511, 0x02}, // short exposure M
> > > +	{0x3512, 0x00}, // short exposure L
> > > +	{0x3700, 0x18},
> > > +	{0x3701, 0x0c},
> > > +	{0x3702, 0x28},
> > > +	{0x3703, 0x19},
> > > +	{0x3704, 0x14},
> > > +	{0x3705, 0x00},
> > > +	{0x3706, 0x82},
> > > +	{0x3707, 0x04},
> > > +	{0x3708, 0x24},
> > > +	{0x3709, 0x33},
> > > +	{0x370a, 0x01},
> > > +	{0x370b, 0x82},
> > > +	{0x370c, 0x04},
> > > +	{0x3718, 0x12},
> > > +	{0x3719, 0x31},
> > > +	{0x3712, 0x42},
> > > +	{0x3714, 0x24},
> > > +	{0x371e, 0x19},
> > > +	{0x371f, 0x40},
> > > +	{0x3720, 0x05},
> > > +	{0x3721, 0x05},
> > > +	{0x3724, 0x06},
> > > +	{0x3725, 0x01},
> > > +	{0x3726, 0x06},
> > > +	{0x3728, 0x05},
> > > +	{0x3729, 0x02},
> > > +	{0x372a, 0x03},
> > > +	{0x372b, 0x53},
> > > +	{0x372c, 0xa3},
> > > +	{0x372d, 0x53},
> > > +	{0x372e, 0x06},
> > > +	{0x372f, 0x10},
> > > +	{0x3730, 0x01},
> > > +	{0x3731, 0x06},
> > > +	{0x3732, 0x14},
> > > +	{0x3733, 0x10},
> > > +	{0x3734, 0x40},
> > > +	{0x3736, 0x20},
> > > +	{0x373a, 0x05},
> > > +	{0x373b, 0x06},
> > > +	{0x373c, 0x0a},
> > > +	{0x373e, 0x03},
> > > +	{0x3750, 0x0a},
> > > +	{0x3751, 0x0e},
> > > +	{0x3755, 0x10},
> > > +	{0x3758, 0x00},
> > > +	{0x3759, 0x4c},
> > > +	{0x375a, 0x06},
> > > +	{0x375b, 0x13},
> > > +	{0x375c, 0x20},
> > > +	{0x375d, 0x02},
> > > +	{0x375e, 0x00},
> > > +	{0x375f, 0x14},
> > > +	{0x3768, 0x22},
> > > +	{0x3769, 0x44},
> > > +	{0x376a, 0x44},
> > > +	{0x3761, 0x00},
> > > +	{0x3762, 0x00},
> > > +	{0x3763, 0x00},
> > > +	{0x3766, 0xff},
> > > +	{0x376b, 0x00},
> > > +	{0x3772, 0x23},
> > > +	{0x3773, 0x02},
> > > +	{0x3774, 0x16},
> > > +	{0x3775, 0x12},
> > > +	{0x3776, 0x04},
> > > +	{0x3777, 0x00},
> > > +	{0x3778, 0x17},
> > > +	{0x37a0, 0x44},
> > > +	{0x37a1, 0x3d},
> > > +	{0x37a2, 0x3d},
> > > +	{0x37a3, 0x00},
> > > +	{0x37a4, 0x00},
> > > +	{0x37a5, 0x00},
> > > +	{0x37a6, 0x00},
> > > +	{0x37a7, 0x44},
> > > +	{0x37a8, 0x4c},
> > > +	{0x37a9, 0x4c},
> > > +	{0x3760, 0x00},
> > > +	{0x376f, 0x01},
> > > +	{0x37aa, 0x44},
> > > +	{0x37ab, 0x2e},
> > > +	{0x37ac, 0x2e},
> > > +	{0x37ad, 0x33},
> > > +	{0x37ae, 0x0d},
> > > +	{0x37af, 0x0d},
> > > +	{0x37b0, 0x00},
> > > +	{0x37b1, 0x00},
> > > +	{0x37b2, 0x00},
> > > +	{0x37b3, 0x42},
> > > +	{0x37b4, 0x42},
> > > +	{0x37b5, 0x31},
> > > +	{0x37b6, 0x00},
> > > +	{0x37b7, 0x00},
> > > +	{0x37b8, 0x00},
> > > +	{0x37b9, 0xff},
> > > +	{0x3800, 0x00}, // x start H
> > > +	{0x3801, 0x0c}, // x start L
> > > +	{0x3802, 0x00}, // y start H
> > > +	{0x3803, 0x0c}, // y start L
> >
> > We have documentation for lots of those registers, it would be nice to
> > give them names, but that's not a hard requirement either.
> 
> Ideally we could be able to split the registers table in 3:
> - global init
> - 4/2 lanes sequences
> - geometry programmed using the actual sizes and not registers table
> 
> I started the effort, but it's rather delicate and error-prone, as in
> example changing the order of registers writes leads to funny results.
> 
> I would prefer doing it on top not to delay upstreaming of the driver

Fine with me.

> > > +	{0x3804, 0x0c}, // x end H
> > > +	{0x3805, 0xd3}, // x end L
> > > +	{0x3806, 0x09}, // y end H
> > > +	{0x3807, 0xa3}, // y end L
> > > +	{0x3808, 0x06}, // x output size H
> > > +	{0x3809, 0x60}, // x output size L
> > > +	{0x380a, 0x04}, // y output size H
> > > +	{0x380b, 0xc8}, // y output size L
> > > +	{0x380c, 0x07}, // HTS H
> > > +	{0x380d, 0x88}, // HTS L
> > > +	{0x380e, 0x04}, // VTS H
> > > +	{0x380f, 0xdc}, // VTS L
> > > +	{0x3810, 0x00}, // ISP x win H
> > > +	{0x3811, 0x04}, // ISP x win L
> > > +	{0x3813, 0x02}, // ISP y win L
> > > +	{0x3814, 0x03}, // x odd inc
> > > +	{0x3815, 0x01}, // x even inc
> > > +	{0x3820, 0x00}, // vflip off
> > > +	{0x3821, 0x67}, // mirror on, bin on
> > > +	{0x382a, 0x03}, // y odd inc
> > > +	{0x382b, 0x01}, // y even inc
> > > +	{0x3830, 0x08}, //
> > > +	{0x3836, 0x02}, //
> > > +	{0x3837, 0x18}, //
> > > +	{0x3841, 0xff}, // window auto size enable
> > > +	{0x3846, 0x48}, //
> > > +	{0x3d85, 0x16}, // OTP power up load data enable with BIST
> > > +	{0x3d8c, 0x73}, // OTP setting start High
> > > +	{0x3d8d, 0xde}, // OTP setting start Low
> > > +	{0x3f08, 0x08}, //
> > > +	{0x3f0a, 0x00}, //
> > > +	{0x4000, 0xf1}, // out_range_trig, format_chg_trig
> > > +	{0x4001, 0x10}, // total 128 black column
> > > +	{0x4005, 0x10}, // BLC target L
> > > +	{0x4002, 0x27}, // value used to limit BLC offset
> > > +	{0x4009, 0x81}, // final BLC offset limitation enable
> > > +	{0x400b, 0x0c}, // DCBLC on, DCBLC manual mode on
> > > +	{0x401b, 0x00}, // zero line R coefficient
> > > +	{0x401d, 0x00}, // zoro line T coefficient
> > > +	{0x4020, 0x00}, // Anchor left start H
> > > +	{0x4021, 0x04}, // Anchor left start L
> > > +	{0x4022, 0x06}, // Anchor left end H
> > > +	{0x4023, 0x00}, // Anchor left end L
> > > +	{0x4024, 0x0f}, // Anchor right start H
> > > +	{0x4025, 0x2a}, // Anchor right start L
> > > +	{0x4026, 0x0f}, // Anchor right end H
> > > +	{0x4027, 0x2b}, // Anchor right end L
> > > +	{0x4028, 0x00}, // top zero line start
> > > +	{0x4029, 0x02}, // top zero line number
> > > +	{0x402a, 0x04}, // top black line start
> > > +	{0x402b, 0x04}, // top black line number
> > > +	{0x402c, 0x00}, // bottom zero line start
> > > +	{0x402d, 0x02}, // bottom zoro line number
> > > +	{0x402e, 0x04}, // bottom black line start
> > > +	{0x402f, 0x04}, // bottom black line number
> > > +	{0x401f, 0x00}, // interpolation x/y disable, Anchor one disable
> > > +	{0x4034, 0x3f}, //
> > > +	{0x403d, 0x04}, // md_precision_en
> > > +	{0x4300, 0xff}, // clip max H
> > > +	{0x4301, 0x00}, // clip min H
> > > +	{0x4302, 0x0f}, // clip min L, clip max L
> > > +	{0x4316, 0x00}, //
> > > +	{0x4500, 0x58}, //
> > > +	{0x4503, 0x18}, //
> > > +	{0x4600, 0x00}, //
> > > +	{0x4601, 0xcb}, //
> > > +	{0x481f, 0x32}, // clk prepare min
> > > +	{0x4837, 0x16}, // global timing
> > > +	{0x4850, 0x10}, // lane 1 = 1, lane 0 = 0
> > > +	{0x4851, 0x32}, // lane 3 = 3, lane 2 = 2
> > > +	{0x4b00, 0x2a}, //
> > > +	{0x4b0d, 0x00}, //
> > > +	{0x4d00, 0x04}, // temperature sensor
> > > +	{0x4d01, 0x18}, //
> > > +	{0x4d02, 0xc3}, //
> > > +	{0x4d03, 0xff}, //
> > > +	{0x4d04, 0xff}, //
> > > +	{0x4d05, 0xff}, // temperature sensor
> > > +	{0x5000, 0xfe}, // lenc on, slave/master AWB gain/statistics enable
> > > +	{0x5001, 0x01}, // BLC on
> > > +	{0x5002, 0x08}, // H scale off, WBMATCH off, OTP_DPC
> > > +	{0x5003, 0x20}, // DPC_DBC buffer control enable, WB
> > > +	{0x501e, 0x93}, // enable digital gain
> > > +	{0x5046, 0x12}, //
> > > +	{0x5780, 0x3e}, // DPC
> > > +	{0x5781, 0x0f}, //
> > > +	{0x5782, 0x44}, //
> > > +	{0x5783, 0x02}, //
> > > +	{0x5784, 0x01}, //
> > > +	{0x5785, 0x00}, //
> > > +	{0x5786, 0x00}, //
> > > +	{0x5787, 0x04}, //
> > > +	{0x5788, 0x02}, //
> > > +	{0x5789, 0x0f}, //
> > > +	{0x578a, 0xfd}, //
> > > +	{0x578b, 0xf5}, //
> > > +	{0x578c, 0xf5}, //
> > > +	{0x578d, 0x03}, //
> > > +	{0x578e, 0x08}, //
> > > +	{0x578f, 0x0c}, //
> > > +	{0x5790, 0x08}, //
> > > +	{0x5791, 0x04}, //
> > > +	{0x5792, 0x00}, //
> > > +	{0x5793, 0x52}, //
> > > +	{0x5794, 0xa3}, // DPC
> > > +	{0x5871, 0x0d}, // Lenc
> > > +	{0x5870, 0x18}, //
> > > +	{0x586e, 0x10}, //
> > > +	{0x586f, 0x08}, //
> > > +	{0x58f7, 0x01}, //
> > > +	{0x58f8, 0x3d}, // Lenc
> > > +	{0x5901, 0x00}, // H skip off, V skip off
> > > +	{0x5b00, 0x02}, // OTP DPC start address
> > > +	{0x5b01, 0x10}, // OTP DPC start address
> > > +	{0x5b02, 0x03}, // OTP DPC end address
> > > +	{0x5b03, 0xcf}, // OTP DPC end address
> > > +	{0x5b05, 0x6c}, // recover method = 2b11,
> > > +	{0x5e00, 0x00}, // use 0x3ff to test pattern off
> > > +	{0x5e01, 0x41}, // window cut enable
> > > +	{0x382d, 0x7f}, //
> > > +	{0x4825, 0x3a}, // lpx_p_min
> > > +	{0x4826, 0x40}, // hs_prepare_min
> > > +	{0x4808, 0x25}, // wake up delay in 1/1024 s
> > > +	{0x3763, 0x18}, //
> > > +	{0x3768, 0xcc}, //
> > > +	{0x470b, 0x28}, //
> > > +	{0x4202, 0x00}, //
> > > +	{0x400d, 0x10}, // BLC offset trigger L
> > > +	{0x4040, 0x04}, // BLC gain th2
> > > +	{0x403e, 0x04}, // BLC gain th1
> > > +	{0x4041, 0xc6}, // BLC
> > > +	{0x3007, 0x80},
> > > +	{0x400a, 0x01},
> > > +	{REG_NULL, 0x00},
> > > +};
> > > +
> > > +/*
> > > + * Xclk 24Mhz
> > > + * max_framerate 30fps
> > > + * mipi_datarate per lane 720Mbps
> > > + */
> > > +static const struct regval ov8858_1632x1224_regs_2lane[] = {
> > > +	/*
> > > +	 * MIPI=720Mbps, SysClk=144Mhz,Dac Clock=360Mhz.
> > > +	 * v00_01_00 (05/29/2014) : initial setting
> > > +	 * AM19 : 3617 <- 0xC0
> > > +	 * AM20 : change FWC_6K_EN to be default 0x3618=0x5a
> > > +	 */
> > > +	{0x0100, 0x00},
> > > +	{0x3501, 0x4d}, // exposure M
> > > +	{0x3502, 0x40}, // exposure L
> > > +	{0x3778, 0x17}, //
> > > +	{0x3808, 0x06}, // x output size H
> > > +	{0x3809, 0x60}, // x output size L
> > > +	{0x380a, 0x04}, // y output size H
> > > +	{0x380b, 0xc8}, // y output size L
> > > +	{0x380c, 0x07}, // HTS H
> > > +	{0x380d, 0x88}, // HTS L
> > > +	{0x380e, 0x04}, // VTS H
> > > +	{0x380f, 0xdc}, // VTS L
> > > +	{0x3814, 0x03}, // x odd inc
> > > +	{0x3821, 0x67}, // mirror on, bin on
> > > +	{0x382a, 0x03}, // y odd inc
> > > +	{0x3830, 0x08},
> > > +	{0x3836, 0x02},
> > > +	{0x3f0a, 0x00},
> > > +	{0x4001, 0x10}, // total 128 black column
> > > +	{0x4022, 0x06}, // Anchor left end H
> > > +	{0x4023, 0x00}, // Anchor left end L
> > > +	{0x4025, 0x2a}, // Anchor right start L
> > > +	{0x4027, 0x2b}, // Anchor right end L
> > > +	{0x402b, 0x04}, // top black line number
> > > +	{0x402f, 0x04}, // bottom black line number
> > > +	{0x4500, 0x58},
> > > +	{0x4600, 0x00},
> > > +	{0x4601, 0xcb},
> > > +	{0x382d, 0x7f},
> > > +	{0x0100, 0x01},
> > > +	{REG_NULL, 0x00},
> > > +};
> > > +
> > > +/*
> > > + * Xclk 24Mhz
> > > + * max_framerate 15fps
> > > + * mipi_datarate per lane 720Mbps
> > > + */
> > > +static const struct regval ov8858_3264x2448_regs_2lane[] = {
> > > +	{0x0100, 0x00},
> > > +	{0x3501, 0x9a},// exposure M
> > > +	{0x3502, 0x20},// exposure L
> > > +	{0x3778, 0x1a},//
> > > +	{0x3808, 0x0c},// x output size H
> > > +	{0x3809, 0xc0},// x output size L
> > > +	{0x380a, 0x09},// y output size H
> > > +	{0x380b, 0x90},// y output size L
> > > +	{0x380c, 0x07},// HTS H
> > > +	{0x380d, 0x94},// HTS L
> > > +	{0x380e, 0x09},// VTS H
> > > +	{0x380f, 0xaa},// VTS L
> > > +	{0x3814, 0x01},// x odd inc
> > > +	{0x3821, 0x46},// mirror on, bin off
> > > +	{0x382a, 0x01},// y odd inc
> > > +	{0x3830, 0x06},
> > > +	{0x3836, 0x01},
> > > +	{0x3f0a, 0x00},
> > > +	{0x4001, 0x00},// total 256 black column
> > > +	{0x4022, 0x0c},// Anchor left end H
> > > +	{0x4023, 0x60},// Anchor left end L
> > > +	{0x4025, 0x36},// Anchor right start L
> > > +	{0x4027, 0x37},// Anchor right end L
> > > +	{0x402b, 0x08},// top black line number
> > > +	{0x402f, 0x08},// bottom black line number
> > > +	{0x4500, 0x58},
> > > +	{0x4600, 0x01},
> > > +	{0x4601, 0x97},
> > > +	{0x382d, 0xff},
> > > +	{REG_NULL, 0x00},
> > > +};
> > > +
> > > +static const struct regval ov8858_global_regs_r2a_4lane[] = {
> > > +	/*
> > > +	 * MIPI=720Mbps, SysClk=144Mhz,Dac Clock=360Mhz.
> > > +	 * v00_01_00 (05/29/2014) : initial setting
> > > +	 * AM19 : 3617 <- 0xC0
> > > +	 * AM20 : change FWC_6K_EN to be default 0x3618=0x5a
> > > +	 */
> > > +	{0x0103, 0x01}, // software reset for OVTATool only
> > > +	{0x0103, 0x01}, // software reset
> > > +	{0x0100, 0x00}, // software standby
> > > +	{0x0302, 0x1e}, // pll1_multi
> > > +	{0x0303, 0x00}, // pll1_divm
> > > +	{0x0304, 0x03}, // pll1_div_mipi
> > > +	{0x030e, 0x00}, // pll2_rdiv
> > > +	{0x030f, 0x04}, // pll2_divsp
> > > +	{0x0312, 0x01}, // pll2_pre_div0, pll2_r_divdac
> > > +	{0x031e, 0x0c}, // pll1_no_lat
> > > +	{0x3600, 0x00},
> > > +	{0x3601, 0x00},
> > > +	{0x3602, 0x00},
> > > +	{0x3603, 0x00},
> > > +	{0x3604, 0x22},
> > > +	{0x3605, 0x20},
> > > +	{0x3606, 0x00},
> > > +	{0x3607, 0x20},
> > > +	{0x3608, 0x11},
> > > +	{0x3609, 0x28},
> > > +	{0x360a, 0x00},
> > > +	{0x360b, 0x05},
> > > +	{0x360c, 0xd4},
> > > +	{0x360d, 0x40},
> > > +	{0x360e, 0x0c},
> > > +	{0x360f, 0x20},
> > > +	{0x3610, 0x07},
> > > +	{0x3611, 0x20},
> > > +	{0x3612, 0x88},
> > > +	{0x3613, 0x80},
> > > +	{0x3614, 0x58},
> > > +	{0x3615, 0x00},
> > > +	{0x3616, 0x4a},
> > > +	{0x3617, 0x90},
> > > +	{0x3618, 0x5a},
> > > +	{0x3619, 0x70},
> > > +	{0x361a, 0x99},
> > > +	{0x361b, 0x0a},
> > > +	{0x361c, 0x07},
> > > +	{0x361d, 0x00},
> > > +	{0x361e, 0x00},
> > > +	{0x361f, 0x00},
> > > +	{0x3638, 0xff},
> > > +	{0x3633, 0x0f},
> > > +	{0x3634, 0x0f},
> > > +	{0x3635, 0x0f},
> > > +	{0x3636, 0x12},
> > > +	{0x3645, 0x13},
> > > +	{0x3646, 0x83},
> > > +	{0x364a, 0x07},
> > > +	{0x3015, 0x01}, //
> > > +	{0x3018, 0x72}, // MIPI 4 lane
> > > +	{0x3020, 0x93}, // Clock switch output normal, pclk_div =/1
> > > +	{0x3022, 0x01}, // pd_mipi enable when rst_sync
> > > +	{0x3031, 0x0a}, // MIPI 10-bit mode
> > > +	{0x3034, 0x00}, //
> > > +	{0x3106, 0x01}, // sclk_div, sclk_pre_div
> > > +	{0x3305, 0xf1},
> > > +	{0x3308, 0x00},
> > > +	{0x3309, 0x28},
> > > +	{0x330a, 0x00},
> > > +	{0x330b, 0x20},
> > > +	{0x330c, 0x00},
> > > +	{0x330d, 0x00},
> > > +	{0x330e, 0x00},
> > > +	{0x330f, 0x40},
> > > +	{0x3307, 0x04},
> > > +	{0x3500, 0x00}, // exposure H
> > > +	{0x3501, 0x4d}, // exposure M
> > > +	{0x3502, 0x40}, // exposure L
> > > +	{0x3503, 0x80}, // gain delay ?, exposure delay 1 frame, real gain
> > > +	{0x3505, 0x80}, // gain option
> > > +	{0x3508, 0x02}, // gain H
> > > +	{0x3509, 0x00}, // gain L
> > > +	{0x350c, 0x00}, // short gain H
> > > +	{0x350d, 0x80}, // short gain L
> > > +	{0x3510, 0x00}, // short exposure H
> > > +	{0x3511, 0x02}, // short exposure M
> > > +	{0x3512, 0x00}, // short exposure L
> > > +	{0x3700, 0x30},
> > > +	{0x3701, 0x18},
> > > +	{0x3702, 0x50},
> > > +	{0x3703, 0x32},
> > > +	{0x3704, 0x28},
> > > +	{0x3705, 0x00},
> > > +	{0x3706, 0x82},
> > > +	{0x3707, 0x08},
> > > +	{0x3708, 0x48},
> > > +	{0x3709, 0x66},
> > > +	{0x370a, 0x01},
> > > +	{0x370b, 0x82},
> > > +	{0x370c, 0x07},
> > > +	{0x3718, 0x14},
> > > +	{0x3719, 0x31},
> > > +	{0x3712, 0x44},
> > > +	{0x3714, 0x24},
> > > +	{0x371e, 0x31},
> > > +	{0x371f, 0x7f},
> > > +	{0x3720, 0x0a},
> > > +	{0x3721, 0x0a},
> > > +	{0x3724, 0x0c},
> > > +	{0x3725, 0x02},
> > > +	{0x3726, 0x0c},
> > > +	{0x3728, 0x0a},
> > > +	{0x3729, 0x03},
> > > +	{0x372a, 0x06},
> > > +	{0x372b, 0xa6},
> > > +	{0x372c, 0xa6},
> > > +	{0x372d, 0xa6},
> > > +	{0x372e, 0x0c},
> > > +	{0x372f, 0x20},
> > > +	{0x3730, 0x02},
> > > +	{0x3731, 0x0c},
> > > +	{0x3732, 0x28},
> > > +	{0x3733, 0x10},
> > > +	{0x3734, 0x40},
> > > +	{0x3736, 0x30},
> > > +	{0x373a, 0x0a},
> > > +	{0x373b, 0x0b},
> > > +	{0x373c, 0x14},
> > > +	{0x373e, 0x06},
> > > +	{0x3750, 0x0a},
> > > +	{0x3751, 0x0e},
> > > +	{0x3755, 0x10},
> > > +	{0x3758, 0x00},
> > > +	{0x3759, 0x4c},
> > > +	{0x375a, 0x0c},
> > > +	{0x375b, 0x26},
> > > +	{0x375c, 0x20},
> > > +	{0x375d, 0x04},
> > > +	{0x375e, 0x00},
> > > +	{0x375f, 0x28},
> > > +	{0x3768, 0x22},
> > > +	{0x3769, 0x44},
> > > +	{0x376a, 0x44},
> > > +	{0x3761, 0x00},
> > > +	{0x3762, 0x00},
> > > +	{0x3763, 0x00},
> > > +	{0x3766, 0xff},
> > > +	{0x376b, 0x00},
> > > +	{0x3772, 0x46},
> > > +	{0x3773, 0x04},
> > > +	{0x3774, 0x2c},
> > > +	{0x3775, 0x13},
> > > +	{0x3776, 0x08},
> > > +	{0x3777, 0x00},
> > > +	{0x3778, 0x17},
> > > +	{0x37a0, 0x88},
> > > +	{0x37a1, 0x7a},
> > > +	{0x37a2, 0x7a},
> > > +	{0x37a3, 0x00},
> > > +	{0x37a4, 0x00},
> > > +	{0x37a5, 0x00},
> > > +	{0x37a6, 0x00},
> > > +	{0x37a7, 0x88},
> > > +	{0x37a8, 0x98},
> > > +	{0x37a9, 0x98},
> > > +	{0x3760, 0x00},
> > > +	{0x376f, 0x01},
> > > +	{0x37aa, 0x88},
> > > +	{0x37ab, 0x5c},
> > > +	{0x37ac, 0x5c},
> > > +	{0x37ad, 0x55},
> > > +	{0x37ae, 0x19},
> > > +	{0x37af, 0x19},
> > > +	{0x37b0, 0x00},
> > > +	{0x37b1, 0x00},
> > > +	{0x37b2, 0x00},
> > > +	{0x37b3, 0x84},
> > > +	{0x37b4, 0x84},
> > > +	{0x37b5, 0x60},
> > > +	{0x37b6, 0x00},
> > > +	{0x37b7, 0x00},
> > > +	{0x37b8, 0x00},
> > > +	{0x37b9, 0xff},
> > > +	{0x3800, 0x00}, // x start H
> > > +	{0x3801, 0x0c}, // x start L
> > > +	{0x3802, 0x00}, // y start H
> > > +	{0x3803, 0x0c}, // y start L
> > > +	{0x3804, 0x0c}, // x end H
> > > +	{0x3805, 0xd3}, // x end L
> > > +	{0x3806, 0x09}, // y end H
> > > +	{0x3807, 0xa3}, // y end L
> > > +	{0x3808, 0x06}, // x output size H
> > > +	{0x3809, 0x60}, // x output size L
> > > +	{0x380a, 0x04}, // y output size H
> > > +	{0x380b, 0xc8}, // y output size L
> > > +	{0x380c, 0x07}, // HTS H
> > > +	{0x380d, 0x88}, // HTS L
> > > +	{0x380e, 0x04}, // VTS H
> > > +	{0x380f, 0xdc}, // VTS L
> > > +	{0x3810, 0x00}, // ISP x win H
> > > +	{0x3811, 0x04}, // ISP x win L
> > > +	{0x3813, 0x02}, // ISP y win L
> > > +	{0x3814, 0x03}, // x odd inc
> > > +	{0x3815, 0x01}, // x even inc
> > > +	{0x3820, 0x00}, // vflip off
> > > +	{0x3821, 0x67}, // mirror on, bin o
> > > +	{0x382a, 0x03}, // y odd inc
> > > +	{0x382b, 0x01}, // y even inc
> > > +	{0x3830, 0x08},
> > > +	{0x3836, 0x02},
> > > +	{0x3837, 0x18},
> > > +	{0x3841, 0xff}, // window auto size enable
> > > +	{0x3846, 0x48}, //
> > > +	{0x3d85, 0x16}, // OTP power up load data/setting enable
> > > +	{0x3d8c, 0x73}, // OTP setting start High
> > > +	{0x3d8d, 0xde}, // OTP setting start Low
> > > +	{0x3f08, 0x10}, //
> > > +	{0x3f0a, 0x00}, //
> > > +	{0x4000, 0xf1}, // out_range/format_chg/gain/exp_chg trig enable
> > > +	{0x4001, 0x10}, // total 128 black column
> > > +	{0x4005, 0x10}, // BLC target L
> > > +	{0x4002, 0x27}, // value used to limit BLC offset
> > > +	{0x4009, 0x81}, // final BLC offset limitation enable
> > > +	{0x400b, 0x0c}, // DCBLC on, DCBLC manual mode on
> > > +	{0x401b, 0x00}, // zero line R coefficient
> > > +	{0x401d, 0x00}, // zoro line T coefficient
> > > +	{0x4020, 0x00}, // Anchor left start H
> > > +	{0x4021, 0x04}, // Anchor left start L
> > > +	{0x4022, 0x06}, // Anchor left end H
> > > +	{0x4023, 0x00}, // Anchor left end L
> > > +	{0x4024, 0x0f}, // Anchor right start H
> > > +	{0x4025, 0x2a}, // Anchor right start L
> > > +	{0x4026, 0x0f}, // Anchor right end H
> > > +	{0x4027, 0x2b}, // Anchor right end L
> > > +	{0x4028, 0x00}, // top zero line start
> > > +	{0x4029, 0x02}, // top zero line number
> > > +	{0x402a, 0x04}, // top black line start
> > > +	{0x402b, 0x04}, // top black line number
> > > +	{0x402c, 0x00}, // bottom zero line start
> > > +	{0x402d, 0x02}, // bottom zoro line number
> > > +	{0x402e, 0x04}, // bottom black line start
> > > +	{0x402f, 0x04}, // bottom black line number
> > > +	{0x401f, 0x00}, // interpolation x/y disable, Anchor one disable
> > > +	{0x4034, 0x3f},
> > > +	{0x403d, 0x04}, // md_precision_en
> > > +	{0x4300, 0xff}, // clip max H
> > > +	{0x4301, 0x00}, // clip min H
> > > +	{0x4302, 0x0f}, // clip min L, clip max L
> > > +	{0x4316, 0x00},
> > > +	{0x4500, 0x58},
> > > +	{0x4503, 0x18},
> > > +	{0x4600, 0x00},
> > > +	{0x4601, 0xcb},
> > > +	{0x481f, 0x32}, // clk prepare min
> > > +	{0x4837, 0x16}, // global timing
> > > +	{0x4850, 0x10}, // lane 1 = 1, lane 0 = 0
> > > +	{0x4851, 0x32}, // lane 3 = 3, lane 2 = 2
> > > +	{0x4b00, 0x2a},
> > > +	{0x4b0d, 0x00},
> > > +	{0x4d00, 0x04}, // temperature sensor
> > > +	{0x4d01, 0x18}, //
> > > +	{0x4d02, 0xc3}, //
> > > +	{0x4d03, 0xff}, //
> > > +	{0x4d04, 0xff}, //
> > > +	{0x4d05, 0xff}, // temperature sensor
> > > +	{0x5000, 0xfe}, // lenc on, slave/master AWB gain/statistics enable
> > > +	{0x5001, 0x01}, // BLC on
> > > +	{0x5002, 0x08}, // WBMATCH sensor's gain, H scale/WBMATCH/OTP_DPC off
> > > +	{0x5003, 0x20}, // DPC_DBC buffer control enable, WB
> > > +	{0x501e, 0x93}, // enable digital gain
> > > +	{0x5046, 0x12}, //
> > > +	{0x5780, 0x3e}, // DPC
> > > +	{0x5781, 0x0f}, //
> > > +	{0x5782, 0x44}, //
> > > +	{0x5783, 0x02}, //
> > > +	{0x5784, 0x01}, //
> > > +	{0x5785, 0x00}, //
> > > +	{0x5786, 0x00}, //
> > > +	{0x5787, 0x04}, //
> > > +	{0x5788, 0x02}, //
> > > +	{0x5789, 0x0f}, //
> > > +	{0x578a, 0xfd}, //
> > > +	{0x578b, 0xf5}, //
> > > +	{0x578c, 0xf5}, //
> > > +	{0x578d, 0x03}, //
> > > +	{0x578e, 0x08}, //
> > > +	{0x578f, 0x0c}, //
> > > +	{0x5790, 0x08}, //
> > > +	{0x5791, 0x04}, //
> > > +	{0x5792, 0x00}, //
> > > +	{0x5793, 0x52}, //
> > > +	{0x5794, 0xa3}, // DPC
> > > +	{0x5871, 0x0d}, // Lenc
> > > +	{0x5870, 0x18}, //
> > > +	{0x586e, 0x10}, //
> > > +	{0x586f, 0x08}, //
> > > +	{0x58f7, 0x01}, //
> > > +	{0x58f8, 0x3d}, // Lenc
> > > +	{0x5901, 0x00}, // H skip off, V skip off
> > > +	{0x5b00, 0x02}, // OTP DPC start address
> > > +	{0x5b01, 0x10}, // OTP DPC start address
> > > +	{0x5b02, 0x03}, // OTP DPC end address
> > > +	{0x5b03, 0xcf}, // OTP DPC end address
> > > +	{0x5b05, 0x6c}, // recover method = 2b11
> > > +	{0x5e00, 0x00}, // use 0x3ff to test pattern off
> > > +	{0x5e01, 0x41}, // window cut enable
> > > +	{0x382d, 0x7f}, //
> > > +	{0x4825, 0x3a}, // lpx_p_min
> > > +	{0x4826, 0x40}, // hs_prepare_min
> > > +	{0x4808, 0x25}, // wake up delay in 1/1024 s
> > > +	{0x3763, 0x18},
> > > +	{0x3768, 0xcc},
> > > +	{0x470b, 0x28},
> > > +	{0x4202, 0x00},
> > > +	{0x400d, 0x10}, // BLC offset trigger L
> > > +	{0x4040, 0x04}, // BLC gain th2
> > > +	{0x403e, 0x04}, // BLC gain th1
> > > +	{0x4041, 0xc6}, // BLC
> > > +	{0x3007, 0x80},
> > > +	{0x400a, 0x01},
> > > +	{REG_NULL, 0x00},
> > > +};
> > > +
> > > +/*
> > > + * Xclk 24Mhz
> > > + * max_framerate 60fps
> > > + * mipi_datarate per lane 720Mbps
> > > + */
> > > +static const struct regval ov8858_1632x1224_regs_4lane[] = {
> > > +	{0x0100, 0x00},
> > > +	{0x3501, 0x4d}, // exposure M
> > > +	{0x3502, 0x40}, // exposure L
> > > +	{0x3808, 0x06}, // x output size H
> > > +	{0x3809, 0x60}, // x output size L
> > > +	{0x380a, 0x04}, // y output size H
> > > +	{0x380b, 0xc8}, // y output size L
> > > +	{0x380c, 0x07}, // HTS H
> > > +	{0x380d, 0x88}, // HTS L
> > > +	{0x380e, 0x04}, // VTS H
> > > +	{0x380f, 0xdc}, // VTS L
> > > +	{0x3814, 0x03}, // x odd inc
> > > +	{0x3821, 0x67}, // mirror on, bin on
> > > +	{0x382a, 0x03}, // y odd inc
> > > +	{0x3830, 0x08},
> > > +	{0x3836, 0x02},
> > > +	{0x3f0a, 0x00},
> > > +	{0x4001, 0x10}, // total 128 black column
> > > +	{0x4022, 0x06}, // Anchor left end H
> > > +	{0x4023, 0x00}, // Anchor left end L
> > > +	{0x4025, 0x2a}, // Anchor right start L
> > > +	{0x4027, 0x2b}, // Anchor right end L
> > > +	{0x402b, 0x04}, // top black line number
> > > +	{0x402f, 0x04}, // bottom black line number
> > > +	{0x4500, 0x58},
> > > +	{0x4600, 0x00},
> > > +	{0x4601, 0xcb},
> > > +	{0x382d, 0x7f},
> > > +	{0x0100, 0x01},
> > > +	{REG_NULL, 0x00},
> > > +};
> > > +
> > > +/*
> > > + * Xclk 24Mhz
> > > + * max_framerate 30fps
> > > + * mipi_datarate per lane 720Mbps
> > > + */
> > > +static const struct regval ov8858_3264x2448_regs_4lane[] = {
> > > +	{0x0100, 0x00},
> > > +	{0x3501, 0x9a}, // exposure M
> > > +	{0x3502, 0x20}, // exposure L
> > > +	{0x3808, 0x0c}, // x output size H
> > > +	{0x3809, 0xc0}, // x output size L
> > > +	{0x380a, 0x09}, // y output size H
> > > +	{0x380b, 0x90}, // y output size L
> > > +	{0x380c, 0x07}, // HTS H
> > > +	{0x380d, 0x94}, // HTS L
> > > +	{0x380e, 0x09}, // VTS H
> > > +	{0x380f, 0xaa}, // VTS L
> > > +	{0x3814, 0x01}, // x odd inc
> > > +	{0x3821, 0x46}, // mirror on, bin off
> > > +	{0x382a, 0x01}, // y odd inc
> > > +	{0x3830, 0x06},
> > > +	{0x3836, 0x01},
> > > +	{0x3f0a, 0x00},
> > > +	{0x4001, 0x00}, // total 256 black column
> > > +	{0x4022, 0x0c}, // Anchor left end H
> > > +	{0x4023, 0x60}, // Anchor left end L
> > > +	{0x4025, 0x36}, // Anchor right start L
> > > +	{0x4027, 0x37}, // Anchor right end L
> > > +	{0x402b, 0x08}, // top black line number
> > > +	{0x402f, 0x08}, // interpolation x/y disable, Anchor one disable
> > > +	{0x4500, 0x58},
> > > +	{0x4600, 0x01},
> > > +	{0x4601, 0x97},
> > > +	{0x382d, 0xff},
> > > +	{REG_NULL, 0x00},
> > > +};
> > > +
> > > +static const struct ov8858_mode ov8858_modes[] = {
> > > +	{
> > > +		.width = 3264,
> > > +		.height = 2448,
> > > +		.exp_def = 2464,
> > > +		.hts_def = 1940 * 2,
> > > +		.vts_def = 2472,
> > > +		.reg_modes = {
> > > +			.mode_2lanes = ov8858_3264x2448_regs_2lane,
> > > +			.mode_4lanes = ov8858_3264x2448_regs_4lane,
> > > +		},
> > > +	},
> > > +	{
> > > +		.width = 1632,
> > > +		.height = 1224,
> > > +		.exp_def = 1232,
> > > +		.hts_def = 1928 * 2,
> >
> > There was no * 2 in v1, is this correct ?
> 
> No there was not, not in the 1632x1224 mode.
> However it is needed to have the frame duration calculations correct:
> 
>                 pixel_rate = 288Mhz (derived from link_freq)
>                 frame duration = hts_df * vts_def / 288
>                                = 1928 * 2 * 1244 / 288 = 16.6 msec
> 
> and the mode actually runs at 60FPS in 4 lanes mode and 30 FPS in 2
> lanes mode where the pixel rate is halved to 144Mhz.
> 
> I decided to keep the * 2 as it make it easier to locate the HTS value
> (1928(10) = 788(16)) in the registers tables
> 
> 	{0x380c, 0x07}, // HTS H
> 	{0x380d, 0x88}, // HTS L
> 
> The register documentation does not report the HTS register unit, so I
> decided to belive it has to be set to twice the number of pclk cycles
> as I've seen other sensors doing the same

I'll trust you one that :-) I had just spotted the difference between v1
and v2, and was wondering if it was intentional.

> > > +		.vts_def = 1244,
> > > +		.reg_modes = {
> > > +			.mode_2lanes = ov8858_1632x1224_regs_2lane,
> > > +			.mode_4lanes = ov8858_1632x1224_regs_4lane,
> > > +		},
> > > +	},
> > > +};
> >
> > Blank line.
> >
> > > +#define OV8858_NUM_MODES ARRAY_SIZE(ov8858_modes)
> > > +
> > > +static const s64 link_freq_menu_items[] = {
> > > +	OV8858_LINK_FREQ
> > > +};
> > > +
> > > +static const char * const ov8858_test_pattern_menu[] = {
> > > +	"Disabled",
> > > +	"Vertical Color Bar Type 1",
> > > +	"Vertical Color Bar Type 2",
> > > +	"Vertical Color Bar Type 3",
> > > +	"Vertical Color Bar Type 4"
> > > +};
> > > +
> > > +static int ov8858_write_reg(struct i2c_client *client, u32 reg, u32 val,
> > > +			    int *err)
> >
> > I'd name this function ov8858_write() (same for the read) to make line
> > shorter, I think it's equally readable.
> 
> ack
> 
> > > +{
> > > +	unsigned int len = (reg >> OV8858_REG_SIZE_SHIFT) & 3;
> > > +	u16 addr = reg & OV8858_REG_ADDR_MASK;
> > > +	u8 buf[6];
> > > +	int ret;
> > > +
> > > +	if (err && *err)
> > > +		return *err;
> > > +
> > > +	put_unaligned_be16(addr, buf);
> > > +	put_unaligned_be32(val << (8 * (4 - len)), buf + 2);
> > > +
> > > +	ret = i2c_master_send(client, buf, len + 2);
> > > +	if (ret != len + 2) {
> > > +		if (err)
> > > +			*err = ret < 0 ? ret : -EIO;
> > > +		return ret < 0 ? ret : -EIO;
> >
> > Maybe
> >
> > 		ret = ret < 0 ? ret : -EIO;
> > 		if (err)
> > 			*err = ret;
> > 		return ret;
> >
> > to avoid repeating the same code.
> >
> > I would also print an error message here, it's good to be informed of
> > errors. Same for reads.
> 
> ack
> 
> > > +	}
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static int ov8858_write_array(struct i2c_client *client, const struct regval *regs)
> > > +{
> > > +	unsigned int i;
> > > +	int ret = 0;
> > > +
> > > +	for (i = 0; ret == 0 && regs[i].addr != REG_NULL; ++i)
> >
> > Curly braces.
> >
> > > +		ov8858_write_reg(client, OV8858_REG_8BIT(regs[i].addr),
> > > +				 regs[i].val, &ret);
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static int ov8858_read_reg(struct i2c_client *client, u32 reg, u32 *val)
> > > +{
> > > +	__be16 reg_addr_be = cpu_to_be16(reg & OV8858_REG_ADDR_MASK);
> > > +	unsigned int len = (reg >> OV8858_REG_SIZE_SHIFT) & 3;
> > > +	struct i2c_msg msgs[2];
> > > +	__be32 data_be = 0;
> > > +	u8 *data_be_p;
> >
> > 	u8 *data_be_p = (u8 *)&data_be;
> >
> > > +	int ret;
> > > +
> > > +	data_be_p = (u8 *)&data_be;
> >
> > and drop this line.
> >
> > > +	/* Write register address */
> > > +	msgs[0].addr = client->addr;
> > > +	msgs[0].flags = 0;
> > > +	msgs[0].len = 2;
> > > +	msgs[0].buf = (u8 *)&reg_addr_be;
> > > +
> > > +	/* Read data from register */
> > > +	msgs[1].addr = client->addr;
> > > +	msgs[1].flags = I2C_M_RD;
> > > +	msgs[1].len = len;
> > > +	msgs[1].buf = &data_be_p[4 - len];
> > > +
> > > +	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
> > > +	if (ret != ARRAY_SIZE(msgs))
> > > +		return -EIO;
> >
> > 		return ret < 0 ? ret : -EIO;
> >
> > to preserve the error.
> >
> > > +
> > > +	*val = be32_to_cpu(data_be);
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static int ov8858_set_fmt(struct v4l2_subdev *sd,
> > > +			  struct v4l2_subdev_state *state,
> > > +			  struct v4l2_subdev_format *fmt)
> >
> > Could you move this function down after ov8858_enum_frame_sizes() to
> > have the same ordering as in ov8858_pad_ops ?
> 
> I think I asked the same to Nicholas in his previous version then I
> forgot to actually do so :)
> 
> > > +{
> > > +	struct ov8858 *ov8858 = to_ov8858(sd);
> > > +	struct v4l2_mbus_framefmt *format;
> > > +	const struct ov8858_mode *mode;
> > > +	s64 h_blank, vblank_def;
> > > +
> > > +	mode = v4l2_find_nearest_size(ov8858_modes, OV8858_NUM_MODES,
> > > +				      width, height, fmt->format.width,
> > > +				      fmt->format.height);
> > > +
> > > +	fmt->format.code = MEDIA_BUS_FMT_SBGGR10_1X10;
> > > +	fmt->format.width = mode->width;
> > > +	fmt->format.height = mode->height;
> > > +	fmt->format.field = V4L2_FIELD_NONE;
> > > +
> > > +	format = v4l2_subdev_get_pad_format(sd, state, 0);
> > > +
> > > +	if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
> > > +		ov8858->cur_mode = mode;
> > > +		h_blank = mode->hts_def - mode->width;
> > > +		__v4l2_ctrl_modify_range(ov8858->hblank, h_blank, h_blank, 1,
> > > +					 h_blank);
> > > +
> > > +		vblank_def = mode->vts_def - mode->height;
> > > +		__v4l2_ctrl_modify_range(ov8858->vblank, vblank_def,
> > > +					 OV8858_VTS_MAX - mode->height, 1,
> > > +					 vblank_def);
> > > +	}
> > > +
> > > +	*format = fmt->format;
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static int ov8858_enum_mbus_code(struct v4l2_subdev *sd,
> > > +				 struct v4l2_subdev_state *state,
> > > +				 struct v4l2_subdev_mbus_code_enum *code)
> > > +{
> > > +	if (code->index != 0)
> > > +		return -EINVAL;
> > > +
> > > +	code->code = MEDIA_BUS_FMT_SBGGR10_1X10;
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static int ov8858_enum_frame_sizes(struct v4l2_subdev *sd,
> > > +				   struct v4l2_subdev_state *state,
> > > +				   struct v4l2_subdev_frame_size_enum *fse)
> > > +{
> > > +	if (fse->index >= OV8858_NUM_MODES)
> > > +		return -EINVAL;
> > > +
> > > +	if (fse->code != MEDIA_BUS_FMT_SBGGR10_1X10)
> > > +		return -EINVAL;
> > > +
> > > +	fse->min_width  = ov8858_modes[fse->index].width;
> > > +	fse->max_width  = ov8858_modes[fse->index].width;
> > > +	fse->max_height = ov8858_modes[fse->index].height;
> > > +	fse->min_height = ov8858_modes[fse->index].height;
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static int ov8858_enable_test_pattern(struct ov8858 *ov8858, u32 pattern)
> >
> > Move this down just before ov8858_set_ctrl() to keep functions related
> > to controls together.
> >
> > > +{
> > > +	u32 val;
> > > +
> > > +	if (pattern)
> > > +		val = (pattern - 1) | OV8858_TEST_PATTERN_ENABLE;
> > > +	else
> > > +		val = OV8858_TEST_PATTERN_DISABLE;
> > > +
> > > +	return ov8858_write_reg(ov8858->client, OV8858_REG_TEST_PATTERN,
> > > +				val, NULL);
> > > +}
> > > +
> > > +static int ov8858_start_stream(struct ov8858 *ov8858)
> > > +{
> > > +	const struct regval *reg_list;
> > > +	int ret;
> > > +
> > > +	ret = ov8858_write_array(ov8858->client, ov8858->global_regs);
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	if (ov8858->num_lanes == 4)
> > > +		reg_list = ov8858->cur_mode->reg_modes.mode_4lanes;
> > > +	else
> > > +		reg_list = ov8858->cur_mode->reg_modes.mode_2lanes;
> > > +
> > > +	ret = ov8858_write_array(ov8858->client, reg_list);
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	/* 200 usec max to have PLL stabilize. */
> > > +	fsleep(200);
> > > +
> > > +	ret = __v4l2_ctrl_handler_setup(&ov8858->ctrl_handler);
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	ret = ov8858_write_reg(ov8858->client, OV8858_REG_CTRL_MODE,
> > > +			       OV8858_MODE_STREAMING, NULL);
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	/* t5 (fixed) = 10msec before entering streaming state */
> > > +	fsleep(10000);
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static int ov8858_stop_stream(struct ov8858 *ov8858)
> > > +{
> > > +	return ov8858_write_reg(ov8858->client, OV8858_REG_CTRL_MODE,
> > > +				OV8858_MODE_SW_STANDBY, NULL);
> > > +}
> > > +
> > > +static int ov8858_s_stream(struct v4l2_subdev *sd, int on)
> > > +{
> > > +	struct ov8858 *ov8858 = to_ov8858(sd);
> > > +	struct i2c_client *client = ov8858->client;
> > > +	struct v4l2_subdev_state *state;
> > > +	int ret = 0;
> > > +
> > > +	/* Only for locking purpose. */
> > > +	state = v4l2_subdev_lock_and_get_active_state(sd);
> >
> > You could also find the mode here from the width and height stored in
> > the state, to avoid storing cur_mode in the ov8858 structure, but that's
> > not required.
> 
> I'll see how complex is to get rid of cur_mode
> 
> > > +
> > > +	if (!!on == ov8858->streaming)
> > > +		goto unlock_and_return;
> >
> > This can't happen, so you can drop this and the streaming flag, as well
> > as the goto below and the error label.
> 
> That's something that always puzzles me: is s_stream() refcounted by
> the core ?

It's not refcounted by the core as such, but it's not supposed to be
called to enable and already enabled subdev (or the other way around).
It's up to the caller to ensure this.

> > > +
> > > +	if (on) {
> > > +		ret = pm_runtime_resume_and_get(&client->dev);
> > > +		if (ret < 0)
> > > +			goto unlock_and_return;
> > > +
> > > +		ret = ov8858_start_stream(ov8858);
> > > +		if (ret) {
> > > +			dev_err(&client->dev, "Failed to start streaming\n");
> > > +			pm_runtime_put(&client->dev);
> >
> > Please use runtime PM autosuspend to avoid costly power down/up
> > sequences when reconfiguring the sensor. See
> > https://git.linuxtv.org/pinchartl/media.git/commit/?h=imx290&id=4da70926308ffa71c059fe06027962463e54b930.
> 
> ah nicer
> 
> > > +			goto unlock_and_return;
> > > +		}
> > > +	} else {
> > > +		ov8858_stop_stream(ov8858);
> > > +		pm_runtime_put(&client->dev);
> > > +	}
> > > +
> > > +	ov8858->streaming = !!on;
> > > +
> > > +unlock_and_return:
> > > +	v4l2_subdev_unlock_state(state);
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static int ov8858_power_on(struct ov8858 *ov8858)
> > > +{
> > > +	struct device *dev = &ov8858->client->dev;
> > > +	unsigned long delay_us;
> > > +	int ret;
> > > +
> > > +	ret = clk_set_rate(ov8858->xvclk, OV8858_XVCLK_FREQ);
> > > +	if (ret < 0)
> > > +		dev_warn(dev, "Failed to set xvclk rate (24MHz)\n");
> > > +	if (clk_get_rate(ov8858->xvclk) != OV8858_XVCLK_FREQ)
> > > +		dev_warn(dev, "xvclk mismatched, modes are based on 24MHz\n");
> > > +
> > > +	ret = clk_prepare_enable(ov8858->xvclk);
> > > +	if (ret < 0) {
> > > +		dev_err(dev, "Failed to enable xvclk\n");
> > > +		return ret;
> > > +	}
> > > +
> > > +	ret = regulator_bulk_enable(OV8858_NUM_SUPPLIES, ov8858->supplies);
> > > +	if (ret < 0) {
> > > +		dev_err(dev, "Failed to enable regulators\n");
> > > +		goto disable_clk;
> > > +	}
> > > +
> > > +	/*
> > > +	 * The chip manual only suggests 8192 cycles prior to first SCCB
> > > +	 * transaction, but a double sleep between the release of gpios
> > > +	 * helps with sporadic failures observed at probe time.
> > > +	 */
> > > +	delay_us = DIV_ROUND_UP(8192, OV8858_XVCLK_FREQ / 1000 / 1000);
> > > +
> > > +	gpiod_set_value_cansleep(ov8858->reset_gpio, 0);
> > > +	fsleep(delay_us);
> > > +	gpiod_set_value_cansleep(ov8858->pwdn_gpio, 0);
> > > +	fsleep(delay_us);
> > > +
> > > +	return 0;
> > > +
> > > +disable_clk:
> > > +	clk_disable_unprepare(ov8858->xvclk);
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static void ov8858_power_off(struct ov8858 *ov8858)
> > > +{
> > > +	gpiod_set_value_cansleep(ov8858->pwdn_gpio, 1);
> > > +	clk_disable_unprepare(ov8858->xvclk);
> > > +	gpiod_set_value_cansleep(ov8858->reset_gpio, 1);
> > > +
> > > +	regulator_bulk_disable(OV8858_NUM_SUPPLIES, ov8858->supplies);
> > > +}
> > > +
> > > +static int ov8858_runtime_resume(struct device *dev)
> > > +{
> > > +	struct i2c_client *client = to_i2c_client(dev);
> > > +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> > > +	struct ov8858 *ov8858 = to_ov8858(sd);
> > > +
> > > +	return ov8858_power_on(ov8858);
> > > +}
> > > +
> > > +static int ov8858_runtime_suspend(struct device *dev)
> > > +{
> > > +	struct i2c_client *client = to_i2c_client(dev);
> > > +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> > > +	struct ov8858 *ov8858 = to_ov8858(sd);
> > > +
> > > +	ov8858_power_off(ov8858);
> > > +
> > > +	return 0;
> > > +}
> >
> > Could you move those four functions in a separate section ? They're in
> > the middle of the subdev operations, but they're not related. I like
> > section delimiters, for instance
> >
> > /* -----------------------------------------------------------------------------
> >  * Power Management
> >  */
> >
> > as it makes it easier to navigate the code in my opinion, but that's not
> > a hard requirement.
> >
> > > +
> > > +static int ov8858_init_cfg(struct v4l2_subdev *sd,
> > > +			   struct v4l2_subdev_state *sd_state)
> > > +{
> > > +	struct ov8858 *ov8858 = to_ov8858(sd);
> > > +	const struct ov8858_mode *def_mode = ov8858->cur_mode;
> > > +	struct v4l2_subdev_format fmt = {
> > > +		.which = V4L2_SUBDEV_FORMAT_TRY,
> > > +		.format = {
> > > +			.width = def_mode->width,
> > > +			.height = def_mode->height,
> > > +		},
> > > +	};
> > > +
> > > +	ov8858_set_fmt(sd, sd_state, &fmt);
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static const struct v4l2_subdev_video_ops ov8858_video_ops = {
> > > +	.s_stream = ov8858_s_stream,
> > > +};
> > > +
> > > +static const struct v4l2_subdev_pad_ops ov8858_pad_ops = {
> > > +	.init_cfg = ov8858_init_cfg,
> > > +	.enum_mbus_code = ov8858_enum_mbus_code,
> > > +	.enum_frame_size = ov8858_enum_frame_sizes,
> > > +	.get_fmt = v4l2_subdev_get_fmt,
> > > +	.set_fmt = ov8858_set_fmt,
> > > +};
> > > +
> > > +static const struct v4l2_subdev_ops ov8858_subdev_ops = {
> > > +	.video	= &ov8858_video_ops,
> > > +	.pad	= &ov8858_pad_ops,
> > > +};
> > > +
> > > +static int ov8858_set_ctrl(struct v4l2_ctrl *ctrl)
> > > +{
> > > +	struct ov8858 *ov8858 = container_of(ctrl->handler,
> > > +					     struct ov8858, ctrl_handler);
> > > +	struct i2c_client *client = ov8858->client;
> > > +	s64 max_exp;
> > > +	int ret;
> > > +
> > > +	/* Propagate change of current control to all related controls */
> > > +	switch (ctrl->id) {
> > > +	case V4L2_CID_VBLANK:
> > > +		/* Update max exposure while meeting expected vblanking */
> > > +		max_exp = ov8858->cur_mode->height + ctrl->val - 4;
> >
> > A macro for the offset (4) would be useful, as you also use it in
> > ov8858_initialize_controls().
> >
> > If you decide to drop cur_mode (or even if you keep it, to minimize
> > dependencies on it), I would instead retrieve the height from the active
> > state.
> >
> > One idea I'm toying with is to store control values in the state, which
> > would make it possibly to use TRY states to try combinations of formats,
> > selection rectangles and controls on subdevs. Using the state here
> > already would help with that in the future.
> >
> > > +		__v4l2_ctrl_modify_range(ov8858->exposure,
> > > +					 ov8858->exposure->minimum, max_exp,
> > > +					 ov8858->exposure->step,
> > > +					 ov8858->exposure->default_value);
> > > +		break;
> > > +	}
> > > +
> > > +	if (!pm_runtime_get_if_in_use(&client->dev))
> > > +		return 0;
> > > +
> > > +	switch (ctrl->id) {
> > > +	case V4L2_CID_EXPOSURE:
> > > +		/* 4 least significant bits of exposure are fractional part */
> > > +		ret = ov8858_write_reg(ov8858->client, OV8858_REG_EXPOSURE,
> > > +				       ctrl->val << 4, NULL);
> > > +		break;
> > > +	case V4L2_CID_ANALOGUE_GAIN:
> > > +		ret = 0;
> > > +		ov8858_write_reg(ov8858->client, OV8858_REG_GAIN_H,
> > > +				 (ctrl->val >> OV8858_GAIN_H_SHIFT) &
> > > +				 OV8858_GAIN_H_MASK, &ret);
> > > +		ov8858_write_reg(ov8858->client,
> > > +				 OV8858_REG_GAIN_L,
> > > +				 ctrl->val & OV8858_GAIN_L_MASK, &ret);
> > > +		break;
> > > +	case V4L2_CID_DIGITAL_GAIN:
> > > +		ret = 0;
> > > +		ov8858_write_reg(ov8858->client, OV8858_REG_DGAIN_H,
> > > +				 (ctrl->val >> OV8858_DGAIN_H_SHIFT) &
> > > +				 OV8858_DGAIN_H_MASK, &ret);
> > > +		ov8858_write_reg(ov8858->client, OV8858_REG_DGAIN_L,
> > > +				 ctrl->val & OV8858_DGAIN_L_MASK, &ret);
> > > +		break;
> > > +	case V4L2_CID_VBLANK:
> > > +		ret = ov8858_write_reg(ov8858->client, OV8858_REG_VTS,
> > > +				       ctrl->val + ov8858->cur_mode->height,
> > > +				       NULL);
> > > +		break;
> > > +	case V4L2_CID_TEST_PATTERN:
> > > +		ret = ov8858_enable_test_pattern(ov8858, ctrl->val);
> > > +		break;
> > > +	default:
> > > +		ret = -EINVAL;
> > > +		dev_warn(&client->dev, "%s Unhandled id: 0x%x\n",
> > > +			 __func__, ctrl->id);
> > > +		break;
> > > +	}
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static const struct v4l2_ctrl_ops ov8858_ctrl_ops = {
> > > +	.s_ctrl = ov8858_set_ctrl,
> > > +};
> > > +
> > > +static int ov8858_initialize_controls(struct ov8858 *ov8858)
> >
> > Other functions or structures use ctrl, maybe ov8858_init_ctrls() ?
> >
> > > +{
> > > +	struct v4l2_fwnode_device_properties props;
> > > +	struct v4l2_ctrl_handler *handler;
> > > +	const struct ov8858_mode *mode;
> > > +	s64 exposure_max, vblank_def;
> > > +	struct v4l2_ctrl *ctrl;
> > > +	u32 h_blank;
> > > +	int ret;
> > > +
> > > +	handler = &ov8858->ctrl_handler;
> > > +	mode = ov8858->cur_mode;
> >
> > You can initialize those two variables when declaring them.
> >
> > I'd use &ov8858_modes[0] here, again to avoid depending on cur_mode.
> >
> > > +	ret = v4l2_ctrl_handler_init(handler, 10);
> > > +	if (ret)
> > > +		return ret;
> > > +	handler->lock = &ov8858->mutex;
> > > +
> > > +	ctrl = v4l2_ctrl_new_int_menu(handler, NULL, V4L2_CID_LINK_FREQ,
> > > +				      0, 0, link_freq_menu_items);
> > > +	if (ctrl)
> > > +		ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> > > +
> > > +	v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE,
> > > +			  0, ov8858->pixel_rate, 1, ov8858->pixel_rate);
> > > +
> > > +	h_blank = mode->hts_def - mode->width;
> > > +	ov8858->hblank = v4l2_ctrl_new_std(handler, NULL, V4L2_CID_HBLANK,
> > > +					   h_blank, h_blank, 1, h_blank);
> > > +	if (ov8858->hblank)
> > > +		ov8858->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> > > +
> > > +	vblank_def = mode->vts_def - mode->height;
> > > +	ov8858->vblank = v4l2_ctrl_new_std(handler, &ov8858_ctrl_ops,
> > > +					   V4L2_CID_VBLANK, vblank_def,
> > > +					   OV8858_VTS_MAX - mode->height,
> > > +					   1, vblank_def);
> > > +
> > > +	exposure_max = mode->vts_def - 4;
> > > +	ov8858->exposure = v4l2_ctrl_new_std(handler, &ov8858_ctrl_ops,
> > > +					     V4L2_CID_EXPOSURE,
> > > +					     OV8858_EXPOSURE_MIN,
> > > +					     exposure_max, OV8858_EXPOSURE_STEP,
> > > +					     mode->exp_def);
> > > +
> > > +	v4l2_ctrl_new_std(handler, &ov8858_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
> > > +			  OV8858_GAIN_MIN, OV8858_GAIN_MAX, OV8858_GAIN_STEP,
> > > +			  OV8858_GAIN_DEFAULT);
> > > +
> > > +	v4l2_ctrl_new_std(handler, &ov8858_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
> > > +			  OV8858_DGAIN_MIN, OV8858_DGAIN_MAX, OV8858_DGAIN_STEP,
> > > +			  OV8858_DGAIN_DEFAULT);
> > > +
> > > +	v4l2_ctrl_new_std_menu_items(handler, &ov8858_ctrl_ops,
> > > +				     V4L2_CID_TEST_PATTERN,
> > > +				     ARRAY_SIZE(ov8858_test_pattern_menu) - 1,
> > > +				     0, 0, ov8858_test_pattern_menu);
> > > +
> > > +	if (handler->error) {
> > > +		ret = handler->error;
> > > +		goto err_free_handler;
> > > +	}
> > > +
> > > +	ret = v4l2_fwnode_device_parse(&ov8858->client->dev, &props);
> > > +	if (ret)
> > > +		goto err_free_handler;
> > > +
> > > +	ret = v4l2_ctrl_new_fwnode_properties(handler, &ov8858_ctrl_ops,
> > > +					      &props);
> > > +	if (ret)
> > > +		goto err_free_handler;
> > > +
> > > +	ov8858->subdev.ctrl_handler = handler;
> > > +
> > > +	return 0;
> > > +
> > > +err_free_handler:
> > > +	dev_err(&ov8858->client->dev, "Failed to init controls (%d)\n", ret);
> > > +	v4l2_ctrl_handler_free(handler);
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static int ov8858_check_sensor_id(struct ov8858 *ov8858,
> > > +				  struct i2c_client *client)
> > > +{
> > > +	struct device *dev = &ov8858->client->dev;
> > > +	u32 id = 0;
> > > +	int ret;
> > > +
> > > +	ret = ov8858_read_reg(client, OV8858_REG_CHIP_ID, &id);
> >
> > 	if (ret)
> > 		return ret;
> >
> > No need for an error message if you add one to ov8858_read_reg().
> >
> > > +	if (id != OV8858_CHIP_ID) {
> > > +		dev_err(dev, "Unexpected sensor id(%06x), ret(%d)\n", id, ret);
> >
> > 		dev_err(dev, "Unexpected sensor ID %06x\n", id);
> >
> > > +		return ret;
> > > +	}
> > > +
> > > +	ret = ov8858_read_reg(client, OV8858_CHIP_REVISION_REG, &id);
> > > +	if (ret) {
> > > +		dev_err(dev, "Read chip revision register error\n");
> >
> > You can drop this if you add an error message in ov8858_read_reg().
> >
> > > +		return ret;
> > > +	}
> > > +
> > > +	dev_info(dev, "Detected OV%06x sensor, REVISION 0x%x\n",
> > > +		 OV8858_CHIP_ID, id);
> >
> > 	dev_info(dev, "Detected OV8858 sensor, REVISION 0x%x\n", id);
> >
> > as OV8858_CHIP_ID is hardcoded. I think I'd make this a debug message
> > though, to avoid too many messages in the kernel log at boot time. Up to
> > you.
> >
> > > +
> > > +	if (id == OV8858_R2A) {
> > > +		ov8858->global_regs = ov8858->num_lanes == 4
> > > +				    ? ov8858_global_regs_r2a_4lane
> > > +				    : ov8858_global_regs_r2a_2lane;
> > > +	} else {
> > > +		ov8858->global_regs = ov8858_global_regs_r1a;
> > > +		dev_warn(dev, "R1A may not work well!\n");
> >
> > As you mention in your cover letter I may be tempted to drop this too,
> > but I have no idea if there are such sensors in the wild.
> 
> I have no idea, that's why I refrained from removing it.
> 
> > > +	}
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static int ov8858_configure_regulators(struct ov8858 *ov8858)
> > > +{
> > > +	unsigned int i;
> > > +
> > > +	for (i = 0; i < OV8858_NUM_SUPPLIES; i++)
> > > +		ov8858->supplies[i].supply = ov8858_supply_names[i];
> > > +
> > > +	return devm_regulator_bulk_get(&ov8858->client->dev,
> > > +				       OV8858_NUM_SUPPLIES,
> > > +				       ov8858->supplies);
> > > +}
> > > +
> > > +static int ov8858_parse_of(struct ov8858 *ov8858)
> > > +{
> > > +	struct v4l2_fwnode_endpoint vep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
> > > +	struct device *dev = &ov8858->client->dev;
> > > +	struct fwnode_handle *endpoint;
> > > +	int ret;
> > > +
> > > +	endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL);
> > > +	if (!endpoint) {
> > > +		dev_err(dev, "Failed to get endpoint\n");
> > > +		return -EINVAL;
> > > +	}
> > > +
> > > +	ret = v4l2_fwnode_endpoint_parse(endpoint, &vep);
> > > +	if (ret) {
> > > +		dev_err(dev, "Failed to parse endpoint: %d\n", ret);
> > > +		fwnode_handle_put(endpoint);
> > > +		return ret;
> > > +	}
> > > +
> > > +	ov8858->num_lanes = vep.bus.mipi_csi2.num_data_lanes;
> > > +	switch (ov8858->num_lanes) {
> > > +	case 4:
> > > +		fallthrough;
> >
> > Not needed.
> 
> Isn't it ?

Not when you have two cases that directly follow each other.

> > > +	case 2:
> > > +		break;
> > > +	default:
> > > +		dev_err(dev, "Unsupported number of data lanes %u\n",
> > > +			ov8858->num_lanes);
> > > +		fwnode_handle_put(endpoint);
> > > +		return -EINVAL;
> > > +	}
> > > +
> > > +	/* pixel rate = link frequency * 2 * lanes / bpp */
> > > +	ov8858->cur_mode = &ov8858_modes[0];
> > > +	ov8858->pixel_rate = OV8858_LINK_FREQ * 2U * ov8858->num_lanes / 10U;
> >
> > 2 and 10 ?
> 
> 2 is CSI-2 DDR mode
> 10 is the single supported format bpp

I meant 2 and 10 instead of 2U and 10U.

> > > +	ov8858->subdev.fwnode = endpoint;
> > > +
> > > +	dev_dbg(dev, "num_lanes = %u - pixel_rate = %u\n",
> >
> > s/ - /, /
> >
> > > +		ov8858->num_lanes, ov8858->pixel_rate);
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static int ov8858_probe(struct i2c_client *client)
> > > +{
> > > +	struct device *dev = &client->dev;
> > > +	struct v4l2_subdev *sd;
> > > +	struct ov8858 *ov8858;
> > > +	int ret;
> > > +
> > > +	ov8858 = devm_kzalloc(dev, sizeof(*ov8858), GFP_KERNEL);
> > > +	if (!ov8858)
> > > +		return -ENOMEM;
> > > +
> > > +	ov8858->client = client;
> > > +
> > > +	ov8858->xvclk = devm_clk_get(dev, "xvclk");
> > > +	if (IS_ERR(ov8858->xvclk))
> > > +		return dev_err_probe(dev, PTR_ERR(ov8858->xvclk),
> > > +				     "failed to get xvclk\n");
> > > +
> > > +	ov8858->reset_gpio = devm_gpiod_get_optional(dev, "reset",
> > > +						     GPIOD_OUT_HIGH);
> > > +	if (IS_ERR(ov8858->reset_gpio))
> > > +		return dev_err_probe(dev, PTR_ERR(ov8858->reset_gpio),
> > > +				     "failed to get reset gpio\n");
> > > +
> > > +	ov8858->pwdn_gpio = devm_gpiod_get_optional(dev, "powerdown",
> > > +						    GPIOD_OUT_HIGH);
> > > +	if (IS_ERR(ov8858->pwdn_gpio))
> > > +		return dev_err_probe(dev, PTR_ERR(ov8858->pwdn_gpio),
> > > +				     "failed to get powerdown gpio\n");
> > > +
> > > +	ret = ov8858_configure_regulators(ov8858);
> > > +	if (ret)
> > > +		return dev_err_probe(dev, ret, "failed to get regulators\n");
> > > +
> > > +	sd = &ov8858->subdev;
> > > +	v4l2_i2c_subdev_init(sd, client, &ov8858_subdev_ops);
> > > +
> > > +	ret = ov8858_parse_of(ov8858);
> > > +	if (ret != 0)
> >
> > 	if (ret)
> >
> > > +		return ret;
> > > +
> > > +	ret = ov8858_initialize_controls(ov8858);
> > > +	if (ret)
> >
> > You're leaking ov8858->subdev.fwnode here. In the remove handler too
> > actually.
> 
> Ah yes, I'll make sure I'll put the node even if I really dislike that
> it is get in one function and put in another
> 
> > > +		return ret;
> > > +
> > > +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> > > +	ov8858->pad.flags = MEDIA_PAD_FL_SOURCE;
> > > +	sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
> > > +	ret = media_entity_pads_init(&sd->entity, 1, &ov8858->pad);
> > > +	if (ret < 0)
> > > +		goto err_free_handler;
> > > +
> > > +	sd->state_lock = &ov8858->mutex;
> > > +	ret = v4l2_subdev_init_finalize(sd);
> > > +	if (ret < 0) {
> > > +		dev_err(&client->dev, "subdev initialization error %d\n", ret);
> > > +		goto err_clean_entity;
> > > +	}
> > > +
> > > +	ret = ov8858_power_on(ov8858);
> > > +	if (ret)
> > > +		goto err_clean_entity;
> > > +
> > > +	ret = ov8858_check_sensor_id(ov8858, client);
> > > +	if (ret)
> > > +		goto err_power_off;
> > > +
> > > +	ret = v4l2_async_register_subdev_sensor(sd);
> > > +	if (ret) {
> > > +		dev_err(dev, "v4l2 async register subdev failed\n");
> > > +		goto err_power_off;
> > > +	}
> > > +
> > > +	pm_runtime_set_active(dev);
> > > +	pm_runtime_enable(dev);
> > > +	pm_runtime_idle(dev);
> >
> > This needs to go before v4l2_async_register_subdev_sensor() or there's a
> > risk someone will start using the sensor before runtime PM is enabled.
> > See
> > https://git.linuxtv.org/pinchartl/media.git/commit/?h=imx290&id=4da70926308ffa71c059fe06027962463e54b930
> > for the required ordering.
> 
> Thanks, it's indeed more correct
> 
> > > +
> > > +	return 0;
> > > +
> > > +err_power_off:
> > > +	ov8858_power_off(ov8858);
> > > +err_clean_entity:
> > > +	media_entity_cleanup(&sd->entity);
> > > +err_free_handler:
> > > +	v4l2_ctrl_handler_free(&ov8858->ctrl_handler);
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static void ov8858_remove(struct i2c_client *client)
> > > +{
> > > +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> > > +	struct ov8858 *ov8858 = to_ov8858(sd);
> > > +
> > > +	v4l2_async_unregister_subdev(sd);
> > > +	media_entity_cleanup(&sd->entity);
> > > +	v4l2_ctrl_handler_free(&ov8858->ctrl_handler);
> > > +
> > > +	pm_runtime_disable(&client->dev);
> > > +	if (!pm_runtime_status_suspended(&client->dev))
> > > +		ov8858_power_off(ov8858);
> > > +	pm_runtime_set_suspended(&client->dev);
> > > +}
> > > +
> > > +static const struct of_device_id ov8858_of_match[] = {
> > > +	{ .compatible = "ovti,ov8858" },
> > > +	{ /* sentinel */ },
> > > +};
> > > +MODULE_DEVICE_TABLE(of, ov8858_of_match);
> > > +
> > > +static const struct dev_pm_ops ov8858_pm_ops = {
> > > +	SET_RUNTIME_PM_OPS(ov8858_runtime_suspend,
> > > +			   ov8858_runtime_resume, NULL)
> > > +};
> > > +
> > > +static struct i2c_driver ov8858_i2c_driver = {
> > > +	.driver = {
> > > +		.name = "ov8858",
> > > +		.pm = &ov8858_pm_ops,
> > > +		.of_match_table = of_match_ptr(ov8858_of_match),
> > > +	},
> > > +	.probe_new	= &ov8858_probe,
> > > +	.remove		= &ov8858_remove,
> > > +};
> > > +
> > > +module_i2c_driver(ov8858_i2c_driver);
> > > +
> > > +MODULE_DESCRIPTION("OmniVision ov8858 sensor driver");
> >
> > s/ov8858/OV8858/
> 
> Ack
> 
> > > +MODULE_LICENSE("GPL");

-- 
Regards,

Laurent Pinchart

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

end of thread, other threads:[~2023-01-08  2:08 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-01-06 20:39 [PATCH v2 0/2] media: i2c: Add driver for OmniVision OV8858 Jacopo Mondi
2023-01-06 20:39 ` [PATCH v2 1/2] dt-bindings: media: Add " Jacopo Mondi
2023-01-07 13:09   ` Krzysztof Kozlowski
2023-01-07 15:18     ` Jacopo Mondi
2023-01-07 15:30       ` Krzysztof Kozlowski
2023-01-06 20:39 ` [PATCH v2 2/2] media: i2c: Add driver for " Jacopo Mondi
2023-01-06 23:07   ` Laurent Pinchart
2023-01-07 15:17     ` Jacopo Mondi
2023-01-08  2:07       ` Laurent Pinchart

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.