Merge git://linuxtv.org/sailus/media_tree into media_stage
authorMauro Carvalho Chehab <mchehab@kernel.org>
Tue, 8 Nov 2022 08:46:21 +0000 (08:46 +0000)
committerMauro Carvalho Chehab <mchehab@kernel.org>
Tue, 8 Nov 2022 08:46:21 +0000 (08:46 +0000)
* git://linuxtv.org/sailus/media_tree: (47 commits)
  media: i2c: ov4689: code cleanup
  media: ov9650: Drop platform data code path
  media: ov7670: Drop unused include
  media: ov2640: Drop legacy includes
  media: tc358746: add Toshiba TC358746 Parallel to CSI-2 bridge driver
  media: dt-bindings: add bindings for Toshiba TC358746
  phy: dphy: add support to calculate the timing based on hs_clk_rate
  phy: dphy: refactor get_default_config
  v4l: subdev: Warn if disabling streaming failed, return success
  dw9768: Enable low-power probe on ACPI
  media: i2c: imx290: Replace GAIN control with ANALOGUE_GAIN
  media: i2c: imx290: Add crop selection targets support
  media: i2c: imx290: Factor out format retrieval to separate function
  media: i2c: imx290: Move registers with fixed value to init array
  media: i2c: imx290: Create controls for fwnode properties
  media: i2c: imx290: Implement HBLANK and VBLANK controls
  media: i2c: imx290: Split control initialization to separate function
  media: i2c: imx290: Fix max gain value
  media: i2c: imx290: Add exposure time control
  media: i2c: imx290: Define more register macros
  ...

36 files changed:
Documentation/devicetree/bindings/media/i2c/imx290.txt [deleted file]
Documentation/devicetree/bindings/media/i2c/ovti,ov4689.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/media/i2c/sony,imx290.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/media/i2c/toshiba,tc358746.yaml [new file with mode: 0644]
Documentation/userspace-api/media/drivers/st-vgxy61.rst [new file with mode: 0644]
Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst
Documentation/userspace-api/media/v4l/subdev-formats.rst
MAINTAINERS
drivers/media/i2c/Kconfig
drivers/media/i2c/Makefile
drivers/media/i2c/ad5820.c
drivers/media/i2c/dw9768.c
drivers/media/i2c/hi846.c
drivers/media/i2c/imx290.c
drivers/media/i2c/ov08x40.c [new file with mode: 0644]
drivers/media/i2c/ov2640.c
drivers/media/i2c/ov4689.c [new file with mode: 0644]
drivers/media/i2c/ov5645.c
drivers/media/i2c/ov5648.c
drivers/media/i2c/ov7670.c
drivers/media/i2c/ov8856.c
drivers/media/i2c/ov9650.c
drivers/media/i2c/st-vgxy61.c [new file with mode: 0644]
drivers/media/i2c/tc358746.c [new file with mode: 0644]
drivers/media/platform/samsung/exynos4-is/media-dev.c
drivers/media/v4l2-core/v4l2-ctrls-defs.c
drivers/media/v4l2-core/v4l2-subdev.c
drivers/phy/phy-core-mipi-dphy.c
drivers/staging/media/ipu3/ipu3-v4l2.c
include/linux/phy/phy-mipi-dphy.h
include/media/i2c/ov9650.h [deleted file]
include/media/media-entity.h
include/media/v4l2-subdev.h
include/uapi/linux/media-bus-format.h
include/uapi/linux/v4l2-controls.h

diff --git a/Documentation/devicetree/bindings/media/i2c/imx290.txt b/Documentation/devicetree/bindings/media/i2c/imx290.txt
deleted file mode 100644 (file)
index a3cc214..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-* Sony IMX290 1/2.8-Inch CMOS Image Sensor
-
-The Sony IMX290 is a 1/2.8-Inch CMOS Solid-state image sensor with
-Square Pixel for Color Cameras. It is programmable through I2C and 4-wire
-interfaces. The sensor output is available via CMOS logic parallel SDR output,
-Low voltage LVDS DDR output and CSI-2 serial data output. The CSI-2 bus is the
-default. No bindings have been defined for the other busses.
-
-Required Properties:
-- compatible: Should be "sony,imx290"
-- reg: I2C bus address of the device
-- clocks: Reference to the xclk clock.
-- clock-names: Should be "xclk".
-- clock-frequency: Frequency of the xclk clock in Hz.
-- vdddo-supply: Sensor digital IO regulator.
-- vdda-supply: Sensor analog regulator.
-- vddd-supply: Sensor digital core regulator.
-
-Optional Properties:
-- reset-gpios: Sensor reset GPIO
-
-The imx290 device node should contain one 'port' child node with
-an 'endpoint' subnode. For further reading on port node refer to
-Documentation/devicetree/bindings/media/video-interfaces.txt.
-
-Required Properties on endpoint:
-- data-lanes: check ../video-interfaces.txt
-- link-frequencies: check ../video-interfaces.txt
-- remote-endpoint: check ../video-interfaces.txt
-
-Example:
-       &i2c1 {
-               ...
-               imx290: camera-sensor@1a {
-                       compatible = "sony,imx290";
-                       reg = <0x1a>;
-
-                       reset-gpios = <&msmgpio 35 GPIO_ACTIVE_LOW>;
-                       pinctrl-names = "default";
-                       pinctrl-0 = <&camera_rear_default>;
-
-                       clocks = <&gcc GCC_CAMSS_MCLK0_CLK>;
-                       clock-names = "xclk";
-                       clock-frequency = <37125000>;
-
-                       vdddo-supply = <&camera_vdddo_1v8>;
-                       vdda-supply = <&camera_vdda_2v8>;
-                       vddd-supply = <&camera_vddd_1v5>;
-
-                       port {
-                               imx290_ep: endpoint {
-                                       data-lanes = <1 2 3 4>;
-                                       link-frequencies = /bits/ 64 <445500000>;
-                                       remote-endpoint = <&csiphy0_ep>;
-                               };
-                       };
-               };
diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,ov4689.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,ov4689.yaml
new file mode 100644 (file)
index 0000000..50579c9
--- /dev/null
@@ -0,0 +1,134 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/i2c/ovti,ov4689.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Omnivision OV4689 CMOS
+
+maintainers:
+  - Mikhail Rudenko <mike.rudenko@gmail.com>
+
+description: |
+  The Omnivision OV4689 is a high performance, 1/3-inch, 4 megapixel
+  image sensor. Ihis chip supports high frame rate speeds up to 90 fps
+  at 2688x1520 resolution. It is programmable through an I2C
+  interface, and sensor output is sent via 1/2/4 lane MIPI CSI-2
+  connection.
+
+allOf:
+  - $ref: /schemas/media/video-interface-devices.yaml#
+
+properties:
+  compatible:
+    const: ovti,ov4689
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    description:
+      External clock (XVCLK) for the sensor, 6-64 MHz
+    maxItems: 1
+
+  dovdd-supply:
+    description:
+      Digital I/O voltage supply, 1.7-3.0 V
+
+  avdd-supply:
+    description:
+      Analog voltage supply, 2.6-3.0 V
+
+  dvdd-supply:
+    description:
+      Digital core voltage supply, 1.1-1.3 V
+
+  powerdown-gpios:
+    description:
+      GPIO connected to the powerdown pin (active low)
+
+  reset-gpios:
+    maxItems: 1
+    description:
+      GPIO connected to the reset pin (active low)
+
+  orientation: true
+
+  rotation: true
+
+  port:
+    $ref: /schemas/graph.yaml#/$defs/port-base
+    additionalProperties: false
+    description:
+      Output port node, single endpoint describing the CSI-2 transmitter
+
+    properties:
+      endpoint:
+        $ref: /schemas/media/video-interfaces.yaml#
+        unevaluatedProperties: false
+
+        properties:
+          data-lanes:
+            oneOf:
+              - items:
+                  - const: 1
+                  - const: 2
+                  - const: 3
+                  - const: 4
+              - items:
+                  - const: 1
+                  - const: 2
+              - items:
+                  - const: 1
+          link-frequencies: true
+
+        required:
+          - data-lanes
+          - link-frequencies
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - dovdd-supply
+  - avdd-supply
+  - dvdd-supply
+  - port
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        ov4689: camera@36 {
+            compatible = "ovti,ov4689";
+            reg = <0x36>;
+
+            clocks = <&ov4689_clk>;
+
+            avdd-supply = <&ov4689_avdd>;
+            dovdd-supply = <&ov4689_dovdd>;
+            dvdd-supply = <&ov4689_dvdd>;
+
+            powerdown-gpios = <&pio 107 GPIO_ACTIVE_LOW>;
+            reset-gpios = <&pio 109 GPIO_ACTIVE_LOW>;
+
+            orientation = <2>;
+            rotation = <0>;
+
+            port {
+                wcam_out: endpoint {
+                    remote-endpoint = <&mipi_in_wcam>;
+                    data-lanes = <1 2 3 4>;
+                    link-frequencies = /bits/ 64 <504000000>;
+                };
+            };
+        };
+    };
+
+...
diff --git a/Documentation/devicetree/bindings/media/i2c/sony,imx290.yaml b/Documentation/devicetree/bindings/media/i2c/sony,imx290.yaml
new file mode 100644 (file)
index 0000000..21377da
--- /dev/null
@@ -0,0 +1,129 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/i2c/sony,imx290.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Sony IMX290 1/2.8-Inch CMOS Image Sensor
+
+maintainers:
+  - Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
+  - Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+
+description: |-
+  The Sony IMX290 is a 1/2.8-Inch CMOS Solid-state image sensor with Square
+  Pixel for Color Cameras. It is programmable through I2C and 4-wire
+  interfaces. The sensor output is available via CMOS logic parallel SDR
+  output, Low voltage LVDS DDR output and CSI-2 serial data output. The CSI-2
+  bus is the default. No bindings have been defined for the other busses.
+
+properties:
+  compatible:
+    enum:
+      - sony,imx290
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  clock-names:
+    description: Input clock (37.125 MHz or 74.25 MHz)
+    items:
+      - const: xclk
+
+  clock-frequency:
+    description: Frequency of the xclk clock in Hz
+
+  vdda-supply:
+    description: Analog power supply (2.9V)
+
+  vddd-supply:
+    description: Digital core power supply (1.2V)
+
+  vdddo-supply:
+    description: Digital I/O power supply (1.8V)
+
+  reset-gpios:
+    description: Sensor reset (XCLR) GPIO
+    maxItems: 1
+
+  port:
+    $ref: /schemas/graph.yaml#/$defs/port-base
+    description: |
+      Video output port
+
+    properties:
+      endpoint:
+        $ref: /schemas/media/video-interfaces.yaml#
+        unevaluatedProperties: false
+
+        properties:
+          data-lanes:
+            anyOf:
+              - items:
+                  - const: 1
+                  - const: 2
+              - items:
+                  - const: 1
+                  - const: 2
+                  - const: 3
+                  - const: 4
+
+          link-frequencies: true
+
+        required:
+          - data-lanes
+          - link-frequencies
+
+    additionalProperties: false
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - clock-names
+  - clock-frequency
+  - vdda-supply
+  - vddd-supply
+  - vdddo-supply
+  - port
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        imx290: camera-sensor@1a {
+            compatible = "sony,imx290";
+            reg = <0x1a>;
+
+            pinctrl-names = "default";
+            pinctrl-0 = <&camera_rear_default>;
+
+            clocks = <&gcc 90>;
+            clock-names = "xclk";
+            clock-frequency = <37125000>;
+
+            vdddo-supply = <&camera_vdddo_1v8>;
+            vdda-supply = <&camera_vdda_2v8>;
+            vddd-supply = <&camera_vddd_1v5>;
+
+            reset-gpios = <&msmgpio 35 GPIO_ACTIVE_LOW>;
+
+            port {
+                imx290_ep: endpoint {
+                    data-lanes = <1 2 3 4>;
+                    link-frequencies = /bits/ 64 <445500000>;
+                    remote-endpoint = <&csiphy0_ep>;
+                };
+            };
+        };
+    };
+...
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 (file)
index 0000000..6597e1d
--- /dev/null
@@ -0,0 +1,113 @@
+# 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
+
+  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.
+
+  st,strobe-gpios-polarity:
+    description:
+      Invert polarity of illuminator's lights strobe GPIOs.
+      These GPIOs directly drive the illuminator LEDs.
+    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:
+          - data-lanes
+
+required:
+  - compatible
+  - clocks
+  - VCORE-supply
+  - VDDIO-supply
+  - VANA-supply
+  - port
+
+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>;
+            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/Documentation/devicetree/bindings/media/i2c/toshiba,tc358746.yaml b/Documentation/devicetree/bindings/media/i2c/toshiba,tc358746.yaml
new file mode 100644 (file)
index 0000000..b8ba85a
--- /dev/null
@@ -0,0 +1,178 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/i2c/toshiba,tc358746.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Toshiba TC358746 Parallel to MIPI CSI2 Bridge
+
+maintainers:
+  - Marco Felsch <kernel@pengutronix.de>
+
+description: |-
+  The Toshiba TC358746 converts a parallel video stream into a MIPI CSI-2
+  stream. The direction can be either parallel-in -> csi-out or csi-in ->
+  parallel-out The chip is programmable trough I2C and SPI but the SPI
+  interface is only supported in parallel-in -> csi-out mode.
+
+  Note that the current device tree bindings only support the
+  parallel-in -> csi-out path.
+
+properties:
+  compatible:
+    const: toshiba,tc358746
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    description:
+      The phandle to the reference clock source. This corresponds to the
+      hardware pin REFCLK.
+    maxItems: 1
+
+  clock-names:
+    const: refclk
+
+  "#clock-cells":
+    description: |
+      The bridge can act as clock provider for the sensor. To enable this
+      support #clock-cells must be specified. Attention if this feature is used
+      then the mclk rate must be at least: (2 * link-frequency) / 8
+                                           `------------------´   ^
+                                           internal PLL rate   smallest possible
+                                                                   mclk-div
+    const: 0
+
+  clock-output-names:
+    description:
+      The clock name of the MCLK output, the default name is tc358746-mclk.
+    maxItems: 1
+
+  vddc-supply:
+    description: Digital core voltage supply, 1.2 volts
+
+  vddio-supply:
+    description: Digital I/O voltage supply, 1.8 volts
+
+  vddmipi-supply:
+    description: MIPI CSI phy voltage supply, 1.2 volts
+
+  reset-gpios:
+    description:
+      The phandle and specifier for the GPIO that controls the chip reset.
+      This corresponds to the hardware pin RESX which is physically active low.
+    maxItems: 1
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+    properties:
+      port@0:
+        $ref: /schemas/graph.yaml#/$defs/port-base
+        description: Input port
+
+        properties:
+          endpoint:
+            $ref: /schemas/media/video-interfaces.yaml#
+            unevaluatedProperties: false
+
+            properties:
+              hsync-active: true
+              vsync-active: true
+              bus-type:
+                enum: [ 5, 6 ]
+
+            required:
+              - hsync-active
+              - vsync-active
+              - bus-type
+
+      port@1:
+        $ref: /schemas/graph.yaml#/$defs/port-base
+        description: Output port
+
+        properties:
+          endpoint:
+            $ref: /schemas/media/video-interfaces.yaml#
+            unevaluatedProperties: false
+
+            properties:
+              data-lanes:
+                minItems: 1
+                maxItems: 4
+
+              clock-noncontinuous: true
+              link-frequencies: true
+
+            required:
+              - data-lanes
+              - link-frequencies
+
+    required:
+      - port@0
+      - port@1
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - clock-names
+  - vddc-supply
+  - vddio-supply
+  - vddmipi-supply
+  - ports
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+
+    i2c {
+      #address-cells = <1>;
+      #size-cells = <0>;
+
+      csi-bridge@e {
+        compatible = "toshiba,tc358746";
+        reg = <0xe>;
+
+        clocks = <&refclk>;
+        clock-names = "refclk";
+
+        reset-gpios = <&gpio 2 GPIO_ACTIVE_LOW>;
+
+        vddc-supply = <&v1_2d>;
+        vddio-supply = <&v1_8d>;
+        vddmipi-supply = <&v1_2d>;
+
+        /* sensor mclk provider */
+        #clock-cells = <0>;
+
+        ports {
+          #address-cells = <1>;
+          #size-cells = <0>;
+
+          /* Input */
+          port@0 {
+            reg = <0>;
+            tc358746_in: endpoint {
+              remote-endpoint = <&sensor_out>;
+              hsync-active = <0>;
+              vsync-active = <0>;
+              bus-type = <5>;
+            };
+          };
+
+          /* Output */
+          port@1 {
+            reg = <1>;
+            tc358746_out: endpoint {
+              remote-endpoint = <&mipi_csi2_in>;
+              data-lanes = <1 2>;
+              clock-noncontinuous;
+              link-frequencies = /bits/ 64 <216000000>;
+            };
+          };
+        };
+      };
+    };
diff --git a/Documentation/userspace-api/media/drivers/st-vgxy61.rst b/Documentation/userspace-api/media/drivers/st-vgxy61.rst
new file mode 100644 (file)
index 0000000..213b884
--- /dev/null
@@ -0,0 +1,23 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+ST VGXY61 camera sensor driver
+==============================
+
+The ST VGXY61 driver implements the following controls:
+
+``V4L2_CID_HDR_SENSOR_MODE``
+-------------------------------
+    Change the sensor HDR mode. A HDR picture is obtained by merging two captures of the same scene
+    using two different exposure periods.
+
+.. flat-table::
+    :header-rows:  0
+    :stub-columns: 0
+    :widths:       1 4
+
+    * - HDR linearize
+      - The merger outputs a long exposure capture as long as it is not saturated.
+    * - HDR substraction
+      - This involves subtracting the short exposure frame from the long exposure frame.
+    * - "No HDR"
+      - This mode is used for standard dynamic range (SDR) exposures.
index 4c5061aa9cd45ee1969ed2c36cf11acbdc274219..daa4f40869f8f82e52be788c84e8b29a223efc55 100644 (file)
@@ -661,3 +661,11 @@ enum v4l2_scene_mode -
 .. [#f1]
    This control may be changed to a menu control in the future, if more
    options are required.
+
+``V4L2_CID_HDR_SENSOR_MODE (menu)``
+    Change the sensor HDR mode. A HDR picture is obtained by merging two
+    captures of the same scene using two different exposure periods. HDR mode
+    describes the way these two captures are merged in the sensor.
+
+    As modes differ for each sensor, menu items are not standardized by this
+    control and are left to the programmer.
index d21d532eee1592ed8ef855e7e21507d766d74dd4..16ef3b41932e767e6e40e9088ec03d1d3c462ba8 100644 (file)
@@ -6057,6 +6057,43 @@ the following codes.
       - y\ :sub:`2`
       - y\ :sub:`1`
       - y\ :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`
     * .. _MEDIA-BUS-FMT-UYVY8-1X16:
 
       - MEDIA_BUS_FMT_UYVY8_1X16
index 046ff06ff97fa48d76e81c09fbc4bbdb128bac5b..aa1974054fcea100eb47504c79dea9109069459d 100644 (file)
@@ -15181,6 +15181,13 @@ S:     Maintained
 T:     git git://linuxtv.org/media_tree.git
 F:     drivers/media/i2c/ov08d10.c
 
+OMNIVISION OV08X40 SENSOR DRIVER
+M:     Jason Chen <jason.z.chen@intel.com>
+L:     linux-media@vger.kernel.org
+S:     Maintained
+T:     git git://linuxtv.org/media_tree.git
+F:     drivers/media/i2c/ov08x40.c
+
 OMNIVISION OV13858 SENSOR DRIVER
 M:     Sakari Ailus <sakari.ailus@linux.intel.com>
 L:     linux-media@vger.kernel.org
@@ -15219,6 +15226,14 @@ S:     Maintained
 T:     git git://linuxtv.org/media_tree.git
 F:     drivers/media/i2c/ov2740.c
 
+OMNIVISION OV4689 SENSOR DRIVER
+M:     Mikhail Rudenko <mike.rudenko@gmail.com>
+L:     linux-media@vger.kernel.org
+S:     Maintained
+T:     git git://linuxtv.org/media_tree.git
+F:     Documentation/devicetree/bindings/media/i2c/ovti,ov4689.yaml
+F:     drivers/media/i2c/ov5647.c
+
 OMNIVISION OV5640 SENSOR DRIVER
 M:     Steve Longerbeam <slongerbeam@gmail.com>
 L:     linux-media@vger.kernel.org
@@ -19188,7 +19203,7 @@ M:      Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
 L:     linux-media@vger.kernel.org
 S:     Maintained
 T:     git git://linuxtv.org/media_tree.git
-F:     Documentation/devicetree/bindings/media/i2c/imx290.txt
+F:     Documentation/devicetree/bindings/media/i2c/sony,imx290.yaml
 F:     drivers/media/i2c/imx290.c
 
 SONY IMX319 SENSOR DRIVER
@@ -19503,6 +19518,16 @@ S:     Maintained
 F:     Documentation/hwmon/stpddc60.rst
 F:     drivers/hwmon/pmbus/stpddc60.c
 
+ST VGXY61 DRIVER
+M:     Benjamin Mugnier <benjamin.mugnier@foss.st.com>
+M:     Sylvain Petinot <sylvain.petinot@foss.st.com>
+L:     linux-media@vger.kernel.org
+S:     Maintained
+T:     git git://linuxtv.org/media_tree.git
+F:     Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml
+F:     Documentation/userspace-api/media/drivers/st-vgxy61.rst
+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
index 7806d4b81716e5a14a2faf0aa0c50b9a3b4ea2e1..49c1c27afdc1b321dadd484b8f90fff1eabdd1cd 100644 (file)
@@ -364,6 +364,19 @@ config VIDEO_OV08D10
           To compile this driver as a module, choose M here: the
           module will be called ov08d10.
 
+config VIDEO_OV08X40
+       tristate "OmniVision OV08X40 sensor support"
+       depends on VIDEO_V4L2 && I2C
+       select MEDIA_CONTROLLER
+       select VIDEO_V4L2_SUBDEV_API
+       select V4L2_FWNODE
+       help
+         This is a Video4Linux2 sensor driver for the OmniVision
+         OV08X40 camera.
+
+         To compile this driver as a module, choose M here: the
+         module will be called ov08x40.
+
 config VIDEO_OV13858
        tristate "OmniVision OV13858 sensor support"
        depends on I2C && VIDEO_DEV
@@ -445,6 +458,19 @@ config VIDEO_OV2740
          To compile this driver as a module, choose M here: the
          module will be called ov2740.
 
+config VIDEO_OV4689
+       tristate "OmniVision OV4689 sensor support"
+       depends on GPIOLIB && VIDEO_DEV && I2C
+       select MEDIA_CONTROLLER
+       select VIDEO_V4L2_SUBDEV_API
+       select V4L2_FWNODE
+       help
+         This is a Video4Linux2 sensor-level driver for the OmniVision
+         OV4689 camera.
+
+         To compile this driver as a module, choose M here: the
+         module will be called ov4689.
+
 config VIDEO_OV5640
        tristate "OmniVision OV5640 sensor support"
        depends on OF
@@ -769,6 +795,16 @@ config VIDEO_SR030PC30
        help
          This driver supports SR030PC30 VGA camera from Siliconfile
 
+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.
+
 config VIDEO_VS6624
        tristate "ST VS6624 sensor support"
        depends on VIDEO_DEV && I2C
@@ -1272,6 +1308,23 @@ config VIDEO_TC358743_CEC
          When selected the tc358743 will support the optional
          HDMI CEC feature.
 
+config VIDEO_TC358746
+       tristate "Toshiba TC358746 parallel-CSI2 bridge"
+       depends on VIDEO_DEV && PM && I2C
+       select VIDEO_V4L2_SUBDEV_API
+       select MEDIA_CONTROLLER
+       select V4L2_FWNODE
+       select GENERIC_PHY_MIPI_DPHY
+       select REGMAP_I2C
+       select COMMON_CLK
+       help
+         Support for the Toshiba TC358746 parallel to MIPI CSI-2 bridge.
+         The bridge can work in both directions but currently only the
+         parallel-in / csi-out path is supported.
+
+         To compile this driver as a module, choose M here: the
+         module will be called tc358746.
+
 config VIDEO_TVP514X
        tristate "Texas Instruments TVP514x video decoder"
        depends on VIDEO_DEV && I2C
index 0a2933103dd9222db9df610ce634311382f1e5c4..ba28a8f8a07f9f84880e5414ab18a348c234da23 100644 (file)
@@ -72,6 +72,7 @@ obj-$(CONFIG_VIDEO_NOON010PC30) += noon010pc30.o
 obj-$(CONFIG_VIDEO_OG01A1B) += og01a1b.o
 obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o
 obj-$(CONFIG_VIDEO_OV08D10) += ov08d10.o
+obj-$(CONFIG_VIDEO_OV08X40) += ov08x40.o
 obj-$(CONFIG_VIDEO_OV13858) += ov13858.o
 obj-$(CONFIG_VIDEO_OV13B10) += ov13b10.o
 obj-$(CONFIG_VIDEO_OV2640) += ov2640.o
@@ -79,6 +80,7 @@ obj-$(CONFIG_VIDEO_OV2659) += ov2659.o
 obj-$(CONFIG_VIDEO_OV2680) += ov2680.o
 obj-$(CONFIG_VIDEO_OV2685) += ov2685.o
 obj-$(CONFIG_VIDEO_OV2740) += ov2740.o
+obj-$(CONFIG_VIDEO_OV4689) += ov4689.o
 obj-$(CONFIG_VIDEO_OV5640) += ov5640.o
 obj-$(CONFIG_VIDEO_OV5645) += ov5645.o
 obj-$(CONFIG_VIDEO_OV5647) += ov5647.o
@@ -117,7 +119,9 @@ 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_TC358746) += tc358746.o
 obj-$(CONFIG_VIDEO_TDA1997X) += tda1997x.o
 obj-$(CONFIG_VIDEO_TDA7432) += tda7432.o
 obj-$(CONFIG_VIDEO_TDA9840) += tda9840.o
index 516de278cc493d374c96a505de8ba03b29a835ad..a12fedcc3a1ced82d965511d34e93ea2445c876b 100644 (file)
@@ -327,18 +327,18 @@ static int ad5820_probe(struct i2c_client *client,
 
        ret = media_entity_pads_init(&coil->subdev.entity, 0, NULL);
        if (ret < 0)
-               goto cleanup2;
+               goto clean_mutex;
 
        ret = v4l2_async_register_subdev(&coil->subdev);
        if (ret < 0)
-               goto cleanup;
+               goto clean_entity;
 
        return ret;
 
-cleanup2:
-       mutex_destroy(&coil->power_lock);
-cleanup:
+clean_entity:
        media_entity_cleanup(&coil->subdev.entity);
+clean_mutex:
+       mutex_destroy(&coil->power_lock);
        return ret;
 }
 
index 0f47ef015a1d309dfe84b313f772d805863593f1..83a3ee275bbe874da49791338d8d73749d83d6b2 100644 (file)
@@ -414,6 +414,7 @@ static int dw9768_probe(struct i2c_client *client)
 {
        struct device *dev = &client->dev;
        struct dw9768 *dw9768;
+       bool full_power;
        unsigned int i;
        int ret;
 
@@ -469,13 +470,23 @@ static int dw9768_probe(struct i2c_client *client)
 
        dw9768->sd.entity.function = MEDIA_ENT_F_LENS;
 
+       /*
+        * Figure out whether we're going to power up the device here. Generally
+        * this is done if CONFIG_PM is disabled in a DT system or the device is
+        * to be powered on in an ACPI system. Similarly for power off in
+        * remove.
+        */
        pm_runtime_enable(dev);
-       if (!pm_runtime_enabled(dev)) {
+       full_power = (is_acpi_node(dev_fwnode(dev)) &&
+                     acpi_dev_state_d0(dev)) ||
+                    (is_of_node(dev_fwnode(dev)) && !pm_runtime_enabled(dev));
+       if (full_power) {
                ret = dw9768_runtime_resume(dev);
                if (ret < 0) {
                        dev_err(dev, "failed to power on: %d\n", ret);
                        goto err_clean_entity;
                }
+               pm_runtime_set_active(dev);
        }
 
        ret = v4l2_async_register_subdev(&dw9768->sd);
@@ -484,14 +495,17 @@ static int dw9768_probe(struct i2c_client *client)
                goto err_power_off;
        }
 
+       pm_runtime_idle(dev);
+
        return 0;
 
 err_power_off:
-       if (pm_runtime_enabled(dev))
-               pm_runtime_disable(dev);
-       else
+       if (full_power) {
                dw9768_runtime_suspend(dev);
+               pm_runtime_set_suspended(dev);
+       }
 err_clean_entity:
+       pm_runtime_disable(dev);
        media_entity_cleanup(&dw9768->sd.entity);
 err_free_handler:
        v4l2_ctrl_handler_free(&dw9768->ctrls);
@@ -503,14 +517,17 @@ static void dw9768_remove(struct i2c_client *client)
 {
        struct v4l2_subdev *sd = i2c_get_clientdata(client);
        struct dw9768 *dw9768 = sd_to_dw9768(sd);
+       struct device *dev = &client->dev;
 
        v4l2_async_unregister_subdev(&dw9768->sd);
        v4l2_ctrl_handler_free(&dw9768->ctrls);
        media_entity_cleanup(&dw9768->sd.entity);
-       pm_runtime_disable(&client->dev);
-       if (!pm_runtime_status_suspended(&client->dev))
-               dw9768_runtime_suspend(&client->dev);
-       pm_runtime_set_suspended(&client->dev);
+       if ((is_acpi_node(dev_fwnode(dev)) && acpi_dev_state_d0(dev)) ||
+           (is_of_node(dev_fwnode(dev)) && !pm_runtime_enabled(dev))) {
+               dw9768_runtime_suspend(dev);
+               pm_runtime_set_suspended(dev);
+       }
+       pm_runtime_disable(dev);
 }
 
 static const struct of_device_id dw9768_of_table[] = {
index c5b69823f257e836e2cb2e09eff15dc242df8b71..7c61873b719812c6c64bb80467bc81e72d1f9f71 100644 (file)
@@ -2008,22 +2008,24 @@ static int hi846_parse_dt(struct hi846 *hi846, struct device *dev)
            bus_cfg.bus.mipi_csi2.num_data_lanes != 4) {
                dev_err(dev, "number of CSI2 data lanes %d is not supported",
                        bus_cfg.bus.mipi_csi2.num_data_lanes);
-               v4l2_fwnode_endpoint_free(&bus_cfg);
-               return -EINVAL;
+               ret = -EINVAL;
+               goto check_hwcfg_error;
        }
 
        hi846->nr_lanes = bus_cfg.bus.mipi_csi2.num_data_lanes;
 
        if (!bus_cfg.nr_of_link_frequencies) {
                dev_err(dev, "link-frequency property not found in DT\n");
-               return -EINVAL;
+               ret = -EINVAL;
+               goto check_hwcfg_error;
        }
 
        /* Check that link frequences for all the modes are in device tree */
        fq = hi846_check_link_freqs(hi846, &bus_cfg);
        if (fq) {
                dev_err(dev, "Link frequency of %lld is not supported\n", fq);
-               return -EINVAL;
+               ret = -EINVAL;
+               goto check_hwcfg_error;
        }
 
        v4l2_fwnode_endpoint_free(&bus_cfg);
@@ -2044,6 +2046,10 @@ static int hi846_parse_dt(struct hi846 *hi846, struct device *dev)
        }
 
        return 0;
+
+check_hwcfg_error:
+       v4l2_fwnode_endpoint_free(&bus_cfg);
+       return ret;
 }
 
 static int hi846_probe(struct i2c_client *client)
index 1ce64dcdf7f02f6a21f8f7ba38a41edfd1a1b7f3..218ded13fd805e70d81bedaae84cc766dc079a8b 100644 (file)
 #include <media/v4l2-fwnode.h>
 #include <media/v4l2-subdev.h>
 
-#define IMX290_STANDBY 0x3000
-#define IMX290_REGHOLD 0x3001
-#define IMX290_XMSTA 0x3002
-#define IMX290_FR_FDG_SEL 0x3009
-#define IMX290_BLKLEVEL_LOW 0x300a
-#define IMX290_BLKLEVEL_HIGH 0x300b
-#define IMX290_GAIN 0x3014
-#define IMX290_HMAX_LOW 0x301c
-#define IMX290_HMAX_HIGH 0x301d
-#define IMX290_PGCTRL 0x308c
-#define IMX290_PHY_LANE_NUM 0x3407
-#define IMX290_CSI_LANE_MODE 0x3443
-
-#define IMX290_PGCTRL_REGEN BIT(0)
-#define IMX290_PGCTRL_THRU BIT(1)
-#define IMX290_PGCTRL_MODE(n) ((n) << 4)
+#define IMX290_REG_SIZE_SHIFT                          16
+#define IMX290_REG_ADDR_MASK                           0xffff
+#define IMX290_REG_8BIT(n)                             ((1U << IMX290_REG_SIZE_SHIFT) | (n))
+#define IMX290_REG_16BIT(n)                            ((2U << IMX290_REG_SIZE_SHIFT) | (n))
+#define IMX290_REG_24BIT(n)                            ((3U << IMX290_REG_SIZE_SHIFT) | (n))
+
+#define IMX290_STANDBY                                 IMX290_REG_8BIT(0x3000)
+#define IMX290_REGHOLD                                 IMX290_REG_8BIT(0x3001)
+#define IMX290_XMSTA                                   IMX290_REG_8BIT(0x3002)
+#define IMX290_ADBIT                                   IMX290_REG_8BIT(0x3005)
+#define IMX290_ADBIT_10BIT                             (0 << 0)
+#define IMX290_ADBIT_12BIT                             (1 << 0)
+#define IMX290_CTRL_07                                 IMX290_REG_8BIT(0x3007)
+#define IMX290_VREVERSE                                        BIT(0)
+#define IMX290_HREVERSE                                        BIT(1)
+#define IMX290_WINMODE_1080P                           (0 << 4)
+#define IMX290_WINMODE_720P                            (1 << 4)
+#define IMX290_WINMODE_CROP                            (4 << 4)
+#define IMX290_FR_FDG_SEL                              IMX290_REG_8BIT(0x3009)
+#define IMX290_BLKLEVEL                                        IMX290_REG_16BIT(0x300a)
+#define IMX290_GAIN                                    IMX290_REG_8BIT(0x3014)
+#define IMX290_VMAX                                    IMX290_REG_24BIT(0x3018)
+#define IMX290_HMAX                                    IMX290_REG_16BIT(0x301c)
+#define IMX290_SHS1                                    IMX290_REG_24BIT(0x3020)
+#define IMX290_WINWV_OB                                        IMX290_REG_8BIT(0x303a)
+#define IMX290_WINPV                                   IMX290_REG_16BIT(0x303c)
+#define IMX290_WINWV                                   IMX290_REG_16BIT(0x303e)
+#define IMX290_WINPH                                   IMX290_REG_16BIT(0x3040)
+#define IMX290_WINWH                                   IMX290_REG_16BIT(0x3042)
+#define IMX290_OUT_CTRL                                        IMX290_REG_8BIT(0x3046)
+#define IMX290_ODBIT_10BIT                             (0 << 0)
+#define IMX290_ODBIT_12BIT                             (1 << 0)
+#define IMX290_OPORTSEL_PARALLEL                       (0x0 << 4)
+#define IMX290_OPORTSEL_LVDS_2CH                       (0xd << 4)
+#define IMX290_OPORTSEL_LVDS_4CH                       (0xe << 4)
+#define IMX290_OPORTSEL_LVDS_8CH                       (0xf << 4)
+#define IMX290_XSOUTSEL                                        IMX290_REG_8BIT(0x304b)
+#define IMX290_XSOUTSEL_XVSOUTSEL_HIGH                 (0 << 0)
+#define IMX290_XSOUTSEL_XVSOUTSEL_VSYNC                        (2 << 0)
+#define IMX290_XSOUTSEL_XHSOUTSEL_HIGH                 (0 << 2)
+#define IMX290_XSOUTSEL_XHSOUTSEL_HSYNC                        (2 << 2)
+#define IMX290_INCKSEL1                                        IMX290_REG_8BIT(0x305c)
+#define IMX290_INCKSEL2                                        IMX290_REG_8BIT(0x305d)
+#define IMX290_INCKSEL3                                        IMX290_REG_8BIT(0x305e)
+#define IMX290_INCKSEL4                                        IMX290_REG_8BIT(0x305f)
+#define IMX290_PGCTRL                                  IMX290_REG_8BIT(0x308c)
+#define IMX290_ADBIT1                                  IMX290_REG_8BIT(0x3129)
+#define IMX290_ADBIT1_10BIT                            0x1d
+#define IMX290_ADBIT1_12BIT                            0x00
+#define IMX290_INCKSEL5                                        IMX290_REG_8BIT(0x315e)
+#define IMX290_INCKSEL6                                        IMX290_REG_8BIT(0x3164)
+#define IMX290_ADBIT2                                  IMX290_REG_8BIT(0x317c)
+#define IMX290_ADBIT2_10BIT                            0x12
+#define IMX290_ADBIT2_12BIT                            0x00
+#define IMX290_CHIP_ID                                 IMX290_REG_16BIT(0x319a)
+#define IMX290_ADBIT3                                  IMX290_REG_8BIT(0x31ec)
+#define IMX290_ADBIT3_10BIT                            0x37
+#define IMX290_ADBIT3_12BIT                            0x0e
+#define IMX290_REPETITION                              IMX290_REG_8BIT(0x3405)
+#define IMX290_PHY_LANE_NUM                            IMX290_REG_8BIT(0x3407)
+#define IMX290_OPB_SIZE_V                              IMX290_REG_8BIT(0x3414)
+#define IMX290_Y_OUT_SIZE                              IMX290_REG_16BIT(0x3418)
+#define IMX290_CSI_DT_FMT                              IMX290_REG_16BIT(0x3441)
+#define IMX290_CSI_DT_FMT_RAW10                                0x0a0a
+#define IMX290_CSI_DT_FMT_RAW12                                0x0c0c
+#define IMX290_CSI_LANE_MODE                           IMX290_REG_8BIT(0x3443)
+#define IMX290_EXTCK_FREQ                              IMX290_REG_16BIT(0x3444)
+#define IMX290_TCLKPOST                                        IMX290_REG_16BIT(0x3446)
+#define IMX290_THSZERO                                 IMX290_REG_16BIT(0x3448)
+#define IMX290_THSPREPARE                              IMX290_REG_16BIT(0x344a)
+#define IMX290_TCLKTRAIL                               IMX290_REG_16BIT(0x344c)
+#define IMX290_THSTRAIL                                        IMX290_REG_16BIT(0x344e)
+#define IMX290_TCLKZERO                                        IMX290_REG_16BIT(0x3450)
+#define IMX290_TCLKPREPARE                             IMX290_REG_16BIT(0x3452)
+#define IMX290_TLPX                                    IMX290_REG_16BIT(0x3454)
+#define IMX290_X_OUT_SIZE                              IMX290_REG_16BIT(0x3472)
+
+#define IMX290_PGCTRL_REGEN                            BIT(0)
+#define IMX290_PGCTRL_THRU                             BIT(1)
+#define IMX290_PGCTRL_MODE(n)                          ((n) << 4)
+
+#define IMX290_VMAX_DEFAULT                            1125
+
+
+/*
+ * The IMX290 pixel array is organized as follows:
+ *
+ *     +------------------------------------+
+ *     |           Optical Black            |     }  Vertical effective optical black (10)
+ * +---+------------------------------------+---+
+ * |   |                                    |   | }  Effective top margin (8)
+ * |   |   +----------------------------+   |   | \
+ * |   |   |                            |   |   |  |
+ * |   |   |                            |   |   |  |
+ * |   |   |                            |   |   |  |
+ * |   |   |    Recording Pixel Area    |   |   |  | Recommended height (1080)
+ * |   |   |                            |   |   |  |
+ * |   |   |                            |   |   |  |
+ * |   |   |                            |   |   |  |
+ * |   |   +----------------------------+   |   | /
+ * |   |                                    |   | }  Effective bottom margin (9)
+ * +---+------------------------------------+---+
+ *  <-> <-> <--------------------------> <-> <->
+ *                                            \----  Ignored right margin (4)
+ *                                        \--------  Effective right margin (9)
+ *                       \-------------------------  Recommended width (1920)
+ *       \-----------------------------------------  Effective left margin (8)
+ *   \---------------------------------------------  Ignored left margin (4)
+ *
+ * The optical black lines are output over CSI-2 with a separate data type.
+ *
+ * The pixel array is meant to have 1920x1080 usable pixels after image
+ * processing in an ISP. It has 8 (9) extra active pixels usable for color
+ * processing in the ISP on the top and left (bottom and right) sides of the
+ * image. In addition, 4 additional pixels are present on the left and right
+ * sides of the image, documented as "ignored area".
+ *
+ * As far as is understood, all pixels of the pixel array (ignored area, color
+ * processing margins and recording area) can be output by the sensor.
+ */
+
+#define IMX290_PIXEL_ARRAY_WIDTH                       1945
+#define IMX290_PIXEL_ARRAY_HEIGHT                      1097
+#define IMX920_PIXEL_ARRAY_MARGIN_LEFT                 12
+#define IMX920_PIXEL_ARRAY_MARGIN_RIGHT                        13
+#define IMX920_PIXEL_ARRAY_MARGIN_TOP                  8
+#define IMX920_PIXEL_ARRAY_MARGIN_BOTTOM               9
+#define IMX290_PIXEL_ARRAY_RECORDING_WIDTH             1920
+#define IMX290_PIXEL_ARRAY_RECORDING_HEIGHT            1080
 
 static const char * const imx290_supply_name[] = {
        "vdda",
@@ -48,8 +161,8 @@ static const char * const imx290_supply_name[] = {
 #define IMX290_NUM_SUPPLIES ARRAY_SIZE(imx290_supply_name)
 
 struct imx290_regval {
-       u16 reg;
-       u8 val;
+       u32 reg;
+       u32 val;
 };
 
 struct imx290_mode {
@@ -80,6 +193,8 @@ struct imx290 {
        struct v4l2_ctrl_handler ctrls;
        struct v4l2_ctrl *link_freq;
        struct v4l2_ctrl *pixel_rate;
+       struct v4l2_ctrl *hblank;
+       struct v4l2_ctrl *vblank;
 
        struct mutex lock;
 };
@@ -97,7 +212,6 @@ static const struct imx290_pixfmt imx290_formats[] = {
 static const struct regmap_config imx290_regmap_config = {
        .reg_bits = 16,
        .val_bits = 8,
-       .cache_type = REGCACHE_RBTREE,
 };
 
 static const char * const imx290_test_pattern_menu[] = {
@@ -112,163 +226,129 @@ static const char * const imx290_test_pattern_menu[] = {
 };
 
 static const struct imx290_regval imx290_global_init_settings[] = {
-       { 0x3007, 0x00 },
-       { 0x3018, 0x65 },
-       { 0x3019, 0x04 },
-       { 0x301a, 0x00 },
-       { 0x3444, 0x20 },
-       { 0x3445, 0x25 },
-       { 0x303a, 0x0c },
-       { 0x3040, 0x00 },
-       { 0x3041, 0x00 },
-       { 0x303c, 0x00 },
-       { 0x303d, 0x00 },
-       { 0x3042, 0x9c },
-       { 0x3043, 0x07 },
-       { 0x303e, 0x49 },
-       { 0x303f, 0x04 },
-       { 0x304b, 0x0a },
-       { 0x300f, 0x00 },
-       { 0x3010, 0x21 },
-       { 0x3012, 0x64 },
-       { 0x3016, 0x09 },
-       { 0x3070, 0x02 },
-       { 0x3071, 0x11 },
-       { 0x309b, 0x10 },
-       { 0x309c, 0x22 },
-       { 0x30a2, 0x02 },
-       { 0x30a6, 0x20 },
-       { 0x30a8, 0x20 },
-       { 0x30aa, 0x20 },
-       { 0x30ac, 0x20 },
-       { 0x30b0, 0x43 },
-       { 0x3119, 0x9e },
-       { 0x311c, 0x1e },
-       { 0x311e, 0x08 },
-       { 0x3128, 0x05 },
-       { 0x313d, 0x83 },
-       { 0x3150, 0x03 },
-       { 0x317e, 0x00 },
-       { 0x32b8, 0x50 },
-       { 0x32b9, 0x10 },
-       { 0x32ba, 0x00 },
-       { 0x32bb, 0x04 },
-       { 0x32c8, 0x50 },
-       { 0x32c9, 0x10 },
-       { 0x32ca, 0x00 },
-       { 0x32cb, 0x04 },
-       { 0x332c, 0xd3 },
-       { 0x332d, 0x10 },
-       { 0x332e, 0x0d },
-       { 0x3358, 0x06 },
-       { 0x3359, 0xe1 },
-       { 0x335a, 0x11 },
-       { 0x3360, 0x1e },
-       { 0x3361, 0x61 },
-       { 0x3362, 0x10 },
-       { 0x33b0, 0x50 },
-       { 0x33b2, 0x1a },
-       { 0x33b3, 0x04 },
+       { IMX290_CTRL_07, IMX290_WINMODE_1080P },
+       { IMX290_VMAX, IMX290_VMAX_DEFAULT },
+       { IMX290_EXTCK_FREQ, 0x2520 },
+       { IMX290_WINWV_OB, 12 },
+       { IMX290_WINPH, 0 },
+       { IMX290_WINPV, 0 },
+       { IMX290_WINWH, 1948 },
+       { IMX290_WINWV, 1097 },
+       { IMX290_XSOUTSEL, IMX290_XSOUTSEL_XVSOUTSEL_VSYNC |
+                          IMX290_XSOUTSEL_XHSOUTSEL_HSYNC },
+       { IMX290_REG_8BIT(0x300f), 0x00 },
+       { IMX290_REG_8BIT(0x3010), 0x21 },
+       { IMX290_REG_8BIT(0x3012), 0x64 },
+       { IMX290_REG_8BIT(0x3013), 0x00 },
+       { IMX290_REG_8BIT(0x3016), 0x09 },
+       { IMX290_REG_8BIT(0x3070), 0x02 },
+       { IMX290_REG_8BIT(0x3071), 0x11 },
+       { IMX290_REG_8BIT(0x309b), 0x10 },
+       { IMX290_REG_8BIT(0x309c), 0x22 },
+       { IMX290_REG_8BIT(0x30a2), 0x02 },
+       { IMX290_REG_8BIT(0x30a6), 0x20 },
+       { IMX290_REG_8BIT(0x30a8), 0x20 },
+       { IMX290_REG_8BIT(0x30aa), 0x20 },
+       { IMX290_REG_8BIT(0x30ac), 0x20 },
+       { IMX290_REG_8BIT(0x30b0), 0x43 },
+       { IMX290_REG_8BIT(0x3119), 0x9e },
+       { IMX290_REG_8BIT(0x311c), 0x1e },
+       { IMX290_REG_8BIT(0x311e), 0x08 },
+       { IMX290_REG_8BIT(0x3128), 0x05 },
+       { IMX290_REG_8BIT(0x313d), 0x83 },
+       { IMX290_REG_8BIT(0x3150), 0x03 },
+       { IMX290_REG_8BIT(0x317e), 0x00 },
+       { IMX290_REG_8BIT(0x32b8), 0x50 },
+       { IMX290_REG_8BIT(0x32b9), 0x10 },
+       { IMX290_REG_8BIT(0x32ba), 0x00 },
+       { IMX290_REG_8BIT(0x32bb), 0x04 },
+       { IMX290_REG_8BIT(0x32c8), 0x50 },
+       { IMX290_REG_8BIT(0x32c9), 0x10 },
+       { IMX290_REG_8BIT(0x32ca), 0x00 },
+       { IMX290_REG_8BIT(0x32cb), 0x04 },
+       { IMX290_REG_8BIT(0x332c), 0xd3 },
+       { IMX290_REG_8BIT(0x332d), 0x10 },
+       { IMX290_REG_8BIT(0x332e), 0x0d },
+       { IMX290_REG_8BIT(0x3358), 0x06 },
+       { IMX290_REG_8BIT(0x3359), 0xe1 },
+       { IMX290_REG_8BIT(0x335a), 0x11 },
+       { IMX290_REG_8BIT(0x3360), 0x1e },
+       { IMX290_REG_8BIT(0x3361), 0x61 },
+       { IMX290_REG_8BIT(0x3362), 0x10 },
+       { IMX290_REG_8BIT(0x33b0), 0x50 },
+       { IMX290_REG_8BIT(0x33b2), 0x1a },
+       { IMX290_REG_8BIT(0x33b3), 0x04 },
+       { IMX290_REG_8BIT(0x3480), 0x49 },
 };
 
 static const struct imx290_regval imx290_1080p_settings[] = {
        /* mode settings */
-       { 0x3007, 0x00 },
-       { 0x303a, 0x0c },
-       { 0x3414, 0x0a },
-       { 0x3472, 0x80 },
-       { 0x3473, 0x07 },
-       { 0x3418, 0x38 },
-       { 0x3419, 0x04 },
-       { 0x3012, 0x64 },
-       { 0x3013, 0x00 },
-       { 0x305c, 0x18 },
-       { 0x305d, 0x03 },
-       { 0x305e, 0x20 },
-       { 0x305f, 0x01 },
-       { 0x315e, 0x1a },
-       { 0x3164, 0x1a },
-       { 0x3480, 0x49 },
+       { IMX290_CTRL_07, IMX290_WINMODE_1080P },
+       { IMX290_WINWV_OB, 12 },
+       { IMX290_OPB_SIZE_V, 10 },
+       { IMX290_X_OUT_SIZE, 1920 },
+       { IMX290_Y_OUT_SIZE, 1080 },
+       { IMX290_INCKSEL1, 0x18 },
+       { IMX290_INCKSEL2, 0x03 },
+       { IMX290_INCKSEL3, 0x20 },
+       { IMX290_INCKSEL4, 0x01 },
+       { IMX290_INCKSEL5, 0x1a },
+       { IMX290_INCKSEL6, 0x1a },
        /* data rate settings */
-       { 0x3405, 0x10 },
-       { 0x3446, 0x57 },
-       { 0x3447, 0x00 },
-       { 0x3448, 0x37 },
-       { 0x3449, 0x00 },
-       { 0x344a, 0x1f },
-       { 0x344b, 0x00 },
-       { 0x344c, 0x1f },
-       { 0x344d, 0x00 },
-       { 0x344e, 0x1f },
-       { 0x344f, 0x00 },
-       { 0x3450, 0x77 },
-       { 0x3451, 0x00 },
-       { 0x3452, 0x1f },
-       { 0x3453, 0x00 },
-       { 0x3454, 0x17 },
-       { 0x3455, 0x00 },
+       { IMX290_REPETITION, 0x10 },
+       { IMX290_TCLKPOST, 87 },
+       { IMX290_THSZERO, 55 },
+       { IMX290_THSPREPARE, 31 },
+       { IMX290_TCLKTRAIL, 31 },
+       { IMX290_THSTRAIL, 31 },
+       { IMX290_TCLKZERO, 119 },
+       { IMX290_TCLKPREPARE, 31 },
+       { IMX290_TLPX, 23 },
 };
 
 static const struct imx290_regval imx290_720p_settings[] = {
        /* mode settings */
-       { 0x3007, 0x10 },
-       { 0x303a, 0x06 },
-       { 0x3414, 0x04 },
-       { 0x3472, 0x00 },
-       { 0x3473, 0x05 },
-       { 0x3418, 0xd0 },
-       { 0x3419, 0x02 },
-       { 0x3012, 0x64 },
-       { 0x3013, 0x00 },
-       { 0x305c, 0x20 },
-       { 0x305d, 0x00 },
-       { 0x305e, 0x20 },
-       { 0x305f, 0x01 },
-       { 0x315e, 0x1a },
-       { 0x3164, 0x1a },
-       { 0x3480, 0x49 },
+       { IMX290_CTRL_07, IMX290_WINMODE_720P },
+       { IMX290_WINWV_OB, 6 },
+       { IMX290_OPB_SIZE_V, 4 },
+       { IMX290_X_OUT_SIZE, 1280 },
+       { IMX290_Y_OUT_SIZE, 720 },
+       { IMX290_INCKSEL1, 0x20 },
+       { IMX290_INCKSEL2, 0x00 },
+       { IMX290_INCKSEL3, 0x20 },
+       { IMX290_INCKSEL4, 0x01 },
+       { IMX290_INCKSEL5, 0x1a },
+       { IMX290_INCKSEL6, 0x1a },
        /* data rate settings */
-       { 0x3405, 0x10 },
-       { 0x3446, 0x4f },
-       { 0x3447, 0x00 },
-       { 0x3448, 0x2f },
-       { 0x3449, 0x00 },
-       { 0x344a, 0x17 },
-       { 0x344b, 0x00 },
-       { 0x344c, 0x17 },
-       { 0x344d, 0x00 },
-       { 0x344e, 0x17 },
-       { 0x344f, 0x00 },
-       { 0x3450, 0x57 },
-       { 0x3451, 0x00 },
-       { 0x3452, 0x17 },
-       { 0x3453, 0x00 },
-       { 0x3454, 0x17 },
-       { 0x3455, 0x00 },
+       { IMX290_REPETITION, 0x10 },
+       { IMX290_TCLKPOST, 79 },
+       { IMX290_THSZERO, 47 },
+       { IMX290_THSPREPARE, 23 },
+       { IMX290_TCLKTRAIL, 23 },
+       { IMX290_THSTRAIL, 23 },
+       { IMX290_TCLKZERO, 87 },
+       { IMX290_TCLKPREPARE, 23 },
+       { IMX290_TLPX, 23 },
 };
 
 static const struct imx290_regval imx290_10bit_settings[] = {
-       { 0x3005, 0x00},
-       { 0x3046, 0x00},
-       { 0x3129, 0x1d},
-       { 0x317c, 0x12},
-       { 0x31ec, 0x37},
-       { 0x3441, 0x0a},
-       { 0x3442, 0x0a},
-       { 0x300a, 0x3c},
-       { 0x300b, 0x00},
+       { IMX290_ADBIT, IMX290_ADBIT_10BIT },
+       { IMX290_OUT_CTRL, IMX290_ODBIT_10BIT },
+       { IMX290_ADBIT1, IMX290_ADBIT1_10BIT },
+       { IMX290_ADBIT2, IMX290_ADBIT2_10BIT },
+       { IMX290_ADBIT3, IMX290_ADBIT3_10BIT },
+       { IMX290_CSI_DT_FMT, IMX290_CSI_DT_FMT_RAW10 },
+       { IMX290_BLKLEVEL, 60 },
 };
 
 static const struct imx290_regval imx290_12bit_settings[] = {
-       { 0x3005, 0x01 },
-       { 0x3046, 0x01 },
-       { 0x3129, 0x00 },
-       { 0x317c, 0x00 },
-       { 0x31ec, 0x0e },
-       { 0x3441, 0x0c },
-       { 0x3442, 0x0c },
-       { 0x300a, 0xf0 },
-       { 0x300b, 0x00 },
+       { IMX290_ADBIT, IMX290_ADBIT_12BIT },
+       { IMX290_OUT_CTRL, IMX290_ODBIT_12BIT },
+       { IMX290_ADBIT1, IMX290_ADBIT1_12BIT },
+       { IMX290_ADBIT2, IMX290_ADBIT2_12BIT },
+       { IMX290_ADBIT3, IMX290_ADBIT3_12BIT },
+       { IMX290_CSI_DT_FMT, IMX290_CSI_DT_FMT_RAW12 },
+       { IMX290_BLKLEVEL, 240 },
 };
 
 /* supported link frequencies */
@@ -308,7 +388,7 @@ static const struct imx290_mode imx290_modes_2lanes[] = {
        {
                .width = 1920,
                .height = 1080,
-               .hmax = 0x1130,
+               .hmax = 4400,
                .link_freq_index = FREQ_INDEX_1080P,
                .data = imx290_1080p_settings,
                .data_size = ARRAY_SIZE(imx290_1080p_settings),
@@ -316,7 +396,7 @@ static const struct imx290_mode imx290_modes_2lanes[] = {
        {
                .width = 1280,
                .height = 720,
-               .hmax = 0x19c8,
+               .hmax = 6600,
                .link_freq_index = FREQ_INDEX_720P,
                .data = imx290_720p_settings,
                .data_size = ARRAY_SIZE(imx290_720p_settings),
@@ -327,7 +407,7 @@ static const struct imx290_mode imx290_modes_4lanes[] = {
        {
                .width = 1920,
                .height = 1080,
-               .hmax = 0x0898,
+               .hmax = 2200,
                .link_freq_index = FREQ_INDEX_1080P,
                .data = imx290_1080p_settings,
                .data_size = ARRAY_SIZE(imx290_1080p_settings),
@@ -335,7 +415,7 @@ static const struct imx290_mode imx290_modes_4lanes[] = {
        {
                .width = 1280,
                .height = 720,
-               .hmax = 0x0ce4,
+               .hmax = 3300,
                .link_freq_index = FREQ_INDEX_720P,
                .data = imx290_720p_settings,
                .data_size = ARRAY_SIZE(imx290_720p_settings),
@@ -363,30 +443,40 @@ static inline struct imx290 *to_imx290(struct v4l2_subdev *_sd)
        return container_of(_sd, struct imx290, sd);
 }
 
-static inline int __always_unused imx290_read_reg(struct imx290 *imx290, u16 addr, u8 *value)
+static int __always_unused imx290_read(struct imx290 *imx290, u32 addr, u32 *value)
 {
-       unsigned int regval;
+       u8 data[3] = { 0, 0, 0 };
        int ret;
 
-       ret = regmap_read(imx290->regmap, addr, &regval);
-       if (ret) {
-               dev_err(imx290->dev, "I2C read failed for addr: %x\n", addr);
+       ret = regmap_raw_read(imx290->regmap, addr & IMX290_REG_ADDR_MASK,
+                             data, (addr >> IMX290_REG_SIZE_SHIFT) & 3);
+       if (ret < 0) {
+               dev_err(imx290->dev, "%u-bit read from 0x%04x failed: %d\n",
+                        ((addr >> IMX290_REG_SIZE_SHIFT) & 3) * 8,
+                        addr & IMX290_REG_ADDR_MASK, ret);
                return ret;
        }
 
-       *value = regval & 0xff;
-
+       *value = (data[2] << 16) | (data[1] << 8) | data[0];
        return 0;
 }
 
-static int imx290_write_reg(struct imx290 *imx290, u16 addr, u8 value)
+static int imx290_write(struct imx290 *imx290, u32 addr, u32 value, int *err)
 {
+       u8 data[3] = { value & 0xff, (value >> 8) & 0xff, value >> 16 };
        int ret;
 
-       ret = regmap_write(imx290->regmap, addr, value);
-       if (ret) {
-               dev_err(imx290->dev, "I2C write failed for addr: %x\n", addr);
-               return ret;
+       if (err && *err)
+               return *err;
+
+       ret = regmap_raw_write(imx290->regmap, addr & IMX290_REG_ADDR_MASK,
+                              data, (addr >> IMX290_REG_SIZE_SHIFT) & 3);
+       if (ret < 0) {
+               dev_err(imx290->dev, "%u-bit write to 0x%04x failed: %d\n",
+                        ((addr >> IMX290_REG_SIZE_SHIFT) & 3) * 8,
+                        addr & IMX290_REG_ADDR_MASK, ret);
+               if (err)
+                       *err = ret;
        }
 
        return ret;
@@ -400,7 +490,7 @@ static int imx290_set_register_array(struct imx290 *imx290,
        int ret;
 
        for (i = 0; i < num_settings; ++i, ++settings) {
-               ret = imx290_write_reg(imx290, settings->reg, settings->val);
+               ret = imx290_write(imx290, settings->reg, settings->val, NULL);
                if (ret < 0)
                        return ret;
        }
@@ -411,59 +501,16 @@ static int imx290_set_register_array(struct imx290 *imx290,
        return 0;
 }
 
-static int imx290_write_buffered_reg(struct imx290 *imx290, u16 address_low,
-                                    u8 nr_regs, u32 value)
-{
-       unsigned int i;
-       int ret;
-
-       ret = imx290_write_reg(imx290, IMX290_REGHOLD, 0x01);
-       if (ret) {
-               dev_err(imx290->dev, "Error setting hold register\n");
-               return ret;
-       }
-
-       for (i = 0; i < nr_regs; i++) {
-               ret = imx290_write_reg(imx290, address_low + i,
-                                      (u8)(value >> (i * 8)));
-               if (ret) {
-                       dev_err(imx290->dev, "Error writing buffered registers\n");
-                       return ret;
-               }
-       }
-
-       ret = imx290_write_reg(imx290, IMX290_REGHOLD, 0x00);
-       if (ret) {
-               dev_err(imx290->dev, "Error setting hold register\n");
-               return ret;
-       }
-
-       return ret;
-}
-
-static int imx290_set_gain(struct imx290 *imx290, u32 value)
-{
-       int ret;
-
-       ret = imx290_write_buffered_reg(imx290, IMX290_GAIN, 1, value);
-       if (ret)
-               dev_err(imx290->dev, "Unable to write gain\n");
-
-       return ret;
-}
-
 /* Stop streaming */
 static int imx290_stop_streaming(struct imx290 *imx290)
 {
-       int ret;
+       int ret = 0;
 
-       ret = imx290_write_reg(imx290, IMX290_STANDBY, 0x01);
-       if (ret < 0)
-               return ret;
+       imx290_write(imx290, IMX290_STANDBY, 0x01, &ret);
 
        msleep(30);
 
-       return imx290_write_reg(imx290, IMX290_XMSTA, 0x01);
+       return imx290_write(imx290, IMX290_XMSTA, 0x01, &ret);
 }
 
 static int imx290_set_ctrl(struct v4l2_ctrl *ctrl)
@@ -477,28 +524,32 @@ static int imx290_set_ctrl(struct v4l2_ctrl *ctrl)
                return 0;
 
        switch (ctrl->id) {
-       case V4L2_CID_GAIN:
-               ret = imx290_set_gain(imx290, ctrl->val);
+       case V4L2_CID_ANALOGUE_GAIN:
+               ret = imx290_write(imx290, IMX290_GAIN, ctrl->val, NULL);
+               break;
+
+       case V4L2_CID_EXPOSURE:
+               ret = imx290_write(imx290, IMX290_SHS1,
+                                  IMX290_VMAX_DEFAULT - ctrl->val - 1, NULL);
                break;
+
        case V4L2_CID_TEST_PATTERN:
                if (ctrl->val) {
-                       imx290_write_reg(imx290, IMX290_BLKLEVEL_LOW, 0x00);
-                       imx290_write_reg(imx290, IMX290_BLKLEVEL_HIGH, 0x00);
+                       imx290_write(imx290, IMX290_BLKLEVEL, 0, &ret);
                        usleep_range(10000, 11000);
-                       imx290_write_reg(imx290, IMX290_PGCTRL,
-                                        (u8)(IMX290_PGCTRL_REGEN |
-                                        IMX290_PGCTRL_THRU |
-                                        IMX290_PGCTRL_MODE(ctrl->val)));
+                       imx290_write(imx290, IMX290_PGCTRL,
+                                    (u8)(IMX290_PGCTRL_REGEN |
+                                    IMX290_PGCTRL_THRU |
+                                    IMX290_PGCTRL_MODE(ctrl->val)), &ret);
                } else {
-                       imx290_write_reg(imx290, IMX290_PGCTRL, 0x00);
+                       imx290_write(imx290, IMX290_PGCTRL, 0x00, &ret);
                        usleep_range(10000, 11000);
                        if (imx290->bpp == 10)
-                               imx290_write_reg(imx290, IMX290_BLKLEVEL_LOW,
-                                                0x3c);
+                               imx290_write(imx290, IMX290_BLKLEVEL, 0x3c,
+                                            &ret);
                        else /* 12 bits per pixel */
-                               imx290_write_reg(imx290, IMX290_BLKLEVEL_LOW,
-                                                0xf0);
-                       imx290_write_reg(imx290, IMX290_BLKLEVEL_HIGH, 0x00);
+                               imx290_write(imx290, IMX290_BLKLEVEL, 0xf0,
+                                            &ret);
                }
                break;
        default:
@@ -515,6 +566,16 @@ static const struct v4l2_ctrl_ops imx290_ctrl_ops = {
        .s_ctrl = imx290_set_ctrl,
 };
 
+static struct v4l2_mbus_framefmt *
+imx290_get_pad_format(struct imx290 *imx290, struct v4l2_subdev_state *state,
+                     u32 which)
+{
+       if (which == V4L2_SUBDEV_FORMAT_ACTIVE)
+               return &imx290->current_format;
+       else
+               return v4l2_subdev_get_try_format(&imx290->sd, state, 0);
+}
+
 static int imx290_enum_mbus_code(struct v4l2_subdev *sd,
                                 struct v4l2_subdev_state *sd_state,
                                 struct v4l2_subdev_mbus_code_enum *code)
@@ -558,12 +619,7 @@ static int imx290_get_fmt(struct v4l2_subdev *sd,
 
        mutex_lock(&imx290->lock);
 
-       if (fmt->which == V4L2_SUBDEV_FORMAT_TRY)
-               framefmt = v4l2_subdev_get_try_format(&imx290->sd, sd_state,
-                                                     fmt->pad);
-       else
-               framefmt = &imx290->current_format;
-
+       framefmt = imx290_get_pad_format(imx290, sd_state, fmt->which);
        fmt->format = *framefmt;
 
        mutex_unlock(&imx290->lock);
@@ -623,10 +679,9 @@ static int imx290_set_fmt(struct v4l2_subdev *sd,
        fmt->format.code = imx290_formats[i].code;
        fmt->format.field = V4L2_FIELD_NONE;
 
-       if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
-               format = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad);
-       } else {
-               format = &imx290->current_format;
+       format = imx290_get_pad_format(imx290, sd_state, fmt->which);
+
+       if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
                imx290->current_mode = mode;
                imx290->bpp = imx290_formats[i].bpp;
 
@@ -636,6 +691,20 @@ static int imx290_set_fmt(struct v4l2_subdev *sd,
                if (imx290->pixel_rate)
                        __v4l2_ctrl_s_ctrl_int64(imx290->pixel_rate,
                                                 imx290_calc_pixel_rate(imx290));
+
+               if (imx290->hblank) {
+                       unsigned int hblank = mode->hmax - mode->width;
+
+                       __v4l2_ctrl_modify_range(imx290->hblank, hblank, hblank,
+                                                1, hblank);
+               }
+
+               if (imx290->vblank) {
+                       unsigned int vblank = IMX290_VMAX_DEFAULT - mode->height;
+
+                       __v4l2_ctrl_modify_range(imx290->vblank, vblank, vblank,
+                                                1, vblank);
+               }
        }
 
        *format = fmt->format;
@@ -645,6 +714,52 @@ static int imx290_set_fmt(struct v4l2_subdev *sd,
        return 0;
 }
 
+static int imx290_get_selection(struct v4l2_subdev *sd,
+                               struct v4l2_subdev_state *sd_state,
+                               struct v4l2_subdev_selection *sel)
+{
+       struct imx290 *imx290 = to_imx290(sd);
+       struct v4l2_mbus_framefmt *format;
+
+       switch (sel->target) {
+       case V4L2_SEL_TGT_CROP: {
+               format = imx290_get_pad_format(imx290, sd_state, sel->which);
+
+               mutex_lock(&imx290->lock);
+
+               sel->r.top = IMX920_PIXEL_ARRAY_MARGIN_TOP
+                          + (IMX290_PIXEL_ARRAY_RECORDING_HEIGHT - format->height) / 2;
+               sel->r.left = IMX920_PIXEL_ARRAY_MARGIN_LEFT
+                           + (IMX290_PIXEL_ARRAY_RECORDING_WIDTH - format->width) / 2;
+               sel->r.width = format->width;
+               sel->r.height = format->height;
+
+               mutex_unlock(&imx290->lock);
+               return 0;
+       }
+
+       case V4L2_SEL_TGT_NATIVE_SIZE:
+       case V4L2_SEL_TGT_CROP_BOUNDS:
+               sel->r.top = 0;
+               sel->r.left = 0;
+               sel->r.width = IMX290_PIXEL_ARRAY_WIDTH;
+               sel->r.height = IMX290_PIXEL_ARRAY_HEIGHT;
+
+               return 0;
+
+       case V4L2_SEL_TGT_CROP_DEFAULT:
+               sel->r.top = IMX920_PIXEL_ARRAY_MARGIN_TOP;
+               sel->r.left = IMX920_PIXEL_ARRAY_MARGIN_LEFT;
+               sel->r.width = IMX290_PIXEL_ARRAY_RECORDING_WIDTH;
+               sel->r.height = IMX290_PIXEL_ARRAY_RECORDING_HEIGHT;
+
+               return 0;
+
+       default:
+               return -EINVAL;
+       }
+}
+
 static int imx290_entity_init_cfg(struct v4l2_subdev *subdev,
                                  struct v4l2_subdev_state *sd_state)
 {
@@ -690,25 +805,6 @@ static int imx290_write_current_format(struct imx290 *imx290)
        return 0;
 }
 
-static int imx290_set_hmax(struct imx290 *imx290, u32 val)
-{
-       int ret;
-
-       ret = imx290_write_reg(imx290, IMX290_HMAX_LOW, (val & 0xff));
-       if (ret) {
-               dev_err(imx290->dev, "Error setting HMAX register\n");
-               return ret;
-       }
-
-       ret = imx290_write_reg(imx290, IMX290_HMAX_HIGH, ((val >> 8) & 0xff));
-       if (ret) {
-               dev_err(imx290->dev, "Error setting HMAX register\n");
-               return ret;
-       }
-
-       return 0;
-}
-
 /* Start streaming */
 static int imx290_start_streaming(struct imx290 *imx290)
 {
@@ -737,8 +833,10 @@ static int imx290_start_streaming(struct imx290 *imx290)
                dev_err(imx290->dev, "Could not set current mode\n");
                return ret;
        }
-       ret = imx290_set_hmax(imx290, imx290->current_mode->hmax);
-       if (ret < 0)
+
+       ret = imx290_write(imx290, IMX290_HMAX, imx290->current_mode->hmax,
+                          NULL);
+       if (ret)
                return ret;
 
        /* Apply customized values from user */
@@ -748,14 +846,12 @@ static int imx290_start_streaming(struct imx290 *imx290)
                return ret;
        }
 
-       ret = imx290_write_reg(imx290, IMX290_STANDBY, 0x00);
-       if (ret < 0)
-               return ret;
+       imx290_write(imx290, IMX290_STANDBY, 0x00, &ret);
 
        msleep(30);
 
        /* Start streaming */
-       return imx290_write_reg(imx290, IMX290_XMSTA, 0x00);
+       return imx290_write(imx290, IMX290_XMSTA, 0x00, &ret);
 }
 
 static int imx290_set_stream(struct v4l2_subdev *sd, int enable)
@@ -788,10 +884,10 @@ static int imx290_get_regulators(struct device *dev, struct imx290 *imx290)
 {
        unsigned int i;
 
-       for (i = 0; i < IMX290_NUM_SUPPLIES; i++)
+       for (i = 0; i < ARRAY_SIZE(imx290->supplies); i++)
                imx290->supplies[i].supply = imx290_supply_name[i];
 
-       return devm_regulator_bulk_get(dev, IMX290_NUM_SUPPLIES,
+       return devm_regulator_bulk_get(dev, ARRAY_SIZE(imx290->supplies),
                                       imx290->supplies);
 }
 
@@ -814,27 +910,13 @@ static int imx290_set_data_lanes(struct imx290 *imx290)
                 * validated in probe itself
                 */
                dev_err(imx290->dev, "Lane configuration not supported\n");
-               ret = -EINVAL;
-               goto exit;
-       }
-
-       ret = imx290_write_reg(imx290, IMX290_PHY_LANE_NUM, laneval);
-       if (ret) {
-               dev_err(imx290->dev, "Error setting Physical Lane number register\n");
-               goto exit;
-       }
-
-       ret = imx290_write_reg(imx290, IMX290_CSI_LANE_MODE, laneval);
-       if (ret) {
-               dev_err(imx290->dev, "Error setting CSI Lane mode register\n");
-               goto exit;
+               return -EINVAL;
        }
 
-       ret = imx290_write_reg(imx290, IMX290_FR_FDG_SEL, frsel);
-       if (ret)
-               dev_err(imx290->dev, "Error setting FR/FDG SEL register\n");
+       imx290_write(imx290, IMX290_PHY_LANE_NUM, laneval, &ret);
+       imx290_write(imx290, IMX290_CSI_LANE_MODE, laneval, &ret);
+       imx290_write(imx290, IMX290_FR_FDG_SEL, frsel, &ret);
 
-exit:
        return ret;
 }
 
@@ -850,7 +932,8 @@ static int imx290_power_on(struct device *dev)
                return ret;
        }
 
-       ret = regulator_bulk_enable(IMX290_NUM_SUPPLIES, imx290->supplies);
+       ret = regulator_bulk_enable(ARRAY_SIZE(imx290->supplies),
+                                   imx290->supplies);
        if (ret) {
                dev_err(dev, "Failed to enable regulators\n");
                clk_disable_unprepare(imx290->xclk);
@@ -874,7 +957,7 @@ static int imx290_power_off(struct device *dev)
 
        clk_disable_unprepare(imx290->xclk);
        gpiod_set_value_cansleep(imx290->rst_gpio, 1);
-       regulator_bulk_disable(IMX290_NUM_SUPPLIES, imx290->supplies);
+       regulator_bulk_disable(ARRAY_SIZE(imx290->supplies), imx290->supplies);
 
        return 0;
 }
@@ -893,6 +976,7 @@ static const struct v4l2_subdev_pad_ops imx290_pad_ops = {
        .enum_frame_size = imx290_enum_frame_size,
        .get_fmt = imx290_get_fmt,
        .set_fmt = imx290_set_fmt,
+       .get_selection = imx290_get_selection,
 };
 
 static const struct v4l2_subdev_ops imx290_subdev_ops = {
@@ -904,6 +988,85 @@ static const struct media_entity_operations imx290_subdev_entity_ops = {
        .link_validate = v4l2_subdev_link_validate,
 };
 
+static int imx290_ctrl_init(struct imx290 *imx290)
+{
+       struct v4l2_fwnode_device_properties props;
+       unsigned int blank;
+       int ret;
+
+       ret = v4l2_fwnode_device_parse(imx290->dev, &props);
+       if (ret < 0)
+               return ret;
+
+       v4l2_ctrl_handler_init(&imx290->ctrls, 9);
+       imx290->ctrls.lock = &imx290->lock;
+
+       /*
+        * The sensor has an analog gain and a digital gain, both controlled
+        * through a single gain value, expressed in 0.3dB increments. Values
+        * from 0.0dB (0) to 30.0dB (100) apply analog gain only, higher values
+        * up to 72.0dB (240) add further digital gain. Limit the range to
+        * analog gain only, support for digital gain can be added separately
+        * if needed.
+        *
+        * The IMX327 and IMX462 are largely compatible with the IMX290, but
+        * have an analog gain range of 0.0dB to 29.4dB and 42dB of digital
+        * gain. When support for those sensors gets added to the driver, the
+        * gain control should be adjusted accordingly.
+        */
+       v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops,
+                         V4L2_CID_ANALOGUE_GAIN, 0, 100, 1, 0);
+
+       v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops,
+                         V4L2_CID_EXPOSURE, 1, IMX290_VMAX_DEFAULT - 2, 1,
+                         IMX290_VMAX_DEFAULT - 2);
+
+       imx290->link_freq =
+               v4l2_ctrl_new_int_menu(&imx290->ctrls, &imx290_ctrl_ops,
+                                      V4L2_CID_LINK_FREQ,
+                                      imx290_link_freqs_num(imx290) - 1, 0,
+                                      imx290_link_freqs_ptr(imx290));
+       if (imx290->link_freq)
+               imx290->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+       imx290->pixel_rate = v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops,
+                                              V4L2_CID_PIXEL_RATE,
+                                              1, INT_MAX, 1,
+                                              imx290_calc_pixel_rate(imx290));
+
+       v4l2_ctrl_new_std_menu_items(&imx290->ctrls, &imx290_ctrl_ops,
+                                    V4L2_CID_TEST_PATTERN,
+                                    ARRAY_SIZE(imx290_test_pattern_menu) - 1,
+                                    0, 0, imx290_test_pattern_menu);
+
+       blank = imx290->current_mode->hmax - imx290->current_mode->width;
+       imx290->hblank = v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops,
+                                          V4L2_CID_HBLANK, blank, blank, 1,
+                                          blank);
+       if (imx290->hblank)
+               imx290->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+       blank = IMX290_VMAX_DEFAULT - imx290->current_mode->height;
+       imx290->vblank = v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops,
+                                          V4L2_CID_VBLANK, blank, blank, 1,
+                                          blank);
+       if (imx290->vblank)
+               imx290->vblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+       v4l2_ctrl_new_fwnode_properties(&imx290->ctrls, &imx290_ctrl_ops,
+                                       &props);
+
+       imx290->sd.ctrl_handler = &imx290->ctrls;
+
+       if (imx290->ctrls.error) {
+               ret = imx290->ctrls.error;
+               v4l2_ctrl_handler_free(&imx290->ctrls);
+               return ret;
+       }
+
+       return 0;
+}
+
 /*
  * Returns 0 if all link frequencies used by the driver for the given number
  * of MIPI data lanes are mentioned in the device tree, or the value of the
@@ -1042,36 +1205,10 @@ static int imx290_probe(struct i2c_client *client)
         */
        imx290_entity_init_cfg(&imx290->sd, NULL);
 
-       v4l2_ctrl_handler_init(&imx290->ctrls, 4);
-
-       v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops,
-                         V4L2_CID_GAIN, 0, 72, 1, 0);
-
-       imx290->link_freq =
-               v4l2_ctrl_new_int_menu(&imx290->ctrls, &imx290_ctrl_ops,
-                                      V4L2_CID_LINK_FREQ,
-                                      imx290_link_freqs_num(imx290) - 1, 0,
-                                      imx290_link_freqs_ptr(imx290));
-       if (imx290->link_freq)
-               imx290->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
-
-       imx290->pixel_rate = v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops,
-                                              V4L2_CID_PIXEL_RATE,
-                                              1, INT_MAX, 1,
-                                              imx290_calc_pixel_rate(imx290));
-
-       v4l2_ctrl_new_std_menu_items(&imx290->ctrls, &imx290_ctrl_ops,
-                                    V4L2_CID_TEST_PATTERN,
-                                    ARRAY_SIZE(imx290_test_pattern_menu) - 1,
-                                    0, 0, imx290_test_pattern_menu);
-
-       imx290->sd.ctrl_handler = &imx290->ctrls;
-
-       if (imx290->ctrls.error) {
-               dev_err(dev, "Control initialization error %d\n",
-                       imx290->ctrls.error);
-               ret = imx290->ctrls.error;
-               goto free_ctrl;
+       ret = imx290_ctrl_init(imx290);
+       if (ret < 0) {
+               dev_err(dev, "Control initialization error %d\n", ret);
+               goto free_mutex;
        }
 
        v4l2_i2c_subdev_init(&imx290->sd, client, &imx290_subdev_ops);
@@ -1112,6 +1249,7 @@ free_entity:
        media_entity_cleanup(&imx290->sd.entity);
 free_ctrl:
        v4l2_ctrl_handler_free(&imx290->ctrls);
+free_mutex:
        mutex_destroy(&imx290->lock);
 free_err:
        v4l2_fwnode_endpoint_free(&ep);
diff --git a/drivers/media/i2c/ov08x40.c b/drivers/media/i2c/ov08x40.c
new file mode 100644 (file)
index 0000000..b4ade17
--- /dev/null
@@ -0,0 +1,3327 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2022 Intel Corporation.
+
+#include <linux/acpi.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/pm_runtime.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+
+#define OV08X40_REG_VALUE_08BIT                1
+#define OV08X40_REG_VALUE_16BIT                2
+#define OV08X40_REG_VALUE_24BIT                3
+
+#define OV08X40_REG_MODE_SELECT                0x0100
+#define OV08X40_MODE_STANDBY           0x00
+#define OV08X40_MODE_STREAMING         0x01
+
+#define OV08X40_REG_AO_STANDBY         0x1000
+#define OV08X40_AO_STREAMING           0x04
+
+#define OV08X40_REG_MS_SELECT          0x1001
+#define OV08X40_MS_STANDBY                     0x00
+#define OV08X40_MS_STREAMING           0x04
+
+#define OV08X40_REG_SOFTWARE_RST       0x0103
+#define OV08X40_SOFTWARE_RST           0x01
+
+/* Chip ID */
+#define OV08X40_REG_CHIP_ID            0x300a
+#define OV08X40_CHIP_ID                        0x560858
+
+/* V_TIMING internal */
+#define OV08X40_REG_VTS                        0x380e
+#define OV08X40_VTS_30FPS              0x1388
+#define OV08X40_VTS_BIN_30FPS          0x115c
+#define OV08X40_VTS_MAX                        0x7fff
+
+/* H TIMING internal */
+#define OV08X40_REG_HTS                        0x380c
+#define OV08X40_HTS_30FPS              0x0280
+
+/* Exposure control */
+#define OV08X40_REG_EXPOSURE           0x3500
+#define OV08X40_EXPOSURE_MAX_MARGIN 31
+#define OV08X40_EXPOSURE_MIN           1
+#define OV08X40_EXPOSURE_STEP          1
+#define OV08X40_EXPOSURE_DEFAULT       0x40
+
+/* Short Exposure control */
+#define OV08X40_REG_SHORT_EXPOSURE     0x3540
+
+/* Analog gain control */
+#define OV08X40_REG_ANALOG_GAIN                0x3508
+#define OV08X40_ANA_GAIN_MIN           0x80
+#define OV08X40_ANA_GAIN_MAX           0x07c0
+#define OV08X40_ANA_GAIN_STEP          1
+#define OV08X40_ANA_GAIN_DEFAULT       0x80
+
+/* Digital gain control */
+#define OV08X40_REG_DGTL_GAIN_H                0x350a
+#define OV08X40_REG_DGTL_GAIN_M                0x350b
+#define OV08X40_REG_DGTL_GAIN_L                0x350c
+
+#define OV08X40_DGTL_GAIN_MIN          1024         /* Min = 1 X */
+#define OV08X40_DGTL_GAIN_MAX          (4096 - 1)   /* Max = 4 X */
+#define OV08X40_DGTL_GAIN_DEFAULT      2560         /* Default gain = 2.5 X */
+#define OV08X40_DGTL_GAIN_STEP         1            /* Each step = 1/1024 */
+
+#define OV08X40_DGTL_GAIN_L_SHIFT      6
+#define OV08X40_DGTL_GAIN_L_MASK       0x3
+#define OV08X40_DGTL_GAIN_M_SHIFT      2
+#define OV08X40_DGTL_GAIN_M_MASK       0xff
+#define OV08X40_DGTL_GAIN_H_SHIFT      10
+#define OV08X40_DGTL_GAIN_H_MASK       0x1F
+
+/* Test Pattern Control */
+#define OV08X40_REG_TEST_PATTERN       0x50C1
+#define OV08X40_REG_ISP             0x5000
+#define OV08X40_REG_SHORT_TEST_PATTERN  0x53C1
+#define OV08X40_TEST_PATTERN_ENABLE    BIT(0)
+#define OV08X40_TEST_PATTERN_MASK      0xcf
+#define OV08X40_TEST_PATTERN_BAR_SHIFT 4
+
+/* Flip Control */
+#define OV08X40_REG_VFLIP              0x3820
+#define OV08X40_REG_MIRROR             0x3821
+
+/* Horizontal Window Offset */
+#define OV08X40_REG_H_WIN_OFFSET       0x3811
+
+/* Vertical Window Offset */
+#define OV08X40_REG_V_WIN_OFFSET       0x3813
+
+enum {
+       OV08X40_LINK_FREQ_400MHZ_INDEX,
+};
+
+struct ov08x40_reg {
+       u16 address;
+       u8 val;
+};
+
+struct ov08x40_reg_list {
+       u32 num_of_regs;
+       const struct ov08x40_reg *regs;
+};
+
+/* Link frequency config */
+struct ov08x40_link_freq_config {
+       u32 pixels_per_line;
+
+       /* registers for this link frequency */
+       struct ov08x40_reg_list reg_list;
+};
+
+/* Mode : resolution and related config&values */
+struct ov08x40_mode {
+       /* Frame width */
+       u32 width;
+       /* Frame height */
+       u32 height;
+
+       u32 lanes;
+       /* V-timing */
+       u32 vts_def;
+       u32 vts_min;
+
+       /* Index of Link frequency config to be used */
+       u32 link_freq_index;
+       /* Default register values */
+       struct ov08x40_reg_list reg_list;
+};
+
+static const struct ov08x40_reg mipi_data_rate_800mbps[] = {
+       {0x0103, 0x01},
+       {0x1000, 0x00},
+       {0x1601, 0xd0},
+       {0x1001, 0x04},
+       {0x5004, 0x53},
+       {0x5110, 0x00},
+       {0x5111, 0x14},
+       {0x5112, 0x01},
+       {0x5113, 0x7b},
+       {0x5114, 0x00},
+       {0x5152, 0xa3},
+       {0x5a52, 0x1f},
+       {0x5a1a, 0x0e},
+       {0x5a1b, 0x10},
+       {0x5a1f, 0x0e},
+       {0x5a27, 0x0e},
+       {0x6002, 0x2e},
+};
+
+static const struct ov08x40_reg mode_3856x2416_regs[] = {
+       {0x5000, 0x5d},
+       {0x5001, 0x20},
+       {0x5008, 0xb0},
+       {0x50c1, 0x00},
+       {0x53c1, 0x00},
+       {0x5f40, 0x00},
+       {0x5f41, 0x40},
+       {0x0300, 0x3a},
+       {0x0301, 0xc8},
+       {0x0302, 0x31},
+       {0x0303, 0x03},
+       {0x0304, 0x01},
+       {0x0305, 0xa1},
+       {0x0306, 0x04},
+       {0x0307, 0x01},
+       {0x0308, 0x03},
+       {0x0309, 0x03},
+       {0x0310, 0x0a},
+       {0x0311, 0x02},
+       {0x0312, 0x01},
+       {0x0313, 0x08},
+       {0x0314, 0x66},
+       {0x0315, 0x00},
+       {0x0316, 0x34},
+       {0x0320, 0x02},
+       {0x0321, 0x03},
+       {0x0323, 0x05},
+       {0x0324, 0x01},
+       {0x0325, 0xb8},
+       {0x0326, 0x4a},
+       {0x0327, 0x04},
+       {0x0329, 0x00},
+       {0x032a, 0x05},
+       {0x032b, 0x00},
+       {0x032c, 0x00},
+       {0x032d, 0x00},
+       {0x032e, 0x02},
+       {0x032f, 0xa0},
+       {0x0350, 0x00},
+       {0x0360, 0x01},
+       {0x1216, 0x60},
+       {0x1217, 0x5b},
+       {0x1218, 0x00},
+       {0x1220, 0x24},
+       {0x198a, 0x00},
+       {0x198b, 0x01},
+       {0x198e, 0x00},
+       {0x198f, 0x01},
+       {0x3009, 0x04},
+       {0x3012, 0x41},
+       {0x3015, 0x00},
+       {0x3016, 0xb0},
+       {0x3017, 0xf0},
+       {0x3018, 0xf0},
+       {0x3019, 0xd2},
+       {0x301a, 0xb0},
+       {0x301c, 0x81},
+       {0x301d, 0x02},
+       {0x301e, 0x80},
+       {0x3022, 0xf0},
+       {0x3025, 0x89},
+       {0x3030, 0x03},
+       {0x3044, 0xc2},
+       {0x3050, 0x35},
+       {0x3051, 0x60},
+       {0x3052, 0x25},
+       {0x3053, 0x00},
+       {0x3054, 0x00},
+       {0x3055, 0x02},
+       {0x3056, 0x80},
+       {0x3057, 0x80},
+       {0x3058, 0x80},
+       {0x3059, 0x00},
+       {0x3107, 0x86},
+       {0x3400, 0x1c},
+       {0x3401, 0x80},
+       {0x3402, 0x8c},
+       {0x3419, 0x13},
+       {0x341a, 0x89},
+       {0x341b, 0x30},
+       {0x3420, 0x00},
+       {0x3421, 0x00},
+       {0x3422, 0x00},
+       {0x3423, 0x00},
+       {0x3424, 0x00},
+       {0x3425, 0x00},
+       {0x3426, 0x00},
+       {0x3427, 0x00},
+       {0x3428, 0x0f},
+       {0x3429, 0x00},
+       {0x342a, 0x00},
+       {0x342b, 0x00},
+       {0x342c, 0x00},
+       {0x342d, 0x00},
+       {0x342e, 0x00},
+       {0x342f, 0x11},
+       {0x3430, 0x11},
+       {0x3431, 0x10},
+       {0x3432, 0x00},
+       {0x3433, 0x00},
+       {0x3434, 0x00},
+       {0x3435, 0x00},
+       {0x3436, 0x00},
+       {0x3437, 0x00},
+       {0x3442, 0x02},
+       {0x3443, 0x02},
+       {0x3444, 0x07},
+       {0x3450, 0x00},
+       {0x3451, 0x00},
+       {0x3452, 0x18},
+       {0x3453, 0x18},
+       {0x3454, 0x00},
+       {0x3455, 0x80},
+       {0x3456, 0x08},
+       {0x3500, 0x00},
+       {0x3501, 0x02},
+       {0x3502, 0x00},
+       {0x3504, 0x4c},
+       {0x3506, 0x30},
+       {0x3507, 0x00},
+       {0x3508, 0x01},
+       {0x3509, 0x00},
+       {0x350a, 0x01},
+       {0x350b, 0x00},
+       {0x350c, 0x00},
+       {0x3540, 0x00},
+       {0x3541, 0x01},
+       {0x3542, 0x00},
+       {0x3544, 0x4c},
+       {0x3546, 0x30},
+       {0x3547, 0x00},
+       {0x3548, 0x01},
+       {0x3549, 0x00},
+       {0x354a, 0x01},
+       {0x354b, 0x00},
+       {0x354c, 0x00},
+       {0x3688, 0x02},
+       {0x368a, 0x2e},
+       {0x368e, 0x71},
+       {0x3696, 0xd1},
+       {0x3699, 0x00},
+       {0x369a, 0x00},
+       {0x36a4, 0x00},
+       {0x36a6, 0x00},
+       {0x3711, 0x00},
+       {0x3712, 0x51},
+       {0x3713, 0x00},
+       {0x3714, 0x24},
+       {0x3716, 0x00},
+       {0x3718, 0x07},
+       {0x371a, 0x1c},
+       {0x371b, 0x00},
+       {0x3720, 0x08},
+       {0x3725, 0x32},
+       {0x3727, 0x05},
+       {0x3760, 0x02},
+       {0x3761, 0x17},
+       {0x3762, 0x02},
+       {0x3763, 0x02},
+       {0x3764, 0x02},
+       {0x3765, 0x2c},
+       {0x3766, 0x04},
+       {0x3767, 0x2c},
+       {0x3768, 0x02},
+       {0x3769, 0x00},
+       {0x376b, 0x20},
+       {0x376e, 0x03},
+       {0x37b0, 0x00},
+       {0x37b1, 0xab},
+       {0x37b2, 0x01},
+       {0x37b3, 0x82},
+       {0x37b4, 0x00},
+       {0x37b5, 0xe4},
+       {0x37b6, 0x01},
+       {0x37b7, 0xee},
+       {0x3800, 0x00},
+       {0x3801, 0x00},
+       {0x3802, 0x00},
+       {0x3803, 0x00},
+       {0x3804, 0x0f},
+       {0x3805, 0x1f},
+       {0x3806, 0x09},
+       {0x3807, 0x7f},
+       {0x3808, 0x0f},
+       {0x3809, 0x10},
+       {0x380a, 0x09},
+       {0x380b, 0x70},
+       {0x380c, 0x02},
+       {0x380d, 0x80},
+       {0x380e, 0x13},
+       {0x380f, 0x88},
+       {0x3810, 0x00},
+       {0x3811, 0x08},
+       {0x3812, 0x00},
+       {0x3813, 0x07},
+       {0x3814, 0x11},
+       {0x3815, 0x11},
+       {0x3820, 0x00},
+       {0x3821, 0x04},
+       {0x3822, 0x00},
+       {0x3823, 0x04},
+       {0x3828, 0x0f},
+       {0x382a, 0x80},
+       {0x382e, 0x41},
+       {0x3837, 0x08},
+       {0x383a, 0x81},
+       {0x383b, 0x81},
+       {0x383c, 0x11},
+       {0x383d, 0x11},
+       {0x383e, 0x00},
+       {0x383f, 0x38},
+       {0x3840, 0x00},
+       {0x3847, 0x00},
+       {0x384a, 0x00},
+       {0x384c, 0x02},
+       {0x384d, 0x80},
+       {0x3856, 0x50},
+       {0x3857, 0x30},
+       {0x3858, 0x80},
+       {0x3859, 0x40},
+       {0x3860, 0x00},
+       {0x3888, 0x00},
+       {0x3889, 0x00},
+       {0x388a, 0x00},
+       {0x388b, 0x00},
+       {0x388c, 0x00},
+       {0x388d, 0x00},
+       {0x388e, 0x00},
+       {0x388f, 0x00},
+       {0x3894, 0x00},
+       {0x3895, 0x00},
+       {0x3c84, 0x00},
+       {0x3d85, 0x8b},
+       {0x3daa, 0x80},
+       {0x3dab, 0x14},
+       {0x3dac, 0x80},
+       {0x3dad, 0xc8},
+       {0x3dae, 0x81},
+       {0x3daf, 0x7b},
+       {0x3f00, 0x10},
+       {0x3f01, 0x11},
+       {0x3f06, 0x0d},
+       {0x3f07, 0x0b},
+       {0x3f08, 0x0d},
+       {0x3f09, 0x0b},
+       {0x3f0a, 0x01},
+       {0x3f0b, 0x11},
+       {0x3f0c, 0x33},
+       {0x4001, 0x07},
+       {0x4007, 0x20},
+       {0x4008, 0x00},
+       {0x4009, 0x05},
+       {0x400a, 0x00},
+       {0x400b, 0x08},
+       {0x400c, 0x00},
+       {0x400d, 0x08},
+       {0x400e, 0x14},
+       {0x4010, 0xf4},
+       {0x4011, 0x03},
+       {0x4012, 0x55},
+       {0x4015, 0x00},
+       {0x4016, 0x2d},
+       {0x4017, 0x00},
+       {0x4018, 0x0f},
+       {0x401b, 0x08},
+       {0x401c, 0x00},
+       {0x401d, 0x10},
+       {0x401e, 0x02},
+       {0x401f, 0x00},
+       {0x4050, 0x06},
+       {0x4051, 0xff},
+       {0x4052, 0xff},
+       {0x4053, 0xff},
+       {0x4054, 0xff},
+       {0x4055, 0xff},
+       {0x4056, 0xff},
+       {0x4057, 0x7f},
+       {0x4058, 0x00},
+       {0x4059, 0x00},
+       {0x405a, 0x00},
+       {0x405b, 0x00},
+       {0x405c, 0x07},
+       {0x405d, 0xff},
+       {0x405e, 0x07},
+       {0x405f, 0xff},
+       {0x4080, 0x78},
+       {0x4081, 0x78},
+       {0x4082, 0x78},
+       {0x4083, 0x78},
+       {0x4019, 0x00},
+       {0x401a, 0x40},
+       {0x4020, 0x04},
+       {0x4021, 0x00},
+       {0x4022, 0x04},
+       {0x4023, 0x00},
+       {0x4024, 0x04},
+       {0x4025, 0x00},
+       {0x4026, 0x04},
+       {0x4027, 0x00},
+       {0x4030, 0x00},
+       {0x4031, 0x00},
+       {0x4032, 0x00},
+       {0x4033, 0x00},
+       {0x4034, 0x00},
+       {0x4035, 0x00},
+       {0x4036, 0x00},
+       {0x4037, 0x00},
+       {0x4040, 0x00},
+       {0x4041, 0x80},
+       {0x4042, 0x00},
+       {0x4043, 0x80},
+       {0x4044, 0x00},
+       {0x4045, 0x80},
+       {0x4046, 0x00},
+       {0x4047, 0x80},
+       {0x4060, 0x00},
+       {0x4061, 0x00},
+       {0x4062, 0x00},
+       {0x4063, 0x00},
+       {0x4064, 0x00},
+       {0x4065, 0x00},
+       {0x4066, 0x00},
+       {0x4067, 0x00},
+       {0x4068, 0x00},
+       {0x4069, 0x00},
+       {0x406a, 0x00},
+       {0x406b, 0x00},
+       {0x406c, 0x00},
+       {0x406d, 0x00},
+       {0x406e, 0x00},
+       {0x406f, 0x00},
+       {0x4070, 0x00},
+       {0x4071, 0x00},
+       {0x4072, 0x00},
+       {0x4073, 0x00},
+       {0x4074, 0x00},
+       {0x4075, 0x00},
+       {0x4076, 0x00},
+       {0x4077, 0x00},
+       {0x4078, 0x00},
+       {0x4079, 0x00},
+       {0x407a, 0x00},
+       {0x407b, 0x00},
+       {0x407c, 0x00},
+       {0x407d, 0x00},
+       {0x407e, 0x00},
+       {0x407f, 0x00},
+       {0x40e0, 0x00},
+       {0x40e1, 0x00},
+       {0x40e2, 0x00},
+       {0x40e3, 0x00},
+       {0x40e4, 0x00},
+       {0x40e5, 0x00},
+       {0x40e6, 0x00},
+       {0x40e7, 0x00},
+       {0x40e8, 0x00},
+       {0x40e9, 0x80},
+       {0x40ea, 0x00},
+       {0x40eb, 0x80},
+       {0x40ec, 0x00},
+       {0x40ed, 0x80},
+       {0x40ee, 0x00},
+       {0x40ef, 0x80},
+       {0x40f0, 0x02},
+       {0x40f1, 0x04},
+       {0x4300, 0x00},
+       {0x4301, 0x00},
+       {0x4302, 0x00},
+       {0x4303, 0x00},
+       {0x4304, 0x00},
+       {0x4305, 0x00},
+       {0x4306, 0x00},
+       {0x4307, 0x00},
+       {0x4308, 0x00},
+       {0x4309, 0x00},
+       {0x430a, 0x00},
+       {0x430b, 0xff},
+       {0x430c, 0xff},
+       {0x430d, 0x00},
+       {0x430e, 0x00},
+       {0x4315, 0x00},
+       {0x4316, 0x00},
+       {0x4317, 0x00},
+       {0x4318, 0x00},
+       {0x4319, 0x00},
+       {0x431a, 0x00},
+       {0x431b, 0x00},
+       {0x431c, 0x00},
+       {0x4500, 0x07},
+       {0x4501, 0x00},
+       {0x4502, 0x00},
+       {0x4503, 0x0f},
+       {0x4504, 0x80},
+       {0x4506, 0x01},
+       {0x4509, 0x05},
+       {0x450c, 0x00},
+       {0x450d, 0x20},
+       {0x450e, 0x00},
+       {0x450f, 0x00},
+       {0x4510, 0x00},
+       {0x4523, 0x00},
+       {0x4526, 0x00},
+       {0x4542, 0x00},
+       {0x4543, 0x00},
+       {0x4544, 0x00},
+       {0x4545, 0x00},
+       {0x4546, 0x00},
+       {0x4547, 0x10},
+       {0x4602, 0x00},
+       {0x4603, 0x15},
+       {0x460b, 0x07},
+       {0x4680, 0x11},
+       {0x4686, 0x00},
+       {0x4687, 0x00},
+       {0x4700, 0x00},
+       {0x4800, 0x64},
+       {0x4806, 0x40},
+       {0x480b, 0x10},
+       {0x480c, 0x80},
+       {0x480f, 0x32},
+       {0x4813, 0xe4},
+       {0x4837, 0x14},
+       {0x4850, 0x42},
+       {0x4884, 0x04},
+       {0x4c00, 0xf8},
+       {0x4c01, 0x44},
+       {0x4c03, 0x00},
+       {0x4d00, 0x00},
+       {0x4d01, 0x16},
+       {0x4d04, 0x10},
+       {0x4d05, 0x00},
+       {0x4d06, 0x0c},
+       {0x4d07, 0x00},
+       {0x3d84, 0x04},
+       {0x3680, 0xa4},
+       {0x3682, 0x80},
+       {0x3601, 0x40},
+       {0x3602, 0x90},
+       {0x3608, 0x0a},
+       {0x3938, 0x09},
+       {0x3a74, 0x84},
+       {0x3a99, 0x84},
+       {0x3ab9, 0xa6},
+       {0x3aba, 0xba},
+       {0x3b12, 0x84},
+       {0x3b14, 0xbb},
+       {0x3b15, 0xbf},
+       {0x3a29, 0x26},
+       {0x3a1f, 0x8a},
+       {0x3a22, 0x91},
+       {0x3a25, 0x96},
+       {0x3a28, 0xb4},
+       {0x3a2b, 0xba},
+       {0x3a2e, 0xbf},
+       {0x3a31, 0xc1},
+       {0x3a20, 0x00},
+       {0x3939, 0x9d},
+       {0x3902, 0x0e},
+       {0x3903, 0x0e},
+       {0x3904, 0x0e},
+       {0x3905, 0x0e},
+       {0x3906, 0x07},
+       {0x3907, 0x0d},
+       {0x3908, 0x11},
+       {0x3909, 0x12},
+       {0x360f, 0x99},
+       {0x390c, 0x33},
+       {0x390d, 0x66},
+       {0x390e, 0xaa},
+       {0x3911, 0x90},
+       {0x3913, 0x90},
+       {0x3915, 0x90},
+       {0x3917, 0x90},
+       {0x3b3f, 0x9d},
+       {0x3b45, 0x9d},
+       {0x3b1b, 0xc9},
+       {0x3b21, 0xc9},
+       {0x3440, 0xa4},
+       {0x3a23, 0x15},
+       {0x3a26, 0x1d},
+       {0x3a2c, 0x4a},
+       {0x3a2f, 0x18},
+       {0x3a32, 0x55},
+       {0x3b0a, 0x01},
+       {0x3b0b, 0x00},
+       {0x3b0e, 0x01},
+       {0x3b0f, 0x00},
+       {0x392c, 0x02},
+       {0x392d, 0x02},
+       {0x392e, 0x04},
+       {0x392f, 0x03},
+       {0x3930, 0x08},
+       {0x3931, 0x07},
+       {0x3932, 0x10},
+       {0x3933, 0x0c},
+       {0x3609, 0x08},
+       {0x3921, 0x0f},
+       {0x3928, 0x15},
+       {0x3929, 0x2a},
+       {0x392a, 0x54},
+       {0x392b, 0xa8},
+       {0x3426, 0x10},
+       {0x3407, 0x01},
+       {0x3404, 0x01},
+       {0x3500, 0x00},
+       {0x3501, 0x10},
+       {0x3502, 0x10},
+       {0x3508, 0x0f},
+       {0x3509, 0x80},
+       {0x5a80, 0x75},
+       {0x5a81, 0x75},
+       {0x5a82, 0x75},
+       {0x5a83, 0x75},
+       {0x5a84, 0x75},
+       {0x5a85, 0x75},
+       {0x5a86, 0x75},
+       {0x5a87, 0x75},
+       {0x5a88, 0x75},
+       {0x5a89, 0x75},
+       {0x5a8a, 0x75},
+       {0x5a8b, 0x75},
+       {0x5a8c, 0x75},
+       {0x5a8d, 0x75},
+       {0x5a8e, 0x75},
+       {0x5a8f, 0x75},
+       {0x5a90, 0x75},
+       {0x5a91, 0x75},
+       {0x5a92, 0x75},
+       {0x5a93, 0x75},
+       {0x5a94, 0x75},
+       {0x5a95, 0x75},
+       {0x5a96, 0x75},
+       {0x5a97, 0x75},
+       {0x5a98, 0x75},
+       {0x5a99, 0x75},
+       {0x5a9a, 0x75},
+       {0x5a9b, 0x75},
+       {0x5a9c, 0x75},
+       {0x5a9d, 0x75},
+       {0x5a9e, 0x75},
+       {0x5a9f, 0x75},
+       {0x5aa0, 0x75},
+       {0x5aa1, 0x75},
+       {0x5aa2, 0x75},
+       {0x5aa3, 0x75},
+       {0x5aa4, 0x75},
+       {0x5aa5, 0x75},
+       {0x5aa6, 0x75},
+       {0x5aa7, 0x75},
+       {0x5aa8, 0x75},
+       {0x5aa9, 0x75},
+       {0x5aaa, 0x75},
+       {0x5aab, 0x75},
+       {0x5aac, 0x75},
+       {0x5aad, 0x75},
+       {0x5aae, 0x75},
+       {0x5aaf, 0x75},
+       {0x5ab0, 0x75},
+       {0x5ab1, 0x75},
+       {0x5ab2, 0x75},
+       {0x5ab3, 0x75},
+       {0x5ab4, 0x75},
+       {0x5ab5, 0x75},
+       {0x5ab6, 0x75},
+       {0x5ab7, 0x75},
+       {0x5ab8, 0x75},
+       {0x5ab9, 0x75},
+       {0x5aba, 0x75},
+       {0x5abb, 0x75},
+       {0x5abc, 0x75},
+       {0x5abd, 0x75},
+       {0x5abe, 0x75},
+       {0x5abf, 0x75},
+       {0x5ac0, 0x75},
+       {0x5ac1, 0x75},
+       {0x5ac2, 0x75},
+       {0x5ac3, 0x75},
+       {0x5ac4, 0x75},
+       {0x5ac5, 0x75},
+       {0x5ac6, 0x75},
+       {0x5ac7, 0x75},
+       {0x5ac8, 0x75},
+       {0x5ac9, 0x75},
+       {0x5aca, 0x75},
+       {0x5acb, 0x75},
+       {0x5acc, 0x75},
+       {0x5acd, 0x75},
+       {0x5ace, 0x75},
+       {0x5acf, 0x75},
+       {0x5ad0, 0x75},
+       {0x5ad1, 0x75},
+       {0x5ad2, 0x75},
+       {0x5ad3, 0x75},
+       {0x5ad4, 0x75},
+       {0x5ad5, 0x75},
+       {0x5ad6, 0x75},
+       {0x5ad7, 0x75},
+       {0x5ad8, 0x75},
+       {0x5ad9, 0x75},
+       {0x5ada, 0x75},
+       {0x5adb, 0x75},
+       {0x5adc, 0x75},
+       {0x5add, 0x75},
+       {0x5ade, 0x75},
+       {0x5adf, 0x75},
+       {0x5ae0, 0x75},
+       {0x5ae1, 0x75},
+       {0x5ae2, 0x75},
+       {0x5ae3, 0x75},
+       {0x5ae4, 0x75},
+       {0x5ae5, 0x75},
+       {0x5ae6, 0x75},
+       {0x5ae7, 0x75},
+       {0x5ae8, 0x75},
+       {0x5ae9, 0x75},
+       {0x5aea, 0x75},
+       {0x5aeb, 0x75},
+       {0x5aec, 0x75},
+       {0x5aed, 0x75},
+       {0x5aee, 0x75},
+       {0x5aef, 0x75},
+       {0x5af0, 0x75},
+       {0x5af1, 0x75},
+       {0x5af2, 0x75},
+       {0x5af3, 0x75},
+       {0x5af4, 0x75},
+       {0x5af5, 0x75},
+       {0x5af6, 0x75},
+       {0x5af7, 0x75},
+       {0x5af8, 0x75},
+       {0x5af9, 0x75},
+       {0x5afa, 0x75},
+       {0x5afb, 0x75},
+       {0x5afc, 0x75},
+       {0x5afd, 0x75},
+       {0x5afe, 0x75},
+       {0x5aff, 0x75},
+       {0x5b00, 0x75},
+       {0x5b01, 0x75},
+       {0x5b02, 0x75},
+       {0x5b03, 0x75},
+       {0x5b04, 0x75},
+       {0x5b05, 0x75},
+       {0x5b06, 0x75},
+       {0x5b07, 0x75},
+       {0x5b08, 0x75},
+       {0x5b09, 0x75},
+       {0x5b0a, 0x75},
+       {0x5b0b, 0x75},
+       {0x5b0c, 0x75},
+       {0x5b0d, 0x75},
+       {0x5b0e, 0x75},
+       {0x5b0f, 0x75},
+       {0x5b10, 0x75},
+       {0x5b11, 0x75},
+       {0x5b12, 0x75},
+       {0x5b13, 0x75},
+       {0x5b14, 0x75},
+       {0x5b15, 0x75},
+       {0x5b16, 0x75},
+       {0x5b17, 0x75},
+       {0x5b18, 0x75},
+       {0x5b19, 0x75},
+       {0x5b1a, 0x75},
+       {0x5b1b, 0x75},
+       {0x5b1c, 0x75},
+       {0x5b1d, 0x75},
+       {0x5b1e, 0x75},
+       {0x5b1f, 0x75},
+       {0x5b20, 0x75},
+       {0x5b21, 0x75},
+       {0x5b22, 0x75},
+       {0x5b23, 0x75},
+       {0x5b24, 0x75},
+       {0x5b25, 0x75},
+       {0x5b26, 0x75},
+       {0x5b27, 0x75},
+       {0x5b28, 0x75},
+       {0x5b29, 0x75},
+       {0x5b2a, 0x75},
+       {0x5b2b, 0x75},
+       {0x5b2c, 0x75},
+       {0x5b2d, 0x75},
+       {0x5b2e, 0x75},
+       {0x5b2f, 0x75},
+       {0x5b30, 0x75},
+       {0x5b31, 0x75},
+       {0x5b32, 0x75},
+       {0x5b33, 0x75},
+       {0x5b34, 0x75},
+       {0x5b35, 0x75},
+       {0x5b36, 0x75},
+       {0x5b37, 0x75},
+       {0x5b38, 0x75},
+       {0x5b39, 0x75},
+       {0x5b3a, 0x75},
+       {0x5b3b, 0x75},
+       {0x5b3c, 0x75},
+       {0x5b3d, 0x75},
+       {0x5b3e, 0x75},
+       {0x5b3f, 0x75},
+       {0x5b40, 0x75},
+       {0x5b41, 0x75},
+       {0x5b42, 0x75},
+       {0x5b43, 0x75},
+       {0x5b44, 0x75},
+       {0x5b45, 0x75},
+       {0x5b46, 0x75},
+       {0x5b47, 0x75},
+       {0x5b48, 0x75},
+       {0x5b49, 0x75},
+       {0x5b4a, 0x75},
+       {0x5b4b, 0x75},
+       {0x5b4c, 0x75},
+       {0x5b4d, 0x75},
+       {0x5b4e, 0x75},
+       {0x5b4f, 0x75},
+       {0x5b50, 0x75},
+       {0x5b51, 0x75},
+       {0x5b52, 0x75},
+       {0x5b53, 0x75},
+       {0x5b54, 0x75},
+       {0x5b55, 0x75},
+       {0x5b56, 0x75},
+       {0x5b57, 0x75},
+       {0x5b58, 0x75},
+       {0x5b59, 0x75},
+       {0x5b5a, 0x75},
+       {0x5b5b, 0x75},
+       {0x5b5c, 0x75},
+       {0x5b5d, 0x75},
+       {0x5b5e, 0x75},
+       {0x5b5f, 0x75},
+       {0x5b60, 0x75},
+       {0x5b61, 0x75},
+       {0x5b62, 0x75},
+       {0x5b63, 0x75},
+       {0x5b64, 0x75},
+       {0x5b65, 0x75},
+       {0x5b66, 0x75},
+       {0x5b67, 0x75},
+       {0x5b68, 0x75},
+       {0x5b69, 0x75},
+       {0x5b6a, 0x75},
+       {0x5b6b, 0x75},
+       {0x5b6c, 0x75},
+       {0x5b6d, 0x75},
+       {0x5b6e, 0x75},
+       {0x5b6f, 0x75},
+       {0x5b70, 0x75},
+       {0x5b71, 0x75},
+       {0x5b72, 0x75},
+       {0x5b73, 0x75},
+       {0x5b74, 0x75},
+       {0x5b75, 0x75},
+       {0x5b76, 0x75},
+       {0x5b77, 0x75},
+       {0x5b78, 0x75},
+       {0x5b79, 0x75},
+       {0x5b7a, 0x75},
+       {0x5b7b, 0x75},
+       {0x5b7c, 0x75},
+       {0x5b7d, 0x75},
+       {0x5b7e, 0x75},
+       {0x5b7f, 0x75},
+       {0x5b80, 0x75},
+       {0x5b81, 0x75},
+       {0x5b82, 0x75},
+       {0x5b83, 0x75},
+       {0x5b84, 0x75},
+       {0x5b85, 0x75},
+       {0x5b86, 0x75},
+       {0x5b87, 0x75},
+       {0x5b88, 0x75},
+       {0x5b89, 0x75},
+       {0x5b8a, 0x75},
+       {0x5b8b, 0x75},
+       {0x5b8c, 0x75},
+       {0x5b8d, 0x75},
+       {0x5b8e, 0x75},
+       {0x5b8f, 0x75},
+       {0x5b90, 0x75},
+       {0x5b91, 0x75},
+       {0x5b92, 0x75},
+       {0x5b93, 0x75},
+       {0x5b94, 0x75},
+       {0x5b95, 0x75},
+       {0x5b96, 0x75},
+       {0x5b97, 0x75},
+       {0x5b98, 0x75},
+       {0x5b99, 0x75},
+       {0x5b9a, 0x75},
+       {0x5b9b, 0x75},
+       {0x5b9c, 0x75},
+       {0x5b9d, 0x75},
+       {0x5b9e, 0x75},
+       {0x5b9f, 0x75},
+       {0x5bc0, 0x75},
+       {0x5bc1, 0x75},
+       {0x5bc2, 0x75},
+       {0x5bc3, 0x75},
+       {0x5bc4, 0x75},
+       {0x5bc5, 0x75},
+       {0x5bc6, 0x75},
+       {0x5bc7, 0x75},
+       {0x5bc8, 0x75},
+       {0x5bc9, 0x75},
+       {0x5bca, 0x75},
+       {0x5bcb, 0x75},
+       {0x5bcc, 0x75},
+       {0x5bcd, 0x75},
+       {0x5bce, 0x75},
+       {0x5bcf, 0x75},
+       {0x5bd0, 0x75},
+       {0x5bd1, 0x75},
+       {0x5bd2, 0x75},
+       {0x5bd3, 0x75},
+       {0x5bd4, 0x75},
+       {0x5bd5, 0x75},
+       {0x5bd6, 0x75},
+       {0x5bd7, 0x75},
+       {0x5bd8, 0x75},
+       {0x5bd9, 0x75},
+       {0x5bda, 0x75},
+       {0x5bdb, 0x75},
+       {0x5bdc, 0x75},
+       {0x5bdd, 0x75},
+       {0x5bde, 0x75},
+       {0x5bdf, 0x75},
+       {0x5be0, 0x75},
+       {0x5be1, 0x75},
+       {0x5be2, 0x75},
+       {0x5be3, 0x75},
+       {0x5be4, 0x75},
+       {0x5be5, 0x75},
+       {0x5be6, 0x75},
+       {0x5be7, 0x75},
+       {0x5be8, 0x75},
+       {0x5be9, 0x75},
+       {0x5bea, 0x75},
+       {0x5beb, 0x75},
+       {0x5bec, 0x75},
+       {0x5bed, 0x75},
+       {0x5bee, 0x75},
+       {0x5bef, 0x75},
+       {0x5bf0, 0x75},
+       {0x5bf1, 0x75},
+       {0x5bf2, 0x75},
+       {0x5bf3, 0x75},
+       {0x5bf4, 0x75},
+       {0x5bf5, 0x75},
+       {0x5bf6, 0x75},
+       {0x5bf7, 0x75},
+       {0x5bf8, 0x75},
+       {0x5bf9, 0x75},
+       {0x5bfa, 0x75},
+       {0x5bfb, 0x75},
+       {0x5bfc, 0x75},
+       {0x5bfd, 0x75},
+       {0x5bfe, 0x75},
+       {0x5bff, 0x75},
+       {0x5c00, 0x75},
+       {0x5c01, 0x75},
+       {0x5c02, 0x75},
+       {0x5c03, 0x75},
+       {0x5c04, 0x75},
+       {0x5c05, 0x75},
+       {0x5c06, 0x75},
+       {0x5c07, 0x75},
+       {0x5c08, 0x75},
+       {0x5c09, 0x75},
+       {0x5c0a, 0x75},
+       {0x5c0b, 0x75},
+       {0x5c0c, 0x75},
+       {0x5c0d, 0x75},
+       {0x5c0e, 0x75},
+       {0x5c0f, 0x75},
+       {0x5c10, 0x75},
+       {0x5c11, 0x75},
+       {0x5c12, 0x75},
+       {0x5c13, 0x75},
+       {0x5c14, 0x75},
+       {0x5c15, 0x75},
+       {0x5c16, 0x75},
+       {0x5c17, 0x75},
+       {0x5c18, 0x75},
+       {0x5c19, 0x75},
+       {0x5c1a, 0x75},
+       {0x5c1b, 0x75},
+       {0x5c1c, 0x75},
+       {0x5c1d, 0x75},
+       {0x5c1e, 0x75},
+       {0x5c1f, 0x75},
+       {0x5c20, 0x75},
+       {0x5c21, 0x75},
+       {0x5c22, 0x75},
+       {0x5c23, 0x75},
+       {0x5c24, 0x75},
+       {0x5c25, 0x75},
+       {0x5c26, 0x75},
+       {0x5c27, 0x75},
+       {0x5c28, 0x75},
+       {0x5c29, 0x75},
+       {0x5c2a, 0x75},
+       {0x5c2b, 0x75},
+       {0x5c2c, 0x75},
+       {0x5c2d, 0x75},
+       {0x5c2e, 0x75},
+       {0x5c2f, 0x75},
+       {0x5c30, 0x75},
+       {0x5c31, 0x75},
+       {0x5c32, 0x75},
+       {0x5c33, 0x75},
+       {0x5c34, 0x75},
+       {0x5c35, 0x75},
+       {0x5c36, 0x75},
+       {0x5c37, 0x75},
+       {0x5c38, 0x75},
+       {0x5c39, 0x75},
+       {0x5c3a, 0x75},
+       {0x5c3b, 0x75},
+       {0x5c3c, 0x75},
+       {0x5c3d, 0x75},
+       {0x5c3e, 0x75},
+       {0x5c3f, 0x75},
+       {0x5c40, 0x75},
+       {0x5c41, 0x75},
+       {0x5c42, 0x75},
+       {0x5c43, 0x75},
+       {0x5c44, 0x75},
+       {0x5c45, 0x75},
+       {0x5c46, 0x75},
+       {0x5c47, 0x75},
+       {0x5c48, 0x75},
+       {0x5c49, 0x75},
+       {0x5c4a, 0x75},
+       {0x5c4b, 0x75},
+       {0x5c4c, 0x75},
+       {0x5c4d, 0x75},
+       {0x5c4e, 0x75},
+       {0x5c4f, 0x75},
+       {0x5c50, 0x75},
+       {0x5c51, 0x75},
+       {0x5c52, 0x75},
+       {0x5c53, 0x75},
+       {0x5c54, 0x75},
+       {0x5c55, 0x75},
+       {0x5c56, 0x75},
+       {0x5c57, 0x75},
+       {0x5c58, 0x75},
+       {0x5c59, 0x75},
+       {0x5c5a, 0x75},
+       {0x5c5b, 0x75},
+       {0x5c5c, 0x75},
+       {0x5c5d, 0x75},
+       {0x5c5e, 0x75},
+       {0x5c5f, 0x75},
+       {0x5c60, 0x75},
+       {0x5c61, 0x75},
+       {0x5c62, 0x75},
+       {0x5c63, 0x75},
+       {0x5c64, 0x75},
+       {0x5c65, 0x75},
+       {0x5c66, 0x75},
+       {0x5c67, 0x75},
+       {0x5c68, 0x75},
+       {0x5c69, 0x75},
+       {0x5c6a, 0x75},
+       {0x5c6b, 0x75},
+       {0x5c6c, 0x75},
+       {0x5c6d, 0x75},
+       {0x5c6e, 0x75},
+       {0x5c6f, 0x75},
+       {0x5c70, 0x75},
+       {0x5c71, 0x75},
+       {0x5c72, 0x75},
+       {0x5c73, 0x75},
+       {0x5c74, 0x75},
+       {0x5c75, 0x75},
+       {0x5c76, 0x75},
+       {0x5c77, 0x75},
+       {0x5c78, 0x75},
+       {0x5c79, 0x75},
+       {0x5c7a, 0x75},
+       {0x5c7b, 0x75},
+       {0x5c7c, 0x75},
+       {0x5c7d, 0x75},
+       {0x5c7e, 0x75},
+       {0x5c7f, 0x75},
+       {0x5c80, 0x75},
+       {0x5c81, 0x75},
+       {0x5c82, 0x75},
+       {0x5c83, 0x75},
+       {0x5c84, 0x75},
+       {0x5c85, 0x75},
+       {0x5c86, 0x75},
+       {0x5c87, 0x75},
+       {0x5c88, 0x75},
+       {0x5c89, 0x75},
+       {0x5c8a, 0x75},
+       {0x5c8b, 0x75},
+       {0x5c8c, 0x75},
+       {0x5c8d, 0x75},
+       {0x5c8e, 0x75},
+       {0x5c8f, 0x75},
+       {0x5c90, 0x75},
+       {0x5c91, 0x75},
+       {0x5c92, 0x75},
+       {0x5c93, 0x75},
+       {0x5c94, 0x75},
+       {0x5c95, 0x75},
+       {0x5c96, 0x75},
+       {0x5c97, 0x75},
+       {0x5c98, 0x75},
+       {0x5c99, 0x75},
+       {0x5c9a, 0x75},
+       {0x5c9b, 0x75},
+       {0x5c9c, 0x75},
+       {0x5c9d, 0x75},
+       {0x5c9e, 0x75},
+       {0x5c9f, 0x75},
+       {0x5ca0, 0x75},
+       {0x5ca1, 0x75},
+       {0x5ca2, 0x75},
+       {0x5ca3, 0x75},
+       {0x5ca4, 0x75},
+       {0x5ca5, 0x75},
+       {0x5ca6, 0x75},
+       {0x5ca7, 0x75},
+       {0x5ca8, 0x75},
+       {0x5ca9, 0x75},
+       {0x5caa, 0x75},
+       {0x5cab, 0x75},
+       {0x5cac, 0x75},
+       {0x5cad, 0x75},
+       {0x5cae, 0x75},
+       {0x5caf, 0x75},
+       {0x5cb0, 0x75},
+       {0x5cb1, 0x75},
+       {0x5cb2, 0x75},
+       {0x5cb3, 0x75},
+       {0x5cb4, 0x75},
+       {0x5cb5, 0x75},
+       {0x5cb6, 0x75},
+       {0x5cb7, 0x75},
+       {0x5cb8, 0x75},
+       {0x5cb9, 0x75},
+       {0x5cba, 0x75},
+       {0x5cbb, 0x75},
+       {0x5cbc, 0x75},
+       {0x5cbd, 0x75},
+       {0x5cbe, 0x75},
+       {0x5cbf, 0x75},
+       {0x5cc0, 0x75},
+       {0x5cc1, 0x75},
+       {0x5cc2, 0x75},
+       {0x5cc3, 0x75},
+       {0x5cc4, 0x75},
+       {0x5cc5, 0x75},
+       {0x5cc6, 0x75},
+       {0x5cc7, 0x75},
+       {0x5cc8, 0x75},
+       {0x5cc9, 0x75},
+       {0x5cca, 0x75},
+       {0x5ccb, 0x75},
+       {0x5ccc, 0x75},
+       {0x5ccd, 0x75},
+       {0x5cce, 0x75},
+       {0x5ccf, 0x75},
+       {0x5cd0, 0x75},
+       {0x5cd1, 0x75},
+       {0x5cd2, 0x75},
+       {0x5cd3, 0x75},
+       {0x5cd4, 0x75},
+       {0x5cd5, 0x75},
+       {0x5cd6, 0x75},
+       {0x5cd7, 0x75},
+       {0x5cd8, 0x75},
+       {0x5cd9, 0x75},
+       {0x5cda, 0x75},
+       {0x5cdb, 0x75},
+       {0x5cdc, 0x75},
+       {0x5cdd, 0x75},
+       {0x5cde, 0x75},
+       {0x5cdf, 0x75},
+       {0x5ce0, 0x75},
+       {0x5ce1, 0x75},
+       {0x5ce2, 0x75},
+       {0x5ce3, 0x75},
+       {0x5ce4, 0x75},
+       {0x5ce5, 0x75},
+       {0x5ce6, 0x75},
+       {0x5ce7, 0x75},
+       {0x5ce8, 0x75},
+       {0x5ce9, 0x75},
+       {0x5cea, 0x75},
+       {0x5ceb, 0x75},
+       {0x5cec, 0x75},
+       {0x5ced, 0x75},
+       {0x5cee, 0x75},
+       {0x5cef, 0x75},
+       {0x5cf0, 0x75},
+       {0x5cf1, 0x75},
+       {0x5cf2, 0x75},
+       {0x5cf3, 0x75},
+       {0x5cf4, 0x75},
+       {0x5cf5, 0x75},
+       {0x5cf6, 0x75},
+       {0x5cf7, 0x75},
+       {0x5cf8, 0x75},
+       {0x5cf9, 0x75},
+       {0x5cfa, 0x75},
+       {0x5cfb, 0x75},
+       {0x5cfc, 0x75},
+       {0x5cfd, 0x75},
+       {0x5cfe, 0x75},
+       {0x5cff, 0x75},
+       {0x5d00, 0x75},
+       {0x5d01, 0x75},
+       {0x5d02, 0x75},
+       {0x5d03, 0x75},
+       {0x5d04, 0x75},
+       {0x5d05, 0x75},
+       {0x5d06, 0x75},
+       {0x5d07, 0x75},
+       {0x5d08, 0x75},
+       {0x5d09, 0x75},
+       {0x5d0a, 0x75},
+       {0x5d0b, 0x75},
+       {0x5d0c, 0x75},
+       {0x5d0d, 0x75},
+       {0x5d0e, 0x75},
+       {0x5d0f, 0x75},
+       {0x5d10, 0x75},
+       {0x5d11, 0x75},
+       {0x5d12, 0x75},
+       {0x5d13, 0x75},
+       {0x5d14, 0x75},
+       {0x5d15, 0x75},
+       {0x5d16, 0x75},
+       {0x5d17, 0x75},
+       {0x5d18, 0x75},
+       {0x5d19, 0x75},
+       {0x5d1a, 0x75},
+       {0x5d1b, 0x75},
+       {0x5d1c, 0x75},
+       {0x5d1d, 0x75},
+       {0x5d1e, 0x75},
+       {0x5d1f, 0x75},
+       {0x5d20, 0x75},
+       {0x5d21, 0x75},
+       {0x5d22, 0x75},
+       {0x5d23, 0x75},
+       {0x5d24, 0x75},
+       {0x5d25, 0x75},
+       {0x5d26, 0x75},
+       {0x5d27, 0x75},
+       {0x5d28, 0x75},
+       {0x5d29, 0x75},
+       {0x5d2a, 0x75},
+       {0x5d2b, 0x75},
+       {0x5d2c, 0x75},
+       {0x5d2d, 0x75},
+       {0x5d2e, 0x75},
+       {0x5d2f, 0x75},
+       {0x5d30, 0x75},
+       {0x5d31, 0x75},
+       {0x5d32, 0x75},
+       {0x5d33, 0x75},
+       {0x5d34, 0x75},
+       {0x5d35, 0x75},
+       {0x5d36, 0x75},
+       {0x5d37, 0x75},
+       {0x5d38, 0x75},
+       {0x5d39, 0x75},
+       {0x5d3a, 0x75},
+       {0x5d3b, 0x75},
+       {0x5d3c, 0x75},
+       {0x5d3d, 0x75},
+       {0x5d3e, 0x75},
+       {0x5d3f, 0x75},
+       {0x5d40, 0x75},
+       {0x5d41, 0x75},
+       {0x5d42, 0x75},
+       {0x5d43, 0x75},
+       {0x5d44, 0x75},
+       {0x5d45, 0x75},
+       {0x5d46, 0x75},
+       {0x5d47, 0x75},
+       {0x5d48, 0x75},
+       {0x5d49, 0x75},
+       {0x5d4a, 0x75},
+       {0x5d4b, 0x75},
+       {0x5d4c, 0x75},
+       {0x5d4d, 0x75},
+       {0x5d4e, 0x75},
+       {0x5d4f, 0x75},
+       {0x5d50, 0x75},
+       {0x5d51, 0x75},
+       {0x5d52, 0x75},
+       {0x5d53, 0x75},
+       {0x5d54, 0x75},
+       {0x5d55, 0x75},
+       {0x5d56, 0x75},
+       {0x5d57, 0x75},
+       {0x5d58, 0x75},
+       {0x5d59, 0x75},
+       {0x5d5a, 0x75},
+       {0x5d5b, 0x75},
+       {0x5d5c, 0x75},
+       {0x5d5d, 0x75},
+       {0x5d5e, 0x75},
+       {0x5d5f, 0x75},
+       {0x5d60, 0x75},
+       {0x5d61, 0x75},
+       {0x5d62, 0x75},
+       {0x5d63, 0x75},
+       {0x5d64, 0x75},
+       {0x5d65, 0x75},
+       {0x5d66, 0x75},
+       {0x5d67, 0x75},
+       {0x5d68, 0x75},
+       {0x5d69, 0x75},
+       {0x5d6a, 0x75},
+       {0x5d6b, 0x75},
+       {0x5d6c, 0x75},
+       {0x5d6d, 0x75},
+       {0x5d6e, 0x75},
+       {0x5d6f, 0x75},
+       {0x5d70, 0x75},
+       {0x5d71, 0x75},
+       {0x5d72, 0x75},
+       {0x5d73, 0x75},
+       {0x5d74, 0x75},
+       {0x5d75, 0x75},
+       {0x5d76, 0x75},
+       {0x5d77, 0x75},
+       {0x5d78, 0x75},
+       {0x5d79, 0x75},
+       {0x5d7a, 0x75},
+       {0x5d7b, 0x75},
+       {0x5d7c, 0x75},
+       {0x5d7d, 0x75},
+       {0x5d7e, 0x75},
+       {0x5d7f, 0x75},
+       {0x5d80, 0x75},
+       {0x5d81, 0x75},
+       {0x5d82, 0x75},
+       {0x5d83, 0x75},
+       {0x5d84, 0x75},
+       {0x5d85, 0x75},
+       {0x5d86, 0x75},
+       {0x5d87, 0x75},
+       {0x5d88, 0x75},
+       {0x5d89, 0x75},
+       {0x5d8a, 0x75},
+       {0x5d8b, 0x75},
+       {0x5d8c, 0x75},
+       {0x5d8d, 0x75},
+       {0x5d8e, 0x75},
+       {0x5d8f, 0x75},
+       {0x5d90, 0x75},
+       {0x5d91, 0x75},
+       {0x5d92, 0x75},
+       {0x5d93, 0x75},
+       {0x5d94, 0x75},
+       {0x5d95, 0x75},
+       {0x5d96, 0x75},
+       {0x5d97, 0x75},
+       {0x5d98, 0x75},
+       {0x5d99, 0x75},
+       {0x5d9a, 0x75},
+       {0x5d9b, 0x75},
+       {0x5d9c, 0x75},
+       {0x5d9d, 0x75},
+       {0x5d9e, 0x75},
+       {0x5d9f, 0x75},
+       {0x5da0, 0x75},
+       {0x5da1, 0x75},
+       {0x5da2, 0x75},
+       {0x5da3, 0x75},
+       {0x5da4, 0x75},
+       {0x5da5, 0x75},
+       {0x5da6, 0x75},
+       {0x5da7, 0x75},
+       {0x5da8, 0x75},
+       {0x5da9, 0x75},
+       {0x5daa, 0x75},
+       {0x5dab, 0x75},
+       {0x5dac, 0x75},
+       {0x5dad, 0x75},
+       {0x5dae, 0x75},
+       {0x5daf, 0x75},
+       {0x5db0, 0x75},
+       {0x5db1, 0x75},
+       {0x5db2, 0x75},
+       {0x5db3, 0x75},
+       {0x5db4, 0x75},
+       {0x5db5, 0x75},
+       {0x5db6, 0x75},
+       {0x5db7, 0x75},
+       {0x5db8, 0x75},
+       {0x5db9, 0x75},
+       {0x5dba, 0x75},
+       {0x5dbb, 0x75},
+       {0x5dbc, 0x75},
+       {0x5dbd, 0x75},
+       {0x5dbe, 0x75},
+       {0x5dbf, 0x75},
+       {0x5dc0, 0x75},
+       {0x5dc1, 0x75},
+       {0x5dc2, 0x75},
+       {0x5dc3, 0x75},
+       {0x5dc4, 0x75},
+       {0x5dc5, 0x75},
+       {0x5dc6, 0x75},
+       {0x5dc7, 0x75},
+       {0x5dc8, 0x75},
+       {0x5dc9, 0x75},
+       {0x5dca, 0x75},
+       {0x5dcb, 0x75},
+       {0x5dcc, 0x75},
+       {0x5dcd, 0x75},
+       {0x5dce, 0x75},
+       {0x5dcf, 0x75},
+       {0x5dd0, 0x75},
+       {0x5dd1, 0x75},
+       {0x5dd2, 0x75},
+       {0x5dd3, 0x75},
+       {0x5dd4, 0x75},
+       {0x5dd5, 0x75},
+       {0x5dd6, 0x75},
+       {0x5dd7, 0x75},
+       {0x5dd8, 0x75},
+       {0x5dd9, 0x75},
+       {0x5dda, 0x75},
+       {0x5ddb, 0x75},
+       {0x5ddc, 0x75},
+       {0x5ddd, 0x75},
+       {0x5dde, 0x75},
+       {0x5ddf, 0x75},
+       {0x5de0, 0x75},
+       {0x5de1, 0x75},
+       {0x5de2, 0x75},
+       {0x5de3, 0x75},
+       {0x5de4, 0x75},
+       {0x5de5, 0x75},
+       {0x5de6, 0x75},
+       {0x5de7, 0x75},
+       {0x5de8, 0x75},
+       {0x5de9, 0x75},
+       {0x5dea, 0x75},
+       {0x5deb, 0x75},
+       {0x5dec, 0x75},
+       {0x5ded, 0x75},
+       {0x5dee, 0x75},
+       {0x5def, 0x75},
+       {0x5df0, 0x75},
+       {0x5df1, 0x75},
+       {0x5df2, 0x75},
+       {0x5df3, 0x75},
+       {0x5df4, 0x75},
+       {0x5df5, 0x75},
+       {0x5df6, 0x75},
+       {0x5df7, 0x75},
+       {0x5df8, 0x75},
+       {0x5df9, 0x75},
+       {0x5dfa, 0x75},
+       {0x5dfb, 0x75},
+       {0x5dfc, 0x75},
+       {0x5dfd, 0x75},
+       {0x5dfe, 0x75},
+       {0x5dff, 0x75},
+       {0x5e00, 0x75},
+       {0x5e01, 0x75},
+       {0x5e02, 0x75},
+       {0x5e03, 0x75},
+       {0x5e04, 0x75},
+       {0x5e05, 0x75},
+       {0x5e06, 0x75},
+       {0x5e07, 0x75},
+       {0x5e08, 0x75},
+       {0x5e09, 0x75},
+       {0x5e0a, 0x75},
+       {0x5e0b, 0x75},
+       {0x5e0c, 0x75},
+       {0x5e0d, 0x75},
+       {0x5e0e, 0x75},
+       {0x5e0f, 0x75},
+       {0x5e10, 0x75},
+       {0x5e11, 0x75},
+       {0x5e12, 0x75},
+       {0x5e13, 0x75},
+       {0x5e14, 0x75},
+       {0x5e15, 0x75},
+       {0x5e16, 0x75},
+       {0x5e17, 0x75},
+       {0x5e18, 0x75},
+       {0x5e19, 0x75},
+       {0x5e1a, 0x75},
+       {0x5e1b, 0x75},
+       {0x5e1c, 0x75},
+       {0x5e1d, 0x75},
+       {0x5e1e, 0x75},
+       {0x5e1f, 0x75},
+       {0x5e20, 0x75},
+       {0x5e21, 0x75},
+       {0x5e22, 0x75},
+       {0x5e23, 0x75},
+       {0x5e24, 0x75},
+       {0x5e25, 0x75},
+       {0x5e26, 0x75},
+       {0x5e27, 0x75},
+       {0x5e28, 0x75},
+       {0x5e29, 0x75},
+       {0x5e2a, 0x75},
+       {0x5e2b, 0x75},
+       {0x5e2c, 0x75},
+       {0x5e2d, 0x75},
+       {0x5e2e, 0x75},
+       {0x5e2f, 0x75},
+       {0x5e30, 0x75},
+       {0x5e31, 0x75},
+       {0x5e32, 0x75},
+       {0x5e33, 0x75},
+       {0x5e34, 0x75},
+       {0x5e35, 0x75},
+       {0x5e36, 0x75},
+       {0x5e37, 0x75},
+       {0x5e38, 0x75},
+       {0x5e39, 0x75},
+       {0x5e3a, 0x75},
+       {0x5e3b, 0x75},
+       {0x5e3c, 0x75},
+       {0x5e3d, 0x75},
+       {0x5e3e, 0x75},
+       {0x5e3f, 0x75},
+       {0x5e40, 0x75},
+       {0x5e41, 0x75},
+       {0x5e42, 0x75},
+       {0x5e43, 0x75},
+       {0x5e44, 0x75},
+       {0x5e45, 0x75},
+       {0x5e46, 0x75},
+       {0x5e47, 0x75},
+       {0x5e48, 0x75},
+       {0x5e49, 0x75},
+       {0x5e4a, 0x75},
+       {0x5e4b, 0x75},
+       {0x5e4c, 0x75},
+       {0x5e4d, 0x75},
+       {0x5e4e, 0x75},
+       {0x5e4f, 0x75},
+       {0x5e50, 0x75},
+       {0x5e51, 0x75},
+       {0x5e52, 0x75},
+       {0x5e53, 0x75},
+       {0x5e54, 0x75},
+       {0x5e55, 0x75},
+       {0x5e56, 0x75},
+       {0x5e57, 0x75},
+       {0x5e58, 0x75},
+       {0x5e59, 0x75},
+       {0x5e5a, 0x75},
+       {0x5e5b, 0x75},
+       {0x5e5c, 0x75},
+       {0x5e5d, 0x75},
+       {0x5e5e, 0x75},
+       {0x5e5f, 0x75},
+       {0x5e60, 0x75},
+       {0x5e61, 0x75},
+       {0x5e62, 0x75},
+       {0x5e63, 0x75},
+       {0x5e64, 0x75},
+       {0x5e65, 0x75},
+       {0x5e66, 0x75},
+       {0x5e67, 0x75},
+       {0x5e68, 0x75},
+       {0x5e69, 0x75},
+       {0x5e6a, 0x75},
+       {0x5e6b, 0x75},
+       {0x5e6c, 0x75},
+       {0x5e6d, 0x75},
+       {0x5e6e, 0x75},
+       {0x5e6f, 0x75},
+       {0x5e70, 0x75},
+       {0x5e71, 0x75},
+       {0x5e72, 0x75},
+       {0x5e73, 0x75},
+       {0x5e74, 0x75},
+       {0x5e75, 0x75},
+       {0x5e76, 0x75},
+       {0x5e77, 0x75},
+       {0x5e78, 0x75},
+       {0x5e79, 0x75},
+       {0x5e7a, 0x75},
+       {0x5e7b, 0x75},
+       {0x5e7c, 0x75},
+       {0x5e7d, 0x75},
+       {0x5e7e, 0x75},
+       {0x5e7f, 0x75},
+       {0x5e80, 0x75},
+       {0x5e81, 0x75},
+       {0x5e82, 0x75},
+       {0x5e83, 0x75},
+       {0x5e84, 0x75},
+       {0x5e85, 0x75},
+       {0x5e86, 0x75},
+       {0x5e87, 0x75},
+       {0x5e88, 0x75},
+       {0x5e89, 0x75},
+       {0x5e8a, 0x75},
+       {0x5e8b, 0x75},
+       {0x5e8c, 0x75},
+       {0x5e8d, 0x75},
+       {0x5e8e, 0x75},
+       {0x5e8f, 0x75},
+       {0x5e90, 0x75},
+       {0x5e91, 0x75},
+       {0x5e92, 0x75},
+       {0x5e93, 0x75},
+       {0x5e94, 0x75},
+       {0x5e95, 0x75},
+       {0x5e96, 0x75},
+       {0x5e97, 0x75},
+       {0x5e98, 0x75},
+       {0x5e99, 0x75},
+       {0x5e9a, 0x75},
+       {0x5e9b, 0x75},
+       {0x5e9c, 0x75},
+       {0x5e9d, 0x75},
+       {0x5e9e, 0x75},
+       {0x5e9f, 0x75},
+       {0x5ea0, 0x75},
+       {0x5ea1, 0x75},
+       {0x5ea2, 0x75},
+       {0x5ea3, 0x75},
+       {0x5ea4, 0x75},
+       {0x5ea5, 0x75},
+       {0x5ea6, 0x75},
+       {0x5ea7, 0x75},
+       {0x5ea8, 0x75},
+       {0x5ea9, 0x75},
+       {0x5eaa, 0x75},
+       {0x5eab, 0x75},
+       {0x5eac, 0x75},
+       {0x5ead, 0x75},
+       {0x5eae, 0x75},
+       {0x5eaf, 0x75},
+       {0x5eb0, 0x75},
+       {0x5eb1, 0x75},
+       {0x5eb2, 0x75},
+       {0x5eb3, 0x75},
+       {0x5eb4, 0x75},
+       {0x5eb5, 0x75},
+       {0x5eb6, 0x75},
+       {0x5eb7, 0x75},
+       {0x5eb8, 0x75},
+       {0x5eb9, 0x75},
+       {0x5eba, 0x75},
+       {0x5ebb, 0x75},
+       {0x5ebc, 0x75},
+       {0x5ebd, 0x75},
+       {0x5ebe, 0x75},
+       {0x5ebf, 0x75},
+       {0x5ec0, 0x75},
+       {0x5ec1, 0x75},
+       {0x5ec2, 0x75},
+       {0x5ec3, 0x75},
+       {0x5ec4, 0x75},
+       {0x5ec5, 0x75},
+       {0x5ec6, 0x75},
+       {0x5ec7, 0x75},
+       {0x5ec8, 0x75},
+       {0x5ec9, 0x75},
+       {0x5eca, 0x75},
+       {0x5ecb, 0x75},
+       {0x5ecc, 0x75},
+       {0x5ecd, 0x75},
+       {0x5ece, 0x75},
+       {0x5ecf, 0x75},
+       {0x5ed0, 0x75},
+       {0x5ed1, 0x75},
+       {0x5ed2, 0x75},
+       {0x5ed3, 0x75},
+       {0x5ed4, 0x75},
+       {0x5ed5, 0x75},
+       {0x5ed6, 0x75},
+       {0x5ed7, 0x75},
+       {0x5ed8, 0x75},
+       {0x5ed9, 0x75},
+       {0x5eda, 0x75},
+       {0x5edb, 0x75},
+       {0x5edc, 0x75},
+       {0x5edd, 0x75},
+       {0x5ede, 0x75},
+       {0x5edf, 0x75},
+       {0x5ee0, 0x75},
+       {0x5ee1, 0x75},
+       {0x5ee2, 0x75},
+       {0x5ee3, 0x75},
+       {0x5ee4, 0x75},
+       {0x5ee5, 0x75},
+       {0x5ee6, 0x75},
+       {0x5ee7, 0x75},
+       {0x5ee8, 0x75},
+       {0x5ee9, 0x75},
+       {0x5eea, 0x75},
+       {0x5eeb, 0x75},
+       {0x5eec, 0x75},
+       {0x5eed, 0x75},
+       {0x5eee, 0x75},
+       {0x5eef, 0x75},
+       {0x5ef0, 0x75},
+       {0x5ef1, 0x75},
+       {0x5ef2, 0x75},
+       {0x5ef3, 0x75},
+       {0x5ef4, 0x75},
+       {0x5ef5, 0x75},
+       {0x5ef6, 0x75},
+       {0x5ef7, 0x75},
+       {0x5ef8, 0x75},
+       {0x5ef9, 0x75},
+       {0x5efa, 0x75},
+       {0x5efb, 0x75},
+       {0x5efc, 0x75},
+       {0x5efd, 0x75},
+       {0x5efe, 0x75},
+       {0x5eff, 0x75},
+       {0x5f00, 0x75},
+       {0x5f01, 0x75},
+       {0x5f02, 0x75},
+       {0x5f03, 0x75},
+       {0x5f04, 0x75},
+       {0x5f05, 0x75},
+       {0x5f06, 0x75},
+       {0x5f07, 0x75},
+       {0x5f08, 0x75},
+       {0x5f09, 0x75},
+       {0x5f0a, 0x75},
+       {0x5f0b, 0x75},
+       {0x5f0c, 0x75},
+       {0x5f0d, 0x75},
+       {0x5f0e, 0x75},
+       {0x5f0f, 0x75},
+       {0x5f10, 0x75},
+       {0x5f11, 0x75},
+       {0x5f12, 0x75},
+       {0x5f13, 0x75},
+       {0x5f14, 0x75},
+       {0x5f15, 0x75},
+       {0x5f16, 0x75},
+       {0x5f17, 0x75},
+       {0x5f18, 0x75},
+       {0x5f19, 0x75},
+       {0x5f1a, 0x75},
+       {0x5f1b, 0x75},
+       {0x5f1c, 0x75},
+       {0x5f1d, 0x75},
+       {0x5f1e, 0x75},
+       {0x5f1f, 0x75},
+};
+
+static const struct ov08x40_reg mode_1928x1208_regs[] = {
+       {0x5000, 0x55},
+       {0x5001, 0x00},
+       {0x5008, 0xb0},
+       {0x50c1, 0x00},
+       {0x53c1, 0x00},
+       {0x5f40, 0x00},
+       {0x5f41, 0x40},
+       {0x0300, 0x3a},
+       {0x0301, 0xc8},
+       {0x0302, 0x31},
+       {0x0303, 0x03},
+       {0x0304, 0x01},
+       {0x0305, 0xa1},
+       {0x0306, 0x04},
+       {0x0307, 0x01},
+       {0x0308, 0x03},
+       {0x0309, 0x03},
+       {0x0310, 0x0a},
+       {0x0311, 0x02},
+       {0x0312, 0x01},
+       {0x0313, 0x08},
+       {0x0314, 0x66},
+       {0x0315, 0x00},
+       {0x0316, 0x34},
+       {0x0320, 0x02},
+       {0x0321, 0x03},
+       {0x0323, 0x05},
+       {0x0324, 0x01},
+       {0x0325, 0xb8},
+       {0x0326, 0x4a},
+       {0x0327, 0x04},
+       {0x0329, 0x00},
+       {0x032a, 0x05},
+       {0x032b, 0x00},
+       {0x032c, 0x00},
+       {0x032d, 0x00},
+       {0x032e, 0x02},
+       {0x032f, 0xa0},
+       {0x0350, 0x00},
+       {0x0360, 0x01},
+       {0x1216, 0x60},
+       {0x1217, 0x5b},
+       {0x1218, 0x00},
+       {0x1220, 0x24},
+       {0x198a, 0x00},
+       {0x198b, 0x01},
+       {0x198e, 0x00},
+       {0x198f, 0x01},
+       {0x3009, 0x04},
+       {0x3012, 0x41},
+       {0x3015, 0x00},
+       {0x3016, 0xb0},
+       {0x3017, 0xf0},
+       {0x3018, 0xf0},
+       {0x3019, 0xd2},
+       {0x301a, 0xb0},
+       {0x301c, 0x81},
+       {0x301d, 0x02},
+       {0x301e, 0x80},
+       {0x3022, 0xf0},
+       {0x3025, 0x89},
+       {0x3030, 0x03},
+       {0x3044, 0xc2},
+       {0x3050, 0x35},
+       {0x3051, 0x60},
+       {0x3052, 0x25},
+       {0x3053, 0x00},
+       {0x3054, 0x00},
+       {0x3055, 0x02},
+       {0x3056, 0x80},
+       {0x3057, 0x80},
+       {0x3058, 0x80},
+       {0x3059, 0x00},
+       {0x3107, 0x86},
+       {0x3400, 0x1c},
+       {0x3401, 0x80},
+       {0x3402, 0x8c},
+       {0x3419, 0x08},
+       {0x341a, 0xaf},
+       {0x341b, 0x30},
+       {0x3420, 0x00},
+       {0x3421, 0x00},
+       {0x3422, 0x00},
+       {0x3423, 0x00},
+       {0x3424, 0x00},
+       {0x3425, 0x00},
+       {0x3426, 0x00},
+       {0x3427, 0x00},
+       {0x3428, 0x0f},
+       {0x3429, 0x00},
+       {0x342a, 0x00},
+       {0x342b, 0x00},
+       {0x342c, 0x00},
+       {0x342d, 0x00},
+       {0x342e, 0x00},
+       {0x342f, 0x11},
+       {0x3430, 0x11},
+       {0x3431, 0x10},
+       {0x3432, 0x00},
+       {0x3433, 0x00},
+       {0x3434, 0x00},
+       {0x3435, 0x00},
+       {0x3436, 0x00},
+       {0x3437, 0x00},
+       {0x3442, 0x02},
+       {0x3443, 0x02},
+       {0x3444, 0x07},
+       {0x3450, 0x00},
+       {0x3451, 0x00},
+       {0x3452, 0x18},
+       {0x3453, 0x18},
+       {0x3454, 0x00},
+       {0x3455, 0x80},
+       {0x3456, 0x08},
+       {0x3500, 0x00},
+       {0x3501, 0x02},
+       {0x3502, 0x00},
+       {0x3504, 0x4c},
+       {0x3506, 0x30},
+       {0x3507, 0x00},
+       {0x3508, 0x01},
+       {0x3509, 0x00},
+       {0x350a, 0x01},
+       {0x350b, 0x00},
+       {0x350c, 0x00},
+       {0x3540, 0x00},
+       {0x3541, 0x01},
+       {0x3542, 0x00},
+       {0x3544, 0x4c},
+       {0x3546, 0x30},
+       {0x3547, 0x00},
+       {0x3548, 0x01},
+       {0x3549, 0x00},
+       {0x354a, 0x01},
+       {0x354b, 0x00},
+       {0x354c, 0x00},
+       {0x3688, 0x02},
+       {0x368a, 0x2e},
+       {0x368e, 0x71},
+       {0x3696, 0xd1},
+       {0x3699, 0x00},
+       {0x369a, 0x00},
+       {0x36a4, 0x00},
+       {0x36a6, 0x00},
+       {0x3711, 0x00},
+       {0x3712, 0x50},
+       {0x3713, 0x00},
+       {0x3714, 0x21},
+       {0x3716, 0x00},
+       {0x3718, 0x07},
+       {0x371a, 0x1c},
+       {0x371b, 0x00},
+       {0x3720, 0x08},
+       {0x3725, 0x32},
+       {0x3727, 0x05},
+       {0x3760, 0x02},
+       {0x3761, 0x28},
+       {0x3762, 0x02},
+       {0x3763, 0x02},
+       {0x3764, 0x02},
+       {0x3765, 0x2c},
+       {0x3766, 0x04},
+       {0x3767, 0x2c},
+       {0x3768, 0x02},
+       {0x3769, 0x00},
+       {0x376b, 0x20},
+       {0x376e, 0x07},
+       {0x37b0, 0x01},
+       {0x37b1, 0x0f},
+       {0x37b2, 0x01},
+       {0x37b3, 0xd6},
+       {0x37b4, 0x01},
+       {0x37b5, 0x48},
+       {0x37b6, 0x02},
+       {0x37b7, 0x40},
+       {0x3800, 0x00},
+       {0x3801, 0x00},
+       {0x3802, 0x00},
+       {0x3803, 0x00},
+       {0x3804, 0x0f},
+       {0x3805, 0x1f},
+       {0x3806, 0x09},
+       {0x3807, 0x7f},
+       {0x3808, 0x07},
+       {0x3809, 0x88},
+       {0x380a, 0x04},
+       {0x380b, 0xb8},
+       {0x380c, 0x02},
+       {0x380d, 0xd0},
+       {0x380e, 0x11},
+       {0x380f, 0x5c},
+       {0x3810, 0x00},
+       {0x3811, 0x04},
+       {0x3812, 0x00},
+       {0x3813, 0x03},
+       {0x3814, 0x11},
+       {0x3815, 0x11},
+       {0x3820, 0x02},
+       {0x3821, 0x14},
+       {0x3822, 0x00},
+       {0x3823, 0x04},
+       {0x3828, 0x0f},
+       {0x382a, 0x80},
+       {0x382e, 0x41},
+       {0x3837, 0x08},
+       {0x383a, 0x81},
+       {0x383b, 0x81},
+       {0x383c, 0x11},
+       {0x383d, 0x11},
+       {0x383e, 0x00},
+       {0x383f, 0x38},
+       {0x3840, 0x00},
+       {0x3847, 0x00},
+       {0x384a, 0x00},
+       {0x384c, 0x02},
+       {0x384d, 0xd0},
+       {0x3856, 0x50},
+       {0x3857, 0x30},
+       {0x3858, 0x80},
+       {0x3859, 0x40},
+       {0x3860, 0x00},
+       {0x3888, 0x00},
+       {0x3889, 0x00},
+       {0x388a, 0x00},
+       {0x388b, 0x00},
+       {0x388c, 0x00},
+       {0x388d, 0x00},
+       {0x388e, 0x00},
+       {0x388f, 0x00},
+       {0x3894, 0x00},
+       {0x3895, 0x00},
+       {0x3c84, 0x00},
+       {0x3d85, 0x8b},
+       {0x3daa, 0x80},
+       {0x3dab, 0x14},
+       {0x3dac, 0x80},
+       {0x3dad, 0xc8},
+       {0x3dae, 0x81},
+       {0x3daf, 0x7b},
+       {0x3f00, 0x10},
+       {0x3f01, 0x11},
+       {0x3f06, 0x0d},
+       {0x3f07, 0x0b},
+       {0x3f08, 0x0d},
+       {0x3f09, 0x0b},
+       {0x3f0a, 0x01},
+       {0x3f0b, 0x11},
+       {0x3f0c, 0x33},
+       {0x4001, 0x07},
+       {0x4007, 0x20},
+       {0x4008, 0x00},
+       {0x4009, 0x05},
+       {0x400a, 0x00},
+       {0x400b, 0x04},
+       {0x400c, 0x00},
+       {0x400d, 0x04},
+       {0x400e, 0x14},
+       {0x4010, 0xf4},
+       {0x4011, 0x03},
+       {0x4012, 0x55},
+       {0x4015, 0x00},
+       {0x4016, 0x27},
+       {0x4017, 0x00},
+       {0x4018, 0x0f},
+       {0x401b, 0x08},
+       {0x401c, 0x00},
+       {0x401d, 0x10},
+       {0x401e, 0x02},
+       {0x401f, 0x00},
+       {0x4050, 0x06},
+       {0x4051, 0xff},
+       {0x4052, 0xff},
+       {0x4053, 0xff},
+       {0x4054, 0xff},
+       {0x4055, 0xff},
+       {0x4056, 0xff},
+       {0x4057, 0x7f},
+       {0x4058, 0x00},
+       {0x4059, 0x00},
+       {0x405a, 0x00},
+       {0x405b, 0x00},
+       {0x405c, 0x07},
+       {0x405d, 0xff},
+       {0x405e, 0x07},
+       {0x405f, 0xff},
+       {0x4080, 0x78},
+       {0x4081, 0x78},
+       {0x4082, 0x78},
+       {0x4083, 0x78},
+       {0x4019, 0x00},
+       {0x401a, 0x40},
+       {0x4020, 0x04},
+       {0x4021, 0x00},
+       {0x4022, 0x04},
+       {0x4023, 0x00},
+       {0x4024, 0x04},
+       {0x4025, 0x00},
+       {0x4026, 0x04},
+       {0x4027, 0x00},
+       {0x4030, 0x00},
+       {0x4031, 0x00},
+       {0x4032, 0x00},
+       {0x4033, 0x00},
+       {0x4034, 0x00},
+       {0x4035, 0x00},
+       {0x4036, 0x00},
+       {0x4037, 0x00},
+       {0x4040, 0x00},
+       {0x4041, 0x80},
+       {0x4042, 0x00},
+       {0x4043, 0x80},
+       {0x4044, 0x00},
+       {0x4045, 0x80},
+       {0x4046, 0x00},
+       {0x4047, 0x80},
+       {0x4060, 0x00},
+       {0x4061, 0x00},
+       {0x4062, 0x00},
+       {0x4063, 0x00},
+       {0x4064, 0x00},
+       {0x4065, 0x00},
+       {0x4066, 0x00},
+       {0x4067, 0x00},
+       {0x4068, 0x00},
+       {0x4069, 0x00},
+       {0x406a, 0x00},
+       {0x406b, 0x00},
+       {0x406c, 0x00},
+       {0x406d, 0x00},
+       {0x406e, 0x00},
+       {0x406f, 0x00},
+       {0x4070, 0x00},
+       {0x4071, 0x00},
+       {0x4072, 0x00},
+       {0x4073, 0x00},
+       {0x4074, 0x00},
+       {0x4075, 0x00},
+       {0x4076, 0x00},
+       {0x4077, 0x00},
+       {0x4078, 0x00},
+       {0x4079, 0x00},
+       {0x407a, 0x00},
+       {0x407b, 0x00},
+       {0x407c, 0x00},
+       {0x407d, 0x00},
+       {0x407e, 0x00},
+       {0x407f, 0x00},
+       {0x40e0, 0x00},
+       {0x40e1, 0x00},
+       {0x40e2, 0x00},
+       {0x40e3, 0x00},
+       {0x40e4, 0x00},
+       {0x40e5, 0x00},
+       {0x40e6, 0x00},
+       {0x40e7, 0x00},
+       {0x40e8, 0x00},
+       {0x40e9, 0x80},
+       {0x40ea, 0x00},
+       {0x40eb, 0x80},
+       {0x40ec, 0x00},
+       {0x40ed, 0x80},
+       {0x40ee, 0x00},
+       {0x40ef, 0x80},
+       {0x40f0, 0x02},
+       {0x40f1, 0x04},
+       {0x4300, 0x00},
+       {0x4301, 0x00},
+       {0x4302, 0x00},
+       {0x4303, 0x00},
+       {0x4304, 0x00},
+       {0x4305, 0x00},
+       {0x4306, 0x00},
+       {0x4307, 0x00},
+       {0x4308, 0x00},
+       {0x4309, 0x00},
+       {0x430a, 0x00},
+       {0x430b, 0xff},
+       {0x430c, 0xff},
+       {0x430d, 0x00},
+       {0x430e, 0x00},
+       {0x4315, 0x00},
+       {0x4316, 0x00},
+       {0x4317, 0x00},
+       {0x4318, 0x00},
+       {0x4319, 0x00},
+       {0x431a, 0x00},
+       {0x431b, 0x00},
+       {0x431c, 0x00},
+       {0x4500, 0x07},
+       {0x4501, 0x10},
+       {0x4502, 0x00},
+       {0x4503, 0x0f},
+       {0x4504, 0x80},
+       {0x4506, 0x01},
+       {0x4509, 0x05},
+       {0x450c, 0x00},
+       {0x450d, 0x20},
+       {0x450e, 0x00},
+       {0x450f, 0x00},
+       {0x4510, 0x00},
+       {0x4523, 0x00},
+       {0x4526, 0x00},
+       {0x4542, 0x00},
+       {0x4543, 0x00},
+       {0x4544, 0x00},
+       {0x4545, 0x00},
+       {0x4546, 0x00},
+       {0x4547, 0x10},
+       {0x4602, 0x00},
+       {0x4603, 0x15},
+       {0x460b, 0x07},
+       {0x4680, 0x11},
+       {0x4686, 0x00},
+       {0x4687, 0x00},
+       {0x4700, 0x00},
+       {0x4800, 0x64},
+       {0x4806, 0x40},
+       {0x480b, 0x10},
+       {0x480c, 0x80},
+       {0x480f, 0x32},
+       {0x4813, 0xe4},
+       {0x4837, 0x14},
+       {0x4850, 0x42},
+       {0x4884, 0x04},
+       {0x4c00, 0xf8},
+       {0x4c01, 0x44},
+       {0x4c03, 0x00},
+       {0x4d00, 0x00},
+       {0x4d01, 0x16},
+       {0x4d04, 0x10},
+       {0x4d05, 0x00},
+       {0x4d06, 0x0c},
+       {0x4d07, 0x00},
+       {0x3d84, 0x04},
+       {0x3680, 0xa4},
+       {0x3682, 0x80},
+       {0x3601, 0x40},
+       {0x3602, 0x90},
+       {0x3608, 0x0a},
+       {0x3938, 0x09},
+       {0x3a74, 0x84},
+       {0x3a99, 0x84},
+       {0x3ab9, 0xa6},
+       {0x3aba, 0xba},
+       {0x3b12, 0x84},
+       {0x3b14, 0xbb},
+       {0x3b15, 0xbf},
+       {0x3a29, 0x26},
+       {0x3a1f, 0x8a},
+       {0x3a22, 0x91},
+       {0x3a25, 0x96},
+       {0x3a28, 0xb4},
+       {0x3a2b, 0xba},
+       {0x3a2e, 0xbf},
+       {0x3a31, 0xc1},
+       {0x3a20, 0x05},
+       {0x3939, 0x6b},
+       {0x3902, 0x10},
+       {0x3903, 0x10},
+       {0x3904, 0x10},
+       {0x3905, 0x10},
+       {0x3906, 0x01},
+       {0x3907, 0x0b},
+       {0x3908, 0x10},
+       {0x3909, 0x13},
+       {0x360f, 0x99},
+       {0x390b, 0x11},
+       {0x390c, 0x21},
+       {0x390d, 0x32},
+       {0x390e, 0x76},
+       {0x3911, 0x90},
+       {0x3913, 0x90},
+       {0x3b3f, 0x9d},
+       {0x3b45, 0x9d},
+       {0x3b1b, 0xc9},
+       {0x3b21, 0xc9},
+       {0x3a1a, 0x1c},
+       {0x3a23, 0x15},
+       {0x3a26, 0x17},
+       {0x3a2c, 0x50},
+       {0x3a2f, 0x18},
+       {0x3a32, 0x4f},
+       {0x3ace, 0x01},
+       {0x3ad2, 0x01},
+       {0x3ad6, 0x01},
+       {0x3ada, 0x01},
+       {0x3ade, 0x01},
+       {0x3ae2, 0x01},
+       {0x3aee, 0x01},
+       {0x3af2, 0x01},
+       {0x3af6, 0x01},
+       {0x3afa, 0x01},
+       {0x3afe, 0x01},
+       {0x3b02, 0x01},
+       {0x3b06, 0x01},
+       {0x3b0a, 0x01},
+       {0x3b0b, 0x00},
+       {0x3b0e, 0x01},
+       {0x3b0f, 0x00},
+       {0x392c, 0x02},
+       {0x392d, 0x01},
+       {0x392e, 0x04},
+       {0x392f, 0x03},
+       {0x3930, 0x09},
+       {0x3931, 0x07},
+       {0x3932, 0x10},
+       {0x3933, 0x0d},
+       {0x3609, 0x08},
+       {0x3921, 0x0f},
+       {0x3928, 0x15},
+       {0x3929, 0x2a},
+       {0x392a, 0x52},
+       {0x392b, 0xa3},
+       {0x340b, 0x1b},
+       {0x3426, 0x10},
+       {0x3407, 0x01},
+       {0x3404, 0x01},
+       {0x3500, 0x00},
+       {0x3501, 0x08},
+       {0x3502, 0x10},
+       {0x3508, 0x04},
+       {0x3509, 0x00},
+};
+
+static const char * const ov08x40_test_pattern_menu[] = {
+       "Disabled",
+       "Vertical Color Bar Type 1",
+       "Vertical Color Bar Type 2",
+       "Vertical Color Bar Type 3",
+       "Vertical Color Bar Type 4"
+};
+
+/* Configurations for supported link frequencies */
+#define OV08X40_LINK_FREQ_400MHZ       400000000ULL
+
+#define OV08X40_EXT_CLK                        19200000
+#define OV08X40_DATA_LANES             4
+
+/*
+ * pixel_rate = link_freq * data-rate * nr_of_lanes / bits_per_sample
+ * data rate => double data rate; number of lanes => 4; bits per pixel => 10
+ */
+static u64 link_freq_to_pixel_rate(u64 f)
+{
+       f *= 2 * OV08X40_DATA_LANES;
+       do_div(f, 10);
+
+       return f;
+}
+
+/* Menu items for LINK_FREQ V4L2 control */
+static const s64 link_freq_menu_items[] = {
+       OV08X40_LINK_FREQ_400MHZ,
+};
+
+/* Link frequency configs */
+static const struct ov08x40_link_freq_config link_freq_configs[] = {
+       [OV08X40_LINK_FREQ_400MHZ_INDEX] = {
+               .reg_list = {
+                       .num_of_regs = ARRAY_SIZE(mipi_data_rate_800mbps),
+                       .regs = mipi_data_rate_800mbps,
+               }
+       },
+};
+
+/* Mode configs */
+static const struct ov08x40_mode supported_modes[] = {
+       {
+               .width = 3856,
+               .height = 2416,
+               .vts_def = OV08X40_VTS_30FPS,
+               .vts_min = OV08X40_VTS_30FPS,
+               .lanes = 4,
+               .reg_list = {
+                       .num_of_regs = ARRAY_SIZE(mode_3856x2416_regs),
+                       .regs = mode_3856x2416_regs,
+               },
+               .link_freq_index = OV08X40_LINK_FREQ_400MHZ_INDEX,
+       },
+       {
+               .width = 1928,
+               .height = 1208,
+               .vts_def = OV08X40_VTS_BIN_30FPS,
+               .vts_min = OV08X40_VTS_BIN_30FPS,
+               .lanes = 4,
+               .reg_list = {
+                       .num_of_regs = ARRAY_SIZE(mode_1928x1208_regs),
+                       .regs = mode_1928x1208_regs,
+               },
+               .link_freq_index = OV08X40_LINK_FREQ_400MHZ_INDEX,
+       },
+};
+
+struct ov08x40 {
+       struct v4l2_subdev sd;
+       struct media_pad pad;
+
+       struct v4l2_ctrl_handler ctrl_handler;
+       /* V4L2 Controls */
+       struct v4l2_ctrl *link_freq;
+       struct v4l2_ctrl *pixel_rate;
+       struct v4l2_ctrl *vblank;
+       struct v4l2_ctrl *hblank;
+       struct v4l2_ctrl *exposure;
+
+       /* Current mode */
+       const struct ov08x40_mode *cur_mode;
+
+       /* Mutex for serialized access */
+       struct mutex mutex;
+
+       /* Streaming on/off */
+       bool streaming;
+};
+
+#define to_ov08x40(_sd)        container_of(_sd, struct ov08x40, sd)
+
+/* Read registers up to 4 at a time */
+static int ov08x40_read_reg(struct ov08x40 *ov08x,
+                           u16 reg, u32 len, u32 *val)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd);
+       struct i2c_msg msgs[2];
+       u8 *data_be_p;
+       int ret;
+       __be32 data_be = 0;
+       __be16 reg_addr_be = cpu_to_be16(reg);
+
+       if (len > 4)
+               return -EINVAL;
+
+       data_be_p = (u8 *)&data_be;
+       /* Write register address */
+       msgs[0].addr = client->addr;
+       msgs[0].flags = 0;
+       msgs[0].len = 2;
+       msgs[0].buf = (u8 *)&reg_addr_be;
+
+       /* Read data from register */
+       msgs[1].addr = client->addr;
+       msgs[1].flags = I2C_M_RD;
+       msgs[1].len = len;
+       msgs[1].buf = &data_be_p[4 - len];
+
+       ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+       if (ret != ARRAY_SIZE(msgs))
+               return -EIO;
+
+       *val = be32_to_cpu(data_be);
+
+       return 0;
+}
+
+/* Write registers up to 4 at a time */
+static int ov08x40_write_reg(struct ov08x40 *ov08x,
+                            u16 reg, u32 len, u32 __val)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd);
+       int buf_i, val_i;
+       u8 buf[6], *val_p;
+       __be32 val;
+
+       if (len > 4)
+               return -EINVAL;
+
+       buf[0] = reg >> 8;
+       buf[1] = reg & 0xff;
+
+       val = cpu_to_be32(__val);
+       val_p = (u8 *)&val;
+       buf_i = 2;
+       val_i = 4 - len;
+
+       while (val_i < 4)
+               buf[buf_i++] = val_p[val_i++];
+
+       if (i2c_master_send(client, buf, len + 2) != len + 2)
+               return -EIO;
+
+       return 0;
+}
+
+/* Write a list of registers */
+static int ov08x40_write_regs(struct ov08x40 *ov08x,
+                             const struct ov08x40_reg *regs, u32 len)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd);
+       int ret;
+       u32 i;
+
+       for (i = 0; i < len; i++) {
+               ret = ov08x40_write_reg(ov08x, regs[i].address, 1,
+                                       regs[i].val);
+
+               if (ret) {
+                       dev_err_ratelimited(&client->dev,
+                                           "Failed to write reg 0x%4.4x. error = %d\n",
+                                           regs[i].address, ret);
+
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+
+static int ov08x40_write_reg_list(struct ov08x40 *ov08x,
+                                 const struct ov08x40_reg_list *r_list)
+{
+       return ov08x40_write_regs(ov08x, r_list->regs, r_list->num_of_regs);
+}
+
+static int ov08x40_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+       const struct ov08x40_mode *default_mode = &supported_modes[0];
+       struct ov08x40 *ov08x = to_ov08x40(sd);
+       struct v4l2_mbus_framefmt *try_fmt =
+               v4l2_subdev_get_try_format(sd, fh->state, 0);
+
+       mutex_lock(&ov08x->mutex);
+
+       /* Initialize try_fmt */
+       try_fmt->width = default_mode->width;
+       try_fmt->height = default_mode->height;
+       try_fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10;
+       try_fmt->field = V4L2_FIELD_NONE;
+
+       /* No crop or compose */
+       mutex_unlock(&ov08x->mutex);
+
+       return 0;
+}
+
+static int ov08x40_update_digital_gain(struct ov08x40 *ov08x, u32 d_gain)
+{
+       int ret;
+       u32 val;
+
+       /*
+        * 0x350C[1:0], 0x350B[7:0], 0x350A[4:0]
+        */
+
+       val = (d_gain & OV08X40_DGTL_GAIN_L_MASK) << OV08X40_DGTL_GAIN_L_SHIFT;
+       ret = ov08x40_write_reg(ov08x, OV08X40_REG_DGTL_GAIN_L,
+                               OV08X40_REG_VALUE_08BIT, val);
+       if (ret)
+               return ret;
+
+       val = (d_gain >> OV08X40_DGTL_GAIN_M_SHIFT) & OV08X40_DGTL_GAIN_M_MASK;
+       ret = ov08x40_write_reg(ov08x, OV08X40_REG_DGTL_GAIN_M,
+                               OV08X40_REG_VALUE_08BIT, val);
+       if (ret)
+               return ret;
+
+       val = (d_gain >> OV08X40_DGTL_GAIN_H_SHIFT) & OV08X40_DGTL_GAIN_H_MASK;
+
+       return ov08x40_write_reg(ov08x, OV08X40_REG_DGTL_GAIN_H,
+                                OV08X40_REG_VALUE_08BIT, val);
+}
+
+static int ov08x40_enable_test_pattern(struct ov08x40 *ov08x, u32 pattern)
+{
+       int ret;
+       u32 val;
+
+       ret = ov08x40_read_reg(ov08x, OV08X40_REG_TEST_PATTERN,
+                              OV08X40_REG_VALUE_08BIT, &val);
+       if (ret)
+               return ret;
+
+       if (pattern) {
+               ret = ov08x40_read_reg(ov08x, OV08X40_REG_ISP,
+                                      OV08X40_REG_VALUE_08BIT, &val);
+               if (ret)
+                       return ret;
+
+               ret = ov08x40_write_reg(ov08x, OV08X40_REG_ISP,
+                                       OV08X40_REG_VALUE_08BIT,
+                                       val | BIT(1));
+               if (ret)
+                       return ret;
+
+               ret = ov08x40_read_reg(ov08x, OV08X40_REG_SHORT_TEST_PATTERN,
+                                      OV08X40_REG_VALUE_08BIT, &val);
+               if (ret)
+                       return ret;
+
+               ret = ov08x40_write_reg(ov08x, OV08X40_REG_SHORT_TEST_PATTERN,
+                                       OV08X40_REG_VALUE_08BIT,
+                                       val | BIT(0));
+               if (ret)
+                       return ret;
+
+               ret = ov08x40_read_reg(ov08x, OV08X40_REG_TEST_PATTERN,
+                                      OV08X40_REG_VALUE_08BIT, &val);
+               if (ret)
+                       return ret;
+
+               val &= OV08X40_TEST_PATTERN_MASK;
+               val |= ((pattern - 1) << OV08X40_TEST_PATTERN_BAR_SHIFT) |
+                       OV08X40_TEST_PATTERN_ENABLE;
+       } else {
+               val &= ~OV08X40_TEST_PATTERN_ENABLE;
+       }
+
+       return ov08x40_write_reg(ov08x, OV08X40_REG_TEST_PATTERN,
+                                OV08X40_REG_VALUE_08BIT, val);
+}
+
+static int ov08x40_set_ctrl_hflip(struct ov08x40 *ov08x, u32 ctrl_val)
+{
+       int ret;
+       u32 val;
+
+       ret = ov08x40_read_reg(ov08x, OV08X40_REG_MIRROR,
+                              OV08X40_REG_VALUE_08BIT, &val);
+       if (ret)
+               return ret;
+
+       return ov08x40_write_reg(ov08x, OV08X40_REG_MIRROR,
+                                OV08X40_REG_VALUE_08BIT,
+                                ctrl_val ? val | BIT(2) : val & ~BIT(2));
+}
+
+static int ov08x40_set_ctrl_vflip(struct ov08x40 *ov08x, u32 ctrl_val)
+{
+       int ret;
+       u32 val;
+
+       ret = ov08x40_read_reg(ov08x, OV08X40_REG_VFLIP,
+                              OV08X40_REG_VALUE_08BIT, &val);
+       if (ret)
+               return ret;
+
+       return ov08x40_write_reg(ov08x, OV08X40_REG_VFLIP,
+                                OV08X40_REG_VALUE_08BIT,
+                                ctrl_val ? val | BIT(2) : val & ~BIT(2));
+}
+
+static int ov08x40_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+       struct ov08x40 *ov08x = container_of(ctrl->handler,
+                                            struct ov08x40, ctrl_handler);
+       struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd);
+       s64 max;
+       int ret = 0;
+
+       /* Propagate change of current control to all related controls */
+       switch (ctrl->id) {
+       case V4L2_CID_VBLANK:
+               /* Update max exposure while meeting expected vblanking */
+               max = ov08x->cur_mode->height + ctrl->val - OV08X40_EXPOSURE_MAX_MARGIN;
+               __v4l2_ctrl_modify_range(ov08x->exposure,
+                                        ov08x->exposure->minimum,
+                                        max, ov08x->exposure->step, max);
+               break;
+       }
+
+       /*
+        * Applying V4L2 control value only happens
+        * when power is up for streaming
+        */
+       if (!pm_runtime_get_if_in_use(&client->dev))
+               return 0;
+
+       switch (ctrl->id) {
+       case V4L2_CID_ANALOGUE_GAIN:
+               ret = ov08x40_write_reg(ov08x, OV08X40_REG_ANALOG_GAIN,
+                                       OV08X40_REG_VALUE_16BIT,
+                                       ctrl->val << 1);
+               break;
+       case V4L2_CID_DIGITAL_GAIN:
+               ret = ov08x40_update_digital_gain(ov08x, ctrl->val);
+               break;
+       case V4L2_CID_EXPOSURE:
+               ret = ov08x40_write_reg(ov08x, OV08X40_REG_EXPOSURE,
+                                       OV08X40_REG_VALUE_24BIT,
+                                       ctrl->val);
+               break;
+       case V4L2_CID_VBLANK:
+               ret = ov08x40_write_reg(ov08x, OV08X40_REG_VTS,
+                                       OV08X40_REG_VALUE_16BIT,
+                                       ov08x->cur_mode->height
+                                       + ctrl->val);
+               break;
+       case V4L2_CID_TEST_PATTERN:
+               ret = ov08x40_enable_test_pattern(ov08x, ctrl->val);
+               break;
+       case V4L2_CID_HFLIP:
+               ov08x40_set_ctrl_hflip(ov08x, ctrl->val);
+               break;
+       case V4L2_CID_VFLIP:
+               ov08x40_set_ctrl_vflip(ov08x, ctrl->val);
+               break;
+       default:
+               dev_info(&client->dev,
+                        "ctrl(id:0x%x,val:0x%x) is not handled\n",
+                        ctrl->id, ctrl->val);
+               break;
+       }
+
+       pm_runtime_put(&client->dev);
+
+       return ret;
+}
+
+static const struct v4l2_ctrl_ops ov08x40_ctrl_ops = {
+       .s_ctrl = ov08x40_set_ctrl,
+};
+
+static int ov08x40_enum_mbus_code(struct v4l2_subdev *sd,
+                                 struct v4l2_subdev_state *sd_state,
+                                 struct v4l2_subdev_mbus_code_enum *code)
+{
+       /* Only one bayer order(GRBG) is supported */
+       if (code->index > 0)
+               return -EINVAL;
+
+       code->code = MEDIA_BUS_FMT_SGRBG10_1X10;
+
+       return 0;
+}
+
+static int ov08x40_enum_frame_size(struct v4l2_subdev *sd,
+                                  struct v4l2_subdev_state *sd_state,
+                                  struct v4l2_subdev_frame_size_enum *fse)
+{
+       if (fse->index >= ARRAY_SIZE(supported_modes))
+               return -EINVAL;
+
+       if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10)
+               return -EINVAL;
+
+       fse->min_width = supported_modes[fse->index].width;
+       fse->max_width = fse->min_width;
+       fse->min_height = supported_modes[fse->index].height;
+       fse->max_height = fse->min_height;
+
+       return 0;
+}
+
+static void ov08x40_update_pad_format(const struct ov08x40_mode *mode,
+                                     struct v4l2_subdev_format *fmt)
+{
+       fmt->format.width = mode->width;
+       fmt->format.height = mode->height;
+       fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;
+       fmt->format.field = V4L2_FIELD_NONE;
+}
+
+static int ov08x40_do_get_pad_format(struct ov08x40 *ov08x,
+                                    struct v4l2_subdev_state *sd_state,
+                                    struct v4l2_subdev_format *fmt)
+{
+       struct v4l2_mbus_framefmt *framefmt;
+       struct v4l2_subdev *sd = &ov08x->sd;
+
+       if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+               framefmt = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad);
+               fmt->format = *framefmt;
+       } else {
+               ov08x40_update_pad_format(ov08x->cur_mode, fmt);
+       }
+
+       return 0;
+}
+
+static int ov08x40_get_pad_format(struct v4l2_subdev *sd,
+                                 struct v4l2_subdev_state *sd_state,
+                                 struct v4l2_subdev_format *fmt)
+{
+       struct ov08x40 *ov08x = to_ov08x40(sd);
+       int ret;
+
+       mutex_lock(&ov08x->mutex);
+       ret = ov08x40_do_get_pad_format(ov08x, sd_state, fmt);
+       mutex_unlock(&ov08x->mutex);
+
+       return ret;
+}
+
+static int
+ov08x40_set_pad_format(struct v4l2_subdev *sd,
+                      struct v4l2_subdev_state *sd_state,
+                      struct v4l2_subdev_format *fmt)
+{
+       struct ov08x40 *ov08x = to_ov08x40(sd);
+       const struct ov08x40_mode *mode;
+       struct v4l2_mbus_framefmt *framefmt;
+       s32 vblank_def;
+       s32 vblank_min;
+       s64 h_blank;
+       s64 pixel_rate;
+       s64 link_freq;
+
+       mutex_lock(&ov08x->mutex);
+
+       /* Only one raw bayer(GRBG) order is supported */
+       if (fmt->format.code != MEDIA_BUS_FMT_SGRBG10_1X10)
+               fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;
+
+       mode = v4l2_find_nearest_size(supported_modes,
+                                     ARRAY_SIZE(supported_modes),
+                                     width, height,
+                                     fmt->format.width, fmt->format.height);
+       ov08x40_update_pad_format(mode, fmt);
+       if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+               framefmt = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad);
+               *framefmt = fmt->format;
+       } else {
+               ov08x->cur_mode = mode;
+               __v4l2_ctrl_s_ctrl(ov08x->link_freq, mode->link_freq_index);
+               link_freq = link_freq_menu_items[mode->link_freq_index];
+               pixel_rate = link_freq_to_pixel_rate(link_freq);
+               __v4l2_ctrl_s_ctrl_int64(ov08x->pixel_rate, pixel_rate);
+
+               /* Update limits and set FPS to default */
+               vblank_def = ov08x->cur_mode->vts_def -
+                            ov08x->cur_mode->height;
+               vblank_min = ov08x->cur_mode->vts_min -
+                            ov08x->cur_mode->height;
+               __v4l2_ctrl_modify_range(ov08x->vblank, vblank_min,
+                                        OV08X40_VTS_MAX
+                                        - ov08x->cur_mode->height,
+                                        1,
+                                        vblank_def);
+               __v4l2_ctrl_s_ctrl(ov08x->vblank, vblank_def);
+               h_blank =
+                       link_freq_configs[mode->link_freq_index].pixels_per_line
+                        - ov08x->cur_mode->width;
+               __v4l2_ctrl_modify_range(ov08x->hblank, h_blank,
+                                        h_blank, 1, h_blank);
+       }
+
+       mutex_unlock(&ov08x->mutex);
+
+       return 0;
+}
+
+static int ov08x40_start_streaming(struct ov08x40 *ov08x)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd);
+       const struct ov08x40_reg_list *reg_list;
+       int ret, link_freq_index;
+
+       /* Get out of from software reset */
+       ret = ov08x40_write_reg(ov08x, OV08X40_REG_SOFTWARE_RST,
+                               OV08X40_REG_VALUE_08BIT, OV08X40_SOFTWARE_RST);
+       if (ret) {
+               dev_err(&client->dev, "%s failed to set powerup registers\n",
+                       __func__);
+               return ret;
+       }
+
+       link_freq_index = ov08x->cur_mode->link_freq_index;
+       reg_list = &link_freq_configs[link_freq_index].reg_list;
+
+       ret = ov08x40_write_reg_list(ov08x, reg_list);
+       if (ret) {
+               dev_err(&client->dev, "%s failed to set plls\n", __func__);
+               return ret;
+       }
+
+       /* Apply default values of current mode */
+       reg_list = &ov08x->cur_mode->reg_list;
+       ret = ov08x40_write_reg_list(ov08x, reg_list);
+       if (ret) {
+               dev_err(&client->dev, "%s failed to set mode\n", __func__);
+               return ret;
+       }
+
+       /* Apply customized values from user */
+       ret =  __v4l2_ctrl_handler_setup(ov08x->sd.ctrl_handler);
+       if (ret)
+               return ret;
+
+       return ov08x40_write_reg(ov08x, OV08X40_REG_MODE_SELECT,
+                                OV08X40_REG_VALUE_08BIT,
+                                OV08X40_MODE_STREAMING);
+}
+
+/* Stop streaming */
+static int ov08x40_stop_streaming(struct ov08x40 *ov08x)
+{
+       return ov08x40_write_reg(ov08x, OV08X40_REG_MODE_SELECT,
+                                OV08X40_REG_VALUE_08BIT, OV08X40_MODE_STANDBY);
+}
+
+static int ov08x40_set_stream(struct v4l2_subdev *sd, int enable)
+{
+       struct ov08x40 *ov08x = to_ov08x40(sd);
+       struct i2c_client *client = v4l2_get_subdevdata(sd);
+       int ret = 0;
+
+       mutex_lock(&ov08x->mutex);
+       if (ov08x->streaming == enable) {
+               mutex_unlock(&ov08x->mutex);
+               return 0;
+       }
+
+       if (enable) {
+               ret = pm_runtime_resume_and_get(&client->dev);
+               if (ret < 0)
+                       goto err_unlock;
+
+               /*
+                * Apply default & customized values
+                * and then start streaming.
+                */
+               ret = ov08x40_start_streaming(ov08x);
+               if (ret)
+                       goto err_rpm_put;
+       } else {
+               ov08x40_stop_streaming(ov08x);
+               pm_runtime_put(&client->dev);
+       }
+
+       ov08x->streaming = enable;
+       mutex_unlock(&ov08x->mutex);
+
+       return ret;
+
+err_rpm_put:
+       pm_runtime_put(&client->dev);
+err_unlock:
+       mutex_unlock(&ov08x->mutex);
+
+       return ret;
+}
+
+static int __maybe_unused ov08x40_suspend(struct device *dev)
+{
+       struct v4l2_subdev *sd = dev_get_drvdata(dev);
+       struct ov08x40 *ov08x = to_ov08x40(sd);
+
+       if (ov08x->streaming)
+               ov08x40_stop_streaming(ov08x);
+
+       return 0;
+}
+
+static int __maybe_unused ov08x40_resume(struct device *dev)
+{
+       struct v4l2_subdev *sd = dev_get_drvdata(dev);
+       struct ov08x40 *ov08x = to_ov08x40(sd);
+       int ret;
+
+       if (ov08x->streaming) {
+               ret = ov08x40_start_streaming(ov08x);
+               if (ret)
+                       goto error;
+       }
+
+       return 0;
+
+error:
+       ov08x40_stop_streaming(ov08x);
+       ov08x->streaming = false;
+       return ret;
+}
+
+/* Verify chip ID */
+static int ov08x40_identify_module(struct ov08x40 *ov08x)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd);
+       int ret;
+       u32 val;
+
+       ret = ov08x40_read_reg(ov08x, OV08X40_REG_CHIP_ID,
+                              OV08X40_REG_VALUE_24BIT, &val);
+       if (ret)
+               return ret;
+
+       if (val != OV08X40_CHIP_ID) {
+               dev_err(&client->dev, "chip id mismatch: %x!=%x\n",
+                       OV08X40_CHIP_ID, val);
+               return -EIO;
+       }
+
+       return 0;
+}
+
+static const struct v4l2_subdev_video_ops ov08x40_video_ops = {
+       .s_stream = ov08x40_set_stream,
+};
+
+static const struct v4l2_subdev_pad_ops ov08x40_pad_ops = {
+       .enum_mbus_code = ov08x40_enum_mbus_code,
+       .get_fmt = ov08x40_get_pad_format,
+       .set_fmt = ov08x40_set_pad_format,
+       .enum_frame_size = ov08x40_enum_frame_size,
+};
+
+static const struct v4l2_subdev_ops ov08x40_subdev_ops = {
+       .video = &ov08x40_video_ops,
+       .pad = &ov08x40_pad_ops,
+};
+
+static const struct media_entity_operations ov08x40_subdev_entity_ops = {
+       .link_validate = v4l2_subdev_link_validate,
+};
+
+static const struct v4l2_subdev_internal_ops ov08x40_internal_ops = {
+       .open = ov08x40_open,
+};
+
+static int ov08x40_init_controls(struct ov08x40 *ov08x)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd);
+       struct v4l2_fwnode_device_properties props;
+       struct v4l2_ctrl_handler *ctrl_hdlr;
+       s64 exposure_max;
+       s64 vblank_def;
+       s64 vblank_min;
+       s64 hblank;
+       s64 pixel_rate_min;
+       s64 pixel_rate_max;
+       const struct ov08x40_mode *mode;
+       u32 max;
+       int ret;
+
+       ctrl_hdlr = &ov08x->ctrl_handler;
+       ret = v4l2_ctrl_handler_init(ctrl_hdlr, 10);
+       if (ret)
+               return ret;
+
+       mutex_init(&ov08x->mutex);
+       ctrl_hdlr->lock = &ov08x->mutex;
+       max = ARRAY_SIZE(link_freq_menu_items) - 1;
+       ov08x->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr,
+                                                 &ov08x40_ctrl_ops,
+                                                 V4L2_CID_LINK_FREQ,
+                                                 max,
+                                                 0,
+                                                 link_freq_menu_items);
+       if (ov08x->link_freq)
+               ov08x->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+       pixel_rate_max = link_freq_to_pixel_rate(link_freq_menu_items[0]);
+       pixel_rate_min = 0;
+       /* By default, PIXEL_RATE is read only */
+       ov08x->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops,
+                                             V4L2_CID_PIXEL_RATE,
+                                             pixel_rate_min, pixel_rate_max,
+                                             1, pixel_rate_max);
+
+       mode = ov08x->cur_mode;
+       vblank_def = mode->vts_def - mode->height;
+       vblank_min = mode->vts_min - mode->height;
+       ov08x->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops,
+                                         V4L2_CID_VBLANK,
+                                         vblank_min,
+                                         OV08X40_VTS_MAX - mode->height, 1,
+                                         vblank_def);
+
+       hblank = link_freq_configs[mode->link_freq_index].pixels_per_line -
+                mode->width;
+       ov08x->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops,
+                                         V4L2_CID_HBLANK,
+                                         hblank, hblank, 1, hblank);
+       if (ov08x->hblank)
+               ov08x->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+       exposure_max = mode->vts_def - OV08X40_EXPOSURE_MAX_MARGIN;
+       ov08x->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops,
+                                           V4L2_CID_EXPOSURE,
+                                           OV08X40_EXPOSURE_MIN,
+                                           exposure_max, OV08X40_EXPOSURE_STEP,
+                                           exposure_max);
+
+       v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
+                         OV08X40_ANA_GAIN_MIN, OV08X40_ANA_GAIN_MAX,
+                         OV08X40_ANA_GAIN_STEP, OV08X40_ANA_GAIN_DEFAULT);
+
+       /* Digital gain */
+       v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
+                         OV08X40_DGTL_GAIN_MIN, OV08X40_DGTL_GAIN_MAX,
+                         OV08X40_DGTL_GAIN_STEP, OV08X40_DGTL_GAIN_DEFAULT);
+
+       v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov08x40_ctrl_ops,
+                                    V4L2_CID_TEST_PATTERN,
+                                    ARRAY_SIZE(ov08x40_test_pattern_menu) - 1,
+                                    0, 0, ov08x40_test_pattern_menu);
+
+       v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops,
+                         V4L2_CID_HFLIP, 0, 1, 1, 0);
+       v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops,
+                         V4L2_CID_VFLIP, 0, 1, 1, 0);
+
+       if (ctrl_hdlr->error) {
+               ret = ctrl_hdlr->error;
+               dev_err(&client->dev, "%s control init failed (%d)\n",
+                       __func__, ret);
+               goto error;
+       }
+
+       ret = v4l2_fwnode_device_parse(&client->dev, &props);
+       if (ret)
+               goto error;
+
+       ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &ov08x40_ctrl_ops,
+                                             &props);
+       if (ret)
+               goto error;
+
+       ov08x->sd.ctrl_handler = ctrl_hdlr;
+
+       return 0;
+
+error:
+       v4l2_ctrl_handler_free(ctrl_hdlr);
+       mutex_destroy(&ov08x->mutex);
+
+       return ret;
+}
+
+static void ov08x40_free_controls(struct ov08x40 *ov08x)
+{
+       v4l2_ctrl_handler_free(ov08x->sd.ctrl_handler);
+       mutex_destroy(&ov08x->mutex);
+}
+
+static int ov08x40_check_hwcfg(struct device *dev)
+{
+       struct v4l2_fwnode_endpoint bus_cfg = {
+               .bus_type = V4L2_MBUS_CSI2_DPHY
+       };
+       struct fwnode_handle *ep;
+       struct fwnode_handle *fwnode = dev_fwnode(dev);
+       unsigned int i, j;
+       int ret;
+       u32 ext_clk;
+
+       if (!fwnode)
+               return -ENXIO;
+
+       ret = fwnode_property_read_u32(dev_fwnode(dev), "clock-frequency",
+                                      &ext_clk);
+       if (ret) {
+               dev_err(dev, "can't get clock frequency");
+               return ret;
+       }
+
+       if (ext_clk != OV08X40_EXT_CLK) {
+               dev_err(dev, "external clock %d is not supported",
+                       ext_clk);
+               return -EINVAL;
+       }
+
+       ep = fwnode_graph_get_next_endpoint(fwnode, NULL);
+       if (!ep)
+               return -ENXIO;
+
+       ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
+       fwnode_handle_put(ep);
+       if (ret)
+               return ret;
+
+       if (bus_cfg.bus.mipi_csi2.num_data_lanes != OV08X40_DATA_LANES) {
+               dev_err(dev, "number of CSI2 data lanes %d is not supported",
+                       bus_cfg.bus.mipi_csi2.num_data_lanes);
+               ret = -EINVAL;
+               goto out_err;
+       }
+
+       if (!bus_cfg.nr_of_link_frequencies) {
+               dev_err(dev, "no link frequencies defined");
+               ret = -EINVAL;
+               goto out_err;
+       }
+
+       for (i = 0; i < ARRAY_SIZE(link_freq_menu_items); i++) {
+               for (j = 0; j < bus_cfg.nr_of_link_frequencies; j++) {
+                       if (link_freq_menu_items[i] ==
+                               bus_cfg.link_frequencies[j])
+                               break;
+               }
+
+               if (j == bus_cfg.nr_of_link_frequencies) {
+                       dev_err(dev, "no link frequency %lld supported",
+                               link_freq_menu_items[i]);
+                       ret = -EINVAL;
+                       goto out_err;
+               }
+       }
+
+out_err:
+       v4l2_fwnode_endpoint_free(&bus_cfg);
+
+       return ret;
+}
+
+static int ov08x40_probe(struct i2c_client *client)
+{
+       struct ov08x40 *ov08x;
+       int ret;
+
+       /* Check HW config */
+       ret = ov08x40_check_hwcfg(&client->dev);
+       if (ret) {
+               dev_err(&client->dev, "failed to check hwcfg: %d", ret);
+               return ret;
+       }
+
+       ov08x = devm_kzalloc(&client->dev, sizeof(*ov08x), GFP_KERNEL);
+       if (!ov08x)
+               return -ENOMEM;
+
+       /* Initialize subdev */
+       v4l2_i2c_subdev_init(&ov08x->sd, client, &ov08x40_subdev_ops);
+
+       /* Check module identity */
+       ret = ov08x40_identify_module(ov08x);
+       if (ret) {
+               dev_err(&client->dev, "failed to find sensor: %d\n", ret);
+               return ret;
+       }
+
+       /* Set default mode to max resolution */
+       ov08x->cur_mode = &supported_modes[0];
+
+       ret = ov08x40_init_controls(ov08x);
+       if (ret)
+               return ret;
+
+       /* Initialize subdev */
+       ov08x->sd.internal_ops = &ov08x40_internal_ops;
+       ov08x->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+       ov08x->sd.entity.ops = &ov08x40_subdev_entity_ops;
+       ov08x->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
+
+       /* Initialize source pad */
+       ov08x->pad.flags = MEDIA_PAD_FL_SOURCE;
+       ret = media_entity_pads_init(&ov08x->sd.entity, 1, &ov08x->pad);
+       if (ret) {
+               dev_err(&client->dev, "%s failed:%d\n", __func__, ret);
+               goto error_handler_free;
+       }
+
+       ret = v4l2_async_register_subdev_sensor(&ov08x->sd);
+       if (ret < 0)
+               goto error_media_entity;
+
+       /*
+        * Device is already turned on by i2c-core with ACPI domain PM.
+        * Enable runtime PM and turn off the device.
+        */
+       pm_runtime_set_active(&client->dev);
+       pm_runtime_enable(&client->dev);
+       pm_runtime_idle(&client->dev);
+
+       return 0;
+
+error_media_entity:
+       media_entity_cleanup(&ov08x->sd.entity);
+
+error_handler_free:
+       ov08x40_free_controls(ov08x);
+
+       return ret;
+}
+
+static int ov08x40_remove(struct i2c_client *client)
+{
+       struct v4l2_subdev *sd = i2c_get_clientdata(client);
+       struct ov08x40 *ov08x = to_ov08x40(sd);
+
+       v4l2_async_unregister_subdev(sd);
+       media_entity_cleanup(&sd->entity);
+       ov08x40_free_controls(ov08x);
+
+       pm_runtime_disable(&client->dev);
+       pm_runtime_set_suspended(&client->dev);
+
+       return 0;
+}
+
+static const struct dev_pm_ops ov08x40_pm_ops = {
+       SET_SYSTEM_SLEEP_PM_OPS(ov08x40_suspend, ov08x40_resume)
+};
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id ov08x40_acpi_ids[] = {
+       {"OVTI08F4"},
+       { /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(acpi, ov08x40_acpi_ids);
+#endif
+
+static struct i2c_driver ov08x40_i2c_driver = {
+       .driver = {
+               .name = "ov08x40",
+               .pm = &ov08x40_pm_ops,
+               .acpi_match_table = ACPI_PTR(ov08x40_acpi_ids),
+       },
+       .probe_new = ov08x40_probe,
+       .remove = ov08x40_remove,
+};
+
+module_i2c_driver(ov08x40_i2c_driver);
+
+MODULE_AUTHOR("Jason Chen <jason.z.chen@intel.com>");
+MODULE_AUTHOR("Shawn Tu <shawnx.tu@intel.com>");
+MODULE_DESCRIPTION("OmniVision OV08X40 sensor driver");
+MODULE_LICENSE("GPL");
index 29ed0ef8c03392e960f7c5060375297e49676763..39d56838a4efcc89d2102a8d14278b6efba6c1f7 100644 (file)
@@ -16,9 +16,7 @@
 #include <linux/clk.h>
 #include <linux/slab.h>
 #include <linux/delay.h>
-#include <linux/gpio.h>
 #include <linux/gpio/consumer.h>
-#include <linux/of_gpio.h>
 #include <linux/v4l2-mediabus.h>
 #include <linux/videodev2.h>
 
diff --git a/drivers/media/i2c/ov4689.c b/drivers/media/i2c/ov4689.c
new file mode 100644 (file)
index 0000000..c602e50
--- /dev/null
@@ -0,0 +1,1018 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ov4689 driver
+ *
+ * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd.
+ * Copyright (C) 2022 Mikhail Rudenko
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <media/media-entity.h>
+#include <media/v4l2-async.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-subdev.h>
+#include <media/v4l2-fwnode.h>
+
+#define CHIP_ID                                0x004688
+#define OV4689_REG_CHIP_ID             0x300a
+
+#define OV4689_XVCLK_FREQ              24000000
+
+#define OV4689_REG_CTRL_MODE           0x0100
+#define OV4689_MODE_SW_STANDBY         0x0
+#define OV4689_MODE_STREAMING          BIT(0)
+
+#define OV4689_REG_EXPOSURE            0x3500
+#define OV4689_EXPOSURE_MIN            4
+#define OV4689_EXPOSURE_STEP           1
+#define OV4689_VTS_MAX                 0x7fff
+
+#define OV4689_REG_GAIN_H              0x3508
+#define OV4689_REG_GAIN_L              0x3509
+#define OV4689_GAIN_H_MASK             0x07
+#define OV4689_GAIN_H_SHIFT            8
+#define OV4689_GAIN_L_MASK             0xff
+#define OV4689_GAIN_STEP               1
+#define OV4689_GAIN_DEFAULT            0x80
+
+#define OV4689_REG_TEST_PATTERN                0x5040
+#define OV4689_TEST_PATTERN_ENABLE     0x80
+#define OV4689_TEST_PATTERN_DISABLE    0x0
+
+#define OV4689_REG_VTS                 0x380e
+
+#define REG_NULL                       0xFFFF
+
+#define OV4689_REG_VALUE_08BIT         1
+#define OV4689_REG_VALUE_16BIT         2
+#define OV4689_REG_VALUE_24BIT         3
+
+#define OV4689_LANES                   4
+
+static const char *const ov4689_supply_names[] = {
+       "avdd", /* Analog power */
+       "dovdd", /* Digital I/O power */
+       "dvdd", /* Digital core power */
+};
+
+struct regval {
+       u16 addr;
+       u8 val;
+};
+
+enum ov4689_mode_id {
+       OV4689_MODE_2688_1520 = 0,
+       OV4689_NUM_MODES,
+};
+
+struct ov4689_mode {
+       enum ov4689_mode_id id;
+       u32 width;
+       u32 height;
+       u32 max_fps;
+       u32 hts_def;
+       u32 vts_def;
+       u32 exp_def;
+       u32 pixel_rate;
+       u32 sensor_width;
+       u32 sensor_height;
+       u32 crop_top;
+       u32 crop_left;
+       const struct regval *reg_list;
+};
+
+struct ov4689 {
+       struct i2c_client *client;
+       struct clk *xvclk;
+       struct gpio_desc *reset_gpio;
+       struct gpio_desc *pwdn_gpio;
+       struct regulator_bulk_data supplies[ARRAY_SIZE(ov4689_supply_names)];
+
+       struct v4l2_subdev subdev;
+       struct media_pad pad;
+
+       u32 clock_rate;
+
+       struct mutex mutex; /* lock to protect streaming, ctrls and cur_mode */
+       bool streaming;
+       struct v4l2_ctrl_handler ctrl_handler;
+       struct v4l2_ctrl *exposure;
+
+       const struct ov4689_mode *cur_mode;
+};
+
+#define to_ov4689(sd) container_of(sd, struct ov4689, subdev)
+
+struct ov4689_gain_range {
+       u32 logical_min;
+       u32 logical_max;
+       u32 offset;
+       u32 divider;
+       u32 physical_min;
+       u32 physical_max;
+};
+
+/*
+ * Xclk 24Mhz
+ * max_framerate 30fps
+ * mipi_datarate per lane 1008Mbps
+ */
+static const struct regval ov4689_2688x1520_regs[] = {
+       {0x0103, 0x01}, {0x3638, 0x00}, {0x0300, 0x00},
+       {0x0302, 0x2a}, {0x0303, 0x00}, {0x0304, 0x03},
+       {0x030b, 0x00}, {0x030d, 0x1e}, {0x030e, 0x04},
+       {0x030f, 0x01}, {0x0312, 0x01}, {0x031e, 0x00},
+       {0x3000, 0x20}, {0x3002, 0x00}, {0x3018, 0x72},
+       {0x3020, 0x93}, {0x3021, 0x03}, {0x3022, 0x01},
+       {0x3031, 0x0a}, {0x303f, 0x0c}, {0x3305, 0xf1},
+       {0x3307, 0x04}, {0x3309, 0x29}, {0x3500, 0x00},
+       {0x3501, 0x60}, {0x3502, 0x00}, {0x3503, 0x04},
+       {0x3504, 0x00}, {0x3505, 0x00}, {0x3506, 0x00},
+       {0x3507, 0x00}, {0x3508, 0x00}, {0x3509, 0x80},
+       {0x350a, 0x00}, {0x350b, 0x00}, {0x350c, 0x00},
+       {0x350d, 0x00}, {0x350e, 0x00}, {0x350f, 0x80},
+       {0x3510, 0x00}, {0x3511, 0x00}, {0x3512, 0x00},
+       {0x3513, 0x00}, {0x3514, 0x00}, {0x3515, 0x80},
+       {0x3516, 0x00}, {0x3517, 0x00}, {0x3518, 0x00},
+       {0x3519, 0x00}, {0x351a, 0x00}, {0x351b, 0x80},
+       {0x351c, 0x00}, {0x351d, 0x00}, {0x351e, 0x00},
+       {0x351f, 0x00}, {0x3520, 0x00}, {0x3521, 0x80},
+       {0x3522, 0x08}, {0x3524, 0x08}, {0x3526, 0x08},
+       {0x3528, 0x08}, {0x352a, 0x08}, {0x3602, 0x00},
+       {0x3603, 0x40}, {0x3604, 0x02}, {0x3605, 0x00},
+       {0x3606, 0x00}, {0x3607, 0x00}, {0x3609, 0x12},
+       {0x360a, 0x40}, {0x360c, 0x08}, {0x360f, 0xe5},
+       {0x3608, 0x8f}, {0x3611, 0x00}, {0x3613, 0xf7},
+       {0x3616, 0x58}, {0x3619, 0x99}, {0x361b, 0x60},
+       {0x361c, 0x7a}, {0x361e, 0x79}, {0x361f, 0x02},
+       {0x3632, 0x00}, {0x3633, 0x10}, {0x3634, 0x10},
+       {0x3635, 0x10}, {0x3636, 0x15}, {0x3646, 0x86},
+       {0x364a, 0x0b}, {0x3700, 0x17}, {0x3701, 0x22},
+       {0x3703, 0x10}, {0x370a, 0x37}, {0x3705, 0x00},
+       {0x3706, 0x63}, {0x3709, 0x3c}, {0x370b, 0x01},
+       {0x370c, 0x30}, {0x3710, 0x24}, {0x3711, 0x0c},
+       {0x3716, 0x00}, {0x3720, 0x28}, {0x3729, 0x7b},
+       {0x372a, 0x84}, {0x372b, 0xbd}, {0x372c, 0xbc},
+       {0x372e, 0x52}, {0x373c, 0x0e}, {0x373e, 0x33},
+       {0x3743, 0x10}, {0x3744, 0x88}, {0x3745, 0xc0},
+       {0x374a, 0x43}, {0x374c, 0x00}, {0x374e, 0x23},
+       {0x3751, 0x7b}, {0x3752, 0x84}, {0x3753, 0xbd},
+       {0x3754, 0xbc}, {0x3756, 0x52}, {0x375c, 0x00},
+       {0x3760, 0x00}, {0x3761, 0x00}, {0x3762, 0x00},
+       {0x3763, 0x00}, {0x3764, 0x00}, {0x3767, 0x04},
+       {0x3768, 0x04}, {0x3769, 0x08}, {0x376a, 0x08},
+       {0x376b, 0x20}, {0x376c, 0x00}, {0x376d, 0x00},
+       {0x376e, 0x00}, {0x3773, 0x00}, {0x3774, 0x51},
+       {0x3776, 0xbd}, {0x3777, 0xbd}, {0x3781, 0x18},
+       {0x3783, 0x25}, {0x3798, 0x1b}, {0x3800, 0x00},
+       {0x3801, 0x08}, {0x3802, 0x00}, {0x3803, 0x04},
+       {0x3804, 0x0a}, {0x3805, 0x97}, {0x3806, 0x05},
+       {0x3807, 0xfb}, {0x3808, 0x0a}, {0x3809, 0x80},
+       {0x380a, 0x05}, {0x380b, 0xf0}, {0x380c, 0x0a},
+       {0x380d, 0x0e}, {0x380e, 0x06}, {0x380f, 0x12},
+       {0x3810, 0x00}, {0x3811, 0x08}, {0x3812, 0x00},
+       {0x3813, 0x04}, {0x3814, 0x01}, {0x3815, 0x01},
+       {0x3819, 0x01}, {0x3820, 0x00}, {0x3821, 0x06},
+       {0x3829, 0x00}, {0x382a, 0x01}, {0x382b, 0x01},
+       {0x382d, 0x7f}, {0x3830, 0x04}, {0x3836, 0x01},
+       {0x3837, 0x00}, {0x3841, 0x02}, {0x3846, 0x08},
+       {0x3847, 0x07}, {0x3d85, 0x36}, {0x3d8c, 0x71},
+       {0x3d8d, 0xcb}, {0x3f0a, 0x00}, {0x4000, 0xf1},
+       {0x4001, 0x40}, {0x4002, 0x04}, {0x4003, 0x14},
+       {0x400e, 0x00}, {0x4011, 0x00}, {0x401a, 0x00},
+       {0x401b, 0x00}, {0x401c, 0x00}, {0x401d, 0x00},
+       {0x401f, 0x00}, {0x4020, 0x00}, {0x4021, 0x10},
+       {0x4022, 0x07}, {0x4023, 0xcf}, {0x4024, 0x09},
+       {0x4025, 0x60}, {0x4026, 0x09}, {0x4027, 0x6f},
+       {0x4028, 0x00}, {0x4029, 0x02}, {0x402a, 0x06},
+       {0x402b, 0x04}, {0x402c, 0x02}, {0x402d, 0x02},
+       {0x402e, 0x0e}, {0x402f, 0x04}, {0x4302, 0xff},
+       {0x4303, 0xff}, {0x4304, 0x00}, {0x4305, 0x00},
+       {0x4306, 0x00}, {0x4308, 0x02}, {0x4500, 0x6c},
+       {0x4501, 0xc4}, {0x4502, 0x40}, {0x4503, 0x01},
+       {0x4601, 0xa7}, {0x4800, 0x04}, {0x4813, 0x08},
+       {0x481f, 0x40}, {0x4829, 0x78}, {0x4837, 0x10},
+       {0x4b00, 0x2a}, {0x4b0d, 0x00}, {0x4d00, 0x04},
+       {0x4d01, 0x42}, {0x4d02, 0xd1}, {0x4d03, 0x93},
+       {0x4d04, 0xf5}, {0x4d05, 0xc1}, {0x5000, 0xf3},
+       {0x5001, 0x11}, {0x5004, 0x00}, {0x500a, 0x00},
+       {0x500b, 0x00}, {0x5032, 0x00}, {0x5040, 0x00},
+       {0x5050, 0x0c}, {0x5500, 0x00}, {0x5501, 0x10},
+       {0x5502, 0x01}, {0x5503, 0x0f}, {0x8000, 0x00},
+       {0x8001, 0x00}, {0x8002, 0x00}, {0x8003, 0x00},
+       {0x8004, 0x00}, {0x8005, 0x00}, {0x8006, 0x00},
+       {0x8007, 0x00}, {0x8008, 0x00}, {0x3638, 0x00},
+       {REG_NULL, 0x00},
+};
+
+static const struct ov4689_mode supported_modes[] = {
+       {
+               .id = OV4689_MODE_2688_1520,
+               .width = 2688,
+               .height = 1520,
+               .sensor_width = 2720,
+               .sensor_height = 1536,
+               .crop_top = 8,
+               .crop_left = 16,
+               .max_fps = 30,
+               .exp_def = 1536,
+               .hts_def = 4 * 2574,
+               .vts_def = 1554,
+               .pixel_rate = 480000000,
+               .reg_list = ov4689_2688x1520_regs,
+       },
+};
+
+static const u64 link_freq_menu_items[] = { 504000000 };
+
+static const char *const ov4689_test_pattern_menu[] = {
+       "Disabled",
+       "Vertical Color Bar Type 1",
+       "Vertical Color Bar Type 2",
+       "Vertical Color Bar Type 3",
+       "Vertical Color Bar Type 4"
+};
+
+/*
+ * These coefficients are based on those used in Rockchip's camera
+ * engine, with minor tweaks for continuity.
+ */
+static const struct ov4689_gain_range ov4689_gain_ranges[] = {
+       {
+               .logical_min = 0,
+               .logical_max = 255,
+               .offset = 0,
+               .divider = 1,
+               .physical_min = 0,
+               .physical_max = 255,
+       },
+       {
+               .logical_min = 256,
+               .logical_max = 511,
+               .offset = 252,
+               .divider = 2,
+               .physical_min = 376,
+               .physical_max = 504,
+       },
+       {
+               .logical_min = 512,
+               .logical_max = 1023,
+               .offset = 758,
+               .divider = 4,
+               .physical_min = 884,
+               .physical_max = 1012,
+       },
+       {
+               .logical_min = 1024,
+               .logical_max = 2047,
+               .offset = 1788,
+               .divider = 8,
+               .physical_min = 1912,
+               .physical_max = 2047,
+       },
+};
+
+/* Write registers up to 4 at a time */
+static int ov4689_write_reg(struct i2c_client *client, u16 reg, u32 len,
+                           u32 val)
+{
+       u32 buf_i, val_i;
+       __be32 val_be;
+       u8 *val_p;
+       u8 buf[6];
+
+       if (len > 4)
+               return -EINVAL;
+
+       buf[0] = reg >> 8;
+       buf[1] = reg & 0xff;
+
+       val_be = cpu_to_be32(val);
+       val_p = (u8 *)&val_be;
+       buf_i = 2;
+       val_i = 4 - len;
+
+       while (val_i < 4)
+               buf[buf_i++] = val_p[val_i++];
+
+       if (i2c_master_send(client, buf, len + 2) != len + 2)
+               return -EIO;
+
+       return 0;
+}
+
+static int ov4689_write_array(struct i2c_client *client,
+                             const struct regval *regs)
+{
+       int ret = 0;
+       u32 i;
+
+       for (i = 0; ret == 0 && regs[i].addr != REG_NULL; i++)
+               ret = ov4689_write_reg(client, regs[i].addr,
+                                      OV4689_REG_VALUE_08BIT, regs[i].val);
+
+       return ret;
+}
+
+/* Read registers up to 4 at a time */
+static int ov4689_read_reg(struct i2c_client *client, u16 reg, unsigned int len,
+                          u32 *val)
+{
+       __be16 reg_addr_be = cpu_to_be16(reg);
+       struct i2c_msg msgs[2];
+       __be32 data_be = 0;
+       u8 *data_be_p;
+       int ret;
+
+       if (len > 4 || !len)
+               return -EINVAL;
+
+       data_be_p = (u8 *)&data_be;
+       /* Write register address */
+       msgs[0].addr = client->addr;
+       msgs[0].flags = 0;
+       msgs[0].len = 2;
+       msgs[0].buf = (u8 *)&reg_addr_be;
+
+       /* Read data from register */
+       msgs[1].addr = client->addr;
+       msgs[1].flags = I2C_M_RD;
+       msgs[1].len = len;
+       msgs[1].buf = &data_be_p[4 - len];
+
+       ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+       if (ret != ARRAY_SIZE(msgs))
+               return -EIO;
+
+       *val = be32_to_cpu(data_be);
+
+       return 0;
+}
+
+static void ov4689_fill_fmt(const struct ov4689_mode *mode,
+                           struct v4l2_mbus_framefmt *fmt)
+{
+       fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10;
+       fmt->width = mode->width;
+       fmt->height = mode->height;
+       fmt->field = V4L2_FIELD_NONE;
+}
+
+static int ov4689_set_fmt(struct v4l2_subdev *sd,
+                         struct v4l2_subdev_state *sd_state,
+                         struct v4l2_subdev_format *fmt)
+{
+       struct v4l2_mbus_framefmt *mbus_fmt = &fmt->format;
+       struct ov4689 *ov4689 = to_ov4689(sd);
+
+       /* only one mode supported for now */
+       ov4689_fill_fmt(ov4689->cur_mode, mbus_fmt);
+
+       return 0;
+}
+
+static int ov4689_get_fmt(struct v4l2_subdev *sd,
+                         struct v4l2_subdev_state *sd_state,
+                         struct v4l2_subdev_format *fmt)
+{
+       struct v4l2_mbus_framefmt *mbus_fmt = &fmt->format;
+       struct ov4689 *ov4689 = to_ov4689(sd);
+
+       /* only one mode supported for now */
+       ov4689_fill_fmt(ov4689->cur_mode, mbus_fmt);
+
+       return 0;
+}
+
+static int ov4689_enum_mbus_code(struct v4l2_subdev *sd,
+                                struct v4l2_subdev_state *sd_state,
+                                struct v4l2_subdev_mbus_code_enum *code)
+{
+       if (code->index != 0)
+               return -EINVAL;
+       code->code = MEDIA_BUS_FMT_SBGGR10_1X10;
+
+       return 0;
+}
+
+static int ov4689_enum_frame_sizes(struct v4l2_subdev *sd,
+                                  struct v4l2_subdev_state *sd_state,
+                                  struct v4l2_subdev_frame_size_enum *fse)
+{
+       if (fse->index >= ARRAY_SIZE(supported_modes))
+               return -EINVAL;
+
+       if (fse->code != MEDIA_BUS_FMT_SBGGR10_1X10)
+               return -EINVAL;
+
+       fse->min_width = supported_modes[fse->index].width;
+       fse->max_width = supported_modes[fse->index].width;
+       fse->max_height = supported_modes[fse->index].height;
+       fse->min_height = supported_modes[fse->index].height;
+
+       return 0;
+}
+
+static int ov4689_enable_test_pattern(struct ov4689 *ov4689, u32 pattern)
+{
+       u32 val;
+
+       if (pattern)
+               val = (pattern - 1) | OV4689_TEST_PATTERN_ENABLE;
+       else
+               val = OV4689_TEST_PATTERN_DISABLE;
+
+       return ov4689_write_reg(ov4689->client, OV4689_REG_TEST_PATTERN,
+                               OV4689_REG_VALUE_08BIT, val);
+}
+
+static int ov4689_get_selection(struct v4l2_subdev *sd,
+                               struct v4l2_subdev_state *state,
+                               struct v4l2_subdev_selection *sel)
+{
+       const struct ov4689_mode *mode = to_ov4689(sd)->cur_mode;
+
+       if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE)
+               return -EINVAL;
+
+       switch (sel->target) {
+       case V4L2_SEL_TGT_CROP_BOUNDS:
+               sel->r.top = 0;
+               sel->r.left = 0;
+               sel->r.width = mode->sensor_width;
+               sel->r.height = mode->sensor_height;
+               return 0;
+       case V4L2_SEL_TGT_CROP:
+       case V4L2_SEL_TGT_CROP_DEFAULT:
+               sel->r.top = mode->crop_top;
+               sel->r.left = mode->crop_left;
+               sel->r.width = mode->width;
+               sel->r.height = mode->height;
+               return 0;
+       }
+
+       return -EINVAL;
+}
+
+static int ov4689_s_stream(struct v4l2_subdev *sd, int on)
+{
+       struct ov4689 *ov4689 = to_ov4689(sd);
+       struct i2c_client *client = ov4689->client;
+       int ret = 0;
+
+       mutex_lock(&ov4689->mutex);
+
+       on = !!on;
+       if (on == ov4689->streaming)
+               goto unlock_and_return;
+
+       if (on) {
+               ret = pm_runtime_resume_and_get(&client->dev);
+               if (ret < 0)
+                       goto unlock_and_return;
+
+               ret = ov4689_write_array(ov4689->client,
+                                        ov4689->cur_mode->reg_list);
+               if (ret) {
+                       pm_runtime_put(&client->dev);
+                       goto unlock_and_return;
+               }
+
+               ret = __v4l2_ctrl_handler_setup(&ov4689->ctrl_handler);
+               if (ret) {
+                       pm_runtime_put(&client->dev);
+                       goto unlock_and_return;
+               }
+
+               ret = ov4689_write_reg(ov4689->client, OV4689_REG_CTRL_MODE,
+                                      OV4689_REG_VALUE_08BIT,
+                                      OV4689_MODE_STREAMING);
+               if (ret) {
+                       pm_runtime_put(&client->dev);
+                       goto unlock_and_return;
+               }
+       } else {
+               ov4689_write_reg(ov4689->client, OV4689_REG_CTRL_MODE,
+                                OV4689_REG_VALUE_08BIT,
+                                OV4689_MODE_SW_STANDBY);
+               pm_runtime_put(&client->dev);
+       }
+
+       ov4689->streaming = on;
+
+unlock_and_return:
+       mutex_unlock(&ov4689->mutex);
+
+       return ret;
+}
+
+/* Calculate the delay in us by clock rate and clock cycles */
+static inline u32 ov4689_cal_delay(struct ov4689 *ov4689, u32 cycles)
+{
+       return DIV_ROUND_UP(cycles * 1000,
+                           DIV_ROUND_UP(ov4689->clock_rate, 1000));
+}
+
+static int __maybe_unused ov4689_power_on(struct device *dev)
+{
+       struct v4l2_subdev *sd = dev_get_drvdata(dev);
+       struct ov4689 *ov4689 = to_ov4689(sd);
+       u32 delay_us;
+       int ret;
+
+       ret = clk_prepare_enable(ov4689->xvclk);
+       if (ret < 0) {
+               dev_err(dev, "Failed to enable xvclk\n");
+               return ret;
+       }
+
+       gpiod_set_value_cansleep(ov4689->reset_gpio, 1);
+
+       ret = regulator_bulk_enable(ARRAY_SIZE(ov4689_supply_names),
+                                   ov4689->supplies);
+       if (ret < 0) {
+               dev_err(dev, "Failed to enable regulators\n");
+               goto disable_clk;
+       }
+
+       gpiod_set_value_cansleep(ov4689->reset_gpio, 0);
+       usleep_range(500, 1000);
+       gpiod_set_value_cansleep(ov4689->pwdn_gpio, 0);
+
+       /* 8192 cycles prior to first SCCB transaction */
+       delay_us = ov4689_cal_delay(ov4689, 8192);
+       usleep_range(delay_us, delay_us * 2);
+
+       return 0;
+
+disable_clk:
+       clk_disable_unprepare(ov4689->xvclk);
+
+       return ret;
+}
+
+static int __maybe_unused ov4689_power_off(struct device *dev)
+{
+       struct v4l2_subdev *sd = dev_get_drvdata(dev);
+       struct ov4689 *ov4689 = to_ov4689(sd);
+
+       gpiod_set_value_cansleep(ov4689->pwdn_gpio, 1);
+       clk_disable_unprepare(ov4689->xvclk);
+       gpiod_set_value_cansleep(ov4689->reset_gpio, 1);
+       regulator_bulk_disable(ARRAY_SIZE(ov4689_supply_names),
+                              ov4689->supplies);
+       return 0;
+}
+
+static int ov4689_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+       struct ov4689 *ov4689 = to_ov4689(sd);
+       struct v4l2_mbus_framefmt *try_fmt;
+
+       mutex_lock(&ov4689->mutex);
+
+       try_fmt = v4l2_subdev_get_try_format(sd, fh->state, 0);
+       /* Initialize try_fmt */
+       ov4689_fill_fmt(&supported_modes[OV4689_MODE_2688_1520], try_fmt);
+
+       mutex_unlock(&ov4689->mutex);
+
+       return 0;
+}
+
+static const struct dev_pm_ops ov4689_pm_ops = {
+       SET_RUNTIME_PM_OPS(ov4689_power_off, ov4689_power_on, NULL)
+};
+
+static const struct v4l2_subdev_internal_ops ov4689_internal_ops = {
+       .open = ov4689_open,
+};
+
+static const struct v4l2_subdev_video_ops ov4689_video_ops = {
+       .s_stream = ov4689_s_stream,
+};
+
+static const struct v4l2_subdev_pad_ops ov4689_pad_ops = {
+       .enum_mbus_code = ov4689_enum_mbus_code,
+       .enum_frame_size = ov4689_enum_frame_sizes,
+       .get_fmt = ov4689_get_fmt,
+       .set_fmt = ov4689_set_fmt,
+       .get_selection = ov4689_get_selection,
+};
+
+static const struct v4l2_subdev_ops ov4689_subdev_ops = {
+       .video = &ov4689_video_ops,
+       .pad = &ov4689_pad_ops,
+};
+
+/*
+ * Map userspace (logical) gain to sensor (physical) gain using
+ * ov4689_gain_ranges table.
+ */
+static int ov4689_map_gain(struct ov4689 *ov4689, int logical_gain, int *result)
+{
+       const struct device *dev = &ov4689->client->dev;
+       const struct ov4689_gain_range *range;
+       unsigned int n;
+
+       for (n = 0; n < ARRAY_SIZE(ov4689_gain_ranges); n++) {
+               if (logical_gain >= ov4689_gain_ranges[n].logical_min &&
+                   logical_gain <= ov4689_gain_ranges[n].logical_max)
+                       break;
+       }
+
+       if (n == ARRAY_SIZE(ov4689_gain_ranges)) {
+               dev_warn_ratelimited(dev, "no mapping found for gain %d\n",
+                                    logical_gain);
+               return -EINVAL;
+       }
+
+       range = &ov4689_gain_ranges[n];
+
+       *result = clamp(range->offset + (logical_gain) / range->divider,
+                       range->physical_min, range->physical_max);
+       return 0;
+}
+
+static int ov4689_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+       struct ov4689 *ov4689 =
+               container_of(ctrl->handler, struct ov4689, ctrl_handler);
+       struct i2c_client *client = ov4689->client;
+       int sensor_gain;
+       s64 max_expo;
+       int ret;
+
+       /* Propagate change of current control to all related controls */
+       switch (ctrl->id) {
+       case V4L2_CID_VBLANK:
+               /* Update max exposure while meeting expected vblanking */
+               max_expo = ov4689->cur_mode->height + ctrl->val - 4;
+               __v4l2_ctrl_modify_range(ov4689->exposure,
+                                        ov4689->exposure->minimum, max_expo,
+                                        ov4689->exposure->step,
+                                        ov4689->exposure->default_value);
+               break;
+       }
+
+       if (!pm_runtime_get_if_in_use(&client->dev))
+               return 0;
+
+       switch (ctrl->id) {
+       case V4L2_CID_EXPOSURE:
+               /* 4 least significant bits of expsoure are fractional part */
+               ret = ov4689_write_reg(ov4689->client, OV4689_REG_EXPOSURE,
+                                      OV4689_REG_VALUE_24BIT, ctrl->val << 4);
+               break;
+       case V4L2_CID_ANALOGUE_GAIN:
+               ret = ov4689_map_gain(ov4689, ctrl->val, &sensor_gain);
+
+               ret = ret ?:
+                       ov4689_write_reg(ov4689->client, OV4689_REG_GAIN_H,
+                                        OV4689_REG_VALUE_08BIT,
+                                        (sensor_gain >> OV4689_GAIN_H_SHIFT) &
+                                        OV4689_GAIN_H_MASK);
+               ret = ret ?:
+                       ov4689_write_reg(ov4689->client, OV4689_REG_GAIN_L,
+                                        OV4689_REG_VALUE_08BIT,
+                                        sensor_gain & OV4689_GAIN_L_MASK);
+               break;
+       case V4L2_CID_VBLANK:
+               ret = ov4689_write_reg(ov4689->client, OV4689_REG_VTS,
+                                      OV4689_REG_VALUE_16BIT,
+                                      ctrl->val + ov4689->cur_mode->height);
+               break;
+       case V4L2_CID_TEST_PATTERN:
+               ret = ov4689_enable_test_pattern(ov4689, ctrl->val);
+               break;
+       default:
+               dev_warn(&client->dev, "%s Unhandled id:0x%x, val:0x%x\n",
+                        __func__, ctrl->id, ctrl->val);
+               ret = -EINVAL;
+               break;
+       }
+
+       pm_runtime_put(&client->dev);
+
+       return ret;
+}
+
+static const struct v4l2_ctrl_ops ov4689_ctrl_ops = {
+       .s_ctrl = ov4689_set_ctrl,
+};
+
+static int ov4689_initialize_controls(struct ov4689 *ov4689)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(&ov4689->subdev);
+       struct v4l2_fwnode_device_properties props;
+       struct v4l2_ctrl_handler *handler;
+       const struct ov4689_mode *mode;
+       s64 exposure_max, vblank_def;
+       struct v4l2_ctrl *ctrl;
+       s64 h_blank_def;
+       int ret;
+
+       handler = &ov4689->ctrl_handler;
+       mode = ov4689->cur_mode;
+       ret = v4l2_ctrl_handler_init(handler, 10);
+       if (ret)
+               return ret;
+       handler->lock = &ov4689->mutex;
+
+       ctrl = v4l2_ctrl_new_int_menu(handler, NULL, V4L2_CID_LINK_FREQ, 0, 0,
+                                     link_freq_menu_items);
+       if (ctrl)
+               ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+       v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE, 0,
+                         mode->pixel_rate, 1, mode->pixel_rate);
+
+       h_blank_def = mode->hts_def - mode->width;
+       ctrl = v4l2_ctrl_new_std(handler, NULL, V4L2_CID_HBLANK, h_blank_def,
+                                h_blank_def, 1, h_blank_def);
+       if (ctrl)
+               ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+       vblank_def = mode->vts_def - mode->height;
+       v4l2_ctrl_new_std(handler, &ov4689_ctrl_ops, V4L2_CID_VBLANK,
+                         vblank_def, OV4689_VTS_MAX - mode->height, 1,
+                         vblank_def);
+
+       exposure_max = mode->vts_def - 4;
+       ov4689->exposure =
+               v4l2_ctrl_new_std(handler, &ov4689_ctrl_ops, V4L2_CID_EXPOSURE,
+                                 OV4689_EXPOSURE_MIN, exposure_max,
+                                 OV4689_EXPOSURE_STEP, mode->exp_def);
+
+       v4l2_ctrl_new_std(handler, &ov4689_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
+                         ov4689_gain_ranges[0].logical_min,
+                         ov4689_gain_ranges[ARRAY_SIZE(ov4689_gain_ranges) - 1]
+                                 .logical_max,
+                         OV4689_GAIN_STEP, OV4689_GAIN_DEFAULT);
+
+       v4l2_ctrl_new_std_menu_items(handler, &ov4689_ctrl_ops,
+                                    V4L2_CID_TEST_PATTERN,
+                                    ARRAY_SIZE(ov4689_test_pattern_menu) - 1,
+                                    0, 0, ov4689_test_pattern_menu);
+
+       if (handler->error) {
+               ret = handler->error;
+               dev_err(&ov4689->client->dev, "Failed to init controls(%d)\n",
+                       ret);
+               goto err_free_handler;
+       }
+
+       ret = v4l2_fwnode_device_parse(&client->dev, &props);
+       if (ret)
+               goto err_free_handler;
+
+       ret = v4l2_ctrl_new_fwnode_properties(handler, &ov4689_ctrl_ops,
+                                             &props);
+       if (ret)
+               goto err_free_handler;
+
+       ov4689->subdev.ctrl_handler = handler;
+
+       return 0;
+
+err_free_handler:
+       v4l2_ctrl_handler_free(handler);
+
+       return ret;
+}
+
+static int ov4689_check_sensor_id(struct ov4689 *ov4689,
+                                 struct i2c_client *client)
+{
+       struct device *dev = &ov4689->client->dev;
+       u32 id = 0;
+       int ret;
+
+       ret = ov4689_read_reg(client, OV4689_REG_CHIP_ID,
+                             OV4689_REG_VALUE_16BIT, &id);
+       if (ret) {
+               dev_err(dev, "Cannot read sensor ID\n");
+               return ret;
+       }
+
+       if (id != CHIP_ID) {
+               dev_err(dev, "Unexpected sensor ID %06x, expected %06x\n",
+                       id, CHIP_ID);
+               return -ENODEV;
+       }
+
+       dev_info(dev, "Detected OV%06x sensor\n", CHIP_ID);
+
+       return 0;
+}
+
+static int ov4689_configure_regulators(struct ov4689 *ov4689)
+{
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(ov4689_supply_names); i++)
+               ov4689->supplies[i].supply = ov4689_supply_names[i];
+
+       return devm_regulator_bulk_get(&ov4689->client->dev,
+                                      ARRAY_SIZE(ov4689_supply_names),
+                                      ov4689->supplies);
+}
+
+static u64 ov4689_check_link_frequency(struct v4l2_fwnode_endpoint *ep)
+{
+       const u64 *freqs = link_freq_menu_items;
+       unsigned int i, j;
+
+       for (i = 0; i < ARRAY_SIZE(link_freq_menu_items); i++) {
+               for (j = 0; j < ep->nr_of_link_frequencies; j++)
+                       if (freqs[i] == ep->link_frequencies[j])
+                               return freqs[i];
+       }
+
+       return 0;
+}
+
+static int ov4689_check_hwcfg(struct device *dev)
+{
+       struct fwnode_handle *fwnode = dev_fwnode(dev);
+       struct v4l2_fwnode_endpoint bus_cfg = {
+               .bus_type = V4L2_MBUS_CSI2_DPHY,
+       };
+       struct fwnode_handle *endpoint;
+       int ret;
+
+       endpoint = fwnode_graph_get_next_endpoint(fwnode, NULL);
+       if (!endpoint)
+               return -EINVAL;
+
+       ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &bus_cfg);
+       fwnode_handle_put(endpoint);
+       if (ret)
+               return ret;
+
+       if (bus_cfg.bus.mipi_csi2.num_data_lanes != OV4689_LANES) {
+               dev_err(dev, "Only a 4-lane CSI2 config is supported");
+               ret = -EINVAL;
+               goto out_free_bus_cfg;
+       }
+
+       if (!ov4689_check_link_frequency(&bus_cfg)) {
+               dev_err(dev, "No supported link frequency found\n");
+               ret = -EINVAL;
+       }
+
+out_free_bus_cfg:
+       v4l2_fwnode_endpoint_free(&bus_cfg);
+
+       return ret;
+}
+
+static int ov4689_probe(struct i2c_client *client)
+{
+       struct device *dev = &client->dev;
+       struct v4l2_subdev *sd;
+       struct ov4689 *ov4689;
+       int ret;
+
+       ret = ov4689_check_hwcfg(dev);
+       if (ret)
+               return ret;
+
+       ov4689 = devm_kzalloc(dev, sizeof(*ov4689), GFP_KERNEL);
+       if (!ov4689)
+               return -ENOMEM;
+
+       ov4689->client = client;
+       ov4689->cur_mode = &supported_modes[OV4689_MODE_2688_1520];
+
+       ov4689->xvclk = devm_clk_get_optional(dev, NULL);
+       if (IS_ERR(ov4689->xvclk))
+               return dev_err_probe(dev, PTR_ERR(ov4689->xvclk),
+                                    "Failed to get external clock\n");
+
+       if (!ov4689->xvclk) {
+               dev_dbg(dev,
+                       "No clock provided, using clock-frequency property\n");
+               device_property_read_u32(dev, "clock-frequency",
+                                        &ov4689->clock_rate);
+       } else {
+               ov4689->clock_rate = clk_get_rate(ov4689->xvclk);
+       }
+
+       if (ov4689->clock_rate != OV4689_XVCLK_FREQ) {
+               dev_err(dev,
+                       "External clock rate mismatch: got %d Hz, expected %d Hz\n",
+                       ov4689->clock_rate, OV4689_XVCLK_FREQ);
+               return -EINVAL;
+       }
+
+       ov4689->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+                                                    GPIOD_OUT_LOW);
+       if (IS_ERR(ov4689->reset_gpio)) {
+               dev_err(dev, "Failed to get reset-gpios\n");
+               return PTR_ERR(ov4689->reset_gpio);
+       }
+
+       ov4689->pwdn_gpio = devm_gpiod_get_optional(dev, "pwdn", GPIOD_OUT_LOW);
+       if (IS_ERR(ov4689->pwdn_gpio)) {
+               dev_err(dev, "Failed to get pwdn-gpios\n");
+               return PTR_ERR(ov4689->pwdn_gpio);
+       }
+
+       ret = ov4689_configure_regulators(ov4689);
+       if (ret)
+               return dev_err_probe(dev, ret,
+                                    "Failed to get power regulators\n");
+
+       mutex_init(&ov4689->mutex);
+
+       sd = &ov4689->subdev;
+       v4l2_i2c_subdev_init(sd, client, &ov4689_subdev_ops);
+       ret = ov4689_initialize_controls(ov4689);
+       if (ret)
+               goto err_destroy_mutex;
+
+       ret = ov4689_power_on(dev);
+       if (ret)
+               goto err_free_handler;
+
+       ret = ov4689_check_sensor_id(ov4689, client);
+       if (ret)
+               goto err_power_off;
+
+       sd->internal_ops = &ov4689_internal_ops;
+       sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+       ov4689->pad.flags = MEDIA_PAD_FL_SOURCE;
+       sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
+       ret = media_entity_pads_init(&sd->entity, 1, &ov4689->pad);
+       if (ret < 0)
+               goto err_power_off;
+
+       ret = v4l2_async_register_subdev_sensor(sd);
+       if (ret) {
+               dev_err(dev, "v4l2 async register subdev failed\n");
+               goto err_clean_entity;
+       }
+
+       pm_runtime_set_active(dev);
+       pm_runtime_enable(dev);
+       pm_runtime_idle(dev);
+
+       return 0;
+
+err_clean_entity:
+       media_entity_cleanup(&sd->entity);
+err_power_off:
+       ov4689_power_off(dev);
+err_free_handler:
+       v4l2_ctrl_handler_free(&ov4689->ctrl_handler);
+err_destroy_mutex:
+       mutex_destroy(&ov4689->mutex);
+
+       return ret;
+}
+
+static void ov4689_remove(struct i2c_client *client)
+{
+       struct v4l2_subdev *sd = i2c_get_clientdata(client);
+       struct ov4689 *ov4689 = to_ov4689(sd);
+
+       v4l2_async_unregister_subdev(sd);
+       media_entity_cleanup(&sd->entity);
+
+       v4l2_ctrl_handler_free(&ov4689->ctrl_handler);
+       mutex_destroy(&ov4689->mutex);
+
+       pm_runtime_disable(&client->dev);
+       if (!pm_runtime_status_suspended(&client->dev))
+               ov4689_power_off(&client->dev);
+       pm_runtime_set_suspended(&client->dev);
+}
+
+static const struct of_device_id ov4689_of_match[] = {
+       { .compatible = "ovti,ov4689" },
+       {},
+};
+MODULE_DEVICE_TABLE(of, ov4689_of_match);
+
+static struct i2c_driver ov4689_i2c_driver = {
+       .driver = {
+               .name = "ov4689",
+               .pm = &ov4689_pm_ops,
+               .of_match_table = ov4689_of_match,
+       },
+       .probe_new = ov4689_probe,
+       .remove = ov4689_remove,
+};
+
+module_i2c_driver(ov4689_i2c_driver);
+
+MODULE_DESCRIPTION("OmniVision ov4689 sensor driver");
+MODULE_LICENSE("GPL");
index 81e4e87e182188f3019f0fe638498eaa1e4b1a5b..47451238ca05c8ba30aaf9ce6fe902dd937dbda6 100644 (file)
@@ -1090,7 +1090,7 @@ static int ov5645_probe(struct i2c_client *client)
        }
 
        /* get system clock (xclk) */
-       ov5645->xclk = devm_clk_get(dev, "xclk");
+       ov5645->xclk = devm_clk_get(dev, NULL);
        if (IS_ERR(ov5645->xclk)) {
                dev_err(dev, "could not get xclk");
                return PTR_ERR(ov5645->xclk);
index 84604ea7bdf9ec3b37f6b6cc78698bb67cde8be3..17465fcf28e335949f070b4961177b207003deda 100644 (file)
@@ -2597,6 +2597,7 @@ static void ov5648_remove(struct i2c_client *client)
        v4l2_ctrl_handler_free(&sensor->ctrls.handler);
        mutex_destroy(&sensor->mutex);
        media_entity_cleanup(&subdev->entity);
+       v4l2_fwnode_endpoint_free(&sensor->endpoint);
 }
 
 static const struct dev_pm_ops ov5648_pm_ops = {
index 4b9b156b53c7a57968c30ed2e0287b9bcc2ba62f..11d3bef65d43ceecf342fa04ffbd2a152e32976a 100644 (file)
@@ -15,7 +15,6 @@
 #include <linux/i2c.h>
 #include <linux/delay.h>
 #include <linux/videodev2.h>
-#include <linux/gpio.h>
 #include <linux/gpio/consumer.h>
 #include <media/v4l2-device.h>
 #include <media/v4l2-event.h>
index efa18d026ac33e743f3146afe140ab512296a1f4..cf8384e09413bee6e1d0b3e155e81b694c2bf210 100644 (file)
@@ -2110,17 +2110,18 @@ static int ov8856_set_stream(struct v4l2_subdev *sd, int enable)
        return ret;
 }
 
-static int __ov8856_power_on(struct ov8856 *ov8856)
+static int ov8856_power_on(struct device *dev)
 {
-       struct i2c_client *client = v4l2_get_subdevdata(&ov8856->sd);
+       struct v4l2_subdev *sd = dev_get_drvdata(dev);
+       struct ov8856 *ov8856 = to_ov8856(sd);
        int ret;
 
-       if (is_acpi_node(dev_fwnode(&client->dev)))
+       if (is_acpi_node(dev_fwnode(dev)))
                return 0;
 
        ret = clk_prepare_enable(ov8856->xvclk);
        if (ret < 0) {
-               dev_err(&client->dev, "failed to enable xvclk\n");
+               dev_err(dev, "failed to enable xvclk\n");
                return ret;
        }
 
@@ -2132,7 +2133,7 @@ static int __ov8856_power_on(struct ov8856 *ov8856)
        ret = regulator_bulk_enable(ARRAY_SIZE(ov8856_supply_names),
                                    ov8856->supplies);
        if (ret < 0) {
-               dev_err(&client->dev, "failed to enable regulators\n");
+               dev_err(dev, "failed to enable regulators\n");
                goto disable_clk;
        }
 
@@ -2148,17 +2149,20 @@ disable_clk:
        return ret;
 }
 
-static void __ov8856_power_off(struct ov8856 *ov8856)
+static int ov8856_power_off(struct device *dev)
 {
-       struct i2c_client *client = v4l2_get_subdevdata(&ov8856->sd);
+       struct v4l2_subdev *sd = dev_get_drvdata(dev);
+       struct ov8856 *ov8856 = to_ov8856(sd);
 
-       if (is_acpi_node(dev_fwnode(&client->dev)))
-               return;
+       if (is_acpi_node(dev_fwnode(dev)))
+               return 0;
 
        gpiod_set_value_cansleep(ov8856->reset_gpio, 1);
        regulator_bulk_disable(ARRAY_SIZE(ov8856_supply_names),
                               ov8856->supplies);
        clk_disable_unprepare(ov8856->xvclk);
+
+       return 0;
 }
 
 static int __maybe_unused ov8856_suspend(struct device *dev)
@@ -2170,7 +2174,7 @@ static int __maybe_unused ov8856_suspend(struct device *dev)
        if (ov8856->streaming)
                ov8856_stop_streaming(ov8856);
 
-       __ov8856_power_off(ov8856);
+       ov8856_power_off(dev);
        mutex_unlock(&ov8856->mutex);
 
        return 0;
@@ -2184,7 +2188,7 @@ static int __maybe_unused ov8856_resume(struct device *dev)
 
        mutex_lock(&ov8856->mutex);
 
-       __ov8856_power_on(ov8856);
+       ov8856_power_on(dev);
        if (ov8856->streaming) {
                ret = ov8856_start_streaming(ov8856);
                if (ret) {
@@ -2451,7 +2455,7 @@ static void ov8856_remove(struct i2c_client *client)
        pm_runtime_disable(&client->dev);
        mutex_destroy(&ov8856->mutex);
 
-       __ov8856_power_off(ov8856);
+       ov8856_power_off(&client->dev);
 }
 
 static int ov8856_probe(struct i2c_client *client)
@@ -2475,7 +2479,7 @@ static int ov8856_probe(struct i2c_client *client)
 
        full_power = acpi_dev_state_d0(&client->dev);
        if (full_power) {
-               ret = __ov8856_power_on(ov8856);
+               ret = ov8856_power_on(&client->dev);
                if (ret) {
                        dev_err(&client->dev, "failed to power on\n");
                        return ret;
@@ -2531,13 +2535,14 @@ probe_error_v4l2_ctrl_handler_free:
        mutex_destroy(&ov8856->mutex);
 
 probe_power_off:
-       __ov8856_power_off(ov8856);
+       ov8856_power_off(&client->dev);
 
        return ret;
 }
 
 static const struct dev_pm_ops ov8856_pm_ops = {
        SET_SYSTEM_SLEEP_PM_OPS(ov8856_suspend, ov8856_resume)
+       SET_RUNTIME_PM_OPS(ov8856_power_off, ov8856_power_on, NULL)
 };
 
 #ifdef CONFIG_ACPI
index 4d458993e6d672f28f6e1cd09d56dd4546bd4084..7e7cb1e4520e07b134809c59047e8e6ee397bb30 100644 (file)
@@ -10,7 +10,6 @@
  */
 #include <linux/clk.h>
 #include <linux/delay.h>
-#include <linux/gpio.h>
 #include <linux/gpio/consumer.h>
 #include <linux/i2c.h>
 #include <linux/kernel.h>
@@ -30,7 +29,6 @@
 #include <media/v4l2-image-sizes.h>
 #include <media/v4l2-subdev.h>
 #include <media/v4l2-mediabus.h>
-#include <media/i2c/ov9650.h>
 
 static int debug;
 module_param(debug, int, 0644);
@@ -1402,38 +1400,6 @@ static const struct v4l2_subdev_ops ov965x_subdev_ops = {
        .video = &ov965x_video_ops,
 };
 
-/*
- * Reset and power down GPIOs configuration
- */
-static int ov965x_configure_gpios_pdata(struct ov965x *ov965x,
-                               const struct ov9650_platform_data *pdata)
-{
-       int ret, i;
-       int gpios[NUM_GPIOS];
-       struct device *dev = regmap_get_device(ov965x->regmap);
-
-       gpios[GPIO_PWDN] = pdata->gpio_pwdn;
-       gpios[GPIO_RST]  = pdata->gpio_reset;
-
-       for (i = 0; i < ARRAY_SIZE(ov965x->gpios); i++) {
-               int gpio = gpios[i];
-
-               if (!gpio_is_valid(gpio))
-                       continue;
-               ret = devm_gpio_request_one(dev, gpio,
-                                           GPIOF_OUT_INIT_HIGH, "OV965X");
-               if (ret < 0)
-                       return ret;
-               v4l2_dbg(1, debug, &ov965x->sd, "set gpio %d to 1\n", gpio);
-
-               gpio_set_value_cansleep(gpio, 1);
-               gpio_export(gpio, 0);
-               ov965x->gpios[i] = gpio_to_desc(gpio);
-       }
-
-       return 0;
-}
-
 static int ov965x_configure_gpios(struct ov965x *ov965x)
 {
        struct device *dev = regmap_get_device(ov965x->regmap);
@@ -1493,7 +1459,6 @@ out:
 
 static int ov965x_probe(struct i2c_client *client)
 {
-       const struct ov9650_platform_data *pdata = client->dev.platform_data;
        struct v4l2_subdev *sd;
        struct ov965x *ov965x;
        int ret;
@@ -1513,17 +1478,7 @@ static int ov965x_probe(struct i2c_client *client)
                return PTR_ERR(ov965x->regmap);
        }
 
-       if (pdata) {
-               if (pdata->mclk_frequency == 0) {
-                       dev_err(&client->dev, "MCLK frequency not specified\n");
-                       return -EINVAL;
-               }
-               ov965x->mclk_frequency = pdata->mclk_frequency;
-
-               ret = ov965x_configure_gpios_pdata(ov965x, pdata);
-               if (ret < 0)
-                       return ret;
-       } else if (dev_fwnode(&client->dev)) {
+       if (dev_fwnode(&client->dev)) {
                ov965x->clk = devm_clk_get(&client->dev, NULL);
                if (IS_ERR(ov965x->clk))
                        return PTR_ERR(ov965x->clk);
@@ -1534,7 +1489,7 @@ static int ov965x_probe(struct i2c_client *client)
                        return ret;
        } else {
                dev_err(&client->dev,
-                       "Neither platform data nor device property specified\n");
+                       "No device properties specified\n");
 
                return -EINVAL;
        }
diff --git a/drivers/media/i2c/st-vgxy61.c b/drivers/media/i2c/st-vgxy61.c
new file mode 100644 (file)
index 0000000..dfbf253
--- /dev/null
@@ -0,0 +1,1962 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for VGXY61 global shutter sensor family driver
+ *
+ * Copyright (C) 2022 STMicroelectronics SA
+ */
+
+#include <asm-generic/unaligned.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <linux/units.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 VGXY61_REG_8BIT(n)                             ((1 << 16) | (n))
+#define VGXY61_REG_16BIT(n)                            ((2 << 16) | (n))
+#define VGXY61_REG_32BIT(n)                            ((4 << 16) | (n))
+#define VGXY61_REG_SIZE_SHIFT                          16
+#define VGXY61_REG_ADDR_MASK                           0xffff
+
+#define VGXY61_REG_MODEL_ID                            VGXY61_REG_16BIT(0x0000)
+#define VG5661_MODEL_ID                                        0x5661
+#define VG5761_MODEL_ID                                        0x5761
+#define VGXY61_REG_REVISION                            VGXY61_REG_16BIT(0x0002)
+#define VGXY61_REG_FWPATCH_REVISION                    VGXY61_REG_16BIT(0x0014)
+#define VGXY61_REG_FWPATCH_START_ADDR                  VGXY61_REG_8BIT(0x2000)
+#define VGXY61_REG_SYSTEM_FSM                          VGXY61_REG_8BIT(0x0020)
+#define VGXY61_SYSTEM_FSM_SW_STBY                      0x03
+#define VGXY61_SYSTEM_FSM_STREAMING                    0x04
+#define VGXY61_REG_NVM                                 VGXY61_REG_8BIT(0x0023)
+#define VGXY61_NVM_OK                                  0x04
+#define VGXY61_REG_STBY                                        VGXY61_REG_8BIT(0x0201)
+#define VGXY61_STBY_NO_REQ                             0
+#define VGXY61_STBY_REQ_TMP_READ                       BIT(2)
+#define VGXY61_REG_STREAMING                           VGXY61_REG_8BIT(0x0202)
+#define VGXY61_STREAMING_NO_REQ                                0
+#define VGXY61_STREAMING_REQ_STOP                      BIT(0)
+#define VGXY61_STREAMING_REQ_START                     BIT(1)
+#define VGXY61_REG_EXT_CLOCK                           VGXY61_REG_32BIT(0x0220)
+#define VGXY61_REG_CLK_PLL_PREDIV                      VGXY61_REG_8BIT(0x0224)
+#define VGXY61_REG_CLK_SYS_PLL_MULT                    VGXY61_REG_8BIT(0x0225)
+#define VGXY61_REG_GPIO_0_CTRL                         VGXY61_REG_8BIT(0x0236)
+#define VGXY61_REG_GPIO_1_CTRL                         VGXY61_REG_8BIT(0x0237)
+#define VGXY61_REG_GPIO_2_CTRL                         VGXY61_REG_8BIT(0x0238)
+#define VGXY61_REG_GPIO_3_CTRL                         VGXY61_REG_8BIT(0x0239)
+#define VGXY61_REG_SIGNALS_POLARITY_CTRL               VGXY61_REG_8BIT(0x023b)
+#define VGXY61_REG_LINE_LENGTH                         VGXY61_REG_16BIT(0x0300)
+#define VGXY61_REG_ORIENTATION                         VGXY61_REG_8BIT(0x0302)
+#define VGXY61_REG_VT_CTRL                             VGXY61_REG_8BIT(0x0304)
+#define VGXY61_REG_FORMAT_CTRL                         VGXY61_REG_8BIT(0x0305)
+#define VGXY61_REG_OIF_CTRL                            VGXY61_REG_16BIT(0x0306)
+#define VGXY61_REG_OIF_ROI0_CTRL                       VGXY61_REG_8BIT(0x030a)
+#define VGXY61_REG_ROI0_START_H                                VGXY61_REG_16BIT(0x0400)
+#define VGXY61_REG_ROI0_START_V                                VGXY61_REG_16BIT(0x0402)
+#define VGXY61_REG_ROI0_END_H                          VGXY61_REG_16BIT(0x0404)
+#define VGXY61_REG_ROI0_END_V                          VGXY61_REG_16BIT(0x0406)
+#define VGXY61_REG_PATGEN_CTRL                         VGXY61_REG_32BIT(0x0440)
+#define VGXY61_PATGEN_LONG_ENABLE                      BIT(16)
+#define VGXY61_PATGEN_SHORT_ENABLE                     BIT(0)
+#define VGXY61_PATGEN_LONG_TYPE_SHIFT                  18
+#define VGXY61_PATGEN_SHORT_TYPE_SHIFT                 4
+#define VGXY61_REG_FRAME_CONTENT_CTRL                  VGXY61_REG_8BIT(0x0478)
+#define VGXY61_REG_COARSE_EXPOSURE_LONG                        VGXY61_REG_16BIT(0x0500)
+#define VGXY61_REG_COARSE_EXPOSURE_SHORT               VGXY61_REG_16BIT(0x0504)
+#define VGXY61_REG_ANALOG_GAIN                         VGXY61_REG_8BIT(0x0508)
+#define VGXY61_REG_DIGITAL_GAIN_LONG                   VGXY61_REG_16BIT(0x050a)
+#define VGXY61_REG_DIGITAL_GAIN_SHORT                  VGXY61_REG_16BIT(0x0512)
+#define VGXY61_REG_FRAME_LENGTH                                VGXY61_REG_16BIT(0x051a)
+#define VGXY61_REG_SIGNALS_CTRL                                VGXY61_REG_16BIT(0x0522)
+#define VGXY61_SIGNALS_GPIO_ID_SHIFT                   4
+#define VGXY61_REG_READOUT_CTRL                                VGXY61_REG_8BIT(0x0530)
+#define VGXY61_REG_HDR_CTRL                            VGXY61_REG_8BIT(0x0532)
+#define VGXY61_REG_PATGEN_LONG_DATA_GR                 VGXY61_REG_16BIT(0x092c)
+#define VGXY61_REG_PATGEN_LONG_DATA_R                  VGXY61_REG_16BIT(0x092e)
+#define VGXY61_REG_PATGEN_LONG_DATA_B                  VGXY61_REG_16BIT(0x0930)
+#define VGXY61_REG_PATGEN_LONG_DATA_GB                 VGXY61_REG_16BIT(0x0932)
+#define VGXY61_REG_PATGEN_SHORT_DATA_GR                        VGXY61_REG_16BIT(0x0950)
+#define VGXY61_REG_PATGEN_SHORT_DATA_R                 VGXY61_REG_16BIT(0x0952)
+#define VGXY61_REG_PATGEN_SHORT_DATA_B                 VGXY61_REG_16BIT(0x0954)
+#define VGXY61_REG_PATGEN_SHORT_DATA_GB                        VGXY61_REG_16BIT(0x0956)
+#define VGXY61_REG_BYPASS_CTRL                         VGXY61_REG_8BIT(0x0a60)
+
+#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_FRAME_LENGTH_DEF                                1313
+#define VGXY61_MIN_FRAME_LENGTH                                1288
+#define VGXY61_MIN_EXPOSURE                            10
+#define VGXY61_HDR_LINEAR_RATIO                                10
+#define VGXY61_TIMEOUT_MS                              500
+#define VGXY61_MEDIA_BUS_FMT_DEF                       MEDIA_BUS_FMT_Y8_1X8
+
+#define VGXY61_FWPATCH_REVISION_MAJOR                  2
+#define VGXY61_FWPATCH_REVISION_MINOR                  0
+#define VGXY61_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_mode_menu[] = {
+       "HDR linearize",
+       "HDR substraction",
+       "No HDR",
+};
+
+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
+};
+
+enum vgxy61_bin_mode {
+       VGXY61_BIN_MODE_NORMAL,
+       VGXY61_BIN_MODE_DIGITAL_X2,
+       VGXY61_BIN_MODE_DIGITAL_X4,
+};
+
+enum vgxy61_hdr_mode {
+       VGXY61_HDR_LINEAR,
+       VGXY61_HDR_SUB,
+       VGXY61_NO_HDR,
+};
+
+enum vgxy61_strobe_mode {
+       VGXY61_STROBE_DISABLED,
+       VGXY61_STROBE_LONG,
+       VGXY61_STROBE_ENABLED,
+};
+
+struct vgxy61_mode_info {
+       u32 width;
+       u32 height;
+       enum vgxy61_bin_mode bin_mode;
+       struct v4l2_rect crop;
+};
+
+struct vgxy61_fmt_desc {
+       u32 code;
+       u8 bpp;
+       u8 data_type;
+};
+
+static const struct vgxy61_fmt_desc vgxy61_supported_codes[] = {
+       {
+               .code = MEDIA_BUS_FMT_Y8_1X8,
+               .bpp = 8,
+               .data_type = MIPI_CSI2_DT_RAW8,
+       },
+       {
+               .code = MEDIA_BUS_FMT_Y10_1X10,
+               .bpp = 10,
+               .data_type = MIPI_CSI2_DT_RAW10,
+       },
+       {
+               .code = MEDIA_BUS_FMT_Y12_1X12,
+               .bpp = 12,
+               .data_type = MIPI_CSI2_DT_RAW12,
+       },
+       {
+               .code = MEDIA_BUS_FMT_Y14_1X14,
+               .bpp = 14,
+               .data_type = MIPI_CSI2_DT_RAW14,
+       },
+       {
+               .code = MEDIA_BUS_FMT_Y16_1X16,
+               .bpp = 16,
+               .data_type = MIPI_CSI2_DT_RAW16,
+       },
+};
+
+static const struct vgxy61_mode_info vgx661_mode_data[] = {
+       {
+               .width = VGX661_WIDTH,
+               .height = VGX661_HEIGHT,
+               .bin_mode = VGXY61_BIN_MODE_NORMAL,
+               .crop = {
+                       .left = 0,
+                       .top = 0,
+                       .width = VGX661_WIDTH,
+                       .height = VGX661_HEIGHT,
+               },
+       },
+       {
+               .width = 1280,
+               .height = 720,
+               .bin_mode = VGXY61_BIN_MODE_NORMAL,
+               .crop = {
+                       .left = 92,
+                       .top = 192,
+                       .width = 1280,
+                       .height = 720,
+               },
+       },
+       {
+               .width = 640,
+               .height = 480,
+               .bin_mode = VGXY61_BIN_MODE_DIGITAL_X2,
+               .crop = {
+                       .left = 92,
+                       .top = 72,
+                       .width = 1280,
+                       .height = 960,
+               },
+       },
+       {
+               .width = 320,
+               .height = 240,
+               .bin_mode = VGXY61_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 = VGXY61_BIN_MODE_NORMAL,
+               .crop = {
+                       .left = 0,
+                       .top = 0,
+                       .width = VGX761_WIDTH,
+                       .height = VGX761_HEIGHT,
+               },
+       },
+       {
+               .width = 1920,
+               .height = 1080,
+               .bin_mode = VGXY61_BIN_MODE_NORMAL,
+               .crop = {
+                       .left = 12,
+                       .top = 62,
+                       .width = 1920,
+                       .height = 1080,
+               },
+       },
+       {
+               .width = 1280,
+               .height = 720,
+               .bin_mode = VGXY61_BIN_MODE_NORMAL,
+               .crop = {
+                       .left = 332,
+                       .top = 242,
+                       .width = 1280,
+                       .height = 720,
+               },
+       },
+       {
+               .width = 640,
+               .height = 480,
+               .bin_mode = VGXY61_BIN_MODE_DIGITAL_X2,
+               .crop = {
+                       .left = 332,
+                       .top = 122,
+                       .width = 1280,
+                       .height = 960,
+               },
+       },
+       {
+               .width = 320,
+               .height = 240,
+               .bin_mode = VGXY61_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[ARRAY_SIZE(vgxy61_supply_name)];
+       struct gpio_desc *reset_gpio;
+       struct clk *xclk;
+       u32 clk_freq;
+       u16 id;
+       u16 sensor_width;
+       u16 sensor_height;
+       u16 oif_ctrl;
+       unsigned int nb_of_lane;
+       u32 data_rate_in_mbps;
+       u32 pclk;
+       u16 line_length;
+       u16 rot_term;
+       bool gpios_polarity;
+       /* 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;
+       unsigned int sensor_modes_nb;
+       const struct vgxy61_mode_info *default_mode;
+       const struct vgxy61_mode_info *current_mode;
+       bool hflip;
+       bool vflip;
+       enum vgxy61_hdr_mode hdr;
+       u16 expo_long;
+       u16 expo_short;
+       u16 expo_max;
+       u16 expo_min;
+       u16 vblank;
+       u16 vblank_min;
+       u16 frame_length;
+       u16 digital_gain;
+       u8 analog_gain;
+       enum vgxy61_strobe_mode strobe_mode;
+       u32 pattern;
+};
+
+static u8 get_bpp_by_code(__u32 code)
+{
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(vgxy61_supported_codes); i++) {
+               if (vgxy61_supported_codes[i].code == code)
+                       return vgxy61_supported_codes[i].bpp;
+       }
+       /* Should never happen */
+       WARN(1, "Unsupported code %d. default to 8 bpp", code);
+       return 8;
+}
+
+static u8 get_data_type_by_code(__u32 code)
+{
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(vgxy61_supported_codes); i++) {
+               if (vgxy61_supported_codes[i].code == code)
+                       return vgxy61_supported_codes[i].data_type;
+       }
+       /* 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, u8 *prediv, u8 *mult)
+{
+       const unsigned int predivs[] = {1, 2, 4};
+       unsigned 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 unsigned 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, u32 reg,
+                               unsigned int len)
+{
+       struct i2c_client *client = sensor->i2c_client;
+       struct i2c_msg msg[2];
+       u8 buf[2];
+       u8 val[sizeof(u32)] = {0};
+       int ret;
+
+       if (len > sizeof(u32))
+               return -EINVAL;
+       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 get_unaligned_le32(val);
+}
+
+static inline int vgxy61_read_reg(struct vgxy61_dev *sensor, u32 reg)
+{
+       return vgxy61_read_multiple(sensor, reg & VGXY61_REG_ADDR_MASK,
+                                    (reg >> VGXY61_REG_SIZE_SHIFT) & 7);
+}
+
+static int vgxy61_write_multiple(struct vgxy61_dev *sensor, u32 reg,
+                                const u8 *data, unsigned int len, int *err)
+{
+       struct i2c_client *client = sensor->i2c_client;
+       struct i2c_msg msg;
+       u8 buf[VGXY61_WRITE_MULTIPLE_CHUNK_MAX + 2];
+       unsigned int i;
+       int ret;
+
+       if (err && *err)
+               return *err;
+
+       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);
+               if (err)
+                       *err = ret;
+               return ret;
+       }
+
+       return 0;
+}
+
+static int vgxy61_write_array(struct vgxy61_dev *sensor, u32 reg,
+                             unsigned int nb, const u8 *array)
+{
+       const unsigned int chunk_size = get_chunk_size(sensor);
+       int ret;
+       unsigned int sz;
+
+       while (nb) {
+               sz = min(nb, chunk_size);
+               ret = vgxy61_write_multiple(sensor, reg, array, sz, NULL);
+               if (ret < 0)
+                       return ret;
+               nb -= sz;
+               reg += sz;
+               array += sz;
+       }
+
+       return 0;
+}
+
+static inline int vgxy61_write_reg(struct vgxy61_dev *sensor, u32 reg, u32 val,
+                                  int *err)
+{
+       return vgxy61_write_multiple(sensor, reg & VGXY61_REG_ADDR_MASK,
+                                    (u8 *)&val,
+                                    (reg >> VGXY61_REG_SIZE_SHIFT) & 7, err);
+}
+
+static int vgxy61_poll_reg(struct vgxy61_dev *sensor, u32 reg, u8 poll_val,
+                          unsigned int timeout_ms)
+{
+       const unsigned int loop_delay_ms = 10;
+       int ret;
+
+       return read_poll_timeout(vgxy61_read_reg, ret,
+                                ((ret < 0) || (ret == poll_val)),
+                                loop_delay_ms * 1000, timeout_ms * 1000,
+                                false, sensor, reg);
+}
+
+static int vgxy61_wait_state(struct vgxy61_dev *sensor, int state,
+                            unsigned int timeout_ms)
+{
+       return vgxy61_poll_reg(sensor, VGXY61_REG_SYSTEM_FSM, state,
+                              timeout_ms);
+}
+
+static int vgxy61_check_bw(struct vgxy61_dev *sensor)
+{
+       /*
+        * Simplification of time needed to send short packets and for the MIPI
+        * to add transition times (EoT, LPS, and SoT packet delimiters) needed
+        * by the protocol to go in low power between 2 packets of data. This
+        * is a mipi IP constant for the sensor.
+        */
+       const unsigned int mipi_margin = 1056;
+       unsigned int binning_scale = sensor->current_mode->crop.height /
+                                    sensor->current_mode->height;
+       u8 bpp = get_bpp_by_code(sensor->fmt.code);
+       unsigned int max_bit_per_line;
+       unsigned 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;
+
+       return bit_per_line > max_bit_per_line ? -EINVAL : 0;
+}
+
+static int vgxy61_apply_exposure(struct vgxy61_dev *sensor)
+{
+       int ret = 0;
+
+        /* We first set expo to zero to avoid forbidden parameters couple */
+       vgxy61_write_reg(sensor, VGXY61_REG_COARSE_EXPOSURE_SHORT, 0, &ret);
+       vgxy61_write_reg(sensor, VGXY61_REG_COARSE_EXPOSURE_LONG,
+                        sensor->expo_long, &ret);
+       vgxy61_write_reg(sensor, VGXY61_REG_COARSE_EXPOSURE_SHORT,
+                        sensor->expo_short, &ret);
+
+       return ret;
+}
+
+static int vgxy61_get_regulators(struct vgxy61_dev *sensor)
+{
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(vgxy61_supply_name); i++)
+               sensor->supplies[i].supply = vgxy61_supply_name[i];
+
+       return devm_regulator_bulk_get(&sensor->i2c_client->dev,
+                                      ARRAY_SIZE(vgxy61_supply_name),
+                                      sensor->supplies);
+}
+
+static int vgxy61_apply_reset(struct vgxy61_dev *sensor)
+{
+       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, VGXY61_SYSTEM_FSM_SW_STBY,
+                                VGXY61_TIMEOUT_MS);
+}
+
+static void vgxy61_fill_framefmt(struct vgxy61_dev *sensor,
+                                const struct vgxy61_mode_info *mode,
+                                struct v4l2_mbus_framefmt *fmt, u32 code)
+{
+       fmt->code = code;
+       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;
+}
+
+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;
+
+       for (index = 0; index < ARRAY_SIZE(vgxy61_supported_codes); index++) {
+               if (vgxy61_supported_codes[index].code == fmt->code)
+                       break;
+       }
+       if (index == ARRAY_SIZE(vgxy61_supported_codes))
+               index = 0;
+
+       mode = v4l2_find_nearest_size(sensor->sensor_modes,
+                                     sensor->sensor_modes_nb, width, height,
+                                     fmt->width, fmt->height);
+       if (new_mode)
+               *new_mode = mode;
+
+       vgxy61_fill_framefmt(sensor, mode, fmt,
+                            vgxy61_supported_codes[index].code);
+
+       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_enum_mbus_code(struct v4l2_subdev *sd,
+                                struct v4l2_subdev_state *sd_state,
+                                struct v4l2_subdev_mbus_code_enum *code)
+{
+       if (code->index >= ARRAY_SIZE(vgxy61_supported_codes))
+               return -EINVAL;
+
+       code->code = vgxy61_supported_codes[code->index].code;
+
+       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 v4l2_mbus_framefmt *fmt;
+
+       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 u16 vgxy61_get_vblank_min(struct vgxy61_dev *sensor,
+                                enum vgxy61_hdr_mode hdr)
+{
+       u16 min_vblank =  VGXY61_MIN_FRAME_LENGTH -
+                         sensor->current_mode->crop.height;
+       /* Ensure the first rule of thumb can't be negative */
+       u16 min_vblank_hdr =  VGXY61_MIN_EXPOSURE + sensor->rot_term + 1;
+
+       if (hdr != VGXY61_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);
+
+       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)
+{
+       sensor->analog_gain = target;
+
+       if (sensor->streaming)
+               return vgxy61_write_reg(sensor, VGXY61_REG_ANALOG_GAIN, target,
+                                       NULL);
+       return 0;
+}
+
+static int vgxy61_apply_digital_gain(struct vgxy61_dev *sensor,
+                                    u32 digital_gain)
+{
+       int ret = 0;
+
+       /*
+        * 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.
+        */
+       vgxy61_write_reg(sensor, VGXY61_REG_DIGITAL_GAIN_LONG, digital_gain,
+                        &ret);
+       vgxy61_write_reg(sensor, VGXY61_REG_DIGITAL_GAIN_SHORT, digital_gain,
+                        &ret);
+
+       return ret;
+}
+
+static int vgxy61_update_digital_gain(struct vgxy61_dev *sensor, u32 target)
+{
+       sensor->digital_gain = target;
+
+       if (sensor->streaming)
+               return vgxy61_apply_digital_gain(sensor, sensor->digital_gain);
+       return 0;
+}
+
+static int vgxy61_apply_patgen(struct vgxy61_dev *sensor, u32 index)
+{
+       static const u8 index2val[] = {
+               0x0, 0x1, 0x2, 0x3, 0x10, 0x11, 0x12, 0x13
+       };
+       u32 pattern = index2val[index];
+       u32 reg = (pattern << VGXY61_PATGEN_LONG_TYPE_SHIFT) |
+             (pattern << VGXY61_PATGEN_SHORT_TYPE_SHIFT);
+
+       if (pattern)
+               reg |= VGXY61_PATGEN_LONG_ENABLE | VGXY61_PATGEN_SHORT_ENABLE;
+       return vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_CTRL, reg, NULL);
+}
+
+static int vgxy61_update_patgen(struct vgxy61_dev *sensor, u32 pattern)
+{
+       sensor->pattern = pattern;
+
+       if (sensor->streaming)
+               return vgxy61_apply_patgen(sensor, sensor->pattern);
+       return 0;
+}
+
+static int vgxy61_apply_gpiox_strobe_mode(struct vgxy61_dev *sensor,
+                                         enum vgxy61_strobe_mode mode,
+                                         unsigned int idx)
+{
+       static const u8 index2val[] = {0x0, 0x1, 0x3};
+       u16 reg;
+
+       reg = vgxy61_read_reg(sensor, VGXY61_REG_SIGNALS_CTRL);
+       if (reg < 0)
+               return reg;
+       reg &= ~(0xf << (idx * VGXY61_SIGNALS_GPIO_ID_SHIFT));
+       reg |= index2val[mode] << (idx * VGXY61_SIGNALS_GPIO_ID_SHIFT);
+
+       return vgxy61_write_reg(sensor, VGXY61_REG_SIGNALS_CTRL, reg, NULL);
+}
+
+static int vgxy61_update_gpios_strobe_mode(struct vgxy61_dev *sensor,
+                                          enum vgxy61_hdr_mode hdr)
+{
+       unsigned int i;
+       int ret;
+
+       switch (hdr) {
+       case VGXY61_HDR_LINEAR:
+               sensor->strobe_mode = VGXY61_STROBE_ENABLED;
+               break;
+       case VGXY61_HDR_SUB:
+       case VGXY61_NO_HDR:
+               sensor->strobe_mode = VGXY61_STROBE_LONG;
+               break;
+       default:
+               /* Should never happen */
+               WARN_ON(true);
+               break;
+       }
+
+       if (!sensor->streaming)
+               return 0;
+
+       for (i = 0; i < VGXY61_NB_GPIOS; i++) {
+               ret = vgxy61_apply_gpiox_strobe_mode(sensor,
+                                                    sensor->strobe_mode,
+                                                    i);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static int vgxy61_update_gpios_strobe_polarity(struct vgxy61_dev *sensor,
+                                              bool polarity)
+{
+       int ret = 0;
+
+       if (sensor->streaming)
+               return -EBUSY;
+
+       vgxy61_write_reg(sensor, VGXY61_REG_GPIO_0_CTRL, polarity << 1, &ret);
+       vgxy61_write_reg(sensor, VGXY61_REG_GPIO_1_CTRL, polarity << 1, &ret);
+       vgxy61_write_reg(sensor, VGXY61_REG_GPIO_2_CTRL, polarity << 1, &ret);
+       vgxy61_write_reg(sensor, VGXY61_REG_GPIO_3_CTRL, polarity << 1, &ret);
+       vgxy61_write_reg(sensor, VGXY61_REG_SIGNALS_POLARITY_CTRL, polarity,
+                        &ret);
+
+       return ret;
+}
+
+static u32 vgxy61_get_expo_long_max(struct vgxy61_dev *sensor,
+                                   unsigned int short_expo_ratio)
+{
+       u32 first_rot_max_expo, second_rot_max_expo, third_rot_max_expo;
+
+       /* Apply sensor's rules of thumb */
+       /*
+        * Short exposure + height must be less than frame length to 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;
+
+       /*
+        * Total exposition time must be less than frame length to avoid sensor
+        * crash
+        */
+       second_rot_max_expo =
+               (((sensor->frame_length - VGXY61_EXPOS_ROT_TERM) *
+               short_expo_ratio) / (short_expo_ratio + 1)) - 1;
+
+       /*
+        * Short exposure times 71 must be less than frame length to 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, u16 new_expo_long,
+                                 enum vgxy61_hdr_mode hdr)
+{
+       struct i2c_client *client = sensor->i2c_client;
+       u16 new_expo_short = 0;
+       u16 expo_short_max = 0;
+       u16 expo_long_min = VGXY61_MIN_EXPOSURE;
+       u16 expo_long_max;
+
+       /* Compute short exposure according to hdr mode and long exposure */
+       switch (hdr) {
+       case VGXY61_HDR_LINEAR:
+               /*
+                * Take ratio into account for minimal exposures in
+                * VGXY61_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 VGXY61_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 VGXY61_HDR_SUB */
+               expo_short_max = expo_long_max;
+               new_expo_short = new_expo_long;
+               break;
+       case VGXY61_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;
+       }
+
+       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 vgxy61_apply_exposure(sensor);
+       return 0;
+}
+
+static int vgxy61_apply_framelength(struct vgxy61_dev *sensor)
+{
+       return vgxy61_write_reg(sensor, VGXY61_REG_FRAME_LENGTH,
+                               sensor->frame_length, NULL);
+}
+
+static int vgxy61_update_vblank(struct vgxy61_dev *sensor, u16 vblank,
+                               enum vgxy61_hdr_mode 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 vgxy61_apply_framelength(sensor);
+       return 0;
+}
+
+static int vgxy61_apply_hdr(struct vgxy61_dev *sensor,
+                           enum vgxy61_hdr_mode index)
+{
+       static const u8 index2val[] = {0x1, 0x4, 0xa};
+
+       return vgxy61_write_reg(sensor, VGXY61_REG_HDR_CTRL, index2val[index],
+                               NULL);
+}
+
+static int vgxy61_update_hdr(struct vgxy61_dev *sensor,
+                            enum vgxy61_hdr_mode index)
+{
+       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;
+
+       sensor->hdr = index;
+
+       if (sensor->streaming)
+               return vgxy61_apply_hdr(sensor, sensor->hdr);
+       return 0;
+}
+
+static int vgxy61_apply_settings(struct vgxy61_dev *sensor)
+{
+       int ret;
+       unsigned int i;
+
+       ret = vgxy61_apply_hdr(sensor, sensor->hdr);
+       if (ret)
+               return ret;
+
+       ret = vgxy61_apply_framelength(sensor);
+       if (ret)
+               return ret;
+
+       ret = vgxy61_apply_exposure(sensor);
+       if (ret)
+               return ret;
+
+       ret = vgxy61_write_reg(sensor, VGXY61_REG_ANALOG_GAIN,
+                              sensor->analog_gain, NULL);
+       if (ret)
+               return ret;
+       ret = vgxy61_apply_digital_gain(sensor, sensor->digital_gain);
+       if (ret)
+               return ret;
+
+       ret = vgxy61_write_reg(sensor, VGXY61_REG_ORIENTATION,
+                              sensor->hflip | (sensor->vflip << 1), NULL);
+       if (ret)
+               return ret;
+
+       ret = vgxy61_apply_patgen(sensor, sensor->pattern);
+       if (ret)
+               return ret;
+
+       for (i = 0; i < VGXY61_NB_GPIOS; i++) {
+               ret = vgxy61_apply_gpiox_strobe_mode(sensor,
+                                                    sensor->strobe_mode, i);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+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 = 0;
+
+       ret = vgxy61_check_bw(sensor);
+       if (ret)
+               return ret;
+
+       ret = pm_runtime_get_sync(&client->dev);
+       if (ret < 0) {
+               pm_runtime_put_autosuspend(&client->dev);
+               return ret;
+       }
+
+       vgxy61_write_reg(sensor, VGXY61_REG_FORMAT_CTRL,
+                        get_bpp_by_code(sensor->fmt.code), &ret);
+       vgxy61_write_reg(sensor, VGXY61_REG_OIF_ROI0_CTRL,
+                        get_data_type_by_code(sensor->fmt.code), &ret);
+
+       vgxy61_write_reg(sensor, VGXY61_REG_READOUT_CTRL,
+                        sensor->current_mode->bin_mode, &ret);
+       vgxy61_write_reg(sensor, VGXY61_REG_ROI0_START_H, crop->left, &ret);
+       vgxy61_write_reg(sensor, VGXY61_REG_ROI0_END_H,
+                        crop->left + crop->width - 1, &ret);
+       vgxy61_write_reg(sensor, VGXY61_REG_ROI0_START_V, crop->top, &ret);
+       vgxy61_write_reg(sensor, VGXY61_REG_ROI0_END_V,
+                        crop->top + crop->height - 1, &ret);
+       if (ret)
+               goto err_rpm_put;
+
+       ret = vgxy61_apply_settings(sensor);
+       if (ret)
+               goto err_rpm_put;
+
+       ret = vgxy61_write_reg(sensor, VGXY61_REG_STREAMING,
+                              VGXY61_STREAMING_REQ_START, NULL);
+       if (ret)
+               goto err_rpm_put;
+
+       ret = vgxy61_poll_reg(sensor, VGXY61_REG_STREAMING,
+                             VGXY61_STREAMING_NO_REQ, VGXY61_TIMEOUT_MS);
+       if (ret)
+               goto err_rpm_put;
+
+       ret = vgxy61_wait_state(sensor, VGXY61_SYSTEM_FSM_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, VGXY61_REG_STREAMING,
+                              VGXY61_STREAMING_REQ_STOP, NULL);
+       if (ret)
+               goto err_str_dis;
+
+       ret = vgxy61_poll_reg(sensor, VGXY61_REG_STREAMING,
+                             VGXY61_STREAMING_NO_REQ, 2000);
+       if (ret)
+               goto err_str_dis;
+
+       ret = vgxy61_wait_state(sensor, VGXY61_SYSTEM_FSM_SW_STBY,
+                               VGXY61_TIMEOUT_MS);
+       if (ret)
+               goto err_str_dis;
+
+       __v4l2_ctrl_grab(sensor->vflip_ctrl, false);
+       __v4l2_ctrl_grab(sensor->hflip_ctrl, false);
+
+err_str_dis:
+       if (ret)
+               WARN(1, "Can't disable stream");
+       pm_runtime_put(&client->dev);
+
+       return ret;
+}
+
+static int vgxy61_s_stream(struct v4l2_subdev *sd, int enable)
+{
+       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
+       int ret = 0;
+
+       mutex_lock(&sensor->lock);
+
+       ret = enable ? vgxy61_stream_enable(sensor) :
+             vgxy61_stream_disable(sensor);
+       if (!ret)
+               sensor->streaming = enable;
+
+       mutex_unlock(&sensor->lock);
+
+       return ret;
+}
+
+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);
+       const struct vgxy61_mode_info *new_mode;
+       struct v4l2_mbus_framefmt *fmt;
+       int ret;
+
+       mutex_lock(&sensor->lock);
+
+       if (sensor->streaming) {
+               ret = -EBUSY;
+               goto out;
+       }
+
+       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 -
+                                          new_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 - new_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_init_cfg(struct v4l2_subdev *sd,
+                          struct v4l2_subdev_state *sd_state)
+{
+       struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
+       struct v4l2_subdev_format fmt = { 0 };
+
+       sensor->current_mode = sensor->default_mode;
+       vgxy61_fill_framefmt(sensor, sensor->current_mode, &fmt.format,
+                            VGXY61_MEDIA_BUS_FMT_DEF);
+
+       return vgxy61_set_fmt(sd, sd_state, &fmt);
+}
+
+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);
+       const struct vgxy61_mode_info *cur_mode = sensor->current_mode;
+       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 = 0;
+               break;
+       case V4L2_CID_TEST_PATTERN:
+               ret = vgxy61_update_patgen(sensor, ctrl->val);
+               break;
+       case V4L2_CID_HDR_SENSOR_MODE:
+               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 - cur_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 const struct v4l2_ctrl_ops vgxy61_ctrl_ops = {
+       .s_ctrl = vgxy61_s_ctrl,
+};
+
+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;
+       const struct vgxy61_mode_info *cur_mode = sensor->current_mode;
+       struct v4l2_ctrl *ctrl;
+       int ret;
+
+       v4l2_ctrl_handler_init(hdl, 16);
+       /* We can use our own mutex for the ctrl lock */
+       hdl->lock = &sensor->lock;
+       v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, 0x1c, 1,
+                         sensor->analog_gain);
+       v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 0, 0xfff, 1,
+                         sensor->digital_gain);
+       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);
+       ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HBLANK, 0,
+                                sensor->line_length, 1,
+                                sensor->line_length - cur_mode->width);
+       if (ctrl)
+               ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+       ctrl = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ,
+                                     ARRAY_SIZE(link_freq) - 1, 0, link_freq);
+       if (ctrl)
+               ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+       v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_HDR_SENSOR_MODE,
+                                    ARRAY_SIZE(vgxy61_hdr_mode_menu) - 1, 0,
+                                    VGXY61_NO_HDR, vgxy61_hdr_mode_menu);
+
+       /*
+        * 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));
+       if (sensor->pixel_rate_ctrl)
+               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 - cur_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);
+       return ret;
+}
+
+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 = {
+       .init_cfg = vgxy61_init_cfg,
+       .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 = {
+       .video = &vgxy61_video_ops,
+       .pad = &vgxy61_pad_ops,
+};
+
+static const struct media_entity_operations vgxy61_subdev_entity_ops = {
+       .link_validate = v4l2_subdev_link_validate,
+};
+
+static int vgxy61_tx_from_ep(struct vgxy61_dev *sensor,
+                            struct fwnode_handle *handle)
+{
+       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;
+       unsigned int p, l, i;
+       int ret;
+
+       ret = v4l2_fwnode_endpoint_alloc_parse(handle, &ep);
+       if (ret)
+               return -EINVAL;
+
+       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);
+
+       return -EINVAL;
+}
+
+static int vgxy61_configure(struct vgxy61_dev *sensor)
+{
+       u32 sensor_freq;
+       u8 prediv, mult;
+       u16 line_length;
+       int ret = 0;
+
+       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;
+
+       line_length = vgxy61_read_reg(sensor, VGXY61_REG_LINE_LENGTH);
+       if (line_length < 0)
+               return line_length;
+       sensor->line_length = line_length;
+       vgxy61_write_reg(sensor, VGXY61_REG_EXT_CLOCK, sensor->clk_freq, &ret);
+       vgxy61_write_reg(sensor, VGXY61_REG_CLK_PLL_PREDIV, prediv, &ret);
+       vgxy61_write_reg(sensor, VGXY61_REG_CLK_SYS_PLL_MULT, mult, &ret);
+       vgxy61_write_reg(sensor, VGXY61_REG_OIF_CTRL, sensor->oif_ctrl, &ret);
+       vgxy61_write_reg(sensor, VGXY61_REG_FRAME_CONTENT_CTRL, 0, &ret);
+       vgxy61_write_reg(sensor, VGXY61_REG_BYPASS_CTRL, 4, &ret);
+       if (ret)
+               return ret;
+       vgxy61_update_gpios_strobe_polarity(sensor, sensor->gpios_polarity);
+       /* Set pattern generator solid to middle value */
+       vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_LONG_DATA_GR, 0x800, &ret);
+       vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_LONG_DATA_R, 0x800, &ret);
+       vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_LONG_DATA_B, 0x800, &ret);
+       vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_LONG_DATA_GB, 0x800, &ret);
+       vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_SHORT_DATA_GR, 0x800, &ret);
+       vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_SHORT_DATA_R, 0x800, &ret);
+       vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_SHORT_DATA_B, 0x800, &ret);
+       vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_SHORT_DATA_GB, 0x800, &ret);
+       if (ret)
+               return ret;
+
+       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, VGXY61_REG_FWPATCH_START_ADDR,
+                                sizeof(patch_array), patch_array);
+       if (ret)
+               return ret;
+
+       ret = vgxy61_write_reg(sensor, VGXY61_REG_STBY, 0x10, NULL);
+       if (ret)
+               return ret;
+
+       ret = vgxy61_poll_reg(sensor, VGXY61_REG_STBY, 0, VGXY61_TIMEOUT_MS);
+       if (ret)
+               return ret;
+
+       patch = vgxy61_read_reg(sensor, VGXY61_REG_FWPATCH_REVISION);
+       if (patch < 0)
+               return patch;
+
+       if (patch != (VGXY61_FWPATCH_REVISION_MAJOR << 12) +
+                    (VGXY61_FWPATCH_REVISION_MINOR << 8) +
+                    VGXY61_FWPATCH_REVISION_MICRO) {
+               dev_err(&client->dev, "bad patch version expected %d.%d.%d got %d.%d.%d\n",
+                       VGXY61_FWPATCH_REVISION_MAJOR,
+                       VGXY61_FWPATCH_REVISION_MINOR,
+                       VGXY61_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;
+
+       device_rev = vgxy61_read_reg(sensor, VGXY61_REG_REVISION);
+       if (device_rev < 0)
+               return device_rev;
+
+       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;
+
+       id = vgxy61_read_reg(sensor, VGXY61_REG_MODEL_ID);
+       if (id < 0)
+               return id;
+       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, VGXY61_SYSTEM_FSM_SW_STBY,
+                               VGXY61_TIMEOUT_MS);
+       if (ret)
+               return ret;
+
+       st = vgxy61_read_reg(sensor, VGXY61_REG_NVM);
+       if (st < 0)
+               return st;
+       if (st != VGXY61_NVM_OK)
+               dev_warn(&client->dev, "Bad nvm state got %d\n", st);
+
+       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 = clk_prepare_enable(sensor->xclk);
+       if (ret) {
+               dev_err(&client->dev, "failed to enable clock %d\n", ret);
+               goto disable_bulk;
+       }
+
+       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_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(ARRAY_SIZE(vgxy61_supply_name),
+                              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(ARRAY_SIZE(vgxy61_supply_name),
+                              sensor->supplies);
+       return 0;
+}
+
+static void vgxy61_fill_sensor_param(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->default_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->default_mode = &vgx661_mode_data[VGX661_DEFAULT_MODE];
+               sensor->rot_term = VGX661_SHORT_ROT_TERM;
+       } else {
+               /* Should never happen */
+               WARN_ON(true);
+       }
+       sensor->current_mode = sensor->default_mode;
+}
+
+static int vgxy61_probe(struct i2c_client *client)
+{
+       struct device *dev = &client->dev;
+       struct fwnode_handle *handle;
+       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->hdr = VGXY61_NO_HDR;
+       sensor->expo_long = 200;
+       sensor->expo_short = 0;
+       sensor->hflip = false;
+       sensor->vflip = false;
+       sensor->analog_gain = 0;
+       sensor->digital_gain = 256;
+
+       handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0);
+       if (!handle) {
+               dev_err(dev, "handle node not found\n");
+               return -EINVAL;
+       }
+
+       ret = vgxy61_tx_from_ep(sensor, handle);
+       fwnode_handle_put(handle);
+       if (ret) {
+               dev_err(dev, "Failed to parse handle %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 =
+               device_property_read_bool(dev, "st,strobe-gpios-polarity");
+
+       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;
+
+       sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+                                                    GPIOD_OUT_HIGH);
+
+       ret = vgxy61_get_regulators(sensor);
+       if (ret) {
+               dev_err(&client->dev, "failed to get regulators %d\n", ret);
+               return ret;
+       }
+
+       ret = regulator_bulk_enable(ARRAY_SIZE(vgxy61_supply_name),
+                                   sensor->supplies);
+       if (ret) {
+               dev_err(&client->dev, "failed to enable regulators %d\n", ret);
+               return ret;
+       }
+
+       ret = vgxy61_power_on(dev);
+       if (ret)
+               return ret;
+
+       ret = vgxy61_detect(sensor);
+       if (ret) {
+               dev_err(&client->dev, "sensor detect failed %d\n", ret);
+               return ret;
+       }
+
+       vgxy61_fill_sensor_param(sensor);
+       vgxy61_fill_framefmt(sensor, sensor->current_mode, &sensor->fmt,
+                            VGXY61_MEDIA_BUS_FMT_DEF);
+
+       ret = vgxy61_update_hdr(sensor, sensor->hdr);
+       if (ret)
+               return ret;
+
+       mutex_init(&sensor->lock);
+
+       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;
+       }
+
+       /* Enable runtime PM and turn off the device */
+       pm_runtime_set_active(dev);
+       pm_runtime_enable(dev);
+       pm_runtime_idle(dev);
+
+       ret = v4l2_async_register_subdev(&sensor->sd);
+       if (ret) {
+               dev_err(&client->dev, "async subdev register failed %d\n", ret);
+               goto error_pm_runtime;
+       }
+
+       pm_runtime_set_autosuspend_delay(&client->dev, 1000);
+       pm_runtime_use_autosuspend(&client->dev);
+
+       dev_dbg(&client->dev, "vgxy61 probe successfully\n");
+
+       return 0;
+
+error_pm_runtime:
+       pm_runtime_disable(&client->dev);
+       pm_runtime_set_suspended(&client->dev);
+       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 void 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);
+}
+
+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_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");
diff --git a/drivers/media/i2c/tc358746.c b/drivers/media/i2c/tc358746.c
new file mode 100644 (file)
index 0000000..171309c
--- /dev/null
@@ -0,0 +1,1694 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * TC358746 - Parallel <-> CSI-2 Bridge
+ *
+ * Copyright 2022 Marco Felsch <kernel@pengutronix.de>
+ *
+ * Notes:
+ *  - Currently only 'Parallel-in -> CSI-out' mode is supported!
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/phy/phy-mipi-dphy.h>
+#include <linux/property.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/units.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-mc.h>
+
+/* 16-bit registers */
+#define CHIPID_REG                     0x0000
+#define                CHIPID                  GENMASK(15, 8)
+
+#define SYSCTL_REG                     0x0002
+#define                SRESET                  BIT(0)
+
+#define CONFCTL_REG                    0x0004
+#define                PDATAF_MASK             GENMASK(9, 8)
+#define                PDATAF_MODE0            0
+#define                PDATAF_MODE1            1
+#define                PDATAF_MODE2            2
+#define                PDATAF(val)             FIELD_PREP(PDATAF_MASK, (val))
+#define                PPEN                    BIT(6)
+#define                DATALANE_MASK           GENMASK(1, 0)
+
+#define FIFOCTL_REG                    0x0006
+#define DATAFMT_REG                    0x0008
+#define                PDFMT(val)              FIELD_PREP(GENMASK(7, 4), (val))
+
+#define MCLKCTL_REG                    0x000c
+#define                MCLK_HIGH_MASK          GENMASK(15, 8)
+#define                MCLK_LOW_MASK           GENMASK(7, 0)
+#define                MCLK_HIGH(val)          FIELD_PREP(MCLK_HIGH_MASK, (val))
+#define                MCLK_LOW(val)           FIELD_PREP(MCLK_LOW_MASK, (val))
+
+#define PLLCTL0_REG                    0x0016
+#define                PLL_PRD_MASK            GENMASK(15, 12)
+#define                PLL_PRD(val)            FIELD_PREP(PLL_PRD_MASK, (val))
+#define                PLL_FBD_MASK            GENMASK(8, 0)
+#define                PLL_FBD(val)            FIELD_PREP(PLL_FBD_MASK, (val))
+
+#define PLLCTL1_REG                    0x0018
+#define                PLL_FRS_MASK            GENMASK(11, 10)
+#define                PLL_FRS(val)            FIELD_PREP(PLL_FRS_MASK, (val))
+#define                CKEN                    BIT(4)
+#define                RESETB                  BIT(1)
+#define                PLL_EN                  BIT(0)
+
+#define CLKCTL_REG                     0x0020
+#define                MCLKDIV_MASK            GENMASK(3, 2)
+#define                MCLKDIV(val)            FIELD_PREP(MCLKDIV_MASK, (val))
+#define                MCLKDIV_8               0
+#define                MCLKDIV_4               1
+#define                MCLKDIV_2               2
+
+#define WORDCNT_REG                    0x0022
+#define PP_MISC_REG                    0x0032
+#define                FRMSTOP                 BIT(15)
+#define                RSTPTR                  BIT(14)
+
+/* 32-bit registers */
+#define CLW_DPHYCONTTX_REG             0x0100
+#define CLW_CNTRL_REG                  0x0140
+#define D0W_CNTRL_REG                  0x0144
+#define                LANEDISABLE             BIT(0)
+
+#define STARTCNTRL_REG                 0x0204
+#define                START                   BIT(0)
+
+#define LINEINITCNT_REG                        0x0210
+#define LPTXTIMECNT_REG                        0x0214
+#define TCLK_HEADERCNT_REG             0x0218
+#define                TCLK_ZEROCNT(val)       FIELD_PREP(GENMASK(15, 8), (val))
+#define                TCLK_PREPARECNT(val)    FIELD_PREP(GENMASK(6, 0), (val))
+
+#define TCLK_TRAILCNT_REG              0x021C
+#define THS_HEADERCNT_REG              0x0220
+#define                THS_ZEROCNT(val)        FIELD_PREP(GENMASK(14, 8), (val))
+#define                THS_PREPARECNT(val)     FIELD_PREP(GENMASK(6, 0), (val))
+
+#define TWAKEUP_REG                    0x0224
+#define TCLK_POSTCNT_REG               0x0228
+#define THS_TRAILCNT_REG               0x022C
+#define HSTXVREGEN_REG                 0x0234
+#define TXOPTIONCNTRL_REG              0x0238
+#define CSI_CONTROL_REG                        0x040C
+#define                CSI_MODE                BIT(15)
+#define                TXHSMD                  BIT(7)
+#define                NOL(val)                FIELD_PREP(GENMASK(2, 1), (val))
+
+#define CSI_CONFW_REG                  0x0500
+#define                MODE(val)               FIELD_PREP(GENMASK(31, 29), (val))
+#define                MODE_SET                0x5
+#define                ADDRESS(val)            FIELD_PREP(GENMASK(28, 24), (val))
+#define                CSI_CONTROL_ADDRESS     0x3
+#define                DATA(val)               FIELD_PREP(GENMASK(15, 0), (val))
+
+#define CSI_START_REG                  0x0518
+#define                STRT                    BIT(0)
+
+static const struct v4l2_mbus_framefmt tc358746_def_fmt = {
+       .width          = 640,
+       .height         = 480,
+       .code           = MEDIA_BUS_FMT_UYVY8_2X8,
+       .field          = V4L2_FIELD_NONE,
+       .colorspace     = V4L2_COLORSPACE_DEFAULT,
+       .ycbcr_enc      = V4L2_YCBCR_ENC_DEFAULT,
+       .quantization   = V4L2_QUANTIZATION_DEFAULT,
+       .xfer_func      = V4L2_XFER_FUNC_DEFAULT,
+};
+
+static const char * const tc358746_supplies[] = {
+       "vddc", "vddio", "vddmipi"
+};
+
+enum {
+       TC358746_SINK,
+       TC358746_SOURCE,
+       TC358746_NR_PADS
+};
+
+struct tc358746 {
+       struct v4l2_subdev              sd;
+       struct media_pad                pads[TC358746_NR_PADS];
+       struct v4l2_async_notifier      notifier;
+       struct v4l2_fwnode_endpoint     csi_vep;
+
+       struct v4l2_ctrl_handler        ctrl_hdl;
+
+       struct regmap                   *regmap;
+       struct clk                      *refclk;
+       struct gpio_desc                *reset_gpio;
+       struct regulator_bulk_data      supplies[ARRAY_SIZE(tc358746_supplies)];
+
+       struct clk_hw                   mclk_hw;
+       unsigned long                   mclk_rate;
+       u8                              mclk_prediv;
+       u16                             mclk_postdiv;
+
+       unsigned long                   pll_rate;
+       u8                              pll_post_div;
+       u16                             pll_pre_div;
+       u16                             pll_mul;
+
+#define TC358746_VB_MAX_SIZE           (511 * 32)
+#define TC358746_VB_DEFAULT_SIZE         (1 * 32)
+       unsigned int                    vb_size; /* Video buffer size in bits */
+
+       struct phy_configure_opts_mipi_dphy dphy_cfg;
+};
+
+static inline struct tc358746 *to_tc358746(struct v4l2_subdev *sd)
+{
+       return container_of(sd, struct tc358746, sd);
+}
+
+static inline struct tc358746 *clk_hw_to_tc358746(struct clk_hw *hw)
+{
+       return container_of(hw, struct tc358746, mclk_hw);
+}
+
+struct tc358746_format {
+       u32             code;
+       bool            csi_format;
+       unsigned char   bus_width;
+       unsigned char   bpp;
+       /* Register values */
+       u8              pdformat; /* Peripheral Data Format */
+       u8              pdataf;   /* Parallel Data Format Option */
+};
+
+enum {
+       PDFORMAT_RAW8 = 0,
+       PDFORMAT_RAW10,
+       PDFORMAT_RAW12,
+       PDFORMAT_RGB888,
+       PDFORMAT_RGB666,
+       PDFORMAT_RGB565,
+       PDFORMAT_YUV422_8BIT,
+       /* RESERVED = 7 */
+       PDFORMAT_RAW14 = 8,
+       PDFORMAT_YUV422_10BIT,
+       PDFORMAT_YUV444,
+};
+
+/* Check tc358746_src_mbus_code() if you add new formats */
+static const struct tc358746_format tc358746_formats[] = {
+       {
+               .code = MEDIA_BUS_FMT_UYVY8_2X8,
+               .bus_width = 8,
+               .bpp = 16,
+               .pdformat = PDFORMAT_YUV422_8BIT,
+               .pdataf = PDATAF_MODE0,
+       }, {
+               .code = MEDIA_BUS_FMT_UYVY8_1X16,
+               .csi_format = true,
+               .bus_width = 16,
+               .bpp = 16,
+               .pdformat = PDFORMAT_YUV422_8BIT,
+               .pdataf = PDATAF_MODE1,
+       }, {
+               .code = MEDIA_BUS_FMT_YUYV8_1X16,
+               .csi_format = true,
+               .bus_width = 16,
+               .bpp = 16,
+               .pdformat = PDFORMAT_YUV422_8BIT,
+               .pdataf = PDATAF_MODE2,
+       }, {
+               .code = MEDIA_BUS_FMT_UYVY10_2X10,
+               .bus_width = 10,
+               .bpp = 20,
+               .pdformat = PDFORMAT_YUV422_10BIT,
+               .pdataf = PDATAF_MODE0, /* don't care */
+       }
+};
+
+/* Get n-th format for pad */
+static const struct tc358746_format *
+tc358746_get_format_by_idx(unsigned int pad, unsigned int index)
+{
+       unsigned int idx = 0;
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(tc358746_formats); i++) {
+               const struct tc358746_format *fmt = &tc358746_formats[i];
+
+               if ((pad == TC358746_SOURCE && fmt->csi_format) ||
+                   (pad == TC358746_SINK)) {
+                       if (idx == index)
+                               return fmt;
+                       idx++;
+               }
+       }
+
+       return ERR_PTR(-EINVAL);
+}
+
+static const struct tc358746_format *
+tc358746_get_format_by_code(unsigned int pad, u32 code)
+{
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(tc358746_formats); i++) {
+               const struct tc358746_format *fmt = &tc358746_formats[i];
+
+               if (pad == TC358746_SINK && fmt->code == code)
+                       return fmt;
+
+               if (pad == TC358746_SOURCE && !fmt->csi_format)
+                       continue;
+
+               if (fmt->code == code)
+                       return fmt;
+       }
+
+       return ERR_PTR(-EINVAL);
+}
+
+static u32 tc358746_src_mbus_code(u32 code)
+{
+       switch (code) {
+       case MEDIA_BUS_FMT_UYVY8_2X8:
+               return MEDIA_BUS_FMT_UYVY8_1X16;
+       case MEDIA_BUS_FMT_UYVY10_2X10:
+               return MEDIA_BUS_FMT_UYVY10_1X20;
+       default:
+               return code;
+       }
+}
+
+static bool tc358746_valid_reg(struct device *dev, unsigned int reg)
+{
+       switch (reg) {
+       case CHIPID_REG ... CSI_START_REG:
+               return true;
+       default:
+               return false;
+       }
+}
+
+static const struct regmap_config tc358746_regmap_config = {
+       .name = "tc358746",
+       .reg_bits = 16,
+       .val_bits = 16,
+       .max_register = CSI_START_REG,
+       .writeable_reg = tc358746_valid_reg,
+       .readable_reg = tc358746_valid_reg,
+       .reg_format_endian = REGMAP_ENDIAN_BIG,
+       .val_format_endian = REGMAP_ENDIAN_BIG,
+};
+
+static int tc358746_write(struct tc358746 *tc358746, u32 reg, u32 val)
+{
+       size_t count;
+       int err;
+
+       /* 32-bit registers starting from CLW_DPHYCONTTX */
+       count = reg < CLW_DPHYCONTTX_REG ? 1 : 2;
+
+       err = regmap_bulk_write(tc358746->regmap, reg, &val, count);
+       if (err)
+               dev_err(tc358746->sd.dev,
+                       "Failed to write reg:0x%04x err:%d\n", reg, err);
+
+       return err;
+}
+
+static int tc358746_read(struct tc358746 *tc358746, u32 reg, u32 *val)
+{
+       size_t count;
+       int err;
+
+       /* 32-bit registers starting from CLW_DPHYCONTTX */
+       count = reg < CLW_DPHYCONTTX_REG ? 1 : 2;
+       *val = 0;
+
+       err = regmap_bulk_read(tc358746->regmap, reg, val, count);
+       if (err)
+               dev_err(tc358746->sd.dev,
+                       "Failed to read reg:0x%04x err:%d\n", reg, err);
+
+       return err;
+}
+
+static int
+tc358746_update_bits(struct tc358746 *tc358746, u32 reg, u32 mask, u32 val)
+{
+       u32 tmp, orig;
+       int err;
+
+       err = tc358746_read(tc358746, reg, &orig);
+       if (err)
+               return err;
+
+       tmp = orig & ~mask;
+       tmp |= val & mask;
+
+       return tc358746_write(tc358746, reg, tmp);
+}
+
+static int tc358746_set_bits(struct tc358746 *tc358746, u32 reg, u32 bits)
+{
+       return tc358746_update_bits(tc358746, reg, bits, bits);
+}
+
+static int tc358746_clear_bits(struct tc358746 *tc358746, u32 reg, u32 bits)
+{
+       return tc358746_update_bits(tc358746, reg, bits, 0);
+}
+
+static int tc358746_sw_reset(struct tc358746 *tc358746)
+{
+       int err;
+
+       err = tc358746_set_bits(tc358746, SYSCTL_REG, SRESET);
+       if (err)
+               return err;
+
+       fsleep(10);
+
+       return tc358746_clear_bits(tc358746, SYSCTL_REG, SRESET);
+}
+
+static int
+tc358746_apply_pll_config(struct tc358746 *tc358746)
+{
+       u8 post = tc358746->pll_post_div;
+       u16 pre = tc358746->pll_pre_div;
+       u16 mul = tc358746->pll_mul;
+       u32 val, mask;
+       int err;
+
+       err = tc358746_read(tc358746, PLLCTL1_REG, &val);
+       if (err)
+               return err;
+
+       /* Don't touch the PLL if running */
+       if (FIELD_GET(PLL_EN, val) == 1)
+               return 0;
+
+       /* Pre-div and Multiplicator have a internal +1 logic */
+       val = PLL_PRD(pre - 1) | PLL_FBD(mul - 1);
+       mask = PLL_PRD_MASK | PLL_FBD_MASK;
+       err = tc358746_update_bits(tc358746, PLLCTL0_REG, mask, val);
+       if (err)
+               return err;
+
+       val = PLL_FRS(ilog2(post)) | RESETB | PLL_EN;
+       mask = PLL_FRS_MASK | RESETB | PLL_EN;
+       tc358746_update_bits(tc358746, PLLCTL1_REG, mask, val);
+       if (err)
+               return err;
+
+       fsleep(1000);
+
+       return tc358746_set_bits(tc358746, PLLCTL1_REG, CKEN);
+}
+
+static int tc358746_apply_misc_config(struct tc358746 *tc358746)
+{
+       const struct v4l2_mbus_framefmt *mbusfmt;
+       struct v4l2_subdev *sd = &tc358746->sd;
+       struct v4l2_subdev_state *sink_state;
+       const struct tc358746_format *fmt;
+       struct device *dev = sd->dev;
+       u32 val;
+       int err;
+
+       sink_state = v4l2_subdev_lock_and_get_active_state(sd);
+
+       mbusfmt = v4l2_subdev_get_pad_format(sd, sink_state, TC358746_SINK);
+       fmt = tc358746_get_format_by_code(TC358746_SINK, mbusfmt->code);
+
+       /* Self defined CSI user data type id's are not supported yet */
+       val = PDFMT(fmt->pdformat);
+       dev_dbg(dev, "DATAFMT: 0x%x\n", val);
+       err = tc358746_write(tc358746, DATAFMT_REG, val);
+       if (err)
+               goto out;
+
+       val = PDATAF(fmt->pdataf);
+       dev_dbg(dev, "CONFCTL[PDATAF]: 0x%x\n", fmt->pdataf);
+       err = tc358746_update_bits(tc358746, CONFCTL_REG, PDATAF_MASK, val);
+       if (err)
+               goto out;
+
+       val = tc358746->vb_size / 32;
+       dev_dbg(dev, "FIFOCTL: %u (0x%x)\n", val, val);
+       err = tc358746_write(tc358746, FIFOCTL_REG, val);
+       if (err)
+               goto out;
+
+       /* Total number of bytes for each line/width */
+       val = mbusfmt->width * fmt->bpp / 8;
+       dev_dbg(dev, "WORDCNT: %u (0x%x)\n", val, val);
+       err = tc358746_write(tc358746, WORDCNT_REG, val);
+
+out:
+       v4l2_subdev_unlock_state(sink_state);
+
+       return err;
+}
+
+/* Use MHz as base so the div needs no u64 */
+static u32 tc358746_cfg_to_cnt(unsigned int cfg_val,
+                              unsigned int clk_mhz,
+                              unsigned int time_base)
+{
+       return DIV_ROUND_UP(cfg_val * clk_mhz, time_base);
+}
+
+static u32 tc358746_ps_to_cnt(unsigned int cfg_val,
+                             unsigned int clk_mhz)
+{
+       return tc358746_cfg_to_cnt(cfg_val, clk_mhz, USEC_PER_SEC);
+}
+
+static u32 tc358746_us_to_cnt(unsigned int cfg_val,
+                             unsigned int clk_mhz)
+{
+       return tc358746_cfg_to_cnt(cfg_val, clk_mhz, 1);
+}
+
+static int tc358746_apply_dphy_config(struct tc358746 *tc358746)
+{
+       struct phy_configure_opts_mipi_dphy *cfg = &tc358746->dphy_cfg;
+       bool non_cont_clk = !!(tc358746->csi_vep.bus.mipi_csi2.flags &
+                              V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK);
+       struct device *dev = tc358746->sd.dev;
+       unsigned long hs_byte_clk, hf_clk;
+       u32 val, val2, lptxcnt;
+       int err;
+
+       /* The hs_byte_clk is also called SYSCLK in the excel sheet */
+       hs_byte_clk = cfg->hs_clk_rate / 8;
+       hs_byte_clk /= HZ_PER_MHZ;
+       hf_clk = hs_byte_clk / 2;
+
+       val = tc358746_us_to_cnt(cfg->init, hf_clk) - 1;
+       dev_dbg(dev, "LINEINITCNT: %u (0x%x)\n", val, val);
+       err = tc358746_write(tc358746, LINEINITCNT_REG, val);
+       if (err)
+               return err;
+
+       val = tc358746_ps_to_cnt(cfg->lpx, hs_byte_clk) - 1;
+       lptxcnt = val;
+       dev_dbg(dev, "LPTXTIMECNT: %u (0x%x)\n", val, val);
+       err = tc358746_write(tc358746, LPTXTIMECNT_REG, val);
+       if (err)
+               return err;
+
+       val = tc358746_ps_to_cnt(cfg->clk_prepare, hs_byte_clk) - 1;
+       val2 = tc358746_ps_to_cnt(cfg->clk_zero, hs_byte_clk) - 1;
+       dev_dbg(dev, "TCLK_PREPARECNT: %u (0x%x)\n", val, val);
+       dev_dbg(dev, "TCLK_ZEROCNT: %u (0x%x)\n", val2, val2);
+       dev_dbg(dev, "TCLK_HEADERCNT: 0x%x\n",
+               (u32)(TCLK_PREPARECNT(val) | TCLK_ZEROCNT(val2)));
+       err = tc358746_write(tc358746, TCLK_HEADERCNT_REG,
+                            TCLK_PREPARECNT(val) | TCLK_ZEROCNT(val2));
+       if (err)
+               return err;
+
+       val = tc358746_ps_to_cnt(cfg->clk_trail, hs_byte_clk);
+       dev_dbg(dev, "TCLK_TRAILCNT: %u (0x%x)\n", val, val);
+       err = tc358746_write(tc358746, TCLK_TRAILCNT_REG, val);
+       if (err)
+               return err;
+
+       val = tc358746_ps_to_cnt(cfg->hs_prepare, hs_byte_clk) - 1;
+       val2 = tc358746_ps_to_cnt(cfg->hs_zero, hs_byte_clk) - 1;
+       dev_dbg(dev, "THS_PREPARECNT: %u (0x%x)\n", val, val);
+       dev_dbg(dev, "THS_ZEROCNT: %u (0x%x)\n", val2, val2);
+       dev_dbg(dev, "THS_HEADERCNT: 0x%x\n",
+               (u32)(THS_PREPARECNT(val) | THS_ZEROCNT(val2)));
+       err = tc358746_write(tc358746, THS_HEADERCNT_REG,
+                            THS_PREPARECNT(val) | THS_ZEROCNT(val2));
+       if (err)
+               return err;
+
+       /* TWAKEUP > 1ms in lptxcnt steps */
+       val = tc358746_us_to_cnt(cfg->wakeup, hs_byte_clk);
+       val = val / (lptxcnt + 1) - 1;
+       dev_dbg(dev, "TWAKEUP: %u (0x%x)\n", val, val);
+       err = tc358746_write(tc358746, TWAKEUP_REG, val);
+       if (err)
+               return err;
+
+       val = tc358746_ps_to_cnt(cfg->clk_post, hs_byte_clk);
+       dev_dbg(dev, "TCLK_POSTCNT: %u (0x%x)\n", val, val);
+       err = tc358746_write(tc358746, TCLK_POSTCNT_REG, val);
+       if (err)
+               return err;
+
+       val = tc358746_ps_to_cnt(cfg->hs_trail, hs_byte_clk);
+       dev_dbg(dev, "THS_TRAILCNT: %u (0x%x)\n", val, val);
+       err = tc358746_write(tc358746, THS_TRAILCNT_REG, val);
+       if (err)
+               return err;
+
+       dev_dbg(dev, "CONTCLKMODE: %u", non_cont_clk ? 0 : 1);
+
+       return  tc358746_write(tc358746, TXOPTIONCNTRL_REG, non_cont_clk ? 0 : 1);
+}
+
+#define MAX_DATA_LANES 4
+
+static int tc358746_enable_csi_lanes(struct tc358746 *tc358746, int enable)
+{
+       unsigned int lanes = tc358746->dphy_cfg.lanes;
+       unsigned int lane;
+       u32 reg, val;
+       int err;
+
+       err = tc358746_update_bits(tc358746, CONFCTL_REG, DATALANE_MASK,
+                                  lanes - 1);
+       if (err)
+               return err;
+
+       /* Clock lane */
+       val = enable ? 0 : LANEDISABLE;
+       dev_dbg(tc358746->sd.dev, "CLW_CNTRL: 0x%x\n", val);
+       err = tc358746_write(tc358746, CLW_CNTRL_REG, val);
+       if (err)
+               return err;
+
+       for (lane = 0; lane < MAX_DATA_LANES; lane++) {
+               /* Data lanes */
+               reg = D0W_CNTRL_REG + lane * 0x4;
+               val = (enable && lane < lanes) ? 0 : LANEDISABLE;
+
+               dev_dbg(tc358746->sd.dev, "D%uW_CNTRL: 0x%x\n", lane, val);
+               err = tc358746_write(tc358746, reg, val);
+               if (err)
+                       return err;
+       }
+
+       val = 0;
+       if (enable) {
+               /* Clock lane */
+               val |= BIT(0);
+
+               /* Data lanes */
+               for (lane = 1; lane <= lanes; lane++)
+                       val |= BIT(lane);
+       }
+
+       dev_dbg(tc358746->sd.dev, "HSTXVREGEN: 0x%x\n", val);
+
+       return tc358746_write(tc358746, HSTXVREGEN_REG, val);
+}
+
+static int tc358746_enable_csi_module(struct tc358746 *tc358746, int enable)
+{
+       unsigned int lanes = tc358746->dphy_cfg.lanes;
+       int err;
+
+       /*
+        * START and STRT are only reseted/disabled by sw reset. This is
+        * required to put the lane state back into LP-11 state. The sw reset
+        * don't reset register values.
+        */
+       if (!enable)
+               return tc358746_sw_reset(tc358746);
+
+       err = tc358746_write(tc358746, STARTCNTRL_REG, START);
+       if (err)
+               return err;
+
+       err = tc358746_write(tc358746, CSI_START_REG, STRT);
+       if (err)
+               return err;
+
+       /* CSI_CONTROL_REG is only indirect accessible */
+       return tc358746_write(tc358746, CSI_CONFW_REG,
+                             MODE(MODE_SET) |
+                             ADDRESS(CSI_CONTROL_ADDRESS) |
+                             DATA(CSI_MODE | TXHSMD | NOL(lanes - 1)));
+}
+
+static int tc358746_enable_parallel_port(struct tc358746 *tc358746, int enable)
+{
+       int err;
+
+       if (enable) {
+               err = tc358746_write(tc358746, PP_MISC_REG, 0);
+               if (err)
+                       return err;
+
+               return tc358746_set_bits(tc358746, CONFCTL_REG, PPEN);
+       }
+
+       err = tc358746_set_bits(tc358746, PP_MISC_REG, FRMSTOP);
+       if (err)
+               return err;
+
+       err = tc358746_clear_bits(tc358746, CONFCTL_REG, PPEN);
+       if (err)
+               return err;
+
+       return tc358746_set_bits(tc358746, PP_MISC_REG, RSTPTR);
+}
+
+static inline struct v4l2_subdev *tc358746_get_remote_sd(struct media_pad *pad)
+{
+       pad = media_pad_remote_pad_first(pad);
+       if (!pad)
+               return NULL;
+
+       return media_entity_to_v4l2_subdev(pad->entity);
+}
+
+static int tc358746_s_stream(struct v4l2_subdev *sd, int enable)
+{
+       struct tc358746 *tc358746 = to_tc358746(sd);
+       struct v4l2_subdev *src;
+       int err;
+
+       dev_dbg(sd->dev, "%sable\n", enable ? "en" : "dis");
+
+       src = tc358746_get_remote_sd(&tc358746->pads[TC358746_SINK]);
+       if (!src)
+               return -EPIPE;
+
+       if (enable) {
+               err = pm_runtime_resume_and_get(sd->dev);
+               if (err)
+                       return err;
+
+               err = tc358746_apply_dphy_config(tc358746);
+               if (err)
+                       goto err_out;
+
+               err = tc358746_apply_misc_config(tc358746);
+               if (err)
+                       goto err_out;
+
+               err = tc358746_enable_csi_lanes(tc358746, 1);
+               if (err)
+                       goto err_out;
+
+               err = tc358746_enable_csi_module(tc358746, 1);
+               if (err)
+                       goto err_out;
+
+               err = tc358746_enable_parallel_port(tc358746, 1);
+               if (err)
+                       goto err_out;
+
+               err = v4l2_subdev_call(src, video, s_stream, 1);
+               if (err)
+                       goto err_out;
+
+               return 0;
+
+err_out:
+               pm_runtime_mark_last_busy(sd->dev);
+               pm_runtime_put_sync_autosuspend(sd->dev);
+
+               return err;
+       }
+
+       /*
+        * The lanes must be disabled first (before the csi module) so the
+        * LP-11 state is entered correctly.
+        */
+       err = tc358746_enable_csi_lanes(tc358746, 0);
+       if (err)
+               return err;
+
+       err = tc358746_enable_csi_module(tc358746, 0);
+       if (err)
+               return err;
+
+       err = tc358746_enable_parallel_port(tc358746, 0);
+       if (err)
+               return err;
+
+       pm_runtime_mark_last_busy(sd->dev);
+       pm_runtime_put_sync_autosuspend(sd->dev);
+
+       return v4l2_subdev_call(src, video, s_stream, 0);
+}
+
+static int tc358746_init_cfg(struct v4l2_subdev *sd,
+                            struct v4l2_subdev_state *state)
+{
+       struct v4l2_mbus_framefmt *fmt;
+
+       fmt = v4l2_subdev_get_pad_format(sd, state, TC358746_SINK);
+       *fmt = tc358746_def_fmt;
+
+       fmt = v4l2_subdev_get_pad_format(sd, state, TC358746_SOURCE);
+       *fmt = tc358746_def_fmt;
+       fmt->code = tc358746_src_mbus_code(tc358746_def_fmt.code);
+
+       return 0;
+}
+
+static int tc358746_enum_mbus_code(struct v4l2_subdev *sd,
+                                  struct v4l2_subdev_state *sd_state,
+                                  struct v4l2_subdev_mbus_code_enum *code)
+{
+       const struct tc358746_format *fmt;
+
+       fmt = tc358746_get_format_by_idx(code->pad, code->index);
+       if (IS_ERR(fmt))
+               return PTR_ERR(fmt);
+
+       code->code = fmt->code;
+
+       return 0;
+}
+
+static int tc358746_set_fmt(struct v4l2_subdev *sd,
+                           struct v4l2_subdev_state *sd_state,
+                           struct v4l2_subdev_format *format)
+{
+       struct v4l2_mbus_framefmt *src_fmt, *sink_fmt;
+       const struct tc358746_format *fmt;
+
+       /* Source follows the sink */
+       if (format->pad == TC358746_SOURCE)
+               return v4l2_subdev_get_fmt(sd, sd_state, format);
+
+       sink_fmt = v4l2_subdev_get_pad_format(sd, sd_state, TC358746_SINK);
+
+       fmt = tc358746_get_format_by_code(format->pad, format->format.code);
+       if (IS_ERR(fmt))
+               fmt = tc358746_get_format_by_code(format->pad, tc358746_def_fmt.code);
+
+       format->format.code = fmt->code;
+       format->format.field = V4L2_FIELD_NONE;
+
+       dev_dbg(sd->dev, "Update format: %ux%u code:0x%x -> %ux%u code:0x%x",
+               sink_fmt->width, sink_fmt->height, sink_fmt->code,
+               format->format.width, format->format.height, format->format.code);
+
+       *sink_fmt = format->format;
+
+       src_fmt = v4l2_subdev_get_pad_format(sd, sd_state, TC358746_SOURCE);
+       *src_fmt = *sink_fmt;
+       src_fmt->code = tc358746_src_mbus_code(sink_fmt->code);
+
+       return 0;
+}
+
+static unsigned long tc358746_find_pll_settings(struct tc358746 *tc358746,
+                                               unsigned long refclk,
+                                               unsigned long fout)
+
+{
+       struct device *dev = tc358746->sd.dev;
+       unsigned long best_freq = 0;
+       u32 min_delta = 0xffffffff;
+       u16 prediv_max = 17;
+       u16 prediv_min = 1;
+       u16 m_best, mul;
+       u16 p_best, p;
+       u8 postdiv;
+
+       if (fout > 1000 * HZ_PER_MHZ) {
+               dev_err(dev, "HS-Clock above 1 Ghz are not supported\n");
+               return 0;
+       }
+
+       if (fout >= 500 * HZ_PER_MHZ)
+               postdiv = 1;
+       else if (fout >= 250 * HZ_PER_MHZ)
+               postdiv = 2;
+       else if (fout >= 125 * HZ_PER_MHZ)
+               postdiv = 4;
+       else
+               postdiv = 8;
+
+       for (p = prediv_min; p <= prediv_max; p++) {
+               unsigned long delta, fin;
+               u64 tmp;
+
+               fin = DIV_ROUND_CLOSEST(refclk, p);
+               if (fin < 4 * HZ_PER_MHZ || fin > 40 * HZ_PER_MHZ)
+                       continue;
+
+               tmp = fout * p * postdiv;
+               do_div(tmp, fin);
+               mul = tmp;
+               if (mul > 511)
+                       continue;
+
+               tmp = mul * fin;
+               do_div(tmp, p * postdiv);
+
+               delta = abs(fout - tmp);
+               if (delta < min_delta) {
+                       p_best = p;
+                       m_best = mul;
+                       min_delta = delta;
+                       best_freq = tmp;
+               };
+
+               if (delta == 0)
+                       break;
+       };
+
+       if (!best_freq) {
+               dev_err(dev, "Failed find PLL frequency\n");
+               return 0;
+       }
+
+       tc358746->pll_post_div = postdiv;
+       tc358746->pll_pre_div = p_best;
+       tc358746->pll_mul = m_best;
+
+       if (best_freq != fout)
+               dev_warn(dev, "Request PLL freq:%lu, found PLL freq:%lu\n",
+                        fout, best_freq);
+
+       dev_dbg(dev, "Found PLL settings: freq:%lu prediv:%u multi:%u postdiv:%u\n",
+               best_freq, p_best, m_best, postdiv);
+
+       return best_freq;
+}
+
+#define TC358746_PRECISION 10
+
+static int
+tc358746_link_validate(struct v4l2_subdev *sd, struct media_link *link,
+                      struct v4l2_subdev_format *source_fmt,
+                      struct v4l2_subdev_format *sink_fmt)
+{
+       struct tc358746 *tc358746 = to_tc358746(sd);
+       unsigned long csi_bitrate, source_bitrate;
+       struct v4l2_subdev_state *sink_state;
+       struct v4l2_mbus_framefmt *mbusfmt;
+       const struct tc358746_format *fmt;
+       unsigned int fifo_sz, tmp, n;
+       struct v4l2_subdev *source;
+       s64 source_link_freq;
+       int err;
+
+       err = v4l2_subdev_link_validate_default(sd, link, source_fmt, sink_fmt);
+       if (err)
+               return err;
+
+       sink_state = v4l2_subdev_lock_and_get_active_state(sd);
+       mbusfmt = v4l2_subdev_get_pad_format(sd, sink_state, TC358746_SINK);
+
+       /* Check the FIFO settings */
+       fmt = tc358746_get_format_by_code(TC358746_SINK, mbusfmt->code);
+
+       source = media_entity_to_v4l2_subdev(link->source->entity);
+       source_link_freq = v4l2_get_link_freq(source->ctrl_handler, 0, 0);
+       if (source_link_freq <= 0) {
+               dev_err(tc358746->sd.dev,
+                       "Failed to query or invalid source link frequency\n");
+               v4l2_subdev_unlock_state(sink_state);
+               /* Return -EINVAL in case of source_link_freq is 0 */
+               return source_link_freq ? : -EINVAL;
+       }
+       source_bitrate = source_link_freq * fmt->bus_width;
+
+       csi_bitrate = tc358746->dphy_cfg.lanes * tc358746->pll_rate;
+
+       dev_dbg(tc358746->sd.dev,
+               "Fifo settings params: source-bitrate:%lu csi-bitrate:%lu",
+               source_bitrate, csi_bitrate);
+
+       /* Avoid possible FIFO overflows */
+       if (csi_bitrate < source_bitrate) {
+               v4l2_subdev_unlock_state(sink_state);
+               return -EINVAL;
+       }
+
+       /* Best case */
+       if (csi_bitrate == source_bitrate) {
+               fifo_sz = TC358746_VB_DEFAULT_SIZE;
+               tc358746->vb_size = TC358746_VB_DEFAULT_SIZE;
+               goto out;
+       }
+
+       /*
+        * Avoid possible FIFO underflow in case of
+        * csi_bitrate > source_bitrate. For such case the chip has a internal
+        * fifo which can be used to delay the line output.
+        *
+        * Fifo size calculation (excluding precision):
+        *
+        * fifo-sz, image-width - in bits
+        * sbr                  - source_bitrate in bits/s
+        * csir                 - csi_bitrate in bits/s
+        *
+        * image-width / csir >= (image-width - fifo-sz) / sbr
+        * image-width * sbr / csir >= image-width - fifo-sz
+        * fifo-sz >= image-width - image-width * sbr / csir; with n = csir/sbr
+        * fifo-sz >= image-width - image-width / n
+        */
+
+       source_bitrate /= TC358746_PRECISION;
+       n = csi_bitrate / source_bitrate;
+       tmp = (mbusfmt->width * TC358746_PRECISION) / n;
+       fifo_sz = mbusfmt->width - tmp;
+       fifo_sz *= fmt->bpp;
+       tc358746->vb_size = round_up(fifo_sz, 32);
+
+out:
+       dev_dbg(tc358746->sd.dev,
+               "Found FIFO size[bits]:%u -> aligned to size[bits]:%u\n",
+               fifo_sz, tc358746->vb_size);
+
+       v4l2_subdev_unlock_state(sink_state);
+
+       return tc358746->vb_size > TC358746_VB_MAX_SIZE ? -EINVAL : 0;
+}
+
+static int tc358746_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad,
+                                   struct v4l2_mbus_config *config)
+{
+       struct tc358746 *tc358746 = to_tc358746(sd);
+
+       if (pad != TC358746_SOURCE)
+               return -EINVAL;
+
+       config->type = V4L2_MBUS_CSI2_DPHY;
+       config->bus.mipi_csi2 = tc358746->csi_vep.bus.mipi_csi2;
+
+       return 0;
+}
+
+static int __maybe_unused
+tc358746_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg)
+{
+       struct tc358746 *tc358746 = to_tc358746(sd);
+
+       /* 32-bit registers starting from CLW_DPHYCONTTX */
+       reg->size = reg->reg < CLW_DPHYCONTTX_REG ? 2 : 4;
+
+       if (!pm_runtime_get_if_in_use(sd->dev))
+               return 0;
+
+       tc358746_read(tc358746, reg->reg, (u32 *)&reg->val);
+
+       pm_runtime_mark_last_busy(sd->dev);
+       pm_runtime_put_sync_autosuspend(sd->dev);
+
+       return 0;
+}
+
+static int __maybe_unused
+tc358746_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg)
+{
+       struct tc358746 *tc358746 = to_tc358746(sd);
+
+       if (!pm_runtime_get_if_in_use(sd->dev))
+               return 0;
+
+       tc358746_write(tc358746, (u32)reg->reg, (u32)reg->val);
+
+       pm_runtime_mark_last_busy(sd->dev);
+       pm_runtime_put_sync_autosuspend(sd->dev);
+
+       return 0;
+}
+
+static const struct v4l2_subdev_core_ops tc358746_core_ops = {
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+       .g_register = tc358746_g_register,
+       .s_register = tc358746_s_register,
+#endif
+};
+
+static const struct v4l2_subdev_video_ops tc358746_video_ops = {
+       .s_stream = tc358746_s_stream,
+};
+
+static const struct v4l2_subdev_pad_ops tc358746_pad_ops = {
+       .init_cfg = tc358746_init_cfg,
+       .enum_mbus_code = tc358746_enum_mbus_code,
+       .set_fmt = tc358746_set_fmt,
+       .get_fmt = v4l2_subdev_get_fmt,
+       .link_validate = tc358746_link_validate,
+       .get_mbus_config = tc358746_get_mbus_config,
+};
+
+static const struct v4l2_subdev_ops tc358746_ops = {
+       .core = &tc358746_core_ops,
+       .video = &tc358746_video_ops,
+       .pad = &tc358746_pad_ops,
+};
+
+static const struct media_entity_operations tc358746_entity_ops = {
+       .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
+       .link_validate = v4l2_subdev_link_validate,
+};
+
+static int tc358746_mclk_enable(struct clk_hw *hw)
+{
+       struct tc358746 *tc358746 = clk_hw_to_tc358746(hw);
+       unsigned int div;
+       u32 val;
+       int err;
+
+       div = tc358746->mclk_postdiv / 2;
+       val = MCLK_HIGH(div - 1) | MCLK_LOW(div - 1);
+       dev_dbg(tc358746->sd.dev, "MCLKCTL: %u (0x%x)\n", val, val);
+       err = tc358746_write(tc358746, MCLKCTL_REG, val);
+       if (err)
+               return err;
+
+       if (tc358746->mclk_prediv == 8)
+               val = MCLKDIV(MCLKDIV_8);
+       else if (tc358746->mclk_prediv == 4)
+               val = MCLKDIV(MCLKDIV_4);
+       else
+               val = MCLKDIV(MCLKDIV_2);
+
+       dev_dbg(tc358746->sd.dev, "CLKCTL[MCLKDIV]: %u (0x%x)\n", val, val);
+
+       return tc358746_update_bits(tc358746, CLKCTL_REG, MCLKDIV_MASK, val);
+}
+
+static void tc358746_mclk_disable(struct clk_hw *hw)
+{
+       struct tc358746 *tc358746 = clk_hw_to_tc358746(hw);
+
+       tc358746_write(tc358746, MCLKCTL_REG, 0);
+}
+
+static long
+tc358746_find_mclk_settings(struct tc358746 *tc358746, unsigned long mclk_rate)
+{
+       unsigned long pll_rate = tc358746->pll_rate;
+       const unsigned char prediv[] = { 2, 4, 8 };
+       unsigned int mclk_prediv, mclk_postdiv;
+       struct device *dev = tc358746->sd.dev;
+       unsigned int postdiv, mclkdiv;
+       unsigned long best_mclk_rate;
+       unsigned int i;
+
+       /*
+        *                          MCLK-Div
+        *           -------------------´`---------------------
+        *          Â´                                          `
+        *         +-------------+     +------------------------+
+        *         | MCLK-PreDiv |     |       MCLK-PostDiv     |
+        * PLL --> |   (2/4/8)   | --> | (mclk_low + mclk_high) | --> MCLK
+        *         +-------------+     +------------------------+
+        *
+        * The register value of mclk_low/high is mclk_low/high+1, i.e.:
+        *   mclk_low/high = 1   --> 2 MCLK-Ref Counts
+        *   mclk_low/high = 255 --> 256 MCLK-Ref Counts == max.
+        * If mclk_low and mclk_high are 0 then MCLK is disabled.
+        *
+        * Keep it simple and support 50/50 duty cycles only for now,
+        * so the calc will be:
+        *
+        *   MCLK = PLL / (MCLK-PreDiv * 2 * MCLK-PostDiv)
+        */
+
+       if (mclk_rate == tc358746->mclk_rate)
+               return mclk_rate;
+
+       /* Highest possible rate */
+       mclkdiv = pll_rate / mclk_rate;
+       if (mclkdiv <= 8) {
+               mclk_prediv = 2;
+               mclk_postdiv = 4;
+               best_mclk_rate = pll_rate / (2 * 4);
+               goto out;
+       }
+
+       /* First check the prediv */
+       for (i = 0; i < ARRAY_SIZE(prediv); i++) {
+               postdiv = mclkdiv / prediv[i];
+
+               if (postdiv % 2)
+                       continue;
+
+               if (postdiv >= 4 && postdiv <= 512) {
+                       mclk_prediv = prediv[i];
+                       mclk_postdiv = postdiv;
+                       best_mclk_rate = pll_rate / (prediv[i] * postdiv);
+                       goto out;
+               }
+       }
+
+       /* No suitable prediv found, so try to adjust the postdiv */
+       for (postdiv = 4; postdiv <= 512; postdiv += 2) {
+               unsigned int pre;
+
+               pre = mclkdiv / postdiv;
+               if (pre == 2 || pre == 4 || pre == 8) {
+                       mclk_prediv = pre;
+                       mclk_postdiv = postdiv;
+                       best_mclk_rate = pll_rate / (pre * postdiv);
+                       goto out;
+               }
+       }
+
+       /* The MCLK <-> PLL gap is to high -> use largest possible div */
+       mclk_prediv = 8;
+       mclk_postdiv = 512;
+       best_mclk_rate = pll_rate / (8 * 512);
+
+out:
+       tc358746->mclk_prediv = mclk_prediv;
+       tc358746->mclk_postdiv = mclk_postdiv;
+       tc358746->mclk_rate = best_mclk_rate;
+
+       if (best_mclk_rate != mclk_rate)
+               dev_warn(dev, "Request MCLK freq:%lu, found MCLK freq:%lu\n",
+                        mclk_rate, best_mclk_rate);
+
+       dev_dbg(dev, "Found MCLK settings: freq:%lu prediv:%u postdiv:%u\n",
+               best_mclk_rate, mclk_prediv, mclk_postdiv);
+
+       return best_mclk_rate;
+}
+
+static unsigned long
+tc358746_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
+{
+       struct tc358746 *tc358746 = clk_hw_to_tc358746(hw);
+       unsigned int prediv, postdiv;
+       u32 val;
+       int err;
+
+       err = tc358746_read(tc358746, MCLKCTL_REG, &val);
+       if (err)
+               return 0;
+
+       postdiv = FIELD_GET(MCLK_LOW_MASK, val) + 1;
+       postdiv += FIELD_GET(MCLK_HIGH_MASK, val) + 1;
+
+       err = tc358746_read(tc358746, CLKCTL_REG, &val);
+       if (err)
+               return 0;
+
+       prediv = FIELD_GET(MCLKDIV_MASK, val);
+       if (prediv == MCLKDIV_8)
+               prediv = 8;
+       else if (prediv == MCLKDIV_4)
+               prediv = 4;
+       else
+               prediv = 2;
+
+       return tc358746->pll_rate / (prediv * postdiv);
+}
+
+static long tc358746_mclk_round_rate(struct clk_hw *hw, unsigned long rate,
+                                    unsigned long *parent_rate)
+{
+       struct tc358746 *tc358746 = clk_hw_to_tc358746(hw);
+
+       *parent_rate = tc358746->pll_rate;
+
+       return tc358746_find_mclk_settings(tc358746, rate);
+}
+
+static int tc358746_mclk_set_rate(struct clk_hw *hw, unsigned long rate,
+                                 unsigned long parent_rate)
+{
+       struct tc358746 *tc358746 = clk_hw_to_tc358746(hw);
+
+       tc358746_find_mclk_settings(tc358746, rate);
+
+       return tc358746_mclk_enable(hw);
+}
+
+static const struct clk_ops tc358746_mclk_ops = {
+       .enable = tc358746_mclk_enable,
+       .disable = tc358746_mclk_disable,
+       .recalc_rate = tc358746_recalc_rate,
+       .round_rate = tc358746_mclk_round_rate,
+       .set_rate = tc358746_mclk_set_rate,
+};
+
+static int tc358746_setup_mclk_provider(struct tc358746 *tc358746)
+{
+       struct clk_init_data mclk_initdata = { };
+       struct device *dev = tc358746->sd.dev;
+       const char *mclk_name;
+       int err;
+
+       /* MCLK clk provider support is optional */
+       if (!device_property_present(dev, "#clock-cells"))
+               return 0;
+
+       /* Init to highest possibel MCLK */
+       tc358746->mclk_postdiv = 512;
+       tc358746->mclk_prediv = 8;
+
+       mclk_name = "tc358746-mclk";
+       device_property_read_string(dev, "clock-output-names", &mclk_name);
+
+       mclk_initdata.name = mclk_name;
+       mclk_initdata.ops = &tc358746_mclk_ops;
+       tc358746->mclk_hw.init = &mclk_initdata;
+
+       err = devm_clk_hw_register(dev, &tc358746->mclk_hw);
+       if (err) {
+               dev_err(dev, "Failed to register mclk provider\n");
+               return err;
+       }
+
+       err = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
+                                         &tc358746->mclk_hw);
+       if (err)
+               dev_err(dev, "Failed to add mclk provider\n");
+
+       return err;
+}
+
+static int
+tc358746_init_subdev(struct tc358746 *tc358746, struct i2c_client *client)
+{
+       struct v4l2_subdev *sd = &tc358746->sd;
+       int err;
+
+       v4l2_i2c_subdev_init(sd, client, &tc358746_ops);
+       sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+       sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
+       sd->entity.ops = &tc358746_entity_ops;
+
+       tc358746->pads[TC358746_SINK].flags = MEDIA_PAD_FL_SINK;
+       tc358746->pads[TC358746_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+       err = media_entity_pads_init(&sd->entity, TC358746_NR_PADS,
+                                    tc358746->pads);
+       if (err)
+               return err;
+
+       err = v4l2_subdev_init_finalize(sd);
+       if (err)
+               media_entity_cleanup(&sd->entity);
+
+       return err;
+}
+
+static int
+tc358746_init_output_port(struct tc358746 *tc358746, unsigned long refclk)
+{
+       struct device *dev = tc358746->sd.dev;
+       struct v4l2_fwnode_endpoint *vep;
+       unsigned long csi_link_rate;
+       struct fwnode_handle *ep;
+       unsigned char csi_lanes;
+       int err;
+
+       ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), TC358746_SOURCE,
+                                            0, 0);
+       if (!ep) {
+               dev_err(dev, "Missing endpoint node\n");
+               return -EINVAL;
+       }
+
+       /* Currently we only support 'parallel in' -> 'csi out' */
+       vep = &tc358746->csi_vep;
+       vep->bus_type = V4L2_MBUS_CSI2_DPHY;
+       err = v4l2_fwnode_endpoint_alloc_parse(ep, vep);
+       fwnode_handle_put(ep);
+       if (err) {
+               dev_err(dev, "Failed to parse source endpoint\n");
+               return err;
+       }
+
+       csi_lanes = vep->bus.mipi_csi2.num_data_lanes;
+       if (csi_lanes == 0 || csi_lanes > 4 ||
+           vep->nr_of_link_frequencies == 0) {
+               dev_err(dev, "error: Invalid CSI-2 settings\n");
+               err = -EINVAL;
+               goto err;
+       }
+
+       /* TODO: Add support to handle multiple link frequencies */
+       csi_link_rate = (unsigned long)vep->link_frequencies[0];
+       tc358746->pll_rate = tc358746_find_pll_settings(tc358746, refclk,
+                                                       csi_link_rate * 2);
+       if (!tc358746->pll_rate) {
+               err = -EINVAL;
+               goto err;
+       }
+
+       err = phy_mipi_dphy_get_default_config_for_hsclk(tc358746->pll_rate,
+                                               csi_lanes, &tc358746->dphy_cfg);
+       if (err)
+               goto err;
+
+       tc358746->vb_size = TC358746_VB_DEFAULT_SIZE;
+
+       return 0;
+
+err:
+       v4l2_fwnode_endpoint_free(vep);
+
+       return err;
+}
+
+static int tc358746_init_hw(struct tc358746 *tc358746)
+{
+       struct device *dev = tc358746->sd.dev;
+       unsigned int chipid;
+       u32 val;
+       int err;
+
+       err = pm_runtime_resume_and_get(dev);
+       if (err < 0) {
+               dev_err(dev, "Failed to resume the device\n");
+               return err;
+       }
+
+        /* Ensure that CSI interface is put into LP-11 state */
+       err = tc358746_sw_reset(tc358746);
+       if (err) {
+               pm_runtime_put_sync(dev);
+               dev_err(dev, "Failed to reset the device\n");
+               return err;
+       }
+
+       err = tc358746_read(tc358746, CHIPID_REG, &val);
+       pm_runtime_mark_last_busy(dev);
+       pm_runtime_put_sync_autosuspend(dev);
+       if (err)
+               return -ENODEV;
+
+       chipid = FIELD_GET(CHIPID, val);
+       if (chipid != 0x44) {
+               dev_err(dev, "Invalid chipid 0x%02x\n", chipid);
+               return -ENODEV;
+       }
+
+       return 0;
+}
+
+static int tc358746_init_controls(struct tc358746 *tc358746)
+{
+       u64 *link_frequencies = tc358746->csi_vep.link_frequencies;
+       struct v4l2_ctrl *ctrl;
+       int err;
+
+       err = v4l2_ctrl_handler_init(&tc358746->ctrl_hdl, 1);
+       if (err)
+               return err;
+
+       /*
+        * The driver currently supports only one link-frequency, regardless of
+        * the input from the firmware, see: tc358746_init_output_port(). So
+        * report only the first frequency from the array of possible given
+        * frequencies.
+        */
+       ctrl = v4l2_ctrl_new_int_menu(&tc358746->ctrl_hdl, NULL,
+                                     V4L2_CID_LINK_FREQ, 0, 0,
+                                     link_frequencies);
+       if (ctrl)
+               ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+       err = tc358746->ctrl_hdl.error;
+       if (err) {
+               v4l2_ctrl_handler_free(&tc358746->ctrl_hdl);
+               return err;
+       }
+
+       tc358746->sd.ctrl_handler = &tc358746->ctrl_hdl;
+
+       return 0;
+}
+
+static int tc358746_notify_bound(struct v4l2_async_notifier *notifier,
+                                struct v4l2_subdev *sd,
+                                struct v4l2_async_subdev *asd)
+{
+       struct tc358746 *tc358746 =
+               container_of(notifier, struct tc358746, notifier);
+       u32 flags = MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE;
+       struct media_pad *sink = &tc358746->pads[TC358746_SINK];
+
+       return v4l2_create_fwnode_links_to_pad(sd, sink, flags);
+}
+
+static const struct v4l2_async_notifier_operations tc358746_notify_ops = {
+       .bound = tc358746_notify_bound,
+};
+
+static int tc358746_async_register(struct tc358746 *tc358746)
+{
+       struct v4l2_fwnode_endpoint vep = {
+               .bus_type = V4L2_MBUS_PARALLEL,
+       };
+       struct v4l2_async_subdev *asd;
+       struct fwnode_handle *ep;
+       int err;
+
+       ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(tc358746->sd.dev),
+                                            TC358746_SINK, 0, 0);
+       if (!ep)
+               return -ENOTCONN;
+
+       err = v4l2_fwnode_endpoint_parse(ep, &vep);
+       if (err) {
+               fwnode_handle_put(ep);
+               return err;
+       }
+
+       v4l2_async_nf_init(&tc358746->notifier);
+       asd = v4l2_async_nf_add_fwnode_remote(&tc358746->notifier, ep,
+                                             struct v4l2_async_subdev);
+       fwnode_handle_put(ep);
+
+       if (IS_ERR(asd)) {
+               err = PTR_ERR(asd);
+               goto err_cleanup;
+       }
+
+       tc358746->notifier.ops = &tc358746_notify_ops;
+
+       err = v4l2_async_subdev_nf_register(&tc358746->sd, &tc358746->notifier);
+       if (err)
+               goto err_cleanup;
+
+       tc358746->sd.fwnode = fwnode_graph_get_endpoint_by_id(
+               dev_fwnode(tc358746->sd.dev), TC358746_SOURCE, 0, 0);
+
+       err = v4l2_async_register_subdev(&tc358746->sd);
+       if (err)
+               goto err_unregister;
+
+       return 0;
+
+err_unregister:
+       fwnode_handle_put(tc358746->sd.fwnode);
+       v4l2_async_nf_unregister(&tc358746->notifier);
+err_cleanup:
+       v4l2_async_nf_cleanup(&tc358746->notifier);
+
+       return err;
+}
+
+static int tc358746_probe(struct i2c_client *client)
+{
+       struct device *dev = &client->dev;
+       struct tc358746 *tc358746;
+       unsigned long refclk;
+       unsigned int i;
+       int err;
+
+       tc358746 = devm_kzalloc(&client->dev, sizeof(*tc358746), GFP_KERNEL);
+       if (!tc358746)
+               return -ENOMEM;
+
+       tc358746->regmap = devm_regmap_init_i2c(client, &tc358746_regmap_config);
+       if (IS_ERR(tc358746->regmap))
+               return dev_err_probe(dev, PTR_ERR(tc358746->regmap),
+                                    "Failed to init regmap\n");
+
+       tc358746->refclk = devm_clk_get(dev, "refclk");
+       if (IS_ERR(tc358746->refclk))
+               return dev_err_probe(dev, PTR_ERR(tc358746->refclk),
+                                    "Failed to get refclk\n");
+
+       err = clk_prepare_enable(tc358746->refclk);
+       if (err)
+               return dev_err_probe(dev, err,
+                                    "Failed to enable refclk\n");
+
+       refclk = clk_get_rate(tc358746->refclk);
+       clk_disable_unprepare(tc358746->refclk);
+
+       if (refclk < 6 * HZ_PER_MHZ || refclk > 40 * HZ_PER_MHZ)
+               return dev_err_probe(dev, -EINVAL, "Invalid refclk range\n");
+
+       for (i = 0; i < ARRAY_SIZE(tc358746_supplies); i++)
+               tc358746->supplies[i].supply = tc358746_supplies[i];
+
+       err = devm_regulator_bulk_get(dev, ARRAY_SIZE(tc358746_supplies),
+                                     tc358746->supplies);
+       if (err)
+               return dev_err_probe(dev, err, "Failed to get supplies\n");
+
+       tc358746->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+                                                      GPIOD_OUT_HIGH);
+       if (IS_ERR(tc358746->reset_gpio))
+               return dev_err_probe(dev, PTR_ERR(tc358746->reset_gpio),
+                                    "Failed to get reset-gpios\n");
+
+       err = tc358746_init_subdev(tc358746, client);
+       if (err)
+               return dev_err_probe(dev, err, "Failed to init subdev\n");
+
+       err = tc358746_init_output_port(tc358746, refclk);
+       if (err)
+               goto err_subdev;
+
+       /*
+        * Keep this order since we need the output port link-frequencies
+        * information.
+        */
+       err = tc358746_init_controls(tc358746);
+       if (err)
+               goto err_fwnode;
+
+       dev_set_drvdata(dev, tc358746);
+
+       /* Set to 1sec to give the stream reconfiguration enough time */
+       pm_runtime_set_autosuspend_delay(dev, 1000);
+       pm_runtime_use_autosuspend(dev);
+       pm_runtime_enable(dev);
+
+       err = tc358746_init_hw(tc358746);
+       if (err)
+               goto err_pm;
+
+       err = tc358746_setup_mclk_provider(tc358746);
+       if (err)
+               goto err_pm;
+
+       err = tc358746_async_register(tc358746);
+       if (err < 0)
+               goto err_pm;
+
+       dev_dbg(dev, "%s found @ 0x%x (%s)\n", client->name,
+               client->addr, client->adapter->name);
+
+       return 0;
+
+err_pm:
+       pm_runtime_disable(dev);
+       pm_runtime_set_suspended(dev);
+       pm_runtime_dont_use_autosuspend(dev);
+       v4l2_ctrl_handler_free(&tc358746->ctrl_hdl);
+err_fwnode:
+       v4l2_fwnode_endpoint_free(&tc358746->csi_vep);
+err_subdev:
+       v4l2_subdev_cleanup(&tc358746->sd);
+       media_entity_cleanup(&tc358746->sd.entity);
+
+       return err;
+}
+
+static void tc358746_remove(struct i2c_client *client)
+{
+       struct v4l2_subdev *sd = i2c_get_clientdata(client);
+       struct tc358746 *tc358746 = to_tc358746(sd);
+
+       v4l2_subdev_cleanup(sd);
+       v4l2_ctrl_handler_free(&tc358746->ctrl_hdl);
+       v4l2_fwnode_endpoint_free(&tc358746->csi_vep);
+       v4l2_async_nf_unregister(&tc358746->notifier);
+       v4l2_async_nf_cleanup(&tc358746->notifier);
+       fwnode_handle_put(sd->fwnode);
+       v4l2_async_unregister_subdev(sd);
+       media_entity_cleanup(&sd->entity);
+
+       pm_runtime_disable(sd->dev);
+       pm_runtime_set_suspended(sd->dev);
+       pm_runtime_dont_use_autosuspend(sd->dev);
+}
+
+static int tc358746_suspend(struct device *dev)
+{
+       struct tc358746 *tc358746 = dev_get_drvdata(dev);
+       int err;
+
+       clk_disable_unprepare(tc358746->refclk);
+
+       err = regulator_bulk_disable(ARRAY_SIZE(tc358746_supplies),
+                                    tc358746->supplies);
+       if (err)
+               clk_prepare_enable(tc358746->refclk);
+
+       return err;
+}
+
+static int tc358746_resume(struct device *dev)
+{
+       struct tc358746 *tc358746 = dev_get_drvdata(dev);
+       int err;
+
+       gpiod_set_value(tc358746->reset_gpio, 1);
+
+       err = regulator_bulk_enable(ARRAY_SIZE(tc358746_supplies),
+                                   tc358746->supplies);
+       if (err)
+               return err;
+
+       /* min. 200ns */
+       usleep_range(10, 20);
+
+       gpiod_set_value(tc358746->reset_gpio, 0);
+
+       err = clk_prepare_enable(tc358746->refclk);
+       if (err)
+               goto err;
+
+       /* min. 700us ... 1ms */
+       usleep_range(1000, 1500);
+
+       /*
+        * Enable the PLL here since it can be called by the clk-framework or by
+        * the .s_stream() callback. So this is the common place for both.
+        */
+       err = tc358746_apply_pll_config(tc358746);
+       if (err)
+               goto err_clk;
+
+       return 0;
+
+err_clk:
+       clk_disable_unprepare(tc358746->refclk);
+err:
+       regulator_bulk_disable(ARRAY_SIZE(tc358746_supplies),
+                              tc358746->supplies);
+       return err;
+}
+
+DEFINE_RUNTIME_DEV_PM_OPS(tc358746_pm_ops, tc358746_suspend,
+                         tc358746_resume, NULL);
+
+static const struct of_device_id __maybe_unused tc358746_of_match[] = {
+       { .compatible = "toshiba,tc358746" },
+       { },
+};
+MODULE_DEVICE_TABLE(of, tc358746_of_match);
+
+static struct i2c_driver tc358746_driver = {
+       .driver = {
+               .name = "tc358746",
+               .pm = pm_ptr(&tc358746_pm_ops),
+               .of_match_table = tc358746_of_match,
+       },
+       .probe_new = tc358746_probe,
+       .remove = tc358746_remove,
+};
+
+module_i2c_driver(tc358746_driver);
+
+MODULE_DESCRIPTION("Toshiba TC358746 Parallel to CSI-2 bridge driver");
+MODULE_AUTHOR("Marco Felsch <kernel@pengutronix.de>");
+MODULE_LICENSE("GPL");
index 52b43ea040302ee555ac6eed6cdd2235e8a53455..412213b0c384f74b22f7ff2f15a3bc0ada65af40 100644 (file)
@@ -1380,9 +1380,7 @@ static int subdev_notifier_bound(struct v4l2_async_notifier *notifier,
 
        /* Find platform data for this sensor subdev */
        for (i = 0; i < ARRAY_SIZE(fmd->sensor); i++)
-               if (fmd->sensor[i].asd &&
-                   fmd->sensor[i].asd->match.fwnode ==
-                   of_fwnode_handle(subdev->dev->of_node))
+               if (fmd->sensor[i].asd == asd)
                        si = &fmd->sensor[i];
 
        if (si == NULL)
index e22921e7ea61732fbaee4de19b9ee7564d8c0906..564fedee2c88ed3ad5d9480df05f1bc1d09f78f4 100644 (file)
@@ -1043,6 +1043,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_HDR_SENSOR_MODE:          return "HDR Sensor Mode";
 
        /* FM Radio Modulator controls */
        /* Keep the order of the 'case's the same as in v4l2-controls.h! */
@@ -1370,6 +1371,7 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type,
        case V4L2_CID_STATELESS_H264_START_CODE:
        case V4L2_CID_CAMERA_ORIENTATION:
        case V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE:
+       case V4L2_CID_HDR_SENSOR_MODE:
                *type = V4L2_CTRL_TYPE_MENU;
                break;
        case V4L2_CID_LINK_FREQ:
index 5c27bac772ea4330f6e25f5bde6dbc9fd58bf218..8a4ca2bd1584dc4aa2cc66d429ac04f3c75442a4 100644 (file)
@@ -318,6 +318,20 @@ static int call_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad,
               sd->ops->pad->get_mbus_config(sd, pad, config);
 }
 
+static int call_s_stream(struct v4l2_subdev *sd, int enable)
+{
+       int ret;
+
+       ret = sd->ops->video->s_stream(sd, enable);
+
+       if (!enable && ret < 0) {
+               dev_warn(sd->dev, "disabling streaming failed (%d)\n", ret);
+               return 0;
+       }
+
+       return ret;
+}
+
 #ifdef CONFIG_MEDIA_CONTROLLER
 /*
  * Create state-management wrapper for pad ops dealing with subdev state. The
@@ -377,6 +391,7 @@ static const struct v4l2_subdev_pad_ops v4l2_subdev_call_pad_wrappers = {
 static const struct v4l2_subdev_video_ops v4l2_subdev_call_video_wrappers = {
        .g_frame_interval       = call_g_frame_interval,
        .s_frame_interval       = call_s_frame_interval,
+       .s_stream               = call_s_stream,
 };
 
 const struct v4l2_subdev_ops v4l2_subdev_call_wrappers = {
index 929e86d6558e0be40c9ac41276535a4c06d9d1e8..f4956a417a47a7fd14b61d53f2436ddc1560bab3 100644 (file)
  * from the valid ranges specified in Section 6.9, Table 14, Page 41
  * of the D-PHY specification (v1.2).
  */
-int phy_mipi_dphy_get_default_config(unsigned long pixel_clock,
+static int phy_mipi_dphy_calc_config(unsigned long pixel_clock,
                                     unsigned int bpp,
                                     unsigned int lanes,
+                                    unsigned long long hs_clk_rate,
                                     struct phy_configure_opts_mipi_dphy *cfg)
 {
-       unsigned long long hs_clk_rate;
        unsigned long long ui;
 
        if (!cfg)
                return -EINVAL;
 
-       hs_clk_rate = pixel_clock * bpp;
-       do_div(hs_clk_rate, lanes);
+       if (!hs_clk_rate) {
+               hs_clk_rate = pixel_clock * bpp;
+               do_div(hs_clk_rate, lanes);
+       }
 
        ui = ALIGN(PSEC_PER_SEC, hs_clk_rate);
        do_div(ui, hs_clk_rate);
@@ -75,8 +77,29 @@ int phy_mipi_dphy_get_default_config(unsigned long pixel_clock,
 
        return 0;
 }
+
+int phy_mipi_dphy_get_default_config(unsigned long pixel_clock,
+                                    unsigned int bpp,
+                                    unsigned int lanes,
+                                    struct phy_configure_opts_mipi_dphy *cfg)
+{
+       return phy_mipi_dphy_calc_config(pixel_clock, bpp, lanes, 0, cfg);
+
+}
 EXPORT_SYMBOL(phy_mipi_dphy_get_default_config);
 
+int phy_mipi_dphy_get_default_config_for_hsclk(unsigned long long hs_clk_rate,
+                                              unsigned int lanes,
+                                              struct phy_configure_opts_mipi_dphy *cfg)
+{
+       if (!hs_clk_rate)
+               return -EINVAL;
+
+       return phy_mipi_dphy_calc_config(0, 0, lanes, hs_clk_rate, cfg);
+
+}
+EXPORT_SYMBOL(phy_mipi_dphy_get_default_config_for_hsclk);
+
 /*
  * Validate D-PHY configuration according to MIPI D-PHY specification
  * (v1.2, Section Section 6.9 "Global Operation Timing Parameters").
index ce13e746c15f30c5f4b7738ed968a69a86dc8d70..e530767e80a5d1cfca3854038dc755f2d388c667 100644 (file)
@@ -188,6 +188,28 @@ static int imgu_subdev_set_fmt(struct v4l2_subdev *sd,
        return 0;
 }
 
+static struct v4l2_rect *
+imgu_subdev_get_crop(struct imgu_v4l2_subdev *sd,
+                    struct v4l2_subdev_state *sd_state, unsigned int pad,
+                    enum v4l2_subdev_format_whence which)
+{
+       if (which == V4L2_SUBDEV_FORMAT_TRY)
+               return v4l2_subdev_get_try_crop(&sd->subdev, sd_state, pad);
+       else
+               return &sd->rect.eff;
+}
+
+static struct v4l2_rect *
+imgu_subdev_get_compose(struct imgu_v4l2_subdev *sd,
+                       struct v4l2_subdev_state *sd_state, unsigned int pad,
+                       enum v4l2_subdev_format_whence which)
+{
+       if (which == V4L2_SUBDEV_FORMAT_TRY)
+               return v4l2_subdev_get_try_compose(&sd->subdev, sd_state, pad);
+       else
+               return &sd->rect.bds;
+}
+
 static int imgu_subdev_get_selection(struct v4l2_subdev *sd,
                                     struct v4l2_subdev_state *sd_state,
                                     struct v4l2_subdev_selection *sel)
@@ -200,18 +222,12 @@ static int imgu_subdev_get_selection(struct v4l2_subdev *sd,
 
        switch (sel->target) {
        case V4L2_SEL_TGT_CROP:
-               if (sel->which == V4L2_SUBDEV_FORMAT_TRY)
-                       sel->r = *v4l2_subdev_get_try_crop(sd, sd_state,
-                                                          sel->pad);
-               else
-                       sel->r = imgu_sd->rect.eff;
+               sel->r = *imgu_subdev_get_crop(imgu_sd, sd_state, sel->pad,
+                                              sel->which);
                return 0;
        case V4L2_SEL_TGT_COMPOSE:
-               if (sel->which == V4L2_SUBDEV_FORMAT_TRY)
-                       sel->r = *v4l2_subdev_get_try_compose(sd, sd_state,
-                                                             sel->pad);
-               else
-                       sel->r = imgu_sd->rect.bds;
+               sel->r = *imgu_subdev_get_compose(imgu_sd, sd_state, sel->pad,
+                                                 sel->which);
                return 0;
        default:
                return -EINVAL;
@@ -223,10 +239,9 @@ static int imgu_subdev_set_selection(struct v4l2_subdev *sd,
                                     struct v4l2_subdev_selection *sel)
 {
        struct imgu_device *imgu = v4l2_get_subdevdata(sd);
-       struct imgu_v4l2_subdev *imgu_sd = container_of(sd,
-                                                       struct imgu_v4l2_subdev,
-                                                       subdev);
-       struct v4l2_rect *rect, *try_sel;
+       struct imgu_v4l2_subdev *imgu_sd =
+               container_of(sd, struct imgu_v4l2_subdev, subdev);
+       struct v4l2_rect *rect;
 
        dev_dbg(&imgu->pci_dev->dev,
                 "set subdev %u sel which %u target 0x%4x rect [%ux%u]",
@@ -238,22 +253,18 @@ static int imgu_subdev_set_selection(struct v4l2_subdev *sd,
 
        switch (sel->target) {
        case V4L2_SEL_TGT_CROP:
-               try_sel = v4l2_subdev_get_try_crop(sd, sd_state, sel->pad);
-               rect = &imgu_sd->rect.eff;
+               rect = imgu_subdev_get_crop(imgu_sd, sd_state, sel->pad,
+                                           sel->which);
                break;
        case V4L2_SEL_TGT_COMPOSE:
-               try_sel = v4l2_subdev_get_try_compose(sd, sd_state, sel->pad);
-               rect = &imgu_sd->rect.bds;
+               rect = imgu_subdev_get_compose(imgu_sd, sd_state, sel->pad,
+                                              sel->which);
                break;
        default:
                return -EINVAL;
        }
 
-       if (sel->which == V4L2_SUBDEV_FORMAT_TRY)
-               *try_sel = sel->r;
-       else
-               *rect = sel->r;
-
+       *rect = sel->r;
        return 0;
 }
 
index a877ffee845deb4f25dfa45a492c513caa383b65..1ac128d78dfeb2c12c0449134aa14a6f5f46eadc 100644 (file)
@@ -279,6 +279,9 @@ int phy_mipi_dphy_get_default_config(unsigned long pixel_clock,
                                     unsigned int bpp,
                                     unsigned int lanes,
                                     struct phy_configure_opts_mipi_dphy *cfg);
+int phy_mipi_dphy_get_default_config_for_hsclk(unsigned long long hs_clk_rate,
+                                              unsigned int lanes,
+                                              struct phy_configure_opts_mipi_dphy *cfg);
 int phy_mipi_dphy_config_validate(struct phy_configure_opts_mipi_dphy *cfg);
 
 #endif /* __PHY_MIPI_DPHY_H_ */
diff --git a/include/media/i2c/ov9650.h b/include/media/i2c/ov9650.h
deleted file mode 100644 (file)
index 3ec7e06..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-only */
-/*
- * OV9650/OV9652 camera sensors driver
- *
- * Copyright (C) 2013 Sylwester Nawrocki <sylvester.nawrocki@gmail.com>
- */
-#ifndef OV9650_H_
-#define OV9650_H_
-
-/**
- * struct ov9650_platform_data - ov9650 driver platform data
- * @mclk_frequency: the sensor's master clock frequency in Hz
- * @gpio_pwdn:     number of a GPIO connected to OV965X PWDN pin
- * @gpio_reset:     number of a GPIO connected to OV965X RESET pin
- *
- * If any of @gpio_pwdn or @gpio_reset are unused then they should be
- * set to a negative value. @mclk_frequency must always be specified.
- */
-struct ov9650_platform_data {
-       unsigned long mclk_frequency;
-       int gpio_pwdn;
-       int gpio_reset;
-};
-#endif /* OV9650_H_ */
index 28c9de8a1f3484aa13b66d862e6a792cb5336995..85ed08ddee9d2d5362dd09861c0ecf09bdd017d1 100644 (file)
@@ -237,7 +237,7 @@ struct media_pad {
  * @link_validate:     Return whether a link is valid from the entity point of
  *                     view. The media_pipeline_start() function
  *                     validates all links by calling this operation. Optional.
- * @has_pad_interdep:  Return whether a two pads inside the entity are
+ * @has_pad_interdep:  Return whether two pads of the entity are
  *                     interdependent. If two pads are interdependent they are
  *                     part of the same pipeline and enabling one of the pads
  *                     means that the other pad will become "locked" and
@@ -1144,7 +1144,7 @@ __must_check int __media_pipeline_start(struct media_pad *pad,
  * media_pipeline_stop - Mark a pipeline as not streaming
  * @pad: Starting pad
  *
- * Mark all pads connected to a given pads through enabled links, either
+ * Mark all pads connected to a given pad through enabled links, either
  * directly or indirectly, as not streaming. The media_pad pipe field is
  * reset to %NULL.
  *
index 2f80c9c818ed0246eaf995c4a36ea04b948af1cf..b15fa9930f30c16e5512fb8544c2cce9691c8bbe 100644 (file)
@@ -176,7 +176,10 @@ struct v4l2_subdev_io_pin_config {
  * @s_register: callback for VIDIOC_DBG_S_REGISTER() ioctl handler code.
  *
  * @s_power: puts subdevice in power saving mode (on == 0) or normal operation
- *     mode (on == 1).
+ *     mode (on == 1). DEPRECATED. See
+ *     Documentation/driver-api/media/camera-sensor.rst . pre_streamon and
+ *     post_streamoff callbacks can be used for e.g. setting the bus to LP-11
+ *     mode before s_stream is called.
  *
  * @interrupt_service_routine: Called by the bridge chip's interrupt service
  *     handler, when an interrupt status has be raised due to this subdev,
@@ -437,8 +440,10 @@ enum v4l2_subdev_pre_streamon_flags {
  * @g_input_status: get input status. Same as the status field in the
  *     &struct v4l2_input
  *
- * @s_stream: used to notify the driver that a video stream will start or has
- *     stopped.
+ * @s_stream: start (enabled == 1) or stop (enabled == 0) streaming on the
+ *     sub-device. Failure on stop will remove any resources acquired in
+ *     streaming start, while the error code is still returned by the driver.
+ *     Also see call_s_stream wrapper in v4l2-subdev.c.
  *
  * @g_pixelaspect: callback to return the pixelaspect ratio.
  *
index ec3323dbb927e306ce3665071f2db5167def8173..ca9a24c874dac9fe6285a266b3f4e88ffd8cb03b 100644 (file)
@@ -69,7 +69,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
@@ -92,6 +92,7 @@
 #define MEDIA_BUS_FMT_YUYV12_2X12              0x201e
 #define MEDIA_BUS_FMT_YVYU12_2X12              0x201f
 #define MEDIA_BUS_FMT_Y14_1X14                 0x202d
+#define MEDIA_BUS_FMT_Y16_1X16                 0x202e
 #define MEDIA_BUS_FMT_UYVY8_1X16               0x200f
 #define MEDIA_BUS_FMT_VYUY8_1X16               0x2010
 #define MEDIA_BUS_FMT_YUYV8_1X16               0x2011
index b5e7d082b8adf65a25d71d520af633d0739763fc..d27e255ed33b382ab67af64cb4fda3fe82f6f13d 100644 (file)
@@ -1019,6 +1019,8 @@ enum v4l2_auto_focus_range {
 
 #define V4L2_CID_CAMERA_SENSOR_ROTATION                (V4L2_CID_CAMERA_CLASS_BASE+35)
 
+#define V4L2_CID_HDR_SENSOR_MODE               (V4L2_CID_CAMERA_CLASS_BASE+36)
+
 /* FM Modulator class control IDs */
 
 #define V4L2_CID_FM_TX_CLASS_BASE              (V4L2_CTRL_CLASS_FM_TX | 0x900)