diff options
author | Mauro Carvalho Chehab <mchehab@kernel.org> | 2022-11-08 08:46:21 +0000 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@kernel.org> | 2022-11-08 08:46:21 +0000 |
commit | 1e284ea984d3705e042b6b07469a66f1d43371e3 (patch) | |
tree | 2fe050eb9ed16f2d1250fb318e0644ebd8351625 | |
parent | dbc1fdcbe2745054b041488e3b097dd0c4760115 (diff) | |
parent | 7336c54a562b479866d2de2abc61487a4e07b0b9 (diff) |
Merge git://linuxtv.org/sailus/media_tree into media_stage
* 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, 9327 insertions, 524 deletions
diff --git a/Documentation/devicetree/bindings/media/i2c/imx290.txt b/Documentation/devicetree/bindings/media/i2c/imx290.txt deleted file mode 100644 index a3cc21410f7c..000000000000 --- a/Documentation/devicetree/bindings/media/i2c/imx290.txt +++ /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 index 000000000000..50579c947f3c --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/ovti,ov4689.yaml @@ -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 index 000000000000..21377daae026 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/sony,imx290.yaml @@ -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 index 000000000000..6597e1d0e65f --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml @@ -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 index 000000000000..b8ba85a2416c --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/toshiba,tc358746.yaml @@ -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 index 000000000000..213b884dcfa6 --- /dev/null +++ b/Documentation/userspace-api/media/drivers/st-vgxy61.rst @@ -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. diff --git a/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst b/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst index 4c5061aa9cd4..daa4f40869f8 100644 --- a/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst +++ b/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst @@ -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. diff --git a/Documentation/userspace-api/media/v4l/subdev-formats.rst b/Documentation/userspace-api/media/v4l/subdev-formats.rst index d21d532eee15..16ef3b41932e 100644 --- a/Documentation/userspace-api/media/v4l/subdev-formats.rst +++ b/Documentation/userspace-api/media/v4l/subdev-formats.rst @@ -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 diff --git a/MAINTAINERS b/MAINTAINERS index 046ff06ff97f..aa1974054fce 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -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 diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 7806d4b81716..49c1c27afdc1 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -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 diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 0a2933103dd9..ba28a8f8a07f 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -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 diff --git a/drivers/media/i2c/ad5820.c b/drivers/media/i2c/ad5820.c index 516de278cc49..a12fedcc3a1c 100644 --- a/drivers/media/i2c/ad5820.c +++ b/drivers/media/i2c/ad5820.c @@ -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; } diff --git a/drivers/media/i2c/dw9768.c b/drivers/media/i2c/dw9768.c index 0f47ef015a1d..83a3ee275bbe 100644 --- a/drivers/media/i2c/dw9768.c +++ b/drivers/media/i2c/dw9768.c @@ -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[] = { diff --git a/drivers/media/i2c/hi846.c b/drivers/media/i2c/hi846.c index c5b69823f257..7c61873b7198 100644 --- a/drivers/media/i2c/hi846.c +++ b/drivers/media/i2c/hi846.c @@ -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) diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c index 1ce64dcdf7f0..218ded13fd80 100644 --- a/drivers/media/i2c/imx290.c +++ b/drivers/media/i2c/imx290.c @@ -22,22 +22,135 @@ #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, ®val); - 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 index 000000000000..b4ade17a83f5 --- /dev/null +++ b/drivers/media/i2c/ov08x40.c @@ -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 *)®_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"); diff --git a/drivers/media/i2c/ov2640.c b/drivers/media/i2c/ov2640.c index 29ed0ef8c033..39d56838a4ef 100644 --- a/drivers/media/i2c/ov2640.c +++ b/drivers/media/i2c/ov2640.c @@ -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 index 000000000000..c602e507d42b --- /dev/null +++ b/drivers/media/i2c/ov4689.c @@ -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 *)®_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"); diff --git a/drivers/media/i2c/ov5645.c b/drivers/media/i2c/ov5645.c index 81e4e87e1821..47451238ca05 100644 --- a/drivers/media/i2c/ov5645.c +++ b/drivers/media/i2c/ov5645.c @@ -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); diff --git a/drivers/media/i2c/ov5648.c b/drivers/media/i2c/ov5648.c index 84604ea7bdf9..17465fcf28e3 100644 --- a/drivers/media/i2c/ov5648.c +++ b/drivers/media/i2c/ov5648.c @@ -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 = { diff --git a/drivers/media/i2c/ov7670.c b/drivers/media/i2c/ov7670.c index 4b9b156b53c7..11d3bef65d43 100644 --- a/drivers/media/i2c/ov7670.c +++ b/drivers/media/i2c/ov7670.c @@ -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> diff --git a/drivers/media/i2c/ov8856.c b/drivers/media/i2c/ov8856.c index efa18d026ac3..cf8384e09413 100644 --- a/drivers/media/i2c/ov8856.c +++ b/drivers/media/i2c/ov8856.c @@ -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 diff --git a/drivers/media/i2c/ov9650.c b/drivers/media/i2c/ov9650.c index 4d458993e6d6..7e7cb1e4520e 100644 --- a/drivers/media/i2c/ov9650.c +++ b/drivers/media/i2c/ov9650.c @@ -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 index 000000000000..dfbf25338160 --- /dev/null +++ b/drivers/media/i2c/st-vgxy61.c @@ -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 index 000000000000..171309c62bb8 --- /dev/null +++ b/drivers/media/i2c/tc358746.c @@ -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 *)®->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"); diff --git a/drivers/media/platform/samsung/exynos4-is/media-dev.c b/drivers/media/platform/samsung/exynos4-is/media-dev.c index 52b43ea04030..412213b0c384 100644 --- a/drivers/media/platform/samsung/exynos4-is/media-dev.c +++ b/drivers/media/platform/samsung/exynos4-is/media-dev.c @@ -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) diff --git a/drivers/media/v4l2-core/v4l2-ctrls-defs.c b/drivers/media/v4l2-core/v4l2-ctrls-defs.c index e22921e7ea61..564fedee2c88 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-defs.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-defs.c @@ -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: diff --git a/drivers/media/v4l2-core/v4l2-subdev.c b/drivers/media/v4l2-core/v4l2-subdev.c index 5c27bac772ea..8a4ca2bd1584 100644 --- a/drivers/media/v4l2-core/v4l2-subdev.c +++ b/drivers/media/v4l2-core/v4l2-subdev.c @@ -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 = { diff --git a/drivers/phy/phy-core-mipi-dphy.c b/drivers/phy/phy-core-mipi-dphy.c index 929e86d6558e..f4956a417a47 100644 --- a/drivers/phy/phy-core-mipi-dphy.c +++ b/drivers/phy/phy-core-mipi-dphy.c @@ -17,19 +17,21 @@ * 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"). diff --git a/drivers/staging/media/ipu3/ipu3-v4l2.c b/drivers/staging/media/ipu3/ipu3-v4l2.c index ce13e746c15f..e530767e80a5 100644 --- a/drivers/staging/media/ipu3/ipu3-v4l2.c +++ b/drivers/staging/media/ipu3/ipu3-v4l2.c @@ -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; } diff --git a/include/linux/phy/phy-mipi-dphy.h b/include/linux/phy/phy-mipi-dphy.h index a877ffee845d..1ac128d78dfe 100644 --- a/include/linux/phy/phy-mipi-dphy.h +++ b/include/linux/phy/phy-mipi-dphy.h @@ -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 index 3ec7e06955b4..000000000000 --- a/include/media/i2c/ov9650.h +++ /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_ */ diff --git a/include/media/media-entity.h b/include/media/media-entity.h index 28c9de8a1f34..85ed08ddee9d 100644 --- a/include/media/media-entity.h +++ b/include/media/media-entity.h @@ -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. * diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h index 2f80c9c818ed..b15fa9930f30 100644 --- a/include/media/v4l2-subdev.h +++ b/include/media/v4l2-subdev.h @@ -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. * diff --git a/include/uapi/linux/media-bus-format.h b/include/uapi/linux/media-bus-format.h index ec3323dbb927..ca9a24c874da 100644 --- a/include/uapi/linux/media-bus-format.h +++ b/include/uapi/linux/media-bus-format.h @@ -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 diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h index b5e7d082b8ad..d27e255ed33b 100644 --- a/include/uapi/linux/v4l2-controls.h +++ b/include/uapi/linux/v4l2-controls.h @@ -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) |