All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/5] media: Add ST VGXY61 camera sensor driver
@ 2022-04-15 11:18 Benjamin Mugnier
  2022-04-15 11:18 ` [PATCH v2 1/5] media: v4l: Add 1X16 16-bit greyscale media bus code definition Benjamin Mugnier
                   ` (4 more replies)
  0 siblings, 5 replies; 33+ messages in thread
From: Benjamin Mugnier @ 2022-04-15 11:18 UTC (permalink / raw)
  To: linux-media
  Cc: alain.volmat, hugues.fruchet, sylvain.petinot, dave.stevenson,
	sakari.ailus, laurent.pinchart, kieran.bingham, 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.
DT bindings are in 4/5, and the driver in 5/5.
Other commits, in order, introduce the MEDIA_BUS_FMT_Y16_1X16 as a media bus format, a v4l2 standard
control to retrieve sensor temperature, and reserve 16 user controls for the driver (required to set
the HDR mode).
Tested on DragonBoard 410c and on Raspberry Pi 4.

The vblank and hblank support required some work to make sure some v4l2 controls update their
minimum and maximum values to avoid crashing the sensor. This required some time to wrap my head
around, I apologize for the delay.

Here are some observations after the first review:
- The sensor requires patch at boot time. Therefore the pm_runtime power_on function applies the
  patch every time it needs to wake up the sensor. The delay is barely noticeable for the user, and
  we benefit from the power management functionalities.
- After changing wrong SGBRG* media bus formats to proper Y*, as this is a monochrome sensor, I
  noticed that my test target db410c offers limited support for monochrome pixel formats as only
  Y10P pixel format is supported. Moreover, Y10P does not seem to be supported by some viewers
  (qv4l2 for instance returns an unknown pixel format error). v4l-ctl and yavta are doing fine but
  do not offer a video preview, and I couldn't find a tool to unpack the captured packed raw frame.
  As advised by Sakari on irc, adding other monochrome pixel formats to the db410c should not be
  really complicated, neither adding support in v4l-convert for monochrome formats to benefit qv4l2.
  But this leaves me without the ability to preview a video on this specific platform for now on. I
  would like to have your opinions on this matter, maybe I missed something.

Thanks a lot.

v1->v2:
- Move temperature control to v4l2-controls.h as a standard control
- Reserve controls in v4l2-controls.h for HDR control
- Remove GPIO strobbing controls
- Remove references to unused controls in sensor struct
- Use v4l2_find_nearest_size
- Use V4L2_COLORSPACE_RAW instead of V4L2_COLORSPACE_SRGB
- Fill ycbcr_enc, quantization, and xfer_func in v4l2_mbus_framefmt
- Change gain from formula to a register RAW write
- Change temperature control value directly
- Remove clock-name and clock-lanes from device tree bindings
- Declare crop zones in mode_info structure
- Remove frame_interval functions and add hblank and vblank instead
- Update controls dependencies on change
- Fix first exposure rule of thumb being too restrictive
- Disable hflip and vflip controls while streaming
- Change RGB media bus code to Y as the sensor is monochrome
- Add Y16 format to v4l2 media bus formats
- Add get_selection API
- Fix timeout errors while disabling streaming on high framerates
- Support pm_runtime
- Rebase on master

Benjamin Mugnier (5):
  media: v4l: Add 1X16 16-bit greyscale media bus code definition
  media: v4l: ctrls: Add a control for temperature
  media: v4l: ctrls: Add user control base for st-vgxy61 controls
  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      |  125 ++
 .../media/v4l/ext-ctrls-camera.rst            |    3 +
 .../media/v4l/subdev-formats.rst              |   37 +
 MAINTAINERS                                   |    9 +
 drivers/media/i2c/Kconfig                     |   10 +
 drivers/media/i2c/Makefile                    |    1 +
 drivers/media/i2c/st-vgxy61.c                 | 1997 +++++++++++++++++
 drivers/media/v4l2-core/v4l2-ctrls-defs.c     |    4 +
 include/uapi/linux/media-bus-format.h         |    3 +-
 include/uapi/linux/v4l2-controls.h            |    7 +
 10 files changed, 2195 insertions(+), 1 deletion(-)
 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] 33+ messages in thread

* [PATCH v2 1/5] media: v4l: Add 1X16 16-bit greyscale media bus code definition
  2022-04-15 11:18 [PATCH v2 0/5] media: Add ST VGXY61 camera sensor driver Benjamin Mugnier
@ 2022-04-15 11:18 ` Benjamin Mugnier
  2022-04-15 14:47   ` Laurent Pinchart
  2022-04-15 11:18 ` [PATCH v2 2/5] media: v4l: ctrls: Add a control for temperature Benjamin Mugnier
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 33+ messages in thread
From: Benjamin Mugnier @ 2022-04-15 11:18 UTC (permalink / raw)
  To: linux-media
  Cc: alain.volmat, hugues.fruchet, sylvain.petinot, dave.stevenson,
	sakari.ailus, laurent.pinchart, kieran.bingham, Benjamin Mugnier

This extends the greyscale media bus family originally from
MEDIA_BUS_FMT_Y8_1X8 up to MEDIA_BUS_FMT_Y14_1X14 by adding
MEDIA_BUS_FMT_Y16_1X16, and behaves the same way with 16 bits.
Add its documentation in subdev-formats.rst

Signed-off-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com>
---
 .../media/v4l/subdev-formats.rst              | 37 +++++++++++++++++++
 include/uapi/linux/media-bus-format.h         |  3 +-
 2 files changed, 39 insertions(+), 1 deletion(-)

diff --git a/Documentation/userspace-api/media/v4l/subdev-formats.rst b/Documentation/userspace-api/media/v4l/subdev-formats.rst
index 0cbc045d5df6..b8bb16c70d62 100644
--- a/Documentation/userspace-api/media/v4l/subdev-formats.rst
+++ b/Documentation/userspace-api/media/v4l/subdev-formats.rst
@@ -7387,6 +7387,43 @@ The following table list existing packed 36bit wide YUV formats.
       - v\ :sub:`2`
       - v\ :sub:`1`
       - v\ :sub:`0`
+    * .. _MEDIA-BUS-FMT-Y16-1X16:
+
+      - MEDIA_BUS_FMT_Y16_1X16
+      - 0x202e
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      - y\ :sub:`15`
+      - y\ :sub:`14`
+      - y\ :sub:`13`
+      - y\ :sub:`12`
+      - y\ :sub:`11`
+      - y\ :sub:`10`
+      - y\ :sub:`9`
+      - y\ :sub:`8`
+      - y\ :sub:`7`
+      - y\ :sub:`6`
+      - y\ :sub:`5`
+      - y\ :sub:`4`
+      - y\ :sub:`3`
+      - y\ :sub:`2`
+      - y\ :sub:`1`
+      - y\ :sub:`0`
 
 
 .. raw:: latex
diff --git a/include/uapi/linux/media-bus-format.h b/include/uapi/linux/media-bus-format.h
index 0dfc11ee243a..0b436759d94d 100644
--- a/include/uapi/linux/media-bus-format.h
+++ b/include/uapi/linux/media-bus-format.h
@@ -65,7 +65,7 @@
 #define MEDIA_BUS_FMT_RGB121212_1X36		0x1019
 #define MEDIA_BUS_FMT_RGB161616_1X48		0x101a
 
-/* YUV (including grey) - next is	0x202e */
+/* YUV (including grey) - next is	0x202f */
 #define MEDIA_BUS_FMT_Y8_1X8			0x2001
 #define MEDIA_BUS_FMT_UV8_1X8			0x2015
 #define MEDIA_BUS_FMT_UYVY8_1_5X8		0x2002
@@ -111,6 +111,7 @@
 #define MEDIA_BUS_FMT_YUV12_1X36		0x2029
 #define MEDIA_BUS_FMT_YUV16_1X48		0x202a
 #define MEDIA_BUS_FMT_UYYVYY16_0_5X48		0x202b
+#define MEDIA_BUS_FMT_Y16_1X16			0x202e
 
 /* Bayer - next is	0x3021 */
 #define MEDIA_BUS_FMT_SBGGR8_1X8		0x3001
-- 
2.25.1


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

* [PATCH v2 2/5] media: v4l: ctrls: Add a control for temperature
  2022-04-15 11:18 [PATCH v2 0/5] media: Add ST VGXY61 camera sensor driver Benjamin Mugnier
  2022-04-15 11:18 ` [PATCH v2 1/5] media: v4l: Add 1X16 16-bit greyscale media bus code definition Benjamin Mugnier
@ 2022-04-15 11:18 ` Benjamin Mugnier
  2022-04-15 14:37   ` Laurent Pinchart
                     ` (2 more replies)
  2022-04-15 11:18 ` [PATCH v2 3/5] media: v4l: ctrls: Add user control base for st-vgxy61 controls Benjamin Mugnier
                   ` (2 subsequent siblings)
  4 siblings, 3 replies; 33+ messages in thread
From: Benjamin Mugnier @ 2022-04-15 11:18 UTC (permalink / raw)
  To: linux-media
  Cc: alain.volmat, hugues.fruchet, sylvain.petinot, dave.stevenson,
	sakari.ailus, laurent.pinchart, kieran.bingham, Benjamin Mugnier

Add V4L2_CID_TEMPERATURE control to get temperature from sensor in
celsius as a volatile and read-only control, and its documentation.
Useful to monitor thermals from v4l controls for sensors that support
this.

Signed-off-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com>
---
 Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst | 3 +++
 drivers/media/v4l2-core/v4l2-ctrls-defs.c                  | 4 ++++
 include/uapi/linux/v4l2-controls.h                         | 2 ++
 3 files changed, 9 insertions(+)

diff --git a/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst b/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst
index 4c5061aa9cd4..26fa21f5c45a 100644
--- a/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst
+++ b/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst
@@ -661,3 +661,6 @@ enum v4l2_scene_mode -
 .. [#f1]
    This control may be changed to a menu control in the future, if more
    options are required.
+
+``V4L2_CID_TEMPERATURE (integer)``
+    The temperature of the sensor in celsius. This is a read-only control.
diff --git a/drivers/media/v4l2-core/v4l2-ctrls-defs.c b/drivers/media/v4l2-core/v4l2-ctrls-defs.c
index 54ca4e6b820b..45ad3edd59e0 100644
--- a/drivers/media/v4l2-core/v4l2-ctrls-defs.c
+++ b/drivers/media/v4l2-core/v4l2-ctrls-defs.c
@@ -1042,6 +1042,7 @@ const char *v4l2_ctrl_get_name(u32 id)
 	case V4L2_CID_UNIT_CELL_SIZE:		return "Unit Cell Size";
 	case V4L2_CID_CAMERA_ORIENTATION:	return "Camera Orientation";
 	case V4L2_CID_CAMERA_SENSOR_ROTATION:	return "Camera Sensor Rotation";
+	case V4L2_CID_TEMPERATURE:		return "Temperature in °C";
 
 	/* FM Radio Modulator controls */
 	/* Keep the order of the 'case's the same as in v4l2-controls.h! */
@@ -1597,6 +1598,9 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type,
 	case V4L2_CID_RF_TUNER_PLL_LOCK:
 		*flags |= V4L2_CTRL_FLAG_VOLATILE;
 		break;
+	case V4L2_CID_TEMPERATURE:
+		*flags |= V4L2_CTRL_FLAG_READ_ONLY |
+			  V4L2_CTRL_FLAG_VOLATILE;
 	}
 }
 EXPORT_SYMBOL(v4l2_ctrl_fill);
diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h
index bb40129446d4..705b4043c2de 100644
--- a/include/uapi/linux/v4l2-controls.h
+++ b/include/uapi/linux/v4l2-controls.h
@@ -1008,6 +1008,8 @@ enum v4l2_auto_focus_range {
 
 #define V4L2_CID_CAMERA_SENSOR_ROTATION		(V4L2_CID_CAMERA_CLASS_BASE+35)
 
+#define V4L2_CID_TEMPERATURE			(V4L2_CID_CAMERA_CLASS_BASE+36)
+
 /* FM Modulator class control IDs */
 
 #define V4L2_CID_FM_TX_CLASS_BASE		(V4L2_CTRL_CLASS_FM_TX | 0x900)
-- 
2.25.1


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

* [PATCH v2 3/5] media: v4l: ctrls: Add user control base for st-vgxy61 controls
  2022-04-15 11:18 [PATCH v2 0/5] media: Add ST VGXY61 camera sensor driver Benjamin Mugnier
  2022-04-15 11:18 ` [PATCH v2 1/5] media: v4l: Add 1X16 16-bit greyscale media bus code definition Benjamin Mugnier
  2022-04-15 11:18 ` [PATCH v2 2/5] media: v4l: ctrls: Add a control for temperature Benjamin Mugnier
@ 2022-04-15 11:18 ` Benjamin Mugnier
  2022-04-15 14:51   ` Laurent Pinchart
  2022-04-15 11:18 ` [PATCH v2 4/5] media: dt-bindings: media: i2c: Add ST VGXY61 camera sensor binding Benjamin Mugnier
  2022-04-15 11:18 ` [PATCH v2 5/5] media: i2c: Add driver for ST VGXY61 camera sensor Benjamin Mugnier
  4 siblings, 1 reply; 33+ messages in thread
From: Benjamin Mugnier @ 2022-04-15 11:18 UTC (permalink / raw)
  To: linux-media
  Cc: alain.volmat, hugues.fruchet, sylvain.petinot, dave.stevenson,
	sakari.ailus, laurent.pinchart, kieran.bingham, Benjamin Mugnier

Add a control base for st-vgxy61 controls and reserve 16 controls.
This is required for HDR mode user control on the st-vgxy61 camera
sensor.

Signed-off-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com>
---
 include/uapi/linux/v4l2-controls.h | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h
index 705b4043c2de..0bdf77df26ac 100644
--- a/include/uapi/linux/v4l2-controls.h
+++ b/include/uapi/linux/v4l2-controls.h
@@ -225,6 +225,11 @@ enum v4l2_colorfx {
  */
 #define V4L2_CID_USER_ISL7998X_BASE		(V4L2_CID_USER_BASE + 0x1180)
 
+/* The base for the st-vgxy61 driver controls.
+ * We reserve 16 controls for this driver.
+ */
+#define V4L2_CID_USER_STVGXY61_BASE		(V4L2_CID_USER_BASE + 0x1190)
+
 /* MPEG-class control IDs */
 /* The MPEG controls are applicable to all codec controls
  * and the 'MPEG' part of the define is historical */
-- 
2.25.1


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

* [PATCH v2 4/5] media: dt-bindings: media: i2c: Add ST VGXY61 camera sensor binding
  2022-04-15 11:18 [PATCH v2 0/5] media: Add ST VGXY61 camera sensor driver Benjamin Mugnier
                   ` (2 preceding siblings ...)
  2022-04-15 11:18 ` [PATCH v2 3/5] media: v4l: ctrls: Add user control base for st-vgxy61 controls Benjamin Mugnier
@ 2022-04-15 11:18 ` Benjamin Mugnier
  2022-04-15 14:59   ` Laurent Pinchart
  2022-04-15 11:18 ` [PATCH v2 5/5] media: i2c: Add driver for ST VGXY61 camera sensor Benjamin Mugnier
  4 siblings, 1 reply; 33+ messages in thread
From: Benjamin Mugnier @ 2022-04-15 11:18 UTC (permalink / raw)
  To: linux-media
  Cc: alain.volmat, hugues.fruchet, sylvain.petinot, dave.stevenson,
	sakari.ailus, laurent.pinchart, kieran.bingham, 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      | 125 ++++++++++++++++++
 MAINTAINERS                                   |   9 ++
 2 files changed, 134 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..24bff161a661
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml
@@ -0,0 +1,125 @@
+# 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
+
+  port:
+    $ref: /schemas/graph.yaml#/$defs/port-base
+    additionalProperties: false
+
+    properties:
+      endpoint:
+        $ref: /schemas/media/video-interfaces.yaml#
+        unevaluatedProperties: false
+
+        properties:
+          data-lanes:
+            description:
+              CSI lanes to use
+            items:
+              - const: 1
+              - const: 2
+              - const: 3
+              - const: 4
+
+          remote-endpoint: true
+
+        required:
+          - clock-lane
+          - data-lanes
+
+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>;
+            VCORE-supply = <&v1v2>;
+            VDDIO-supply = <&v1v8>;
+            VANA-supply = <&v2v8>;
+            reset-gpios = <&mfxgpio 18 GPIO_ACTIVE_LOW>;
+            port {
+                ep0: endpoint {
+                    data-lanes = <1 2 3 4>;
+                    remote-endpoint = <&mipi_csi2_out>;
+                };
+            };
+        };
+    };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index fd768d43e048..6233e073da30 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18664,6 +18664,15 @@ 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] 33+ messages in thread

* [PATCH v2 5/5] media: i2c: Add driver for ST VGXY61 camera sensor
  2022-04-15 11:18 [PATCH v2 0/5] media: Add ST VGXY61 camera sensor driver Benjamin Mugnier
                   ` (3 preceding siblings ...)
  2022-04-15 11:18 ` [PATCH v2 4/5] media: dt-bindings: media: i2c: Add ST VGXY61 camera sensor binding Benjamin Mugnier
@ 2022-04-15 11:18 ` Benjamin Mugnier
  2022-04-15 15:39   ` Laurent Pinchart
  4 siblings, 1 reply; 33+ messages in thread
From: Benjamin Mugnier @ 2022-04-15 11:18 UTC (permalink / raw)
  To: linux-media
  Cc: alain.volmat, hugues.fruchet, sylvain.petinot, dave.stevenson,
	sakari.ailus, laurent.pinchart, kieran.bingham, 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     |   10 +
 drivers/media/i2c/Makefile    |    1 +
 drivers/media/i2c/st-vgxy61.c | 1997 +++++++++++++++++++++++++++++++++
 3 files changed, 2008 insertions(+)
 create mode 100644 drivers/media/i2c/st-vgxy61.c

diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index fae2baabb773..734f3e1130b0 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -763,6 +763,16 @@ config VIDEO_VS6624
 	  To compile this driver as a module, choose M here: the
 	  module will be called vs6624.
 
+config VIDEO_ST_VGXY61
+	tristate "ST VGXY61 sensor support"
+	depends on OF && GPIOLIB && VIDEO_DEV && 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.
+
 source "drivers/media/i2c/ccs/Kconfig"
 source "drivers/media/i2c/et8ek8/Kconfig"
 source "drivers/media/i2c/m5mols/Kconfig"
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index 3e1696963e7f..7f2362228484 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -116,6 +116,7 @@ obj-$(CONFIG_VIDEO_SAA7185) += saa7185.o
 obj-$(CONFIG_VIDEO_SONY_BTF_MPX) += sony-btf-mpx.o
 obj-$(CONFIG_VIDEO_SR030PC30) += sr030pc30.o
 obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o
+obj-$(CONFIG_VIDEO_ST_VGXY61) += st-vgxy61.o
 obj-$(CONFIG_VIDEO_TC358743) += tc358743.o
 obj-$(CONFIG_VIDEO_TDA1997X) += tda1997x.o
 obj-$(CONFIG_VIDEO_TDA7432) += tda7432.o
diff --git a/drivers/media/i2c/st-vgxy61.c b/drivers/media/i2c/st-vgxy61.c
new file mode 100644
index 000000000000..1219d278e648
--- /dev/null
+++ b/drivers/media/i2c/st-vgxy61.c
@@ -0,0 +1,1997 @@
+// 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/pm_runtime.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 VGXY61_WRITE_MULTIPLE_CHUNK_MAX			16
+#define VGXY61_NB_GPIOS					4
+#define VGXY61_NB_POLARITIES				5
+#define VGXY61_MIN_FRAME_LENGTH				1288
+#define VGXY61_MIN_EXPOSURE				10
+#define VGXY61_HDR_LINEAR_RATIO				10
+#define VGXY61_TIMEOUT_MS				500
+#define VGXY61_FRAME_LENGTH_DEF				1313
+#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_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_STVGXY61_BASE + 0)
+
+#define DEVICE_FWPATCH_REVISION_MAJOR			2
+#define DEVICE_FWPATCH_REVISION_MINOR			0
+#define DEVICE_FWPATCH_REVISION_MICRO			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)
+
+enum bin_mode {
+	BIN_MODE_NORMAL,
+	BIN_MODE_DIGITAL_X2,
+	BIN_MODE_DIGITAL_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;
+	struct v4l2_rect crop;
+};
+
+static const u32 vgxy61_supported_codes[] = {
+	MEDIA_BUS_FMT_Y8_1X8,
+	MEDIA_BUS_FMT_Y10_1X10,
+	MEDIA_BUS_FMT_Y12_1X12,
+	MEDIA_BUS_FMT_Y14_1X14,
+	MEDIA_BUS_FMT_Y16_1X16
+};
+
+static const struct vgxy61_mode_info vgx661_mode_data[] = {
+	{
+		.width = VGX661_WIDTH,
+		.height = VGX661_HEIGHT,
+		.bin_mode = BIN_MODE_NORMAL,
+		.crop = {
+			.left = 0,
+			.top = 0,
+			.width = VGX661_WIDTH,
+			.height = VGX661_HEIGHT,
+		},
+	},
+	{
+		.width = 1280,
+		.height = 720,
+		.bin_mode = BIN_MODE_NORMAL,
+		.crop = {
+			.left = 92,
+			.top = 192,
+			.width = 1280,
+			.height = 720,
+		},
+	},
+	{
+		.width = 640,
+		.height = 480,
+		.bin_mode = BIN_MODE_DIGITAL_X2,
+		.crop = {
+			.left = 92,
+			.top = 72,
+			.width = 1280,
+			.height = 960,
+		},
+	},
+	{
+		.width = 320,
+		.height = 240,
+		.bin_mode = BIN_MODE_DIGITAL_X4,
+		.crop = {
+			.left = 92,
+			.top = 72,
+			.width = 1280,
+			.height = 960,
+		},
+	},
+};
+
+static const struct vgxy61_mode_info vgx761_mode_data[] = {
+	{
+		.width = VGX761_WIDTH,
+		.height = VGX761_HEIGHT,
+		.bin_mode = BIN_MODE_NORMAL,
+		.crop = {
+			.left = 0,
+			.top = 0,
+			.width = VGX761_WIDTH,
+			.height = VGX761_HEIGHT,
+		},
+	},
+	{
+		.width = 1920,
+		.height = 1080,
+		.bin_mode = BIN_MODE_NORMAL,
+		.crop = {
+			.left = 12,
+			.top = 62,
+			.width = 1920,
+			.height = 1080,
+		},
+	},
+	{
+		.width = 1280,
+		.height = 720,
+		.bin_mode = BIN_MODE_NORMAL,
+		.crop = {
+			.left = 332,
+			.top = 242,
+			.width = 1280,
+			.height = 720,
+		},
+	},
+	{
+		.width = 640,
+		.height = 480,
+		.bin_mode = BIN_MODE_DIGITAL_X2,
+		.crop = {
+			.left = 332,
+			.top = 122,
+			.width = 1280,
+			.height = 960,
+		},
+	},
+	{
+		.width = 320,
+		.height = 240,
+		.bin_mode = BIN_MODE_DIGITAL_X4,
+		.crop = {
+			.left = 332,
+			.top = 122,
+			.width = 1280,
+			.height = 960,
+		},
+	},
+};
+
+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 id;
+	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 v4l2_ctrl_handler ctrl_handler;
+	struct v4l2_ctrl *pixel_rate_ctrl;
+	struct v4l2_ctrl *expo_ctrl;
+	struct v4l2_ctrl *vblank_ctrl;
+	struct v4l2_ctrl *vflip_ctrl;
+	struct v4l2_ctrl *hflip_ctrl;
+	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;
+	struct v4l2_fract frame_interval;
+	bool hflip;
+	bool vflip;
+	enum hdr hdr;
+	int expo_long;
+	int expo_short;
+	int expo_max;
+	int expo_min;
+	int vblank;
+	int vblank_min;
+	u16 frame_length;
+};
+
+static u8 get_bpp_by_code(__u32 code)
+{
+	switch (code) {
+	case MEDIA_BUS_FMT_Y8_1X8:
+		return 8;
+	case MEDIA_BUS_FMT_Y10_1X10:
+		return 10;
+	case MEDIA_BUS_FMT_Y12_1X12:
+		return 12;
+	case MEDIA_BUS_FMT_Y14_1X14:
+		return 14;
+	case MEDIA_BUS_FMT_Y16_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_Y8_1X8:
+		return MIPI_CSI2_DT_RAW8;
+	case MEDIA_BUS_FMT_Y10_1X10:
+		return MIPI_CSI2_DT_RAW10;
+	case MEDIA_BUS_FMT_Y12_1X12:
+		return MIPI_CSI2_DT_RAW12;
+	case MEDIA_BUS_FMT_Y14_1X14:
+		return MIPI_CSI2_DT_RAW14;
+	case MEDIA_BUS_FMT_Y16_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, ctrl_handler)->sd;
+}
+
+static int get_chunk_size(struct vgxy61_dev *sensor)
+{
+	struct i2c_adapter *adapter = sensor->i2c_client->adapter;
+	int max_write_len = VGXY61_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, VGXY61_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[VGXY61_WRITE_MULTIPLE_CHUNK_MAX + 2];
+	int i;
+	int ret;
+
+	if (len > VGXY61_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, int timeout_ms)
+{
+	const int loop_delay_ms = 10;
+	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, int timeout_ms)
+{
+	return vgxy61_poll_reg(sensor, DEVICE_SYSTEM_FSM, state, timeout_ms);
+}
+
+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 = sensor->current_mode->crop.height / sensor->current_mode->height;
+	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)
+{
+	return vgxy61_write_reg16(sensor, DEVICE_FRAME_LENGTH, sensor->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, VGXY61_TIMEOUT_MS);
+}
+
+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 */
+	mode = v4l2_find_nearest_size(sensor->sensor_modes, sensor->sensor_modes_nb,
+				      width, height, fmt->width, fmt->height);
+	if (new_mode)
+		*new_mode = mode;
+
+	fmt->code = vgxy61_supported_codes[index];
+	fmt->width = mode->width;
+	fmt->height = mode->height;
+	fmt->colorspace = V4L2_COLORSPACE_RAW;
+	fmt->field = V4L2_FIELD_NONE;
+	fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+	fmt->quantization = V4L2_QUANTIZATION_DEFAULT;
+	fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT;
+
+	return 0;
+}
+
+static int vgxy61_get_selection(struct v4l2_subdev *sd,
+				struct v4l2_subdev_state *sd_state,
+				struct v4l2_subdev_selection *sel)
+{
+	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
+
+	switch (sel->target) {
+	case V4L2_SEL_TGT_CROP:
+		sel->r = sensor->current_mode->crop;
+		return 0;
+	case V4L2_SEL_TGT_NATIVE_SIZE:
+	case V4L2_SEL_TGT_CROP_DEFAULT:
+	case V4L2_SEL_TGT_CROP_BOUNDS:
+		sel->r.top = 0;
+		sel->r.left = 0;
+		sel->r.width = sensor->sensor_width;
+		sel->r.height = sensor->sensor_height;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static int vgxy61_stream_enable(struct vgxy61_dev *sensor)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd);
+	const struct v4l2_rect *crop = &sensor->current_mode->crop;
+	int ret;
+
+	ret = vgxy61_check_bw(sensor);
+	if (ret)
+		return ret;
+
+	ret = pm_runtime_get_sync(&client->dev);
+	if (ret < 0) {
+		pm_runtime_put_noidle(&client->dev);
+		return ret;
+	}
+
+	/* Configure sensor */
+	ret = vgxy61_write_reg(sensor, DEVICE_FORMAT_CTRL, get_bpp_by_code(sensor->fmt.code));
+	if (ret)
+		goto err_rpm_put;
+	ret = vgxy61_write_reg(sensor, DEVICE_OIF_ROI0_CTRL,
+			       get_data_type_by_code(sensor->fmt.code));
+	if (ret)
+		goto err_rpm_put;
+
+	ret = vgxy61_write_reg(sensor, DEVICE_READOUT_CTRL, sensor->current_mode->bin_mode);
+	if (ret)
+		goto err_rpm_put;
+	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_H, crop->left);
+	if (ret)
+		goto err_rpm_put;
+	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_H, crop->left + crop->width - 1);
+	if (ret)
+		goto err_rpm_put;
+	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_V, crop->top);
+	if (ret)
+		goto err_rpm_put;
+	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_V, crop->top + crop->height - 1);
+	if (ret)
+		goto err_rpm_put;
+
+	ret = set_frame_rate(sensor);
+	if (ret)
+		goto err_rpm_put;
+
+	ret = apply_exposure(sensor);
+	if (ret)
+		goto err_rpm_put;
+
+	/* Start streaming */
+	ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_START_STREAMING);
+	if (ret)
+		goto err_rpm_put;
+
+	ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST, VGXY61_TIMEOUT_MS);
+	if (ret)
+		goto err_rpm_put;
+
+	ret = vgxy61_wait_state(sensor, STREAMING, VGXY61_TIMEOUT_MS);
+	if (ret)
+		goto err_rpm_put;
+
+	/* vflip and hflip cannot change during streaming */
+	__v4l2_ctrl_grab(sensor->vflip_ctrl, true);
+	__v4l2_ctrl_grab(sensor->hflip_ctrl, true);
+
+	return 0;
+
+err_rpm_put:
+	pm_runtime_put(&client->dev);
+	return ret;
+}
+
+static int vgxy61_stream_disable(struct vgxy61_dev *sensor)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd);
+	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, 2000);
+	if (ret)
+		return ret;
+
+	ret = vgxy61_wait_state(sensor, SW_STBY, VGXY61_TIMEOUT_MS);
+	if (ret)
+		return ret;
+
+	__v4l2_ctrl_grab(sensor->vflip_ctrl, true);
+	__v4l2_ctrl_grab(sensor->hflip_ctrl, true);
+
+	pm_runtime_put(&client->dev);
+
+	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_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_get_vblank_min(struct vgxy61_dev *sensor, enum hdr hdr)
+{
+	int min_vblank =  VGXY61_MIN_FRAME_LENGTH - sensor->current_mode->crop.height;
+	/* Ensure the first tule of thumb can't be negative */
+	int min_vblank_hdr =  VGXY61_MIN_EXPOSURE + sensor->rot_term + 1;
+
+	if (hdr != NO_HDR)
+		return max(min_vblank, min_vblank_hdr);
+	return min_vblank;
+}
+
+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_update_analog_gain(struct vgxy61_dev *sensor, u32 target)
+{
+	struct i2c_client *client = sensor->i2c_client;
+	int ret;
+
+	/* Apply gain */
+	ret = vgxy61_write_reg(sensor, DEVICE_ANALOG_GAIN, target);
+	if (ret)
+		return ret;
+
+	dev_dbg(&client->dev, "Set analog gain: 0x%04x\n", target);
+
+	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 < VGXY61_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 vgxy61_get_expo_long_max(struct vgxy61_dev *sensor, int short_expo_ratio)
+{
+	int first_rot_max_expo, second_rot_max_expo, third_rot_max_expo;
+
+	/* Apply sensor rule of thumbs */
+	/* Avoid bad pixel line at the botom of the image */
+	first_rot_max_expo =
+		((sensor->frame_length - sensor->current_mode->crop.height - sensor->rot_term) *
+		short_expo_ratio) - 1;
+
+	/* Avoid sensor crash */
+	second_rot_max_expo =
+		(((sensor->frame_length - VGXY61_EXPOS_ROT_TERM) * short_expo_ratio) /
+		(short_expo_ratio + 1)) - 1;
+
+	/* Avoid sensor crash */
+	third_rot_max_expo = (sensor->frame_length / 71) * short_expo_ratio;
+
+	/* Take the minimum from all rules */
+	return min(min(first_rot_max_expo, second_rot_max_expo), third_rot_max_expo);
+}
+
+static int vgxy61_update_exposure(struct vgxy61_dev *sensor, int new_expo_long, enum hdr hdr)
+{
+	struct i2c_client *client = sensor->i2c_client;
+	int new_expo_short = 0;
+	int expo_short_max = 0;
+	int expo_long_min = VGXY61_MIN_EXPOSURE;
+	int expo_long_max;
+
+	/* Compute short exposure according to hdr mode and long exposure */
+	switch (hdr) {
+	case HDR_LINEAR:
+		/* Take ratio into account for minimal exposures in HDR_LINEAR */
+		expo_long_min = VGXY61_MIN_EXPOSURE * VGXY61_HDR_LINEAR_RATIO;
+		new_expo_long = max(expo_long_min, new_expo_long);
+
+		expo_long_max = vgxy61_get_expo_long_max(sensor, VGXY61_HDR_LINEAR_RATIO);
+		expo_short_max =
+			(expo_long_max + (VGXY61_HDR_LINEAR_RATIO / 2)) / VGXY61_HDR_LINEAR_RATIO;
+		new_expo_short =
+			(new_expo_long + (VGXY61_HDR_LINEAR_RATIO / 2)) / VGXY61_HDR_LINEAR_RATIO;
+		break;
+	case HDR_SUB:
+		new_expo_long = max(expo_long_min, new_expo_long);
+
+		expo_long_max = vgxy61_get_expo_long_max(sensor, 1);
+		/* Short and long are the same in HDR_SUB */
+		expo_short_max = expo_long_max;
+		new_expo_short = new_expo_long;
+		break;
+	case NO_HDR:
+		new_expo_long = max(expo_long_min, new_expo_long);
+
+		/*
+		 * As short expo is 0 here, only the second rule of thumb applies, see
+		 * vgxy61_get_expo_long_max for more
+		 */
+		expo_long_max = sensor->frame_length - VGXY61_EXPOS_ROT_TERM;
+		break;
+	default:
+		/* Should never happen */
+		WARN_ON(true);
+		break;
+	}
+
+	/* If this happens, something is wrong with formulas */
+	WARN_ON(expo_long_min > expo_long_max);
+
+	if (new_expo_long > expo_long_max) {
+		dev_warn(&client->dev, "Exposure %d too high, clamping to %d\n",
+			 new_expo_long, expo_long_max);
+		new_expo_long = expo_long_max;
+		new_expo_short = expo_short_max;
+	}
+
+	dev_dbg(&client->dev, "frame_length %d, new_expo_long %d, new_expo_short %d\n",
+		sensor->frame_length, new_expo_long, new_expo_short);
+	dev_dbg(&client->dev, "expo_long_min %d, expo_long_max %d\n",
+		expo_long_min, expo_long_max);
+
+	/* Apply exposure */
+	sensor->expo_long = new_expo_long;
+	sensor->expo_short = new_expo_short;
+	sensor->expo_max = expo_long_max;
+	sensor->expo_min = expo_long_min;
+
+	if (sensor->streaming)
+		return apply_exposure(sensor);
+	return 0;
+}
+
+static int vgxy61_update_vblank(struct vgxy61_dev *sensor, int vblank, int hdr)
+{
+	int ret;
+
+	sensor->vblank_min = vgxy61_get_vblank_min(sensor, hdr);
+	sensor->vblank = max(sensor->vblank_min, vblank);
+	sensor->frame_length = sensor->current_mode->crop.height + sensor->vblank;
+
+	/* Update exposure according to vblank */
+	ret = vgxy61_update_exposure(sensor, sensor->expo_long, hdr);
+	if (ret)
+		return ret;
+
+	if (sensor->streaming)
+		return set_frame_rate(sensor);
+	return 0;
+}
+
+static int vgxy61_update_hdr(struct vgxy61_dev *sensor, u32 index)
+{
+	const u8 index2val[] = {0x1, 0x4, 0xa};
+	int ret;
+
+	/*
+	 * vblank and short exposure change 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_vblank(sensor, sensor->vblank, index);
+	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_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);
+		*fmt = format->format;
+	} else if (sensor->current_mode != new_mode || sensor->fmt.code != format->format.code) {
+		fmt = &sensor->fmt;
+		*fmt = format->format;
+
+		sensor->current_mode = new_mode;
+
+		/* Reset vblank and framelength to default */
+		ret = vgxy61_update_vblank(sensor,
+					   VGXY61_FRAME_LENGTH_DEF -
+					   sensor->current_mode->crop.height,
+					   sensor->hdr);
+
+		/* Update controls to reflect new mode */
+		__v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate_ctrl, get_pixel_rate(sensor));
+		__v4l2_ctrl_modify_range(sensor->vblank_ctrl, sensor->vblank_min,
+					 0xffff - sensor->current_mode->crop.height, 1,
+					 sensor->vblank);
+		__v4l2_ctrl_s_ctrl(sensor->vblank_ctrl, sensor->vblank);
+		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
+					 sensor->expo_max, 1,
+					 sensor->expo_long);
+	}
+
+out:
+	mutex_unlock(&sensor->lock);
+
+	return ret;
+}
+
+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, VGXY61_TIMEOUT_MS);
+	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);
+		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);
+		/* Update vblank and exposure controls to match new hdr */
+		__v4l2_ctrl_modify_range(sensor->vblank_ctrl, sensor->vblank_min,
+					 0xffff - sensor->current_mode->crop.height, 1,
+					 sensor->vblank);
+		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
+					 sensor->expo_max, 1, sensor->expo_long);
+		break;
+	case V4L2_CID_VBLANK:
+		ret = vgxy61_update_vblank(sensor, ctrl->val, sensor->hdr);
+		/* Update exposure control to match new vblank */
+		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
+					 sensor->expo_max, 1, sensor->expo_long);
+		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 ret;
+
+	switch (ctrl->id) {
+	case V4L2_CID_TEMPERATURE:
+		ret = vgxy61_get_temp(sensor, &ctrl->val);
+		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 int vgxy61_init_controls(struct vgxy61_dev *sensor)
+{
+	const struct v4l2_ctrl_ops *ops = &vgxy61_ctrl_ops;
+	struct v4l2_ctrl_handler *hdl = &sensor->ctrl_handler;
+	struct v4l2_ctrl *ctrl;
+	int ret;
+
+	v4l2_ctrl_handler_init(hdl, 16);
+	mutex_init(&sensor->lock);
+	/* We can use our own mutex for the ctrl lock */
+	hdl->lock = &sensor->lock;
+	/* Standard controls */
+	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, 0x1c, 1, 0x0);
+	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 0, 0xfff, 1, 256);
+	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_TEMPERATURE, -128, 128, 1, 0);
+	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);
+	v4l2_ctrl_new_custom(hdl, &vgxy61_hdr_ctrl, NULL);
+	ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HBLANK, 0, sensor->line_length, 1,
+				 sensor->line_length - sensor->current_mode->width);
+	ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+	/* Custom controls */
+	ctrl = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ, ARRAY_SIZE(link_freq) - 1, 0,
+				      link_freq);
+	ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+	/* Keep a pointer to these controls as we need to update them when setting the format */
+	sensor->pixel_rate_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1,
+						    get_pixel_rate(sensor));
+	sensor->pixel_rate_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+	sensor->expo_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, sensor->expo_min,
+					      sensor->expo_max, 1, sensor->expo_long);
+	sensor->vblank_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VBLANK, sensor->vblank_min,
+						0xffff - sensor->current_mode->crop.height, 1,
+						sensor->vblank);
+	sensor->vflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, sensor->vflip);
+	sensor->hflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, sensor->hflip);
+
+	if (hdl->error) {
+		ret = hdl->error;
+		goto free_ctrls;
+	}
+
+	sensor->sd.ctrl_handler = hdl;
+	return 0;
+
+free_ctrls:
+	v4l2_ctrl_handler_free(hdl);
+	mutex_destroy(&sensor->lock);
+	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,
+};
+
+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,
+	.get_selection = vgxy61_get_selection,
+	.enum_frame_size = vgxy61_enum_frame_size,
+};
+
+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[VGXY61_NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
+	u32 phy2log[VGXY61_NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
+	int polarities[VGXY61_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 < VGXY61_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;
+	/* 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, VGXY61_TIMEOUT_MS);
+	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_dbg(&client->dev, "Cut1 detected\n");
+		dev_err(&client->dev, "Cut1 not supported by this driver\n");
+		return -ENODEV;
+	case 0xB:
+		dev_dbg(&client->dev, "Cut2 detected\n");
+		return 0;
+	case 0xC:
+		dev_dbg(&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);
+	sensor->id = id;
+
+	ret = vgxy61_wait_state(sensor, SW_STBY, VGXY61_TIMEOUT_MS);
+	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;
+}
+
+/* Power/clock management functions */
+static int vgxy61_power_on(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
+	int ret;
+
+	ret = vgxy61_get_regulators(sensor);
+	if (ret) {
+		dev_err(&client->dev, "failed to get regulators %d\n", ret);
+		return ret;
+	}
+
+	ret = regulator_bulk_enable(VGXY61_NUM_SUPPLIES, sensor->supplies);
+	if (ret) {
+		dev_err(&client->dev, "failed to enable regulators %d\n", ret);
+		return ret;
+	}
+
+	ret = clk_prepare_enable(sensor->xclk);
+	if (ret) {
+		dev_err(&client->dev, "failed to enable clock %d\n", ret);
+		goto disable_bulk;
+	}
+
+	/* 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;
+	}
+
+	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;
+	}
+
+	return 0;
+
+disable_clock:
+	clk_disable_unprepare(sensor->xclk);
+disable_bulk:
+	regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
+
+	return ret;
+}
+
+static int vgxy61_power_off(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
+
+	clk_disable_unprepare(sensor->xclk);
+	regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
+	return 0;
+}
+
+static int __maybe_unused vgxy61_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
+
+	if (sensor->streaming)
+		vgxy61_stream_disable(sensor);
+
+	return 0;
+}
+
+static int __maybe_unused vgxy61_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
+	int ret;
+
+	if (sensor->streaming) {
+		ret = vgxy61_stream_enable(sensor);
+		if (ret)
+			goto error;
+	}
+
+	return 0;
+
+error:
+	vgxy61_stream_disable(sensor);
+	sensor->streaming = false;
+
+	return ret;
+}
+
+static void vgxy61_set_default_format(struct vgxy61_dev *sensor)
+{
+	if (sensor->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;
+	} else if (sensor->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;
+	} else {
+		/* Should never happen */
+		WARN_ON(true);
+	}
+	sensor->fmt.width = sensor->current_mode->width;
+	sensor->fmt.height = sensor->current_mode->height;
+	sensor->vblank = sensor->frame_length - sensor->current_mode->crop.height;
+}
+
+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_Y8_1X8;
+	sensor->fmt.colorspace = V4L2_COLORSPACE_RAW;
+	sensor->fmt.field = V4L2_FIELD_NONE;
+	sensor->fmt.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+	sensor->fmt.quantization = V4L2_QUANTIZATION_DEFAULT;
+	sensor->fmt.xfer_func = V4L2_XFER_FUNC_DEFAULT;
+	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;
+	sensor->frame_length = VGXY61_FRAME_LENGTH_DEF;
+
+	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, NULL);
+	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 < 6 * HZ_PER_MHZ || sensor->clk_freq > 27 * HZ_PER_MHZ) {
+		dev_err(dev, "Only 6Mhz-27Mhz clock range supported. provide %lu MHz\n",
+			sensor->clk_freq / HZ_PER_MHZ);
+		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;
+
+	/* Request optional reset pin */
+	sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+
+	ret = vgxy61_power_on(dev);
+	if (ret)
+		return ret;
+
+	vgxy61_set_default_format(sensor);
+
+	/* HDR mode */
+	ret = vgxy61_update_hdr(sensor, sensor->hdr);
+	if (ret)
+		return ret;
+
+	ret = vgxy61_init_controls(sensor);
+	if (ret) {
+		dev_err(&client->dev, "controls initialization failed %d\n", ret);
+		goto error_power_off;
+	}
+
+	ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
+	if (ret) {
+		dev_err(&client->dev, "pads init failed %d\n", ret);
+		goto error_handler_free;
+	}
+
+	ret = v4l2_async_register_subdev(&sensor->sd);
+	if (ret) {
+		dev_err(&client->dev, "async subdev register failed %d\n", ret);
+		goto error_media_entity;
+	}
+
+	/* Enable runtime PM and turn off the device */
+	pm_runtime_set_active(dev);
+	pm_runtime_enable(dev);
+	pm_runtime_idle(dev);
+
+	dev_info(&client->dev, "vgxy61 probe successfully\n");
+
+	return 0;
+
+error_media_entity:
+	media_entity_cleanup(&sensor->sd.entity);
+error_handler_free:
+	v4l2_ctrl_handler_free(sensor->sd.ctrl_handler);
+	mutex_destroy(&sensor->lock);
+error_power_off:
+	vgxy61_power_off(dev);
+
+	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);
+	mutex_destroy(&sensor->lock);
+	media_entity_cleanup(&sensor->sd.entity);
+
+	pm_runtime_disable(&client->dev);
+	if (!pm_runtime_status_suspended(&client->dev))
+		vgxy61_power_off(&client->dev);
+	pm_runtime_set_suspended(&client->dev);
+
+	return 0;
+}
+
+static const struct of_device_id vgxy61_dt_ids[] = {
+	{ .compatible = "st,st-vgxy61" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, vgxy61_dt_ids);
+
+static const struct dev_pm_ops vgxy61_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(vgxy61_suspend, vgxy61_resume)
+	SET_RUNTIME_PM_OPS(vgxy61_power_off, vgxy61_power_on, NULL)
+};
+
+static struct i2c_driver vgxy61_i2c_driver = {
+	.driver = {
+		.name  = "st-vgxy61",
+		.of_match_table = vgxy61_dt_ids,
+		.pm = &vgxy61_pm_ops,
+	},
+	.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] 33+ messages in thread

* Re: [PATCH v2 2/5] media: v4l: ctrls: Add a control for temperature
  2022-04-15 11:18 ` [PATCH v2 2/5] media: v4l: ctrls: Add a control for temperature Benjamin Mugnier
@ 2022-04-15 14:37   ` Laurent Pinchart
  2022-04-20 13:01     ` Benjamin Mugnier
  2022-04-19  7:03   ` Hans Verkuil
  2022-04-19 18:24   ` Nicolas Dufresne
  2 siblings, 1 reply; 33+ messages in thread
From: Laurent Pinchart @ 2022-04-15 14:37 UTC (permalink / raw)
  To: Benjamin Mugnier
  Cc: linux-media, alain.volmat, hugues.fruchet, sylvain.petinot,
	dave.stevenson, sakari.ailus, kieran.bingham

Hi Benjamin,

Thank you for the patch.

On Fri, Apr 15, 2022 at 01:18:42PM +0200, Benjamin Mugnier wrote:
> Add V4L2_CID_TEMPERATURE control to get temperature from sensor in
> celsius as a volatile and read-only control, and its documentation.
> Useful to monitor thermals from v4l controls for sensors that support
> this.
> 
> Signed-off-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com>
> ---
>  Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst | 3 +++
>  drivers/media/v4l2-core/v4l2-ctrls-defs.c                  | 4 ++++
>  include/uapi/linux/v4l2-controls.h                         | 2 ++
>  3 files changed, 9 insertions(+)
> 
> diff --git a/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst b/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst
> index 4c5061aa9cd4..26fa21f5c45a 100644
> --- a/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst
> +++ b/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst
> @@ -661,3 +661,6 @@ enum v4l2_scene_mode -
>  .. [#f1]
>     This control may be changed to a menu control in the future, if more
>     options are required.
> +
> +``V4L2_CID_TEMPERATURE (integer)``
> +    The temperature of the sensor in celsius. This is a read-only control.

I've seen sensors where the temperature sensor has a 1/10th degree
precision. Should we standardize on that ? Anything more precise is
likely overkill.

There are also sensors with multiple temperature sensors. If there are
too many of them I suppose the temperature would be reported in embedded
data, but perhaps not always. How can we prepare for this ?

There are also a few details that I think should be documented. Is the
temperature always read on-demand when reading the control, or updated
periodically ? I would assume most drivers would implement the former,
which means no control notification events will be generated. This
should be documented. Furthermore, do drivers need to support reading
the temperature when the sensor isn't streaming ? If not, when should a
control read ioctl return, the last value, or an error ?

> diff --git a/drivers/media/v4l2-core/v4l2-ctrls-defs.c b/drivers/media/v4l2-core/v4l2-ctrls-defs.c
> index 54ca4e6b820b..45ad3edd59e0 100644
> --- a/drivers/media/v4l2-core/v4l2-ctrls-defs.c
> +++ b/drivers/media/v4l2-core/v4l2-ctrls-defs.c
> @@ -1042,6 +1042,7 @@ const char *v4l2_ctrl_get_name(u32 id)
>  	case V4L2_CID_UNIT_CELL_SIZE:		return "Unit Cell Size";
>  	case V4L2_CID_CAMERA_ORIENTATION:	return "Camera Orientation";
>  	case V4L2_CID_CAMERA_SENSOR_ROTATION:	return "Camera Sensor Rotation";
> +	case V4L2_CID_TEMPERATURE:		return "Temperature in °C";
>  
>  	/* FM Radio Modulator controls */
>  	/* Keep the order of the 'case's the same as in v4l2-controls.h! */
> @@ -1597,6 +1598,9 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type,
>  	case V4L2_CID_RF_TUNER_PLL_LOCK:
>  		*flags |= V4L2_CTRL_FLAG_VOLATILE;
>  		break;
> +	case V4L2_CID_TEMPERATURE:
> +		*flags |= V4L2_CTRL_FLAG_READ_ONLY |
> +			  V4L2_CTRL_FLAG_VOLATILE;
>  	}
>  }
>  EXPORT_SYMBOL(v4l2_ctrl_fill);
> diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h
> index bb40129446d4..705b4043c2de 100644
> --- a/include/uapi/linux/v4l2-controls.h
> +++ b/include/uapi/linux/v4l2-controls.h
> @@ -1008,6 +1008,8 @@ enum v4l2_auto_focus_range {
>  
>  #define V4L2_CID_CAMERA_SENSOR_ROTATION		(V4L2_CID_CAMERA_CLASS_BASE+35)
>  
> +#define V4L2_CID_TEMPERATURE			(V4L2_CID_CAMERA_CLASS_BASE+36)
> +
>  /* FM Modulator class control IDs */
>  
>  #define V4L2_CID_FM_TX_CLASS_BASE		(V4L2_CTRL_CLASS_FM_TX | 0x900)

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v2 1/5] media: v4l: Add 1X16 16-bit greyscale media bus code definition
  2022-04-15 11:18 ` [PATCH v2 1/5] media: v4l: Add 1X16 16-bit greyscale media bus code definition Benjamin Mugnier
@ 2022-04-15 14:47   ` Laurent Pinchart
  0 siblings, 0 replies; 33+ messages in thread
From: Laurent Pinchart @ 2022-04-15 14:47 UTC (permalink / raw)
  To: Benjamin Mugnier
  Cc: linux-media, alain.volmat, hugues.fruchet, sylvain.petinot,
	dave.stevenson, sakari.ailus, kieran.bingham

Hi Benjamin,

Thank you for the patch.

On Fri, Apr 15, 2022 at 01:18:41PM +0200, Benjamin Mugnier wrote:
> This extends the greyscale media bus family originally from
> MEDIA_BUS_FMT_Y8_1X8 up to MEDIA_BUS_FMT_Y14_1X14 by adding
> MEDIA_BUS_FMT_Y16_1X16, and behaves the same way with 16 bits.
> Add its documentation in subdev-formats.rst
> 
> Signed-off-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com>

Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

> ---
>  .../media/v4l/subdev-formats.rst              | 37 +++++++++++++++++++
>  include/uapi/linux/media-bus-format.h         |  3 +-
>  2 files changed, 39 insertions(+), 1 deletion(-)
> 
> diff --git a/Documentation/userspace-api/media/v4l/subdev-formats.rst b/Documentation/userspace-api/media/v4l/subdev-formats.rst
> index 0cbc045d5df6..b8bb16c70d62 100644
> --- a/Documentation/userspace-api/media/v4l/subdev-formats.rst
> +++ b/Documentation/userspace-api/media/v4l/subdev-formats.rst
> @@ -7387,6 +7387,43 @@ The following table list existing packed 36bit wide YUV formats.
>        - v\ :sub:`2`
>        - v\ :sub:`1`
>        - v\ :sub:`0`
> +    * .. _MEDIA-BUS-FMT-Y16-1X16:
> +
> +      - MEDIA_BUS_FMT_Y16_1X16
> +      - 0x202e
> +      -
> +      -
> +      -
> +      -
> +      -
> +      -
> +      -
> +      -
> +      -
> +      -
> +      -
> +      -
> +      -
> +      -
> +      -
> +      -
> +      -
> +      - y\ :sub:`15`
> +      - y\ :sub:`14`
> +      - y\ :sub:`13`
> +      - y\ :sub:`12`
> +      - y\ :sub:`11`
> +      - y\ :sub:`10`
> +      - y\ :sub:`9`
> +      - y\ :sub:`8`
> +      - y\ :sub:`7`
> +      - y\ :sub:`6`
> +      - y\ :sub:`5`
> +      - y\ :sub:`4`
> +      - y\ :sub:`3`
> +      - y\ :sub:`2`
> +      - y\ :sub:`1`
> +      - y\ :sub:`0`
>  
>  
>  .. raw:: latex
> diff --git a/include/uapi/linux/media-bus-format.h b/include/uapi/linux/media-bus-format.h
> index 0dfc11ee243a..0b436759d94d 100644
> --- a/include/uapi/linux/media-bus-format.h
> +++ b/include/uapi/linux/media-bus-format.h
> @@ -65,7 +65,7 @@
>  #define MEDIA_BUS_FMT_RGB121212_1X36		0x1019
>  #define MEDIA_BUS_FMT_RGB161616_1X48		0x101a
>  
> -/* YUV (including grey) - next is	0x202e */
> +/* YUV (including grey) - next is	0x202f */
>  #define MEDIA_BUS_FMT_Y8_1X8			0x2001
>  #define MEDIA_BUS_FMT_UV8_1X8			0x2015
>  #define MEDIA_BUS_FMT_UYVY8_1_5X8		0x2002
> @@ -111,6 +111,7 @@
>  #define MEDIA_BUS_FMT_YUV12_1X36		0x2029
>  #define MEDIA_BUS_FMT_YUV16_1X48		0x202a
>  #define MEDIA_BUS_FMT_UYYVYY16_0_5X48		0x202b
> +#define MEDIA_BUS_FMT_Y16_1X16			0x202e

I was going to ask for this to be moved before MEDIA_BUS_FMT_YUV16_1X48
to match the order of the other formats, but it looks like we have
already messed up ordering, so I suppose it's ok. Please still feel free
to move it to a more appropriate place, if any.

>  
>  /* Bayer - next is	0x3021 */
>  #define MEDIA_BUS_FMT_SBGGR8_1X8		0x3001

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v2 3/5] media: v4l: ctrls: Add user control base for st-vgxy61 controls
  2022-04-15 11:18 ` [PATCH v2 3/5] media: v4l: ctrls: Add user control base for st-vgxy61 controls Benjamin Mugnier
@ 2022-04-15 14:51   ` Laurent Pinchart
  0 siblings, 0 replies; 33+ messages in thread
From: Laurent Pinchart @ 2022-04-15 14:51 UTC (permalink / raw)
  To: Benjamin Mugnier
  Cc: linux-media, alain.volmat, hugues.fruchet, sylvain.petinot,
	dave.stevenson, sakari.ailus, kieran.bingham

Hi Benjamin,

Thank you for the patch.

On Fri, Apr 15, 2022 at 01:18:43PM +0200, Benjamin Mugnier wrote:
> Add a control base for st-vgxy61 controls and reserve 16 controls.
> This is required for HDR mode user control on the st-vgxy61 camera
> sensor.
> 
> Signed-off-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com>

Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

> ---
>  include/uapi/linux/v4l2-controls.h | 5 +++++
>  1 file changed, 5 insertions(+)
> 
> diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h
> index 705b4043c2de..0bdf77df26ac 100644
> --- a/include/uapi/linux/v4l2-controls.h
> +++ b/include/uapi/linux/v4l2-controls.h
> @@ -225,6 +225,11 @@ enum v4l2_colorfx {
>   */
>  #define V4L2_CID_USER_ISL7998X_BASE		(V4L2_CID_USER_BASE + 0x1180)
>  
> +/* The base for the st-vgxy61 driver controls.
> + * We reserve 16 controls for this driver.
> + */
> +#define V4L2_CID_USER_STVGXY61_BASE		(V4L2_CID_USER_BASE + 0x1190)
> +
>  /* MPEG-class control IDs */
>  /* The MPEG controls are applicable to all codec controls
>   * and the 'MPEG' part of the define is historical */

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v2 4/5] media: dt-bindings: media: i2c: Add ST VGXY61 camera sensor binding
  2022-04-15 11:18 ` [PATCH v2 4/5] media: dt-bindings: media: i2c: Add ST VGXY61 camera sensor binding Benjamin Mugnier
@ 2022-04-15 14:59   ` Laurent Pinchart
  2022-04-20 13:01     ` Benjamin Mugnier
  0 siblings, 1 reply; 33+ messages in thread
From: Laurent Pinchart @ 2022-04-15 14:59 UTC (permalink / raw)
  To: Benjamin Mugnier
  Cc: linux-media, alain.volmat, hugues.fruchet, sylvain.petinot,
	dave.stevenson, sakari.ailus, kieran.bingham

Hi Benjamin,

Thank you for the patch.

On Fri, Apr 15, 2022 at 01:18:44PM +0200, 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      | 125 ++++++++++++++++++
>  MAINTAINERS                                   |   9 ++
>  2 files changed, 134 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..24bff161a661
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml
> @@ -0,0 +1,125 @@
> +# 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

If there's a single clock you can drop the clock name.

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

Vendor-specific properties should have a vendor prefix (e.g.
st,inver-gpios-polarity).

> +    description:
> +      If gpios polarity should be inversed

What is this though, what GPIOs does it invert ?

> +    type: boolean
> +
> +  slave-mode:
> +    description:
> +      If the sensor operates in slave mode
> +    type: boolean
> +
> +  port:
> +    $ref: /schemas/graph.yaml#/$defs/port-base
> +    additionalProperties: false
> +
> +    properties:
> +      endpoint:
> +        $ref: /schemas/media/video-interfaces.yaml#
> +        unevaluatedProperties: false
> +
> +        properties:
> +          data-lanes:
> +            description:
> +              CSI lanes to use
> +            items:
> +              - const: 1
> +              - const: 2
> +              - const: 3
> +              - const: 4
> +
> +          remote-endpoint: true
> +
> +        required:
> +          - clock-lane
> +          - data-lanes
> +
> +required:
> +  - compatible
> +  - clocks
> +  - clock-names
> +  - VCORE-supply
> +  - VDDIO-supply
> +  - VANA-supply

The port property should be required too.

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

That's the default, it's not needed in DT examples.

> +            clocks = <&clk_ext_camera>;

There's no clock-names. Have you run DT bindings validation ? It should
have reported an error.

> +            VCORE-supply = <&v1v2>;
> +            VDDIO-supply = <&v1v8>;
> +            VANA-supply = <&v2v8>;
> +            reset-gpios = <&mfxgpio 18 GPIO_ACTIVE_LOW>;
> +            port {
> +                ep0: endpoint {
> +                    data-lanes = <1 2 3 4>;
> +                    remote-endpoint = <&mipi_csi2_out>;
> +                };
> +            };
> +        };
> +    };
> +...
> diff --git a/MAINTAINERS b/MAINTAINERS
> index fd768d43e048..6233e073da30 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -18664,6 +18664,15 @@ 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

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v2 5/5] media: i2c: Add driver for ST VGXY61 camera sensor
  2022-04-15 11:18 ` [PATCH v2 5/5] media: i2c: Add driver for ST VGXY61 camera sensor Benjamin Mugnier
@ 2022-04-15 15:39   ` Laurent Pinchart
  2022-04-20 13:01     ` Benjamin Mugnier
  0 siblings, 1 reply; 33+ messages in thread
From: Laurent Pinchart @ 2022-04-15 15:39 UTC (permalink / raw)
  To: Benjamin Mugnier
  Cc: linux-media, alain.volmat, hugues.fruchet, sylvain.petinot,
	dave.stevenson, sakari.ailus, kieran.bingham

Hi Benjamin,

Thank you for the patch.

This is a partial review as I'm short on time now.

On Fri, Apr 15, 2022 at 01:18:45PM +0200, Benjamin Mugnier wrote:
> 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     |   10 +
>  drivers/media/i2c/Makefile    |    1 +
>  drivers/media/i2c/st-vgxy61.c | 1997 +++++++++++++++++++++++++++++++++
>  3 files changed, 2008 insertions(+)
>  create mode 100644 drivers/media/i2c/st-vgxy61.c
> 
> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> index fae2baabb773..734f3e1130b0 100644
> --- a/drivers/media/i2c/Kconfig
> +++ b/drivers/media/i2c/Kconfig
> @@ -763,6 +763,16 @@ config VIDEO_VS6624
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called vs6624.
>  
> +config VIDEO_ST_VGXY61

Alphabetical order please.

> +	tristate "ST VGXY61 sensor support"
> +	depends on OF && GPIOLIB && VIDEO_DEV && 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.
> +
>  source "drivers/media/i2c/ccs/Kconfig"
>  source "drivers/media/i2c/et8ek8/Kconfig"
>  source "drivers/media/i2c/m5mols/Kconfig"
> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> index 3e1696963e7f..7f2362228484 100644
> --- a/drivers/media/i2c/Makefile
> +++ b/drivers/media/i2c/Makefile
> @@ -116,6 +116,7 @@ obj-$(CONFIG_VIDEO_SAA7185) += saa7185.o
>  obj-$(CONFIG_VIDEO_SONY_BTF_MPX) += sony-btf-mpx.o
>  obj-$(CONFIG_VIDEO_SR030PC30) += sr030pc30.o
>  obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o
> +obj-$(CONFIG_VIDEO_ST_VGXY61) += st-vgxy61.o
>  obj-$(CONFIG_VIDEO_TC358743) += tc358743.o
>  obj-$(CONFIG_VIDEO_TDA1997X) += tda1997x.o
>  obj-$(CONFIG_VIDEO_TDA7432) += tda7432.o
> diff --git a/drivers/media/i2c/st-vgxy61.c b/drivers/media/i2c/st-vgxy61.c
> new file mode 100644
> index 000000000000..1219d278e648
> --- /dev/null
> +++ b/drivers/media/i2c/st-vgxy61.c
> @@ -0,0 +1,1997 @@
> +// 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/pm_runtime.h>

Alphabetical order in headers too please.

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

I don't think this deserves a macro, but if you want to keep it, you
should avoid generic names that may cause namespace clashes.

> +
> +#define DEVICE_MODEL_ID_REG				0x0000

Please prefix macros with a driver-specific prefix. Many of them have a
too generic name.

> +#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 VGXY61_WRITE_MULTIPLE_CHUNK_MAX			16
> +#define VGXY61_NB_GPIOS					4
> +#define VGXY61_NB_POLARITIES				5
> +#define VGXY61_MIN_FRAME_LENGTH				1288
> +#define VGXY61_MIN_EXPOSURE				10
> +#define VGXY61_HDR_LINEAR_RATIO				10
> +#define VGXY61_TIMEOUT_MS				500
> +#define VGXY61_FRAME_LENGTH_DEF				1313
> +#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_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_STVGXY61_BASE + 0)

This macro should have a device-specific name, should be moved to a
public header, and the control should be documented. I would split the
control documentation and macro definition to a separate patch.

> +
> +#define DEVICE_FWPATCH_REVISION_MAJOR			2
> +#define DEVICE_FWPATCH_REVISION_MINOR			0
> +#define DEVICE_FWPATCH_REVISION_MICRO			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

The link frequencies should come from DT.

> +};
> +
> +#define VGXY61_NUM_SUPPLIES		ARRAY_SIZE(vgxy61_supply_name)
> +
> +enum bin_mode {

Same comment here, all enums and structures should use a driver-specific
prefix.

> +	BIN_MODE_NORMAL,
> +	BIN_MODE_DIGITAL_X2,
> +	BIN_MODE_DIGITAL_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;
> +	struct v4l2_rect crop;
> +};
> +
> +static const u32 vgxy61_supported_codes[] = {
> +	MEDIA_BUS_FMT_Y8_1X8,
> +	MEDIA_BUS_FMT_Y10_1X10,
> +	MEDIA_BUS_FMT_Y12_1X12,
> +	MEDIA_BUS_FMT_Y14_1X14,
> +	MEDIA_BUS_FMT_Y16_1X16
> +};
> +
> +static const struct vgxy61_mode_info vgx661_mode_data[] = {
> +	{
> +		.width = VGX661_WIDTH,
> +		.height = VGX661_HEIGHT,
> +		.bin_mode = BIN_MODE_NORMAL,
> +		.crop = {
> +			.left = 0,
> +			.top = 0,
> +			.width = VGX661_WIDTH,
> +			.height = VGX661_HEIGHT,
> +		},

It's very nice to see no hardcoded register values in the modes tables
:-) As the register values are computed by the driver, would it make
sense to go one step further and drop the modes completely ? Analog crop
and binning could then be selected freely from userspace.

> +	},
> +	{
> +		.width = 1280,
> +		.height = 720,
> +		.bin_mode = BIN_MODE_NORMAL,
> +		.crop = {
> +			.left = 92,
> +			.top = 192,
> +			.width = 1280,
> +			.height = 720,
> +		},
> +	},
> +	{
> +		.width = 640,
> +		.height = 480,
> +		.bin_mode = BIN_MODE_DIGITAL_X2,
> +		.crop = {
> +			.left = 92,
> +			.top = 72,
> +			.width = 1280,
> +			.height = 960,
> +		},
> +	},
> +	{
> +		.width = 320,
> +		.height = 240,
> +		.bin_mode = BIN_MODE_DIGITAL_X4,

Can the bin mode computed by dividing the crop width by the output width
instead of being stored here ?

> +		.crop = {
> +			.left = 92,
> +			.top = 72,
> +			.width = 1280,
> +			.height = 960,
> +		},
> +	},
> +};
> +
> +static const struct vgxy61_mode_info vgx761_mode_data[] = {
> +	{
> +		.width = VGX761_WIDTH,
> +		.height = VGX761_HEIGHT,
> +		.bin_mode = BIN_MODE_NORMAL,
> +		.crop = {
> +			.left = 0,
> +			.top = 0,
> +			.width = VGX761_WIDTH,
> +			.height = VGX761_HEIGHT,
> +		},
> +	},
> +	{
> +		.width = 1920,
> +		.height = 1080,
> +		.bin_mode = BIN_MODE_NORMAL,
> +		.crop = {
> +			.left = 12,
> +			.top = 62,
> +			.width = 1920,
> +			.height = 1080,
> +		},
> +	},
> +	{
> +		.width = 1280,
> +		.height = 720,
> +		.bin_mode = BIN_MODE_NORMAL,
> +		.crop = {
> +			.left = 332,
> +			.top = 242,
> +			.width = 1280,
> +			.height = 720,
> +		},
> +	},
> +	{
> +		.width = 640,
> +		.height = 480,
> +		.bin_mode = BIN_MODE_DIGITAL_X2,
> +		.crop = {
> +			.left = 332,
> +			.top = 122,
> +			.width = 1280,
> +			.height = 960,
> +		},
> +	},
> +	{
> +		.width = 320,
> +		.height = 240,
> +		.bin_mode = BIN_MODE_DIGITAL_X4,
> +		.crop = {
> +			.left = 332,
> +			.top = 122,
> +			.width = 1280,
> +			.height = 960,
> +		},
> +	},
> +};
> +
> +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 id;
> +	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 v4l2_ctrl_handler ctrl_handler;
> +	struct v4l2_ctrl *pixel_rate_ctrl;
> +	struct v4l2_ctrl *expo_ctrl;
> +	struct v4l2_ctrl *vblank_ctrl;
> +	struct v4l2_ctrl *vflip_ctrl;
> +	struct v4l2_ctrl *hflip_ctrl;
> +	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;
> +	struct v4l2_fract frame_interval;

This is not used. Please check all other fields too.

> +	bool hflip;
> +	bool vflip;
> +	enum hdr hdr;
> +	int expo_long;
> +	int expo_short;
> +	int expo_max;
> +	int expo_min;
> +	int vblank;
> +	int vblank_min;
> +	u16 frame_length;
> +};
> +
> +static u8 get_bpp_by_code(__u32 code)
> +{
> +	switch (code) {
> +	case MEDIA_BUS_FMT_Y8_1X8:
> +		return 8;
> +	case MEDIA_BUS_FMT_Y10_1X10:
> +		return 10;
> +	case MEDIA_BUS_FMT_Y12_1X12:
> +		return 12;
> +	case MEDIA_BUS_FMT_Y14_1X14:
> +		return 14;
> +	case MEDIA_BUS_FMT_Y16_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_Y8_1X8:
> +		return MIPI_CSI2_DT_RAW8;
> +	case MEDIA_BUS_FMT_Y10_1X10:
> +		return MIPI_CSI2_DT_RAW10;
> +	case MEDIA_BUS_FMT_Y12_1X12:
> +		return MIPI_CSI2_DT_RAW12;
> +	case MEDIA_BUS_FMT_Y14_1X14:
> +		return MIPI_CSI2_DT_RAW14;
> +	case MEDIA_BUS_FMT_Y16_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;
> +	}
> +}

I'd put all this data in a format description structure, and store that
in the vgxy61_supported_codes array.

> +
> +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, ctrl_handler)->sd;
> +}
> +
> +static int get_chunk_size(struct vgxy61_dev *sensor)
> +{
> +	struct i2c_adapter *adapter = sensor->i2c_client->adapter;
> +	int max_write_len = VGXY61_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, VGXY61_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[VGXY61_WRITE_MULTIPLE_CHUNK_MAX + 2];
> +	int i;
> +	int ret;
> +
> +	if (len > VGXY61_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));
> +}

You could possibly simplify handling of registers of different sizes by
encoding the size in the register address macros. See
https://gitlab.com/ideasonboard/nxp/linux/-/blob/pinchartl/v5.18/sensors/imx/drivers/media/i2c/imx296.c
for an example.

> +
> +static int vgxy61_poll_reg(struct vgxy61_dev *sensor, u16 reg, u8 poll_val, int timeout_ms)
> +{
> +	const int loop_delay_ms = 10;
> +	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, int timeout_ms)
> +{
> +	return vgxy61_poll_reg(sensor, DEVICE_SYSTEM_FSM, state, timeout_ms);
> +}
> +
> +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 = sensor->current_mode->crop.height / sensor->current_mode->height;
> +	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;

Is there a risk those writes may be applied to different frames ?

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

The function sets the frame length, not the frame rate.

> +{
> +	return vgxy61_write_reg16(sensor, DEVICE_FRAME_LENGTH, sensor->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, VGXY61_TIMEOUT_MS);
> +}
> +
> +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 */
> +	mode = v4l2_find_nearest_size(sensor->sensor_modes, sensor->sensor_modes_nb,
> +				      width, height, fmt->width, fmt->height);
> +	if (new_mode)
> +		*new_mode = mode;
> +
> +	fmt->code = vgxy61_supported_codes[index];
> +	fmt->width = mode->width;
> +	fmt->height = mode->height;
> +	fmt->colorspace = V4L2_COLORSPACE_RAW;
> +	fmt->field = V4L2_FIELD_NONE;
> +	fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> +	fmt->quantization = V4L2_QUANTIZATION_DEFAULT;
> +	fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT;
> +
> +	return 0;
> +}
> +
> +static int vgxy61_get_selection(struct v4l2_subdev *sd,
> +				struct v4l2_subdev_state *sd_state,
> +				struct v4l2_subdev_selection *sel)
> +{
> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> +
> +	switch (sel->target) {
> +	case V4L2_SEL_TGT_CROP:
> +		sel->r = sensor->current_mode->crop;
> +		return 0;
> +	case V4L2_SEL_TGT_NATIVE_SIZE:
> +	case V4L2_SEL_TGT_CROP_DEFAULT:
> +	case V4L2_SEL_TGT_CROP_BOUNDS:
> +		sel->r.top = 0;
> +		sel->r.left = 0;
> +		sel->r.width = sensor->sensor_width;
> +		sel->r.height = sensor->sensor_height;
> +		return 0;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static int vgxy61_stream_enable(struct vgxy61_dev *sensor)
> +{
> +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd);
> +	const struct v4l2_rect *crop = &sensor->current_mode->crop;
> +	int ret;
> +
> +	ret = vgxy61_check_bw(sensor);
> +	if (ret)
> +		return ret;
> +
> +	ret = pm_runtime_get_sync(&client->dev);
> +	if (ret < 0) {
> +		pm_runtime_put_noidle(&client->dev);
> +		return ret;
> +	}
> +
> +	/* Configure sensor */
> +	ret = vgxy61_write_reg(sensor, DEVICE_FORMAT_CTRL, get_bpp_by_code(sensor->fmt.code));
> +	if (ret)
> +		goto err_rpm_put;
> +	ret = vgxy61_write_reg(sensor, DEVICE_OIF_ROI0_CTRL,
> +			       get_data_type_by_code(sensor->fmt.code));
> +	if (ret)
> +		goto err_rpm_put;
> +
> +	ret = vgxy61_write_reg(sensor, DEVICE_READOUT_CTRL, sensor->current_mode->bin_mode);
> +	if (ret)
> +		goto err_rpm_put;
> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_H, crop->left);
> +	if (ret)
> +		goto err_rpm_put;
> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_H, crop->left + crop->width - 1);
> +	if (ret)
> +		goto err_rpm_put;
> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_V, crop->top);
> +	if (ret)
> +		goto err_rpm_put;
> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_V, crop->top + crop->height - 1);
> +	if (ret)
> +		goto err_rpm_put;
> +
> +	ret = set_frame_rate(sensor);
> +	if (ret)
> +		goto err_rpm_put;
> +
> +	ret = apply_exposure(sensor);
> +	if (ret)
> +		goto err_rpm_put;
> +
> +	/* Start streaming */
> +	ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_START_STREAMING);
> +	if (ret)
> +		goto err_rpm_put;
> +
> +	ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST, VGXY61_TIMEOUT_MS);
> +	if (ret)
> +		goto err_rpm_put;
> +
> +	ret = vgxy61_wait_state(sensor, STREAMING, VGXY61_TIMEOUT_MS);
> +	if (ret)
> +		goto err_rpm_put;
> +
> +	/* vflip and hflip cannot change during streaming */
> +	__v4l2_ctrl_grab(sensor->vflip_ctrl, true);
> +	__v4l2_ctrl_grab(sensor->hflip_ctrl, true);
> +
> +	return 0;
> +
> +err_rpm_put:
> +	pm_runtime_put(&client->dev);
> +	return ret;
> +}
> +
> +static int vgxy61_stream_disable(struct vgxy61_dev *sensor)
> +{
> +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd);
> +	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, 2000);
> +	if (ret)
> +		return ret;
> +
> +	ret = vgxy61_wait_state(sensor, SW_STBY, VGXY61_TIMEOUT_MS);
> +	if (ret)
> +		return ret;
> +
> +	__v4l2_ctrl_grab(sensor->vflip_ctrl, true);
> +	__v4l2_ctrl_grab(sensor->hflip_ctrl, true);
> +
> +	pm_runtime_put(&client->dev);
> +
> +	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;

This should never happen. If you want to be on the safe side and keep
the check, I'd turn it into a WARN_ON().

> +
> +	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_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);q
> +
> +	return 0;
> +}
> +
> +static int vgxy61_get_vblank_min(struct vgxy61_dev *sensor, enum hdr hdr)
> +{
> +	int min_vblank =  VGXY61_MIN_FRAME_LENGTH - sensor->current_mode->crop.height;
> +	/* Ensure the first tule of thumb can't be negative */
> +	int min_vblank_hdr =  VGXY61_MIN_EXPOSURE + sensor->rot_term + 1;
> +
> +	if (hdr != NO_HDR)
> +		return max(min_vblank, min_vblank_hdr);
> +	return min_vblank;
> +}
> +
> +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_update_analog_gain(struct vgxy61_dev *sensor, u32 target)
> +{
> +	struct i2c_client *client = sensor->i2c_client;
> +	int ret;
> +
> +	/* Apply gain */
> +	ret = vgxy61_write_reg(sensor, DEVICE_ANALOG_GAIN, target);
> +	if (ret)
> +		return ret;
> +
> +	dev_dbg(&client->dev, "Set analog gain: 0x%04x\n", target);
> +
> +	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;

Macros would be nice instead of magic numbers. Same where applicable
below.

> +	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};

Make this static const.

> +	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 < VGXY61_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 vgxy61_get_expo_long_max(struct vgxy61_dev *sensor, int short_expo_ratio)
> +{
> +	int first_rot_max_expo, second_rot_max_expo, third_rot_max_expo;
> +
> +	/* Apply sensor rule of thumbs */
> +	/* Avoid bad pixel line at the botom of the image */
> +	first_rot_max_expo =
> +		((sensor->frame_length - sensor->current_mode->crop.height - sensor->rot_term) *
> +		short_expo_ratio) - 1;
> +
> +	/* Avoid sensor crash */

How so ?

> +	second_rot_max_expo =
> +		(((sensor->frame_length - VGXY61_EXPOS_ROT_TERM) * short_expo_ratio) /
> +		(short_expo_ratio + 1)) - 1;
> +
> +	/* Avoid sensor crash */
> +	third_rot_max_expo = (sensor->frame_length / 71) * short_expo_ratio;
> +
> +	/* Take the minimum from all rules */
> +	return min(min(first_rot_max_expo, second_rot_max_expo), third_rot_max_expo);
> +}
> +
> +static int vgxy61_update_exposure(struct vgxy61_dev *sensor, int new_expo_long, enum hdr hdr)
> +{
> +	struct i2c_client *client = sensor->i2c_client;
> +	int new_expo_short = 0;
> +	int expo_short_max = 0;
> +	int expo_long_min = VGXY61_MIN_EXPOSURE;
> +	int expo_long_max;
> +
> +	/* Compute short exposure according to hdr mode and long exposure */
> +	switch (hdr) {
> +	case HDR_LINEAR:
> +		/* Take ratio into account for minimal exposures in HDR_LINEAR */
> +		expo_long_min = VGXY61_MIN_EXPOSURE * VGXY61_HDR_LINEAR_RATIO;
> +		new_expo_long = max(expo_long_min, new_expo_long);
> +
> +		expo_long_max = vgxy61_get_expo_long_max(sensor, VGXY61_HDR_LINEAR_RATIO);
> +		expo_short_max =
> +			(expo_long_max + (VGXY61_HDR_LINEAR_RATIO / 2)) / VGXY61_HDR_LINEAR_RATIO;
> +		new_expo_short =
> +			(new_expo_long + (VGXY61_HDR_LINEAR_RATIO / 2)) / VGXY61_HDR_LINEAR_RATIO;
> +		break;
> +	case HDR_SUB:
> +		new_expo_long = max(expo_long_min, new_expo_long);
> +
> +		expo_long_max = vgxy61_get_expo_long_max(sensor, 1);
> +		/* Short and long are the same in HDR_SUB */
> +		expo_short_max = expo_long_max;
> +		new_expo_short = new_expo_long;
> +		break;
> +	case NO_HDR:
> +		new_expo_long = max(expo_long_min, new_expo_long);
> +
> +		/*
> +		 * As short expo is 0 here, only the second rule of thumb applies, see
> +		 * vgxy61_get_expo_long_max for more
> +		 */
> +		expo_long_max = sensor->frame_length - VGXY61_EXPOS_ROT_TERM;
> +		break;
> +	default:
> +		/* Should never happen */
> +		WARN_ON(true);
> +		break;
> +	}
> +
> +	/* If this happens, something is wrong with formulas */
> +	WARN_ON(expo_long_min > expo_long_max);
> +
> +	if (new_expo_long > expo_long_max) {
> +		dev_warn(&client->dev, "Exposure %d too high, clamping to %d\n",
> +			 new_expo_long, expo_long_max);
> +		new_expo_long = expo_long_max;
> +		new_expo_short = expo_short_max;
> +	}
> +
> +	dev_dbg(&client->dev, "frame_length %d, new_expo_long %d, new_expo_short %d\n",
> +		sensor->frame_length, new_expo_long, new_expo_short);
> +	dev_dbg(&client->dev, "expo_long_min %d, expo_long_max %d\n",
> +		expo_long_min, expo_long_max);
> +
> +	/* Apply exposure */
> +	sensor->expo_long = new_expo_long;
> +	sensor->expo_short = new_expo_short;
> +	sensor->expo_max = expo_long_max;
> +	sensor->expo_min = expo_long_min;
> +
> +	if (sensor->streaming)
> +		return apply_exposure(sensor);
> +	return 0;
> +}
> +
> +static int vgxy61_update_vblank(struct vgxy61_dev *sensor, int vblank, int hdr)
> +{
> +	int ret;
> +
> +	sensor->vblank_min = vgxy61_get_vblank_min(sensor, hdr);
> +	sensor->vblank = max(sensor->vblank_min, vblank);
> +	sensor->frame_length = sensor->current_mode->crop.height + sensor->vblank;
> +
> +	/* Update exposure according to vblank */
> +	ret = vgxy61_update_exposure(sensor, sensor->expo_long, hdr);
> +	if (ret)
> +		return ret;
> +
> +	if (sensor->streaming)
> +		return set_frame_rate(sensor);
> +	return 0;
> +}
> +
> +static int vgxy61_update_hdr(struct vgxy61_dev *sensor, u32 index)
> +{
> +	const u8 index2val[] = {0x1, 0x4, 0xa};
> +	int ret;
> +
> +	/*
> +	 * vblank and short exposure change 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_vblank(sensor, sensor->vblank, index);
> +	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_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);
> +		*fmt = format->format;
> +	} else if (sensor->current_mode != new_mode || sensor->fmt.code != format->format.code) {
> +		fmt = &sensor->fmt;
> +		*fmt = format->format;
> +
> +		sensor->current_mode = new_mode;
> +
> +		/* Reset vblank and framelength to default */
> +		ret = vgxy61_update_vblank(sensor,
> +					   VGXY61_FRAME_LENGTH_DEF -
> +					   sensor->current_mode->crop.height,
> +					   sensor->hdr);
> +
> +		/* Update controls to reflect new mode */
> +		__v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate_ctrl, get_pixel_rate(sensor));
> +		__v4l2_ctrl_modify_range(sensor->vblank_ctrl, sensor->vblank_min,
> +					 0xffff - sensor->current_mode->crop.height, 1,
> +					 sensor->vblank);
> +		__v4l2_ctrl_s_ctrl(sensor->vblank_ctrl, sensor->vblank);
> +		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
> +					 sensor->expo_max, 1,
> +					 sensor->expo_long);
> +	}
> +
> +out:
> +	mutex_unlock(&sensor->lock);
> +
> +	return ret;
> +}
> +
> +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);

Can't runtime PM be disabled here ?

> +	if (ret)
> +		return ret;
> +	ret = vgxy61_poll_reg(sensor, DEVICE_STBY, STBY_NO_REQ, VGXY61_TIMEOUT_MS);
> +	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);
> +		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);
> +		/* Update vblank and exposure controls to match new hdr */
> +		__v4l2_ctrl_modify_range(sensor->vblank_ctrl, sensor->vblank_min,
> +					 0xffff - sensor->current_mode->crop.height, 1,
> +					 sensor->vblank);
> +		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
> +					 sensor->expo_max, 1, sensor->expo_long);
> +		break;
> +	case V4L2_CID_VBLANK:
> +		ret = vgxy61_update_vblank(sensor, ctrl->val, sensor->hdr);
> +		/* Update exposure control to match new vblank */
> +		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
> +					 sensor->expo_max, 1, sensor->expo_long);
> +		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 ret;
> +
> +	switch (ctrl->id) {
> +	case V4L2_CID_TEMPERATURE:
> +		ret = vgxy61_get_temp(sensor, &ctrl->val);
> +		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 int vgxy61_init_controls(struct vgxy61_dev *sensor)
> +{
> +	const struct v4l2_ctrl_ops *ops = &vgxy61_ctrl_ops;
> +	struct v4l2_ctrl_handler *hdl = &sensor->ctrl_handler;
> +	struct v4l2_ctrl *ctrl;
> +	int ret;
> +
> +	v4l2_ctrl_handler_init(hdl, 16);
> +	mutex_init(&sensor->lock);

This should be initialized in probe(), it doesn't belong here.

> +	/* We can use our own mutex for the ctrl lock */
> +	hdl->lock = &sensor->lock;
> +	/* Standard controls */
> +	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, 0x1c, 1, 0x0);
> +	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 0, 0xfff, 1, 256);
> +	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_TEMPERATURE, -128, 128, 1, 0);
> +	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);
> +	v4l2_ctrl_new_custom(hdl, &vgxy61_hdr_ctrl, NULL);
> +	ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HBLANK, 0, sensor->line_length, 1,
> +				 sensor->line_length - sensor->current_mode->width);
> +	ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> +	/* Custom controls */
> +	ctrl = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ, ARRAY_SIZE(link_freq) - 1, 0,
> +				      link_freq);
> +	ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> +	/* Keep a pointer to these controls as we need to update them when setting the format */
> +	sensor->pixel_rate_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1,
> +						    get_pixel_rate(sensor));
> +	sensor->pixel_rate_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> +	sensor->expo_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, sensor->expo_min,
> +					      sensor->expo_max, 1, sensor->expo_long);
> +	sensor->vblank_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VBLANK, sensor->vblank_min,
> +						0xffff - sensor->current_mode->crop.height, 1,
> +						sensor->vblank);
> +	sensor->vflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, sensor->vflip);
> +	sensor->hflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, sensor->hflip);
> +
> +	if (hdl->error) {
> +		ret = hdl->error;
> +		goto free_ctrls;
> +	}
> +
> +	sensor->sd.ctrl_handler = hdl;
> +	return 0;
> +
> +free_ctrls:
> +	v4l2_ctrl_handler_free(hdl);
> +	mutex_destroy(&sensor->lock);
> +	return ret;
> +}
> +
> +static const struct v4l2_subdev_core_ops vgxy61_core_ops = {
> +};

You can drop this.

> +
> +static const struct v4l2_subdev_video_ops vgxy61_video_ops = {
> +	.s_stream = vgxy61_s_stream,
> +};
> +
> +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,
> +	.get_selection = vgxy61_get_selection,
> +	.enum_frame_size = vgxy61_enum_frame_size,
> +};
> +
> +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[VGXY61_NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
> +	u32 phy2log[VGXY61_NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
> +	int polarities[VGXY61_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 < VGXY61_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;
> +	/* 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, VGXY61_TIMEOUT_MS);
> +	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_dbg(&client->dev, "Cut1 detected\n");
> +		dev_err(&client->dev, "Cut1 not supported by this driver\n");
> +		return -ENODEV;
> +	case 0xB:
> +		dev_dbg(&client->dev, "Cut2 detected\n");
> +		return 0;
> +	case 0xC:
> +		dev_dbg(&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);
> +	sensor->id = id;
> +
> +	ret = vgxy61_wait_state(sensor, SW_STBY, VGXY61_TIMEOUT_MS);
> +	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;
> +}
> +
> +/* Power/clock management functions */
> +static int vgxy61_power_on(struct device *dev)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> +	int ret;
> +
> +	ret = vgxy61_get_regulators(sensor);
> +	if (ret) {
> +		dev_err(&client->dev, "failed to get regulators %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = regulator_bulk_enable(VGXY61_NUM_SUPPLIES, sensor->supplies);
> +	if (ret) {
> +		dev_err(&client->dev, "failed to enable regulators %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = clk_prepare_enable(sensor->xclk);
> +	if (ret) {
> +		dev_err(&client->dev, "failed to enable clock %d\n", ret);
> +		goto disable_bulk;
> +	}
> +
> +	/* 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);

Do we really need to run detection every time the sensor is powered on ?

> +	if (ret) {
> +		dev_err(&client->dev, "sensor detect failed %d\n", ret);
> +		goto disable_clock;
> +	}
> +
> +	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;
> +	}
> +
> +	return 0;
> +
> +disable_clock:
> +	clk_disable_unprepare(sensor->xclk);
> +disable_bulk:
> +	regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
> +
> +	return ret;
> +}
> +
> +static int vgxy61_power_off(struct device *dev)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> +
> +	clk_disable_unprepare(sensor->xclk);
> +	regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
> +	return 0;
> +}
> +
> +static int __maybe_unused vgxy61_suspend(struct device *dev)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> +
> +	if (sensor->streaming)
> +		vgxy61_stream_disable(sensor);

This shoudn't happen. The sensor should be stopped by the driver on the
receiver side during system suspend. Same for resume.

> +
> +	return 0;
> +}
> +
> +static int __maybe_unused vgxy61_resume(struct device *dev)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> +	int ret;
> +
> +	if (sensor->streaming) {
> +		ret = vgxy61_stream_enable(sensor);
> +		if (ret)
> +			goto error;
> +	}
> +
> +	return 0;
> +
> +error:
> +	vgxy61_stream_disable(sensor);
> +	sensor->streaming = false;
> +
> +	return ret;
> +}
> +
> +static void vgxy61_set_default_format(struct vgxy61_dev *sensor)
> +{
> +	if (sensor->id == VG5761_MODEL_ID) {

Would it make sense to have different compatible strings instead of
detecting the version automatically ? Not only could it speed up probe
if we could skip the detection, it would also allow not powering the
sensor up at probe time, which may be required in some cases (for
instance when a privacy LED is connected to the sensor power supply,
flashing the LED at probe time can be very confusing for users).

> +		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;
> +	} else if (sensor->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;
> +	} else {
> +		/* Should never happen */
> +		WARN_ON(true);
> +	}
> +	sensor->fmt.width = sensor->current_mode->width;
> +	sensor->fmt.height = sensor->current_mode->height;
> +	sensor->vblank = sensor->frame_length - sensor->current_mode->crop.height;
> +}
> +
> +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_Y8_1X8;
> +	sensor->fmt.colorspace = V4L2_COLORSPACE_RAW;
> +	sensor->fmt.field = V4L2_FIELD_NONE;
> +	sensor->fmt.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> +	sensor->fmt.quantization = V4L2_QUANTIZATION_DEFAULT;
> +	sensor->fmt.xfer_func = V4L2_XFER_FUNC_DEFAULT;

Please implement the subdev .init_cfg() operation, and call it here.
There are several examples in existing drivers.

> +	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;
> +	sensor->frame_length = VGXY61_FRAME_LENGTH_DEF;
> +
> +	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, NULL);
> +	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 < 6 * HZ_PER_MHZ || sensor->clk_freq > 27 * HZ_PER_MHZ) {
> +		dev_err(dev, "Only 6Mhz-27Mhz clock range supported. provide %lu MHz\n",
> +			sensor->clk_freq / HZ_PER_MHZ);
> +		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;
> +
> +	/* Request optional reset pin */
> +	sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
> +
> +	ret = vgxy61_power_on(dev);
> +	if (ret)
> +		return ret;
> +
> +	vgxy61_set_default_format(sensor);
> +
> +	/* HDR mode */
> +	ret = vgxy61_update_hdr(sensor, sensor->hdr);
> +	if (ret)
> +		return ret;
> +
> +	ret = vgxy61_init_controls(sensor);
> +	if (ret) {
> +		dev_err(&client->dev, "controls initialization failed %d\n", ret);
> +		goto error_power_off;
> +	}
> +
> +	ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
> +	if (ret) {
> +		dev_err(&client->dev, "pads init failed %d\n", ret);
> +		goto error_handler_free;
> +	}
> +
> +	ret = v4l2_async_register_subdev(&sensor->sd);
> +	if (ret) {
> +		dev_err(&client->dev, "async subdev register failed %d\n", ret);
> +		goto error_media_entity;
> +	}
> +
> +	/* Enable runtime PM and turn off the device */
> +	pm_runtime_set_active(dev);
> +	pm_runtime_enable(dev);
> +	pm_runtime_idle(dev);

You should do this before registering the subdev, as it can be accessed
from userspace right after being registered.

Please also use auto-suspend to avoid long delays when stopping and
restarting streaming quickly.

> +
> +	dev_info(&client->dev, "vgxy61 probe successfully\n");

I'd drop this, or turn it into a debug message.

> +
> +	return 0;
> +
> +error_media_entity:
> +	media_entity_cleanup(&sensor->sd.entity);
> +error_handler_free:
> +	v4l2_ctrl_handler_free(sensor->sd.ctrl_handler);
> +	mutex_destroy(&sensor->lock);
> +error_power_off:
> +	vgxy61_power_off(dev);
> +
> +	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);
> +	mutex_destroy(&sensor->lock);
> +	media_entity_cleanup(&sensor->sd.entity);
> +
> +	pm_runtime_disable(&client->dev);
> +	if (!pm_runtime_status_suspended(&client->dev))
> +		vgxy61_power_off(&client->dev);
> +	pm_runtime_set_suspended(&client->dev);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id vgxy61_dt_ids[] = {
> +	{ .compatible = "st,st-vgxy61" },
> +	{ /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, vgxy61_dt_ids);
> +
> +static const struct dev_pm_ops vgxy61_pm_ops = {
> +	SET_SYSTEM_SLEEP_PM_OPS(vgxy61_suspend, vgxy61_resume)
> +	SET_RUNTIME_PM_OPS(vgxy61_power_off, vgxy61_power_on, NULL)
> +};
> +
> +static struct i2c_driver vgxy61_i2c_driver = {
> +	.driver = {
> +		.name  = "st-vgxy61",
> +		.of_match_table = vgxy61_dt_ids,
> +		.pm = &vgxy61_pm_ops,
> +	},
> +	.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");

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v2 2/5] media: v4l: ctrls: Add a control for temperature
  2022-04-15 11:18 ` [PATCH v2 2/5] media: v4l: ctrls: Add a control for temperature Benjamin Mugnier
  2022-04-15 14:37   ` Laurent Pinchart
@ 2022-04-19  7:03   ` Hans Verkuil
  2022-04-20 13:01     ` Benjamin Mugnier
  2022-04-19 18:24   ` Nicolas Dufresne
  2 siblings, 1 reply; 33+ messages in thread
From: Hans Verkuil @ 2022-04-19  7:03 UTC (permalink / raw)
  To: Benjamin Mugnier, linux-media
  Cc: alain.volmat, hugues.fruchet, sylvain.petinot, dave.stevenson,
	sakari.ailus, laurent.pinchart, kieran.bingham

On 15/04/2022 13:18, Benjamin Mugnier wrote:
> Add V4L2_CID_TEMPERATURE control to get temperature from sensor in
> celsius as a volatile and read-only control, and its documentation.

celsius -> degrees Celsius

(see https://en.wikipedia.org/wiki/Celsius)

> Useful to monitor thermals from v4l controls for sensors that support
> this.
> 
> Signed-off-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com>
> ---
>  Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst | 3 +++
>  drivers/media/v4l2-core/v4l2-ctrls-defs.c                  | 4 ++++
>  include/uapi/linux/v4l2-controls.h                         | 2 ++
>  3 files changed, 9 insertions(+)
> 
> diff --git a/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst b/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst
> index 4c5061aa9cd4..26fa21f5c45a 100644
> --- a/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst
> +++ b/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst
> @@ -661,3 +661,6 @@ enum v4l2_scene_mode -
>  .. [#f1]
>     This control may be changed to a menu control in the future, if more
>     options are required.
> +
> +``V4L2_CID_TEMPERATURE (integer)``
> +    The temperature of the sensor in celsius. This is a read-only control.

Ditto

> diff --git a/drivers/media/v4l2-core/v4l2-ctrls-defs.c b/drivers/media/v4l2-core/v4l2-ctrls-defs.c
> index 54ca4e6b820b..45ad3edd59e0 100644
> --- a/drivers/media/v4l2-core/v4l2-ctrls-defs.c
> +++ b/drivers/media/v4l2-core/v4l2-ctrls-defs.c
> @@ -1042,6 +1042,7 @@ const char *v4l2_ctrl_get_name(u32 id)
>  	case V4L2_CID_UNIT_CELL_SIZE:		return "Unit Cell Size";
>  	case V4L2_CID_CAMERA_ORIENTATION:	return "Camera Orientation";
>  	case V4L2_CID_CAMERA_SENSOR_ROTATION:	return "Camera Sensor Rotation";
> +	case V4L2_CID_TEMPERATURE:		return "Temperature in °C";

I am not sure how well this ° symbol will work. The V4L2 spec says that this is an
ASCII string, so that doesn't allow for this symbol.

I would just call it "Temperature".

>  
>  	/* FM Radio Modulator controls */
>  	/* Keep the order of the 'case's the same as in v4l2-controls.h! */
> @@ -1597,6 +1598,9 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type,
>  	case V4L2_CID_RF_TUNER_PLL_LOCK:
>  		*flags |= V4L2_CTRL_FLAG_VOLATILE;
>  		break;
> +	case V4L2_CID_TEMPERATURE:
> +		*flags |= V4L2_CTRL_FLAG_READ_ONLY |
> +			  V4L2_CTRL_FLAG_VOLATILE;

Add a break!

>  	}
>  }
>  EXPORT_SYMBOL(v4l2_ctrl_fill);
> diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h
> index bb40129446d4..705b4043c2de 100644
> --- a/include/uapi/linux/v4l2-controls.h
> +++ b/include/uapi/linux/v4l2-controls.h
> @@ -1008,6 +1008,8 @@ enum v4l2_auto_focus_range {
>  
>  #define V4L2_CID_CAMERA_SENSOR_ROTATION		(V4L2_CID_CAMERA_CLASS_BASE+35)
>  
> +#define V4L2_CID_TEMPERATURE			(V4L2_CID_CAMERA_CLASS_BASE+36)

Does it make sense to add this to CAMERA_CLASS? Can't this be a generic temperature
control? (i.e. in USER_BASE) Any device can have a temperature sensor.

I also think that making this an array control would make sense as well in case there
are multiple temperature sensors.

Brainstorming some more: does this even belong here? Isn't this more a hwmon thing?
E.g. compare this to drivers/nvme/host/hwmon.c.

A hwmon implementation seems to be a more natural mechanism.

Regards,

	Hans

> +
>  /* FM Modulator class control IDs */
>  
>  #define V4L2_CID_FM_TX_CLASS_BASE		(V4L2_CTRL_CLASS_FM_TX | 0x900)


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

* Re: [PATCH v2 2/5] media: v4l: ctrls: Add a control for temperature
  2022-04-15 11:18 ` [PATCH v2 2/5] media: v4l: ctrls: Add a control for temperature Benjamin Mugnier
  2022-04-15 14:37   ` Laurent Pinchart
  2022-04-19  7:03   ` Hans Verkuil
@ 2022-04-19 18:24   ` Nicolas Dufresne
  2022-04-19 19:28     ` Guenter Roeck
  2 siblings, 1 reply; 33+ messages in thread
From: Nicolas Dufresne @ 2022-04-19 18:24 UTC (permalink / raw)
  To: Benjamin Mugnier, linux-media
  Cc: alain.volmat, hugues.fruchet, sylvain.petinot, dave.stevenson,
	sakari.ailus, laurent.pinchart, kieran.bingham, linux-hwmon,
	Ricardo Ribalda

Hi,

adding linux-hwmon in CC for a wider feedback.

Le vendredi 15 avril 2022 à 13:18 +0200, Benjamin Mugnier a écrit :
> Add V4L2_CID_TEMPERATURE control to get temperature from sensor in
> celsius as a volatile and read-only control, and its documentation.
> Useful to monitor thermals from v4l controls for sensors that support
> this.

Any justification to expose a temperature sensor outside of the dedicated kernel
API hwmon ? I know if may makes it harder to use, as the sensor isn't bound to
the camera driver, and also the sensor may not work if the camera is not
streaming. But in the end, the API in hwmon does not look that complex, and is
perhaps more precise ?

All in all, I think we need a strong justification to implement a custom
thermometer interface, something described here and documented with care to
prevent abuse. I would also see opinion from folks outside of the linux-media,
hence why I have CCed hwmon mailing list.

regards,
Nicolas

> 
> Signed-off-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com>
> ---
>  Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst | 3 +++
>  drivers/media/v4l2-core/v4l2-ctrls-defs.c                  | 4 ++++
>  include/uapi/linux/v4l2-controls.h                         | 2 ++
>  3 files changed, 9 insertions(+)
> 
> diff --git a/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst b/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst
> index 4c5061aa9cd4..26fa21f5c45a 100644
> --- a/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst
> +++ b/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst
> @@ -661,3 +661,6 @@ enum v4l2_scene_mode -
>  .. [#f1]
>     This control may be changed to a menu control in the future, if more
>     options are required.
> +
> +``V4L2_CID_TEMPERATURE (integer)``
> +    The temperature of the sensor in celsius. This is a read-only control.
> diff --git a/drivers/media/v4l2-core/v4l2-ctrls-defs.c b/drivers/media/v4l2-core/v4l2-ctrls-defs.c
> index 54ca4e6b820b..45ad3edd59e0 100644
> --- a/drivers/media/v4l2-core/v4l2-ctrls-defs.c
> +++ b/drivers/media/v4l2-core/v4l2-ctrls-defs.c
> @@ -1042,6 +1042,7 @@ const char *v4l2_ctrl_get_name(u32 id)
>  	case V4L2_CID_UNIT_CELL_SIZE:		return "Unit Cell Size";
>  	case V4L2_CID_CAMERA_ORIENTATION:	return "Camera Orientation";
>  	case V4L2_CID_CAMERA_SENSOR_ROTATION:	return "Camera Sensor Rotation";
> +	case V4L2_CID_TEMPERATURE:		return "Temperature in °C";
>  
>  	/* FM Radio Modulator controls */
>  	/* Keep the order of the 'case's the same as in v4l2-controls.h! */
> @@ -1597,6 +1598,9 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type,
>  	case V4L2_CID_RF_TUNER_PLL_LOCK:
>  		*flags |= V4L2_CTRL_FLAG_VOLATILE;
>  		break;
> +	case V4L2_CID_TEMPERATURE:
> +		*flags |= V4L2_CTRL_FLAG_READ_ONLY |
> +			  V4L2_CTRL_FLAG_VOLATILE;
>  	}
>  }
>  EXPORT_SYMBOL(v4l2_ctrl_fill);
> diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h
> index bb40129446d4..705b4043c2de 100644
> --- a/include/uapi/linux/v4l2-controls.h
> +++ b/include/uapi/linux/v4l2-controls.h
> @@ -1008,6 +1008,8 @@ enum v4l2_auto_focus_range {
>  
>  #define V4L2_CID_CAMERA_SENSOR_ROTATION		(V4L2_CID_CAMERA_CLASS_BASE+35)
>  
> +#define V4L2_CID_TEMPERATURE			(V4L2_CID_CAMERA_CLASS_BASE+36)
> +
>  /* FM Modulator class control IDs */
>  
>  #define V4L2_CID_FM_TX_CLASS_BASE		(V4L2_CTRL_CLASS_FM_TX | 0x900)


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

* Re: [PATCH v2 2/5] media: v4l: ctrls: Add a control for temperature
  2022-04-19 18:24   ` Nicolas Dufresne
@ 2022-04-19 19:28     ` Guenter Roeck
  2022-04-19 21:01       ` Laurent Pinchart
  0 siblings, 1 reply; 33+ messages in thread
From: Guenter Roeck @ 2022-04-19 19:28 UTC (permalink / raw)
  To: Nicolas Dufresne, Benjamin Mugnier, linux-media
  Cc: alain.volmat, hugues.fruchet, sylvain.petinot, dave.stevenson,
	sakari.ailus, laurent.pinchart, kieran.bingham, linux-hwmon,
	Ricardo Ribalda

On 4/19/22 11:24, Nicolas Dufresne wrote:
> Hi,
> 
> adding linux-hwmon in CC for a wider feedback.
> 
> Le vendredi 15 avril 2022 à 13:18 +0200, Benjamin Mugnier a écrit :
>> Add V4L2_CID_TEMPERATURE control to get temperature from sensor in
>> celsius as a volatile and read-only control, and its documentation.
>> Useful to monitor thermals from v4l controls for sensors that support
>> this.
> 
> Any justification to expose a temperature sensor outside of the dedicated kernel
> API hwmon ? I know if may makes it harder to use, as the sensor isn't bound to
> the camera driver, and also the sensor may not work if the camera is not
> streaming. But in the end, the API in hwmon does not look that complex, and is
> perhaps more precise ?
> 
> All in all, I think we need a strong justification to implement a custom
> thermometer interface, something described here and documented with care to
> prevent abuse. I would also see opinion from folks outside of the linux-media,
> hence why I have CCed hwmon mailing list.
> 


All I can say is that this seems to be odd and a bit outside the scope of
v4l2. I would have expected the vgxy61 driver to register a hwmon device
instead.

Guenter

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

* Re: [PATCH v2 2/5] media: v4l: ctrls: Add a control for temperature
  2022-04-19 19:28     ` Guenter Roeck
@ 2022-04-19 21:01       ` Laurent Pinchart
  2022-04-19 22:04         ` Guenter Roeck
  0 siblings, 1 reply; 33+ messages in thread
From: Laurent Pinchart @ 2022-04-19 21:01 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Nicolas Dufresne, Benjamin Mugnier, linux-media, alain.volmat,
	hugues.fruchet, sylvain.petinot, dave.stevenson, sakari.ailus,
	kieran.bingham, linux-hwmon, Ricardo Ribalda

On Tue, Apr 19, 2022 at 12:28:06PM -0700, Guenter Roeck wrote:
> On 4/19/22 11:24, Nicolas Dufresne wrote:
> > Hi,
> > 
> > adding linux-hwmon in CC for a wider feedback.
> > 
> > Le vendredi 15 avril 2022 à 13:18 +0200, Benjamin Mugnier a écrit :
> >> Add V4L2_CID_TEMPERATURE control to get temperature from sensor in
> >> celsius as a volatile and read-only control, and its documentation.
> >> Useful to monitor thermals from v4l controls for sensors that support
> >> this.
> > 
> > Any justification to expose a temperature sensor outside of the dedicated kernel
> > API hwmon ? I know if may makes it harder to use, as the sensor isn't bound to
> > the camera driver, and also the sensor may not work if the camera is not
> > streaming. But in the end, the API in hwmon does not look that complex, and is
> > perhaps more precise ?
> > 
> > All in all, I think we need a strong justification to implement a custom
> > thermometer interface, something described here and documented with care to
> > prevent abuse. I would also see opinion from folks outside of the linux-media,
> > hence why I have CCed hwmon mailing list.
> 
> All I can say is that this seems to be odd and a bit outside the scope of
> v4l2. I would have expected the vgxy61 driver to register a hwmon device
> instead.

I don't have a definitive opinion yet, but as Nicolas raised the issue
by pushing towards hwmon, I'll offer counter-arguments for the sake of
it :-)

The temperature sensor in the imaging sensor is used to measure the die
temperature, in order to tune the noise and spectral response model of
the imaging sensor. It's thus not a generic-purpose temperature sensor.
If the feature were to be exposed through hwmon, userspace would need to
associate an hwmon device to the imaging sensor V4L2 subdev (we have a
way to do so through the MC API, it doesn't support hwmon at this point,
but I suppose it could be added). There are also various constraints
that tie the temperature reading to the imaging side, it could be that
the temperature would only be readable while capturing frames. That's
probably possible to handle too but returning an error from the
temperature read.

Code-wise, both the driver and userspace would be more complex, for very
little practical gain (I don't dispute a theorical gain).

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v2 2/5] media: v4l: ctrls: Add a control for temperature
  2022-04-19 21:01       ` Laurent Pinchart
@ 2022-04-19 22:04         ` Guenter Roeck
  2022-04-20 13:01           ` Benjamin Mugnier
  0 siblings, 1 reply; 33+ messages in thread
From: Guenter Roeck @ 2022-04-19 22:04 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Nicolas Dufresne, Benjamin Mugnier, linux-media, alain.volmat,
	hugues.fruchet, sylvain.petinot, dave.stevenson, sakari.ailus,
	kieran.bingham, linux-hwmon, Ricardo Ribalda

On 4/19/22 14:01, Laurent Pinchart wrote:
> On Tue, Apr 19, 2022 at 12:28:06PM -0700, Guenter Roeck wrote:
>> On 4/19/22 11:24, Nicolas Dufresne wrote:
>>> Hi,
>>>
>>> adding linux-hwmon in CC for a wider feedback.
>>>
>>> Le vendredi 15 avril 2022 à 13:18 +0200, Benjamin Mugnier a écrit :
>>>> Add V4L2_CID_TEMPERATURE control to get temperature from sensor in
>>>> celsius as a volatile and read-only control, and its documentation.
>>>> Useful to monitor thermals from v4l controls for sensors that support
>>>> this.
>>>
>>> Any justification to expose a temperature sensor outside of the dedicated kernel
>>> API hwmon ? I know if may makes it harder to use, as the sensor isn't bound to
>>> the camera driver, and also the sensor may not work if the camera is not
>>> streaming. But in the end, the API in hwmon does not look that complex, and is
>>> perhaps more precise ?
>>>
>>> All in all, I think we need a strong justification to implement a custom
>>> thermometer interface, something described here and documented with care to
>>> prevent abuse. I would also see opinion from folks outside of the linux-media,
>>> hence why I have CCed hwmon mailing list.
>>
>> All I can say is that this seems to be odd and a bit outside the scope of
>> v4l2. I would have expected the vgxy61 driver to register a hwmon device
>> instead.
> 
> I don't have a definitive opinion yet, but as Nicolas raised the issue
> by pushing towards hwmon, I'll offer counter-arguments for the sake of
> it :-)
> 
> The temperature sensor in the imaging sensor is used to measure the die
> temperature, in order to tune the noise and spectral response model of
> the imaging sensor. It's thus not a generic-purpose temperature sensor.
> If the feature were to be exposed through hwmon, userspace would need to
> associate an hwmon device to the imaging sensor V4L2 subdev (we have a
> way to do so through the MC API, it doesn't support hwmon at this point,
> but I suppose it could be added). There are also various constraints
> that tie the temperature reading to the imaging side, it could be that
> the temperature would only be readable while capturing frames. That's
> probably possible to handle too but returning an error from the
> temperature read.
> 
> Code-wise, both the driver and userspace would be more complex, for very
> little practical gain (I don't dispute a theorical gain).
> 

All I can say is - not my subsystem, not my call to make. If you say this
is special and is better handled in V4L2, I'll take you by your word.

Guenter

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

* Re: [PATCH v2 2/5] media: v4l: ctrls: Add a control for temperature
  2022-04-19  7:03   ` Hans Verkuil
@ 2022-04-20 13:01     ` Benjamin Mugnier
  0 siblings, 0 replies; 33+ messages in thread
From: Benjamin Mugnier @ 2022-04-20 13:01 UTC (permalink / raw)
  To: Hans Verkuil, linux-media
  Cc: alain.volmat, hugues.fruchet, sylvain.petinot, dave.stevenson,
	sakari.ailus, laurent.pinchart, kieran.bingham

Hi Hans,

Thank you for your review.

On 19/04/2022 09:03, Hans Verkuil wrote:
> On 15/04/2022 13:18, Benjamin Mugnier wrote:
>> Add V4L2_CID_TEMPERATURE control to get temperature from sensor in
>> celsius as a volatile and read-only control, and its documentation.
> 
> celsius -> degrees Celsius
> 
> (see https://en.wikipedia.org/wiki/Celsius)
> 

Yes, thank you.

>> Useful to monitor thermals from v4l controls for sensors that support
>> this.
>>
>> Signed-off-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com>
>> ---
>>  Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst | 3 +++
>>  drivers/media/v4l2-core/v4l2-ctrls-defs.c                  | 4 ++++
>>  include/uapi/linux/v4l2-controls.h                         | 2 ++
>>  3 files changed, 9 insertions(+)
>>
>> diff --git a/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst b/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst
>> index 4c5061aa9cd4..26fa21f5c45a 100644
>> --- a/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst
>> +++ b/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst
>> @@ -661,3 +661,6 @@ enum v4l2_scene_mode -
>>  .. [#f1]
>>     This control may be changed to a menu control in the future, if more
>>     options are required.
>> +
>> +``V4L2_CID_TEMPERATURE (integer)``
>> +    The temperature of the sensor in celsius. This is a read-only control.
> 
> Ditto
> 
>> diff --git a/drivers/media/v4l2-core/v4l2-ctrls-defs.c b/drivers/media/v4l2-core/v4l2-ctrls-defs.c
>> index 54ca4e6b820b..45ad3edd59e0 100644
>> --- a/drivers/media/v4l2-core/v4l2-ctrls-defs.c
>> +++ b/drivers/media/v4l2-core/v4l2-ctrls-defs.c
>> @@ -1042,6 +1042,7 @@ const char *v4l2_ctrl_get_name(u32 id)
>>  	case V4L2_CID_UNIT_CELL_SIZE:		return "Unit Cell Size";
>>  	case V4L2_CID_CAMERA_ORIENTATION:	return "Camera Orientation";
>>  	case V4L2_CID_CAMERA_SENSOR_ROTATION:	return "Camera Sensor Rotation";
>> +	case V4L2_CID_TEMPERATURE:		return "Temperature in °C";
> 
> I am not sure how well this ° symbol will work. The V4L2 spec says that this is an
> ASCII string, so that doesn't allow for this symbol.
> 
> I would just call it "Temperature".
> 

From what I see in v4l2-ctl it translate it to "temperature_in_c".
If we agree this is implicitly in in degrees celsius then let's remove it.

>>  
>>  	/* FM Radio Modulator controls */
>>  	/* Keep the order of the 'case's the same as in v4l2-controls.h! */
>> @@ -1597,6 +1598,9 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type,
>>  	case V4L2_CID_RF_TUNER_PLL_LOCK:
>>  		*flags |= V4L2_CTRL_FLAG_VOLATILE;
>>  		break;
>> +	case V4L2_CID_TEMPERATURE:
>> +		*flags |= V4L2_CTRL_FLAG_READ_ONLY |
>> +			  V4L2_CTRL_FLAG_VOLATILE;
> 
> Add a break!
> 

Whoops!

>>  	}
>>  }
>>  EXPORT_SYMBOL(v4l2_ctrl_fill);
>> diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h
>> index bb40129446d4..705b4043c2de 100644
>> --- a/include/uapi/linux/v4l2-controls.h
>> +++ b/include/uapi/linux/v4l2-controls.h
>> @@ -1008,6 +1008,8 @@ enum v4l2_auto_focus_range {
>>  
>>  #define V4L2_CID_CAMERA_SENSOR_ROTATION		(V4L2_CID_CAMERA_CLASS_BASE+35)
>>  
>> +#define V4L2_CID_TEMPERATURE			(V4L2_CID_CAMERA_CLASS_BASE+36)
> 
> Does it make sense to add this to CAMERA_CLASS? Can't this be a generic temperature
> control? (i.e. in USER_BASE) Any device can have a temperature sensor.
> 

I see no issue in moving it. Your call.

> I also think that making this an array control would make sense as well in case there
> are multiple temperature sensors.
> 
> Brainstorming some more: does this even belong here? Isn't this more a hwmon thing?
> E.g. compare this to drivers/nvme/host/hwmon.c.
> 
> A hwmon implementation seems to be a more natural mechanism.
> 
> Regards,
> 
> 	Hans
> 
>> +
>>  /* FM Modulator class control IDs */
>>  
>>  #define V4L2_CID_FM_TX_CLASS_BASE		(V4L2_CTRL_CLASS_FM_TX | 0x900)
> 

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

* Re: [PATCH v2 2/5] media: v4l: ctrls: Add a control for temperature
  2022-04-15 14:37   ` Laurent Pinchart
@ 2022-04-20 13:01     ` Benjamin Mugnier
  0 siblings, 0 replies; 33+ messages in thread
From: Benjamin Mugnier @ 2022-04-20 13:01 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: linux-media, alain.volmat, hugues.fruchet, sylvain.petinot,
	dave.stevenson, sakari.ailus, kieran.bingham

Hi Laurent,

Thank you for your review.

On 15/04/2022 16:37, Laurent Pinchart wrote:
> Hi Benjamin,
> 
> Thank you for the patch.
> 
> On Fri, Apr 15, 2022 at 01:18:42PM +0200, Benjamin Mugnier wrote:
>> Add V4L2_CID_TEMPERATURE control to get temperature from sensor in
>> celsius as a volatile and read-only control, and its documentation.
>> Useful to monitor thermals from v4l controls for sensors that support
>> this.
>>
>> Signed-off-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com>
>> ---
>>  Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst | 3 +++
>>  drivers/media/v4l2-core/v4l2-ctrls-defs.c                  | 4 ++++
>>  include/uapi/linux/v4l2-controls.h                         | 2 ++
>>  3 files changed, 9 insertions(+)
>>
>> diff --git a/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst b/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst
>> index 4c5061aa9cd4..26fa21f5c45a 100644
>> --- a/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst
>> +++ b/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst
>> @@ -661,3 +661,6 @@ enum v4l2_scene_mode -
>>  .. [#f1]
>>     This control may be changed to a menu control in the future, if more
>>     options are required.
>> +
>> +``V4L2_CID_TEMPERATURE (integer)``
>> +    The temperature of the sensor in celsius. This is a read-only control.
> 
> I've seen sensors where the temperature sensor has a 1/10th degree
> precision. Should we standardize on that ? Anything more precise is
> likely overkill.
> 

This sensor also has a 1/10th degree precision, here I only display the integer part. Ok to standardize on that.

> There are also sensors with multiple temperature sensors. If there are
> too many of them I suppose the temperature would be reported in embedded
> data, but perhaps not always. How can we prepare for this ?
> 
> There are also a few details that I think should be documented. Is the
> temperature always read on-demand when reading the control, or updated
> periodically ? I would assume most drivers would implement the former,
> which means no control notification events will be generated. This
> should be documented. Furthermore, do drivers need to support reading
> the temperature when the sensor isn't streaming ? If not, when should a
> control read ioctl return, the last value, or an error ?
> 

From what I see read on demand seems sufficient.
This sensor supports reading the temperature read even if not streaming. I don't know for other sensors.
See my comments on driver implementation on 5/5 for more.

>> diff --git a/drivers/media/v4l2-core/v4l2-ctrls-defs.c b/drivers/media/v4l2-core/v4l2-ctrls-defs.c
>> index 54ca4e6b820b..45ad3edd59e0 100644
>> --- a/drivers/media/v4l2-core/v4l2-ctrls-defs.c
>> +++ b/drivers/media/v4l2-core/v4l2-ctrls-defs.c
>> @@ -1042,6 +1042,7 @@ const char *v4l2_ctrl_get_name(u32 id)
>>  	case V4L2_CID_UNIT_CELL_SIZE:		return "Unit Cell Size";
>>  	case V4L2_CID_CAMERA_ORIENTATION:	return "Camera Orientation";
>>  	case V4L2_CID_CAMERA_SENSOR_ROTATION:	return "Camera Sensor Rotation";
>> +	case V4L2_CID_TEMPERATURE:		return "Temperature in °C";
>>  
>>  	/* FM Radio Modulator controls */
>>  	/* Keep the order of the 'case's the same as in v4l2-controls.h! */
>> @@ -1597,6 +1598,9 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type,
>>  	case V4L2_CID_RF_TUNER_PLL_LOCK:
>>  		*flags |= V4L2_CTRL_FLAG_VOLATILE;
>>  		break;
>> +	case V4L2_CID_TEMPERATURE:
>> +		*flags |= V4L2_CTRL_FLAG_READ_ONLY |
>> +			  V4L2_CTRL_FLAG_VOLATILE;
>>  	}
>>  }
>>  EXPORT_SYMBOL(v4l2_ctrl_fill);
>> diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h
>> index bb40129446d4..705b4043c2de 100644
>> --- a/include/uapi/linux/v4l2-controls.h
>> +++ b/include/uapi/linux/v4l2-controls.h
>> @@ -1008,6 +1008,8 @@ enum v4l2_auto_focus_range {
>>  
>>  #define V4L2_CID_CAMERA_SENSOR_ROTATION		(V4L2_CID_CAMERA_CLASS_BASE+35)
>>  
>> +#define V4L2_CID_TEMPERATURE			(V4L2_CID_CAMERA_CLASS_BASE+36)
>> +
>>  /* FM Modulator class control IDs */
>>  
>>  #define V4L2_CID_FM_TX_CLASS_BASE		(V4L2_CTRL_CLASS_FM_TX | 0x900)
> 

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

* Re: [PATCH v2 2/5] media: v4l: ctrls: Add a control for temperature
  2022-04-19 22:04         ` Guenter Roeck
@ 2022-04-20 13:01           ` Benjamin Mugnier
  2022-04-20 13:21             ` Laurent Pinchart
  0 siblings, 1 reply; 33+ messages in thread
From: Benjamin Mugnier @ 2022-04-20 13:01 UTC (permalink / raw)
  To: Guenter Roeck, Laurent Pinchart
  Cc: Nicolas Dufresne, linux-media, alain.volmat, hugues.fruchet,
	sylvain.petinot, dave.stevenson, sakari.ailus, kieran.bingham,
	linux-hwmon, Ricardo Ribalda

Hi everyone,

Just some little comments for my use case. Hope that helps.

On 20/04/2022 00:04, Guenter Roeck wrote:
> On 4/19/22 14:01, Laurent Pinchart wrote:
>> On Tue, Apr 19, 2022 at 12:28:06PM -0700, Guenter Roeck wrote:
>>> On 4/19/22 11:24, Nicolas Dufresne wrote:
>>>> Hi,
>>>>
>>>> adding linux-hwmon in CC for a wider feedback.
>>>>
>>>> Le vendredi 15 avril 2022 à 13:18 +0200, Benjamin Mugnier a écrit :
>>>>> Add V4L2_CID_TEMPERATURE control to get temperature from sensor in
>>>>> celsius as a volatile and read-only control, and its documentation.
>>>>> Useful to monitor thermals from v4l controls for sensors that support
>>>>> this.
>>>>
>>>> Any justification to expose a temperature sensor outside of the dedicated kernel
>>>> API hwmon ? I know if may makes it harder to use, as the sensor isn't bound to
>>>> the camera driver, and also the sensor may not work if the camera is not
>>>> streaming. But in the end, the API in hwmon does not look that complex, and is
>>>> perhaps more precise ?

This sensor is able to read the temperature even if not streaming.

>>>>
>>>> All in all, I think we need a strong justification to implement a custom
>>>> thermometer interface, something described here and documented with care to
>>>> prevent abuse. I would also see opinion from folks outside of the linux-media,
>>>> hence why I have CCed hwmon mailing list.
>>>
>>> All I can say is that this seems to be odd and a bit outside the scope of
>>> v4l2. I would have expected the vgxy61 driver to register a hwmon device
>>> instead.
>>
>> I don't have a definitive opinion yet, but as Nicolas raised the issue
>> by pushing towards hwmon, I'll offer counter-arguments for the sake of
>> it :-)
>>
>> The temperature sensor in the imaging sensor is used to measure the die
>> temperature, in order to tune the noise and spectral response model of
>> the imaging sensor. It's thus not a generic-purpose temperature sensor.
>> If the feature were to be exposed through hwmon, userspace would need to
>> associate an hwmon device to the imaging sensor V4L2 subdev (we have a
>> way to do so through the MC API, it doesn't support hwmon at this point,
>> but I suppose it could be added). There are also various constraints
>> that tie the temperature reading to the imaging side, it could be that
>> the temperature would only be readable while capturing frames. That's
>> probably possible to handle too but returning an error from the
>> temperature read.
>>
>> Code-wise, both the driver and userspace would be more complex, for very
>> little practical gain (I don't dispute a theorical gain).
>>
> 
> All I can say is - not my subsystem, not my call to make. If you say this
> is special and is better handled in V4L2, I'll take you by your word.
> 
> Guenter
> 

I'll happily implement whatever conclusion we make here.

I could also drop this control for the first iteration of the driver, and come back later once a consensus is reached.

Benjamin

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

* Re: [PATCH v2 4/5] media: dt-bindings: media: i2c: Add ST VGXY61 camera sensor binding
  2022-04-15 14:59   ` Laurent Pinchart
@ 2022-04-20 13:01     ` Benjamin Mugnier
  0 siblings, 0 replies; 33+ messages in thread
From: Benjamin Mugnier @ 2022-04-20 13:01 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: linux-media, alain.volmat, hugues.fruchet, sylvain.petinot,
	dave.stevenson, sakari.ailus, kieran.bingham

Hi Laurent,

Thank you for your review.

On 15/04/2022 16:59, Laurent Pinchart wrote:
> Hi Benjamin,
> 
> Thank you for the patch.
> 
> On Fri, Apr 15, 2022 at 01:18:44PM +0200, 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      | 125 ++++++++++++++++++
>>  MAINTAINERS                                   |   9 ++
>>  2 files changed, 134 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..24bff161a661
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml
>> @@ -0,0 +1,125 @@
>> +# 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
> 
> If there's a single clock you can drop the clock name.
> 

Indeed, I forgot to remove it while refactoring. Thanks.

>> +
>> +  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:
> 
> Vendor-specific properties should have a vendor prefix (e.g.
> st,inver-gpios-polarity).
> 
>> +    description:
>> +      If gpios polarity should be inversed
> 
> What is this though, what GPIOs does it invert ?
> 

This inverts every strobe GPIO polarity.
I will rename it to st,strobe-gpios-polarity, so that it better reflects what it truly is, and add a better comment than the current one.
I also renamed slave-mode to st,slave-mode as afaik this is specific to this sensor.

>> +    type: boolean
>> +
>> +  slave-mode:
>> +    description:
>> +      If the sensor operates in slave mode
>> +    type: boolean
>> +
>> +  port:
>> +    $ref: /schemas/graph.yaml#/$defs/port-base
>> +    additionalProperties: false
>> +
>> +    properties:
>> +      endpoint:
>> +        $ref: /schemas/media/video-interfaces.yaml#
>> +        unevaluatedProperties: false
>> +
>> +        properties:
>> +          data-lanes:
>> +            description:
>> +              CSI lanes to use
>> +            items:
>> +              - const: 1
>> +              - const: 2
>> +              - const: 3
>> +              - const: 4
>> +
>> +          remote-endpoint: true
>> +
>> +        required:
>> +          - clock-lane
>> +          - data-lanes
>> +
>> +required:
>> +  - compatible
>> +  - clocks
>> +  - clock-names
>> +  - VCORE-supply
>> +  - VDDIO-supply
>> +  - VANA-supply
> 
> The port property should be required too.
> 
>> +
>> +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";
> 
> That's the default, it's not needed in DT examples.
> 
>> +            clocks = <&clk_ext_camera>;
> 
> There's no clock-names. Have you run DT bindings validation ? It should
> have reported an error.
> 

I ran again the dt binding validation and it does pop an error. I must have missed it. Sorry about that.

>> +            VCORE-supply = <&v1v2>;
>> +            VDDIO-supply = <&v1v8>;
>> +            VANA-supply = <&v2v8>;
>> +            reset-gpios = <&mfxgpio 18 GPIO_ACTIVE_LOW>;
>> +            port {
>> +                ep0: endpoint {
>> +                    data-lanes = <1 2 3 4>;
>> +                    remote-endpoint = <&mipi_csi2_out>;
>> +                };
>> +            };
>> +        };
>> +    };
>> +...
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index fd768d43e048..6233e073da30 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -18664,6 +18664,15 @@ 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
> 

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

* Re: [PATCH v2 5/5] media: i2c: Add driver for ST VGXY61 camera sensor
  2022-04-15 15:39   ` Laurent Pinchart
@ 2022-04-20 13:01     ` Benjamin Mugnier
  2022-04-20 16:48       ` Laurent Pinchart
  0 siblings, 1 reply; 33+ messages in thread
From: Benjamin Mugnier @ 2022-04-20 13:01 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: linux-media, alain.volmat, hugues.fruchet, sylvain.petinot,
	dave.stevenson, sakari.ailus, kieran.bingham

Hi Laurent,

Thank you for your review.
Please consider everything I did not comment as agreed.

On 15/04/2022 17:39, Laurent Pinchart wrote:
> Hi Benjamin,
> 
> Thank you for the patch.
> 
> This is a partial review as I'm short on time now.
> 

No worries ;)

> On Fri, Apr 15, 2022 at 01:18:45PM +0200, Benjamin Mugnier wrote:
>> 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     |   10 +
>>  drivers/media/i2c/Makefile    |    1 +
>>  drivers/media/i2c/st-vgxy61.c | 1997 +++++++++++++++++++++++++++++++++
>>  3 files changed, 2008 insertions(+)
>>  create mode 100644 drivers/media/i2c/st-vgxy61.c
>>
>> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
>> index fae2baabb773..734f3e1130b0 100644
>> --- a/drivers/media/i2c/Kconfig
>> +++ b/drivers/media/i2c/Kconfig
>> @@ -763,6 +763,16 @@ config VIDEO_VS6624
>>  	  To compile this driver as a module, choose M here: the
>>  	  module will be called vs6624.
>>  
>> +config VIDEO_ST_VGXY61
> 
> Alphabetical order please.
> 
>> +	tristate "ST VGXY61 sensor support"
>> +	depends on OF && GPIOLIB && VIDEO_DEV && 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.
>> +
>>  source "drivers/media/i2c/ccs/Kconfig"
>>  source "drivers/media/i2c/et8ek8/Kconfig"
>>  source "drivers/media/i2c/m5mols/Kconfig"
>> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
>> index 3e1696963e7f..7f2362228484 100644
>> --- a/drivers/media/i2c/Makefile
>> +++ b/drivers/media/i2c/Makefile
>> @@ -116,6 +116,7 @@ obj-$(CONFIG_VIDEO_SAA7185) += saa7185.o
>>  obj-$(CONFIG_VIDEO_SONY_BTF_MPX) += sony-btf-mpx.o
>>  obj-$(CONFIG_VIDEO_SR030PC30) += sr030pc30.o
>>  obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o
>> +obj-$(CONFIG_VIDEO_ST_VGXY61) += st-vgxy61.o
>>  obj-$(CONFIG_VIDEO_TC358743) += tc358743.o
>>  obj-$(CONFIG_VIDEO_TDA1997X) += tda1997x.o
>>  obj-$(CONFIG_VIDEO_TDA7432) += tda7432.o
>> diff --git a/drivers/media/i2c/st-vgxy61.c b/drivers/media/i2c/st-vgxy61.c
>> new file mode 100644
>> index 000000000000..1219d278e648
>> --- /dev/null
>> +++ b/drivers/media/i2c/st-vgxy61.c
>> @@ -0,0 +1,1997 @@
>> +// 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/pm_runtime.h>
> 
> Alphabetical order in headers too please.
> 
>> +#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
> 
> I don't think this deserves a macro, but if you want to keep it, you
> should avoid generic names that may cause namespace clashes.
> 

I don't make heavy usage of it so I will remove it.

>> +
>> +#define DEVICE_MODEL_ID_REG				0x0000
> 
> Please prefix macros with a driver-specific prefix. Many of them have a
> too generic name.
> 

I will change this to VGXY61_REG_* for registers, and simply VGXY61_* for others.

>> +#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 VGXY61_WRITE_MULTIPLE_CHUNK_MAX			16
>> +#define VGXY61_NB_GPIOS					4
>> +#define VGXY61_NB_POLARITIES				5
>> +#define VGXY61_MIN_FRAME_LENGTH				1288
>> +#define VGXY61_MIN_EXPOSURE				10
>> +#define VGXY61_HDR_LINEAR_RATIO				10
>> +#define VGXY61_TIMEOUT_MS				500
>> +#define VGXY61_FRAME_LENGTH_DEF				1313
>> +#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_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_STVGXY61_BASE + 0)
> 
> This macro should have a device-specific name, should be moved to a
> public header, and the control should be documented. I would split the
> control documentation and macro definition to a separate patch.
> 

I created two new files include/uapi/linux/st-vgxy61.h and Documentation/userspace-api/media/drivers/st-vgxy61.rst.
I'm not sure what you mean by separating the documentation and definition. As there is a significant amount of code dependent on the HDR mode I will keep the public header in this patch, and create a new patch for the documentation. Tell me if there is any issue with this.

>> +
>> +#define DEVICE_FWPATCH_REVISION_MAJOR			2
>> +#define DEVICE_FWPATCH_REVISION_MINOR			0
>> +#define DEVICE_FWPATCH_REVISION_MICRO			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
> 
> The link frequencies should come from DT.
> 

This is a hard requirement for this sensor. This value can not be modified and must be 402MHz.
I see the ov9734.c does something similar. Is adding this value to the device tree really mandatory?

>> +};
>> +
>> +#define VGXY61_NUM_SUPPLIES		ARRAY_SIZE(vgxy61_supply_name)
>> +
>> +enum bin_mode {
> 
> Same comment here, all enums and structures should use a driver-specific
> prefix.
> 
>> +	BIN_MODE_NORMAL,
>> +	BIN_MODE_DIGITAL_X2,
>> +	BIN_MODE_DIGITAL_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;
>> +	struct v4l2_rect crop;
>> +};
>> +
>> +static const u32 vgxy61_supported_codes[] = {
>> +	MEDIA_BUS_FMT_Y8_1X8,
>> +	MEDIA_BUS_FMT_Y10_1X10,
>> +	MEDIA_BUS_FMT_Y12_1X12,
>> +	MEDIA_BUS_FMT_Y14_1X14,
>> +	MEDIA_BUS_FMT_Y16_1X16
>> +};
>> +
>> +static const struct vgxy61_mode_info vgx661_mode_data[] = {
>> +	{
>> +		.width = VGX661_WIDTH,
>> +		.height = VGX661_HEIGHT,
>> +		.bin_mode = BIN_MODE_NORMAL,
>> +		.crop = {
>> +			.left = 0,
>> +			.top = 0,
>> +			.width = VGX661_WIDTH,
>> +			.height = VGX661_HEIGHT,
>> +		},
> 
> It's very nice to see no hardcoded register values in the modes tables
> :-) As the register values are computed by the driver, would it make
> sense to go one step further and drop the modes completely ? Analog crop
> and binning could then be selected freely from userspace.
> 

All credit goes to Dave for pointing this in the first review ;)
As for the modes, in theory we could drop them completely, but this will require some extra testing as the sensor can't really handle every single mode freely.
Could this be done in a second step?

>> +	},
>> +	{
>> +		.width = 1280,
>> +		.height = 720,
>> +		.bin_mode = BIN_MODE_NORMAL,
>> +		.crop = {
>> +			.left = 92,
>> +			.top = 192,
>> +			.width = 1280,
>> +			.height = 720,
>> +		},
>> +	},
>> +	{
>> +		.width = 640,
>> +		.height = 480,
>> +		.bin_mode = BIN_MODE_DIGITAL_X2,
>> +		.crop = {
>> +			.left = 92,
>> +			.top = 72,
>> +			.width = 1280,
>> +			.height = 960,
>> +		},
>> +	},
>> +	{
>> +		.width = 320,
>> +		.height = 240,
>> +		.bin_mode = BIN_MODE_DIGITAL_X4,
> 
> Can the bin mode computed by dividing the crop width by the output width
> instead of being stored here ?
> 

The sensor also supports analog subsampling on top of digital binning. I did not implement it in the driver but I made the choice to explicitly set the bin_mode for further development. For example the line above could be:
  .bin_mode = BIN_MODE_ANALOG_X4
I can compute the factor (X2/X4), but not the analog/digital mode. Hope this make sense.

>> +		.crop = {
>> +			.left = 92,
>> +			.top = 72,
>> +			.width = 1280,
>> +			.height = 960,
>> +		},
>> +	},
>> +};
>> +
>> +static const struct vgxy61_mode_info vgx761_mode_data[] = {
>> +	{
>> +		.width = VGX761_WIDTH,
>> +		.height = VGX761_HEIGHT,
>> +		.bin_mode = BIN_MODE_NORMAL,
>> +		.crop = {
>> +			.left = 0,
>> +			.top = 0,
>> +			.width = VGX761_WIDTH,
>> +			.height = VGX761_HEIGHT,
>> +		},
>> +	},
>> +	{
>> +		.width = 1920,
>> +		.height = 1080,
>> +		.bin_mode = BIN_MODE_NORMAL,
>> +		.crop = {
>> +			.left = 12,
>> +			.top = 62,
>> +			.width = 1920,
>> +			.height = 1080,
>> +		},
>> +	},
>> +	{
>> +		.width = 1280,
>> +		.height = 720,
>> +		.bin_mode = BIN_MODE_NORMAL,
>> +		.crop = {
>> +			.left = 332,
>> +			.top = 242,
>> +			.width = 1280,
>> +			.height = 720,
>> +		},
>> +	},
>> +	{
>> +		.width = 640,
>> +		.height = 480,
>> +		.bin_mode = BIN_MODE_DIGITAL_X2,
>> +		.crop = {
>> +			.left = 332,
>> +			.top = 122,
>> +			.width = 1280,
>> +			.height = 960,
>> +		},
>> +	},
>> +	{
>> +		.width = 320,
>> +		.height = 240,
>> +		.bin_mode = BIN_MODE_DIGITAL_X4,
>> +		.crop = {
>> +			.left = 332,
>> +			.top = 122,
>> +			.width = 1280,
>> +			.height = 960,
>> +		},
>> +	},
>> +};
>> +
>> +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 id;
>> +	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 v4l2_ctrl_handler ctrl_handler;
>> +	struct v4l2_ctrl *pixel_rate_ctrl;
>> +	struct v4l2_ctrl *expo_ctrl;
>> +	struct v4l2_ctrl *vblank_ctrl;
>> +	struct v4l2_ctrl *vflip_ctrl;
>> +	struct v4l2_ctrl *hflip_ctrl;
>> +	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;
>> +	struct v4l2_fract frame_interval;
> 
> This is not used. Please check all other fields too.
> 
>> +	bool hflip;
>> +	bool vflip;
>> +	enum hdr hdr;
>> +	int expo_long;
>> +	int expo_short;
>> +	int expo_max;
>> +	int expo_min;
>> +	int vblank;
>> +	int vblank_min;
>> +	u16 frame_length;
>> +};
>> +
>> +static u8 get_bpp_by_code(__u32 code)
>> +{
>> +	switch (code) {
>> +	case MEDIA_BUS_FMT_Y8_1X8:
>> +		return 8;
>> +	case MEDIA_BUS_FMT_Y10_1X10:
>> +		return 10;
>> +	case MEDIA_BUS_FMT_Y12_1X12:
>> +		return 12;
>> +	case MEDIA_BUS_FMT_Y14_1X14:
>> +		return 14;
>> +	case MEDIA_BUS_FMT_Y16_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_Y8_1X8:
>> +		return MIPI_CSI2_DT_RAW8;
>> +	case MEDIA_BUS_FMT_Y10_1X10:
>> +		return MIPI_CSI2_DT_RAW10;
>> +	case MEDIA_BUS_FMT_Y12_1X12:
>> +		return MIPI_CSI2_DT_RAW12;
>> +	case MEDIA_BUS_FMT_Y14_1X14:
>> +		return MIPI_CSI2_DT_RAW14;
>> +	case MEDIA_BUS_FMT_Y16_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;
>> +	}
>> +}
> 
> I'd put all this data in a format description structure, and store that
> in the vgxy61_supported_codes array.
> 
>> +
>> +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, ctrl_handler)->sd;
>> +}
>> +
>> +static int get_chunk_size(struct vgxy61_dev *sensor)
>> +{
>> +	struct i2c_adapter *adapter = sensor->i2c_client->adapter;
>> +	int max_write_len = VGXY61_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, VGXY61_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[VGXY61_WRITE_MULTIPLE_CHUNK_MAX + 2];
>> +	int i;
>> +	int ret;
>> +
>> +	if (len > VGXY61_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));
>> +}
> 
> You could possibly simplify handling of registers of different sizes by
> encoding the size in the register address macros. See
> https://gitlab.com/ideasonboard/nxp/linux/-/blob/pinchartl/v5.18/sensors/imx/drivers/media/i2c/imx296.c
> for an example.
> 

That sounds good, thank you. I will look into this.

>> +
>> +static int vgxy61_poll_reg(struct vgxy61_dev *sensor, u16 reg, u8 poll_val, int timeout_ms)
>> +{
>> +	const int loop_delay_ms = 10;
>> +	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, int timeout_ms)
>> +{
>> +	return vgxy61_poll_reg(sensor, DEVICE_SYSTEM_FSM, state, timeout_ms);
>> +}
>> +
>> +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 = sensor->current_mode->crop.height / sensor->current_mode->height;
>> +	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;
> 
> Is there a risk those writes may be applied to different frames ?
> 

Yes, but it does not matter for the sensor. Are you thinking of an issue about user space applications for instance?

>> +
>> +	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)
> 
> The function sets the frame length, not the frame rate.
> 
>> +{
>> +	return vgxy61_write_reg16(sensor, DEVICE_FRAME_LENGTH, sensor->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, VGXY61_TIMEOUT_MS);
>> +}
>> +
>> +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 */
>> +	mode = v4l2_find_nearest_size(sensor->sensor_modes, sensor->sensor_modes_nb,
>> +				      width, height, fmt->width, fmt->height);
>> +	if (new_mode)
>> +		*new_mode = mode;
>> +
>> +	fmt->code = vgxy61_supported_codes[index];
>> +	fmt->width = mode->width;
>> +	fmt->height = mode->height;
>> +	fmt->colorspace = V4L2_COLORSPACE_RAW;
>> +	fmt->field = V4L2_FIELD_NONE;
>> +	fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
>> +	fmt->quantization = V4L2_QUANTIZATION_DEFAULT;
>> +	fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT;
>> +
>> +	return 0;
>> +}
>> +
>> +static int vgxy61_get_selection(struct v4l2_subdev *sd,
>> +				struct v4l2_subdev_state *sd_state,
>> +				struct v4l2_subdev_selection *sel)
>> +{
>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>> +
>> +	switch (sel->target) {
>> +	case V4L2_SEL_TGT_CROP:
>> +		sel->r = sensor->current_mode->crop;
>> +		return 0;
>> +	case V4L2_SEL_TGT_NATIVE_SIZE:
>> +	case V4L2_SEL_TGT_CROP_DEFAULT:
>> +	case V4L2_SEL_TGT_CROP_BOUNDS:
>> +		sel->r.top = 0;
>> +		sel->r.left = 0;
>> +		sel->r.width = sensor->sensor_width;
>> +		sel->r.height = sensor->sensor_height;
>> +		return 0;
>> +	}
>> +
>> +	return -EINVAL;
>> +}
>> +
>> +static int vgxy61_stream_enable(struct vgxy61_dev *sensor)
>> +{
>> +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd);
>> +	const struct v4l2_rect *crop = &sensor->current_mode->crop;
>> +	int ret;
>> +
>> +	ret = vgxy61_check_bw(sensor);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = pm_runtime_get_sync(&client->dev);
>> +	if (ret < 0) {
>> +		pm_runtime_put_noidle(&client->dev);
>> +		return ret;
>> +	}
>> +
>> +	/* Configure sensor */
>> +	ret = vgxy61_write_reg(sensor, DEVICE_FORMAT_CTRL, get_bpp_by_code(sensor->fmt.code));
>> +	if (ret)
>> +		goto err_rpm_put;
>> +	ret = vgxy61_write_reg(sensor, DEVICE_OIF_ROI0_CTRL,
>> +			       get_data_type_by_code(sensor->fmt.code));
>> +	if (ret)
>> +		goto err_rpm_put;
>> +
>> +	ret = vgxy61_write_reg(sensor, DEVICE_READOUT_CTRL, sensor->current_mode->bin_mode);
>> +	if (ret)
>> +		goto err_rpm_put;
>> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_H, crop->left);
>> +	if (ret)
>> +		goto err_rpm_put;
>> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_H, crop->left + crop->width - 1);
>> +	if (ret)
>> +		goto err_rpm_put;
>> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_V, crop->top);
>> +	if (ret)
>> +		goto err_rpm_put;
>> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_V, crop->top + crop->height - 1);
>> +	if (ret)
>> +		goto err_rpm_put;
>> +
>> +	ret = set_frame_rate(sensor);
>> +	if (ret)
>> +		goto err_rpm_put;
>> +
>> +	ret = apply_exposure(sensor);
>> +	if (ret)
>> +		goto err_rpm_put;
>> +
>> +	/* Start streaming */
>> +	ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_START_STREAMING);
>> +	if (ret)
>> +		goto err_rpm_put;
>> +
>> +	ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST, VGXY61_TIMEOUT_MS);
>> +	if (ret)
>> +		goto err_rpm_put;
>> +
>> +	ret = vgxy61_wait_state(sensor, STREAMING, VGXY61_TIMEOUT_MS);
>> +	if (ret)
>> +		goto err_rpm_put;
>> +
>> +	/* vflip and hflip cannot change during streaming */
>> +	__v4l2_ctrl_grab(sensor->vflip_ctrl, true);
>> +	__v4l2_ctrl_grab(sensor->hflip_ctrl, true);
>> +
>> +	return 0;
>> +
>> +err_rpm_put:
>> +	pm_runtime_put(&client->dev);
>> +	return ret;
>> +}
>> +
>> +static int vgxy61_stream_disable(struct vgxy61_dev *sensor)
>> +{
>> +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd);
>> +	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, 2000);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = vgxy61_wait_state(sensor, SW_STBY, VGXY61_TIMEOUT_MS);
>> +	if (ret)
>> +		return ret;
>> +
>> +	__v4l2_ctrl_grab(sensor->vflip_ctrl, true);
>> +	__v4l2_ctrl_grab(sensor->hflip_ctrl, true);
>> +
>> +	pm_runtime_put(&client->dev);
>> +
>> +	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;
> 
> This should never happen. If you want to be on the safe side and keep
> the check, I'd turn it into a WARN_ON().
> 

You are right, I will remove this.

>> +
>> +	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_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);q
>> +
>> +	return 0;
>> +}
>> +
>> +static int vgxy61_get_vblank_min(struct vgxy61_dev *sensor, enum hdr hdr)
>> +{
>> +	int min_vblank =  VGXY61_MIN_FRAME_LENGTH - sensor->current_mode->crop.height;
>> +	/* Ensure the first tule of thumb can't be negative */
>> +	int min_vblank_hdr =  VGXY61_MIN_EXPOSURE + sensor->rot_term + 1;
>> +
>> +	if (hdr != NO_HDR)
>> +		return max(min_vblank, min_vblank_hdr);
>> +	return min_vblank;
>> +}
>> +
>> +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_update_analog_gain(struct vgxy61_dev *sensor, u32 target)
>> +{
>> +	struct i2c_client *client = sensor->i2c_client;
>> +	int ret;
>> +
>> +	/* Apply gain */
>> +	ret = vgxy61_write_reg(sensor, DEVICE_ANALOG_GAIN, target);
>> +	if (ret)
>> +		return ret;
>> +
>> +	dev_dbg(&client->dev, "Set analog gain: 0x%04x\n", target);
>> +
>> +	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;
> 
> Macros would be nice instead of magic numbers. Same where applicable
> below.
> 
>> +	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};
> 
> Make this static const.
> 
>> +	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 < VGXY61_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 vgxy61_get_expo_long_max(struct vgxy61_dev *sensor, int short_expo_ratio)
>> +{
>> +	int first_rot_max_expo, second_rot_max_expo, third_rot_max_expo;
>> +
>> +	/* Apply sensor rule of thumbs */
>> +	/* Avoid bad pixel line at the botom of the image */
>> +	first_rot_max_expo =
>> +		((sensor->frame_length - sensor->current_mode->crop.height - sensor->rot_term) *
>> +		short_expo_ratio) - 1;
>> +
>> +	/* Avoid sensor crash */
> 
> How so ?
> 

This specific rule guarantees the framelength < expo_time. If this is not the case then the sensor will crash.
I will add a bit more comment for this, and for other rules also.

>> +	second_rot_max_expo =
>> +		(((sensor->frame_length - VGXY61_EXPOS_ROT_TERM) * short_expo_ratio) /
>> +		(short_expo_ratio + 1)) - 1;
>> +
>> +	/* Avoid sensor crash */
>> +	third_rot_max_expo = (sensor->frame_length / 71) * short_expo_ratio;
>> +
>> +	/* Take the minimum from all rules */
>> +	return min(min(first_rot_max_expo, second_rot_max_expo), third_rot_max_expo);
>> +}
>> +
>> +static int vgxy61_update_exposure(struct vgxy61_dev *sensor, int new_expo_long, enum hdr hdr)
>> +{
>> +	struct i2c_client *client = sensor->i2c_client;
>> +	int new_expo_short = 0;
>> +	int expo_short_max = 0;
>> +	int expo_long_min = VGXY61_MIN_EXPOSURE;
>> +	int expo_long_max;
>> +
>> +	/* Compute short exposure according to hdr mode and long exposure */
>> +	switch (hdr) {
>> +	case HDR_LINEAR:
>> +		/* Take ratio into account for minimal exposures in HDR_LINEAR */
>> +		expo_long_min = VGXY61_MIN_EXPOSURE * VGXY61_HDR_LINEAR_RATIO;
>> +		new_expo_long = max(expo_long_min, new_expo_long);
>> +
>> +		expo_long_max = vgxy61_get_expo_long_max(sensor, VGXY61_HDR_LINEAR_RATIO);
>> +		expo_short_max =
>> +			(expo_long_max + (VGXY61_HDR_LINEAR_RATIO / 2)) / VGXY61_HDR_LINEAR_RATIO;
>> +		new_expo_short =
>> +			(new_expo_long + (VGXY61_HDR_LINEAR_RATIO / 2)) / VGXY61_HDR_LINEAR_RATIO;
>> +		break;
>> +	case HDR_SUB:
>> +		new_expo_long = max(expo_long_min, new_expo_long);
>> +
>> +		expo_long_max = vgxy61_get_expo_long_max(sensor, 1);
>> +		/* Short and long are the same in HDR_SUB */
>> +		expo_short_max = expo_long_max;
>> +		new_expo_short = new_expo_long;
>> +		break;
>> +	case NO_HDR:
>> +		new_expo_long = max(expo_long_min, new_expo_long);
>> +
>> +		/*
>> +		 * As short expo is 0 here, only the second rule of thumb applies, see
>> +		 * vgxy61_get_expo_long_max for more
>> +		 */
>> +		expo_long_max = sensor->frame_length - VGXY61_EXPOS_ROT_TERM;
>> +		break;
>> +	default:
>> +		/* Should never happen */
>> +		WARN_ON(true);
>> +		break;
>> +	}
>> +
>> +	/* If this happens, something is wrong with formulas */
>> +	WARN_ON(expo_long_min > expo_long_max);
>> +
>> +	if (new_expo_long > expo_long_max) {
>> +		dev_warn(&client->dev, "Exposure %d too high, clamping to %d\n",
>> +			 new_expo_long, expo_long_max);
>> +		new_expo_long = expo_long_max;
>> +		new_expo_short = expo_short_max;
>> +	}
>> +
>> +	dev_dbg(&client->dev, "frame_length %d, new_expo_long %d, new_expo_short %d\n",
>> +		sensor->frame_length, new_expo_long, new_expo_short);
>> +	dev_dbg(&client->dev, "expo_long_min %d, expo_long_max %d\n",
>> +		expo_long_min, expo_long_max);
>> +
>> +	/* Apply exposure */
>> +	sensor->expo_long = new_expo_long;
>> +	sensor->expo_short = new_expo_short;
>> +	sensor->expo_max = expo_long_max;
>> +	sensor->expo_min = expo_long_min;
>> +
>> +	if (sensor->streaming)
>> +		return apply_exposure(sensor);
>> +	return 0;
>> +}
>> +
>> +static int vgxy61_update_vblank(struct vgxy61_dev *sensor, int vblank, int hdr)
>> +{
>> +	int ret;
>> +
>> +	sensor->vblank_min = vgxy61_get_vblank_min(sensor, hdr);
>> +	sensor->vblank = max(sensor->vblank_min, vblank);
>> +	sensor->frame_length = sensor->current_mode->crop.height + sensor->vblank;
>> +
>> +	/* Update exposure according to vblank */
>> +	ret = vgxy61_update_exposure(sensor, sensor->expo_long, hdr);
>> +	if (ret)
>> +		return ret;
>> +
>> +	if (sensor->streaming)
>> +		return set_frame_rate(sensor);
>> +	return 0;
>> +}
>> +
>> +static int vgxy61_update_hdr(struct vgxy61_dev *sensor, u32 index)
>> +{
>> +	const u8 index2val[] = {0x1, 0x4, 0xa};
>> +	int ret;
>> +
>> +	/*
>> +	 * vblank and short exposure change 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_vblank(sensor, sensor->vblank, index);
>> +	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_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);
>> +		*fmt = format->format;
>> +	} else if (sensor->current_mode != new_mode || sensor->fmt.code != format->format.code) {
>> +		fmt = &sensor->fmt;
>> +		*fmt = format->format;
>> +
>> +		sensor->current_mode = new_mode;
>> +
>> +		/* Reset vblank and framelength to default */
>> +		ret = vgxy61_update_vblank(sensor,
>> +					   VGXY61_FRAME_LENGTH_DEF -
>> +					   sensor->current_mode->crop.height,
>> +					   sensor->hdr);
>> +
>> +		/* Update controls to reflect new mode */
>> +		__v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate_ctrl, get_pixel_rate(sensor));
>> +		__v4l2_ctrl_modify_range(sensor->vblank_ctrl, sensor->vblank_min,
>> +					 0xffff - sensor->current_mode->crop.height, 1,
>> +					 sensor->vblank);
>> +		__v4l2_ctrl_s_ctrl(sensor->vblank_ctrl, sensor->vblank);
>> +		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
>> +					 sensor->expo_max, 1,
>> +					 sensor->expo_long);
>> +	}
>> +
>> +out:
>> +	mutex_unlock(&sensor->lock);
>> +
>> +	return ret;
>> +}
>> +
>> +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);
> 
> Can't runtime PM be disabled here ?
> 

You're absolutely right. This works as of now because I guess my regulators are used for something else and therefore do not go down, but should not.
This is tricky as I don't want to power up (and therefore patch) the sensor every time I want to see the temperature, but maybe I won't have the choice.
For example every time I stop streaming in qv4l2, the temperature is read. This would result in this:
	power off -> get_temp -> power on (+patch) -> read temp -> power off
Maybe I can just remove the temperature reading while off? Any other idea?

Anyway this will depend on the 2/5 patch discussion thread result.

>> +	if (ret)
>> +		return ret;
>> +	ret = vgxy61_poll_reg(sensor, DEVICE_STBY, STBY_NO_REQ, VGXY61_TIMEOUT_MS);
>> +	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);
>> +		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);
>> +		/* Update vblank and exposure controls to match new hdr */
>> +		__v4l2_ctrl_modify_range(sensor->vblank_ctrl, sensor->vblank_min,
>> +					 0xffff - sensor->current_mode->crop.height, 1,
>> +					 sensor->vblank);
>> +		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
>> +					 sensor->expo_max, 1, sensor->expo_long);
>> +		break;
>> +	case V4L2_CID_VBLANK:
>> +		ret = vgxy61_update_vblank(sensor, ctrl->val, sensor->hdr);
>> +		/* Update exposure control to match new vblank */
>> +		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
>> +					 sensor->expo_max, 1, sensor->expo_long);
>> +		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 ret;
>> +
>> +	switch (ctrl->id) {
>> +	case V4L2_CID_TEMPERATURE:
>> +		ret = vgxy61_get_temp(sensor, &ctrl->val);
>> +		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 int vgxy61_init_controls(struct vgxy61_dev *sensor)
>> +{
>> +	const struct v4l2_ctrl_ops *ops = &vgxy61_ctrl_ops;
>> +	struct v4l2_ctrl_handler *hdl = &sensor->ctrl_handler;
>> +	struct v4l2_ctrl *ctrl;
>> +	int ret;
>> +
>> +	v4l2_ctrl_handler_init(hdl, 16);
>> +	mutex_init(&sensor->lock);
> 
> This should be initialized in probe(), it doesn't belong here.
> 
>> +	/* We can use our own mutex for the ctrl lock */
>> +	hdl->lock = &sensor->lock;
>> +	/* Standard controls */
>> +	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, 0x1c, 1, 0x0);
>> +	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 0, 0xfff, 1, 256);
>> +	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_TEMPERATURE, -128, 128, 1, 0);
>> +	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);
>> +	v4l2_ctrl_new_custom(hdl, &vgxy61_hdr_ctrl, NULL);
>> +	ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HBLANK, 0, sensor->line_length, 1,
>> +				 sensor->line_length - sensor->current_mode->width);
>> +	ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>> +	/* Custom controls */
>> +	ctrl = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ, ARRAY_SIZE(link_freq) - 1, 0,
>> +				      link_freq);
>> +	ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>> +	/* Keep a pointer to these controls as we need to update them when setting the format */
>> +	sensor->pixel_rate_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1,
>> +						    get_pixel_rate(sensor));
>> +	sensor->pixel_rate_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>> +	sensor->expo_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, sensor->expo_min,
>> +					      sensor->expo_max, 1, sensor->expo_long);
>> +	sensor->vblank_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VBLANK, sensor->vblank_min,
>> +						0xffff - sensor->current_mode->crop.height, 1,
>> +						sensor->vblank);
>> +	sensor->vflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, sensor->vflip);
>> +	sensor->hflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, sensor->hflip);
>> +
>> +	if (hdl->error) {
>> +		ret = hdl->error;
>> +		goto free_ctrls;
>> +	}
>> +
>> +	sensor->sd.ctrl_handler = hdl;
>> +	return 0;
>> +
>> +free_ctrls:
>> +	v4l2_ctrl_handler_free(hdl);
>> +	mutex_destroy(&sensor->lock);
>> +	return ret;
>> +}
>> +
>> +static const struct v4l2_subdev_core_ops vgxy61_core_ops = {
>> +};
> 
> You can drop this.
> 
>> +
>> +static const struct v4l2_subdev_video_ops vgxy61_video_ops = {
>> +	.s_stream = vgxy61_s_stream,
>> +};
>> +
>> +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,
>> +	.get_selection = vgxy61_get_selection,
>> +	.enum_frame_size = vgxy61_enum_frame_size,
>> +};
>> +
>> +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[VGXY61_NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
>> +	u32 phy2log[VGXY61_NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
>> +	int polarities[VGXY61_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 < VGXY61_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;
>> +	/* 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, VGXY61_TIMEOUT_MS);
>> +	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_dbg(&client->dev, "Cut1 detected\n");
>> +		dev_err(&client->dev, "Cut1 not supported by this driver\n");
>> +		return -ENODEV;
>> +	case 0xB:
>> +		dev_dbg(&client->dev, "Cut2 detected\n");
>> +		return 0;
>> +	case 0xC:
>> +		dev_dbg(&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);
>> +	sensor->id = id;
>> +
>> +	ret = vgxy61_wait_state(sensor, SW_STBY, VGXY61_TIMEOUT_MS);
>> +	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;
>> +}
>> +
>> +/* Power/clock management functions */
>> +static int vgxy61_power_on(struct device *dev)
>> +{
>> +	struct i2c_client *client = to_i2c_client(dev);
>> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>> +	int ret;
>> +
>> +	ret = vgxy61_get_regulators(sensor);
>> +	if (ret) {
>> +		dev_err(&client->dev, "failed to get regulators %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	ret = regulator_bulk_enable(VGXY61_NUM_SUPPLIES, sensor->supplies);
>> +	if (ret) {
>> +		dev_err(&client->dev, "failed to enable regulators %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	ret = clk_prepare_enable(sensor->xclk);
>> +	if (ret) {
>> +		dev_err(&client->dev, "failed to enable clock %d\n", ret);
>> +		goto disable_bulk;
>> +	}
>> +
>> +	/* 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);
> 
> Do we really need to run detection every time the sensor is powered on ?
> 

Surely not, I will add a if (sensor->already_detected) guard for this call to make it runs only once.

>> +	if (ret) {
>> +		dev_err(&client->dev, "sensor detect failed %d\n", ret);
>> +		goto disable_clock;
>> +	}
>> +
>> +	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;
>> +	}
>> +
>> +	return 0;
>> +
>> +disable_clock:
>> +	clk_disable_unprepare(sensor->xclk);
>> +disable_bulk:
>> +	regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
>> +
>> +	return ret;
>> +}
>> +
>> +static int vgxy61_power_off(struct device *dev)
>> +{
>> +	struct i2c_client *client = to_i2c_client(dev);
>> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>> +
>> +	clk_disable_unprepare(sensor->xclk);
>> +	regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
>> +	return 0;
>> +}
>> +
>> +static int __maybe_unused vgxy61_suspend(struct device *dev)
>> +{
>> +	struct i2c_client *client = to_i2c_client(dev);
>> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>> +
>> +	if (sensor->streaming)
>> +		vgxy61_stream_disable(sensor);
> 
> This shoudn't happen. The sensor should be stopped by the driver on the
> receiver side during system suspend. Same for resume.
> 

Sorry I'm still learning the pm_runtime API and I'm not sure I got your point.
If the sensor is already stopped then I could just remove the whole suspend function? Same for resume.
I'm not sure when the suspend function is called by the framework.

>> +
>> +	return 0;
>> +}
>> +
>> +static int __maybe_unused vgxy61_resume(struct device *dev)
>> +{
>> +	struct i2c_client *client = to_i2c_client(dev);
>> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>> +	int ret;
>> +
>> +	if (sensor->streaming) {
>> +		ret = vgxy61_stream_enable(sensor);
>> +		if (ret)
>> +			goto error;
>> +	}
>> +
>> +	return 0;
>> +
>> +error:
>> +	vgxy61_stream_disable(sensor);
>> +	sensor->streaming = false;
>> +
>> +	return ret;
>> +}
>> +
>> +static void vgxy61_set_default_format(struct vgxy61_dev *sensor)
>> +{
>> +	if (sensor->id == VG5761_MODEL_ID) {
> 
> Would it make sense to have different compatible strings instead of
> detecting the version automatically ? Not only could it speed up probe
> if we could skip the detection, it would also allow not powering the
> sensor up at probe time, which may be required in some cases (for
> instance when a privacy LED is connected to the sensor power supply,
> flashing the LED at probe time can be very confusing for users).
> 

Your flashing LED example is a very good concern.
The idea behind this is to ease the user life as he does not have to know what model he is using.
For example we have several boards with either the VG5761 or VG5661 and are interchangeable without changing the device tree, which is also better for us as it shrinks the device tree number to maintain.
Also as a company we treat them the same way, hence the same driver.
I'm not hardly commited to keep this, but it indeed makes my life easier.

>> +		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;
>> +	} else if (sensor->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;
>> +	} else {
>> +		/* Should never happen */
>> +		WARN_ON(true);
>> +	}
>> +	sensor->fmt.width = sensor->current_mode->width;
>> +	sensor->fmt.height = sensor->current_mode->height;
>> +	sensor->vblank = sensor->frame_length - sensor->current_mode->crop.height;
>> +}
>> +
>> +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_Y8_1X8;
>> +	sensor->fmt.colorspace = V4L2_COLORSPACE_RAW;
>> +	sensor->fmt.field = V4L2_FIELD_NONE;
>> +	sensor->fmt.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
>> +	sensor->fmt.quantization = V4L2_QUANTIZATION_DEFAULT;
>> +	sensor->fmt.xfer_func = V4L2_XFER_FUNC_DEFAULT;
> 
> Please implement the subdev .init_cfg() operation, and call it here.
> There are several examples in existing drivers.
> 
>> +	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;
>> +	sensor->frame_length = VGXY61_FRAME_LENGTH_DEF;
>> +
>> +	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, NULL);
>> +	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 < 6 * HZ_PER_MHZ || sensor->clk_freq > 27 * HZ_PER_MHZ) {
>> +		dev_err(dev, "Only 6Mhz-27Mhz clock range supported. provide %lu MHz\n",
>> +			sensor->clk_freq / HZ_PER_MHZ);
>> +		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;
>> +
>> +	/* Request optional reset pin */
>> +	sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
>> +
>> +	ret = vgxy61_power_on(dev);
>> +	if (ret)
>> +		return ret;
>> +
>> +	vgxy61_set_default_format(sensor);
>> +
>> +	/* HDR mode */
>> +	ret = vgxy61_update_hdr(sensor, sensor->hdr);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = vgxy61_init_controls(sensor);
>> +	if (ret) {
>> +		dev_err(&client->dev, "controls initialization failed %d\n", ret);
>> +		goto error_power_off;
>> +	}
>> +
>> +	ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
>> +	if (ret) {
>> +		dev_err(&client->dev, "pads init failed %d\n", ret);
>> +		goto error_handler_free;
>> +	}
>> +
>> +	ret = v4l2_async_register_subdev(&sensor->sd);
>> +	if (ret) {
>> +		dev_err(&client->dev, "async subdev register failed %d\n", ret);
>> +		goto error_media_entity;
>> +	}
>> +
>> +	/* Enable runtime PM and turn off the device */
>> +	pm_runtime_set_active(dev);
>> +	pm_runtime_enable(dev);
>> +	pm_runtime_idle(dev);
> 
> You should do this before registering the subdev, as it can be accessed
> from userspace right after being registered.
> 
> Please also use auto-suspend to avoid long delays when stopping and
> restarting streaming quickly.
> 

I was not aware of autosuspend. Thanks a lot, I will look into this.

>> +
>> +	dev_info(&client->dev, "vgxy61 probe successfully\n");
> 
> I'd drop this, or turn it into a debug message.
> 
>> +
>> +	return 0;
>> +
>> +error_media_entity:
>> +	media_entity_cleanup(&sensor->sd.entity);
>> +error_handler_free:
>> +	v4l2_ctrl_handler_free(sensor->sd.ctrl_handler);
>> +	mutex_destroy(&sensor->lock);
>> +error_power_off:
>> +	vgxy61_power_off(dev);
>> +
>> +	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);
>> +	mutex_destroy(&sensor->lock);
>> +	media_entity_cleanup(&sensor->sd.entity);
>> +
>> +	pm_runtime_disable(&client->dev);
>> +	if (!pm_runtime_status_suspended(&client->dev))
>> +		vgxy61_power_off(&client->dev);
>> +	pm_runtime_set_suspended(&client->dev);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct of_device_id vgxy61_dt_ids[] = {
>> +	{ .compatible = "st,st-vgxy61" },
>> +	{ /* sentinel */ }
>> +};
>> +MODULE_DEVICE_TABLE(of, vgxy61_dt_ids);
>> +
>> +static const struct dev_pm_ops vgxy61_pm_ops = {
>> +	SET_SYSTEM_SLEEP_PM_OPS(vgxy61_suspend, vgxy61_resume)
>> +	SET_RUNTIME_PM_OPS(vgxy61_power_off, vgxy61_power_on, NULL)
>> +};
>> +
>> +static struct i2c_driver vgxy61_i2c_driver = {
>> +	.driver = {
>> +		.name  = "st-vgxy61",
>> +		.of_match_table = vgxy61_dt_ids,
>> +		.pm = &vgxy61_pm_ops,
>> +	},
>> +	.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");
> 

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

* Re: [PATCH v2 2/5] media: v4l: ctrls: Add a control for temperature
  2022-04-20 13:01           ` Benjamin Mugnier
@ 2022-04-20 13:21             ` Laurent Pinchart
  2022-04-20 13:58               ` Guenter Roeck
  0 siblings, 1 reply; 33+ messages in thread
From: Laurent Pinchart @ 2022-04-20 13:21 UTC (permalink / raw)
  To: Benjamin Mugnier
  Cc: Guenter Roeck, Nicolas Dufresne, linux-media, alain.volmat,
	hugues.fruchet, sylvain.petinot, dave.stevenson, sakari.ailus,
	kieran.bingham, linux-hwmon, Ricardo Ribalda

Hi Benjamin,

On Wed, Apr 20, 2022 at 03:01:18PM +0200, Benjamin Mugnier wrote:
> On 20/04/2022 00:04, Guenter Roeck wrote:
> > On 4/19/22 14:01, Laurent Pinchart wrote:
> >> On Tue, Apr 19, 2022 at 12:28:06PM -0700, Guenter Roeck wrote:
> >>> On 4/19/22 11:24, Nicolas Dufresne wrote:
> >>>> Hi,
> >>>>
> >>>> adding linux-hwmon in CC for a wider feedback.
> >>>>
> >>>> Le vendredi 15 avril 2022 à 13:18 +0200, Benjamin Mugnier a écrit :
> >>>>> Add V4L2_CID_TEMPERATURE control to get temperature from sensor in
> >>>>> celsius as a volatile and read-only control, and its documentation.
> >>>>> Useful to monitor thermals from v4l controls for sensors that support
> >>>>> this.
> >>>>
> >>>> Any justification to expose a temperature sensor outside of the dedicated kernel
> >>>> API hwmon ? I know if may makes it harder to use, as the sensor isn't bound to
> >>>> the camera driver, and also the sensor may not work if the camera is not
> >>>> streaming. But in the end, the API in hwmon does not look that complex, and is
> >>>> perhaps more precise ?
> 
> This sensor is able to read the temperature even if not streaming.
> 
> >>>>
> >>>> All in all, I think we need a strong justification to implement a custom
> >>>> thermometer interface, something described here and documented with care to
> >>>> prevent abuse. I would also see opinion from folks outside of the linux-media,
> >>>> hence why I have CCed hwmon mailing list.
> >>>
> >>> All I can say is that this seems to be odd and a bit outside the scope of
> >>> v4l2. I would have expected the vgxy61 driver to register a hwmon device
> >>> instead.
> >>
> >> I don't have a definitive opinion yet, but as Nicolas raised the issue
> >> by pushing towards hwmon, I'll offer counter-arguments for the sake of
> >> it :-)
> >>
> >> The temperature sensor in the imaging sensor is used to measure the die
> >> temperature, in order to tune the noise and spectral response model of
> >> the imaging sensor. It's thus not a generic-purpose temperature sensor.
> >> If the feature were to be exposed through hwmon, userspace would need to
> >> associate an hwmon device to the imaging sensor V4L2 subdev (we have a
> >> way to do so through the MC API, it doesn't support hwmon at this point,
> >> but I suppose it could be added). There are also various constraints
> >> that tie the temperature reading to the imaging side, it could be that
> >> the temperature would only be readable while capturing frames. That's
> >> probably possible to handle too but returning an error from the
> >> temperature read.
> >>
> >> Code-wise, both the driver and userspace would be more complex, for very
> >> little practical gain (I don't dispute a theorical gain).
> >>
> > 
> > All I can say is - not my subsystem, not my call to make. If you say this
> > is special and is better handled in V4L2, I'll take you by your word.
> > 
> > Guenter
> > 
> 
> I'll happily implement whatever conclusion we make here.
> 
> I could also drop this control for the first iteration of the driver,
> and come back later once a consensus is reached.

That would work too. By the way, what are your use cases for the
temperature sensor ? Have you added the control for the sake of
completeness, or do you have use cases ?

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v2 2/5] media: v4l: ctrls: Add a control for temperature
  2022-04-20 13:21             ` Laurent Pinchart
@ 2022-04-20 13:58               ` Guenter Roeck
  2022-04-20 14:23                 ` Laurent Pinchart
  0 siblings, 1 reply; 33+ messages in thread
From: Guenter Roeck @ 2022-04-20 13:58 UTC (permalink / raw)
  To: Laurent Pinchart, Benjamin Mugnier
  Cc: Nicolas Dufresne, linux-media, alain.volmat, hugues.fruchet,
	sylvain.petinot, dave.stevenson, sakari.ailus, kieran.bingham,
	linux-hwmon, Ricardo Ribalda

On 4/20/22 06:21, Laurent Pinchart wrote:
> Hi Benjamin,
> 
> On Wed, Apr 20, 2022 at 03:01:18PM +0200, Benjamin Mugnier wrote:
>> On 20/04/2022 00:04, Guenter Roeck wrote:
>>> On 4/19/22 14:01, Laurent Pinchart wrote:
>>>> On Tue, Apr 19, 2022 at 12:28:06PM -0700, Guenter Roeck wrote:
>>>>> On 4/19/22 11:24, Nicolas Dufresne wrote:
>>>>>> Hi,
>>>>>>
>>>>>> adding linux-hwmon in CC for a wider feedback.
>>>>>>
>>>>>> Le vendredi 15 avril 2022 à 13:18 +0200, Benjamin Mugnier a écrit :
>>>>>>> Add V4L2_CID_TEMPERATURE control to get temperature from sensor in
>>>>>>> celsius as a volatile and read-only control, and its documentation.
>>>>>>> Useful to monitor thermals from v4l controls for sensors that support
>>>>>>> this.
>>>>>>
>>>>>> Any justification to expose a temperature sensor outside of the dedicated kernel
>>>>>> API hwmon ? I know if may makes it harder to use, as the sensor isn't bound to
>>>>>> the camera driver, and also the sensor may not work if the camera is not
>>>>>> streaming. But in the end, the API in hwmon does not look that complex, and is
>>>>>> perhaps more precise ?
>>
>> This sensor is able to read the temperature even if not streaming.
>>
>>>>>>
>>>>>> All in all, I think we need a strong justification to implement a custom
>>>>>> thermometer interface, something described here and documented with care to
>>>>>> prevent abuse. I would also see opinion from folks outside of the linux-media,
>>>>>> hence why I have CCed hwmon mailing list.
>>>>>
>>>>> All I can say is that this seems to be odd and a bit outside the scope of
>>>>> v4l2. I would have expected the vgxy61 driver to register a hwmon device
>>>>> instead.
>>>>
>>>> I don't have a definitive opinion yet, but as Nicolas raised the issue
>>>> by pushing towards hwmon, I'll offer counter-arguments for the sake of
>>>> it :-)
>>>>
>>>> The temperature sensor in the imaging sensor is used to measure the die
>>>> temperature, in order to tune the noise and spectral response model of
>>>> the imaging sensor. It's thus not a generic-purpose temperature sensor.
>>>> If the feature were to be exposed through hwmon, userspace would need to
>>>> associate an hwmon device to the imaging sensor V4L2 subdev (we have a
>>>> way to do so through the MC API, it doesn't support hwmon at this point,
>>>> but I suppose it could be added). There are also various constraints
>>>> that tie the temperature reading to the imaging side, it could be that
>>>> the temperature would only be readable while capturing frames. That's
>>>> probably possible to handle too but returning an error from the
>>>> temperature read.
>>>>
>>>> Code-wise, both the driver and userspace would be more complex, for very
>>>> little practical gain (I don't dispute a theorical gain).
>>>>
>>>
>>> All I can say is - not my subsystem, not my call to make. If you say this
>>> is special and is better handled in V4L2, I'll take you by your word.
>>>
>>> Guenter
>>>
>>
>> I'll happily implement whatever conclusion we make here.
>>
>> I could also drop this control for the first iteration of the driver,
>> and come back later once a consensus is reached.
> 
> That would work too. By the way, what are your use cases for the
> temperature sensor ? Have you added the control for the sake of
> completeness, or do you have use cases ?
> 

You provided a use case above. Are you saying you made it up ?
Still fine with me, your call, just wondering.

Guenter

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

* Re: [PATCH v2 2/5] media: v4l: ctrls: Add a control for temperature
  2022-04-20 13:58               ` Guenter Roeck
@ 2022-04-20 14:23                 ` Laurent Pinchart
  2022-04-20 15:19                   ` Benjamin Mugnier
  0 siblings, 1 reply; 33+ messages in thread
From: Laurent Pinchart @ 2022-04-20 14:23 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Benjamin Mugnier, Nicolas Dufresne, linux-media, alain.volmat,
	hugues.fruchet, sylvain.petinot, dave.stevenson, sakari.ailus,
	kieran.bingham, linux-hwmon, Ricardo Ribalda

On Wed, Apr 20, 2022 at 06:58:32AM -0700, Guenter Roeck wrote:
> On 4/20/22 06:21, Laurent Pinchart wrote:
> > On Wed, Apr 20, 2022 at 03:01:18PM +0200, Benjamin Mugnier wrote:
> >> On 20/04/2022 00:04, Guenter Roeck wrote:
> >>> On 4/19/22 14:01, Laurent Pinchart wrote:
> >>>> On Tue, Apr 19, 2022 at 12:28:06PM -0700, Guenter Roeck wrote:
> >>>>> On 4/19/22 11:24, Nicolas Dufresne wrote:
> >>>>>> Hi,
> >>>>>>
> >>>>>> adding linux-hwmon in CC for a wider feedback.
> >>>>>>
> >>>>>> Le vendredi 15 avril 2022 à 13:18 +0200, Benjamin Mugnier a écrit :
> >>>>>>> Add V4L2_CID_TEMPERATURE control to get temperature from sensor in
> >>>>>>> celsius as a volatile and read-only control, and its documentation.
> >>>>>>> Useful to monitor thermals from v4l controls for sensors that support
> >>>>>>> this.
> >>>>>>
> >>>>>> Any justification to expose a temperature sensor outside of the dedicated kernel
> >>>>>> API hwmon ? I know if may makes it harder to use, as the sensor isn't bound to
> >>>>>> the camera driver, and also the sensor may not work if the camera is not
> >>>>>> streaming. But in the end, the API in hwmon does not look that complex, and is
> >>>>>> perhaps more precise ?
> >>
> >> This sensor is able to read the temperature even if not streaming.
> >>
> >>>>>>
> >>>>>> All in all, I think we need a strong justification to implement a custom
> >>>>>> thermometer interface, something described here and documented with care to
> >>>>>> prevent abuse. I would also see opinion from folks outside of the linux-media,
> >>>>>> hence why I have CCed hwmon mailing list.
> >>>>>
> >>>>> All I can say is that this seems to be odd and a bit outside the scope of
> >>>>> v4l2. I would have expected the vgxy61 driver to register a hwmon device
> >>>>> instead.
> >>>>
> >>>> I don't have a definitive opinion yet, but as Nicolas raised the issue
> >>>> by pushing towards hwmon, I'll offer counter-arguments for the sake of
> >>>> it :-)
> >>>>
> >>>> The temperature sensor in the imaging sensor is used to measure the die
> >>>> temperature, in order to tune the noise and spectral response model of
> >>>> the imaging sensor. It's thus not a generic-purpose temperature sensor.
> >>>> If the feature were to be exposed through hwmon, userspace would need to
> >>>> associate an hwmon device to the imaging sensor V4L2 subdev (we have a
> >>>> way to do so through the MC API, it doesn't support hwmon at this point,
> >>>> but I suppose it could be added). There are also various constraints
> >>>> that tie the temperature reading to the imaging side, it could be that
> >>>> the temperature would only be readable while capturing frames. That's
> >>>> probably possible to handle too but returning an error from the
> >>>> temperature read.
> >>>>
> >>>> Code-wise, both the driver and userspace would be more complex, for very
> >>>> little practical gain (I don't dispute a theorical gain).
> >>>>
> >>>
> >>> All I can say is - not my subsystem, not my call to make. If you say this
> >>> is special and is better handled in V4L2, I'll take you by your word.
> >>>
> >>> Guenter
> >>>
> >>
> >> I'll happily implement whatever conclusion we make here.
> >>
> >> I could also drop this control for the first iteration of the driver,
> >> and come back later once a consensus is reached.
> > 
> > That would work too. By the way, what are your use cases for the
> > temperature sensor ? Have you added the control for the sake of
> > completeness, or do you have use cases ?
> 
> You provided a use case above. Are you saying you made it up ?
> Still fine with me, your call, just wondering.

It's the two most common use cases for imaging sensor temperature
measurements that I know of. My question to Benjamin is if he has the
same and/or other use cases.

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v2 2/5] media: v4l: ctrls: Add a control for temperature
  2022-04-20 14:23                 ` Laurent Pinchart
@ 2022-04-20 15:19                   ` Benjamin Mugnier
  2022-04-20 16:19                     ` Guenter Roeck
  0 siblings, 1 reply; 33+ messages in thread
From: Benjamin Mugnier @ 2022-04-20 15:19 UTC (permalink / raw)
  To: Laurent Pinchart, Guenter Roeck
  Cc: Nicolas Dufresne, linux-media, alain.volmat, hugues.fruchet,
	sylvain.petinot, dave.stevenson, sakari.ailus, kieran.bingham,
	linux-hwmon, Ricardo Ribalda



On 20/04/2022 16:23, Laurent Pinchart wrote:
> On Wed, Apr 20, 2022 at 06:58:32AM -0700, Guenter Roeck wrote:
>> On 4/20/22 06:21, Laurent Pinchart wrote:
>>> On Wed, Apr 20, 2022 at 03:01:18PM +0200, Benjamin Mugnier wrote:
>>>> On 20/04/2022 00:04, Guenter Roeck wrote:
>>>>> On 4/19/22 14:01, Laurent Pinchart wrote:
>>>>>> On Tue, Apr 19, 2022 at 12:28:06PM -0700, Guenter Roeck wrote:
>>>>>>> On 4/19/22 11:24, Nicolas Dufresne wrote:
>>>>>>>> Hi,
>>>>>>>>
>>>>>>>> adding linux-hwmon in CC for a wider feedback.
>>>>>>>>
>>>>>>>> Le vendredi 15 avril 2022 à 13:18 +0200, Benjamin Mugnier a écrit :
>>>>>>>>> Add V4L2_CID_TEMPERATURE control to get temperature from sensor in
>>>>>>>>> celsius as a volatile and read-only control, and its documentation.
>>>>>>>>> Useful to monitor thermals from v4l controls for sensors that support
>>>>>>>>> this.
>>>>>>>>
>>>>>>>> Any justification to expose a temperature sensor outside of the dedicated kernel
>>>>>>>> API hwmon ? I know if may makes it harder to use, as the sensor isn't bound to
>>>>>>>> the camera driver, and also the sensor may not work if the camera is not
>>>>>>>> streaming. But in the end, the API in hwmon does not look that complex, and is
>>>>>>>> perhaps more precise ?
>>>>
>>>> This sensor is able to read the temperature even if not streaming.
>>>>
>>>>>>>>
>>>>>>>> All in all, I think we need a strong justification to implement a custom
>>>>>>>> thermometer interface, something described here and documented with care to
>>>>>>>> prevent abuse. I would also see opinion from folks outside of the linux-media,
>>>>>>>> hence why I have CCed hwmon mailing list.
>>>>>>>
>>>>>>> All I can say is that this seems to be odd and a bit outside the scope of
>>>>>>> v4l2. I would have expected the vgxy61 driver to register a hwmon device
>>>>>>> instead.
>>>>>>
>>>>>> I don't have a definitive opinion yet, but as Nicolas raised the issue
>>>>>> by pushing towards hwmon, I'll offer counter-arguments for the sake of
>>>>>> it :-)
>>>>>>
>>>>>> The temperature sensor in the imaging sensor is used to measure the die
>>>>>> temperature, in order to tune the noise and spectral response model of
>>>>>> the imaging sensor. It's thus not a generic-purpose temperature sensor.
>>>>>> If the feature were to be exposed through hwmon, userspace would need to
>>>>>> associate an hwmon device to the imaging sensor V4L2 subdev (we have a
>>>>>> way to do so through the MC API, it doesn't support hwmon at this point,
>>>>>> but I suppose it could be added). There are also various constraints
>>>>>> that tie the temperature reading to the imaging side, it could be that
>>>>>> the temperature would only be readable while capturing frames. That's
>>>>>> probably possible to handle too but returning an error from the
>>>>>> temperature read.
>>>>>>
>>>>>> Code-wise, both the driver and userspace would be more complex, for very
>>>>>> little practical gain (I don't dispute a theorical gain).
>>>>>>
>>>>>
>>>>> All I can say is - not my subsystem, not my call to make. If you say this
>>>>> is special and is better handled in V4L2, I'll take you by your word.
>>>>>
>>>>> Guenter
>>>>>
>>>>
>>>> I'll happily implement whatever conclusion we make here.
>>>>
>>>> I could also drop this control for the first iteration of the driver,
>>>> and come back later once a consensus is reached.
>>>
>>> That would work too. By the way, what are your use cases for the
>>> temperature sensor ? Have you added the control for the sake of
>>> completeness, or do you have use cases ?
>>
>> You provided a use case above. Are you saying you made it up ?
>> Still fine with me, your call, just wondering.
> 
> It's the two most common use cases for imaging sensor temperature
> measurements that I know of. My question to Benjamin is if he has the
> same and/or other use cases.
> 

Just like you said in a previous mail. This temperature sensor can be used to implement a retroactive loop from the host according to its value, such as noise correction for instance.
We don't have anything in the Linux user space that implements this yet, this was in anticipation.
So dropping it is fine, I will come back to it if need be ;)

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

* Re: [PATCH v2 2/5] media: v4l: ctrls: Add a control for temperature
  2022-04-20 15:19                   ` Benjamin Mugnier
@ 2022-04-20 16:19                     ` Guenter Roeck
  2022-04-20 16:51                       ` Laurent Pinchart
  0 siblings, 1 reply; 33+ messages in thread
From: Guenter Roeck @ 2022-04-20 16:19 UTC (permalink / raw)
  To: Benjamin Mugnier, Laurent Pinchart
  Cc: Nicolas Dufresne, linux-media, alain.volmat, hugues.fruchet,
	sylvain.petinot, dave.stevenson, sakari.ailus, kieran.bingham,
	linux-hwmon, Ricardo Ribalda

On 4/20/22 08:19, Benjamin Mugnier wrote:
[ ... ]
>>
>> It's the two most common use cases for imaging sensor temperature
>> measurements that I know of. My question to Benjamin is if he has the
>> same and/or other use cases.
>>
> 
> Just like you said in a previous mail. This temperature sensor can be used to implement a retroactive loop from the host according to its value, such as noise correction for instance.
> We don't have anything in the Linux user space that implements this yet, this was in anticipation.
> So dropping it is fine, I will come back to it if need be ;)

When you implement this in userspace, you might want to consider situations
where the temperature is _not_ reported via media controls (which might
at least in theory happen if the temperature sensor is not part of the
v4l device), or for existing drivers with hwmon support
(drivers/media/i2c/video-i2c.c comes into mind).

Guenter

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

* Re: [PATCH v2 5/5] media: i2c: Add driver for ST VGXY61 camera sensor
  2022-04-20 13:01     ` Benjamin Mugnier
@ 2022-04-20 16:48       ` Laurent Pinchart
  2022-04-22  8:44         ` Benjamin Mugnier
  0 siblings, 1 reply; 33+ messages in thread
From: Laurent Pinchart @ 2022-04-20 16:48 UTC (permalink / raw)
  To: Benjamin Mugnier
  Cc: linux-media, alain.volmat, hugues.fruchet, sylvain.petinot,
	dave.stevenson, sakari.ailus, kieran.bingham

Hi Benjamin,

(Sakari, there are two questions for you below)

On Wed, Apr 20, 2022 at 03:01:24PM +0200, Benjamin Mugnier wrote:
> Hi Laurent,
> 
> Thank you for your review.
> Please consider everything I did not comment as agreed.
> 
> On 15/04/2022 17:39, Laurent Pinchart wrote:
> > Hi Benjamin,
> > 
> > Thank you for the patch.
> > 
> > This is a partial review as I'm short on time now.
> > 
> 
> No worries ;)
> 
> > On Fri, Apr 15, 2022 at 01:18:45PM +0200, Benjamin Mugnier wrote:
> >> 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     |   10 +
> >>  drivers/media/i2c/Makefile    |    1 +
> >>  drivers/media/i2c/st-vgxy61.c | 1997 +++++++++++++++++++++++++++++++++
> >>  3 files changed, 2008 insertions(+)
> >>  create mode 100644 drivers/media/i2c/st-vgxy61.c
> >>
> >> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> >> index fae2baabb773..734f3e1130b0 100644
> >> --- a/drivers/media/i2c/Kconfig
> >> +++ b/drivers/media/i2c/Kconfig
> >> @@ -763,6 +763,16 @@ config VIDEO_VS6624
> >>  	  To compile this driver as a module, choose M here: the
> >>  	  module will be called vs6624.
> >>  
> >> +config VIDEO_ST_VGXY61
> > 
> > Alphabetical order please.
> > 
> >> +	tristate "ST VGXY61 sensor support"
> >> +	depends on OF && GPIOLIB && VIDEO_DEV && 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.
> >> +
> >>  source "drivers/media/i2c/ccs/Kconfig"
> >>  source "drivers/media/i2c/et8ek8/Kconfig"
> >>  source "drivers/media/i2c/m5mols/Kconfig"
> >> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> >> index 3e1696963e7f..7f2362228484 100644
> >> --- a/drivers/media/i2c/Makefile
> >> +++ b/drivers/media/i2c/Makefile
> >> @@ -116,6 +116,7 @@ obj-$(CONFIG_VIDEO_SAA7185) += saa7185.o
> >>  obj-$(CONFIG_VIDEO_SONY_BTF_MPX) += sony-btf-mpx.o
> >>  obj-$(CONFIG_VIDEO_SR030PC30) += sr030pc30.o
> >>  obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o
> >> +obj-$(CONFIG_VIDEO_ST_VGXY61) += st-vgxy61.o
> >>  obj-$(CONFIG_VIDEO_TC358743) += tc358743.o
> >>  obj-$(CONFIG_VIDEO_TDA1997X) += tda1997x.o
> >>  obj-$(CONFIG_VIDEO_TDA7432) += tda7432.o
> >> diff --git a/drivers/media/i2c/st-vgxy61.c b/drivers/media/i2c/st-vgxy61.c
> >> new file mode 100644
> >> index 000000000000..1219d278e648
> >> --- /dev/null
> >> +++ b/drivers/media/i2c/st-vgxy61.c
> >> @@ -0,0 +1,1997 @@
> >> +// 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/pm_runtime.h>
> > 
> > Alphabetical order in headers too please.
> > 
> >> +#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
> > 
> > I don't think this deserves a macro, but if you want to keep it, you
> > should avoid generic names that may cause namespace clashes.
> > 
> 
> I don't make heavy usage of it so I will remove it.
> 
> >> +
> >> +#define DEVICE_MODEL_ID_REG				0x0000
> > 
> > Please prefix macros with a driver-specific prefix. Many of them have a
> > too generic name.
> > 
> 
> I will change this to VGXY61_REG_* for registers, and simply VGXY61_* for others.
> 
> >> +#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 VGXY61_WRITE_MULTIPLE_CHUNK_MAX			16
> >> +#define VGXY61_NB_GPIOS					4
> >> +#define VGXY61_NB_POLARITIES				5
> >> +#define VGXY61_MIN_FRAME_LENGTH				1288
> >> +#define VGXY61_MIN_EXPOSURE				10
> >> +#define VGXY61_HDR_LINEAR_RATIO				10
> >> +#define VGXY61_TIMEOUT_MS				500
> >> +#define VGXY61_FRAME_LENGTH_DEF				1313
> >> +#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_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_STVGXY61_BASE + 0)
> > 
> > This macro should have a device-specific name, should be moved to a
> > public header, and the control should be documented. I would split the
> > control documentation and macro definition to a separate patch.
> > 
> 
> I created two new files include/uapi/linux/st-vgxy61.h and
> Documentation/userspace-api/media/drivers/st-vgxy61.rst.
>
> I'm not sure what you mean by separating the documentation and
> definition. As there is a significant amount of code dependent on the
> HDR mode I will keep the public header in this patch, and create a new
> patch for the documentation. Tell me if there is any issue with this.

I meant a patch that adds include/uapi/linux/st-vgxy61.h and
Documentation/userspace-api/media/drivers/st-vgxy61.rst separate from
the driver, but if it causes more issues than it helps review, you can
combine them in a single patch.

> >> +
> >> +#define DEVICE_FWPATCH_REVISION_MAJOR			2
> >> +#define DEVICE_FWPATCH_REVISION_MINOR			0
> >> +#define DEVICE_FWPATCH_REVISION_MICRO			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
> > 
> > The link frequencies should come from DT.
> 
> This is a hard requirement for this sensor. This value can not be
> modified and must be 402MHz.
> 
> I see the ov9734.c does something similar. Is adding this value to the
> device tree really mandatory?

I suppose it's fine then. Sakari, what do you think ?

> >> +};
> >> +
> >> +#define VGXY61_NUM_SUPPLIES		ARRAY_SIZE(vgxy61_supply_name)
> >> +
> >> +enum bin_mode {
> > 
> > Same comment here, all enums and structures should use a driver-specific
> > prefix.
> > 
> >> +	BIN_MODE_NORMAL,
> >> +	BIN_MODE_DIGITAL_X2,
> >> +	BIN_MODE_DIGITAL_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;
> >> +	struct v4l2_rect crop;
> >> +};
> >> +
> >> +static const u32 vgxy61_supported_codes[] = {
> >> +	MEDIA_BUS_FMT_Y8_1X8,
> >> +	MEDIA_BUS_FMT_Y10_1X10,
> >> +	MEDIA_BUS_FMT_Y12_1X12,
> >> +	MEDIA_BUS_FMT_Y14_1X14,
> >> +	MEDIA_BUS_FMT_Y16_1X16
> >> +};
> >> +
> >> +static const struct vgxy61_mode_info vgx661_mode_data[] = {
> >> +	{
> >> +		.width = VGX661_WIDTH,
> >> +		.height = VGX661_HEIGHT,
> >> +		.bin_mode = BIN_MODE_NORMAL,
> >> +		.crop = {
> >> +			.left = 0,
> >> +			.top = 0,
> >> +			.width = VGX661_WIDTH,
> >> +			.height = VGX661_HEIGHT,
> >> +		},
> > 
> > It's very nice to see no hardcoded register values in the modes tables
> > :-) As the register values are computed by the driver, would it make
> > sense to go one step further and drop the modes completely ? Analog crop
> > and binning could then be selected freely from userspace.
> 
> All credit goes to Dave for pointing this in the first review ;)
> As for the modes, in theory we could drop them completely, but this
> will require some extra testing as the sensor can't really handle
> every single mode freely.
>
> Could this be done in a second step?

I'm a bit concerned it could then break userspace, if some applications
start depending on a fixed list of modes. How difficult do you think
this would be ?

> >> +	},
> >> +	{
> >> +		.width = 1280,
> >> +		.height = 720,
> >> +		.bin_mode = BIN_MODE_NORMAL,
> >> +		.crop = {
> >> +			.left = 92,
> >> +			.top = 192,
> >> +			.width = 1280,
> >> +			.height = 720,
> >> +		},
> >> +	},
> >> +	{
> >> +		.width = 640,
> >> +		.height = 480,
> >> +		.bin_mode = BIN_MODE_DIGITAL_X2,
> >> +		.crop = {
> >> +			.left = 92,
> >> +			.top = 72,
> >> +			.width = 1280,
> >> +			.height = 960,
> >> +		},
> >> +	},
> >> +	{
> >> +		.width = 320,
> >> +		.height = 240,
> >> +		.bin_mode = BIN_MODE_DIGITAL_X4,
> > 
> > Can the bin mode computed by dividing the crop width by the output width
> > instead of being stored here ?
> 
> The sensor also supports analog subsampling on top of digital binning.
> I did not implement it in the driver but I made the choice to
> explicitly set the bin_mode for further development. For example the
> line above could be:
>
>   .bin_mode = BIN_MODE_ANALOG_X4
>
> I can compute the factor (X2/X4), but not the analog/digital mode.
> Hope this make sense.

It thus indeed, but it then leads to the question of how to configure
skipping and binning separately. The CCS driver registers two subdevs to
make this possible. It increases the complexity, and I'm increasingly
thinking that we should extend the V4L2 API to allow configure all the
common parameters of camera sensors with a single subdev.

Sakari, any opinion ?

> >> +		.crop = {
> >> +			.left = 92,
> >> +			.top = 72,
> >> +			.width = 1280,
> >> +			.height = 960,
> >> +		},
> >> +	},
> >> +};
> >> +
> >> +static const struct vgxy61_mode_info vgx761_mode_data[] = {
> >> +	{
> >> +		.width = VGX761_WIDTH,
> >> +		.height = VGX761_HEIGHT,
> >> +		.bin_mode = BIN_MODE_NORMAL,
> >> +		.crop = {
> >> +			.left = 0,
> >> +			.top = 0,
> >> +			.width = VGX761_WIDTH,
> >> +			.height = VGX761_HEIGHT,
> >> +		},
> >> +	},
> >> +	{
> >> +		.width = 1920,
> >> +		.height = 1080,
> >> +		.bin_mode = BIN_MODE_NORMAL,
> >> +		.crop = {
> >> +			.left = 12,
> >> +			.top = 62,
> >> +			.width = 1920,
> >> +			.height = 1080,
> >> +		},
> >> +	},
> >> +	{
> >> +		.width = 1280,
> >> +		.height = 720,
> >> +		.bin_mode = BIN_MODE_NORMAL,
> >> +		.crop = {
> >> +			.left = 332,
> >> +			.top = 242,
> >> +			.width = 1280,
> >> +			.height = 720,
> >> +		},
> >> +	},
> >> +	{
> >> +		.width = 640,
> >> +		.height = 480,
> >> +		.bin_mode = BIN_MODE_DIGITAL_X2,
> >> +		.crop = {
> >> +			.left = 332,
> >> +			.top = 122,
> >> +			.width = 1280,
> >> +			.height = 960,
> >> +		},
> >> +	},
> >> +	{
> >> +		.width = 320,
> >> +		.height = 240,
> >> +		.bin_mode = BIN_MODE_DIGITAL_X4,
> >> +		.crop = {
> >> +			.left = 332,
> >> +			.top = 122,
> >> +			.width = 1280,
> >> +			.height = 960,
> >> +		},
> >> +	},
> >> +};
> >> +
> >> +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 id;
> >> +	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 v4l2_ctrl_handler ctrl_handler;
> >> +	struct v4l2_ctrl *pixel_rate_ctrl;
> >> +	struct v4l2_ctrl *expo_ctrl;
> >> +	struct v4l2_ctrl *vblank_ctrl;
> >> +	struct v4l2_ctrl *vflip_ctrl;
> >> +	struct v4l2_ctrl *hflip_ctrl;
> >> +	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;
> >> +	struct v4l2_fract frame_interval;
> > 
> > This is not used. Please check all other fields too.
> > 
> >> +	bool hflip;
> >> +	bool vflip;
> >> +	enum hdr hdr;
> >> +	int expo_long;
> >> +	int expo_short;
> >> +	int expo_max;
> >> +	int expo_min;
> >> +	int vblank;
> >> +	int vblank_min;
> >> +	u16 frame_length;
> >> +};
> >> +
> >> +static u8 get_bpp_by_code(__u32 code)
> >> +{
> >> +	switch (code) {
> >> +	case MEDIA_BUS_FMT_Y8_1X8:
> >> +		return 8;
> >> +	case MEDIA_BUS_FMT_Y10_1X10:
> >> +		return 10;
> >> +	case MEDIA_BUS_FMT_Y12_1X12:
> >> +		return 12;
> >> +	case MEDIA_BUS_FMT_Y14_1X14:
> >> +		return 14;
> >> +	case MEDIA_BUS_FMT_Y16_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_Y8_1X8:
> >> +		return MIPI_CSI2_DT_RAW8;
> >> +	case MEDIA_BUS_FMT_Y10_1X10:
> >> +		return MIPI_CSI2_DT_RAW10;
> >> +	case MEDIA_BUS_FMT_Y12_1X12:
> >> +		return MIPI_CSI2_DT_RAW12;
> >> +	case MEDIA_BUS_FMT_Y14_1X14:
> >> +		return MIPI_CSI2_DT_RAW14;
> >> +	case MEDIA_BUS_FMT_Y16_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;
> >> +	}
> >> +}
> > 
> > I'd put all this data in a format description structure, and store that
> > in the vgxy61_supported_codes array.
> > 
> >> +
> >> +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, ctrl_handler)->sd;
> >> +}
> >> +
> >> +static int get_chunk_size(struct vgxy61_dev *sensor)
> >> +{
> >> +	struct i2c_adapter *adapter = sensor->i2c_client->adapter;
> >> +	int max_write_len = VGXY61_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, VGXY61_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[VGXY61_WRITE_MULTIPLE_CHUNK_MAX + 2];
> >> +	int i;
> >> +	int ret;
> >> +
> >> +	if (len > VGXY61_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));
> >> +}
> > 
> > You could possibly simplify handling of registers of different sizes by
> > encoding the size in the register address macros. See
> > https://gitlab.com/ideasonboard/nxp/linux/-/blob/pinchartl/v5.18/sensors/imx/drivers/media/i2c/imx296.c
> > for an example.
> 
> That sounds good, thank you. I will look into this.

On the topic of register writes, you may also want to consider the *err
argument to imx296_write(), it simplifies error handling (see how errors
are handled in imx296_setup(), compared to checking the return value of
every single write call).

> >> +
> >> +static int vgxy61_poll_reg(struct vgxy61_dev *sensor, u16 reg, u8 poll_val, int timeout_ms)
> >> +{
> >> +	const int loop_delay_ms = 10;
> >> +	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, int timeout_ms)
> >> +{
> >> +	return vgxy61_poll_reg(sensor, DEVICE_SYSTEM_FSM, state, timeout_ms);
> >> +}
> >> +
> >> +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 = sensor->current_mode->crop.height / sensor->current_mode->height;
> >> +	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;
> > 
> > Is there a risk those writes may be applied to different frames ?
> 
> Yes, but it does not matter for the sensor. Are you thinking of an
> issue about user space applications for instance?

Yes, if the writes cross frame boundaries, we could have a frame with a
weird exposure time, which could confuse ISP algorithms.

Does the sensor support CSI-2 embedded data ?

> >> +
> >> +	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)
> > 
> > The function sets the frame length, not the frame rate.
> > 
> >> +{
> >> +	return vgxy61_write_reg16(sensor, DEVICE_FRAME_LENGTH, sensor->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, VGXY61_TIMEOUT_MS);
> >> +}
> >> +
> >> +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 */
> >> +	mode = v4l2_find_nearest_size(sensor->sensor_modes, sensor->sensor_modes_nb,
> >> +				      width, height, fmt->width, fmt->height);
> >> +	if (new_mode)
> >> +		*new_mode = mode;
> >> +
> >> +	fmt->code = vgxy61_supported_codes[index];
> >> +	fmt->width = mode->width;
> >> +	fmt->height = mode->height;
> >> +	fmt->colorspace = V4L2_COLORSPACE_RAW;
> >> +	fmt->field = V4L2_FIELD_NONE;
> >> +	fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> >> +	fmt->quantization = V4L2_QUANTIZATION_DEFAULT;
> >> +	fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT;
> >> +
> >> +	return 0;
> >> +}
> >> +
> >> +static int vgxy61_get_selection(struct v4l2_subdev *sd,
> >> +				struct v4l2_subdev_state *sd_state,
> >> +				struct v4l2_subdev_selection *sel)
> >> +{
> >> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >> +
> >> +	switch (sel->target) {
> >> +	case V4L2_SEL_TGT_CROP:
> >> +		sel->r = sensor->current_mode->crop;
> >> +		return 0;
> >> +	case V4L2_SEL_TGT_NATIVE_SIZE:
> >> +	case V4L2_SEL_TGT_CROP_DEFAULT:
> >> +	case V4L2_SEL_TGT_CROP_BOUNDS:
> >> +		sel->r.top = 0;
> >> +		sel->r.left = 0;
> >> +		sel->r.width = sensor->sensor_width;
> >> +		sel->r.height = sensor->sensor_height;
> >> +		return 0;
> >> +	}
> >> +
> >> +	return -EINVAL;
> >> +}
> >> +
> >> +static int vgxy61_stream_enable(struct vgxy61_dev *sensor)
> >> +{
> >> +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd);
> >> +	const struct v4l2_rect *crop = &sensor->current_mode->crop;
> >> +	int ret;
> >> +
> >> +	ret = vgxy61_check_bw(sensor);
> >> +	if (ret)
> >> +		return ret;
> >> +
> >> +	ret = pm_runtime_get_sync(&client->dev);
> >> +	if (ret < 0) {
> >> +		pm_runtime_put_noidle(&client->dev);
> >> +		return ret;
> >> +	}
> >> +
> >> +	/* Configure sensor */
> >> +	ret = vgxy61_write_reg(sensor, DEVICE_FORMAT_CTRL, get_bpp_by_code(sensor->fmt.code));
> >> +	if (ret)
> >> +		goto err_rpm_put;
> >> +	ret = vgxy61_write_reg(sensor, DEVICE_OIF_ROI0_CTRL,
> >> +			       get_data_type_by_code(sensor->fmt.code));
> >> +	if (ret)
> >> +		goto err_rpm_put;
> >> +
> >> +	ret = vgxy61_write_reg(sensor, DEVICE_READOUT_CTRL, sensor->current_mode->bin_mode);
> >> +	if (ret)
> >> +		goto err_rpm_put;
> >> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_H, crop->left);
> >> +	if (ret)
> >> +		goto err_rpm_put;
> >> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_H, crop->left + crop->width - 1);
> >> +	if (ret)
> >> +		goto err_rpm_put;
> >> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_V, crop->top);
> >> +	if (ret)
> >> +		goto err_rpm_put;
> >> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_V, crop->top + crop->height - 1);
> >> +	if (ret)
> >> +		goto err_rpm_put;
> >> +
> >> +	ret = set_frame_rate(sensor);
> >> +	if (ret)
> >> +		goto err_rpm_put;
> >> +
> >> +	ret = apply_exposure(sensor);
> >> +	if (ret)
> >> +		goto err_rpm_put;
> >> +
> >> +	/* Start streaming */
> >> +	ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_START_STREAMING);
> >> +	if (ret)
> >> +		goto err_rpm_put;
> >> +
> >> +	ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST, VGXY61_TIMEOUT_MS);
> >> +	if (ret)
> >> +		goto err_rpm_put;
> >> +
> >> +	ret = vgxy61_wait_state(sensor, STREAMING, VGXY61_TIMEOUT_MS);
> >> +	if (ret)
> >> +		goto err_rpm_put;
> >> +
> >> +	/* vflip and hflip cannot change during streaming */
> >> +	__v4l2_ctrl_grab(sensor->vflip_ctrl, true);
> >> +	__v4l2_ctrl_grab(sensor->hflip_ctrl, true);
> >> +
> >> +	return 0;
> >> +
> >> +err_rpm_put:
> >> +	pm_runtime_put(&client->dev);
> >> +	return ret;
> >> +}
> >> +
> >> +static int vgxy61_stream_disable(struct vgxy61_dev *sensor)
> >> +{
> >> +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd);
> >> +	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, 2000);
> >> +	if (ret)
> >> +		return ret;
> >> +
> >> +	ret = vgxy61_wait_state(sensor, SW_STBY, VGXY61_TIMEOUT_MS);
> >> +	if (ret)
> >> +		return ret;
> >> +
> >> +	__v4l2_ctrl_grab(sensor->vflip_ctrl, true);
> >> +	__v4l2_ctrl_grab(sensor->hflip_ctrl, true);
> >> +
> >> +	pm_runtime_put(&client->dev);
> >> +
> >> +	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;
> > 
> > This should never happen. If you want to be on the safe side and keep
> > the check, I'd turn it into a WARN_ON().
> 
> You are right, I will remove this.
> 
> >> +
> >> +	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_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);q
> >> +
> >> +	return 0;
> >> +}
> >> +
> >> +static int vgxy61_get_vblank_min(struct vgxy61_dev *sensor, enum hdr hdr)
> >> +{
> >> +	int min_vblank =  VGXY61_MIN_FRAME_LENGTH - sensor->current_mode->crop.height;
> >> +	/* Ensure the first tule of thumb can't be negative */
> >> +	int min_vblank_hdr =  VGXY61_MIN_EXPOSURE + sensor->rot_term + 1;
> >> +
> >> +	if (hdr != NO_HDR)
> >> +		return max(min_vblank, min_vblank_hdr);
> >> +	return min_vblank;
> >> +}
> >> +
> >> +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_update_analog_gain(struct vgxy61_dev *sensor, u32 target)
> >> +{
> >> +	struct i2c_client *client = sensor->i2c_client;
> >> +	int ret;
> >> +
> >> +	/* Apply gain */
> >> +	ret = vgxy61_write_reg(sensor, DEVICE_ANALOG_GAIN, target);
> >> +	if (ret)
> >> +		return ret;
> >> +
> >> +	dev_dbg(&client->dev, "Set analog gain: 0x%04x\n", target);
> >> +
> >> +	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;
> > 
> > Macros would be nice instead of magic numbers. Same where applicable
> > below.
> > 
> >> +	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};
> > 
> > Make this static const.
> > 
> >> +	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 < VGXY61_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 vgxy61_get_expo_long_max(struct vgxy61_dev *sensor, int short_expo_ratio)
> >> +{
> >> +	int first_rot_max_expo, second_rot_max_expo, third_rot_max_expo;
> >> +
> >> +	/* Apply sensor rule of thumbs */
> >> +	/* Avoid bad pixel line at the botom of the image */
> >> +	first_rot_max_expo =
> >> +		((sensor->frame_length - sensor->current_mode->crop.height - sensor->rot_term) *
> >> +		short_expo_ratio) - 1;
> >> +
> >> +	/* Avoid sensor crash */
> > 
> > How so ?
> 
> This specific rule guarantees the framelength < expo_time. If this is
> not the case then the sensor will crash.
> 
> I will add a bit more comment for this, and for other rules also.
> 
> >> +	second_rot_max_expo =
> >> +		(((sensor->frame_length - VGXY61_EXPOS_ROT_TERM) * short_expo_ratio) /
> >> +		(short_expo_ratio + 1)) - 1;
> >> +
> >> +	/* Avoid sensor crash */
> >> +	third_rot_max_expo = (sensor->frame_length / 71) * short_expo_ratio;
> >> +
> >> +	/* Take the minimum from all rules */
> >> +	return min(min(first_rot_max_expo, second_rot_max_expo), third_rot_max_expo);
> >> +}
> >> +
> >> +static int vgxy61_update_exposure(struct vgxy61_dev *sensor, int new_expo_long, enum hdr hdr)
> >> +{
> >> +	struct i2c_client *client = sensor->i2c_client;
> >> +	int new_expo_short = 0;
> >> +	int expo_short_max = 0;
> >> +	int expo_long_min = VGXY61_MIN_EXPOSURE;
> >> +	int expo_long_max;
> >> +
> >> +	/* Compute short exposure according to hdr mode and long exposure */
> >> +	switch (hdr) {
> >> +	case HDR_LINEAR:
> >> +		/* Take ratio into account for minimal exposures in HDR_LINEAR */
> >> +		expo_long_min = VGXY61_MIN_EXPOSURE * VGXY61_HDR_LINEAR_RATIO;
> >> +		new_expo_long = max(expo_long_min, new_expo_long);
> >> +
> >> +		expo_long_max = vgxy61_get_expo_long_max(sensor, VGXY61_HDR_LINEAR_RATIO);
> >> +		expo_short_max =
> >> +			(expo_long_max + (VGXY61_HDR_LINEAR_RATIO / 2)) / VGXY61_HDR_LINEAR_RATIO;
> >> +		new_expo_short =
> >> +			(new_expo_long + (VGXY61_HDR_LINEAR_RATIO / 2)) / VGXY61_HDR_LINEAR_RATIO;
> >> +		break;
> >> +	case HDR_SUB:
> >> +		new_expo_long = max(expo_long_min, new_expo_long);
> >> +
> >> +		expo_long_max = vgxy61_get_expo_long_max(sensor, 1);
> >> +		/* Short and long are the same in HDR_SUB */
> >> +		expo_short_max = expo_long_max;
> >> +		new_expo_short = new_expo_long;
> >> +		break;
> >> +	case NO_HDR:
> >> +		new_expo_long = max(expo_long_min, new_expo_long);
> >> +
> >> +		/*
> >> +		 * As short expo is 0 here, only the second rule of thumb applies, see
> >> +		 * vgxy61_get_expo_long_max for more
> >> +		 */
> >> +		expo_long_max = sensor->frame_length - VGXY61_EXPOS_ROT_TERM;
> >> +		break;
> >> +	default:
> >> +		/* Should never happen */
> >> +		WARN_ON(true);
> >> +		break;
> >> +	}
> >> +
> >> +	/* If this happens, something is wrong with formulas */
> >> +	WARN_ON(expo_long_min > expo_long_max);
> >> +
> >> +	if (new_expo_long > expo_long_max) {
> >> +		dev_warn(&client->dev, "Exposure %d too high, clamping to %d\n",
> >> +			 new_expo_long, expo_long_max);
> >> +		new_expo_long = expo_long_max;
> >> +		new_expo_short = expo_short_max;
> >> +	}
> >> +
> >> +	dev_dbg(&client->dev, "frame_length %d, new_expo_long %d, new_expo_short %d\n",
> >> +		sensor->frame_length, new_expo_long, new_expo_short);
> >> +	dev_dbg(&client->dev, "expo_long_min %d, expo_long_max %d\n",
> >> +		expo_long_min, expo_long_max);
> >> +
> >> +	/* Apply exposure */
> >> +	sensor->expo_long = new_expo_long;
> >> +	sensor->expo_short = new_expo_short;
> >> +	sensor->expo_max = expo_long_max;
> >> +	sensor->expo_min = expo_long_min;
> >> +
> >> +	if (sensor->streaming)
> >> +		return apply_exposure(sensor);
> >> +	return 0;
> >> +}
> >> +
> >> +static int vgxy61_update_vblank(struct vgxy61_dev *sensor, int vblank, int hdr)
> >> +{
> >> +	int ret;
> >> +
> >> +	sensor->vblank_min = vgxy61_get_vblank_min(sensor, hdr);
> >> +	sensor->vblank = max(sensor->vblank_min, vblank);
> >> +	sensor->frame_length = sensor->current_mode->crop.height + sensor->vblank;
> >> +
> >> +	/* Update exposure according to vblank */
> >> +	ret = vgxy61_update_exposure(sensor, sensor->expo_long, hdr);
> >> +	if (ret)
> >> +		return ret;
> >> +
> >> +	if (sensor->streaming)
> >> +		return set_frame_rate(sensor);
> >> +	return 0;
> >> +}
> >> +
> >> +static int vgxy61_update_hdr(struct vgxy61_dev *sensor, u32 index)
> >> +{
> >> +	const u8 index2val[] = {0x1, 0x4, 0xa};
> >> +	int ret;
> >> +
> >> +	/*
> >> +	 * vblank and short exposure change 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_vblank(sensor, sensor->vblank, index);
> >> +	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_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);
> >> +		*fmt = format->format;
> >> +	} else if (sensor->current_mode != new_mode || sensor->fmt.code != format->format.code) {
> >> +		fmt = &sensor->fmt;
> >> +		*fmt = format->format;
> >> +
> >> +		sensor->current_mode = new_mode;
> >> +
> >> +		/* Reset vblank and framelength to default */
> >> +		ret = vgxy61_update_vblank(sensor,
> >> +					   VGXY61_FRAME_LENGTH_DEF -
> >> +					   sensor->current_mode->crop.height,
> >> +					   sensor->hdr);
> >> +
> >> +		/* Update controls to reflect new mode */
> >> +		__v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate_ctrl, get_pixel_rate(sensor));
> >> +		__v4l2_ctrl_modify_range(sensor->vblank_ctrl, sensor->vblank_min,
> >> +					 0xffff - sensor->current_mode->crop.height, 1,
> >> +					 sensor->vblank);
> >> +		__v4l2_ctrl_s_ctrl(sensor->vblank_ctrl, sensor->vblank);
> >> +		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
> >> +					 sensor->expo_max, 1,
> >> +					 sensor->expo_long);
> >> +	}
> >> +
> >> +out:
> >> +	mutex_unlock(&sensor->lock);
> >> +
> >> +	return ret;
> >> +}
> >> +
> >> +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);
> > 
> > Can't runtime PM be disabled here ?
> 
> You're absolutely right. This works as of now because I guess my
> regulators are used for something else and therefore do not go down,
> but should not.
>
> This is tricky as I don't want to power up (and therefore patch) the
> sensor every time I want to see the temperature, but maybe I won't
> have the choice.
>
> For example every time I stop streaming in qv4l2, the temperature is read. This would result in this:
> 	power off -> get_temp -> power on (+patch) -> read temp -> power off
> Maybe I can just remove the temperature reading while off? Any other idea?

I think returning an error when the sensor is off is fine. The main use
cases for the temperature sensor are related to imaging.

> Anyway this will depend on the 2/5 patch discussion thread result.
> 
> >> +	if (ret)
> >> +		return ret;
> >> +	ret = vgxy61_poll_reg(sensor, DEVICE_STBY, STBY_NO_REQ, VGXY61_TIMEOUT_MS);
> >> +	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);
> >> +		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);
> >> +		/* Update vblank and exposure controls to match new hdr */
> >> +		__v4l2_ctrl_modify_range(sensor->vblank_ctrl, sensor->vblank_min,
> >> +					 0xffff - sensor->current_mode->crop.height, 1,
> >> +					 sensor->vblank);
> >> +		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
> >> +					 sensor->expo_max, 1, sensor->expo_long);
> >> +		break;
> >> +	case V4L2_CID_VBLANK:
> >> +		ret = vgxy61_update_vblank(sensor, ctrl->val, sensor->hdr);
> >> +		/* Update exposure control to match new vblank */
> >> +		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
> >> +					 sensor->expo_max, 1, sensor->expo_long);
> >> +		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 ret;
> >> +
> >> +	switch (ctrl->id) {
> >> +	case V4L2_CID_TEMPERATURE:
> >> +		ret = vgxy61_get_temp(sensor, &ctrl->val);
> >> +		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 int vgxy61_init_controls(struct vgxy61_dev *sensor)
> >> +{
> >> +	const struct v4l2_ctrl_ops *ops = &vgxy61_ctrl_ops;
> >> +	struct v4l2_ctrl_handler *hdl = &sensor->ctrl_handler;
> >> +	struct v4l2_ctrl *ctrl;
> >> +	int ret;
> >> +
> >> +	v4l2_ctrl_handler_init(hdl, 16);
> >> +	mutex_init(&sensor->lock);
> > 
> > This should be initialized in probe(), it doesn't belong here.
> > 
> >> +	/* We can use our own mutex for the ctrl lock */
> >> +	hdl->lock = &sensor->lock;
> >> +	/* Standard controls */
> >> +	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, 0x1c, 1, 0x0);
> >> +	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 0, 0xfff, 1, 256);
> >> +	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_TEMPERATURE, -128, 128, 1, 0);
> >> +	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);
> >> +	v4l2_ctrl_new_custom(hdl, &vgxy61_hdr_ctrl, NULL);
> >> +	ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HBLANK, 0, sensor->line_length, 1,
> >> +				 sensor->line_length - sensor->current_mode->width);
> >> +	ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> >> +	/* Custom controls */
> >> +	ctrl = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ, ARRAY_SIZE(link_freq) - 1, 0,
> >> +				      link_freq);
> >> +	ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> >> +	/* Keep a pointer to these controls as we need to update them when setting the format */
> >> +	sensor->pixel_rate_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1,
> >> +						    get_pixel_rate(sensor));
> >> +	sensor->pixel_rate_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> >> +	sensor->expo_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, sensor->expo_min,
> >> +					      sensor->expo_max, 1, sensor->expo_long);
> >> +	sensor->vblank_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VBLANK, sensor->vblank_min,
> >> +						0xffff - sensor->current_mode->crop.height, 1,
> >> +						sensor->vblank);
> >> +	sensor->vflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, sensor->vflip);
> >> +	sensor->hflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, sensor->hflip);
> >> +
> >> +	if (hdl->error) {
> >> +		ret = hdl->error;
> >> +		goto free_ctrls;
> >> +	}
> >> +
> >> +	sensor->sd.ctrl_handler = hdl;
> >> +	return 0;
> >> +
> >> +free_ctrls:
> >> +	v4l2_ctrl_handler_free(hdl);
> >> +	mutex_destroy(&sensor->lock);
> >> +	return ret;
> >> +}
> >> +
> >> +static const struct v4l2_subdev_core_ops vgxy61_core_ops = {
> >> +};
> > 
> > You can drop this.
> > 
> >> +
> >> +static const struct v4l2_subdev_video_ops vgxy61_video_ops = {
> >> +	.s_stream = vgxy61_s_stream,
> >> +};
> >> +
> >> +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,
> >> +	.get_selection = vgxy61_get_selection,
> >> +	.enum_frame_size = vgxy61_enum_frame_size,
> >> +};
> >> +
> >> +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[VGXY61_NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
> >> +	u32 phy2log[VGXY61_NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
> >> +	int polarities[VGXY61_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 < VGXY61_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;
> >> +	/* 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, VGXY61_TIMEOUT_MS);
> >> +	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_dbg(&client->dev, "Cut1 detected\n");
> >> +		dev_err(&client->dev, "Cut1 not supported by this driver\n");
> >> +		return -ENODEV;
> >> +	case 0xB:
> >> +		dev_dbg(&client->dev, "Cut2 detected\n");
> >> +		return 0;
> >> +	case 0xC:
> >> +		dev_dbg(&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);
> >> +	sensor->id = id;
> >> +
> >> +	ret = vgxy61_wait_state(sensor, SW_STBY, VGXY61_TIMEOUT_MS);
> >> +	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;
> >> +}
> >> +
> >> +/* Power/clock management functions */
> >> +static int vgxy61_power_on(struct device *dev)
> >> +{
> >> +	struct i2c_client *client = to_i2c_client(dev);
> >> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> >> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >> +	int ret;
> >> +
> >> +	ret = vgxy61_get_regulators(sensor);
> >> +	if (ret) {
> >> +		dev_err(&client->dev, "failed to get regulators %d\n", ret);
> >> +		return ret;
> >> +	}
> >> +
> >> +	ret = regulator_bulk_enable(VGXY61_NUM_SUPPLIES, sensor->supplies);
> >> +	if (ret) {
> >> +		dev_err(&client->dev, "failed to enable regulators %d\n", ret);
> >> +		return ret;
> >> +	}
> >> +
> >> +	ret = clk_prepare_enable(sensor->xclk);
> >> +	if (ret) {
> >> +		dev_err(&client->dev, "failed to enable clock %d\n", ret);
> >> +		goto disable_bulk;
> >> +	}
> >> +
> >> +	/* 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);
> > 
> > Do we really need to run detection every time the sensor is powered on ?
> 
> Surely not, I will add a if (sensor->already_detected) guard for this
> call to make it runs only once.

It could also be done in probe().

> >> +	if (ret) {
> >> +		dev_err(&client->dev, "sensor detect failed %d\n", ret);
> >> +		goto disable_clock;
> >> +	}
> >> +
> >> +	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;
> >> +	}
> >> +
> >> +	return 0;
> >> +
> >> +disable_clock:
> >> +	clk_disable_unprepare(sensor->xclk);
> >> +disable_bulk:
> >> +	regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
> >> +
> >> +	return ret;
> >> +}
> >> +
> >> +static int vgxy61_power_off(struct device *dev)
> >> +{
> >> +	struct i2c_client *client = to_i2c_client(dev);
> >> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> >> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >> +
> >> +	clk_disable_unprepare(sensor->xclk);
> >> +	regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
> >> +	return 0;
> >> +}
> >> +
> >> +static int __maybe_unused vgxy61_suspend(struct device *dev)
> >> +{
> >> +	struct i2c_client *client = to_i2c_client(dev);
> >> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> >> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >> +
> >> +	if (sensor->streaming)
> >> +		vgxy61_stream_disable(sensor);
> > 
> > This shoudn't happen. The sensor should be stopped by the driver on the
> > receiver side during system suspend. Same for resume.
> 
> Sorry I'm still learning the pm_runtime API and I'm not sure I got your point.
> If the sensor is already stopped then I could just remove the whole suspend function? Same for resume.
> I'm not sure when the suspend function is called by the framework.

vgxy61_suspend() is called on system suspend. Disabling streaming here
is problematic, as it's done independently from the CSI-2 receiver. It's
even worse on resume, as if you resume the sensor first, the CSI-2 lanes
will go to HS mode before the receiver is started, and the receiver will
never be able to synchronized. That's why the CSI-2 receiver resume
handler needs to restart streaming by calling the sensor .s_stream()
operation. Same thing at suspend time. Most sensors will thus not need
system suspend/resume handlers.

This means that the CSI-2 receiver needs to be suspended before the
sensor, and resumed after. You can achieve this with the device link
API, calling device_link_add(csi2_dev, sensor_dev, DL_FLAG_STATELESS)
in the CSI-2 receiver driver.

> >> +
> >> +	return 0;
> >> +}
> >> +
> >> +static int __maybe_unused vgxy61_resume(struct device *dev)
> >> +{
> >> +	struct i2c_client *client = to_i2c_client(dev);
> >> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> >> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >> +	int ret;
> >> +
> >> +	if (sensor->streaming) {
> >> +		ret = vgxy61_stream_enable(sensor);
> >> +		if (ret)
> >> +			goto error;
> >> +	}
> >> +
> >> +	return 0;
> >> +
> >> +error:
> >> +	vgxy61_stream_disable(sensor);
> >> +	sensor->streaming = false;
> >> +
> >> +	return ret;
> >> +}
> >> +
> >> +static void vgxy61_set_default_format(struct vgxy61_dev *sensor)
> >> +{
> >> +	if (sensor->id == VG5761_MODEL_ID) {
> > 
> > Would it make sense to have different compatible strings instead of
> > detecting the version automatically ? Not only could it speed up probe
> > if we could skip the detection, it would also allow not powering the
> > sensor up at probe time, which may be required in some cases (for
> > instance when a privacy LED is connected to the sensor power supply,
> > flashing the LED at probe time can be very confusing for users).
> 
> Your flashing LED example is a very good concern.
>
> The idea behind this is to ease the user life as he does not have to
> know what model he is using.

I agree with you, I've implemented the same in sensor drivers. One
option may be to have a generic compatible string that would lead to
model detection at probe time, and specific compatible strings that
would skip it and avoid powering up the sensor. As long as we consider
the requirements on the DT side and make sure to have a way forward
without breaking backward compatibility, this can also be implemented
later.

> For example we have several boards with either the VG5761 or VG5661
> and are interchangeable without changing the device tree, which is
> also better for us as it shrinks the device tree number to maintain.
>
> Also as a company we treat them the same way, hence the same driver.
> I'm not hardly commited to keep this, but it indeed makes my life easier.
> 
> >> +		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;
> >> +	} else if (sensor->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;
> >> +	} else {
> >> +		/* Should never happen */
> >> +		WARN_ON(true);
> >> +	}
> >> +	sensor->fmt.width = sensor->current_mode->width;
> >> +	sensor->fmt.height = sensor->current_mode->height;
> >> +	sensor->vblank = sensor->frame_length - sensor->current_mode->crop.height;
> >> +}
> >> +
> >> +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_Y8_1X8;
> >> +	sensor->fmt.colorspace = V4L2_COLORSPACE_RAW;
> >> +	sensor->fmt.field = V4L2_FIELD_NONE;
> >> +	sensor->fmt.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> >> +	sensor->fmt.quantization = V4L2_QUANTIZATION_DEFAULT;
> >> +	sensor->fmt.xfer_func = V4L2_XFER_FUNC_DEFAULT;
> > 
> > Please implement the subdev .init_cfg() operation, and call it here.
> > There are several examples in existing drivers.
> > 
> >> +	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;
> >> +	sensor->frame_length = VGXY61_FRAME_LENGTH_DEF;
> >> +
> >> +	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, NULL);
> >> +	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 < 6 * HZ_PER_MHZ || sensor->clk_freq > 27 * HZ_PER_MHZ) {
> >> +		dev_err(dev, "Only 6Mhz-27Mhz clock range supported. provide %lu MHz\n",
> >> +			sensor->clk_freq / HZ_PER_MHZ);
> >> +		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;
> >> +
> >> +	/* Request optional reset pin */
> >> +	sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
> >> +
> >> +	ret = vgxy61_power_on(dev);
> >> +	if (ret)
> >> +		return ret;
> >> +
> >> +	vgxy61_set_default_format(sensor);
> >> +
> >> +	/* HDR mode */
> >> +	ret = vgxy61_update_hdr(sensor, sensor->hdr);
> >> +	if (ret)
> >> +		return ret;
> >> +
> >> +	ret = vgxy61_init_controls(sensor);
> >> +	if (ret) {
> >> +		dev_err(&client->dev, "controls initialization failed %d\n", ret);
> >> +		goto error_power_off;
> >> +	}
> >> +
> >> +	ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
> >> +	if (ret) {
> >> +		dev_err(&client->dev, "pads init failed %d\n", ret);
> >> +		goto error_handler_free;
> >> +	}
> >> +
> >> +	ret = v4l2_async_register_subdev(&sensor->sd);
> >> +	if (ret) {
> >> +		dev_err(&client->dev, "async subdev register failed %d\n", ret);
> >> +		goto error_media_entity;
> >> +	}
> >> +
> >> +	/* Enable runtime PM and turn off the device */
> >> +	pm_runtime_set_active(dev);
> >> +	pm_runtime_enable(dev);
> >> +	pm_runtime_idle(dev);
> > 
> > You should do this before registering the subdev, as it can be accessed
> > from userspace right after being registered.
> > 
> > Please also use auto-suspend to avoid long delays when stopping and
> > restarting streaming quickly.
> 
> I was not aware of autosuspend. Thanks a lot, I will look into this.
> 
> >> +
> >> +	dev_info(&client->dev, "vgxy61 probe successfully\n");
> > 
> > I'd drop this, or turn it into a debug message.
> > 
> >> +
> >> +	return 0;
> >> +
> >> +error_media_entity:
> >> +	media_entity_cleanup(&sensor->sd.entity);
> >> +error_handler_free:
> >> +	v4l2_ctrl_handler_free(sensor->sd.ctrl_handler);
> >> +	mutex_destroy(&sensor->lock);
> >> +error_power_off:
> >> +	vgxy61_power_off(dev);
> >> +
> >> +	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);
> >> +	mutex_destroy(&sensor->lock);
> >> +	media_entity_cleanup(&sensor->sd.entity);
> >> +
> >> +	pm_runtime_disable(&client->dev);
> >> +	if (!pm_runtime_status_suspended(&client->dev))
> >> +		vgxy61_power_off(&client->dev);
> >> +	pm_runtime_set_suspended(&client->dev);
> >> +
> >> +	return 0;
> >> +}
> >> +
> >> +static const struct of_device_id vgxy61_dt_ids[] = {
> >> +	{ .compatible = "st,st-vgxy61" },
> >> +	{ /* sentinel */ }
> >> +};
> >> +MODULE_DEVICE_TABLE(of, vgxy61_dt_ids);
> >> +
> >> +static const struct dev_pm_ops vgxy61_pm_ops = {
> >> +	SET_SYSTEM_SLEEP_PM_OPS(vgxy61_suspend, vgxy61_resume)
> >> +	SET_RUNTIME_PM_OPS(vgxy61_power_off, vgxy61_power_on, NULL)
> >> +};
> >> +
> >> +static struct i2c_driver vgxy61_i2c_driver = {
> >> +	.driver = {
> >> +		.name  = "st-vgxy61",
> >> +		.of_match_table = vgxy61_dt_ids,
> >> +		.pm = &vgxy61_pm_ops,
> >> +	},
> >> +	.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");

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v2 2/5] media: v4l: ctrls: Add a control for temperature
  2022-04-20 16:19                     ` Guenter Roeck
@ 2022-04-20 16:51                       ` Laurent Pinchart
  0 siblings, 0 replies; 33+ messages in thread
From: Laurent Pinchart @ 2022-04-20 16:51 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Benjamin Mugnier, Nicolas Dufresne, linux-media, alain.volmat,
	hugues.fruchet, sylvain.petinot, dave.stevenson, sakari.ailus,
	kieran.bingham, linux-hwmon, Ricardo Ribalda

On Wed, Apr 20, 2022 at 09:19:53AM -0700, Guenter Roeck wrote:
> On 4/20/22 08:19, Benjamin Mugnier wrote:
> [ ... ]
> >>
> >> It's the two most common use cases for imaging sensor temperature
> >> measurements that I know of. My question to Benjamin is if he has the
> >> same and/or other use cases.
> >>
> > 
> > Just like you said in a previous mail. This temperature sensor can
> > be used to implement a retroactive loop from the host according to
> > its value, such as noise correction for instance.
> > We don't have anything in the Linux user space that implements this
> > yet, this was in anticipation.
> > So dropping it is fine, I will come back to it if need be ;)
> 
> When you implement this in userspace, you might want to consider situations
> where the temperature is _not_ reported via media controls (which might
> at least in theory happen if the temperature sensor is not part of the
> v4l device), or for existing drivers with hwmon support
> (drivers/media/i2c/video-i2c.c comes into mind).

That's a good point. I wouldn't expect external temperature sensors to
be very useful for this use case though, as what we really need is the
temperature of the camera sensor die.

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v2 5/5] media: i2c: Add driver for ST VGXY61 camera sensor
  2022-04-20 16:48       ` Laurent Pinchart
@ 2022-04-22  8:44         ` Benjamin Mugnier
  2022-04-22 13:29           ` Laurent Pinchart
  0 siblings, 1 reply; 33+ messages in thread
From: Benjamin Mugnier @ 2022-04-22  8:44 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: linux-media, alain.volmat, hugues.fruchet, sylvain.petinot,
	dave.stevenson, sakari.ailus, kieran.bingham



On 20/04/2022 18:48, Laurent Pinchart wrote:
> Hi Benjamin,
> 
> (Sakari, there are two questions for you below)
> 
> On Wed, Apr 20, 2022 at 03:01:24PM +0200, Benjamin Mugnier wrote:
>> Hi Laurent,
>>
>> Thank you for your review.
>> Please consider everything I did not comment as agreed.
>>
>> On 15/04/2022 17:39, Laurent Pinchart wrote:
>>> Hi Benjamin,
>>>
>>> Thank you for the patch.
>>>
>>> This is a partial review as I'm short on time now.
>>>
>>
>> No worries ;)
>>
>>> On Fri, Apr 15, 2022 at 01:18:45PM +0200, Benjamin Mugnier wrote:
>>>> 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     |   10 +
>>>>  drivers/media/i2c/Makefile    |    1 +
>>>>  drivers/media/i2c/st-vgxy61.c | 1997 +++++++++++++++++++++++++++++++++
>>>>  3 files changed, 2008 insertions(+)
>>>>  create mode 100644 drivers/media/i2c/st-vgxy61.c
>>>>
>>>> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
>>>> index fae2baabb773..734f3e1130b0 100644
>>>> --- a/drivers/media/i2c/Kconfig
>>>> +++ b/drivers/media/i2c/Kconfig
>>>> @@ -763,6 +763,16 @@ config VIDEO_VS6624
>>>>  	  To compile this driver as a module, choose M here: the
>>>>  	  module will be called vs6624.
>>>>  
>>>> +config VIDEO_ST_VGXY61
>>>
>>> Alphabetical order please.
>>>
>>>> +	tristate "ST VGXY61 sensor support"
>>>> +	depends on OF && GPIOLIB && VIDEO_DEV && 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.
>>>> +
>>>>  source "drivers/media/i2c/ccs/Kconfig"
>>>>  source "drivers/media/i2c/et8ek8/Kconfig"
>>>>  source "drivers/media/i2c/m5mols/Kconfig"
>>>> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
>>>> index 3e1696963e7f..7f2362228484 100644
>>>> --- a/drivers/media/i2c/Makefile
>>>> +++ b/drivers/media/i2c/Makefile
>>>> @@ -116,6 +116,7 @@ obj-$(CONFIG_VIDEO_SAA7185) += saa7185.o
>>>>  obj-$(CONFIG_VIDEO_SONY_BTF_MPX) += sony-btf-mpx.o
>>>>  obj-$(CONFIG_VIDEO_SR030PC30) += sr030pc30.o
>>>>  obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o
>>>> +obj-$(CONFIG_VIDEO_ST_VGXY61) += st-vgxy61.o
>>>>  obj-$(CONFIG_VIDEO_TC358743) += tc358743.o
>>>>  obj-$(CONFIG_VIDEO_TDA1997X) += tda1997x.o
>>>>  obj-$(CONFIG_VIDEO_TDA7432) += tda7432.o
>>>> diff --git a/drivers/media/i2c/st-vgxy61.c b/drivers/media/i2c/st-vgxy61.c
>>>> new file mode 100644
>>>> index 000000000000..1219d278e648
>>>> --- /dev/null
>>>> +++ b/drivers/media/i2c/st-vgxy61.c
>>>> @@ -0,0 +1,1997 @@
>>>> +// 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/pm_runtime.h>
>>>
>>> Alphabetical order in headers too please.
>>>
>>>> +#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
>>>
>>> I don't think this deserves a macro, but if you want to keep it, you
>>> should avoid generic names that may cause namespace clashes.
>>>
>>
>> I don't make heavy usage of it so I will remove it.
>>
>>>> +
>>>> +#define DEVICE_MODEL_ID_REG				0x0000
>>>
>>> Please prefix macros with a driver-specific prefix. Many of them have a
>>> too generic name.
>>>
>>
>> I will change this to VGXY61_REG_* for registers, and simply VGXY61_* for others.
>>
>>>> +#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 VGXY61_WRITE_MULTIPLE_CHUNK_MAX			16
>>>> +#define VGXY61_NB_GPIOS					4
>>>> +#define VGXY61_NB_POLARITIES				5
>>>> +#define VGXY61_MIN_FRAME_LENGTH				1288
>>>> +#define VGXY61_MIN_EXPOSURE				10
>>>> +#define VGXY61_HDR_LINEAR_RATIO				10
>>>> +#define VGXY61_TIMEOUT_MS				500
>>>> +#define VGXY61_FRAME_LENGTH_DEF				1313
>>>> +#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_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_STVGXY61_BASE + 0)
>>>
>>> This macro should have a device-specific name, should be moved to a
>>> public header, and the control should be documented. I would split the
>>> control documentation and macro definition to a separate patch.
>>>
>>
>> I created two new files include/uapi/linux/st-vgxy61.h and
>> Documentation/userspace-api/media/drivers/st-vgxy61.rst.
>>
>> I'm not sure what you mean by separating the documentation and
>> definition. As there is a significant amount of code dependent on the
>> HDR mode I will keep the public header in this patch, and create a new
>> patch for the documentation. Tell me if there is any issue with this.
> 
> I meant a patch that adds include/uapi/linux/st-vgxy61.h and
> Documentation/userspace-api/media/drivers/st-vgxy61.rst separate from
> the driver, but if it causes more issues than it helps review, you can
> combine them in a single patch.
> 

Ok, I initially thought about moving the relevant st-vgxy61.c hdr code to a new patch, which will be quite complicated.
Only definition and documentation make no issue.

>>>> +
>>>> +#define DEVICE_FWPATCH_REVISION_MAJOR			2
>>>> +#define DEVICE_FWPATCH_REVISION_MINOR			0
>>>> +#define DEVICE_FWPATCH_REVISION_MICRO			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
>>>
>>> The link frequencies should come from DT.
>>
>> This is a hard requirement for this sensor. This value can not be
>> modified and must be 402MHz.
>>
>> I see the ov9734.c does something similar. Is adding this value to the
>> device tree really mandatory?
> 
> I suppose it's fine then. Sakari, what do you think ?
> 
>>>> +};
>>>> +
>>>> +#define VGXY61_NUM_SUPPLIES		ARRAY_SIZE(vgxy61_supply_name)
>>>> +
>>>> +enum bin_mode {
>>>
>>> Same comment here, all enums and structures should use a driver-specific
>>> prefix.
>>>
>>>> +	BIN_MODE_NORMAL,
>>>> +	BIN_MODE_DIGITAL_X2,
>>>> +	BIN_MODE_DIGITAL_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;
>>>> +	struct v4l2_rect crop;
>>>> +};
>>>> +
>>>> +static const u32 vgxy61_supported_codes[] = {
>>>> +	MEDIA_BUS_FMT_Y8_1X8,
>>>> +	MEDIA_BUS_FMT_Y10_1X10,
>>>> +	MEDIA_BUS_FMT_Y12_1X12,
>>>> +	MEDIA_BUS_FMT_Y14_1X14,
>>>> +	MEDIA_BUS_FMT_Y16_1X16
>>>> +};
>>>> +
>>>> +static const struct vgxy61_mode_info vgx661_mode_data[] = {
>>>> +	{
>>>> +		.width = VGX661_WIDTH,
>>>> +		.height = VGX661_HEIGHT,
>>>> +		.bin_mode = BIN_MODE_NORMAL,
>>>> +		.crop = {
>>>> +			.left = 0,
>>>> +			.top = 0,
>>>> +			.width = VGX661_WIDTH,
>>>> +			.height = VGX661_HEIGHT,
>>>> +		},
>>>
>>> It's very nice to see no hardcoded register values in the modes tables
>>> :-) As the register values are computed by the driver, would it make
>>> sense to go one step further and drop the modes completely ? Analog crop
>>> and binning could then be selected freely from userspace.
>>
>> All credit goes to Dave for pointing this in the first review ;)
>> As for the modes, in theory we could drop them completely, but this
>> will require some extra testing as the sensor can't really handle
>> every single mode freely.
>>
>> Could this be done in a second step?
> 
> I'm a bit concerned it could then break userspace, if some applications
> start depending on a fixed list of modes. How difficult do you think
> this would be ?
> 

I mean if they use a fixed list, the upgrade will still allow them to use this list right? As it will implement a superset of the resolution list.
Also if I understand correctly they will get this list dynamically from the get_fmt call, hence once the full support is added the application will update itself right?

For the complexity I need to check various rules to make sure the sensor will not be in a bad state. On top of my head the X and Y must be even, binning must match exactly the format ratio, and certainly other stuff I have not been confronted to yet. And of course testing.

Tell me what you think.

>>>> +	},
>>>> +	{
>>>> +		.width = 1280,
>>>> +		.height = 720,
>>>> +		.bin_mode = BIN_MODE_NORMAL,
>>>> +		.crop = {
>>>> +			.left = 92,
>>>> +			.top = 192,
>>>> +			.width = 1280,
>>>> +			.height = 720,
>>>> +		},
>>>> +	},
>>>> +	{
>>>> +		.width = 640,
>>>> +		.height = 480,
>>>> +		.bin_mode = BIN_MODE_DIGITAL_X2,
>>>> +		.crop = {
>>>> +			.left = 92,
>>>> +			.top = 72,
>>>> +			.width = 1280,
>>>> +			.height = 960,
>>>> +		},
>>>> +	},
>>>> +	{
>>>> +		.width = 320,
>>>> +		.height = 240,
>>>> +		.bin_mode = BIN_MODE_DIGITAL_X4,
>>>
>>> Can the bin mode computed by dividing the crop width by the output width
>>> instead of being stored here ?
>>
>> The sensor also supports analog subsampling on top of digital binning.
>> I did not implement it in the driver but I made the choice to
>> explicitly set the bin_mode for further development. For example the
>> line above could be:
>>
>>   .bin_mode = BIN_MODE_ANALOG_X4
>>
>> I can compute the factor (X2/X4), but not the analog/digital mode.
>> Hope this make sense.
> 
> It thus indeed, but it then leads to the question of how to configure
> skipping and binning separately. The CCS driver registers two subdevs to
> make this possible. It increases the complexity, and I'm increasingly
> thinking that we should extend the V4L2 API to allow configure all the
> common parameters of camera sensors with a single subdev.
> 
> Sakari, any opinion ?
> 
>>>> +		.crop = {
>>>> +			.left = 92,
>>>> +			.top = 72,
>>>> +			.width = 1280,
>>>> +			.height = 960,
>>>> +		},
>>>> +	},
>>>> +};
>>>> +
>>>> +static const struct vgxy61_mode_info vgx761_mode_data[] = {
>>>> +	{
>>>> +		.width = VGX761_WIDTH,
>>>> +		.height = VGX761_HEIGHT,
>>>> +		.bin_mode = BIN_MODE_NORMAL,
>>>> +		.crop = {
>>>> +			.left = 0,
>>>> +			.top = 0,
>>>> +			.width = VGX761_WIDTH,
>>>> +			.height = VGX761_HEIGHT,
>>>> +		},
>>>> +	},
>>>> +	{
>>>> +		.width = 1920,
>>>> +		.height = 1080,
>>>> +		.bin_mode = BIN_MODE_NORMAL,
>>>> +		.crop = {
>>>> +			.left = 12,
>>>> +			.top = 62,
>>>> +			.width = 1920,
>>>> +			.height = 1080,
>>>> +		},
>>>> +	},
>>>> +	{
>>>> +		.width = 1280,
>>>> +		.height = 720,
>>>> +		.bin_mode = BIN_MODE_NORMAL,
>>>> +		.crop = {
>>>> +			.left = 332,
>>>> +			.top = 242,
>>>> +			.width = 1280,
>>>> +			.height = 720,
>>>> +		},
>>>> +	},
>>>> +	{
>>>> +		.width = 640,
>>>> +		.height = 480,
>>>> +		.bin_mode = BIN_MODE_DIGITAL_X2,
>>>> +		.crop = {
>>>> +			.left = 332,
>>>> +			.top = 122,
>>>> +			.width = 1280,
>>>> +			.height = 960,
>>>> +		},
>>>> +	},
>>>> +	{
>>>> +		.width = 320,
>>>> +		.height = 240,
>>>> +		.bin_mode = BIN_MODE_DIGITAL_X4,
>>>> +		.crop = {
>>>> +			.left = 332,
>>>> +			.top = 122,
>>>> +			.width = 1280,
>>>> +			.height = 960,
>>>> +		},
>>>> +	},
>>>> +};
>>>> +
>>>> +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 id;
>>>> +	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 v4l2_ctrl_handler ctrl_handler;
>>>> +	struct v4l2_ctrl *pixel_rate_ctrl;
>>>> +	struct v4l2_ctrl *expo_ctrl;
>>>> +	struct v4l2_ctrl *vblank_ctrl;
>>>> +	struct v4l2_ctrl *vflip_ctrl;
>>>> +	struct v4l2_ctrl *hflip_ctrl;
>>>> +	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;
>>>> +	struct v4l2_fract frame_interval;
>>>
>>> This is not used. Please check all other fields too.
>>>
>>>> +	bool hflip;
>>>> +	bool vflip;
>>>> +	enum hdr hdr;
>>>> +	int expo_long;
>>>> +	int expo_short;
>>>> +	int expo_max;
>>>> +	int expo_min;
>>>> +	int vblank;
>>>> +	int vblank_min;
>>>> +	u16 frame_length;
>>>> +};
>>>> +
>>>> +static u8 get_bpp_by_code(__u32 code)
>>>> +{
>>>> +	switch (code) {
>>>> +	case MEDIA_BUS_FMT_Y8_1X8:
>>>> +		return 8;
>>>> +	case MEDIA_BUS_FMT_Y10_1X10:
>>>> +		return 10;
>>>> +	case MEDIA_BUS_FMT_Y12_1X12:
>>>> +		return 12;
>>>> +	case MEDIA_BUS_FMT_Y14_1X14:
>>>> +		return 14;
>>>> +	case MEDIA_BUS_FMT_Y16_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_Y8_1X8:
>>>> +		return MIPI_CSI2_DT_RAW8;
>>>> +	case MEDIA_BUS_FMT_Y10_1X10:
>>>> +		return MIPI_CSI2_DT_RAW10;
>>>> +	case MEDIA_BUS_FMT_Y12_1X12:
>>>> +		return MIPI_CSI2_DT_RAW12;
>>>> +	case MEDIA_BUS_FMT_Y14_1X14:
>>>> +		return MIPI_CSI2_DT_RAW14;
>>>> +	case MEDIA_BUS_FMT_Y16_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;
>>>> +	}
>>>> +}
>>>
>>> I'd put all this data in a format description structure, and store that
>>> in the vgxy61_supported_codes array.
>>>
>>>> +
>>>> +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, ctrl_handler)->sd;
>>>> +}
>>>> +
>>>> +static int get_chunk_size(struct vgxy61_dev *sensor)
>>>> +{
>>>> +	struct i2c_adapter *adapter = sensor->i2c_client->adapter;
>>>> +	int max_write_len = VGXY61_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, VGXY61_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[VGXY61_WRITE_MULTIPLE_CHUNK_MAX + 2];
>>>> +	int i;
>>>> +	int ret;
>>>> +
>>>> +	if (len > VGXY61_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));
>>>> +}
>>>
>>> You could possibly simplify handling of registers of different sizes by
>>> encoding the size in the register address macros. See
>>> https://gitlab.com/ideasonboard/nxp/linux/-/blob/pinchartl/v5.18/sensors/imx/drivers/media/i2c/imx296.c
>>> for an example.
>>
>> That sounds good, thank you. I will look into this.
> 
> On the topic of register writes, you may also want to consider the *err
> argument to imx296_write(), it simplifies error handling (see how errors
> are handled in imx296_setup(), compared to checking the return value of
> every single write call).
> 
>>>> +
>>>> +static int vgxy61_poll_reg(struct vgxy61_dev *sensor, u16 reg, u8 poll_val, int timeout_ms)
>>>> +{
>>>> +	const int loop_delay_ms = 10;
>>>> +	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, int timeout_ms)
>>>> +{
>>>> +	return vgxy61_poll_reg(sensor, DEVICE_SYSTEM_FSM, state, timeout_ms);
>>>> +}
>>>> +
>>>> +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 = sensor->current_mode->crop.height / sensor->current_mode->height;
>>>> +	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;
>>>
>>> Is there a risk those writes may be applied to different frames ?
>>
>> Yes, but it does not matter for the sensor. Are you thinking of an
>> issue about user space applications for instance?
> 
> Yes, if the writes cross frame boundaries, we could have a frame with a
> weird exposure time, which could confuse ISP algorithms.
> 
> Does the sensor support CSI-2 embedded data ?
> 

It does, and embed the exposures in it. So I guess the ISP algorithm could check these and react accordingly.
As a side note the short exposure is only used for HDR modes, so as long as the user is running in SDR there is no impact at all.

>>>> +
>>>> +	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)
>>>
>>> The function sets the frame length, not the frame rate.
>>>
>>>> +{
>>>> +	return vgxy61_write_reg16(sensor, DEVICE_FRAME_LENGTH, sensor->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, VGXY61_TIMEOUT_MS);
>>>> +}
>>>> +
>>>> +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 */
>>>> +	mode = v4l2_find_nearest_size(sensor->sensor_modes, sensor->sensor_modes_nb,
>>>> +				      width, height, fmt->width, fmt->height);
>>>> +	if (new_mode)
>>>> +		*new_mode = mode;
>>>> +
>>>> +	fmt->code = vgxy61_supported_codes[index];
>>>> +	fmt->width = mode->width;
>>>> +	fmt->height = mode->height;
>>>> +	fmt->colorspace = V4L2_COLORSPACE_RAW;
>>>> +	fmt->field = V4L2_FIELD_NONE;
>>>> +	fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
>>>> +	fmt->quantization = V4L2_QUANTIZATION_DEFAULT;
>>>> +	fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT;
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static int vgxy61_get_selection(struct v4l2_subdev *sd,
>>>> +				struct v4l2_subdev_state *sd_state,
>>>> +				struct v4l2_subdev_selection *sel)
>>>> +{
>>>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>> +
>>>> +	switch (sel->target) {
>>>> +	case V4L2_SEL_TGT_CROP:
>>>> +		sel->r = sensor->current_mode->crop;
>>>> +		return 0;
>>>> +	case V4L2_SEL_TGT_NATIVE_SIZE:
>>>> +	case V4L2_SEL_TGT_CROP_DEFAULT:
>>>> +	case V4L2_SEL_TGT_CROP_BOUNDS:
>>>> +		sel->r.top = 0;
>>>> +		sel->r.left = 0;
>>>> +		sel->r.width = sensor->sensor_width;
>>>> +		sel->r.height = sensor->sensor_height;
>>>> +		return 0;
>>>> +	}
>>>> +
>>>> +	return -EINVAL;
>>>> +}
>>>> +
>>>> +static int vgxy61_stream_enable(struct vgxy61_dev *sensor)
>>>> +{
>>>> +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd);
>>>> +	const struct v4l2_rect *crop = &sensor->current_mode->crop;
>>>> +	int ret;
>>>> +
>>>> +	ret = vgxy61_check_bw(sensor);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +
>>>> +	ret = pm_runtime_get_sync(&client->dev);
>>>> +	if (ret < 0) {
>>>> +		pm_runtime_put_noidle(&client->dev);
>>>> +		return ret;
>>>> +	}
>>>> +
>>>> +	/* Configure sensor */
>>>> +	ret = vgxy61_write_reg(sensor, DEVICE_FORMAT_CTRL, get_bpp_by_code(sensor->fmt.code));
>>>> +	if (ret)
>>>> +		goto err_rpm_put;
>>>> +	ret = vgxy61_write_reg(sensor, DEVICE_OIF_ROI0_CTRL,
>>>> +			       get_data_type_by_code(sensor->fmt.code));
>>>> +	if (ret)
>>>> +		goto err_rpm_put;
>>>> +
>>>> +	ret = vgxy61_write_reg(sensor, DEVICE_READOUT_CTRL, sensor->current_mode->bin_mode);
>>>> +	if (ret)
>>>> +		goto err_rpm_put;
>>>> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_H, crop->left);
>>>> +	if (ret)
>>>> +		goto err_rpm_put;
>>>> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_H, crop->left + crop->width - 1);
>>>> +	if (ret)
>>>> +		goto err_rpm_put;
>>>> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_V, crop->top);
>>>> +	if (ret)
>>>> +		goto err_rpm_put;
>>>> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_V, crop->top + crop->height - 1);
>>>> +	if (ret)
>>>> +		goto err_rpm_put;
>>>> +
>>>> +	ret = set_frame_rate(sensor);
>>>> +	if (ret)
>>>> +		goto err_rpm_put;
>>>> +
>>>> +	ret = apply_exposure(sensor);
>>>> +	if (ret)
>>>> +		goto err_rpm_put;
>>>> +
>>>> +	/* Start streaming */
>>>> +	ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_START_STREAMING);
>>>> +	if (ret)
>>>> +		goto err_rpm_put;
>>>> +
>>>> +	ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST, VGXY61_TIMEOUT_MS);
>>>> +	if (ret)
>>>> +		goto err_rpm_put;
>>>> +
>>>> +	ret = vgxy61_wait_state(sensor, STREAMING, VGXY61_TIMEOUT_MS);
>>>> +	if (ret)
>>>> +		goto err_rpm_put;
>>>> +
>>>> +	/* vflip and hflip cannot change during streaming */
>>>> +	__v4l2_ctrl_grab(sensor->vflip_ctrl, true);
>>>> +	__v4l2_ctrl_grab(sensor->hflip_ctrl, true);
>>>> +
>>>> +	return 0;
>>>> +
>>>> +err_rpm_put:
>>>> +	pm_runtime_put(&client->dev);
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +static int vgxy61_stream_disable(struct vgxy61_dev *sensor)
>>>> +{
>>>> +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd);
>>>> +	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, 2000);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +
>>>> +	ret = vgxy61_wait_state(sensor, SW_STBY, VGXY61_TIMEOUT_MS);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +
>>>> +	__v4l2_ctrl_grab(sensor->vflip_ctrl, true);
>>>> +	__v4l2_ctrl_grab(sensor->hflip_ctrl, true);
>>>> +
>>>> +	pm_runtime_put(&client->dev);
>>>> +
>>>> +	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;
>>>
>>> This should never happen. If you want to be on the safe side and keep
>>> the check, I'd turn it into a WARN_ON().
>>
>> You are right, I will remove this.
>>
>>>> +
>>>> +	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_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);q
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static int vgxy61_get_vblank_min(struct vgxy61_dev *sensor, enum hdr hdr)
>>>> +{
>>>> +	int min_vblank =  VGXY61_MIN_FRAME_LENGTH - sensor->current_mode->crop.height;
>>>> +	/* Ensure the first tule of thumb can't be negative */
>>>> +	int min_vblank_hdr =  VGXY61_MIN_EXPOSURE + sensor->rot_term + 1;
>>>> +
>>>> +	if (hdr != NO_HDR)
>>>> +		return max(min_vblank, min_vblank_hdr);
>>>> +	return min_vblank;
>>>> +}
>>>> +
>>>> +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_update_analog_gain(struct vgxy61_dev *sensor, u32 target)
>>>> +{
>>>> +	struct i2c_client *client = sensor->i2c_client;
>>>> +	int ret;
>>>> +
>>>> +	/* Apply gain */
>>>> +	ret = vgxy61_write_reg(sensor, DEVICE_ANALOG_GAIN, target);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +
>>>> +	dev_dbg(&client->dev, "Set analog gain: 0x%04x\n", target);
>>>> +
>>>> +	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;
>>>
>>> Macros would be nice instead of magic numbers. Same where applicable
>>> below.
>>>
>>>> +	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};
>>>
>>> Make this static const.
>>>
>>>> +	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 < VGXY61_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 vgxy61_get_expo_long_max(struct vgxy61_dev *sensor, int short_expo_ratio)
>>>> +{
>>>> +	int first_rot_max_expo, second_rot_max_expo, third_rot_max_expo;
>>>> +
>>>> +	/* Apply sensor rule of thumbs */
>>>> +	/* Avoid bad pixel line at the botom of the image */
>>>> +	first_rot_max_expo =
>>>> +		((sensor->frame_length - sensor->current_mode->crop.height - sensor->rot_term) *
>>>> +		short_expo_ratio) - 1;
>>>> +
>>>> +	/* Avoid sensor crash */
>>>
>>> How so ?
>>
>> This specific rule guarantees the framelength < expo_time. If this is
>> not the case then the sensor will crash.
>>
>> I will add a bit more comment for this, and for other rules also.
>>
>>>> +	second_rot_max_expo =
>>>> +		(((sensor->frame_length - VGXY61_EXPOS_ROT_TERM) * short_expo_ratio) /
>>>> +		(short_expo_ratio + 1)) - 1;
>>>> +
>>>> +	/* Avoid sensor crash */
>>>> +	third_rot_max_expo = (sensor->frame_length / 71) * short_expo_ratio;
>>>> +
>>>> +	/* Take the minimum from all rules */
>>>> +	return min(min(first_rot_max_expo, second_rot_max_expo), third_rot_max_expo);
>>>> +}
>>>> +
>>>> +static int vgxy61_update_exposure(struct vgxy61_dev *sensor, int new_expo_long, enum hdr hdr)
>>>> +{
>>>> +	struct i2c_client *client = sensor->i2c_client;
>>>> +	int new_expo_short = 0;
>>>> +	int expo_short_max = 0;
>>>> +	int expo_long_min = VGXY61_MIN_EXPOSURE;
>>>> +	int expo_long_max;
>>>> +
>>>> +	/* Compute short exposure according to hdr mode and long exposure */
>>>> +	switch (hdr) {
>>>> +	case HDR_LINEAR:
>>>> +		/* Take ratio into account for minimal exposures in HDR_LINEAR */
>>>> +		expo_long_min = VGXY61_MIN_EXPOSURE * VGXY61_HDR_LINEAR_RATIO;
>>>> +		new_expo_long = max(expo_long_min, new_expo_long);
>>>> +
>>>> +		expo_long_max = vgxy61_get_expo_long_max(sensor, VGXY61_HDR_LINEAR_RATIO);
>>>> +		expo_short_max =
>>>> +			(expo_long_max + (VGXY61_HDR_LINEAR_RATIO / 2)) / VGXY61_HDR_LINEAR_RATIO;
>>>> +		new_expo_short =
>>>> +			(new_expo_long + (VGXY61_HDR_LINEAR_RATIO / 2)) / VGXY61_HDR_LINEAR_RATIO;
>>>> +		break;
>>>> +	case HDR_SUB:
>>>> +		new_expo_long = max(expo_long_min, new_expo_long);
>>>> +
>>>> +		expo_long_max = vgxy61_get_expo_long_max(sensor, 1);
>>>> +		/* Short and long are the same in HDR_SUB */
>>>> +		expo_short_max = expo_long_max;
>>>> +		new_expo_short = new_expo_long;
>>>> +		break;
>>>> +	case NO_HDR:
>>>> +		new_expo_long = max(expo_long_min, new_expo_long);
>>>> +
>>>> +		/*
>>>> +		 * As short expo is 0 here, only the second rule of thumb applies, see
>>>> +		 * vgxy61_get_expo_long_max for more
>>>> +		 */
>>>> +		expo_long_max = sensor->frame_length - VGXY61_EXPOS_ROT_TERM;
>>>> +		break;
>>>> +	default:
>>>> +		/* Should never happen */
>>>> +		WARN_ON(true);
>>>> +		break;
>>>> +	}
>>>> +
>>>> +	/* If this happens, something is wrong with formulas */
>>>> +	WARN_ON(expo_long_min > expo_long_max);
>>>> +
>>>> +	if (new_expo_long > expo_long_max) {
>>>> +		dev_warn(&client->dev, "Exposure %d too high, clamping to %d\n",
>>>> +			 new_expo_long, expo_long_max);
>>>> +		new_expo_long = expo_long_max;
>>>> +		new_expo_short = expo_short_max;
>>>> +	}
>>>> +
>>>> +	dev_dbg(&client->dev, "frame_length %d, new_expo_long %d, new_expo_short %d\n",
>>>> +		sensor->frame_length, new_expo_long, new_expo_short);
>>>> +	dev_dbg(&client->dev, "expo_long_min %d, expo_long_max %d\n",
>>>> +		expo_long_min, expo_long_max);
>>>> +
>>>> +	/* Apply exposure */
>>>> +	sensor->expo_long = new_expo_long;
>>>> +	sensor->expo_short = new_expo_short;
>>>> +	sensor->expo_max = expo_long_max;
>>>> +	sensor->expo_min = expo_long_min;
>>>> +
>>>> +	if (sensor->streaming)
>>>> +		return apply_exposure(sensor);
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static int vgxy61_update_vblank(struct vgxy61_dev *sensor, int vblank, int hdr)
>>>> +{
>>>> +	int ret;
>>>> +
>>>> +	sensor->vblank_min = vgxy61_get_vblank_min(sensor, hdr);
>>>> +	sensor->vblank = max(sensor->vblank_min, vblank);
>>>> +	sensor->frame_length = sensor->current_mode->crop.height + sensor->vblank;
>>>> +
>>>> +	/* Update exposure according to vblank */
>>>> +	ret = vgxy61_update_exposure(sensor, sensor->expo_long, hdr);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +
>>>> +	if (sensor->streaming)
>>>> +		return set_frame_rate(sensor);
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static int vgxy61_update_hdr(struct vgxy61_dev *sensor, u32 index)
>>>> +{
>>>> +	const u8 index2val[] = {0x1, 0x4, 0xa};
>>>> +	int ret;
>>>> +
>>>> +	/*
>>>> +	 * vblank and short exposure change 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_vblank(sensor, sensor->vblank, index);
>>>> +	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_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);
>>>> +		*fmt = format->format;
>>>> +	} else if (sensor->current_mode != new_mode || sensor->fmt.code != format->format.code) {
>>>> +		fmt = &sensor->fmt;
>>>> +		*fmt = format->format;
>>>> +
>>>> +		sensor->current_mode = new_mode;
>>>> +
>>>> +		/* Reset vblank and framelength to default */
>>>> +		ret = vgxy61_update_vblank(sensor,
>>>> +					   VGXY61_FRAME_LENGTH_DEF -
>>>> +					   sensor->current_mode->crop.height,
>>>> +					   sensor->hdr);
>>>> +
>>>> +		/* Update controls to reflect new mode */
>>>> +		__v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate_ctrl, get_pixel_rate(sensor));
>>>> +		__v4l2_ctrl_modify_range(sensor->vblank_ctrl, sensor->vblank_min,
>>>> +					 0xffff - sensor->current_mode->crop.height, 1,
>>>> +					 sensor->vblank);
>>>> +		__v4l2_ctrl_s_ctrl(sensor->vblank_ctrl, sensor->vblank);
>>>> +		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
>>>> +					 sensor->expo_max, 1,
>>>> +					 sensor->expo_long);
>>>> +	}
>>>> +
>>>> +out:
>>>> +	mutex_unlock(&sensor->lock);
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +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);
>>>
>>> Can't runtime PM be disabled here ?
>>
>> You're absolutely right. This works as of now because I guess my
>> regulators are used for something else and therefore do not go down,
>> but should not.
>>
>> This is tricky as I don't want to power up (and therefore patch) the
>> sensor every time I want to see the temperature, but maybe I won't
>> have the choice.
>>
>> For example every time I stop streaming in qv4l2, the temperature is read. This would result in this:
>> 	power off -> get_temp -> power on (+patch) -> read temp -> power off
>> Maybe I can just remove the temperature reading while off? Any other idea?
> 
> I think returning an error when the sensor is off is fine. The main use
> cases for the temperature sensor are related to imaging.
> 
>> Anyway this will depend on the 2/5 patch discussion thread result.
>>
>>>> +	if (ret)
>>>> +		return ret;
>>>> +	ret = vgxy61_poll_reg(sensor, DEVICE_STBY, STBY_NO_REQ, VGXY61_TIMEOUT_MS);
>>>> +	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);
>>>> +		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);
>>>> +		/* Update vblank and exposure controls to match new hdr */
>>>> +		__v4l2_ctrl_modify_range(sensor->vblank_ctrl, sensor->vblank_min,
>>>> +					 0xffff - sensor->current_mode->crop.height, 1,
>>>> +					 sensor->vblank);
>>>> +		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
>>>> +					 sensor->expo_max, 1, sensor->expo_long);
>>>> +		break;
>>>> +	case V4L2_CID_VBLANK:
>>>> +		ret = vgxy61_update_vblank(sensor, ctrl->val, sensor->hdr);
>>>> +		/* Update exposure control to match new vblank */
>>>> +		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
>>>> +					 sensor->expo_max, 1, sensor->expo_long);
>>>> +		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 ret;
>>>> +
>>>> +	switch (ctrl->id) {
>>>> +	case V4L2_CID_TEMPERATURE:
>>>> +		ret = vgxy61_get_temp(sensor, &ctrl->val);
>>>> +		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 int vgxy61_init_controls(struct vgxy61_dev *sensor)
>>>> +{
>>>> +	const struct v4l2_ctrl_ops *ops = &vgxy61_ctrl_ops;
>>>> +	struct v4l2_ctrl_handler *hdl = &sensor->ctrl_handler;
>>>> +	struct v4l2_ctrl *ctrl;
>>>> +	int ret;
>>>> +
>>>> +	v4l2_ctrl_handler_init(hdl, 16);
>>>> +	mutex_init(&sensor->lock);
>>>
>>> This should be initialized in probe(), it doesn't belong here.
>>>
>>>> +	/* We can use our own mutex for the ctrl lock */
>>>> +	hdl->lock = &sensor->lock;
>>>> +	/* Standard controls */
>>>> +	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, 0x1c, 1, 0x0);
>>>> +	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 0, 0xfff, 1, 256);
>>>> +	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_TEMPERATURE, -128, 128, 1, 0);
>>>> +	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);
>>>> +	v4l2_ctrl_new_custom(hdl, &vgxy61_hdr_ctrl, NULL);
>>>> +	ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HBLANK, 0, sensor->line_length, 1,
>>>> +				 sensor->line_length - sensor->current_mode->width);
>>>> +	ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>>>> +	/* Custom controls */
>>>> +	ctrl = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ, ARRAY_SIZE(link_freq) - 1, 0,
>>>> +				      link_freq);
>>>> +	ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>>>> +	/* Keep a pointer to these controls as we need to update them when setting the format */
>>>> +	sensor->pixel_rate_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1,
>>>> +						    get_pixel_rate(sensor));
>>>> +	sensor->pixel_rate_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>>>> +	sensor->expo_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, sensor->expo_min,
>>>> +					      sensor->expo_max, 1, sensor->expo_long);
>>>> +	sensor->vblank_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VBLANK, sensor->vblank_min,
>>>> +						0xffff - sensor->current_mode->crop.height, 1,
>>>> +						sensor->vblank);
>>>> +	sensor->vflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, sensor->vflip);
>>>> +	sensor->hflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, sensor->hflip);
>>>> +
>>>> +	if (hdl->error) {
>>>> +		ret = hdl->error;
>>>> +		goto free_ctrls;
>>>> +	}
>>>> +
>>>> +	sensor->sd.ctrl_handler = hdl;
>>>> +	return 0;
>>>> +
>>>> +free_ctrls:
>>>> +	v4l2_ctrl_handler_free(hdl);
>>>> +	mutex_destroy(&sensor->lock);
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +static const struct v4l2_subdev_core_ops vgxy61_core_ops = {
>>>> +};
>>>
>>> You can drop this.
>>>
>>>> +
>>>> +static const struct v4l2_subdev_video_ops vgxy61_video_ops = {
>>>> +	.s_stream = vgxy61_s_stream,
>>>> +};
>>>> +
>>>> +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,
>>>> +	.get_selection = vgxy61_get_selection,
>>>> +	.enum_frame_size = vgxy61_enum_frame_size,
>>>> +};
>>>> +
>>>> +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[VGXY61_NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
>>>> +	u32 phy2log[VGXY61_NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
>>>> +	int polarities[VGXY61_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 < VGXY61_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;
>>>> +	/* 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, VGXY61_TIMEOUT_MS);
>>>> +	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_dbg(&client->dev, "Cut1 detected\n");
>>>> +		dev_err(&client->dev, "Cut1 not supported by this driver\n");
>>>> +		return -ENODEV;
>>>> +	case 0xB:
>>>> +		dev_dbg(&client->dev, "Cut2 detected\n");
>>>> +		return 0;
>>>> +	case 0xC:
>>>> +		dev_dbg(&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);
>>>> +	sensor->id = id;
>>>> +
>>>> +	ret = vgxy61_wait_state(sensor, SW_STBY, VGXY61_TIMEOUT_MS);
>>>> +	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;
>>>> +}
>>>> +
>>>> +/* Power/clock management functions */
>>>> +static int vgxy61_power_on(struct device *dev)
>>>> +{
>>>> +	struct i2c_client *client = to_i2c_client(dev);
>>>> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
>>>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>> +	int ret;
>>>> +
>>>> +	ret = vgxy61_get_regulators(sensor);
>>>> +	if (ret) {
>>>> +		dev_err(&client->dev, "failed to get regulators %d\n", ret);
>>>> +		return ret;
>>>> +	}
>>>> +
>>>> +	ret = regulator_bulk_enable(VGXY61_NUM_SUPPLIES, sensor->supplies);
>>>> +	if (ret) {
>>>> +		dev_err(&client->dev, "failed to enable regulators %d\n", ret);
>>>> +		return ret;
>>>> +	}
>>>> +
>>>> +	ret = clk_prepare_enable(sensor->xclk);
>>>> +	if (ret) {
>>>> +		dev_err(&client->dev, "failed to enable clock %d\n", ret);
>>>> +		goto disable_bulk;
>>>> +	}
>>>> +
>>>> +	/* 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);
>>>
>>> Do we really need to run detection every time the sensor is powered on ?
>>
>> Surely not, I will add a if (sensor->already_detected) guard for this
>> call to make it runs only once.
> 
> It could also be done in probe().
> 
>>>> +	if (ret) {
>>>> +		dev_err(&client->dev, "sensor detect failed %d\n", ret);
>>>> +		goto disable_clock;
>>>> +	}
>>>> +
>>>> +	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;
>>>> +	}
>>>> +
>>>> +	return 0;
>>>> +
>>>> +disable_clock:
>>>> +	clk_disable_unprepare(sensor->xclk);
>>>> +disable_bulk:
>>>> +	regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +static int vgxy61_power_off(struct device *dev)
>>>> +{
>>>> +	struct i2c_client *client = to_i2c_client(dev);
>>>> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
>>>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>> +
>>>> +	clk_disable_unprepare(sensor->xclk);
>>>> +	regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static int __maybe_unused vgxy61_suspend(struct device *dev)
>>>> +{
>>>> +	struct i2c_client *client = to_i2c_client(dev);
>>>> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
>>>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>> +
>>>> +	if (sensor->streaming)
>>>> +		vgxy61_stream_disable(sensor);
>>>
>>> This shoudn't happen. The sensor should be stopped by the driver on the
>>> receiver side during system suspend. Same for resume.
>>
>> Sorry I'm still learning the pm_runtime API and I'm not sure I got your point.
>> If the sensor is already stopped then I could just remove the whole suspend function? Same for resume.
>> I'm not sure when the suspend function is called by the framework.
> 
> vgxy61_suspend() is called on system suspend. Disabling streaming here
> is problematic, as it's done independently from the CSI-2 receiver. It's
> even worse on resume, as if you resume the sensor first, the CSI-2 lanes
> will go to HS mode before the receiver is started, and the receiver will
> never be able to synchronized. That's why the CSI-2 receiver resume
> handler needs to restart streaming by calling the sensor .s_stream()
> operation. Same thing at suspend time. Most sensors will thus not need
> system suspend/resume handlers.
> 
> This means that the CSI-2 receiver needs to be suspended before the
> sensor, and resumed after. You can achieve this with the device link
> API, calling device_link_add(csi2_dev, sensor_dev, DL_FLAG_STATELESS)
> in the CSI-2 receiver driver.
> 

Let me rephrase your comment just to make sure I got everything right.
On system suspend, the CSI-2 receiver driver should already have stopped the stream for me, so this is irrelevant.
On resume, if by any chance I try to resume streaming before the CSI2 receiver is resuming things will go terribly wrong.
So this means I should trust the CSI2 driver to have performed device_link_add on its side, and can delete the vgxy61 resume and suspend ops.

I have trouble understanding why my use case differs from the imx219 for instance, that afaik does the same thing.

>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static int __maybe_unused vgxy61_resume(struct device *dev)
>>>> +{
>>>> +	struct i2c_client *client = to_i2c_client(dev);
>>>> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
>>>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>> +	int ret;
>>>> +
>>>> +	if (sensor->streaming) {
>>>> +		ret = vgxy61_stream_enable(sensor);
>>>> +		if (ret)
>>>> +			goto error;
>>>> +	}
>>>> +
>>>> +	return 0;
>>>> +
>>>> +error:
>>>> +	vgxy61_stream_disable(sensor);
>>>> +	sensor->streaming = false;
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +static void vgxy61_set_default_format(struct vgxy61_dev *sensor)
>>>> +{
>>>> +	if (sensor->id == VG5761_MODEL_ID) {
>>>
>>> Would it make sense to have different compatible strings instead of
>>> detecting the version automatically ? Not only could it speed up probe
>>> if we could skip the detection, it would also allow not powering the
>>> sensor up at probe time, which may be required in some cases (for
>>> instance when a privacy LED is connected to the sensor power supply,
>>> flashing the LED at probe time can be very confusing for users).
>>
>> Your flashing LED example is a very good concern.
>>
>> The idea behind this is to ease the user life as he does not have to
>> know what model he is using.
> 
> I agree with you, I've implemented the same in sensor drivers. One
> option may be to have a generic compatible string that would lead to
> model detection at probe time, and specific compatible strings that
> would skip it and avoid powering up the sensor. As long as we consider
> the requirements on the DT side and make sure to have a way forward
> without breaking backward compatibility, this can also be implemented
> later.
> 

This is a very good idea.

>> For example we have several boards with either the VG5761 or VG5661
>> and are interchangeable without changing the device tree, which is
>> also better for us as it shrinks the device tree number to maintain.
>>
>> Also as a company we treat them the same way, hence the same driver.
>> I'm not hardly commited to keep this, but it indeed makes my life easier.
>>
>>>> +		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;
>>>> +	} else if (sensor->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;
>>>> +	} else {
>>>> +		/* Should never happen */
>>>> +		WARN_ON(true);
>>>> +	}
>>>> +	sensor->fmt.width = sensor->current_mode->width;
>>>> +	sensor->fmt.height = sensor->current_mode->height;
>>>> +	sensor->vblank = sensor->frame_length - sensor->current_mode->crop.height;
>>>> +}
>>>> +
>>>> +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_Y8_1X8;
>>>> +	sensor->fmt.colorspace = V4L2_COLORSPACE_RAW;
>>>> +	sensor->fmt.field = V4L2_FIELD_NONE;
>>>> +	sensor->fmt.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
>>>> +	sensor->fmt.quantization = V4L2_QUANTIZATION_DEFAULT;
>>>> +	sensor->fmt.xfer_func = V4L2_XFER_FUNC_DEFAULT;
>>>
>>> Please implement the subdev .init_cfg() operation, and call it here.
>>> There are several examples in existing drivers.
>>>
>>>> +	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;
>>>> +	sensor->frame_length = VGXY61_FRAME_LENGTH_DEF;
>>>> +
>>>> +	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, NULL);
>>>> +	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 < 6 * HZ_PER_MHZ || sensor->clk_freq > 27 * HZ_PER_MHZ) {
>>>> +		dev_err(dev, "Only 6Mhz-27Mhz clock range supported. provide %lu MHz\n",
>>>> +			sensor->clk_freq / HZ_PER_MHZ);
>>>> +		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;
>>>> +
>>>> +	/* Request optional reset pin */
>>>> +	sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
>>>> +
>>>> +	ret = vgxy61_power_on(dev);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +
>>>> +	vgxy61_set_default_format(sensor);
>>>> +
>>>> +	/* HDR mode */
>>>> +	ret = vgxy61_update_hdr(sensor, sensor->hdr);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +
>>>> +	ret = vgxy61_init_controls(sensor);
>>>> +	if (ret) {
>>>> +		dev_err(&client->dev, "controls initialization failed %d\n", ret);
>>>> +		goto error_power_off;
>>>> +	}
>>>> +
>>>> +	ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
>>>> +	if (ret) {
>>>> +		dev_err(&client->dev, "pads init failed %d\n", ret);
>>>> +		goto error_handler_free;
>>>> +	}
>>>> +
>>>> +	ret = v4l2_async_register_subdev(&sensor->sd);
>>>> +	if (ret) {
>>>> +		dev_err(&client->dev, "async subdev register failed %d\n", ret);
>>>> +		goto error_media_entity;
>>>> +	}
>>>> +
>>>> +	/* Enable runtime PM and turn off the device */
>>>> +	pm_runtime_set_active(dev);
>>>> +	pm_runtime_enable(dev);
>>>> +	pm_runtime_idle(dev);
>>>
>>> You should do this before registering the subdev, as it can be accessed
>>> from userspace right after being registered.
>>>
>>> Please also use auto-suspend to avoid long delays when stopping and
>>> restarting streaming quickly.
>>
>> I was not aware of autosuspend. Thanks a lot, I will look into this.
>>
>>>> +
>>>> +	dev_info(&client->dev, "vgxy61 probe successfully\n");
>>>
>>> I'd drop this, or turn it into a debug message.
>>>
>>>> +
>>>> +	return 0;
>>>> +
>>>> +error_media_entity:
>>>> +	media_entity_cleanup(&sensor->sd.entity);
>>>> +error_handler_free:
>>>> +	v4l2_ctrl_handler_free(sensor->sd.ctrl_handler);
>>>> +	mutex_destroy(&sensor->lock);
>>>> +error_power_off:
>>>> +	vgxy61_power_off(dev);
>>>> +
>>>> +	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);
>>>> +	mutex_destroy(&sensor->lock);
>>>> +	media_entity_cleanup(&sensor->sd.entity);
>>>> +
>>>> +	pm_runtime_disable(&client->dev);
>>>> +	if (!pm_runtime_status_suspended(&client->dev))
>>>> +		vgxy61_power_off(&client->dev);
>>>> +	pm_runtime_set_suspended(&client->dev);
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static const struct of_device_id vgxy61_dt_ids[] = {
>>>> +	{ .compatible = "st,st-vgxy61" },
>>>> +	{ /* sentinel */ }
>>>> +};
>>>> +MODULE_DEVICE_TABLE(of, vgxy61_dt_ids);
>>>> +
>>>> +static const struct dev_pm_ops vgxy61_pm_ops = {
>>>> +	SET_SYSTEM_SLEEP_PM_OPS(vgxy61_suspend, vgxy61_resume)
>>>> +	SET_RUNTIME_PM_OPS(vgxy61_power_off, vgxy61_power_on, NULL)
>>>> +};
>>>> +
>>>> +static struct i2c_driver vgxy61_i2c_driver = {
>>>> +	.driver = {
>>>> +		.name  = "st-vgxy61",
>>>> +		.of_match_table = vgxy61_dt_ids,
>>>> +		.pm = &vgxy61_pm_ops,
>>>> +	},
>>>> +	.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");
> 

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

* Re: [PATCH v2 5/5] media: i2c: Add driver for ST VGXY61 camera sensor
  2022-04-22  8:44         ` Benjamin Mugnier
@ 2022-04-22 13:29           ` Laurent Pinchart
  2022-04-22 14:40             ` Benjamin Mugnier
  0 siblings, 1 reply; 33+ messages in thread
From: Laurent Pinchart @ 2022-04-22 13:29 UTC (permalink / raw)
  To: Benjamin Mugnier
  Cc: linux-media, alain.volmat, hugues.fruchet, sylvain.petinot,
	dave.stevenson, sakari.ailus, kieran.bingham

Hi Benjamin,

On Fri, Apr 22, 2022 at 10:44:13AM +0200, Benjamin Mugnier wrote:
> On 20/04/2022 18:48, Laurent Pinchart wrote:
> > On Wed, Apr 20, 2022 at 03:01:24PM +0200, Benjamin Mugnier wrote:
> >> On 15/04/2022 17:39, Laurent Pinchart wrote:
> >>> On Fri, Apr 15, 2022 at 01:18:45PM +0200, Benjamin Mugnier wrote:
> >>>> 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     |   10 +
> >>>>  drivers/media/i2c/Makefile    |    1 +
> >>>>  drivers/media/i2c/st-vgxy61.c | 1997 +++++++++++++++++++++++++++++++++
> >>>>  3 files changed, 2008 insertions(+)
> >>>>  create mode 100644 drivers/media/i2c/st-vgxy61.c
> >>>>
> >>>> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> >>>> index fae2baabb773..734f3e1130b0 100644
> >>>> --- a/drivers/media/i2c/Kconfig
> >>>> +++ b/drivers/media/i2c/Kconfig
> >>>> @@ -763,6 +763,16 @@ config VIDEO_VS6624
> >>>>  	  To compile this driver as a module, choose M here: the
> >>>>  	  module will be called vs6624.
> >>>>  
> >>>> +config VIDEO_ST_VGXY61
> >>>
> >>> Alphabetical order please.
> >>>
> >>>> +	tristate "ST VGXY61 sensor support"
> >>>> +	depends on OF && GPIOLIB && VIDEO_DEV && 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.
> >>>> +
> >>>>  source "drivers/media/i2c/ccs/Kconfig"
> >>>>  source "drivers/media/i2c/et8ek8/Kconfig"
> >>>>  source "drivers/media/i2c/m5mols/Kconfig"
> >>>> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> >>>> index 3e1696963e7f..7f2362228484 100644
> >>>> --- a/drivers/media/i2c/Makefile
> >>>> +++ b/drivers/media/i2c/Makefile
> >>>> @@ -116,6 +116,7 @@ obj-$(CONFIG_VIDEO_SAA7185) += saa7185.o
> >>>>  obj-$(CONFIG_VIDEO_SONY_BTF_MPX) += sony-btf-mpx.o
> >>>>  obj-$(CONFIG_VIDEO_SR030PC30) += sr030pc30.o
> >>>>  obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o
> >>>> +obj-$(CONFIG_VIDEO_ST_VGXY61) += st-vgxy61.o
> >>>>  obj-$(CONFIG_VIDEO_TC358743) += tc358743.o
> >>>>  obj-$(CONFIG_VIDEO_TDA1997X) += tda1997x.o
> >>>>  obj-$(CONFIG_VIDEO_TDA7432) += tda7432.o
> >>>> diff --git a/drivers/media/i2c/st-vgxy61.c b/drivers/media/i2c/st-vgxy61.c
> >>>> new file mode 100644
> >>>> index 000000000000..1219d278e648
> >>>> --- /dev/null
> >>>> +++ b/drivers/media/i2c/st-vgxy61.c
> >>>> @@ -0,0 +1,1997 @@
> >>>> +// 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/pm_runtime.h>
> >>>
> >>> Alphabetical order in headers too please.
> >>>
> >>>> +#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
> >>>
> >>> I don't think this deserves a macro, but if you want to keep it, you
> >>> should avoid generic names that may cause namespace clashes.
> >>>
> >>
> >> I don't make heavy usage of it so I will remove it.
> >>
> >>>> +
> >>>> +#define DEVICE_MODEL_ID_REG				0x0000
> >>>
> >>> Please prefix macros with a driver-specific prefix. Many of them have a
> >>> too generic name.
> >>>
> >>
> >> I will change this to VGXY61_REG_* for registers, and simply VGXY61_* for others.
> >>
> >>>> +#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 VGXY61_WRITE_MULTIPLE_CHUNK_MAX			16
> >>>> +#define VGXY61_NB_GPIOS					4
> >>>> +#define VGXY61_NB_POLARITIES				5
> >>>> +#define VGXY61_MIN_FRAME_LENGTH				1288
> >>>> +#define VGXY61_MIN_EXPOSURE				10
> >>>> +#define VGXY61_HDR_LINEAR_RATIO				10
> >>>> +#define VGXY61_TIMEOUT_MS				500
> >>>> +#define VGXY61_FRAME_LENGTH_DEF				1313
> >>>> +#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_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_STVGXY61_BASE + 0)
> >>>
> >>> This macro should have a device-specific name, should be moved to a
> >>> public header, and the control should be documented. I would split the
> >>> control documentation and macro definition to a separate patch.
> >>>
> >>
> >> I created two new files include/uapi/linux/st-vgxy61.h and
> >> Documentation/userspace-api/media/drivers/st-vgxy61.rst.
> >>
> >> I'm not sure what you mean by separating the documentation and
> >> definition. As there is a significant amount of code dependent on the
> >> HDR mode I will keep the public header in this patch, and create a new
> >> patch for the documentation. Tell me if there is any issue with this.
> > 
> > I meant a patch that adds include/uapi/linux/st-vgxy61.h and
> > Documentation/userspace-api/media/drivers/st-vgxy61.rst separate from
> > the driver, but if it causes more issues than it helps review, you can
> > combine them in a single patch.
> 
> Ok, I initially thought about moving the relevant st-vgxy61.c hdr code
> to a new patch, which will be quite complicated.
> Only definition and documentation make no issue.
> 
> >>>> +
> >>>> +#define DEVICE_FWPATCH_REVISION_MAJOR			2
> >>>> +#define DEVICE_FWPATCH_REVISION_MINOR			0
> >>>> +#define DEVICE_FWPATCH_REVISION_MICRO			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
> >>>
> >>> The link frequencies should come from DT.
> >>
> >> This is a hard requirement for this sensor. This value can not be
> >> modified and must be 402MHz.
> >>
> >> I see the ov9734.c does something similar. Is adding this value to the
> >> device tree really mandatory?
> > 
> > I suppose it's fine then. Sakari, what do you think ?
> > 
> >>>> +};
> >>>> +
> >>>> +#define VGXY61_NUM_SUPPLIES		ARRAY_SIZE(vgxy61_supply_name)
> >>>> +
> >>>> +enum bin_mode {
> >>>
> >>> Same comment here, all enums and structures should use a driver-specific
> >>> prefix.
> >>>
> >>>> +	BIN_MODE_NORMAL,
> >>>> +	BIN_MODE_DIGITAL_X2,
> >>>> +	BIN_MODE_DIGITAL_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;
> >>>> +	struct v4l2_rect crop;
> >>>> +};
> >>>> +
> >>>> +static const u32 vgxy61_supported_codes[] = {
> >>>> +	MEDIA_BUS_FMT_Y8_1X8,
> >>>> +	MEDIA_BUS_FMT_Y10_1X10,
> >>>> +	MEDIA_BUS_FMT_Y12_1X12,
> >>>> +	MEDIA_BUS_FMT_Y14_1X14,
> >>>> +	MEDIA_BUS_FMT_Y16_1X16
> >>>> +};
> >>>> +
> >>>> +static const struct vgxy61_mode_info vgx661_mode_data[] = {
> >>>> +	{
> >>>> +		.width = VGX661_WIDTH,
> >>>> +		.height = VGX661_HEIGHT,
> >>>> +		.bin_mode = BIN_MODE_NORMAL,
> >>>> +		.crop = {
> >>>> +			.left = 0,
> >>>> +			.top = 0,
> >>>> +			.width = VGX661_WIDTH,
> >>>> +			.height = VGX661_HEIGHT,
> >>>> +		},
> >>>
> >>> It's very nice to see no hardcoded register values in the modes tables
> >>> :-) As the register values are computed by the driver, would it make
> >>> sense to go one step further and drop the modes completely ? Analog crop
> >>> and binning could then be selected freely from userspace.
> >>
> >> All credit goes to Dave for pointing this in the first review ;)
> >> As for the modes, in theory we could drop them completely, but this
> >> will require some extra testing as the sensor can't really handle
> >> every single mode freely.
> >>
> >> Could this be done in a second step?
> > 
> > I'm a bit concerned it could then break userspace, if some applications
> > start depending on a fixed list of modes. How difficult do you think
> > this would be ?
> 
> I mean if they use a fixed list, the upgrade will still allow them to
> use this list right? As it will implement a superset of the resolution
> list.
>
> Also if I understand correctly they will get this list dynamically
> from the get_fmt call, hence once the full support is added the
> application will update itself right?

The idea is that we could get rid of the modes list completely, and let
the application configure everything through the formats and selection
rectangles. Moving in that direction later may cause some regressions,
for instance an application may set a mode that is not supported, with
the driver picking the closest option now, and later it may get the
exact resolution it requested. It's hard to know if this could break
something.

> For the complexity I need to check various rules to make sure the
> sensor will not be in a bad state. On top of my head the X and Y must
> be even, binning must match exactly the format ratio, and certainly
> other stuff I have not been confronted to yet. And of course testing.

Those rules don't seem to be too complicated. Testing will likely be the
biggest time consumer :-)

> Tell me what you think.
> 
> >>>> +	},
> >>>> +	{
> >>>> +		.width = 1280,
> >>>> +		.height = 720,
> >>>> +		.bin_mode = BIN_MODE_NORMAL,
> >>>> +		.crop = {
> >>>> +			.left = 92,
> >>>> +			.top = 192,
> >>>> +			.width = 1280,
> >>>> +			.height = 720,
> >>>> +		},
> >>>> +	},
> >>>> +	{
> >>>> +		.width = 640,
> >>>> +		.height = 480,
> >>>> +		.bin_mode = BIN_MODE_DIGITAL_X2,
> >>>> +		.crop = {
> >>>> +			.left = 92,
> >>>> +			.top = 72,
> >>>> +			.width = 1280,
> >>>> +			.height = 960,
> >>>> +		},
> >>>> +	},
> >>>> +	{
> >>>> +		.width = 320,
> >>>> +		.height = 240,
> >>>> +		.bin_mode = BIN_MODE_DIGITAL_X4,
> >>>
> >>> Can the bin mode computed by dividing the crop width by the output width
> >>> instead of being stored here ?
> >>
> >> The sensor also supports analog subsampling on top of digital binning.
> >> I did not implement it in the driver but I made the choice to
> >> explicitly set the bin_mode for further development. For example the
> >> line above could be:
> >>
> >>   .bin_mode = BIN_MODE_ANALOG_X4
> >>
> >> I can compute the factor (X2/X4), but not the analog/digital mode.
> >> Hope this make sense.
> > 
> > It thus indeed, but it then leads to the question of how to configure
> > skipping and binning separately. The CCS driver registers two subdevs to
> > make this possible. It increases the complexity, and I'm increasingly
> > thinking that we should extend the V4L2 API to allow configure all the
> > common parameters of camera sensors with a single subdev.
> > 
> > Sakari, any opinion ?
> > 
> >>>> +		.crop = {
> >>>> +			.left = 92,
> >>>> +			.top = 72,
> >>>> +			.width = 1280,
> >>>> +			.height = 960,
> >>>> +		},
> >>>> +	},
> >>>> +};
> >>>> +
> >>>> +static const struct vgxy61_mode_info vgx761_mode_data[] = {
> >>>> +	{
> >>>> +		.width = VGX761_WIDTH,
> >>>> +		.height = VGX761_HEIGHT,
> >>>> +		.bin_mode = BIN_MODE_NORMAL,
> >>>> +		.crop = {
> >>>> +			.left = 0,
> >>>> +			.top = 0,
> >>>> +			.width = VGX761_WIDTH,
> >>>> +			.height = VGX761_HEIGHT,
> >>>> +		},
> >>>> +	},
> >>>> +	{
> >>>> +		.width = 1920,
> >>>> +		.height = 1080,
> >>>> +		.bin_mode = BIN_MODE_NORMAL,
> >>>> +		.crop = {
> >>>> +			.left = 12,
> >>>> +			.top = 62,
> >>>> +			.width = 1920,
> >>>> +			.height = 1080,
> >>>> +		},
> >>>> +	},
> >>>> +	{
> >>>> +		.width = 1280,
> >>>> +		.height = 720,
> >>>> +		.bin_mode = BIN_MODE_NORMAL,
> >>>> +		.crop = {
> >>>> +			.left = 332,
> >>>> +			.top = 242,
> >>>> +			.width = 1280,
> >>>> +			.height = 720,
> >>>> +		},
> >>>> +	},
> >>>> +	{
> >>>> +		.width = 640,
> >>>> +		.height = 480,
> >>>> +		.bin_mode = BIN_MODE_DIGITAL_X2,
> >>>> +		.crop = {
> >>>> +			.left = 332,
> >>>> +			.top = 122,
> >>>> +			.width = 1280,
> >>>> +			.height = 960,
> >>>> +		},
> >>>> +	},
> >>>> +	{
> >>>> +		.width = 320,
> >>>> +		.height = 240,
> >>>> +		.bin_mode = BIN_MODE_DIGITAL_X4,
> >>>> +		.crop = {
> >>>> +			.left = 332,
> >>>> +			.top = 122,
> >>>> +			.width = 1280,
> >>>> +			.height = 960,
> >>>> +		},
> >>>> +	},
> >>>> +};
> >>>> +
> >>>> +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 id;
> >>>> +	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 v4l2_ctrl_handler ctrl_handler;
> >>>> +	struct v4l2_ctrl *pixel_rate_ctrl;
> >>>> +	struct v4l2_ctrl *expo_ctrl;
> >>>> +	struct v4l2_ctrl *vblank_ctrl;
> >>>> +	struct v4l2_ctrl *vflip_ctrl;
> >>>> +	struct v4l2_ctrl *hflip_ctrl;
> >>>> +	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;
> >>>> +	struct v4l2_fract frame_interval;
> >>>
> >>> This is not used. Please check all other fields too.
> >>>
> >>>> +	bool hflip;
> >>>> +	bool vflip;
> >>>> +	enum hdr hdr;
> >>>> +	int expo_long;
> >>>> +	int expo_short;
> >>>> +	int expo_max;
> >>>> +	int expo_min;
> >>>> +	int vblank;
> >>>> +	int vblank_min;
> >>>> +	u16 frame_length;
> >>>> +};
> >>>> +
> >>>> +static u8 get_bpp_by_code(__u32 code)
> >>>> +{
> >>>> +	switch (code) {
> >>>> +	case MEDIA_BUS_FMT_Y8_1X8:
> >>>> +		return 8;
> >>>> +	case MEDIA_BUS_FMT_Y10_1X10:
> >>>> +		return 10;
> >>>> +	case MEDIA_BUS_FMT_Y12_1X12:
> >>>> +		return 12;
> >>>> +	case MEDIA_BUS_FMT_Y14_1X14:
> >>>> +		return 14;
> >>>> +	case MEDIA_BUS_FMT_Y16_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_Y8_1X8:
> >>>> +		return MIPI_CSI2_DT_RAW8;
> >>>> +	case MEDIA_BUS_FMT_Y10_1X10:
> >>>> +		return MIPI_CSI2_DT_RAW10;
> >>>> +	case MEDIA_BUS_FMT_Y12_1X12:
> >>>> +		return MIPI_CSI2_DT_RAW12;
> >>>> +	case MEDIA_BUS_FMT_Y14_1X14:
> >>>> +		return MIPI_CSI2_DT_RAW14;
> >>>> +	case MEDIA_BUS_FMT_Y16_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;
> >>>> +	}
> >>>> +}
> >>>
> >>> I'd put all this data in a format description structure, and store that
> >>> in the vgxy61_supported_codes array.
> >>>
> >>>> +
> >>>> +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, ctrl_handler)->sd;
> >>>> +}
> >>>> +
> >>>> +static int get_chunk_size(struct vgxy61_dev *sensor)
> >>>> +{
> >>>> +	struct i2c_adapter *adapter = sensor->i2c_client->adapter;
> >>>> +	int max_write_len = VGXY61_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, VGXY61_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[VGXY61_WRITE_MULTIPLE_CHUNK_MAX + 2];
> >>>> +	int i;
> >>>> +	int ret;
> >>>> +
> >>>> +	if (len > VGXY61_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));
> >>>> +}
> >>>
> >>> You could possibly simplify handling of registers of different sizes by
> >>> encoding the size in the register address macros. See
> >>> https://gitlab.com/ideasonboard/nxp/linux/-/blob/pinchartl/v5.18/sensors/imx/drivers/media/i2c/imx296.c
> >>> for an example.
> >>
> >> That sounds good, thank you. I will look into this.
> > 
> > On the topic of register writes, you may also want to consider the *err
> > argument to imx296_write(), it simplifies error handling (see how errors
> > are handled in imx296_setup(), compared to checking the return value of
> > every single write call).
> > 
> >>>> +
> >>>> +static int vgxy61_poll_reg(struct vgxy61_dev *sensor, u16 reg, u8 poll_val, int timeout_ms)
> >>>> +{
> >>>> +	const int loop_delay_ms = 10;
> >>>> +	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, int timeout_ms)
> >>>> +{
> >>>> +	return vgxy61_poll_reg(sensor, DEVICE_SYSTEM_FSM, state, timeout_ms);
> >>>> +}
> >>>> +
> >>>> +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 = sensor->current_mode->crop.height / sensor->current_mode->height;
> >>>> +	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;
> >>>
> >>> Is there a risk those writes may be applied to different frames ?
> >>
> >> Yes, but it does not matter for the sensor. Are you thinking of an
> >> issue about user space applications for instance?
> > 
> > Yes, if the writes cross frame boundaries, we could have a frame with a
> > weird exposure time, which could confuse ISP algorithms.
> > 
> > Does the sensor support CSI-2 embedded data ?
> 
> It does, and embed the exposures in it. So I guess the ISP algorithm
> could check these and react accordingly.

For ISPs that can capture embedded data, that sounds good.

> As a side note the short exposure is only used for HDR modes, so as
> long as the user is running in SDR there is no impact at all.

That can help.

I suppose we can improve this further later if/when needed. I'm just
worried it could lead to issues that are difficult to debug.

> >>>> +
> >>>> +	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)
> >>>
> >>> The function sets the frame length, not the frame rate.
> >>>
> >>>> +{
> >>>> +	return vgxy61_write_reg16(sensor, DEVICE_FRAME_LENGTH, sensor->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, VGXY61_TIMEOUT_MS);
> >>>> +}
> >>>> +
> >>>> +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 */
> >>>> +	mode = v4l2_find_nearest_size(sensor->sensor_modes, sensor->sensor_modes_nb,
> >>>> +				      width, height, fmt->width, fmt->height);
> >>>> +	if (new_mode)
> >>>> +		*new_mode = mode;
> >>>> +
> >>>> +	fmt->code = vgxy61_supported_codes[index];
> >>>> +	fmt->width = mode->width;
> >>>> +	fmt->height = mode->height;
> >>>> +	fmt->colorspace = V4L2_COLORSPACE_RAW;
> >>>> +	fmt->field = V4L2_FIELD_NONE;
> >>>> +	fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> >>>> +	fmt->quantization = V4L2_QUANTIZATION_DEFAULT;
> >>>> +	fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT;
> >>>> +
> >>>> +	return 0;
> >>>> +}
> >>>> +
> >>>> +static int vgxy61_get_selection(struct v4l2_subdev *sd,
> >>>> +				struct v4l2_subdev_state *sd_state,
> >>>> +				struct v4l2_subdev_selection *sel)
> >>>> +{
> >>>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >>>> +
> >>>> +	switch (sel->target) {
> >>>> +	case V4L2_SEL_TGT_CROP:
> >>>> +		sel->r = sensor->current_mode->crop;
> >>>> +		return 0;
> >>>> +	case V4L2_SEL_TGT_NATIVE_SIZE:
> >>>> +	case V4L2_SEL_TGT_CROP_DEFAULT:
> >>>> +	case V4L2_SEL_TGT_CROP_BOUNDS:
> >>>> +		sel->r.top = 0;
> >>>> +		sel->r.left = 0;
> >>>> +		sel->r.width = sensor->sensor_width;
> >>>> +		sel->r.height = sensor->sensor_height;
> >>>> +		return 0;
> >>>> +	}
> >>>> +
> >>>> +	return -EINVAL;
> >>>> +}
> >>>> +
> >>>> +static int vgxy61_stream_enable(struct vgxy61_dev *sensor)
> >>>> +{
> >>>> +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd);
> >>>> +	const struct v4l2_rect *crop = &sensor->current_mode->crop;
> >>>> +	int ret;
> >>>> +
> >>>> +	ret = vgxy61_check_bw(sensor);
> >>>> +	if (ret)
> >>>> +		return ret;
> >>>> +
> >>>> +	ret = pm_runtime_get_sync(&client->dev);
> >>>> +	if (ret < 0) {
> >>>> +		pm_runtime_put_noidle(&client->dev);
> >>>> +		return ret;
> >>>> +	}
> >>>> +
> >>>> +	/* Configure sensor */
> >>>> +	ret = vgxy61_write_reg(sensor, DEVICE_FORMAT_CTRL, get_bpp_by_code(sensor->fmt.code));
> >>>> +	if (ret)
> >>>> +		goto err_rpm_put;
> >>>> +	ret = vgxy61_write_reg(sensor, DEVICE_OIF_ROI0_CTRL,
> >>>> +			       get_data_type_by_code(sensor->fmt.code));
> >>>> +	if (ret)
> >>>> +		goto err_rpm_put;
> >>>> +
> >>>> +	ret = vgxy61_write_reg(sensor, DEVICE_READOUT_CTRL, sensor->current_mode->bin_mode);
> >>>> +	if (ret)
> >>>> +		goto err_rpm_put;
> >>>> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_H, crop->left);
> >>>> +	if (ret)
> >>>> +		goto err_rpm_put;
> >>>> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_H, crop->left + crop->width - 1);
> >>>> +	if (ret)
> >>>> +		goto err_rpm_put;
> >>>> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_V, crop->top);
> >>>> +	if (ret)
> >>>> +		goto err_rpm_put;
> >>>> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_V, crop->top + crop->height - 1);
> >>>> +	if (ret)
> >>>> +		goto err_rpm_put;
> >>>> +
> >>>> +	ret = set_frame_rate(sensor);
> >>>> +	if (ret)
> >>>> +		goto err_rpm_put;
> >>>> +
> >>>> +	ret = apply_exposure(sensor);
> >>>> +	if (ret)
> >>>> +		goto err_rpm_put;
> >>>> +
> >>>> +	/* Start streaming */
> >>>> +	ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_START_STREAMING);
> >>>> +	if (ret)
> >>>> +		goto err_rpm_put;
> >>>> +
> >>>> +	ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST, VGXY61_TIMEOUT_MS);
> >>>> +	if (ret)
> >>>> +		goto err_rpm_put;
> >>>> +
> >>>> +	ret = vgxy61_wait_state(sensor, STREAMING, VGXY61_TIMEOUT_MS);
> >>>> +	if (ret)
> >>>> +		goto err_rpm_put;
> >>>> +
> >>>> +	/* vflip and hflip cannot change during streaming */
> >>>> +	__v4l2_ctrl_grab(sensor->vflip_ctrl, true);
> >>>> +	__v4l2_ctrl_grab(sensor->hflip_ctrl, true);
> >>>> +
> >>>> +	return 0;
> >>>> +
> >>>> +err_rpm_put:
> >>>> +	pm_runtime_put(&client->dev);
> >>>> +	return ret;
> >>>> +}
> >>>> +
> >>>> +static int vgxy61_stream_disable(struct vgxy61_dev *sensor)
> >>>> +{
> >>>> +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd);
> >>>> +	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, 2000);
> >>>> +	if (ret)
> >>>> +		return ret;
> >>>> +
> >>>> +	ret = vgxy61_wait_state(sensor, SW_STBY, VGXY61_TIMEOUT_MS);
> >>>> +	if (ret)
> >>>> +		return ret;
> >>>> +
> >>>> +	__v4l2_ctrl_grab(sensor->vflip_ctrl, true);
> >>>> +	__v4l2_ctrl_grab(sensor->hflip_ctrl, true);
> >>>> +
> >>>> +	pm_runtime_put(&client->dev);
> >>>> +
> >>>> +	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;
> >>>
> >>> This should never happen. If you want to be on the safe side and keep
> >>> the check, I'd turn it into a WARN_ON().
> >>
> >> You are right, I will remove this.
> >>
> >>>> +
> >>>> +	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_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);q
> >>>> +
> >>>> +	return 0;
> >>>> +}
> >>>> +
> >>>> +static int vgxy61_get_vblank_min(struct vgxy61_dev *sensor, enum hdr hdr)
> >>>> +{
> >>>> +	int min_vblank =  VGXY61_MIN_FRAME_LENGTH - sensor->current_mode->crop.height;
> >>>> +	/* Ensure the first tule of thumb can't be negative */
> >>>> +	int min_vblank_hdr =  VGXY61_MIN_EXPOSURE + sensor->rot_term + 1;
> >>>> +
> >>>> +	if (hdr != NO_HDR)
> >>>> +		return max(min_vblank, min_vblank_hdr);
> >>>> +	return min_vblank;
> >>>> +}
> >>>> +
> >>>> +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_update_analog_gain(struct vgxy61_dev *sensor, u32 target)
> >>>> +{
> >>>> +	struct i2c_client *client = sensor->i2c_client;
> >>>> +	int ret;
> >>>> +
> >>>> +	/* Apply gain */
> >>>> +	ret = vgxy61_write_reg(sensor, DEVICE_ANALOG_GAIN, target);
> >>>> +	if (ret)
> >>>> +		return ret;
> >>>> +
> >>>> +	dev_dbg(&client->dev, "Set analog gain: 0x%04x\n", target);
> >>>> +
> >>>> +	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;
> >>>
> >>> Macros would be nice instead of magic numbers. Same where applicable
> >>> below.
> >>>
> >>>> +	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};
> >>>
> >>> Make this static const.
> >>>
> >>>> +	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 < VGXY61_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 vgxy61_get_expo_long_max(struct vgxy61_dev *sensor, int short_expo_ratio)
> >>>> +{
> >>>> +	int first_rot_max_expo, second_rot_max_expo, third_rot_max_expo;
> >>>> +
> >>>> +	/* Apply sensor rule of thumbs */
> >>>> +	/* Avoid bad pixel line at the botom of the image */
> >>>> +	first_rot_max_expo =
> >>>> +		((sensor->frame_length - sensor->current_mode->crop.height - sensor->rot_term) *
> >>>> +		short_expo_ratio) - 1;
> >>>> +
> >>>> +	/* Avoid sensor crash */
> >>>
> >>> How so ?
> >>
> >> This specific rule guarantees the framelength < expo_time. If this is
> >> not the case then the sensor will crash.
> >>
> >> I will add a bit more comment for this, and for other rules also.
> >>
> >>>> +	second_rot_max_expo =
> >>>> +		(((sensor->frame_length - VGXY61_EXPOS_ROT_TERM) * short_expo_ratio) /
> >>>> +		(short_expo_ratio + 1)) - 1;
> >>>> +
> >>>> +	/* Avoid sensor crash */
> >>>> +	third_rot_max_expo = (sensor->frame_length / 71) * short_expo_ratio;
> >>>> +
> >>>> +	/* Take the minimum from all rules */
> >>>> +	return min(min(first_rot_max_expo, second_rot_max_expo), third_rot_max_expo);
> >>>> +}
> >>>> +
> >>>> +static int vgxy61_update_exposure(struct vgxy61_dev *sensor, int new_expo_long, enum hdr hdr)
> >>>> +{
> >>>> +	struct i2c_client *client = sensor->i2c_client;
> >>>> +	int new_expo_short = 0;
> >>>> +	int expo_short_max = 0;
> >>>> +	int expo_long_min = VGXY61_MIN_EXPOSURE;
> >>>> +	int expo_long_max;
> >>>> +
> >>>> +	/* Compute short exposure according to hdr mode and long exposure */
> >>>> +	switch (hdr) {
> >>>> +	case HDR_LINEAR:
> >>>> +		/* Take ratio into account for minimal exposures in HDR_LINEAR */
> >>>> +		expo_long_min = VGXY61_MIN_EXPOSURE * VGXY61_HDR_LINEAR_RATIO;
> >>>> +		new_expo_long = max(expo_long_min, new_expo_long);
> >>>> +
> >>>> +		expo_long_max = vgxy61_get_expo_long_max(sensor, VGXY61_HDR_LINEAR_RATIO);
> >>>> +		expo_short_max =
> >>>> +			(expo_long_max + (VGXY61_HDR_LINEAR_RATIO / 2)) / VGXY61_HDR_LINEAR_RATIO;
> >>>> +		new_expo_short =
> >>>> +			(new_expo_long + (VGXY61_HDR_LINEAR_RATIO / 2)) / VGXY61_HDR_LINEAR_RATIO;
> >>>> +		break;
> >>>> +	case HDR_SUB:
> >>>> +		new_expo_long = max(expo_long_min, new_expo_long);
> >>>> +
> >>>> +		expo_long_max = vgxy61_get_expo_long_max(sensor, 1);
> >>>> +		/* Short and long are the same in HDR_SUB */
> >>>> +		expo_short_max = expo_long_max;
> >>>> +		new_expo_short = new_expo_long;
> >>>> +		break;
> >>>> +	case NO_HDR:
> >>>> +		new_expo_long = max(expo_long_min, new_expo_long);
> >>>> +
> >>>> +		/*
> >>>> +		 * As short expo is 0 here, only the second rule of thumb applies, see
> >>>> +		 * vgxy61_get_expo_long_max for more
> >>>> +		 */
> >>>> +		expo_long_max = sensor->frame_length - VGXY61_EXPOS_ROT_TERM;
> >>>> +		break;
> >>>> +	default:
> >>>> +		/* Should never happen */
> >>>> +		WARN_ON(true);
> >>>> +		break;
> >>>> +	}
> >>>> +
> >>>> +	/* If this happens, something is wrong with formulas */
> >>>> +	WARN_ON(expo_long_min > expo_long_max);
> >>>> +
> >>>> +	if (new_expo_long > expo_long_max) {
> >>>> +		dev_warn(&client->dev, "Exposure %d too high, clamping to %d\n",
> >>>> +			 new_expo_long, expo_long_max);
> >>>> +		new_expo_long = expo_long_max;
> >>>> +		new_expo_short = expo_short_max;
> >>>> +	}
> >>>> +
> >>>> +	dev_dbg(&client->dev, "frame_length %d, new_expo_long %d, new_expo_short %d\n",
> >>>> +		sensor->frame_length, new_expo_long, new_expo_short);
> >>>> +	dev_dbg(&client->dev, "expo_long_min %d, expo_long_max %d\n",
> >>>> +		expo_long_min, expo_long_max);
> >>>> +
> >>>> +	/* Apply exposure */
> >>>> +	sensor->expo_long = new_expo_long;
> >>>> +	sensor->expo_short = new_expo_short;
> >>>> +	sensor->expo_max = expo_long_max;
> >>>> +	sensor->expo_min = expo_long_min;
> >>>> +
> >>>> +	if (sensor->streaming)
> >>>> +		return apply_exposure(sensor);
> >>>> +	return 0;
> >>>> +}
> >>>> +
> >>>> +static int vgxy61_update_vblank(struct vgxy61_dev *sensor, int vblank, int hdr)
> >>>> +{
> >>>> +	int ret;
> >>>> +
> >>>> +	sensor->vblank_min = vgxy61_get_vblank_min(sensor, hdr);
> >>>> +	sensor->vblank = max(sensor->vblank_min, vblank);
> >>>> +	sensor->frame_length = sensor->current_mode->crop.height + sensor->vblank;
> >>>> +
> >>>> +	/* Update exposure according to vblank */
> >>>> +	ret = vgxy61_update_exposure(sensor, sensor->expo_long, hdr);
> >>>> +	if (ret)
> >>>> +		return ret;
> >>>> +
> >>>> +	if (sensor->streaming)
> >>>> +		return set_frame_rate(sensor);
> >>>> +	return 0;
> >>>> +}
> >>>> +
> >>>> +static int vgxy61_update_hdr(struct vgxy61_dev *sensor, u32 index)
> >>>> +{
> >>>> +	const u8 index2val[] = {0x1, 0x4, 0xa};
> >>>> +	int ret;
> >>>> +
> >>>> +	/*
> >>>> +	 * vblank and short exposure change 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_vblank(sensor, sensor->vblank, index);
> >>>> +	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_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);
> >>>> +		*fmt = format->format;
> >>>> +	} else if (sensor->current_mode != new_mode || sensor->fmt.code != format->format.code) {
> >>>> +		fmt = &sensor->fmt;
> >>>> +		*fmt = format->format;
> >>>> +
> >>>> +		sensor->current_mode = new_mode;
> >>>> +
> >>>> +		/* Reset vblank and framelength to default */
> >>>> +		ret = vgxy61_update_vblank(sensor,
> >>>> +					   VGXY61_FRAME_LENGTH_DEF -
> >>>> +					   sensor->current_mode->crop.height,
> >>>> +					   sensor->hdr);
> >>>> +
> >>>> +		/* Update controls to reflect new mode */
> >>>> +		__v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate_ctrl, get_pixel_rate(sensor));
> >>>> +		__v4l2_ctrl_modify_range(sensor->vblank_ctrl, sensor->vblank_min,
> >>>> +					 0xffff - sensor->current_mode->crop.height, 1,
> >>>> +					 sensor->vblank);
> >>>> +		__v4l2_ctrl_s_ctrl(sensor->vblank_ctrl, sensor->vblank);
> >>>> +		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
> >>>> +					 sensor->expo_max, 1,
> >>>> +					 sensor->expo_long);
> >>>> +	}
> >>>> +
> >>>> +out:
> >>>> +	mutex_unlock(&sensor->lock);
> >>>> +
> >>>> +	return ret;
> >>>> +}
> >>>> +
> >>>> +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);
> >>>
> >>> Can't runtime PM be disabled here ?
> >>
> >> You're absolutely right. This works as of now because I guess my
> >> regulators are used for something else and therefore do not go down,
> >> but should not.
> >>
> >> This is tricky as I don't want to power up (and therefore patch) the
> >> sensor every time I want to see the temperature, but maybe I won't
> >> have the choice.
> >>
> >> For example every time I stop streaming in qv4l2, the temperature is read. This would result in this:
> >> 	power off -> get_temp -> power on (+patch) -> read temp -> power off
> >> Maybe I can just remove the temperature reading while off? Any other idea?
> > 
> > I think returning an error when the sensor is off is fine. The main use
> > cases for the temperature sensor are related to imaging.
> > 
> >> Anyway this will depend on the 2/5 patch discussion thread result.
> >>
> >>>> +	if (ret)
> >>>> +		return ret;
> >>>> +	ret = vgxy61_poll_reg(sensor, DEVICE_STBY, STBY_NO_REQ, VGXY61_TIMEOUT_MS);
> >>>> +	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);
> >>>> +		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);
> >>>> +		/* Update vblank and exposure controls to match new hdr */
> >>>> +		__v4l2_ctrl_modify_range(sensor->vblank_ctrl, sensor->vblank_min,
> >>>> +					 0xffff - sensor->current_mode->crop.height, 1,
> >>>> +					 sensor->vblank);
> >>>> +		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
> >>>> +					 sensor->expo_max, 1, sensor->expo_long);
> >>>> +		break;
> >>>> +	case V4L2_CID_VBLANK:
> >>>> +		ret = vgxy61_update_vblank(sensor, ctrl->val, sensor->hdr);
> >>>> +		/* Update exposure control to match new vblank */
> >>>> +		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
> >>>> +					 sensor->expo_max, 1, sensor->expo_long);
> >>>> +		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 ret;
> >>>> +
> >>>> +	switch (ctrl->id) {
> >>>> +	case V4L2_CID_TEMPERATURE:
> >>>> +		ret = vgxy61_get_temp(sensor, &ctrl->val);
> >>>> +		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 int vgxy61_init_controls(struct vgxy61_dev *sensor)
> >>>> +{
> >>>> +	const struct v4l2_ctrl_ops *ops = &vgxy61_ctrl_ops;
> >>>> +	struct v4l2_ctrl_handler *hdl = &sensor->ctrl_handler;
> >>>> +	struct v4l2_ctrl *ctrl;
> >>>> +	int ret;
> >>>> +
> >>>> +	v4l2_ctrl_handler_init(hdl, 16);
> >>>> +	mutex_init(&sensor->lock);
> >>>
> >>> This should be initialized in probe(), it doesn't belong here.
> >>>
> >>>> +	/* We can use our own mutex for the ctrl lock */
> >>>> +	hdl->lock = &sensor->lock;
> >>>> +	/* Standard controls */
> >>>> +	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, 0x1c, 1, 0x0);
> >>>> +	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 0, 0xfff, 1, 256);
> >>>> +	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_TEMPERATURE, -128, 128, 1, 0);
> >>>> +	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);
> >>>> +	v4l2_ctrl_new_custom(hdl, &vgxy61_hdr_ctrl, NULL);
> >>>> +	ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HBLANK, 0, sensor->line_length, 1,
> >>>> +				 sensor->line_length - sensor->current_mode->width);
> >>>> +	ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> >>>> +	/* Custom controls */
> >>>> +	ctrl = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ, ARRAY_SIZE(link_freq) - 1, 0,
> >>>> +				      link_freq);
> >>>> +	ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> >>>> +	/* Keep a pointer to these controls as we need to update them when setting the format */
> >>>> +	sensor->pixel_rate_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1,
> >>>> +						    get_pixel_rate(sensor));
> >>>> +	sensor->pixel_rate_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> >>>> +	sensor->expo_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, sensor->expo_min,
> >>>> +					      sensor->expo_max, 1, sensor->expo_long);
> >>>> +	sensor->vblank_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VBLANK, sensor->vblank_min,
> >>>> +						0xffff - sensor->current_mode->crop.height, 1,
> >>>> +						sensor->vblank);
> >>>> +	sensor->vflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, sensor->vflip);
> >>>> +	sensor->hflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, sensor->hflip);
> >>>> +
> >>>> +	if (hdl->error) {
> >>>> +		ret = hdl->error;
> >>>> +		goto free_ctrls;
> >>>> +	}
> >>>> +
> >>>> +	sensor->sd.ctrl_handler = hdl;
> >>>> +	return 0;
> >>>> +
> >>>> +free_ctrls:
> >>>> +	v4l2_ctrl_handler_free(hdl);
> >>>> +	mutex_destroy(&sensor->lock);
> >>>> +	return ret;
> >>>> +}
> >>>> +
> >>>> +static const struct v4l2_subdev_core_ops vgxy61_core_ops = {
> >>>> +};
> >>>
> >>> You can drop this.
> >>>
> >>>> +
> >>>> +static const struct v4l2_subdev_video_ops vgxy61_video_ops = {
> >>>> +	.s_stream = vgxy61_s_stream,
> >>>> +};
> >>>> +
> >>>> +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,
> >>>> +	.get_selection = vgxy61_get_selection,
> >>>> +	.enum_frame_size = vgxy61_enum_frame_size,
> >>>> +};
> >>>> +
> >>>> +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[VGXY61_NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
> >>>> +	u32 phy2log[VGXY61_NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
> >>>> +	int polarities[VGXY61_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 < VGXY61_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;
> >>>> +	/* 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, VGXY61_TIMEOUT_MS);
> >>>> +	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_dbg(&client->dev, "Cut1 detected\n");
> >>>> +		dev_err(&client->dev, "Cut1 not supported by this driver\n");
> >>>> +		return -ENODEV;
> >>>> +	case 0xB:
> >>>> +		dev_dbg(&client->dev, "Cut2 detected\n");
> >>>> +		return 0;
> >>>> +	case 0xC:
> >>>> +		dev_dbg(&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);
> >>>> +	sensor->id = id;
> >>>> +
> >>>> +	ret = vgxy61_wait_state(sensor, SW_STBY, VGXY61_TIMEOUT_MS);
> >>>> +	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;
> >>>> +}
> >>>> +
> >>>> +/* Power/clock management functions */
> >>>> +static int vgxy61_power_on(struct device *dev)
> >>>> +{
> >>>> +	struct i2c_client *client = to_i2c_client(dev);
> >>>> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> >>>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >>>> +	int ret;
> >>>> +
> >>>> +	ret = vgxy61_get_regulators(sensor);
> >>>> +	if (ret) {
> >>>> +		dev_err(&client->dev, "failed to get regulators %d\n", ret);
> >>>> +		return ret;
> >>>> +	}
> >>>> +
> >>>> +	ret = regulator_bulk_enable(VGXY61_NUM_SUPPLIES, sensor->supplies);
> >>>> +	if (ret) {
> >>>> +		dev_err(&client->dev, "failed to enable regulators %d\n", ret);
> >>>> +		return ret;
> >>>> +	}
> >>>> +
> >>>> +	ret = clk_prepare_enable(sensor->xclk);
> >>>> +	if (ret) {
> >>>> +		dev_err(&client->dev, "failed to enable clock %d\n", ret);
> >>>> +		goto disable_bulk;
> >>>> +	}
> >>>> +
> >>>> +	/* 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);
> >>>
> >>> Do we really need to run detection every time the sensor is powered on ?
> >>
> >> Surely not, I will add a if (sensor->already_detected) guard for this
> >> call to make it runs only once.
> > 
> > It could also be done in probe().
> > 
> >>>> +	if (ret) {
> >>>> +		dev_err(&client->dev, "sensor detect failed %d\n", ret);
> >>>> +		goto disable_clock;
> >>>> +	}
> >>>> +
> >>>> +	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;
> >>>> +	}
> >>>> +
> >>>> +	return 0;
> >>>> +
> >>>> +disable_clock:
> >>>> +	clk_disable_unprepare(sensor->xclk);
> >>>> +disable_bulk:
> >>>> +	regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
> >>>> +
> >>>> +	return ret;
> >>>> +}
> >>>> +
> >>>> +static int vgxy61_power_off(struct device *dev)
> >>>> +{
> >>>> +	struct i2c_client *client = to_i2c_client(dev);
> >>>> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> >>>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >>>> +
> >>>> +	clk_disable_unprepare(sensor->xclk);
> >>>> +	regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
> >>>> +	return 0;
> >>>> +}
> >>>> +
> >>>> +static int __maybe_unused vgxy61_suspend(struct device *dev)
> >>>> +{
> >>>> +	struct i2c_client *client = to_i2c_client(dev);
> >>>> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> >>>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >>>> +
> >>>> +	if (sensor->streaming)
> >>>> +		vgxy61_stream_disable(sensor);
> >>>
> >>> This shoudn't happen. The sensor should be stopped by the driver on the
> >>> receiver side during system suspend. Same for resume.
> >>
> >> Sorry I'm still learning the pm_runtime API and I'm not sure I got your point.
> >> If the sensor is already stopped then I could just remove the whole suspend function? Same for resume.
> >> I'm not sure when the suspend function is called by the framework.
> > 
> > vgxy61_suspend() is called on system suspend. Disabling streaming here
> > is problematic, as it's done independently from the CSI-2 receiver. It's
> > even worse on resume, as if you resume the sensor first, the CSI-2 lanes
> > will go to HS mode before the receiver is started, and the receiver will
> > never be able to synchronized. That's why the CSI-2 receiver resume
> > handler needs to restart streaming by calling the sensor .s_stream()
> > operation. Same thing at suspend time. Most sensors will thus not need
> > system suspend/resume handlers.
> > 
> > This means that the CSI-2 receiver needs to be suspended before the
> > sensor, and resumed after. You can achieve this with the device link
> > API, calling device_link_add(csi2_dev, sensor_dev, DL_FLAG_STATELESS)
> > in the CSI-2 receiver driver.
> 
> Let me rephrase your comment just to make sure I got everything right.
>
> On system suspend, the CSI-2 receiver driver should already have
> stopped the stream for me, so this is irrelevant.
>
> On resume, if by any chance I try to resume streaming before the CSI2
> receiver is resuming things will go terribly wrong.
>
> So this means I should trust the CSI2 driver to have performed
> device_link_add on its side, and can delete the vgxy61 resume and
> suspend ops.

Correct.

> I have trouble understanding why my use case differs from the imx219
> for instance, that afaik does the same thing.

Your use case doesn't differ, but we have lots of sensors drivers that
have been developed over time while our understanding of power
management evolved, and older drivers haven't been updated to the latest
best practices. This results in lots of cargo-cult for new drivers too,
it's not a great situation.

> >>>> +
> >>>> +	return 0;
> >>>> +}
> >>>> +
> >>>> +static int __maybe_unused vgxy61_resume(struct device *dev)
> >>>> +{
> >>>> +	struct i2c_client *client = to_i2c_client(dev);
> >>>> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> >>>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >>>> +	int ret;
> >>>> +
> >>>> +	if (sensor->streaming) {
> >>>> +		ret = vgxy61_stream_enable(sensor);
> >>>> +		if (ret)
> >>>> +			goto error;
> >>>> +	}
> >>>> +
> >>>> +	return 0;
> >>>> +
> >>>> +error:
> >>>> +	vgxy61_stream_disable(sensor);
> >>>> +	sensor->streaming = false;
> >>>> +
> >>>> +	return ret;
> >>>> +}
> >>>> +
> >>>> +static void vgxy61_set_default_format(struct vgxy61_dev *sensor)
> >>>> +{
> >>>> +	if (sensor->id == VG5761_MODEL_ID) {
> >>>
> >>> Would it make sense to have different compatible strings instead of
> >>> detecting the version automatically ? Not only could it speed up probe
> >>> if we could skip the detection, it would also allow not powering the
> >>> sensor up at probe time, which may be required in some cases (for
> >>> instance when a privacy LED is connected to the sensor power supply,
> >>> flashing the LED at probe time can be very confusing for users).
> >>
> >> Your flashing LED example is a very good concern.
> >>
> >> The idea behind this is to ease the user life as he does not have to
> >> know what model he is using.
> > 
> > I agree with you, I've implemented the same in sensor drivers. One
> > option may be to have a generic compatible string that would lead to
> > model detection at probe time, and specific compatible strings that
> > would skip it and avoid powering up the sensor. As long as we consider
> > the requirements on the DT side and make sure to have a way forward
> > without breaking backward compatibility, this can also be implemented
> > later.
> 
> This is a very good idea.
> 
> >> For example we have several boards with either the VG5761 or VG5661
> >> and are interchangeable without changing the device tree, which is
> >> also better for us as it shrinks the device tree number to maintain.
> >>
> >> Also as a company we treat them the same way, hence the same driver.
> >> I'm not hardly commited to keep this, but it indeed makes my life easier.
> >>
> >>>> +		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;
> >>>> +	} else if (sensor->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;
> >>>> +	} else {
> >>>> +		/* Should never happen */
> >>>> +		WARN_ON(true);
> >>>> +	}
> >>>> +	sensor->fmt.width = sensor->current_mode->width;
> >>>> +	sensor->fmt.height = sensor->current_mode->height;
> >>>> +	sensor->vblank = sensor->frame_length - sensor->current_mode->crop.height;
> >>>> +}
> >>>> +
> >>>> +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_Y8_1X8;
> >>>> +	sensor->fmt.colorspace = V4L2_COLORSPACE_RAW;
> >>>> +	sensor->fmt.field = V4L2_FIELD_NONE;
> >>>> +	sensor->fmt.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> >>>> +	sensor->fmt.quantization = V4L2_QUANTIZATION_DEFAULT;
> >>>> +	sensor->fmt.xfer_func = V4L2_XFER_FUNC_DEFAULT;
> >>>
> >>> Please implement the subdev .init_cfg() operation, and call it here.
> >>> There are several examples in existing drivers.
> >>>
> >>>> +	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;
> >>>> +	sensor->frame_length = VGXY61_FRAME_LENGTH_DEF;
> >>>> +
> >>>> +	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, NULL);
> >>>> +	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 < 6 * HZ_PER_MHZ || sensor->clk_freq > 27 * HZ_PER_MHZ) {
> >>>> +		dev_err(dev, "Only 6Mhz-27Mhz clock range supported. provide %lu MHz\n",
> >>>> +			sensor->clk_freq / HZ_PER_MHZ);
> >>>> +		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;
> >>>> +
> >>>> +	/* Request optional reset pin */
> >>>> +	sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
> >>>> +
> >>>> +	ret = vgxy61_power_on(dev);
> >>>> +	if (ret)
> >>>> +		return ret;
> >>>> +
> >>>> +	vgxy61_set_default_format(sensor);
> >>>> +
> >>>> +	/* HDR mode */
> >>>> +	ret = vgxy61_update_hdr(sensor, sensor->hdr);
> >>>> +	if (ret)
> >>>> +		return ret;
> >>>> +
> >>>> +	ret = vgxy61_init_controls(sensor);
> >>>> +	if (ret) {
> >>>> +		dev_err(&client->dev, "controls initialization failed %d\n", ret);
> >>>> +		goto error_power_off;
> >>>> +	}
> >>>> +
> >>>> +	ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
> >>>> +	if (ret) {
> >>>> +		dev_err(&client->dev, "pads init failed %d\n", ret);
> >>>> +		goto error_handler_free;
> >>>> +	}
> >>>> +
> >>>> +	ret = v4l2_async_register_subdev(&sensor->sd);
> >>>> +	if (ret) {
> >>>> +		dev_err(&client->dev, "async subdev register failed %d\n", ret);
> >>>> +		goto error_media_entity;
> >>>> +	}
> >>>> +
> >>>> +	/* Enable runtime PM and turn off the device */
> >>>> +	pm_runtime_set_active(dev);
> >>>> +	pm_runtime_enable(dev);
> >>>> +	pm_runtime_idle(dev);
> >>>
> >>> You should do this before registering the subdev, as it can be accessed
> >>> from userspace right after being registered.
> >>>
> >>> Please also use auto-suspend to avoid long delays when stopping and
> >>> restarting streaming quickly.
> >>
> >> I was not aware of autosuspend. Thanks a lot, I will look into this.
> >>
> >>>> +
> >>>> +	dev_info(&client->dev, "vgxy61 probe successfully\n");
> >>>
> >>> I'd drop this, or turn it into a debug message.
> >>>
> >>>> +
> >>>> +	return 0;
> >>>> +
> >>>> +error_media_entity:
> >>>> +	media_entity_cleanup(&sensor->sd.entity);
> >>>> +error_handler_free:
> >>>> +	v4l2_ctrl_handler_free(sensor->sd.ctrl_handler);
> >>>> +	mutex_destroy(&sensor->lock);
> >>>> +error_power_off:
> >>>> +	vgxy61_power_off(dev);
> >>>> +
> >>>> +	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);
> >>>> +	mutex_destroy(&sensor->lock);
> >>>> +	media_entity_cleanup(&sensor->sd.entity);
> >>>> +
> >>>> +	pm_runtime_disable(&client->dev);
> >>>> +	if (!pm_runtime_status_suspended(&client->dev))
> >>>> +		vgxy61_power_off(&client->dev);
> >>>> +	pm_runtime_set_suspended(&client->dev);
> >>>> +
> >>>> +	return 0;
> >>>> +}
> >>>> +
> >>>> +static const struct of_device_id vgxy61_dt_ids[] = {
> >>>> +	{ .compatible = "st,st-vgxy61" },
> >>>> +	{ /* sentinel */ }
> >>>> +};
> >>>> +MODULE_DEVICE_TABLE(of, vgxy61_dt_ids);
> >>>> +
> >>>> +static const struct dev_pm_ops vgxy61_pm_ops = {
> >>>> +	SET_SYSTEM_SLEEP_PM_OPS(vgxy61_suspend, vgxy61_resume)
> >>>> +	SET_RUNTIME_PM_OPS(vgxy61_power_off, vgxy61_power_on, NULL)
> >>>> +};
> >>>> +
> >>>> +static struct i2c_driver vgxy61_i2c_driver = {
> >>>> +	.driver = {
> >>>> +		.name  = "st-vgxy61",
> >>>> +		.of_match_table = vgxy61_dt_ids,
> >>>> +		.pm = &vgxy61_pm_ops,
> >>>> +	},
> >>>> +	.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");

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v2 5/5] media: i2c: Add driver for ST VGXY61 camera sensor
  2022-04-22 13:29           ` Laurent Pinchart
@ 2022-04-22 14:40             ` Benjamin Mugnier
  2022-04-24 16:34               ` Laurent Pinchart
  0 siblings, 1 reply; 33+ messages in thread
From: Benjamin Mugnier @ 2022-04-22 14:40 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: linux-media, alain.volmat, hugues.fruchet, sylvain.petinot,
	dave.stevenson, sakari.ailus, kieran.bingham

Hi Laurent,

On 22/04/2022 15:29, Laurent Pinchart wrote:
> Hi Benjamin,
> 
> On Fri, Apr 22, 2022 at 10:44:13AM +0200, Benjamin Mugnier wrote:
>> On 20/04/2022 18:48, Laurent Pinchart wrote:
>>> On Wed, Apr 20, 2022 at 03:01:24PM +0200, Benjamin Mugnier wrote:
>>>> On 15/04/2022 17:39, Laurent Pinchart wrote:
>>>>> On Fri, Apr 15, 2022 at 01:18:45PM +0200, Benjamin Mugnier wrote:
>>>>>> 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     |   10 +
>>>>>>  drivers/media/i2c/Makefile    |    1 +
>>>>>>  drivers/media/i2c/st-vgxy61.c | 1997 +++++++++++++++++++++++++++++++++
>>>>>>  3 files changed, 2008 insertions(+)
>>>>>>  create mode 100644 drivers/media/i2c/st-vgxy61.c
>>>>>>
>>>>>> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
>>>>>> index fae2baabb773..734f3e1130b0 100644
>>>>>> --- a/drivers/media/i2c/Kconfig
>>>>>> +++ b/drivers/media/i2c/Kconfig
>>>>>> @@ -763,6 +763,16 @@ config VIDEO_VS6624
>>>>>>  	  To compile this driver as a module, choose M here: the
>>>>>>  	  module will be called vs6624.
>>>>>>  
>>>>>> +config VIDEO_ST_VGXY61
>>>>>
>>>>> Alphabetical order please.
>>>>>
>>>>>> +	tristate "ST VGXY61 sensor support"
>>>>>> +	depends on OF && GPIOLIB && VIDEO_DEV && 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.
>>>>>> +
>>>>>>  source "drivers/media/i2c/ccs/Kconfig"
>>>>>>  source "drivers/media/i2c/et8ek8/Kconfig"
>>>>>>  source "drivers/media/i2c/m5mols/Kconfig"
>>>>>> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
>>>>>> index 3e1696963e7f..7f2362228484 100644
>>>>>> --- a/drivers/media/i2c/Makefile
>>>>>> +++ b/drivers/media/i2c/Makefile
>>>>>> @@ -116,6 +116,7 @@ obj-$(CONFIG_VIDEO_SAA7185) += saa7185.o
>>>>>>  obj-$(CONFIG_VIDEO_SONY_BTF_MPX) += sony-btf-mpx.o
>>>>>>  obj-$(CONFIG_VIDEO_SR030PC30) += sr030pc30.o
>>>>>>  obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o
>>>>>> +obj-$(CONFIG_VIDEO_ST_VGXY61) += st-vgxy61.o
>>>>>>  obj-$(CONFIG_VIDEO_TC358743) += tc358743.o
>>>>>>  obj-$(CONFIG_VIDEO_TDA1997X) += tda1997x.o
>>>>>>  obj-$(CONFIG_VIDEO_TDA7432) += tda7432.o
>>>>>> diff --git a/drivers/media/i2c/st-vgxy61.c b/drivers/media/i2c/st-vgxy61.c
>>>>>> new file mode 100644
>>>>>> index 000000000000..1219d278e648
>>>>>> --- /dev/null
>>>>>> +++ b/drivers/media/i2c/st-vgxy61.c
>>>>>> @@ -0,0 +1,1997 @@
>>>>>> +// 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/pm_runtime.h>
>>>>>
>>>>> Alphabetical order in headers too please.
>>>>>
>>>>>> +#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
>>>>>
>>>>> I don't think this deserves a macro, but if you want to keep it, you
>>>>> should avoid generic names that may cause namespace clashes.
>>>>>
>>>>
>>>> I don't make heavy usage of it so I will remove it.
>>>>
>>>>>> +
>>>>>> +#define DEVICE_MODEL_ID_REG				0x0000
>>>>>
>>>>> Please prefix macros with a driver-specific prefix. Many of them have a
>>>>> too generic name.
>>>>>
>>>>
>>>> I will change this to VGXY61_REG_* for registers, and simply VGXY61_* for others.
>>>>
>>>>>> +#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 VGXY61_WRITE_MULTIPLE_CHUNK_MAX			16
>>>>>> +#define VGXY61_NB_GPIOS					4
>>>>>> +#define VGXY61_NB_POLARITIES				5
>>>>>> +#define VGXY61_MIN_FRAME_LENGTH				1288
>>>>>> +#define VGXY61_MIN_EXPOSURE				10
>>>>>> +#define VGXY61_HDR_LINEAR_RATIO				10
>>>>>> +#define VGXY61_TIMEOUT_MS				500
>>>>>> +#define VGXY61_FRAME_LENGTH_DEF				1313
>>>>>> +#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_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_STVGXY61_BASE + 0)
>>>>>
>>>>> This macro should have a device-specific name, should be moved to a
>>>>> public header, and the control should be documented. I would split the
>>>>> control documentation and macro definition to a separate patch.
>>>>>
>>>>
>>>> I created two new files include/uapi/linux/st-vgxy61.h and
>>>> Documentation/userspace-api/media/drivers/st-vgxy61.rst.
>>>>
>>>> I'm not sure what you mean by separating the documentation and
>>>> definition. As there is a significant amount of code dependent on the
>>>> HDR mode I will keep the public header in this patch, and create a new
>>>> patch for the documentation. Tell me if there is any issue with this.
>>>
>>> I meant a patch that adds include/uapi/linux/st-vgxy61.h and
>>> Documentation/userspace-api/media/drivers/st-vgxy61.rst separate from
>>> the driver, but if it causes more issues than it helps review, you can
>>> combine them in a single patch.
>>
>> Ok, I initially thought about moving the relevant st-vgxy61.c hdr code
>> to a new patch, which will be quite complicated.
>> Only definition and documentation make no issue.
>>
>>>>>> +
>>>>>> +#define DEVICE_FWPATCH_REVISION_MAJOR			2
>>>>>> +#define DEVICE_FWPATCH_REVISION_MINOR			0
>>>>>> +#define DEVICE_FWPATCH_REVISION_MICRO			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
>>>>>
>>>>> The link frequencies should come from DT.
>>>>
>>>> This is a hard requirement for this sensor. This value can not be
>>>> modified and must be 402MHz.
>>>>
>>>> I see the ov9734.c does something similar. Is adding this value to the
>>>> device tree really mandatory?
>>>
>>> I suppose it's fine then. Sakari, what do you think ?
>>>
>>>>>> +};
>>>>>> +
>>>>>> +#define VGXY61_NUM_SUPPLIES		ARRAY_SIZE(vgxy61_supply_name)
>>>>>> +
>>>>>> +enum bin_mode {
>>>>>
>>>>> Same comment here, all enums and structures should use a driver-specific
>>>>> prefix.
>>>>>
>>>>>> +	BIN_MODE_NORMAL,
>>>>>> +	BIN_MODE_DIGITAL_X2,
>>>>>> +	BIN_MODE_DIGITAL_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;
>>>>>> +	struct v4l2_rect crop;
>>>>>> +};
>>>>>> +
>>>>>> +static const u32 vgxy61_supported_codes[] = {
>>>>>> +	MEDIA_BUS_FMT_Y8_1X8,
>>>>>> +	MEDIA_BUS_FMT_Y10_1X10,
>>>>>> +	MEDIA_BUS_FMT_Y12_1X12,
>>>>>> +	MEDIA_BUS_FMT_Y14_1X14,
>>>>>> +	MEDIA_BUS_FMT_Y16_1X16
>>>>>> +};
>>>>>> +
>>>>>> +static const struct vgxy61_mode_info vgx661_mode_data[] = {
>>>>>> +	{
>>>>>> +		.width = VGX661_WIDTH,
>>>>>> +		.height = VGX661_HEIGHT,
>>>>>> +		.bin_mode = BIN_MODE_NORMAL,
>>>>>> +		.crop = {
>>>>>> +			.left = 0,
>>>>>> +			.top = 0,
>>>>>> +			.width = VGX661_WIDTH,
>>>>>> +			.height = VGX661_HEIGHT,
>>>>>> +		},
>>>>>
>>>>> It's very nice to see no hardcoded register values in the modes tables
>>>>> :-) As the register values are computed by the driver, would it make
>>>>> sense to go one step further and drop the modes completely ? Analog crop
>>>>> and binning could then be selected freely from userspace.
>>>>
>>>> All credit goes to Dave for pointing this in the first review ;)
>>>> As for the modes, in theory we could drop them completely, but this
>>>> will require some extra testing as the sensor can't really handle
>>>> every single mode freely.
>>>>
>>>> Could this be done in a second step?
>>>
>>> I'm a bit concerned it could then break userspace, if some applications
>>> start depending on a fixed list of modes. How difficult do you think
>>> this would be ?
>>
>> I mean if they use a fixed list, the upgrade will still allow them to
>> use this list right? As it will implement a superset of the resolution
>> list.
>>
>> Also if I understand correctly they will get this list dynamically
>> from the get_fmt call, hence once the full support is added the
>> application will update itself right?
> 
> The idea is that we could get rid of the modes list completely, and let
> the application configure everything through the formats and selection
> rectangles. Moving in that direction later may cause some regressions,
> for instance an application may set a mode that is not supported, with
> the driver picking the closest option now, and later it may get the
> exact resolution it requested. It's hard to know if this could break
> something.
> 
>> For the complexity I need to check various rules to make sure the
>> sensor will not be in a bad state. On top of my head the X and Y must
>> be even, binning must match exactly the format ratio, and certainly
>> other stuff I have not been confronted to yet. And of course testing.
> 
> Those rules don't seem to be too complicated. Testing will likely be the
> biggest time consumer :-)
> 

I will give it go for v3 then. If I run into issues I could still remove the find_nearest_size call and return an error if the exact size is not supported, but appart from the time consumming part this should go well.

>> Tell me what you think.
>>
>>>>>> +	},
>>>>>> +	{
>>>>>> +		.width = 1280,
>>>>>> +		.height = 720,
>>>>>> +		.bin_mode = BIN_MODE_NORMAL,
>>>>>> +		.crop = {
>>>>>> +			.left = 92,
>>>>>> +			.top = 192,
>>>>>> +			.width = 1280,
>>>>>> +			.height = 720,
>>>>>> +		},
>>>>>> +	},
>>>>>> +	{
>>>>>> +		.width = 640,
>>>>>> +		.height = 480,
>>>>>> +		.bin_mode = BIN_MODE_DIGITAL_X2,
>>>>>> +		.crop = {
>>>>>> +			.left = 92,
>>>>>> +			.top = 72,
>>>>>> +			.width = 1280,
>>>>>> +			.height = 960,
>>>>>> +		},
>>>>>> +	},
>>>>>> +	{
>>>>>> +		.width = 320,
>>>>>> +		.height = 240,
>>>>>> +		.bin_mode = BIN_MODE_DIGITAL_X4,
>>>>>
>>>>> Can the bin mode computed by dividing the crop width by the output width
>>>>> instead of being stored here ?
>>>>
>>>> The sensor also supports analog subsampling on top of digital binning.
>>>> I did not implement it in the driver but I made the choice to
>>>> explicitly set the bin_mode for further development. For example the
>>>> line above could be:
>>>>
>>>>   .bin_mode = BIN_MODE_ANALOG_X4
>>>>
>>>> I can compute the factor (X2/X4), but not the analog/digital mode.
>>>> Hope this make sense.
>>>
>>> It thus indeed, but it then leads to the question of how to configure
>>> skipping and binning separately. The CCS driver registers two subdevs to
>>> make this possible. It increases the complexity, and I'm increasingly
>>> thinking that we should extend the V4L2 API to allow configure all the
>>> common parameters of camera sensors with a single subdev.
>>>
>>> Sakari, any opinion ?
>>>
>>>>>> +		.crop = {
>>>>>> +			.left = 92,
>>>>>> +			.top = 72,
>>>>>> +			.width = 1280,
>>>>>> +			.height = 960,
>>>>>> +		},
>>>>>> +	},
>>>>>> +};
>>>>>> +
>>>>>> +static const struct vgxy61_mode_info vgx761_mode_data[] = {
>>>>>> +	{
>>>>>> +		.width = VGX761_WIDTH,
>>>>>> +		.height = VGX761_HEIGHT,
>>>>>> +		.bin_mode = BIN_MODE_NORMAL,
>>>>>> +		.crop = {
>>>>>> +			.left = 0,
>>>>>> +			.top = 0,
>>>>>> +			.width = VGX761_WIDTH,
>>>>>> +			.height = VGX761_HEIGHT,
>>>>>> +		},
>>>>>> +	},
>>>>>> +	{
>>>>>> +		.width = 1920,
>>>>>> +		.height = 1080,
>>>>>> +		.bin_mode = BIN_MODE_NORMAL,
>>>>>> +		.crop = {
>>>>>> +			.left = 12,
>>>>>> +			.top = 62,
>>>>>> +			.width = 1920,
>>>>>> +			.height = 1080,
>>>>>> +		},
>>>>>> +	},
>>>>>> +	{
>>>>>> +		.width = 1280,
>>>>>> +		.height = 720,
>>>>>> +		.bin_mode = BIN_MODE_NORMAL,
>>>>>> +		.crop = {
>>>>>> +			.left = 332,
>>>>>> +			.top = 242,
>>>>>> +			.width = 1280,
>>>>>> +			.height = 720,
>>>>>> +		},
>>>>>> +	},
>>>>>> +	{
>>>>>> +		.width = 640,
>>>>>> +		.height = 480,
>>>>>> +		.bin_mode = BIN_MODE_DIGITAL_X2,
>>>>>> +		.crop = {
>>>>>> +			.left = 332,
>>>>>> +			.top = 122,
>>>>>> +			.width = 1280,
>>>>>> +			.height = 960,
>>>>>> +		},
>>>>>> +	},
>>>>>> +	{
>>>>>> +		.width = 320,
>>>>>> +		.height = 240,
>>>>>> +		.bin_mode = BIN_MODE_DIGITAL_X4,
>>>>>> +		.crop = {
>>>>>> +			.left = 332,
>>>>>> +			.top = 122,
>>>>>> +			.width = 1280,
>>>>>> +			.height = 960,
>>>>>> +		},
>>>>>> +	},
>>>>>> +};
>>>>>> +
>>>>>> +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 id;
>>>>>> +	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 v4l2_ctrl_handler ctrl_handler;
>>>>>> +	struct v4l2_ctrl *pixel_rate_ctrl;
>>>>>> +	struct v4l2_ctrl *expo_ctrl;
>>>>>> +	struct v4l2_ctrl *vblank_ctrl;
>>>>>> +	struct v4l2_ctrl *vflip_ctrl;
>>>>>> +	struct v4l2_ctrl *hflip_ctrl;
>>>>>> +	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;
>>>>>> +	struct v4l2_fract frame_interval;
>>>>>
>>>>> This is not used. Please check all other fields too.
>>>>>
>>>>>> +	bool hflip;
>>>>>> +	bool vflip;
>>>>>> +	enum hdr hdr;
>>>>>> +	int expo_long;
>>>>>> +	int expo_short;
>>>>>> +	int expo_max;
>>>>>> +	int expo_min;
>>>>>> +	int vblank;
>>>>>> +	int vblank_min;
>>>>>> +	u16 frame_length;
>>>>>> +};
>>>>>> +
>>>>>> +static u8 get_bpp_by_code(__u32 code)
>>>>>> +{
>>>>>> +	switch (code) {
>>>>>> +	case MEDIA_BUS_FMT_Y8_1X8:
>>>>>> +		return 8;
>>>>>> +	case MEDIA_BUS_FMT_Y10_1X10:
>>>>>> +		return 10;
>>>>>> +	case MEDIA_BUS_FMT_Y12_1X12:
>>>>>> +		return 12;
>>>>>> +	case MEDIA_BUS_FMT_Y14_1X14:
>>>>>> +		return 14;
>>>>>> +	case MEDIA_BUS_FMT_Y16_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_Y8_1X8:
>>>>>> +		return MIPI_CSI2_DT_RAW8;
>>>>>> +	case MEDIA_BUS_FMT_Y10_1X10:
>>>>>> +		return MIPI_CSI2_DT_RAW10;
>>>>>> +	case MEDIA_BUS_FMT_Y12_1X12:
>>>>>> +		return MIPI_CSI2_DT_RAW12;
>>>>>> +	case MEDIA_BUS_FMT_Y14_1X14:
>>>>>> +		return MIPI_CSI2_DT_RAW14;
>>>>>> +	case MEDIA_BUS_FMT_Y16_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;
>>>>>> +	}
>>>>>> +}
>>>>>
>>>>> I'd put all this data in a format description structure, and store that
>>>>> in the vgxy61_supported_codes array.
>>>>>
>>>>>> +
>>>>>> +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, ctrl_handler)->sd;
>>>>>> +}
>>>>>> +
>>>>>> +static int get_chunk_size(struct vgxy61_dev *sensor)
>>>>>> +{
>>>>>> +	struct i2c_adapter *adapter = sensor->i2c_client->adapter;
>>>>>> +	int max_write_len = VGXY61_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, VGXY61_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[VGXY61_WRITE_MULTIPLE_CHUNK_MAX + 2];
>>>>>> +	int i;
>>>>>> +	int ret;
>>>>>> +
>>>>>> +	if (len > VGXY61_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));
>>>>>> +}
>>>>>
>>>>> You could possibly simplify handling of registers of different sizes by
>>>>> encoding the size in the register address macros. See
>>>>> https://gitlab.com/ideasonboard/nxp/linux/-/blob/pinchartl/v5.18/sensors/imx/drivers/media/i2c/imx296.c
>>>>> for an example.
>>>>
>>>> That sounds good, thank you. I will look into this.
>>>
>>> On the topic of register writes, you may also want to consider the *err
>>> argument to imx296_write(), it simplifies error handling (see how errors
>>> are handled in imx296_setup(), compared to checking the return value of
>>> every single write call).
>>>
>>>>>> +
>>>>>> +static int vgxy61_poll_reg(struct vgxy61_dev *sensor, u16 reg, u8 poll_val, int timeout_ms)
>>>>>> +{
>>>>>> +	const int loop_delay_ms = 10;
>>>>>> +	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, int timeout_ms)
>>>>>> +{
>>>>>> +	return vgxy61_poll_reg(sensor, DEVICE_SYSTEM_FSM, state, timeout_ms);
>>>>>> +}
>>>>>> +
>>>>>> +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 = sensor->current_mode->crop.height / sensor->current_mode->height;
>>>>>> +	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;
>>>>>
>>>>> Is there a risk those writes may be applied to different frames ?
>>>>
>>>> Yes, but it does not matter for the sensor. Are you thinking of an
>>>> issue about user space applications for instance?
>>>
>>> Yes, if the writes cross frame boundaries, we could have a frame with a
>>> weird exposure time, which could confuse ISP algorithms.
>>>
>>> Does the sensor support CSI-2 embedded data ?
>>
>> It does, and embed the exposures in it. So I guess the ISP algorithm
>> could check these and react accordingly.
> 
> For ISPs that can capture embedded data, that sounds good.
> 
>> As a side note the short exposure is only used for HDR modes, so as
>> long as the user is running in SDR there is no impact at all.
> 
> That can help.
> 
> I suppose we can improve this further later if/when needed. I'm just
> worried it could lead to issues that are difficult to debug.
> 
>>>>>> +
>>>>>> +	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)
>>>>>
>>>>> The function sets the frame length, not the frame rate.
>>>>>
>>>>>> +{
>>>>>> +	return vgxy61_write_reg16(sensor, DEVICE_FRAME_LENGTH, sensor->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, VGXY61_TIMEOUT_MS);
>>>>>> +}
>>>>>> +
>>>>>> +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 */
>>>>>> +	mode = v4l2_find_nearest_size(sensor->sensor_modes, sensor->sensor_modes_nb,
>>>>>> +				      width, height, fmt->width, fmt->height);
>>>>>> +	if (new_mode)
>>>>>> +		*new_mode = mode;
>>>>>> +
>>>>>> +	fmt->code = vgxy61_supported_codes[index];
>>>>>> +	fmt->width = mode->width;
>>>>>> +	fmt->height = mode->height;
>>>>>> +	fmt->colorspace = V4L2_COLORSPACE_RAW;
>>>>>> +	fmt->field = V4L2_FIELD_NONE;
>>>>>> +	fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
>>>>>> +	fmt->quantization = V4L2_QUANTIZATION_DEFAULT;
>>>>>> +	fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT;
>>>>>> +
>>>>>> +	return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static int vgxy61_get_selection(struct v4l2_subdev *sd,
>>>>>> +				struct v4l2_subdev_state *sd_state,
>>>>>> +				struct v4l2_subdev_selection *sel)
>>>>>> +{
>>>>>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>>>> +
>>>>>> +	switch (sel->target) {
>>>>>> +	case V4L2_SEL_TGT_CROP:
>>>>>> +		sel->r = sensor->current_mode->crop;
>>>>>> +		return 0;
>>>>>> +	case V4L2_SEL_TGT_NATIVE_SIZE:
>>>>>> +	case V4L2_SEL_TGT_CROP_DEFAULT:
>>>>>> +	case V4L2_SEL_TGT_CROP_BOUNDS:
>>>>>> +		sel->r.top = 0;
>>>>>> +		sel->r.left = 0;
>>>>>> +		sel->r.width = sensor->sensor_width;
>>>>>> +		sel->r.height = sensor->sensor_height;
>>>>>> +		return 0;
>>>>>> +	}
>>>>>> +
>>>>>> +	return -EINVAL;
>>>>>> +}
>>>>>> +
>>>>>> +static int vgxy61_stream_enable(struct vgxy61_dev *sensor)
>>>>>> +{
>>>>>> +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd);
>>>>>> +	const struct v4l2_rect *crop = &sensor->current_mode->crop;
>>>>>> +	int ret;
>>>>>> +
>>>>>> +	ret = vgxy61_check_bw(sensor);
>>>>>> +	if (ret)
>>>>>> +		return ret;
>>>>>> +
>>>>>> +	ret = pm_runtime_get_sync(&client->dev);
>>>>>> +	if (ret < 0) {
>>>>>> +		pm_runtime_put_noidle(&client->dev);
>>>>>> +		return ret;
>>>>>> +	}
>>>>>> +
>>>>>> +	/* Configure sensor */
>>>>>> +	ret = vgxy61_write_reg(sensor, DEVICE_FORMAT_CTRL, get_bpp_by_code(sensor->fmt.code));
>>>>>> +	if (ret)
>>>>>> +		goto err_rpm_put;
>>>>>> +	ret = vgxy61_write_reg(sensor, DEVICE_OIF_ROI0_CTRL,
>>>>>> +			       get_data_type_by_code(sensor->fmt.code));
>>>>>> +	if (ret)
>>>>>> +		goto err_rpm_put;
>>>>>> +
>>>>>> +	ret = vgxy61_write_reg(sensor, DEVICE_READOUT_CTRL, sensor->current_mode->bin_mode);
>>>>>> +	if (ret)
>>>>>> +		goto err_rpm_put;
>>>>>> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_H, crop->left);
>>>>>> +	if (ret)
>>>>>> +		goto err_rpm_put;
>>>>>> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_H, crop->left + crop->width - 1);
>>>>>> +	if (ret)
>>>>>> +		goto err_rpm_put;
>>>>>> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_V, crop->top);
>>>>>> +	if (ret)
>>>>>> +		goto err_rpm_put;
>>>>>> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_V, crop->top + crop->height - 1);
>>>>>> +	if (ret)
>>>>>> +		goto err_rpm_put;
>>>>>> +
>>>>>> +	ret = set_frame_rate(sensor);
>>>>>> +	if (ret)
>>>>>> +		goto err_rpm_put;
>>>>>> +
>>>>>> +	ret = apply_exposure(sensor);
>>>>>> +	if (ret)
>>>>>> +		goto err_rpm_put;
>>>>>> +
>>>>>> +	/* Start streaming */
>>>>>> +	ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_START_STREAMING);
>>>>>> +	if (ret)
>>>>>> +		goto err_rpm_put;
>>>>>> +
>>>>>> +	ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST, VGXY61_TIMEOUT_MS);
>>>>>> +	if (ret)
>>>>>> +		goto err_rpm_put;
>>>>>> +
>>>>>> +	ret = vgxy61_wait_state(sensor, STREAMING, VGXY61_TIMEOUT_MS);
>>>>>> +	if (ret)
>>>>>> +		goto err_rpm_put;
>>>>>> +
>>>>>> +	/* vflip and hflip cannot change during streaming */
>>>>>> +	__v4l2_ctrl_grab(sensor->vflip_ctrl, true);
>>>>>> +	__v4l2_ctrl_grab(sensor->hflip_ctrl, true);
>>>>>> +
>>>>>> +	return 0;
>>>>>> +
>>>>>> +err_rpm_put:
>>>>>> +	pm_runtime_put(&client->dev);
>>>>>> +	return ret;
>>>>>> +}
>>>>>> +
>>>>>> +static int vgxy61_stream_disable(struct vgxy61_dev *sensor)
>>>>>> +{
>>>>>> +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd);
>>>>>> +	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, 2000);
>>>>>> +	if (ret)
>>>>>> +		return ret;
>>>>>> +
>>>>>> +	ret = vgxy61_wait_state(sensor, SW_STBY, VGXY61_TIMEOUT_MS);
>>>>>> +	if (ret)
>>>>>> +		return ret;
>>>>>> +
>>>>>> +	__v4l2_ctrl_grab(sensor->vflip_ctrl, true);
>>>>>> +	__v4l2_ctrl_grab(sensor->hflip_ctrl, true);
>>>>>> +
>>>>>> +	pm_runtime_put(&client->dev);
>>>>>> +
>>>>>> +	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;
>>>>>
>>>>> This should never happen. If you want to be on the safe side and keep
>>>>> the check, I'd turn it into a WARN_ON().
>>>>
>>>> You are right, I will remove this.
>>>>
>>>>>> +
>>>>>> +	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_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);q
>>>>>> +
>>>>>> +	return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static int vgxy61_get_vblank_min(struct vgxy61_dev *sensor, enum hdr hdr)
>>>>>> +{
>>>>>> +	int min_vblank =  VGXY61_MIN_FRAME_LENGTH - sensor->current_mode->crop.height;
>>>>>> +	/* Ensure the first tule of thumb can't be negative */
>>>>>> +	int min_vblank_hdr =  VGXY61_MIN_EXPOSURE + sensor->rot_term + 1;
>>>>>> +
>>>>>> +	if (hdr != NO_HDR)
>>>>>> +		return max(min_vblank, min_vblank_hdr);
>>>>>> +	return min_vblank;
>>>>>> +}
>>>>>> +
>>>>>> +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_update_analog_gain(struct vgxy61_dev *sensor, u32 target)
>>>>>> +{
>>>>>> +	struct i2c_client *client = sensor->i2c_client;
>>>>>> +	int ret;
>>>>>> +
>>>>>> +	/* Apply gain */
>>>>>> +	ret = vgxy61_write_reg(sensor, DEVICE_ANALOG_GAIN, target);
>>>>>> +	if (ret)
>>>>>> +		return ret;
>>>>>> +
>>>>>> +	dev_dbg(&client->dev, "Set analog gain: 0x%04x\n", target);
>>>>>> +
>>>>>> +	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;
>>>>>
>>>>> Macros would be nice instead of magic numbers. Same where applicable
>>>>> below.
>>>>>
>>>>>> +	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};
>>>>>
>>>>> Make this static const.
>>>>>
>>>>>> +	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 < VGXY61_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 vgxy61_get_expo_long_max(struct vgxy61_dev *sensor, int short_expo_ratio)
>>>>>> +{
>>>>>> +	int first_rot_max_expo, second_rot_max_expo, third_rot_max_expo;
>>>>>> +
>>>>>> +	/* Apply sensor rule of thumbs */
>>>>>> +	/* Avoid bad pixel line at the botom of the image */
>>>>>> +	first_rot_max_expo =
>>>>>> +		((sensor->frame_length - sensor->current_mode->crop.height - sensor->rot_term) *
>>>>>> +		short_expo_ratio) - 1;
>>>>>> +
>>>>>> +	/* Avoid sensor crash */
>>>>>
>>>>> How so ?
>>>>
>>>> This specific rule guarantees the framelength < expo_time. If this is
>>>> not the case then the sensor will crash.
>>>>
>>>> I will add a bit more comment for this, and for other rules also.
>>>>
>>>>>> +	second_rot_max_expo =
>>>>>> +		(((sensor->frame_length - VGXY61_EXPOS_ROT_TERM) * short_expo_ratio) /
>>>>>> +		(short_expo_ratio + 1)) - 1;
>>>>>> +
>>>>>> +	/* Avoid sensor crash */
>>>>>> +	third_rot_max_expo = (sensor->frame_length / 71) * short_expo_ratio;
>>>>>> +
>>>>>> +	/* Take the minimum from all rules */
>>>>>> +	return min(min(first_rot_max_expo, second_rot_max_expo), third_rot_max_expo);
>>>>>> +}
>>>>>> +
>>>>>> +static int vgxy61_update_exposure(struct vgxy61_dev *sensor, int new_expo_long, enum hdr hdr)
>>>>>> +{
>>>>>> +	struct i2c_client *client = sensor->i2c_client;
>>>>>> +	int new_expo_short = 0;
>>>>>> +	int expo_short_max = 0;
>>>>>> +	int expo_long_min = VGXY61_MIN_EXPOSURE;
>>>>>> +	int expo_long_max;
>>>>>> +
>>>>>> +	/* Compute short exposure according to hdr mode and long exposure */
>>>>>> +	switch (hdr) {
>>>>>> +	case HDR_LINEAR:
>>>>>> +		/* Take ratio into account for minimal exposures in HDR_LINEAR */
>>>>>> +		expo_long_min = VGXY61_MIN_EXPOSURE * VGXY61_HDR_LINEAR_RATIO;
>>>>>> +		new_expo_long = max(expo_long_min, new_expo_long);
>>>>>> +
>>>>>> +		expo_long_max = vgxy61_get_expo_long_max(sensor, VGXY61_HDR_LINEAR_RATIO);
>>>>>> +		expo_short_max =
>>>>>> +			(expo_long_max + (VGXY61_HDR_LINEAR_RATIO / 2)) / VGXY61_HDR_LINEAR_RATIO;
>>>>>> +		new_expo_short =
>>>>>> +			(new_expo_long + (VGXY61_HDR_LINEAR_RATIO / 2)) / VGXY61_HDR_LINEAR_RATIO;
>>>>>> +		break;
>>>>>> +	case HDR_SUB:
>>>>>> +		new_expo_long = max(expo_long_min, new_expo_long);
>>>>>> +
>>>>>> +		expo_long_max = vgxy61_get_expo_long_max(sensor, 1);
>>>>>> +		/* Short and long are the same in HDR_SUB */
>>>>>> +		expo_short_max = expo_long_max;
>>>>>> +		new_expo_short = new_expo_long;
>>>>>> +		break;
>>>>>> +	case NO_HDR:
>>>>>> +		new_expo_long = max(expo_long_min, new_expo_long);
>>>>>> +
>>>>>> +		/*
>>>>>> +		 * As short expo is 0 here, only the second rule of thumb applies, see
>>>>>> +		 * vgxy61_get_expo_long_max for more
>>>>>> +		 */
>>>>>> +		expo_long_max = sensor->frame_length - VGXY61_EXPOS_ROT_TERM;
>>>>>> +		break;
>>>>>> +	default:
>>>>>> +		/* Should never happen */
>>>>>> +		WARN_ON(true);
>>>>>> +		break;
>>>>>> +	}
>>>>>> +
>>>>>> +	/* If this happens, something is wrong with formulas */
>>>>>> +	WARN_ON(expo_long_min > expo_long_max);
>>>>>> +
>>>>>> +	if (new_expo_long > expo_long_max) {
>>>>>> +		dev_warn(&client->dev, "Exposure %d too high, clamping to %d\n",
>>>>>> +			 new_expo_long, expo_long_max);
>>>>>> +		new_expo_long = expo_long_max;
>>>>>> +		new_expo_short = expo_short_max;
>>>>>> +	}
>>>>>> +
>>>>>> +	dev_dbg(&client->dev, "frame_length %d, new_expo_long %d, new_expo_short %d\n",
>>>>>> +		sensor->frame_length, new_expo_long, new_expo_short);
>>>>>> +	dev_dbg(&client->dev, "expo_long_min %d, expo_long_max %d\n",
>>>>>> +		expo_long_min, expo_long_max);
>>>>>> +
>>>>>> +	/* Apply exposure */
>>>>>> +	sensor->expo_long = new_expo_long;
>>>>>> +	sensor->expo_short = new_expo_short;
>>>>>> +	sensor->expo_max = expo_long_max;
>>>>>> +	sensor->expo_min = expo_long_min;
>>>>>> +
>>>>>> +	if (sensor->streaming)
>>>>>> +		return apply_exposure(sensor);
>>>>>> +	return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static int vgxy61_update_vblank(struct vgxy61_dev *sensor, int vblank, int hdr)
>>>>>> +{
>>>>>> +	int ret;
>>>>>> +
>>>>>> +	sensor->vblank_min = vgxy61_get_vblank_min(sensor, hdr);
>>>>>> +	sensor->vblank = max(sensor->vblank_min, vblank);
>>>>>> +	sensor->frame_length = sensor->current_mode->crop.height + sensor->vblank;
>>>>>> +
>>>>>> +	/* Update exposure according to vblank */
>>>>>> +	ret = vgxy61_update_exposure(sensor, sensor->expo_long, hdr);
>>>>>> +	if (ret)
>>>>>> +		return ret;
>>>>>> +
>>>>>> +	if (sensor->streaming)
>>>>>> +		return set_frame_rate(sensor);
>>>>>> +	return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static int vgxy61_update_hdr(struct vgxy61_dev *sensor, u32 index)
>>>>>> +{
>>>>>> +	const u8 index2val[] = {0x1, 0x4, 0xa};
>>>>>> +	int ret;
>>>>>> +
>>>>>> +	/*
>>>>>> +	 * vblank and short exposure change 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_vblank(sensor, sensor->vblank, index);
>>>>>> +	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_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);
>>>>>> +		*fmt = format->format;
>>>>>> +	} else if (sensor->current_mode != new_mode || sensor->fmt.code != format->format.code) {
>>>>>> +		fmt = &sensor->fmt;
>>>>>> +		*fmt = format->format;
>>>>>> +
>>>>>> +		sensor->current_mode = new_mode;
>>>>>> +
>>>>>> +		/* Reset vblank and framelength to default */
>>>>>> +		ret = vgxy61_update_vblank(sensor,
>>>>>> +					   VGXY61_FRAME_LENGTH_DEF -
>>>>>> +					   sensor->current_mode->crop.height,
>>>>>> +					   sensor->hdr);
>>>>>> +
>>>>>> +		/* Update controls to reflect new mode */
>>>>>> +		__v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate_ctrl, get_pixel_rate(sensor));
>>>>>> +		__v4l2_ctrl_modify_range(sensor->vblank_ctrl, sensor->vblank_min,
>>>>>> +					 0xffff - sensor->current_mode->crop.height, 1,
>>>>>> +					 sensor->vblank);
>>>>>> +		__v4l2_ctrl_s_ctrl(sensor->vblank_ctrl, sensor->vblank);
>>>>>> +		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
>>>>>> +					 sensor->expo_max, 1,
>>>>>> +					 sensor->expo_long);
>>>>>> +	}
>>>>>> +
>>>>>> +out:
>>>>>> +	mutex_unlock(&sensor->lock);
>>>>>> +
>>>>>> +	return ret;
>>>>>> +}
>>>>>> +
>>>>>> +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);
>>>>>
>>>>> Can't runtime PM be disabled here ?
>>>>
>>>> You're absolutely right. This works as of now because I guess my
>>>> regulators are used for something else and therefore do not go down,
>>>> but should not.
>>>>
>>>> This is tricky as I don't want to power up (and therefore patch) the
>>>> sensor every time I want to see the temperature, but maybe I won't
>>>> have the choice.
>>>>
>>>> For example every time I stop streaming in qv4l2, the temperature is read. This would result in this:
>>>> 	power off -> get_temp -> power on (+patch) -> read temp -> power off
>>>> Maybe I can just remove the temperature reading while off? Any other idea?
>>>
>>> I think returning an error when the sensor is off is fine. The main use
>>> cases for the temperature sensor are related to imaging.
>>>
>>>> Anyway this will depend on the 2/5 patch discussion thread result.
>>>>
>>>>>> +	if (ret)
>>>>>> +		return ret;
>>>>>> +	ret = vgxy61_poll_reg(sensor, DEVICE_STBY, STBY_NO_REQ, VGXY61_TIMEOUT_MS);
>>>>>> +	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);
>>>>>> +		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);
>>>>>> +		/* Update vblank and exposure controls to match new hdr */
>>>>>> +		__v4l2_ctrl_modify_range(sensor->vblank_ctrl, sensor->vblank_min,
>>>>>> +					 0xffff - sensor->current_mode->crop.height, 1,
>>>>>> +					 sensor->vblank);
>>>>>> +		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
>>>>>> +					 sensor->expo_max, 1, sensor->expo_long);
>>>>>> +		break;
>>>>>> +	case V4L2_CID_VBLANK:
>>>>>> +		ret = vgxy61_update_vblank(sensor, ctrl->val, sensor->hdr);
>>>>>> +		/* Update exposure control to match new vblank */
>>>>>> +		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
>>>>>> +					 sensor->expo_max, 1, sensor->expo_long);
>>>>>> +		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 ret;
>>>>>> +
>>>>>> +	switch (ctrl->id) {
>>>>>> +	case V4L2_CID_TEMPERATURE:
>>>>>> +		ret = vgxy61_get_temp(sensor, &ctrl->val);
>>>>>> +		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 int vgxy61_init_controls(struct vgxy61_dev *sensor)
>>>>>> +{
>>>>>> +	const struct v4l2_ctrl_ops *ops = &vgxy61_ctrl_ops;
>>>>>> +	struct v4l2_ctrl_handler *hdl = &sensor->ctrl_handler;
>>>>>> +	struct v4l2_ctrl *ctrl;
>>>>>> +	int ret;
>>>>>> +
>>>>>> +	v4l2_ctrl_handler_init(hdl, 16);
>>>>>> +	mutex_init(&sensor->lock);
>>>>>
>>>>> This should be initialized in probe(), it doesn't belong here.
>>>>>
>>>>>> +	/* We can use our own mutex for the ctrl lock */
>>>>>> +	hdl->lock = &sensor->lock;
>>>>>> +	/* Standard controls */
>>>>>> +	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, 0x1c, 1, 0x0);
>>>>>> +	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 0, 0xfff, 1, 256);
>>>>>> +	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_TEMPERATURE, -128, 128, 1, 0);
>>>>>> +	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);
>>>>>> +	v4l2_ctrl_new_custom(hdl, &vgxy61_hdr_ctrl, NULL);
>>>>>> +	ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HBLANK, 0, sensor->line_length, 1,
>>>>>> +				 sensor->line_length - sensor->current_mode->width);
>>>>>> +	ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>>>>>> +	/* Custom controls */
>>>>>> +	ctrl = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ, ARRAY_SIZE(link_freq) - 1, 0,
>>>>>> +				      link_freq);
>>>>>> +	ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>>>>>> +	/* Keep a pointer to these controls as we need to update them when setting the format */
>>>>>> +	sensor->pixel_rate_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1,
>>>>>> +						    get_pixel_rate(sensor));
>>>>>> +	sensor->pixel_rate_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>>>>>> +	sensor->expo_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, sensor->expo_min,
>>>>>> +					      sensor->expo_max, 1, sensor->expo_long);
>>>>>> +	sensor->vblank_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VBLANK, sensor->vblank_min,
>>>>>> +						0xffff - sensor->current_mode->crop.height, 1,
>>>>>> +						sensor->vblank);
>>>>>> +	sensor->vflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, sensor->vflip);
>>>>>> +	sensor->hflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, sensor->hflip);
>>>>>> +
>>>>>> +	if (hdl->error) {
>>>>>> +		ret = hdl->error;
>>>>>> +		goto free_ctrls;
>>>>>> +	}
>>>>>> +
>>>>>> +	sensor->sd.ctrl_handler = hdl;
>>>>>> +	return 0;
>>>>>> +
>>>>>> +free_ctrls:
>>>>>> +	v4l2_ctrl_handler_free(hdl);
>>>>>> +	mutex_destroy(&sensor->lock);
>>>>>> +	return ret;
>>>>>> +}
>>>>>> +
>>>>>> +static const struct v4l2_subdev_core_ops vgxy61_core_ops = {
>>>>>> +};
>>>>>
>>>>> You can drop this.
>>>>>
>>>>>> +
>>>>>> +static const struct v4l2_subdev_video_ops vgxy61_video_ops = {
>>>>>> +	.s_stream = vgxy61_s_stream,
>>>>>> +};
>>>>>> +
>>>>>> +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,
>>>>>> +	.get_selection = vgxy61_get_selection,
>>>>>> +	.enum_frame_size = vgxy61_enum_frame_size,
>>>>>> +};
>>>>>> +
>>>>>> +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[VGXY61_NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
>>>>>> +	u32 phy2log[VGXY61_NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
>>>>>> +	int polarities[VGXY61_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 < VGXY61_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;
>>>>>> +	/* 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, VGXY61_TIMEOUT_MS);
>>>>>> +	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_dbg(&client->dev, "Cut1 detected\n");
>>>>>> +		dev_err(&client->dev, "Cut1 not supported by this driver\n");
>>>>>> +		return -ENODEV;
>>>>>> +	case 0xB:
>>>>>> +		dev_dbg(&client->dev, "Cut2 detected\n");
>>>>>> +		return 0;
>>>>>> +	case 0xC:
>>>>>> +		dev_dbg(&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);
>>>>>> +	sensor->id = id;
>>>>>> +
>>>>>> +	ret = vgxy61_wait_state(sensor, SW_STBY, VGXY61_TIMEOUT_MS);
>>>>>> +	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;
>>>>>> +}
>>>>>> +
>>>>>> +/* Power/clock management functions */
>>>>>> +static int vgxy61_power_on(struct device *dev)
>>>>>> +{
>>>>>> +	struct i2c_client *client = to_i2c_client(dev);
>>>>>> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
>>>>>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>>>> +	int ret;
>>>>>> +
>>>>>> +	ret = vgxy61_get_regulators(sensor);
>>>>>> +	if (ret) {
>>>>>> +		dev_err(&client->dev, "failed to get regulators %d\n", ret);
>>>>>> +		return ret;
>>>>>> +	}
>>>>>> +
>>>>>> +	ret = regulator_bulk_enable(VGXY61_NUM_SUPPLIES, sensor->supplies);
>>>>>> +	if (ret) {
>>>>>> +		dev_err(&client->dev, "failed to enable regulators %d\n", ret);
>>>>>> +		return ret;
>>>>>> +	}
>>>>>> +
>>>>>> +	ret = clk_prepare_enable(sensor->xclk);
>>>>>> +	if (ret) {
>>>>>> +		dev_err(&client->dev, "failed to enable clock %d\n", ret);
>>>>>> +		goto disable_bulk;
>>>>>> +	}
>>>>>> +
>>>>>> +	/* 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);
>>>>>
>>>>> Do we really need to run detection every time the sensor is powered on ?
>>>>
>>>> Surely not, I will add a if (sensor->already_detected) guard for this
>>>> call to make it runs only once.
>>>
>>> It could also be done in probe().
>>>
>>>>>> +	if (ret) {
>>>>>> +		dev_err(&client->dev, "sensor detect failed %d\n", ret);
>>>>>> +		goto disable_clock;
>>>>>> +	}
>>>>>> +
>>>>>> +	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;
>>>>>> +	}
>>>>>> +
>>>>>> +	return 0;
>>>>>> +
>>>>>> +disable_clock:
>>>>>> +	clk_disable_unprepare(sensor->xclk);
>>>>>> +disable_bulk:
>>>>>> +	regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
>>>>>> +
>>>>>> +	return ret;
>>>>>> +}
>>>>>> +
>>>>>> +static int vgxy61_power_off(struct device *dev)
>>>>>> +{
>>>>>> +	struct i2c_client *client = to_i2c_client(dev);
>>>>>> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
>>>>>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>>>> +
>>>>>> +	clk_disable_unprepare(sensor->xclk);
>>>>>> +	regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
>>>>>> +	return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static int __maybe_unused vgxy61_suspend(struct device *dev)
>>>>>> +{
>>>>>> +	struct i2c_client *client = to_i2c_client(dev);
>>>>>> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
>>>>>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>>>> +
>>>>>> +	if (sensor->streaming)
>>>>>> +		vgxy61_stream_disable(sensor);
>>>>>
>>>>> This shoudn't happen. The sensor should be stopped by the driver on the
>>>>> receiver side during system suspend. Same for resume.
>>>>
>>>> Sorry I'm still learning the pm_runtime API and I'm not sure I got your point.
>>>> If the sensor is already stopped then I could just remove the whole suspend function? Same for resume.
>>>> I'm not sure when the suspend function is called by the framework.
>>>
>>> vgxy61_suspend() is called on system suspend. Disabling streaming here
>>> is problematic, as it's done independently from the CSI-2 receiver. It's
>>> even worse on resume, as if you resume the sensor first, the CSI-2 lanes
>>> will go to HS mode before the receiver is started, and the receiver will
>>> never be able to synchronized. That's why the CSI-2 receiver resume
>>> handler needs to restart streaming by calling the sensor .s_stream()
>>> operation. Same thing at suspend time. Most sensors will thus not need
>>> system suspend/resume handlers.
>>>
>>> This means that the CSI-2 receiver needs to be suspended before the
>>> sensor, and resumed after. You can achieve this with the device link
>>> API, calling device_link_add(csi2_dev, sensor_dev, DL_FLAG_STATELESS)
>>> in the CSI-2 receiver driver.
>>
>> Let me rephrase your comment just to make sure I got everything right.
>>
>> On system suspend, the CSI-2 receiver driver should already have
>> stopped the stream for me, so this is irrelevant.
>>
>> On resume, if by any chance I try to resume streaming before the CSI2
>> receiver is resuming things will go terribly wrong.
>>
>> So this means I should trust the CSI2 driver to have performed
>> device_link_add on its side, and can delete the vgxy61 resume and
>> suspend ops.
> 
> Correct.
> 
>> I have trouble understanding why my use case differs from the imx219
>> for instance, that afaik does the same thing.
> 
> Your use case doesn't differ, but we have lots of sensors drivers that
> have been developed over time while our understanding of power
> management evolved, and older drivers haven't been updated to the latest
> best practices. This results in lots of cargo-cult for new drivers too,
> it's not a great situation.
> 

I understand, thanks a lot for your explanations.

>>>>>> +
>>>>>> +	return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static int __maybe_unused vgxy61_resume(struct device *dev)
>>>>>> +{
>>>>>> +	struct i2c_client *client = to_i2c_client(dev);
>>>>>> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
>>>>>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>>>> +	int ret;
>>>>>> +
>>>>>> +	if (sensor->streaming) {
>>>>>> +		ret = vgxy61_stream_enable(sensor);
>>>>>> +		if (ret)
>>>>>> +			goto error;
>>>>>> +	}
>>>>>> +
>>>>>> +	return 0;
>>>>>> +
>>>>>> +error:
>>>>>> +	vgxy61_stream_disable(sensor);
>>>>>> +	sensor->streaming = false;
>>>>>> +
>>>>>> +	return ret;
>>>>>> +}
>>>>>> +
>>>>>> +static void vgxy61_set_default_format(struct vgxy61_dev *sensor)
>>>>>> +{
>>>>>> +	if (sensor->id == VG5761_MODEL_ID) {
>>>>>
>>>>> Would it make sense to have different compatible strings instead of
>>>>> detecting the version automatically ? Not only could it speed up probe
>>>>> if we could skip the detection, it would also allow not powering the
>>>>> sensor up at probe time, which may be required in some cases (for
>>>>> instance when a privacy LED is connected to the sensor power supply,
>>>>> flashing the LED at probe time can be very confusing for users).
>>>>
>>>> Your flashing LED example is a very good concern.
>>>>
>>>> The idea behind this is to ease the user life as he does not have to
>>>> know what model he is using.
>>>
>>> I agree with you, I've implemented the same in sensor drivers. One
>>> option may be to have a generic compatible string that would lead to
>>> model detection at probe time, and specific compatible strings that
>>> would skip it and avoid powering up the sensor. As long as we consider
>>> the requirements on the DT side and make sure to have a way forward
>>> without breaking backward compatibility, this can also be implemented
>>> later.
>>
>> This is a very good idea.
>>
>>>> For example we have several boards with either the VG5761 or VG5661
>>>> and are interchangeable without changing the device tree, which is
>>>> also better for us as it shrinks the device tree number to maintain.
>>>>
>>>> Also as a company we treat them the same way, hence the same driver.
>>>> I'm not hardly commited to keep this, but it indeed makes my life easier.
>>>>
>>>>>> +		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;
>>>>>> +	} else if (sensor->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;
>>>>>> +	} else {
>>>>>> +		/* Should never happen */
>>>>>> +		WARN_ON(true);
>>>>>> +	}
>>>>>> +	sensor->fmt.width = sensor->current_mode->width;
>>>>>> +	sensor->fmt.height = sensor->current_mode->height;
>>>>>> +	sensor->vblank = sensor->frame_length - sensor->current_mode->crop.height;
>>>>>> +}
>>>>>> +
>>>>>> +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_Y8_1X8;
>>>>>> +	sensor->fmt.colorspace = V4L2_COLORSPACE_RAW;
>>>>>> +	sensor->fmt.field = V4L2_FIELD_NONE;
>>>>>> +	sensor->fmt.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
>>>>>> +	sensor->fmt.quantization = V4L2_QUANTIZATION_DEFAULT;
>>>>>> +	sensor->fmt.xfer_func = V4L2_XFER_FUNC_DEFAULT;
>>>>>
>>>>> Please implement the subdev .init_cfg() operation, and call it here.
>>>>> There are several examples in existing drivers.
>>>>>
>>>>>> +	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;
>>>>>> +	sensor->frame_length = VGXY61_FRAME_LENGTH_DEF;
>>>>>> +
>>>>>> +	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, NULL);
>>>>>> +	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 < 6 * HZ_PER_MHZ || sensor->clk_freq > 27 * HZ_PER_MHZ) {
>>>>>> +		dev_err(dev, "Only 6Mhz-27Mhz clock range supported. provide %lu MHz\n",
>>>>>> +			sensor->clk_freq / HZ_PER_MHZ);
>>>>>> +		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;
>>>>>> +
>>>>>> +	/* Request optional reset pin */
>>>>>> +	sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
>>>>>> +
>>>>>> +	ret = vgxy61_power_on(dev);
>>>>>> +	if (ret)
>>>>>> +		return ret;
>>>>>> +
>>>>>> +	vgxy61_set_default_format(sensor);
>>>>>> +
>>>>>> +	/* HDR mode */
>>>>>> +	ret = vgxy61_update_hdr(sensor, sensor->hdr);
>>>>>> +	if (ret)
>>>>>> +		return ret;
>>>>>> +
>>>>>> +	ret = vgxy61_init_controls(sensor);
>>>>>> +	if (ret) {
>>>>>> +		dev_err(&client->dev, "controls initialization failed %d\n", ret);
>>>>>> +		goto error_power_off;
>>>>>> +	}
>>>>>> +
>>>>>> +	ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
>>>>>> +	if (ret) {
>>>>>> +		dev_err(&client->dev, "pads init failed %d\n", ret);
>>>>>> +		goto error_handler_free;
>>>>>> +	}
>>>>>> +
>>>>>> +	ret = v4l2_async_register_subdev(&sensor->sd);
>>>>>> +	if (ret) {
>>>>>> +		dev_err(&client->dev, "async subdev register failed %d\n", ret);
>>>>>> +		goto error_media_entity;
>>>>>> +	}
>>>>>> +
>>>>>> +	/* Enable runtime PM and turn off the device */
>>>>>> +	pm_runtime_set_active(dev);
>>>>>> +	pm_runtime_enable(dev);
>>>>>> +	pm_runtime_idle(dev);
>>>>>
>>>>> You should do this before registering the subdev, as it can be accessed
>>>>> from userspace right after being registered.
>>>>>
>>>>> Please also use auto-suspend to avoid long delays when stopping and
>>>>> restarting streaming quickly.
>>>>
>>>> I was not aware of autosuspend. Thanks a lot, I will look into this.
>>>>
>>>>>> +
>>>>>> +	dev_info(&client->dev, "vgxy61 probe successfully\n");
>>>>>
>>>>> I'd drop this, or turn it into a debug message.
>>>>>
>>>>>> +
>>>>>> +	return 0;
>>>>>> +
>>>>>> +error_media_entity:
>>>>>> +	media_entity_cleanup(&sensor->sd.entity);
>>>>>> +error_handler_free:
>>>>>> +	v4l2_ctrl_handler_free(sensor->sd.ctrl_handler);
>>>>>> +	mutex_destroy(&sensor->lock);
>>>>>> +error_power_off:
>>>>>> +	vgxy61_power_off(dev);
>>>>>> +
>>>>>> +	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);
>>>>>> +	mutex_destroy(&sensor->lock);
>>>>>> +	media_entity_cleanup(&sensor->sd.entity);
>>>>>> +
>>>>>> +	pm_runtime_disable(&client->dev);
>>>>>> +	if (!pm_runtime_status_suspended(&client->dev))
>>>>>> +		vgxy61_power_off(&client->dev);
>>>>>> +	pm_runtime_set_suspended(&client->dev);
>>>>>> +
>>>>>> +	return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static const struct of_device_id vgxy61_dt_ids[] = {
>>>>>> +	{ .compatible = "st,st-vgxy61" },
>>>>>> +	{ /* sentinel */ }
>>>>>> +};
>>>>>> +MODULE_DEVICE_TABLE(of, vgxy61_dt_ids);
>>>>>> +
>>>>>> +static const struct dev_pm_ops vgxy61_pm_ops = {
>>>>>> +	SET_SYSTEM_SLEEP_PM_OPS(vgxy61_suspend, vgxy61_resume)
>>>>>> +	SET_RUNTIME_PM_OPS(vgxy61_power_off, vgxy61_power_on, NULL)
>>>>>> +};
>>>>>> +
>>>>>> +static struct i2c_driver vgxy61_i2c_driver = {
>>>>>> +	.driver = {
>>>>>> +		.name  = "st-vgxy61",
>>>>>> +		.of_match_table = vgxy61_dt_ids,
>>>>>> +		.pm = &vgxy61_pm_ops,
>>>>>> +	},
>>>>>> +	.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");
> 

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

* Re: [PATCH v2 5/5] media: i2c: Add driver for ST VGXY61 camera sensor
  2022-04-22 14:40             ` Benjamin Mugnier
@ 2022-04-24 16:34               ` Laurent Pinchart
  2022-04-27 14:27                 ` Benjamin Mugnier
  0 siblings, 1 reply; 33+ messages in thread
From: Laurent Pinchart @ 2022-04-24 16:34 UTC (permalink / raw)
  To: Benjamin Mugnier
  Cc: linux-media, alain.volmat, hugues.fruchet, sylvain.petinot,
	dave.stevenson, sakari.ailus, kieran.bingham

Hi Benjamin,

On Fri, Apr 22, 2022 at 04:40:14PM +0200, Benjamin Mugnier wrote:
> On 22/04/2022 15:29, Laurent Pinchart wrote:
> > On Fri, Apr 22, 2022 at 10:44:13AM +0200, Benjamin Mugnier wrote:
> >> On 20/04/2022 18:48, Laurent Pinchart wrote:
> >>> On Wed, Apr 20, 2022 at 03:01:24PM +0200, Benjamin Mugnier wrote:
> >>>> On 15/04/2022 17:39, Laurent Pinchart wrote:
> >>>>> On Fri, Apr 15, 2022 at 01:18:45PM +0200, Benjamin Mugnier wrote:
> >>>>>> 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     |   10 +
> >>>>>>  drivers/media/i2c/Makefile    |    1 +
> >>>>>>  drivers/media/i2c/st-vgxy61.c | 1997 +++++++++++++++++++++++++++++++++
> >>>>>>  3 files changed, 2008 insertions(+)
> >>>>>>  create mode 100644 drivers/media/i2c/st-vgxy61.c
> >>>>>>
> >>>>>> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> >>>>>> index fae2baabb773..734f3e1130b0 100644
> >>>>>> --- a/drivers/media/i2c/Kconfig
> >>>>>> +++ b/drivers/media/i2c/Kconfig
> >>>>>> @@ -763,6 +763,16 @@ config VIDEO_VS6624
> >>>>>>  	  To compile this driver as a module, choose M here: the
> >>>>>>  	  module will be called vs6624.
> >>>>>>  
> >>>>>> +config VIDEO_ST_VGXY61
> >>>>>
> >>>>> Alphabetical order please.
> >>>>>
> >>>>>> +	tristate "ST VGXY61 sensor support"
> >>>>>> +	depends on OF && GPIOLIB && VIDEO_DEV && 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.
> >>>>>> +
> >>>>>>  source "drivers/media/i2c/ccs/Kconfig"
> >>>>>>  source "drivers/media/i2c/et8ek8/Kconfig"
> >>>>>>  source "drivers/media/i2c/m5mols/Kconfig"
> >>>>>> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> >>>>>> index 3e1696963e7f..7f2362228484 100644
> >>>>>> --- a/drivers/media/i2c/Makefile
> >>>>>> +++ b/drivers/media/i2c/Makefile
> >>>>>> @@ -116,6 +116,7 @@ obj-$(CONFIG_VIDEO_SAA7185) += saa7185.o
> >>>>>>  obj-$(CONFIG_VIDEO_SONY_BTF_MPX) += sony-btf-mpx.o
> >>>>>>  obj-$(CONFIG_VIDEO_SR030PC30) += sr030pc30.o
> >>>>>>  obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o
> >>>>>> +obj-$(CONFIG_VIDEO_ST_VGXY61) += st-vgxy61.o
> >>>>>>  obj-$(CONFIG_VIDEO_TC358743) += tc358743.o
> >>>>>>  obj-$(CONFIG_VIDEO_TDA1997X) += tda1997x.o
> >>>>>>  obj-$(CONFIG_VIDEO_TDA7432) += tda7432.o
> >>>>>> diff --git a/drivers/media/i2c/st-vgxy61.c b/drivers/media/i2c/st-vgxy61.c
> >>>>>> new file mode 100644
> >>>>>> index 000000000000..1219d278e648
> >>>>>> --- /dev/null
> >>>>>> +++ b/drivers/media/i2c/st-vgxy61.c
> >>>>>> @@ -0,0 +1,1997 @@
> >>>>>> +// 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/pm_runtime.h>
> >>>>>
> >>>>> Alphabetical order in headers too please.
> >>>>>
> >>>>>> +#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
> >>>>>
> >>>>> I don't think this deserves a macro, but if you want to keep it, you
> >>>>> should avoid generic names that may cause namespace clashes.
> >>>>>
> >>>>
> >>>> I don't make heavy usage of it so I will remove it.
> >>>>
> >>>>>> +
> >>>>>> +#define DEVICE_MODEL_ID_REG				0x0000
> >>>>>
> >>>>> Please prefix macros with a driver-specific prefix. Many of them have a
> >>>>> too generic name.
> >>>>>
> >>>>
> >>>> I will change this to VGXY61_REG_* for registers, and simply VGXY61_* for others.
> >>>>
> >>>>>> +#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 VGXY61_WRITE_MULTIPLE_CHUNK_MAX			16
> >>>>>> +#define VGXY61_NB_GPIOS					4
> >>>>>> +#define VGXY61_NB_POLARITIES				5
> >>>>>> +#define VGXY61_MIN_FRAME_LENGTH				1288
> >>>>>> +#define VGXY61_MIN_EXPOSURE				10
> >>>>>> +#define VGXY61_HDR_LINEAR_RATIO				10
> >>>>>> +#define VGXY61_TIMEOUT_MS				500
> >>>>>> +#define VGXY61_FRAME_LENGTH_DEF				1313
> >>>>>> +#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_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_STVGXY61_BASE + 0)
> >>>>>
> >>>>> This macro should have a device-specific name, should be moved to a
> >>>>> public header, and the control should be documented. I would split the
> >>>>> control documentation and macro definition to a separate patch.
> >>>>>
> >>>>
> >>>> I created two new files include/uapi/linux/st-vgxy61.h and
> >>>> Documentation/userspace-api/media/drivers/st-vgxy61.rst.
> >>>>
> >>>> I'm not sure what you mean by separating the documentation and
> >>>> definition. As there is a significant amount of code dependent on the
> >>>> HDR mode I will keep the public header in this patch, and create a new
> >>>> patch for the documentation. Tell me if there is any issue with this.
> >>>
> >>> I meant a patch that adds include/uapi/linux/st-vgxy61.h and
> >>> Documentation/userspace-api/media/drivers/st-vgxy61.rst separate from
> >>> the driver, but if it causes more issues than it helps review, you can
> >>> combine them in a single patch.
> >>
> >> Ok, I initially thought about moving the relevant st-vgxy61.c hdr code
> >> to a new patch, which will be quite complicated.
> >> Only definition and documentation make no issue.
> >>
> >>>>>> +
> >>>>>> +#define DEVICE_FWPATCH_REVISION_MAJOR			2
> >>>>>> +#define DEVICE_FWPATCH_REVISION_MINOR			0
> >>>>>> +#define DEVICE_FWPATCH_REVISION_MICRO			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
> >>>>>
> >>>>> The link frequencies should come from DT.
> >>>>
> >>>> This is a hard requirement for this sensor. This value can not be
> >>>> modified and must be 402MHz.
> >>>>
> >>>> I see the ov9734.c does something similar. Is adding this value to the
> >>>> device tree really mandatory?
> >>>
> >>> I suppose it's fine then. Sakari, what do you think ?
> >>>
> >>>>>> +};
> >>>>>> +
> >>>>>> +#define VGXY61_NUM_SUPPLIES		ARRAY_SIZE(vgxy61_supply_name)
> >>>>>> +
> >>>>>> +enum bin_mode {
> >>>>>
> >>>>> Same comment here, all enums and structures should use a driver-specific
> >>>>> prefix.
> >>>>>
> >>>>>> +	BIN_MODE_NORMAL,
> >>>>>> +	BIN_MODE_DIGITAL_X2,
> >>>>>> +	BIN_MODE_DIGITAL_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;
> >>>>>> +	struct v4l2_rect crop;
> >>>>>> +};
> >>>>>> +
> >>>>>> +static const u32 vgxy61_supported_codes[] = {
> >>>>>> +	MEDIA_BUS_FMT_Y8_1X8,
> >>>>>> +	MEDIA_BUS_FMT_Y10_1X10,
> >>>>>> +	MEDIA_BUS_FMT_Y12_1X12,
> >>>>>> +	MEDIA_BUS_FMT_Y14_1X14,
> >>>>>> +	MEDIA_BUS_FMT_Y16_1X16
> >>>>>> +};
> >>>>>> +
> >>>>>> +static const struct vgxy61_mode_info vgx661_mode_data[] = {
> >>>>>> +	{
> >>>>>> +		.width = VGX661_WIDTH,
> >>>>>> +		.height = VGX661_HEIGHT,
> >>>>>> +		.bin_mode = BIN_MODE_NORMAL,
> >>>>>> +		.crop = {
> >>>>>> +			.left = 0,
> >>>>>> +			.top = 0,
> >>>>>> +			.width = VGX661_WIDTH,
> >>>>>> +			.height = VGX661_HEIGHT,
> >>>>>> +		},
> >>>>>
> >>>>> It's very nice to see no hardcoded register values in the modes tables
> >>>>> :-) As the register values are computed by the driver, would it make
> >>>>> sense to go one step further and drop the modes completely ? Analog crop
> >>>>> and binning could then be selected freely from userspace.
> >>>>
> >>>> All credit goes to Dave for pointing this in the first review ;)
> >>>> As for the modes, in theory we could drop them completely, but this
> >>>> will require some extra testing as the sensor can't really handle
> >>>> every single mode freely.
> >>>>
> >>>> Could this be done in a second step?
> >>>
> >>> I'm a bit concerned it could then break userspace, if some applications
> >>> start depending on a fixed list of modes. How difficult do you think
> >>> this would be ?
> >>
> >> I mean if they use a fixed list, the upgrade will still allow them to
> >> use this list right? As it will implement a superset of the resolution
> >> list.
> >>
> >> Also if I understand correctly they will get this list dynamically
> >> from the get_fmt call, hence once the full support is added the
> >> application will update itself right?
> > 
> > The idea is that we could get rid of the modes list completely, and let
> > the application configure everything through the formats and selection
> > rectangles. Moving in that direction later may cause some regressions,
> > for instance an application may set a mode that is not supported, with
> > the driver picking the closest option now, and later it may get the
> > exact resolution it requested. It's hard to know if this could break
> > something.
> > 
> >> For the complexity I need to check various rules to make sure the
> >> sensor will not be in a bad state. On top of my head the X and Y must
> >> be even, binning must match exactly the format ratio, and certainly
> >> other stuff I have not been confronted to yet. And of course testing.
> > 
> > Those rules don't seem to be too complicated. Testing will likely be the
> > biggest time consumer :-)
> 
> I will give it go for v3 then. If I run into issues I could still
> remove the find_nearest_size call and return an error if the exact
> size is not supported, but appart from the time consumming part this
> should go well.

Note that the V4L2 subdev API doesn't return errors in that case, but
picks the closest option.

> >> Tell me what you think.
> >>
> >>>>>> +	},
> >>>>>> +	{
> >>>>>> +		.width = 1280,
> >>>>>> +		.height = 720,
> >>>>>> +		.bin_mode = BIN_MODE_NORMAL,
> >>>>>> +		.crop = {
> >>>>>> +			.left = 92,
> >>>>>> +			.top = 192,
> >>>>>> +			.width = 1280,
> >>>>>> +			.height = 720,
> >>>>>> +		},
> >>>>>> +	},
> >>>>>> +	{
> >>>>>> +		.width = 640,
> >>>>>> +		.height = 480,
> >>>>>> +		.bin_mode = BIN_MODE_DIGITAL_X2,
> >>>>>> +		.crop = {
> >>>>>> +			.left = 92,
> >>>>>> +			.top = 72,
> >>>>>> +			.width = 1280,
> >>>>>> +			.height = 960,
> >>>>>> +		},
> >>>>>> +	},
> >>>>>> +	{
> >>>>>> +		.width = 320,
> >>>>>> +		.height = 240,
> >>>>>> +		.bin_mode = BIN_MODE_DIGITAL_X4,
> >>>>>
> >>>>> Can the bin mode computed by dividing the crop width by the output width
> >>>>> instead of being stored here ?
> >>>>
> >>>> The sensor also supports analog subsampling on top of digital binning.
> >>>> I did not implement it in the driver but I made the choice to
> >>>> explicitly set the bin_mode for further development. For example the
> >>>> line above could be:
> >>>>
> >>>>   .bin_mode = BIN_MODE_ANALOG_X4
> >>>>
> >>>> I can compute the factor (X2/X4), but not the analog/digital mode.
> >>>> Hope this make sense.
> >>>
> >>> It thus indeed, but it then leads to the question of how to configure
> >>> skipping and binning separately. The CCS driver registers two subdevs to
> >>> make this possible. It increases the complexity, and I'm increasingly
> >>> thinking that we should extend the V4L2 API to allow configure all the
> >>> common parameters of camera sensors with a single subdev.
> >>>
> >>> Sakari, any opinion ?
> >>>
> >>>>>> +		.crop = {
> >>>>>> +			.left = 92,
> >>>>>> +			.top = 72,
> >>>>>> +			.width = 1280,
> >>>>>> +			.height = 960,
> >>>>>> +		},
> >>>>>> +	},
> >>>>>> +};
> >>>>>> +
> >>>>>> +static const struct vgxy61_mode_info vgx761_mode_data[] = {
> >>>>>> +	{
> >>>>>> +		.width = VGX761_WIDTH,
> >>>>>> +		.height = VGX761_HEIGHT,
> >>>>>> +		.bin_mode = BIN_MODE_NORMAL,
> >>>>>> +		.crop = {
> >>>>>> +			.left = 0,
> >>>>>> +			.top = 0,
> >>>>>> +			.width = VGX761_WIDTH,
> >>>>>> +			.height = VGX761_HEIGHT,
> >>>>>> +		},
> >>>>>> +	},
> >>>>>> +	{
> >>>>>> +		.width = 1920,
> >>>>>> +		.height = 1080,
> >>>>>> +		.bin_mode = BIN_MODE_NORMAL,
> >>>>>> +		.crop = {
> >>>>>> +			.left = 12,
> >>>>>> +			.top = 62,
> >>>>>> +			.width = 1920,
> >>>>>> +			.height = 1080,
> >>>>>> +		},
> >>>>>> +	},
> >>>>>> +	{
> >>>>>> +		.width = 1280,
> >>>>>> +		.height = 720,
> >>>>>> +		.bin_mode = BIN_MODE_NORMAL,
> >>>>>> +		.crop = {
> >>>>>> +			.left = 332,
> >>>>>> +			.top = 242,
> >>>>>> +			.width = 1280,
> >>>>>> +			.height = 720,
> >>>>>> +		},
> >>>>>> +	},
> >>>>>> +	{
> >>>>>> +		.width = 640,
> >>>>>> +		.height = 480,
> >>>>>> +		.bin_mode = BIN_MODE_DIGITAL_X2,
> >>>>>> +		.crop = {
> >>>>>> +			.left = 332,
> >>>>>> +			.top = 122,
> >>>>>> +			.width = 1280,
> >>>>>> +			.height = 960,
> >>>>>> +		},
> >>>>>> +	},
> >>>>>> +	{
> >>>>>> +		.width = 320,
> >>>>>> +		.height = 240,
> >>>>>> +		.bin_mode = BIN_MODE_DIGITAL_X4,
> >>>>>> +		.crop = {
> >>>>>> +			.left = 332,
> >>>>>> +			.top = 122,
> >>>>>> +			.width = 1280,
> >>>>>> +			.height = 960,
> >>>>>> +		},
> >>>>>> +	},
> >>>>>> +};
> >>>>>> +
> >>>>>> +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 id;
> >>>>>> +	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 v4l2_ctrl_handler ctrl_handler;
> >>>>>> +	struct v4l2_ctrl *pixel_rate_ctrl;
> >>>>>> +	struct v4l2_ctrl *expo_ctrl;
> >>>>>> +	struct v4l2_ctrl *vblank_ctrl;
> >>>>>> +	struct v4l2_ctrl *vflip_ctrl;
> >>>>>> +	struct v4l2_ctrl *hflip_ctrl;
> >>>>>> +	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;
> >>>>>> +	struct v4l2_fract frame_interval;
> >>>>>
> >>>>> This is not used. Please check all other fields too.
> >>>>>
> >>>>>> +	bool hflip;
> >>>>>> +	bool vflip;
> >>>>>> +	enum hdr hdr;
> >>>>>> +	int expo_long;
> >>>>>> +	int expo_short;
> >>>>>> +	int expo_max;
> >>>>>> +	int expo_min;
> >>>>>> +	int vblank;
> >>>>>> +	int vblank_min;
> >>>>>> +	u16 frame_length;
> >>>>>> +};
> >>>>>> +
> >>>>>> +static u8 get_bpp_by_code(__u32 code)
> >>>>>> +{
> >>>>>> +	switch (code) {
> >>>>>> +	case MEDIA_BUS_FMT_Y8_1X8:
> >>>>>> +		return 8;
> >>>>>> +	case MEDIA_BUS_FMT_Y10_1X10:
> >>>>>> +		return 10;
> >>>>>> +	case MEDIA_BUS_FMT_Y12_1X12:
> >>>>>> +		return 12;
> >>>>>> +	case MEDIA_BUS_FMT_Y14_1X14:
> >>>>>> +		return 14;
> >>>>>> +	case MEDIA_BUS_FMT_Y16_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_Y8_1X8:
> >>>>>> +		return MIPI_CSI2_DT_RAW8;
> >>>>>> +	case MEDIA_BUS_FMT_Y10_1X10:
> >>>>>> +		return MIPI_CSI2_DT_RAW10;
> >>>>>> +	case MEDIA_BUS_FMT_Y12_1X12:
> >>>>>> +		return MIPI_CSI2_DT_RAW12;
> >>>>>> +	case MEDIA_BUS_FMT_Y14_1X14:
> >>>>>> +		return MIPI_CSI2_DT_RAW14;
> >>>>>> +	case MEDIA_BUS_FMT_Y16_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;
> >>>>>> +	}
> >>>>>> +}
> >>>>>
> >>>>> I'd put all this data in a format description structure, and store that
> >>>>> in the vgxy61_supported_codes array.
> >>>>>
> >>>>>> +
> >>>>>> +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, ctrl_handler)->sd;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int get_chunk_size(struct vgxy61_dev *sensor)
> >>>>>> +{
> >>>>>> +	struct i2c_adapter *adapter = sensor->i2c_client->adapter;
> >>>>>> +	int max_write_len = VGXY61_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, VGXY61_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[VGXY61_WRITE_MULTIPLE_CHUNK_MAX + 2];
> >>>>>> +	int i;
> >>>>>> +	int ret;
> >>>>>> +
> >>>>>> +	if (len > VGXY61_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));
> >>>>>> +}
> >>>>>
> >>>>> You could possibly simplify handling of registers of different sizes by
> >>>>> encoding the size in the register address macros. See
> >>>>> https://gitlab.com/ideasonboard/nxp/linux/-/blob/pinchartl/v5.18/sensors/imx/drivers/media/i2c/imx296.c
> >>>>> for an example.
> >>>>
> >>>> That sounds good, thank you. I will look into this.
> >>>
> >>> On the topic of register writes, you may also want to consider the *err
> >>> argument to imx296_write(), it simplifies error handling (see how errors
> >>> are handled in imx296_setup(), compared to checking the return value of
> >>> every single write call).
> >>>
> >>>>>> +
> >>>>>> +static int vgxy61_poll_reg(struct vgxy61_dev *sensor, u16 reg, u8 poll_val, int timeout_ms)
> >>>>>> +{
> >>>>>> +	const int loop_delay_ms = 10;
> >>>>>> +	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, int timeout_ms)
> >>>>>> +{
> >>>>>> +	return vgxy61_poll_reg(sensor, DEVICE_SYSTEM_FSM, state, timeout_ms);
> >>>>>> +}
> >>>>>> +
> >>>>>> +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 = sensor->current_mode->crop.height / sensor->current_mode->height;
> >>>>>> +	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;
> >>>>>
> >>>>> Is there a risk those writes may be applied to different frames ?
> >>>>
> >>>> Yes, but it does not matter for the sensor. Are you thinking of an
> >>>> issue about user space applications for instance?
> >>>
> >>> Yes, if the writes cross frame boundaries, we could have a frame with a
> >>> weird exposure time, which could confuse ISP algorithms.
> >>>
> >>> Does the sensor support CSI-2 embedded data ?
> >>
> >> It does, and embed the exposures in it. So I guess the ISP algorithm
> >> could check these and react accordingly.
> > 
> > For ISPs that can capture embedded data, that sounds good.
> > 
> >> As a side note the short exposure is only used for HDR modes, so as
> >> long as the user is running in SDR there is no impact at all.
> > 
> > That can help.
> > 
> > I suppose we can improve this further later if/when needed. I'm just
> > worried it could lead to issues that are difficult to debug.
> > 
> >>>>>> +
> >>>>>> +	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)
> >>>>>
> >>>>> The function sets the frame length, not the frame rate.
> >>>>>
> >>>>>> +{
> >>>>>> +	return vgxy61_write_reg16(sensor, DEVICE_FRAME_LENGTH, sensor->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, VGXY61_TIMEOUT_MS);
> >>>>>> +}
> >>>>>> +
> >>>>>> +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 */
> >>>>>> +	mode = v4l2_find_nearest_size(sensor->sensor_modes, sensor->sensor_modes_nb,
> >>>>>> +				      width, height, fmt->width, fmt->height);
> >>>>>> +	if (new_mode)
> >>>>>> +		*new_mode = mode;
> >>>>>> +
> >>>>>> +	fmt->code = vgxy61_supported_codes[index];
> >>>>>> +	fmt->width = mode->width;
> >>>>>> +	fmt->height = mode->height;
> >>>>>> +	fmt->colorspace = V4L2_COLORSPACE_RAW;
> >>>>>> +	fmt->field = V4L2_FIELD_NONE;
> >>>>>> +	fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> >>>>>> +	fmt->quantization = V4L2_QUANTIZATION_DEFAULT;
> >>>>>> +	fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT;
> >>>>>> +
> >>>>>> +	return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int vgxy61_get_selection(struct v4l2_subdev *sd,
> >>>>>> +				struct v4l2_subdev_state *sd_state,
> >>>>>> +				struct v4l2_subdev_selection *sel)
> >>>>>> +{
> >>>>>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >>>>>> +
> >>>>>> +	switch (sel->target) {
> >>>>>> +	case V4L2_SEL_TGT_CROP:
> >>>>>> +		sel->r = sensor->current_mode->crop;
> >>>>>> +		return 0;
> >>>>>> +	case V4L2_SEL_TGT_NATIVE_SIZE:
> >>>>>> +	case V4L2_SEL_TGT_CROP_DEFAULT:
> >>>>>> +	case V4L2_SEL_TGT_CROP_BOUNDS:
> >>>>>> +		sel->r.top = 0;
> >>>>>> +		sel->r.left = 0;
> >>>>>> +		sel->r.width = sensor->sensor_width;
> >>>>>> +		sel->r.height = sensor->sensor_height;
> >>>>>> +		return 0;
> >>>>>> +	}
> >>>>>> +
> >>>>>> +	return -EINVAL;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int vgxy61_stream_enable(struct vgxy61_dev *sensor)
> >>>>>> +{
> >>>>>> +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd);
> >>>>>> +	const struct v4l2_rect *crop = &sensor->current_mode->crop;
> >>>>>> +	int ret;
> >>>>>> +
> >>>>>> +	ret = vgxy61_check_bw(sensor);
> >>>>>> +	if (ret)
> >>>>>> +		return ret;
> >>>>>> +
> >>>>>> +	ret = pm_runtime_get_sync(&client->dev);
> >>>>>> +	if (ret < 0) {
> >>>>>> +		pm_runtime_put_noidle(&client->dev);
> >>>>>> +		return ret;
> >>>>>> +	}
> >>>>>> +
> >>>>>> +	/* Configure sensor */
> >>>>>> +	ret = vgxy61_write_reg(sensor, DEVICE_FORMAT_CTRL, get_bpp_by_code(sensor->fmt.code));
> >>>>>> +	if (ret)
> >>>>>> +		goto err_rpm_put;
> >>>>>> +	ret = vgxy61_write_reg(sensor, DEVICE_OIF_ROI0_CTRL,
> >>>>>> +			       get_data_type_by_code(sensor->fmt.code));
> >>>>>> +	if (ret)
> >>>>>> +		goto err_rpm_put;
> >>>>>> +
> >>>>>> +	ret = vgxy61_write_reg(sensor, DEVICE_READOUT_CTRL, sensor->current_mode->bin_mode);
> >>>>>> +	if (ret)
> >>>>>> +		goto err_rpm_put;
> >>>>>> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_H, crop->left);
> >>>>>> +	if (ret)
> >>>>>> +		goto err_rpm_put;
> >>>>>> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_H, crop->left + crop->width - 1);
> >>>>>> +	if (ret)
> >>>>>> +		goto err_rpm_put;
> >>>>>> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_V, crop->top);
> >>>>>> +	if (ret)
> >>>>>> +		goto err_rpm_put;
> >>>>>> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_V, crop->top + crop->height - 1);
> >>>>>> +	if (ret)
> >>>>>> +		goto err_rpm_put;
> >>>>>> +
> >>>>>> +	ret = set_frame_rate(sensor);
> >>>>>> +	if (ret)
> >>>>>> +		goto err_rpm_put;
> >>>>>> +
> >>>>>> +	ret = apply_exposure(sensor);
> >>>>>> +	if (ret)
> >>>>>> +		goto err_rpm_put;
> >>>>>> +
> >>>>>> +	/* Start streaming */
> >>>>>> +	ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_START_STREAMING);
> >>>>>> +	if (ret)
> >>>>>> +		goto err_rpm_put;
> >>>>>> +
> >>>>>> +	ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST, VGXY61_TIMEOUT_MS);
> >>>>>> +	if (ret)
> >>>>>> +		goto err_rpm_put;
> >>>>>> +
> >>>>>> +	ret = vgxy61_wait_state(sensor, STREAMING, VGXY61_TIMEOUT_MS);
> >>>>>> +	if (ret)
> >>>>>> +		goto err_rpm_put;
> >>>>>> +
> >>>>>> +	/* vflip and hflip cannot change during streaming */
> >>>>>> +	__v4l2_ctrl_grab(sensor->vflip_ctrl, true);
> >>>>>> +	__v4l2_ctrl_grab(sensor->hflip_ctrl, true);
> >>>>>> +
> >>>>>> +	return 0;
> >>>>>> +
> >>>>>> +err_rpm_put:
> >>>>>> +	pm_runtime_put(&client->dev);
> >>>>>> +	return ret;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int vgxy61_stream_disable(struct vgxy61_dev *sensor)
> >>>>>> +{
> >>>>>> +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd);
> >>>>>> +	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, 2000);
> >>>>>> +	if (ret)
> >>>>>> +		return ret;
> >>>>>> +
> >>>>>> +	ret = vgxy61_wait_state(sensor, SW_STBY, VGXY61_TIMEOUT_MS);
> >>>>>> +	if (ret)
> >>>>>> +		return ret;
> >>>>>> +
> >>>>>> +	__v4l2_ctrl_grab(sensor->vflip_ctrl, true);
> >>>>>> +	__v4l2_ctrl_grab(sensor->hflip_ctrl, true);
> >>>>>> +
> >>>>>> +	pm_runtime_put(&client->dev);
> >>>>>> +
> >>>>>> +	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;
> >>>>>
> >>>>> This should never happen. If you want to be on the safe side and keep
> >>>>> the check, I'd turn it into a WARN_ON().
> >>>>
> >>>> You are right, I will remove this.
> >>>>
> >>>>>> +
> >>>>>> +	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_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);q
> >>>>>> +
> >>>>>> +	return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int vgxy61_get_vblank_min(struct vgxy61_dev *sensor, enum hdr hdr)
> >>>>>> +{
> >>>>>> +	int min_vblank =  VGXY61_MIN_FRAME_LENGTH - sensor->current_mode->crop.height;
> >>>>>> +	/* Ensure the first tule of thumb can't be negative */
> >>>>>> +	int min_vblank_hdr =  VGXY61_MIN_EXPOSURE + sensor->rot_term + 1;
> >>>>>> +
> >>>>>> +	if (hdr != NO_HDR)
> >>>>>> +		return max(min_vblank, min_vblank_hdr);
> >>>>>> +	return min_vblank;
> >>>>>> +}
> >>>>>> +
> >>>>>> +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_update_analog_gain(struct vgxy61_dev *sensor, u32 target)
> >>>>>> +{
> >>>>>> +	struct i2c_client *client = sensor->i2c_client;
> >>>>>> +	int ret;
> >>>>>> +
> >>>>>> +	/* Apply gain */
> >>>>>> +	ret = vgxy61_write_reg(sensor, DEVICE_ANALOG_GAIN, target);
> >>>>>> +	if (ret)
> >>>>>> +		return ret;
> >>>>>> +
> >>>>>> +	dev_dbg(&client->dev, "Set analog gain: 0x%04x\n", target);
> >>>>>> +
> >>>>>> +	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;
> >>>>>
> >>>>> Macros would be nice instead of magic numbers. Same where applicable
> >>>>> below.
> >>>>>
> >>>>>> +	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};
> >>>>>
> >>>>> Make this static const.
> >>>>>
> >>>>>> +	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 < VGXY61_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 vgxy61_get_expo_long_max(struct vgxy61_dev *sensor, int short_expo_ratio)
> >>>>>> +{
> >>>>>> +	int first_rot_max_expo, second_rot_max_expo, third_rot_max_expo;
> >>>>>> +
> >>>>>> +	/* Apply sensor rule of thumbs */
> >>>>>> +	/* Avoid bad pixel line at the botom of the image */
> >>>>>> +	first_rot_max_expo =
> >>>>>> +		((sensor->frame_length - sensor->current_mode->crop.height - sensor->rot_term) *
> >>>>>> +		short_expo_ratio) - 1;
> >>>>>> +
> >>>>>> +	/* Avoid sensor crash */
> >>>>>
> >>>>> How so ?
> >>>>
> >>>> This specific rule guarantees the framelength < expo_time. If this is
> >>>> not the case then the sensor will crash.
> >>>>
> >>>> I will add a bit more comment for this, and for other rules also.
> >>>>
> >>>>>> +	second_rot_max_expo =
> >>>>>> +		(((sensor->frame_length - VGXY61_EXPOS_ROT_TERM) * short_expo_ratio) /
> >>>>>> +		(short_expo_ratio + 1)) - 1;
> >>>>>> +
> >>>>>> +	/* Avoid sensor crash */
> >>>>>> +	third_rot_max_expo = (sensor->frame_length / 71) * short_expo_ratio;
> >>>>>> +
> >>>>>> +	/* Take the minimum from all rules */
> >>>>>> +	return min(min(first_rot_max_expo, second_rot_max_expo), third_rot_max_expo);
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int vgxy61_update_exposure(struct vgxy61_dev *sensor, int new_expo_long, enum hdr hdr)
> >>>>>> +{
> >>>>>> +	struct i2c_client *client = sensor->i2c_client;
> >>>>>> +	int new_expo_short = 0;
> >>>>>> +	int expo_short_max = 0;
> >>>>>> +	int expo_long_min = VGXY61_MIN_EXPOSURE;
> >>>>>> +	int expo_long_max;
> >>>>>> +
> >>>>>> +	/* Compute short exposure according to hdr mode and long exposure */
> >>>>>> +	switch (hdr) {
> >>>>>> +	case HDR_LINEAR:
> >>>>>> +		/* Take ratio into account for minimal exposures in HDR_LINEAR */
> >>>>>> +		expo_long_min = VGXY61_MIN_EXPOSURE * VGXY61_HDR_LINEAR_RATIO;
> >>>>>> +		new_expo_long = max(expo_long_min, new_expo_long);
> >>>>>> +
> >>>>>> +		expo_long_max = vgxy61_get_expo_long_max(sensor, VGXY61_HDR_LINEAR_RATIO);
> >>>>>> +		expo_short_max =
> >>>>>> +			(expo_long_max + (VGXY61_HDR_LINEAR_RATIO / 2)) / VGXY61_HDR_LINEAR_RATIO;
> >>>>>> +		new_expo_short =
> >>>>>> +			(new_expo_long + (VGXY61_HDR_LINEAR_RATIO / 2)) / VGXY61_HDR_LINEAR_RATIO;
> >>>>>> +		break;
> >>>>>> +	case HDR_SUB:
> >>>>>> +		new_expo_long = max(expo_long_min, new_expo_long);
> >>>>>> +
> >>>>>> +		expo_long_max = vgxy61_get_expo_long_max(sensor, 1);
> >>>>>> +		/* Short and long are the same in HDR_SUB */
> >>>>>> +		expo_short_max = expo_long_max;
> >>>>>> +		new_expo_short = new_expo_long;
> >>>>>> +		break;
> >>>>>> +	case NO_HDR:
> >>>>>> +		new_expo_long = max(expo_long_min, new_expo_long);
> >>>>>> +
> >>>>>> +		/*
> >>>>>> +		 * As short expo is 0 here, only the second rule of thumb applies, see
> >>>>>> +		 * vgxy61_get_expo_long_max for more
> >>>>>> +		 */
> >>>>>> +		expo_long_max = sensor->frame_length - VGXY61_EXPOS_ROT_TERM;
> >>>>>> +		break;
> >>>>>> +	default:
> >>>>>> +		/* Should never happen */
> >>>>>> +		WARN_ON(true);
> >>>>>> +		break;
> >>>>>> +	}
> >>>>>> +
> >>>>>> +	/* If this happens, something is wrong with formulas */
> >>>>>> +	WARN_ON(expo_long_min > expo_long_max);
> >>>>>> +
> >>>>>> +	if (new_expo_long > expo_long_max) {
> >>>>>> +		dev_warn(&client->dev, "Exposure %d too high, clamping to %d\n",
> >>>>>> +			 new_expo_long, expo_long_max);
> >>>>>> +		new_expo_long = expo_long_max;
> >>>>>> +		new_expo_short = expo_short_max;
> >>>>>> +	}
> >>>>>> +
> >>>>>> +	dev_dbg(&client->dev, "frame_length %d, new_expo_long %d, new_expo_short %d\n",
> >>>>>> +		sensor->frame_length, new_expo_long, new_expo_short);
> >>>>>> +	dev_dbg(&client->dev, "expo_long_min %d, expo_long_max %d\n",
> >>>>>> +		expo_long_min, expo_long_max);
> >>>>>> +
> >>>>>> +	/* Apply exposure */
> >>>>>> +	sensor->expo_long = new_expo_long;
> >>>>>> +	sensor->expo_short = new_expo_short;
> >>>>>> +	sensor->expo_max = expo_long_max;
> >>>>>> +	sensor->expo_min = expo_long_min;
> >>>>>> +
> >>>>>> +	if (sensor->streaming)
> >>>>>> +		return apply_exposure(sensor);
> >>>>>> +	return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int vgxy61_update_vblank(struct vgxy61_dev *sensor, int vblank, int hdr)
> >>>>>> +{
> >>>>>> +	int ret;
> >>>>>> +
> >>>>>> +	sensor->vblank_min = vgxy61_get_vblank_min(sensor, hdr);
> >>>>>> +	sensor->vblank = max(sensor->vblank_min, vblank);
> >>>>>> +	sensor->frame_length = sensor->current_mode->crop.height + sensor->vblank;
> >>>>>> +
> >>>>>> +	/* Update exposure according to vblank */
> >>>>>> +	ret = vgxy61_update_exposure(sensor, sensor->expo_long, hdr);
> >>>>>> +	if (ret)
> >>>>>> +		return ret;
> >>>>>> +
> >>>>>> +	if (sensor->streaming)
> >>>>>> +		return set_frame_rate(sensor);
> >>>>>> +	return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int vgxy61_update_hdr(struct vgxy61_dev *sensor, u32 index)
> >>>>>> +{
> >>>>>> +	const u8 index2val[] = {0x1, 0x4, 0xa};
> >>>>>> +	int ret;
> >>>>>> +
> >>>>>> +	/*
> >>>>>> +	 * vblank and short exposure change 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_vblank(sensor, sensor->vblank, index);
> >>>>>> +	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_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);
> >>>>>> +		*fmt = format->format;
> >>>>>> +	} else if (sensor->current_mode != new_mode || sensor->fmt.code != format->format.code) {
> >>>>>> +		fmt = &sensor->fmt;
> >>>>>> +		*fmt = format->format;
> >>>>>> +
> >>>>>> +		sensor->current_mode = new_mode;
> >>>>>> +
> >>>>>> +		/* Reset vblank and framelength to default */
> >>>>>> +		ret = vgxy61_update_vblank(sensor,
> >>>>>> +					   VGXY61_FRAME_LENGTH_DEF -
> >>>>>> +					   sensor->current_mode->crop.height,
> >>>>>> +					   sensor->hdr);
> >>>>>> +
> >>>>>> +		/* Update controls to reflect new mode */
> >>>>>> +		__v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate_ctrl, get_pixel_rate(sensor));
> >>>>>> +		__v4l2_ctrl_modify_range(sensor->vblank_ctrl, sensor->vblank_min,
> >>>>>> +					 0xffff - sensor->current_mode->crop.height, 1,
> >>>>>> +					 sensor->vblank);
> >>>>>> +		__v4l2_ctrl_s_ctrl(sensor->vblank_ctrl, sensor->vblank);
> >>>>>> +		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
> >>>>>> +					 sensor->expo_max, 1,
> >>>>>> +					 sensor->expo_long);
> >>>>>> +	}
> >>>>>> +
> >>>>>> +out:
> >>>>>> +	mutex_unlock(&sensor->lock);
> >>>>>> +
> >>>>>> +	return ret;
> >>>>>> +}
> >>>>>> +
> >>>>>> +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);
> >>>>>
> >>>>> Can't runtime PM be disabled here ?
> >>>>
> >>>> You're absolutely right. This works as of now because I guess my
> >>>> regulators are used for something else and therefore do not go down,
> >>>> but should not.
> >>>>
> >>>> This is tricky as I don't want to power up (and therefore patch) the
> >>>> sensor every time I want to see the temperature, but maybe I won't
> >>>> have the choice.
> >>>>
> >>>> For example every time I stop streaming in qv4l2, the temperature is read. This would result in this:
> >>>> 	power off -> get_temp -> power on (+patch) -> read temp -> power off
> >>>> Maybe I can just remove the temperature reading while off? Any other idea?
> >>>
> >>> I think returning an error when the sensor is off is fine. The main use
> >>> cases for the temperature sensor are related to imaging.
> >>>
> >>>> Anyway this will depend on the 2/5 patch discussion thread result.
> >>>>
> >>>>>> +	if (ret)
> >>>>>> +		return ret;
> >>>>>> +	ret = vgxy61_poll_reg(sensor, DEVICE_STBY, STBY_NO_REQ, VGXY61_TIMEOUT_MS);
> >>>>>> +	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);
> >>>>>> +		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);
> >>>>>> +		/* Update vblank and exposure controls to match new hdr */
> >>>>>> +		__v4l2_ctrl_modify_range(sensor->vblank_ctrl, sensor->vblank_min,
> >>>>>> +					 0xffff - sensor->current_mode->crop.height, 1,
> >>>>>> +					 sensor->vblank);
> >>>>>> +		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
> >>>>>> +					 sensor->expo_max, 1, sensor->expo_long);
> >>>>>> +		break;
> >>>>>> +	case V4L2_CID_VBLANK:
> >>>>>> +		ret = vgxy61_update_vblank(sensor, ctrl->val, sensor->hdr);
> >>>>>> +		/* Update exposure control to match new vblank */
> >>>>>> +		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
> >>>>>> +					 sensor->expo_max, 1, sensor->expo_long);
> >>>>>> +		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 ret;
> >>>>>> +
> >>>>>> +	switch (ctrl->id) {
> >>>>>> +	case V4L2_CID_TEMPERATURE:
> >>>>>> +		ret = vgxy61_get_temp(sensor, &ctrl->val);
> >>>>>> +		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 int vgxy61_init_controls(struct vgxy61_dev *sensor)
> >>>>>> +{
> >>>>>> +	const struct v4l2_ctrl_ops *ops = &vgxy61_ctrl_ops;
> >>>>>> +	struct v4l2_ctrl_handler *hdl = &sensor->ctrl_handler;
> >>>>>> +	struct v4l2_ctrl *ctrl;
> >>>>>> +	int ret;
> >>>>>> +
> >>>>>> +	v4l2_ctrl_handler_init(hdl, 16);
> >>>>>> +	mutex_init(&sensor->lock);
> >>>>>
> >>>>> This should be initialized in probe(), it doesn't belong here.
> >>>>>
> >>>>>> +	/* We can use our own mutex for the ctrl lock */
> >>>>>> +	hdl->lock = &sensor->lock;
> >>>>>> +	/* Standard controls */
> >>>>>> +	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, 0x1c, 1, 0x0);
> >>>>>> +	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 0, 0xfff, 1, 256);
> >>>>>> +	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_TEMPERATURE, -128, 128, 1, 0);
> >>>>>> +	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);
> >>>>>> +	v4l2_ctrl_new_custom(hdl, &vgxy61_hdr_ctrl, NULL);
> >>>>>> +	ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HBLANK, 0, sensor->line_length, 1,
> >>>>>> +				 sensor->line_length - sensor->current_mode->width);
> >>>>>> +	ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> >>>>>> +	/* Custom controls */
> >>>>>> +	ctrl = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ, ARRAY_SIZE(link_freq) - 1, 0,
> >>>>>> +				      link_freq);
> >>>>>> +	ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> >>>>>> +	/* Keep a pointer to these controls as we need to update them when setting the format */
> >>>>>> +	sensor->pixel_rate_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1,
> >>>>>> +						    get_pixel_rate(sensor));
> >>>>>> +	sensor->pixel_rate_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> >>>>>> +	sensor->expo_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, sensor->expo_min,
> >>>>>> +					      sensor->expo_max, 1, sensor->expo_long);
> >>>>>> +	sensor->vblank_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VBLANK, sensor->vblank_min,
> >>>>>> +						0xffff - sensor->current_mode->crop.height, 1,
> >>>>>> +						sensor->vblank);
> >>>>>> +	sensor->vflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, sensor->vflip);
> >>>>>> +	sensor->hflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, sensor->hflip);
> >>>>>> +
> >>>>>> +	if (hdl->error) {
> >>>>>> +		ret = hdl->error;
> >>>>>> +		goto free_ctrls;
> >>>>>> +	}
> >>>>>> +
> >>>>>> +	sensor->sd.ctrl_handler = hdl;
> >>>>>> +	return 0;
> >>>>>> +
> >>>>>> +free_ctrls:
> >>>>>> +	v4l2_ctrl_handler_free(hdl);
> >>>>>> +	mutex_destroy(&sensor->lock);
> >>>>>> +	return ret;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static const struct v4l2_subdev_core_ops vgxy61_core_ops = {
> >>>>>> +};
> >>>>>
> >>>>> You can drop this.
> >>>>>
> >>>>>> +
> >>>>>> +static const struct v4l2_subdev_video_ops vgxy61_video_ops = {
> >>>>>> +	.s_stream = vgxy61_s_stream,
> >>>>>> +};
> >>>>>> +
> >>>>>> +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,
> >>>>>> +	.get_selection = vgxy61_get_selection,
> >>>>>> +	.enum_frame_size = vgxy61_enum_frame_size,
> >>>>>> +};
> >>>>>> +
> >>>>>> +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[VGXY61_NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
> >>>>>> +	u32 phy2log[VGXY61_NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
> >>>>>> +	int polarities[VGXY61_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 < VGXY61_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;
> >>>>>> +	/* 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, VGXY61_TIMEOUT_MS);
> >>>>>> +	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_dbg(&client->dev, "Cut1 detected\n");
> >>>>>> +		dev_err(&client->dev, "Cut1 not supported by this driver\n");
> >>>>>> +		return -ENODEV;
> >>>>>> +	case 0xB:
> >>>>>> +		dev_dbg(&client->dev, "Cut2 detected\n");
> >>>>>> +		return 0;
> >>>>>> +	case 0xC:
> >>>>>> +		dev_dbg(&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);
> >>>>>> +	sensor->id = id;
> >>>>>> +
> >>>>>> +	ret = vgxy61_wait_state(sensor, SW_STBY, VGXY61_TIMEOUT_MS);
> >>>>>> +	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;
> >>>>>> +}
> >>>>>> +
> >>>>>> +/* Power/clock management functions */
> >>>>>> +static int vgxy61_power_on(struct device *dev)
> >>>>>> +{
> >>>>>> +	struct i2c_client *client = to_i2c_client(dev);
> >>>>>> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> >>>>>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >>>>>> +	int ret;
> >>>>>> +
> >>>>>> +	ret = vgxy61_get_regulators(sensor);
> >>>>>> +	if (ret) {
> >>>>>> +		dev_err(&client->dev, "failed to get regulators %d\n", ret);
> >>>>>> +		return ret;
> >>>>>> +	}
> >>>>>> +
> >>>>>> +	ret = regulator_bulk_enable(VGXY61_NUM_SUPPLIES, sensor->supplies);
> >>>>>> +	if (ret) {
> >>>>>> +		dev_err(&client->dev, "failed to enable regulators %d\n", ret);
> >>>>>> +		return ret;
> >>>>>> +	}
> >>>>>> +
> >>>>>> +	ret = clk_prepare_enable(sensor->xclk);
> >>>>>> +	if (ret) {
> >>>>>> +		dev_err(&client->dev, "failed to enable clock %d\n", ret);
> >>>>>> +		goto disable_bulk;
> >>>>>> +	}
> >>>>>> +
> >>>>>> +	/* 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);
> >>>>>
> >>>>> Do we really need to run detection every time the sensor is powered on ?
> >>>>
> >>>> Surely not, I will add a if (sensor->already_detected) guard for this
> >>>> call to make it runs only once.
> >>>
> >>> It could also be done in probe().
> >>>
> >>>>>> +	if (ret) {
> >>>>>> +		dev_err(&client->dev, "sensor detect failed %d\n", ret);
> >>>>>> +		goto disable_clock;
> >>>>>> +	}
> >>>>>> +
> >>>>>> +	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;
> >>>>>> +	}
> >>>>>> +
> >>>>>> +	return 0;
> >>>>>> +
> >>>>>> +disable_clock:
> >>>>>> +	clk_disable_unprepare(sensor->xclk);
> >>>>>> +disable_bulk:
> >>>>>> +	regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
> >>>>>> +
> >>>>>> +	return ret;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int vgxy61_power_off(struct device *dev)
> >>>>>> +{
> >>>>>> +	struct i2c_client *client = to_i2c_client(dev);
> >>>>>> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> >>>>>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >>>>>> +
> >>>>>> +	clk_disable_unprepare(sensor->xclk);
> >>>>>> +	regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
> >>>>>> +	return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int __maybe_unused vgxy61_suspend(struct device *dev)
> >>>>>> +{
> >>>>>> +	struct i2c_client *client = to_i2c_client(dev);
> >>>>>> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> >>>>>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >>>>>> +
> >>>>>> +	if (sensor->streaming)
> >>>>>> +		vgxy61_stream_disable(sensor);
> >>>>>
> >>>>> This shoudn't happen. The sensor should be stopped by the driver on the
> >>>>> receiver side during system suspend. Same for resume.
> >>>>
> >>>> Sorry I'm still learning the pm_runtime API and I'm not sure I got your point.
> >>>> If the sensor is already stopped then I could just remove the whole suspend function? Same for resume.
> >>>> I'm not sure when the suspend function is called by the framework.
> >>>
> >>> vgxy61_suspend() is called on system suspend. Disabling streaming here
> >>> is problematic, as it's done independently from the CSI-2 receiver. It's
> >>> even worse on resume, as if you resume the sensor first, the CSI-2 lanes
> >>> will go to HS mode before the receiver is started, and the receiver will
> >>> never be able to synchronized. That's why the CSI-2 receiver resume
> >>> handler needs to restart streaming by calling the sensor .s_stream()
> >>> operation. Same thing at suspend time. Most sensors will thus not need
> >>> system suspend/resume handlers.
> >>>
> >>> This means that the CSI-2 receiver needs to be suspended before the
> >>> sensor, and resumed after. You can achieve this with the device link
> >>> API, calling device_link_add(csi2_dev, sensor_dev, DL_FLAG_STATELESS)
> >>> in the CSI-2 receiver driver.
> >>
> >> Let me rephrase your comment just to make sure I got everything right.
> >>
> >> On system suspend, the CSI-2 receiver driver should already have
> >> stopped the stream for me, so this is irrelevant.
> >>
> >> On resume, if by any chance I try to resume streaming before the CSI2
> >> receiver is resuming things will go terribly wrong.
> >>
> >> So this means I should trust the CSI2 driver to have performed
> >> device_link_add on its side, and can delete the vgxy61 resume and
> >> suspend ops.
> > 
> > Correct.
> > 
> >> I have trouble understanding why my use case differs from the imx219
> >> for instance, that afaik does the same thing.
> > 
> > Your use case doesn't differ, but we have lots of sensors drivers that
> > have been developed over time while our understanding of power
> > management evolved, and older drivers haven't been updated to the latest
> > best practices. This results in lots of cargo-cult for new drivers too,
> > it's not a great situation.
> 
> I understand, thanks a lot for your explanations.
> 
> >>>>>> +
> >>>>>> +	return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int __maybe_unused vgxy61_resume(struct device *dev)
> >>>>>> +{
> >>>>>> +	struct i2c_client *client = to_i2c_client(dev);
> >>>>>> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> >>>>>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> >>>>>> +	int ret;
> >>>>>> +
> >>>>>> +	if (sensor->streaming) {
> >>>>>> +		ret = vgxy61_stream_enable(sensor);
> >>>>>> +		if (ret)
> >>>>>> +			goto error;
> >>>>>> +	}
> >>>>>> +
> >>>>>> +	return 0;
> >>>>>> +
> >>>>>> +error:
> >>>>>> +	vgxy61_stream_disable(sensor);
> >>>>>> +	sensor->streaming = false;
> >>>>>> +
> >>>>>> +	return ret;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static void vgxy61_set_default_format(struct vgxy61_dev *sensor)
> >>>>>> +{
> >>>>>> +	if (sensor->id == VG5761_MODEL_ID) {
> >>>>>
> >>>>> Would it make sense to have different compatible strings instead of
> >>>>> detecting the version automatically ? Not only could it speed up probe
> >>>>> if we could skip the detection, it would also allow not powering the
> >>>>> sensor up at probe time, which may be required in some cases (for
> >>>>> instance when a privacy LED is connected to the sensor power supply,
> >>>>> flashing the LED at probe time can be very confusing for users).
> >>>>
> >>>> Your flashing LED example is a very good concern.
> >>>>
> >>>> The idea behind this is to ease the user life as he does not have to
> >>>> know what model he is using.
> >>>
> >>> I agree with you, I've implemented the same in sensor drivers. One
> >>> option may be to have a generic compatible string that would lead to
> >>> model detection at probe time, and specific compatible strings that
> >>> would skip it and avoid powering up the sensor. As long as we consider
> >>> the requirements on the DT side and make sure to have a way forward
> >>> without breaking backward compatibility, this can also be implemented
> >>> later.
> >>
> >> This is a very good idea.
> >>
> >>>> For example we have several boards with either the VG5761 or VG5661
> >>>> and are interchangeable without changing the device tree, which is
> >>>> also better for us as it shrinks the device tree number to maintain.
> >>>>
> >>>> Also as a company we treat them the same way, hence the same driver.
> >>>> I'm not hardly commited to keep this, but it indeed makes my life easier.
> >>>>
> >>>>>> +		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;
> >>>>>> +	} else if (sensor->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;
> >>>>>> +	} else {
> >>>>>> +		/* Should never happen */
> >>>>>> +		WARN_ON(true);
> >>>>>> +	}
> >>>>>> +	sensor->fmt.width = sensor->current_mode->width;
> >>>>>> +	sensor->fmt.height = sensor->current_mode->height;
> >>>>>> +	sensor->vblank = sensor->frame_length - sensor->current_mode->crop.height;
> >>>>>> +}
> >>>>>> +
> >>>>>> +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_Y8_1X8;
> >>>>>> +	sensor->fmt.colorspace = V4L2_COLORSPACE_RAW;
> >>>>>> +	sensor->fmt.field = V4L2_FIELD_NONE;
> >>>>>> +	sensor->fmt.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> >>>>>> +	sensor->fmt.quantization = V4L2_QUANTIZATION_DEFAULT;
> >>>>>> +	sensor->fmt.xfer_func = V4L2_XFER_FUNC_DEFAULT;
> >>>>>
> >>>>> Please implement the subdev .init_cfg() operation, and call it here.
> >>>>> There are several examples in existing drivers.
> >>>>>
> >>>>>> +	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;
> >>>>>> +	sensor->frame_length = VGXY61_FRAME_LENGTH_DEF;
> >>>>>> +
> >>>>>> +	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, NULL);
> >>>>>> +	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 < 6 * HZ_PER_MHZ || sensor->clk_freq > 27 * HZ_PER_MHZ) {
> >>>>>> +		dev_err(dev, "Only 6Mhz-27Mhz clock range supported. provide %lu MHz\n",
> >>>>>> +			sensor->clk_freq / HZ_PER_MHZ);
> >>>>>> +		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;
> >>>>>> +
> >>>>>> +	/* Request optional reset pin */
> >>>>>> +	sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
> >>>>>> +
> >>>>>> +	ret = vgxy61_power_on(dev);
> >>>>>> +	if (ret)
> >>>>>> +		return ret;
> >>>>>> +
> >>>>>> +	vgxy61_set_default_format(sensor);
> >>>>>> +
> >>>>>> +	/* HDR mode */
> >>>>>> +	ret = vgxy61_update_hdr(sensor, sensor->hdr);
> >>>>>> +	if (ret)
> >>>>>> +		return ret;
> >>>>>> +
> >>>>>> +	ret = vgxy61_init_controls(sensor);
> >>>>>> +	if (ret) {
> >>>>>> +		dev_err(&client->dev, "controls initialization failed %d\n", ret);
> >>>>>> +		goto error_power_off;
> >>>>>> +	}
> >>>>>> +
> >>>>>> +	ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
> >>>>>> +	if (ret) {
> >>>>>> +		dev_err(&client->dev, "pads init failed %d\n", ret);
> >>>>>> +		goto error_handler_free;
> >>>>>> +	}
> >>>>>> +
> >>>>>> +	ret = v4l2_async_register_subdev(&sensor->sd);
> >>>>>> +	if (ret) {
> >>>>>> +		dev_err(&client->dev, "async subdev register failed %d\n", ret);
> >>>>>> +		goto error_media_entity;
> >>>>>> +	}
> >>>>>> +
> >>>>>> +	/* Enable runtime PM and turn off the device */
> >>>>>> +	pm_runtime_set_active(dev);
> >>>>>> +	pm_runtime_enable(dev);
> >>>>>> +	pm_runtime_idle(dev);
> >>>>>
> >>>>> You should do this before registering the subdev, as it can be accessed
> >>>>> from userspace right after being registered.
> >>>>>
> >>>>> Please also use auto-suspend to avoid long delays when stopping and
> >>>>> restarting streaming quickly.
> >>>>
> >>>> I was not aware of autosuspend. Thanks a lot, I will look into this.
> >>>>
> >>>>>> +
> >>>>>> +	dev_info(&client->dev, "vgxy61 probe successfully\n");
> >>>>>
> >>>>> I'd drop this, or turn it into a debug message.
> >>>>>
> >>>>>> +
> >>>>>> +	return 0;
> >>>>>> +
> >>>>>> +error_media_entity:
> >>>>>> +	media_entity_cleanup(&sensor->sd.entity);
> >>>>>> +error_handler_free:
> >>>>>> +	v4l2_ctrl_handler_free(sensor->sd.ctrl_handler);
> >>>>>> +	mutex_destroy(&sensor->lock);
> >>>>>> +error_power_off:
> >>>>>> +	vgxy61_power_off(dev);
> >>>>>> +
> >>>>>> +	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);
> >>>>>> +	mutex_destroy(&sensor->lock);
> >>>>>> +	media_entity_cleanup(&sensor->sd.entity);
> >>>>>> +
> >>>>>> +	pm_runtime_disable(&client->dev);
> >>>>>> +	if (!pm_runtime_status_suspended(&client->dev))
> >>>>>> +		vgxy61_power_off(&client->dev);
> >>>>>> +	pm_runtime_set_suspended(&client->dev);
> >>>>>> +
> >>>>>> +	return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static const struct of_device_id vgxy61_dt_ids[] = {
> >>>>>> +	{ .compatible = "st,st-vgxy61" },
> >>>>>> +	{ /* sentinel */ }
> >>>>>> +};
> >>>>>> +MODULE_DEVICE_TABLE(of, vgxy61_dt_ids);
> >>>>>> +
> >>>>>> +static const struct dev_pm_ops vgxy61_pm_ops = {
> >>>>>> +	SET_SYSTEM_SLEEP_PM_OPS(vgxy61_suspend, vgxy61_resume)
> >>>>>> +	SET_RUNTIME_PM_OPS(vgxy61_power_off, vgxy61_power_on, NULL)
> >>>>>> +};
> >>>>>> +
> >>>>>> +static struct i2c_driver vgxy61_i2c_driver = {
> >>>>>> +	.driver = {
> >>>>>> +		.name  = "st-vgxy61",
> >>>>>> +		.of_match_table = vgxy61_dt_ids,
> >>>>>> +		.pm = &vgxy61_pm_ops,
> >>>>>> +	},
> >>>>>> +	.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");

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v2 5/5] media: i2c: Add driver for ST VGXY61 camera sensor
  2022-04-24 16:34               ` Laurent Pinchart
@ 2022-04-27 14:27                 ` Benjamin Mugnier
  0 siblings, 0 replies; 33+ messages in thread
From: Benjamin Mugnier @ 2022-04-27 14:27 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: linux-media, alain.volmat, hugues.fruchet, sylvain.petinot,
	dave.stevenson, sakari.ailus, kieran.bingham

Hi Laurent,

On 24/04/2022 18:34, Laurent Pinchart wrote:
> Hi Benjamin,
> 
> On Fri, Apr 22, 2022 at 04:40:14PM +0200, Benjamin Mugnier wrote:
>> On 22/04/2022 15:29, Laurent Pinchart wrote:
>>> On Fri, Apr 22, 2022 at 10:44:13AM +0200, Benjamin Mugnier wrote:
>>>> On 20/04/2022 18:48, Laurent Pinchart wrote:
>>>>> On Wed, Apr 20, 2022 at 03:01:24PM +0200, Benjamin Mugnier wrote:
>>>>>> On 15/04/2022 17:39, Laurent Pinchart wrote:
>>>>>>> On Fri, Apr 15, 2022 at 01:18:45PM +0200, Benjamin Mugnier wrote:
>>>>>>>> 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     |   10 +
>>>>>>>>  drivers/media/i2c/Makefile    |    1 +
>>>>>>>>  drivers/media/i2c/st-vgxy61.c | 1997 +++++++++++++++++++++++++++++++++
>>>>>>>>  3 files changed, 2008 insertions(+)
>>>>>>>>  create mode 100644 drivers/media/i2c/st-vgxy61.c
>>>>>>>>
>>>>>>>> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
>>>>>>>> index fae2baabb773..734f3e1130b0 100644
>>>>>>>> --- a/drivers/media/i2c/Kconfig
>>>>>>>> +++ b/drivers/media/i2c/Kconfig
>>>>>>>> @@ -763,6 +763,16 @@ config VIDEO_VS6624
>>>>>>>>  	  To compile this driver as a module, choose M here: the
>>>>>>>>  	  module will be called vs6624.
>>>>>>>>  
>>>>>>>> +config VIDEO_ST_VGXY61
>>>>>>>
>>>>>>> Alphabetical order please.
>>>>>>>
>>>>>>>> +	tristate "ST VGXY61 sensor support"
>>>>>>>> +	depends on OF && GPIOLIB && VIDEO_DEV && 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.
>>>>>>>> +
>>>>>>>>  source "drivers/media/i2c/ccs/Kconfig"
>>>>>>>>  source "drivers/media/i2c/et8ek8/Kconfig"
>>>>>>>>  source "drivers/media/i2c/m5mols/Kconfig"
>>>>>>>> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
>>>>>>>> index 3e1696963e7f..7f2362228484 100644
>>>>>>>> --- a/drivers/media/i2c/Makefile
>>>>>>>> +++ b/drivers/media/i2c/Makefile
>>>>>>>> @@ -116,6 +116,7 @@ obj-$(CONFIG_VIDEO_SAA7185) += saa7185.o
>>>>>>>>  obj-$(CONFIG_VIDEO_SONY_BTF_MPX) += sony-btf-mpx.o
>>>>>>>>  obj-$(CONFIG_VIDEO_SR030PC30) += sr030pc30.o
>>>>>>>>  obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o
>>>>>>>> +obj-$(CONFIG_VIDEO_ST_VGXY61) += st-vgxy61.o
>>>>>>>>  obj-$(CONFIG_VIDEO_TC358743) += tc358743.o
>>>>>>>>  obj-$(CONFIG_VIDEO_TDA1997X) += tda1997x.o
>>>>>>>>  obj-$(CONFIG_VIDEO_TDA7432) += tda7432.o
>>>>>>>> diff --git a/drivers/media/i2c/st-vgxy61.c b/drivers/media/i2c/st-vgxy61.c
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000000..1219d278e648
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/drivers/media/i2c/st-vgxy61.c
>>>>>>>> @@ -0,0 +1,1997 @@
>>>>>>>> +// 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/pm_runtime.h>
>>>>>>>
>>>>>>> Alphabetical order in headers too please.
>>>>>>>
>>>>>>>> +#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
>>>>>>>
>>>>>>> I don't think this deserves a macro, but if you want to keep it, you
>>>>>>> should avoid generic names that may cause namespace clashes.
>>>>>>>
>>>>>>
>>>>>> I don't make heavy usage of it so I will remove it.
>>>>>>
>>>>>>>> +
>>>>>>>> +#define DEVICE_MODEL_ID_REG				0x0000
>>>>>>>
>>>>>>> Please prefix macros with a driver-specific prefix. Many of them have a
>>>>>>> too generic name.
>>>>>>>
>>>>>>
>>>>>> I will change this to VGXY61_REG_* for registers, and simply VGXY61_* for others.
>>>>>>
>>>>>>>> +#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 VGXY61_WRITE_MULTIPLE_CHUNK_MAX			16
>>>>>>>> +#define VGXY61_NB_GPIOS					4
>>>>>>>> +#define VGXY61_NB_POLARITIES				5
>>>>>>>> +#define VGXY61_MIN_FRAME_LENGTH				1288
>>>>>>>> +#define VGXY61_MIN_EXPOSURE				10
>>>>>>>> +#define VGXY61_HDR_LINEAR_RATIO				10
>>>>>>>> +#define VGXY61_TIMEOUT_MS				500
>>>>>>>> +#define VGXY61_FRAME_LENGTH_DEF				1313
>>>>>>>> +#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_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_STVGXY61_BASE + 0)
>>>>>>>
>>>>>>> This macro should have a device-specific name, should be moved to a
>>>>>>> public header, and the control should be documented. I would split the
>>>>>>> control documentation and macro definition to a separate patch.
>>>>>>>
>>>>>>
>>>>>> I created two new files include/uapi/linux/st-vgxy61.h and
>>>>>> Documentation/userspace-api/media/drivers/st-vgxy61.rst.
>>>>>>
>>>>>> I'm not sure what you mean by separating the documentation and
>>>>>> definition. As there is a significant amount of code dependent on the
>>>>>> HDR mode I will keep the public header in this patch, and create a new
>>>>>> patch for the documentation. Tell me if there is any issue with this.
>>>>>
>>>>> I meant a patch that adds include/uapi/linux/st-vgxy61.h and
>>>>> Documentation/userspace-api/media/drivers/st-vgxy61.rst separate from
>>>>> the driver, but if it causes more issues than it helps review, you can
>>>>> combine them in a single patch.
>>>>
>>>> Ok, I initially thought about moving the relevant st-vgxy61.c hdr code
>>>> to a new patch, which will be quite complicated.
>>>> Only definition and documentation make no issue.
>>>>
>>>>>>>> +
>>>>>>>> +#define DEVICE_FWPATCH_REVISION_MAJOR			2
>>>>>>>> +#define DEVICE_FWPATCH_REVISION_MINOR			0
>>>>>>>> +#define DEVICE_FWPATCH_REVISION_MICRO			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
>>>>>>>
>>>>>>> The link frequencies should come from DT.
>>>>>>
>>>>>> This is a hard requirement for this sensor. This value can not be
>>>>>> modified and must be 402MHz.
>>>>>>
>>>>>> I see the ov9734.c does something similar. Is adding this value to the
>>>>>> device tree really mandatory?
>>>>>
>>>>> I suppose it's fine then. Sakari, what do you think ?
>>>>>
>>>>>>>> +};
>>>>>>>> +
>>>>>>>> +#define VGXY61_NUM_SUPPLIES		ARRAY_SIZE(vgxy61_supply_name)
>>>>>>>> +
>>>>>>>> +enum bin_mode {
>>>>>>>
>>>>>>> Same comment here, all enums and structures should use a driver-specific
>>>>>>> prefix.
>>>>>>>
>>>>>>>> +	BIN_MODE_NORMAL,
>>>>>>>> +	BIN_MODE_DIGITAL_X2,
>>>>>>>> +	BIN_MODE_DIGITAL_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;
>>>>>>>> +	struct v4l2_rect crop;
>>>>>>>> +};
>>>>>>>> +
>>>>>>>> +static const u32 vgxy61_supported_codes[] = {
>>>>>>>> +	MEDIA_BUS_FMT_Y8_1X8,
>>>>>>>> +	MEDIA_BUS_FMT_Y10_1X10,
>>>>>>>> +	MEDIA_BUS_FMT_Y12_1X12,
>>>>>>>> +	MEDIA_BUS_FMT_Y14_1X14,
>>>>>>>> +	MEDIA_BUS_FMT_Y16_1X16
>>>>>>>> +};
>>>>>>>> +
>>>>>>>> +static const struct vgxy61_mode_info vgx661_mode_data[] = {
>>>>>>>> +	{
>>>>>>>> +		.width = VGX661_WIDTH,
>>>>>>>> +		.height = VGX661_HEIGHT,
>>>>>>>> +		.bin_mode = BIN_MODE_NORMAL,
>>>>>>>> +		.crop = {
>>>>>>>> +			.left = 0,
>>>>>>>> +			.top = 0,
>>>>>>>> +			.width = VGX661_WIDTH,
>>>>>>>> +			.height = VGX661_HEIGHT,
>>>>>>>> +		},
>>>>>>>
>>>>>>> It's very nice to see no hardcoded register values in the modes tables
>>>>>>> :-) As the register values are computed by the driver, would it make
>>>>>>> sense to go one step further and drop the modes completely ? Analog crop
>>>>>>> and binning could then be selected freely from userspace.
>>>>>>
>>>>>> All credit goes to Dave for pointing this in the first review ;)
>>>>>> As for the modes, in theory we could drop them completely, but this
>>>>>> will require some extra testing as the sensor can't really handle
>>>>>> every single mode freely.
>>>>>>
>>>>>> Could this be done in a second step?
>>>>>
>>>>> I'm a bit concerned it could then break userspace, if some applications
>>>>> start depending on a fixed list of modes. How difficult do you think
>>>>> this would be ?
>>>>
>>>> I mean if they use a fixed list, the upgrade will still allow them to
>>>> use this list right? As it will implement a superset of the resolution
>>>> list.
>>>>
>>>> Also if I understand correctly they will get this list dynamically
>>>> from the get_fmt call, hence once the full support is added the
>>>> application will update itself right?
>>>
>>> The idea is that we could get rid of the modes list completely, and let
>>> the application configure everything through the formats and selection
>>> rectangles. Moving in that direction later may cause some regressions,
>>> for instance an application may set a mode that is not supported, with
>>> the driver picking the closest option now, and later it may get the
>>> exact resolution it requested. It's hard to know if this could break
>>> something.
>>>
>>>> For the complexity I need to check various rules to make sure the
>>>> sensor will not be in a bad state. On top of my head the X and Y must
>>>> be even, binning must match exactly the format ratio, and certainly
>>>> other stuff I have not been confronted to yet. And of course testing.
>>>
>>> Those rules don't seem to be too complicated. Testing will likely be the
>>> biggest time consumer :-)
>>
>> I will give it go for v3 then. If I run into issues I could still
>> remove the find_nearest_size call and return an error if the exact
>> size is not supported, but appart from the time consumming part this
>> should go well.
> 
> Note that the V4L2 subdev API doesn't return errors in that case, but
> picks the closest option.
> 

I looked into it and have a quick question about the implementation.
I can implement the get_selection api to set the crop zone. However, I didn't find a way for the user to set the binning.
One possibility is doing like the ov5693 and automatically enable binning once available by computing hratio and vratio. But this is not really satistfying as if I jump from no binning to binning x2 my field of view will double (as it takes twice more pixels). If the user is cropping I guess he wants to isolate a specific zone. This also cause issues for centering this area. as it may not be always possible if the user wants to crop a corner for instance.
Is there a way to allow the user to explicitely set the binning? I could create control but unfortunately it will be hard for it to support all sensors use cases.
Another possibility is to drop the binning for now.

>>>> Tell me what you think.
>>>>
>>>>>>>> +	},
>>>>>>>> +	{
>>>>>>>> +		.width = 1280,
>>>>>>>> +		.height = 720,
>>>>>>>> +		.bin_mode = BIN_MODE_NORMAL,
>>>>>>>> +		.crop = {
>>>>>>>> +			.left = 92,
>>>>>>>> +			.top = 192,
>>>>>>>> +			.width = 1280,
>>>>>>>> +			.height = 720,
>>>>>>>> +		},
>>>>>>>> +	},
>>>>>>>> +	{
>>>>>>>> +		.width = 640,
>>>>>>>> +		.height = 480,
>>>>>>>> +		.bin_mode = BIN_MODE_DIGITAL_X2,
>>>>>>>> +		.crop = {
>>>>>>>> +			.left = 92,
>>>>>>>> +			.top = 72,
>>>>>>>> +			.width = 1280,
>>>>>>>> +			.height = 960,
>>>>>>>> +		},
>>>>>>>> +	},
>>>>>>>> +	{
>>>>>>>> +		.width = 320,
>>>>>>>> +		.height = 240,
>>>>>>>> +		.bin_mode = BIN_MODE_DIGITAL_X4,
>>>>>>>
>>>>>>> Can the bin mode computed by dividing the crop width by the output width
>>>>>>> instead of being stored here ?
>>>>>>
>>>>>> The sensor also supports analog subsampling on top of digital binning.
>>>>>> I did not implement it in the driver but I made the choice to
>>>>>> explicitly set the bin_mode for further development. For example the
>>>>>> line above could be:
>>>>>>
>>>>>>   .bin_mode = BIN_MODE_ANALOG_X4
>>>>>>
>>>>>> I can compute the factor (X2/X4), but not the analog/digital mode.
>>>>>> Hope this make sense.
>>>>>
>>>>> It thus indeed, but it then leads to the question of how to configure
>>>>> skipping and binning separately. The CCS driver registers two subdevs to
>>>>> make this possible. It increases the complexity, and I'm increasingly
>>>>> thinking that we should extend the V4L2 API to allow configure all the
>>>>> common parameters of camera sensors with a single subdev.
>>>>>
>>>>> Sakari, any opinion ?
>>>>>
>>>>>>>> +		.crop = {
>>>>>>>> +			.left = 92,
>>>>>>>> +			.top = 72,
>>>>>>>> +			.width = 1280,
>>>>>>>> +			.height = 960,
>>>>>>>> +		},
>>>>>>>> +	},
>>>>>>>> +};
>>>>>>>> +
>>>>>>>> +static const struct vgxy61_mode_info vgx761_mode_data[] = {
>>>>>>>> +	{
>>>>>>>> +		.width = VGX761_WIDTH,
>>>>>>>> +		.height = VGX761_HEIGHT,
>>>>>>>> +		.bin_mode = BIN_MODE_NORMAL,
>>>>>>>> +		.crop = {
>>>>>>>> +			.left = 0,
>>>>>>>> +			.top = 0,
>>>>>>>> +			.width = VGX761_WIDTH,
>>>>>>>> +			.height = VGX761_HEIGHT,
>>>>>>>> +		},
>>>>>>>> +	},
>>>>>>>> +	{
>>>>>>>> +		.width = 1920,
>>>>>>>> +		.height = 1080,
>>>>>>>> +		.bin_mode = BIN_MODE_NORMAL,
>>>>>>>> +		.crop = {
>>>>>>>> +			.left = 12,
>>>>>>>> +			.top = 62,
>>>>>>>> +			.width = 1920,
>>>>>>>> +			.height = 1080,
>>>>>>>> +		},
>>>>>>>> +	},
>>>>>>>> +	{
>>>>>>>> +		.width = 1280,
>>>>>>>> +		.height = 720,
>>>>>>>> +		.bin_mode = BIN_MODE_NORMAL,
>>>>>>>> +		.crop = {
>>>>>>>> +			.left = 332,
>>>>>>>> +			.top = 242,
>>>>>>>> +			.width = 1280,
>>>>>>>> +			.height = 720,
>>>>>>>> +		},
>>>>>>>> +	},
>>>>>>>> +	{
>>>>>>>> +		.width = 640,
>>>>>>>> +		.height = 480,
>>>>>>>> +		.bin_mode = BIN_MODE_DIGITAL_X2,
>>>>>>>> +		.crop = {
>>>>>>>> +			.left = 332,
>>>>>>>> +			.top = 122,
>>>>>>>> +			.width = 1280,
>>>>>>>> +			.height = 960,
>>>>>>>> +		},
>>>>>>>> +	},
>>>>>>>> +	{
>>>>>>>> +		.width = 320,
>>>>>>>> +		.height = 240,
>>>>>>>> +		.bin_mode = BIN_MODE_DIGITAL_X4,
>>>>>>>> +		.crop = {
>>>>>>>> +			.left = 332,
>>>>>>>> +			.top = 122,
>>>>>>>> +			.width = 1280,
>>>>>>>> +			.height = 960,
>>>>>>>> +		},
>>>>>>>> +	},
>>>>>>>> +};
>>>>>>>> +
>>>>>>>> +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 id;
>>>>>>>> +	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 v4l2_ctrl_handler ctrl_handler;
>>>>>>>> +	struct v4l2_ctrl *pixel_rate_ctrl;
>>>>>>>> +	struct v4l2_ctrl *expo_ctrl;
>>>>>>>> +	struct v4l2_ctrl *vblank_ctrl;
>>>>>>>> +	struct v4l2_ctrl *vflip_ctrl;
>>>>>>>> +	struct v4l2_ctrl *hflip_ctrl;
>>>>>>>> +	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;
>>>>>>>> +	struct v4l2_fract frame_interval;
>>>>>>>
>>>>>>> This is not used. Please check all other fields too.
>>>>>>>
>>>>>>>> +	bool hflip;
>>>>>>>> +	bool vflip;
>>>>>>>> +	enum hdr hdr;
>>>>>>>> +	int expo_long;
>>>>>>>> +	int expo_short;
>>>>>>>> +	int expo_max;
>>>>>>>> +	int expo_min;
>>>>>>>> +	int vblank;
>>>>>>>> +	int vblank_min;
>>>>>>>> +	u16 frame_length;
>>>>>>>> +};
>>>>>>>> +
>>>>>>>> +static u8 get_bpp_by_code(__u32 code)
>>>>>>>> +{
>>>>>>>> +	switch (code) {
>>>>>>>> +	case MEDIA_BUS_FMT_Y8_1X8:
>>>>>>>> +		return 8;
>>>>>>>> +	case MEDIA_BUS_FMT_Y10_1X10:
>>>>>>>> +		return 10;
>>>>>>>> +	case MEDIA_BUS_FMT_Y12_1X12:
>>>>>>>> +		return 12;
>>>>>>>> +	case MEDIA_BUS_FMT_Y14_1X14:
>>>>>>>> +		return 14;
>>>>>>>> +	case MEDIA_BUS_FMT_Y16_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_Y8_1X8:
>>>>>>>> +		return MIPI_CSI2_DT_RAW8;
>>>>>>>> +	case MEDIA_BUS_FMT_Y10_1X10:
>>>>>>>> +		return MIPI_CSI2_DT_RAW10;
>>>>>>>> +	case MEDIA_BUS_FMT_Y12_1X12:
>>>>>>>> +		return MIPI_CSI2_DT_RAW12;
>>>>>>>> +	case MEDIA_BUS_FMT_Y14_1X14:
>>>>>>>> +		return MIPI_CSI2_DT_RAW14;
>>>>>>>> +	case MEDIA_BUS_FMT_Y16_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;
>>>>>>>> +	}
>>>>>>>> +}
>>>>>>>
>>>>>>> I'd put all this data in a format description structure, and store that
>>>>>>> in the vgxy61_supported_codes array.
>>>>>>>
>>>>>>>> +
>>>>>>>> +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, ctrl_handler)->sd;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +static int get_chunk_size(struct vgxy61_dev *sensor)
>>>>>>>> +{
>>>>>>>> +	struct i2c_adapter *adapter = sensor->i2c_client->adapter;
>>>>>>>> +	int max_write_len = VGXY61_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, VGXY61_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[VGXY61_WRITE_MULTIPLE_CHUNK_MAX + 2];
>>>>>>>> +	int i;
>>>>>>>> +	int ret;
>>>>>>>> +
>>>>>>>> +	if (len > VGXY61_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));
>>>>>>>> +}
>>>>>>>
>>>>>>> You could possibly simplify handling of registers of different sizes by
>>>>>>> encoding the size in the register address macros. See
>>>>>>> https://gitlab.com/ideasonboard/nxp/linux/-/blob/pinchartl/v5.18/sensors/imx/drivers/media/i2c/imx296.c
>>>>>>> for an example.
>>>>>>
>>>>>> That sounds good, thank you. I will look into this.
>>>>>
>>>>> On the topic of register writes, you may also want to consider the *err
>>>>> argument to imx296_write(), it simplifies error handling (see how errors
>>>>> are handled in imx296_setup(), compared to checking the return value of
>>>>> every single write call).
>>>>>
>>>>>>>> +
>>>>>>>> +static int vgxy61_poll_reg(struct vgxy61_dev *sensor, u16 reg, u8 poll_val, int timeout_ms)
>>>>>>>> +{
>>>>>>>> +	const int loop_delay_ms = 10;
>>>>>>>> +	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, int timeout_ms)
>>>>>>>> +{
>>>>>>>> +	return vgxy61_poll_reg(sensor, DEVICE_SYSTEM_FSM, state, timeout_ms);
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +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 = sensor->current_mode->crop.height / sensor->current_mode->height;
>>>>>>>> +	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;
>>>>>>>
>>>>>>> Is there a risk those writes may be applied to different frames ?
>>>>>>
>>>>>> Yes, but it does not matter for the sensor. Are you thinking of an
>>>>>> issue about user space applications for instance?
>>>>>
>>>>> Yes, if the writes cross frame boundaries, we could have a frame with a
>>>>> weird exposure time, which could confuse ISP algorithms.
>>>>>
>>>>> Does the sensor support CSI-2 embedded data ?
>>>>
>>>> It does, and embed the exposures in it. So I guess the ISP algorithm
>>>> could check these and react accordingly.
>>>
>>> For ISPs that can capture embedded data, that sounds good.
>>>
>>>> As a side note the short exposure is only used for HDR modes, so as
>>>> long as the user is running in SDR there is no impact at all.
>>>
>>> That can help.
>>>
>>> I suppose we can improve this further later if/when needed. I'm just
>>> worried it could lead to issues that are difficult to debug.
>>>
>>>>>>>> +
>>>>>>>> +	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)
>>>>>>>
>>>>>>> The function sets the frame length, not the frame rate.
>>>>>>>
>>>>>>>> +{
>>>>>>>> +	return vgxy61_write_reg16(sensor, DEVICE_FRAME_LENGTH, sensor->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, VGXY61_TIMEOUT_MS);
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +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 */
>>>>>>>> +	mode = v4l2_find_nearest_size(sensor->sensor_modes, sensor->sensor_modes_nb,
>>>>>>>> +				      width, height, fmt->width, fmt->height);
>>>>>>>> +	if (new_mode)
>>>>>>>> +		*new_mode = mode;
>>>>>>>> +
>>>>>>>> +	fmt->code = vgxy61_supported_codes[index];
>>>>>>>> +	fmt->width = mode->width;
>>>>>>>> +	fmt->height = mode->height;
>>>>>>>> +	fmt->colorspace = V4L2_COLORSPACE_RAW;
>>>>>>>> +	fmt->field = V4L2_FIELD_NONE;
>>>>>>>> +	fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
>>>>>>>> +	fmt->quantization = V4L2_QUANTIZATION_DEFAULT;
>>>>>>>> +	fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT;
>>>>>>>> +
>>>>>>>> +	return 0;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +static int vgxy61_get_selection(struct v4l2_subdev *sd,
>>>>>>>> +				struct v4l2_subdev_state *sd_state,
>>>>>>>> +				struct v4l2_subdev_selection *sel)
>>>>>>>> +{
>>>>>>>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>>>>>> +
>>>>>>>> +	switch (sel->target) {
>>>>>>>> +	case V4L2_SEL_TGT_CROP:
>>>>>>>> +		sel->r = sensor->current_mode->crop;
>>>>>>>> +		return 0;
>>>>>>>> +	case V4L2_SEL_TGT_NATIVE_SIZE:
>>>>>>>> +	case V4L2_SEL_TGT_CROP_DEFAULT:
>>>>>>>> +	case V4L2_SEL_TGT_CROP_BOUNDS:
>>>>>>>> +		sel->r.top = 0;
>>>>>>>> +		sel->r.left = 0;
>>>>>>>> +		sel->r.width = sensor->sensor_width;
>>>>>>>> +		sel->r.height = sensor->sensor_height;
>>>>>>>> +		return 0;
>>>>>>>> +	}
>>>>>>>> +
>>>>>>>> +	return -EINVAL;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +static int vgxy61_stream_enable(struct vgxy61_dev *sensor)
>>>>>>>> +{
>>>>>>>> +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd);
>>>>>>>> +	const struct v4l2_rect *crop = &sensor->current_mode->crop;
>>>>>>>> +	int ret;
>>>>>>>> +
>>>>>>>> +	ret = vgxy61_check_bw(sensor);
>>>>>>>> +	if (ret)
>>>>>>>> +		return ret;
>>>>>>>> +
>>>>>>>> +	ret = pm_runtime_get_sync(&client->dev);
>>>>>>>> +	if (ret < 0) {
>>>>>>>> +		pm_runtime_put_noidle(&client->dev);
>>>>>>>> +		return ret;
>>>>>>>> +	}
>>>>>>>> +
>>>>>>>> +	/* Configure sensor */
>>>>>>>> +	ret = vgxy61_write_reg(sensor, DEVICE_FORMAT_CTRL, get_bpp_by_code(sensor->fmt.code));
>>>>>>>> +	if (ret)
>>>>>>>> +		goto err_rpm_put;
>>>>>>>> +	ret = vgxy61_write_reg(sensor, DEVICE_OIF_ROI0_CTRL,
>>>>>>>> +			       get_data_type_by_code(sensor->fmt.code));
>>>>>>>> +	if (ret)
>>>>>>>> +		goto err_rpm_put;
>>>>>>>> +
>>>>>>>> +	ret = vgxy61_write_reg(sensor, DEVICE_READOUT_CTRL, sensor->current_mode->bin_mode);
>>>>>>>> +	if (ret)
>>>>>>>> +		goto err_rpm_put;
>>>>>>>> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_H, crop->left);
>>>>>>>> +	if (ret)
>>>>>>>> +		goto err_rpm_put;
>>>>>>>> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_H, crop->left + crop->width - 1);
>>>>>>>> +	if (ret)
>>>>>>>> +		goto err_rpm_put;
>>>>>>>> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_V, crop->top);
>>>>>>>> +	if (ret)
>>>>>>>> +		goto err_rpm_put;
>>>>>>>> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_V, crop->top + crop->height - 1);
>>>>>>>> +	if (ret)
>>>>>>>> +		goto err_rpm_put;
>>>>>>>> +
>>>>>>>> +	ret = set_frame_rate(sensor);
>>>>>>>> +	if (ret)
>>>>>>>> +		goto err_rpm_put;
>>>>>>>> +
>>>>>>>> +	ret = apply_exposure(sensor);
>>>>>>>> +	if (ret)
>>>>>>>> +		goto err_rpm_put;
>>>>>>>> +
>>>>>>>> +	/* Start streaming */
>>>>>>>> +	ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_START_STREAMING);
>>>>>>>> +	if (ret)
>>>>>>>> +		goto err_rpm_put;
>>>>>>>> +
>>>>>>>> +	ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST, VGXY61_TIMEOUT_MS);
>>>>>>>> +	if (ret)
>>>>>>>> +		goto err_rpm_put;
>>>>>>>> +
>>>>>>>> +	ret = vgxy61_wait_state(sensor, STREAMING, VGXY61_TIMEOUT_MS);
>>>>>>>> +	if (ret)
>>>>>>>> +		goto err_rpm_put;
>>>>>>>> +
>>>>>>>> +	/* vflip and hflip cannot change during streaming */
>>>>>>>> +	__v4l2_ctrl_grab(sensor->vflip_ctrl, true);
>>>>>>>> +	__v4l2_ctrl_grab(sensor->hflip_ctrl, true);
>>>>>>>> +
>>>>>>>> +	return 0;
>>>>>>>> +
>>>>>>>> +err_rpm_put:
>>>>>>>> +	pm_runtime_put(&client->dev);
>>>>>>>> +	return ret;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +static int vgxy61_stream_disable(struct vgxy61_dev *sensor)
>>>>>>>> +{
>>>>>>>> +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd);
>>>>>>>> +	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, 2000);
>>>>>>>> +	if (ret)
>>>>>>>> +		return ret;
>>>>>>>> +
>>>>>>>> +	ret = vgxy61_wait_state(sensor, SW_STBY, VGXY61_TIMEOUT_MS);
>>>>>>>> +	if (ret)
>>>>>>>> +		return ret;
>>>>>>>> +
>>>>>>>> +	__v4l2_ctrl_grab(sensor->vflip_ctrl, true);
>>>>>>>> +	__v4l2_ctrl_grab(sensor->hflip_ctrl, true);
>>>>>>>> +
>>>>>>>> +	pm_runtime_put(&client->dev);
>>>>>>>> +
>>>>>>>> +	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;
>>>>>>>
>>>>>>> This should never happen. If you want to be on the safe side and keep
>>>>>>> the check, I'd turn it into a WARN_ON().
>>>>>>
>>>>>> You are right, I will remove this.
>>>>>>
>>>>>>>> +
>>>>>>>> +	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_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);q
>>>>>>>> +
>>>>>>>> +	return 0;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +static int vgxy61_get_vblank_min(struct vgxy61_dev *sensor, enum hdr hdr)
>>>>>>>> +{
>>>>>>>> +	int min_vblank =  VGXY61_MIN_FRAME_LENGTH - sensor->current_mode->crop.height;
>>>>>>>> +	/* Ensure the first tule of thumb can't be negative */
>>>>>>>> +	int min_vblank_hdr =  VGXY61_MIN_EXPOSURE + sensor->rot_term + 1;
>>>>>>>> +
>>>>>>>> +	if (hdr != NO_HDR)
>>>>>>>> +		return max(min_vblank, min_vblank_hdr);
>>>>>>>> +	return min_vblank;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +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_update_analog_gain(struct vgxy61_dev *sensor, u32 target)
>>>>>>>> +{
>>>>>>>> +	struct i2c_client *client = sensor->i2c_client;
>>>>>>>> +	int ret;
>>>>>>>> +
>>>>>>>> +	/* Apply gain */
>>>>>>>> +	ret = vgxy61_write_reg(sensor, DEVICE_ANALOG_GAIN, target);
>>>>>>>> +	if (ret)
>>>>>>>> +		return ret;
>>>>>>>> +
>>>>>>>> +	dev_dbg(&client->dev, "Set analog gain: 0x%04x\n", target);
>>>>>>>> +
>>>>>>>> +	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;
>>>>>>>
>>>>>>> Macros would be nice instead of magic numbers. Same where applicable
>>>>>>> below.
>>>>>>>
>>>>>>>> +	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};
>>>>>>>
>>>>>>> Make this static const.
>>>>>>>
>>>>>>>> +	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 < VGXY61_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 vgxy61_get_expo_long_max(struct vgxy61_dev *sensor, int short_expo_ratio)
>>>>>>>> +{
>>>>>>>> +	int first_rot_max_expo, second_rot_max_expo, third_rot_max_expo;
>>>>>>>> +
>>>>>>>> +	/* Apply sensor rule of thumbs */
>>>>>>>> +	/* Avoid bad pixel line at the botom of the image */
>>>>>>>> +	first_rot_max_expo =
>>>>>>>> +		((sensor->frame_length - sensor->current_mode->crop.height - sensor->rot_term) *
>>>>>>>> +		short_expo_ratio) - 1;
>>>>>>>> +
>>>>>>>> +	/* Avoid sensor crash */
>>>>>>>
>>>>>>> How so ?
>>>>>>
>>>>>> This specific rule guarantees the framelength < expo_time. If this is
>>>>>> not the case then the sensor will crash.
>>>>>>
>>>>>> I will add a bit more comment for this, and for other rules also.
>>>>>>
>>>>>>>> +	second_rot_max_expo =
>>>>>>>> +		(((sensor->frame_length - VGXY61_EXPOS_ROT_TERM) * short_expo_ratio) /
>>>>>>>> +		(short_expo_ratio + 1)) - 1;
>>>>>>>> +
>>>>>>>> +	/* Avoid sensor crash */
>>>>>>>> +	third_rot_max_expo = (sensor->frame_length / 71) * short_expo_ratio;
>>>>>>>> +
>>>>>>>> +	/* Take the minimum from all rules */
>>>>>>>> +	return min(min(first_rot_max_expo, second_rot_max_expo), third_rot_max_expo);
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +static int vgxy61_update_exposure(struct vgxy61_dev *sensor, int new_expo_long, enum hdr hdr)
>>>>>>>> +{
>>>>>>>> +	struct i2c_client *client = sensor->i2c_client;
>>>>>>>> +	int new_expo_short = 0;
>>>>>>>> +	int expo_short_max = 0;
>>>>>>>> +	int expo_long_min = VGXY61_MIN_EXPOSURE;
>>>>>>>> +	int expo_long_max;
>>>>>>>> +
>>>>>>>> +	/* Compute short exposure according to hdr mode and long exposure */
>>>>>>>> +	switch (hdr) {
>>>>>>>> +	case HDR_LINEAR:
>>>>>>>> +		/* Take ratio into account for minimal exposures in HDR_LINEAR */
>>>>>>>> +		expo_long_min = VGXY61_MIN_EXPOSURE * VGXY61_HDR_LINEAR_RATIO;
>>>>>>>> +		new_expo_long = max(expo_long_min, new_expo_long);
>>>>>>>> +
>>>>>>>> +		expo_long_max = vgxy61_get_expo_long_max(sensor, VGXY61_HDR_LINEAR_RATIO);
>>>>>>>> +		expo_short_max =
>>>>>>>> +			(expo_long_max + (VGXY61_HDR_LINEAR_RATIO / 2)) / VGXY61_HDR_LINEAR_RATIO;
>>>>>>>> +		new_expo_short =
>>>>>>>> +			(new_expo_long + (VGXY61_HDR_LINEAR_RATIO / 2)) / VGXY61_HDR_LINEAR_RATIO;
>>>>>>>> +		break;
>>>>>>>> +	case HDR_SUB:
>>>>>>>> +		new_expo_long = max(expo_long_min, new_expo_long);
>>>>>>>> +
>>>>>>>> +		expo_long_max = vgxy61_get_expo_long_max(sensor, 1);
>>>>>>>> +		/* Short and long are the same in HDR_SUB */
>>>>>>>> +		expo_short_max = expo_long_max;
>>>>>>>> +		new_expo_short = new_expo_long;
>>>>>>>> +		break;
>>>>>>>> +	case NO_HDR:
>>>>>>>> +		new_expo_long = max(expo_long_min, new_expo_long);
>>>>>>>> +
>>>>>>>> +		/*
>>>>>>>> +		 * As short expo is 0 here, only the second rule of thumb applies, see
>>>>>>>> +		 * vgxy61_get_expo_long_max for more
>>>>>>>> +		 */
>>>>>>>> +		expo_long_max = sensor->frame_length - VGXY61_EXPOS_ROT_TERM;
>>>>>>>> +		break;
>>>>>>>> +	default:
>>>>>>>> +		/* Should never happen */
>>>>>>>> +		WARN_ON(true);
>>>>>>>> +		break;
>>>>>>>> +	}
>>>>>>>> +
>>>>>>>> +	/* If this happens, something is wrong with formulas */
>>>>>>>> +	WARN_ON(expo_long_min > expo_long_max);
>>>>>>>> +
>>>>>>>> +	if (new_expo_long > expo_long_max) {
>>>>>>>> +		dev_warn(&client->dev, "Exposure %d too high, clamping to %d\n",
>>>>>>>> +			 new_expo_long, expo_long_max);
>>>>>>>> +		new_expo_long = expo_long_max;
>>>>>>>> +		new_expo_short = expo_short_max;
>>>>>>>> +	}
>>>>>>>> +
>>>>>>>> +	dev_dbg(&client->dev, "frame_length %d, new_expo_long %d, new_expo_short %d\n",
>>>>>>>> +		sensor->frame_length, new_expo_long, new_expo_short);
>>>>>>>> +	dev_dbg(&client->dev, "expo_long_min %d, expo_long_max %d\n",
>>>>>>>> +		expo_long_min, expo_long_max);
>>>>>>>> +
>>>>>>>> +	/* Apply exposure */
>>>>>>>> +	sensor->expo_long = new_expo_long;
>>>>>>>> +	sensor->expo_short = new_expo_short;
>>>>>>>> +	sensor->expo_max = expo_long_max;
>>>>>>>> +	sensor->expo_min = expo_long_min;
>>>>>>>> +
>>>>>>>> +	if (sensor->streaming)
>>>>>>>> +		return apply_exposure(sensor);
>>>>>>>> +	return 0;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +static int vgxy61_update_vblank(struct vgxy61_dev *sensor, int vblank, int hdr)
>>>>>>>> +{
>>>>>>>> +	int ret;
>>>>>>>> +
>>>>>>>> +	sensor->vblank_min = vgxy61_get_vblank_min(sensor, hdr);
>>>>>>>> +	sensor->vblank = max(sensor->vblank_min, vblank);
>>>>>>>> +	sensor->frame_length = sensor->current_mode->crop.height + sensor->vblank;
>>>>>>>> +
>>>>>>>> +	/* Update exposure according to vblank */
>>>>>>>> +	ret = vgxy61_update_exposure(sensor, sensor->expo_long, hdr);
>>>>>>>> +	if (ret)
>>>>>>>> +		return ret;
>>>>>>>> +
>>>>>>>> +	if (sensor->streaming)
>>>>>>>> +		return set_frame_rate(sensor);
>>>>>>>> +	return 0;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +static int vgxy61_update_hdr(struct vgxy61_dev *sensor, u32 index)
>>>>>>>> +{
>>>>>>>> +	const u8 index2val[] = {0x1, 0x4, 0xa};
>>>>>>>> +	int ret;
>>>>>>>> +
>>>>>>>> +	/*
>>>>>>>> +	 * vblank and short exposure change 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_vblank(sensor, sensor->vblank, index);
>>>>>>>> +	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_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);
>>>>>>>> +		*fmt = format->format;
>>>>>>>> +	} else if (sensor->current_mode != new_mode || sensor->fmt.code != format->format.code) {
>>>>>>>> +		fmt = &sensor->fmt;
>>>>>>>> +		*fmt = format->format;
>>>>>>>> +
>>>>>>>> +		sensor->current_mode = new_mode;
>>>>>>>> +
>>>>>>>> +		/* Reset vblank and framelength to default */
>>>>>>>> +		ret = vgxy61_update_vblank(sensor,
>>>>>>>> +					   VGXY61_FRAME_LENGTH_DEF -
>>>>>>>> +					   sensor->current_mode->crop.height,
>>>>>>>> +					   sensor->hdr);
>>>>>>>> +
>>>>>>>> +		/* Update controls to reflect new mode */
>>>>>>>> +		__v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate_ctrl, get_pixel_rate(sensor));
>>>>>>>> +		__v4l2_ctrl_modify_range(sensor->vblank_ctrl, sensor->vblank_min,
>>>>>>>> +					 0xffff - sensor->current_mode->crop.height, 1,
>>>>>>>> +					 sensor->vblank);
>>>>>>>> +		__v4l2_ctrl_s_ctrl(sensor->vblank_ctrl, sensor->vblank);
>>>>>>>> +		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
>>>>>>>> +					 sensor->expo_max, 1,
>>>>>>>> +					 sensor->expo_long);
>>>>>>>> +	}
>>>>>>>> +
>>>>>>>> +out:
>>>>>>>> +	mutex_unlock(&sensor->lock);
>>>>>>>> +
>>>>>>>> +	return ret;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +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);
>>>>>>>
>>>>>>> Can't runtime PM be disabled here ?
>>>>>>
>>>>>> You're absolutely right. This works as of now because I guess my
>>>>>> regulators are used for something else and therefore do not go down,
>>>>>> but should not.
>>>>>>
>>>>>> This is tricky as I don't want to power up (and therefore patch) the
>>>>>> sensor every time I want to see the temperature, but maybe I won't
>>>>>> have the choice.
>>>>>>
>>>>>> For example every time I stop streaming in qv4l2, the temperature is read. This would result in this:
>>>>>> 	power off -> get_temp -> power on (+patch) -> read temp -> power off
>>>>>> Maybe I can just remove the temperature reading while off? Any other idea?
>>>>>
>>>>> I think returning an error when the sensor is off is fine. The main use
>>>>> cases for the temperature sensor are related to imaging.
>>>>>
>>>>>> Anyway this will depend on the 2/5 patch discussion thread result.
>>>>>>
>>>>>>>> +	if (ret)
>>>>>>>> +		return ret;
>>>>>>>> +	ret = vgxy61_poll_reg(sensor, DEVICE_STBY, STBY_NO_REQ, VGXY61_TIMEOUT_MS);
>>>>>>>> +	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);
>>>>>>>> +		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);
>>>>>>>> +		/* Update vblank and exposure controls to match new hdr */
>>>>>>>> +		__v4l2_ctrl_modify_range(sensor->vblank_ctrl, sensor->vblank_min,
>>>>>>>> +					 0xffff - sensor->current_mode->crop.height, 1,
>>>>>>>> +					 sensor->vblank);
>>>>>>>> +		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
>>>>>>>> +					 sensor->expo_max, 1, sensor->expo_long);
>>>>>>>> +		break;
>>>>>>>> +	case V4L2_CID_VBLANK:
>>>>>>>> +		ret = vgxy61_update_vblank(sensor, ctrl->val, sensor->hdr);
>>>>>>>> +		/* Update exposure control to match new vblank */
>>>>>>>> +		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
>>>>>>>> +					 sensor->expo_max, 1, sensor->expo_long);
>>>>>>>> +		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 ret;
>>>>>>>> +
>>>>>>>> +	switch (ctrl->id) {
>>>>>>>> +	case V4L2_CID_TEMPERATURE:
>>>>>>>> +		ret = vgxy61_get_temp(sensor, &ctrl->val);
>>>>>>>> +		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 int vgxy61_init_controls(struct vgxy61_dev *sensor)
>>>>>>>> +{
>>>>>>>> +	const struct v4l2_ctrl_ops *ops = &vgxy61_ctrl_ops;
>>>>>>>> +	struct v4l2_ctrl_handler *hdl = &sensor->ctrl_handler;
>>>>>>>> +	struct v4l2_ctrl *ctrl;
>>>>>>>> +	int ret;
>>>>>>>> +
>>>>>>>> +	v4l2_ctrl_handler_init(hdl, 16);
>>>>>>>> +	mutex_init(&sensor->lock);
>>>>>>>
>>>>>>> This should be initialized in probe(), it doesn't belong here.
>>>>>>>
>>>>>>>> +	/* We can use our own mutex for the ctrl lock */
>>>>>>>> +	hdl->lock = &sensor->lock;
>>>>>>>> +	/* Standard controls */
>>>>>>>> +	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, 0x1c, 1, 0x0);
>>>>>>>> +	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 0, 0xfff, 1, 256);
>>>>>>>> +	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_TEMPERATURE, -128, 128, 1, 0);
>>>>>>>> +	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);
>>>>>>>> +	v4l2_ctrl_new_custom(hdl, &vgxy61_hdr_ctrl, NULL);
>>>>>>>> +	ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HBLANK, 0, sensor->line_length, 1,
>>>>>>>> +				 sensor->line_length - sensor->current_mode->width);
>>>>>>>> +	ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>>>>>>>> +	/* Custom controls */
>>>>>>>> +	ctrl = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ, ARRAY_SIZE(link_freq) - 1, 0,
>>>>>>>> +				      link_freq);
>>>>>>>> +	ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>>>>>>>> +	/* Keep a pointer to these controls as we need to update them when setting the format */
>>>>>>>> +	sensor->pixel_rate_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1,
>>>>>>>> +						    get_pixel_rate(sensor));
>>>>>>>> +	sensor->pixel_rate_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>>>>>>>> +	sensor->expo_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, sensor->expo_min,
>>>>>>>> +					      sensor->expo_max, 1, sensor->expo_long);
>>>>>>>> +	sensor->vblank_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VBLANK, sensor->vblank_min,
>>>>>>>> +						0xffff - sensor->current_mode->crop.height, 1,
>>>>>>>> +						sensor->vblank);
>>>>>>>> +	sensor->vflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, sensor->vflip);
>>>>>>>> +	sensor->hflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, sensor->hflip);
>>>>>>>> +
>>>>>>>> +	if (hdl->error) {
>>>>>>>> +		ret = hdl->error;
>>>>>>>> +		goto free_ctrls;
>>>>>>>> +	}
>>>>>>>> +
>>>>>>>> +	sensor->sd.ctrl_handler = hdl;
>>>>>>>> +	return 0;
>>>>>>>> +
>>>>>>>> +free_ctrls:
>>>>>>>> +	v4l2_ctrl_handler_free(hdl);
>>>>>>>> +	mutex_destroy(&sensor->lock);
>>>>>>>> +	return ret;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +static const struct v4l2_subdev_core_ops vgxy61_core_ops = {
>>>>>>>> +};
>>>>>>>
>>>>>>> You can drop this.
>>>>>>>
>>>>>>>> +
>>>>>>>> +static const struct v4l2_subdev_video_ops vgxy61_video_ops = {
>>>>>>>> +	.s_stream = vgxy61_s_stream,
>>>>>>>> +};
>>>>>>>> +
>>>>>>>> +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,
>>>>>>>> +	.get_selection = vgxy61_get_selection,
>>>>>>>> +	.enum_frame_size = vgxy61_enum_frame_size,
>>>>>>>> +};
>>>>>>>> +
>>>>>>>> +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[VGXY61_NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
>>>>>>>> +	u32 phy2log[VGXY61_NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
>>>>>>>> +	int polarities[VGXY61_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 < VGXY61_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;
>>>>>>>> +	/* 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, VGXY61_TIMEOUT_MS);
>>>>>>>> +	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_dbg(&client->dev, "Cut1 detected\n");
>>>>>>>> +		dev_err(&client->dev, "Cut1 not supported by this driver\n");
>>>>>>>> +		return -ENODEV;
>>>>>>>> +	case 0xB:
>>>>>>>> +		dev_dbg(&client->dev, "Cut2 detected\n");
>>>>>>>> +		return 0;
>>>>>>>> +	case 0xC:
>>>>>>>> +		dev_dbg(&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);
>>>>>>>> +	sensor->id = id;
>>>>>>>> +
>>>>>>>> +	ret = vgxy61_wait_state(sensor, SW_STBY, VGXY61_TIMEOUT_MS);
>>>>>>>> +	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;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +/* Power/clock management functions */
>>>>>>>> +static int vgxy61_power_on(struct device *dev)
>>>>>>>> +{
>>>>>>>> +	struct i2c_client *client = to_i2c_client(dev);
>>>>>>>> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
>>>>>>>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>>>>>> +	int ret;
>>>>>>>> +
>>>>>>>> +	ret = vgxy61_get_regulators(sensor);
>>>>>>>> +	if (ret) {
>>>>>>>> +		dev_err(&client->dev, "failed to get regulators %d\n", ret);
>>>>>>>> +		return ret;
>>>>>>>> +	}
>>>>>>>> +
>>>>>>>> +	ret = regulator_bulk_enable(VGXY61_NUM_SUPPLIES, sensor->supplies);
>>>>>>>> +	if (ret) {
>>>>>>>> +		dev_err(&client->dev, "failed to enable regulators %d\n", ret);
>>>>>>>> +		return ret;
>>>>>>>> +	}
>>>>>>>> +
>>>>>>>> +	ret = clk_prepare_enable(sensor->xclk);
>>>>>>>> +	if (ret) {
>>>>>>>> +		dev_err(&client->dev, "failed to enable clock %d\n", ret);
>>>>>>>> +		goto disable_bulk;
>>>>>>>> +	}
>>>>>>>> +
>>>>>>>> +	/* 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);
>>>>>>>
>>>>>>> Do we really need to run detection every time the sensor is powered on ?
>>>>>>
>>>>>> Surely not, I will add a if (sensor->already_detected) guard for this
>>>>>> call to make it runs only once.
>>>>>
>>>>> It could also be done in probe().
>>>>>
>>>>>>>> +	if (ret) {
>>>>>>>> +		dev_err(&client->dev, "sensor detect failed %d\n", ret);
>>>>>>>> +		goto disable_clock;
>>>>>>>> +	}
>>>>>>>> +
>>>>>>>> +	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;
>>>>>>>> +	}
>>>>>>>> +
>>>>>>>> +	return 0;
>>>>>>>> +
>>>>>>>> +disable_clock:
>>>>>>>> +	clk_disable_unprepare(sensor->xclk);
>>>>>>>> +disable_bulk:
>>>>>>>> +	regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
>>>>>>>> +
>>>>>>>> +	return ret;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +static int vgxy61_power_off(struct device *dev)
>>>>>>>> +{
>>>>>>>> +	struct i2c_client *client = to_i2c_client(dev);
>>>>>>>> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
>>>>>>>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>>>>>> +
>>>>>>>> +	clk_disable_unprepare(sensor->xclk);
>>>>>>>> +	regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies);
>>>>>>>> +	return 0;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +static int __maybe_unused vgxy61_suspend(struct device *dev)
>>>>>>>> +{
>>>>>>>> +	struct i2c_client *client = to_i2c_client(dev);
>>>>>>>> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
>>>>>>>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>>>>>> +
>>>>>>>> +	if (sensor->streaming)
>>>>>>>> +		vgxy61_stream_disable(sensor);
>>>>>>>
>>>>>>> This shoudn't happen. The sensor should be stopped by the driver on the
>>>>>>> receiver side during system suspend. Same for resume.
>>>>>>
>>>>>> Sorry I'm still learning the pm_runtime API and I'm not sure I got your point.
>>>>>> If the sensor is already stopped then I could just remove the whole suspend function? Same for resume.
>>>>>> I'm not sure when the suspend function is called by the framework.
>>>>>
>>>>> vgxy61_suspend() is called on system suspend. Disabling streaming here
>>>>> is problematic, as it's done independently from the CSI-2 receiver. It's
>>>>> even worse on resume, as if you resume the sensor first, the CSI-2 lanes
>>>>> will go to HS mode before the receiver is started, and the receiver will
>>>>> never be able to synchronized. That's why the CSI-2 receiver resume
>>>>> handler needs to restart streaming by calling the sensor .s_stream()
>>>>> operation. Same thing at suspend time. Most sensors will thus not need
>>>>> system suspend/resume handlers.
>>>>>
>>>>> This means that the CSI-2 receiver needs to be suspended before the
>>>>> sensor, and resumed after. You can achieve this with the device link
>>>>> API, calling device_link_add(csi2_dev, sensor_dev, DL_FLAG_STATELESS)
>>>>> in the CSI-2 receiver driver.
>>>>
>>>> Let me rephrase your comment just to make sure I got everything right.
>>>>
>>>> On system suspend, the CSI-2 receiver driver should already have
>>>> stopped the stream for me, so this is irrelevant.
>>>>
>>>> On resume, if by any chance I try to resume streaming before the CSI2
>>>> receiver is resuming things will go terribly wrong.
>>>>
>>>> So this means I should trust the CSI2 driver to have performed
>>>> device_link_add on its side, and can delete the vgxy61 resume and
>>>> suspend ops.
>>>
>>> Correct.
>>>
>>>> I have trouble understanding why my use case differs from the imx219
>>>> for instance, that afaik does the same thing.
>>>
>>> Your use case doesn't differ, but we have lots of sensors drivers that
>>> have been developed over time while our understanding of power
>>> management evolved, and older drivers haven't been updated to the latest
>>> best practices. This results in lots of cargo-cult for new drivers too,
>>> it's not a great situation.
>>
>> I understand, thanks a lot for your explanations.
>>
>>>>>>>> +
>>>>>>>> +	return 0;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +static int __maybe_unused vgxy61_resume(struct device *dev)
>>>>>>>> +{
>>>>>>>> +	struct i2c_client *client = to_i2c_client(dev);
>>>>>>>> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
>>>>>>>> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
>>>>>>>> +	int ret;
>>>>>>>> +
>>>>>>>> +	if (sensor->streaming) {
>>>>>>>> +		ret = vgxy61_stream_enable(sensor);
>>>>>>>> +		if (ret)
>>>>>>>> +			goto error;
>>>>>>>> +	}
>>>>>>>> +
>>>>>>>> +	return 0;
>>>>>>>> +
>>>>>>>> +error:
>>>>>>>> +	vgxy61_stream_disable(sensor);
>>>>>>>> +	sensor->streaming = false;
>>>>>>>> +
>>>>>>>> +	return ret;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +static void vgxy61_set_default_format(struct vgxy61_dev *sensor)
>>>>>>>> +{
>>>>>>>> +	if (sensor->id == VG5761_MODEL_ID) {
>>>>>>>
>>>>>>> Would it make sense to have different compatible strings instead of
>>>>>>> detecting the version automatically ? Not only could it speed up probe
>>>>>>> if we could skip the detection, it would also allow not powering the
>>>>>>> sensor up at probe time, which may be required in some cases (for
>>>>>>> instance when a privacy LED is connected to the sensor power supply,
>>>>>>> flashing the LED at probe time can be very confusing for users).
>>>>>>
>>>>>> Your flashing LED example is a very good concern.
>>>>>>
>>>>>> The idea behind this is to ease the user life as he does not have to
>>>>>> know what model he is using.
>>>>>
>>>>> I agree with you, I've implemented the same in sensor drivers. One
>>>>> option may be to have a generic compatible string that would lead to
>>>>> model detection at probe time, and specific compatible strings that
>>>>> would skip it and avoid powering up the sensor. As long as we consider
>>>>> the requirements on the DT side and make sure to have a way forward
>>>>> without breaking backward compatibility, this can also be implemented
>>>>> later.
>>>>
>>>> This is a very good idea.
>>>>
>>>>>> For example we have several boards with either the VG5761 or VG5661
>>>>>> and are interchangeable without changing the device tree, which is
>>>>>> also better for us as it shrinks the device tree number to maintain.
>>>>>>
>>>>>> Also as a company we treat them the same way, hence the same driver.
>>>>>> I'm not hardly commited to keep this, but it indeed makes my life easier.
>>>>>>
>>>>>>>> +		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;
>>>>>>>> +	} else if (sensor->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;
>>>>>>>> +	} else {
>>>>>>>> +		/* Should never happen */
>>>>>>>> +		WARN_ON(true);
>>>>>>>> +	}
>>>>>>>> +	sensor->fmt.width = sensor->current_mode->width;
>>>>>>>> +	sensor->fmt.height = sensor->current_mode->height;
>>>>>>>> +	sensor->vblank = sensor->frame_length - sensor->current_mode->crop.height;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +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_Y8_1X8;
>>>>>>>> +	sensor->fmt.colorspace = V4L2_COLORSPACE_RAW;
>>>>>>>> +	sensor->fmt.field = V4L2_FIELD_NONE;
>>>>>>>> +	sensor->fmt.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
>>>>>>>> +	sensor->fmt.quantization = V4L2_QUANTIZATION_DEFAULT;
>>>>>>>> +	sensor->fmt.xfer_func = V4L2_XFER_FUNC_DEFAULT;
>>>>>>>
>>>>>>> Please implement the subdev .init_cfg() operation, and call it here.
>>>>>>> There are several examples in existing drivers.
>>>>>>>
>>>>>>>> +	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;
>>>>>>>> +	sensor->frame_length = VGXY61_FRAME_LENGTH_DEF;
>>>>>>>> +
>>>>>>>> +	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, NULL);
>>>>>>>> +	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 < 6 * HZ_PER_MHZ || sensor->clk_freq > 27 * HZ_PER_MHZ) {
>>>>>>>> +		dev_err(dev, "Only 6Mhz-27Mhz clock range supported. provide %lu MHz\n",
>>>>>>>> +			sensor->clk_freq / HZ_PER_MHZ);
>>>>>>>> +		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;
>>>>>>>> +
>>>>>>>> +	/* Request optional reset pin */
>>>>>>>> +	sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
>>>>>>>> +
>>>>>>>> +	ret = vgxy61_power_on(dev);
>>>>>>>> +	if (ret)
>>>>>>>> +		return ret;
>>>>>>>> +
>>>>>>>> +	vgxy61_set_default_format(sensor);
>>>>>>>> +
>>>>>>>> +	/* HDR mode */
>>>>>>>> +	ret = vgxy61_update_hdr(sensor, sensor->hdr);
>>>>>>>> +	if (ret)
>>>>>>>> +		return ret;
>>>>>>>> +
>>>>>>>> +	ret = vgxy61_init_controls(sensor);
>>>>>>>> +	if (ret) {
>>>>>>>> +		dev_err(&client->dev, "controls initialization failed %d\n", ret);
>>>>>>>> +		goto error_power_off;
>>>>>>>> +	}
>>>>>>>> +
>>>>>>>> +	ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
>>>>>>>> +	if (ret) {
>>>>>>>> +		dev_err(&client->dev, "pads init failed %d\n", ret);
>>>>>>>> +		goto error_handler_free;
>>>>>>>> +	}
>>>>>>>> +
>>>>>>>> +	ret = v4l2_async_register_subdev(&sensor->sd);
>>>>>>>> +	if (ret) {
>>>>>>>> +		dev_err(&client->dev, "async subdev register failed %d\n", ret);
>>>>>>>> +		goto error_media_entity;
>>>>>>>> +	}
>>>>>>>> +
>>>>>>>> +	/* Enable runtime PM and turn off the device */
>>>>>>>> +	pm_runtime_set_active(dev);
>>>>>>>> +	pm_runtime_enable(dev);
>>>>>>>> +	pm_runtime_idle(dev);
>>>>>>>
>>>>>>> You should do this before registering the subdev, as it can be accessed
>>>>>>> from userspace right after being registered.
>>>>>>>
>>>>>>> Please also use auto-suspend to avoid long delays when stopping and
>>>>>>> restarting streaming quickly.
>>>>>>
>>>>>> I was not aware of autosuspend. Thanks a lot, I will look into this.
>>>>>>
>>>>>>>> +
>>>>>>>> +	dev_info(&client->dev, "vgxy61 probe successfully\n");
>>>>>>>
>>>>>>> I'd drop this, or turn it into a debug message.
>>>>>>>
>>>>>>>> +
>>>>>>>> +	return 0;
>>>>>>>> +
>>>>>>>> +error_media_entity:
>>>>>>>> +	media_entity_cleanup(&sensor->sd.entity);
>>>>>>>> +error_handler_free:
>>>>>>>> +	v4l2_ctrl_handler_free(sensor->sd.ctrl_handler);
>>>>>>>> +	mutex_destroy(&sensor->lock);
>>>>>>>> +error_power_off:
>>>>>>>> +	vgxy61_power_off(dev);
>>>>>>>> +
>>>>>>>> +	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);
>>>>>>>> +	mutex_destroy(&sensor->lock);
>>>>>>>> +	media_entity_cleanup(&sensor->sd.entity);
>>>>>>>> +
>>>>>>>> +	pm_runtime_disable(&client->dev);
>>>>>>>> +	if (!pm_runtime_status_suspended(&client->dev))
>>>>>>>> +		vgxy61_power_off(&client->dev);
>>>>>>>> +	pm_runtime_set_suspended(&client->dev);
>>>>>>>> +
>>>>>>>> +	return 0;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +static const struct of_device_id vgxy61_dt_ids[] = {
>>>>>>>> +	{ .compatible = "st,st-vgxy61" },
>>>>>>>> +	{ /* sentinel */ }
>>>>>>>> +};
>>>>>>>> +MODULE_DEVICE_TABLE(of, vgxy61_dt_ids);
>>>>>>>> +
>>>>>>>> +static const struct dev_pm_ops vgxy61_pm_ops = {
>>>>>>>> +	SET_SYSTEM_SLEEP_PM_OPS(vgxy61_suspend, vgxy61_resume)
>>>>>>>> +	SET_RUNTIME_PM_OPS(vgxy61_power_off, vgxy61_power_on, NULL)
>>>>>>>> +};
>>>>>>>> +
>>>>>>>> +static struct i2c_driver vgxy61_i2c_driver = {
>>>>>>>> +	.driver = {
>>>>>>>> +		.name  = "st-vgxy61",
>>>>>>>> +		.of_match_table = vgxy61_dt_ids,
>>>>>>>> +		.pm = &vgxy61_pm_ops,
>>>>>>>> +	},
>>>>>>>> +	.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");
> 

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

end of thread, other threads:[~2022-04-27 14:27 UTC | newest]

Thread overview: 33+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-04-15 11:18 [PATCH v2 0/5] media: Add ST VGXY61 camera sensor driver Benjamin Mugnier
2022-04-15 11:18 ` [PATCH v2 1/5] media: v4l: Add 1X16 16-bit greyscale media bus code definition Benjamin Mugnier
2022-04-15 14:47   ` Laurent Pinchart
2022-04-15 11:18 ` [PATCH v2 2/5] media: v4l: ctrls: Add a control for temperature Benjamin Mugnier
2022-04-15 14:37   ` Laurent Pinchart
2022-04-20 13:01     ` Benjamin Mugnier
2022-04-19  7:03   ` Hans Verkuil
2022-04-20 13:01     ` Benjamin Mugnier
2022-04-19 18:24   ` Nicolas Dufresne
2022-04-19 19:28     ` Guenter Roeck
2022-04-19 21:01       ` Laurent Pinchart
2022-04-19 22:04         ` Guenter Roeck
2022-04-20 13:01           ` Benjamin Mugnier
2022-04-20 13:21             ` Laurent Pinchart
2022-04-20 13:58               ` Guenter Roeck
2022-04-20 14:23                 ` Laurent Pinchart
2022-04-20 15:19                   ` Benjamin Mugnier
2022-04-20 16:19                     ` Guenter Roeck
2022-04-20 16:51                       ` Laurent Pinchart
2022-04-15 11:18 ` [PATCH v2 3/5] media: v4l: ctrls: Add user control base for st-vgxy61 controls Benjamin Mugnier
2022-04-15 14:51   ` Laurent Pinchart
2022-04-15 11:18 ` [PATCH v2 4/5] media: dt-bindings: media: i2c: Add ST VGXY61 camera sensor binding Benjamin Mugnier
2022-04-15 14:59   ` Laurent Pinchart
2022-04-20 13:01     ` Benjamin Mugnier
2022-04-15 11:18 ` [PATCH v2 5/5] media: i2c: Add driver for ST VGXY61 camera sensor Benjamin Mugnier
2022-04-15 15:39   ` Laurent Pinchart
2022-04-20 13:01     ` Benjamin Mugnier
2022-04-20 16:48       ` Laurent Pinchart
2022-04-22  8:44         ` Benjamin Mugnier
2022-04-22 13:29           ` Laurent Pinchart
2022-04-22 14:40             ` Benjamin Mugnier
2022-04-24 16:34               ` Laurent Pinchart
2022-04-27 14:27                 ` Benjamin Mugnier

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