diff --git a/Documentation/admin-guide/media/em28xx-cardlist.rst b/Documentation/admin-guide/media/em28xx-cardlist.rst index 741aa7f7c882..df2595b439aa 100644 --- a/Documentation/admin-guide/media/em28xx-cardlist.rst +++ b/Documentation/admin-guide/media/em28xx-cardlist.rst @@ -450,3 +450,7 @@ EM28xx cards list - StarTech SVID2USB232 - em28281 - eb1a:8286 + * - 114 + - StarTech.com SVID2USB23 + - em2860 + - eb1a:5051 diff --git a/Documentation/admin-guide/media/vivid.rst b/Documentation/admin-guide/media/vivid.rst index 034ca7c77fb9..6741f25220fc 100644 --- a/Documentation/admin-guide/media/vivid.rst +++ b/Documentation/admin-guide/media/vivid.rst @@ -6,7 +6,7 @@ The Virtual Video Test Driver (vivid) This driver emulates video4linux hardware of various types: video capture, video output, vbi capture and output, metadata capture and output, radio receivers and transmitters, touch capture and a software defined radio receiver. In addition a -simple framebuffer device is available for testing capture and output overlays. +simple framebuffer device is available for testing output overlays. Up to 64 vivid instances can be created, each with up to 16 inputs and 16 outputs. @@ -35,7 +35,7 @@ This document describes the features implemented by this driver: - Raw and Sliced VBI capture and output support - Radio receiver and transmitter support, including RDS support - Software defined radio (SDR) support -- Capture and output overlay support +- Output overlay support - Metadata capture and output support - Touch capture support diff --git a/Documentation/devicetree/bindings/clock/nxp,imx95-blk-ctl.yaml b/Documentation/devicetree/bindings/clock/nxp,imx95-blk-ctl.yaml index 27403b4c52d6..fbbf1b3f1790 100644 --- a/Documentation/devicetree/bindings/clock/nxp,imx95-blk-ctl.yaml +++ b/Documentation/devicetree/bindings/clock/nxp,imx95-blk-ctl.yaml @@ -39,6 +39,18 @@ properties: ID in its "clocks" phandle cell. See include/dt-bindings/clock/nxp,imx95-clock.h + '#address-cells': + const: 1 + + '#size-cells': + const: 1 + +patternProperties: + '^formatter@[0-9a-f]+$': + type: object + $ref: /schemas/media/fsl,imx95-csi-formatter.yaml# + unevaluatedProperties: false + required: - compatible - reg @@ -46,6 +58,23 @@ required: - power-domains - clocks +allOf: + - if: + properties: + compatible: + contains: + const: nxp,imx95-camera-csr + then: + required: + - '#address-cells' + - '#size-cells' + else: + properties: + '#address-cells': false + '#size-cells': false + patternProperties: + '^formatter@[0-9a-f]+$': false + additionalProperties: false examples: @@ -57,4 +86,46 @@ examples: clocks = <&scmi_clk 114>; power-domains = <&scmi_devpd 21>; }; + + - | + #include + + syscon@4ac10000 { + compatible = "nxp,imx95-camera-csr", "syscon"; + reg = <0x4ac10000 0x10000>; + #address-cells = <1>; + #size-cells = <1>; + #clock-cells = <1>; + clocks = <&scmi_clk 62>; + power-domains = <&scmi_devpd 3>; + + formatter@20 { + compatible = "fsl,imx95-csi-formatter"; + reg = <0x20 0x100>; + clocks = <&cameramix_csr IMX95_CLK_CAMBLK_CSI2_FOR0>; + power-domains = <&scmi_devpd 3>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + endpoint { + remote-endpoint = <&mipi_csi_0_out>; + }; + + }; + + port@1 { + reg = <1>; + + endpoint { + remote-endpoint = <&isi_in_2>; + }; + }; + }; + }; + }; ... diff --git a/Documentation/devicetree/bindings/media/allwinner,sun4i-a10-video-engine.yaml b/Documentation/devicetree/bindings/media/allwinner,sun4i-a10-video-engine.yaml index 01f2afa023f0..20c430795134 100644 --- a/Documentation/devicetree/bindings/media/allwinner,sun4i-a10-video-engine.yaml +++ b/Documentation/devicetree/bindings/media/allwinner,sun4i-a10-video-engine.yaml @@ -24,6 +24,7 @@ properties: - allwinner,sun50i-a64-video-engine - allwinner,sun50i-h5-video-engine - allwinner,sun50i-h6-video-engine + - allwinner,sun50i-h616-video-engine reg: maxItems: 1 @@ -48,14 +49,21 @@ properties: allwinner,sram: $ref: /schemas/types.yaml#/definitions/phandle-array + minItems: 1 items: - items: - description: phandle to SRAM - description: register value for device - description: Phandle to the device SRAM + - items: + - description: phandle to SRAM alias + - description: register value for device + description: Phandle to the device SRAM(s) iommus: - maxItems: 1 + minItems: 1 + items: + - description: VE port on IOMMU + - description: VE_R port on IOMMU memory-region: maxItems: 1 @@ -82,6 +90,39 @@ required: - resets - allwinner,sram +allOf: + - if: + properties: + compatible: + contains: + enum: + - allwinner,sun50i-h6-video-engine + - allwinner,sun50i-h616-video-engine + then: + properties: + iommus: + minItems: 2 + else: + properties: + iommus: + maxItems: 1 + + - if: + properties: + compatible: + contains: + enum: + - allwinner,sun50i-h6-video-engine + - allwinner,sun50i-h616-video-engine + then: + properties: + allwinner,sram: + minItems: 2 + else: + properties: + allwinner,sram: + maxItems: 1 + additionalProperties: false examples: diff --git a/Documentation/devicetree/bindings/media/fsl,imx95-csi-formatter.yaml b/Documentation/devicetree/bindings/media/fsl,imx95-csi-formatter.yaml new file mode 100644 index 000000000000..58c4e1cc056b --- /dev/null +++ b/Documentation/devicetree/bindings/media/fsl,imx95-csi-formatter.yaml @@ -0,0 +1,88 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/fsl,imx95-csi-formatter.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: i.MX95 CSI Pixel Formatter + +maintainers: + - Guoniu Zhou + +description: + The CSI pixel formatting module found on i.MX95 uses packet info, pixel + and non-pixel data from the CSI-2 host controller and reformat them to + match Pixel Link(PL) definition. + +properties: + compatible: + const: fsl,imx95-csi-formatter + + reg: + maxItems: 1 + description: Register offset and size within the parent syscon + + clocks: + maxItems: 1 + + power-domains: + maxItems: 1 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/properties/port + description: + Input port, connects to MIPI CSI-2 receiver output (IDI interface) + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: + Output port, connects to ISI input via Pixel Link (PL) + + required: + - port@0 + - port@1 + +required: + - compatible + - reg + - clocks + - power-domains + - ports + +additionalProperties: false + +examples: + - | + #include + + formatter@20 { + compatible = "fsl,imx95-csi-formatter"; + reg = <0x20 0x100>; + clocks = <&cameramix_csr IMX95_CLK_CAMBLK_CSI2_FOR0>; + power-domains = <&scmi_devpd 3>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + endpoint { + remote-endpoint = <&mipi_csi_0_out>; + }; + }; + + port@1 { + reg = <1>; + + endpoint { + remote-endpoint = <&isi_in_2>; + }; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/media/i2c/himax,hm1246.yaml b/Documentation/devicetree/bindings/media/i2c/himax,hm1246.yaml new file mode 100644 index 000000000000..9994105552e7 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/himax,hm1246.yaml @@ -0,0 +1,121 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright 2025 Matthias Fend +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/himax,hm1246.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Himax HM1246-AWD 1/3.7-Inch megapixel SoC image sensor + +maintainers: + - Matthias Fend + +description: + The Himax HM1246-AWD is a 1/3.7-Inch CMOS image sensor SoC with an active + array size of 1296 x 976. It is programmable through an I2C interface and + connected via parallel bus. + +allOf: + - $ref: /schemas/media/video-interface-devices.yaml# + +properties: + compatible: + const: himax,hm1246 + + reg: + maxItems: 1 + + clocks: + description: Input reference clock (6 - 27 MHz) + maxItems: 1 + + reset-gpios: + description: Active low XSHUTDOWN pin + maxItems: 1 + + avdd-supply: + description: Power for analog circuit (3.0 - 3.6 V) + + iovdd-supply: + description: Power for I/O circuit (1.7 - 3.6 V) + + dvdd-supply: + description: Power for digital circuit (1.5 / 1.8 V) + + port: + $ref: /schemas/graph.yaml#/$defs/port-base + additionalProperties: false + description: Parallel video output port + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + bus-width: + default: 10 + + hsync-active: + default: 1 + + vsync-active: + default: 1 + + pclk-sample: + default: 0 + + required: + - link-frequencies + + required: + - endpoint + +required: + - compatible + - reg + - clocks + - avdd-supply + - iovdd-supply + - dvdd-supply + - port + +unevaluatedProperties: false + +examples: + - | + #include + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + sensor@24 { + compatible = "himax,hm1246"; + reg = <0x24>; + + clocks = <&hm1246_clk>; + + reset-gpios = <&gpio0 0 GPIO_ACTIVE_LOW>; + + avdd-supply = <&hm1246_avdd>; + iovdd-supply = <&hm1246_iovdd>; + dvdd-supply = <&hm1246_dvdd>; + + orientation = ; + rotation = <0>; + + port { + endpoint { + remote-endpoint = <&isp_par_in>; + bus-width = <10>; + hsync-active = <1>; /* active high */ + vsync-active = <1>; /* active high */ + pclk-sample = <1>; /* sample on rising edge */ + link-frequencies = /bits/ 64 <42200000>; + }; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/media/i2c/hynix,hi846.yaml b/Documentation/devicetree/bindings/media/i2c/hynix,hi846.yaml index 1a57f2aa1982..b7bc6ba26e6e 100644 --- a/Documentation/devicetree/bindings/media/i2c/hynix,hi846.yaml +++ b/Documentation/devicetree/bindings/media/i2c/hynix,hi846.yaml @@ -86,6 +86,7 @@ unevaluatedProperties: false examples: - | #include + #include i2c { #address-cells = <1>; @@ -102,7 +103,7 @@ examples: vddio-supply = <®_camera_vddio>; reset-gpios = <&gpio1 25 GPIO_ACTIVE_LOW>; shutdown-gpios = <&gpio5 4 GPIO_ACTIVE_LOW>; - orientation = <0>; + orientation = ; rotation = <0>; port { diff --git a/Documentation/devicetree/bindings/media/i2c/ite,it6625.yaml b/Documentation/devicetree/bindings/media/i2c/ite,it6625.yaml new file mode 100644 index 000000000000..9b621f726249 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/ite,it6625.yaml @@ -0,0 +1,177 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/ite,it6625.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ITE IT6625/IT6626 HDMI to dual MIPI CSI-2 bridge + +maintainers: + - Hermes Wu + +description: + The ITE IT6625 and IT6626 are HDMI to MIPI CSI-2 bridge devices. + IT6625 supports an HDMI 2.0 input and converts it to one or two D-PHY CSI + outputs, while IT6626 supports an HDMI 2.1 input and converts it to one or + two C/D-PHY CSI outputs. The bridges are programmable through I2C and + expose two selectable CSI-2 output ports and an HDMI input port. The + bridge can operate in split mode or clone mode. + +properties: + compatible: + enum: + - ite,it6625 + - ite,it6626 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + reset-gpios: + description: + GPIO connected to the active-low reset line. + maxItems: 1 + + vcc10-supply: + description: 1.0V core supply + + vdd33-supply: + description: 3.3V supply + + ovdd-supply: + description: I/O supply voltage + + ports: + $ref: /schemas/graph.yaml#/properties/ports + properties: + port@0: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: CSI-2 output port MIPI0 + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + minItems: 1 + maxItems: 4 + + bus-type: + enum: + - 1 # MEDIA_BUS_TYPE_CSI2_CPHY + - 4 # MEDIA_BUS_TYPE_CSI2_DPHY + default: 4 # MEDIA_BUS_TYPE_CSI2_DPHY + + required: + - data-lanes + + port@1: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: CSI-2 output port MIPI1 + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + minItems: 1 + maxItems: 4 + + bus-type: + enum: + - 1 # MEDIA_BUS_TYPE_CSI2_CPHY + - 4 # MEDIA_BUS_TYPE_CSI2_DPHY + default: 4 # MEDIA_BUS_TYPE_CSI2_DPHY + + required: + - data-lanes + + port@2: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: HDMI input port + + required: + - port@0 + +required: + - compatible + - reg + - ports + - vcc10-supply + - vdd33-supply + - ovdd-supply + +allOf: + - if: + properties: + compatible: + contains: + const: ite,it6626 + then: + properties: + ports: + properties: + port@0: + properties: + endpoint: + required: + - bus-type + port@1: + properties: + endpoint: + required: + - bus-type + +additionalProperties: false + +examples: + - | + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + hdmi-bridge@4c { + compatible = "ite,it6625"; + reg = <0x4c>; + interrupts = <3 IRQ_TYPE_LEVEL_LOW>; + + reset-gpios = <&gpio 2 GPIO_ACTIVE_LOW>; + + vcc10-supply = <&vcc10_reg>; + vdd33-supply = <&vdd33_reg>; + ovdd-supply = <&ovdd_reg>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + csi_out0: endpoint { + remote-endpoint = <&csi2_rx0>; + bus-type = <4>; /* MEDIA_BUS_TYPE_CSI2_DPHY */ + data-lanes = <1 2 3 4>; + }; + }; + + port@2 { + reg = <2>; + endpoint { + remote-endpoint = <&hdmi_con>; + }; + }; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max9296a.yaml b/Documentation/devicetree/bindings/media/i2c/maxim,max9296a.yaml new file mode 100644 index 000000000000..de6bfcec5dc6 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/maxim,max9296a.yaml @@ -0,0 +1,242 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (C) 2024 Collabora Ltd. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/maxim,max9296a.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Maxim MAX9296A GMSL2 to CSI-2 Deserializer + +maintainers: + - Cosmin Tanislav + +description: > + The MAX9296A deserializer converts single or dual serial inputs to + MIPI CSI-2 outputs. The GMSL2 links operate at a fixed rate of 3Gbps + or 6Gbps in the forward direction and 187.5Mbps in the reverse + direction. In GMSL1 mode, each serial link can be paired with 3.12Gbps + or 1.5Gbps GMSL1 serializers or operate up to 4.5Gbps with GMSL2 + serializers with GMSL1 backward compatibility. The MAX9296A supports + mixed GMSL2 and GMSL1 links. The serial inputs operate independently, + allowing videos with different timings and resolutions to be received + on each input. + + MAX96716A supports both tunnel and pixel mode. + + MAX96792A supports both tunnel and pixel mode, and has two GMSL3 links. + +properties: + compatible: + enum: + - maxim,max9296a + - maxim,max96716a + - maxim,max96792a + + reg: + maxItems: 1 + + powerdown-gpios: + maxItems: 1 + description: Specifier for the GPIO connected to the PWDNB pin. + + port0-poc-supply: + description: Regulator providing Power over Coax for GMSL port 0 + + port1-poc-supply: + description: Regulator providing Power over Coax for GMSL port 1 + + i2c-alias-pool: + maxItems: 2 + + i2c-atr: + type: object + additionalProperties: false + + properties: + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + patternProperties: + '^i2c@[0-1]$': + $ref: /schemas/i2c/i2c-controller.yaml# + unevaluatedProperties: false + properties: + reg: + items: + minimum: 0 + maximum: 1 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + patternProperties: + '^port@[0-1]$': + $ref: /schemas/graph.yaml#/properties/port + description: GMSL Input ports 0-1 + + '^port@[2-3]$': + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: CSI-2 Output ports 0-1 + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + minItems: 1 + maxItems: 4 + + lane-polarities: + minItems: 2 + maxItems: 5 + + link-frequencies: + maxItems: 1 + + required: + - data-lanes + + anyOf: + - required: + - port@2 + - required: + - port@3 + +required: + - compatible + - reg + - ports + +additionalProperties: false + +allOf: + - $ref: /schemas/i2c/i2c-atr.yaml# + +dependentRequired: + i2c-atr: [i2c-alias-pool] + i2c-alias-pool: [i2c-atr] + +examples: + - | + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + deserializer@28 { + compatible = "maxim,max9296a"; + reg = <0x28>; + powerdown-gpios = <&main_gpio0 37 GPIO_ACTIVE_LOW>; + + i2c-alias-pool = <0x40 0x41>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + des_gmsl_in_0: endpoint { + remote-endpoint = <&ser_0_gmsl_out>; + }; + }; + + port@1 { + reg = <1>; + des_gmsl_in_1: endpoint { + remote-endpoint = <&ser_1_gmsl_out>; + }; + }; + + port@2 { + reg = <2>; + des_csi_out: endpoint { + data-lanes = <1 2 3 4>; + link-frequencies = /bits/ 64 <400000000>; + remote-endpoint = <&csi_in>; + }; + }; + }; + + i2c-atr { + #address-cells = <1>; + #size-cells = <0>; + + i2c@0 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + + serializer@40 { + compatible = "maxim,max96717", "maxim,max96717f"; + reg = <0x40>; + gpio-controller; + #gpio-cells = <2>; + #clock-cells = <0>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + ser_0_csi_in: endpoint { + data-lanes = <1 2>; + remote-endpoint = <&sensor_0_out>; + }; + }; + + port@1 { + reg = <1>; + ser_0_gmsl_out: endpoint { + remote-endpoint = <&des_gmsl_in_0>; + }; + }; + }; + }; + }; + + i2c@1 { + #address-cells = <1>; + #size-cells = <0>; + reg = <1>; + + serializer@40 { + compatible = "maxim,max96717", "maxim,max96717f"; + reg = <0x40>; + gpio-controller; + #gpio-cells = <2>; + #clock-cells = <0>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + ser_1_csi_in: endpoint { + data-lanes = <1 2>; + remote-endpoint = <&sensor_1_out>; + }; + }; + + port@1 { + reg = <1>; + ser_1_gmsl_out: endpoint { + remote-endpoint = <&des_gmsl_in_1>; + }; + }; + }; + }; + }; + }; + }; + }; +... diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml b/Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml index 26f85151afbd..ab1dcd8ff8e7 100644 --- a/Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml +++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml @@ -23,12 +23,17 @@ description: | MAX96712 can be paired with first-generation 3.12Gbps or 1.5Gbps GMSL1 serializers or operate up to 3.12Gbps with GMSL2 serializers in GMSL1 mode. + MAX96724F and MAX96724R only support a fixed rate of 3Gbps in the forward + direction. + properties: compatible: items: - enum: - maxim,max96712 - maxim,max96724 + - maxim,max96724f + - maxim,max96724r reg: description: I2C device address @@ -36,30 +41,51 @@ properties: enable-gpios: true + maxim,control-channel-port: + description: + Selects which deserializer control-channel port is connected to the + upstream I2C segment when the device resets. 0 selects port 0, 1 selects + port 1. Defaults to 0 when omitted. + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1] + default: 0 + + i2c-alias-pool: + maxItems: 4 + + i2c-atr: + type: object + additionalProperties: false + + properties: + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + patternProperties: + '^i2c@[0-3]$': + $ref: /schemas/i2c/i2c-controller.yaml# + unevaluatedProperties: false + properties: + reg: + items: + minimum: 0 + maximum: 3 + ports: $ref: /schemas/graph.yaml#/properties/ports - properties: - port@0: + patternProperties: + '^port@[0-3]$': $ref: /schemas/graph.yaml#/properties/port - description: GMSL Input 0 + description: GMSL Input ports 0-3 - port@1: - $ref: /schemas/graph.yaml#/properties/port - description: GMSL Input 1 - - port@2: - $ref: /schemas/graph.yaml#/properties/port - description: GMSL Input 2 - - port@3: - $ref: /schemas/graph.yaml#/properties/port - description: GMSL Input 3 - - port@4: + '^port@[4-7]$': $ref: /schemas/graph.yaml#/$defs/port-base unevaluatedProperties: false - description: CSI-2 Output + description: CSI-2 Output port 0-3 properties: endpoint: @@ -77,8 +103,15 @@ properties: - data-lanes - bus-type - required: - - port@4 + anyOf: + - required: [port@4] + - required: [port@5] + - required: [port@6] + - required: [port@7] + +patternProperties: + '^port[0-3]-poc-supply$': + description: Regulator providing Power over Coax for GMSL ports required: - compatible @@ -87,6 +120,13 @@ required: additionalProperties: false +allOf: + - $ref: /schemas/i2c/i2c-atr.yaml# + +dependentRequired: + i2c-atr: [i2c-alias-pool] + i2c-alias-pool: [i2c-atr] + examples: - | #include diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96714.yaml b/Documentation/devicetree/bindings/media/i2c/maxim,max96714.yaml index 3ace50e11921..ec6a91c4a51c 100644 --- a/Documentation/devicetree/bindings/media/i2c/maxim,max96714.yaml +++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96714.yaml @@ -22,7 +22,9 @@ description: The GMSL2 serial link operates at a fixed rate of 3Gbps or 6Gbps in the forward direction and 187.5Mbps in the reverse direction. - MAX96714F only supports a fixed rate of 3Gbps in the forward direction. + + MAX96714F and MAX96714R only support a fixed rate of 3Gbps in the forward + direction. properties: compatible: @@ -31,6 +33,7 @@ properties: - items: - enum: - maxim,max96714 + - maxim,max96714r - const: maxim,max96714f reg: diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml b/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml index d1e8ba6e368e..eea0f974874f 100644 --- a/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml +++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml @@ -24,15 +24,23 @@ description: The GMSL2 serial link operates at a fixed rate of 3Gbps or 6Gbps in the forward direction and 187.5Mbps in the reverse direction. + MAX96717F only supports a fixed rate of 3Gbps in the forward direction. + MAX9295A only supports pixel mode. + + MAX96793 also supports GMSL3 mode. + properties: compatible: oneOf: - - const: maxim,max96717f + - enum: + - maxim,max9295a + - maxim,max96717f - items: - enum: - maxim,max96717 + - maxim,max96793 - const: maxim,max96717f '#gpio-cells': @@ -91,13 +99,166 @@ properties: incoming GMSL2 link. Therefore, it supports an i2c-gate subnode to configure a sensor. + i2c-alias-pool: + maxItems: 2 + + i2c-atr: + type: object + additionalProperties: false + + properties: + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + patternProperties: + '^i2c@[01]$': + $ref: /schemas/i2c/i2c-controller.yaml# + unevaluatedProperties: false + properties: + reg: + items: + minimum: 0 + maximum: 1 + required: - compatible - reg - ports +patternProperties: + '-pins$': + type: object + additionalProperties: false + + properties: + function: + enum: [gpio, rclkout] + + pins: true + drive-open-drain: true + drive-push-pull: true + bias-disable: true + output-disable: true + output-enable: true + output-low: true + output-high: true + input-enable: true + + slew-rate: + description: | + Slew rate. + Rise and fall times represent the time needed for a GPIO to go + from 20% to 80% of VDDIO. + 0 - Fastest + rise: 1.0ns @ 1.8V, 0.6ns @ 3.3V, + fall: 0.8ns @ 1.8V, 0.5ns @ 3.3V + 1 - Fast + rise: 2.1ns @ 1.8V, 1.1ns @ 3.3V, + fall: 2.0ns @ 1.8V, 1.1ns @ 3.3V + 2 - Slow + rise: 4.0ns @ 1.8V, 2.3ns @3.3V, + fall: 10.0ns @ 1.8V, 5.0ns @3.3V + 3 - Slowest + rise: 9.0ns @ 1.8V, 5.0ns @3.3V, + fall: 10.0ns @ 1.8V, 5.0ns @3.3V + maximum: 3 + + bias-pull-up: + oneOf: + - type: boolean + description: Enable regular 40kOhm pull-up + - enum: [ 40000, 1000000 ] + description: Enable either the 40kOhm or the 1MOhm pull-up + + bias-pull-down: + oneOf: + - type: boolean + description: Enable regular 40kOhm pull-down + - enum: [ 40000, 1000000 ] + description: Enable either the 40kOhm or the 1MOhm pull-down + + maxim,jitter-compensation: + type: boolean + description: | + Enables jitter compensation. + Jitter compensation is used to minimize the jitter of the + signals transmitted from the deserializer to the serializer + by adding a fixed delay to every transition on the serializer + side. This can be used for pulse generation where timing is + critical. + + maxim,tx-id: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Enable transmission of the pin state from the serializer to + the deserializer using the specified identifier. + maximum: 31 + + maxim,rx-id: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Enable transmission of the pin state from the deserializer to + the serializer using the specified identifier. + maximum: 31 + + required: + - pins + - function + + allOf: + - $ref: /schemas/pinctrl/pincfg-node.yaml# + - $ref: /schemas/pinctrl/pinmux-node.yaml# + + - if: + properties: + function: + const: gpio + then: + properties: + pins: + items: + enum: [mfp0, mfp1, mfp2, mfp3, mfp4, mfp5, mfp6, mfp7, + mfp8, mfp9, mfp10] + + - if: + properties: + function: + const: rclkout + then: + properties: + pins: + items: + enum: [mfp2, mfp4] + + - if: + required: + - slew-rate + then: + properties: + pins: + items: + enum: [mfp0, mfp1, mfp2, mfp3, mfp4, mfp7, mfp8] + additionalProperties: false +allOf: + - $ref: /schemas/i2c/i2c-atr.yaml# + + - anyOf: + - oneOf: + - required: [i2c-atr] + - required: [i2c-gate] + + - not: + required: [i2c-atr, i2c-gate] + +dependentRequired: + i2c-atr: [i2c-alias-pool] + i2c-alias-pool: [i2c-atr] + examples: - | #include diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,og0ve1b.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,og0ve1b.yaml index bd2f1ae23e65..b6fa9645c7c6 100644 --- a/Documentation/devicetree/bindings/media/i2c/ovti,og0ve1b.yaml +++ b/Documentation/devicetree/bindings/media/i2c/ovti,og0ve1b.yaml @@ -4,14 +4,13 @@ $id: http://devicetree.org/schemas/media/i2c/ovti,og0ve1b.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: OmniVision OG0VE1B Image Sensor +title: OmniVision OG0VA1B and OG0VE1B Image Sensors description: - OmniVision OG0VE1B image sensor is a low power consuming monochrome - image sensor. The sensor is controlled over a serial camera control - bus protocol (SCCB), the widest supported image size is 640x480 at - 120 frames per second rate, data output format is 8/10-bit RAW - transferred over one-lane MIPI D-PHY at up to 800 Mbps. + OmniVision OG0VA1B and OG0VE1B are low power consuming monochrome image + sensors. The sensors are controlled over a serial camera control bus + protocol (SCCB), the widest supported image size is 640x480, data output + format is 8/10-bit RAW transferred over one-lane MIPI D-PHY. maintainers: - Vladimir Zapolskiy @@ -21,7 +20,9 @@ allOf: properties: compatible: - const: ovti,og0ve1b + enum: + - ovti,og0va1b + - ovti,og0ve1b reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,os02g10.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,os02g10.yaml new file mode 100644 index 000000000000..e240974dcc9a --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/ovti,os02g10.yaml @@ -0,0 +1,94 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/ovti,os02g10.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: OmniVision OS02G10 Image Sensor + +maintainers: + - Tarang Raval + +description: + The OmniVision OS02G10 is a 2MP (1920x1080) color CMOS image sensor controlled + through an I2C-compatible SCCB bus. It outputs RAW10 format data and supports + a 2-lane MIPI interface. + +properties: + compatible: + const: ovti,os02g10 + + reg: + maxItems: 1 + + clocks: + items: + - description: XCLK clock + + avdd-supply: + description: Analog Domain Power Supply (2.8v) + + dovdd-supply: + description: I/O Domain Power Supply (1.8v) + + dvdd-supply: + description: Digital core Power Supply (1.5v) + + reset-gpios: + maxItems: 1 + description: Reset Pin GPIO Control (active low) + + port: + description: MIPI CSI-2 transmitter port + $ref: /schemas/graph.yaml#/$defs/port-base + additionalProperties: false + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + required: + - link-frequencies + + required: + - endpoint + +required: + - compatible + - reg + - clocks + - avdd-supply + - dovdd-supply + - dvdd-supply + - port + +additionalProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + camera-sensor@3c { + compatible = "ovti,os02g10"; + reg = <0x3c>; + clocks = <&os02g10_clk>; + reset-gpios = <&gpio1 7 GPIO_ACTIVE_LOW>; + + avdd-supply = <&os02g10_avdd_2v8>; + dvdd-supply = <&os02g10_dvdd_1v5>; + dovdd-supply = <&os02g10_dovdd_1v8>; + + port { + cam_out: endpoint { + remote-endpoint = <&mipi_in_cam>; + data-lanes = <1 2>; + link-frequencies = /bits/ 64 <720000000>; + }; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,ov08d10.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,ov08d10.yaml index 6f2017c75125..b9c61395b24f 100644 --- a/Documentation/devicetree/bindings/media/i2c/ovti,ov08d10.yaml +++ b/Documentation/devicetree/bindings/media/i2c/ovti,ov08d10.yaml @@ -69,6 +69,7 @@ examples: - | #include #include + #include i2c { #address-cells = <1>; @@ -84,7 +85,7 @@ examples: avdd-supply = <&ov08d10_vdda_2v8>; dvdd-supply = <&ov08d10_vddd_1v2>; - orientation = <2>; + orientation = ; rotation = <0>; reset-gpios = <&gpio 1 GPIO_ACTIVE_LOW>; diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,ov4689.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,ov4689.yaml index d96199031b66..fcd617848ce3 100644 --- a/Documentation/devicetree/bindings/media/i2c/ovti,ov4689.yaml +++ b/Documentation/devicetree/bindings/media/i2c/ovti,ov4689.yaml @@ -96,6 +96,7 @@ unevaluatedProperties: false examples: - | #include + #include i2c { #address-cells = <1>; @@ -114,7 +115,7 @@ examples: powerdown-gpios = <&pio 107 GPIO_ACTIVE_LOW>; reset-gpios = <&pio 109 GPIO_ACTIVE_LOW>; - orientation = <2>; + orientation = ; rotation = <0>; port { diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,ov5675.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,ov5675.yaml index ad07204057f9..6df62fd0c0c0 100644 --- a/Documentation/devicetree/bindings/media/i2c/ovti,ov5675.yaml +++ b/Documentation/devicetree/bindings/media/i2c/ovti,ov5675.yaml @@ -85,6 +85,7 @@ examples: - | #include #include + #include #include i2c { @@ -108,7 +109,7 @@ examples: dovdd-supply = <&vcc_2v8>; rotation = <90>; - orientation = <0>; + orientation = ; port { ucam_out: endpoint { diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,ov5693.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,ov5693.yaml index 3368b3bd8ef2..b2eb63d7d40a 100644 --- a/Documentation/devicetree/bindings/media/i2c/ovti,ov5693.yaml +++ b/Documentation/devicetree/bindings/media/i2c/ovti,ov5693.yaml @@ -82,6 +82,8 @@ properties: unevaluatedProperties: false properties: + clock-noncontinuous: true + link-frequencies: true data-lanes: @@ -103,6 +105,7 @@ examples: - | #include #include + #include #include i2c { @@ -126,7 +129,7 @@ examples: dovdd-supply = <&vcc_2v8>; rotation = <90>; - orientation = <0>; + orientation = ; port { ucam_out: endpoint { diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,ov64a40.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,ov64a40.yaml index 2b6143aff391..24787c9aa155 100644 --- a/Documentation/devicetree/bindings/media/i2c/ovti,ov64a40.yaml +++ b/Documentation/devicetree/bindings/media/i2c/ovti,ov64a40.yaml @@ -72,6 +72,7 @@ unevaluatedProperties: false examples: - | #include + #include i2c { #address-cells = <1>; @@ -87,7 +88,7 @@ examples: powerdown-gpios = <&gpio1 9 GPIO_ACTIVE_HIGH>; reset-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>; rotation = <180>; - orientation = <2>; + orientation = ; port { endpoint { diff --git a/Documentation/devicetree/bindings/media/i2c/samsung,s5kjn5.yaml b/Documentation/devicetree/bindings/media/i2c/samsung,s5kjn5.yaml new file mode 100644 index 000000000000..fedb274679cd --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/samsung,s5kjn5.yaml @@ -0,0 +1,117 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/samsung,s5kjn5.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Samsung S5KJN5 Image Sensor + +description: + Samsung S5KJN5 is a 50MP 10-bit RAW MIPI CSI-2 image sensor. + The sensor is controlled over I2C. The full resolution pixel array is + 8192x6144, data output format is RAW10 transferred over 4-lane MIPI + D-PHY interface. + +maintainers: + - Wenmeng Liu + +allOf: + - $ref: /schemas/media/video-interface-devices.yaml# + +properties: + compatible: + const: samsung,s5kjn5 + + reg: + maxItems: 1 + + clocks: + description: MCLK/XVCLK input clock. + maxItems: 1 + + reset-gpios: + description: Active low GPIO connected to RESET pad of the sensor. + maxItems: 1 + + vddio-supply: + description: + 1.8 volt I/O voltage supply (VDD18). Required for both 1.8 and 1.2 volt + I/O operation. + + vddd-supply: + description: Digital core voltage supply, 1.0 volts. + + vddm-supply: + description: MIPI voltage supply, 1.0 volts. + + vdda-supply: + description: Analogue voltage supply, 2.2 volts. + + vddio1p2-supply: + description: + 1.2 volt I/O voltage supply (VDDIO12). Required only for 1.2 volt I/O + operation. + + afvdd-supply: + description: Autofocus actuator voltage supply, 2.8-3.0 volts. + + port: + $ref: /schemas/graph.yaml#/$defs/port-base + additionalProperties: false + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + items: + - const: 1 + - const: 2 + - const: 3 + - const: 4 + + required: + - link-frequencies + +required: + - compatible + - reg + - clocks + - port + +unevaluatedProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + camera@10 { + compatible = "samsung,s5kjn5"; + reg = <0x10>; + clocks = <&camera_mclk 0>; + assigned-clocks = <&camera_mclk 0>; + assigned-clock-rates = <19200000>; + reset-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>; + vddio-supply = <&vreg_1p8>; + vddd-supply = <&vreg_1p0>; + vddm-supply = <&vreg_1p0>; + vdda-supply = <&vreg_2p2>; + vddio1p2-supply = <&vreg_1p2>; + afvdd-supply = <&vreg_2p8>; + + port { + endpoint { + link-frequencies = /bits/ 64 <1248000000>; + data-lanes = <1 2 3 4>; + remote-endpoint = <&mipi_csi2_ep>; + }; + }; + }; + }; +... diff --git a/Documentation/devicetree/bindings/media/i2c/sony,imx111.yaml b/Documentation/devicetree/bindings/media/i2c/sony,imx111.yaml index 20f48d5e9b2d..56fb5f18f07b 100644 --- a/Documentation/devicetree/bindings/media/i2c/sony,imx111.yaml +++ b/Documentation/devicetree/bindings/media/i2c/sony,imx111.yaml @@ -69,6 +69,7 @@ examples: - | #include #include + #include i2c { #address-cells = <1>; @@ -84,7 +85,7 @@ examples: dvdd-supply = <&camera_vddd_1v2>; avdd-supply = <&camera_vdda_2v7>; - orientation = <1>; + orientation = ; rotation = <90>; nvmem = <&eeprom>; diff --git a/Documentation/devicetree/bindings/media/i2c/sony,imx355.yaml b/Documentation/devicetree/bindings/media/i2c/sony,imx355.yaml index d9cdfda699bf..6699c238ee0e 100644 --- a/Documentation/devicetree/bindings/media/i2c/sony,imx355.yaml +++ b/Documentation/devicetree/bindings/media/i2c/sony,imx355.yaml @@ -81,6 +81,7 @@ examples: - | #include #include + #include i2c { #address-cells = <1>; @@ -105,7 +106,7 @@ examples: pinctrl-0 = <&cam_front_default>; rotation = <270>; - orientation = <0>; + orientation = ; port { cam_front_endpoint: endpoint { diff --git a/Documentation/devicetree/bindings/media/i2c/sony,imx415.yaml b/Documentation/devicetree/bindings/media/i2c/sony,imx415.yaml index 7c11e871dca6..69a37ff68db3 100644 --- a/Documentation/devicetree/bindings/media/i2c/sony,imx415.yaml +++ b/Documentation/devicetree/bindings/media/i2c/sony,imx415.yaml @@ -86,6 +86,7 @@ unevaluatedProperties: false examples: - | #include + #include i2c { #address-cells = <1>; @@ -98,7 +99,7 @@ examples: clocks = <&clock_cam>; dvdd-supply = <&vcc1v1_cam>; lens-focus = <&vcm>; - orientation = <2>; + orientation = ; ovdd-supply = <&vcc1v8_cam>; reset-gpios = <&gpio_expander 14 GPIO_ACTIVE_LOW>; rotation = <180>; diff --git a/Documentation/devicetree/bindings/media/i2c/sony,imx576.yaml b/Documentation/devicetree/bindings/media/i2c/sony,imx576.yaml new file mode 100644 index 000000000000..c82efd17eb7f --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/sony,imx576.yaml @@ -0,0 +1,114 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/sony,imx576.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Sony IMX576 Image Sensor + +maintainers: + - Hardevsinh Palaniya + +description: + IMX576 sensor is a Sony CMOS active pixel digital image sensor with an active + array size of 5760(H) × 4312(V). It is programmable through an I2C interface. + Image data is transmitted through MIPI CSI-2. It supports RAW10/RAW8, COMP8 + output formats. + +allOf: + - $ref: /schemas/media/video-interface-devices.yaml# + +properties: + compatible: + const: sony,imx576 + + reg: + maxItems: 1 + + clocks: + items: + - description: INCLK clock + + vana-supply: + description: Analog Supply Voltage (2.8v) + + vdig-supply: + description: Digital Supply Voltage (1.05v) + + vif-supply: + description: Interface Supply Voltage (1.8v) + + reset-gpios: + maxItems: 1 + description: Reset Pin GPIO Control (active low) + + port: + description: MIPI CSI-2 transmitter port + $ref: /schemas/graph.yaml#/$defs/port-base + additionalProperties: false + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + oneOf: + - items: + - const: 1 + - const: 2 + - const: 3 + - const: 4 + - items: + - const: 1 + - const: 2 + required: + - data-lanes + - link-frequencies + + required: + - endpoint + +required: + - compatible + - reg + - clocks + - vana-supply + - vif-supply + - vdig-supply + - port + +unevaluatedProperties: false + +examples: + - | + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + camera-sensor@10 { + compatible = "sony,imx576"; + reg = <0x10>; + clocks = <&imx576_clk>; + reset-gpios = <&tlmm 35 GPIO_ACTIVE_LOW>; + + vana-supply = <&vreg_l3p>; + vif-supply = <&vreg_l6p>; + vdig-supply = <&vreg_32m_cam_dvdd_1p05>; + + orientation = <0>; + rotation = <90>; + + port { + cam_out: endpoint { + remote-endpoint = <&csiphy3_ep>; + data-lanes = <1 2 3 4>; + link-frequencies = /bits/ 64 <1017000000>; + }; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/media/i2c/st,vd55g1.yaml b/Documentation/devicetree/bindings/media/i2c/st,vd55g1.yaml index 58b1f9e85a9d..dfd87beba065 100644 --- a/Documentation/devicetree/bindings/media/i2c/st,vd55g1.yaml +++ b/Documentation/devicetree/bindings/media/i2c/st,vd55g1.yaml @@ -106,6 +106,7 @@ unevaluatedProperties: false examples: - | #include + #include i2c { #address-cells = <1>; @@ -124,7 +125,7 @@ examples: reset-gpios = <&gpio 5 GPIO_ACTIVE_LOW>; st,leds = <2>; - orientation = <2>; + orientation = ; rotation = <0>; port { diff --git a/Documentation/devicetree/bindings/media/i2c/st,vd56g3.yaml b/Documentation/devicetree/bindings/media/i2c/st,vd56g3.yaml index c6673b8539db..48db22ca4a7e 100644 --- a/Documentation/devicetree/bindings/media/i2c/st,vd56g3.yaml +++ b/Documentation/devicetree/bindings/media/i2c/st,vd56g3.yaml @@ -107,6 +107,7 @@ unevaluatedProperties: false examples: - | #include + #include i2c { #address-cells = <1>; @@ -125,7 +126,7 @@ examples: reset-gpios = <&gpio 5 GPIO_ACTIVE_LOW>; st,leds = <6>; - orientation = <2>; + orientation = ; rotation = <0>; port { diff --git a/Documentation/devicetree/bindings/media/i2c/thine,thp7312.yaml b/Documentation/devicetree/bindings/media/i2c/thine,thp7312.yaml index bc339a7374b2..4a66cb711372 100644 --- a/Documentation/devicetree/bindings/media/i2c/thine,thp7312.yaml +++ b/Documentation/devicetree/bindings/media/i2c/thine,thp7312.yaml @@ -173,6 +173,7 @@ examples: - | #include #include + #include i2c { #address-cells = <1>; @@ -196,7 +197,7 @@ examples: vddgpio-0-supply = <&vsys_v4p2>; vddgpio-1-supply = <&vsys_v4p2>; - orientation = <0>; + orientation = ; rotation = <0>; sensors { diff --git a/Documentation/devicetree/bindings/media/i2c/toshiba,tc358743.txt b/Documentation/devicetree/bindings/media/i2c/toshiba,tc358743.txt deleted file mode 100644 index 59102edcf01e..000000000000 --- a/Documentation/devicetree/bindings/media/i2c/toshiba,tc358743.txt +++ /dev/null @@ -1,48 +0,0 @@ -* Toshiba TC358743 HDMI-RX to MIPI CSI2-TX Bridge - -The Toshiba TC358743 HDMI-RX to MIPI CSI2-TX (H2C) is a bridge that converts -a HDMI stream to MIPI CSI-2 TX. It is programmable through I2C. - -Required Properties: - -- compatible: value should be "toshiba,tc358743" -- clocks, clock-names: should contain a phandle link to the reference clock - source, the clock input is named "refclk". - -Optional Properties: - -- reset-gpios: gpio phandle GPIO connected to the reset pin -- interrupts: GPIO connected to the interrupt pin -- data-lanes: should be <1 2 3 4> for four-lane operation, - or <1 2> for two-lane operation -- clock-lanes: should be <0> -- clock-noncontinuous: Presence of this boolean property decides whether the - MIPI CSI-2 clock is continuous or non-continuous. -- link-frequencies: List of allowed link frequencies in Hz. Each frequency is - expressed as a 64-bit big-endian integer. The frequency - is half of the bps per lane due to DDR transmission. - -For further information on the MIPI CSI-2 endpoint node properties, see -Documentation/devicetree/bindings/media/video-interfaces.txt. - -Example: - - tc358743@f { - compatible = "toshiba,tc358743"; - reg = <0x0f>; - clocks = <&hdmi_osc>; - clock-names = "refclk"; - reset-gpios = <&gpio6 9 GPIO_ACTIVE_LOW>; - interrupt-parent = <&gpio2>; - interrupts = <5 IRQ_TYPE_LEVEL_HIGH>; - - port { - tc358743_out: endpoint { - remote-endpoint = <&mipi_csi2_in>; - data-lanes = <1 2 3 4>; - clock-lanes = <0>; - clock-noncontinuous; - link-frequencies = /bits/ 64 <297000000>; - }; - }; - }; diff --git a/Documentation/devicetree/bindings/media/i2c/toshiba,tc358743.yaml b/Documentation/devicetree/bindings/media/i2c/toshiba,tc358743.yaml new file mode 100644 index 000000000000..29dd1d58f766 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/toshiba,tc358743.yaml @@ -0,0 +1,93 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/toshiba,tc358743.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Toshiba TC358743 HDMI-RX to MIPI CSI2-TX Bridge + +maintainers: + - Hans Verkuil + +description: + The Toshiba TC358743 HDMI-RX to MIPI CSI2-TX (H2C) is a bridge that converts + a HDMI stream to MIPI CSI-2 TX. It is programmable through I2C. + +properties: + compatible: + const: toshiba,tc358743 + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + clock-names: + const: refclk + + reset-gpios: + maxItems: 1 + + interrupts: + maxItems: 1 + + port: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + oneOf: + - items: + - const: 1 + - const: 2 + - items: + - const: 1 + - const: 2 + - const: 3 + - const: 4 + +required: + - compatible + - reg + - clocks + - clock-names + - port + +additionalProperties: false + +examples: + - | + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + bridge@f { + compatible = "toshiba,tc358743"; + reg = <0x0f>; + clocks = <&hdmi_osc>; + clock-names = "refclk"; + reset-gpios = <&gpio6 9 GPIO_ACTIVE_LOW>; + interrupt-parent = <&gpio2>; + interrupts = <5 IRQ_TYPE_LEVEL_HIGH>; + + port { + endpoint { + remote-endpoint = <&mipi_csi2_in>; + data-lanes = <1 2 3 4>; + clock-lanes = <0>; + clock-noncontinuous; + link-frequencies = /bits/ 64 <297000000>; + }; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/media/mediatek-jpeg-decoder.yaml b/Documentation/devicetree/bindings/media/mediatek-jpeg-decoder.yaml index a4aacd3eb189..654360c19c48 100644 --- a/Documentation/devicetree/bindings/media/mediatek-jpeg-decoder.yaml +++ b/Documentation/devicetree/bindings/media/mediatek-jpeg-decoder.yaml @@ -15,10 +15,10 @@ description: |- properties: compatible: oneOf: - - items: - - enum: - - mediatek,mt8173-jpgdec - - mediatek,mt2701-jpgdec + - enum: + - mediatek,mt2701-jpgdec + - mediatek,mt8173-jpgdec + - mediatek,mt8189-jpgdec - items: - enum: - mediatek,mt7623-jpgdec @@ -32,13 +32,16 @@ properties: maxItems: 1 clocks: + minItems: 1 maxItems: 2 - minItems: 2 clock-names: - items: - - const: jpgdec-smi - - const: jpgdec + oneOf: + - items: + - const: jpgdec + - items: + - const: jpgdec-smi + - const: jpgdec power-domains: maxItems: 1 @@ -51,6 +54,15 @@ properties: Documentation/devicetree/bindings/iommu/mediatek,iommu.yaml for details. Ports are according to the HW. + mediatek,larb: + $ref: /schemas/types.yaml#/definitions/phandle + description: | + A phandle to the local arbiter (SMI LARB) node. This explicitly + describes the hardware topology where the JPEG block is connected + to a dedicated SMI LARB for memory bandwidth arbitration. It is + required to accurately represent the hardware connection independently + of the IOMMU. + required: - compatible - reg @@ -60,6 +72,28 @@ required: - power-domains - iommus +allOf: + - if: + properties: + compatible: + contains: + const: mediatek,mt8189-jpgdec + then: + properties: + clocks: + maxItems: 1 + clock-names: + maxItems: 1 + required: + - mediatek,larb + else: + properties: + clocks: + minItems: 2 + clock-names: + minItems: 2 + mediatek,larb: false + additionalProperties: false examples: diff --git a/Documentation/devicetree/bindings/media/mediatek-jpeg-encoder.yaml b/Documentation/devicetree/bindings/media/mediatek-jpeg-encoder.yaml index 5b15f8977f67..d2f035715a2e 100644 --- a/Documentation/devicetree/bindings/media/mediatek-jpeg-encoder.yaml +++ b/Documentation/devicetree/bindings/media/mediatek-jpeg-encoder.yaml @@ -14,13 +14,17 @@ description: |- properties: compatible: - items: + oneOf: - enum: - - mediatek,mt2701-jpgenc - - mediatek,mt8183-jpgenc - - mediatek,mt8186-jpgenc - - mediatek,mt8188-jpgenc - - const: mediatek,mtk-jpgenc + - mediatek,mt8189-jpgenc + - items: + - enum: + - mediatek,mt2701-jpgenc + - mediatek,mt8183-jpgenc + - mediatek,mt8186-jpgenc + - mediatek,mt8188-jpgenc + - const: mediatek,mtk-jpgenc + reg: maxItems: 1 @@ -45,6 +49,15 @@ properties: Documentation/devicetree/bindings/iommu/mediatek,iommu.yaml for details. Ports are according to the HW. + mediatek,larb: + $ref: /schemas/types.yaml#/definitions/phandle + description: | + A phandle to the local arbiter (SMI LARB) node. This explicitly + describes the hardware topology where the JPEG block is connected + to a dedicated SMI LARB for memory bandwidth arbitration. It is + required to accurately represent the hardware connection independently + of the IOMMU. + required: - compatible - reg @@ -54,6 +67,19 @@ required: - power-domains - iommus +allOf: + - if: + properties: + compatible: + contains: + const: mediatek,mt8189-jpgenc + then: + required: + - mediatek,larb + else: + properties: + mediatek,larb: false + additionalProperties: false examples: diff --git a/Documentation/devicetree/bindings/media/nxp,imx8mq-mipi-csi2.yaml b/Documentation/devicetree/bindings/media/nxp,imx8mq-mipi-csi2.yaml index 4fcfc4fd3565..9eee67ed2685 100644 --- a/Documentation/devicetree/bindings/media/nxp,imx8mq-mipi-csi2.yaml +++ b/Documentation/devicetree/bindings/media/nxp,imx8mq-mipi-csi2.yaml @@ -220,7 +220,7 @@ examples: port@0 { reg = <0>; - imx8mm_mipi_csi_in: endpoint { + mipi_csi_in: endpoint { remote-endpoint = <&imx477_out>; data-lanes = <1 2 3 4>; }; @@ -229,7 +229,7 @@ examples: port@1 { reg = <1>; - imx8mm_mipi_csi_out: endpoint { + mipi_csi_out: endpoint { remote-endpoint = <&csi_in>; }; }; diff --git a/Documentation/devicetree/bindings/media/qcom,glymur-camss.yaml b/Documentation/devicetree/bindings/media/qcom,glymur-camss.yaml new file mode 100644 index 000000000000..a0b77edc2123 --- /dev/null +++ b/Documentation/devicetree/bindings/media/qcom,glymur-camss.yaml @@ -0,0 +1,300 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/qcom,glymur-camss.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm Glymur Camera Subsystem (CAMSS) + +maintainers: + - Nihal Kumar Gupta + +description: + The CAMSS IP is a CSI decoder and ISP present on Qualcomm platforms. + +properties: + compatible: + const: qcom,glymur-camss + + reg: + maxItems: 13 + + reg-names: + items: + - const: csid_wrapper + - const: csid0 + - const: csid1 + - const: csid2 + - const: csid_lite0 + - const: csid_lite1 + - const: csitpg0 + - const: csitpg1 + - const: csitpg2 + - const: vfe0 + - const: vfe1 + - const: vfe_lite0 + - const: vfe_lite1 + + clocks: + maxItems: 20 + + clock-names: + items: + - const: camnoc_nrt_axi + - const: camnoc_rt_axi + - const: core_ahb + - const: cpas_ahb + - const: cpas_fast_ahb + - const: cpas_vfe0 + - const: cpas_vfe1 + - const: cpas_vfe_lite + - const: csid + - const: csid_csiphy_rx + - const: gcc_axi_hf + - const: gcc_axi_sf + - const: vfe0 + - const: vfe0_fast_ahb + - const: vfe1 + - const: vfe1_fast_ahb + - const: vfe_lite + - const: vfe_lite_ahb + - const: vfe_lite_cphy_rx + - const: vfe_lite_csid + + interrupts: + maxItems: 9 + + interrupt-names: + items: + - const: csid0 + - const: csid1 + - const: csid2 + - const: csid_lite0 + - const: csid_lite1 + - const: vfe0 + - const: vfe1 + - const: vfe_lite0 + - const: vfe_lite1 + + interconnects: + maxItems: 4 + + interconnect-names: + items: + - const: ahb + - const: hf_mnoc + - const: sf_mnoc + - const: sf_icp_mnoc + + iommus: + items: + - description: S1 HLOS IFE and IFE_LITE non-protected + + power-domains: + items: + - description: IFE0 GDSC - Image Front End, Global Distributed Switch Controller. + - description: IFE1 GDSC - Image Front End, Global Distributed Switch Controller. + - description: Titan Top GDSC - Titan ISP Block, Global Distributed Switch Controller. + + power-domain-names: + items: + - const: ife0 + - const: ife1 + - const: top + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + description: + CSI input ports. + + patternProperties: + "^port@[0-3]$": + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + + description: + Input port for receiving CSI data. + + properties: + endpoint: + $ref: video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + minItems: 1 + maxItems: 4 + +required: + - compatible + - reg + - reg-names + - clocks + - clock-names + - interrupts + - interrupt-names + - interconnects + - interconnect-names + - iommus + - power-domains + - power-domain-names + - ports + +additionalProperties: false + +examples: + - | + #include + #include + #include + #include + #include + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + camss: isp@acb6000 { + compatible = "qcom,glymur-camss"; + + reg = <0x0 0x0acb6000 0x0 0x1000>, + <0x0 0x0acb7000 0x0 0x2000>, + <0x0 0x0acb9000 0x0 0x2000>, + <0x0 0x0acbb000 0x0 0x2000>, + <0x0 0x0acc6000 0x0 0x1000>, + <0x0 0x0acca000 0x0 0x1000>, + <0x0 0x0acf6000 0x0 0x1000>, + <0x0 0x0acf7000 0x0 0x1000>, + <0x0 0x0acf8000 0x0 0x1000>, + <0x0 0x0ac62000 0x0 0xf000>, + <0x0 0x0ac71000 0x0 0xf000>, + <0x0 0x0acc7000 0x0 0x2000>, + <0x0 0x0accb000 0x0 0x2000>; + reg-names = "csid_wrapper", + "csid0", + "csid1", + "csid2", + "csid_lite0", + "csid_lite1", + "csitpg0", + "csitpg1", + "csitpg2", + "vfe0", + "vfe1", + "vfe_lite0", + "vfe_lite1"; + + clocks = <&camcc CAM_CC_CAMNOC_AXI_NRT_CLK>, + <&camcc CAM_CC_CAMNOC_AXI_RT_CLK>, + <&camcc CAM_CC_CORE_AHB_CLK>, + <&camcc CAM_CC_CPAS_AHB_CLK>, + <&camcc CAM_CC_CPAS_FAST_AHB_CLK>, + <&camcc CAM_CC_CPAS_IFE_0_CLK>, + <&camcc CAM_CC_CPAS_IFE_1_CLK>, + <&camcc CAM_CC_CPAS_IFE_LITE_CLK>, + <&camcc CAM_CC_CSID_CLK>, + <&camcc CAM_CC_CSID_CSIPHY_RX_CLK>, + <&gcc GCC_CAMERA_HF_AXI_CLK>, + <&gcc GCC_CAMERA_SF_AXI_CLK>, + <&camcc CAM_CC_IFE_0_CLK>, + <&camcc CAM_CC_IFE_0_FAST_AHB_CLK>, + <&camcc CAM_CC_IFE_1_CLK>, + <&camcc CAM_CC_IFE_1_FAST_AHB_CLK>, + <&camcc CAM_CC_IFE_LITE_CLK>, + <&camcc CAM_CC_IFE_LITE_AHB_CLK>, + <&camcc CAM_CC_IFE_LITE_CPHY_RX_CLK>, + <&camcc CAM_CC_IFE_LITE_CSID_CLK>; + clock-names = "camnoc_nrt_axi", + "camnoc_rt_axi", + "core_ahb", + "cpas_ahb", + "cpas_fast_ahb", + "cpas_vfe0", + "cpas_vfe1", + "cpas_vfe_lite", + "csid", + "csid_csiphy_rx", + "gcc_axi_hf", + "gcc_axi_sf", + "vfe0", + "vfe0_fast_ahb", + "vfe1", + "vfe1_fast_ahb", + "vfe_lite", + "vfe_lite_ahb", + "vfe_lite_cphy_rx", + "vfe_lite_csid"; + + interrupts = , + , + , + , + , + , + , + , + ; + interrupt-names = "csid0", + "csid1", + "csid2", + "csid_lite0", + "csid_lite1", + "vfe0", + "vfe1", + "vfe_lite0", + "vfe_lite1"; + + interconnects = <&hsc_noc MASTER_APPSS_PROC QCOM_ICC_TAG_ACTIVE_ONLY + &config_noc SLAVE_CAMERA_CFG QCOM_ICC_TAG_ACTIVE_ONLY>, + <&mmss_noc MASTER_CAMNOC_HF QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>, + <&mmss_noc MASTER_CAMNOC_SF QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>, + <&mmss_noc MASTER_CAMNOC_ICP QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "ahb", + "hf_mnoc", + "sf_mnoc", + "sf_icp_mnoc"; + + iommus = <&apps_smmu 0x800 0x20>; + + power-domains = <&camcc CAM_CC_IFE_0_GDSC>, + <&camcc CAM_CC_IFE_1_GDSC>, + <&camcc CAM_CC_TITAN_TOP_GDSC>; + power-domain-names = "ife0", + "ife1", + "top"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + camss_csiphy0_ep: endpoint { + data-lanes = <1 2 3 4>; + remote-endpoint = <&csiphy0_out>; + }; + }; + + port@1 { + reg = <1>; + camss_csiphy1_ep: endpoint { + data-lanes = <1 2 3 4>; + remote-endpoint = <&csiphy1_out>; + }; + }; + + port@3 { + reg = <3>; + camss_csiphy4_ep: endpoint { + data-lanes = <1 2 3 4>; + remote-endpoint = <&csiphy4_out>; + }; + }; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/media/qcom,qcm2290-venus.yaml b/Documentation/devicetree/bindings/media/qcom,qcm2290-venus.yaml index 5977e7d0a71b..b27899ebf164 100644 --- a/Documentation/devicetree/bindings/media/qcom,qcm2290-venus.yaml +++ b/Documentation/devicetree/bindings/media/qcom,qcm2290-venus.yaml @@ -13,14 +13,13 @@ description: The Venus AR50_LITE IP is a video encode and decode accelerator present on Qualcomm platforms. -allOf: - - $ref: qcom,venus-common.yaml# - properties: compatible: oneOf: - items: - - const: qcom,sm6115-venus + - enum: + - qcom,shikra-venus + - qcom,sm6115-venus - const: qcom,qcm2290-venus - const: qcom,qcm2290-venus @@ -45,9 +44,6 @@ properties: - const: vcodec0_core - const: vcodec0_bus - iommus: - maxItems: 2 - interconnects: maxItems: 2 @@ -65,6 +61,22 @@ required: - power-domain-names - iommus +allOf: + - $ref: qcom,venus-common.yaml# + - if: + properties: + compatible: + contains: + const: qcom,shikra-venus + then: + properties: + iommus: + maxItems: 1 + else: + properties: + iommus: + maxItems: 2 + unevaluatedProperties: false examples: diff --git a/Documentation/devicetree/bindings/media/qcom,venus-common.yaml b/Documentation/devicetree/bindings/media/qcom,venus-common.yaml index 59a3fde846d2..0be2f9119e78 100644 --- a/Documentation/devicetree/bindings/media/qcom,venus-common.yaml +++ b/Documentation/devicetree/bindings/media/qcom,venus-common.yaml @@ -37,7 +37,10 @@ properties: maxItems: 20 memory-region: - maxItems: 1 + minItems: 1 + items: + - description: Firmware-loaded codec carveout + - description: IOMMU IOVA reservation region power-domains: minItems: 1 diff --git a/Documentation/devicetree/bindings/media/qcom,x1e80100-camss.yaml b/Documentation/devicetree/bindings/media/qcom,x1e80100-camss.yaml index 2d1662ef522b..57a838fe2d22 100644 --- a/Documentation/devicetree/bindings/media/qcom,x1e80100-camss.yaml +++ b/Documentation/devicetree/bindings/media/qcom,x1e80100-camss.yaml @@ -104,7 +104,22 @@ properties: - const: sf_icp_mnoc iommus: - maxItems: 8 + oneOf: + - items: + - description: S1 HLOS IFE and IFE_LITE non-protected read + - description: S1 HLOS IFE and IFE_LITE non-protected write + - description: S1 HLOS SFE non-protected read + - description: S1 HLOS SFE non-protected write + - description: S1 HLOS CDM IFE non-protected + - description: Legacy slot 0 - do not use + - description: Legacy slot 1 - do not use + - description: Legacy slot 2 - do not use + - items: + - description: S1 HLOS IFE and IFE_LITE non-protected read + - description: S1 HLOS IFE and IFE_LITE non-protected write + - description: S1 HLOS SFE non-protected read + - description: S1 HLOS SFE non-protected write + - description: S1 HLOS CDM IFE non-protected power-domains: items: @@ -171,8 +186,6 @@ required: - iommus - power-domains - power-domain-names - - vdd-csiphy-0p8-supply - - vdd-csiphy-1p2-supply - ports additionalProperties: false @@ -332,13 +345,10 @@ examples: "sf_icp_mnoc"; iommus = <&apps_smmu 0x800 0x60>, + <&apps_smmu 0x820 0x60>, + <&apps_smmu 0x840 0x60>, <&apps_smmu 0x860 0x60>, - <&apps_smmu 0x1800 0x60>, - <&apps_smmu 0x1860 0x60>, - <&apps_smmu 0x18e0 0x00>, - <&apps_smmu 0x1980 0x20>, - <&apps_smmu 0x1900 0x00>, - <&apps_smmu 0x19a0 0x20>; + <&apps_smmu 0x18a0 0x0>; power-domains = <&camcc CAM_CC_IFE_0_GDSC>, <&camcc CAM_CC_IFE_1_GDSC>, diff --git a/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml b/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml index 8bfad0fca3b7..1898f3787a98 100644 --- a/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml +++ b/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml @@ -23,6 +23,7 @@ properties: - rockchip,rk3568-mipi-csi2 - items: - enum: + - rockchip,rk3576-mipi-csi2 - rockchip,rk3588-mipi-csi2 - const: rockchip,rk3568-mipi-csi2 diff --git a/Documentation/devicetree/bindings/media/rockchip,rk3568-vicap.yaml b/Documentation/devicetree/bindings/media/rockchip,rk3568-vicap.yaml index 080b64503b1b..b7dfaf3b378d 100644 --- a/Documentation/devicetree/bindings/media/rockchip,rk3568-vicap.yaml +++ b/Documentation/devicetree/bindings/media/rockchip,rk3568-vicap.yaml @@ -15,6 +15,10 @@ description: the data from camera sensors, video decoders, or other companion ICs and transfers it into system main memory by AXI bus. + The Rockchip RK3576 Video Capture (VICAP) is similar to its RK3568 + counterpart, but features five MIPI CSI-2 ports and an additional + connection to the image signal processor (ISP) block. + The Rockchip RK3588 Video Capture (VICAP) is similar to its RK3568 counterpart, but features six MIPI CSI-2 ports and additional connections to the image signal processor (ISP) blocks. @@ -23,6 +27,7 @@ properties: compatible: enum: - rockchip,rk3568-vicap + - rockchip,rk3576-vicap - rockchip,rk3588-vicap reg: @@ -33,7 +38,7 @@ properties: clocks: minItems: 4 - maxItems: 5 + maxItems: 8 clock-names: items: @@ -42,6 +47,9 @@ properties: - const: dclk - const: iclk - const: iclk1 + - const: iclk2 + - const: iclk3 + - const: iclk4 minItems: 4 iommus: @@ -221,6 +229,40 @@ allOf: port@11: false + - if: + properties: + compatible: + contains: + const: rockchip,rk3576-vicap + then: + properties: + clocks: + minItems: 8 + + clock-names: + minItems: 8 + + resets: + minItems: 8 + maxItems: 8 + + reset-names: + items: + - const: arst + - const: hrst + - const: drst + - const: irst0 + - const: irst1 + - const: irst2 + - const: irst3 + - const: irst4 + + ports: + properties: + port@6: false + + port@11: false + - if: properties: compatible: @@ -230,9 +272,11 @@ allOf: properties: clocks: minItems: 5 + maxItems: 5 clock-names: minItems: 5 + maxItems: 5 resets: minItems: 9 diff --git a/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml b/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml index b7f6c87d0e06..b80660da1050 100644 --- a/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml +++ b/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml @@ -78,6 +78,13 @@ properties: The phandle of the syscon node for the Video Output GRF register to enable EDID transfer through SDAIN and SCLIN. + "#sound-dai-cells": + const: 1 + description: + The HDMI RX controller has two digital audio interfaces, one for + I2S and one for S/PDIF. The DAI cell selects the interface, 0 for + I2S and 1 for S/PDIF. + required: - compatible - reg @@ -90,7 +97,10 @@ required: - pinctrl-0 - hpd-gpios -additionalProperties: false +allOf: + - $ref: /schemas/sound/dai-common.yaml# + +unevaluatedProperties: false examples: - | @@ -129,4 +139,5 @@ examples: pinctrl-0 = <&hdmim1_rx_cec &hdmim1_rx_hpdin &hdmim1_rx_scl &hdmim1_rx_sda &hdmirx_5v_detection>; pinctrl-names = "default"; hpd-gpios = <&gpio1 22 GPIO_ACTIVE_LOW>; + #sound-dai-cells = <1>; }; diff --git a/Documentation/devicetree/bindings/media/video-interface-devices.yaml b/Documentation/devicetree/bindings/media/video-interface-devices.yaml index a81d2a155fe6..c9c3f4f16719 100644 --- a/Documentation/devicetree/bindings/media/video-interface-devices.yaml +++ b/Documentation/devicetree/bindings/media/video-interface-devices.yaml @@ -392,17 +392,22 @@ properties: The orientation of a device (typically an image sensor or a flash LED) describing its mounting position relative to the usage orientation of the system where the device is installed on. + See include/dt-bindings/media/video-interface-devices.h. + $ref: /schemas/types.yaml#/definitions/uint32 enum: - # Front. The device is mounted on the front facing side of the system. For - # mobile devices such as smartphones, tablets and laptops the front side - # is the user facing side. + # MEDIA_ORIENTATION_FRONT + # The device is mounted on the front facing side of the system. For + # mobile devices such as smartphones, tablets and laptops the front + # side is the user facing side. - 0 - # Back. The device is mounted on the back side of the system, which is + # MEDIA_ORIENTATION_BACK + # The device is mounted on the back side of the system, which is # defined as the opposite side of the front facing one. - 1 - # External. The device is not attached directly to the system but is - # attached in a way that allows it to move freely. + # MEDIA_ORIENTATION_EXTERNAL + # The device is not attached directly to the system but is attached in + # a way that allows it to move freely. - 2 additionalProperties: true diff --git a/Documentation/devicetree/bindings/phy/qcom,x1e80100-csi2-phy.yaml b/Documentation/devicetree/bindings/phy/qcom,x1e80100-csi2-phy.yaml new file mode 100644 index 000000000000..1f1e2a4993b2 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/qcom,x1e80100-csi2-phy.yaml @@ -0,0 +1,213 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/phy/qcom,x1e80100-csi2-phy.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm X1E80100 SoC CSI2 PHY + +maintainers: + - Bryan O'Donoghue + +description: + Qualcomm MIPI CSI2 C-PHY/D-PHY combination PHY. Connects MIPI CSI2 sensors + to Qualcomm's Camera CSI Decoder. The PHY supports both C-PHY and D-PHY + modes. + +properties: + compatible: + oneOf: + - const: qcom,x1e80100-csi2-phy + - items: + - const: qcom,glymur-csi2-phy + - const: qcom,x1e80100-csi2-phy + + reg: + maxItems: 1 + + clocks: + maxItems: 3 + + clock-names: + items: + - const: core + - const: timer + - const: ahb + + interrupts: + maxItems: 1 + + operating-points-v2: true + + opp-table: + type: object + + power-domains: + items: + - description: Titan Top GDSC - Titan ISP Block, Global Distributed Switch Controller. + - description: MMCX voltage rail + - description: MXC or MXA voltage rail + + power-domain-names: + items: + - const: top + - const: mmcx + - const: mx + + vdda-0p9-supply: + description: Phandle to a 0.9V regulator supply to a PHY. + + vdda-1p2-supply: + description: Phandle to 1.2V regulator supply to a PHY. + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/$defs/port-base + description: + Sensor input. Always present. A single sensor is described by a + single endpoint with one to four data lanes, numbered 1-4. The + clock lane is position 0. + + DPHY split mode, where two independent sensors share the PHY, is + described by two endpoints. endpoint@0 uses data lanes 1 or 1-2, + clock lane position 0; endpoint@1 uses data lane 3, clock lane + position 4. + + unevaluatedProperties: false + + patternProperties: + "^endpoint(@[01])?$": + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + properties: + bus-type: + enum: + - 1 # MEDIA_BUS_TYPE_CSI2_CPHY + - 4 # MEDIA_BUS_TYPE_CSI2_DPHY + default: 4 + + data-lanes: + minItems: 1 + maxItems: 4 + items: + minimum: 1 + maximum: 4 + description: + Data lane positions following this endpoint's clock + lane. Positions are a per-port logical numbering, not + package pad or ball positions. + + required: + - data-lanes + - remote-endpoint + + allOf: + - if: + required: + - endpoint@1 + then: + properties: + endpoint@0: + properties: + data-lanes: + maxItems: 2 + items: + maximum: 2 + endpoint@1: + properties: + data-lanes: + items: + - const: 3 + required: + - endpoint@0 + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: Output to the CAMSS CSID controller. + + required: + - port@0 + - port@1 + +required: + - compatible + - reg + - clocks + - clock-names + - interrupts + - operating-points-v2 + - power-domains + - power-domain-names + - vdda-0p9-supply + - vdda-1p2-supply + - ports + +additionalProperties: false + +examples: + - | + #include + #include + #include + #include + #include + + phy@ace4000 { + compatible = "qcom,x1e80100-csi2-phy"; + reg = <0x0ace4000 0x2000>; + + clocks = <&camcc CAM_CC_CSIPHY0_CLK>, + <&camcc CAM_CC_CSI0PHYTIMER_CLK>, + <&camcc CAM_CC_CORE_AHB_CLK>; + clock-names = "core", + "timer", + "ahb"; + + interrupts = ; + + operating-points-v2 = <&csiphy_opp_table>; + + power-domains = <&camcc CAM_CC_TITAN_TOP_GDSC>, + <&rpmhpd RPMHPD_MMCX>, + <&rpmhpd RPMHPD_MX>; + power-domain-names = "top", + "mmcx", + "mx"; + + vdda-0p9-supply = <&vreg_l2c_0p9>; + vdda-1p2-supply = <&vreg_l1c_1p2>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + csiphy0_in: endpoint { + bus-type = ; + data-lanes = <1 2 3 4>; + remote-endpoint = <&sensor_out>; + }; + }; + + port@1 { + reg = <1>; + csiphy0_out: endpoint { + remote-endpoint = <&csid_in>; + }; + }; + }; + + csiphy_opp_table: opp-table { + compatible = "operating-points-v2"; + + opp-300000000 { + opp-hz = /bits/ 64 <300000000>; + required-opps = <&rpmhpd_opp_low_svs_d1>, + <&rpmhpd_opp_low_svs_d1>; + }; + }; + }; diff --git a/Documentation/driver-api/media/tx-rx.rst b/Documentation/driver-api/media/tx-rx.rst index 7df2407817b3..9b231fa0216a 100644 --- a/Documentation/driver-api/media/tx-rx.rst +++ b/Documentation/driver-api/media/tx-rx.rst @@ -104,7 +104,11 @@ where * - k - 16 for D-PHY and 7 for C-PHY. -Information on whether D-PHY or C-PHY is used, and the value of ``nr_of_lanes``, can be obtained from the OF endpoint configuration. +Information on whether D-PHY or C-PHY is used as well as the value of +``nr_of_lanes`` can be obtained from the V4L2 endpoint configuration; see +:c:func:`v4l2_fwnode_endpoint_alloc_parse()`, +:c:func:`v4l2_fwnode_endpoint_parse()` and +:c:func:`v4l2_get_active_data_lanes()`. .. note:: diff --git a/Documentation/userspace-api/media/drivers/dcmipp.rst b/Documentation/userspace-api/media/drivers/dcmipp.rst new file mode 100644 index 000000000000..ed4272da4b68 --- /dev/null +++ b/Documentation/userspace-api/media/drivers/dcmipp.rst @@ -0,0 +1,14 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +ST DCMIPP driver +================ + +The ST DCMIPP driver implements a driver specific control as part of its +pixelproc subdev. + +``V4L2_CID_DCMIPP_PIXELPROC_GAMMA_CORRECTION_ENABLE (boolean)`` + Enable / disable the gamma correction block. + + The DCMIPP PixelProc stage implements a gamma compression on each R, G, B + component, using a static gamma exponent 2.2. + The gamma is implemented as a 7-segment linear curve. diff --git a/Documentation/userspace-api/media/drivers/index.rst b/Documentation/userspace-api/media/drivers/index.rst index 02967c9b18d6..8d2c489b8453 100644 --- a/Documentation/userspace-api/media/drivers/index.rst +++ b/Documentation/userspace-api/media/drivers/index.rst @@ -30,6 +30,7 @@ For more details see the file COPYING in the source distribution of Linux. camera-sensor ccs cx2341x-uapi + dcmipp dw100 imx-uapi mali-c55 diff --git a/Documentation/userspace-api/media/mediactl/media-types.rst b/Documentation/userspace-api/media/mediactl/media-types.rst index 6332e8395263..4f6d1ecf2853 100644 --- a/Documentation/userspace-api/media/mediactl/media-types.rst +++ b/Documentation/userspace-api/media/mediactl/media-types.rst @@ -361,6 +361,7 @@ Types and flags used to represent the media graph elements .. _MEDIA-PAD-FL-SINK: .. _MEDIA-PAD-FL-SOURCE: .. _MEDIA-PAD-FL-MUST-CONNECT: +.. _MEDIA-PAD-FL-INTERNAL: .. flat-table:: Media pad flags :header-rows: 0 @@ -381,6 +382,13 @@ Types and flags used to represent the media graph elements enabled links even when this flag isn't set; the absence of the flag doesn't imply there is none. + * - ``MEDIA_PAD_FL_INTERNAL`` + - The internal flag indicates an internal pad that has no external + connections. As they are internal to entities, internal pads shall not + be connected with links. + + The internal flag may currently be present only in a sink pad where it + indicates that the :term:`Stream` originates from within the entity. One and only one of ``MEDIA_PAD_FL_SINK`` and ``MEDIA_PAD_FL_SOURCE`` must be set for every pad. diff --git a/MAINTAINERS b/MAINTAINERS index 60734cdd3812..d4ef4547b1c2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11932,6 +11932,13 @@ W: http://www.highpoint-tech.com F: Documentation/scsi/hptiop.rst F: drivers/scsi/hptiop.c +HIMAX HM1246 SENSOR DRIVER +M: Matthias Fend +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/i2c/himax,hm1246.yaml +F: drivers/media/i2c/hm1246.c + HIMAX HX83112B TOUCHSCREEN SUPPORT M: Job Noorman L: linux-input@vger.kernel.org @@ -13242,6 +13249,8 @@ F: drivers/scsi/isci/ INTEL COMPUTER VISION SENSING (CVS) DRIVER M: Miguel Vadillo +M: Sakari Ailus +R: Antti Laakso L: linux-media@vger.kernel.org S: Maintained F: drivers/media/i2c/cvs/ @@ -13429,6 +13438,15 @@ S: Supported T: git git://git.kernel.org/pub/scm/linux/kernel/git/iommu/linux.git F: drivers/iommu/intel/ +INTEL IPU BRIDGE +M: Sakari Ailus +M: Dan Scally +R: Hans de Goede +L: linux-media@vger.kernel.org +S: Maintained +F: drivers/media/pci/intel/ipu-bridge.c +F: include/media/ipu-bridge.h + INTEL IPU3 CSI-2 CIO2 DRIVER M: Yong Zhi M: Sakari Ailus @@ -13450,6 +13468,10 @@ F: drivers/staging/media/ipu3/ INTEL IPU6 INPUT SYSTEM DRIVER M: Sakari Ailus +R: Antti Laakso +R: "Sapre, Sarang" +R: Divyamani Tripathi +R: Manik Bajpai L: linux-media@vger.kernel.org S: Maintained T: git git://linuxtv.org/media.git @@ -13686,6 +13708,7 @@ K: \bSGX_ INTEL SKYLAKE INT3472 ACPI DEVICE DRIVER M: Daniel Scally M: Sakari Ailus +L: linux-media@vger.kernel.org S: Maintained T: git git://linuxtv.org/media.git F: drivers/platform/x86/intel/int3472/ @@ -14152,6 +14175,14 @@ T: git https://gitlab.freedesktop.org/drm/misc/kernel.git F: Documentation/devicetree/bindings/display/bridge/ite,it66121.yaml F: drivers/gpu/drm/bridge/ite-it66121.c +ITE IT6625 HDMI to MIPI MEDIA DRIVER +M: Hermes Wu +S: Maintained +T: git git://linuxtv.org/media.git +F: Documentation/devicetree/bindings/media/i2c/ite,it6625.yaml +F: drivers/media/i2c/it6625.c +F: include/uapi/linux/it6625.h + IVTV VIDEO4LINUX DRIVER M: Andy Walls L: linux-media@vger.kernel.org @@ -16286,21 +16317,18 @@ M: Niklas Söderlund L: linux-media@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml -F: drivers/staging/media/max96712/max96712.c MAX96714 GMSL2 DESERIALIZER DRIVER M: Julien Massot L: linux-media@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/media/i2c/maxim,max96714.yaml -F: drivers/media/i2c/max96714.c MAX96717 GMSL2 SERIALIZER DRIVER M: Julien Massot L: linux-media@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml -F: drivers/media/i2c/max96717.c MAX9860 MONO AUDIO VOICE CODEC DRIVER M: Peter Rosin @@ -16316,6 +16344,13 @@ S: Maintained F: Documentation/devicetree/bindings/iio/proximity/maxbotix,mb1232.yaml F: drivers/iio/proximity/mb1232.c +MAXIM GMSL2/3 SERIALIZERS AND DESERIALIZERS +M: Cosmin Tanislav +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/i2c/maxim,max9296a.yaml +F: drivers/media/i2c/maxim-serdes/ + MAXIM MAX11205 DRIVER M: Ramona Bolboaca L: linux-iio@vger.kernel.org @@ -16649,6 +16684,7 @@ M: Frank Li M: Martin Kepplinger-Novakovic R: Rui Miguel Silva R: Purism Kernel Team +R: Bryan O'Donoghue L: imx@lists.linux.dev L: linux-media@vger.kernel.org S: Maintained @@ -20022,6 +20058,14 @@ S: Maintained F: Documentation/devicetree/bindings/media/nxp,imx8-jpeg.yaml F: drivers/media/platform/nxp/imx-jpeg +NXP i.MX 95 CSI PIXEL FORMATTER V4L2 DRIVER +M: Guoniu Zhou +L: imx@lists.linux.dev +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/fsl,imx95-csi-formatter.yaml +F: drivers/media/platform/nxp/imx95-csi-formatter.c + NXP i.MX CLOCK DRIVERS M: Abel Vesa R: Peng Fan @@ -20412,6 +20456,14 @@ T: git git://linuxtv.org/media_tree.git F: Documentation/devicetree/bindings/media/i2c/ovti,og0ve1b.yaml F: drivers/media/i2c/og0ve1b.c +OMNIVISION OS02G10 SENSOR DRIVER +M: Tarang Raval +M: Elgin Perumbilly +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/i2c/ovti,os02g10.yaml +F: drivers/media/i2c/os02g10.c + OMNIVISION OS05B10 SENSOR DRIVER M: Himanshu Bhavani M: Elgin Perumbilly @@ -20452,6 +20504,13 @@ T: git git://linuxtv.org/media.git F: Documentation/devicetree/bindings/media/i2c/ovti,ov02e10.yaml F: drivers/media/i2c/ov02e10.c +OMNIVISION OV05C10 SENSOR DRIVER +M: Sakari Ailus +R: Antti Laakso +L: linux-media@vger.kernel.org +S: Maintained +F: drivers/media/i2c/ov05c10.c + OMNIVISION OV08D10 SENSOR DRIVER M: Jimmy Su R: Matthias Fend @@ -22860,6 +22919,16 @@ S: Maintained F: Documentation/devicetree/bindings/media/qcom,*-iris.yaml F: drivers/media/platform/qcom/iris/ +QUALCOMM MIPI CSI2 PHY DRIVER +M: Bryan O'Donoghue +L: linux-phy@lists.infradead.org +L: linux-media@vger.kernel.org +L: linux-arm-msm@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/phy/qcom,*-csi2-phy.yaml +F: drivers/phy/qualcomm/phy-qcom-mipi-csi2*.c +F: drivers/phy/qualcomm/phy-qcom-mipi-csi2*.h + QUALCOMM NAND CONTROLLER DRIVER M: Manivannan Sadhasivam L: linux-mtd@lists.infradead.org @@ -24775,6 +24844,13 @@ T: git git://linuxtv.org/media_tree.git F: Documentation/devicetree/bindings/media/i2c/samsung,s5kjn1.yaml F: drivers/media/i2c/s5kjn1.c +SAMSUNG S5KJN5 CAMERA DRIVER +M: Wenmeng Liu +L: linux-media@vger.kernel.org +S: Supported +F: Documentation/devicetree/bindings/media/i2c/samsung,s5kjn5.yaml +F: drivers/media/i2c/s5kjn5.c + SAMSUNG S5P Security SubSystem (SSS) DRIVER M: Krzysztof Kozlowski M: Vladimir Zapolskiy @@ -25940,6 +26016,14 @@ L: linux-media@vger.kernel.org S: Maintained F: drivers/media/i2c/imx471.c +SONY IMX576 SENSOR DRIVER +M: Himanshu Bhavani +M: Hardevsinh Palaniya +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/i2c/sony,imx576.yaml +F: drivers/media/i2c/imx576.c + SONY IMX678 SENSOR DRIVER M: Jai Luthra L: linux-media@vger.kernel.org @@ -26955,6 +27039,7 @@ F: include/linux/soc/amd/isp4_misc.h SYNOPSYS DESIGNWARE MIPI CSI-2 RECEIVER DRIVER M: Michael Riesch +R: Bryan O'Donoghue L: linux-media@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml @@ -27329,9 +27414,11 @@ M: Luca Ceresoli L: linux-media@vger.kernel.org L: linux-tegra@vger.kernel.org S: Maintained +F: Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-csi.yaml F: Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml F: Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-vi.yaml F: Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-vip.yaml +F: Documentation/devicetree/bindings/display/tegra/nvidia,tegra210-csi.yaml F: drivers/staging/media/tegra-video/ TEGRA XUSB PADCTL DRIVER @@ -28068,7 +28155,7 @@ TOSHIBA TC358743 DRIVER M: Hans Verkuil L: linux-media@vger.kernel.org S: Maintained -F: Documentation/devicetree/bindings/media/i2c/toshiba,tc358743.txt +F: Documentation/devicetree/bindings/media/i2c/toshiba,tc358743.yaml F: drivers/media/i2c/tc358743* F: include/media/i2c/tc358743.h diff --git a/arch/arm/boot/dts/nvidia/tegra30-asus-nexus7-grouper-common.dtsi b/arch/arm/boot/dts/nvidia/tegra30-asus-nexus7-grouper-common.dtsi index 892d718294dd..a7fdd194300c 100644 --- a/arch/arm/boot/dts/nvidia/tegra30-asus-nexus7-grouper-common.dtsi +++ b/arch/arm/boot/dts/nvidia/tegra30-asus-nexus7-grouper-common.dtsi @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -991,7 +992,7 @@ front-camera@48 { vdd-supply = <&vddio_cam>; vaa-supply = <&avdd_cam1>; - orientation = <0>; /* Front camera */ + orientation = ; assigned-clocks = <&tegra_car TEGRA30_CLK_VI_SENSOR>, <&tegra_car TEGRA30_CLK_CSUS>; diff --git a/arch/arm/boot/dts/nvidia/tegra30-asus-transformer-common.dtsi b/arch/arm/boot/dts/nvidia/tegra30-asus-transformer-common.dtsi index bf1c3a31d406..76286e15684c 100644 --- a/arch/arm/boot/dts/nvidia/tegra30-asus-transformer-common.dtsi +++ b/arch/arm/boot/dts/nvidia/tegra30-asus-transformer-common.dtsi @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "tegra30.dtsi" @@ -1262,7 +1263,7 @@ front-camera@48 { vdd-supply = <&vdd_1v8_cam>; vaa-supply = <&avdd_2v85_fcam>; - orientation = <0>; /* Front camera */ + orientation = ; assigned-clocks = <&tegra_car TEGRA30_CLK_VI_SENSOR>, <&tegra_car TEGRA30_CLK_CSUS>; diff --git a/arch/arm/boot/dts/nvidia/tegra30-lg-p895.dts b/arch/arm/boot/dts/nvidia/tegra30-lg-p895.dts index 0d56fd25ce17..cfa806649243 100644 --- a/arch/arm/boot/dts/nvidia/tegra30-lg-p895.dts +++ b/arch/arm/boot/dts/nvidia/tegra30-lg-p895.dts @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-2.0 /dts-v1/; +#include + #include "tegra30-lg-x3.dtsi" / { @@ -129,7 +131,7 @@ front-camera@48 { vdd-supply = <&vt_1v8_front>; vaa-supply = <&vt_2v8_front>; - orientation = <0>; /* Front camera */ + orientation = ; assigned-clocks = <&tegra_car TEGRA30_CLK_VI_SENSOR>, <&tegra_car TEGRA30_CLK_CSUS>; diff --git a/arch/arm/boot/dts/nvidia/tegra30-lg-x3.dtsi b/arch/arm/boot/dts/nvidia/tegra30-lg-x3.dtsi index 781cf38e9f18..de4d5bc2f7d5 100644 --- a/arch/arm/boot/dts/nvidia/tegra30-lg-x3.dtsi +++ b/arch/arm/boot/dts/nvidia/tegra30-lg-x3.dtsi @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -1253,7 +1254,7 @@ rear-camera@10 { dvdd-supply = <&vdd_1v2_rear>; avdd-supply = <&vdd_2v7_rear>; - orientation = <1>; /* Rear camera */ + orientation = ; rotation = <90>; nvmem = <&m24c08>; diff --git a/arch/arm64/boot/dts/freescale/imx8mp-tqma8mpql-mba8mp-ras314-imx219.dtso b/arch/arm64/boot/dts/freescale/imx8mp-tqma8mpql-mba8mp-ras314-imx219.dtso index e5a2b3780215..7b44ae0f19b2 100644 --- a/arch/arm64/boot/dts/freescale/imx8mp-tqma8mpql-mba8mp-ras314-imx219.dtso +++ b/arch/arm64/boot/dts/freescale/imx8mp-tqma8mpql-mba8mp-ras314-imx219.dtso @@ -9,6 +9,7 @@ #include #include +#include #include "imx8mp-pinfunc.h" @@ -47,7 +48,7 @@ camera@10 { VANA-supply = <®_cam>; VDIG-supply = <®_cam>; VDDL-supply = <®_cam>; - orientation = <2>; + orientation = ; rotation = <0>; port { diff --git a/arch/arm64/boot/dts/freescale/imx8mq-librem5.dtsi b/arch/arm64/boot/dts/freescale/imx8mq-librem5.dtsi index ad7c0e7422ca..a212c031d4c6 100644 --- a/arch/arm64/boot/dts/freescale/imx8mq-librem5.dtsi +++ b/arch/arm64/boot/dts/freescale/imx8mq-librem5.dtsi @@ -8,6 +8,7 @@ #include "dt-bindings/input/input.h" #include #include +#include #include "dt-bindings/pwm/pwm.h" #include "dt-bindings/usb/pd.h" #include "imx8mq.dtsi" @@ -1116,7 +1117,7 @@ camera_front: camera@20 { vddd-supply = <®_vcam_1v2>; vddio-supply = <®_csi_1v8>; rotation = <90>; - orientation = <0>; + orientation = ; port { camera1_ep: endpoint { diff --git a/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts b/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts index e9bf2faf6628..2758f1f57ac0 100644 --- a/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts +++ b/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -755,7 +756,7 @@ camera@10 { pinctrl-0 = <&cam_mclk3_default>; pinctrl-names = "default"; - orientation = <0>; /* Front facing */ + orientation = ; rotation = <270>; port { diff --git a/arch/arm64/boot/dts/qcom/sc8280xp-lenovo-thinkpad-x13s.dts b/arch/arm64/boot/dts/qcom/sc8280xp-lenovo-thinkpad-x13s.dts index 52e5e239382b..ee0928721cdf 100644 --- a/arch/arm64/boot/dts/qcom/sc8280xp-lenovo-thinkpad-x13s.dts +++ b/arch/arm64/boot/dts/qcom/sc8280xp-lenovo-thinkpad-x13s.dts @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "sc8280xp.dtsi" @@ -683,7 +684,7 @@ camera@10 { clocks = <&camcc CAMCC_MCLK3_CLK>; - orientation = <0>; /* Front facing */ + orientation = ; avdd-supply = <&vreg_l6q>; dvdd-supply = <&vreg_l2q>; diff --git a/arch/arm64/boot/dts/qcom/sdm670-google-common.dtsi b/arch/arm64/boot/dts/qcom/sdm670-google-common.dtsi index 55f313b5efec..4ce9b62abe53 100644 --- a/arch/arm64/boot/dts/qcom/sdm670-google-common.dtsi +++ b/arch/arm64/boot/dts/qcom/sdm670-google-common.dtsi @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include "sdm670.dtsi" @@ -462,7 +463,7 @@ camera@1a { pinctrl-names = "default"; rotation = <270>; - orientation = <0>; + orientation = ; port { cam_front_endpoint: endpoint { diff --git a/arch/arm64/boot/dts/renesas/r8a779g3-sparrow-hawk-camera-j1-imx219.dtso b/arch/arm64/boot/dts/renesas/r8a779g3-sparrow-hawk-camera-j1-imx219.dtso index 3acaf714cf24..b816382bba0a 100644 --- a/arch/arm64/boot/dts/renesas/r8a779g3-sparrow-hawk-camera-j1-imx219.dtso +++ b/arch/arm64/boot/dts/renesas/r8a779g3-sparrow-hawk-camera-j1-imx219.dtso @@ -12,6 +12,7 @@ #include #include +#include &{/} { clk_cam_j1: clk-cam-j1 { @@ -44,7 +45,7 @@ cam@10 { VDIG-supply = <®_cam_j1>; VDDL-supply = <®_cam_j1>; - orientation = <2>; + orientation = ; rotation = <0>; port { diff --git a/arch/arm64/boot/dts/renesas/r8a779g3-sparrow-hawk-camera-j1-imx462.dtso b/arch/arm64/boot/dts/renesas/r8a779g3-sparrow-hawk-camera-j1-imx462.dtso index a19bc0840392..4019b80a88b7 100644 --- a/arch/arm64/boot/dts/renesas/r8a779g3-sparrow-hawk-camera-j1-imx462.dtso +++ b/arch/arm64/boot/dts/renesas/r8a779g3-sparrow-hawk-camera-j1-imx462.dtso @@ -12,6 +12,7 @@ #include #include +#include &{/} { clk_cam_j1: clk-cam-j1 { @@ -46,7 +47,7 @@ cam@1a { vdda-supply = <®_cam_j1>; vddd-supply = <®_cam_j1>; - orientation = <2>; + orientation = ; rotation = <0>; port { diff --git a/arch/arm64/boot/dts/renesas/r8a779g3-sparrow-hawk-camera-j2-imx219.dtso b/arch/arm64/boot/dts/renesas/r8a779g3-sparrow-hawk-camera-j2-imx219.dtso index 512810b861aa..fea1ef4a1178 100644 --- a/arch/arm64/boot/dts/renesas/r8a779g3-sparrow-hawk-camera-j2-imx219.dtso +++ b/arch/arm64/boot/dts/renesas/r8a779g3-sparrow-hawk-camera-j2-imx219.dtso @@ -12,6 +12,7 @@ #include #include +#include &{/} { clk_cam_j2: clk-cam-j2 { @@ -44,7 +45,7 @@ cam@10 { VDIG-supply = <®_cam_j2>; VDDL-supply = <®_cam_j2>; - orientation = <2>; + orientation = ; rotation = <0>; port { diff --git a/arch/arm64/boot/dts/renesas/r8a779g3-sparrow-hawk-camera-j2-imx462.dtso b/arch/arm64/boot/dts/renesas/r8a779g3-sparrow-hawk-camera-j2-imx462.dtso index a31524b59834..177201a8a6d2 100644 --- a/arch/arm64/boot/dts/renesas/r8a779g3-sparrow-hawk-camera-j2-imx462.dtso +++ b/arch/arm64/boot/dts/renesas/r8a779g3-sparrow-hawk-camera-j2-imx462.dtso @@ -12,6 +12,7 @@ #include #include +#include &{/} { clk_cam_j2: clk-cam-j2 { @@ -46,7 +47,7 @@ cam@1a { vdda-supply = <®_cam_j2>; vddd-supply = <®_cam_j2>; - orientation = <2>; + orientation = ; rotation = <0>; port { diff --git a/arch/arm64/boot/dts/rockchip/px30-pp1516.dtsi b/arch/arm64/boot/dts/rockchip/px30-pp1516.dtsi index 02200de695d3..3ae65dbcfffe 100644 --- a/arch/arm64/boot/dts/rockchip/px30-pp1516.dtsi +++ b/arch/arm64/boot/dts/rockchip/px30-pp1516.dtsi @@ -6,6 +6,7 @@ /dts-v1/; #include #include +#include #include #include "px30.dtsi" @@ -413,7 +414,7 @@ camera@36 { dvdd-supply = <&vcc_cam_dvdd>; dovdd-supply = <&vcc_cam_dovdd>; lens-focus = <&focus>; - orientation = <0>; + orientation = ; pinctrl-names = "default"; pinctrl-0 = <&cif_clkout_m0 &cam_pwdn>; reset-gpios = <&gpio2 RK_PB0 GPIO_ACTIVE_LOW>; diff --git a/arch/arm64/boot/dts/rockchip/px30-ringneck-haikou-video-demo.dtso b/arch/arm64/boot/dts/rockchip/px30-ringneck-haikou-video-demo.dtso index d0725595ade0..1813f06bbff5 100644 --- a/arch/arm64/boot/dts/rockchip/px30-ringneck-haikou-video-demo.dtso +++ b/arch/arm64/boot/dts/rockchip/px30-ringneck-haikou-video-demo.dtso @@ -16,6 +16,7 @@ #include #include #include +#include #include &{/} { @@ -185,7 +186,7 @@ camera@36 { dvdd-supply = <&cam_dvdd_1v2>; dovdd-supply = <&cam_dovdd_1v8>; lens-focus = <&focus>; - orientation = <0>; + orientation = ; pinctrl-names = "default"; pinctrl-0 = <&cif_clkout_m0>; reset-gpios = <&pca9670 6 GPIO_ACTIVE_LOW>; diff --git a/arch/arm64/boot/dts/rockchip/rk3399-pinephone-pro.dts b/arch/arm64/boot/dts/rockchip/rk3399-pinephone-pro.dts index d46cdfe3f784..1a36d54ddfa2 100644 --- a/arch/arm64/boot/dts/rockchip/rk3399-pinephone-pro.dts +++ b/arch/arm64/boot/dts/rockchip/rk3399-pinephone-pro.dts @@ -13,6 +13,7 @@ #include #include #include +#include #include "rk3399-s.dtsi" / { @@ -455,7 +456,7 @@ wcam: camera@1a { reg = <0x1a>; clocks = <&cru SCLK_CIF_OUT>; /* MIPI_MCLK0, derived from CIF_CLKO */ lens-focus = <&wcam_lens>; - orientation = <1>; /* V4L2_CAMERA_ORIENTATION_BACK */ + orientation = ; pinctrl-names = "default"; pinctrl-0 = <&camera_rst_l>; reset-gpios = <&gpio1 RK_PA0 GPIO_ACTIVE_LOW>; @@ -487,7 +488,7 @@ ucam: camera@36 { clocks = <&cru SCLK_CIF_OUT>; /* MIPI_MCLK1, derived from CIF_CLK0 */ clock-names = "xvclk"; dovdd-supply = <&vcc1v8_dvp>; - orientation = <0>; /* V4L2_CAMERA_ORIENTATION_FRONT */ + orientation = ; pinctrl-names = "default"; pinctrl-0 = <&camera2_rst_l &dvp_pdn0_h>; powerdown-gpios = <&gpio2 RK_PB4 GPIO_ACTIVE_LOW>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-plus-radxa-cam4k-cam0.dtso b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-plus-radxa-cam4k-cam0.dtso index 5c7fb960b099..5420ec3fe2ed 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-plus-radxa-cam4k-cam0.dtso +++ b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-plus-radxa-cam4k-cam0.dtso @@ -10,6 +10,7 @@ #include #include #include +#include #include &{/} { @@ -51,7 +52,7 @@ imx415: camera-sensor@1a { avdd-supply = <&savdd_cam0>; clocks = <&cru CLK_MIPI_CAMARAOUT_M3>; dvdd-supply = <&sdvdd_cam0>; - orientation = <2>; /* External */ + orientation = ; ovdd-supply = <&siovdd_cam0>; pinctrl-names = "default"; pinctrl-0 = <&cam0_rstn &mipim0_camera3_clk>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-plus-radxa-cam4k-cam1.dtso b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-plus-radxa-cam4k-cam1.dtso index 1ee9036b9095..8ef764aab73a 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-plus-radxa-cam4k-cam1.dtso +++ b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-plus-radxa-cam4k-cam1.dtso @@ -10,6 +10,7 @@ #include #include #include +#include #include &{/} { @@ -51,7 +52,7 @@ cam1_imx415: camera-sensor@1a { avdd-supply = <&savdd_cam1>; clocks = <&cru CLK_MIPI_CAMARAOUT_M4>; dvdd-supply = <&sdvdd_cam1>; - orientation = <2>; /* External */ + orientation = ; ovdd-supply = <&siovdd_cam1>; pinctrl-names = "default"; pinctrl-0 = <&cam1_rstn &mipim0_camera4_clk>; diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index 317337c32c53..7b4985b29b21 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -1436,7 +1436,6 @@ CONFIG_GREYBUS=m CONFIG_GREYBUS_BEAGLEPLAY=m CONFIG_STAGING=y CONFIG_STAGING_MEDIA=y -CONFIG_VIDEO_MAX96712=m CONFIG_VIDEO_MESON_VDEC=m CONFIG_SND_BCM2835=m CONFIG_CHROME_PLATFORMS=y diff --git a/drivers/gpu/drm/i915/display/intel_display_power.c b/drivers/gpu/drm/i915/display/intel_display_power.c index 1b60ce2dd00c..b000ec001ccf 100644 --- a/drivers/gpu/drm/i915/display/intel_display_power.c +++ b/drivers/gpu/drm/i915/display/intel_display_power.c @@ -2085,9 +2085,9 @@ static void assert_ved_power_gated(struct intel_display *display) static void assert_isp_power_gated(struct intel_display *display) { static const struct pci_device_id isp_ids[] = { - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0f38)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x22b8)}, - {} + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISP_BYT) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISP_BSW) }, + { } }; drm_WARN(display->drm, !pci_dev_present(isp_ids) && diff --git a/drivers/i2c/i2c-atr.c b/drivers/i2c/i2c-atr.c index ca29633dcd62..b98442ffff94 100644 --- a/drivers/i2c/i2c-atr.c +++ b/drivers/i2c/i2c-atr.c @@ -540,6 +540,12 @@ static int i2c_atr_attach_addr(struct i2c_adapter *adapter, struct i2c_atr_alias_pair *c2a; int ret = 0; + /* + * attach_addr() may reconfigure the remote side of the bus, keep + * transfers on other channels out until it is done. + */ + mutex_lock(&atr->lock); + mutex_lock(&chan->alias_pairs_lock); c2a = i2c_atr_create_mapping_by_addr(chan, addr); @@ -557,6 +563,8 @@ static int i2c_atr_attach_addr(struct i2c_adapter *adapter, out_unlock: mutex_unlock(&chan->alias_pairs_lock); + mutex_unlock(&atr->lock); + return ret; } @@ -567,6 +575,8 @@ static void i2c_atr_detach_addr(struct i2c_adapter *adapter, struct i2c_atr *atr = chan->atr; struct i2c_atr_alias_pair *c2a; + mutex_lock(&atr->lock); + atr->ops->detach_addr(atr, chan->chan_id, addr); mutex_lock(&chan->alias_pairs_lock); @@ -574,6 +584,7 @@ static void i2c_atr_detach_addr(struct i2c_adapter *adapter, c2a = i2c_atr_find_mapping_by_addr(chan, addr); if (!c2a) { mutex_unlock(&chan->alias_pairs_lock); + mutex_unlock(&atr->lock); return; } @@ -586,6 +597,7 @@ static void i2c_atr_detach_addr(struct i2c_adapter *adapter, i2c_atr_destroy_c2a(&c2a); mutex_unlock(&chan->alias_pairs_lock); + mutex_unlock(&atr->lock); } static int i2c_atr_bus_notifier_call(struct notifier_block *nb, diff --git a/drivers/media/cec/platform/meson/ao-cec-g12a.c b/drivers/media/cec/platform/meson/ao-cec-g12a.c index b175be3f2bf4..55bd0d416460 100644 --- a/drivers/media/cec/platform/meson/ao-cec-g12a.c +++ b/drivers/media/cec/platform/meson/ao-cec-g12a.c @@ -334,7 +334,7 @@ static int meson_ao_cec_g12a_setup_clk(struct meson_ao_cec_g12a_device *ao_cec) { struct meson_ao_cec_g12a_dualdiv_clk *dualdiv_clk; struct device *dev = &ao_cec->pdev->dev; - struct clk_init_data init; + struct clk_init_data init = {}; const char *parent_name; struct clk *clk; char *name; diff --git a/drivers/media/cec/usb/extron-da-hd-4k-plus/extron-da-hd-4k-plus.c b/drivers/media/cec/usb/extron-da-hd-4k-plus/extron-da-hd-4k-plus.c index 3c6ce6f3d93e..1f8ed7ff31e5 100644 --- a/drivers/media/cec/usb/extron-da-hd-4k-plus/extron-da-hd-4k-plus.c +++ b/drivers/media/cec/usb/extron-da-hd-4k-plus/extron-da-hd-4k-plus.c @@ -847,7 +847,9 @@ static irqreturn_t extron_interrupt(struct serio *serio, unsigned char data, return IRQ_HANDLED; memcpy(extron->data, extron->buf, extron->idx); extron->len = extron->idx; - extron->data[extron->len] = 0; + /* Keep fixed-offset response tests from using stale tail bytes. */ + memset(extron->data + extron->len, 0, + min_t(size_t, 10, sizeof(extron->data) - extron->len)); if (debug) dev_info(extron->dev, "received %s\n", extron->data); extron->idx = 0; diff --git a/drivers/media/common/cypress_firmware.c b/drivers/media/common/cypress_firmware.c index 66274fdf5243..d0f66ed01c4b 100644 --- a/drivers/media/common/cypress_firmware.c +++ b/drivers/media/common/cypress_firmware.c @@ -59,6 +59,8 @@ static int cypress_get_hexline(const struct firmware *fw, if (hx->type == 0x04) { /* b[4] and b[5] are the Extended linear address record data * field */ + if (hx->len != 2) + return -EINVAL; hx->addr |= (b[4] << 24) | (b[5] << 16); } diff --git a/drivers/media/common/saa7146/saa7146_core.c b/drivers/media/common/saa7146/saa7146_core.c index c297d019f2b3..92b5be153613 100644 --- a/drivers/media/common/saa7146/saa7146_core.c +++ b/drivers/media/common/saa7146/saa7146_core.c @@ -18,17 +18,6 @@ unsigned int saa7146_debug; module_param(saa7146_debug, uint, 0644); MODULE_PARM_DESC(saa7146_debug, "debug level (default: 0)"); -#if 0 -static void dump_registers(struct saa7146_dev* dev) -{ - int i = 0; - - pr_info(" @ %li jiffies:\n", jiffies); - for (i = 0; i <= 0x148; i += 4) - pr_info("0x%03x: 0x%08x\n", i, saa7146_read(dev, i)); -} -#endif - /**************************************************************************** * gpio and debi helper functions ****************************************************************************/ @@ -252,6 +241,15 @@ int saa7146_pgtable_build_single(struct pci_dev *pci, struct saa7146_pgtable *pt ptr = pt->cpu; for_each_sg_dma_page(list, &dma_iter, sglen, 0) { + /* + * The page table is exactly PAGE_SIZE large, i.e. it holds + * PAGE_SIZE / sizeof(__le32) entries. Buffers needing more + * pages would overflow it. + */ + if (nr_pages >= PAGE_SIZE / sizeof(__le32)) { + pr_err("page table too small\n"); + return -EIO; + } *ptr++ = cpu_to_le32(sg_page_iter_dma_address(&dma_iter)); nr_pages++; } @@ -340,6 +338,8 @@ static int saa7146_init_one(struct pci_dev *pci, const struct pci_device_id *ent goto out; } + spin_lock_init(&dev->int_slock); + /* create a nice device name */ sprintf(dev->name, "saa7146 (%d)", saa7146_num); @@ -425,7 +425,6 @@ static int saa7146_init_one(struct pci_dev *pci, const struct pci_device_id *ent dev->ext = ext; mutex_init(&dev->v4l2_lock); - spin_lock_init(&dev->int_slock); spin_lock_init(&dev->slock); mutex_init(&dev->i2c_lock); diff --git a/drivers/media/common/saa7146/saa7146_video.c b/drivers/media/common/saa7146/saa7146_video.c index 733e18001d0d..c895c90b1378 100644 --- a/drivers/media/common/saa7146/saa7146_video.c +++ b/drivers/media/common/saa7146/saa7146_video.c @@ -410,6 +410,17 @@ static int vidioc_try_fmt_vid_cap(struct file *file, void *fh, struct v4l2_forma f->fmt.pix.bytesperline = calc_bpl; f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height; + + /* + * The DMA page tables hold one entry per page and are exactly + * PAGE_SIZE large. Reject formats whose buffer would need more + * entries than the tables can hold. + */ + if (f->fmt.pix.sizeimage > PAGE_SIZE / sizeof(__le32) * PAGE_SIZE) { + DEB_D("sizeimage %d too large\n", f->fmt.pix.sizeimage); + return -EINVAL; + } + DEB_D("w:%d, h:%d, bytesperline:%d, sizeimage:%d\n", f->fmt.pix.width, f->fmt.pix.height, f->fmt.pix.bytesperline, f->fmt.pix.sizeimage); diff --git a/drivers/media/dvb-frontends/drxd_map_firm.h b/drivers/media/dvb-frontends/drxd_map_firm.h index bdcc63576df1..a6585e53d879 100644 --- a/drivers/media/dvb-frontends/drxd_map_firm.h +++ b/drivers/media/dvb-frontends/drxd_map_firm.h @@ -10,8 +10,8 @@ /* * Note: originally, this file contained 12000+ lines of data - * Probably a few lines for every firwmare assembler instruction. However, - * only a few defines were actually used. So, removed all uneeded lines. + * Probably a few lines for every firmware assembler instruction. However, + * only a few defines were actually used. So, removed all unneeded lines. * If ever needed, the other lines can be easily obtained via git history. */ diff --git a/drivers/media/dvb-frontends/stv0900_core.c b/drivers/media/dvb-frontends/stv0900_core.c index d15c55de2723..0ca6b6d81273 100644 --- a/drivers/media/dvb-frontends/stv0900_core.c +++ b/drivers/media/dvb-frontends/stv0900_core.c @@ -1773,6 +1773,8 @@ static int stv0900_recv_slave_reply(struct dvb_frontend *fe, if (stv0900_get_bits(intp, RX_END)) { reply->msg_len = stv0900_get_bits(intp, FIFO_BYTENBR); + if (reply->msg_len > sizeof(reply->msg)) + reply->msg_len = sizeof(reply->msg); for (i = 0; i < reply->msg_len; i++) reply->msg[i] = stv0900_read_reg(intp, DISRXDATA); diff --git a/drivers/media/dvb-frontends/stv090x.c b/drivers/media/dvb-frontends/stv090x.c index 932bbed5497a..63be9ee104ab 100644 --- a/drivers/media/dvb-frontends/stv090x.c +++ b/drivers/media/dvb-frontends/stv090x.c @@ -3902,6 +3902,8 @@ static int stv090x_recv_slave_reply(struct dvb_frontend *fe, struct dvb_diseqc_s if (rx_end) { reply->msg_len = STV090x_GETFIELD_Px(reg, FIFO_BYTENBR_FIELD); + if (reply->msg_len > sizeof(reply->msg)) + reply->msg_len = sizeof(reply->msg); for (i = 0; i < reply->msg_len; i++) reply->msg[i] = STV090x_READ_DEMOD(state, DISRXDATA); } diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 5c52007f9cbe..7de2d095c969 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -137,6 +137,17 @@ config VIDEO_HI847 To compile this driver as a module, choose M here: the module will be called hi847. +config VIDEO_HM1246 + tristate "Himax HM1246 sensor support" + depends on OF + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the Himax + HM1246 camera. + + To compile this driver as a module, choose M here: the + module will be called hm1246. + config VIDEO_IMX111 tristate "Sony IMX111 sensor support" select V4L2_CCI_I2C @@ -299,6 +310,17 @@ config VIDEO_IMX471 To compile this driver as a module, choose M here: the module will be called imx471. +config VIDEO_IMX576 + tristate "Sony IMX576 sensor support" + select V4L2_CCI_I2C + select VIDEO_CCS_PLL + help + This is a Video4Linux2 sensor driver for the Sony + IMX576 camera. + + To compile this driver as a module, choose M here: the + module will be called imx576. + config VIDEO_IMX678 tristate "Sony IMX678 sensor support" depends on GPIOLIB @@ -396,6 +418,16 @@ config VIDEO_OG0VE1B To compile this driver as a module, choose M here: the module will be called og0ve1b. +config VIDEO_OS02G10 + tristate "OmniVision OS02G10 sensor support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for Omnivision + OS02G10 camera sensor. + + To compile this driver as a module, choose M here: the + module will be called os02g10. + config VIDEO_OS05B10 tristate "OmniVision OS05B10 sensor support" select V4L2_CCI_I2C @@ -445,6 +477,16 @@ config VIDEO_OV02C10 To compile this driver as a module, choose M here: the module will be called ov02c10. +config VIDEO_OV05C10 + tristate "OmniVision OV05C10 sensor support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the OmniVision + OV05C10 camera. + + To compile this driver as a module, choose M here: the + module will be called ov05c10. + config VIDEO_OV08D10 tristate "OmniVision OV08D10 sensor support" help @@ -826,6 +868,17 @@ config VIDEO_S5KJN1 To compile this driver as a module, choose M here: the module will be called s5kjn1. +config VIDEO_S5KJN5 + tristate "Samsung S5KJN5 sensor support" + depends on GPIOLIB + select V4L2_CCI_I2C + help + This is a V4L2 sensor driver for Samsung S5KJN5 50MP + 10-bit RAW MIPI CSI-2 camera sensor. + + To compile this driver as a module, choose M here: the + module will be called s5kjn5. + config VIDEO_T4KA3 tristate "Toshiba T4KA3 sensor support" depends on ACPI || COMPILE_TEST @@ -989,14 +1042,12 @@ endmenu # V4L2 I2C drivers that aren't related with Camera support # -comment "audio, video and radio I2C drivers auto-selected by 'Autoselect ancillary drivers'" - depends on MEDIA_HIDE_ANCILLARY_SUBDRV +comment "audio, video and radio I2C drivers" # # Encoder / Decoder module configuration # menu "Audio decoders, processors and mixers" - visible if !MEDIA_HIDE_ANCILLARY_SUBDRV config VIDEO_CS3308 tristate "Cirrus Logic CS3308 audio ADC" @@ -1159,7 +1210,6 @@ config VIDEO_WM8775 endmenu menu "RDS decoders" - visible if !MEDIA_HIDE_ANCILLARY_SUBDRV config VIDEO_SAA6588 tristate "SAA6588 Radio Chip RDS decoder support" @@ -1176,7 +1226,6 @@ config VIDEO_SAA6588 endmenu menu "Video decoders" - visible if !MEDIA_HIDE_ANCILLARY_SUBDRV config VIDEO_ADV7180 tristate "Analog Devices ADV7180 decoder" @@ -1301,6 +1350,25 @@ config VIDEO_ISL7998X Support for Intersil ISL7998x analog to MIPI-CSI2 or BT.656 decoder. +config VIDEO_IT6625 + tristate "IT6625 HDMI to MIPI CSI bridge" + depends on VIDEO_DEV && I2C + depends on OF + select CEC_CORE + select HDMI + select MEDIA_CONTROLLER + select REGMAP_I2C + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + help + V4L2 subdevice driver for the ITE IT6625/IT6626 HDMI to MIPI + CSI-2 bridge chips. IT6625 accepts an HDMI 2.0 input and + IT6626 an HDMI 2.1 input, converting it to a MIPI CSI-2 + output. The driver also exposes an HDMI CEC adapter. + + To compile this driver as a module, choose M here: the + module will be called it6625. + config VIDEO_LT6911UXE tristate "Lontium LT6911UXE decoder" depends on ACPI && VIDEO_DEV && I2C @@ -1517,7 +1585,6 @@ source "drivers/media/i2c/cx25840/Kconfig" endmenu menu "Video encoders" - visible if !MEDIA_HIDE_ANCILLARY_SUBDRV config VIDEO_ADV7170 tristate "Analog Devices ADV7170 video encoder" @@ -1616,7 +1683,6 @@ config VIDEO_THS8200 endmenu menu "Video improvement chips" - visible if !MEDIA_HIDE_ANCILLARY_SUBDRV config VIDEO_UPD64031A tristate "NEC Electronics uPD64031A Ghost Reduction" @@ -1645,7 +1711,6 @@ config VIDEO_UPD64083 endmenu menu "Audio/Video compression chips" - visible if !MEDIA_HIDE_ANCILLARY_SUBDRV config VIDEO_SAA6752HS tristate "Philips SAA6752HS MPEG-2 Audio/Video Encoder" @@ -1661,7 +1726,6 @@ config VIDEO_SAA6752HS endmenu menu "SDR tuner chips" - visible if !MEDIA_HIDE_ANCILLARY_SUBDRV config SDR_MAX2175 tristate "Maxim 2175 RF to Bits tuner" @@ -1678,7 +1742,6 @@ config SDR_MAX2175 endmenu menu "Miscellaneous helper chips" - visible if !MEDIA_HIDE_ANCILLARY_SUBDRV source "drivers/media/i2c/cvs/Kconfig" @@ -1777,39 +1840,7 @@ config VIDEO_DS90UB960 Device driver for the Texas Instruments DS90UB954, DS90UB960 FPD-Link III Deserializers and DS90UB9702 FPD-Link IV Deserializer. -config VIDEO_MAX96714 - tristate "Maxim MAX96714 GMSL2 deserializer" - depends on OF && I2C && VIDEO_DEV - select I2C_MUX - select MEDIA_CONTROLLER - select GPIOLIB - select V4L2_CCI_I2C - select V4L2_FWNODE - select VIDEO_V4L2_SUBDEV_API - help - Device driver for the Maxim MAX96714 GMSL2 Deserializer. - MAX96714 deserializers convert a GMSL2 input to MIPI CSI-2 - output. - - To compile this driver as a module, choose M here: the - module will be called max96714. - -config VIDEO_MAX96717 - tristate "Maxim MAX96717 GMSL2 Serializer support" - depends on I2C && VIDEO_DEV && COMMON_CLK - select I2C_MUX - select MEDIA_CONTROLLER - select GPIOLIB - select V4L2_CCI_I2C - select V4L2_FWNODE - select VIDEO_V4L2_SUBDEV_API - help - Device driver for the Maxim MAX96717 GMSL2 Serializer. - MAX96717 serializers convert video on a MIPI CSI-2 - input to a GMSL2 output. - - To compile this driver as a module, choose M here: the - module will be called max96717. +source "drivers/media/i2c/maxim-serdes/Kconfig" endmenu diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index d04bd5724552..59e31af457cd 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -45,6 +45,7 @@ obj-$(CONFIG_VIDEO_GC2145) += gc2145.o obj-$(CONFIG_VIDEO_HI556) += hi556.o obj-$(CONFIG_VIDEO_HI846) += hi846.o obj-$(CONFIG_VIDEO_HI847) += hi847.o +obj-$(CONFIG_VIDEO_HM1246) += hm1246.o obj-$(CONFIG_VIDEO_I2C) += video-i2c.o obj-$(CONFIG_VIDEO_IMX111) += imx111.o obj-$(CONFIG_VIDEO_IMX208) += imx208.o @@ -61,10 +62,12 @@ obj-$(CONFIG_VIDEO_IMX335) += imx335.o obj-$(CONFIG_VIDEO_IMX355) += imx355.o obj-$(CONFIG_VIDEO_IMX412) += imx412.o obj-$(CONFIG_VIDEO_IMX415) += imx415.o -obj-$(CONFIG_VIDEO_IMX678) += imx678.o obj-$(CONFIG_VIDEO_IMX471) += imx471.o +obj-$(CONFIG_VIDEO_IMX576) += imx576.o +obj-$(CONFIG_VIDEO_IMX678) += imx678.o obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o obj-$(CONFIG_VIDEO_ISL7998X) += isl7998x.o +obj-$(CONFIG_VIDEO_IT6625) += it6625.o obj-$(CONFIG_VIDEO_KS0127) += ks0127.o obj-$(CONFIG_VIDEO_LM3560) += lm3560.o obj-$(CONFIG_VIDEO_LM3646) += lm3646.o @@ -72,8 +75,7 @@ obj-$(CONFIG_VIDEO_LT6911UXE) += lt6911uxe.o obj-$(CONFIG_VIDEO_M52790) += m52790.o obj-$(CONFIG_VIDEO_MAX9271_LIB) += max9271.o obj-$(CONFIG_VIDEO_MAX9286) += max9286.o -obj-$(CONFIG_VIDEO_MAX96714) += max96714.o -obj-$(CONFIG_VIDEO_MAX96717) += max96717.o +obj-$(CONFIG_VIDEO_MAXIM_SERDES) += maxim-serdes/ obj-$(CONFIG_VIDEO_ML86V7667) += ml86v7667.o obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o obj-$(CONFIG_VIDEO_MT9M001) += mt9m001.o @@ -86,11 +88,13 @@ obj-$(CONFIG_VIDEO_MT9V032) += mt9v032.o obj-$(CONFIG_VIDEO_MT9V111) += mt9v111.o obj-$(CONFIG_VIDEO_OG01A1B) += og01a1b.o obj-$(CONFIG_VIDEO_OG0VE1B) += og0ve1b.o +obj-$(CONFIG_VIDEO_OS02G10) += os02g10.o obj-$(CONFIG_VIDEO_OS05B10) += os05b10.o obj-$(CONFIG_VIDEO_OV01A10) += ov01a10.o obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o obj-$(CONFIG_VIDEO_OV02C10) += ov02c10.o obj-$(CONFIG_VIDEO_OV02E10) += ov02e10.o +obj-$(CONFIG_VIDEO_OV05C10) += ov05c10.o obj-$(CONFIG_VIDEO_OV08D10) += ov08d10.o obj-$(CONFIG_VIDEO_OV08X40) += ov08x40.o obj-$(CONFIG_VIDEO_OV13858) += ov13858.o @@ -133,6 +137,7 @@ obj-$(CONFIG_VIDEO_S5K3M5) += s5k3m5.o obj-$(CONFIG_VIDEO_S5K5BAF) += s5k5baf.o obj-$(CONFIG_VIDEO_S5K6A3) += s5k6a3.o obj-$(CONFIG_VIDEO_S5KJN1) += s5kjn1.o +obj-$(CONFIG_VIDEO_S5KJN5) += s5kjn5.o obj-$(CONFIG_VIDEO_SAA6588) += saa6588.o obj-$(CONFIG_VIDEO_SAA6752HS) += saa6752hs.o obj-$(CONFIG_VIDEO_SAA7110) += saa7110.o diff --git a/drivers/media/i2c/adv7170.c b/drivers/media/i2c/adv7170.c index 812998729207..9a7edd6583c1 100644 --- a/drivers/media/i2c/adv7170.c +++ b/drivers/media/i2c/adv7170.c @@ -284,6 +284,7 @@ static int adv7170_get_fmt(struct v4l2_subdev *sd, } static int adv7170_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/adv7175.c b/drivers/media/i2c/adv7175.c index f1caab8e2abd..b77737e23a47 100644 --- a/drivers/media/i2c/adv7175.c +++ b/drivers/media/i2c/adv7175.c @@ -209,7 +209,7 @@ static int adv7175_s_std_output(struct v4l2_subdev *sd, v4l2_std_id std) /* This is an attempt to convert * SECAM->PAL (typically it does not work * due to genlock: when decoder is in SECAM - * and encoder in in PAL the subcarrier can + * and encoder in PAL the subcarrier can * not be synchronized with horizontal * quency) */ adv7175_write_block(sd, init_pal, sizeof(init_pal)); @@ -322,6 +322,7 @@ static int adv7175_get_fmt(struct v4l2_subdev *sd, } static int adv7175_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/adv7180.c b/drivers/media/i2c/adv7180.c index a1c7f68225b4..02197f813758 100644 --- a/drivers/media/i2c/adv7180.c +++ b/drivers/media/i2c/adv7180.c @@ -770,6 +770,7 @@ static int adv7180_get_pad_format(struct v4l2_subdev *sd, } static int adv7180_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -807,7 +808,7 @@ static int adv7180_init_state(struct v4l2_subdev *sd, : V4L2_SUBDEV_FORMAT_ACTIVE, }; - return adv7180_set_pad_format(sd, sd_state, &fmt); + return adv7180_set_pad_format(sd, NULL, sd_state, &fmt); } static int adv7180_get_mbus_config(struct v4l2_subdev *sd, @@ -1454,6 +1455,7 @@ static int adv7180_probe(struct i2c_client *client) if (state == NULL) return -ENOMEM; + mutex_init(&state->mutex); state->client = client; state->field = V4L2_FIELD_ALTERNATE; state->chip_info = i2c_get_match_data(client); @@ -1497,7 +1499,6 @@ static int adv7180_probe(struct i2c_client *client) } state->irq = client->irq; - mutex_init(&state->mutex); state->curr_norm = V4L2_STD_NTSC; state->input = 0; diff --git a/drivers/media/i2c/adv7183.c b/drivers/media/i2c/adv7183.c index a04a1a205fe0..9e4cbebe6e5a 100644 --- a/drivers/media/i2c/adv7183.c +++ b/drivers/media/i2c/adv7183.c @@ -420,6 +420,7 @@ static int adv7183_enum_mbus_code(struct v4l2_subdev *sd, } static int adv7183_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -598,7 +599,7 @@ static int adv7183_probe(struct i2c_client *client) adv7183_s_std(sd, decoder->std); fmt.format.width = 720; fmt.format.height = 576; - adv7183_set_fmt(sd, NULL, &fmt); + adv7183_set_fmt(sd, NULL, NULL, &fmt); /* initialize the hardware to the default control values */ ret = v4l2_ctrl_handler_setup(hdl); diff --git a/drivers/media/i2c/adv7343.c b/drivers/media/i2c/adv7343.c index 9b91d7073d3c..2bf17cd633c7 100644 --- a/drivers/media/i2c/adv7343.c +++ b/drivers/media/i2c/adv7343.c @@ -1,18 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * adv7343 - ADV7343 Video Encoder Driver * * The encoder hardware does not support SECAM. * * Copyright (C) 2009 Texas Instruments Incorporated - http://www.ti.com/ - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation version 2. - * - * This program is distributed .as is. WITHOUT ANY WARRANTY of any - * kind, whether express or implied; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #include diff --git a/drivers/media/i2c/adv7393.c b/drivers/media/i2c/adv7393.c index 6f948ba02f86..b00783c141a9 100644 --- a/drivers/media/i2c/adv7393.c +++ b/drivers/media/i2c/adv7393.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * adv7393 - ADV7393 Video Encoder Driver * @@ -9,15 +10,6 @@ * Based on ADV7343 driver, * * Copyright (C) 2009 Texas Instruments Incorporated - http://www.ti.com/ - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation version 2. - * - * This program is distributed .as is. WITHOUT ANY WARRANTY of any - * kind, whether express or implied; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #include diff --git a/drivers/media/i2c/adv748x/adv748x-afe.c b/drivers/media/i2c/adv748x/adv748x-afe.c index 678199196b84..28b755df30cf 100644 --- a/drivers/media/i2c/adv748x/adv748x-afe.c +++ b/drivers/media/i2c/adv748x/adv748x-afe.c @@ -349,6 +349,7 @@ static int adv748x_afe_get_format(struct v4l2_subdev *sd, } static int adv748x_afe_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *sdformat) { diff --git a/drivers/media/i2c/adv748x/adv748x-core.c b/drivers/media/i2c/adv748x/adv748x-core.c index 3eb6d5e8f082..70594d001804 100644 --- a/drivers/media/i2c/adv748x/adv748x-core.c +++ b/drivers/media/i2c/adv748x/adv748x-core.c @@ -675,9 +675,6 @@ static int adv748x_parse_dt(struct adv748x_state *state) continue; } - of_node_get(ep_np); - state->endpoints[ep.port] = ep_np; - /* * At least one input endpoint and one output endpoint shall * be defined. @@ -689,8 +686,13 @@ static int adv748x_parse_dt(struct adv748x_state *state) /* Store number of CSI-2 lanes used for TXA and TXB. */ ret = adv748x_parse_csi2_lanes(state, ep.port, ep_np); - if (ret) + if (ret) { + of_node_put(ep_np); return ret; + } + + of_node_get(ep_np); + state->endpoints[ep.port] = ep_np; } return in_found && out_found ? 0 : -ENODEV; @@ -739,7 +741,7 @@ static int adv748x_probe(struct i2c_client *client) ret = adv748x_parse_dt(state); if (ret) { adv_err(state, "Failed to parse device tree"); - goto err_free_mutex; + goto err_cleanup_dt; } /* Configure IO Regmap region */ @@ -809,7 +811,6 @@ static int adv748x_probe(struct i2c_client *client) adv748x_unregister_clients(state); err_cleanup_dt: adv748x_dt_cleanup(state); -err_free_mutex: mutex_destroy(&state->mutex); return ret; diff --git a/drivers/media/i2c/adv748x/adv748x-csi2.c b/drivers/media/i2c/adv748x/adv748x-csi2.c index ebe7da8ebed7..1d7ab685d229 100644 --- a/drivers/media/i2c/adv748x/adv748x-csi2.c +++ b/drivers/media/i2c/adv748x/adv748x-csi2.c @@ -226,6 +226,7 @@ static bool adv748x_csi2_is_fmt_supported(struct adv748x_csi2 *tx, u32 code) } static int adv748x_csi2_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *sdformat) { diff --git a/drivers/media/i2c/adv748x/adv748x-hdmi.c b/drivers/media/i2c/adv748x/adv748x-hdmi.c index b154dea29ba2..8d5ebb369961 100644 --- a/drivers/media/i2c/adv748x/adv748x-hdmi.c +++ b/drivers/media/i2c/adv748x/adv748x-hdmi.c @@ -440,6 +440,7 @@ static int adv748x_hdmi_get_format(struct v4l2_subdev *sd, } static int adv748x_hdmi_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *sdformat) { diff --git a/drivers/media/i2c/adv7511-v4l2.c b/drivers/media/i2c/adv7511-v4l2.c index 860cff50c522..27e0e2c31dbd 100644 --- a/drivers/media/i2c/adv7511-v4l2.c +++ b/drivers/media/i2c/adv7511-v4l2.c @@ -1273,6 +1273,7 @@ static int adv7511_get_fmt(struct v4l2_subdev *sd, } static int adv7511_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/adv7604.c b/drivers/media/i2c/adv7604.c index ac9c69ce438f..2de743d8ca21 100644 --- a/drivers/media/i2c/adv7604.c +++ b/drivers/media/i2c/adv7604.c @@ -1943,6 +1943,7 @@ static int adv76xx_get_format(struct v4l2_subdev *sd, } static int adv76xx_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1963,6 +1964,7 @@ static int adv76xx_get_selection(struct v4l2_subdev *sd, } static int adv76xx_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -2449,7 +2451,7 @@ static int adv76xx_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) cec_s_phys_addr(state->cec_adap, parent_pa, false); /* enable hotplug after 143 ms */ - schedule_delayed_work(&state->delayed_work_enable_hotplug, HZ / 7); + schedule_delayed_work(&state->delayed_work_enable_hotplug, V4L2_SET_EDID_HPD_LOW_JIFFIES); return 0; } @@ -2641,8 +2643,9 @@ static int adv76xx_log_status(struct v4l2_subdev *sd) "(16-235)" : "(0-255)", (reg_io_0x02 & 0x08) ? "enabled" : "disabled"); } + ret = cp_read(sd, info->cp_csc) >> 4; v4l2_info(sd, "Color space conversion: %s\n", - csc_coeff_sel_rb[cp_read(sd, info->cp_csc) >> 4]); + ret < 0 ? "" : csc_coeff_sel_rb[ret]); if (!is_digital_input(sd)) return 0; diff --git a/drivers/media/i2c/adv7842.c b/drivers/media/i2c/adv7842.c index 3cfae89ce944..df6601d6d2b6 100644 --- a/drivers/media/i2c/adv7842.c +++ b/drivers/media/i2c/adv7842.c @@ -747,8 +747,9 @@ static int edid_write_vga_segment(struct v4l2_subdev *sd) return -EIO; } - /* enable hotplug after 200 ms */ - schedule_delayed_work(&state->delayed_work_enable_hotplug, HZ / 5); + /* enable hotplug after 143 ms */ + schedule_delayed_work(&state->delayed_work_enable_hotplug, + V4L2_SET_EDID_HPD_LOW_JIFFIES); return 0; } @@ -830,8 +831,9 @@ static int edid_write_hdmi_segment(struct v4l2_subdev *sd, u8 port) } cec_s_phys_addr(state->cec_adap, parent_pa, false); - /* enable hotplug after 200 ms */ - schedule_delayed_work(&state->delayed_work_enable_hotplug, HZ / 5); + /* enable hotplug after 143 ms */ + schedule_delayed_work(&state->delayed_work_enable_hotplug, + V4L2_SET_EDID_HPD_LOW_JIFFIES); return 0; } @@ -2110,6 +2112,7 @@ static int adv7842_get_format(struct v4l2_subdev *sd, } static int adv7842_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/ak881x.c b/drivers/media/i2c/ak881x.c index cea46f01997d..3449d95ee8a0 100644 --- a/drivers/media/i2c/ak881x.c +++ b/drivers/media/i2c/ak881x.c @@ -122,6 +122,7 @@ static int ak881x_enum_mbus_code(struct v4l2_subdev *sd, } static int ak881x_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -216,7 +217,6 @@ static const struct v4l2_subdev_video_ops ak881x_subdev_video_ops = { static const struct v4l2_subdev_pad_ops ak881x_subdev_pad_ops = { .enum_mbus_code = ak881x_enum_mbus_code, .get_selection = ak881x_get_selection, - .set_fmt = ak881x_fill_fmt, .get_fmt = ak881x_fill_fmt, }; diff --git a/drivers/media/i2c/alvium-csi2.c b/drivers/media/i2c/alvium-csi2.c index f51f9b987759..d9a73565457f 100644 --- a/drivers/media/i2c/alvium-csi2.c +++ b/drivers/media/i2c/alvium-csi2.c @@ -1887,6 +1887,7 @@ static int alvium_init_state(struct v4l2_subdev *sd, } static int alvium_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -1922,6 +1923,7 @@ static int alvium_set_fmt(struct v4l2_subdev *sd, } static int alvium_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1957,6 +1959,7 @@ static int alvium_set_selection(struct v4l2_subdev *sd, } static int alvium_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -2523,6 +2526,7 @@ static void alvium_remove(struct i2c_client *client) * make sure to turn power off manually. */ pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); if (!pm_runtime_status_suspended(dev)) alvium_set_power(alvium, false); pm_runtime_set_suspended(dev); diff --git a/drivers/media/i2c/ar0521.c b/drivers/media/i2c/ar0521.c index ed324c2d87aa..3ddbf10ef8a4 100644 --- a/drivers/media/i2c/ar0521.c +++ b/drivers/media/i2c/ar0521.c @@ -457,6 +457,7 @@ static int ar0521_get_fmt(struct v4l2_subdev *sd, } static int ar0521_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -1142,7 +1143,6 @@ static int ar0521_probe(struct i2c_client *client) disable: v4l2_async_unregister_subdev(&sensor->sd); - media_entity_cleanup(&sensor->sd.entity); free_ctrls: v4l2_ctrl_handler_free(&sensor->ctrls.handler); entity_cleanup: diff --git a/drivers/media/i2c/ccs/ccs-core.c b/drivers/media/i2c/ccs/ccs-core.c index 8e25f970fd12..1cc4350856fd 100644 --- a/drivers/media/i2c/ccs/ccs-core.c +++ b/drivers/media/i2c/ccs/ccs-core.c @@ -2189,6 +2189,7 @@ static void ccs_propagate(struct v4l2_subdev *subdev, } static int ccs_set_format_source(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -2242,6 +2243,7 @@ static int ccs_set_format_source(struct v4l2_subdev *subdev, } static int ccs_set_format(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -2252,7 +2254,7 @@ static int ccs_set_format(struct v4l2_subdev *subdev, if (fmt->pad == ssd->source_pad) { int rval; - rval = ccs_set_format_source(subdev, sd_state, fmt); + rval = ccs_set_format_source(subdev, ci, sd_state, fmt); return rval; } @@ -2467,6 +2469,7 @@ static void ccs_set_compose_scaler(struct v4l2_subdev *subdev, } /* We're only called on source pads. This function sets scaling. */ static int ccs_set_compose(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -2536,6 +2539,7 @@ static int ccs_sel_supported(struct v4l2_subdev *subdev, } static int ccs_set_crop(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -2587,6 +2591,7 @@ static void ccs_get_native_size(struct ccs_subdev *ssd, struct v4l2_rect *r) } static int ccs_get_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -2633,6 +2638,7 @@ static int ccs_get_selection(struct v4l2_subdev *subdev, } static int ccs_set_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -2655,10 +2661,10 @@ static int ccs_set_selection(struct v4l2_subdev *subdev, switch (sel->target) { case V4L2_SEL_TGT_CROP: - ret = ccs_set_crop(subdev, sd_state, sel); + ret = ccs_set_crop(subdev, ci, sd_state, sel); break; case V4L2_SEL_TGT_COMPOSE: - ret = ccs_set_compose(subdev, sd_state, sel); + ret = ccs_set_compose(subdev, ci, sd_state, sel); break; default: ret = -EINVAL; diff --git a/drivers/media/i2c/ccs/ccs-data.h b/drivers/media/i2c/ccs/ccs-data.h index 638df69804ec..522466b81929 100644 --- a/drivers/media/i2c/ccs/ccs-data.h +++ b/drivers/media/i2c/ccs/ccs-data.h @@ -160,7 +160,7 @@ struct ccs_pdaf_pix_loc_pixel_desc_group { * @main_offset_y: Start Y coordinate of PDAF pixel blocks * @global_pdaf_type: PDAF pattern type * @block_width: Width of a block in pixels - * @block_height: Heigth of a block in pixels + * @block_height: Height of a block in pixels * @num_block_desc_groups: Number of block descriptor groups * @block_desc_groups: Block descriptor groups * @num_pixel_desc_grups: Number of pixel descriptor groups @@ -197,7 +197,7 @@ struct ccs_pdaf_pix_loc { * @module_rules: Rules for the module * @sensor_pdaf: PDAF data for the sensor * @module_pdaf: PDAF data for the module - * @license_length: Lenght of the license data + * @license_length: Length of the license data * @license: License data * @end: Whether or not there's an end block * @backing: Raw data, pointed to from elsewhere so keep it around diff --git a/drivers/media/i2c/cvs/Kconfig b/drivers/media/i2c/cvs/Kconfig index 4309d20dd726..f482f1ccc38a 100644 --- a/drivers/media/i2c/cvs/Kconfig +++ b/drivers/media/i2c/cvs/Kconfig @@ -4,6 +4,7 @@ config VIDEO_INTEL_CVS tristate "Intel CVS CSI-2 bridge support" depends on I2C && ACPI && VIDEO_DEV depends on IPU_BRIDGE || !IPU_BRIDGE + default VIDEO_INTEL_IPU6 select MEDIA_CONTROLLER select VIDEO_V4L2_SUBDEV_API select V4L2_FWNODE diff --git a/drivers/media/i2c/cvs/core.c b/drivers/media/i2c/cvs/core.c index d4a3b9c3bab1..d9c51e1c0fb5 100644 --- a/drivers/media/i2c/cvs/core.c +++ b/drivers/media/i2c/cvs/core.c @@ -21,7 +21,6 @@ #include #include -#include #include "icvs.h" @@ -70,6 +69,13 @@ static const struct icvs_device_quirk cvs_quirk_table[] = { ICVS_NO_CAPS | ICVS_NO_FW_UPDATE }, /* Lattice NX33 */ + { 0x2ac1, 0x20d1, ICVS_NO_MIPI_CONFIG | + ICVS_NO_CAPS | + ICVS_NO_FW_UPDATE + }, /* + * The Lattice device was found on a Dell laptop + * XPS 14 (Dell 14 Premium) DA14250 + */ { 0x06CB, 0x0701, ICVS_SKIP_FW_RESET | ICVS_HOST_SENSOR_PWR_CTRL | ICVS_HOST_PRIV_CTRL | @@ -656,14 +662,12 @@ static int cvs_configure_dev_caps(struct icvs *ctx) */ static int cvs_core_probe(struct device *dev, struct i2c_client *i2c) { - struct pci_dev *ipu = NULL; + struct pci_dev *ipu; struct icvs *ctx; int ret; /* Locate IPU device */ - for (unsigned int i = 0; !ipu && ipu6_pci_tbl[i].vendor; i++) - ipu = pci_get_device(ipu6_pci_tbl[i].vendor, - ipu6_pci_tbl[i].device, NULL); + ipu = ipu_bridge_get_ipu6(); for (unsigned int i = 0; !ipu && icvs_ipu7_tbl[i].vendor; i++) ipu = pci_get_device(icvs_ipu7_tbl[i].vendor, icvs_ipu7_tbl[i].device, NULL); @@ -725,8 +729,6 @@ static int cvs_core_probe(struct device *dev, struct i2c_client *i2c) } if (ctx->res == ICVS_FULLCAP) { - struct gpio_desc *wake; - ctx->rst = devm_gpiod_get(dev, "rst", GPIOD_OUT_HIGH); if (IS_ERR(ctx->rst)) { ret = dev_err_probe(dev, PTR_ERR(ctx->rst), @@ -734,14 +736,12 @@ static int cvs_core_probe(struct device *dev, struct i2c_client *i2c) goto err_put_ipu; } - wake = devm_gpiod_get(dev, "wake", GPIOD_IN); - if (IS_ERR(wake)) { - ret = dev_err_probe(dev, PTR_ERR(wake), - "failed to get wake GPIO\n"); - goto err_put_ipu; - } - - ctx->irq = gpiod_to_irq(wake); + /* + * Do not request the line: another device's _CRS may list + * the same pin, and its driver would then fail with -EBUSY. + */ + ctx->irq = acpi_dev_gpio_irq_get_by(ACPI_COMPANION(dev), + "wake", 0); if (ctx->irq < 0) { ret = dev_err_probe(dev, ctx->irq, "failed to get wake IRQ\n"); diff --git a/drivers/media/i2c/cvs/v4l2.c b/drivers/media/i2c/cvs/v4l2.c index 9fadca7a3bee..776fe9460168 100644 --- a/drivers/media/i2c/cvs/v4l2.c +++ b/drivers/media/i2c/cvs/v4l2.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -71,19 +72,6 @@ static int csi_set_link_cfg(struct icvs *ctx, u64 link_freq) * Streaming */ -/** - * cvs_csi_enable_streams - Start streaming through the bridge - * @sd: Sub-device pointer - * @state: Active state - * @pad: Pad identifier (must be ICVS_CSI_PAD_SOURCE) - * @streams_mask: Streams to enable (bit 0 supported) - * - * Runtime-resumes the bridge (triggering cvs_runtime_resume() to claim CSI-2 - * link ownership), fetches the link frequency, programs the MIPI configuration, - * and forwards the enable request downstream. - * - * Return: 0 on success or negative errno. - */ static int cvs_csi_enable_streams(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, u32 pad, u64 streams_mask) @@ -129,19 +117,6 @@ static int cvs_csi_enable_streams(struct v4l2_subdev *sd, return ret; } -/** - * cvs_csi_disable_streams - Stop streaming through the bridge - * @sd: Sub-device pointer - * @state: Active state - * @pad: Pad identifier (must be ICVS_CSI_PAD_SOURCE) - * @streams_mask: Streams to disable (bit 0 supported) - * - * Disables the remote sensor stream then drops the PM reference acquired - * during enable. After the autosuspend delay, cvs_runtime_suspend() will - * return CSI-2 link ownership to CVS firmware. - * - * Return: 0 on success or negative errno. - */ static int cvs_csi_disable_streams(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, u32 pad, u64 streams_mask) @@ -167,15 +142,6 @@ static int cvs_csi_disable_streams(struct v4l2_subdev *sd, /* * Pad operations / formats */ -/** - * cvs_csi_init_state - Initialize pad formats in subdev state - * @sd: Sub-device - * @state: State container - * - * Sets all pad formats to a minimal 1x1 default. - * - * Return: 0. - */ static int cvs_csi_init_state(struct v4l2_subdev *sd, struct v4l2_subdev_state *state) { @@ -186,18 +152,8 @@ static int cvs_csi_init_state(struct v4l2_subdev *sd, return 0; } -/** - * cvs_csi_set_fmt - Negotiate pad format - * @sd: Sub-device - * @state: State - * @format: Desired / returned format - * - * Mirrors sink format onto source pad. Accepts many media bus codes, falling - * back to Y8 if unsupported. Normalizes field setting. - * - * Return: 0. - */ static int cvs_csi_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { @@ -319,17 +275,6 @@ static int cvs_csi_set_fmt(struct v4l2_subdev *sd, return 0; } -/** - * cvs_csi_get_mbus_config - Provide current CSI-2 bus configuration - * @sd: Sub-device - * @pad: Pad index - * @cfg: Returned bus config - * - * Fills lane ordering and number of lanes; retrieves link frequency from - * remote entity. - * - * Return: 0 on success or negative errno. - */ static int cvs_csi_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, struct v4l2_mbus_config *cfg) { @@ -384,23 +329,12 @@ static const struct media_entity_operations cvs_csi_entity_ops = { /* * Async notifier */ -/** - * cvs_csi_notify_bound - Remote sensor bound callback - * @notifier: Async notifier - * @sd: Remote subdev - * @asc: Async match connection - * - * Locates the source pad of the remote sensor and creates a media link to - * the CVS bridge sink pad enabling it by default. - * - * Return: 0 on success or negative errno. - */ static int cvs_csi_notify_bound(struct v4l2_async_notifier *notifier, struct v4l2_subdev *sd, struct v4l2_async_connection *asc) { struct icvs *ctx = notifier_to_csi(notifier); - int pad; + int pad, ret; pad = media_entity_get_fwnode_pad(&sd->entity, asc->match.fwnode, MEDIA_PAD_FL_SOURCE); @@ -409,17 +343,15 @@ static int cvs_csi_notify_bound(struct v4l2_async_notifier *notifier, ctx->remote = &sd->entity.pads[pad]; - return media_create_pad_link(&sd->entity, pad, &ctx->subdev.entity, - ICVS_CSI_PAD_SINK, MEDIA_LNK_FL_ENABLED | - MEDIA_LNK_FL_IMMUTABLE); + ret = media_create_pad_link(&sd->entity, pad, &ctx->subdev.entity, + ICVS_CSI_PAD_SINK, MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) + return ret; + + return v4l2_device_register_subdev_nodes(sd->v4l2_dev); } -/** - * cvs_csi_notify_unbind - Remote sensor unbind callback - * @notifier: Notifier - * @sd: Remote subdev - * @asc: Connection - */ static void cvs_csi_notify_unbind(struct v4l2_async_notifier *notifier, struct v4l2_subdev *sd, struct v4l2_async_connection *asc) @@ -437,14 +369,6 @@ static const struct v4l2_async_notifier_operations cvs_csi_notify_ops = { /* * Controls */ -/** - * cvs_csi_init_controls - Initialize V4L2 controls - * @ctx: CVS context - * - * Currently sets up a read-only privacy control placeholder. - * - * Return: 0 on success or negative errno. - */ static int cvs_csi_init_controls(struct icvs *ctx) { struct v4l2_ctrl *privacy_ctrl; diff --git a/drivers/media/i2c/cx25840/cx25840-core.c b/drivers/media/i2c/cx25840/cx25840-core.c index 8b7dd43ed208..e90d71fe9959 100644 --- a/drivers/media/i2c/cx25840/cx25840-core.c +++ b/drivers/media/i2c/cx25840/cx25840-core.c @@ -1771,6 +1771,7 @@ static int cx25840_s_ctrl(struct v4l2_ctrl *ctrl) /* ----------------------------------------------------------------------- */ static int cx25840_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -2289,29 +2290,27 @@ static int cx25840_init(struct v4l2_subdev *sd, u32 val) { struct cx25840_state *state = to_state(sd); + if (!is_cx2584x(state)) + return -EOPNOTSUPP; + state->generic_mode = true; - if (is_cx2584x(state)) { - /* set datasheet video output defaults */ - state->vid_config = CX25840_VCONFIG_FMT_BT656 | - CX25840_VCONFIG_RES_8BIT | - CX25840_VCONFIG_VBIRAW_DISABLED | - CX25840_VCONFIG_ANCDATA_ENABLED | - CX25840_VCONFIG_TASKBIT_ONE | - CX25840_VCONFIG_ACTIVE_HORIZONTAL | - CX25840_VCONFIG_VALID_NORMAL | - CX25840_VCONFIG_HRESETW_NORMAL | - CX25840_VCONFIG_CLKGATE_NONE | - CX25840_VCONFIG_DCMODE_DWORDS | - CX25840_VCONFIG_IDID0S_NORMAL | - CX25840_VCONFIG_VIPCLAMP_DISABLED; + /* set datasheet video output defaults */ + state->vid_config = CX25840_VCONFIG_FMT_BT656 | + CX25840_VCONFIG_RES_8BIT | + CX25840_VCONFIG_VBIRAW_DISABLED | + CX25840_VCONFIG_ANCDATA_ENABLED | + CX25840_VCONFIG_TASKBIT_ONE | + CX25840_VCONFIG_ACTIVE_HORIZONTAL | + CX25840_VCONFIG_VALID_NORMAL | + CX25840_VCONFIG_HRESETW_NORMAL | + CX25840_VCONFIG_CLKGATE_NONE | + CX25840_VCONFIG_DCMODE_DWORDS | + CX25840_VCONFIG_IDID0S_NORMAL | + CX25840_VCONFIG_VIPCLAMP_DISABLED; - /* add additional settings */ - cx25840_vconfig_add(state, val); - } else { - /* TODO: generic mode needs to be developed for other chips */ - WARN_ON(1); - } + /* add additional settings */ + cx25840_vconfig_add(state, val); return 0; } diff --git a/drivers/media/i2c/ds90ub913.c b/drivers/media/i2c/ds90ub913.c index 6abb5e324ae1..ca0b07d7109b 100644 --- a/drivers/media/i2c/ds90ub913.c +++ b/drivers/media/i2c/ds90ub913.c @@ -373,6 +373,7 @@ static int ub913_set_routing(struct v4l2_subdev *sd, } static int ub913_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/ds90ub953.c b/drivers/media/i2c/ds90ub953.c index d4228e1134ff..e6ae9cadefa2 100644 --- a/drivers/media/i2c/ds90ub953.c +++ b/drivers/media/i2c/ds90ub953.c @@ -426,6 +426,7 @@ static int ub953_set_routing(struct v4l2_subdev *sd, static int ub953_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/ds90ub960.c b/drivers/media/i2c/ds90ub960.c index 15a9797b47ac..7d7ba6d349fc 100644 --- a/drivers/media/i2c/ds90ub960.c +++ b/drivers/media/i2c/ds90ub960.c @@ -4045,6 +4045,7 @@ static int ub960_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, } static int ub960_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/et8ek8/et8ek8_driver.c b/drivers/media/i2c/et8ek8/et8ek8_driver.c index 738e2801016a..e8a7b7d87bb1 100644 --- a/drivers/media/i2c/et8ek8/et8ek8_driver.c +++ b/drivers/media/i2c/et8ek8/et8ek8_driver.c @@ -1013,6 +1013,7 @@ static int et8ek8_get_pad_format(struct v4l2_subdev *subdev, } static int et8ek8_set_pad_format(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/gc0308.c b/drivers/media/i2c/gc0308.c index 15900d5414cf..893dfe2e1c3e 100644 --- a/drivers/media/i2c/gc0308.c +++ b/drivers/media/i2c/gc0308.c @@ -1043,6 +1043,7 @@ static void gc0308_update_pad_format(const struct gc0308_frame_size *mode, } static int gc0308_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/gc0310.c b/drivers/media/i2c/gc0310.c index 754e82ad50ae..5e941291dbf7 100644 --- a/drivers/media/i2c/gc0310.c +++ b/drivers/media/i2c/gc0310.c @@ -365,6 +365,7 @@ static void gc0310_fill_format(struct v4l2_mbus_framefmt *fmt) } static int gc0310_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -537,7 +538,6 @@ static const struct v4l2_subdev_pad_ops gc0310_pad_ops = { .enum_mbus_code = gc0310_enum_mbus_code, .enum_frame_size = gc0310_enum_frame_size, .get_fmt = v4l2_subdev_get_fmt, - .set_fmt = v4l2_subdev_get_fmt, /* Only 1 fixed mode supported */ .get_selection = gc0310_get_selection, .set_selection = gc0310_get_selection, .enable_streams = gc0310_enable_streams, diff --git a/drivers/media/i2c/gc05a2.c b/drivers/media/i2c/gc05a2.c index 7cf7cde1f936..5b95d8881649 100644 --- a/drivers/media/i2c/gc05a2.c +++ b/drivers/media/i2c/gc05a2.c @@ -730,6 +730,7 @@ static void gc05a2_update_pad_format(struct gc05a2 *gc08a3, } static int gc05a2_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -761,6 +762,7 @@ static int gc05a2_set_format(struct v4l2_subdev *sd, } static int gc05a2_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -795,7 +797,7 @@ static int gc05a2_init_state(struct v4l2_subdev *sd, }, }; - gc05a2_set_format(sd, state, &fmt); + gc05a2_set_format(sd, NULL, state, &fmt); return 0; } diff --git a/drivers/media/i2c/gc08a3.c b/drivers/media/i2c/gc08a3.c index 4144aad8f2da..c1ae05ed1f2a 100644 --- a/drivers/media/i2c/gc08a3.c +++ b/drivers/media/i2c/gc08a3.c @@ -705,6 +705,7 @@ static void gc08a3_update_pad_format(struct gc08a3 *gc08a3, } static int gc08a3_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -737,6 +738,7 @@ static int gc08a3_set_format(struct v4l2_subdev *sd, } static int gc08a3_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -771,7 +773,7 @@ static int gc08a3_init_state(struct v4l2_subdev *sd, }, }; - gc08a3_set_format(sd, state, &fmt); + gc08a3_set_format(sd, NULL, state, &fmt); return 0; } diff --git a/drivers/media/i2c/gc2145.c b/drivers/media/i2c/gc2145.c index b215963a2648..661a59641f19 100644 --- a/drivers/media/i2c/gc2145.c +++ b/drivers/media/i2c/gc2145.c @@ -709,6 +709,7 @@ static int gc2145_init_state(struct v4l2_subdev *sd, } static int gc2145_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -774,6 +775,7 @@ static int gc2145_enum_frame_size(struct v4l2_subdev *sd, } static int gc2145_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/hi556.c b/drivers/media/i2c/hi556.c index de573cee4451..4c13adf4a900 100644 --- a/drivers/media/i2c/hi556.c +++ b/drivers/media/i2c/hi556.c @@ -960,6 +960,7 @@ __hi556_get_pad_crop(struct hi556 *hi556, } static int hi556_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1071,6 +1072,7 @@ static int hi556_set_stream(struct v4l2_subdev *sd, int enable) } static int hi556_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/hi846.c b/drivers/media/i2c/hi846.c index a3f77b8434ca..025f8bcd17bc 100644 --- a/drivers/media/i2c/hi846.c +++ b/drivers/media/i2c/hi846.c @@ -1688,6 +1688,7 @@ static int __maybe_unused hi846_resume(struct device *dev) } static int hi846_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -1840,6 +1841,7 @@ static int hi846_enum_frame_size(struct v4l2_subdev *sd, } static int hi846_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/hi847.c b/drivers/media/i2c/hi847.c index def01aa07b2f..41b8cac6a5e3 100644 --- a/drivers/media/i2c/hi847.c +++ b/drivers/media/i2c/hi847.c @@ -2187,7 +2187,7 @@ struct hi847 { /* Current mode */ const struct hi847_mode *cur_mode; - /* To serialize asynchronus callbacks */ + /* To serialize asynchronous callbacks */ struct mutex mutex; }; @@ -2639,6 +2639,7 @@ static int hi847_set_stream(struct v4l2_subdev *sd, int enable) } static int hi847_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/hm1246.c b/drivers/media/i2c/hm1246.c new file mode 100644 index 000000000000..be1c3d75657e --- /dev/null +++ b/drivers/media/i2c/hm1246.c @@ -0,0 +1,1287 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Himax HM1246 image sensor + * + * Copyright 2026 Matthias Fend + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Status registers */ +#define HM1246_MODEL_ID_REG CCI_REG16(0x0000) + +/* General setup registers */ +#define HM1246_MODE_SELECT_REG CCI_REG8(0x0100) +#define HM1246_MODE_SELECT_STANDBY 0x00 +#define HM1246_MODE_SELECT_STREAM 0x01 +#define HM1246_MODE_SELECT_STOP 0x02 +#define HM1246_IMAGE_ORIENTATION_REG CCI_REG8(0x0101) +#define HM1246_IMAGE_ORIENTATION_VFLIP BIT(1) +#define HM1246_IMAGE_ORIENTATION_HFLIP BIT(0) +#define HM1246_CMU_UPDATE_REG CCI_REG8(0x0104) + +/* Output setup registers */ +#define HM1246_COARSE_INTG_REG CCI_REG16(0x0202) +#define HM1246_ANALOG_GLOBAL_GAIN_REG CCI_REG8(0x0205) + +/* Clock setup registers */ +#define HM1246_PLL1CFG_REG CCI_REG8(0x0303) +#define HM1246_PLL1CFG_MULTIPLIER(x) (((x) & 0xff) << 0) +#define HM1246_PLL2CFG_REG CCI_REG8(0x0305) +#define HM1246_PLL2CFG_PRE_DIV(x) (((x) & 0x1f) << 1) +#define HM1246_PLL2CFG_MULTIPLIER(x) (((x) & 0x01) << 0) +#define HM1246_PLL3CFG_REG CCI_REG8(0x0307) +#define HM1246_PLL3CFG_POST_DIV(x) (((x) & 0x3) << 6) +#define HM1246_PLL3CFG_SYSCLK_DIV(x) (((x) & 0x3) << 4) +#define HM1246_PLL3CFG_PCLK_DIV(x) (((x) & 0x7) << 0) + +/* Frame timing registers */ +#define HM1246_FRAME_LENGTH_LINES_REG CCI_REG16(0x0340) +#define HM1246_LINE_LENGTH_PCK_REG CCI_REG16(0x0342) + +/* Image size registers */ +#define HM1246_X_ADDR_START_REG CCI_REG16(0x0344) +#define HM1246_Y_ADDR_START_REG CCI_REG16(0x0346) +#define HM1246_X_ADDR_END_REG CCI_REG16(0x0348) +#define HM1246_Y_ADDR_END_REG CCI_REG16(0x034a) +#define HM1246_X_LA_START_REG CCI_REG16(0x0351) +#define HM1246_X_LA_END_REG CCI_REG16(0x0353) +#define HM1246_Y_LA_START_REG CCI_REG16(0x0355) +#define HM1246_Y_LA_END_REG CCI_REG16(0x0357) + +/* Test pattern registers */ +#define HM1246_TEST_PATTERN_MODE_REG CCI_REG8(0x0601) +#define HM1246_TEST_PATTERN_MODE_MODE(x) (((x) & 0xf) << 4) +#define HM1246_TEST_PATTERN_MODE_ENABLE BIT(0) +#define HM1246_TEST_DATA_BLUE_REG CCI_REG16(0x0602) +#define HM1246_TEST_DATA_GB_REG CCI_REG16(0x0604) +#define HM1246_TEST_DATA_RED_REG CCI_REG16(0x0606) +#define HM1246_TEST_DATA_GR_REG CCI_REG16(0x0608) + +/* SBC registers */ +#define HM1246_SBC_BOOT_REF2_REG CCI_REG8(0x2001) +#define HM1246_SBC_BOOT_REF2_PLL_LOCK BIT(4) +#define HM1246_SBC_CTRL_REG CCI_REG8(0x2003) +#define HM1246_SBC_CTRL_PLL_EN BIT(0) + +/* System registers */ +#define HM1246_OUTPUT_PRT_CTRL_REG CCI_REG8(0x2f02) +#define HM1246_POLARITY_CTRL_REG CCI_REG8(0x2f20) +#define HM1246_POLARITY_CTRL_HSYNC BIT(7) +#define HM1246_POLARITY_CTRL_VSYNC BIT(6) +#define HM1246_PCLK_CTRL_REG CCI_REG8(0x2f24) +#define HM1246_PCLK_CTRL_POL BIT(3) + +/* Digital window control & parameter registers */ +#define HM1246_DWIN_XOFFSET_REG CCI_REG16(0xd5e4) +#define HM1246_DWIN_XSIZE_REG CCI_REG16(0xd5e6) +#define HM1246_DWIN_YOFFSET_REG CCI_REG16(0xd5e8) +#define HM1246_DWIN_YSIZE_REG CCI_REG16(0xd5ea) + +#define HM1246_MODEL_ID 0x1245 + +#define HM1246_NATIVE_WIDTH 1296 +#define HM1246_NATIVE_HEIGHT 976 + +#define HM1246_VTS_MAX 65535 + +#define HM1246_COARSE_INTG_MARGIN 2 +#define HM1246_COARSE_INTG_MIN 4 +#define HM1246_COARSE_INTG_STEP 1 + +#define HM1246_ANALOG_GLOBAL_GAIN_MIN 0x00 +#define HM1246_ANALOG_GLOBAL_GAIN_MAX 0xe8 +#define HM1246_ANALOG_GLOBAL_GAIN_STEP 0x01 + +#define HM1246_XCLK_MIN (6 * HZ_PER_MHZ) +#define HM1246_XCLK_MAX (27 * HZ_PER_MHZ) + +#define HM1246_PCLK_MIN (8 * HZ_PER_MHZ) +#define HM1246_PCLK_MAX (96 * HZ_PER_MHZ) + +#define HM1246_PLL_VCO_MIN (360 * HZ_PER_MHZ) +#define HM1246_PLL_VCO_MAX (680 * HZ_PER_MHZ) + +#define HM1246_PLL_INCLK_MIN (1000 * HZ_PER_KHZ) +#define HM1246_PLL_INCLK_MAX (2500 * HZ_PER_KHZ) + +#define HM1246_PLL_MULTI_L_MIN 1 +#define HM1246_PLL_MULTI_L_MAX 256 + +#define HM1246_PLL_MULTI_H_MIN 2 +#define HM1246_PLL_MULTI_H_MAX 3 + +#define HM1246_PLL_MULTI_MIN \ + (HM1246_PLL_MULTI_H_MIN * HM1246_PLL_MULTI_L_MIN) +#define HM1246_PLL_MULTI_MAX \ + (HM1246_PLL_MULTI_H_MAX * HM1246_PLL_MULTI_L_MAX) + +static const char *const hm1246_test_pattern_menu[] = { + "Disabled", + "Checkerboard", + "Ramp", + "Moving ones", + "Blending color bars", + "Color bars", + "Solid white", + "Solid black", + "Solid red", + "Solid green", + "Solid blue", +}; + +static const char *const hm1246_supply_names[] = { + "avdd", + "iovdd", + "dvdd", +}; + +struct hm1246 { + struct v4l2_subdev sd; + struct media_pad pad; + struct device *dev; + + struct regulator_bulk_data supplies[ARRAY_SIZE(hm1246_supply_names)]; + struct clk *xclk; + unsigned long xclk_freq; + struct reset_control *reset; + unsigned int mbus_flags; + s64 link_frequency; + + struct v4l2_ctrl_handler ctrls; + struct v4l2_ctrl *exposure_ctrl; + struct v4l2_ctrl *hflip_ctrl; + struct v4l2_ctrl *vflip_ctrl; + + struct regmap *regmap; + + bool identified; +}; + +static const struct cci_reg_sequence mode_1296x976_raw[] = { + { HM1246_X_LA_START_REG, 60 }, + { HM1246_X_LA_END_REG, 1355 }, + { HM1246_Y_LA_START_REG, 0 }, + { HM1246_Y_LA_END_REG, 975 }, + { HM1246_OUTPUT_PRT_CTRL_REG, 0x20 }, + { CCI_REG8(0x300a), 0x01 }, + { CCI_REG8(0x300b), 0x00 }, + { CCI_REG8(0x50f5), 0x01 }, + { CCI_REG8(0x50dd), 0x00 }, + { CCI_REG8(0x50a1), 0x02 }, + { CCI_REG8(0x50aa), 0x1c }, + { CCI_REG8(0x50ac), 0xdd }, + { CCI_REG8(0x50ad), 0x08 }, + { CCI_REG8(0x50ab), 0x04 }, + { CCI_REG8(0x50a0), 0x40 }, + { CCI_REG8(0x50a2), 0x12 }, + { CCI_REG8(0x50ae), 0x30 }, + { CCI_REG8(0x50b3), 0x04 }, + { CCI_REG8(0x5204), 0x40 }, + { CCI_REG8(0x5208), 0x55 }, + { CCI_REG8(0x520b), 0x05 }, + { CCI_REG8(0x520d), 0x40 }, + { CCI_REG8(0x5214), 0x18 }, + { CCI_REG8(0x5215), 0x0f }, + { CCI_REG8(0x5217), 0x01 }, + { CCI_REG8(0x5218), 0x07 }, + { CCI_REG8(0x5219), 0x01 }, + { CCI_REG8(0x521a), 0x50 }, + { CCI_REG8(0x521b), 0x24 }, + { CCI_REG8(0x5232), 0x01 }, + { CCI_REG8(0x5220), 0x11 }, + { CCI_REG8(0x5227), 0x01 }, + { CCI_REG8(0x5106), 0xc1 }, + { CCI_REG8(0x5115), 0xc0 }, + { CCI_REG8(0x5116), 0xc1 }, + { CCI_REG8(0x5138), 0x40 }, + { CCI_REG8(0x5139), 0x60 }, + { CCI_REG8(0x513a), 0x80 }, + { CCI_REG8(0x513b), 0xa0 }, + { CCI_REG8(0x513c), 0xa1 }, + { CCI_REG8(0x513d), 0xa2 }, + { CCI_REG8(0x513e), 0xa3 }, + { CCI_REG8(0x5140), 0x40 }, + { CCI_REG8(0x5141), 0x60 }, + { CCI_REG8(0x5142), 0x80 }, + { CCI_REG8(0x5143), 0x81 }, + { CCI_REG8(0x5144), 0x82 }, + { CCI_REG8(0x5145), 0x83 }, + { CCI_REG8(0x5146), 0x93 }, + { CCI_REG8(0x51c1), 0xc3 }, + { CCI_REG8(0x51c5), 0xc3 }, + { CCI_REG8(0x51c9), 0xc3 }, + { CCI_REG8(0x51cd), 0xc2 }, + { CCI_REG8(0x51d1), 0xc1 }, + { CCI_REG8(0x51d5), 0xc1 }, + { CCI_REG8(0x51d9), 0x81 }, + { CCI_REG8(0x51dd), 0x81 }, + { CCI_REG8(0x51c2), 0x49 }, + { CCI_REG8(0x51c6), 0x49 }, + { CCI_REG8(0x51ca), 0x49 }, + { CCI_REG8(0x51ce), 0x49 }, + { CCI_REG8(0x51d2), 0x49 }, + { CCI_REG8(0x51d6), 0x59 }, + { CCI_REG8(0x51da), 0x59 }, + { CCI_REG8(0x51de), 0x59 }, + { CCI_REG8(0x51c3), 0x20 }, + { CCI_REG8(0x51c7), 0x38 }, + { CCI_REG8(0x51cb), 0x21 }, + { CCI_REG8(0x51cf), 0x11 }, + { CCI_REG8(0x51d3), 0x11 }, + { CCI_REG8(0x51d7), 0x13 }, + { CCI_REG8(0x51db), 0x13 }, + { CCI_REG8(0x51df), 0x13 }, + { CCI_REG8(0x51e0), 0x03 }, + { CCI_REG8(0x51e2), 0x03 }, + { CCI_REG8(0x51f0), 0x42 }, + { CCI_REG8(0x51f1), 0x40 }, + { CCI_REG8(0x51f2), 0x4a }, + { CCI_REG8(0x51f3), 0x48 }, + { CCI_REG8(0x5015), 0x73 }, + { CCI_REG8(0x504a), 0x04 }, + { CCI_REG8(0x5044), 0x07 }, + { CCI_REG8(0x5040), 0x03 }, + { CCI_REG8(0x5135), 0xc4 }, + { CCI_REG8(0x5136), 0xc5 }, + { CCI_REG8(0x5166), 0xc4 }, + { CCI_REG8(0x5196), 0xc4 }, + { CCI_REG8(0x51c0), 0x10 }, + { CCI_REG8(0x51c4), 0x10 }, + { CCI_REG8(0x51c8), 0xa0 }, + { CCI_REG8(0x51cc), 0xa0 }, + { CCI_REG8(0x51d0), 0xa1 }, + { CCI_REG8(0x51d4), 0xa5 }, + { CCI_REG8(0x51d8), 0xa5 }, + { CCI_REG8(0x51dc), 0xa5 }, + { CCI_REG8(0x5200), 0xe4 }, + { CCI_REG8(0x5209), 0x04 }, + { CCI_REG8(0x301b), 0x01 }, + { CCI_REG8(0x3130), 0x01 }, + { CCI_REG8(0x5013), 0x07 }, + { CCI_REG8(0x5016), 0x01 }, + { CCI_REG8(0x501d), 0x50 }, + { CCI_REG8(0x0350), 0xfe }, + { CCI_REG8(0x2f03), 0x15 }, + { CCI_REG8(0xd380), 0x00 }, + { CCI_REG8(0x3047), 0x7f }, + { CCI_REG8(0x304d), 0x34 }, + { CCI_REG8(0x3041), 0x4b }, + { CCI_REG8(0x3042), 0x2d }, + { CCI_REG8(0x3056), 0x64 }, + { CCI_REG8(0x3059), 0x1e }, + { CCI_REG8(0x305e), 0x10 }, + { CCI_REG8(0x305f), 0x10 }, + { CCI_REG8(0x306d), 0x10 }, + { CCI_REG8(0x306e), 0x0c }, + { CCI_REG8(0x3064), 0x50 }, + { CCI_REG8(0x3067), 0x78 }, + { CCI_REG8(0x3068), 0x4b }, + { CCI_REG8(0x306a), 0x78 }, + { CCI_REG8(0x306b), 0x4b }, + { CCI_REG8(0xd442), 0x3d }, + { CCI_REG8(0xd443), 0x06 }, + { CCI_REG8(0xd440), 0x63 }, + { CCI_REG8(0xd446), 0xb0 }, + { CCI_REG8(0xd447), 0x60 }, + { CCI_REG8(0xd448), 0x48 }, + { CCI_REG8(0xd449), 0x30 }, + { CCI_REG8(0xd44a), 0x18 }, + { CCI_REG8(0xd360), 0x03 }, + { CCI_REG8(0x30ac), 0x10 }, + { CCI_REG8(0x30ad), 0x10 }, + { CCI_REG8(0x30ae), 0x10 }, + { CCI_REG8(0x3040), 0x0b }, + { CCI_REG8(0x2002), 0x00 }, + { CCI_REG8(0x2000), 0x08 }, +}; + +struct hm1246_reg_list { + u32 num_of_regs; + const struct cci_reg_sequence *regs; +}; + +struct hm1246_mode { + u32 codes[4]; + u32 clocks_per_pixel; + struct v4l2_rect rect; + u32 hts; + u32 vts_min; + const struct hm1246_reg_list reg_list; +}; + +#define FLIP_FORMAT_INDEX(v, h) ((v ? 2 : 0) | (h ? 1 : 0)) + +/* Get the format code of the mode considering current flip setting. */ +static u32 hm1246_get_format_code(struct hm1246 *hm1246, + const struct hm1246_mode *hm1246_mode) +{ + return hm1246_mode->codes[FLIP_FORMAT_INDEX(hm1246->vflip_ctrl->val, + hm1246->hflip_ctrl->val)]; +} + +static const struct hm1246_mode hm1246_modes[] = { + { + .codes = { + [FLIP_FORMAT_INDEX(0, 0)] = MEDIA_BUS_FMT_SBGGR10_1X10, + [FLIP_FORMAT_INDEX(0, 1)] = MEDIA_BUS_FMT_SGBRG10_1X10, + [FLIP_FORMAT_INDEX(1, 0)] = MEDIA_BUS_FMT_SGRBG10_1X10, + [FLIP_FORMAT_INDEX(1, 1)] = MEDIA_BUS_FMT_SRGGB10_1X10, + }, + .clocks_per_pixel = 1, + .rect.top = 0, + .rect.left = 0, + .rect.width = 1296, + .rect.height = 976, + .hts = 1420, + .vts_min = 990, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1296x976_raw), + .regs = mode_1296x976_raw, + }, + }, +}; + +static inline struct hm1246 *to_hm1246(struct v4l2_subdev *sd) +{ + return container_of_const(sd, struct hm1246, sd); +} + +static const struct hm1246_mode * +hm1246_find_mode_by_mbus_code(struct hm1246 *hm1246, u32 code) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(hm1246_modes); i++) { + if (code == hm1246_get_format_code(hm1246, &hm1246_modes[i])) + return &hm1246_modes[i]; + } + + return NULL; +} + +static int hm1246_power_on(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct hm1246 *hm1246 = to_hm1246(sd); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(hm1246_supply_names), + hm1246->supplies); + if (ret) { + dev_err(hm1246->dev, "failed to enable regulators\n"); + return ret; + } + + ret = clk_prepare_enable(hm1246->xclk); + if (ret) { + regulator_bulk_disable(ARRAY_SIZE(hm1246_supply_names), + hm1246->supplies); + dev_err(hm1246->dev, "failed to enable clock\n"); + return ret; + } + + reset_control_deassert(hm1246->reset); + + /* + * XSHUTDOWN to crystal clock oscillation (tcrystal): 650us (typical) + * Sample bootstrap pin (tsample): 2000us (maximum) + * Built in self test (tbist): 3000us (maximum) + */ + fsleep(6 * USEC_PER_MSEC); + + return 0; +} + +static int hm1246_power_off(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct hm1246 *hm1246 = to_hm1246(sd); + + reset_control_assert(hm1246->reset); + + clk_disable_unprepare(hm1246->xclk); + + regulator_bulk_disable(ARRAY_SIZE(hm1246_supply_names), + hm1246->supplies); + + return 0; +} + +static int hm1246_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct hm1246 *hm1246 = to_hm1246(sd); + + if (code->index >= ARRAY_SIZE(hm1246_modes)) + return -EINVAL; + + code->code = hm1246_get_format_code(hm1246, &hm1246_modes[code->index]); + + return 0; +} + +static int hm1246_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct hm1246 *hm1246 = to_hm1246(subdev); + const struct hm1246_mode *mode; + + if (fse->index > 0) + return -EINVAL; + + mode = hm1246_find_mode_by_mbus_code(hm1246, fse->code); + if (!mode) + return -EINVAL; + + fse->min_width = mode->rect.width; + fse->max_width = mode->rect.width; + fse->min_height = mode->rect.height; + fse->max_height = mode->rect.height; + + return 0; +} + +static void hm1246_update_pad_format(struct hm1246 *hm1246, + const struct hm1246_mode *hm1246_mode, + struct v4l2_mbus_framefmt *fmt) +{ + fmt->width = hm1246_mode->rect.width; + fmt->height = hm1246_mode->rect.height; + fmt->code = hm1246_get_format_code(hm1246, hm1246_mode); + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_RAW; + fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); + fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; + fmt->xfer_func = V4L2_XFER_FUNC_NONE; +} + +static int hm1246_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + struct hm1246 *hm1246 = to_hm1246(sd); + struct v4l2_mbus_framefmt *mbus_fmt; + struct v4l2_rect *crop; + const struct hm1246_mode *mode; + + mode = hm1246_find_mode_by_mbus_code(hm1246, fmt->format.code); + if (!mode) + mode = &hm1246_modes[0]; + + crop = v4l2_subdev_state_get_crop(state, 0); + *crop = mode->rect; + + hm1246_update_pad_format(hm1246, mode, &fmt->format); + mbus_fmt = v4l2_subdev_state_get_format(state, 0); + *mbus_fmt = fmt->format; + + return 0; +} + +static int hm1246_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + const struct v4l2_mbus_framefmt *format; + const struct hm1246_mode *mode; + + format = v4l2_subdev_state_get_format(state, 0); + mode = v4l2_find_nearest_size(hm1246_modes, ARRAY_SIZE(hm1246_modes), + rect.width, rect.height, format->width, + format->height); + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + sel->r = *v4l2_subdev_state_get_crop(state, 0); + return 0; + + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = HM1246_NATIVE_WIDTH; + sel->r.height = HM1246_NATIVE_HEIGHT; + return 0; + + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r = mode->rect; + return 0; + + default: + return -EINVAL; + } +} + +static int hm1246_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct hm1246 *hm1246 = to_hm1246(sd); + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_TRY, + .pad = 0, + .format = { + .code = hm1246_get_format_code(hm1246, + &hm1246_modes[0]), + .width = hm1246_modes[0].rect.width, + .height = hm1246_modes[0].rect.height, + }, + }; + + hm1246_set_format(sd, NULL, state, &fmt); + + return 0; +} + +static int hm1246_calc_pll(u32 xclk, u32 link_freq, u32 clocks_per_pixel, + u8 *pll1, u8 *pll2, u8 *pll3) +{ + static const u8 pclk_div_table[] = { 4, 5, 6, 7, 8, 12, 14, 16 }; + static const u8 sysclk_div_table[] = { 1, 2, 3, 4 }; + static const u8 post_div_table[] = { 1, 2, 4, 8 }; + static const int sysclk_pclk_ratio = 3; /* Recommended value */ + u32 pclk, vco_out; + int pclk_div_index, sysclk_div_index, post_div_index; + bool sysclk_pclk_ratio_found = false; + + if (link_freq < HM1246_PCLK_MIN || link_freq > HM1246_PCLK_MAX) + return -EINVAL; + + /* + * In raw mode (1 pixel per clock) the pixel clock is internally + * divided by two. + */ + pclk = 2 * link_freq / clocks_per_pixel; + + /* Find suitable PCLK and SYSCLK dividers. */ + for (pclk_div_index = 0; pclk_div_index < ARRAY_SIZE(pclk_div_table); + pclk_div_index++) { + for (sysclk_div_index = 0; + sysclk_div_index < ARRAY_SIZE(sysclk_div_table); + sysclk_div_index++) { + if (sysclk_div_table[sysclk_div_index] * + sysclk_pclk_ratio == + pclk_div_table[pclk_div_index]) { + sysclk_pclk_ratio_found = true; + break; + } + } + if (sysclk_pclk_ratio_found) + break; + } + + if (!sysclk_pclk_ratio_found) + return -EINVAL; + + /* Determine an appropriate post divider. */ + for (post_div_index = 0; post_div_index < ARRAY_SIZE(post_div_table); + post_div_index++) { + vco_out = pclk * pclk_div_table[pclk_div_index] * + post_div_table[post_div_index]; + + if (vco_out >= HM1246_PLL_VCO_MIN && + vco_out <= HM1246_PLL_VCO_MAX) + break; + } + if (post_div_index >= ARRAY_SIZE(post_div_table)) + return -EINVAL; + + /* Find pre-divider and multiplier values. */ + for (u32 div = DIV_ROUND_UP(xclk, HM1246_PLL_INCLK_MAX); + div <= xclk / HM1246_PLL_INCLK_MIN; div++) { + u32 multi, multi_h, multi_l, vco; + + multi = DIV_ROUND_CLOSEST_ULL((u64)vco_out * div, xclk); + if (multi < HM1246_PLL_MULTI_MIN || + multi > HM1246_PLL_MULTI_MAX) + continue; + + multi_h = multi / (HM1246_PLL_MULTI_H_MIN * + HM1246_PLL_MULTI_L_MAX) + + 2; + multi_l = multi / multi_h; + vco = div_u64((u64)xclk * multi_h * multi_l, div); + + if (vco != vco_out) + continue; + + if (pll1 && pll2 && pll3) { + *pll1 = HM1246_PLL1CFG_MULTIPLIER(multi_l - 1); + *pll2 = HM1246_PLL2CFG_PRE_DIV(div - 1) | + HM1246_PLL2CFG_MULTIPLIER(multi_h - 2); + *pll3 = HM1246_PLL3CFG_POST_DIV(post_div_index) | + HM1246_PLL3CFG_SYSCLK_DIV(sysclk_div_index) | + HM1246_PLL3CFG_PCLK_DIV(pclk_div_index); + } + + return 0; + } + + return -EINVAL; +} + +static int hm1246_cci_write_pll(struct hm1246 *hm1246, u8 pll1, u8 pll2, + u8 pll3) +{ + const struct cci_reg_sequence pll_regs[] = { + { HM1246_PLL1CFG_REG, pll1 }, + { HM1246_PLL2CFG_REG, pll2 }, + { HM1246_PLL3CFG_REG, pll3 }, + { HM1246_SBC_CTRL_REG, HM1246_SBC_CTRL_PLL_EN }, + }; + + return cci_multi_reg_write(hm1246->regmap, pll_regs, + ARRAY_SIZE(pll_regs), NULL); +} + +static int hm1246_pll_check_locked(struct hm1246 *hm1246) +{ + u64 boot_ref2; + int ret; + + ret = cci_read(hm1246->regmap, HM1246_SBC_BOOT_REF2_REG, &boot_ref2, + NULL); + if (ret) + return ret; + + return (boot_ref2 & HM1246_SBC_BOOT_REF2_PLL_LOCK) ? 0 : -EIO; +} + +static int hm1246_setup_pll(struct hm1246 *hm1246, + const struct hm1246_mode *mode) +{ + u8 pll1, pll2, pll3; + int ret; + + ret = hm1246_calc_pll(hm1246->xclk_freq, hm1246->link_frequency, + mode->clocks_per_pixel, &pll1, &pll2, &pll3); + if (ret) + return ret; + + ret = hm1246_cci_write_pll(hm1246, pll1, pll2, pll3); + if (ret) + return ret; + + /* PLL lock time (tpll): 100us (typical) */ + fsleep(200); + + return hm1246_pll_check_locked(hm1246); +} + +static int hm1246_cci_write_test_pattern(struct hm1246 *hm1246, u8 mode, + u16 r, u16 g, u16 b) +{ + const struct cci_reg_sequence tpg_enable_regs[] = { + { HM1246_TEST_DATA_RED_REG, r }, + { HM1246_TEST_DATA_GR_REG, g }, + { HM1246_TEST_DATA_GB_REG, g }, + { HM1246_TEST_DATA_BLUE_REG, b }, + { HM1246_TEST_PATTERN_MODE_REG, mode }, + }; + + return cci_multi_reg_write(hm1246->regmap, tpg_enable_regs, + ARRAY_SIZE(tpg_enable_regs), NULL); +} + +static int hm1246_test_pattern(struct hm1246 *hm1246, u32 index) +{ + static const u16 RGBMIN = 0, RGBMAX = 0x3ff; + static const struct tp { + int pattern; + u16 r, g, b; + } tps[] = { + /* Disabled */ + [0] = { .pattern = 0, .r = RGBMIN, .g = RGBMIN, .b = RGBMIN }, + /* Checkboard pattern */ + [1] = { .pattern = 0, .r = RGBMIN, .g = RGBMIN, .b = RGBMIN }, + /* Ramp */ + [2] = { .pattern = 1, .r = RGBMIN, .g = RGBMIN, .b = RGBMIN }, + /* Moving ones */ + [3] = { .pattern = 2, .r = RGBMIN, .g = RGBMIN, .b = RGBMIN }, + /* Blending color bars */ + [4] = { .pattern = 3, .r = RGBMIN, .g = RGBMIN, .b = RGBMIN }, + /* Color bars */ + [5] = { .pattern = 4, .r = RGBMIN, .g = RGBMIN, .b = RGBMIN }, + /* Solid white */ + [6] = { .pattern = 15, .r = RGBMAX, .g = RGBMAX, .b = RGBMAX }, + /* Solid black */ + [7] = { .pattern = 15, .r = RGBMIN, .g = RGBMIN, .b = RGBMIN }, + /* Solid red */ + [8] = { .pattern = 15, .r = RGBMAX, .g = RGBMIN, .b = RGBMIN }, + /* Solid green */ + [9] = { .pattern = 15, .r = RGBMIN, .g = RGBMAX, .b = RGBMIN }, + /* Solid blue */ + [10] = { .pattern = 15, .r = RGBMIN, .g = RGBMIN, .b = RGBMAX }, + }; + u8 mode; + + if (index >= ARRAY_SIZE(tps)) + return -EINVAL; + + mode = HM1246_TEST_PATTERN_MODE_MODE(tps[index].pattern); + if (index) + mode |= HM1246_TEST_PATTERN_MODE_ENABLE; + + return hm1246_cci_write_test_pattern(hm1246, mode, tps[index].r, + tps[index].g, tps[index].b); +} + +static int hm1246_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct hm1246 *hm1246 = + container_of_const(ctrl->handler, struct hm1246, ctrls); + struct v4l2_subdev_state *state; + const struct v4l2_mbus_framefmt *format; + u32 val; + bool needs_cmu_update = true; + int ret; + + state = v4l2_subdev_get_locked_active_state(&hm1246->sd); + format = v4l2_subdev_state_get_format(state, 0); + + if (ctrl->id == V4L2_CID_VBLANK) { + s64 exposure_max; + + exposure_max = + format->height + ctrl->val - HM1246_COARSE_INTG_MARGIN; + ret = __v4l2_ctrl_modify_range(hm1246->exposure_ctrl, + hm1246->exposure_ctrl->minimum, + exposure_max, + hm1246->exposure_ctrl->step, + exposure_max); + + if (ret) { + dev_err(hm1246->dev, "exposure ctrl range update failed\n"); + return ret; + } + } + + if (!pm_runtime_get_if_active(hm1246->dev)) + return 0; + + ret = 0; + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + cci_write(hm1246->regmap, HM1246_COARSE_INTG_REG, ctrl->val, + &ret); + break; + + case V4L2_CID_ANALOGUE_GAIN: + cci_write(hm1246->regmap, HM1246_ANALOG_GLOBAL_GAIN_REG, + ctrl->val, &ret); + break; + + case V4L2_CID_VBLANK: + val = format->height + ctrl->val; + cci_write(hm1246->regmap, HM1246_FRAME_LENGTH_LINES_REG, val, + &ret); + break; + + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + val = 0; + if (hm1246->hflip_ctrl->val) + val |= HM1246_IMAGE_ORIENTATION_HFLIP; + if (hm1246->vflip_ctrl->val) + val |= HM1246_IMAGE_ORIENTATION_VFLIP; + + cci_write(hm1246->regmap, HM1246_IMAGE_ORIENTATION_REG, val, + &ret); + break; + + case V4L2_CID_TEST_PATTERN: + ret = hm1246_test_pattern(hm1246, ctrl->val); + needs_cmu_update = false; + break; + + default: + ret = -EINVAL; + needs_cmu_update = false; + break; + } + + if (needs_cmu_update) + cci_write(hm1246->regmap, HM1246_CMU_UPDATE_REG, 0, &ret); + + pm_runtime_put(hm1246->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops hm1246_ctrl_ops = { + .s_ctrl = hm1246_set_ctrl, +}; + +static int hm1246_identify_module(struct hm1246 *hm1246) +{ + u64 model_id; + int ret; + + if (hm1246->identified) + return 0; + + ret = cci_read(hm1246->regmap, HM1246_MODEL_ID_REG, &model_id, NULL); + if (ret) + return ret; + + if (model_id != HM1246_MODEL_ID) { + dev_err(hm1246->dev, "model id mismatch: 0x%llx!=0x%x\n", + model_id, HM1246_MODEL_ID); + return -ENXIO; + } + + hm1246->identified = true; + + return 0; +} + +static int hm1246_setup_moderegs(struct hm1246 *hm1246, + const struct hm1246_mode *mode) +{ + const struct hm1246_reg_list *reg_list = &mode->reg_list; + const struct cci_reg_sequence modeaw[] = { + { HM1246_X_ADDR_START_REG, mode->rect.left }, + { HM1246_Y_ADDR_START_REG, mode->rect.top }, + { HM1246_X_ADDR_END_REG, mode->rect.width - 1 }, + { HM1246_Y_ADDR_END_REG, mode->rect.height - 1 }, + { HM1246_DWIN_XOFFSET_REG, mode->rect.left }, + { HM1246_DWIN_YOFFSET_REG, mode->rect.top }, + { HM1246_DWIN_XSIZE_REG, mode->rect.width }, + { HM1246_DWIN_YSIZE_REG, mode->rect.height }, + { HM1246_LINE_LENGTH_PCK_REG, mode->hts }, + }; + int ret = 0; + + cci_multi_reg_write(hm1246->regmap, modeaw, ARRAY_SIZE(modeaw), &ret); + cci_multi_reg_write(hm1246->regmap, reg_list->regs, + reg_list->num_of_regs, &ret); + + return ret; +} + +static int hm1246_setup_bus(struct hm1246 *hm1246) +{ + u64 polarity_ctrl = 0, pclk_ctrl = 0; + int ret = 0; + + if (hm1246->mbus_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) + polarity_ctrl |= HM1246_POLARITY_CTRL_HSYNC; + + if (hm1246->mbus_flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) + polarity_ctrl |= HM1246_POLARITY_CTRL_VSYNC; + + cci_write(hm1246->regmap, HM1246_POLARITY_CTRL_REG, polarity_ctrl, + &ret); + + /* + * If the clock output polarity flag PCLK_CTRL[3] is set (high), the + * data lines change state on the falling edge of PCLK and should + * therefore be sampled on the rising edge. + * This is different than described in the data sheet. + */ + if (hm1246->mbus_flags & V4L2_MBUS_PCLK_SAMPLE_RISING) + pclk_ctrl |= HM1246_PCLK_CTRL_POL; + + cci_write(hm1246->regmap, HM1246_PCLK_CTRL_REG, pclk_ctrl, &ret); + + return ret; +} + +static int hm1246_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct hm1246 *hm1246 = to_hm1246(sd); + const struct v4l2_mbus_framefmt *format; + const struct hm1246_mode *mode; + int ret; + + format = v4l2_subdev_state_get_format(state, 0); + mode = v4l2_find_nearest_size(hm1246_modes, ARRAY_SIZE(hm1246_modes), + rect.width, rect.height, format->width, + format->height); + + ret = pm_runtime_resume_and_get(hm1246->dev); + if (ret) + return ret; + + ret = hm1246_identify_module(hm1246); + if (ret) + goto err_rpm_put; + + ret = hm1246_setup_pll(hm1246, mode); + if (ret) { + dev_err(hm1246->dev, "failed to setup PLL\n"); + goto err_rpm_put; + } + + ret = hm1246_setup_moderegs(hm1246, mode); + if (ret) + goto err_rpm_put; + + ret = hm1246_setup_bus(hm1246); + if (ret) + goto err_rpm_put; + + ret = __v4l2_ctrl_handler_setup(&hm1246->ctrls); + if (ret) { + dev_err(hm1246->dev, "failed to setup v4l2 controls\n"); + goto err_rpm_put; + } + + ret = cci_write(hm1246->regmap, HM1246_MODE_SELECT_REG, + HM1246_MODE_SELECT_STREAM, NULL); + if (ret) + goto err_rpm_put; + + /* + * Since mirroring may change the actual pixel format, it must not be + * changed during streaming. + */ + __v4l2_ctrl_grab(hm1246->vflip_ctrl, true); + __v4l2_ctrl_grab(hm1246->hflip_ctrl, true); + + return 0; + +err_rpm_put: + pm_runtime_put_autosuspend(hm1246->dev); + + return ret; +} + +static int hm1246_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct hm1246 *hm1246 = to_hm1246(sd); + int ret; + + ret = cci_write(hm1246->regmap, HM1246_MODE_SELECT_REG, + HM1246_MODE_SELECT_STANDBY, NULL); + + __v4l2_ctrl_grab(hm1246->vflip_ctrl, false); + __v4l2_ctrl_grab(hm1246->hflip_ctrl, false); + + pm_runtime_put_autosuspend(hm1246->dev); + + return ret; +} + +static const struct v4l2_subdev_video_ops hm1246_video_ops = { + .s_stream = v4l2_subdev_s_stream_helper, +}; + +static const struct v4l2_subdev_pad_ops hm1246_subdev_pad_ops = { + .enum_mbus_code = hm1246_enum_mbus_code, + .enum_frame_size = hm1246_enum_frame_size, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = hm1246_set_format, + .get_selection = hm1246_get_selection, + .enable_streams = hm1246_enable_streams, + .disable_streams = hm1246_disable_streams, +}; + +static const struct v4l2_subdev_ops hm1246_subdev_ops = { + .video = &hm1246_video_ops, + .pad = &hm1246_subdev_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops hm1246_internal_ops = { + .init_state = hm1246_init_state, +}; + +static int hm1246_get_regulators(struct device *dev, struct hm1246 *hm1246) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(hm1246_supply_names); i++) + hm1246->supplies[i].supply = hm1246_supply_names[i]; + + return devm_regulator_bulk_get(dev, ARRAY_SIZE(hm1246_supply_names), + hm1246->supplies); +} + +static int hm1246_parse_fwnode(struct hm1246 *hm1246) +{ + struct fwnode_handle *endpoint; + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_PARALLEL, + }; + int ret; + + endpoint = fwnode_graph_get_endpoint_by_id(dev_fwnode(hm1246->dev), + 0, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + + ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &bus_cfg); + fwnode_handle_put(endpoint); + if (ret) + return dev_err_probe(hm1246->dev, ret, + "parsing endpoint node failed\n"); + + hm1246->mbus_flags = bus_cfg.bus.parallel.flags; + + if (bus_cfg.nr_of_link_frequencies > 0) + hm1246->link_frequency = bus_cfg.link_frequencies[0]; + + v4l2_fwnode_endpoint_free(&bus_cfg); + + if (!hm1246->link_frequency) + return dev_err_probe(hm1246->dev, -EINVAL, + "one link frequency expected\n"); + + return 0; +} + +static int hm1246_init_controls(struct hm1246 *hm1246) +{ + const struct hm1246_mode *mode = &hm1246_modes[0]; + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl_handler *ctrl_hdlr = &hm1246->ctrls; + struct v4l2_ctrl *ctrl; + s64 pixel_rate, exposure_max, vblank_min, hblank; + int ret; + + ret = v4l2_fwnode_device_parse(hm1246->dev, &props); + if (ret) + return ret; + + v4l2_ctrl_handler_init(ctrl_hdlr, 11); + + hm1246->hflip_ctrl = v4l2_ctrl_new_std(ctrl_hdlr, &hm1246_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + if (hm1246->hflip_ctrl) + hm1246->hflip_ctrl->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + hm1246->vflip_ctrl = v4l2_ctrl_new_std(ctrl_hdlr, &hm1246_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + if (hm1246->vflip_ctrl) + hm1246->vflip_ctrl->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + v4l2_ctrl_cluster(2, &hm1246->hflip_ctrl); + + ctrl = v4l2_ctrl_new_int_menu(ctrl_hdlr, &hm1246_ctrl_ops, + V4L2_CID_LINK_FREQ, + 0, 0, + &hm1246->link_frequency); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + pixel_rate = div_u64(hm1246->link_frequency, mode->clocks_per_pixel); + ctrl = v4l2_ctrl_new_std(ctrl_hdlr, &hm1246_ctrl_ops, + V4L2_CID_PIXEL_RATE, + pixel_rate, pixel_rate, 1, + pixel_rate); + + vblank_min = mode->vts_min - mode->rect.height; + ctrl = v4l2_ctrl_new_std(ctrl_hdlr, &hm1246_ctrl_ops, + V4L2_CID_VBLANK, vblank_min, + HM1246_VTS_MAX - mode->rect.height, + 1, vblank_min); + + hblank = mode->hts - mode->rect.width; + ctrl = v4l2_ctrl_new_std(ctrl_hdlr, &hm1246_ctrl_ops, + V4L2_CID_HBLANK, hblank, hblank, + 1, hblank); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + v4l2_ctrl_new_std(ctrl_hdlr, &hm1246_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + HM1246_ANALOG_GLOBAL_GAIN_MIN, + HM1246_ANALOG_GLOBAL_GAIN_MAX, + HM1246_ANALOG_GLOBAL_GAIN_STEP, + HM1246_ANALOG_GLOBAL_GAIN_MIN); + + exposure_max = mode->vts_min - HM1246_COARSE_INTG_MARGIN; + hm1246->exposure_ctrl = v4l2_ctrl_new_std(ctrl_hdlr, &hm1246_ctrl_ops, + V4L2_CID_EXPOSURE, + HM1246_COARSE_INTG_MIN, + exposure_max, + HM1246_COARSE_INTG_STEP, + exposure_max); + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &hm1246_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(hm1246_test_pattern_menu) - 1, + 0, 0, hm1246_test_pattern_menu); + + v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &hm1246_ctrl_ops, &props); + + if (ctrl_hdlr->error) { + v4l2_ctrl_handler_free(ctrl_hdlr); + return ctrl_hdlr->error; + } + + hm1246->sd.ctrl_handler = ctrl_hdlr; + + return 0; +} + +static int hm1246_probe(struct i2c_client *client) +{ + struct hm1246 *hm1246; + int ret; + + hm1246 = devm_kzalloc(&client->dev, sizeof(*hm1246), GFP_KERNEL); + if (!hm1246) + return -ENOMEM; + + hm1246->dev = &client->dev; + + ret = hm1246_parse_fwnode(hm1246); + if (ret) + return ret; + + hm1246->regmap = devm_cci_regmap_init_i2c(client, 16); + if (IS_ERR(hm1246->regmap)) + return dev_err_probe(hm1246->dev, PTR_ERR(hm1246->regmap), + "failed to init CCI\n"); + + hm1246->xclk = devm_v4l2_sensor_clk_get(hm1246->dev, NULL); + if (IS_ERR(hm1246->xclk)) + return dev_err_probe(hm1246->dev, PTR_ERR(hm1246->xclk), + "failed to get xclk\n"); + + hm1246->xclk_freq = clk_get_rate(hm1246->xclk); + if (hm1246->xclk_freq < HM1246_XCLK_MIN || + hm1246->xclk_freq > HM1246_XCLK_MAX) + return dev_err_probe(hm1246->dev, -EINVAL, + "xclk frequency out of range: %luHz\n", + hm1246->xclk_freq); + + for (unsigned int i = 0; i < ARRAY_SIZE(hm1246_modes); i++) { + ret = hm1246_calc_pll(hm1246->xclk_freq, hm1246->link_frequency, + hm1246_modes[i].clocks_per_pixel, + NULL, NULL, NULL); + if (ret) + return dev_err_probe(hm1246->dev, ret, + "no PLL setup for %lld Hz\n", + hm1246->link_frequency); + } + + ret = hm1246_get_regulators(hm1246->dev, hm1246); + if (ret) + return dev_err_probe(hm1246->dev, ret, + "failed to get regulators\n"); + + hm1246->reset = devm_reset_control_get_optional(hm1246->dev, NULL); + if (IS_ERR(hm1246->reset)) + return dev_err_probe(hm1246->dev, PTR_ERR(hm1246->reset), + "failed to get reset\n"); + reset_control_assert(hm1246->reset); + + v4l2_i2c_subdev_init(&hm1246->sd, client, &hm1246_subdev_ops); + hm1246->sd.internal_ops = &hm1246_internal_ops; + + ret = hm1246_init_controls(hm1246); + if (ret) + return dev_err_probe(hm1246->dev, ret, + "failed to init controls\n"); + + hm1246->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + hm1246->pad.flags = MEDIA_PAD_FL_SOURCE; + hm1246->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + ret = media_entity_pads_init(&hm1246->sd.entity, 1, &hm1246->pad); + if (ret) { + dev_err_probe(hm1246->dev, ret, "failed to init media pads\n"); + goto err_v4l2_ctrl_handler_free; + } + + hm1246->sd.state_lock = hm1246->ctrls.lock; + ret = v4l2_subdev_init_finalize(&hm1246->sd); + if (ret) { + dev_err_probe(hm1246->dev, ret, "failed to init v4l2 subdev\n"); + goto err_media_entity_cleanup; + } + + pm_runtime_enable(hm1246->dev); + pm_runtime_set_autosuspend_delay(hm1246->dev, 1000); + pm_runtime_use_autosuspend(hm1246->dev); + + ret = v4l2_async_register_subdev_sensor(&hm1246->sd); + if (ret) { + dev_err_probe(hm1246->dev, ret, + "failed to register v4l2 subdev\n"); + goto err_subdev_cleanup; + } + + return 0; + +err_subdev_cleanup: + v4l2_subdev_cleanup(&hm1246->sd); + pm_runtime_disable(hm1246->dev); + pm_runtime_set_suspended(hm1246->dev); + +err_media_entity_cleanup: + media_entity_cleanup(&hm1246->sd.entity); + +err_v4l2_ctrl_handler_free: + v4l2_ctrl_handler_free(&hm1246->ctrls); + + return ret; +} + +static void hm1246_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct hm1246 *hm1246 = to_hm1246(sd); + + v4l2_async_unregister_subdev(&hm1246->sd); + v4l2_subdev_cleanup(sd); + media_entity_cleanup(&hm1246->sd.entity); + v4l2_ctrl_handler_free(&hm1246->ctrls); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) { + hm1246_power_off(hm1246->dev); + pm_runtime_set_suspended(&client->dev); + } +} + +static const struct of_device_id hm1246_of_match[] = { + { .compatible = "himax,hm1246" }, + {} +}; +MODULE_DEVICE_TABLE(of, hm1246_of_match); + +static DEFINE_RUNTIME_DEV_PM_OPS(hm1246_pm_ops, + hm1246_power_off, hm1246_power_on, NULL); + +static struct i2c_driver hm1246_i2c_driver = { + .driver = { + .of_match_table = hm1246_of_match, + .pm = pm_ptr(&hm1246_pm_ops), + .name = "hm1246", + }, + .probe = hm1246_probe, + .remove = hm1246_remove, +}; +module_i2c_driver(hm1246_i2c_driver); + +MODULE_DESCRIPTION("Himax HM1246 camera driver"); +MODULE_AUTHOR("Matthias Fend "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/imx111.c b/drivers/media/i2c/imx111.c index 8eb919788ef7..5b860fa052b1 100644 --- a/drivers/media/i2c/imx111.c +++ b/drivers/media/i2c/imx111.c @@ -1122,6 +1122,7 @@ static int imx111_enum_frame_size(struct v4l2_subdev *sd, } static int imx111_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/imx208.c b/drivers/media/i2c/imx208.c index d5350bb46f14..3a899b41a9d2 100644 --- a/drivers/media/i2c/imx208.c +++ b/drivers/media/i2c/imx208.c @@ -574,6 +574,7 @@ static int imx208_get_pad_format(struct v4l2_subdev *sd, } static int imx208_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/imx214.c b/drivers/media/i2c/imx214.c index d4945b192776..96833f2bf287 100644 --- a/drivers/media/i2c/imx214.c +++ b/drivers/media/i2c/imx214.c @@ -662,6 +662,7 @@ static const struct v4l2_subdev_core_ops imx214_core_ops = { }; static int imx214_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -717,6 +718,7 @@ static int imx214_set_format(struct v4l2_subdev *sd, } static int imx214_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -754,7 +756,7 @@ static int imx214_entity_init_state(struct v4l2_subdev *subdev, fmt.format.width = imx214_modes[0].width; fmt.format.height = imx214_modes[0].height; - imx214_set_format(subdev, sd_state, &fmt); + imx214_set_format(subdev, NULL, sd_state, &fmt); return 0; } diff --git a/drivers/media/i2c/imx219.c b/drivers/media/i2c/imx219.c index 9571f3622d2d..1580562093e8 100644 --- a/drivers/media/i2c/imx219.c +++ b/drivers/media/i2c/imx219.c @@ -78,6 +78,7 @@ #define IMX219_LLP_MIN 0x0d78 #define IMX219_BINNED_LLP_MIN 0x0de8 #define IMX219_LLP_MAX 0x7ff0 +#define IMX219_LLP_STEP 8 #define IMX219_REG_X_ADD_STA_A CCI_REG16(0x0164) #define IMX219_REG_X_ADD_END_A CCI_REG16(0x0166) @@ -340,13 +341,13 @@ static const struct imx219_mode supported_modes[] = { /* 2x2 binned 60fps mode */ .width = 1640, .height = 1232, - .fll_def = 1707, + .fll_def = 1706, }, { /* 640x480 60fps mode */ .width = 640, .height = 480, - .fll_def = 1707, + .fll_def = 1706, }, }; @@ -466,14 +467,14 @@ static int imx219_set_ctrl(struct v4l2_ctrl *ctrl) int exposure_max, exposure_def; /* Update max exposure while meeting expected vblanking */ - exposure_max = format->height + ctrl->val - IMX219_EXPOSURE_OFFSET; + exposure_max = format->height + ctrl->val - + IMX219_EXPOSURE_OFFSET * rate_factor; exposure_def = (exposure_max < IMX219_EXPOSURE_DEFAULT) ? exposure_max : IMX219_EXPOSURE_DEFAULT; ret = __v4l2_ctrl_modify_range(imx219->exposure, imx219->exposure->minimum, exposure_max, - imx219->exposure->step, - exposure_def); + rate_factor, exposure_def); if (ret) return ret; @@ -593,7 +594,8 @@ static int imx219_init_controls(struct imx219 *imx219) imx219->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx219_ctrl_ops, V4L2_CID_HBLANK, IMX219_LLP_MIN - mode->width, - IMX219_LLP_MAX - mode->width, 1, + IMX219_LLP_MAX - mode->width, + IMX219_LLP_STEP, IMX219_LLP_MIN - mode->width); exposure_max = mode->fll_def - IMX219_EXPOSURE_OFFSET; exposure_def = (exposure_max < IMX219_EXPOSURE_DEFAULT) ? @@ -844,6 +846,7 @@ static int imx219_enum_frame_size(struct v4l2_subdev *sd, } static int imx219_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -851,7 +854,7 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd, const struct imx219_mode *mode; struct v4l2_mbus_framefmt *format; struct v4l2_rect *crop; - u8 bin_h, bin_v, binning; + u8 bin_h, bin_v, bin_hv; int ret; format = v4l2_subdev_state_get_format(state, 0); @@ -884,15 +887,16 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd, bin_v = min(IMX219_ACTIVE_AREA_HEIGHT / format->height, 2U); /* Ensure bin_h and bin_v are same to avoid 1:2 or 2:1 stretching */ - binning = min(bin_h, bin_v); + bin_hv = min(bin_h, bin_v); crop = v4l2_subdev_state_get_crop(state, 0); - crop->width = format->width * binning; - crop->height = format->height * binning; + crop->width = format->width * bin_hv; + crop->height = format->height * bin_hv; crop->left = (IMX219_NATIVE_WIDTH - crop->width) / 2; crop->top = (IMX219_NATIVE_HEIGHT - crop->height) / 2; if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + int rate_factor = imx219_get_rate_factor(state); int exposure_max; int exposure_def; int llp_min; @@ -900,7 +904,8 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd, /* Update limits and set FPS to default */ ret = __v4l2_ctrl_modify_range(imx219->vblank, IMX219_VBLANK_MIN, - IMX219_FLL_MAX - mode->height, 1, + IMX219_FLL_MAX - mode->height, + rate_factor, mode->fll_def - mode->height); if (ret) return ret; @@ -911,14 +916,14 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd, return ret; /* Update max exposure while meeting expected vblanking */ - exposure_max = mode->fll_def - IMX219_EXPOSURE_OFFSET; + exposure_max = mode->fll_def - + IMX219_EXPOSURE_OFFSET * rate_factor; exposure_def = (exposure_max < IMX219_EXPOSURE_DEFAULT) ? exposure_max : IMX219_EXPOSURE_DEFAULT; ret = __v4l2_ctrl_modify_range(imx219->exposure, imx219->exposure->minimum, exposure_max, - imx219->exposure->step, - exposure_def); + rate_factor, exposure_def); if (ret) return ret; @@ -933,7 +938,8 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd, IMX219_BINNED_LLP_MIN : IMX219_LLP_MIN; ret = __v4l2_ctrl_modify_range(imx219->hblank, llp_min - mode->width, - IMX219_LLP_MAX - mode->width, 1, + IMX219_LLP_MAX - mode->width, + IMX219_LLP_STEP, llp_min - mode->width); if (ret) return ret; @@ -943,8 +949,7 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd, return ret; /* Scale the pixel rate based on the mode specific factor */ - pixel_rate = imx219_get_pixel_rate(imx219) * - imx219_get_rate_factor(state); + pixel_rate = imx219_get_pixel_rate(imx219) * rate_factor; ret = __v4l2_ctrl_modify_range(imx219->pixel_rate, pixel_rate, pixel_rate, 1, pixel_rate); if (ret) @@ -955,6 +960,7 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd, } static int imx219_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -997,7 +1003,7 @@ static int imx219_init_state(struct v4l2_subdev *sd, }, }; - return imx219_set_pad_format(sd, state, &fmt); + return imx219_set_pad_format(sd, NULL, state, &fmt); } static const struct v4l2_subdev_video_ops imx219_video_ops = { diff --git a/drivers/media/i2c/imx258.c b/drivers/media/i2c/imx258.c index bc9ee449a87c..065c33380f60 100644 --- a/drivers/media/i2c/imx258.c +++ b/drivers/media/i2c/imx258.c @@ -913,6 +913,7 @@ static int imx258_get_pad_format(struct v4l2_subdev *sd, } static int imx258_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -988,6 +989,7 @@ __imx258_get_pad_crop(struct imx258 *imx258, } static int imx258_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/imx274.c b/drivers/media/i2c/imx274.c index 76da647f9cf9..3245afc8ae5b 100644 --- a/drivers/media/i2c/imx274.c +++ b/drivers/media/i2c/imx274.c @@ -1059,6 +1059,7 @@ static int imx274_get_fmt(struct v4l2_subdev *sd, } static int imx274_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -1092,6 +1093,7 @@ static int imx274_set_fmt(struct v4l2_subdev *sd, } static int imx274_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1208,6 +1210,7 @@ static int imx274_set_selection_crop(struct stimx274 *imx274, } static int imx274_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/imx283.c b/drivers/media/i2c/imx283.c index 38ea1902f2c2..e3cd27ca238e 100644 --- a/drivers/media/i2c/imx283.c +++ b/drivers/media/i2c/imx283.c @@ -958,6 +958,7 @@ static void imx283_set_framing_limits(struct imx283 *imx283, } static int imx283_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -1260,6 +1261,7 @@ static int imx283_identify_module(struct imx283 *imx283) } static int imx283_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c index 21cbc81cb2ed..d562a9aa455c 100644 --- a/drivers/media/i2c/imx290.c +++ b/drivers/media/i2c/imx290.c @@ -1149,6 +1149,7 @@ static int imx290_enum_frame_size(struct v4l2_subdev *sd, } static int imx290_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -1187,6 +1188,7 @@ static int imx290_set_fmt(struct v4l2_subdev *sd, } static int imx290_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1246,7 +1248,7 @@ static int imx290_entity_init_state(struct v4l2_subdev *subdev, }, }; - imx290_set_fmt(subdev, sd_state, &fmt); + imx290_set_fmt(subdev, NULL, sd_state, &fmt); return 0; } diff --git a/drivers/media/i2c/imx296.c b/drivers/media/i2c/imx296.c index 69636db11a2b..74bb295799fd 100644 --- a/drivers/media/i2c/imx296.c +++ b/drivers/media/i2c/imx296.c @@ -675,6 +675,7 @@ static int imx296_enum_frame_size(struct v4l2_subdev *sd, } static int imx296_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -726,6 +727,7 @@ static int imx296_set_format(struct v4l2_subdev *sd, } static int imx296_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -751,6 +753,7 @@ static int imx296_get_selection(struct v4l2_subdev *sd, } static int imx296_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -812,8 +815,8 @@ static int imx296_init_state(struct v4l2_subdev *sd, }, }; - imx296_set_selection(sd, state, &sel); - imx296_set_format(sd, state, &format); + imx296_set_selection(sd, NULL, state, &sel); + imx296_set_format(sd, NULL, state, &format); return 0; } diff --git a/drivers/media/i2c/imx319.c b/drivers/media/i2c/imx319.c index 953310ef3046..281d39ed4194 100644 --- a/drivers/media/i2c/imx319.c +++ b/drivers/media/i2c/imx319.c @@ -2030,6 +2030,7 @@ static int imx319_get_pad_format(struct v4l2_subdev *sd, static int imx319_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/imx334.c b/drivers/media/i2c/imx334.c index 553a16b84f4d..da19d5fc19f5 100644 --- a/drivers/media/i2c/imx334.c +++ b/drivers/media/i2c/imx334.c @@ -109,6 +109,7 @@ /* CSI2 HW configuration */ #define IMX334_LINK_FREQ_891M 891000000 #define IMX334_LINK_FREQ_445M 445500000 +#define IMX334_LINK_FREQ_222M 222750000 #define IMX334_NUM_DATA_LANES 4 #define IMX334_REG_MIN 0x00 @@ -154,7 +155,6 @@ struct imx334_reg_list { * @vblank_min: Minimal vertical blanking in lines * @vblank_max: Maximum vertical blanking in lines * @pclk: Sensor pixel clock - * @link_freq_idx: Link frequency index * @reg_list: Register list for sensor mode */ struct imx334_mode { @@ -165,7 +165,28 @@ struct imx334_mode { u32 vblank_min; u32 vblank_max; u64 pclk; - u32 link_freq_idx; + struct imx334_reg_list reg_list; +}; + +/** + * struct imx334_clk_params - imx334 sensor clock parameters + * @data_rate_per_lane: Data rate per lane in bits per second + * @link_freq: Link frequency in Hz + * @width_max: Maximum image width in pixels + * @height_max: Maximum image height in pixels + * @width_min: Minimum image width in pixels + * @height_min: Minimum image height in pixels + * @default_mode: Pointer to the default sensor mode + * @reg_list: Register list for clock configuration + */ +struct imx334_clk_params { + u32 data_rate_per_lane; + u32 link_freq; + u32 width_max; + u32 height_max; + u32 width_min; + u32 height_min; + const struct imx334_mode *default_mode; struct imx334_reg_list reg_list; }; @@ -216,6 +237,7 @@ struct imx334 { static const s64 link_freq[] = { IMX334_LINK_FREQ_891M, IMX334_LINK_FREQ_445M, + IMX334_LINK_FREQ_222M, }; /* Sensor common mode registers values */ @@ -233,13 +255,6 @@ static const struct cci_reg_sequence common_mode_regs[] = { { IMX334_REG_UNREAD_PARAM6, 0x0008 }, { IMX334_REG_XVS_XHS_OUTSEL, 0x20 }, { IMX334_REG_XVS_XHS_DRV, 0x0f }, - { IMX334_REG_BCWAIT_TIME, 0x3b }, - { IMX334_REG_CPWAIT_TIME, 0x2a }, - { IMX334_REG_INCKSEL1, 0x0129 }, - { IMX334_REG_INCKSEL2, 0x06 }, - { IMX334_REG_INCKSEL3, 0xa0 }, - { IMX334_REG_INCKSEL4, 0x7e }, - { IMX334_REG_SYS_MODE, 0x02 }, { IMX334_REG_HADD_VADD, 0x00 }, { IMX334_REG_VALID_EXPAND, 0x03 }, { IMX334_REG_TCYCLE, 0x00 }, @@ -397,6 +412,39 @@ static const struct cci_reg_sequence mode_3840x2160_regs[] = { { IMX334_REG_TPLX, 0x005f }, }; +/* Data rate 1782Mbps per lane and 891Mhz link frequency */ +static const struct cci_reg_sequence link_freq_891m_regs[] = { + { IMX334_REG_BCWAIT_TIME, 0x3b }, + { IMX334_REG_CPWAIT_TIME, 0x2a }, + { IMX334_REG_INCKSEL1, 0x0129 }, + { IMX334_REG_INCKSEL2, 0x02 }, + { IMX334_REG_INCKSEL3, 0xa0 }, + { IMX334_REG_INCKSEL4, 0x7e }, + { IMX334_REG_SYS_MODE, 0x00 }, +}; + +/* Data rate 891Mbps per lane and 445Mhz link frequency */ +static const struct cci_reg_sequence link_freq_445m_regs[] = { + { IMX334_REG_BCWAIT_TIME, 0x3b }, + { IMX334_REG_CPWAIT_TIME, 0x2a }, + { IMX334_REG_INCKSEL1, 0x0129 }, + { IMX334_REG_INCKSEL2, 0x06 }, + { IMX334_REG_INCKSEL3, 0xa0 }, + { IMX334_REG_INCKSEL4, 0x7e }, + { IMX334_REG_SYS_MODE, 0x02 }, +}; + +/* Data rate 445Mbps per lane and 222Mhz link frequency */ +static const struct cci_reg_sequence link_freq_222m_regs[] = { + { IMX334_REG_BCWAIT_TIME, 0x3b }, + { IMX334_REG_CPWAIT_TIME, 0x2a }, + { IMX334_REG_INCKSEL1, 0x0129 }, + { IMX334_REG_INCKSEL2, 0x0a }, + { IMX334_REG_INCKSEL3, 0xa0 }, + { IMX334_REG_INCKSEL4, 0x7e }, + { IMX334_REG_SYS_MODE, 0x02 }, +}; + static const char * const imx334_test_pattern_menu[] = { "Disabled", "Vertical Color Bars", @@ -442,7 +490,6 @@ static const struct imx334_mode supported_modes[] = { .vblank_min = 90, .vblank_max = 132840, .pclk = 594000000, - .link_freq_idx = 0, .reg_list = { .num_of_regs = ARRAY_SIZE(mode_3840x2160_regs), .regs = mode_3840x2160_regs, @@ -455,7 +502,6 @@ static const struct imx334_mode supported_modes[] = { .vblank_min = 45, .vblank_max = 132840, .pclk = 297000000, - .link_freq_idx = 1, .reg_list = { .num_of_regs = ARRAY_SIZE(mode_1920x1080_regs), .regs = mode_1920x1080_regs, @@ -468,7 +514,6 @@ static const struct imx334_mode supported_modes[] = { .vblank_min = 45, .vblank_max = 132840, .pclk = 297000000, - .link_freq_idx = 1, .reg_list = { .num_of_regs = ARRAY_SIZE(mode_1280x720_regs), .regs = mode_1280x720_regs, @@ -481,7 +526,6 @@ static const struct imx334_mode supported_modes[] = { .vblank_min = 45, .vblank_max = 132840, .pclk = 297000000, - .link_freq_idx = 1, .reg_list = { .num_of_regs = ARRAY_SIZE(mode_640x480_regs), .regs = mode_640x480_regs, @@ -489,6 +533,46 @@ static const struct imx334_mode supported_modes[] = { }, }; +static const struct imx334_clk_params imx334_clk_params[] = { + { + .data_rate_per_lane = 1782000000, + .link_freq = IMX334_LINK_FREQ_891M, + .width_max = 3840, + .height_max = 2160, + .width_min = 3840, + .height_min = 2160, + .default_mode = &supported_modes[0], /* 3840x2160 */ + .reg_list = { + .num_of_regs = ARRAY_SIZE(link_freq_891m_regs), + .regs = link_freq_891m_regs, + }, + }, { + .data_rate_per_lane = 891000000, + .link_freq = IMX334_LINK_FREQ_445M, + .width_max = 1920, + .height_max = 1080, + .width_min = 640, + .height_min = 480, + .default_mode = &supported_modes[1], /* 1920x1080 */ + .reg_list = { + .num_of_regs = ARRAY_SIZE(link_freq_445m_regs), + .regs = link_freq_445m_regs, + }, + }, { + .data_rate_per_lane = 445500000, + .link_freq = IMX334_LINK_FREQ_222M, + .width_max = 1920, + .height_max = 1080, + .width_min = 640, + .height_min = 480, + .default_mode = &supported_modes[1], /* 1920x1080 */ + .reg_list = { + .num_of_regs = ARRAY_SIZE(link_freq_222m_regs), + .regs = link_freq_222m_regs, + }, + } +}; + /** * to_imx334() - imv334 V4L2 sub-device to imx334 device. * @subdev: pointer to imx334 V4L2 sub-device @@ -512,10 +596,6 @@ static int imx334_update_controls(struct imx334 *imx334, { int ret; - ret = __v4l2_ctrl_s_ctrl(imx334->link_freq_ctrl, mode->link_freq_idx); - if (ret) - return ret; - ret = __v4l2_ctrl_modify_range(imx334->pclk_ctrl, mode->pclk, mode->pclk, 1, mode->pclk); if (ret) @@ -741,11 +821,13 @@ static int imx334_get_pad_format(struct v4l2_subdev *sd, } static int imx334_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { struct imx334 *imx334 = to_imx334(sd); const struct imx334_mode *mode; + const struct imx334_clk_params *clk_params; int ret = 0; mode = v4l2_find_nearest_size(supported_modes, @@ -753,6 +835,11 @@ static int imx334_set_pad_format(struct v4l2_subdev *sd, width, height, fmt->format.width, fmt->format.height); + clk_params = &imx334_clk_params[imx334->link_freq_ctrl->val]; + if (mode->width > clk_params->width_max || mode->height > clk_params->height_max || + mode->width < clk_params->width_min || mode->height < clk_params->height_min) + mode = clk_params->default_mode; + imx334_fill_pad_format(imx334, mode, fmt); fmt->format.code = imx334_get_format_code(imx334, fmt->format.code); @@ -786,7 +873,7 @@ static int imx334_init_state(struct v4l2_subdev *sd, ~(imx334->link_freq_bitmap), __ffs(imx334->link_freq_bitmap)); - return imx334_set_pad_format(sd, sd_state, &fmt); + return imx334_set_pad_format(sd, NULL, sd_state, &fmt); } static int imx334_set_framefmt(struct imx334 *imx334) @@ -824,6 +911,15 @@ static int imx334_enable_streams(struct v4l2_subdev *sd, goto err_rpm_put; } + /* Write sensor link freq registers */ + reg_list = &imx334_clk_params[imx334->link_freq_ctrl->val].reg_list; + ret = cci_multi_reg_write(imx334->cci, reg_list->regs, + reg_list->num_of_regs, NULL); + if (ret) { + dev_err(imx334->dev, "fail to write initial registers\n"); + goto err_rpm_put; + } + /* Write sensor mode registers */ reg_list = &imx334->cur_mode->reg_list; ret = cci_multi_reg_write(imx334->cci, reg_list->regs, @@ -1096,9 +1192,6 @@ static int imx334_init_controls(struct imx334 *imx334) __ffs(imx334->link_freq_bitmap), link_freq); - if (imx334->link_freq_ctrl) - imx334->link_freq_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; - imx334->hblank_ctrl = v4l2_ctrl_new_std(ctrl_hdlr, &imx334_ctrl_ops, V4L2_CID_HBLANK, diff --git a/drivers/media/i2c/imx335.c b/drivers/media/i2c/imx335.c index 1f777a1a8192..8f29d9f2da83 100644 --- a/drivers/media/i2c/imx335.c +++ b/drivers/media/i2c/imx335.c @@ -844,6 +844,7 @@ static void imx335_fill_pad_format(struct imx335 *imx335, } static int imx335_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -901,10 +902,11 @@ static int imx335_init_state(struct v4l2_subdev *sd, ~(imx335->link_freq_bitmap), __ffs(imx335->link_freq_bitmap)); - return imx335_set_pad_format(sd, sd_state, &fmt); + return imx335_set_pad_format(sd, NULL, sd_state, &fmt); } static int imx335_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/imx355.c b/drivers/media/i2c/imx355.c index 8eb8588cb71b..45e3002bd93c 100644 --- a/drivers/media/i2c/imx355.c +++ b/drivers/media/i2c/imx355.c @@ -714,6 +714,7 @@ static void imx355_update_pad_format(struct imx355 *imx355, static int imx355_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -765,6 +766,7 @@ imx355_set_pad_format(struct v4l2_subdev *sd, } static int imx355_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -796,7 +798,7 @@ static int imx355_entity_init_state(struct v4l2_subdev *subdev, fmt.format.width = supported_modes[0].width; fmt.format.height = supported_modes[0].height; - imx355_set_pad_format(subdev, sd_state, &fmt); + imx355_set_pad_format(subdev, NULL, sd_state, &fmt); return 0; } diff --git a/drivers/media/i2c/imx412.c b/drivers/media/i2c/imx412.c index d2760e9e6194..4628e2eda231 100644 --- a/drivers/media/i2c/imx412.c +++ b/drivers/media/i2c/imx412.c @@ -588,6 +588,7 @@ static int imx412_get_pad_format(struct v4l2_subdev *sd, } static int imx412_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -621,7 +622,7 @@ static int imx412_init_state(struct v4l2_subdev *sd, fmt.which = sd_state ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; imx412_fill_pad_format(imx412, &supported_mode, &fmt); - return imx412_set_pad_format(sd, sd_state, &fmt); + return imx412_set_pad_format(sd, NULL, sd_state, &fmt); } /** diff --git a/drivers/media/i2c/imx415.c b/drivers/media/i2c/imx415.c index c3b22b238ea0..2571718cffff 100644 --- a/drivers/media/i2c/imx415.c +++ b/drivers/media/i2c/imx415.c @@ -1021,6 +1021,7 @@ static int imx415_enum_frame_size(struct v4l2_subdev *sd, } static int imx415_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -1042,6 +1043,7 @@ static int imx415_set_format(struct v4l2_subdev *sd, } static int imx415_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1070,7 +1072,7 @@ static int imx415_init_state(struct v4l2_subdev *sd, }, }; - imx415_set_format(sd, state, &format); + imx415_set_format(sd, NULL, state, &format); return 0; } diff --git a/drivers/media/i2c/imx471.c b/drivers/media/i2c/imx471.c index 4053aed84340..0f3a2812b9ea 100644 --- a/drivers/media/i2c/imx471.c +++ b/drivers/media/i2c/imx471.c @@ -41,12 +41,12 @@ /* Analog gain control */ #define IMX471_REG_ANALOG_GAIN CCI_REG16(0x0204) #define IMX471_ANA_GAIN_MIN 0 -#define IMX471_ANA_GAIN_MAX 800 +#define IMX471_ANA_GAIN_MAX 960 #define IMX471_ANA_GAIN_STEP 1 #define IMX471_ANA_GAIN_DEFAULT 0 /* Digital gain control */ -#define IMX471_REG_DPGA_USE_GLOBAL_GAIN CCI_REG16(0x3ff9) +#define IMX471_REG_DPGA_USE_GLOBAL_GAIN CCI_REG8(0x3ff9) #define IMX471_REG_DIG_GAIN_GLOBAL CCI_REG16(0x020e) #define IMX471_DGTL_GAIN_MIN 256 #define IMX471_DGTL_GAIN_MAX 4095 @@ -416,6 +416,7 @@ static void imx471_update_pad_format(struct imx471 *sensor, } static int imx471_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -455,6 +456,7 @@ static int imx471_set_pad_format(struct v4l2_subdev *sd, } static int imx471_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -494,7 +496,7 @@ static int imx471_init_state(struct v4l2_subdev *sd, }, }; - return imx471_set_pad_format(sd, sd_state, &fmt); + return imx471_set_pad_format(sd, NULL, sd_state, &fmt); } static int imx471_identify_module(struct imx471 *sensor) diff --git a/drivers/media/i2c/imx576.c b/drivers/media/i2c/imx576.c new file mode 100644 index 000000000000..6243683302c7 --- /dev/null +++ b/drivers/media/i2c/imx576.c @@ -0,0 +1,1105 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * V4L2 Support for the IMX576 + * + * Copyright (C) 2026 Silicon Signals Pvt. Ltd. + * + * Copyright (C) 2024 Luca Weiss + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "ccs/ccs-regs.h" +#include "ccs-pll.h" + +#define IMX576_INCLK_RATE (24 * HZ_PER_MHZ) + +#define IMX576_CHIP_ID 0x0576 + +#define IMX576_EXPOSURE_MIN 8 +#define IMX576_EXPOSURE_OFFSET 22 +#define IMX576_EXPOSURE_STEP 1 +#define IMX576_EXPOSURE_DEFAULT 0x0648 + +#define IMX576_ANA_GAIN_MIN 0 +#define IMX576_ANA_GAIN_MAX 978 +#define IMX576_ANA_GAIN_STEP 1 +#define IMX576_ANA_GAIN_DEFAULT 0 + +#define IMX576_LINE_LENGTH 6144 +#define IMX576_VBLANK_DEF 4387 + +/* FIXME: Exact VBLANK limit unknown (no datasheet). */ +#define IMX576_VBLANK_MAX 32420 + +#define IMX576_PIXEL_RATE 813600000 +#define IMX576_NUM_DATA_LANES 4 + +/* IMX576 native and active pixel array size */ +static const struct v4l2_rect imx576_native_area = { + .top = 0, + .left = 0, + .width = 5792, + .height = 4464, +}; + +static const struct v4l2_rect imx576_active_area = { + .top = 136, + .left = 16, + .width = 5760, + .height = 4312, +}; + +static const char * const imx576_supply_names[] = { + "vana", /* Analog Power */ + "vif", /* Interface Power */ + "vdig", /* Digital Power */ +}; + +static const struct cci_reg_sequence imx576_common_regs[] = { + { CCS_R_EXTCLK_FREQUENCY_MHZ, 0x1800 }, + { CCI_REG8(0x3c7e), 0x05 }, + { CCI_REG8(0x3c7f), 0x07 }, + { CCI_REG8(0x380d), 0x80 }, + { CCI_REG8(0x3c00), 0x1a }, + { CCI_REG8(0x3c01), 0x1a }, + { CCI_REG8(0x3c02), 0x1a }, + { CCI_REG8(0x3c03), 0x1a }, + { CCI_REG8(0x3c04), 0x1a }, + { CCI_REG8(0x3c05), 0x01 }, + { CCI_REG8(0x3c08), 0xff }, + { CCI_REG8(0x3c09), 0xff }, + { CCI_REG8(0x3c0a), 0x01 }, + { CCI_REG8(0x3c0d), 0xff }, + { CCI_REG8(0x3c0e), 0xff }, + { CCI_REG8(0x3c0f), 0x20 }, + { CCI_REG8(0x3f89), 0x01 }, + { CCI_REG8(0x4b8e), 0x18 }, + { CCI_REG8(0x4b8f), 0x10 }, + { CCI_REG8(0x4ba8), 0x08 }, + { CCI_REG8(0x4baa), 0x08 }, + { CCI_REG8(0x4bab), 0x08 }, + { CCI_REG8(0x4bc9), 0x10 }, + { CCI_REG8(0x5511), 0x01 }, + { CCI_REG8(0x560b), 0x5b }, + { CCI_REG8(0x56a7), 0x60 }, + { CCI_REG8(0x5b3b), 0x60 }, + { CCI_REG8(0x5ba7), 0x60 }, + { CCI_REG8(0x6002), 0x00 }, + { CCI_REG8(0x6014), 0x01 }, + { CCI_REG8(0x6118), 0x0a }, + { CCI_REG8(0x6122), 0x0a }, + { CCI_REG8(0x6128), 0x0a }, + { CCI_REG8(0x6132), 0x0a }, + { CCI_REG8(0x6138), 0x0a }, + { CCI_REG8(0x6142), 0x0a }, + { CCI_REG8(0x6148), 0x0a }, + { CCI_REG8(0x6152), 0x0a }, + { CCI_REG8(0x617b), 0x04 }, + { CCI_REG8(0x617e), 0x04 }, + { CCI_REG8(0x6187), 0x04 }, + { CCI_REG8(0x618a), 0x04 }, + { CCI_REG8(0x6193), 0x04 }, + { CCI_REG8(0x6196), 0x04 }, + { CCI_REG8(0x619f), 0x04 }, + { CCI_REG8(0x61a2), 0x04 }, + { CCI_REG8(0x61ab), 0x04 }, + { CCI_REG8(0x61ae), 0x04 }, + { CCI_REG8(0x61b7), 0x04 }, + { CCI_REG8(0x61ba), 0x04 }, + { CCI_REG8(0x61c3), 0x04 }, + { CCI_REG8(0x61c6), 0x04 }, + { CCI_REG8(0x61cf), 0x04 }, + { CCI_REG8(0x61d2), 0x04 }, + { CCI_REG8(0x61db), 0x04 }, + { CCI_REG8(0x61de), 0x04 }, + { CCI_REG8(0x61e7), 0x04 }, + { CCI_REG8(0x61ea), 0x04 }, + { CCI_REG8(0x61f3), 0x04 }, + { CCI_REG8(0x61f6), 0x04 }, + { CCI_REG8(0x61ff), 0x04 }, + { CCI_REG8(0x6202), 0x04 }, + { CCI_REG8(0x620b), 0x04 }, + { CCI_REG8(0x620e), 0x04 }, + { CCI_REG8(0x6217), 0x04 }, + { CCI_REG8(0x621a), 0x04 }, + { CCI_REG8(0x6223), 0x04 }, + { CCI_REG8(0x6226), 0x04 }, + { CCI_REG8(0x6b0b), 0x02 }, + { CCI_REG8(0x6b0c), 0x01 }, + { CCI_REG8(0x6b0d), 0x05 }, + { CCI_REG8(0x6b0f), 0x04 }, + { CCI_REG8(0x6b10), 0x02 }, + { CCI_REG8(0x6b11), 0x06 }, + { CCI_REG8(0x6b12), 0x03 }, + { CCI_REG8(0x6b13), 0x07 }, + { CCI_REG8(0x6b14), 0x0d }, + { CCI_REG8(0x6b15), 0x09 }, + { CCI_REG8(0x6b16), 0x0c }, + { CCI_REG8(0x6b17), 0x08 }, + { CCI_REG8(0x6b18), 0x0e }, + { CCI_REG8(0x6b19), 0x0a }, + { CCI_REG8(0x6b1a), 0x0f }, + { CCI_REG8(0x6b1b), 0x0b }, + { CCI_REG8(0x6b1c), 0x01 }, + { CCI_REG8(0x6b1d), 0x05 }, + { CCI_REG8(0x6b1f), 0x04 }, + { CCI_REG8(0x6b20), 0x02 }, + { CCI_REG8(0x6b21), 0x06 }, + { CCI_REG8(0x6b22), 0x03 }, + { CCI_REG8(0x6b23), 0x07 }, + { CCI_REG8(0x6b24), 0x0d }, + { CCI_REG8(0x6b25), 0x09 }, + { CCI_REG8(0x6b26), 0x0c }, + { CCI_REG8(0x6b27), 0x08 }, + { CCI_REG8(0x6b28), 0x0e }, + { CCI_REG8(0x6b29), 0x0a }, + { CCI_REG8(0x6b2a), 0x0f }, + { CCI_REG8(0x6b2b), 0x0b }, + { CCI_REG8(0x7948), 0x01 }, + { CCI_REG8(0x7949), 0x06 }, + { CCI_REG8(0x794b), 0x04 }, + { CCI_REG8(0x794c), 0x04 }, + { CCI_REG8(0x794d), 0x3a }, + { CCI_REG8(0x7951), 0x00 }, + { CCI_REG8(0x7952), 0x01 }, + { CCI_REG8(0x7955), 0x00 }, + { CCI_REG8(0x9004), 0x10 }, + { CCI_REG8(0x9200), 0xa0 }, + { CCI_REG8(0x9201), 0xa7 }, + { CCI_REG8(0x9202), 0xa0 }, + { CCI_REG8(0x9203), 0xaa }, + { CCI_REG8(0x9204), 0xa0 }, + { CCI_REG8(0x9205), 0xad }, + { CCI_REG8(0x9206), 0xa0 }, + { CCI_REG8(0x9207), 0xb0 }, + { CCI_REG8(0x9208), 0xa0 }, + { CCI_REG8(0x9209), 0xb3 }, + { CCI_REG8(0x920a), 0xb7 }, + { CCI_REG8(0x920b), 0x34 }, + { CCI_REG8(0x920c), 0xb7 }, + { CCI_REG8(0x920d), 0x36 }, + { CCI_REG8(0x920e), 0xb7 }, + { CCI_REG8(0x920f), 0x37 }, + { CCI_REG8(0x9210), 0xb7 }, + { CCI_REG8(0x9211), 0x38 }, + { CCI_REG8(0x9212), 0xb7 }, + { CCI_REG8(0x9213), 0x39 }, + { CCI_REG8(0x9214), 0xb7 }, + { CCI_REG8(0x9215), 0x3a }, + { CCI_REG8(0x9216), 0xb7 }, + { CCI_REG8(0x9217), 0x3c }, + { CCI_REG8(0x9218), 0xb7 }, + { CCI_REG8(0x9219), 0x3d }, + { CCI_REG8(0x921a), 0xb7 }, + { CCI_REG8(0x921b), 0x3e }, + { CCI_REG8(0x921c), 0xb7 }, + { CCI_REG8(0x921d), 0x3f }, + { CCI_REG8(0x921e), 0x7f }, + { CCI_REG8(0x921f), 0x77 }, + { CCI_REG8(0x99af), 0x0f }, + { CCI_REG8(0x99b0), 0x0f }, + { CCI_REG8(0x99b1), 0x0f }, + { CCI_REG8(0x99b2), 0x0f }, + { CCI_REG8(0x99b3), 0x0f }, + { CCI_REG8(0x99e1), 0x0f }, + { CCI_REG8(0x99e2), 0x0f }, + { CCI_REG8(0x99e3), 0x0f }, + { CCI_REG8(0x99e4), 0x0f }, + { CCI_REG8(0x99e5), 0x0f }, + { CCI_REG8(0x99e6), 0x0f }, + { CCI_REG8(0x99e7), 0x0f }, + { CCI_REG8(0x99e8), 0x0f }, + { CCI_REG8(0x99e9), 0x0f }, + { CCI_REG8(0x99ea), 0x0f }, + { CCI_REG8(0xe286), 0x31 }, + { CCI_REG8(0xe2a6), 0x32 }, + { CCI_REG8(0xe2c6), 0x33 }, + { CCI_REG8(0x4038), 0x00 }, + { CCI_REG8(0x9856), 0xa0 }, + { CCI_REG8(0x9857), 0x78 }, + { CCI_REG8(0x9858), 0x64 }, + { CCI_REG8(0x986e), 0x64 }, + { CCI_REG8(0x9870), 0x3c }, + { CCI_REG8(0x993a), 0x0e }, + { CCI_REG8(0x993b), 0x0e }, + { CCI_REG8(0x9953), 0x08 }, + { CCI_REG8(0x9954), 0x08 }, + { CCI_REG8(0x996b), 0x0f }, + { CCI_REG8(0x996d), 0x0f }, + { CCI_REG8(0x996f), 0x0f }, + { CCI_REG8(0x998e), 0x0f }, + { CCI_REG8(0xa101), 0x01 }, + { CCI_REG8(0xa103), 0x01 }, + { CCI_REG8(0xa105), 0x01 }, + { CCI_REG8(0xa107), 0x01 }, + { CCI_REG8(0xa109), 0x01 }, + { CCI_REG8(0xa10b), 0x01 }, + { CCI_REG8(0xa10d), 0x01 }, + { CCI_REG8(0xa10f), 0x01 }, + { CCI_REG8(0xa111), 0x01 }, + { CCI_REG8(0xa113), 0x01 }, + { CCI_REG8(0xa115), 0x01 }, + { CCI_REG8(0xa117), 0x01 }, + { CCI_REG8(0xa119), 0x01 }, + { CCI_REG8(0xa11b), 0x01 }, + { CCI_REG8(0xa11d), 0x01 }, + { CCI_REG8(0xaa58), 0x00 }, + { CCI_REG8(0xaa59), 0x01 }, + { CCI_REG8(0xab03), 0x10 }, + { CCI_REG8(0xab04), 0x10 }, + { CCI_REG8(0xab05), 0x10 }, + { CCI_REG8(0xad6a), 0x03 }, + { CCI_REG8(0xad6b), 0xff }, + { CCI_REG8(0xad77), 0x00 }, + { CCI_REG8(0xad82), 0x03 }, + { CCI_REG8(0xad83), 0xff }, + { CCI_REG8(0xae06), 0x04 }, + { CCI_REG8(0xae07), 0x16 }, + { CCI_REG8(0xae08), 0xff }, + { CCI_REG8(0xae09), 0x04 }, + { CCI_REG8(0xae0a), 0x16 }, + { CCI_REG8(0xae0b), 0xff }, + { CCI_REG8(0xaf01), 0x04 }, + { CCI_REG8(0xaf03), 0x0a }, + { CCI_REG8(0xaf05), 0x18 }, + { CCI_REG8(0xb048), 0x0a }, + { CCS_R_CSI_DATA_FORMAT, 0x0a0a }, + { CCS_R_CSI_LANE_MODE, 0x03 }, + { CCS_R_HDR_MODE, 0x00 }, + { CCI_REG8(0x3140), 0x00 }, + { CCI_REG8(0x3246), 0x01 }, + { CCI_REG8(0x3247), 0x01 }, + { CCI_REG8(0x0401), 0x00 }, + { CCS_R_SCALE_M, 0x0010 }, + { CCS_R_SINGLE_DEFECT_CORRECT_EN, CCS_SINGLE_DEFECT_CORRECT_EN_ENABLE }, + { CCI_REG8(0x3620), 0x01 }, + { CCI_REG8(0x3f0c), 0x00 }, +}; + +struct imx576 { + struct device *dev; + struct regmap *regmap; + struct ccs_pll pll; + struct v4l2_subdev sd; + struct media_pad pad; + struct gpio_desc *reset_gpio; + struct clk *inclk; + struct regulator_bulk_data supplies[ARRAY_SIZE(imx576_supply_names)]; + + /* V4L2 Controls */ + struct v4l2_ctrl_handler handler; + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *gain; + + u32 link_freq_index; + u32 inclk_freq; + unsigned long link_freq_bitmap; +}; + +struct imx576_reg_list { + u32 num_of_regs; + const struct cci_reg_sequence *regs; +}; + +enum { + IMX576_LINK_FREQ_1011MHZ, + IMX576_LINK_FREQ_1014MHZ, + IMX576_LINK_FREQ_1017MHZ, +}; + +static const s64 link_freqs[] = { + [IMX576_LINK_FREQ_1011MHZ] = 1011000000, + [IMX576_LINK_FREQ_1014MHZ] = 1014000000, + [IMX576_LINK_FREQ_1017MHZ] = 1017000000, +}; + +static const u32 imx576_mbus_codes[] = { + MEDIA_BUS_FMT_SRGGB10_1X10, +}; + +static inline struct imx576 *to_imx576(struct v4l2_subdev *sd) +{ + return container_of_const(sd, struct imx576, sd); +} + +static int imx576_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imx576 *imx576 = container_of_const(ctrl->handler, + struct imx576, handler); + struct v4l2_subdev_state *state; + struct v4l2_mbus_framefmt *fmt; + int ret = 0; + + state = v4l2_subdev_get_locked_active_state(&imx576->sd); + fmt = v4l2_subdev_state_get_format(state, 0); + + if (ctrl->id == V4L2_CID_VBLANK) { + /* Honour the VBLANK limits when setting exposure */ + ret = __v4l2_ctrl_modify_range(imx576->exposure, + IMX576_EXPOSURE_MIN, + fmt->height + ctrl->val - + IMX576_EXPOSURE_OFFSET, + IMX576_EXPOSURE_STEP, + IMX576_EXPOSURE_DEFAULT); + if (ret) + return ret; + } + + if (pm_runtime_get_if_active(imx576->dev) == 0) + return 0; + + cci_write(imx576->regmap, CCS_R_GROUPED_PARAMETER_HOLD, 1, &ret); + + switch (ctrl->id) { + case V4L2_CID_VBLANK: { + u64 vmax = fmt->height + ctrl->val; + + cci_write(imx576->regmap, CCS_R_FRAME_LENGTH_LINES, vmax, &ret); + break; + } + case V4L2_CID_EXPOSURE: + cci_write(imx576->regmap, CCS_R_COARSE_INTEGRATION_TIME, ctrl->val, &ret); + break; + case V4L2_CID_ANALOGUE_GAIN: + cci_write(imx576->regmap, CCS_R_ANALOG_GAIN_CODE_GLOBAL, + ctrl->val, &ret); + break; + default: + dev_err(imx576->dev, "Invalid control %d\n", ctrl->id); + ret = -EINVAL; + break; + } + + cci_write(imx576->regmap, CCS_R_GROUPED_PARAMETER_HOLD, 0, &ret); + + pm_runtime_put(imx576->dev); + + return ret; +} + +static int imx576_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(imx576_mbus_codes)) + return -EINVAL; + + code->code = imx576_mbus_codes[code->index]; + + return 0; +} + +static int imx576_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct imx576 *imx576 = to_imx576(sd); + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *crop; + int ret; + + fmt->format.width = imx576_active_area.width; + fmt->format.height = imx576_active_area.height; + fmt->format.code = imx576_mbus_codes[0]; + fmt->format.field = V4L2_FIELD_NONE; + fmt->format.colorspace = V4L2_COLORSPACE_RAW; + fmt->format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + fmt->format.quantization = V4L2_QUANTIZATION_DEFAULT; + fmt->format.xfer_func = V4L2_XFER_FUNC_NONE; + + format = v4l2_subdev_state_get_format(sd_state, 0); + + *format = fmt->format; + + crop = v4l2_subdev_state_get_crop(sd_state, 0); + *crop = imx576_active_area; + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + u32 vblank_def = IMX576_VBLANK_DEF - imx576_active_area.height; + + ret = __v4l2_ctrl_modify_range(imx576->vblank, vblank_def, + IMX576_VBLANK_MAX, 1, vblank_def); + if (ret) + return ret; + } + + return 0; +} + +static int imx576_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_NATIVE_SIZE: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r = imx576_native_area; + return 0; + case V4L2_SEL_TGT_CROP: { + struct v4l2_rect *crop; + + crop = v4l2_subdev_state_get_crop(sd_state, sel->pad); + sel->r = *crop; + + return 0; + } + case V4L2_SEL_TGT_CROP_DEFAULT: + sel->r = imx576_active_area; + return 0; + default: + return -EINVAL; + } +} + +static int imx576_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fsize) +{ + if (fsize->index > 0) + return -EINVAL; + + if (fsize->code != imx576_mbus_codes[0]) + return -EINVAL; + + fsize->min_width = imx576_active_area.width; + fsize->max_width = fsize->min_width; + fsize->min_height = imx576_active_area.height; + fsize->max_height = fsize->min_height; + + return 0; +} + +static int imx576_set_window(struct imx576 *imx576, + struct v4l2_subdev_state *state) +{ + const struct v4l2_rect *crop; + const struct v4l2_mbus_framefmt *format; + int ret = 0; + s32 x_start, y_start; + + crop = v4l2_subdev_state_get_crop(state, 0); + format = v4l2_subdev_state_get_format(state, 0); + + /* Line length */ + cci_write(imx576->regmap, CCS_R_LINE_LENGTH_PCK, IMX576_LINE_LENGTH, + &ret); + + /* Imaging area */ + x_start = crop->left - imx576_active_area.left; + y_start = crop->top - imx576_active_area.top; + cci_write(imx576->regmap, CCS_R_X_ADDR_START, x_start, &ret); + cci_write(imx576->regmap, CCS_R_Y_ADDR_START, y_start, &ret); + cci_write(imx576->regmap, CCS_R_X_ADDR_END, x_start + crop->width - 1, + &ret); + cci_write(imx576->regmap, CCS_R_Y_ADDR_END, y_start + crop->height - 1, + &ret); + + /* Binning */ + cci_write(imx576->regmap, CCS_R_BINNING_MODE, 0x00, &ret); + cci_write(imx576->regmap, CCS_R_BINNING_TYPE, 0x11, &ret); + cci_write(imx576->regmap, CCS_R_BINNING_WEIGHTING, 0x0a, &ret); + + /* Digital crop */ + cci_write(imx576->regmap, CCS_R_DIGITAL_CROP_X_OFFSET, 0, &ret); + cci_write(imx576->regmap, CCS_R_DIGITAL_CROP_Y_OFFSET, 0, &ret); + cci_write(imx576->regmap, CCS_R_DIGITAL_CROP_IMAGE_WIDTH, format->width, &ret); + cci_write(imx576->regmap, CCS_R_DIGITAL_CROP_IMAGE_HEIGHT, format->height, &ret); + + /* Output size */ + cci_write(imx576->regmap, CCS_R_X_OUTPUT_SIZE, format->width, &ret); + cci_write(imx576->regmap, CCS_R_Y_OUTPUT_SIZE, format->height, &ret); + + return ret; +} + +static int imx576_pll_calculate(struct imx576 *imx576, u64 link_freq) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx576->sd); + struct ccs_pll *pll = &imx576->pll; + static const struct ccs_pll_limits imx576_pll_limits = { + .min_ext_clk_freq_hz = 6000000, + .max_ext_clk_freq_hz = 27000000, + + .vt_fr = { + .min_pre_pll_clk_div = 1, + .max_pre_pll_clk_div = 4, + .min_pll_ip_clk_freq_hz = 6000000, + .max_pll_ip_clk_freq_hz = 12000000, + .min_pll_multiplier = 87, + .max_pll_multiplier = 350, + .min_pll_op_clk_freq_hz = 1050000000, + .max_pll_op_clk_freq_hz = 2100000000, + }, + + .vt_bk = { + .min_sys_clk_div = 2, + .max_sys_clk_div = 4, + .min_sys_clk_freq_hz = 262500000, + .max_sys_clk_freq_hz = 1050000000, + /* divisior is Fixed to 5 */ + .min_pix_clk_div = 5, + .max_pix_clk_div = 5, + .min_pix_clk_freq_hz = 52500000, + .max_pix_clk_freq_hz = 210000000, + }, + .op_fr = { + .min_pre_pll_clk_div = 1, + .max_pre_pll_clk_div = 15, + .min_pll_ip_clk_freq_hz = 6000000, + .max_pll_ip_clk_freq_hz = 12000000, + .min_pll_multiplier = 47, + .max_pll_multiplier = 2300, + .min_pll_op_clk_freq_hz = 1250000000, + .max_pll_op_clk_freq_hz = 2300000000, + }, + .op_bk = { + .min_sys_clk_div = 1, + .max_sys_clk_div = 2, + .min_sys_clk_freq_hz = 625000000, + .max_sys_clk_freq_hz = 2300000000, + /* (RAW8 and RAW10) */ + .min_pix_clk_div = 8, + .max_pix_clk_div = 10, + .min_pix_clk_freq_hz = 62500000, + .max_pix_clk_freq_hz = 287500000, + }, + + .min_line_length_pck_bin = IMX576_LINE_LENGTH, + .min_line_length_pck = IMX576_LINE_LENGTH, + }; + + memset(pll, 0, sizeof(*pll)); + + pll->bus_type = CCS_PLL_BUS_TYPE_CSI2_DPHY; + pll->op_lanes = IMX576_NUM_DATA_LANES; + pll->csi2.lanes = IMX576_NUM_DATA_LANES; + pll->vt_lanes = 4; + pll->binning_horizontal = 1; + pll->binning_vertical = 1; + pll->scale_m = 1; + pll->bits_per_pixel = 10; + pll->flags = CCS_PLL_FLAG_LANE_SPEED_MODEL | CCS_PLL_FLAG_DUAL_PLL; + pll->link_freq = link_freq; + pll->ext_clk_freq_hz = imx576->inclk_freq; + pll->pixel_rate_pixel_array = IMX576_PIXEL_RATE; + + return ccs_pll_calculate(&client->dev, &imx576_pll_limits, pll); +} + +static int imx576_set_pll(struct imx576 *imx576) +{ + int ret = 0; + + cci_write(imx576->regmap, CCS_R_VT_PIX_CLK_DIV, + imx576->pll.vt_bk.pix_clk_div, &ret); + cci_write(imx576->regmap, CCS_R_VT_SYS_CLK_DIV, + imx576->pll.vt_bk.sys_clk_div, &ret); + cci_write(imx576->regmap, CCS_R_PRE_PLL_CLK_DIV, + imx576->pll.vt_fr.pre_pll_clk_div, &ret); + cci_write(imx576->regmap, CCS_R_PLL_MULTIPLIER, + imx576->pll.vt_fr.pll_multiplier, &ret); + cci_write(imx576->regmap, CCS_R_OP_PIX_CLK_DIV, + imx576->pll.op_bk.pix_clk_div, &ret); + cci_write(imx576->regmap, CCS_R_OP_SYS_CLK_DIV, + imx576->pll.op_bk.sys_clk_div, &ret); + cci_write(imx576->regmap, CCS_R_OP_PRE_PLL_CLK_DIV, + imx576->pll.op_fr.pre_pll_clk_div, &ret); + cci_write(imx576->regmap, CCS_R_OP_PLL_MULTIPLIER, + imx576->pll.op_fr.pll_multiplier, &ret); + cci_write(imx576->regmap, CCS_R_PLL_MODE, CCS_PLL_MODE_DUAL, + &ret); + + return ret; +} + +static int imx576_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct imx576 *imx576 = to_imx576(sd); + int ret; + + ret = pm_runtime_resume_and_get(imx576->dev); + if (ret < 0) + return ret; + + /* Write common registers */ + ret = cci_multi_reg_write(imx576->regmap, imx576_common_regs, + ARRAY_SIZE(imx576_common_regs), NULL); + if (ret) { + dev_err(imx576->dev, "failed to write common registers\n"); + goto err_rpm_put; + } + + /* Set window registers */ + ret = imx576_set_window(imx576, state); + if (ret) { + dev_err(imx576->dev, "failed to program window\n"); + goto err_rpm_put; + } + + /* Set link frequency from PLL calculation */ + ret = imx576_set_pll(imx576); + if (ret) { + dev_err(imx576->dev, "failed to configure PLL\n"); + goto err_rpm_put; + } + + ret = __v4l2_ctrl_handler_setup(imx576->sd.ctrl_handler); + if (ret) { + dev_err(imx576->dev, "fail to setup handler\n"); + goto err_rpm_put; + } + + /* T7: delay before sending stream command */ + usleep_range(8000, 9000); + + /* Start streaming */ + ret = cci_write(imx576->regmap, CCS_R_MODE_SELECT, + CCS_MODE_SELECT_STREAMING, NULL); + if (ret) { + dev_err(imx576->dev, "fail to start streaming\n"); + goto err_rpm_put; + } + + return 0; + +err_rpm_put: + pm_runtime_put(imx576->dev); + + return ret; +} + +static int imx576_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct imx576 *imx576 = to_imx576(sd); + int ret; + + ret = cci_write(imx576->regmap, CCS_R_MODE_SELECT, + CCS_MODE_SELECT_SOFTWARE_STANDBY, NULL); + if (ret) + dev_err(imx576->dev, "failed to set stream off\n"); + + pm_runtime_put(imx576->dev); + + return 0; +} + +static int imx576_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_TRY, + .pad = 0, + .format = { + .code = imx576_mbus_codes[0], + .width = imx576_active_area.width, + .height = imx576_active_area.height, + }, + }; + + return imx576_set_pad_format(sd, NULL, sd_state, &fmt); +} + +static const struct v4l2_subdev_video_ops imx576_video_ops = { + .s_stream = v4l2_subdev_s_stream_helper, +}; + +static const struct v4l2_subdev_pad_ops imx576_pad_ops = { + .enum_mbus_code = imx576_enum_mbus_code, + .enum_frame_size = imx576_enum_frame_size, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = imx576_set_pad_format, + .get_selection = imx576_get_selection, + .enable_streams = imx576_enable_streams, + .disable_streams = imx576_disable_streams, +}; + +static const struct v4l2_subdev_internal_ops imx576_internal_ops = { + .init_state = imx576_init_state, +}; + +static const struct v4l2_subdev_ops imx576_subdev_ops = { + .video = &imx576_video_ops, + .pad = &imx576_pad_ops, +}; + +static const struct v4l2_ctrl_ops imx576_ctrl_ops = { + .s_ctrl = imx576_set_ctrl, +}; + +static int imx576_detect(struct imx576 *imx576) +{ + int ret; + u64 val; + + ret = cci_read(imx576->regmap, CCS_R_SENSOR_MODEL_ID, &val, NULL); + if (ret) + return dev_err_probe(imx576->dev, ret, + "failed to read chip id %x\n", + IMX576_CHIP_ID); + + if (val != IMX576_CHIP_ID) + return dev_err_probe(imx576->dev, -EIO, + "chip id mismatch: %x!=%llx\n", + IMX576_CHIP_ID, val); + + return 0; +} + +static int imx576_power_on(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct imx576 *imx576 = to_imx576(sd); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(imx576_supply_names), + imx576->supplies); + if (ret < 0) { + dev_err(dev, "failed to enable regulators\n"); + return ret; + } + + ret = clk_prepare_enable(imx576->inclk); + if (ret) { + dev_err(imx576->dev, "fail to enable inclk\n"); + goto err_regulator_off; + } + + gpiod_set_value_cansleep(imx576->reset_gpio, 0); + + /* T6: Wait for internal init before CCI access */ + usleep_range(1000, 1200); + + return 0; + +err_regulator_off: + regulator_bulk_disable(ARRAY_SIZE(imx576_supply_names), + imx576->supplies); + + return ret; +} + +static int imx576_power_off(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct imx576 *imx576 = to_imx576(sd); + + clk_disable_unprepare(imx576->inclk); + + gpiod_set_value_cansleep(imx576->reset_gpio, 1); + + regulator_bulk_disable(ARRAY_SIZE(imx576_supply_names), + imx576->supplies); + + return 0; +} + +static int imx576_parse_endpoint(struct imx576 *imx576) +{ + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; + struct fwnode_handle *ep; + int ret; + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(imx576->dev), 0, 0, 0); + if (!ep) + return dev_err_probe(imx576->dev, -ENXIO, + "Failed to get next endpoint\n"); + + 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 != IMX576_NUM_DATA_LANES) { + ret = dev_err_probe(imx576->dev, -EINVAL, + "only 4 data lanes are supported\n"); + goto error_out; + } + + ret = v4l2_link_freq_to_bitmap(imx576->dev, bus_cfg.link_frequencies, + bus_cfg.nr_of_link_frequencies, + link_freqs, ARRAY_SIZE(link_freqs), + &imx576->link_freq_bitmap); + if (ret) { + ret = dev_err_probe(imx576->dev, -EINVAL, + "only 1017MHz frequency is available\n"); + goto error_out; + } + + imx576->link_freq_index = __ffs(imx576->link_freq_bitmap); + +error_out: + v4l2_fwnode_endpoint_free(&bus_cfg); + + return ret; +} + +static int imx576_init_controls(struct imx576 *imx576) +{ + struct v4l2_fwnode_device_properties props; + u64 vblank_def, hblank_def; + struct v4l2_ctrl_handler *ctrl_hdlr; + u32 lpfr; + int ret; + + ret = v4l2_fwnode_device_parse(imx576->dev, &props); + if (ret) + return ret; + + ctrl_hdlr = &imx576->handler; + v4l2_ctrl_handler_init(ctrl_hdlr, 8); + + ret = imx576_pll_calculate(imx576, + link_freqs[__ffs(imx576->link_freq_bitmap)]); + if (ret) { + v4l2_ctrl_handler_free(ctrl_hdlr); + dev_err(imx576->sd.dev, "PLL calculations failed: %d\n", ret); + return ret; + } + + vblank_def = IMX576_VBLANK_DEF - imx576_active_area.height; + lpfr = vblank_def + imx576_active_area.height; + imx576->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx576_ctrl_ops, + V4L2_CID_EXPOSURE, + IMX576_EXPOSURE_MIN, + lpfr - IMX576_EXPOSURE_OFFSET, + IMX576_EXPOSURE_STEP, + IMX576_EXPOSURE_DEFAULT); + + imx576->gain = v4l2_ctrl_new_std(ctrl_hdlr, &imx576_ctrl_ops, + V4L2_CID_ANALOGUE_GAIN, + IMX576_ANA_GAIN_MIN, + IMX576_ANA_GAIN_MAX, + IMX576_ANA_GAIN_STEP, + IMX576_ANA_GAIN_DEFAULT); + + imx576->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx576_ctrl_ops, + V4L2_CID_VBLANK, vblank_def, + IMX576_VBLANK_MAX, 1, vblank_def); + + v4l2_ctrl_new_std(ctrl_hdlr, &imx576_ctrl_ops, V4L2_CID_PIXEL_RATE, + imx576->pll.pixel_rate_pixel_array, + imx576->pll.pixel_rate_pixel_array, 1, + imx576->pll.pixel_rate_pixel_array); + + imx576->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx576_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(link_freqs) - 1, + imx576->link_freq_index, + link_freqs); + + hblank_def = IMX576_LINE_LENGTH - imx576_active_area.width; + imx576->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx576_ctrl_ops, + V4L2_CID_HBLANK, hblank_def, + hblank_def, 1, hblank_def); + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx576_ctrl_ops, &props); + if (ret) + goto err_handler_free; + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + goto err_handler_free; + } + + imx576->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + imx576->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + imx576->sd.ctrl_handler = ctrl_hdlr; + + return 0; + +err_handler_free: + v4l2_ctrl_handler_free(ctrl_hdlr); + + return ret; +} + +static int imx576_probe(struct i2c_client *client) +{ + struct imx576 *imx576; + int ret; + + imx576 = devm_kzalloc(&client->dev, sizeof(*imx576), GFP_KERNEL); + if (!imx576) + return -ENOMEM; + + imx576->dev = &client->dev; + + /* Initialize subdev */ + v4l2_i2c_subdev_init(&imx576->sd, client, &imx576_subdev_ops); + imx576->sd.internal_ops = &imx576_internal_ops; + + imx576->regmap = devm_cci_regmap_init_i2c(client, 16); + if (IS_ERR(imx576->regmap)) + return dev_err_probe(imx576->dev, PTR_ERR(imx576->regmap), + "failed to initialize CCI\n"); + + ret = imx576_parse_endpoint(imx576); + if (ret) + return dev_err_probe(imx576->dev, ret, + "failed to parse endpoint configuration\n"); + + /* Get sensor input clock */ + imx576->inclk = devm_v4l2_sensor_clk_get(imx576->dev, NULL); + if (IS_ERR(imx576->inclk)) + return dev_err_probe(imx576->dev, PTR_ERR(imx576->inclk), + "failed to get inclk\n"); + + imx576->inclk_freq = clk_get_rate(imx576->inclk); + if (imx576->inclk_freq != IMX576_INCLK_RATE) + return dev_err_probe(imx576->dev, -EINVAL, + "inclk frequency not supported: %u Hz\n", + imx576->inclk_freq); + + for (unsigned int i = 0; i < ARRAY_SIZE(imx576_supply_names); i++) + imx576->supplies[i].supply = imx576_supply_names[i]; + + ret = devm_regulator_bulk_get(imx576->dev, + ARRAY_SIZE(imx576_supply_names), + imx576->supplies); + if (ret) + return dev_err_probe(imx576->dev, ret, + "failed to get regulators\n"); + + imx576->reset_gpio = devm_gpiod_get_optional(imx576->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(imx576->reset_gpio)) + return dev_err_probe(imx576->dev, PTR_ERR(imx576->reset_gpio), + "failed to get reset GPIO\n"); + + ret = imx576_power_on(imx576->dev); + if (ret) + return ret; + + ret = imx576_detect(imx576); + if (ret) + goto error_power_off; + + ret = imx576_init_controls(imx576); + if (ret) { + dev_err_probe(imx576->dev, ret, "failed to init controls"); + goto error_power_off; + } + + /* Initialize subdev */ + imx576->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + imx576->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + imx576->pad.flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&imx576->sd.entity, 1, &imx576->pad); + if (ret) { + dev_err_probe(imx576->dev, ret, "failed to init entity pads"); + goto error_handler_free; + } + + imx576->sd.state_lock = imx576->handler.lock; + ret = v4l2_subdev_init_finalize(&imx576->sd); + if (ret < 0) { + dev_err_probe(imx576->dev, ret, "subdev init error\n"); + goto error_media_entity; + } + + pm_runtime_set_active(imx576->dev); + pm_runtime_enable(imx576->dev); + + ret = v4l2_async_register_subdev_sensor(&imx576->sd); + if (ret < 0) { + dev_err_probe(imx576->dev, ret, + "failed to register imx576 sub-device\n"); + goto error_subdev_cleanup; + } + + pm_runtime_idle(imx576->dev); + + return 0; + +error_subdev_cleanup: + v4l2_subdev_cleanup(&imx576->sd); + pm_runtime_disable(imx576->dev); + pm_runtime_set_suspended(imx576->dev); + +error_media_entity: + media_entity_cleanup(&imx576->sd.entity); + +error_handler_free: + v4l2_ctrl_handler_free(imx576->sd.ctrl_handler); + +error_power_off: + imx576_power_off(imx576->dev); + + return ret; +} + +static void imx576_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx576 *imx576 = to_imx576(sd); + + v4l2_async_unregister_subdev(sd); + v4l2_subdev_cleanup(&imx576->sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(imx576->sd.ctrl_handler); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) { + imx576_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); + } +} + +static DEFINE_RUNTIME_DEV_PM_OPS(imx576_pm_ops, + imx576_power_off, imx576_power_on, NULL); + +static const struct of_device_id imx576_of_match[] = { + { .compatible = "sony,imx576" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx576_of_match); + +static struct i2c_driver imx576_driver = { + .driver = { + .name = "imx576", + .pm = &imx576_pm_ops, + .of_match_table = imx576_of_match, + }, + .probe = imx576_probe, + .remove = imx576_remove, +}; +module_i2c_driver(imx576_driver); + +MODULE_DESCRIPTION("IMX576 Camera Sensor Driver"); +MODULE_AUTHOR("Himanshu Bhavani "); +MODULE_AUTHOR("Hardevsinh Palaniya "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/imx678.c b/drivers/media/i2c/imx678.c index 0efbf43d2fe6..8413fd44d657 100644 --- a/drivers/media/i2c/imx678.c +++ b/drivers/media/i2c/imx678.c @@ -849,6 +849,7 @@ static int imx678_enum_frame_size(struct v4l2_subdev *sd, } static int imx678_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1130,7 +1131,6 @@ static const struct v4l2_subdev_video_ops imx678_video_ops = { static const struct v4l2_subdev_pad_ops imx678_pad_ops = { .enum_mbus_code = imx678_enum_mbus_code, .get_fmt = v4l2_subdev_get_fmt, - .set_fmt = v4l2_subdev_get_fmt, .get_selection = imx678_get_selection, .enum_frame_size = imx678_enum_frame_size, .enable_streams = imx678_enable_streams, diff --git a/drivers/media/i2c/isl7998x.c b/drivers/media/i2c/isl7998x.c index 8244d4296a02..adb49eb62642 100644 --- a/drivers/media/i2c/isl7998x.c +++ b/drivers/media/i2c/isl7998x.c @@ -1027,6 +1027,7 @@ static int isl7998x_get_fmt(struct v4l2_subdev *sd, } static int isl7998x_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/it6625.c b/drivers/media/i2c/it6625.c new file mode 100644 index 000000000000..b74df9dbf8c2 --- /dev/null +++ b/drivers/media/i2c/it6625.c @@ -0,0 +1,2459 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * it6625 - ite HDMI to MIPI bridge + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-3)"); + +#define REG_CHIP_ID_0 0x00 +#define REG_CHIP_ID_1 0x01 +#define REG_FW_VER_MAJOR 0x03 +#define REG_FW_VER_MINOR 0x04 +#define REG_PROTOCOL_VERSION 0x05 +#define B_PVER_MINOR BIT(0) +#define B_PVER_MAJOR BIT(4) + +#define REG_CMD_SET 0x10 +#define CMD_SET_CEC_LA 0xC0 +#define CMD_SET_CEC_ENABLE 0xC1 + +#define REG_EDID_START 0x20 +#define REG_CEC_RX_DATA 0x20 +#define REG_CEC_TX_DATA 0x30 + +#define REG_H_ACTIVE_1 0x50 +#define REG_H_ACTIVE_0 0x51 +#define REG_V_ACTIVE_1 0x52 +#define REG_V_ACTIVE_0 0x53 +#define REG_H_TOTAL_1 0x54 +#define REG_H_TOTAL_0 0x55 +#define REG_V_TOTAL_1 0x56 +#define REG_V_TOTAL_0 0x57 +#define REG_VID_PCLK 0x58 +#define REG_H_FP_1 0x5C +#define REG_H_FP_0 0x5D +#define REG_H_SW_1 0x5E +#define REG_H_SW_0 0x5F +#define REG_H_BP_1 0x60 +#define REG_H_BP_0 0x61 +#define REG_V_FP_1 0x62 +#define REG_V_FP_0 0x63 +#define REG_V_SW_1 0x64 +#define REG_V_SW_0 0x65 +#define REG_V_BP_1 0x66 +#define REG_V_BP_0 0x67 +#define REG_VID_INFO 0x68 +#define B_INTERLACE BIT(0) +#define B_HSWPOL BIT(1) +#define B_VSWPOL BIT(2) +#define B_PIXEL_REP BIT(4) + +#define REG_VIC 0x69 +#define REG_HDMI_VIDEO_INFO 0x6A +#define B_COLOR_MODE BIT(0) +#define B_COLOR_DEPTH BIT(4) +#define REG_HDMI_AUDIO_INFO1 0x6B +#define B_AUD_FS BIT(0) + +#define REG_HDMI_AUDIO_INFO2 0x6C +#define B_AUD_CH BIT(0) +#define B_AUD_WL BIT(4) + +#define REG_HDMI_AUDIO_INFO3 0x6D +#define B_AUD_TYPE BIT(0) +#define B_AUD_MS BIT(2) +#define B_AUD_3D BIT(3) + +#define REG_AUDIO_FMT 0x6E +#define B_I2S_WL BIT(0) +#define B_I2S_ALN BIT(2) +#define B_I2S_DLY BIT(3) +#define B_I2S_LR BIT(4) +#define B_I2S_SFT BIT(5) +#define B_AUD_OUT_IF BIT(6) + +#define REG_IF_LATCH_HB 0x6F +#define REG_IF_DATA 0x70 +#define REG_EMP_DATA 0xA0 +#define REG_AVI_DATA 0xAE + +#define REG_TX_STATUS 0xE0 + +#define REG_RX_STATUS 0xE2 +#define B_RX_5V BIT(0) +#define B_RX_HPD BIT(1) +#define B_RX_STABLE BIT(2) +#define B_RX_HDMI BIT(3) +#define B_RX_AVMUTE BIT(4) +#define B_RX_AUD_ON BIT(5) + +#define REG_RX_HDCP_STS 0xE3 +#define B_HDCP1_AUTH_START BIT(0) +#define B_HDCP2_AUTH_START BIT(1) +#define B_HDCP_AUTH_DONE BIT(2) +#define B_HDCP_ENC BIT(3) + +#define REG_CEC_STATUS 0xE4 +#define B_CEC_TX_DONE BIT(0) +#define B_CEC_TX_NACK BIT(1) + +#define REG_CEC_RX_DATA_LEN 0xE5 +#define REG_CEC_TX_DATA_LEN 0xE6 + +#define REG_SYS_MIPI_INT 0xEB +#define B_MIPI_OUTPUT_ENABLE BIT(0) +#define B_MIPI_VIDEO_UNSTABLE BIT(1) + +#define REG_RX_INT_STATUS1 0xEC +#define B_HDMI_5V_CHG BIT(0) +#define B_HDMI_VID_CHG BIT(1) +#define B_HDMI_AUD_CHG BIT(2) +#define B_HDMI_CP_CHG BIT(3) +#define B_HDMI_IF_LATCH BIT(4) +#define B_HDMI_NO_IF_LATCH BIT(5) +#define B_HDMI_EMP BIT(6) +#define B_HDMI_NO_EMP BIT(7) + +#define REG_RX_INT_STATUS2 0xED +#define B_HDMI_AVI BIT(0) +#define B_HDMI_NO_AVI BIT(1) +#define B_HDMI_VSIF BIT(2) +#define B_HDMI_NO_VSIF BIT(3) +#define B_HDMI_AVMUTE_CHG BIT(4) + +#define REG_CHIP_CONTROL 0xF0 +#define B_HDMI_RESET BIT(5) +#define B_FW_START BIT(6) + +#define REG_MIPI_CFG 0xF1 +#define M_MIPI_LANE 0x03 +#define B_MIPI_SPLIT BIT(2) +#define B_MIPI_SPLIT_CFG BIT(3) +#define B_MIPI_DPHY BIT(4) +#define B_MIPI_USE_DSI BIT(5) +#define B_MIPI_CONTINU_CLK BIT(6) + +#define REG_MIPI_DATA_TYPE 0xF2 +#define CSI_RGB444 0x20 +#define CSI_RGB555 0x21 +#define CSI_RGB565 0x22 +#define CSI_RGB666 0x23 +#define CSI_RGB888 0x24 +#define CSI_YUV420_8b_L 0x1A +#define CSI_YUV420_8b 0x1C +#define CSI_YUV420_10b 0x1D +#define CSI_YUV422_8b 0x1E +#define CSI_YUV422_10b 0x1F +#define CSI_RGB_10b 0x30 +#define CSI_RGB_12b 0x31 +#define CSI_YUV422_12b 0x32 +#define CSI_YUV420_10b_L 0x33 +#define CSI_YUV420_12b 0x34 +#define CSI_YUV444_8b 0x35 +#define CSI_YUV444_10b 0x36 +#define CSI_YUV444_12b 0x37 + +#define REG_MIPI_CONTROL 0xF3 +#define B_MIPI_OUTPUT BIT(0) + +#define REG_RX_CFG 0xF4 +#define B_MANUAL_HPD BIT(0) +#define B_HPD_HIGH BIT(1) +#define B_HPD_TOGGLE BIT(3) + +#define REG_CSC_CFG 0xF5 +#define B_DYNAMIC_RANGE BIT(0) + +#define REG_MISC_CFG 0xF6 +#define B_BAUD_RATE BIT(0) +#define B_DEBUG_MSG BIT(1) + +#define REG_HPD_DELAY 0xF7 +#define B_DELAY_COUNT BIT(0) +#define B_DELAY_UNIT BIT(7) + +#define REG_INFO_BANK_SEL 0xFD +#define CTL_BANK_EDID_READ 1 +#define CTL_BANK_EDID_WRITE 5 + +#define REG_HOST_CTRL_INT 0xFE +#define B_CMD_SET BIT(4) +#define B_CEC_SEND_DATA BIT(5) +#define B_CONFIG_UPDATE BIT(6) +#define B_IF_BANK BIT(7) + +#define REG_MCU_INTERRUPT 0xFF +#define B_SYS_INT_ACTIVE BIT(0) +#define B_CEC_RX_RECEIVED BIT(1) +#define B_CEC_TX_UPDATE BIT(2) + +#define EDID_NUM_BLOCKS_MAX 4 +#define EDID_BLOCK_SIZE 128 + +#define I2C_MAX_XFER_SIZE 8 +#define POLL_INTERVAL_CEC_MS 10 +#define POLL_INTERVAL_MS 40 + +#define AUD32K 0x03 +#define AUD44K 0x00 +#define AUD48K 0x02 +#define AUD64K 0x0B +#define AUD88K 0x08 +#define AUD96K 0x0A +#define AUD128K 0x2B +#define AUD176K 0x0C +#define AUD192K 0x0E +#define AUD256K 0x1B +#define AUD352K 0x0D +#define AUD384K 0x05 +#define AUD512K 0x3B +#define AUD705K 0x2D +#define AUD768K 0x09 +#define AUD1024K 0x35 +#define AUD1411K 0x1D +#define AUD1536K 0x15 + +enum it6625_chip_type { + IT6625_CHIP = 0, + IT6626_CHIP = 1, +}; + +struct it6625 { + struct device *dev; + struct i2c_client *i2c_client; + struct regmap *it6625_regmap; + enum it6625_chip_type chip_type; + + /* Serializes MCU transactions. */ + struct mutex it6625_lock; + /* serializes the complete VIDIOC_S_EDID sequence against itself */ + struct mutex edid_lock; + /* serializes a full InfoFrame debugfs read transaction against itself */ + struct mutex if_read_lock; + /* + * protects if_active/if_type/if_snapshot/if_snapshot_err/ + * if_snapshot_done and the REG_MCU_INTERRUPT/REG_RX_INT_STATUS1/2/ + * REG_IF_LATCH_HB/REG_IF_DATA register group between the interrupt + * path and the InfoFrame debugfs callback. Never held across + * wait_for_completion_timeout(). Nests outside it6625_lock. + */ + struct mutex if_state_lock; + + struct v4l2_subdev sd; + struct video_device *vdev; + struct media_pad pad; + struct v4l2_ctrl_handler hdl; + + /* controls */ + struct v4l2_ctrl *ctrl_5v_detect; + struct v4l2_ctrl *ctrl_audio_sampling_rate; + struct v4l2_ctrl *ctrl_audio_present; + struct v4l2_ctrl *ctrl_link_freq; + + struct delayed_work hpd_delayed_work; + + struct timer_list timer; + struct work_struct polling_work; + + struct v4l2_dv_timings timings; + + u8 csi_lanes; + u8 port_num; + enum v4l2_mbus_type bus_type; + /* number of EDID blocks currently loaded, protected by edid_lock */ + u8 edid_blocks; + + struct gpio_desc *reset_gpio; + + struct cec_adapter *cec_adap; + + struct dentry *debugfs_dir; + + struct v4l2_debugfs_if *infoframes; + struct completion if_latched; + /* true while an InfoFrame debugfs request is outstanding */ + bool if_active; + /* HDMI packet-type byte currently armed in REG_IF_LATCH_HB */ + u32 if_type; + /* driver-private copy of REG_IF_DATA, captured at the genuine latch */ + u8 if_snapshot[31]; + /* it6625_read_bytes() result for the if_snapshot capture */ + int if_snapshot_err; + /* set just before complete(), to disambiguate timeout vs. capture */ + bool if_snapshot_done; +}; + +/* + * Reported link frequency for every topology except the reference + * exception below: D-PHY (any lane count) and one-/two-trio C-PHY. + */ +static const s64 it6625_link_freq_default[] = { + 445500000, +}; + +/* + * IT6626 C-PHY, three trios: the confirmed hardware max C-PHY + * capability, tested single-port/three-trio. + */ +static const s64 it6626_cphy_3trio_link_freq[] = { + 2500000000LL, +}; + +/* + * Shared cap for every topology except the reference exception below: + * IT6625 (no C-PHY support at all), IT6626 running D-PHY, and IT6626 + * running C-PHY with fewer than three trios. This is conservative + * scoping, not a claim that one-/two-trio C-PHY can't also support a + * higher rate -- they're simply unvalidated. + */ +static const struct v4l2_dv_timings_cap it6625_timings_cap = { + .type = V4L2_DV_BT_656_1120, + /* keep this initialization for compatibility with GCC < 4.4.6 */ + .reserved = { 0 }, + + V4L2_INIT_BT_TIMINGS(640, 3840, 480, 2160, 27000000, 300000000, + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT, + V4L2_DV_BT_CAP_PROGRESSIVE | V4L2_DV_BT_CAP_INTERLACED | + V4L2_DV_BT_CAP_REDUCED_BLANKING | V4L2_DV_BT_CAP_CUSTOM) +}; + +/* + * IT6626 C-PHY, three trios: raised pixel-clock ceiling (594 MHz vs + * the shared 300 MHz cap) for this topology's higher C-PHY capability. + */ +static const struct v4l2_dv_timings_cap it6626_cphy_3trio_timings_cap = { + .type = V4L2_DV_BT_656_1120, + .reserved = { 0 }, + + V4L2_INIT_BT_TIMINGS(640, 3840, 480, 2160, 27000000, 594000000, + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT, + V4L2_DV_BT_CAP_PROGRESSIVE | V4L2_DV_BT_CAP_INTERLACED | + V4L2_DV_BT_CAP_REDUCED_BLANKING | V4L2_DV_BT_CAP_CUSTOM) +}; + +static const struct v4l2_dv_timings_cap * +it6625_get_timings_cap(struct it6625 *it6625) +{ + if (it6625->chip_type == IT6626_CHIP && + it6625->bus_type == V4L2_MBUS_CSI2_CPHY && + it6625->csi_lanes == 3) + return &it6626_cphy_3trio_timings_cap; + + return &it6625_timings_cap; +} + +static const struct it6625_format_info { + u8 csi_format; + u32 mbus_fmt_code; +} it6625_formats[] = { + { CSI_YUV422_8b, MEDIA_BUS_FMT_UYVY8_1X16 }, + { CSI_RGB888, MEDIA_BUS_FMT_RGB888_1X24 }, + { CSI_YUV444_8b, MEDIA_BUS_FMT_YUV8_1X24 }, +}; + +static inline int it6625_csi_format_idx(u8 csi_format) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(it6625_formats); i++) { + if (it6625_formats[i].csi_format == csi_format) + return i; + } + + return -EINVAL; +} + +static inline int it6625_csi_mbus_code_idx(u32 mbus_fmt_code) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(it6625_formats); i++) { + if (it6625_formats[i].mbus_fmt_code == mbus_fmt_code) + return i; + } + + return -EINVAL; +} + +static inline struct it6625 *sd_to_6625(struct v4l2_subdev *sd) +{ + return container_of(sd, struct it6625, sd); +} + +static const struct regmap_config it6625_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + .max_raw_read = I2C_MAX_XFER_SIZE, + .max_raw_write = I2C_MAX_XFER_SIZE, +}; + +static int it6625_regmap_i2c_init(struct i2c_client *client, + struct it6625 *it6625) +{ + it6625->i2c_client = client; + it6625->dev = &client->dev; + + it6625->it6625_regmap = devm_regmap_init_i2c(it6625->i2c_client, + &it6625_regmap_config); + if (IS_ERR(it6625->it6625_regmap)) + return PTR_ERR(it6625->it6625_regmap); + + return 0; +} + +static int it6625_read_byte(struct it6625 *it6625, u8 reg) +{ + struct device *dev = it6625->dev; + unsigned int val; + int err; + + err = regmap_read(it6625->it6625_regmap, reg, &val); + if (err < 0) { + dev_err(dev, "reg[0x%x] read failed err: %d", reg, err); + return err; + } + + return val; +} + +static int it6625_write_byte(struct it6625 *it6625, u8 reg, u8 val) +{ + struct device *dev = it6625->dev; + int err; + + err = regmap_write(it6625->it6625_regmap, reg, val); + if (err < 0) { + dev_err(dev, "reg[0x%x] write failed err: %d", reg, err); + return err; + } + + return 0; +} + +static int it6625_set_bits(struct it6625 *it6625, u8 reg, u8 mask, u8 val) +{ + struct device *dev = it6625->dev; + int err; + + err = regmap_update_bits(it6625->it6625_regmap, reg, mask, val); + if (err < 0) { + dev_err(dev, "reg[0x%x] set bits failed err: %d", reg, err); + return err; + } + + return 0; +} + +static int it6625_read_bytes(struct it6625 *it6625, u8 reg, u8 *buf, int len) +{ + struct device *dev = it6625->dev; + int err; + + err = regmap_bulk_read(it6625->it6625_regmap, reg, buf, len); + if (err < 0) { + dev_err(dev, "reg[0x%x] read failed err: %d", reg, err); + return err; + } + + return 0; +} + +static int it6625_write_bytes(struct it6625 *it6625, u8 reg, u8 *buf, int len) +{ + struct device *dev = it6625->dev; + int err; + + err = regmap_bulk_write(it6625->it6625_regmap, reg, buf, len); + if (err < 0) { + dev_err(dev, "reg[0x%x] write failed err: %d", reg, err); + return err; + } + + return 0; +} + +static int it6625_wait_for_status(struct it6625 *it6625, u8 reg, u8 val, + int timeout_ms) +{ + struct device *dev = it6625->dev; + int status; + int rval; + int sleep_ms = 10; + int timeout_round_ms = DIV_ROUND_UP(timeout_ms, sleep_ms) * sleep_ms; + + status = read_poll_timeout(it6625_read_byte, rval, rval == val, + sleep_ms * USEC_PER_MSEC, + timeout_round_ms * USEC_PER_MSEC, + false, it6625, reg); + + dev_dbg(dev, "%s status = %d %d", __func__, status, rval); + if (status < 0) { + dev_err(dev, "%s err status = %d", __func__, status); + return -ETIMEDOUT; + } + + return 0; +} + +static void it6625_write_command(struct it6625 *it6625, u8 *cmds, int cmd_len) +{ + it6625_write_bytes(it6625, REG_CMD_SET, cmds, cmd_len); + it6625_write_byte(it6625, REG_HOST_CTRL_INT, B_CMD_SET); + it6625_wait_for_status(it6625, REG_HOST_CTRL_INT, 0x00, 25); +} + +static int it6625_update_config(struct it6625 *it6625) +{ + int err; + + err = it6625_set_bits(it6625, REG_HOST_CTRL_INT, + B_CONFIG_UPDATE, B_CONFIG_UPDATE); + if (err < 0) + return err; + + return it6625_wait_for_status(it6625, REG_HOST_CTRL_INT, 0x00, 25); +} + +static int it6625_set_bank(struct it6625 *it6625, u8 bank) +{ + int err; + + err = it6625_write_byte(it6625, REG_INFO_BANK_SEL, bank); + if (err < 0) + return err; + + err = it6625_write_byte(it6625, REG_HOST_CTRL_INT, B_IF_BANK); + if (err < 0) + return err; + + return it6625_wait_for_status(it6625, REG_HOST_CTRL_INT, 0x00, 25); +} + +static inline bool is_hdmi(struct it6625 *it6625) +{ + int val; + + val = it6625_read_byte(it6625, REG_RX_STATUS); + return val < 0 ? false : val & B_RX_HDMI; +} + +static inline bool hdmi_5v_power_present(struct it6625 *it6625) +{ + int val; + + val = it6625_read_byte(it6625, REG_RX_STATUS); + return val < 0 ? false : val & B_RX_5V; +} + +static inline bool no_signal(struct it6625 *it6625) +{ + int val; + + val = it6625_read_byte(it6625, REG_RX_STATUS); + return val < 0 ? true : !(val & B_RX_STABLE); +} + +static inline bool audio_present(struct it6625 *it6625) +{ + int val; + + val = it6625_read_byte(it6625, REG_RX_STATUS); + return val < 0 ? false : val & B_RX_AUD_ON; +} + +static int get_audio_sampling_rate(struct it6625 *it6625) +{ + static const struct fs_id_map { + u8 fs_id; + u32 freq; + } s_fsid_map[] = { + { AUD32K, 32000 }, + { AUD44K, 44100 }, + { AUD48K, 48000 }, + { AUD64K, 64000 }, + { AUD88K, 88200 }, + { AUD96K, 96000 }, + + { AUD128K, 128000 }, + { AUD176K, 176400 }, + { AUD192K, 192000 }, + { AUD256K, 256000 }, + { AUD352K, 352800 }, + { AUD384K, 384000 }, + + { AUD512K, 512000 }, + { AUD705K, 705600 }, + { AUD768K, 768000 }, + { AUD1024K, 1024000 }, + { AUD1411K, 1411200 }, + { AUD1536K, 1536000 }, + }; + int fs_id; + int i, freq = 0; + + if (no_signal(it6625) || !audio_present(it6625)) + return 0; + + guard(mutex)(&it6625->it6625_lock); + + fs_id = it6625_read_byte(it6625, REG_HDMI_AUDIO_INFO1); + if (fs_id < 0) + return 0; + + for (i = 0; i < ARRAY_SIZE(s_fsid_map); i++) { + if (s_fsid_map[i].fs_id == fs_id) { + freq = s_fsid_map[i].freq; + break; + } + } + + return freq; +} + +static u64 it6625_get_pclk(struct it6625 *it6625) +{ + u8 ck[4]; + u32 pclk; + int ret; + + ret = it6625_read_bytes(it6625, REG_VID_PCLK, ck, sizeof(ck)); + if (ret < 0) { + dev_err(it6625->dev, "failed to read pixel clock"); + return 0; + } + + pclk = get_unaligned_be32(ck); + + v4l2_dbg(1, debug, &it6625->sd, "%s: pclk=%u (%08x)", + __func__, pclk, pclk); + + return (u64)pclk * HZ_PER_KHZ; +} + +static int it6625_read_edid(struct it6625 *it6625, u8 *edid, int start_block, + int num_blocks) +{ + struct device *dev = it6625->dev; + unsigned int i, bank_ctrl; + int err = 0; + + if (!edid) { + dev_err(dev, "edid buffer is NULL"); + return -EINVAL; + } + + if (start_block < 0 || num_blocks <= 0 || + start_block > EDID_NUM_BLOCKS_MAX || + num_blocks > EDID_NUM_BLOCKS_MAX || + start_block + num_blocks > EDID_NUM_BLOCKS_MAX) { + dev_err(dev, + "invalid block range: start_block=%d, num_blocks=%d", + start_block, num_blocks); + return -EINVAL; + } + + guard(mutex)(&it6625->it6625_lock); + for (i = 0; i < num_blocks; i++) { + bank_ctrl = CTL_BANK_EDID_READ + start_block + i; + err = it6625_set_bank(it6625, bank_ctrl); + if (err < 0) + break; + + err = it6625_read_bytes(it6625, REG_EDID_START, + edid + (i * 128), 128); + if (err < 0) + break; + } + + it6625_set_bank(it6625, 0); + + return err < 0 ? err : num_blocks; +} + +static int it6625_write_edid(struct it6625 *it6625, u8 *edid, int start_block, + int num_blocks) +{ + struct device *dev = it6625->dev; + unsigned int i, bank_ctrl; + int err = 0; + + if (start_block < 0 || num_blocks <= 0 || + start_block > EDID_NUM_BLOCKS_MAX || + num_blocks > EDID_NUM_BLOCKS_MAX || + start_block + num_blocks > EDID_NUM_BLOCKS_MAX) { + dev_err(dev, + "invalid block range: start_block=%d, num_blocks=%d", + start_block, num_blocks); + return -EINVAL; + } + + guard(mutex)(&it6625->it6625_lock); + for (i = 0; i < num_blocks; i++) { + bank_ctrl = CTL_BANK_EDID_WRITE + start_block + i; + err = it6625_set_bank(it6625, bank_ctrl); + if (err < 0) + break; + + err = it6625_write_bytes(it6625, REG_EDID_START, + edid + (i * 128), 128); + if (err < 0) + break; + + err = it6625_update_config(it6625); + if (err < 0) + break; + } + + it6625_set_bank(it6625, 0); + + return err < 0 ? err : num_blocks; +} + +static void it6625_enable_auto_hpd(struct it6625 *it6625) +{ + dev_dbg(it6625->dev, "%s: auto HPD", __func__); + guard(mutex)(&it6625->it6625_lock); + it6625_set_bits(it6625, REG_RX_CFG, 0x03, 0x00); + it6625_update_config(it6625); +} + +static void it6625_disable_hpd(struct it6625 *it6625) +{ + cancel_delayed_work_sync(&it6625->hpd_delayed_work); + + guard(mutex)(&it6625->it6625_lock); + it6625_set_bits(it6625, REG_RX_CFG, 0x03, 0x01); + it6625_update_config(it6625); +} + +static void it6625_enable_hpd(struct it6625 *it6625) +{ + schedule_delayed_work(&it6625->hpd_delayed_work, V4L2_SET_EDID_HPD_LOW_JIFFIES); +} + +static void it6625_hpd_delayed_work(struct work_struct *work) +{ + struct it6625 *it6625 = + container_of(work, struct it6625, hpd_delayed_work.work); + int val = 0; + + guard(mutex)(&it6625->it6625_lock); + it6625_set_bits(it6625, REG_RX_CFG, 0x03, val); + it6625_update_config(it6625); +} + +/* REG_H_ACTIVE_1..REG_V_ACTIVE_0 */ +struct it6625_active_size_regs { + __be16 h_active; + __be16 v_active; +}; + +static_assert(sizeof(struct it6625_active_size_regs) == 4); + +/* REG_H_FP_1..REG_V_BP_0 */ +struct it6625_porch_regs { + __be16 hfrontporch; + __be16 hsync; + __be16 hbackporch; + __be16 vfrontporch; + __be16 vsync; + __be16 vbackporch; +}; + +static_assert(sizeof(struct it6625_porch_regs) == 12); + +static int it6625_get_detected_timings(struct it6625 *it6625, + struct v4l2_dv_timings *timings) +{ + struct v4l2_bt_timings *bt = &timings->bt; + struct it6625_active_size_regs active; + struct it6625_porch_regs porch; + int val; + + if (no_signal(it6625)) { + dev_err(it6625->dev, "no signal detected"); + return -ENOLINK; + } + + guard(mutex)(&it6625->it6625_lock); + + memset(timings, 0, sizeof(struct v4l2_dv_timings)); + timings->type = V4L2_DV_BT_656_1120; + val = it6625_read_byte(it6625, REG_VID_INFO); + if (val < 0) { + dev_err(it6625->dev, "failed to read video info"); + return -EIO; + } + + bt->interlaced = val & B_INTERLACE ? + V4L2_DV_INTERLACED : V4L2_DV_PROGRESSIVE; + + if (it6625_read_bytes(it6625, REG_H_ACTIVE_1, (u8 *)&active, sizeof(active)) < 0) + return -EIO; + + bt->width = be16_to_cpu(active.h_active); + bt->height = be16_to_cpu(active.v_active); + + if (it6625_read_bytes(it6625, REG_H_FP_1, (u8 *)&porch, sizeof(porch)) < 0) + return -EIO; + + bt->hfrontporch = be16_to_cpu(porch.hfrontporch); + bt->hsync = be16_to_cpu(porch.hsync); + bt->hbackporch = be16_to_cpu(porch.hbackporch); + bt->vfrontporch = be16_to_cpu(porch.vfrontporch); + bt->vsync = be16_to_cpu(porch.vsync); + bt->vbackporch = be16_to_cpu(porch.vbackporch); + + bt->pixelclock = it6625_get_pclk(it6625); + if (bt->interlaced == V4L2_DV_INTERLACED) { + bt->height *= 2; + bt->il_vsync = bt->vsync + 1; + } + + return 0; +} + +static void it6625_show_avi_infoframe(struct it6625 *it6625) +{ + struct device *dev = it6625->dev; + union hdmi_infoframe frame; + u8 buffer[HDMI_INFOFRAME_SIZE(AVI)]; + u8 ver, len; + int ret; + + if (!is_hdmi(it6625)) { + dev_err(dev, "not HDMI signal, skip AVI infoframe log"); + return; + } + + ret = it6625_read_bytes(it6625, REG_AVI_DATA, buffer + 1, + HDMI_INFOFRAME_SIZE(AVI) - 1); + if (ret < 0) { + dev_err(dev, "failed to read AVI infoframe data"); + return; + } + + len = buffer[1]; + ver = buffer[2]; + + buffer[0] = HDMI_INFOFRAME_TYPE_AVI; + buffer[1] = ver; + buffer[2] = len; + + ret = hdmi_infoframe_unpack(&frame, buffer, sizeof(buffer)); + if (ret < 0) { + dev_err(dev, "unpack of AVI infoframe failed"); + return; + } + + hdmi_infoframe_log(KERN_INFO, dev, &frame); +} + +static int it6625_s_ctrl_detect_hdmi_5v(struct v4l2_subdev *sd) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + return v4l2_ctrl_s_ctrl(it6625->ctrl_5v_detect, + hdmi_5v_power_present(it6625)); +} + +static int it6625_s_ctrl_audio_sampling_rate(struct v4l2_subdev *sd) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + return v4l2_ctrl_s_ctrl(it6625->ctrl_audio_sampling_rate, + get_audio_sampling_rate(it6625)); +} + +static int it6625_s_ctrl_audio_present(struct v4l2_subdev *sd) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + return v4l2_ctrl_s_ctrl(it6625->ctrl_audio_present, + audio_present(it6625)); +} + +static int it6625_v4l2_sd_ctrl_update(struct v4l2_subdev *sd) +{ + int ret; + + ret = it6625_s_ctrl_detect_hdmi_5v(sd); + if (ret) + return ret; + + ret = it6625_s_ctrl_audio_sampling_rate(sd); + if (ret) + return ret; + + return it6625_s_ctrl_audio_present(sd); +} + +static int it6625_enable_stream_locked(struct it6625 *it6625, bool enable) +{ + struct v4l2_subdev *sd = &it6625->sd; + int val; + int err; + + lockdep_assert_held(&it6625->it6625_lock); + + v4l2_dbg(3, debug, sd, "%s: %sable", + __func__, enable ? "en" : "dis"); + + val = enable ? B_MIPI_OUTPUT : 0; + err = it6625_set_bits(it6625, REG_MIPI_CONTROL, B_MIPI_OUTPUT, val); + if (err < 0) + return err; + + return it6625_update_config(it6625); +} + +static int it6625_set_mipi_config_locked(struct it6625 *it6625, u32 cfg_val) +{ + u8 mipi_data_type; + int err; + + lockdep_assert_held(&it6625->it6625_lock); + + dev_dbg(it6625->dev, "mipi_data_type = 0x%x", cfg_val); + + mipi_data_type = cfg_val & 0xFF; + err = it6625_write_byte(it6625, REG_MIPI_DATA_TYPE, mipi_data_type); + if (err < 0) + return err; + + return it6625_update_config(it6625); +} + +static inline unsigned int fps_from_bt_timings(const struct v4l2_bt_timings *t) +{ + if (!V4L2_DV_BT_FRAME_HEIGHT(t) || !V4L2_DV_BT_FRAME_WIDTH(t)) + return 0; + + return DIV_ROUND_CLOSEST((unsigned int)t->pixelclock, + V4L2_DV_BT_FRAME_HEIGHT(t) * + V4L2_DV_BT_FRAME_WIDTH(t)); +} + +static int it6625_initial_setup(struct it6625 *it6625) +{ + struct v4l2_subdev *sd = &it6625->sd; + struct v4l2_subdev_state *state; + struct v4l2_mbus_framefmt *fmt; + int idx; + int val = 0; + int err; + + state = v4l2_subdev_lock_and_get_active_state(sd); + fmt = v4l2_subdev_state_get_format(state, 0); + idx = it6625_csi_mbus_code_idx(fmt->code); + if (idx < 0) + idx = 0; + v4l2_subdev_unlock_state(state); + + guard(mutex)(&it6625->it6625_lock); + + /* + * REG_MIPI_CFG[0:2] lane count field: 1 lane -> 0, 2 lanes -> 1, + * 3 lanes (C-PHY only) -> 3, 4 lanes (D-PHY only) -> 3. + */ + switch (it6625->csi_lanes) { + case 1: + val = FIELD_PREP(M_MIPI_LANE, 0); + break; + case 2: + val = FIELD_PREP(M_MIPI_LANE, 1); + break; + default: + val = FIELD_PREP(M_MIPI_LANE, 3); + break; + } + + if (it6625->bus_type == V4L2_MBUS_CSI2_DPHY) + val |= FIELD_PREP(B_MIPI_DPHY, 1); + + if (it6625->port_num == 2) + val |= FIELD_PREP(B_MIPI_SPLIT, 1); + + err = it6625_write_byte(it6625, REG_MIPI_CFG, val); + if (err) + return err; + + err = it6625_write_byte(it6625, REG_MIPI_DATA_TYPE, + it6625_formats[idx].csi_format); + if (err) + return err; + + err = it6625_write_byte(it6625, REG_MIPI_CONTROL, 0x00); + if (err) + return err; + + err = it6625_write_byte(it6625, REG_RX_CFG, 0x00); + if (err) + return err; + + err = it6625_set_bits(it6625, REG_HOST_CTRL_INT, B_CONFIG_UPDATE, + B_CONFIG_UPDATE); + if (err) + return err; + + return it6625_wait_for_status(it6625, REG_HOST_CTRL_INT, 0x00, 25); +} + +static int it6625_cec_adap_enable(struct cec_adapter *adap, bool enable) +{ + struct it6625 *it6625 = adap->priv; + u8 cmds[2] = { CMD_SET_CEC_ENABLE, enable ? 1 : 0 }; + + guard(mutex)(&it6625->it6625_lock); + it6625_write_command(it6625, cmds, sizeof(cmds)); + + return 0; +} + +static void it6625_cec_reset_la(struct it6625 *it6625, bool keep_enabled) +{ + u8 cmds[2]; + + if (keep_enabled) { + cmds[0] = CMD_SET_CEC_LA; + cmds[1] = CEC_LOG_ADDR_UNREGISTERED; + } else { + cmds[0] = CMD_SET_CEC_ENABLE; + cmds[1] = 0; + } + + guard(mutex)(&it6625->it6625_lock); + it6625_write_command(it6625, cmds, sizeof(cmds)); +} + +static int it6625_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr) +{ + struct it6625 *it6625 = adap->priv; + u8 cmds[2] = { CMD_SET_CEC_LA, log_addr }; + + dev_dbg(it6625->dev, "%s: la=%d", __func__, log_addr); + + if (log_addr == CEC_LOG_ADDR_INVALID) { + it6625_cec_reset_la(it6625, adap->is_enabled); + return 0; + } + + guard(mutex)(&it6625->it6625_lock); + it6625_write_command(it6625, cmds, sizeof(cmds)); + + return 0; +} + +static int it6625_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct it6625 *it6625 = adap->priv; + + guard(mutex)(&it6625->it6625_lock); + it6625_write_bytes(it6625, REG_CEC_TX_DATA, msg->msg, msg->len); + it6625_write_byte(it6625, REG_CEC_TX_DATA_LEN, msg->len); + it6625_set_bits(it6625, REG_HOST_CTRL_INT, B_CEC_SEND_DATA, B_CEC_SEND_DATA); + it6625_wait_for_status(it6625, REG_HOST_CTRL_INT, 0x00, 25); + + return 0; +} + +static const struct cec_adap_ops it6625_cec_adap_ops = { + .adap_enable = it6625_cec_adap_enable, + .adap_log_addr = it6625_cec_adap_log_addr, + .adap_transmit = it6625_cec_adap_transmit, +}; + +static void it6625_cec_handler(struct it6625 *it6625, u8 intstatus) +{ + struct cec_msg rxmsg = {}; + int val = 0; + + if (intstatus & B_CEC_RX_RECEIVED) { + scoped_guard(mutex, &it6625->it6625_lock) { + val = it6625_read_byte(it6625, REG_CEC_RX_DATA_LEN); + if (val > 0 && val <= CEC_MAX_MSG_SIZE) + it6625_read_bytes(it6625, REG_CEC_RX_DATA, &rxmsg.msg[0], val); + it6625_write_byte(it6625, REG_CEC_RX_DATA_LEN, 0); + } + + if (val > 0 && val <= CEC_MAX_MSG_SIZE) { + rxmsg.len = val; + cec_received_msg(it6625->cec_adap, &rxmsg); + } else { + dev_err(it6625->dev, "invalid CEC RX length %d", val); + } + } + + if (intstatus & B_CEC_TX_UPDATE) { + val = it6625_read_byte(it6625, REG_CEC_STATUS); + if (val < 0) { + dev_err(it6625->dev, "read CEC status failed"); + return; + } + + if (val & BIT(0)) { + cec_transmit_attempt_done(it6625->cec_adap, + CEC_TX_STATUS_OK); + } else if (val & BIT(1)) { + cec_transmit_attempt_done(it6625->cec_adap, + CEC_TX_STATUS_NACK); + } else { + dev_info(it6625->dev, "unknown CEC status %02X", val); + cec_transmit_attempt_done(it6625->cec_adap, + CEC_TX_STATUS_NACK); + } + } +} + +static void it6625_irq_format_change(struct it6625 *it6625) +{ + struct v4l2_subdev *sd = &it6625->sd; + struct v4l2_dv_timings timings; + const struct v4l2_event it6625_ev_fmt = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION, + }; + int ret; + + if (no_signal(it6625)) { + if (sd->devnode) + v4l2_subdev_notify_event(sd, &it6625_ev_fmt); + return; + } + + ret = it6625_get_detected_timings(it6625, &timings); + if (ret < 0) { + v4l2_dbg(1, debug, sd, "Failed to get detected timings"); + return; + } + + if (sd->devnode) + v4l2_subdev_notify_event(sd, &it6625_ev_fmt); +} + +static void it6625_irq_hdmi_audio_change(struct it6625 *it6625) +{ + struct v4l2_subdev *sd = &it6625->sd; + + it6625_s_ctrl_audio_sampling_rate(sd); + it6625_s_ctrl_audio_present(sd); +} + +static void it6625_get_timings(struct it6625 *it6625, + struct v4l2_dv_timings *timings) +{ + guard(mutex)(&it6625->it6625_lock); + *timings = it6625->timings; +} + +/* + * Project a DV-timings struct's width/height/field onto a pad format. + * Caller must hold it6625_lock and, separately, whichever subdev + * state fmt belongs to. + */ +static void it6625_fill_timings_format(const struct v4l2_dv_timings *timings, + struct v4l2_mbus_framefmt *fmt) +{ + fmt->width = timings->bt.width; + fmt->height = timings->bt.height; + fmt->field = timings->bt.interlaced == V4L2_DV_INTERLACED ? + V4L2_FIELD_INTERLACED : V4L2_FIELD_NONE; +} + +static void it6625_clear_timings(struct it6625 *it6625) +{ + struct v4l2_subdev *sd = &it6625->sd; + struct v4l2_subdev_state *state = v4l2_subdev_lock_and_get_active_state(sd); + struct v4l2_mbus_framefmt *fmt = v4l2_subdev_state_get_format(state, 0); + + scoped_guard(mutex, &it6625->it6625_lock) { + memset(&it6625->timings, 0, sizeof(it6625->timings)); + it6625_fill_timings_format(&it6625->timings, fmt); + } + + v4l2_subdev_unlock_state(state); +} + +static void it6625_irq_hdmi_5v_change(struct it6625 *it6625) +{ + struct v4l2_subdev *sd = &it6625->sd; + int ret; + + it6625_clear_timings(it6625); + ret = it6625_v4l2_sd_ctrl_update(sd); + if (ret) + dev_err(it6625->dev, "%s: failed to update controls: %d", __func__, ret); +} + +static void it6625_irq_hdcp_change(struct it6625 *it6625) +{ + struct v4l2_subdev *sd = &it6625->sd; + u8 cp_sts; + + cp_sts = it6625_read_byte(it6625, REG_RX_HDCP_STS); + v4l2_info(sd, "HDCP change to %02X", cp_sts); +} + +static void it6625_irq_infoframe_latch(struct it6625 *it6625) +{ + if (!it6625->if_active) + return; + + it6625->if_active = false; + + scoped_guard(mutex, &it6625->it6625_lock) { + it6625->if_snapshot_err = + it6625_read_bytes(it6625, REG_IF_DATA, + it6625->if_snapshot, 31); + } + it6625->if_snapshot_done = true; + complete(&it6625->if_latched); +} + +static void it6625_irq_emata_packet_latch(struct it6625 *it6625) +{ + u8 extend_packet[14]; + + it6625_read_bytes(it6625, REG_EMP_DATA, extend_packet, 13); +} + +static void it6625_irq_avmute_change(struct it6625 *it6625) +{ + struct v4l2_subdev *sd = &it6625->sd; + int val; + u8 avmute; + + val = it6625_read_byte(it6625, REG_RX_STATUS); + if (val < 0) + return; + + avmute = (val & B_RX_AVMUTE) ? 1 : 0; + v4l2_info(sd, "AVMute change to %d", avmute); +} + +static void it6625_dispatch_int_status1(struct it6625 *it6625, int int_sts1) +{ + if (int_sts1 & B_HDMI_5V_CHG) + it6625_irq_hdmi_5v_change(it6625); + + if (int_sts1 & B_HDMI_VID_CHG) + it6625_irq_format_change(it6625); + + if (int_sts1 & B_HDMI_AUD_CHG) + it6625_irq_hdmi_audio_change(it6625); + + if (int_sts1 & B_HDMI_CP_CHG) + it6625_irq_hdcp_change(it6625); + + if (int_sts1 & B_HDMI_IF_LATCH) + it6625_irq_infoframe_latch(it6625); + + if (int_sts1 & B_HDMI_EMP) + it6625_irq_emata_packet_latch(it6625); +} + +static void it6625_dispatch_int_status2(struct it6625 *it6625, int int_sts2) +{ + struct v4l2_subdev *sd = &it6625->sd; + + if (int_sts2 & B_HDMI_AVI) + it6625_show_avi_infoframe(it6625); + + if (int_sts2 & B_HDMI_NO_AVI) + v4l2_info(sd, "AVI-info stopped"); + + if (int_sts2 & B_HDMI_AVMUTE_CHG) + it6625_irq_avmute_change(it6625); +} + +/* caller must already hold if_state_lock */ +static int it6625_drain_rx_int_status(struct it6625 *it6625) +{ + int val1, val2, err; + + val1 = it6625_read_byte(it6625, REG_RX_INT_STATUS1); + val2 = it6625_read_byte(it6625, REG_RX_INT_STATUS2); + + err = 0; + if (val1 < 0) + err = val1; + else if (val2 < 0) + err = val2; + + if (val1 >= 0) { + int werr = it6625_write_byte(it6625, REG_RX_INT_STATUS1, 0x00); + + if (werr && !err) + err = werr; + it6625_dispatch_int_status1(it6625, val1); + } + + if (val2 >= 0) { + int werr = it6625_write_byte(it6625, REG_RX_INT_STATUS2, 0x00); + + if (werr && !err) + err = werr; + it6625_dispatch_int_status2(it6625, val2); + } + + return err; +} + +/* caller must already hold if_state_lock */ +static int it6625_drain_interrupts(struct it6625 *it6625, bool *had_event) +{ + struct v4l2_subdev *sd = &it6625->sd; + int val, err; + + val = it6625_read_byte(it6625, REG_MCU_INTERRUPT); + if (val < 0) { + if (had_event) + *had_event = false; + return val; + } + if (had_event) + *had_event = val > 0; + if (val == 0) + return 0; + + err = it6625_write_byte(it6625, REG_MCU_INTERRUPT, val); + + v4l2_dbg(1, debug, sd, "%s: INT = 0x%02X", __func__, val); + + if (val & (B_CEC_RX_RECEIVED | B_CEC_TX_UPDATE) && it6625->cec_adap) + it6625_cec_handler(it6625, val & (B_CEC_RX_RECEIVED | B_CEC_TX_UPDATE)); + + if (val & B_SYS_INT_ACTIVE) { + int child_err = it6625_drain_rx_int_status(it6625); + + if (child_err && !err) + err = child_err; + } + + return err; +} + +static bool it6625_interrupt_handler(struct it6625 *it6625) +{ + bool had_event = false; + + guard(mutex)(&it6625->if_state_lock); + it6625_drain_interrupts(it6625, &had_event); + return had_event; +} + +static irqreturn_t it6625_irq_handler(int unused, void *data) +{ + struct it6625 *it6625 = data; + + return it6625_interrupt_handler(it6625) ? IRQ_HANDLED : IRQ_NONE; +} + +static void it6625_irq_poll_timer(struct timer_list *t) +{ + struct it6625 *it6625 = timer_container_of(it6625, t, timer); + unsigned int msecs; + + schedule_work(&it6625->polling_work); + /* + * If CEC is present, then we need to poll more frequently, + * otherwise we will miss CEC messages. + */ + msecs = it6625->cec_adap ? POLL_INTERVAL_CEC_MS : POLL_INTERVAL_MS; + mod_timer(&it6625->timer, jiffies + msecs_to_jiffies(msecs)); +} + +static void it6625_polling_work(struct work_struct *work) +{ + struct it6625 *it6625 = container_of(work, struct it6625, + polling_work); + + it6625_interrupt_handler(it6625); +} + +static int it6625_log_status(struct v4l2_subdev *sd) +{ + struct it6625 *it6625 = sd_to_6625(sd); + struct v4l2_subdev_state *state; + struct v4l2_dv_timings timings, configured_timings; + struct v4l2_bt_timings bt; + u32 mbus_fmt_code; + + if (it6625_get_detected_timings(it6625, &timings)) + v4l2_info(sd, "No video detected"); + else + v4l2_print_dv_timings(sd->name, "Detected format: ", &timings, + true); + + it6625_get_timings(it6625, &configured_timings); + v4l2_print_dv_timings(sd->name, "Configured format: ", + &configured_timings, true); + + /* + * VIDIOC_LOG_STATUS isn't core-locked, so take both locks + * ourselves; snapshot together so the reported pair was + * actually configured together. + */ + state = v4l2_subdev_lock_and_get_active_state(sd); + scoped_guard(mutex, &it6625->it6625_lock) { + mbus_fmt_code = v4l2_subdev_state_get_format(state, 0)->code; + bt = it6625->timings.bt; + } + v4l2_subdev_unlock_state(state); + + v4l2_info(sd, "CSI format: %#x @ %uHz", mbus_fmt_code, fps_from_bt_timings(&bt)); + + it6625_show_avi_infoframe(it6625); + + return 0; +} + +static int it6625_isr(struct v4l2_subdev *sd, u32 status, bool *handled) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + schedule_work(&it6625->polling_work); + *handled = true; + + return 0; +} + +static int it6625_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subdev_subscribe(sd, fh, sub); + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subdev_subscribe_event(sd, fh, sub); + default: + return -EINVAL; + } +} + +static int it6625_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + struct it6625 *it6625 = sd_to_6625(sd); + bool val; + + val = no_signal(it6625); + *status = 0; + *status |= val ? V4L2_IN_ST_NO_SIGNAL : 0; + *status |= val ? V4L2_IN_ST_NO_SYNC : 0; + + v4l2_dbg(1, debug, sd, "%s: status = 0x%x", __func__, *status); + + return 0; +} + +static int +it6625_update_timings_if_changed(struct it6625 *it6625, + const struct v4l2_dv_timings *timings) +{ + struct v4l2_subdev *sd = &it6625->sd; + struct v4l2_subdev_state *state; + struct v4l2_mbus_framefmt *fmt; + int ret; + + /* .s_dv_timings isn't core-locked, so take both locks ourselves */ + state = v4l2_subdev_lock_and_get_active_state(sd); + fmt = v4l2_subdev_state_get_format(state, 0); + + scoped_guard(mutex, &it6625->it6625_lock) { + if (v4l2_match_dv_timings(&it6625->timings, timings, 0, false)) { + ret = 0; + } else if (!v4l2_valid_dv_timings(timings, + it6625_get_timings_cap(it6625), + NULL, NULL)) { + ret = -ERANGE; + } else { + it6625->timings = *timings; + it6625_fill_timings_format(&it6625->timings, fmt); + ret = 1; + } + } + + v4l2_subdev_unlock_state(state); + + return ret; +} + +static int it6625_enum_dv_timings(struct v4l2_subdev *sd, + struct v4l2_enum_dv_timings *timings) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + if (timings->pad != 0) + return -EINVAL; + + return v4l2_enum_dv_timings_cap(timings, + it6625_get_timings_cap(it6625), NULL, NULL); +} + +static int it6625_dv_timings_cap(struct v4l2_subdev *sd, + struct v4l2_dv_timings_cap *cap) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + if (cap->pad != 0) + return -EINVAL; + + *cap = *it6625_get_timings_cap(it6625); + + return 0; +} + +static int it6625_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + guard(mutex)(&it6625->it6625_lock); + return it6625_enable_stream_locked(it6625, true); +} + +static int it6625_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + guard(mutex)(&it6625->it6625_lock); + return it6625_enable_stream_locked(it6625, false); +} + +static int it6625_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + dev_dbg(sd->dev, "%s: index=%d", __func__, code->index); + + if (code->index >= ARRAY_SIZE(it6625_formats)) + return -EINVAL; + + dev_dbg(sd->dev, "%s: code=0x%08x", __func__, + it6625_formats[code->index].mbus_fmt_code); + code->code = it6625_formats[code->index].mbus_fmt_code; + + return 0; +} + +static int it6625_get_mbus_config(struct v4l2_subdev *sd, + unsigned int pad, + struct v4l2_mbus_config *cfg) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + if (pad != 0) + return -EINVAL; + + cfg->type = it6625->bus_type; + cfg->bus.mipi_csi2.flags = 0; + cfg->bus.mipi_csi2.num_data_lanes = it6625->csi_lanes; + + return 0; +} + +static int it6625_pad_s_dv_timings(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_dv_timings *timings) +{ + struct it6625 *it6625 = sd_to_6625(sd); + int ret; + + if (pad != 0) + return -EINVAL; + + if (!timings) + return -EINVAL; + + if (debug) + v4l2_print_dv_timings(sd->name, __func__, timings, false); + + ret = it6625_update_timings_if_changed(it6625, timings); + if (ret == -ERANGE) { + v4l2_dbg(1, debug, sd, "%s: timings out of range", __func__); + return ret; + } + + if (ret == 0) + v4l2_dbg(1, debug, sd, "%s: no change", __func__); + + return 0; +} + +static int it6625_pad_g_dv_timings(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_dv_timings *timings) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + if (pad != 0) + return -EINVAL; + + it6625_get_timings(it6625, timings); + + return 0; +} + +static int it6625_pad_query_dv_timings(struct v4l2_subdev *sd, + unsigned int pad, + struct v4l2_dv_timings *timings) +{ + struct it6625 *it6625 = sd_to_6625(sd); + int ret; + + if (pad != 0) + return -EINVAL; + + ret = it6625_get_detected_timings(it6625, timings); + if (ret) + return ret; + + if (debug) + v4l2_print_dv_timings(sd->name, __func__, timings, false); + + if (!v4l2_valid_dv_timings(timings, it6625_get_timings_cap(it6625), NULL, NULL)) { + v4l2_dbg(1, debug, sd, "%s: timings out of range", __func__); + return -ERANGE; + } + + return 0; +} + +static inline u32 format_to_colorspace(u8 csi_format) +{ + switch (csi_format) { + case CSI_RGB444: + case CSI_RGB555: + case CSI_RGB565: + case CSI_RGB666: + case CSI_RGB888: + case CSI_RGB_10b: + case CSI_RGB_12b: + return V4L2_COLORSPACE_SRGB; + case CSI_YUV420_8b_L: + case CSI_YUV420_8b: + case CSI_YUV420_10b: + case CSI_YUV422_8b: + case CSI_YUV422_10b: + case CSI_YUV422_12b: + case CSI_YUV420_10b_L: + case CSI_YUV420_12b: + case CSI_YUV444_8b: + case CSI_YUV444_10b: + case CSI_YUV444_12b: + return V4L2_COLORSPACE_REC709; + default: + return 0; + } +} + +static int it6625_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct it6625 *it6625 = sd_to_6625(sd); + struct v4l2_mbus_framefmt *fmt; + u8 csi_format; + int idx; + int ret; + + if (format->pad != 0) + return -EINVAL; + + idx = it6625_csi_mbus_code_idx(format->format.code); + if (idx < 0) { + v4l2_dbg(1, debug, sd, + "%s: unsupported format code 0x%x, falling back to default", + __func__, format->format.code); + idx = 0; + } + + csi_format = it6625_formats[idx].csi_format; + + /* fmt already carries width/height/field for this state; leave alone */ + fmt = v4l2_subdev_state_get_format(sd_state, format->pad); + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + if (v4l2_subdev_is_streaming(sd)) + return -EBUSY; + + guard(mutex)(&it6625->it6625_lock); + + ret = it6625_enable_stream_locked(it6625, false); + if (ret) + return ret; + + ret = it6625_set_mipi_config_locked(it6625, csi_format); + if (ret) + return ret; + } + + /* + * TRY: commit unconditionally. ACTIVE: commit only after the + * hardware programming above actually succeeded. + */ + fmt->code = it6625_formats[idx].mbus_fmt_code; + fmt->colorspace = format_to_colorspace(csi_format); + format->format = *fmt; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + v4l2_dbg(1, debug, sd, "%s: try format code = 0x%x", + __func__, format->format.code); + + return 0; +} + +static int it6625_g_edid(struct v4l2_subdev *sd, + struct v4l2_subdev_edid *edid) +{ + struct it6625 *it6625 = sd_to_6625(sd); + int err; + + if (edid->pad != 0) + return -EINVAL; + + memset(edid->reserved, 0, sizeof(edid->reserved)); + + guard(mutex)(&it6625->edid_lock); + + if (edid->start_block == 0 && edid->blocks == 0) { + edid->blocks = it6625->edid_blocks; + return 0; + } + + if (it6625->edid_blocks == 0) + return -ENODATA; + + if (edid->start_block >= it6625->edid_blocks || edid->blocks == 0) + return -EINVAL; + + if (edid->blocks > it6625->edid_blocks - edid->start_block) + edid->blocks = it6625->edid_blocks - edid->start_block; + + err = it6625_read_edid(it6625, edid->edid, edid->start_block, + edid->blocks); + + return (err < 0) ? err : 0; +} + +static int it6625_s_edid(struct v4l2_subdev *sd, + struct v4l2_subdev_edid *edid) +{ + struct it6625 *it6625 = sd_to_6625(sd); + u16 parent_pa = CEC_PHYS_ADDR_INVALID; + int err; + + if (edid->pad != 0) { + v4l2_err(sd, "invalid pad %d", edid->pad); + return -EINVAL; + } + + memset(edid->reserved, 0, sizeof(edid->reserved)); + + if (edid->start_block != 0) { + v4l2_err(sd, "start_block must be 0 for set edid"); + return -EINVAL; + } + + if (edid->blocks > EDID_NUM_BLOCKS_MAX) { + v4l2_err(sd, "too many edid blocks: %d", edid->blocks); + edid->blocks = EDID_NUM_BLOCKS_MAX; + return -E2BIG; + } + + if (edid->blocks != 0) { + u16 pa = v4l2_get_edid_phys_addr(edid->edid, + edid->blocks * 128, NULL); + err = v4l2_phys_addr_validate(pa, &parent_pa, NULL); + if (err) { + v4l2_err(sd, "invalid CEC physical address in EDID"); + return err; + } + } + + guard(mutex)(&it6625->edid_lock); + + it6625_disable_hpd(it6625); + cec_phys_addr_invalidate(it6625->cec_adap); + it6625->edid_blocks = 0; + + if (edid->blocks == 0) + return 0; + + err = it6625_write_edid(it6625, edid->edid, + edid->start_block, edid->blocks); + if (err < 0) { + v4l2_err(sd, "write edid failed"); + return err; + } + + it6625->edid_blocks = edid->blocks; + cec_s_phys_addr(it6625->cec_adap, parent_pa, false); + + if (hdmi_5v_power_present(it6625)) { + it6625_enable_hpd(it6625); + it6625_s_ctrl_detect_hdmi_5v(sd); + } else { + it6625_enable_auto_hpd(it6625); + } + + return 0; +} + +static const struct v4l2_subdev_core_ops it6625_core_ops = { + .log_status = it6625_log_status, + .interrupt_service_routine = it6625_isr, + .subscribe_event = it6625_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops it6625_video_ops = { + .g_input_status = it6625_g_input_status, + .s_stream = v4l2_subdev_s_stream_helper, +}; + +static const struct v4l2_subdev_pad_ops it6625_pad_ops = { + .enum_mbus_code = it6625_enum_mbus_code, + .set_fmt = it6625_set_fmt, + .get_fmt = v4l2_subdev_get_fmt, + .get_edid = it6625_g_edid, + .set_edid = it6625_s_edid, + .enum_dv_timings = it6625_enum_dv_timings, + .dv_timings_cap = it6625_dv_timings_cap, + .get_mbus_config = it6625_get_mbus_config, + .s_dv_timings = it6625_pad_s_dv_timings, + .g_dv_timings = it6625_pad_g_dv_timings, + .query_dv_timings = it6625_pad_query_dv_timings, + .enable_streams = it6625_enable_streams, + .disable_streams = it6625_disable_streams, +}; + +static const struct v4l2_subdev_ops it6625_ops = { + .core = &it6625_core_ops, + .video = &it6625_video_ops, + .pad = &it6625_pad_ops, +}; + +static int it6625_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct it6625 *it6625 = sd_to_6625(sd); + struct v4l2_mbus_framefmt *fmt = v4l2_subdev_state_get_format(sd_state, 0); + + scoped_guard(mutex, &it6625->it6625_lock) + it6625_fill_timings_format(&it6625->timings, fmt); + + fmt->code = it6625_formats[0].mbus_fmt_code; + fmt->colorspace = format_to_colorspace(it6625_formats[0].csi_format); + + return 0; +} + +static const struct v4l2_subdev_internal_ops it6625_internal_ops = { + .init_state = it6625_init_state, +}; + +static const struct v4l2_ctrl_config it6625_ctrl_audio_sampling_rate = { + .id = V4L2_CID_IT6625_AUDIO_SAMPLING_RATE, + .name = "Audio Sampling Rate", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 1536000, + .step = 1, + .def = 0, + .flags = V4L2_CTRL_FLAG_READ_ONLY, +}; + +static const struct v4l2_ctrl_config it6625_ctrl_audio_present = { + .id = V4L2_CID_IT6625_AUDIO_PRESENT, + .name = "Audio Present", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .step = 1, + .def = 0, + .flags = V4L2_CTRL_FLAG_READ_ONLY, +}; + +static int it6625_v4l2_init_controls(struct v4l2_subdev *sd) +{ + struct it6625 *it6625 = sd_to_6625(sd); + struct v4l2_ctrl_handler *hdl = &it6625->hdl; + bool cphy_3trio = it6625->bus_type == V4L2_MBUS_CSI2_CPHY && + it6625->csi_lanes == 3; + + v4l2_ctrl_handler_init(hdl, 4); + + it6625->ctrl_5v_detect = + v4l2_ctrl_new_std(hdl, NULL, V4L2_CID_DV_RX_POWER_PRESENT, + 0, 1, 0, 0); + + it6625->ctrl_audio_sampling_rate = + v4l2_ctrl_new_custom(hdl, + &it6625_ctrl_audio_sampling_rate, + NULL); + it6625->ctrl_audio_present = + v4l2_ctrl_new_custom(hdl, &it6625_ctrl_audio_present, NULL); + it6625->ctrl_link_freq = + v4l2_ctrl_new_int_menu(hdl, NULL, V4L2_CID_LINK_FREQ, 0, 0, + cphy_3trio ? it6626_cphy_3trio_link_freq : + it6625_link_freq_default); + if (hdl->error) { + v4l2_err(sd, "Failed to initialize controls"); + v4l2_ctrl_handler_free(hdl); + return hdl->error; + } + + sd->ctrl_handler = hdl; + + return 0; +} + +static void it6625_regdump_print(struct seq_file *s, const u8 *reg_buf) +{ + seq_puts(s, " 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F\n"); + + for (unsigned int i = 0; i < 256; i++) { + if (i % 16 == 0) + seq_printf(s, "[%02X] ", i & 0xF0); + seq_printf(s, "0x%02X ", reg_buf[i]); + if (i % 16 == 15) + seq_putc(s, '\n'); + } +} + +static int it6625_mipi_reg_show(struct seq_file *s, void *data) +{ + struct it6625 *it6625 = s->private; + u8 reg_buf[256]; + int ret; + + scoped_guard(mutex, &it6625->it6625_lock) + ret = it6625_read_bytes(it6625, 0x00, reg_buf, sizeof(reg_buf)); + if (ret < 0) + return ret; + it6625_regdump_print(s, reg_buf); + + return 0; +} + +static int it6625_mipi_reg_open(struct inode *inode, struct file *file) +{ + return single_open(file, it6625_mipi_reg_show, inode->i_private); +} + +static ssize_t it6625_mipi_reg_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct it6625 *it6625 = file_inode(file)->i_private; + char buf[32] = {}; + unsigned int addr, val; + ssize_t len; + loff_t pos = 0; + + len = simple_write_to_buffer(buf, sizeof(buf) - 1, &pos, user_buf, count); + if (len < 0) + return len; + buf[len] = '\0'; + + if (sscanf(buf, "%X %X", &addr, &val) != 2) + return -EINVAL; + + scoped_guard(mutex, &it6625->it6625_lock) + it6625_write_byte(it6625, addr, val); + + return count; +} + +static const struct file_operations it6625_mipi_reg_fops = { + .owner = THIS_MODULE, + .open = it6625_mipi_reg_open, + .read = seq_read, + .write = it6625_mipi_reg_write, + .llseek = seq_lseek, + .release = single_release, +}; + +/* + * Maps a V4L2_DEBUGFS_IF_* framework flag to the actual HDMI packet-type + * header byte REG_IF_LATCH_HB expects to arm that InfoFrame type. + */ +static int it6625_if_packet_type(u32 type) +{ + switch (type) { + case V4L2_DEBUGFS_IF_AVI: + return 0x82; + case V4L2_DEBUGFS_IF_AUDIO: + return 0x84; + case V4L2_DEBUGFS_IF_SPD: + return 0x83; + case V4L2_DEBUGFS_IF_HDMI: + return 0x81; + default: + return -EINVAL; + } +} + +static ssize_t it6625_debugfs_if_read(u32 type, void *priv, struct file *filp, + char __user *ubuf, size_t count, + loff_t *ppos) +{ + struct v4l2_subdev *sd = priv; + struct it6625 *it6625 = sd_to_6625(sd); + u8 buf[32] = {}; + int packet_type; + int err, err_reset; + bool captured; + int len; + + packet_type = it6625_if_packet_type(type); + if (packet_type < 0) + return 0; + + guard(mutex)(&it6625->if_read_lock); + + scoped_guard(mutex, &it6625->if_state_lock) { + scoped_guard(mutex, &it6625->it6625_lock) + err = it6625_write_byte(it6625, REG_IF_LATCH_HB, 0); + if (err) + return err; + + err = it6625_drain_interrupts(it6625, NULL); + if (err) + return err; + + reinit_completion(&it6625->if_latched); + + it6625->if_active = true; + it6625->if_type = packet_type; + it6625->if_snapshot_done = false; + it6625->if_snapshot_err = 0; + + scoped_guard(mutex, &it6625->it6625_lock) + err = it6625_write_byte(it6625, REG_IF_LATCH_HB, + packet_type); + if (err) { + scoped_guard(mutex, &it6625->it6625_lock) + it6625_write_byte(it6625, REG_IF_LATCH_HB, 0); + it6625->if_active = false; + return err; + } + } + + wait_for_completion_timeout(&it6625->if_latched, msecs_to_jiffies(100)); + + scoped_guard(mutex, &it6625->if_state_lock) { + captured = it6625->if_snapshot_done; + it6625->if_active = false; + + scoped_guard(mutex, &it6625->it6625_lock) + err_reset = it6625_write_byte(it6625, REG_IF_LATCH_HB, 0); + + if (!captured) { + if (err_reset) + return err_reset; + return 0; + } + + if (!it6625->if_snapshot_err) { + buf[0] = packet_type; + memcpy(&buf[1], it6625->if_snapshot, 31); + } + + err = it6625->if_snapshot_err ? it6625->if_snapshot_err : err_reset; + if (err) + return err; + } + + len = buf[2] ? buf[2] + 4 : -ENOENT; + if (len > (int)sizeof(buf)) + len = -ENOENT; + if (len < 0) + return 0; + return simple_read_from_buffer(ubuf, count, ppos, buf, len); +} + +static void it6625_debugfs_init(struct it6625 *it6625, struct i2c_client *client) +{ + it6625->debugfs_dir = debugfs_create_dir(dev_name(&client->dev), NULL); + + debugfs_create_file("mipi_reg", 0600, it6625->debugfs_dir, it6625, + &it6625_mipi_reg_fops); + + it6625->infoframes = v4l2_debugfs_if_alloc(it6625->debugfs_dir, + V4L2_DEBUGFS_IF_AVI | V4L2_DEBUGFS_IF_AUDIO | + V4L2_DEBUGFS_IF_SPD | V4L2_DEBUGFS_IF_HDMI, + &it6625->sd, it6625_debugfs_if_read); +} + +static void it6625_init_data(struct it6625 *it6625) +{ + static struct v4l2_dv_timings default_timing = + V4L2_DV_BT_CEA_1920X1080P60; + + it6625->timings = default_timing; + /* firmware ships with a verified 2-block default EDID in EDID RAM */ + it6625->edid_blocks = 2; +} + +static int it6625_parse_endpoint(struct it6625 *it6625) +{ + struct device *dev = it6625->dev; + /* + * Pre-setting bus_type here makes v4l2_fwnode_endpoint_alloc_parse() + * treat it as a hard requirement and reject any endpoint whose DT + * bus-type disagrees, so this must stay V4L2_MBUS_UNKNOWN to let it + * autodetect C-PHY vs D-PHY from the endpoint itself. + */ + struct v4l2_fwnode_endpoint endpoint = { .bus_type = V4L2_MBUS_UNKNOWN }; + struct device_node *ep = NULL; + unsigned int max_lanes; + unsigned int port; + int ret; + + /* + * port@0 and port@1 are the two CSI-2 output ports MIPI0/MIPI1 + * (port@2 is the HDMI input). This chip series can drive both + * simultaneously in split or mirror mode, so port_num counts how + * many of MIPI0/MIPI1 have an endpoint wired up. This driver only + * wires up a single source pad, so lane/bus-type configuration is + * parsed from whichever of the two is found first. + */ + it6625->port_num = 0; + for (port = 0; port < 2; port++) { + struct device_node *port_ep = + of_graph_get_endpoint_by_regs(dev->of_node, port, -1); + + if (!port_ep) + continue; + + it6625->port_num++; + if (!ep) + ep = port_ep; + else + of_node_put(port_ep); + } + + ret = v4l2_fwnode_endpoint_alloc_parse(of_fwnode_handle(ep), &endpoint); + of_node_put(ep); + if (ret) + return dev_err_probe(dev, ret, "failed to parse endpoint"); + + if (endpoint.bus_type != V4L2_MBUS_CSI2_DPHY && + endpoint.bus_type != V4L2_MBUS_CSI2_CPHY) { + dev_err(dev, "unsupported bus type %d, expected CSI-2 D-PHY or C-PHY", + endpoint.bus_type); + ret = -EINVAL; + goto out_free_endpoint; + } + + if (endpoint.bus_type == V4L2_MBUS_CSI2_CPHY && + it6625->chip_type != IT6626_CHIP) { + dev_err(dev, "IT6625 does not support C-PHY, only IT6626 does"); + ret = -EINVAL; + goto out_free_endpoint; + } + + max_lanes = (endpoint.bus_type == V4L2_MBUS_CSI2_CPHY) ? 3 : 4; + + if (endpoint.bus.mipi_csi2.num_data_lanes == 0 || + endpoint.bus.mipi_csi2.num_data_lanes > max_lanes) { + dev_err(dev, + "invalid number of CSI data lanes: %u (max %u for this bus type)", + endpoint.bus.mipi_csi2.num_data_lanes, max_lanes); + ret = -EINVAL; + goto out_free_endpoint; + } + + it6625->csi_lanes = endpoint.bus.mipi_csi2.num_data_lanes; + it6625->bus_type = endpoint.bus_type; + +out_free_endpoint: + v4l2_fwnode_endpoint_free(&endpoint); + + return ret; +} + +static int it6625_parse_dt(struct it6625 *it6625) +{ + return it6625_parse_endpoint(it6625); +} + +static int it6625_check_device(struct it6625 *it6625) +{ + static const u8 chip_ids[][2] = { + { 0x66, 0x25 }, + { 0x66, 0x26 }, + }; + int chip_id0, chip_id1; + + chip_id0 = it6625_read_byte(it6625, REG_CHIP_ID_0); + chip_id1 = it6625_read_byte(it6625, REG_CHIP_ID_1); + if (chip_id0 != chip_ids[it6625->chip_type][0] || + chip_id1 != chip_ids[it6625->chip_type][1]) { + dev_err(it6625->dev, + "chip ID mismatch: got 0x%02x%02x, expected 0x%02x%02x", + chip_id0, chip_id1, + chip_ids[it6625->chip_type][0], + chip_ids[it6625->chip_type][1]); + return -ENODEV; + } + + return 0; +} + +static int it6625_probe(struct i2c_client *client) +{ + struct it6625 *it6625; + struct v4l2_subdev *sd; + int err; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + it6625 = devm_kzalloc(&client->dev, sizeof(struct it6625), GFP_KERNEL); + if (!it6625) + return -ENOMEM; + + it6625->chip_type = (uintptr_t)i2c_get_match_data(client); + + it6625->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(it6625->reset_gpio)) + return PTR_ERR(it6625->reset_gpio); + + if (it6625->reset_gpio) { + usleep_range(1000, 2000); + gpiod_set_value_cansleep(it6625->reset_gpio, 0); + usleep_range(10000, 11000); + } + + err = it6625_regmap_i2c_init(client, it6625); + if (err) + return err; + + err = it6625_check_device(it6625); + if (err) + return err; + + it6625_init_data(it6625); + + err = it6625_parse_dt(it6625); + if (err) + return err; + + mutex_init(&it6625->it6625_lock); + mutex_init(&it6625->edid_lock); + mutex_init(&it6625->if_read_lock); + mutex_init(&it6625->if_state_lock); + init_completion(&it6625->if_latched); + INIT_DELAYED_WORK(&it6625->hpd_delayed_work, it6625_hpd_delayed_work); + INIT_WORK(&it6625->polling_work, it6625_polling_work); + + if (client->irq) { + err = devm_request_threaded_irq(&client->dev, client->irq, + NULL, it6625_irq_handler, + IRQF_ONESHOT | + IRQF_NO_AUTOEN, + "it6625", it6625); + if (err) + goto err_clean_work_queues; + } else { + dev_info(it6625->dev, "no IRQ, falling back to polling"); + timer_setup(&it6625->timer, it6625_irq_poll_timer, 0); + } + + sd = &it6625->sd; + v4l2_i2c_subdev_init(sd, it6625->i2c_client, &it6625_ops); + sd->internal_ops = &it6625_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + + err = it6625_v4l2_init_controls(sd); + if (err) { + dev_err(it6625->dev, "failed to initialize v4l2 controls: %d", err); + goto err_clean_work_queues; + } + + it6625->pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + err = media_entity_pads_init(&sd->entity, 1, &it6625->pad); + if (err < 0) { + dev_err(it6625->dev, "%s %d err=%d", __func__, __LINE__, err); + goto err_clean_ctrl_handler; + } + + sd->state_lock = sd->ctrl_handler->lock; + err = v4l2_subdev_init_finalize(sd); + if (err) { + dev_err(it6625->dev, "%s %d err=%d", __func__, __LINE__, err); + goto err_clean_hdl; + } + + err = v4l2_ctrl_handler_setup(sd->ctrl_handler); + if (err) + goto err_clean_state; + + it6625->cec_adap = cec_allocate_adapter(&it6625_cec_adap_ops, + it6625, dev_name(it6625->dev), + CEC_CAP_DEFAULTS | + CEC_CAP_MONITOR_ALL | + CEC_CAP_PHYS_ADDR, + 1); + if (IS_ERR(it6625->cec_adap)) { + err = PTR_ERR(it6625->cec_adap); + dev_err(it6625->dev, "%s %d", __func__, __LINE__); + goto err_clean_state; + } + + err = cec_register_adapter(it6625->cec_adap, &client->dev); + if (err < 0) { + dev_err(it6625->dev, "%s: failed to register the cec device", __func__); + cec_delete_adapter(it6625->cec_adap); + it6625->cec_adap = NULL; + goto err_clean_state; + } + + it6625_debugfs_init(it6625, client); + + err = it6625_initial_setup(it6625); + if (err) { + dev_err_probe(it6625->dev, err, "failed initial hardware setup"); + goto err_clean_debugfs; + } + + err = it6625_v4l2_sd_ctrl_update(sd); + if (err) { + dev_err_probe(it6625->dev, err, "failed to update controls"); + goto err_clean_debugfs; + } + + err = v4l2_async_register_subdev(sd); + if (err < 0) { + dev_err(it6625->dev, "%s %d err=%d", __func__, __LINE__, err); + goto err_clean_debugfs; + } + + if (client->irq) + enable_irq(client->irq); + else + mod_timer(&it6625->timer, jiffies + msecs_to_jiffies(POLL_INTERVAL_MS)); + + return 0; + +err_clean_debugfs: + v4l2_debugfs_if_free(it6625->infoframes); + debugfs_remove_recursive(it6625->debugfs_dir); + cec_unregister_adapter(it6625->cec_adap); +err_clean_state: + v4l2_subdev_cleanup(sd); +err_clean_hdl: + media_entity_cleanup(&sd->entity); +err_clean_ctrl_handler: + v4l2_ctrl_handler_free(&it6625->hdl); + +err_clean_work_queues: + if (!client->irq) + timer_shutdown_sync(&it6625->timer); + cancel_work_sync(&it6625->polling_work); + cancel_delayed_work_sync(&it6625->hpd_delayed_work); + mutex_destroy(&it6625->it6625_lock); + mutex_destroy(&it6625->edid_lock); + mutex_destroy(&it6625->if_read_lock); + mutex_destroy(&it6625->if_state_lock); + return err; +} + +static void it6625_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct it6625 *it6625 = sd_to_6625(sd); + + v4l2_debugfs_if_free(it6625->infoframes); + + if (client->irq) + disable_irq(client->irq); + else + timer_shutdown_sync(&it6625->timer); + + cancel_work_sync(&it6625->polling_work); + cancel_delayed_work_sync(&it6625->hpd_delayed_work); + + v4l2_async_unregister_subdev(sd); + v4l2_device_unregister_subdev(sd); + + debugfs_remove_recursive(it6625->debugfs_dir); + cec_unregister_adapter(it6625->cec_adap); + + v4l2_subdev_cleanup(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(&it6625->hdl); + + mutex_destroy(&it6625->it6625_lock); + mutex_destroy(&it6625->edid_lock); + mutex_destroy(&it6625->if_read_lock); + mutex_destroy(&it6625->if_state_lock); +} + +static const struct i2c_device_id it6625_id[] = { + { .name = "it6625", .driver_data = IT6625_CHIP }, + { .name = "it6626", .driver_data = IT6626_CHIP }, + {} +}; +MODULE_DEVICE_TABLE(i2c, it6625_id); + +static const struct of_device_id it6625_of_match[] = { + { .compatible = "ite,it6625", .data = (void *)IT6625_CHIP }, + { .compatible = "ite,it6626", .data = (void *)IT6626_CHIP }, + {}, +}; +MODULE_DEVICE_TABLE(of, it6625_of_match); + +static struct i2c_driver it6625_driver = { + .driver = { + .name = "it6625", + .of_match_table = it6625_of_match, + }, + .probe = it6625_probe, + .remove = it6625_remove, + .id_table = it6625_id, +}; +module_i2c_driver(it6625_driver); + +MODULE_DESCRIPTION("iTE it6625/it6626 HDMI to MIPI CSI bridge driver"); +MODULE_AUTHOR("Hermes Wu "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/lt6911uxe.c b/drivers/media/i2c/lt6911uxe.c index bdefdd157e69..c9da174dbfa6 100644 --- a/drivers/media/i2c/lt6911uxe.c +++ b/drivers/media/i2c/lt6911uxe.c @@ -384,6 +384,7 @@ static int lt6911uxe_disable_streams(struct v4l2_subdev *sd, } static int lt6911uxe_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -439,7 +440,7 @@ static int lt6911uxe_init_state(struct v4l2_subdev *sd, : V4L2_SUBDEV_FORMAT_ACTIVE, }; - return lt6911uxe_set_format(sd, sd_state, &fmt); + return lt6911uxe_set_format(sd, NULL, sd_state, &fmt); } static const struct v4l2_subdev_video_ops lt6911uxe_video_ops = { @@ -562,7 +563,7 @@ static irqreturn_t lt6911uxe_threaded_irq_fn(int irq, void *dev_id) * As a HDMI to CSI2 bridge, it needs to update the format in time * when the HDMI source changes. */ - lt6911uxe_set_format(sd, state, &fmt); + lt6911uxe_set_format(sd, NULL, state, &fmt); v4l2_subdev_unlock_state(state); return IRQ_HANDLED; diff --git a/drivers/media/i2c/max9286.c b/drivers/media/i2c/max9286.c index 79eab9045e24..050ad9405c5f 100644 --- a/drivers/media/i2c/max9286.c +++ b/drivers/media/i2c/max9286.c @@ -910,6 +910,7 @@ static int max9286_enum_mbus_code(struct v4l2_subdev *sd, } static int max9286_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/max96714.c b/drivers/media/i2c/max96714.c deleted file mode 100644 index e3e625e6f11a..000000000000 --- a/drivers/media/i2c/max96714.c +++ /dev/null @@ -1,1017 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Maxim GMSL2 Deserializer Driver - * - * Copyright (C) 2024 Collabora Ltd. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#define MAX96714_DEVICE_ID 0xc9 -#define MAX96714F_DEVICE_ID 0xca -#define MAX96714_NPORTS 2 -#define MAX96714_PAD_SINK 0 -#define MAX96714_PAD_SOURCE 1 -#define MAX96714_CSI_NLANES 4 - -/* DEV */ -#define MAX96714_REG13 CCI_REG8(0x0d) -#define MAX96714_DEV_REV CCI_REG8(0x0e) -#define MAX96714_DEV_REV_MASK GENMASK(3, 0) -#define MAX96714_LINK_LOCK CCI_REG8(0x13) -#define MAX96714_LINK_LOCK_BIT BIT(3) -#define MAX96714_IO_CHK0 CCI_REG8(0x38) -#define MAX96714_PATTERN_CLK_FREQ GENMASK(1, 0) -/* VID_RX */ -#define MAX96714_VIDEO_RX8 CCI_REG8(0x11a) -#define MAX96714_VID_LOCK BIT(6) - -/* VRX_PATGEN_0 */ -#define MAX96714_PATGEN_0 CCI_REG8(0x240) -#define MAX96714_PATGEN_1 CCI_REG8(0x241) -#define MAX96714_PATGEN_MODE GENMASK(5, 4) -#define MAX96714_PATGEN_VS_DLY CCI_REG24(0x242) -#define MAX96714_PATGEN_VS_HIGH CCI_REG24(0x245) -#define MAX96714_PATGEN_VS_LOW CCI_REG24(0x248) -#define MAX96714_PATGEN_V2H CCI_REG24(0x24b) -#define MAX96714_PATGEN_HS_HIGH CCI_REG16(0x24e) -#define MAX96714_PATGEN_HS_LOW CCI_REG16(0x250) -#define MAX96714_PATGEN_HS_CNT CCI_REG16(0x252) -#define MAX96714_PATGEN_V2D CCI_REG24(0x254) -#define MAX96714_PATGEN_DE_HIGH CCI_REG16(0x257) -#define MAX96714_PATGEN_DE_LOW CCI_REG16(0x259) -#define MAX96714_PATGEN_DE_CNT CCI_REG16(0x25b) -#define MAX96714_PATGEN_GRAD_INC CCI_REG8(0x25d) -#define MAX96714_PATGEN_CHKB_COLOR_A CCI_REG24(0x25e) -#define MAX96714_PATGEN_CHKB_COLOR_B CCI_REG24(0x261) -#define MAX96714_PATGEN_CHKB_RPT_CNT_A CCI_REG8(0x264) -#define MAX96714_PATGEN_CHKB_RPT_CNT_B CCI_REG8(0x265) -#define MAX96714_PATGEN_CHKB_ALT CCI_REG8(0x266) -/* BACKTOP */ -#define MAX96714_BACKTOP25 CCI_REG8(0x320) -#define CSI_DPLL_FREQ_MASK GENMASK(4, 0) - -/* MIPI_PHY */ -#define MAX96714_MIPI_PHY0 CCI_REG8(0x330) -#define MAX96714_FORCE_CSI_OUT BIT(7) -#define MAX96714_MIPI_STDBY_N CCI_REG8(0x332) -#define MAX96714_MIPI_STDBY_MASK GENMASK(5, 4) -#define MAX96714_MIPI_LANE_MAP CCI_REG8(0x333) -#define MAX96714_MIPI_POLARITY CCI_REG8(0x335) -#define MAX96714_MIPI_POLARITY_MASK GENMASK(5, 0) - -/* MIPI_TX */ -#define MAX96714_MIPI_LANE_CNT CCI_REG8(0x44a) -#define MAX96714_CSI2_LANE_CNT_MASK GENMASK(7, 6) -#define MAX96714_MIPI_TX52 CCI_REG8(0x474) -#define MAX96714_TUN_EN BIT(0) - -#define MHZ(v) ((u32)((v) * 1000000U)) - -enum max96714_vpg_mode { - MAX96714_VPG_DISABLED = 0, - MAX96714_VPG_CHECKERBOARD = 1, - MAX96714_VPG_GRADIENT = 2, -}; - -struct max96714_rxport { - struct { - struct v4l2_subdev *sd; - u16 pad; - struct fwnode_handle *ep_fwnode; - } source; - struct regulator *poc; -}; - -struct max96714_txport { - struct v4l2_fwnode_endpoint vep; -}; - -struct max96714_priv { - struct i2c_client *client; - struct regmap *regmap; - struct gpio_desc *pd_gpio; - struct max96714_rxport rxport; - struct i2c_mux_core *mux; - u64 enabled_source_streams; - struct v4l2_subdev sd; - struct media_pad pads[MAX96714_NPORTS]; - struct v4l2_mbus_config_mipi_csi2 mipi_csi2; - struct v4l2_ctrl_handler ctrl_handler; - struct v4l2_async_notifier notifier; - s64 tx_link_freq; - enum max96714_vpg_mode pattern; -}; - -static inline struct max96714_priv *sd_to_max96714(struct v4l2_subdev *sd) -{ - return container_of(sd, struct max96714_priv, sd); -} - -static int max96714_enable_tx_port(struct max96714_priv *priv) -{ - return cci_update_bits(priv->regmap, MAX96714_MIPI_STDBY_N, - MAX96714_MIPI_STDBY_MASK, - MAX96714_MIPI_STDBY_MASK, NULL); -} - -static int max96714_disable_tx_port(struct max96714_priv *priv) -{ - return cci_update_bits(priv->regmap, MAX96714_MIPI_STDBY_N, - MAX96714_MIPI_STDBY_MASK, 0, NULL); -} - -static bool max96714_tx_port_enabled(struct max96714_priv *priv) -{ - u64 val; - - cci_read(priv->regmap, MAX96714_MIPI_STDBY_N, &val, NULL); - - return val & MAX96714_MIPI_STDBY_MASK; -} - -static int max96714_apply_patgen_timing(struct max96714_priv *priv, - struct v4l2_subdev_state *state) -{ - struct v4l2_mbus_framefmt *fmt = - v4l2_subdev_state_get_format(state, MAX96714_PAD_SOURCE); - const u32 h_active = fmt->width; - const u32 h_fp = 88; - const u32 h_sw = 44; - const u32 h_bp = 148; - u32 h_tot; - const u32 v_active = fmt->height; - const u32 v_fp = 4; - const u32 v_sw = 5; - const u32 v_bp = 36; - u32 v_tot; - int ret = 0; - - h_tot = h_active + h_fp + h_sw + h_bp; - v_tot = v_active + v_fp + v_sw + v_bp; - - /* 75 Mhz pixel clock */ - cci_update_bits(priv->regmap, MAX96714_IO_CHK0, - MAX96714_PATTERN_CLK_FREQ, 1, &ret); - - dev_info(&priv->client->dev, "height: %d width: %d\n", fmt->height, - fmt->width); - - cci_write(priv->regmap, MAX96714_PATGEN_VS_DLY, 0, &ret); - cci_write(priv->regmap, MAX96714_PATGEN_VS_HIGH, v_sw * h_tot, &ret); - cci_write(priv->regmap, MAX96714_PATGEN_VS_LOW, - (v_active + v_fp + v_bp) * h_tot, &ret); - cci_write(priv->regmap, MAX96714_PATGEN_HS_HIGH, h_sw, &ret); - cci_write(priv->regmap, MAX96714_PATGEN_HS_LOW, h_active + h_fp + h_bp, - &ret); - cci_write(priv->regmap, MAX96714_PATGEN_V2D, - h_tot * (v_sw + v_bp) + (h_sw + h_bp), &ret); - cci_write(priv->regmap, MAX96714_PATGEN_HS_CNT, v_tot, &ret); - cci_write(priv->regmap, MAX96714_PATGEN_DE_HIGH, h_active, &ret); - cci_write(priv->regmap, MAX96714_PATGEN_DE_LOW, h_fp + h_sw + h_bp, - &ret); - cci_write(priv->regmap, MAX96714_PATGEN_DE_CNT, v_active, &ret); - /* B G R */ - cci_write(priv->regmap, MAX96714_PATGEN_CHKB_COLOR_A, 0xfecc00, &ret); - /* B G R */ - cci_write(priv->regmap, MAX96714_PATGEN_CHKB_COLOR_B, 0x006aa7, &ret); - cci_write(priv->regmap, MAX96714_PATGEN_CHKB_RPT_CNT_A, 0x3c, &ret); - cci_write(priv->regmap, MAX96714_PATGEN_CHKB_RPT_CNT_B, 0x3c, &ret); - cci_write(priv->regmap, MAX96714_PATGEN_CHKB_ALT, 0x3c, &ret); - cci_write(priv->regmap, MAX96714_PATGEN_GRAD_INC, 0x10, &ret); - - return ret; -} - -static int max96714_apply_patgen(struct max96714_priv *priv, - struct v4l2_subdev_state *state) -{ - unsigned int val; - int ret = 0; - - if (priv->pattern) - ret = max96714_apply_patgen_timing(priv, state); - - cci_write(priv->regmap, MAX96714_PATGEN_0, priv->pattern ? 0xfb : 0, - &ret); - - val = FIELD_PREP(MAX96714_PATGEN_MODE, priv->pattern); - cci_update_bits(priv->regmap, MAX96714_PATGEN_1, MAX96714_PATGEN_MODE, - val, &ret); - return ret; -} - -static int max96714_s_ctrl(struct v4l2_ctrl *ctrl) -{ - struct max96714_priv *priv = - container_of(ctrl->handler, struct max96714_priv, ctrl_handler); - int ret; - - switch (ctrl->id) { - case V4L2_CID_TEST_PATTERN: - if (priv->enabled_source_streams) - return -EBUSY; - priv->pattern = ctrl->val; - break; - default: - return -EINVAL; - } - - ret = cci_update_bits(priv->regmap, MAX96714_MIPI_PHY0, - MAX96714_FORCE_CSI_OUT, - priv->pattern ? MAX96714_FORCE_CSI_OUT : 0, NULL); - - /* Pattern generator doesn't work with tunnel mode */ - return cci_update_bits(priv->regmap, MAX96714_MIPI_TX52, - MAX96714_TUN_EN, - priv->pattern ? 0 : MAX96714_TUN_EN, &ret); -} - -static const char * const max96714_test_pattern[] = { - "Disabled", - "Checkerboard", - "Gradient" -}; - -static const struct v4l2_ctrl_ops max96714_ctrl_ops = { - .s_ctrl = max96714_s_ctrl, -}; - -static int max96714_enable_streams(struct v4l2_subdev *sd, - struct v4l2_subdev_state *state, - u32 source_pad, u64 streams_mask) -{ - struct max96714_priv *priv = sd_to_max96714(sd); - u64 sink_streams; - int ret; - - if (!priv->enabled_source_streams) - max96714_enable_tx_port(priv); - - ret = max96714_apply_patgen(priv, state); - if (ret) - goto err; - - if (!priv->pattern) { - if (!priv->rxport.source.sd) { - ret = -ENODEV; - goto err; - } - - sink_streams = - v4l2_subdev_state_xlate_streams(state, - MAX96714_PAD_SOURCE, - MAX96714_PAD_SINK, - &streams_mask); - - ret = v4l2_subdev_enable_streams(priv->rxport.source.sd, - priv->rxport.source.pad, - sink_streams); - if (ret) - goto err; - } - - priv->enabled_source_streams |= streams_mask; - - return 0; - -err: - if (!priv->enabled_source_streams) - max96714_disable_tx_port(priv); - - return ret; -} - -static int max96714_disable_streams(struct v4l2_subdev *sd, - struct v4l2_subdev_state *state, - u32 source_pad, u64 streams_mask) -{ - struct max96714_priv *priv = sd_to_max96714(sd); - u64 sink_streams; - - if (!priv->pattern) { - int ret; - - sink_streams = - v4l2_subdev_state_xlate_streams(state, - MAX96714_PAD_SOURCE, - MAX96714_PAD_SINK, - &streams_mask); - - ret = v4l2_subdev_disable_streams(priv->rxport.source.sd, - priv->rxport.source.pad, - sink_streams); - if (ret) - return ret; - } - - priv->enabled_source_streams &= ~streams_mask; - - if (!priv->enabled_source_streams) - max96714_disable_tx_port(priv); - - return 0; -} - -static int max96714_set_fmt(struct v4l2_subdev *sd, - struct v4l2_subdev_state *state, - struct v4l2_subdev_format *format) -{ - struct max96714_priv *priv = sd_to_max96714(sd); - struct v4l2_mbus_framefmt *fmt; - - if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && - priv->enabled_source_streams) - return -EBUSY; - - /* No transcoding, source and sink formats must match. */ - if (format->pad == MAX96714_PAD_SOURCE) - return v4l2_subdev_get_fmt(sd, state, format); - - fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream); - if (!fmt) - return -EINVAL; - - *fmt = format->format; - - fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad, - format->stream); - if (!fmt) - return -EINVAL; - - *fmt = format->format; - - return 0; -} - -static int _max96714_set_routing(struct v4l2_subdev *sd, - struct v4l2_subdev_state *state, - enum v4l2_subdev_format_whence which, - struct v4l2_subdev_krouting *routing) -{ - static const struct v4l2_mbus_framefmt format = { - .width = 1280, - .height = 1080, - .code = MEDIA_BUS_FMT_Y8_1X8, - .field = V4L2_FIELD_NONE, - }; - int ret; - - ret = v4l2_subdev_routing_validate(sd, routing, - V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); - if (ret) - return ret; - - return v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format); -} - -static int max96714_set_routing(struct v4l2_subdev *sd, - struct v4l2_subdev_state *state, - enum v4l2_subdev_format_whence which, - struct v4l2_subdev_krouting *routing) -{ - struct max96714_priv *priv = sd_to_max96714(sd); - - if (which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->enabled_source_streams) - return -EBUSY; - - return _max96714_set_routing(sd, state, which, routing); -} - -static int max96714_init_state(struct v4l2_subdev *sd, - struct v4l2_subdev_state *state) -{ - struct v4l2_subdev_route routes[] = { - { - .sink_pad = MAX96714_PAD_SINK, - .sink_stream = 0, - .source_pad = MAX96714_PAD_SOURCE, - .source_stream = 0, - .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, - } - }; - struct v4l2_subdev_krouting routing = { - .num_routes = ARRAY_SIZE(routes), - .routes = routes, - }; - - return _max96714_set_routing(sd, state, V4L2_SUBDEV_FORMAT_ACTIVE, - &routing); -} - -static const struct v4l2_subdev_pad_ops max96714_pad_ops = { - .enable_streams = max96714_enable_streams, - .disable_streams = max96714_disable_streams, - - .set_routing = max96714_set_routing, - .get_fmt = v4l2_subdev_get_fmt, - .set_fmt = max96714_set_fmt, -}; - -static bool max96714_link_locked(struct max96714_priv *priv) -{ - u64 val = 0; - - cci_read(priv->regmap, MAX96714_LINK_LOCK, &val, NULL); - - return val & MAX96714_LINK_LOCK_BIT; -} - -static void max96714_link_status(struct max96714_priv *priv) -{ - struct device *dev = &priv->client->dev; - - dev_info(dev, "Link locked:%d\n", max96714_link_locked(priv)); -} - -static bool max96714_pipe_locked(struct max96714_priv *priv) -{ - u64 val; - - cci_read(priv->regmap, MAX96714_VIDEO_RX8, &val, NULL); - - return val & MAX96714_VID_LOCK; -} - -static void max96714_pipe_status(struct max96714_priv *priv) -{ - struct device *dev = &priv->client->dev; - - dev_info(dev, "Pipe vidlock:%d\n", max96714_pipe_locked(priv)); -} - -static void max96714_csi_status(struct max96714_priv *priv) -{ - struct device *dev = &priv->client->dev; - u64 freq = 0; - - cci_read(priv->regmap, MAX96714_BACKTOP25, &freq, NULL); - freq = FIELD_GET(CSI_DPLL_FREQ_MASK, freq); - - dev_info(dev, "CSI controller DPLL freq:%u00MHz CSIPHY enabled:%d\n", - (u8)freq, max96714_tx_port_enabled(priv)); -} - -static int max96714_log_status(struct v4l2_subdev *sd) -{ - struct max96714_priv *priv = sd_to_max96714(sd); - struct device *dev = &priv->client->dev; - - dev_info(dev, "Deserializer: max96714\n"); - - max96714_link_status(priv); - max96714_pipe_status(priv); - max96714_csi_status(priv); - - return 0; -} - -static const struct v4l2_subdev_core_ops max96714_subdev_core_ops = { - .log_status = max96714_log_status, -}; - -static const struct v4l2_subdev_video_ops max96714_video_ops = { - .s_stream = v4l2_subdev_s_stream_helper, -}; - -static const struct v4l2_subdev_internal_ops max96714_internal_ops = { - .init_state = max96714_init_state, -}; - -static const struct v4l2_subdev_ops max96714_subdev_ops = { - .video = &max96714_video_ops, - .core = &max96714_subdev_core_ops, - .pad = &max96714_pad_ops, -}; - -static const struct media_entity_operations max96714_entity_ops = { - .link_validate = v4l2_subdev_link_validate, -}; - -static int max96714_notify_bound(struct v4l2_async_notifier *notifier, - struct v4l2_subdev *subdev, - struct v4l2_async_connection *asd) -{ - struct max96714_priv *priv = sd_to_max96714(notifier->sd); - struct device *dev = &priv->client->dev; - int ret; - - ret = media_entity_get_fwnode_pad(&subdev->entity, - priv->rxport.source.ep_fwnode, - MEDIA_PAD_FL_SOURCE); - if (ret < 0) { - dev_err(dev, "Failed to find pad for %s\n", subdev->name); - return ret; - } - - priv->rxport.source.sd = subdev; - priv->rxport.source.pad = ret; - - ret = media_create_pad_link(&priv->rxport.source.sd->entity, - priv->rxport.source.pad, &priv->sd.entity, - MAX96714_PAD_SINK, - MEDIA_LNK_FL_ENABLED | - MEDIA_LNK_FL_IMMUTABLE); - if (ret) { - dev_err(dev, "Unable to link %s:%u -> %s:%u\n", - priv->rxport.source.sd->name, priv->rxport.source.pad, - priv->sd.name, MAX96714_PAD_SINK); - return ret; - } - - return 0; -} - -static const struct v4l2_async_notifier_operations max96714_notify_ops = { - .bound = max96714_notify_bound, -}; - -static int max96714_v4l2_notifier_register(struct max96714_priv *priv) -{ - struct device *dev = &priv->client->dev; - struct max96714_rxport *rxport = &priv->rxport; - struct v4l2_async_connection *asd; - int ret; - - if (!rxport->source.ep_fwnode) - return 0; - - v4l2_async_subdev_nf_init(&priv->notifier, &priv->sd); - - asd = v4l2_async_nf_add_fwnode(&priv->notifier, - rxport->source.ep_fwnode, - struct v4l2_async_connection); - if (IS_ERR(asd)) { - dev_err(dev, "Failed to add subdev: %pe", asd); - v4l2_async_nf_cleanup(&priv->notifier); - return PTR_ERR(asd); - } - - priv->notifier.ops = &max96714_notify_ops; - - ret = v4l2_async_nf_register(&priv->notifier); - if (ret) { - dev_err(dev, "Failed to register subdev_notifier"); - v4l2_async_nf_cleanup(&priv->notifier); - return ret; - } - - return 0; -} - -static int max96714_create_subdev(struct max96714_priv *priv) -{ - struct device *dev = &priv->client->dev; - int ret; - - v4l2_i2c_subdev_init(&priv->sd, priv->client, &max96714_subdev_ops); - priv->sd.internal_ops = &max96714_internal_ops; - - v4l2_ctrl_handler_init(&priv->ctrl_handler, 1); - priv->sd.ctrl_handler = &priv->ctrl_handler; - - v4l2_ctrl_new_int_menu(&priv->ctrl_handler, NULL, V4L2_CID_LINK_FREQ, - 0, 0, &priv->tx_link_freq); - v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler, - &max96714_ctrl_ops, - V4L2_CID_TEST_PATTERN, - ARRAY_SIZE(max96714_test_pattern) - 1, - 0, 0, max96714_test_pattern); - if (priv->ctrl_handler.error) { - ret = priv->ctrl_handler.error; - goto err_free_ctrl; - } - - priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS; - priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; - priv->sd.entity.ops = &max96714_entity_ops; - - priv->pads[MAX96714_PAD_SINK].flags = MEDIA_PAD_FL_SINK; - priv->pads[MAX96714_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; - - ret = media_entity_pads_init(&priv->sd.entity, - MAX96714_NPORTS, - priv->pads); - if (ret) - goto err_free_ctrl; - - priv->sd.state_lock = priv->sd.ctrl_handler->lock; - - ret = v4l2_subdev_init_finalize(&priv->sd); - if (ret) - goto err_entity_cleanup; - - ret = max96714_v4l2_notifier_register(priv); - if (ret) { - dev_err(dev, "v4l2 subdev notifier register failed: %d\n", ret); - goto err_subdev_cleanup; - } - - ret = v4l2_async_register_subdev(&priv->sd); - if (ret) { - dev_err(dev, "v4l2_async_register_subdev error: %d\n", ret); - goto err_unreg_notif; - } - - return 0; - -err_unreg_notif: - v4l2_async_nf_unregister(&priv->notifier); - v4l2_async_nf_cleanup(&priv->notifier); -err_subdev_cleanup: - v4l2_subdev_cleanup(&priv->sd); -err_entity_cleanup: - media_entity_cleanup(&priv->sd.entity); -err_free_ctrl: - v4l2_ctrl_handler_free(&priv->ctrl_handler); - - return ret; -}; - -static void max96714_destroy_subdev(struct max96714_priv *priv) -{ - v4l2_async_nf_unregister(&priv->notifier); - v4l2_async_nf_cleanup(&priv->notifier); - v4l2_async_unregister_subdev(&priv->sd); - - v4l2_subdev_cleanup(&priv->sd); - - media_entity_cleanup(&priv->sd.entity); - v4l2_ctrl_handler_free(&priv->ctrl_handler); -} - -static int max96714_i2c_mux_select(struct i2c_mux_core *mux, u32 chan) -{ - return 0; -} - -static int max96714_i2c_mux_init(struct max96714_priv *priv) -{ - priv->mux = i2c_mux_alloc(priv->client->adapter, &priv->client->dev, - 1, 0, I2C_MUX_LOCKED | I2C_MUX_GATE, - max96714_i2c_mux_select, NULL); - if (!priv->mux) - return -ENOMEM; - - return i2c_mux_add_adapter(priv->mux, 0, 0); -} - -static int max96714_init_tx_port(struct max96714_priv *priv) -{ - struct v4l2_mbus_config_mipi_csi2 *mipi; - unsigned long lanes_used = 0; - unsigned int val, lane; - int ret; - - ret = max96714_disable_tx_port(priv); - - mipi = &priv->mipi_csi2; - val = div_u64(priv->tx_link_freq * 2, MHZ(100)); - - cci_update_bits(priv->regmap, MAX96714_BACKTOP25, - CSI_DPLL_FREQ_MASK, val, &ret); - - val = FIELD_PREP(MAX96714_CSI2_LANE_CNT_MASK, mipi->num_data_lanes - 1); - cci_update_bits(priv->regmap, MAX96714_MIPI_LANE_CNT, - MAX96714_CSI2_LANE_CNT_MASK, val, &ret); - - /* lanes polarity */ - val = 0; - for (lane = 0; lane < mipi->num_data_lanes + 1; lane++) { - if (!mipi->lane_polarities[lane]) - continue; - if (lane == 0) - /* clock lane */ - val |= BIT(5); - else if (lane < 3) - /* Lane D0 and D1 */ - val |= BIT(lane - 1); - else - /* D2 and D3 */ - val |= BIT(lane); - } - - cci_update_bits(priv->regmap, MAX96714_MIPI_POLARITY, - MAX96714_MIPI_POLARITY_MASK, val, &ret); - - /* lanes mapping */ - val = 0; - for (lane = 0; lane < mipi->num_data_lanes; lane++) { - val |= (mipi->data_lanes[lane] - 1) << (lane * 2); - lanes_used |= BIT(mipi->data_lanes[lane] - 1); - } - - /* - * Unused lanes need to be mapped as well to not have - * the same lanes mapped twice. - */ - for (; lane < MAX96714_CSI_NLANES; lane++) { - unsigned int idx = find_first_zero_bit(&lanes_used, - MAX96714_CSI_NLANES); - - val |= idx << (lane * 2); - lanes_used |= BIT(idx); - } - - return cci_write(priv->regmap, MAX96714_MIPI_LANE_MAP, val, &ret); -} - -static int max96714_rxport_enable_poc(struct max96714_priv *priv) -{ - struct max96714_rxport *rxport = &priv->rxport; - - if (!rxport->poc) - return 0; - - return regulator_enable(rxport->poc); -} - -static int max96714_rxport_disable_poc(struct max96714_priv *priv) -{ - struct max96714_rxport *rxport = &priv->rxport; - - if (!rxport->poc) - return 0; - - return regulator_disable(rxport->poc); -} - -static int max96714_parse_dt_txport(struct max96714_priv *priv) -{ - struct device *dev = &priv->client->dev; - struct v4l2_fwnode_endpoint vep = { .bus_type = V4L2_MBUS_CSI2_DPHY }; - struct fwnode_handle *ep_fwnode; - u32 num_data_lanes; - int ret; - - ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), - MAX96714_PAD_SOURCE, 0, 0); - if (!ep_fwnode) - return -EINVAL; - - ret = v4l2_fwnode_endpoint_alloc_parse(ep_fwnode, &vep); - fwnode_handle_put(ep_fwnode); - if (ret) { - dev_err(dev, "tx: failed to parse endpoint data\n"); - return -EINVAL; - } - - if (vep.nr_of_link_frequencies != 1) { - ret = -EINVAL; - goto err_free_vep; - } - - priv->tx_link_freq = vep.link_frequencies[0]; - /* Min 50MHz, Max 1250MHz, 50MHz step */ - if (priv->tx_link_freq < MHZ(50) || priv->tx_link_freq > MHZ(1250) || - (u32)priv->tx_link_freq % MHZ(50)) { - dev_err(dev, "tx: invalid link frequency\n"); - ret = -EINVAL; - goto err_free_vep; - } - - num_data_lanes = vep.bus.mipi_csi2.num_data_lanes; - if (num_data_lanes < 1 || num_data_lanes > MAX96714_CSI_NLANES) { - dev_err(dev, - "tx: invalid number of data lanes must be 1 to 4\n"); - ret = -EINVAL; - goto err_free_vep; - } - - priv->mipi_csi2 = vep.bus.mipi_csi2; - -err_free_vep: - v4l2_fwnode_endpoint_free(&vep); - - return ret; -} - -static int max96714_parse_dt_rxport(struct max96714_priv *priv) -{ - static const char *poc_name = "port0-poc"; - struct max96714_rxport *rxport = &priv->rxport; - struct device *dev = &priv->client->dev; - struct fwnode_handle *ep_fwnode; - int ret; - - ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), - MAX96714_PAD_SINK, 0, 0); - if (!ep_fwnode) - return -ENOENT; - - rxport->source.ep_fwnode = fwnode_graph_get_remote_endpoint(ep_fwnode); - fwnode_handle_put(ep_fwnode); - - if (!rxport->source.ep_fwnode) { - dev_err(dev, "rx: no remote endpoint\n"); - return -EINVAL; - } - - rxport->poc = devm_regulator_get_optional(dev, poc_name); - if (IS_ERR(rxport->poc)) { - ret = PTR_ERR(rxport->poc); - if (ret == -ENODEV) { - rxport->poc = NULL; - } else { - dev_err(dev, "rx: failed to get POC supply: %d\n", ret); - goto err_put_source_ep_fwnode; - } - } - - return 0; - -err_put_source_ep_fwnode: - fwnode_handle_put(rxport->source.ep_fwnode); - return ret; -} - -static int max96714_parse_dt(struct max96714_priv *priv) -{ - int ret; - - ret = max96714_parse_dt_txport(priv); - if (ret) - return ret; - - ret = max96714_parse_dt_rxport(priv); - /* - * The deserializer can create a test pattern even if the - * rx port is not connected to a serializer. - */ - if (ret && ret == -ENOENT) - ret = 0; - - return ret; -} - -static int max96714_enable_core_hw(struct max96714_priv *priv) -{ - struct device *dev = &priv->client->dev; - u64 val; - int ret; - - if (priv->pd_gpio) { - /* wait min 2 ms for reset to complete */ - gpiod_set_value_cansleep(priv->pd_gpio, 1); - fsleep(2000); - gpiod_set_value_cansleep(priv->pd_gpio, 0); - /* wait min 2 ms for power up to finish */ - fsleep(2000); - } - - ret = cci_read(priv->regmap, MAX96714_REG13, &val, NULL); - if (ret) { - dev_err_probe(dev, ret, "Cannot read first register, abort\n"); - goto err_pd_gpio; - } - - if (val != MAX96714_DEVICE_ID && val != MAX96714F_DEVICE_ID) { - dev_err(dev, "Unsupported device id expected %x got %x\n", - MAX96714F_DEVICE_ID, (u8)val); - ret = -EOPNOTSUPP; - goto err_pd_gpio; - } - - ret = cci_read(priv->regmap, MAX96714_DEV_REV, &val, NULL); - if (ret) - goto err_pd_gpio; - - dev_dbg(dev, "Found %x (rev %lx)\n", MAX96714F_DEVICE_ID, - (u8)val & MAX96714_DEV_REV_MASK); - - ret = cci_read(priv->regmap, MAX96714_MIPI_TX52, &val, NULL); - if (ret) - goto err_pd_gpio; - - if (!(val & MAX96714_TUN_EN)) { - dev_err(dev, "Only supporting tunnel mode"); - ret = -EOPNOTSUPP; - goto err_pd_gpio; - } - - return 0; - -err_pd_gpio: - gpiod_set_value_cansleep(priv->pd_gpio, 1); - return ret; -} - -static void max96714_disable_core_hw(struct max96714_priv *priv) -{ - gpiod_set_value_cansleep(priv->pd_gpio, 1); -} - -static int max96714_get_hw_resources(struct max96714_priv *priv) -{ - struct device *dev = &priv->client->dev; - - priv->regmap = devm_cci_regmap_init_i2c(priv->client, 16); - if (IS_ERR(priv->regmap)) - return PTR_ERR(priv->regmap); - - priv->pd_gpio = - devm_gpiod_get_optional(dev, "powerdown", GPIOD_OUT_HIGH); - if (IS_ERR(priv->pd_gpio)) - return dev_err_probe(dev, PTR_ERR(priv->pd_gpio), - "Cannot get powerdown GPIO\n"); - return 0; -} - -static int max96714_probe(struct i2c_client *client) -{ - struct device *dev = &client->dev; - struct max96714_priv *priv; - int ret; - - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - priv->client = client; - - ret = max96714_get_hw_resources(priv); - if (ret) - return ret; - - ret = max96714_enable_core_hw(priv); - if (ret) - return ret; - - ret = max96714_parse_dt(priv); - if (ret) - goto err_disable_core_hw; - - max96714_init_tx_port(priv); - - ret = max96714_rxport_enable_poc(priv); - if (ret) - goto err_free_ports; - - ret = max96714_i2c_mux_init(priv); - if (ret) - goto err_disable_poc; - - ret = max96714_create_subdev(priv); - if (ret) - goto err_del_mux; - - return 0; - -err_del_mux: - i2c_mux_del_adapters(priv->mux); -err_disable_poc: - max96714_rxport_disable_poc(priv); -err_free_ports: - fwnode_handle_put(priv->rxport.source.ep_fwnode); -err_disable_core_hw: - max96714_disable_core_hw(priv); - - return ret; -} - -static void max96714_remove(struct i2c_client *client) -{ - struct v4l2_subdev *sd = i2c_get_clientdata(client); - struct max96714_priv *priv = sd_to_max96714(sd); - - max96714_destroy_subdev(priv); - i2c_mux_del_adapters(priv->mux); - max96714_rxport_disable_poc(priv); - fwnode_handle_put(priv->rxport.source.ep_fwnode); - max96714_disable_core_hw(priv); - gpiod_set_value_cansleep(priv->pd_gpio, 1); -} - -static const struct of_device_id max96714_of_ids[] = { - { .compatible = "maxim,max96714f" }, - { } -}; -MODULE_DEVICE_TABLE(of, max96714_of_ids); - -static struct i2c_driver max96714_i2c_driver = { - .driver = { - .name = "max96714", - .of_match_table = max96714_of_ids, - }, - .probe = max96714_probe, - .remove = max96714_remove, -}; - -module_i2c_driver(max96714_i2c_driver); - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("Maxim Integrated GMSL2 Deserializers Driver"); -MODULE_AUTHOR("Julien Massot "); diff --git a/drivers/media/i2c/max96717.c b/drivers/media/i2c/max96717.c deleted file mode 100644 index 72f021b1a7b9..000000000000 --- a/drivers/media/i2c/max96717.c +++ /dev/null @@ -1,1104 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Maxim GMSL2 Serializer Driver - * - * Copyright (C) 2024 Collabora Ltd. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#define MAX96717_DEVICE_ID 0xbf -#define MAX96717F_DEVICE_ID 0xc8 -#define MAX96717_PORTS 2 -#define MAX96717_PAD_SINK 0 -#define MAX96717_PAD_SOURCE 1 -#define MAX96717_CSI_NLANES 4 - -#define MAX96717_DEFAULT_CLKOUT_RATE 24000000UL - -/* DEV */ -#define MAX96717_REG3 CCI_REG8(0x3) -#define MAX96717_RCLKSEL GENMASK(1, 0) -#define RCLKSEL_REF_PLL CCI_REG8(0x3) -#define MAX96717_REG6 CCI_REG8(0x6) -#define RCLKEN BIT(5) -#define MAX96717_DEV_ID CCI_REG8(0xd) -#define MAX96717_DEV_REV CCI_REG8(0xe) -#define MAX96717_DEV_REV_MASK GENMASK(3, 0) - -/* VID_TX Z */ -#define MAX96717_VIDEO_TX0 CCI_REG8(0x110) -#define MAX96717_VIDEO_AUTO_BPP BIT(3) -#define MAX96717_VIDEO_TX2 CCI_REG8(0x112) -#define MAX96717_VIDEO_PCLKDET BIT(7) - -/* VTX_Z */ -#define MAX96717_VTX0 CCI_REG8(0x24e) -#define MAX96717_VTX1 CCI_REG8(0x24f) -#define MAX96717_PATTERN_CLK_FREQ GENMASK(3, 1) -#define MAX96717_VTX_VS_DLY CCI_REG24(0x250) -#define MAX96717_VTX_VS_HIGH CCI_REG24(0x253) -#define MAX96717_VTX_VS_LOW CCI_REG24(0x256) -#define MAX96717_VTX_V2H CCI_REG24(0x259) -#define MAX96717_VTX_HS_HIGH CCI_REG16(0x25c) -#define MAX96717_VTX_HS_LOW CCI_REG16(0x25e) -#define MAX96717_VTX_HS_CNT CCI_REG16(0x260) -#define MAX96717_VTX_V2D CCI_REG24(0x262) -#define MAX96717_VTX_DE_HIGH CCI_REG16(0x265) -#define MAX96717_VTX_DE_LOW CCI_REG16(0x267) -#define MAX96717_VTX_DE_CNT CCI_REG16(0x269) -#define MAX96717_VTX29 CCI_REG8(0x26b) -#define MAX96717_VTX_MODE GENMASK(1, 0) -#define MAX96717_VTX_GRAD_INC CCI_REG8(0x26c) -#define MAX96717_VTX_CHKB_COLOR_A CCI_REG24(0x26d) -#define MAX96717_VTX_CHKB_COLOR_B CCI_REG24(0x270) -#define MAX96717_VTX_CHKB_RPT_CNT_A CCI_REG8(0x273) -#define MAX96717_VTX_CHKB_RPT_CNT_B CCI_REG8(0x274) -#define MAX96717_VTX_CHKB_ALT CCI_REG8(0x275) - -/* GPIO */ -#define MAX96717_NUM_GPIO 11 -#define MAX96717_GPIO_REG_A(gpio) CCI_REG8(0x2be + (gpio) * 3) -#define MAX96717_GPIO_OUT BIT(4) -#define MAX96717_GPIO_IN BIT(3) -#define MAX96717_GPIO_RX_EN BIT(2) -#define MAX96717_GPIO_TX_EN BIT(1) -#define MAX96717_GPIO_OUT_DIS BIT(0) - -/* FRONTTOP */ -/* MAX96717 only have CSI port 'B' */ -#define MAX96717_FRONTOP0 CCI_REG8(0x308) -#define MAX96717_START_PORT_B BIT(5) - -/* MIPI_RX */ -#define MAX96717_MIPI_RX1 CCI_REG8(0x331) -#define MAX96717_MIPI_LANES_CNT GENMASK(5, 4) -#define MAX96717_MIPI_RX2 CCI_REG8(0x332) /* phy1 Lanes map */ -#define MAX96717_PHY2_LANES_MAP GENMASK(7, 4) -#define MAX96717_MIPI_RX3 CCI_REG8(0x333) /* phy2 Lanes map */ -#define MAX96717_PHY1_LANES_MAP GENMASK(3, 0) -#define MAX96717_MIPI_RX4 CCI_REG8(0x334) /* phy1 lane polarities */ -#define MAX96717_PHY1_LANES_POL GENMASK(6, 4) -#define MAX96717_MIPI_RX5 CCI_REG8(0x335) /* phy2 lane polarities */ -#define MAX96717_PHY2_LANES_POL GENMASK(2, 0) - -/* MIPI_RX_EXT */ -#define MAX96717_MIPI_RX_EXT11 CCI_REG8(0x383) -#define MAX96717_TUN_MODE BIT(7) - -/* REF_VTG */ -#define REF_VTG0 CCI_REG8(0x3f0) -#define REFGEN_PREDEF_EN BIT(6) -#define REFGEN_PREDEF_FREQ_MASK GENMASK(5, 4) -#define REFGEN_PREDEF_FREQ_ALT BIT(3) -#define REFGEN_RST BIT(1) -#define REFGEN_EN BIT(0) - -/* MISC */ -#define PIO_SLEW_1 CCI_REG8(0x570) - -enum max96717_vpg_mode { - MAX96717_VPG_DISABLED = 0, - MAX96717_VPG_CHECKERBOARD = 1, - MAX96717_VPG_GRADIENT = 2, -}; - -struct max96717_priv { - struct i2c_client *client; - struct regmap *regmap; - struct i2c_mux_core *mux; - struct v4l2_mbus_config_mipi_csi2 mipi_csi2; - struct v4l2_subdev sd; - struct media_pad pads[MAX96717_PORTS]; - struct v4l2_ctrl_handler ctrl_handler; - struct v4l2_async_notifier notifier; - struct v4l2_subdev *source_sd; - u16 source_sd_pad; - u64 enabled_source_streams; - u8 pll_predef_index; - struct clk_hw clk_hw; - struct gpio_chip gpio_chip; - enum max96717_vpg_mode pattern; -}; - -static inline struct max96717_priv *sd_to_max96717(struct v4l2_subdev *sd) -{ - return container_of(sd, struct max96717_priv, sd); -} - -static inline struct max96717_priv *clk_hw_to_max96717(struct clk_hw *hw) -{ - return container_of(hw, struct max96717_priv, clk_hw); -} - -static int max96717_i2c_mux_select(struct i2c_mux_core *mux, u32 chan) -{ - return 0; -} - -static int max96717_i2c_mux_init(struct max96717_priv *priv) -{ - priv->mux = i2c_mux_alloc(priv->client->adapter, &priv->client->dev, - 1, 0, I2C_MUX_LOCKED | I2C_MUX_GATE, - max96717_i2c_mux_select, NULL); - if (!priv->mux) - return -ENOMEM; - - return i2c_mux_add_adapter(priv->mux, 0, 0); -} - -static inline int max96717_start_csi(struct max96717_priv *priv, bool start) -{ - return cci_update_bits(priv->regmap, MAX96717_FRONTOP0, - MAX96717_START_PORT_B, - start ? MAX96717_START_PORT_B : 0, NULL); -} - -static int max96717_apply_patgen_timing(struct max96717_priv *priv, - struct v4l2_subdev_state *state) -{ - struct v4l2_mbus_framefmt *fmt = - v4l2_subdev_state_get_format(state, MAX96717_PAD_SOURCE); - const u32 h_active = fmt->width; - const u32 h_fp = 88; - const u32 h_sw = 44; - const u32 h_bp = 148; - u32 h_tot; - const u32 v_active = fmt->height; - const u32 v_fp = 4; - const u32 v_sw = 5; - const u32 v_bp = 36; - u32 v_tot; - int ret = 0; - - h_tot = h_active + h_fp + h_sw + h_bp; - v_tot = v_active + v_fp + v_sw + v_bp; - - /* 75 Mhz pixel clock */ - cci_update_bits(priv->regmap, MAX96717_VTX1, - MAX96717_PATTERN_CLK_FREQ, 0xa, &ret); - - dev_info(&priv->client->dev, "height: %d width: %d\n", fmt->height, - fmt->width); - - cci_write(priv->regmap, MAX96717_VTX_VS_DLY, 0, &ret); - cci_write(priv->regmap, MAX96717_VTX_VS_HIGH, v_sw * h_tot, &ret); - cci_write(priv->regmap, MAX96717_VTX_VS_LOW, - (v_active + v_fp + v_bp) * h_tot, &ret); - cci_write(priv->regmap, MAX96717_VTX_HS_HIGH, h_sw, &ret); - cci_write(priv->regmap, MAX96717_VTX_HS_LOW, h_active + h_fp + h_bp, - &ret); - cci_write(priv->regmap, MAX96717_VTX_V2D, - h_tot * (v_sw + v_bp) + (h_sw + h_bp), &ret); - cci_write(priv->regmap, MAX96717_VTX_HS_CNT, v_tot, &ret); - cci_write(priv->regmap, MAX96717_VTX_DE_HIGH, h_active, &ret); - cci_write(priv->regmap, MAX96717_VTX_DE_LOW, h_fp + h_sw + h_bp, - &ret); - cci_write(priv->regmap, MAX96717_VTX_DE_CNT, v_active, &ret); - /* B G R */ - cci_write(priv->regmap, MAX96717_VTX_CHKB_COLOR_A, 0xfecc00, &ret); - /* B G R */ - cci_write(priv->regmap, MAX96717_VTX_CHKB_COLOR_B, 0x006aa7, &ret); - cci_write(priv->regmap, MAX96717_VTX_CHKB_RPT_CNT_A, 0x3c, &ret); - cci_write(priv->regmap, MAX96717_VTX_CHKB_RPT_CNT_B, 0x3c, &ret); - cci_write(priv->regmap, MAX96717_VTX_CHKB_ALT, 0x3c, &ret); - cci_write(priv->regmap, MAX96717_VTX_GRAD_INC, 0x10, &ret); - - return ret; -} - -static int max96717_apply_patgen(struct max96717_priv *priv, - struct v4l2_subdev_state *state) -{ - unsigned int val; - int ret = 0; - - if (priv->pattern) - ret = max96717_apply_patgen_timing(priv, state); - - cci_write(priv->regmap, MAX96717_VTX0, priv->pattern ? 0xfb : 0, - &ret); - - val = FIELD_PREP(MAX96717_VTX_MODE, priv->pattern); - cci_update_bits(priv->regmap, MAX96717_VTX29, MAX96717_VTX_MODE, - val, &ret); - return ret; -} - -static int max96717_s_ctrl(struct v4l2_ctrl *ctrl) -{ - struct max96717_priv *priv = - container_of(ctrl->handler, struct max96717_priv, ctrl_handler); - int ret; - - switch (ctrl->id) { - case V4L2_CID_TEST_PATTERN: - if (priv->enabled_source_streams) - return -EBUSY; - priv->pattern = ctrl->val; - break; - default: - return -EINVAL; - } - - /* Use bpp from bpp register */ - ret = cci_update_bits(priv->regmap, MAX96717_VIDEO_TX0, - MAX96717_VIDEO_AUTO_BPP, - priv->pattern ? 0 : MAX96717_VIDEO_AUTO_BPP, - NULL); - - /* - * Pattern generator doesn't work with tunnel mode. - * Needs RGB color format and deserializer tunnel mode must be disabled. - */ - return cci_update_bits(priv->regmap, MAX96717_MIPI_RX_EXT11, - MAX96717_TUN_MODE, - priv->pattern ? 0 : MAX96717_TUN_MODE, &ret); -} - -static const char * const max96717_test_pattern[] = { - "Disabled", - "Checkerboard", - "Gradient" -}; - -static const struct v4l2_ctrl_ops max96717_ctrl_ops = { - .s_ctrl = max96717_s_ctrl, -}; - -static int max96717_gpiochip_get(struct gpio_chip *gpiochip, - unsigned int offset) -{ - struct max96717_priv *priv = gpiochip_get_data(gpiochip); - u64 val; - int ret; - - ret = cci_read(priv->regmap, MAX96717_GPIO_REG_A(offset), - &val, NULL); - if (ret) - return ret; - - if (val & MAX96717_GPIO_OUT_DIS) - return !!(val & MAX96717_GPIO_IN); - else - return !!(val & MAX96717_GPIO_OUT); -} - -static int max96717_gpiochip_set(struct gpio_chip *gpiochip, - unsigned int offset, int value) -{ - struct max96717_priv *priv = gpiochip_get_data(gpiochip); - - return cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(offset), - MAX96717_GPIO_OUT, MAX96717_GPIO_OUT, NULL); -} - -static int max96717_gpio_get_direction(struct gpio_chip *gpiochip, - unsigned int offset) -{ - struct max96717_priv *priv = gpiochip_get_data(gpiochip); - u64 val; - int ret; - - ret = cci_read(priv->regmap, MAX96717_GPIO_REG_A(offset), &val, NULL); - if (ret < 0) - return ret; - - return !!(val & MAX96717_GPIO_OUT_DIS); -} - -static int max96717_gpio_direction_out(struct gpio_chip *gpiochip, - unsigned int offset, int value) -{ - struct max96717_priv *priv = gpiochip_get_data(gpiochip); - - return cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(offset), - MAX96717_GPIO_OUT_DIS | MAX96717_GPIO_OUT, - value ? MAX96717_GPIO_OUT : 0, NULL); -} - -static int max96717_gpio_direction_in(struct gpio_chip *gpiochip, - unsigned int offset) -{ - struct max96717_priv *priv = gpiochip_get_data(gpiochip); - - return cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(offset), - MAX96717_GPIO_OUT_DIS, MAX96717_GPIO_OUT_DIS, - NULL); -} - -static int max96717_gpiochip_probe(struct max96717_priv *priv) -{ - struct device *dev = &priv->client->dev; - struct gpio_chip *gc = &priv->gpio_chip; - int i, ret = 0; - - gc->label = dev_name(dev); - gc->parent = dev; - gc->owner = THIS_MODULE; - gc->ngpio = MAX96717_NUM_GPIO; - gc->base = -1; - gc->can_sleep = true; - gc->get_direction = max96717_gpio_get_direction; - gc->direction_input = max96717_gpio_direction_in; - gc->direction_output = max96717_gpio_direction_out; - gc->set = max96717_gpiochip_set; - gc->get = max96717_gpiochip_get; - - /* Disable GPIO forwarding */ - for (i = 0; i < gc->ngpio; i++) - cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(i), - MAX96717_GPIO_RX_EN | MAX96717_GPIO_TX_EN, - 0, &ret); - - if (ret) - return ret; - - ret = devm_gpiochip_add_data(dev, gc, priv); - if (ret) { - dev_err(dev, "Unable to create gpio_chip\n"); - return ret; - } - - return 0; -} - -static int _max96717_set_routing(struct v4l2_subdev *sd, - struct v4l2_subdev_state *state, - struct v4l2_subdev_krouting *routing) -{ - static const struct v4l2_mbus_framefmt format = { - .width = 1280, - .height = 1080, - .code = MEDIA_BUS_FMT_Y8_1X8, - .field = V4L2_FIELD_NONE, - }; - int ret; - - ret = v4l2_subdev_routing_validate(sd, routing, - V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); - if (ret) - return ret; - - ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format); - if (ret) - return ret; - - return 0; -} - -static int max96717_set_routing(struct v4l2_subdev *sd, - struct v4l2_subdev_state *state, - enum v4l2_subdev_format_whence which, - struct v4l2_subdev_krouting *routing) -{ - struct max96717_priv *priv = sd_to_max96717(sd); - - if (which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->enabled_source_streams) - return -EBUSY; - - return _max96717_set_routing(sd, state, routing); -} - -static int max96717_set_fmt(struct v4l2_subdev *sd, - struct v4l2_subdev_state *state, - struct v4l2_subdev_format *format) -{ - struct max96717_priv *priv = sd_to_max96717(sd); - struct v4l2_mbus_framefmt *fmt; - u64 stream_source_mask; - - if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && - priv->enabled_source_streams) - return -EBUSY; - - /* No transcoding, source and sink formats must match. */ - if (format->pad == MAX96717_PAD_SOURCE) - return v4l2_subdev_get_fmt(sd, state, format); - - /* Set sink format */ - fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream); - if (!fmt) - return -EINVAL; - - *fmt = format->format; - - /* Propagate to source format */ - fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad, - format->stream); - if (!fmt) - return -EINVAL; - *fmt = format->format; - - stream_source_mask = BIT(format->stream); - - return v4l2_subdev_state_xlate_streams(state, MAX96717_PAD_SOURCE, - MAX96717_PAD_SINK, - &stream_source_mask); -} - -static int max96717_init_state(struct v4l2_subdev *sd, - struct v4l2_subdev_state *state) -{ - struct v4l2_subdev_route routes[] = { - { - .sink_pad = MAX96717_PAD_SINK, - .sink_stream = 0, - .source_pad = MAX96717_PAD_SOURCE, - .source_stream = 0, - .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, - }, - }; - struct v4l2_subdev_krouting routing = { - .num_routes = ARRAY_SIZE(routes), - .routes = routes, - }; - - return _max96717_set_routing(sd, state, &routing); -} - -static bool max96717_pipe_pclkdet(struct max96717_priv *priv) -{ - u64 val = 0; - - cci_read(priv->regmap, MAX96717_VIDEO_TX2, &val, NULL); - - return val & MAX96717_VIDEO_PCLKDET; -} - -static int max96717_log_status(struct v4l2_subdev *sd) -{ - struct max96717_priv *priv = sd_to_max96717(sd); - struct device *dev = &priv->client->dev; - - dev_info(dev, "Serializer: max96717\n"); - dev_info(dev, "Pipe: pclkdet:%d\n", max96717_pipe_pclkdet(priv)); - - return 0; -} - -static int max96717_enable_streams(struct v4l2_subdev *sd, - struct v4l2_subdev_state *state, u32 pad, - u64 streams_mask) -{ - struct max96717_priv *priv = sd_to_max96717(sd); - u64 sink_streams; - int ret; - - if (!priv->enabled_source_streams) - max96717_start_csi(priv, true); - - ret = max96717_apply_patgen(priv, state); - if (ret) - goto stop_csi; - - if (!priv->pattern) { - sink_streams = - v4l2_subdev_state_xlate_streams(state, - MAX96717_PAD_SOURCE, - MAX96717_PAD_SINK, - &streams_mask); - - ret = v4l2_subdev_enable_streams(priv->source_sd, - priv->source_sd_pad, - sink_streams); - if (ret) - goto stop_csi; - } - - priv->enabled_source_streams |= streams_mask; - - return 0; - -stop_csi: - if (!priv->enabled_source_streams) - max96717_start_csi(priv, false); - - return ret; -} - -static int max96717_disable_streams(struct v4l2_subdev *sd, - struct v4l2_subdev_state *state, u32 pad, - u64 streams_mask) -{ - struct max96717_priv *priv = sd_to_max96717(sd); - u64 sink_streams; - - /* - * Stop the CSI receiver first then the source, - * otherwise the device may become unresponsive - * while holding the I2C bus low. - */ - priv->enabled_source_streams &= ~streams_mask; - if (!priv->enabled_source_streams) - max96717_start_csi(priv, false); - - if (!priv->pattern) { - int ret; - - sink_streams = - v4l2_subdev_state_xlate_streams(state, - MAX96717_PAD_SOURCE, - MAX96717_PAD_SINK, - &streams_mask); - - ret = v4l2_subdev_disable_streams(priv->source_sd, - priv->source_sd_pad, - sink_streams); - if (ret) - return ret; - } - - return 0; -} - -static const struct v4l2_subdev_pad_ops max96717_pad_ops = { - .enable_streams = max96717_enable_streams, - .disable_streams = max96717_disable_streams, - .set_routing = max96717_set_routing, - .get_fmt = v4l2_subdev_get_fmt, - .set_fmt = max96717_set_fmt, -}; - -static const struct v4l2_subdev_core_ops max96717_subdev_core_ops = { - .log_status = max96717_log_status, -}; - -static const struct v4l2_subdev_internal_ops max96717_internal_ops = { - .init_state = max96717_init_state, -}; - -static const struct v4l2_subdev_ops max96717_subdev_ops = { - .core = &max96717_subdev_core_ops, - .pad = &max96717_pad_ops, -}; - -static const struct media_entity_operations max96717_entity_ops = { - .link_validate = v4l2_subdev_link_validate, -}; - -static int max96717_notify_bound(struct v4l2_async_notifier *notifier, - struct v4l2_subdev *source_subdev, - struct v4l2_async_connection *asd) -{ - struct max96717_priv *priv = sd_to_max96717(notifier->sd); - struct device *dev = &priv->client->dev; - int ret; - - ret = media_entity_get_fwnode_pad(&source_subdev->entity, - source_subdev->fwnode, - MEDIA_PAD_FL_SOURCE); - if (ret < 0) { - dev_err(dev, "Failed to find pad for %s\n", - source_subdev->name); - return ret; - } - - priv->source_sd = source_subdev; - priv->source_sd_pad = ret; - - ret = media_create_pad_link(&source_subdev->entity, priv->source_sd_pad, - &priv->sd.entity, 0, - MEDIA_LNK_FL_ENABLED | - MEDIA_LNK_FL_IMMUTABLE); - if (ret) { - dev_err(dev, "Unable to link %s:%u -> %s:0\n", - source_subdev->name, priv->source_sd_pad, - priv->sd.name); - return ret; - } - - return 0; -} - -static const struct v4l2_async_notifier_operations max96717_notify_ops = { - .bound = max96717_notify_bound, -}; - -static int max96717_v4l2_notifier_register(struct max96717_priv *priv) -{ - struct device *dev = &priv->client->dev; - struct v4l2_async_connection *asd; - struct fwnode_handle *ep_fwnode; - int ret; - - ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), - MAX96717_PAD_SINK, 0, 0); - if (!ep_fwnode) { - dev_err(dev, "No graph endpoint\n"); - return -ENODEV; - } - - v4l2_async_subdev_nf_init(&priv->notifier, &priv->sd); - - asd = v4l2_async_nf_add_fwnode_remote(&priv->notifier, ep_fwnode, - struct v4l2_async_connection); - - fwnode_handle_put(ep_fwnode); - - if (IS_ERR(asd)) { - dev_err(dev, "Failed to add subdev: %pe", asd); - v4l2_async_nf_cleanup(&priv->notifier); - return PTR_ERR(asd); - } - - priv->notifier.ops = &max96717_notify_ops; - - ret = v4l2_async_nf_register(&priv->notifier); - if (ret) { - dev_err(dev, "Failed to register subdev_notifier"); - v4l2_async_nf_cleanup(&priv->notifier); - return ret; - } - - return 0; -} - -static int max96717_subdev_init(struct max96717_priv *priv) -{ - struct device *dev = &priv->client->dev; - int ret; - - v4l2_i2c_subdev_init(&priv->sd, priv->client, &max96717_subdev_ops); - priv->sd.internal_ops = &max96717_internal_ops; - - v4l2_ctrl_handler_init(&priv->ctrl_handler, 1); - priv->sd.ctrl_handler = &priv->ctrl_handler; - - v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler, - &max96717_ctrl_ops, - V4L2_CID_TEST_PATTERN, - ARRAY_SIZE(max96717_test_pattern) - 1, - 0, 0, max96717_test_pattern); - if (priv->ctrl_handler.error) { - ret = priv->ctrl_handler.error; - goto err_free_ctrl; - } - - priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS; - priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; - priv->sd.entity.ops = &max96717_entity_ops; - - priv->pads[MAX96717_PAD_SINK].flags = MEDIA_PAD_FL_SINK; - priv->pads[MAX96717_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; - - ret = media_entity_pads_init(&priv->sd.entity, 2, priv->pads); - if (ret) { - dev_err_probe(dev, ret, "Failed to init pads\n"); - goto err_free_ctrl; - } - - ret = v4l2_subdev_init_finalize(&priv->sd); - if (ret) { - dev_err_probe(dev, ret, - "v4l2 subdev init finalized failed\n"); - goto err_entity_cleanup; - } - ret = max96717_v4l2_notifier_register(priv); - if (ret) { - dev_err_probe(dev, ret, - "v4l2 subdev notifier register failed\n"); - goto err_free_state; - } - - ret = v4l2_async_register_subdev(&priv->sd); - if (ret) { - dev_err_probe(dev, ret, "v4l2_async_register_subdev error\n"); - goto err_unreg_notif; - } - - return 0; - -err_unreg_notif: - v4l2_async_nf_unregister(&priv->notifier); - v4l2_async_nf_cleanup(&priv->notifier); -err_free_state: - v4l2_subdev_cleanup(&priv->sd); -err_entity_cleanup: - media_entity_cleanup(&priv->sd.entity); -err_free_ctrl: - v4l2_ctrl_handler_free(&priv->ctrl_handler); - - return ret; -} - -static void max96717_subdev_uninit(struct max96717_priv *priv) -{ - v4l2_async_unregister_subdev(&priv->sd); - v4l2_async_nf_unregister(&priv->notifier); - v4l2_async_nf_cleanup(&priv->notifier); - v4l2_subdev_cleanup(&priv->sd); - media_entity_cleanup(&priv->sd.entity); - v4l2_ctrl_handler_free(&priv->ctrl_handler); -} - -struct max96717_pll_predef_freq { - unsigned long freq; - bool is_alt; - u8 val; -}; - -static const struct max96717_pll_predef_freq max96717_predef_freqs[] = { - { 13500000, true, 0 }, { 19200000, false, 0 }, - { 24000000, true, 1 }, { 27000000, false, 1 }, - { 37125000, false, 2 }, { 74250000, false, 3 }, -}; - -static unsigned long -max96717_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) -{ - struct max96717_priv *priv = clk_hw_to_max96717(hw); - - return max96717_predef_freqs[priv->pll_predef_index].freq; -} - -static unsigned int max96717_clk_find_best_index(struct max96717_priv *priv, - unsigned long rate) -{ - unsigned int i, idx = 0; - unsigned long diff_new, diff_old = U32_MAX; - - for (i = 0; i < ARRAY_SIZE(max96717_predef_freqs); i++) { - diff_new = abs(rate - max96717_predef_freqs[i].freq); - if (diff_new < diff_old) { - diff_old = diff_new; - idx = i; - } - } - - return idx; -} - -static int max96717_clk_determine_rate(struct clk_hw *hw, - struct clk_rate_request *req) -{ - struct max96717_priv *priv = clk_hw_to_max96717(hw); - struct device *dev = &priv->client->dev; - unsigned int idx; - - idx = max96717_clk_find_best_index(priv, req->rate); - - if (req->rate != max96717_predef_freqs[idx].freq) { - dev_warn(dev, "Request CLK freq:%lu, found CLK freq:%lu\n", - req->rate, max96717_predef_freqs[idx].freq); - } - - req->rate = max96717_predef_freqs[idx].freq; - - return 0; -} - -static int max96717_clk_set_rate(struct clk_hw *hw, unsigned long rate, - unsigned long parent_rate) -{ - struct max96717_priv *priv = clk_hw_to_max96717(hw); - unsigned int val, idx; - int ret = 0; - - idx = max96717_clk_find_best_index(priv, rate); - - val = FIELD_PREP(REFGEN_PREDEF_FREQ_MASK, - max96717_predef_freqs[idx].val); - - if (max96717_predef_freqs[idx].is_alt) - val |= REFGEN_PREDEF_FREQ_ALT; - - val |= REFGEN_RST | REFGEN_PREDEF_EN; - - cci_write(priv->regmap, REF_VTG0, val, &ret); - cci_update_bits(priv->regmap, REF_VTG0, REFGEN_RST | REFGEN_EN, - REFGEN_EN, &ret); - if (ret) - return ret; - - priv->pll_predef_index = idx; - - return 0; -} - -static int max96717_clk_prepare(struct clk_hw *hw) -{ - struct max96717_priv *priv = clk_hw_to_max96717(hw); - - return cci_update_bits(priv->regmap, MAX96717_REG6, RCLKEN, - RCLKEN, NULL); -} - -static void max96717_clk_unprepare(struct clk_hw *hw) -{ - struct max96717_priv *priv = clk_hw_to_max96717(hw); - - cci_update_bits(priv->regmap, MAX96717_REG6, RCLKEN, 0, NULL); -} - -static const struct clk_ops max96717_clk_ops = { - .prepare = max96717_clk_prepare, - .unprepare = max96717_clk_unprepare, - .set_rate = max96717_clk_set_rate, - .recalc_rate = max96717_clk_recalc_rate, - .determine_rate = max96717_clk_determine_rate, -}; - -static int max96717_register_clkout(struct max96717_priv *priv) -{ - struct device *dev = &priv->client->dev; - struct clk_init_data init = { .ops = &max96717_clk_ops }; - int ret; - - init.name = kasprintf(GFP_KERNEL, "max96717.%s.clk_out", dev_name(dev)); - if (!init.name) - return -ENOMEM; - - /* RCLKSEL Reference PLL output */ - ret = cci_update_bits(priv->regmap, MAX96717_REG3, MAX96717_RCLKSEL, - MAX96717_RCLKSEL, NULL); - /* MFP4 fastest slew rate */ - cci_update_bits(priv->regmap, PIO_SLEW_1, BIT(5) | BIT(4), 0, &ret); - if (ret) - goto free_init_name; - - priv->clk_hw.init = &init; - - /* Initialize to 24 MHz */ - ret = max96717_clk_set_rate(&priv->clk_hw, - MAX96717_DEFAULT_CLKOUT_RATE, 0); - if (ret < 0) - goto free_init_name; - - ret = devm_clk_hw_register(dev, &priv->clk_hw); - kfree(init.name); - if (ret) - return dev_err_probe(dev, ret, "Cannot register clock HW\n"); - - ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, - &priv->clk_hw); - if (ret) - return dev_err_probe(dev, ret, - "Cannot add OF clock provider\n"); - - return 0; - -free_init_name: - kfree(init.name); - return ret; -} - -static int max96717_init_csi_lanes(struct max96717_priv *priv) -{ - struct v4l2_mbus_config_mipi_csi2 *mipi = &priv->mipi_csi2; - unsigned long lanes_used = 0; - unsigned int nlanes, lane, val = 0; - int ret; - - nlanes = mipi->num_data_lanes; - - ret = cci_update_bits(priv->regmap, MAX96717_MIPI_RX1, - MAX96717_MIPI_LANES_CNT, - FIELD_PREP(MAX96717_MIPI_LANES_CNT, - nlanes - 1), NULL); - - /* lanes polarity */ - for (lane = 0; lane < nlanes + 1; lane++) { - if (!mipi->lane_polarities[lane]) - continue; - /* Clock lane */ - if (lane == 0) - val |= BIT(2); - else if (lane < 3) - val |= BIT(lane - 1); - else - val |= BIT(lane); - } - - cci_update_bits(priv->regmap, MAX96717_MIPI_RX5, - MAX96717_PHY2_LANES_POL, - FIELD_PREP(MAX96717_PHY2_LANES_POL, val), &ret); - - cci_update_bits(priv->regmap, MAX96717_MIPI_RX4, - MAX96717_PHY1_LANES_POL, - FIELD_PREP(MAX96717_PHY1_LANES_POL, - val >> 3), &ret); - /* lanes mapping */ - for (lane = 0, val = 0; lane < nlanes; lane++) { - val |= (mipi->data_lanes[lane] - 1) << (lane * 2); - lanes_used |= BIT(mipi->data_lanes[lane] - 1); - } - - /* - * Unused lanes need to be mapped as well to not have - * the same lanes mapped twice. - */ - for (; lane < MAX96717_CSI_NLANES; lane++) { - unsigned int idx = find_first_zero_bit(&lanes_used, - MAX96717_CSI_NLANES); - - val |= idx << (lane * 2); - lanes_used |= BIT(idx); - } - - cci_update_bits(priv->regmap, MAX96717_MIPI_RX3, - MAX96717_PHY1_LANES_MAP, - FIELD_PREP(MAX96717_PHY1_LANES_MAP, val), &ret); - - return cci_update_bits(priv->regmap, MAX96717_MIPI_RX2, - MAX96717_PHY2_LANES_MAP, - FIELD_PREP(MAX96717_PHY2_LANES_MAP, val >> 4), - &ret); -} - -static int max96717_hw_init(struct max96717_priv *priv) -{ - struct device *dev = &priv->client->dev; - u64 dev_id, val; - int ret; - - ret = cci_read(priv->regmap, MAX96717_DEV_ID, &dev_id, NULL); - if (ret) - return dev_err_probe(dev, ret, - "Fail to read the device id\n"); - - if (dev_id != MAX96717_DEVICE_ID && dev_id != MAX96717F_DEVICE_ID) - return dev_err_probe(dev, -EOPNOTSUPP, - "Unsupported device id got %x\n", (u8)dev_id); - - ret = cci_read(priv->regmap, MAX96717_DEV_REV, &val, NULL); - if (ret) - return dev_err_probe(dev, ret, - "Fail to read device revision"); - - dev_dbg(dev, "Found %x (rev %lx)\n", (u8)dev_id, - (u8)val & MAX96717_DEV_REV_MASK); - - ret = cci_read(priv->regmap, MAX96717_MIPI_RX_EXT11, &val, NULL); - if (ret) - return dev_err_probe(dev, ret, - "Fail to read mipi rx extension"); - - if (!(val & MAX96717_TUN_MODE)) - return dev_err_probe(dev, -EOPNOTSUPP, - "Only supporting tunnel mode"); - - return max96717_init_csi_lanes(priv); -} - -static int max96717_parse_dt(struct max96717_priv *priv) -{ - struct device *dev = &priv->client->dev; - struct v4l2_fwnode_endpoint vep = { .bus_type = V4L2_MBUS_CSI2_DPHY }; - struct fwnode_handle *ep_fwnode; - unsigned char num_data_lanes; - int ret; - - ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), - MAX96717_PAD_SINK, 0, 0); - if (!ep_fwnode) - return dev_err_probe(dev, -ENOENT, "no endpoint found\n"); - - ret = v4l2_fwnode_endpoint_parse(ep_fwnode, &vep); - - fwnode_handle_put(ep_fwnode); - - if (ret < 0) - return dev_err_probe(dev, ret, "Failed to parse sink endpoint"); - - num_data_lanes = vep.bus.mipi_csi2.num_data_lanes; - if (num_data_lanes < 1 || num_data_lanes > MAX96717_CSI_NLANES) - return dev_err_probe(dev, -EINVAL, - "Invalid data lanes must be 1 to 4\n"); - - priv->mipi_csi2 = vep.bus.mipi_csi2; - - return 0; -} - -static int max96717_probe(struct i2c_client *client) -{ - struct device *dev = &client->dev; - struct max96717_priv *priv; - int ret; - - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - priv->client = client; - priv->regmap = devm_cci_regmap_init_i2c(client, 16); - if (IS_ERR(priv->regmap)) { - ret = PTR_ERR(priv->regmap); - return dev_err_probe(dev, ret, "Failed to init regmap\n"); - } - - ret = max96717_parse_dt(priv); - if (ret) - return dev_err_probe(dev, ret, "Failed to parse the dt\n"); - - ret = max96717_hw_init(priv); - if (ret) - return dev_err_probe(dev, ret, - "Failed to initialize the hardware\n"); - - ret = max96717_gpiochip_probe(priv); - if (ret) - return dev_err_probe(&client->dev, ret, - "Failed to init gpiochip\n"); - - ret = max96717_register_clkout(priv); - if (ret) - return dev_err_probe(dev, ret, "Failed to register clkout\n"); - - ret = max96717_subdev_init(priv); - if (ret) - return dev_err_probe(dev, ret, - "Failed to initialize v4l2 subdev\n"); - - ret = max96717_i2c_mux_init(priv); - if (ret) { - dev_err_probe(dev, ret, "failed to add remote i2c adapter\n"); - max96717_subdev_uninit(priv); - } - - return ret; -} - -static void max96717_remove(struct i2c_client *client) -{ - struct v4l2_subdev *sd = i2c_get_clientdata(client); - struct max96717_priv *priv = sd_to_max96717(sd); - - max96717_subdev_uninit(priv); - i2c_mux_del_adapters(priv->mux); -} - -static const struct of_device_id max96717_of_ids[] = { - { .compatible = "maxim,max96717f" }, - { } -}; -MODULE_DEVICE_TABLE(of, max96717_of_ids); - -static struct i2c_driver max96717_i2c_driver = { - .driver = { - .name = "max96717", - .of_match_table = max96717_of_ids, - }, - .probe = max96717_probe, - .remove = max96717_remove, -}; - -module_i2c_driver(max96717_i2c_driver); - -MODULE_DESCRIPTION("Maxim GMSL2 MAX96717 Serializer Driver"); -MODULE_AUTHOR("Julien Massot "); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/maxim-serdes/Kconfig b/drivers/media/i2c/maxim-serdes/Kconfig new file mode 100644 index 000000000000..d0c70d678699 --- /dev/null +++ b/drivers/media/i2c/maxim-serdes/Kconfig @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: GPL-2.0 + +config VIDEO_MAXIM_SERDES + tristate "Maxim GMSL2/3 Serializer and Deserializer support" + depends on VIDEO_DEV + depends on I2C + select I2C_ATR + select I2C_MUX + select MEDIA_CONTROLLER + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + help + This driver supports the Maxim GMSL2/3 common Serializer and + Deserializer framework. + + To compile this driver as a module, choose M here: the module + will be called max_serdes. + +config VIDEO_MAX96717 + tristate "Maxim MAX96717 Serializer support" + depends on COMMON_CLK + depends on I2C + depends on PINCTRL + depends on VIDEO_DEV + select VIDEO_MAXIM_SERDES + select GENERIC_PINCONF + select GENERIC_PINCTRL_GROUPS + select GENERIC_PINMUX_FUNCTIONS + select GPIOLIB + help + This driver supports the Maxim MAX9295A, MAX96717, MAX96717F, + MAX96793 Serializers, which receive video on a MIPI CSI-2 + interface and output it on a GMSL2/3 link. + + To compile this driver as a module, choose M here: the module + will be called max96717. + +config VIDEO_MAX96724 + tristate "Maxim MAX96724 Quad Deserializer support" + depends on I2C + depends on VIDEO_DEV + select VIDEO_MAXIM_SERDES + help + This driver supports the Maxim MAX96712, MAX96724, MAX96724F, + MAX96724R Quad Deserializers, which convert from four GMSL2 + links to up to four MIPI D-PHY or C-PHY outputs. + + To compile this driver as a module, choose M here: the module + will be called max96724. + +config VIDEO_MAX9296A + tristate "Maxim MAX9296A Dual Deserializer support" + depends on I2C + depends on VIDEO_DEV + select VIDEO_MAXIM_SERDES + help + This driver supports the Maxim MAX9296A, MAX96716A, MAX96792A + Dual Deserializers, and the MAX96714, MAX96714F, MAX96714R + Single Deserializers, which convert from up to two GMSL2/3 + links to up to two MIPI D-PHY outputs. + + To compile this driver as a module, choose M here: the module + will be called max9296a. diff --git a/drivers/media/i2c/maxim-serdes/Makefile b/drivers/media/i2c/maxim-serdes/Makefile new file mode 100644 index 000000000000..ae306bc33bfb --- /dev/null +++ b/drivers/media/i2c/maxim-serdes/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +max-serdes-objs := max_serdes.o max_ser.o max_des.o +obj-$(CONFIG_VIDEO_MAXIM_SERDES) += max-serdes.o +obj-$(CONFIG_VIDEO_MAX96717) += max96717.o +obj-$(CONFIG_VIDEO_MAX96724) += max96724.o +obj-$(CONFIG_VIDEO_MAX9296A) += max9296a.o diff --git a/drivers/media/i2c/maxim-serdes/max9296a.c b/drivers/media/i2c/maxim-serdes/max9296a.c new file mode 100644 index 000000000000..7b25b54e63de --- /dev/null +++ b/drivers/media/i2c/maxim-serdes/max9296a.c @@ -0,0 +1,1461 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Maxim MAX9296A Dual GMSL2/3 Deserializer Driver + * + * Copyright (C) 2025 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "max_des.h" + +#define MAX9296A_REG0 0x0 + +#define MAX9296A_REG1 0x1 +#define MAX9296A_REG1_RX_RATE_A GENMASK(1, 0) +#define MAX9296A_REG1_RX_RATE_3GBPS 0b01 +#define MAX9296A_REG1_RX_RATE_6GBPS 0b10 +#define MAX9296A_REG1_RX_RATE_12GBPS 0b11 + +#define MAX9296A_REG2 0x2 +#define MAX9296A_REG2_VID_EN(p) BIT((p) + 4) + +#define MAX9296A_REG4 0x4 +#define MAX9296A_REG4_GMSL3_X(x) BIT((x) + 6) +#define MAX9296A_REG4_RX_RATE_B GENMASK(1, 0) + +#define MAX9296A_REG6 0x6 +#define MAX9296A_REG6_GMSL2_X(x) BIT((x) + 6) + +#define MAX9296A_CTRL0 0x10 +#define MAX9296A_CTRL0_LINK_CFG GENMASK(1, 0) +#define MAX9296A_CTRL0_AUTO_LINK BIT(4) +#define MAX9296A_CTRL0_RESET_ONESHOT BIT(5) +#define MAX9296A_CTRL0_RESET_ALL BIT(7) + +#define MAX9296A_CTRL2 0x12 +#define MAX9296A_CTRL2_RESET_ONESHOT_B BIT(5) + +#define MAX9296A_CTRL3 0x13 +#define MAX9296A_CTRL3_LOCKED BIT(3) + +#define MAX9296A_MIPI_TX0(x) (0x28 + (x) * 0x5000) +#define MAX9296A_MIPI_TX0_RX_FEC_EN BIT(1) + +#define MAX9296A_IO_CHK0 0x38 +#define MAX9296A_IO_CHK0_PIN_DRV_EN_0 GENMASK(1, 0) +#define MAX9296A_IO_CHK0_PIN_DRV_EN_0_25MHZ 0b00 +#define MAX9296A_IO_CHK0_PIN_DRV_EN_0_75MHZ 0b01 +#define MAX9296A_IO_CHK0_PIN_DRV_EN_0_USE_PIPE 0b10 + +#define MAX9296A_RX50(p) (0x50 + (p)) +#define MAX9296A_RX50_STR_SEL GENMASK(1, 0) + +#define MAX9296A_VIDEO_PIPE_EN 0x160 +#define MAX9296A_VIDEO_PIPE_EN_MASK(p) BIT(p) + +#define MAX9296A_VIDEO_PIPE_SEL 0x161 +#define MAX9296A_VIDEO_PIPE_SEL_STREAM(p) (GENMASK(1, 0) << ((p) * 3)) +#define MAX9296A_VIDEO_PIPE_SEL_LINK(p) BIT(2 + (p) * 3) + +#define MAX9296A_VPRBS(p) (0x1fc + (p) * 0x20) +#define MAX9296A_VPRBS_VIDEO_LOCK BIT(0) +#define MAX9296A_VPRBS_PATGEN_CLK_SRC BIT(7) +#define MAX9296A_VPRBS_PATGEN_CLK_SRC_150MHZ 0b0 +#define MAX9296A_VPRBS_PATGEN_CLK_SRC_600MHZ 0b1 + +#define MAX9296A_PATGEN_0 0x240 +#define MAX9296A_PATGEN_0_VTG_MODE GENMASK(1, 0) +#define MAX9296A_PATGEN_0_VTG_MODE_FREE_RUNNING 0b11 +#define MAX9296A_PATGEN_0_DE_INV BIT(2) +#define MAX9296A_PATGEN_0_HS_INV BIT(3) +#define MAX9296A_PATGEN_0_VS_INV BIT(4) +#define MAX9296A_PATGEN_0_GEN_DE BIT(5) +#define MAX9296A_PATGEN_0_GEN_HS BIT(6) +#define MAX9296A_PATGEN_0_GEN_VS BIT(7) + +#define MAX9296A_PATGEN_1 0x241 +#define MAX9296A_PATGEN_1_PATGEN_MODE GENMASK(5, 4) +#define MAX9296A_PATGEN_1_PATGEN_MODE_DISABLED 0b00 +#define MAX9296A_PATGEN_1_PATGEN_MODE_CHECKER 0b01 +#define MAX9296A_PATGEN_1_PATGEN_MODE_GRADIENT 0b10 + +#define MAX9296A_VS_DLY_2 0x242 +#define MAX9296A_VS_HIGH_2 0x245 +#define MAX9296A_VS_LOW_2 0x248 +#define MAX9296A_V2H_2 0x24b +#define MAX9296A_HS_HIGH_1 0x24e +#define MAX9296A_HS_LOW_1 0x250 +#define MAX9296A_HS_CNT_1 0x252 +#define MAX9296A_V2D_2 0x254 +#define MAX9296A_DE_HIGH_1 0x257 +#define MAX9296A_DE_LOW_1 0x259 +#define MAX9296A_DE_CNT_1 0x25b +#define MAX9296A_GRAD_INCR 0x25d +#define MAX9296A_CHKR_COLOR_A_L 0x25e +#define MAX9296A_CHKR_COLOR_B_L 0x261 +#define MAX9296A_CHKR_RPT_A 0x264 +#define MAX9296A_CHKR_RPT_B 0x265 +#define MAX9296A_CHKR_ALT 0x266 + +#define MAX9296A_BACKTOP12 0x313 +#define MAX9296A_BACKTOP12_CSI_OUT_EN BIT(1) + +#define MAX9296A_BACKTOP21 0x31c +#define MAX9296A_BACKTOP21_BPP8DBL(p) BIT(4 + (p)) + +#define MAX9296A_BACKTOP22(x) (0x31d + (x) * 0x3) +#define MAX9296A_BACKTOP22_PHY_CSI_TX_DPLL GENMASK(4, 0) +#define MAX9296A_BACKTOP22_PHY_CSI_TX_DPLL_EN BIT(5) + +#define MAX9296A_BACKTOP24 0x31f +#define MAX9296A_BACKTOP24_BPP8DBL_MODE(p) BIT(4 + (p)) + +#define MAX9296A_BACKTOP30 0x325 +#define MAX9296A_BACKTOP30_W_FRAME BIT(7) + +#define MAX9296A_BACKTOP32 0x327 +#define MAX9296A_BACKTOP32_BPP10DBL(p) BIT(p) +#define MAX9296A_BACKTOP32_BPP10DBL_MODE(p) BIT(4 + (p)) + +#define MAX9296A_BACKTOP33 0x328 +#define MAX9296A_BACKTOP32_BPP12DBL(p) BIT(p) + +#define MAX9296A_MIPI_PHY0 0x330 +#define MAX9296A_MIPI_PHY0_FORCE_CSI_OUT_EN BIT(7) + +#define MAX9296A_MIPI_PHY2 0x332 +#define MAX9296A_MIPI_PHY2_PHY_STDBY_N(x) (GENMASK(5, 4) << ((x) * 2)) + +#define MAX9296A_MIPI_PHY3(x) (0x333 + (x)) +#define MAX9296A_MIPI_PHY3_PHY_LANE_MAP_4 GENMASK(7, 0) + +#define MAX9296A_MIPI_PHY5(x) (0x335 + (x)) +#define MAX9296A_MIPI_PHY5_PHY_POL_MAP_0_1 GENMASK(1, 0) +#define MAX9296A_MIPI_PHY5_PHY_POL_MAP_2_3 GENMASK(4, 3) +#define MAX9296A_MIPI_PHY5_PHY_POL_MAP_CLK(x) BIT((x) == 0 ? 5 : 2) + +#define MAX9296A_MIPI_PHY18 0x342 +#define MAX9296A_MIPI_PHY18_CSI2_TX_PKT_CNT(x) (GENMASK(3, 0) << (4 * (x))) + +#define MAX9296A_MIPI_PHY20(x) (0x344 + (x)) + +#define MAX9296A_MIPI_TX3(x) (0x403 + (x) * 0x40) +#define MAX9296A_MIPI_TX3_DESKEW_INIT_8X32K FIELD_PREP(GENMASK(2, 0), 0b001) +#define MAX9296A_MIPI_TX3_DESKEW_INIT_AUTO BIT(7) + +#define MAX9296A_MIPI_TX4(x) (0x404 + (x) * 0x40) +#define MAX9296A_MIPI_TX4_DESKEW_PER_2K FIELD_PREP(GENMASK(2, 0), 0b001) +#define MAX9296A_MIPI_TX4_DESKEW_PER_AUTO BIT(7) + +#define MAX9296A_MIPI_TX10(x) (0x40a + (x) * 0x40) +#define MAX9296A_MIPI_TX10_CSI2_LANE_CNT GENMASK(7, 6) +#define MAX9296A_MIPI_TX10_CSI2_CPHY_EN BIT(5) + +#define MAX9296A_MIPI_TX11(p) (0x40b + (p) * 0x40) +#define MAX9296A_MIPI_TX12(p) (0x40c + (p) * 0x40) + +#define MAX9296A_MIPI_TX13(p, x) (0x40d + (p) * 0x40 + (x) * 0x2) +#define MAX9296A_MIPI_TX13_MAP_SRC_DT GENMASK(5, 0) +#define MAX9296A_MIPI_TX13_MAP_SRC_VC GENMASK(7, 6) + +#define MAX9296A_MIPI_TX14(p, x) (0x40e + (p) * 0x40 + (x) * 0x2) +#define MAX9296A_MIPI_TX14_MAP_DST_DT GENMASK(5, 0) +#define MAX9296A_MIPI_TX14_MAP_DST_VC GENMASK(7, 6) + +#define MAX9296A_MIPI_TX45(p, x) (0x42d + (p) * 0x40 + (x) / 4) +#define MAX9296A_MIPI_TX45_MAP_DPHY_DEST(x) (GENMASK(1, 0) << (2 * ((x) % 4))) + +#define MAX9296A_MIPI_TX51(x) (0x433 + (x) * 0x40) +#define MAX9296A_MIPI_TX51_ALT_MEM_MAP_12 BIT(0) +#define MAX9296A_MIPI_TX51_ALT_MEM_MAP_8 BIT(1) +#define MAX9296A_MIPI_TX51_ALT_MEM_MAP_10 BIT(2) +#define MAX9296A_MIPI_TX51_ALT2_MEM_MAP_8 BIT(4) + +#define MAX9296A_MIPI_TX52(x) (0x434 + (x) * 0x40) +#define MAX9296A_MIPI_TX52_TUN_DEST BIT(1) +#define MAX9296A_MIPI_TX52_TUN_EN BIT(0) + +#define MAX9296A_GMSL1_EN 0xf00 +#define MAX9296A_GMSL1_EN_LINK_EN GENMASK(1, 0) + +#define MAX9296A_RLMS3E(x) (0x143e + (x) * 0x100) +#define MAX9296A_RLMS3F(x) (0x143f + (x) * 0x100) +#define MAX9296A_RLMS49(x) (0x1449 + (x) * 0x100) +#define MAX9296A_RLMS7E(x) (0x147e + (x) * 0x100) +#define MAX9296A_RLMS7F(x) (0x147f + (x) * 0x100) +#define MAX9296A_RLMSA3(x) (0x14a3 + (x) * 0x100) +#define MAX9296A_RLMSA5(x) (0x14a5 + (x) * 0x100) +#define MAX9296A_RLMSD8(x) (0x14d8 + (x) * 0x100) + +#define MAX9296A_DPLL_0(x) (0x1c00 + (x) * 0x100) +#define MAX9296A_DPLL_0_CONFIG_SOFT_RST_N BIT(0) + +#define MAX9296A_CTRL9 0x5009 +#define MAX9296A_CTRL9_LOCKED_B BIT(3) + +#define MAX9296A_PIPES_NUM 4 +#define MAX9296A_PHYS_NUM 2 + +static const struct regmap_config max9296a_i2c_regmap = { + .reg_bits = 16, + .val_bits = 8, +}; + +struct max9296a_priv { + struct max_des des; + const struct max9296a_chip_info *info; + + struct device *dev; + struct i2c_client *client; + struct regmap *regmap; + + struct gpio_desc *gpiod_pwdn; +}; + +struct max9296a_chip_info { + const struct max_des_info *info; + const struct max_des_ops *ops; + const struct reg_sequence *rlms_adjust_sequence; + unsigned int rlms_adjust_sequence_len; + unsigned int max_register; + unsigned int pipe_hw_ids[MAX9296A_PIPES_NUM]; + unsigned int phy_hw_ids[MAX9296A_PHYS_NUM]; + bool has_per_link_lock; + bool has_per_link_reset; + bool phy0_lanes_0_1_on_second_phy; + bool polarity_on_physical_lanes; + bool supports_cphy; + bool supports_phy_log; +}; + +#define des_to_priv(_des) \ + container_of(_des, struct max9296a_priv, des) + +static int max9296a_wait_for_device(struct max9296a_priv *priv) +{ + unsigned int val; + int ret, err; + + err = read_poll_timeout(regmap_read, ret, + !ret && val, + 100 * USEC_PER_MSEC, + 1 * USEC_PER_SEC, false, + priv->regmap, MAX9296A_REG0, &val); + if (err) + dev_err(priv->dev, "Timeout waiting for deserializer: %d\n", ret); + + return err; +} + +static int max9296a_reset(struct max9296a_priv *priv) +{ + int ret; + + ret = max9296a_wait_for_device(priv); + if (ret) + return ret; + + ret = regmap_set_bits(priv->regmap, MAX9296A_CTRL0, + MAX9296A_CTRL0_RESET_ALL); + if (ret) + return ret; + + msleep(100); + + return max9296a_wait_for_device(priv); +} + +static unsigned int max9296a_pipe_id(struct max9296a_priv *priv, + struct max_des_pipe *pipe) +{ + return priv->info->pipe_hw_ids[pipe->index]; +} + +static unsigned int max9296a_phy_id(struct max9296a_priv *priv, + struct max_des_phy *phy) +{ + return priv->info->phy_hw_ids[phy->index]; +} + +static int __maybe_unused max9296a_reg_read(struct max_des *des, unsigned int reg, + unsigned int *val) +{ + struct max9296a_priv *priv = des_to_priv(des); + + return regmap_read(priv->regmap, reg, val); +} + +static int __maybe_unused max9296a_reg_write(struct max_des *des, unsigned int reg, + unsigned int val) +{ + struct max9296a_priv *priv = des_to_priv(des); + + return regmap_write(priv->regmap, reg, val); +} + +static int max9296a_log_pipe_status(struct max_des *des, + struct max_des_pipe *pipe) +{ + struct max9296a_priv *priv = des_to_priv(des); + unsigned int index = max9296a_pipe_id(priv, pipe); + unsigned int val; + int ret; + + ret = regmap_read(priv->regmap, MAX9296A_VPRBS(index), &val); + if (ret) + return ret; + + dev_info(priv->dev, "\tvideo_lock: %u\n", + !!(val & MAX9296A_VPRBS_VIDEO_LOCK)); + + return 0; +} + +static int max9296a_log_phy_status(struct max_des *des, + struct max_des_phy *phy) +{ + struct max9296a_priv *priv = des_to_priv(des); + unsigned int index = phy->index; + unsigned int val; + int ret; + + if (!priv->info->supports_phy_log) + return 0; + + ret = regmap_read(priv->regmap, MAX9296A_MIPI_PHY18, &val); + if (ret) + return ret; + + dev_info(priv->dev, "\tcsi2_pkt_cnt: %lu\n", + field_get(MAX9296A_MIPI_PHY18_CSI2_TX_PKT_CNT(index), val)); + + ret = regmap_read(priv->regmap, MAX9296A_MIPI_PHY20(index), &val); + if (ret) + return ret; + + dev_info(priv->dev, "\tphy_pkt_cnt: %u\n", val); + + return 0; +} + +static int max9296a_set_enable(struct max_des *des, bool enable) +{ + struct max9296a_priv *priv = des_to_priv(des); + + return regmap_assign_bits(priv->regmap, MAX9296A_BACKTOP12, + MAX9296A_BACKTOP12_CSI_OUT_EN, enable); +} + +static int max9296a_init_tpg(struct max_des *des) +{ + const struct reg_sequence regs[] = { + { MAX9296A_GRAD_INCR, MAX_SERDES_GRAD_INCR }, + REG_SEQUENCE_3_LE(MAX9296A_CHKR_COLOR_A_L, + MAX_SERDES_CHECKER_COLOR_A), + REG_SEQUENCE_3_LE(MAX9296A_CHKR_COLOR_B_L, + MAX_SERDES_CHECKER_COLOR_B), + { MAX9296A_CHKR_RPT_A, MAX_SERDES_CHECKER_SIZE }, + { MAX9296A_CHKR_RPT_B, MAX_SERDES_CHECKER_SIZE }, + { MAX9296A_CHKR_ALT, MAX_SERDES_CHECKER_SIZE }, + }; + struct max9296a_priv *priv = des_to_priv(des); + + return regmap_multi_reg_write(priv->regmap, regs, ARRAY_SIZE(regs)); +} + +static int max9296a_init(struct max_des *des) +{ + struct max9296a_priv *priv = des_to_priv(des); + int ret; + + if (priv->info->rlms_adjust_sequence) { + ret = regmap_multi_reg_write(priv->regmap, + priv->info->rlms_adjust_sequence, + priv->info->rlms_adjust_sequence_len); + if (ret) + return ret; + } + + /* + * Hold off MIPI packet requests until a frame start is seen. + * Assures the first frame out of the line buffer is whole. + */ + ret = regmap_set_bits(priv->regmap, MAX9296A_BACKTOP30, + MAX9296A_BACKTOP30_W_FRAME); + if (ret) + return ret; + + return max9296a_init_tpg(des); +} + +static int max9296a_init_phy(struct max_des *des, struct max_des_phy *phy) +{ + struct max9296a_priv *priv = des_to_priv(des); + bool is_cphy = phy->bus_type == V4L2_MBUS_CSI2_CPHY; + unsigned int num_data_lanes = phy->mipi.num_data_lanes; + unsigned int dpll_freq = is_cphy ? phy->link_frequency + : phy->link_frequency * 2; + unsigned int num_hw_data_lanes; + unsigned int hw_index = max9296a_phy_id(priv, phy); + unsigned int index = phy->index; + unsigned int used_data_lanes = 0; + unsigned int val; + unsigned int i; + int ret; + + if (is_cphy && !priv->info->supports_cphy) { + dev_err(priv->dev, "CPHY not supported\n"); + return -EINVAL; + } + + num_hw_data_lanes = max_des_phy_hw_data_lanes(des, phy); + + /* + * MAX9296A has four PHYs, but does not support single-PHY configurations, + * only double-PHY configurations, even when only using two lanes. + * For PHY 0 + PHY 1, PHY 1 is the master PHY. + * For PHY 2 + PHY 3, PHY 2 is the master PHY. + * Clock is always on the master PHY. + * For first pair of PHYs, first lanes are on the master PHY. + * For second pair of PHYs, first lanes are on the master PHY too. + * + * PHY 0 + 1 + * CLK = PHY 1 + * PHY1 Lane 0 = D0 + * PHY1 Lane 1 = D1 + * PHY0 Lane 0 = D2 + * PHY0 Lane 1 = D3 + * + * PHY 2 + 3 + * CLK = PHY 2 + * PHY2 Lane 0 = D0 + * PHY2 Lane 1 = D1 + * PHY3 Lane 0 = D2 + * PHY3 Lane 1 = D3 + * + * MAX96714 only has two PHYs which cannot support single-PHY configurations. + * Clock is always on the master PHY, first lanes are on PHY 0, even if + * PHY 1 is the master PHY. + * + * PHY 0 + 1 + * CLK = PHY 1 + * PHY0 Lane 0 = D0 + * PHY0 Lane 1 = D1 + * PHY1 Lane 0 = D2 + * PHY1 Lane 1 = D3 + */ + + /* Configure a lane count. */ + ret = regmap_update_bits(priv->regmap, MAX9296A_MIPI_TX10(hw_index), + MAX9296A_MIPI_TX10_CSI2_LANE_CNT, + FIELD_PREP(MAX9296A_MIPI_TX10_CSI2_LANE_CNT, + num_data_lanes - 1)); + if (ret) + return ret; + + ret = regmap_assign_bits(priv->regmap, MAX9296A_MIPI_TX10(hw_index), + MAX9296A_MIPI_TX10_CSI2_CPHY_EN, is_cphy); + if (ret) + return ret; + + /* Configure lane mapping. */ + /* + * The lane of each PHY can be mapped to physical lanes 0, 1, 2, and 3. + * This mapping is exclusive, multiple lanes, even if unused cannot be + * mapped to the same physical lane. + * Each lane mapping is represented as two bits. + */ + val = 0; + for (i = 0; i < num_hw_data_lanes ; i++) { + unsigned int map; + + if (i < num_data_lanes) + map = phy->mipi.data_lanes[i] - 1; + else + map = ffz(used_data_lanes); + + val |= map << (i * 2); + used_data_lanes |= BIT(map); + } + + if (phy->index == 0 && priv->info->phy0_lanes_0_1_on_second_phy) + val = ((val & 0xf) << 4) | ((val >> 4) & 0xf); + + ret = regmap_update_bits(priv->regmap, MAX9296A_MIPI_PHY3(index), + MAX9296A_MIPI_PHY3_PHY_LANE_MAP_4, + FIELD_PREP(MAX9296A_MIPI_PHY3_PHY_LANE_MAP_4, val)); + if (ret) + return ret; + + /* + * Configure lane polarity. + * + * PHY 0 and 1 are on register 0x335. + * PHY 2 and 3 are on register 0x336. + * + * Each PHY has 3 bits of polarity configuration. + * + * On MAX9296A, each bit represents the lane polarity of logical lanes. + * Each of these lanes can be mapped to any physical lane. + * 0th bit is for lane 0. + * 1st bit is for lane 1. + * 2nd bit is for clock lane. + * + * On MAX96714, each bit represents the lane polarity of physical lanes. + * 0th bit for physical lane 0. + * 1st bit for physical lane 1. + * 2nd bit for clock lane of PHY 0, the slave PHY, which is unused. + * + * 3rd bit for physical lane 2. + * 4th bit for physical lane 3. + * 5th bit for clock lane of PHY 1, the master PHY. + */ + + for (i = 0, val = 0; i < num_data_lanes; i++) { + unsigned int map; + + if (!phy->mipi.lane_polarities[i + 1]) + continue; + + /* + * The numbers inside the data_lanes array specify the hardware + * lane each logical lane maps to. + * If polarity is set for the physical lanes, retrieve the + * physical lane matching the logical lane from data_lanes. + * Otherwise, when polarity is set for the logical lanes + * the index of the polarity can be used. + */ + + if (priv->info->polarity_on_physical_lanes) + map = phy->mipi.data_lanes[i] - 1; + else + map = i; + + val |= BIT(map); + } + + if (phy->index == 0 && priv->info->phy0_lanes_0_1_on_second_phy) + val = ((val & 0x3) << 2) | ((val >> 2) & 0x3); + + ret = regmap_update_bits(priv->regmap, MAX9296A_MIPI_PHY5(index), + MAX9296A_MIPI_PHY5_PHY_POL_MAP_0_1 | + MAX9296A_MIPI_PHY5_PHY_POL_MAP_2_3, + FIELD_PREP(MAX9296A_MIPI_PHY5_PHY_POL_MAP_0_1, val) | + FIELD_PREP(MAX9296A_MIPI_PHY5_PHY_POL_MAP_2_3, val >> 2)); + if (ret) + return ret; + + ret = regmap_assign_bits(priv->regmap, MAX9296A_MIPI_PHY5(index), + MAX9296A_MIPI_PHY5_PHY_POL_MAP_CLK(index), + phy->mipi.lane_polarities[0]); + if (ret) + return ret; + + /* Put DPLL block into reset. */ + ret = regmap_clear_bits(priv->regmap, MAX9296A_DPLL_0(hw_index), + MAX9296A_DPLL_0_CONFIG_SOFT_RST_N); + if (ret) + return ret; + + /* Set DPLL frequency. */ + ret = regmap_update_bits(priv->regmap, MAX9296A_BACKTOP22(hw_index), + MAX9296A_BACKTOP22_PHY_CSI_TX_DPLL, + FIELD_PREP(MAX9296A_BACKTOP22_PHY_CSI_TX_DPLL, + div_u64(dpll_freq, 100000000))); + if (ret) + return ret; + + /* Enable DPLL frequency. */ + ret = regmap_set_bits(priv->regmap, MAX9296A_BACKTOP22(hw_index), + MAX9296A_BACKTOP22_PHY_CSI_TX_DPLL_EN); + if (ret) + return ret; + + /* Pull DPLL block out of reset. */ + ret = regmap_set_bits(priv->regmap, MAX9296A_DPLL_0(hw_index), + MAX9296A_DPLL_0_CONFIG_SOFT_RST_N); + if (ret) + return ret; + + if (!is_cphy && dpll_freq > 1500000000ull) { + /* Enable initial deskew with 2 x 32k UI. */ + ret = regmap_write(priv->regmap, MAX9296A_MIPI_TX3(hw_index), + MAX9296A_MIPI_TX3_DESKEW_INIT_AUTO | + MAX9296A_MIPI_TX3_DESKEW_INIT_8X32K); + if (ret) + return ret; + + /* Enable periodic deskew with 2 x 1k UI.. */ + ret = regmap_write(priv->regmap, MAX9296A_MIPI_TX4(hw_index), + MAX9296A_MIPI_TX4_DESKEW_PER_AUTO | + MAX9296A_MIPI_TX4_DESKEW_PER_2K); + if (ret) + return ret; + } else { + /* Disable initial deskew. */ + ret = regmap_write(priv->regmap, MAX9296A_MIPI_TX3(hw_index), 0x0); + if (ret) + return ret; + + /* Disable periodic deskew. */ + ret = regmap_write(priv->regmap, MAX9296A_MIPI_TX4(hw_index), 0x0); + if (ret) + return ret; + } + + return 0; +} + +static int max9296a_set_phy_mode(struct max_des *des, struct max_des_phy *phy, + struct max_des_phy_mode *mode) +{ + struct max9296a_priv *priv = des_to_priv(des); + unsigned int phy_id = max9296a_phy_id(priv, phy); + int ret; + + /* Set alternate memory map modes. */ + ret = regmap_assign_bits(priv->regmap, MAX9296A_MIPI_TX51(phy_id), + MAX9296A_MIPI_TX51_ALT_MEM_MAP_12, + mode->alt_mem_map12); + if (ret) + return ret; + + ret = regmap_assign_bits(priv->regmap, MAX9296A_MIPI_TX51(phy_id), + MAX9296A_MIPI_TX51_ALT_MEM_MAP_8, + mode->alt_mem_map8); + if (ret) + return ret; + + ret = regmap_assign_bits(priv->regmap, MAX9296A_MIPI_TX51(phy_id), + MAX9296A_MIPI_TX51_ALT_MEM_MAP_10, + mode->alt_mem_map10); + if (ret) + return ret; + + return regmap_assign_bits(priv->regmap, MAX9296A_MIPI_TX51(phy_id), + MAX9296A_MIPI_TX51_ALT2_MEM_MAP_8, + mode->alt2_mem_map8); +} + +static int max9296a_set_phy_enable(struct max_des *des, struct max_des_phy *phy, + bool enable) +{ + struct max9296a_priv *priv = des_to_priv(des); + + return regmap_assign_bits(priv->regmap, MAX9296A_MIPI_PHY2, + MAX9296A_MIPI_PHY2_PHY_STDBY_N(phy->index), enable); +} + +static int max9296a_set_pipe_remap(struct max_des *des, + struct max_des_pipe *pipe, + unsigned int i, + struct max_des_remap *remap) +{ + struct max9296a_priv *priv = des_to_priv(des); + struct max_des_phy *phy = &des->phys[remap->phy]; + unsigned int phy_id = max9296a_phy_id(priv, phy); + unsigned int index = max9296a_pipe_id(priv, pipe); + int ret; + + /* Set source Data Type and Virtual Channel. */ + /* TODO: implement extended Virtual Channel. */ + ret = regmap_write(priv->regmap, MAX9296A_MIPI_TX13(index, i), + FIELD_PREP(MAX9296A_MIPI_TX13_MAP_SRC_DT, + remap->from_dt) | + FIELD_PREP(MAX9296A_MIPI_TX13_MAP_SRC_VC, + remap->from_vc)); + if (ret) + return ret; + + /* Set destination Data Type and Virtual Channel. */ + /* TODO: implement extended Virtual Channel. */ + ret = regmap_write(priv->regmap, MAX9296A_MIPI_TX14(index, i), + FIELD_PREP(MAX9296A_MIPI_TX14_MAP_DST_DT, + remap->to_dt) | + FIELD_PREP(MAX9296A_MIPI_TX14_MAP_DST_VC, + remap->to_vc)); + if (ret) + return ret; + + /* Set destination PHY. */ + return regmap_update_bits(priv->regmap, MAX9296A_MIPI_TX45(index, i), + MAX9296A_MIPI_TX45_MAP_DPHY_DEST(i), + field_prep(MAX9296A_MIPI_TX45_MAP_DPHY_DEST(i), + phy_id)); +} + +static int max9296a_set_pipe_remaps_enable(struct max_des *des, + struct max_des_pipe *pipe, + unsigned int mask) +{ + struct max9296a_priv *priv = des_to_priv(des); + unsigned int index = max9296a_pipe_id(priv, pipe); + int ret; + + ret = regmap_write(priv->regmap, MAX9296A_MIPI_TX11(index), mask); + if (ret) + return ret; + + return regmap_write(priv->regmap, MAX9296A_MIPI_TX12(index), mask >> 8); +} + +static int max9296a_set_pipe_enable(struct max_des *des, struct max_des_pipe *pipe, + bool enable) +{ + struct max9296a_priv *priv = des_to_priv(des); + unsigned int index = max9296a_pipe_id(priv, pipe); + + return regmap_assign_bits(priv->regmap, MAX9296A_REG2, + MAX9296A_REG2_VID_EN(index), enable); +} + +static int max96714_set_pipe_enable(struct max_des *des, struct max_des_pipe *pipe, + bool enable) +{ + struct max9296a_priv *priv = des_to_priv(des); + unsigned int index = max9296a_pipe_id(priv, pipe); + + return regmap_assign_bits(priv->regmap, MAX9296A_VIDEO_PIPE_EN, + MAX9296A_VIDEO_PIPE_EN_MASK(index - 1), enable); +} + +static int max96714_set_pipe_tunnel_enable(struct max_des *des, + struct max_des_pipe *pipe, bool enable) +{ + struct max9296a_priv *priv = des_to_priv(des); + unsigned int index = max9296a_pipe_id(priv, pipe); + + return regmap_assign_bits(priv->regmap, MAX9296A_MIPI_TX52(index), + MAX9296A_MIPI_TX52_TUN_EN, enable); +} + +static int max9296a_set_pipe_stream_id(struct max_des *des, struct max_des_pipe *pipe, + unsigned int stream_id) +{ + struct max9296a_priv *priv = des_to_priv(des); + unsigned int index = max9296a_pipe_id(priv, pipe); + + return regmap_update_bits(priv->regmap, MAX9296A_RX50(index), MAX9296A_RX50_STR_SEL, + FIELD_PREP(MAX9296A_RX50_STR_SEL, stream_id)); +} + +static int max96714_set_pipe_stream_id(struct max_des *des, struct max_des_pipe *pipe, + unsigned int stream_id) +{ + struct max9296a_priv *priv = des_to_priv(des); + unsigned int index = pipe->index; + + return regmap_update_bits(priv->regmap, MAX9296A_VIDEO_PIPE_SEL, + MAX9296A_VIDEO_PIPE_SEL_STREAM(index), + field_prep(MAX9296A_VIDEO_PIPE_SEL_STREAM(index), + stream_id)); +} + +static int max96716a_set_pipe_link(struct max_des *des, struct max_des_pipe *pipe, + struct max_des_link *link) +{ + struct max9296a_priv *priv = des_to_priv(des); + unsigned int index = pipe->index; + + return regmap_update_bits(priv->regmap, MAX9296A_VIDEO_PIPE_SEL, + MAX9296A_VIDEO_PIPE_SEL_LINK(index), + field_prep(MAX9296A_VIDEO_PIPE_SEL_LINK(index), + link->index)); +} + +static int max96716a_set_pipe_tunnel_phy(struct max_des *des, + struct max_des_pipe *pipe, + struct max_des_phy *phy) +{ + struct max9296a_priv *priv = des_to_priv(des); + unsigned int index = max9296a_pipe_id(priv, pipe); + + return regmap_assign_bits(priv->regmap, MAX9296A_MIPI_TX52(index), + MAX9296A_MIPI_TX52_TUN_DEST, phy->index); +} + +static int max9296a_set_pipe_mode(struct max_des *des, + struct max_des_pipe *pipe, + struct max_des_pipe_mode *mode) +{ + struct max9296a_priv *priv = des_to_priv(des); + unsigned int index = max9296a_pipe_id(priv, pipe); + int ret; + + /* Set 8bit double mode. */ + ret = regmap_assign_bits(priv->regmap, MAX9296A_BACKTOP21, + MAX9296A_BACKTOP21_BPP8DBL(index), mode->dbl8); + if (ret) + return ret; + + ret = regmap_assign_bits(priv->regmap, MAX9296A_BACKTOP24, + MAX9296A_BACKTOP24_BPP8DBL_MODE(index), + mode->dbl8mode); + if (ret) + return ret; + + /* Set 10bit double mode. */ + ret = regmap_assign_bits(priv->regmap, MAX9296A_BACKTOP32, + MAX9296A_BACKTOP32_BPP10DBL(index), mode->dbl10); + if (ret) + return ret; + + ret = regmap_assign_bits(priv->regmap, MAX9296A_BACKTOP32, + MAX9296A_BACKTOP32_BPP10DBL_MODE(index), + mode->dbl10mode); + if (ret) + return ret; + + /* Set 12bit double mode. */ + /* TODO: check support for double mode on MAX96714. */ + return regmap_assign_bits(priv->regmap, MAX9296A_BACKTOP33, + MAX9296A_BACKTOP32_BPP12DBL(index), mode->dbl12); +} + +static int max9296a_reset_link(struct max9296a_priv *priv, unsigned int index) +{ + unsigned int reg, mask; + + if (index == 0) { + reg = MAX9296A_CTRL0; + mask = MAX9296A_CTRL0_RESET_ONESHOT; + } else { + reg = MAX9296A_CTRL2; + mask = MAX9296A_CTRL2_RESET_ONESHOT_B; + } + + return regmap_set_bits(priv->regmap, reg, mask); +} + +static int max9296a_read_link_locked(struct max9296a_priv *priv, + unsigned int index, bool *locked) +{ + unsigned int reg, mask, val; + int ret; + + if (index == 0) { + reg = MAX9296A_CTRL3; + mask = MAX9296A_CTRL3_LOCKED; + } else { + reg = MAX9296A_CTRL9; + mask = MAX9296A_CTRL9_LOCKED_B; + } + + ret = regmap_read(priv->regmap, reg, &val); + if (ret) + return ret; + + *locked = val & mask; + + return 0; +} + +static int max9296a_wait_for_link_lock(struct max9296a_priv *priv, + unsigned int mask) +{ + unsigned int i; + bool locked; + int ret, err; + + if (!priv->info->has_per_link_lock) { + err = read_poll_timeout(max9296a_read_link_locked, ret, + ret || locked, + 10 * USEC_PER_MSEC, + 200 * USEC_PER_MSEC, true, + priv, 0, &locked); + if (ret) + return ret; + + if (err) + dev_dbg(priv->dev, "Timeout waiting for link lock\n"); + + return err; + } + + for (i = 0; i < priv->des.info->num_links; i++) { + if (!(mask & BIT(i))) + continue; + + err = read_poll_timeout(max9296a_read_link_locked, ret, + ret || locked, + 10 * USEC_PER_MSEC, + 200 * USEC_PER_MSEC, true, + priv, i, &locked); + if (ret) + return ret; + + if (err) { + dev_dbg(priv->dev, + "Timeout waiting for link %u lock\n", i); + + return err; + } + } + + return 0; +} + +static int max9296a_select_links(struct max_des *des, unsigned int mask) +{ + struct max9296a_priv *priv = des_to_priv(des); + int ret; + + if (des->info->num_links == 1) { + ret = max9296a_reset_link(priv, 0); + if (ret) + return ret; + + return max9296a_wait_for_link_lock(priv, BIT(0)); + } + + if (!mask) { + dev_err(priv->dev, "Disable all links unsupported\n"); + return -EINVAL; + } + + ret = regmap_update_bits(priv->regmap, MAX9296A_GMSL1_EN, + MAX9296A_GMSL1_EN_LINK_EN, + FIELD_PREP(MAX9296A_GMSL1_EN_LINK_EN, mask)); + if (ret) + return ret; + + ret = regmap_update_bits(priv->regmap, MAX9296A_CTRL0, + MAX9296A_CTRL0_AUTO_LINK | + MAX9296A_CTRL0_LINK_CFG | + MAX9296A_CTRL0_RESET_ONESHOT, + FIELD_PREP(MAX9296A_CTRL0_LINK_CFG, mask) | + FIELD_PREP(MAX9296A_CTRL0_RESET_ONESHOT, 1)); + if (ret) + return ret; + + if (priv->info->has_per_link_reset) { + ret = max9296a_reset_link(priv, 1); + if (ret) + return ret; + } + + return max9296a_wait_for_link_lock(priv, mask); +} + +static int max9296a_set_link_version(struct max_des *des, + struct max_des_link *link, + enum max_serdes_gmsl_version version) +{ + struct max9296a_priv *priv = des_to_priv(des); + unsigned int index = link->index; + bool gmsl3_en = version == MAX_SERDES_GMSL_3_12GBPS; + unsigned int reg, mask, val; + int ret; + + if (des->info->needs_single_link_version) + index = 0; + + if (index == 0) { + reg = MAX9296A_REG1; + mask = MAX9296A_REG1_RX_RATE_A; + } else { + reg = MAX9296A_REG4; + mask = MAX9296A_REG4_RX_RATE_B; + } + + if (version == MAX_SERDES_GMSL_3_12GBPS) + val = MAX9296A_REG1_RX_RATE_12GBPS; + else if (version == MAX_SERDES_GMSL_2_6GBPS) + val = MAX9296A_REG1_RX_RATE_6GBPS; + else + val = MAX9296A_REG1_RX_RATE_3GBPS; + + ret = regmap_update_bits(priv->regmap, reg, mask, field_prep(mask, val)); + if (ret) + return ret; + + if (!(des->info->versions & BIT(MAX_SERDES_GMSL_3_12GBPS))) + return 0; + + ret = regmap_assign_bits(priv->regmap, MAX9296A_MIPI_TX0(index), + MAX9296A_MIPI_TX0_RX_FEC_EN, gmsl3_en); + if (ret) + return ret; + + ret = regmap_assign_bits(priv->regmap, MAX9296A_REG6, + MAX9296A_REG6_GMSL2_X(index), !gmsl3_en); + if (ret) + return ret; + + return regmap_assign_bits(priv->regmap, MAX9296A_REG4, + MAX9296A_REG4_GMSL3_X(index), gmsl3_en); +} + +static int max9296a_set_tpg_timings(struct max9296a_priv *priv, + const struct max_serdes_tpg_timings *tm) +{ + const struct reg_sequence regs[] = { + REG_SEQUENCE_3(MAX9296A_VS_DLY_2, tm->vs_dly), + REG_SEQUENCE_3(MAX9296A_VS_HIGH_2, tm->vs_high), + REG_SEQUENCE_3(MAX9296A_VS_LOW_2, tm->vs_low), + REG_SEQUENCE_3(MAX9296A_V2H_2, tm->v2h), + REG_SEQUENCE_2(MAX9296A_HS_HIGH_1, tm->hs_high), + REG_SEQUENCE_2(MAX9296A_HS_LOW_1, tm->hs_low), + REG_SEQUENCE_2(MAX9296A_HS_CNT_1, tm->hs_cnt), + REG_SEQUENCE_3(MAX9296A_V2D_2, tm->v2d), + REG_SEQUENCE_2(MAX9296A_DE_HIGH_1, tm->de_high), + REG_SEQUENCE_2(MAX9296A_DE_LOW_1, tm->de_low), + REG_SEQUENCE_2(MAX9296A_DE_CNT_1, tm->de_cnt), + }; + int ret; + + ret = regmap_multi_reg_write(priv->regmap, regs, ARRAY_SIZE(regs)); + if (ret) + return ret; + + return regmap_write(priv->regmap, MAX9296A_PATGEN_0, + FIELD_PREP(MAX9296A_PATGEN_0_VTG_MODE, + MAX9296A_PATGEN_0_VTG_MODE_FREE_RUNNING) | + FIELD_PREP(MAX9296A_PATGEN_0_DE_INV, tm->de_inv) | + FIELD_PREP(MAX9296A_PATGEN_0_HS_INV, tm->hs_inv) | + FIELD_PREP(MAX9296A_PATGEN_0_VS_INV, tm->vs_inv) | + FIELD_PREP(MAX9296A_PATGEN_0_GEN_DE, tm->gen_de) | + FIELD_PREP(MAX9296A_PATGEN_0_GEN_HS, tm->gen_hs) | + FIELD_PREP(MAX9296A_PATGEN_0_GEN_VS, tm->gen_vs)); +} + +static int max9296a_set_tpg_clk(struct max9296a_priv *priv, u32 clock) +{ + bool patgen_clk_src = 0; + unsigned int index; + u8 pin_drv_en; + int ret; + + switch (clock) { + case 25000000: + pin_drv_en = MAX9296A_IO_CHK0_PIN_DRV_EN_0_25MHZ; + break; + case 75000000: + pin_drv_en = MAX9296A_IO_CHK0_PIN_DRV_EN_0_75MHZ; + break; + case 150000000: + pin_drv_en = MAX9296A_IO_CHK0_PIN_DRV_EN_0_USE_PIPE; + patgen_clk_src = MAX9296A_VPRBS_PATGEN_CLK_SRC_150MHZ; + break; + case 600000000: + pin_drv_en = MAX9296A_IO_CHK0_PIN_DRV_EN_0_USE_PIPE; + patgen_clk_src = MAX9296A_VPRBS_PATGEN_CLK_SRC_600MHZ; + break; + case 0: + return 0; + default: + return -EINVAL; + } + + /* TPG data is always injected on link 0, routed to the first pipe. */ + index = max9296a_pipe_id(priv, &priv->des.pipes[0]); + + ret = regmap_update_bits(priv->regmap, MAX9296A_VPRBS(index), + MAX9296A_VPRBS_PATGEN_CLK_SRC, + FIELD_PREP(MAX9296A_VPRBS_PATGEN_CLK_SRC, + patgen_clk_src)); + if (ret) + return ret; + + return regmap_update_bits(priv->regmap, MAX9296A_IO_CHK0, + MAX9296A_IO_CHK0_PIN_DRV_EN_0, + FIELD_PREP(MAX9296A_IO_CHK0_PIN_DRV_EN_0, + pin_drv_en)); +} + +static int max9296a_set_tpg_mode(struct max9296a_priv *priv, bool enable) +{ + unsigned int patgen_mode; + + switch (priv->des.tpg_pattern) { + case MAX_SERDES_TPG_PATTERN_GRADIENT: + patgen_mode = MAX9296A_PATGEN_1_PATGEN_MODE_GRADIENT; + break; + case MAX_SERDES_TPG_PATTERN_CHECKERBOARD: + patgen_mode = MAX9296A_PATGEN_1_PATGEN_MODE_CHECKER; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(priv->regmap, MAX9296A_PATGEN_1, + MAX9296A_PATGEN_1_PATGEN_MODE, + FIELD_PREP(MAX9296A_PATGEN_1_PATGEN_MODE, + enable ? patgen_mode + : MAX9296A_PATGEN_1_PATGEN_MODE_DISABLED)); +} + +static int max9296a_set_tpg(struct max_des *des, + const struct max_serdes_tpg_entry *entry) +{ + struct max9296a_priv *priv = des_to_priv(des); + struct max_serdes_tpg_timings timings = { 0 }; + int ret; + + ret = max_serdes_get_tpg_timings(entry, &timings); + if (ret) + return ret; + + ret = max9296a_set_tpg_timings(priv, &timings); + if (ret) + return ret; + + ret = max9296a_set_tpg_clk(priv, timings.clock); + if (ret) + return ret; + + ret = max9296a_set_tpg_mode(priv, entry); + if (ret) + return ret; + + return regmap_assign_bits(priv->regmap, MAX9296A_MIPI_PHY0, + MAX9296A_MIPI_PHY0_FORCE_CSI_OUT_EN, !!entry); +} + +static const struct max_serdes_tpg_entry max9296a_tpg_entries[] = { + MAX_TPG_ENTRY_640X480P60_RGB888, + MAX_TPG_ENTRY_1920X1080P30_RGB888, + MAX_TPG_ENTRY_1920X1080P60_RGB888, +}; + +#ifdef CONFIG_VIDEO_ADV_DEBUG +#define MAX9296A_DEBUG_OPS \ + .reg_read = max9296a_reg_read, \ + .reg_write = max9296a_reg_write, +#else +#define MAX9296A_DEBUG_OPS +#endif + +#define MAX9296A_COMMON_INFO \ + .num_remaps_per_pipe = 16, \ + .tpg_entries = { \ + .num_entries = ARRAY_SIZE(max9296a_tpg_entries), \ + .entries = max9296a_tpg_entries, \ + }, \ + .tpg_patterns = BIT(MAX_SERDES_TPG_PATTERN_CHECKERBOARD) | \ + BIT(MAX_SERDES_TPG_PATTERN_GRADIENT) + +#define MAX9296A_COMMON_OPS \ + MAX9296A_DEBUG_OPS \ + .log_pipe_status = max9296a_log_pipe_status, \ + .log_phy_status = max9296a_log_phy_status, \ + .set_enable = max9296a_set_enable, \ + .init = max9296a_init, \ + .init_phy = max9296a_init_phy, \ + .set_phy_mode = max9296a_set_phy_mode, \ + .set_phy_enable = max9296a_set_phy_enable, \ + .set_pipe_remap = max9296a_set_pipe_remap, \ + .set_pipe_remaps_enable = max9296a_set_pipe_remaps_enable, \ + .set_pipe_mode = max9296a_set_pipe_mode, \ + .set_tpg = max9296a_set_tpg, \ + .select_links = max9296a_select_links, \ + .set_link_version = max9296a_set_link_version + +static void max9296a_power_off(void *data) +{ + struct max9296a_priv *priv = data; + + gpiod_set_value_cansleep(priv->gpiod_pwdn, 1); +} + +static int max9296a_probe(struct i2c_client *client) +{ + struct regmap_config i2c_regmap = max9296a_i2c_regmap; + struct device *dev = &client->dev; + struct max9296a_priv *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->info = device_get_match_data(dev); + if (!priv->info) { + dev_err(dev, "Failed to get match data\n"); + return -ENODEV; + } + + priv->dev = dev; + priv->client = client; + i2c_set_clientdata(client, priv); + + i2c_regmap.max_register = priv->info->max_register; + priv->regmap = devm_regmap_init_i2c(client, &i2c_regmap); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + priv->gpiod_pwdn = devm_gpiod_get_optional(&client->dev, "powerdown", + GPIOD_OUT_HIGH); + if (IS_ERR(priv->gpiod_pwdn)) + return PTR_ERR(priv->gpiod_pwdn); + + if (priv->gpiod_pwdn) { + /* PWDN must be held for 1us for reset */ + udelay(1); + + gpiod_set_value_cansleep(priv->gpiod_pwdn, 0); + /* Maximum power-up time (tLOCK) 4ms */ + usleep_range(4000, 5000); + + ret = devm_add_action_or_reset(dev, max9296a_power_off, + priv); + if (ret) + return ret; + } + + priv->des.info = priv->info->info; + priv->des.ops = priv->info->ops; + + ret = max9296a_reset(priv); + if (ret) + return ret; + + return max_des_probe(client, &priv->des); +} + +static void max9296a_remove(struct i2c_client *client) +{ + struct max9296a_priv *priv = i2c_get_clientdata(client); + + max_des_remove(&priv->des); +} + +static const struct max_serdes_phys_config max9296a_phys_configs[] = { + { { 4, 4 } }, +}; + +static const struct max_serdes_phys_config max96714_phys_configs[] = { + { { 4 } }, +}; + +static const struct max_des_info max9296a_des_info = { + MAX9296A_COMMON_INFO, + .versions = BIT(MAX_SERDES_GMSL_2_3GBPS) | + BIT(MAX_SERDES_GMSL_2_6GBPS), + .modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE), + .needs_single_link_version = true, + .needs_unique_stream_id = true, + .phys_configs = { + .num_configs = ARRAY_SIZE(max9296a_phys_configs), + .configs = max9296a_phys_configs, + }, + .fix_tx_ids = true, + .use_atr = true, + .num_pipes = 4, + .num_phys = 2, + .num_links = 2, +}; + +static const struct max_des_ops max9296a_ops = { + MAX9296A_COMMON_OPS, + .set_pipe_stream_id = max9296a_set_pipe_stream_id, + .set_pipe_enable = max9296a_set_pipe_enable, +}; + +static const struct max9296a_chip_info max9296a_info = { + .info = &max9296a_des_info, + .ops = &max9296a_ops, + .max_register = 0x1f00, + .phy0_lanes_0_1_on_second_phy = true, + .pipe_hw_ids = { 0, 1, 2, 3 }, + .phy_hw_ids = { 1, 2 }, +}; + +static const struct max_des_info max96714_des_info = { + MAX9296A_COMMON_INFO, + .versions = BIT(MAX_SERDES_GMSL_2_3GBPS) | + BIT(MAX_SERDES_GMSL_2_6GBPS), + .modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE) | + BIT(MAX_SERDES_GMSL_TUNNEL_MODE), + .phys_configs = { + .num_configs = ARRAY_SIZE(max96714_phys_configs), + .configs = max96714_phys_configs, + }, + .tpg_mode = MAX_SERDES_GMSL_PIXEL_MODE, + .num_pipes = 1, + .num_phys = 1, + .num_links = 1, +}; + +static const struct max_des_ops max96714_ops = { + MAX9296A_COMMON_OPS, + .set_pipe_stream_id = max96714_set_pipe_stream_id, + .set_pipe_enable = max96714_set_pipe_enable, + .set_pipe_tunnel_enable = max96714_set_pipe_tunnel_enable, +}; + +/* + * These register writes are described as required in MAX96714 datasheet + * Page 53, Section Register Map, to optimize link performance in 6Gbps + * and 3Gbps links for all cable lengths. + */ +static const struct reg_sequence max96714_rlms_reg_sequence[] = { + { MAX9296A_RLMS3E(0), 0xfd }, + { MAX9296A_RLMS3F(0), 0x3d }, + { MAX9296A_RLMS49(0), 0xf5 }, + { MAX9296A_RLMS7E(0), 0xa8 }, + { MAX9296A_RLMS7F(0), 0x68 }, + { MAX9296A_RLMSA3(0), 0x30 }, + { MAX9296A_RLMSA5(0), 0x70 }, + { MAX9296A_RLMSD8(0), 0x07 }, +}; + +static const struct max9296a_chip_info max96714_info = { + .info = &max96714_des_info, + .ops = &max96714_ops, + .max_register = 0x5011, + .polarity_on_physical_lanes = true, + .supports_phy_log = true, + .rlms_adjust_sequence = max96714_rlms_reg_sequence, + .rlms_adjust_sequence_len = ARRAY_SIZE(max96714_rlms_reg_sequence), + .pipe_hw_ids = { 1 }, + .phy_hw_ids = { 1 }, +}; + +static const struct max_des_info max96714f_des_info = { + MAX9296A_COMMON_INFO, + .versions = BIT(MAX_SERDES_GMSL_2_3GBPS), + .modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE) | + BIT(MAX_SERDES_GMSL_TUNNEL_MODE), + .phys_configs = { + .num_configs = ARRAY_SIZE(max96714_phys_configs), + .configs = max96714_phys_configs, + }, + .tpg_mode = MAX_SERDES_GMSL_PIXEL_MODE, + .num_pipes = 1, + .num_phys = 1, + .num_links = 1, +}; + +static const struct max_des_ops max96714f_ops = { + MAX9296A_COMMON_OPS, + .set_pipe_stream_id = max96714_set_pipe_stream_id, + .set_pipe_enable = max96714_set_pipe_enable, + .set_pipe_tunnel_enable = max96714_set_pipe_tunnel_enable, +}; + +static const struct max9296a_chip_info max96714f_info = { + .info = &max96714f_des_info, + .ops = &max96714f_ops, + .max_register = 0x5011, + .polarity_on_physical_lanes = true, + .supports_phy_log = true, + .rlms_adjust_sequence = max96714_rlms_reg_sequence, + .rlms_adjust_sequence_len = ARRAY_SIZE(max96714_rlms_reg_sequence), + .pipe_hw_ids = { 1 }, + .phy_hw_ids = { 1 }, +}; + +static const struct max_des_info max96716a_des_info = { + MAX9296A_COMMON_INFO, + .versions = BIT(MAX_SERDES_GMSL_2_3GBPS) | + BIT(MAX_SERDES_GMSL_2_6GBPS), + .modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE) | + BIT(MAX_SERDES_GMSL_TUNNEL_MODE), + .use_atr = true, + .phys_configs = { + .num_configs = ARRAY_SIZE(max9296a_phys_configs), + .configs = max9296a_phys_configs, + }, + .tpg_mode = MAX_SERDES_GMSL_PIXEL_MODE, + .num_pipes = 2, + .num_phys = 2, + .num_links = 2, +}; + +static const struct max_des_ops max96716a_ops = { + MAX9296A_COMMON_OPS, + .set_pipe_stream_id = max96714_set_pipe_stream_id, + .set_pipe_link = max96716a_set_pipe_link, + .set_pipe_enable = max96714_set_pipe_enable, + .set_pipe_tunnel_phy = max96716a_set_pipe_tunnel_phy, + .set_pipe_tunnel_enable = max96714_set_pipe_tunnel_enable, +}; + +static const struct max9296a_chip_info max96716a_info = { + .info = &max96716a_des_info, + .ops = &max96716a_ops, + .max_register = 0x52d6, + .has_per_link_lock = true, + .has_per_link_reset = true, + .phy0_lanes_0_1_on_second_phy = true, + .supports_cphy = true, + .supports_phy_log = true, + .pipe_hw_ids = { 1, 2 }, + .phy_hw_ids = { 1, 2 }, +}; + +static const struct max_des_info max96792a_des_info = { + MAX9296A_COMMON_INFO, + .versions = BIT(MAX_SERDES_GMSL_2_3GBPS) | + BIT(MAX_SERDES_GMSL_2_6GBPS) | + BIT(MAX_SERDES_GMSL_3_12GBPS), + .modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE) | + BIT(MAX_SERDES_GMSL_TUNNEL_MODE), + .use_atr = true, + .phys_configs = { + .num_configs = ARRAY_SIZE(max9296a_phys_configs), + .configs = max9296a_phys_configs, + }, + .tpg_mode = MAX_SERDES_GMSL_PIXEL_MODE, + .num_pipes = 2, + .num_phys = 2, + .num_links = 2, +}; + +static const struct max_des_ops max96792a_ops = { + MAX9296A_COMMON_OPS, + .set_pipe_stream_id = max96714_set_pipe_stream_id, + .set_pipe_link = max96716a_set_pipe_link, + .set_pipe_enable = max96714_set_pipe_enable, + .set_pipe_tunnel_phy = max96716a_set_pipe_tunnel_phy, + .set_pipe_tunnel_enable = max96714_set_pipe_tunnel_enable, +}; + +static const struct max9296a_chip_info max96792a_info = { + .info = &max96792a_des_info, + .ops = &max96792a_ops, + .max_register = 0x52d6, + .has_per_link_lock = true, + .has_per_link_reset = true, + .phy0_lanes_0_1_on_second_phy = true, + .supports_cphy = true, + .supports_phy_log = true, + .pipe_hw_ids = { 1, 2 }, + .phy_hw_ids = { 1, 2 }, +}; + +static const struct of_device_id max9296a_of_table[] = { + { .compatible = "maxim,max9296a", .data = &max9296a_info }, + { .compatible = "maxim,max96714", .data = &max96714_info }, + { .compatible = "maxim,max96714f", .data = &max96714f_info }, + { .compatible = "maxim,max96714r", .data = &max96714f_info }, + { .compatible = "maxim,max96716a", .data = &max96716a_info }, + { .compatible = "maxim,max96792a", .data = &max96792a_info }, + { }, +}; +MODULE_DEVICE_TABLE(of, max9296a_of_table); + +static struct i2c_driver max9296a_i2c_driver = { + .driver = { + .name = "max9296a", + .of_match_table = max9296a_of_table, + }, + .probe = max9296a_probe, + .remove = max9296a_remove, +}; + +module_i2c_driver(max9296a_i2c_driver); + +MODULE_IMPORT_NS("MAX_SERDES"); +MODULE_DESCRIPTION("Maxim MAX9296A Dual GMSL2/3 Deserializer Driver"); +MODULE_AUTHOR("Cosmin Tanislav "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/maxim-serdes/max96717.c b/drivers/media/i2c/maxim-serdes/max96717.c new file mode 100644 index 000000000000..e004e71cb768 --- /dev/null +++ b/drivers/media/i2c/maxim-serdes/max96717.c @@ -0,0 +1,1705 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Maxim MAX96717 GMSL2 Serializer Driver + * + * Copyright (C) 2025 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "max_ser.h" + +#define MAX96717_REG0 0x0 + +#define MAX96717_REG2 0x2 +#define MAX96717_REG2_VID_TX_EN_P(p) BIT(4 + (p)) + +#define MAX96717_REG3 0x3 +#define MAX96717_REG3_RCLKSEL GENMASK(1, 0) +#define MAX96717_REG3_RCLK_ALT BIT(2) + +#define MAX96717_REG6 0x6 +#define MAX96717_REG6_RCLKEN BIT(5) + +#define MAX96717_I2C_2(x) (0x42 + (x) * 0x2) +#define MAX96717_I2C_2_SRC GENMASK(7, 1) + +#define MAX96717_I2C_3(x) (0x43 + (x) * 0x2) +#define MAX96717_I2C_3_DST GENMASK(7, 1) + +#define MAX96717_TX3(p) (0x53 + (p) * 0x4) +#define MAX96717_TX3_TX_STR_SEL GENMASK(1, 0) + +#define MAX96717_VIDEO_TX0(p) (0x100 + (p) * 0x8) +#define MAX96717_VIDEO_TX0_AUTO_BPP BIT(3) + +#define MAX96717_VIDEO_TX1(p) (0x101 + (p) * 0x8) +#define MAX96717_VIDEO_TX1_BPP GENMASK(5, 0) + +#define MAX96717_VIDEO_TX2(p) (0x102 + (p) * 0x8) +#define MAX96717_VIDEO_TX2_PCLKDET BIT(7) +#define MAX96717_VIDEO_TX2_DRIFT_DET_EN BIT(1) + +#define MAX96717_VTX0(p) (0x1c8 + (p) * 0x43) +#define MAX96717_VTX0_VTG_MODE GENMASK(1, 0) +#define MAX96717_VTX0_VTG_MODE_FREE_RUNNING 0b11 +#define MAX96717_VTX0_DE_INV BIT(2) +#define MAX96717_VTX0_HS_INV BIT(3) +#define MAX96717_VTX0_VS_INV BIT(4) +#define MAX96717_VTX0_GEN_DE BIT(5) +#define MAX96717_VTX0_GEN_HS BIT(6) +#define MAX96717_VTX0_GEN_VS BIT(7) + +#define MAX96717_VTX1(p) (0x1c9 + (p) * 0x43) +#define MAX96717_VTX1_PATGEN_CLK_SRC GENMASK(3, 1) +#define MAX96717_VTX1_PATGEN_CLK_SRC_25MHZ 0b100 +#define MAX96717_VTX1_PATGEN_CLK_SRC_75MHZ 0b101 +#define MAX96717_VTX1_PATGEN_CLK_SRC_150MHZ 0b110 +#define MAX96717_VTX1_PATGEN_CLK_SRC_375MHZ 0b111 + +#define MAX96717_VTX2_VS_DLY_2(p) (0x1ca + (p) * 0x43) +#define MAX96717_VTX5_VS_HIGH_2(p) (0x1cd + (p) * 0x43) +#define MAX96717_VTX8_VS_LOW_2(p) (0x1d0 + (p) * 0x43) +#define MAX96717_VTX11_V2H_2(p) (0x1d3 + (p) * 0x43) +#define MAX96717_VTX14_HS_HIGH_1(p) (0x1d6 + (p) * 0x43) +#define MAX96717_VTX16_HS_LOW_1(p) (0x1d8 + (p) * 0x43) +#define MAX96717_VTX18_HS_CNT_1(p) (0x1da + (p) * 0x43) +#define MAX96717_VTX20_V2D_2(p) (0x1dc + (p) * 0x43) +#define MAX96717_VTX23_DE_HIGH_1(p) (0x1df + (p) * 0x43) +#define MAX96717_VTX25_DE_LOW_1(p) (0x1e1 + (p) * 0x43) +#define MAX96717_VTX27_DE_CNT_1(p) (0x1e3 + (p) * 0x43) +#define MAX96717_VTX29(p) (0x1e5 + (p) * 0x43) + +#define MAX96717_VTX29_PATGEN_MODE GENMASK(1, 0) +#define MAX96717_VTX29_PATGEN_MODE_DISABLED 0b00 +#define MAX96717_VTX29_PATGEN_MODE_CHECKER 0b01 +#define MAX96717_VTX29_PATGEN_MODE_GRADIENT 0b10 + +#define MAX96717_VTX30_GRAD_INCR(p) (0x1e6 + (p) * 0x43) +#define MAX96717_VTX31_CHKR_A_L(p) (0x1e7 + (p) * 0x43) +#define MAX96717_VTX34_CHKR_B_L(p) (0x1ea + (p) * 0x43) +#define MAX96717_VTX37_CHKR_RPT_A(p) (0x1ed + (p) * 0x43) +#define MAX96717_VTX38_CHKR_RPT_B(p) (0x1ee + (p) * 0x43) +#define MAX96717_VTX39_CHKR_ALT(p) (0x1ef + (p) * 0x43) + +#define MAX96717_GPIO_A(x) (0x2be + (x) * 0x3) +#define MAX96717_GPIO_A_GPIO_OUT_DIS BIT(0) +#define MAX96717_GPIO_A_GPIO_TX_EN BIT(1) +#define MAX96717_GPIO_A_GPIO_RX_EN BIT(2) +#define MAX96717_GPIO_A_GPIO_IN BIT(3) +#define MAX96717_GPIO_A_GPIO_OUT BIT(4) +#define MAX96717_GPIO_A_TX_COMP_EN BIT(5) +#define MAX96717_GPIO_A_RES_CFG BIT(7) + +#define MAX96717_GPIO_B(x) (0x2bf + (x) * 0x3) +#define MAX96717_GPIO_B_GPIO_TX_ID GENMASK(4, 0) +#define MAX96717_GPIO_B_OUT_TYPE BIT(5) +#define MAX96717_GPIO_B_PULL_UPDN_SEL GENMASK(7, 6) +#define MAX96717_GPIO_B_PULL_UPDN_SEL_NONE 0b00 +#define MAX96717_GPIO_B_PULL_UPDN_SEL_PU 0b01 +#define MAX96717_GPIO_B_PULL_UPDN_SEL_PD 0b10 + +#define MAX96717_GPIO_C(x) (0x2c0 + (x) * 0x3) +#define MAX96717_GPIO_C_GPIO_RX_ID GENMASK(4, 0) + +#define MAX96717_CMU2 0x302 +#define MAX96717_CMU2_PFDDIV_RSHORT GENMASK(6, 4) +#define MAX96717_CMU2_PFDDIV_RSHORT_1_1V 0b001 + +#define MAX96717_FRONTTOP_0 0x308 +#define MAX96717_FRONTTOP_0_CLK_SEL_P(x) BIT(x) +#define MAX96717_FRONTTOP_0_START_PORT(x) BIT((x) + 4) + +#define MAX96717_FRONTTOP_1(p) (0x309 + (p) * 0x2) +#define MAX96717_FRONTTOP_2(p) (0x30a + (p) * 0x2) + +#define MAX96717_FRONTTOP_9 0x311 +#define MAX96717_FRONTTOP_9_START_PORT(p, x) BIT((p) + (x) * 4) + +#define MAX96717_FRONTTOP_10 0x312 +#define MAX96717_FRONTTOP_10_BPP8DBL(p) BIT(p) + +#define MAX96717_FRONTTOP_11 0x313 +#define MAX96717_FRONTTOP_11_BPP10DBL(p) BIT(p) +#define MAX96717_FRONTTOP_11_BPP12DBL(p) BIT((p) + 4) + +#define MAX96717_FRONTTOP_12(p, x) (0x314 + (p) * 0x2 + (x)) +#define MAX96717_MEM_DT_SEL GENMASK(5, 0) +#define MAX96717_MEM_DT_EN BIT(6) + +#define MAX96717_FRONTTOP_20(p) (0x31c + (p) * 0x1) +#define MAX96717_FRONTTOP_20_SOFT_BPP_EN BIT(5) +#define MAX96717_FRONTTOP_20_SOFT_BPP GENMASK(4, 0) + +#define MAX96717_MIPI_RX0 0x330 +#define MAX96717_MIPI_RX0_NONCONTCLK_EN BIT(6) + +#define MAX96717_MIPI_RX1 0x331 +#define MAX96717_MIPI_RX1_CTRL_NUM_LANES GENMASK(5, 4) + +#define MAX96717_MIPI_RX2 0x332 +#define MAX96717_MIPI_RX2_PHY1_LANE_MAP GENMASK(7, 4) + +#define MAX96717_MIPI_RX3 0x333 +#define MAX96717_MIPI_RX3_PHY2_LANE_MAP GENMASK(3, 0) + +#define MAX96717_MIPI_RX4 0x334 +#define MAX96717_MIPI_RX4_PHY1_POL_MAP GENMASK(5, 4) + +#define MAX96717_MIPI_RX5 0x335 +#define MAX96717_MIPI_RX5_PHY2_POL_MAP GENMASK(1, 0) +#define MAX96717_MIPI_RX5_PHY2_POL_MAP_CLK BIT(2) + +#define MAX96717_EXTA(x) (0x3dc + (x)) + +#define MAX96717_EXT11 0x383 +#define MAX96717_EXT11_TUN_MODE BIT(7) + +#define MAX96717_EXT21 0x38d +#define MAX96717_EXT22 0x38e +#define MAX96717_EXT23 0x38f +#define MAX96717_EXT24 0x390 + +#define MAX96717_REF_VTG0 0x3f0 +#define MAX96717_REF_VTG0_REFGEN_EN BIT(0) +#define MAX96717_REF_VTG0_REFGEN_RST BIT(1) +#define MAX96717_REF_VTG0_REFGEN_PREDEF_FREQ_ALT\ + BIT(3) +#define MAX96717_REF_VTG0_REFGEN_PREDEF_FREQ GENMASK(5, 4) +#define MAX96717_REF_VTG0_REFGEN_PREDEF_EN BIT(6) + +#define MAX96717_PIO_SLEW_0 0x56f +#define MAX96717_PIO_SLEW_0_PIO00_SLEW GENMASK(1, 0) +#define MAX96717_PIO_SLEW_0_PIO01_SLEW GENMASK(3, 2) +#define MAX96717_PIO_SLEW_0_PIO02_SLEW GENMASK(5, 4) + +#define MAX96717_PIO_SLEW_1 0x570 +#define MAX96717_PIO_SLEW_1_PIO05_SLEW GENMASK(3, 2) +#define MAX96717_PIO_SLEW_1_PIO06_SLEW GENMASK(5, 4) + +#define MAX96717_PIO_SLEW_2 0x571 +#define MAX96717_PIO_SLEW_2_PIO010_SLEW GENMASK(5, 4) +#define MAX96717_PIO_SLEW_2_PIO011_SLEW GENMASK(7, 6) + +#define MAX96717_PIO_SLEW_FASTEST 0b00 + +#define MAX96717_BIAS_PULL_STRENGTH_1000000_OHM 1000000U +#define MAX96717_BIAS_PULL_STRENGTH_40000_OHM 40000U + +#define MAX96717_DEFAULT_CLKOUT_RATE 24000000UL + +#define MAX96717_NAME "max96717" +#define MAX96717_PINCTRL_NAME MAX96717_NAME "-pinctrl" +#define MAX96717_GPIOCHIP_NAME MAX96717_NAME "-gpiochip" +#define MAX96717_GPIO_NUM 11 +#define MAX96717_RCLK_ALT_MFP 2 +#define MAX96717_RCLK_MFP 4 +#define MAX96717_PIPES_NUM 4 +#define MAX96717_PHYS_NUM 2 + +struct max96717_priv { + struct max_ser ser; + struct pinctrl_desc pctldesc; + struct gpio_chip gc; + const struct max96717_chip_info *info; + + struct device *dev; + struct i2c_client *client; + struct regmap *regmap; + struct pinctrl_dev *pctldev; + + struct clk_hw clk_hw; + u8 pll_predef_index; + bool rclkout_muxed; +}; + +struct max96717_chip_info { + bool supports_3_data_lanes; + bool supports_noncontinuous_clock; + bool supports_pkt_cnt; + unsigned int modes; + unsigned int num_pipes; + unsigned int num_dts_per_pipe; + unsigned int pipe_hw_ids[MAX96717_PIPES_NUM]; + unsigned int num_phys; + unsigned int phy_hw_ids[MAX96717_PHYS_NUM]; +}; + +#define ser_to_priv(_ser) \ + container_of(_ser, struct max96717_priv, ser) + +static inline struct max96717_priv *clk_hw_to_priv(struct clk_hw *hw) +{ + return container_of(hw, struct max96717_priv, clk_hw); +} + +static const struct regmap_config max96717_i2c_regmap = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0x1f00, +}; + +static int max96717_wait_for_device(struct max96717_priv *priv) +{ + unsigned int val; + int ret, err; + + err = read_poll_timeout(regmap_read, ret, + !ret && val, + 100 * USEC_PER_MSEC, + 1 * USEC_PER_SEC, false, + priv->regmap, MAX96717_REG0, &val); + if (err) + dev_err(priv->dev, "Timeout waiting for serializer: %d\n", ret); + + return err; +} + +#define MAX96717_PIN(n) \ + PINCTRL_PIN(n, "mfp" __stringify(n)) + +static const struct pinctrl_pin_desc max96717_pins[] = { + MAX96717_PIN(0), + MAX96717_PIN(1), + MAX96717_PIN(2), + MAX96717_PIN(3), + MAX96717_PIN(4), + MAX96717_PIN(5), + MAX96717_PIN(6), + MAX96717_PIN(7), + MAX96717_PIN(8), + MAX96717_PIN(9), + MAX96717_PIN(10), +}; + +#define MAX96717_GROUP_PINS(name, ...) \ + static const unsigned int name ## _pins[] = { __VA_ARGS__ } + +MAX96717_GROUP_PINS(mfp0, 0); +MAX96717_GROUP_PINS(mfp1, 1); +MAX96717_GROUP_PINS(mfp2, 2); +MAX96717_GROUP_PINS(mfp3, 3); +MAX96717_GROUP_PINS(mfp4, 4); +MAX96717_GROUP_PINS(mfp5, 5); +MAX96717_GROUP_PINS(mfp6, 6); +MAX96717_GROUP_PINS(mfp7, 7); +MAX96717_GROUP_PINS(mfp8, 8); +MAX96717_GROUP_PINS(mfp9, 9); +MAX96717_GROUP_PINS(mfp10, 10); + +#define MAX96717_GROUP(name) \ + PINCTRL_PINGROUP(__stringify(name), name ## _pins, ARRAY_SIZE(name ## _pins)) + +static const struct pingroup max96717_ctrl_groups[] = { + MAX96717_GROUP(mfp0), + MAX96717_GROUP(mfp1), + MAX96717_GROUP(mfp2), + MAX96717_GROUP(mfp3), + MAX96717_GROUP(mfp4), + MAX96717_GROUP(mfp5), + MAX96717_GROUP(mfp6), + MAX96717_GROUP(mfp7), + MAX96717_GROUP(mfp8), + MAX96717_GROUP(mfp9), + MAX96717_GROUP(mfp10), +}; + +#define MAX96717_FUNC_GROUPS(name, ...) \ + static const char * const name ## _groups[] = { __VA_ARGS__ } + +MAX96717_FUNC_GROUPS(gpio, "mfp0", "mfp1", "mfp2", "mfp3", "mfp4", "mfp5", + "mfp6", "mfp7", "mfp8", "mfp9", "mfp10"); +MAX96717_FUNC_GROUPS(rclkout, "mfp2", "mfp4"); + +enum max96717_func { + max96717_func_gpio, + max96717_func_rclkout, +}; + +#define MAX96717_FUNC(name) \ + [max96717_func_ ## name] = \ + PINCTRL_PINFUNCTION(__stringify(name), name ## _groups, \ + ARRAY_SIZE(name ## _groups)) + +static const struct pinfunction max96717_functions[] = { + MAX96717_FUNC(gpio), + MAX96717_FUNC(rclkout), +}; + +#define MAX96717_PINCTRL_X(x) (PIN_CONFIG_END + (x)) +#define MAX96717_PINCTRL_JITTER_COMPENSATION_EN MAX96717_PINCTRL_X(1) +#define MAX96717_PINCTRL_TX_ID MAX96717_PINCTRL_X(2) +#define MAX96717_PINCTRL_RX_ID MAX96717_PINCTRL_X(3) +#define MAX96717_PINCTRL_PULL_STRENGTH_HIGH MAX96717_PINCTRL_X(4) +#define MAX96717_PINCTRL_INPUT_VALUE MAX96717_PINCTRL_X(5) +#define MAX96717_PINCTRL_TX_EN MAX96717_PINCTRL_X(6) +#define MAX96717_PINCTRL_RX_EN MAX96717_PINCTRL_X(7) + +static const struct pinconf_generic_params max96717_cfg_params[] = { + { "maxim,jitter-compensation", MAX96717_PINCTRL_JITTER_COMPENSATION_EN, 1 }, + { "maxim,tx-id", MAX96717_PINCTRL_TX_ID, 0 }, + { "maxim,rx-id", MAX96717_PINCTRL_RX_ID, 0 }, +}; + +static int max96717_ctrl_get_groups_count(struct pinctrl_dev *pctldev) +{ + return ARRAY_SIZE(max96717_ctrl_groups); +} + +static const char *max96717_ctrl_get_group_name(struct pinctrl_dev *pctldev, + unsigned int selector) +{ + return max96717_ctrl_groups[selector].name; +} + +static int max96717_ctrl_get_group_pins(struct pinctrl_dev *pctldev, + unsigned int selector, + const unsigned int **pins, + unsigned int *num_pins) +{ + *pins = (unsigned int *)max96717_ctrl_groups[selector].pins; + *num_pins = max96717_ctrl_groups[selector].npins; + + return 0; +} + +static int max96717_get_pin_config_reg(unsigned int offset, u32 param, + unsigned int *reg, unsigned int *mask, + unsigned int *val) +{ + *reg = MAX96717_GPIO_A(offset); + + switch (param) { + case PIN_CONFIG_OUTPUT_ENABLE: + *mask = MAX96717_GPIO_A_GPIO_OUT_DIS; + *val = 0b0; + return 0; + case PIN_CONFIG_INPUT_ENABLE: + *mask = MAX96717_GPIO_A_GPIO_OUT_DIS; + *val = 0b1; + return 0; + case MAX96717_PINCTRL_TX_EN: + *mask = MAX96717_GPIO_A_GPIO_TX_EN; + *val = 0b1; + return 0; + case MAX96717_PINCTRL_RX_EN: + *mask = MAX96717_GPIO_A_GPIO_RX_EN; + *val = 0b1; + return 0; + case MAX96717_PINCTRL_INPUT_VALUE: + *mask = MAX96717_GPIO_A_GPIO_IN; + *val = 0b1; + return 0; + case PIN_CONFIG_LEVEL: + *mask = MAX96717_GPIO_A_GPIO_OUT; + *val = 0b1; + return 0; + case MAX96717_PINCTRL_JITTER_COMPENSATION_EN: + *mask = MAX96717_GPIO_A_TX_COMP_EN; + *val = 0b1; + return 0; + case MAX96717_PINCTRL_PULL_STRENGTH_HIGH: + *mask = MAX96717_GPIO_A_RES_CFG; + *val = 0b1; + return 0; + } + + *reg = MAX96717_GPIO_B(offset); + + switch (param) { + case MAX96717_PINCTRL_TX_ID: + *mask = MAX96717_GPIO_B_GPIO_TX_ID; + return 0; + case PIN_CONFIG_DRIVE_OPEN_DRAIN: + *mask = MAX96717_GPIO_B_OUT_TYPE; + *val = 0b0; + return 0; + case PIN_CONFIG_DRIVE_PUSH_PULL: + *mask = MAX96717_GPIO_B_OUT_TYPE; + *val = 0b1; + return 0; + case PIN_CONFIG_BIAS_DISABLE: + *mask = MAX96717_GPIO_B_PULL_UPDN_SEL; + *val = MAX96717_GPIO_B_PULL_UPDN_SEL_NONE; + return 0; + case PIN_CONFIG_BIAS_PULL_DOWN: + *mask = MAX96717_GPIO_B_PULL_UPDN_SEL; + *val = MAX96717_GPIO_B_PULL_UPDN_SEL_PD; + return 0; + case PIN_CONFIG_BIAS_PULL_UP: + *mask = MAX96717_GPIO_B_PULL_UPDN_SEL; + *val = MAX96717_GPIO_B_PULL_UPDN_SEL_PU; + return 0; + } + + switch (param) { + case PIN_CONFIG_SLEW_RATE: + if (offset < 3) { + *reg = MAX96717_PIO_SLEW_0; + if (offset == 0) + *mask = MAX96717_PIO_SLEW_0_PIO00_SLEW; + else if (offset == 1) + *mask = MAX96717_PIO_SLEW_0_PIO01_SLEW; + else + *mask = MAX96717_PIO_SLEW_0_PIO02_SLEW; + } else if (offset < 5) { + *reg = MAX96717_PIO_SLEW_1; + if (offset == 3) + *mask = MAX96717_PIO_SLEW_1_PIO05_SLEW; + else + *mask = MAX96717_PIO_SLEW_1_PIO06_SLEW; + } else if (offset < 7) { + return -EINVAL; + } else if (offset < 9) { + *reg = MAX96717_PIO_SLEW_2; + if (offset == 7) + *mask = MAX96717_PIO_SLEW_2_PIO010_SLEW; + else + *mask = MAX96717_PIO_SLEW_2_PIO011_SLEW; + } else { + return -EINVAL; + } + return 0; + case MAX96717_PINCTRL_RX_ID: + *reg = MAX96717_GPIO_C(offset); + *mask = MAX96717_GPIO_C_GPIO_RX_ID; + return 0; + default: + return -ENOTSUPP; + } +} + +static int max96717_conf_pin_config_get(struct pinctrl_dev *pctldev, + unsigned int offset, + unsigned long *config) +{ + struct max96717_priv *priv = pinctrl_dev_get_drvdata(pctldev); + u32 param = pinconf_to_config_param(*config); + unsigned int reg, mask, val, en_val; + int ret; + + ret = max96717_get_pin_config_reg(offset, param, ®, &mask, &en_val); + if (ret) + return ret; + + switch (param) { + case PIN_CONFIG_DRIVE_OPEN_DRAIN: + case PIN_CONFIG_DRIVE_PUSH_PULL: + case PIN_CONFIG_BIAS_DISABLE: + case PIN_CONFIG_BIAS_PULL_DOWN: + case PIN_CONFIG_BIAS_PULL_UP: + case MAX96717_PINCTRL_JITTER_COMPENSATION_EN: + case MAX96717_PINCTRL_TX_EN: + case MAX96717_PINCTRL_RX_EN: + ret = regmap_read(priv->regmap, reg, &val); + if (ret) + return ret; + + val = field_get(mask, val) == en_val; + if (!val) + return -EINVAL; + + break; + case PIN_CONFIG_OUTPUT_ENABLE: + case PIN_CONFIG_INPUT_ENABLE: + case MAX96717_PINCTRL_PULL_STRENGTH_HIGH: + case MAX96717_PINCTRL_INPUT_VALUE: + case PIN_CONFIG_LEVEL: + ret = regmap_read(priv->regmap, reg, &val); + if (ret) + return ret; + + val = field_get(mask, val) == en_val; + break; + case MAX96717_PINCTRL_TX_ID: + case MAX96717_PINCTRL_RX_ID: + case PIN_CONFIG_SLEW_RATE: + ret = regmap_read(priv->regmap, reg, &val); + if (ret) + return ret; + + val = field_get(mask, val); + break; + default: + return -ENOTSUPP; + } + + switch (param) { + case PIN_CONFIG_BIAS_PULL_DOWN: + case PIN_CONFIG_BIAS_PULL_UP: + *config = pinconf_to_config_packed(MAX96717_PINCTRL_PULL_STRENGTH_HIGH, 0); + + ret = max96717_conf_pin_config_get(pctldev, offset, config); + if (ret) + return ret; + + val = pinconf_to_config_argument(*config); + if (val) + val = MAX96717_BIAS_PULL_STRENGTH_1000000_OHM; + else + val = MAX96717_BIAS_PULL_STRENGTH_40000_OHM; + + break; + case MAX96717_PINCTRL_TX_ID: + *config = pinconf_to_config_packed(MAX96717_PINCTRL_TX_EN, 0); + + ret = max96717_conf_pin_config_get(pctldev, offset, config); + if (ret) + return ret; + + break; + case MAX96717_PINCTRL_RX_ID: + *config = pinconf_to_config_packed(MAX96717_PINCTRL_RX_EN, 0); + + ret = max96717_conf_pin_config_get(pctldev, offset, config); + if (ret) + return ret; + + break; + default: + break; + } + + *config = pinconf_to_config_packed(param, val); + + return 0; +} + +static int max96717_conf_pin_config_set_one(struct max96717_priv *priv, + unsigned int offset, + unsigned long config) +{ + u32 param = pinconf_to_config_param(config); + u32 arg = pinconf_to_config_argument(config); + unsigned int reg, mask, val, en_val; + int ret; + + ret = max96717_get_pin_config_reg(offset, param, ®, &mask, &en_val); + if (ret) + return ret; + + switch (param) { + case PIN_CONFIG_DRIVE_OPEN_DRAIN: + case PIN_CONFIG_DRIVE_PUSH_PULL: + case PIN_CONFIG_BIAS_DISABLE: + case PIN_CONFIG_BIAS_PULL_DOWN: + case PIN_CONFIG_BIAS_PULL_UP: + val = field_prep(mask, en_val); + + ret = regmap_update_bits(priv->regmap, reg, mask, val); + break; + case MAX96717_PINCTRL_JITTER_COMPENSATION_EN: + case MAX96717_PINCTRL_PULL_STRENGTH_HIGH: + case MAX96717_PINCTRL_TX_EN: + case MAX96717_PINCTRL_RX_EN: + case PIN_CONFIG_OUTPUT_ENABLE: + case PIN_CONFIG_INPUT_ENABLE: + case PIN_CONFIG_LEVEL: + val = field_prep(mask, arg ? en_val : ~en_val); + + ret = regmap_update_bits(priv->regmap, reg, mask, val); + break; + case MAX96717_PINCTRL_TX_ID: + case MAX96717_PINCTRL_RX_ID: + case PIN_CONFIG_SLEW_RATE: + val = field_prep(mask, arg); + + ret = regmap_update_bits(priv->regmap, reg, mask, val); + break; + default: + return -ENOTSUPP; + } + + if (ret) + return ret; + + switch (param) { + case PIN_CONFIG_BIAS_PULL_DOWN: + case PIN_CONFIG_BIAS_PULL_UP: + arg = arg >= MAX96717_BIAS_PULL_STRENGTH_1000000_OHM; + config = pinconf_to_config_packed(MAX96717_PINCTRL_PULL_STRENGTH_HIGH, arg); + return max96717_conf_pin_config_set_one(priv, offset, config); + case PIN_CONFIG_LEVEL: + config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT_ENABLE, 1); + return max96717_conf_pin_config_set_one(priv, offset, config); + case PIN_CONFIG_OUTPUT_ENABLE: + config = pinconf_to_config_packed(MAX96717_PINCTRL_RX_EN, 0); + return max96717_conf_pin_config_set_one(priv, offset, config); + case MAX96717_PINCTRL_TX_ID: + config = pinconf_to_config_packed(MAX96717_PINCTRL_TX_EN, 1); + return max96717_conf_pin_config_set_one(priv, offset, config); + case MAX96717_PINCTRL_RX_ID: + config = pinconf_to_config_packed(MAX96717_PINCTRL_RX_EN, 1); + return max96717_conf_pin_config_set_one(priv, offset, config); + default: + break; + } + + return 0; +} + +static int max96717_conf_pin_config_set(struct pinctrl_dev *pctldev, + unsigned int offset, + unsigned long *configs, + unsigned int num_configs) +{ + struct max96717_priv *priv = pinctrl_dev_get_drvdata(pctldev); + int ret; + + while (num_configs--) { + unsigned long config = *configs; + + ret = max96717_conf_pin_config_set_one(priv, offset, config); + if (ret) + return ret; + + configs++; + } + + return 0; +} + +static int max96717_mux_get_functions_count(struct pinctrl_dev *pctldev) +{ + return ARRAY_SIZE(max96717_functions); +} + +static const char *max96717_mux_get_function_name(struct pinctrl_dev *pctldev, + unsigned int selector) +{ + return max96717_functions[selector].name; +} + +static int max96717_mux_get_groups(struct pinctrl_dev *pctldev, + unsigned int selector, + const char * const **groups, + unsigned int * const num_groups) +{ + *groups = max96717_functions[selector].groups; + *num_groups = max96717_functions[selector].ngroups; + + return 0; +} + +static int max96717_mux_set_rclkout(struct max96717_priv *priv, unsigned int group) +{ + unsigned long config; + int ret; + + config = pinconf_to_config_packed(PIN_CONFIG_SLEW_RATE, + MAX96717_PIO_SLEW_FASTEST); + ret = max96717_conf_pin_config_set_one(priv, group, config); + if (ret) + return ret; + + ret = regmap_assign_bits(priv->regmap, MAX96717_REG3, + MAX96717_REG3_RCLK_ALT, + group == MAX96717_RCLK_ALT_MFP); + if (ret) + return ret; + + priv->rclkout_muxed = true; + + return 0; +} + +static int max96717_mux_set(struct pinctrl_dev *pctldev, unsigned int selector, + unsigned int group) +{ + struct max96717_priv *priv = pinctrl_dev_get_drvdata(pctldev); + + switch (selector) { + case max96717_func_rclkout: + return max96717_mux_set_rclkout(priv, group); + } + + return 0; +} + +static int max96717_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + unsigned long config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT_ENABLE, 0); + struct max96717_priv *priv = gpiochip_get_data(gc); + int ret; + + ret = max96717_conf_pin_config_get(priv->pctldev, offset, &config); + if (ret) + return ret; + + return pinconf_to_config_argument(config) ? GPIO_LINE_DIRECTION_OUT + : GPIO_LINE_DIRECTION_IN; +} + +static int max96717_gpio_direction_input(struct gpio_chip *gc, unsigned int offset) +{ + unsigned long config = pinconf_to_config_packed(PIN_CONFIG_INPUT_ENABLE, 1); + struct max96717_priv *priv = gpiochip_get_data(gc); + + return max96717_conf_pin_config_set_one(priv, offset, config); +} + +static int max96717_gpio_direction_output(struct gpio_chip *gc, unsigned int offset, + int value) +{ + unsigned long config = pinconf_to_config_packed(PIN_CONFIG_LEVEL, value); + struct max96717_priv *priv = gpiochip_get_data(gc); + + return max96717_conf_pin_config_set_one(priv, offset, config); +} + +static int max96717_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + unsigned long config = pinconf_to_config_packed(MAX96717_PINCTRL_INPUT_VALUE, 0); + struct max96717_priv *priv = gpiochip_get_data(gc); + int ret; + + ret = max96717_conf_pin_config_get(priv->pctldev, offset, &config); + if (ret) + return ret; + + return pinconf_to_config_argument(config); +} + +static int max96717_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) +{ + unsigned long config = pinconf_to_config_packed(PIN_CONFIG_LEVEL, value); + struct max96717_priv *priv = gpiochip_get_data(gc); + + return max96717_conf_pin_config_set_one(priv, offset, config); +} + +static unsigned int max96717_pipe_id(struct max96717_priv *priv, + struct max_ser_pipe *pipe) +{ + return priv->info->pipe_hw_ids[pipe->index]; +} + +static unsigned int max96717_phy_id(struct max96717_priv *priv, + struct max_ser_phy *phy) +{ + return priv->info->phy_hw_ids[phy->index]; +} + +static int max96717_set_pipe_enable(struct max_ser *ser, + struct max_ser_pipe *pipe, bool enable) +{ + struct max96717_priv *priv = ser_to_priv(ser); + unsigned int index = max96717_pipe_id(priv, pipe); + unsigned int mask = MAX96717_REG2_VID_TX_EN_P(index); + + return regmap_assign_bits(priv->regmap, MAX96717_REG2, mask, enable); +} + +static int __maybe_unused max96717_reg_read(struct max_ser *ser, unsigned int reg, + unsigned int *val) +{ + struct max96717_priv *priv = ser_to_priv(ser); + + return regmap_read(priv->regmap, reg, val); +} + +static int __maybe_unused max96717_reg_write(struct max_ser *ser, unsigned int reg, + unsigned int val) +{ + struct max96717_priv *priv = ser_to_priv(ser); + + return regmap_write(priv->regmap, reg, val); +} + +static int max96717_set_pipe_dt_en(struct max_ser *ser, struct max_ser_pipe *pipe, + unsigned int i, bool enable) +{ + struct max96717_priv *priv = ser_to_priv(ser); + unsigned int index = max96717_pipe_id(priv, pipe); + unsigned int reg; + + if (i < 2) + reg = MAX96717_FRONTTOP_12(index, i); + else + /* + * DT 7 and 8 are only supported on MAX96717, no need for pipe + * index to be taken into account. + */ + reg = MAX96717_EXTA(i - 2); + + return regmap_assign_bits(priv->regmap, reg, MAX96717_MEM_DT_EN, enable); +} + +static int max96717_set_pipe_dt(struct max_ser *ser, struct max_ser_pipe *pipe, + unsigned int i, unsigned int dt) +{ + struct max96717_priv *priv = ser_to_priv(ser); + unsigned int index = max96717_pipe_id(priv, pipe); + unsigned int reg; + + if (i < 2) + reg = MAX96717_FRONTTOP_12(index, i); + else + reg = MAX96717_EXTA(i - 2); + + return regmap_update_bits(priv->regmap, reg, MAX96717_MEM_DT_SEL, + FIELD_PREP(MAX96717_MEM_DT_SEL, dt)); +} + +static int max96717_set_pipe_vcs(struct max_ser *ser, + struct max_ser_pipe *pipe, + unsigned int vcs) +{ + struct max96717_priv *priv = ser_to_priv(ser); + unsigned int index = max96717_pipe_id(priv, pipe); + int ret; + + ret = regmap_write(priv->regmap, MAX96717_FRONTTOP_1(index), + (vcs >> 0) & 0xff); + if (ret) + return ret; + + return regmap_write(priv->regmap, MAX96717_FRONTTOP_2(index), + (vcs >> 8) & 0xff); +} + +static int max96717_log_status(struct max_ser *ser) +{ + struct max96717_priv *priv = ser_to_priv(ser); + unsigned int val; + int ret; + + if (!(priv->info->modes & BIT(MAX_SERDES_GMSL_TUNNEL_MODE))) + return 0; + + ret = regmap_read(priv->regmap, MAX96717_EXT23, &val); + if (ret) + return ret; + + dev_info(priv->dev, "tun_pkt_cnt: %u\n", val); + + return 0; +} + +static int max96717_log_pipe_status(struct max_ser *ser, + struct max_ser_pipe *pipe) +{ + struct max96717_priv *priv = ser_to_priv(ser); + unsigned int index = max96717_pipe_id(priv, pipe); + unsigned int val; + int ret; + + ret = regmap_read(priv->regmap, MAX96717_VIDEO_TX2(index), &val); + if (ret) + return ret; + + dev_info(priv->dev, "\tpclkdet: %u\n", + !!(val & MAX96717_VIDEO_TX2_PCLKDET)); + + return 0; +} + +static int max96717_log_phy_status(struct max_ser *ser, + struct max_ser_phy *phy) +{ + struct max96717_priv *priv = ser_to_priv(ser); + unsigned int val; + int ret; + + if (!priv->info->supports_pkt_cnt) + return 0; + + ret = regmap_read(priv->regmap, MAX96717_EXT21, &val); + if (ret) + return ret; + + dev_info(priv->dev, "\tphy_pkt_cnt: %u\n", val); + + ret = regmap_read(priv->regmap, MAX96717_EXT22, &val); + if (ret) + return ret; + + dev_info(priv->dev, "\tcsi_pkt_cnt: %u\n", val); + + ret = regmap_read(priv->regmap, MAX96717_EXT24, &val); + if (ret) + return ret; + + dev_info(priv->dev, "\tphy_clk_cnt: %u\n", val); + + return 0; +} + +static int max96717_init_phy(struct max_ser *ser, + struct max_ser_phy *phy) +{ + struct max96717_priv *priv = ser_to_priv(ser); + unsigned int num_data_lanes = phy->mipi.num_data_lanes; + unsigned int used_data_lanes = 0; + unsigned int val; + unsigned int i; + int ret; + + if (num_data_lanes == 3 && !priv->info->supports_3_data_lanes) { + dev_err(priv->dev, "Unsupported 3 data lane mode\n"); + return -EINVAL; + } + + if (phy->mipi.flags & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK && + !priv->info->supports_noncontinuous_clock) { + dev_err(priv->dev, "Unsupported non-continuous mode\n"); + return -EINVAL; + } + + /* Configure a lane count. */ + ret = regmap_update_bits(priv->regmap, MAX96717_MIPI_RX1, + MAX96717_MIPI_RX1_CTRL_NUM_LANES, + FIELD_PREP(MAX96717_MIPI_RX1_CTRL_NUM_LANES, + num_data_lanes - 1)); + if (ret) + return ret; + + /* Configure lane mapping. */ + val = 0; + for (i = 0; i < 4; i++) { + unsigned int map; + + if (i < num_data_lanes) + map = phy->mipi.data_lanes[i] - 1; + else + map = ffz(used_data_lanes); + + val |= map << (i * 2); + used_data_lanes |= BIT(map); + } + + ret = regmap_update_bits(priv->regmap, MAX96717_MIPI_RX3, + MAX96717_MIPI_RX3_PHY2_LANE_MAP, + FIELD_PREP(MAX96717_MIPI_RX3_PHY2_LANE_MAP, val)); + if (ret) + return ret; + + ret = regmap_update_bits(priv->regmap, MAX96717_MIPI_RX2, + MAX96717_MIPI_RX2_PHY1_LANE_MAP, + FIELD_PREP(MAX96717_MIPI_RX2_PHY1_LANE_MAP, val >> 4)); + if (ret) + return ret; + + /* Configure lane polarity. */ + for (i = 0, val = 0; i < num_data_lanes; i++) + if (phy->mipi.lane_polarities[i + 1]) + val |= BIT(i); + + ret = regmap_update_bits(priv->regmap, MAX96717_MIPI_RX5, + MAX96717_MIPI_RX5_PHY2_POL_MAP, + FIELD_PREP(MAX96717_MIPI_RX5_PHY2_POL_MAP, val)); + if (ret) + return ret; + + ret = regmap_update_bits(priv->regmap, MAX96717_MIPI_RX4, + MAX96717_MIPI_RX4_PHY1_POL_MAP, + FIELD_PREP(MAX96717_MIPI_RX4_PHY1_POL_MAP, val >> 2)); + if (ret) + return ret; + + ret = regmap_assign_bits(priv->regmap, MAX96717_MIPI_RX5, + MAX96717_MIPI_RX5_PHY2_POL_MAP_CLK, + phy->mipi.lane_polarities[0]); + if (ret) + return ret; + + if (priv->info->supports_noncontinuous_clock) { + ret = regmap_assign_bits(priv->regmap, MAX96717_MIPI_RX0, + MAX96717_MIPI_RX0_NONCONTCLK_EN, + phy->mipi.flags & + V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK); + if (ret) + return ret; + } + + return 0; +} + +static int max96717_set_phy_active(struct max_ser *ser, struct max_ser_phy *phy, + bool enable) +{ + struct max96717_priv *priv = ser_to_priv(ser); + unsigned int index = max96717_phy_id(priv, phy); + + return regmap_assign_bits(priv->regmap, MAX96717_FRONTTOP_0, + MAX96717_FRONTTOP_0_START_PORT(index), enable); +} + +static int max96717_set_pipe_stream_id(struct max_ser *ser, + struct max_ser_pipe *pipe, + unsigned int stream_id) +{ + struct max96717_priv *priv = ser_to_priv(ser); + unsigned int index = max96717_pipe_id(priv, pipe); + + return regmap_update_bits(priv->regmap, MAX96717_TX3(index), + MAX96717_TX3_TX_STR_SEL, + FIELD_PREP(MAX96717_TX3_TX_STR_SEL, stream_id)); +} + +static int max96717_set_pipe_phy(struct max_ser *ser, struct max_ser_pipe *pipe, + struct max_ser_phy *phy) +{ + struct max96717_priv *priv = ser_to_priv(ser); + unsigned int index = max96717_pipe_id(priv, pipe); + unsigned int phy_id = max96717_phy_id(priv, phy); + int ret; + + ret = regmap_assign_bits(priv->regmap, MAX96717_FRONTTOP_0, + MAX96717_FRONTTOP_0_CLK_SEL_P(index), + phy_id == 1); + if (ret) + return ret; + + ret = regmap_assign_bits(priv->regmap, MAX96717_FRONTTOP_9, + MAX96717_FRONTTOP_9_START_PORT(index, 0), + phy_id == 0); + if (ret) + return ret; + + return regmap_assign_bits(priv->regmap, MAX96717_FRONTTOP_9, + MAX96717_FRONTTOP_9_START_PORT(index, 1), + phy_id == 1); +} + +static int max96717_set_pipe_mode(struct max_ser *ser, + struct max_ser_pipe *pipe, + struct max_ser_pipe_mode *mode) +{ + struct max96717_priv *priv = ser_to_priv(ser); + unsigned int index = max96717_pipe_id(priv, pipe); + int ret; + + ret = regmap_assign_bits(priv->regmap, MAX96717_VIDEO_TX0(index), + MAX96717_VIDEO_TX0_AUTO_BPP, !mode->bpp); + if (ret) + return ret; + + ret = regmap_update_bits(priv->regmap, MAX96717_VIDEO_TX1(index), + MAX96717_VIDEO_TX1_BPP, + FIELD_PREP(MAX96717_VIDEO_TX1_BPP, mode->bpp)); + if (ret) + return ret; + + ret = regmap_assign_bits(priv->regmap, MAX96717_VIDEO_TX2(index), + MAX96717_VIDEO_TX2_DRIFT_DET_EN, !mode->bpp); + if (ret) + return ret; + + ret = regmap_assign_bits(priv->regmap, MAX96717_FRONTTOP_10, + MAX96717_FRONTTOP_10_BPP8DBL(index), + mode->dbl8); + if (ret) + return ret; + + ret = regmap_assign_bits(priv->regmap, MAX96717_FRONTTOP_11, + MAX96717_FRONTTOP_11_BPP10DBL(index), + mode->dbl10); + if (ret) + return ret; + + ret = regmap_assign_bits(priv->regmap, MAX96717_FRONTTOP_11, + MAX96717_FRONTTOP_11_BPP12DBL(index), + mode->dbl12); + if (ret) + return ret; + + return regmap_update_bits(priv->regmap, MAX96717_FRONTTOP_20(index), + MAX96717_FRONTTOP_20_SOFT_BPP | + MAX96717_FRONTTOP_20_SOFT_BPP_EN, + FIELD_PREP(MAX96717_FRONTTOP_20_SOFT_BPP, + mode->soft_bpp) | + FIELD_PREP(MAX96717_FRONTTOP_20_SOFT_BPP_EN, + !!mode->soft_bpp)); +} + +static int max96717_set_i2c_xlate(struct max_ser *ser, unsigned int i, + struct max_serdes_i2c_xlate *xlate) +{ + struct max96717_priv *priv = ser_to_priv(ser); + int ret; + + ret = regmap_update_bits(priv->regmap, MAX96717_I2C_2(i), + MAX96717_I2C_2_SRC, + FIELD_PREP(MAX96717_I2C_2_SRC, xlate->src)); + if (ret) + return ret; + + return regmap_update_bits(priv->regmap, MAX96717_I2C_3(i), + MAX96717_I2C_3_DST, + FIELD_PREP(MAX96717_I2C_3_DST, xlate->dst)); +} + +static int max96717_set_tunnel_enable(struct max_ser *ser, bool enable) +{ + struct max96717_priv *priv = ser_to_priv(ser); + + return regmap_assign_bits(priv->regmap, MAX96717_EXT11, + MAX96717_EXT11_TUN_MODE, enable); +} + +static int max96717_set_tpg_timings(struct max96717_priv *priv, + const struct max_serdes_tpg_timings *tm, + unsigned int index) +{ + const struct reg_sequence regs[] = { + REG_SEQUENCE_3(MAX96717_VTX2_VS_DLY_2(index), tm->vs_dly), + REG_SEQUENCE_3(MAX96717_VTX5_VS_HIGH_2(index), tm->vs_high), + REG_SEQUENCE_3(MAX96717_VTX8_VS_LOW_2(index), tm->vs_low), + REG_SEQUENCE_3(MAX96717_VTX11_V2H_2(index), tm->v2h), + REG_SEQUENCE_2(MAX96717_VTX14_HS_HIGH_1(index), tm->hs_high), + REG_SEQUENCE_2(MAX96717_VTX16_HS_LOW_1(index), tm->hs_low), + REG_SEQUENCE_2(MAX96717_VTX18_HS_CNT_1(index), tm->hs_cnt), + REG_SEQUENCE_3(MAX96717_VTX20_V2D_2(index), tm->v2d), + REG_SEQUENCE_2(MAX96717_VTX23_DE_HIGH_1(index), tm->de_high), + REG_SEQUENCE_2(MAX96717_VTX25_DE_LOW_1(index), tm->de_low), + REG_SEQUENCE_2(MAX96717_VTX27_DE_CNT_1(index), tm->de_cnt), + }; + int ret; + + ret = regmap_multi_reg_write(priv->regmap, regs, ARRAY_SIZE(regs)); + if (ret) + return ret; + + return regmap_write(priv->regmap, MAX96717_VTX0(index), + FIELD_PREP(MAX96717_VTX0_VTG_MODE, + MAX96717_VTX0_VTG_MODE_FREE_RUNNING) | + FIELD_PREP(MAX96717_VTX0_DE_INV, tm->de_inv) | + FIELD_PREP(MAX96717_VTX0_HS_INV, tm->hs_inv) | + FIELD_PREP(MAX96717_VTX0_VS_INV, tm->vs_inv) | + FIELD_PREP(MAX96717_VTX0_GEN_DE, tm->gen_de) | + FIELD_PREP(MAX96717_VTX0_GEN_HS, tm->gen_hs) | + FIELD_PREP(MAX96717_VTX0_GEN_VS, tm->gen_vs)); +} + +static int max96717_set_tpg_clk(struct max96717_priv *priv, u32 clock, + unsigned int index) +{ + u8 pclk_src; + + switch (clock) { + case 25000000: + pclk_src = MAX96717_VTX1_PATGEN_CLK_SRC_25MHZ; + break; + case 75000000: + pclk_src = MAX96717_VTX1_PATGEN_CLK_SRC_75MHZ; + break; + case 150000000: + pclk_src = MAX96717_VTX1_PATGEN_CLK_SRC_150MHZ; + break; + case 375000000: + pclk_src = MAX96717_VTX1_PATGEN_CLK_SRC_375MHZ; + break; + case 0: + return 0; + default: + return -EINVAL; + } + + return regmap_update_bits(priv->regmap, MAX96717_VTX1(index), + MAX96717_VTX1_PATGEN_CLK_SRC, + FIELD_PREP(MAX96717_VTX1_PATGEN_CLK_SRC, + pclk_src)); +} + +static int max96717_set_tpg_mode(struct max96717_priv *priv, bool enable, + unsigned int index) +{ + unsigned int patgen_mode; + + switch (priv->ser.tpg_pattern) { + case MAX_SERDES_TPG_PATTERN_GRADIENT: + patgen_mode = MAX96717_VTX29_PATGEN_MODE_GRADIENT; + break; + case MAX_SERDES_TPG_PATTERN_CHECKERBOARD: + patgen_mode = MAX96717_VTX29_PATGEN_MODE_CHECKER; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(priv->regmap, MAX96717_VTX29(index), + MAX96717_VTX29_PATGEN_MODE, + FIELD_PREP(MAX96717_VTX29_PATGEN_MODE, + enable ? patgen_mode + : MAX96717_VTX29_PATGEN_MODE_DISABLED)); +} + +static int max96717_set_tpg(struct max_ser *ser, + const struct max_serdes_tpg_entry *entry) +{ + struct max96717_priv *priv = ser_to_priv(ser); + /* + * MAX9295A supports multiple pipes, each with a pattern generator, + * use only the first pipe for simplicity. + */ + unsigned int index = max96717_pipe_id(priv, &ser->pipes[0]); + struct max_serdes_tpg_timings timings = { 0 }; + int ret; + + ret = max_serdes_get_tpg_timings(entry, &timings); + if (ret) + return ret; + + ret = max96717_set_tpg_timings(priv, &timings, index); + if (ret) + return ret; + + ret = max96717_set_tpg_clk(priv, timings.clock, index); + if (ret) + return ret; + + return max96717_set_tpg_mode(priv, entry, index); +} + +static const struct max_serdes_phys_config max96717_phys_configs[] = { + { { 4 } }, +}; + +static int max96717_init_tpg(struct max_ser *ser) +{ + struct max96717_priv *priv = ser_to_priv(ser); + /* + * MAX9295A supports multiple pipes, each with a pattern generator, + * use only the first pipe for simplicity. + */ + unsigned int index = max96717_pipe_id(priv, &ser->pipes[0]); + + const struct reg_sequence regs[] = { + { MAX96717_VTX30_GRAD_INCR(index), MAX_SERDES_GRAD_INCR }, + REG_SEQUENCE_3_LE(MAX96717_VTX31_CHKR_A_L(index), + MAX_SERDES_CHECKER_COLOR_A), + REG_SEQUENCE_3_LE(MAX96717_VTX34_CHKR_B_L(index), + MAX_SERDES_CHECKER_COLOR_B), + { MAX96717_VTX37_CHKR_RPT_A(index), MAX_SERDES_CHECKER_SIZE }, + { MAX96717_VTX38_CHKR_RPT_B(index), MAX_SERDES_CHECKER_SIZE }, + { MAX96717_VTX39_CHKR_ALT(index), MAX_SERDES_CHECKER_SIZE }, + }; + + return regmap_multi_reg_write(priv->regmap, regs, ARRAY_SIZE(regs)); +} + +static int max96717_init(struct max_ser *ser) +{ + struct max96717_priv *priv = ser_to_priv(ser); + int ret; + + /* + * Set CMU2 PFDDIV to 1.1V for correct functionality of the device, + * as mentioned in the datasheet, under section MANDATORY REGISTER PROGRAMMING. + */ + ret = regmap_update_bits(priv->regmap, MAX96717_CMU2, + MAX96717_CMU2_PFDDIV_RSHORT, + FIELD_PREP(MAX96717_CMU2_PFDDIV_RSHORT, + MAX96717_CMU2_PFDDIV_RSHORT_1_1V)); + if (ret) + return ret; + + if (ser->ops->set_tunnel_enable) { + ret = ser->ops->set_tunnel_enable(ser, false); + if (ret) + return ret; + } + + return max96717_init_tpg(ser); +} + +static const struct pinctrl_ops max96717_ctrl_ops = { + .get_groups_count = max96717_ctrl_get_groups_count, + .get_group_name = max96717_ctrl_get_group_name, + .get_group_pins = max96717_ctrl_get_group_pins, +#if IS_BUILTIN(CONFIG_OF) + .dt_node_to_map = pinconf_generic_dt_node_to_map_pin, + .dt_free_map = pinconf_generic_dt_free_map, +#endif +}; + +static const struct pinconf_ops max96717_conf_ops = { + .pin_config_get = max96717_conf_pin_config_get, + .pin_config_set = max96717_conf_pin_config_set, + .is_generic = true, +}; + +static const struct pinmux_ops max96717_mux_ops = { + .get_functions_count = max96717_mux_get_functions_count, + .get_function_name = max96717_mux_get_function_name, + .get_function_groups = max96717_mux_get_groups, + .set_mux = max96717_mux_set, +}; + +static const struct max_serdes_tpg_entry max96717_tpg_entries[] = { + MAX_TPG_ENTRY_640X480P60_RGB888, + MAX_TPG_ENTRY_1920X1080P30_RGB888, + MAX_TPG_ENTRY_1920X1080P60_RGB888, +}; + +static const struct max_ser_ops max96717_ops = { + .num_i2c_xlates = 2, + .phys_configs = { + .num_configs = ARRAY_SIZE(max96717_phys_configs), + .configs = max96717_phys_configs, + }, + .tpg_entries = { + .num_entries = ARRAY_SIZE(max96717_tpg_entries), + .entries = max96717_tpg_entries, + }, + .tpg_mode = MAX_SERDES_GMSL_PIXEL_MODE, + .tpg_patterns = BIT(MAX_SERDES_TPG_PATTERN_CHECKERBOARD) | + BIT(MAX_SERDES_TPG_PATTERN_GRADIENT), +#ifdef CONFIG_VIDEO_ADV_DEBUG + .reg_read = max96717_reg_read, + .reg_write = max96717_reg_write, +#endif + .log_status = max96717_log_status, + .log_pipe_status = max96717_log_pipe_status, + .log_phy_status = max96717_log_phy_status, + .init = max96717_init, + .set_i2c_xlate = max96717_set_i2c_xlate, + .set_tpg = max96717_set_tpg, + .init_phy = max96717_init_phy, + .set_phy_active = max96717_set_phy_active, + .set_pipe_enable = max96717_set_pipe_enable, + .set_pipe_dt = max96717_set_pipe_dt, + .set_pipe_dt_en = max96717_set_pipe_dt_en, + .set_pipe_vcs = max96717_set_pipe_vcs, + .set_pipe_mode = max96717_set_pipe_mode, + .set_pipe_stream_id = max96717_set_pipe_stream_id, + .set_pipe_phy = max96717_set_pipe_phy, +}; + +struct max96717_pll_predef_freq { + unsigned long freq; + bool is_rclk; + bool is_alt; + u8 val; + u8 rclksel; +}; + +static const struct max96717_pll_predef_freq max96717_predef_freqs[] = { + { 6250000, true, false, 0, 2 }, + { 12500000, true, false, 0, 1 }, + { 13500000, false, true, 0, 3 }, + { 19200000, false, false, 0, 3 }, + { 24000000, false, true, 1, 3 }, + { 25000000, true, false, 0, 0 }, + { 27000000, false, false, 1, 3 }, + { 37125000, false, false, 2, 3 }, + { 74250000, false, false, 3, 3 }, +}; + +static unsigned long +max96717_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + struct max96717_priv *priv = clk_hw_to_priv(hw); + + return max96717_predef_freqs[priv->pll_predef_index].freq; +} + +static unsigned int max96717_clk_find_best_index(struct max96717_priv *priv, + unsigned long rate) +{ + unsigned int i, idx = 0; + unsigned long diff_new, diff_old = U32_MAX; + + for (i = 0; i < ARRAY_SIZE(max96717_predef_freqs); i++) { + diff_new = abs_diff(rate, max96717_predef_freqs[i].freq); + if (diff_new < diff_old) { + diff_old = diff_new; + idx = i; + } + } + + return idx; +} + +static int max96717_clk_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct max96717_priv *priv = clk_hw_to_priv(hw); + struct device *dev = &priv->client->dev; + unsigned int idx; + + idx = max96717_clk_find_best_index(priv, req->rate); + + if (req->rate != max96717_predef_freqs[idx].freq) { + dev_dbg(dev, "Request CLK freq:%lu, found CLK freq:%lu\n", + req->rate, max96717_predef_freqs[idx].freq); + } + + req->rate = max96717_predef_freqs[idx].freq; + + return 0; +} + +static int max96717_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + const struct max96717_pll_predef_freq *predef_freq; + struct max96717_priv *priv = clk_hw_to_priv(hw); + unsigned int val, idx; + int ret = 0; + + idx = max96717_clk_find_best_index(priv, rate); + predef_freq = &max96717_predef_freqs[idx]; + + ret = regmap_update_bits(priv->regmap, MAX96717_REG3, + MAX96717_REG3_RCLKSEL, + FIELD_PREP(MAX96717_REG3_RCLKSEL, + predef_freq->rclksel)); + if (ret) + return ret; + + val = FIELD_PREP(MAX96717_REF_VTG0_REFGEN_PREDEF_FREQ, + predef_freq->val); + + if (predef_freq->is_alt) + val |= MAX96717_REF_VTG0_REFGEN_PREDEF_FREQ_ALT; + if (!predef_freq->is_rclk) + val |= MAX96717_REF_VTG0_REFGEN_EN; + + val |= MAX96717_REF_VTG0_REFGEN_RST; + val |= MAX96717_REF_VTG0_REFGEN_PREDEF_EN; + + ret = regmap_write(priv->regmap, MAX96717_REF_VTG0, val); + if (ret) + return ret; + + ret = regmap_clear_bits(priv->regmap, MAX96717_REF_VTG0, + MAX96717_REF_VTG0_REFGEN_RST); + if (ret) + return ret; + + priv->pll_predef_index = idx; + + return 0; +} + +static int max96717_clk_prepare(struct clk_hw *hw) +{ + struct max96717_priv *priv = clk_hw_to_priv(hw); + + return regmap_set_bits(priv->regmap, MAX96717_REG6, MAX96717_REG6_RCLKEN); +} + +static void max96717_clk_unprepare(struct clk_hw *hw) +{ + struct max96717_priv *priv = clk_hw_to_priv(hw); + + regmap_clear_bits(priv->regmap, MAX96717_REG6, MAX96717_REG6_RCLKEN); +} + +static const struct clk_ops max96717_clk_ops = { + .prepare = max96717_clk_prepare, + .unprepare = max96717_clk_unprepare, + .set_rate = max96717_clk_set_rate, + .recalc_rate = max96717_clk_recalc_rate, + .determine_rate = max96717_clk_determine_rate, +}; + +static int max96717_register_clkout(struct max96717_priv *priv) +{ + struct device *dev = &priv->client->dev; + struct clk_init_data init = { .ops = &max96717_clk_ops }; + int ret; + + /* + * Only fall back to the default pin if pinctrl has not already routed + * RCLKOUT, to avoid overriding the mux and slew rate chosen in DT. + */ + if (!priv->rclkout_muxed) { + ret = max96717_mux_set_rclkout(priv, MAX96717_RCLK_MFP); + if (ret) + return ret; + } + + init.name = kasprintf(GFP_KERNEL, "max96717.%s.clk_out", dev_name(dev)); + if (!init.name) + return -ENOMEM; + + priv->clk_hw.init = &init; + + ret = max96717_clk_set_rate(&priv->clk_hw, + MAX96717_DEFAULT_CLKOUT_RATE, 0); + if (ret) + goto free_init_name; + + ret = devm_clk_hw_register(dev, &priv->clk_hw); + kfree(init.name); + if (ret) + return dev_err_probe(dev, ret, "Cannot register clock HW\n"); + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, + &priv->clk_hw); + if (ret) + return dev_err_probe(dev, ret, + "Cannot add OF clock provider\n"); + + return 0; + +free_init_name: + kfree(init.name); + return ret; +} + +static int max96717_gpiochip_probe(struct max96717_priv *priv) +{ + struct device *dev = priv->dev; + int ret; + + priv->pctldesc = (struct pinctrl_desc) { + .owner = THIS_MODULE, + .name = MAX96717_PINCTRL_NAME, + .pins = max96717_pins, + .npins = ARRAY_SIZE(max96717_pins), + .pctlops = &max96717_ctrl_ops, + .confops = &max96717_conf_ops, + .pmxops = &max96717_mux_ops, + .custom_params = max96717_cfg_params, + .num_custom_params = ARRAY_SIZE(max96717_cfg_params), + }; + + ret = devm_pinctrl_register_and_init(dev, &priv->pctldesc, priv, &priv->pctldev); + if (ret) + return ret; + + ret = pinctrl_enable(priv->pctldev); + if (ret) + return ret; + + priv->gc = (struct gpio_chip) { + .owner = THIS_MODULE, + .label = MAX96717_GPIOCHIP_NAME, + .base = -1, + .ngpio = MAX96717_GPIO_NUM, + .parent = dev, + .can_sleep = true, + .request = gpiochip_generic_request, + .free = gpiochip_generic_free, + .set_config = gpiochip_generic_config, + .get_direction = max96717_gpio_get_direction, + .direction_input = max96717_gpio_direction_input, + .direction_output = max96717_gpio_direction_output, + .get = max96717_gpio_get, + .set = max96717_gpio_set, + }; + + return devm_gpiochip_add_data(dev, &priv->gc, priv); +} + +static int max96717_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct max96717_priv *priv; + struct max_ser_ops *ops; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + ops = devm_kzalloc(dev, sizeof(*ops), GFP_KERNEL); + if (!ops) + return -ENOMEM; + + priv->info = device_get_match_data(dev); + if (!priv->info) { + dev_err(dev, "Failed to get match data\n"); + return -ENODEV; + } + + priv->dev = dev; + priv->client = client; + i2c_set_clientdata(client, priv); + + priv->regmap = devm_regmap_init_i2c(client, &max96717_i2c_regmap); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + *ops = max96717_ops; + + if (priv->info->modes & BIT(MAX_SERDES_GMSL_TUNNEL_MODE)) + ops->set_tunnel_enable = max96717_set_tunnel_enable; + + ops->modes = priv->info->modes; + ops->num_pipes = priv->info->num_pipes; + ops->num_dts_per_pipe = priv->info->num_dts_per_pipe; + ops->num_phys = priv->info->num_phys; + priv->ser.ops = ops; + + ret = max96717_wait_for_device(priv); + if (ret) + return ret; + + ret = max96717_gpiochip_probe(priv); + if (ret) + return ret; + + ret = max96717_register_clkout(priv); + if (ret) + return ret; + + return max_ser_probe(client, &priv->ser); +} + +static void max96717_remove(struct i2c_client *client) +{ + struct max96717_priv *priv = i2c_get_clientdata(client); + + max_ser_remove(&priv->ser); +} + +static const struct max96717_chip_info max9295a_info = { + .modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE), + .num_pipes = 4, + .num_dts_per_pipe = 2, + .pipe_hw_ids = { 0, 1, 2, 3 }, + .num_phys = 1, + .phy_hw_ids = { 1 }, +}; + +static const struct max96717_chip_info max96717_info = { + .modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE) | + BIT(MAX_SERDES_GMSL_TUNNEL_MODE), + .supports_3_data_lanes = true, + .supports_pkt_cnt = true, + .supports_noncontinuous_clock = true, + .num_pipes = 1, + .num_dts_per_pipe = 4, + .pipe_hw_ids = { 2 }, + .num_phys = 1, + .phy_hw_ids = { 1 }, +}; + +static const struct of_device_id max96717_of_ids[] = { + { .compatible = "maxim,max9295a", .data = &max9295a_info }, + { .compatible = "maxim,max96717", .data = &max96717_info }, + { .compatible = "maxim,max96717f", .data = &max96717_info }, + { .compatible = "maxim,max96793", .data = &max96717_info }, + { } +}; +MODULE_DEVICE_TABLE(of, max96717_of_ids); + +static struct i2c_driver max96717_i2c_driver = { + .driver = { + .name = MAX96717_NAME, + .of_match_table = max96717_of_ids, + }, + .probe = max96717_probe, + .remove = max96717_remove, +}; + +module_i2c_driver(max96717_i2c_driver); + +MODULE_IMPORT_NS("MAX_SERDES"); +MODULE_DESCRIPTION("MAX96717 GMSL2 Serializer Driver"); +MODULE_AUTHOR("Cosmin Tanislav "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/maxim-serdes/max96724.c b/drivers/media/i2c/maxim-serdes/max96724.c new file mode 100644 index 000000000000..3889d955f84b --- /dev/null +++ b/drivers/media/i2c/maxim-serdes/max96724.c @@ -0,0 +1,1347 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Maxim MAX96724 Quad GMSL2 Deserializer Driver + * + * Copyright (C) 2025 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "max_des.h" + +#define MAX96724_REG0 0x0 + +#define MAX96724_REG3 0x3 +#define MAX96724_REG3_CC_PORT_SEL(n) GENMASK((n) * 2 + 1, (n) * 2) +#define MAX96724_REG3_CC_PORT_SEL_MASK (MAX96724_REG3_CC_PORT_SEL(0) | \ + MAX96724_REG3_CC_PORT_SEL(1) | \ + MAX96724_REG3_CC_PORT_SEL(2) | \ + MAX96724_REG3_CC_PORT_SEL(3)) +#define MAX96724_REG3_CC_PORT_SEL_PORT0 0x2 +#define MAX96724_REG3_CC_PORT_SEL_PORT1 0x1 +#define MAX96724_REG3_CC_PORT_CFG(sel) \ + (FIELD_PREP(MAX96724_REG3_CC_PORT_SEL(0), (sel)) | \ + FIELD_PREP(MAX96724_REG3_CC_PORT_SEL(1), (sel)) | \ + FIELD_PREP(MAX96724_REG3_CC_PORT_SEL(2), (sel)) | \ + FIELD_PREP(MAX96724_REG3_CC_PORT_SEL(3), (sel))) +#define MAX96724_REG3_CC_PORT_CFG_PORT0 \ + MAX96724_REG3_CC_PORT_CFG(MAX96724_REG3_CC_PORT_SEL_PORT0) +#define MAX96724_REG3_CC_PORT_CFG_PORT1 \ + MAX96724_REG3_CC_PORT_CFG(MAX96724_REG3_CC_PORT_SEL_PORT1) + +#define MAX96724_REG6 0x6 +#define MAX96724_REG6_LINK_EN GENMASK(3, 0) + +#define MAX96724_REG7 0x7 +#define MAX96724_REG7_CC_CROSSOVER_SEL GENMASK(7, 4) + +#define MAX96724_DEBUG_EXTRA 0x9 +#define MAX96724_DEBUG_EXTRA_PCLK_SRC GENMASK(1, 0) +#define MAX96724_DEBUG_EXTRA_PCLK_SRC_25MHZ 0b00 +#define MAX96724_DEBUG_EXTRA_PCLK_SRC_75MHZ 0b01 +#define MAX96724_DEBUG_EXTRA_PCLK_SRC_USE_PIPE 0b10 + +/* Link A lock status lives in CTRL3, links B through D in CTRL12 through CTRL14. */ +#define MAX96724_CTRL12(l) (0xa + (l) - 1) + +#define MAX96724_REG26(x) (0x10 + (x) / 2) +#define MAX96724_REG26_RX_RATE_PHY(x) (GENMASK(1, 0) << (4 * ((x) % 2))) +#define MAX96724_REG26_RX_RATE_3GBPS 0b01 +#define MAX96724_REG26_RX_RATE_6GBPS 0b10 + +#define MAX96724_PWR1 0x13 +#define MAX96724_PWR1_RESET_ALL BIT(6) + +#define MAX96724_CTRL1 0x18 +#define MAX96724_CTRL1_RESET_ONESHOT GENMASK(3, 0) + +#define MAX96724_CTRL3 0x1a +#define MAX96724_CTRL_LOCKED BIT(3) + +#define MAX96724_VIDEO_PIPE_SEL(p) (0xf0 + (p) / 2) +#define MAX96724_VIDEO_PIPE_SEL_STREAM(p) (GENMASK(1, 0) << (4 * ((p) % 2))) +#define MAX96724_VIDEO_PIPE_SEL_LINK(p) (GENMASK(3, 2) << (4 * ((p) % 2))) + +#define MAX96724_VIDEO_PIPE_EN 0xf4 +#define MAX96724_VIDEO_PIPE_EN_MASK(p) BIT(p) +#define MAX96724_VIDEO_PIPE_EN_STREAM_SEL_ALL BIT(4) + +#define MAX96724_VPRBS(p) (0x1dc + (p) * 0x20) +#define MAX96724_VPRBS_VIDEO_LOCK BIT(0) +#define MAX96724_VPRBS_PATGEN_CLK_SRC BIT(7) +#define MAX96724_VPRBS_PATGEN_CLK_SRC_150MHZ 0b0 +#define MAX96724_VPRBS_PATGEN_CLK_SRC_375MHZ 0b1 + +#define MAX96724_BACKTOP12 0x40b +#define MAX96724_BACKTOP12_CSI_OUT_EN BIT(1) + +#define MAX96724_BACKTOP21(p) (0x414 + (p) / 4 * 0x20) +#define MAX96724_BACKTOP21_BPP8DBL(p) BIT(4 + (p) % 4) + +#define MAX96724_BACKTOP22(x) (0x415 + (x) * 0x3) +#define MAX96724_BACKTOP22_PHY_CSI_TX_DPLL GENMASK(4, 0) +#define MAX96724_BACKTOP22_PHY_CSI_TX_DPLL_EN BIT(5) + +#define MAX96724_BACKTOP24(p) (0x417 + (p) / 4 * 0x20) +#define MAX96724_BACKTOP24_BPP8DBL_MODE(p) BIT(4 + (p) % 4) + +#define MAX96724_BACKTOP30(p) (0x41d + (p) / 4 * 0x20) +#define MAX96724_BACKTOP30_BPP10DBL3 BIT(4) +#define MAX96724_BACKTOP30_BPP10DBL3_MODE BIT(5) + +#define MAX96724_BACKTOP31(p) (0x41e + (p) / 4 * 0x20) +#define MAX96724_BACKTOP31_BPP10DBL2 BIT(6) +#define MAX96724_BACKTOP31_BPP10DBL2_MODE BIT(7) + +#define MAX96724_BACKTOP32(p) (0x41f + (p) / 4 * 0x20) +#define MAX96724_BACKTOP32_BPP12(p) BIT((p) % 4) +#define MAX96724_BACKTOP32_BPP10DBL0 BIT(4) +#define MAX96724_BACKTOP32_BPP10DBL0_MODE BIT(5) +#define MAX96724_BACKTOP32_BPP10DBL1 BIT(6) +#define MAX96724_BACKTOP32_BPP10DBL1_MODE BIT(7) + +#define MAX96724_MIPI_PHY0 0x8a0 +#define MAX96724_MIPI_PHY0_PHY_CONFIG GENMASK(4, 0) +#define MAX96724_MIPI_PHY0_PHY_4X2 BIT(0) +#define MAX96724_MIPI_PHY0_PHY_2X4 BIT(2) +#define MAX96724_MIPI_PHY0_PHY_1X4A_2X2 BIT(3) +#define MAX96724_MIPI_PHY0_PHY_1X4B_2X2 BIT(4) +#define MAX96724_MIPI_PHY0_FORCE_CSI_OUT_EN BIT(7) + +#define MAX96724_MIPI_PHY2 0x8a2 +#define MAX96724_MIPI_PHY2_PHY_STDB_N_4(x) (GENMASK(5, 4) << ((x) / 2 * 2)) +#define MAX96724_MIPI_PHY2_PHY_STDB_N_2(x) (BIT(4 + (x))) + +#define MAX96724_MIPI_PHY3(x) (0x8a3 + (x) / 2) +#define MAX96724_MIPI_PHY3_PHY_LANE_MAP_4 GENMASK(7, 0) +#define MAX96724_MIPI_PHY3_PHY_LANE_MAP_2(x) (GENMASK(3, 0) << (4 * ((x) % 2))) + +#define MAX96724_MIPI_PHY5(x) (0x8a5 + (x) / 2) +#define MAX96724_MIPI_PHY5_PHY_POL_MAP_4_0_1 GENMASK(1, 0) +#define MAX96724_MIPI_PHY5_PHY_POL_MAP_4_2_3 GENMASK(4, 3) +#define MAX96724_MIPI_PHY5_PHY_POL_MAP_4_CLK BIT(5) +#define MAX96724_MIPI_PHY5_PHY_POL_MAP_2(x) (GENMASK(1, 0) << (3 * ((x) % 2))) +#define MAX96724_MIPI_PHY5_PHY_POL_MAP_2_CLK(x) BIT(2 + 3 * ((x) % 2)) + +#define MAX96724_MIPI_PHY13 0x8ad +#define MAX96724_MIPI_PHY13_T_T3_PREBEGIN GENMASK(5, 0) +#define MAX96724_MIPI_PHY13_T_T3_PREBEGIN_64X7 FIELD_PREP(MAX96724_MIPI_PHY13_T_T3_PREBEGIN, 63) + +#define MAX96724_MIPI_PHY14 0x8ae +#define MAX96724_MIPI_PHY14_T_T3_PREP GENMASK(1, 0) +#define MAX96724_MIPI_PHY14_T_T3_PREP_55NS FIELD_PREP(MAX96724_MIPI_PHY14_T_T3_PREP, 0b01) +#define MAX96724_MIPI_PHY14_T_T3_POST GENMASK(6, 2) +#define MAX96724_MIPI_PHY14_T_T3_POST_32X7 FIELD_PREP(MAX96724_MIPI_PHY14_T_T3_POST, 31) + +#define MAX96724_MIPI_CTRL_SEL 0x8ca +#define MAX96724_MIPI_CTRL_SEL_MASK(p) (GENMASK(1, 0) << ((p) * 2)) + +#define MAX96724_MIPI_PHY25(x) (0x8d0 + (x) / 2) +#define MAX96724_MIPI_PHY25_CSI2_TX_PKT_CNT(x) (GENMASK(3, 0) << (4 * ((x) % 2))) + +#define MAX96724_MIPI_PHY27(x) (0x8d2 + (x) / 2) +#define MAX96724_MIPI_PHY27_PHY_PKT_CNT(x) (GENMASK(3, 0) << (4 * ((x) % 2))) + +#define MAX96724_MIPI_TX3(x) (0x903 + (x) * 0x40) +#define MAX96724_MIPI_TX3_DESKEW_INIT_8X32K FIELD_PREP(GENMASK(2, 0), 0b001) +#define MAX96724_MIPI_TX3_DESKEW_INIT_AUTO BIT(7) + +#define MAX96724_MIPI_TX4(x) (0x904 + (x) * 0x40) +#define MAX96724_MIPI_TX4_DESKEW_PER_2K FIELD_PREP(GENMASK(2, 0), 0b001) +#define MAX96724_MIPI_TX4_DESKEW_PER_AUTO BIT(7) + +#define MAX96724_MIPI_TX10(x) (0x90a + (x) * 0x40) +#define MAX96724_MIPI_TX10_CSI2_CPHY_EN BIT(5) +#define MAX96724_MIPI_TX10_CSI2_LANE_CNT GENMASK(7, 6) + +#define MAX96724_MIPI_TX11(p) (0x90b + (p) * 0x40) +#define MAX96724_MIPI_TX12(p) (0x90c + (p) * 0x40) + +#define MAX96724_MIPI_TX13(p, x) (0x90d + (p) * 0x40 + (x) * 0x2) +#define MAX96724_MIPI_TX13_MAP_SRC_DT GENMASK(5, 0) +#define MAX96724_MIPI_TX13_MAP_SRC_VC GENMASK(7, 6) + +#define MAX96724_MIPI_TX14(p, x) (0x90e + (p) * 0x40 + (x) * 0x2) +#define MAX96724_MIPI_TX14_MAP_DST_DT GENMASK(5, 0) +#define MAX96724_MIPI_TX14_MAP_DST_VC GENMASK(7, 6) + +#define MAX96724_MIPI_TX45(p, x) (0x92d + (p) * 0x40 + (x) / 4) +#define MAX96724_MIPI_TX45_MAP_DPHY_DEST(x) (GENMASK(1, 0) << (2 * ((x) % 4))) + +#define MAX96724_MIPI_TX51(x) (0x933 + (x) * 0x40) +#define MAX96724_MIPI_TX51_ALT_MEM_MAP_12 BIT(0) +#define MAX96724_MIPI_TX51_ALT_MEM_MAP_8 BIT(1) +#define MAX96724_MIPI_TX51_ALT_MEM_MAP_10 BIT(2) +#define MAX96724_MIPI_TX51_ALT2_MEM_MAP_8 BIT(4) + +#define MAX96724_MIPI_TX54(x) (0x936 + (x) * 0x40) +#define MAX96724_MIPI_TX54_TUN_EN BIT(0) + +#define MAX96724_MIPI_TX57(x) (0x939 + (x) * 0x40) +#define MAX96724_MIPI_TX57_TUN_DEST GENMASK(5, 4) +#define MAX96724_MIPI_TX57_DIS_AUTO_TUN_DET BIT(6) +#define MAX96724_DET(p) BIT(p) + +#define MAX96724_PATGEN_0 0x1050 +#define MAX96724_PATGEN_0_VTG_MODE GENMASK(1, 0) +#define MAX96724_PATGEN_0_VTG_MODE_FREE_RUNNING 0b11 +#define MAX96724_PATGEN_0_DE_INV BIT(2) +#define MAX96724_PATGEN_0_HS_INV BIT(3) +#define MAX96724_PATGEN_0_VS_INV BIT(4) +#define MAX96724_PATGEN_0_GEN_DE BIT(5) +#define MAX96724_PATGEN_0_GEN_HS BIT(6) +#define MAX96724_PATGEN_0_GEN_VS BIT(7) + +#define MAX96724_PATGEN_1 0x1051 +#define MAX96724_PATGEN_1_PATGEN_MODE GENMASK(5, 4) +#define MAX96724_PATGEN_1_PATGEN_MODE_DISABLED 0b00 +#define MAX96724_PATGEN_1_PATGEN_MODE_CHECKER 0b01 +#define MAX96724_PATGEN_1_PATGEN_MODE_GRADIENT 0b10 + +#define MAX96724_VS_DLY_2 0x1052 +#define MAX96724_VS_HIGH_2 0x1055 +#define MAX96724_VS_LOW_2 0x1058 +#define MAX96724_V2H_2 0x105b +#define MAX96724_HS_HIGH_1 0x105e +#define MAX96724_HS_LOW_1 0x1060 +#define MAX96724_HS_CNT_1 0x1062 +#define MAX96724_V2D_2 0x1064 +#define MAX96724_DE_HIGH_1 0x1067 +#define MAX96724_DE_LOW_1 0x1069 +#define MAX96724_DE_CNT_1 0x106b +#define MAX96724_GRAD_INCR 0x106d +#define MAX96724_CHKR_COLOR_A_L 0x106e +#define MAX96724_CHKR_COLOR_B_L 0x1071 +#define MAX96724_CHKR_RPT_A 0x1074 +#define MAX96724_CHKR_RPT_B 0x1075 +#define MAX96724_CHKR_ALT 0x1076 + +#define MAX96724_DE_DET 0x11f0 +#define MAX96724_HS_DET 0x11f1 +#define MAX96724_VS_DET 0x11f2 +#define MAX96724_HS_POL 0x11f3 +#define MAX96724_VS_POL 0x11f4 +#define MAX96724_DET(p) BIT(p) + +#define MAX96724_DPLL_0(x) (0x1c00 + (x) * 0x100) +#define MAX96724_DPLL_0_CONFIG_SOFT_RST_N BIT(0) + +#define MAX96724_PHY1_ALT_CLOCK 5 + +static const struct regmap_config max96724_i2c_regmap = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0x1f00, +}; + +struct max96724_priv { + struct max_des des; + const struct max96724_chip_info *info; + + struct device *dev; + struct i2c_client *client; + struct regmap *regmap; + + struct gpio_desc *gpiod_enable; + unsigned int cc_port_cfg; + bool cc_port_crossover; +}; + +struct max96724_chip_info { + unsigned int versions; + unsigned int modes; + bool supports_pipe_stream_autoselect; + unsigned int num_pipes; + + int (*set_pipe_phy)(struct max_des *des, struct max_des_pipe *pipe, + struct max_des_phy *phy); + int (*set_pipe_tunnel_phy)(struct max_des *des, struct max_des_pipe *pipe, + struct max_des_phy *phy); + int (*set_pipe_tunnel_enable)(struct max_des *des, struct max_des_pipe *pipe, + bool enable); +}; + +#define des_to_priv(_des) \ + container_of(_des, struct max96724_priv, des) + +static int max96724_wait_for_device(struct max96724_priv *priv) +{ + unsigned int val; + int ret, err; + + err = read_poll_timeout(regmap_read, ret, + !ret && val, + 100 * USEC_PER_MSEC, + 1 * USEC_PER_SEC, false, + priv->regmap, MAX96724_REG0, &val); + if (err) + dev_err(priv->dev, "Timeout waiting for deserializer: %d\n", ret); + + return err; +} + +static int max96724_reset(struct max96724_priv *priv) +{ + int ret; + + ret = max96724_wait_for_device(priv); + if (ret) + return ret; + + ret = regmap_update_bits(priv->regmap, MAX96724_PWR1, + MAX96724_PWR1_RESET_ALL, + FIELD_PREP(MAX96724_PWR1_RESET_ALL, 1)); + if (ret) + return ret; + + fsleep(10000); + + ret = max96724_wait_for_device(priv); + if (ret) + return ret; + + /* Restore I2C control-channel access after a reset. */ + return regmap_update_bits(priv->regmap, MAX96724_REG3, + MAX96724_REG3_CC_PORT_SEL_MASK, + priv->cc_port_cfg); +} + +static int __maybe_unused max96724_reg_read(struct max_des *des, unsigned int reg, + unsigned int *val) +{ + struct max96724_priv *priv = des_to_priv(des); + + return regmap_read(priv->regmap, reg, val); +} + +static int __maybe_unused max96724_reg_write(struct max_des *des, unsigned int reg, + unsigned int val) +{ + struct max96724_priv *priv = des_to_priv(des); + + return regmap_write(priv->regmap, reg, val); +} + +static unsigned int max96724_phy_id(struct max_des *des, struct max_des_phy *phy) +{ + unsigned int num_hw_data_lanes = max_des_phy_hw_data_lanes(des, phy); + + /* PHY 1 is the master PHY when combining PHY 0 and PHY 1. */ + if (phy->index == 0 && num_hw_data_lanes == 4) + return 1; + + /* + * PHY 1 takes PHY 0's identifier only when it has no data lanes of its + * own, meaning it is absorbed into PHY 0. + * + * A disabled PHY 1 that still owns lanes keeps its own identifier. It + * is a candidate for the unused PHY that unmapped data is discarded + * to, so aliasing it here would send that data to PHY 0 instead, which + * may be streaming. + */ + if (phy->index == 1 && !des->phys[1].enabled && !num_hw_data_lanes) + return 0; + + return phy->index; +} + +static int max96724_log_pipe_status(struct max_des *des, + struct max_des_pipe *pipe) +{ + struct max96724_priv *priv = des_to_priv(des); + unsigned int index = pipe->index; + unsigned int val, mask; + int ret; + + ret = regmap_read(priv->regmap, MAX96724_VPRBS(index), &val); + if (ret) + return ret; + + dev_info(priv->dev, "\tvideo_lock: %u\n", + !!(val & MAX96724_VPRBS_VIDEO_LOCK)); + + mask = MAX96724_DET(index); + + ret = regmap_read(priv->regmap, MAX96724_DE_DET, &val); + if (ret) + return ret; + + dev_info(priv->dev, "\tde_det: %u\n", !!(val & mask)); + + ret = regmap_read(priv->regmap, MAX96724_HS_DET, &val); + if (ret) + return ret; + + dev_info(priv->dev, "\ths_det: %u\n", !!(val & mask)); + + ret = regmap_read(priv->regmap, MAX96724_VS_DET, &val); + if (ret) + return ret; + + dev_info(priv->dev, "\tvs_det: %u\n", !!(val & mask)); + + ret = regmap_read(priv->regmap, MAX96724_HS_POL, &val); + if (ret) + return ret; + + dev_info(priv->dev, "\ths_pol: %u\n", !!(val & mask)); + + ret = regmap_read(priv->regmap, MAX96724_VS_POL, &val); + if (ret) + return ret; + + dev_info(priv->dev, "\tvs_pol: %u\n", !!(val & mask)); + + return 0; +} + +static int max96724_log_phy_status(struct max_des *des, + struct max_des_phy *phy) +{ + struct max96724_priv *priv = des_to_priv(des); + unsigned int index = max96724_phy_id(des, phy); + unsigned int val; + int ret; + + ret = regmap_read(priv->regmap, MAX96724_MIPI_PHY25(index), &val); + if (ret) + return ret; + + dev_info(priv->dev, "\tcsi2_pkt_cnt: %lu\n", + field_get(MAX96724_MIPI_PHY25_CSI2_TX_PKT_CNT(index), val)); + + ret = regmap_read(priv->regmap, MAX96724_MIPI_PHY27(index), &val); + if (ret) + return ret; + + dev_info(priv->dev, "\tphy_pkt_cnt: %lu\n", + field_get(MAX96724_MIPI_PHY27_PHY_PKT_CNT(index), val)); + + return 0; +} + +static int max96724_set_enable(struct max_des *des, bool enable) +{ + struct max96724_priv *priv = des_to_priv(des); + + return regmap_assign_bits(priv->regmap, MAX96724_BACKTOP12, + MAX96724_BACKTOP12_CSI_OUT_EN, enable); +} + +static const unsigned int max96724_phys_configs_reg_val[] = { + MAX96724_MIPI_PHY0_PHY_1X4A_2X2, + MAX96724_MIPI_PHY0_PHY_2X4, + + MAX96724_MIPI_PHY0_PHY_4X2, + MAX96724_MIPI_PHY0_PHY_1X4A_2X2, + MAX96724_MIPI_PHY0_PHY_1X4B_2X2, + MAX96724_MIPI_PHY0_PHY_2X4, +}; + +static const struct max_serdes_phys_config max96724_phys_configs[] = { + /* + * PHY 1 can be in 4-lane mode (combining lanes of PHY 0 and PHY 1) + * but only use the data lanes of PHY0, while continuing to use the + * clock lane of PHY 1. + * Specifying clock-lanes as 5 turns on alternate clocking mode. + */ + { { 2, 0, 2, 2 }, { MAX96724_PHY1_ALT_CLOCK, 0, 0, 0 } }, + { { 2, 0, 4, 0 }, { MAX96724_PHY1_ALT_CLOCK, 0, 0, 0 } }, + + /* + * When combining PHY 0 and PHY 1 to make them function in 4-lane mode, + * PHY 1 is the master PHY, but we use PHY 0 here to maintain + * compatibility. + */ + { { 2, 2, 2, 2 } }, + { { 4, 0, 2, 2 } }, + { { 2, 2, 4, 0 } }, + { { 4, 0, 4, 0 } }, +}; + +static int max96724_init_tpg(struct max_des *des) +{ + const struct reg_sequence regs[] = { + { MAX96724_GRAD_INCR, MAX_SERDES_GRAD_INCR }, + REG_SEQUENCE_3_LE(MAX96724_CHKR_COLOR_A_L, + MAX_SERDES_CHECKER_COLOR_A), + REG_SEQUENCE_3_LE(MAX96724_CHKR_COLOR_B_L, + MAX_SERDES_CHECKER_COLOR_B), + { MAX96724_CHKR_RPT_A, MAX_SERDES_CHECKER_SIZE }, + { MAX96724_CHKR_RPT_B, MAX_SERDES_CHECKER_SIZE }, + { MAX96724_CHKR_ALT, MAX_SERDES_CHECKER_SIZE }, + }; + struct max96724_priv *priv = des_to_priv(des); + + return regmap_multi_reg_write(priv->regmap, regs, ARRAY_SIZE(regs)); +} + +static int max96724_init(struct max_des *des) +{ + struct max96724_priv *priv = des_to_priv(des); + unsigned int i; + int ret; + + if (priv->info->set_pipe_tunnel_enable) { + for (i = 0; i < des->info->num_pipes; i++) { + ret = regmap_set_bits(priv->regmap, MAX96724_MIPI_TX57(i), + MAX96724_MIPI_TX57_DIS_AUTO_TUN_DET); + if (ret) + return ret; + } + } + + if (priv->info->supports_pipe_stream_autoselect) { + /* Enable stream autoselect. */ + ret = regmap_set_bits(priv->regmap, MAX96724_VIDEO_PIPE_EN, + MAX96724_VIDEO_PIPE_EN_STREAM_SEL_ALL); + if (ret) + return ret; + } + + /* + * The serializer control channel is available through local I2C port 0 + * by default. Enable crossover only when local port 1 is selected. + */ + ret = regmap_update_bits(priv->regmap, MAX96724_REG7, + MAX96724_REG7_CC_CROSSOVER_SEL, + priv->cc_port_crossover ? + MAX96724_REG7_CC_CROSSOVER_SEL : 0); + if (ret) + return ret; + + /* Set PHY mode. */ + ret = regmap_update_bits(priv->regmap, MAX96724_MIPI_PHY0, + MAX96724_MIPI_PHY0_PHY_CONFIG, + max96724_phys_configs_reg_val[des->phys_config]); + if (ret) + return ret; + + return max96724_init_tpg(des); +} + +static int max96724_init_phy(struct max_des *des, struct max_des_phy *phy) +{ + struct max96724_priv *priv = des_to_priv(des); + bool is_cphy = phy->bus_type == V4L2_MBUS_CSI2_CPHY; + unsigned int num_data_lanes = phy->mipi.num_data_lanes; + unsigned int num_hw_data_lanes; + unsigned int index; + unsigned int used_data_lanes = 0; + unsigned int val, mask; + unsigned int i; + u64 dpll_freq; + int ret; + + index = max96724_phy_id(des, phy); + num_hw_data_lanes = max_des_phy_hw_data_lanes(des, phy); + dpll_freq = is_cphy ? phy->link_frequency + : phy->link_frequency * 2; + + ret = regmap_update_bits(priv->regmap, MAX96724_MIPI_TX10(index), + MAX96724_MIPI_TX10_CSI2_LANE_CNT, + FIELD_PREP(MAX96724_MIPI_TX10_CSI2_LANE_CNT, + num_data_lanes - 1)); + if (ret) + return ret; + + ret = regmap_assign_bits(priv->regmap, MAX96724_MIPI_TX10(index), + MAX96724_MIPI_TX10_CSI2_CPHY_EN, is_cphy); + if (ret) + return ret; + + /* Configure lane mapping. */ + val = 0; + for (i = 0; i < num_hw_data_lanes ; i++) { + unsigned int map; + + if (i < num_data_lanes) + map = phy->mipi.data_lanes[i] - 1; + else + map = ffz(used_data_lanes); + + val |= map << (i * 2); + used_data_lanes |= BIT(map); + } + + if (num_hw_data_lanes == 4) + mask = MAX96724_MIPI_PHY3_PHY_LANE_MAP_4; + else + mask = MAX96724_MIPI_PHY3_PHY_LANE_MAP_2(index); + + ret = regmap_update_bits(priv->regmap, MAX96724_MIPI_PHY3(index), + mask, field_prep(mask, val)); + if (ret) + return ret; + + /* Configure lane polarity. */ + for (i = 0, val = 0; i < num_data_lanes; i++) + if (phy->mipi.lane_polarities[i + 1]) + val |= BIT(i); + + if (num_hw_data_lanes == 4) { + ret = regmap_update_bits(priv->regmap, MAX96724_MIPI_PHY5(index), + MAX96724_MIPI_PHY5_PHY_POL_MAP_4_0_1 | + MAX96724_MIPI_PHY5_PHY_POL_MAP_4_2_3, + FIELD_PREP(MAX96724_MIPI_PHY5_PHY_POL_MAP_4_0_1, + val) | + FIELD_PREP(MAX96724_MIPI_PHY5_PHY_POL_MAP_4_2_3, + val >> 2)); + if (ret) + return ret; + + ret = regmap_assign_bits(priv->regmap, MAX96724_MIPI_PHY5(index), + MAX96724_MIPI_PHY5_PHY_POL_MAP_4_CLK, + phy->mipi.lane_polarities[0]); + if (ret) + return ret; + } else { + ret = regmap_update_bits(priv->regmap, MAX96724_MIPI_PHY5(index), + MAX96724_MIPI_PHY5_PHY_POL_MAP_2(index), + field_prep(MAX96724_MIPI_PHY5_PHY_POL_MAP_2(index), val)); + if (ret) + return ret; + + ret = regmap_assign_bits(priv->regmap, MAX96724_MIPI_PHY5(index), + MAX96724_MIPI_PHY5_PHY_POL_MAP_2_CLK(index), + phy->mipi.lane_polarities[0]); + if (ret) + return ret; + } + + if (!is_cphy && dpll_freq > 1500000000ull) { + /* Enable initial deskew with 2 x 32k UI. */ + ret = regmap_write(priv->regmap, MAX96724_MIPI_TX3(index), + MAX96724_MIPI_TX3_DESKEW_INIT_AUTO | + MAX96724_MIPI_TX3_DESKEW_INIT_8X32K); + if (ret) + return ret; + + /* Enable periodic deskew with 2 x 1k UI.. */ + ret = regmap_write(priv->regmap, MAX96724_MIPI_TX4(index), + MAX96724_MIPI_TX4_DESKEW_PER_AUTO | + MAX96724_MIPI_TX4_DESKEW_PER_2K); + if (ret) + return ret; + } else { + /* Disable initial deskew. */ + ret = regmap_write(priv->regmap, MAX96724_MIPI_TX3(index), 0x0); + if (ret) + return ret; + + /* Disable periodic deskew. */ + ret = regmap_write(priv->regmap, MAX96724_MIPI_TX4(index), 0x0); + if (ret) + return ret; + } + + if (is_cphy) { + /* Configure C-PHY timings. */ + ret = regmap_write(priv->regmap, MAX96724_MIPI_PHY13, + MAX96724_MIPI_PHY13_T_T3_PREBEGIN_64X7); + if (ret) + return ret; + + ret = regmap_write(priv->regmap, MAX96724_MIPI_PHY14, + MAX96724_MIPI_PHY14_T_T3_PREP_55NS | + MAX96724_MIPI_PHY14_T_T3_POST_32X7); + if (ret) + return ret; + } + + /* Put DPLL block into reset. */ + ret = regmap_clear_bits(priv->regmap, MAX96724_DPLL_0(index), + MAX96724_DPLL_0_CONFIG_SOFT_RST_N); + if (ret) + return ret; + + /* Set DPLL frequency. */ + ret = regmap_update_bits(priv->regmap, MAX96724_BACKTOP22(index), + MAX96724_BACKTOP22_PHY_CSI_TX_DPLL, + FIELD_PREP(MAX96724_BACKTOP22_PHY_CSI_TX_DPLL, + div_u64(dpll_freq, 100000000))); + if (ret) + return ret; + + /* Enable DPLL frequency. */ + ret = regmap_set_bits(priv->regmap, MAX96724_BACKTOP22(index), + MAX96724_BACKTOP22_PHY_CSI_TX_DPLL_EN); + if (ret) + return ret; + + /* Pull DPLL block out of reset. */ + return regmap_set_bits(priv->regmap, MAX96724_DPLL_0(index), + MAX96724_DPLL_0_CONFIG_SOFT_RST_N); +} + +static int max96724_set_phy_mode(struct max_des *des, struct max_des_phy *phy, + struct max_des_phy_mode *mode) +{ + struct max96724_priv *priv = des_to_priv(des); + unsigned int index = max96724_phy_id(des, phy); + int ret; + + /* Set alternate memory map modes. */ + ret = regmap_assign_bits(priv->regmap, MAX96724_MIPI_TX51(index), + MAX96724_MIPI_TX51_ALT_MEM_MAP_12, + mode->alt_mem_map12); + if (ret) + return ret; + + ret = regmap_assign_bits(priv->regmap, MAX96724_MIPI_TX51(index), + MAX96724_MIPI_TX51_ALT_MEM_MAP_8, + mode->alt_mem_map8); + if (ret) + return ret; + + ret = regmap_assign_bits(priv->regmap, MAX96724_MIPI_TX51(index), + MAX96724_MIPI_TX51_ALT_MEM_MAP_10, + mode->alt_mem_map10); + if (ret) + return ret; + + return regmap_assign_bits(priv->regmap, MAX96724_MIPI_TX51(index), + MAX96724_MIPI_TX51_ALT2_MEM_MAP_8, + mode->alt2_mem_map8); +} + +static int max96724_set_phy_enable(struct max_des *des, struct max_des_phy *phy, + bool enable) +{ + struct max96724_priv *priv = des_to_priv(des); + unsigned int index = max96724_phy_id(des, phy); + unsigned int num_hw_data_lanes; + unsigned int mask; + + num_hw_data_lanes = max_des_phy_hw_data_lanes(des, phy); + + /* + * Some configurations merge two logical PHYs into one hardware PHY. + * Skip writes for absorbed PHYs to avoid clobbering the master's bits. + */ + if (!num_hw_data_lanes) + return 0; + + if (num_hw_data_lanes == 4) + /* PHY 1 -> bits [5:4] */ + /* PHY 2 -> bits [7:6] */ + mask = MAX96724_MIPI_PHY2_PHY_STDB_N_4(index); + else + mask = MAX96724_MIPI_PHY2_PHY_STDB_N_2(index); + + return regmap_assign_bits(priv->regmap, MAX96724_MIPI_PHY2, mask, enable); +} + +static int max96724_set_pipe_remap(struct max_des *des, + struct max_des_pipe *pipe, + unsigned int i, + struct max_des_remap *remap) +{ + struct max96724_priv *priv = des_to_priv(des); + struct max_des_phy *phy = &des->phys[remap->phy]; + unsigned int phy_id = max96724_phy_id(des, phy); + unsigned int index = pipe->index; + int ret; + + /* Set source Data Type and Virtual Channel. */ + /* TODO: implement extended Virtual Channel. */ + ret = regmap_write(priv->regmap, MAX96724_MIPI_TX13(index, i), + FIELD_PREP(MAX96724_MIPI_TX13_MAP_SRC_DT, + remap->from_dt) | + FIELD_PREP(MAX96724_MIPI_TX13_MAP_SRC_VC, + remap->from_vc)); + if (ret) + return ret; + + /* Set destination Data Type and Virtual Channel. */ + /* TODO: implement extended Virtual Channel. */ + ret = regmap_write(priv->regmap, MAX96724_MIPI_TX14(index, i), + FIELD_PREP(MAX96724_MIPI_TX14_MAP_DST_DT, + remap->to_dt) | + FIELD_PREP(MAX96724_MIPI_TX14_MAP_DST_VC, + remap->to_vc)); + if (ret) + return ret; + + /* Set destination PHY. */ + return regmap_update_bits(priv->regmap, MAX96724_MIPI_TX45(index, i), + MAX96724_MIPI_TX45_MAP_DPHY_DEST(i), + field_prep(MAX96724_MIPI_TX45_MAP_DPHY_DEST(i), + phy_id)); +} + +static int max96724_set_pipe_remaps_enable(struct max_des *des, + struct max_des_pipe *pipe, + unsigned int mask) +{ + struct max96724_priv *priv = des_to_priv(des); + unsigned int index = pipe->index; + int ret; + + ret = regmap_write(priv->regmap, MAX96724_MIPI_TX11(index), mask); + if (ret) + return ret; + + return regmap_write(priv->regmap, MAX96724_MIPI_TX12(index), mask >> 8); +} + +static int max96724_set_pipe_tunnel_phy(struct max_des *des, + struct max_des_pipe *pipe, + struct max_des_phy *phy) +{ + struct max96724_priv *priv = des_to_priv(des); + unsigned int phy_index = max96724_phy_id(des, phy); + + return regmap_update_bits(priv->regmap, MAX96724_MIPI_TX57(pipe->index), + MAX96724_MIPI_TX57_TUN_DEST, + FIELD_PREP(MAX96724_MIPI_TX57_TUN_DEST, + phy_index)); +} + +static int max96724_set_pipe_phy(struct max_des *des, struct max_des_pipe *pipe, + struct max_des_phy *phy) +{ + struct max96724_priv *priv = des_to_priv(des); + unsigned int phy_index = max96724_phy_id(des, phy); + + return regmap_update_bits(priv->regmap, MAX96724_MIPI_CTRL_SEL, + MAX96724_MIPI_CTRL_SEL_MASK(pipe->index), + field_prep(MAX96724_MIPI_CTRL_SEL_MASK(pipe->index), + phy_index)); +} + +static int max96724_set_pipe_enable(struct max_des *des, struct max_des_pipe *pipe, + bool enable) +{ + struct max96724_priv *priv = des_to_priv(des); + unsigned int index = pipe->index; + + return regmap_assign_bits(priv->regmap, MAX96724_VIDEO_PIPE_EN, + MAX96724_VIDEO_PIPE_EN_MASK(index), enable); +} + +static int max96724_set_pipe_stream_id(struct max_des *des, struct max_des_pipe *pipe, + unsigned int stream_id) +{ + struct max96724_priv *priv = des_to_priv(des); + unsigned int index = pipe->index; + + return regmap_update_bits(priv->regmap, MAX96724_VIDEO_PIPE_SEL(index), + MAX96724_VIDEO_PIPE_SEL_STREAM(index), + field_prep(MAX96724_VIDEO_PIPE_SEL_STREAM(index), + stream_id)); +} + +static int max96724_set_pipe_link(struct max_des *des, struct max_des_pipe *pipe, + struct max_des_link *link) +{ + struct max96724_priv *priv = des_to_priv(des); + unsigned int index = pipe->index; + + return regmap_update_bits(priv->regmap, MAX96724_VIDEO_PIPE_SEL(index), + MAX96724_VIDEO_PIPE_SEL_LINK(index), + field_prep(MAX96724_VIDEO_PIPE_SEL_LINK(index), + link->index)); +} + +static int max96724_set_pipe_mode(struct max_des *des, + struct max_des_pipe *pipe, + struct max_des_pipe_mode *mode) +{ + struct max96724_priv *priv = des_to_priv(des); + unsigned int index = pipe->index; + unsigned int reg, mask, mode_mask; + int ret; + + /* Set 8bit double mode. */ + ret = regmap_assign_bits(priv->regmap, MAX96724_BACKTOP21(index), + MAX96724_BACKTOP21_BPP8DBL(index), mode->dbl8); + if (ret) + return ret; + + ret = regmap_assign_bits(priv->regmap, MAX96724_BACKTOP24(index), + MAX96724_BACKTOP24_BPP8DBL_MODE(index), + mode->dbl8mode); + if (ret) + return ret; + + /* Set 10bit double mode. */ + if (index % 4 == 3) { + reg = MAX96724_BACKTOP30(index); + mask = MAX96724_BACKTOP30_BPP10DBL3; + mode_mask = MAX96724_BACKTOP30_BPP10DBL3_MODE; + } else if (index % 4 == 2) { + reg = MAX96724_BACKTOP31(index); + mask = MAX96724_BACKTOP31_BPP10DBL2; + mode_mask = MAX96724_BACKTOP31_BPP10DBL2_MODE; + } else if (index % 4 == 1) { + reg = MAX96724_BACKTOP32(index); + mask = MAX96724_BACKTOP32_BPP10DBL1; + mode_mask = MAX96724_BACKTOP32_BPP10DBL1_MODE; + } else { + reg = MAX96724_BACKTOP32(index); + mask = MAX96724_BACKTOP32_BPP10DBL0; + mode_mask = MAX96724_BACKTOP32_BPP10DBL0_MODE; + } + + ret = regmap_assign_bits(priv->regmap, reg, mask, mode->dbl10); + if (ret) + return ret; + + ret = regmap_assign_bits(priv->regmap, reg, mode_mask, mode->dbl10mode); + if (ret) + return ret; + + /* Set 12bit double mode. */ + return regmap_assign_bits(priv->regmap, MAX96724_BACKTOP32(index), + MAX96724_BACKTOP32_BPP12(index), mode->dbl12); +} + +static int max96724_set_pipe_tunnel_enable(struct max_des *des, + struct max_des_pipe *pipe, bool enable) +{ + struct max96724_priv *priv = des_to_priv(des); + + return regmap_assign_bits(priv->regmap, MAX96724_MIPI_TX54(pipe->index), + MAX96724_MIPI_TX54_TUN_EN, enable); +} + +static int max96724_read_link_locked(struct max96724_priv *priv, + unsigned int index, bool *locked) +{ + unsigned int reg, val; + int ret; + + reg = index == 0 ? MAX96724_CTRL3 : MAX96724_CTRL12(index); + + ret = regmap_read(priv->regmap, reg, &val); + if (ret) + return ret; + + *locked = val & MAX96724_CTRL_LOCKED; + + return 0; +} + +static int max96724_wait_for_link_lock(struct max96724_priv *priv, + unsigned int mask) +{ + unsigned int i; + bool locked; + int ret, err; + + for (i = 0; i < priv->des.info->num_links; i++) { + if (!(mask & BIT(i))) + continue; + + err = read_poll_timeout(max96724_read_link_locked, ret, + ret || locked, + 10 * USEC_PER_MSEC, + 200 * USEC_PER_MSEC, true, + priv, i, &locked); + if (ret) + return ret; + + if (err) { + dev_dbg(priv->dev, + "Timeout waiting for link %u lock\n", i); + + return err; + } + } + + return 0; +} + +static int max96724_select_links(struct max_des *des, unsigned int mask) +{ + struct max96724_priv *priv = des_to_priv(des); + unsigned int val = priv->cc_port_cfg; + unsigned int i; + int ret; + + for (i = 0; i < des->info->num_links; i++) { + if (!(mask & BIT(i))) + val |= MAX96724_REG3_CC_PORT_SEL(i); + } + + ret = regmap_write(priv->regmap, MAX96724_REG3, val); + if (ret) + return ret; + + ret = regmap_update_bits(priv->regmap, MAX96724_REG6, MAX96724_REG6_LINK_EN, + field_prep(MAX96724_REG6_LINK_EN, mask)); + if (ret) + return ret; + + ret = regmap_set_bits(priv->regmap, MAX96724_CTRL1, + MAX96724_CTRL1_RESET_ONESHOT); + if (ret) + return ret; + + return max96724_wait_for_link_lock(priv, mask); +} + +static int max96724_set_link_version(struct max_des *des, + struct max_des_link *link, + enum max_serdes_gmsl_version version) +{ + struct max96724_priv *priv = des_to_priv(des); + unsigned int index = link->index; + unsigned int val; + + if (version == MAX_SERDES_GMSL_2_6GBPS) + val = MAX96724_REG26_RX_RATE_6GBPS; + else + val = MAX96724_REG26_RX_RATE_3GBPS; + + return regmap_update_bits(priv->regmap, MAX96724_REG26(index), + MAX96724_REG26_RX_RATE_PHY(index), + field_prep(MAX96724_REG26_RX_RATE_PHY(index), val)); +} + +static int max96724_set_tpg_timings(struct max96724_priv *priv, + const struct max_serdes_tpg_timings *tm) +{ + const struct reg_sequence regs[] = { + REG_SEQUENCE_3(MAX96724_VS_DLY_2, tm->vs_dly), + REG_SEQUENCE_3(MAX96724_VS_HIGH_2, tm->vs_high), + REG_SEQUENCE_3(MAX96724_VS_LOW_2, tm->vs_low), + REG_SEQUENCE_3(MAX96724_V2H_2, tm->v2h), + REG_SEQUENCE_2(MAX96724_HS_HIGH_1, tm->hs_high), + REG_SEQUENCE_2(MAX96724_HS_LOW_1, tm->hs_low), + REG_SEQUENCE_2(MAX96724_HS_CNT_1, tm->hs_cnt), + REG_SEQUENCE_3(MAX96724_V2D_2, tm->v2d), + REG_SEQUENCE_2(MAX96724_DE_HIGH_1, tm->de_high), + REG_SEQUENCE_2(MAX96724_DE_LOW_1, tm->de_low), + REG_SEQUENCE_2(MAX96724_DE_CNT_1, tm->de_cnt), + }; + int ret; + + ret = regmap_multi_reg_write(priv->regmap, regs, ARRAY_SIZE(regs)); + if (ret) + return ret; + + return regmap_write(priv->regmap, MAX96724_PATGEN_0, + FIELD_PREP(MAX96724_PATGEN_0_VTG_MODE, + MAX96724_PATGEN_0_VTG_MODE_FREE_RUNNING) | + FIELD_PREP(MAX96724_PATGEN_0_DE_INV, tm->de_inv) | + FIELD_PREP(MAX96724_PATGEN_0_HS_INV, tm->hs_inv) | + FIELD_PREP(MAX96724_PATGEN_0_VS_INV, tm->vs_inv) | + FIELD_PREP(MAX96724_PATGEN_0_GEN_DE, tm->gen_de) | + FIELD_PREP(MAX96724_PATGEN_0_GEN_HS, tm->gen_hs) | + FIELD_PREP(MAX96724_PATGEN_0_GEN_VS, tm->gen_vs)); +} + +static int max96724_set_tpg_clk(struct max96724_priv *priv, u32 clock) +{ + bool patgen_clk_src = 0; + u8 pclk_src; + int ret; + + switch (clock) { + case 25000000: + pclk_src = MAX96724_DEBUG_EXTRA_PCLK_SRC_25MHZ; + break; + case 75000000: + pclk_src = MAX96724_DEBUG_EXTRA_PCLK_SRC_75MHZ; + break; + case 150000000: + pclk_src = MAX96724_DEBUG_EXTRA_PCLK_SRC_USE_PIPE; + patgen_clk_src = MAX96724_VPRBS_PATGEN_CLK_SRC_150MHZ; + break; + case 375000000: + pclk_src = MAX96724_DEBUG_EXTRA_PCLK_SRC_USE_PIPE; + patgen_clk_src = MAX96724_VPRBS_PATGEN_CLK_SRC_375MHZ; + break; + case 0: + return 0; + default: + return -EINVAL; + } + + /* + * TPG data is always injected on link 0, which is always routed to + * pipe 0. + */ + ret = regmap_update_bits(priv->regmap, MAX96724_VPRBS(0), + MAX96724_VPRBS_PATGEN_CLK_SRC, + FIELD_PREP(MAX96724_VPRBS_PATGEN_CLK_SRC, + patgen_clk_src)); + if (ret) + return ret; + + return regmap_update_bits(priv->regmap, MAX96724_DEBUG_EXTRA, + MAX96724_DEBUG_EXTRA_PCLK_SRC, + FIELD_PREP(MAX96724_DEBUG_EXTRA_PCLK_SRC, + pclk_src)); +} + +static int max96724_set_tpg_mode(struct max96724_priv *priv, bool enable) +{ + unsigned int patgen_mode; + + switch (priv->des.tpg_pattern) { + case MAX_SERDES_TPG_PATTERN_GRADIENT: + patgen_mode = MAX96724_PATGEN_1_PATGEN_MODE_GRADIENT; + break; + case MAX_SERDES_TPG_PATTERN_CHECKERBOARD: + patgen_mode = MAX96724_PATGEN_1_PATGEN_MODE_CHECKER; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(priv->regmap, MAX96724_PATGEN_1, + MAX96724_PATGEN_1_PATGEN_MODE, + FIELD_PREP(MAX96724_PATGEN_1_PATGEN_MODE, + enable ? patgen_mode + : MAX96724_PATGEN_1_PATGEN_MODE_DISABLED)); +} + +static int max96724_set_tpg(struct max_des *des, + const struct max_serdes_tpg_entry *entry) +{ + struct max96724_priv *priv = des_to_priv(des); + struct max_serdes_tpg_timings timings = { 0 }; + int ret; + + ret = max_serdes_get_tpg_timings(entry, &timings); + if (ret) + return ret; + + ret = max96724_set_tpg_timings(priv, &timings); + if (ret) + return ret; + + ret = max96724_set_tpg_clk(priv, timings.clock); + if (ret) + return ret; + + ret = max96724_set_tpg_mode(priv, entry); + if (ret) + return ret; + + return regmap_assign_bits(priv->regmap, MAX96724_MIPI_PHY0, + MAX96724_MIPI_PHY0_FORCE_CSI_OUT_EN, !!entry); +} + +static const struct max_serdes_tpg_entry max96724_tpg_entries[] = { + MAX_TPG_ENTRY_640X480P60_RGB888, + MAX_TPG_ENTRY_1920X1080P30_RGB888, + MAX_TPG_ENTRY_1920X1080P60_RGB888, +}; + +static const struct max_des_info max96724_des_info = { + .num_phys = 4, + .num_links = 4, + .num_remaps_per_pipe = 16, + .phys_configs = { + .num_configs = ARRAY_SIZE(max96724_phys_configs), + .configs = max96724_phys_configs, + }, + .tpg_entries = { + .num_entries = ARRAY_SIZE(max96724_tpg_entries), + .entries = max96724_tpg_entries, + }, + .tpg_mode = MAX_SERDES_GMSL_PIXEL_MODE, + .tpg_patterns = BIT(MAX_SERDES_TPG_PATTERN_CHECKERBOARD) | + BIT(MAX_SERDES_TPG_PATTERN_GRADIENT), + .use_atr = true, +}; + +static const struct max_des_ops max96724_des_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .reg_read = max96724_reg_read, + .reg_write = max96724_reg_write, +#endif + .log_pipe_status = max96724_log_pipe_status, + .log_phy_status = max96724_log_phy_status, + .set_enable = max96724_set_enable, + .init = max96724_init, + .init_phy = max96724_init_phy, + .set_phy_mode = max96724_set_phy_mode, + .set_phy_enable = max96724_set_phy_enable, + .set_pipe_stream_id = max96724_set_pipe_stream_id, + .set_pipe_link = max96724_set_pipe_link, + .set_pipe_enable = max96724_set_pipe_enable, + .set_pipe_remap = max96724_set_pipe_remap, + .set_pipe_remaps_enable = max96724_set_pipe_remaps_enable, + .set_pipe_mode = max96724_set_pipe_mode, + .set_tpg = max96724_set_tpg, + .select_links = max96724_select_links, + .set_link_version = max96724_set_link_version, +}; + +static const struct max96724_chip_info max96724_info = { + .versions = BIT(MAX_SERDES_GMSL_2_3GBPS) | + BIT(MAX_SERDES_GMSL_2_6GBPS), + .modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE) | + BIT(MAX_SERDES_GMSL_TUNNEL_MODE), + .set_pipe_tunnel_enable = max96724_set_pipe_tunnel_enable, + .set_pipe_phy = max96724_set_pipe_phy, + .set_pipe_tunnel_phy = max96724_set_pipe_tunnel_phy, + .supports_pipe_stream_autoselect = true, + .num_pipes = 4, +}; + +static const struct max96724_chip_info max96724f_info = { + .versions = BIT(MAX_SERDES_GMSL_2_3GBPS), + .modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE) | + BIT(MAX_SERDES_GMSL_TUNNEL_MODE), + .set_pipe_tunnel_enable = max96724_set_pipe_tunnel_enable, + .set_pipe_phy = max96724_set_pipe_phy, + .set_pipe_tunnel_phy = max96724_set_pipe_tunnel_phy, + .supports_pipe_stream_autoselect = true, + .num_pipes = 4, +}; + +static const struct max96724_chip_info max96712_info = { + .versions = BIT(MAX_SERDES_GMSL_2_3GBPS) | + BIT(MAX_SERDES_GMSL_2_6GBPS), + .modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE), + .num_pipes = 8, +}; + +static void max96724_power_off(void *data) +{ + struct max96724_priv *priv = data; + + gpiod_set_value_cansleep(priv->gpiod_enable, 0); +} + +static int max96724_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct max96724_priv *priv; + struct max_des_info *info; + struct max_des_ops *ops; + u32 cc_port; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + ops = devm_kzalloc(dev, sizeof(*ops), GFP_KERNEL); + if (!ops) + return -ENOMEM; + + priv->info = device_get_match_data(dev); + if (!priv->info) { + dev_err(dev, "Failed to get match data\n"); + return -ENODEV; + } + + priv->dev = dev; + priv->client = client; + i2c_set_clientdata(client, priv); + + priv->regmap = devm_regmap_init_i2c(client, &max96724_i2c_regmap); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + priv->gpiod_enable = devm_gpiod_get_optional(&client->dev, "enable", + GPIOD_OUT_LOW); + if (IS_ERR(priv->gpiod_enable)) + return PTR_ERR(priv->gpiod_enable); + + if (priv->gpiod_enable) { + /* PWDN must be held for 1us for reset */ + udelay(1); + + gpiod_set_value_cansleep(priv->gpiod_enable, 1); + + /* Maximum power-up time (tLOCK) 4ms */ + usleep_range(4000, 5000); + + ret = devm_add_action_or_reset(dev, max96724_power_off, + priv); + if (ret) + return ret; + } + + priv->cc_port_cfg = MAX96724_REG3_CC_PORT_CFG_PORT0; + priv->cc_port_crossover = false; + + ret = device_property_read_u32(dev, "maxim,control-channel-port", + &cc_port); + if (!ret) { + switch (cc_port) { + case 0: + priv->cc_port_cfg = MAX96724_REG3_CC_PORT_CFG_PORT0; + break; + case 1: + priv->cc_port_cfg = MAX96724_REG3_CC_PORT_CFG_PORT1; + priv->cc_port_crossover = true; + break; + default: + dev_err(dev, "Invalid control-channel port %u\n", cc_port); + return -EINVAL; + } + } else if (ret != -ENODATA && ret != -ENOENT && ret != -EINVAL) { + return ret; + } + + *info = max96724_des_info; + info->versions = priv->info->versions; + info->modes = priv->info->modes; + info->num_pipes = priv->info->num_pipes; + priv->des.info = info; + + *ops = max96724_des_ops; + ops->set_pipe_tunnel_enable = priv->info->set_pipe_tunnel_enable; + ops->set_pipe_phy = priv->info->set_pipe_phy; + ops->set_pipe_tunnel_phy = priv->info->set_pipe_tunnel_phy; + priv->des.ops = ops; + + ret = max96724_reset(priv); + if (ret) + return ret; + + return max_des_probe(client, &priv->des); +} + +static void max96724_remove(struct i2c_client *client) +{ + struct max96724_priv *priv = i2c_get_clientdata(client); + + max_des_remove(&priv->des); +} + +static const struct of_device_id max96724_of_table[] = { + { .compatible = "maxim,max96712", .data = &max96712_info }, + { .compatible = "maxim,max96724", .data = &max96724_info }, + { .compatible = "maxim,max96724f", .data = &max96724f_info }, + { .compatible = "maxim,max96724r", .data = &max96724f_info }, + { }, +}; +MODULE_DEVICE_TABLE(of, max96724_of_table); + +static struct i2c_driver max96724_i2c_driver = { + .driver = { + .name = "max96724", + .of_match_table = max96724_of_table, + }, + .probe = max96724_probe, + .remove = max96724_remove, +}; + +module_i2c_driver(max96724_i2c_driver); + +MODULE_IMPORT_NS("MAX_SERDES"); +MODULE_DESCRIPTION("Maxim MAX96724 Quad GMSL2 Deserializer Driver"); +MODULE_AUTHOR("Cosmin Tanislav "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/maxim-serdes/max_des.c b/drivers/media/i2c/maxim-serdes/max_des.c new file mode 100644 index 000000000000..6b4ffd0e2415 --- /dev/null +++ b/drivers/media/i2c/maxim-serdes/max_des.c @@ -0,0 +1,3352 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Maxim GMSL2 Deserializer Driver + * + * Copyright (C) 2025 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "max_des.h" +#include "max_ser.h" +#include "max_serdes.h" + +#define MAX_DES_LINK_FREQUENCY_MIN (100 * HZ_PER_MHZ) +#define MAX_DES_LINK_FREQUENCY_DEFAULT (750 * HZ_PER_MHZ) +#define MAX_DES_LINK_FREQUENCY_MAX (1250 * HZ_PER_MHZ) + +#define MAX_DES_NUM_PHYS 4 +#define MAX_DES_NUM_LINKS 4 +#define MAX_DES_NUM_PIPES 8 + +struct max_des_priv { + struct max_des *des; + + struct device *dev; + struct i2c_client *client; + struct i2c_atr *atr; + struct i2c_mux_core *mux; + + struct media_pad *pads; + struct regulator **pocs; + struct max_serdes_source *sources; + u64 *streams_masks; + + struct notifier_block i2c_nb; + struct max_des_link *pending_link; + struct task_struct *pending_task; + struct v4l2_subdev sd; + struct v4l2_async_notifier nf; + struct v4l2_ctrl_handler ctrl_handler; + + struct max_des_phy *unused_phy; +}; + +struct max_des_remap_context { + enum max_serdes_gmsl_mode mode; + /* Mark whether TPG is enabled */ + bool tpg; + /* Mark the PHYs to which each pipe is mapped. */ + unsigned long pipe_phy_masks[MAX_DES_NUM_PIPES]; + /* Mark the pipes in use. */ + bool pipe_in_use[MAX_DES_NUM_PIPES]; + /* Mark whether pipe has remapped VC ids. */ + bool vc_ids_remapped[MAX_DES_NUM_PIPES]; + /* Map between pipe VC ids and PHY VC ids. */ + unsigned int vc_ids_map[MAX_DES_NUM_PIPES][MAX_DES_NUM_PHYS][MAX_SERDES_VC_ID_NUM]; + /* Mark whether a pipe VC id has been mapped to a PHY VC id. */ + unsigned long vc_ids_masks[MAX_DES_NUM_PIPES][MAX_DES_NUM_PHYS]; + /* Mark whether a PHY VC id has been mapped. */ + unsigned long dst_vc_ids_masks[MAX_DES_NUM_PHYS]; +}; + +struct max_des_mode_context { + bool phys_bpp8_shared_with_16[MAX_DES_NUM_PHYS]; + bool pipes_bpp8_shared_with_16[MAX_DES_NUM_PIPES]; + u32 phys_double_bpps[MAX_DES_NUM_PHYS]; + u32 pipes_double_bpps[MAX_DES_NUM_PIPES]; +}; + +struct max_des_route_hw { + struct max_serdes_source *source; + struct max_des_pipe *pipe; + struct max_des_phy *phy; + struct v4l2_mbus_frame_desc_entry entry; + bool is_tpg; +}; + +struct max_des_link_hw { + struct max_serdes_source *source; + struct max_des_link *link; + struct max_des_pipe *pipe; +}; + +static inline struct max_des_priv *sd_to_priv(struct v4l2_subdev *sd) +{ + return container_of(sd, struct max_des_priv, sd); +} + +static inline struct max_des_priv *nf_to_priv(struct v4l2_async_notifier *nf) +{ + return container_of(nf, struct max_des_priv, nf); +} + +static inline struct max_des_priv *ctrl_to_priv(struct v4l2_ctrl_handler *handler) +{ + return container_of(handler, struct max_des_priv, ctrl_handler); +} + +static inline bool max_des_pad_is_sink(struct max_des *des, u32 pad) +{ + return pad < des->info->num_links; +} + +static inline bool max_des_pad_is_source(struct max_des *des, u32 pad) +{ + return pad >= des->info->num_links && + pad < des->info->num_links + des->info->num_phys; +} + +static inline bool max_des_pad_is_tpg(struct max_des *des, u32 pad) +{ + return pad == des->info->num_links + des->info->num_phys; +} + +static inline unsigned int max_des_link_to_pad(struct max_des *des, + struct max_des_link *link) +{ + return link->index; +} + +static inline unsigned int max_des_phy_to_pad(struct max_des *des, + struct max_des_phy *phy) +{ + return phy->index + des->info->num_links; +} + +static inline unsigned int max_des_num_pads(struct max_des *des) +{ + return des->info->num_links + des->info->num_phys; + /* + * TODO: once MEDIA_PAD_FL_INTERNAL is enabled in the uAPI we + * can add the TPG pad: + * return des->info->num_links + des->info->num_phys + + * (des->ops->set_tpg ? 1 : 0); + */ +} + +static struct max_des_phy *max_des_pad_to_phy(struct max_des *des, u32 pad) +{ + if (!max_des_pad_is_source(des, pad)) + return NULL; + + return &des->phys[pad - des->info->num_links]; +} + +static struct max_des_link *max_des_pad_to_link(struct max_des *des, u32 pad) +{ + if (!max_des_pad_is_sink(des, pad)) + return NULL; + + return &des->links[pad]; +} + +static struct max_des_pipe * +max_des_find_link_pipe(struct max_des *des, struct max_des_link *link) +{ + unsigned int i; + + for (i = 0; i < des->info->num_pipes; i++) { + struct max_des_pipe *pipe = &des->pipes[i]; + + if (pipe->link_id == link->index) + return pipe; + } + + return NULL; +} + +static struct max_serdes_source * +max_des_get_link_source(struct max_des_priv *priv, struct max_des_link *link) +{ + return &priv->sources[link->index]; +} + +static const struct max_serdes_tpg_entry * +max_des_find_tpg_entry(struct max_des *des, u32 target_index, + u32 width, u32 height, u32 code, + u32 numerator, u32 denominator) +{ + const struct max_serdes_tpg_entry *entry; + unsigned int index = 0; + unsigned int i; + + for (i = 0; i < des->info->tpg_entries.num_entries; i++) { + entry = &des->info->tpg_entries.entries[i]; + + if ((width != 0 && width != entry->width) || + (height != 0 && height != entry->height) || + (code != 0 && code != entry->code) || + (numerator != 0 && numerator != entry->interval.numerator) || + (denominator != 0 && denominator != entry->interval.denominator)) + continue; + + if (index == target_index) + break; + + index++; + } + + if (i == des->info->tpg_entries.num_entries) + return NULL; + + return &des->info->tpg_entries.entries[i]; +} + +static const struct max_serdes_tpg_entry * +max_des_find_state_tpg_entry(struct max_des *des, struct v4l2_subdev_state *state, + unsigned int pad) +{ + struct v4l2_mbus_framefmt *fmt; + struct v4l2_fract *in; + + fmt = v4l2_subdev_state_get_format(state, pad, MAX_SERDES_TPG_STREAM); + if (!fmt) + return NULL; + + in = v4l2_subdev_state_get_interval(state, pad, MAX_SERDES_TPG_STREAM); + if (!in) + return NULL; + + return max_des_find_tpg_entry(des, 0, fmt->width, fmt->height, fmt->code, + in->numerator, in->denominator); +} + +static int max_des_get_tpg_fd_entry_state(struct max_des *des, + struct v4l2_subdev_state *state, + struct v4l2_mbus_frame_desc_entry *fd_entry, + unsigned int pad) +{ + const struct max_serdes_tpg_entry *entry; + + entry = max_des_find_state_tpg_entry(des, state, pad); + if (!entry) + return -EINVAL; + + fd_entry->stream = MAX_SERDES_TPG_STREAM; + fd_entry->flags = V4L2_MBUS_FRAME_DESC_FL_LEN_MAX; + fd_entry->length = entry->width * entry->height * entry->bpp / 8; + fd_entry->pixelcode = entry->code; + fd_entry->bus.csi2.vc = 0; + fd_entry->bus.csi2.dt = entry->dt; + + return 0; +} + +static int max_des_tpg_route_to_hw(struct max_des_priv *priv, + struct v4l2_subdev_state *state, + struct v4l2_subdev_route *route, + struct max_des_route_hw *hw) +{ + struct max_des *des = priv->des; + + /* TPG injects its data into all pipes, but use pipe 0 for simplicity. */ + hw->pipe = &des->pipes[0]; + + hw->phy = max_des_pad_to_phy(des, route->source_pad); + if (!hw->phy) + return -ENOENT; + + return max_des_get_tpg_fd_entry_state(des, state, &hw->entry, + route->sink_pad); +} + +static int max_des_route_to_hw(struct max_des_priv *priv, + struct v4l2_subdev_state *state, + struct v4l2_subdev_route *route, + struct max_des_route_hw *hw) +{ + struct max_des *des = priv->des; + struct v4l2_mbus_frame_desc fd = {}; + struct max_des_link *link; + unsigned int i; + int ret; + + memset(hw, 0, sizeof(*hw)); + + hw->is_tpg = max_des_pad_is_tpg(des, route->sink_pad); + if (hw->is_tpg) + return max_des_tpg_route_to_hw(priv, state, route, hw); + + link = max_des_pad_to_link(des, route->sink_pad); + if (!link) + return -ENOENT; + + hw->phy = max_des_pad_to_phy(des, route->source_pad); + if (!hw->phy) + return -ENOENT; + + hw->pipe = max_des_find_link_pipe(des, link); + if (!hw->pipe) + return -ENOENT; + + hw->source = max_des_get_link_source(priv, link); + if (!hw->source->sd) + return 0; + + ret = v4l2_subdev_call(hw->source->sd, pad, get_frame_desc, + hw->source->pad, &fd); + if (ret) + return ret; + + for (i = 0; i < fd.num_entries; i++) + if (fd.entry[i].stream == route->sink_stream) + break; + + if (i == fd.num_entries) + return -ENOENT; + + hw->entry = fd.entry[i]; + + return 0; +} + +static int max_des_link_to_hw(struct max_des_priv *priv, + struct max_des_link *link, + struct max_des_link_hw *hw) +{ + struct max_des *des = priv->des; + + memset(hw, 0, sizeof(*hw)); + + hw->link = link; + + hw->pipe = max_des_find_link_pipe(des, hw->link); + if (!hw->pipe) + return -ENOENT; + + hw->source = max_des_get_link_source(priv, hw->link); + + return 0; +} + +static int max_des_link_index_to_hw(struct max_des_priv *priv, unsigned int i, + struct max_des_link_hw *hw) +{ + return max_des_link_to_hw(priv, &priv->des->links[i], hw); +} + +static int max_des_set_pipe_remaps(struct max_des_priv *priv, + struct max_des_pipe *pipe, + struct max_des_remap *remaps, + unsigned int num_remaps) +{ + struct max_des *des = priv->des; + unsigned int mask = 0; + unsigned int i; + int ret; + + if (!des->ops->set_pipe_remap) + return 0; + + for (i = 0; i < num_remaps; i++) { + ret = des->ops->set_pipe_remap(des, pipe, i, &remaps[i]); + if (ret) + return ret; + + mask |= BIT(i); + } + + return des->ops->set_pipe_remaps_enable(des, pipe, mask); +} + +static int max_des_set_pipe_vc_remaps(struct max_des_priv *priv, + struct max_des_pipe *pipe, + struct max_serdes_vc_remap *vc_remaps, + unsigned int num_vc_remaps) +{ + struct max_des *des = priv->des; + unsigned int mask = 0; + unsigned int i; + int ret; + + for (i = 0; i < num_vc_remaps; i++) { + ret = des->ops->set_pipe_vc_remap(des, pipe, i, &vc_remaps[i]); + if (ret) + return ret; + + mask |= BIT(i); + } + + return des->ops->set_pipe_vc_remaps_enable(des, pipe, mask); +} + +static int max_des_map_src_dst_vc_id(struct max_des_remap_context *context, + unsigned int pipe_id, unsigned int phy_id, + unsigned int src_vc_id, bool keep_vc) +{ + unsigned int vc_id; + + if (src_vc_id >= MAX_SERDES_VC_ID_NUM) + return -E2BIG; + + if (context->vc_ids_masks[pipe_id][phy_id] & BIT(src_vc_id)) + return 0; + + if (keep_vc && !(context->dst_vc_ids_masks[phy_id] & BIT(src_vc_id))) + vc_id = src_vc_id; + else + vc_id = ffz(context->dst_vc_ids_masks[phy_id]); + + if (vc_id != src_vc_id) + context->vc_ids_remapped[pipe_id] = true; + + if (vc_id >= MAX_SERDES_VC_ID_NUM) + return -E2BIG; + + context->pipe_phy_masks[pipe_id] |= BIT(phy_id); + context->dst_vc_ids_masks[phy_id] |= BIT(vc_id); + + context->vc_ids_map[pipe_id][phy_id][src_vc_id] = vc_id; + context->vc_ids_masks[pipe_id][phy_id] |= BIT(src_vc_id); + + return 0; +} + +static int max_des_get_src_dst_vc_id(struct max_des_remap_context *context, + unsigned int pipe_id, unsigned int phy_id, + unsigned int src_vc_id, unsigned int *dst_vc_id) +{ + if (!(context->vc_ids_masks[pipe_id][phy_id] & BIT(src_vc_id))) + return -ENOENT; + + *dst_vc_id = context->vc_ids_map[pipe_id][phy_id][src_vc_id]; + + return 0; +} + +static int max_des_populate_remap_usage(struct max_des_priv *priv, + struct max_des_remap_context *context, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_route *route; + int ret; + + for_each_active_route(&state->routing, route) { + struct max_des_route_hw hw; + + ret = max_des_route_to_hw(priv, state, route, &hw); + if (ret) + return ret; + + if (hw.is_tpg) + context->tpg = true; + + context->pipe_in_use[hw.pipe->index] = true; + } + + return 0; +} + +static int max_des_get_supported_modes(struct max_des_priv *priv, + struct max_des_remap_context *context, + unsigned int *modes) +{ + struct max_des *des = priv->des; + unsigned int i; + int ret; + + *modes = des->info->modes; + + if (context->tpg) + *modes = BIT(des->info->tpg_mode); + + for (i = 0; i < des->info->num_links; i++) { + struct max_des_link_hw hw; + + ret = max_des_link_index_to_hw(priv, i, &hw); + if (ret) + return ret; + + if (!hw.link->enabled) + continue; + + if (!hw.source->sd) + continue; + + if (!context->pipe_in_use[hw.pipe->index]) + continue; + + *modes &= max_ser_get_supported_modes(hw.source->sd); + } + + /* + * Serializers need to all be in the same mode because of hardware + * issues when running them in mixed modes. + */ + if (!*modes) + return -EINVAL; + + return 0; +} + +static int max_des_populate_remap_context_mode(struct max_des_priv *priv, + struct max_des_remap_context *context, + unsigned int modes) +{ + struct max_des *des = priv->des; + unsigned int i; + int ret; + + context->mode = MAX_SERDES_GMSL_PIXEL_MODE; + + /* + * If pixel mode is the only supported mode, do not try to see if + * tunnel mode can be used. + */ + if (modes == BIT(MAX_SERDES_GMSL_PIXEL_MODE)) + return 0; + + for (i = 0; i < des->info->num_links; i++) { + struct max_des_link_hw hw; + + ret = max_des_link_index_to_hw(priv, i, &hw); + if (ret) + return ret; + + if (!hw.link->enabled) + continue; + + if (!hw.source->sd) + continue; + + if (!context->pipe_in_use[hw.pipe->index]) + continue; + + if (hweight_long(context->pipe_phy_masks[hw.pipe->index]) == 1 && + (!context->vc_ids_remapped[hw.pipe->index] || + max_ser_supports_vc_remap(hw.source->sd) || + des->ops->set_pipe_vc_remap)) + continue; + + return 0; + } + + context->mode = MAX_SERDES_GMSL_TUNNEL_MODE; + + return 0; +} + +static int max_des_should_keep_vc(struct max_des_priv *priv, + struct max_des_route_hw *hw, + unsigned int modes) +{ + struct max_des *des = priv->des; + + /* Pixel mode deserializers always have the ability to remap VCs. */ + if (modes == BIT(MAX_SERDES_GMSL_PIXEL_MODE)) + return false; + + if (des->ops->set_pipe_vc_remap) + return false; + + if (!hw->is_tpg && hw->source && hw->source->sd && + max_ser_supports_vc_remap(hw->source->sd)) + return false; + + return true; +} + +static int max_des_populate_remap_context(struct max_des_priv *priv, + struct max_des_remap_context *context, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_route *route; + unsigned int modes; + int ret; + + ret = max_des_populate_remap_usage(priv, context, state); + if (ret) + return ret; + + ret = max_des_get_supported_modes(priv, context, &modes); + if (ret) + return ret; + + for_each_active_route(&state->routing, route) { + struct max_des_route_hw hw; + bool keep_vc; + + ret = max_des_route_to_hw(priv, state, route, &hw); + if (ret) + return ret; + + keep_vc = max_des_should_keep_vc(priv, &hw, modes); + + ret = max_des_map_src_dst_vc_id(context, hw.pipe->index, hw.phy->index, + hw.entry.bus.csi2.vc, keep_vc); + if (ret) + return ret; + } + + return max_des_populate_remap_context_mode(priv, context, modes); +} + +static int max_des_populate_mode_context(struct max_des_priv *priv, + struct max_des_mode_context *context, + struct v4l2_subdev_state *state, + enum max_serdes_gmsl_mode mode) +{ + bool bpp8_not_shared_with_16_phys[MAX_DES_NUM_PHYS] = { 0 }; + u32 undoubled_bpps_phys[MAX_DES_NUM_PHYS] = { 0 }; + u32 bpps_pipes[MAX_DES_NUM_PIPES] = { 0 }; + struct max_des *des = priv->des; + struct v4l2_subdev_route *route; + unsigned int i; + int ret; + + if (mode != MAX_SERDES_GMSL_PIXEL_MODE) + return 0; + + /* + * Go over all streams and gather the bpps for all pipes. + * + * Then, go over all the streams again and check if the + * current stream is doubled. + * + * If the current stream is doubled, add it to a doubled mask for both + * the pipe and the PHY. + * + * If the current stream is not doubled, add it to a local undoubled + * mask for the PHY. + * + * Also, track whether an 8bpp stream is shared with any bpp > 8 on both + * the PHYs and the pipes, since that needs to be special cased. + * + * After going over all the streams, remove the undoubled streams from + * the doubled ones. Doubled and undoubled streams cannot be streamed + * over the same PHY. + * + * Then, do a second pass to remove the undoubled streams from the pipes. + * + * This operation cannot be done in a single pass because any pipe might + * generate an undoubled stream for a specific bpp, causing already + * processed pipes to need to have their doubled bpps updated. + */ + + for_each_active_route(&state->routing, route) { + struct max_des_route_hw hw; + unsigned int bpp; + + ret = max_des_route_to_hw(priv, state, route, &hw); + if (ret) + return ret; + + ret = max_serdes_get_fd_bpp(&hw.entry, &bpp); + if (ret) + return ret; + + bpps_pipes[hw.pipe->index] |= BIT(bpp); + } + + for_each_active_route(&state->routing, route) { + unsigned int bpp, min_bpp, max_bpp, doubled_bpp; + unsigned int pipe_id, phy_id; + struct max_des_route_hw hw; + u32 sink_bpps; + + ret = max_des_route_to_hw(priv, state, route, &hw); + if (ret) + return ret; + + ret = max_serdes_get_fd_bpp(&hw.entry, &bpp); + if (ret) + return ret; + + sink_bpps = bpps_pipes[hw.pipe->index]; + + ret = max_serdes_process_bpps(priv->dev, sink_bpps, ~0U, &doubled_bpp); + if (ret) + return ret; + + min_bpp = __ffs(sink_bpps); + max_bpp = __fls(sink_bpps); + pipe_id = hw.pipe->index; + phy_id = hw.phy->index; + + if (bpp == doubled_bpp) { + context->phys_double_bpps[phy_id] |= BIT(bpp); + context->pipes_double_bpps[pipe_id] |= BIT(bpp); + } else { + undoubled_bpps_phys[phy_id] |= BIT(bpp); + } + + if (min_bpp == 8 && max_bpp > 8) { + context->phys_bpp8_shared_with_16[phy_id] = true; + context->pipes_bpp8_shared_with_16[pipe_id] = true; + } else if (min_bpp == 8 && max_bpp == 8) { + bpp8_not_shared_with_16_phys[phy_id] = true; + } + } + + for (i = 0; i < des->info->num_phys; i++) { + if (context->phys_bpp8_shared_with_16[i] && bpp8_not_shared_with_16_phys[i]) { + dev_err(priv->dev, + "Cannot stream 8bpp coming from pipes padded to 16bpp and pipes not padded to 16bpp on the same PHY\n"); + return -EINVAL; + } + } + + for (i = 0; i < des->info->num_phys; i++) + context->phys_double_bpps[i] &= ~undoubled_bpps_phys[i]; + + for_each_active_route(&state->routing, route) { + struct max_des_route_hw hw; + + ret = max_des_route_to_hw(priv, state, route, &hw); + if (ret) + return ret; + + context->pipes_double_bpps[hw.pipe->index] &= + context->phys_double_bpps[hw.phy->index]; + } + + return 0; +} + +static int max_des_add_vc_remap(struct max_des *des, struct max_serdes_vc_remap *vc_remaps, + unsigned int *num_vc_remaps, unsigned int src_vc_id, + unsigned int dst_vc_id) +{ + struct max_serdes_vc_remap *vc_remap; + unsigned int i; + + for (i = 0; i < *num_vc_remaps; i++) { + vc_remap = &vc_remaps[i]; + + if (vc_remap->src == src_vc_id && vc_remap->dst == dst_vc_id) + return 0; + } + + if (*num_vc_remaps == MAX_SERDES_VC_ID_NUM) + return -E2BIG; + + vc_remaps[*num_vc_remaps].src = src_vc_id; + vc_remaps[*num_vc_remaps].dst = dst_vc_id; + + (*num_vc_remaps)++; + + return 0; +} + +static int max_des_get_pipe_vc_remaps(struct max_des_priv *priv, + struct max_des_remap_context *context, + struct max_des_pipe *pipe, + struct max_serdes_vc_remap *vc_remaps, + unsigned int *num_vc_remaps, + struct v4l2_subdev_state *state, + u64 *streams_masks, bool with_tpg) +{ + struct max_des *des = priv->des; + struct v4l2_subdev_route *route; + int ret; + + *num_vc_remaps = 0; + + if (context->mode != MAX_SERDES_GMSL_TUNNEL_MODE) + return 0; + + for_each_active_route(&state->routing, route) { + unsigned int src_vc_id, dst_vc_id; + struct max_des_route_hw hw; + + if (!(BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad])) + continue; + + ret = max_des_route_to_hw(priv, state, route, &hw); + if (ret) + return ret; + + if (!with_tpg && hw.is_tpg) + continue; + + if (hw.pipe != pipe) + continue; + + src_vc_id = hw.entry.bus.csi2.vc; + + ret = max_des_get_src_dst_vc_id(context, pipe->index, hw.phy->index, + src_vc_id, &dst_vc_id); + if (ret) + return ret; + + ret = max_des_add_vc_remap(des, vc_remaps, num_vc_remaps, + src_vc_id, dst_vc_id); + if (ret) + return ret; + } + + return 0; +} + +static void max_des_get_pipe_mode(struct max_des_mode_context *context, + struct max_des_pipe *pipe, + struct max_des_pipe_mode *mode) +{ + u32 double_bpps = context->pipes_double_bpps[pipe->index]; + + if ((double_bpps & BIT(8)) && + !context->pipes_bpp8_shared_with_16[pipe->index]) { + mode->dbl8 = true; + mode->dbl8mode = true; + } +} + +static void max_des_get_phy_mode(struct max_des_mode_context *context, + struct max_des_phy *phy, + struct max_des_phy_mode *mode) +{ + bool bpp8_pipe_shared_with_16 = context->phys_bpp8_shared_with_16[phy->index]; + u32 double_bpps = context->phys_double_bpps[phy->index]; + + if (BIT(8) & double_bpps) { + if (bpp8_pipe_shared_with_16) + mode->alt2_mem_map8 = true; + else + mode->alt_mem_map8 = true; + } + + if (BIT(10) & double_bpps) + mode->alt_mem_map10 = true; + + if (BIT(12) & double_bpps) + mode->alt_mem_map12 = true; +} + +static int max_des_set_modes(struct max_des_priv *priv, + struct max_des_mode_context *context) +{ + struct max_des *des = priv->des; + unsigned int i; + int ret; + + for (i = 0; i < des->info->num_phys; i++) { + struct max_des_phy *phy = &des->phys[i]; + struct max_des_phy_mode mode = { 0 }; + + max_des_get_phy_mode(context, phy, &mode); + + if (phy->mode.alt_mem_map8 == mode.alt_mem_map8 && + phy->mode.alt_mem_map10 == mode.alt_mem_map10 && + phy->mode.alt_mem_map12 == mode.alt_mem_map12 && + phy->mode.alt2_mem_map8 == mode.alt2_mem_map8) + continue; + + if (des->ops->set_phy_mode) { + ret = des->ops->set_phy_mode(des, phy, &mode); + if (ret) + return ret; + } + + phy->mode = mode; + } + + for (i = 0; i < des->info->num_pipes; i++) { + struct max_des_pipe *pipe = &des->pipes[i]; + struct max_des_pipe_mode mode = { 0 }; + + max_des_get_pipe_mode(context, pipe, &mode); + + if (pipe->mode.dbl8 == mode.dbl8 && + pipe->mode.dbl10 == mode.dbl10 && + pipe->mode.dbl12 == mode.dbl12 && + pipe->mode.dbl8mode == mode.dbl8mode && + pipe->mode.dbl10mode == mode.dbl10mode) + continue; + + if (des->ops->set_pipe_mode) { + ret = des->ops->set_pipe_mode(des, pipe, &mode); + if (ret) + return ret; + } + + pipe->mode = mode; + } + + for (i = 0; i < des->info->num_links; i++) { + struct max_des_link_hw hw; + u32 pipe_double_bpps = 0; + + ret = max_des_link_index_to_hw(priv, i, &hw); + if (ret) + return ret; + + if (!hw.link->enabled) + continue; + + if (!hw.source->sd) + continue; + + pipe_double_bpps = context->pipes_double_bpps[hw.pipe->index]; + + ret = max_ser_set_double_bpps(hw.source->sd, pipe_double_bpps); + if (ret) + return ret; + } + + return 0; +} + +static int max_des_set_tunnel(struct max_des_priv *priv, + struct max_des_remap_context *context) +{ + struct max_des *des = priv->des; + unsigned int i; + int ret; + + if (des->ops->set_pipe_tunnel_enable) { + for (i = 0; i < des->info->num_pipes; i++) { + struct max_des_pipe *pipe = &des->pipes[i]; + bool tunnel_mode = context->mode == MAX_SERDES_GMSL_TUNNEL_MODE; + + ret = des->ops->set_pipe_tunnel_enable(des, pipe, tunnel_mode); + if (ret) + return ret; + } + } + + for (i = 0; i < des->info->num_links; i++) { + struct max_des_link_hw hw; + + ret = max_des_link_index_to_hw(priv, i, &hw); + if (ret) + return ret; + + if (!hw.link->enabled) + continue; + + if (!hw.source->sd) + continue; + + if (!context->pipe_in_use[hw.pipe->index]) + continue; + + ret = max_ser_set_mode(hw.source->sd, context->mode); + if (ret) + return ret; + } + + des->mode = context->mode; + + return 0; +} + +static int max_des_set_vc_remaps(struct max_des_priv *priv, + struct max_des_remap_context *context, + struct v4l2_subdev_state *state, + u64 *streams_masks) +{ + struct max_des *des = priv->des; + unsigned int i; + int ret; + + if (des->ops->set_pipe_vc_remap) + return 0; + + for (i = 0; i < des->info->num_links; i++) { + struct max_serdes_vc_remap vc_remaps[MAX_SERDES_VC_ID_NUM]; + struct max_des_link_hw hw; + unsigned int num_vc_remaps; + + ret = max_des_link_index_to_hw(priv, i, &hw); + if (ret) + return ret; + + if (!hw.link->enabled) + continue; + + if (!hw.source->sd) + continue; + + if (!max_ser_supports_vc_remap(hw.source->sd)) + continue; + + ret = max_des_get_pipe_vc_remaps(priv, context, hw.pipe, + vc_remaps, &num_vc_remaps, + state, streams_masks, false); + if (ret) + return ret; + + ret = max_ser_set_vc_remaps(hw.source->sd, vc_remaps, num_vc_remaps); + if (ret) + return ret; + } + + return 0; +} + +static int max_des_set_pipes_stream_id(struct max_des_priv *priv) +{ + bool stream_id_usage[MAX_SERDES_STREAMS_NUM] = { 0 }; + struct max_des *des = priv->des; + unsigned int i; + int ret; + + for (i = 0; i < des->info->num_links; i++) { + struct max_des_link_hw hw; + unsigned int stream_id; + + ret = max_des_link_index_to_hw(priv, i, &hw); + if (ret) + return ret; + + if (!hw.link->enabled) + continue; + + if (!hw.source->sd) + continue; + + stream_id = hw.pipe->stream_id; + + ret = max_ser_set_stream_id(hw.source->sd, stream_id); + if (ret == -EOPNOTSUPP) { + /* + * Serializer does not support setting the stream id, + * retrieve its hardcoded stream id. + */ + ret = max_ser_get_stream_id(hw.source->sd, &stream_id); + } + + if (ret) + return ret; + + if (stream_id >= MAX_SERDES_STREAMS_NUM) { + dev_err(priv->dev, "Invalid stream id %u\n", stream_id); + return -EINVAL; + } + + if (stream_id_usage[stream_id] && des->info->needs_unique_stream_id) { + dev_err(priv->dev, "Duplicate stream id %u\n", stream_id); + return -EINVAL; + } + + ret = des->ops->set_pipe_stream_id(des, hw.pipe, stream_id); + if (ret) + return ret; + + stream_id_usage[stream_id] = true; + hw.pipe->stream_id = stream_id; + } + + return 0; +} + +static int max_des_set_pipes_phy(struct max_des_priv *priv, + struct max_des_remap_context *context) +{ + struct max_des *des = priv->des; + unsigned int i; + int ret; + + if (!des->ops->set_pipe_phy && !des->ops->set_pipe_tunnel_phy) + return 0; + + for (i = 0; i < des->info->num_pipes; i++) { + struct max_des_pipe *pipe = &des->pipes[i]; + struct max_des_phy *phy; + unsigned int phy_id; + + phy_id = find_first_bit(&context->pipe_phy_masks[pipe->index], + des->info->num_phys); + + if (priv->unused_phy && + (context->mode != MAX_SERDES_GMSL_TUNNEL_MODE || + phy_id == des->info->num_phys)) + phy_id = priv->unused_phy->index; + + if (phy_id != des->info->num_phys) { + phy = &des->phys[phy_id]; + + if (context->mode == MAX_SERDES_GMSL_PIXEL_MODE && + des->ops->set_pipe_phy) + ret = des->ops->set_pipe_phy(des, pipe, phy); + else if (context->mode == MAX_SERDES_GMSL_TUNNEL_MODE && + des->ops->set_pipe_tunnel_phy) + ret = des->ops->set_pipe_tunnel_phy(des, pipe, phy); + else + ret = 0; + + if (ret) + return ret; + } + + pipe->phy_id = phy_id; + } + + return 0; +} + +static int max_des_add_remap(struct max_des *des, struct max_des_remap *remaps, + unsigned int *num_remaps, unsigned int phy_id, + unsigned int src_vc_id, unsigned int dst_vc_id, + unsigned int dt) +{ + struct max_des_remap *remap; + unsigned int i; + + for (i = 0; i < *num_remaps; i++) { + remap = &remaps[i]; + + if (remap->from_dt == dt && remap->to_dt == dt && + remap->from_vc == src_vc_id && remap->to_vc == dst_vc_id && + remap->phy == phy_id) + return 0; + } + + if (*num_remaps == des->info->num_remaps_per_pipe) + return -E2BIG; + + remap = &remaps[*num_remaps]; + remap->from_dt = dt; + remap->from_vc = src_vc_id; + remap->to_dt = dt; + remap->to_vc = dst_vc_id; + remap->phy = phy_id; + + (*num_remaps)++; + + return 0; +} + +static int max_des_add_remaps(struct max_des *des, struct max_des_remap *remaps, + unsigned int *num_remaps, unsigned int phy_id, + unsigned int src_vc_id, unsigned int dst_vc_id, + unsigned int dt) +{ + int ret; + + ret = max_des_add_remap(des, remaps, num_remaps, phy_id, + src_vc_id, dst_vc_id, dt); + if (ret) + return ret; + + ret = max_des_add_remap(des, remaps, num_remaps, phy_id, + src_vc_id, dst_vc_id, MIPI_CSI2_DT_FS); + if (ret) + return ret; + + ret = max_des_add_remap(des, remaps, num_remaps, phy_id, + src_vc_id, dst_vc_id, MIPI_CSI2_DT_FE); + if (ret) + return ret; + + return 0; +} + +static int max_des_get_pipe_remaps(struct max_des_priv *priv, + struct max_des_remap_context *context, + struct max_des_pipe *pipe, + struct max_des_remap *remaps, + unsigned int *num_remaps, + struct v4l2_subdev_state *state, + u64 *streams_masks) +{ + struct v4l2_mbus_frame_desc_entry tpg_entry = { 0 }; + struct max_des *des = priv->des; + struct v4l2_subdev_route *route; + bool is_tpg_pipe = true; + int ret; + + *num_remaps = 0; + + if (context->mode != MAX_SERDES_GMSL_PIXEL_MODE) + return 0; + + for_each_active_route(&state->routing, route) { + struct max_des_route_hw hw; + unsigned int src_vc_id, dst_vc_id; + + if (!(BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad])) + continue; + + ret = max_des_route_to_hw(priv, state, route, &hw); + if (ret) + return ret; + + if (hw.is_tpg && hw.pipe != pipe) { + is_tpg_pipe = false; + tpg_entry = hw.entry; + } + + if (hw.pipe != pipe) + continue; + + src_vc_id = hw.entry.bus.csi2.vc; + + ret = max_des_get_src_dst_vc_id(context, pipe->index, hw.phy->index, + src_vc_id, &dst_vc_id); + if (ret) + return ret; + + ret = max_des_add_remaps(des, remaps, num_remaps, hw.phy->index, + src_vc_id, dst_vc_id, + hw.entry.bus.csi2.dt); + if (ret) + return ret; + } + + /* + * TPG mode is only handled on pipe 0, but the TPG pollutes other pipes + * with the same data. + * For devices that do not support setting the default PHY of a pipe, + * we want to filter out this data so it does not end up on the wrong + * PHY. + * Devices that support setting the default PHY of a pipe already use it + * to route unused pipes to an unused PHY. + */ + if (context->tpg && !is_tpg_pipe && !des->ops->set_pipe_phy && + priv->unused_phy) { + ret = max_des_add_remaps(des, remaps, num_remaps, + priv->unused_phy->index, + tpg_entry.bus.csi2.vc, + tpg_entry.bus.csi2.vc, + tpg_entry.bus.csi2.dt); + if (ret) + return ret; + } + + return 0; +} + +static int max_des_update_pipe_vc_remaps(struct max_des_priv *priv, + struct max_des_remap_context *context, + struct max_des_pipe *pipe, + struct v4l2_subdev_state *state, + u64 *streams_masks) +{ + struct max_des *des = priv->des; + struct max_serdes_vc_remap *vc_remaps; + unsigned int num_vc_remaps; + int ret; + + if (!des->ops->set_pipe_vc_remap) + return 0; + + vc_remaps = devm_kcalloc(priv->dev, MAX_SERDES_VC_ID_NUM, + sizeof(*vc_remaps), GFP_KERNEL); + if (!vc_remaps) + return -ENOMEM; + + ret = max_des_get_pipe_vc_remaps(priv, context, pipe, vc_remaps, &num_vc_remaps, + state, streams_masks, true); + if (ret) + goto err_free_new_vc_remaps; + + ret = max_des_set_pipe_vc_remaps(priv, pipe, vc_remaps, num_vc_remaps); + if (ret) + goto err_free_new_vc_remaps; + + if (pipe->vc_remaps) + devm_kfree(priv->dev, pipe->vc_remaps); + + pipe->vc_remaps = vc_remaps; + pipe->num_vc_remaps = num_vc_remaps; + + return 0; + +err_free_new_vc_remaps: + devm_kfree(priv->dev, vc_remaps); + + return ret; +} + +static int max_des_update_pipe_remaps(struct max_des_priv *priv, + struct max_des_remap_context *context, + struct max_des_pipe *pipe, + struct v4l2_subdev_state *state, + u64 *streams_masks) +{ + struct max_des *des = priv->des; + struct max_des_remap *remaps; + unsigned int num_remaps; + int ret; + + if (!des->ops->set_pipe_remap) + return 0; + + remaps = devm_kcalloc(priv->dev, des->info->num_remaps_per_pipe, + sizeof(*remaps), GFP_KERNEL); + if (!remaps) + return -ENOMEM; + + ret = max_des_get_pipe_remaps(priv, context, pipe, remaps, &num_remaps, + state, streams_masks); + if (ret) + goto err_free_new_remaps; + + ret = max_des_set_pipe_remaps(priv, pipe, remaps, num_remaps); + if (ret) + goto err_free_new_remaps; + + if (pipe->remaps) + devm_kfree(priv->dev, pipe->remaps); + + pipe->remaps = remaps; + pipe->num_remaps = num_remaps; + + return 0; + +err_free_new_remaps: + devm_kfree(priv->dev, remaps); + + return ret; +} + +static int max_des_update_pipe_enable(struct max_des_priv *priv, + struct max_des_pipe *pipe, + struct v4l2_subdev_state *state, + u64 *streams_masks) +{ + struct max_des *des = priv->des; + struct v4l2_subdev_route *route; + bool enable = false; + int ret; + + for_each_active_route(&state->routing, route) { + struct max_des_route_hw hw; + + if (!(BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad])) + continue; + + ret = max_des_route_to_hw(priv, state, route, &hw); + if (ret) + return ret; + + if (hw.pipe != pipe) + continue; + + enable = true; + break; + } + + if (enable == pipe->enabled) + return 0; + + ret = des->ops->set_pipe_enable(des, pipe, enable); + if (ret) + return ret; + + pipe->enabled = enable; + + return 0; +} + +static int max_des_update_pipe(struct max_des_priv *priv, + struct max_des_remap_context *context, + struct max_des_pipe *pipe, + struct v4l2_subdev_state *state, + u64 *streams_masks) +{ + int ret; + + ret = max_des_update_pipe_remaps(priv, context, pipe, + state, streams_masks); + if (ret) + return ret; + + ret = max_des_update_pipe_vc_remaps(priv, context, pipe, state, + streams_masks); + if (ret) + goto err_revert_update_pipe_remaps; + + ret = max_des_update_pipe_enable(priv, pipe, state, streams_masks); + if (ret) + goto err_revert_update_pipe_vc_remaps; + + return 0; + +err_revert_update_pipe_vc_remaps: + max_des_update_pipe_vc_remaps(priv, context, pipe, state, + priv->streams_masks); + +err_revert_update_pipe_remaps: + max_des_update_pipe_remaps(priv, context, pipe, state, + priv->streams_masks); + + return ret; +} + +static int max_des_init_link_ser_xlate(struct max_des_priv *priv, + struct max_des_link *link, + struct i2c_adapter *adapter, + u8 power_up_addr, u8 new_addr) +{ + struct max_des *des = priv->des; + u8 addrs[] = { power_up_addr, new_addr }; + u8 current_addr; + int ret; + + if (des->ops->select_links) { + ret = des->ops->select_links(des, BIT(link->index)); + if (ret) + return ret; + } + + ret = max_ser_wait_for_multiple(adapter, addrs, ARRAY_SIZE(addrs), + ¤t_addr); + if (ret) { + dev_err(priv->dev, + "Failed to wait for serializer at 0x%02x or 0x%02x: %d\n", + power_up_addr, new_addr, ret); + return ret; + } + + ret = max_ser_reset(adapter, current_addr); + if (ret) { + dev_err(priv->dev, "Failed to reset serializer: %d\n", ret); + return ret; + } + + ret = max_ser_wait(adapter, power_up_addr); + if (ret) { + dev_err(priv->dev, + "Failed to wait for serializer at 0x%02x: %d\n", + power_up_addr, ret); + return ret; + } + + ret = max_ser_change_address(adapter, power_up_addr, new_addr); + if (ret) { + dev_err(priv->dev, + "Failed to change serializer from 0x%02x to 0x%02x: %d\n", + power_up_addr, new_addr, ret); + return ret; + } + + ret = max_ser_wait(adapter, new_addr); + if (ret) { + dev_err(priv->dev, + "Failed to wait for serializer at 0x%02x: %d\n", + new_addr, ret); + return ret; + } + + if (des->info->fix_tx_ids) { + ret = max_ser_fix_tx_ids(adapter, new_addr); + if (ret) + return ret; + } + + return ret; +} + +static int max_des_init(struct max_des_priv *priv) +{ + struct max_des *des = priv->des; + unsigned int i; + int ret; + + if (des->ops->init) { + ret = des->ops->init(des); + if (ret) + return ret; + } + + if (des->ops->set_enable) { + ret = des->ops->set_enable(des, false); + if (ret) + return ret; + } + + for (i = 0; i < des->info->num_phys; i++) { + struct max_des_phy *phy = &des->phys[i]; + + if (phy->enabled) { + ret = des->ops->init_phy(des, phy); + if (ret) + return ret; + } + + ret = des->ops->set_phy_enable(des, phy, phy->enabled); + if (ret) + return ret; + } + + for (i = 0; i < des->info->num_pipes; i++) { + struct max_des_pipe *pipe = &des->pipes[i]; + struct max_des_link *link = &des->links[pipe->link_id]; + + ret = des->ops->set_pipe_enable(des, pipe, false); + if (ret) + return ret; + + if (des->ops->set_pipe_tunnel_enable) { + ret = des->ops->set_pipe_tunnel_enable(des, pipe, false); + if (ret) + return ret; + } + + if (des->ops->set_pipe_stream_id) { + ret = des->ops->set_pipe_stream_id(des, pipe, pipe->stream_id); + if (ret) + return ret; + } + + if (des->ops->set_pipe_link) { + ret = des->ops->set_pipe_link(des, pipe, link); + if (ret) + return ret; + } + + ret = max_des_set_pipe_remaps(priv, pipe, pipe->remaps, + pipe->num_remaps); + if (ret) + return ret; + } + + if (!des->ops->init_link) + return 0; + + for (i = 0; i < des->info->num_links; i++) { + struct max_des_link *link = &des->links[i]; + + if (!link->enabled) + continue; + + ret = des->ops->init_link(des, link); + if (ret) + return ret; + } + + return 0; +} + +static void max_des_ser_find_version_range(struct max_des *des, int *min, int *max) +{ + unsigned int i; + + *min = MAX_SERDES_GMSL_MIN; + *max = MAX_SERDES_GMSL_MAX; + + if (!des->info->needs_single_link_version) + return; + + for (i = 0; i < des->info->num_links; i++) { + struct max_des_link *link = &des->links[i]; + + if (!link->enabled) + continue; + + if (!link->ser_xlate.en) + continue; + + *min = *max = link->version; + + return; + } +} + +static unsigned int max_des_enabled_links_mask(struct max_des *des) +{ + unsigned int mask = 0; + unsigned int i; + + for (i = 0; i < des->info->num_links; i++) { + struct max_des_link *link = &des->links[i]; + + if (link->enabled) + mask |= BIT(link->index); + } + + return mask; +} + +static int max_des_ser_attach_addr(struct max_des_priv *priv, u32 chan_id, + u16 addr, u16 alias) +{ + struct max_des *des = priv->des; + struct max_des_link *link = &des->links[chan_id]; + unsigned int mask; + int i, min, max; + int ret = -ENOENT; + int err; + + max_des_ser_find_version_range(des, &min, &max); + + if (link->ser_xlate.en) { + dev_err(priv->dev, "Serializer for link %u already bound\n", + link->index); + return -EINVAL; + } + + for (i = max; i >= min; i--) { + if (!(des->info->versions & BIT(i))) + continue; + + if (des->ops->set_link_version) { + ret = des->ops->set_link_version(des, link, i); + if (ret) + goto out_select_links; + } + + ret = max_des_init_link_ser_xlate(priv, link, priv->client->adapter, + addr, alias); + if (!ret) + break; + } + + if (ret) { + dev_err(priv->dev, "Cannot find serializer for link %u\n", + link->index); + ret = -ENOENT; + goto out_select_links; + } + + link->version = i; + link->ser_xlate.src = alias; + link->ser_xlate.dst = addr; + link->ser_xlate.en = true; + +out_select_links: + if (!des->ops->select_links) + return ret; + + mask = max_des_enabled_links_mask(des); + err = des->ops->select_links(des, mask); + if (err) + dev_warn(priv->dev, "Failed to restore link selection: %d\n", + err); + + return ret; +} + +static int max_des_ser_atr_attach_addr(struct i2c_atr *atr, u32 chan_id, + u16 addr, u16 alias) +{ + struct max_des_priv *priv = i2c_atr_get_driver_data(atr); + + return max_des_ser_attach_addr(priv, chan_id, addr, alias); +} + +static void max_des_ser_atr_detach_addr(struct i2c_atr *atr, u32 chan_id, u16 addr) +{ + /* Don't do anything. */ +} + +static const struct i2c_atr_ops max_des_i2c_atr_ops = { + .attach_addr = max_des_ser_atr_attach_addr, + .detach_addr = max_des_ser_atr_detach_addr, +}; + +static void max_des_i2c_atr_deinit(struct max_des_priv *priv) +{ + struct max_des *des = priv->des; + unsigned int i; + + for (i = 0; i < des->info->num_links; i++) { + struct max_des_link *link = &des->links[i]; + + /* Deleting adapters that haven't been added does no harm. */ + i2c_atr_del_adapter(priv->atr, link->index); + } + + i2c_atr_delete(priv->atr); + priv->atr = NULL; +} + +static int max_des_i2c_atr_init(struct max_des_priv *priv) +{ + struct max_des *des = priv->des; + unsigned int mask = 0; + unsigned int i; + int ret; + + if (!i2c_check_functionality(priv->client->adapter, + I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) + return -ENODEV; + + priv->atr = i2c_atr_new(priv->client->adapter, priv->dev, + &max_des_i2c_atr_ops, des->info->num_links, + I2C_ATR_F_STATIC | I2C_ATR_F_PASSTHROUGH); + if (IS_ERR(priv->atr)) + return PTR_ERR(priv->atr); + + i2c_atr_set_driver_data(priv->atr, priv); + + for (i = 0; i < des->info->num_links; i++) { + struct max_des_link *link = &des->links[i]; + struct i2c_atr_adap_desc desc = { + .chan_id = i, + }; + + if (!link->enabled) + continue; + + ret = i2c_atr_add_adapter(priv->atr, &desc); + if (ret) + goto err_add_adapters; + } + + if (des->ops->select_links) { + mask = max_des_enabled_links_mask(des); + + ret = des->ops->select_links(des, mask); + if (ret) { + dev_warn(priv->dev, "Failed to select links: %d\n", ret); + + goto err_add_adapters; + } + } + + return 0; + +err_add_adapters: + max_des_i2c_atr_deinit(priv); + + return ret; +} + +static void max_des_i2c_mux_deinit(struct max_des_priv *priv) +{ + struct max_des *des = priv->des; + unsigned int i; + + bus_unregister_notifier(&i2c_bus_type, &priv->i2c_nb); + i2c_mux_del_adapters(priv->mux); + + for (i = 0; i < des->info->num_links; i++) + des->links[i].adapter = NULL; +} + +static int max_des_i2c_mux_bus_notifier_call(struct notifier_block *nb, + unsigned long event, void *device) +{ + struct max_des_priv *priv = container_of(nb, struct max_des_priv, i2c_nb); + struct max_des *des = priv->des; + struct device *dev = device; + struct i2c_client *client; + unsigned int i; + + /* + * Ideally, we would want to negotiate the GMSL version on + * BUS_NOTIFY_ADD_DEVICE, but the adapters list is only populated with + * the new adapter after BUS_NOTIFY_ADD_DEVICE is issued. + */ + if (event != BUS_NOTIFY_BIND_DRIVER) + return NOTIFY_DONE; + + client = i2c_verify_client(dev); + if (!client) + return NOTIFY_DONE; + + for (i = 0; i < des->info->num_links; i++) { + if (des->links[i].enabled && + client->adapter == des->links[i].adapter) + break; + } + + /* + * i2c_mux_add_adapter() only publishes the adapter once it has been + * registered, but registering it already probes the children declared + * under it. Such a child binds synchronously, from this same task, and + * lands here before link->adapter has been assigned. Match it against + * the link whose adapter is being added. + */ + if (i == des->info->num_links && priv->pending_task == current) + i = priv->pending_link->index; + + if (i == des->info->num_links) + return NOTIFY_DONE; + + max_des_ser_attach_addr(priv, i, client->addr, client->addr); + + return NOTIFY_DONE; +} + +static int max_des_i2c_mux_select(struct i2c_mux_core *muxc, u32 chan) +{ + struct max_des_priv *priv = i2c_mux_priv(muxc); + struct max_des *des = priv->des; + int ret; + + if (!des->ops->select_links) + return 0; + + /* + * This runs on every i2c transfer. With a single link there is nothing + * to select, so avoid resetting it on every transfer. + * + * Check must be done here due to select_links() use in multiple paths. + */ + if (des->info->num_links == 1) + return 0; + + ret = des->ops->select_links(des, BIT(chan)); + if (ret) + dev_warn(priv->dev, "Failed to select link %u: %d\n", chan, ret); + + return ret; +} + +static int max_des_i2c_mux_init(struct max_des_priv *priv) +{ + struct max_des *des = priv->des; + u32 flags = I2C_MUX_LOCKED; + unsigned int i; + int ret; + + if (des->info->num_links == 1) + flags |= I2C_MUX_GATE; + + priv->mux = i2c_mux_alloc(priv->client->adapter, priv->dev, + des->info->num_links, 0, flags, + max_des_i2c_mux_select, NULL); + if (!priv->mux) + return -ENOMEM; + + priv->mux->priv = priv; + + priv->i2c_nb.notifier_call = max_des_i2c_mux_bus_notifier_call; + ret = bus_register_notifier(&i2c_bus_type, &priv->i2c_nb); + if (ret) + return ret; + + for (i = 0; i < des->info->num_links; i++) { + struct max_des_link *link = &des->links[i]; + + if (!link->enabled) + continue; + + priv->pending_link = link; + priv->pending_task = current; + ret = i2c_mux_add_adapter(priv->mux, 0, i); + priv->pending_task = NULL; + priv->pending_link = NULL; + if (ret) + goto err_add_adapters; + + link->adapter = priv->mux->adapter[priv->mux->num_adapters - 1]; + } + + return 0; + +err_add_adapters: + max_des_i2c_mux_deinit(priv); + + return ret; +} + +static void max_des_i2c_adapter_deinit(struct max_des_priv *priv) +{ + struct max_des *des = priv->des; + + if (des->info->use_atr) + return max_des_i2c_atr_deinit(priv); + else + return max_des_i2c_mux_deinit(priv); +} + +static int max_des_i2c_adapter_init(struct max_des_priv *priv) +{ + struct max_des *des = priv->des; + + if (des->info->use_atr) + return max_des_i2c_atr_init(priv); + else + return max_des_i2c_mux_init(priv); + + return 0; +} + +static int max_des_set_tpg_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *fmt = &format->format; + struct max_des_priv *priv = v4l2_get_subdevdata(sd); + struct max_des *des = priv->des; + const struct max_serdes_tpg_entry *entry; + struct v4l2_fract *in; + + if (format->stream != MAX_SERDES_TPG_STREAM) + return -EINVAL; + + entry = max_des_find_tpg_entry(des, 0, fmt->width, fmt->height, + fmt->code, 0, 0); + if (!entry) + return -EINVAL; + + in = v4l2_subdev_state_get_interval(state, format->pad, format->stream); + if (!in) + return -EINVAL; + + in->numerator = entry->interval.numerator; + in->denominator = entry->interval.denominator; + + return 0; +} + +static int max_des_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct max_des_priv *priv = v4l2_get_subdevdata(sd); + struct max_des *des = priv->des; + struct v4l2_mbus_framefmt *fmt; + int ret; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && des->active) + return -EBUSY; + + /* No transcoding, source and sink formats must match. */ + if (max_des_pad_is_source(des, format->pad)) + return v4l2_subdev_get_fmt(sd, state, format); + + if (max_des_pad_is_tpg(des, format->pad)) { + ret = max_des_set_tpg_fmt(sd, state, format); + if (ret) + return ret; + } + + if (format->format.code == ~0U) + format->format.code = MEDIA_BUS_FMT_UYVY8_1X16; + + fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + return 0; +} + +static int max_des_enum_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_interval_enum *fie) +{ + struct max_des_priv *priv = v4l2_get_subdevdata(sd); + struct max_des *des = priv->des; + const struct max_serdes_tpg_entry *entry; + + if (!max_des_pad_is_tpg(des, fie->pad) || + fie->stream != MAX_SERDES_TPG_STREAM) + return -ENOTTY; + + entry = max_des_find_tpg_entry(des, fie->index, fie->width, fie->height, + fie->code, 0, 0); + if (!entry) + return -EINVAL; + + fie->interval.numerator = entry->interval.numerator; + fie->interval.denominator = entry->interval.denominator; + + return 0; +} + +static int max_des_get_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_interval *fi) +{ + struct max_des_priv *priv = v4l2_get_subdevdata(sd); + struct max_des *des = priv->des; + + if (!max_des_pad_is_tpg(des, fi->pad) || + fi->stream != MAX_SERDES_TPG_STREAM) + return -ENOTTY; + + return v4l2_subdev_get_frame_interval(sd, state, fi); +} + +static int max_des_set_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_interval *fi) +{ + struct max_des_priv *priv = v4l2_get_subdevdata(sd); + struct max_des *des = priv->des; + const struct max_serdes_tpg_entry *entry; + struct v4l2_mbus_framefmt *fmt; + struct v4l2_fract *in; + + if (!max_des_pad_is_tpg(des, fi->pad) || + fi->stream != MAX_SERDES_TPG_STREAM) + return -ENOTTY; + + if (fi->which == V4L2_SUBDEV_FORMAT_ACTIVE && des->active) + return -EBUSY; + + fmt = v4l2_subdev_state_get_format(state, fi->pad, fi->stream); + if (!fmt) + return -EINVAL; + + entry = max_des_find_tpg_entry(des, 0, fmt->width, fmt->height, + fmt->code, fi->interval.numerator, + fi->interval.denominator); + if (!entry) + return -EINVAL; + + in = v4l2_subdev_state_get_interval(state, fi->pad, fi->stream); + if (!in) + return -EINVAL; + + in->numerator = fi->interval.numerator; + in->denominator = fi->interval.denominator; + + return 0; +} + +static int max_des_log_status(struct v4l2_subdev *sd) +{ + struct max_des_priv *priv = v4l2_get_subdevdata(sd); + struct max_des *des = priv->des; + struct v4l2_subdev_state *state; + unsigned int i, j; + int ret; + + state = v4l2_subdev_lock_and_get_active_state(&priv->sd); + + v4l2_info(sd, "active: %u\n", des->active); + v4l2_info(sd, "mode: %s", max_serdes_gmsl_mode_str(des->mode)); + if (des->ops->set_tpg) { + const struct max_serdes_tpg_entry *entry = des->tpg_entry; + + if (entry) { + v4l2_info(sd, "tpg: %ux%u@%u/%u, code: %u, dt: %u, bpp: %u\n", + entry->width, entry->height, + entry->interval.numerator, + entry->interval.denominator, + entry->code, entry->dt, entry->bpp); + } else { + v4l2_info(sd, "tpg: disabled\n"); + } + } + if (des->ops->log_status) { + ret = des->ops->log_status(des); + if (ret) + goto out_unlock; + } + v4l2_info(sd, "\n"); + + for (i = 0; i < des->info->num_links; i++) { + struct max_des_link *link = &des->links[i]; + + v4l2_info(sd, "link: %u\n", link->index); + v4l2_info(sd, "\tenabled: %u\n", link->enabled); + + if (!link->enabled) { + v4l2_info(sd, "\n"); + continue; + } + + v4l2_info(sd, "\tversion: %s\n", max_serdes_gmsl_version_str(link->version)); + v4l2_info(sd, "\tser_xlate: en: %u, src: 0x%02x dst: 0x%02x\n", + link->ser_xlate.en, link->ser_xlate.src, + link->ser_xlate.dst); + v4l2_info(sd, "\n"); + } + + for (i = 0; i < des->info->num_pipes; i++) { + struct max_des_pipe *pipe = &des->pipes[i]; + + v4l2_info(sd, "pipe: %u\n", pipe->index); + v4l2_info(sd, "\tenabled: %u\n", pipe->enabled); + if (pipe->phy_id == des->info->num_phys || + (priv->unused_phy && pipe->phy_id == priv->unused_phy->index)) + v4l2_info(sd, "\tphy_id: invalid\n"); + else + v4l2_info(sd, "\tphy_id: %u\n", pipe->phy_id); + v4l2_info(sd, "\tlink_id: %u\n", pipe->link_id); + if (des->ops->set_pipe_stream_id) + v4l2_info(sd, "\tstream_id: %u\n", pipe->stream_id); + if (des->ops->set_pipe_mode) { + v4l2_info(sd, "\tdbl8: %u\n", pipe->mode.dbl8); + v4l2_info(sd, "\tdbl8mode: %u\n", pipe->mode.dbl8mode); + v4l2_info(sd, "\tdbl10: %u\n", pipe->mode.dbl10); + v4l2_info(sd, "\tdbl10mode: %u\n", pipe->mode.dbl10mode); + v4l2_info(sd, "\tdbl12: %u\n", pipe->mode.dbl12); + } + if (des->ops->set_pipe_remap) { + v4l2_info(sd, "\tremaps: %u\n", pipe->num_remaps); + for (j = 0; j < pipe->num_remaps; j++) { + struct max_des_remap *remap = &pipe->remaps[j]; + + v4l2_info(sd, "\t\tremap: from: vc: %u, dt: 0x%02x\n", + remap->from_vc, remap->from_dt); + v4l2_info(sd, "\t\t to: vc: %u, dt: 0x%02x, phy: %u\n", + remap->to_vc, remap->to_dt, remap->phy); + } + } + if (des->ops->set_pipe_vc_remap) { + v4l2_info(sd, "\tvc_remaps: %u\n", pipe->num_vc_remaps); + for (j = 0; j < pipe->num_vc_remaps; j++) { + v4l2_info(sd, "\t\tvc_remap: src: %u, dst: %u\n", + pipe->vc_remaps[j].src, pipe->vc_remaps[j].dst); + } + } + if (des->ops->log_pipe_status) { + ret = des->ops->log_pipe_status(des, pipe); + if (ret) + goto out_unlock; + } + v4l2_info(sd, "\n"); + } + + for (i = 0; i < des->info->num_phys; i++) { + struct max_des_phy *phy = &des->phys[i]; + + v4l2_info(sd, "phy: %u\n", phy->index); + v4l2_info(sd, "\tenabled: %u\n", phy->enabled); + + if (!phy->enabled) { + v4l2_info(sd, "\n"); + continue; + } + + v4l2_info(sd, "\tlink_frequency: %llu\n", phy->link_frequency); + v4l2_info(sd, "\tnum_data_lanes: %u\n", phy->mipi.num_data_lanes); + v4l2_info(sd, "\tclock_lane: %u\n", phy->mipi.clock_lane); + if (des->ops->set_phy_mode) { + v4l2_info(sd, "\talt_mem_map8: %u\n", phy->mode.alt_mem_map8); + v4l2_info(sd, "\talt2_mem_map8: %u\n", phy->mode.alt2_mem_map8); + v4l2_info(sd, "\talt_mem_map10: %u\n", phy->mode.alt_mem_map10); + v4l2_info(sd, "\talt_mem_map12: %u\n", phy->mode.alt_mem_map12); + } + if (des->ops->log_phy_status) { + ret = des->ops->log_phy_status(des, phy); + if (ret) + goto out_unlock; + } + v4l2_info(sd, "\n"); + } + + ret = 0; + +out_unlock: + v4l2_subdev_unlock_state(state); + + return ret; +} + +static int max_des_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct max_des_priv *priv = ctrl_to_priv(ctrl->handler); + struct max_des *des = priv->des; + + switch (ctrl->id) { + case V4L2_CID_TEST_PATTERN: + des->tpg_pattern = ctrl->val; + return 0; + } + + return -EINVAL; +} + +static int max_des_get_frame_desc_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_mbus_frame_desc *fd, + unsigned int pad) +{ + struct max_des_remap_context context = { 0 }; + struct max_des_priv *priv = sd_to_priv(sd); + struct v4l2_subdev_route *route; + int ret; + + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2; + fd->num_entries = 0; + + ret = max_des_populate_remap_context(priv, &context, state); + if (ret) + return ret; + + for_each_active_route(&state->routing, route) { + struct max_des_route_hw hw; + unsigned int dst_vc_id; + + if (pad != route->source_pad) + continue; + + ret = max_des_route_to_hw(priv, state, route, &hw); + if (ret) + return ret; + + ret = max_des_get_src_dst_vc_id(&context, hw.pipe->index, hw.phy->index, + hw.entry.bus.csi2.vc, &dst_vc_id); + if (ret) + return ret; + + hw.entry.bus.csi2.vc = dst_vc_id; + hw.entry.stream = route->source_stream; + + fd->entry[fd->num_entries++] = hw.entry; + } + + return 0; +} + +static int max_des_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_frame_desc *fd) +{ + struct max_des_priv *priv = sd_to_priv(sd); + struct v4l2_subdev_state *state; + int ret; + + state = v4l2_subdev_lock_and_get_active_state(&priv->sd); + + ret = max_des_get_frame_desc_state(sd, state, fd, pad); + + v4l2_subdev_unlock_state(state); + + return ret; +} + +static int max_des_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_config *cfg) +{ + struct max_des_priv *priv = sd_to_priv(sd); + struct max_des *des = priv->des; + struct max_des_phy *phy; + + phy = max_des_pad_to_phy(des, pad); + if (!phy) + return -EINVAL; + + cfg->type = phy->bus_type; + cfg->bus.mipi_csi2 = phy->mipi; + cfg->link_freq = phy->link_frequency; + + return 0; +} + +static int max_des_set_tpg_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_krouting *routing) +{ + struct max_des_priv *priv = sd_to_priv(sd); + struct max_des *des = priv->des; + const struct max_serdes_tpg_entry *entry; + struct v4l2_mbus_framefmt fmt = { 0 }; + int ret; + + ret = max_serdes_validate_tpg_routing(routing); + if (ret) + return ret; + + entry = &des->info->tpg_entries.entries[0]; + + fmt.width = entry->width; + fmt.height = entry->height; + fmt.code = entry->code; + + return v4l2_subdev_set_routing_with_fmt(sd, state, routing, &fmt); +} + +static int __max_des_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_krouting *routing) +{ + struct max_des_priv *priv = sd_to_priv(sd); + struct max_des *des = priv->des; + struct v4l2_subdev_route *route; + bool is_tpg = false; + int ret; + + ret = v4l2_subdev_routing_validate(sd, routing, + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1 | + V4L2_SUBDEV_ROUTING_NO_SINK_STREAM_MIX); + if (ret) + return ret; + + for_each_active_route(routing, route) { + if (max_des_pad_is_tpg(des, route->sink_pad)) { + is_tpg = true; + break; + } + } + + if (is_tpg) + return max_des_set_tpg_routing(sd, state, routing); + + static const struct v4l2_mbus_framefmt format = { + .code = MEDIA_BUS_FMT_Y8_1X8, + .field = V4L2_FIELD_NONE, + .width = 640, + .height = 480, + }; + + return v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format); +} + +static int max_des_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + struct max_des_priv *priv = sd_to_priv(sd); + struct max_des *des = priv->des; + + if (which == V4L2_SUBDEV_FORMAT_ACTIVE && des->active) + return -EBUSY; + + return __max_des_set_routing(sd, state, routing); +} + +static int max_des_update_link(struct max_des_priv *priv, + struct max_des_remap_context *context, + struct max_des_link *link, + struct v4l2_subdev_state *state, + u64 *streams_masks) +{ + struct max_des *des = priv->des; + struct max_des_pipe *pipe; + int ret; + + pipe = max_des_find_link_pipe(des, link); + if (!pipe) + return -ENOENT; + + ret = max_des_update_pipe(priv, context, pipe, state, streams_masks); + if (ret) + return ret; + + return 0; +} + +static int max_des_update_tpg(struct max_des_priv *priv, + struct v4l2_subdev_state *state, + u64 *streams_masks) +{ + const struct max_serdes_tpg_entry *entry = NULL; + struct max_des *des = priv->des; + struct v4l2_subdev_route *route; + int ret; + + for_each_active_route(&state->routing, route) { + struct max_des_route_hw hw; + + if (!(BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad])) + continue; + + ret = max_des_route_to_hw(priv, state, route, &hw); + if (ret) + return ret; + + if (!hw.is_tpg) + continue; + + entry = max_des_find_state_tpg_entry(des, state, route->sink_pad); + break; + } + + if (entry == des->tpg_entry) + return 0; + + ret = des->ops->set_tpg(des, entry); + if (ret) + return ret; + + des->tpg_entry = entry; + + return 0; +} + +static int max_des_update_active(struct max_des_priv *priv, u64 *streams_masks, + bool expected_active) +{ + struct max_des *des = priv->des; + bool active = false; + unsigned int i; + int ret; + + for (i = 0; i < des->info->num_phys; i++) { + struct max_des_phy *phy = &des->phys[i]; + u32 pad = max_des_phy_to_pad(des, phy); + + if (streams_masks[pad]) { + active = true; + break; + } + } + + if (active != expected_active || des->active == active) + return 0; + + if (des->ops->set_enable) { + ret = des->ops->set_enable(des, active); + if (ret) + return ret; + } + + des->active = active; + + return 0; +} + +static int max_des_update_links(struct max_des_priv *priv, + struct max_des_remap_context *context, + struct v4l2_subdev_state *state, + u64 *streams_masks) +{ + struct max_des *des = priv->des; + unsigned int failed_update_link_id = des->info->num_links; + unsigned int i; + int ret; + + for (i = 0; i < des->info->num_links; i++) { + struct max_des_link *link = &des->links[i]; + + ret = max_des_update_link(priv, context, link, state, + streams_masks); + if (ret) { + failed_update_link_id = i; + goto err; + } + } + + return 0; + +err: + for (i = 0; i < failed_update_link_id; i++) { + struct max_des_link *link = &des->links[i]; + + max_des_update_link(priv, context, link, state, + priv->streams_masks); + } + + return ret; +} + +static int max_des_enable_disable_streams(struct max_des_priv *priv, + struct v4l2_subdev_state *state, + u32 pad, u64 updated_streams_mask, + bool enable) +{ + struct max_des *des = priv->des; + + return max_serdes_xlate_enable_disable_streams(priv->sources, 0, state, + pad, updated_streams_mask, 0, + des->info->num_links, enable); +} + +static int max_des_update_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 updated_streams_mask, bool enable) +{ + struct max_des_priv *priv = v4l2_get_subdevdata(sd); + struct max_des_remap_context context = { 0 }; + struct max_des_mode_context mode_context = { 0 }; + struct max_des *des = priv->des; + unsigned int num_pads = max_des_num_pads(des); + u64 *streams_masks; + int ret; + + ret = max_des_populate_remap_context(priv, &context, state); + if (ret) + return ret; + + ret = max_des_populate_mode_context(priv, &mode_context, state, context.mode); + if (ret) + return ret; + + ret = max_serdes_get_streams_masks(priv->dev, state, pad, updated_streams_mask, + num_pads, priv->streams_masks, &streams_masks, + enable); + if (ret) + return ret; + + ret = max_des_set_pipes_phy(priv, &context); + if (ret) + goto err_free_streams_masks; + + ret = max_des_set_tunnel(priv, &context); + if (ret) + goto err_free_streams_masks; + + ret = max_des_set_modes(priv, &mode_context); + if (ret) + goto err_free_streams_masks; + + ret = max_des_set_vc_remaps(priv, &context, state, streams_masks); + if (ret) + goto err_free_streams_masks; + + ret = max_des_set_pipes_stream_id(priv); + if (ret) + goto err_free_streams_masks; + + if (!enable) { + ret = max_des_enable_disable_streams(priv, state, pad, + updated_streams_mask, enable); + if (ret) + goto err_free_streams_masks; + } + + ret = max_des_update_active(priv, streams_masks, false); + if (ret) + goto err_revert_streams_disable; + + ret = max_des_update_links(priv, &context, state, streams_masks); + if (ret) + goto err_revert_active_disable; + + ret = max_des_update_tpg(priv, state, streams_masks); + if (ret) + goto err_revert_links_update; + + ret = max_des_update_active(priv, streams_masks, true); + if (ret) + goto err_revert_tpg_update; + + if (enable) { + ret = max_des_enable_disable_streams(priv, state, pad, + updated_streams_mask, enable); + if (ret) + goto err_revert_active_enable; + } + + devm_kfree(priv->dev, priv->streams_masks); + priv->streams_masks = streams_masks; + + return 0; + +err_revert_active_enable: + max_des_update_active(priv, priv->streams_masks, false); + +err_revert_tpg_update: + max_des_update_tpg(priv, state, priv->streams_masks); + +err_revert_links_update: + max_des_update_links(priv, &context, state, priv->streams_masks); + +err_revert_active_disable: + max_des_update_active(priv, priv->streams_masks, true); + +err_revert_streams_disable: + if (!enable) + max_des_enable_disable_streams(priv, state, pad, + updated_streams_mask, !enable); + +err_free_streams_masks: + devm_kfree(priv->dev, streams_masks); + + return ret; +} + +static int max_des_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + return max_des_update_streams(sd, state, pad, streams_mask, true); +} + +static int max_des_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + return max_des_update_streams(sd, state, pad, streams_mask, false); +} + +static int max_des_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_route routes[MAX_DES_NUM_LINKS] = { 0 }; + struct v4l2_subdev_krouting routing = { + .routes = routes, + }; + struct max_des_priv *priv = v4l2_get_subdevdata(sd); + struct max_des *des = priv->des; + struct max_des_phy *phy = NULL; + unsigned int stream = 0; + unsigned int i; + + for (i = 0; i < des->info->num_phys; i++) { + if (des->phys[i].enabled) { + phy = &des->phys[i]; + break; + } + } + + if (!phy) + return 0; + + for (i = 0; i < des->info->num_links; i++) { + struct max_des_link *link = &des->links[i]; + + if (!link->enabled) + continue; + + routing.routes[routing.num_routes++] = (struct v4l2_subdev_route) { + .sink_pad = max_des_link_to_pad(des, link), + .sink_stream = 0, + .source_pad = max_des_phy_to_pad(des, phy), + .source_stream = stream, + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }; + stream++; + + /* + * The Streams API is an experimental feature. + * If multiple routes are provided here, userspace will not be + * able to configure them unless the Streams API is enabled. + * Provide a single route until it is enabled. + */ + break; + } + + return __max_des_set_routing(sd, state, &routing); +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int max_des_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct max_des_priv *priv = v4l2_get_subdevdata(sd); + struct max_des *des = priv->des; + unsigned int val; + int ret; + + ret = des->ops->reg_read(des, reg->reg, &val); + if (ret) + return ret; + + reg->val = val; + reg->size = 1; + + return 0; +} + +static int max_des_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct max_des_priv *priv = v4l2_get_subdevdata(sd); + struct max_des *des = priv->des; + + return des->ops->reg_write(des, reg->reg, reg->val); +} +#endif + +static const struct v4l2_subdev_core_ops max_des_core_ops = { + .log_status = max_des_log_status, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = max_des_g_register, + .s_register = max_des_s_register, +#endif +}; + +static const struct v4l2_ctrl_ops max_des_ctrl_ops = { + .s_ctrl = max_des_s_ctrl, +}; + +static const struct v4l2_subdev_pad_ops max_des_pad_ops = { + .enable_streams = max_des_enable_streams, + .disable_streams = max_des_disable_streams, + + .set_routing = max_des_set_routing, + .get_frame_desc = max_des_get_frame_desc, + + .get_mbus_config = max_des_get_mbus_config, + + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = max_des_set_fmt, + + .enum_frame_interval = max_des_enum_frame_interval, + .get_frame_interval = max_des_get_frame_interval, + .set_frame_interval = max_des_set_frame_interval, +}; + +static const struct v4l2_subdev_ops max_des_subdev_ops = { + .core = &max_des_core_ops, + .pad = &max_des_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops max_des_internal_ops = { + .init_state = &max_des_init_state, +}; + +static const struct media_entity_operations max_des_media_ops = { + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1, + .has_pad_interdep = v4l2_subdev_has_pad_interdep, + .link_validate = v4l2_subdev_link_validate, +}; + +static int max_des_notify_bound(struct v4l2_async_notifier *nf, + struct v4l2_subdev *subdev, + struct v4l2_async_connection *base_asc) +{ + struct max_des_priv *priv = nf_to_priv(nf); + struct max_serdes_asc *asc = asc_to_max(base_asc); + struct max_serdes_source *source = asc->source; + struct max_des *des = priv->des; + struct max_des_link *link = &des->links[source->index]; + u32 pad = max_des_link_to_pad(des, link); + int ret; + + ret = media_entity_get_fwnode_pad(&subdev->entity, + source->ep_fwnode, + MEDIA_PAD_FL_SOURCE); + if (ret < 0) { + dev_err(priv->dev, "Failed to find pad for %s\n", subdev->name); + return ret; + } + + source->sd = subdev; + source->pad = ret; + + ret = media_create_pad_link(&source->sd->entity, source->pad, + &priv->sd.entity, pad, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_err(priv->dev, "Unable to link %s:%u -> %s:%u\n", + source->sd->name, source->pad, priv->sd.name, pad); + source->sd = NULL; + return ret; + } + + return 0; +} + +static void max_des_notify_unbind(struct v4l2_async_notifier *nf, + struct v4l2_subdev *subdev, + struct v4l2_async_connection *base_asc) +{ + struct max_serdes_asc *asc = asc_to_max(base_asc); + struct max_serdes_source *source = asc->source; + + source->sd = NULL; +} + +static const struct v4l2_async_notifier_operations max_des_notify_ops = { + .bound = max_des_notify_bound, + .unbind = max_des_notify_unbind, +}; + +static int max_des_v4l2_notifier_register(struct max_des_priv *priv) +{ + struct max_des *des = priv->des; + unsigned int i; + int ret; + + v4l2_async_subdev_nf_init(&priv->nf, &priv->sd); + + for (i = 0; i < des->info->num_links; i++) { + struct max_des_link *link = &des->links[i]; + struct max_serdes_source *source; + struct max_serdes_asc *asc; + + if (!link->enabled) + continue; + + source = max_des_get_link_source(priv, link); + if (!source->ep_fwnode) + continue; + + asc = v4l2_async_nf_add_fwnode(&priv->nf, source->ep_fwnode, + struct max_serdes_asc); + if (IS_ERR(asc)) { + dev_err(priv->dev, + "Failed to add subdev for source %u: %pe", i, + asc); + + v4l2_async_nf_cleanup(&priv->nf); + + return PTR_ERR(asc); + } + + asc->source = source; + } + + priv->nf.ops = &max_des_notify_ops; + + ret = v4l2_async_nf_register(&priv->nf); + if (ret) { + dev_err(priv->dev, "Failed to register subdev notifier"); + v4l2_async_nf_cleanup(&priv->nf); + return ret; + } + + return 0; +} + +static void max_des_v4l2_notifier_unregister(struct max_des_priv *priv) +{ + v4l2_async_nf_unregister(&priv->nf); + v4l2_async_nf_cleanup(&priv->nf); +} + +static int max_des_v4l2_register(struct max_des_priv *priv) +{ + struct v4l2_subdev *sd = &priv->sd; + struct max_des *des = priv->des; + void *data = i2c_get_clientdata(priv->client); + unsigned int num_pads = max_des_num_pads(des); + unsigned int i; + int ret; + + v4l2_i2c_subdev_init(sd, priv->client, &max_des_subdev_ops); + i2c_set_clientdata(priv->client, data); + sd->internal_ops = &max_des_internal_ops; + sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + sd->entity.ops = &max_des_media_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS; + + for (i = 0; i < num_pads; i++) { + if (max_des_pad_is_sink(des, i)) + priv->pads[i].flags = MEDIA_PAD_FL_SINK; + else if (max_des_pad_is_source(des, i)) + priv->pads[i].flags = MEDIA_PAD_FL_SOURCE; + else if (max_des_pad_is_tpg(des, i)) + priv->pads[i].flags = MEDIA_PAD_FL_SINK | + MEDIA_PAD_FL_INTERNAL; + else + return -EINVAL; + } + + v4l2_set_subdevdata(sd, priv); + + if (des->info->tpg_patterns) { + v4l2_ctrl_handler_init(&priv->ctrl_handler, 1); + priv->sd.ctrl_handler = &priv->ctrl_handler; + + v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler, + &max_des_ctrl_ops, + V4L2_CID_TEST_PATTERN, + MAX_SERDES_TPG_PATTERN_MAX, + ~des->info->tpg_patterns, + __ffs(des->info->tpg_patterns), + max_serdes_tpg_patterns); + if (priv->ctrl_handler.error) { + ret = priv->ctrl_handler.error; + goto err_free_ctrl; + } + } + + ret = media_entity_pads_init(&sd->entity, num_pads, priv->pads); + if (ret) + goto err_free_ctrl; + + ret = max_des_v4l2_notifier_register(priv); + if (ret) + goto err_media_entity_cleanup; + + ret = v4l2_subdev_init_finalize(sd); + if (ret) + goto err_nf_cleanup; + + ret = v4l2_async_register_subdev(sd); + if (ret) + goto err_sd_cleanup; + + return 0; + +err_sd_cleanup: + v4l2_subdev_cleanup(sd); +err_nf_cleanup: + max_des_v4l2_notifier_unregister(priv); +err_media_entity_cleanup: + media_entity_cleanup(&sd->entity); +err_free_ctrl: + v4l2_ctrl_handler_free(&priv->ctrl_handler); + + return ret; +} + +static void max_des_put_source_ep_fwnodes(struct max_des_priv *priv) +{ + struct max_des *des = priv->des; + unsigned int i; + + for (i = 0; i < des->info->num_links; i++) { + fwnode_handle_put(priv->sources[i].ep_fwnode); + priv->sources[i].ep_fwnode = NULL; + } +} + +static void max_des_v4l2_unregister(struct max_des_priv *priv) +{ + struct v4l2_subdev *sd = &priv->sd; + + v4l2_async_unregister_subdev(sd); + v4l2_subdev_cleanup(sd); + max_des_v4l2_notifier_unregister(priv); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(&priv->ctrl_handler); +} + +static int max_des_update_pocs(struct max_des_priv *priv, bool enable) +{ + struct max_des *des = priv->des; + unsigned int i; + int err = 0; + int ret; + + for (i = 0; i < des->info->num_links; i++) { + struct max_des_link *link = &des->links[i]; + + if (!link->enabled) + continue; + + if (!priv->pocs[i]) + continue; + + if (enable) + ret = regulator_enable(priv->pocs[i]); + else + ret = regulator_disable(priv->pocs[i]); + + if (ret) { + dev_err(priv->dev, + "Failed to set POC supply to %u: %u\n", + enable, ret); + if (!enable) { + err = err ?: ret; + continue; + } + goto err_rollback; + } + } + + return err; + +err_rollback: + while (i--) { + struct max_des_link *link = &des->links[i]; + + if (!link->enabled) + continue; + + if (!priv->pocs[i]) + continue; + + regulator_disable(priv->pocs[i]); + } + + return ret; +} + +static int max_des_parse_sink_dt_endpoint(struct max_des_priv *priv, + struct max_des_link *link, + struct max_serdes_source *source, + struct fwnode_handle *fwnode) +{ + struct max_des *des = priv->des; + u32 pad = max_des_link_to_pad(des, link); + unsigned int index = link->index; + struct fwnode_handle *ep; + char poc_name[10]; + int ret; + + ep = fwnode_graph_get_endpoint_by_id(fwnode, pad, 0, 0); + if (!ep) + return 0; + + source->ep_fwnode = fwnode_graph_get_remote_endpoint(ep); + fwnode_handle_put(ep); + if (!source->ep_fwnode) { + dev_err(priv->dev, + "Failed to get remote endpoint on port %u\n", pad); + return -ENODEV; + } + + snprintf(poc_name, sizeof(poc_name), "port%u-poc", index); + priv->pocs[index] = devm_regulator_get_optional(priv->dev, poc_name); + if (IS_ERR(priv->pocs[index])) { + ret = PTR_ERR(priv->pocs[index]); + if (ret != -ENODEV) { + dev_err(priv->dev, + "Failed to get POC supply on port %u: %d\n", + index, ret); + goto err_put_source_ep_fwnode; + } + + priv->pocs[index] = NULL; + } + + link->enabled = true; + + return 0; + +err_put_source_ep_fwnode: + fwnode_handle_put(source->ep_fwnode); + source->ep_fwnode = NULL; + + return ret; +} + +static int max_des_parse_src_dt_endpoint(struct max_des_priv *priv, + struct max_des_phy *phy, + struct fwnode_handle *fwnode) +{ + struct max_des *des = priv->des; + u32 pad = max_des_phy_to_pad(des, phy); + struct v4l2_fwnode_endpoint v4l2_ep = { .bus_type = V4L2_MBUS_UNKNOWN }; + struct v4l2_mbus_config_mipi_csi2 *mipi = &v4l2_ep.bus.mipi_csi2; + enum v4l2_mbus_type bus_type; + struct fwnode_handle *ep; + u64 link_frequency; + unsigned int i; + int ret; + + ep = fwnode_graph_get_endpoint_by_id(fwnode, pad, 0, 0); + if (!ep) + return 0; + + ret = v4l2_fwnode_endpoint_alloc_parse(ep, &v4l2_ep); + fwnode_handle_put(ep); + if (ret) { + dev_err(priv->dev, "Could not parse endpoint on port %u\n", pad); + return ret; + } + + bus_type = v4l2_ep.bus_type; + if (bus_type != V4L2_MBUS_CSI2_DPHY && + bus_type != V4L2_MBUS_CSI2_CPHY) { + v4l2_fwnode_endpoint_free(&v4l2_ep); + dev_err(priv->dev, "Unsupported bus-type %u on port %u\n", + bus_type, pad); + return -EINVAL; + } + + if (v4l2_ep.nr_of_link_frequencies == 0) { + link_frequency = MAX_DES_LINK_FREQUENCY_DEFAULT; + } else if (v4l2_ep.nr_of_link_frequencies == 1) { + link_frequency = v4l2_ep.link_frequencies[0]; + } else { + dev_err(priv->dev, "Invalid link frequencies %u on port %u\n", + v4l2_ep.nr_of_link_frequencies, pad); + ret = -EINVAL; + } + + v4l2_fwnode_endpoint_free(&v4l2_ep); + + if (ret) + return ret; + + if (link_frequency < MAX_DES_LINK_FREQUENCY_MIN || + link_frequency > MAX_DES_LINK_FREQUENCY_MAX) { + dev_err(priv->dev, "Invalid link frequency %llu on port %u\n", + link_frequency, pad); + return -EINVAL; + } + + if (!mipi->num_data_lanes) { + dev_err(priv->dev, "No data lanes on port %u\n", pad); + return -EINVAL; + } + + for (i = 0; i < mipi->num_data_lanes; i++) { + if (mipi->data_lanes[i] > mipi->num_data_lanes) { + dev_err(priv->dev, "Invalid data lane %u on port %u\n", + mipi->data_lanes[i], pad); + return -EINVAL; + } + } + + phy->bus_type = bus_type; + phy->mipi = *mipi; + phy->link_frequency = link_frequency; + phy->enabled = true; + + return 0; +} + +int max_des_phy_hw_data_lanes(struct max_des *des, struct max_des_phy *phy) +{ + const struct max_serdes_phys_configs *configs = &des->info->phys_configs; + const struct max_serdes_phys_config *config = + &configs->configs[des->phys_config]; + + return config->lanes[phy->index]; +} +EXPORT_SYMBOL_NS_GPL(max_des_phy_hw_data_lanes, "MAX_SERDES"); + +static int max_des_find_phys_config(struct max_des_priv *priv) +{ + struct max_des *des = priv->des; + const struct max_serdes_phys_configs *configs = &des->info->phys_configs; + struct max_des_phy *phy; + unsigned int i, j; + + if (!configs->num_configs) + return 0; + + for (i = 0; i < configs->num_configs; i++) { + const struct max_serdes_phys_config *config = &configs->configs[i]; + bool matching = true; + + for (j = 0; j < des->info->num_phys; j++) { + phy = &des->phys[j]; + + if (!phy->enabled) + continue; + + if (phy->mipi.num_data_lanes <= config->lanes[j] && + phy->mipi.clock_lane == config->clock_lane[j]) + continue; + + matching = false; + + break; + } + + if (matching) + break; + } + + if (i == configs->num_configs) { + dev_err(priv->dev, "Invalid lane configuration\n"); + return -EINVAL; + } + + des->phys_config = i; + + return 0; +} + +static int max_des_parse_dt(struct max_des_priv *priv) +{ + struct fwnode_handle *fwnode = dev_fwnode(priv->dev); + struct max_des *des = priv->des; + struct max_des_link *link; + struct max_des_pipe *pipe; + struct max_des_phy *phy; + unsigned int i; + int ret; + + for (i = 0; i < des->info->num_phys; i++) { + phy = &des->phys[i]; + phy->index = i; + + ret = max_des_parse_src_dt_endpoint(priv, phy, fwnode); + if (ret) + return ret; + } + + ret = max_des_find_phys_config(priv); + if (ret) + return ret; + + /* + * Find an unused PHY to send unmapped data to. + * + * Skip PHYs that have no hardware data lanes of their own: those are + * absorbed into another PHY by the selected configuration, so sending + * unmapped data to them would corrupt the PHY they are merged with. + */ + for (i = 0; i < des->info->num_phys; i++) { + phy = &des->phys[i]; + + if (!phy->enabled && max_des_phy_hw_data_lanes(des, phy)) { + priv->unused_phy = phy; + break; + } + } + + for (i = 0; i < des->info->num_pipes; i++) { + pipe = &des->pipes[i]; + pipe->index = i; + + /* + * Serializers can send data on different stream ids over the + * same link, and some deserializers support stream id autoselect + * allowing them to receive data from all stream ids. + * Deserializers that support that feature should enable it. + * Deserializers that support per-link stream ids do not need + * to assign unique stream ids to each serializer. + */ + if (des->info->needs_unique_stream_id) + pipe->stream_id = i; + else + pipe->stream_id = 0; + + /* + * We already checked that num_pipes >= num_links. + * Set up pipe to receive data from the link with the same index. + * This is already the default for most chips, and some of them + * don't even support receiving pipe data from a different link. + */ + pipe->link_id = i % des->info->num_links; + } + + for (i = 0; i < des->info->num_links; i++) { + link = &des->links[i]; + link->index = i; + } + + for (i = 0; i < des->info->num_links; i++) { + struct max_des_link *link = &des->links[i]; + struct max_serdes_source *source; + + source = max_des_get_link_source(priv, link); + source->index = i; + + ret = max_des_parse_sink_dt_endpoint(priv, link, source, fwnode); + if (ret) + return ret; + } + + return 0; +} + +static int max_des_allocate(struct max_des_priv *priv) +{ + struct max_des *des = priv->des; + unsigned int num_pads = max_des_num_pads(des); + + des->phys = devm_kcalloc(priv->dev, des->info->num_phys, + sizeof(*des->phys), GFP_KERNEL); + if (!des->phys) + return -ENOMEM; + + des->pipes = devm_kcalloc(priv->dev, des->info->num_pipes, + sizeof(*des->pipes), GFP_KERNEL); + if (!des->pipes) + return -ENOMEM; + + des->links = devm_kcalloc(priv->dev, des->info->num_links, + sizeof(*des->links), GFP_KERNEL); + if (!des->links) + return -ENOMEM; + + priv->sources = devm_kcalloc(priv->dev, des->info->num_links, + sizeof(*priv->sources), GFP_KERNEL); + if (!priv->sources) + return -ENOMEM; + + priv->pocs = devm_kcalloc(priv->dev, des->info->num_links, + sizeof(*priv->pocs), GFP_KERNEL); + if (!priv->pocs) + return -ENOMEM; + + priv->pads = devm_kcalloc(priv->dev, num_pads, + sizeof(*priv->pads), GFP_KERNEL); + if (!priv->pads) + return -ENOMEM; + + priv->streams_masks = devm_kcalloc(priv->dev, num_pads, + sizeof(*priv->streams_masks), + GFP_KERNEL); + if (!priv->streams_masks) + return -ENOMEM; + + return 0; +} + +int max_des_probe(struct i2c_client *client, struct max_des *des) +{ + struct device *dev = &client->dev; + struct max_des_priv *priv; + int ret; + + if (des->info->num_phys > MAX_DES_NUM_PHYS) + return -E2BIG; + + if (des->info->num_pipes > MAX_DES_NUM_PIPES) + return -E2BIG; + + if (des->info->num_links > MAX_DES_NUM_LINKS) + return -E2BIG; + + if (des->info->num_links > des->info->num_pipes) + return -E2BIG; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + if (!des->info->versions) { + dev_err(dev, "No GMSL version declared\n"); + return -EINVAL; + } + + if (des->ops->set_link_version && !des->ops->select_links) { + dev_err(dev, + "Cannot implement .set_link_version() without .select_links()\n"); + return -EINVAL; + } + + if (hweight_long(des->info->versions) > 1 && + !des->ops->set_link_version) { + dev_err(dev, "Multiple versions without .set_link_version()\n"); + return -EINVAL; + } + + priv->client = client; + priv->dev = dev; + priv->des = des; + des->priv = priv; + + ret = max_des_allocate(priv); + if (ret) + return ret; + + ret = max_des_parse_dt(priv); + if (ret) + goto err_put_source_ep_fwnodes; + + ret = max_des_init(priv); + if (ret) + goto err_put_source_ep_fwnodes; + + ret = max_des_update_pocs(priv, true); + if (ret) + goto err_put_source_ep_fwnodes; + + ret = max_des_i2c_adapter_init(priv); + if (ret) + goto err_disable_pocs; + + ret = max_des_v4l2_register(priv); + if (ret) + goto err_i2c_adapter_deinit; + + return 0; + +err_i2c_adapter_deinit: + max_des_i2c_adapter_deinit(priv); + +err_disable_pocs: + max_des_update_pocs(priv, false); + +err_put_source_ep_fwnodes: + max_des_put_source_ep_fwnodes(priv); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(max_des_probe, "MAX_SERDES"); + +int max_des_remove(struct max_des *des) +{ + struct max_des_priv *priv = des->priv; + + max_des_v4l2_unregister(priv); + + max_des_i2c_adapter_deinit(priv); + + max_des_update_pocs(priv, false); + + max_des_put_source_ep_fwnodes(priv); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(max_des_remove, "MAX_SERDES"); + +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("I2C_ATR"); diff --git a/drivers/media/i2c/maxim-serdes/max_des.h b/drivers/media/i2c/maxim-serdes/max_des.h new file mode 100644 index 000000000000..3ad8246b1981 --- /dev/null +++ b/drivers/media/i2c/maxim-serdes/max_des.h @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2025 Analog Devices Inc. + */ + +#ifndef MAX_DES_H +#define MAX_DES_H + +#include + +#include "max_serdes.h" + +#define MAX_DES_DT_VC(dt, vc) (((vc) & 0x3) << 6 | ((dt) & 0x3f)) + +struct max_des_remap { + u8 from_dt; + u8 from_vc; + u8 to_dt; + u8 to_vc; + u8 phy; +}; + +struct max_des_link { + unsigned int index; + bool enabled; + enum max_serdes_gmsl_version version; + struct max_serdes_i2c_xlate ser_xlate; + struct i2c_adapter *adapter; +}; + +struct max_des_pipe_mode { + bool dbl8; + bool dbl10; + bool dbl12; + bool dbl8mode; + bool dbl10mode; +}; + +struct max_des_pipe { + unsigned int index; + unsigned int stream_id; + unsigned int link_id; + unsigned int phy_id; + struct max_des_remap *remaps; + unsigned int num_remaps; + struct max_serdes_vc_remap *vc_remaps; + unsigned int num_vc_remaps; + struct max_des_pipe_mode mode; + bool enabled; +}; + +struct max_des_phy_mode { + bool alt_mem_map8; + bool alt2_mem_map8; + bool alt_mem_map10; + bool alt_mem_map12; +}; + +struct max_des_phy { + unsigned int index; + u64 link_frequency; + struct v4l2_mbus_config_mipi_csi2 mipi; + enum v4l2_mbus_type bus_type; + struct max_des_phy_mode mode; + bool enabled; +}; + +struct max_des; + +struct max_des_info { + unsigned int num_phys; + unsigned int num_pipes; + unsigned int num_links; + unsigned int num_remaps_per_pipe; + unsigned int versions; + unsigned int modes; + bool fix_tx_ids; + bool use_atr; + bool needs_single_link_version; + bool needs_unique_stream_id; + + struct max_serdes_phys_configs phys_configs; + struct max_serdes_tpg_entries tpg_entries; + enum max_serdes_gmsl_mode tpg_mode; + unsigned int tpg_patterns; +}; + +struct max_des_ops { +#ifdef CONFIG_VIDEO_ADV_DEBUG + int (*reg_read)(struct max_des *des, unsigned int reg, unsigned int *val); + int (*reg_write)(struct max_des *des, unsigned int reg, unsigned int val); +#endif + int (*log_status)(struct max_des *des); + int (*log_pipe_status)(struct max_des *des, struct max_des_pipe *pipe); + int (*log_phy_status)(struct max_des *des, struct max_des_phy *phy); + int (*set_enable)(struct max_des *des, bool enable); + int (*set_tpg)(struct max_des *des, const struct max_serdes_tpg_entry *entry); + int (*init)(struct max_des *des); + int (*init_phy)(struct max_des *des, struct max_des_phy *phy); + int (*set_phy_mode)(struct max_des *des, struct max_des_phy *phy, + struct max_des_phy_mode *mode); + int (*set_phy_enable)(struct max_des *des, struct max_des_phy *phy, + bool active); + int (*set_pipe_stream_id)(struct max_des *des, struct max_des_pipe *pipe, + unsigned int stream_id); + int (*set_pipe_link)(struct max_des *des, struct max_des_pipe *pipe, + struct max_des_link *link); + int (*set_pipe_phy)(struct max_des *des, struct max_des_pipe *pipe, + struct max_des_phy *phy); + int (*set_pipe_tunnel_phy)(struct max_des *des, struct max_des_pipe *pipe, + struct max_des_phy *phy); + int (*set_pipe_enable)(struct max_des *des, struct max_des_pipe *pipe, + bool enable); + int (*set_pipe_remap)(struct max_des *des, struct max_des_pipe *pipe, + unsigned int i, struct max_des_remap *remap); + int (*set_pipe_remaps_enable)(struct max_des *des, struct max_des_pipe *pipe, + unsigned int mask); + int (*set_pipe_vc_remap)(struct max_des *des, struct max_des_pipe *pipe, + unsigned int i, struct max_serdes_vc_remap *vc_remap); + int (*set_pipe_vc_remaps_enable)(struct max_des *des, struct max_des_pipe *pipe, + unsigned int mask); + int (*set_pipe_mode)(struct max_des *des, struct max_des_pipe *pipe, + struct max_des_pipe_mode *mode); + int (*set_pipe_tunnel_enable)(struct max_des *des, struct max_des_pipe *pipe, + bool enable); + int (*init_link)(struct max_des *des, struct max_des_link *link); + int (*select_links)(struct max_des *des, unsigned int mask); + int (*set_link_version)(struct max_des *des, struct max_des_link *link, + enum max_serdes_gmsl_version version); +}; + +struct max_des_priv; + +struct max_des { + struct max_des_priv *priv; + + const struct max_des_info *info; + const struct max_des_ops *ops; + + struct max_des_phy *phys; + struct max_des_pipe *pipes; + struct max_des_link *links; + const struct max_serdes_tpg_entry *tpg_entry; + enum max_serdes_tpg_pattern tpg_pattern; + + unsigned int phys_config; + enum max_serdes_gmsl_mode mode; + bool active; +}; + +int max_des_probe(struct i2c_client *client, struct max_des *des); + +int max_des_remove(struct max_des *des); + +int max_des_phy_hw_data_lanes(struct max_des *des, struct max_des_phy *phy); + +#endif // MAX_DES_H diff --git a/drivers/media/i2c/maxim-serdes/max_ser.c b/drivers/media/i2c/maxim-serdes/max_ser.c new file mode 100644 index 000000000000..d6a17f987e46 --- /dev/null +++ b/drivers/media/i2c/maxim-serdes/max_ser.c @@ -0,0 +1,2218 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Maxim GMSL2 Serializer Driver + * + * Copyright (C) 2025 Analog Devices Inc. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "max_ser.h" +#include "max_serdes.h" + +#define MAX_SER_NUM_LINKS 1 +#define MAX_SER_NUM_PHYS 1 + +struct max_ser_priv { + struct max_ser *ser; + struct device *dev; + struct i2c_client *client; + + struct i2c_atr *atr; + struct i2c_mux_core *mux; + + struct media_pad *pads; + struct max_serdes_source *sources; + u64 *streams_masks; + u32 double_bpps; + + struct v4l2_subdev sd; + struct v4l2_async_notifier nf; + struct v4l2_ctrl_handler ctrl_handler; +}; + +struct max_ser_route_hw { + struct max_serdes_source *source; + struct max_ser_pipe *pipe; + struct v4l2_mbus_frame_desc_entry entry; + bool is_tpg; +}; + +static inline struct max_ser_priv *sd_to_priv(struct v4l2_subdev *sd) +{ + return container_of(sd, struct max_ser_priv, sd); +} + +static inline struct max_ser_priv *nf_to_priv(struct v4l2_async_notifier *nf) +{ + return container_of(nf, struct max_ser_priv, nf); +} + +static inline struct max_ser_priv *ctrl_to_priv(struct v4l2_ctrl_handler *handler) +{ + return container_of(handler, struct max_ser_priv, ctrl_handler); +} + +static inline bool max_ser_pad_is_sink(struct max_ser *ser, u32 pad) +{ + return pad < ser->ops->num_phys; +} + +static inline bool max_ser_pad_is_source(struct max_ser *ser, u32 pad) +{ + return pad >= ser->ops->num_phys && + pad < ser->ops->num_phys + MAX_SER_NUM_LINKS; +} + +static inline u32 max_ser_source_pad(struct max_ser *ser) +{ + return ser->ops->num_phys; +} + +static inline bool max_ser_pad_is_tpg(struct max_ser *ser, u32 pad) +{ + return pad >= ser->ops->num_phys + MAX_SER_NUM_LINKS; +} + +static inline unsigned int max_ser_phy_to_pad(struct max_ser *ser, + struct max_ser_phy *phy) +{ + return phy->index; +} + +static inline unsigned int max_ser_num_pads(struct max_ser *ser) +{ + return ser->ops->num_phys + MAX_SER_NUM_LINKS; + /* + * TODO: once MEDIA_PAD_FL_INTERNAL is enabled in the uAPI we + * can add the TPG pad: + * return ser->ops->num_phys + MAX_SER_NUM_LINKS + + * (ser->ops->set_tpg ? 1 : 0); + */ +} + +static struct max_ser_phy *max_ser_pad_to_phy(struct max_ser *ser, u32 pad) +{ + if (!max_ser_pad_is_sink(ser, pad)) + return NULL; + + return &ser->phys[pad]; +} + +static struct max_ser_pipe * +max_ser_find_phy_pipe(struct max_ser *ser, struct max_ser_phy *phy) +{ + unsigned int i; + + for (i = 0; i < ser->ops->num_pipes; i++) { + struct max_ser_pipe *pipe = &ser->pipes[i]; + + if (pipe->phy_id == phy->index) + return pipe; + } + + return NULL; +} + +static struct max_serdes_source * +max_ser_get_phy_source(struct max_ser_priv *priv, struct max_ser_phy *phy) +{ + return &priv->sources[phy->index]; +} + +static const struct max_serdes_tpg_entry * +max_ser_find_tpg_entry(struct max_ser *ser, u32 target_index, + u32 width, u32 height, u32 code, + u32 numerator, u32 denominator) +{ + const struct max_serdes_tpg_entry *entry; + unsigned int index = 0; + unsigned int i; + + for (i = 0; i < ser->ops->tpg_entries.num_entries; i++) { + entry = &ser->ops->tpg_entries.entries[i]; + + if ((width != 0 && width != entry->width) || + (height != 0 && height != entry->height) || + (code != 0 && code != entry->code) || + (numerator != 0 && numerator != entry->interval.numerator) || + (denominator != 0 && denominator != entry->interval.denominator)) + continue; + + if (index == target_index) + break; + + index++; + } + + if (i == ser->ops->tpg_entries.num_entries) + return NULL; + + return &ser->ops->tpg_entries.entries[i]; +} + +static const struct max_serdes_tpg_entry * +max_ser_find_state_tpg_entry(struct max_ser *ser, struct v4l2_subdev_state *state, + unsigned int pad) +{ + struct v4l2_mbus_framefmt *fmt; + struct v4l2_fract *in; + + fmt = v4l2_subdev_state_get_format(state, pad, MAX_SERDES_TPG_STREAM); + if (!fmt) + return NULL; + + in = v4l2_subdev_state_get_interval(state, pad, MAX_SERDES_TPG_STREAM); + if (!in) + return NULL; + + return max_ser_find_tpg_entry(ser, 0, fmt->width, fmt->height, fmt->code, + in->numerator, in->denominator); +} + +static int max_ser_get_tpg_fd_entry_state(struct max_ser *ser, + struct v4l2_subdev_state *state, + struct v4l2_mbus_frame_desc_entry *fd_entry, + unsigned int pad) +{ + const struct max_serdes_tpg_entry *entry; + + entry = max_ser_find_state_tpg_entry(ser, state, pad); + if (!entry) + return -EINVAL; + + fd_entry->stream = MAX_SERDES_TPG_STREAM; + fd_entry->flags = V4L2_MBUS_FRAME_DESC_FL_LEN_MAX; + fd_entry->length = entry->width * entry->height * entry->bpp / 8; + fd_entry->pixelcode = entry->code; + fd_entry->bus.csi2.vc = 0; + fd_entry->bus.csi2.dt = entry->dt; + + return 0; +} + +static int max_ser_tpg_route_to_hw(struct max_ser_priv *priv, + struct v4l2_subdev_state *state, + struct v4l2_subdev_route *route, + struct max_ser_route_hw *hw) +{ + struct max_ser *ser = priv->ser; + + hw->pipe = &ser->pipes[0]; + + return max_ser_get_tpg_fd_entry_state(ser, state, &hw->entry, + route->sink_pad); +} + +static int max_ser_route_to_hw(struct max_ser_priv *priv, + struct v4l2_subdev_state *state, + struct v4l2_subdev_route *route, + struct max_ser_route_hw *hw) +{ + struct max_ser *ser = priv->ser; + struct v4l2_mbus_frame_desc fd = {}; + struct max_ser_phy *phy; + unsigned int i; + int ret; + + memset(hw, 0, sizeof(*hw)); + + hw->is_tpg = max_ser_pad_is_tpg(ser, route->sink_pad); + if (hw->is_tpg) + return max_ser_tpg_route_to_hw(priv, state, route, hw); + + phy = max_ser_pad_to_phy(ser, route->sink_pad); + if (!phy) + return -ENOENT; + + hw->pipe = max_ser_find_phy_pipe(ser, phy); + if (!hw->pipe) + return -ENOENT; + + hw->source = max_ser_get_phy_source(priv, phy); + if (!hw->source->sd) + return 0; + + ret = v4l2_subdev_call(hw->source->sd, pad, get_frame_desc, + hw->source->pad, &fd); + if (ret) + return ret; + + for (i = 0; i < fd.num_entries; i++) + if (fd.entry[i].stream == route->sink_stream) + break; + + if (i == fd.num_entries) + return -ENOENT; + + hw->entry = fd.entry[i]; + + return 0; +} + +static int max_ser_phy_set_active(struct max_ser *ser, struct max_ser_phy *phy, + bool active) +{ + int ret; + + if (ser->ops->set_phy_active) { + ret = ser->ops->set_phy_active(ser, phy, active); + if (ret) + return ret; + } + + phy->active = active; + + return 0; +} + +static int max_ser_set_pipe_dts(struct max_ser_priv *priv, struct max_ser_pipe *pipe, + unsigned int *dts, unsigned int num_dts) +{ + struct max_ser *ser = priv->ser; + unsigned int i; + int ret; + + if (!ser->ops->set_pipe_dt || !ser->ops->set_pipe_dt_en) + return 0; + + for (i = 0; i < num_dts; i++) { + ret = ser->ops->set_pipe_dt(ser, pipe, i, dts[i]); + if (ret) + return ret; + + ret = ser->ops->set_pipe_dt_en(ser, pipe, i, true); + if (ret) + return ret; + } + + if (num_dts == pipe->num_dts) + return 0; + + for (i = num_dts; i < ser->ops->num_dts_per_pipe; i++) { + ret = ser->ops->set_pipe_dt_en(ser, pipe, i, false); + if (ret) + return ret; + } + + return 0; +} + +static int max_ser_set_pipe_mode(struct max_ser_priv *priv, struct max_ser_pipe *pipe, + struct max_ser_pipe_mode *mode) +{ + struct max_ser *ser = priv->ser; + + if (mode->bpp == pipe->mode.bpp && + mode->soft_bpp == pipe->mode.soft_bpp && + mode->dbl8 == pipe->mode.dbl8 && + mode->dbl10 == pipe->mode.dbl10 && + mode->dbl12 == pipe->mode.dbl12) + return 0; + + if (!ser->ops->set_pipe_mode) + return 0; + + return ser->ops->set_pipe_mode(ser, pipe, mode); +} + +static int max_ser_i2c_atr_attach_addr(struct i2c_atr *atr, u32 chan_id, + u16 addr, u16 alias) +{ + struct max_serdes_i2c_xlate xlate = { + .src = alias, + .dst = addr, + .en = true, + }; + struct max_ser_priv *priv = i2c_atr_get_driver_data(atr); + struct max_ser *ser = priv->ser; + unsigned int i; + int ret; + + for (i = 0; i < ser->ops->num_i2c_xlates; i++) + if (!ser->i2c_xlates[i].en) + break; + + if (i == ser->ops->num_i2c_xlates) { + dev_err(priv->dev, + "Reached maximum number of I2C translations\n"); + return -EINVAL; + } + + ret = ser->ops->set_i2c_xlate(ser, i, &xlate); + if (ret) + return ret; + + ser->i2c_xlates[i] = xlate; + + return 0; +} + +static void max_ser_i2c_atr_detach_addr(struct i2c_atr *atr, u32 chan_id, u16 addr) +{ + struct max_ser_priv *priv = i2c_atr_get_driver_data(atr); + struct max_ser *ser = priv->ser; + struct max_serdes_i2c_xlate xlate = { 0 }; + unsigned int i; + + /* Find index of matching I2C translation. */ + for (i = 0; i < ser->ops->num_i2c_xlates; i++) + if (ser->i2c_xlates[i].dst == addr) + break; + + if (WARN_ON(i == ser->ops->num_i2c_xlates)) + return; + + ser->ops->set_i2c_xlate(ser, i, &xlate); + ser->i2c_xlates[i] = xlate; +} + +static const struct i2c_atr_ops max_ser_i2c_atr_ops = { + .attach_addr = max_ser_i2c_atr_attach_addr, + .detach_addr = max_ser_i2c_atr_detach_addr, +}; + +static void max_ser_i2c_atr_deinit(struct max_ser_priv *priv) +{ + /* Deleting adapters that haven't been added does no harm. */ + i2c_atr_del_adapter(priv->atr, 0); + + i2c_atr_delete(priv->atr); + priv->atr = NULL; +} + +static int max_ser_i2c_atr_init(struct max_ser_priv *priv) +{ + struct i2c_atr_adap_desc desc = { + .chan_id = 0, + }; + int ret; + + if (!i2c_check_functionality(priv->client->adapter, + I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) + return -ENODEV; + + priv->atr = i2c_atr_new(priv->client->adapter, priv->dev, + &max_ser_i2c_atr_ops, 1, 0); + if (IS_ERR(priv->atr)) + return PTR_ERR(priv->atr); + + i2c_atr_set_driver_data(priv->atr, priv); + + ret = i2c_atr_add_adapter(priv->atr, &desc); + if (ret) + max_ser_i2c_atr_deinit(priv); + + return ret; +} + +static int max_ser_i2c_mux_select(struct i2c_mux_core *mux, u32 chan) +{ + return 0; +} + +static void max_ser_i2c_mux_deinit(struct max_ser_priv *priv) +{ + i2c_mux_del_adapters(priv->mux); +} + +static int max_ser_i2c_mux_init(struct max_ser_priv *priv) +{ + priv->mux = i2c_mux_alloc(priv->client->adapter, &priv->client->dev, + 1, 0, I2C_MUX_LOCKED | I2C_MUX_GATE, + max_ser_i2c_mux_select, NULL); + if (!priv->mux) + return -ENOMEM; + + return i2c_mux_add_adapter(priv->mux, 0, 0); +} + +static int max_ser_i2c_adapter_init(struct max_ser_priv *priv) +{ + struct fwnode_handle *fwnode; + + fwnode = device_get_named_child_node(priv->dev, "i2c-gate"); + if (fwnode) { + fwnode_handle_put(fwnode); + return max_ser_i2c_mux_init(priv); + } + + return max_ser_i2c_atr_init(priv); +} + +static void max_ser_i2c_adapter_deinit(struct max_ser_priv *priv) +{ + if (priv->mux) + max_ser_i2c_mux_deinit(priv); + else + max_ser_i2c_atr_deinit(priv); +} + +static int max_ser_set_tpg_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *fmt = &format->format; + struct max_ser_priv *priv = v4l2_get_subdevdata(sd); + struct max_ser *ser = priv->ser; + const struct max_serdes_tpg_entry *entry; + struct v4l2_fract *in; + + if (format->stream != MAX_SERDES_TPG_STREAM) + return -EINVAL; + + entry = max_ser_find_tpg_entry(ser, 0, fmt->width, fmt->height, + fmt->code, 0, 0); + if (!entry) + return -EINVAL; + + in = v4l2_subdev_state_get_interval(state, format->pad, format->stream); + if (!in) + return -EINVAL; + + in->numerator = entry->interval.numerator; + in->denominator = entry->interval.denominator; + + return 0; +} + +static int max_ser_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct max_ser_priv *priv = v4l2_get_subdevdata(sd); + struct max_ser *ser = priv->ser; + struct v4l2_mbus_framefmt *fmt; + int ret; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && ser->active) + return -EBUSY; + + /* No transcoding, source and sink formats must match. */ + if (max_ser_pad_is_source(ser, format->pad)) + return v4l2_subdev_get_fmt(sd, state, format); + + if (max_ser_pad_is_tpg(ser, format->pad)) { + ret = max_ser_set_tpg_fmt(sd, state, format); + if (ret) + return ret; + } + + if (format->format.code == ~0U) + format->format.code = MEDIA_BUS_FMT_UYVY8_1X16; + + fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + return 0; +} + +static int max_ser_log_status(struct v4l2_subdev *sd) +{ + struct max_ser_priv *priv = sd_to_priv(sd); + struct max_ser *ser = priv->ser; + struct v4l2_subdev_state *state; + unsigned int i, j; + int ret; + + state = v4l2_subdev_lock_and_get_active_state(&priv->sd); + + v4l2_info(sd, "mode: %s\n", max_serdes_gmsl_mode_str(ser->mode)); + if (ser->ops->set_tpg) { + const struct max_serdes_tpg_entry *entry = ser->tpg_entry; + + if (entry) { + v4l2_info(sd, "tpg: %ux%u@%u/%u, code: %u, dt: %u, bpp: %u\n", + entry->width, entry->height, + entry->interval.numerator, + entry->interval.denominator, + entry->code, entry->dt, entry->bpp); + } else { + v4l2_info(sd, "tpg: disabled\n"); + } + } + if (ser->ops->log_status) { + ret = ser->ops->log_status(ser); + if (ret) + goto out_unlock; + } + v4l2_info(sd, "i2c_xlates:\n"); + for (i = 0; i < ser->ops->num_i2c_xlates; i++) { + v4l2_info(sd, "\ten: %u, src: 0x%02x dst: 0x%02x\n", + ser->i2c_xlates[i].en, ser->i2c_xlates[i].src, + ser->i2c_xlates[i].dst); + if (!ser->i2c_xlates[i].en) + break; + } + v4l2_info(sd, "\n"); + if (ser->ops->set_vc_remap) { + v4l2_info(sd, "vc_remaps: %u\n", ser->num_vc_remaps); + for (j = 0; j < ser->num_vc_remaps; j++) { + v4l2_info(sd, "\tvc_remap: src: %u, dst: %u\n", + ser->vc_remaps[j].src, ser->vc_remaps[j].dst); + } + } + v4l2_info(sd, "\n"); + + for (i = 0; i < ser->ops->num_pipes; i++) { + struct max_ser_pipe *pipe = &ser->pipes[i]; + + v4l2_info(sd, "pipe: %u\n", pipe->index); + v4l2_info(sd, "\tenabled: %u\n", pipe->enabled); + + if (!pipe->enabled) { + v4l2_info(sd, "\n"); + continue; + } + + v4l2_info(sd, "\tphy_id: %u\n", pipe->phy_id); + v4l2_info(sd, "\tstream_id: %u\n", pipe->stream_id); + if (ser->ops->set_pipe_phy) + v4l2_info(sd, "\tphy_id: %u\n", pipe->phy_id); + if (ser->ops->set_pipe_dt) { + v4l2_info(sd, "\tdts: %u\n", pipe->num_dts); + for (j = 0; j < pipe->num_dts; j++) + v4l2_info(sd, "\t\tdt: 0x%02x\n", pipe->dts[j]); + } + if (ser->ops->set_pipe_vcs) + v4l2_info(sd, "\tvcs: 0x%08x\n", pipe->vcs); + if (ser->ops->set_pipe_mode) { + v4l2_info(sd, "\tdbl8: %u\n", pipe->mode.dbl8); + v4l2_info(sd, "\tdbl10: %u\n", pipe->mode.dbl10); + v4l2_info(sd, "\tdbl12: %u\n", pipe->mode.dbl12); + v4l2_info(sd, "\tsoft_bpp: %u\n", pipe->mode.soft_bpp); + v4l2_info(sd, "\tbpp: %u\n", pipe->mode.bpp); + } + if (ser->ops->log_pipe_status) { + ret = ser->ops->log_pipe_status(ser, pipe); + if (ret) + goto out_unlock; + } + v4l2_info(sd, "\n"); + } + + for (i = 0; i < ser->ops->num_phys; i++) { + struct max_ser_phy *phy = &ser->phys[i]; + + v4l2_info(sd, "phy: %u\n", phy->index); + v4l2_info(sd, "\tenabled: %u\n", phy->enabled); + + if (!phy->enabled) { + v4l2_info(sd, "\n"); + continue; + } + + v4l2_info(sd, "\tactive: %u\n", phy->active); + v4l2_info(sd, "\tnum_data_lanes: %u\n", phy->mipi.num_data_lanes); + v4l2_info(sd, "\tclock_lane: %u\n", phy->mipi.clock_lane); + v4l2_info(sd, "\tnoncontinuous_clock: %u\n", + !!(phy->mipi.flags & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK)); + if (ser->ops->log_phy_status) { + ret = ser->ops->log_phy_status(ser, phy); + if (ret) + goto out_unlock; + } + v4l2_info(sd, "\n"); + } + + ret = 0; + +out_unlock: + v4l2_subdev_unlock_state(state); + + return ret; +} + +static int max_ser_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct max_ser_priv *priv = ctrl_to_priv(ctrl->handler); + struct max_ser *ser = priv->ser; + + switch (ctrl->id) { + case V4L2_CID_TEST_PATTERN: + ser->tpg_pattern = ctrl->val; + return 0; + } + + return -EINVAL; +} + +static int max_ser_enum_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_interval_enum *fie) +{ + struct max_ser_priv *priv = v4l2_get_subdevdata(sd); + struct max_ser *ser = priv->ser; + const struct max_serdes_tpg_entry *entry; + + if (!max_ser_pad_is_tpg(ser, fie->pad) || + fie->stream != MAX_SERDES_TPG_STREAM) + return -ENOTTY; + + entry = max_ser_find_tpg_entry(ser, fie->index, fie->width, fie->height, + fie->code, 0, 0); + if (!entry) + return -EINVAL; + + fie->interval.numerator = entry->interval.numerator; + fie->interval.denominator = entry->interval.denominator; + + return 0; +} + +static int max_ser_set_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_interval *fi) +{ + struct max_ser_priv *priv = v4l2_get_subdevdata(sd); + struct max_ser *ser = priv->ser; + const struct max_serdes_tpg_entry *entry; + struct v4l2_mbus_framefmt *fmt; + struct v4l2_fract *in; + + if (!max_ser_pad_is_tpg(ser, fi->pad) || + fi->stream != MAX_SERDES_TPG_STREAM) + return -ENOTTY; + + if (fi->which == V4L2_SUBDEV_FORMAT_ACTIVE && ser->active) + return -EBUSY; + + fmt = v4l2_subdev_state_get_format(state, fi->pad, fi->stream); + if (!fmt) + return -EINVAL; + + entry = max_ser_find_tpg_entry(ser, 0, fmt->width, fmt->height, + fmt->code, fi->interval.numerator, + fi->interval.denominator); + if (!entry) + return -EINVAL; + + in = v4l2_subdev_state_get_interval(state, fi->pad, fi->stream); + if (!in) + return -EINVAL; + + in->numerator = fi->interval.numerator; + in->denominator = fi->interval.denominator; + + return 0; +} + +static int max_ser_get_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_interval *fi) +{ + struct max_ser_priv *priv = v4l2_get_subdevdata(sd); + struct max_ser *ser = priv->ser; + + if (!max_ser_pad_is_tpg(ser, fi->pad) || + fi->stream != MAX_SERDES_TPG_STREAM) + return -ENOTTY; + + return v4l2_subdev_get_frame_interval(sd, state, fi); +} + +static int max_ser_get_frame_desc_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_mbus_frame_desc *fd, + unsigned int pad) +{ + struct max_ser_priv *priv = sd_to_priv(sd); + struct max_ser *ser = priv->ser; + struct v4l2_subdev_route *route; + int ret; + + if (!max_ser_pad_is_source(ser, pad)) + return -ENOENT; + + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2; + + for_each_active_route(&state->routing, route) { + struct max_ser_route_hw hw; + + if (pad != route->source_pad) + continue; + + ret = max_ser_route_to_hw(priv, state, route, &hw); + if (ret) + return ret; + + hw.entry.stream = route->source_stream; + + fd->entry[fd->num_entries++] = hw.entry; + } + + return 0; +} + +static int max_ser_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_frame_desc *fd) +{ + struct max_ser_priv *priv = sd_to_priv(sd); + struct v4l2_subdev_state *state; + int ret; + + state = v4l2_subdev_lock_and_get_active_state(&priv->sd); + + ret = max_ser_get_frame_desc_state(sd, state, fd, pad); + + v4l2_subdev_unlock_state(state); + + return ret; +} + +static int max_ser_set_tpg_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_krouting *routing) +{ + struct max_ser_priv *priv = sd_to_priv(sd); + struct max_ser *ser = priv->ser; + const struct max_serdes_tpg_entry *entry; + struct v4l2_mbus_framefmt fmt = { 0 }; + int ret; + + ret = max_serdes_validate_tpg_routing(routing); + if (ret) + return ret; + + entry = &ser->ops->tpg_entries.entries[0]; + + fmt.width = entry->width; + fmt.height = entry->height; + fmt.code = entry->code; + + return v4l2_subdev_set_routing_with_fmt(sd, state, routing, &fmt); +} + +static int __max_ser_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_krouting *routing) +{ + struct max_ser_priv *priv = sd_to_priv(sd); + struct max_ser *ser = priv->ser; + struct v4l2_subdev_route *route; + bool is_tpg = false; + int ret; + + ret = v4l2_subdev_routing_validate(sd, routing, + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1 | + V4L2_SUBDEV_ROUTING_NO_SINK_STREAM_MIX); + if (ret) + return ret; + + for_each_active_route(routing, route) { + if (max_ser_pad_is_tpg(ser, route->sink_pad)) { + is_tpg = true; + break; + } + } + + if (is_tpg) + return max_ser_set_tpg_routing(sd, state, routing); + + static const struct v4l2_mbus_framefmt format = { + .code = MEDIA_BUS_FMT_Y8_1X8, + .field = V4L2_FIELD_NONE, + .width = 640, + .height = 480, + }; + + return v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format); +} + +static int max_ser_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + struct max_ser_priv *priv = sd_to_priv(sd); + struct max_ser *ser = priv->ser; + + if (which == V4L2_SUBDEV_FORMAT_ACTIVE && ser->active) + return -EBUSY; + + return __max_ser_set_routing(sd, state, routing); +} + +static int max_ser_get_pipe_vcs_dts(struct max_ser_priv *priv, + struct v4l2_subdev_state *state, + struct max_ser_pipe *pipe, + unsigned int *vcs, + unsigned int *dts, unsigned int *num_dts, + u64 *streams_masks) +{ + struct v4l2_subdev_route *route; + struct max_ser *ser = priv->ser; + unsigned int i; + int ret; + + *vcs = 0; + *num_dts = 0; + + if (ser->mode != MAX_SERDES_GMSL_PIXEL_MODE) + return 0; + + for_each_active_route(&state->routing, route) { + struct max_ser_route_hw hw; + unsigned int vc, dt; + + if (!(BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad])) + continue; + + ret = max_ser_route_to_hw(priv, state, route, &hw); + if (ret) + return ret; + + if (hw.pipe != pipe) + continue; + + vc = hw.entry.bus.csi2.vc; + dt = hw.entry.bus.csi2.dt; + + if (vc >= MAX_SERDES_VC_ID_NUM) + return -E2BIG; + + *vcs |= BIT(vc); + + /* Skip already added DT. */ + for (i = 0; i < *num_dts; i++) + if (dts[i] == dt) + break; + + if (i < *num_dts) + continue; + + if (*num_dts >= ser->ops->num_dts_per_pipe) + return -EINVAL; + + dts[*num_dts] = dt; + (*num_dts)++; + } + + /* + * Hardware cannot distinguish between different pairs of VC and DT, + * issue a warning. + */ + for_each_active_route(&state->routing, route) { + struct max_ser_route_hw hw; + unsigned int vc, dt; + + /* + * Skip enabled streams, we only want to check for leaks + * among the disabled streams. + */ + if ((BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad])) + continue; + + ret = max_ser_route_to_hw(priv, state, route, &hw); + if (ret) + return ret; + + if (hw.pipe != pipe) + continue; + + vc = hw.entry.bus.csi2.vc; + dt = hw.entry.bus.csi2.dt; + + if (vc >= MAX_SERDES_VC_ID_NUM) + return -E2BIG; + + if (!(*vcs & BIT(vc))) + continue; + + for (i = 0; i < *num_dts; i++) + if (dts[i] == dt) + break; + + if (i == *num_dts) + continue; + + dev_warn(priv->dev, "Leaked disabled stream %u:%u with VC: %u, DT: %u", + route->source_pad, route->source_stream, vc, dt); + } + + return 0; +} + +static int max_ser_get_pipe_mode(struct max_ser_priv *priv, + struct v4l2_subdev_state *state, + struct max_ser_pipe *pipe, + struct max_ser_pipe_mode *mode, + u64 *streams_masks) +{ + struct v4l2_subdev_route *route; + struct max_ser *ser = priv->ser; + bool force_set_bpp = false; + unsigned int doubled_bpp = 0; + unsigned int min_bpp; + unsigned int max_bpp; + u32 bpps = 0; + int ret; + + if (ser->mode != MAX_SERDES_GMSL_PIXEL_MODE) + return 0; + + for_each_active_route(&state->routing, route) { + struct max_ser_route_hw hw; + unsigned int bpp; + + if (!(BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad])) + continue; + + ret = max_ser_route_to_hw(priv, state, route, &hw); + if (ret) + return ret; + + if (hw.pipe != pipe) + continue; + + if (hw.is_tpg) + force_set_bpp = true; + + ret = max_serdes_get_fd_bpp(&hw.entry, &bpp); + if (ret) + return ret; + + bpps |= BIT(bpp); + } + + ret = max_serdes_process_bpps(priv->dev, bpps, priv->double_bpps, &doubled_bpp); + if (ret) + return ret; + + if (doubled_bpp == 8) + mode->dbl8 = true; + else if (doubled_bpp == 10) + mode->dbl10 = true; + else if (doubled_bpp == 12) + mode->dbl12 = true; + + if (doubled_bpp) { + bpps &= ~BIT(doubled_bpp); + bpps |= BIT(doubled_bpp * 2); + } + + if (!bpps) + return 0; + + min_bpp = __ffs(bpps); + max_bpp = __fls(bpps); + + if (doubled_bpp) + mode->soft_bpp = min_bpp; + + if (min_bpp != max_bpp || force_set_bpp) + mode->bpp = max_bpp; + + return 0; +} + +static int max_ser_update_pipe_enable(struct max_ser_priv *priv, + struct max_ser_pipe *pipe, + struct v4l2_subdev_state *state, + u64 *streams_masks) +{ + struct max_ser *ser = priv->ser; + struct v4l2_subdev_route *route; + bool enable = false; + int ret; + + for_each_active_route(&state->routing, route) { + struct max_ser_route_hw hw; + + if (!(BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad])) + continue; + + ret = max_ser_route_to_hw(priv, state, route, &hw); + if (ret) + return ret; + + if (hw.pipe != pipe) + continue; + + enable = true; + break; + } + + if (enable == pipe->enabled) + return 0; + + ret = ser->ops->set_pipe_enable(ser, pipe, enable); + if (ret) + return ret; + + pipe->enabled = enable; + + return 0; +} + +static int max_ser_update_pipe(struct max_ser_priv *priv, + struct max_ser_pipe *pipe, + struct v4l2_subdev_state *state, + u64 *streams_masks) +{ + struct max_ser *ser = priv->ser; + struct max_ser_pipe_mode mode = { 0 }; + unsigned int num_dts; + unsigned int *dts; + unsigned int vcs; + int ret; + + if (!ser->ops->num_dts_per_pipe) + return 0; + + dts = devm_kcalloc(priv->dev, ser->ops->num_dts_per_pipe, sizeof(*dts), + GFP_KERNEL); + if (!dts) + return -ENOMEM; + + ret = max_ser_get_pipe_vcs_dts(priv, state, pipe, &vcs, dts, &num_dts, + streams_masks); + if (ret) + goto err_free_dts; + + ret = max_ser_get_pipe_mode(priv, state, pipe, &mode, streams_masks); + if (ret) + goto err_free_dts; + + if (ser->ops->set_pipe_vcs) { + ret = ser->ops->set_pipe_vcs(ser, pipe, vcs); + if (ret) + goto err_free_dts; + } + + ret = max_ser_set_pipe_mode(priv, pipe, &mode); + if (ret) + goto err_revert_vcs; + + ret = max_ser_set_pipe_dts(priv, pipe, dts, num_dts); + if (ret) + goto err_revert_mode; + + pipe->vcs = vcs; + pipe->mode = mode; + + if (pipe->dts) + devm_kfree(priv->dev, pipe->dts); + + pipe->dts = dts; + pipe->num_dts = num_dts; + + return 0; + +err_revert_mode: + if (ser->ops->set_pipe_mode) + ser->ops->set_pipe_mode(ser, pipe, &pipe->mode); + +err_revert_vcs: + if (ser->ops->set_pipe_vcs) + ser->ops->set_pipe_vcs(ser, pipe, pipe->vcs); + +err_free_dts: + devm_kfree(priv->dev, dts); + + return ret; +} + +static int max_ser_update_phy(struct max_ser_priv *priv, + struct v4l2_subdev_state *state, + struct max_ser_phy *phy, u64 *streams_masks, + u64 *old_streams_masks) +{ + struct max_ser *ser = priv->ser; + u32 pad = max_ser_phy_to_pad(ser, phy); + bool enable_changed = !streams_masks[pad] != !old_streams_masks[pad]; + bool enable = !!streams_masks[pad]; + struct max_ser_pipe *pipe; + int ret; + + pipe = max_ser_find_phy_pipe(ser, phy); + if (!pipe) + return -ENOENT; + + if (!enable && enable_changed) { + ret = max_ser_phy_set_active(ser, phy, enable); + if (ret) + return ret; + } + + ret = max_ser_update_pipe(priv, pipe, state, streams_masks); + if (ret) + goto err_revert_phy_disable; + + ret = max_ser_update_pipe_enable(priv, pipe, state, streams_masks); + if (ret) + goto err_revert_pipe_update; + + if (enable && enable_changed) { + ret = max_ser_phy_set_active(ser, phy, enable); + if (ret) + goto err_revert_update_pipe_enable; + } + + return 0; + +err_revert_update_pipe_enable: + max_ser_update_pipe_enable(priv, pipe, state, priv->streams_masks); + +err_revert_pipe_update: + max_ser_update_pipe(priv, pipe, state, priv->streams_masks); + +err_revert_phy_disable: + if (!enable && enable_changed) + max_ser_phy_set_active(ser, phy, !enable); + + return ret; +} + +static int max_ser_update_phys(struct max_ser_priv *priv, + struct v4l2_subdev_state *state, + u64 *streams_masks, u64 *old_streams_masks) +{ + struct max_ser *ser = priv->ser; + unsigned int failed_update_phy_id = ser->ops->num_phys; + unsigned int i; + int ret; + + for (i = 0; i < ser->ops->num_phys; i++) { + struct max_ser_phy *phy = &ser->phys[i]; + + ret = max_ser_update_phy(priv, state, phy, streams_masks, + old_streams_masks); + if (ret) { + failed_update_phy_id = i; + goto err; + } + } + + return 0; + +err: + for (i = 0; i < failed_update_phy_id; i++) { + struct max_ser_phy *phy = &ser->phys[i]; + + max_ser_update_phy(priv, state, phy, old_streams_masks, + streams_masks); + } + + return ret; +} + +static int max_ser_enable_disable_streams(struct max_ser_priv *priv, + struct v4l2_subdev_state *state, + u32 pad, u64 updated_streams_mask, + bool enable) +{ + struct max_ser *ser = priv->ser; + + return max_serdes_xlate_enable_disable_streams(priv->sources, 0, state, + pad, updated_streams_mask, 0, + ser->ops->num_phys, enable); +} + +static bool max_ser_is_tpg_routed(struct max_ser_priv *priv, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_route *route; + int ret; + + for_each_active_route(&state->routing, route) { + struct max_ser_route_hw hw; + + ret = max_ser_route_to_hw(priv, state, route, &hw); + if (ret) + return false; + + if (hw.is_tpg) + return true; + } + + return false; +} + +static int max_ser_update_tpg(struct max_ser_priv *priv, + struct v4l2_subdev_state *state, + u64 *streams_masks) +{ + const struct max_serdes_tpg_entry *entry = NULL; + struct max_ser *ser = priv->ser; + struct v4l2_subdev_route *route; + int ret; + + for_each_active_route(&state->routing, route) { + struct max_ser_route_hw hw; + + if (!(BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad])) + continue; + + ret = max_ser_route_to_hw(priv, state, route, &hw); + if (ret) + return ret; + + if (!hw.is_tpg) + continue; + + entry = max_ser_find_state_tpg_entry(ser, state, route->sink_pad); + break; + } + + if (entry == ser->tpg_entry) + return 0; + + ret = ser->ops->set_tpg(ser, entry); + if (ret) + return ret; + + ser->tpg_entry = entry; + + return 0; +} + +static int max_ser_update_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 updated_streams_mask, bool enable) +{ + struct max_ser_priv *priv = v4l2_get_subdevdata(sd); + struct max_ser *ser = priv->ser; + unsigned int num_pads = max_ser_num_pads(ser); + u64 *streams_masks; + int ret; + + ret = max_serdes_get_streams_masks(priv->dev, state, pad, updated_streams_mask, + num_pads, priv->streams_masks, &streams_masks, + enable); + if (ret) + return ret; + + if (!enable) { + ret = max_ser_enable_disable_streams(priv, state, pad, + updated_streams_mask, enable); + if (ret) + goto err_free_streams_masks; + } + + ret = max_ser_update_tpg(priv, state, streams_masks); + if (ret) + goto err_revert_streams_disable; + + ret = max_ser_update_phys(priv, state, streams_masks, + priv->streams_masks); + if (ret) + goto err_revert_update_tpg; + + if (enable) { + ret = max_ser_enable_disable_streams(priv, state, pad, + updated_streams_mask, enable); + if (ret) + goto err_revert_phys_update; + } + + devm_kfree(priv->dev, priv->streams_masks); + priv->streams_masks = streams_masks; + ser->active = !!streams_masks[pad]; + + return 0; + +err_revert_phys_update: + max_ser_update_phys(priv, state, priv->streams_masks, streams_masks); + +err_revert_update_tpg: + max_ser_update_tpg(priv, state, priv->streams_masks); + +err_revert_streams_disable: + if (!enable) + max_ser_enable_disable_streams(priv, state, pad, + updated_streams_mask, !enable); + +err_free_streams_masks: + devm_kfree(priv->dev, streams_masks); + + return ret; +} + +static int max_ser_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + return max_ser_update_streams(sd, state, pad, streams_mask, true); +} + +static int max_ser_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + return max_ser_update_streams(sd, state, pad, streams_mask, false); +} + +static int max_ser_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_route routes[MAX_SER_NUM_PHYS] = { 0 }; + struct v4l2_subdev_krouting routing = { + .routes = routes, + }; + struct max_ser_priv *priv = v4l2_get_subdevdata(sd); + struct max_ser *ser = priv->ser; + unsigned int stream = 0; + unsigned int i; + + for (i = 0; i < ser->ops->num_phys; i++) { + struct max_ser_phy *phy = &ser->phys[i]; + + if (!phy->enabled) + continue; + + routing.routes[routing.num_routes++] = (struct v4l2_subdev_route) { + .sink_pad = max_ser_phy_to_pad(ser, phy), + .sink_stream = 0, + .source_pad = max_ser_source_pad(ser), + .source_stream = stream, + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }; + stream++; + + /* + * The Streams API is an experimental feature. + * If multiple routes are provided here, userspace will not be + * able to configure them unless the Streams API is enabled. + * Provide a single route until it is enabled. + */ + break; + } + + return __max_ser_set_routing(sd, state, &routing); +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int max_ser_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct max_ser_priv *priv = sd_to_priv(sd); + struct max_ser *ser = priv->ser; + unsigned int val; + int ret; + + ret = ser->ops->reg_read(ser, reg->reg, &val); + if (ret) + return ret; + + reg->val = val; + reg->size = 1; + + return 0; +} + +static int max_ser_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct max_ser_priv *priv = sd_to_priv(sd); + struct max_ser *ser = priv->ser; + + return ser->ops->reg_write(ser, reg->reg, reg->val); +} +#endif + +static const struct v4l2_subdev_core_ops max_ser_core_ops = { + .log_status = max_ser_log_status, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = max_ser_g_register, + .s_register = max_ser_s_register, +#endif +}; + +static const struct v4l2_ctrl_ops max_ser_ctrl_ops = { + .s_ctrl = max_ser_s_ctrl, +}; + +static const struct v4l2_subdev_pad_ops max_ser_pad_ops = { + .enable_streams = max_ser_enable_streams, + .disable_streams = max_ser_disable_streams, + + .set_routing = max_ser_set_routing, + .get_frame_desc = max_ser_get_frame_desc, + + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = max_ser_set_fmt, + + .enum_frame_interval = max_ser_enum_frame_interval, + .get_frame_interval = max_ser_get_frame_interval, + .set_frame_interval = max_ser_set_frame_interval, +}; + +static const struct v4l2_subdev_ops max_ser_subdev_ops = { + .core = &max_ser_core_ops, + .pad = &max_ser_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops max_ser_internal_ops = { + .init_state = &max_ser_init_state, +}; + +static const struct media_entity_operations max_ser_media_ops = { + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1, + .has_pad_interdep = v4l2_subdev_has_pad_interdep, + .link_validate = v4l2_subdev_link_validate, +}; + +static int max_ser_init(struct max_ser_priv *priv) +{ + struct max_ser *ser = priv->ser; + unsigned int i; + int ret; + + if (ser->ops->init) { + ret = ser->ops->init(ser); + if (ret) + return ret; + } + + if (ser->ops->set_tunnel_enable) { + ret = ser->ops->set_tunnel_enable(ser, false); + if (ret) + return ret; + } + + for (i = 0; i < ser->ops->num_phys; i++) { + struct max_ser_phy *phy = &ser->phys[i]; + + if (phy->enabled) { + ret = ser->ops->init_phy(ser, phy); + if (ret) + return ret; + } + + if (ser->ops->set_phy_active) { + ret = ser->ops->set_phy_active(ser, phy, false); + if (ret) + return ret; + } + } + + for (i = 0; i < ser->ops->num_pipes; i++) { + struct max_ser_pipe *pipe = &ser->pipes[i]; + struct max_ser_phy *phy = &ser->phys[pipe->phy_id]; + + ret = ser->ops->set_pipe_enable(ser, pipe, false); + if (ret) + return ret; + + if (ser->ops->set_pipe_stream_id) { + ret = ser->ops->set_pipe_stream_id(ser, pipe, pipe->stream_id); + if (ret) + return ret; + } + + if (ser->ops->set_pipe_phy) { + ret = ser->ops->set_pipe_phy(ser, pipe, phy); + if (ret) + return ret; + } + + if (ser->ops->set_pipe_vcs) { + ret = ser->ops->set_pipe_vcs(ser, pipe, 0); + if (ret) + return ret; + } + + if (ser->ops->set_pipe_mode) { + ret = ser->ops->set_pipe_mode(ser, pipe, &pipe->mode); + if (ret) + return ret; + } + + ret = max_ser_set_pipe_dts(priv, pipe, NULL, 0); + if (ret) + return ret; + } + + return 0; +} + +static int max_ser_notify_bound(struct v4l2_async_notifier *nf, + struct v4l2_subdev *subdev, + struct v4l2_async_connection *base_asc) +{ + struct max_ser_priv *priv = nf_to_priv(nf); + struct max_serdes_asc *asc = asc_to_max(base_asc); + struct max_serdes_source *source = asc->source; + u32 pad = source->index; + int ret; + + ret = media_entity_get_fwnode_pad(&subdev->entity, + source->ep_fwnode, + MEDIA_PAD_FL_SOURCE); + if (ret < 0) { + dev_err(priv->dev, "Failed to find pad for %s\n", subdev->name); + return ret; + } + + source->sd = subdev; + source->pad = ret; + + ret = media_create_pad_link(&source->sd->entity, source->pad, + &priv->sd.entity, pad, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_err(priv->dev, "Unable to link %s:%u -> %s:%u\n", + source->sd->name, source->pad, priv->sd.name, pad); + source->sd = NULL; + return ret; + } + + return 0; +} + +static void max_ser_notify_unbind(struct v4l2_async_notifier *nf, + struct v4l2_subdev *subdev, + struct v4l2_async_connection *base_asc) +{ + struct max_serdes_asc *asc = asc_to_max(base_asc); + struct max_serdes_source *source = asc->source; + + source->sd = NULL; +} + +static const struct v4l2_async_notifier_operations max_ser_notify_ops = { + .bound = max_ser_notify_bound, + .unbind = max_ser_notify_unbind, +}; + +static int max_ser_v4l2_notifier_register(struct max_ser_priv *priv) +{ + struct max_ser *ser = priv->ser; + unsigned int i; + int ret; + + v4l2_async_subdev_nf_init(&priv->nf, &priv->sd); + + for (i = 0; i < ser->ops->num_phys; i++) { + struct max_ser_phy *phy = &ser->phys[i]; + struct max_serdes_source *source; + struct max_serdes_asc *asc; + + source = max_ser_get_phy_source(priv, phy); + if (!source->ep_fwnode) + continue; + + asc = v4l2_async_nf_add_fwnode(&priv->nf, source->ep_fwnode, + struct max_serdes_asc); + if (IS_ERR(asc)) { + dev_err(priv->dev, + "Failed to add subdev for source %u: %pe", i, + asc); + + v4l2_async_nf_cleanup(&priv->nf); + + return PTR_ERR(asc); + } + + asc->source = source; + } + + priv->nf.ops = &max_ser_notify_ops; + + ret = v4l2_async_nf_register(&priv->nf); + if (ret) { + dev_err(priv->dev, "Failed to register subdev notifier"); + v4l2_async_nf_cleanup(&priv->nf); + return ret; + } + + return 0; +} + +static void max_ser_v4l2_notifier_unregister(struct max_ser_priv *priv) +{ + v4l2_async_nf_unregister(&priv->nf); + v4l2_async_nf_cleanup(&priv->nf); +} + +static int max_ser_v4l2_register(struct max_ser_priv *priv) +{ + struct v4l2_subdev *sd = &priv->sd; + struct max_ser *ser = priv->ser; + void *data = i2c_get_clientdata(priv->client); + unsigned int num_pads = max_ser_num_pads(ser); + unsigned int i; + int ret; + + v4l2_i2c_subdev_init(sd, priv->client, &max_ser_subdev_ops); + i2c_set_clientdata(priv->client, data); + sd->internal_ops = &max_ser_internal_ops; + sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + sd->entity.ops = &max_ser_media_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS; + + priv->pads = devm_kcalloc(priv->dev, num_pads, sizeof(*priv->pads), GFP_KERNEL); + if (!priv->pads) + return -ENOMEM; + + for (i = 0; i < num_pads; i++) { + if (max_ser_pad_is_sink(ser, i)) + priv->pads[i].flags = MEDIA_PAD_FL_SINK; + else if (max_ser_pad_is_source(ser, i)) + priv->pads[i].flags = MEDIA_PAD_FL_SOURCE; + else if (max_ser_pad_is_tpg(ser, i)) + priv->pads[i].flags = MEDIA_PAD_FL_SINK | + MEDIA_PAD_FL_INTERNAL; + else + return -EINVAL; + } + + v4l2_set_subdevdata(sd, priv); + + if (ser->ops->tpg_patterns) { + v4l2_ctrl_handler_init(&priv->ctrl_handler, 1); + priv->sd.ctrl_handler = &priv->ctrl_handler; + + v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler, + &max_ser_ctrl_ops, + V4L2_CID_TEST_PATTERN, + MAX_SERDES_TPG_PATTERN_MAX, + ~ser->ops->tpg_patterns, + __ffs(ser->ops->tpg_patterns), + max_serdes_tpg_patterns); + if (priv->ctrl_handler.error) { + ret = priv->ctrl_handler.error; + goto err_free_ctrl; + } + } + + ret = media_entity_pads_init(&sd->entity, num_pads, priv->pads); + if (ret) + goto err_free_ctrl; + + ret = max_ser_v4l2_notifier_register(priv); + if (ret) + goto err_media_entity_cleanup; + + ret = v4l2_subdev_init_finalize(sd); + if (ret) + goto err_nf_cleanup; + + ret = v4l2_async_register_subdev(sd); + if (ret) + goto err_sd_cleanup; + + return 0; + +err_sd_cleanup: + v4l2_subdev_cleanup(sd); +err_nf_cleanup: + max_ser_v4l2_notifier_unregister(priv); +err_media_entity_cleanup: + media_entity_cleanup(&sd->entity); +err_free_ctrl: + v4l2_ctrl_handler_free(&priv->ctrl_handler); + + return ret; +} + +static void max_ser_put_source_ep_fwnodes(struct max_ser_priv *priv) +{ + struct max_ser *ser = priv->ser; + unsigned int i; + + for (i = 0; i < ser->ops->num_phys; i++) { + fwnode_handle_put(priv->sources[i].ep_fwnode); + priv->sources[i].ep_fwnode = NULL; + } +} + +static void max_ser_v4l2_unregister(struct max_ser_priv *priv) +{ + struct v4l2_subdev *sd = &priv->sd; + + v4l2_async_unregister_subdev(sd); + v4l2_subdev_cleanup(sd); + max_ser_v4l2_notifier_unregister(priv); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(&priv->ctrl_handler); +} + +static int max_ser_parse_sink_dt_endpoint(struct max_ser_priv *priv, + struct max_ser_phy *phy, + struct max_serdes_source *source, + struct fwnode_handle *fwnode) +{ + struct max_ser *ser = priv->ser; + u32 pad = max_ser_phy_to_pad(ser, phy); + struct v4l2_fwnode_endpoint v4l2_ep = { .bus_type = V4L2_MBUS_CSI2_DPHY }; + struct fwnode_handle *ep; + int ret; + + ep = fwnode_graph_get_endpoint_by_id(fwnode, pad, 0, 0); + if (!ep) + return 0; + + source->ep_fwnode = fwnode_graph_get_remote_endpoint(ep); + if (!source->ep_fwnode) { + fwnode_handle_put(ep); + dev_err(priv->dev, + "Failed to get remote endpoint on port %u\n", pad); + return -EINVAL; + } + + ret = v4l2_fwnode_endpoint_parse(ep, &v4l2_ep); + fwnode_handle_put(ep); + if (ret) { + dev_err(priv->dev, "Could not parse endpoint on port %u\n", pad); + return ret; + } + + if (!v4l2_ep.bus.mipi_csi2.num_data_lanes) { + dev_err(priv->dev, "No data lanes on port %u\n", pad); + return -EINVAL; + } + + phy->mipi = v4l2_ep.bus.mipi_csi2; + phy->enabled = true; + + return 0; +} + +static int max_ser_find_phys_config(struct max_ser_priv *priv) +{ + struct max_ser *ser = priv->ser; + const struct max_serdes_phys_configs *configs = &ser->ops->phys_configs; + struct max_ser_phy *phy; + unsigned int i, j; + + if (!configs->num_configs) + return 0; + + for (i = 0; i < configs->num_configs; i++) { + const struct max_serdes_phys_config *config = &configs->configs[i]; + bool matching = true; + + for (j = 0; j < ser->ops->num_phys; j++) { + phy = &ser->phys[j]; + + if (!phy->enabled) + continue; + + if (phy->mipi.num_data_lanes <= config->lanes[j]) + continue; + + matching = false; + + break; + } + + if (matching) + break; + } + + if (i == configs->num_configs) { + dev_err(priv->dev, "Invalid lane configuration\n"); + return -EINVAL; + } + + ser->phys_config = i; + + return 0; +} + +static int max_ser_parse_dt(struct max_ser_priv *priv) +{ + struct fwnode_handle *fwnode = dev_fwnode(priv->dev); + struct max_ser *ser = priv->ser; + struct max_ser_pipe *pipe; + struct max_ser_phy *phy; + unsigned int i; + int ret; + + for (i = 0; i < ser->ops->num_phys; i++) { + phy = &ser->phys[i]; + phy->index = i; + } + + for (i = 0; i < ser->ops->num_pipes; i++) { + pipe = &ser->pipes[i]; + pipe->index = i; + pipe->phy_id = i % ser->ops->num_phys; + pipe->stream_id = i % MAX_SERDES_STREAMS_NUM; + } + + for (i = 0; i < ser->ops->num_phys; i++) { + struct max_ser_phy *phy = &ser->phys[i]; + struct max_serdes_source *source; + + source = max_ser_get_phy_source(priv, phy); + source->index = i; + + ret = max_ser_parse_sink_dt_endpoint(priv, phy, source, fwnode); + if (ret) + return ret; + } + + return max_ser_find_phys_config(priv); +} + +static int max_ser_allocate(struct max_ser_priv *priv) +{ + struct max_ser *ser = priv->ser; + unsigned int num_pads = max_ser_num_pads(ser); + + ser->phys = devm_kcalloc(priv->dev, ser->ops->num_phys, + sizeof(*ser->phys), GFP_KERNEL); + if (!ser->phys) + return -ENOMEM; + + ser->pipes = devm_kcalloc(priv->dev, ser->ops->num_pipes, + sizeof(*ser->pipes), GFP_KERNEL); + if (!ser->pipes) + return -ENOMEM; + + ser->vc_remaps = devm_kcalloc(priv->dev, ser->ops->num_vc_remaps, + sizeof(*ser->vc_remaps), GFP_KERNEL); + if (!ser->vc_remaps) + return -ENOMEM; + + ser->i2c_xlates = devm_kcalloc(priv->dev, ser->ops->num_i2c_xlates, + sizeof(*ser->i2c_xlates), GFP_KERNEL); + if (!ser->i2c_xlates) + return -ENOMEM; + + priv->sources = devm_kcalloc(priv->dev, ser->ops->num_phys, + sizeof(*priv->sources), GFP_KERNEL); + if (!priv->sources) + return -ENOMEM; + + priv->streams_masks = devm_kcalloc(priv->dev, num_pads, + sizeof(*priv->streams_masks), + GFP_KERNEL); + if (!priv->streams_masks) + return -ENOMEM; + + return 0; +} + +int max_ser_probe(struct i2c_client *client, struct max_ser *ser) +{ + struct device *dev = &client->dev; + struct max_ser_priv *priv; + int ret; + + if (ser->ops->num_phys > MAX_SER_NUM_PHYS) + return -E2BIG; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client = client; + priv->dev = dev; + priv->ser = ser; + ser->priv = priv; + ser->mode = __ffs(ser->ops->modes); + + ret = max_ser_allocate(priv); + if (ret) + return ret; + + ret = max_ser_parse_dt(priv); + if (ret) + goto err_put_source_ep_fwnodes; + + ret = max_ser_init(priv); + if (ret) + goto err_put_source_ep_fwnodes; + + ret = max_ser_i2c_adapter_init(priv); + if (ret) + goto err_put_source_ep_fwnodes; + + ret = max_ser_v4l2_register(priv); + if (ret) + goto err_i2c_adapter_deinit; + + return 0; + +err_i2c_adapter_deinit: + max_ser_i2c_adapter_deinit(priv); +err_put_source_ep_fwnodes: + max_ser_put_source_ep_fwnodes(priv); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(max_ser_probe, "MAX_SERDES"); + +int max_ser_remove(struct max_ser *ser) +{ + struct max_ser_priv *priv = ser->priv; + + max_ser_v4l2_unregister(priv); + + max_ser_i2c_adapter_deinit(priv); + + max_ser_put_source_ep_fwnodes(priv); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(max_ser_remove, "MAX_SERDES"); + +int max_ser_set_double_bpps(struct v4l2_subdev *sd, u32 double_bpps) +{ + struct max_ser_priv *priv = sd_to_priv(sd); + + priv->double_bpps = double_bpps; + + return 0; +} + +int max_ser_set_stream_id(struct v4l2_subdev *sd, unsigned int stream_id) +{ + struct max_ser_priv *priv = sd_to_priv(sd); + struct max_ser *ser = priv->ser; + struct max_ser_pipe *pipe = &ser->pipes[0]; + + if (!ser->ops->set_pipe_stream_id) + return -EOPNOTSUPP; + + return ser->ops->set_pipe_stream_id(ser, pipe, stream_id); +} + +int max_ser_get_stream_id(struct v4l2_subdev *sd, unsigned int *stream_id) +{ + struct max_ser_priv *priv = sd_to_priv(sd); + struct max_ser *ser = priv->ser; + struct max_ser_pipe *pipe = &ser->pipes[0]; + + if (!ser->ops->get_pipe_stream_id) + return -EOPNOTSUPP; + + *stream_id = ser->ops->get_pipe_stream_id(ser, pipe); + + return 0; +} + +unsigned int max_ser_get_supported_modes(struct v4l2_subdev *sd) +{ + struct max_ser_priv *priv = sd_to_priv(sd); + struct max_ser *ser = priv->ser; + struct v4l2_subdev_state *state; + unsigned int modes = ser->ops->modes; + + state = v4l2_subdev_lock_and_get_active_state(&priv->sd); + + if (max_ser_is_tpg_routed(priv, state)) + modes = BIT(ser->ops->tpg_mode); + + v4l2_subdev_unlock_state(state); + + return modes; +} + +bool max_ser_supports_vc_remap(struct v4l2_subdev *sd) +{ + struct max_ser_priv *priv = sd_to_priv(sd); + struct max_ser *ser = priv->ser; + + return !!ser->ops->set_vc_remap; +} + +int max_ser_set_mode(struct v4l2_subdev *sd, enum max_serdes_gmsl_mode mode) +{ + struct max_ser_priv *priv = sd_to_priv(sd); + struct max_ser *ser = priv->ser; + int ret; + + if (!(ser->ops->modes & BIT(mode))) + return -EINVAL; + + if (ser->mode == mode) + return 0; + + if (ser->ops->set_tunnel_enable) { + bool tunnel_enable = mode == MAX_SERDES_GMSL_TUNNEL_MODE; + + ret = ser->ops->set_tunnel_enable(ser, tunnel_enable); + if (ret) + return ret; + } + + ser->mode = mode; + + return 0; +} + +int max_ser_set_vc_remaps(struct v4l2_subdev *sd, + struct max_serdes_vc_remap *vc_remaps, + int num_vc_remaps) +{ + struct max_ser_priv *priv = sd_to_priv(sd); + struct max_ser *ser = priv->ser; + unsigned int mask = 0; + unsigned int i; + int ret; + + if (!ser->ops->set_vc_remap) + return -EOPNOTSUPP; + + if (num_vc_remaps > ser->ops->num_vc_remaps) + return -E2BIG; + + for (i = 0; i < num_vc_remaps; i++) { + ret = ser->ops->set_vc_remap(ser, i, &vc_remaps[i]); + if (ret) + return ret; + + mask |= BIT(i); + } + + ret = ser->ops->set_vc_remaps_enable(ser, mask); + if (ret) + return ret; + + for (i = 0; i < num_vc_remaps; i++) + ser->vc_remaps[i] = vc_remaps[i]; + + ser->num_vc_remaps = num_vc_remaps; + + return 0; +} + +static int max_ser_read_reg(struct i2c_adapter *adapter, u8 addr, + u16 reg, u8 *val) +{ + u8 buf[2] = { reg >> 8, reg & 0xff }; + struct i2c_msg msg[2] = { + { + .addr = addr, + .flags = 0, + .buf = buf, + .len = sizeof(buf), + }, + { + .addr = addr, + .flags = I2C_M_RD, + .buf = buf, + .len = 1, + }, + }; + int ret; + + ret = i2c_transfer(adapter, msg, ARRAY_SIZE(msg)); + if (ret < 0) + return ret; + + *val = buf[0]; + + return 0; +} + +static int max_ser_write_reg(struct i2c_adapter *adapter, u8 addr, + u16 reg, u8 val) +{ + u8 buf[3] = { reg >> 8, reg & 0xff, val }; + struct i2c_msg msg[1] = { + { + .addr = addr, + .flags = 0, + .buf = buf, + .len = sizeof(buf), + }, + }; + int ret; + + ret = i2c_transfer(adapter, msg, ARRAY_SIZE(msg)); + if (ret < 0) + return ret; + + return 0; +} + +int max_ser_reset(struct i2c_adapter *adapter, u8 addr) +{ + int ret; + u8 val; + + ret = max_ser_read_reg(adapter, addr, MAX_SER_CTRL0, &val); + if (ret) + return ret; + + val |= MAX_SER_CTRL0_RESET_ALL; + + return max_ser_write_reg(adapter, addr, MAX_SER_CTRL0, val); +} + +int max_ser_wait_for_multiple(struct i2c_adapter *adapter, u8 *addrs, + unsigned int num_addrs, u8 *current_addr) +{ + unsigned int i, j; + int ret = 0; + u8 val; + + for (i = 0; i < 10; i++) { + for (j = 0; j < num_addrs; j++) { + ret = max_ser_read_reg(adapter, addrs[j], MAX_SER_REG0, &val); + if (!ret && val) { + *current_addr = addrs[j]; + return 0; + } + + msleep(100); + } + } + + return ret ?: -ETIMEDOUT; +} + +int max_ser_wait(struct i2c_adapter *adapter, u8 addr) +{ + u8 current_addr; + + return max_ser_wait_for_multiple(adapter, &addr, 1, ¤t_addr); +} + +int max_ser_fix_tx_ids(struct i2c_adapter *adapter, u8 addr) +{ + unsigned int addr_regs[] = { + MAX_SER_CFGI_INFOFR_TR3, + MAX_SER_CFGL_SPI_TR3, + MAX_SER_CFGC_CC_TR3, + MAX_SER_CFGC_GPIO_TR3, + MAX_SER_CFGL_IIC_X_TR3, + MAX_SER_CFGL_IIC_Y_TR3, + }; + unsigned int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(addr_regs); i++) { + ret = max_ser_write_reg(adapter, addr, addr_regs[i], addr); + if (ret) + return ret; + } + + return 0; +} + +int max_ser_change_address(struct i2c_adapter *adapter, u8 addr, u8 new_addr) +{ + u8 val = FIELD_PREP(MAX_SER_REG0_DEV_ADDR, new_addr); + + return max_ser_write_reg(adapter, addr, MAX_SER_REG0, val); +} + +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("I2C_ATR"); diff --git a/drivers/media/i2c/maxim-serdes/max_ser.h b/drivers/media/i2c/maxim-serdes/max_ser.h new file mode 100644 index 000000000000..a9365be5e62d --- /dev/null +++ b/drivers/media/i2c/maxim-serdes/max_ser.h @@ -0,0 +1,147 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2025 Analog Devices Inc. + */ + +#ifndef MAX_SER_H +#define MAX_SER_H + +#include + +#include + +#include "max_serdes.h" + +#define MAX_SER_REG0 0x0 +#define MAX_SER_REG0_DEV_ADDR GENMASK(7, 1) + +#define MAX_SER_CTRL0 0x10 +#define MAX_SER_CTRL0_RESET_ALL BIT(7) + +#define MAX_SER_CFGI_INFOFR_TR3 0x7b +#define MAX_SER_CFGL_SPI_TR3 0x83 +#define MAX_SER_CFGC_CC_TR3 0x8b +#define MAX_SER_CFGC_GPIO_TR3 0x93 +#define MAX_SER_CFGL_IIC_X_TR3 0xa3 +#define MAX_SER_CFGL_IIC_Y_TR3 0xab + +struct max_ser_phy { + unsigned int index; + struct v4l2_mbus_config_mipi_csi2 mipi; + bool enabled; + bool active; +}; + +struct max_ser_pipe_mode { + unsigned int soft_bpp; + unsigned int bpp; + bool dbl8; + bool dbl10; + bool dbl12; +}; + +struct max_ser_pipe { + unsigned int index; + unsigned int phy_id; + unsigned int stream_id; + unsigned int *dts; + unsigned int num_dts; + unsigned int vcs; + struct max_ser_pipe_mode mode; + bool enabled; +}; + +struct max_ser; + +struct max_ser_ops { + unsigned int modes; + unsigned int num_pipes; + unsigned int num_dts_per_pipe; + unsigned int num_phys; + unsigned int num_i2c_xlates; + unsigned int num_vc_remaps; + + struct max_serdes_phys_configs phys_configs; + struct max_serdes_tpg_entries tpg_entries; + enum max_serdes_gmsl_mode tpg_mode; + unsigned int tpg_patterns; + +#ifdef CONFIG_VIDEO_ADV_DEBUG + int (*reg_read)(struct max_ser *ser, unsigned int reg, unsigned int *val); + int (*reg_write)(struct max_ser *ser, unsigned int reg, unsigned int val); +#endif + int (*log_status)(struct max_ser *ser); + int (*log_pipe_status)(struct max_ser *ser, struct max_ser_pipe *pipe); + int (*log_phy_status)(struct max_ser *ser, struct max_ser_phy *phy); + int (*init)(struct max_ser *ser); + int (*set_i2c_xlate)(struct max_ser *ser, unsigned int i, + struct max_serdes_i2c_xlate *i2c_xlate); + int (*set_tunnel_enable)(struct max_ser *ser, bool enable); + int (*set_tpg)(struct max_ser *ser, const struct max_serdes_tpg_entry *entry); + int (*init_phy)(struct max_ser *ser, struct max_ser_phy *phy); + int (*set_phy_active)(struct max_ser *ser, struct max_ser_phy *phy, + bool enable); + int (*set_pipe_enable)(struct max_ser *ser, struct max_ser_pipe *pipe, + bool enable); + int (*set_pipe_dt)(struct max_ser *ser, struct max_ser_pipe *pipe, + unsigned int i, unsigned int dt); + int (*set_pipe_dt_en)(struct max_ser *ser, struct max_ser_pipe *pipe, + unsigned int i, bool enable); + int (*set_pipe_vcs)(struct max_ser *ser, struct max_ser_pipe *pipe, + unsigned int vcs); + int (*set_pipe_mode)(struct max_ser *ser, struct max_ser_pipe *pipe, + struct max_ser_pipe_mode *mode); + int (*set_vc_remap)(struct max_ser *ser, unsigned int i, + struct max_serdes_vc_remap *vc_remap); + int (*set_vc_remaps_enable)(struct max_ser *ser, unsigned int mask); + int (*set_pipe_stream_id)(struct max_ser *ser, struct max_ser_pipe *pipe, + unsigned int stream_id); + unsigned int (*get_pipe_stream_id)(struct max_ser *ser, struct max_ser_pipe *pipe); + int (*set_pipe_phy)(struct max_ser *ser, struct max_ser_pipe *pipe, + struct max_ser_phy *phy); +}; + +struct max_ser_priv; + +struct max_ser { + struct max_ser_priv *priv; + + const struct max_ser_ops *ops; + + struct max_serdes_i2c_xlate *i2c_xlates; + + struct max_ser_phy *phys; + struct max_ser_pipe *pipes; + const struct max_serdes_tpg_entry *tpg_entry; + enum max_serdes_tpg_pattern tpg_pattern; + + struct max_serdes_vc_remap *vc_remaps; + unsigned int num_vc_remaps; + + unsigned int phys_config; + unsigned int active; + enum max_serdes_gmsl_mode mode; +}; + +int max_ser_probe(struct i2c_client *client, struct max_ser *ser); + +int max_ser_remove(struct max_ser *ser); + +int max_ser_set_double_bpps(struct v4l2_subdev *sd, u32 double_bpps); +unsigned int max_ser_get_supported_modes(struct v4l2_subdev *sd); +int max_ser_set_mode(struct v4l2_subdev *sd, enum max_serdes_gmsl_mode mode); +bool max_ser_supports_vc_remap(struct v4l2_subdev *sd); +int max_ser_set_stream_id(struct v4l2_subdev *sd, unsigned int stream_id); +int max_ser_get_stream_id(struct v4l2_subdev *sd, unsigned int *stream_id); +int max_ser_set_vc_remaps(struct v4l2_subdev *sd, struct max_serdes_vc_remap *vc_remaps, + int num_vc_remaps); + +int max_ser_reset(struct i2c_adapter *adapter, u8 addr); +int max_ser_wait(struct i2c_adapter *adapter, u8 addr); +int max_ser_wait_for_multiple(struct i2c_adapter *adapter, u8 *addrs, + unsigned int num_addrs, u8 *current_addr); + +int max_ser_change_address(struct i2c_adapter *adapter, u8 addr, u8 new_addr); +int max_ser_fix_tx_ids(struct i2c_adapter *adapter, u8 addr); + +#endif // MAX_SER_H diff --git a/drivers/media/i2c/maxim-serdes/max_serdes.c b/drivers/media/i2c/maxim-serdes/max_serdes.c new file mode 100644 index 000000000000..7d013c4739d1 --- /dev/null +++ b/drivers/media/i2c/maxim-serdes/max_serdes.c @@ -0,0 +1,413 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2025 Analog Devices Inc. + */ + +#include +#include +#include +#include + +#include + +#include