linux-media.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/2] media: Add ST VGXY61 camera sensor driver
@ 2022-03-10 13:32 Benjamin Mugnier
  2022-03-10 13:32 ` [PATCH 1/2] media: dt-bindings: media: i2c: Add ST VGXY61 camera sensor binding Benjamin Mugnier
  2022-03-10 13:32 ` [PATCH 2/2] media: i2c: Add driver for ST VGXY61 camera sensor Benjamin Mugnier
  0 siblings, 2 replies; 17+ messages in thread
From: Benjamin Mugnier @ 2022-03-10 13:32 UTC (permalink / raw)
  To: linux-media
  Cc: alain.volmat, hugues.fruchet, sylvain.petinot, Benjamin Mugnier

Hello,

This series adds a driver for the ST VGXY61 camera sensor.  This camera sensor is using the i2c bus
for control and the csi-2 bus for data.

Tested on DragonBoard 410c and on Raspberry Pi 4. DT bindings are in 1/2, and the driver in
2/2.

Benjamin Mugnier (2):
  media: dt-bindings: media: i2c: Add ST VGXY61 camera sensor binding
  media: i2c: Add driver for ST VGXY61 camera sensor

 .../bindings/media/i2c/st,st-vgxy61.yaml      |  134 ++
 MAINTAINERS                                   |   10 +
 drivers/media/i2c/Kconfig                     |   11 +
 drivers/media/i2c/Makefile                    |    1 +
 drivers/media/i2c/st-vgxy61.c                 | 1919 +++++++++++++++++
 5 files changed, 2075 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml
 create mode 100644 drivers/media/i2c/st-vgxy61.c

-- 
2.25.1


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

* [PATCH 1/2] media: dt-bindings: media: i2c: Add ST VGXY61 camera sensor binding
  2022-03-10 13:32 [PATCH 0/2] media: Add ST VGXY61 camera sensor driver Benjamin Mugnier
@ 2022-03-10 13:32 ` Benjamin Mugnier
  2022-03-10 15:38   ` Dave Stevenson
  2022-03-10 13:32 ` [PATCH 2/2] media: i2c: Add driver for ST VGXY61 camera sensor Benjamin Mugnier
  1 sibling, 1 reply; 17+ messages in thread
From: Benjamin Mugnier @ 2022-03-10 13:32 UTC (permalink / raw)
  To: linux-media
  Cc: alain.volmat, hugues.fruchet, sylvain.petinot, Benjamin Mugnier

Add device tree binding for the ST VGXY61 camera sensor, and update
MAINTAINERS file.

Signed-off-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com>
---
 .../bindings/media/i2c/st,st-vgxy61.yaml      | 134 ++++++++++++++++++
 MAINTAINERS                                   |  10 ++
 2 files changed, 144 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml

diff --git a/Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml b/Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml
new file mode 100644
index 000000000000..8740ed2623e4
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml
@@ -0,0 +1,134 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+# Copyright (c) 2022 STMicroelectronics SA.
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/i2c/st,st-vgxy61.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: STMicroelectronics VGxy61 HDR Global Shutter Sensor Family Device Tree Bindings
+
+maintainers:
+  - Benjamin Mugnier <benjamin.mugnier@foss.st.com>
+  - Sylvain Petinot <sylvain.petinot@foss.st.com>
+
+description: |-
+  STMicroelectronics VGxy61 family has a CSI-2 output port. CSI-2 output is a
+  quad lanes 800Mbps per lane.
+  Supported formats are RAW8, RAW10, RAW12, RAW14 and RAW16.
+  Following part number are supported
+  - VG5661 and VG6661 are 1.6 Mpx (1464 x 1104) monochrome and color sensors.
+  Maximum frame rate is 75 fps.
+  - VG5761 and VG6761 are 2.3 Mpx (1944 x 1204) monochrome and color sensors.
+  Maximum frame rate is 60 fps.
+
+properties:
+  compatible:
+    const: st,st-vgxy61
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  clock-names:
+    description:
+      Input clock for the sensor.
+    items:
+      - const: xclk
+
+  VCORE-supply:
+    description:
+      Sensor digital core supply. Must be 1.2 volts.
+
+  VDDIO-supply:
+    description:
+      Sensor digital IO supply. Must be 1.8 volts.
+
+  VANA-supply:
+    description:
+      Sensor analog supply. Must be 2.8 volts.
+
+  reset-gpios:
+    description:
+      Reference to the GPIO connected to the reset pin, if any.
+      This is an active low signal to the vgxy61.
+
+  invert-gpios-polarity:
+    description:
+      If gpios polarity should be inversed
+    type: boolean
+
+  slave-mode:
+    description:
+      If the sensor operates in slave mode
+    type: boolean
+
+    #TODO check all this or copy from elsewhere
+  port:
+    $ref: /schemas/graph.yaml#/$defs/port-base
+    additionalProperties: false
+
+    properties:
+      endpoint:
+        $ref: /schemas/media/video-interfaces.yaml#
+        unevaluatedProperties: false
+
+        properties:
+          clock-lane:
+            description:
+              Clock lane index
+            maxItems: 1
+
+          data-lanes:
+            description:
+              CSI lanes to use
+            items:
+              - const: 1
+              - const: 2
+              - const: 3
+              - const: 4
+
+          remote-endpoint: true
+
+        required:
+          - clock-lane
+          - data-lanes
+          - remote-endpoint
+
+required:
+  - compatible
+  - clocks
+  - clock-names
+  - VCORE-supply
+  - VDDIO-supply
+  - VANA-supply
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+        vgxy61: csi2tx@10 {
+            compatible = "st,st-vgxy61";
+            reg = <0x10>;
+            status = "okay";
+            clocks = <&clk_ext_camera>;
+            clock-names = "xclk";
+            VCORE-supply = <&v1v2>;
+            VDDIO-supply = <&v1v8>;
+            VANA-supply = <&v2v8>;
+            reset-gpios = <&mfxgpio 18 GPIO_ACTIVE_LOW>;
+            port {
+                ep0: endpoint {
+                    clock-lane = <0>;
+                    data-lanes = <1 2 3 4>;
+                    remote-endpoint = <&mipi_csi2_out>;
+                };
+            };
+        };
+    };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 83d27b57016f..f358d15f68a0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18297,6 +18297,16 @@ S:	Maintained
 F:	Documentation/hwmon/stpddc60.rst
 F:	drivers/hwmon/pmbus/stpddc60.c
 
+ST VGXY61 DRIVER
+M:	Benjamin Mugnier <benjamin.mugnier@foss.st.com>
+M:	Sylvain Petinot <sylvain.petinot@foss.st.com>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+T:	git git://linuxtv.org/media_tree.git
+F:	Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.txt
+F:	drivers/media/i2c/st-vgxy61.c
+
+
 ST VL53L0X ToF RANGER(I2C) IIO DRIVER
 M:	Song Qiang <songqiang1304521@gmail.com>
 L:	linux-iio@vger.kernel.org
-- 
2.25.1


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

* [PATCH 2/2] media: i2c: Add driver for ST VGXY61 camera sensor
  2022-03-10 13:32 [PATCH 0/2] media: Add ST VGXY61 camera sensor driver Benjamin Mugnier
  2022-03-10 13:32 ` [PATCH 1/2] media: dt-bindings: media: i2c: Add ST VGXY61 camera sensor binding Benjamin Mugnier
@ 2022-03-10 13:32 ` Benjamin Mugnier
  2022-03-10 13:52   ` Kieran Bingham
  1 sibling, 1 reply; 17+ messages in thread
From: Benjamin Mugnier @ 2022-03-10 13:32 UTC (permalink / raw)
  To: linux-media
  Cc: alain.volmat, hugues.fruchet, sylvain.petinot, Benjamin Mugnier

The VGXY61 has a quad lanes CSI-2 output port running at 800mbps per
lane, and supports RAW8, RAW10, RAW12, RAW14 and RAW16 formats.
The driver handles both sensor types:
- VG5661 and VG6661: 1.6 Mpx (1464 x 1104) 75fps.
- VG5761 and VG6761: 2.3 Mpx (1944 x 1204) 60 fps.
The driver supports:
- HDR linearize mode, HDR substraction mode, and no HDR
- GPIOs LEDs strobing
- Digital binning and analog subsampling
- Horizontal and vertical flip
- Manual exposure
- Analog and digital gains
- Test patterns

Signed-off-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com>
---
 drivers/media/i2c/Kconfig     |   11 +
 drivers/media/i2c/Makefile    |    1 +
 drivers/media/i2c/st-vgxy61.c | 1919 +++++++++++++++++++++++++++++++++
 3 files changed, 1931 insertions(+)
 create mode 100644 drivers/media/i2c/st-vgxy61.c

diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index fe66093b8849..e6e9c1f19c09 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -1454,6 +1454,17 @@ config VIDEO_S5C73M3
 	  This is a V4L2 sensor driver for Samsung S5C73M3
 	  8 Mpixel camera.
 
+config VIDEO_ST_VGXY61
+	tristate "ST VGXY61 sensor support"
+	depends on OF
+	depends on GPIOLIB && VIDEO_V4L2 && I2C
+	select MEDIA_CONTROLLER
+	select VIDEO_V4L2_SUBDEV_API
+	select V4L2_FWNODE
+	help
+	  This is a Video4Linux2 sensor driver for the ST VGXY61
+	  camera sensor.
+
 endmenu
 
 menu "Lens drivers"
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index f6b80ef6f41d..0190b5edfa24 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -138,4 +138,5 @@ obj-$(CONFIG_VIDEO_MAX9271_LIB)	+= max9271.o
 obj-$(CONFIG_VIDEO_RDACM20)	+= rdacm20.o
 obj-$(CONFIG_VIDEO_RDACM21)	+= rdacm21.o
 obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o
+obj-$(CONFIG_VIDEO_ST_VGXY61)	+= st-vgxy61.o
 obj-$(CONFIG_SDR_MAX2175) += max2175.o
diff --git a/drivers/media/i2c/st-vgxy61.c b/drivers/media/i2c/st-vgxy61.c
new file mode 100644
index 000000000000..d3d816189f86
--- /dev/null
+++ b/drivers/media/i2c/st-vgxy61.c
@@ -0,0 +1,1919 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for VGXY61 global shutter sensor family driver
+ *
+ * Copyright (C) 2022 STMicroelectronics SA
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <linux/units.h>
+#include <linux/iopoll.h>
+#include <media/mipi-csi2.h>
+#include <media/v4l2-async.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-subdev.h>
+
+#define US_PER_MS					1000
+
+#define DEVICE_MODEL_ID_REG				0x0000
+#define VG5661_MODEL_ID					0x5661
+#define VG5761_MODEL_ID					0x5761
+#define VGX661_WIDTH					1464
+#define VGX661_HEIGHT					1104
+#define VGX761_WIDTH					1944
+#define VGX761_HEIGHT					1204
+#define VGX661_DEFAULT_MODE				1
+#define VGX761_DEFAULT_MODE				1
+#define VGX661_SHORT_ROT_TERM				93
+#define VGX761_SHORT_ROT_TERM				90
+#define VGXY61_EXPOS_ROT_TERM				66
+#define DEVICE_REVISION					0x0002
+#define DEVICE_FWPATCH_REVISION				0x0014
+#define DEVICE_FWPATCH_START_ADDR			0x2000
+#define DEVICE_SYSTEM_FSM				0x0020
+#define SW_STBY						0x03
+#define STREAMING					0x04
+#define DEVICE_NVM					0x0023
+#define NVM_OK						0x04
+#define DEVICE_THSENS1_TEMPERATURE			0x0044
+#define DEVICE_STBY					0x0201
+#define STBY_NO_REQ					0
+#define STBY_REQ_TMP_READ				BIT(2)
+#define DEVICE_STREAMING				0x0202
+#define REQ_NO_REQUEST					0
+#define REQ_STOP_STREAMING				BIT(0)
+#define REQ_START_STREAMING				BIT(1)
+#define DEVICE_EXT_CLOCK				0x0220
+#define DEVICE_CLK_PLL_PREDIV				0x0224
+#define DEVICE_CLK_SYS_PLL_MULT				0x0225
+#define DEVICE_GPIO_0_CTRL				0x0236
+#define DEVICE_GPIO_1_CTRL				0x0237
+#define DEVICE_GPIO_2_CTRL				0x0238
+#define DEVICE_GPIO_3_CTRL				0x0239
+#define DEVICE_SIGNALS_POLARITY_CTRL			0x023b
+#define DEVICE_LINE_LENGTH				0x0300
+#define DEVICE_ORIENTATION				0x0302
+#define DEVICE_VT_CTRL					0x0304
+#define DEVICE_FORMAT_CTRL				0x0305
+#define DEVICE_OIF_CTRL					0x0306
+#define DEVICE_OIF_ROI0_CTRL				0x030a
+#define DEVICE_ROI0_START_H				0x0400
+#define DEVICE_ROI0_START_V				0x0402
+#define DEVICE_ROI0_END_H				0x0404
+#define DEVICE_ROI0_END_V				0x0406
+#define DEVICE_PATGEN_CTRL				0x0440
+#define DEVICE_FRAME_CONTENT_CTRL			0x0478
+#define DEVICE_COARSE_EXPOSURE_LONG			0x0500
+#define DEVICE_COARSE_EXPOSURE_SHORT			0x0504
+#define DEVICE_ANALOG_GAIN				0x0508
+#define DEVICE_DIGITAL_GAIN_LONG			0x050a
+#define DEVICE_DIGITAL_GAIN_SHORT			0x0512
+#define DEVICE_FRAME_LENGTH				0x051a
+#define DEVICE_SIGNALS_CTRL				0x0522
+#define DEVICE_STROBE_LONG_START_DELAY			0x0528
+#define DEVICE_STROBE_LONG_END_DELAY			0x052a
+#define DEVICE_STROBE_SHORT_START_DELAY			0x052c
+#define DEVICE_STROBE_SHORT_END_DELAY			0x052e
+#define DEVICE_READOUT_CTRL				0x0530
+#define DEVICE_HDR_CTRL					0x0532
+#define DEVICE_PATGEN_LONG_DATA_GR			0x092c
+#define DEVICE_PATGEN_LONG_DATA_R			0x092e
+#define DEVICE_PATGEN_LONG_DATA_B			0x0930
+#define DEVICE_PATGEN_LONG_DATA_GB			0x0932
+#define DEVICE_PATGEN_SHORT_DATA_GR			0x0950
+#define DEVICE_PATGEN_SHORT_DATA_R			0x0952
+#define DEVICE_PATGEN_SHORT_DATA_B			0x0954
+#define DEVICE_PATGEN_SHORT_DATA_GB			0x0956
+#define DEVICE_BYPASS_CTRL				0x0a60
+
+#define V4L2_CID_HDR				(V4L2_CID_USER_BASE | 0x1004)
+#define V4L2_CID_GPIOS_STROBE_LONG_START_DELAY	(V4L2_CID_USER_BASE | 0x1019)
+#define V4L2_CID_GPIOS_STROBE_LONG_END_DELAY	(V4L2_CID_USER_BASE | 0x101a)
+#define V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY	(V4L2_CID_USER_BASE | 0x101b)
+#define V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY	(V4L2_CID_USER_BASE | 0x101c)
+#define V4L2_CID_TEMPERATURE			(V4L2_CID_USER_BASE | 0x1020)
+
+#define DEVICE_FWPATCH_REVISION_MAJOR			2
+#define DEVICE_FWPATCH_REVISION_MINOR			0
+#define DEVICE_FWPATCH_REVISION_MICRO			5
+
+#define WRITE_MULTIPLE_CHUNK_MAX			16
+#define NB_GPIOS					4
+#define NB_POLARITIES					5
+
+static const u8 patch_array[] = {
+	0xbf, 0x00, 0x05, 0x20, 0x06, 0x01, 0xe0, 0xe0, 0x04, 0x80, 0xe6, 0x45,
+	0xed, 0x6f, 0xfe, 0xff, 0x14, 0x80, 0x1f, 0x84, 0x10, 0x42, 0x05, 0x7c,
+	0x01, 0xc4, 0x1e, 0x80, 0xb6, 0x42, 0x00, 0xe0, 0x1e, 0x82, 0x1e, 0xc0,
+	0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa, 0x86, 0x0d, 0x70, 0xe1,
+	0x04, 0x98, 0x15, 0x00, 0x28, 0xe0, 0x14, 0x02, 0x08, 0xfc, 0x15, 0x40,
+	0x28, 0xe0, 0x98, 0x58, 0xe0, 0xef, 0x04, 0x98, 0x0e, 0x04, 0x00, 0xf0,
+	0x15, 0x00, 0x28, 0xe0, 0x19, 0xc8, 0x15, 0x40, 0x28, 0xe0, 0xc6, 0x41,
+	0xfc, 0xe0, 0x14, 0x80, 0x1f, 0x84, 0x14, 0x02, 0xa0, 0xfc, 0x1e, 0x80,
+	0x14, 0x80, 0x14, 0x02, 0x80, 0xfb, 0x14, 0x02, 0xe0, 0xfc, 0x1e, 0x80,
+	0x14, 0xc0, 0x1f, 0x84, 0x14, 0x02, 0xa4, 0xfc, 0x1e, 0xc0, 0x14, 0xc0,
+	0x14, 0x02, 0x80, 0xfb, 0x14, 0x02, 0xe4, 0xfc, 0x1e, 0xc0, 0x0c, 0x0c,
+	0x00, 0xf2, 0x93, 0xdd, 0x86, 0x00, 0xf8, 0xe0, 0x04, 0x80, 0xc6, 0x03,
+	0x70, 0xe1, 0x0e, 0x84, 0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa,
+	0x6b, 0x80, 0x06, 0x40, 0x6c, 0xe1, 0x04, 0x80, 0x09, 0x00, 0xe0, 0xe0,
+	0x0b, 0xa1, 0x95, 0x84, 0x05, 0x0c, 0x1c, 0xe0, 0x86, 0x02, 0xf9, 0x60,
+	0xe0, 0xcf, 0x78, 0x6e, 0x80, 0xef, 0x25, 0x0c, 0x18, 0xe0, 0x05, 0x4c,
+	0x1c, 0xe0, 0x86, 0x02, 0xf9, 0x60, 0xe0, 0xcf, 0x0b, 0x84, 0xd8, 0x6d,
+	0x80, 0xef, 0x05, 0x4c, 0x18, 0xe0, 0x04, 0xd8, 0x0b, 0xa5, 0x95, 0x84,
+	0x05, 0x0c, 0x2c, 0xe0, 0x06, 0x02, 0x01, 0x60, 0xe0, 0xce, 0x18, 0x6d,
+	0x80, 0xef, 0x25, 0x0c, 0x30, 0xe0, 0x05, 0x4c, 0x2c, 0xe0, 0x06, 0x02,
+	0x01, 0x60, 0xe0, 0xce, 0x0b, 0x84, 0x78, 0x6c, 0x80, 0xef, 0x05, 0x4c,
+	0x30, 0xe0, 0x0c, 0x0c, 0x00, 0xf2, 0x93, 0xdd, 0x46, 0x01, 0x70, 0xe1,
+	0x08, 0x80, 0x0b, 0xa1, 0x08, 0x5c, 0x00, 0xda, 0x06, 0x01, 0x68, 0xe1,
+	0x04, 0x80, 0x4a, 0x40, 0x84, 0xe0, 0x08, 0x5c, 0x00, 0x9a, 0x06, 0x01,
+	0xe0, 0xe0, 0x04, 0x80, 0x15, 0x00, 0x60, 0xe0, 0x19, 0xc4, 0x15, 0x40,
+	0x60, 0xe0, 0x15, 0x00, 0x78, 0xe0, 0x19, 0xc4, 0x15, 0x40, 0x78, 0xe0,
+	0x93, 0xdd, 0xc3, 0xc1, 0x46, 0x01, 0x70, 0xe1, 0x08, 0x80, 0x0b, 0xa1,
+	0x08, 0x5c, 0x00, 0xda, 0x06, 0x01, 0x68, 0xe1, 0x04, 0x80, 0x4a, 0x40,
+	0x84, 0xe0, 0x08, 0x5c, 0x00, 0x9a, 0x06, 0x01, 0xe0, 0xe0, 0x14, 0x80,
+	0x25, 0x02, 0x54, 0xe0, 0x29, 0xc4, 0x25, 0x42, 0x54, 0xe0, 0x24, 0x80,
+	0x35, 0x04, 0x6c, 0xe0, 0x39, 0xc4, 0x35, 0x44, 0x6c, 0xe0, 0x25, 0x02,
+	0x64, 0xe0, 0x29, 0xc4, 0x25, 0x42, 0x64, 0xe0, 0x04, 0x80, 0x15, 0x00,
+	0x7c, 0xe0, 0x19, 0xc4, 0x15, 0x40, 0x7c, 0xe0, 0x93, 0xdd, 0xc3, 0xc1,
+	0x4c, 0x04, 0x7c, 0xfa, 0x86, 0x40, 0x98, 0xe0, 0x14, 0x80, 0x1b, 0xa1,
+	0x06, 0x00, 0x00, 0xc0, 0x08, 0x42, 0x38, 0xdc, 0x08, 0x64, 0xa0, 0xef,
+	0x86, 0x42, 0x3c, 0xe0, 0x68, 0x49, 0x80, 0xef, 0x6b, 0x80, 0x78, 0x53,
+	0xc8, 0xef, 0xc6, 0x54, 0x6c, 0xe1, 0x7b, 0x80, 0xb5, 0x14, 0x0c, 0xf8,
+	0x05, 0x14, 0x14, 0xf8, 0x1a, 0xac, 0x8a, 0x80, 0x0b, 0x90, 0x38, 0x55,
+	0x80, 0xef, 0x1a, 0xae, 0x17, 0xc2, 0x03, 0x82, 0x88, 0x65, 0x80, 0xef,
+	0x1b, 0x80, 0x0b, 0x8e, 0x68, 0x65, 0x80, 0xef, 0x9b, 0x80, 0x0b, 0x8c,
+	0x08, 0x65, 0x80, 0xef, 0x6b, 0x80, 0x0b, 0x92, 0x1b, 0x8c, 0x98, 0x64,
+	0x80, 0xef, 0x1a, 0xec, 0x9b, 0x80, 0x0b, 0x90, 0x95, 0x54, 0x10, 0xe0,
+	0xa8, 0x53, 0x80, 0xef, 0x1a, 0xee, 0x17, 0xc2, 0x03, 0x82, 0xf8, 0x63,
+	0x80, 0xef, 0x1b, 0x80, 0x0b, 0x8e, 0xd8, 0x63, 0x80, 0xef, 0x1b, 0x8c,
+	0x68, 0x63, 0x80, 0xef, 0x6b, 0x80, 0x0b, 0x92, 0x65, 0x54, 0x14, 0xe0,
+	0x08, 0x65, 0x84, 0xef, 0x68, 0x63, 0x80, 0xef, 0x7b, 0x80, 0x0b, 0x8c,
+	0xa8, 0x64, 0x84, 0xef, 0x08, 0x63, 0x80, 0xef, 0x14, 0xe8, 0x46, 0x44,
+	0x94, 0xe1, 0x24, 0x88, 0x4a, 0x4e, 0x04, 0xe0, 0x14, 0xea, 0x1a, 0x04,
+	0x08, 0xe0, 0x0a, 0x40, 0x84, 0xed, 0x0c, 0x04, 0x00, 0xe2, 0x4a, 0x40,
+	0x04, 0xe0, 0x19, 0x16, 0xc0, 0xe0, 0x0a, 0x40, 0x84, 0xed, 0x21, 0x54,
+	0x60, 0xe0, 0x0c, 0x04, 0x00, 0xe2, 0x1b, 0xa5, 0x0e, 0xea, 0x01, 0x89,
+	0x21, 0x54, 0x64, 0xe0, 0x7e, 0xe8, 0x65, 0x82, 0x1b, 0xa7, 0x26, 0x00,
+	0x00, 0x80, 0xa5, 0x82, 0x1b, 0xa9, 0x65, 0x82, 0x1b, 0xa3, 0x01, 0x85,
+	0x16, 0x00, 0x00, 0xc0, 0x01, 0x54, 0x04, 0xf8, 0x06, 0xaa, 0x01, 0x83,
+	0x06, 0xa8, 0x65, 0x81, 0x06, 0xa8, 0x01, 0x54, 0x04, 0xf8, 0x01, 0x83,
+	0x06, 0xaa, 0x09, 0x14, 0x18, 0xf8, 0x0b, 0xa1, 0x05, 0x84, 0xc6, 0x42,
+	0xd4, 0xe0, 0x14, 0x84, 0x01, 0x83, 0x01, 0x54, 0x60, 0xe0, 0x01, 0x54,
+	0x64, 0xe0, 0x0b, 0x02, 0x90, 0xe0, 0x10, 0x02, 0x90, 0xe5, 0x01, 0x54,
+	0x88, 0xe0, 0xb5, 0x81, 0xc6, 0x40, 0xd4, 0xe0, 0x14, 0x80, 0x0b, 0x02,
+	0xe0, 0xe4, 0x10, 0x02, 0x31, 0x66, 0x02, 0xc0, 0x01, 0x54, 0x88, 0xe0,
+	0x1a, 0x84, 0x29, 0x14, 0x10, 0xe0, 0x1c, 0xaa, 0x2b, 0xa1, 0xf5, 0x82,
+	0x25, 0x14, 0x10, 0xf8, 0x2b, 0x04, 0xa8, 0xe0, 0x20, 0x44, 0x0d, 0x70,
+	0x03, 0xc0, 0x2b, 0xa1, 0x04, 0x00, 0x80, 0x9a, 0x02, 0x40, 0x84, 0x90,
+	0x03, 0x54, 0x04, 0x80, 0x4c, 0x0c, 0x7c, 0xf2, 0x93, 0xdd, 0x00, 0x00,
+	0x02, 0xa9, 0x00, 0x00, 0x64, 0x4a, 0x40, 0x00, 0x08, 0x2d, 0x58, 0xe0,
+	0xa8, 0x98, 0x40, 0x00, 0x28, 0x07, 0x34, 0xe0, 0x05, 0xb9, 0x00, 0x00,
+	0x28, 0x00, 0x41, 0x05, 0x88, 0x00, 0x41, 0x3c, 0x98, 0x00, 0x41, 0x52,
+	0x04, 0x01, 0x41, 0x79, 0x3c, 0x01, 0x41, 0x6a, 0x3d, 0xfe, 0x00, 0x00,
+};
+
+static const char * const vgxy61_test_pattern_menu[] = {
+	"Disabled",
+	"Solid",
+	"Colorbar",
+	"Gradbar",
+	"Hgrey",
+	"Vgrey",
+	"Dgrey",
+	"PN28",
+};
+
+static const char * const vgxy61_hdr_modes[] = {
+	"HDR linearize",
+	"HDR substraction",
+	"no HDR",
+};
+
+/* Regulator supplies */
+static const char * const vgxy61_supply_name[] = {
+	"VCORE",
+	"VDDIO",
+	"VANA",
+};
+
+static const s64 link_freq[] = {
+	/*
+	 * MIPI output freq is 804Mhz / 2, as it uses both rising edge and falling edges to send
+	 * data
+	 */
+	402000000ULL
+};
+
+#define VGXY61_NUM_SUPPLIES		ARRAY_SIZE(vgxy61_supply_name)
+
+/* Macro to convert index to 8.8 fixed point gain */
+#define I2FP(i)				((u32)(8192.0 / (32 - (i))))
+/* Array of possibles analog gains in 8.8 fixed point */
+static const u16 analog_gains[] = {
+	I2FP(0), I2FP(1), I2FP(2), I2FP(3), I2FP(4), I2FP(5), I2FP(6), I2FP(7), I2FP(8), I2FP(9),
+	I2FP(10), I2FP(11), I2FP(12), I2FP(13), I2FP(14), I2FP(15), I2FP(16),
+};
+
+enum bin_mode {
+	BIN_MODE_NORMAL,
+	BIN_MODE_DIGITAL_X2,
+	BIN_MODE_DIGITAL_X4,
+	BIN_MODE_ANALOG_SUB_X2,
+	BIN_MODE_ANALOG_SUB_X4,
+};
+
+enum hdr {
+	HDR_LINEAR,
+	HDR_SUB,
+	NO_HDR,
+};
+
+enum strobe_modes {
+	STROBE_DISABLED,
+	STROBE_LONG,
+	STROBE_ENABLED,
+};
+
+struct vgxy61_mode_info {
+	u32 width;
+	u32 height;
+	enum bin_mode bin_mode;
+};
+
+static const u32 vgxy61_supported_codes[] = {
+	MEDIA_BUS_FMT_SGBRG8_1X8,
+	MEDIA_BUS_FMT_SGBRG10_1X10,
+	MEDIA_BUS_FMT_SGBRG12_1X12,
+	MEDIA_BUS_FMT_SGBRG14_1X14,
+	MEDIA_BUS_FMT_SGBRG16_1X16
+};
+
+const int vgx761_sensor_frame_rates[] = {75, 60, 30, 15, 10, 5, 2};
+const int vgx661_sensor_frame_rates[] = {60, 30, 15, 10, 5, 2};
+
+static const struct vgxy61_mode_info vgx661_mode_data[] = {
+	{1464, 1104, BIN_MODE_NORMAL},
+	{1280,  720, BIN_MODE_NORMAL},
+	{ 640,  480, BIN_MODE_DIGITAL_X2},
+	{ 320,  240, BIN_MODE_DIGITAL_X4},
+};
+
+static const struct vgxy61_mode_info vgx761_mode_data[] = {
+	{1944, 1204, BIN_MODE_NORMAL},
+	{1920, 1080, BIN_MODE_NORMAL},
+	{1280,  720, BIN_MODE_NORMAL},
+	{ 640,  480, BIN_MODE_DIGITAL_X2},
+	{ 320,  240, BIN_MODE_DIGITAL_X4},
+};
+
+struct gpios_ctrls {
+	struct v4l2_ctrl *long_start;
+	struct v4l2_ctrl *long_end;
+	struct v4l2_ctrl *short_start;
+	struct v4l2_ctrl *short_end;
+};
+
+struct vgxy61_ctrls {
+	struct v4l2_ctrl_handler handler;
+	struct v4l2_ctrl *exposure;
+	struct v4l2_ctrl *analog_gain;
+	struct v4l2_ctrl *digital_gain;
+	struct gpios_ctrls gpios;
+	struct v4l2_ctrl *vflip;
+	struct v4l2_ctrl *hflip;
+	struct v4l2_ctrl *patgen;
+	struct v4l2_ctrl *hdr;
+	struct v4l2_ctrl *pixel_rate;
+	struct v4l2_ctrl *link_freq;
+	struct v4l2_ctrl *temp;
+};
+
+struct vgxy61_dev {
+	struct i2c_client *i2c_client;
+	struct v4l2_subdev sd;
+	struct media_pad pad;
+	struct regulator_bulk_data supplies[VGXY61_NUM_SUPPLIES];
+	struct gpio_desc *reset_gpio;
+	struct clk *xclk;
+	u32 clk_freq;
+	int sensor_width;
+	int sensor_height;
+	u16 oif_ctrl;
+	int nb_of_lane;
+	int data_rate_in_mbps;
+	int pclk;
+	u16 line_length;
+	int rot_term;
+	bool gpios_polarity;
+	bool slave_mode;
+	/* Lock to protect all members below */
+	struct mutex lock;
+	struct vgxy61_ctrls ctrls;
+	bool streaming;
+	struct v4l2_mbus_framefmt fmt;
+	const struct vgxy61_mode_info *sensor_modes;
+	int sensor_modes_nb;
+	const struct vgxy61_mode_info *current_mode;
+	const int *sensor_rates;
+	int sensor_rates_nb;
+	struct v4l2_fract frame_interval;
+	bool hflip;
+	bool vflip;
+	enum hdr hdr;
+	int expo_long;
+	int expo_short;
+};
+
+static u8 get_bpp_by_code(__u32 code)
+{
+	switch (code) {
+	case MEDIA_BUS_FMT_SGBRG8_1X8:
+		return 8;
+	case MEDIA_BUS_FMT_SGBRG10_1X10:
+		return 10;
+	case MEDIA_BUS_FMT_SGBRG12_1X12:
+		return 12;
+	case MEDIA_BUS_FMT_SGBRG14_1X14:
+		return 14;
+	case MEDIA_BUS_FMT_SGBRG16_1X16:
+		return 16;
+	default:
+		/* Should never happen */
+		WARN(1, "Unsupported code %d. default to 8 bpp", code);
+		return 8;
+	}
+}
+
+static u8 get_data_type_by_code(__u32 code)
+{
+	switch (code) {
+	case MEDIA_BUS_FMT_SGBRG8_1X8:
+		return MIPI_CSI2_DT_RAW8;
+	case MEDIA_BUS_FMT_SGBRG10_1X10:
+		return MIPI_CSI2_DT_RAW10;
+	case MEDIA_BUS_FMT_SGBRG12_1X12:
+		return MIPI_CSI2_DT_RAW12;
+	case MEDIA_BUS_FMT_SGBRG14_1X14:
+		return MIPI_CSI2_DT_RAW14;
+	case MEDIA_BUS_FMT_SGBRG16_1X16:
+		return MIPI_CSI2_DT_RAW16;
+	default:
+		/* Should never happen */
+		WARN(1, "Unsupported code %d. default to MIPI_CSI2_DT_RAW8 data type", code);
+		return MIPI_CSI2_DT_RAW8;
+	}
+}
+
+static void compute_pll_parameters_by_freq(u32 freq, unsigned int *prediv, unsigned int *mult)
+{
+	const unsigned int predivs[] = {1, 2, 4};
+	int i;
+
+	/*
+	 * Freq range is [6Mhz-27Mhz] already checked.
+	 * Output of divider should be in [6Mhz-12Mhz[.
+	 */
+	for (i = 0; i < ARRAY_SIZE(predivs); i++) {
+		*prediv = predivs[i];
+		if (freq / *prediv < 12 * HZ_PER_MHZ)
+			break;
+	}
+	WARN_ON(i == ARRAY_SIZE(predivs));
+
+	/*
+	 * Target freq is 804Mhz. Don't change this as it will impact image quality.
+	 */
+	*mult = ((804 * HZ_PER_MHZ) * (*prediv) + freq / 2) / freq;
+}
+
+static s32 get_pixel_rate(struct vgxy61_dev *sensor)
+{
+	return div64_u64((u64)sensor->data_rate_in_mbps * sensor->nb_of_lane,
+			 get_bpp_by_code(sensor->fmt.code));
+}
+
+static inline struct vgxy61_dev *to_vgxy61_dev(struct v4l2_subdev *sd)
+{
+	return container_of(sd, struct vgxy61_dev, sd);
+}
+
+static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl)
+{
+	return &container_of(ctrl->handler, struct vgxy61_dev, ctrls.handler)->sd;
+}
+
+static int get_chunk_size(struct vgxy61_dev *sensor)
+{
+	struct i2c_adapter *adapter = sensor->i2c_client->adapter;
+	int max_write_len = WRITE_MULTIPLE_CHUNK_MAX;
+
+	if (adapter->quirks && adapter->quirks->max_write_len)
+		max_write_len = adapter->quirks->max_write_len - 2;
+
+	max_write_len = min(max_write_len, WRITE_MULTIPLE_CHUNK_MAX);
+
+	return max(max_write_len, 1);
+}
+
+static int vgxy61_read_multiple(struct vgxy61_dev *sensor, u16 reg, u8 *val, int len)
+{
+	struct i2c_client *client = sensor->i2c_client;
+	struct i2c_msg msg[2];
+	u8 buf[2];
+	int ret;
+
+	buf[0] = reg >> 8;
+	buf[1] = reg & 0xff;
+
+	msg[0].addr = client->addr;
+	msg[0].flags = client->flags;
+	msg[0].buf = buf;
+	msg[0].len = sizeof(buf);
+
+	msg[1].addr = client->addr;
+	msg[1].flags = client->flags | I2C_M_RD;
+	msg[1].buf = val;
+	msg[1].len = len;
+
+	ret = i2c_transfer(client->adapter, msg, 2);
+	if (ret < 0) {
+		dev_dbg(&client->dev, "%s: %x i2c_transfer, reg: %x => %d\n", __func__,
+			client->addr, reg, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static inline int vgxy61_read_reg(struct vgxy61_dev *sensor, u16 reg, u8 *val)
+{
+	return vgxy61_read_multiple(sensor, reg, val, sizeof(*val));
+}
+
+static inline int vgxy61_read_reg16(struct vgxy61_dev *sensor, u16 reg, u16 *val)
+{
+	return vgxy61_read_multiple(sensor, reg, (u8 *)val, sizeof(*val));
+}
+
+static int vgxy61_write_multiple(struct vgxy61_dev *sensor, u16 reg, const u8 *data, int len)
+{
+	struct i2c_client *client = sensor->i2c_client;
+	struct i2c_msg msg;
+	u8 buf[WRITE_MULTIPLE_CHUNK_MAX + 2];
+	int i;
+	int ret;
+
+	if (len > WRITE_MULTIPLE_CHUNK_MAX)
+		return -EINVAL;
+	buf[0] = reg >> 8;
+	buf[1] = reg & 0xff;
+	for (i = 0; i < len; i++)
+		buf[i + 2] = data[i];
+
+	msg.addr = client->addr;
+	msg.flags = client->flags;
+	msg.buf = buf;
+	msg.len = len + 2;
+
+	ret = i2c_transfer(client->adapter, &msg, 1);
+	if (ret < 0) {
+		dev_dbg(&client->dev, "%s: i2c_transfer, reg: %x => %d\n", __func__, reg, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int vgxy61_write_array(struct vgxy61_dev *sensor, u16 reg, int nb, const u8 *array)
+{
+	const int chunk_size = get_chunk_size(sensor);
+	int ret;
+	int sz;
+
+	while (nb) {
+		sz = min(nb, chunk_size);
+		ret = vgxy61_write_multiple(sensor, reg, array, sz);
+		if (ret < 0)
+			return ret;
+		nb -= sz;
+		reg += sz;
+		array += sz;
+	}
+
+	return 0;
+}
+
+static inline int vgxy61_write_reg(struct vgxy61_dev *sensor, u16 reg, u8 val)
+{
+	return vgxy61_write_multiple(sensor, reg, &val, sizeof(val));
+}
+
+static inline int vgxy61_write_reg16(struct vgxy61_dev *sensor, u16 reg, u16 val)
+{
+	return vgxy61_write_multiple(sensor, reg, (u8 *)&val, sizeof(val));
+}
+
+static inline int vgxy61_write_reg32(struct vgxy61_dev *sensor, u16 reg, u32 val)
+{
+	return vgxy61_write_multiple(sensor, reg, (u8 *)&val, sizeof(val));
+}
+
+static int vgxy61_poll_reg(struct vgxy61_dev *sensor, u16 reg, u8 poll_val)
+{
+	const int loop_delay_ms = 10;
+	const int timeout_ms = 500;
+	u8 val;
+	int ret, timeout;
+
+	timeout = read_poll_timeout(vgxy61_read_reg, ret, ((ret != 0) || (val == poll_val)),
+				    loop_delay_ms * US_PER_MS, timeout_ms * US_PER_MS, false,
+				    sensor, reg, &val);
+	if (timeout)
+		return timeout;
+
+	return ret;
+}
+
+static int vgxy61_wait_state(struct vgxy61_dev *sensor, int state)
+{
+	return vgxy61_poll_reg(sensor, DEVICE_SYSTEM_FSM, state);
+}
+
+static int vgxy61_check_bw(struct vgxy61_dev *sensor)
+{
+	struct i2c_client *client = sensor->i2c_client;
+	/* Correction factor for time required between 2 lines */
+	const int mipi_margin = 1056;
+	int binning_scale = 1 << sensor->current_mode->bin_mode;
+	int bpp = get_bpp_by_code(sensor->fmt.code);
+	int max_bit_per_line;
+	int bit_per_line;
+	u64 line_rate;
+
+	line_rate = sensor->nb_of_lane * (u64)sensor->data_rate_in_mbps * sensor->line_length;
+	max_bit_per_line = div64_u64(line_rate, sensor->pclk) - mipi_margin;
+	bit_per_line = (bpp * sensor->current_mode->width) / binning_scale;
+
+	dev_dbg(&client->dev, "max_bit_per_line = %d\n", max_bit_per_line);
+	dev_dbg(&client->dev, "required bit_per_line = %d\n", bit_per_line);
+
+	return bit_per_line > max_bit_per_line ? -EINVAL : 0;
+}
+
+static int apply_exposure(struct vgxy61_dev *sensor)
+{
+	struct i2c_client *client = sensor->i2c_client;
+	int ret;
+
+	 /* We first set expo to zero to avoid forbidden parameters couple */
+	ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_SHORT, 0);
+	if (ret)
+		return ret;
+	ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_LONG, sensor->expo_long);
+	if (ret)
+		return ret;
+	ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_SHORT, sensor->expo_short);
+	if (ret)
+		return ret;
+
+	dev_dbg(&client->dev, "%s applied expo %d (short: %d)\n", __func__,
+		sensor->expo_long, sensor->expo_short);
+
+	return 0;
+}
+
+static int set_frame_rate(struct vgxy61_dev *sensor)
+{
+	u16 frame_length;
+
+	frame_length = sensor->pclk / (sensor->line_length * sensor->frame_interval.denominator);
+
+	return vgxy61_write_reg16(sensor, DEVICE_FRAME_LENGTH, frame_length);
+}
+
+static int vgxy61_get_regulators(struct vgxy61_dev *sensor)
+{
+	int i;
+
+	for (i = 0; i < VGXY61_NUM_SUPPLIES; i++)
+		sensor->supplies[i].supply = vgxy61_supply_name[i];
+
+	return devm_regulator_bulk_get(&sensor->i2c_client->dev, VGXY61_NUM_SUPPLIES,
+				       sensor->supplies);
+}
+
+static int vgxy61_apply_reset(struct vgxy61_dev *sensor)
+{
+	struct i2c_client *client = sensor->i2c_client;
+
+	dev_dbg(&client->dev, "%s applied reset\n", __func__);
+	gpiod_set_value_cansleep(sensor->reset_gpio, 0);
+	usleep_range(5000, 10000);
+	gpiod_set_value_cansleep(sensor->reset_gpio, 1);
+	usleep_range(5000, 10000);
+	gpiod_set_value_cansleep(sensor->reset_gpio, 0);
+	usleep_range(40000, 100000);
+	return vgxy61_wait_state(sensor, SW_STBY);
+}
+
+static int vgxy61_try_fmt_internal(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *fmt,
+				   const struct vgxy61_mode_info **new_mode)
+{
+	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
+	const struct vgxy61_mode_info *mode = sensor->sensor_modes;
+	unsigned int index;
+
+	/* Select code */
+	for (index = 0; index < ARRAY_SIZE(vgxy61_supported_codes); index++) {
+		if (vgxy61_supported_codes[index] == fmt->code)
+			break;
+	}
+	if (index == ARRAY_SIZE(vgxy61_supported_codes))
+		index = 0;
+
+	/* Select size */
+	do {
+		if (mode->width <= fmt->width && mode->height <= fmt->height)
+			break;
+	} while ((++mode)->width);
+	if (!mode->width)
+		mode--;
+
+	*new_mode = mode;
+	fmt->code = vgxy61_supported_codes[index];
+	fmt->width = mode->width;
+	fmt->height = mode->height;
+	fmt->colorspace = V4L2_COLORSPACE_SRGB;
+	fmt->field = V4L2_FIELD_NONE;
+
+	return 0;
+}
+
+static int vgxy61_stream_enable(struct vgxy61_dev *sensor)
+{
+	int center_x = sensor->sensor_width / 2;
+	int center_y = sensor->sensor_height / 2;
+	int scale = 1 << sensor->current_mode->bin_mode;
+	int width = sensor->current_mode->width * scale;
+	int height = sensor->current_mode->height * scale;
+	int ret;
+
+	ret = vgxy61_check_bw(sensor);
+	if (ret)
+		return ret;
+
+	/* Configure sensor */
+	ret = vgxy61_write_reg(sensor, DEVICE_FORMAT_CTRL, get_bpp_by_code(sensor->fmt.code));
+	if (ret)
+		return ret;
+	ret = vgxy61_write_reg(sensor, DEVICE_OIF_ROI0_CTRL,
+			       get_data_type_by_code(sensor->fmt.code));
+	if (ret)
+		return ret;
+
+	ret = vgxy61_write_reg(sensor, DEVICE_READOUT_CTRL, sensor->current_mode->bin_mode);
+	if (ret)
+		return ret;
+	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_H, center_x - width / 2);
+	if (ret)
+		return ret;
+	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_H, center_x + width / 2 - 1);
+	if (ret)
+		return ret;
+	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_V, center_y - height / 2);
+	if (ret)
+		return ret;
+	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_V, center_y + height / 2 - 1);
+	if (ret)
+		return ret;
+
+	ret = set_frame_rate(sensor);
+	if (ret)
+		return ret;
+
+	ret = apply_exposure(sensor);
+	if (ret)
+		return ret;
+
+	/* Start streaming */
+	ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_START_STREAMING);
+	if (ret)
+		return ret;
+
+	ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST);
+	if (ret)
+		return ret;
+
+	ret = vgxy61_wait_state(sensor, STREAMING);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int vgxy61_stream_disable(struct vgxy61_dev *sensor)
+{
+	int ret;
+
+	ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_STOP_STREAMING);
+	if (ret)
+		return ret;
+
+	ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST);
+	if (ret)
+		return ret;
+
+	ret = vgxy61_wait_state(sensor, SW_STBY);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int vgxy61_s_stream(struct v4l2_subdev *sd, int enable)
+{
+	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
+	struct i2c_client *client = sensor->i2c_client;
+	int ret = 0;
+
+	mutex_lock(&sensor->lock);
+	dev_dbg(&client->dev, "%s : requested %d / current = %d\n", __func__, enable,
+		sensor->streaming);
+	if (sensor->streaming == enable)
+		goto out;
+
+	ret = enable ? vgxy61_stream_enable(sensor) : vgxy61_stream_disable(sensor);
+	if (!ret)
+		sensor->streaming = enable;
+
+out:
+	dev_dbg(&client->dev, "%s current now = %d / %d\n", __func__, sensor->streaming, ret);
+	mutex_unlock(&sensor->lock);
+
+	return ret;
+}
+
+static int vgxy61_g_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *fi)
+{
+	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
+
+	mutex_lock(&sensor->lock);
+	fi->interval = sensor->frame_interval;
+	mutex_unlock(&sensor->lock);
+
+	return 0;
+}
+
+static int vgxy61_s_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *fi)
+{
+	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
+	struct i2c_client *client = sensor->i2c_client;
+	u64 req_int, err, min_err = ~0ULL;
+	u64 test_int;
+	int i = 0;
+	int ret;
+
+	if (fi->interval.denominator == 0)
+		return -EINVAL;
+
+	mutex_lock(&sensor->lock);
+
+	if (sensor->streaming) {
+		ret = -EBUSY;
+		goto out;
+	}
+
+	dev_dbg(&client->dev, "%s request %d/%d\n", __func__,
+		fi->interval.numerator, fi->interval.denominator);
+	/* Find nearest period */
+	req_int = div64_u64((u64)(fi->interval.numerator * 10000), fi->interval.denominator);
+	for (i = 0; i < sensor->sensor_rates_nb; i++) {
+		test_int = div64_u64((u64)10000, sensor->sensor_rates[i]);
+		err = abs(test_int - req_int);
+		if (err < min_err) {
+			fi->interval.numerator = 1;
+			fi->interval.denominator = sensor->sensor_rates[i];
+			min_err = err;
+		}
+	}
+	sensor->frame_interval = fi->interval;
+	dev_dbg(&client->dev, "%s set     %d/%d\n", __func__,
+		fi->interval.numerator, fi->interval.denominator);
+
+	ret = 0;
+out:
+	mutex_unlock(&sensor->lock);
+
+	return ret;
+}
+
+static int vgxy61_enum_mbus_code(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *sd_state,
+				 struct v4l2_subdev_mbus_code_enum *code)
+{
+	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
+	struct i2c_client *client = sensor->i2c_client;
+
+	dev_dbg(&client->dev, "%s probe index %d\n", __func__, code->index);
+	if (code->index >= ARRAY_SIZE(vgxy61_supported_codes))
+		return -EINVAL;
+
+	code->code = vgxy61_supported_codes[code->index];
+
+	return 0;
+}
+
+static int vgxy61_get_fmt(struct v4l2_subdev *sd,
+			  struct v4l2_subdev_state *sd_state,
+			  struct v4l2_subdev_format *format)
+{
+	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
+	struct i2c_client *client = sensor->i2c_client;
+	struct v4l2_mbus_framefmt *fmt;
+
+	dev_dbg(&client->dev, "%s probe %d\n", __func__, format->pad);
+	dev_dbg(&client->dev, "%s %dx%d\n", __func__, format->format.width, format->format.height);
+
+	mutex_lock(&sensor->lock);
+
+	if (format->which == V4L2_SUBDEV_FORMAT_TRY)
+		fmt = v4l2_subdev_get_try_format(&sensor->sd, sd_state, format->pad);
+	else
+		fmt = &sensor->fmt;
+
+	format->format = *fmt;
+
+	mutex_unlock(&sensor->lock);
+
+	return 0;
+}
+
+static int vgxy61_set_fmt(struct v4l2_subdev *sd,
+			  struct v4l2_subdev_state *sd_state,
+			  struct v4l2_subdev_format *format)
+{
+	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
+	struct i2c_client *client = sensor->i2c_client;
+	const struct vgxy61_mode_info *new_mode;
+	struct v4l2_mbus_framefmt *fmt;
+	int ret;
+
+	dev_dbg(&client->dev, "%s probe %d\n", __func__, format->pad);
+	dev_dbg(&client->dev, "%s %dx%d\n", __func__, format->format.width, format->format.height);
+
+	mutex_lock(&sensor->lock);
+
+	if (sensor->streaming) {
+		ret = -EBUSY;
+		goto out;
+	}
+
+	/* Find best format */
+	ret = vgxy61_try_fmt_internal(sd, &format->format, &new_mode);
+	if (ret)
+		goto out;
+
+	if (format->which == V4L2_SUBDEV_FORMAT_TRY)
+		fmt = v4l2_subdev_get_try_format(sd, sd_state, 0);
+	else
+		fmt = &sensor->fmt;
+	*fmt = format->format;
+	sensor->current_mode = new_mode;
+	/* Update pixel rate control to reflect new mode */
+	__v4l2_ctrl_s_ctrl_int64(sensor->ctrls.pixel_rate, get_pixel_rate(sensor));
+
+out:
+	mutex_unlock(&sensor->lock);
+
+	return ret;
+}
+
+static int vgxy61_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state,
+				  struct v4l2_subdev_frame_size_enum *fse)
+{
+	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
+	struct i2c_client *client = sensor->i2c_client;
+
+	dev_dbg(&client->dev, "%s for index %d\n", __func__, fse->index);
+	if (fse->index >= sensor->sensor_modes_nb)
+		return -EINVAL;
+
+	fse->min_width = sensor->sensor_modes[fse->index].width;
+	fse->max_width = fse->min_width;
+	fse->min_height = sensor->sensor_modes[fse->index].height;
+	fse->max_height = fse->min_height;
+
+	return 0;
+}
+
+static int vgxy61_enum_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state,
+				      struct v4l2_subdev_frame_interval_enum *fie)
+{
+	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
+	const struct vgxy61_mode_info *mode = sensor->sensor_modes;
+
+	if (fie->index >= sensor->sensor_rates_nb)
+		return -EINVAL;
+	do {
+		if (mode->width == fie->width && mode->height == fie->height)
+			break;
+	} while ((++mode)->width);
+	if (!mode->width)
+		return -EINVAL;
+
+	fie->interval.numerator = 1;
+	fie->interval.denominator = sensor->sensor_rates[fie->index];
+
+	return 0;
+}
+
+static int vgxy61_update_analog_gain(struct vgxy61_dev *sensor, u32 target)
+{
+	struct i2c_client *client = sensor->i2c_client;
+	unsigned int idx;
+	int ret;
+
+	/* Find smallest analog gains which is above or equal to target gain */
+	for (idx = 0; idx < ARRAY_SIZE(analog_gains); idx++) {
+		if (analog_gains[idx] >= target)
+			break;
+	}
+	/* Cap to maximum gain if no matching gain found */
+	if (idx == ARRAY_SIZE(analog_gains))
+		idx--;
+
+	/* Apply gain */
+	ret = vgxy61_write_reg(sensor, DEVICE_ANALOG_GAIN, idx);
+	if (ret)
+		return ret;
+
+	dev_dbg(&client->dev, "Target analog gain: 0x%04x\n", target);
+	dev_dbg(&client->dev, "   Set analog gain: 0x%04x\n", analog_gains[idx]);
+
+	return 0;
+}
+
+static int vgxy61_update_digital_gain(struct vgxy61_dev *sensor, u32 target)
+{
+	struct i2c_client *client = sensor->i2c_client;
+	int ret;
+
+	/*
+	 * For a monochrome version, configuring DIGITAL_GAIN_LONG_CH0 and
+	 * DIGITAL_GAIN_SHORT_CH0 is enough to configure the gain of all
+	 * four sub pixels.
+	 */
+	ret = vgxy61_write_reg16(sensor, DEVICE_DIGITAL_GAIN_LONG, target);
+	if (ret)
+		return ret;
+	ret = vgxy61_write_reg16(sensor, DEVICE_DIGITAL_GAIN_SHORT, target);
+	if (ret)
+		return ret;
+
+	dev_dbg(&client->dev, "  Set digital gain: 0x%04x\n", target);
+
+	return 0;
+}
+
+static int vgxy61_update_patgen(struct vgxy61_dev *sensor, u32 index)
+{
+	u32 pattern = index <= 3 ? index : index + 12;
+	u32 reg;
+
+	reg = (pattern << 18) | (pattern << 4);
+	if (index)
+		reg |= (1 << 16) | 1;
+	return vgxy61_write_reg32(sensor, DEVICE_PATGEN_CTRL, reg);
+}
+
+static int vgxy61_update_gpiox_strobe_mode(struct vgxy61_dev *sensor, enum strobe_modes mode,
+					   int idx)
+{
+	const u8 index2val[] = {0x0, 0x1, 0x3};
+	u16 reg;
+	int ret;
+
+	ret = vgxy61_read_reg16(sensor, DEVICE_SIGNALS_CTRL, &reg);
+	if (ret)
+		return ret;
+	reg &= ~(0xf << (idx * 4));
+	reg |= index2val[mode] << (idx * 4);
+
+	return vgxy61_write_reg16(sensor, DEVICE_SIGNALS_CTRL, reg);
+}
+
+static int vgxy61_update_gpios_strobe_mode(struct vgxy61_dev *sensor, enum hdr hdr)
+{
+	enum strobe_modes strobe_mode;
+	int i, ret;
+
+	switch (hdr) {
+	case HDR_LINEAR:
+		strobe_mode = STROBE_ENABLED;
+		break;
+	case HDR_SUB:
+	case NO_HDR:
+		strobe_mode = STROBE_LONG;
+		break;
+	default:
+		/* Should never happen */
+		WARN_ON(true);
+		break;
+	}
+
+	for (i = 0; i < NB_GPIOS; i++) {
+		ret = vgxy61_update_gpiox_strobe_mode(sensor, strobe_mode, i);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int vgxy61_update_gpios_strobe_polarity(struct vgxy61_dev *sensor, int polarity)
+{
+	struct i2c_client *client = sensor->i2c_client;
+	int ret;
+
+	if (sensor->streaming)
+		return -EBUSY;
+
+	dev_dbg(&client->dev, "setting gpios polarity: %d\n", polarity);
+
+	ret = vgxy61_write_reg(sensor, DEVICE_GPIO_0_CTRL, polarity << 1);
+	if (ret)
+		return ret;
+	ret = vgxy61_write_reg(sensor, DEVICE_GPIO_1_CTRL, polarity << 1);
+	if (ret)
+		return ret;
+	ret = vgxy61_write_reg(sensor, DEVICE_GPIO_2_CTRL, polarity << 1);
+	if (ret)
+		return ret;
+	ret = vgxy61_write_reg(sensor, DEVICE_GPIO_3_CTRL, polarity << 1);
+	if (ret)
+		return ret;
+	return vgxy61_write_reg(sensor, DEVICE_SIGNALS_POLARITY_CTRL, polarity);
+}
+
+static int find_max_long_exposure(struct vgxy61_dev *sensor, int frame_length, int short_expo_ratio)
+{
+	int first_rot_max_expo;
+	int second_rot_max_expo;
+
+	/*
+	 * Apply first rule of thumb:
+	 * frame_length < short_line_nb + sensor->sensor_height + sensor->rot_term
+	 * with short_line_nb = long_line_nb / short_expo_ratio
+	 */
+	first_rot_max_expo = ((frame_length - sensor->sensor_height - sensor->rot_term) *
+			     short_expo_ratio) - 1;
+
+	/*
+	 * Apply second rule of thumb
+	 * frame_length < short_line_nb + long_line_nb + VGXY61_EXPOS_ROT_TERM
+	 * with short_line_nb = long_line_nb / short_expo_ratio
+	 */
+	second_rot_max_expo = (((frame_length - VGXY61_EXPOS_ROT_TERM) * short_expo_ratio) /
+			      (short_expo_ratio + 1)) - 1;
+
+	/* Take the minimum of both rules */
+	return min(first_rot_max_expo, second_rot_max_expo);
+}
+
+static int vgxy61_update_exposure(struct vgxy61_dev *sensor, int long_expo_line_nb, enum hdr hdr,
+				  bool clamp)
+{
+	struct i2c_client *client = sensor->i2c_client;
+	int max_long_expo;
+	int max_short_expo = 0;
+	int short_expo_line_nb = 0;
+	/* We use a constant ratio of 10 for linear HDR mode */
+	int hdr_linear_ratio = 10;
+	u16 frame_length;
+	int ret;
+
+	ret = vgxy61_read_reg16(sensor, DEVICE_FRAME_LENGTH, &frame_length);
+	if (ret)
+		return ret;
+
+	/* Long and short integration times must not be less than 10 lines */
+	long_expo_line_nb = max(10, long_expo_line_nb);
+	long_expo_line_nb = min_t(int, frame_length, long_expo_line_nb);
+
+	/* Compute short exposure according to hdr mode and long exposure */
+	switch (hdr) {
+	case HDR_LINEAR:
+		max_long_expo = find_max_long_exposure(sensor, frame_length, hdr_linear_ratio);
+		max_short_expo = (max_long_expo + (hdr_linear_ratio / 2)) / hdr_linear_ratio;
+		short_expo_line_nb = (long_expo_line_nb + (hdr_linear_ratio / 2)) /
+				     hdr_linear_ratio;
+		break;
+	case HDR_SUB:
+		max_long_expo = find_max_long_exposure(sensor, frame_length, 1);
+		max_short_expo = max_long_expo;
+		short_expo_line_nb = long_expo_line_nb;
+		break;
+	case NO_HDR:
+		/*
+		 * As short expo is 0 here, only the second rule of thumb applies, see
+		 * find_max_long_exposure for more
+		 */
+		max_long_expo = frame_length - VGXY61_EXPOS_ROT_TERM;
+		break;
+	default:
+		/* Should never happen */
+		WARN_ON(true);
+		break;
+	}
+
+	if (long_expo_line_nb > max_long_expo) {
+		if (!clamp) {
+			dev_err(&client->dev, "Exposure %d too high (max for this hdr mode %d)\n",
+				long_expo_line_nb, max_long_expo);
+			return -EINVAL;
+		}
+		dev_warn(&client->dev, "Exposure %d too high for this hdr mode, clamping to %d\n",
+			 long_expo_line_nb, max_long_expo);
+		long_expo_line_nb = max_long_expo;
+		short_expo_line_nb = max_short_expo;
+	}
+
+	dev_dbg(&client->dev, "frame_length %d, long_expo_line_nb %d, short_expo_line_nb %d",
+		frame_length, long_expo_line_nb, short_expo_line_nb);
+
+	/* Apply exposure */
+	sensor->expo_long = long_expo_line_nb;
+	sensor->expo_short = short_expo_line_nb;
+
+	if (sensor->streaming)
+		return apply_exposure(sensor);
+	return 0;
+}
+
+static int vgxy61_update_hdr(struct vgxy61_dev *sensor, u32 index)
+{
+	const u8 index2val[] = {0x1, 0x4, 0xa};
+	int ret;
+
+	/*
+	 * Short exposure changes according to HDR mode, do it first as it can
+	 * violate sensors 'rule of thumbs' and therefore will require to change
+	 * the long exposure
+	 */
+	ret = vgxy61_update_exposure(sensor, sensor->expo_long, index, true);
+	if (ret)
+		return ret;
+
+	/* Update strobe mode according to HDR */
+	ret = vgxy61_update_gpios_strobe_mode(sensor, index);
+	if (ret)
+		return ret;
+
+	ret = vgxy61_write_reg(sensor, DEVICE_HDR_CTRL, index2val[index]);
+	if (ret)
+		return ret;
+	sensor->hdr = index;
+
+	return 0;
+}
+
+static int vgxy61_get_temp_stream_enabled(struct vgxy61_dev *sensor, int *temp)
+{
+	int ret;
+	u16 temperature;
+
+	ret = vgxy61_read_reg16(sensor, DEVICE_THSENS1_TEMPERATURE, &temperature);
+	if (ret)
+		return ret;
+
+	/* Temperature is expressed in Kelvin in Q10.2 fixed point format*/
+	temperature = (temperature & 0x0fff) >> 2;
+	temperature = kelvin_to_celsius(temperature);
+
+	*temp = temperature;
+
+	return 0;
+}
+
+static int vgxy61_get_temp_stream_disabled(struct vgxy61_dev *sensor, int *temp)
+{
+	int ret;
+
+	/* Device needs to be in standby mode if streaming is off */
+	ret = vgxy61_write_reg(sensor, DEVICE_STBY, STBY_REQ_TMP_READ);
+	if (ret)
+		return ret;
+	ret = vgxy61_poll_reg(sensor, DEVICE_STBY, STBY_NO_REQ);
+	if (ret)
+		return ret;
+
+	return vgxy61_get_temp_stream_enabled(sensor, temp);
+}
+
+static int vgxy61_get_temp(struct vgxy61_dev *sensor, int *temp)
+{
+	*temp = 0;
+	if (sensor->streaming)
+		return vgxy61_get_temp_stream_enabled(sensor, temp);
+	else
+		return vgxy61_get_temp_stream_disabled(sensor, temp);
+}
+
+static int vgxy61_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
+	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
+	int ret;
+
+	switch (ctrl->id) {
+	case V4L2_CID_EXPOSURE:
+		ret = vgxy61_update_exposure(sensor, ctrl->val, sensor->hdr, false);
+		ctrl->val = sensor->expo_long;
+		break;
+	case V4L2_CID_ANALOGUE_GAIN:
+		ret = vgxy61_update_analog_gain(sensor, ctrl->val);
+		break;
+	case V4L2_CID_DIGITAL_GAIN:
+		ret = vgxy61_update_digital_gain(sensor, ctrl->val);
+		break;
+	case V4L2_CID_VFLIP:
+	case V4L2_CID_HFLIP:
+		if (sensor->streaming) {
+			ret = -EBUSY;
+			break;
+		}
+		if (ctrl->id == V4L2_CID_VFLIP)
+			sensor->vflip = ctrl->val;
+		if (ctrl->id == V4L2_CID_HFLIP)
+			sensor->hflip = ctrl->val;
+		ret = vgxy61_write_reg(sensor, DEVICE_ORIENTATION,
+				       sensor->hflip | (sensor->vflip << 1));
+		break;
+	case V4L2_CID_TEST_PATTERN:
+		ret = vgxy61_update_patgen(sensor, ctrl->val);
+		break;
+	case V4L2_CID_HDR:
+		ret = vgxy61_update_hdr(sensor, ctrl->val);
+		break;
+	case V4L2_CID_GPIOS_STROBE_LONG_START_DELAY:
+		ret = vgxy61_write_reg(sensor, DEVICE_STROBE_LONG_START_DELAY, ctrl->val);
+		break;
+	case V4L2_CID_GPIOS_STROBE_LONG_END_DELAY:
+		ret = vgxy61_write_reg(sensor, DEVICE_STROBE_LONG_END_DELAY, ctrl->val);
+		break;
+	case V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY:
+		ret = vgxy61_write_reg(sensor, DEVICE_STROBE_SHORT_START_DELAY, ctrl->val);
+		break;
+	case V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY:
+		ret = vgxy61_write_reg(sensor, DEVICE_STROBE_SHORT_END_DELAY, ctrl->val);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static int vgxy61_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
+	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
+	int temperature;
+	int ret;
+
+	switch (ctrl->id) {
+	case V4L2_CID_TEMPERATURE:
+		ret = vgxy61_get_temp(sensor, &temperature);
+		if (ret)
+			break;
+		ret = __v4l2_ctrl_s_ctrl(ctrl, temperature);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static const struct v4l2_ctrl_ops vgxy61_ctrl_ops = {
+	.g_volatile_ctrl = vgxy61_g_volatile_ctrl,
+	.s_ctrl = vgxy61_s_ctrl,
+};
+
+static const struct v4l2_ctrl_config vgxy61_hdr_ctrl = {
+	.ops		= &vgxy61_ctrl_ops,
+	.id		= V4L2_CID_HDR,
+	.name		= "HDR mode",
+	.type		= V4L2_CTRL_TYPE_MENU,
+	.min		= 0,
+	.max		= ARRAY_SIZE(vgxy61_hdr_modes) - 1,
+	.def		= NO_HDR,
+	.qmenu		= vgxy61_hdr_modes,
+};
+
+static const struct v4l2_ctrl_config vgxy61_strobe_long_start_delay = {
+	.ops		= &vgxy61_ctrl_ops,
+	.id		= V4L2_CID_GPIOS_STROBE_LONG_START_DELAY,
+	.name		= "Long strobe mode start delay in lines",
+	.type		= V4L2_CTRL_TYPE_INTEGER,
+	.min		= -128,
+	.max		= 127,
+	.step		= 1,
+	.def		= 0,
+	.flags		= 0,
+};
+
+static const struct v4l2_ctrl_config vgxy61_strobe_long_end_delay = {
+	.ops		= &vgxy61_ctrl_ops,
+	.id		= V4L2_CID_GPIOS_STROBE_LONG_END_DELAY,
+	.name		= "Long strobe mode end delay in lines",
+	.type		= V4L2_CTRL_TYPE_INTEGER,
+	.min		= -128,
+	.max		= 127,
+	.step		= 1,
+	.def		= 0,
+	.flags		= 0,
+};
+
+static const struct v4l2_ctrl_config vgxy61_strobe_short_start_delay = {
+	.ops		= &vgxy61_ctrl_ops,
+	.id		= V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY,
+	.name		= "Short strobe mode start delay in lines",
+	.type		= V4L2_CTRL_TYPE_INTEGER,
+	.min		= -128,
+	.max		= 127,
+	.step		= 1,
+	.def		= 0,
+	.flags		= 0,
+};
+
+static const struct v4l2_ctrl_config vgxy61_strobe_short_end_delay = {
+	.ops		= &vgxy61_ctrl_ops,
+	.id		= V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY,
+	.name		= "Short strobe mode end delay in lines",
+	.type		= V4L2_CTRL_TYPE_INTEGER,
+	.min		= -128,
+	.max		= 127,
+	.step		= 1,
+	.def		= 0,
+	.flags		= 0,
+};
+
+static const struct v4l2_ctrl_config vgxy61_temp_ctrl = {
+	.ops		= &vgxy61_ctrl_ops,
+	.id		= V4L2_CID_TEMPERATURE,
+	.name		= "Temperature in °C",
+	.type		= V4L2_CTRL_TYPE_INTEGER,
+	.min		= -128,
+	.max		= 128,
+	.step		= 1,
+};
+
+static int vgxy61_init_controls(struct vgxy61_dev *sensor)
+{
+	const struct v4l2_ctrl_ops *ops = &vgxy61_ctrl_ops;
+	struct vgxy61_ctrls *ctrls = &sensor->ctrls;
+	struct v4l2_ctrl_handler *hdl = &sensor->ctrls.handler;
+	int ret;
+
+	v4l2_ctrl_handler_init(hdl, 16);
+	/* We can use our own mutex for the ctrl lock */
+	hdl->lock = &sensor->lock;
+	ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, 10, 0xffff, 1,
+					    sensor->expo_long);
+	/* This is 8.8 fixed point value */
+	ctrls->analog_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, 0x3fff, 1,
+					       0x0);
+	/* This is 8.8 fixed point value */
+	ctrls->digital_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 0, 0xfff, 1,
+						0x100);
+	ctrls->vflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, sensor->vflip);
+	ctrls->hflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, sensor->hflip);
+	ctrls->patgen = v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN,
+						     ARRAY_SIZE(vgxy61_test_pattern_menu) - 1,
+						     0, 0, vgxy61_test_pattern_menu);
+	ctrls->hdr = v4l2_ctrl_new_custom(hdl, &vgxy61_hdr_ctrl, NULL);
+	ctrls->pixel_rate = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1,
+					      get_pixel_rate(sensor));
+	ctrls->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+	ctrls->link_freq = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ,
+						  ARRAY_SIZE(link_freq) - 1, 0, link_freq);
+	ctrls->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+	/* Gpios ctrls */
+	ctrls->gpios.long_start = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_long_start_delay, NULL);
+	ctrls->gpios.long_end = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_long_end_delay, NULL);
+	ctrls->gpios.short_start = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_short_start_delay,
+							NULL);
+	ctrls->gpios.short_end = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_short_end_delay, NULL);
+	/* Temperature ctrl */
+	ctrls->temp = v4l2_ctrl_new_custom(hdl, &vgxy61_temp_ctrl, NULL);
+	ctrls->temp->flags |= V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY;
+
+	if (hdl->error) {
+		ret = hdl->error;
+		goto free_ctrls;
+	}
+
+	sensor->sd.ctrl_handler = hdl;
+	return 0;
+
+free_ctrls:
+	v4l2_ctrl_handler_free(hdl);
+	return ret;
+}
+
+static const struct v4l2_subdev_core_ops vgxy61_core_ops = {
+};
+
+static const struct v4l2_subdev_video_ops vgxy61_video_ops = {
+	.s_stream = vgxy61_s_stream,
+	.g_frame_interval = vgxy61_g_frame_interval,
+	.s_frame_interval = vgxy61_s_frame_interval,
+};
+
+static const struct v4l2_subdev_pad_ops vgxy61_pad_ops = {
+	.enum_mbus_code = vgxy61_enum_mbus_code,
+	.get_fmt = vgxy61_get_fmt,
+	.set_fmt = vgxy61_set_fmt,
+	.enum_frame_size = vgxy61_enum_frame_size,
+	.enum_frame_interval = vgxy61_enum_frame_interval,
+};
+
+static const struct v4l2_subdev_ops vgxy61_subdev_ops = {
+	.core = &vgxy61_core_ops,
+	.video = &vgxy61_video_ops,
+	.pad = &vgxy61_pad_ops,
+};
+
+static const struct media_entity_operations vgxy61_subdev_entity_ops = {
+	.link_validate = v4l2_subdev_link_validate,
+};
+
+/* Set phy polarities */
+static int vgxy61_tx_from_ep(struct vgxy61_dev *sensor, struct fwnode_handle *endpoint)
+{
+	struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
+	struct i2c_client *client = sensor->i2c_client;
+	u32 log2phy[NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
+	u32 phy2log[NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
+	int polarities[NB_POLARITIES] = {0, 0, 0, 0, 0};
+	int l_nb;
+	int p, l;
+	int ret;
+	int i;
+
+	ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep);
+	if (ret)
+		goto error_alloc;
+
+	l_nb = ep.bus.mipi_csi2.num_data_lanes;
+	if (l_nb != 1 && l_nb != 2 && l_nb != 4) {
+		dev_err(&client->dev, "invalid data lane number %d\n", l_nb);
+		goto error_ep;
+	}
+
+	/* Build log2phy, phy2log and polarities from ep info */
+	log2phy[0] = ep.bus.mipi_csi2.clock_lane;
+	phy2log[log2phy[0]] = 0;
+	for (l = 1; l < l_nb + 1; l++) {
+		log2phy[l] = ep.bus.mipi_csi2.data_lanes[l - 1];
+		phy2log[log2phy[l]] = l;
+	}
+	/*
+	 * Then fill remaining slots for every physical slot to have something valid for hardware
+	 * stuff.
+	 */
+	for (p = 0; p < NB_POLARITIES; p++) {
+		if (phy2log[p] != ~0)
+			continue;
+		phy2log[p] = l;
+		log2phy[l] = p;
+		l++;
+	}
+	for (l = 0; l < l_nb + 1; l++)
+		polarities[l] = ep.bus.mipi_csi2.lane_polarities[l];
+
+	if (log2phy[0] != 0) {
+		dev_err(&client->dev, "clk lane must be map to physical lane 0\n");
+		goto error_ep;
+	}
+	sensor->oif_ctrl = (polarities[4] << 15) + ((phy2log[4] - 1) << 13) +
+			   (polarities[3] << 12) + ((phy2log[3] - 1) << 10) +
+			   (polarities[2] <<  9) + ((phy2log[2] - 1) <<  7) +
+			   (polarities[1] <<  6) + ((phy2log[1] - 1) <<  4) +
+			   (polarities[0] <<  3) +
+			   l_nb;
+	sensor->nb_of_lane = l_nb;
+
+	dev_dbg(&client->dev, "tx uses %d lanes", l_nb);
+	for (i = 0; i < 5; i++) {
+		dev_dbg(&client->dev, "log2phy[%d] = %d\n", i, log2phy[i]);
+		dev_dbg(&client->dev, "phy2log[%d] = %d\n", i, phy2log[i]);
+		dev_dbg(&client->dev, "polarity[%d] = %d\n", i, polarities[i]);
+	}
+	dev_dbg(&client->dev, "oif_ctrl = 0x%04x\n", sensor->oif_ctrl);
+
+	v4l2_fwnode_endpoint_free(&ep);
+
+	return 0;
+
+error_ep:
+	v4l2_fwnode_endpoint_free(&ep);
+error_alloc:
+
+	return -EINVAL;
+}
+
+static int vgxy61_configure(struct vgxy61_dev *sensor)
+{
+	struct i2c_client *client = sensor->i2c_client;
+	int sensor_freq;
+	unsigned int prediv;
+	unsigned int mult;
+	int ret;
+
+	compute_pll_parameters_by_freq(sensor->clk_freq, &prediv, &mult);
+	sensor_freq = (mult * sensor->clk_freq) / prediv;
+	/* Frequency to data rate is 1:1 ratio for MIPI */
+	sensor->data_rate_in_mbps = sensor_freq;
+	/* Video timing ISP path (pixel clock)  requires 804/5 mhz = 160 mhz */
+	sensor->pclk = sensor_freq / 5;
+
+	/* Cache line_length value */
+	ret = vgxy61_read_reg16(sensor, DEVICE_LINE_LENGTH, &sensor->line_length);
+	if (ret)
+		return ret;
+	/* Configure clocks */
+	ret = vgxy61_write_reg32(sensor, DEVICE_EXT_CLOCK, sensor->clk_freq);
+	if (ret)
+		return ret;
+	ret = vgxy61_write_reg(sensor, DEVICE_CLK_PLL_PREDIV, prediv);
+	if (ret)
+		return ret;
+	ret = vgxy61_write_reg(sensor, DEVICE_CLK_SYS_PLL_MULT, mult);
+	if (ret)
+		return ret;
+	/* Configure interface */
+	ret = vgxy61_write_reg16(sensor, DEVICE_OIF_CTRL, sensor->oif_ctrl);
+	if (ret)
+		return ret;
+	/* Disable pwm compression */
+	ret = vgxy61_write_reg(sensor, DEVICE_FRAME_CONTENT_CTRL, 0);
+	if (ret)
+		return ret;
+	/* Disable asil lines */
+	ret = vgxy61_write_reg(sensor, DEVICE_BYPASS_CTRL, 4);
+	if (ret)
+		return ret;
+	/* Set gpios polarity according to device tree value */
+	ret = vgxy61_update_gpios_strobe_polarity(sensor, sensor->gpios_polarity);
+	if (ret)
+		return ret;
+	/* HDR mode */
+	ret = vgxy61_update_hdr(sensor, sensor->hdr);
+	if (ret)
+		return ret;
+	/* Slave mode */
+	ret = vgxy61_write_reg(sensor, DEVICE_VT_CTRL, sensor->slave_mode);
+	if (ret)
+		return ret;
+	/* Set pattern generator solid to middle value */
+	ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_GR, 0x800);
+	if (ret)
+		return ret;
+	ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_R, 0x800);
+	if (ret)
+		return ret;
+	ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_B, 0x800);
+	if (ret)
+		return ret;
+	ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_GB, 0x800);
+	if (ret)
+		return ret;
+	ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_GR, 0x800);
+	if (ret)
+		return ret;
+	ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_R, 0x800);
+	if (ret)
+		return ret;
+	ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_B, 0x800);
+	if (ret)
+		return ret;
+	ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_GB, 0x800);
+	if (ret)
+		return ret;
+
+	dev_dbg(&client->dev, "clock prediv = %d\n", prediv);
+	dev_dbg(&client->dev, "clock mult = %d\n", mult);
+	dev_dbg(&client->dev, "data rate = %d mbps\n",
+		sensor->data_rate_in_mbps);
+
+	return 0;
+}
+
+static int vgxy61_patch(struct vgxy61_dev *sensor)
+{
+	struct i2c_client *client = sensor->i2c_client;
+	u16 patch;
+	int ret;
+
+	ret = vgxy61_write_array(sensor, DEVICE_FWPATCH_START_ADDR, sizeof(patch_array),
+				 patch_array);
+	if (ret)
+		return ret;
+
+	ret = vgxy61_write_reg(sensor, DEVICE_STBY, 0x10);
+	if (ret)
+		return ret;
+
+	ret = vgxy61_poll_reg(sensor, DEVICE_STBY, 0);
+	if (ret)
+		return ret;
+
+	ret = vgxy61_read_reg16(sensor, DEVICE_FWPATCH_REVISION, &patch);
+	if (ret)
+		return ret;
+
+	if (patch != (DEVICE_FWPATCH_REVISION_MAJOR << 12) +
+		     (DEVICE_FWPATCH_REVISION_MINOR << 8) +
+		     DEVICE_FWPATCH_REVISION_MICRO) {
+		dev_err(&client->dev, "bad patch version expected %d.%d.%d got %d.%d.%d\n",
+			DEVICE_FWPATCH_REVISION_MAJOR,
+			DEVICE_FWPATCH_REVISION_MINOR,
+			DEVICE_FWPATCH_REVISION_MICRO,
+			patch >> 12, (patch >> 8) & 0x0f, patch & 0xff);
+		return -ENODEV;
+	}
+	dev_dbg(&client->dev, "patch %d.%d.%d applied\n",
+		patch >> 12, (patch >> 8) & 0x0f, patch & 0xff);
+
+	return 0;
+}
+
+static int vgxy61_detect_cut_version(struct vgxy61_dev *sensor)
+{
+	struct i2c_client *client = sensor->i2c_client;
+	u16 device_rev;
+	int ret;
+
+	ret = vgxy61_read_reg16(sensor, DEVICE_REVISION, &device_rev);
+	if (ret)
+		return ret;
+
+	switch (device_rev >> 8) {
+	case 0xA:
+		dev_info(&client->dev, "Cut1 detected\n");
+		dev_err(&client->dev, "Cut1 not supported by this driver\n");
+		return -ENODEV;
+	case 0xB:
+		dev_info(&client->dev, "Cut2 detected\n");
+		return 0;
+	case 0xC:
+		dev_info(&client->dev, "Cut3 detected\n");
+		return 0;
+	default:
+		dev_err(&client->dev, "Unable to detect cut version\n");
+		return -ENODEV;
+	}
+}
+
+static int vgxy61_detect(struct vgxy61_dev *sensor)
+{
+	struct i2c_client *client = sensor->i2c_client;
+	u16 id = 0;
+	int ret;
+	u8 st;
+
+	ret = vgxy61_read_reg16(sensor, DEVICE_MODEL_ID_REG, &id);
+	if (ret)
+		return ret;
+	if (id != VG5661_MODEL_ID && id != VG5761_MODEL_ID) {
+		dev_warn(&client->dev, "Unsupported sensor id %x\n", id);
+		return -ENODEV;
+	}
+	dev_dbg(&client->dev, "detected sensor id = 0x%04x\n", id);
+
+	if (id == VG5761_MODEL_ID) {
+		sensor->sensor_width = VGX761_WIDTH;
+		sensor->sensor_height = VGX761_HEIGHT;
+		sensor->sensor_modes = vgx761_mode_data;
+		sensor->sensor_modes_nb = ARRAY_SIZE(vgx761_mode_data);
+		sensor->current_mode = &vgx761_mode_data[VGX761_DEFAULT_MODE];
+		sensor->rot_term = VGX761_SHORT_ROT_TERM;
+		sensor->sensor_rates = vgx761_sensor_frame_rates;
+		sensor->sensor_rates_nb = ARRAY_SIZE(vgx761_sensor_frame_rates);
+	} else if (id == VG5661_MODEL_ID) {
+		sensor->sensor_width = VGX661_WIDTH;
+		sensor->sensor_height = VGX661_HEIGHT;
+		sensor->sensor_modes = vgx661_mode_data;
+		sensor->sensor_modes_nb = ARRAY_SIZE(vgx661_mode_data);
+		sensor->current_mode = &vgx661_mode_data[VGX661_DEFAULT_MODE];
+		sensor->rot_term = VGX661_SHORT_ROT_TERM;
+		sensor->sensor_rates = vgx661_sensor_frame_rates;
+		sensor->sensor_rates_nb = ARRAY_SIZE(vgx661_sensor_frame_rates);
+	}
+
+	ret = vgxy61_wait_state(sensor, SW_STBY);
+	if (ret)
+		return ret;
+
+	ret = vgxy61_read_reg(sensor, DEVICE_NVM, &st);
+	if (ret)
+		return ret;
+	if (st != NVM_OK)
+		dev_warn(&client->dev, "Bad nvm state got %d\n", st);
+
+	/* Detect cut version */
+	ret = vgxy61_detect_cut_version(sensor);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int vgxy61_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct fwnode_handle *endpoint;
+	struct vgxy61_dev *sensor;
+	int ret;
+
+	sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
+	if (!sensor)
+		return -ENOMEM;
+
+	sensor->i2c_client = client;
+	sensor->streaming = false;
+	sensor->fmt.code = MEDIA_BUS_FMT_SGBRG8_1X8;
+	sensor->fmt.field = V4L2_FIELD_NONE;
+	sensor->fmt.colorspace = V4L2_COLORSPACE_SRGB;
+	sensor->frame_interval.numerator = 1;
+	sensor->frame_interval.denominator = 60;
+	sensor->hdr = NO_HDR;
+	sensor->expo_long = 200;
+	sensor->expo_short = 0;
+	sensor->hflip = false;
+	sensor->vflip = false;
+
+	endpoint = fwnode_graph_get_next_endpoint(of_fwnode_handle(dev->of_node), NULL);
+	if (!endpoint) {
+		dev_err(dev, "endpoint node not found\n");
+		return -EINVAL;
+	}
+
+	ret = vgxy61_tx_from_ep(sensor, endpoint);
+	fwnode_handle_put(endpoint);
+	if (ret) {
+		dev_err(dev, "Failed to parse endpoint %d\n", ret);
+		return ret;
+	}
+
+	sensor->xclk = devm_clk_get(dev, "xclk");
+	if (IS_ERR(sensor->xclk)) {
+		dev_err(dev, "failed to get xclk\n");
+		return PTR_ERR(sensor->xclk);
+	}
+	sensor->clk_freq = clk_get_rate(sensor->xclk);
+	if (sensor->clk_freq < 6000000 || sensor->clk_freq > 27000000) {
+		dev_err(dev, "Only 6Mhz-27Mhz clock range supported. provide %d Hz\n",
+			sensor->clk_freq);
+		return -EINVAL;
+	}
+	sensor->gpios_polarity = of_property_read_bool(dev->of_node, "invert-gpios-polarity");
+	sensor->slave_mode = of_property_read_bool(dev->of_node, "slave-mode");
+
+	v4l2_i2c_subdev_init(&sensor->sd, client, &vgxy61_subdev_ops);
+	sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+	sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
+	sensor->sd.entity.ops = &vgxy61_subdev_entity_ops;
+	sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
+
+	ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
+	if (ret) {
+		dev_err(&client->dev, "pads init failed %d\n", ret);
+		return ret;
+	}
+
+	/* Request optional reset pin */
+	sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+
+	ret = vgxy61_get_regulators(sensor);
+	if (ret) {
+		dev_err(&client->dev, "failed to get regulators %d\n", ret);
+		goto entity_cleanup;
+	}
+
+	ret = regulator_bulk_enable(VGXY61_NUM_SUPPLIES, sensor->supplies);
+	if (ret) {
+		dev_err(&client->dev, "failed to enable regulators %d\n", ret);
+		goto entity_cleanup;
+	}
+
+	ret = clk_prepare_enable(sensor->xclk);
+	if (ret) {
+		dev_err(&client->dev, "failed to enable clock %d\n", ret);
+		goto disable_bulk;
+	}
+
+	mutex_init(&sensor->lock);
+
+	/* Apply reset sequence */
+	if (sensor->reset_gpio) {
+		ret = vgxy61_apply_reset(sensor);
+		if (ret) {
+			dev_err(&client->dev, "sensor reset failed %d\n", ret);
+			goto disable_clock;
+		}
+	}
+
+	ret = vgxy61_detect(sensor);
+	if (ret) {
+		dev_err(&client->dev, "sensor detect failed %d\n", ret);
+		goto disable_clock;
+	}
+	sensor->fmt.width = sensor->current_mode->width;
+	sensor->fmt.height = sensor->current_mode->height;
+
+	ret = vgxy61_patch(sensor);
+	if (ret) {
+		dev_err(&client->dev, "sensor patch failed %d\n", ret);
+		goto disable_clock;
+	}
+
+	ret = vgxy61_configure(sensor);
+	if (ret) {
+		dev_err(&client->dev, "sensor configuration failed %d\n", ret);
+		goto disable_clock;
+	}
+
+	ret = vgxy61_init_controls(sensor);
+	if (ret) {
+		dev_err(&client->dev, "controls initialization failed %d\n", ret);
+		goto disable_clock;
+	}
+
+	ret = v4l2_async_register_subdev(&sensor->sd);
+	if (ret) {
+		dev_err(&client->dev, "async subdev register failed %d\n", ret);
+		goto disable_clock;
+	}
+
+	dev_info(&client->dev, "vgxy61 probe successfully\n");
+
+	return 0;
+
+disable_clock:
+	clk_disable_unprepare(sensor->xclk);
+disable_bulk:
+	regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
+entity_cleanup:
+	mutex_destroy(&sensor->lock);
+	media_entity_cleanup(&sensor->sd.entity);
+	return ret;
+}
+
+static int vgxy61_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
+
+	v4l2_async_unregister_subdev(&sensor->sd);
+	clk_disable_unprepare(sensor->xclk);
+	mutex_destroy(&sensor->lock);
+	media_entity_cleanup(&sensor->sd.entity);
+	regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
+
+	return 0;
+}
+
+static const struct of_device_id vgxy61_dt_ids[] = {
+	{ .compatible = "st,st-vgxy61" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, vgxy61_dt_ids);
+
+static struct i2c_driver vgxy61_i2c_driver = {
+	.driver = {
+		.name  = "st-vgxy61",
+		.of_match_table = vgxy61_dt_ids,
+	},
+	.probe_new = vgxy61_probe,
+	.remove = vgxy61_remove,
+};
+
+module_i2c_driver(vgxy61_i2c_driver);
+
+MODULE_AUTHOR("Benjamin Mugnier <benjamin.mugnier@foss.st.com>");
+MODULE_AUTHOR("Mickael Guene <mickael.guene@st.com>");
+MODULE_AUTHOR("Sylvain Petinot <sylvain.petinot@foss.st.com>");
+MODULE_DESCRIPTION("VGXY61 camera subdev driver");
+MODULE_LICENSE("GPL v2");
-- 
2.25.1


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

* Re: [PATCH 2/2] media: i2c: Add driver for ST VGXY61 camera sensor
  2022-03-10 13:32 ` [PATCH 2/2] media: i2c: Add driver for ST VGXY61 camera sensor Benjamin Mugnier
@ 2022-03-10 13:52   ` Kieran Bingham
  2022-03-10 15:15     ` Benjamin Mugnier
  2022-03-10 15:21     ` Dave Stevenson
  0 siblings, 2 replies; 17+ messages in thread
From: Kieran Bingham @ 2022-03-10 13:52 UTC (permalink / raw)
  To: Benjamin Mugnier, linux-media
  Cc: alain.volmat, hugues.fruchet, sylvain.petinot, Benjamin Mugnier

Hi Benjamin,

Thank you for the patch - 

Quoting Benjamin Mugnier (2022-03-10 13:32:55)
> The VGXY61 has a quad lanes CSI-2 output port running at 800mbps per
> lane, and supports RAW8, RAW10, RAW12, RAW14 and RAW16 formats.
> The driver handles both sensor types:
> - VG5661 and VG6661: 1.6 Mpx (1464 x 1104) 75fps.
> - VG5761 and VG6761: 2.3 Mpx (1944 x 1204) 60 fps.
> The driver supports:
> - HDR linearize mode, HDR substraction mode, and no HDR
> - GPIOs LEDs strobing
> - Digital binning and analog subsampling
> - Horizontal and vertical flip
> - Manual exposure
> - Analog and digital gains
> - Test patterns

I haven't reviewed the driver directly yet, but I have a script which
looks for key requirements for libcamera.
(https://git.linuxtv.org/libcamera.git/tree/Documentation/sensor_driver_requirements.rst)


Media Controller Support:
 - V4L2_SUBDEV_FL_HAS_DEVNODE      : found
 - MEDIA_ENT_F_CAM_SENSOR          : found

Mandatory Controls:
 - V4L2_CID_EXPOSURE               : found
 - V4L2_CID_HBLANK                 : --------
 - V4L2_CID_VBLANK                 : --------
 - V4L2_CID_PIXEL_RATE             : found

Selection Controls (will become mandatory):
 - V4L2_SEL_TGT_CROP_DEFAULT       : --------
 - V4L2_SEL_TGT_CROP               : --------
 - V4L2_SEL_TGT_CROP_BOUNDS        : --------
 - .get_selection                  : --------

Optional Controls:
 - V4L2_CID_TEST_PATTERN           : found
 - V4L2_CID_GAIN                   : --------
 - V4L2_CID_ANALOGUE_GAIN          : found
 - V4L2_CID_CAMERA_SENSOR_ROTATION : --------
 - V4L2_CID_CAMERA_ORIENTATION     : --------


The key missing pieces are HBLANK/VBLANK and the .get_selection API. Is
it easy/feasible to add these?

--
Regards

Kieran


> 
> Signed-off-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com>
> ---
>  drivers/media/i2c/Kconfig     |   11 +
>  drivers/media/i2c/Makefile    |    1 +
>  drivers/media/i2c/st-vgxy61.c | 1919 +++++++++++++++++++++++++++++++++
>  3 files changed, 1931 insertions(+)
>  create mode 100644 drivers/media/i2c/st-vgxy61.c
> 
> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> index fe66093b8849..e6e9c1f19c09 100644
> --- a/drivers/media/i2c/Kconfig
> +++ b/drivers/media/i2c/Kconfig
> @@ -1454,6 +1454,17 @@ config VIDEO_S5C73M3
>           This is a V4L2 sensor driver for Samsung S5C73M3
>           8 Mpixel camera.
>  
> +config VIDEO_ST_VGXY61
> +       tristate "ST VGXY61 sensor support"
> +       depends on OF
> +       depends on GPIOLIB && VIDEO_V4L2 && I2C
> +       select MEDIA_CONTROLLER
> +       select VIDEO_V4L2_SUBDEV_API
> +       select V4L2_FWNODE
> +       help
> +         This is a Video4Linux2 sensor driver for the ST VGXY61
> +         camera sensor.
> +
>  endmenu
>  
>  menu "Lens drivers"
> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> index f6b80ef6f41d..0190b5edfa24 100644
> --- a/drivers/media/i2c/Makefile
> +++ b/drivers/media/i2c/Makefile
> @@ -138,4 +138,5 @@ obj-$(CONFIG_VIDEO_MAX9271_LIB)     += max9271.o
>  obj-$(CONFIG_VIDEO_RDACM20)    += rdacm20.o
>  obj-$(CONFIG_VIDEO_RDACM21)    += rdacm21.o
>  obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o
> +obj-$(CONFIG_VIDEO_ST_VGXY61)  += st-vgxy61.o
>  obj-$(CONFIG_SDR_MAX2175) += max2175.o
> diff --git a/drivers/media/i2c/st-vgxy61.c b/drivers/media/i2c/st-vgxy61.c
> new file mode 100644
> index 000000000000..d3d816189f86
> --- /dev/null
> +++ b/drivers/media/i2c/st-vgxy61.c
> @@ -0,0 +1,1919 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Driver for VGXY61 global shutter sensor family driver
> + *
> + * Copyright (C) 2022 STMicroelectronics SA
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/units.h>
> +#include <linux/iopoll.h>
> +#include <media/mipi-csi2.h>
> +#include <media/v4l2-async.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-subdev.h>
> +
> +#define US_PER_MS                                      1000
> +
> +#define DEVICE_MODEL_ID_REG                            0x0000
> +#define VG5661_MODEL_ID                                        0x5661
> +#define VG5761_MODEL_ID                                        0x5761
> +#define VGX661_WIDTH                                   1464
> +#define VGX661_HEIGHT                                  1104
> +#define VGX761_WIDTH                                   1944
> +#define VGX761_HEIGHT                                  1204
> +#define VGX661_DEFAULT_MODE                            1
> +#define VGX761_DEFAULT_MODE                            1
> +#define VGX661_SHORT_ROT_TERM                          93
> +#define VGX761_SHORT_ROT_TERM                          90
> +#define VGXY61_EXPOS_ROT_TERM                          66
> +#define DEVICE_REVISION                                        0x0002
> +#define DEVICE_FWPATCH_REVISION                                0x0014
> +#define DEVICE_FWPATCH_START_ADDR                      0x2000
> +#define DEVICE_SYSTEM_FSM                              0x0020
> +#define SW_STBY                                                0x03
> +#define STREAMING                                      0x04
> +#define DEVICE_NVM                                     0x0023
> +#define NVM_OK                                         0x04
> +#define DEVICE_THSENS1_TEMPERATURE                     0x0044
> +#define DEVICE_STBY                                    0x0201
> +#define STBY_NO_REQ                                    0
> +#define STBY_REQ_TMP_READ                              BIT(2)
> +#define DEVICE_STREAMING                               0x0202
> +#define REQ_NO_REQUEST                                 0
> +#define REQ_STOP_STREAMING                             BIT(0)
> +#define REQ_START_STREAMING                            BIT(1)
> +#define DEVICE_EXT_CLOCK                               0x0220
> +#define DEVICE_CLK_PLL_PREDIV                          0x0224
> +#define DEVICE_CLK_SYS_PLL_MULT                                0x0225
> +#define DEVICE_GPIO_0_CTRL                             0x0236
> +#define DEVICE_GPIO_1_CTRL                             0x0237
> +#define DEVICE_GPIO_2_CTRL                             0x0238
> +#define DEVICE_GPIO_3_CTRL                             0x0239
> +#define DEVICE_SIGNALS_POLARITY_CTRL                   0x023b
> +#define DEVICE_LINE_LENGTH                             0x0300
> +#define DEVICE_ORIENTATION                             0x0302
> +#define DEVICE_VT_CTRL                                 0x0304
> +#define DEVICE_FORMAT_CTRL                             0x0305
> +#define DEVICE_OIF_CTRL                                        0x0306
> +#define DEVICE_OIF_ROI0_CTRL                           0x030a
> +#define DEVICE_ROI0_START_H                            0x0400
> +#define DEVICE_ROI0_START_V                            0x0402
> +#define DEVICE_ROI0_END_H                              0x0404
> +#define DEVICE_ROI0_END_V                              0x0406
> +#define DEVICE_PATGEN_CTRL                             0x0440
> +#define DEVICE_FRAME_CONTENT_CTRL                      0x0478
> +#define DEVICE_COARSE_EXPOSURE_LONG                    0x0500
> +#define DEVICE_COARSE_EXPOSURE_SHORT                   0x0504
> +#define DEVICE_ANALOG_GAIN                             0x0508
> +#define DEVICE_DIGITAL_GAIN_LONG                       0x050a
> +#define DEVICE_DIGITAL_GAIN_SHORT                      0x0512
> +#define DEVICE_FRAME_LENGTH                            0x051a
> +#define DEVICE_SIGNALS_CTRL                            0x0522
> +#define DEVICE_STROBE_LONG_START_DELAY                 0x0528
> +#define DEVICE_STROBE_LONG_END_DELAY                   0x052a
> +#define DEVICE_STROBE_SHORT_START_DELAY                        0x052c
> +#define DEVICE_STROBE_SHORT_END_DELAY                  0x052e
> +#define DEVICE_READOUT_CTRL                            0x0530
> +#define DEVICE_HDR_CTRL                                        0x0532
> +#define DEVICE_PATGEN_LONG_DATA_GR                     0x092c
> +#define DEVICE_PATGEN_LONG_DATA_R                      0x092e
> +#define DEVICE_PATGEN_LONG_DATA_B                      0x0930
> +#define DEVICE_PATGEN_LONG_DATA_GB                     0x0932
> +#define DEVICE_PATGEN_SHORT_DATA_GR                    0x0950
> +#define DEVICE_PATGEN_SHORT_DATA_R                     0x0952
> +#define DEVICE_PATGEN_SHORT_DATA_B                     0x0954
> +#define DEVICE_PATGEN_SHORT_DATA_GB                    0x0956
> +#define DEVICE_BYPASS_CTRL                             0x0a60
> +
> +#define V4L2_CID_HDR                           (V4L2_CID_USER_BASE | 0x1004)
> +#define V4L2_CID_GPIOS_STROBE_LONG_START_DELAY (V4L2_CID_USER_BASE | 0x1019)
> +#define V4L2_CID_GPIOS_STROBE_LONG_END_DELAY   (V4L2_CID_USER_BASE | 0x101a)
> +#define V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY        (V4L2_CID_USER_BASE | 0x101b)
> +#define V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY  (V4L2_CID_USER_BASE | 0x101c)
> +#define V4L2_CID_TEMPERATURE                   (V4L2_CID_USER_BASE | 0x1020)
> +
> +#define DEVICE_FWPATCH_REVISION_MAJOR                  2
> +#define DEVICE_FWPATCH_REVISION_MINOR                  0
> +#define DEVICE_FWPATCH_REVISION_MICRO                  5
> +
> +#define WRITE_MULTIPLE_CHUNK_MAX                       16
> +#define NB_GPIOS                                       4
> +#define NB_POLARITIES                                  5
> +
> +static const u8 patch_array[] = {
> +       0xbf, 0x00, 0x05, 0x20, 0x06, 0x01, 0xe0, 0xe0, 0x04, 0x80, 0xe6, 0x45,
> +       0xed, 0x6f, 0xfe, 0xff, 0x14, 0x80, 0x1f, 0x84, 0x10, 0x42, 0x05, 0x7c,
> +       0x01, 0xc4, 0x1e, 0x80, 0xb6, 0x42, 0x00, 0xe0, 0x1e, 0x82, 0x1e, 0xc0,
> +       0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa, 0x86, 0x0d, 0x70, 0xe1,
> +       0x04, 0x98, 0x15, 0x00, 0x28, 0xe0, 0x14, 0x02, 0x08, 0xfc, 0x15, 0x40,
> +       0x28, 0xe0, 0x98, 0x58, 0xe0, 0xef, 0x04, 0x98, 0x0e, 0x04, 0x00, 0xf0,
> +       0x15, 0x00, 0x28, 0xe0, 0x19, 0xc8, 0x15, 0x40, 0x28, 0xe0, 0xc6, 0x41,
> +       0xfc, 0xe0, 0x14, 0x80, 0x1f, 0x84, 0x14, 0x02, 0xa0, 0xfc, 0x1e, 0x80,
> +       0x14, 0x80, 0x14, 0x02, 0x80, 0xfb, 0x14, 0x02, 0xe0, 0xfc, 0x1e, 0x80,
> +       0x14, 0xc0, 0x1f, 0x84, 0x14, 0x02, 0xa4, 0xfc, 0x1e, 0xc0, 0x14, 0xc0,
> +       0x14, 0x02, 0x80, 0xfb, 0x14, 0x02, 0xe4, 0xfc, 0x1e, 0xc0, 0x0c, 0x0c,
> +       0x00, 0xf2, 0x93, 0xdd, 0x86, 0x00, 0xf8, 0xe0, 0x04, 0x80, 0xc6, 0x03,
> +       0x70, 0xe1, 0x0e, 0x84, 0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa,
> +       0x6b, 0x80, 0x06, 0x40, 0x6c, 0xe1, 0x04, 0x80, 0x09, 0x00, 0xe0, 0xe0,
> +       0x0b, 0xa1, 0x95, 0x84, 0x05, 0x0c, 0x1c, 0xe0, 0x86, 0x02, 0xf9, 0x60,
> +       0xe0, 0xcf, 0x78, 0x6e, 0x80, 0xef, 0x25, 0x0c, 0x18, 0xe0, 0x05, 0x4c,
> +       0x1c, 0xe0, 0x86, 0x02, 0xf9, 0x60, 0xe0, 0xcf, 0x0b, 0x84, 0xd8, 0x6d,
> +       0x80, 0xef, 0x05, 0x4c, 0x18, 0xe0, 0x04, 0xd8, 0x0b, 0xa5, 0x95, 0x84,
> +       0x05, 0x0c, 0x2c, 0xe0, 0x06, 0x02, 0x01, 0x60, 0xe0, 0xce, 0x18, 0x6d,
> +       0x80, 0xef, 0x25, 0x0c, 0x30, 0xe0, 0x05, 0x4c, 0x2c, 0xe0, 0x06, 0x02,
> +       0x01, 0x60, 0xe0, 0xce, 0x0b, 0x84, 0x78, 0x6c, 0x80, 0xef, 0x05, 0x4c,
> +       0x30, 0xe0, 0x0c, 0x0c, 0x00, 0xf2, 0x93, 0xdd, 0x46, 0x01, 0x70, 0xe1,
> +       0x08, 0x80, 0x0b, 0xa1, 0x08, 0x5c, 0x00, 0xda, 0x06, 0x01, 0x68, 0xe1,
> +       0x04, 0x80, 0x4a, 0x40, 0x84, 0xe0, 0x08, 0x5c, 0x00, 0x9a, 0x06, 0x01,
> +       0xe0, 0xe0, 0x04, 0x80, 0x15, 0x00, 0x60, 0xe0, 0x19, 0xc4, 0x15, 0x40,
> +       0x60, 0xe0, 0x15, 0x00, 0x78, 0xe0, 0x19, 0xc4, 0x15, 0x40, 0x78, 0xe0,
> +       0x93, 0xdd, 0xc3, 0xc1, 0x46, 0x01, 0x70, 0xe1, 0x08, 0x80, 0x0b, 0xa1,
> +       0x08, 0x5c, 0x00, 0xda, 0x06, 0x01, 0x68, 0xe1, 0x04, 0x80, 0x4a, 0x40,
> +       0x84, 0xe0, 0x08, 0x5c, 0x00, 0x9a, 0x06, 0x01, 0xe0, 0xe0, 0x14, 0x80,
> +       0x25, 0x02, 0x54, 0xe0, 0x29, 0xc4, 0x25, 0x42, 0x54, 0xe0, 0x24, 0x80,
> +       0x35, 0x04, 0x6c, 0xe0, 0x39, 0xc4, 0x35, 0x44, 0x6c, 0xe0, 0x25, 0x02,
> +       0x64, 0xe0, 0x29, 0xc4, 0x25, 0x42, 0x64, 0xe0, 0x04, 0x80, 0x15, 0x00,
> +       0x7c, 0xe0, 0x19, 0xc4, 0x15, 0x40, 0x7c, 0xe0, 0x93, 0xdd, 0xc3, 0xc1,
> +       0x4c, 0x04, 0x7c, 0xfa, 0x86, 0x40, 0x98, 0xe0, 0x14, 0x80, 0x1b, 0xa1,
> +       0x06, 0x00, 0x00, 0xc0, 0x08, 0x42, 0x38, 0xdc, 0x08, 0x64, 0xa0, 0xef,
> +       0x86, 0x42, 0x3c, 0xe0, 0x68, 0x49, 0x80, 0xef, 0x6b, 0x80, 0x78, 0x53,
> +       0xc8, 0xef, 0xc6, 0x54, 0x6c, 0xe1, 0x7b, 0x80, 0xb5, 0x14, 0x0c, 0xf8,
> +       0x05, 0x14, 0x14, 0xf8, 0x1a, 0xac, 0x8a, 0x80, 0x0b, 0x90, 0x38, 0x55,
> +       0x80, 0xef, 0x1a, 0xae, 0x17, 0xc2, 0x03, 0x82, 0x88, 0x65, 0x80, 0xef,
> +       0x1b, 0x80, 0x0b, 0x8e, 0x68, 0x65, 0x80, 0xef, 0x9b, 0x80, 0x0b, 0x8c,
> +       0x08, 0x65, 0x80, 0xef, 0x6b, 0x80, 0x0b, 0x92, 0x1b, 0x8c, 0x98, 0x64,
> +       0x80, 0xef, 0x1a, 0xec, 0x9b, 0x80, 0x0b, 0x90, 0x95, 0x54, 0x10, 0xe0,
> +       0xa8, 0x53, 0x80, 0xef, 0x1a, 0xee, 0x17, 0xc2, 0x03, 0x82, 0xf8, 0x63,
> +       0x80, 0xef, 0x1b, 0x80, 0x0b, 0x8e, 0xd8, 0x63, 0x80, 0xef, 0x1b, 0x8c,
> +       0x68, 0x63, 0x80, 0xef, 0x6b, 0x80, 0x0b, 0x92, 0x65, 0x54, 0x14, 0xe0,
> +       0x08, 0x65, 0x84, 0xef, 0x68, 0x63, 0x80, 0xef, 0x7b, 0x80, 0x0b, 0x8c,
> +       0xa8, 0x64, 0x84, 0xef, 0x08, 0x63, 0x80, 0xef, 0x14, 0xe8, 0x46, 0x44,
> +       0x94, 0xe1, 0x24, 0x88, 0x4a, 0x4e, 0x04, 0xe0, 0x14, 0xea, 0x1a, 0x04,
> +       0x08, 0xe0, 0x0a, 0x40, 0x84, 0xed, 0x0c, 0x04, 0x00, 0xe2, 0x4a, 0x40,
> +       0x04, 0xe0, 0x19, 0x16, 0xc0, 0xe0, 0x0a, 0x40, 0x84, 0xed, 0x21, 0x54,
> +       0x60, 0xe0, 0x0c, 0x04, 0x00, 0xe2, 0x1b, 0xa5, 0x0e, 0xea, 0x01, 0x89,
> +       0x21, 0x54, 0x64, 0xe0, 0x7e, 0xe8, 0x65, 0x82, 0x1b, 0xa7, 0x26, 0x00,
> +       0x00, 0x80, 0xa5, 0x82, 0x1b, 0xa9, 0x65, 0x82, 0x1b, 0xa3, 0x01, 0x85,
> +       0x16, 0x00, 0x00, 0xc0, 0x01, 0x54, 0x04, 0xf8, 0x06, 0xaa, 0x01, 0x83,
> +       0x06, 0xa8, 0x65, 0x81, 0x06, 0xa8, 0x01, 0x54, 0x04, 0xf8, 0x01, 0x83,
> +       0x06, 0xaa, 0x09, 0x14, 0x18, 0xf8, 0x0b, 0xa1, 0x05, 0x84, 0xc6, 0x42,
> +       0xd4, 0xe0, 0x14, 0x84, 0x01, 0x83, 0x01, 0x54, 0x60, 0xe0, 0x01, 0x54,
> +       0x64, 0xe0, 0x0b, 0x02, 0x90, 0xe0, 0x10, 0x02, 0x90, 0xe5, 0x01, 0x54,
> +       0x88, 0xe0, 0xb5, 0x81, 0xc6, 0x40, 0xd4, 0xe0, 0x14, 0x80, 0x0b, 0x02,
> +       0xe0, 0xe4, 0x10, 0x02, 0x31, 0x66, 0x02, 0xc0, 0x01, 0x54, 0x88, 0xe0,
> +       0x1a, 0x84, 0x29, 0x14, 0x10, 0xe0, 0x1c, 0xaa, 0x2b, 0xa1, 0xf5, 0x82,
> +       0x25, 0x14, 0x10, 0xf8, 0x2b, 0x04, 0xa8, 0xe0, 0x20, 0x44, 0x0d, 0x70,
> +       0x03, 0xc0, 0x2b, 0xa1, 0x04, 0x00, 0x80, 0x9a, 0x02, 0x40, 0x84, 0x90,
> +       0x03, 0x54, 0x04, 0x80, 0x4c, 0x0c, 0x7c, 0xf2, 0x93, 0xdd, 0x00, 0x00,
> +       0x02, 0xa9, 0x00, 0x00, 0x64, 0x4a, 0x40, 0x00, 0x08, 0x2d, 0x58, 0xe0,
> +       0xa8, 0x98, 0x40, 0x00, 0x28, 0x07, 0x34, 0xe0, 0x05, 0xb9, 0x00, 0x00,
> +       0x28, 0x00, 0x41, 0x05, 0x88, 0x00, 0x41, 0x3c, 0x98, 0x00, 0x41, 0x52,
> +       0x04, 0x01, 0x41, 0x79, 0x3c, 0x01, 0x41, 0x6a, 0x3d, 0xfe, 0x00, 0x00,
> +};
> +
> +static const char * const vgxy61_test_pattern_menu[] = {
> +       "Disabled",
> +       "Solid",
> +       "Colorbar",
> +       "Gradbar",
> +       "Hgrey",
> +       "Vgrey",
> +       "Dgrey",
> +       "PN28",
> +};
> +
> +static const char * const vgxy61_hdr_modes[] = {
> +       "HDR linearize",
> +       "HDR substraction",
> +       "no HDR",
> +};
> +
> +/* Regulator supplies */
> +static const char * const vgxy61_supply_name[] = {
> +       "VCORE",
> +       "VDDIO",
> +       "VANA",
> +};
> +
> +static const s64 link_freq[] = {
> +       /*
> +        * MIPI output freq is 804Mhz / 2, as it uses both rising edge and falling edges to send
> +        * data
> +        */
> +       402000000ULL
> +};
> +
> +#define VGXY61_NUM_SUPPLIES            ARRAY_SIZE(vgxy61_supply_name)
> +
> +/* Macro to convert index to 8.8 fixed point gain */
> +#define I2FP(i)                                ((u32)(8192.0 / (32 - (i))))
> +/* Array of possibles analog gains in 8.8 fixed point */
> +static const u16 analog_gains[] = {
> +       I2FP(0), I2FP(1), I2FP(2), I2FP(3), I2FP(4), I2FP(5), I2FP(6), I2FP(7), I2FP(8), I2FP(9),
> +       I2FP(10), I2FP(11), I2FP(12), I2FP(13), I2FP(14), I2FP(15), I2FP(16),
> +};
> +
> +enum bin_mode {
> +       BIN_MODE_NORMAL,
> +       BIN_MODE_DIGITAL_X2,
> +       BIN_MODE_DIGITAL_X4,
> +       BIN_MODE_ANALOG_SUB_X2,
> +       BIN_MODE_ANALOG_SUB_X4,
> +};
> +
> +enum hdr {
> +       HDR_LINEAR,
> +       HDR_SUB,
> +       NO_HDR,
> +};
> +
> +enum strobe_modes {
> +       STROBE_DISABLED,
> +       STROBE_LONG,
> +       STROBE_ENABLED,
> +};
> +
> +struct vgxy61_mode_info {
> +       u32 width;
> +       u32 height;
> +       enum bin_mode bin_mode;
> +};
> +
> +static const u32 vgxy61_supported_codes[] = {
> +       MEDIA_BUS_FMT_SGBRG8_1X8,
> +       MEDIA_BUS_FMT_SGBRG10_1X10,
> +       MEDIA_BUS_FMT_SGBRG12_1X12,
> +       MEDIA_BUS_FMT_SGBRG14_1X14,
> +       MEDIA_BUS_FMT_SGBRG16_1X16
> +};
> +
> +const int vgx761_sensor_frame_rates[] = {75, 60, 30, 15, 10, 5, 2};
> +const int vgx661_sensor_frame_rates[] = {60, 30, 15, 10, 5, 2};
> +
> +static const struct vgxy61_mode_info vgx661_mode_data[] = {
> +       {1464, 1104, BIN_MODE_NORMAL},
> +       {1280,  720, BIN_MODE_NORMAL},
> +       { 640,  480, BIN_MODE_DIGITAL_X2},
> +       { 320,  240, BIN_MODE_DIGITAL_X4},
> +};
> +
> +static const struct vgxy61_mode_info vgx761_mode_data[] = {
> +       {1944, 1204, BIN_MODE_NORMAL},
> +       {1920, 1080, BIN_MODE_NORMAL},
> +       {1280,  720, BIN_MODE_NORMAL},
> +       { 640,  480, BIN_MODE_DIGITAL_X2},
> +       { 320,  240, BIN_MODE_DIGITAL_X4},
> +};
> +
> +struct gpios_ctrls {
> +       struct v4l2_ctrl *long_start;
> +       struct v4l2_ctrl *long_end;
> +       struct v4l2_ctrl *short_start;
> +       struct v4l2_ctrl *short_end;
> +};
> +
> +struct vgxy61_ctrls {
> +       struct v4l2_ctrl_handler handler;
> +       struct v4l2_ctrl *exposure;
> +       struct v4l2_ctrl *analog_gain;
> +       struct v4l2_ctrl *digital_gain;
> +       struct gpios_ctrls gpios;
> +       struct v4l2_ctrl *vflip;
> +       struct v4l2_ctrl *hflip;
> +       struct v4l2_ctrl *patgen;
> +       struct v4l2_ctrl *hdr;
> +       struct v4l2_ctrl *pixel_rate;
> +       struct v4l2_ctrl *link_freq;
> +       struct v4l2_ctrl *temp;
> +};
> +
> +struct vgxy61_dev {
> +       struct i2c_client *i2c_client;
> +       struct v4l2_subdev sd;
> +       struct media_pad pad;
> +       struct regulator_bulk_data supplies[VGXY61_NUM_SUPPLIES];
> +       struct gpio_desc *reset_gpio;
> +       struct clk *xclk;
> +       u32 clk_freq;
> +       int sensor_width;
> +       int sensor_height;
> +       u16 oif_ctrl;
> +       int nb_of_lane;
> +       int data_rate_in_mbps;
> +       int pclk;
> +       u16 line_length;
> +       int rot_term;
> +       bool gpios_polarity;
> +       bool slave_mode;
> +       /* Lock to protect all members below */
> +       struct mutex lock;
> +       struct vgxy61_ctrls ctrls;
> +       bool streaming;
> +       struct v4l2_mbus_framefmt fmt;
> +       const struct vgxy61_mode_info *sensor_modes;
> +       int sensor_modes_nb;
> +       const struct vgxy61_mode_info *current_mode;
> +       const int *sensor_rates;
> +       int sensor_rates_nb;
> +       struct v4l2_fract frame_interval;
> +       bool hflip;
> +       bool vflip;
> +       enum hdr hdr;
> +       int expo_long;
> +       int expo_short;
> +};
> +
> +static u8 get_bpp_by_code(__u32 code)
> +{
> +       switch (code) {
> +       case MEDIA_BUS_FMT_SGBRG8_1X8:
> +               return 8;
> +       case MEDIA_BUS_FMT_SGBRG10_1X10:
> +               return 10;
> +       case MEDIA_BUS_FMT_SGBRG12_1X12:
> +               return 12;
> +       case MEDIA_BUS_FMT_SGBRG14_1X14:
> +               return 14;
> +       case MEDIA_BUS_FMT_SGBRG16_1X16:
> +               return 16;
> +       default:
> +               /* Should never happen */
> +               WARN(1, "Unsupported code %d. default to 8 bpp", code);
> +               return 8;
> +       }
> +}
> +
> +static u8 get_data_type_by_code(__u32 code)
> +{
> +       switch (code) {
> +       case MEDIA_BUS_FMT_SGBRG8_1X8:
> +               return MIPI_CSI2_DT_RAW8;
> +       case MEDIA_BUS_FMT_SGBRG10_1X10:
> +               return MIPI_CSI2_DT_RAW10;
> +       case MEDIA_BUS_FMT_SGBRG12_1X12:
> +               return MIPI_CSI2_DT_RAW12;
> +       case MEDIA_BUS_FMT_SGBRG14_1X14:
> +               return MIPI_CSI2_DT_RAW14;
> +       case MEDIA_BUS_FMT_SGBRG16_1X16:
> +               return MIPI_CSI2_DT_RAW16;
> +       default:
> +               /* Should never happen */
> +               WARN(1, "Unsupported code %d. default to MIPI_CSI2_DT_RAW8 data type", code);
> +               return MIPI_CSI2_DT_RAW8;
> +       }
> +}
> +
> +static void compute_pll_parameters_by_freq(u32 freq, unsigned int *prediv, unsigned int *mult)
> +{
> +       const unsigned int predivs[] = {1, 2, 4};
> +       int i;
> +
> +       /*
> +        * Freq range is [6Mhz-27Mhz] already checked.
> +        * Output of divider should be in [6Mhz-12Mhz[.
> +        */
> +       for (i = 0; i < ARRAY_SIZE(predivs); i++) {
> +               *prediv = predivs[i];
> +               if (freq / *prediv < 12 * HZ_PER_MHZ)
> +                       break;
> +       }
> +       WARN_ON(i == ARRAY_SIZE(predivs));
> +
> +       /*
> +        * Target freq is 804Mhz. Don't change this as it will impact image quality.
> +        */
> +       *mult = ((804 * HZ_PER_MHZ) * (*prediv) + freq / 2) / freq;
> +}
> +
> +static s32 get_pixel_rate(struct vgxy61_dev *sensor)
> +{
> +       return div64_u64((u64)sensor->data_rate_in_mbps * sensor->nb_of_lane,
> +                        get_bpp_by_code(sensor->fmt.code));
> +}
> +
> +static inline struct vgxy61_dev *to_vgxy61_dev(struct v4l2_subdev *sd)
> +{
> +       return container_of(sd, struct vgxy61_dev, sd);
> +}
> +
> +static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl)
> +{
> +       return &container_of(ctrl->handler, struct vgxy61_dev, ctrls.handler)->sd;
> +}
> +
> +static int get_chunk_size(struct vgxy61_dev *sensor)
> +{
> +       struct i2c_adapter *adapter = sensor->i2c_client->adapter;
> +       int max_write_len = WRITE_MULTIPLE_CHUNK_MAX;
> +
> +       if (adapter->quirks && adapter->quirks->max_write_len)
> +               max_write_len = adapter->quirks->max_write_len - 2;
> +
> +       max_write_len = min(max_write_len, WRITE_MULTIPLE_CHUNK_MAX);
> +
> +       return max(max_write_len, 1);
> +}
> +
> +static int vgxy61_read_multiple(struct vgxy61_dev *sensor, u16 reg, u8 *val, int len)
> +{
> +       struct i2c_client *client = sensor->i2c_client;
> +       struct i2c_msg msg[2];
> +       u8 buf[2];
> +       int ret;
> +
> +       buf[0] = reg >> 8;
> +       buf[1] = reg & 0xff;
> +
> +       msg[0].addr = client->addr;
> +       msg[0].flags = client->flags;
> +       msg[0].buf = buf;
> +       msg[0].len = sizeof(buf);
> +
> +       msg[1].addr = client->addr;
> +       msg[1].flags = client->flags | I2C_M_RD;
> +       msg[1].buf = val;
> +       msg[1].len = len;
> +
> +       ret = i2c_transfer(client->adapter, msg, 2);
> +       if (ret < 0) {
> +               dev_dbg(&client->dev, "%s: %x i2c_transfer, reg: %x => %d\n", __func__,
> +                       client->addr, reg, ret);
> +               return ret;
> +       }
> +
> +       return 0;
> +}
> +
> +static inline int vgxy61_read_reg(struct vgxy61_dev *sensor, u16 reg, u8 *val)
> +{
> +       return vgxy61_read_multiple(sensor, reg, val, sizeof(*val));
> +}
> +
> +static inline int vgxy61_read_reg16(struct vgxy61_dev *sensor, u16 reg, u16 *val)
> +{
> +       return vgxy61_read_multiple(sensor, reg, (u8 *)val, sizeof(*val));
> +}
> +
> +static int vgxy61_write_multiple(struct vgxy61_dev *sensor, u16 reg, const u8 *data, int len)
> +{
> +       struct i2c_client *client = sensor->i2c_client;
> +       struct i2c_msg msg;
> +       u8 buf[WRITE_MULTIPLE_CHUNK_MAX + 2];
> +       int i;
> +       int ret;
> +
> +       if (len > WRITE_MULTIPLE_CHUNK_MAX)
> +               return -EINVAL;
> +       buf[0] = reg >> 8;
> +       buf[1] = reg & 0xff;
> +       for (i = 0; i < len; i++)
> +               buf[i + 2] = data[i];
> +
> +       msg.addr = client->addr;
> +       msg.flags = client->flags;
> +       msg.buf = buf;
> +       msg.len = len + 2;
> +
> +       ret = i2c_transfer(client->adapter, &msg, 1);
> +       if (ret < 0) {
> +               dev_dbg(&client->dev, "%s: i2c_transfer, reg: %x => %d\n", __func__, reg, ret);
> +               return ret;
> +       }
> +
> +       return 0;
> +}
> +
> +static int vgxy61_write_array(struct vgxy61_dev *sensor, u16 reg, int nb, const u8 *array)
> +{
> +       const int chunk_size = get_chunk_size(sensor);
> +       int ret;
> +       int sz;
> +
> +       while (nb) {
> +               sz = min(nb, chunk_size);
> +               ret = vgxy61_write_multiple(sensor, reg, array, sz);
> +               if (ret < 0)
> +                       return ret;
> +               nb -= sz;
> +               reg += sz;
> +               array += sz;
> +       }
> +
> +       return 0;
> +}
> +
> +static inline int vgxy61_write_reg(struct vgxy61_dev *sensor, u16 reg, u8 val)
> +{
> +       return vgxy61_write_multiple(sensor, reg, &val, sizeof(val));
> +}
> +
> +static inline int vgxy61_write_reg16(struct vgxy61_dev *sensor, u16 reg, u16 val)
> +{
> +       return vgxy61_write_multiple(sensor, reg, (u8 *)&val, sizeof(val));
> +}
> +
> +static inline int vgxy61_write_reg32(struct vgxy61_dev *sensor, u16 reg, u32 val)
> +{
> +       return vgxy61_write_multiple(sensor, reg, (u8 *)&val, sizeof(val));
> +}
> +
> +static int vgxy61_poll_reg(struct vgxy61_dev *sensor, u16 reg, u8 poll_val)
> +{
> +       const int loop_delay_ms = 10;
> +       const int timeout_ms = 500;
> +       u8 val;
> +       int ret, timeout;
> +
> +       timeout = read_poll_timeout(vgxy61_read_reg, ret, ((ret != 0) || (val == poll_val)),
> +                                   loop_delay_ms * US_PER_MS, timeout_ms * US_PER_MS, false,
> +                                   sensor, reg, &val);
> +       if (timeout)
> +               return timeout;
> +
> +       return ret;
> +}
> +
> +static int vgxy61_wait_state(struct vgxy61_dev *sensor, int state)
> +{
> +       return vgxy61_poll_reg(sensor, DEVICE_SYSTEM_FSM, state);
> +}
> +
> +static int vgxy61_check_bw(struct vgxy61_dev *sensor)
> +{
> +       struct i2c_client *client = sensor->i2c_client;
> +       /* Correction factor for time required between 2 lines */
> +       const int mipi_margin = 1056;
> +       int binning_scale = 1 << sensor->current_mode->bin_mode;
> +       int bpp = get_bpp_by_code(sensor->fmt.code);
> +       int max_bit_per_line;
> +       int bit_per_line;
> +       u64 line_rate;
> +
> +       line_rate = sensor->nb_of_lane * (u64)sensor->data_rate_in_mbps * sensor->line_length;
> +       max_bit_per_line = div64_u64(line_rate, sensor->pclk) - mipi_margin;
> +       bit_per_line = (bpp * sensor->current_mode->width) / binning_scale;
> +
> +       dev_dbg(&client->dev, "max_bit_per_line = %d\n", max_bit_per_line);
> +       dev_dbg(&client->dev, "required bit_per_line = %d\n", bit_per_line);
> +
> +       return bit_per_line > max_bit_per_line ? -EINVAL : 0;
> +}
> +
> +static int apply_exposure(struct vgxy61_dev *sensor)
> +{
> +       struct i2c_client *client = sensor->i2c_client;
> +       int ret;
> +
> +        /* We first set expo to zero to avoid forbidden parameters couple */
> +       ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_SHORT, 0);
> +       if (ret)
> +               return ret;
> +       ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_LONG, sensor->expo_long);
> +       if (ret)
> +               return ret;
> +       ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_SHORT, sensor->expo_short);
> +       if (ret)
> +               return ret;
> +
> +       dev_dbg(&client->dev, "%s applied expo %d (short: %d)\n", __func__,
> +               sensor->expo_long, sensor->expo_short);
> +
> +       return 0;
> +}
> +
> +static int set_frame_rate(struct vgxy61_dev *sensor)
> +{
> +       u16 frame_length;
> +
> +       frame_length = sensor->pclk / (sensor->line_length * sensor->frame_interval.denominator);
> +
> +       return vgxy61_write_reg16(sensor, DEVICE_FRAME_LENGTH, frame_length);
> +}
> +
> +static int vgxy61_get_regulators(struct vgxy61_dev *sensor)
> +{
> +       int i;
> +
> +       for (i = 0; i < VGXY61_NUM_SUPPLIES; i++)
> +               sensor->supplies[i].supply = vgxy61_supply_name[i];
> +
> +       return devm_regulator_bulk_get(&sensor->i2c_client->dev, VGXY61_NUM_SUPPLIES,
> +                                      sensor->supplies);
> +}
> +
> +static int vgxy61_apply_reset(struct vgxy61_dev *sensor)
> +{
> +       struct i2c_client *client = sensor->i2c_client;
> +
> +       dev_dbg(&client->dev, "%s applied reset\n", __func__);
> +       gpiod_set_value_cansleep(sensor->reset_gpio, 0);
> +       usleep_range(5000, 10000);
> +       gpiod_set_value_cansleep(sensor->reset_gpio, 1);
> +       usleep_range(5000, 10000);
> +       gpiod_set_value_cansleep(sensor->reset_gpio, 0);
> +       usleep_range(40000, 100000);
> +       return vgxy61_wait_state(sensor, SW_STBY);
> +}
> +
> +static int vgxy61_try_fmt_internal(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *fmt,
> +                                  const struct vgxy61_mode_info **new_mode)
> +{
> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> +       const struct vgxy61_mode_info *mode = sensor->sensor_modes;
> +       unsigned int index;
> +
> +       /* Select code */
> +       for (index = 0; index < ARRAY_SIZE(vgxy61_supported_codes); index++) {
> +               if (vgxy61_supported_codes[index] == fmt->code)
> +                       break;
> +       }
> +       if (index == ARRAY_SIZE(vgxy61_supported_codes))
> +               index = 0;
> +
> +       /* Select size */
> +       do {
> +               if (mode->width <= fmt->width && mode->height <= fmt->height)
> +                       break;
> +       } while ((++mode)->width);
> +       if (!mode->width)
> +               mode--;
> +
> +       *new_mode = mode;
> +       fmt->code = vgxy61_supported_codes[index];
> +       fmt->width = mode->width;
> +       fmt->height = mode->height;
> +       fmt->colorspace = V4L2_COLORSPACE_SRGB;
> +       fmt->field = V4L2_FIELD_NONE;
> +
> +       return 0;
> +}
> +
> +static int vgxy61_stream_enable(struct vgxy61_dev *sensor)
> +{
> +       int center_x = sensor->sensor_width / 2;
> +       int center_y = sensor->sensor_height / 2;
> +       int scale = 1 << sensor->current_mode->bin_mode;
> +       int width = sensor->current_mode->width * scale;
> +       int height = sensor->current_mode->height * scale;
> +       int ret;
> +
> +       ret = vgxy61_check_bw(sensor);
> +       if (ret)
> +               return ret;
> +
> +       /* Configure sensor */
> +       ret = vgxy61_write_reg(sensor, DEVICE_FORMAT_CTRL, get_bpp_by_code(sensor->fmt.code));
> +       if (ret)
> +               return ret;
> +       ret = vgxy61_write_reg(sensor, DEVICE_OIF_ROI0_CTRL,
> +                              get_data_type_by_code(sensor->fmt.code));
> +       if (ret)
> +               return ret;
> +
> +       ret = vgxy61_write_reg(sensor, DEVICE_READOUT_CTRL, sensor->current_mode->bin_mode);
> +       if (ret)
> +               return ret;
> +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_H, center_x - width / 2);
> +       if (ret)
> +               return ret;
> +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_H, center_x + width / 2 - 1);
> +       if (ret)
> +               return ret;
> +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_V, center_y - height / 2);
> +       if (ret)
> +               return ret;
> +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_V, center_y + height / 2 - 1);
> +       if (ret)
> +               return ret;
> +
> +       ret = set_frame_rate(sensor);
> +       if (ret)
> +               return ret;
> +
> +       ret = apply_exposure(sensor);
> +       if (ret)
> +               return ret;
> +
> +       /* Start streaming */
> +       ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_START_STREAMING);
> +       if (ret)
> +               return ret;
> +
> +       ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST);
> +       if (ret)
> +               return ret;
> +
> +       ret = vgxy61_wait_state(sensor, STREAMING);
> +       if (ret)
> +               return ret;
> +
> +       return 0;
> +}
> +
> +static int vgxy61_stream_disable(struct vgxy61_dev *sensor)
> +{
> +       int ret;
> +
> +       ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_STOP_STREAMING);
> +       if (ret)
> +               return ret;
> +
> +       ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST);
> +       if (ret)
> +               return ret;
> +
> +       ret = vgxy61_wait_state(sensor, SW_STBY);
> +       if (ret)
> +               return ret;
> +
> +       return 0;
> +}
> +
> +static int vgxy61_s_stream(struct v4l2_subdev *sd, int enable)
> +{
> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> +       struct i2c_client *client = sensor->i2c_client;
> +       int ret = 0;
> +
> +       mutex_lock(&sensor->lock);
> +       dev_dbg(&client->dev, "%s : requested %d / current = %d\n", __func__, enable,
> +               sensor->streaming);
> +       if (sensor->streaming == enable)
> +               goto out;
> +
> +       ret = enable ? vgxy61_stream_enable(sensor) : vgxy61_stream_disable(sensor);
> +       if (!ret)
> +               sensor->streaming = enable;
> +
> +out:
> +       dev_dbg(&client->dev, "%s current now = %d / %d\n", __func__, sensor->streaming, ret);
> +       mutex_unlock(&sensor->lock);
> +
> +       return ret;
> +}
> +
> +static int vgxy61_g_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *fi)
> +{
> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> +
> +       mutex_lock(&sensor->lock);
> +       fi->interval = sensor->frame_interval;
> +       mutex_unlock(&sensor->lock);
> +
> +       return 0;
> +}
> +
> +static int vgxy61_s_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *fi)
> +{
> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> +       struct i2c_client *client = sensor->i2c_client;
> +       u64 req_int, err, min_err = ~0ULL;
> +       u64 test_int;
> +       int i = 0;
> +       int ret;
> +
> +       if (fi->interval.denominator == 0)
> +               return -EINVAL;
> +
> +       mutex_lock(&sensor->lock);
> +
> +       if (sensor->streaming) {
> +               ret = -EBUSY;
> +               goto out;
> +       }
> +
> +       dev_dbg(&client->dev, "%s request %d/%d\n", __func__,
> +               fi->interval.numerator, fi->interval.denominator);
> +       /* Find nearest period */
> +       req_int = div64_u64((u64)(fi->interval.numerator * 10000), fi->interval.denominator);
> +       for (i = 0; i < sensor->sensor_rates_nb; i++) {
> +               test_int = div64_u64((u64)10000, sensor->sensor_rates[i]);
> +               err = abs(test_int - req_int);
> +               if (err < min_err) {
> +                       fi->interval.numerator = 1;
> +                       fi->interval.denominator = sensor->sensor_rates[i];
> +                       min_err = err;
> +               }
> +       }
> +       sensor->frame_interval = fi->interval;
> +       dev_dbg(&client->dev, "%s set     %d/%d\n", __func__,
> +               fi->interval.numerator, fi->interval.denominator);
> +
> +       ret = 0;
> +out:
> +       mutex_unlock(&sensor->lock);
> +
> +       return ret;
> +}
> +
> +static int vgxy61_enum_mbus_code(struct v4l2_subdev *sd,
> +                                struct v4l2_subdev_state *sd_state,
> +                                struct v4l2_subdev_mbus_code_enum *code)
> +{
> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> +       struct i2c_client *client = sensor->i2c_client;
> +
> +       dev_dbg(&client->dev, "%s probe index %d\n", __func__, code->index);
> +       if (code->index >= ARRAY_SIZE(vgxy61_supported_codes))
> +               return -EINVAL;
> +
> +       code->code = vgxy61_supported_codes[code->index];
> +
> +       return 0;
> +}
> +
> +static int vgxy61_get_fmt(struct v4l2_subdev *sd,
> +                         struct v4l2_subdev_state *sd_state,
> +                         struct v4l2_subdev_format *format)
> +{
> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> +       struct i2c_client *client = sensor->i2c_client;
> +       struct v4l2_mbus_framefmt *fmt;
> +
> +       dev_dbg(&client->dev, "%s probe %d\n", __func__, format->pad);
> +       dev_dbg(&client->dev, "%s %dx%d\n", __func__, format->format.width, format->format.height);
> +
> +       mutex_lock(&sensor->lock);
> +
> +       if (format->which == V4L2_SUBDEV_FORMAT_TRY)
> +               fmt = v4l2_subdev_get_try_format(&sensor->sd, sd_state, format->pad);
> +       else
> +               fmt = &sensor->fmt;
> +
> +       format->format = *fmt;
> +
> +       mutex_unlock(&sensor->lock);
> +
> +       return 0;
> +}
> +
> +static int vgxy61_set_fmt(struct v4l2_subdev *sd,
> +                         struct v4l2_subdev_state *sd_state,
> +                         struct v4l2_subdev_format *format)
> +{
> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> +       struct i2c_client *client = sensor->i2c_client;
> +       const struct vgxy61_mode_info *new_mode;
> +       struct v4l2_mbus_framefmt *fmt;
> +       int ret;
> +
> +       dev_dbg(&client->dev, "%s probe %d\n", __func__, format->pad);
> +       dev_dbg(&client->dev, "%s %dx%d\n", __func__, format->format.width, format->format.height);
> +
> +       mutex_lock(&sensor->lock);
> +
> +       if (sensor->streaming) {
> +               ret = -EBUSY;
> +               goto out;
> +       }
> +
> +       /* Find best format */
> +       ret = vgxy61_try_fmt_internal(sd, &format->format, &new_mode);
> +       if (ret)
> +               goto out;
> +
> +       if (format->which == V4L2_SUBDEV_FORMAT_TRY)
> +               fmt = v4l2_subdev_get_try_format(sd, sd_state, 0);
> +       else
> +               fmt = &sensor->fmt;
> +       *fmt = format->format;
> +       sensor->current_mode = new_mode;
> +       /* Update pixel rate control to reflect new mode */
> +       __v4l2_ctrl_s_ctrl_int64(sensor->ctrls.pixel_rate, get_pixel_rate(sensor));
> +
> +out:
> +       mutex_unlock(&sensor->lock);
> +
> +       return ret;
> +}
> +
> +static int vgxy61_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state,
> +                                 struct v4l2_subdev_frame_size_enum *fse)
> +{
> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> +       struct i2c_client *client = sensor->i2c_client;
> +
> +       dev_dbg(&client->dev, "%s for index %d\n", __func__, fse->index);
> +       if (fse->index >= sensor->sensor_modes_nb)
> +               return -EINVAL;
> +
> +       fse->min_width = sensor->sensor_modes[fse->index].width;
> +       fse->max_width = fse->min_width;
> +       fse->min_height = sensor->sensor_modes[fse->index].height;
> +       fse->max_height = fse->min_height;
> +
> +       return 0;
> +}
> +
> +static int vgxy61_enum_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state,
> +                                     struct v4l2_subdev_frame_interval_enum *fie)
> +{
> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> +       const struct vgxy61_mode_info *mode = sensor->sensor_modes;
> +
> +       if (fie->index >= sensor->sensor_rates_nb)
> +               return -EINVAL;
> +       do {
> +               if (mode->width == fie->width && mode->height == fie->height)
> +                       break;
> +       } while ((++mode)->width);
> +       if (!mode->width)
> +               return -EINVAL;
> +
> +       fie->interval.numerator = 1;
> +       fie->interval.denominator = sensor->sensor_rates[fie->index];
> +
> +       return 0;
> +}
> +
> +static int vgxy61_update_analog_gain(struct vgxy61_dev *sensor, u32 target)
> +{
> +       struct i2c_client *client = sensor->i2c_client;
> +       unsigned int idx;
> +       int ret;
> +
> +       /* Find smallest analog gains which is above or equal to target gain */
> +       for (idx = 0; idx < ARRAY_SIZE(analog_gains); idx++) {
> +               if (analog_gains[idx] >= target)
> +                       break;
> +       }
> +       /* Cap to maximum gain if no matching gain found */
> +       if (idx == ARRAY_SIZE(analog_gains))
> +               idx--;
> +
> +       /* Apply gain */
> +       ret = vgxy61_write_reg(sensor, DEVICE_ANALOG_GAIN, idx);
> +       if (ret)
> +               return ret;
> +
> +       dev_dbg(&client->dev, "Target analog gain: 0x%04x\n", target);
> +       dev_dbg(&client->dev, "   Set analog gain: 0x%04x\n", analog_gains[idx]);
> +
> +       return 0;
> +}
> +
> +static int vgxy61_update_digital_gain(struct vgxy61_dev *sensor, u32 target)
> +{
> +       struct i2c_client *client = sensor->i2c_client;
> +       int ret;
> +
> +       /*
> +        * For a monochrome version, configuring DIGITAL_GAIN_LONG_CH0 and
> +        * DIGITAL_GAIN_SHORT_CH0 is enough to configure the gain of all
> +        * four sub pixels.
> +        */
> +       ret = vgxy61_write_reg16(sensor, DEVICE_DIGITAL_GAIN_LONG, target);
> +       if (ret)
> +               return ret;
> +       ret = vgxy61_write_reg16(sensor, DEVICE_DIGITAL_GAIN_SHORT, target);
> +       if (ret)
> +               return ret;
> +
> +       dev_dbg(&client->dev, "  Set digital gain: 0x%04x\n", target);
> +
> +       return 0;
> +}
> +
> +static int vgxy61_update_patgen(struct vgxy61_dev *sensor, u32 index)
> +{
> +       u32 pattern = index <= 3 ? index : index + 12;
> +       u32 reg;
> +
> +       reg = (pattern << 18) | (pattern << 4);
> +       if (index)
> +               reg |= (1 << 16) | 1;
> +       return vgxy61_write_reg32(sensor, DEVICE_PATGEN_CTRL, reg);
> +}
> +
> +static int vgxy61_update_gpiox_strobe_mode(struct vgxy61_dev *sensor, enum strobe_modes mode,
> +                                          int idx)
> +{
> +       const u8 index2val[] = {0x0, 0x1, 0x3};
> +       u16 reg;
> +       int ret;
> +
> +       ret = vgxy61_read_reg16(sensor, DEVICE_SIGNALS_CTRL, &reg);
> +       if (ret)
> +               return ret;
> +       reg &= ~(0xf << (idx * 4));
> +       reg |= index2val[mode] << (idx * 4);
> +
> +       return vgxy61_write_reg16(sensor, DEVICE_SIGNALS_CTRL, reg);
> +}
> +
> +static int vgxy61_update_gpios_strobe_mode(struct vgxy61_dev *sensor, enum hdr hdr)
> +{
> +       enum strobe_modes strobe_mode;
> +       int i, ret;
> +
> +       switch (hdr) {
> +       case HDR_LINEAR:
> +               strobe_mode = STROBE_ENABLED;
> +               break;
> +       case HDR_SUB:
> +       case NO_HDR:
> +               strobe_mode = STROBE_LONG;
> +               break;
> +       default:
> +               /* Should never happen */
> +               WARN_ON(true);
> +               break;
> +       }
> +
> +       for (i = 0; i < NB_GPIOS; i++) {
> +               ret = vgxy61_update_gpiox_strobe_mode(sensor, strobe_mode, i);
> +               if (ret)
> +                       return ret;
> +       }
> +
> +       return 0;
> +}
> +
> +static int vgxy61_update_gpios_strobe_polarity(struct vgxy61_dev *sensor, int polarity)
> +{
> +       struct i2c_client *client = sensor->i2c_client;
> +       int ret;
> +
> +       if (sensor->streaming)
> +               return -EBUSY;
> +
> +       dev_dbg(&client->dev, "setting gpios polarity: %d\n", polarity);
> +
> +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_0_CTRL, polarity << 1);
> +       if (ret)
> +               return ret;
> +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_1_CTRL, polarity << 1);
> +       if (ret)
> +               return ret;
> +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_2_CTRL, polarity << 1);
> +       if (ret)
> +               return ret;
> +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_3_CTRL, polarity << 1);
> +       if (ret)
> +               return ret;
> +       return vgxy61_write_reg(sensor, DEVICE_SIGNALS_POLARITY_CTRL, polarity);
> +}
> +
> +static int find_max_long_exposure(struct vgxy61_dev *sensor, int frame_length, int short_expo_ratio)
> +{
> +       int first_rot_max_expo;
> +       int second_rot_max_expo;
> +
> +       /*
> +        * Apply first rule of thumb:
> +        * frame_length < short_line_nb + sensor->sensor_height + sensor->rot_term
> +        * with short_line_nb = long_line_nb / short_expo_ratio
> +        */
> +       first_rot_max_expo = ((frame_length - sensor->sensor_height - sensor->rot_term) *
> +                            short_expo_ratio) - 1;
> +
> +       /*
> +        * Apply second rule of thumb
> +        * frame_length < short_line_nb + long_line_nb + VGXY61_EXPOS_ROT_TERM
> +        * with short_line_nb = long_line_nb / short_expo_ratio
> +        */
> +       second_rot_max_expo = (((frame_length - VGXY61_EXPOS_ROT_TERM) * short_expo_ratio) /
> +                             (short_expo_ratio + 1)) - 1;
> +
> +       /* Take the minimum of both rules */
> +       return min(first_rot_max_expo, second_rot_max_expo);
> +}
> +
> +static int vgxy61_update_exposure(struct vgxy61_dev *sensor, int long_expo_line_nb, enum hdr hdr,
> +                                 bool clamp)
> +{
> +       struct i2c_client *client = sensor->i2c_client;
> +       int max_long_expo;
> +       int max_short_expo = 0;
> +       int short_expo_line_nb = 0;
> +       /* We use a constant ratio of 10 for linear HDR mode */
> +       int hdr_linear_ratio = 10;
> +       u16 frame_length;
> +       int ret;
> +
> +       ret = vgxy61_read_reg16(sensor, DEVICE_FRAME_LENGTH, &frame_length);
> +       if (ret)
> +               return ret;
> +
> +       /* Long and short integration times must not be less than 10 lines */
> +       long_expo_line_nb = max(10, long_expo_line_nb);
> +       long_expo_line_nb = min_t(int, frame_length, long_expo_line_nb);
> +
> +       /* Compute short exposure according to hdr mode and long exposure */
> +       switch (hdr) {
> +       case HDR_LINEAR:
> +               max_long_expo = find_max_long_exposure(sensor, frame_length, hdr_linear_ratio);
> +               max_short_expo = (max_long_expo + (hdr_linear_ratio / 2)) / hdr_linear_ratio;
> +               short_expo_line_nb = (long_expo_line_nb + (hdr_linear_ratio / 2)) /
> +                                    hdr_linear_ratio;
> +               break;
> +       case HDR_SUB:
> +               max_long_expo = find_max_long_exposure(sensor, frame_length, 1);
> +               max_short_expo = max_long_expo;
> +               short_expo_line_nb = long_expo_line_nb;
> +               break;
> +       case NO_HDR:
> +               /*
> +                * As short expo is 0 here, only the second rule of thumb applies, see
> +                * find_max_long_exposure for more
> +                */
> +               max_long_expo = frame_length - VGXY61_EXPOS_ROT_TERM;
> +               break;
> +       default:
> +               /* Should never happen */
> +               WARN_ON(true);
> +               break;
> +       }
> +
> +       if (long_expo_line_nb > max_long_expo) {
> +               if (!clamp) {
> +                       dev_err(&client->dev, "Exposure %d too high (max for this hdr mode %d)\n",
> +                               long_expo_line_nb, max_long_expo);
> +                       return -EINVAL;
> +               }
> +               dev_warn(&client->dev, "Exposure %d too high for this hdr mode, clamping to %d\n",
> +                        long_expo_line_nb, max_long_expo);
> +               long_expo_line_nb = max_long_expo;
> +               short_expo_line_nb = max_short_expo;
> +       }
> +
> +       dev_dbg(&client->dev, "frame_length %d, long_expo_line_nb %d, short_expo_line_nb %d",
> +               frame_length, long_expo_line_nb, short_expo_line_nb);
> +
> +       /* Apply exposure */
> +       sensor->expo_long = long_expo_line_nb;
> +       sensor->expo_short = short_expo_line_nb;
> +
> +       if (sensor->streaming)
> +               return apply_exposure(sensor);
> +       return 0;
> +}
> +
> +static int vgxy61_update_hdr(struct vgxy61_dev *sensor, u32 index)
> +{
> +       const u8 index2val[] = {0x1, 0x4, 0xa};
> +       int ret;
> +
> +       /*
> +        * Short exposure changes according to HDR mode, do it first as it can
> +        * violate sensors 'rule of thumbs' and therefore will require to change
> +        * the long exposure
> +        */
> +       ret = vgxy61_update_exposure(sensor, sensor->expo_long, index, true);
> +       if (ret)
> +               return ret;
> +
> +       /* Update strobe mode according to HDR */
> +       ret = vgxy61_update_gpios_strobe_mode(sensor, index);
> +       if (ret)
> +               return ret;
> +
> +       ret = vgxy61_write_reg(sensor, DEVICE_HDR_CTRL, index2val[index]);
> +       if (ret)
> +               return ret;
> +       sensor->hdr = index;
> +
> +       return 0;
> +}
> +
> +static int vgxy61_get_temp_stream_enabled(struct vgxy61_dev *sensor, int *temp)
> +{
> +       int ret;
> +       u16 temperature;
> +
> +       ret = vgxy61_read_reg16(sensor, DEVICE_THSENS1_TEMPERATURE, &temperature);
> +       if (ret)
> +               return ret;
> +
> +       /* Temperature is expressed in Kelvin in Q10.2 fixed point format*/
> +       temperature = (temperature & 0x0fff) >> 2;
> +       temperature = kelvin_to_celsius(temperature);
> +
> +       *temp = temperature;
> +
> +       return 0;
> +}
> +
> +static int vgxy61_get_temp_stream_disabled(struct vgxy61_dev *sensor, int *temp)
> +{
> +       int ret;
> +
> +       /* Device needs to be in standby mode if streaming is off */
> +       ret = vgxy61_write_reg(sensor, DEVICE_STBY, STBY_REQ_TMP_READ);
> +       if (ret)
> +               return ret;
> +       ret = vgxy61_poll_reg(sensor, DEVICE_STBY, STBY_NO_REQ);
> +       if (ret)
> +               return ret;
> +
> +       return vgxy61_get_temp_stream_enabled(sensor, temp);
> +}
> +
> +static int vgxy61_get_temp(struct vgxy61_dev *sensor, int *temp)
> +{
> +       *temp = 0;
> +       if (sensor->streaming)
> +               return vgxy61_get_temp_stream_enabled(sensor, temp);
> +       else
> +               return vgxy61_get_temp_stream_disabled(sensor, temp);
> +}
> +
> +static int vgxy61_s_ctrl(struct v4l2_ctrl *ctrl)
> +{
> +       struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> +       int ret;
> +
> +       switch (ctrl->id) {
> +       case V4L2_CID_EXPOSURE:
> +               ret = vgxy61_update_exposure(sensor, ctrl->val, sensor->hdr, false);
> +               ctrl->val = sensor->expo_long;
> +               break;
> +       case V4L2_CID_ANALOGUE_GAIN:
> +               ret = vgxy61_update_analog_gain(sensor, ctrl->val);
> +               break;
> +       case V4L2_CID_DIGITAL_GAIN:
> +               ret = vgxy61_update_digital_gain(sensor, ctrl->val);
> +               break;
> +       case V4L2_CID_VFLIP:
> +       case V4L2_CID_HFLIP:
> +               if (sensor->streaming) {
> +                       ret = -EBUSY;
> +                       break;
> +               }
> +               if (ctrl->id == V4L2_CID_VFLIP)
> +                       sensor->vflip = ctrl->val;
> +               if (ctrl->id == V4L2_CID_HFLIP)
> +                       sensor->hflip = ctrl->val;
> +               ret = vgxy61_write_reg(sensor, DEVICE_ORIENTATION,
> +                                      sensor->hflip | (sensor->vflip << 1));
> +               break;
> +       case V4L2_CID_TEST_PATTERN:
> +               ret = vgxy61_update_patgen(sensor, ctrl->val);
> +               break;
> +       case V4L2_CID_HDR:
> +               ret = vgxy61_update_hdr(sensor, ctrl->val);
> +               break;
> +       case V4L2_CID_GPIOS_STROBE_LONG_START_DELAY:
> +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_LONG_START_DELAY, ctrl->val);
> +               break;
> +       case V4L2_CID_GPIOS_STROBE_LONG_END_DELAY:
> +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_LONG_END_DELAY, ctrl->val);
> +               break;
> +       case V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY:
> +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_SHORT_START_DELAY, ctrl->val);
> +               break;
> +       case V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY:
> +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_SHORT_END_DELAY, ctrl->val);
> +               break;
> +       default:
> +               ret = -EINVAL;
> +               break;
> +       }
> +
> +       return ret;
> +}
> +
> +static int vgxy61_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
> +{
> +       struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> +       int temperature;
> +       int ret;
> +
> +       switch (ctrl->id) {
> +       case V4L2_CID_TEMPERATURE:
> +               ret = vgxy61_get_temp(sensor, &temperature);
> +               if (ret)
> +                       break;
> +               ret = __v4l2_ctrl_s_ctrl(ctrl, temperature);
> +               break;
> +       default:
> +               ret = -EINVAL;
> +               break;
> +       }
> +
> +       return ret;
> +}
> +
> +static const struct v4l2_ctrl_ops vgxy61_ctrl_ops = {
> +       .g_volatile_ctrl = vgxy61_g_volatile_ctrl,
> +       .s_ctrl = vgxy61_s_ctrl,
> +};
> +
> +static const struct v4l2_ctrl_config vgxy61_hdr_ctrl = {
> +       .ops            = &vgxy61_ctrl_ops,
> +       .id             = V4L2_CID_HDR,
> +       .name           = "HDR mode",
> +       .type           = V4L2_CTRL_TYPE_MENU,
> +       .min            = 0,
> +       .max            = ARRAY_SIZE(vgxy61_hdr_modes) - 1,
> +       .def            = NO_HDR,
> +       .qmenu          = vgxy61_hdr_modes,
> +};
> +
> +static const struct v4l2_ctrl_config vgxy61_strobe_long_start_delay = {
> +       .ops            = &vgxy61_ctrl_ops,
> +       .id             = V4L2_CID_GPIOS_STROBE_LONG_START_DELAY,
> +       .name           = "Long strobe mode start delay in lines",
> +       .type           = V4L2_CTRL_TYPE_INTEGER,
> +       .min            = -128,
> +       .max            = 127,
> +       .step           = 1,
> +       .def            = 0,
> +       .flags          = 0,
> +};
> +
> +static const struct v4l2_ctrl_config vgxy61_strobe_long_end_delay = {
> +       .ops            = &vgxy61_ctrl_ops,
> +       .id             = V4L2_CID_GPIOS_STROBE_LONG_END_DELAY,
> +       .name           = "Long strobe mode end delay in lines",
> +       .type           = V4L2_CTRL_TYPE_INTEGER,
> +       .min            = -128,
> +       .max            = 127,
> +       .step           = 1,
> +       .def            = 0,
> +       .flags          = 0,
> +};
> +
> +static const struct v4l2_ctrl_config vgxy61_strobe_short_start_delay = {
> +       .ops            = &vgxy61_ctrl_ops,
> +       .id             = V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY,
> +       .name           = "Short strobe mode start delay in lines",
> +       .type           = V4L2_CTRL_TYPE_INTEGER,
> +       .min            = -128,
> +       .max            = 127,
> +       .step           = 1,
> +       .def            = 0,
> +       .flags          = 0,
> +};
> +
> +static const struct v4l2_ctrl_config vgxy61_strobe_short_end_delay = {
> +       .ops            = &vgxy61_ctrl_ops,
> +       .id             = V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY,
> +       .name           = "Short strobe mode end delay in lines",
> +       .type           = V4L2_CTRL_TYPE_INTEGER,
> +       .min            = -128,
> +       .max            = 127,
> +       .step           = 1,
> +       .def            = 0,
> +       .flags          = 0,
> +};
> +
> +static const struct v4l2_ctrl_config vgxy61_temp_ctrl = {
> +       .ops            = &vgxy61_ctrl_ops,
> +       .id             = V4L2_CID_TEMPERATURE,
> +       .name           = "Temperature in °C",
> +       .type           = V4L2_CTRL_TYPE_INTEGER,
> +       .min            = -128,
> +       .max            = 128,
> +       .step           = 1,
> +};
> +
> +static int vgxy61_init_controls(struct vgxy61_dev *sensor)
> +{
> +       const struct v4l2_ctrl_ops *ops = &vgxy61_ctrl_ops;
> +       struct vgxy61_ctrls *ctrls = &sensor->ctrls;
> +       struct v4l2_ctrl_handler *hdl = &sensor->ctrls.handler;
> +       int ret;
> +
> +       v4l2_ctrl_handler_init(hdl, 16);
> +       /* We can use our own mutex for the ctrl lock */
> +       hdl->lock = &sensor->lock;
> +       ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, 10, 0xffff, 1,
> +                                           sensor->expo_long);
> +       /* This is 8.8 fixed point value */
> +       ctrls->analog_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, 0x3fff, 1,
> +                                              0x0);
> +       /* This is 8.8 fixed point value */
> +       ctrls->digital_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 0, 0xfff, 1,
> +                                               0x100);
> +       ctrls->vflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, sensor->vflip);
> +       ctrls->hflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, sensor->hflip);
> +       ctrls->patgen = v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN,
> +                                                    ARRAY_SIZE(vgxy61_test_pattern_menu) - 1,
> +                                                    0, 0, vgxy61_test_pattern_menu);
> +       ctrls->hdr = v4l2_ctrl_new_custom(hdl, &vgxy61_hdr_ctrl, NULL);
> +       ctrls->pixel_rate = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1,
> +                                             get_pixel_rate(sensor));
> +       ctrls->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> +       ctrls->link_freq = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ,
> +                                                 ARRAY_SIZE(link_freq) - 1, 0, link_freq);
> +       ctrls->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> +       /* Gpios ctrls */
> +       ctrls->gpios.long_start = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_long_start_delay, NULL);
> +       ctrls->gpios.long_end = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_long_end_delay, NULL);
> +       ctrls->gpios.short_start = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_short_start_delay,
> +                                                       NULL);
> +       ctrls->gpios.short_end = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_short_end_delay, NULL);
> +       /* Temperature ctrl */
> +       ctrls->temp = v4l2_ctrl_new_custom(hdl, &vgxy61_temp_ctrl, NULL);
> +       ctrls->temp->flags |= V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY;
> +
> +       if (hdl->error) {
> +               ret = hdl->error;
> +               goto free_ctrls;
> +       }
> +
> +       sensor->sd.ctrl_handler = hdl;
> +       return 0;
> +
> +free_ctrls:
> +       v4l2_ctrl_handler_free(hdl);
> +       return ret;
> +}
> +
> +static const struct v4l2_subdev_core_ops vgxy61_core_ops = {
> +};
> +
> +static const struct v4l2_subdev_video_ops vgxy61_video_ops = {
> +       .s_stream = vgxy61_s_stream,
> +       .g_frame_interval = vgxy61_g_frame_interval,
> +       .s_frame_interval = vgxy61_s_frame_interval,
> +};
> +
> +static const struct v4l2_subdev_pad_ops vgxy61_pad_ops = {
> +       .enum_mbus_code = vgxy61_enum_mbus_code,
> +       .get_fmt = vgxy61_get_fmt,
> +       .set_fmt = vgxy61_set_fmt,
> +       .enum_frame_size = vgxy61_enum_frame_size,
> +       .enum_frame_interval = vgxy61_enum_frame_interval,
> +};
> +
> +static const struct v4l2_subdev_ops vgxy61_subdev_ops = {
> +       .core = &vgxy61_core_ops,
> +       .video = &vgxy61_video_ops,
> +       .pad = &vgxy61_pad_ops,
> +};
> +
> +static const struct media_entity_operations vgxy61_subdev_entity_ops = {
> +       .link_validate = v4l2_subdev_link_validate,
> +};
> +
> +/* Set phy polarities */
> +static int vgxy61_tx_from_ep(struct vgxy61_dev *sensor, struct fwnode_handle *endpoint)
> +{
> +       struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
> +       struct i2c_client *client = sensor->i2c_client;
> +       u32 log2phy[NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
> +       u32 phy2log[NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
> +       int polarities[NB_POLARITIES] = {0, 0, 0, 0, 0};
> +       int l_nb;
> +       int p, l;
> +       int ret;
> +       int i;
> +
> +       ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep);
> +       if (ret)
> +               goto error_alloc;
> +
> +       l_nb = ep.bus.mipi_csi2.num_data_lanes;
> +       if (l_nb != 1 && l_nb != 2 && l_nb != 4) {
> +               dev_err(&client->dev, "invalid data lane number %d\n", l_nb);
> +               goto error_ep;
> +       }
> +
> +       /* Build log2phy, phy2log and polarities from ep info */
> +       log2phy[0] = ep.bus.mipi_csi2.clock_lane;
> +       phy2log[log2phy[0]] = 0;
> +       for (l = 1; l < l_nb + 1; l++) {
> +               log2phy[l] = ep.bus.mipi_csi2.data_lanes[l - 1];
> +               phy2log[log2phy[l]] = l;
> +       }
> +       /*
> +        * Then fill remaining slots for every physical slot to have something valid for hardware
> +        * stuff.
> +        */
> +       for (p = 0; p < NB_POLARITIES; p++) {
> +               if (phy2log[p] != ~0)
> +                       continue;
> +               phy2log[p] = l;
> +               log2phy[l] = p;
> +               l++;
> +       }
> +       for (l = 0; l < l_nb + 1; l++)
> +               polarities[l] = ep.bus.mipi_csi2.lane_polarities[l];
> +
> +       if (log2phy[0] != 0) {
> +               dev_err(&client->dev, "clk lane must be map to physical lane 0\n");
> +               goto error_ep;
> +       }
> +       sensor->oif_ctrl = (polarities[4] << 15) + ((phy2log[4] - 1) << 13) +
> +                          (polarities[3] << 12) + ((phy2log[3] - 1) << 10) +
> +                          (polarities[2] <<  9) + ((phy2log[2] - 1) <<  7) +
> +                          (polarities[1] <<  6) + ((phy2log[1] - 1) <<  4) +
> +                          (polarities[0] <<  3) +
> +                          l_nb;
> +       sensor->nb_of_lane = l_nb;
> +
> +       dev_dbg(&client->dev, "tx uses %d lanes", l_nb);
> +       for (i = 0; i < 5; i++) {
> +               dev_dbg(&client->dev, "log2phy[%d] = %d\n", i, log2phy[i]);
> +               dev_dbg(&client->dev, "phy2log[%d] = %d\n", i, phy2log[i]);
> +               dev_dbg(&client->dev, "polarity[%d] = %d\n", i, polarities[i]);
> +       }
> +       dev_dbg(&client->dev, "oif_ctrl = 0x%04x\n", sensor->oif_ctrl);
> +
> +       v4l2_fwnode_endpoint_free(&ep);
> +
> +       return 0;
> +
> +error_ep:
> +       v4l2_fwnode_endpoint_free(&ep);
> +error_alloc:
> +
> +       return -EINVAL;
> +}
> +
> +static int vgxy61_configure(struct vgxy61_dev *sensor)
> +{
> +       struct i2c_client *client = sensor->i2c_client;
> +       int sensor_freq;
> +       unsigned int prediv;
> +       unsigned int mult;
> +       int ret;
> +
> +       compute_pll_parameters_by_freq(sensor->clk_freq, &prediv, &mult);
> +       sensor_freq = (mult * sensor->clk_freq) / prediv;
> +       /* Frequency to data rate is 1:1 ratio for MIPI */
> +       sensor->data_rate_in_mbps = sensor_freq;
> +       /* Video timing ISP path (pixel clock)  requires 804/5 mhz = 160 mhz */
> +       sensor->pclk = sensor_freq / 5;
> +
> +       /* Cache line_length value */
> +       ret = vgxy61_read_reg16(sensor, DEVICE_LINE_LENGTH, &sensor->line_length);
> +       if (ret)
> +               return ret;
> +       /* Configure clocks */
> +       ret = vgxy61_write_reg32(sensor, DEVICE_EXT_CLOCK, sensor->clk_freq);
> +       if (ret)
> +               return ret;
> +       ret = vgxy61_write_reg(sensor, DEVICE_CLK_PLL_PREDIV, prediv);
> +       if (ret)
> +               return ret;
> +       ret = vgxy61_write_reg(sensor, DEVICE_CLK_SYS_PLL_MULT, mult);
> +       if (ret)
> +               return ret;
> +       /* Configure interface */
> +       ret = vgxy61_write_reg16(sensor, DEVICE_OIF_CTRL, sensor->oif_ctrl);
> +       if (ret)
> +               return ret;
> +       /* Disable pwm compression */
> +       ret = vgxy61_write_reg(sensor, DEVICE_FRAME_CONTENT_CTRL, 0);
> +       if (ret)
> +               return ret;
> +       /* Disable asil lines */
> +       ret = vgxy61_write_reg(sensor, DEVICE_BYPASS_CTRL, 4);
> +       if (ret)
> +               return ret;
> +       /* Set gpios polarity according to device tree value */
> +       ret = vgxy61_update_gpios_strobe_polarity(sensor, sensor->gpios_polarity);
> +       if (ret)
> +               return ret;
> +       /* HDR mode */
> +       ret = vgxy61_update_hdr(sensor, sensor->hdr);
> +       if (ret)
> +               return ret;
> +       /* Slave mode */
> +       ret = vgxy61_write_reg(sensor, DEVICE_VT_CTRL, sensor->slave_mode);
> +       if (ret)
> +               return ret;
> +       /* Set pattern generator solid to middle value */
> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_GR, 0x800);
> +       if (ret)
> +               return ret;
> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_R, 0x800);
> +       if (ret)
> +               return ret;
> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_B, 0x800);
> +       if (ret)
> +               return ret;
> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_GB, 0x800);
> +       if (ret)
> +               return ret;
> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_GR, 0x800);
> +       if (ret)
> +               return ret;
> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_R, 0x800);
> +       if (ret)
> +               return ret;
> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_B, 0x800);
> +       if (ret)
> +               return ret;
> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_GB, 0x800);
> +       if (ret)
> +               return ret;
> +
> +       dev_dbg(&client->dev, "clock prediv = %d\n", prediv);
> +       dev_dbg(&client->dev, "clock mult = %d\n", mult);
> +       dev_dbg(&client->dev, "data rate = %d mbps\n",
> +               sensor->data_rate_in_mbps);
> +
> +       return 0;
> +}
> +
> +static int vgxy61_patch(struct vgxy61_dev *sensor)
> +{
> +       struct i2c_client *client = sensor->i2c_client;
> +       u16 patch;
> +       int ret;
> +
> +       ret = vgxy61_write_array(sensor, DEVICE_FWPATCH_START_ADDR, sizeof(patch_array),
> +                                patch_array);
> +       if (ret)
> +               return ret;
> +
> +       ret = vgxy61_write_reg(sensor, DEVICE_STBY, 0x10);
> +       if (ret)
> +               return ret;
> +
> +       ret = vgxy61_poll_reg(sensor, DEVICE_STBY, 0);
> +       if (ret)
> +               return ret;
> +
> +       ret = vgxy61_read_reg16(sensor, DEVICE_FWPATCH_REVISION, &patch);
> +       if (ret)
> +               return ret;
> +
> +       if (patch != (DEVICE_FWPATCH_REVISION_MAJOR << 12) +
> +                    (DEVICE_FWPATCH_REVISION_MINOR << 8) +
> +                    DEVICE_FWPATCH_REVISION_MICRO) {
> +               dev_err(&client->dev, "bad patch version expected %d.%d.%d got %d.%d.%d\n",
> +                       DEVICE_FWPATCH_REVISION_MAJOR,
> +                       DEVICE_FWPATCH_REVISION_MINOR,
> +                       DEVICE_FWPATCH_REVISION_MICRO,
> +                       patch >> 12, (patch >> 8) & 0x0f, patch & 0xff);
> +               return -ENODEV;
> +       }
> +       dev_dbg(&client->dev, "patch %d.%d.%d applied\n",
> +               patch >> 12, (patch >> 8) & 0x0f, patch & 0xff);
> +
> +       return 0;
> +}
> +
> +static int vgxy61_detect_cut_version(struct vgxy61_dev *sensor)
> +{
> +       struct i2c_client *client = sensor->i2c_client;
> +       u16 device_rev;
> +       int ret;
> +
> +       ret = vgxy61_read_reg16(sensor, DEVICE_REVISION, &device_rev);
> +       if (ret)
> +               return ret;
> +
> +       switch (device_rev >> 8) {
> +       case 0xA:
> +               dev_info(&client->dev, "Cut1 detected\n");
> +               dev_err(&client->dev, "Cut1 not supported by this driver\n");
> +               return -ENODEV;
> +       case 0xB:
> +               dev_info(&client->dev, "Cut2 detected\n");
> +               return 0;
> +       case 0xC:
> +               dev_info(&client->dev, "Cut3 detected\n");
> +               return 0;
> +       default:
> +               dev_err(&client->dev, "Unable to detect cut version\n");
> +               return -ENODEV;
> +       }
> +}
> +
> +static int vgxy61_detect(struct vgxy61_dev *sensor)
> +{
> +       struct i2c_client *client = sensor->i2c_client;
> +       u16 id = 0;
> +       int ret;
> +       u8 st;
> +
> +       ret = vgxy61_read_reg16(sensor, DEVICE_MODEL_ID_REG, &id);
> +       if (ret)
> +               return ret;
> +       if (id != VG5661_MODEL_ID && id != VG5761_MODEL_ID) {
> +               dev_warn(&client->dev, "Unsupported sensor id %x\n", id);
> +               return -ENODEV;
> +       }
> +       dev_dbg(&client->dev, "detected sensor id = 0x%04x\n", id);
> +
> +       if (id == VG5761_MODEL_ID) {
> +               sensor->sensor_width = VGX761_WIDTH;
> +               sensor->sensor_height = VGX761_HEIGHT;
> +               sensor->sensor_modes = vgx761_mode_data;
> +               sensor->sensor_modes_nb = ARRAY_SIZE(vgx761_mode_data);
> +               sensor->current_mode = &vgx761_mode_data[VGX761_DEFAULT_MODE];
> +               sensor->rot_term = VGX761_SHORT_ROT_TERM;
> +               sensor->sensor_rates = vgx761_sensor_frame_rates;
> +               sensor->sensor_rates_nb = ARRAY_SIZE(vgx761_sensor_frame_rates);
> +       } else if (id == VG5661_MODEL_ID) {
> +               sensor->sensor_width = VGX661_WIDTH;
> +               sensor->sensor_height = VGX661_HEIGHT;
> +               sensor->sensor_modes = vgx661_mode_data;
> +               sensor->sensor_modes_nb = ARRAY_SIZE(vgx661_mode_data);
> +               sensor->current_mode = &vgx661_mode_data[VGX661_DEFAULT_MODE];
> +               sensor->rot_term = VGX661_SHORT_ROT_TERM;
> +               sensor->sensor_rates = vgx661_sensor_frame_rates;
> +               sensor->sensor_rates_nb = ARRAY_SIZE(vgx661_sensor_frame_rates);
> +       }
> +
> +       ret = vgxy61_wait_state(sensor, SW_STBY);
> +       if (ret)
> +               return ret;
> +
> +       ret = vgxy61_read_reg(sensor, DEVICE_NVM, &st);
> +       if (ret)
> +               return ret;
> +       if (st != NVM_OK)
> +               dev_warn(&client->dev, "Bad nvm state got %d\n", st);
> +
> +       /* Detect cut version */
> +       ret = vgxy61_detect_cut_version(sensor);
> +       if (ret)
> +               return ret;
> +
> +       return 0;
> +}
> +
> +static int vgxy61_probe(struct i2c_client *client)
> +{
> +       struct device *dev = &client->dev;
> +       struct fwnode_handle *endpoint;
> +       struct vgxy61_dev *sensor;
> +       int ret;
> +
> +       sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
> +       if (!sensor)
> +               return -ENOMEM;
> +
> +       sensor->i2c_client = client;
> +       sensor->streaming = false;
> +       sensor->fmt.code = MEDIA_BUS_FMT_SGBRG8_1X8;
> +       sensor->fmt.field = V4L2_FIELD_NONE;
> +       sensor->fmt.colorspace = V4L2_COLORSPACE_SRGB;
> +       sensor->frame_interval.numerator = 1;
> +       sensor->frame_interval.denominator = 60;
> +       sensor->hdr = NO_HDR;
> +       sensor->expo_long = 200;
> +       sensor->expo_short = 0;
> +       sensor->hflip = false;
> +       sensor->vflip = false;
> +
> +       endpoint = fwnode_graph_get_next_endpoint(of_fwnode_handle(dev->of_node), NULL);
> +       if (!endpoint) {
> +               dev_err(dev, "endpoint node not found\n");
> +               return -EINVAL;
> +       }
> +
> +       ret = vgxy61_tx_from_ep(sensor, endpoint);
> +       fwnode_handle_put(endpoint);
> +       if (ret) {
> +               dev_err(dev, "Failed to parse endpoint %d\n", ret);
> +               return ret;
> +       }
> +
> +       sensor->xclk = devm_clk_get(dev, "xclk");
> +       if (IS_ERR(sensor->xclk)) {
> +               dev_err(dev, "failed to get xclk\n");
> +               return PTR_ERR(sensor->xclk);
> +       }
> +       sensor->clk_freq = clk_get_rate(sensor->xclk);
> +       if (sensor->clk_freq < 6000000 || sensor->clk_freq > 27000000) {
> +               dev_err(dev, "Only 6Mhz-27Mhz clock range supported. provide %d Hz\n",
> +                       sensor->clk_freq);
> +               return -EINVAL;
> +       }
> +       sensor->gpios_polarity = of_property_read_bool(dev->of_node, "invert-gpios-polarity");
> +       sensor->slave_mode = of_property_read_bool(dev->of_node, "slave-mode");
> +
> +       v4l2_i2c_subdev_init(&sensor->sd, client, &vgxy61_subdev_ops);
> +       sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> +       sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
> +       sensor->sd.entity.ops = &vgxy61_subdev_entity_ops;
> +       sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
> +
> +       ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
> +       if (ret) {
> +               dev_err(&client->dev, "pads init failed %d\n", ret);
> +               return ret;
> +       }
> +
> +       /* Request optional reset pin */
> +       sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
> +
> +       ret = vgxy61_get_regulators(sensor);
> +       if (ret) {
> +               dev_err(&client->dev, "failed to get regulators %d\n", ret);
> +               goto entity_cleanup;
> +       }
> +
> +       ret = regulator_bulk_enable(VGXY61_NUM_SUPPLIES, sensor->supplies);
> +       if (ret) {
> +               dev_err(&client->dev, "failed to enable regulators %d\n", ret);
> +               goto entity_cleanup;
> +       }
> +
> +       ret = clk_prepare_enable(sensor->xclk);
> +       if (ret) {
> +               dev_err(&client->dev, "failed to enable clock %d\n", ret);
> +               goto disable_bulk;
> +       }
> +
> +       mutex_init(&sensor->lock);
> +
> +       /* Apply reset sequence */
> +       if (sensor->reset_gpio) {
> +               ret = vgxy61_apply_reset(sensor);
> +               if (ret) {
> +                       dev_err(&client->dev, "sensor reset failed %d\n", ret);
> +                       goto disable_clock;
> +               }
> +       }
> +
> +       ret = vgxy61_detect(sensor);
> +       if (ret) {
> +               dev_err(&client->dev, "sensor detect failed %d\n", ret);
> +               goto disable_clock;
> +       }
> +       sensor->fmt.width = sensor->current_mode->width;
> +       sensor->fmt.height = sensor->current_mode->height;
> +
> +       ret = vgxy61_patch(sensor);
> +       if (ret) {
> +               dev_err(&client->dev, "sensor patch failed %d\n", ret);
> +               goto disable_clock;
> +       }
> +
> +       ret = vgxy61_configure(sensor);
> +       if (ret) {
> +               dev_err(&client->dev, "sensor configuration failed %d\n", ret);
> +               goto disable_clock;
> +       }
> +
> +       ret = vgxy61_init_controls(sensor);
> +       if (ret) {
> +               dev_err(&client->dev, "controls initialization failed %d\n", ret);
> +               goto disable_clock;
> +       }
> +
> +       ret = v4l2_async_register_subdev(&sensor->sd);
> +       if (ret) {
> +               dev_err(&client->dev, "async subdev register failed %d\n", ret);
> +               goto disable_clock;
> +       }
> +
> +       dev_info(&client->dev, "vgxy61 probe successfully\n");
> +
> +       return 0;
> +
> +disable_clock:
> +       clk_disable_unprepare(sensor->xclk);
> +disable_bulk:
> +       regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
> +entity_cleanup:
> +       mutex_destroy(&sensor->lock);
> +       media_entity_cleanup(&sensor->sd.entity);
> +       return ret;
> +}
> +
> +static int vgxy61_remove(struct i2c_client *client)
> +{
> +       struct v4l2_subdev *sd = i2c_get_clientdata(client);
> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> +
> +       v4l2_async_unregister_subdev(&sensor->sd);
> +       clk_disable_unprepare(sensor->xclk);
> +       mutex_destroy(&sensor->lock);
> +       media_entity_cleanup(&sensor->sd.entity);
> +       regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
> +
> +       return 0;
> +}
> +
> +static const struct of_device_id vgxy61_dt_ids[] = {
> +       { .compatible = "st,st-vgxy61" },
> +       { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, vgxy61_dt_ids);
> +
> +static struct i2c_driver vgxy61_i2c_driver = {
> +       .driver = {
> +               .name  = "st-vgxy61",
> +               .of_match_table = vgxy61_dt_ids,
> +       },
> +       .probe_new = vgxy61_probe,
> +       .remove = vgxy61_remove,
> +};
> +
> +module_i2c_driver(vgxy61_i2c_driver);
> +
> +MODULE_AUTHOR("Benjamin Mugnier <benjamin.mugnier@foss.st.com>");
> +MODULE_AUTHOR("Mickael Guene <mickael.guene@st.com>");
> +MODULE_AUTHOR("Sylvain Petinot <sylvain.petinot@foss.st.com>");
> +MODULE_DESCRIPTION("VGXY61 camera subdev driver");
> +MODULE_LICENSE("GPL v2");
> -- 
> 2.25.1
>

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

* Re: [PATCH 2/2] media: i2c: Add driver for ST VGXY61 camera sensor
  2022-03-10 13:52   ` Kieran Bingham
@ 2022-03-10 15:15     ` Benjamin Mugnier
  2022-03-10 15:50       ` Dave Stevenson
  2022-03-10 15:21     ` Dave Stevenson
  1 sibling, 1 reply; 17+ messages in thread
From: Benjamin Mugnier @ 2022-03-10 15:15 UTC (permalink / raw)
  To: Kieran Bingham, linux-media; +Cc: alain.volmat, hugues.fruchet, sylvain.petinot

Hi Kieran,

On 10/03/2022 14:52, Kieran Bingham wrote:
> Hi Benjamin,
> 
> Thank you for the patch - 
> 
> Quoting Benjamin Mugnier (2022-03-10 13:32:55)
>> The VGXY61 has a quad lanes CSI-2 output port running at 800mbps per
>> lane, and supports RAW8, RAW10, RAW12, RAW14 and RAW16 formats.
>> The driver handles both sensor types:
>> - VG5661 and VG6661: 1.6 Mpx (1464 x 1104) 75fps.
>> - VG5761 and VG6761: 2.3 Mpx (1944 x 1204) 60 fps.
>> The driver supports:
>> - HDR linearize mode, HDR substraction mode, and no HDR
>> - GPIOs LEDs strobing
>> - Digital binning and analog subsampling
>> - Horizontal and vertical flip
>> - Manual exposure
>> - Analog and digital gains
>> - Test patterns
> 
> I haven't reviewed the driver directly yet, but I have a script which
> looks for key requirements for libcamera.
> (https://git.linuxtv.org/libcamera.git/tree/Documentation/sensor_driver_requirements.rst)
> 
> 
> Media Controller Support:
>  - V4L2_SUBDEV_FL_HAS_DEVNODE      : found
>  - MEDIA_ENT_F_CAM_SENSOR          : found
> 
> Mandatory Controls:
>  - V4L2_CID_EXPOSURE               : found
>  - V4L2_CID_HBLANK                 : --------
>  - V4L2_CID_VBLANK                 : --------
>  - V4L2_CID_PIXEL_RATE             : found
> 
> Selection Controls (will become mandatory):
>  - V4L2_SEL_TGT_CROP_DEFAULT       : --------
>  - V4L2_SEL_TGT_CROP               : --------
>  - V4L2_SEL_TGT_CROP_BOUNDS        : --------
>  - .get_selection                  : --------
> 
> Optional Controls:
>  - V4L2_CID_TEST_PATTERN           : found
>  - V4L2_CID_GAIN                   : --------
>  - V4L2_CID_ANALOGUE_GAIN          : found
>  - V4L2_CID_CAMERA_SENSOR_ROTATION : --------
>  - V4L2_CID_CAMERA_ORIENTATION     : --------
> 
> 
> The key missing pieces are HBLANK/VBLANK and the .get_selection API. Is
> it easy/feasible to add these?
> 

The .get_selection API should be easy.
Unfortunately, this device does not have registers for HBLANK/VBLANK. I may be able to compute them for a read only control, but it will be harder to set. Is the read only control enough for libcamera?

My next goal is to run this sensor on a Raspberry Pi through libcamera. As I'm fairly new to libcamera it may take some time. My initial idea was to submit libcamera related changes in another set of patches once done, so I could test the entire stack and make extra adjustements and fixes to the driver that I'm not aware of yet. What do you think?

Regards,

Benjamin

> --
> Regards
> 
> Kieran
> 
> 
>>
>> Signed-off-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com>
>> ---
>>  drivers/media/i2c/Kconfig     |   11 +
>>  drivers/media/i2c/Makefile    |    1 +
>>  drivers/media/i2c/st-vgxy61.c | 1919 +++++++++++++++++++++++++++++++++
>>  3 files changed, 1931 insertions(+)
>>  create mode 100644 drivers/media/i2c/st-vgxy61.c
>>
>> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
>> index fe66093b8849..e6e9c1f19c09 100644
>> --- a/drivers/media/i2c/Kconfig
>> +++ b/drivers/media/i2c/Kconfig
>> @@ -1454,6 +1454,17 @@ config VIDEO_S5C73M3
>>           This is a V4L2 sensor driver for Samsung S5C73M3
>>           8 Mpixel camera.
>>  
>> +config VIDEO_ST_VGXY61
>> +       tristate "ST VGXY61 sensor support"
>> +       depends on OF
>> +       depends on GPIOLIB && VIDEO_V4L2 && I2C
>> +       select MEDIA_CONTROLLER
>> +       select VIDEO_V4L2_SUBDEV_API
>> +       select V4L2_FWNODE
>> +       help
>> +         This is a Video4Linux2 sensor driver for the ST VGXY61
>> +         camera sensor.
>> +
>>  endmenu
>>  
>>  menu "Lens drivers"
>> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
>> index f6b80ef6f41d..0190b5edfa24 100644
>> --- a/drivers/media/i2c/Makefile
>> +++ b/drivers/media/i2c/Makefile
>> @@ -138,4 +138,5 @@ obj-$(CONFIG_VIDEO_MAX9271_LIB)     += max9271.o
>>  obj-$(CONFIG_VIDEO_RDACM20)    += rdacm20.o
>>  obj-$(CONFIG_VIDEO_RDACM21)    += rdacm21.o
>>  obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o
>> +obj-$(CONFIG_VIDEO_ST_VGXY61)  += st-vgxy61.o
>>  obj-$(CONFIG_SDR_MAX2175) += max2175.o
>> diff --git a/drivers/media/i2c/st-vgxy61.c b/drivers/media/i2c/st-vgxy61.c
>> new file mode 100644
>> index 000000000000..d3d816189f86
>> --- /dev/null
>> +++ b/drivers/media/i2c/st-vgxy61.c
>> @@ -0,0 +1,1919 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Driver for VGXY61 global shutter sensor family driver
>> + *
>> + * Copyright (C) 2022 STMicroelectronics SA
>> + */
>> +
>> +#include <linux/clk.h>
>> +#include <linux/delay.h>
>> +#include <linux/gpio/consumer.h>
>> +#include <linux/i2c.h>
>> +#include <linux/module.h>
>> +#include <linux/regulator/consumer.h>
>> +#include <linux/units.h>
>> +#include <linux/iopoll.h>
>> +#include <media/mipi-csi2.h>
>> +#include <media/v4l2-async.h>
>> +#include <media/v4l2-ctrls.h>
>> +#include <media/v4l2-device.h>
>> +#include <media/v4l2-fwnode.h>
>> +#include <media/v4l2-subdev.h>
>> +
>> +#define US_PER_MS                                      1000
>> +
>> +#define DEVICE_MODEL_ID_REG                            0x0000
>> +#define VG5661_MODEL_ID                                        0x5661
>> +#define VG5761_MODEL_ID                                        0x5761
>> +#define VGX661_WIDTH                                   1464
>> +#define VGX661_HEIGHT                                  1104
>> +#define VGX761_WIDTH                                   1944
>> +#define VGX761_HEIGHT                                  1204
>> +#define VGX661_DEFAULT_MODE                            1
>> +#define VGX761_DEFAULT_MODE                            1
>> +#define VGX661_SHORT_ROT_TERM                          93
>> +#define VGX761_SHORT_ROT_TERM                          90
>> +#define VGXY61_EXPOS_ROT_TERM                          66
>> +#define DEVICE_REVISION                                        0x0002
>> +#define DEVICE_FWPATCH_REVISION                                0x0014
>> +#define DEVICE_FWPATCH_START_ADDR                      0x2000
>> +#define DEVICE_SYSTEM_FSM                              0x0020
>> +#define SW_STBY                                                0x03
>> +#define STREAMING                                      0x04
>> +#define DEVICE_NVM                                     0x0023
>> +#define NVM_OK                                         0x04
>> +#define DEVICE_THSENS1_TEMPERATURE                     0x0044
>> +#define DEVICE_STBY                                    0x0201
>> +#define STBY_NO_REQ                                    0
>> +#define STBY_REQ_TMP_READ                              BIT(2)
>> +#define DEVICE_STREAMING                               0x0202
>> +#define REQ_NO_REQUEST                                 0
>> +#define REQ_STOP_STREAMING                             BIT(0)
>> +#define REQ_START_STREAMING                            BIT(1)
>> +#define DEVICE_EXT_CLOCK                               0x0220
>> +#define DEVICE_CLK_PLL_PREDIV                          0x0224
>> +#define DEVICE_CLK_SYS_PLL_MULT                                0x0225
>> +#define DEVICE_GPIO_0_CTRL                             0x0236
>> +#define DEVICE_GPIO_1_CTRL                             0x0237
>> +#define DEVICE_GPIO_2_CTRL                             0x0238
>> +#define DEVICE_GPIO_3_CTRL                             0x0239
>> +#define DEVICE_SIGNALS_POLARITY_CTRL                   0x023b
>> +#define DEVICE_LINE_LENGTH                             0x0300
>> +#define DEVICE_ORIENTATION                             0x0302
>> +#define DEVICE_VT_CTRL                                 0x0304
>> +#define DEVICE_FORMAT_CTRL                             0x0305
>> +#define DEVICE_OIF_CTRL                                        0x0306
>> +#define DEVICE_OIF_ROI0_CTRL                           0x030a
>> +#define DEVICE_ROI0_START_H                            0x0400
>> +#define DEVICE_ROI0_START_V                            0x0402
>> +#define DEVICE_ROI0_END_H                              0x0404
>> +#define DEVICE_ROI0_END_V                              0x0406
>> +#define DEVICE_PATGEN_CTRL                             0x0440
>> +#define DEVICE_FRAME_CONTENT_CTRL                      0x0478
>> +#define DEVICE_COARSE_EXPOSURE_LONG                    0x0500
>> +#define DEVICE_COARSE_EXPOSURE_SHORT                   0x0504
>> +#define DEVICE_ANALOG_GAIN                             0x0508
>> +#define DEVICE_DIGITAL_GAIN_LONG                       0x050a
>> +#define DEVICE_DIGITAL_GAIN_SHORT                      0x0512
>> +#define DEVICE_FRAME_LENGTH                            0x051a
>> +#define DEVICE_SIGNALS_CTRL                            0x0522
>> +#define DEVICE_STROBE_LONG_START_DELAY                 0x0528
>> +#define DEVICE_STROBE_LONG_END_DELAY                   0x052a
>> +#define DEVICE_STROBE_SHORT_START_DELAY                        0x052c
>> +#define DEVICE_STROBE_SHORT_END_DELAY                  0x052e
>> +#define DEVICE_READOUT_CTRL                            0x0530
>> +#define DEVICE_HDR_CTRL                                        0x0532
>> +#define DEVICE_PATGEN_LONG_DATA_GR                     0x092c
>> +#define DEVICE_PATGEN_LONG_DATA_R                      0x092e
>> +#define DEVICE_PATGEN_LONG_DATA_B                      0x0930
>> +#define DEVICE_PATGEN_LONG_DATA_GB                     0x0932
>> +#define DEVICE_PATGEN_SHORT_DATA_GR                    0x0950
>> +#define DEVICE_PATGEN_SHORT_DATA_R                     0x0952
>> +#define DEVICE_PATGEN_SHORT_DATA_B                     0x0954
>> +#define DEVICE_PATGEN_SHORT_DATA_GB                    0x0956
>> +#define DEVICE_BYPASS_CTRL                             0x0a60
>> +
>> +#define V4L2_CID_HDR                           (V4L2_CID_USER_BASE | 0x1004)
>> +#define V4L2_CID_GPIOS_STROBE_LONG_START_DELAY (V4L2_CID_USER_BASE | 0x1019)
>> +#define V4L2_CID_GPIOS_STROBE_LONG_END_DELAY   (V4L2_CID_USER_BASE | 0x101a)
>> +#define V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY        (V4L2_CID_USER_BASE | 0x101b)
>> +#define V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY  (V4L2_CID_USER_BASE | 0x101c)
>> +#define V4L2_CID_TEMPERATURE                   (V4L2_CID_USER_BASE | 0x1020)
>> +
>> +#define DEVICE_FWPATCH_REVISION_MAJOR                  2
>> +#define DEVICE_FWPATCH_REVISION_MINOR                  0
>> +#define DEVICE_FWPATCH_REVISION_MICRO                  5
>> +
>> +#define WRITE_MULTIPLE_CHUNK_MAX                       16
>> +#define NB_GPIOS                                       4
>> +#define NB_POLARITIES                                  5
>> +
>> +static const u8 patch_array[] = {
>> +       0xbf, 0x00, 0x05, 0x20, 0x06, 0x01, 0xe0, 0xe0, 0x04, 0x80, 0xe6, 0x45,
>> +       0xed, 0x6f, 0xfe, 0xff, 0x14, 0x80, 0x1f, 0x84, 0x10, 0x42, 0x05, 0x7c,
>> +       0x01, 0xc4, 0x1e, 0x80, 0xb6, 0x42, 0x00, 0xe0, 0x1e, 0x82, 0x1e, 0xc0,
>> +       0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa, 0x86, 0x0d, 0x70, 0xe1,
>> +       0x04, 0x98, 0x15, 0x00, 0x28, 0xe0, 0x14, 0x02, 0x08, 0xfc, 0x15, 0x40,
>> +       0x28, 0xe0, 0x98, 0x58, 0xe0, 0xef, 0x04, 0x98, 0x0e, 0x04, 0x00, 0xf0,
>> +       0x15, 0x00, 0x28, 0xe0, 0x19, 0xc8, 0x15, 0x40, 0x28, 0xe0, 0xc6, 0x41,
>> +       0xfc, 0xe0, 0x14, 0x80, 0x1f, 0x84, 0x14, 0x02, 0xa0, 0xfc, 0x1e, 0x80,
>> +       0x14, 0x80, 0x14, 0x02, 0x80, 0xfb, 0x14, 0x02, 0xe0, 0xfc, 0x1e, 0x80,
>> +       0x14, 0xc0, 0x1f, 0x84, 0x14, 0x02, 0xa4, 0xfc, 0x1e, 0xc0, 0x14, 0xc0,
>> +       0x14, 0x02, 0x80, 0xfb, 0x14, 0x02, 0xe4, 0xfc, 0x1e, 0xc0, 0x0c, 0x0c,
>> +       0x00, 0xf2, 0x93, 0xdd, 0x86, 0x00, 0xf8, 0xe0, 0x04, 0x80, 0xc6, 0x03,
>> +       0x70, 0xe1, 0x0e, 0x84, 0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa,
>> +       0x6b, 0x80, 0x06, 0x40, 0x6c, 0xe1, 0x04, 0x80, 0x09, 0x00, 0xe0, 0xe0,
>> +       0x0b, 0xa1, 0x95, 0x84, 0x05, 0x0c, 0x1c, 0xe0, 0x86, 0x02, 0xf9, 0x60,
>> +       0xe0, 0xcf, 0x78, 0x6e, 0x80, 0xef, 0x25, 0x0c, 0x18, 0xe0, 0x05, 0x4c,
>> +       0x1c, 0xe0, 0x86, 0x02, 0xf9, 0x60, 0xe0, 0xcf, 0x0b, 0x84, 0xd8, 0x6d,
>> +       0x80, 0xef, 0x05, 0x4c, 0x18, 0xe0, 0x04, 0xd8, 0x0b, 0xa5, 0x95, 0x84,
>> +       0x05, 0x0c, 0x2c, 0xe0, 0x06, 0x02, 0x01, 0x60, 0xe0, 0xce, 0x18, 0x6d,
>> +       0x80, 0xef, 0x25, 0x0c, 0x30, 0xe0, 0x05, 0x4c, 0x2c, 0xe0, 0x06, 0x02,
>> +       0x01, 0x60, 0xe0, 0xce, 0x0b, 0x84, 0x78, 0x6c, 0x80, 0xef, 0x05, 0x4c,
>> +       0x30, 0xe0, 0x0c, 0x0c, 0x00, 0xf2, 0x93, 0xdd, 0x46, 0x01, 0x70, 0xe1,
>> +       0x08, 0x80, 0x0b, 0xa1, 0x08, 0x5c, 0x00, 0xda, 0x06, 0x01, 0x68, 0xe1,
>> +       0x04, 0x80, 0x4a, 0x40, 0x84, 0xe0, 0x08, 0x5c, 0x00, 0x9a, 0x06, 0x01,
>> +       0xe0, 0xe0, 0x04, 0x80, 0x15, 0x00, 0x60, 0xe0, 0x19, 0xc4, 0x15, 0x40,
>> +       0x60, 0xe0, 0x15, 0x00, 0x78, 0xe0, 0x19, 0xc4, 0x15, 0x40, 0x78, 0xe0,
>> +       0x93, 0xdd, 0xc3, 0xc1, 0x46, 0x01, 0x70, 0xe1, 0x08, 0x80, 0x0b, 0xa1,
>> +       0x08, 0x5c, 0x00, 0xda, 0x06, 0x01, 0x68, 0xe1, 0x04, 0x80, 0x4a, 0x40,
>> +       0x84, 0xe0, 0x08, 0x5c, 0x00, 0x9a, 0x06, 0x01, 0xe0, 0xe0, 0x14, 0x80,
>> +       0x25, 0x02, 0x54, 0xe0, 0x29, 0xc4, 0x25, 0x42, 0x54, 0xe0, 0x24, 0x80,
>> +       0x35, 0x04, 0x6c, 0xe0, 0x39, 0xc4, 0x35, 0x44, 0x6c, 0xe0, 0x25, 0x02,
>> +       0x64, 0xe0, 0x29, 0xc4, 0x25, 0x42, 0x64, 0xe0, 0x04, 0x80, 0x15, 0x00,
>> +       0x7c, 0xe0, 0x19, 0xc4, 0x15, 0x40, 0x7c, 0xe0, 0x93, 0xdd, 0xc3, 0xc1,
>> +       0x4c, 0x04, 0x7c, 0xfa, 0x86, 0x40, 0x98, 0xe0, 0x14, 0x80, 0x1b, 0xa1,
>> +       0x06, 0x00, 0x00, 0xc0, 0x08, 0x42, 0x38, 0xdc, 0x08, 0x64, 0xa0, 0xef,
>> +       0x86, 0x42, 0x3c, 0xe0, 0x68, 0x49, 0x80, 0xef, 0x6b, 0x80, 0x78, 0x53,
>> +       0xc8, 0xef, 0xc6, 0x54, 0x6c, 0xe1, 0x7b, 0x80, 0xb5, 0x14, 0x0c, 0xf8,
>> +       0x05, 0x14, 0x14, 0xf8, 0x1a, 0xac, 0x8a, 0x80, 0x0b, 0x90, 0x38, 0x55,
>> +       0x80, 0xef, 0x1a, 0xae, 0x17, 0xc2, 0x03, 0x82, 0x88, 0x65, 0x80, 0xef,
>> +       0x1b, 0x80, 0x0b, 0x8e, 0x68, 0x65, 0x80, 0xef, 0x9b, 0x80, 0x0b, 0x8c,
>> +       0x08, 0x65, 0x80, 0xef, 0x6b, 0x80, 0x0b, 0x92, 0x1b, 0x8c, 0x98, 0x64,
>> +       0x80, 0xef, 0x1a, 0xec, 0x9b, 0x80, 0x0b, 0x90, 0x95, 0x54, 0x10, 0xe0,
>> +       0xa8, 0x53, 0x80, 0xef, 0x1a, 0xee, 0x17, 0xc2, 0x03, 0x82, 0xf8, 0x63,
>> +       0x80, 0xef, 0x1b, 0x80, 0x0b, 0x8e, 0xd8, 0x63, 0x80, 0xef, 0x1b, 0x8c,
>> +       0x68, 0x63, 0x80, 0xef, 0x6b, 0x80, 0x0b, 0x92, 0x65, 0x54, 0x14, 0xe0,
>> +       0x08, 0x65, 0x84, 0xef, 0x68, 0x63, 0x80, 0xef, 0x7b, 0x80, 0x0b, 0x8c,
>> +       0xa8, 0x64, 0x84, 0xef, 0x08, 0x63, 0x80, 0xef, 0x14, 0xe8, 0x46, 0x44,
>> +       0x94, 0xe1, 0x24, 0x88, 0x4a, 0x4e, 0x04, 0xe0, 0x14, 0xea, 0x1a, 0x04,
>> +       0x08, 0xe0, 0x0a, 0x40, 0x84, 0xed, 0x0c, 0x04, 0x00, 0xe2, 0x4a, 0x40,
>> +       0x04, 0xe0, 0x19, 0x16, 0xc0, 0xe0, 0x0a, 0x40, 0x84, 0xed, 0x21, 0x54,
>> +       0x60, 0xe0, 0x0c, 0x04, 0x00, 0xe2, 0x1b, 0xa5, 0x0e, 0xea, 0x01, 0x89,
>> +       0x21, 0x54, 0x64, 0xe0, 0x7e, 0xe8, 0x65, 0x82, 0x1b, 0xa7, 0x26, 0x00,
>> +       0x00, 0x80, 0xa5, 0x82, 0x1b, 0xa9, 0x65, 0x82, 0x1b, 0xa3, 0x01, 0x85,
>> +       0x16, 0x00, 0x00, 0xc0, 0x01, 0x54, 0x04, 0xf8, 0x06, 0xaa, 0x01, 0x83,
>> +       0x06, 0xa8, 0x65, 0x81, 0x06, 0xa8, 0x01, 0x54, 0x04, 0xf8, 0x01, 0x83,
>> +       0x06, 0xaa, 0x09, 0x14, 0x18, 0xf8, 0x0b, 0xa1, 0x05, 0x84, 0xc6, 0x42,
>> +       0xd4, 0xe0, 0x14, 0x84, 0x01, 0x83, 0x01, 0x54, 0x60, 0xe0, 0x01, 0x54,
>> +       0x64, 0xe0, 0x0b, 0x02, 0x90, 0xe0, 0x10, 0x02, 0x90, 0xe5, 0x01, 0x54,
>> +       0x88, 0xe0, 0xb5, 0x81, 0xc6, 0x40, 0xd4, 0xe0, 0x14, 0x80, 0x0b, 0x02,
>> +       0xe0, 0xe4, 0x10, 0x02, 0x31, 0x66, 0x02, 0xc0, 0x01, 0x54, 0x88, 0xe0,
>> +       0x1a, 0x84, 0x29, 0x14, 0x10, 0xe0, 0x1c, 0xaa, 0x2b, 0xa1, 0xf5, 0x82,
>> +       0x25, 0x14, 0x10, 0xf8, 0x2b, 0x04, 0xa8, 0xe0, 0x20, 0x44, 0x0d, 0x70,
>> +       0x03, 0xc0, 0x2b, 0xa1, 0x04, 0x00, 0x80, 0x9a, 0x02, 0x40, 0x84, 0x90,
>> +       0x03, 0x54, 0x04, 0x80, 0x4c, 0x0c, 0x7c, 0xf2, 0x93, 0xdd, 0x00, 0x00,
>> +       0x02, 0xa9, 0x00, 0x00, 0x64, 0x4a, 0x40, 0x00, 0x08, 0x2d, 0x58, 0xe0,
>> +       0xa8, 0x98, 0x40, 0x00, 0x28, 0x07, 0x34, 0xe0, 0x05, 0xb9, 0x00, 0x00,
>> +       0x28, 0x00, 0x41, 0x05, 0x88, 0x00, 0x41, 0x3c, 0x98, 0x00, 0x41, 0x52,
>> +       0x04, 0x01, 0x41, 0x79, 0x3c, 0x01, 0x41, 0x6a, 0x3d, 0xfe, 0x00, 0x00,
>> +};
>> +
>> +static const char * const vgxy61_test_pattern_menu[] = {
>> +       "Disabled",
>> +       "Solid",
>> +       "Colorbar",
>> +       "Gradbar",
>> +       "Hgrey",
>> +       "Vgrey",
>> +       "Dgrey",
>> +       "PN28",
>> +};
>> +
>> +static const char * const vgxy61_hdr_modes[] = {
>> +       "HDR linearize",
>> +       "HDR substraction",
>> +       "no HDR",
>> +};
>> +
>> +/* Regulator supplies */
>> +static const char * const vgxy61_supply_name[] = {
>> +       "VCORE",
>> +       "VDDIO",
>> +       "VANA",
>> +};
>> +
>> +static const s64 link_freq[] = {
>> +       /*
>> +        * MIPI output freq is 804Mhz / 2, as it uses both rising edge and falling edges to send
>> +        * data
>> +        */
>> +       402000000ULL
>> +};
>> +
>> +#define VGXY61_NUM_SUPPLIES            ARRAY_SIZE(vgxy61_supply_name)
>> +
>> +/* Macro to convert index to 8.8 fixed point gain */
>> +#define I2FP(i)                                ((u32)(8192.0 / (32 - (i))))
>> +/* Array of possibles analog gains in 8.8 fixed point */
>> +static const u16 analog_gains[] = {
>> +       I2FP(0), I2FP(1), I2FP(2), I2FP(3), I2FP(4), I2FP(5), I2FP(6), I2FP(7), I2FP(8), I2FP(9),
>> +       I2FP(10), I2FP(11), I2FP(12), I2FP(13), I2FP(14), I2FP(15), I2FP(16),
>> +};
>> +
>> +enum bin_mode {
>> +       BIN_MODE_NORMAL,
>> +       BIN_MODE_DIGITAL_X2,
>> +       BIN_MODE_DIGITAL_X4,
>> +       BIN_MODE_ANALOG_SUB_X2,
>> +       BIN_MODE_ANALOG_SUB_X4,
>> +};
>> +
>> +enum hdr {
>> +       HDR_LINEAR,
>> +       HDR_SUB,
>> +       NO_HDR,
>> +};
>> +
>> +enum strobe_modes {
>> +       STROBE_DISABLED,
>> +       STROBE_LONG,
>> +       STROBE_ENABLED,
>> +};
>> +
>> +struct vgxy61_mode_info {
>> +       u32 width;
>> +       u32 height;
>> +       enum bin_mode bin_mode;
>> +};
>> +
>> +static const u32 vgxy61_supported_codes[] = {
>> +       MEDIA_BUS_FMT_SGBRG8_1X8,
>> +       MEDIA_BUS_FMT_SGBRG10_1X10,
>> +       MEDIA_BUS_FMT_SGBRG12_1X12,
>> +       MEDIA_BUS_FMT_SGBRG14_1X14,
>> +       MEDIA_BUS_FMT_SGBRG16_1X16
>> +};
>> +
>> +const int vgx761_sensor_frame_rates[] = {75, 60, 30, 15, 10, 5, 2};
>> +const int vgx661_sensor_frame_rates[] = {60, 30, 15, 10, 5, 2};
>> +
>> +static const struct vgxy61_mode_info vgx661_mode_data[] = {
>> +       {1464, 1104, BIN_MODE_NORMAL},
>> +       {1280,  720, BIN_MODE_NORMAL},
>> +       { 640,  480, BIN_MODE_DIGITAL_X2},
>> +       { 320,  240, BIN_MODE_DIGITAL_X4},
>> +};
>> +
>> +static const struct vgxy61_mode_info vgx761_mode_data[] = {
>> +       {1944, 1204, BIN_MODE_NORMAL},
>> +       {1920, 1080, BIN_MODE_NORMAL},
>> +       {1280,  720, BIN_MODE_NORMAL},
>> +       { 640,  480, BIN_MODE_DIGITAL_X2},
>> +       { 320,  240, BIN_MODE_DIGITAL_X4},
>> +};
>> +
>> +struct gpios_ctrls {
>> +       struct v4l2_ctrl *long_start;
>> +       struct v4l2_ctrl *long_end;
>> +       struct v4l2_ctrl *short_start;
>> +       struct v4l2_ctrl *short_end;
>> +};
>> +
>> +struct vgxy61_ctrls {
>> +       struct v4l2_ctrl_handler handler;
>> +       struct v4l2_ctrl *exposure;
>> +       struct v4l2_ctrl *analog_gain;
>> +       struct v4l2_ctrl *digital_gain;
>> +       struct gpios_ctrls gpios;
>> +       struct v4l2_ctrl *vflip;
>> +       struct v4l2_ctrl *hflip;
>> +       struct v4l2_ctrl *patgen;
>> +       struct v4l2_ctrl *hdr;
>> +       struct v4l2_ctrl *pixel_rate;
>> +       struct v4l2_ctrl *link_freq;
>> +       struct v4l2_ctrl *temp;
>> +};
>> +
>> +struct vgxy61_dev {
>> +       struct i2c_client *i2c_client;
>> +       struct v4l2_subdev sd;
>> +       struct media_pad pad;
>> +       struct regulator_bulk_data supplies[VGXY61_NUM_SUPPLIES];
>> +       struct gpio_desc *reset_gpio;
>> +       struct clk *xclk;
>> +       u32 clk_freq;
>> +       int sensor_width;
>> +       int sensor_height;
>> +       u16 oif_ctrl;
>> +       int nb_of_lane;
>> +       int data_rate_in_mbps;
>> +       int pclk;
>> +       u16 line_length;
>> +       int rot_term;
>> +       bool gpios_polarity;
>> +       bool slave_mode;
>> +       /* Lock to protect all members below */
>> +       struct mutex lock;
>> +       struct vgxy61_ctrls ctrls;
>> +       bool streaming;
>> +       struct v4l2_mbus_framefmt fmt;
>> +       const struct vgxy61_mode_info *sensor_modes;
>> +       int sensor_modes_nb;
>> +       const struct vgxy61_mode_info *current_mode;
>> +       const int *sensor_rates;
>> +       int sensor_rates_nb;
>> +       struct v4l2_fract frame_interval;
>> +       bool hflip;
>> +       bool vflip;
>> +       enum hdr hdr;
>> +       int expo_long;
>> +       int expo_short;
>> +};
>> +
>> +static u8 get_bpp_by_code(__u32 code)
>> +{
>> +       switch (code) {
>> +       case MEDIA_BUS_FMT_SGBRG8_1X8:
>> +               return 8;
>> +       case MEDIA_BUS_FMT_SGBRG10_1X10:
>> +               return 10;
>> +       case MEDIA_BUS_FMT_SGBRG12_1X12:
>> +               return 12;
>> +       case MEDIA_BUS_FMT_SGBRG14_1X14:
>> +               return 14;
>> +       case MEDIA_BUS_FMT_SGBRG16_1X16:
>> +               return 16;
>> +       default:
>> +               /* Should never happen */
>> +               WARN(1, "Unsupported code %d. default to 8 bpp", code);
>> +               return 8;
>> +       }
>> +}
>> +
>> +static u8 get_data_type_by_code(__u32 code)
>> +{
>> +       switch (code) {
>> +       case MEDIA_BUS_FMT_SGBRG8_1X8:
>> +               return MIPI_CSI2_DT_RAW8;
>> +       case MEDIA_BUS_FMT_SGBRG10_1X10:
>> +               return MIPI_CSI2_DT_RAW10;
>> +       case MEDIA_BUS_FMT_SGBRG12_1X12:
>> +               return MIPI_CSI2_DT_RAW12;
>> +       case MEDIA_BUS_FMT_SGBRG14_1X14:
>> +               return MIPI_CSI2_DT_RAW14;
>> +       case MEDIA_BUS_FMT_SGBRG16_1X16:
>> +               return MIPI_CSI2_DT_RAW16;
>> +       default:
>> +               /* Should never happen */
>> +               WARN(1, "Unsupported code %d. default to MIPI_CSI2_DT_RAW8 data type", code);
>> +               return MIPI_CSI2_DT_RAW8;
>> +       }
>> +}
>> +
>> +static void compute_pll_parameters_by_freq(u32 freq, unsigned int *prediv, unsigned int *mult)
>> +{
>> +       const unsigned int predivs[] = {1, 2, 4};
>> +       int i;
>> +
>> +       /*
>> +        * Freq range is [6Mhz-27Mhz] already checked.
>> +        * Output of divider should be in [6Mhz-12Mhz[.
>> +        */
>> +       for (i = 0; i < ARRAY_SIZE(predivs); i++) {
>> +               *prediv = predivs[i];
>> +               if (freq / *prediv < 12 * HZ_PER_MHZ)
>> +                       break;
>> +       }
>> +       WARN_ON(i == ARRAY_SIZE(predivs));
>> +
>> +       /*
>> +        * Target freq is 804Mhz. Don't change this as it will impact image quality.
>> +        */
>> +       *mult = ((804 * HZ_PER_MHZ) * (*prediv) + freq / 2) / freq;
>> +}
>> +
>> +static s32 get_pixel_rate(struct vgxy61_dev *sensor)
>> +{
>> +       return div64_u64((u64)sensor->data_rate_in_mbps * sensor->nb_of_lane,
>> +                        get_bpp_by_code(sensor->fmt.code));
>> +}
>> +
>> +static inline struct vgxy61_dev *to_vgxy61_dev(struct v4l2_subdev *sd)
>> +{
>> +       return container_of(sd, struct vgxy61_dev, sd);
>> +}
>> +
>> +static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl)
>> +{
>> +       return &container_of(ctrl->handler, struct vgxy61_dev, ctrls.handler)->sd;
>> +}
>> +
>> +static int get_chunk_size(struct vgxy61_dev *sensor)
>> +{
>> +       struct i2c_adapter *adapter = sensor->i2c_client->adapter;
>> +       int max_write_len = WRITE_MULTIPLE_CHUNK_MAX;
>> +
>> +       if (adapter->quirks && adapter->quirks->max_write_len)
>> +               max_write_len = adapter->quirks->max_write_len - 2;
>> +
>> +       max_write_len = min(max_write_len, WRITE_MULTIPLE_CHUNK_MAX);
>> +
>> +       return max(max_write_len, 1);
>> +}
>> +
>> +static int vgxy61_read_multiple(struct vgxy61_dev *sensor, u16 reg, u8 *val, int len)
>> +{
>> +       struct i2c_client *client = sensor->i2c_client;
>> +       struct i2c_msg msg[2];
>> +       u8 buf[2];
>> +       int ret;
>> +
>> +       buf[0] = reg >> 8;
>> +       buf[1] = reg & 0xff;
>> +
>> +       msg[0].addr = client->addr;
>> +       msg[0].flags = client->flags;
>> +       msg[0].buf = buf;
>> +       msg[0].len = sizeof(buf);
>> +
>> +       msg[1].addr = client->addr;
>> +       msg[1].flags = client->flags | I2C_M_RD;
>> +       msg[1].buf = val;
>> +       msg[1].len = len;
>> +
>> +       ret = i2c_transfer(client->adapter, msg, 2);
>> +       if (ret < 0) {
>> +               dev_dbg(&client->dev, "%s: %x i2c_transfer, reg: %x => %d\n", __func__,
>> +                       client->addr, reg, ret);
>> +               return ret;
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>> +static inline int vgxy61_read_reg(struct vgxy61_dev *sensor, u16 reg, u8 *val)
>> +{
>> +       return vgxy61_read_multiple(sensor, reg, val, sizeof(*val));
>> +}
>> +
>> +static inline int vgxy61_read_reg16(struct vgxy61_dev *sensor, u16 reg, u16 *val)
>> +{
>> +       return vgxy61_read_multiple(sensor, reg, (u8 *)val, sizeof(*val));
>> +}
>> +
>> +static int vgxy61_write_multiple(struct vgxy61_dev *sensor, u16 reg, const u8 *data, int len)
>> +{
>> +       struct i2c_client *client = sensor->i2c_client;
>> +       struct i2c_msg msg;
>> +       u8 buf[WRITE_MULTIPLE_CHUNK_MAX + 2];
>> +       int i;
>> +       int ret;
>> +
>> +       if (len > WRITE_MULTIPLE_CHUNK_MAX)
>> +               return -EINVAL;
>> +       buf[0] = reg >> 8;
>> +       buf[1] = reg & 0xff;
>> +       for (i = 0; i < len; i++)
>> +               buf[i + 2] = data[i];
>> +
>> +       msg.addr = client->addr;
>> +       msg.flags = client->flags;
>> +       msg.buf = buf;
>> +       msg.len = len + 2;
>> +
>> +       ret = i2c_transfer(client->adapter, &msg, 1);
>> +       if (ret < 0) {
>> +               dev_dbg(&client->dev, "%s: i2c_transfer, reg: %x => %d\n", __func__, reg, ret);
>> +               return ret;
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>> +static int vgxy61_write_array(struct vgxy61_dev *sensor, u16 reg, int nb, const u8 *array)
>> +{
>> +       const int chunk_size = get_chunk_size(sensor);
>> +       int ret;
>> +       int sz;
>> +
>> +       while (nb) {
>> +               sz = min(nb, chunk_size);
>> +               ret = vgxy61_write_multiple(sensor, reg, array, sz);
>> +               if (ret < 0)
>> +                       return ret;
>> +               nb -= sz;
>> +               reg += sz;
>> +               array += sz;
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>> +static inline int vgxy61_write_reg(struct vgxy61_dev *sensor, u16 reg, u8 val)
>> +{
>> +       return vgxy61_write_multiple(sensor, reg, &val, sizeof(val));
>> +}
>> +
>> +static inline int vgxy61_write_reg16(struct vgxy61_dev *sensor, u16 reg, u16 val)
>> +{
>> +       return vgxy61_write_multiple(sensor, reg, (u8 *)&val, sizeof(val));
>> +}
>> +
>> +static inline int vgxy61_write_reg32(struct vgxy61_dev *sensor, u16 reg, u32 val)
>> +{
>> +       return vgxy61_write_multiple(sensor, reg, (u8 *)&val, sizeof(val));
>> +}
>> +
>> +static int vgxy61_poll_reg(struct vgxy61_dev *sensor, u16 reg, u8 poll_val)
>> +{
>> +       const int loop_delay_ms = 10;
>> +       const int timeout_ms = 500;
>> +       u8 val;
>> +       int ret, timeout;
>> +
>> +       timeout = read_poll_timeout(vgxy61_read_reg, ret, ((ret != 0) || (val == poll_val)),
>> +                                   loop_delay_ms * US_PER_MS, timeout_ms * US_PER_MS, false,
>> +                                   sensor, reg, &val);
>> +       if (timeout)
>> +               return timeout;
>> +
>> +       return ret;
>> +}
>> +
>> +static int vgxy61_wait_state(struct vgxy61_dev *sensor, int state)
>> +{
>> +       return vgxy61_poll_reg(sensor, DEVICE_SYSTEM_FSM, state);
>> +}
>> +
>> +static int vgxy61_check_bw(struct vgxy61_dev *sensor)
>> +{
>> +       struct i2c_client *client = sensor->i2c_client;
>> +       /* Correction factor for time required between 2 lines */
>> +       const int mipi_margin = 1056;
>> +       int binning_scale = 1 << sensor->current_mode->bin_mode;
>> +       int bpp = get_bpp_by_code(sensor->fmt.code);
>> +       int max_bit_per_line;
>> +       int bit_per_line;
>> +       u64 line_rate;
>> +
>> +       line_rate = sensor->nb_of_lane * (u64)sensor->data_rate_in_mbps * sensor->line_length;
>> +       max_bit_per_line = div64_u64(line_rate, sensor->pclk) - mipi_margin;
>> +       bit_per_line = (bpp * sensor->current_mode->width) / binning_scale;
>> +
>> +       dev_dbg(&client->dev, "max_bit_per_line = %d\n", max_bit_per_line);
>> +       dev_dbg(&client->dev, "required bit_per_line = %d\n", bit_per_line);
>> +
>> +       return bit_per_line > max_bit_per_line ? -EINVAL : 0;
>> +}
>> +
>> +static int apply_exposure(struct vgxy61_dev *sensor)
>> +{
>> +       struct i2c_client *client = sensor->i2c_client;
>> +       int ret;
>> +
>> +        /* We first set expo to zero to avoid forbidden parameters couple */
>> +       ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_SHORT, 0);
>> +       if (ret)
>> +               return ret;
>> +       ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_LONG, sensor->expo_long);
>> +       if (ret)
>> +               return ret;
>> +       ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_SHORT, sensor->expo_short);
>> +       if (ret)
>> +               return ret;
>> +
>> +       dev_dbg(&client->dev, "%s applied expo %d (short: %d)\n", __func__,
>> +               sensor->expo_long, sensor->expo_short);
>> +
>> +       return 0;
>> +}
>> +
>> +static int set_frame_rate(struct vgxy61_dev *sensor)
>> +{
>> +       u16 frame_length;
>> +
>> +       frame_length = sensor->pclk / (sensor->line_length * sensor->frame_interval.denominator);
>> +
>> +       return vgxy61_write_reg16(sensor, DEVICE_FRAME_LENGTH, frame_length);
>> +}
>> +
>> +static int vgxy61_get_regulators(struct vgxy61_dev *sensor)
>> +{
>> +       int i;
>> +
>> +       for (i = 0; i < VGXY61_NUM_SUPPLIES; i++)
>> +               sensor->supplies[i].supply = vgxy61_supply_name[i];
>> +
>> +       return devm_regulator_bulk_get(&sensor->i2c_client->dev, VGXY61_NUM_SUPPLIES,
>> +                                      sensor->supplies);
>> +}
>> +
>> +static int vgxy61_apply_reset(struct vgxy61_dev *sensor)
>> +{
>> +       struct i2c_client *client = sensor->i2c_client;
>> +
>> +       dev_dbg(&client->dev, "%s applied reset\n", __func__);
>> +       gpiod_set_value_cansleep(sensor->reset_gpio, 0);
>> +       usleep_range(5000, 10000);
>> +       gpiod_set_value_cansleep(sensor->reset_gpio, 1);
>> +       usleep_range(5000, 10000);
>> +       gpiod_set_value_cansleep(sensor->reset_gpio, 0);
>> +       usleep_range(40000, 100000);
>> +       return vgxy61_wait_state(sensor, SW_STBY);
>> +}
>> +
>> +static int vgxy61_try_fmt_internal(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *fmt,
>> +                                  const struct vgxy61_mode_info **new_mode)
>> +{
>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>> +       const struct vgxy61_mode_info *mode = sensor->sensor_modes;
>> +       unsigned int index;
>> +
>> +       /* Select code */
>> +       for (index = 0; index < ARRAY_SIZE(vgxy61_supported_codes); index++) {
>> +               if (vgxy61_supported_codes[index] == fmt->code)
>> +                       break;
>> +       }
>> +       if (index == ARRAY_SIZE(vgxy61_supported_codes))
>> +               index = 0;
>> +
>> +       /* Select size */
>> +       do {
>> +               if (mode->width <= fmt->width && mode->height <= fmt->height)
>> +                       break;
>> +       } while ((++mode)->width);
>> +       if (!mode->width)
>> +               mode--;
>> +
>> +       *new_mode = mode;
>> +       fmt->code = vgxy61_supported_codes[index];
>> +       fmt->width = mode->width;
>> +       fmt->height = mode->height;
>> +       fmt->colorspace = V4L2_COLORSPACE_SRGB;
>> +       fmt->field = V4L2_FIELD_NONE;
>> +
>> +       return 0;
>> +}
>> +
>> +static int vgxy61_stream_enable(struct vgxy61_dev *sensor)
>> +{
>> +       int center_x = sensor->sensor_width / 2;
>> +       int center_y = sensor->sensor_height / 2;
>> +       int scale = 1 << sensor->current_mode->bin_mode;
>> +       int width = sensor->current_mode->width * scale;
>> +       int height = sensor->current_mode->height * scale;
>> +       int ret;
>> +
>> +       ret = vgxy61_check_bw(sensor);
>> +       if (ret)
>> +               return ret;
>> +
>> +       /* Configure sensor */
>> +       ret = vgxy61_write_reg(sensor, DEVICE_FORMAT_CTRL, get_bpp_by_code(sensor->fmt.code));
>> +       if (ret)
>> +               return ret;
>> +       ret = vgxy61_write_reg(sensor, DEVICE_OIF_ROI0_CTRL,
>> +                              get_data_type_by_code(sensor->fmt.code));
>> +       if (ret)
>> +               return ret;
>> +
>> +       ret = vgxy61_write_reg(sensor, DEVICE_READOUT_CTRL, sensor->current_mode->bin_mode);
>> +       if (ret)
>> +               return ret;
>> +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_H, center_x - width / 2);
>> +       if (ret)
>> +               return ret;
>> +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_H, center_x + width / 2 - 1);
>> +       if (ret)
>> +               return ret;
>> +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_V, center_y - height / 2);
>> +       if (ret)
>> +               return ret;
>> +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_V, center_y + height / 2 - 1);
>> +       if (ret)
>> +               return ret;
>> +
>> +       ret = set_frame_rate(sensor);
>> +       if (ret)
>> +               return ret;
>> +
>> +       ret = apply_exposure(sensor);
>> +       if (ret)
>> +               return ret;
>> +
>> +       /* Start streaming */
>> +       ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_START_STREAMING);
>> +       if (ret)
>> +               return ret;
>> +
>> +       ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST);
>> +       if (ret)
>> +               return ret;
>> +
>> +       ret = vgxy61_wait_state(sensor, STREAMING);
>> +       if (ret)
>> +               return ret;
>> +
>> +       return 0;
>> +}
>> +
>> +static int vgxy61_stream_disable(struct vgxy61_dev *sensor)
>> +{
>> +       int ret;
>> +
>> +       ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_STOP_STREAMING);
>> +       if (ret)
>> +               return ret;
>> +
>> +       ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST);
>> +       if (ret)
>> +               return ret;
>> +
>> +       ret = vgxy61_wait_state(sensor, SW_STBY);
>> +       if (ret)
>> +               return ret;
>> +
>> +       return 0;
>> +}
>> +
>> +static int vgxy61_s_stream(struct v4l2_subdev *sd, int enable)
>> +{
>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>> +       struct i2c_client *client = sensor->i2c_client;
>> +       int ret = 0;
>> +
>> +       mutex_lock(&sensor->lock);
>> +       dev_dbg(&client->dev, "%s : requested %d / current = %d\n", __func__, enable,
>> +               sensor->streaming);
>> +       if (sensor->streaming == enable)
>> +               goto out;
>> +
>> +       ret = enable ? vgxy61_stream_enable(sensor) : vgxy61_stream_disable(sensor);
>> +       if (!ret)
>> +               sensor->streaming = enable;
>> +
>> +out:
>> +       dev_dbg(&client->dev, "%s current now = %d / %d\n", __func__, sensor->streaming, ret);
>> +       mutex_unlock(&sensor->lock);
>> +
>> +       return ret;
>> +}
>> +
>> +static int vgxy61_g_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *fi)
>> +{
>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>> +
>> +       mutex_lock(&sensor->lock);
>> +       fi->interval = sensor->frame_interval;
>> +       mutex_unlock(&sensor->lock);
>> +
>> +       return 0;
>> +}
>> +
>> +static int vgxy61_s_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *fi)
>> +{
>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>> +       struct i2c_client *client = sensor->i2c_client;
>> +       u64 req_int, err, min_err = ~0ULL;
>> +       u64 test_int;
>> +       int i = 0;
>> +       int ret;
>> +
>> +       if (fi->interval.denominator == 0)
>> +               return -EINVAL;
>> +
>> +       mutex_lock(&sensor->lock);
>> +
>> +       if (sensor->streaming) {
>> +               ret = -EBUSY;
>> +               goto out;
>> +       }
>> +
>> +       dev_dbg(&client->dev, "%s request %d/%d\n", __func__,
>> +               fi->interval.numerator, fi->interval.denominator);
>> +       /* Find nearest period */
>> +       req_int = div64_u64((u64)(fi->interval.numerator * 10000), fi->interval.denominator);
>> +       for (i = 0; i < sensor->sensor_rates_nb; i++) {
>> +               test_int = div64_u64((u64)10000, sensor->sensor_rates[i]);
>> +               err = abs(test_int - req_int);
>> +               if (err < min_err) {
>> +                       fi->interval.numerator = 1;
>> +                       fi->interval.denominator = sensor->sensor_rates[i];
>> +                       min_err = err;
>> +               }
>> +       }
>> +       sensor->frame_interval = fi->interval;
>> +       dev_dbg(&client->dev, "%s set     %d/%d\n", __func__,
>> +               fi->interval.numerator, fi->interval.denominator);
>> +
>> +       ret = 0;
>> +out:
>> +       mutex_unlock(&sensor->lock);
>> +
>> +       return ret;
>> +}
>> +
>> +static int vgxy61_enum_mbus_code(struct v4l2_subdev *sd,
>> +                                struct v4l2_subdev_state *sd_state,
>> +                                struct v4l2_subdev_mbus_code_enum *code)
>> +{
>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>> +       struct i2c_client *client = sensor->i2c_client;
>> +
>> +       dev_dbg(&client->dev, "%s probe index %d\n", __func__, code->index);
>> +       if (code->index >= ARRAY_SIZE(vgxy61_supported_codes))
>> +               return -EINVAL;
>> +
>> +       code->code = vgxy61_supported_codes[code->index];
>> +
>> +       return 0;
>> +}
>> +
>> +static int vgxy61_get_fmt(struct v4l2_subdev *sd,
>> +                         struct v4l2_subdev_state *sd_state,
>> +                         struct v4l2_subdev_format *format)
>> +{
>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>> +       struct i2c_client *client = sensor->i2c_client;
>> +       struct v4l2_mbus_framefmt *fmt;
>> +
>> +       dev_dbg(&client->dev, "%s probe %d\n", __func__, format->pad);
>> +       dev_dbg(&client->dev, "%s %dx%d\n", __func__, format->format.width, format->format.height);
>> +
>> +       mutex_lock(&sensor->lock);
>> +
>> +       if (format->which == V4L2_SUBDEV_FORMAT_TRY)
>> +               fmt = v4l2_subdev_get_try_format(&sensor->sd, sd_state, format->pad);
>> +       else
>> +               fmt = &sensor->fmt;
>> +
>> +       format->format = *fmt;
>> +
>> +       mutex_unlock(&sensor->lock);
>> +
>> +       return 0;
>> +}
>> +
>> +static int vgxy61_set_fmt(struct v4l2_subdev *sd,
>> +                         struct v4l2_subdev_state *sd_state,
>> +                         struct v4l2_subdev_format *format)
>> +{
>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>> +       struct i2c_client *client = sensor->i2c_client;
>> +       const struct vgxy61_mode_info *new_mode;
>> +       struct v4l2_mbus_framefmt *fmt;
>> +       int ret;
>> +
>> +       dev_dbg(&client->dev, "%s probe %d\n", __func__, format->pad);
>> +       dev_dbg(&client->dev, "%s %dx%d\n", __func__, format->format.width, format->format.height);
>> +
>> +       mutex_lock(&sensor->lock);
>> +
>> +       if (sensor->streaming) {
>> +               ret = -EBUSY;
>> +               goto out;
>> +       }
>> +
>> +       /* Find best format */
>> +       ret = vgxy61_try_fmt_internal(sd, &format->format, &new_mode);
>> +       if (ret)
>> +               goto out;
>> +
>> +       if (format->which == V4L2_SUBDEV_FORMAT_TRY)
>> +               fmt = v4l2_subdev_get_try_format(sd, sd_state, 0);
>> +       else
>> +               fmt = &sensor->fmt;
>> +       *fmt = format->format;
>> +       sensor->current_mode = new_mode;
>> +       /* Update pixel rate control to reflect new mode */
>> +       __v4l2_ctrl_s_ctrl_int64(sensor->ctrls.pixel_rate, get_pixel_rate(sensor));
>> +
>> +out:
>> +       mutex_unlock(&sensor->lock);
>> +
>> +       return ret;
>> +}
>> +
>> +static int vgxy61_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state,
>> +                                 struct v4l2_subdev_frame_size_enum *fse)
>> +{
>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>> +       struct i2c_client *client = sensor->i2c_client;
>> +
>> +       dev_dbg(&client->dev, "%s for index %d\n", __func__, fse->index);
>> +       if (fse->index >= sensor->sensor_modes_nb)
>> +               return -EINVAL;
>> +
>> +       fse->min_width = sensor->sensor_modes[fse->index].width;
>> +       fse->max_width = fse->min_width;
>> +       fse->min_height = sensor->sensor_modes[fse->index].height;
>> +       fse->max_height = fse->min_height;
>> +
>> +       return 0;
>> +}
>> +
>> +static int vgxy61_enum_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state,
>> +                                     struct v4l2_subdev_frame_interval_enum *fie)
>> +{
>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>> +       const struct vgxy61_mode_info *mode = sensor->sensor_modes;
>> +
>> +       if (fie->index >= sensor->sensor_rates_nb)
>> +               return -EINVAL;
>> +       do {
>> +               if (mode->width == fie->width && mode->height == fie->height)
>> +                       break;
>> +       } while ((++mode)->width);
>> +       if (!mode->width)
>> +               return -EINVAL;
>> +
>> +       fie->interval.numerator = 1;
>> +       fie->interval.denominator = sensor->sensor_rates[fie->index];
>> +
>> +       return 0;
>> +}
>> +
>> +static int vgxy61_update_analog_gain(struct vgxy61_dev *sensor, u32 target)
>> +{
>> +       struct i2c_client *client = sensor->i2c_client;
>> +       unsigned int idx;
>> +       int ret;
>> +
>> +       /* Find smallest analog gains which is above or equal to target gain */
>> +       for (idx = 0; idx < ARRAY_SIZE(analog_gains); idx++) {
>> +               if (analog_gains[idx] >= target)
>> +                       break;
>> +       }
>> +       /* Cap to maximum gain if no matching gain found */
>> +       if (idx == ARRAY_SIZE(analog_gains))
>> +               idx--;
>> +
>> +       /* Apply gain */
>> +       ret = vgxy61_write_reg(sensor, DEVICE_ANALOG_GAIN, idx);
>> +       if (ret)
>> +               return ret;
>> +
>> +       dev_dbg(&client->dev, "Target analog gain: 0x%04x\n", target);
>> +       dev_dbg(&client->dev, "   Set analog gain: 0x%04x\n", analog_gains[idx]);
>> +
>> +       return 0;
>> +}
>> +
>> +static int vgxy61_update_digital_gain(struct vgxy61_dev *sensor, u32 target)
>> +{
>> +       struct i2c_client *client = sensor->i2c_client;
>> +       int ret;
>> +
>> +       /*
>> +        * For a monochrome version, configuring DIGITAL_GAIN_LONG_CH0 and
>> +        * DIGITAL_GAIN_SHORT_CH0 is enough to configure the gain of all
>> +        * four sub pixels.
>> +        */
>> +       ret = vgxy61_write_reg16(sensor, DEVICE_DIGITAL_GAIN_LONG, target);
>> +       if (ret)
>> +               return ret;
>> +       ret = vgxy61_write_reg16(sensor, DEVICE_DIGITAL_GAIN_SHORT, target);
>> +       if (ret)
>> +               return ret;
>> +
>> +       dev_dbg(&client->dev, "  Set digital gain: 0x%04x\n", target);
>> +
>> +       return 0;
>> +}
>> +
>> +static int vgxy61_update_patgen(struct vgxy61_dev *sensor, u32 index)
>> +{
>> +       u32 pattern = index <= 3 ? index : index + 12;
>> +       u32 reg;
>> +
>> +       reg = (pattern << 18) | (pattern << 4);
>> +       if (index)
>> +               reg |= (1 << 16) | 1;
>> +       return vgxy61_write_reg32(sensor, DEVICE_PATGEN_CTRL, reg);
>> +}
>> +
>> +static int vgxy61_update_gpiox_strobe_mode(struct vgxy61_dev *sensor, enum strobe_modes mode,
>> +                                          int idx)
>> +{
>> +       const u8 index2val[] = {0x0, 0x1, 0x3};
>> +       u16 reg;
>> +       int ret;
>> +
>> +       ret = vgxy61_read_reg16(sensor, DEVICE_SIGNALS_CTRL, &reg);
>> +       if (ret)
>> +               return ret;
>> +       reg &= ~(0xf << (idx * 4));
>> +       reg |= index2val[mode] << (idx * 4);
>> +
>> +       return vgxy61_write_reg16(sensor, DEVICE_SIGNALS_CTRL, reg);
>> +}
>> +
>> +static int vgxy61_update_gpios_strobe_mode(struct vgxy61_dev *sensor, enum hdr hdr)
>> +{
>> +       enum strobe_modes strobe_mode;
>> +       int i, ret;
>> +
>> +       switch (hdr) {
>> +       case HDR_LINEAR:
>> +               strobe_mode = STROBE_ENABLED;
>> +               break;
>> +       case HDR_SUB:
>> +       case NO_HDR:
>> +               strobe_mode = STROBE_LONG;
>> +               break;
>> +       default:
>> +               /* Should never happen */
>> +               WARN_ON(true);
>> +               break;
>> +       }
>> +
>> +       for (i = 0; i < NB_GPIOS; i++) {
>> +               ret = vgxy61_update_gpiox_strobe_mode(sensor, strobe_mode, i);
>> +               if (ret)
>> +                       return ret;
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>> +static int vgxy61_update_gpios_strobe_polarity(struct vgxy61_dev *sensor, int polarity)
>> +{
>> +       struct i2c_client *client = sensor->i2c_client;
>> +       int ret;
>> +
>> +       if (sensor->streaming)
>> +               return -EBUSY;
>> +
>> +       dev_dbg(&client->dev, "setting gpios polarity: %d\n", polarity);
>> +
>> +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_0_CTRL, polarity << 1);
>> +       if (ret)
>> +               return ret;
>> +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_1_CTRL, polarity << 1);
>> +       if (ret)
>> +               return ret;
>> +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_2_CTRL, polarity << 1);
>> +       if (ret)
>> +               return ret;
>> +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_3_CTRL, polarity << 1);
>> +       if (ret)
>> +               return ret;
>> +       return vgxy61_write_reg(sensor, DEVICE_SIGNALS_POLARITY_CTRL, polarity);
>> +}
>> +
>> +static int find_max_long_exposure(struct vgxy61_dev *sensor, int frame_length, int short_expo_ratio)
>> +{
>> +       int first_rot_max_expo;
>> +       int second_rot_max_expo;
>> +
>> +       /*
>> +        * Apply first rule of thumb:
>> +        * frame_length < short_line_nb + sensor->sensor_height + sensor->rot_term
>> +        * with short_line_nb = long_line_nb / short_expo_ratio
>> +        */
>> +       first_rot_max_expo = ((frame_length - sensor->sensor_height - sensor->rot_term) *
>> +                            short_expo_ratio) - 1;
>> +
>> +       /*
>> +        * Apply second rule of thumb
>> +        * frame_length < short_line_nb + long_line_nb + VGXY61_EXPOS_ROT_TERM
>> +        * with short_line_nb = long_line_nb / short_expo_ratio
>> +        */
>> +       second_rot_max_expo = (((frame_length - VGXY61_EXPOS_ROT_TERM) * short_expo_ratio) /
>> +                             (short_expo_ratio + 1)) - 1;
>> +
>> +       /* Take the minimum of both rules */
>> +       return min(first_rot_max_expo, second_rot_max_expo);
>> +}
>> +
>> +static int vgxy61_update_exposure(struct vgxy61_dev *sensor, int long_expo_line_nb, enum hdr hdr,
>> +                                 bool clamp)
>> +{
>> +       struct i2c_client *client = sensor->i2c_client;
>> +       int max_long_expo;
>> +       int max_short_expo = 0;
>> +       int short_expo_line_nb = 0;
>> +       /* We use a constant ratio of 10 for linear HDR mode */
>> +       int hdr_linear_ratio = 10;
>> +       u16 frame_length;
>> +       int ret;
>> +
>> +       ret = vgxy61_read_reg16(sensor, DEVICE_FRAME_LENGTH, &frame_length);
>> +       if (ret)
>> +               return ret;
>> +
>> +       /* Long and short integration times must not be less than 10 lines */
>> +       long_expo_line_nb = max(10, long_expo_line_nb);
>> +       long_expo_line_nb = min_t(int, frame_length, long_expo_line_nb);
>> +
>> +       /* Compute short exposure according to hdr mode and long exposure */
>> +       switch (hdr) {
>> +       case HDR_LINEAR:
>> +               max_long_expo = find_max_long_exposure(sensor, frame_length, hdr_linear_ratio);
>> +               max_short_expo = (max_long_expo + (hdr_linear_ratio / 2)) / hdr_linear_ratio;
>> +               short_expo_line_nb = (long_expo_line_nb + (hdr_linear_ratio / 2)) /
>> +                                    hdr_linear_ratio;
>> +               break;
>> +       case HDR_SUB:
>> +               max_long_expo = find_max_long_exposure(sensor, frame_length, 1);
>> +               max_short_expo = max_long_expo;
>> +               short_expo_line_nb = long_expo_line_nb;
>> +               break;
>> +       case NO_HDR:
>> +               /*
>> +                * As short expo is 0 here, only the second rule of thumb applies, see
>> +                * find_max_long_exposure for more
>> +                */
>> +               max_long_expo = frame_length - VGXY61_EXPOS_ROT_TERM;
>> +               break;
>> +       default:
>> +               /* Should never happen */
>> +               WARN_ON(true);
>> +               break;
>> +       }
>> +
>> +       if (long_expo_line_nb > max_long_expo) {
>> +               if (!clamp) {
>> +                       dev_err(&client->dev, "Exposure %d too high (max for this hdr mode %d)\n",
>> +                               long_expo_line_nb, max_long_expo);
>> +                       return -EINVAL;
>> +               }
>> +               dev_warn(&client->dev, "Exposure %d too high for this hdr mode, clamping to %d\n",
>> +                        long_expo_line_nb, max_long_expo);
>> +               long_expo_line_nb = max_long_expo;
>> +               short_expo_line_nb = max_short_expo;
>> +       }
>> +
>> +       dev_dbg(&client->dev, "frame_length %d, long_expo_line_nb %d, short_expo_line_nb %d",
>> +               frame_length, long_expo_line_nb, short_expo_line_nb);
>> +
>> +       /* Apply exposure */
>> +       sensor->expo_long = long_expo_line_nb;
>> +       sensor->expo_short = short_expo_line_nb;
>> +
>> +       if (sensor->streaming)
>> +               return apply_exposure(sensor);
>> +       return 0;
>> +}
>> +
>> +static int vgxy61_update_hdr(struct vgxy61_dev *sensor, u32 index)
>> +{
>> +       const u8 index2val[] = {0x1, 0x4, 0xa};
>> +       int ret;
>> +
>> +       /*
>> +        * Short exposure changes according to HDR mode, do it first as it can
>> +        * violate sensors 'rule of thumbs' and therefore will require to change
>> +        * the long exposure
>> +        */
>> +       ret = vgxy61_update_exposure(sensor, sensor->expo_long, index, true);
>> +       if (ret)
>> +               return ret;
>> +
>> +       /* Update strobe mode according to HDR */
>> +       ret = vgxy61_update_gpios_strobe_mode(sensor, index);
>> +       if (ret)
>> +               return ret;
>> +
>> +       ret = vgxy61_write_reg(sensor, DEVICE_HDR_CTRL, index2val[index]);
>> +       if (ret)
>> +               return ret;
>> +       sensor->hdr = index;
>> +
>> +       return 0;
>> +}
>> +
>> +static int vgxy61_get_temp_stream_enabled(struct vgxy61_dev *sensor, int *temp)
>> +{
>> +       int ret;
>> +       u16 temperature;
>> +
>> +       ret = vgxy61_read_reg16(sensor, DEVICE_THSENS1_TEMPERATURE, &temperature);
>> +       if (ret)
>> +               return ret;
>> +
>> +       /* Temperature is expressed in Kelvin in Q10.2 fixed point format*/
>> +       temperature = (temperature & 0x0fff) >> 2;
>> +       temperature = kelvin_to_celsius(temperature);
>> +
>> +       *temp = temperature;
>> +
>> +       return 0;
>> +}
>> +
>> +static int vgxy61_get_temp_stream_disabled(struct vgxy61_dev *sensor, int *temp)
>> +{
>> +       int ret;
>> +
>> +       /* Device needs to be in standby mode if streaming is off */
>> +       ret = vgxy61_write_reg(sensor, DEVICE_STBY, STBY_REQ_TMP_READ);
>> +       if (ret)
>> +               return ret;
>> +       ret = vgxy61_poll_reg(sensor, DEVICE_STBY, STBY_NO_REQ);
>> +       if (ret)
>> +               return ret;
>> +
>> +       return vgxy61_get_temp_stream_enabled(sensor, temp);
>> +}
>> +
>> +static int vgxy61_get_temp(struct vgxy61_dev *sensor, int *temp)
>> +{
>> +       *temp = 0;
>> +       if (sensor->streaming)
>> +               return vgxy61_get_temp_stream_enabled(sensor, temp);
>> +       else
>> +               return vgxy61_get_temp_stream_disabled(sensor, temp);
>> +}
>> +
>> +static int vgxy61_s_ctrl(struct v4l2_ctrl *ctrl)
>> +{
>> +       struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>> +       int ret;
>> +
>> +       switch (ctrl->id) {
>> +       case V4L2_CID_EXPOSURE:
>> +               ret = vgxy61_update_exposure(sensor, ctrl->val, sensor->hdr, false);
>> +               ctrl->val = sensor->expo_long;
>> +               break;
>> +       case V4L2_CID_ANALOGUE_GAIN:
>> +               ret = vgxy61_update_analog_gain(sensor, ctrl->val);
>> +               break;
>> +       case V4L2_CID_DIGITAL_GAIN:
>> +               ret = vgxy61_update_digital_gain(sensor, ctrl->val);
>> +               break;
>> +       case V4L2_CID_VFLIP:
>> +       case V4L2_CID_HFLIP:
>> +               if (sensor->streaming) {
>> +                       ret = -EBUSY;
>> +                       break;
>> +               }
>> +               if (ctrl->id == V4L2_CID_VFLIP)
>> +                       sensor->vflip = ctrl->val;
>> +               if (ctrl->id == V4L2_CID_HFLIP)
>> +                       sensor->hflip = ctrl->val;
>> +               ret = vgxy61_write_reg(sensor, DEVICE_ORIENTATION,
>> +                                      sensor->hflip | (sensor->vflip << 1));
>> +               break;
>> +       case V4L2_CID_TEST_PATTERN:
>> +               ret = vgxy61_update_patgen(sensor, ctrl->val);
>> +               break;
>> +       case V4L2_CID_HDR:
>> +               ret = vgxy61_update_hdr(sensor, ctrl->val);
>> +               break;
>> +       case V4L2_CID_GPIOS_STROBE_LONG_START_DELAY:
>> +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_LONG_START_DELAY, ctrl->val);
>> +               break;
>> +       case V4L2_CID_GPIOS_STROBE_LONG_END_DELAY:
>> +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_LONG_END_DELAY, ctrl->val);
>> +               break;
>> +       case V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY:
>> +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_SHORT_START_DELAY, ctrl->val);
>> +               break;
>> +       case V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY:
>> +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_SHORT_END_DELAY, ctrl->val);
>> +               break;
>> +       default:
>> +               ret = -EINVAL;
>> +               break;
>> +       }
>> +
>> +       return ret;
>> +}
>> +
>> +static int vgxy61_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
>> +{
>> +       struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>> +       int temperature;
>> +       int ret;
>> +
>> +       switch (ctrl->id) {
>> +       case V4L2_CID_TEMPERATURE:
>> +               ret = vgxy61_get_temp(sensor, &temperature);
>> +               if (ret)
>> +                       break;
>> +               ret = __v4l2_ctrl_s_ctrl(ctrl, temperature);
>> +               break;
>> +       default:
>> +               ret = -EINVAL;
>> +               break;
>> +       }
>> +
>> +       return ret;
>> +}
>> +
>> +static const struct v4l2_ctrl_ops vgxy61_ctrl_ops = {
>> +       .g_volatile_ctrl = vgxy61_g_volatile_ctrl,
>> +       .s_ctrl = vgxy61_s_ctrl,
>> +};
>> +
>> +static const struct v4l2_ctrl_config vgxy61_hdr_ctrl = {
>> +       .ops            = &vgxy61_ctrl_ops,
>> +       .id             = V4L2_CID_HDR,
>> +       .name           = "HDR mode",
>> +       .type           = V4L2_CTRL_TYPE_MENU,
>> +       .min            = 0,
>> +       .max            = ARRAY_SIZE(vgxy61_hdr_modes) - 1,
>> +       .def            = NO_HDR,
>> +       .qmenu          = vgxy61_hdr_modes,
>> +};
>> +
>> +static const struct v4l2_ctrl_config vgxy61_strobe_long_start_delay = {
>> +       .ops            = &vgxy61_ctrl_ops,
>> +       .id             = V4L2_CID_GPIOS_STROBE_LONG_START_DELAY,
>> +       .name           = "Long strobe mode start delay in lines",
>> +       .type           = V4L2_CTRL_TYPE_INTEGER,
>> +       .min            = -128,
>> +       .max            = 127,
>> +       .step           = 1,
>> +       .def            = 0,
>> +       .flags          = 0,
>> +};
>> +
>> +static const struct v4l2_ctrl_config vgxy61_strobe_long_end_delay = {
>> +       .ops            = &vgxy61_ctrl_ops,
>> +       .id             = V4L2_CID_GPIOS_STROBE_LONG_END_DELAY,
>> +       .name           = "Long strobe mode end delay in lines",
>> +       .type           = V4L2_CTRL_TYPE_INTEGER,
>> +       .min            = -128,
>> +       .max            = 127,
>> +       .step           = 1,
>> +       .def            = 0,
>> +       .flags          = 0,
>> +};
>> +
>> +static const struct v4l2_ctrl_config vgxy61_strobe_short_start_delay = {
>> +       .ops            = &vgxy61_ctrl_ops,
>> +       .id             = V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY,
>> +       .name           = "Short strobe mode start delay in lines",
>> +       .type           = V4L2_CTRL_TYPE_INTEGER,
>> +       .min            = -128,
>> +       .max            = 127,
>> +       .step           = 1,
>> +       .def            = 0,
>> +       .flags          = 0,
>> +};
>> +
>> +static const struct v4l2_ctrl_config vgxy61_strobe_short_end_delay = {
>> +       .ops            = &vgxy61_ctrl_ops,
>> +       .id             = V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY,
>> +       .name           = "Short strobe mode end delay in lines",
>> +       .type           = V4L2_CTRL_TYPE_INTEGER,
>> +       .min            = -128,
>> +       .max            = 127,
>> +       .step           = 1,
>> +       .def            = 0,
>> +       .flags          = 0,
>> +};
>> +
>> +static const struct v4l2_ctrl_config vgxy61_temp_ctrl = {
>> +       .ops            = &vgxy61_ctrl_ops,
>> +       .id             = V4L2_CID_TEMPERATURE,
>> +       .name           = "Temperature in °C",
>> +       .type           = V4L2_CTRL_TYPE_INTEGER,
>> +       .min            = -128,
>> +       .max            = 128,
>> +       .step           = 1,
>> +};
>> +
>> +static int vgxy61_init_controls(struct vgxy61_dev *sensor)
>> +{
>> +       const struct v4l2_ctrl_ops *ops = &vgxy61_ctrl_ops;
>> +       struct vgxy61_ctrls *ctrls = &sensor->ctrls;
>> +       struct v4l2_ctrl_handler *hdl = &sensor->ctrls.handler;
>> +       int ret;
>> +
>> +       v4l2_ctrl_handler_init(hdl, 16);
>> +       /* We can use our own mutex for the ctrl lock */
>> +       hdl->lock = &sensor->lock;
>> +       ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, 10, 0xffff, 1,
>> +                                           sensor->expo_long);
>> +       /* This is 8.8 fixed point value */
>> +       ctrls->analog_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, 0x3fff, 1,
>> +                                              0x0);
>> +       /* This is 8.8 fixed point value */
>> +       ctrls->digital_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 0, 0xfff, 1,
>> +                                               0x100);
>> +       ctrls->vflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, sensor->vflip);
>> +       ctrls->hflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, sensor->hflip);
>> +       ctrls->patgen = v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN,
>> +                                                    ARRAY_SIZE(vgxy61_test_pattern_menu) - 1,
>> +                                                    0, 0, vgxy61_test_pattern_menu);
>> +       ctrls->hdr = v4l2_ctrl_new_custom(hdl, &vgxy61_hdr_ctrl, NULL);
>> +       ctrls->pixel_rate = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1,
>> +                                             get_pixel_rate(sensor));
>> +       ctrls->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>> +       ctrls->link_freq = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ,
>> +                                                 ARRAY_SIZE(link_freq) - 1, 0, link_freq);
>> +       ctrls->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>> +       /* Gpios ctrls */
>> +       ctrls->gpios.long_start = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_long_start_delay, NULL);
>> +       ctrls->gpios.long_end = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_long_end_delay, NULL);
>> +       ctrls->gpios.short_start = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_short_start_delay,
>> +                                                       NULL);
>> +       ctrls->gpios.short_end = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_short_end_delay, NULL);
>> +       /* Temperature ctrl */
>> +       ctrls->temp = v4l2_ctrl_new_custom(hdl, &vgxy61_temp_ctrl, NULL);
>> +       ctrls->temp->flags |= V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY;
>> +
>> +       if (hdl->error) {
>> +               ret = hdl->error;
>> +               goto free_ctrls;
>> +       }
>> +
>> +       sensor->sd.ctrl_handler = hdl;
>> +       return 0;
>> +
>> +free_ctrls:
>> +       v4l2_ctrl_handler_free(hdl);
>> +       return ret;
>> +}
>> +
>> +static const struct v4l2_subdev_core_ops vgxy61_core_ops = {
>> +};
>> +
>> +static const struct v4l2_subdev_video_ops vgxy61_video_ops = {
>> +       .s_stream = vgxy61_s_stream,
>> +       .g_frame_interval = vgxy61_g_frame_interval,
>> +       .s_frame_interval = vgxy61_s_frame_interval,
>> +};
>> +
>> +static const struct v4l2_subdev_pad_ops vgxy61_pad_ops = {
>> +       .enum_mbus_code = vgxy61_enum_mbus_code,
>> +       .get_fmt = vgxy61_get_fmt,
>> +       .set_fmt = vgxy61_set_fmt,
>> +       .enum_frame_size = vgxy61_enum_frame_size,
>> +       .enum_frame_interval = vgxy61_enum_frame_interval,
>> +};
>> +
>> +static const struct v4l2_subdev_ops vgxy61_subdev_ops = {
>> +       .core = &vgxy61_core_ops,
>> +       .video = &vgxy61_video_ops,
>> +       .pad = &vgxy61_pad_ops,
>> +};
>> +
>> +static const struct media_entity_operations vgxy61_subdev_entity_ops = {
>> +       .link_validate = v4l2_subdev_link_validate,
>> +};
>> +
>> +/* Set phy polarities */
>> +static int vgxy61_tx_from_ep(struct vgxy61_dev *sensor, struct fwnode_handle *endpoint)
>> +{
>> +       struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
>> +       struct i2c_client *client = sensor->i2c_client;
>> +       u32 log2phy[NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
>> +       u32 phy2log[NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
>> +       int polarities[NB_POLARITIES] = {0, 0, 0, 0, 0};
>> +       int l_nb;
>> +       int p, l;
>> +       int ret;
>> +       int i;
>> +
>> +       ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep);
>> +       if (ret)
>> +               goto error_alloc;
>> +
>> +       l_nb = ep.bus.mipi_csi2.num_data_lanes;
>> +       if (l_nb != 1 && l_nb != 2 && l_nb != 4) {
>> +               dev_err(&client->dev, "invalid data lane number %d\n", l_nb);
>> +               goto error_ep;
>> +       }
>> +
>> +       /* Build log2phy, phy2log and polarities from ep info */
>> +       log2phy[0] = ep.bus.mipi_csi2.clock_lane;
>> +       phy2log[log2phy[0]] = 0;
>> +       for (l = 1; l < l_nb + 1; l++) {
>> +               log2phy[l] = ep.bus.mipi_csi2.data_lanes[l - 1];
>> +               phy2log[log2phy[l]] = l;
>> +       }
>> +       /*
>> +        * Then fill remaining slots for every physical slot to have something valid for hardware
>> +        * stuff.
>> +        */
>> +       for (p = 0; p < NB_POLARITIES; p++) {
>> +               if (phy2log[p] != ~0)
>> +                       continue;
>> +               phy2log[p] = l;
>> +               log2phy[l] = p;
>> +               l++;
>> +       }
>> +       for (l = 0; l < l_nb + 1; l++)
>> +               polarities[l] = ep.bus.mipi_csi2.lane_polarities[l];
>> +
>> +       if (log2phy[0] != 0) {
>> +               dev_err(&client->dev, "clk lane must be map to physical lane 0\n");
>> +               goto error_ep;
>> +       }
>> +       sensor->oif_ctrl = (polarities[4] << 15) + ((phy2log[4] - 1) << 13) +
>> +                          (polarities[3] << 12) + ((phy2log[3] - 1) << 10) +
>> +                          (polarities[2] <<  9) + ((phy2log[2] - 1) <<  7) +
>> +                          (polarities[1] <<  6) + ((phy2log[1] - 1) <<  4) +
>> +                          (polarities[0] <<  3) +
>> +                          l_nb;
>> +       sensor->nb_of_lane = l_nb;
>> +
>> +       dev_dbg(&client->dev, "tx uses %d lanes", l_nb);
>> +       for (i = 0; i < 5; i++) {
>> +               dev_dbg(&client->dev, "log2phy[%d] = %d\n", i, log2phy[i]);
>> +               dev_dbg(&client->dev, "phy2log[%d] = %d\n", i, phy2log[i]);
>> +               dev_dbg(&client->dev, "polarity[%d] = %d\n", i, polarities[i]);
>> +       }
>> +       dev_dbg(&client->dev, "oif_ctrl = 0x%04x\n", sensor->oif_ctrl);
>> +
>> +       v4l2_fwnode_endpoint_free(&ep);
>> +
>> +       return 0;
>> +
>> +error_ep:
>> +       v4l2_fwnode_endpoint_free(&ep);
>> +error_alloc:
>> +
>> +       return -EINVAL;
>> +}
>> +
>> +static int vgxy61_configure(struct vgxy61_dev *sensor)
>> +{
>> +       struct i2c_client *client = sensor->i2c_client;
>> +       int sensor_freq;
>> +       unsigned int prediv;
>> +       unsigned int mult;
>> +       int ret;
>> +
>> +       compute_pll_parameters_by_freq(sensor->clk_freq, &prediv, &mult);
>> +       sensor_freq = (mult * sensor->clk_freq) / prediv;
>> +       /* Frequency to data rate is 1:1 ratio for MIPI */
>> +       sensor->data_rate_in_mbps = sensor_freq;
>> +       /* Video timing ISP path (pixel clock)  requires 804/5 mhz = 160 mhz */
>> +       sensor->pclk = sensor_freq / 5;
>> +
>> +       /* Cache line_length value */
>> +       ret = vgxy61_read_reg16(sensor, DEVICE_LINE_LENGTH, &sensor->line_length);
>> +       if (ret)
>> +               return ret;
>> +       /* Configure clocks */
>> +       ret = vgxy61_write_reg32(sensor, DEVICE_EXT_CLOCK, sensor->clk_freq);
>> +       if (ret)
>> +               return ret;
>> +       ret = vgxy61_write_reg(sensor, DEVICE_CLK_PLL_PREDIV, prediv);
>> +       if (ret)
>> +               return ret;
>> +       ret = vgxy61_write_reg(sensor, DEVICE_CLK_SYS_PLL_MULT, mult);
>> +       if (ret)
>> +               return ret;
>> +       /* Configure interface */
>> +       ret = vgxy61_write_reg16(sensor, DEVICE_OIF_CTRL, sensor->oif_ctrl);
>> +       if (ret)
>> +               return ret;
>> +       /* Disable pwm compression */
>> +       ret = vgxy61_write_reg(sensor, DEVICE_FRAME_CONTENT_CTRL, 0);
>> +       if (ret)
>> +               return ret;
>> +       /* Disable asil lines */
>> +       ret = vgxy61_write_reg(sensor, DEVICE_BYPASS_CTRL, 4);
>> +       if (ret)
>> +               return ret;
>> +       /* Set gpios polarity according to device tree value */
>> +       ret = vgxy61_update_gpios_strobe_polarity(sensor, sensor->gpios_polarity);
>> +       if (ret)
>> +               return ret;
>> +       /* HDR mode */
>> +       ret = vgxy61_update_hdr(sensor, sensor->hdr);
>> +       if (ret)
>> +               return ret;
>> +       /* Slave mode */
>> +       ret = vgxy61_write_reg(sensor, DEVICE_VT_CTRL, sensor->slave_mode);
>> +       if (ret)
>> +               return ret;
>> +       /* Set pattern generator solid to middle value */
>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_GR, 0x800);
>> +       if (ret)
>> +               return ret;
>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_R, 0x800);
>> +       if (ret)
>> +               return ret;
>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_B, 0x800);
>> +       if (ret)
>> +               return ret;
>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_GB, 0x800);
>> +       if (ret)
>> +               return ret;
>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_GR, 0x800);
>> +       if (ret)
>> +               return ret;
>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_R, 0x800);
>> +       if (ret)
>> +               return ret;
>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_B, 0x800);
>> +       if (ret)
>> +               return ret;
>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_GB, 0x800);
>> +       if (ret)
>> +               return ret;
>> +
>> +       dev_dbg(&client->dev, "clock prediv = %d\n", prediv);
>> +       dev_dbg(&client->dev, "clock mult = %d\n", mult);
>> +       dev_dbg(&client->dev, "data rate = %d mbps\n",
>> +               sensor->data_rate_in_mbps);
>> +
>> +       return 0;
>> +}
>> +
>> +static int vgxy61_patch(struct vgxy61_dev *sensor)
>> +{
>> +       struct i2c_client *client = sensor->i2c_client;
>> +       u16 patch;
>> +       int ret;
>> +
>> +       ret = vgxy61_write_array(sensor, DEVICE_FWPATCH_START_ADDR, sizeof(patch_array),
>> +                                patch_array);
>> +       if (ret)
>> +               return ret;
>> +
>> +       ret = vgxy61_write_reg(sensor, DEVICE_STBY, 0x10);
>> +       if (ret)
>> +               return ret;
>> +
>> +       ret = vgxy61_poll_reg(sensor, DEVICE_STBY, 0);
>> +       if (ret)
>> +               return ret;
>> +
>> +       ret = vgxy61_read_reg16(sensor, DEVICE_FWPATCH_REVISION, &patch);
>> +       if (ret)
>> +               return ret;
>> +
>> +       if (patch != (DEVICE_FWPATCH_REVISION_MAJOR << 12) +
>> +                    (DEVICE_FWPATCH_REVISION_MINOR << 8) +
>> +                    DEVICE_FWPATCH_REVISION_MICRO) {
>> +               dev_err(&client->dev, "bad patch version expected %d.%d.%d got %d.%d.%d\n",
>> +                       DEVICE_FWPATCH_REVISION_MAJOR,
>> +                       DEVICE_FWPATCH_REVISION_MINOR,
>> +                       DEVICE_FWPATCH_REVISION_MICRO,
>> +                       patch >> 12, (patch >> 8) & 0x0f, patch & 0xff);
>> +               return -ENODEV;
>> +       }
>> +       dev_dbg(&client->dev, "patch %d.%d.%d applied\n",
>> +               patch >> 12, (patch >> 8) & 0x0f, patch & 0xff);
>> +
>> +       return 0;
>> +}
>> +
>> +static int vgxy61_detect_cut_version(struct vgxy61_dev *sensor)
>> +{
>> +       struct i2c_client *client = sensor->i2c_client;
>> +       u16 device_rev;
>> +       int ret;
>> +
>> +       ret = vgxy61_read_reg16(sensor, DEVICE_REVISION, &device_rev);
>> +       if (ret)
>> +               return ret;
>> +
>> +       switch (device_rev >> 8) {
>> +       case 0xA:
>> +               dev_info(&client->dev, "Cut1 detected\n");
>> +               dev_err(&client->dev, "Cut1 not supported by this driver\n");
>> +               return -ENODEV;
>> +       case 0xB:
>> +               dev_info(&client->dev, "Cut2 detected\n");
>> +               return 0;
>> +       case 0xC:
>> +               dev_info(&client->dev, "Cut3 detected\n");
>> +               return 0;
>> +       default:
>> +               dev_err(&client->dev, "Unable to detect cut version\n");
>> +               return -ENODEV;
>> +       }
>> +}
>> +
>> +static int vgxy61_detect(struct vgxy61_dev *sensor)
>> +{
>> +       struct i2c_client *client = sensor->i2c_client;
>> +       u16 id = 0;
>> +       int ret;
>> +       u8 st;
>> +
>> +       ret = vgxy61_read_reg16(sensor, DEVICE_MODEL_ID_REG, &id);
>> +       if (ret)
>> +               return ret;
>> +       if (id != VG5661_MODEL_ID && id != VG5761_MODEL_ID) {
>> +               dev_warn(&client->dev, "Unsupported sensor id %x\n", id);
>> +               return -ENODEV;
>> +       }
>> +       dev_dbg(&client->dev, "detected sensor id = 0x%04x\n", id);
>> +
>> +       if (id == VG5761_MODEL_ID) {
>> +               sensor->sensor_width = VGX761_WIDTH;
>> +               sensor->sensor_height = VGX761_HEIGHT;
>> +               sensor->sensor_modes = vgx761_mode_data;
>> +               sensor->sensor_modes_nb = ARRAY_SIZE(vgx761_mode_data);
>> +               sensor->current_mode = &vgx761_mode_data[VGX761_DEFAULT_MODE];
>> +               sensor->rot_term = VGX761_SHORT_ROT_TERM;
>> +               sensor->sensor_rates = vgx761_sensor_frame_rates;
>> +               sensor->sensor_rates_nb = ARRAY_SIZE(vgx761_sensor_frame_rates);
>> +       } else if (id == VG5661_MODEL_ID) {
>> +               sensor->sensor_width = VGX661_WIDTH;
>> +               sensor->sensor_height = VGX661_HEIGHT;
>> +               sensor->sensor_modes = vgx661_mode_data;
>> +               sensor->sensor_modes_nb = ARRAY_SIZE(vgx661_mode_data);
>> +               sensor->current_mode = &vgx661_mode_data[VGX661_DEFAULT_MODE];
>> +               sensor->rot_term = VGX661_SHORT_ROT_TERM;
>> +               sensor->sensor_rates = vgx661_sensor_frame_rates;
>> +               sensor->sensor_rates_nb = ARRAY_SIZE(vgx661_sensor_frame_rates);
>> +       }
>> +
>> +       ret = vgxy61_wait_state(sensor, SW_STBY);
>> +       if (ret)
>> +               return ret;
>> +
>> +       ret = vgxy61_read_reg(sensor, DEVICE_NVM, &st);
>> +       if (ret)
>> +               return ret;
>> +       if (st != NVM_OK)
>> +               dev_warn(&client->dev, "Bad nvm state got %d\n", st);
>> +
>> +       /* Detect cut version */
>> +       ret = vgxy61_detect_cut_version(sensor);
>> +       if (ret)
>> +               return ret;
>> +
>> +       return 0;
>> +}
>> +
>> +static int vgxy61_probe(struct i2c_client *client)
>> +{
>> +       struct device *dev = &client->dev;
>> +       struct fwnode_handle *endpoint;
>> +       struct vgxy61_dev *sensor;
>> +       int ret;
>> +
>> +       sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
>> +       if (!sensor)
>> +               return -ENOMEM;
>> +
>> +       sensor->i2c_client = client;
>> +       sensor->streaming = false;
>> +       sensor->fmt.code = MEDIA_BUS_FMT_SGBRG8_1X8;
>> +       sensor->fmt.field = V4L2_FIELD_NONE;
>> +       sensor->fmt.colorspace = V4L2_COLORSPACE_SRGB;
>> +       sensor->frame_interval.numerator = 1;
>> +       sensor->frame_interval.denominator = 60;
>> +       sensor->hdr = NO_HDR;
>> +       sensor->expo_long = 200;
>> +       sensor->expo_short = 0;
>> +       sensor->hflip = false;
>> +       sensor->vflip = false;
>> +
>> +       endpoint = fwnode_graph_get_next_endpoint(of_fwnode_handle(dev->of_node), NULL);
>> +       if (!endpoint) {
>> +               dev_err(dev, "endpoint node not found\n");
>> +               return -EINVAL;
>> +       }
>> +
>> +       ret = vgxy61_tx_from_ep(sensor, endpoint);
>> +       fwnode_handle_put(endpoint);
>> +       if (ret) {
>> +               dev_err(dev, "Failed to parse endpoint %d\n", ret);
>> +               return ret;
>> +       }
>> +
>> +       sensor->xclk = devm_clk_get(dev, "xclk");
>> +       if (IS_ERR(sensor->xclk)) {
>> +               dev_err(dev, "failed to get xclk\n");
>> +               return PTR_ERR(sensor->xclk);
>> +       }
>> +       sensor->clk_freq = clk_get_rate(sensor->xclk);
>> +       if (sensor->clk_freq < 6000000 || sensor->clk_freq > 27000000) {
>> +               dev_err(dev, "Only 6Mhz-27Mhz clock range supported. provide %d Hz\n",
>> +                       sensor->clk_freq);
>> +               return -EINVAL;
>> +       }
>> +       sensor->gpios_polarity = of_property_read_bool(dev->of_node, "invert-gpios-polarity");
>> +       sensor->slave_mode = of_property_read_bool(dev->of_node, "slave-mode");
>> +
>> +       v4l2_i2c_subdev_init(&sensor->sd, client, &vgxy61_subdev_ops);
>> +       sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>> +       sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
>> +       sensor->sd.entity.ops = &vgxy61_subdev_entity_ops;
>> +       sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
>> +
>> +       ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
>> +       if (ret) {
>> +               dev_err(&client->dev, "pads init failed %d\n", ret);
>> +               return ret;
>> +       }
>> +
>> +       /* Request optional reset pin */
>> +       sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
>> +
>> +       ret = vgxy61_get_regulators(sensor);
>> +       if (ret) {
>> +               dev_err(&client->dev, "failed to get regulators %d\n", ret);
>> +               goto entity_cleanup;
>> +       }
>> +
>> +       ret = regulator_bulk_enable(VGXY61_NUM_SUPPLIES, sensor->supplies);
>> +       if (ret) {
>> +               dev_err(&client->dev, "failed to enable regulators %d\n", ret);
>> +               goto entity_cleanup;
>> +       }
>> +
>> +       ret = clk_prepare_enable(sensor->xclk);
>> +       if (ret) {
>> +               dev_err(&client->dev, "failed to enable clock %d\n", ret);
>> +               goto disable_bulk;
>> +       }
>> +
>> +       mutex_init(&sensor->lock);
>> +
>> +       /* Apply reset sequence */
>> +       if (sensor->reset_gpio) {
>> +               ret = vgxy61_apply_reset(sensor);
>> +               if (ret) {
>> +                       dev_err(&client->dev, "sensor reset failed %d\n", ret);
>> +                       goto disable_clock;
>> +               }
>> +       }
>> +
>> +       ret = vgxy61_detect(sensor);
>> +       if (ret) {
>> +               dev_err(&client->dev, "sensor detect failed %d\n", ret);
>> +               goto disable_clock;
>> +       }
>> +       sensor->fmt.width = sensor->current_mode->width;
>> +       sensor->fmt.height = sensor->current_mode->height;
>> +
>> +       ret = vgxy61_patch(sensor);
>> +       if (ret) {
>> +               dev_err(&client->dev, "sensor patch failed %d\n", ret);
>> +               goto disable_clock;
>> +       }
>> +
>> +       ret = vgxy61_configure(sensor);
>> +       if (ret) {
>> +               dev_err(&client->dev, "sensor configuration failed %d\n", ret);
>> +               goto disable_clock;
>> +       }
>> +
>> +       ret = vgxy61_init_controls(sensor);
>> +       if (ret) {
>> +               dev_err(&client->dev, "controls initialization failed %d\n", ret);
>> +               goto disable_clock;
>> +       }
>> +
>> +       ret = v4l2_async_register_subdev(&sensor->sd);
>> +       if (ret) {
>> +               dev_err(&client->dev, "async subdev register failed %d\n", ret);
>> +               goto disable_clock;
>> +       }
>> +
>> +       dev_info(&client->dev, "vgxy61 probe successfully\n");
>> +
>> +       return 0;
>> +
>> +disable_clock:
>> +       clk_disable_unprepare(sensor->xclk);
>> +disable_bulk:
>> +       regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
>> +entity_cleanup:
>> +       mutex_destroy(&sensor->lock);
>> +       media_entity_cleanup(&sensor->sd.entity);
>> +       return ret;
>> +}
>> +
>> +static int vgxy61_remove(struct i2c_client *client)
>> +{
>> +       struct v4l2_subdev *sd = i2c_get_clientdata(client);
>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>> +
>> +       v4l2_async_unregister_subdev(&sensor->sd);
>> +       clk_disable_unprepare(sensor->xclk);
>> +       mutex_destroy(&sensor->lock);
>> +       media_entity_cleanup(&sensor->sd.entity);
>> +       regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
>> +
>> +       return 0;
>> +}
>> +
>> +static const struct of_device_id vgxy61_dt_ids[] = {
>> +       { .compatible = "st,st-vgxy61" },
>> +       { /* sentinel */ }
>> +};
>> +MODULE_DEVICE_TABLE(of, vgxy61_dt_ids);
>> +
>> +static struct i2c_driver vgxy61_i2c_driver = {
>> +       .driver = {
>> +               .name  = "st-vgxy61",
>> +               .of_match_table = vgxy61_dt_ids,
>> +       },
>> +       .probe_new = vgxy61_probe,
>> +       .remove = vgxy61_remove,
>> +};
>> +
>> +module_i2c_driver(vgxy61_i2c_driver);
>> +
>> +MODULE_AUTHOR("Benjamin Mugnier <benjamin.mugnier@foss.st.com>");
>> +MODULE_AUTHOR("Mickael Guene <mickael.guene@st.com>");
>> +MODULE_AUTHOR("Sylvain Petinot <sylvain.petinot@foss.st.com>");
>> +MODULE_DESCRIPTION("VGXY61 camera subdev driver");
>> +MODULE_LICENSE("GPL v2");
>> -- 
>> 2.25.1
>>

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

* Re: [PATCH 2/2] media: i2c: Add driver for ST VGXY61 camera sensor
  2022-03-10 13:52   ` Kieran Bingham
  2022-03-10 15:15     ` Benjamin Mugnier
@ 2022-03-10 15:21     ` Dave Stevenson
  2022-03-10 15:54       ` Kieran Bingham
  2022-03-11 10:56       ` Benjamin Mugnier
  1 sibling, 2 replies; 17+ messages in thread
From: Dave Stevenson @ 2022-03-10 15:21 UTC (permalink / raw)
  To: Kieran Bingham
  Cc: Benjamin Mugnier, Linux Media Mailing List, alain.volmat,
	hugues.fruchet, sylvain.petinot

Hi Benjamin and Kieran

On Thu, 10 Mar 2022 at 13:52, Kieran Bingham
<kieran.bingham@ideasonboard.com> wrote:
>
> Hi Benjamin,
>
> Thank you for the patch -
>
> Quoting Benjamin Mugnier (2022-03-10 13:32:55)
> > The VGXY61 has a quad lanes CSI-2 output port running at 800mbps per
> > lane, and supports RAW8, RAW10, RAW12, RAW14 and RAW16 formats.
> > The driver handles both sensor types:
> > - VG5661 and VG6661: 1.6 Mpx (1464 x 1104) 75fps.
> > - VG5761 and VG6761: 2.3 Mpx (1944 x 1204) 60 fps.
> > The driver supports:
> > - HDR linearize mode, HDR substraction mode, and no HDR
> > - GPIOs LEDs strobing
> > - Digital binning and analog subsampling
> > - Horizontal and vertical flip
> > - Manual exposure
> > - Analog and digital gains
> > - Test patterns
>
> I haven't reviewed the driver directly yet, but I have a script which
> looks for key requirements for libcamera.
> (https://git.linuxtv.org/libcamera.git/tree/Documentation/sensor_driver_requirements.rst)
>
>
> Media Controller Support:
>  - V4L2_SUBDEV_FL_HAS_DEVNODE      : found
>  - MEDIA_ENT_F_CAM_SENSOR          : found
>
> Mandatory Controls:
>  - V4L2_CID_EXPOSURE               : found
>  - V4L2_CID_HBLANK                 : --------
>  - V4L2_CID_VBLANK                 : --------
>  - V4L2_CID_PIXEL_RATE             : found
>
> Selection Controls (will become mandatory):
>  - V4L2_SEL_TGT_CROP_DEFAULT       : --------
>  - V4L2_SEL_TGT_CROP               : --------
>  - V4L2_SEL_TGT_CROP_BOUNDS        : --------
>  - .get_selection                  : --------
>
> Optional Controls:
>  - V4L2_CID_TEST_PATTERN           : found
>  - V4L2_CID_GAIN                   : --------
>  - V4L2_CID_ANALOGUE_GAIN          : found
>  - V4L2_CID_CAMERA_SENSOR_ROTATION : --------
>  - V4L2_CID_CAMERA_ORIENTATION     : --------
>
>
> The key missing pieces are HBLANK/VBLANK and the .get_selection API. Is
> it easy/feasible to add these?

It's documented that HBLANK/VBLANK are the correct parameters to use
for raw image sensors [1].

It looks like register DEVICE_FRAME_LENGTH is mode->height +
V4L2_CID_VBLANK, and DEVICE_LINE_LENGTH is mode->width +
V4L2_CID_HBLANK (appears to be treated as read only)
Most other sensors will adjust the max value of V4L2_CID_EXPOSURE
based on the frame length.

[1] https://www.kernel.org/doc/html/latest/driver-api/media/camera-sensor.html#frame-interval-configuration

I've only done a quick review on the rest.

> --
> Regards
>
> Kieran
>
>
> >
> > Signed-off-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com>
> > ---
> >  drivers/media/i2c/Kconfig     |   11 +
> >  drivers/media/i2c/Makefile    |    1 +
> >  drivers/media/i2c/st-vgxy61.c | 1919 +++++++++++++++++++++++++++++++++
> >  3 files changed, 1931 insertions(+)
> >  create mode 100644 drivers/media/i2c/st-vgxy61.c
> >
> > diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> > index fe66093b8849..e6e9c1f19c09 100644
> > --- a/drivers/media/i2c/Kconfig
> > +++ b/drivers/media/i2c/Kconfig
> > @@ -1454,6 +1454,17 @@ config VIDEO_S5C73M3
> >           This is a V4L2 sensor driver for Samsung S5C73M3
> >           8 Mpixel camera.
> >
> > +config VIDEO_ST_VGXY61
> > +       tristate "ST VGXY61 sensor support"
> > +       depends on OF
> > +       depends on GPIOLIB && VIDEO_V4L2 && I2C
> > +       select MEDIA_CONTROLLER
> > +       select VIDEO_V4L2_SUBDEV_API
> > +       select V4L2_FWNODE
> > +       help
> > +         This is a Video4Linux2 sensor driver for the ST VGXY61
> > +         camera sensor.
> > +
> >  endmenu
> >
> >  menu "Lens drivers"
> > diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> > index f6b80ef6f41d..0190b5edfa24 100644
> > --- a/drivers/media/i2c/Makefile
> > +++ b/drivers/media/i2c/Makefile
> > @@ -138,4 +138,5 @@ obj-$(CONFIG_VIDEO_MAX9271_LIB)     += max9271.o
> >  obj-$(CONFIG_VIDEO_RDACM20)    += rdacm20.o
> >  obj-$(CONFIG_VIDEO_RDACM21)    += rdacm21.o
> >  obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o
> > +obj-$(CONFIG_VIDEO_ST_VGXY61)  += st-vgxy61.o
> >  obj-$(CONFIG_SDR_MAX2175) += max2175.o
> > diff --git a/drivers/media/i2c/st-vgxy61.c b/drivers/media/i2c/st-vgxy61.c
> > new file mode 100644
> > index 000000000000..d3d816189f86
> > --- /dev/null
> > +++ b/drivers/media/i2c/st-vgxy61.c
> > @@ -0,0 +1,1919 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Driver for VGXY61 global shutter sensor family driver
> > + *
> > + * Copyright (C) 2022 STMicroelectronics SA
> > + */
> > +
> > +#include <linux/clk.h>
> > +#include <linux/delay.h>
> > +#include <linux/gpio/consumer.h>
> > +#include <linux/i2c.h>
> > +#include <linux/module.h>
> > +#include <linux/regulator/consumer.h>
> > +#include <linux/units.h>
> > +#include <linux/iopoll.h>
> > +#include <media/mipi-csi2.h>
> > +#include <media/v4l2-async.h>
> > +#include <media/v4l2-ctrls.h>
> > +#include <media/v4l2-device.h>
> > +#include <media/v4l2-fwnode.h>
> > +#include <media/v4l2-subdev.h>
> > +
> > +#define US_PER_MS                                      1000
> > +
> > +#define DEVICE_MODEL_ID_REG                            0x0000
> > +#define VG5661_MODEL_ID                                        0x5661
> > +#define VG5761_MODEL_ID                                        0x5761
> > +#define VGX661_WIDTH                                   1464
> > +#define VGX661_HEIGHT                                  1104
> > +#define VGX761_WIDTH                                   1944
> > +#define VGX761_HEIGHT                                  1204
> > +#define VGX661_DEFAULT_MODE                            1
> > +#define VGX761_DEFAULT_MODE                            1
> > +#define VGX661_SHORT_ROT_TERM                          93
> > +#define VGX761_SHORT_ROT_TERM                          90
> > +#define VGXY61_EXPOS_ROT_TERM                          66
> > +#define DEVICE_REVISION                                        0x0002
> > +#define DEVICE_FWPATCH_REVISION                                0x0014
> > +#define DEVICE_FWPATCH_START_ADDR                      0x2000
> > +#define DEVICE_SYSTEM_FSM                              0x0020
> > +#define SW_STBY                                                0x03
> > +#define STREAMING                                      0x04
> > +#define DEVICE_NVM                                     0x0023
> > +#define NVM_OK                                         0x04
> > +#define DEVICE_THSENS1_TEMPERATURE                     0x0044
> > +#define DEVICE_STBY                                    0x0201
> > +#define STBY_NO_REQ                                    0
> > +#define STBY_REQ_TMP_READ                              BIT(2)
> > +#define DEVICE_STREAMING                               0x0202
> > +#define REQ_NO_REQUEST                                 0
> > +#define REQ_STOP_STREAMING                             BIT(0)
> > +#define REQ_START_STREAMING                            BIT(1)
> > +#define DEVICE_EXT_CLOCK                               0x0220
> > +#define DEVICE_CLK_PLL_PREDIV                          0x0224
> > +#define DEVICE_CLK_SYS_PLL_MULT                                0x0225
> > +#define DEVICE_GPIO_0_CTRL                             0x0236
> > +#define DEVICE_GPIO_1_CTRL                             0x0237
> > +#define DEVICE_GPIO_2_CTRL                             0x0238
> > +#define DEVICE_GPIO_3_CTRL                             0x0239
> > +#define DEVICE_SIGNALS_POLARITY_CTRL                   0x023b
> > +#define DEVICE_LINE_LENGTH                             0x0300
> > +#define DEVICE_ORIENTATION                             0x0302
> > +#define DEVICE_VT_CTRL                                 0x0304
> > +#define DEVICE_FORMAT_CTRL                             0x0305
> > +#define DEVICE_OIF_CTRL                                        0x0306
> > +#define DEVICE_OIF_ROI0_CTRL                           0x030a
> > +#define DEVICE_ROI0_START_H                            0x0400
> > +#define DEVICE_ROI0_START_V                            0x0402
> > +#define DEVICE_ROI0_END_H                              0x0404
> > +#define DEVICE_ROI0_END_V                              0x0406
> > +#define DEVICE_PATGEN_CTRL                             0x0440
> > +#define DEVICE_FRAME_CONTENT_CTRL                      0x0478
> > +#define DEVICE_COARSE_EXPOSURE_LONG                    0x0500
> > +#define DEVICE_COARSE_EXPOSURE_SHORT                   0x0504
> > +#define DEVICE_ANALOG_GAIN                             0x0508
> > +#define DEVICE_DIGITAL_GAIN_LONG                       0x050a
> > +#define DEVICE_DIGITAL_GAIN_SHORT                      0x0512
> > +#define DEVICE_FRAME_LENGTH                            0x051a
> > +#define DEVICE_SIGNALS_CTRL                            0x0522
> > +#define DEVICE_STROBE_LONG_START_DELAY                 0x0528
> > +#define DEVICE_STROBE_LONG_END_DELAY                   0x052a
> > +#define DEVICE_STROBE_SHORT_START_DELAY                        0x052c
> > +#define DEVICE_STROBE_SHORT_END_DELAY                  0x052e
> > +#define DEVICE_READOUT_CTRL                            0x0530
> > +#define DEVICE_HDR_CTRL                                        0x0532
> > +#define DEVICE_PATGEN_LONG_DATA_GR                     0x092c
> > +#define DEVICE_PATGEN_LONG_DATA_R                      0x092e
> > +#define DEVICE_PATGEN_LONG_DATA_B                      0x0930
> > +#define DEVICE_PATGEN_LONG_DATA_GB                     0x0932
> > +#define DEVICE_PATGEN_SHORT_DATA_GR                    0x0950
> > +#define DEVICE_PATGEN_SHORT_DATA_R                     0x0952
> > +#define DEVICE_PATGEN_SHORT_DATA_B                     0x0954
> > +#define DEVICE_PATGEN_SHORT_DATA_GB                    0x0956
> > +#define DEVICE_BYPASS_CTRL                             0x0a60
> > +
> > +#define V4L2_CID_HDR                           (V4L2_CID_USER_BASE | 0x1004)
> > +#define V4L2_CID_GPIOS_STROBE_LONG_START_DELAY (V4L2_CID_USER_BASE | 0x1019)
> > +#define V4L2_CID_GPIOS_STROBE_LONG_END_DELAY   (V4L2_CID_USER_BASE | 0x101a)
> > +#define V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY        (V4L2_CID_USER_BASE | 0x101b)
> > +#define V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY  (V4L2_CID_USER_BASE | 0x101c)
> > +#define V4L2_CID_TEMPERATURE                   (V4L2_CID_USER_BASE | 0x1020)

The strobe and temperature controls are equally valid for other
sensors, so IMHO ought to be added as standard controls.

> > +
> > +#define DEVICE_FWPATCH_REVISION_MAJOR                  2
> > +#define DEVICE_FWPATCH_REVISION_MINOR                  0
> > +#define DEVICE_FWPATCH_REVISION_MICRO                  5
> > +
> > +#define WRITE_MULTIPLE_CHUNK_MAX                       16
> > +#define NB_GPIOS                                       4
> > +#define NB_POLARITIES                                  5
> > +
> > +static const u8 patch_array[] = {
> > +       0xbf, 0x00, 0x05, 0x20, 0x06, 0x01, 0xe0, 0xe0, 0x04, 0x80, 0xe6, 0x45,
> > +       0xed, 0x6f, 0xfe, 0xff, 0x14, 0x80, 0x1f, 0x84, 0x10, 0x42, 0x05, 0x7c,
> > +       0x01, 0xc4, 0x1e, 0x80, 0xb6, 0x42, 0x00, 0xe0, 0x1e, 0x82, 0x1e, 0xc0,
> > +       0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa, 0x86, 0x0d, 0x70, 0xe1,
> > +       0x04, 0x98, 0x15, 0x00, 0x28, 0xe0, 0x14, 0x02, 0x08, 0xfc, 0x15, 0x40,
> > +       0x28, 0xe0, 0x98, 0x58, 0xe0, 0xef, 0x04, 0x98, 0x0e, 0x04, 0x00, 0xf0,
> > +       0x15, 0x00, 0x28, 0xe0, 0x19, 0xc8, 0x15, 0x40, 0x28, 0xe0, 0xc6, 0x41,
> > +       0xfc, 0xe0, 0x14, 0x80, 0x1f, 0x84, 0x14, 0x02, 0xa0, 0xfc, 0x1e, 0x80,
> > +       0x14, 0x80, 0x14, 0x02, 0x80, 0xfb, 0x14, 0x02, 0xe0, 0xfc, 0x1e, 0x80,
> > +       0x14, 0xc0, 0x1f, 0x84, 0x14, 0x02, 0xa4, 0xfc, 0x1e, 0xc0, 0x14, 0xc0,
> > +       0x14, 0x02, 0x80, 0xfb, 0x14, 0x02, 0xe4, 0xfc, 0x1e, 0xc0, 0x0c, 0x0c,
> > +       0x00, 0xf2, 0x93, 0xdd, 0x86, 0x00, 0xf8, 0xe0, 0x04, 0x80, 0xc6, 0x03,
> > +       0x70, 0xe1, 0x0e, 0x84, 0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa,
> > +       0x6b, 0x80, 0x06, 0x40, 0x6c, 0xe1, 0x04, 0x80, 0x09, 0x00, 0xe0, 0xe0,
> > +       0x0b, 0xa1, 0x95, 0x84, 0x05, 0x0c, 0x1c, 0xe0, 0x86, 0x02, 0xf9, 0x60,
> > +       0xe0, 0xcf, 0x78, 0x6e, 0x80, 0xef, 0x25, 0x0c, 0x18, 0xe0, 0x05, 0x4c,
> > +       0x1c, 0xe0, 0x86, 0x02, 0xf9, 0x60, 0xe0, 0xcf, 0x0b, 0x84, 0xd8, 0x6d,
> > +       0x80, 0xef, 0x05, 0x4c, 0x18, 0xe0, 0x04, 0xd8, 0x0b, 0xa5, 0x95, 0x84,
> > +       0x05, 0x0c, 0x2c, 0xe0, 0x06, 0x02, 0x01, 0x60, 0xe0, 0xce, 0x18, 0x6d,
> > +       0x80, 0xef, 0x25, 0x0c, 0x30, 0xe0, 0x05, 0x4c, 0x2c, 0xe0, 0x06, 0x02,
> > +       0x01, 0x60, 0xe0, 0xce, 0x0b, 0x84, 0x78, 0x6c, 0x80, 0xef, 0x05, 0x4c,
> > +       0x30, 0xe0, 0x0c, 0x0c, 0x00, 0xf2, 0x93, 0xdd, 0x46, 0x01, 0x70, 0xe1,
> > +       0x08, 0x80, 0x0b, 0xa1, 0x08, 0x5c, 0x00, 0xda, 0x06, 0x01, 0x68, 0xe1,
> > +       0x04, 0x80, 0x4a, 0x40, 0x84, 0xe0, 0x08, 0x5c, 0x00, 0x9a, 0x06, 0x01,
> > +       0xe0, 0xe0, 0x04, 0x80, 0x15, 0x00, 0x60, 0xe0, 0x19, 0xc4, 0x15, 0x40,
> > +       0x60, 0xe0, 0x15, 0x00, 0x78, 0xe0, 0x19, 0xc4, 0x15, 0x40, 0x78, 0xe0,
> > +       0x93, 0xdd, 0xc3, 0xc1, 0x46, 0x01, 0x70, 0xe1, 0x08, 0x80, 0x0b, 0xa1,
> > +       0x08, 0x5c, 0x00, 0xda, 0x06, 0x01, 0x68, 0xe1, 0x04, 0x80, 0x4a, 0x40,
> > +       0x84, 0xe0, 0x08, 0x5c, 0x00, 0x9a, 0x06, 0x01, 0xe0, 0xe0, 0x14, 0x80,
> > +       0x25, 0x02, 0x54, 0xe0, 0x29, 0xc4, 0x25, 0x42, 0x54, 0xe0, 0x24, 0x80,
> > +       0x35, 0x04, 0x6c, 0xe0, 0x39, 0xc4, 0x35, 0x44, 0x6c, 0xe0, 0x25, 0x02,
> > +       0x64, 0xe0, 0x29, 0xc4, 0x25, 0x42, 0x64, 0xe0, 0x04, 0x80, 0x15, 0x00,
> > +       0x7c, 0xe0, 0x19, 0xc4, 0x15, 0x40, 0x7c, 0xe0, 0x93, 0xdd, 0xc3, 0xc1,
> > +       0x4c, 0x04, 0x7c, 0xfa, 0x86, 0x40, 0x98, 0xe0, 0x14, 0x80, 0x1b, 0xa1,
> > +       0x06, 0x00, 0x00, 0xc0, 0x08, 0x42, 0x38, 0xdc, 0x08, 0x64, 0xa0, 0xef,
> > +       0x86, 0x42, 0x3c, 0xe0, 0x68, 0x49, 0x80, 0xef, 0x6b, 0x80, 0x78, 0x53,
> > +       0xc8, 0xef, 0xc6, 0x54, 0x6c, 0xe1, 0x7b, 0x80, 0xb5, 0x14, 0x0c, 0xf8,
> > +       0x05, 0x14, 0x14, 0xf8, 0x1a, 0xac, 0x8a, 0x80, 0x0b, 0x90, 0x38, 0x55,
> > +       0x80, 0xef, 0x1a, 0xae, 0x17, 0xc2, 0x03, 0x82, 0x88, 0x65, 0x80, 0xef,
> > +       0x1b, 0x80, 0x0b, 0x8e, 0x68, 0x65, 0x80, 0xef, 0x9b, 0x80, 0x0b, 0x8c,
> > +       0x08, 0x65, 0x80, 0xef, 0x6b, 0x80, 0x0b, 0x92, 0x1b, 0x8c, 0x98, 0x64,
> > +       0x80, 0xef, 0x1a, 0xec, 0x9b, 0x80, 0x0b, 0x90, 0x95, 0x54, 0x10, 0xe0,
> > +       0xa8, 0x53, 0x80, 0xef, 0x1a, 0xee, 0x17, 0xc2, 0x03, 0x82, 0xf8, 0x63,
> > +       0x80, 0xef, 0x1b, 0x80, 0x0b, 0x8e, 0xd8, 0x63, 0x80, 0xef, 0x1b, 0x8c,
> > +       0x68, 0x63, 0x80, 0xef, 0x6b, 0x80, 0x0b, 0x92, 0x65, 0x54, 0x14, 0xe0,
> > +       0x08, 0x65, 0x84, 0xef, 0x68, 0x63, 0x80, 0xef, 0x7b, 0x80, 0x0b, 0x8c,
> > +       0xa8, 0x64, 0x84, 0xef, 0x08, 0x63, 0x80, 0xef, 0x14, 0xe8, 0x46, 0x44,
> > +       0x94, 0xe1, 0x24, 0x88, 0x4a, 0x4e, 0x04, 0xe0, 0x14, 0xea, 0x1a, 0x04,
> > +       0x08, 0xe0, 0x0a, 0x40, 0x84, 0xed, 0x0c, 0x04, 0x00, 0xe2, 0x4a, 0x40,
> > +       0x04, 0xe0, 0x19, 0x16, 0xc0, 0xe0, 0x0a, 0x40, 0x84, 0xed, 0x21, 0x54,
> > +       0x60, 0xe0, 0x0c, 0x04, 0x00, 0xe2, 0x1b, 0xa5, 0x0e, 0xea, 0x01, 0x89,
> > +       0x21, 0x54, 0x64, 0xe0, 0x7e, 0xe8, 0x65, 0x82, 0x1b, 0xa7, 0x26, 0x00,
> > +       0x00, 0x80, 0xa5, 0x82, 0x1b, 0xa9, 0x65, 0x82, 0x1b, 0xa3, 0x01, 0x85,
> > +       0x16, 0x00, 0x00, 0xc0, 0x01, 0x54, 0x04, 0xf8, 0x06, 0xaa, 0x01, 0x83,
> > +       0x06, 0xa8, 0x65, 0x81, 0x06, 0xa8, 0x01, 0x54, 0x04, 0xf8, 0x01, 0x83,
> > +       0x06, 0xaa, 0x09, 0x14, 0x18, 0xf8, 0x0b, 0xa1, 0x05, 0x84, 0xc6, 0x42,
> > +       0xd4, 0xe0, 0x14, 0x84, 0x01, 0x83, 0x01, 0x54, 0x60, 0xe0, 0x01, 0x54,
> > +       0x64, 0xe0, 0x0b, 0x02, 0x90, 0xe0, 0x10, 0x02, 0x90, 0xe5, 0x01, 0x54,
> > +       0x88, 0xe0, 0xb5, 0x81, 0xc6, 0x40, 0xd4, 0xe0, 0x14, 0x80, 0x0b, 0x02,
> > +       0xe0, 0xe4, 0x10, 0x02, 0x31, 0x66, 0x02, 0xc0, 0x01, 0x54, 0x88, 0xe0,
> > +       0x1a, 0x84, 0x29, 0x14, 0x10, 0xe0, 0x1c, 0xaa, 0x2b, 0xa1, 0xf5, 0x82,
> > +       0x25, 0x14, 0x10, 0xf8, 0x2b, 0x04, 0xa8, 0xe0, 0x20, 0x44, 0x0d, 0x70,
> > +       0x03, 0xc0, 0x2b, 0xa1, 0x04, 0x00, 0x80, 0x9a, 0x02, 0x40, 0x84, 0x90,
> > +       0x03, 0x54, 0x04, 0x80, 0x4c, 0x0c, 0x7c, 0xf2, 0x93, 0xdd, 0x00, 0x00,
> > +       0x02, 0xa9, 0x00, 0x00, 0x64, 0x4a, 0x40, 0x00, 0x08, 0x2d, 0x58, 0xe0,
> > +       0xa8, 0x98, 0x40, 0x00, 0x28, 0x07, 0x34, 0xe0, 0x05, 0xb9, 0x00, 0x00,
> > +       0x28, 0x00, 0x41, 0x05, 0x88, 0x00, 0x41, 0x3c, 0x98, 0x00, 0x41, 0x52,
> > +       0x04, 0x01, 0x41, 0x79, 0x3c, 0x01, 0x41, 0x6a, 0x3d, 0xfe, 0x00, 0x00,
> > +};
> > +
> > +static const char * const vgxy61_test_pattern_menu[] = {
> > +       "Disabled",
> > +       "Solid",
> > +       "Colorbar",
> > +       "Gradbar",
> > +       "Hgrey",
> > +       "Vgrey",
> > +       "Dgrey",
> > +       "PN28",
> > +};
> > +
> > +static const char * const vgxy61_hdr_modes[] = {
> > +       "HDR linearize",
> > +       "HDR substraction",
> > +       "no HDR",
> > +};
> > +
> > +/* Regulator supplies */
> > +static const char * const vgxy61_supply_name[] = {
> > +       "VCORE",
> > +       "VDDIO",
> > +       "VANA",
> > +};
> > +
> > +static const s64 link_freq[] = {
> > +       /*
> > +        * MIPI output freq is 804Mhz / 2, as it uses both rising edge and falling edges to send
> > +        * data
> > +        */
> > +       402000000ULL
> > +};
> > +
> > +#define VGXY61_NUM_SUPPLIES            ARRAY_SIZE(vgxy61_supply_name)
> > +
> > +/* Macro to convert index to 8.8 fixed point gain */
> > +#define I2FP(i)                                ((u32)(8192.0 / (32 - (i))))
> > +/* Array of possibles analog gains in 8.8 fixed point */
> > +static const u16 analog_gains[] = {
> > +       I2FP(0), I2FP(1), I2FP(2), I2FP(3), I2FP(4), I2FP(5), I2FP(6), I2FP(7), I2FP(8), I2FP(9),
> > +       I2FP(10), I2FP(11), I2FP(12), I2FP(13), I2FP(14), I2FP(15), I2FP(16),
> > +};
> > +
> > +enum bin_mode {
> > +       BIN_MODE_NORMAL,
> > +       BIN_MODE_DIGITAL_X2,
> > +       BIN_MODE_DIGITAL_X4,
> > +       BIN_MODE_ANALOG_SUB_X2,
> > +       BIN_MODE_ANALOG_SUB_X4,
> > +};
> > +
> > +enum hdr {
> > +       HDR_LINEAR,
> > +       HDR_SUB,
> > +       NO_HDR,
> > +};
> > +
> > +enum strobe_modes {
> > +       STROBE_DISABLED,
> > +       STROBE_LONG,
> > +       STROBE_ENABLED,
> > +};
> > +
> > +struct vgxy61_mode_info {
> > +       u32 width;
> > +       u32 height;
> > +       enum bin_mode bin_mode;
> > +};
> > +
> > +static const u32 vgxy61_supported_codes[] = {
> > +       MEDIA_BUS_FMT_SGBRG8_1X8,
> > +       MEDIA_BUS_FMT_SGBRG10_1X10,
> > +       MEDIA_BUS_FMT_SGBRG12_1X12,
> > +       MEDIA_BUS_FMT_SGBRG14_1X14,
> > +       MEDIA_BUS_FMT_SGBRG16_1X16
> > +};
> > +
> > +const int vgx761_sensor_frame_rates[] = {75, 60, 30, 15, 10, 5, 2};
> > +const int vgx661_sensor_frame_rates[] = {60, 30, 15, 10, 5, 2};
> > +
> > +static const struct vgxy61_mode_info vgx661_mode_data[] = {
> > +       {1464, 1104, BIN_MODE_NORMAL},
> > +       {1280,  720, BIN_MODE_NORMAL},
> > +       { 640,  480, BIN_MODE_DIGITAL_X2},
> > +       { 320,  240, BIN_MODE_DIGITAL_X4},
> > +};
> > +
> > +static const struct vgxy61_mode_info vgx761_mode_data[] = {
> > +       {1944, 1204, BIN_MODE_NORMAL},
> > +       {1920, 1080, BIN_MODE_NORMAL},
> > +       {1280,  720, BIN_MODE_NORMAL},
> > +       { 640,  480, BIN_MODE_DIGITAL_X2},
> > +       { 320,  240, BIN_MODE_DIGITAL_X4},
> > +};
> > +
> > +struct gpios_ctrls {
> > +       struct v4l2_ctrl *long_start;
> > +       struct v4l2_ctrl *long_end;
> > +       struct v4l2_ctrl *short_start;
> > +       struct v4l2_ctrl *short_end;
> > +};
> > +
> > +struct vgxy61_ctrls {
> > +       struct v4l2_ctrl_handler handler;
> > +       struct v4l2_ctrl *exposure;
> > +       struct v4l2_ctrl *analog_gain;
> > +       struct v4l2_ctrl *digital_gain;
> > +       struct gpios_ctrls gpios;
> > +       struct v4l2_ctrl *vflip;
> > +       struct v4l2_ctrl *hflip;
> > +       struct v4l2_ctrl *patgen;
> > +       struct v4l2_ctrl *hdr;
> > +       struct v4l2_ctrl *pixel_rate;
> > +       struct v4l2_ctrl *link_freq;

No need to store analog_gain, digital_gain, vflip, hflip, patgen, hdr,
or link_freq as they are never programmatically altered. The s_ctrl
handler is given the struct v4l2_ctrl, and where you need it again
you've copied the value into the main struct vgxy61_dev.

> > +       struct v4l2_ctrl *temp;
> > +};
> > +
> > +struct vgxy61_dev {
> > +       struct i2c_client *i2c_client;
> > +       struct v4l2_subdev sd;
> > +       struct media_pad pad;
> > +       struct regulator_bulk_data supplies[VGXY61_NUM_SUPPLIES];
> > +       struct gpio_desc *reset_gpio;
> > +       struct clk *xclk;
> > +       u32 clk_freq;
> > +       int sensor_width;
> > +       int sensor_height;
> > +       u16 oif_ctrl;
> > +       int nb_of_lane;
> > +       int data_rate_in_mbps;
> > +       int pclk;
> > +       u16 line_length;
> > +       int rot_term;
> > +       bool gpios_polarity;
> > +       bool slave_mode;
> > +       /* Lock to protect all members below */
> > +       struct mutex lock;
> > +       struct vgxy61_ctrls ctrls;
> > +       bool streaming;
> > +       struct v4l2_mbus_framefmt fmt;
> > +       const struct vgxy61_mode_info *sensor_modes;
> > +       int sensor_modes_nb;
> > +       const struct vgxy61_mode_info *current_mode;
> > +       const int *sensor_rates;
> > +       int sensor_rates_nb;
> > +       struct v4l2_fract frame_interval;
> > +       bool hflip;
> > +       bool vflip;
> > +       enum hdr hdr;
> > +       int expo_long;
> > +       int expo_short;
> > +};
> > +
> > +static u8 get_bpp_by_code(__u32 code)
> > +{
> > +       switch (code) {
> > +       case MEDIA_BUS_FMT_SGBRG8_1X8:
> > +               return 8;
> > +       case MEDIA_BUS_FMT_SGBRG10_1X10:
> > +               return 10;
> > +       case MEDIA_BUS_FMT_SGBRG12_1X12:
> > +               return 12;
> > +       case MEDIA_BUS_FMT_SGBRG14_1X14:
> > +               return 14;
> > +       case MEDIA_BUS_FMT_SGBRG16_1X16:
> > +               return 16;
> > +       default:
> > +               /* Should never happen */
> > +               WARN(1, "Unsupported code %d. default to 8 bpp", code);
> > +               return 8;
> > +       }
> > +}
> > +
> > +static u8 get_data_type_by_code(__u32 code)
> > +{
> > +       switch (code) {
> > +       case MEDIA_BUS_FMT_SGBRG8_1X8:
> > +               return MIPI_CSI2_DT_RAW8;
> > +       case MEDIA_BUS_FMT_SGBRG10_1X10:
> > +               return MIPI_CSI2_DT_RAW10;
> > +       case MEDIA_BUS_FMT_SGBRG12_1X12:
> > +               return MIPI_CSI2_DT_RAW12;
> > +       case MEDIA_BUS_FMT_SGBRG14_1X14:
> > +               return MIPI_CSI2_DT_RAW14;
> > +       case MEDIA_BUS_FMT_SGBRG16_1X16:
> > +               return MIPI_CSI2_DT_RAW16;
> > +       default:
> > +               /* Should never happen */
> > +               WARN(1, "Unsupported code %d. default to MIPI_CSI2_DT_RAW8 data type", code);
> > +               return MIPI_CSI2_DT_RAW8;
> > +       }
> > +}
> > +
> > +static void compute_pll_parameters_by_freq(u32 freq, unsigned int *prediv, unsigned int *mult)
> > +{
> > +       const unsigned int predivs[] = {1, 2, 4};
> > +       int i;
> > +
> > +       /*
> > +        * Freq range is [6Mhz-27Mhz] already checked.
> > +        * Output of divider should be in [6Mhz-12Mhz[.
> > +        */
> > +       for (i = 0; i < ARRAY_SIZE(predivs); i++) {
> > +               *prediv = predivs[i];
> > +               if (freq / *prediv < 12 * HZ_PER_MHZ)
> > +                       break;
> > +       }
> > +       WARN_ON(i == ARRAY_SIZE(predivs));
> > +
> > +       /*
> > +        * Target freq is 804Mhz. Don't change this as it will impact image quality.
> > +        */
> > +       *mult = ((804 * HZ_PER_MHZ) * (*prediv) + freq / 2) / freq;
> > +}
> > +
> > +static s32 get_pixel_rate(struct vgxy61_dev *sensor)
> > +{
> > +       return div64_u64((u64)sensor->data_rate_in_mbps * sensor->nb_of_lane,
> > +                        get_bpp_by_code(sensor->fmt.code));
> > +}
> > +
> > +static inline struct vgxy61_dev *to_vgxy61_dev(struct v4l2_subdev *sd)
> > +{
> > +       return container_of(sd, struct vgxy61_dev, sd);
> > +}
> > +
> > +static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl)
> > +{
> > +       return &container_of(ctrl->handler, struct vgxy61_dev, ctrls.handler)->sd;
> > +}
> > +
> > +static int get_chunk_size(struct vgxy61_dev *sensor)
> > +{
> > +       struct i2c_adapter *adapter = sensor->i2c_client->adapter;
> > +       int max_write_len = WRITE_MULTIPLE_CHUNK_MAX;
> > +
> > +       if (adapter->quirks && adapter->quirks->max_write_len)
> > +               max_write_len = adapter->quirks->max_write_len - 2;
> > +
> > +       max_write_len = min(max_write_len, WRITE_MULTIPLE_CHUNK_MAX);
> > +
> > +       return max(max_write_len, 1);
> > +}
> > +
> > +static int vgxy61_read_multiple(struct vgxy61_dev *sensor, u16 reg, u8 *val, int len)
> > +{
> > +       struct i2c_client *client = sensor->i2c_client;
> > +       struct i2c_msg msg[2];
> > +       u8 buf[2];
> > +       int ret;
> > +
> > +       buf[0] = reg >> 8;
> > +       buf[1] = reg & 0xff;
> > +
> > +       msg[0].addr = client->addr;
> > +       msg[0].flags = client->flags;
> > +       msg[0].buf = buf;
> > +       msg[0].len = sizeof(buf);
> > +
> > +       msg[1].addr = client->addr;
> > +       msg[1].flags = client->flags | I2C_M_RD;
> > +       msg[1].buf = val;
> > +       msg[1].len = len;
> > +
> > +       ret = i2c_transfer(client->adapter, msg, 2);
> > +       if (ret < 0) {
> > +               dev_dbg(&client->dev, "%s: %x i2c_transfer, reg: %x => %d\n", __func__,
> > +                       client->addr, reg, ret);
> > +               return ret;
> > +       }
> > +
> > +       return 0;
> > +}
> > +
> > +static inline int vgxy61_read_reg(struct vgxy61_dev *sensor, u16 reg, u8 *val)
> > +{
> > +       return vgxy61_read_multiple(sensor, reg, val, sizeof(*val));
> > +}
> > +
> > +static inline int vgxy61_read_reg16(struct vgxy61_dev *sensor, u16 reg, u16 *val)
> > +{
> > +       return vgxy61_read_multiple(sensor, reg, (u8 *)val, sizeof(*val));
> > +}
> > +
> > +static int vgxy61_write_multiple(struct vgxy61_dev *sensor, u16 reg, const u8 *data, int len)
> > +{
> > +       struct i2c_client *client = sensor->i2c_client;
> > +       struct i2c_msg msg;
> > +       u8 buf[WRITE_MULTIPLE_CHUNK_MAX + 2];
> > +       int i;
> > +       int ret;
> > +
> > +       if (len > WRITE_MULTIPLE_CHUNK_MAX)
> > +               return -EINVAL;
> > +       buf[0] = reg >> 8;
> > +       buf[1] = reg & 0xff;
> > +       for (i = 0; i < len; i++)
> > +               buf[i + 2] = data[i];
> > +
> > +       msg.addr = client->addr;
> > +       msg.flags = client->flags;
> > +       msg.buf = buf;
> > +       msg.len = len + 2;
> > +
> > +       ret = i2c_transfer(client->adapter, &msg, 1);
> > +       if (ret < 0) {
> > +               dev_dbg(&client->dev, "%s: i2c_transfer, reg: %x => %d\n", __func__, reg, ret);
> > +               return ret;
> > +       }
> > +
> > +       return 0;
> > +}
> > +
> > +static int vgxy61_write_array(struct vgxy61_dev *sensor, u16 reg, int nb, const u8 *array)
> > +{
> > +       const int chunk_size = get_chunk_size(sensor);
> > +       int ret;
> > +       int sz;
> > +
> > +       while (nb) {
> > +               sz = min(nb, chunk_size);
> > +               ret = vgxy61_write_multiple(sensor, reg, array, sz);
> > +               if (ret < 0)
> > +                       return ret;
> > +               nb -= sz;
> > +               reg += sz;
> > +               array += sz;
> > +       }
> > +
> > +       return 0;
> > +}
> > +
> > +static inline int vgxy61_write_reg(struct vgxy61_dev *sensor, u16 reg, u8 val)
> > +{
> > +       return vgxy61_write_multiple(sensor, reg, &val, sizeof(val));
> > +}
> > +
> > +static inline int vgxy61_write_reg16(struct vgxy61_dev *sensor, u16 reg, u16 val)
> > +{
> > +       return vgxy61_write_multiple(sensor, reg, (u8 *)&val, sizeof(val));
> > +}
> > +
> > +static inline int vgxy61_write_reg32(struct vgxy61_dev *sensor, u16 reg, u32 val)
> > +{
> > +       return vgxy61_write_multiple(sensor, reg, (u8 *)&val, sizeof(val));
> > +}
> > +
> > +static int vgxy61_poll_reg(struct vgxy61_dev *sensor, u16 reg, u8 poll_val)
> > +{
> > +       const int loop_delay_ms = 10;
> > +       const int timeout_ms = 500;
> > +       u8 val;
> > +       int ret, timeout;
> > +
> > +       timeout = read_poll_timeout(vgxy61_read_reg, ret, ((ret != 0) || (val == poll_val)),
> > +                                   loop_delay_ms * US_PER_MS, timeout_ms * US_PER_MS, false,
> > +                                   sensor, reg, &val);
> > +       if (timeout)
> > +               return timeout;
> > +
> > +       return ret;
> > +}
> > +
> > +static int vgxy61_wait_state(struct vgxy61_dev *sensor, int state)
> > +{
> > +       return vgxy61_poll_reg(sensor, DEVICE_SYSTEM_FSM, state);
> > +}
> > +
> > +static int vgxy61_check_bw(struct vgxy61_dev *sensor)
> > +{
> > +       struct i2c_client *client = sensor->i2c_client;
> > +       /* Correction factor for time required between 2 lines */
> > +       const int mipi_margin = 1056;
> > +       int binning_scale = 1 << sensor->current_mode->bin_mode;
> > +       int bpp = get_bpp_by_code(sensor->fmt.code);
> > +       int max_bit_per_line;
> > +       int bit_per_line;
> > +       u64 line_rate;
> > +
> > +       line_rate = sensor->nb_of_lane * (u64)sensor->data_rate_in_mbps * sensor->line_length;
> > +       max_bit_per_line = div64_u64(line_rate, sensor->pclk) - mipi_margin;
> > +       bit_per_line = (bpp * sensor->current_mode->width) / binning_scale;
> > +
> > +       dev_dbg(&client->dev, "max_bit_per_line = %d\n", max_bit_per_line);
> > +       dev_dbg(&client->dev, "required bit_per_line = %d\n", bit_per_line);
> > +
> > +       return bit_per_line > max_bit_per_line ? -EINVAL : 0;
> > +}
> > +
> > +static int apply_exposure(struct vgxy61_dev *sensor)
> > +{
> > +       struct i2c_client *client = sensor->i2c_client;
> > +       int ret;
> > +
> > +        /* We first set expo to zero to avoid forbidden parameters couple */
> > +       ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_SHORT, 0);
> > +       if (ret)
> > +               return ret;
> > +       ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_LONG, sensor->expo_long);
> > +       if (ret)
> > +               return ret;
> > +       ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_SHORT, sensor->expo_short);
> > +       if (ret)
> > +               return ret;
> > +
> > +       dev_dbg(&client->dev, "%s applied expo %d (short: %d)\n", __func__,
> > +               sensor->expo_long, sensor->expo_short);
> > +
> > +       return 0;
> > +}
> > +
> > +static int set_frame_rate(struct vgxy61_dev *sensor)
> > +{
> > +       u16 frame_length;
> > +
> > +       frame_length = sensor->pclk / (sensor->line_length * sensor->frame_interval.denominator);
> > +
> > +       return vgxy61_write_reg16(sensor, DEVICE_FRAME_LENGTH, frame_length);
> > +}
> > +
> > +static int vgxy61_get_regulators(struct vgxy61_dev *sensor)
> > +{
> > +       int i;
> > +
> > +       for (i = 0; i < VGXY61_NUM_SUPPLIES; i++)
> > +               sensor->supplies[i].supply = vgxy61_supply_name[i];
> > +
> > +       return devm_regulator_bulk_get(&sensor->i2c_client->dev, VGXY61_NUM_SUPPLIES,
> > +                                      sensor->supplies);
> > +}
> > +
> > +static int vgxy61_apply_reset(struct vgxy61_dev *sensor)
> > +{
> > +       struct i2c_client *client = sensor->i2c_client;
> > +
> > +       dev_dbg(&client->dev, "%s applied reset\n", __func__);
> > +       gpiod_set_value_cansleep(sensor->reset_gpio, 0);
> > +       usleep_range(5000, 10000);
> > +       gpiod_set_value_cansleep(sensor->reset_gpio, 1);
> > +       usleep_range(5000, 10000);
> > +       gpiod_set_value_cansleep(sensor->reset_gpio, 0);
> > +       usleep_range(40000, 100000);
> > +       return vgxy61_wait_state(sensor, SW_STBY);
> > +}
> > +
> > +static int vgxy61_try_fmt_internal(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *fmt,
> > +                                  const struct vgxy61_mode_info **new_mode)
> > +{
> > +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> > +       const struct vgxy61_mode_info *mode = sensor->sensor_modes;
> > +       unsigned int index;
> > +
> > +       /* Select code */
> > +       for (index = 0; index < ARRAY_SIZE(vgxy61_supported_codes); index++) {
> > +               if (vgxy61_supported_codes[index] == fmt->code)
> > +                       break;
> > +       }
> > +       if (index == ARRAY_SIZE(vgxy61_supported_codes))
> > +               index = 0;
> > +
> > +       /* Select size */
> > +       do {
> > +               if (mode->width <= fmt->width && mode->height <= fmt->height)
> > +                       break;
> > +       } while ((++mode)->width);
> > +       if (!mode->width)
> > +               mode--;

Use v4l2_find_nearest_size?

> > +
> > +       *new_mode = mode;
> > +       fmt->code = vgxy61_supported_codes[index];
> > +       fmt->width = mode->width;
> > +       fmt->height = mode->height;
> > +       fmt->colorspace = V4L2_COLORSPACE_SRGB;

Is it? Most Bayer sensors are V4L2_COLORSPACE_RAW.
What about ycbcr_enc, quantization, and xfer_func?

> > +       fmt->field = V4L2_FIELD_NONE;
> > +
> > +       return 0;
> > +}
> > +
> > +static int vgxy61_stream_enable(struct vgxy61_dev *sensor)
> > +{
> > +       int center_x = sensor->sensor_width / 2;
> > +       int center_y = sensor->sensor_height / 2;
> > +       int scale = 1 << sensor->current_mode->bin_mode;
> > +       int width = sensor->current_mode->width * scale;
> > +       int height = sensor->current_mode->height * scale;
> > +       int ret;
> > +
> > +       ret = vgxy61_check_bw(sensor);
> > +       if (ret)
> > +               return ret;
> > +
> > +       /* Configure sensor */
> > +       ret = vgxy61_write_reg(sensor, DEVICE_FORMAT_CTRL, get_bpp_by_code(sensor->fmt.code));
> > +       if (ret)
> > +               return ret;
> > +       ret = vgxy61_write_reg(sensor, DEVICE_OIF_ROI0_CTRL,
> > +                              get_data_type_by_code(sensor->fmt.code));
> > +       if (ret)
> > +               return ret;
> > +
> > +       ret = vgxy61_write_reg(sensor, DEVICE_READOUT_CTRL, sensor->current_mode->bin_mode);
> > +       if (ret)
> > +               return ret;
> > +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_H, center_x - width / 2);
> > +       if (ret)
> > +               return ret;
> > +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_H, center_x + width / 2 - 1);
> > +       if (ret)
> > +               return ret;
> > +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_V, center_y - height / 2);
> > +       if (ret)
> > +               return ret;
> > +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_V, center_y + height / 2 - 1);
> > +       if (ret)
> > +               return ret;

Use the selection API to allow the crop region to be configured?
Avoids only supporting centre cropping.

> > +
> > +       ret = set_frame_rate(sensor);
> > +       if (ret)
> > +               return ret;
> > +
> > +       ret = apply_exposure(sensor);
> > +       if (ret)
> > +               return ret;
> > +
> > +       /* Start streaming */
> > +       ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_START_STREAMING);
> > +       if (ret)
> > +               return ret;
> > +
> > +       ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST);
> > +       if (ret)
> > +               return ret;
> > +
> > +       ret = vgxy61_wait_state(sensor, STREAMING);
> > +       if (ret)
> > +               return ret;
> > +
> > +       return 0;
> > +}
> > +
> > +static int vgxy61_stream_disable(struct vgxy61_dev *sensor)
> > +{
> > +       int ret;
> > +
> > +       ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_STOP_STREAMING);
> > +       if (ret)
> > +               return ret;
> > +
> > +       ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST);
> > +       if (ret)
> > +               return ret;
> > +
> > +       ret = vgxy61_wait_state(sensor, SW_STBY);
> > +       if (ret)
> > +               return ret;
> > +
> > +       return 0;
> > +}
> > +
> > +static int vgxy61_s_stream(struct v4l2_subdev *sd, int enable)
> > +{
> > +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> > +       struct i2c_client *client = sensor->i2c_client;
> > +       int ret = 0;
> > +
> > +       mutex_lock(&sensor->lock);
> > +       dev_dbg(&client->dev, "%s : requested %d / current = %d\n", __func__, enable,
> > +               sensor->streaming);
> > +       if (sensor->streaming == enable)
> > +               goto out;
> > +
> > +       ret = enable ? vgxy61_stream_enable(sensor) : vgxy61_stream_disable(sensor);
> > +       if (!ret)
> > +               sensor->streaming = enable;
> > +
> > +out:
> > +       dev_dbg(&client->dev, "%s current now = %d / %d\n", __func__, sensor->streaming, ret);
> > +       mutex_unlock(&sensor->lock);
> > +
> > +       return ret;
> > +}
> > +
> > +static int vgxy61_g_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *fi)
> > +{
> > +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> > +
> > +       mutex_lock(&sensor->lock);
> > +       fi->interval = sensor->frame_interval;
> > +       mutex_unlock(&sensor->lock);
> > +
> > +       return 0;
> > +}
> > +
> > +static int vgxy61_s_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *fi)
> > +{
> > +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> > +       struct i2c_client *client = sensor->i2c_client;
> > +       u64 req_int, err, min_err = ~0ULL;
> > +       u64 test_int;
> > +       int i = 0;
> > +       int ret;
> > +
> > +       if (fi->interval.denominator == 0)
> > +               return -EINVAL;
> > +
> > +       mutex_lock(&sensor->lock);
> > +
> > +       if (sensor->streaming) {
> > +               ret = -EBUSY;
> > +               goto out;
> > +       }
> > +
> > +       dev_dbg(&client->dev, "%s request %d/%d\n", __func__,
> > +               fi->interval.numerator, fi->interval.denominator);
> > +       /* Find nearest period */
> > +       req_int = div64_u64((u64)(fi->interval.numerator * 10000), fi->interval.denominator);
> > +       for (i = 0; i < sensor->sensor_rates_nb; i++) {
> > +               test_int = div64_u64((u64)10000, sensor->sensor_rates[i]);
> > +               err = abs(test_int - req_int);
> > +               if (err < min_err) {
> > +                       fi->interval.numerator = 1;
> > +                       fi->interval.denominator = sensor->sensor_rates[i];
> > +                       min_err = err;
> > +               }
> > +       }
> > +       sensor->frame_interval = fi->interval;
> > +       dev_dbg(&client->dev, "%s set     %d/%d\n", __func__,
> > +               fi->interval.numerator, fi->interval.denominator);
> > +
> > +       ret = 0;
> > +out:
> > +       mutex_unlock(&sensor->lock);
> > +
> > +       return ret;
> > +}
> > +
> > +static int vgxy61_enum_mbus_code(struct v4l2_subdev *sd,
> > +                                struct v4l2_subdev_state *sd_state,
> > +                                struct v4l2_subdev_mbus_code_enum *code)
> > +{
> > +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> > +       struct i2c_client *client = sensor->i2c_client;
> > +
> > +       dev_dbg(&client->dev, "%s probe index %d\n", __func__, code->index);
> > +       if (code->index >= ARRAY_SIZE(vgxy61_supported_codes))
> > +               return -EINVAL;
> > +
> > +       code->code = vgxy61_supported_codes[code->index];
> > +
> > +       return 0;
> > +}
> > +
> > +static int vgxy61_get_fmt(struct v4l2_subdev *sd,
> > +                         struct v4l2_subdev_state *sd_state,
> > +                         struct v4l2_subdev_format *format)
> > +{
> > +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> > +       struct i2c_client *client = sensor->i2c_client;
> > +       struct v4l2_mbus_framefmt *fmt;
> > +
> > +       dev_dbg(&client->dev, "%s probe %d\n", __func__, format->pad);
> > +       dev_dbg(&client->dev, "%s %dx%d\n", __func__, format->format.width, format->format.height);
> > +
> > +       mutex_lock(&sensor->lock);
> > +
> > +       if (format->which == V4L2_SUBDEV_FORMAT_TRY)
> > +               fmt = v4l2_subdev_get_try_format(&sensor->sd, sd_state, format->pad);
> > +       else
> > +               fmt = &sensor->fmt;
> > +
> > +       format->format = *fmt;
> > +
> > +       mutex_unlock(&sensor->lock);
> > +
> > +       return 0;
> > +}
> > +
> > +static int vgxy61_set_fmt(struct v4l2_subdev *sd,
> > +                         struct v4l2_subdev_state *sd_state,
> > +                         struct v4l2_subdev_format *format)
> > +{
> > +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> > +       struct i2c_client *client = sensor->i2c_client;
> > +       const struct vgxy61_mode_info *new_mode;
> > +       struct v4l2_mbus_framefmt *fmt;
> > +       int ret;
> > +
> > +       dev_dbg(&client->dev, "%s probe %d\n", __func__, format->pad);
> > +       dev_dbg(&client->dev, "%s %dx%d\n", __func__, format->format.width, format->format.height);
> > +
> > +       mutex_lock(&sensor->lock);
> > +
> > +       if (sensor->streaming) {
> > +               ret = -EBUSY;
> > +               goto out;
> > +       }
> > +
> > +       /* Find best format */
> > +       ret = vgxy61_try_fmt_internal(sd, &format->format, &new_mode);
> > +       if (ret)
> > +               goto out;
> > +
> > +       if (format->which == V4L2_SUBDEV_FORMAT_TRY)
> > +               fmt = v4l2_subdev_get_try_format(sd, sd_state, 0);
> > +       else
> > +               fmt = &sensor->fmt;
> > +       *fmt = format->format;
> > +       sensor->current_mode = new_mode;
> > +       /* Update pixel rate control to reflect new mode */
> > +       __v4l2_ctrl_s_ctrl_int64(sensor->ctrls.pixel_rate, get_pixel_rate(sensor));
> > +
> > +out:
> > +       mutex_unlock(&sensor->lock);
> > +
> > +       return ret;
> > +}
> > +
> > +static int vgxy61_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state,
> > +                                 struct v4l2_subdev_frame_size_enum *fse)
> > +{
> > +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> > +       struct i2c_client *client = sensor->i2c_client;
> > +
> > +       dev_dbg(&client->dev, "%s for index %d\n", __func__, fse->index);
> > +       if (fse->index >= sensor->sensor_modes_nb)
> > +               return -EINVAL;
> > +
> > +       fse->min_width = sensor->sensor_modes[fse->index].width;
> > +       fse->max_width = fse->min_width;
> > +       fse->min_height = sensor->sensor_modes[fse->index].height;
> > +       fse->max_height = fse->min_height;
> > +
> > +       return 0;
> > +}
> > +
> > +static int vgxy61_enum_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state,
> > +                                     struct v4l2_subdev_frame_interval_enum *fie)
> > +{
> > +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> > +       const struct vgxy61_mode_info *mode = sensor->sensor_modes;
> > +
> > +       if (fie->index >= sensor->sensor_rates_nb)
> > +               return -EINVAL;
> > +       do {
> > +               if (mode->width == fie->width && mode->height == fie->height)
> > +                       break;
> > +       } while ((++mode)->width);
> > +       if (!mode->width)
> > +               return -EINVAL;
> > +
> > +       fie->interval.numerator = 1;
> > +       fie->interval.denominator = sensor->sensor_rates[fie->index];
> > +
> > +       return 0;
> > +}
> > +
> > +static int vgxy61_update_analog_gain(struct vgxy61_dev *sensor, u32 target)
> > +{
> > +       struct i2c_client *client = sensor->i2c_client;
> > +       unsigned int idx;
> > +       int ret;
> > +
> > +       /* Find smallest analog gains which is above or equal to target gain */
> > +       for (idx = 0; idx < ARRAY_SIZE(analog_gains); idx++) {
> > +               if (analog_gains[idx] >= target)
> > +                       break;
> > +       }

Why? The units of V4L2_CID_ANALOGUE_GAIN are not specified, so more
typically it is a raw register write.


> > +       /* Cap to maximum gain if no matching gain found */
> > +       if (idx == ARRAY_SIZE(analog_gains))
> > +               idx--;
> > +
> > +       /* Apply gain */
> > +       ret = vgxy61_write_reg(sensor, DEVICE_ANALOG_GAIN, idx);
> > +       if (ret)
> > +               return ret;
> > +
> > +       dev_dbg(&client->dev, "Target analog gain: 0x%04x\n", target);
> > +       dev_dbg(&client->dev, "   Set analog gain: 0x%04x\n", analog_gains[idx]);
> > +
> > +       return 0;
> > +}
> > +
> > +static int vgxy61_update_digital_gain(struct vgxy61_dev *sensor, u32 target)
> > +{
> > +       struct i2c_client *client = sensor->i2c_client;
> > +       int ret;
> > +
> > +       /*
> > +        * For a monochrome version, configuring DIGITAL_GAIN_LONG_CH0 and
> > +        * DIGITAL_GAIN_SHORT_CH0 is enough to configure the gain of all
> > +        * four sub pixels.
> > +        */
> > +       ret = vgxy61_write_reg16(sensor, DEVICE_DIGITAL_GAIN_LONG, target);
> > +       if (ret)
> > +               return ret;
> > +       ret = vgxy61_write_reg16(sensor, DEVICE_DIGITAL_GAIN_SHORT, target);
> > +       if (ret)
> > +               return ret;
> > +
> > +       dev_dbg(&client->dev, "  Set digital gain: 0x%04x\n", target);
> > +
> > +       return 0;
> > +}
> > +
> > +static int vgxy61_update_patgen(struct vgxy61_dev *sensor, u32 index)
> > +{
> > +       u32 pattern = index <= 3 ? index : index + 12;
> > +       u32 reg;
> > +
> > +       reg = (pattern << 18) | (pattern << 4);
> > +       if (index)
> > +               reg |= (1 << 16) | 1;
> > +       return vgxy61_write_reg32(sensor, DEVICE_PATGEN_CTRL, reg);
> > +}
> > +
> > +static int vgxy61_update_gpiox_strobe_mode(struct vgxy61_dev *sensor, enum strobe_modes mode,
> > +                                          int idx)
> > +{
> > +       const u8 index2val[] = {0x0, 0x1, 0x3};
> > +       u16 reg;
> > +       int ret;
> > +
> > +       ret = vgxy61_read_reg16(sensor, DEVICE_SIGNALS_CTRL, &reg);
> > +       if (ret)
> > +               return ret;
> > +       reg &= ~(0xf << (idx * 4));
> > +       reg |= index2val[mode] << (idx * 4);
> > +
> > +       return vgxy61_write_reg16(sensor, DEVICE_SIGNALS_CTRL, reg);
> > +}
> > +
> > +static int vgxy61_update_gpios_strobe_mode(struct vgxy61_dev *sensor, enum hdr hdr)
> > +{
> > +       enum strobe_modes strobe_mode;
> > +       int i, ret;
> > +
> > +       switch (hdr) {
> > +       case HDR_LINEAR:
> > +               strobe_mode = STROBE_ENABLED;
> > +               break;
> > +       case HDR_SUB:
> > +       case NO_HDR:
> > +               strobe_mode = STROBE_LONG;
> > +               break;
> > +       default:
> > +               /* Should never happen */
> > +               WARN_ON(true);
> > +               break;
> > +       }
> > +
> > +       for (i = 0; i < NB_GPIOS; i++) {
> > +               ret = vgxy61_update_gpiox_strobe_mode(sensor, strobe_mode, i);
> > +               if (ret)
> > +                       return ret;
> > +       }
> > +
> > +       return 0;
> > +}
> > +
> > +static int vgxy61_update_gpios_strobe_polarity(struct vgxy61_dev *sensor, int polarity)
> > +{
> > +       struct i2c_client *client = sensor->i2c_client;
> > +       int ret;
> > +
> > +       if (sensor->streaming)
> > +               return -EBUSY;
> > +
> > +       dev_dbg(&client->dev, "setting gpios polarity: %d\n", polarity);
> > +
> > +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_0_CTRL, polarity << 1);
> > +       if (ret)
> > +               return ret;
> > +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_1_CTRL, polarity << 1);
> > +       if (ret)
> > +               return ret;
> > +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_2_CTRL, polarity << 1);
> > +       if (ret)
> > +               return ret;
> > +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_3_CTRL, polarity << 1);
> > +       if (ret)
> > +               return ret;
> > +       return vgxy61_write_reg(sensor, DEVICE_SIGNALS_POLARITY_CTRL, polarity);
> > +}
> > +
> > +static int find_max_long_exposure(struct vgxy61_dev *sensor, int frame_length, int short_expo_ratio)
> > +{
> > +       int first_rot_max_expo;
> > +       int second_rot_max_expo;
> > +
> > +       /*
> > +        * Apply first rule of thumb:
> > +        * frame_length < short_line_nb + sensor->sensor_height + sensor->rot_term
> > +        * with short_line_nb = long_line_nb / short_expo_ratio
> > +        */
> > +       first_rot_max_expo = ((frame_length - sensor->sensor_height - sensor->rot_term) *
> > +                            short_expo_ratio) - 1;
> > +
> > +       /*
> > +        * Apply second rule of thumb
> > +        * frame_length < short_line_nb + long_line_nb + VGXY61_EXPOS_ROT_TERM
> > +        * with short_line_nb = long_line_nb / short_expo_ratio
> > +        */
> > +       second_rot_max_expo = (((frame_length - VGXY61_EXPOS_ROT_TERM) * short_expo_ratio) /
> > +                             (short_expo_ratio + 1)) - 1;
> > +
> > +       /* Take the minimum of both rules */
> > +       return min(first_rot_max_expo, second_rot_max_expo);
> > +}
> > +
> > +static int vgxy61_update_exposure(struct vgxy61_dev *sensor, int long_expo_line_nb, enum hdr hdr,
> > +                                 bool clamp)
> > +{
> > +       struct i2c_client *client = sensor->i2c_client;
> > +       int max_long_expo;
> > +       int max_short_expo = 0;
> > +       int short_expo_line_nb = 0;
> > +       /* We use a constant ratio of 10 for linear HDR mode */
> > +       int hdr_linear_ratio = 10;
> > +       u16 frame_length;
> > +       int ret;
> > +
> > +       ret = vgxy61_read_reg16(sensor, DEVICE_FRAME_LENGTH, &frame_length);
> > +       if (ret)
> > +               return ret;
> > +
> > +       /* Long and short integration times must not be less than 10 lines */
> > +       long_expo_line_nb = max(10, long_expo_line_nb);
> > +       long_expo_line_nb = min_t(int, frame_length, long_expo_line_nb);
> > +
> > +       /* Compute short exposure according to hdr mode and long exposure */
> > +       switch (hdr) {
> > +       case HDR_LINEAR:
> > +               max_long_expo = find_max_long_exposure(sensor, frame_length, hdr_linear_ratio);
> > +               max_short_expo = (max_long_expo + (hdr_linear_ratio / 2)) / hdr_linear_ratio;
> > +               short_expo_line_nb = (long_expo_line_nb + (hdr_linear_ratio / 2)) /
> > +                                    hdr_linear_ratio;
> > +               break;
> > +       case HDR_SUB:
> > +               max_long_expo = find_max_long_exposure(sensor, frame_length, 1);
> > +               max_short_expo = max_long_expo;
> > +               short_expo_line_nb = long_expo_line_nb;
> > +               break;
> > +       case NO_HDR:
> > +               /*
> > +                * As short expo is 0 here, only the second rule of thumb applies, see
> > +                * find_max_long_exposure for more
> > +                */
> > +               max_long_expo = frame_length - VGXY61_EXPOS_ROT_TERM;
> > +               break;
> > +       default:
> > +               /* Should never happen */
> > +               WARN_ON(true);
> > +               break;
> > +       }
> > +
> > +       if (long_expo_line_nb > max_long_expo) {
> > +               if (!clamp) {
> > +                       dev_err(&client->dev, "Exposure %d too high (max for this hdr mode %d)\n",
> > +                               long_expo_line_nb, max_long_expo);
> > +                       return -EINVAL;
> > +               }
> > +               dev_warn(&client->dev, "Exposure %d too high for this hdr mode, clamping to %d\n",
> > +                        long_expo_line_nb, max_long_expo);
> > +               long_expo_line_nb = max_long_expo;
> > +               short_expo_line_nb = max_short_expo;
> > +       }
> > +
> > +       dev_dbg(&client->dev, "frame_length %d, long_expo_line_nb %d, short_expo_line_nb %d",
> > +               frame_length, long_expo_line_nb, short_expo_line_nb);
> > +
> > +       /* Apply exposure */
> > +       sensor->expo_long = long_expo_line_nb;
> > +       sensor->expo_short = short_expo_line_nb;
> > +
> > +       if (sensor->streaming)
> > +               return apply_exposure(sensor);
> > +       return 0;
> > +}
> > +
> > +static int vgxy61_update_hdr(struct vgxy61_dev *sensor, u32 index)
> > +{
> > +       const u8 index2val[] = {0x1, 0x4, 0xa};
> > +       int ret;
> > +
> > +       /*
> > +        * Short exposure changes according to HDR mode, do it first as it can
> > +        * violate sensors 'rule of thumbs' and therefore will require to change
> > +        * the long exposure
> > +        */
> > +       ret = vgxy61_update_exposure(sensor, sensor->expo_long, index, true);
> > +       if (ret)
> > +               return ret;
> > +
> > +       /* Update strobe mode according to HDR */
> > +       ret = vgxy61_update_gpios_strobe_mode(sensor, index);
> > +       if (ret)
> > +               return ret;
> > +
> > +       ret = vgxy61_write_reg(sensor, DEVICE_HDR_CTRL, index2val[index]);
> > +       if (ret)
> > +               return ret;
> > +       sensor->hdr = index;
> > +
> > +       return 0;
> > +}
> > +
> > +static int vgxy61_get_temp_stream_enabled(struct vgxy61_dev *sensor, int *temp)
> > +{
> > +       int ret;
> > +       u16 temperature;
> > +
> > +       ret = vgxy61_read_reg16(sensor, DEVICE_THSENS1_TEMPERATURE, &temperature);
> > +       if (ret)
> > +               return ret;
> > +
> > +       /* Temperature is expressed in Kelvin in Q10.2 fixed point format*/
> > +       temperature = (temperature & 0x0fff) >> 2;
> > +       temperature = kelvin_to_celsius(temperature);
> > +
> > +       *temp = temperature;
> > +
> > +       return 0;
> > +}
> > +
> > +static int vgxy61_get_temp_stream_disabled(struct vgxy61_dev *sensor, int *temp)
> > +{
> > +       int ret;
> > +
> > +       /* Device needs to be in standby mode if streaming is off */
> > +       ret = vgxy61_write_reg(sensor, DEVICE_STBY, STBY_REQ_TMP_READ);
> > +       if (ret)
> > +               return ret;
> > +       ret = vgxy61_poll_reg(sensor, DEVICE_STBY, STBY_NO_REQ);
> > +       if (ret)
> > +               return ret;
> > +
> > +       return vgxy61_get_temp_stream_enabled(sensor, temp);
> > +}
> > +
> > +static int vgxy61_get_temp(struct vgxy61_dev *sensor, int *temp)
> > +{
> > +       *temp = 0;
> > +       if (sensor->streaming)
> > +               return vgxy61_get_temp_stream_enabled(sensor, temp);
> > +       else
> > +               return vgxy61_get_temp_stream_disabled(sensor, temp);
> > +}
> > +
> > +static int vgxy61_s_ctrl(struct v4l2_ctrl *ctrl)
> > +{
> > +       struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
> > +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> > +       int ret;
> > +
> > +       switch (ctrl->id) {
> > +       case V4L2_CID_EXPOSURE:
> > +               ret = vgxy61_update_exposure(sensor, ctrl->val, sensor->hdr, false);
> > +               ctrl->val = sensor->expo_long;
> > +               break;
> > +       case V4L2_CID_ANALOGUE_GAIN:
> > +               ret = vgxy61_update_analog_gain(sensor, ctrl->val);
> > +               break;
> > +       case V4L2_CID_DIGITAL_GAIN:
> > +               ret = vgxy61_update_digital_gain(sensor, ctrl->val);
> > +               break;
> > +       case V4L2_CID_VFLIP:
> > +       case V4L2_CID_HFLIP:
> > +               if (sensor->streaming) {
> > +                       ret = -EBUSY;
> > +                       break;
> > +               }
> > +               if (ctrl->id == V4L2_CID_VFLIP)
> > +                       sensor->vflip = ctrl->val;
> > +               if (ctrl->id == V4L2_CID_HFLIP)
> > +                       sensor->hflip = ctrl->val;
> > +               ret = vgxy61_write_reg(sensor, DEVICE_ORIENTATION,
> > +                                      sensor->hflip | (sensor->vflip << 1));
> > +               break;
> > +       case V4L2_CID_TEST_PATTERN:
> > +               ret = vgxy61_update_patgen(sensor, ctrl->val);
> > +               break;
> > +       case V4L2_CID_HDR:
> > +               ret = vgxy61_update_hdr(sensor, ctrl->val);
> > +               break;
> > +       case V4L2_CID_GPIOS_STROBE_LONG_START_DELAY:
> > +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_LONG_START_DELAY, ctrl->val);
> > +               break;
> > +       case V4L2_CID_GPIOS_STROBE_LONG_END_DELAY:
> > +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_LONG_END_DELAY, ctrl->val);
> > +               break;
> > +       case V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY:
> > +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_SHORT_START_DELAY, ctrl->val);
> > +               break;
> > +       case V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY:
> > +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_SHORT_END_DELAY, ctrl->val);
> > +               break;
> > +       default:
> > +               ret = -EINVAL;
> > +               break;
> > +       }
> > +
> > +       return ret;
> > +}
> > +
> > +static int vgxy61_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
> > +{
> > +       struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
> > +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> > +       int temperature;
> > +       int ret;
> > +
> > +       switch (ctrl->id) {
> > +       case V4L2_CID_TEMPERATURE:
> > +               ret = vgxy61_get_temp(sensor, &temperature);
> > +               if (ret)
> > +                       break;
> > +               ret = __v4l2_ctrl_s_ctrl(ctrl, temperature);

Do you need to call __v4l2_ctrl_s_ctrl instead of just updating
ctrl->val? There is no s_ctrl handler for the control as you've
declared it as read only.

> > +               break;
> > +       default:
> > +               ret = -EINVAL;
> > +               break;
> > +       }
> > +
> > +       return ret;
> > +}
> > +
> > +static const struct v4l2_ctrl_ops vgxy61_ctrl_ops = {
> > +       .g_volatile_ctrl = vgxy61_g_volatile_ctrl,
> > +       .s_ctrl = vgxy61_s_ctrl,
> > +};
> > +
> > +static const struct v4l2_ctrl_config vgxy61_hdr_ctrl = {
> > +       .ops            = &vgxy61_ctrl_ops,
> > +       .id             = V4L2_CID_HDR,
> > +       .name           = "HDR mode",
> > +       .type           = V4L2_CTRL_TYPE_MENU,
> > +       .min            = 0,
> > +       .max            = ARRAY_SIZE(vgxy61_hdr_modes) - 1,
> > +       .def            = NO_HDR,
> > +       .qmenu          = vgxy61_hdr_modes,
> > +};
> > +
> > +static const struct v4l2_ctrl_config vgxy61_strobe_long_start_delay = {
> > +       .ops            = &vgxy61_ctrl_ops,
> > +       .id             = V4L2_CID_GPIOS_STROBE_LONG_START_DELAY,
> > +       .name           = "Long strobe mode start delay in lines",
> > +       .type           = V4L2_CTRL_TYPE_INTEGER,
> > +       .min            = -128,
> > +       .max            = 127,
> > +       .step           = 1,
> > +       .def            = 0,
> > +       .flags          = 0,
> > +};
> > +
> > +static const struct v4l2_ctrl_config vgxy61_strobe_long_end_delay = {
> > +       .ops            = &vgxy61_ctrl_ops,
> > +       .id             = V4L2_CID_GPIOS_STROBE_LONG_END_DELAY,
> > +       .name           = "Long strobe mode end delay in lines",
> > +       .type           = V4L2_CTRL_TYPE_INTEGER,
> > +       .min            = -128,
> > +       .max            = 127,
> > +       .step           = 1,
> > +       .def            = 0,
> > +       .flags          = 0,
> > +};
> > +
> > +static const struct v4l2_ctrl_config vgxy61_strobe_short_start_delay = {
> > +       .ops            = &vgxy61_ctrl_ops,
> > +       .id             = V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY,
> > +       .name           = "Short strobe mode start delay in lines",
> > +       .type           = V4L2_CTRL_TYPE_INTEGER,
> > +       .min            = -128,
> > +       .max            = 127,
> > +       .step           = 1,
> > +       .def            = 0,
> > +       .flags          = 0,
> > +};
> > +
> > +static const struct v4l2_ctrl_config vgxy61_strobe_short_end_delay = {
> > +       .ops            = &vgxy61_ctrl_ops,
> > +       .id             = V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY,
> > +       .name           = "Short strobe mode end delay in lines",
> > +       .type           = V4L2_CTRL_TYPE_INTEGER,
> > +       .min            = -128,
> > +       .max            = 127,
> > +       .step           = 1,
> > +       .def            = 0,
> > +       .flags          = 0,
> > +};
> > +
> > +static const struct v4l2_ctrl_config vgxy61_temp_ctrl = {
> > +       .ops            = &vgxy61_ctrl_ops,
> > +       .id             = V4L2_CID_TEMPERATURE,
> > +       .name           = "Temperature in °C",
> > +       .type           = V4L2_CTRL_TYPE_INTEGER,
> > +       .min            = -128,
> > +       .max            = 128,
> > +       .step           = 1,
> > +};
> > +
> > +static int vgxy61_init_controls(struct vgxy61_dev *sensor)
> > +{
> > +       const struct v4l2_ctrl_ops *ops = &vgxy61_ctrl_ops;
> > +       struct vgxy61_ctrls *ctrls = &sensor->ctrls;
> > +       struct v4l2_ctrl_handler *hdl = &sensor->ctrls.handler;
> > +       int ret;
> > +
> > +       v4l2_ctrl_handler_init(hdl, 16);
> > +       /* We can use our own mutex for the ctrl lock */
> > +       hdl->lock = &sensor->lock;
> > +       ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, 10, 0xffff, 1,
> > +                                           sensor->expo_long);
> > +       /* This is 8.8 fixed point value */
> > +       ctrls->analog_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, 0x3fff, 1,
> > +                                              0x0);
> > +       /* This is 8.8 fixed point value */
> > +       ctrls->digital_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 0, 0xfff, 1,
> > +                                               0x100);
> > +       ctrls->vflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, sensor->vflip);
> > +       ctrls->hflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, sensor->hflip);
> > +       ctrls->patgen = v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN,
> > +                                                    ARRAY_SIZE(vgxy61_test_pattern_menu) - 1,
> > +                                                    0, 0, vgxy61_test_pattern_menu);
> > +       ctrls->hdr = v4l2_ctrl_new_custom(hdl, &vgxy61_hdr_ctrl, NULL);
> > +       ctrls->pixel_rate = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1,
> > +                                             get_pixel_rate(sensor));
> > +       ctrls->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> > +       ctrls->link_freq = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ,
> > +                                                 ARRAY_SIZE(link_freq) - 1, 0, link_freq);
> > +       ctrls->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> > +       /* Gpios ctrls */
> > +       ctrls->gpios.long_start = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_long_start_delay, NULL);
> > +       ctrls->gpios.long_end = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_long_end_delay, NULL);
> > +       ctrls->gpios.short_start = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_short_start_delay,
> > +                                                       NULL);
> > +       ctrls->gpios.short_end = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_short_end_delay, NULL);
> > +       /* Temperature ctrl */
> > +       ctrls->temp = v4l2_ctrl_new_custom(hdl, &vgxy61_temp_ctrl, NULL);
> > +       ctrls->temp->flags |= V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY;
> > +
> > +       if (hdl->error) {
> > +               ret = hdl->error;
> > +               goto free_ctrls;
> > +       }
> > +
> > +       sensor->sd.ctrl_handler = hdl;
> > +       return 0;
> > +
> > +free_ctrls:
> > +       v4l2_ctrl_handler_free(hdl);
> > +       return ret;
> > +}
> > +
> > +static const struct v4l2_subdev_core_ops vgxy61_core_ops = {
> > +};
> > +
> > +static const struct v4l2_subdev_video_ops vgxy61_video_ops = {
> > +       .s_stream = vgxy61_s_stream,
> > +       .g_frame_interval = vgxy61_g_frame_interval,
> > +       .s_frame_interval = vgxy61_s_frame_interval,
> > +};
> > +
> > +static const struct v4l2_subdev_pad_ops vgxy61_pad_ops = {
> > +       .enum_mbus_code = vgxy61_enum_mbus_code,
> > +       .get_fmt = vgxy61_get_fmt,
> > +       .set_fmt = vgxy61_set_fmt,
> > +       .enum_frame_size = vgxy61_enum_frame_size,
> > +       .enum_frame_interval = vgxy61_enum_frame_interval,
> > +};
> > +
> > +static const struct v4l2_subdev_ops vgxy61_subdev_ops = {
> > +       .core = &vgxy61_core_ops,
> > +       .video = &vgxy61_video_ops,
> > +       .pad = &vgxy61_pad_ops,
> > +};
> > +
> > +static const struct media_entity_operations vgxy61_subdev_entity_ops = {
> > +       .link_validate = v4l2_subdev_link_validate,
> > +};
> > +
> > +/* Set phy polarities */
> > +static int vgxy61_tx_from_ep(struct vgxy61_dev *sensor, struct fwnode_handle *endpoint)
> > +{
> > +       struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
> > +       struct i2c_client *client = sensor->i2c_client;
> > +       u32 log2phy[NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
> > +       u32 phy2log[NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
> > +       int polarities[NB_POLARITIES] = {0, 0, 0, 0, 0};
> > +       int l_nb;
> > +       int p, l;
> > +       int ret;
> > +       int i;
> > +
> > +       ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep);
> > +       if (ret)
> > +               goto error_alloc;
> > +
> > +       l_nb = ep.bus.mipi_csi2.num_data_lanes;
> > +       if (l_nb != 1 && l_nb != 2 && l_nb != 4) {
> > +               dev_err(&client->dev, "invalid data lane number %d\n", l_nb);
> > +               goto error_ep;
> > +       }
> > +
> > +       /* Build log2phy, phy2log and polarities from ep info */
> > +       log2phy[0] = ep.bus.mipi_csi2.clock_lane;
> > +       phy2log[log2phy[0]] = 0;
> > +       for (l = 1; l < l_nb + 1; l++) {
> > +               log2phy[l] = ep.bus.mipi_csi2.data_lanes[l - 1];
> > +               phy2log[log2phy[l]] = l;
> > +       }
> > +       /*
> > +        * Then fill remaining slots for every physical slot to have something valid for hardware
> > +        * stuff.
> > +        */
> > +       for (p = 0; p < NB_POLARITIES; p++) {
> > +               if (phy2log[p] != ~0)
> > +                       continue;
> > +               phy2log[p] = l;
> > +               log2phy[l] = p;
> > +               l++;
> > +       }
> > +       for (l = 0; l < l_nb + 1; l++)
> > +               polarities[l] = ep.bus.mipi_csi2.lane_polarities[l];
> > +
> > +       if (log2phy[0] != 0) {
> > +               dev_err(&client->dev, "clk lane must be map to physical lane 0\n");
> > +               goto error_ep;
> > +       }
> > +       sensor->oif_ctrl = (polarities[4] << 15) + ((phy2log[4] - 1) << 13) +
> > +                          (polarities[3] << 12) + ((phy2log[3] - 1) << 10) +
> > +                          (polarities[2] <<  9) + ((phy2log[2] - 1) <<  7) +
> > +                          (polarities[1] <<  6) + ((phy2log[1] - 1) <<  4) +
> > +                          (polarities[0] <<  3) +
> > +                          l_nb;
> > +       sensor->nb_of_lane = l_nb;
> > +
> > +       dev_dbg(&client->dev, "tx uses %d lanes", l_nb);
> > +       for (i = 0; i < 5; i++) {
> > +               dev_dbg(&client->dev, "log2phy[%d] = %d\n", i, log2phy[i]);
> > +               dev_dbg(&client->dev, "phy2log[%d] = %d\n", i, phy2log[i]);
> > +               dev_dbg(&client->dev, "polarity[%d] = %d\n", i, polarities[i]);
> > +       }
> > +       dev_dbg(&client->dev, "oif_ctrl = 0x%04x\n", sensor->oif_ctrl);
> > +
> > +       v4l2_fwnode_endpoint_free(&ep);
> > +
> > +       return 0;
> > +
> > +error_ep:
> > +       v4l2_fwnode_endpoint_free(&ep);
> > +error_alloc:
> > +
> > +       return -EINVAL;
> > +}
> > +
> > +static int vgxy61_configure(struct vgxy61_dev *sensor)
> > +{
> > +       struct i2c_client *client = sensor->i2c_client;
> > +       int sensor_freq;
> > +       unsigned int prediv;
> > +       unsigned int mult;
> > +       int ret;
> > +
> > +       compute_pll_parameters_by_freq(sensor->clk_freq, &prediv, &mult);
> > +       sensor_freq = (mult * sensor->clk_freq) / prediv;
> > +       /* Frequency to data rate is 1:1 ratio for MIPI */
> > +       sensor->data_rate_in_mbps = sensor_freq;
> > +       /* Video timing ISP path (pixel clock)  requires 804/5 mhz = 160 mhz */
> > +       sensor->pclk = sensor_freq / 5;
> > +
> > +       /* Cache line_length value */
> > +       ret = vgxy61_read_reg16(sensor, DEVICE_LINE_LENGTH, &sensor->line_length);
> > +       if (ret)
> > +               return ret;
> > +       /* Configure clocks */
> > +       ret = vgxy61_write_reg32(sensor, DEVICE_EXT_CLOCK, sensor->clk_freq);
> > +       if (ret)
> > +               return ret;
> > +       ret = vgxy61_write_reg(sensor, DEVICE_CLK_PLL_PREDIV, prediv);
> > +       if (ret)
> > +               return ret;
> > +       ret = vgxy61_write_reg(sensor, DEVICE_CLK_SYS_PLL_MULT, mult);
> > +       if (ret)
> > +               return ret;
> > +       /* Configure interface */
> > +       ret = vgxy61_write_reg16(sensor, DEVICE_OIF_CTRL, sensor->oif_ctrl);
> > +       if (ret)
> > +               return ret;
> > +       /* Disable pwm compression */
> > +       ret = vgxy61_write_reg(sensor, DEVICE_FRAME_CONTENT_CTRL, 0);
> > +       if (ret)
> > +               return ret;
> > +       /* Disable asil lines */
> > +       ret = vgxy61_write_reg(sensor, DEVICE_BYPASS_CTRL, 4);
> > +       if (ret)
> > +               return ret;
> > +       /* Set gpios polarity according to device tree value */
> > +       ret = vgxy61_update_gpios_strobe_polarity(sensor, sensor->gpios_polarity);
> > +       if (ret)
> > +               return ret;
> > +       /* HDR mode */
> > +       ret = vgxy61_update_hdr(sensor, sensor->hdr);
> > +       if (ret)
> > +               return ret;
> > +       /* Slave mode */
> > +       ret = vgxy61_write_reg(sensor, DEVICE_VT_CTRL, sensor->slave_mode);
> > +       if (ret)
> > +               return ret;
> > +       /* Set pattern generator solid to middle value */
> > +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_GR, 0x800);
> > +       if (ret)
> > +               return ret;
> > +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_R, 0x800);
> > +       if (ret)
> > +               return ret;
> > +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_B, 0x800);
> > +       if (ret)
> > +               return ret;
> > +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_GB, 0x800);
> > +       if (ret)
> > +               return ret;
> > +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_GR, 0x800);
> > +       if (ret)
> > +               return ret;
> > +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_R, 0x800);
> > +       if (ret)
> > +               return ret;
> > +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_B, 0x800);
> > +       if (ret)
> > +               return ret;
> > +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_GB, 0x800);
> > +       if (ret)
> > +               return ret;
> > +
> > +       dev_dbg(&client->dev, "clock prediv = %d\n", prediv);
> > +       dev_dbg(&client->dev, "clock mult = %d\n", mult);
> > +       dev_dbg(&client->dev, "data rate = %d mbps\n",
> > +               sensor->data_rate_in_mbps);
> > +
> > +       return 0;
> > +}
> > +
> > +static int vgxy61_patch(struct vgxy61_dev *sensor)
> > +{
> > +       struct i2c_client *client = sensor->i2c_client;
> > +       u16 patch;
> > +       int ret;
> > +
> > +       ret = vgxy61_write_array(sensor, DEVICE_FWPATCH_START_ADDR, sizeof(patch_array),
> > +                                patch_array);
> > +       if (ret)
> > +               return ret;
> > +
> > +       ret = vgxy61_write_reg(sensor, DEVICE_STBY, 0x10);
> > +       if (ret)
> > +               return ret;
> > +
> > +       ret = vgxy61_poll_reg(sensor, DEVICE_STBY, 0);
> > +       if (ret)
> > +               return ret;
> > +
> > +       ret = vgxy61_read_reg16(sensor, DEVICE_FWPATCH_REVISION, &patch);
> > +       if (ret)
> > +               return ret;
> > +
> > +       if (patch != (DEVICE_FWPATCH_REVISION_MAJOR << 12) +
> > +                    (DEVICE_FWPATCH_REVISION_MINOR << 8) +
> > +                    DEVICE_FWPATCH_REVISION_MICRO) {
> > +               dev_err(&client->dev, "bad patch version expected %d.%d.%d got %d.%d.%d\n",
> > +                       DEVICE_FWPATCH_REVISION_MAJOR,
> > +                       DEVICE_FWPATCH_REVISION_MINOR,
> > +                       DEVICE_FWPATCH_REVISION_MICRO,
> > +                       patch >> 12, (patch >> 8) & 0x0f, patch & 0xff);
> > +               return -ENODEV;
> > +       }
> > +       dev_dbg(&client->dev, "patch %d.%d.%d applied\n",
> > +               patch >> 12, (patch >> 8) & 0x0f, patch & 0xff);
> > +
> > +       return 0;
> > +}
> > +
> > +static int vgxy61_detect_cut_version(struct vgxy61_dev *sensor)
> > +{
> > +       struct i2c_client *client = sensor->i2c_client;
> > +       u16 device_rev;
> > +       int ret;
> > +
> > +       ret = vgxy61_read_reg16(sensor, DEVICE_REVISION, &device_rev);
> > +       if (ret)
> > +               return ret;
> > +
> > +       switch (device_rev >> 8) {
> > +       case 0xA:
> > +               dev_info(&client->dev, "Cut1 detected\n");
> > +               dev_err(&client->dev, "Cut1 not supported by this driver\n");
> > +               return -ENODEV;
> > +       case 0xB:
> > +               dev_info(&client->dev, "Cut2 detected\n");
> > +               return 0;
> > +       case 0xC:
> > +               dev_info(&client->dev, "Cut3 detected\n");
> > +               return 0;
> > +       default:
> > +               dev_err(&client->dev, "Unable to detect cut version\n");
> > +               return -ENODEV;
> > +       }
> > +}
> > +
> > +static int vgxy61_detect(struct vgxy61_dev *sensor)
> > +{
> > +       struct i2c_client *client = sensor->i2c_client;
> > +       u16 id = 0;
> > +       int ret;
> > +       u8 st;
> > +
> > +       ret = vgxy61_read_reg16(sensor, DEVICE_MODEL_ID_REG, &id);
> > +       if (ret)
> > +               return ret;
> > +       if (id != VG5661_MODEL_ID && id != VG5761_MODEL_ID) {
> > +               dev_warn(&client->dev, "Unsupported sensor id %x\n", id);
> > +               return -ENODEV;
> > +       }
> > +       dev_dbg(&client->dev, "detected sensor id = 0x%04x\n", id);
> > +
> > +       if (id == VG5761_MODEL_ID) {
> > +               sensor->sensor_width = VGX761_WIDTH;
> > +               sensor->sensor_height = VGX761_HEIGHT;
> > +               sensor->sensor_modes = vgx761_mode_data;
> > +               sensor->sensor_modes_nb = ARRAY_SIZE(vgx761_mode_data);
> > +               sensor->current_mode = &vgx761_mode_data[VGX761_DEFAULT_MODE];
> > +               sensor->rot_term = VGX761_SHORT_ROT_TERM;
> > +               sensor->sensor_rates = vgx761_sensor_frame_rates;
> > +               sensor->sensor_rates_nb = ARRAY_SIZE(vgx761_sensor_frame_rates);
> > +       } else if (id == VG5661_MODEL_ID) {
> > +               sensor->sensor_width = VGX661_WIDTH;
> > +               sensor->sensor_height = VGX661_HEIGHT;
> > +               sensor->sensor_modes = vgx661_mode_data;
> > +               sensor->sensor_modes_nb = ARRAY_SIZE(vgx661_mode_data);
> > +               sensor->current_mode = &vgx661_mode_data[VGX661_DEFAULT_MODE];
> > +               sensor->rot_term = VGX661_SHORT_ROT_TERM;
> > +               sensor->sensor_rates = vgx661_sensor_frame_rates;
> > +               sensor->sensor_rates_nb = ARRAY_SIZE(vgx661_sensor_frame_rates);
> > +       }
> > +
> > +       ret = vgxy61_wait_state(sensor, SW_STBY);
> > +       if (ret)
> > +               return ret;
> > +
> > +       ret = vgxy61_read_reg(sensor, DEVICE_NVM, &st);
> > +       if (ret)
> > +               return ret;
> > +       if (st != NVM_OK)
> > +               dev_warn(&client->dev, "Bad nvm state got %d\n", st);
> > +
> > +       /* Detect cut version */
> > +       ret = vgxy61_detect_cut_version(sensor);
> > +       if (ret)
> > +               return ret;
> > +
> > +       return 0;
> > +}
> > +
> > +static int vgxy61_probe(struct i2c_client *client)
> > +{
> > +       struct device *dev = &client->dev;
> > +       struct fwnode_handle *endpoint;
> > +       struct vgxy61_dev *sensor;
> > +       int ret;
> > +
> > +       sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
> > +       if (!sensor)
> > +               return -ENOMEM;
> > +
> > +       sensor->i2c_client = client;
> > +       sensor->streaming = false;
> > +       sensor->fmt.code = MEDIA_BUS_FMT_SGBRG8_1X8;
> > +       sensor->fmt.field = V4L2_FIELD_NONE;
> > +       sensor->fmt.colorspace = V4L2_COLORSPACE_SRGB;

ycbcr_enc, quantization, and xfer_func too.

> > +       sensor->frame_interval.numerator = 1;
> > +       sensor->frame_interval.denominator = 60;
> > +       sensor->hdr = NO_HDR;
> > +       sensor->expo_long = 200;
> > +       sensor->expo_short = 0;
> > +       sensor->hflip = false;
> > +       sensor->vflip = false;
> > +
> > +       endpoint = fwnode_graph_get_next_endpoint(of_fwnode_handle(dev->of_node), NULL);
> > +       if (!endpoint) {
> > +               dev_err(dev, "endpoint node not found\n");
> > +               return -EINVAL;
> > +       }
> > +
> > +       ret = vgxy61_tx_from_ep(sensor, endpoint);
> > +       fwnode_handle_put(endpoint);
> > +       if (ret) {
> > +               dev_err(dev, "Failed to parse endpoint %d\n", ret);
> > +               return ret;
> > +       }
> > +
> > +       sensor->xclk = devm_clk_get(dev, "xclk");
> > +       if (IS_ERR(sensor->xclk)) {
> > +               dev_err(dev, "failed to get xclk\n");
> > +               return PTR_ERR(sensor->xclk);
> > +       }
> > +       sensor->clk_freq = clk_get_rate(sensor->xclk);
> > +       if (sensor->clk_freq < 6000000 || sensor->clk_freq > 27000000) {
> > +               dev_err(dev, "Only 6Mhz-27Mhz clock range supported. provide %d Hz\n",
> > +                       sensor->clk_freq);
> > +               return -EINVAL;
> > +       }
> > +       sensor->gpios_polarity = of_property_read_bool(dev->of_node, "invert-gpios-polarity");
> > +       sensor->slave_mode = of_property_read_bool(dev->of_node, "slave-mode");
> > +
> > +       v4l2_i2c_subdev_init(&sensor->sd, client, &vgxy61_subdev_ops);
> > +       sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> > +       sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
> > +       sensor->sd.entity.ops = &vgxy61_subdev_entity_ops;
> > +       sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
> > +
> > +       ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
> > +       if (ret) {
> > +               dev_err(&client->dev, "pads init failed %d\n", ret);
> > +               return ret;
> > +       }
> > +
> > +       /* Request optional reset pin */
> > +       sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
> > +
> > +       ret = vgxy61_get_regulators(sensor);
> > +       if (ret) {
> > +               dev_err(&client->dev, "failed to get regulators %d\n", ret);
> > +               goto entity_cleanup;
> > +       }
> > +
> > +       ret = regulator_bulk_enable(VGXY61_NUM_SUPPLIES, sensor->supplies);
> > +       if (ret) {
> > +               dev_err(&client->dev, "failed to enable regulators %d\n", ret);
> > +               goto entity_cleanup;
> > +       }
> > +
> > +       ret = clk_prepare_enable(sensor->xclk);
> > +       if (ret) {
> > +               dev_err(&client->dev, "failed to enable clock %d\n", ret);
> > +               goto disable_bulk;
> > +       }
> > +
> > +       mutex_init(&sensor->lock);
> > +
> > +       /* Apply reset sequence */
> > +       if (sensor->reset_gpio) {
> > +               ret = vgxy61_apply_reset(sensor);
> > +               if (ret) {
> > +                       dev_err(&client->dev, "sensor reset failed %d\n", ret);
> > +                       goto disable_clock;
> > +               }
> > +       }
> > +
> > +       ret = vgxy61_detect(sensor);
> > +       if (ret) {
> > +               dev_err(&client->dev, "sensor detect failed %d\n", ret);
> > +               goto disable_clock;
> > +       }
> > +       sensor->fmt.width = sensor->current_mode->width;
> > +       sensor->fmt.height = sensor->current_mode->height;
> > +
> > +       ret = vgxy61_patch(sensor);
> > +       if (ret) {
> > +               dev_err(&client->dev, "sensor patch failed %d\n", ret);
> > +               goto disable_clock;
> > +       }
> > +
> > +       ret = vgxy61_configure(sensor);
> > +       if (ret) {
> > +               dev_err(&client->dev, "sensor configuration failed %d\n", ret);
> > +               goto disable_clock;
> > +       }
> > +
> > +       ret = vgxy61_init_controls(sensor);
> > +       if (ret) {
> > +               dev_err(&client->dev, "controls initialization failed %d\n", ret);
> > +               goto disable_clock;
> > +       }
> > +
> > +       ret = v4l2_async_register_subdev(&sensor->sd);
> > +       if (ret) {
> > +               dev_err(&client->dev, "async subdev register failed %d\n", ret);
> > +               goto disable_clock;
> > +       }
> > +
> > +       dev_info(&client->dev, "vgxy61 probe successfully\n");
> > +
> > +       return 0;
> > +
> > +disable_clock:
> > +       clk_disable_unprepare(sensor->xclk);
> > +disable_bulk:
> > +       regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
> > +entity_cleanup:
> > +       mutex_destroy(&sensor->lock);
> > +       media_entity_cleanup(&sensor->sd.entity);
> > +       return ret;
> > +}
> > +
> > +static int vgxy61_remove(struct i2c_client *client)
> > +{
> > +       struct v4l2_subdev *sd = i2c_get_clientdata(client);
> > +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> > +
> > +       v4l2_async_unregister_subdev(&sensor->sd);
> > +       clk_disable_unprepare(sensor->xclk);
> > +       mutex_destroy(&sensor->lock);
> > +       media_entity_cleanup(&sensor->sd.entity);
> > +       regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
> > +
> > +       return 0;
> > +}
> > +
> > +static const struct of_device_id vgxy61_dt_ids[] = {
> > +       { .compatible = "st,st-vgxy61" },
> > +       { /* sentinel */ }
> > +};
> > +MODULE_DEVICE_TABLE(of, vgxy61_dt_ids);
> > +
> > +static struct i2c_driver vgxy61_i2c_driver = {
> > +       .driver = {
> > +               .name  = "st-vgxy61",
> > +               .of_match_table = vgxy61_dt_ids,

Support for pm_runtime? Then again the sensor appears to be powered up
in probe and never powered down again.

  Dave

> > +       },
> > +       .probe_new = vgxy61_probe,
> > +       .remove = vgxy61_remove,
> > +};
> > +
> > +module_i2c_driver(vgxy61_i2c_driver);
> > +
> > +MODULE_AUTHOR("Benjamin Mugnier <benjamin.mugnier@foss.st.com>");
> > +MODULE_AUTHOR("Mickael Guene <mickael.guene@st.com>");
> > +MODULE_AUTHOR("Sylvain Petinot <sylvain.petinot@foss.st.com>");
> > +MODULE_DESCRIPTION("VGXY61 camera subdev driver");
> > +MODULE_LICENSE("GPL v2");
> > --
> > 2.25.1
> >

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

* Re: [PATCH 1/2] media: dt-bindings: media: i2c: Add ST VGXY61 camera sensor binding
  2022-03-10 13:32 ` [PATCH 1/2] media: dt-bindings: media: i2c: Add ST VGXY61 camera sensor binding Benjamin Mugnier
@ 2022-03-10 15:38   ` Dave Stevenson
  2022-03-11 11:25     ` Benjamin Mugnier
  0 siblings, 1 reply; 17+ messages in thread
From: Dave Stevenson @ 2022-03-10 15:38 UTC (permalink / raw)
  To: Benjamin Mugnier
  Cc: Linux Media Mailing List, alain.volmat, hugues.fruchet,
	sylvain.petinot, Laurent Pinchart, Sakari Ailus

Hi Benjamin

cc Laurent and Sakari as maintainers of video-interfaces.yaml

On Thu, 10 Mar 2022 at 13:37, Benjamin Mugnier
<benjamin.mugnier@foss.st.com> wrote:
>
> Add device tree binding for the ST VGXY61 camera sensor, and update
> MAINTAINERS file.
>
> Signed-off-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com>
> ---
>  .../bindings/media/i2c/st,st-vgxy61.yaml      | 134 ++++++++++++++++++
>  MAINTAINERS                                   |  10 ++
>  2 files changed, 144 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml
>
> diff --git a/Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml b/Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml
> new file mode 100644
> index 000000000000..8740ed2623e4
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml
> @@ -0,0 +1,134 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +# Copyright (c) 2022 STMicroelectronics SA.
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/media/i2c/st,st-vgxy61.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: STMicroelectronics VGxy61 HDR Global Shutter Sensor Family Device Tree Bindings
> +
> +maintainers:
> +  - Benjamin Mugnier <benjamin.mugnier@foss.st.com>
> +  - Sylvain Petinot <sylvain.petinot@foss.st.com>
> +
> +description: |-
> +  STMicroelectronics VGxy61 family has a CSI-2 output port. CSI-2 output is a
> +  quad lanes 800Mbps per lane.
> +  Supported formats are RAW8, RAW10, RAW12, RAW14 and RAW16.
> +  Following part number are supported
> +  - VG5661 and VG6661 are 1.6 Mpx (1464 x 1104) monochrome and color sensors.
> +  Maximum frame rate is 75 fps.
> +  - VG5761 and VG6761 are 2.3 Mpx (1944 x 1204) monochrome and color sensors.
> +  Maximum frame rate is 60 fps.
> +
> +properties:
> +  compatible:
> +    const: st,st-vgxy61
> +
> +  reg:
> +    maxItems: 1
> +
> +  clocks:
> +    maxItems: 1
> +
> +  clock-names:
> +    description:
> +      Input clock for the sensor.
> +    items:
> +      - const: xclk
> +
> +  VCORE-supply:
> +    description:
> +      Sensor digital core supply. Must be 1.2 volts.
> +
> +  VDDIO-supply:
> +    description:
> +      Sensor digital IO supply. Must be 1.8 volts.
> +
> +  VANA-supply:
> +    description:
> +      Sensor analog supply. Must be 2.8 volts.
> +
> +  reset-gpios:
> +    description:
> +      Reference to the GPIO connected to the reset pin, if any.
> +      This is an active low signal to the vgxy61.
> +
> +  invert-gpios-polarity:
> +    description:
> +      If gpios polarity should be inversed

s/inversed/inverted

> +    type: boolean
> +
> +  slave-mode:
> +    description:
> +      If the sensor operates in slave mode
> +    type: boolean

This is one I've been meaning to raise for a while.
Is DT the correct place to be configuring hardware sync options for
image sensors? (There may be the linguistic discussions over master /
slave terminology too).
We also have IMX477 and a number of other sensors that support
external sync control of some form.

As I see it, there are nominally 3 settings - disabled (reduces EMC
noise), generate syncs, and receive syncs.
For test purposes it would be useful to be able to switch between
generate and receive modes at runtime, so that would make it a control
instead of being fixed in DT.

If it should be configured in DT, then how does ACPI need to handle it?

If DT is the correct place to define the role, should it be in
video-interfaces.yaml as an optional property, instead of being a
sensor specific binding?

Sorry, more questions rather than answers.

  Dave

> +    #TODO check all this or copy from elsewhere
> +  port:
> +    $ref: /schemas/graph.yaml#/$defs/port-base
> +    additionalProperties: false
> +
> +    properties:
> +      endpoint:
> +        $ref: /schemas/media/video-interfaces.yaml#
> +        unevaluatedProperties: false
> +
> +        properties:
> +          clock-lane:
> +            description:
> +              Clock lane index
> +            maxItems: 1
> +
> +          data-lanes:
> +            description:
> +              CSI lanes to use
> +            items:
> +              - const: 1
> +              - const: 2
> +              - const: 3
> +              - const: 4
> +
> +          remote-endpoint: true
> +
> +        required:
> +          - clock-lane
> +          - data-lanes
> +          - remote-endpoint
> +
> +required:
> +  - compatible
> +  - clocks
> +  - clock-names
> +  - VCORE-supply
> +  - VDDIO-supply
> +  - VANA-supply
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/gpio/gpio.h>
> +    i2c {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +        vgxy61: csi2tx@10 {
> +            compatible = "st,st-vgxy61";
> +            reg = <0x10>;
> +            status = "okay";
> +            clocks = <&clk_ext_camera>;
> +            clock-names = "xclk";
> +            VCORE-supply = <&v1v2>;
> +            VDDIO-supply = <&v1v8>;
> +            VANA-supply = <&v2v8>;
> +            reset-gpios = <&mfxgpio 18 GPIO_ACTIVE_LOW>;
> +            port {
> +                ep0: endpoint {
> +                    clock-lane = <0>;
> +                    data-lanes = <1 2 3 4>;
> +                    remote-endpoint = <&mipi_csi2_out>;
> +                };
> +            };
> +        };
> +    };
> +...
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 83d27b57016f..f358d15f68a0 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -18297,6 +18297,16 @@ S:     Maintained
>  F:     Documentation/hwmon/stpddc60.rst
>  F:     drivers/hwmon/pmbus/stpddc60.c
>
> +ST VGXY61 DRIVER
> +M:     Benjamin Mugnier <benjamin.mugnier@foss.st.com>
> +M:     Sylvain Petinot <sylvain.petinot@foss.st.com>
> +L:     linux-media@vger.kernel.org
> +S:     Maintained
> +T:     git git://linuxtv.org/media_tree.git
> +F:     Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.txt
> +F:     drivers/media/i2c/st-vgxy61.c
> +
> +
>  ST VL53L0X ToF RANGER(I2C) IIO DRIVER
>  M:     Song Qiang <songqiang1304521@gmail.com>
>  L:     linux-iio@vger.kernel.org
> --
> 2.25.1
>

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

* Re: [PATCH 2/2] media: i2c: Add driver for ST VGXY61 camera sensor
  2022-03-10 15:15     ` Benjamin Mugnier
@ 2022-03-10 15:50       ` Dave Stevenson
  2022-03-11  9:34         ` Benjamin Mugnier
  0 siblings, 1 reply; 17+ messages in thread
From: Dave Stevenson @ 2022-03-10 15:50 UTC (permalink / raw)
  To: Benjamin Mugnier
  Cc: Kieran Bingham, Linux Media Mailing List, alain.volmat,
	hugues.fruchet, sylvain.petinot

Hi Benjamin

On Thu, 10 Mar 2022 at 15:15, Benjamin Mugnier
<benjamin.mugnier@foss.st.com> wrote:
>
> Hi Kieran,
>
> On 10/03/2022 14:52, Kieran Bingham wrote:
> > Hi Benjamin,
> >
> > Thank you for the patch -
> >
> > Quoting Benjamin Mugnier (2022-03-10 13:32:55)
> >> The VGXY61 has a quad lanes CSI-2 output port running at 800mbps per
> >> lane, and supports RAW8, RAW10, RAW12, RAW14 and RAW16 formats.
> >> The driver handles both sensor types:
> >> - VG5661 and VG6661: 1.6 Mpx (1464 x 1104) 75fps.
> >> - VG5761 and VG6761: 2.3 Mpx (1944 x 1204) 60 fps.
> >> The driver supports:
> >> - HDR linearize mode, HDR substraction mode, and no HDR
> >> - GPIOs LEDs strobing
> >> - Digital binning and analog subsampling
> >> - Horizontal and vertical flip
> >> - Manual exposure
> >> - Analog and digital gains
> >> - Test patterns
> >
> > I haven't reviewed the driver directly yet, but I have a script which
> > looks for key requirements for libcamera.
> > (https://git.linuxtv.org/libcamera.git/tree/Documentation/sensor_driver_requirements.rst)
> >
> >
> > Media Controller Support:
> >  - V4L2_SUBDEV_FL_HAS_DEVNODE      : found
> >  - MEDIA_ENT_F_CAM_SENSOR          : found
> >
> > Mandatory Controls:
> >  - V4L2_CID_EXPOSURE               : found
> >  - V4L2_CID_HBLANK                 : --------
> >  - V4L2_CID_VBLANK                 : --------
> >  - V4L2_CID_PIXEL_RATE             : found
> >
> > Selection Controls (will become mandatory):
> >  - V4L2_SEL_TGT_CROP_DEFAULT       : --------
> >  - V4L2_SEL_TGT_CROP               : --------
> >  - V4L2_SEL_TGT_CROP_BOUNDS        : --------
> >  - .get_selection                  : --------
> >
> > Optional Controls:
> >  - V4L2_CID_TEST_PATTERN           : found
> >  - V4L2_CID_GAIN                   : --------
> >  - V4L2_CID_ANALOGUE_GAIN          : found
> >  - V4L2_CID_CAMERA_SENSOR_ROTATION : --------
> >  - V4L2_CID_CAMERA_ORIENTATION     : --------
> >
> >
> > The key missing pieces are HBLANK/VBLANK and the .get_selection API. Is
> > it easy/feasible to add these?
> >
>
> The .get_selection API should be easy.
> Unfortunately, this device does not have registers for HBLANK/VBLANK. I may be able to compute them for a read only control, but it will be harder to set. Is the read only control enough for libcamera?
>
> My next goal is to run this sensor on a Raspberry Pi through libcamera. As I'm fairly new to libcamera it may take some time. My initial idea was to submit libcamera related changes in another set of patches once done, so I could test the entire stack and make extra adjustements and fixes to the driver that I'm not aware of yet. What do you think?

Sounds fun :-) Shout if you need any assistance.

The Pi pipeline handler only supports VBLANK/HBLANK for frame rate
control, so implementing the frame_interval ops is a dead end.
Currently it only supports varying VBLANK for frame rate control, but
in theory variable HBLANK could also be supported (IIRC Sony
IMX290/327 varies HBLANK by default).

As I commented in my other reply, the docs say that raw sensors should
use these as they don't have a high level control for frame rate.

Your registers DEVICE_LINE_LENGTH and DEVICE_FRAME_LENGTH look to be
exactly the settings that map onto HBLANK and VBLANK respectively, but
with a need to subtract the active frame width / height from the
value.
Drop all the frame_interval stuff and add the HBLANK (ro) and VBLANK
controls. If you look at imx219 or similar you'll see the handling of
setting VBLANK then adjusting the max value for the exposure control.

There are very few things that libcamera needs over and above a
standard sensor driver, so IMHO there's little point in deferring
implementing them.

  Dave

> Regards,
>
> Benjamin
>
> > --
> > Regards
> >
> > Kieran
> >
> >
> >>
> >> Signed-off-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com>
> >> ---
> >>  drivers/media/i2c/Kconfig     |   11 +
> >>  drivers/media/i2c/Makefile    |    1 +
> >>  drivers/media/i2c/st-vgxy61.c | 1919 +++++++++++++++++++++++++++++++++
> >>  3 files changed, 1931 insertions(+)
> >>  create mode 100644 drivers/media/i2c/st-vgxy61.c
> >>
> >> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> >> index fe66093b8849..e6e9c1f19c09 100644
> >> --- a/drivers/media/i2c/Kconfig
> >> +++ b/drivers/media/i2c/Kconfig
> >> @@ -1454,6 +1454,17 @@ config VIDEO_S5C73M3
> >>           This is a V4L2 sensor driver for Samsung S5C73M3
> >>           8 Mpixel camera.
> >>
> >> +config VIDEO_ST_VGXY61
> >> +       tristate "ST VGXY61 sensor support"
> >> +       depends on OF
> >> +       depends on GPIOLIB && VIDEO_V4L2 && I2C
> >> +       select MEDIA_CONTROLLER
> >> +       select VIDEO_V4L2_SUBDEV_API
> >> +       select V4L2_FWNODE
> >> +       help
> >> +         This is a Video4Linux2 sensor driver for the ST VGXY61
> >> +         camera sensor.
> >> +
> >>  endmenu
> >>
> >>  menu "Lens drivers"
> >> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> >> index f6b80ef6f41d..0190b5edfa24 100644
> >> --- a/drivers/media/i2c/Makefile
> >> +++ b/drivers/media/i2c/Makefile
> >> @@ -138,4 +138,5 @@ obj-$(CONFIG_VIDEO_MAX9271_LIB)     += max9271.o
> >>  obj-$(CONFIG_VIDEO_RDACM20)    += rdacm20.o
> >>  obj-$(CONFIG_VIDEO_RDACM21)    += rdacm21.o
> >>  obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o
> >> +obj-$(CONFIG_VIDEO_ST_VGXY61)  += st-vgxy61.o
> >>  obj-$(CONFIG_SDR_MAX2175) += max2175.o
> >> diff --git a/drivers/media/i2c/st-vgxy61.c b/drivers/media/i2c/st-vgxy61.c
> >> new file mode 100644
> >> index 000000000000..d3d816189f86
> >> --- /dev/null
> >> +++ b/drivers/media/i2c/st-vgxy61.c
> >> @@ -0,0 +1,1919 @@
> >> +// SPDX-License-Identifier: GPL-2.0
> >> +/*
> >> + * Driver for VGXY61 global shutter sensor family driver
> >> + *
> >> + * Copyright (C) 2022 STMicroelectronics SA
> >> + */
> >> +
> >> +#include <linux/clk.h>
> >> +#include <linux/delay.h>
> >> +#include <linux/gpio/consumer.h>
> >> +#include <linux/i2c.h>
> >> +#include <linux/module.h>
> >> +#include <linux/regulator/consumer.h>
> >> +#include <linux/units.h>
> >> +#include <linux/iopoll.h>
> >> +#include <media/mipi-csi2.h>
> >> +#include <media/v4l2-async.h>
> >> +#include <media/v4l2-ctrls.h>
> >> +#include <media/v4l2-device.h>
> >> +#include <media/v4l2-fwnode.h>
> >> +#include <media/v4l2-subdev.h>
> >> +
> >> +#define US_PER_MS                                      1000
> >> +
> >> +#define DEVICE_MODEL_ID_REG                            0x0000
> >> +#define VG5661_MODEL_ID                                        0x5661
> >> +#define VG5761_MODEL_ID                                        0x5761
> >> +#define VGX661_WIDTH                                   1464
> >> +#define VGX661_HEIGHT                                  1104
> >> +#define VGX761_WIDTH                                   1944
> >> +#define VGX761_HEIGHT                                  1204
> >> +#define VGX661_DEFAULT_MODE                            1
> >> +#define VGX761_DEFAULT_MODE                            1
> >> +#define VGX661_SHORT_ROT_TERM                          93
> >> +#define VGX761_SHORT_ROT_TERM                          90
> >> +#define VGXY61_EXPOS_ROT_TERM                          66
> >> +#define DEVICE_REVISION                                        0x0002
> >> +#define DEVICE_FWPATCH_REVISION                                0x0014
> >> +#define DEVICE_FWPATCH_START_ADDR                      0x2000
> >> +#define DEVICE_SYSTEM_FSM                              0x0020
> >> +#define SW_STBY                                                0x03
> >> +#define STREAMING                                      0x04
> >> +#define DEVICE_NVM                                     0x0023
> >> +#define NVM_OK                                         0x04
> >> +#define DEVICE_THSENS1_TEMPERATURE                     0x0044
> >> +#define DEVICE_STBY                                    0x0201
> >> +#define STBY_NO_REQ                                    0
> >> +#define STBY_REQ_TMP_READ                              BIT(2)
> >> +#define DEVICE_STREAMING                               0x0202
> >> +#define REQ_NO_REQUEST                                 0
> >> +#define REQ_STOP_STREAMING                             BIT(0)
> >> +#define REQ_START_STREAMING                            BIT(1)
> >> +#define DEVICE_EXT_CLOCK                               0x0220
> >> +#define DEVICE_CLK_PLL_PREDIV                          0x0224
> >> +#define DEVICE_CLK_SYS_PLL_MULT                                0x0225
> >> +#define DEVICE_GPIO_0_CTRL                             0x0236
> >> +#define DEVICE_GPIO_1_CTRL                             0x0237
> >> +#define DEVICE_GPIO_2_CTRL                             0x0238
> >> +#define DEVICE_GPIO_3_CTRL                             0x0239
> >> +#define DEVICE_SIGNALS_POLARITY_CTRL                   0x023b
> >> +#define DEVICE_LINE_LENGTH                             0x0300
> >> +#define DEVICE_ORIENTATION                             0x0302
> >> +#define DEVICE_VT_CTRL                                 0x0304
> >> +#define DEVICE_FORMAT_CTRL                             0x0305
> >> +#define DEVICE_OIF_CTRL                                        0x0306
> >> +#define DEVICE_OIF_ROI0_CTRL                           0x030a
> >> +#define DEVICE_ROI0_START_H                            0x0400
> >> +#define DEVICE_ROI0_START_V                            0x0402
> >> +#define DEVICE_ROI0_END_H                              0x0404
> >> +#define DEVICE_ROI0_END_V                              0x0406
> >> +#define DEVICE_PATGEN_CTRL                             0x0440
> >> +#define DEVICE_FRAME_CONTENT_CTRL                      0x0478
> >> +#define DEVICE_COARSE_EXPOSURE_LONG                    0x0500
> >> +#define DEVICE_COARSE_EXPOSURE_SHORT                   0x0504
> >> +#define DEVICE_ANALOG_GAIN                             0x0508
> >> +#define DEVICE_DIGITAL_GAIN_LONG                       0x050a
> >> +#define DEVICE_DIGITAL_GAIN_SHORT                      0x0512
> >> +#define DEVICE_FRAME_LENGTH                            0x051a
> >> +#define DEVICE_SIGNALS_CTRL                            0x0522
> >> +#define DEVICE_STROBE_LONG_START_DELAY                 0x0528
> >> +#define DEVICE_STROBE_LONG_END_DELAY                   0x052a
> >> +#define DEVICE_STROBE_SHORT_START_DELAY                        0x052c
> >> +#define DEVICE_STROBE_SHORT_END_DELAY                  0x052e
> >> +#define DEVICE_READOUT_CTRL                            0x0530
> >> +#define DEVICE_HDR_CTRL                                        0x0532
> >> +#define DEVICE_PATGEN_LONG_DATA_GR                     0x092c
> >> +#define DEVICE_PATGEN_LONG_DATA_R                      0x092e
> >> +#define DEVICE_PATGEN_LONG_DATA_B                      0x0930
> >> +#define DEVICE_PATGEN_LONG_DATA_GB                     0x0932
> >> +#define DEVICE_PATGEN_SHORT_DATA_GR                    0x0950
> >> +#define DEVICE_PATGEN_SHORT_DATA_R                     0x0952
> >> +#define DEVICE_PATGEN_SHORT_DATA_B                     0x0954
> >> +#define DEVICE_PATGEN_SHORT_DATA_GB                    0x0956
> >> +#define DEVICE_BYPASS_CTRL                             0x0a60
> >> +
> >> +#define V4L2_CID_HDR                           (V4L2_CID_USER_BASE | 0x1004)
> >> +#define V4L2_CID_GPIOS_STROBE_LONG_START_DELAY (V4L2_CID_USER_BASE | 0x1019)
> >> +#define V4L2_CID_GPIOS_STROBE_LONG_END_DELAY   (V4L2_CID_USER_BASE | 0x101a)
> >> +#define V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY        (V4L2_CID_USER_BASE | 0x101b)
> >> +#define V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY  (V4L2_CID_USER_BASE | 0x101c)
> >> +#define V4L2_CID_TEMPERATURE                   (V4L2_CID_USER_BASE | 0x1020)
> >> +
> >> +#define DEVICE_FWPATCH_REVISION_MAJOR                  2
> >> +#define DEVICE_FWPATCH_REVISION_MINOR                  0
> >> +#define DEVICE_FWPATCH_REVISION_MICRO                  5
> >> +
> >> +#define WRITE_MULTIPLE_CHUNK_MAX                       16
> >> +#define NB_GPIOS                                       4
> >> +#define NB_POLARITIES                                  5
> >> +
> >> +static const u8 patch_array[] = {
> >> +       0xbf, 0x00, 0x05, 0x20, 0x06, 0x01, 0xe0, 0xe0, 0x04, 0x80, 0xe6, 0x45,
> >> +       0xed, 0x6f, 0xfe, 0xff, 0x14, 0x80, 0x1f, 0x84, 0x10, 0x42, 0x05, 0x7c,
> >> +       0x01, 0xc4, 0x1e, 0x80, 0xb6, 0x42, 0x00, 0xe0, 0x1e, 0x82, 0x1e, 0xc0,
> >> +       0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa, 0x86, 0x0d, 0x70, 0xe1,
> >> +       0x04, 0x98, 0x15, 0x00, 0x28, 0xe0, 0x14, 0x02, 0x08, 0xfc, 0x15, 0x40,
> >> +       0x28, 0xe0, 0x98, 0x58, 0xe0, 0xef, 0x04, 0x98, 0x0e, 0x04, 0x00, 0xf0,
> >> +       0x15, 0x00, 0x28, 0xe0, 0x19, 0xc8, 0x15, 0x40, 0x28, 0xe0, 0xc6, 0x41,
> >> +       0xfc, 0xe0, 0x14, 0x80, 0x1f, 0x84, 0x14, 0x02, 0xa0, 0xfc, 0x1e, 0x80,
> >> +       0x14, 0x80, 0x14, 0x02, 0x80, 0xfb, 0x14, 0x02, 0xe0, 0xfc, 0x1e, 0x80,
> >> +       0x14, 0xc0, 0x1f, 0x84, 0x14, 0x02, 0xa4, 0xfc, 0x1e, 0xc0, 0x14, 0xc0,
> >> +       0x14, 0x02, 0x80, 0xfb, 0x14, 0x02, 0xe4, 0xfc, 0x1e, 0xc0, 0x0c, 0x0c,
> >> +       0x00, 0xf2, 0x93, 0xdd, 0x86, 0x00, 0xf8, 0xe0, 0x04, 0x80, 0xc6, 0x03,
> >> +       0x70, 0xe1, 0x0e, 0x84, 0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa,
> >> +       0x6b, 0x80, 0x06, 0x40, 0x6c, 0xe1, 0x04, 0x80, 0x09, 0x00, 0xe0, 0xe0,
> >> +       0x0b, 0xa1, 0x95, 0x84, 0x05, 0x0c, 0x1c, 0xe0, 0x86, 0x02, 0xf9, 0x60,
> >> +       0xe0, 0xcf, 0x78, 0x6e, 0x80, 0xef, 0x25, 0x0c, 0x18, 0xe0, 0x05, 0x4c,
> >> +       0x1c, 0xe0, 0x86, 0x02, 0xf9, 0x60, 0xe0, 0xcf, 0x0b, 0x84, 0xd8, 0x6d,
> >> +       0x80, 0xef, 0x05, 0x4c, 0x18, 0xe0, 0x04, 0xd8, 0x0b, 0xa5, 0x95, 0x84,
> >> +       0x05, 0x0c, 0x2c, 0xe0, 0x06, 0x02, 0x01, 0x60, 0xe0, 0xce, 0x18, 0x6d,
> >> +       0x80, 0xef, 0x25, 0x0c, 0x30, 0xe0, 0x05, 0x4c, 0x2c, 0xe0, 0x06, 0x02,
> >> +       0x01, 0x60, 0xe0, 0xce, 0x0b, 0x84, 0x78, 0x6c, 0x80, 0xef, 0x05, 0x4c,
> >> +       0x30, 0xe0, 0x0c, 0x0c, 0x00, 0xf2, 0x93, 0xdd, 0x46, 0x01, 0x70, 0xe1,
> >> +       0x08, 0x80, 0x0b, 0xa1, 0x08, 0x5c, 0x00, 0xda, 0x06, 0x01, 0x68, 0xe1,
> >> +       0x04, 0x80, 0x4a, 0x40, 0x84, 0xe0, 0x08, 0x5c, 0x00, 0x9a, 0x06, 0x01,
> >> +       0xe0, 0xe0, 0x04, 0x80, 0x15, 0x00, 0x60, 0xe0, 0x19, 0xc4, 0x15, 0x40,
> >> +       0x60, 0xe0, 0x15, 0x00, 0x78, 0xe0, 0x19, 0xc4, 0x15, 0x40, 0x78, 0xe0,
> >> +       0x93, 0xdd, 0xc3, 0xc1, 0x46, 0x01, 0x70, 0xe1, 0x08, 0x80, 0x0b, 0xa1,
> >> +       0x08, 0x5c, 0x00, 0xda, 0x06, 0x01, 0x68, 0xe1, 0x04, 0x80, 0x4a, 0x40,
> >> +       0x84, 0xe0, 0x08, 0x5c, 0x00, 0x9a, 0x06, 0x01, 0xe0, 0xe0, 0x14, 0x80,
> >> +       0x25, 0x02, 0x54, 0xe0, 0x29, 0xc4, 0x25, 0x42, 0x54, 0xe0, 0x24, 0x80,
> >> +       0x35, 0x04, 0x6c, 0xe0, 0x39, 0xc4, 0x35, 0x44, 0x6c, 0xe0, 0x25, 0x02,
> >> +       0x64, 0xe0, 0x29, 0xc4, 0x25, 0x42, 0x64, 0xe0, 0x04, 0x80, 0x15, 0x00,
> >> +       0x7c, 0xe0, 0x19, 0xc4, 0x15, 0x40, 0x7c, 0xe0, 0x93, 0xdd, 0xc3, 0xc1,
> >> +       0x4c, 0x04, 0x7c, 0xfa, 0x86, 0x40, 0x98, 0xe0, 0x14, 0x80, 0x1b, 0xa1,
> >> +       0x06, 0x00, 0x00, 0xc0, 0x08, 0x42, 0x38, 0xdc, 0x08, 0x64, 0xa0, 0xef,
> >> +       0x86, 0x42, 0x3c, 0xe0, 0x68, 0x49, 0x80, 0xef, 0x6b, 0x80, 0x78, 0x53,
> >> +       0xc8, 0xef, 0xc6, 0x54, 0x6c, 0xe1, 0x7b, 0x80, 0xb5, 0x14, 0x0c, 0xf8,
> >> +       0x05, 0x14, 0x14, 0xf8, 0x1a, 0xac, 0x8a, 0x80, 0x0b, 0x90, 0x38, 0x55,
> >> +       0x80, 0xef, 0x1a, 0xae, 0x17, 0xc2, 0x03, 0x82, 0x88, 0x65, 0x80, 0xef,
> >> +       0x1b, 0x80, 0x0b, 0x8e, 0x68, 0x65, 0x80, 0xef, 0x9b, 0x80, 0x0b, 0x8c,
> >> +       0x08, 0x65, 0x80, 0xef, 0x6b, 0x80, 0x0b, 0x92, 0x1b, 0x8c, 0x98, 0x64,
> >> +       0x80, 0xef, 0x1a, 0xec, 0x9b, 0x80, 0x0b, 0x90, 0x95, 0x54, 0x10, 0xe0,
> >> +       0xa8, 0x53, 0x80, 0xef, 0x1a, 0xee, 0x17, 0xc2, 0x03, 0x82, 0xf8, 0x63,
> >> +       0x80, 0xef, 0x1b, 0x80, 0x0b, 0x8e, 0xd8, 0x63, 0x80, 0xef, 0x1b, 0x8c,
> >> +       0x68, 0x63, 0x80, 0xef, 0x6b, 0x80, 0x0b, 0x92, 0x65, 0x54, 0x14, 0xe0,
> >> +       0x08, 0x65, 0x84, 0xef, 0x68, 0x63, 0x80, 0xef, 0x7b, 0x80, 0x0b, 0x8c,
> >> +       0xa8, 0x64, 0x84, 0xef, 0x08, 0x63, 0x80, 0xef, 0x14, 0xe8, 0x46, 0x44,
> >> +       0x94, 0xe1, 0x24, 0x88, 0x4a, 0x4e, 0x04, 0xe0, 0x14, 0xea, 0x1a, 0x04,
> >> +       0x08, 0xe0, 0x0a, 0x40, 0x84, 0xed, 0x0c, 0x04, 0x00, 0xe2, 0x4a, 0x40,
> >> +       0x04, 0xe0, 0x19, 0x16, 0xc0, 0xe0, 0x0a, 0x40, 0x84, 0xed, 0x21, 0x54,
> >> +       0x60, 0xe0, 0x0c, 0x04, 0x00, 0xe2, 0x1b, 0xa5, 0x0e, 0xea, 0x01, 0x89,
> >> +       0x21, 0x54, 0x64, 0xe0, 0x7e, 0xe8, 0x65, 0x82, 0x1b, 0xa7, 0x26, 0x00,
> >> +       0x00, 0x80, 0xa5, 0x82, 0x1b, 0xa9, 0x65, 0x82, 0x1b, 0xa3, 0x01, 0x85,
> >> +       0x16, 0x00, 0x00, 0xc0, 0x01, 0x54, 0x04, 0xf8, 0x06, 0xaa, 0x01, 0x83,
> >> +       0x06, 0xa8, 0x65, 0x81, 0x06, 0xa8, 0x01, 0x54, 0x04, 0xf8, 0x01, 0x83,
> >> +       0x06, 0xaa, 0x09, 0x14, 0x18, 0xf8, 0x0b, 0xa1, 0x05, 0x84, 0xc6, 0x42,
> >> +       0xd4, 0xe0, 0x14, 0x84, 0x01, 0x83, 0x01, 0x54, 0x60, 0xe0, 0x01, 0x54,
> >> +       0x64, 0xe0, 0x0b, 0x02, 0x90, 0xe0, 0x10, 0x02, 0x90, 0xe5, 0x01, 0x54,
> >> +       0x88, 0xe0, 0xb5, 0x81, 0xc6, 0x40, 0xd4, 0xe0, 0x14, 0x80, 0x0b, 0x02,
> >> +       0xe0, 0xe4, 0x10, 0x02, 0x31, 0x66, 0x02, 0xc0, 0x01, 0x54, 0x88, 0xe0,
> >> +       0x1a, 0x84, 0x29, 0x14, 0x10, 0xe0, 0x1c, 0xaa, 0x2b, 0xa1, 0xf5, 0x82,
> >> +       0x25, 0x14, 0x10, 0xf8, 0x2b, 0x04, 0xa8, 0xe0, 0x20, 0x44, 0x0d, 0x70,
> >> +       0x03, 0xc0, 0x2b, 0xa1, 0x04, 0x00, 0x80, 0x9a, 0x02, 0x40, 0x84, 0x90,
> >> +       0x03, 0x54, 0x04, 0x80, 0x4c, 0x0c, 0x7c, 0xf2, 0x93, 0xdd, 0x00, 0x00,
> >> +       0x02, 0xa9, 0x00, 0x00, 0x64, 0x4a, 0x40, 0x00, 0x08, 0x2d, 0x58, 0xe0,
> >> +       0xa8, 0x98, 0x40, 0x00, 0x28, 0x07, 0x34, 0xe0, 0x05, 0xb9, 0x00, 0x00,
> >> +       0x28, 0x00, 0x41, 0x05, 0x88, 0x00, 0x41, 0x3c, 0x98, 0x00, 0x41, 0x52,
> >> +       0x04, 0x01, 0x41, 0x79, 0x3c, 0x01, 0x41, 0x6a, 0x3d, 0xfe, 0x00, 0x00,
> >> +};
> >> +
> >> +static const char * const vgxy61_test_pattern_menu[] = {
> >> +       "Disabled",
> >> +       "Solid",
> >> +       "Colorbar",
> >> +       "Gradbar",
> >> +       "Hgrey",
> >> +       "Vgrey",
> >> +       "Dgrey",
> >> +       "PN28",
> >> +};
> >> +
> >> +static const char * const vgxy61_hdr_modes[] = {
> >> +       "HDR linearize",
> >> +       "HDR substraction",
> >> +       "no HDR",
> >> +};
> >> +
> >> +/* Regulator supplies */
> >> +static const char * const vgxy61_supply_name[] = {
> >> +       "VCORE",
> >> +       "VDDIO",
> >> +       "VANA",
> >> +};
> >> +
> >> +static const s64 link_freq[] = {
> >> +       /*
> >> +        * MIPI output freq is 804Mhz / 2, as it uses both rising edge and falling edges to send
> >> +        * data
> >> +        */
> >> +       402000000ULL
> >> +};
> >> +
> >> +#define VGXY61_NUM_SUPPLIES            ARRAY_SIZE(vgxy61_supply_name)
> >> +
> >> +/* Macro to convert index to 8.8 fixed point gain */
> >> +#define I2FP(i)                                ((u32)(8192.0 / (32 - (i))))
> >> +/* Array of possibles analog gains in 8.8 fixed point */
> >> +static const u16 analog_gains[] = {
> >> +       I2FP(0), I2FP(1), I2FP(2), I2FP(3), I2FP(4), I2FP(5), I2FP(6), I2FP(7), I2FP(8), I2FP(9),
> >> +       I2FP(10), I2FP(11), I2FP(12), I2FP(13), I2FP(14), I2FP(15), I2FP(16),
> >> +};
> >> +
> >> +enum bin_mode {
> >> +       BIN_MODE_NORMAL,
> >> +       BIN_MODE_DIGITAL_X2,
> >> +       BIN_MODE_DIGITAL_X4,
> >> +       BIN_MODE_ANALOG_SUB_X2,
> >> +       BIN_MODE_ANALOG_SUB_X4,
> >> +};
> >> +
> >> +enum hdr {
> >> +       HDR_LINEAR,
> >> +       HDR_SUB,
> >> +       NO_HDR,
> >> +};
> >> +
> >> +enum strobe_modes {
> >> +       STROBE_DISABLED,
> >> +       STROBE_LONG,
> >> +       STROBE_ENABLED,
> >> +};
> >> +
> >> +struct vgxy61_mode_info {
> >> +       u32 width;
> >> +       u32 height;
> >> +       enum bin_mode bin_mode;
> >> +};
> >> +
> >> +static const u32 vgxy61_supported_codes[] = {
> >> +       MEDIA_BUS_FMT_SGBRG8_1X8,
> >> +       MEDIA_BUS_FMT_SGBRG10_1X10,
> >> +       MEDIA_BUS_FMT_SGBRG12_1X12,
> >> +       MEDIA_BUS_FMT_SGBRG14_1X14,
> >> +       MEDIA_BUS_FMT_SGBRG16_1X16
> >> +};
> >> +
> >> +const int vgx761_sensor_frame_rates[] = {75, 60, 30, 15, 10, 5, 2};
> >> +const int vgx661_sensor_frame_rates[] = {60, 30, 15, 10, 5, 2};
> >> +
> >> +static const struct vgxy61_mode_info vgx661_mode_data[] = {
> >> +       {1464, 1104, BIN_MODE_NORMAL},
> >> +       {1280,  720, BIN_MODE_NORMAL},
> >> +       { 640,  480, BIN_MODE_DIGITAL_X2},
> >> +       { 320,  240, BIN_MODE_DIGITAL_X4},
> >> +};
> >> +
> >> +static const struct vgxy61_mode_info vgx761_mode_data[] = {
> >> +       {1944, 1204, BIN_MODE_NORMAL},
> >> +       {1920, 1080, BIN_MODE_NORMAL},
> >> +       {1280,  720, BIN_MODE_NORMAL},
> >> +       { 640,  480, BIN_MODE_DIGITAL_X2},
> >> +       { 320,  240, BIN_MODE_DIGITAL_X4},
> >> +};
> >> +
> >> +struct gpios_ctrls {
> >> +       struct v4l2_ctrl *long_start;
> >> +       struct v4l2_ctrl *long_end;
> >> +       struct v4l2_ctrl *short_start;
> >> +       struct v4l2_ctrl *short_end;
> >> +};
> >> +
> >> +struct vgxy61_ctrls {
> >> +       struct v4l2_ctrl_handler handler;
> >> +       struct v4l2_ctrl *exposure;
> >> +       struct v4l2_ctrl *analog_gain;
> >> +       struct v4l2_ctrl *digital_gain;
> >> +       struct gpios_ctrls gpios;
> >> +       struct v4l2_ctrl *vflip;
> >> +       struct v4l2_ctrl *hflip;
> >> +       struct v4l2_ctrl *patgen;
> >> +       struct v4l2_ctrl *hdr;
> >> +       struct v4l2_ctrl *pixel_rate;
> >> +       struct v4l2_ctrl *link_freq;
> >> +       struct v4l2_ctrl *temp;
> >> +};
> >> +
> >> +struct vgxy61_dev {
> >> +       struct i2c_client *i2c_client;
> >> +       struct v4l2_subdev sd;
> >> +       struct media_pad pad;
> >> +       struct regulator_bulk_data supplies[VGXY61_NUM_SUPPLIES];
> >> +       struct gpio_desc *reset_gpio;
> >> +       struct clk *xclk;
> >> +       u32 clk_freq;
> >> +       int sensor_width;
> >> +       int sensor_height;
> >> +       u16 oif_ctrl;
> >> +       int nb_of_lane;
> >> +       int data_rate_in_mbps;
> >> +       int pclk;
> >> +       u16 line_length;
> >> +       int rot_term;
> >> +       bool gpios_polarity;
> >> +       bool slave_mode;
> >> +       /* Lock to protect all members below */
> >> +       struct mutex lock;
> >> +       struct vgxy61_ctrls ctrls;
> >> +       bool streaming;
> >> +       struct v4l2_mbus_framefmt fmt;
> >> +       const struct vgxy61_mode_info *sensor_modes;
> >> +       int sensor_modes_nb;
> >> +       const struct vgxy61_mode_info *current_mode;
> >> +       const int *sensor_rates;
> >> +       int sensor_rates_nb;
> >> +       struct v4l2_fract frame_interval;
> >> +       bool hflip;
> >> +       bool vflip;
> >> +       enum hdr hdr;
> >> +       int expo_long;
> >> +       int expo_short;
> >> +};
> >> +
> >> +static u8 get_bpp_by_code(__u32 code)
> >> +{
> >> +       switch (code) {
> >> +       case MEDIA_BUS_FMT_SGBRG8_1X8:
> >> +               return 8;
> >> +       case MEDIA_BUS_FMT_SGBRG10_1X10:
> >> +               return 10;
> >> +       case MEDIA_BUS_FMT_SGBRG12_1X12:
> >> +               return 12;
> >> +       case MEDIA_BUS_FMT_SGBRG14_1X14:
> >> +               return 14;
> >> +       case MEDIA_BUS_FMT_SGBRG16_1X16:
> >> +               return 16;
> >> +       default:
> >> +               /* Should never happen */
> >> +               WARN(1, "Unsupported code %d. default to 8 bpp", code);
> >> +               return 8;
> >> +       }
> >> +}
> >> +
> >> +static u8 get_data_type_by_code(__u32 code)
> >> +{
> >> +       switch (code) {
> >> +       case MEDIA_BUS_FMT_SGBRG8_1X8:
> >> +               return MIPI_CSI2_DT_RAW8;
> >> +       case MEDIA_BUS_FMT_SGBRG10_1X10:
> >> +               return MIPI_CSI2_DT_RAW10;
> >> +       case MEDIA_BUS_FMT_SGBRG12_1X12:
> >> +               return MIPI_CSI2_DT_RAW12;
> >> +       case MEDIA_BUS_FMT_SGBRG14_1X14:
> >> +               return MIPI_CSI2_DT_RAW14;
> >> +       case MEDIA_BUS_FMT_SGBRG16_1X16:
> >> +               return MIPI_CSI2_DT_RAW16;
> >> +       default:
> >> +               /* Should never happen */
> >> +               WARN(1, "Unsupported code %d. default to MIPI_CSI2_DT_RAW8 data type", code);
> >> +               return MIPI_CSI2_DT_RAW8;
> >> +       }
> >> +}
> >> +
> >> +static void compute_pll_parameters_by_freq(u32 freq, unsigned int *prediv, unsigned int *mult)
> >> +{
> >> +       const unsigned int predivs[] = {1, 2, 4};
> >> +       int i;
> >> +
> >> +       /*
> >> +        * Freq range is [6Mhz-27Mhz] already checked.
> >> +        * Output of divider should be in [6Mhz-12Mhz[.
> >> +        */
> >> +       for (i = 0; i < ARRAY_SIZE(predivs); i++) {
> >> +               *prediv = predivs[i];
> >> +               if (freq / *prediv < 12 * HZ_PER_MHZ)
> >> +                       break;
> >> +       }
> >> +       WARN_ON(i == ARRAY_SIZE(predivs));
> >> +
> >> +       /*
> >> +        * Target freq is 804Mhz. Don't change this as it will impact image quality.
> >> +        */
> >> +       *mult = ((804 * HZ_PER_MHZ) * (*prediv) + freq / 2) / freq;
> >> +}
> >> +
> >> +static s32 get_pixel_rate(struct vgxy61_dev *sensor)
> >> +{
> >> +       return div64_u64((u64)sensor->data_rate_in_mbps * sensor->nb_of_lane,
> >> +                        get_bpp_by_code(sensor->fmt.code));
> >> +}
> >> +
> >> +static inline struct vgxy61_dev *to_vgxy61_dev(struct v4l2_subdev *sd)
> >> +{
> >> +       return container_of(sd, struct vgxy61_dev, sd);
> >> +}
> >> +
> >> +static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl)
> >> +{
> >> +       return &container_of(ctrl->handler, struct vgxy61_dev, ctrls.handler)->sd;
> >> +}
> >> +
> >> +static int get_chunk_size(struct vgxy61_dev *sensor)
> >> +{
> >> +       struct i2c_adapter *adapter = sensor->i2c_client->adapter;
> >> +       int max_write_len = WRITE_MULTIPLE_CHUNK_MAX;
> >> +
> >> +       if (adapter->quirks && adapter->quirks->max_write_len)
> >> +               max_write_len = adapter->quirks->max_write_len - 2;
> >> +
> >> +       max_write_len = min(max_write_len, WRITE_MULTIPLE_CHUNK_MAX);
> >> +
> >> +       return max(max_write_len, 1);
> >> +}
> >> +
> >> +static int vgxy61_read_multiple(struct vgxy61_dev *sensor, u16 reg, u8 *val, int len)
> >> +{
> >> +       struct i2c_client *client = sensor->i2c_client;
> >> +       struct i2c_msg msg[2];
> >> +       u8 buf[2];
> >> +       int ret;
> >> +
> >> +       buf[0] = reg >> 8;
> >> +       buf[1] = reg & 0xff;
> >> +
> >> +       msg[0].addr = client->addr;
> >> +       msg[0].flags = client->flags;
> >> +       msg[0].buf = buf;
> >> +       msg[0].len = sizeof(buf);
> >> +
> >> +       msg[1].addr = client->addr;
> >> +       msg[1].flags = client->flags | I2C_M_RD;
> >> +       msg[1].buf = val;
> >> +       msg[1].len = len;
> >> +
> >> +       ret = i2c_transfer(client->adapter, msg, 2);
> >> +       if (ret < 0) {
> >> +               dev_dbg(&client->dev, "%s: %x i2c_transfer, reg: %x => %d\n", __func__,
> >> +                       client->addr, reg, ret);
> >> +               return ret;
> >> +       }
> >> +
> >> +       return 0;
> >> +}
> >> +
> >> +static inline int vgxy61_read_reg(struct vgxy61_dev *sensor, u16 reg, u8 *val)
> >> +{
> >> +       return vgxy61_read_multiple(sensor, reg, val, sizeof(*val));
> >> +}
> >> +
> >> +static inline int vgxy61_read_reg16(struct vgxy61_dev *sensor, u16 reg, u16 *val)
> >> +{
> >> +       return vgxy61_read_multiple(sensor, reg, (u8 *)val, sizeof(*val));
> >> +}
> >> +
> >> +static int vgxy61_write_multiple(struct vgxy61_dev *sensor, u16 reg, const u8 *data, int len)
> >> +{
> >> +       struct i2c_client *client = sensor->i2c_client;
> >> +       struct i2c_msg msg;
> >> +       u8 buf[WRITE_MULTIPLE_CHUNK_MAX + 2];
> >> +       int i;
> >> +       int ret;
> >> +
> >> +       if (len > WRITE_MULTIPLE_CHUNK_MAX)
> >> +               return -EINVAL;
> >> +       buf[0] = reg >> 8;
> >> +       buf[1] = reg & 0xff;
> >> +       for (i = 0; i < len; i++)
> >> +               buf[i + 2] = data[i];
> >> +
> >> +       msg.addr = client->addr;
> >> +       msg.flags = client->flags;
> >> +       msg.buf = buf;
> >> +       msg.len = len + 2;
> >> +
> >> +       ret = i2c_transfer(client->adapter, &msg, 1);
> >> +       if (ret < 0) {
> >> +               dev_dbg(&client->dev, "%s: i2c_transfer, reg: %x => %d\n", __func__, reg, ret);
> >> +               return ret;
> >> +       }
> >> +
> >> +       return 0;
> >> +}
> >> +
> >> +static int vgxy61_write_array(struct vgxy61_dev *sensor, u16 reg, int nb, const u8 *array)
> >> +{
> >> +       const int chunk_size = get_chunk_size(sensor);
> >> +       int ret;
> >> +       int sz;
> >> +
> >> +       while (nb) {
> >> +               sz = min(nb, chunk_size);
> >> +               ret = vgxy61_write_multiple(sensor, reg, array, sz);
> >> +               if (ret < 0)
> >> +                       return ret;
> >> +               nb -= sz;
> >> +               reg += sz;
> >> +               array += sz;
> >> +       }
> >> +
> >> +       return 0;
> >> +}
> >> +
> >> +static inline int vgxy61_write_reg(struct vgxy61_dev *sensor, u16 reg, u8 val)
> >> +{
> >> +       return vgxy61_write_multiple(sensor, reg, &val, sizeof(val));
> >> +}
> >> +
> >> +static inline int vgxy61_write_reg16(struct vgxy61_dev *sensor, u16 reg, u16 val)
> >> +{
> >> +       return vgxy61_write_multiple(sensor, reg, (u8 *)&val, sizeof(val));
> >> +}
> >> +
> >> +static inline int vgxy61_write_reg32(struct vgxy61_dev *sensor, u16 reg, u32 val)
> >> +{
> >> +       return vgxy61_write_multiple(sensor, reg, (u8 *)&val, sizeof(val));
> >> +}
> >> +
> >> +static int vgxy61_poll_reg(struct vgxy61_dev *sensor, u16 reg, u8 poll_val)
> >> +{
> >> +       const int loop_delay_ms = 10;
> >> +       const int timeout_ms = 500;
> >> +       u8 val;
> >> +       int ret, timeout;
> >> +
> >> +       timeout = read_poll_timeout(vgxy61_read_reg, ret, ((ret != 0) || (val == poll_val)),
> >> +                                   loop_delay_ms * US_PER_MS, timeout_ms * US_PER_MS, false,
> >> +                                   sensor, reg, &val);
> >> +       if (timeout)
> >> +               return timeout;
> >> +
> >> +       return ret;
> >> +}
> >> +
> >> +static int vgxy61_wait_state(struct vgxy61_dev *sensor, int state)
> >> +{
> >> +       return vgxy61_poll_reg(sensor, DEVICE_SYSTEM_FSM, state);
> >> +}
> >> +
> >> +static int vgxy61_check_bw(struct vgxy61_dev *sensor)
> >> +{
> >> +       struct i2c_client *client = sensor->i2c_client;
> >> +       /* Correction factor for time required between 2 lines */
> >> +       const int mipi_margin = 1056;
> >> +       int binning_scale = 1 << sensor->current_mode->bin_mode;
> >> +       int bpp = get_bpp_by_code(sensor->fmt.code);
> >> +       int max_bit_per_line;
> >> +       int bit_per_line;
> >> +       u64 line_rate;
> >> +
> >> +       line_rate = sensor->nb_of_lane * (u64)sensor->data_rate_in_mbps * sensor->line_length;
> >> +       max_bit_per_line = div64_u64(line_rate, sensor->pclk) - mipi_margin;
> >> +       bit_per_line = (bpp * sensor->current_mode->width) / binning_scale;
> >> +
> >> +       dev_dbg(&client->dev, "max_bit_per_line = %d\n", max_bit_per_line);
> >> +       dev_dbg(&client->dev, "required bit_per_line = %d\n", bit_per_line);
> >> +
> >> +       return bit_per_line > max_bit_per_line ? -EINVAL : 0;
> >> +}
> >> +
> >> +static int apply_exposure(struct vgxy61_dev *sensor)
> >> +{
> >> +       struct i2c_client *client = sensor->i2c_client;
> >> +       int ret;
> >> +
> >> +        /* We first set expo to zero to avoid forbidden parameters couple */
> >> +       ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_SHORT, 0);
> >> +       if (ret)
> >> +               return ret;
> >> +       ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_LONG, sensor->expo_long);
> >> +       if (ret)
> >> +               return ret;
> >> +       ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_SHORT, sensor->expo_short);
> >> +       if (ret)
> >> +               return ret;
> >> +
> >> +       dev_dbg(&client->dev, "%s applied expo %d (short: %d)\n", __func__,
> >> +               sensor->expo_long, sensor->expo_short);
> >> +
> >> +       return 0;
> >> +}
> >> +
> >> +static int set_frame_rate(struct vgxy61_dev *sensor)
> >> +{
> >> +       u16 frame_length;
> >> +
> >> +       frame_length = sensor->pclk / (sensor->line_length * sensor->frame_interval.denominator);
> >> +
> >> +       return vgxy61_write_reg16(sensor, DEVICE_FRAME_LENGTH, frame_length);
> >> +}
> >> +
> >> +static int vgxy61_get_regulators(struct vgxy61_dev *sensor)
> >> +{
> >> +       int i;
> >> +
> >> +       for (i = 0; i < VGXY61_NUM_SUPPLIES; i++)
> >> +               sensor->supplies[i].supply = vgxy61_supply_name[i];
> >> +
> >> +       return devm_regulator_bulk_get(&sensor->i2c_client->dev, VGXY61_NUM_SUPPLIES,
> >> +                                      sensor->supplies);
> >> +}
> >> +
> >> +static int vgxy61_apply_reset(struct vgxy61_dev *sensor)
> >> +{
> >> +       struct i2c_client *client = sensor->i2c_client;
> >> +
> >> +       dev_dbg(&client->dev, "%s applied reset\n", __func__);
> >> +       gpiod_set_value_cansleep(sensor->reset_gpio, 0);
> >> +       usleep_range(5000, 10000);
> >> +       gpiod_set_value_cansleep(sensor->reset_gpio, 1);
> >> +       usleep_range(5000, 10000);
> >> +       gpiod_set_value_cansleep(sensor->reset_gpio, 0);
> >> +       usleep_range(40000, 100000);
> >> +       return vgxy61_wait_state(sensor, SW_STBY);
> >> +}
> >> +
> >> +static int vgxy61_try_fmt_internal(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *fmt,
> >> +                                  const struct vgxy61_mode_info **new_mode)
> >> +{
> >> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >> +       const struct vgxy61_mode_info *mode = sensor->sensor_modes;
> >> +       unsigned int index;
> >> +
> >> +       /* Select code */
> >> +       for (index = 0; index < ARRAY_SIZE(vgxy61_supported_codes); index++) {
> >> +               if (vgxy61_supported_codes[index] == fmt->code)
> >> +                       break;
> >> +       }
> >> +       if (index == ARRAY_SIZE(vgxy61_supported_codes))
> >> +               index = 0;
> >> +
> >> +       /* Select size */
> >> +       do {
> >> +               if (mode->width <= fmt->width && mode->height <= fmt->height)
> >> +                       break;
> >> +       } while ((++mode)->width);
> >> +       if (!mode->width)
> >> +               mode--;
> >> +
> >> +       *new_mode = mode;
> >> +       fmt->code = vgxy61_supported_codes[index];
> >> +       fmt->width = mode->width;
> >> +       fmt->height = mode->height;
> >> +       fmt->colorspace = V4L2_COLORSPACE_SRGB;
> >> +       fmt->field = V4L2_FIELD_NONE;
> >> +
> >> +       return 0;
> >> +}
> >> +
> >> +static int vgxy61_stream_enable(struct vgxy61_dev *sensor)
> >> +{
> >> +       int center_x = sensor->sensor_width / 2;
> >> +       int center_y = sensor->sensor_height / 2;
> >> +       int scale = 1 << sensor->current_mode->bin_mode;
> >> +       int width = sensor->current_mode->width * scale;
> >> +       int height = sensor->current_mode->height * scale;
> >> +       int ret;
> >> +
> >> +       ret = vgxy61_check_bw(sensor);
> >> +       if (ret)
> >> +               return ret;
> >> +
> >> +       /* Configure sensor */
> >> +       ret = vgxy61_write_reg(sensor, DEVICE_FORMAT_CTRL, get_bpp_by_code(sensor->fmt.code));
> >> +       if (ret)
> >> +               return ret;
> >> +       ret = vgxy61_write_reg(sensor, DEVICE_OIF_ROI0_CTRL,
> >> +                              get_data_type_by_code(sensor->fmt.code));
> >> +       if (ret)
> >> +               return ret;
> >> +
> >> +       ret = vgxy61_write_reg(sensor, DEVICE_READOUT_CTRL, sensor->current_mode->bin_mode);
> >> +       if (ret)
> >> +               return ret;
> >> +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_H, center_x - width / 2);
> >> +       if (ret)
> >> +               return ret;
> >> +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_H, center_x + width / 2 - 1);
> >> +       if (ret)
> >> +               return ret;
> >> +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_V, center_y - height / 2);
> >> +       if (ret)
> >> +               return ret;
> >> +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_V, center_y + height / 2 - 1);
> >> +       if (ret)
> >> +               return ret;
> >> +
> >> +       ret = set_frame_rate(sensor);
> >> +       if (ret)
> >> +               return ret;
> >> +
> >> +       ret = apply_exposure(sensor);
> >> +       if (ret)
> >> +               return ret;
> >> +
> >> +       /* Start streaming */
> >> +       ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_START_STREAMING);
> >> +       if (ret)
> >> +               return ret;
> >> +
> >> +       ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST);
> >> +       if (ret)
> >> +               return ret;
> >> +
> >> +       ret = vgxy61_wait_state(sensor, STREAMING);
> >> +       if (ret)
> >> +               return ret;
> >> +
> >> +       return 0;
> >> +}
> >> +
> >> +static int vgxy61_stream_disable(struct vgxy61_dev *sensor)
> >> +{
> >> +       int ret;
> >> +
> >> +       ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_STOP_STREAMING);
> >> +       if (ret)
> >> +               return ret;
> >> +
> >> +       ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST);
> >> +       if (ret)
> >> +               return ret;
> >> +
> >> +       ret = vgxy61_wait_state(sensor, SW_STBY);
> >> +       if (ret)
> >> +               return ret;
> >> +
> >> +       return 0;
> >> +}
> >> +
> >> +static int vgxy61_s_stream(struct v4l2_subdev *sd, int enable)
> >> +{
> >> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >> +       struct i2c_client *client = sensor->i2c_client;
> >> +       int ret = 0;
> >> +
> >> +       mutex_lock(&sensor->lock);
> >> +       dev_dbg(&client->dev, "%s : requested %d / current = %d\n", __func__, enable,
> >> +               sensor->streaming);
> >> +       if (sensor->streaming == enable)
> >> +               goto out;
> >> +
> >> +       ret = enable ? vgxy61_stream_enable(sensor) : vgxy61_stream_disable(sensor);
> >> +       if (!ret)
> >> +               sensor->streaming = enable;
> >> +
> >> +out:
> >> +       dev_dbg(&client->dev, "%s current now = %d / %d\n", __func__, sensor->streaming, ret);
> >> +       mutex_unlock(&sensor->lock);
> >> +
> >> +       return ret;
> >> +}
> >> +
> >> +static int vgxy61_g_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *fi)
> >> +{
> >> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >> +
> >> +       mutex_lock(&sensor->lock);
> >> +       fi->interval = sensor->frame_interval;
> >> +       mutex_unlock(&sensor->lock);
> >> +
> >> +       return 0;
> >> +}
> >> +
> >> +static int vgxy61_s_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *fi)
> >> +{
> >> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >> +       struct i2c_client *client = sensor->i2c_client;
> >> +       u64 req_int, err, min_err = ~0ULL;
> >> +       u64 test_int;
> >> +       int i = 0;
> >> +       int ret;
> >> +
> >> +       if (fi->interval.denominator == 0)
> >> +               return -EINVAL;
> >> +
> >> +       mutex_lock(&sensor->lock);
> >> +
> >> +       if (sensor->streaming) {
> >> +               ret = -EBUSY;
> >> +               goto out;
> >> +       }
> >> +
> >> +       dev_dbg(&client->dev, "%s request %d/%d\n", __func__,
> >> +               fi->interval.numerator, fi->interval.denominator);
> >> +       /* Find nearest period */
> >> +       req_int = div64_u64((u64)(fi->interval.numerator * 10000), fi->interval.denominator);
> >> +       for (i = 0; i < sensor->sensor_rates_nb; i++) {
> >> +               test_int = div64_u64((u64)10000, sensor->sensor_rates[i]);
> >> +               err = abs(test_int - req_int);
> >> +               if (err < min_err) {
> >> +                       fi->interval.numerator = 1;
> >> +                       fi->interval.denominator = sensor->sensor_rates[i];
> >> +                       min_err = err;
> >> +               }
> >> +       }
> >> +       sensor->frame_interval = fi->interval;
> >> +       dev_dbg(&client->dev, "%s set     %d/%d\n", __func__,
> >> +               fi->interval.numerator, fi->interval.denominator);
> >> +
> >> +       ret = 0;
> >> +out:
> >> +       mutex_unlock(&sensor->lock);
> >> +
> >> +       return ret;
> >> +}
> >> +
> >> +static int vgxy61_enum_mbus_code(struct v4l2_subdev *sd,
> >> +                                struct v4l2_subdev_state *sd_state,
> >> +                                struct v4l2_subdev_mbus_code_enum *code)
> >> +{
> >> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >> +       struct i2c_client *client = sensor->i2c_client;
> >> +
> >> +       dev_dbg(&client->dev, "%s probe index %d\n", __func__, code->index);
> >> +       if (code->index >= ARRAY_SIZE(vgxy61_supported_codes))
> >> +               return -EINVAL;
> >> +
> >> +       code->code = vgxy61_supported_codes[code->index];
> >> +
> >> +       return 0;
> >> +}
> >> +
> >> +static int vgxy61_get_fmt(struct v4l2_subdev *sd,
> >> +                         struct v4l2_subdev_state *sd_state,
> >> +                         struct v4l2_subdev_format *format)
> >> +{
> >> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >> +       struct i2c_client *client = sensor->i2c_client;
> >> +       struct v4l2_mbus_framefmt *fmt;
> >> +
> >> +       dev_dbg(&client->dev, "%s probe %d\n", __func__, format->pad);
> >> +       dev_dbg(&client->dev, "%s %dx%d\n", __func__, format->format.width, format->format.height);
> >> +
> >> +       mutex_lock(&sensor->lock);
> >> +
> >> +       if (format->which == V4L2_SUBDEV_FORMAT_TRY)
> >> +               fmt = v4l2_subdev_get_try_format(&sensor->sd, sd_state, format->pad);
> >> +       else
> >> +               fmt = &sensor->fmt;
> >> +
> >> +       format->format = *fmt;
> >> +
> >> +       mutex_unlock(&sensor->lock);
> >> +
> >> +       return 0;
> >> +}
> >> +
> >> +static int vgxy61_set_fmt(struct v4l2_subdev *sd,
> >> +                         struct v4l2_subdev_state *sd_state,
> >> +                         struct v4l2_subdev_format *format)
> >> +{
> >> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >> +       struct i2c_client *client = sensor->i2c_client;
> >> +       const struct vgxy61_mode_info *new_mode;
> >> +       struct v4l2_mbus_framefmt *fmt;
> >> +       int ret;
> >> +
> >> +       dev_dbg(&client->dev, "%s probe %d\n", __func__, format->pad);
> >> +       dev_dbg(&client->dev, "%s %dx%d\n", __func__, format->format.width, format->format.height);
> >> +
> >> +       mutex_lock(&sensor->lock);
> >> +
> >> +       if (sensor->streaming) {
> >> +               ret = -EBUSY;
> >> +               goto out;
> >> +       }
> >> +
> >> +       /* Find best format */
> >> +       ret = vgxy61_try_fmt_internal(sd, &format->format, &new_mode);
> >> +       if (ret)
> >> +               goto out;
> >> +
> >> +       if (format->which == V4L2_SUBDEV_FORMAT_TRY)
> >> +               fmt = v4l2_subdev_get_try_format(sd, sd_state, 0);
> >> +       else
> >> +               fmt = &sensor->fmt;
> >> +       *fmt = format->format;
> >> +       sensor->current_mode = new_mode;
> >> +       /* Update pixel rate control to reflect new mode */
> >> +       __v4l2_ctrl_s_ctrl_int64(sensor->ctrls.pixel_rate, get_pixel_rate(sensor));
> >> +
> >> +out:
> >> +       mutex_unlock(&sensor->lock);
> >> +
> >> +       return ret;
> >> +}
> >> +
> >> +static int vgxy61_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state,
> >> +                                 struct v4l2_subdev_frame_size_enum *fse)
> >> +{
> >> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >> +       struct i2c_client *client = sensor->i2c_client;
> >> +
> >> +       dev_dbg(&client->dev, "%s for index %d\n", __func__, fse->index);
> >> +       if (fse->index >= sensor->sensor_modes_nb)
> >> +               return -EINVAL;
> >> +
> >> +       fse->min_width = sensor->sensor_modes[fse->index].width;
> >> +       fse->max_width = fse->min_width;
> >> +       fse->min_height = sensor->sensor_modes[fse->index].height;
> >> +       fse->max_height = fse->min_height;
> >> +
> >> +       return 0;
> >> +}
> >> +
> >> +static int vgxy61_enum_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state,
> >> +                                     struct v4l2_subdev_frame_interval_enum *fie)
> >> +{
> >> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >> +       const struct vgxy61_mode_info *mode = sensor->sensor_modes;
> >> +
> >> +       if (fie->index >= sensor->sensor_rates_nb)
> >> +               return -EINVAL;
> >> +       do {
> >> +               if (mode->width == fie->width && mode->height == fie->height)
> >> +                       break;
> >> +       } while ((++mode)->width);
> >> +       if (!mode->width)
> >> +               return -EINVAL;
> >> +
> >> +       fie->interval.numerator = 1;
> >> +       fie->interval.denominator = sensor->sensor_rates[fie->index];
> >> +
> >> +       return 0;
> >> +}
> >> +
> >> +static int vgxy61_update_analog_gain(struct vgxy61_dev *sensor, u32 target)
> >> +{
> >> +       struct i2c_client *client = sensor->i2c_client;
> >> +       unsigned int idx;
> >> +       int ret;
> >> +
> >> +       /* Find smallest analog gains which is above or equal to target gain */
> >> +       for (idx = 0; idx < ARRAY_SIZE(analog_gains); idx++) {
> >> +               if (analog_gains[idx] >= target)
> >> +                       break;
> >> +       }
> >> +       /* Cap to maximum gain if no matching gain found */
> >> +       if (idx == ARRAY_SIZE(analog_gains))
> >> +               idx--;
> >> +
> >> +       /* Apply gain */
> >> +       ret = vgxy61_write_reg(sensor, DEVICE_ANALOG_GAIN, idx);
> >> +       if (ret)
> >> +               return ret;
> >> +
> >> +       dev_dbg(&client->dev, "Target analog gain: 0x%04x\n", target);
> >> +       dev_dbg(&client->dev, "   Set analog gain: 0x%04x\n", analog_gains[idx]);
> >> +
> >> +       return 0;
> >> +}
> >> +
> >> +static int vgxy61_update_digital_gain(struct vgxy61_dev *sensor, u32 target)
> >> +{
> >> +       struct i2c_client *client = sensor->i2c_client;
> >> +       int ret;
> >> +
> >> +       /*
> >> +        * For a monochrome version, configuring DIGITAL_GAIN_LONG_CH0 and
> >> +        * DIGITAL_GAIN_SHORT_CH0 is enough to configure the gain of all
> >> +        * four sub pixels.
> >> +        */
> >> +       ret = vgxy61_write_reg16(sensor, DEVICE_DIGITAL_GAIN_LONG, target);
> >> +       if (ret)
> >> +               return ret;
> >> +       ret = vgxy61_write_reg16(sensor, DEVICE_DIGITAL_GAIN_SHORT, target);
> >> +       if (ret)
> >> +               return ret;
> >> +
> >> +       dev_dbg(&client->dev, "  Set digital gain: 0x%04x\n", target);
> >> +
> >> +       return 0;
> >> +}
> >> +
> >> +static int vgxy61_update_patgen(struct vgxy61_dev *sensor, u32 index)
> >> +{
> >> +       u32 pattern = index <= 3 ? index : index + 12;
> >> +       u32 reg;
> >> +
> >> +       reg = (pattern << 18) | (pattern << 4);
> >> +       if (index)
> >> +               reg |= (1 << 16) | 1;
> >> +       return vgxy61_write_reg32(sensor, DEVICE_PATGEN_CTRL, reg);
> >> +}
> >> +
> >> +static int vgxy61_update_gpiox_strobe_mode(struct vgxy61_dev *sensor, enum strobe_modes mode,
> >> +                                          int idx)
> >> +{
> >> +       const u8 index2val[] = {0x0, 0x1, 0x3};
> >> +       u16 reg;
> >> +       int ret;
> >> +
> >> +       ret = vgxy61_read_reg16(sensor, DEVICE_SIGNALS_CTRL, &reg);
> >> +       if (ret)
> >> +               return ret;
> >> +       reg &= ~(0xf << (idx * 4));
> >> +       reg |= index2val[mode] << (idx * 4);
> >> +
> >> +       return vgxy61_write_reg16(sensor, DEVICE_SIGNALS_CTRL, reg);
> >> +}
> >> +
> >> +static int vgxy61_update_gpios_strobe_mode(struct vgxy61_dev *sensor, enum hdr hdr)
> >> +{
> >> +       enum strobe_modes strobe_mode;
> >> +       int i, ret;
> >> +
> >> +       switch (hdr) {
> >> +       case HDR_LINEAR:
> >> +               strobe_mode = STROBE_ENABLED;
> >> +               break;
> >> +       case HDR_SUB:
> >> +       case NO_HDR:
> >> +               strobe_mode = STROBE_LONG;
> >> +               break;
> >> +       default:
> >> +               /* Should never happen */
> >> +               WARN_ON(true);
> >> +               break;
> >> +       }
> >> +
> >> +       for (i = 0; i < NB_GPIOS; i++) {
> >> +               ret = vgxy61_update_gpiox_strobe_mode(sensor, strobe_mode, i);
> >> +               if (ret)
> >> +                       return ret;
> >> +       }
> >> +
> >> +       return 0;
> >> +}
> >> +
> >> +static int vgxy61_update_gpios_strobe_polarity(struct vgxy61_dev *sensor, int polarity)
> >> +{
> >> +       struct i2c_client *client = sensor->i2c_client;
> >> +       int ret;
> >> +
> >> +       if (sensor->streaming)
> >> +               return -EBUSY;
> >> +
> >> +       dev_dbg(&client->dev, "setting gpios polarity: %d\n", polarity);
> >> +
> >> +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_0_CTRL, polarity << 1);
> >> +       if (ret)
> >> +               return ret;
> >> +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_1_CTRL, polarity << 1);
> >> +       if (ret)
> >> +               return ret;
> >> +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_2_CTRL, polarity << 1);
> >> +       if (ret)
> >> +               return ret;
> >> +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_3_CTRL, polarity << 1);
> >> +       if (ret)
> >> +               return ret;
> >> +       return vgxy61_write_reg(sensor, DEVICE_SIGNALS_POLARITY_CTRL, polarity);
> >> +}
> >> +
> >> +static int find_max_long_exposure(struct vgxy61_dev *sensor, int frame_length, int short_expo_ratio)
> >> +{
> >> +       int first_rot_max_expo;
> >> +       int second_rot_max_expo;
> >> +
> >> +       /*
> >> +        * Apply first rule of thumb:
> >> +        * frame_length < short_line_nb + sensor->sensor_height + sensor->rot_term
> >> +        * with short_line_nb = long_line_nb / short_expo_ratio
> >> +        */
> >> +       first_rot_max_expo = ((frame_length - sensor->sensor_height - sensor->rot_term) *
> >> +                            short_expo_ratio) - 1;
> >> +
> >> +       /*
> >> +        * Apply second rule of thumb
> >> +        * frame_length < short_line_nb + long_line_nb + VGXY61_EXPOS_ROT_TERM
> >> +        * with short_line_nb = long_line_nb / short_expo_ratio
> >> +        */
> >> +       second_rot_max_expo = (((frame_length - VGXY61_EXPOS_ROT_TERM) * short_expo_ratio) /
> >> +                             (short_expo_ratio + 1)) - 1;
> >> +
> >> +       /* Take the minimum of both rules */
> >> +       return min(first_rot_max_expo, second_rot_max_expo);
> >> +}
> >> +
> >> +static int vgxy61_update_exposure(struct vgxy61_dev *sensor, int long_expo_line_nb, enum hdr hdr,
> >> +                                 bool clamp)
> >> +{
> >> +       struct i2c_client *client = sensor->i2c_client;
> >> +       int max_long_expo;
> >> +       int max_short_expo = 0;
> >> +       int short_expo_line_nb = 0;
> >> +       /* We use a constant ratio of 10 for linear HDR mode */
> >> +       int hdr_linear_ratio = 10;
> >> +       u16 frame_length;
> >> +       int ret;
> >> +
> >> +       ret = vgxy61_read_reg16(sensor, DEVICE_FRAME_LENGTH, &frame_length);
> >> +       if (ret)
> >> +               return ret;
> >> +
> >> +       /* Long and short integration times must not be less than 10 lines */
> >> +       long_expo_line_nb = max(10, long_expo_line_nb);
> >> +       long_expo_line_nb = min_t(int, frame_length, long_expo_line_nb);
> >> +
> >> +       /* Compute short exposure according to hdr mode and long exposure */
> >> +       switch (hdr) {
> >> +       case HDR_LINEAR:
> >> +               max_long_expo = find_max_long_exposure(sensor, frame_length, hdr_linear_ratio);
> >> +               max_short_expo = (max_long_expo + (hdr_linear_ratio / 2)) / hdr_linear_ratio;
> >> +               short_expo_line_nb = (long_expo_line_nb + (hdr_linear_ratio / 2)) /
> >> +                                    hdr_linear_ratio;
> >> +               break;
> >> +       case HDR_SUB:
> >> +               max_long_expo = find_max_long_exposure(sensor, frame_length, 1);
> >> +               max_short_expo = max_long_expo;
> >> +               short_expo_line_nb = long_expo_line_nb;
> >> +               break;
> >> +       case NO_HDR:
> >> +               /*
> >> +                * As short expo is 0 here, only the second rule of thumb applies, see
> >> +                * find_max_long_exposure for more
> >> +                */
> >> +               max_long_expo = frame_length - VGXY61_EXPOS_ROT_TERM;
> >> +               break;
> >> +       default:
> >> +               /* Should never happen */
> >> +               WARN_ON(true);
> >> +               break;
> >> +       }
> >> +
> >> +       if (long_expo_line_nb > max_long_expo) {
> >> +               if (!clamp) {
> >> +                       dev_err(&client->dev, "Exposure %d too high (max for this hdr mode %d)\n",
> >> +                               long_expo_line_nb, max_long_expo);
> >> +                       return -EINVAL;
> >> +               }
> >> +               dev_warn(&client->dev, "Exposure %d too high for this hdr mode, clamping to %d\n",
> >> +                        long_expo_line_nb, max_long_expo);
> >> +               long_expo_line_nb = max_long_expo;
> >> +               short_expo_line_nb = max_short_expo;
> >> +       }
> >> +
> >> +       dev_dbg(&client->dev, "frame_length %d, long_expo_line_nb %d, short_expo_line_nb %d",
> >> +               frame_length, long_expo_line_nb, short_expo_line_nb);
> >> +
> >> +       /* Apply exposure */
> >> +       sensor->expo_long = long_expo_line_nb;
> >> +       sensor->expo_short = short_expo_line_nb;
> >> +
> >> +       if (sensor->streaming)
> >> +               return apply_exposure(sensor);
> >> +       return 0;
> >> +}
> >> +
> >> +static int vgxy61_update_hdr(struct vgxy61_dev *sensor, u32 index)
> >> +{
> >> +       const u8 index2val[] = {0x1, 0x4, 0xa};
> >> +       int ret;
> >> +
> >> +       /*
> >> +        * Short exposure changes according to HDR mode, do it first as it can
> >> +        * violate sensors 'rule of thumbs' and therefore will require to change
> >> +        * the long exposure
> >> +        */
> >> +       ret = vgxy61_update_exposure(sensor, sensor->expo_long, index, true);
> >> +       if (ret)
> >> +               return ret;
> >> +
> >> +       /* Update strobe mode according to HDR */
> >> +       ret = vgxy61_update_gpios_strobe_mode(sensor, index);
> >> +       if (ret)
> >> +               return ret;
> >> +
> >> +       ret = vgxy61_write_reg(sensor, DEVICE_HDR_CTRL, index2val[index]);
> >> +       if (ret)
> >> +               return ret;
> >> +       sensor->hdr = index;
> >> +
> >> +       return 0;
> >> +}
> >> +
> >> +static int vgxy61_get_temp_stream_enabled(struct vgxy61_dev *sensor, int *temp)
> >> +{
> >> +       int ret;
> >> +       u16 temperature;
> >> +
> >> +       ret = vgxy61_read_reg16(sensor, DEVICE_THSENS1_TEMPERATURE, &temperature);
> >> +       if (ret)
> >> +               return ret;
> >> +
> >> +       /* Temperature is expressed in Kelvin in Q10.2 fixed point format*/
> >> +       temperature = (temperature & 0x0fff) >> 2;
> >> +       temperature = kelvin_to_celsius(temperature);
> >> +
> >> +       *temp = temperature;
> >> +
> >> +       return 0;
> >> +}
> >> +
> >> +static int vgxy61_get_temp_stream_disabled(struct vgxy61_dev *sensor, int *temp)
> >> +{
> >> +       int ret;
> >> +
> >> +       /* Device needs to be in standby mode if streaming is off */
> >> +       ret = vgxy61_write_reg(sensor, DEVICE_STBY, STBY_REQ_TMP_READ);
> >> +       if (ret)
> >> +               return ret;
> >> +       ret = vgxy61_poll_reg(sensor, DEVICE_STBY, STBY_NO_REQ);
> >> +       if (ret)
> >> +               return ret;
> >> +
> >> +       return vgxy61_get_temp_stream_enabled(sensor, temp);
> >> +}
> >> +
> >> +static int vgxy61_get_temp(struct vgxy61_dev *sensor, int *temp)
> >> +{
> >> +       *temp = 0;
> >> +       if (sensor->streaming)
> >> +               return vgxy61_get_temp_stream_enabled(sensor, temp);
> >> +       else
> >> +               return vgxy61_get_temp_stream_disabled(sensor, temp);
> >> +}
> >> +
> >> +static int vgxy61_s_ctrl(struct v4l2_ctrl *ctrl)
> >> +{
> >> +       struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
> >> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >> +       int ret;
> >> +
> >> +       switch (ctrl->id) {
> >> +       case V4L2_CID_EXPOSURE:
> >> +               ret = vgxy61_update_exposure(sensor, ctrl->val, sensor->hdr, false);
> >> +               ctrl->val = sensor->expo_long;
> >> +               break;
> >> +       case V4L2_CID_ANALOGUE_GAIN:
> >> +               ret = vgxy61_update_analog_gain(sensor, ctrl->val);
> >> +               break;
> >> +       case V4L2_CID_DIGITAL_GAIN:
> >> +               ret = vgxy61_update_digital_gain(sensor, ctrl->val);
> >> +               break;
> >> +       case V4L2_CID_VFLIP:
> >> +       case V4L2_CID_HFLIP:
> >> +               if (sensor->streaming) {
> >> +                       ret = -EBUSY;
> >> +                       break;
> >> +               }
> >> +               if (ctrl->id == V4L2_CID_VFLIP)
> >> +                       sensor->vflip = ctrl->val;
> >> +               if (ctrl->id == V4L2_CID_HFLIP)
> >> +                       sensor->hflip = ctrl->val;
> >> +               ret = vgxy61_write_reg(sensor, DEVICE_ORIENTATION,
> >> +                                      sensor->hflip | (sensor->vflip << 1));
> >> +               break;
> >> +       case V4L2_CID_TEST_PATTERN:
> >> +               ret = vgxy61_update_patgen(sensor, ctrl->val);
> >> +               break;
> >> +       case V4L2_CID_HDR:
> >> +               ret = vgxy61_update_hdr(sensor, ctrl->val);
> >> +               break;
> >> +       case V4L2_CID_GPIOS_STROBE_LONG_START_DELAY:
> >> +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_LONG_START_DELAY, ctrl->val);
> >> +               break;
> >> +       case V4L2_CID_GPIOS_STROBE_LONG_END_DELAY:
> >> +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_LONG_END_DELAY, ctrl->val);
> >> +               break;
> >> +       case V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY:
> >> +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_SHORT_START_DELAY, ctrl->val);
> >> +               break;
> >> +       case V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY:
> >> +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_SHORT_END_DELAY, ctrl->val);
> >> +               break;
> >> +       default:
> >> +               ret = -EINVAL;
> >> +               break;
> >> +       }
> >> +
> >> +       return ret;
> >> +}
> >> +
> >> +static int vgxy61_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
> >> +{
> >> +       struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
> >> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >> +       int temperature;
> >> +       int ret;
> >> +
> >> +       switch (ctrl->id) {
> >> +       case V4L2_CID_TEMPERATURE:
> >> +               ret = vgxy61_get_temp(sensor, &temperature);
> >> +               if (ret)
> >> +                       break;
> >> +               ret = __v4l2_ctrl_s_ctrl(ctrl, temperature);
> >> +               break;
> >> +       default:
> >> +               ret = -EINVAL;
> >> +               break;
> >> +       }
> >> +
> >> +       return ret;
> >> +}
> >> +
> >> +static const struct v4l2_ctrl_ops vgxy61_ctrl_ops = {
> >> +       .g_volatile_ctrl = vgxy61_g_volatile_ctrl,
> >> +       .s_ctrl = vgxy61_s_ctrl,
> >> +};
> >> +
> >> +static const struct v4l2_ctrl_config vgxy61_hdr_ctrl = {
> >> +       .ops            = &vgxy61_ctrl_ops,
> >> +       .id             = V4L2_CID_HDR,
> >> +       .name           = "HDR mode",
> >> +       .type           = V4L2_CTRL_TYPE_MENU,
> >> +       .min            = 0,
> >> +       .max            = ARRAY_SIZE(vgxy61_hdr_modes) - 1,
> >> +       .def            = NO_HDR,
> >> +       .qmenu          = vgxy61_hdr_modes,
> >> +};
> >> +
> >> +static const struct v4l2_ctrl_config vgxy61_strobe_long_start_delay = {
> >> +       .ops            = &vgxy61_ctrl_ops,
> >> +       .id             = V4L2_CID_GPIOS_STROBE_LONG_START_DELAY,
> >> +       .name           = "Long strobe mode start delay in lines",
> >> +       .type           = V4L2_CTRL_TYPE_INTEGER,
> >> +       .min            = -128,
> >> +       .max            = 127,
> >> +       .step           = 1,
> >> +       .def            = 0,
> >> +       .flags          = 0,
> >> +};
> >> +
> >> +static const struct v4l2_ctrl_config vgxy61_strobe_long_end_delay = {
> >> +       .ops            = &vgxy61_ctrl_ops,
> >> +       .id             = V4L2_CID_GPIOS_STROBE_LONG_END_DELAY,
> >> +       .name           = "Long strobe mode end delay in lines",
> >> +       .type           = V4L2_CTRL_TYPE_INTEGER,
> >> +       .min            = -128,
> >> +       .max            = 127,
> >> +       .step           = 1,
> >> +       .def            = 0,
> >> +       .flags          = 0,
> >> +};
> >> +
> >> +static const struct v4l2_ctrl_config vgxy61_strobe_short_start_delay = {
> >> +       .ops            = &vgxy61_ctrl_ops,
> >> +       .id             = V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY,
> >> +       .name           = "Short strobe mode start delay in lines",
> >> +       .type           = V4L2_CTRL_TYPE_INTEGER,
> >> +       .min            = -128,
> >> +       .max            = 127,
> >> +       .step           = 1,
> >> +       .def            = 0,
> >> +       .flags          = 0,
> >> +};
> >> +
> >> +static const struct v4l2_ctrl_config vgxy61_strobe_short_end_delay = {
> >> +       .ops            = &vgxy61_ctrl_ops,
> >> +       .id             = V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY,
> >> +       .name           = "Short strobe mode end delay in lines",
> >> +       .type           = V4L2_CTRL_TYPE_INTEGER,
> >> +       .min            = -128,
> >> +       .max            = 127,
> >> +       .step           = 1,
> >> +       .def            = 0,
> >> +       .flags          = 0,
> >> +};
> >> +
> >> +static const struct v4l2_ctrl_config vgxy61_temp_ctrl = {
> >> +       .ops            = &vgxy61_ctrl_ops,
> >> +       .id             = V4L2_CID_TEMPERATURE,
> >> +       .name           = "Temperature in °C",
> >> +       .type           = V4L2_CTRL_TYPE_INTEGER,
> >> +       .min            = -128,
> >> +       .max            = 128,
> >> +       .step           = 1,
> >> +};
> >> +
> >> +static int vgxy61_init_controls(struct vgxy61_dev *sensor)
> >> +{
> >> +       const struct v4l2_ctrl_ops *ops = &vgxy61_ctrl_ops;
> >> +       struct vgxy61_ctrls *ctrls = &sensor->ctrls;
> >> +       struct v4l2_ctrl_handler *hdl = &sensor->ctrls.handler;
> >> +       int ret;
> >> +
> >> +       v4l2_ctrl_handler_init(hdl, 16);
> >> +       /* We can use our own mutex for the ctrl lock */
> >> +       hdl->lock = &sensor->lock;
> >> +       ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, 10, 0xffff, 1,
> >> +                                           sensor->expo_long);
> >> +       /* This is 8.8 fixed point value */
> >> +       ctrls->analog_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, 0x3fff, 1,
> >> +                                              0x0);
> >> +       /* This is 8.8 fixed point value */
> >> +       ctrls->digital_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 0, 0xfff, 1,
> >> +                                               0x100);
> >> +       ctrls->vflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, sensor->vflip);
> >> +       ctrls->hflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, sensor->hflip);
> >> +       ctrls->patgen = v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN,
> >> +                                                    ARRAY_SIZE(vgxy61_test_pattern_menu) - 1,
> >> +                                                    0, 0, vgxy61_test_pattern_menu);
> >> +       ctrls->hdr = v4l2_ctrl_new_custom(hdl, &vgxy61_hdr_ctrl, NULL);
> >> +       ctrls->pixel_rate = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1,
> >> +                                             get_pixel_rate(sensor));
> >> +       ctrls->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> >> +       ctrls->link_freq = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ,
> >> +                                                 ARRAY_SIZE(link_freq) - 1, 0, link_freq);
> >> +       ctrls->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> >> +       /* Gpios ctrls */
> >> +       ctrls->gpios.long_start = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_long_start_delay, NULL);
> >> +       ctrls->gpios.long_end = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_long_end_delay, NULL);
> >> +       ctrls->gpios.short_start = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_short_start_delay,
> >> +                                                       NULL);
> >> +       ctrls->gpios.short_end = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_short_end_delay, NULL);
> >> +       /* Temperature ctrl */
> >> +       ctrls->temp = v4l2_ctrl_new_custom(hdl, &vgxy61_temp_ctrl, NULL);
> >> +       ctrls->temp->flags |= V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY;
> >> +
> >> +       if (hdl->error) {
> >> +               ret = hdl->error;
> >> +               goto free_ctrls;
> >> +       }
> >> +
> >> +       sensor->sd.ctrl_handler = hdl;
> >> +       return 0;
> >> +
> >> +free_ctrls:
> >> +       v4l2_ctrl_handler_free(hdl);
> >> +       return ret;
> >> +}
> >> +
> >> +static const struct v4l2_subdev_core_ops vgxy61_core_ops = {
> >> +};
> >> +
> >> +static const struct v4l2_subdev_video_ops vgxy61_video_ops = {
> >> +       .s_stream = vgxy61_s_stream,
> >> +       .g_frame_interval = vgxy61_g_frame_interval,
> >> +       .s_frame_interval = vgxy61_s_frame_interval,
> >> +};
> >> +
> >> +static const struct v4l2_subdev_pad_ops vgxy61_pad_ops = {
> >> +       .enum_mbus_code = vgxy61_enum_mbus_code,
> >> +       .get_fmt = vgxy61_get_fmt,
> >> +       .set_fmt = vgxy61_set_fmt,
> >> +       .enum_frame_size = vgxy61_enum_frame_size,
> >> +       .enum_frame_interval = vgxy61_enum_frame_interval,
> >> +};
> >> +
> >> +static const struct v4l2_subdev_ops vgxy61_subdev_ops = {
> >> +       .core = &vgxy61_core_ops,
> >> +       .video = &vgxy61_video_ops,
> >> +       .pad = &vgxy61_pad_ops,
> >> +};
> >> +
> >> +static const struct media_entity_operations vgxy61_subdev_entity_ops = {
> >> +       .link_validate = v4l2_subdev_link_validate,
> >> +};
> >> +
> >> +/* Set phy polarities */
> >> +static int vgxy61_tx_from_ep(struct vgxy61_dev *sensor, struct fwnode_handle *endpoint)
> >> +{
> >> +       struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
> >> +       struct i2c_client *client = sensor->i2c_client;
> >> +       u32 log2phy[NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
> >> +       u32 phy2log[NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
> >> +       int polarities[NB_POLARITIES] = {0, 0, 0, 0, 0};
> >> +       int l_nb;
> >> +       int p, l;
> >> +       int ret;
> >> +       int i;
> >> +
> >> +       ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep);
> >> +       if (ret)
> >> +               goto error_alloc;
> >> +
> >> +       l_nb = ep.bus.mipi_csi2.num_data_lanes;
> >> +       if (l_nb != 1 && l_nb != 2 && l_nb != 4) {
> >> +               dev_err(&client->dev, "invalid data lane number %d\n", l_nb);
> >> +               goto error_ep;
> >> +       }
> >> +
> >> +       /* Build log2phy, phy2log and polarities from ep info */
> >> +       log2phy[0] = ep.bus.mipi_csi2.clock_lane;
> >> +       phy2log[log2phy[0]] = 0;
> >> +       for (l = 1; l < l_nb + 1; l++) {
> >> +               log2phy[l] = ep.bus.mipi_csi2.data_lanes[l - 1];
> >> +               phy2log[log2phy[l]] = l;
> >> +       }
> >> +       /*
> >> +        * Then fill remaining slots for every physical slot to have something valid for hardware
> >> +        * stuff.
> >> +        */
> >> +       for (p = 0; p < NB_POLARITIES; p++) {
> >> +               if (phy2log[p] != ~0)
> >> +                       continue;
> >> +               phy2log[p] = l;
> >> +               log2phy[l] = p;
> >> +               l++;
> >> +       }
> >> +       for (l = 0; l < l_nb + 1; l++)
> >> +               polarities[l] = ep.bus.mipi_csi2.lane_polarities[l];
> >> +
> >> +       if (log2phy[0] != 0) {
> >> +               dev_err(&client->dev, "clk lane must be map to physical lane 0\n");
> >> +               goto error_ep;
> >> +       }
> >> +       sensor->oif_ctrl = (polarities[4] << 15) + ((phy2log[4] - 1) << 13) +
> >> +                          (polarities[3] << 12) + ((phy2log[3] - 1) << 10) +
> >> +                          (polarities[2] <<  9) + ((phy2log[2] - 1) <<  7) +
> >> +                          (polarities[1] <<  6) + ((phy2log[1] - 1) <<  4) +
> >> +                          (polarities[0] <<  3) +
> >> +                          l_nb;
> >> +       sensor->nb_of_lane = l_nb;
> >> +
> >> +       dev_dbg(&client->dev, "tx uses %d lanes", l_nb);
> >> +       for (i = 0; i < 5; i++) {
> >> +               dev_dbg(&client->dev, "log2phy[%d] = %d\n", i, log2phy[i]);
> >> +               dev_dbg(&client->dev, "phy2log[%d] = %d\n", i, phy2log[i]);
> >> +               dev_dbg(&client->dev, "polarity[%d] = %d\n", i, polarities[i]);
> >> +       }
> >> +       dev_dbg(&client->dev, "oif_ctrl = 0x%04x\n", sensor->oif_ctrl);
> >> +
> >> +       v4l2_fwnode_endpoint_free(&ep);
> >> +
> >> +       return 0;
> >> +
> >> +error_ep:
> >> +       v4l2_fwnode_endpoint_free(&ep);
> >> +error_alloc:
> >> +
> >> +       return -EINVAL;
> >> +}
> >> +
> >> +static int vgxy61_configure(struct vgxy61_dev *sensor)
> >> +{
> >> +       struct i2c_client *client = sensor->i2c_client;
> >> +       int sensor_freq;
> >> +       unsigned int prediv;
> >> +       unsigned int mult;
> >> +       int ret;
> >> +
> >> +       compute_pll_parameters_by_freq(sensor->clk_freq, &prediv, &mult);
> >> +       sensor_freq = (mult * sensor->clk_freq) / prediv;
> >> +       /* Frequency to data rate is 1:1 ratio for MIPI */
> >> +       sensor->data_rate_in_mbps = sensor_freq;
> >> +       /* Video timing ISP path (pixel clock)  requires 804/5 mhz = 160 mhz */
> >> +       sensor->pclk = sensor_freq / 5;
> >> +
> >> +       /* Cache line_length value */
> >> +       ret = vgxy61_read_reg16(sensor, DEVICE_LINE_LENGTH, &sensor->line_length);
> >> +       if (ret)
> >> +               return ret;
> >> +       /* Configure clocks */
> >> +       ret = vgxy61_write_reg32(sensor, DEVICE_EXT_CLOCK, sensor->clk_freq);
> >> +       if (ret)
> >> +               return ret;
> >> +       ret = vgxy61_write_reg(sensor, DEVICE_CLK_PLL_PREDIV, prediv);
> >> +       if (ret)
> >> +               return ret;
> >> +       ret = vgxy61_write_reg(sensor, DEVICE_CLK_SYS_PLL_MULT, mult);
> >> +       if (ret)
> >> +               return ret;
> >> +       /* Configure interface */
> >> +       ret = vgxy61_write_reg16(sensor, DEVICE_OIF_CTRL, sensor->oif_ctrl);
> >> +       if (ret)
> >> +               return ret;
> >> +       /* Disable pwm compression */
> >> +       ret = vgxy61_write_reg(sensor, DEVICE_FRAME_CONTENT_CTRL, 0);
> >> +       if (ret)
> >> +               return ret;
> >> +       /* Disable asil lines */
> >> +       ret = vgxy61_write_reg(sensor, DEVICE_BYPASS_CTRL, 4);
> >> +       if (ret)
> >> +               return ret;
> >> +       /* Set gpios polarity according to device tree value */
> >> +       ret = vgxy61_update_gpios_strobe_polarity(sensor, sensor->gpios_polarity);
> >> +       if (ret)
> >> +               return ret;
> >> +       /* HDR mode */
> >> +       ret = vgxy61_update_hdr(sensor, sensor->hdr);
> >> +       if (ret)
> >> +               return ret;
> >> +       /* Slave mode */
> >> +       ret = vgxy61_write_reg(sensor, DEVICE_VT_CTRL, sensor->slave_mode);
> >> +       if (ret)
> >> +               return ret;
> >> +       /* Set pattern generator solid to middle value */
> >> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_GR, 0x800);
> >> +       if (ret)
> >> +               return ret;
> >> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_R, 0x800);
> >> +       if (ret)
> >> +               return ret;
> >> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_B, 0x800);
> >> +       if (ret)
> >> +               return ret;
> >> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_GB, 0x800);
> >> +       if (ret)
> >> +               return ret;
> >> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_GR, 0x800);
> >> +       if (ret)
> >> +               return ret;
> >> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_R, 0x800);
> >> +       if (ret)
> >> +               return ret;
> >> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_B, 0x800);
> >> +       if (ret)
> >> +               return ret;
> >> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_GB, 0x800);
> >> +       if (ret)
> >> +               return ret;
> >> +
> >> +       dev_dbg(&client->dev, "clock prediv = %d\n", prediv);
> >> +       dev_dbg(&client->dev, "clock mult = %d\n", mult);
> >> +       dev_dbg(&client->dev, "data rate = %d mbps\n",
> >> +               sensor->data_rate_in_mbps);
> >> +
> >> +       return 0;
> >> +}
> >> +
> >> +static int vgxy61_patch(struct vgxy61_dev *sensor)
> >> +{
> >> +       struct i2c_client *client = sensor->i2c_client;
> >> +       u16 patch;
> >> +       int ret;
> >> +
> >> +       ret = vgxy61_write_array(sensor, DEVICE_FWPATCH_START_ADDR, sizeof(patch_array),
> >> +                                patch_array);
> >> +       if (ret)
> >> +               return ret;
> >> +
> >> +       ret = vgxy61_write_reg(sensor, DEVICE_STBY, 0x10);
> >> +       if (ret)
> >> +               return ret;
> >> +
> >> +       ret = vgxy61_poll_reg(sensor, DEVICE_STBY, 0);
> >> +       if (ret)
> >> +               return ret;
> >> +
> >> +       ret = vgxy61_read_reg16(sensor, DEVICE_FWPATCH_REVISION, &patch);
> >> +       if (ret)
> >> +               return ret;
> >> +
> >> +       if (patch != (DEVICE_FWPATCH_REVISION_MAJOR << 12) +
> >> +                    (DEVICE_FWPATCH_REVISION_MINOR << 8) +
> >> +                    DEVICE_FWPATCH_REVISION_MICRO) {
> >> +               dev_err(&client->dev, "bad patch version expected %d.%d.%d got %d.%d.%d\n",
> >> +                       DEVICE_FWPATCH_REVISION_MAJOR,
> >> +                       DEVICE_FWPATCH_REVISION_MINOR,
> >> +                       DEVICE_FWPATCH_REVISION_MICRO,
> >> +                       patch >> 12, (patch >> 8) & 0x0f, patch & 0xff);
> >> +               return -ENODEV;
> >> +       }
> >> +       dev_dbg(&client->dev, "patch %d.%d.%d applied\n",
> >> +               patch >> 12, (patch >> 8) & 0x0f, patch & 0xff);
> >> +
> >> +       return 0;
> >> +}
> >> +
> >> +static int vgxy61_detect_cut_version(struct vgxy61_dev *sensor)
> >> +{
> >> +       struct i2c_client *client = sensor->i2c_client;
> >> +       u16 device_rev;
> >> +       int ret;
> >> +
> >> +       ret = vgxy61_read_reg16(sensor, DEVICE_REVISION, &device_rev);
> >> +       if (ret)
> >> +               return ret;
> >> +
> >> +       switch (device_rev >> 8) {
> >> +       case 0xA:
> >> +               dev_info(&client->dev, "Cut1 detected\n");
> >> +               dev_err(&client->dev, "Cut1 not supported by this driver\n");
> >> +               return -ENODEV;
> >> +       case 0xB:
> >> +               dev_info(&client->dev, "Cut2 detected\n");
> >> +               return 0;
> >> +       case 0xC:
> >> +               dev_info(&client->dev, "Cut3 detected\n");
> >> +               return 0;
> >> +       default:
> >> +               dev_err(&client->dev, "Unable to detect cut version\n");
> >> +               return -ENODEV;
> >> +       }
> >> +}
> >> +
> >> +static int vgxy61_detect(struct vgxy61_dev *sensor)
> >> +{
> >> +       struct i2c_client *client = sensor->i2c_client;
> >> +       u16 id = 0;
> >> +       int ret;
> >> +       u8 st;
> >> +
> >> +       ret = vgxy61_read_reg16(sensor, DEVICE_MODEL_ID_REG, &id);
> >> +       if (ret)
> >> +               return ret;
> >> +       if (id != VG5661_MODEL_ID && id != VG5761_MODEL_ID) {
> >> +               dev_warn(&client->dev, "Unsupported sensor id %x\n", id);
> >> +               return -ENODEV;
> >> +       }
> >> +       dev_dbg(&client->dev, "detected sensor id = 0x%04x\n", id);
> >> +
> >> +       if (id == VG5761_MODEL_ID) {
> >> +               sensor->sensor_width = VGX761_WIDTH;
> >> +               sensor->sensor_height = VGX761_HEIGHT;
> >> +               sensor->sensor_modes = vgx761_mode_data;
> >> +               sensor->sensor_modes_nb = ARRAY_SIZE(vgx761_mode_data);
> >> +               sensor->current_mode = &vgx761_mode_data[VGX761_DEFAULT_MODE];
> >> +               sensor->rot_term = VGX761_SHORT_ROT_TERM;
> >> +               sensor->sensor_rates = vgx761_sensor_frame_rates;
> >> +               sensor->sensor_rates_nb = ARRAY_SIZE(vgx761_sensor_frame_rates);
> >> +       } else if (id == VG5661_MODEL_ID) {
> >> +               sensor->sensor_width = VGX661_WIDTH;
> >> +               sensor->sensor_height = VGX661_HEIGHT;
> >> +               sensor->sensor_modes = vgx661_mode_data;
> >> +               sensor->sensor_modes_nb = ARRAY_SIZE(vgx661_mode_data);
> >> +               sensor->current_mode = &vgx661_mode_data[VGX661_DEFAULT_MODE];
> >> +               sensor->rot_term = VGX661_SHORT_ROT_TERM;
> >> +               sensor->sensor_rates = vgx661_sensor_frame_rates;
> >> +               sensor->sensor_rates_nb = ARRAY_SIZE(vgx661_sensor_frame_rates);
> >> +       }
> >> +
> >> +       ret = vgxy61_wait_state(sensor, SW_STBY);
> >> +       if (ret)
> >> +               return ret;
> >> +
> >> +       ret = vgxy61_read_reg(sensor, DEVICE_NVM, &st);
> >> +       if (ret)
> >> +               return ret;
> >> +       if (st != NVM_OK)
> >> +               dev_warn(&client->dev, "Bad nvm state got %d\n", st);
> >> +
> >> +       /* Detect cut version */
> >> +       ret = vgxy61_detect_cut_version(sensor);
> >> +       if (ret)
> >> +               return ret;
> >> +
> >> +       return 0;
> >> +}
> >> +
> >> +static int vgxy61_probe(struct i2c_client *client)
> >> +{
> >> +       struct device *dev = &client->dev;
> >> +       struct fwnode_handle *endpoint;
> >> +       struct vgxy61_dev *sensor;
> >> +       int ret;
> >> +
> >> +       sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
> >> +       if (!sensor)
> >> +               return -ENOMEM;
> >> +
> >> +       sensor->i2c_client = client;
> >> +       sensor->streaming = false;
> >> +       sensor->fmt.code = MEDIA_BUS_FMT_SGBRG8_1X8;
> >> +       sensor->fmt.field = V4L2_FIELD_NONE;
> >> +       sensor->fmt.colorspace = V4L2_COLORSPACE_SRGB;
> >> +       sensor->frame_interval.numerator = 1;
> >> +       sensor->frame_interval.denominator = 60;
> >> +       sensor->hdr = NO_HDR;
> >> +       sensor->expo_long = 200;
> >> +       sensor->expo_short = 0;
> >> +       sensor->hflip = false;
> >> +       sensor->vflip = false;
> >> +
> >> +       endpoint = fwnode_graph_get_next_endpoint(of_fwnode_handle(dev->of_node), NULL);
> >> +       if (!endpoint) {
> >> +               dev_err(dev, "endpoint node not found\n");
> >> +               return -EINVAL;
> >> +       }
> >> +
> >> +       ret = vgxy61_tx_from_ep(sensor, endpoint);
> >> +       fwnode_handle_put(endpoint);
> >> +       if (ret) {
> >> +               dev_err(dev, "Failed to parse endpoint %d\n", ret);
> >> +               return ret;
> >> +       }
> >> +
> >> +       sensor->xclk = devm_clk_get(dev, "xclk");
> >> +       if (IS_ERR(sensor->xclk)) {
> >> +               dev_err(dev, "failed to get xclk\n");
> >> +               return PTR_ERR(sensor->xclk);
> >> +       }
> >> +       sensor->clk_freq = clk_get_rate(sensor->xclk);
> >> +       if (sensor->clk_freq < 6000000 || sensor->clk_freq > 27000000) {
> >> +               dev_err(dev, "Only 6Mhz-27Mhz clock range supported. provide %d Hz\n",
> >> +                       sensor->clk_freq);
> >> +               return -EINVAL;
> >> +       }
> >> +       sensor->gpios_polarity = of_property_read_bool(dev->of_node, "invert-gpios-polarity");
> >> +       sensor->slave_mode = of_property_read_bool(dev->of_node, "slave-mode");
> >> +
> >> +       v4l2_i2c_subdev_init(&sensor->sd, client, &vgxy61_subdev_ops);
> >> +       sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> >> +       sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
> >> +       sensor->sd.entity.ops = &vgxy61_subdev_entity_ops;
> >> +       sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
> >> +
> >> +       ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
> >> +       if (ret) {
> >> +               dev_err(&client->dev, "pads init failed %d\n", ret);
> >> +               return ret;
> >> +       }
> >> +
> >> +       /* Request optional reset pin */
> >> +       sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
> >> +
> >> +       ret = vgxy61_get_regulators(sensor);
> >> +       if (ret) {
> >> +               dev_err(&client->dev, "failed to get regulators %d\n", ret);
> >> +               goto entity_cleanup;
> >> +       }
> >> +
> >> +       ret = regulator_bulk_enable(VGXY61_NUM_SUPPLIES, sensor->supplies);
> >> +       if (ret) {
> >> +               dev_err(&client->dev, "failed to enable regulators %d\n", ret);
> >> +               goto entity_cleanup;
> >> +       }
> >> +
> >> +       ret = clk_prepare_enable(sensor->xclk);
> >> +       if (ret) {
> >> +               dev_err(&client->dev, "failed to enable clock %d\n", ret);
> >> +               goto disable_bulk;
> >> +       }
> >> +
> >> +       mutex_init(&sensor->lock);
> >> +
> >> +       /* Apply reset sequence */
> >> +       if (sensor->reset_gpio) {
> >> +               ret = vgxy61_apply_reset(sensor);
> >> +               if (ret) {
> >> +                       dev_err(&client->dev, "sensor reset failed %d\n", ret);
> >> +                       goto disable_clock;
> >> +               }
> >> +       }
> >> +
> >> +       ret = vgxy61_detect(sensor);
> >> +       if (ret) {
> >> +               dev_err(&client->dev, "sensor detect failed %d\n", ret);
> >> +               goto disable_clock;
> >> +       }
> >> +       sensor->fmt.width = sensor->current_mode->width;
> >> +       sensor->fmt.height = sensor->current_mode->height;
> >> +
> >> +       ret = vgxy61_patch(sensor);
> >> +       if (ret) {
> >> +               dev_err(&client->dev, "sensor patch failed %d\n", ret);
> >> +               goto disable_clock;
> >> +       }
> >> +
> >> +       ret = vgxy61_configure(sensor);
> >> +       if (ret) {
> >> +               dev_err(&client->dev, "sensor configuration failed %d\n", ret);
> >> +               goto disable_clock;
> >> +       }
> >> +
> >> +       ret = vgxy61_init_controls(sensor);
> >> +       if (ret) {
> >> +               dev_err(&client->dev, "controls initialization failed %d\n", ret);
> >> +               goto disable_clock;
> >> +       }
> >> +
> >> +       ret = v4l2_async_register_subdev(&sensor->sd);
> >> +       if (ret) {
> >> +               dev_err(&client->dev, "async subdev register failed %d\n", ret);
> >> +               goto disable_clock;
> >> +       }
> >> +
> >> +       dev_info(&client->dev, "vgxy61 probe successfully\n");
> >> +
> >> +       return 0;
> >> +
> >> +disable_clock:
> >> +       clk_disable_unprepare(sensor->xclk);
> >> +disable_bulk:
> >> +       regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
> >> +entity_cleanup:
> >> +       mutex_destroy(&sensor->lock);
> >> +       media_entity_cleanup(&sensor->sd.entity);
> >> +       return ret;
> >> +}
> >> +
> >> +static int vgxy61_remove(struct i2c_client *client)
> >> +{
> >> +       struct v4l2_subdev *sd = i2c_get_clientdata(client);
> >> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >> +
> >> +       v4l2_async_unregister_subdev(&sensor->sd);
> >> +       clk_disable_unprepare(sensor->xclk);
> >> +       mutex_destroy(&sensor->lock);
> >> +       media_entity_cleanup(&sensor->sd.entity);
> >> +       regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
> >> +
> >> +       return 0;
> >> +}
> >> +
> >> +static const struct of_device_id vgxy61_dt_ids[] = {
> >> +       { .compatible = "st,st-vgxy61" },
> >> +       { /* sentinel */ }
> >> +};
> >> +MODULE_DEVICE_TABLE(of, vgxy61_dt_ids);
> >> +
> >> +static struct i2c_driver vgxy61_i2c_driver = {
> >> +       .driver = {
> >> +               .name  = "st-vgxy61",
> >> +               .of_match_table = vgxy61_dt_ids,
> >> +       },
> >> +       .probe_new = vgxy61_probe,
> >> +       .remove = vgxy61_remove,
> >> +};
> >> +
> >> +module_i2c_driver(vgxy61_i2c_driver);
> >> +
> >> +MODULE_AUTHOR("Benjamin Mugnier <benjamin.mugnier@foss.st.com>");
> >> +MODULE_AUTHOR("Mickael Guene <mickael.guene@st.com>");
> >> +MODULE_AUTHOR("Sylvain Petinot <sylvain.petinot@foss.st.com>");
> >> +MODULE_DESCRIPTION("VGXY61 camera subdev driver");
> >> +MODULE_LICENSE("GPL v2");
> >> --
> >> 2.25.1
> >>

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

* Re: [PATCH 2/2] media: i2c: Add driver for ST VGXY61 camera sensor
  2022-03-10 15:21     ` Dave Stevenson
@ 2022-03-10 15:54       ` Kieran Bingham
  2022-03-11 10:56       ` Benjamin Mugnier
  1 sibling, 0 replies; 17+ messages in thread
From: Kieran Bingham @ 2022-03-10 15:54 UTC (permalink / raw)
  To: Dave Stevenson
  Cc: Benjamin Mugnier, Linux Media Mailing List, alain.volmat,
	hugues.fruchet, sylvain.petinot

Hi Dave,

Quoting Dave Stevenson (2022-03-10 15:21:28)
> Hi Benjamin and Kieran
> 
> On Thu, 10 Mar 2022 at 13:52, Kieran Bingham
> <kieran.bingham@ideasonboard.com> wrote:
> >
> > Hi Benjamin,
> >
> > Thank you for the patch -
> >
> > Quoting Benjamin Mugnier (2022-03-10 13:32:55)
> > > The VGXY61 has a quad lanes CSI-2 output port running at 800mbps per
> > > lane, and supports RAW8, RAW10, RAW12, RAW14 and RAW16 formats.
> > > The driver handles both sensor types:
> > > - VG5661 and VG6661: 1.6 Mpx (1464 x 1104) 75fps.
> > > - VG5761 and VG6761: 2.3 Mpx (1944 x 1204) 60 fps.
> > > The driver supports:
> > > - HDR linearize mode, HDR substraction mode, and no HDR
> > > - GPIOs LEDs strobing
> > > - Digital binning and analog subsampling
> > > - Horizontal and vertical flip
> > > - Manual exposure
> > > - Analog and digital gains
> > > - Test patterns
> >
> > I haven't reviewed the driver directly yet, but I have a script which
> > looks for key requirements for libcamera.
> > (https://git.linuxtv.org/libcamera.git/tree/Documentation/sensor_driver_requirements.rst)
> >
> >
> > Media Controller Support:
> >  - V4L2_SUBDEV_FL_HAS_DEVNODE      : found
> >  - MEDIA_ENT_F_CAM_SENSOR          : found
> >
> > Mandatory Controls:
> >  - V4L2_CID_EXPOSURE               : found
> >  - V4L2_CID_HBLANK                 : --------
> >  - V4L2_CID_VBLANK                 : --------
> >  - V4L2_CID_PIXEL_RATE             : found
> >
> > Selection Controls (will become mandatory):
> >  - V4L2_SEL_TGT_CROP_DEFAULT       : --------
> >  - V4L2_SEL_TGT_CROP               : --------
> >  - V4L2_SEL_TGT_CROP_BOUNDS        : --------
> >  - .get_selection                  : --------
> >
> > Optional Controls:
> >  - V4L2_CID_TEST_PATTERN           : found
> >  - V4L2_CID_GAIN                   : --------
> >  - V4L2_CID_ANALOGUE_GAIN          : found
> >  - V4L2_CID_CAMERA_SENSOR_ROTATION : --------
> >  - V4L2_CID_CAMERA_ORIENTATION     : --------
> >
> >
> > The key missing pieces are HBLANK/VBLANK and the .get_selection API. Is
> > it easy/feasible to add these?
> 
> It's documented that HBLANK/VBLANK are the correct parameters to use
> for raw image sensors [1].
> 
> It looks like register DEVICE_FRAME_LENGTH is mode->height +
> V4L2_CID_VBLANK, and DEVICE_LINE_LENGTH is mode->width +
> V4L2_CID_HBLANK (appears to be treated as read only)
> Most other sensors will adjust the max value of V4L2_CID_EXPOSURE
> based on the frame length.
> 
> [1] https://www.kernel.org/doc/html/latest/driver-api/media/camera-sensor.html#frame-interval-configuration

Aha, Thanks - I hadn't realised it was 'formally' required by the
kernel, so I was trying to be more gentle. I'll be more assertive on
those now ;-) and I've added the reference to my review script.

--
Kieran

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

* Re: [PATCH 2/2] media: i2c: Add driver for ST VGXY61 camera sensor
  2022-03-10 15:50       ` Dave Stevenson
@ 2022-03-11  9:34         ` Benjamin Mugnier
  0 siblings, 0 replies; 17+ messages in thread
From: Benjamin Mugnier @ 2022-03-11  9:34 UTC (permalink / raw)
  To: Dave Stevenson
  Cc: Kieran Bingham, Linux Media Mailing List, alain.volmat,
	hugues.fruchet, sylvain.petinot

Hi Dave,

On 10/03/2022 16:50, Dave Stevenson wrote:
> Hi Benjamin
> 
> On Thu, 10 Mar 2022 at 15:15, Benjamin Mugnier
> <benjamin.mugnier@foss.st.com> wrote:
>>
>> Hi Kieran,
>>
>> On 10/03/2022 14:52, Kieran Bingham wrote:
>>> Hi Benjamin,
>>>
>>> Thank you for the patch -
>>>
>>> Quoting Benjamin Mugnier (2022-03-10 13:32:55)
>>>> The VGXY61 has a quad lanes CSI-2 output port running at 800mbps per
>>>> lane, and supports RAW8, RAW10, RAW12, RAW14 and RAW16 formats.
>>>> The driver handles both sensor types:
>>>> - VG5661 and VG6661: 1.6 Mpx (1464 x 1104) 75fps.
>>>> - VG5761 and VG6761: 2.3 Mpx (1944 x 1204) 60 fps.
>>>> The driver supports:
>>>> - HDR linearize mode, HDR substraction mode, and no HDR
>>>> - GPIOs LEDs strobing
>>>> - Digital binning and analog subsampling
>>>> - Horizontal and vertical flip
>>>> - Manual exposure
>>>> - Analog and digital gains
>>>> - Test patterns
>>>
>>> I haven't reviewed the driver directly yet, but I have a script which
>>> looks for key requirements for libcamera.
>>> (https://git.linuxtv.org/libcamera.git/tree/Documentation/sensor_driver_requirements.rst)
>>>
>>>
>>> Media Controller Support:
>>>  - V4L2_SUBDEV_FL_HAS_DEVNODE      : found
>>>  - MEDIA_ENT_F_CAM_SENSOR          : found
>>>
>>> Mandatory Controls:
>>>  - V4L2_CID_EXPOSURE               : found
>>>  - V4L2_CID_HBLANK                 : --------
>>>  - V4L2_CID_VBLANK                 : --------
>>>  - V4L2_CID_PIXEL_RATE             : found
>>>
>>> Selection Controls (will become mandatory):
>>>  - V4L2_SEL_TGT_CROP_DEFAULT       : --------
>>>  - V4L2_SEL_TGT_CROP               : --------
>>>  - V4L2_SEL_TGT_CROP_BOUNDS        : --------
>>>  - .get_selection                  : --------
>>>
>>> Optional Controls:
>>>  - V4L2_CID_TEST_PATTERN           : found
>>>  - V4L2_CID_GAIN                   : --------
>>>  - V4L2_CID_ANALOGUE_GAIN          : found
>>>  - V4L2_CID_CAMERA_SENSOR_ROTATION : --------
>>>  - V4L2_CID_CAMERA_ORIENTATION     : --------
>>>
>>>
>>> The key missing pieces are HBLANK/VBLANK and the .get_selection API. Is
>>> it easy/feasible to add these?
>>>
>>
>> The .get_selection API should be easy.
>> Unfortunately, this device does not have registers for HBLANK/VBLANK. I may be able to compute them for a read only control, but it will be harder to set. Is the read only control enough for libcamera?
>>
>> My next goal is to run this sensor on a Raspberry Pi through libcamera. As I'm fairly new to libcamera it may take some time. My initial idea was to submit libcamera related changes in another set of patches once done, so I could test the entire stack and make extra adjustements and fixes to the driver that I'm not aware of yet. What do you think?
> 
> Sounds fun :-) Shout if you need any assistance.
> 

I won't hesitate, thank you :)

> The Pi pipeline handler only supports VBLANK/HBLANK for frame rate
> control, so implementing the frame_interval ops is a dead end.
> Currently it only supports varying VBLANK for frame rate control, but
> in theory variable HBLANK could also be supported (IIRC Sony
> IMX290/327 varies HBLANK by default).
> 
> As I commented in my other reply, the docs say that raw sensors should
> use these as they don't have a high level control for frame rate.
> 
> Your registers DEVICE_LINE_LENGTH and DEVICE_FRAME_LENGTH look to be
> exactly the settings that map onto HBLANK and VBLANK respectively, but
> with a need to subtract the active frame width / height from the
> value.
> Drop all the frame_interval stuff and add the HBLANK (ro) and VBLANK
> controls. If you look at imx219 or similar you'll see the handling of
> setting VBLANK then adjusting the max value for the exposure control.
> 
> There are very few things that libcamera needs over and above a
> standard sensor driver, so IMHO there's little point in deferring
> implementing them.

Perfect, thank you for your explanation.
The imx219 seems to be what I'm looking for. I'll implement HBLANK and VBLANK the same way.

I'm currently looking at your review from the other thread.


Regards,

Benjamin

> 
>   Dave
> 
>> Regards,
>>
>> Benjamin
>>
>>> --
>>> Regards
>>>
>>> Kieran
>>>
>>>
>>>>
>>>> Signed-off-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com>
>>>> ---
>>>>  drivers/media/i2c/Kconfig     |   11 +
>>>>  drivers/media/i2c/Makefile    |    1 +
>>>>  drivers/media/i2c/st-vgxy61.c | 1919 +++++++++++++++++++++++++++++++++
>>>>  3 files changed, 1931 insertions(+)
>>>>  create mode 100644 drivers/media/i2c/st-vgxy61.c
>>>>
>>>> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
>>>> index fe66093b8849..e6e9c1f19c09 100644
>>>> --- a/drivers/media/i2c/Kconfig
>>>> +++ b/drivers/media/i2c/Kconfig
>>>> @@ -1454,6 +1454,17 @@ config VIDEO_S5C73M3
>>>>           This is a V4L2 sensor driver for Samsung S5C73M3
>>>>           8 Mpixel camera.
>>>>
>>>> +config VIDEO_ST_VGXY61
>>>> +       tristate "ST VGXY61 sensor support"
>>>> +       depends on OF
>>>> +       depends on GPIOLIB && VIDEO_V4L2 && I2C
>>>> +       select MEDIA_CONTROLLER
>>>> +       select VIDEO_V4L2_SUBDEV_API
>>>> +       select V4L2_FWNODE
>>>> +       help
>>>> +         This is a Video4Linux2 sensor driver for the ST VGXY61
>>>> +         camera sensor.
>>>> +
>>>>  endmenu
>>>>
>>>>  menu "Lens drivers"
>>>> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
>>>> index f6b80ef6f41d..0190b5edfa24 100644
>>>> --- a/drivers/media/i2c/Makefile
>>>> +++ b/drivers/media/i2c/Makefile
>>>> @@ -138,4 +138,5 @@ obj-$(CONFIG_VIDEO_MAX9271_LIB)     += max9271.o
>>>>  obj-$(CONFIG_VIDEO_RDACM20)    += rdacm20.o
>>>>  obj-$(CONFIG_VIDEO_RDACM21)    += rdacm21.o
>>>>  obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o
>>>> +obj-$(CONFIG_VIDEO_ST_VGXY61)  += st-vgxy61.o
>>>>  obj-$(CONFIG_SDR_MAX2175) += max2175.o
>>>> diff --git a/drivers/media/i2c/st-vgxy61.c b/drivers/media/i2c/st-vgxy61.c
>>>> new file mode 100644
>>>> index 000000000000..d3d816189f86
>>>> --- /dev/null
>>>> +++ b/drivers/media/i2c/st-vgxy61.c
>>>> @@ -0,0 +1,1919 @@
>>>> +// SPDX-License-Identifier: GPL-2.0
>>>> +/*
>>>> + * Driver for VGXY61 global shutter sensor family driver
>>>> + *
>>>> + * Copyright (C) 2022 STMicroelectronics SA
>>>> + */
>>>> +
>>>> +#include <linux/clk.h>
>>>> +#include <linux/delay.h>
>>>> +#include <linux/gpio/consumer.h>
>>>> +#include <linux/i2c.h>
>>>> +#include <linux/module.h>
>>>> +#include <linux/regulator/consumer.h>
>>>> +#include <linux/units.h>
>>>> +#include <linux/iopoll.h>
>>>> +#include <media/mipi-csi2.h>
>>>> +#include <media/v4l2-async.h>
>>>> +#include <media/v4l2-ctrls.h>
>>>> +#include <media/v4l2-device.h>
>>>> +#include <media/v4l2-fwnode.h>
>>>> +#include <media/v4l2-subdev.h>
>>>> +
>>>> +#define US_PER_MS                                      1000
>>>> +
>>>> +#define DEVICE_MODEL_ID_REG                            0x0000
>>>> +#define VG5661_MODEL_ID                                        0x5661
>>>> +#define VG5761_MODEL_ID                                        0x5761
>>>> +#define VGX661_WIDTH                                   1464
>>>> +#define VGX661_HEIGHT                                  1104
>>>> +#define VGX761_WIDTH                                   1944
>>>> +#define VGX761_HEIGHT                                  1204
>>>> +#define VGX661_DEFAULT_MODE                            1
>>>> +#define VGX761_DEFAULT_MODE                            1
>>>> +#define VGX661_SHORT_ROT_TERM                          93
>>>> +#define VGX761_SHORT_ROT_TERM                          90
>>>> +#define VGXY61_EXPOS_ROT_TERM                          66
>>>> +#define DEVICE_REVISION                                        0x0002
>>>> +#define DEVICE_FWPATCH_REVISION                                0x0014
>>>> +#define DEVICE_FWPATCH_START_ADDR                      0x2000
>>>> +#define DEVICE_SYSTEM_FSM                              0x0020
>>>> +#define SW_STBY                                                0x03
>>>> +#define STREAMING                                      0x04
>>>> +#define DEVICE_NVM                                     0x0023
>>>> +#define NVM_OK                                         0x04
>>>> +#define DEVICE_THSENS1_TEMPERATURE                     0x0044
>>>> +#define DEVICE_STBY                                    0x0201
>>>> +#define STBY_NO_REQ                                    0
>>>> +#define STBY_REQ_TMP_READ                              BIT(2)
>>>> +#define DEVICE_STREAMING                               0x0202
>>>> +#define REQ_NO_REQUEST                                 0
>>>> +#define REQ_STOP_STREAMING                             BIT(0)
>>>> +#define REQ_START_STREAMING                            BIT(1)
>>>> +#define DEVICE_EXT_CLOCK                               0x0220
>>>> +#define DEVICE_CLK_PLL_PREDIV                          0x0224
>>>> +#define DEVICE_CLK_SYS_PLL_MULT                                0x0225
>>>> +#define DEVICE_GPIO_0_CTRL                             0x0236
>>>> +#define DEVICE_GPIO_1_CTRL                             0x0237
>>>> +#define DEVICE_GPIO_2_CTRL                             0x0238
>>>> +#define DEVICE_GPIO_3_CTRL                             0x0239
>>>> +#define DEVICE_SIGNALS_POLARITY_CTRL                   0x023b
>>>> +#define DEVICE_LINE_LENGTH                             0x0300
>>>> +#define DEVICE_ORIENTATION                             0x0302
>>>> +#define DEVICE_VT_CTRL                                 0x0304
>>>> +#define DEVICE_FORMAT_CTRL                             0x0305
>>>> +#define DEVICE_OIF_CTRL                                        0x0306
>>>> +#define DEVICE_OIF_ROI0_CTRL                           0x030a
>>>> +#define DEVICE_ROI0_START_H                            0x0400
>>>> +#define DEVICE_ROI0_START_V                            0x0402
>>>> +#define DEVICE_ROI0_END_H                              0x0404
>>>> +#define DEVICE_ROI0_END_V                              0x0406
>>>> +#define DEVICE_PATGEN_CTRL                             0x0440
>>>> +#define DEVICE_FRAME_CONTENT_CTRL                      0x0478
>>>> +#define DEVICE_COARSE_EXPOSURE_LONG                    0x0500
>>>> +#define DEVICE_COARSE_EXPOSURE_SHORT                   0x0504
>>>> +#define DEVICE_ANALOG_GAIN                             0x0508
>>>> +#define DEVICE_DIGITAL_GAIN_LONG                       0x050a
>>>> +#define DEVICE_DIGITAL_GAIN_SHORT                      0x0512
>>>> +#define DEVICE_FRAME_LENGTH                            0x051a
>>>> +#define DEVICE_SIGNALS_CTRL                            0x0522
>>>> +#define DEVICE_STROBE_LONG_START_DELAY                 0x0528
>>>> +#define DEVICE_STROBE_LONG_END_DELAY                   0x052a
>>>> +#define DEVICE_STROBE_SHORT_START_DELAY                        0x052c
>>>> +#define DEVICE_STROBE_SHORT_END_DELAY                  0x052e
>>>> +#define DEVICE_READOUT_CTRL                            0x0530
>>>> +#define DEVICE_HDR_CTRL                                        0x0532
>>>> +#define DEVICE_PATGEN_LONG_DATA_GR                     0x092c
>>>> +#define DEVICE_PATGEN_LONG_DATA_R                      0x092e
>>>> +#define DEVICE_PATGEN_LONG_DATA_B                      0x0930
>>>> +#define DEVICE_PATGEN_LONG_DATA_GB                     0x0932
>>>> +#define DEVICE_PATGEN_SHORT_DATA_GR                    0x0950
>>>> +#define DEVICE_PATGEN_SHORT_DATA_R                     0x0952
>>>> +#define DEVICE_PATGEN_SHORT_DATA_B                     0x0954
>>>> +#define DEVICE_PATGEN_SHORT_DATA_GB                    0x0956
>>>> +#define DEVICE_BYPASS_CTRL                             0x0a60
>>>> +
>>>> +#define V4L2_CID_HDR                           (V4L2_CID_USER_BASE | 0x1004)
>>>> +#define V4L2_CID_GPIOS_STROBE_LONG_START_DELAY (V4L2_CID_USER_BASE | 0x1019)
>>>> +#define V4L2_CID_GPIOS_STROBE_LONG_END_DELAY   (V4L2_CID_USER_BASE | 0x101a)
>>>> +#define V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY        (V4L2_CID_USER_BASE | 0x101b)
>>>> +#define V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY  (V4L2_CID_USER_BASE | 0x101c)
>>>> +#define V4L2_CID_TEMPERATURE                   (V4L2_CID_USER_BASE | 0x1020)
>>>> +
>>>> +#define DEVICE_FWPATCH_REVISION_MAJOR                  2
>>>> +#define DEVICE_FWPATCH_REVISION_MINOR                  0
>>>> +#define DEVICE_FWPATCH_REVISION_MICRO                  5
>>>> +
>>>> +#define WRITE_MULTIPLE_CHUNK_MAX                       16
>>>> +#define NB_GPIOS                                       4
>>>> +#define NB_POLARITIES                                  5
>>>> +
>>>> +static const u8 patch_array[] = {
>>>> +       0xbf, 0x00, 0x05, 0x20, 0x06, 0x01, 0xe0, 0xe0, 0x04, 0x80, 0xe6, 0x45,
>>>> +       0xed, 0x6f, 0xfe, 0xff, 0x14, 0x80, 0x1f, 0x84, 0x10, 0x42, 0x05, 0x7c,
>>>> +       0x01, 0xc4, 0x1e, 0x80, 0xb6, 0x42, 0x00, 0xe0, 0x1e, 0x82, 0x1e, 0xc0,
>>>> +       0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa, 0x86, 0x0d, 0x70, 0xe1,
>>>> +       0x04, 0x98, 0x15, 0x00, 0x28, 0xe0, 0x14, 0x02, 0x08, 0xfc, 0x15, 0x40,
>>>> +       0x28, 0xe0, 0x98, 0x58, 0xe0, 0xef, 0x04, 0x98, 0x0e, 0x04, 0x00, 0xf0,
>>>> +       0x15, 0x00, 0x28, 0xe0, 0x19, 0xc8, 0x15, 0x40, 0x28, 0xe0, 0xc6, 0x41,
>>>> +       0xfc, 0xe0, 0x14, 0x80, 0x1f, 0x84, 0x14, 0x02, 0xa0, 0xfc, 0x1e, 0x80,
>>>> +       0x14, 0x80, 0x14, 0x02, 0x80, 0xfb, 0x14, 0x02, 0xe0, 0xfc, 0x1e, 0x80,
>>>> +       0x14, 0xc0, 0x1f, 0x84, 0x14, 0x02, 0xa4, 0xfc, 0x1e, 0xc0, 0x14, 0xc0,
>>>> +       0x14, 0x02, 0x80, 0xfb, 0x14, 0x02, 0xe4, 0xfc, 0x1e, 0xc0, 0x0c, 0x0c,
>>>> +       0x00, 0xf2, 0x93, 0xdd, 0x86, 0x00, 0xf8, 0xe0, 0x04, 0x80, 0xc6, 0x03,
>>>> +       0x70, 0xe1, 0x0e, 0x84, 0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa,
>>>> +       0x6b, 0x80, 0x06, 0x40, 0x6c, 0xe1, 0x04, 0x80, 0x09, 0x00, 0xe0, 0xe0,
>>>> +       0x0b, 0xa1, 0x95, 0x84, 0x05, 0x0c, 0x1c, 0xe0, 0x86, 0x02, 0xf9, 0x60,
>>>> +       0xe0, 0xcf, 0x78, 0x6e, 0x80, 0xef, 0x25, 0x0c, 0x18, 0xe0, 0x05, 0x4c,
>>>> +       0x1c, 0xe0, 0x86, 0x02, 0xf9, 0x60, 0xe0, 0xcf, 0x0b, 0x84, 0xd8, 0x6d,
>>>> +       0x80, 0xef, 0x05, 0x4c, 0x18, 0xe0, 0x04, 0xd8, 0x0b, 0xa5, 0x95, 0x84,
>>>> +       0x05, 0x0c, 0x2c, 0xe0, 0x06, 0x02, 0x01, 0x60, 0xe0, 0xce, 0x18, 0x6d,
>>>> +       0x80, 0xef, 0x25, 0x0c, 0x30, 0xe0, 0x05, 0x4c, 0x2c, 0xe0, 0x06, 0x02,
>>>> +       0x01, 0x60, 0xe0, 0xce, 0x0b, 0x84, 0x78, 0x6c, 0x80, 0xef, 0x05, 0x4c,
>>>> +       0x30, 0xe0, 0x0c, 0x0c, 0x00, 0xf2, 0x93, 0xdd, 0x46, 0x01, 0x70, 0xe1,
>>>> +       0x08, 0x80, 0x0b, 0xa1, 0x08, 0x5c, 0x00, 0xda, 0x06, 0x01, 0x68, 0xe1,
>>>> +       0x04, 0x80, 0x4a, 0x40, 0x84, 0xe0, 0x08, 0x5c, 0x00, 0x9a, 0x06, 0x01,
>>>> +       0xe0, 0xe0, 0x04, 0x80, 0x15, 0x00, 0x60, 0xe0, 0x19, 0xc4, 0x15, 0x40,
>>>> +       0x60, 0xe0, 0x15, 0x00, 0x78, 0xe0, 0x19, 0xc4, 0x15, 0x40, 0x78, 0xe0,
>>>> +       0x93, 0xdd, 0xc3, 0xc1, 0x46, 0x01, 0x70, 0xe1, 0x08, 0x80, 0x0b, 0xa1,
>>>> +       0x08, 0x5c, 0x00, 0xda, 0x06, 0x01, 0x68, 0xe1, 0x04, 0x80, 0x4a, 0x40,
>>>> +       0x84, 0xe0, 0x08, 0x5c, 0x00, 0x9a, 0x06, 0x01, 0xe0, 0xe0, 0x14, 0x80,
>>>> +       0x25, 0x02, 0x54, 0xe0, 0x29, 0xc4, 0x25, 0x42, 0x54, 0xe0, 0x24, 0x80,
>>>> +       0x35, 0x04, 0x6c, 0xe0, 0x39, 0xc4, 0x35, 0x44, 0x6c, 0xe0, 0x25, 0x02,
>>>> +       0x64, 0xe0, 0x29, 0xc4, 0x25, 0x42, 0x64, 0xe0, 0x04, 0x80, 0x15, 0x00,
>>>> +       0x7c, 0xe0, 0x19, 0xc4, 0x15, 0x40, 0x7c, 0xe0, 0x93, 0xdd, 0xc3, 0xc1,
>>>> +       0x4c, 0x04, 0x7c, 0xfa, 0x86, 0x40, 0x98, 0xe0, 0x14, 0x80, 0x1b, 0xa1,
>>>> +       0x06, 0x00, 0x00, 0xc0, 0x08, 0x42, 0x38, 0xdc, 0x08, 0x64, 0xa0, 0xef,
>>>> +       0x86, 0x42, 0x3c, 0xe0, 0x68, 0x49, 0x80, 0xef, 0x6b, 0x80, 0x78, 0x53,
>>>> +       0xc8, 0xef, 0xc6, 0x54, 0x6c, 0xe1, 0x7b, 0x80, 0xb5, 0x14, 0x0c, 0xf8,
>>>> +       0x05, 0x14, 0x14, 0xf8, 0x1a, 0xac, 0x8a, 0x80, 0x0b, 0x90, 0x38, 0x55,
>>>> +       0x80, 0xef, 0x1a, 0xae, 0x17, 0xc2, 0x03, 0x82, 0x88, 0x65, 0x80, 0xef,
>>>> +       0x1b, 0x80, 0x0b, 0x8e, 0x68, 0x65, 0x80, 0xef, 0x9b, 0x80, 0x0b, 0x8c,
>>>> +       0x08, 0x65, 0x80, 0xef, 0x6b, 0x80, 0x0b, 0x92, 0x1b, 0x8c, 0x98, 0x64,
>>>> +       0x80, 0xef, 0x1a, 0xec, 0x9b, 0x80, 0x0b, 0x90, 0x95, 0x54, 0x10, 0xe0,
>>>> +       0xa8, 0x53, 0x80, 0xef, 0x1a, 0xee, 0x17, 0xc2, 0x03, 0x82, 0xf8, 0x63,
>>>> +       0x80, 0xef, 0x1b, 0x80, 0x0b, 0x8e, 0xd8, 0x63, 0x80, 0xef, 0x1b, 0x8c,
>>>> +       0x68, 0x63, 0x80, 0xef, 0x6b, 0x80, 0x0b, 0x92, 0x65, 0x54, 0x14, 0xe0,
>>>> +       0x08, 0x65, 0x84, 0xef, 0x68, 0x63, 0x80, 0xef, 0x7b, 0x80, 0x0b, 0x8c,
>>>> +       0xa8, 0x64, 0x84, 0xef, 0x08, 0x63, 0x80, 0xef, 0x14, 0xe8, 0x46, 0x44,
>>>> +       0x94, 0xe1, 0x24, 0x88, 0x4a, 0x4e, 0x04, 0xe0, 0x14, 0xea, 0x1a, 0x04,
>>>> +       0x08, 0xe0, 0x0a, 0x40, 0x84, 0xed, 0x0c, 0x04, 0x00, 0xe2, 0x4a, 0x40,
>>>> +       0x04, 0xe0, 0x19, 0x16, 0xc0, 0xe0, 0x0a, 0x40, 0x84, 0xed, 0x21, 0x54,
>>>> +       0x60, 0xe0, 0x0c, 0x04, 0x00, 0xe2, 0x1b, 0xa5, 0x0e, 0xea, 0x01, 0x89,
>>>> +       0x21, 0x54, 0x64, 0xe0, 0x7e, 0xe8, 0x65, 0x82, 0x1b, 0xa7, 0x26, 0x00,
>>>> +       0x00, 0x80, 0xa5, 0x82, 0x1b, 0xa9, 0x65, 0x82, 0x1b, 0xa3, 0x01, 0x85,
>>>> +       0x16, 0x00, 0x00, 0xc0, 0x01, 0x54, 0x04, 0xf8, 0x06, 0xaa, 0x01, 0x83,
>>>> +       0x06, 0xa8, 0x65, 0x81, 0x06, 0xa8, 0x01, 0x54, 0x04, 0xf8, 0x01, 0x83,
>>>> +       0x06, 0xaa, 0x09, 0x14, 0x18, 0xf8, 0x0b, 0xa1, 0x05, 0x84, 0xc6, 0x42,
>>>> +       0xd4, 0xe0, 0x14, 0x84, 0x01, 0x83, 0x01, 0x54, 0x60, 0xe0, 0x01, 0x54,
>>>> +       0x64, 0xe0, 0x0b, 0x02, 0x90, 0xe0, 0x10, 0x02, 0x90, 0xe5, 0x01, 0x54,
>>>> +       0x88, 0xe0, 0xb5, 0x81, 0xc6, 0x40, 0xd4, 0xe0, 0x14, 0x80, 0x0b, 0x02,
>>>> +       0xe0, 0xe4, 0x10, 0x02, 0x31, 0x66, 0x02, 0xc0, 0x01, 0x54, 0x88, 0xe0,
>>>> +       0x1a, 0x84, 0x29, 0x14, 0x10, 0xe0, 0x1c, 0xaa, 0x2b, 0xa1, 0xf5, 0x82,
>>>> +       0x25, 0x14, 0x10, 0xf8, 0x2b, 0x04, 0xa8, 0xe0, 0x20, 0x44, 0x0d, 0x70,
>>>> +       0x03, 0xc0, 0x2b, 0xa1, 0x04, 0x00, 0x80, 0x9a, 0x02, 0x40, 0x84, 0x90,
>>>> +       0x03, 0x54, 0x04, 0x80, 0x4c, 0x0c, 0x7c, 0xf2, 0x93, 0xdd, 0x00, 0x00,
>>>> +       0x02, 0xa9, 0x00, 0x00, 0x64, 0x4a, 0x40, 0x00, 0x08, 0x2d, 0x58, 0xe0,
>>>> +       0xa8, 0x98, 0x40, 0x00, 0x28, 0x07, 0x34, 0xe0, 0x05, 0xb9, 0x00, 0x00,
>>>> +       0x28, 0x00, 0x41, 0x05, 0x88, 0x00, 0x41, 0x3c, 0x98, 0x00, 0x41, 0x52,
>>>> +       0x04, 0x01, 0x41, 0x79, 0x3c, 0x01, 0x41, 0x6a, 0x3d, 0xfe, 0x00, 0x00,
>>>> +};
>>>> +
>>>> +static const char * const vgxy61_test_pattern_menu[] = {
>>>> +       "Disabled",
>>>> +       "Solid",
>>>> +       "Colorbar",
>>>> +       "Gradbar",
>>>> +       "Hgrey",
>>>> +       "Vgrey",
>>>> +       "Dgrey",
>>>> +       "PN28",
>>>> +};
>>>> +
>>>> +static const char * const vgxy61_hdr_modes[] = {
>>>> +       "HDR linearize",
>>>> +       "HDR substraction",
>>>> +       "no HDR",
>>>> +};
>>>> +
>>>> +/* Regulator supplies */
>>>> +static const char * const vgxy61_supply_name[] = {
>>>> +       "VCORE",
>>>> +       "VDDIO",
>>>> +       "VANA",
>>>> +};
>>>> +
>>>> +static const s64 link_freq[] = {
>>>> +       /*
>>>> +        * MIPI output freq is 804Mhz / 2, as it uses both rising edge and falling edges to send
>>>> +        * data
>>>> +        */
>>>> +       402000000ULL
>>>> +};
>>>> +
>>>> +#define VGXY61_NUM_SUPPLIES            ARRAY_SIZE(vgxy61_supply_name)
>>>> +
>>>> +/* Macro to convert index to 8.8 fixed point gain */
>>>> +#define I2FP(i)                                ((u32)(8192.0 / (32 - (i))))
>>>> +/* Array of possibles analog gains in 8.8 fixed point */
>>>> +static const u16 analog_gains[] = {
>>>> +       I2FP(0), I2FP(1), I2FP(2), I2FP(3), I2FP(4), I2FP(5), I2FP(6), I2FP(7), I2FP(8), I2FP(9),
>>>> +       I2FP(10), I2FP(11), I2FP(12), I2FP(13), I2FP(14), I2FP(15), I2FP(16),
>>>> +};
>>>> +
>>>> +enum bin_mode {
>>>> +       BIN_MODE_NORMAL,
>>>> +       BIN_MODE_DIGITAL_X2,
>>>> +       BIN_MODE_DIGITAL_X4,
>>>> +       BIN_MODE_ANALOG_SUB_X2,
>>>> +       BIN_MODE_ANALOG_SUB_X4,
>>>> +};
>>>> +
>>>> +enum hdr {
>>>> +       HDR_LINEAR,
>>>> +       HDR_SUB,
>>>> +       NO_HDR,
>>>> +};
>>>> +
>>>> +enum strobe_modes {
>>>> +       STROBE_DISABLED,
>>>> +       STROBE_LONG,
>>>> +       STROBE_ENABLED,
>>>> +};
>>>> +
>>>> +struct vgxy61_mode_info {
>>>> +       u32 width;
>>>> +       u32 height;
>>>> +       enum bin_mode bin_mode;
>>>> +};
>>>> +
>>>> +static const u32 vgxy61_supported_codes[] = {
>>>> +       MEDIA_BUS_FMT_SGBRG8_1X8,
>>>> +       MEDIA_BUS_FMT_SGBRG10_1X10,
>>>> +       MEDIA_BUS_FMT_SGBRG12_1X12,
>>>> +       MEDIA_BUS_FMT_SGBRG14_1X14,
>>>> +       MEDIA_BUS_FMT_SGBRG16_1X16
>>>> +};
>>>> +
>>>> +const int vgx761_sensor_frame_rates[] = {75, 60, 30, 15, 10, 5, 2};
>>>> +const int vgx661_sensor_frame_rates[] = {60, 30, 15, 10, 5, 2};
>>>> +
>>>> +static const struct vgxy61_mode_info vgx661_mode_data[] = {
>>>> +       {1464, 1104, BIN_MODE_NORMAL},
>>>> +       {1280,  720, BIN_MODE_NORMAL},
>>>> +       { 640,  480, BIN_MODE_DIGITAL_X2},
>>>> +       { 320,  240, BIN_MODE_DIGITAL_X4},
>>>> +};
>>>> +
>>>> +static const struct vgxy61_mode_info vgx761_mode_data[] = {
>>>> +       {1944, 1204, BIN_MODE_NORMAL},
>>>> +       {1920, 1080, BIN_MODE_NORMAL},
>>>> +       {1280,  720, BIN_MODE_NORMAL},
>>>> +       { 640,  480, BIN_MODE_DIGITAL_X2},
>>>> +       { 320,  240, BIN_MODE_DIGITAL_X4},
>>>> +};
>>>> +
>>>> +struct gpios_ctrls {
>>>> +       struct v4l2_ctrl *long_start;
>>>> +       struct v4l2_ctrl *long_end;
>>>> +       struct v4l2_ctrl *short_start;
>>>> +       struct v4l2_ctrl *short_end;
>>>> +};
>>>> +
>>>> +struct vgxy61_ctrls {
>>>> +       struct v4l2_ctrl_handler handler;
>>>> +       struct v4l2_ctrl *exposure;
>>>> +       struct v4l2_ctrl *analog_gain;
>>>> +       struct v4l2_ctrl *digital_gain;
>>>> +       struct gpios_ctrls gpios;
>>>> +       struct v4l2_ctrl *vflip;
>>>> +       struct v4l2_ctrl *hflip;
>>>> +       struct v4l2_ctrl *patgen;
>>>> +       struct v4l2_ctrl *hdr;
>>>> +       struct v4l2_ctrl *pixel_rate;
>>>> +       struct v4l2_ctrl *link_freq;
>>>> +       struct v4l2_ctrl *temp;
>>>> +};
>>>> +
>>>> +struct vgxy61_dev {
>>>> +       struct i2c_client *i2c_client;
>>>> +       struct v4l2_subdev sd;
>>>> +       struct media_pad pad;
>>>> +       struct regulator_bulk_data supplies[VGXY61_NUM_SUPPLIES];
>>>> +       struct gpio_desc *reset_gpio;
>>>> +       struct clk *xclk;
>>>> +       u32 clk_freq;
>>>> +       int sensor_width;
>>>> +       int sensor_height;
>>>> +       u16 oif_ctrl;
>>>> +       int nb_of_lane;
>>>> +       int data_rate_in_mbps;
>>>> +       int pclk;
>>>> +       u16 line_length;
>>>> +       int rot_term;
>>>> +       bool gpios_polarity;
>>>> +       bool slave_mode;
>>>> +       /* Lock to protect all members below */
>>>> +       struct mutex lock;
>>>> +       struct vgxy61_ctrls ctrls;
>>>> +       bool streaming;
>>>> +       struct v4l2_mbus_framefmt fmt;
>>>> +       const struct vgxy61_mode_info *sensor_modes;
>>>> +       int sensor_modes_nb;
>>>> +       const struct vgxy61_mode_info *current_mode;
>>>> +       const int *sensor_rates;
>>>> +       int sensor_rates_nb;
>>>> +       struct v4l2_fract frame_interval;
>>>> +       bool hflip;
>>>> +       bool vflip;
>>>> +       enum hdr hdr;
>>>> +       int expo_long;
>>>> +       int expo_short;
>>>> +};
>>>> +
>>>> +static u8 get_bpp_by_code(__u32 code)
>>>> +{
>>>> +       switch (code) {
>>>> +       case MEDIA_BUS_FMT_SGBRG8_1X8:
>>>> +               return 8;
>>>> +       case MEDIA_BUS_FMT_SGBRG10_1X10:
>>>> +               return 10;
>>>> +       case MEDIA_BUS_FMT_SGBRG12_1X12:
>>>> +               return 12;
>>>> +       case MEDIA_BUS_FMT_SGBRG14_1X14:
>>>> +               return 14;
>>>> +       case MEDIA_BUS_FMT_SGBRG16_1X16:
>>>> +               return 16;
>>>> +       default:
>>>> +               /* Should never happen */
>>>> +               WARN(1, "Unsupported code %d. default to 8 bpp", code);
>>>> +               return 8;
>>>> +       }
>>>> +}
>>>> +
>>>> +static u8 get_data_type_by_code(__u32 code)
>>>> +{
>>>> +       switch (code) {
>>>> +       case MEDIA_BUS_FMT_SGBRG8_1X8:
>>>> +               return MIPI_CSI2_DT_RAW8;
>>>> +       case MEDIA_BUS_FMT_SGBRG10_1X10:
>>>> +               return MIPI_CSI2_DT_RAW10;
>>>> +       case MEDIA_BUS_FMT_SGBRG12_1X12:
>>>> +               return MIPI_CSI2_DT_RAW12;
>>>> +       case MEDIA_BUS_FMT_SGBRG14_1X14:
>>>> +               return MIPI_CSI2_DT_RAW14;
>>>> +       case MEDIA_BUS_FMT_SGBRG16_1X16:
>>>> +               return MIPI_CSI2_DT_RAW16;
>>>> +       default:
>>>> +               /* Should never happen */
>>>> +               WARN(1, "Unsupported code %d. default to MIPI_CSI2_DT_RAW8 data type", code);
>>>> +               return MIPI_CSI2_DT_RAW8;
>>>> +       }
>>>> +}
>>>> +
>>>> +static void compute_pll_parameters_by_freq(u32 freq, unsigned int *prediv, unsigned int *mult)
>>>> +{
>>>> +       const unsigned int predivs[] = {1, 2, 4};
>>>> +       int i;
>>>> +
>>>> +       /*
>>>> +        * Freq range is [6Mhz-27Mhz] already checked.
>>>> +        * Output of divider should be in [6Mhz-12Mhz[.
>>>> +        */
>>>> +       for (i = 0; i < ARRAY_SIZE(predivs); i++) {
>>>> +               *prediv = predivs[i];
>>>> +               if (freq / *prediv < 12 * HZ_PER_MHZ)
>>>> +                       break;
>>>> +       }
>>>> +       WARN_ON(i == ARRAY_SIZE(predivs));
>>>> +
>>>> +       /*
>>>> +        * Target freq is 804Mhz. Don't change this as it will impact image quality.
>>>> +        */
>>>> +       *mult = ((804 * HZ_PER_MHZ) * (*prediv) + freq / 2) / freq;
>>>> +}
>>>> +
>>>> +static s32 get_pixel_rate(struct vgxy61_dev *sensor)
>>>> +{
>>>> +       return div64_u64((u64)sensor->data_rate_in_mbps * sensor->nb_of_lane,
>>>> +                        get_bpp_by_code(sensor->fmt.code));
>>>> +}
>>>> +
>>>> +static inline struct vgxy61_dev *to_vgxy61_dev(struct v4l2_subdev *sd)
>>>> +{
>>>> +       return container_of(sd, struct vgxy61_dev, sd);
>>>> +}
>>>> +
>>>> +static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl)
>>>> +{
>>>> +       return &container_of(ctrl->handler, struct vgxy61_dev, ctrls.handler)->sd;
>>>> +}
>>>> +
>>>> +static int get_chunk_size(struct vgxy61_dev *sensor)
>>>> +{
>>>> +       struct i2c_adapter *adapter = sensor->i2c_client->adapter;
>>>> +       int max_write_len = WRITE_MULTIPLE_CHUNK_MAX;
>>>> +
>>>> +       if (adapter->quirks && adapter->quirks->max_write_len)
>>>> +               max_write_len = adapter->quirks->max_write_len - 2;
>>>> +
>>>> +       max_write_len = min(max_write_len, WRITE_MULTIPLE_CHUNK_MAX);
>>>> +
>>>> +       return max(max_write_len, 1);
>>>> +}
>>>> +
>>>> +static int vgxy61_read_multiple(struct vgxy61_dev *sensor, u16 reg, u8 *val, int len)
>>>> +{
>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>> +       struct i2c_msg msg[2];
>>>> +       u8 buf[2];
>>>> +       int ret;
>>>> +
>>>> +       buf[0] = reg >> 8;
>>>> +       buf[1] = reg & 0xff;
>>>> +
>>>> +       msg[0].addr = client->addr;
>>>> +       msg[0].flags = client->flags;
>>>> +       msg[0].buf = buf;
>>>> +       msg[0].len = sizeof(buf);
>>>> +
>>>> +       msg[1].addr = client->addr;
>>>> +       msg[1].flags = client->flags | I2C_M_RD;
>>>> +       msg[1].buf = val;
>>>> +       msg[1].len = len;
>>>> +
>>>> +       ret = i2c_transfer(client->adapter, msg, 2);
>>>> +       if (ret < 0) {
>>>> +               dev_dbg(&client->dev, "%s: %x i2c_transfer, reg: %x => %d\n", __func__,
>>>> +                       client->addr, reg, ret);
>>>> +               return ret;
>>>> +       }
>>>> +
>>>> +       return 0;
>>>> +}
>>>> +
>>>> +static inline int vgxy61_read_reg(struct vgxy61_dev *sensor, u16 reg, u8 *val)
>>>> +{
>>>> +       return vgxy61_read_multiple(sensor, reg, val, sizeof(*val));
>>>> +}
>>>> +
>>>> +static inline int vgxy61_read_reg16(struct vgxy61_dev *sensor, u16 reg, u16 *val)
>>>> +{
>>>> +       return vgxy61_read_multiple(sensor, reg, (u8 *)val, sizeof(*val));
>>>> +}
>>>> +
>>>> +static int vgxy61_write_multiple(struct vgxy61_dev *sensor, u16 reg, const u8 *data, int len)
>>>> +{
>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>> +       struct i2c_msg msg;
>>>> +       u8 buf[WRITE_MULTIPLE_CHUNK_MAX + 2];
>>>> +       int i;
>>>> +       int ret;
>>>> +
>>>> +       if (len > WRITE_MULTIPLE_CHUNK_MAX)
>>>> +               return -EINVAL;
>>>> +       buf[0] = reg >> 8;
>>>> +       buf[1] = reg & 0xff;
>>>> +       for (i = 0; i < len; i++)
>>>> +               buf[i + 2] = data[i];
>>>> +
>>>> +       msg.addr = client->addr;
>>>> +       msg.flags = client->flags;
>>>> +       msg.buf = buf;
>>>> +       msg.len = len + 2;
>>>> +
>>>> +       ret = i2c_transfer(client->adapter, &msg, 1);
>>>> +       if (ret < 0) {
>>>> +               dev_dbg(&client->dev, "%s: i2c_transfer, reg: %x => %d\n", __func__, reg, ret);
>>>> +               return ret;
>>>> +       }
>>>> +
>>>> +       return 0;
>>>> +}
>>>> +
>>>> +static int vgxy61_write_array(struct vgxy61_dev *sensor, u16 reg, int nb, const u8 *array)
>>>> +{
>>>> +       const int chunk_size = get_chunk_size(sensor);
>>>> +       int ret;
>>>> +       int sz;
>>>> +
>>>> +       while (nb) {
>>>> +               sz = min(nb, chunk_size);
>>>> +               ret = vgxy61_write_multiple(sensor, reg, array, sz);
>>>> +               if (ret < 0)
>>>> +                       return ret;
>>>> +               nb -= sz;
>>>> +               reg += sz;
>>>> +               array += sz;
>>>> +       }
>>>> +
>>>> +       return 0;
>>>> +}
>>>> +
>>>> +static inline int vgxy61_write_reg(struct vgxy61_dev *sensor, u16 reg, u8 val)
>>>> +{
>>>> +       return vgxy61_write_multiple(sensor, reg, &val, sizeof(val));
>>>> +}
>>>> +
>>>> +static inline int vgxy61_write_reg16(struct vgxy61_dev *sensor, u16 reg, u16 val)
>>>> +{
>>>> +       return vgxy61_write_multiple(sensor, reg, (u8 *)&val, sizeof(val));
>>>> +}
>>>> +
>>>> +static inline int vgxy61_write_reg32(struct vgxy61_dev *sensor, u16 reg, u32 val)
>>>> +{
>>>> +       return vgxy61_write_multiple(sensor, reg, (u8 *)&val, sizeof(val));
>>>> +}
>>>> +
>>>> +static int vgxy61_poll_reg(struct vgxy61_dev *sensor, u16 reg, u8 poll_val)
>>>> +{
>>>> +       const int loop_delay_ms = 10;
>>>> +       const int timeout_ms = 500;
>>>> +       u8 val;
>>>> +       int ret, timeout;
>>>> +
>>>> +       timeout = read_poll_timeout(vgxy61_read_reg, ret, ((ret != 0) || (val == poll_val)),
>>>> +                                   loop_delay_ms * US_PER_MS, timeout_ms * US_PER_MS, false,
>>>> +                                   sensor, reg, &val);
>>>> +       if (timeout)
>>>> +               return timeout;
>>>> +
>>>> +       return ret;
>>>> +}
>>>> +
>>>> +static int vgxy61_wait_state(struct vgxy61_dev *sensor, int state)
>>>> +{
>>>> +       return vgxy61_poll_reg(sensor, DEVICE_SYSTEM_FSM, state);
>>>> +}
>>>> +
>>>> +static int vgxy61_check_bw(struct vgxy61_dev *sensor)
>>>> +{
>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>> +       /* Correction factor for time required between 2 lines */
>>>> +       const int mipi_margin = 1056;
>>>> +       int binning_scale = 1 << sensor->current_mode->bin_mode;
>>>> +       int bpp = get_bpp_by_code(sensor->fmt.code);
>>>> +       int max_bit_per_line;
>>>> +       int bit_per_line;
>>>> +       u64 line_rate;
>>>> +
>>>> +       line_rate = sensor->nb_of_lane * (u64)sensor->data_rate_in_mbps * sensor->line_length;
>>>> +       max_bit_per_line = div64_u64(line_rate, sensor->pclk) - mipi_margin;
>>>> +       bit_per_line = (bpp * sensor->current_mode->width) / binning_scale;
>>>> +
>>>> +       dev_dbg(&client->dev, "max_bit_per_line = %d\n", max_bit_per_line);
>>>> +       dev_dbg(&client->dev, "required bit_per_line = %d\n", bit_per_line);
>>>> +
>>>> +       return bit_per_line > max_bit_per_line ? -EINVAL : 0;
>>>> +}
>>>> +
>>>> +static int apply_exposure(struct vgxy61_dev *sensor)
>>>> +{
>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>> +       int ret;
>>>> +
>>>> +        /* We first set expo to zero to avoid forbidden parameters couple */
>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_SHORT, 0);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_LONG, sensor->expo_long);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_SHORT, sensor->expo_short);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +
>>>> +       dev_dbg(&client->dev, "%s applied expo %d (short: %d)\n", __func__,
>>>> +               sensor->expo_long, sensor->expo_short);
>>>> +
>>>> +       return 0;
>>>> +}
>>>> +
>>>> +static int set_frame_rate(struct vgxy61_dev *sensor)
>>>> +{
>>>> +       u16 frame_length;
>>>> +
>>>> +       frame_length = sensor->pclk / (sensor->line_length * sensor->frame_interval.denominator);
>>>> +
>>>> +       return vgxy61_write_reg16(sensor, DEVICE_FRAME_LENGTH, frame_length);
>>>> +}
>>>> +
>>>> +static int vgxy61_get_regulators(struct vgxy61_dev *sensor)
>>>> +{
>>>> +       int i;
>>>> +
>>>> +       for (i = 0; i < VGXY61_NUM_SUPPLIES; i++)
>>>> +               sensor->supplies[i].supply = vgxy61_supply_name[i];
>>>> +
>>>> +       return devm_regulator_bulk_get(&sensor->i2c_client->dev, VGXY61_NUM_SUPPLIES,
>>>> +                                      sensor->supplies);
>>>> +}
>>>> +
>>>> +static int vgxy61_apply_reset(struct vgxy61_dev *sensor)
>>>> +{
>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>> +
>>>> +       dev_dbg(&client->dev, "%s applied reset\n", __func__);
>>>> +       gpiod_set_value_cansleep(sensor->reset_gpio, 0);
>>>> +       usleep_range(5000, 10000);
>>>> +       gpiod_set_value_cansleep(sensor->reset_gpio, 1);
>>>> +       usleep_range(5000, 10000);
>>>> +       gpiod_set_value_cansleep(sensor->reset_gpio, 0);
>>>> +       usleep_range(40000, 100000);
>>>> +       return vgxy61_wait_state(sensor, SW_STBY);
>>>> +}
>>>> +
>>>> +static int vgxy61_try_fmt_internal(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *fmt,
>>>> +                                  const struct vgxy61_mode_info **new_mode)
>>>> +{
>>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>> +       const struct vgxy61_mode_info *mode = sensor->sensor_modes;
>>>> +       unsigned int index;
>>>> +
>>>> +       /* Select code */
>>>> +       for (index = 0; index < ARRAY_SIZE(vgxy61_supported_codes); index++) {
>>>> +               if (vgxy61_supported_codes[index] == fmt->code)
>>>> +                       break;
>>>> +       }
>>>> +       if (index == ARRAY_SIZE(vgxy61_supported_codes))
>>>> +               index = 0;
>>>> +
>>>> +       /* Select size */
>>>> +       do {
>>>> +               if (mode->width <= fmt->width && mode->height <= fmt->height)
>>>> +                       break;
>>>> +       } while ((++mode)->width);
>>>> +       if (!mode->width)
>>>> +               mode--;
>>>> +
>>>> +       *new_mode = mode;
>>>> +       fmt->code = vgxy61_supported_codes[index];
>>>> +       fmt->width = mode->width;
>>>> +       fmt->height = mode->height;
>>>> +       fmt->colorspace = V4L2_COLORSPACE_SRGB;
>>>> +       fmt->field = V4L2_FIELD_NONE;
>>>> +
>>>> +       return 0;
>>>> +}
>>>> +
>>>> +static int vgxy61_stream_enable(struct vgxy61_dev *sensor)
>>>> +{
>>>> +       int center_x = sensor->sensor_width / 2;
>>>> +       int center_y = sensor->sensor_height / 2;
>>>> +       int scale = 1 << sensor->current_mode->bin_mode;
>>>> +       int width = sensor->current_mode->width * scale;
>>>> +       int height = sensor->current_mode->height * scale;
>>>> +       int ret;
>>>> +
>>>> +       ret = vgxy61_check_bw(sensor);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +
>>>> +       /* Configure sensor */
>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_FORMAT_CTRL, get_bpp_by_code(sensor->fmt.code));
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_OIF_ROI0_CTRL,
>>>> +                              get_data_type_by_code(sensor->fmt.code));
>>>> +       if (ret)
>>>> +               return ret;
>>>> +
>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_READOUT_CTRL, sensor->current_mode->bin_mode);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_H, center_x - width / 2);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_H, center_x + width / 2 - 1);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_V, center_y - height / 2);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_V, center_y + height / 2 - 1);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +
>>>> +       ret = set_frame_rate(sensor);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +
>>>> +       ret = apply_exposure(sensor);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +
>>>> +       /* Start streaming */
>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_START_STREAMING);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +
>>>> +       ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +
>>>> +       ret = vgxy61_wait_state(sensor, STREAMING);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +
>>>> +       return 0;
>>>> +}
>>>> +
>>>> +static int vgxy61_stream_disable(struct vgxy61_dev *sensor)
>>>> +{
>>>> +       int ret;
>>>> +
>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_STOP_STREAMING);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +
>>>> +       ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +
>>>> +       ret = vgxy61_wait_state(sensor, SW_STBY);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +
>>>> +       return 0;
>>>> +}
>>>> +
>>>> +static int vgxy61_s_stream(struct v4l2_subdev *sd, int enable)
>>>> +{
>>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>> +       int ret = 0;
>>>> +
>>>> +       mutex_lock(&sensor->lock);
>>>> +       dev_dbg(&client->dev, "%s : requested %d / current = %d\n", __func__, enable,
>>>> +               sensor->streaming);
>>>> +       if (sensor->streaming == enable)
>>>> +               goto out;
>>>> +
>>>> +       ret = enable ? vgxy61_stream_enable(sensor) : vgxy61_stream_disable(sensor);
>>>> +       if (!ret)
>>>> +               sensor->streaming = enable;
>>>> +
>>>> +out:
>>>> +       dev_dbg(&client->dev, "%s current now = %d / %d\n", __func__, sensor->streaming, ret);
>>>> +       mutex_unlock(&sensor->lock);
>>>> +
>>>> +       return ret;
>>>> +}
>>>> +
>>>> +static int vgxy61_g_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *fi)
>>>> +{
>>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>> +
>>>> +       mutex_lock(&sensor->lock);
>>>> +       fi->interval = sensor->frame_interval;
>>>> +       mutex_unlock(&sensor->lock);
>>>> +
>>>> +       return 0;
>>>> +}
>>>> +
>>>> +static int vgxy61_s_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *fi)
>>>> +{
>>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>> +       u64 req_int, err, min_err = ~0ULL;
>>>> +       u64 test_int;
>>>> +       int i = 0;
>>>> +       int ret;
>>>> +
>>>> +       if (fi->interval.denominator == 0)
>>>> +               return -EINVAL;
>>>> +
>>>> +       mutex_lock(&sensor->lock);
>>>> +
>>>> +       if (sensor->streaming) {
>>>> +               ret = -EBUSY;
>>>> +               goto out;
>>>> +       }
>>>> +
>>>> +       dev_dbg(&client->dev, "%s request %d/%d\n", __func__,
>>>> +               fi->interval.numerator, fi->interval.denominator);
>>>> +       /* Find nearest period */
>>>> +       req_int = div64_u64((u64)(fi->interval.numerator * 10000), fi->interval.denominator);
>>>> +       for (i = 0; i < sensor->sensor_rates_nb; i++) {
>>>> +               test_int = div64_u64((u64)10000, sensor->sensor_rates[i]);
>>>> +               err = abs(test_int - req_int);
>>>> +               if (err < min_err) {
>>>> +                       fi->interval.numerator = 1;
>>>> +                       fi->interval.denominator = sensor->sensor_rates[i];
>>>> +                       min_err = err;
>>>> +               }
>>>> +       }
>>>> +       sensor->frame_interval = fi->interval;
>>>> +       dev_dbg(&client->dev, "%s set     %d/%d\n", __func__,
>>>> +               fi->interval.numerator, fi->interval.denominator);
>>>> +
>>>> +       ret = 0;
>>>> +out:
>>>> +       mutex_unlock(&sensor->lock);
>>>> +
>>>> +       return ret;
>>>> +}
>>>> +
>>>> +static int vgxy61_enum_mbus_code(struct v4l2_subdev *sd,
>>>> +                                struct v4l2_subdev_state *sd_state,
>>>> +                                struct v4l2_subdev_mbus_code_enum *code)
>>>> +{
>>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>> +
>>>> +       dev_dbg(&client->dev, "%s probe index %d\n", __func__, code->index);
>>>> +       if (code->index >= ARRAY_SIZE(vgxy61_supported_codes))
>>>> +               return -EINVAL;
>>>> +
>>>> +       code->code = vgxy61_supported_codes[code->index];
>>>> +
>>>> +       return 0;
>>>> +}
>>>> +
>>>> +static int vgxy61_get_fmt(struct v4l2_subdev *sd,
>>>> +                         struct v4l2_subdev_state *sd_state,
>>>> +                         struct v4l2_subdev_format *format)
>>>> +{
>>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>> +       struct v4l2_mbus_framefmt *fmt;
>>>> +
>>>> +       dev_dbg(&client->dev, "%s probe %d\n", __func__, format->pad);
>>>> +       dev_dbg(&client->dev, "%s %dx%d\n", __func__, format->format.width, format->format.height);
>>>> +
>>>> +       mutex_lock(&sensor->lock);
>>>> +
>>>> +       if (format->which == V4L2_SUBDEV_FORMAT_TRY)
>>>> +               fmt = v4l2_subdev_get_try_format(&sensor->sd, sd_state, format->pad);
>>>> +       else
>>>> +               fmt = &sensor->fmt;
>>>> +
>>>> +       format->format = *fmt;
>>>> +
>>>> +       mutex_unlock(&sensor->lock);
>>>> +
>>>> +       return 0;
>>>> +}
>>>> +
>>>> +static int vgxy61_set_fmt(struct v4l2_subdev *sd,
>>>> +                         struct v4l2_subdev_state *sd_state,
>>>> +                         struct v4l2_subdev_format *format)
>>>> +{
>>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>> +       const struct vgxy61_mode_info *new_mode;
>>>> +       struct v4l2_mbus_framefmt *fmt;
>>>> +       int ret;
>>>> +
>>>> +       dev_dbg(&client->dev, "%s probe %d\n", __func__, format->pad);
>>>> +       dev_dbg(&client->dev, "%s %dx%d\n", __func__, format->format.width, format->format.height);
>>>> +
>>>> +       mutex_lock(&sensor->lock);
>>>> +
>>>> +       if (sensor->streaming) {
>>>> +               ret = -EBUSY;
>>>> +               goto out;
>>>> +       }
>>>> +
>>>> +       /* Find best format */
>>>> +       ret = vgxy61_try_fmt_internal(sd, &format->format, &new_mode);
>>>> +       if (ret)
>>>> +               goto out;
>>>> +
>>>> +       if (format->which == V4L2_SUBDEV_FORMAT_TRY)
>>>> +               fmt = v4l2_subdev_get_try_format(sd, sd_state, 0);
>>>> +       else
>>>> +               fmt = &sensor->fmt;
>>>> +       *fmt = format->format;
>>>> +       sensor->current_mode = new_mode;
>>>> +       /* Update pixel rate control to reflect new mode */
>>>> +       __v4l2_ctrl_s_ctrl_int64(sensor->ctrls.pixel_rate, get_pixel_rate(sensor));
>>>> +
>>>> +out:
>>>> +       mutex_unlock(&sensor->lock);
>>>> +
>>>> +       return ret;
>>>> +}
>>>> +
>>>> +static int vgxy61_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state,
>>>> +                                 struct v4l2_subdev_frame_size_enum *fse)
>>>> +{
>>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>> +
>>>> +       dev_dbg(&client->dev, "%s for index %d\n", __func__, fse->index);
>>>> +       if (fse->index >= sensor->sensor_modes_nb)
>>>> +               return -EINVAL;
>>>> +
>>>> +       fse->min_width = sensor->sensor_modes[fse->index].width;
>>>> +       fse->max_width = fse->min_width;
>>>> +       fse->min_height = sensor->sensor_modes[fse->index].height;
>>>> +       fse->max_height = fse->min_height;
>>>> +
>>>> +       return 0;
>>>> +}
>>>> +
>>>> +static int vgxy61_enum_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state,
>>>> +                                     struct v4l2_subdev_frame_interval_enum *fie)
>>>> +{
>>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>> +       const struct vgxy61_mode_info *mode = sensor->sensor_modes;
>>>> +
>>>> +       if (fie->index >= sensor->sensor_rates_nb)
>>>> +               return -EINVAL;
>>>> +       do {
>>>> +               if (mode->width == fie->width && mode->height == fie->height)
>>>> +                       break;
>>>> +       } while ((++mode)->width);
>>>> +       if (!mode->width)
>>>> +               return -EINVAL;
>>>> +
>>>> +       fie->interval.numerator = 1;
>>>> +       fie->interval.denominator = sensor->sensor_rates[fie->index];
>>>> +
>>>> +       return 0;
>>>> +}
>>>> +
>>>> +static int vgxy61_update_analog_gain(struct vgxy61_dev *sensor, u32 target)
>>>> +{
>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>> +       unsigned int idx;
>>>> +       int ret;
>>>> +
>>>> +       /* Find smallest analog gains which is above or equal to target gain */
>>>> +       for (idx = 0; idx < ARRAY_SIZE(analog_gains); idx++) {
>>>> +               if (analog_gains[idx] >= target)
>>>> +                       break;
>>>> +       }
>>>> +       /* Cap to maximum gain if no matching gain found */
>>>> +       if (idx == ARRAY_SIZE(analog_gains))
>>>> +               idx--;
>>>> +
>>>> +       /* Apply gain */
>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_ANALOG_GAIN, idx);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +
>>>> +       dev_dbg(&client->dev, "Target analog gain: 0x%04x\n", target);
>>>> +       dev_dbg(&client->dev, "   Set analog gain: 0x%04x\n", analog_gains[idx]);
>>>> +
>>>> +       return 0;
>>>> +}
>>>> +
>>>> +static int vgxy61_update_digital_gain(struct vgxy61_dev *sensor, u32 target)
>>>> +{
>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>> +       int ret;
>>>> +
>>>> +       /*
>>>> +        * For a monochrome version, configuring DIGITAL_GAIN_LONG_CH0 and
>>>> +        * DIGITAL_GAIN_SHORT_CH0 is enough to configure the gain of all
>>>> +        * four sub pixels.
>>>> +        */
>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_DIGITAL_GAIN_LONG, target);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_DIGITAL_GAIN_SHORT, target);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +
>>>> +       dev_dbg(&client->dev, "  Set digital gain: 0x%04x\n", target);
>>>> +
>>>> +       return 0;
>>>> +}
>>>> +
>>>> +static int vgxy61_update_patgen(struct vgxy61_dev *sensor, u32 index)
>>>> +{
>>>> +       u32 pattern = index <= 3 ? index : index + 12;
>>>> +       u32 reg;
>>>> +
>>>> +       reg = (pattern << 18) | (pattern << 4);
>>>> +       if (index)
>>>> +               reg |= (1 << 16) | 1;
>>>> +       return vgxy61_write_reg32(sensor, DEVICE_PATGEN_CTRL, reg);
>>>> +}
>>>> +
>>>> +static int vgxy61_update_gpiox_strobe_mode(struct vgxy61_dev *sensor, enum strobe_modes mode,
>>>> +                                          int idx)
>>>> +{
>>>> +       const u8 index2val[] = {0x0, 0x1, 0x3};
>>>> +       u16 reg;
>>>> +       int ret;
>>>> +
>>>> +       ret = vgxy61_read_reg16(sensor, DEVICE_SIGNALS_CTRL, &reg);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       reg &= ~(0xf << (idx * 4));
>>>> +       reg |= index2val[mode] << (idx * 4);
>>>> +
>>>> +       return vgxy61_write_reg16(sensor, DEVICE_SIGNALS_CTRL, reg);
>>>> +}
>>>> +
>>>> +static int vgxy61_update_gpios_strobe_mode(struct vgxy61_dev *sensor, enum hdr hdr)
>>>> +{
>>>> +       enum strobe_modes strobe_mode;
>>>> +       int i, ret;
>>>> +
>>>> +       switch (hdr) {
>>>> +       case HDR_LINEAR:
>>>> +               strobe_mode = STROBE_ENABLED;
>>>> +               break;
>>>> +       case HDR_SUB:
>>>> +       case NO_HDR:
>>>> +               strobe_mode = STROBE_LONG;
>>>> +               break;
>>>> +       default:
>>>> +               /* Should never happen */
>>>> +               WARN_ON(true);
>>>> +               break;
>>>> +       }
>>>> +
>>>> +       for (i = 0; i < NB_GPIOS; i++) {
>>>> +               ret = vgxy61_update_gpiox_strobe_mode(sensor, strobe_mode, i);
>>>> +               if (ret)
>>>> +                       return ret;
>>>> +       }
>>>> +
>>>> +       return 0;
>>>> +}
>>>> +
>>>> +static int vgxy61_update_gpios_strobe_polarity(struct vgxy61_dev *sensor, int polarity)
>>>> +{
>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>> +       int ret;
>>>> +
>>>> +       if (sensor->streaming)
>>>> +               return -EBUSY;
>>>> +
>>>> +       dev_dbg(&client->dev, "setting gpios polarity: %d\n", polarity);
>>>> +
>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_0_CTRL, polarity << 1);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_1_CTRL, polarity << 1);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_2_CTRL, polarity << 1);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_3_CTRL, polarity << 1);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       return vgxy61_write_reg(sensor, DEVICE_SIGNALS_POLARITY_CTRL, polarity);
>>>> +}
>>>> +
>>>> +static int find_max_long_exposure(struct vgxy61_dev *sensor, int frame_length, int short_expo_ratio)
>>>> +{
>>>> +       int first_rot_max_expo;
>>>> +       int second_rot_max_expo;
>>>> +
>>>> +       /*
>>>> +        * Apply first rule of thumb:
>>>> +        * frame_length < short_line_nb + sensor->sensor_height + sensor->rot_term
>>>> +        * with short_line_nb = long_line_nb / short_expo_ratio
>>>> +        */
>>>> +       first_rot_max_expo = ((frame_length - sensor->sensor_height - sensor->rot_term) *
>>>> +                            short_expo_ratio) - 1;
>>>> +
>>>> +       /*
>>>> +        * Apply second rule of thumb
>>>> +        * frame_length < short_line_nb + long_line_nb + VGXY61_EXPOS_ROT_TERM
>>>> +        * with short_line_nb = long_line_nb / short_expo_ratio
>>>> +        */
>>>> +       second_rot_max_expo = (((frame_length - VGXY61_EXPOS_ROT_TERM) * short_expo_ratio) /
>>>> +                             (short_expo_ratio + 1)) - 1;
>>>> +
>>>> +       /* Take the minimum of both rules */
>>>> +       return min(first_rot_max_expo, second_rot_max_expo);
>>>> +}
>>>> +
>>>> +static int vgxy61_update_exposure(struct vgxy61_dev *sensor, int long_expo_line_nb, enum hdr hdr,
>>>> +                                 bool clamp)
>>>> +{
>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>> +       int max_long_expo;
>>>> +       int max_short_expo = 0;
>>>> +       int short_expo_line_nb = 0;
>>>> +       /* We use a constant ratio of 10 for linear HDR mode */
>>>> +       int hdr_linear_ratio = 10;
>>>> +       u16 frame_length;
>>>> +       int ret;
>>>> +
>>>> +       ret = vgxy61_read_reg16(sensor, DEVICE_FRAME_LENGTH, &frame_length);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +
>>>> +       /* Long and short integration times must not be less than 10 lines */
>>>> +       long_expo_line_nb = max(10, long_expo_line_nb);
>>>> +       long_expo_line_nb = min_t(int, frame_length, long_expo_line_nb);
>>>> +
>>>> +       /* Compute short exposure according to hdr mode and long exposure */
>>>> +       switch (hdr) {
>>>> +       case HDR_LINEAR:
>>>> +               max_long_expo = find_max_long_exposure(sensor, frame_length, hdr_linear_ratio);
>>>> +               max_short_expo = (max_long_expo + (hdr_linear_ratio / 2)) / hdr_linear_ratio;
>>>> +               short_expo_line_nb = (long_expo_line_nb + (hdr_linear_ratio / 2)) /
>>>> +                                    hdr_linear_ratio;
>>>> +               break;
>>>> +       case HDR_SUB:
>>>> +               max_long_expo = find_max_long_exposure(sensor, frame_length, 1);
>>>> +               max_short_expo = max_long_expo;
>>>> +               short_expo_line_nb = long_expo_line_nb;
>>>> +               break;
>>>> +       case NO_HDR:
>>>> +               /*
>>>> +                * As short expo is 0 here, only the second rule of thumb applies, see
>>>> +                * find_max_long_exposure for more
>>>> +                */
>>>> +               max_long_expo = frame_length - VGXY61_EXPOS_ROT_TERM;
>>>> +               break;
>>>> +       default:
>>>> +               /* Should never happen */
>>>> +               WARN_ON(true);
>>>> +               break;
>>>> +       }
>>>> +
>>>> +       if (long_expo_line_nb > max_long_expo) {
>>>> +               if (!clamp) {
>>>> +                       dev_err(&client->dev, "Exposure %d too high (max for this hdr mode %d)\n",
>>>> +                               long_expo_line_nb, max_long_expo);
>>>> +                       return -EINVAL;
>>>> +               }
>>>> +               dev_warn(&client->dev, "Exposure %d too high for this hdr mode, clamping to %d\n",
>>>> +                        long_expo_line_nb, max_long_expo);
>>>> +               long_expo_line_nb = max_long_expo;
>>>> +               short_expo_line_nb = max_short_expo;
>>>> +       }
>>>> +
>>>> +       dev_dbg(&client->dev, "frame_length %d, long_expo_line_nb %d, short_expo_line_nb %d",
>>>> +               frame_length, long_expo_line_nb, short_expo_line_nb);
>>>> +
>>>> +       /* Apply exposure */
>>>> +       sensor->expo_long = long_expo_line_nb;
>>>> +       sensor->expo_short = short_expo_line_nb;
>>>> +
>>>> +       if (sensor->streaming)
>>>> +               return apply_exposure(sensor);
>>>> +       return 0;
>>>> +}
>>>> +
>>>> +static int vgxy61_update_hdr(struct vgxy61_dev *sensor, u32 index)
>>>> +{
>>>> +       const u8 index2val[] = {0x1, 0x4, 0xa};
>>>> +       int ret;
>>>> +
>>>> +       /*
>>>> +        * Short exposure changes according to HDR mode, do it first as it can
>>>> +        * violate sensors 'rule of thumbs' and therefore will require to change
>>>> +        * the long exposure
>>>> +        */
>>>> +       ret = vgxy61_update_exposure(sensor, sensor->expo_long, index, true);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +
>>>> +       /* Update strobe mode according to HDR */
>>>> +       ret = vgxy61_update_gpios_strobe_mode(sensor, index);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +
>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_HDR_CTRL, index2val[index]);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       sensor->hdr = index;
>>>> +
>>>> +       return 0;
>>>> +}
>>>> +
>>>> +static int vgxy61_get_temp_stream_enabled(struct vgxy61_dev *sensor, int *temp)
>>>> +{
>>>> +       int ret;
>>>> +       u16 temperature;
>>>> +
>>>> +       ret = vgxy61_read_reg16(sensor, DEVICE_THSENS1_TEMPERATURE, &temperature);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +
>>>> +       /* Temperature is expressed in Kelvin in Q10.2 fixed point format*/
>>>> +       temperature = (temperature & 0x0fff) >> 2;
>>>> +       temperature = kelvin_to_celsius(temperature);
>>>> +
>>>> +       *temp = temperature;
>>>> +
>>>> +       return 0;
>>>> +}
>>>> +
>>>> +static int vgxy61_get_temp_stream_disabled(struct vgxy61_dev *sensor, int *temp)
>>>> +{
>>>> +       int ret;
>>>> +
>>>> +       /* Device needs to be in standby mode if streaming is off */
>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_STBY, STBY_REQ_TMP_READ);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       ret = vgxy61_poll_reg(sensor, DEVICE_STBY, STBY_NO_REQ);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +
>>>> +       return vgxy61_get_temp_stream_enabled(sensor, temp);
>>>> +}
>>>> +
>>>> +static int vgxy61_get_temp(struct vgxy61_dev *sensor, int *temp)
>>>> +{
>>>> +       *temp = 0;
>>>> +       if (sensor->streaming)
>>>> +               return vgxy61_get_temp_stream_enabled(sensor, temp);
>>>> +       else
>>>> +               return vgxy61_get_temp_stream_disabled(sensor, temp);
>>>> +}
>>>> +
>>>> +static int vgxy61_s_ctrl(struct v4l2_ctrl *ctrl)
>>>> +{
>>>> +       struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
>>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>> +       int ret;
>>>> +
>>>> +       switch (ctrl->id) {
>>>> +       case V4L2_CID_EXPOSURE:
>>>> +               ret = vgxy61_update_exposure(sensor, ctrl->val, sensor->hdr, false);
>>>> +               ctrl->val = sensor->expo_long;
>>>> +               break;
>>>> +       case V4L2_CID_ANALOGUE_GAIN:
>>>> +               ret = vgxy61_update_analog_gain(sensor, ctrl->val);
>>>> +               break;
>>>> +       case V4L2_CID_DIGITAL_GAIN:
>>>> +               ret = vgxy61_update_digital_gain(sensor, ctrl->val);
>>>> +               break;
>>>> +       case V4L2_CID_VFLIP:
>>>> +       case V4L2_CID_HFLIP:
>>>> +               if (sensor->streaming) {
>>>> +                       ret = -EBUSY;
>>>> +                       break;
>>>> +               }
>>>> +               if (ctrl->id == V4L2_CID_VFLIP)
>>>> +                       sensor->vflip = ctrl->val;
>>>> +               if (ctrl->id == V4L2_CID_HFLIP)
>>>> +                       sensor->hflip = ctrl->val;
>>>> +               ret = vgxy61_write_reg(sensor, DEVICE_ORIENTATION,
>>>> +                                      sensor->hflip | (sensor->vflip << 1));
>>>> +               break;
>>>> +       case V4L2_CID_TEST_PATTERN:
>>>> +               ret = vgxy61_update_patgen(sensor, ctrl->val);
>>>> +               break;
>>>> +       case V4L2_CID_HDR:
>>>> +               ret = vgxy61_update_hdr(sensor, ctrl->val);
>>>> +               break;
>>>> +       case V4L2_CID_GPIOS_STROBE_LONG_START_DELAY:
>>>> +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_LONG_START_DELAY, ctrl->val);
>>>> +               break;
>>>> +       case V4L2_CID_GPIOS_STROBE_LONG_END_DELAY:
>>>> +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_LONG_END_DELAY, ctrl->val);
>>>> +               break;
>>>> +       case V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY:
>>>> +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_SHORT_START_DELAY, ctrl->val);
>>>> +               break;
>>>> +       case V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY:
>>>> +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_SHORT_END_DELAY, ctrl->val);
>>>> +               break;
>>>> +       default:
>>>> +               ret = -EINVAL;
>>>> +               break;
>>>> +       }
>>>> +
>>>> +       return ret;
>>>> +}
>>>> +
>>>> +static int vgxy61_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
>>>> +{
>>>> +       struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
>>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>> +       int temperature;
>>>> +       int ret;
>>>> +
>>>> +       switch (ctrl->id) {
>>>> +       case V4L2_CID_TEMPERATURE:
>>>> +               ret = vgxy61_get_temp(sensor, &temperature);
>>>> +               if (ret)
>>>> +                       break;
>>>> +               ret = __v4l2_ctrl_s_ctrl(ctrl, temperature);
>>>> +               break;
>>>> +       default:
>>>> +               ret = -EINVAL;
>>>> +               break;
>>>> +       }
>>>> +
>>>> +       return ret;
>>>> +}
>>>> +
>>>> +static const struct v4l2_ctrl_ops vgxy61_ctrl_ops = {
>>>> +       .g_volatile_ctrl = vgxy61_g_volatile_ctrl,
>>>> +       .s_ctrl = vgxy61_s_ctrl,
>>>> +};
>>>> +
>>>> +static const struct v4l2_ctrl_config vgxy61_hdr_ctrl = {
>>>> +       .ops            = &vgxy61_ctrl_ops,
>>>> +       .id             = V4L2_CID_HDR,
>>>> +       .name           = "HDR mode",
>>>> +       .type           = V4L2_CTRL_TYPE_MENU,
>>>> +       .min            = 0,
>>>> +       .max            = ARRAY_SIZE(vgxy61_hdr_modes) - 1,
>>>> +       .def            = NO_HDR,
>>>> +       .qmenu          = vgxy61_hdr_modes,
>>>> +};
>>>> +
>>>> +static const struct v4l2_ctrl_config vgxy61_strobe_long_start_delay = {
>>>> +       .ops            = &vgxy61_ctrl_ops,
>>>> +       .id             = V4L2_CID_GPIOS_STROBE_LONG_START_DELAY,
>>>> +       .name           = "Long strobe mode start delay in lines",
>>>> +       .type           = V4L2_CTRL_TYPE_INTEGER,
>>>> +       .min            = -128,
>>>> +       .max            = 127,
>>>> +       .step           = 1,
>>>> +       .def            = 0,
>>>> +       .flags          = 0,
>>>> +};
>>>> +
>>>> +static const struct v4l2_ctrl_config vgxy61_strobe_long_end_delay = {
>>>> +       .ops            = &vgxy61_ctrl_ops,
>>>> +       .id             = V4L2_CID_GPIOS_STROBE_LONG_END_DELAY,
>>>> +       .name           = "Long strobe mode end delay in lines",
>>>> +       .type           = V4L2_CTRL_TYPE_INTEGER,
>>>> +       .min            = -128,
>>>> +       .max            = 127,
>>>> +       .step           = 1,
>>>> +       .def            = 0,
>>>> +       .flags          = 0,
>>>> +};
>>>> +
>>>> +static const struct v4l2_ctrl_config vgxy61_strobe_short_start_delay = {
>>>> +       .ops            = &vgxy61_ctrl_ops,
>>>> +       .id             = V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY,
>>>> +       .name           = "Short strobe mode start delay in lines",
>>>> +       .type           = V4L2_CTRL_TYPE_INTEGER,
>>>> +       .min            = -128,
>>>> +       .max            = 127,
>>>> +       .step           = 1,
>>>> +       .def            = 0,
>>>> +       .flags          = 0,
>>>> +};
>>>> +
>>>> +static const struct v4l2_ctrl_config vgxy61_strobe_short_end_delay = {
>>>> +       .ops            = &vgxy61_ctrl_ops,
>>>> +       .id             = V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY,
>>>> +       .name           = "Short strobe mode end delay in lines",
>>>> +       .type           = V4L2_CTRL_TYPE_INTEGER,
>>>> +       .min            = -128,
>>>> +       .max            = 127,
>>>> +       .step           = 1,
>>>> +       .def            = 0,
>>>> +       .flags          = 0,
>>>> +};
>>>> +
>>>> +static const struct v4l2_ctrl_config vgxy61_temp_ctrl = {
>>>> +       .ops            = &vgxy61_ctrl_ops,
>>>> +       .id             = V4L2_CID_TEMPERATURE,
>>>> +       .name           = "Temperature in °C",
>>>> +       .type           = V4L2_CTRL_TYPE_INTEGER,
>>>> +       .min            = -128,
>>>> +       .max            = 128,
>>>> +       .step           = 1,
>>>> +};
>>>> +
>>>> +static int vgxy61_init_controls(struct vgxy61_dev *sensor)
>>>> +{
>>>> +       const struct v4l2_ctrl_ops *ops = &vgxy61_ctrl_ops;
>>>> +       struct vgxy61_ctrls *ctrls = &sensor->ctrls;
>>>> +       struct v4l2_ctrl_handler *hdl = &sensor->ctrls.handler;
>>>> +       int ret;
>>>> +
>>>> +       v4l2_ctrl_handler_init(hdl, 16);
>>>> +       /* We can use our own mutex for the ctrl lock */
>>>> +       hdl->lock = &sensor->lock;
>>>> +       ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, 10, 0xffff, 1,
>>>> +                                           sensor->expo_long);
>>>> +       /* This is 8.8 fixed point value */
>>>> +       ctrls->analog_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, 0x3fff, 1,
>>>> +                                              0x0);
>>>> +       /* This is 8.8 fixed point value */
>>>> +       ctrls->digital_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 0, 0xfff, 1,
>>>> +                                               0x100);
>>>> +       ctrls->vflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, sensor->vflip);
>>>> +       ctrls->hflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, sensor->hflip);
>>>> +       ctrls->patgen = v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN,
>>>> +                                                    ARRAY_SIZE(vgxy61_test_pattern_menu) - 1,
>>>> +                                                    0, 0, vgxy61_test_pattern_menu);
>>>> +       ctrls->hdr = v4l2_ctrl_new_custom(hdl, &vgxy61_hdr_ctrl, NULL);
>>>> +       ctrls->pixel_rate = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1,
>>>> +                                             get_pixel_rate(sensor));
>>>> +       ctrls->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>>>> +       ctrls->link_freq = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ,
>>>> +                                                 ARRAY_SIZE(link_freq) - 1, 0, link_freq);
>>>> +       ctrls->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>>>> +       /* Gpios ctrls */
>>>> +       ctrls->gpios.long_start = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_long_start_delay, NULL);
>>>> +       ctrls->gpios.long_end = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_long_end_delay, NULL);
>>>> +       ctrls->gpios.short_start = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_short_start_delay,
>>>> +                                                       NULL);
>>>> +       ctrls->gpios.short_end = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_short_end_delay, NULL);
>>>> +       /* Temperature ctrl */
>>>> +       ctrls->temp = v4l2_ctrl_new_custom(hdl, &vgxy61_temp_ctrl, NULL);
>>>> +       ctrls->temp->flags |= V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY;
>>>> +
>>>> +       if (hdl->error) {
>>>> +               ret = hdl->error;
>>>> +               goto free_ctrls;
>>>> +       }
>>>> +
>>>> +       sensor->sd.ctrl_handler = hdl;
>>>> +       return 0;
>>>> +
>>>> +free_ctrls:
>>>> +       v4l2_ctrl_handler_free(hdl);
>>>> +       return ret;
>>>> +}
>>>> +
>>>> +static const struct v4l2_subdev_core_ops vgxy61_core_ops = {
>>>> +};
>>>> +
>>>> +static const struct v4l2_subdev_video_ops vgxy61_video_ops = {
>>>> +       .s_stream = vgxy61_s_stream,
>>>> +       .g_frame_interval = vgxy61_g_frame_interval,
>>>> +       .s_frame_interval = vgxy61_s_frame_interval,
>>>> +};
>>>> +
>>>> +static const struct v4l2_subdev_pad_ops vgxy61_pad_ops = {
>>>> +       .enum_mbus_code = vgxy61_enum_mbus_code,
>>>> +       .get_fmt = vgxy61_get_fmt,
>>>> +       .set_fmt = vgxy61_set_fmt,
>>>> +       .enum_frame_size = vgxy61_enum_frame_size,
>>>> +       .enum_frame_interval = vgxy61_enum_frame_interval,
>>>> +};
>>>> +
>>>> +static const struct v4l2_subdev_ops vgxy61_subdev_ops = {
>>>> +       .core = &vgxy61_core_ops,
>>>> +       .video = &vgxy61_video_ops,
>>>> +       .pad = &vgxy61_pad_ops,
>>>> +};
>>>> +
>>>> +static const struct media_entity_operations vgxy61_subdev_entity_ops = {
>>>> +       .link_validate = v4l2_subdev_link_validate,
>>>> +};
>>>> +
>>>> +/* Set phy polarities */
>>>> +static int vgxy61_tx_from_ep(struct vgxy61_dev *sensor, struct fwnode_handle *endpoint)
>>>> +{
>>>> +       struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>> +       u32 log2phy[NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
>>>> +       u32 phy2log[NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
>>>> +       int polarities[NB_POLARITIES] = {0, 0, 0, 0, 0};
>>>> +       int l_nb;
>>>> +       int p, l;
>>>> +       int ret;
>>>> +       int i;
>>>> +
>>>> +       ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep);
>>>> +       if (ret)
>>>> +               goto error_alloc;
>>>> +
>>>> +       l_nb = ep.bus.mipi_csi2.num_data_lanes;
>>>> +       if (l_nb != 1 && l_nb != 2 && l_nb != 4) {
>>>> +               dev_err(&client->dev, "invalid data lane number %d\n", l_nb);
>>>> +               goto error_ep;
>>>> +       }
>>>> +
>>>> +       /* Build log2phy, phy2log and polarities from ep info */
>>>> +       log2phy[0] = ep.bus.mipi_csi2.clock_lane;
>>>> +       phy2log[log2phy[0]] = 0;
>>>> +       for (l = 1; l < l_nb + 1; l++) {
>>>> +               log2phy[l] = ep.bus.mipi_csi2.data_lanes[l - 1];
>>>> +               phy2log[log2phy[l]] = l;
>>>> +       }
>>>> +       /*
>>>> +        * Then fill remaining slots for every physical slot to have something valid for hardware
>>>> +        * stuff.
>>>> +        */
>>>> +       for (p = 0; p < NB_POLARITIES; p++) {
>>>> +               if (phy2log[p] != ~0)
>>>> +                       continue;
>>>> +               phy2log[p] = l;
>>>> +               log2phy[l] = p;
>>>> +               l++;
>>>> +       }
>>>> +       for (l = 0; l < l_nb + 1; l++)
>>>> +               polarities[l] = ep.bus.mipi_csi2.lane_polarities[l];
>>>> +
>>>> +       if (log2phy[0] != 0) {
>>>> +               dev_err(&client->dev, "clk lane must be map to physical lane 0\n");
>>>> +               goto error_ep;
>>>> +       }
>>>> +       sensor->oif_ctrl = (polarities[4] << 15) + ((phy2log[4] - 1) << 13) +
>>>> +                          (polarities[3] << 12) + ((phy2log[3] - 1) << 10) +
>>>> +                          (polarities[2] <<  9) + ((phy2log[2] - 1) <<  7) +
>>>> +                          (polarities[1] <<  6) + ((phy2log[1] - 1) <<  4) +
>>>> +                          (polarities[0] <<  3) +
>>>> +                          l_nb;
>>>> +       sensor->nb_of_lane = l_nb;
>>>> +
>>>> +       dev_dbg(&client->dev, "tx uses %d lanes", l_nb);
>>>> +       for (i = 0; i < 5; i++) {
>>>> +               dev_dbg(&client->dev, "log2phy[%d] = %d\n", i, log2phy[i]);
>>>> +               dev_dbg(&client->dev, "phy2log[%d] = %d\n", i, phy2log[i]);
>>>> +               dev_dbg(&client->dev, "polarity[%d] = %d\n", i, polarities[i]);
>>>> +       }
>>>> +       dev_dbg(&client->dev, "oif_ctrl = 0x%04x\n", sensor->oif_ctrl);
>>>> +
>>>> +       v4l2_fwnode_endpoint_free(&ep);
>>>> +
>>>> +       return 0;
>>>> +
>>>> +error_ep:
>>>> +       v4l2_fwnode_endpoint_free(&ep);
>>>> +error_alloc:
>>>> +
>>>> +       return -EINVAL;
>>>> +}
>>>> +
>>>> +static int vgxy61_configure(struct vgxy61_dev *sensor)
>>>> +{
>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>> +       int sensor_freq;
>>>> +       unsigned int prediv;
>>>> +       unsigned int mult;
>>>> +       int ret;
>>>> +
>>>> +       compute_pll_parameters_by_freq(sensor->clk_freq, &prediv, &mult);
>>>> +       sensor_freq = (mult * sensor->clk_freq) / prediv;
>>>> +       /* Frequency to data rate is 1:1 ratio for MIPI */
>>>> +       sensor->data_rate_in_mbps = sensor_freq;
>>>> +       /* Video timing ISP path (pixel clock)  requires 804/5 mhz = 160 mhz */
>>>> +       sensor->pclk = sensor_freq / 5;
>>>> +
>>>> +       /* Cache line_length value */
>>>> +       ret = vgxy61_read_reg16(sensor, DEVICE_LINE_LENGTH, &sensor->line_length);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       /* Configure clocks */
>>>> +       ret = vgxy61_write_reg32(sensor, DEVICE_EXT_CLOCK, sensor->clk_freq);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_CLK_PLL_PREDIV, prediv);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_CLK_SYS_PLL_MULT, mult);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       /* Configure interface */
>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_OIF_CTRL, sensor->oif_ctrl);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       /* Disable pwm compression */
>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_FRAME_CONTENT_CTRL, 0);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       /* Disable asil lines */
>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_BYPASS_CTRL, 4);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       /* Set gpios polarity according to device tree value */
>>>> +       ret = vgxy61_update_gpios_strobe_polarity(sensor, sensor->gpios_polarity);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       /* HDR mode */
>>>> +       ret = vgxy61_update_hdr(sensor, sensor->hdr);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       /* Slave mode */
>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_VT_CTRL, sensor->slave_mode);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       /* Set pattern generator solid to middle value */
>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_GR, 0x800);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_R, 0x800);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_B, 0x800);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_GB, 0x800);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_GR, 0x800);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_R, 0x800);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_B, 0x800);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_GB, 0x800);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +
>>>> +       dev_dbg(&client->dev, "clock prediv = %d\n", prediv);
>>>> +       dev_dbg(&client->dev, "clock mult = %d\n", mult);
>>>> +       dev_dbg(&client->dev, "data rate = %d mbps\n",
>>>> +               sensor->data_rate_in_mbps);
>>>> +
>>>> +       return 0;
>>>> +}
>>>> +
>>>> +static int vgxy61_patch(struct vgxy61_dev *sensor)
>>>> +{
>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>> +       u16 patch;
>>>> +       int ret;
>>>> +
>>>> +       ret = vgxy61_write_array(sensor, DEVICE_FWPATCH_START_ADDR, sizeof(patch_array),
>>>> +                                patch_array);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +
>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_STBY, 0x10);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +
>>>> +       ret = vgxy61_poll_reg(sensor, DEVICE_STBY, 0);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +
>>>> +       ret = vgxy61_read_reg16(sensor, DEVICE_FWPATCH_REVISION, &patch);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +
>>>> +       if (patch != (DEVICE_FWPATCH_REVISION_MAJOR << 12) +
>>>> +                    (DEVICE_FWPATCH_REVISION_MINOR << 8) +
>>>> +                    DEVICE_FWPATCH_REVISION_MICRO) {
>>>> +               dev_err(&client->dev, "bad patch version expected %d.%d.%d got %d.%d.%d\n",
>>>> +                       DEVICE_FWPATCH_REVISION_MAJOR,
>>>> +                       DEVICE_FWPATCH_REVISION_MINOR,
>>>> +                       DEVICE_FWPATCH_REVISION_MICRO,
>>>> +                       patch >> 12, (patch >> 8) & 0x0f, patch & 0xff);
>>>> +               return -ENODEV;
>>>> +       }
>>>> +       dev_dbg(&client->dev, "patch %d.%d.%d applied\n",
>>>> +               patch >> 12, (patch >> 8) & 0x0f, patch & 0xff);
>>>> +
>>>> +       return 0;
>>>> +}
>>>> +
>>>> +static int vgxy61_detect_cut_version(struct vgxy61_dev *sensor)
>>>> +{
>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>> +       u16 device_rev;
>>>> +       int ret;
>>>> +
>>>> +       ret = vgxy61_read_reg16(sensor, DEVICE_REVISION, &device_rev);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +
>>>> +       switch (device_rev >> 8) {
>>>> +       case 0xA:
>>>> +               dev_info(&client->dev, "Cut1 detected\n");
>>>> +               dev_err(&client->dev, "Cut1 not supported by this driver\n");
>>>> +               return -ENODEV;
>>>> +       case 0xB:
>>>> +               dev_info(&client->dev, "Cut2 detected\n");
>>>> +               return 0;
>>>> +       case 0xC:
>>>> +               dev_info(&client->dev, "Cut3 detected\n");
>>>> +               return 0;
>>>> +       default:
>>>> +               dev_err(&client->dev, "Unable to detect cut version\n");
>>>> +               return -ENODEV;
>>>> +       }
>>>> +}
>>>> +
>>>> +static int vgxy61_detect(struct vgxy61_dev *sensor)
>>>> +{
>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>> +       u16 id = 0;
>>>> +       int ret;
>>>> +       u8 st;
>>>> +
>>>> +       ret = vgxy61_read_reg16(sensor, DEVICE_MODEL_ID_REG, &id);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       if (id != VG5661_MODEL_ID && id != VG5761_MODEL_ID) {
>>>> +               dev_warn(&client->dev, "Unsupported sensor id %x\n", id);
>>>> +               return -ENODEV;
>>>> +       }
>>>> +       dev_dbg(&client->dev, "detected sensor id = 0x%04x\n", id);
>>>> +
>>>> +       if (id == VG5761_MODEL_ID) {
>>>> +               sensor->sensor_width = VGX761_WIDTH;
>>>> +               sensor->sensor_height = VGX761_HEIGHT;
>>>> +               sensor->sensor_modes = vgx761_mode_data;
>>>> +               sensor->sensor_modes_nb = ARRAY_SIZE(vgx761_mode_data);
>>>> +               sensor->current_mode = &vgx761_mode_data[VGX761_DEFAULT_MODE];
>>>> +               sensor->rot_term = VGX761_SHORT_ROT_TERM;
>>>> +               sensor->sensor_rates = vgx761_sensor_frame_rates;
>>>> +               sensor->sensor_rates_nb = ARRAY_SIZE(vgx761_sensor_frame_rates);
>>>> +       } else if (id == VG5661_MODEL_ID) {
>>>> +               sensor->sensor_width = VGX661_WIDTH;
>>>> +               sensor->sensor_height = VGX661_HEIGHT;
>>>> +               sensor->sensor_modes = vgx661_mode_data;
>>>> +               sensor->sensor_modes_nb = ARRAY_SIZE(vgx661_mode_data);
>>>> +               sensor->current_mode = &vgx661_mode_data[VGX661_DEFAULT_MODE];
>>>> +               sensor->rot_term = VGX661_SHORT_ROT_TERM;
>>>> +               sensor->sensor_rates = vgx661_sensor_frame_rates;
>>>> +               sensor->sensor_rates_nb = ARRAY_SIZE(vgx661_sensor_frame_rates);
>>>> +       }
>>>> +
>>>> +       ret = vgxy61_wait_state(sensor, SW_STBY);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +
>>>> +       ret = vgxy61_read_reg(sensor, DEVICE_NVM, &st);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +       if (st != NVM_OK)
>>>> +               dev_warn(&client->dev, "Bad nvm state got %d\n", st);
>>>> +
>>>> +       /* Detect cut version */
>>>> +       ret = vgxy61_detect_cut_version(sensor);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +
>>>> +       return 0;
>>>> +}
>>>> +
>>>> +static int vgxy61_probe(struct i2c_client *client)
>>>> +{
>>>> +       struct device *dev = &client->dev;
>>>> +       struct fwnode_handle *endpoint;
>>>> +       struct vgxy61_dev *sensor;
>>>> +       int ret;
>>>> +
>>>> +       sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
>>>> +       if (!sensor)
>>>> +               return -ENOMEM;
>>>> +
>>>> +       sensor->i2c_client = client;
>>>> +       sensor->streaming = false;
>>>> +       sensor->fmt.code = MEDIA_BUS_FMT_SGBRG8_1X8;
>>>> +       sensor->fmt.field = V4L2_FIELD_NONE;
>>>> +       sensor->fmt.colorspace = V4L2_COLORSPACE_SRGB;
>>>> +       sensor->frame_interval.numerator = 1;
>>>> +       sensor->frame_interval.denominator = 60;
>>>> +       sensor->hdr = NO_HDR;
>>>> +       sensor->expo_long = 200;
>>>> +       sensor->expo_short = 0;
>>>> +       sensor->hflip = false;
>>>> +       sensor->vflip = false;
>>>> +
>>>> +       endpoint = fwnode_graph_get_next_endpoint(of_fwnode_handle(dev->of_node), NULL);
>>>> +       if (!endpoint) {
>>>> +               dev_err(dev, "endpoint node not found\n");
>>>> +               return -EINVAL;
>>>> +       }
>>>> +
>>>> +       ret = vgxy61_tx_from_ep(sensor, endpoint);
>>>> +       fwnode_handle_put(endpoint);
>>>> +       if (ret) {
>>>> +               dev_err(dev, "Failed to parse endpoint %d\n", ret);
>>>> +               return ret;
>>>> +       }
>>>> +
>>>> +       sensor->xclk = devm_clk_get(dev, "xclk");
>>>> +       if (IS_ERR(sensor->xclk)) {
>>>> +               dev_err(dev, "failed to get xclk\n");
>>>> +               return PTR_ERR(sensor->xclk);
>>>> +       }
>>>> +       sensor->clk_freq = clk_get_rate(sensor->xclk);
>>>> +       if (sensor->clk_freq < 6000000 || sensor->clk_freq > 27000000) {
>>>> +               dev_err(dev, "Only 6Mhz-27Mhz clock range supported. provide %d Hz\n",
>>>> +                       sensor->clk_freq);
>>>> +               return -EINVAL;
>>>> +       }
>>>> +       sensor->gpios_polarity = of_property_read_bool(dev->of_node, "invert-gpios-polarity");
>>>> +       sensor->slave_mode = of_property_read_bool(dev->of_node, "slave-mode");
>>>> +
>>>> +       v4l2_i2c_subdev_init(&sensor->sd, client, &vgxy61_subdev_ops);
>>>> +       sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>>>> +       sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
>>>> +       sensor->sd.entity.ops = &vgxy61_subdev_entity_ops;
>>>> +       sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
>>>> +
>>>> +       ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
>>>> +       if (ret) {
>>>> +               dev_err(&client->dev, "pads init failed %d\n", ret);
>>>> +               return ret;
>>>> +       }
>>>> +
>>>> +       /* Request optional reset pin */
>>>> +       sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
>>>> +
>>>> +       ret = vgxy61_get_regulators(sensor);
>>>> +       if (ret) {
>>>> +               dev_err(&client->dev, "failed to get regulators %d\n", ret);
>>>> +               goto entity_cleanup;
>>>> +       }
>>>> +
>>>> +       ret = regulator_bulk_enable(VGXY61_NUM_SUPPLIES, sensor->supplies);
>>>> +       if (ret) {
>>>> +               dev_err(&client->dev, "failed to enable regulators %d\n", ret);
>>>> +               goto entity_cleanup;
>>>> +       }
>>>> +
>>>> +       ret = clk_prepare_enable(sensor->xclk);
>>>> +       if (ret) {
>>>> +               dev_err(&client->dev, "failed to enable clock %d\n", ret);
>>>> +               goto disable_bulk;
>>>> +       }
>>>> +
>>>> +       mutex_init(&sensor->lock);
>>>> +
>>>> +       /* Apply reset sequence */
>>>> +       if (sensor->reset_gpio) {
>>>> +               ret = vgxy61_apply_reset(sensor);
>>>> +               if (ret) {
>>>> +                       dev_err(&client->dev, "sensor reset failed %d\n", ret);
>>>> +                       goto disable_clock;
>>>> +               }
>>>> +       }
>>>> +
>>>> +       ret = vgxy61_detect(sensor);
>>>> +       if (ret) {
>>>> +               dev_err(&client->dev, "sensor detect failed %d\n", ret);
>>>> +               goto disable_clock;
>>>> +       }
>>>> +       sensor->fmt.width = sensor->current_mode->width;
>>>> +       sensor->fmt.height = sensor->current_mode->height;
>>>> +
>>>> +       ret = vgxy61_patch(sensor);
>>>> +       if (ret) {
>>>> +               dev_err(&client->dev, "sensor patch failed %d\n", ret);
>>>> +               goto disable_clock;
>>>> +       }
>>>> +
>>>> +       ret = vgxy61_configure(sensor);
>>>> +       if (ret) {
>>>> +               dev_err(&client->dev, "sensor configuration failed %d\n", ret);
>>>> +               goto disable_clock;
>>>> +       }
>>>> +
>>>> +       ret = vgxy61_init_controls(sensor);
>>>> +       if (ret) {
>>>> +               dev_err(&client->dev, "controls initialization failed %d\n", ret);
>>>> +               goto disable_clock;
>>>> +       }
>>>> +
>>>> +       ret = v4l2_async_register_subdev(&sensor->sd);
>>>> +       if (ret) {
>>>> +               dev_err(&client->dev, "async subdev register failed %d\n", ret);
>>>> +               goto disable_clock;
>>>> +       }
>>>> +
>>>> +       dev_info(&client->dev, "vgxy61 probe successfully\n");
>>>> +
>>>> +       return 0;
>>>> +
>>>> +disable_clock:
>>>> +       clk_disable_unprepare(sensor->xclk);
>>>> +disable_bulk:
>>>> +       regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
>>>> +entity_cleanup:
>>>> +       mutex_destroy(&sensor->lock);
>>>> +       media_entity_cleanup(&sensor->sd.entity);
>>>> +       return ret;
>>>> +}
>>>> +
>>>> +static int vgxy61_remove(struct i2c_client *client)
>>>> +{
>>>> +       struct v4l2_subdev *sd = i2c_get_clientdata(client);
>>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>> +
>>>> +       v4l2_async_unregister_subdev(&sensor->sd);
>>>> +       clk_disable_unprepare(sensor->xclk);
>>>> +       mutex_destroy(&sensor->lock);
>>>> +       media_entity_cleanup(&sensor->sd.entity);
>>>> +       regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
>>>> +
>>>> +       return 0;
>>>> +}
>>>> +
>>>> +static const struct of_device_id vgxy61_dt_ids[] = {
>>>> +       { .compatible = "st,st-vgxy61" },
>>>> +       { /* sentinel */ }
>>>> +};
>>>> +MODULE_DEVICE_TABLE(of, vgxy61_dt_ids);
>>>> +
>>>> +static struct i2c_driver vgxy61_i2c_driver = {
>>>> +       .driver = {
>>>> +               .name  = "st-vgxy61",
>>>> +               .of_match_table = vgxy61_dt_ids,
>>>> +       },
>>>> +       .probe_new = vgxy61_probe,
>>>> +       .remove = vgxy61_remove,
>>>> +};
>>>> +
>>>> +module_i2c_driver(vgxy61_i2c_driver);
>>>> +
>>>> +MODULE_AUTHOR("Benjamin Mugnier <benjamin.mugnier@foss.st.com>");
>>>> +MODULE_AUTHOR("Mickael Guene <mickael.guene@st.com>");
>>>> +MODULE_AUTHOR("Sylvain Petinot <sylvain.petinot@foss.st.com>");
>>>> +MODULE_DESCRIPTION("VGXY61 camera subdev driver");
>>>> +MODULE_LICENSE("GPL v2");
>>>> --
>>>> 2.25.1
>>>>

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

* Re: [PATCH 2/2] media: i2c: Add driver for ST VGXY61 camera sensor
  2022-03-10 15:21     ` Dave Stevenson
  2022-03-10 15:54       ` Kieran Bingham
@ 2022-03-11 10:56       ` Benjamin Mugnier
  2022-03-11 11:57         ` Dave Stevenson
  1 sibling, 1 reply; 17+ messages in thread
From: Benjamin Mugnier @ 2022-03-11 10:56 UTC (permalink / raw)
  To: Dave Stevenson, Kieran Bingham
  Cc: Linux Media Mailing List, alain.volmat, hugues.fruchet, sylvain.petinot

Hi Dave,

Thanks for your review. Here are my comments and some questions.

On 10/03/2022 16:21, Dave Stevenson wrote:
> Hi Benjamin and Kieran
> 
> On Thu, 10 Mar 2022 at 13:52, Kieran Bingham
> <kieran.bingham@ideasonboard.com> wrote:
>>
>> Hi Benjamin,
>>
>> Thank you for the patch -
>>
>> Quoting Benjamin Mugnier (2022-03-10 13:32:55)
>>> The VGXY61 has a quad lanes CSI-2 output port running at 800mbps per
>>> lane, and supports RAW8, RAW10, RAW12, RAW14 and RAW16 formats.
>>> The driver handles both sensor types:
>>> - VG5661 and VG6661: 1.6 Mpx (1464 x 1104) 75fps.
>>> - VG5761 and VG6761: 2.3 Mpx (1944 x 1204) 60 fps.
>>> The driver supports:
>>> - HDR linearize mode, HDR substraction mode, and no HDR
>>> - GPIOs LEDs strobing
>>> - Digital binning and analog subsampling
>>> - Horizontal and vertical flip
>>> - Manual exposure
>>> - Analog and digital gains
>>> - Test patterns
>>
>> I haven't reviewed the driver directly yet, but I have a script which
>> looks for key requirements for libcamera.
>> (https://git.linuxtv.org/libcamera.git/tree/Documentation/sensor_driver_requirements.rst)
>>
>>
>> Media Controller Support:
>>  - V4L2_SUBDEV_FL_HAS_DEVNODE      : found
>>  - MEDIA_ENT_F_CAM_SENSOR          : found
>>
>> Mandatory Controls:
>>  - V4L2_CID_EXPOSURE               : found
>>  - V4L2_CID_HBLANK                 : --------
>>  - V4L2_CID_VBLANK                 : --------
>>  - V4L2_CID_PIXEL_RATE             : found
>>
>> Selection Controls (will become mandatory):
>>  - V4L2_SEL_TGT_CROP_DEFAULT       : --------
>>  - V4L2_SEL_TGT_CROP               : --------
>>  - V4L2_SEL_TGT_CROP_BOUNDS        : --------
>>  - .get_selection                  : --------
>>
>> Optional Controls:
>>  - V4L2_CID_TEST_PATTERN           : found
>>  - V4L2_CID_GAIN                   : --------
>>  - V4L2_CID_ANALOGUE_GAIN          : found
>>  - V4L2_CID_CAMERA_SENSOR_ROTATION : --------
>>  - V4L2_CID_CAMERA_ORIENTATION     : --------
>>
>>
>> The key missing pieces are HBLANK/VBLANK and the .get_selection API. Is
>> it easy/feasible to add these?
> 
> It's documented that HBLANK/VBLANK are the correct parameters to use
> for raw image sensors [1].
> 
> It looks like register DEVICE_FRAME_LENGTH is mode->height +
> V4L2_CID_VBLANK, and DEVICE_LINE_LENGTH is mode->width +
> V4L2_CID_HBLANK (appears to be treated as read only)
> Most other sensors will adjust the max value of V4L2_CID_EXPOSURE
> based on the frame length.
> 
> [1] https://www.kernel.org/doc/html/latest/driver-api/media/camera-sensor.html#frame-interval-configuration
> 
> I've only done a quick review on the rest.
> 
>> --
>> Regards
>>
>> Kieran
>>
>>
>>>
>>> Signed-off-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com>
>>> ---
>>>  drivers/media/i2c/Kconfig     |   11 +
>>>  drivers/media/i2c/Makefile    |    1 +
>>>  drivers/media/i2c/st-vgxy61.c | 1919 +++++++++++++++++++++++++++++++++
>>>  3 files changed, 1931 insertions(+)
>>>  create mode 100644 drivers/media/i2c/st-vgxy61.c
>>>
>>> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
>>> index fe66093b8849..e6e9c1f19c09 100644
>>> --- a/drivers/media/i2c/Kconfig
>>> +++ b/drivers/media/i2c/Kconfig
>>> @@ -1454,6 +1454,17 @@ config VIDEO_S5C73M3
>>>           This is a V4L2 sensor driver for Samsung S5C73M3
>>>           8 Mpixel camera.
>>>
>>> +config VIDEO_ST_VGXY61
>>> +       tristate "ST VGXY61 sensor support"
>>> +       depends on OF
>>> +       depends on GPIOLIB && VIDEO_V4L2 && I2C
>>> +       select MEDIA_CONTROLLER
>>> +       select VIDEO_V4L2_SUBDEV_API
>>> +       select V4L2_FWNODE
>>> +       help
>>> +         This is a Video4Linux2 sensor driver for the ST VGXY61
>>> +         camera sensor.
>>> +
>>>  endmenu
>>>
>>>  menu "Lens drivers"
>>> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
>>> index f6b80ef6f41d..0190b5edfa24 100644
>>> --- a/drivers/media/i2c/Makefile
>>> +++ b/drivers/media/i2c/Makefile
>>> @@ -138,4 +138,5 @@ obj-$(CONFIG_VIDEO_MAX9271_LIB)     += max9271.o
>>>  obj-$(CONFIG_VIDEO_RDACM20)    += rdacm20.o
>>>  obj-$(CONFIG_VIDEO_RDACM21)    += rdacm21.o
>>>  obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o
>>> +obj-$(CONFIG_VIDEO_ST_VGXY61)  += st-vgxy61.o
>>>  obj-$(CONFIG_SDR_MAX2175) += max2175.o
>>> diff --git a/drivers/media/i2c/st-vgxy61.c b/drivers/media/i2c/st-vgxy61.c
>>> new file mode 100644
>>> index 000000000000..d3d816189f86
>>> --- /dev/null
>>> +++ b/drivers/media/i2c/st-vgxy61.c
>>> @@ -0,0 +1,1919 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +/*
>>> + * Driver for VGXY61 global shutter sensor family driver
>>> + *
>>> + * Copyright (C) 2022 STMicroelectronics SA
>>> + */
>>> +
>>> +#include <linux/clk.h>
>>> +#include <linux/delay.h>
>>> +#include <linux/gpio/consumer.h>
>>> +#include <linux/i2c.h>
>>> +#include <linux/module.h>
>>> +#include <linux/regulator/consumer.h>
>>> +#include <linux/units.h>
>>> +#include <linux/iopoll.h>
>>> +#include <media/mipi-csi2.h>
>>> +#include <media/v4l2-async.h>
>>> +#include <media/v4l2-ctrls.h>
>>> +#include <media/v4l2-device.h>
>>> +#include <media/v4l2-fwnode.h>
>>> +#include <media/v4l2-subdev.h>
>>> +
>>> +#define US_PER_MS                                      1000
>>> +
>>> +#define DEVICE_MODEL_ID_REG                            0x0000
>>> +#define VG5661_MODEL_ID                                        0x5661
>>> +#define VG5761_MODEL_ID                                        0x5761
>>> +#define VGX661_WIDTH                                   1464
>>> +#define VGX661_HEIGHT                                  1104
>>> +#define VGX761_WIDTH                                   1944
>>> +#define VGX761_HEIGHT                                  1204
>>> +#define VGX661_DEFAULT_MODE                            1
>>> +#define VGX761_DEFAULT_MODE                            1
>>> +#define VGX661_SHORT_ROT_TERM                          93
>>> +#define VGX761_SHORT_ROT_TERM                          90
>>> +#define VGXY61_EXPOS_ROT_TERM                          66
>>> +#define DEVICE_REVISION                                        0x0002
>>> +#define DEVICE_FWPATCH_REVISION                                0x0014
>>> +#define DEVICE_FWPATCH_START_ADDR                      0x2000
>>> +#define DEVICE_SYSTEM_FSM                              0x0020
>>> +#define SW_STBY                                                0x03
>>> +#define STREAMING                                      0x04
>>> +#define DEVICE_NVM                                     0x0023
>>> +#define NVM_OK                                         0x04
>>> +#define DEVICE_THSENS1_TEMPERATURE                     0x0044
>>> +#define DEVICE_STBY                                    0x0201
>>> +#define STBY_NO_REQ                                    0
>>> +#define STBY_REQ_TMP_READ                              BIT(2)
>>> +#define DEVICE_STREAMING                               0x0202
>>> +#define REQ_NO_REQUEST                                 0
>>> +#define REQ_STOP_STREAMING                             BIT(0)
>>> +#define REQ_START_STREAMING                            BIT(1)
>>> +#define DEVICE_EXT_CLOCK                               0x0220
>>> +#define DEVICE_CLK_PLL_PREDIV                          0x0224
>>> +#define DEVICE_CLK_SYS_PLL_MULT                                0x0225
>>> +#define DEVICE_GPIO_0_CTRL                             0x0236
>>> +#define DEVICE_GPIO_1_CTRL                             0x0237
>>> +#define DEVICE_GPIO_2_CTRL                             0x0238
>>> +#define DEVICE_GPIO_3_CTRL                             0x0239
>>> +#define DEVICE_SIGNALS_POLARITY_CTRL                   0x023b
>>> +#define DEVICE_LINE_LENGTH                             0x0300
>>> +#define DEVICE_ORIENTATION                             0x0302
>>> +#define DEVICE_VT_CTRL                                 0x0304
>>> +#define DEVICE_FORMAT_CTRL                             0x0305
>>> +#define DEVICE_OIF_CTRL                                        0x0306
>>> +#define DEVICE_OIF_ROI0_CTRL                           0x030a
>>> +#define DEVICE_ROI0_START_H                            0x0400
>>> +#define DEVICE_ROI0_START_V                            0x0402
>>> +#define DEVICE_ROI0_END_H                              0x0404
>>> +#define DEVICE_ROI0_END_V                              0x0406
>>> +#define DEVICE_PATGEN_CTRL                             0x0440
>>> +#define DEVICE_FRAME_CONTENT_CTRL                      0x0478
>>> +#define DEVICE_COARSE_EXPOSURE_LONG                    0x0500
>>> +#define DEVICE_COARSE_EXPOSURE_SHORT                   0x0504
>>> +#define DEVICE_ANALOG_GAIN                             0x0508
>>> +#define DEVICE_DIGITAL_GAIN_LONG                       0x050a
>>> +#define DEVICE_DIGITAL_GAIN_SHORT                      0x0512
>>> +#define DEVICE_FRAME_LENGTH                            0x051a
>>> +#define DEVICE_SIGNALS_CTRL                            0x0522
>>> +#define DEVICE_STROBE_LONG_START_DELAY                 0x0528
>>> +#define DEVICE_STROBE_LONG_END_DELAY                   0x052a
>>> +#define DEVICE_STROBE_SHORT_START_DELAY                        0x052c
>>> +#define DEVICE_STROBE_SHORT_END_DELAY                  0x052e
>>> +#define DEVICE_READOUT_CTRL                            0x0530
>>> +#define DEVICE_HDR_CTRL                                        0x0532
>>> +#define DEVICE_PATGEN_LONG_DATA_GR                     0x092c
>>> +#define DEVICE_PATGEN_LONG_DATA_R                      0x092e
>>> +#define DEVICE_PATGEN_LONG_DATA_B                      0x0930
>>> +#define DEVICE_PATGEN_LONG_DATA_GB                     0x0932
>>> +#define DEVICE_PATGEN_SHORT_DATA_GR                    0x0950
>>> +#define DEVICE_PATGEN_SHORT_DATA_R                     0x0952
>>> +#define DEVICE_PATGEN_SHORT_DATA_B                     0x0954
>>> +#define DEVICE_PATGEN_SHORT_DATA_GB                    0x0956
>>> +#define DEVICE_BYPASS_CTRL                             0x0a60
>>> +
>>> +#define V4L2_CID_HDR                           (V4L2_CID_USER_BASE | 0x1004)
>>> +#define V4L2_CID_GPIOS_STROBE_LONG_START_DELAY (V4L2_CID_USER_BASE | 0x1019)
>>> +#define V4L2_CID_GPIOS_STROBE_LONG_END_DELAY   (V4L2_CID_USER_BASE | 0x101a)
>>> +#define V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY        (V4L2_CID_USER_BASE | 0x101b)
>>> +#define V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY  (V4L2_CID_USER_BASE | 0x101c)
>>> +#define V4L2_CID_TEMPERATURE                   (V4L2_CID_USER_BASE | 0x1020)
> 
> The strobe and temperature controls are equally valid for other
> sensors, so IMHO ought to be added as standard controls.
> 

Agreed for the temperature, I will add it as a standard control in v2.
As for the strobing I am not sure, these are controls to set the strobing delay for long exposure and short exposure (if in HDR mode), not to enable or disable the strobbing. I could not find anything like this in the codebase so I guess this is pretty unique to this sensor?

>>> +
>>> +#define DEVICE_FWPATCH_REVISION_MAJOR                  2
>>> +#define DEVICE_FWPATCH_REVISION_MINOR                  0
>>> +#define DEVICE_FWPATCH_REVISION_MICRO                  5
>>> +
>>> +#define WRITE_MULTIPLE_CHUNK_MAX                       16
>>> +#define NB_GPIOS                                       4
>>> +#define NB_POLARITIES                                  5
>>> +
>>> +static const u8 patch_array[] = {
>>> +       0xbf, 0x00, 0x05, 0x20, 0x06, 0x01, 0xe0, 0xe0, 0x04, 0x80, 0xe6, 0x45,
>>> +       0xed, 0x6f, 0xfe, 0xff, 0x14, 0x80, 0x1f, 0x84, 0x10, 0x42, 0x05, 0x7c,
>>> +       0x01, 0xc4, 0x1e, 0x80, 0xb6, 0x42, 0x00, 0xe0, 0x1e, 0x82, 0x1e, 0xc0,
>>> +       0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa, 0x86, 0x0d, 0x70, 0xe1,
>>> +       0x04, 0x98, 0x15, 0x00, 0x28, 0xe0, 0x14, 0x02, 0x08, 0xfc, 0x15, 0x40,
>>> +       0x28, 0xe0, 0x98, 0x58, 0xe0, 0xef, 0x04, 0x98, 0x0e, 0x04, 0x00, 0xf0,
>>> +       0x15, 0x00, 0x28, 0xe0, 0x19, 0xc8, 0x15, 0x40, 0x28, 0xe0, 0xc6, 0x41,
>>> +       0xfc, 0xe0, 0x14, 0x80, 0x1f, 0x84, 0x14, 0x02, 0xa0, 0xfc, 0x1e, 0x80,
>>> +       0x14, 0x80, 0x14, 0x02, 0x80, 0xfb, 0x14, 0x02, 0xe0, 0xfc, 0x1e, 0x80,
>>> +       0x14, 0xc0, 0x1f, 0x84, 0x14, 0x02, 0xa4, 0xfc, 0x1e, 0xc0, 0x14, 0xc0,
>>> +       0x14, 0x02, 0x80, 0xfb, 0x14, 0x02, 0xe4, 0xfc, 0x1e, 0xc0, 0x0c, 0x0c,
>>> +       0x00, 0xf2, 0x93, 0xdd, 0x86, 0x00, 0xf8, 0xe0, 0x04, 0x80, 0xc6, 0x03,
>>> +       0x70, 0xe1, 0x0e, 0x84, 0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa,
>>> +       0x6b, 0x80, 0x06, 0x40, 0x6c, 0xe1, 0x04, 0x80, 0x09, 0x00, 0xe0, 0xe0,
>>> +       0x0b, 0xa1, 0x95, 0x84, 0x05, 0x0c, 0x1c, 0xe0, 0x86, 0x02, 0xf9, 0x60,
>>> +       0xe0, 0xcf, 0x78, 0x6e, 0x80, 0xef, 0x25, 0x0c, 0x18, 0xe0, 0x05, 0x4c,
>>> +       0x1c, 0xe0, 0x86, 0x02, 0xf9, 0x60, 0xe0, 0xcf, 0x0b, 0x84, 0xd8, 0x6d,
>>> +       0x80, 0xef, 0x05, 0x4c, 0x18, 0xe0, 0x04, 0xd8, 0x0b, 0xa5, 0x95, 0x84,
>>> +       0x05, 0x0c, 0x2c, 0xe0, 0x06, 0x02, 0x01, 0x60, 0xe0, 0xce, 0x18, 0x6d,
>>> +       0x80, 0xef, 0x25, 0x0c, 0x30, 0xe0, 0x05, 0x4c, 0x2c, 0xe0, 0x06, 0x02,
>>> +       0x01, 0x60, 0xe0, 0xce, 0x0b, 0x84, 0x78, 0x6c, 0x80, 0xef, 0x05, 0x4c,
>>> +       0x30, 0xe0, 0x0c, 0x0c, 0x00, 0xf2, 0x93, 0xdd, 0x46, 0x01, 0x70, 0xe1,
>>> +       0x08, 0x80, 0x0b, 0xa1, 0x08, 0x5c, 0x00, 0xda, 0x06, 0x01, 0x68, 0xe1,
>>> +       0x04, 0x80, 0x4a, 0x40, 0x84, 0xe0, 0x08, 0x5c, 0x00, 0x9a, 0x06, 0x01,
>>> +       0xe0, 0xe0, 0x04, 0x80, 0x15, 0x00, 0x60, 0xe0, 0x19, 0xc4, 0x15, 0x40,
>>> +       0x60, 0xe0, 0x15, 0x00, 0x78, 0xe0, 0x19, 0xc4, 0x15, 0x40, 0x78, 0xe0,
>>> +       0x93, 0xdd, 0xc3, 0xc1, 0x46, 0x01, 0x70, 0xe1, 0x08, 0x80, 0x0b, 0xa1,
>>> +       0x08, 0x5c, 0x00, 0xda, 0x06, 0x01, 0x68, 0xe1, 0x04, 0x80, 0x4a, 0x40,
>>> +       0x84, 0xe0, 0x08, 0x5c, 0x00, 0x9a, 0x06, 0x01, 0xe0, 0xe0, 0x14, 0x80,
>>> +       0x25, 0x02, 0x54, 0xe0, 0x29, 0xc4, 0x25, 0x42, 0x54, 0xe0, 0x24, 0x80,
>>> +       0x35, 0x04, 0x6c, 0xe0, 0x39, 0xc4, 0x35, 0x44, 0x6c, 0xe0, 0x25, 0x02,
>>> +       0x64, 0xe0, 0x29, 0xc4, 0x25, 0x42, 0x64, 0xe0, 0x04, 0x80, 0x15, 0x00,
>>> +       0x7c, 0xe0, 0x19, 0xc4, 0x15, 0x40, 0x7c, 0xe0, 0x93, 0xdd, 0xc3, 0xc1,
>>> +       0x4c, 0x04, 0x7c, 0xfa, 0x86, 0x40, 0x98, 0xe0, 0x14, 0x80, 0x1b, 0xa1,
>>> +       0x06, 0x00, 0x00, 0xc0, 0x08, 0x42, 0x38, 0xdc, 0x08, 0x64, 0xa0, 0xef,
>>> +       0x86, 0x42, 0x3c, 0xe0, 0x68, 0x49, 0x80, 0xef, 0x6b, 0x80, 0x78, 0x53,
>>> +       0xc8, 0xef, 0xc6, 0x54, 0x6c, 0xe1, 0x7b, 0x80, 0xb5, 0x14, 0x0c, 0xf8,
>>> +       0x05, 0x14, 0x14, 0xf8, 0x1a, 0xac, 0x8a, 0x80, 0x0b, 0x90, 0x38, 0x55,
>>> +       0x80, 0xef, 0x1a, 0xae, 0x17, 0xc2, 0x03, 0x82, 0x88, 0x65, 0x80, 0xef,
>>> +       0x1b, 0x80, 0x0b, 0x8e, 0x68, 0x65, 0x80, 0xef, 0x9b, 0x80, 0x0b, 0x8c,
>>> +       0x08, 0x65, 0x80, 0xef, 0x6b, 0x80, 0x0b, 0x92, 0x1b, 0x8c, 0x98, 0x64,
>>> +       0x80, 0xef, 0x1a, 0xec, 0x9b, 0x80, 0x0b, 0x90, 0x95, 0x54, 0x10, 0xe0,
>>> +       0xa8, 0x53, 0x80, 0xef, 0x1a, 0xee, 0x17, 0xc2, 0x03, 0x82, 0xf8, 0x63,
>>> +       0x80, 0xef, 0x1b, 0x80, 0x0b, 0x8e, 0xd8, 0x63, 0x80, 0xef, 0x1b, 0x8c,
>>> +       0x68, 0x63, 0x80, 0xef, 0x6b, 0x80, 0x0b, 0x92, 0x65, 0x54, 0x14, 0xe0,
>>> +       0x08, 0x65, 0x84, 0xef, 0x68, 0x63, 0x80, 0xef, 0x7b, 0x80, 0x0b, 0x8c,
>>> +       0xa8, 0x64, 0x84, 0xef, 0x08, 0x63, 0x80, 0xef, 0x14, 0xe8, 0x46, 0x44,
>>> +       0x94, 0xe1, 0x24, 0x88, 0x4a, 0x4e, 0x04, 0xe0, 0x14, 0xea, 0x1a, 0x04,
>>> +       0x08, 0xe0, 0x0a, 0x40, 0x84, 0xed, 0x0c, 0x04, 0x00, 0xe2, 0x4a, 0x40,
>>> +       0x04, 0xe0, 0x19, 0x16, 0xc0, 0xe0, 0x0a, 0x40, 0x84, 0xed, 0x21, 0x54,
>>> +       0x60, 0xe0, 0x0c, 0x04, 0x00, 0xe2, 0x1b, 0xa5, 0x0e, 0xea, 0x01, 0x89,
>>> +       0x21, 0x54, 0x64, 0xe0, 0x7e, 0xe8, 0x65, 0x82, 0x1b, 0xa7, 0x26, 0x00,
>>> +       0x00, 0x80, 0xa5, 0x82, 0x1b, 0xa9, 0x65, 0x82, 0x1b, 0xa3, 0x01, 0x85,
>>> +       0x16, 0x00, 0x00, 0xc0, 0x01, 0x54, 0x04, 0xf8, 0x06, 0xaa, 0x01, 0x83,
>>> +       0x06, 0xa8, 0x65, 0x81, 0x06, 0xa8, 0x01, 0x54, 0x04, 0xf8, 0x01, 0x83,
>>> +       0x06, 0xaa, 0x09, 0x14, 0x18, 0xf8, 0x0b, 0xa1, 0x05, 0x84, 0xc6, 0x42,
>>> +       0xd4, 0xe0, 0x14, 0x84, 0x01, 0x83, 0x01, 0x54, 0x60, 0xe0, 0x01, 0x54,
>>> +       0x64, 0xe0, 0x0b, 0x02, 0x90, 0xe0, 0x10, 0x02, 0x90, 0xe5, 0x01, 0x54,
>>> +       0x88, 0xe0, 0xb5, 0x81, 0xc6, 0x40, 0xd4, 0xe0, 0x14, 0x80, 0x0b, 0x02,
>>> +       0xe0, 0xe4, 0x10, 0x02, 0x31, 0x66, 0x02, 0xc0, 0x01, 0x54, 0x88, 0xe0,
>>> +       0x1a, 0x84, 0x29, 0x14, 0x10, 0xe0, 0x1c, 0xaa, 0x2b, 0xa1, 0xf5, 0x82,
>>> +       0x25, 0x14, 0x10, 0xf8, 0x2b, 0x04, 0xa8, 0xe0, 0x20, 0x44, 0x0d, 0x70,
>>> +       0x03, 0xc0, 0x2b, 0xa1, 0x04, 0x00, 0x80, 0x9a, 0x02, 0x40, 0x84, 0x90,
>>> +       0x03, 0x54, 0x04, 0x80, 0x4c, 0x0c, 0x7c, 0xf2, 0x93, 0xdd, 0x00, 0x00,
>>> +       0x02, 0xa9, 0x00, 0x00, 0x64, 0x4a, 0x40, 0x00, 0x08, 0x2d, 0x58, 0xe0,
>>> +       0xa8, 0x98, 0x40, 0x00, 0x28, 0x07, 0x34, 0xe0, 0x05, 0xb9, 0x00, 0x00,
>>> +       0x28, 0x00, 0x41, 0x05, 0x88, 0x00, 0x41, 0x3c, 0x98, 0x00, 0x41, 0x52,
>>> +       0x04, 0x01, 0x41, 0x79, 0x3c, 0x01, 0x41, 0x6a, 0x3d, 0xfe, 0x00, 0x00,
>>> +};
>>> +
>>> +static const char * const vgxy61_test_pattern_menu[] = {
>>> +       "Disabled",
>>> +       "Solid",
>>> +       "Colorbar",
>>> +       "Gradbar",
>>> +       "Hgrey",
>>> +       "Vgrey",
>>> +       "Dgrey",
>>> +       "PN28",
>>> +};
>>> +
>>> +static const char * const vgxy61_hdr_modes[] = {
>>> +       "HDR linearize",
>>> +       "HDR substraction",
>>> +       "no HDR",
>>> +};
>>> +
>>> +/* Regulator supplies */
>>> +static const char * const vgxy61_supply_name[] = {
>>> +       "VCORE",
>>> +       "VDDIO",
>>> +       "VANA",
>>> +};
>>> +
>>> +static const s64 link_freq[] = {
>>> +       /*
>>> +        * MIPI output freq is 804Mhz / 2, as it uses both rising edge and falling edges to send
>>> +        * data
>>> +        */
>>> +       402000000ULL
>>> +};
>>> +
>>> +#define VGXY61_NUM_SUPPLIES            ARRAY_SIZE(vgxy61_supply_name)
>>> +
>>> +/* Macro to convert index to 8.8 fixed point gain */
>>> +#define I2FP(i)                                ((u32)(8192.0 / (32 - (i))))
>>> +/* Array of possibles analog gains in 8.8 fixed point */
>>> +static const u16 analog_gains[] = {
>>> +       I2FP(0), I2FP(1), I2FP(2), I2FP(3), I2FP(4), I2FP(5), I2FP(6), I2FP(7), I2FP(8), I2FP(9),
>>> +       I2FP(10), I2FP(11), I2FP(12), I2FP(13), I2FP(14), I2FP(15), I2FP(16),
>>> +};
>>> +
>>> +enum bin_mode {
>>> +       BIN_MODE_NORMAL,
>>> +       BIN_MODE_DIGITAL_X2,
>>> +       BIN_MODE_DIGITAL_X4,
>>> +       BIN_MODE_ANALOG_SUB_X2,
>>> +       BIN_MODE_ANALOG_SUB_X4,
>>> +};
>>> +
>>> +enum hdr {
>>> +       HDR_LINEAR,
>>> +       HDR_SUB,
>>> +       NO_HDR,
>>> +};
>>> +
>>> +enum strobe_modes {
>>> +       STROBE_DISABLED,
>>> +       STROBE_LONG,
>>> +       STROBE_ENABLED,
>>> +};
>>> +
>>> +struct vgxy61_mode_info {
>>> +       u32 width;
>>> +       u32 height;
>>> +       enum bin_mode bin_mode;
>>> +};
>>> +
>>> +static const u32 vgxy61_supported_codes[] = {
>>> +       MEDIA_BUS_FMT_SGBRG8_1X8,
>>> +       MEDIA_BUS_FMT_SGBRG10_1X10,
>>> +       MEDIA_BUS_FMT_SGBRG12_1X12,
>>> +       MEDIA_BUS_FMT_SGBRG14_1X14,
>>> +       MEDIA_BUS_FMT_SGBRG16_1X16
>>> +};
>>> +
>>> +const int vgx761_sensor_frame_rates[] = {75, 60, 30, 15, 10, 5, 2};
>>> +const int vgx661_sensor_frame_rates[] = {60, 30, 15, 10, 5, 2};
>>> +
>>> +static const struct vgxy61_mode_info vgx661_mode_data[] = {
>>> +       {1464, 1104, BIN_MODE_NORMAL},
>>> +       {1280,  720, BIN_MODE_NORMAL},
>>> +       { 640,  480, BIN_MODE_DIGITAL_X2},
>>> +       { 320,  240, BIN_MODE_DIGITAL_X4},
>>> +};
>>> +
>>> +static const struct vgxy61_mode_info vgx761_mode_data[] = {
>>> +       {1944, 1204, BIN_MODE_NORMAL},
>>> +       {1920, 1080, BIN_MODE_NORMAL},
>>> +       {1280,  720, BIN_MODE_NORMAL},
>>> +       { 640,  480, BIN_MODE_DIGITAL_X2},
>>> +       { 320,  240, BIN_MODE_DIGITAL_X4},
>>> +};
>>> +
>>> +struct gpios_ctrls {
>>> +       struct v4l2_ctrl *long_start;
>>> +       struct v4l2_ctrl *long_end;
>>> +       struct v4l2_ctrl *short_start;
>>> +       struct v4l2_ctrl *short_end;
>>> +};
>>> +
>>> +struct vgxy61_ctrls {
>>> +       struct v4l2_ctrl_handler handler;
>>> +       struct v4l2_ctrl *exposure;
>>> +       struct v4l2_ctrl *analog_gain;
>>> +       struct v4l2_ctrl *digital_gain;
>>> +       struct gpios_ctrls gpios;
>>> +       struct v4l2_ctrl *vflip;
>>> +       struct v4l2_ctrl *hflip;
>>> +       struct v4l2_ctrl *patgen;
>>> +       struct v4l2_ctrl *hdr;
>>> +       struct v4l2_ctrl *pixel_rate;
>>> +       struct v4l2_ctrl *link_freq;
> 
> No need to store analog_gain, digital_gain, vflip, hflip, patgen, hdr,
> or link_freq as they are never programmatically altered. The s_ctrl
> handler is given the struct v4l2_ctrl, and where you need it again
> you've copied the value into the main struct vgxy61_dev.
> 

Definetly. I just need to keep the pixel_rate ctrl to change its value in the set_fmt function.
I will remove all the others, and put the pixel_rate control directly in the vgxy61 structure.

>>> +       struct v4l2_ctrl *temp;
>>> +};
>>> +
>>> +struct vgxy61_dev {
>>> +       struct i2c_client *i2c_client;
>>> +       struct v4l2_subdev sd;
>>> +       struct media_pad pad;
>>> +       struct regulator_bulk_data supplies[VGXY61_NUM_SUPPLIES];
>>> +       struct gpio_desc *reset_gpio;
>>> +       struct clk *xclk;
>>> +       u32 clk_freq;
>>> +       int sensor_width;
>>> +       int sensor_height;
>>> +       u16 oif_ctrl;
>>> +       int nb_of_lane;
>>> +       int data_rate_in_mbps;
>>> +       int pclk;
>>> +       u16 line_length;
>>> +       int rot_term;
>>> +       bool gpios_polarity;
>>> +       bool slave_mode;
>>> +       /* Lock to protect all members below */
>>> +       struct mutex lock;
>>> +       struct vgxy61_ctrls ctrls;
>>> +       bool streaming;
>>> +       struct v4l2_mbus_framefmt fmt;
>>> +       const struct vgxy61_mode_info *sensor_modes;
>>> +       int sensor_modes_nb;
>>> +       const struct vgxy61_mode_info *current_mode;
>>> +       const int *sensor_rates;
>>> +       int sensor_rates_nb;
>>> +       struct v4l2_fract frame_interval;
>>> +       bool hflip;
>>> +       bool vflip;
>>> +       enum hdr hdr;
>>> +       int expo_long;
>>> +       int expo_short;
>>> +};
>>> +
>>> +static u8 get_bpp_by_code(__u32 code)
>>> +{
>>> +       switch (code) {
>>> +       case MEDIA_BUS_FMT_SGBRG8_1X8:
>>> +               return 8;
>>> +       case MEDIA_BUS_FMT_SGBRG10_1X10:
>>> +               return 10;
>>> +       case MEDIA_BUS_FMT_SGBRG12_1X12:
>>> +               return 12;
>>> +       case MEDIA_BUS_FMT_SGBRG14_1X14:
>>> +               return 14;
>>> +       case MEDIA_BUS_FMT_SGBRG16_1X16:
>>> +               return 16;
>>> +       default:
>>> +               /* Should never happen */
>>> +               WARN(1, "Unsupported code %d. default to 8 bpp", code);
>>> +               return 8;
>>> +       }
>>> +}
>>> +
>>> +static u8 get_data_type_by_code(__u32 code)
>>> +{
>>> +       switch (code) {
>>> +       case MEDIA_BUS_FMT_SGBRG8_1X8:
>>> +               return MIPI_CSI2_DT_RAW8;
>>> +       case MEDIA_BUS_FMT_SGBRG10_1X10:
>>> +               return MIPI_CSI2_DT_RAW10;
>>> +       case MEDIA_BUS_FMT_SGBRG12_1X12:
>>> +               return MIPI_CSI2_DT_RAW12;
>>> +       case MEDIA_BUS_FMT_SGBRG14_1X14:
>>> +               return MIPI_CSI2_DT_RAW14;
>>> +       case MEDIA_BUS_FMT_SGBRG16_1X16:
>>> +               return MIPI_CSI2_DT_RAW16;
>>> +       default:
>>> +               /* Should never happen */
>>> +               WARN(1, "Unsupported code %d. default to MIPI_CSI2_DT_RAW8 data type", code);
>>> +               return MIPI_CSI2_DT_RAW8;
>>> +       }
>>> +}
>>> +
>>> +static void compute_pll_parameters_by_freq(u32 freq, unsigned int *prediv, unsigned int *mult)
>>> +{
>>> +       const unsigned int predivs[] = {1, 2, 4};
>>> +       int i;
>>> +
>>> +       /*
>>> +        * Freq range is [6Mhz-27Mhz] already checked.
>>> +        * Output of divider should be in [6Mhz-12Mhz[.
>>> +        */
>>> +       for (i = 0; i < ARRAY_SIZE(predivs); i++) {
>>> +               *prediv = predivs[i];
>>> +               if (freq / *prediv < 12 * HZ_PER_MHZ)
>>> +                       break;
>>> +       }
>>> +       WARN_ON(i == ARRAY_SIZE(predivs));
>>> +
>>> +       /*
>>> +        * Target freq is 804Mhz. Don't change this as it will impact image quality.
>>> +        */
>>> +       *mult = ((804 * HZ_PER_MHZ) * (*prediv) + freq / 2) / freq;
>>> +}
>>> +
>>> +static s32 get_pixel_rate(struct vgxy61_dev *sensor)
>>> +{
>>> +       return div64_u64((u64)sensor->data_rate_in_mbps * sensor->nb_of_lane,
>>> +                        get_bpp_by_code(sensor->fmt.code));
>>> +}
>>> +
>>> +static inline struct vgxy61_dev *to_vgxy61_dev(struct v4l2_subdev *sd)
>>> +{
>>> +       return container_of(sd, struct vgxy61_dev, sd);
>>> +}
>>> +
>>> +static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl)
>>> +{
>>> +       return &container_of(ctrl->handler, struct vgxy61_dev, ctrls.handler)->sd;
>>> +}
>>> +
>>> +static int get_chunk_size(struct vgxy61_dev *sensor)
>>> +{
>>> +       struct i2c_adapter *adapter = sensor->i2c_client->adapter;
>>> +       int max_write_len = WRITE_MULTIPLE_CHUNK_MAX;
>>> +
>>> +       if (adapter->quirks && adapter->quirks->max_write_len)
>>> +               max_write_len = adapter->quirks->max_write_len - 2;
>>> +
>>> +       max_write_len = min(max_write_len, WRITE_MULTIPLE_CHUNK_MAX);
>>> +
>>> +       return max(max_write_len, 1);
>>> +}
>>> +
>>> +static int vgxy61_read_multiple(struct vgxy61_dev *sensor, u16 reg, u8 *val, int len)
>>> +{
>>> +       struct i2c_client *client = sensor->i2c_client;
>>> +       struct i2c_msg msg[2];
>>> +       u8 buf[2];
>>> +       int ret;
>>> +
>>> +       buf[0] = reg >> 8;
>>> +       buf[1] = reg & 0xff;
>>> +
>>> +       msg[0].addr = client->addr;
>>> +       msg[0].flags = client->flags;
>>> +       msg[0].buf = buf;
>>> +       msg[0].len = sizeof(buf);
>>> +
>>> +       msg[1].addr = client->addr;
>>> +       msg[1].flags = client->flags | I2C_M_RD;
>>> +       msg[1].buf = val;
>>> +       msg[1].len = len;
>>> +
>>> +       ret = i2c_transfer(client->adapter, msg, 2);
>>> +       if (ret < 0) {
>>> +               dev_dbg(&client->dev, "%s: %x i2c_transfer, reg: %x => %d\n", __func__,
>>> +                       client->addr, reg, ret);
>>> +               return ret;
>>> +       }
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static inline int vgxy61_read_reg(struct vgxy61_dev *sensor, u16 reg, u8 *val)
>>> +{
>>> +       return vgxy61_read_multiple(sensor, reg, val, sizeof(*val));
>>> +}
>>> +
>>> +static inline int vgxy61_read_reg16(struct vgxy61_dev *sensor, u16 reg, u16 *val)
>>> +{
>>> +       return vgxy61_read_multiple(sensor, reg, (u8 *)val, sizeof(*val));
>>> +}
>>> +
>>> +static int vgxy61_write_multiple(struct vgxy61_dev *sensor, u16 reg, const u8 *data, int len)
>>> +{
>>> +       struct i2c_client *client = sensor->i2c_client;
>>> +       struct i2c_msg msg;
>>> +       u8 buf[WRITE_MULTIPLE_CHUNK_MAX + 2];
>>> +       int i;
>>> +       int ret;
>>> +
>>> +       if (len > WRITE_MULTIPLE_CHUNK_MAX)
>>> +               return -EINVAL;
>>> +       buf[0] = reg >> 8;
>>> +       buf[1] = reg & 0xff;
>>> +       for (i = 0; i < len; i++)
>>> +               buf[i + 2] = data[i];
>>> +
>>> +       msg.addr = client->addr;
>>> +       msg.flags = client->flags;
>>> +       msg.buf = buf;
>>> +       msg.len = len + 2;
>>> +
>>> +       ret = i2c_transfer(client->adapter, &msg, 1);
>>> +       if (ret < 0) {
>>> +               dev_dbg(&client->dev, "%s: i2c_transfer, reg: %x => %d\n", __func__, reg, ret);
>>> +               return ret;
>>> +       }
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static int vgxy61_write_array(struct vgxy61_dev *sensor, u16 reg, int nb, const u8 *array)
>>> +{
>>> +       const int chunk_size = get_chunk_size(sensor);
>>> +       int ret;
>>> +       int sz;
>>> +
>>> +       while (nb) {
>>> +               sz = min(nb, chunk_size);
>>> +               ret = vgxy61_write_multiple(sensor, reg, array, sz);
>>> +               if (ret < 0)
>>> +                       return ret;
>>> +               nb -= sz;
>>> +               reg += sz;
>>> +               array += sz;
>>> +       }
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static inline int vgxy61_write_reg(struct vgxy61_dev *sensor, u16 reg, u8 val)
>>> +{
>>> +       return vgxy61_write_multiple(sensor, reg, &val, sizeof(val));
>>> +}
>>> +
>>> +static inline int vgxy61_write_reg16(struct vgxy61_dev *sensor, u16 reg, u16 val)
>>> +{
>>> +       return vgxy61_write_multiple(sensor, reg, (u8 *)&val, sizeof(val));
>>> +}
>>> +
>>> +static inline int vgxy61_write_reg32(struct vgxy61_dev *sensor, u16 reg, u32 val)
>>> +{
>>> +       return vgxy61_write_multiple(sensor, reg, (u8 *)&val, sizeof(val));
>>> +}
>>> +
>>> +static int vgxy61_poll_reg(struct vgxy61_dev *sensor, u16 reg, u8 poll_val)
>>> +{
>>> +       const int loop_delay_ms = 10;
>>> +       const int timeout_ms = 500;
>>> +       u8 val;
>>> +       int ret, timeout;
>>> +
>>> +       timeout = read_poll_timeout(vgxy61_read_reg, ret, ((ret != 0) || (val == poll_val)),
>>> +                                   loop_delay_ms * US_PER_MS, timeout_ms * US_PER_MS, false,
>>> +                                   sensor, reg, &val);
>>> +       if (timeout)
>>> +               return timeout;
>>> +
>>> +       return ret;
>>> +}
>>> +
>>> +static int vgxy61_wait_state(struct vgxy61_dev *sensor, int state)
>>> +{
>>> +       return vgxy61_poll_reg(sensor, DEVICE_SYSTEM_FSM, state);
>>> +}
>>> +
>>> +static int vgxy61_check_bw(struct vgxy61_dev *sensor)
>>> +{
>>> +       struct i2c_client *client = sensor->i2c_client;
>>> +       /* Correction factor for time required between 2 lines */
>>> +       const int mipi_margin = 1056;
>>> +       int binning_scale = 1 << sensor->current_mode->bin_mode;
>>> +       int bpp = get_bpp_by_code(sensor->fmt.code);
>>> +       int max_bit_per_line;
>>> +       int bit_per_line;
>>> +       u64 line_rate;
>>> +
>>> +       line_rate = sensor->nb_of_lane * (u64)sensor->data_rate_in_mbps * sensor->line_length;
>>> +       max_bit_per_line = div64_u64(line_rate, sensor->pclk) - mipi_margin;
>>> +       bit_per_line = (bpp * sensor->current_mode->width) / binning_scale;
>>> +
>>> +       dev_dbg(&client->dev, "max_bit_per_line = %d\n", max_bit_per_line);
>>> +       dev_dbg(&client->dev, "required bit_per_line = %d\n", bit_per_line);
>>> +
>>> +       return bit_per_line > max_bit_per_line ? -EINVAL : 0;
>>> +}
>>> +
>>> +static int apply_exposure(struct vgxy61_dev *sensor)
>>> +{
>>> +       struct i2c_client *client = sensor->i2c_client;
>>> +       int ret;
>>> +
>>> +        /* We first set expo to zero to avoid forbidden parameters couple */
>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_SHORT, 0);
>>> +       if (ret)
>>> +               return ret;
>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_LONG, sensor->expo_long);
>>> +       if (ret)
>>> +               return ret;
>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_SHORT, sensor->expo_short);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       dev_dbg(&client->dev, "%s applied expo %d (short: %d)\n", __func__,
>>> +               sensor->expo_long, sensor->expo_short);
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static int set_frame_rate(struct vgxy61_dev *sensor)
>>> +{
>>> +       u16 frame_length;
>>> +
>>> +       frame_length = sensor->pclk / (sensor->line_length * sensor->frame_interval.denominator);
>>> +
>>> +       return vgxy61_write_reg16(sensor, DEVICE_FRAME_LENGTH, frame_length);
>>> +}
>>> +
>>> +static int vgxy61_get_regulators(struct vgxy61_dev *sensor)
>>> +{
>>> +       int i;
>>> +
>>> +       for (i = 0; i < VGXY61_NUM_SUPPLIES; i++)
>>> +               sensor->supplies[i].supply = vgxy61_supply_name[i];
>>> +
>>> +       return devm_regulator_bulk_get(&sensor->i2c_client->dev, VGXY61_NUM_SUPPLIES,
>>> +                                      sensor->supplies);
>>> +}
>>> +
>>> +static int vgxy61_apply_reset(struct vgxy61_dev *sensor)
>>> +{
>>> +       struct i2c_client *client = sensor->i2c_client;
>>> +
>>> +       dev_dbg(&client->dev, "%s applied reset\n", __func__);
>>> +       gpiod_set_value_cansleep(sensor->reset_gpio, 0);
>>> +       usleep_range(5000, 10000);
>>> +       gpiod_set_value_cansleep(sensor->reset_gpio, 1);
>>> +       usleep_range(5000, 10000);
>>> +       gpiod_set_value_cansleep(sensor->reset_gpio, 0);
>>> +       usleep_range(40000, 100000);
>>> +       return vgxy61_wait_state(sensor, SW_STBY);
>>> +}
>>> +
>>> +static int vgxy61_try_fmt_internal(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *fmt,
>>> +                                  const struct vgxy61_mode_info **new_mode)
>>> +{
>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>> +       const struct vgxy61_mode_info *mode = sensor->sensor_modes;
>>> +       unsigned int index;
>>> +
>>> +       /* Select code */
>>> +       for (index = 0; index < ARRAY_SIZE(vgxy61_supported_codes); index++) {
>>> +               if (vgxy61_supported_codes[index] == fmt->code)
>>> +                       break;
>>> +       }
>>> +       if (index == ARRAY_SIZE(vgxy61_supported_codes))
>>> +               index = 0;
>>> +
>>> +       /* Select size */
>>> +       do {
>>> +               if (mode->width <= fmt->width && mode->height <= fmt->height)
>>> +                       break;
>>> +       } while ((++mode)->width);
>>> +       if (!mode->width)
>>> +               mode--;
> 
> Use v4l2_find_nearest_size?
> 

Sure.

>>> +
>>> +       *new_mode = mode;
>>> +       fmt->code = vgxy61_supported_codes[index];
>>> +       fmt->width = mode->width;
>>> +       fmt->height = mode->height;
>>> +       fmt->colorspace = V4L2_COLORSPACE_SRGB;
> 
> Is it? Most Bayer sensors are V4L2_COLORSPACE_RAW.
> What about ycbcr_enc, quantization, and xfer_func?
> 

Yes it should be RAW, but iirc I had some issue with the RAW colorspace. I will test it again in order to change it.
I will add missing fields too.


>>> +       fmt->field = V4L2_FIELD_NONE;
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static int vgxy61_stream_enable(struct vgxy61_dev *sensor)
>>> +{
>>> +       int center_x = sensor->sensor_width / 2;
>>> +       int center_y = sensor->sensor_height / 2;
>>> +       int scale = 1 << sensor->current_mode->bin_mode;
>>> +       int width = sensor->current_mode->width * scale;
>>> +       int height = sensor->current_mode->height * scale;
>>> +       int ret;
>>> +
>>> +       ret = vgxy61_check_bw(sensor);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       /* Configure sensor */
>>> +       ret = vgxy61_write_reg(sensor, DEVICE_FORMAT_CTRL, get_bpp_by_code(sensor->fmt.code));
>>> +       if (ret)
>>> +               return ret;
>>> +       ret = vgxy61_write_reg(sensor, DEVICE_OIF_ROI0_CTRL,
>>> +                              get_data_type_by_code(sensor->fmt.code));
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       ret = vgxy61_write_reg(sensor, DEVICE_READOUT_CTRL, sensor->current_mode->bin_mode);
>>> +       if (ret)
>>> +               return ret;
>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_H, center_x - width / 2);
>>> +       if (ret)
>>> +               return ret;
>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_H, center_x + width / 2 - 1);
>>> +       if (ret)
>>> +               return ret;
>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_V, center_y - height / 2);
>>> +       if (ret)
>>> +               return ret;
>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_V, center_y + height / 2 - 1);
>>> +       if (ret)
>>> +               return ret;
> 
> Use the selection API to allow the crop region to be configured?
> Avoids only supporting centre cropping.
> 

Yes, as we already mentionned in the other thread this is required by libcamera anyway. Two birds one stone.

>>> +
>>> +       ret = set_frame_rate(sensor);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       ret = apply_exposure(sensor);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       /* Start streaming */
>>> +       ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_START_STREAMING);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       ret = vgxy61_wait_state(sensor, STREAMING);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static int vgxy61_stream_disable(struct vgxy61_dev *sensor)
>>> +{
>>> +       int ret;
>>> +
>>> +       ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_STOP_STREAMING);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       ret = vgxy61_wait_state(sensor, SW_STBY);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static int vgxy61_s_stream(struct v4l2_subdev *sd, int enable)
>>> +{
>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>> +       struct i2c_client *client = sensor->i2c_client;
>>> +       int ret = 0;
>>> +
>>> +       mutex_lock(&sensor->lock);
>>> +       dev_dbg(&client->dev, "%s : requested %d / current = %d\n", __func__, enable,
>>> +               sensor->streaming);
>>> +       if (sensor->streaming == enable)
>>> +               goto out;
>>> +
>>> +       ret = enable ? vgxy61_stream_enable(sensor) : vgxy61_stream_disable(sensor);
>>> +       if (!ret)
>>> +               sensor->streaming = enable;
>>> +
>>> +out:
>>> +       dev_dbg(&client->dev, "%s current now = %d / %d\n", __func__, sensor->streaming, ret);
>>> +       mutex_unlock(&sensor->lock);
>>> +
>>> +       return ret;
>>> +}
>>> +
>>> +static int vgxy61_g_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *fi)
>>> +{
>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>> +
>>> +       mutex_lock(&sensor->lock);
>>> +       fi->interval = sensor->frame_interval;
>>> +       mutex_unlock(&sensor->lock);
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static int vgxy61_s_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *fi)
>>> +{
>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>> +       struct i2c_client *client = sensor->i2c_client;
>>> +       u64 req_int, err, min_err = ~0ULL;
>>> +       u64 test_int;
>>> +       int i = 0;
>>> +       int ret;
>>> +
>>> +       if (fi->interval.denominator == 0)
>>> +               return -EINVAL;
>>> +
>>> +       mutex_lock(&sensor->lock);
>>> +
>>> +       if (sensor->streaming) {
>>> +               ret = -EBUSY;
>>> +               goto out;
>>> +       }
>>> +
>>> +       dev_dbg(&client->dev, "%s request %d/%d\n", __func__,
>>> +               fi->interval.numerator, fi->interval.denominator);
>>> +       /* Find nearest period */
>>> +       req_int = div64_u64((u64)(fi->interval.numerator * 10000), fi->interval.denominator);
>>> +       for (i = 0; i < sensor->sensor_rates_nb; i++) {
>>> +               test_int = div64_u64((u64)10000, sensor->sensor_rates[i]);
>>> +               err = abs(test_int - req_int);
>>> +               if (err < min_err) {
>>> +                       fi->interval.numerator = 1;
>>> +                       fi->interval.denominator = sensor->sensor_rates[i];
>>> +                       min_err = err;
>>> +               }
>>> +       }
>>> +       sensor->frame_interval = fi->interval;
>>> +       dev_dbg(&client->dev, "%s set     %d/%d\n", __func__,
>>> +               fi->interval.numerator, fi->interval.denominator);
>>> +
>>> +       ret = 0;
>>> +out:
>>> +       mutex_unlock(&sensor->lock);
>>> +
>>> +       return ret;
>>> +}
>>> +
>>> +static int vgxy61_enum_mbus_code(struct v4l2_subdev *sd,
>>> +                                struct v4l2_subdev_state *sd_state,
>>> +                                struct v4l2_subdev_mbus_code_enum *code)
>>> +{
>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>> +       struct i2c_client *client = sensor->i2c_client;
>>> +
>>> +       dev_dbg(&client->dev, "%s probe index %d\n", __func__, code->index);
>>> +       if (code->index >= ARRAY_SIZE(vgxy61_supported_codes))
>>> +               return -EINVAL;
>>> +
>>> +       code->code = vgxy61_supported_codes[code->index];
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static int vgxy61_get_fmt(struct v4l2_subdev *sd,
>>> +                         struct v4l2_subdev_state *sd_state,
>>> +                         struct v4l2_subdev_format *format)
>>> +{
>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>> +       struct i2c_client *client = sensor->i2c_client;
>>> +       struct v4l2_mbus_framefmt *fmt;
>>> +
>>> +       dev_dbg(&client->dev, "%s probe %d\n", __func__, format->pad);
>>> +       dev_dbg(&client->dev, "%s %dx%d\n", __func__, format->format.width, format->format.height);
>>> +
>>> +       mutex_lock(&sensor->lock);
>>> +
>>> +       if (format->which == V4L2_SUBDEV_FORMAT_TRY)
>>> +               fmt = v4l2_subdev_get_try_format(&sensor->sd, sd_state, format->pad);
>>> +       else
>>> +               fmt = &sensor->fmt;
>>> +
>>> +       format->format = *fmt;
>>> +
>>> +       mutex_unlock(&sensor->lock);
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static int vgxy61_set_fmt(struct v4l2_subdev *sd,
>>> +                         struct v4l2_subdev_state *sd_state,
>>> +                         struct v4l2_subdev_format *format)
>>> +{
>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>> +       struct i2c_client *client = sensor->i2c_client;
>>> +       const struct vgxy61_mode_info *new_mode;
>>> +       struct v4l2_mbus_framefmt *fmt;
>>> +       int ret;
>>> +
>>> +       dev_dbg(&client->dev, "%s probe %d\n", __func__, format->pad);
>>> +       dev_dbg(&client->dev, "%s %dx%d\n", __func__, format->format.width, format->format.height);
>>> +
>>> +       mutex_lock(&sensor->lock);
>>> +
>>> +       if (sensor->streaming) {
>>> +               ret = -EBUSY;
>>> +               goto out;
>>> +       }
>>> +
>>> +       /* Find best format */
>>> +       ret = vgxy61_try_fmt_internal(sd, &format->format, &new_mode);
>>> +       if (ret)
>>> +               goto out;
>>> +
>>> +       if (format->which == V4L2_SUBDEV_FORMAT_TRY)
>>> +               fmt = v4l2_subdev_get_try_format(sd, sd_state, 0);
>>> +       else
>>> +               fmt = &sensor->fmt;
>>> +       *fmt = format->format;
>>> +       sensor->current_mode = new_mode;
>>> +       /* Update pixel rate control to reflect new mode */
>>> +       __v4l2_ctrl_s_ctrl_int64(sensor->ctrls.pixel_rate, get_pixel_rate(sensor));
>>> +
>>> +out:
>>> +       mutex_unlock(&sensor->lock);
>>> +
>>> +       return ret;
>>> +}
>>> +
>>> +static int vgxy61_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state,
>>> +                                 struct v4l2_subdev_frame_size_enum *fse)
>>> +{
>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>> +       struct i2c_client *client = sensor->i2c_client;
>>> +
>>> +       dev_dbg(&client->dev, "%s for index %d\n", __func__, fse->index);
>>> +       if (fse->index >= sensor->sensor_modes_nb)
>>> +               return -EINVAL;
>>> +
>>> +       fse->min_width = sensor->sensor_modes[fse->index].width;
>>> +       fse->max_width = fse->min_width;
>>> +       fse->min_height = sensor->sensor_modes[fse->index].height;
>>> +       fse->max_height = fse->min_height;
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static int vgxy61_enum_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state,
>>> +                                     struct v4l2_subdev_frame_interval_enum *fie)
>>> +{
>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>> +       const struct vgxy61_mode_info *mode = sensor->sensor_modes;
>>> +
>>> +       if (fie->index >= sensor->sensor_rates_nb)
>>> +               return -EINVAL;
>>> +       do {
>>> +               if (mode->width == fie->width && mode->height == fie->height)
>>> +                       break;
>>> +       } while ((++mode)->width);
>>> +       if (!mode->width)
>>> +               return -EINVAL;
>>> +
>>> +       fie->interval.numerator = 1;
>>> +       fie->interval.denominator = sensor->sensor_rates[fie->index];
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static int vgxy61_update_analog_gain(struct vgxy61_dev *sensor, u32 target)
>>> +{
>>> +       struct i2c_client *client = sensor->i2c_client;
>>> +       unsigned int idx;
>>> +       int ret;
>>> +
>>> +       /* Find smallest analog gains which is above or equal to target gain */
>>> +       for (idx = 0; idx < ARRAY_SIZE(analog_gains); idx++) {
>>> +               if (analog_gains[idx] >= target)
>>> +                       break;
>>> +       }
> 
> Why? The units of V4L2_CID_ANALOGUE_GAIN are not specified, so more
> typically it is a raw register write.
> 
> 

The output analog gain is 32/(32-idx). This code maps a gain to its matching register value.
The goal is to allow setting the control using a "real world gain" value. I could perform a raw register write ranging from 1 to 16. It would clean the code but would not be representative of the real gain.
Maybe I am just overthinking it though.

>>> +       /* Cap to maximum gain if no matching gain found */
>>> +       if (idx == ARRAY_SIZE(analog_gains))
>>> +               idx--;
>>> +
>>> +       /* Apply gain */
>>> +       ret = vgxy61_write_reg(sensor, DEVICE_ANALOG_GAIN, idx);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       dev_dbg(&client->dev, "Target analog gain: 0x%04x\n", target);
>>> +       dev_dbg(&client->dev, "   Set analog gain: 0x%04x\n", analog_gains[idx]);
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static int vgxy61_update_digital_gain(struct vgxy61_dev *sensor, u32 target)
>>> +{
>>> +       struct i2c_client *client = sensor->i2c_client;
>>> +       int ret;
>>> +
>>> +       /*
>>> +        * For a monochrome version, configuring DIGITAL_GAIN_LONG_CH0 and
>>> +        * DIGITAL_GAIN_SHORT_CH0 is enough to configure the gain of all
>>> +        * four sub pixels.
>>> +        */
>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_DIGITAL_GAIN_LONG, target);
>>> +       if (ret)
>>> +               return ret;
>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_DIGITAL_GAIN_SHORT, target);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       dev_dbg(&client->dev, "  Set digital gain: 0x%04x\n", target);
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static int vgxy61_update_patgen(struct vgxy61_dev *sensor, u32 index)
>>> +{
>>> +       u32 pattern = index <= 3 ? index : index + 12;
>>> +       u32 reg;
>>> +
>>> +       reg = (pattern << 18) | (pattern << 4);
>>> +       if (index)
>>> +               reg |= (1 << 16) | 1;
>>> +       return vgxy61_write_reg32(sensor, DEVICE_PATGEN_CTRL, reg);
>>> +}
>>> +
>>> +static int vgxy61_update_gpiox_strobe_mode(struct vgxy61_dev *sensor, enum strobe_modes mode,
>>> +                                          int idx)
>>> +{
>>> +       const u8 index2val[] = {0x0, 0x1, 0x3};
>>> +       u16 reg;
>>> +       int ret;
>>> +
>>> +       ret = vgxy61_read_reg16(sensor, DEVICE_SIGNALS_CTRL, &reg);
>>> +       if (ret)
>>> +               return ret;
>>> +       reg &= ~(0xf << (idx * 4));
>>> +       reg |= index2val[mode] << (idx * 4);
>>> +
>>> +       return vgxy61_write_reg16(sensor, DEVICE_SIGNALS_CTRL, reg);
>>> +}
>>> +
>>> +static int vgxy61_update_gpios_strobe_mode(struct vgxy61_dev *sensor, enum hdr hdr)
>>> +{
>>> +       enum strobe_modes strobe_mode;
>>> +       int i, ret;
>>> +
>>> +       switch (hdr) {
>>> +       case HDR_LINEAR:
>>> +               strobe_mode = STROBE_ENABLED;
>>> +               break;
>>> +       case HDR_SUB:
>>> +       case NO_HDR:
>>> +               strobe_mode = STROBE_LONG;
>>> +               break;
>>> +       default:
>>> +               /* Should never happen */
>>> +               WARN_ON(true);
>>> +               break;
>>> +       }
>>> +
>>> +       for (i = 0; i < NB_GPIOS; i++) {
>>> +               ret = vgxy61_update_gpiox_strobe_mode(sensor, strobe_mode, i);
>>> +               if (ret)
>>> +                       return ret;
>>> +       }
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static int vgxy61_update_gpios_strobe_polarity(struct vgxy61_dev *sensor, int polarity)
>>> +{
>>> +       struct i2c_client *client = sensor->i2c_client;
>>> +       int ret;
>>> +
>>> +       if (sensor->streaming)
>>> +               return -EBUSY;
>>> +
>>> +       dev_dbg(&client->dev, "setting gpios polarity: %d\n", polarity);
>>> +
>>> +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_0_CTRL, polarity << 1);
>>> +       if (ret)
>>> +               return ret;
>>> +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_1_CTRL, polarity << 1);
>>> +       if (ret)
>>> +               return ret;
>>> +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_2_CTRL, polarity << 1);
>>> +       if (ret)
>>> +               return ret;
>>> +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_3_CTRL, polarity << 1);
>>> +       if (ret)
>>> +               return ret;
>>> +       return vgxy61_write_reg(sensor, DEVICE_SIGNALS_POLARITY_CTRL, polarity);
>>> +}
>>> +
>>> +static int find_max_long_exposure(struct vgxy61_dev *sensor, int frame_length, int short_expo_ratio)
>>> +{
>>> +       int first_rot_max_expo;
>>> +       int second_rot_max_expo;
>>> +
>>> +       /*
>>> +        * Apply first rule of thumb:
>>> +        * frame_length < short_line_nb + sensor->sensor_height + sensor->rot_term
>>> +        * with short_line_nb = long_line_nb / short_expo_ratio
>>> +        */
>>> +       first_rot_max_expo = ((frame_length - sensor->sensor_height - sensor->rot_term) *
>>> +                            short_expo_ratio) - 1;
>>> +
>>> +       /*
>>> +        * Apply second rule of thumb
>>> +        * frame_length < short_line_nb + long_line_nb + VGXY61_EXPOS_ROT_TERM
>>> +        * with short_line_nb = long_line_nb / short_expo_ratio
>>> +        */
>>> +       second_rot_max_expo = (((frame_length - VGXY61_EXPOS_ROT_TERM) * short_expo_ratio) /
>>> +                             (short_expo_ratio + 1)) - 1;
>>> +
>>> +       /* Take the minimum of both rules */
>>> +       return min(first_rot_max_expo, second_rot_max_expo);
>>> +}
>>> +
>>> +static int vgxy61_update_exposure(struct vgxy61_dev *sensor, int long_expo_line_nb, enum hdr hdr,
>>> +                                 bool clamp)
>>> +{
>>> +       struct i2c_client *client = sensor->i2c_client;
>>> +       int max_long_expo;
>>> +       int max_short_expo = 0;
>>> +       int short_expo_line_nb = 0;
>>> +       /* We use a constant ratio of 10 for linear HDR mode */
>>> +       int hdr_linear_ratio = 10;
>>> +       u16 frame_length;
>>> +       int ret;
>>> +
>>> +       ret = vgxy61_read_reg16(sensor, DEVICE_FRAME_LENGTH, &frame_length);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       /* Long and short integration times must not be less than 10 lines */
>>> +       long_expo_line_nb = max(10, long_expo_line_nb);
>>> +       long_expo_line_nb = min_t(int, frame_length, long_expo_line_nb);
>>> +
>>> +       /* Compute short exposure according to hdr mode and long exposure */
>>> +       switch (hdr) {
>>> +       case HDR_LINEAR:
>>> +               max_long_expo = find_max_long_exposure(sensor, frame_length, hdr_linear_ratio);
>>> +               max_short_expo = (max_long_expo + (hdr_linear_ratio / 2)) / hdr_linear_ratio;
>>> +               short_expo_line_nb = (long_expo_line_nb + (hdr_linear_ratio / 2)) /
>>> +                                    hdr_linear_ratio;
>>> +               break;
>>> +       case HDR_SUB:
>>> +               max_long_expo = find_max_long_exposure(sensor, frame_length, 1);
>>> +               max_short_expo = max_long_expo;
>>> +               short_expo_line_nb = long_expo_line_nb;
>>> +               break;
>>> +       case NO_HDR:
>>> +               /*
>>> +                * As short expo is 0 here, only the second rule of thumb applies, see
>>> +                * find_max_long_exposure for more
>>> +                */
>>> +               max_long_expo = frame_length - VGXY61_EXPOS_ROT_TERM;
>>> +               break;
>>> +       default:
>>> +               /* Should never happen */
>>> +               WARN_ON(true);
>>> +               break;
>>> +       }
>>> +
>>> +       if (long_expo_line_nb > max_long_expo) {
>>> +               if (!clamp) {
>>> +                       dev_err(&client->dev, "Exposure %d too high (max for this hdr mode %d)\n",
>>> +                               long_expo_line_nb, max_long_expo);
>>> +                       return -EINVAL;
>>> +               }
>>> +               dev_warn(&client->dev, "Exposure %d too high for this hdr mode, clamping to %d\n",
>>> +                        long_expo_line_nb, max_long_expo);
>>> +               long_expo_line_nb = max_long_expo;
>>> +               short_expo_line_nb = max_short_expo;
>>> +       }
>>> +
>>> +       dev_dbg(&client->dev, "frame_length %d, long_expo_line_nb %d, short_expo_line_nb %d",
>>> +               frame_length, long_expo_line_nb, short_expo_line_nb);
>>> +
>>> +       /* Apply exposure */
>>> +       sensor->expo_long = long_expo_line_nb;
>>> +       sensor->expo_short = short_expo_line_nb;
>>> +
>>> +       if (sensor->streaming)
>>> +               return apply_exposure(sensor);
>>> +       return 0;
>>> +}
>>> +
>>> +static int vgxy61_update_hdr(struct vgxy61_dev *sensor, u32 index)
>>> +{
>>> +       const u8 index2val[] = {0x1, 0x4, 0xa};
>>> +       int ret;
>>> +
>>> +       /*
>>> +        * Short exposure changes according to HDR mode, do it first as it can
>>> +        * violate sensors 'rule of thumbs' and therefore will require to change
>>> +        * the long exposure
>>> +        */
>>> +       ret = vgxy61_update_exposure(sensor, sensor->expo_long, index, true);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       /* Update strobe mode according to HDR */
>>> +       ret = vgxy61_update_gpios_strobe_mode(sensor, index);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       ret = vgxy61_write_reg(sensor, DEVICE_HDR_CTRL, index2val[index]);
>>> +       if (ret)
>>> +               return ret;
>>> +       sensor->hdr = index;
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static int vgxy61_get_temp_stream_enabled(struct vgxy61_dev *sensor, int *temp)
>>> +{
>>> +       int ret;
>>> +       u16 temperature;
>>> +
>>> +       ret = vgxy61_read_reg16(sensor, DEVICE_THSENS1_TEMPERATURE, &temperature);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       /* Temperature is expressed in Kelvin in Q10.2 fixed point format*/
>>> +       temperature = (temperature & 0x0fff) >> 2;
>>> +       temperature = kelvin_to_celsius(temperature);
>>> +
>>> +       *temp = temperature;
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static int vgxy61_get_temp_stream_disabled(struct vgxy61_dev *sensor, int *temp)
>>> +{
>>> +       int ret;
>>> +
>>> +       /* Device needs to be in standby mode if streaming is off */
>>> +       ret = vgxy61_write_reg(sensor, DEVICE_STBY, STBY_REQ_TMP_READ);
>>> +       if (ret)
>>> +               return ret;
>>> +       ret = vgxy61_poll_reg(sensor, DEVICE_STBY, STBY_NO_REQ);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       return vgxy61_get_temp_stream_enabled(sensor, temp);
>>> +}
>>> +
>>> +static int vgxy61_get_temp(struct vgxy61_dev *sensor, int *temp)
>>> +{
>>> +       *temp = 0;
>>> +       if (sensor->streaming)
>>> +               return vgxy61_get_temp_stream_enabled(sensor, temp);
>>> +       else
>>> +               return vgxy61_get_temp_stream_disabled(sensor, temp);
>>> +}
>>> +
>>> +static int vgxy61_s_ctrl(struct v4l2_ctrl *ctrl)
>>> +{
>>> +       struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>> +       int ret;
>>> +
>>> +       switch (ctrl->id) {
>>> +       case V4L2_CID_EXPOSURE:
>>> +               ret = vgxy61_update_exposure(sensor, ctrl->val, sensor->hdr, false);
>>> +               ctrl->val = sensor->expo_long;
>>> +               break;
>>> +       case V4L2_CID_ANALOGUE_GAIN:
>>> +               ret = vgxy61_update_analog_gain(sensor, ctrl->val);
>>> +               break;
>>> +       case V4L2_CID_DIGITAL_GAIN:
>>> +               ret = vgxy61_update_digital_gain(sensor, ctrl->val);
>>> +               break;
>>> +       case V4L2_CID_VFLIP:
>>> +       case V4L2_CID_HFLIP:
>>> +               if (sensor->streaming) {
>>> +                       ret = -EBUSY;
>>> +                       break;
>>> +               }
>>> +               if (ctrl->id == V4L2_CID_VFLIP)
>>> +                       sensor->vflip = ctrl->val;
>>> +               if (ctrl->id == V4L2_CID_HFLIP)
>>> +                       sensor->hflip = ctrl->val;
>>> +               ret = vgxy61_write_reg(sensor, DEVICE_ORIENTATION,
>>> +                                      sensor->hflip | (sensor->vflip << 1));
>>> +               break;
>>> +       case V4L2_CID_TEST_PATTERN:
>>> +               ret = vgxy61_update_patgen(sensor, ctrl->val);
>>> +               break;
>>> +       case V4L2_CID_HDR:
>>> +               ret = vgxy61_update_hdr(sensor, ctrl->val);
>>> +               break;
>>> +       case V4L2_CID_GPIOS_STROBE_LONG_START_DELAY:
>>> +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_LONG_START_DELAY, ctrl->val);
>>> +               break;
>>> +       case V4L2_CID_GPIOS_STROBE_LONG_END_DELAY:
>>> +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_LONG_END_DELAY, ctrl->val);
>>> +               break;
>>> +       case V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY:
>>> +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_SHORT_START_DELAY, ctrl->val);
>>> +               break;
>>> +       case V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY:
>>> +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_SHORT_END_DELAY, ctrl->val);
>>> +               break;
>>> +       default:
>>> +               ret = -EINVAL;
>>> +               break;
>>> +       }
>>> +
>>> +       return ret;
>>> +}
>>> +
>>> +static int vgxy61_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
>>> +{
>>> +       struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>> +       int temperature;
>>> +       int ret;
>>> +
>>> +       switch (ctrl->id) {
>>> +       case V4L2_CID_TEMPERATURE:
>>> +               ret = vgxy61_get_temp(sensor, &temperature);
>>> +               if (ret)
>>> +                       break;
>>> +               ret = __v4l2_ctrl_s_ctrl(ctrl, temperature);
> 
> Do you need to call __v4l2_ctrl_s_ctrl instead of just updating
> ctrl->val? There is no s_ctrl handler for the control as you've
> declared it as read only.
> 

Sure.

>>> +               break;
>>> +       default:
>>> +               ret = -EINVAL;
>>> +               break;
>>> +       }
>>> +
>>> +       return ret;
>>> +}
>>> +
>>> +static const struct v4l2_ctrl_ops vgxy61_ctrl_ops = {
>>> +       .g_volatile_ctrl = vgxy61_g_volatile_ctrl,
>>> +       .s_ctrl = vgxy61_s_ctrl,
>>> +};
>>> +
>>> +static const struct v4l2_ctrl_config vgxy61_hdr_ctrl = {
>>> +       .ops            = &vgxy61_ctrl_ops,
>>> +       .id             = V4L2_CID_HDR,
>>> +       .name           = "HDR mode",
>>> +       .type           = V4L2_CTRL_TYPE_MENU,
>>> +       .min            = 0,
>>> +       .max            = ARRAY_SIZE(vgxy61_hdr_modes) - 1,
>>> +       .def            = NO_HDR,
>>> +       .qmenu          = vgxy61_hdr_modes,
>>> +};
>>> +
>>> +static const struct v4l2_ctrl_config vgxy61_strobe_long_start_delay = {
>>> +       .ops            = &vgxy61_ctrl_ops,
>>> +       .id             = V4L2_CID_GPIOS_STROBE_LONG_START_DELAY,
>>> +       .name           = "Long strobe mode start delay in lines",
>>> +       .type           = V4L2_CTRL_TYPE_INTEGER,
>>> +       .min            = -128,
>>> +       .max            = 127,
>>> +       .step           = 1,
>>> +       .def            = 0,
>>> +       .flags          = 0,
>>> +};
>>> +
>>> +static const struct v4l2_ctrl_config vgxy61_strobe_long_end_delay = {
>>> +       .ops            = &vgxy61_ctrl_ops,
>>> +       .id             = V4L2_CID_GPIOS_STROBE_LONG_END_DELAY,
>>> +       .name           = "Long strobe mode end delay in lines",
>>> +       .type           = V4L2_CTRL_TYPE_INTEGER,
>>> +       .min            = -128,
>>> +       .max            = 127,
>>> +       .step           = 1,
>>> +       .def            = 0,
>>> +       .flags          = 0,
>>> +};
>>> +
>>> +static const struct v4l2_ctrl_config vgxy61_strobe_short_start_delay = {
>>> +       .ops            = &vgxy61_ctrl_ops,
>>> +       .id             = V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY,
>>> +       .name           = "Short strobe mode start delay in lines",
>>> +       .type           = V4L2_CTRL_TYPE_INTEGER,
>>> +       .min            = -128,
>>> +       .max            = 127,
>>> +       .step           = 1,
>>> +       .def            = 0,
>>> +       .flags          = 0,
>>> +};
>>> +
>>> +static const struct v4l2_ctrl_config vgxy61_strobe_short_end_delay = {
>>> +       .ops            = &vgxy61_ctrl_ops,
>>> +       .id             = V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY,
>>> +       .name           = "Short strobe mode end delay in lines",
>>> +       .type           = V4L2_CTRL_TYPE_INTEGER,
>>> +       .min            = -128,
>>> +       .max            = 127,
>>> +       .step           = 1,
>>> +       .def            = 0,
>>> +       .flags          = 0,
>>> +};
>>> +
>>> +static const struct v4l2_ctrl_config vgxy61_temp_ctrl = {
>>> +       .ops            = &vgxy61_ctrl_ops,
>>> +       .id             = V4L2_CID_TEMPERATURE,
>>> +       .name           = "Temperature in °C",
>>> +       .type           = V4L2_CTRL_TYPE_INTEGER,
>>> +       .min            = -128,
>>> +       .max            = 128,
>>> +       .step           = 1,
>>> +};
>>> +
>>> +static int vgxy61_init_controls(struct vgxy61_dev *sensor)
>>> +{
>>> +       const struct v4l2_ctrl_ops *ops = &vgxy61_ctrl_ops;
>>> +       struct vgxy61_ctrls *ctrls = &sensor->ctrls;
>>> +       struct v4l2_ctrl_handler *hdl = &sensor->ctrls.handler;
>>> +       int ret;
>>> +
>>> +       v4l2_ctrl_handler_init(hdl, 16);
>>> +       /* We can use our own mutex for the ctrl lock */
>>> +       hdl->lock = &sensor->lock;
>>> +       ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, 10, 0xffff, 1,
>>> +                                           sensor->expo_long);
>>> +       /* This is 8.8 fixed point value */
>>> +       ctrls->analog_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, 0x3fff, 1,
>>> +                                              0x0);
>>> +       /* This is 8.8 fixed point value */
>>> +       ctrls->digital_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 0, 0xfff, 1,
>>> +                                               0x100);
>>> +       ctrls->vflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, sensor->vflip);
>>> +       ctrls->hflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, sensor->hflip);
>>> +       ctrls->patgen = v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN,
>>> +                                                    ARRAY_SIZE(vgxy61_test_pattern_menu) - 1,
>>> +                                                    0, 0, vgxy61_test_pattern_menu);
>>> +       ctrls->hdr = v4l2_ctrl_new_custom(hdl, &vgxy61_hdr_ctrl, NULL);
>>> +       ctrls->pixel_rate = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1,
>>> +                                             get_pixel_rate(sensor));
>>> +       ctrls->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>>> +       ctrls->link_freq = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ,
>>> +                                                 ARRAY_SIZE(link_freq) - 1, 0, link_freq);
>>> +       ctrls->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>>> +       /* Gpios ctrls */
>>> +       ctrls->gpios.long_start = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_long_start_delay, NULL);
>>> +       ctrls->gpios.long_end = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_long_end_delay, NULL);
>>> +       ctrls->gpios.short_start = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_short_start_delay,
>>> +                                                       NULL);
>>> +       ctrls->gpios.short_end = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_short_end_delay, NULL);
>>> +       /* Temperature ctrl */
>>> +       ctrls->temp = v4l2_ctrl_new_custom(hdl, &vgxy61_temp_ctrl, NULL);
>>> +       ctrls->temp->flags |= V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY;
>>> +
>>> +       if (hdl->error) {
>>> +               ret = hdl->error;
>>> +               goto free_ctrls;
>>> +       }
>>> +
>>> +       sensor->sd.ctrl_handler = hdl;
>>> +       return 0;
>>> +
>>> +free_ctrls:
>>> +       v4l2_ctrl_handler_free(hdl);
>>> +       return ret;
>>> +}
>>> +
>>> +static const struct v4l2_subdev_core_ops vgxy61_core_ops = {
>>> +};
>>> +
>>> +static const struct v4l2_subdev_video_ops vgxy61_video_ops = {
>>> +       .s_stream = vgxy61_s_stream,
>>> +       .g_frame_interval = vgxy61_g_frame_interval,
>>> +       .s_frame_interval = vgxy61_s_frame_interval,
>>> +};
>>> +
>>> +static const struct v4l2_subdev_pad_ops vgxy61_pad_ops = {
>>> +       .enum_mbus_code = vgxy61_enum_mbus_code,
>>> +       .get_fmt = vgxy61_get_fmt,
>>> +       .set_fmt = vgxy61_set_fmt,
>>> +       .enum_frame_size = vgxy61_enum_frame_size,
>>> +       .enum_frame_interval = vgxy61_enum_frame_interval,
>>> +};
>>> +
>>> +static const struct v4l2_subdev_ops vgxy61_subdev_ops = {
>>> +       .core = &vgxy61_core_ops,
>>> +       .video = &vgxy61_video_ops,
>>> +       .pad = &vgxy61_pad_ops,
>>> +};
>>> +
>>> +static const struct media_entity_operations vgxy61_subdev_entity_ops = {
>>> +       .link_validate = v4l2_subdev_link_validate,
>>> +};
>>> +
>>> +/* Set phy polarities */
>>> +static int vgxy61_tx_from_ep(struct vgxy61_dev *sensor, struct fwnode_handle *endpoint)
>>> +{
>>> +       struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
>>> +       struct i2c_client *client = sensor->i2c_client;
>>> +       u32 log2phy[NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
>>> +       u32 phy2log[NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
>>> +       int polarities[NB_POLARITIES] = {0, 0, 0, 0, 0};
>>> +       int l_nb;
>>> +       int p, l;
>>> +       int ret;
>>> +       int i;
>>> +
>>> +       ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep);
>>> +       if (ret)
>>> +               goto error_alloc;
>>> +
>>> +       l_nb = ep.bus.mipi_csi2.num_data_lanes;
>>> +       if (l_nb != 1 && l_nb != 2 && l_nb != 4) {
>>> +               dev_err(&client->dev, "invalid data lane number %d\n", l_nb);
>>> +               goto error_ep;
>>> +       }
>>> +
>>> +       /* Build log2phy, phy2log and polarities from ep info */
>>> +       log2phy[0] = ep.bus.mipi_csi2.clock_lane;
>>> +       phy2log[log2phy[0]] = 0;
>>> +       for (l = 1; l < l_nb + 1; l++) {
>>> +               log2phy[l] = ep.bus.mipi_csi2.data_lanes[l - 1];
>>> +               phy2log[log2phy[l]] = l;
>>> +       }
>>> +       /*
>>> +        * Then fill remaining slots for every physical slot to have something valid for hardware
>>> +        * stuff.
>>> +        */
>>> +       for (p = 0; p < NB_POLARITIES; p++) {
>>> +               if (phy2log[p] != ~0)
>>> +                       continue;
>>> +               phy2log[p] = l;
>>> +               log2phy[l] = p;
>>> +               l++;
>>> +       }
>>> +       for (l = 0; l < l_nb + 1; l++)
>>> +               polarities[l] = ep.bus.mipi_csi2.lane_polarities[l];
>>> +
>>> +       if (log2phy[0] != 0) {
>>> +               dev_err(&client->dev, "clk lane must be map to physical lane 0\n");
>>> +               goto error_ep;
>>> +       }
>>> +       sensor->oif_ctrl = (polarities[4] << 15) + ((phy2log[4] - 1) << 13) +
>>> +                          (polarities[3] << 12) + ((phy2log[3] - 1) << 10) +
>>> +                          (polarities[2] <<  9) + ((phy2log[2] - 1) <<  7) +
>>> +                          (polarities[1] <<  6) + ((phy2log[1] - 1) <<  4) +
>>> +                          (polarities[0] <<  3) +
>>> +                          l_nb;
>>> +       sensor->nb_of_lane = l_nb;
>>> +
>>> +       dev_dbg(&client->dev, "tx uses %d lanes", l_nb);
>>> +       for (i = 0; i < 5; i++) {
>>> +               dev_dbg(&client->dev, "log2phy[%d] = %d\n", i, log2phy[i]);
>>> +               dev_dbg(&client->dev, "phy2log[%d] = %d\n", i, phy2log[i]);
>>> +               dev_dbg(&client->dev, "polarity[%d] = %d\n", i, polarities[i]);
>>> +       }
>>> +       dev_dbg(&client->dev, "oif_ctrl = 0x%04x\n", sensor->oif_ctrl);
>>> +
>>> +       v4l2_fwnode_endpoint_free(&ep);
>>> +
>>> +       return 0;
>>> +
>>> +error_ep:
>>> +       v4l2_fwnode_endpoint_free(&ep);
>>> +error_alloc:
>>> +
>>> +       return -EINVAL;
>>> +}
>>> +
>>> +static int vgxy61_configure(struct vgxy61_dev *sensor)
>>> +{
>>> +       struct i2c_client *client = sensor->i2c_client;
>>> +       int sensor_freq;
>>> +       unsigned int prediv;
>>> +       unsigned int mult;
>>> +       int ret;
>>> +
>>> +       compute_pll_parameters_by_freq(sensor->clk_freq, &prediv, &mult);
>>> +       sensor_freq = (mult * sensor->clk_freq) / prediv;
>>> +       /* Frequency to data rate is 1:1 ratio for MIPI */
>>> +       sensor->data_rate_in_mbps = sensor_freq;
>>> +       /* Video timing ISP path (pixel clock)  requires 804/5 mhz = 160 mhz */
>>> +       sensor->pclk = sensor_freq / 5;
>>> +
>>> +       /* Cache line_length value */
>>> +       ret = vgxy61_read_reg16(sensor, DEVICE_LINE_LENGTH, &sensor->line_length);
>>> +       if (ret)
>>> +               return ret;
>>> +       /* Configure clocks */
>>> +       ret = vgxy61_write_reg32(sensor, DEVICE_EXT_CLOCK, sensor->clk_freq);
>>> +       if (ret)
>>> +               return ret;
>>> +       ret = vgxy61_write_reg(sensor, DEVICE_CLK_PLL_PREDIV, prediv);
>>> +       if (ret)
>>> +               return ret;
>>> +       ret = vgxy61_write_reg(sensor, DEVICE_CLK_SYS_PLL_MULT, mult);
>>> +       if (ret)
>>> +               return ret;
>>> +       /* Configure interface */
>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_OIF_CTRL, sensor->oif_ctrl);
>>> +       if (ret)
>>> +               return ret;
>>> +       /* Disable pwm compression */
>>> +       ret = vgxy61_write_reg(sensor, DEVICE_FRAME_CONTENT_CTRL, 0);
>>> +       if (ret)
>>> +               return ret;
>>> +       /* Disable asil lines */
>>> +       ret = vgxy61_write_reg(sensor, DEVICE_BYPASS_CTRL, 4);
>>> +       if (ret)
>>> +               return ret;
>>> +       /* Set gpios polarity according to device tree value */
>>> +       ret = vgxy61_update_gpios_strobe_polarity(sensor, sensor->gpios_polarity);
>>> +       if (ret)
>>> +               return ret;
>>> +       /* HDR mode */
>>> +       ret = vgxy61_update_hdr(sensor, sensor->hdr);
>>> +       if (ret)
>>> +               return ret;
>>> +       /* Slave mode */
>>> +       ret = vgxy61_write_reg(sensor, DEVICE_VT_CTRL, sensor->slave_mode);
>>> +       if (ret)
>>> +               return ret;
>>> +       /* Set pattern generator solid to middle value */
>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_GR, 0x800);
>>> +       if (ret)
>>> +               return ret;
>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_R, 0x800);
>>> +       if (ret)
>>> +               return ret;
>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_B, 0x800);
>>> +       if (ret)
>>> +               return ret;
>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_GB, 0x800);
>>> +       if (ret)
>>> +               return ret;
>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_GR, 0x800);
>>> +       if (ret)
>>> +               return ret;
>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_R, 0x800);
>>> +       if (ret)
>>> +               return ret;
>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_B, 0x800);
>>> +       if (ret)
>>> +               return ret;
>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_GB, 0x800);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       dev_dbg(&client->dev, "clock prediv = %d\n", prediv);
>>> +       dev_dbg(&client->dev, "clock mult = %d\n", mult);
>>> +       dev_dbg(&client->dev, "data rate = %d mbps\n",
>>> +               sensor->data_rate_in_mbps);
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static int vgxy61_patch(struct vgxy61_dev *sensor)
>>> +{
>>> +       struct i2c_client *client = sensor->i2c_client;
>>> +       u16 patch;
>>> +       int ret;
>>> +
>>> +       ret = vgxy61_write_array(sensor, DEVICE_FWPATCH_START_ADDR, sizeof(patch_array),
>>> +                                patch_array);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       ret = vgxy61_write_reg(sensor, DEVICE_STBY, 0x10);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       ret = vgxy61_poll_reg(sensor, DEVICE_STBY, 0);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       ret = vgxy61_read_reg16(sensor, DEVICE_FWPATCH_REVISION, &patch);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       if (patch != (DEVICE_FWPATCH_REVISION_MAJOR << 12) +
>>> +                    (DEVICE_FWPATCH_REVISION_MINOR << 8) +
>>> +                    DEVICE_FWPATCH_REVISION_MICRO) {
>>> +               dev_err(&client->dev, "bad patch version expected %d.%d.%d got %d.%d.%d\n",
>>> +                       DEVICE_FWPATCH_REVISION_MAJOR,
>>> +                       DEVICE_FWPATCH_REVISION_MINOR,
>>> +                       DEVICE_FWPATCH_REVISION_MICRO,
>>> +                       patch >> 12, (patch >> 8) & 0x0f, patch & 0xff);
>>> +               return -ENODEV;
>>> +       }
>>> +       dev_dbg(&client->dev, "patch %d.%d.%d applied\n",
>>> +               patch >> 12, (patch >> 8) & 0x0f, patch & 0xff);
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static int vgxy61_detect_cut_version(struct vgxy61_dev *sensor)
>>> +{
>>> +       struct i2c_client *client = sensor->i2c_client;
>>> +       u16 device_rev;
>>> +       int ret;
>>> +
>>> +       ret = vgxy61_read_reg16(sensor, DEVICE_REVISION, &device_rev);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       switch (device_rev >> 8) {
>>> +       case 0xA:
>>> +               dev_info(&client->dev, "Cut1 detected\n");
>>> +               dev_err(&client->dev, "Cut1 not supported by this driver\n");
>>> +               return -ENODEV;
>>> +       case 0xB:
>>> +               dev_info(&client->dev, "Cut2 detected\n");
>>> +               return 0;
>>> +       case 0xC:
>>> +               dev_info(&client->dev, "Cut3 detected\n");
>>> +               return 0;
>>> +       default:
>>> +               dev_err(&client->dev, "Unable to detect cut version\n");
>>> +               return -ENODEV;
>>> +       }
>>> +}
>>> +
>>> +static int vgxy61_detect(struct vgxy61_dev *sensor)
>>> +{
>>> +       struct i2c_client *client = sensor->i2c_client;
>>> +       u16 id = 0;
>>> +       int ret;
>>> +       u8 st;
>>> +
>>> +       ret = vgxy61_read_reg16(sensor, DEVICE_MODEL_ID_REG, &id);
>>> +       if (ret)
>>> +               return ret;
>>> +       if (id != VG5661_MODEL_ID && id != VG5761_MODEL_ID) {
>>> +               dev_warn(&client->dev, "Unsupported sensor id %x\n", id);
>>> +               return -ENODEV;
>>> +       }
>>> +       dev_dbg(&client->dev, "detected sensor id = 0x%04x\n", id);
>>> +
>>> +       if (id == VG5761_MODEL_ID) {
>>> +               sensor->sensor_width = VGX761_WIDTH;
>>> +               sensor->sensor_height = VGX761_HEIGHT;
>>> +               sensor->sensor_modes = vgx761_mode_data;
>>> +               sensor->sensor_modes_nb = ARRAY_SIZE(vgx761_mode_data);
>>> +               sensor->current_mode = &vgx761_mode_data[VGX761_DEFAULT_MODE];
>>> +               sensor->rot_term = VGX761_SHORT_ROT_TERM;
>>> +               sensor->sensor_rates = vgx761_sensor_frame_rates;
>>> +               sensor->sensor_rates_nb = ARRAY_SIZE(vgx761_sensor_frame_rates);
>>> +       } else if (id == VG5661_MODEL_ID) {
>>> +               sensor->sensor_width = VGX661_WIDTH;
>>> +               sensor->sensor_height = VGX661_HEIGHT;
>>> +               sensor->sensor_modes = vgx661_mode_data;
>>> +               sensor->sensor_modes_nb = ARRAY_SIZE(vgx661_mode_data);
>>> +               sensor->current_mode = &vgx661_mode_data[VGX661_DEFAULT_MODE];
>>> +               sensor->rot_term = VGX661_SHORT_ROT_TERM;
>>> +               sensor->sensor_rates = vgx661_sensor_frame_rates;
>>> +               sensor->sensor_rates_nb = ARRAY_SIZE(vgx661_sensor_frame_rates);
>>> +       }
>>> +
>>> +       ret = vgxy61_wait_state(sensor, SW_STBY);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       ret = vgxy61_read_reg(sensor, DEVICE_NVM, &st);
>>> +       if (ret)
>>> +               return ret;
>>> +       if (st != NVM_OK)
>>> +               dev_warn(&client->dev, "Bad nvm state got %d\n", st);
>>> +
>>> +       /* Detect cut version */
>>> +       ret = vgxy61_detect_cut_version(sensor);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static int vgxy61_probe(struct i2c_client *client)
>>> +{
>>> +       struct device *dev = &client->dev;
>>> +       struct fwnode_handle *endpoint;
>>> +       struct vgxy61_dev *sensor;
>>> +       int ret;
>>> +
>>> +       sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
>>> +       if (!sensor)
>>> +               return -ENOMEM;
>>> +
>>> +       sensor->i2c_client = client;
>>> +       sensor->streaming = false;
>>> +       sensor->fmt.code = MEDIA_BUS_FMT_SGBRG8_1X8;
>>> +       sensor->fmt.field = V4L2_FIELD_NONE;
>>> +       sensor->fmt.colorspace = V4L2_COLORSPACE_SRGB;
> 
> ycbcr_enc, quantization, and xfer_func too.
> 

Ok.

>>> +       sensor->frame_interval.numerator = 1;
>>> +       sensor->frame_interval.denominator = 60;
>>> +       sensor->hdr = NO_HDR;
>>> +       sensor->expo_long = 200;
>>> +       sensor->expo_short = 0;
>>> +       sensor->hflip = false;
>>> +       sensor->vflip = false;
>>> +
>>> +       endpoint = fwnode_graph_get_next_endpoint(of_fwnode_handle(dev->of_node), NULL);
>>> +       if (!endpoint) {
>>> +               dev_err(dev, "endpoint node not found\n");
>>> +               return -EINVAL;
>>> +       }
>>> +
>>> +       ret = vgxy61_tx_from_ep(sensor, endpoint);
>>> +       fwnode_handle_put(endpoint);
>>> +       if (ret) {
>>> +               dev_err(dev, "Failed to parse endpoint %d\n", ret);
>>> +               return ret;
>>> +       }
>>> +
>>> +       sensor->xclk = devm_clk_get(dev, "xclk");
>>> +       if (IS_ERR(sensor->xclk)) {
>>> +               dev_err(dev, "failed to get xclk\n");
>>> +               return PTR_ERR(sensor->xclk);
>>> +       }
>>> +       sensor->clk_freq = clk_get_rate(sensor->xclk);
>>> +       if (sensor->clk_freq < 6000000 || sensor->clk_freq > 27000000) {
>>> +               dev_err(dev, "Only 6Mhz-27Mhz clock range supported. provide %d Hz\n",
>>> +                       sensor->clk_freq);
>>> +               return -EINVAL;
>>> +       }
>>> +       sensor->gpios_polarity = of_property_read_bool(dev->of_node, "invert-gpios-polarity");
>>> +       sensor->slave_mode = of_property_read_bool(dev->of_node, "slave-mode");
>>> +
>>> +       v4l2_i2c_subdev_init(&sensor->sd, client, &vgxy61_subdev_ops);
>>> +       sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>>> +       sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
>>> +       sensor->sd.entity.ops = &vgxy61_subdev_entity_ops;
>>> +       sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
>>> +
>>> +       ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
>>> +       if (ret) {
>>> +               dev_err(&client->dev, "pads init failed %d\n", ret);
>>> +               return ret;
>>> +       }
>>> +
>>> +       /* Request optional reset pin */
>>> +       sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
>>> +
>>> +       ret = vgxy61_get_regulators(sensor);
>>> +       if (ret) {
>>> +               dev_err(&client->dev, "failed to get regulators %d\n", ret);
>>> +               goto entity_cleanup;
>>> +       }
>>> +
>>> +       ret = regulator_bulk_enable(VGXY61_NUM_SUPPLIES, sensor->supplies);
>>> +       if (ret) {
>>> +               dev_err(&client->dev, "failed to enable regulators %d\n", ret);
>>> +               goto entity_cleanup;
>>> +       }
>>> +
>>> +       ret = clk_prepare_enable(sensor->xclk);
>>> +       if (ret) {
>>> +               dev_err(&client->dev, "failed to enable clock %d\n", ret);
>>> +               goto disable_bulk;
>>> +       }
>>> +
>>> +       mutex_init(&sensor->lock);
>>> +
>>> +       /* Apply reset sequence */
>>> +       if (sensor->reset_gpio) {
>>> +               ret = vgxy61_apply_reset(sensor);
>>> +               if (ret) {
>>> +                       dev_err(&client->dev, "sensor reset failed %d\n", ret);
>>> +                       goto disable_clock;
>>> +               }
>>> +       }
>>> +
>>> +       ret = vgxy61_detect(sensor);
>>> +       if (ret) {
>>> +               dev_err(&client->dev, "sensor detect failed %d\n", ret);
>>> +               goto disable_clock;
>>> +       }
>>> +       sensor->fmt.width = sensor->current_mode->width;
>>> +       sensor->fmt.height = sensor->current_mode->height;
>>> +
>>> +       ret = vgxy61_patch(sensor);
>>> +       if (ret) {
>>> +               dev_err(&client->dev, "sensor patch failed %d\n", ret);
>>> +               goto disable_clock;
>>> +       }
>>> +
>>> +       ret = vgxy61_configure(sensor);
>>> +       if (ret) {
>>> +               dev_err(&client->dev, "sensor configuration failed %d\n", ret);
>>> +               goto disable_clock;
>>> +       }
>>> +
>>> +       ret = vgxy61_init_controls(sensor);
>>> +       if (ret) {
>>> +               dev_err(&client->dev, "controls initialization failed %d\n", ret);
>>> +               goto disable_clock;
>>> +       }
>>> +
>>> +       ret = v4l2_async_register_subdev(&sensor->sd);
>>> +       if (ret) {
>>> +               dev_err(&client->dev, "async subdev register failed %d\n", ret);
>>> +               goto disable_clock;
>>> +       }
>>> +
>>> +       dev_info(&client->dev, "vgxy61 probe successfully\n");
>>> +
>>> +       return 0;
>>> +
>>> +disable_clock:
>>> +       clk_disable_unprepare(sensor->xclk);
>>> +disable_bulk:
>>> +       regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
>>> +entity_cleanup:
>>> +       mutex_destroy(&sensor->lock);
>>> +       media_entity_cleanup(&sensor->sd.entity);
>>> +       return ret;
>>> +}
>>> +
>>> +static int vgxy61_remove(struct i2c_client *client)
>>> +{
>>> +       struct v4l2_subdev *sd = i2c_get_clientdata(client);
>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>> +
>>> +       v4l2_async_unregister_subdev(&sensor->sd);
>>> +       clk_disable_unprepare(sensor->xclk);
>>> +       mutex_destroy(&sensor->lock);
>>> +       media_entity_cleanup(&sensor->sd.entity);
>>> +       regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static const struct of_device_id vgxy61_dt_ids[] = {
>>> +       { .compatible = "st,st-vgxy61" },
>>> +       { /* sentinel */ }
>>> +};
>>> +MODULE_DEVICE_TABLE(of, vgxy61_dt_ids);
>>> +
>>> +static struct i2c_driver vgxy61_i2c_driver = {
>>> +       .driver = {
>>> +               .name  = "st-vgxy61",
>>> +               .of_match_table = vgxy61_dt_ids,
> 
> Support for pm_runtime? Then again the sensor appears to be powered up
> in probe and never powered down again.
> 
>   Dave
> 

The power management is handled by the internal CPU. When in SW_STBY the consumption is as its lowest.
Therefore sensor does not provide any power up/down register access, only the optional reset pin you saw in the probe.
I am not familiar with pm_runtime yet, I think it is not necessary in this case.

>>> +       },
>>> +       .probe_new = vgxy61_probe,
>>> +       .remove = vgxy61_remove,
>>> +};
>>> +
>>> +module_i2c_driver(vgxy61_i2c_driver);
>>> +
>>> +MODULE_AUTHOR("Benjamin Mugnier <benjamin.mugnier@foss.st.com>");
>>> +MODULE_AUTHOR("Mickael Guene <mickael.guene@st.com>");
>>> +MODULE_AUTHOR("Sylvain Petinot <sylvain.petinot@foss.st.com>");
>>> +MODULE_DESCRIPTION("VGXY61 camera subdev driver");
>>> +MODULE_LICENSE("GPL v2");
>>> --
>>> 2.25.1
>>>

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

* Re: [PATCH 1/2] media: dt-bindings: media: i2c: Add ST VGXY61 camera sensor binding
  2022-03-10 15:38   ` Dave Stevenson
@ 2022-03-11 11:25     ` Benjamin Mugnier
  2022-03-11 13:13       ` Sakari Ailus
  0 siblings, 1 reply; 17+ messages in thread
From: Benjamin Mugnier @ 2022-03-11 11:25 UTC (permalink / raw)
  To: Dave Stevenson
  Cc: Linux Media Mailing List, alain.volmat, hugues.fruchet,
	sylvain.petinot, Laurent Pinchart, Sakari Ailus

Hi Dave,

Thank you for your review.

On 10/03/2022 16:38, Dave Stevenson wrote:
> Hi Benjamin
> 
> cc Laurent and Sakari as maintainers of video-interfaces.yaml
> 
> On Thu, 10 Mar 2022 at 13:37, Benjamin Mugnier
> <benjamin.mugnier@foss.st.com> wrote:
>>
>> Add device tree binding for the ST VGXY61 camera sensor, and update
>> MAINTAINERS file.
>>
>> Signed-off-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com>
>> ---
>>  .../bindings/media/i2c/st,st-vgxy61.yaml      | 134 ++++++++++++++++++
>>  MAINTAINERS                                   |  10 ++
>>  2 files changed, 144 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml b/Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml
>> new file mode 100644
>> index 000000000000..8740ed2623e4
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml
>> @@ -0,0 +1,134 @@
>> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
>> +# Copyright (c) 2022 STMicroelectronics SA.
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/media/i2c/st,st-vgxy61.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: STMicroelectronics VGxy61 HDR Global Shutter Sensor Family Device Tree Bindings
>> +
>> +maintainers:
>> +  - Benjamin Mugnier <benjamin.mugnier@foss.st.com>
>> +  - Sylvain Petinot <sylvain.petinot@foss.st.com>
>> +
>> +description: |-
>> +  STMicroelectronics VGxy61 family has a CSI-2 output port. CSI-2 output is a
>> +  quad lanes 800Mbps per lane.
>> +  Supported formats are RAW8, RAW10, RAW12, RAW14 and RAW16.
>> +  Following part number are supported
>> +  - VG5661 and VG6661 are 1.6 Mpx (1464 x 1104) monochrome and color sensors.
>> +  Maximum frame rate is 75 fps.
>> +  - VG5761 and VG6761 are 2.3 Mpx (1944 x 1204) monochrome and color sensors.
>> +  Maximum frame rate is 60 fps.
>> +
>> +properties:
>> +  compatible:
>> +    const: st,st-vgxy61
>> +
>> +  reg:
>> +    maxItems: 1
>> +
>> +  clocks:
>> +    maxItems: 1
>> +
>> +  clock-names:
>> +    description:
>> +      Input clock for the sensor.
>> +    items:
>> +      - const: xclk
>> +
>> +  VCORE-supply:
>> +    description:
>> +      Sensor digital core supply. Must be 1.2 volts.
>> +
>> +  VDDIO-supply:
>> +    description:
>> +      Sensor digital IO supply. Must be 1.8 volts.
>> +
>> +  VANA-supply:
>> +    description:
>> +      Sensor analog supply. Must be 2.8 volts.
>> +
>> +  reset-gpios:
>> +    description:
>> +      Reference to the GPIO connected to the reset pin, if any.
>> +      This is an active low signal to the vgxy61.
>> +
>> +  invert-gpios-polarity:
>> +    description:
>> +      If gpios polarity should be inversed
> 
> s/inversed/inverted
> 

Ok.

>> +    type: boolean
>> +
>> +  slave-mode:
>> +    description:
>> +      If the sensor operates in slave mode
>> +    type: boolean
> 
> This is one I've been meaning to raise for a while.
> Is DT the correct place to be configuring hardware sync options for
> image sensors? (There may be the linguistic discussions over master /
> slave terminology too).
> We also have IMX477 and a number of other sensors that support
> external sync control of some form.
> 
> As I see it, there are nominally 3 settings - disabled (reduces EMC
> noise), generate syncs, and receive syncs.
> For test purposes it would be useful to be able to switch between
> generate and receive modes at runtime, so that would make it a control
> instead of being fixed in DT.
> 
> If it should be configured in DT, then how does ACPI need to handle it?
> 
> If DT is the correct place to define the role, should it be in
> video-interfaces.yaml as an optional property, instead of being a
> sensor specific binding?
> 
> Sorry, more questions rather than answers.
> 
>   Dave

Maybe I can provide additional info on this sensor to help find an answer.
The "slave mode" has 2 settings: enabled or disabled. If disabled you are in master mode ('generate sync' and 'disabled' modes Dave mentionned, they are the same here), and if enabled you are in slave mode ('receive sync').
As you said he master sends frame sync signals to the slave each frame acquired, this allows both sensors to synchronize themselves.

I put this in the device tree as we only use it for 3D stereocam boards which already have 2 sensors on them, meaning this is hardware specific. I don't have any use case where we manually wire 2 sensors on 2 separate boards.
One good point you mentioned is that I may not always want run this board in master/slaver, and both sensors could run on master mode without interacting with each other, thus justifying a dedicated v4l2 control.

Any ideas on how to name it instead of "slave mode" for coherency between sensors?


Regard,

Benjamin

> 
>> +    #TODO check all this or copy from elsewhere

Just noticed this and will remove it.

>> +  port:
>> +    $ref: /schemas/graph.yaml#/$defs/port-base
>> +    additionalProperties: false
>> +
>> +    properties:
>> +      endpoint:
>> +        $ref: /schemas/media/video-interfaces.yaml#
>> +        unevaluatedProperties: false
>> +
>> +        properties:
>> +          clock-lane:
>> +            description:
>> +              Clock lane index
>> +            maxItems: 1
>> +
>> +          data-lanes:
>> +            description:
>> +              CSI lanes to use
>> +            items:
>> +              - const: 1
>> +              - const: 2
>> +              - const: 3
>> +              - const: 4
>> +
>> +          remote-endpoint: true
>> +
>> +        required:
>> +          - clock-lane
>> +          - data-lanes
>> +          - remote-endpoint
>> +
>> +required:
>> +  - compatible
>> +  - clocks
>> +  - clock-names
>> +  - VCORE-supply
>> +  - VDDIO-supply
>> +  - VANA-supply
>> +
>> +additionalProperties: false
>> +
>> +examples:
>> +  - |
>> +    #include <dt-bindings/gpio/gpio.h>
>> +    i2c {
>> +        #address-cells = <1>;
>> +        #size-cells = <0>;
>> +        vgxy61: csi2tx@10 {
>> +            compatible = "st,st-vgxy61";
>> +            reg = <0x10>;
>> +            status = "okay";
>> +            clocks = <&clk_ext_camera>;
>> +            clock-names = "xclk";
>> +            VCORE-supply = <&v1v2>;
>> +            VDDIO-supply = <&v1v8>;
>> +            VANA-supply = <&v2v8>;
>> +            reset-gpios = <&mfxgpio 18 GPIO_ACTIVE_LOW>;
>> +            port {
>> +                ep0: endpoint {
>> +                    clock-lane = <0>;
>> +                    data-lanes = <1 2 3 4>;
>> +                    remote-endpoint = <&mipi_csi2_out>;
>> +                };
>> +            };
>> +        };
>> +    };
>> +...
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 83d27b57016f..f358d15f68a0 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -18297,6 +18297,16 @@ S:     Maintained
>>  F:     Documentation/hwmon/stpddc60.rst
>>  F:     drivers/hwmon/pmbus/stpddc60.c
>>
>> +ST VGXY61 DRIVER
>> +M:     Benjamin Mugnier <benjamin.mugnier@foss.st.com>
>> +M:     Sylvain Petinot <sylvain.petinot@foss.st.com>
>> +L:     linux-media@vger.kernel.org
>> +S:     Maintained
>> +T:     git git://linuxtv.org/media_tree.git
>> +F:     Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.txt
>> +F:     drivers/media/i2c/st-vgxy61.c
>> +
>> +
>>  ST VL53L0X ToF RANGER(I2C) IIO DRIVER
>>  M:     Song Qiang <songqiang1304521@gmail.com>
>>  L:     linux-iio@vger.kernel.org
>> --
>> 2.25.1
>>

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

* Re: [PATCH 2/2] media: i2c: Add driver for ST VGXY61 camera sensor
  2022-03-11 10:56       ` Benjamin Mugnier
@ 2022-03-11 11:57         ` Dave Stevenson
  2022-03-14  9:33           ` Benjamin Mugnier
  0 siblings, 1 reply; 17+ messages in thread
From: Dave Stevenson @ 2022-03-11 11:57 UTC (permalink / raw)
  To: Benjamin Mugnier
  Cc: Kieran Bingham, Linux Media Mailing List, alain.volmat,
	hugues.fruchet, sylvain.petinot

Hi Benjamin

On Fri, 11 Mar 2022 at 10:56, Benjamin Mugnier
<benjamin.mugnier@foss.st.com> wrote:
>
> Hi Dave,
>
> Thanks for your review. Here are my comments and some questions.
>
> On 10/03/2022 16:21, Dave Stevenson wrote:
> > Hi Benjamin and Kieran
> >
> > On Thu, 10 Mar 2022 at 13:52, Kieran Bingham
> > <kieran.bingham@ideasonboard.com> wrote:
> >>
> >> Hi Benjamin,
> >>
> >> Thank you for the patch -
> >>
> >> Quoting Benjamin Mugnier (2022-03-10 13:32:55)
> >>> The VGXY61 has a quad lanes CSI-2 output port running at 800mbps per
> >>> lane, and supports RAW8, RAW10, RAW12, RAW14 and RAW16 formats.
> >>> The driver handles both sensor types:
> >>> - VG5661 and VG6661: 1.6 Mpx (1464 x 1104) 75fps.
> >>> - VG5761 and VG6761: 2.3 Mpx (1944 x 1204) 60 fps.
> >>> The driver supports:
> >>> - HDR linearize mode, HDR substraction mode, and no HDR
> >>> - GPIOs LEDs strobing
> >>> - Digital binning and analog subsampling
> >>> - Horizontal and vertical flip
> >>> - Manual exposure
> >>> - Analog and digital gains
> >>> - Test patterns
> >>
> >> I haven't reviewed the driver directly yet, but I have a script which
> >> looks for key requirements for libcamera.
> >> (https://git.linuxtv.org/libcamera.git/tree/Documentation/sensor_driver_requirements.rst)
> >>
> >>
> >> Media Controller Support:
> >>  - V4L2_SUBDEV_FL_HAS_DEVNODE      : found
> >>  - MEDIA_ENT_F_CAM_SENSOR          : found
> >>
> >> Mandatory Controls:
> >>  - V4L2_CID_EXPOSURE               : found
> >>  - V4L2_CID_HBLANK                 : --------
> >>  - V4L2_CID_VBLANK                 : --------
> >>  - V4L2_CID_PIXEL_RATE             : found
> >>
> >> Selection Controls (will become mandatory):
> >>  - V4L2_SEL_TGT_CROP_DEFAULT       : --------
> >>  - V4L2_SEL_TGT_CROP               : --------
> >>  - V4L2_SEL_TGT_CROP_BOUNDS        : --------
> >>  - .get_selection                  : --------
> >>
> >> Optional Controls:
> >>  - V4L2_CID_TEST_PATTERN           : found
> >>  - V4L2_CID_GAIN                   : --------
> >>  - V4L2_CID_ANALOGUE_GAIN          : found
> >>  - V4L2_CID_CAMERA_SENSOR_ROTATION : --------
> >>  - V4L2_CID_CAMERA_ORIENTATION     : --------
> >>
> >>
> >> The key missing pieces are HBLANK/VBLANK and the .get_selection API. Is
> >> it easy/feasible to add these?
> >
> > It's documented that HBLANK/VBLANK are the correct parameters to use
> > for raw image sensors [1].
> >
> > It looks like register DEVICE_FRAME_LENGTH is mode->height +
> > V4L2_CID_VBLANK, and DEVICE_LINE_LENGTH is mode->width +
> > V4L2_CID_HBLANK (appears to be treated as read only)
> > Most other sensors will adjust the max value of V4L2_CID_EXPOSURE
> > based on the frame length.
> >
> > [1] https://www.kernel.org/doc/html/latest/driver-api/media/camera-sensor.html#frame-interval-configuration
> >
> > I've only done a quick review on the rest.
> >
> >> --
> >> Regards
> >>
> >> Kieran
> >>
> >>
> >>>
> >>> Signed-off-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com>
> >>> ---
> >>>  drivers/media/i2c/Kconfig     |   11 +
> >>>  drivers/media/i2c/Makefile    |    1 +
> >>>  drivers/media/i2c/st-vgxy61.c | 1919 +++++++++++++++++++++++++++++++++
> >>>  3 files changed, 1931 insertions(+)
> >>>  create mode 100644 drivers/media/i2c/st-vgxy61.c
> >>>
> >>> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> >>> index fe66093b8849..e6e9c1f19c09 100644
> >>> --- a/drivers/media/i2c/Kconfig
> >>> +++ b/drivers/media/i2c/Kconfig
> >>> @@ -1454,6 +1454,17 @@ config VIDEO_S5C73M3
> >>>           This is a V4L2 sensor driver for Samsung S5C73M3
> >>>           8 Mpixel camera.
> >>>
> >>> +config VIDEO_ST_VGXY61
> >>> +       tristate "ST VGXY61 sensor support"
> >>> +       depends on OF
> >>> +       depends on GPIOLIB && VIDEO_V4L2 && I2C
> >>> +       select MEDIA_CONTROLLER
> >>> +       select VIDEO_V4L2_SUBDEV_API
> >>> +       select V4L2_FWNODE
> >>> +       help
> >>> +         This is a Video4Linux2 sensor driver for the ST VGXY61
> >>> +         camera sensor.
> >>> +
> >>>  endmenu
> >>>
> >>>  menu "Lens drivers"
> >>> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> >>> index f6b80ef6f41d..0190b5edfa24 100644
> >>> --- a/drivers/media/i2c/Makefile
> >>> +++ b/drivers/media/i2c/Makefile
> >>> @@ -138,4 +138,5 @@ obj-$(CONFIG_VIDEO_MAX9271_LIB)     += max9271.o
> >>>  obj-$(CONFIG_VIDEO_RDACM20)    += rdacm20.o
> >>>  obj-$(CONFIG_VIDEO_RDACM21)    += rdacm21.o
> >>>  obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o
> >>> +obj-$(CONFIG_VIDEO_ST_VGXY61)  += st-vgxy61.o
> >>>  obj-$(CONFIG_SDR_MAX2175) += max2175.o
> >>> diff --git a/drivers/media/i2c/st-vgxy61.c b/drivers/media/i2c/st-vgxy61.c
> >>> new file mode 100644
> >>> index 000000000000..d3d816189f86
> >>> --- /dev/null
> >>> +++ b/drivers/media/i2c/st-vgxy61.c
> >>> @@ -0,0 +1,1919 @@
> >>> +// SPDX-License-Identifier: GPL-2.0
> >>> +/*
> >>> + * Driver for VGXY61 global shutter sensor family driver
> >>> + *
> >>> + * Copyright (C) 2022 STMicroelectronics SA
> >>> + */
> >>> +
> >>> +#include <linux/clk.h>
> >>> +#include <linux/delay.h>
> >>> +#include <linux/gpio/consumer.h>
> >>> +#include <linux/i2c.h>
> >>> +#include <linux/module.h>
> >>> +#include <linux/regulator/consumer.h>
> >>> +#include <linux/units.h>
> >>> +#include <linux/iopoll.h>
> >>> +#include <media/mipi-csi2.h>
> >>> +#include <media/v4l2-async.h>
> >>> +#include <media/v4l2-ctrls.h>
> >>> +#include <media/v4l2-device.h>
> >>> +#include <media/v4l2-fwnode.h>
> >>> +#include <media/v4l2-subdev.h>
> >>> +
> >>> +#define US_PER_MS                                      1000
> >>> +
> >>> +#define DEVICE_MODEL_ID_REG                            0x0000
> >>> +#define VG5661_MODEL_ID                                        0x5661
> >>> +#define VG5761_MODEL_ID                                        0x5761
> >>> +#define VGX661_WIDTH                                   1464
> >>> +#define VGX661_HEIGHT                                  1104
> >>> +#define VGX761_WIDTH                                   1944
> >>> +#define VGX761_HEIGHT                                  1204
> >>> +#define VGX661_DEFAULT_MODE                            1
> >>> +#define VGX761_DEFAULT_MODE                            1
> >>> +#define VGX661_SHORT_ROT_TERM                          93
> >>> +#define VGX761_SHORT_ROT_TERM                          90
> >>> +#define VGXY61_EXPOS_ROT_TERM                          66
> >>> +#define DEVICE_REVISION                                        0x0002
> >>> +#define DEVICE_FWPATCH_REVISION                                0x0014
> >>> +#define DEVICE_FWPATCH_START_ADDR                      0x2000
> >>> +#define DEVICE_SYSTEM_FSM                              0x0020
> >>> +#define SW_STBY                                                0x03
> >>> +#define STREAMING                                      0x04
> >>> +#define DEVICE_NVM                                     0x0023
> >>> +#define NVM_OK                                         0x04
> >>> +#define DEVICE_THSENS1_TEMPERATURE                     0x0044
> >>> +#define DEVICE_STBY                                    0x0201
> >>> +#define STBY_NO_REQ                                    0
> >>> +#define STBY_REQ_TMP_READ                              BIT(2)
> >>> +#define DEVICE_STREAMING                               0x0202
> >>> +#define REQ_NO_REQUEST                                 0
> >>> +#define REQ_STOP_STREAMING                             BIT(0)
> >>> +#define REQ_START_STREAMING                            BIT(1)
> >>> +#define DEVICE_EXT_CLOCK                               0x0220
> >>> +#define DEVICE_CLK_PLL_PREDIV                          0x0224
> >>> +#define DEVICE_CLK_SYS_PLL_MULT                                0x0225
> >>> +#define DEVICE_GPIO_0_CTRL                             0x0236
> >>> +#define DEVICE_GPIO_1_CTRL                             0x0237
> >>> +#define DEVICE_GPIO_2_CTRL                             0x0238
> >>> +#define DEVICE_GPIO_3_CTRL                             0x0239
> >>> +#define DEVICE_SIGNALS_POLARITY_CTRL                   0x023b
> >>> +#define DEVICE_LINE_LENGTH                             0x0300
> >>> +#define DEVICE_ORIENTATION                             0x0302
> >>> +#define DEVICE_VT_CTRL                                 0x0304
> >>> +#define DEVICE_FORMAT_CTRL                             0x0305
> >>> +#define DEVICE_OIF_CTRL                                        0x0306
> >>> +#define DEVICE_OIF_ROI0_CTRL                           0x030a
> >>> +#define DEVICE_ROI0_START_H                            0x0400
> >>> +#define DEVICE_ROI0_START_V                            0x0402
> >>> +#define DEVICE_ROI0_END_H                              0x0404
> >>> +#define DEVICE_ROI0_END_V                              0x0406
> >>> +#define DEVICE_PATGEN_CTRL                             0x0440
> >>> +#define DEVICE_FRAME_CONTENT_CTRL                      0x0478
> >>> +#define DEVICE_COARSE_EXPOSURE_LONG                    0x0500
> >>> +#define DEVICE_COARSE_EXPOSURE_SHORT                   0x0504
> >>> +#define DEVICE_ANALOG_GAIN                             0x0508
> >>> +#define DEVICE_DIGITAL_GAIN_LONG                       0x050a
> >>> +#define DEVICE_DIGITAL_GAIN_SHORT                      0x0512
> >>> +#define DEVICE_FRAME_LENGTH                            0x051a
> >>> +#define DEVICE_SIGNALS_CTRL                            0x0522
> >>> +#define DEVICE_STROBE_LONG_START_DELAY                 0x0528
> >>> +#define DEVICE_STROBE_LONG_END_DELAY                   0x052a
> >>> +#define DEVICE_STROBE_SHORT_START_DELAY                        0x052c
> >>> +#define DEVICE_STROBE_SHORT_END_DELAY                  0x052e
> >>> +#define DEVICE_READOUT_CTRL                            0x0530
> >>> +#define DEVICE_HDR_CTRL                                        0x0532
> >>> +#define DEVICE_PATGEN_LONG_DATA_GR                     0x092c
> >>> +#define DEVICE_PATGEN_LONG_DATA_R                      0x092e
> >>> +#define DEVICE_PATGEN_LONG_DATA_B                      0x0930
> >>> +#define DEVICE_PATGEN_LONG_DATA_GB                     0x0932
> >>> +#define DEVICE_PATGEN_SHORT_DATA_GR                    0x0950
> >>> +#define DEVICE_PATGEN_SHORT_DATA_R                     0x0952
> >>> +#define DEVICE_PATGEN_SHORT_DATA_B                     0x0954
> >>> +#define DEVICE_PATGEN_SHORT_DATA_GB                    0x0956
> >>> +#define DEVICE_BYPASS_CTRL                             0x0a60
> >>> +
> >>> +#define V4L2_CID_HDR                           (V4L2_CID_USER_BASE | 0x1004)
> >>> +#define V4L2_CID_GPIOS_STROBE_LONG_START_DELAY (V4L2_CID_USER_BASE | 0x1019)
> >>> +#define V4L2_CID_GPIOS_STROBE_LONG_END_DELAY   (V4L2_CID_USER_BASE | 0x101a)
> >>> +#define V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY        (V4L2_CID_USER_BASE | 0x101b)
> >>> +#define V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY  (V4L2_CID_USER_BASE | 0x101c)
> >>> +#define V4L2_CID_TEMPERATURE                   (V4L2_CID_USER_BASE | 0x1020)
> >
> > The strobe and temperature controls are equally valid for other
> > sensors, so IMHO ought to be added as standard controls.
> >
>
> Agreed for the temperature, I will add it as a standard control in v2.
> As for the strobing I am not sure, these are controls to set the strobing delay for long exposure and short exposure (if in HDR mode), not to enable or disable the strobbing. I could not find anything like this in the codebase so I guess this is pretty unique to this sensor?

I'm not sure I follow what the controls are doing then. "GPIOs LEDs
strobing" and the name V4L2_CID_GPIOS_STROBE_* implies to me that it
is an output GPIO that is asserted during the short/long exposure
period with an optional delay.
Many sensors have a GPIO that is asserted during the exposure period,
often with an optional delay. Whilst they don't necessarily support
HDR and therefore have two outputs, there are going to be a number
with commonality.
External GPIO control is another area that seems to be under-served
within V4L2. A bit of discussion to sort a generic set of controls
would be useful.

If they are custom to your sensor, then you really want to define a
block of CID values for your sensor in
https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/v4l2-controls.h#L152
to avoid something else using the same numbers.

Unless they are totally critical to the function of this sensor, you
could drop them for this initial driver, and then the discussion about
the correct way to support them can come later.

> >>> +
> >>> +#define DEVICE_FWPATCH_REVISION_MAJOR                  2
> >>> +#define DEVICE_FWPATCH_REVISION_MINOR                  0
> >>> +#define DEVICE_FWPATCH_REVISION_MICRO                  5
> >>> +
> >>> +#define WRITE_MULTIPLE_CHUNK_MAX                       16
> >>> +#define NB_GPIOS                                       4
> >>> +#define NB_POLARITIES                                  5
> >>> +
> >>> +static const u8 patch_array[] = {
> >>> +       0xbf, 0x00, 0x05, 0x20, 0x06, 0x01, 0xe0, 0xe0, 0x04, 0x80, 0xe6, 0x45,
> >>> +       0xed, 0x6f, 0xfe, 0xff, 0x14, 0x80, 0x1f, 0x84, 0x10, 0x42, 0x05, 0x7c,
> >>> +       0x01, 0xc4, 0x1e, 0x80, 0xb6, 0x42, 0x00, 0xe0, 0x1e, 0x82, 0x1e, 0xc0,
> >>> +       0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa, 0x86, 0x0d, 0x70, 0xe1,
> >>> +       0x04, 0x98, 0x15, 0x00, 0x28, 0xe0, 0x14, 0x02, 0x08, 0xfc, 0x15, 0x40,
> >>> +       0x28, 0xe0, 0x98, 0x58, 0xe0, 0xef, 0x04, 0x98, 0x0e, 0x04, 0x00, 0xf0,
> >>> +       0x15, 0x00, 0x28, 0xe0, 0x19, 0xc8, 0x15, 0x40, 0x28, 0xe0, 0xc6, 0x41,
> >>> +       0xfc, 0xe0, 0x14, 0x80, 0x1f, 0x84, 0x14, 0x02, 0xa0, 0xfc, 0x1e, 0x80,
> >>> +       0x14, 0x80, 0x14, 0x02, 0x80, 0xfb, 0x14, 0x02, 0xe0, 0xfc, 0x1e, 0x80,
> >>> +       0x14, 0xc0, 0x1f, 0x84, 0x14, 0x02, 0xa4, 0xfc, 0x1e, 0xc0, 0x14, 0xc0,
> >>> +       0x14, 0x02, 0x80, 0xfb, 0x14, 0x02, 0xe4, 0xfc, 0x1e, 0xc0, 0x0c, 0x0c,
> >>> +       0x00, 0xf2, 0x93, 0xdd, 0x86, 0x00, 0xf8, 0xe0, 0x04, 0x80, 0xc6, 0x03,
> >>> +       0x70, 0xe1, 0x0e, 0x84, 0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa,
> >>> +       0x6b, 0x80, 0x06, 0x40, 0x6c, 0xe1, 0x04, 0x80, 0x09, 0x00, 0xe0, 0xe0,
> >>> +       0x0b, 0xa1, 0x95, 0x84, 0x05, 0x0c, 0x1c, 0xe0, 0x86, 0x02, 0xf9, 0x60,
> >>> +       0xe0, 0xcf, 0x78, 0x6e, 0x80, 0xef, 0x25, 0x0c, 0x18, 0xe0, 0x05, 0x4c,
> >>> +       0x1c, 0xe0, 0x86, 0x02, 0xf9, 0x60, 0xe0, 0xcf, 0x0b, 0x84, 0xd8, 0x6d,
> >>> +       0x80, 0xef, 0x05, 0x4c, 0x18, 0xe0, 0x04, 0xd8, 0x0b, 0xa5, 0x95, 0x84,
> >>> +       0x05, 0x0c, 0x2c, 0xe0, 0x06, 0x02, 0x01, 0x60, 0xe0, 0xce, 0x18, 0x6d,
> >>> +       0x80, 0xef, 0x25, 0x0c, 0x30, 0xe0, 0x05, 0x4c, 0x2c, 0xe0, 0x06, 0x02,
> >>> +       0x01, 0x60, 0xe0, 0xce, 0x0b, 0x84, 0x78, 0x6c, 0x80, 0xef, 0x05, 0x4c,
> >>> +       0x30, 0xe0, 0x0c, 0x0c, 0x00, 0xf2, 0x93, 0xdd, 0x46, 0x01, 0x70, 0xe1,
> >>> +       0x08, 0x80, 0x0b, 0xa1, 0x08, 0x5c, 0x00, 0xda, 0x06, 0x01, 0x68, 0xe1,
> >>> +       0x04, 0x80, 0x4a, 0x40, 0x84, 0xe0, 0x08, 0x5c, 0x00, 0x9a, 0x06, 0x01,
> >>> +       0xe0, 0xe0, 0x04, 0x80, 0x15, 0x00, 0x60, 0xe0, 0x19, 0xc4, 0x15, 0x40,
> >>> +       0x60, 0xe0, 0x15, 0x00, 0x78, 0xe0, 0x19, 0xc4, 0x15, 0x40, 0x78, 0xe0,
> >>> +       0x93, 0xdd, 0xc3, 0xc1, 0x46, 0x01, 0x70, 0xe1, 0x08, 0x80, 0x0b, 0xa1,
> >>> +       0x08, 0x5c, 0x00, 0xda, 0x06, 0x01, 0x68, 0xe1, 0x04, 0x80, 0x4a, 0x40,
> >>> +       0x84, 0xe0, 0x08, 0x5c, 0x00, 0x9a, 0x06, 0x01, 0xe0, 0xe0, 0x14, 0x80,
> >>> +       0x25, 0x02, 0x54, 0xe0, 0x29, 0xc4, 0x25, 0x42, 0x54, 0xe0, 0x24, 0x80,
> >>> +       0x35, 0x04, 0x6c, 0xe0, 0x39, 0xc4, 0x35, 0x44, 0x6c, 0xe0, 0x25, 0x02,
> >>> +       0x64, 0xe0, 0x29, 0xc4, 0x25, 0x42, 0x64, 0xe0, 0x04, 0x80, 0x15, 0x00,
> >>> +       0x7c, 0xe0, 0x19, 0xc4, 0x15, 0x40, 0x7c, 0xe0, 0x93, 0xdd, 0xc3, 0xc1,
> >>> +       0x4c, 0x04, 0x7c, 0xfa, 0x86, 0x40, 0x98, 0xe0, 0x14, 0x80, 0x1b, 0xa1,
> >>> +       0x06, 0x00, 0x00, 0xc0, 0x08, 0x42, 0x38, 0xdc, 0x08, 0x64, 0xa0, 0xef,
> >>> +       0x86, 0x42, 0x3c, 0xe0, 0x68, 0x49, 0x80, 0xef, 0x6b, 0x80, 0x78, 0x53,
> >>> +       0xc8, 0xef, 0xc6, 0x54, 0x6c, 0xe1, 0x7b, 0x80, 0xb5, 0x14, 0x0c, 0xf8,
> >>> +       0x05, 0x14, 0x14, 0xf8, 0x1a, 0xac, 0x8a, 0x80, 0x0b, 0x90, 0x38, 0x55,
> >>> +       0x80, 0xef, 0x1a, 0xae, 0x17, 0xc2, 0x03, 0x82, 0x88, 0x65, 0x80, 0xef,
> >>> +       0x1b, 0x80, 0x0b, 0x8e, 0x68, 0x65, 0x80, 0xef, 0x9b, 0x80, 0x0b, 0x8c,
> >>> +       0x08, 0x65, 0x80, 0xef, 0x6b, 0x80, 0x0b, 0x92, 0x1b, 0x8c, 0x98, 0x64,
> >>> +       0x80, 0xef, 0x1a, 0xec, 0x9b, 0x80, 0x0b, 0x90, 0x95, 0x54, 0x10, 0xe0,
> >>> +       0xa8, 0x53, 0x80, 0xef, 0x1a, 0xee, 0x17, 0xc2, 0x03, 0x82, 0xf8, 0x63,
> >>> +       0x80, 0xef, 0x1b, 0x80, 0x0b, 0x8e, 0xd8, 0x63, 0x80, 0xef, 0x1b, 0x8c,
> >>> +       0x68, 0x63, 0x80, 0xef, 0x6b, 0x80, 0x0b, 0x92, 0x65, 0x54, 0x14, 0xe0,
> >>> +       0x08, 0x65, 0x84, 0xef, 0x68, 0x63, 0x80, 0xef, 0x7b, 0x80, 0x0b, 0x8c,
> >>> +       0xa8, 0x64, 0x84, 0xef, 0x08, 0x63, 0x80, 0xef, 0x14, 0xe8, 0x46, 0x44,
> >>> +       0x94, 0xe1, 0x24, 0x88, 0x4a, 0x4e, 0x04, 0xe0, 0x14, 0xea, 0x1a, 0x04,
> >>> +       0x08, 0xe0, 0x0a, 0x40, 0x84, 0xed, 0x0c, 0x04, 0x00, 0xe2, 0x4a, 0x40,
> >>> +       0x04, 0xe0, 0x19, 0x16, 0xc0, 0xe0, 0x0a, 0x40, 0x84, 0xed, 0x21, 0x54,
> >>> +       0x60, 0xe0, 0x0c, 0x04, 0x00, 0xe2, 0x1b, 0xa5, 0x0e, 0xea, 0x01, 0x89,
> >>> +       0x21, 0x54, 0x64, 0xe0, 0x7e, 0xe8, 0x65, 0x82, 0x1b, 0xa7, 0x26, 0x00,
> >>> +       0x00, 0x80, 0xa5, 0x82, 0x1b, 0xa9, 0x65, 0x82, 0x1b, 0xa3, 0x01, 0x85,
> >>> +       0x16, 0x00, 0x00, 0xc0, 0x01, 0x54, 0x04, 0xf8, 0x06, 0xaa, 0x01, 0x83,
> >>> +       0x06, 0xa8, 0x65, 0x81, 0x06, 0xa8, 0x01, 0x54, 0x04, 0xf8, 0x01, 0x83,
> >>> +       0x06, 0xaa, 0x09, 0x14, 0x18, 0xf8, 0x0b, 0xa1, 0x05, 0x84, 0xc6, 0x42,
> >>> +       0xd4, 0xe0, 0x14, 0x84, 0x01, 0x83, 0x01, 0x54, 0x60, 0xe0, 0x01, 0x54,
> >>> +       0x64, 0xe0, 0x0b, 0x02, 0x90, 0xe0, 0x10, 0x02, 0x90, 0xe5, 0x01, 0x54,
> >>> +       0x88, 0xe0, 0xb5, 0x81, 0xc6, 0x40, 0xd4, 0xe0, 0x14, 0x80, 0x0b, 0x02,
> >>> +       0xe0, 0xe4, 0x10, 0x02, 0x31, 0x66, 0x02, 0xc0, 0x01, 0x54, 0x88, 0xe0,
> >>> +       0x1a, 0x84, 0x29, 0x14, 0x10, 0xe0, 0x1c, 0xaa, 0x2b, 0xa1, 0xf5, 0x82,
> >>> +       0x25, 0x14, 0x10, 0xf8, 0x2b, 0x04, 0xa8, 0xe0, 0x20, 0x44, 0x0d, 0x70,
> >>> +       0x03, 0xc0, 0x2b, 0xa1, 0x04, 0x00, 0x80, 0x9a, 0x02, 0x40, 0x84, 0x90,
> >>> +       0x03, 0x54, 0x04, 0x80, 0x4c, 0x0c, 0x7c, 0xf2, 0x93, 0xdd, 0x00, 0x00,
> >>> +       0x02, 0xa9, 0x00, 0x00, 0x64, 0x4a, 0x40, 0x00, 0x08, 0x2d, 0x58, 0xe0,
> >>> +       0xa8, 0x98, 0x40, 0x00, 0x28, 0x07, 0x34, 0xe0, 0x05, 0xb9, 0x00, 0x00,
> >>> +       0x28, 0x00, 0x41, 0x05, 0x88, 0x00, 0x41, 0x3c, 0x98, 0x00, 0x41, 0x52,
> >>> +       0x04, 0x01, 0x41, 0x79, 0x3c, 0x01, 0x41, 0x6a, 0x3d, 0xfe, 0x00, 0x00,
> >>> +};
> >>> +
> >>> +static const char * const vgxy61_test_pattern_menu[] = {
> >>> +       "Disabled",
> >>> +       "Solid",
> >>> +       "Colorbar",
> >>> +       "Gradbar",
> >>> +       "Hgrey",
> >>> +       "Vgrey",
> >>> +       "Dgrey",
> >>> +       "PN28",
> >>> +};
> >>> +
> >>> +static const char * const vgxy61_hdr_modes[] = {
> >>> +       "HDR linearize",
> >>> +       "HDR substraction",
> >>> +       "no HDR",
> >>> +};
> >>> +
> >>> +/* Regulator supplies */
> >>> +static const char * const vgxy61_supply_name[] = {
> >>> +       "VCORE",
> >>> +       "VDDIO",
> >>> +       "VANA",
> >>> +};
> >>> +
> >>> +static const s64 link_freq[] = {
> >>> +       /*
> >>> +        * MIPI output freq is 804Mhz / 2, as it uses both rising edge and falling edges to send
> >>> +        * data
> >>> +        */
> >>> +       402000000ULL
> >>> +};
> >>> +
> >>> +#define VGXY61_NUM_SUPPLIES            ARRAY_SIZE(vgxy61_supply_name)
> >>> +
> >>> +/* Macro to convert index to 8.8 fixed point gain */
> >>> +#define I2FP(i)                                ((u32)(8192.0 / (32 - (i))))
> >>> +/* Array of possibles analog gains in 8.8 fixed point */
> >>> +static const u16 analog_gains[] = {
> >>> +       I2FP(0), I2FP(1), I2FP(2), I2FP(3), I2FP(4), I2FP(5), I2FP(6), I2FP(7), I2FP(8), I2FP(9),
> >>> +       I2FP(10), I2FP(11), I2FP(12), I2FP(13), I2FP(14), I2FP(15), I2FP(16),
> >>> +};
> >>> +
> >>> +enum bin_mode {
> >>> +       BIN_MODE_NORMAL,
> >>> +       BIN_MODE_DIGITAL_X2,
> >>> +       BIN_MODE_DIGITAL_X4,
> >>> +       BIN_MODE_ANALOG_SUB_X2,
> >>> +       BIN_MODE_ANALOG_SUB_X4,
> >>> +};
> >>> +
> >>> +enum hdr {
> >>> +       HDR_LINEAR,
> >>> +       HDR_SUB,
> >>> +       NO_HDR,
> >>> +};
> >>> +
> >>> +enum strobe_modes {
> >>> +       STROBE_DISABLED,
> >>> +       STROBE_LONG,
> >>> +       STROBE_ENABLED,
> >>> +};
> >>> +
> >>> +struct vgxy61_mode_info {
> >>> +       u32 width;
> >>> +       u32 height;
> >>> +       enum bin_mode bin_mode;
> >>> +};
> >>> +
> >>> +static const u32 vgxy61_supported_codes[] = {
> >>> +       MEDIA_BUS_FMT_SGBRG8_1X8,
> >>> +       MEDIA_BUS_FMT_SGBRG10_1X10,
> >>> +       MEDIA_BUS_FMT_SGBRG12_1X12,
> >>> +       MEDIA_BUS_FMT_SGBRG14_1X14,
> >>> +       MEDIA_BUS_FMT_SGBRG16_1X16
> >>> +};
> >>> +
> >>> +const int vgx761_sensor_frame_rates[] = {75, 60, 30, 15, 10, 5, 2};
> >>> +const int vgx661_sensor_frame_rates[] = {60, 30, 15, 10, 5, 2};
> >>> +
> >>> +static const struct vgxy61_mode_info vgx661_mode_data[] = {
> >>> +       {1464, 1104, BIN_MODE_NORMAL},
> >>> +       {1280,  720, BIN_MODE_NORMAL},
> >>> +       { 640,  480, BIN_MODE_DIGITAL_X2},
> >>> +       { 320,  240, BIN_MODE_DIGITAL_X4},
> >>> +};
> >>> +
> >>> +static const struct vgxy61_mode_info vgx761_mode_data[] = {
> >>> +       {1944, 1204, BIN_MODE_NORMAL},
> >>> +       {1920, 1080, BIN_MODE_NORMAL},
> >>> +       {1280,  720, BIN_MODE_NORMAL},
> >>> +       { 640,  480, BIN_MODE_DIGITAL_X2},
> >>> +       { 320,  240, BIN_MODE_DIGITAL_X4},
> >>> +};
> >>> +
> >>> +struct gpios_ctrls {
> >>> +       struct v4l2_ctrl *long_start;
> >>> +       struct v4l2_ctrl *long_end;
> >>> +       struct v4l2_ctrl *short_start;
> >>> +       struct v4l2_ctrl *short_end;
> >>> +};
> >>> +
> >>> +struct vgxy61_ctrls {
> >>> +       struct v4l2_ctrl_handler handler;
> >>> +       struct v4l2_ctrl *exposure;
> >>> +       struct v4l2_ctrl *analog_gain;
> >>> +       struct v4l2_ctrl *digital_gain;
> >>> +       struct gpios_ctrls gpios;
> >>> +       struct v4l2_ctrl *vflip;
> >>> +       struct v4l2_ctrl *hflip;
> >>> +       struct v4l2_ctrl *patgen;
> >>> +       struct v4l2_ctrl *hdr;
> >>> +       struct v4l2_ctrl *pixel_rate;
> >>> +       struct v4l2_ctrl *link_freq;
> >
> > No need to store analog_gain, digital_gain, vflip, hflip, patgen, hdr,
> > or link_freq as they are never programmatically altered. The s_ctrl
> > handler is given the struct v4l2_ctrl, and where you need it again
> > you've copied the value into the main struct vgxy61_dev.
> >
>
> Definetly. I just need to keep the pixel_rate ctrl to change its value in the set_fmt function.
> I will remove all the others, and put the pixel_rate control directly in the vgxy61 structure.
>
> >>> +       struct v4l2_ctrl *temp;
> >>> +};
> >>> +
> >>> +struct vgxy61_dev {
> >>> +       struct i2c_client *i2c_client;
> >>> +       struct v4l2_subdev sd;
> >>> +       struct media_pad pad;
> >>> +       struct regulator_bulk_data supplies[VGXY61_NUM_SUPPLIES];
> >>> +       struct gpio_desc *reset_gpio;
> >>> +       struct clk *xclk;
> >>> +       u32 clk_freq;
> >>> +       int sensor_width;
> >>> +       int sensor_height;
> >>> +       u16 oif_ctrl;
> >>> +       int nb_of_lane;
> >>> +       int data_rate_in_mbps;
> >>> +       int pclk;
> >>> +       u16 line_length;
> >>> +       int rot_term;
> >>> +       bool gpios_polarity;
> >>> +       bool slave_mode;
> >>> +       /* Lock to protect all members below */
> >>> +       struct mutex lock;
> >>> +       struct vgxy61_ctrls ctrls;
> >>> +       bool streaming;
> >>> +       struct v4l2_mbus_framefmt fmt;
> >>> +       const struct vgxy61_mode_info *sensor_modes;
> >>> +       int sensor_modes_nb;
> >>> +       const struct vgxy61_mode_info *current_mode;
> >>> +       const int *sensor_rates;
> >>> +       int sensor_rates_nb;
> >>> +       struct v4l2_fract frame_interval;
> >>> +       bool hflip;
> >>> +       bool vflip;
> >>> +       enum hdr hdr;
> >>> +       int expo_long;
> >>> +       int expo_short;
> >>> +};
> >>> +
> >>> +static u8 get_bpp_by_code(__u32 code)
> >>> +{
> >>> +       switch (code) {
> >>> +       case MEDIA_BUS_FMT_SGBRG8_1X8:
> >>> +               return 8;
> >>> +       case MEDIA_BUS_FMT_SGBRG10_1X10:
> >>> +               return 10;
> >>> +       case MEDIA_BUS_FMT_SGBRG12_1X12:
> >>> +               return 12;
> >>> +       case MEDIA_BUS_FMT_SGBRG14_1X14:
> >>> +               return 14;
> >>> +       case MEDIA_BUS_FMT_SGBRG16_1X16:
> >>> +               return 16;
> >>> +       default:
> >>> +               /* Should never happen */
> >>> +               WARN(1, "Unsupported code %d. default to 8 bpp", code);
> >>> +               return 8;
> >>> +       }
> >>> +}
> >>> +
> >>> +static u8 get_data_type_by_code(__u32 code)
> >>> +{
> >>> +       switch (code) {
> >>> +       case MEDIA_BUS_FMT_SGBRG8_1X8:
> >>> +               return MIPI_CSI2_DT_RAW8;
> >>> +       case MEDIA_BUS_FMT_SGBRG10_1X10:
> >>> +               return MIPI_CSI2_DT_RAW10;
> >>> +       case MEDIA_BUS_FMT_SGBRG12_1X12:
> >>> +               return MIPI_CSI2_DT_RAW12;
> >>> +       case MEDIA_BUS_FMT_SGBRG14_1X14:
> >>> +               return MIPI_CSI2_DT_RAW14;
> >>> +       case MEDIA_BUS_FMT_SGBRG16_1X16:
> >>> +               return MIPI_CSI2_DT_RAW16;
> >>> +       default:
> >>> +               /* Should never happen */
> >>> +               WARN(1, "Unsupported code %d. default to MIPI_CSI2_DT_RAW8 data type", code);
> >>> +               return MIPI_CSI2_DT_RAW8;
> >>> +       }
> >>> +}
> >>> +
> >>> +static void compute_pll_parameters_by_freq(u32 freq, unsigned int *prediv, unsigned int *mult)
> >>> +{
> >>> +       const unsigned int predivs[] = {1, 2, 4};
> >>> +       int i;
> >>> +
> >>> +       /*
> >>> +        * Freq range is [6Mhz-27Mhz] already checked.
> >>> +        * Output of divider should be in [6Mhz-12Mhz[.
> >>> +        */
> >>> +       for (i = 0; i < ARRAY_SIZE(predivs); i++) {
> >>> +               *prediv = predivs[i];
> >>> +               if (freq / *prediv < 12 * HZ_PER_MHZ)
> >>> +                       break;
> >>> +       }
> >>> +       WARN_ON(i == ARRAY_SIZE(predivs));
> >>> +
> >>> +       /*
> >>> +        * Target freq is 804Mhz. Don't change this as it will impact image quality.
> >>> +        */
> >>> +       *mult = ((804 * HZ_PER_MHZ) * (*prediv) + freq / 2) / freq;
> >>> +}
> >>> +
> >>> +static s32 get_pixel_rate(struct vgxy61_dev *sensor)
> >>> +{
> >>> +       return div64_u64((u64)sensor->data_rate_in_mbps * sensor->nb_of_lane,
> >>> +                        get_bpp_by_code(sensor->fmt.code));
> >>> +}
> >>> +
> >>> +static inline struct vgxy61_dev *to_vgxy61_dev(struct v4l2_subdev *sd)
> >>> +{
> >>> +       return container_of(sd, struct vgxy61_dev, sd);
> >>> +}
> >>> +
> >>> +static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl)
> >>> +{
> >>> +       return &container_of(ctrl->handler, struct vgxy61_dev, ctrls.handler)->sd;
> >>> +}
> >>> +
> >>> +static int get_chunk_size(struct vgxy61_dev *sensor)
> >>> +{
> >>> +       struct i2c_adapter *adapter = sensor->i2c_client->adapter;
> >>> +       int max_write_len = WRITE_MULTIPLE_CHUNK_MAX;
> >>> +
> >>> +       if (adapter->quirks && adapter->quirks->max_write_len)
> >>> +               max_write_len = adapter->quirks->max_write_len - 2;
> >>> +
> >>> +       max_write_len = min(max_write_len, WRITE_MULTIPLE_CHUNK_MAX);
> >>> +
> >>> +       return max(max_write_len, 1);
> >>> +}
> >>> +
> >>> +static int vgxy61_read_multiple(struct vgxy61_dev *sensor, u16 reg, u8 *val, int len)
> >>> +{
> >>> +       struct i2c_client *client = sensor->i2c_client;
> >>> +       struct i2c_msg msg[2];
> >>> +       u8 buf[2];
> >>> +       int ret;
> >>> +
> >>> +       buf[0] = reg >> 8;
> >>> +       buf[1] = reg & 0xff;
> >>> +
> >>> +       msg[0].addr = client->addr;
> >>> +       msg[0].flags = client->flags;
> >>> +       msg[0].buf = buf;
> >>> +       msg[0].len = sizeof(buf);
> >>> +
> >>> +       msg[1].addr = client->addr;
> >>> +       msg[1].flags = client->flags | I2C_M_RD;
> >>> +       msg[1].buf = val;
> >>> +       msg[1].len = len;
> >>> +
> >>> +       ret = i2c_transfer(client->adapter, msg, 2);
> >>> +       if (ret < 0) {
> >>> +               dev_dbg(&client->dev, "%s: %x i2c_transfer, reg: %x => %d\n", __func__,
> >>> +                       client->addr, reg, ret);
> >>> +               return ret;
> >>> +       }
> >>> +
> >>> +       return 0;
> >>> +}
> >>> +
> >>> +static inline int vgxy61_read_reg(struct vgxy61_dev *sensor, u16 reg, u8 *val)
> >>> +{
> >>> +       return vgxy61_read_multiple(sensor, reg, val, sizeof(*val));
> >>> +}
> >>> +
> >>> +static inline int vgxy61_read_reg16(struct vgxy61_dev *sensor, u16 reg, u16 *val)
> >>> +{
> >>> +       return vgxy61_read_multiple(sensor, reg, (u8 *)val, sizeof(*val));
> >>> +}
> >>> +
> >>> +static int vgxy61_write_multiple(struct vgxy61_dev *sensor, u16 reg, const u8 *data, int len)
> >>> +{
> >>> +       struct i2c_client *client = sensor->i2c_client;
> >>> +       struct i2c_msg msg;
> >>> +       u8 buf[WRITE_MULTIPLE_CHUNK_MAX + 2];
> >>> +       int i;
> >>> +       int ret;
> >>> +
> >>> +       if (len > WRITE_MULTIPLE_CHUNK_MAX)
> >>> +               return -EINVAL;
> >>> +       buf[0] = reg >> 8;
> >>> +       buf[1] = reg & 0xff;
> >>> +       for (i = 0; i < len; i++)
> >>> +               buf[i + 2] = data[i];
> >>> +
> >>> +       msg.addr = client->addr;
> >>> +       msg.flags = client->flags;
> >>> +       msg.buf = buf;
> >>> +       msg.len = len + 2;
> >>> +
> >>> +       ret = i2c_transfer(client->adapter, &msg, 1);
> >>> +       if (ret < 0) {
> >>> +               dev_dbg(&client->dev, "%s: i2c_transfer, reg: %x => %d\n", __func__, reg, ret);
> >>> +               return ret;
> >>> +       }
> >>> +
> >>> +       return 0;
> >>> +}
> >>> +
> >>> +static int vgxy61_write_array(struct vgxy61_dev *sensor, u16 reg, int nb, const u8 *array)
> >>> +{
> >>> +       const int chunk_size = get_chunk_size(sensor);
> >>> +       int ret;
> >>> +       int sz;
> >>> +
> >>> +       while (nb) {
> >>> +               sz = min(nb, chunk_size);
> >>> +               ret = vgxy61_write_multiple(sensor, reg, array, sz);
> >>> +               if (ret < 0)
> >>> +                       return ret;
> >>> +               nb -= sz;
> >>> +               reg += sz;
> >>> +               array += sz;
> >>> +       }
> >>> +
> >>> +       return 0;
> >>> +}
> >>> +
> >>> +static inline int vgxy61_write_reg(struct vgxy61_dev *sensor, u16 reg, u8 val)
> >>> +{
> >>> +       return vgxy61_write_multiple(sensor, reg, &val, sizeof(val));
> >>> +}
> >>> +
> >>> +static inline int vgxy61_write_reg16(struct vgxy61_dev *sensor, u16 reg, u16 val)
> >>> +{
> >>> +       return vgxy61_write_multiple(sensor, reg, (u8 *)&val, sizeof(val));
> >>> +}
> >>> +
> >>> +static inline int vgxy61_write_reg32(struct vgxy61_dev *sensor, u16 reg, u32 val)
> >>> +{
> >>> +       return vgxy61_write_multiple(sensor, reg, (u8 *)&val, sizeof(val));
> >>> +}
> >>> +
> >>> +static int vgxy61_poll_reg(struct vgxy61_dev *sensor, u16 reg, u8 poll_val)
> >>> +{
> >>> +       const int loop_delay_ms = 10;
> >>> +       const int timeout_ms = 500;
> >>> +       u8 val;
> >>> +       int ret, timeout;
> >>> +
> >>> +       timeout = read_poll_timeout(vgxy61_read_reg, ret, ((ret != 0) || (val == poll_val)),
> >>> +                                   loop_delay_ms * US_PER_MS, timeout_ms * US_PER_MS, false,
> >>> +                                   sensor, reg, &val);
> >>> +       if (timeout)
> >>> +               return timeout;
> >>> +
> >>> +       return ret;
> >>> +}
> >>> +
> >>> +static int vgxy61_wait_state(struct vgxy61_dev *sensor, int state)
> >>> +{
> >>> +       return vgxy61_poll_reg(sensor, DEVICE_SYSTEM_FSM, state);
> >>> +}
> >>> +
> >>> +static int vgxy61_check_bw(struct vgxy61_dev *sensor)
> >>> +{
> >>> +       struct i2c_client *client = sensor->i2c_client;
> >>> +       /* Correction factor for time required between 2 lines */
> >>> +       const int mipi_margin = 1056;
> >>> +       int binning_scale = 1 << sensor->current_mode->bin_mode;
> >>> +       int bpp = get_bpp_by_code(sensor->fmt.code);
> >>> +       int max_bit_per_line;
> >>> +       int bit_per_line;
> >>> +       u64 line_rate;
> >>> +
> >>> +       line_rate = sensor->nb_of_lane * (u64)sensor->data_rate_in_mbps * sensor->line_length;
> >>> +       max_bit_per_line = div64_u64(line_rate, sensor->pclk) - mipi_margin;
> >>> +       bit_per_line = (bpp * sensor->current_mode->width) / binning_scale;
> >>> +
> >>> +       dev_dbg(&client->dev, "max_bit_per_line = %d\n", max_bit_per_line);
> >>> +       dev_dbg(&client->dev, "required bit_per_line = %d\n", bit_per_line);
> >>> +
> >>> +       return bit_per_line > max_bit_per_line ? -EINVAL : 0;
> >>> +}
> >>> +
> >>> +static int apply_exposure(struct vgxy61_dev *sensor)
> >>> +{
> >>> +       struct i2c_client *client = sensor->i2c_client;
> >>> +       int ret;
> >>> +
> >>> +        /* We first set expo to zero to avoid forbidden parameters couple */
> >>> +       ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_SHORT, 0);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_LONG, sensor->expo_long);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_SHORT, sensor->expo_short);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +
> >>> +       dev_dbg(&client->dev, "%s applied expo %d (short: %d)\n", __func__,
> >>> +               sensor->expo_long, sensor->expo_short);
> >>> +
> >>> +       return 0;
> >>> +}
> >>> +
> >>> +static int set_frame_rate(struct vgxy61_dev *sensor)
> >>> +{
> >>> +       u16 frame_length;
> >>> +
> >>> +       frame_length = sensor->pclk / (sensor->line_length * sensor->frame_interval.denominator);
> >>> +
> >>> +       return vgxy61_write_reg16(sensor, DEVICE_FRAME_LENGTH, frame_length);
> >>> +}
> >>> +
> >>> +static int vgxy61_get_regulators(struct vgxy61_dev *sensor)
> >>> +{
> >>> +       int i;
> >>> +
> >>> +       for (i = 0; i < VGXY61_NUM_SUPPLIES; i++)
> >>> +               sensor->supplies[i].supply = vgxy61_supply_name[i];
> >>> +
> >>> +       return devm_regulator_bulk_get(&sensor->i2c_client->dev, VGXY61_NUM_SUPPLIES,
> >>> +                                      sensor->supplies);
> >>> +}
> >>> +
> >>> +static int vgxy61_apply_reset(struct vgxy61_dev *sensor)
> >>> +{
> >>> +       struct i2c_client *client = sensor->i2c_client;
> >>> +
> >>> +       dev_dbg(&client->dev, "%s applied reset\n", __func__);
> >>> +       gpiod_set_value_cansleep(sensor->reset_gpio, 0);
> >>> +       usleep_range(5000, 10000);
> >>> +       gpiod_set_value_cansleep(sensor->reset_gpio, 1);
> >>> +       usleep_range(5000, 10000);
> >>> +       gpiod_set_value_cansleep(sensor->reset_gpio, 0);
> >>> +       usleep_range(40000, 100000);
> >>> +       return vgxy61_wait_state(sensor, SW_STBY);
> >>> +}
> >>> +
> >>> +static int vgxy61_try_fmt_internal(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *fmt,
> >>> +                                  const struct vgxy61_mode_info **new_mode)
> >>> +{
> >>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >>> +       const struct vgxy61_mode_info *mode = sensor->sensor_modes;
> >>> +       unsigned int index;
> >>> +
> >>> +       /* Select code */
> >>> +       for (index = 0; index < ARRAY_SIZE(vgxy61_supported_codes); index++) {
> >>> +               if (vgxy61_supported_codes[index] == fmt->code)
> >>> +                       break;
> >>> +       }
> >>> +       if (index == ARRAY_SIZE(vgxy61_supported_codes))
> >>> +               index = 0;
> >>> +
> >>> +       /* Select size */
> >>> +       do {
> >>> +               if (mode->width <= fmt->width && mode->height <= fmt->height)
> >>> +                       break;
> >>> +       } while ((++mode)->width);
> >>> +       if (!mode->width)
> >>> +               mode--;
> >
> > Use v4l2_find_nearest_size?
> >
>
> Sure.
>
> >>> +
> >>> +       *new_mode = mode;
> >>> +       fmt->code = vgxy61_supported_codes[index];
> >>> +       fmt->width = mode->width;
> >>> +       fmt->height = mode->height;
> >>> +       fmt->colorspace = V4L2_COLORSPACE_SRGB;
> >
> > Is it? Most Bayer sensors are V4L2_COLORSPACE_RAW.
> > What about ycbcr_enc, quantization, and xfer_func?
> >
>
> Yes it should be RAW, but iirc I had some issue with the RAW colorspace. I will test it again in order to change it.
> I will add missing fields too.
>
>
> >>> +       fmt->field = V4L2_FIELD_NONE;
> >>> +
> >>> +       return 0;
> >>> +}
> >>> +
> >>> +static int vgxy61_stream_enable(struct vgxy61_dev *sensor)
> >>> +{
> >>> +       int center_x = sensor->sensor_width / 2;
> >>> +       int center_y = sensor->sensor_height / 2;
> >>> +       int scale = 1 << sensor->current_mode->bin_mode;
> >>> +       int width = sensor->current_mode->width * scale;
> >>> +       int height = sensor->current_mode->height * scale;
> >>> +       int ret;
> >>> +
> >>> +       ret = vgxy61_check_bw(sensor);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +
> >>> +       /* Configure sensor */
> >>> +       ret = vgxy61_write_reg(sensor, DEVICE_FORMAT_CTRL, get_bpp_by_code(sensor->fmt.code));
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       ret = vgxy61_write_reg(sensor, DEVICE_OIF_ROI0_CTRL,
> >>> +                              get_data_type_by_code(sensor->fmt.code));
> >>> +       if (ret)
> >>> +               return ret;
> >>> +
> >>> +       ret = vgxy61_write_reg(sensor, DEVICE_READOUT_CTRL, sensor->current_mode->bin_mode);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_H, center_x - width / 2);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_H, center_x + width / 2 - 1);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_V, center_y - height / 2);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_V, center_y + height / 2 - 1);
> >>> +       if (ret)
> >>> +               return ret;
> >
> > Use the selection API to allow the crop region to be configured?
> > Avoids only supporting centre cropping.
> >
>
> Yes, as we already mentionned in the other thread this is required by libcamera anyway. Two birds one stone.

There are a couple of bits to the selection API.

libcamera's mainly after it for read-only information on the crop
applied by each mode, and the total sensor size. This allows things
like lens shading tables to be appropriately cropped to match the
sensor mode.

There is also a s_selection call, so that could allow totally
free-form cropping of anywhere from the sensor. This isn't a
requirement. I was only observing that you were computing the start
and end values, therefore it seemed like a potential extension.
Many other sensor drivers have a small table of the registers that are
different for each mode rather than computing the registers.

> >>> +
> >>> +       ret = set_frame_rate(sensor);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +
> >>> +       ret = apply_exposure(sensor);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +
> >>> +       /* Start streaming */
> >>> +       ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_START_STREAMING);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +
> >>> +       ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +
> >>> +       ret = vgxy61_wait_state(sensor, STREAMING);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +
> >>> +       return 0;
> >>> +}
> >>> +
> >>> +static int vgxy61_stream_disable(struct vgxy61_dev *sensor)
> >>> +{
> >>> +       int ret;
> >>> +
> >>> +       ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_STOP_STREAMING);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +
> >>> +       ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +
> >>> +       ret = vgxy61_wait_state(sensor, SW_STBY);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +
> >>> +       return 0;
> >>> +}
> >>> +
> >>> +static int vgxy61_s_stream(struct v4l2_subdev *sd, int enable)
> >>> +{
> >>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >>> +       struct i2c_client *client = sensor->i2c_client;
> >>> +       int ret = 0;
> >>> +
> >>> +       mutex_lock(&sensor->lock);
> >>> +       dev_dbg(&client->dev, "%s : requested %d / current = %d\n", __func__, enable,
> >>> +               sensor->streaming);
> >>> +       if (sensor->streaming == enable)
> >>> +               goto out;
> >>> +
> >>> +       ret = enable ? vgxy61_stream_enable(sensor) : vgxy61_stream_disable(sensor);
> >>> +       if (!ret)
> >>> +               sensor->streaming = enable;
> >>> +
> >>> +out:
> >>> +       dev_dbg(&client->dev, "%s current now = %d / %d\n", __func__, sensor->streaming, ret);
> >>> +       mutex_unlock(&sensor->lock);
> >>> +
> >>> +       return ret;
> >>> +}
> >>> +
> >>> +static int vgxy61_g_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *fi)
> >>> +{
> >>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >>> +
> >>> +       mutex_lock(&sensor->lock);
> >>> +       fi->interval = sensor->frame_interval;
> >>> +       mutex_unlock(&sensor->lock);
> >>> +
> >>> +       return 0;
> >>> +}
> >>> +
> >>> +static int vgxy61_s_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *fi)
> >>> +{
> >>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >>> +       struct i2c_client *client = sensor->i2c_client;
> >>> +       u64 req_int, err, min_err = ~0ULL;
> >>> +       u64 test_int;
> >>> +       int i = 0;
> >>> +       int ret;
> >>> +
> >>> +       if (fi->interval.denominator == 0)
> >>> +               return -EINVAL;
> >>> +
> >>> +       mutex_lock(&sensor->lock);
> >>> +
> >>> +       if (sensor->streaming) {
> >>> +               ret = -EBUSY;
> >>> +               goto out;
> >>> +       }
> >>> +
> >>> +       dev_dbg(&client->dev, "%s request %d/%d\n", __func__,
> >>> +               fi->interval.numerator, fi->interval.denominator);
> >>> +       /* Find nearest period */
> >>> +       req_int = div64_u64((u64)(fi->interval.numerator * 10000), fi->interval.denominator);
> >>> +       for (i = 0; i < sensor->sensor_rates_nb; i++) {
> >>> +               test_int = div64_u64((u64)10000, sensor->sensor_rates[i]);
> >>> +               err = abs(test_int - req_int);
> >>> +               if (err < min_err) {
> >>> +                       fi->interval.numerator = 1;
> >>> +                       fi->interval.denominator = sensor->sensor_rates[i];
> >>> +                       min_err = err;
> >>> +               }
> >>> +       }
> >>> +       sensor->frame_interval = fi->interval;
> >>> +       dev_dbg(&client->dev, "%s set     %d/%d\n", __func__,
> >>> +               fi->interval.numerator, fi->interval.denominator);
> >>> +
> >>> +       ret = 0;
> >>> +out:
> >>> +       mutex_unlock(&sensor->lock);
> >>> +
> >>> +       return ret;
> >>> +}
> >>> +
> >>> +static int vgxy61_enum_mbus_code(struct v4l2_subdev *sd,
> >>> +                                struct v4l2_subdev_state *sd_state,
> >>> +                                struct v4l2_subdev_mbus_code_enum *code)
> >>> +{
> >>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >>> +       struct i2c_client *client = sensor->i2c_client;
> >>> +
> >>> +       dev_dbg(&client->dev, "%s probe index %d\n", __func__, code->index);
> >>> +       if (code->index >= ARRAY_SIZE(vgxy61_supported_codes))
> >>> +               return -EINVAL;
> >>> +
> >>> +       code->code = vgxy61_supported_codes[code->index];
> >>> +
> >>> +       return 0;
> >>> +}
> >>> +
> >>> +static int vgxy61_get_fmt(struct v4l2_subdev *sd,
> >>> +                         struct v4l2_subdev_state *sd_state,
> >>> +                         struct v4l2_subdev_format *format)
> >>> +{
> >>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >>> +       struct i2c_client *client = sensor->i2c_client;
> >>> +       struct v4l2_mbus_framefmt *fmt;
> >>> +
> >>> +       dev_dbg(&client->dev, "%s probe %d\n", __func__, format->pad);
> >>> +       dev_dbg(&client->dev, "%s %dx%d\n", __func__, format->format.width, format->format.height);
> >>> +
> >>> +       mutex_lock(&sensor->lock);
> >>> +
> >>> +       if (format->which == V4L2_SUBDEV_FORMAT_TRY)
> >>> +               fmt = v4l2_subdev_get_try_format(&sensor->sd, sd_state, format->pad);
> >>> +       else
> >>> +               fmt = &sensor->fmt;
> >>> +
> >>> +       format->format = *fmt;
> >>> +
> >>> +       mutex_unlock(&sensor->lock);
> >>> +
> >>> +       return 0;
> >>> +}
> >>> +
> >>> +static int vgxy61_set_fmt(struct v4l2_subdev *sd,
> >>> +                         struct v4l2_subdev_state *sd_state,
> >>> +                         struct v4l2_subdev_format *format)
> >>> +{
> >>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >>> +       struct i2c_client *client = sensor->i2c_client;
> >>> +       const struct vgxy61_mode_info *new_mode;
> >>> +       struct v4l2_mbus_framefmt *fmt;
> >>> +       int ret;
> >>> +
> >>> +       dev_dbg(&client->dev, "%s probe %d\n", __func__, format->pad);
> >>> +       dev_dbg(&client->dev, "%s %dx%d\n", __func__, format->format.width, format->format.height);
> >>> +
> >>> +       mutex_lock(&sensor->lock);
> >>> +
> >>> +       if (sensor->streaming) {
> >>> +               ret = -EBUSY;
> >>> +               goto out;
> >>> +       }
> >>> +
> >>> +       /* Find best format */
> >>> +       ret = vgxy61_try_fmt_internal(sd, &format->format, &new_mode);
> >>> +       if (ret)
> >>> +               goto out;
> >>> +
> >>> +       if (format->which == V4L2_SUBDEV_FORMAT_TRY)
> >>> +               fmt = v4l2_subdev_get_try_format(sd, sd_state, 0);
> >>> +       else
> >>> +               fmt = &sensor->fmt;
> >>> +       *fmt = format->format;
> >>> +       sensor->current_mode = new_mode;
> >>> +       /* Update pixel rate control to reflect new mode */
> >>> +       __v4l2_ctrl_s_ctrl_int64(sensor->ctrls.pixel_rate, get_pixel_rate(sensor));
> >>> +
> >>> +out:
> >>> +       mutex_unlock(&sensor->lock);
> >>> +
> >>> +       return ret;
> >>> +}
> >>> +
> >>> +static int vgxy61_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state,
> >>> +                                 struct v4l2_subdev_frame_size_enum *fse)
> >>> +{
> >>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >>> +       struct i2c_client *client = sensor->i2c_client;
> >>> +
> >>> +       dev_dbg(&client->dev, "%s for index %d\n", __func__, fse->index);
> >>> +       if (fse->index >= sensor->sensor_modes_nb)
> >>> +               return -EINVAL;
> >>> +
> >>> +       fse->min_width = sensor->sensor_modes[fse->index].width;
> >>> +       fse->max_width = fse->min_width;
> >>> +       fse->min_height = sensor->sensor_modes[fse->index].height;
> >>> +       fse->max_height = fse->min_height;
> >>> +
> >>> +       return 0;
> >>> +}
> >>> +
> >>> +static int vgxy61_enum_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state,
> >>> +                                     struct v4l2_subdev_frame_interval_enum *fie)
> >>> +{
> >>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >>> +       const struct vgxy61_mode_info *mode = sensor->sensor_modes;
> >>> +
> >>> +       if (fie->index >= sensor->sensor_rates_nb)
> >>> +               return -EINVAL;
> >>> +       do {
> >>> +               if (mode->width == fie->width && mode->height == fie->height)
> >>> +                       break;
> >>> +       } while ((++mode)->width);
> >>> +       if (!mode->width)
> >>> +               return -EINVAL;
> >>> +
> >>> +       fie->interval.numerator = 1;
> >>> +       fie->interval.denominator = sensor->sensor_rates[fie->index];
> >>> +
> >>> +       return 0;
> >>> +}
> >>> +
> >>> +static int vgxy61_update_analog_gain(struct vgxy61_dev *sensor, u32 target)
> >>> +{
> >>> +       struct i2c_client *client = sensor->i2c_client;
> >>> +       unsigned int idx;
> >>> +       int ret;
> >>> +
> >>> +       /* Find smallest analog gains which is above or equal to target gain */
> >>> +       for (idx = 0; idx < ARRAY_SIZE(analog_gains); idx++) {
> >>> +               if (analog_gains[idx] >= target)
> >>> +                       break;
> >>> +       }
> >
> > Why? The units of V4L2_CID_ANALOGUE_GAIN are not specified, so more
> > typically it is a raw register write.
> >
> >
>
> The output analog gain is 32/(32-idx). This code maps a gain to its matching register value.
> The goal is to allow setting the control using a "real world gain" value. I could perform a raw register write ranging from 1 to 16. It would clean the code but would not be representative of the real gain.
> Maybe I am just overthinking it though.

Most other sensors make it a raw register value, and you get a
userspace conversion function, and they do vary between sensors.

In the Raspberry Pi libcamera IPA, you get the cam_helper functions to
provide the conversions to/from gain code
eg https://git.linuxtv.org/libcamera.git/tree/src/ipa/raspberrypi/cam_helper_imx219.cpp#n67
https://git.linuxtv.org/libcamera.git/tree/src/ipa/raspberrypi/cam_helper_ov5647.cpp#n45

> >>> +       /* Cap to maximum gain if no matching gain found */
> >>> +       if (idx == ARRAY_SIZE(analog_gains))
> >>> +               idx--;
> >>> +
> >>> +       /* Apply gain */
> >>> +       ret = vgxy61_write_reg(sensor, DEVICE_ANALOG_GAIN, idx);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +
> >>> +       dev_dbg(&client->dev, "Target analog gain: 0x%04x\n", target);
> >>> +       dev_dbg(&client->dev, "   Set analog gain: 0x%04x\n", analog_gains[idx]);
> >>> +
> >>> +       return 0;
> >>> +}
> >>> +
> >>> +static int vgxy61_update_digital_gain(struct vgxy61_dev *sensor, u32 target)
> >>> +{
> >>> +       struct i2c_client *client = sensor->i2c_client;
> >>> +       int ret;
> >>> +
> >>> +       /*
> >>> +        * For a monochrome version, configuring DIGITAL_GAIN_LONG_CH0 and
> >>> +        * DIGITAL_GAIN_SHORT_CH0 is enough to configure the gain of all
> >>> +        * four sub pixels.
> >>> +        */
> >>> +       ret = vgxy61_write_reg16(sensor, DEVICE_DIGITAL_GAIN_LONG, target);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       ret = vgxy61_write_reg16(sensor, DEVICE_DIGITAL_GAIN_SHORT, target);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +
> >>> +       dev_dbg(&client->dev, "  Set digital gain: 0x%04x\n", target);
> >>> +
> >>> +       return 0;
> >>> +}
> >>> +
> >>> +static int vgxy61_update_patgen(struct vgxy61_dev *sensor, u32 index)
> >>> +{
> >>> +       u32 pattern = index <= 3 ? index : index + 12;
> >>> +       u32 reg;
> >>> +
> >>> +       reg = (pattern << 18) | (pattern << 4);
> >>> +       if (index)
> >>> +               reg |= (1 << 16) | 1;
> >>> +       return vgxy61_write_reg32(sensor, DEVICE_PATGEN_CTRL, reg);
> >>> +}
> >>> +
> >>> +static int vgxy61_update_gpiox_strobe_mode(struct vgxy61_dev *sensor, enum strobe_modes mode,
> >>> +                                          int idx)
> >>> +{
> >>> +       const u8 index2val[] = {0x0, 0x1, 0x3};
> >>> +       u16 reg;
> >>> +       int ret;
> >>> +
> >>> +       ret = vgxy61_read_reg16(sensor, DEVICE_SIGNALS_CTRL, &reg);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       reg &= ~(0xf << (idx * 4));
> >>> +       reg |= index2val[mode] << (idx * 4);
> >>> +
> >>> +       return vgxy61_write_reg16(sensor, DEVICE_SIGNALS_CTRL, reg);
> >>> +}
> >>> +
> >>> +static int vgxy61_update_gpios_strobe_mode(struct vgxy61_dev *sensor, enum hdr hdr)
> >>> +{
> >>> +       enum strobe_modes strobe_mode;
> >>> +       int i, ret;
> >>> +
> >>> +       switch (hdr) {
> >>> +       case HDR_LINEAR:
> >>> +               strobe_mode = STROBE_ENABLED;
> >>> +               break;
> >>> +       case HDR_SUB:
> >>> +       case NO_HDR:
> >>> +               strobe_mode = STROBE_LONG;
> >>> +               break;
> >>> +       default:
> >>> +               /* Should never happen */
> >>> +               WARN_ON(true);
> >>> +               break;
> >>> +       }
> >>> +
> >>> +       for (i = 0; i < NB_GPIOS; i++) {
> >>> +               ret = vgxy61_update_gpiox_strobe_mode(sensor, strobe_mode, i);
> >>> +               if (ret)
> >>> +                       return ret;
> >>> +       }
> >>> +
> >>> +       return 0;
> >>> +}
> >>> +
> >>> +static int vgxy61_update_gpios_strobe_polarity(struct vgxy61_dev *sensor, int polarity)
> >>> +{
> >>> +       struct i2c_client *client = sensor->i2c_client;
> >>> +       int ret;
> >>> +
> >>> +       if (sensor->streaming)
> >>> +               return -EBUSY;
> >>> +
> >>> +       dev_dbg(&client->dev, "setting gpios polarity: %d\n", polarity);
> >>> +
> >>> +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_0_CTRL, polarity << 1);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_1_CTRL, polarity << 1);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_2_CTRL, polarity << 1);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_3_CTRL, polarity << 1);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       return vgxy61_write_reg(sensor, DEVICE_SIGNALS_POLARITY_CTRL, polarity);
> >>> +}
> >>> +
> >>> +static int find_max_long_exposure(struct vgxy61_dev *sensor, int frame_length, int short_expo_ratio)
> >>> +{
> >>> +       int first_rot_max_expo;
> >>> +       int second_rot_max_expo;
> >>> +
> >>> +       /*
> >>> +        * Apply first rule of thumb:
> >>> +        * frame_length < short_line_nb + sensor->sensor_height + sensor->rot_term
> >>> +        * with short_line_nb = long_line_nb / short_expo_ratio
> >>> +        */
> >>> +       first_rot_max_expo = ((frame_length - sensor->sensor_height - sensor->rot_term) *
> >>> +                            short_expo_ratio) - 1;
> >>> +
> >>> +       /*
> >>> +        * Apply second rule of thumb
> >>> +        * frame_length < short_line_nb + long_line_nb + VGXY61_EXPOS_ROT_TERM
> >>> +        * with short_line_nb = long_line_nb / short_expo_ratio
> >>> +        */
> >>> +       second_rot_max_expo = (((frame_length - VGXY61_EXPOS_ROT_TERM) * short_expo_ratio) /
> >>> +                             (short_expo_ratio + 1)) - 1;
> >>> +
> >>> +       /* Take the minimum of both rules */
> >>> +       return min(first_rot_max_expo, second_rot_max_expo);
> >>> +}
> >>> +
> >>> +static int vgxy61_update_exposure(struct vgxy61_dev *sensor, int long_expo_line_nb, enum hdr hdr,
> >>> +                                 bool clamp)
> >>> +{
> >>> +       struct i2c_client *client = sensor->i2c_client;
> >>> +       int max_long_expo;
> >>> +       int max_short_expo = 0;
> >>> +       int short_expo_line_nb = 0;
> >>> +       /* We use a constant ratio of 10 for linear HDR mode */
> >>> +       int hdr_linear_ratio = 10;
> >>> +       u16 frame_length;
> >>> +       int ret;
> >>> +
> >>> +       ret = vgxy61_read_reg16(sensor, DEVICE_FRAME_LENGTH, &frame_length);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +
> >>> +       /* Long and short integration times must not be less than 10 lines */
> >>> +       long_expo_line_nb = max(10, long_expo_line_nb);
> >>> +       long_expo_line_nb = min_t(int, frame_length, long_expo_line_nb);
> >>> +
> >>> +       /* Compute short exposure according to hdr mode and long exposure */
> >>> +       switch (hdr) {
> >>> +       case HDR_LINEAR:
> >>> +               max_long_expo = find_max_long_exposure(sensor, frame_length, hdr_linear_ratio);
> >>> +               max_short_expo = (max_long_expo + (hdr_linear_ratio / 2)) / hdr_linear_ratio;
> >>> +               short_expo_line_nb = (long_expo_line_nb + (hdr_linear_ratio / 2)) /
> >>> +                                    hdr_linear_ratio;
> >>> +               break;
> >>> +       case HDR_SUB:
> >>> +               max_long_expo = find_max_long_exposure(sensor, frame_length, 1);
> >>> +               max_short_expo = max_long_expo;
> >>> +               short_expo_line_nb = long_expo_line_nb;
> >>> +               break;
> >>> +       case NO_HDR:
> >>> +               /*
> >>> +                * As short expo is 0 here, only the second rule of thumb applies, see
> >>> +                * find_max_long_exposure for more
> >>> +                */
> >>> +               max_long_expo = frame_length - VGXY61_EXPOS_ROT_TERM;
> >>> +               break;
> >>> +       default:
> >>> +               /* Should never happen */
> >>> +               WARN_ON(true);
> >>> +               break;
> >>> +       }
> >>> +
> >>> +       if (long_expo_line_nb > max_long_expo) {
> >>> +               if (!clamp) {
> >>> +                       dev_err(&client->dev, "Exposure %d too high (max for this hdr mode %d)\n",
> >>> +                               long_expo_line_nb, max_long_expo);
> >>> +                       return -EINVAL;
> >>> +               }
> >>> +               dev_warn(&client->dev, "Exposure %d too high for this hdr mode, clamping to %d\n",
> >>> +                        long_expo_line_nb, max_long_expo);
> >>> +               long_expo_line_nb = max_long_expo;
> >>> +               short_expo_line_nb = max_short_expo;
> >>> +       }
> >>> +
> >>> +       dev_dbg(&client->dev, "frame_length %d, long_expo_line_nb %d, short_expo_line_nb %d",
> >>> +               frame_length, long_expo_line_nb, short_expo_line_nb);
> >>> +
> >>> +       /* Apply exposure */
> >>> +       sensor->expo_long = long_expo_line_nb;
> >>> +       sensor->expo_short = short_expo_line_nb;
> >>> +
> >>> +       if (sensor->streaming)
> >>> +               return apply_exposure(sensor);
> >>> +       return 0;
> >>> +}
> >>> +
> >>> +static int vgxy61_update_hdr(struct vgxy61_dev *sensor, u32 index)
> >>> +{
> >>> +       const u8 index2val[] = {0x1, 0x4, 0xa};
> >>> +       int ret;
> >>> +
> >>> +       /*
> >>> +        * Short exposure changes according to HDR mode, do it first as it can
> >>> +        * violate sensors 'rule of thumbs' and therefore will require to change
> >>> +        * the long exposure
> >>> +        */
> >>> +       ret = vgxy61_update_exposure(sensor, sensor->expo_long, index, true);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +
> >>> +       /* Update strobe mode according to HDR */
> >>> +       ret = vgxy61_update_gpios_strobe_mode(sensor, index);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +
> >>> +       ret = vgxy61_write_reg(sensor, DEVICE_HDR_CTRL, index2val[index]);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       sensor->hdr = index;
> >>> +
> >>> +       return 0;
> >>> +}
> >>> +
> >>> +static int vgxy61_get_temp_stream_enabled(struct vgxy61_dev *sensor, int *temp)
> >>> +{
> >>> +       int ret;
> >>> +       u16 temperature;
> >>> +
> >>> +       ret = vgxy61_read_reg16(sensor, DEVICE_THSENS1_TEMPERATURE, &temperature);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +
> >>> +       /* Temperature is expressed in Kelvin in Q10.2 fixed point format*/
> >>> +       temperature = (temperature & 0x0fff) >> 2;
> >>> +       temperature = kelvin_to_celsius(temperature);
> >>> +
> >>> +       *temp = temperature;
> >>> +
> >>> +       return 0;
> >>> +}
> >>> +
> >>> +static int vgxy61_get_temp_stream_disabled(struct vgxy61_dev *sensor, int *temp)
> >>> +{
> >>> +       int ret;
> >>> +
> >>> +       /* Device needs to be in standby mode if streaming is off */
> >>> +       ret = vgxy61_write_reg(sensor, DEVICE_STBY, STBY_REQ_TMP_READ);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       ret = vgxy61_poll_reg(sensor, DEVICE_STBY, STBY_NO_REQ);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +
> >>> +       return vgxy61_get_temp_stream_enabled(sensor, temp);
> >>> +}
> >>> +
> >>> +static int vgxy61_get_temp(struct vgxy61_dev *sensor, int *temp)
> >>> +{
> >>> +       *temp = 0;
> >>> +       if (sensor->streaming)
> >>> +               return vgxy61_get_temp_stream_enabled(sensor, temp);
> >>> +       else
> >>> +               return vgxy61_get_temp_stream_disabled(sensor, temp);
> >>> +}
> >>> +
> >>> +static int vgxy61_s_ctrl(struct v4l2_ctrl *ctrl)
> >>> +{
> >>> +       struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
> >>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >>> +       int ret;
> >>> +
> >>> +       switch (ctrl->id) {
> >>> +       case V4L2_CID_EXPOSURE:
> >>> +               ret = vgxy61_update_exposure(sensor, ctrl->val, sensor->hdr, false);
> >>> +               ctrl->val = sensor->expo_long;
> >>> +               break;
> >>> +       case V4L2_CID_ANALOGUE_GAIN:
> >>> +               ret = vgxy61_update_analog_gain(sensor, ctrl->val);
> >>> +               break;
> >>> +       case V4L2_CID_DIGITAL_GAIN:
> >>> +               ret = vgxy61_update_digital_gain(sensor, ctrl->val);
> >>> +               break;
> >>> +       case V4L2_CID_VFLIP:
> >>> +       case V4L2_CID_HFLIP:
> >>> +               if (sensor->streaming) {
> >>> +                       ret = -EBUSY;
> >>> +                       break;
> >>> +               }
> >>> +               if (ctrl->id == V4L2_CID_VFLIP)
> >>> +                       sensor->vflip = ctrl->val;
> >>> +               if (ctrl->id == V4L2_CID_HFLIP)
> >>> +                       sensor->hflip = ctrl->val;
> >>> +               ret = vgxy61_write_reg(sensor, DEVICE_ORIENTATION,
> >>> +                                      sensor->hflip | (sensor->vflip << 1));
> >>> +               break;
> >>> +       case V4L2_CID_TEST_PATTERN:
> >>> +               ret = vgxy61_update_patgen(sensor, ctrl->val);
> >>> +               break;
> >>> +       case V4L2_CID_HDR:
> >>> +               ret = vgxy61_update_hdr(sensor, ctrl->val);
> >>> +               break;
> >>> +       case V4L2_CID_GPIOS_STROBE_LONG_START_DELAY:
> >>> +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_LONG_START_DELAY, ctrl->val);
> >>> +               break;
> >>> +       case V4L2_CID_GPIOS_STROBE_LONG_END_DELAY:
> >>> +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_LONG_END_DELAY, ctrl->val);
> >>> +               break;
> >>> +       case V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY:
> >>> +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_SHORT_START_DELAY, ctrl->val);
> >>> +               break;
> >>> +       case V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY:
> >>> +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_SHORT_END_DELAY, ctrl->val);
> >>> +               break;
> >>> +       default:
> >>> +               ret = -EINVAL;
> >>> +               break;
> >>> +       }
> >>> +
> >>> +       return ret;
> >>> +}
> >>> +
> >>> +static int vgxy61_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
> >>> +{
> >>> +       struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
> >>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >>> +       int temperature;
> >>> +       int ret;
> >>> +
> >>> +       switch (ctrl->id) {
> >>> +       case V4L2_CID_TEMPERATURE:
> >>> +               ret = vgxy61_get_temp(sensor, &temperature);
> >>> +               if (ret)
> >>> +                       break;
> >>> +               ret = __v4l2_ctrl_s_ctrl(ctrl, temperature);
> >
> > Do you need to call __v4l2_ctrl_s_ctrl instead of just updating
> > ctrl->val? There is no s_ctrl handler for the control as you've
> > declared it as read only.
> >
>
> Sure.
>
> >>> +               break;
> >>> +       default:
> >>> +               ret = -EINVAL;
> >>> +               break;
> >>> +       }
> >>> +
> >>> +       return ret;
> >>> +}
> >>> +
> >>> +static const struct v4l2_ctrl_ops vgxy61_ctrl_ops = {
> >>> +       .g_volatile_ctrl = vgxy61_g_volatile_ctrl,
> >>> +       .s_ctrl = vgxy61_s_ctrl,
> >>> +};
> >>> +
> >>> +static const struct v4l2_ctrl_config vgxy61_hdr_ctrl = {
> >>> +       .ops            = &vgxy61_ctrl_ops,
> >>> +       .id             = V4L2_CID_HDR,
> >>> +       .name           = "HDR mode",
> >>> +       .type           = V4L2_CTRL_TYPE_MENU,
> >>> +       .min            = 0,
> >>> +       .max            = ARRAY_SIZE(vgxy61_hdr_modes) - 1,
> >>> +       .def            = NO_HDR,
> >>> +       .qmenu          = vgxy61_hdr_modes,
> >>> +};
> >>> +
> >>> +static const struct v4l2_ctrl_config vgxy61_strobe_long_start_delay = {
> >>> +       .ops            = &vgxy61_ctrl_ops,
> >>> +       .id             = V4L2_CID_GPIOS_STROBE_LONG_START_DELAY,
> >>> +       .name           = "Long strobe mode start delay in lines",
> >>> +       .type           = V4L2_CTRL_TYPE_INTEGER,
> >>> +       .min            = -128,
> >>> +       .max            = 127,
> >>> +       .step           = 1,
> >>> +       .def            = 0,
> >>> +       .flags          = 0,
> >>> +};
> >>> +
> >>> +static const struct v4l2_ctrl_config vgxy61_strobe_long_end_delay = {
> >>> +       .ops            = &vgxy61_ctrl_ops,
> >>> +       .id             = V4L2_CID_GPIOS_STROBE_LONG_END_DELAY,
> >>> +       .name           = "Long strobe mode end delay in lines",
> >>> +       .type           = V4L2_CTRL_TYPE_INTEGER,
> >>> +       .min            = -128,
> >>> +       .max            = 127,
> >>> +       .step           = 1,
> >>> +       .def            = 0,
> >>> +       .flags          = 0,
> >>> +};
> >>> +
> >>> +static const struct v4l2_ctrl_config vgxy61_strobe_short_start_delay = {
> >>> +       .ops            = &vgxy61_ctrl_ops,
> >>> +       .id             = V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY,
> >>> +       .name           = "Short strobe mode start delay in lines",
> >>> +       .type           = V4L2_CTRL_TYPE_INTEGER,
> >>> +       .min            = -128,
> >>> +       .max            = 127,
> >>> +       .step           = 1,
> >>> +       .def            = 0,
> >>> +       .flags          = 0,
> >>> +};
> >>> +
> >>> +static const struct v4l2_ctrl_config vgxy61_strobe_short_end_delay = {
> >>> +       .ops            = &vgxy61_ctrl_ops,
> >>> +       .id             = V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY,
> >>> +       .name           = "Short strobe mode end delay in lines",
> >>> +       .type           = V4L2_CTRL_TYPE_INTEGER,
> >>> +       .min            = -128,
> >>> +       .max            = 127,
> >>> +       .step           = 1,
> >>> +       .def            = 0,
> >>> +       .flags          = 0,
> >>> +};
> >>> +
> >>> +static const struct v4l2_ctrl_config vgxy61_temp_ctrl = {
> >>> +       .ops            = &vgxy61_ctrl_ops,
> >>> +       .id             = V4L2_CID_TEMPERATURE,
> >>> +       .name           = "Temperature in °C",
> >>> +       .type           = V4L2_CTRL_TYPE_INTEGER,
> >>> +       .min            = -128,
> >>> +       .max            = 128,
> >>> +       .step           = 1,
> >>> +};
> >>> +
> >>> +static int vgxy61_init_controls(struct vgxy61_dev *sensor)
> >>> +{
> >>> +       const struct v4l2_ctrl_ops *ops = &vgxy61_ctrl_ops;
> >>> +       struct vgxy61_ctrls *ctrls = &sensor->ctrls;
> >>> +       struct v4l2_ctrl_handler *hdl = &sensor->ctrls.handler;
> >>> +       int ret;
> >>> +
> >>> +       v4l2_ctrl_handler_init(hdl, 16);
> >>> +       /* We can use our own mutex for the ctrl lock */
> >>> +       hdl->lock = &sensor->lock;
> >>> +       ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, 10, 0xffff, 1,
> >>> +                                           sensor->expo_long);
> >>> +       /* This is 8.8 fixed point value */
> >>> +       ctrls->analog_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, 0x3fff, 1,
> >>> +                                              0x0);
> >>> +       /* This is 8.8 fixed point value */
> >>> +       ctrls->digital_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 0, 0xfff, 1,
> >>> +                                               0x100);
> >>> +       ctrls->vflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, sensor->vflip);
> >>> +       ctrls->hflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, sensor->hflip);
> >>> +       ctrls->patgen = v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN,
> >>> +                                                    ARRAY_SIZE(vgxy61_test_pattern_menu) - 1,
> >>> +                                                    0, 0, vgxy61_test_pattern_menu);
> >>> +       ctrls->hdr = v4l2_ctrl_new_custom(hdl, &vgxy61_hdr_ctrl, NULL);
> >>> +       ctrls->pixel_rate = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1,
> >>> +                                             get_pixel_rate(sensor));
> >>> +       ctrls->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> >>> +       ctrls->link_freq = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ,
> >>> +                                                 ARRAY_SIZE(link_freq) - 1, 0, link_freq);
> >>> +       ctrls->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> >>> +       /* Gpios ctrls */
> >>> +       ctrls->gpios.long_start = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_long_start_delay, NULL);
> >>> +       ctrls->gpios.long_end = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_long_end_delay, NULL);
> >>> +       ctrls->gpios.short_start = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_short_start_delay,
> >>> +                                                       NULL);
> >>> +       ctrls->gpios.short_end = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_short_end_delay, NULL);
> >>> +       /* Temperature ctrl */
> >>> +       ctrls->temp = v4l2_ctrl_new_custom(hdl, &vgxy61_temp_ctrl, NULL);
> >>> +       ctrls->temp->flags |= V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY;
> >>> +
> >>> +       if (hdl->error) {
> >>> +               ret = hdl->error;
> >>> +               goto free_ctrls;
> >>> +       }
> >>> +
> >>> +       sensor->sd.ctrl_handler = hdl;
> >>> +       return 0;
> >>> +
> >>> +free_ctrls:
> >>> +       v4l2_ctrl_handler_free(hdl);
> >>> +       return ret;
> >>> +}
> >>> +
> >>> +static const struct v4l2_subdev_core_ops vgxy61_core_ops = {
> >>> +};
> >>> +
> >>> +static const struct v4l2_subdev_video_ops vgxy61_video_ops = {
> >>> +       .s_stream = vgxy61_s_stream,
> >>> +       .g_frame_interval = vgxy61_g_frame_interval,
> >>> +       .s_frame_interval = vgxy61_s_frame_interval,
> >>> +};
> >>> +
> >>> +static const struct v4l2_subdev_pad_ops vgxy61_pad_ops = {
> >>> +       .enum_mbus_code = vgxy61_enum_mbus_code,
> >>> +       .get_fmt = vgxy61_get_fmt,
> >>> +       .set_fmt = vgxy61_set_fmt,
> >>> +       .enum_frame_size = vgxy61_enum_frame_size,
> >>> +       .enum_frame_interval = vgxy61_enum_frame_interval,
> >>> +};
> >>> +
> >>> +static const struct v4l2_subdev_ops vgxy61_subdev_ops = {
> >>> +       .core = &vgxy61_core_ops,
> >>> +       .video = &vgxy61_video_ops,
> >>> +       .pad = &vgxy61_pad_ops,
> >>> +};
> >>> +
> >>> +static const struct media_entity_operations vgxy61_subdev_entity_ops = {
> >>> +       .link_validate = v4l2_subdev_link_validate,
> >>> +};
> >>> +
> >>> +/* Set phy polarities */
> >>> +static int vgxy61_tx_from_ep(struct vgxy61_dev *sensor, struct fwnode_handle *endpoint)
> >>> +{
> >>> +       struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
> >>> +       struct i2c_client *client = sensor->i2c_client;
> >>> +       u32 log2phy[NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
> >>> +       u32 phy2log[NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
> >>> +       int polarities[NB_POLARITIES] = {0, 0, 0, 0, 0};
> >>> +       int l_nb;
> >>> +       int p, l;
> >>> +       int ret;
> >>> +       int i;
> >>> +
> >>> +       ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep);
> >>> +       if (ret)
> >>> +               goto error_alloc;
> >>> +
> >>> +       l_nb = ep.bus.mipi_csi2.num_data_lanes;
> >>> +       if (l_nb != 1 && l_nb != 2 && l_nb != 4) {
> >>> +               dev_err(&client->dev, "invalid data lane number %d\n", l_nb);
> >>> +               goto error_ep;
> >>> +       }
> >>> +
> >>> +       /* Build log2phy, phy2log and polarities from ep info */
> >>> +       log2phy[0] = ep.bus.mipi_csi2.clock_lane;
> >>> +       phy2log[log2phy[0]] = 0;
> >>> +       for (l = 1; l < l_nb + 1; l++) {
> >>> +               log2phy[l] = ep.bus.mipi_csi2.data_lanes[l - 1];
> >>> +               phy2log[log2phy[l]] = l;
> >>> +       }
> >>> +       /*
> >>> +        * Then fill remaining slots for every physical slot to have something valid for hardware
> >>> +        * stuff.
> >>> +        */
> >>> +       for (p = 0; p < NB_POLARITIES; p++) {
> >>> +               if (phy2log[p] != ~0)
> >>> +                       continue;
> >>> +               phy2log[p] = l;
> >>> +               log2phy[l] = p;
> >>> +               l++;
> >>> +       }
> >>> +       for (l = 0; l < l_nb + 1; l++)
> >>> +               polarities[l] = ep.bus.mipi_csi2.lane_polarities[l];
> >>> +
> >>> +       if (log2phy[0] != 0) {
> >>> +               dev_err(&client->dev, "clk lane must be map to physical lane 0\n");
> >>> +               goto error_ep;
> >>> +       }
> >>> +       sensor->oif_ctrl = (polarities[4] << 15) + ((phy2log[4] - 1) << 13) +
> >>> +                          (polarities[3] << 12) + ((phy2log[3] - 1) << 10) +
> >>> +                          (polarities[2] <<  9) + ((phy2log[2] - 1) <<  7) +
> >>> +                          (polarities[1] <<  6) + ((phy2log[1] - 1) <<  4) +
> >>> +                          (polarities[0] <<  3) +
> >>> +                          l_nb;
> >>> +       sensor->nb_of_lane = l_nb;
> >>> +
> >>> +       dev_dbg(&client->dev, "tx uses %d lanes", l_nb);
> >>> +       for (i = 0; i < 5; i++) {
> >>> +               dev_dbg(&client->dev, "log2phy[%d] = %d\n", i, log2phy[i]);
> >>> +               dev_dbg(&client->dev, "phy2log[%d] = %d\n", i, phy2log[i]);
> >>> +               dev_dbg(&client->dev, "polarity[%d] = %d\n", i, polarities[i]);
> >>> +       }
> >>> +       dev_dbg(&client->dev, "oif_ctrl = 0x%04x\n", sensor->oif_ctrl);
> >>> +
> >>> +       v4l2_fwnode_endpoint_free(&ep);
> >>> +
> >>> +       return 0;
> >>> +
> >>> +error_ep:
> >>> +       v4l2_fwnode_endpoint_free(&ep);
> >>> +error_alloc:
> >>> +
> >>> +       return -EINVAL;
> >>> +}
> >>> +
> >>> +static int vgxy61_configure(struct vgxy61_dev *sensor)
> >>> +{
> >>> +       struct i2c_client *client = sensor->i2c_client;
> >>> +       int sensor_freq;
> >>> +       unsigned int prediv;
> >>> +       unsigned int mult;
> >>> +       int ret;
> >>> +
> >>> +       compute_pll_parameters_by_freq(sensor->clk_freq, &prediv, &mult);
> >>> +       sensor_freq = (mult * sensor->clk_freq) / prediv;
> >>> +       /* Frequency to data rate is 1:1 ratio for MIPI */
> >>> +       sensor->data_rate_in_mbps = sensor_freq;
> >>> +       /* Video timing ISP path (pixel clock)  requires 804/5 mhz = 160 mhz */
> >>> +       sensor->pclk = sensor_freq / 5;
> >>> +
> >>> +       /* Cache line_length value */
> >>> +       ret = vgxy61_read_reg16(sensor, DEVICE_LINE_LENGTH, &sensor->line_length);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       /* Configure clocks */
> >>> +       ret = vgxy61_write_reg32(sensor, DEVICE_EXT_CLOCK, sensor->clk_freq);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       ret = vgxy61_write_reg(sensor, DEVICE_CLK_PLL_PREDIV, prediv);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       ret = vgxy61_write_reg(sensor, DEVICE_CLK_SYS_PLL_MULT, mult);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       /* Configure interface */
> >>> +       ret = vgxy61_write_reg16(sensor, DEVICE_OIF_CTRL, sensor->oif_ctrl);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       /* Disable pwm compression */
> >>> +       ret = vgxy61_write_reg(sensor, DEVICE_FRAME_CONTENT_CTRL, 0);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       /* Disable asil lines */
> >>> +       ret = vgxy61_write_reg(sensor, DEVICE_BYPASS_CTRL, 4);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       /* Set gpios polarity according to device tree value */
> >>> +       ret = vgxy61_update_gpios_strobe_polarity(sensor, sensor->gpios_polarity);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       /* HDR mode */
> >>> +       ret = vgxy61_update_hdr(sensor, sensor->hdr);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       /* Slave mode */
> >>> +       ret = vgxy61_write_reg(sensor, DEVICE_VT_CTRL, sensor->slave_mode);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       /* Set pattern generator solid to middle value */
> >>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_GR, 0x800);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_R, 0x800);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_B, 0x800);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_GB, 0x800);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_GR, 0x800);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_R, 0x800);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_B, 0x800);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_GB, 0x800);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +
> >>> +       dev_dbg(&client->dev, "clock prediv = %d\n", prediv);
> >>> +       dev_dbg(&client->dev, "clock mult = %d\n", mult);
> >>> +       dev_dbg(&client->dev, "data rate = %d mbps\n",
> >>> +               sensor->data_rate_in_mbps);
> >>> +
> >>> +       return 0;
> >>> +}
> >>> +
> >>> +static int vgxy61_patch(struct vgxy61_dev *sensor)
> >>> +{
> >>> +       struct i2c_client *client = sensor->i2c_client;
> >>> +       u16 patch;
> >>> +       int ret;
> >>> +
> >>> +       ret = vgxy61_write_array(sensor, DEVICE_FWPATCH_START_ADDR, sizeof(patch_array),
> >>> +                                patch_array);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +
> >>> +       ret = vgxy61_write_reg(sensor, DEVICE_STBY, 0x10);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +
> >>> +       ret = vgxy61_poll_reg(sensor, DEVICE_STBY, 0);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +
> >>> +       ret = vgxy61_read_reg16(sensor, DEVICE_FWPATCH_REVISION, &patch);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +
> >>> +       if (patch != (DEVICE_FWPATCH_REVISION_MAJOR << 12) +
> >>> +                    (DEVICE_FWPATCH_REVISION_MINOR << 8) +
> >>> +                    DEVICE_FWPATCH_REVISION_MICRO) {
> >>> +               dev_err(&client->dev, "bad patch version expected %d.%d.%d got %d.%d.%d\n",
> >>> +                       DEVICE_FWPATCH_REVISION_MAJOR,
> >>> +                       DEVICE_FWPATCH_REVISION_MINOR,
> >>> +                       DEVICE_FWPATCH_REVISION_MICRO,
> >>> +                       patch >> 12, (patch >> 8) & 0x0f, patch & 0xff);
> >>> +               return -ENODEV;
> >>> +       }
> >>> +       dev_dbg(&client->dev, "patch %d.%d.%d applied\n",
> >>> +               patch >> 12, (patch >> 8) & 0x0f, patch & 0xff);
> >>> +
> >>> +       return 0;
> >>> +}
> >>> +
> >>> +static int vgxy61_detect_cut_version(struct vgxy61_dev *sensor)
> >>> +{
> >>> +       struct i2c_client *client = sensor->i2c_client;
> >>> +       u16 device_rev;
> >>> +       int ret;
> >>> +
> >>> +       ret = vgxy61_read_reg16(sensor, DEVICE_REVISION, &device_rev);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +
> >>> +       switch (device_rev >> 8) {
> >>> +       case 0xA:
> >>> +               dev_info(&client->dev, "Cut1 detected\n");
> >>> +               dev_err(&client->dev, "Cut1 not supported by this driver\n");
> >>> +               return -ENODEV;
> >>> +       case 0xB:
> >>> +               dev_info(&client->dev, "Cut2 detected\n");
> >>> +               return 0;
> >>> +       case 0xC:
> >>> +               dev_info(&client->dev, "Cut3 detected\n");
> >>> +               return 0;
> >>> +       default:
> >>> +               dev_err(&client->dev, "Unable to detect cut version\n");
> >>> +               return -ENODEV;
> >>> +       }
> >>> +}
> >>> +
> >>> +static int vgxy61_detect(struct vgxy61_dev *sensor)
> >>> +{
> >>> +       struct i2c_client *client = sensor->i2c_client;
> >>> +       u16 id = 0;
> >>> +       int ret;
> >>> +       u8 st;
> >>> +
> >>> +       ret = vgxy61_read_reg16(sensor, DEVICE_MODEL_ID_REG, &id);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       if (id != VG5661_MODEL_ID && id != VG5761_MODEL_ID) {
> >>> +               dev_warn(&client->dev, "Unsupported sensor id %x\n", id);
> >>> +               return -ENODEV;
> >>> +       }
> >>> +       dev_dbg(&client->dev, "detected sensor id = 0x%04x\n", id);
> >>> +
> >>> +       if (id == VG5761_MODEL_ID) {
> >>> +               sensor->sensor_width = VGX761_WIDTH;
> >>> +               sensor->sensor_height = VGX761_HEIGHT;
> >>> +               sensor->sensor_modes = vgx761_mode_data;
> >>> +               sensor->sensor_modes_nb = ARRAY_SIZE(vgx761_mode_data);
> >>> +               sensor->current_mode = &vgx761_mode_data[VGX761_DEFAULT_MODE];
> >>> +               sensor->rot_term = VGX761_SHORT_ROT_TERM;
> >>> +               sensor->sensor_rates = vgx761_sensor_frame_rates;
> >>> +               sensor->sensor_rates_nb = ARRAY_SIZE(vgx761_sensor_frame_rates);
> >>> +       } else if (id == VG5661_MODEL_ID) {
> >>> +               sensor->sensor_width = VGX661_WIDTH;
> >>> +               sensor->sensor_height = VGX661_HEIGHT;
> >>> +               sensor->sensor_modes = vgx661_mode_data;
> >>> +               sensor->sensor_modes_nb = ARRAY_SIZE(vgx661_mode_data);
> >>> +               sensor->current_mode = &vgx661_mode_data[VGX661_DEFAULT_MODE];
> >>> +               sensor->rot_term = VGX661_SHORT_ROT_TERM;
> >>> +               sensor->sensor_rates = vgx661_sensor_frame_rates;
> >>> +               sensor->sensor_rates_nb = ARRAY_SIZE(vgx661_sensor_frame_rates);
> >>> +       }
> >>> +
> >>> +       ret = vgxy61_wait_state(sensor, SW_STBY);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +
> >>> +       ret = vgxy61_read_reg(sensor, DEVICE_NVM, &st);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +       if (st != NVM_OK)
> >>> +               dev_warn(&client->dev, "Bad nvm state got %d\n", st);
> >>> +
> >>> +       /* Detect cut version */
> >>> +       ret = vgxy61_detect_cut_version(sensor);
> >>> +       if (ret)
> >>> +               return ret;
> >>> +
> >>> +       return 0;
> >>> +}
> >>> +
> >>> +static int vgxy61_probe(struct i2c_client *client)
> >>> +{
> >>> +       struct device *dev = &client->dev;
> >>> +       struct fwnode_handle *endpoint;
> >>> +       struct vgxy61_dev *sensor;
> >>> +       int ret;
> >>> +
> >>> +       sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
> >>> +       if (!sensor)
> >>> +               return -ENOMEM;
> >>> +
> >>> +       sensor->i2c_client = client;
> >>> +       sensor->streaming = false;
> >>> +       sensor->fmt.code = MEDIA_BUS_FMT_SGBRG8_1X8;
> >>> +       sensor->fmt.field = V4L2_FIELD_NONE;
> >>> +       sensor->fmt.colorspace = V4L2_COLORSPACE_SRGB;
> >
> > ycbcr_enc, quantization, and xfer_func too.
> >
>
> Ok.
>
> >>> +       sensor->frame_interval.numerator = 1;
> >>> +       sensor->frame_interval.denominator = 60;
> >>> +       sensor->hdr = NO_HDR;
> >>> +       sensor->expo_long = 200;
> >>> +       sensor->expo_short = 0;
> >>> +       sensor->hflip = false;
> >>> +       sensor->vflip = false;
> >>> +
> >>> +       endpoint = fwnode_graph_get_next_endpoint(of_fwnode_handle(dev->of_node), NULL);
> >>> +       if (!endpoint) {
> >>> +               dev_err(dev, "endpoint node not found\n");
> >>> +               return -EINVAL;
> >>> +       }
> >>> +
> >>> +       ret = vgxy61_tx_from_ep(sensor, endpoint);
> >>> +       fwnode_handle_put(endpoint);
> >>> +       if (ret) {
> >>> +               dev_err(dev, "Failed to parse endpoint %d\n", ret);
> >>> +               return ret;
> >>> +       }
> >>> +
> >>> +       sensor->xclk = devm_clk_get(dev, "xclk");
> >>> +       if (IS_ERR(sensor->xclk)) {
> >>> +               dev_err(dev, "failed to get xclk\n");
> >>> +               return PTR_ERR(sensor->xclk);
> >>> +       }
> >>> +       sensor->clk_freq = clk_get_rate(sensor->xclk);
> >>> +       if (sensor->clk_freq < 6000000 || sensor->clk_freq > 27000000) {
> >>> +               dev_err(dev, "Only 6Mhz-27Mhz clock range supported. provide %d Hz\n",
> >>> +                       sensor->clk_freq);
> >>> +               return -EINVAL;
> >>> +       }
> >>> +       sensor->gpios_polarity = of_property_read_bool(dev->of_node, "invert-gpios-polarity");
> >>> +       sensor->slave_mode = of_property_read_bool(dev->of_node, "slave-mode");
> >>> +
> >>> +       v4l2_i2c_subdev_init(&sensor->sd, client, &vgxy61_subdev_ops);
> >>> +       sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> >>> +       sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
> >>> +       sensor->sd.entity.ops = &vgxy61_subdev_entity_ops;
> >>> +       sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
> >>> +
> >>> +       ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
> >>> +       if (ret) {
> >>> +               dev_err(&client->dev, "pads init failed %d\n", ret);
> >>> +               return ret;
> >>> +       }
> >>> +
> >>> +       /* Request optional reset pin */
> >>> +       sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
> >>> +
> >>> +       ret = vgxy61_get_regulators(sensor);
> >>> +       if (ret) {
> >>> +               dev_err(&client->dev, "failed to get regulators %d\n", ret);
> >>> +               goto entity_cleanup;
> >>> +       }
> >>> +
> >>> +       ret = regulator_bulk_enable(VGXY61_NUM_SUPPLIES, sensor->supplies);
> >>> +       if (ret) {
> >>> +               dev_err(&client->dev, "failed to enable regulators %d\n", ret);
> >>> +               goto entity_cleanup;
> >>> +       }
> >>> +
> >>> +       ret = clk_prepare_enable(sensor->xclk);
> >>> +       if (ret) {
> >>> +               dev_err(&client->dev, "failed to enable clock %d\n", ret);
> >>> +               goto disable_bulk;
> >>> +       }
> >>> +
> >>> +       mutex_init(&sensor->lock);
> >>> +
> >>> +       /* Apply reset sequence */
> >>> +       if (sensor->reset_gpio) {
> >>> +               ret = vgxy61_apply_reset(sensor);
> >>> +               if (ret) {
> >>> +                       dev_err(&client->dev, "sensor reset failed %d\n", ret);
> >>> +                       goto disable_clock;
> >>> +               }
> >>> +       }
> >>> +
> >>> +       ret = vgxy61_detect(sensor);
> >>> +       if (ret) {
> >>> +               dev_err(&client->dev, "sensor detect failed %d\n", ret);
> >>> +               goto disable_clock;
> >>> +       }
> >>> +       sensor->fmt.width = sensor->current_mode->width;
> >>> +       sensor->fmt.height = sensor->current_mode->height;
> >>> +
> >>> +       ret = vgxy61_patch(sensor);
> >>> +       if (ret) {
> >>> +               dev_err(&client->dev, "sensor patch failed %d\n", ret);
> >>> +               goto disable_clock;
> >>> +       }
> >>> +
> >>> +       ret = vgxy61_configure(sensor);
> >>> +       if (ret) {
> >>> +               dev_err(&client->dev, "sensor configuration failed %d\n", ret);
> >>> +               goto disable_clock;
> >>> +       }
> >>> +
> >>> +       ret = vgxy61_init_controls(sensor);
> >>> +       if (ret) {
> >>> +               dev_err(&client->dev, "controls initialization failed %d\n", ret);
> >>> +               goto disable_clock;
> >>> +       }
> >>> +
> >>> +       ret = v4l2_async_register_subdev(&sensor->sd);
> >>> +       if (ret) {
> >>> +               dev_err(&client->dev, "async subdev register failed %d\n", ret);
> >>> +               goto disable_clock;
> >>> +       }
> >>> +
> >>> +       dev_info(&client->dev, "vgxy61 probe successfully\n");
> >>> +
> >>> +       return 0;
> >>> +
> >>> +disable_clock:
> >>> +       clk_disable_unprepare(sensor->xclk);
> >>> +disable_bulk:
> >>> +       regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
> >>> +entity_cleanup:
> >>> +       mutex_destroy(&sensor->lock);
> >>> +       media_entity_cleanup(&sensor->sd.entity);
> >>> +       return ret;
> >>> +}
> >>> +
> >>> +static int vgxy61_remove(struct i2c_client *client)
> >>> +{
> >>> +       struct v4l2_subdev *sd = i2c_get_clientdata(client);
> >>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >>> +
> >>> +       v4l2_async_unregister_subdev(&sensor->sd);
> >>> +       clk_disable_unprepare(sensor->xclk);
> >>> +       mutex_destroy(&sensor->lock);
> >>> +       media_entity_cleanup(&sensor->sd.entity);
> >>> +       regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
> >>> +
> >>> +       return 0;
> >>> +}
> >>> +
> >>> +static const struct of_device_id vgxy61_dt_ids[] = {
> >>> +       { .compatible = "st,st-vgxy61" },
> >>> +       { /* sentinel */ }
> >>> +};
> >>> +MODULE_DEVICE_TABLE(of, vgxy61_dt_ids);
> >>> +
> >>> +static struct i2c_driver vgxy61_i2c_driver = {
> >>> +       .driver = {
> >>> +               .name  = "st-vgxy61",
> >>> +               .of_match_table = vgxy61_dt_ids,
> >
> > Support for pm_runtime? Then again the sensor appears to be powered up
> > in probe and never powered down again.
> >
> >   Dave
> >
>
> The power management is handled by the internal CPU. When in SW_STBY the consumption is as its lowest.
> Therefore sensor does not provide any power up/down register access, only the optional reset pin you saw in the probe.
> I am not familiar with pm_runtime yet, I think it is not necessary in this case.

It sounds like a more complex sensor than many. Certainly with that
binary blob to program you don't want to have to send that more often
than necessary. It just looks a little odd requesting all the
regulators and clock, and then just enabling them permanently.
I don't claim to be a pm_runtime expert, just noting that many sensors
make use of it.

Cheers.
  Dave

> >>> +       },
> >>> +       .probe_new = vgxy61_probe,
> >>> +       .remove = vgxy61_remove,
> >>> +};
> >>> +
> >>> +module_i2c_driver(vgxy61_i2c_driver);
> >>> +
> >>> +MODULE_AUTHOR("Benjamin Mugnier <benjamin.mugnier@foss.st.com>");
> >>> +MODULE_AUTHOR("Mickael Guene <mickael.guene@st.com>");
> >>> +MODULE_AUTHOR("Sylvain Petinot <sylvain.petinot@foss.st.com>");
> >>> +MODULE_DESCRIPTION("VGXY61 camera subdev driver");
> >>> +MODULE_LICENSE("GPL v2");
> >>> --
> >>> 2.25.1
> >>>

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

* Re: [PATCH 1/2] media: dt-bindings: media: i2c: Add ST VGXY61 camera sensor binding
  2022-03-11 11:25     ` Benjamin Mugnier
@ 2022-03-11 13:13       ` Sakari Ailus
  2022-03-13 14:53         ` Laurent Pinchart
  0 siblings, 1 reply; 17+ messages in thread
From: Sakari Ailus @ 2022-03-11 13:13 UTC (permalink / raw)
  To: Benjamin Mugnier
  Cc: Dave Stevenson, Linux Media Mailing List, alain.volmat,
	hugues.fruchet, sylvain.petinot, Laurent Pinchart

Hi Benjamin,

On Fri, Mar 11, 2022 at 12:25:38PM +0100, Benjamin Mugnier wrote:
> Hi Dave,
> 
> Thank you for your review.
> 
> On 10/03/2022 16:38, Dave Stevenson wrote:
> > Hi Benjamin
> > 
> > cc Laurent and Sakari as maintainers of video-interfaces.yaml
> > 
> > On Thu, 10 Mar 2022 at 13:37, Benjamin Mugnier
> > <benjamin.mugnier@foss.st.com> wrote:
> >>
> >> Add device tree binding for the ST VGXY61 camera sensor, and update
> >> MAINTAINERS file.
> >>
> >> Signed-off-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com>
> >> ---
> >>  .../bindings/media/i2c/st,st-vgxy61.yaml      | 134 ++++++++++++++++++
> >>  MAINTAINERS                                   |  10 ++
> >>  2 files changed, 144 insertions(+)
> >>  create mode 100644 Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml
> >>
> >> diff --git a/Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml b/Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml
> >> new file mode 100644
> >> index 000000000000..8740ed2623e4
> >> --- /dev/null
> >> +++ b/Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml
> >> @@ -0,0 +1,134 @@
> >> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> >> +# Copyright (c) 2022 STMicroelectronics SA.
> >> +%YAML 1.2
> >> +---
> >> +$id: http://devicetree.org/schemas/media/i2c/st,st-vgxy61.yaml#
> >> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> >> +
> >> +title: STMicroelectronics VGxy61 HDR Global Shutter Sensor Family Device Tree Bindings
> >> +
> >> +maintainers:
> >> +  - Benjamin Mugnier <benjamin.mugnier@foss.st.com>
> >> +  - Sylvain Petinot <sylvain.petinot@foss.st.com>
> >> +
> >> +description: |-
> >> +  STMicroelectronics VGxy61 family has a CSI-2 output port. CSI-2 output is a
> >> +  quad lanes 800Mbps per lane.
> >> +  Supported formats are RAW8, RAW10, RAW12, RAW14 and RAW16.
> >> +  Following part number are supported
> >> +  - VG5661 and VG6661 are 1.6 Mpx (1464 x 1104) monochrome and color sensors.
> >> +  Maximum frame rate is 75 fps.
> >> +  - VG5761 and VG6761 are 2.3 Mpx (1944 x 1204) monochrome and color sensors.
> >> +  Maximum frame rate is 60 fps.
> >> +
> >> +properties:
> >> +  compatible:
> >> +    const: st,st-vgxy61
> >> +
> >> +  reg:
> >> +    maxItems: 1
> >> +
> >> +  clocks:
> >> +    maxItems: 1
> >> +
> >> +  clock-names:
> >> +    description:
> >> +      Input clock for the sensor.
> >> +    items:
> >> +      - const: xclk

Do you need this if you have a single clock?

Also see Documentation/driver-api/media/camera-sensor.rst .

> >> +
> >> +  VCORE-supply:
> >> +    description:
> >> +      Sensor digital core supply. Must be 1.2 volts.
> >> +
> >> +  VDDIO-supply:
> >> +    description:
> >> +      Sensor digital IO supply. Must be 1.8 volts.
> >> +
> >> +  VANA-supply:
> >> +    description:
> >> +      Sensor analog supply. Must be 2.8 volts.
> >> +
> >> +  reset-gpios:
> >> +    description:
> >> +      Reference to the GPIO connected to the reset pin, if any.
> >> +      This is an active low signal to the vgxy61.
> >> +
> >> +  invert-gpios-polarity:
> >> +    description:
> >> +      If gpios polarity should be inversed
> > 
> > s/inversed/inverted
> > 
> 
> Ok.
> 
> >> +    type: boolean
> >> +
> >> +  slave-mode:
> >> +    description:
> >> +      If the sensor operates in slave mode
> >> +    type: boolean
> > 
> > This is one I've been meaning to raise for a while.
> > Is DT the correct place to be configuring hardware sync options for
> > image sensors? (There may be the linguistic discussions over master /
> > slave terminology too).
> > We also have IMX477 and a number of other sensors that support
> > external sync control of some form.
> > 
> > As I see it, there are nominally 3 settings - disabled (reduces EMC
> > noise), generate syncs, and receive syncs.
> > For test purposes it would be useful to be able to switch between
> > generate and receive modes at runtime, so that would make it a control
> > instead of being fixed in DT.
> > 
> > If it should be configured in DT, then how does ACPI need to handle it?
> > 
> > If DT is the correct place to define the role, should it be in
> > video-interfaces.yaml as an optional property, instead of being a
> > sensor specific binding?
> > 
> > Sorry, more questions rather than answers.
> > 
> >   Dave
> 
> Maybe I can provide additional info on this sensor to help find an
> answer. The "slave mode" has 2 settings: enabled or disabled. If disabled
> you are in master mode ('generate sync' and 'disabled' modes Dave
> mentionned, they are the same here), and if enabled you are in slave mode
> ('receive sync'). As you said he master sends frame sync signals to the
> slave each frame acquired, this allows both sensors to synchronize
> themselves.
> 
> I put this in the device tree as we only use it for 3D stereocam boards
> which already have 2 sensors on them, meaning this is hardware specific.
> I don't have any use case where we manually wire 2 sensors on 2 separate
> boards. One good point you mentioned is that I may not always want run
> this board in master/slaver, and both sensors could run on master mode
> without interacting with each other, thus justifying a dedicated v4l2
> control.
> 
> Any ideas on how to name it instead of "slave mode" for coherency between
> sensors?

How is this wired? The slave-mode property documentation explicitly refers
to synchronisation signals that do not exists in CSI-2.

> 
> 
> Regard,
> 
> Benjamin
> 
> > 
> >> +    #TODO check all this or copy from elsewhere
> 
> Just noticed this and will remove it.
> 
> >> +  port:
> >> +    $ref: /schemas/graph.yaml#/$defs/port-base
> >> +    additionalProperties: false
> >> +
> >> +    properties:
> >> +      endpoint:
> >> +        $ref: /schemas/media/video-interfaces.yaml#
> >> +        unevaluatedProperties: false
> >> +
> >> +        properties:
> >> +          clock-lane:
> >> +            description:
> >> +              Clock lane index
> >> +            maxItems: 1

Does the device support lane reordering? If not, please drop.

> >> +
> >> +          data-lanes:
> >> +            description:
> >> +              CSI lanes to use
> >> +            items:
> >> +              - const: 1
> >> +              - const: 2
> >> +              - const: 3
> >> +              - const: 4

Which lane configurations does the device support? If it's four lanes only,
then you can drop this property, too.

> >> +
> >> +          remote-endpoint: true
> >> +
> >> +        required:
> >> +          - clock-lane
> >> +          - data-lanes
> >> +          - remote-endpoint

Listing remote-endpoint here isn't needed as this comes from the schema.

> >> +
> >> +required:
> >> +  - compatible
> >> +  - clocks
> >> +  - clock-names
> >> +  - VCORE-supply
> >> +  - VDDIO-supply
> >> +  - VANA-supply
> >> +
> >> +additionalProperties: false
> >> +
> >> +examples:
> >> +  - |
> >> +    #include <dt-bindings/gpio/gpio.h>
> >> +    i2c {
> >> +        #address-cells = <1>;
> >> +        #size-cells = <0>;
> >> +        vgxy61: csi2tx@10 {
> >> +            compatible = "st,st-vgxy61";
> >> +            reg = <0x10>;
> >> +            status = "okay";
> >> +            clocks = <&clk_ext_camera>;
> >> +            clock-names = "xclk";
> >> +            VCORE-supply = <&v1v2>;
> >> +            VDDIO-supply = <&v1v8>;
> >> +            VANA-supply = <&v2v8>;
> >> +            reset-gpios = <&mfxgpio 18 GPIO_ACTIVE_LOW>;
> >> +            port {
> >> +                ep0: endpoint {
> >> +                    clock-lane = <0>;
> >> +                    data-lanes = <1 2 3 4>;
> >> +                    remote-endpoint = <&mipi_csi2_out>;
> >> +                };
> >> +            };
> >> +        };
> >> +    };
> >> +...
> >> diff --git a/MAINTAINERS b/MAINTAINERS
> >> index 83d27b57016f..f358d15f68a0 100644
> >> --- a/MAINTAINERS
> >> +++ b/MAINTAINERS
> >> @@ -18297,6 +18297,16 @@ S:     Maintained
> >>  F:     Documentation/hwmon/stpddc60.rst
> >>  F:     drivers/hwmon/pmbus/stpddc60.c
> >>
> >> +ST VGXY61 DRIVER
> >> +M:     Benjamin Mugnier <benjamin.mugnier@foss.st.com>
> >> +M:     Sylvain Petinot <sylvain.petinot@foss.st.com>
> >> +L:     linux-media@vger.kernel.org
> >> +S:     Maintained
> >> +T:     git git://linuxtv.org/media_tree.git
> >> +F:     Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.txt
> >> +F:     drivers/media/i2c/st-vgxy61.c
> >> +

Extra newline.

> >> +
> >>  ST VL53L0X ToF RANGER(I2C) IIO DRIVER
> >>  M:     Song Qiang <songqiang1304521@gmail.com>
> >>  L:     linux-iio@vger.kernel.org
> >> --
> >> 2.25.1
> >>

-- 
Kind regards,

Sakari Ailus

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

* Re: [PATCH 1/2] media: dt-bindings: media: i2c: Add ST VGXY61 camera sensor binding
  2022-03-11 13:13       ` Sakari Ailus
@ 2022-03-13 14:53         ` Laurent Pinchart
  2022-03-14  9:41           ` Benjamin Mugnier
  0 siblings, 1 reply; 17+ messages in thread
From: Laurent Pinchart @ 2022-03-13 14:53 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: Benjamin Mugnier, Dave Stevenson, Linux Media Mailing List,
	alain.volmat, hugues.fruchet, sylvain.petinot

Hello,

On Fri, Mar 11, 2022 at 03:13:11PM +0200, Sakari Ailus wrote:
> On Fri, Mar 11, 2022 at 12:25:38PM +0100, Benjamin Mugnier wrote:
> > On 10/03/2022 16:38, Dave Stevenson wrote:
> > > On Thu, 10 Mar 2022 at 13:37, Benjamin Mugnier wrote:
> > >>
> > >> Add device tree binding for the ST VGXY61 camera sensor, and update
> > >> MAINTAINERS file.
> > >>
> > >> Signed-off-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com>
> > >> ---
> > >>  .../bindings/media/i2c/st,st-vgxy61.yaml      | 134 ++++++++++++++++++
> > >>  MAINTAINERS                                   |  10 ++
> > >>  2 files changed, 144 insertions(+)
> > >>  create mode 100644 Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml
> > >>
> > >> diff --git a/Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml b/Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml
> > >> new file mode 100644
> > >> index 000000000000..8740ed2623e4
> > >> --- /dev/null
> > >> +++ b/Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml
> > >> @@ -0,0 +1,134 @@
> > >> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> > >> +# Copyright (c) 2022 STMicroelectronics SA.
> > >> +%YAML 1.2
> > >> +---
> > >> +$id: http://devicetree.org/schemas/media/i2c/st,st-vgxy61.yaml#
> > >> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > >> +
> > >> +title: STMicroelectronics VGxy61 HDR Global Shutter Sensor Family Device Tree Bindings
> > >> +
> > >> +maintainers:
> > >> +  - Benjamin Mugnier <benjamin.mugnier@foss.st.com>
> > >> +  - Sylvain Petinot <sylvain.petinot@foss.st.com>
> > >> +
> > >> +description: |-
> > >> +  STMicroelectronics VGxy61 family has a CSI-2 output port. CSI-2 output is a
> > >> +  quad lanes 800Mbps per lane.
> > >> +  Supported formats are RAW8, RAW10, RAW12, RAW14 and RAW16.
> > >> +  Following part number are supported
> > >> +  - VG5661 and VG6661 are 1.6 Mpx (1464 x 1104) monochrome and color sensors.
> > >> +  Maximum frame rate is 75 fps.
> > >> +  - VG5761 and VG6761 are 2.3 Mpx (1944 x 1204) monochrome and color sensors.
> > >> +  Maximum frame rate is 60 fps.
> > >> +
> > >> +properties:
> > >> +  compatible:
> > >> +    const: st,st-vgxy61
> > >> +
> > >> +  reg:
> > >> +    maxItems: 1
> > >> +
> > >> +  clocks:
> > >> +    maxItems: 1
> > >> +
> > >> +  clock-names:
> > >> +    description:
> > >> +      Input clock for the sensor.
> > >> +    items:
> > >> +      - const: xclk
> 
> Do you need this if you have a single clock?
> 
> Also see Documentation/driver-api/media/camera-sensor.rst .
> 
> > >> +
> > >> +  VCORE-supply:
> > >> +    description:
> > >> +      Sensor digital core supply. Must be 1.2 volts.
> > >> +
> > >> +  VDDIO-supply:
> > >> +    description:
> > >> +      Sensor digital IO supply. Must be 1.8 volts.
> > >> +
> > >> +  VANA-supply:
> > >> +    description:
> > >> +      Sensor analog supply. Must be 2.8 volts.
> > >> +
> > >> +  reset-gpios:
> > >> +    description:
> > >> +      Reference to the GPIO connected to the reset pin, if any.
> > >> +      This is an active low signal to the vgxy61.
> > >> +
> > >> +  invert-gpios-polarity:
> > >> +    description:
> > >> +      If gpios polarity should be inversed
> > > 
> > > s/inversed/inverted
> > > 
> > 
> > Ok.
> > 
> > >> +    type: boolean
> > >> +
> > >> +  slave-mode:
> > >> +    description:
> > >> +      If the sensor operates in slave mode
> > >> +    type: boolean
> > > 
> > > This is one I've been meaning to raise for a while.
> > > Is DT the correct place to be configuring hardware sync options for
> > > image sensors? (There may be the linguistic discussions over master /
> > > slave terminology too).
> > > We also have IMX477 and a number of other sensors that support
> > > external sync control of some form.
> > > 
> > > As I see it, there are nominally 3 settings - disabled (reduces EMC
> > > noise), generate syncs, and receive syncs.
> > > For test purposes it would be useful to be able to switch between
> > > generate and receive modes at runtime, so that would make it a control
> > > instead of being fixed in DT.
> > > 
> > > If it should be configured in DT, then how does ACPI need to handle it?
> > > 
> > > If DT is the correct place to define the role, should it be in
> > > video-interfaces.yaml as an optional property, instead of being a
> > > sensor specific binding?
> > > 
> > > Sorry, more questions rather than answers.
> > > 
> > >   Dave
> > 
> > Maybe I can provide additional info on this sensor to help find an
> > answer. The "slave mode" has 2 settings: enabled or disabled. If disabled
> > you are in master mode ('generate sync' and 'disabled' modes Dave
> > mentionned, they are the same here), and if enabled you are in slave mode
> > ('receive sync'). As you said he master sends frame sync signals to the
> > slave each frame acquired, this allows both sensors to synchronize
> > themselves.
> > 
> > I put this in the device tree as we only use it for 3D stereocam boards
> > which already have 2 sensors on them, meaning this is hardware specific.
> > I don't have any use case where we manually wire 2 sensors on 2 separate
> > boards. One good point you mentioned is that I may not always want run
> > this board in master/slaver, and both sensors could run on master mode
> > without interacting with each other, thus justifying a dedicated v4l2
> > control.
> > 
> > Any ideas on how to name it instead of "slave mode" for coherency between
> > sensors?
> 
> How is this wired? The slave-mode property documentation explicitly refers
> to synchronisation signals that do not exists in CSI-2.

What is slave mode in this case ? Does it only mean that the sensor is
externally triggered, or is there something else ? For parallel
interfaces with H/V sync there's a possibility of the H/V sync signals
being inputs instead of outputs, but that's not applicable to CSI-2.

> > >> +    #TODO check all this or copy from elsewhere
> > 
> > Just noticed this and will remove it.
> > 
> > >> +  port:
> > >> +    $ref: /schemas/graph.yaml#/$defs/port-base
> > >> +    additionalProperties: false
> > >> +
> > >> +    properties:
> > >> +      endpoint:
> > >> +        $ref: /schemas/media/video-interfaces.yaml#
> > >> +        unevaluatedProperties: false
> > >> +
> > >> +        properties:
> > >> +          clock-lane:
> > >> +            description:
> > >> +              Clock lane index
> > >> +            maxItems: 1
> 
> Does the device support lane reordering? If not, please drop.
> 
> > >> +
> > >> +          data-lanes:
> > >> +            description:
> > >> +              CSI lanes to use
> > >> +            items:
> > >> +              - const: 1
> > >> +              - const: 2
> > >> +              - const: 3
> > >> +              - const: 4
> 
> Which lane configurations does the device support? If it's four lanes only,
> then you can drop this property, too.
> 
> > >> +
> > >> +          remote-endpoint: true
> > >> +
> > >> +        required:
> > >> +          - clock-lane
> > >> +          - data-lanes
> > >> +          - remote-endpoint
> 
> Listing remote-endpoint here isn't needed as this comes from the schema.
> 
> > >> +
> > >> +required:
> > >> +  - compatible
> > >> +  - clocks
> > >> +  - clock-names
> > >> +  - VCORE-supply
> > >> +  - VDDIO-supply
> > >> +  - VANA-supply
> > >> +
> > >> +additionalProperties: false
> > >> +
> > >> +examples:
> > >> +  - |
> > >> +    #include <dt-bindings/gpio/gpio.h>
> > >> +    i2c {
> > >> +        #address-cells = <1>;
> > >> +        #size-cells = <0>;
> > >> +        vgxy61: csi2tx@10 {
> > >> +            compatible = "st,st-vgxy61";
> > >> +            reg = <0x10>;
> > >> +            status = "okay";
> > >> +            clocks = <&clk_ext_camera>;
> > >> +            clock-names = "xclk";
> > >> +            VCORE-supply = <&v1v2>;
> > >> +            VDDIO-supply = <&v1v8>;
> > >> +            VANA-supply = <&v2v8>;
> > >> +            reset-gpios = <&mfxgpio 18 GPIO_ACTIVE_LOW>;
> > >> +            port {
> > >> +                ep0: endpoint {
> > >> +                    clock-lane = <0>;
> > >> +                    data-lanes = <1 2 3 4>;
> > >> +                    remote-endpoint = <&mipi_csi2_out>;
> > >> +                };
> > >> +            };
> > >> +        };
> > >> +    };
> > >> +...
> > >> diff --git a/MAINTAINERS b/MAINTAINERS
> > >> index 83d27b57016f..f358d15f68a0 100644
> > >> --- a/MAINTAINERS
> > >> +++ b/MAINTAINERS
> > >> @@ -18297,6 +18297,16 @@ S:     Maintained
> > >>  F:     Documentation/hwmon/stpddc60.rst
> > >>  F:     drivers/hwmon/pmbus/stpddc60.c
> > >>
> > >> +ST VGXY61 DRIVER
> > >> +M:     Benjamin Mugnier <benjamin.mugnier@foss.st.com>
> > >> +M:     Sylvain Petinot <sylvain.petinot@foss.st.com>
> > >> +L:     linux-media@vger.kernel.org
> > >> +S:     Maintained
> > >> +T:     git git://linuxtv.org/media_tree.git
> > >> +F:     Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.txt
> > >> +F:     drivers/media/i2c/st-vgxy61.c
> > >> +
> 
> Extra newline.
> 
> > >> +
> > >>  ST VL53L0X ToF RANGER(I2C) IIO DRIVER
> > >>  M:     Song Qiang <songqiang1304521@gmail.com>
> > >>  L:     linux-iio@vger.kernel.org

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH 2/2] media: i2c: Add driver for ST VGXY61 camera sensor
  2022-03-11 11:57         ` Dave Stevenson
@ 2022-03-14  9:33           ` Benjamin Mugnier
  0 siblings, 0 replies; 17+ messages in thread
From: Benjamin Mugnier @ 2022-03-14  9:33 UTC (permalink / raw)
  To: Dave Stevenson
  Cc: Kieran Bingham, Linux Media Mailing List, alain.volmat,
	hugues.fruchet, sylvain.petinot

Hi Dave,

Thank you for your answers.

On 11/03/2022 12:57, Dave Stevenson wrote:
> Hi Benjamin
> 
> On Fri, 11 Mar 2022 at 10:56, Benjamin Mugnier
> <benjamin.mugnier@foss.st.com> wrote:
>>
>> Hi Dave,
>>
>> Thanks for your review. Here are my comments and some questions.
>>
>> On 10/03/2022 16:21, Dave Stevenson wrote:
>>> Hi Benjamin and Kieran
>>>
>>> On Thu, 10 Mar 2022 at 13:52, Kieran Bingham
>>> <kieran.bingham@ideasonboard.com> wrote:
>>>>
>>>> Hi Benjamin,
>>>>
>>>> Thank you for the patch -
>>>>
>>>> Quoting Benjamin Mugnier (2022-03-10 13:32:55)
>>>>> The VGXY61 has a quad lanes CSI-2 output port running at 800mbps per
>>>>> lane, and supports RAW8, RAW10, RAW12, RAW14 and RAW16 formats.
>>>>> The driver handles both sensor types:
>>>>> - VG5661 and VG6661: 1.6 Mpx (1464 x 1104) 75fps.
>>>>> - VG5761 and VG6761: 2.3 Mpx (1944 x 1204) 60 fps.
>>>>> The driver supports:
>>>>> - HDR linearize mode, HDR substraction mode, and no HDR
>>>>> - GPIOs LEDs strobing
>>>>> - Digital binning and analog subsampling
>>>>> - Horizontal and vertical flip
>>>>> - Manual exposure
>>>>> - Analog and digital gains
>>>>> - Test patterns
>>>>
>>>> I haven't reviewed the driver directly yet, but I have a script which
>>>> looks for key requirements for libcamera.
>>>> (https://git.linuxtv.org/libcamera.git/tree/Documentation/sensor_driver_requirements.rst)
>>>>
>>>>
>>>> Media Controller Support:
>>>>  - V4L2_SUBDEV_FL_HAS_DEVNODE      : found
>>>>  - MEDIA_ENT_F_CAM_SENSOR          : found
>>>>
>>>> Mandatory Controls:
>>>>  - V4L2_CID_EXPOSURE               : found
>>>>  - V4L2_CID_HBLANK                 : --------
>>>>  - V4L2_CID_VBLANK                 : --------
>>>>  - V4L2_CID_PIXEL_RATE             : found
>>>>
>>>> Selection Controls (will become mandatory):
>>>>  - V4L2_SEL_TGT_CROP_DEFAULT       : --------
>>>>  - V4L2_SEL_TGT_CROP               : --------
>>>>  - V4L2_SEL_TGT_CROP_BOUNDS        : --------
>>>>  - .get_selection                  : --------
>>>>
>>>> Optional Controls:
>>>>  - V4L2_CID_TEST_PATTERN           : found
>>>>  - V4L2_CID_GAIN                   : --------
>>>>  - V4L2_CID_ANALOGUE_GAIN          : found
>>>>  - V4L2_CID_CAMERA_SENSOR_ROTATION : --------
>>>>  - V4L2_CID_CAMERA_ORIENTATION     : --------
>>>>
>>>>
>>>> The key missing pieces are HBLANK/VBLANK and the .get_selection API. Is
>>>> it easy/feasible to add these?
>>>
>>> It's documented that HBLANK/VBLANK are the correct parameters to use
>>> for raw image sensors [1].
>>>
>>> It looks like register DEVICE_FRAME_LENGTH is mode->height +
>>> V4L2_CID_VBLANK, and DEVICE_LINE_LENGTH is mode->width +
>>> V4L2_CID_HBLANK (appears to be treated as read only)
>>> Most other sensors will adjust the max value of V4L2_CID_EXPOSURE
>>> based on the frame length.
>>>
>>> [1] https://www.kernel.org/doc/html/latest/driver-api/media/camera-sensor.html#frame-interval-configuration
>>>
>>> I've only done a quick review on the rest.
>>>
>>>> --
>>>> Regards
>>>>
>>>> Kieran
>>>>
>>>>
>>>>>
>>>>> Signed-off-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com>
>>>>> ---
>>>>>  drivers/media/i2c/Kconfig     |   11 +
>>>>>  drivers/media/i2c/Makefile    |    1 +
>>>>>  drivers/media/i2c/st-vgxy61.c | 1919 +++++++++++++++++++++++++++++++++
>>>>>  3 files changed, 1931 insertions(+)
>>>>>  create mode 100644 drivers/media/i2c/st-vgxy61.c
>>>>>
>>>>> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
>>>>> index fe66093b8849..e6e9c1f19c09 100644
>>>>> --- a/drivers/media/i2c/Kconfig
>>>>> +++ b/drivers/media/i2c/Kconfig
>>>>> @@ -1454,6 +1454,17 @@ config VIDEO_S5C73M3
>>>>>           This is a V4L2 sensor driver for Samsung S5C73M3
>>>>>           8 Mpixel camera.
>>>>>
>>>>> +config VIDEO_ST_VGXY61
>>>>> +       tristate "ST VGXY61 sensor support"
>>>>> +       depends on OF
>>>>> +       depends on GPIOLIB && VIDEO_V4L2 && I2C
>>>>> +       select MEDIA_CONTROLLER
>>>>> +       select VIDEO_V4L2_SUBDEV_API
>>>>> +       select V4L2_FWNODE
>>>>> +       help
>>>>> +         This is a Video4Linux2 sensor driver for the ST VGXY61
>>>>> +         camera sensor.
>>>>> +
>>>>>  endmenu
>>>>>
>>>>>  menu "Lens drivers"
>>>>> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
>>>>> index f6b80ef6f41d..0190b5edfa24 100644
>>>>> --- a/drivers/media/i2c/Makefile
>>>>> +++ b/drivers/media/i2c/Makefile
>>>>> @@ -138,4 +138,5 @@ obj-$(CONFIG_VIDEO_MAX9271_LIB)     += max9271.o
>>>>>  obj-$(CONFIG_VIDEO_RDACM20)    += rdacm20.o
>>>>>  obj-$(CONFIG_VIDEO_RDACM21)    += rdacm21.o
>>>>>  obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o
>>>>> +obj-$(CONFIG_VIDEO_ST_VGXY61)  += st-vgxy61.o
>>>>>  obj-$(CONFIG_SDR_MAX2175) += max2175.o
>>>>> diff --git a/drivers/media/i2c/st-vgxy61.c b/drivers/media/i2c/st-vgxy61.c
>>>>> new file mode 100644
>>>>> index 000000000000..d3d816189f86
>>>>> --- /dev/null
>>>>> +++ b/drivers/media/i2c/st-vgxy61.c
>>>>> @@ -0,0 +1,1919 @@
>>>>> +// SPDX-License-Identifier: GPL-2.0
>>>>> +/*
>>>>> + * Driver for VGXY61 global shutter sensor family driver
>>>>> + *
>>>>> + * Copyright (C) 2022 STMicroelectronics SA
>>>>> + */
>>>>> +
>>>>> +#include <linux/clk.h>
>>>>> +#include <linux/delay.h>
>>>>> +#include <linux/gpio/consumer.h>
>>>>> +#include <linux/i2c.h>
>>>>> +#include <linux/module.h>
>>>>> +#include <linux/regulator/consumer.h>
>>>>> +#include <linux/units.h>
>>>>> +#include <linux/iopoll.h>
>>>>> +#include <media/mipi-csi2.h>
>>>>> +#include <media/v4l2-async.h>
>>>>> +#include <media/v4l2-ctrls.h>
>>>>> +#include <media/v4l2-device.h>
>>>>> +#include <media/v4l2-fwnode.h>
>>>>> +#include <media/v4l2-subdev.h>
>>>>> +
>>>>> +#define US_PER_MS                                      1000
>>>>> +
>>>>> +#define DEVICE_MODEL_ID_REG                            0x0000
>>>>> +#define VG5661_MODEL_ID                                        0x5661
>>>>> +#define VG5761_MODEL_ID                                        0x5761
>>>>> +#define VGX661_WIDTH                                   1464
>>>>> +#define VGX661_HEIGHT                                  1104
>>>>> +#define VGX761_WIDTH                                   1944
>>>>> +#define VGX761_HEIGHT                                  1204
>>>>> +#define VGX661_DEFAULT_MODE                            1
>>>>> +#define VGX761_DEFAULT_MODE                            1
>>>>> +#define VGX661_SHORT_ROT_TERM                          93
>>>>> +#define VGX761_SHORT_ROT_TERM                          90
>>>>> +#define VGXY61_EXPOS_ROT_TERM                          66
>>>>> +#define DEVICE_REVISION                                        0x0002
>>>>> +#define DEVICE_FWPATCH_REVISION                                0x0014
>>>>> +#define DEVICE_FWPATCH_START_ADDR                      0x2000
>>>>> +#define DEVICE_SYSTEM_FSM                              0x0020
>>>>> +#define SW_STBY                                                0x03
>>>>> +#define STREAMING                                      0x04
>>>>> +#define DEVICE_NVM                                     0x0023
>>>>> +#define NVM_OK                                         0x04
>>>>> +#define DEVICE_THSENS1_TEMPERATURE                     0x0044
>>>>> +#define DEVICE_STBY                                    0x0201
>>>>> +#define STBY_NO_REQ                                    0
>>>>> +#define STBY_REQ_TMP_READ                              BIT(2)
>>>>> +#define DEVICE_STREAMING                               0x0202
>>>>> +#define REQ_NO_REQUEST                                 0
>>>>> +#define REQ_STOP_STREAMING                             BIT(0)
>>>>> +#define REQ_START_STREAMING                            BIT(1)
>>>>> +#define DEVICE_EXT_CLOCK                               0x0220
>>>>> +#define DEVICE_CLK_PLL_PREDIV                          0x0224
>>>>> +#define DEVICE_CLK_SYS_PLL_MULT                                0x0225
>>>>> +#define DEVICE_GPIO_0_CTRL                             0x0236
>>>>> +#define DEVICE_GPIO_1_CTRL                             0x0237
>>>>> +#define DEVICE_GPIO_2_CTRL                             0x0238
>>>>> +#define DEVICE_GPIO_3_CTRL                             0x0239
>>>>> +#define DEVICE_SIGNALS_POLARITY_CTRL                   0x023b
>>>>> +#define DEVICE_LINE_LENGTH                             0x0300
>>>>> +#define DEVICE_ORIENTATION                             0x0302
>>>>> +#define DEVICE_VT_CTRL                                 0x0304
>>>>> +#define DEVICE_FORMAT_CTRL                             0x0305
>>>>> +#define DEVICE_OIF_CTRL                                        0x0306
>>>>> +#define DEVICE_OIF_ROI0_CTRL                           0x030a
>>>>> +#define DEVICE_ROI0_START_H                            0x0400
>>>>> +#define DEVICE_ROI0_START_V                            0x0402
>>>>> +#define DEVICE_ROI0_END_H                              0x0404
>>>>> +#define DEVICE_ROI0_END_V                              0x0406
>>>>> +#define DEVICE_PATGEN_CTRL                             0x0440
>>>>> +#define DEVICE_FRAME_CONTENT_CTRL                      0x0478
>>>>> +#define DEVICE_COARSE_EXPOSURE_LONG                    0x0500
>>>>> +#define DEVICE_COARSE_EXPOSURE_SHORT                   0x0504
>>>>> +#define DEVICE_ANALOG_GAIN                             0x0508
>>>>> +#define DEVICE_DIGITAL_GAIN_LONG                       0x050a
>>>>> +#define DEVICE_DIGITAL_GAIN_SHORT                      0x0512
>>>>> +#define DEVICE_FRAME_LENGTH                            0x051a
>>>>> +#define DEVICE_SIGNALS_CTRL                            0x0522
>>>>> +#define DEVICE_STROBE_LONG_START_DELAY                 0x0528
>>>>> +#define DEVICE_STROBE_LONG_END_DELAY                   0x052a
>>>>> +#define DEVICE_STROBE_SHORT_START_DELAY                        0x052c
>>>>> +#define DEVICE_STROBE_SHORT_END_DELAY                  0x052e
>>>>> +#define DEVICE_READOUT_CTRL                            0x0530
>>>>> +#define DEVICE_HDR_CTRL                                        0x0532
>>>>> +#define DEVICE_PATGEN_LONG_DATA_GR                     0x092c
>>>>> +#define DEVICE_PATGEN_LONG_DATA_R                      0x092e
>>>>> +#define DEVICE_PATGEN_LONG_DATA_B                      0x0930
>>>>> +#define DEVICE_PATGEN_LONG_DATA_GB                     0x0932
>>>>> +#define DEVICE_PATGEN_SHORT_DATA_GR                    0x0950
>>>>> +#define DEVICE_PATGEN_SHORT_DATA_R                     0x0952
>>>>> +#define DEVICE_PATGEN_SHORT_DATA_B                     0x0954
>>>>> +#define DEVICE_PATGEN_SHORT_DATA_GB                    0x0956
>>>>> +#define DEVICE_BYPASS_CTRL                             0x0a60
>>>>> +
>>>>> +#define V4L2_CID_HDR                           (V4L2_CID_USER_BASE | 0x1004)
>>>>> +#define V4L2_CID_GPIOS_STROBE_LONG_START_DELAY (V4L2_CID_USER_BASE | 0x1019)
>>>>> +#define V4L2_CID_GPIOS_STROBE_LONG_END_DELAY   (V4L2_CID_USER_BASE | 0x101a)
>>>>> +#define V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY        (V4L2_CID_USER_BASE | 0x101b)
>>>>> +#define V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY  (V4L2_CID_USER_BASE | 0x101c)
>>>>> +#define V4L2_CID_TEMPERATURE                   (V4L2_CID_USER_BASE | 0x1020)
>>>
>>> The strobe and temperature controls are equally valid for other
>>> sensors, so IMHO ought to be added as standard controls.
>>>
>>
>> Agreed for the temperature, I will add it as a standard control in v2.
>> As for the strobing I am not sure, these are controls to set the strobing delay for long exposure and short exposure (if in HDR mode), not to enable or disable the strobbing. I could not find anything like this in the codebase so I guess this is pretty unique to this sensor?
> 
> I'm not sure I follow what the controls are doing then. "GPIOs LEDs
> strobing" and the name V4L2_CID_GPIOS_STROBE_* implies to me that it
> is an output GPIO that is asserted during the short/long exposure
> period with an optional delay.
> Many sensors have a GPIO that is asserted during the exposure period,
> often with an optional delay. Whilst they don't necessarily support
> HDR and therefore have two outputs, there are going to be a number
> with commonality.
> External GPIO control is another area that seems to be under-served
> within V4L2. A bit of discussion to sort a generic set of controls
> would be useful.
> 
> If they are custom to your sensor, then you really want to define a
> block of CID values for your sensor in
> https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/v4l2-controls.h#L152
> to avoid something else using the same numbers.
> 
> Unless they are totally critical to the function of this sensor, you
> could drop them for this initial driver, and then the discussion about
> the correct way to support them can come later.
> 

Yes this is linked to the strobe GPIO that is asserted during the exposure period, but not directly:
These are I2C registers where you write the timings you want the GPIO to strobe, and the sensor CPU is responsible to strobe fot this period of time. I don't hanldes the GPIOS directly in the driver. There is another register to tell what GPIOS you want to strobe with but as they are dedicated to strobing I did not make them configurable.
This makes me think that the "GPIO" naming scheme was maybe not a really good naming. Tell me if you need additional info.
 
You are right this is not critical for the initial driver, and adds more complexity than it solves problems. So I will remove these.

Thanks for the block of CID values advice, I will reserve some space as I need it for the HDR control.

>>>>> +
>>>>> +#define DEVICE_FWPATCH_REVISION_MAJOR                  2
>>>>> +#define DEVICE_FWPATCH_REVISION_MINOR                  0
>>>>> +#define DEVICE_FWPATCH_REVISION_MICRO                  5
>>>>> +
>>>>> +#define WRITE_MULTIPLE_CHUNK_MAX                       16
>>>>> +#define NB_GPIOS                                       4
>>>>> +#define NB_POLARITIES                                  5
>>>>> +
>>>>> +static const u8 patch_array[] = {
>>>>> +       0xbf, 0x00, 0x05, 0x20, 0x06, 0x01, 0xe0, 0xe0, 0x04, 0x80, 0xe6, 0x45,
>>>>> +       0xed, 0x6f, 0xfe, 0xff, 0x14, 0x80, 0x1f, 0x84, 0x10, 0x42, 0x05, 0x7c,
>>>>> +       0x01, 0xc4, 0x1e, 0x80, 0xb6, 0x42, 0x00, 0xe0, 0x1e, 0x82, 0x1e, 0xc0,
>>>>> +       0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa, 0x86, 0x0d, 0x70, 0xe1,
>>>>> +       0x04, 0x98, 0x15, 0x00, 0x28, 0xe0, 0x14, 0x02, 0x08, 0xfc, 0x15, 0x40,
>>>>> +       0x28, 0xe0, 0x98, 0x58, 0xe0, 0xef, 0x04, 0x98, 0x0e, 0x04, 0x00, 0xf0,
>>>>> +       0x15, 0x00, 0x28, 0xe0, 0x19, 0xc8, 0x15, 0x40, 0x28, 0xe0, 0xc6, 0x41,
>>>>> +       0xfc, 0xe0, 0x14, 0x80, 0x1f, 0x84, 0x14, 0x02, 0xa0, 0xfc, 0x1e, 0x80,
>>>>> +       0x14, 0x80, 0x14, 0x02, 0x80, 0xfb, 0x14, 0x02, 0xe0, 0xfc, 0x1e, 0x80,
>>>>> +       0x14, 0xc0, 0x1f, 0x84, 0x14, 0x02, 0xa4, 0xfc, 0x1e, 0xc0, 0x14, 0xc0,
>>>>> +       0x14, 0x02, 0x80, 0xfb, 0x14, 0x02, 0xe4, 0xfc, 0x1e, 0xc0, 0x0c, 0x0c,
>>>>> +       0x00, 0xf2, 0x93, 0xdd, 0x86, 0x00, 0xf8, 0xe0, 0x04, 0x80, 0xc6, 0x03,
>>>>> +       0x70, 0xe1, 0x0e, 0x84, 0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa,
>>>>> +       0x6b, 0x80, 0x06, 0x40, 0x6c, 0xe1, 0x04, 0x80, 0x09, 0x00, 0xe0, 0xe0,
>>>>> +       0x0b, 0xa1, 0x95, 0x84, 0x05, 0x0c, 0x1c, 0xe0, 0x86, 0x02, 0xf9, 0x60,
>>>>> +       0xe0, 0xcf, 0x78, 0x6e, 0x80, 0xef, 0x25, 0x0c, 0x18, 0xe0, 0x05, 0x4c,
>>>>> +       0x1c, 0xe0, 0x86, 0x02, 0xf9, 0x60, 0xe0, 0xcf, 0x0b, 0x84, 0xd8, 0x6d,
>>>>> +       0x80, 0xef, 0x05, 0x4c, 0x18, 0xe0, 0x04, 0xd8, 0x0b, 0xa5, 0x95, 0x84,
>>>>> +       0x05, 0x0c, 0x2c, 0xe0, 0x06, 0x02, 0x01, 0x60, 0xe0, 0xce, 0x18, 0x6d,
>>>>> +       0x80, 0xef, 0x25, 0x0c, 0x30, 0xe0, 0x05, 0x4c, 0x2c, 0xe0, 0x06, 0x02,
>>>>> +       0x01, 0x60, 0xe0, 0xce, 0x0b, 0x84, 0x78, 0x6c, 0x80, 0xef, 0x05, 0x4c,
>>>>> +       0x30, 0xe0, 0x0c, 0x0c, 0x00, 0xf2, 0x93, 0xdd, 0x46, 0x01, 0x70, 0xe1,
>>>>> +       0x08, 0x80, 0x0b, 0xa1, 0x08, 0x5c, 0x00, 0xda, 0x06, 0x01, 0x68, 0xe1,
>>>>> +       0x04, 0x80, 0x4a, 0x40, 0x84, 0xe0, 0x08, 0x5c, 0x00, 0x9a, 0x06, 0x01,
>>>>> +       0xe0, 0xe0, 0x04, 0x80, 0x15, 0x00, 0x60, 0xe0, 0x19, 0xc4, 0x15, 0x40,
>>>>> +       0x60, 0xe0, 0x15, 0x00, 0x78, 0xe0, 0x19, 0xc4, 0x15, 0x40, 0x78, 0xe0,
>>>>> +       0x93, 0xdd, 0xc3, 0xc1, 0x46, 0x01, 0x70, 0xe1, 0x08, 0x80, 0x0b, 0xa1,
>>>>> +       0x08, 0x5c, 0x00, 0xda, 0x06, 0x01, 0x68, 0xe1, 0x04, 0x80, 0x4a, 0x40,
>>>>> +       0x84, 0xe0, 0x08, 0x5c, 0x00, 0x9a, 0x06, 0x01, 0xe0, 0xe0, 0x14, 0x80,
>>>>> +       0x25, 0x02, 0x54, 0xe0, 0x29, 0xc4, 0x25, 0x42, 0x54, 0xe0, 0x24, 0x80,
>>>>> +       0x35, 0x04, 0x6c, 0xe0, 0x39, 0xc4, 0x35, 0x44, 0x6c, 0xe0, 0x25, 0x02,
>>>>> +       0x64, 0xe0, 0x29, 0xc4, 0x25, 0x42, 0x64, 0xe0, 0x04, 0x80, 0x15, 0x00,
>>>>> +       0x7c, 0xe0, 0x19, 0xc4, 0x15, 0x40, 0x7c, 0xe0, 0x93, 0xdd, 0xc3, 0xc1,
>>>>> +       0x4c, 0x04, 0x7c, 0xfa, 0x86, 0x40, 0x98, 0xe0, 0x14, 0x80, 0x1b, 0xa1,
>>>>> +       0x06, 0x00, 0x00, 0xc0, 0x08, 0x42, 0x38, 0xdc, 0x08, 0x64, 0xa0, 0xef,
>>>>> +       0x86, 0x42, 0x3c, 0xe0, 0x68, 0x49, 0x80, 0xef, 0x6b, 0x80, 0x78, 0x53,
>>>>> +       0xc8, 0xef, 0xc6, 0x54, 0x6c, 0xe1, 0x7b, 0x80, 0xb5, 0x14, 0x0c, 0xf8,
>>>>> +       0x05, 0x14, 0x14, 0xf8, 0x1a, 0xac, 0x8a, 0x80, 0x0b, 0x90, 0x38, 0x55,
>>>>> +       0x80, 0xef, 0x1a, 0xae, 0x17, 0xc2, 0x03, 0x82, 0x88, 0x65, 0x80, 0xef,
>>>>> +       0x1b, 0x80, 0x0b, 0x8e, 0x68, 0x65, 0x80, 0xef, 0x9b, 0x80, 0x0b, 0x8c,
>>>>> +       0x08, 0x65, 0x80, 0xef, 0x6b, 0x80, 0x0b, 0x92, 0x1b, 0x8c, 0x98, 0x64,
>>>>> +       0x80, 0xef, 0x1a, 0xec, 0x9b, 0x80, 0x0b, 0x90, 0x95, 0x54, 0x10, 0xe0,
>>>>> +       0xa8, 0x53, 0x80, 0xef, 0x1a, 0xee, 0x17, 0xc2, 0x03, 0x82, 0xf8, 0x63,
>>>>> +       0x80, 0xef, 0x1b, 0x80, 0x0b, 0x8e, 0xd8, 0x63, 0x80, 0xef, 0x1b, 0x8c,
>>>>> +       0x68, 0x63, 0x80, 0xef, 0x6b, 0x80, 0x0b, 0x92, 0x65, 0x54, 0x14, 0xe0,
>>>>> +       0x08, 0x65, 0x84, 0xef, 0x68, 0x63, 0x80, 0xef, 0x7b, 0x80, 0x0b, 0x8c,
>>>>> +       0xa8, 0x64, 0x84, 0xef, 0x08, 0x63, 0x80, 0xef, 0x14, 0xe8, 0x46, 0x44,
>>>>> +       0x94, 0xe1, 0x24, 0x88, 0x4a, 0x4e, 0x04, 0xe0, 0x14, 0xea, 0x1a, 0x04,
>>>>> +       0x08, 0xe0, 0x0a, 0x40, 0x84, 0xed, 0x0c, 0x04, 0x00, 0xe2, 0x4a, 0x40,
>>>>> +       0x04, 0xe0, 0x19, 0x16, 0xc0, 0xe0, 0x0a, 0x40, 0x84, 0xed, 0x21, 0x54,
>>>>> +       0x60, 0xe0, 0x0c, 0x04, 0x00, 0xe2, 0x1b, 0xa5, 0x0e, 0xea, 0x01, 0x89,
>>>>> +       0x21, 0x54, 0x64, 0xe0, 0x7e, 0xe8, 0x65, 0x82, 0x1b, 0xa7, 0x26, 0x00,
>>>>> +       0x00, 0x80, 0xa5, 0x82, 0x1b, 0xa9, 0x65, 0x82, 0x1b, 0xa3, 0x01, 0x85,
>>>>> +       0x16, 0x00, 0x00, 0xc0, 0x01, 0x54, 0x04, 0xf8, 0x06, 0xaa, 0x01, 0x83,
>>>>> +       0x06, 0xa8, 0x65, 0x81, 0x06, 0xa8, 0x01, 0x54, 0x04, 0xf8, 0x01, 0x83,
>>>>> +       0x06, 0xaa, 0x09, 0x14, 0x18, 0xf8, 0x0b, 0xa1, 0x05, 0x84, 0xc6, 0x42,
>>>>> +       0xd4, 0xe0, 0x14, 0x84, 0x01, 0x83, 0x01, 0x54, 0x60, 0xe0, 0x01, 0x54,
>>>>> +       0x64, 0xe0, 0x0b, 0x02, 0x90, 0xe0, 0x10, 0x02, 0x90, 0xe5, 0x01, 0x54,
>>>>> +       0x88, 0xe0, 0xb5, 0x81, 0xc6, 0x40, 0xd4, 0xe0, 0x14, 0x80, 0x0b, 0x02,
>>>>> +       0xe0, 0xe4, 0x10, 0x02, 0x31, 0x66, 0x02, 0xc0, 0x01, 0x54, 0x88, 0xe0,
>>>>> +       0x1a, 0x84, 0x29, 0x14, 0x10, 0xe0, 0x1c, 0xaa, 0x2b, 0xa1, 0xf5, 0x82,
>>>>> +       0x25, 0x14, 0x10, 0xf8, 0x2b, 0x04, 0xa8, 0xe0, 0x20, 0x44, 0x0d, 0x70,
>>>>> +       0x03, 0xc0, 0x2b, 0xa1, 0x04, 0x00, 0x80, 0x9a, 0x02, 0x40, 0x84, 0x90,
>>>>> +       0x03, 0x54, 0x04, 0x80, 0x4c, 0x0c, 0x7c, 0xf2, 0x93, 0xdd, 0x00, 0x00,
>>>>> +       0x02, 0xa9, 0x00, 0x00, 0x64, 0x4a, 0x40, 0x00, 0x08, 0x2d, 0x58, 0xe0,
>>>>> +       0xa8, 0x98, 0x40, 0x00, 0x28, 0x07, 0x34, 0xe0, 0x05, 0xb9, 0x00, 0x00,
>>>>> +       0x28, 0x00, 0x41, 0x05, 0x88, 0x00, 0x41, 0x3c, 0x98, 0x00, 0x41, 0x52,
>>>>> +       0x04, 0x01, 0x41, 0x79, 0x3c, 0x01, 0x41, 0x6a, 0x3d, 0xfe, 0x00, 0x00,
>>>>> +};
>>>>> +
>>>>> +static const char * const vgxy61_test_pattern_menu[] = {
>>>>> +       "Disabled",
>>>>> +       "Solid",
>>>>> +       "Colorbar",
>>>>> +       "Gradbar",
>>>>> +       "Hgrey",
>>>>> +       "Vgrey",
>>>>> +       "Dgrey",
>>>>> +       "PN28",
>>>>> +};
>>>>> +
>>>>> +static const char * const vgxy61_hdr_modes[] = {
>>>>> +       "HDR linearize",
>>>>> +       "HDR substraction",
>>>>> +       "no HDR",
>>>>> +};
>>>>> +
>>>>> +/* Regulator supplies */
>>>>> +static const char * const vgxy61_supply_name[] = {
>>>>> +       "VCORE",
>>>>> +       "VDDIO",
>>>>> +       "VANA",
>>>>> +};
>>>>> +
>>>>> +static const s64 link_freq[] = {
>>>>> +       /*
>>>>> +        * MIPI output freq is 804Mhz / 2, as it uses both rising edge and falling edges to send
>>>>> +        * data
>>>>> +        */
>>>>> +       402000000ULL
>>>>> +};
>>>>> +
>>>>> +#define VGXY61_NUM_SUPPLIES            ARRAY_SIZE(vgxy61_supply_name)
>>>>> +
>>>>> +/* Macro to convert index to 8.8 fixed point gain */
>>>>> +#define I2FP(i)                                ((u32)(8192.0 / (32 - (i))))
>>>>> +/* Array of possibles analog gains in 8.8 fixed point */
>>>>> +static const u16 analog_gains[] = {
>>>>> +       I2FP(0), I2FP(1), I2FP(2), I2FP(3), I2FP(4), I2FP(5), I2FP(6), I2FP(7), I2FP(8), I2FP(9),
>>>>> +       I2FP(10), I2FP(11), I2FP(12), I2FP(13), I2FP(14), I2FP(15), I2FP(16),
>>>>> +};
>>>>> +
>>>>> +enum bin_mode {
>>>>> +       BIN_MODE_NORMAL,
>>>>> +       BIN_MODE_DIGITAL_X2,
>>>>> +       BIN_MODE_DIGITAL_X4,
>>>>> +       BIN_MODE_ANALOG_SUB_X2,
>>>>> +       BIN_MODE_ANALOG_SUB_X4,
>>>>> +};
>>>>> +
>>>>> +enum hdr {
>>>>> +       HDR_LINEAR,
>>>>> +       HDR_SUB,
>>>>> +       NO_HDR,
>>>>> +};
>>>>> +
>>>>> +enum strobe_modes {
>>>>> +       STROBE_DISABLED,
>>>>> +       STROBE_LONG,
>>>>> +       STROBE_ENABLED,
>>>>> +};
>>>>> +
>>>>> +struct vgxy61_mode_info {
>>>>> +       u32 width;
>>>>> +       u32 height;
>>>>> +       enum bin_mode bin_mode;
>>>>> +};
>>>>> +
>>>>> +static const u32 vgxy61_supported_codes[] = {
>>>>> +       MEDIA_BUS_FMT_SGBRG8_1X8,
>>>>> +       MEDIA_BUS_FMT_SGBRG10_1X10,
>>>>> +       MEDIA_BUS_FMT_SGBRG12_1X12,
>>>>> +       MEDIA_BUS_FMT_SGBRG14_1X14,
>>>>> +       MEDIA_BUS_FMT_SGBRG16_1X16
>>>>> +};
>>>>> +
>>>>> +const int vgx761_sensor_frame_rates[] = {75, 60, 30, 15, 10, 5, 2};
>>>>> +const int vgx661_sensor_frame_rates[] = {60, 30, 15, 10, 5, 2};
>>>>> +
>>>>> +static const struct vgxy61_mode_info vgx661_mode_data[] = {
>>>>> +       {1464, 1104, BIN_MODE_NORMAL},
>>>>> +       {1280,  720, BIN_MODE_NORMAL},
>>>>> +       { 640,  480, BIN_MODE_DIGITAL_X2},
>>>>> +       { 320,  240, BIN_MODE_DIGITAL_X4},
>>>>> +};
>>>>> +
>>>>> +static const struct vgxy61_mode_info vgx761_mode_data[] = {
>>>>> +       {1944, 1204, BIN_MODE_NORMAL},
>>>>> +       {1920, 1080, BIN_MODE_NORMAL},
>>>>> +       {1280,  720, BIN_MODE_NORMAL},
>>>>> +       { 640,  480, BIN_MODE_DIGITAL_X2},
>>>>> +       { 320,  240, BIN_MODE_DIGITAL_X4},
>>>>> +};
>>>>> +
>>>>> +struct gpios_ctrls {
>>>>> +       struct v4l2_ctrl *long_start;
>>>>> +       struct v4l2_ctrl *long_end;
>>>>> +       struct v4l2_ctrl *short_start;
>>>>> +       struct v4l2_ctrl *short_end;
>>>>> +};
>>>>> +
>>>>> +struct vgxy61_ctrls {
>>>>> +       struct v4l2_ctrl_handler handler;
>>>>> +       struct v4l2_ctrl *exposure;
>>>>> +       struct v4l2_ctrl *analog_gain;
>>>>> +       struct v4l2_ctrl *digital_gain;
>>>>> +       struct gpios_ctrls gpios;
>>>>> +       struct v4l2_ctrl *vflip;
>>>>> +       struct v4l2_ctrl *hflip;
>>>>> +       struct v4l2_ctrl *patgen;
>>>>> +       struct v4l2_ctrl *hdr;
>>>>> +       struct v4l2_ctrl *pixel_rate;
>>>>> +       struct v4l2_ctrl *link_freq;
>>>
>>> No need to store analog_gain, digital_gain, vflip, hflip, patgen, hdr,
>>> or link_freq as they are never programmatically altered. The s_ctrl
>>> handler is given the struct v4l2_ctrl, and where you need it again
>>> you've copied the value into the main struct vgxy61_dev.
>>>
>>
>> Definetly. I just need to keep the pixel_rate ctrl to change its value in the set_fmt function.
>> I will remove all the others, and put the pixel_rate control directly in the vgxy61 structure.
>>
>>>>> +       struct v4l2_ctrl *temp;
>>>>> +};
>>>>> +
>>>>> +struct vgxy61_dev {
>>>>> +       struct i2c_client *i2c_client;
>>>>> +       struct v4l2_subdev sd;
>>>>> +       struct media_pad pad;
>>>>> +       struct regulator_bulk_data supplies[VGXY61_NUM_SUPPLIES];
>>>>> +       struct gpio_desc *reset_gpio;
>>>>> +       struct clk *xclk;
>>>>> +       u32 clk_freq;
>>>>> +       int sensor_width;
>>>>> +       int sensor_height;
>>>>> +       u16 oif_ctrl;
>>>>> +       int nb_of_lane;
>>>>> +       int data_rate_in_mbps;
>>>>> +       int pclk;
>>>>> +       u16 line_length;
>>>>> +       int rot_term;
>>>>> +       bool gpios_polarity;
>>>>> +       bool slave_mode;
>>>>> +       /* Lock to protect all members below */
>>>>> +       struct mutex lock;
>>>>> +       struct vgxy61_ctrls ctrls;
>>>>> +       bool streaming;
>>>>> +       struct v4l2_mbus_framefmt fmt;
>>>>> +       const struct vgxy61_mode_info *sensor_modes;
>>>>> +       int sensor_modes_nb;
>>>>> +       const struct vgxy61_mode_info *current_mode;
>>>>> +       const int *sensor_rates;
>>>>> +       int sensor_rates_nb;
>>>>> +       struct v4l2_fract frame_interval;
>>>>> +       bool hflip;
>>>>> +       bool vflip;
>>>>> +       enum hdr hdr;
>>>>> +       int expo_long;
>>>>> +       int expo_short;
>>>>> +};
>>>>> +
>>>>> +static u8 get_bpp_by_code(__u32 code)
>>>>> +{
>>>>> +       switch (code) {
>>>>> +       case MEDIA_BUS_FMT_SGBRG8_1X8:
>>>>> +               return 8;
>>>>> +       case MEDIA_BUS_FMT_SGBRG10_1X10:
>>>>> +               return 10;
>>>>> +       case MEDIA_BUS_FMT_SGBRG12_1X12:
>>>>> +               return 12;
>>>>> +       case MEDIA_BUS_FMT_SGBRG14_1X14:
>>>>> +               return 14;
>>>>> +       case MEDIA_BUS_FMT_SGBRG16_1X16:
>>>>> +               return 16;
>>>>> +       default:
>>>>> +               /* Should never happen */
>>>>> +               WARN(1, "Unsupported code %d. default to 8 bpp", code);
>>>>> +               return 8;
>>>>> +       }
>>>>> +}
>>>>> +
>>>>> +static u8 get_data_type_by_code(__u32 code)
>>>>> +{
>>>>> +       switch (code) {
>>>>> +       case MEDIA_BUS_FMT_SGBRG8_1X8:
>>>>> +               return MIPI_CSI2_DT_RAW8;
>>>>> +       case MEDIA_BUS_FMT_SGBRG10_1X10:
>>>>> +               return MIPI_CSI2_DT_RAW10;
>>>>> +       case MEDIA_BUS_FMT_SGBRG12_1X12:
>>>>> +               return MIPI_CSI2_DT_RAW12;
>>>>> +       case MEDIA_BUS_FMT_SGBRG14_1X14:
>>>>> +               return MIPI_CSI2_DT_RAW14;
>>>>> +       case MEDIA_BUS_FMT_SGBRG16_1X16:
>>>>> +               return MIPI_CSI2_DT_RAW16;
>>>>> +       default:
>>>>> +               /* Should never happen */
>>>>> +               WARN(1, "Unsupported code %d. default to MIPI_CSI2_DT_RAW8 data type", code);
>>>>> +               return MIPI_CSI2_DT_RAW8;
>>>>> +       }
>>>>> +}
>>>>> +
>>>>> +static void compute_pll_parameters_by_freq(u32 freq, unsigned int *prediv, unsigned int *mult)
>>>>> +{
>>>>> +       const unsigned int predivs[] = {1, 2, 4};
>>>>> +       int i;
>>>>> +
>>>>> +       /*
>>>>> +        * Freq range is [6Mhz-27Mhz] already checked.
>>>>> +        * Output of divider should be in [6Mhz-12Mhz[.
>>>>> +        */
>>>>> +       for (i = 0; i < ARRAY_SIZE(predivs); i++) {
>>>>> +               *prediv = predivs[i];
>>>>> +               if (freq / *prediv < 12 * HZ_PER_MHZ)
>>>>> +                       break;
>>>>> +       }
>>>>> +       WARN_ON(i == ARRAY_SIZE(predivs));
>>>>> +
>>>>> +       /*
>>>>> +        * Target freq is 804Mhz. Don't change this as it will impact image quality.
>>>>> +        */
>>>>> +       *mult = ((804 * HZ_PER_MHZ) * (*prediv) + freq / 2) / freq;
>>>>> +}
>>>>> +
>>>>> +static s32 get_pixel_rate(struct vgxy61_dev *sensor)
>>>>> +{
>>>>> +       return div64_u64((u64)sensor->data_rate_in_mbps * sensor->nb_of_lane,
>>>>> +                        get_bpp_by_code(sensor->fmt.code));
>>>>> +}
>>>>> +
>>>>> +static inline struct vgxy61_dev *to_vgxy61_dev(struct v4l2_subdev *sd)
>>>>> +{
>>>>> +       return container_of(sd, struct vgxy61_dev, sd);
>>>>> +}
>>>>> +
>>>>> +static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl)
>>>>> +{
>>>>> +       return &container_of(ctrl->handler, struct vgxy61_dev, ctrls.handler)->sd;
>>>>> +}
>>>>> +
>>>>> +static int get_chunk_size(struct vgxy61_dev *sensor)
>>>>> +{
>>>>> +       struct i2c_adapter *adapter = sensor->i2c_client->adapter;
>>>>> +       int max_write_len = WRITE_MULTIPLE_CHUNK_MAX;
>>>>> +
>>>>> +       if (adapter->quirks && adapter->quirks->max_write_len)
>>>>> +               max_write_len = adapter->quirks->max_write_len - 2;
>>>>> +
>>>>> +       max_write_len = min(max_write_len, WRITE_MULTIPLE_CHUNK_MAX);
>>>>> +
>>>>> +       return max(max_write_len, 1);
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_read_multiple(struct vgxy61_dev *sensor, u16 reg, u8 *val, int len)
>>>>> +{
>>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>>> +       struct i2c_msg msg[2];
>>>>> +       u8 buf[2];
>>>>> +       int ret;
>>>>> +
>>>>> +       buf[0] = reg >> 8;
>>>>> +       buf[1] = reg & 0xff;
>>>>> +
>>>>> +       msg[0].addr = client->addr;
>>>>> +       msg[0].flags = client->flags;
>>>>> +       msg[0].buf = buf;
>>>>> +       msg[0].len = sizeof(buf);
>>>>> +
>>>>> +       msg[1].addr = client->addr;
>>>>> +       msg[1].flags = client->flags | I2C_M_RD;
>>>>> +       msg[1].buf = val;
>>>>> +       msg[1].len = len;
>>>>> +
>>>>> +       ret = i2c_transfer(client->adapter, msg, 2);
>>>>> +       if (ret < 0) {
>>>>> +               dev_dbg(&client->dev, "%s: %x i2c_transfer, reg: %x => %d\n", __func__,
>>>>> +                       client->addr, reg, ret);
>>>>> +               return ret;
>>>>> +       }
>>>>> +
>>>>> +       return 0;
>>>>> +}
>>>>> +
>>>>> +static inline int vgxy61_read_reg(struct vgxy61_dev *sensor, u16 reg, u8 *val)
>>>>> +{
>>>>> +       return vgxy61_read_multiple(sensor, reg, val, sizeof(*val));
>>>>> +}
>>>>> +
>>>>> +static inline int vgxy61_read_reg16(struct vgxy61_dev *sensor, u16 reg, u16 *val)
>>>>> +{
>>>>> +       return vgxy61_read_multiple(sensor, reg, (u8 *)val, sizeof(*val));
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_write_multiple(struct vgxy61_dev *sensor, u16 reg, const u8 *data, int len)
>>>>> +{
>>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>>> +       struct i2c_msg msg;
>>>>> +       u8 buf[WRITE_MULTIPLE_CHUNK_MAX + 2];
>>>>> +       int i;
>>>>> +       int ret;
>>>>> +
>>>>> +       if (len > WRITE_MULTIPLE_CHUNK_MAX)
>>>>> +               return -EINVAL;
>>>>> +       buf[0] = reg >> 8;
>>>>> +       buf[1] = reg & 0xff;
>>>>> +       for (i = 0; i < len; i++)
>>>>> +               buf[i + 2] = data[i];
>>>>> +
>>>>> +       msg.addr = client->addr;
>>>>> +       msg.flags = client->flags;
>>>>> +       msg.buf = buf;
>>>>> +       msg.len = len + 2;
>>>>> +
>>>>> +       ret = i2c_transfer(client->adapter, &msg, 1);
>>>>> +       if (ret < 0) {
>>>>> +               dev_dbg(&client->dev, "%s: i2c_transfer, reg: %x => %d\n", __func__, reg, ret);
>>>>> +               return ret;
>>>>> +       }
>>>>> +
>>>>> +       return 0;
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_write_array(struct vgxy61_dev *sensor, u16 reg, int nb, const u8 *array)
>>>>> +{
>>>>> +       const int chunk_size = get_chunk_size(sensor);
>>>>> +       int ret;
>>>>> +       int sz;
>>>>> +
>>>>> +       while (nb) {
>>>>> +               sz = min(nb, chunk_size);
>>>>> +               ret = vgxy61_write_multiple(sensor, reg, array, sz);
>>>>> +               if (ret < 0)
>>>>> +                       return ret;
>>>>> +               nb -= sz;
>>>>> +               reg += sz;
>>>>> +               array += sz;
>>>>> +       }
>>>>> +
>>>>> +       return 0;
>>>>> +}
>>>>> +
>>>>> +static inline int vgxy61_write_reg(struct vgxy61_dev *sensor, u16 reg, u8 val)
>>>>> +{
>>>>> +       return vgxy61_write_multiple(sensor, reg, &val, sizeof(val));
>>>>> +}
>>>>> +
>>>>> +static inline int vgxy61_write_reg16(struct vgxy61_dev *sensor, u16 reg, u16 val)
>>>>> +{
>>>>> +       return vgxy61_write_multiple(sensor, reg, (u8 *)&val, sizeof(val));
>>>>> +}
>>>>> +
>>>>> +static inline int vgxy61_write_reg32(struct vgxy61_dev *sensor, u16 reg, u32 val)
>>>>> +{
>>>>> +       return vgxy61_write_multiple(sensor, reg, (u8 *)&val, sizeof(val));
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_poll_reg(struct vgxy61_dev *sensor, u16 reg, u8 poll_val)
>>>>> +{
>>>>> +       const int loop_delay_ms = 10;
>>>>> +       const int timeout_ms = 500;
>>>>> +       u8 val;
>>>>> +       int ret, timeout;
>>>>> +
>>>>> +       timeout = read_poll_timeout(vgxy61_read_reg, ret, ((ret != 0) || (val == poll_val)),
>>>>> +                                   loop_delay_ms * US_PER_MS, timeout_ms * US_PER_MS, false,
>>>>> +                                   sensor, reg, &val);
>>>>> +       if (timeout)
>>>>> +               return timeout;
>>>>> +
>>>>> +       return ret;
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_wait_state(struct vgxy61_dev *sensor, int state)
>>>>> +{
>>>>> +       return vgxy61_poll_reg(sensor, DEVICE_SYSTEM_FSM, state);
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_check_bw(struct vgxy61_dev *sensor)
>>>>> +{
>>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>>> +       /* Correction factor for time required between 2 lines */
>>>>> +       const int mipi_margin = 1056;
>>>>> +       int binning_scale = 1 << sensor->current_mode->bin_mode;
>>>>> +       int bpp = get_bpp_by_code(sensor->fmt.code);
>>>>> +       int max_bit_per_line;
>>>>> +       int bit_per_line;
>>>>> +       u64 line_rate;
>>>>> +
>>>>> +       line_rate = sensor->nb_of_lane * (u64)sensor->data_rate_in_mbps * sensor->line_length;
>>>>> +       max_bit_per_line = div64_u64(line_rate, sensor->pclk) - mipi_margin;
>>>>> +       bit_per_line = (bpp * sensor->current_mode->width) / binning_scale;
>>>>> +
>>>>> +       dev_dbg(&client->dev, "max_bit_per_line = %d\n", max_bit_per_line);
>>>>> +       dev_dbg(&client->dev, "required bit_per_line = %d\n", bit_per_line);
>>>>> +
>>>>> +       return bit_per_line > max_bit_per_line ? -EINVAL : 0;
>>>>> +}
>>>>> +
>>>>> +static int apply_exposure(struct vgxy61_dev *sensor)
>>>>> +{
>>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>>> +       int ret;
>>>>> +
>>>>> +        /* We first set expo to zero to avoid forbidden parameters couple */
>>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_SHORT, 0);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_LONG, sensor->expo_long);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_SHORT, sensor->expo_short);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +
>>>>> +       dev_dbg(&client->dev, "%s applied expo %d (short: %d)\n", __func__,
>>>>> +               sensor->expo_long, sensor->expo_short);
>>>>> +
>>>>> +       return 0;
>>>>> +}
>>>>> +
>>>>> +static int set_frame_rate(struct vgxy61_dev *sensor)
>>>>> +{
>>>>> +       u16 frame_length;
>>>>> +
>>>>> +       frame_length = sensor->pclk / (sensor->line_length * sensor->frame_interval.denominator);
>>>>> +
>>>>> +       return vgxy61_write_reg16(sensor, DEVICE_FRAME_LENGTH, frame_length);
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_get_regulators(struct vgxy61_dev *sensor)
>>>>> +{
>>>>> +       int i;
>>>>> +
>>>>> +       for (i = 0; i < VGXY61_NUM_SUPPLIES; i++)
>>>>> +               sensor->supplies[i].supply = vgxy61_supply_name[i];
>>>>> +
>>>>> +       return devm_regulator_bulk_get(&sensor->i2c_client->dev, VGXY61_NUM_SUPPLIES,
>>>>> +                                      sensor->supplies);
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_apply_reset(struct vgxy61_dev *sensor)
>>>>> +{
>>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>>> +
>>>>> +       dev_dbg(&client->dev, "%s applied reset\n", __func__);
>>>>> +       gpiod_set_value_cansleep(sensor->reset_gpio, 0);
>>>>> +       usleep_range(5000, 10000);
>>>>> +       gpiod_set_value_cansleep(sensor->reset_gpio, 1);
>>>>> +       usleep_range(5000, 10000);
>>>>> +       gpiod_set_value_cansleep(sensor->reset_gpio, 0);
>>>>> +       usleep_range(40000, 100000);
>>>>> +       return vgxy61_wait_state(sensor, SW_STBY);
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_try_fmt_internal(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *fmt,
>>>>> +                                  const struct vgxy61_mode_info **new_mode)
>>>>> +{
>>>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>>> +       const struct vgxy61_mode_info *mode = sensor->sensor_modes;
>>>>> +       unsigned int index;
>>>>> +
>>>>> +       /* Select code */
>>>>> +       for (index = 0; index < ARRAY_SIZE(vgxy61_supported_codes); index++) {
>>>>> +               if (vgxy61_supported_codes[index] == fmt->code)
>>>>> +                       break;
>>>>> +       }
>>>>> +       if (index == ARRAY_SIZE(vgxy61_supported_codes))
>>>>> +               index = 0;
>>>>> +
>>>>> +       /* Select size */
>>>>> +       do {
>>>>> +               if (mode->width <= fmt->width && mode->height <= fmt->height)
>>>>> +                       break;
>>>>> +       } while ((++mode)->width);
>>>>> +       if (!mode->width)
>>>>> +               mode--;
>>>
>>> Use v4l2_find_nearest_size?
>>>
>>
>> Sure.
>>
>>>>> +
>>>>> +       *new_mode = mode;
>>>>> +       fmt->code = vgxy61_supported_codes[index];
>>>>> +       fmt->width = mode->width;
>>>>> +       fmt->height = mode->height;
>>>>> +       fmt->colorspace = V4L2_COLORSPACE_SRGB;
>>>
>>> Is it? Most Bayer sensors are V4L2_COLORSPACE_RAW.
>>> What about ycbcr_enc, quantization, and xfer_func?
>>>
>>
>> Yes it should be RAW, but iirc I had some issue with the RAW colorspace. I will test it again in order to change it.
>> I will add missing fields too.
>>
>>
>>>>> +       fmt->field = V4L2_FIELD_NONE;
>>>>> +
>>>>> +       return 0;
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_stream_enable(struct vgxy61_dev *sensor)
>>>>> +{
>>>>> +       int center_x = sensor->sensor_width / 2;
>>>>> +       int center_y = sensor->sensor_height / 2;
>>>>> +       int scale = 1 << sensor->current_mode->bin_mode;
>>>>> +       int width = sensor->current_mode->width * scale;
>>>>> +       int height = sensor->current_mode->height * scale;
>>>>> +       int ret;
>>>>> +
>>>>> +       ret = vgxy61_check_bw(sensor);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +
>>>>> +       /* Configure sensor */
>>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_FORMAT_CTRL, get_bpp_by_code(sensor->fmt.code));
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_OIF_ROI0_CTRL,
>>>>> +                              get_data_type_by_code(sensor->fmt.code));
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +
>>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_READOUT_CTRL, sensor->current_mode->bin_mode);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_H, center_x - width / 2);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_H, center_x + width / 2 - 1);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_V, center_y - height / 2);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_V, center_y + height / 2 - 1);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>
>>> Use the selection API to allow the crop region to be configured?
>>> Avoids only supporting centre cropping.
>>>
>>
>> Yes, as we already mentionned in the other thread this is required by libcamera anyway. Two birds one stone.
> 
> There are a couple of bits to the selection API.
> 
> libcamera's mainly after it for read-only information on the crop
> applied by each mode, and the total sensor size. This allows things
> like lens shading tables to be appropriately cropped to match the
> sensor mode.
> 
> There is also a s_selection call, so that could allow totally
> free-form cropping of anywhere from the sensor. This isn't a
> requirement. I was only observing that you were computing the start
> and end values, therefore it seemed like a potential extension.
> Many other sensor drivers have a small table of the registers that are
> different for each mode rather than computing the registers.
> 

Thank you for the explaination, I will look into this.

>>>>> +
>>>>> +       ret = set_frame_rate(sensor);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +
>>>>> +       ret = apply_exposure(sensor);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +
>>>>> +       /* Start streaming */
>>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_START_STREAMING);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +
>>>>> +       ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +
>>>>> +       ret = vgxy61_wait_state(sensor, STREAMING);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +
>>>>> +       return 0;
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_stream_disable(struct vgxy61_dev *sensor)
>>>>> +{
>>>>> +       int ret;
>>>>> +
>>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_STOP_STREAMING);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +
>>>>> +       ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +
>>>>> +       ret = vgxy61_wait_state(sensor, SW_STBY);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +
>>>>> +       return 0;
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_s_stream(struct v4l2_subdev *sd, int enable)
>>>>> +{
>>>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>>> +       int ret = 0;
>>>>> +
>>>>> +       mutex_lock(&sensor->lock);
>>>>> +       dev_dbg(&client->dev, "%s : requested %d / current = %d\n", __func__, enable,
>>>>> +               sensor->streaming);
>>>>> +       if (sensor->streaming == enable)
>>>>> +               goto out;
>>>>> +
>>>>> +       ret = enable ? vgxy61_stream_enable(sensor) : vgxy61_stream_disable(sensor);
>>>>> +       if (!ret)
>>>>> +               sensor->streaming = enable;
>>>>> +
>>>>> +out:
>>>>> +       dev_dbg(&client->dev, "%s current now = %d / %d\n", __func__, sensor->streaming, ret);
>>>>> +       mutex_unlock(&sensor->lock);
>>>>> +
>>>>> +       return ret;
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_g_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *fi)
>>>>> +{
>>>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>>> +
>>>>> +       mutex_lock(&sensor->lock);
>>>>> +       fi->interval = sensor->frame_interval;
>>>>> +       mutex_unlock(&sensor->lock);
>>>>> +
>>>>> +       return 0;
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_s_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *fi)
>>>>> +{
>>>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>>> +       u64 req_int, err, min_err = ~0ULL;
>>>>> +       u64 test_int;
>>>>> +       int i = 0;
>>>>> +       int ret;
>>>>> +
>>>>> +       if (fi->interval.denominator == 0)
>>>>> +               return -EINVAL;
>>>>> +
>>>>> +       mutex_lock(&sensor->lock);
>>>>> +
>>>>> +       if (sensor->streaming) {
>>>>> +               ret = -EBUSY;
>>>>> +               goto out;
>>>>> +       }
>>>>> +
>>>>> +       dev_dbg(&client->dev, "%s request %d/%d\n", __func__,
>>>>> +               fi->interval.numerator, fi->interval.denominator);
>>>>> +       /* Find nearest period */
>>>>> +       req_int = div64_u64((u64)(fi->interval.numerator * 10000), fi->interval.denominator);
>>>>> +       for (i = 0; i < sensor->sensor_rates_nb; i++) {
>>>>> +               test_int = div64_u64((u64)10000, sensor->sensor_rates[i]);
>>>>> +               err = abs(test_int - req_int);
>>>>> +               if (err < min_err) {
>>>>> +                       fi->interval.numerator = 1;
>>>>> +                       fi->interval.denominator = sensor->sensor_rates[i];
>>>>> +                       min_err = err;
>>>>> +               }
>>>>> +       }
>>>>> +       sensor->frame_interval = fi->interval;
>>>>> +       dev_dbg(&client->dev, "%s set     %d/%d\n", __func__,
>>>>> +               fi->interval.numerator, fi->interval.denominator);
>>>>> +
>>>>> +       ret = 0;
>>>>> +out:
>>>>> +       mutex_unlock(&sensor->lock);
>>>>> +
>>>>> +       return ret;
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_enum_mbus_code(struct v4l2_subdev *sd,
>>>>> +                                struct v4l2_subdev_state *sd_state,
>>>>> +                                struct v4l2_subdev_mbus_code_enum *code)
>>>>> +{
>>>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>>> +
>>>>> +       dev_dbg(&client->dev, "%s probe index %d\n", __func__, code->index);
>>>>> +       if (code->index >= ARRAY_SIZE(vgxy61_supported_codes))
>>>>> +               return -EINVAL;
>>>>> +
>>>>> +       code->code = vgxy61_supported_codes[code->index];
>>>>> +
>>>>> +       return 0;
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_get_fmt(struct v4l2_subdev *sd,
>>>>> +                         struct v4l2_subdev_state *sd_state,
>>>>> +                         struct v4l2_subdev_format *format)
>>>>> +{
>>>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>>> +       struct v4l2_mbus_framefmt *fmt;
>>>>> +
>>>>> +       dev_dbg(&client->dev, "%s probe %d\n", __func__, format->pad);
>>>>> +       dev_dbg(&client->dev, "%s %dx%d\n", __func__, format->format.width, format->format.height);
>>>>> +
>>>>> +       mutex_lock(&sensor->lock);
>>>>> +
>>>>> +       if (format->which == V4L2_SUBDEV_FORMAT_TRY)
>>>>> +               fmt = v4l2_subdev_get_try_format(&sensor->sd, sd_state, format->pad);
>>>>> +       else
>>>>> +               fmt = &sensor->fmt;
>>>>> +
>>>>> +       format->format = *fmt;
>>>>> +
>>>>> +       mutex_unlock(&sensor->lock);
>>>>> +
>>>>> +       return 0;
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_set_fmt(struct v4l2_subdev *sd,
>>>>> +                         struct v4l2_subdev_state *sd_state,
>>>>> +                         struct v4l2_subdev_format *format)
>>>>> +{
>>>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>>> +       const struct vgxy61_mode_info *new_mode;
>>>>> +       struct v4l2_mbus_framefmt *fmt;
>>>>> +       int ret;
>>>>> +
>>>>> +       dev_dbg(&client->dev, "%s probe %d\n", __func__, format->pad);
>>>>> +       dev_dbg(&client->dev, "%s %dx%d\n", __func__, format->format.width, format->format.height);
>>>>> +
>>>>> +       mutex_lock(&sensor->lock);
>>>>> +
>>>>> +       if (sensor->streaming) {
>>>>> +               ret = -EBUSY;
>>>>> +               goto out;
>>>>> +       }
>>>>> +
>>>>> +       /* Find best format */
>>>>> +       ret = vgxy61_try_fmt_internal(sd, &format->format, &new_mode);
>>>>> +       if (ret)
>>>>> +               goto out;
>>>>> +
>>>>> +       if (format->which == V4L2_SUBDEV_FORMAT_TRY)
>>>>> +               fmt = v4l2_subdev_get_try_format(sd, sd_state, 0);
>>>>> +       else
>>>>> +               fmt = &sensor->fmt;
>>>>> +       *fmt = format->format;
>>>>> +       sensor->current_mode = new_mode;
>>>>> +       /* Update pixel rate control to reflect new mode */
>>>>> +       __v4l2_ctrl_s_ctrl_int64(sensor->ctrls.pixel_rate, get_pixel_rate(sensor));
>>>>> +
>>>>> +out:
>>>>> +       mutex_unlock(&sensor->lock);
>>>>> +
>>>>> +       return ret;
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state,
>>>>> +                                 struct v4l2_subdev_frame_size_enum *fse)
>>>>> +{
>>>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>>> +
>>>>> +       dev_dbg(&client->dev, "%s for index %d\n", __func__, fse->index);
>>>>> +       if (fse->index >= sensor->sensor_modes_nb)
>>>>> +               return -EINVAL;
>>>>> +
>>>>> +       fse->min_width = sensor->sensor_modes[fse->index].width;
>>>>> +       fse->max_width = fse->min_width;
>>>>> +       fse->min_height = sensor->sensor_modes[fse->index].height;
>>>>> +       fse->max_height = fse->min_height;
>>>>> +
>>>>> +       return 0;
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_enum_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state,
>>>>> +                                     struct v4l2_subdev_frame_interval_enum *fie)
>>>>> +{
>>>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>>> +       const struct vgxy61_mode_info *mode = sensor->sensor_modes;
>>>>> +
>>>>> +       if (fie->index >= sensor->sensor_rates_nb)
>>>>> +               return -EINVAL;
>>>>> +       do {
>>>>> +               if (mode->width == fie->width && mode->height == fie->height)
>>>>> +                       break;
>>>>> +       } while ((++mode)->width);
>>>>> +       if (!mode->width)
>>>>> +               return -EINVAL;
>>>>> +
>>>>> +       fie->interval.numerator = 1;
>>>>> +       fie->interval.denominator = sensor->sensor_rates[fie->index];
>>>>> +
>>>>> +       return 0;
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_update_analog_gain(struct vgxy61_dev *sensor, u32 target)
>>>>> +{
>>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>>> +       unsigned int idx;
>>>>> +       int ret;
>>>>> +
>>>>> +       /* Find smallest analog gains which is above or equal to target gain */
>>>>> +       for (idx = 0; idx < ARRAY_SIZE(analog_gains); idx++) {
>>>>> +               if (analog_gains[idx] >= target)
>>>>> +                       break;
>>>>> +       }
>>>
>>> Why? The units of V4L2_CID_ANALOGUE_GAIN are not specified, so more
>>> typically it is a raw register write.
>>>
>>>
>>
>> The output analog gain is 32/(32-idx). This code maps a gain to its matching register value.
>> The goal is to allow setting the control using a "real world gain" value. I could perform a raw register write ranging from 1 to 16. It would clean the code but would not be representative of the real gain.
>> Maybe I am just overthinking it though.
> 
> Most other sensors make it a raw register value, and you get a
> userspace conversion function, and they do vary between sensors.
> 
> In the Raspberry Pi libcamera IPA, you get the cam_helper functions to
> provide the conversions to/from gain code
> eg https://git.linuxtv.org/libcamera.git/tree/src/ipa/raspberrypi/cam_helper_imx219.cpp#n67
> https://git.linuxtv.org/libcamera.git/tree/src/ipa/raspberrypi/cam_helper_ov5647.cpp#n45
> 

That is perfect for this use case. I will modify to add a raw value, and add the conversion in my sensor's cam_helper on libcamera side.

>>>>> +       /* Cap to maximum gain if no matching gain found */
>>>>> +       if (idx == ARRAY_SIZE(analog_gains))
>>>>> +               idx--;
>>>>> +
>>>>> +       /* Apply gain */
>>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_ANALOG_GAIN, idx);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +
>>>>> +       dev_dbg(&client->dev, "Target analog gain: 0x%04x\n", target);
>>>>> +       dev_dbg(&client->dev, "   Set analog gain: 0x%04x\n", analog_gains[idx]);
>>>>> +
>>>>> +       return 0;
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_update_digital_gain(struct vgxy61_dev *sensor, u32 target)
>>>>> +{
>>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>>> +       int ret;
>>>>> +
>>>>> +       /*
>>>>> +        * For a monochrome version, configuring DIGITAL_GAIN_LONG_CH0 and
>>>>> +        * DIGITAL_GAIN_SHORT_CH0 is enough to configure the gain of all
>>>>> +        * four sub pixels.
>>>>> +        */
>>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_DIGITAL_GAIN_LONG, target);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_DIGITAL_GAIN_SHORT, target);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +
>>>>> +       dev_dbg(&client->dev, "  Set digital gain: 0x%04x\n", target);
>>>>> +
>>>>> +       return 0;
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_update_patgen(struct vgxy61_dev *sensor, u32 index)
>>>>> +{
>>>>> +       u32 pattern = index <= 3 ? index : index + 12;
>>>>> +       u32 reg;
>>>>> +
>>>>> +       reg = (pattern << 18) | (pattern << 4);
>>>>> +       if (index)
>>>>> +               reg |= (1 << 16) | 1;
>>>>> +       return vgxy61_write_reg32(sensor, DEVICE_PATGEN_CTRL, reg);
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_update_gpiox_strobe_mode(struct vgxy61_dev *sensor, enum strobe_modes mode,
>>>>> +                                          int idx)
>>>>> +{
>>>>> +       const u8 index2val[] = {0x0, 0x1, 0x3};
>>>>> +       u16 reg;
>>>>> +       int ret;
>>>>> +
>>>>> +       ret = vgxy61_read_reg16(sensor, DEVICE_SIGNALS_CTRL, &reg);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       reg &= ~(0xf << (idx * 4));
>>>>> +       reg |= index2val[mode] << (idx * 4);
>>>>> +
>>>>> +       return vgxy61_write_reg16(sensor, DEVICE_SIGNALS_CTRL, reg);
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_update_gpios_strobe_mode(struct vgxy61_dev *sensor, enum hdr hdr)
>>>>> +{
>>>>> +       enum strobe_modes strobe_mode;
>>>>> +       int i, ret;
>>>>> +
>>>>> +       switch (hdr) {
>>>>> +       case HDR_LINEAR:
>>>>> +               strobe_mode = STROBE_ENABLED;
>>>>> +               break;
>>>>> +       case HDR_SUB:
>>>>> +       case NO_HDR:
>>>>> +               strobe_mode = STROBE_LONG;
>>>>> +               break;
>>>>> +       default:
>>>>> +               /* Should never happen */
>>>>> +               WARN_ON(true);
>>>>> +               break;
>>>>> +       }
>>>>> +
>>>>> +       for (i = 0; i < NB_GPIOS; i++) {
>>>>> +               ret = vgxy61_update_gpiox_strobe_mode(sensor, strobe_mode, i);
>>>>> +               if (ret)
>>>>> +                       return ret;
>>>>> +       }
>>>>> +
>>>>> +       return 0;
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_update_gpios_strobe_polarity(struct vgxy61_dev *sensor, int polarity)
>>>>> +{
>>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>>> +       int ret;
>>>>> +
>>>>> +       if (sensor->streaming)
>>>>> +               return -EBUSY;
>>>>> +
>>>>> +       dev_dbg(&client->dev, "setting gpios polarity: %d\n", polarity);
>>>>> +
>>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_0_CTRL, polarity << 1);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_1_CTRL, polarity << 1);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_2_CTRL, polarity << 1);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_GPIO_3_CTRL, polarity << 1);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       return vgxy61_write_reg(sensor, DEVICE_SIGNALS_POLARITY_CTRL, polarity);
>>>>> +}
>>>>> +
>>>>> +static int find_max_long_exposure(struct vgxy61_dev *sensor, int frame_length, int short_expo_ratio)
>>>>> +{
>>>>> +       int first_rot_max_expo;
>>>>> +       int second_rot_max_expo;
>>>>> +
>>>>> +       /*
>>>>> +        * Apply first rule of thumb:
>>>>> +        * frame_length < short_line_nb + sensor->sensor_height + sensor->rot_term
>>>>> +        * with short_line_nb = long_line_nb / short_expo_ratio
>>>>> +        */
>>>>> +       first_rot_max_expo = ((frame_length - sensor->sensor_height - sensor->rot_term) *
>>>>> +                            short_expo_ratio) - 1;
>>>>> +
>>>>> +       /*
>>>>> +        * Apply second rule of thumb
>>>>> +        * frame_length < short_line_nb + long_line_nb + VGXY61_EXPOS_ROT_TERM
>>>>> +        * with short_line_nb = long_line_nb / short_expo_ratio
>>>>> +        */
>>>>> +       second_rot_max_expo = (((frame_length - VGXY61_EXPOS_ROT_TERM) * short_expo_ratio) /
>>>>> +                             (short_expo_ratio + 1)) - 1;
>>>>> +
>>>>> +       /* Take the minimum of both rules */
>>>>> +       return min(first_rot_max_expo, second_rot_max_expo);
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_update_exposure(struct vgxy61_dev *sensor, int long_expo_line_nb, enum hdr hdr,
>>>>> +                                 bool clamp)
>>>>> +{
>>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>>> +       int max_long_expo;
>>>>> +       int max_short_expo = 0;
>>>>> +       int short_expo_line_nb = 0;
>>>>> +       /* We use a constant ratio of 10 for linear HDR mode */
>>>>> +       int hdr_linear_ratio = 10;
>>>>> +       u16 frame_length;
>>>>> +       int ret;
>>>>> +
>>>>> +       ret = vgxy61_read_reg16(sensor, DEVICE_FRAME_LENGTH, &frame_length);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +
>>>>> +       /* Long and short integration times must not be less than 10 lines */
>>>>> +       long_expo_line_nb = max(10, long_expo_line_nb);
>>>>> +       long_expo_line_nb = min_t(int, frame_length, long_expo_line_nb);
>>>>> +
>>>>> +       /* Compute short exposure according to hdr mode and long exposure */
>>>>> +       switch (hdr) {
>>>>> +       case HDR_LINEAR:
>>>>> +               max_long_expo = find_max_long_exposure(sensor, frame_length, hdr_linear_ratio);
>>>>> +               max_short_expo = (max_long_expo + (hdr_linear_ratio / 2)) / hdr_linear_ratio;
>>>>> +               short_expo_line_nb = (long_expo_line_nb + (hdr_linear_ratio / 2)) /
>>>>> +                                    hdr_linear_ratio;
>>>>> +               break;
>>>>> +       case HDR_SUB:
>>>>> +               max_long_expo = find_max_long_exposure(sensor, frame_length, 1);
>>>>> +               max_short_expo = max_long_expo;
>>>>> +               short_expo_line_nb = long_expo_line_nb;
>>>>> +               break;
>>>>> +       case NO_HDR:
>>>>> +               /*
>>>>> +                * As short expo is 0 here, only the second rule of thumb applies, see
>>>>> +                * find_max_long_exposure for more
>>>>> +                */
>>>>> +               max_long_expo = frame_length - VGXY61_EXPOS_ROT_TERM;
>>>>> +               break;
>>>>> +       default:
>>>>> +               /* Should never happen */
>>>>> +               WARN_ON(true);
>>>>> +               break;
>>>>> +       }
>>>>> +
>>>>> +       if (long_expo_line_nb > max_long_expo) {
>>>>> +               if (!clamp) {
>>>>> +                       dev_err(&client->dev, "Exposure %d too high (max for this hdr mode %d)\n",
>>>>> +                               long_expo_line_nb, max_long_expo);
>>>>> +                       return -EINVAL;
>>>>> +               }
>>>>> +               dev_warn(&client->dev, "Exposure %d too high for this hdr mode, clamping to %d\n",
>>>>> +                        long_expo_line_nb, max_long_expo);
>>>>> +               long_expo_line_nb = max_long_expo;
>>>>> +               short_expo_line_nb = max_short_expo;
>>>>> +       }
>>>>> +
>>>>> +       dev_dbg(&client->dev, "frame_length %d, long_expo_line_nb %d, short_expo_line_nb %d",
>>>>> +               frame_length, long_expo_line_nb, short_expo_line_nb);
>>>>> +
>>>>> +       /* Apply exposure */
>>>>> +       sensor->expo_long = long_expo_line_nb;
>>>>> +       sensor->expo_short = short_expo_line_nb;
>>>>> +
>>>>> +       if (sensor->streaming)
>>>>> +               return apply_exposure(sensor);
>>>>> +       return 0;
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_update_hdr(struct vgxy61_dev *sensor, u32 index)
>>>>> +{
>>>>> +       const u8 index2val[] = {0x1, 0x4, 0xa};
>>>>> +       int ret;
>>>>> +
>>>>> +       /*
>>>>> +        * Short exposure changes according to HDR mode, do it first as it can
>>>>> +        * violate sensors 'rule of thumbs' and therefore will require to change
>>>>> +        * the long exposure
>>>>> +        */
>>>>> +       ret = vgxy61_update_exposure(sensor, sensor->expo_long, index, true);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +
>>>>> +       /* Update strobe mode according to HDR */
>>>>> +       ret = vgxy61_update_gpios_strobe_mode(sensor, index);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +
>>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_HDR_CTRL, index2val[index]);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       sensor->hdr = index;
>>>>> +
>>>>> +       return 0;
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_get_temp_stream_enabled(struct vgxy61_dev *sensor, int *temp)
>>>>> +{
>>>>> +       int ret;
>>>>> +       u16 temperature;
>>>>> +
>>>>> +       ret = vgxy61_read_reg16(sensor, DEVICE_THSENS1_TEMPERATURE, &temperature);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +
>>>>> +       /* Temperature is expressed in Kelvin in Q10.2 fixed point format*/
>>>>> +       temperature = (temperature & 0x0fff) >> 2;
>>>>> +       temperature = kelvin_to_celsius(temperature);
>>>>> +
>>>>> +       *temp = temperature;
>>>>> +
>>>>> +       return 0;
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_get_temp_stream_disabled(struct vgxy61_dev *sensor, int *temp)
>>>>> +{
>>>>> +       int ret;
>>>>> +
>>>>> +       /* Device needs to be in standby mode if streaming is off */
>>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_STBY, STBY_REQ_TMP_READ);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       ret = vgxy61_poll_reg(sensor, DEVICE_STBY, STBY_NO_REQ);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +
>>>>> +       return vgxy61_get_temp_stream_enabled(sensor, temp);
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_get_temp(struct vgxy61_dev *sensor, int *temp)
>>>>> +{
>>>>> +       *temp = 0;
>>>>> +       if (sensor->streaming)
>>>>> +               return vgxy61_get_temp_stream_enabled(sensor, temp);
>>>>> +       else
>>>>> +               return vgxy61_get_temp_stream_disabled(sensor, temp);
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_s_ctrl(struct v4l2_ctrl *ctrl)
>>>>> +{
>>>>> +       struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
>>>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>>> +       int ret;
>>>>> +
>>>>> +       switch (ctrl->id) {
>>>>> +       case V4L2_CID_EXPOSURE:
>>>>> +               ret = vgxy61_update_exposure(sensor, ctrl->val, sensor->hdr, false);
>>>>> +               ctrl->val = sensor->expo_long;
>>>>> +               break;
>>>>> +       case V4L2_CID_ANALOGUE_GAIN:
>>>>> +               ret = vgxy61_update_analog_gain(sensor, ctrl->val);
>>>>> +               break;
>>>>> +       case V4L2_CID_DIGITAL_GAIN:
>>>>> +               ret = vgxy61_update_digital_gain(sensor, ctrl->val);
>>>>> +               break;
>>>>> +       case V4L2_CID_VFLIP:
>>>>> +       case V4L2_CID_HFLIP:
>>>>> +               if (sensor->streaming) {
>>>>> +                       ret = -EBUSY;
>>>>> +                       break;
>>>>> +               }
>>>>> +               if (ctrl->id == V4L2_CID_VFLIP)
>>>>> +                       sensor->vflip = ctrl->val;
>>>>> +               if (ctrl->id == V4L2_CID_HFLIP)
>>>>> +                       sensor->hflip = ctrl->val;
>>>>> +               ret = vgxy61_write_reg(sensor, DEVICE_ORIENTATION,
>>>>> +                                      sensor->hflip | (sensor->vflip << 1));
>>>>> +               break;
>>>>> +       case V4L2_CID_TEST_PATTERN:
>>>>> +               ret = vgxy61_update_patgen(sensor, ctrl->val);
>>>>> +               break;
>>>>> +       case V4L2_CID_HDR:
>>>>> +               ret = vgxy61_update_hdr(sensor, ctrl->val);
>>>>> +               break;
>>>>> +       case V4L2_CID_GPIOS_STROBE_LONG_START_DELAY:
>>>>> +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_LONG_START_DELAY, ctrl->val);
>>>>> +               break;
>>>>> +       case V4L2_CID_GPIOS_STROBE_LONG_END_DELAY:
>>>>> +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_LONG_END_DELAY, ctrl->val);
>>>>> +               break;
>>>>> +       case V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY:
>>>>> +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_SHORT_START_DELAY, ctrl->val);
>>>>> +               break;
>>>>> +       case V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY:
>>>>> +               ret = vgxy61_write_reg(sensor, DEVICE_STROBE_SHORT_END_DELAY, ctrl->val);
>>>>> +               break;
>>>>> +       default:
>>>>> +               ret = -EINVAL;
>>>>> +               break;
>>>>> +       }
>>>>> +
>>>>> +       return ret;
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
>>>>> +{
>>>>> +       struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
>>>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>>> +       int temperature;
>>>>> +       int ret;
>>>>> +
>>>>> +       switch (ctrl->id) {
>>>>> +       case V4L2_CID_TEMPERATURE:
>>>>> +               ret = vgxy61_get_temp(sensor, &temperature);
>>>>> +               if (ret)
>>>>> +                       break;
>>>>> +               ret = __v4l2_ctrl_s_ctrl(ctrl, temperature);
>>>
>>> Do you need to call __v4l2_ctrl_s_ctrl instead of just updating
>>> ctrl->val? There is no s_ctrl handler for the control as you've
>>> declared it as read only.
>>>
>>
>> Sure.
>>
>>>>> +               break;
>>>>> +       default:
>>>>> +               ret = -EINVAL;
>>>>> +               break;
>>>>> +       }
>>>>> +
>>>>> +       return ret;
>>>>> +}
>>>>> +
>>>>> +static const struct v4l2_ctrl_ops vgxy61_ctrl_ops = {
>>>>> +       .g_volatile_ctrl = vgxy61_g_volatile_ctrl,
>>>>> +       .s_ctrl = vgxy61_s_ctrl,
>>>>> +};
>>>>> +
>>>>> +static const struct v4l2_ctrl_config vgxy61_hdr_ctrl = {
>>>>> +       .ops            = &vgxy61_ctrl_ops,
>>>>> +       .id             = V4L2_CID_HDR,
>>>>> +       .name           = "HDR mode",
>>>>> +       .type           = V4L2_CTRL_TYPE_MENU,
>>>>> +       .min            = 0,
>>>>> +       .max            = ARRAY_SIZE(vgxy61_hdr_modes) - 1,
>>>>> +       .def            = NO_HDR,
>>>>> +       .qmenu          = vgxy61_hdr_modes,
>>>>> +};
>>>>> +
>>>>> +static const struct v4l2_ctrl_config vgxy61_strobe_long_start_delay = {
>>>>> +       .ops            = &vgxy61_ctrl_ops,
>>>>> +       .id             = V4L2_CID_GPIOS_STROBE_LONG_START_DELAY,
>>>>> +       .name           = "Long strobe mode start delay in lines",
>>>>> +       .type           = V4L2_CTRL_TYPE_INTEGER,
>>>>> +       .min            = -128,
>>>>> +       .max            = 127,
>>>>> +       .step           = 1,
>>>>> +       .def            = 0,
>>>>> +       .flags          = 0,
>>>>> +};
>>>>> +
>>>>> +static const struct v4l2_ctrl_config vgxy61_strobe_long_end_delay = {
>>>>> +       .ops            = &vgxy61_ctrl_ops,
>>>>> +       .id             = V4L2_CID_GPIOS_STROBE_LONG_END_DELAY,
>>>>> +       .name           = "Long strobe mode end delay in lines",
>>>>> +       .type           = V4L2_CTRL_TYPE_INTEGER,
>>>>> +       .min            = -128,
>>>>> +       .max            = 127,
>>>>> +       .step           = 1,
>>>>> +       .def            = 0,
>>>>> +       .flags          = 0,
>>>>> +};
>>>>> +
>>>>> +static const struct v4l2_ctrl_config vgxy61_strobe_short_start_delay = {
>>>>> +       .ops            = &vgxy61_ctrl_ops,
>>>>> +       .id             = V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY,
>>>>> +       .name           = "Short strobe mode start delay in lines",
>>>>> +       .type           = V4L2_CTRL_TYPE_INTEGER,
>>>>> +       .min            = -128,
>>>>> +       .max            = 127,
>>>>> +       .step           = 1,
>>>>> +       .def            = 0,
>>>>> +       .flags          = 0,
>>>>> +};
>>>>> +
>>>>> +static const struct v4l2_ctrl_config vgxy61_strobe_short_end_delay = {
>>>>> +       .ops            = &vgxy61_ctrl_ops,
>>>>> +       .id             = V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY,
>>>>> +       .name           = "Short strobe mode end delay in lines",
>>>>> +       .type           = V4L2_CTRL_TYPE_INTEGER,
>>>>> +       .min            = -128,
>>>>> +       .max            = 127,
>>>>> +       .step           = 1,
>>>>> +       .def            = 0,
>>>>> +       .flags          = 0,
>>>>> +};
>>>>> +
>>>>> +static const struct v4l2_ctrl_config vgxy61_temp_ctrl = {
>>>>> +       .ops            = &vgxy61_ctrl_ops,
>>>>> +       .id             = V4L2_CID_TEMPERATURE,
>>>>> +       .name           = "Temperature in °C",
>>>>> +       .type           = V4L2_CTRL_TYPE_INTEGER,
>>>>> +       .min            = -128,
>>>>> +       .max            = 128,
>>>>> +       .step           = 1,
>>>>> +};
>>>>> +
>>>>> +static int vgxy61_init_controls(struct vgxy61_dev *sensor)
>>>>> +{
>>>>> +       const struct v4l2_ctrl_ops *ops = &vgxy61_ctrl_ops;
>>>>> +       struct vgxy61_ctrls *ctrls = &sensor->ctrls;
>>>>> +       struct v4l2_ctrl_handler *hdl = &sensor->ctrls.handler;
>>>>> +       int ret;
>>>>> +
>>>>> +       v4l2_ctrl_handler_init(hdl, 16);
>>>>> +       /* We can use our own mutex for the ctrl lock */
>>>>> +       hdl->lock = &sensor->lock;
>>>>> +       ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, 10, 0xffff, 1,
>>>>> +                                           sensor->expo_long);
>>>>> +       /* This is 8.8 fixed point value */
>>>>> +       ctrls->analog_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, 0x3fff, 1,
>>>>> +                                              0x0);
>>>>> +       /* This is 8.8 fixed point value */
>>>>> +       ctrls->digital_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 0, 0xfff, 1,
>>>>> +                                               0x100);
>>>>> +       ctrls->vflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, sensor->vflip);
>>>>> +       ctrls->hflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, sensor->hflip);
>>>>> +       ctrls->patgen = v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN,
>>>>> +                                                    ARRAY_SIZE(vgxy61_test_pattern_menu) - 1,
>>>>> +                                                    0, 0, vgxy61_test_pattern_menu);
>>>>> +       ctrls->hdr = v4l2_ctrl_new_custom(hdl, &vgxy61_hdr_ctrl, NULL);
>>>>> +       ctrls->pixel_rate = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1,
>>>>> +                                             get_pixel_rate(sensor));
>>>>> +       ctrls->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>>>>> +       ctrls->link_freq = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ,
>>>>> +                                                 ARRAY_SIZE(link_freq) - 1, 0, link_freq);
>>>>> +       ctrls->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>>>>> +       /* Gpios ctrls */
>>>>> +       ctrls->gpios.long_start = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_long_start_delay, NULL);
>>>>> +       ctrls->gpios.long_end = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_long_end_delay, NULL);
>>>>> +       ctrls->gpios.short_start = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_short_start_delay,
>>>>> +                                                       NULL);
>>>>> +       ctrls->gpios.short_end = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_short_end_delay, NULL);
>>>>> +       /* Temperature ctrl */
>>>>> +       ctrls->temp = v4l2_ctrl_new_custom(hdl, &vgxy61_temp_ctrl, NULL);
>>>>> +       ctrls->temp->flags |= V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY;
>>>>> +
>>>>> +       if (hdl->error) {
>>>>> +               ret = hdl->error;
>>>>> +               goto free_ctrls;
>>>>> +       }
>>>>> +
>>>>> +       sensor->sd.ctrl_handler = hdl;
>>>>> +       return 0;
>>>>> +
>>>>> +free_ctrls:
>>>>> +       v4l2_ctrl_handler_free(hdl);
>>>>> +       return ret;
>>>>> +}
>>>>> +
>>>>> +static const struct v4l2_subdev_core_ops vgxy61_core_ops = {
>>>>> +};
>>>>> +
>>>>> +static const struct v4l2_subdev_video_ops vgxy61_video_ops = {
>>>>> +       .s_stream = vgxy61_s_stream,
>>>>> +       .g_frame_interval = vgxy61_g_frame_interval,
>>>>> +       .s_frame_interval = vgxy61_s_frame_interval,
>>>>> +};
>>>>> +
>>>>> +static const struct v4l2_subdev_pad_ops vgxy61_pad_ops = {
>>>>> +       .enum_mbus_code = vgxy61_enum_mbus_code,
>>>>> +       .get_fmt = vgxy61_get_fmt,
>>>>> +       .set_fmt = vgxy61_set_fmt,
>>>>> +       .enum_frame_size = vgxy61_enum_frame_size,
>>>>> +       .enum_frame_interval = vgxy61_enum_frame_interval,
>>>>> +};
>>>>> +
>>>>> +static const struct v4l2_subdev_ops vgxy61_subdev_ops = {
>>>>> +       .core = &vgxy61_core_ops,
>>>>> +       .video = &vgxy61_video_ops,
>>>>> +       .pad = &vgxy61_pad_ops,
>>>>> +};
>>>>> +
>>>>> +static const struct media_entity_operations vgxy61_subdev_entity_ops = {
>>>>> +       .link_validate = v4l2_subdev_link_validate,
>>>>> +};
>>>>> +
>>>>> +/* Set phy polarities */
>>>>> +static int vgxy61_tx_from_ep(struct vgxy61_dev *sensor, struct fwnode_handle *endpoint)
>>>>> +{
>>>>> +       struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
>>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>>> +       u32 log2phy[NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
>>>>> +       u32 phy2log[NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
>>>>> +       int polarities[NB_POLARITIES] = {0, 0, 0, 0, 0};
>>>>> +       int l_nb;
>>>>> +       int p, l;
>>>>> +       int ret;
>>>>> +       int i;
>>>>> +
>>>>> +       ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep);
>>>>> +       if (ret)
>>>>> +               goto error_alloc;
>>>>> +
>>>>> +       l_nb = ep.bus.mipi_csi2.num_data_lanes;
>>>>> +       if (l_nb != 1 && l_nb != 2 && l_nb != 4) {
>>>>> +               dev_err(&client->dev, "invalid data lane number %d\n", l_nb);
>>>>> +               goto error_ep;
>>>>> +       }
>>>>> +
>>>>> +       /* Build log2phy, phy2log and polarities from ep info */
>>>>> +       log2phy[0] = ep.bus.mipi_csi2.clock_lane;
>>>>> +       phy2log[log2phy[0]] = 0;
>>>>> +       for (l = 1; l < l_nb + 1; l++) {
>>>>> +               log2phy[l] = ep.bus.mipi_csi2.data_lanes[l - 1];
>>>>> +               phy2log[log2phy[l]] = l;
>>>>> +       }
>>>>> +       /*
>>>>> +        * Then fill remaining slots for every physical slot to have something valid for hardware
>>>>> +        * stuff.
>>>>> +        */
>>>>> +       for (p = 0; p < NB_POLARITIES; p++) {
>>>>> +               if (phy2log[p] != ~0)
>>>>> +                       continue;
>>>>> +               phy2log[p] = l;
>>>>> +               log2phy[l] = p;
>>>>> +               l++;
>>>>> +       }
>>>>> +       for (l = 0; l < l_nb + 1; l++)
>>>>> +               polarities[l] = ep.bus.mipi_csi2.lane_polarities[l];
>>>>> +
>>>>> +       if (log2phy[0] != 0) {
>>>>> +               dev_err(&client->dev, "clk lane must be map to physical lane 0\n");
>>>>> +               goto error_ep;
>>>>> +       }
>>>>> +       sensor->oif_ctrl = (polarities[4] << 15) + ((phy2log[4] - 1) << 13) +
>>>>> +                          (polarities[3] << 12) + ((phy2log[3] - 1) << 10) +
>>>>> +                          (polarities[2] <<  9) + ((phy2log[2] - 1) <<  7) +
>>>>> +                          (polarities[1] <<  6) + ((phy2log[1] - 1) <<  4) +
>>>>> +                          (polarities[0] <<  3) +
>>>>> +                          l_nb;
>>>>> +       sensor->nb_of_lane = l_nb;
>>>>> +
>>>>> +       dev_dbg(&client->dev, "tx uses %d lanes", l_nb);
>>>>> +       for (i = 0; i < 5; i++) {
>>>>> +               dev_dbg(&client->dev, "log2phy[%d] = %d\n", i, log2phy[i]);
>>>>> +               dev_dbg(&client->dev, "phy2log[%d] = %d\n", i, phy2log[i]);
>>>>> +               dev_dbg(&client->dev, "polarity[%d] = %d\n", i, polarities[i]);
>>>>> +       }
>>>>> +       dev_dbg(&client->dev, "oif_ctrl = 0x%04x\n", sensor->oif_ctrl);
>>>>> +
>>>>> +       v4l2_fwnode_endpoint_free(&ep);
>>>>> +
>>>>> +       return 0;
>>>>> +
>>>>> +error_ep:
>>>>> +       v4l2_fwnode_endpoint_free(&ep);
>>>>> +error_alloc:
>>>>> +
>>>>> +       return -EINVAL;
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_configure(struct vgxy61_dev *sensor)
>>>>> +{
>>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>>> +       int sensor_freq;
>>>>> +       unsigned int prediv;
>>>>> +       unsigned int mult;
>>>>> +       int ret;
>>>>> +
>>>>> +       compute_pll_parameters_by_freq(sensor->clk_freq, &prediv, &mult);
>>>>> +       sensor_freq = (mult * sensor->clk_freq) / prediv;
>>>>> +       /* Frequency to data rate is 1:1 ratio for MIPI */
>>>>> +       sensor->data_rate_in_mbps = sensor_freq;
>>>>> +       /* Video timing ISP path (pixel clock)  requires 804/5 mhz = 160 mhz */
>>>>> +       sensor->pclk = sensor_freq / 5;
>>>>> +
>>>>> +       /* Cache line_length value */
>>>>> +       ret = vgxy61_read_reg16(sensor, DEVICE_LINE_LENGTH, &sensor->line_length);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       /* Configure clocks */
>>>>> +       ret = vgxy61_write_reg32(sensor, DEVICE_EXT_CLOCK, sensor->clk_freq);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_CLK_PLL_PREDIV, prediv);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_CLK_SYS_PLL_MULT, mult);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       /* Configure interface */
>>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_OIF_CTRL, sensor->oif_ctrl);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       /* Disable pwm compression */
>>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_FRAME_CONTENT_CTRL, 0);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       /* Disable asil lines */
>>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_BYPASS_CTRL, 4);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       /* Set gpios polarity according to device tree value */
>>>>> +       ret = vgxy61_update_gpios_strobe_polarity(sensor, sensor->gpios_polarity);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       /* HDR mode */
>>>>> +       ret = vgxy61_update_hdr(sensor, sensor->hdr);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       /* Slave mode */
>>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_VT_CTRL, sensor->slave_mode);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       /* Set pattern generator solid to middle value */
>>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_GR, 0x800);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_R, 0x800);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_B, 0x800);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_GB, 0x800);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_GR, 0x800);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_R, 0x800);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_B, 0x800);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_GB, 0x800);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +
>>>>> +       dev_dbg(&client->dev, "clock prediv = %d\n", prediv);
>>>>> +       dev_dbg(&client->dev, "clock mult = %d\n", mult);
>>>>> +       dev_dbg(&client->dev, "data rate = %d mbps\n",
>>>>> +               sensor->data_rate_in_mbps);
>>>>> +
>>>>> +       return 0;
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_patch(struct vgxy61_dev *sensor)
>>>>> +{
>>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>>> +       u16 patch;
>>>>> +       int ret;
>>>>> +
>>>>> +       ret = vgxy61_write_array(sensor, DEVICE_FWPATCH_START_ADDR, sizeof(patch_array),
>>>>> +                                patch_array);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +
>>>>> +       ret = vgxy61_write_reg(sensor, DEVICE_STBY, 0x10);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +
>>>>> +       ret = vgxy61_poll_reg(sensor, DEVICE_STBY, 0);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +
>>>>> +       ret = vgxy61_read_reg16(sensor, DEVICE_FWPATCH_REVISION, &patch);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +
>>>>> +       if (patch != (DEVICE_FWPATCH_REVISION_MAJOR << 12) +
>>>>> +                    (DEVICE_FWPATCH_REVISION_MINOR << 8) +
>>>>> +                    DEVICE_FWPATCH_REVISION_MICRO) {
>>>>> +               dev_err(&client->dev, "bad patch version expected %d.%d.%d got %d.%d.%d\n",
>>>>> +                       DEVICE_FWPATCH_REVISION_MAJOR,
>>>>> +                       DEVICE_FWPATCH_REVISION_MINOR,
>>>>> +                       DEVICE_FWPATCH_REVISION_MICRO,
>>>>> +                       patch >> 12, (patch >> 8) & 0x0f, patch & 0xff);
>>>>> +               return -ENODEV;
>>>>> +       }
>>>>> +       dev_dbg(&client->dev, "patch %d.%d.%d applied\n",
>>>>> +               patch >> 12, (patch >> 8) & 0x0f, patch & 0xff);
>>>>> +
>>>>> +       return 0;
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_detect_cut_version(struct vgxy61_dev *sensor)
>>>>> +{
>>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>>> +       u16 device_rev;
>>>>> +       int ret;
>>>>> +
>>>>> +       ret = vgxy61_read_reg16(sensor, DEVICE_REVISION, &device_rev);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +
>>>>> +       switch (device_rev >> 8) {
>>>>> +       case 0xA:
>>>>> +               dev_info(&client->dev, "Cut1 detected\n");
>>>>> +               dev_err(&client->dev, "Cut1 not supported by this driver\n");
>>>>> +               return -ENODEV;
>>>>> +       case 0xB:
>>>>> +               dev_info(&client->dev, "Cut2 detected\n");
>>>>> +               return 0;
>>>>> +       case 0xC:
>>>>> +               dev_info(&client->dev, "Cut3 detected\n");
>>>>> +               return 0;
>>>>> +       default:
>>>>> +               dev_err(&client->dev, "Unable to detect cut version\n");
>>>>> +               return -ENODEV;
>>>>> +       }
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_detect(struct vgxy61_dev *sensor)
>>>>> +{
>>>>> +       struct i2c_client *client = sensor->i2c_client;
>>>>> +       u16 id = 0;
>>>>> +       int ret;
>>>>> +       u8 st;
>>>>> +
>>>>> +       ret = vgxy61_read_reg16(sensor, DEVICE_MODEL_ID_REG, &id);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       if (id != VG5661_MODEL_ID && id != VG5761_MODEL_ID) {
>>>>> +               dev_warn(&client->dev, "Unsupported sensor id %x\n", id);
>>>>> +               return -ENODEV;
>>>>> +       }
>>>>> +       dev_dbg(&client->dev, "detected sensor id = 0x%04x\n", id);
>>>>> +
>>>>> +       if (id == VG5761_MODEL_ID) {
>>>>> +               sensor->sensor_width = VGX761_WIDTH;
>>>>> +               sensor->sensor_height = VGX761_HEIGHT;
>>>>> +               sensor->sensor_modes = vgx761_mode_data;
>>>>> +               sensor->sensor_modes_nb = ARRAY_SIZE(vgx761_mode_data);
>>>>> +               sensor->current_mode = &vgx761_mode_data[VGX761_DEFAULT_MODE];
>>>>> +               sensor->rot_term = VGX761_SHORT_ROT_TERM;
>>>>> +               sensor->sensor_rates = vgx761_sensor_frame_rates;
>>>>> +               sensor->sensor_rates_nb = ARRAY_SIZE(vgx761_sensor_frame_rates);
>>>>> +       } else if (id == VG5661_MODEL_ID) {
>>>>> +               sensor->sensor_width = VGX661_WIDTH;
>>>>> +               sensor->sensor_height = VGX661_HEIGHT;
>>>>> +               sensor->sensor_modes = vgx661_mode_data;
>>>>> +               sensor->sensor_modes_nb = ARRAY_SIZE(vgx661_mode_data);
>>>>> +               sensor->current_mode = &vgx661_mode_data[VGX661_DEFAULT_MODE];
>>>>> +               sensor->rot_term = VGX661_SHORT_ROT_TERM;
>>>>> +               sensor->sensor_rates = vgx661_sensor_frame_rates;
>>>>> +               sensor->sensor_rates_nb = ARRAY_SIZE(vgx661_sensor_frame_rates);
>>>>> +       }
>>>>> +
>>>>> +       ret = vgxy61_wait_state(sensor, SW_STBY);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +
>>>>> +       ret = vgxy61_read_reg(sensor, DEVICE_NVM, &st);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +       if (st != NVM_OK)
>>>>> +               dev_warn(&client->dev, "Bad nvm state got %d\n", st);
>>>>> +
>>>>> +       /* Detect cut version */
>>>>> +       ret = vgxy61_detect_cut_version(sensor);
>>>>> +       if (ret)
>>>>> +               return ret;
>>>>> +
>>>>> +       return 0;
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_probe(struct i2c_client *client)
>>>>> +{
>>>>> +       struct device *dev = &client->dev;
>>>>> +       struct fwnode_handle *endpoint;
>>>>> +       struct vgxy61_dev *sensor;
>>>>> +       int ret;
>>>>> +
>>>>> +       sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
>>>>> +       if (!sensor)
>>>>> +               return -ENOMEM;
>>>>> +
>>>>> +       sensor->i2c_client = client;
>>>>> +       sensor->streaming = false;
>>>>> +       sensor->fmt.code = MEDIA_BUS_FMT_SGBRG8_1X8;
>>>>> +       sensor->fmt.field = V4L2_FIELD_NONE;
>>>>> +       sensor->fmt.colorspace = V4L2_COLORSPACE_SRGB;
>>>
>>> ycbcr_enc, quantization, and xfer_func too.
>>>
>>
>> Ok.
>>
>>>>> +       sensor->frame_interval.numerator = 1;
>>>>> +       sensor->frame_interval.denominator = 60;
>>>>> +       sensor->hdr = NO_HDR;
>>>>> +       sensor->expo_long = 200;
>>>>> +       sensor->expo_short = 0;
>>>>> +       sensor->hflip = false;
>>>>> +       sensor->vflip = false;
>>>>> +
>>>>> +       endpoint = fwnode_graph_get_next_endpoint(of_fwnode_handle(dev->of_node), NULL);
>>>>> +       if (!endpoint) {
>>>>> +               dev_err(dev, "endpoint node not found\n");
>>>>> +               return -EINVAL;
>>>>> +       }
>>>>> +
>>>>> +       ret = vgxy61_tx_from_ep(sensor, endpoint);
>>>>> +       fwnode_handle_put(endpoint);
>>>>> +       if (ret) {
>>>>> +               dev_err(dev, "Failed to parse endpoint %d\n", ret);
>>>>> +               return ret;
>>>>> +       }
>>>>> +
>>>>> +       sensor->xclk = devm_clk_get(dev, "xclk");
>>>>> +       if (IS_ERR(sensor->xclk)) {
>>>>> +               dev_err(dev, "failed to get xclk\n");
>>>>> +               return PTR_ERR(sensor->xclk);
>>>>> +       }
>>>>> +       sensor->clk_freq = clk_get_rate(sensor->xclk);
>>>>> +       if (sensor->clk_freq < 6000000 || sensor->clk_freq > 27000000) {
>>>>> +               dev_err(dev, "Only 6Mhz-27Mhz clock range supported. provide %d Hz\n",
>>>>> +                       sensor->clk_freq);
>>>>> +               return -EINVAL;
>>>>> +       }
>>>>> +       sensor->gpios_polarity = of_property_read_bool(dev->of_node, "invert-gpios-polarity");
>>>>> +       sensor->slave_mode = of_property_read_bool(dev->of_node, "slave-mode");
>>>>> +
>>>>> +       v4l2_i2c_subdev_init(&sensor->sd, client, &vgxy61_subdev_ops);
>>>>> +       sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>>>>> +       sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
>>>>> +       sensor->sd.entity.ops = &vgxy61_subdev_entity_ops;
>>>>> +       sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
>>>>> +
>>>>> +       ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
>>>>> +       if (ret) {
>>>>> +               dev_err(&client->dev, "pads init failed %d\n", ret);
>>>>> +               return ret;
>>>>> +       }
>>>>> +
>>>>> +       /* Request optional reset pin */
>>>>> +       sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
>>>>> +
>>>>> +       ret = vgxy61_get_regulators(sensor);
>>>>> +       if (ret) {
>>>>> +               dev_err(&client->dev, "failed to get regulators %d\n", ret);
>>>>> +               goto entity_cleanup;
>>>>> +       }
>>>>> +
>>>>> +       ret = regulator_bulk_enable(VGXY61_NUM_SUPPLIES, sensor->supplies);
>>>>> +       if (ret) {
>>>>> +               dev_err(&client->dev, "failed to enable regulators %d\n", ret);
>>>>> +               goto entity_cleanup;
>>>>> +       }
>>>>> +
>>>>> +       ret = clk_prepare_enable(sensor->xclk);
>>>>> +       if (ret) {
>>>>> +               dev_err(&client->dev, "failed to enable clock %d\n", ret);
>>>>> +               goto disable_bulk;
>>>>> +       }
>>>>> +
>>>>> +       mutex_init(&sensor->lock);
>>>>> +
>>>>> +       /* Apply reset sequence */
>>>>> +       if (sensor->reset_gpio) {
>>>>> +               ret = vgxy61_apply_reset(sensor);
>>>>> +               if (ret) {
>>>>> +                       dev_err(&client->dev, "sensor reset failed %d\n", ret);
>>>>> +                       goto disable_clock;
>>>>> +               }
>>>>> +       }
>>>>> +
>>>>> +       ret = vgxy61_detect(sensor);
>>>>> +       if (ret) {
>>>>> +               dev_err(&client->dev, "sensor detect failed %d\n", ret);
>>>>> +               goto disable_clock;
>>>>> +       }
>>>>> +       sensor->fmt.width = sensor->current_mode->width;
>>>>> +       sensor->fmt.height = sensor->current_mode->height;
>>>>> +
>>>>> +       ret = vgxy61_patch(sensor);
>>>>> +       if (ret) {
>>>>> +               dev_err(&client->dev, "sensor patch failed %d\n", ret);
>>>>> +               goto disable_clock;
>>>>> +       }
>>>>> +
>>>>> +       ret = vgxy61_configure(sensor);
>>>>> +       if (ret) {
>>>>> +               dev_err(&client->dev, "sensor configuration failed %d\n", ret);
>>>>> +               goto disable_clock;
>>>>> +       }
>>>>> +
>>>>> +       ret = vgxy61_init_controls(sensor);
>>>>> +       if (ret) {
>>>>> +               dev_err(&client->dev, "controls initialization failed %d\n", ret);
>>>>> +               goto disable_clock;
>>>>> +       }
>>>>> +
>>>>> +       ret = v4l2_async_register_subdev(&sensor->sd);
>>>>> +       if (ret) {
>>>>> +               dev_err(&client->dev, "async subdev register failed %d\n", ret);
>>>>> +               goto disable_clock;
>>>>> +       }
>>>>> +
>>>>> +       dev_info(&client->dev, "vgxy61 probe successfully\n");
>>>>> +
>>>>> +       return 0;
>>>>> +
>>>>> +disable_clock:
>>>>> +       clk_disable_unprepare(sensor->xclk);
>>>>> +disable_bulk:
>>>>> +       regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
>>>>> +entity_cleanup:
>>>>> +       mutex_destroy(&sensor->lock);
>>>>> +       media_entity_cleanup(&sensor->sd.entity);
>>>>> +       return ret;
>>>>> +}
>>>>> +
>>>>> +static int vgxy61_remove(struct i2c_client *client)
>>>>> +{
>>>>> +       struct v4l2_subdev *sd = i2c_get_clientdata(client);
>>>>> +       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>>> +
>>>>> +       v4l2_async_unregister_subdev(&sensor->sd);
>>>>> +       clk_disable_unprepare(sensor->xclk);
>>>>> +       mutex_destroy(&sensor->lock);
>>>>> +       media_entity_cleanup(&sensor->sd.entity);
>>>>> +       regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
>>>>> +
>>>>> +       return 0;
>>>>> +}
>>>>> +
>>>>> +static const struct of_device_id vgxy61_dt_ids[] = {
>>>>> +       { .compatible = "st,st-vgxy61" },
>>>>> +       { /* sentinel */ }
>>>>> +};
>>>>> +MODULE_DEVICE_TABLE(of, vgxy61_dt_ids);
>>>>> +
>>>>> +static struct i2c_driver vgxy61_i2c_driver = {
>>>>> +       .driver = {
>>>>> +               .name  = "st-vgxy61",
>>>>> +               .of_match_table = vgxy61_dt_ids,
>>>
>>> Support for pm_runtime? Then again the sensor appears to be powered up
>>> in probe and never powered down again.
>>>
>>>   Dave
>>>
>>
>> The power management is handled by the internal CPU. When in SW_STBY the consumption is as its lowest.
>> Therefore sensor does not provide any power up/down register access, only the optional reset pin you saw in the probe.
>> I am not familiar with pm_runtime yet, I think it is not necessary in this case.
> 
> It sounds like a more complex sensor than many. Certainly with that
> binary blob to program you don't want to have to send that more often
> than necessary. It just looks a little odd requesting all the
> regulators and clock, and then just enabling them permanently.
> I don't claim to be a pm_runtime expert, just noting that many sensors
> make use of it.

Agreed. I saw in Documentation/driver-api/media/camera-sensor.rst linked by Sakari in the other thread that all sensors should use pm_runtime as you mentionned. I will look into pm_runtime API and come back in case you troubles.


Regards,

Benjamin

> 
> Cheers.
>   Dave
> 
>>>>> +       },
>>>>> +       .probe_new = vgxy61_probe,
>>>>> +       .remove = vgxy61_remove,
>>>>> +};
>>>>> +
>>>>> +module_i2c_driver(vgxy61_i2c_driver);
>>>>> +
>>>>> +MODULE_AUTHOR("Benjamin Mugnier <benjamin.mugnier@foss.st.com>");
>>>>> +MODULE_AUTHOR("Mickael Guene <mickael.guene@st.com>");
>>>>> +MODULE_AUTHOR("Sylvain Petinot <sylvain.petinot@foss.st.com>");
>>>>> +MODULE_DESCRIPTION("VGXY61 camera subdev driver");
>>>>> +MODULE_LICENSE("GPL v2");
>>>>> --
>>>>> 2.25.1
>>>>>

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

* Re: [PATCH 1/2] media: dt-bindings: media: i2c: Add ST VGXY61 camera sensor binding
  2022-03-13 14:53         ` Laurent Pinchart
@ 2022-03-14  9:41           ` Benjamin Mugnier
  0 siblings, 0 replies; 17+ messages in thread
From: Benjamin Mugnier @ 2022-03-14  9:41 UTC (permalink / raw)
  To: Laurent Pinchart, Sakari Ailus
  Cc: Dave Stevenson, Linux Media Mailing List, alain.volmat,
	hugues.fruchet, sylvain.petinot

Hi Sakari and Laurent,

Thank you for your reviews.

On 13/03/2022 15:53, Laurent Pinchart wrote:
> Hello,
> 
> On Fri, Mar 11, 2022 at 03:13:11PM +0200, Sakari Ailus wrote:
>> On Fri, Mar 11, 2022 at 12:25:38PM +0100, Benjamin Mugnier wrote:
>>> On 10/03/2022 16:38, Dave Stevenson wrote:
>>>> On Thu, 10 Mar 2022 at 13:37, Benjamin Mugnier wrote:
>>>>>
>>>>> Add device tree binding for the ST VGXY61 camera sensor, and update
>>>>> MAINTAINERS file.
>>>>>
>>>>> Signed-off-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com>
>>>>> ---
>>>>>  .../bindings/media/i2c/st,st-vgxy61.yaml      | 134 ++++++++++++++++++
>>>>>  MAINTAINERS                                   |  10 ++
>>>>>  2 files changed, 144 insertions(+)
>>>>>  create mode 100644 Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml
>>>>>
>>>>> diff --git a/Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml b/Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml
>>>>> new file mode 100644
>>>>> index 000000000000..8740ed2623e4
>>>>> --- /dev/null
>>>>> +++ b/Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml
>>>>> @@ -0,0 +1,134 @@
>>>>> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
>>>>> +# Copyright (c) 2022 STMicroelectronics SA.
>>>>> +%YAML 1.2
>>>>> +---
>>>>> +$id: http://devicetree.org/schemas/media/i2c/st,st-vgxy61.yaml#
>>>>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>>>>> +
>>>>> +title: STMicroelectronics VGxy61 HDR Global Shutter Sensor Family Device Tree Bindings
>>>>> +
>>>>> +maintainers:
>>>>> +  - Benjamin Mugnier <benjamin.mugnier@foss.st.com>
>>>>> +  - Sylvain Petinot <sylvain.petinot@foss.st.com>
>>>>> +
>>>>> +description: |-
>>>>> +  STMicroelectronics VGxy61 family has a CSI-2 output port. CSI-2 output is a
>>>>> +  quad lanes 800Mbps per lane.
>>>>> +  Supported formats are RAW8, RAW10, RAW12, RAW14 and RAW16.
>>>>> +  Following part number are supported
>>>>> +  - VG5661 and VG6661 are 1.6 Mpx (1464 x 1104) monochrome and color sensors.
>>>>> +  Maximum frame rate is 75 fps.
>>>>> +  - VG5761 and VG6761 are 2.3 Mpx (1944 x 1204) monochrome and color sensors.
>>>>> +  Maximum frame rate is 60 fps.
>>>>> +
>>>>> +properties:
>>>>> +  compatible:
>>>>> +    const: st,st-vgxy61
>>>>> +
>>>>> +  reg:
>>>>> +    maxItems: 1
>>>>> +
>>>>> +  clocks:
>>>>> +    maxItems: 1
>>>>> +
>>>>> +  clock-names:
>>>>> +    description:
>>>>> +      Input clock for the sensor.
>>>>> +    items:
>>>>> +      - const: xclk
>>
>> Do you need this if you have a single clock?
>>
>> Also see Documentation/driver-api/media/camera-sensor.rst .
>>

Yes the sensor only uses 1 clock.
I will move this to assigned-clocks and use clk_get_rate instead.

>>>>> +
>>>>> +  VCORE-supply:
>>>>> +    description:
>>>>> +      Sensor digital core supply. Must be 1.2 volts.
>>>>> +
>>>>> +  VDDIO-supply:
>>>>> +    description:
>>>>> +      Sensor digital IO supply. Must be 1.8 volts.
>>>>> +
>>>>> +  VANA-supply:
>>>>> +    description:
>>>>> +      Sensor analog supply. Must be 2.8 volts.
>>>>> +
>>>>> +  reset-gpios:
>>>>> +    description:
>>>>> +      Reference to the GPIO connected to the reset pin, if any.
>>>>> +      This is an active low signal to the vgxy61.
>>>>> +
>>>>> +  invert-gpios-polarity:
>>>>> +    description:
>>>>> +      If gpios polarity should be inversed
>>>>
>>>> s/inversed/inverted
>>>>
>>>
>>> Ok.
>>>
>>>>> +    type: boolean
>>>>> +
>>>>> +  slave-mode:
>>>>> +    description:
>>>>> +      If the sensor operates in slave mode
>>>>> +    type: boolean
>>>>
>>>> This is one I've been meaning to raise for a while.
>>>> Is DT the correct place to be configuring hardware sync options for
>>>> image sensors? (There may be the linguistic discussions over master /
>>>> slave terminology too).
>>>> We also have IMX477 and a number of other sensors that support
>>>> external sync control of some form.
>>>>
>>>> As I see it, there are nominally 3 settings - disabled (reduces EMC
>>>> noise), generate syncs, and receive syncs.
>>>> For test purposes it would be useful to be able to switch between
>>>> generate and receive modes at runtime, so that would make it a control
>>>> instead of being fixed in DT.
>>>>
>>>> If it should be configured in DT, then how does ACPI need to handle it?
>>>>
>>>> If DT is the correct place to define the role, should it be in
>>>> video-interfaces.yaml as an optional property, instead of being a
>>>> sensor specific binding?
>>>>
>>>> Sorry, more questions rather than answers.
>>>>
>>>>   Dave
>>>
>>> Maybe I can provide additional info on this sensor to help find an
>>> answer. The "slave mode" has 2 settings: enabled or disabled. If disabled
>>> you are in master mode ('generate sync' and 'disabled' modes Dave
>>> mentionned, they are the same here), and if enabled you are in slave mode
>>> ('receive sync'). As you said he master sends frame sync signals to the
>>> slave each frame acquired, this allows both sensors to synchronize
>>> themselves.
>>>
>>> I put this in the device tree as we only use it for 3D stereocam boards
>>> which already have 2 sensors on them, meaning this is hardware specific.
>>> I don't have any use case where we manually wire 2 sensors on 2 separate
>>> boards. One good point you mentioned is that I may not always want run
>>> this board in master/slaver, and both sensors could run on master mode
>>> without interacting with each other, thus justifying a dedicated v4l2
>>> control.
>>>
>>> Any ideas on how to name it instead of "slave mode" for coherency between
>>> sensors?
>>
>> How is this wired? The slave-mode property documentation explicitly refers
>> to synchronisation signals that do not exists in CSI-2.

Each sensor have a FRAMESTART output pin and an EXTSYNC input pin.
In master mode, the sensor will trigger a rising pulse for each acquired frame on its FRAMESTART pin.
In slave mode, the sensor will wait for a rising pulse on its EXTSYNC pin to trigger a frame acquisition.
What is typically done for sterocams:

┌───────┐FRAMESTART     EXTSYNC┌───────┐
│sensor1├─────────────────────>│sensor2│
└───────┘                      └───────┘

Sensor 1 has its FRAMESTART pin wired to sensor 2's EXTSYNC pin. From this you can set the sensor 1 to master mode, this will guarantee that on each acquired frame, it will emit a rising pulse on its FRAMESTART pin, wired to the sensor 2 EXTSYNC pin. If the sensor 2 is set to slave mode, this will triggering a frame acquisition for sensor 2.
You can also set both sensors to master mode and they will acquire frames independently.

> 
> What is slave mode in this case ? Does it only mean that the sensor is
> externally triggered, or is there something else ? For parallel
> interfaces with H/V sync there's a possibility of the H/V sync signals
> being inputs instead of outputs, but that's not applicable to CSI-2.
> 

Exactly, if the sensor is running in slave mode the frame acquisition is triggered for each rising pulse on its EXTSYNC pin, supposedly by another sensor.
I'm not sure I got what you meant for parallel interfaces, but afaik this behavior seems close to the vertical sync you are mentioning.

Tell me if you need more info.

>>>>> +    #TODO check all this or copy from elsewhere
>>>
>>> Just noticed this and will remove it.
>>>
>>>>> +  port:
>>>>> +    $ref: /schemas/graph.yaml#/$defs/port-base
>>>>> +    additionalProperties: false
>>>>> +
>>>>> +    properties:
>>>>> +      endpoint:
>>>>> +        $ref: /schemas/media/video-interfaces.yaml#
>>>>> +        unevaluatedProperties: false
>>>>> +
>>>>> +        properties:
>>>>> +          clock-lane:
>>>>> +            description:
>>>>> +              Clock lane index
>>>>> +            maxItems: 1
>>
>> Does the device support lane reordering? If not, please drop.
>>

The sensor does not support lane reordering, hence the clock is always on lane 0. Thank you I will remove this.

>>>>> +
>>>>> +          data-lanes:
>>>>> +            description:
>>>>> +              CSI lanes to use
>>>>> +            items:
>>>>> +              - const: 1
>>>>> +              - const: 2
>>>>> +              - const: 3
>>>>> +              - const: 4
>>
>> Which lane configurations does the device support? If it's four lanes only,
>> then you can drop this property, too.
>>

It supports 1, 2 or 4 lanes, and you can choose which lane you want to use. This property is the array of lane indexes you want to use.

>>>>> +
>>>>> +          remote-endpoint: true
>>>>> +
>>>>> +        required:
>>>>> +          - clock-lane
>>>>> +          - data-lanes
>>>>> +          - remote-endpoint
>>
>> Listing remote-endpoint here isn't needed as this comes from the schema.
>>

Ok.

>>>>> +
>>>>> +required:
>>>>> +  - compatible
>>>>> +  - clocks
>>>>> +  - clock-names
>>>>> +  - VCORE-supply
>>>>> +  - VDDIO-supply
>>>>> +  - VANA-supply
>>>>> +
>>>>> +additionalProperties: false
>>>>> +
>>>>> +examples:
>>>>> +  - |
>>>>> +    #include <dt-bindings/gpio/gpio.h>
>>>>> +    i2c {
>>>>> +        #address-cells = <1>;
>>>>> +        #size-cells = <0>;
>>>>> +        vgxy61: csi2tx@10 {
>>>>> +            compatible = "st,st-vgxy61";
>>>>> +            reg = <0x10>;
>>>>> +            status = "okay";
>>>>> +            clocks = <&clk_ext_camera>;
>>>>> +            clock-names = "xclk";
>>>>> +            VCORE-supply = <&v1v2>;
>>>>> +            VDDIO-supply = <&v1v8>;
>>>>> +            VANA-supply = <&v2v8>;
>>>>> +            reset-gpios = <&mfxgpio 18 GPIO_ACTIVE_LOW>;
>>>>> +            port {
>>>>> +                ep0: endpoint {
>>>>> +                    clock-lane = <0>;
>>>>> +                    data-lanes = <1 2 3 4>;
>>>>> +                    remote-endpoint = <&mipi_csi2_out>;
>>>>> +                };
>>>>> +            };
>>>>> +        };
>>>>> +    };
>>>>> +...
>>>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>>>> index 83d27b57016f..f358d15f68a0 100644
>>>>> --- a/MAINTAINERS
>>>>> +++ b/MAINTAINERS
>>>>> @@ -18297,6 +18297,16 @@ S:     Maintained
>>>>>  F:     Documentation/hwmon/stpddc60.rst
>>>>>  F:     drivers/hwmon/pmbus/stpddc60.c
>>>>>
>>>>> +ST VGXY61 DRIVER
>>>>> +M:     Benjamin Mugnier <benjamin.mugnier@foss.st.com>
>>>>> +M:     Sylvain Petinot <sylvain.petinot@foss.st.com>
>>>>> +L:     linux-media@vger.kernel.org
>>>>> +S:     Maintained
>>>>> +T:     git git://linuxtv.org/media_tree.git
>>>>> +F:     Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.txt
>>>>> +F:     drivers/media/i2c/st-vgxy61.c
>>>>> +
>>
>> Extra newline.
>>

Ok.

>>>>> +
>>>>>  ST VL53L0X ToF RANGER(I2C) IIO DRIVER
>>>>>  M:     Song Qiang <songqiang1304521@gmail.com>
>>>>>  L:     linux-iio@vger.kernel.org
> 

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

end of thread, other threads:[~2022-03-14  9:41 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-03-10 13:32 [PATCH 0/2] media: Add ST VGXY61 camera sensor driver Benjamin Mugnier
2022-03-10 13:32 ` [PATCH 1/2] media: dt-bindings: media: i2c: Add ST VGXY61 camera sensor binding Benjamin Mugnier
2022-03-10 15:38   ` Dave Stevenson
2022-03-11 11:25     ` Benjamin Mugnier
2022-03-11 13:13       ` Sakari Ailus
2022-03-13 14:53         ` Laurent Pinchart
2022-03-14  9:41           ` Benjamin Mugnier
2022-03-10 13:32 ` [PATCH 2/2] media: i2c: Add driver for ST VGXY61 camera sensor Benjamin Mugnier
2022-03-10 13:52   ` Kieran Bingham
2022-03-10 15:15     ` Benjamin Mugnier
2022-03-10 15:50       ` Dave Stevenson
2022-03-11  9:34         ` Benjamin Mugnier
2022-03-10 15:21     ` Dave Stevenson
2022-03-10 15:54       ` Kieran Bingham
2022-03-11 10:56       ` Benjamin Mugnier
2022-03-11 11:57         ` Dave Stevenson
2022-03-14  9:33           ` Benjamin Mugnier

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