diff options
| author | Mark Brown <broonie@kernel.org> | 2026-09-30 13:10:28 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-09-30 13:10:28 +0100 |
| commit | f68b7d2660db090105778a2899dd9edeb94bf51c (patch) | |
| tree | c9483418b6cbc27f7474720e3a6dec70e5db0fb8 | |
| parent | fb3b8808dfbf9b41df01e90c68c40a9757cb7694 (diff) | |
| parent | 58348f64125e9a3e44d3abb275ca7f4e6c9641e5 (diff) | |
| download | linux-next-f68b7d2660db090105778a2899dd9edeb94bf51c.tar.gz linux-next-f68b7d2660db090105778a2899dd9edeb94bf51c.zip | |
Merge branch 'next' of git://linuxtv.org/media-ci/media-pending.git
499 files changed, 20194 insertions, 4278 deletions
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 <dt-bindings/clock/nxp,imx95-clock.h> + + 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/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 <guoniu.zhou@nxp.com> + +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 <dt-bindings/clock/nxp,imx95-clock.h> + + 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 <matthias.fend@emfend.at> +%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 <matthias.fend@emfend.at> + +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 <dt-bindings/gpio/gpio.h> + #include <dt-bindings/media/video-interfaces.h> + #include <dt-bindings/media/video-interface-devices.h> + + 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 = <MEDIA_ORIENTATION_EXTERNAL>; + 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 <dt-bindings/gpio/gpio.h> + #include <dt-bindings/media/video-interface-devices.h> 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 = <MEDIA_ORIENTATION_FRONT>; 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..756fe655f534 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/ite,it6625.yaml @@ -0,0 +1,175 @@ +# 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 <Hermes.wu@ite.com.tw> + +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 + + 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 + + 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 <dt-bindings/gpio/gpio.h> + #include <dt-bindings/interrupt-controller/irq.h> + + 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/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 <tarang.raval@siliconsignals.io> + +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 <dt-bindings/gpio/gpio.h> + + 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 <dt-bindings/gpio/gpio.h> #include <dt-bindings/media/video-interfaces.h> + #include <dt-bindings/media/video-interface-devices.h> i2c { #address-cells = <1>; @@ -84,7 +85,7 @@ examples: avdd-supply = <&ov08d10_vdda_2v8>; dvdd-supply = <&ov08d10_vddd_1v2>; - orientation = <2>; + orientation = <MEDIA_ORIENTATION_EXTERNAL>; 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 <dt-bindings/gpio/gpio.h> + #include <dt-bindings/media/video-interface-devices.h> 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 = <MEDIA_ORIENTATION_EXTERNAL>; 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 <dt-bindings/clock/px30-cru.h> #include <dt-bindings/gpio/gpio.h> + #include <dt-bindings/media/video-interface-devices.h> #include <dt-bindings/pinctrl/rockchip.h> i2c { @@ -108,7 +109,7 @@ examples: dovdd-supply = <&vcc_2v8>; rotation = <90>; - orientation = <0>; + orientation = <MEDIA_ORIENTATION_FRONT>; 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 <dt-bindings/clock/px30-cru.h> #include <dt-bindings/gpio/gpio.h> + #include <dt-bindings/media/video-interface-devices.h> #include <dt-bindings/pinctrl/rockchip.h> i2c { @@ -126,7 +129,7 @@ examples: dovdd-supply = <&vcc_2v8>; rotation = <90>; - orientation = <0>; + orientation = <MEDIA_ORIENTATION_FRONT>; 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 <dt-bindings/gpio/gpio.h> + #include <dt-bindings/media/video-interface-devices.h> 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 = <MEDIA_ORIENTATION_EXTERNAL>; port { endpoint { 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 <dt-bindings/gpio/gpio.h> #include <dt-bindings/media/video-interfaces.h> + #include <dt-bindings/media/video-interface-devices.h> i2c { #address-cells = <1>; @@ -84,7 +85,7 @@ examples: dvdd-supply = <&camera_vddd_1v2>; avdd-supply = <&camera_vdda_2v7>; - orientation = <1>; + orientation = <MEDIA_ORIENTATION_BACK>; 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 <dt-bindings/clock/qcom,camcc-sdm845.h> #include <dt-bindings/gpio/gpio.h> + #include <dt-bindings/media/video-interface-devices.h> i2c { #address-cells = <1>; @@ -105,7 +106,7 @@ examples: pinctrl-0 = <&cam_front_default>; rotation = <270>; - orientation = <0>; + orientation = <MEDIA_ORIENTATION_FRONT>; 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 <dt-bindings/gpio/gpio.h> + #include <dt-bindings/media/video-interface-devices.h> i2c { #address-cells = <1>; @@ -98,7 +99,7 @@ examples: clocks = <&clock_cam>; dvdd-supply = <&vcc1v1_cam>; lens-focus = <&vcm>; - orientation = <2>; + orientation = <MEDIA_ORIENTATION_EXTERNAL>; ovdd-supply = <&vcc1v8_cam>; reset-gpios = <&gpio_expander 14 GPIO_ACTIVE_LOW>; rotation = <180>; 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 <dt-bindings/gpio/gpio.h> + #include <dt-bindings/media/video-interface-devices.h> i2c { #address-cells = <1>; @@ -124,7 +125,7 @@ examples: reset-gpios = <&gpio 5 GPIO_ACTIVE_LOW>; st,leds = <2>; - orientation = <2>; + orientation = <MEDIA_ORIENTATION_EXTERNAL>; 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 <dt-bindings/gpio/gpio.h> + #include <dt-bindings/media/video-interface-devices.h> i2c { #address-cells = <1>; @@ -125,7 +126,7 @@ examples: reset-gpios = <&gpio 5 GPIO_ACTIVE_LOW>; st,leds = <6>; - orientation = <2>; + orientation = <MEDIA_ORIENTATION_EXTERNAL>; 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 <dt-bindings/gpio/gpio.h> #include <dt-bindings/media/video-interfaces.h> + #include <dt-bindings/media/video-interface-devices.h> i2c { #address-cells = <1>; @@ -196,7 +197,7 @@ examples: vddgpio-0-supply = <&vsys_v4p2>; vddgpio-1-supply = <&vsys_v4p2>; - orientation = <0>; + orientation = <MEDIA_ORIENTATION_FRONT>; 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 <hverkuil@kernel.org> + +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 <dt-bindings/gpio/gpio.h> + #include <dt-bindings/interrupt-controller/irq.h> + + 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/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/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/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/MAINTAINERS b/MAINTAINERS index fafe90d6ca4d..c33ef2191a13 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11772,6 +11772,13 @@ W: http://www.highpoint-tech.com F: Documentation/scsi/hptiop.rst F: drivers/scsi/hptiop.c +HIMAX HM1246 SENSOR DRIVER +M: Matthias Fend <matthias.fend@emfend.at> +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 <job@noorman.info> L: linux-input@vger.kernel.org @@ -13081,6 +13088,8 @@ F: drivers/scsi/isci/ INTEL COMPUTER VISION SENSING (CVS) DRIVER M: Miguel Vadillo <miguel.vadillo@intel.com> +M: Sakari Ailus <sakari.ailus@linux.intel.com> +R: Antti Laakso <antti.laakso@linux.intel.com> L: linux-media@vger.kernel.org S: Maintained F: drivers/media/i2c/cvs/ @@ -13268,6 +13277,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 <sakari.ailus@linux.intel.com> +M: Dan Scally <dan.scally@ideasonboard.com> +R: Hans de Goede <hansg@kernel.org> +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 <yong.zhi@intel.com> M: Sakari Ailus <sakari.ailus@linux.intel.com> @@ -13289,6 +13307,8 @@ F: drivers/staging/media/ipu3/ INTEL IPU6 INPUT SYSTEM DRIVER M: Sakari Ailus <sakari.ailus@linux.intel.com> +R: Antti Laakso <antti.laakso@linux.intel.com> +R: "Sapre, Sarang" <sarang.sapre@intel.com> L: linux-media@vger.kernel.org S: Maintained T: git git://linuxtv.org/media.git @@ -13525,6 +13545,7 @@ K: \bSGX_ INTEL SKYLAKE INT3472 ACPI DEVICE DRIVER M: Daniel Scally <dan.scally@ideasonboard.com> M: Sakari Ailus <sakari.ailus@linux.intel.com> +L: linux-media@vger.kernel.org S: Maintained T: git git://linuxtv.org/media.git F: drivers/platform/x86/intel/int3472/ @@ -13991,6 +14012,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 <Hermes.Wu@ite.com.tw> +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 <awalls@md.metrocast.net> L: linux-media@vger.kernel.org @@ -16468,6 +16497,7 @@ M: Frank Li <Frank.Li@nxp.com> M: Martin Kepplinger-Novakovic <martink@posteo.de> R: Rui Miguel Silva <rmfrfs@gmail.com> R: Purism Kernel Team <kernel@puri.sm> +R: Bryan O'Donoghue <bod@kernel.org> L: imx@lists.linux.dev L: linux-media@vger.kernel.org S: Maintained @@ -19822,6 +19852,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 <guoniu.zhou@nxp.com> +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 <abelvesa@kernel.org> R: Peng Fan <peng.fan@nxp.com> @@ -20212,6 +20250,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 <tarang.raval@siliconsignals.io> +M: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io> +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 <himanshu.bhavani@siliconsignals.io> M: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io> @@ -26669,6 +26715,7 @@ F: include/linux/soc/amd/isp4_misc.h SYNOPSYS DESIGNWARE MIPI CSI-2 RECEIVER DRIVER M: Michael Riesch <michael.riesch@collabora.com> +R: Bryan O'Donoghue <bod@kernel.org> L: linux-media@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml @@ -27048,9 +27095,11 @@ M: Luca Ceresoli <luca.ceresoli@bootlin.com> 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 @@ -27780,7 +27829,7 @@ TOSHIBA TC358743 DRIVER M: Hans Verkuil <hverkuil@kernel.org> 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 <dt-bindings/input/gpio-keys.h> #include <dt-bindings/input/input.h> #include <dt-bindings/media/video-interfaces.h> +#include <dt-bindings/media/video-interface-devices.h> #include <dt-bindings/power/summit,smb347-charger.h> #include <dt-bindings/thermal/thermal.h> @@ -991,7 +992,7 @@ vdd-supply = <&vddio_cam>; vaa-supply = <&avdd_cam1>; - orientation = <0>; /* Front camera */ + orientation = <MEDIA_ORIENTATION_FRONT>; 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 <dt-bindings/input/gpio-keys.h> #include <dt-bindings/input/input.h> #include <dt-bindings/media/video-interfaces.h> +#include <dt-bindings/media/video-interface-devices.h> #include <dt-bindings/thermal/thermal.h> #include "tegra30.dtsi" @@ -1262,7 +1263,7 @@ vdd-supply = <&vdd_1v8_cam>; vaa-supply = <&avdd_2v85_fcam>; - orientation = <0>; /* Front camera */ + orientation = <MEDIA_ORIENTATION_FRONT>; 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 896639599c12..28680063bcc0 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 <dt-bindings/media/video-interface-devices.h> + #include "tegra30-lg-x3.dtsi" / { @@ -132,7 +134,7 @@ vdd-supply = <&vt_1v8_front>; vaa-supply = <&vt_2v8_front>; - orientation = <0>; /* Front camera */ + orientation = <MEDIA_ORIENTATION_FRONT>; 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 60e8a19aa70e..77cab695d3da 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 <dt-bindings/input/input.h> #include <dt-bindings/leds/common.h> #include <dt-bindings/media/video-interfaces.h> +#include <dt-bindings/media/video-interface-devices.h> #include <dt-bindings/mfd/max77620.h> #include <dt-bindings/thermal/thermal.h> @@ -1216,7 +1217,7 @@ dvdd-supply = <&vdd_1v2_rear>; avdd-supply = <&vdd_2v7_rear>; - orientation = <1>; /* Rear camera */ + orientation = <MEDIA_ORIENTATION_BACK>; 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 <dt-bindings/gpio/gpio.h> #include <dt-bindings/media/video-interfaces.h> +#include <dt-bindings/media/video-interface-devices.h> #include "imx8mp-pinfunc.h" @@ -47,7 +48,7 @@ VANA-supply = <®_cam>; VDIG-supply = <®_cam>; VDDL-supply = <®_cam>; - orientation = <2>; + orientation = <MEDIA_ORIENTATION_EXTERNAL>; 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 <dt-bindings/interrupt-controller/irq.h> #include <dt-bindings/leds/common.h> +#include <dt-bindings/media/video-interface-devices.h> #include "dt-bindings/pwm/pwm.h" #include "dt-bindings/usb/pd.h" #include "imx8mq.dtsi" @@ -1116,7 +1117,7 @@ vddd-supply = <®_vcam_1v2>; vddio-supply = <®_csi_1v8>; rotation = <90>; - orientation = <0>; + orientation = <MEDIA_ORIENTATION_FRONT>; 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 <dt-bindings/iio/qcom,spmi-adc7-pmk8350.h> #include <dt-bindings/leds/common.h> #include <dt-bindings/media/video-interfaces.h> +#include <dt-bindings/media/video-interface-devices.h> #include <dt-bindings/pinctrl/qcom,pmic-gpio.h> #include <dt-bindings/regulator/qcom,rpmh-regulator.h> #include <dt-bindings/sound/qcom,q6asm.h> @@ -755,7 +756,7 @@ pinctrl-0 = <&cam_mclk3_default>; pinctrl-names = "default"; - orientation = <0>; /* Front facing */ + orientation = <MEDIA_ORIENTATION_FRONT>; 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 <dt-bindings/input/gpio-keys.h> #include <dt-bindings/input/input.h> #include <dt-bindings/leds/common.h> +#include <dt-bindings/media/video-interface-devices.h> #include <dt-bindings/regulator/qcom,rpmh-regulator.h> #include "sc8280xp.dtsi" @@ -683,7 +684,7 @@ clocks = <&camcc CAMCC_MCLK3_CLK>; - orientation = <0>; /* Front facing */ + orientation = <MEDIA_ORIENTATION_FRONT>; 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 <dt-bindings/gpio/gpio.h> #include <dt-bindings/input/input.h> #include <dt-bindings/leds/common.h> +#include <dt-bindings/media/video-interface-devices.h> #include <dt-bindings/pinctrl/qcom,pmic-gpio.h> #include <dt-bindings/power/qcom-rpmpd.h> #include "sdm670.dtsi" @@ -462,7 +463,7 @@ pinctrl-names = "default"; rotation = <270>; - orientation = <0>; + orientation = <MEDIA_ORIENTATION_FRONT>; 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 <dt-bindings/gpio/gpio.h> #include <dt-bindings/media/video-interfaces.h> +#include <dt-bindings/media/video-interface-devices.h> &{/} { clk_cam_j1: clk-cam-j1 { @@ -44,7 +45,7 @@ VDIG-supply = <®_cam_j1>; VDDL-supply = <®_cam_j1>; - orientation = <2>; + orientation = <MEDIA_ORIENTATION_EXTERNAL>; 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 <dt-bindings/gpio/gpio.h> #include <dt-bindings/media/video-interfaces.h> +#include <dt-bindings/media/video-interface-devices.h> &{/} { clk_cam_j1: clk-cam-j1 { @@ -46,7 +47,7 @@ vdda-supply = <®_cam_j1>; vddd-supply = <®_cam_j1>; - orientation = <2>; + orientation = <MEDIA_ORIENTATION_EXTERNAL>; 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 <dt-bindings/gpio/gpio.h> #include <dt-bindings/media/video-interfaces.h> +#include <dt-bindings/media/video-interface-devices.h> &{/} { clk_cam_j2: clk-cam-j2 { @@ -44,7 +45,7 @@ VDIG-supply = <®_cam_j2>; VDDL-supply = <®_cam_j2>; - orientation = <2>; + orientation = <MEDIA_ORIENTATION_EXTERNAL>; 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 <dt-bindings/gpio/gpio.h> #include <dt-bindings/media/video-interfaces.h> +#include <dt-bindings/media/video-interface-devices.h> &{/} { clk_cam_j2: clk-cam-j2 { @@ -46,7 +47,7 @@ vdda-supply = <®_cam_j2>; vddd-supply = <®_cam_j2>; - orientation = <2>; + orientation = <MEDIA_ORIENTATION_EXTERNAL>; 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 <dt-bindings/gpio/gpio.h> #include <dt-bindings/input/input.h> +#include <dt-bindings/media/video-interface-devices.h> #include <dt-bindings/pinctrl/rockchip.h> #include "px30.dtsi" @@ -413,7 +414,7 @@ dvdd-supply = <&vcc_cam_dvdd>; dovdd-supply = <&vcc_cam_dovdd>; lens-focus = <&focus>; - orientation = <0>; + orientation = <MEDIA_ORIENTATION_FRONT>; 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 <dt-bindings/gpio/gpio.h> #include <dt-bindings/interrupt-controller/irq.h> #include <dt-bindings/leds/common.h> +#include <dt-bindings/media/video-interface-devices.h> #include <dt-bindings/pinctrl/rockchip.h> &{/} { @@ -185,7 +186,7 @@ dvdd-supply = <&cam_dvdd_1v2>; dovdd-supply = <&cam_dovdd_1v8>; lens-focus = <&focus>; - orientation = <0>; + orientation = <MEDIA_ORIENTATION_FRONT>; 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 <dt-bindings/input/gpio-keys.h> #include <dt-bindings/input/linux-event-codes.h> #include <dt-bindings/leds/common.h> +#include <dt-bindings/media/video-interface-devices.h> #include "rk3399-s.dtsi" / { @@ -455,7 +456,7 @@ reg = <0x1a>; clocks = <&cru SCLK_CIF_OUT>; /* MIPI_MCLK0, derived from CIF_CLKO */ lens-focus = <&wcam_lens>; - orientation = <1>; /* V4L2_CAMERA_ORIENTATION_BACK */ + orientation = <MEDIA_ORIENTATION_BACK>; pinctrl-names = "default"; pinctrl-0 = <&camera_rst_l>; reset-gpios = <&gpio1 RK_PA0 GPIO_ACTIVE_LOW>; @@ -487,7 +488,7 @@ 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 = <MEDIA_ORIENTATION_FRONT>; 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 <dt-bindings/clock/rockchip,rk3588-cru.h> #include <dt-bindings/gpio/gpio.h> #include <dt-bindings/media/video-interfaces.h> +#include <dt-bindings/media/video-interface-devices.h> #include <dt-bindings/pinctrl/rockchip.h> &{/} { @@ -51,7 +52,7 @@ avdd-supply = <&savdd_cam0>; clocks = <&cru CLK_MIPI_CAMARAOUT_M3>; dvdd-supply = <&sdvdd_cam0>; - orientation = <2>; /* External */ + orientation = <MEDIA_ORIENTATION_EXTERNAL>; 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 <dt-bindings/clock/rockchip,rk3588-cru.h> #include <dt-bindings/gpio/gpio.h> #include <dt-bindings/media/video-interfaces.h> +#include <dt-bindings/media/video-interface-devices.h> #include <dt-bindings/pinctrl/rockchip.h> &{/} { @@ -51,7 +52,7 @@ avdd-supply = <&savdd_cam1>; clocks = <&cru CLK_MIPI_CAMARAOUT_M4>; dvdd-supply = <&sdvdd_cam1>; - orientation = <2>; /* External */ + orientation = <MEDIA_ORIENTATION_EXTERNAL>; ovdd-supply = <&siovdd_cam1>; pinctrl-names = "default"; pinctrl-0 = <&cam1_rstn &mipim0_camera4_clk>; 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..b705c4bcaffa 100644 --- a/drivers/media/common/saa7146/saa7146_core.c +++ b/drivers/media/common/saa7146/saa7146_core.c @@ -252,6 +252,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 +349,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 +436,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..9b28e91b8a67 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 @@ -396,6 +407,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 @@ -989,14 +1010,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 +1178,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 +1194,6 @@ config VIDEO_SAA6588 endmenu menu "Video decoders" - visible if !MEDIA_HIDE_ANCILLARY_SUBDRV config VIDEO_ADV7180 tristate "Analog Devices ADV7180 decoder" @@ -1301,6 +1318,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 +1553,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 +1651,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 +1679,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 +1694,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 +1710,6 @@ config SDR_MAX2175 endmenu menu "Miscellaneous helper chips" - visible if !MEDIA_HIDE_ANCILLARY_SUBDRV source "drivers/media/i2c/cvs/Kconfig" diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index d04bd5724552..fd1cb25718c0 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 @@ -65,6 +66,7 @@ obj-$(CONFIG_VIDEO_IMX678) += imx678.o obj-$(CONFIG_VIDEO_IMX471) += imx471.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 @@ -86,6 +88,7 @@ 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 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 <linux/kernel.h> 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 <linux/kernel.h> 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 @@ err_cleanup_clients: 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 <linux/workqueue.h> #include <media/ipu-bridge.h> -#include <media/ipu6-pci-table.h> #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 <media/v4l2-async.h> #include <media/v4l2-common.h> #include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> #include <media/v4l2-event.h> #include <media/v4l2-fwnode.h> #include <media/v4l2-mc.h> @@ -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 @@ err_rpm_put: 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; - - /* add additional settings */ - cx25840_vconfig_add(state, val); - } else { - /* TODO: generic mode needs to be developed for other chips */ - WARN_ON(1); - } + /* 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); 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 @@ out_unlock: } 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 <matthias.fend@emfend.at> + */ + +#include <linux/array_size.h> +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/limits.h> +#include <linux/math.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/property.h> +#include <linux/regulator/consumer.h> +#include <linux/reset.h> +#include <linux/types.h> +#include <linux/units.h> +#include <media/media-entity.h> +#include <media/v4l2-async.h> +#include <media/v4l2-cci.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-subdev.h> + +/* 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 <matthias.fend@emfend.at>"); +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 @@ out: } 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..03173197f332 100644 --- a/drivers/media/i2c/imx471.c +++ b/drivers/media/i2c/imx471.c @@ -41,7 +41,7 @@ /* 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 @@ -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/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 @@ out: } 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..25f9e61a7b03 --- /dev/null +++ b/drivers/media/i2c/it6625.c @@ -0,0 +1,2391 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * it6625 - ite HDMI to MIPI bridge + */ +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/completion.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/hdmi.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_graph.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/timer.h> +#include <linux/v4l2-dv-timings.h> +#include <linux/videodev2.h> +#include <linux/workqueue.h> + +#include <media/cec.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-dv-timings.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fwnode.h> +#include <uapi/linux/it6625.h> + +static int debug = 3; +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; + + /* protects concurrent access to the chip's registers and state */ + 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 v4l2_mbus_config_mipi_csi2 bus; + 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; + u8 csi_format; + u32 mbus_fmt_code; + /* 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; +}; + +/* + * Index 0: D-PHY (4-lane). Index 1: C-PHY (3-trio) -- the confirmed + * hardware max C-PHY capability, tested single-port/three-trio. + */ +static const s64 it6625_link_freq[] = { + 445500000, + 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) +{ + int i; + + for (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) +{ + int i; + + for (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) +{ + unsigned int val; + int err; + struct device *dev = it6625->dev; + + 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) +{ + int err; + struct device *dev = it6625->dev; + + 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) +{ + int err; + struct device *dev = it6625->dev; + + 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) +{ + int err; + struct device *dev = it6625->dev; + + 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) +{ + int err; + struct device *dev = it6625->dev; + + 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 * 1000, + timeout_round_ms * 1000, + false, it6625, reg); + + dev_info(dev, "%s status = %d %d", __func__, status, (int)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) +{ + int fs_id; + int i, freq = 0; + 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 }, + }; + + 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) +{ + u32 pclk; + u8 ck[4]; + int ret; + + ret = it6625_read_bytes(it6625, REG_VID_PCLK, ck, 4); + if (ret < 0) { + dev_err(it6625->dev, "failed to read pixel clock"); + return 0; + } + + pclk = ck[0]; + pclk <<= 8; + pclk |= ck[1]; + pclk <<= 8; + pclk |= ck[2]; + pclk <<= 8; + pclk |= ck[3]; + + v4l2_dbg(1, debug, &it6625->sd, "%s: pclk=%u (%08x)", + __func__, pclk, pclk); + + return (u64)pclk * 1000; +} + +static int it6625_read_edid(struct it6625 *it6625, u8 *edid, int start_block, + int num_blocks) +{ + int i, bank_ctrl, err = 0; + struct device *dev = it6625->dev; + + 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) +{ + int i, bank_ctrl, err = 0; + struct device *dev = it6625->dev; + + 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); +} + +static int it6625_get_detected_timings(struct it6625 *it6625, + struct v4l2_dv_timings *timings) +{ + struct v4l2_bt_timings *bt = &timings->bt; + int val; + unsigned int width, height; + u8 buffer[4]; + u8 buffer2[12]; + + 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, buffer, 4) < 0) + return -EIO; + + width = ((buffer[0] & 0xff) << 8) + buffer[1]; + height = ((buffer[2] & 0xff) << 8) + buffer[3]; + + bt->width = width; + bt->height = height; + + if (it6625_read_bytes(it6625, REG_H_FP_1, buffer2, 12) < 0) + return -EIO; + + bt->hfrontporch = ((buffer2[0] & 0xff) << 8) + buffer2[1]; + bt->hsync = ((buffer2[2] & 0xff) << 8) + buffer2[3]; + bt->hbackporch = ((buffer2[4] & 0xff) << 8) + buffer2[5]; + bt->vfrontporch = ((buffer2[6] & 0xff) << 8) + buffer2[7]; + bt->vsync = ((buffer2[8] & 0xff) << 8) + buffer2[9]; + bt->vbackporch = ((buffer2[10] & 0xff) << 8) + buffer2[11]; + + 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 void it6625_v4l2_sd_ctrl_update(struct v4l2_subdev *sd) +{ + it6625_s_ctrl_detect_hdmi_5v(sd); + it6625_s_ctrl_audio_sampling_rate(sd); + it6625_s_ctrl_audio_present(sd); +} + +static void it6625_enable_stream_locked(struct it6625 *it6625, bool enable) +{ + struct v4l2_subdev *sd = &it6625->sd; + int val; + + lockdep_assert_held(&it6625->it6625_lock); + + v4l2_dbg(3, debug, sd, "%s: %sable", + __func__, enable ? "en" : "dis"); + + val = enable ? B_MIPI_OUTPUT : 0; + it6625_set_bits(it6625, REG_MIPI_CONTROL, B_MIPI_OUTPUT, val); + it6625_update_config(it6625); +} + +static void it6625_enable_stream(struct it6625 *it6625, bool enable) +{ + guard(mutex)(&it6625->it6625_lock); + it6625_enable_stream_locked(it6625, enable); +} + +static void it6625_set_mipi_config_locked(struct it6625 *it6625, u32 cfg_val) +{ + u8 mipi_data_type; + + lockdep_assert_held(&it6625->it6625_lock); + + dev_dbg(it6625->dev, "mipi_data_type = 0x%x", cfg_val); + + mipi_data_type = cfg_val & 0xFF; + it6625_write_byte(it6625, REG_MIPI_DATA_TYPE, mipi_data_type); + 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 void it6625_initial_setup(struct it6625 *it6625) +{ + int val = 0; + + 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); + + it6625_write_byte(it6625, REG_MIPI_CFG, val); + it6625_write_byte(it6625, REG_MIPI_DATA_TYPE, it6625->csi_format); + it6625_write_byte(it6625, REG_MIPI_CONTROL, 0x00); + it6625_write_byte(it6625, REG_RX_CFG, 0x00); + + it6625_set_bits(it6625, REG_HOST_CTRL_INT, B_CONFIG_UPDATE, B_CONFIG_UPDATE); + 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]; + + cmds[0] = CMD_SET_CEC_ENABLE; + cmds[1] = 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; +} + +static void it6625_clear_timings(struct it6625 *it6625) +{ + guard(mutex)(&it6625->it6625_lock); + memset(&it6625->timings, 0, sizeof(it6625->timings)); +} + +static void it6625_irq_hdmi_5v_change(struct it6625 *it6625) +{ + struct v4l2_subdev *sd = &it6625->sd; + + it6625_clear_timings(it6625); + it6625_v4l2_sd_ctrl_update(sd); +} + +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 const char *it6625_csi_format_name(u8 csi_format) +{ + switch (csi_format) { + case CSI_YUV422_8b: + return "YUV422 8bit"; + case CSI_RGB888: + return "RGB888 8bit"; + case CSI_YUV444_8b: + return "YUV444 8bit"; + default: + return "unknown"; + } +} + +static int it6625_log_status(struct v4l2_subdev *sd) +{ + struct it6625 *it6625 = sd_to_6625(sd); + struct v4l2_dv_timings timings, configured_timings; + struct v4l2_bt_timings bt; + u8 csi_format; + + 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); + + /* snapshot together so the reported pair was actually configured together */ + scoped_guard(mutex, &it6625->it6625_lock) { + csi_format = it6625->csi_format; + bt = it6625->timings.bt; + } + + v4l2_info(sd, "CSI format: %s @ %uHz", + it6625_csi_format_name(csi_format), + 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) +{ + int ret; + + 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; + ret = 1; + } + + 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_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + it6625_enable_stream(it6625, enable); + return 0; +} + +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_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct it6625 *it6625 = sd_to_6625(sd); + struct v4l2_dv_timings timings; + + if (format->pad != 0) + return -EINVAL; + + it6625_get_timings(it6625, &timings); + format->format.width = timings.bt.width; + format->format.height = timings.bt.height; + format->format.field = timings.bt.interlaced == V4L2_DV_INTERLACED ? + V4L2_FIELD_INTERLACED : V4L2_FIELD_NONE; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_state_get_format(sd_state, format->pad); + format->format.code = fmt->code; + format->format.colorspace = fmt->colorspace; + } else { + scoped_guard(mutex, &it6625->it6625_lock) { + format->format.colorspace = + format_to_colorspace(it6625->csi_format); + format->format.code = it6625->mbus_fmt_code; + } + } + + 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); + int ret; + u32 mbus_fmt_code = format->format.code; + + ret = it6625_get_fmt(sd, sd_state, format); + format->format.code = mbus_fmt_code; + + if (ret) + return ret; + + ret = it6625_csi_mbus_code_idx(mbus_fmt_code); + + if (ret < 0) { + v4l2_dbg(1, debug, sd, + "%s: unsupported format code 0x%x, falling back to default", + __func__, mbus_fmt_code); + ret = 0; + mbus_fmt_code = it6625_formats[ret].mbus_fmt_code; + format->format.code = mbus_fmt_code; + } + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_state_get_format(sd_state, format->pad); + fmt->code = format->format.code; + fmt->colorspace = format_to_colorspace(it6625_formats[ret].csi_format); + format->format.colorspace = fmt->colorspace; + v4l2_dbg(1, debug, sd, "%s: try format code = 0x%x", + __func__, format->format.code); + return 0; + } + + scoped_guard(mutex, &it6625->it6625_lock) { + it6625->csi_format = it6625_formats[ret].csi_format; + it6625->mbus_fmt_code = format->format.code; + it6625_enable_stream_locked(it6625, false); + it6625_set_mipi_config_locked(it6625, it6625->csi_format); + } + + format->format.colorspace = format_to_colorspace(it6625_formats[ret].csi_format); + + 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); + int err; + u16 parent_pa = CEC_PHYS_ADDR_INVALID; + + 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 = it6625_s_stream, +}; + +static const struct v4l2_subdev_pad_ops it6625_pad_ops = { + .enum_mbus_code = it6625_enum_mbus_code, + .set_fmt = it6625_set_fmt, + .get_fmt = it6625_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, +}; + +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 v4l2_mbus_framefmt *fmt = v4l2_subdev_state_get_format(sd_state, 0); + + 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; + + 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, + ARRAY_SIZE(it6625_link_freq) - 1, + it6625->bus_type == V4L2_MBUS_CSI2_CPHY ? 1 : 0, + it6625_link_freq); + 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) +{ + int i; + + seq_puts(s, " 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F\n"); + + for (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->csi_lanes = 4; + it6625->port_num = 1; + it6625->bus_type = V4L2_MBUS_CSI2_DPHY; + it6625->csi_format = it6625_formats[0].csi_format; + it6625->mbus_fmt_code = it6625_formats[0].mbus_fmt_code; + 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); + } + + if (!ep) { + it6625->port_num = 1; + dev_dbg(dev, "no CSI-2 endpoint node found, using default %u CSI lanes", + it6625->csi_lanes); + return 0; + } + + ret = v4l2_fwnode_endpoint_alloc_parse(of_fwnode_handle(ep), &endpoint); + of_node_put(ep); + if (ret) { + dev_err(dev, "failed to parse endpoint: %d", ret); + return ret; + } + + 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); + v4l2_fwnode_endpoint_free(&endpoint); + return -EINVAL; + } + + 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"); + v4l2_fwnode_endpoint_free(&endpoint); + return -EINVAL; + } + + 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); + v4l2_fwnode_endpoint_free(&endpoint); + return -EINVAL; + } + + it6625->csi_lanes = endpoint.bus.mipi_csi2.num_data_lanes; + it6625->bus_type = endpoint.bus_type; + v4l2_fwnode_endpoint_free(&endpoint); + + return 0; +} + +static int it6625_parse_dt(struct it6625 *it6625) +{ + return it6625_parse_endpoint(it6625); +} + +static int it6625_init_v4l2_subdev(struct it6625 *it6625) +{ + struct v4l2_subdev *sd = &it6625->sd; + int err; + + sd->dev = it6625->dev; + + 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; + if (it6625_v4l2_init_controls(sd)) { + dev_err(it6625->dev, "Failed to initialize v4l2 controls"); + return -ENOMEM; + } + + 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); + v4l2_ctrl_handler_free(sd->ctrl_handler); + return err; + } + + return 0; +} + +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; + err = it6625_init_v4l2_subdev(it6625); + if (err) + goto err_clean_work_queues; + + err = v4l2_ctrl_handler_setup(sd->ctrl_handler); + if (err) + goto err_clean_hdl; + + 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_hdl; + } + + 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_hdl; + } + + it6625_debugfs_init(it6625, client); + + it6625_initial_setup(it6625); + it6625_v4l2_sd_ctrl_update(sd); + + 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_hdl: + media_entity_cleanup(&sd->entity); + 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); + mutex_destroy(&it6625->it6625_lock); + mutex_destroy(&it6625->edid_lock); + mutex_destroy(&it6625->if_read_lock); + mutex_destroy(&it6625->if_state_lock); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(&it6625->hdl); +} + +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 <Hermes.wu@ite.com.tw>"); +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 index e3e625e6f11a..0c2ff62a77ea 100644 --- a/drivers/media/i2c/max96714.c +++ b/drivers/media/i2c/max96714.c @@ -327,6 +327,7 @@ static int max96714_disable_streams(struct v4l2_subdev *sd, } static int max96714_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/max96717.c b/drivers/media/i2c/max96717.c index 72f021b1a7b9..ff1df1d84049 100644 --- a/drivers/media/i2c/max96717.c +++ b/drivers/media/i2c/max96717.c @@ -414,6 +414,7 @@ static int max96717_set_routing(struct v4l2_subdev *sd, } static int max96717_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/ml86v7667.c b/drivers/media/i2c/ml86v7667.c index 48b7d589df31..eda41a80d20c 100644 --- a/drivers/media/i2c/ml86v7667.c +++ b/drivers/media/i2c/ml86v7667.c @@ -298,7 +298,6 @@ static const struct v4l2_subdev_video_ops ml86v7667_subdev_video_ops = { static const struct v4l2_subdev_pad_ops ml86v7667_subdev_pad_ops = { .enum_mbus_code = ml86v7667_enum_mbus_code, .get_fmt = ml86v7667_fill_fmt, - .set_fmt = ml86v7667_fill_fmt, .get_mbus_config = ml86v7667_get_mbus_config, }; diff --git a/drivers/media/i2c/mt9m001.c b/drivers/media/i2c/mt9m001.c index 0ade967b357b..fe46ea65550a 100644 --- a/drivers/media/i2c/mt9m001.c +++ b/drivers/media/i2c/mt9m001.c @@ -248,6 +248,7 @@ unlock: } static int mt9m001_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -289,6 +290,7 @@ static int mt9m001_set_selection(struct v4l2_subdev *sd, } static int mt9m001_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -343,6 +345,8 @@ static int mt9m001_get_fmt(struct v4l2_subdev *sd, } static int mt9m001_s_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, + struct v4l2_subdev_state *state, const struct mt9m001_datafmt *fmt, struct v4l2_mbus_framefmt *mf) { @@ -359,7 +363,7 @@ static int mt9m001_s_fmt(struct v4l2_subdev *sd, int ret; /* No support for scaling so far, just crop. TODO: use skipping */ - ret = mt9m001_set_selection(sd, NULL, &sel); + ret = mt9m001_set_selection(sd, ci, state, &sel); if (!ret) { mf->width = mt9m001->rect.width; mf->height = mt9m001->rect.height; @@ -371,6 +375,7 @@ static int mt9m001_s_fmt(struct v4l2_subdev *sd, } static int mt9m001_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -404,7 +409,7 @@ static int mt9m001_set_fmt(struct v4l2_subdev *sd, mf->xfer_func = V4L2_XFER_FUNC_DEFAULT; if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) - return mt9m001_s_fmt(sd, fmt, mf); + return mt9m001_s_fmt(sd, ci, sd_state, fmt, mf); *v4l2_subdev_state_get_format(sd_state, 0) = *mf; return 0; } diff --git a/drivers/media/i2c/mt9m111.c b/drivers/media/i2c/mt9m111.c index 4e748080b798..f911c1cb43e1 100644 --- a/drivers/media/i2c/mt9m111.c +++ b/drivers/media/i2c/mt9m111.c @@ -446,6 +446,7 @@ static int mt9m111_reset(struct mt9m111 *mt9m111) } static int mt9m111_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -490,6 +491,7 @@ static int mt9m111_set_selection(struct v4l2_subdev *sd, } static int mt9m111_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -617,6 +619,7 @@ static int mt9m111_set_pixfmt(struct mt9m111 *mt9m111, } static int mt9m111_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/mt9m114.c b/drivers/media/i2c/mt9m114.c index 848ea06e70ab..c6c950d5c8f3 100644 --- a/drivers/media/i2c/mt9m114.c +++ b/drivers/media/i2c/mt9m114.c @@ -1254,6 +1254,7 @@ static int mt9m114_pa_enum_framesizes(struct v4l2_subdev *sd, } static int mt9m114_pa_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -1281,6 +1282,7 @@ static int mt9m114_pa_set_fmt(struct v4l2_subdev *sd, } static int mt9m114_pa_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -1304,6 +1306,7 @@ static int mt9m114_pa_get_selection(struct v4l2_subdev *sd, } static int mt9m114_pa_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -1877,6 +1880,7 @@ static void mt9m114_ifp_update_sel_and_src_fmt(struct v4l2_subdev_state *state) } static int mt9m114_ifp_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -1923,6 +1927,7 @@ static int mt9m114_ifp_set_fmt(struct v4l2_subdev *sd, } static int mt9m114_ifp_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -1982,6 +1987,7 @@ static int mt9m114_ifp_get_selection(struct v4l2_subdev *sd, } static int mt9m114_ifp_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/mt9p031.c b/drivers/media/i2c/mt9p031.c index 2b09e8315c8e..5b0a84c66043 100644 --- a/drivers/media/i2c/mt9p031.c +++ b/drivers/media/i2c/mt9p031.c @@ -580,6 +580,7 @@ static int mt9p031_get_format(struct v4l2_subdev *subdev, } static int mt9p031_set_format(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -618,6 +619,7 @@ static int mt9p031_set_format(struct v4l2_subdev *subdev, } static int mt9p031_get_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -642,6 +644,7 @@ static int mt9p031_get_selection(struct v4l2_subdev *subdev, } static int mt9p031_set_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/mt9t112.c b/drivers/media/i2c/mt9t112.c index b3a6c6d76632..7a47756b9356 100644 --- a/drivers/media/i2c/mt9t112.c +++ b/drivers/media/i2c/mt9t112.c @@ -872,6 +872,7 @@ static int mt9t112_set_params(struct mt9t112_priv *priv, } static int mt9t112_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -897,6 +898,7 @@ static int mt9t112_get_selection(struct v4l2_subdev *sd, } static int mt9t112_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -953,6 +955,7 @@ static int mt9t112_s_fmt(struct v4l2_subdev *sd, } static int mt9t112_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/mt9v011.c b/drivers/media/i2c/mt9v011.c index ff55a8ff32b4..6ea31a4a6463 100644 --- a/drivers/media/i2c/mt9v011.c +++ b/drivers/media/i2c/mt9v011.c @@ -336,6 +336,7 @@ static int mt9v011_enum_mbus_code(struct v4l2_subdev *sd, } static int mt9v011_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/mt9v032.c b/drivers/media/i2c/mt9v032.c index 5113826534d7..29823378f0c7 100644 --- a/drivers/media/i2c/mt9v032.c +++ b/drivers/media/i2c/mt9v032.c @@ -502,6 +502,7 @@ static unsigned int mt9v032_calc_ratio(unsigned int input, unsigned int output) } static int mt9v032_set_format(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -546,6 +547,7 @@ static int mt9v032_set_format(struct v4l2_subdev *subdev, } static int mt9v032_get_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -560,6 +562,7 @@ static int mt9v032_get_selection(struct v4l2_subdev *subdev, } static int mt9v032_set_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/mt9v111.c b/drivers/media/i2c/mt9v111.c index 64a758c95ab7..6e9b3e190bca 100644 --- a/drivers/media/i2c/mt9v111.c +++ b/drivers/media/i2c/mt9v111.c @@ -886,6 +886,7 @@ static int mt9v111_get_format(struct v4l2_subdev *subdev, } static int mt9v111_set_format(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/og01a1b.c b/drivers/media/i2c/og01a1b.c index 1675f0460969..b207b1d853a4 100644 --- a/drivers/media/i2c/og01a1b.c +++ b/drivers/media/i2c/og01a1b.c @@ -677,6 +677,7 @@ static int og01a1b_disable_streams(struct v4l2_subdev *sd, } static int og01a1b_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -763,7 +764,7 @@ static int og01a1b_init_state(struct v4l2_subdev *sd, }, }; - og01a1b_set_format(sd, state, &fmt); + og01a1b_set_format(sd, NULL, state, &fmt); return 0; } @@ -956,6 +957,11 @@ static void og01a1b_remove(struct i2c_client *client) media_entity_cleanup(&sd->entity); v4l2_ctrl_handler_free(sd->ctrl_handler); pm_runtime_disable(og01a1b->dev); + + if (!pm_runtime_status_suspended(og01a1b->dev)) { + og01a1b_power_off(og01a1b->dev); + pm_runtime_set_suspended(og01a1b->dev); + } } static int og01a1b_probe(struct i2c_client *client) diff --git a/drivers/media/i2c/og0ve1b.c b/drivers/media/i2c/og0ve1b.c index 84a28cdcade1..84682389d989 100644 --- a/drivers/media/i2c/og0ve1b.c +++ b/drivers/media/i2c/og0ve1b.c @@ -481,6 +481,7 @@ static int og0ve1b_disable_streams(struct v4l2_subdev *sd, } static int og0ve1b_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -544,7 +545,7 @@ static int og0ve1b_init_state(struct v4l2_subdev *sd, }, }; - og0ve1b_set_pad_format(sd, state, &fmt); + og0ve1b_set_pad_format(sd, NULL, state, &fmt); return 0; } diff --git a/drivers/media/i2c/os02g10.c b/drivers/media/i2c/os02g10.c new file mode 100644 index 000000000000..141b7cb11fc5 --- /dev/null +++ b/drivers/media/i2c/os02g10.c @@ -0,0 +1,934 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * V4L2 Support for the OS02G10 + * + * Copyright (C) 2026 Silicon Signals Pvt. Ltd. + * + */ + +#include <linux/array_size.h> +#include <linux/bitops.h> +#include <linux/cleanup.h> +#include <linux/clk.h> +#include <linux/container_of.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/pm_runtime.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/units.h> +#include <linux/types.h> +#include <linux/time.h> + +#include <media/v4l2-cci.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-mediabus.h> + +#define OS02G10_XCLK_FREQ_24MHZ (24 * HZ_PER_MHZ) + +/* Page 0 */ +#define OS02G10_REG_CHIPID CCI_REG24(0x002) +#define OS02G10_CHIPID 0x560247 + +#define OS02G10_REG_PLL_DIV_CTRL CCI_REG8(0x030) +#define OS02G10_REG_PLL_DCTL_BIAS_CTRL CCI_REG8(0x035) +#define OS02G10_REG_GATE_EN_CTRL CCI_REG8(0x038) +#define OS02G10_REG_DPLL_NC CCI_REG8(0x041) +#define OS02G10_REG_MP_PHASE_CTRL CCI_REG8(0x044) + +/* Page 1 */ +#define OS02G10_REG_FRAME_SYNC CCI_REG8(0x101) + +#define OS02G10_REG_LONG_EXPOSURE CCI_REG16(0x103) +#define OS02G10_EXPOSURE_MIN 4 +#define OS02G10_EXPOSURE_STEP 1 +#define OS02G10_EXPOSURE_MARGIN 9 + +#define OS02G10_REG_HBLANK CCI_REG16(0x109) + +#define OS02G10_REG_FRAME_TEST_CTRL CCI_REG8(0x10d) +#define OS02G10_FRAME_EXP_SEPERATE_EN BIT(4) +#define OS02G10_TEST_PATTERN_ENABLE BIT(0) + +#define OS02G10_REG_FRAME_LENGTH CCI_REG16(0x10e) +#define OS02G10_FRAME_LENGTH_MAX (BIT(16) - 1) + +#define OS02G10_REG_ANALOG_GAIN CCI_REG8(0x124) +#define OS02G10_ANALOG_GAIN_MIN 16 +#define OS02G10_ANALOG_GAIN_MAX 248 +#define OS02G10_ANALOG_GAIN_STEP 1 +#define OS02G10_ANALOG_GAIN_DEFAULT 16 + +#define OS02G10_REG_DIGITAL_GAIN_H CCI_REG8(0x137) +#define OS02G10_REG_DIGITAL_GAIN_L CCI_REG8(0x139) +#define OS02G10_DIGITAL_GAIN_MIN 64 +#define OS02G10_DIGITAL_GAIN_MAX 2048 +#define OS02G10_DIGITAL_GAIN_STEP 64 +#define OS02G10_DIGITAL_GAIN_DEFAULT 64 + +#define OS02G10_REG_ULP_PWD_DUMMY_CTRL CCI_REG8(0x13c) + +#define OS02G10_REG_FLIP_MIRROR CCI_REG8(0x13f) +#define OS02G10_FLIP BIT(1) +#define OS02G10_MIRROR BIT(0) + +#define OS02G10_REG_DC_LEVEL_LIMIT_EN CCI_REG8(0x146) +#define OS02G10_REG_DC_LEVEL_LIMIT_L CCI_REG8(0x147) +#define OS02G10_REG_BLC_DATA_LIMIT_L CCI_REG8(0x148) +#define OS02G10_REG_DC_BLC_LIMIT_H CCI_REG8(0x149) + +#define OS02G10_REG_H_SIZE_MIPI CCI_REG16(0x18e) +#define OS02G10_REG_V_SIZE_MIPI CCI_REG16(0x190) + +#define OS02G10_REG_HS_LP_CTRL CCI_REG8(0x192) +#define OS02G10_REG_HS_LEVEL CCI_REG8(0x19d) +#define OS02G10_REG_HS_DRV CCI_REG8(0x19e) + +#define OS02G10_REG_MIPI_TX_SPEED_CTRL CCI_REG8(0x1a1) + +#define OS02G10_REG_STREAM_CTRL CCI_REG8(0x1b1) +#define OS02G10_STREAM_CTRL_ON 0x03 +#define OS02G10_STREAM_CTRL_OFF 0x00 + +#define OS02G10_REG_GB_SUBOFFSET CCI_REG8(0x1f0) +#define OS02G10_REG_BLUE_SUBOFFSET CCI_REG8(0x1f1) +#define OS02G10_REG_RED_SUBOFFSET CCI_REG8(0x1f2) +#define OS02G10_REG_GR_SUBOFFSET CCI_REG8(0x1f3) + +#define OS02G10_REG_ABL_TRIGGER CCI_REG8(0x1fa) +#define OS02G10_REG_ABL CCI_REG8(0x1fb) + +/* Page 2 */ +#define OS02G10_REG_SIF_CTRL CCI_REG8(0x25e) +#define OS02G10_ORIENTATION_BAYER_FIX 0x32 + +#define OS02G10_REG_V_START CCI_REG16(0x2a0) +#define OS02G10_REG_V_SIZE CCI_REG16(0x2a2) +#define OS02G10_REG_H_START CCI_REG16(0x2a4) +#define OS02G10_REG_H_SIZE CCI_REG16(0x2a6) + +#define OS02G10_LINK_FREQ_720MHZ (720 * HZ_PER_MHZ) +#define OS02G10_DATA_LANES 2 + +/* OS02G10 native and active pixel array size */ +static const struct v4l2_rect os02g10_native_area = { + .top = 0, + .left = 0, + .width = 1928, + .height = 1088, +}; + +static const struct v4l2_rect os02g10_active_area = { + .top = 4, + .left = 4, + .width = 1920, + .height = 1080, +}; + +static const char * const os02g10_supply_name[] = { + "avdd", /* Analog power */ + "dovdd", /* Digital I/O power */ + "dvdd", /* Digital core power */ +}; + +struct os02g10 { + struct device *dev; + struct regmap *cci; + struct v4l2_subdev sd; + struct media_pad pad; + struct clk *xclk; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[ARRAY_SIZE(os02g10_supply_name)]; + + /* V4L2 Controls */ + struct v4l2_ctrl_handler handler; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; +}; + +struct os02g10_mode { + u32 width; + u32 height; + u32 vts_def; + u32 exp_def; + u32 x_start; + u32 y_start; +}; + +static const struct cci_reg_sequence os02g10_common_regs[] = { + { OS02G10_REG_PLL_DIV_CTRL, 0x0a}, + { OS02G10_REG_PLL_DCTL_BIAS_CTRL, 0x04}, + { OS02G10_REG_GATE_EN_CTRL, 0x11}, + { OS02G10_REG_DPLL_NC, 0x06}, + { OS02G10_REG_MP_PHASE_CTRL, 0x20}, + { CCI_REG8(0x119), 0x50}, + { CCI_REG8(0x11a), 0x0c}, + { CCI_REG8(0x11b), 0x0d}, + { CCI_REG8(0x11c), 0x00}, + { CCI_REG8(0x11d), 0x75}, + { CCI_REG8(0x11e), 0x52}, + { CCI_REG8(0x122), 0x14}, + { CCI_REG8(0x125), 0x44}, + { CCI_REG8(0x126), 0x0f}, + { OS02G10_REG_ULP_PWD_DUMMY_CTRL, 0xca}, + { CCI_REG8(0x13d), 0x4a}, + { CCI_REG8(0x140), 0x0f}, + { CCI_REG8(0x143), 0x38}, + { OS02G10_REG_DC_LEVEL_LIMIT_EN, 0x01}, + { OS02G10_REG_DC_LEVEL_LIMIT_L, 0x00}, + { OS02G10_REG_DC_BLC_LIMIT_H, 0x32}, + { CCI_REG8(0x150), 0x01}, + { CCI_REG8(0x151), 0x28}, + { CCI_REG8(0x152), 0x20}, + { CCI_REG8(0x153), 0x03}, + { CCI_REG8(0x157), 0x16}, + { CCI_REG8(0x159), 0x01}, + { CCI_REG8(0x15a), 0x01}, + { CCI_REG8(0x15d), 0x04}, + { CCI_REG8(0x16a), 0x04}, + { CCI_REG8(0x16b), 0x03}, + { CCI_REG8(0x16e), 0x28}, + { CCI_REG8(0x171), 0xc2}, + { CCI_REG8(0x172), 0x04}, + { CCI_REG8(0x173), 0x38}, + { CCI_REG8(0x174), 0x04}, + { CCI_REG8(0x179), 0x00}, + { CCI_REG8(0x17a), 0xb2}, + { CCI_REG8(0x17b), 0x10}, + { OS02G10_REG_HS_LP_CTRL, 0x02}, + { OS02G10_REG_HS_LEVEL, 0x03}, + { OS02G10_REG_HS_DRV, 0x55}, + { CCI_REG8(0x1b8), 0x70}, + { CCI_REG8(0x1b9), 0x70}, + { CCI_REG8(0x1ba), 0x70}, + { CCI_REG8(0x1bb), 0x70}, + { CCI_REG8(0x1bc), 0x00}, + { CCI_REG8(0x1c4), 0x6d}, + { CCI_REG8(0x1c5), 0x6d}, + { CCI_REG8(0x1c6), 0x6d}, + { CCI_REG8(0x1c7), 0x6d}, + { CCI_REG8(0x1cc), 0x11}, + { CCI_REG8(0x1cd), 0xe0}, + { CCI_REG8(0x1d0), 0x1b}, + { CCI_REG8(0x1d2), 0x76}, + { CCI_REG8(0x1d3), 0x68}, + { CCI_REG8(0x1d4), 0x68}, + { CCI_REG8(0x1d5), 0x73}, + { CCI_REG8(0x1d6), 0x73}, + { CCI_REG8(0x1e8), 0x55}, + { OS02G10_REG_GB_SUBOFFSET, 0x40}, + { OS02G10_REG_BLUE_SUBOFFSET, 0x40}, + { OS02G10_REG_RED_SUBOFFSET, 0x40}, + { OS02G10_REG_GR_SUBOFFSET, 0x40}, + { OS02G10_REG_ABL_TRIGGER, 0x1c}, + { OS02G10_REG_ABL, 0x33}, + { CCI_REG8(0x1fc), 0x80}, + { CCI_REG8(0x1fe), 0x80}, + { CCI_REG8(0x303), 0x67}, + { CCI_REG8(0x300), 0x59}, + { CCI_REG8(0x304), 0x11}, + { CCI_REG8(0x305), 0x04}, + { CCI_REG8(0x306), 0x0c}, + { CCI_REG8(0x307), 0x08}, + { CCI_REG8(0x308), 0x08}, + { CCI_REG8(0x309), 0x4f}, + { CCI_REG8(0x30b), 0x08}, + { CCI_REG8(0x30d), 0x26}, + { CCI_REG8(0x30f), 0x00}, + { CCI_REG8(0x234), 0xfe}, + { OS02G10_REG_MIPI_TX_SPEED_CTRL, 0x05}, +}; + +static const struct os02g10_mode supported_modes[] = { + { + .width = 1920, + .height = 1080, + .vts_def = 1246, + .exp_def = 1100, + .x_start = 2, + .y_start = 6, + }, +}; + +static const s64 link_freq_menu_items[] = { + OS02G10_LINK_FREQ_720MHZ, +}; + +static const char * const os02g10_test_pattern_menu[] = { + "Disabled", + "Colorbar", +}; + +static inline struct os02g10 *to_os02g10(struct v4l2_subdev *sd) +{ + return container_of_const(sd, struct os02g10, sd); +} + +static u32 os02g10_get_format_code(struct os02g10 *os02g10) +{ + static const u32 codes[2][2] = { + { MEDIA_BUS_FMT_SBGGR10_1X10, MEDIA_BUS_FMT_SGBRG10_1X10, }, + { MEDIA_BUS_FMT_SGRBG10_1X10, MEDIA_BUS_FMT_SRGGB10_1X10, }, + }; + + return codes[os02g10->vflip->val][os02g10->hflip->val]; +} + +static int os02g10_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct os02g10 *os02g10 = container_of_const(ctrl->handler, + struct os02g10, handler); + struct v4l2_subdev_state *state; + struct v4l2_mbus_framefmt *fmt; + int ret = 0; + + state = v4l2_subdev_get_locked_active_state(&os02g10->sd); + fmt = v4l2_subdev_state_get_format(state, 0); + + if (ctrl->id == V4L2_CID_VBLANK) { + /* Honour the VBLANK limits when setting exposure */ + s64 max = fmt->height + ctrl->val - OS02G10_EXPOSURE_MARGIN; + + ret = __v4l2_ctrl_modify_range(os02g10->exposure, + os02g10->exposure->minimum, max, + os02g10->exposure->step, + os02g10->exposure->default_value); + if (ret) + return ret; + } + + if (pm_runtime_get_if_active(os02g10->dev) == 0) + return 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + cci_write(os02g10->cci, OS02G10_REG_LONG_EXPOSURE, + ctrl->val, &ret); + break; + case V4L2_CID_ANALOGUE_GAIN: + cci_write(os02g10->cci, OS02G10_REG_ANALOG_GAIN, + ctrl->val, &ret); + break; + case V4L2_CID_DIGITAL_GAIN: + cci_write(os02g10->cci, OS02G10_REG_DIGITAL_GAIN_L, + (ctrl->val & 0xff), &ret); + cci_write(os02g10->cci, OS02G10_REG_DIGITAL_GAIN_H, + ((ctrl->val >> 8) & 0x7), &ret); + break; + case V4L2_CID_VBLANK: { + u64 vts = ctrl->val + fmt->height; + + cci_update_bits(os02g10->cci, OS02G10_REG_FRAME_TEST_CTRL, + OS02G10_FRAME_EXP_SEPERATE_EN, + OS02G10_FRAME_EXP_SEPERATE_EN, &ret); + cci_write(os02g10->cci, OS02G10_REG_FRAME_LENGTH, vts, &ret); + break; + } + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + cci_write(os02g10->cci, OS02G10_REG_FLIP_MIRROR, + os02g10->hflip->val | os02g10->vflip->val << 1, &ret); + cci_write(os02g10->cci, OS02G10_REG_SIF_CTRL, + OS02G10_ORIENTATION_BAYER_FIX, &ret); + break; + case V4L2_CID_TEST_PATTERN: + cci_update_bits(os02g10->cci, + OS02G10_REG_FRAME_TEST_CTRL, + OS02G10_TEST_PATTERN_ENABLE, + ctrl->val ? OS02G10_TEST_PATTERN_ENABLE : 0, + &ret); + break; + default: + ret = -EINVAL; + break; + } + cci_write(os02g10->cci, OS02G10_REG_FRAME_SYNC, 0x01, &ret); + + pm_runtime_put(os02g10->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops os02g10_ctrl_ops = { + .s_ctrl = os02g10_set_ctrl, +}; + +static int os02g10_init_controls(struct os02g10 *os02g10) +{ + const struct os02g10_mode *mode = &supported_modes[0]; + struct v4l2_fwnode_device_properties props; + u64 vblank_def, exp_max, pixel_rate; + struct v4l2_ctrl_handler *ctrl_hdlr; + struct v4l2_ctrl *link_freq; + int ret; + + ret = v4l2_fwnode_device_parse(os02g10->dev, &props); + if (ret) + return ret; + + ctrl_hdlr = &os02g10->handler; + v4l2_ctrl_handler_init(ctrl_hdlr, 11); + + /* pixel_rate = link_freq * 2 * nr_of_lanes / bits_per_sample */ + pixel_rate = div_u64(OS02G10_LINK_FREQ_720MHZ * 2 * OS02G10_DATA_LANES, 10); + v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, V4L2_CID_PIXEL_RATE, 0, + pixel_rate, 1, pixel_rate); + + link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &os02g10_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(link_freq_menu_items) - 1, + 0, link_freq_menu_items); + + vblank_def = mode->vts_def - mode->height; + os02g10->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, + V4L2_CID_VBLANK, vblank_def, + OS02G10_FRAME_LENGTH_MAX - mode->height, + 1, vblank_def); + + exp_max = mode->vts_def - OS02G10_EXPOSURE_MARGIN; + os02g10->exposure = + v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, + V4L2_CID_EXPOSURE, + OS02G10_EXPOSURE_MIN, exp_max, + OS02G10_EXPOSURE_STEP, mode->exp_def); + + v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, + V4L2_CID_ANALOGUE_GAIN, OS02G10_ANALOG_GAIN_MIN, + OS02G10_ANALOG_GAIN_MAX, OS02G10_ANALOG_GAIN_STEP, + OS02G10_ANALOG_GAIN_DEFAULT); + + v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, + V4L2_CID_DIGITAL_GAIN, OS02G10_DIGITAL_GAIN_MIN, + OS02G10_DIGITAL_GAIN_MAX, OS02G10_DIGITAL_GAIN_STEP, + OS02G10_DIGITAL_GAIN_DEFAULT); + + os02g10->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + + os02g10->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &os02g10_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(os02g10_test_pattern_menu) - 1, + 0, 0, os02g10_test_pattern_menu); + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, + &os02g10_ctrl_ops, &props); + if (ret) + goto err_handler_free; + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + goto err_handler_free; + } + + link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + os02g10->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + os02g10->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + os02g10->sd.ctrl_handler = ctrl_hdlr; + + return 0; + +err_handler_free: + v4l2_ctrl_handler_free(ctrl_hdlr); + + return ret; +} + +static int os02g10_set_framefmt(struct os02g10 *os02g10, + struct v4l2_subdev_state *state) +{ + const struct v4l2_mbus_framefmt *format; + const struct os02g10_mode *mode; + int ret = 0; + + format = v4l2_subdev_state_get_format(state, 0); + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), width, + height, format->width, format->height); + + cci_write(os02g10->cci, OS02G10_REG_V_START, mode->y_start, &ret); + cci_write(os02g10->cci, OS02G10_REG_V_SIZE, mode->height, &ret); + cci_write(os02g10->cci, OS02G10_REG_V_SIZE_MIPI, mode->height, &ret); + cci_write(os02g10->cci, OS02G10_REG_H_START, mode->x_start, &ret); + cci_write(os02g10->cci, OS02G10_REG_H_SIZE, mode->width, &ret); + cci_write(os02g10->cci, OS02G10_REG_H_SIZE_MIPI, mode->width, &ret); + + return ret; +} + +static int os02g10_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct os02g10 *os02g10 = to_os02g10(sd); + int ret; + + ret = pm_runtime_resume_and_get(os02g10->dev); + if (ret < 0) + return ret; + + ret = cci_multi_reg_write(os02g10->cci, os02g10_common_regs, + ARRAY_SIZE(os02g10_common_regs), NULL); + if (ret) { + dev_err(os02g10->dev, "failed to write common registers\n"); + goto err_rpm_put; + } + + ret = os02g10_set_framefmt(os02g10, state); + if (ret) { + dev_err(os02g10->dev, "failed to set frame foramt\n"); + goto err_rpm_put; + } + + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(os02g10->sd.ctrl_handler); + if (ret) + goto err_rpm_put; + + ret = cci_write(os02g10->cci, OS02G10_REG_STREAM_CTRL, + OS02G10_STREAM_CTRL_ON, NULL); + if (ret) + goto err_rpm_put; + + /* vflip and hflip cannot change during streaming */ + __v4l2_ctrl_grab(os02g10->vflip, true); + __v4l2_ctrl_grab(os02g10->hflip, true); + + return 0; + +err_rpm_put: + pm_runtime_put(os02g10->dev); + return ret; +} + +static int os02g10_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct os02g10 *os02g10 = to_os02g10(sd); + int ret; + + ret = cci_write(os02g10->cci, OS02G10_REG_STREAM_CTRL, + OS02G10_STREAM_CTRL_OFF, NULL); + if (ret) + dev_err(os02g10->dev, "Failed to stop stream\n"); + + __v4l2_ctrl_grab(os02g10->vflip, false); + __v4l2_ctrl_grab(os02g10->hflip, false); + + pm_runtime_put(os02g10->dev); + + return ret; +} + +static int os02g10_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_CROP_BOUNDS: + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r = os02g10_native_area; + return 0; + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_CROP_DEFAULT: + sel->r = os02g10_active_area; + return 0; + default: + return -EINVAL; + } +} + +static int os02g10_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct os02g10 *os02g10 = to_os02g10(sd); + + if (code->index) + return -EINVAL; + + code->code = os02g10_get_format_code(os02g10); + + return 0; +} + +static int os02g10_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct os02g10 *os02g10 = to_os02g10(sd); + + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != os02g10_get_format_code(os02g10)) + return -EINVAL; + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = supported_modes[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static int os02g10_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 os02g10 *os02g10 = to_os02g10(sd); + struct v4l2_mbus_framefmt *format; + const struct os02g10_mode *mode; + + format = v4l2_subdev_state_get_format(sd_state, 0); + + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), + width, height, + fmt->format.width, fmt->format.height); + + fmt->format.code = os02g10_get_format_code(os02g10); + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.field = V4L2_FIELD_NONE; + fmt->format.colorspace = V4L2_COLORSPACE_RAW; + fmt->format.quantization = V4L2_QUANTIZATION_FULL_RANGE; + fmt->format.xfer_func = V4L2_XFER_FUNC_NONE; + + *format = fmt->format; + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + u32 vblank_def = mode->vts_def - mode->height; + + return __v4l2_ctrl_modify_range(os02g10->vblank, vblank_def, + OS02G10_FRAME_LENGTH_MAX - + mode->height, 1, vblank_def); + } + + return 0; +} + +static int os02g10_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_TRY, + .format = { + .width = supported_modes[0].width, + .height = supported_modes[0].height, + }, + }; + + return os02g10_set_pad_format(sd, NULL, state, &fmt); +} + +static const struct v4l2_subdev_video_ops os02g10_video_ops = { + .s_stream = v4l2_subdev_s_stream_helper, +}; + +static const struct v4l2_subdev_pad_ops os02g10_pad_ops = { + .enum_mbus_code = os02g10_enum_mbus_code, + .enum_frame_size = os02g10_enum_frame_size, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = os02g10_set_pad_format, + .get_selection = os02g10_get_selection, + .enable_streams = os02g10_enable_streams, + .disable_streams = os02g10_disable_streams, +}; + +static const struct v4l2_subdev_ops os02g10_subdev_ops = { + .video = &os02g10_video_ops, + .pad = &os02g10_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops os02g10_internal_ops = { + .init_state = os02g10_init_state, +}; + +static int os02g10_power_on(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct os02g10 *os02g10 = to_os02g10(sd); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(os02g10_supply_name), + os02g10->supplies); + if (ret) { + dev_err(os02g10->dev, "failed to enable regulators\n"); + return ret; + } + + /* Wait for T3/T4 timing requirements after supplies become stable */ + fsleep(5 * USEC_PER_MSEC); + + ret = clk_prepare_enable(os02g10->xclk); + if (ret) + goto err_regulator_off; + + gpiod_set_value_cansleep(os02g10->reset_gpio, 0); + + /* T5: delay from sensor power up stable to SCCB initialization */ + fsleep(5 * USEC_PER_MSEC); + + return 0; + +err_regulator_off: + regulator_bulk_disable(ARRAY_SIZE(os02g10_supply_name), os02g10->supplies); + + return ret; +} + +static int os02g10_power_off(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct os02g10 *os02g10 = to_os02g10(sd); + + clk_disable_unprepare(os02g10->xclk); + gpiod_set_value_cansleep(os02g10->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(os02g10_supply_name), os02g10->supplies); + + return 0; +} + +static int os02g10_identify_module(struct os02g10 *os02g10) +{ + u64 chip_id; + int ret; + + ret = cci_read(os02g10->cci, OS02G10_REG_CHIPID, &chip_id, NULL); + if (ret) + return dev_err_probe(os02g10->dev, ret, + "failed to read chip id %x\n", + OS02G10_CHIPID); + + if (chip_id != OS02G10_CHIPID) + return dev_err_probe(os02g10->dev, -EIO, + "chip id mismatch: %x!=%llx\n", + OS02G10_CHIPID, chip_id); + + return 0; +} + +static int os02g10_parse_endpoint(struct os02g10 *os02g10) +{ + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; + unsigned long link_freq_bitmap; + struct fwnode_handle *ep; + int ret; + + ep = fwnode_graph_get_next_endpoint(dev_fwnode(os02g10->dev), NULL); + ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); + fwnode_handle_put(ep); + if (ret) + return ret; + + ret = v4l2_link_freq_to_bitmap(os02g10->dev, bus_cfg.link_frequencies, + bus_cfg.nr_of_link_frequencies, + link_freq_menu_items, + ARRAY_SIZE(link_freq_menu_items), + &link_freq_bitmap); + + v4l2_fwnode_endpoint_free(&bus_cfg); + + return ret; +}; + +static const struct regmap_range_cfg os02g10_ranges[] = { + { + .range_min = 0x0000, + .range_max = 0x03ff, + .selector_reg = 0xfd, + .selector_mask = 0x03, + .selector_shift = 0, + .window_start = 0x00, + .window_len = 0x100, + }, +}; + +static const struct regmap_config os02g10_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .max_register = 0x3ff, + .ranges = os02g10_ranges, + .num_ranges = ARRAY_SIZE(os02g10_ranges), + .disable_locking = true, +}; + +static int os02g10_probe(struct i2c_client *client) +{ + struct os02g10 *os02g10; + unsigned int xclk_freq; + int ret; + + os02g10 = devm_kzalloc(&client->dev, sizeof(*os02g10), GFP_KERNEL); + if (!os02g10) + return -ENOMEM; + + os02g10->dev = &client->dev; + + v4l2_i2c_subdev_init(&os02g10->sd, client, &os02g10_subdev_ops); + os02g10->sd.internal_ops = &os02g10_internal_ops; + + /* + * This is not using devm_cci_regmap_init_i2c(), because the driver + * makes use of regmap's pagination feature. The chosen settings are + * compatible with the CCI helpers. + */ + os02g10->cci = devm_regmap_init_i2c(client, &os02g10_regmap_config); + if (IS_ERR(os02g10->cci)) + return dev_err_probe(os02g10->dev, PTR_ERR(os02g10->cci), + "failed to initialize CCI\n"); + + ret = os02g10_parse_endpoint(os02g10); + if (ret) + return dev_err_probe(os02g10->dev, ret, + "failed to parse endpoint configuration\n"); + + /* Get system clock (xvclk) */ + os02g10->xclk = devm_v4l2_sensor_clk_get(os02g10->dev, NULL); + if (IS_ERR(os02g10->xclk)) + return dev_err_probe(os02g10->dev, PTR_ERR(os02g10->xclk), + "failed to get xclk\n"); + + xclk_freq = clk_get_rate(os02g10->xclk); + if (xclk_freq != OS02G10_XCLK_FREQ_24MHZ) + return dev_err_probe(os02g10->dev, -EINVAL, + "xclk frequency not supported: %u Hz\n", + xclk_freq); + + for (unsigned int i = 0; i < ARRAY_SIZE(os02g10_supply_name); i++) + os02g10->supplies[i].supply = os02g10_supply_name[i]; + + ret = devm_regulator_bulk_get(os02g10->dev, + ARRAY_SIZE(os02g10_supply_name), + os02g10->supplies); + if (ret) + return dev_err_probe(os02g10->dev, ret, + "failed to get regulators\n"); + + os02g10->reset_gpio = devm_gpiod_get_optional(os02g10->dev, + "reset", GPIOD_OUT_HIGH); + if (IS_ERR(os02g10->reset_gpio)) + return dev_err_probe(os02g10->dev, PTR_ERR(os02g10->reset_gpio), + "failed to get reset GPIO\n"); + + ret = os02g10_power_on(os02g10->dev); + if (ret) + return ret; + + ret = os02g10_identify_module(os02g10); + if (ret) + goto error_power_off; + + ret = os02g10_init_controls(os02g10); + if (ret) + goto error_power_off; + + /* Initialize subdev */ + os02g10->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + os02g10->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + os02g10->pad.flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&os02g10->sd.entity, 1, &os02g10->pad); + if (ret) { + dev_err_probe(os02g10->dev, ret, "failed to init entity pads\n"); + goto error_handler_free; + } + + os02g10->sd.state_lock = os02g10->handler.lock; + ret = v4l2_subdev_init_finalize(&os02g10->sd); + if (ret) { + dev_err_probe(os02g10->dev, ret, "subdev init error\n"); + goto error_media_entity; + } + + pm_runtime_set_active(os02g10->dev); + pm_runtime_enable(os02g10->dev); + + ret = v4l2_async_register_subdev_sensor(&os02g10->sd); + if (ret) { + dev_err_probe(os02g10->dev, ret, + "failed to register os02g10 sub-device\n"); + goto error_subdev_cleanup; + } + + pm_runtime_idle(os02g10->dev); + + return 0; + +error_subdev_cleanup: + v4l2_subdev_cleanup(&os02g10->sd); + pm_runtime_disable(os02g10->dev); + pm_runtime_set_suspended(os02g10->dev); + +error_media_entity: + media_entity_cleanup(&os02g10->sd.entity); + +error_handler_free: + v4l2_ctrl_handler_free(os02g10->sd.ctrl_handler); + +error_power_off: + os02g10_power_off(os02g10->dev); + + return ret; +} + +static void os02g10_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct os02g10 *os02g10 = to_os02g10(sd); + + v4l2_async_unregister_subdev(sd); + v4l2_subdev_cleanup(&os02g10->sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(os02g10->sd.ctrl_handler); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) { + os02g10_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); + } +} + +static DEFINE_RUNTIME_DEV_PM_OPS(os02g10_pm_ops, + os02g10_power_off, os02g10_power_on, NULL); + +static const struct of_device_id os02g10_id[] = { + { .compatible = "ovti,os02g10" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, os02g10_id); + +static struct i2c_driver os02g10_driver = { + .driver = { + .name = "os02g10", + .pm = pm_ptr(&os02g10_pm_ops), + .of_match_table = os02g10_id, + }, + .probe = os02g10_probe, + .remove = os02g10_remove, +}; +module_i2c_driver(os02g10_driver); + +MODULE_DESCRIPTION("OS02G10 Camera Sensor Driver"); +MODULE_AUTHOR("Tarang Raval <tarang.raval@siliconsignals.io>"); +MODULE_AUTHOR("Elgin Perumbilly <elgin.perumbilly@siliconsignals.io>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/os05b10.c b/drivers/media/i2c/os05b10.c index e0453c988e4a..a46cfa513e98 100644 --- a/drivers/media/i2c/os05b10.c +++ b/drivers/media/i2c/os05b10.c @@ -595,6 +595,7 @@ static int os05b10_set_framing_limits(struct os05b10 *os05b10, } static int os05b10_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) { @@ -624,6 +625,7 @@ static int os05b10_set_pad_format(struct v4l2_subdev *sd, } static int os05b10_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/ov01a10.c b/drivers/media/i2c/ov01a10.c index 8a29e5b4b6ba..b9a7dacd603e 100644 --- a/drivers/media/i2c/ov01a10.c +++ b/drivers/media/i2c/ov01a10.c @@ -634,6 +634,7 @@ static void ov01a10_update_blank_ctrls(struct ov01a10 *ov01a10, } static int ov01a10_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -715,6 +716,7 @@ static int ov01a10_enum_frame_size(struct v4l2_subdev *sd, } static int ov01a10_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -747,6 +749,7 @@ static int ov01a10_get_selection(struct v4l2_subdev *sd, } static int ov01a10_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/ov02a10.c b/drivers/media/i2c/ov02a10.c index 0150e4d296af..375cd52d61b9 100644 --- a/drivers/media/i2c/ov02a10.c +++ b/drivers/media/i2c/ov02a10.c @@ -296,6 +296,7 @@ static void ov02a10_fill_fmt(const struct ov02a10_mode *mode, } static int ov02a10_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -523,7 +524,7 @@ static int ov02a10_init_state(struct v4l2_subdev *sd, } }; - ov02a10_set_fmt(sd, sd_state, &fmt); + ov02a10_set_fmt(sd, NULL, sd_state, &fmt); return 0; } diff --git a/drivers/media/i2c/ov02c10.c b/drivers/media/i2c/ov02c10.c index cf93d36032e1..d622f5dcac60 100644 --- a/drivers/media/i2c/ov02c10.c +++ b/drivers/media/i2c/ov02c10.c @@ -701,6 +701,7 @@ static int ov02c10_power_on(struct device *dev) } static int ov02c10_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/ov02e10.c b/drivers/media/i2c/ov02e10.c index 4a64cba99991..5c1e7013f559 100644 --- a/drivers/media/i2c/ov02e10.c +++ b/drivers/media/i2c/ov02e10.c @@ -590,6 +590,7 @@ disable_clk: } static int ov02e10_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/ov08d10.c b/drivers/media/i2c/ov08d10.c index 9adef5446a61..0710d2243cc0 100644 --- a/drivers/media/i2c/ov08d10.c +++ b/drivers/media/i2c/ov08d10.c @@ -1178,6 +1178,7 @@ static int ov08d10_set_stream(struct v4l2_subdev *sd, int enable) } static int ov08d10_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/ov08x40.c b/drivers/media/i2c/ov08x40.c index 5eaf454f4763..6de0c17e633d 100644 --- a/drivers/media/i2c/ov08x40.c +++ b/drivers/media/i2c/ov08x40.c @@ -1844,6 +1844,7 @@ static int ov08x40_get_pad_format(struct v4l2_subdev *sd, static int ov08x40_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/ov13858.c b/drivers/media/i2c/ov13858.c index 09d58e8b1c7f..de2b79a9a0e3 100644 --- a/drivers/media/i2c/ov13858.c +++ b/drivers/media/i2c/ov13858.c @@ -1345,6 +1345,7 @@ static int ov13858_get_pad_format(struct v4l2_subdev *sd, static int ov13858_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/ov13b10.c b/drivers/media/i2c/ov13b10.c index b0d34141a13a..242a95834126 100644 --- a/drivers/media/i2c/ov13b10.c +++ b/drivers/media/i2c/ov13b10.c @@ -1118,6 +1118,7 @@ static int ov13b10_get_pad_format(struct v4l2_subdev *sd, static int ov13b10_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/ov2640.c b/drivers/media/i2c/ov2640.c index 50feb608b92b..3dbefac6d305 100644 --- a/drivers/media/i2c/ov2640.c +++ b/drivers/media/i2c/ov2640.c @@ -938,6 +938,7 @@ static int ov2640_get_fmt(struct v4l2_subdev *sd, } static int ov2640_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -1028,6 +1029,7 @@ static int ov2640_enum_mbus_code(struct v4l2_subdev *sd, } static int ov2640_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/ov2659.c b/drivers/media/i2c/ov2659.c index 7d8c7c3465a4..5bc30e7b8325 100644 --- a/drivers/media/i2c/ov2659.c +++ b/drivers/media/i2c/ov2659.c @@ -1080,6 +1080,7 @@ static void __ov2659_try_frame_size(struct v4l2_mbus_framefmt *mf, } static int ov2659_set_fmt(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/ov2680.c b/drivers/media/i2c/ov2680.c index 5f1938c7a944..147862a813c5 100644 --- a/drivers/media/i2c/ov2680.c +++ b/drivers/media/i2c/ov2680.c @@ -639,6 +639,7 @@ static int ov2680_get_fmt(struct v4l2_subdev *sd, } static int ov2680_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -703,6 +704,7 @@ unlock: } static int ov2680_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -733,6 +735,7 @@ static int ov2680_get_selection(struct v4l2_subdev *sd, } static int ov2680_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/ov2685.c b/drivers/media/i2c/ov2685.c index 4911a4eea126..162859a140eb 100644 --- a/drivers/media/i2c/ov2685.c +++ b/drivers/media/i2c/ov2685.c @@ -340,6 +340,7 @@ static void ov2685_fill_fmt(const struct ov2685_mode *mode, } static int ov2685_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -413,6 +414,7 @@ __ov2685_get_pad_crop(struct ov2685 *ov2685, } static int ov2685_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/ov2732.c b/drivers/media/i2c/ov2732.c index 40035320fec6..57dd0dd3fd85 100644 --- a/drivers/media/i2c/ov2732.c +++ b/drivers/media/i2c/ov2732.c @@ -281,6 +281,7 @@ static int ov2732_enum_frame_size(struct v4l2_subdev *sd, } static int ov2732_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -317,6 +318,7 @@ static int ov2732_set_fmt(struct v4l2_subdev *sd, } static int ov2732_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -442,7 +444,7 @@ static int ov2732_init_state(struct v4l2_subdev *sd, } }; - return ov2732_set_fmt(sd, sd_state, &fmt); + return ov2732_set_fmt(sd, NULL, sd_state, &fmt); } static const struct v4l2_subdev_internal_ops ov2732_internal_ops = { diff --git a/drivers/media/i2c/ov2735.c b/drivers/media/i2c/ov2735.c index dcb1add1fd9f..0361904c4841 100644 --- a/drivers/media/i2c/ov2735.c +++ b/drivers/media/i2c/ov2735.c @@ -674,6 +674,7 @@ static int ov2735_disable_streams(struct v4l2_subdev *sd, } static int ov2735_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -742,6 +743,7 @@ static int ov2735_set_framing_limits(struct ov2735 *ov2735, } static int ov2735_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) { @@ -792,7 +794,7 @@ static int ov2735_init_state(struct v4l2_subdev *sd, }, }; - ov2735_set_pad_format(sd, state, &fmt); + ov2735_set_pad_format(sd, NULL, state, &fmt); return 0; } @@ -1081,6 +1083,7 @@ static void ov2735_remove(struct i2c_client *client) v4l2_subdev_cleanup(&ov2735->sd); media_entity_cleanup(&sd->entity); v4l2_ctrl_handler_free(ov2735->sd.ctrl_handler); + ov2735_power_off(ov2735->dev); } static DEFINE_RUNTIME_DEV_PM_OPS(ov2735_pm_ops, diff --git a/drivers/media/i2c/ov2740.c b/drivers/media/i2c/ov2740.c index 39003c1632ad..536325bb7b92 100644 --- a/drivers/media/i2c/ov2740.c +++ b/drivers/media/i2c/ov2740.c @@ -1023,6 +1023,7 @@ out_unlock: } static int ov2740_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/ov4689.c b/drivers/media/i2c/ov4689.c index a59d25b09b5b..466b6f74d982 100644 --- a/drivers/media/i2c/ov4689.c +++ b/drivers/media/i2c/ov4689.c @@ -330,6 +330,7 @@ static void ov4689_fill_fmt(const struct ov4689_mode *mode, } static int ov4689_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -385,6 +386,7 @@ static int ov4689_enable_test_pattern(struct ov4689 *ov4689, u32 pattern) } static int ov4689_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/ov5640.c b/drivers/media/i2c/ov5640.c index 8deb5f5501fa..22d08d0a90cf 100644 --- a/drivers/media/i2c/ov5640.c +++ b/drivers/media/i2c/ov5640.c @@ -2947,6 +2947,7 @@ static int ov5640_update_pixel_rate(struct ov5640_dev *sensor) } static int ov5640_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -2993,6 +2994,7 @@ out: } static int ov5640_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/ov5645.c b/drivers/media/i2c/ov5645.c index c772ef6e51d2..a8d740ed4dc9 100644 --- a/drivers/media/i2c/ov5645.c +++ b/drivers/media/i2c/ov5645.c @@ -848,6 +848,7 @@ static int ov5645_enum_frame_size(struct v4l2_subdev *subdev, } static int ov5645_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -905,12 +906,13 @@ static int ov5645_init_state(struct v4l2_subdev *subdev, }, }; - ov5645_set_format(subdev, sd_state, &fmt); + ov5645_set_format(subdev, NULL, sd_state, &fmt); return 0; } static int ov5645_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/ov5647.c b/drivers/media/i2c/ov5647.c index 3facf92b3841..e1120ca68828 100644 --- a/drivers/media/i2c/ov5647.c +++ b/drivers/media/i2c/ov5647.c @@ -787,6 +787,7 @@ static int ov5647_get_pad_fmt(struct v4l2_subdev *sd, } static int ov5647_set_pad_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -837,6 +838,7 @@ static int ov5647_set_pad_fmt(struct v4l2_subdev *sd, } static int ov5647_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1271,6 +1273,10 @@ static void ov5647_remove(struct i2c_client *client) v4l2_ctrl_handler_free(&sensor->ctrls); v4l2_device_unregister_subdev(sd); pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) { + ov5647_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); + } } static const struct dev_pm_ops ov5647_pm_ops = { diff --git a/drivers/media/i2c/ov5648.c b/drivers/media/i2c/ov5648.c index f0b839cd65f1..6a1efa12c38d 100644 --- a/drivers/media/i2c/ov5648.c +++ b/drivers/media/i2c/ov5648.c @@ -2145,13 +2145,13 @@ static int ov5648_s_stream(struct v4l2_subdev *subdev, int enable) mutex_lock(&sensor->mutex); ret = ov5648_sw_standby(sensor, !enable); + if (!ret) + state->streaming = !!enable; mutex_unlock(&sensor->mutex); if (ret) return ret; - state->streaming = !!enable; - if (!enable) pm_runtime_put(sensor->dev); @@ -2215,6 +2215,7 @@ static int ov5648_get_fmt(struct v4l2_subdev *subdev, } static int ov5648_set_fmt(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/ov5670.c b/drivers/media/i2c/ov5670.c index 01fa892de079..940f21fe0dd3 100644 --- a/drivers/media/i2c/ov5670.c +++ b/drivers/media/i2c/ov5670.c @@ -2285,6 +2285,7 @@ static int ov5670_get_pad_format(struct v4l2_subdev *sd, } static int ov5670_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) { @@ -2553,6 +2554,7 @@ __ov5670_get_pad_crop(struct ov5670 *sensor, struct v4l2_subdev_state *state, } static int ov5670_get_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/ov5675.c b/drivers/media/i2c/ov5675.c index 1c31b2a57eea..c5eb07576129 100644 --- a/drivers/media/i2c/ov5675.c +++ b/drivers/media/i2c/ov5675.c @@ -1015,6 +1015,7 @@ static int ov5675_power_on(struct device *dev) } static int ov5675_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -1074,6 +1075,7 @@ static int ov5675_get_format(struct v4l2_subdev *sd, } static int ov5675_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c index 4cc796bbee92..56599f9ca74b 100644 --- a/drivers/media/i2c/ov5693.c +++ b/drivers/media/i2c/ov5693.c @@ -35,6 +35,11 @@ #define OV5693_STOP_STREAMING 0x00 #define OV5693_SW_RESET 0x01 +/* MIPI transmitter control */ +#define OV5693_MIPI_CTRL00_REG CCI_REG8(0x4800) +/* Gate the clock lane when there is no packet to transmit */ +#define OV5693_MIPI_CTRL00_CLOCK_LANE_GATE BIT(5) + #define OV5693_REG_CHIP_ID CCI_REG16(0x300a) /* Yes, this is right. The datasheet for the OV5693 gives its ID as 0x5690 */ #define OV5693_CHIP_ID 0x5690 @@ -144,6 +149,9 @@ struct ov5693_device { struct regulator_bulk_data supplies[OV5693_NUM_SUPPLIES]; struct clk *xvclk; + /* Gate the MIPI clock lane when idle (CSI-2 non-continuous clock) */ + bool clock_ncont; + struct ov5693_mode { struct v4l2_rect crop; struct v4l2_mbus_framefmt format; @@ -611,6 +619,19 @@ static int ov5693_enable_streaming(struct ov5693_device *ov5693, bool enable) { int ret = 0; + /* + * Gate the MIPI clock lane while idle if the CSI-2 link is configured + * for a non-continuous clock. Only that bit is touched, and only in + * that case, so the register keeps whatever the platform left in it + * and the clock stays free-running as before everywhere else. It + * needs no counterpart at stream off: the link is down by then, and + * the register returns to its default when the sensor is powered off. + */ + if (enable && ov5693->clock_ncont) + cci_update_bits(ov5693->regmap, OV5693_MIPI_CTRL00_REG, + OV5693_MIPI_CTRL00_CLOCK_LANE_GATE, + OV5693_MIPI_CTRL00_CLOCK_LANE_GATE, &ret); + cci_write(ov5693->regmap, OV5693_SW_STREAM_REG, enable ? OV5693_START_STREAMING : OV5693_STOP_STREAMING, &ret); @@ -807,6 +828,7 @@ static int ov5693_get_fmt(struct v4l2_subdev *sd, } static int ov5693_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { @@ -882,6 +904,7 @@ static int ov5693_set_fmt(struct v4l2_subdev *sd, } static int ov5693_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -915,6 +938,7 @@ static int ov5693_get_selection(struct v4l2_subdev *sd, } static int ov5693_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -1259,6 +1283,9 @@ static int ov5693_check_hwcfg(struct ov5693_device *ov5693) goto out_free_bus_cfg; } + ov5693->clock_ncont = bus_cfg.bus.mipi_csi2.flags & + V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK; + out_free_bus_cfg: v4l2_fwnode_endpoint_free(&bus_cfg); @@ -1396,6 +1423,7 @@ static const struct dev_pm_ops ov5693_pm_ops = { static const struct acpi_device_id ov5693_acpi_match[] = { {"INT33BE"}, + {"OVTI5693"}, {}, }; MODULE_DEVICE_TABLE(acpi, ov5693_acpi_match); diff --git a/drivers/media/i2c/ov5695.c b/drivers/media/i2c/ov5695.c index 5bb6ce7b3237..5b23f04ab9f8 100644 --- a/drivers/media/i2c/ov5695.c +++ b/drivers/media/i2c/ov5695.c @@ -805,6 +805,7 @@ ov5695_find_best_fit(struct v4l2_subdev_format *fmt) } static int ov5695_set_fmt(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/ov6211.c b/drivers/media/i2c/ov6211.c index 034d5d57d67e..2ad31b2c5249 100644 --- a/drivers/media/i2c/ov6211.c +++ b/drivers/media/i2c/ov6211.c @@ -459,6 +459,7 @@ static int ov6211_disable_streams(struct v4l2_subdev *sd, } static int ov6211_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -522,7 +523,7 @@ static int ov6211_init_state(struct v4l2_subdev *sd, }, }; - ov6211_set_pad_format(sd, state, &fmt); + ov6211_set_pad_format(sd, NULL, state, &fmt); return 0; } diff --git a/drivers/media/i2c/ov64a40.c b/drivers/media/i2c/ov64a40.c index ed59b4818c55..b369c002a25a 100644 --- a/drivers/media/i2c/ov64a40.c +++ b/drivers/media/i2c/ov64a40.c @@ -3106,6 +3106,7 @@ static int ov64a40_enum_frame_size(struct v4l2_subdev *sd, } static int ov64a40_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -3137,6 +3138,7 @@ static int ov64a40_get_selection(struct v4l2_subdev *sd, } static int ov64a40_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/ov7251.c b/drivers/media/i2c/ov7251.c index 311c61d9e25d..fce6344b5120 100644 --- a/drivers/media/i2c/ov7251.c +++ b/drivers/media/i2c/ov7251.c @@ -1213,6 +1213,7 @@ ov7251_find_mode_by_ival(struct ov7251 *ov7251, struct v4l2_fract *timeperframe) } static int ov7251_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -1295,12 +1296,13 @@ static int ov7251_init_state(struct v4l2_subdev *subdev, } }; - ov7251_set_format(subdev, sd_state, &fmt); + ov7251_set_format(subdev, NULL, sd_state, &fmt); return 0; } static int ov7251_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/ov7670.c b/drivers/media/i2c/ov7670.c index 4d040e9feeac..5a553d50f3a3 100644 --- a/drivers/media/i2c/ov7670.c +++ b/drivers/media/i2c/ov7670.c @@ -1097,6 +1097,7 @@ static int ov7670_apply_fmt(struct v4l2_subdev *sd) * Set a format. */ static int ov7670_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/ov772x.c b/drivers/media/i2c/ov772x.c index be3ba284ee0b..cb3f3f813837 100644 --- a/drivers/media/i2c/ov772x.c +++ b/drivers/media/i2c/ov772x.c @@ -122,7 +122,7 @@ #define LC_COEFB 0x4B /* Lens B channel compensation coefficient */ #define LC_COEFR 0x4C /* Lens R channel compensation coefficient */ -#define FIXGAIN 0x4D /* Analog fix gain amplifer */ +#define FIXGAIN 0x4D /* Analog fix gain amplifier */ #define AREF0 0x4E /* Sensor reference control */ #define AREF1 0x4F /* Sensor reference current control */ #define AREF2 0x50 /* Analog reference control */ @@ -1171,6 +1171,7 @@ ov772x_set_fmt_error: } static int ov772x_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1212,6 +1213,7 @@ static int ov772x_get_fmt(struct v4l2_subdev *sd, } static int ov772x_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/ov7740.c b/drivers/media/i2c/ov7740.c index b4e14171556f..c8a74ce39841 100644 --- a/drivers/media/i2c/ov7740.c +++ b/drivers/media/i2c/ov7740.c @@ -766,6 +766,7 @@ static int ov7740_try_fmt_internal(struct v4l2_subdev *sd, } static int ov7740_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/ov8856.c b/drivers/media/i2c/ov8856.c index 8bedb47cd7cf..42c4b9c4e380 100644 --- a/drivers/media/i2c/ov8856.c +++ b/drivers/media/i2c/ov8856.c @@ -2082,9 +2082,6 @@ static int ov8856_power_on(struct device *dev) struct ov8856 *ov8856 = to_ov8856(sd); int ret; - if (is_acpi_node(dev_fwnode(dev))) - return 0; - ret = clk_prepare_enable(ov8856->xvclk); if (ret < 0) { dev_err(dev, "failed to enable xvclk\n"); @@ -2120,9 +2117,6 @@ static int ov8856_power_off(struct device *dev) struct v4l2_subdev *sd = dev_get_drvdata(dev); struct ov8856 *ov8856 = to_ov8856(sd); - if (is_acpi_node(dev_fwnode(dev))) - return 0; - gpiod_set_value_cansleep(ov8856->reset_gpio, 1); regulator_bulk_disable(ARRAY_SIZE(ov8856_supply_names), ov8856->supplies); @@ -2132,6 +2126,7 @@ static int ov8856_power_off(struct device *dev) } static int ov8856_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -2293,21 +2288,18 @@ static int ov8856_get_hwcfg(struct ov8856 *ov8856) dev_warn(dev, "external clock rate %u is unsupported", xvclk_rate); - if (!is_acpi_node(fwnode)) { - ov8856->reset_gpio = devm_gpiod_get_optional(dev, "reset", - GPIOD_OUT_LOW); - if (IS_ERR(ov8856->reset_gpio)) - return PTR_ERR(ov8856->reset_gpio); + ov8856->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(ov8856->reset_gpio)) + return PTR_ERR(ov8856->reset_gpio); - for (i = 0; i < ARRAY_SIZE(ov8856_supply_names); i++) - ov8856->supplies[i].supply = ov8856_supply_names[i]; + for (i = 0; i < ARRAY_SIZE(ov8856_supply_names); i++) + ov8856->supplies[i].supply = ov8856_supply_names[i]; - ret = devm_regulator_bulk_get(dev, - ARRAY_SIZE(ov8856_supply_names), - ov8856->supplies); - if (ret) - return ret; - } + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ov8856_supply_names), + ov8856->supplies); + if (ret) + return ret; ep = fwnode_graph_get_next_endpoint(fwnode, NULL); if (!ep) @@ -2327,7 +2319,8 @@ static int ov8856_get_hwcfg(struct ov8856 *ov8856) goto check_hwcfg_error; } - dev_dbg(dev, "Using %u data lanes\n", ov8856->cur_mode->data_lanes); + dev_dbg(dev, "Using %u data lanes\n", + bus_cfg.bus.mipi_csi2.num_data_lanes); if (bus_cfg.bus.mipi_csi2.num_data_lanes == 2) ov8856->priv_lane = &lane_cfg_2; diff --git a/drivers/media/i2c/ov8858.c b/drivers/media/i2c/ov8858.c index 3f45f7fab833..0bfc4350a8c9 100644 --- a/drivers/media/i2c/ov8858.c +++ b/drivers/media/i2c/ov8858.c @@ -1409,6 +1409,7 @@ static const struct v4l2_subdev_video_ops ov8858_video_ops = { */ static int ov8858_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -1486,7 +1487,7 @@ static int ov8858_init_state(struct v4l2_subdev *sd, }, }; - ov8858_set_fmt(sd, sd_state, &fmt); + ov8858_set_fmt(sd, NULL, sd_state, &fmt); return 0; } diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c index 289a34a3eeb1..5a59cca8eeae 100644 --- a/drivers/media/i2c/ov8865.c +++ b/drivers/media/i2c/ov8865.c @@ -2702,6 +2702,7 @@ static int ov8865_get_fmt(struct v4l2_subdev *subdev, } static int ov8865_set_fmt(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -2810,6 +2811,7 @@ __ov8865_get_pad_crop(struct ov8865_sensor *sensor, } static int ov8865_get_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/ov9282.c b/drivers/media/i2c/ov9282.c index 5d301660a87d..3204e8d60809 100644 --- a/drivers/media/i2c/ov9282.c +++ b/drivers/media/i2c/ov9282.c @@ -780,6 +780,7 @@ static int ov9282_get_pad_format(struct v4l2_subdev *sd, } static int ov9282_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) { @@ -826,7 +827,7 @@ static int ov9282_init_state(struct v4l2_subdev *sd, ov9282_fill_pad_format(ov9282, &supported_modes[DEFAULT_MODE], ov9282->code, &fmt); - return ov9282_set_pad_format(sd, sd_state, &fmt); + return ov9282_set_pad_format(sd, NULL, sd_state, &fmt); } static const struct v4l2_rect * @@ -845,6 +846,7 @@ __ov9282_get_pad_crop(struct ov9282 *ov9282, } static int ov9282_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/ov9640.c b/drivers/media/i2c/ov9640.c index 122f411044ce..d24b3be1e0b6 100644 --- a/drivers/media/i2c/ov9640.c +++ b/drivers/media/i2c/ov9640.c @@ -519,6 +519,7 @@ static int ov9640_s_fmt(struct v4l2_subdev *sd, } static int ov9640_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -563,6 +564,7 @@ static int ov9640_enum_mbus_code(struct v4l2_subdev *sd, } static int ov9640_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/ov9650.c b/drivers/media/i2c/ov9650.c index 5c85db8a4a38..b596cfb2d7f3 100644 --- a/drivers/media/i2c/ov9650.c +++ b/drivers/media/i2c/ov9650.c @@ -1226,6 +1226,7 @@ static void __ov965x_try_frame_size(struct v4l2_mbus_framefmt *mf, } static int ov965x_set_fmt(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/ov9734.c b/drivers/media/i2c/ov9734.c index 0eaf33807fc9..8f39609ad1b6 100644 --- a/drivers/media/i2c/ov9734.c +++ b/drivers/media/i2c/ov9734.c @@ -681,6 +681,7 @@ static int ov9734_set_stream(struct v4l2_subdev *sd, int enable) } static int ov9734_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/rdacm20.c b/drivers/media/i2c/rdacm20.c index 52e8e2620b4d..0179508626b5 100644 --- a/drivers/media/i2c/rdacm20.c +++ b/drivers/media/i2c/rdacm20.c @@ -442,7 +442,6 @@ static const struct v4l2_subdev_video_ops rdacm20_video_ops = { static const struct v4l2_subdev_pad_ops rdacm20_subdev_pad_ops = { .enum_mbus_code = rdacm20_enum_mbus_code, .get_fmt = rdacm20_get_fmt, - .set_fmt = rdacm20_get_fmt, }; static const struct v4l2_subdev_ops rdacm20_subdev_ops = { diff --git a/drivers/media/i2c/rdacm21.c b/drivers/media/i2c/rdacm21.c index ece8a410e7ce..68d2b9d83c3c 100644 --- a/drivers/media/i2c/rdacm21.c +++ b/drivers/media/i2c/rdacm21.c @@ -322,7 +322,6 @@ static const struct v4l2_subdev_video_ops rdacm21_video_ops = { static const struct v4l2_subdev_pad_ops rdacm21_subdev_pad_ops = { .enum_mbus_code = rdacm21_enum_mbus_code, .get_fmt = rdacm21_get_fmt, - .set_fmt = rdacm21_get_fmt, }; static const struct v4l2_subdev_ops rdacm21_subdev_ops = { diff --git a/drivers/media/i2c/rj54n1cb0c.c b/drivers/media/i2c/rj54n1cb0c.c index 23352d71a108..211f890a9cbd 100644 --- a/drivers/media/i2c/rj54n1cb0c.c +++ b/drivers/media/i2c/rj54n1cb0c.c @@ -541,6 +541,7 @@ static int rj54n1_sensor_scale(struct v4l2_subdev *sd, s32 *in_w, s32 *in_h, s32 *out_w, s32 *out_h); static int rj54n1_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -578,6 +579,7 @@ static int rj54n1_set_selection(struct v4l2_subdev *sd, } static int rj54n1_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -973,6 +975,7 @@ static int rj54n1_reg_init(struct i2c_client *client) } static int rj54n1_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/s5c73m3/s5c73m3-core.c b/drivers/media/i2c/s5c73m3/s5c73m3-core.c index 551387cea521..680add1da244 100644 --- a/drivers/media/i2c/s5c73m3/s5c73m3-core.c +++ b/drivers/media/i2c/s5c73m3/s5c73m3-core.c @@ -1069,6 +1069,7 @@ static int s5c73m3_oif_get_fmt(struct v4l2_subdev *sd, } static int s5c73m3_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -1108,6 +1109,7 @@ static int s5c73m3_set_fmt(struct v4l2_subdev *sd, } static int s5c73m3_oif_set_fmt(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/s5k3m5.c b/drivers/media/i2c/s5k3m5.c index c591b580d2e7..bdf5bea78685 100644 --- a/drivers/media/i2c/s5k3m5.c +++ b/drivers/media/i2c/s5k3m5.c @@ -958,6 +958,7 @@ static void s5k3m5_update_pad_format(struct s5k3m5 *s5k3m5, } static int s5k3m5_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -1038,6 +1039,7 @@ static int s5k3m5_enum_frame_size(struct v4l2_subdev *sd, } static int s5k3m5_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1075,7 +1077,7 @@ static int s5k3m5_init_state(struct v4l2_subdev *sd, }, }; - s5k3m5_set_pad_format(sd, state, &fmt); + s5k3m5_set_pad_format(sd, NULL, state, &fmt); return 0; } diff --git a/drivers/media/i2c/s5k5baf.c b/drivers/media/i2c/s5k5baf.c index 378d273055ee..b13c4754ec3c 100644 --- a/drivers/media/i2c/s5k5baf.c +++ b/drivers/media/i2c/s5k5baf.c @@ -1307,6 +1307,7 @@ static int s5k5baf_get_fmt(struct v4l2_subdev *sd, } static int s5k5baf_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -1370,6 +1371,7 @@ static int s5k5baf_is_bound_target(u32 target) } static int s5k5baf_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1463,6 +1465,7 @@ static bool s5k5baf_cmp_rect(const struct v4l2_rect *r1, } static int s5k5baf_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/s5k6a3.c b/drivers/media/i2c/s5k6a3.c index ba6477e88da3..071b31728a00 100644 --- a/drivers/media/i2c/s5k6a3.c +++ b/drivers/media/i2c/s5k6a3.c @@ -131,6 +131,7 @@ static struct v4l2_mbus_framefmt *__s5k6a3_get_format( } static int s5k6a3_set_fmt(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/s5kjn1.c b/drivers/media/i2c/s5kjn1.c index a707cb740556..80d713ba3a1e 100644 --- a/drivers/media/i2c/s5kjn1.c +++ b/drivers/media/i2c/s5kjn1.c @@ -985,6 +985,7 @@ static void s5kjn1_update_pad_format(struct s5kjn1 *s5kjn1, } static int s5kjn1_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -1065,6 +1066,7 @@ static int s5kjn1_enum_frame_size(struct v4l2_subdev *sd, } static int s5kjn1_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1102,7 +1104,7 @@ static int s5kjn1_init_state(struct v4l2_subdev *sd, }, }; - s5kjn1_set_pad_format(sd, state, &fmt); + s5kjn1_set_pad_format(sd, NULL, state, &fmt); return 0; } diff --git a/drivers/media/i2c/saa6752hs.c b/drivers/media/i2c/saa6752hs.c index c6bf0b0902e8..6e2a3e4a63f7 100644 --- a/drivers/media/i2c/saa6752hs.c +++ b/drivers/media/i2c/saa6752hs.c @@ -563,6 +563,7 @@ static int saa6752hs_get_fmt(struct v4l2_subdev *sd, } static int saa6752hs_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/saa7115.c b/drivers/media/i2c/saa7115.c index d20695238b48..88b159418c7c 100644 --- a/drivers/media/i2c/saa7115.c +++ b/drivers/media/i2c/saa7115.c @@ -1159,6 +1159,7 @@ static int saa711x_s_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_f } static int saa711x_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/saa717x.c b/drivers/media/i2c/saa717x.c index 0536ceb54650..367539667948 100644 --- a/drivers/media/i2c/saa717x.c +++ b/drivers/media/i2c/saa717x.c @@ -980,6 +980,7 @@ static int saa717x_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_regi #endif static int saa717x_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/st-mipid02.c b/drivers/media/i2c/st-mipid02.c index 4675181af5fb..f9eabd9632d3 100644 --- a/drivers/media/i2c/st-mipid02.c +++ b/drivers/media/i2c/st-mipid02.c @@ -597,6 +597,7 @@ static int mipid02_enum_mbus_code(struct v4l2_subdev *sd, } static int mipid02_set_fmt(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/t4ka3.c b/drivers/media/i2c/t4ka3.c index a5a68e3fbec2..6506225683bb 100644 --- a/drivers/media/i2c/t4ka3.c +++ b/drivers/media/i2c/t4ka3.c @@ -363,6 +363,7 @@ static void t4ka3_get_vblank_limits(struct t4ka3_data *sensor, } static int t4ka3_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) { @@ -626,6 +627,7 @@ static int t4ka3_disable_stream(struct v4l2_subdev *sd, } static int t4ka3_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -651,6 +653,7 @@ static int t4ka3_get_selection(struct v4l2_subdev *sd, } static int t4ka3_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -1034,7 +1037,6 @@ err_media_entity: err_pm_disable: pm_runtime_disable(&client->dev); - pm_runtime_put_noidle(&client->dev); t4ka3_pm_suspend(&client->dev); return ret; diff --git a/drivers/media/i2c/tc358743.c b/drivers/media/i2c/tc358743.c index fbd38bbfee03..d9cd0563d50b 100644 --- a/drivers/media/i2c/tc358743.c +++ b/drivers/media/i2c/tc358743.c @@ -453,7 +453,7 @@ static void tc358743_enable_edid(struct v4l2_subdev *sd) /* Enable hotplug after 143 ms. DDC access to EDID is also enabled when * hotplug is enabled. See register DDC_CTL */ - schedule_delayed_work(&state->delayed_work_enable_hotplug, HZ / 7); + schedule_delayed_work(&state->delayed_work_enable_hotplug, V4L2_SET_EDID_HPD_LOW_JIFFIES); tc358743_enable_interrupts(sd, true); tc358743_s_ctrl_detect_tx_5v(sd); @@ -1373,7 +1373,8 @@ static int tc358743_log_status(struct v4l2_subdev *sd) static const char * const input_color_space[] = { "RGB", "YCbCr 601", "opRGB", "YCbCr 709", "NA (4)", "xvYCC 601", "NA(6)", "xvYCC 709", "NA(8)", "sYCC601", - "NA(10)", "NA(11)", "NA(12)", "opYCC 601"}; + "NA(10)", "NA(11)", "NA(12)", "opYCC 601", "NA(14)", + "NA(15)"}; v4l2_info(sd, "-----Chip status-----\n"); v4l2_info(sd, "Chip ID: 0x%02x\n", @@ -1816,6 +1817,7 @@ static int tc358743_get_fmt(struct v4l2_subdev *sd, } static int tc358743_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/tc358746.c b/drivers/media/i2c/tc358746.c index 86d9ba3ea4e5..d55539c35a26 100644 --- a/drivers/media/i2c/tc358746.c +++ b/drivers/media/i2c/tc358746.c @@ -873,6 +873,7 @@ static int tc358746_enum_mbus_code(struct v4l2_subdev *sd, } static int tc358746_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/tda1997x.c b/drivers/media/i2c/tda1997x.c index afa1d6f34c9c..30dfd8a7b42b 100644 --- a/drivers/media/i2c/tda1997x.c +++ b/drivers/media/i2c/tda1997x.c @@ -590,7 +590,7 @@ static void tda1997x_enable_edid(struct v4l2_subdev *sd) v4l2_dbg(1, debug, sd, "%s\n", __func__); /* Enable hotplug after 143ms */ - schedule_delayed_work(&state->delayed_work_enable_hpd, HZ / 7); + schedule_delayed_work(&state->delayed_work_enable_hpd, V4L2_SET_EDID_HPD_LOW_JIFFIES); } /* ----------------------------------------------------------------------------- @@ -1798,6 +1798,7 @@ static int tda1997x_get_format(struct v4l2_subdev *sd, } static int tda1997x_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/thp7312.c b/drivers/media/i2c/thp7312.c index 775cfba188d8..f1c7f149c06c 100644 --- a/drivers/media/i2c/thp7312.c +++ b/drivers/media/i2c/thp7312.c @@ -729,6 +729,7 @@ static int thp7312_enum_frame_interval(struct v4l2_subdev *sd, } static int thp7312_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/ths7303.c b/drivers/media/i2c/ths7303.c index fa5b9c884c1d..e04da5519a39 100644 --- a/drivers/media/i2c/ths7303.c +++ b/drivers/media/i2c/ths7303.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * ths7303/53- THS7303/53 Video Amplifier driver * @@ -10,15 +11,6 @@ * Hans Verkuil <hverkuil@kernel.org> * Lad, Prabhakar <prabhakar.lad@ti.com> * Martin Bugge <marbugge@cisco.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 <linux/i2c.h> diff --git a/drivers/media/i2c/ths8200.c b/drivers/media/i2c/ths8200.c index 808ef16ec3b3..00535958faf1 100644 --- a/drivers/media/i2c/ths8200.c +++ b/drivers/media/i2c/ths8200.c @@ -1,20 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * ths8200 - Texas Instruments THS8200 video encoder driver * * Copyright 2013 Cisco Systems, Inc. and/or its affiliates. - * - * This program is free software; you may redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * 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 <linux/i2c.h> diff --git a/drivers/media/i2c/ths8200_regs.h b/drivers/media/i2c/ths8200_regs.h index 6bc9fd1111db..6a96b6a94406 100644 --- a/drivers/media/i2c/ths8200_regs.h +++ b/drivers/media/i2c/ths8200_regs.h @@ -1,20 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * ths8200 - Texas Instruments THS8200 video encoder driver * * Copyright 2013 Cisco Systems, Inc. and/or its affiliates. - * - * This program is free software; you may redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * 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. */ #ifndef THS8200_REGS_H diff --git a/drivers/media/i2c/tvp514x.c b/drivers/media/i2c/tvp514x.c index 99ace2acdb35..1e927896a4b3 100644 --- a/drivers/media/i2c/tvp514x.c +++ b/drivers/media/i2c/tvp514x.c @@ -879,6 +879,7 @@ static int tvp514x_get_pad_format(struct v4l2_subdev *sd, } static int tvp514x_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/tvp5150.c b/drivers/media/i2c/tvp5150.c index 9c204f38935d..91064f06f8c1 100644 --- a/drivers/media/i2c/tvp5150.c +++ b/drivers/media/i2c/tvp5150.c @@ -1104,6 +1104,7 @@ static void tvp5150_set_hw_selection(struct v4l2_subdev *sd, } static int tvp5150_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1156,6 +1157,7 @@ static int tvp5150_set_selection(struct v4l2_subdev *sd, } static int tvp5150_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1724,7 +1726,6 @@ static const struct v4l2_subdev_vbi_ops tvp5150_vbi_ops = { static const struct v4l2_subdev_pad_ops tvp5150_pad_ops = { .enum_mbus_code = tvp5150_enum_mbus_code, .enum_frame_size = tvp5150_enum_frame_size, - .set_fmt = tvp5150_fill_fmt, .get_fmt = tvp5150_fill_fmt, .get_selection = tvp5150_get_selection, .set_selection = tvp5150_set_selection, diff --git a/drivers/media/i2c/tvp7002.c b/drivers/media/i2c/tvp7002.c index 3979ccde5a95..ada28cb8af7c 100644 --- a/drivers/media/i2c/tvp7002.c +++ b/drivers/media/i2c/tvp7002.c @@ -854,6 +854,7 @@ tvp7002_get_pad_format(struct v4l2_subdev *sd, */ static int tvp7002_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/tw9900.c b/drivers/media/i2c/tw9900.c index 617fcf7f0b45..60c102aba750 100644 --- a/drivers/media/i2c/tw9900.c +++ b/drivers/media/i2c/tw9900.c @@ -198,6 +198,7 @@ static int tw9900_get_fmt(struct v4l2_subdev *sd, } static int tw9900_set_fmt(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/tw9910.c b/drivers/media/i2c/tw9910.c index 872207e688bf..731c97a40c82 100644 --- a/drivers/media/i2c/tw9910.c +++ b/drivers/media/i2c/tw9910.c @@ -715,6 +715,7 @@ tw9910_set_fmt_error: } static int tw9910_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -792,6 +793,7 @@ static int tw9910_s_fmt(struct v4l2_subdev *sd, } static int tw9910_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/vd55g1.c b/drivers/media/i2c/vd55g1.c index 6f458f611f63..22b1497e8851 100644 --- a/drivers/media/i2c/vd55g1.c +++ b/drivers/media/i2c/vd55g1.c @@ -1255,6 +1255,7 @@ static int vd55g1_patch(struct vd55g1 *sensor) } static int vd55g1_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1332,6 +1333,7 @@ static int vd55g1_new_format_change_controls(struct vd55g1 *sensor, } static int vd55g1_set_pad_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *sd_fmt) { @@ -1402,7 +1404,7 @@ static int vd55g1_init_state(struct v4l2_subdev *sd, fmt.format.width = vd55g1_supported_modes[VD55G1_MODE_IDX_DEF].width; fmt.format.height = vd55g1_supported_modes[VD55G1_MODE_IDX_DEF].height; - return vd55g1_set_pad_fmt(sd, sd_state, &fmt); + return vd55g1_set_pad_fmt(sd, NULL, sd_state, &fmt); } static int vd55g1_enum_frame_size(struct v4l2_subdev *sd, diff --git a/drivers/media/i2c/vd56g3.c b/drivers/media/i2c/vd56g3.c index 157acea9e286..ca0dd1b24072 100644 --- a/drivers/media/i2c/vd56g3.c +++ b/drivers/media/i2c/vd56g3.c @@ -823,6 +823,7 @@ static void vd56g3_update_img_pad_format(struct vd56g3 *sensor, } static int vd56g3_set_pad_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *sd_fmt) { @@ -858,6 +859,7 @@ static int vd56g3_set_pad_fmt(struct v4l2_subdev *sd, } static int vd56g3_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1038,7 +1040,7 @@ static int vd56g3_init_state(struct v4l2_subdev *sd, }, }; - return vd56g3_set_pad_fmt(sd, sd_state, &fmt); + return vd56g3_set_pad_fmt(sd, NULL, sd_state, &fmt); } static const struct v4l2_subdev_video_ops vd56g3_video_ops = { diff --git a/drivers/media/i2c/vgxy61.c b/drivers/media/i2c/vgxy61.c index 3fb2166c81ef..e7819691723d 100644 --- a/drivers/media/i2c/vgxy61.c +++ b/drivers/media/i2c/vgxy61.c @@ -652,6 +652,7 @@ static int vgxy61_try_fmt_internal(struct v4l2_subdev *sd, } static int vgxy61_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1197,6 +1198,7 @@ static int vgxy61_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, } static int vgxy61_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -1260,7 +1262,7 @@ static int vgxy61_init_state(struct v4l2_subdev *sd, vgxy61_fill_framefmt(sensor, sensor->current_mode, &fmt.format, VGXY61_MEDIA_BUS_FMT_DEF); - return vgxy61_set_fmt(sd, sd_state, &fmt); + return vgxy61_set_fmt(sd, NULL, sd_state, &fmt); } static int vgxy61_s_ctrl(struct v4l2_ctrl *ctrl) diff --git a/drivers/media/i2c/wm8739.c b/drivers/media/i2c/wm8739.c index db62bafb447c..9eaf48f1c3a2 100644 --- a/drivers/media/i2c/wm8739.c +++ b/drivers/media/i2c/wm8739.c @@ -217,7 +217,7 @@ static int wm8739_probe(struct i2c_client *client) /* reset */ wm8739_write(sd, R15, 0x00); - /* filter setting, high path, offet clear */ + /* filter setting, high path, offset clear */ wm8739_write(sd, R5, 0x000); /* ADC, OSC, Power Off mode Disable */ wm8739_write(sd, R6, 0x000); diff --git a/drivers/media/pci/bt8xx/bttv-driver.c b/drivers/media/pci/bt8xx/bttv-driver.c index c631b8bbd386..8b9ca59f9434 100644 --- a/drivers/media/pci/bt8xx/bttv-driver.c +++ b/drivers/media/pci/bt8xx/bttv-driver.c @@ -3504,7 +3504,7 @@ static void bttv_remove(struct pci_dev *pci_dev) return; } -static int __maybe_unused bttv_suspend(struct device *dev) +static int bttv_suspend(struct device *dev) { struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); struct bttv *btv = to_bttv(v4l2_dev); @@ -3535,7 +3535,7 @@ static int __maybe_unused bttv_suspend(struct device *dev) return 0; } -static int __maybe_unused bttv_resume(struct device *dev) +static int bttv_resume(struct device *dev) { struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); struct bttv *btv = to_bttv(v4l2_dev); @@ -3573,7 +3573,7 @@ static const struct pci_device_id bttv_pci_tbl[] = { MODULE_DEVICE_TABLE(pci, bttv_pci_tbl); -static SIMPLE_DEV_PM_OPS(bttv_pm_ops, +static DEFINE_SIMPLE_DEV_PM_OPS(bttv_pm_ops, bttv_suspend, bttv_resume); @@ -3582,7 +3582,7 @@ static struct pci_driver bttv_pci_driver = { .id_table = bttv_pci_tbl, .probe = bttv_probe, .remove = bttv_remove, - .driver.pm = &bttv_pm_ops, + .driver.pm = pm_sleep_ptr(&bttv_pm_ops), }; static int __init bttv_init_module(void) diff --git a/drivers/media/pci/cobalt/cobalt-driver.c b/drivers/media/pci/cobalt/cobalt-driver.c index 7b1ca1238c8d..ca96cbe4f194 100644 --- a/drivers/media/pci/cobalt/cobalt-driver.c +++ b/drivers/media/pci/cobalt/cobalt-driver.c @@ -524,8 +524,8 @@ static int cobalt_subdevs_init(struct cobalt *cobalt) &cobalt_edid); if (err) return err; - err = v4l2_subdev_call(s[i].sd, pad, set_fmt, NULL, - &sd_fmt); + err = v4l2_subdev_call(s[i].sd, pad, set_fmt, NULL, NULL, + &sd_fmt); if (err) return err; /* Reset channel video module */ @@ -608,8 +608,8 @@ static int cobalt_subdevs_hsma_init(struct cobalt *cobalt) if (err) return err; - err = v4l2_subdev_call(s->sd, pad, set_fmt, NULL, - &sd_fmt); + err = v4l2_subdev_call(s->sd, pad, set_fmt, NULL, NULL, + &sd_fmt); if (err) return err; cobalt->have_hsma_rx = true; diff --git a/drivers/media/pci/cobalt/cobalt-v4l2.c b/drivers/media/pci/cobalt/cobalt-v4l2.c index 51fd9576c6c2..ad89e62a2747 100644 --- a/drivers/media/pci/cobalt/cobalt-v4l2.c +++ b/drivers/media/pci/cobalt/cobalt-v4l2.c @@ -172,7 +172,7 @@ static void cobalt_enable_output(struct cobalt_stream *s) sd_fmt.format.code = MEDIA_BUS_FMT_RGB888_1X24; break; } - v4l2_subdev_call(s->sd, pad, set_fmt, NULL, &sd_fmt); + v4l2_subdev_call(s->sd, pad, set_fmt, NULL, NULL, &sd_fmt); iowrite32(0, &vo->control); /* 1080p60 */ @@ -223,14 +223,14 @@ static void cobalt_enable_input(struct cobalt_stream *s) iowrite32(M00235_CONTROL_BITMAP_ENABLE_MSK | (1 << M00235_CONTROL_BITMAP_PACK_FORMAT_OFST), &packer->control); - v4l2_subdev_call(s->sd, pad, set_fmt, NULL, + v4l2_subdev_call(s->sd, pad, set_fmt, NULL, NULL, &sd_fmt_yuyv); break; case V4L2_PIX_FMT_RGB24: iowrite32(M00235_CONTROL_BITMAP_ENABLE_MSK | (2 << M00235_CONTROL_BITMAP_PACK_FORMAT_OFST), &packer->control); - v4l2_subdev_call(s->sd, pad, set_fmt, NULL, + v4l2_subdev_call(s->sd, pad, set_fmt, NULL, NULL, &sd_fmt_rgb); break; case V4L2_PIX_FMT_BGR32: @@ -238,7 +238,7 @@ static void cobalt_enable_input(struct cobalt_stream *s) M00235_CONTROL_BITMAP_ENDIAN_FORMAT_MSK | (3 << M00235_CONTROL_BITMAP_PACK_FORMAT_OFST), &packer->control); - v4l2_subdev_call(s->sd, pad, set_fmt, NULL, + v4l2_subdev_call(s->sd, pad, set_fmt, NULL, NULL, &sd_fmt_rgb); break; } @@ -938,7 +938,7 @@ static int cobalt_s_fmt_vid_out(struct file *file, void *priv, s->ycbcr_enc = pix->ycbcr_enc; s->quantization = pix->quantization; v4l2_fill_mbus_format(&sd_fmt.format, pix, code); - v4l2_subdev_call(s->sd, pad, set_fmt, NULL, &sd_fmt); + v4l2_subdev_call(s->sd, pad, set_fmt, NULL, NULL, &sd_fmt); return 0; } diff --git a/drivers/media/pci/cx18/cx18-av-core.c b/drivers/media/pci/cx18/cx18-av-core.c index 4fb19d26ee29..f5961853eeae 100644 --- a/drivers/media/pci/cx18/cx18-av-core.c +++ b/drivers/media/pci/cx18/cx18-av-core.c @@ -930,6 +930,7 @@ static int cx18_av_s_ctrl(struct v4l2_ctrl *ctrl) } static int cx18_av_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/pci/cx18/cx18-controls.c b/drivers/media/pci/cx18/cx18-controls.c index 78eadad8b6e8..41f388ba22ab 100644 --- a/drivers/media/pci/cx18/cx18-controls.c +++ b/drivers/media/pci/cx18/cx18-controls.c @@ -85,7 +85,7 @@ static int cx18_s_video_encoding(struct cx2341x_handler *cxhdl, u32 val) fmt->width = cxhdl->width / (is_mpeg1 ? 2 : 1); fmt->height = cxhdl->height; fmt->code = MEDIA_BUS_FMT_FIXED; - v4l2_subdev_call(cx->sd_av, pad, set_fmt, NULL, &format); + v4l2_subdev_call(cx->sd_av, pad, set_fmt, NULL, NULL, &format); return 0; } diff --git a/drivers/media/pci/cx18/cx18-driver.c b/drivers/media/pci/cx18/cx18-driver.c index 214fac7af61e..ac90ce6cd044 100644 --- a/drivers/media/pci/cx18/cx18-driver.c +++ b/drivers/media/pci/cx18/cx18-driver.c @@ -805,12 +805,12 @@ static int cx18_setup_pci(struct cx18 *cx, struct pci_dev *pci_dev, } if (dma_set_mask(&pci_dev->dev, DMA_BIT_MASK(32))) { CX18_ERR("No suitable DMA available, card %d\n", cx->instance); - return -EIO; + goto err_disable_device; } if (!request_mem_region(cx->base_addr, CX18_MEM_SIZE, "cx18 encoder")) { CX18_ERR("Cannot request encoder memory region, card %d\n", cx->instance); - return -EIO; + goto err_disable_device; } /* Enable bus mastering and memory mapped IO for the CX23418 */ @@ -834,6 +834,10 @@ static int cx18_setup_pci(struct cx18 *cx, struct pci_dev *pci_dev, cx->pci_dev->irq, pci_latency, (u64)cx->base_addr); return 0; + +err_disable_device: + pci_disable_device(pci_dev); + return -EIO; } static void cx18_init_subdevs(struct cx18 *cx) @@ -1120,6 +1124,7 @@ free_map: cx18_iounmap(cx); free_mem: release_mem_region(cx->base_addr, CX18_MEM_SIZE); + pci_disable_device(pci_dev); free_workqueues: destroy_workqueue(cx->in_work_queue); err: diff --git a/drivers/media/pci/cx18/cx18-ioctl.c b/drivers/media/pci/cx18/cx18-ioctl.c index 0d676a57e24e..6f8d7f700016 100644 --- a/drivers/media/pci/cx18/cx18-ioctl.c +++ b/drivers/media/pci/cx18/cx18-ioctl.c @@ -150,7 +150,7 @@ static int cx18_s_fmt_vid_cap(struct file *file, void *fh, format.format.width = cx->cxhdl.width = w; format.format.height = cx->cxhdl.height = h; format.format.code = MEDIA_BUS_FMT_FIXED; - v4l2_subdev_call(cx->sd_av, pad, set_fmt, NULL, &format); + v4l2_subdev_call(cx->sd_av, pad, set_fmt, NULL, NULL, &format); return cx18_g_fmt_vid_cap(file, fh, fmt); } diff --git a/drivers/media/pci/cx18/cx18-queue.c b/drivers/media/pci/cx18/cx18-queue.c index 04d6828f0259..a973367d4cde 100644 --- a/drivers/media/pci/cx18/cx18-queue.c +++ b/drivers/media/pci/cx18/cx18-queue.c @@ -114,7 +114,10 @@ static inline void cx18_mdl_update_bufs_for_cpu(struct cx18_stream *s, if (list_is_singular(&mdl->buf_list)) { buf = list_first_entry(&mdl->buf_list, struct cx18_buffer, list); - buf->bytesused = mdl->bytesused; + if (mdl->bytesused > s->buf_size) + buf->bytesused = s->buf_size; + else + buf->bytesused = mdl->bytesused; buf->readpos = 0; cx18_buf_sync_for_cpu(s, buf); } else { diff --git a/drivers/media/pci/cx18/cx23418.h b/drivers/media/pci/cx18/cx23418.h index 22486f39bcda..4ee45be757cb 100644 --- a/drivers/media/pci/cx18/cx23418.h +++ b/drivers/media/pci/cx18/cx23418.h @@ -24,7 +24,7 @@ #define CX18_CREATE_TASK (MGR_CMD_MASK | 0x0001) /* Description: This command destroys an instance of a task - IN[0] - Task handle. Hanlde of the task to destroy + IN[0] - Task handle. Handle of the task to destroy ReturnCode - One of the ERR_SYS_... */ #define CX18_DESTROY_TASK (MGR_CMD_MASK | 0x0002) diff --git a/drivers/media/pci/cx23885/cx23885-core.c b/drivers/media/pci/cx23885/cx23885-core.c index 5fb26285e4af..7498091176b7 100644 --- a/drivers/media/pci/cx23885/cx23885-core.c +++ b/drivers/media/pci/cx23885/cx23885-core.c @@ -2246,6 +2246,10 @@ static void cx23885_finidev(struct pci_dev *pci_dev) /* unregister stuff */ free_irq(pci_dev->irq, dev); + cancel_work_sync(&dev->cx25840_work); + cancel_work_sync(&dev->ir_rx_work); + cancel_work_sync(&dev->ir_tx_work); + pci_disable_device(pci_dev); cx23885_dev_unregister(dev); diff --git a/drivers/media/pci/cx23885/cx23885-dvb.c b/drivers/media/pci/cx23885/cx23885-dvb.c index f240ccda40ed..348abde0ba26 100644 --- a/drivers/media/pci/cx23885/cx23885-dvb.c +++ b/drivers/media/pci/cx23885/cx23885-dvb.c @@ -1158,8 +1158,12 @@ static int dvb_register_ci_mac(struct cx23885_tsport *port) info.platform_data = &sp2_config; request_module(info.type); client_ci = i2c_new_client_device(&i2c_bus->i2c_adap, &info); - if (!i2c_client_has_driver(client_ci)) + if (IS_ERR(client_ci)) + return PTR_ERR(client_ci); + if (!client_ci->dev.driver) { + i2c_unregister_device(client_ci); return -ENODEV; + } if (!try_module_get(client_ci->dev.driver->owner)) { i2c_unregister_device(client_ci); return -ENODEV; @@ -1202,7 +1206,7 @@ static int dvb_register(struct cx23885_tsport *port) int (*p_set_voltage)(struct dvb_frontend *fe, enum fe_sec_voltage voltage) = NULL; int mfe_shared = 0; /* bus not shared by default */ - int ret; + int ret = -EINVAL; /* Get the first frontend */ fe0 = vb2_dvb_get_frontend(&port->frontends, 1); @@ -2586,8 +2590,10 @@ static int dvb_register(struct cx23885_tsport *port) goto frontend_detach; ret = dvb_register_ci_mac(port); - if (ret) + if (ret) { + vb2_dvb_unregister_bus(&port->frontends); goto frontend_detach; + } return 0; @@ -2618,7 +2624,7 @@ frontend_detach: port->gate_ctrl = NULL; vb2_dvb_dealloc_frontends(&port->frontends); - return -EINVAL; + return ret; } int cx23885_dvb_register(struct cx23885_tsport *port) diff --git a/drivers/media/pci/cx23885/cx23885-video.c b/drivers/media/pci/cx23885/cx23885-video.c index 14d219fd1d8a..94ce00154e1f 100644 --- a/drivers/media/pci/cx23885/cx23885-video.c +++ b/drivers/media/pci/cx23885/cx23885-video.c @@ -134,7 +134,7 @@ int cx23885_set_tvnorm(struct cx23885_dev *dev, v4l2_std_id norm) format.format.width = dev->width; format.format.height = dev->height; format.format.field = dev->field; - call_all(dev, pad, set_fmt, NULL, &format); + call_all(dev, pad, set_fmt, NULL, NULL, &format); return 0; } @@ -619,7 +619,7 @@ static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, dprintk(2, "%s() width=%d height=%d field=%d\n", __func__, dev->width, dev->height, dev->field); v4l2_fill_mbus_format(&format.format, &f->fmt.pix, MEDIA_BUS_FMT_FIXED); - call_all(dev, pad, set_fmt, NULL, &format); + call_all(dev, pad, set_fmt, NULL, NULL, &format); v4l2_fill_pix_format(&f->fmt.pix, &format.format); /* set_fmt overwrites f->fmt.pix.field, restore it */ f->fmt.pix.field = dev->field; diff --git a/drivers/media/pci/cx88/cx88-input.c b/drivers/media/pci/cx88/cx88-input.c index 5d9ce4f9af01..c96d289e5ab7 100644 --- a/drivers/media/pci/cx88/cx88-input.c +++ b/drivers/media/pci/cx88/cx88-input.c @@ -259,7 +259,7 @@ static void cx88_ir_close(struct rc_dev *rc) int cx88_ir_init(struct cx88_core *core, struct pci_dev *pci) { struct cx88_IR *ir; - struct rc_dev *dev; + struct rc_dev *dev = NULL; char *ir_codes = NULL; u64 rc_proto = RC_PROTO_BIT_OTHER; int err = -ENOMEM; @@ -268,11 +268,8 @@ int cx88_ir_init(struct cx88_core *core, struct pci_dev *pci) */ ir = kzalloc_obj(*ir); - dev = rc_allocate_device(RC_DRIVER_IR_RAW); - if (!ir || !dev) - goto err_out_free; - - ir->dev = dev; + if (!ir) + return -ENOMEM; /* detect & configure */ switch (core->boardnr) { @@ -439,6 +436,13 @@ int cx88_ir_init(struct cx88_core *core, struct pci_dev *pci) goto err_out_free; } + dev = rc_allocate_device(ir->sampling ? + RC_DRIVER_IR_RAW : RC_DRIVER_SCANCODE); + if (!dev) + goto err_out_free; + + ir->dev = dev; + /* * The usage of mask_keycode were very convenient, due to several * reasons. Among others, the scancode tables were using the scancode @@ -477,12 +481,10 @@ int cx88_ir_init(struct cx88_core *core, struct pci_dev *pci) dev->close = cx88_ir_close; dev->scancode_mask = hardware_mask; - if (ir->sampling) { + if (ir->sampling) dev->timeout = MS_TO_US(10); /* 10 ms */ - } else { - dev->driver_type = RC_DRIVER_SCANCODE; + else dev->allowed_protocols = rc_proto; - } ir->core = core; core->ir = ir; diff --git a/drivers/media/pci/cx88/cx88-mpeg.c b/drivers/media/pci/cx88/cx88-mpeg.c index 0c07ad335799..cd2366768f4a 100644 --- a/drivers/media/pci/cx88/cx88-mpeg.c +++ b/drivers/media/pci/cx88/cx88-mpeg.c @@ -392,7 +392,8 @@ static int cx8802_init_common(struct cx8802_dev *dev) err = dma_set_mask(&dev->pci->dev, DMA_BIT_MASK(32)); if (err) { pr_err("Oops: no 32bit PCI DMA ???\n"); - return -EIO; + err = -EIO; + goto fail_disable_device; } dev->pci_rev = dev->pci->revision; @@ -413,13 +414,17 @@ static int cx8802_init_common(struct cx8802_dev *dev) IRQF_SHARED, dev->core->name, dev); if (err < 0) { pr_err("can't get IRQ %d\n", dev->pci->irq); - return err; + goto fail_disable_device; } cx_set(MO_PCI_INTMSK, core->pci_irqmask); /* everything worked */ pci_set_drvdata(dev->pci, dev); return 0; + +fail_disable_device: + pci_disable_device(dev->pci); + return err; } static void cx8802_fini_common(struct cx8802_dev *dev) diff --git a/drivers/media/pci/cx88/cx88-video.c b/drivers/media/pci/cx88/cx88-video.c index eaa46a2f92e7..f8f2843495f3 100644 --- a/drivers/media/pci/cx88/cx88-video.c +++ b/drivers/media/pci/cx88/cx88-video.c @@ -1549,7 +1549,7 @@ static void cx8800_finidev(struct pci_dev *pci_dev) kfree(dev); } -static int __maybe_unused cx8800_suspend(struct device *dev_d) +static int cx8800_suspend(struct device *dev_d) { struct cx8800_dev *dev = dev_get_drvdata(dev_d); struct cx88_core *core = dev->core; @@ -1576,7 +1576,7 @@ static int __maybe_unused cx8800_suspend(struct device *dev_d) return 0; } -static int __maybe_unused cx8800_resume(struct device *dev_d) +static int cx8800_resume(struct device *dev_d) { struct cx8800_dev *dev = dev_get_drvdata(dev_d); struct cx88_core *core = dev->core; @@ -1617,14 +1617,14 @@ static const struct pci_device_id cx8800_pci_tbl[] = { }; MODULE_DEVICE_TABLE(pci, cx8800_pci_tbl); -static SIMPLE_DEV_PM_OPS(cx8800_pm_ops, cx8800_suspend, cx8800_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(cx8800_pm_ops, cx8800_suspend, cx8800_resume); static struct pci_driver cx8800_pci_driver = { .name = "cx8800", .id_table = cx8800_pci_tbl, .probe = cx8800_initdev, .remove = cx8800_finidev, - .driver.pm = &cx8800_pm_ops, + .driver.pm = pm_sleep_ptr(&cx8800_pm_ops), }; module_pci_driver(cx8800_pci_driver); diff --git a/drivers/media/pci/hws/hws.h b/drivers/media/pci/hws/hws.h index 8fbe1fe27844..d87d52674b69 100644 --- a/drivers/media/pci/hws/hws.h +++ b/drivers/media/pci/hws/hws.h @@ -33,8 +33,8 @@ struct hws_pix_state { u32 sizeimage; /* full frame */ enum v4l2_field field; /* V4L2_FIELD_NONE or INTERLACED */ enum v4l2_colorspace colorspace; /* e.g., REC709 */ - enum v4l2_ycbcr_encoding ycbcr_enc; /* V4L2_YCBCR_ENC_DEFAULT */ - enum v4l2_quantization quantization; /* V4L2_QUANTIZATION_LIM_RANGE */ + enum v4l2_ycbcr_encoding ycbcr_enc; /* V4L2_YCBCR_ENC_601 */ + enum v4l2_quantization quantization; /* V4L2_QUANTIZATION_FULL_RANGE */ enum v4l2_xfer_func xfer_func; /* V4L2_XFER_FUNC_DEFAULT */ bool interlaced; /* cached hardware state */ u32 half_size; /* hardware half-frame size */ diff --git a/drivers/media/pci/hws/hws_pci.c b/drivers/media/pci/hws/hws_pci.c index 30bb7d34465b..f06e60dc2ee6 100644 --- a/drivers/media/pci/hws/hws_pci.c +++ b/drivers/media/pci/hws/hws_pci.c @@ -33,8 +33,8 @@ static unsigned long long hws_elapsed_us(u64 start_ns) } /* register layout inside HWS_REG_DEVICE_INFO */ -#define DEVINFO_VER GENMASK(7, 0) -#define DEVINFO_SUBVER GENMASK(15, 8) +#define DEVINFO_VER GENMASK(15, 8) +#define DEVINFO_SUBVER GENMASK(23, 16) #define DEVINFO_YV12 GENMASK(31, 28) #define DEVINFO_HWKEY GENMASK(27, 24) #define DEVINFO_PORTID GENMASK(25, 24) /* low 2 bits of HW-key */ @@ -441,7 +441,7 @@ static int hws_probe(struct pci_dev *pdev, const struct pci_device_id *pci_id) hws_init_video_sys(hws, false); /* 5) Init channels (video state, locks, vb2, ctrls) */ - for (i = 0; i < hws->max_channels; i++) { + for (i = 0; i < hws->cur_max_video_ch; i++) { ret = hws_video_init_channel(hws, i); if (ret) { dev_err(&pdev->dev, "video channel init failed (ch=%d)\n", i); diff --git a/drivers/media/pci/hws/hws_reg.h b/drivers/media/pci/hws/hws_reg.h index e4fb4af44434..ba1d22f4376c 100644 --- a/drivers/media/pci/hws/hws_reg.h +++ b/drivers/media/pci/hws/hws_reg.h @@ -70,7 +70,8 @@ #define HWS_CTL_IRQ_ENABLE_BIT BIT(0) /* Global interrupt enable bit */ /* Write 0x00 to fully reset decoder, * set bit 31=1 to "start run", - * low byte=0x13 selects YUYV/BT.709/etc, + * low byte=0x13 is the vendor baseline capture-mode value; its individual + * bit meanings are not documented, * in ReadChipId() we also write 0x00 and 0x10 here for chip-ID sequencing. */ @@ -121,9 +122,10 @@ #define HWS_REG_DEVICE_INFO (CVBS_IN_BASE + 88 * PCIE_BARADDROFSIZE) /* * Reading this 32-bit word returns: - * bits 7:0 = "device version" - * bits 15:8 = "device sub-version" - * bits 23:24 = "HW key / port ID" etc. + * bits 7:0 = unused by the baseline driver + * bits 15:8 = device version + * bits 23:16 = device sub-version + * bits 27:24 = HW key (port ID in bits 25:24) * bits 31:28 = "support YV12" flags */ diff --git a/drivers/media/pci/hws/hws_v4l2_ioctl.c b/drivers/media/pci/hws/hws_v4l2_ioctl.c index 0303e311ee4a..ce396b7225d2 100644 --- a/drivers/media/pci/hws/hws_v4l2_ioctl.c +++ b/drivers/media/pci/hws/hws_v4l2_ioctl.c @@ -511,8 +511,9 @@ static inline void hws_set_colorimetry_state(struct hws_pix_state *p) { bool sd = p->height <= 576; + /* Captured samples use full-range BT.601 Y'CbCr at every resolution. */ p->colorspace = sd ? V4L2_COLORSPACE_SMPTE170M : V4L2_COLORSPACE_REC709; - p->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + p->ycbcr_enc = V4L2_YCBCR_ENC_601; p->quantization = V4L2_QUANTIZATION_FULL_RANGE; p->xfer_func = V4L2_XFER_FUNC_DEFAULT; } @@ -736,7 +737,7 @@ static inline void hws_set_colorimetry_fmt(struct v4l2_pix_format *p) bool sd = p->height <= 576; p->colorspace = sd ? V4L2_COLORSPACE_SMPTE170M : V4L2_COLORSPACE_REC709; - p->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + p->ycbcr_enc = V4L2_YCBCR_ENC_601; p->quantization = V4L2_QUANTIZATION_FULL_RANGE; p->xfer_func = V4L2_XFER_FUNC_DEFAULT; } diff --git a/drivers/media/pci/hws/hws_video.c b/drivers/media/pci/hws/hws_video.c index 18e4bc6901d3..dbe0fc2a66b5 100644 --- a/drivers/media/pci/hws/hws_video.c +++ b/drivers/media/pci/hws/hws_video.c @@ -327,7 +327,7 @@ int hws_video_init_channel(struct hws_pcie_dev *pdev, int ch) vid->pix.sizeimage = vid->pix.bytesperline * vid->pix.height; vid->pix.field = V4L2_FIELD_NONE; vid->pix.colorspace = V4L2_COLORSPACE_REC709; - vid->pix.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + vid->pix.ycbcr_enc = V4L2_YCBCR_ENC_601; vid->pix.quantization = V4L2_QUANTIZATION_FULL_RANGE; vid->pix.xfer_func = V4L2_XFER_FUNC_DEFAULT; vid->pix.interlaced = false; @@ -895,6 +895,8 @@ static void hws_video_apply_mode_change(struct hws_pcie_dev *pdx, v->pix.width = w; v->pix.height = h; v->pix.interlaced = interlaced; + v->pix.colorspace = h <= 576 ? V4L2_COLORSPACE_SMPTE170M : + V4L2_COLORSPACE_REC709; hws_set_current_dv_timings(v, w, h, interlaced); v->current_fps = fps; @@ -1292,6 +1294,8 @@ static void hws_stop_streaming(struct vb2_queue *q) WRITE_ONCE(v->stop_requested, true); hws_enable_video_capture(v->parent, v->channel_index, false); + if (hws->irq >= 0) + synchronize_irq(hws->irq); /* 2) Collect in-flight + queued under the IRQ lock */ spin_lock_irqsave(&v->irq_lock, flags); diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c index bd64c0400c0d..e476e269a50a 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -8,6 +8,7 @@ #include <linux/dmi.h> #include <linux/i2c.h> #include <linux/mei_cl_bus.h> +#include <linux/pci.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/property.h> @@ -15,6 +16,7 @@ #include <linux/workqueue.h> #include <media/ipu-bridge.h> +#include <media/ipu6-pci-table.h> #include <media/v4l2-fwnode.h> #define ADEV_DEV(adev) ACPI_PTR(&((adev)->dev)) @@ -48,6 +50,14 @@ * * Please keep the list sorted by ACPI HID. */ +/* IPU6 variants whose CSI-2 receiver needs the ov5693 clock lane gated */ +static const u16 ipu6_ov5693_ncont_clk[] = { + PCI_DEVICE_ID_INTEL_IPU6, /* Tiger Lake */ + PCI_DEVICE_ID_INTEL_IPU6EP_ADLP, /* Alder Lake-P */ + PCI_DEVICE_ID_INTEL_IPU6EP_ADLN, /* Alder Lake-N */ + 0 +}; + static const struct ipu_sensor_config ipu_supported_sensors[] = { /* Himax HM1092 */ IPU_SENSOR_CONFIG("HIMX1092", 2, 180000000, 180480000), @@ -60,6 +70,9 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { /* GalaxyCore GC0310 */ IPU_SENSOR_CONFIG("INT0310", 1, 55692000), /* Omnivision OV5693 */ + IPU_SENSOR_CONFIG_MATCH_FL("INT33BE", ipu6_ov5693_ncont_clk, + IPU_BR_FL_CSI2_CLK_NONCONTINUOUS, + 1, 419200000), IPU_SENSOR_CONFIG("INT33BE", 1, 419200000), /* Onsemi MT9M114 */ IPU_SENSOR_CONFIG("INT33F0", 1, 384000000), @@ -75,19 +88,22 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { IPU_SENSOR_CONFIG("INT3537", 1, 437000000), /* Lontium lt6911uxe */ IPU_SENSOR_CONFIG("INTC10C5", 0), - /* Omnivision OV01A10 / OV01A1S */ + /* Omnivision OV01A10 / OV01A1B / OV01A1S */ IPU_SENSOR_CONFIG("OVTI01A0", 1, 400000000), + IPU_SENSOR_CONFIG("OVTI01AB", 1, 400000000), IPU_SENSOR_CONFIG("OVTI01AS", 1, 400000000), /* Omnivision OV02C10 */ IPU_SENSOR_CONFIG("OVTI02C1", 1, 400000000), /* Omnivision OV02E10 */ IPU_SENSOR_CONFIG("OVTI02E1", 1, 360000000), /* Omnivision ov05c10 */ - IPU_SENSOR_CONFIG("OVTI05C1", 1, 480000000), + IPU_SENSOR_CONFIG("OVTI05C1", 2, 480000000, 900000000), /* Omnivision OV08A10 */ IPU_SENSOR_CONFIG("OVTI08A1", 1, 500000000), /* Omnivision OV08x40 */ IPU_SENSOR_CONFIG("OVTI08F4", 3, 400000000, 749000000, 800000000), + /* Omnivision OV13858 */ + IPU_SENSOR_CONFIG("OVTID858", 2, 540000000, 270000000), /* Omnivision OV13B10 */ IPU_SENSOR_CONFIG("OVTI13B1", 1, 560000000), IPU_SENSOR_CONFIG("OVTIDB10", 1, 560000000), @@ -95,6 +111,11 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { IPU_SENSOR_CONFIG("OVTI2680", 1, 331200000), /* Omnivision OV5675 */ IPU_SENSOR_CONFIG("OVTI5675", 1, 450000000), + /* Omnivision OV5693 */ + IPU_SENSOR_CONFIG_MATCH_FL("OVTI5693", ipu6_ov5693_ncont_clk, + IPU_BR_FL_CSI2_CLK_NONCONTINUOUS, + 1, 419200000), + IPU_SENSOR_CONFIG("OVTI5693", 1, 419200000), /* Omnivision OV8856 */ IPU_SENSOR_CONFIG("OVTI8856", 3, 180000000, 360000000, 720000000), /* Sony IMX471 */ @@ -127,6 +148,13 @@ static const struct dmi_system_id upside_down_sensor_dmi_ids[] = { { .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS 14 (Dell 14 Premium) DA14250"), + }, + .driver_data = "OVTI02C1", + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS 14 9440"), }, .driver_data = "OVTI02C1", @@ -145,6 +173,13 @@ static const struct dmi_system_id upside_down_sensor_dmi_ids[] = { }, .driver_data = "OVTI02C1", }, + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Dell Pro 14 Premium PA14260"), + }, + .driver_data = "OVTI08F4", + }, /* * The first four characters of DMI_BOARD_NAME identify the Lenovo * machine type/model. For example, a DMI_BOARD_NAME starting with @@ -185,6 +220,14 @@ static const struct dmi_system_id upside_down_sensor_dmi_ids[] = { .driver_data = "SONY471A", }, { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, + "Surface Pro for Business 11th Edition with Intel"), + }, + .driver_data = "OVTID858", + }, + { /* Samsung Galaxy Book5 Pro 360 */ .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), @@ -192,6 +235,14 @@ static const struct dmi_system_id upside_down_sensor_dmi_ids[] = { }, .driver_data = "OVTI02E1", }, + { + /* Samsung Galaxy Book3 Ultra */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "960XFH"), + }, + .driver_data = "OVTI02C1", + }, {} /* Terminating entry */ }; @@ -326,8 +377,8 @@ static int ipu_bridge_check_ivsc_dev(struct ipu_sensor *sensor, if (adev) { csi_dev = ipu_bridge_get_ivsc_csi_dev(adev); if (!csi_dev) { - acpi_dev_put(adev); dev_err(ADEV_DEV(adev), "Failed to find MEI or CVS CSI dev\n"); + acpi_dev_put(adev); return -ENODEV; } @@ -436,6 +487,7 @@ static enum v4l2_fwnode_orientation ipu_bridge_parse_orientation(struct acpi_dev int ipu_bridge_parse_ssdb(struct acpi_device *adev, struct ipu_sensor *sensor) { + acpi_handle handle = acpi_device_handle(ACPI_PTR(adev)); struct ipu_sensor_ssdb ssdb = {}; int ret; @@ -459,8 +511,15 @@ int ipu_bridge_parse_ssdb(struct acpi_device *adev, struct ipu_sensor *sensor) sensor->rotation = ipu_bridge_parse_rotation(adev, &ssdb); sensor->orientation = ipu_bridge_parse_orientation(adev); - if (ssdb.vcmtype) + acpi_handle_debug(handle, + "CSI-2 port %u, lanes %u, mclkspeed %u, rotation %u (SSDB %u), orientation %u\n", + sensor->link, sensor->lanes, sensor->mclkspeed, + sensor->rotation, ssdb.degree, sensor->orientation); + + if (ssdb.vcmtype) { sensor->vcm_type = ipu_vcm_types[ssdb.vcmtype - 1]; + acpi_handle_debug(handle, "VCM %s\n", sensor->vcm_type); + } return 0; } @@ -473,6 +532,7 @@ static void ipu_bridge_create_fwnode_properties( { struct ipu_property_names *names = &sensor->prop_names; struct software_node *nodes = sensor->swnodes; + unsigned int i = 0; sensor->prop_names = prop_names; @@ -530,21 +590,25 @@ static void ipu_bridge_create_fwnode_properties( PROPERTY_ENTRY_REF_ARRAY("lens-focus", sensor->vcm_ref); } - sensor->ep_properties[0] = PROPERTY_ENTRY_U32( - sensor->prop_names.bus_type, - V4L2_FWNODE_BUS_TYPE_CSI2_DPHY); - sensor->ep_properties[1] = PROPERTY_ENTRY_U32_ARRAY_LEN( - sensor->prop_names.data_lanes, - bridge->data_lanes, sensor->lanes); - sensor->ep_properties[2] = PROPERTY_ENTRY_REF_ARRAY( - sensor->prop_names.remote_endpoint, - sensor->local_ref); + sensor->ep_properties[IPU_BRIDGE_NEXT_PROPERTY(i, EP_BUS_TYPE)] = + PROPERTY_ENTRY_U32(names->bus_type, + V4L2_FWNODE_BUS_TYPE_CSI2_DPHY); + sensor->ep_properties[IPU_BRIDGE_NEXT_PROPERTY(i, EP_DATA_LANES)] = + PROPERTY_ENTRY_U32_ARRAY_LEN(names->data_lanes, + bridge->data_lanes, sensor->lanes); + sensor->ep_properties[IPU_BRIDGE_NEXT_PROPERTY(i, EP_REMOTE_EP)] = + PROPERTY_ENTRY_REF_ARRAY(names->remote_endpoint, + sensor->local_ref); if (cfg->nr_link_freqs > 0) - sensor->ep_properties[3] = PROPERTY_ENTRY_U64_ARRAY_LEN( - sensor->prop_names.link_frequencies, - cfg->link_freqs, - cfg->nr_link_freqs); + sensor->ep_properties[IPU_BRIDGE_NEXT_PROPERTY(i, EP_LINK_FREQUENCIES)] = + PROPERTY_ENTRY_U64_ARRAY_LEN(names->link_frequencies, + cfg->link_freqs, + cfg->nr_link_freqs); + + if (cfg->flags & IPU_BR_FL_CSI2_CLK_NONCONTINUOUS) + sensor->ep_properties[IPU_BRIDGE_NEXT_PROPERTY(i, EP_CLOCK_NONCONTINUOUS)] = + PROPERTY_ENTRY_BOOL("clock-noncontinuous"); sensor->ipu_properties[0] = PROPERTY_ENTRY_U32_ARRAY_LEN( sensor->prop_names.data_lanes, @@ -856,8 +920,8 @@ static int ipu_bridge_connect_sensor(const struct ipu_sensor_config *cfg, if (ret) goto err_free_swnodes; - dev_info(bridge->dev, "Found supported sensor %s\n", - acpi_dev_name(adev)); + dev_info(bridge->dev, "Found supported sensor %s (%pfw)\n", + acpi_dev_name(adev), primary); bridge->n_sensors++; } @@ -874,8 +938,28 @@ err_put_adev: return ret; } +/* + * Whether a sensor config applies to the IPU this bridge sits on. A config + * listing PCI product IDs only applies to those IPUs. + */ +static bool ipu_bridge_config_matches(const struct ipu_sensor_config *cfg, + struct ipu_bridge *bridge) +{ + const u16 *id; + + if (!cfg->pci_ids) + return true; + + for (id = cfg->pci_ids; *id; id++) + if (*id == bridge->pci_id) + return true; + + return false; +} + static int ipu_bridge_connect_sensors(struct ipu_bridge *bridge) { + const char *done_hid = NULL; unsigned int i; int ret; @@ -883,9 +967,22 @@ static int ipu_bridge_connect_sensors(struct ipu_bridge *bridge) const struct ipu_sensor_config *cfg = &ipu_supported_sensors[i]; + /* + * Entries for one HID are adjacent, IPU-specific ones first, + * so the generic entry is skipped once a specific one has + * matched and the sensor is not connected twice. + */ + if (done_hid && !strcmp(cfg->hid, done_hid)) + continue; + + if (!ipu_bridge_config_matches(cfg, bridge)) + continue; + ret = ipu_bridge_connect_sensor(cfg, bridge); if (ret) goto err_unregister_sensors; + + done_hid = cfg->hid; } return 0; @@ -942,6 +1039,18 @@ static int ipu_bridge_check_fwnode_graph(struct fwnode_handle *fwnode) return ipu_bridge_check_fwnode_graph(fwnode->secondary); } +struct pci_dev *ipu_bridge_get_ipu6(void) +{ + struct pci_dev *ipu = NULL; + + 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); + + return ipu; +} +EXPORT_SYMBOL_NS_GPL(ipu_bridge_get_ipu6, "INTEL_IPU_BRIDGE"); + static DEFINE_MUTEX(ipu_bridge_mutex); int ipu_bridge_init(struct device *dev, @@ -969,6 +1078,7 @@ int ipu_bridge_init(struct device *dev, sizeof(bridge->ipu_node_name)); bridge->ipu_hid_node.name = bridge->ipu_node_name; bridge->dev = dev; + bridge->pci_id = dev_is_pci(dev) ? to_pci_dev(dev)->device : 0; bridge->parse_sensor_fwnode = parse_sensor_fwnode; ret = software_node_register(&bridge->ipu_hid_node); diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2.c b/drivers/media/pci/intel/ipu3/ipu3-cio2.c index eb1824ee86fd..4d2b231291f5 100644 --- a/drivers/media/pci/intel/ipu3/ipu3-cio2.c +++ b/drivers/media/pci/intel/ipu3/ipu3-cio2.c @@ -1229,6 +1229,7 @@ static int cio2_subdev_init_state(struct v4l2_subdev *sd, } static int cio2_subdev_set_fmt(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/pci/intel/ipu6/Kconfig b/drivers/media/pci/intel/ipu6/Kconfig index 1129e2beb4be..a1d036a78b57 100644 --- a/drivers/media/pci/intel/ipu6/Kconfig +++ b/drivers/media/pci/intel/ipu6/Kconfig @@ -16,3 +16,12 @@ config VIDEO_INTEL_IPU6 To compile this driver, say Y here! It contains 2 modules - intel_ipu6 and intel_ipu6_isys. + +config VIDEO_INTEL_IPU6_IPU7 + bool "Support IPU 7 and IPU 7.5 in ipu6 driver by default" + depends on VIDEO_INTEL_IPU6 + default y + help + Support Intel IPU 7 and IPU 7.5 in ipu6 driver by default, instead of + the ipu7 staging driver. This behaviour can be changed at runtime with + module parameters in the respective drivers. diff --git a/drivers/media/pci/intel/ipu6/Makefile b/drivers/media/pci/intel/ipu6/Makefile index a821b0a1567f..676bc978b972 100644 --- a/drivers/media/pci/intel/ipu6/Makefile +++ b/drivers/media/pci/intel/ipu6/Makefile @@ -4,20 +4,26 @@ intel-ipu6-y := ipu6.o \ ipu6-bus.o \ ipu6-dma.o \ ipu6-mmu.o \ + ipu6-mmu-hw.o \ + ipu7-mmu-hw.o \ ipu6-buttress.o \ ipu6-cpd.o \ - ipu6-fw-com.o + ipu6-fw-com.o \ + ipu7-fw-com.o \ + ipu7-boot.o obj-$(CONFIG_VIDEO_INTEL_IPU6) += intel-ipu6.o intel-ipu6-isys-y := ipu6-isys.o \ ipu6-isys-csi2.o \ ipu6-fw-isys.o \ + ipu7-fw-isys.o \ ipu6-isys-video.o \ ipu6-isys-queue.o \ ipu6-isys-subdev.o \ ipu6-isys-mcd-phy.o \ ipu6-isys-jsl-phy.o \ - ipu6-isys-dwc-phy.o + ipu6-isys-dwc-phy.o \ + ipu7-isys-csi-phy.o obj-$(CONFIG_VIDEO_INTEL_IPU6) += intel-ipu6-isys.o diff --git a/drivers/media/pci/intel/ipu6/ipu6-bus.h b/drivers/media/pci/intel/ipu6/ipu6-bus.h index a08c5468d536..d2f93eb03dea 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-bus.h +++ b/drivers/media/pci/intel/ipu6/ipu6-bus.h @@ -26,17 +26,18 @@ struct ipu6_bus_device { struct ipu6_mmu *mmu; struct ipu6_device *isp; const struct ipu6_buttress_ctrl *ctrl; - const struct firmware *fw; struct sg_table fw_sgt; u64 *pkg_dir; dma_addr_t pkg_dir_dma_addr; unsigned int pkg_dir_size; + u32 fw_entry; }; struct ipu6_auxdrv_data { irqreturn_t (*isr)(struct ipu6_bus_device *adev); irqreturn_t (*isr_threaded)(struct ipu6_bus_device *adev); bool wake_isr_thread; + const struct ipu6_fw_isys_ops *fw_ops; }; #define to_ipu6_bus_device(_dev) \ @@ -45,6 +46,9 @@ struct ipu6_auxdrv_data { container_of(_auxdev, struct ipu6_bus_device, auxdev) #define ipu6_bus_get_drvdata(adev) dev_get_drvdata(&(adev)->auxdev.dev) +extern const struct ipu6_fw_isys_ops ipu6_fw_isys_ops; +extern const struct ipu6_fw_isys_ops ipu7_fw_isys_ops; + struct ipu6_bus_device * ipu6_bus_initialize_device(struct pci_dev *pdev, struct device *parent, void *pdata, const struct ipu6_buttress_ctrl *ctrl, diff --git a/drivers/media/pci/intel/ipu6/ipu6-buttress.c b/drivers/media/pci/intel/ipu6/ipu6-buttress.c index e0ecb4c8b081..105de1744dff 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-buttress.c +++ b/drivers/media/pci/intel/ipu6/ipu6-buttress.c @@ -3,6 +3,7 @@ * Copyright (C) 2013--2024 Intel Corporation */ +#include <asm/cpu_device_id.h> #include <linux/bitfield.h> #include <linux/bits.h> #include <linux/completion.h> @@ -55,16 +56,11 @@ #define BUTTRESS_MAX_CONSECUTIVE_IRQS 100 -static const u32 ipu6_adev_irq_mask[2] = { - BUTTRESS_ISR_IS_IRQ, - BUTTRESS_ISR_PS_IRQ -}; - -int ipu6_buttress_ipc_reset(struct ipu6_device *isp, - struct ipu6_buttress_ipc *ipc) +int ipu6_buttress_ipc_reset(struct ipu6_device *isp) { unsigned int retries = BUTTRESS_IPC_RESET_RETRY; struct ipu6_buttress *b = &isp->buttress; + const struct ipu6_buttress_registers *regs = b->regs; u32 val = 0, csr_in_clr; if (!isp->secure_mode) { @@ -75,11 +71,11 @@ int ipu6_buttress_ipc_reset(struct ipu6_device *isp, mutex_lock(&b->ipc_mutex); /* Clear-by-1 CSR (all bits), corresponding internal states. */ - val = readl(isp->base + ipc->csr_in); - writel(val, isp->base + ipc->csr_in); + val = readl(isp->base + regs->csr_in); + writel(val, isp->base + regs->csr_in); /* Set peer CSR bit IPC_PEER_COMP_ACTIONS_RST_PHASE1 */ - writel(ENTRY, isp->base + ipc->csr_out); + writel(ENTRY, isp->base + regs->csr_out); /* * Clear-by-1 all CSR bits EXCEPT following * bits: @@ -94,7 +90,7 @@ int ipu6_buttress_ipc_reset(struct ipu6_device *isp, do { usleep_range(400, 500); - val = readl(isp->base + ipc->csr_in); + val = readl(isp->base + regs->csr_in); switch (val) { case ENTRY | EXIT: case ENTRY | EXIT | QUERY: @@ -105,8 +101,8 @@ int ipu6_buttress_ipc_reset(struct ipu6_device *isp, * 2) Set peer CSR bit * IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE. */ - writel(ENTRY | EXIT, isp->base + ipc->csr_in); - writel(QUERY, isp->base + ipc->csr_out); + writel(ENTRY | EXIT, isp->base + regs->csr_in); + writel(QUERY, isp->base + regs->csr_out); break; case ENTRY: case ENTRY | QUERY: @@ -117,8 +113,8 @@ int ipu6_buttress_ipc_reset(struct ipu6_device *isp, * 2) Set peer CSR bit * IPC_PEER_COMP_ACTIONS_RST_PHASE1. */ - writel(ENTRY | QUERY, isp->base + ipc->csr_in); - writel(ENTRY, isp->base + ipc->csr_out); + writel(ENTRY | QUERY, isp->base + regs->csr_in); + writel(ENTRY, isp->base + regs->csr_out); break; case EXIT: case EXIT | QUERY: @@ -135,17 +131,17 @@ int ipu6_buttress_ipc_reset(struct ipu6_device *isp, * 3) Set peer CSR bit * IPC_PEER_COMP_ACTIONS_RST_PHASE2. */ - writel(EXIT, isp->base + ipc->csr_in); - writel(0, isp->base + ipc->db0_in); - writel(csr_in_clr, isp->base + ipc->csr_in); - writel(EXIT, isp->base + ipc->csr_out); + writel(EXIT, isp->base + regs->csr_in); + writel(0, isp->base + regs->db0_in); + writel(csr_in_clr, isp->base + regs->csr_in); + writel(EXIT, isp->base + regs->csr_out); /* * Read csr_in again to make sure if RST_PHASE2 is done. * If csr_in is QUERY, it should be handled again. */ usleep_range(200, 300); - val = readl(isp->base + ipc->csr_in); + val = readl(isp->base + regs->csr_in); if (val & QUERY) { dev_dbg(&isp->pdev->dev, "RST_PHASE2 retry csr_in = %x\n", val); @@ -160,8 +156,8 @@ int ipu6_buttress_ipc_reset(struct ipu6_device *isp, * 2) Set peer CSR bit * IPC_PEER_COMP_ACTIONS_RST_PHASE1 */ - writel(QUERY, isp->base + ipc->csr_in); - writel(ENTRY, isp->base + ipc->csr_out); + writel(QUERY, isp->base + regs->csr_in); + writel(ENTRY, isp->base + regs->csr_out); break; default: dev_dbg_ratelimited(&isp->pdev->dev, @@ -176,42 +172,42 @@ int ipu6_buttress_ipc_reset(struct ipu6_device *isp, return -ETIMEDOUT; } -static void ipu6_buttress_ipc_validity_close(struct ipu6_device *isp, - struct ipu6_buttress_ipc *ipc) +static void ipu6_buttress_ipc_validity_close(struct ipu6_device *isp) { writel(BUTTRESS_IU2CSECSR_IPC_PEER_DEASSERTED_REG_VALID_REQ, - isp->base + ipc->csr_out); + isp->base + isp->buttress.regs->csr_out); } static int -ipu6_buttress_ipc_validity_open(struct ipu6_device *isp, - struct ipu6_buttress_ipc *ipc) +ipu6_buttress_ipc_validity_open(struct ipu6_device *isp) { unsigned int mask = BUTTRESS_IU2CSECSR_IPC_PEER_ACKED_REG_VALID; + const struct ipu6_buttress_registers *regs = isp->buttress.regs; void __iomem *addr; int ret; u32 val; writel(BUTTRESS_IU2CSECSR_IPC_PEER_ASSERTED_REG_VALID_REQ, - isp->base + ipc->csr_out); + isp->base + regs->csr_out); - addr = isp->base + ipc->csr_in; + addr = isp->base + regs->csr_in; ret = readl_poll_timeout(addr, val, val & mask, 200, BUTTRESS_IPC_VALIDITY_TIMEOUT_US); if (ret) { dev_err(&isp->pdev->dev, "CSE validity timeout 0x%x\n", val); - ipu6_buttress_ipc_validity_close(isp, ipc); + ipu6_buttress_ipc_validity_close(isp); } return ret; } -static void ipu6_buttress_ipc_recv(struct ipu6_device *isp, - struct ipu6_buttress_ipc *ipc, u32 *ipc_msg) +static void ipu6_buttress_ipc_recv(struct ipu6_device *isp, u32 *ipc_msg) { + const struct ipu6_buttress_registers *regs = isp->buttress.regs; + if (ipc_msg) - *ipc_msg = readl(isp->base + ipc->data0_in); - writel(0, isp->base + ipc->db0_in); + *ipc_msg = readl(isp->base + regs->data0_in); + writel(0, isp->base + regs->db0_in); } static int ipu6_buttress_ipc_send_bulk(struct ipu6_device *isp, @@ -221,14 +217,15 @@ static int ipu6_buttress_ipc_send_bulk(struct ipu6_device *isp, unsigned long tx_timeout_jiffies, rx_timeout_jiffies; unsigned int i, retry = BUTTRESS_IPC_CMD_SEND_RETRY; struct ipu6_buttress *b = &isp->buttress; - struct ipu6_buttress_ipc *ipc = &b->cse; + struct ipu6_buttress_ipc *ipc = &b->ipc; + const struct ipu6_buttress_registers *regs = b->regs; u32 val; int ret; int tout; mutex_lock(&b->ipc_mutex); - ret = ipu6_buttress_ipc_validity_open(isp, ipc); + ret = ipu6_buttress_ipc_validity_open(isp); if (ret) { dev_err(&isp->pdev->dev, "IPC validity open failed\n"); goto out; @@ -244,9 +241,9 @@ static int ipu6_buttress_ipc_send_bulk(struct ipu6_device *isp, dev_dbg(&isp->pdev->dev, "bulk IPC command: 0x%x\n", msgs[i].cmd); - writel(msgs[i].cmd, isp->base + ipc->data0_out); + writel(msgs[i].cmd, isp->base + regs->data0_out); val = BUTTRESS_IU2CSEDB0_BUSY | msgs[i].cmd_size; - writel(val, isp->base + ipc->db0_out); + writel(val, isp->base + regs->db0_out); tout = wait_for_completion_timeout(&ipc->send_complete, tx_timeout_jiffies); @@ -258,7 +255,7 @@ static int ipu6_buttress_ipc_send_bulk(struct ipu6_device *isp, } /* Try again if CSE is not responding on first try */ - writel(0, isp->base + ipc->db0_out); + writel(0, isp->base + regs->db0_out); i--; continue; } @@ -276,8 +273,8 @@ static int ipu6_buttress_ipc_send_bulk(struct ipu6_device *isp, goto out; } - if (ipc->nack_mask && - (ipc->recv_data & ipc->nack_mask) == ipc->nack) { + if ((ipc->recv_data & BUTTRESS_CSE2IUDATA0_IPC_NACK_MASK) == + BUTTRESS_CSE2IUDATA0_IPC_NACK) { dev_err(&isp->pdev->dev, "IPC NACK for cmd 0x%x\n", msgs[i].cmd); ret = -EIO; @@ -296,7 +293,7 @@ static int ipu6_buttress_ipc_send_bulk(struct ipu6_device *isp, dev_dbg(&isp->pdev->dev, "bulk IPC commands done\n"); out: - ipu6_buttress_ipc_validity_close(isp, ipc); + ipu6_buttress_ipc_validity_close(isp); mutex_unlock(&b->ipc_mutex); return ret; } @@ -337,7 +334,8 @@ irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr) struct ipu6_device *isp = isp_ptr; struct ipu6_bus_device *adev[] = { isp->isys, isp->psys }; struct ipu6_buttress *b = &isp->buttress; - u32 reg_irq_sts = BUTTRESS_REG_ISR_STATUS; + const struct ipu6_buttress_registers *regs = b->regs; + const u32 adev_irq_mask[] = { regs->irq_is, regs->irq_ps }; irqreturn_t ret = IRQ_NONE; u32 disable_irqs = 0; u32 irq_status; @@ -348,7 +346,14 @@ irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr) if (!active) return IRQ_NONE; - irq_status = readl(isp->base + reg_irq_sts); + if (IS_IPU7(isp)) { + u32 pb_irq; + + pb_irq = readl(isp->pb_base + IPU7_PB_INTERRUPT_STATUS); + writel(pb_irq, isp->pb_base + IPU7_PB_INTERRUPT_STATUS); + } + + irq_status = readl(isp->base + regs->irq_status); if (irq_status == 0 || WARN_ON_ONCE(irq_status == 0xffffffffu)) { if (active > 0) pm_runtime_put_noidle(&isp->pdev->dev); @@ -356,39 +361,40 @@ irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr) } do { - writel(irq_status, isp->base + BUTTRESS_REG_ISR_CLEAR); + writel(irq_status, isp->base + regs->irq_clear); - for (i = 0; i < ARRAY_SIZE(ipu6_adev_irq_mask); i++) { + for (i = 0; i < ARRAY_SIZE(adev_irq_mask); i++) { irqreturn_t r = ipu6_buttress_call_isr(adev[i]); - if (!(irq_status & ipu6_adev_irq_mask[i])) + if (!(irq_status & adev_irq_mask[i])) continue; if (r == IRQ_WAKE_THREAD) { ret = IRQ_WAKE_THREAD; - disable_irqs |= ipu6_adev_irq_mask[i]; + disable_irqs |= adev_irq_mask[i]; } else if (ret == IRQ_NONE && r == IRQ_HANDLED) { ret = IRQ_HANDLED; } } - if ((irq_status & BUTTRESS_EVENT) && ret == IRQ_NONE) + if ((irq_status & regs->irq_events) && ret == IRQ_NONE) ret = IRQ_HANDLED; - if (irq_status & BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING) { + if (irq_status & regs->irq_cse_ipc) { dev_dbg(&isp->pdev->dev, "BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING\n"); - ipu6_buttress_ipc_recv(isp, &b->cse, &b->cse.recv_data); - complete(&b->cse.recv_complete); + + ipu6_buttress_ipc_recv(isp, &b->ipc.recv_data); + complete(&b->ipc.recv_complete); } - if (irq_status & BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE) { + if (irq_status & regs->irq_exec_done) { dev_dbg(&isp->pdev->dev, "BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE\n"); - complete(&b->cse.send_complete); + complete(&b->ipc.send_complete); } - if (irq_status & BUTTRESS_ISR_SAI_VIOLATION && + if (irq_status & regs->irq_sai && ipu6_buttress_get_secure_mode(isp)) dev_err(&isp->pdev->dev, "BUTTRESS_ISR_SAI_VIOLATION\n"); @@ -407,12 +413,12 @@ irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr) break; } - irq_status = readl(isp->base + reg_irq_sts); + irq_status = readl(isp->base + regs->irq_status); } while (irq_status); if (disable_irqs) - writel(BUTTRESS_IRQS & ~disable_irqs, - isp->base + BUTTRESS_REG_ISR_ENABLE); + writel(regs->irq_all & ~disable_irqs, + isp->base + regs->irq_enable); if (active > 0) pm_runtime_put(&isp->pdev->dev); @@ -423,12 +429,13 @@ irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr) irqreturn_t ipu6_buttress_isr_threaded(int irq, void *isp_ptr) { struct ipu6_device *isp = isp_ptr; + const struct ipu6_buttress_registers *regs = isp->buttress.regs; struct ipu6_bus_device *adev[] = { isp->isys, isp->psys }; const struct ipu6_auxdrv_data *drv_data = NULL; irqreturn_t ret = IRQ_NONE; unsigned int i; - for (i = 0; i < ARRAY_SIZE(ipu6_adev_irq_mask) && adev[i]; i++) { + for (i = 0; i < ARRAY_SIZE(adev) && adev[i]; i++) { drv_data = adev[i]->auxdrv_data; if (!drv_data) continue; @@ -438,46 +445,179 @@ irqreturn_t ipu6_buttress_isr_threaded(int irq, void *isp_ptr) ret = IRQ_HANDLED; } - writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_ENABLE); + writel(regs->irq_all, isp->base + regs->irq_enable); return ret; } -int ipu6_buttress_power(struct device *dev, - const struct ipu6_buttress_ctrl *ctrl, bool on) +static int ipu7_isys_d2d_power(struct ipu6_device *isp, bool on) +{ + u32 target = on ? IPU7_BUTTRESS_D2D_PWR_ACK : 0U; + u32 val; + int ret; + + val = readl(isp->base + IPU7_BUTTRESS_REG_D2D_CTL); + if ((val & IPU7_BUTTRESS_D2D_PWR_ACK) == target) + return 0; + + if (on) + val |= IPU7_BUTTRESS_D2D_PWR_EN; + else + val &= ~IPU7_BUTTRESS_D2D_PWR_EN; + writel(val, isp->base + IPU7_BUTTRESS_REG_D2D_CTL); + + ret = readl_poll_timeout(isp->base + IPU7_BUTTRESS_REG_D2D_CTL, val, + (val & IPU7_BUTTRESS_D2D_PWR_ACK) == target, + 100, BUTTRESS_POWER_TIMEOUT_US); + if (ret) + dev_err(&isp->pdev->dev, "D2D power %s timeout: 0x%x\n", + on ? "up" : "down", val); + + return ret; +} + +static void ipu7_nde_control(struct ipu6_device *isp, bool on) +{ + u32 val; + + val = FIELD_PREP(IPU7_NDE_VAL_MASK, + on ? IPU7_NDE_VAL_ACTIVE : IPU7_NDE_VAL_DEFAULT) | + FIELD_PREP(IPU7_NDE_SCALE_MASK, + on ? IPU7_NDE_SCALE_ACTIVE : IPU7_NDE_SCALE_DEFAULT) | + FIELD_PREP(IPU7_NDE_VALID_MASK, + on ? IPU7_NDE_VALID_ACTIVE : IPU7_NDE_VALID_DEFAULT) | + FIELD_PREP(IPU7_NDE_RESVEC_MASK, IPU7_NDE_RESVEC); + writel(val, isp->base + IPU7_BUTTRESS_REG_NDE_CONTROL); +} + +static int __ipu7_power_on(struct device *dev, + const struct ipu6_buttress_ctrl *ctrl) +{ + struct ipu6_device *isp = to_ipu6_bus_device(dev)->isp; + bool is_isys = ctrl->subsys_id == IPU_ISYS; + u32 pwr_sts, val, ovrd_clk, slp, own_clk_ack; + int ret; + + val = ctrl->ratio | (IPU7_FREQ_CTL_CDYN << IPU7_FREQ_CTL_CDYN_SHIFT); + pwr_sts = ctrl->pwr_sts_on << ctrl->pwr_sts_shift; + + if (is_isys) { + ret = ipu7_isys_d2d_power(isp, true); + if (ret) + return ret; + + ipu7_nde_control(isp, true); + } + + /* Request clock ownership. */ + ovrd_clk = is_isys ? IPU7_BUTTRESS_OVERRIDE_IS_CLK : + IPU7_BUTTRESS_OVERRIDE_PS_CLK; + + slp = readl(isp->base + IPU7_BUTTRESS_REG_SLEEP_LEVEL_CFG); + writel(slp | ovrd_clk, isp->base + IPU7_BUTTRESS_REG_SLEEP_LEVEL_CFG); + + own_clk_ack = is_isys ? IPU7_BUTTRESS_OWN_ACK_IS_CLK : + IPU7_BUTTRESS_OWN_ACK_PS_CLK; + ret = readl_poll_timeout(isp->base + IPU7_BUTTRESS_REG_SLEEP_LEVEL_STS, + slp, (slp & own_clk_ack), + 100, BUTTRESS_POWER_TIMEOUT_US); + if (ret) + dev_warn(&isp->pdev->dev, "clock ownership timeout: 0x%x\n", + slp); + + writel(val, isp->base + ctrl->freq_ctl); + + ret = readl_poll_timeout(isp->base + isp->buttress.regs->pwr_status, + val, (val & ctrl->pwr_sts_mask) == pwr_sts, + 100, BUTTRESS_POWER_TIMEOUT_US); + if (ret) { + dev_err(&isp->pdev->dev, + "Change power status timeout with 0x%x\n", val); + return ret; + } + + slp = readl(isp->base + IPU7_BUTTRESS_REG_SLEEP_LEVEL_CFG); + writel(slp & ~ovrd_clk, isp->base + IPU7_BUTTRESS_REG_SLEEP_LEVEL_CFG); + + return 0; +} + +static int __ipu7_power_off(struct device *dev, + const struct ipu6_buttress_ctrl *ctrl) { struct ipu6_device *isp = to_ipu6_bus_device(dev)->isp; u32 pwr_sts, val; int ret; - if (!ctrl) - return 0; + writel(0x8U, isp->base + ctrl->freq_ctl); - mutex_lock(&isp->buttress.power_mutex); + pwr_sts = ctrl->pwr_sts_off << ctrl->pwr_sts_shift; + ret = readl_poll_timeout(isp->base + isp->buttress.regs->pwr_status, + val, (val & ctrl->pwr_sts_mask) == pwr_sts, + 100, BUTTRESS_POWER_TIMEOUT_US); + if (ret) { + dev_err(&isp->pdev->dev, + "Change power status timeout with 0x%x\n", val); + return ret; + } + + if (ctrl->subsys_id == IPU_ISYS) { + ipu7_isys_d2d_power(isp, false); + ipu7_nde_control(isp, false); + } + + return 0; +} + +static int __ipu6_power(struct device *dev, + const struct ipu6_buttress_ctrl *ctrl, bool on) +{ + struct ipu6_device *isp = to_ipu6_bus_device(dev)->isp; + u32 pwr_sts, val; + int ret; if (!on) { val = 0; pwr_sts = ctrl->pwr_sts_off << ctrl->pwr_sts_shift; } else { val = BUTTRESS_FREQ_CTL_START | - FIELD_PREP(BUTTRESS_FREQ_CTL_RATIO_MASK, - ctrl->ratio) | - FIELD_PREP(BUTTRESS_FREQ_CTL_QOS_FLOOR_MASK, - ctrl->qos_floor) | - BUTTRESS_FREQ_CTL_ICCMAX_LEVEL; + FIELD_PREP(BUTTRESS_FREQ_CTL_RATIO_MASK, ctrl->ratio) | + FIELD_PREP(BUTTRESS_FREQ_CTL_QOS_FLOOR_MASK, + ctrl->qos_floor) | + BUTTRESS_FREQ_CTL_ICCMAX_LEVEL; pwr_sts = ctrl->pwr_sts_on << ctrl->pwr_sts_shift; } writel(val, isp->base + ctrl->freq_ctl); - ret = readl_poll_timeout(isp->base + BUTTRESS_REG_PWR_STATE, + ret = readl_poll_timeout(isp->base + isp->buttress.regs->pwr_status, val, (val & ctrl->pwr_sts_mask) == pwr_sts, 100, BUTTRESS_POWER_TIMEOUT_US); if (ret) dev_err(&isp->pdev->dev, "Change power status timeout with 0x%x\n", val); + return ret; +} + +int ipu6_buttress_power(struct device *dev, + const struct ipu6_buttress_ctrl *ctrl, bool on) +{ + struct ipu6_device *isp = to_ipu6_bus_device(dev)->isp; + int ret; + + if (!ctrl) + return 0; + + mutex_lock(&isp->buttress.power_mutex); + + if (IS_IPU7(isp)) + ret = on ? __ipu7_power_on(dev, ctrl) : + __ipu7_power_off(dev, ctrl); + else + ret = __ipu6_power(dev, ctrl, on); + mutex_unlock(&isp->buttress.power_mutex); return ret; @@ -487,7 +627,7 @@ bool ipu6_buttress_get_secure_mode(struct ipu6_device *isp) { u32 val; - val = readl(isp->base + BUTTRESS_REG_SECURITY_CTL); + val = readl(isp->base + isp->buttress.regs->security_ctl); return val & BUTTRESS_SECURITY_CTL_FW_SECURE_MODE; } @@ -499,7 +639,7 @@ bool ipu6_buttress_auth_done(struct ipu6_device *isp) if (!isp->secure_mode) return true; - val = readl(isp->base + BUTTRESS_REG_SECURITY_CTL); + val = readl(isp->base + isp->buttress.regs->security_ctl); val = FIELD_GET(BUTTRESS_SECURITY_CTL_FW_SETUP_MASK, val); return val == BUTTRESS_SECURITY_CTL_AUTH_DONE; @@ -517,10 +657,10 @@ int ipu6_buttress_reset_authentication(struct ipu6_device *isp) } writel(BUTTRESS_FW_RESET_CTL_START, isp->base + - BUTTRESS_REG_FW_RESET_CTL); + isp->buttress.regs->fw_reset_ctl); - ret = readl_poll_timeout(isp->base + BUTTRESS_REG_FW_RESET_CTL, val, - val & BUTTRESS_FW_RESET_CTL_DONE, 500, + ret = readl_poll_timeout(isp->base + isp->buttress.regs->fw_reset_ctl, + val, val & BUTTRESS_FW_RESET_CTL_DONE, 500, BUTTRESS_CSE_FWRESET_TIMEOUT_US); if (ret) { dev_err(&isp->pdev->dev, @@ -529,62 +669,63 @@ int ipu6_buttress_reset_authentication(struct ipu6_device *isp) } dev_dbg(&isp->pdev->dev, "FW reset for authentication done\n"); - writel(0, isp->base + BUTTRESS_REG_FW_RESET_CTL); + writel(0, isp->base + isp->buttress.regs->fw_reset_ctl); + /* leave some time for HW restore */ usleep_range(800, 1000); return 0; } -int ipu6_buttress_map_fw_image(struct ipu6_bus_device *sys, - const struct firmware *fw, struct sg_table *sgt) +int ipu6_map_fw_region(struct ipu6_bus_device *sys, const void *data, + size_t size, enum dma_data_direction dir, + unsigned long attrs) { - bool is_vmalloc = is_vmalloc_addr(fw->data); + bool is_vmalloc = is_vmalloc_addr(data); struct pci_dev *pdev = sys->isp->pdev; + struct sg_table *sgt = &sys->fw_sgt; struct page **pages; - const void *addr; unsigned long n_pages; unsigned int i; int ret; - if (!is_vmalloc && !virt_addr_valid(fw->data)) + if (!is_vmalloc && !virt_addr_valid(data)) return -EDOM; - n_pages = PFN_UP(fw->size); + n_pages = PFN_UP(size); pages = kmalloc_objs(*pages, n_pages); if (!pages) return -ENOMEM; - addr = fw->data; for (i = 0; i < n_pages; i++) { struct page *p = is_vmalloc ? - vmalloc_to_page(addr) : virt_to_page(addr); + vmalloc_to_page(data) : virt_to_page(data); if (!p) { ret = -ENOMEM; goto out; } pages[i] = p; - addr += PAGE_SIZE; + data += PAGE_SIZE; } - ret = sg_alloc_table_from_pages(sgt, pages, n_pages, 0, fw->size, + ret = sg_alloc_table_from_pages(sgt, pages, n_pages, 0, size, GFP_KERNEL); if (ret) { ret = -ENOMEM; goto out; } - ret = dma_map_sgtable(&pdev->dev, sgt, DMA_TO_DEVICE, 0); + ret = dma_map_sgtable(&pdev->dev, sgt, dir, 0); if (ret) { sg_free_table(sgt); goto out; } - ret = ipu6_dma_map_sgtable(sys, sgt, DMA_TO_DEVICE, 0); + ret = ipu6_dma_map_sgtable(sys, sgt, dir, attrs); if (ret) { - dma_unmap_sgtable(&pdev->dev, sgt, DMA_TO_DEVICE, 0); + dma_unmap_sgtable(&pdev->dev, sgt, dir, 0); sg_free_table(sgt); goto out; } @@ -596,18 +737,18 @@ out: return ret; } -EXPORT_SYMBOL_NS_GPL(ipu6_buttress_map_fw_image, "INTEL_IPU6"); +EXPORT_SYMBOL_NS_GPL(ipu6_map_fw_region, "INTEL_IPU6"); -void ipu6_buttress_unmap_fw_image(struct ipu6_bus_device *sys, - struct sg_table *sgt) +void ipu6_unmap_fw_region(struct ipu6_bus_device *sys, + enum dma_data_direction dir) { struct pci_dev *pdev = sys->isp->pdev; - ipu6_dma_unmap_sgtable(sys, sgt, DMA_TO_DEVICE, 0); - dma_unmap_sgtable(&pdev->dev, sgt, DMA_TO_DEVICE, 0); - sg_free_table(sgt); + ipu6_dma_unmap_sgtable(sys, &sys->fw_sgt, dir, 0); + dma_unmap_sgtable(&pdev->dev, &sys->fw_sgt, dir, 0); + sg_free_table(&sys->fw_sgt); } -EXPORT_SYMBOL_NS_GPL(ipu6_buttress_unmap_fw_image, "INTEL_IPU6"); +EXPORT_SYMBOL_NS_GPL(ipu6_unmap_fw_region, "INTEL_IPU6"); int ipu6_buttress_authenticate(struct ipu6_device *isp) { @@ -634,11 +775,18 @@ int ipu6_buttress_authenticate(struct ipu6_device *isp) * Write address of FIT table to FW_SOURCE register * Let's use fw address. I.e. not using FIT table yet */ - data = lower_32_bits(isp->psys->pkg_dir_dma_addr); - writel(data, isp->base + BUTTRESS_REG_FW_SOURCE_BASE_LO); + if (IS_IPU7(isp)) { + writel(isp->cpd_fw->size, + isp->base + IPU7_BUTTRESS_REG_FW_SOURCE_SIZE); + writel(sg_dma_address(isp->psys->fw_sgt.sgl), + isp->base + IPU7_BUTTRESS_REG_FW_SOURCE_BASE); + } else { + data = lower_32_bits(isp->psys->pkg_dir_dma_addr); + writel(data, isp->base + BUTTRESS_REG_FW_SOURCE_BASE_LO); - data = upper_32_bits(isp->psys->pkg_dir_dma_addr); - writel(data, isp->base + BUTTRESS_REG_FW_SOURCE_BASE_HI); + data = upper_32_bits(isp->psys->pkg_dir_dma_addr); + writel(data, isp->base + BUTTRESS_REG_FW_SOURCE_BASE_HI); + } /* * Write boot_load into IU2CSEDATA0 @@ -659,7 +807,7 @@ int ipu6_buttress_authenticate(struct ipu6_device *isp) mask = BUTTRESS_SECURITY_CTL_FW_SETUP_MASK; done = BUTTRESS_SECURITY_CTL_FW_SETUP_DONE; fail = BUTTRESS_SECURITY_CTL_AUTH_FAILED; - ret = readl_poll_timeout(isp->base + BUTTRESS_REG_SECURITY_CTL, data, + ret = readl_poll_timeout(isp->base + b->regs->security_ctl, data, ((data & mask) == done || (data & mask) == fail), 500, BUTTRESS_CSE_BOOTLOAD_TIMEOUT_US); @@ -674,8 +822,11 @@ int ipu6_buttress_authenticate(struct ipu6_device *isp) goto out_unlock; } - ret = readl_poll_timeout(psys_pdata->base + BOOTLOADER_STATUS_OFFSET, - data, data == BOOTLOADER_MAGIC_KEY, 500, + void __iomem *base = IS_IPU7(isp) ? + isp->base + IPU7_BUTTRESS_REG_FW_BOOT_PARAMS7 : + psys_pdata->base + BOOTLOADER_STATUS_OFFSET; + + ret = readl_poll_timeout(base, data, data == BOOTLOADER_MAGIC_KEY, 500, BUTTRESS_CSE_BOOTLOAD_TIMEOUT_US); if (ret) { dev_err(&isp->pdev->dev, "Unexpected magic number 0x%x\n", @@ -699,7 +850,7 @@ int ipu6_buttress_authenticate(struct ipu6_device *isp) } done = BUTTRESS_SECURITY_CTL_AUTH_DONE; - ret = readl_poll_timeout(isp->base + BUTTRESS_REG_SECURITY_CTL, data, + ret = readl_poll_timeout(isp->base + b->regs->security_ctl, data, ((data & mask) == done || (data & mask) == fail), 500, BUTTRESS_CSE_AUTHENTICATE_TIMEOUT_US); @@ -724,15 +875,16 @@ out_unlock: static int ipu6_buttress_send_tsc_request(struct ipu6_device *isp) { + const struct ipu6_buttress_registers *regs = isp->buttress.regs; u32 val, mask, done; int ret; mask = BUTTRESS_PWR_STATE_HH_STATUS_MASK; writel(BUTTRESS_FABRIC_CMD_START_TSC_SYNC, - isp->base + BUTTRESS_REG_FABRIC_CMD); + isp->base + regs->fabric_cmd); - val = readl(isp->base + BUTTRESS_REG_PWR_STATE); + val = readl(isp->base + regs->pwr_status); val = FIELD_GET(mask, val); if (val == BUTTRESS_PWR_STATE_HH_STATE_ERR) { dev_err(&isp->pdev->dev, "Start tsc sync failed\n"); @@ -740,8 +892,8 @@ static int ipu6_buttress_send_tsc_request(struct ipu6_device *isp) } done = BUTTRESS_PWR_STATE_HH_STATE_DONE; - ret = readl_poll_timeout(isp->base + BUTTRESS_REG_PWR_STATE, val, - FIELD_GET(mask, val) == done, 500, + ret = readl_poll_timeout(isp->base + regs->pwr_status, + val, FIELD_GET(mask, val) == done, 500, BUTTRESS_TSC_SYNC_TIMEOUT_US); if (ret) dev_err(&isp->pdev->dev, "Start tsc sync timeout\n"); @@ -749,11 +901,30 @@ static int ipu6_buttress_send_tsc_request(struct ipu6_device *isp) return ret; } -int ipu6_buttress_start_tsc_sync(struct ipu6_device *isp) +static int __ipu7p5_start_tsc_sync(struct ipu6_device *isp) { - unsigned int i; + u32 val; + + val = readl(isp->base + IPU7_BUTTRESS_REG_TSC_CTL); + val |= IPU7_BUTTRESS_SEL_PB_TIMESTAMP; + writel(val, isp->base + IPU7_BUTTRESS_REG_TSC_CTL); + + for (unsigned int i = 0; i < BUTTRESS_TSC_SYNC_RESET_TRIAL_MAX; i++) { + val = readl(isp->base + IPU7_BUTTRESS_REG_PB_TIMESTAMP_VALID); + if (val == 1) + return 0; + + usleep_range(40, 50); + } + + dev_err(&isp->pdev->dev, "TSC sync failed (timeout)\n"); - for (i = 0; i < BUTTRESS_TSC_SYNC_RESET_TRIAL_MAX; i++) { + return -ETIMEDOUT; +} + +static int __ipu6_start_tsc_sync(struct ipu6_device *isp) +{ + for (unsigned int i = 0; i < BUTTRESS_TSC_SYNC_RESET_TRIAL_MAX; i++) { u32 val; int ret; @@ -761,28 +932,39 @@ int ipu6_buttress_start_tsc_sync(struct ipu6_device *isp) if (ret != -ETIMEDOUT) return ret; - val = readl(isp->base + BUTTRESS_REG_TSW_CTL); + u32 tsw_ctl = isp->buttress.regs->tsw_ctl; + + val = readl(isp->base + tsw_ctl); val = val | BUTTRESS_TSW_CTL_SOFT_RESET; - writel(val, isp->base + BUTTRESS_REG_TSW_CTL); + writel(val, isp->base + tsw_ctl); val = val & ~BUTTRESS_TSW_CTL_SOFT_RESET; - writel(val, isp->base + BUTTRESS_REG_TSW_CTL); + writel(val, isp->base + tsw_ctl); } dev_err(&isp->pdev->dev, "TSC sync failed (timeout)\n"); return -ETIMEDOUT; } + +int ipu6_buttress_start_tsc_sync(struct ipu6_device *isp) +{ + if (IS_IPU7P5(isp)) + return __ipu7p5_start_tsc_sync(isp); + + return __ipu6_start_tsc_sync(isp); +} EXPORT_SYMBOL_NS_GPL(ipu6_buttress_start_tsc_sync, "INTEL_IPU6"); void ipu6_buttress_tsc_read(struct ipu6_device *isp, u64 *val) { + void __iomem *tsc = isp->base + isp->buttress.regs->tsc_lo; u32 tsc_hi_1, tsc_hi_2, tsc_lo; unsigned long flags; local_irq_save(flags); - tsc_hi_1 = readl(isp->base + BUTTRESS_REG_TSC_HI); - tsc_lo = readl(isp->base + BUTTRESS_REG_TSC_LO); - tsc_hi_2 = readl(isp->base + BUTTRESS_REG_TSC_HI); + tsc_hi_1 = readl(tsc + BUTTRESS_TSC_HI_OFFSET); + tsc_lo = readl(tsc); + tsc_hi_2 = readl(tsc + BUTTRESS_TSC_HI_OFFSET); if (tsc_hi_1 == tsc_hi_2) { *val = (u64)tsc_hi_1 << 32 | tsc_lo; } else { @@ -811,13 +993,77 @@ u64 ipu6_buttress_tsc_ticks_to_ns(u64 ticks, const struct ipu6_device *isp) } EXPORT_SYMBOL_NS_GPL(ipu6_buttress_tsc_ticks_to_ns, "INTEL_IPU6"); +/* trigger uc control to wakeup fw */ +void ipu7_buttress_wakeup_isys(const struct ipu6_device *isp) +{ + u32 val; + + val = readl(isp->base + IPU7_BUTTRESS_REG_ISYS_UCX_CTRL_STATUS); + val |= IPU7_UCX_CTL_WAKEUP; + writel(val, isp->base + IPU7_BUTTRESS_REG_ISYS_UCX_CTRL_STATUS); +} +EXPORT_SYMBOL_NS_GPL(ipu7_buttress_wakeup_isys, "INTEL_IPU6"); + +u32 ipu7_buttress_get_isys_freq(struct ipu6_device *isp) +{ + u32 val; + + val = readl(isp->base + IPU7_BUTTRESS_REG_IS_WORKPOINT_REQ); + val &= IPU7_BUTTRESS_IS_FREQ_CTL_RATIO_MASK; + + return val * 50 / 3; +} +EXPORT_SYMBOL_NS_GPL(ipu7_buttress_get_isys_freq, "INTEL_IPU6"); + +static const struct x86_cpu_id ipu7_misc_cfg_exclusion[] = { + X86_MATCH_VFM_STEPS(INTEL_PANTHERLAKE_L, 0x1, 0x1, 0), + {}, +}; + +#define IPU7_WRXREQOP_OVRD_VAL_MASK GENMASK(22, 19) + +static void ipu7_buttress_setup(struct ipu6_device *isp) +{ + struct ipu6_buttress *b = &isp->buttress; + u32 val; + + /* program PB BAR */ + writel(0, isp->pb_base + IPU7_GLOBAL_INTERRUPT_MASK); + val = readl(isp->pb_base + IPU7_BAR2_MISC_CONFIG); + + val |= 0x100U; + if (!IS_IPU7_MTL(isp) && !x86_match_cpu(ipu7_misc_cfg_exclusion)) + val |= FIELD_PREP(IPU7_WRXREQOP_OVRD_VAL_MASK, 0xf) | BIT(18); + + writel(val, isp->pb_base + IPU7_BAR2_MISC_CONFIG); + + if (IS_IPU7P5(isp)) { + writel(BIT(14), isp->pb_base + IPU7_TLBID_HASH_ENABLE_63_32); + writel(BIT(9), isp->pb_base + IPU7_TLBID_HASH_ENABLE_95_64); + } else { + writel(BIT(22), isp->pb_base + IPU7_TLBID_HASH_ENABLE_63_32); + writel(BIT(1), isp->pb_base + IPU7_TLBID_HASH_ENABLE_127_96); + } + + writel(b->regs->irq_all, isp->base + b->regs->irq_clear); + writel(b->regs->irq_all, isp->base + IPU7_BUTTRESS_REG_IRQ_MASK); + writel(b->regs->irq_all, isp->base + b->regs->irq_enable); + + /* LNL SW workaround for PS PD hang when PS sub-domain during PD */ + writel(IPU7_BUTTRESS_CG_CTRL_PS_FSM_CG, isp->base + IPU7_BUTTRESS_REG_CG_CTRL_BITS); +} + void ipu6_buttress_restore(struct ipu6_device *isp) { struct ipu6_buttress *b = &isp->buttress; - writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_CLEAR); - writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_ENABLE); - writel(b->wdt_cached_value, isp->base + BUTTRESS_REG_WDT); + if (IS_IPU7(isp)) { + ipu7_buttress_setup(isp); + } else { + writel(b->regs->irq_all, isp->base + b->regs->irq_clear); + writel(b->regs->irq_all, isp->base + b->regs->irq_enable); + } + writel(b->wdt_cached_value, isp->base + b->regs->wdt); } int ipu6_buttress_init(struct ipu6_device *isp) @@ -830,54 +1076,52 @@ int ipu6_buttress_init(struct ipu6_device *isp) mutex_init(&b->auth_mutex); mutex_init(&b->cons_mutex); mutex_init(&b->ipc_mutex); - init_completion(&b->cse.send_complete); - init_completion(&b->cse.recv_complete); - - b->cse.nack = BUTTRESS_CSE2IUDATA0_IPC_NACK; - b->cse.nack_mask = BUTTRESS_CSE2IUDATA0_IPC_NACK_MASK; - b->cse.csr_in = BUTTRESS_REG_CSE2IUCSR; - b->cse.csr_out = BUTTRESS_REG_IU2CSECSR; - b->cse.db0_in = BUTTRESS_REG_CSE2IUDB0; - b->cse.db0_out = BUTTRESS_REG_IU2CSEDB0; - b->cse.data0_in = BUTTRESS_REG_CSE2IUDATA0; - b->cse.data0_out = BUTTRESS_REG_IU2CSEDATA0; + init_completion(&b->ipc.send_complete); + init_completion(&b->ipc.recv_complete); INIT_LIST_HEAD(&b->constraints); - isp->secure_mode = ipu6_buttress_get_secure_mode(isp); - dev_dbg(&isp->pdev->dev, "IPU6 in %s mode touch 0x%x mask 0x%x\n", - isp->secure_mode ? "secure" : "non-secure", - readl(isp->base + BUTTRESS_REG_SECURITY_TOUCH), - readl(isp->base + BUTTRESS_REG_CAMERA_MASK)); - - b->wdt_cached_value = readl(isp->base + BUTTRESS_REG_WDT); - writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_CLEAR); - writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_ENABLE); - - /* get ref_clk frequency by reading the indication in btrs control */ - val = readl(isp->base + BUTTRESS_REG_BTRS_CTRL); - val = FIELD_GET(BUTTRESS_REG_BTRS_CTRL_REF_CLK_IND, val); - - switch (val) { - case 0x0: - b->ref_clk = 240; - break; - case 0x1: - b->ref_clk = 192; - break; - case 0x2: + + dev_dbg(&isp->pdev->dev, "IPU in %s mode\n", + isp->secure_mode ? "secure" : "non-secure"); + + if (IS_IPU7(isp)) { + ipu7_buttress_setup(isp); b->ref_clk = 384; - break; - default: - dev_warn(&isp->pdev->dev, - "Unsupported ref clock, use 19.2Mhz by default.\n"); - b->ref_clk = 192; - break; + } else { + dev_dbg(&isp->pdev->dev, "IPU6 touch 0x%x mask 0x%x\n", + readl(isp->base + BUTTRESS_REG_SECURITY_TOUCH), + readl(isp->base + BUTTRESS_REG_CAMERA_MASK)); + + writel(b->regs->irq_all, isp->base + b->regs->irq_clear); + writel(b->regs->irq_all, isp->base + b->regs->irq_enable); + + /* get ref_clk frequency by reading the indication in btrs control */ + val = readl(isp->base + b->regs->btrs_ctrl); + val = FIELD_GET(BUTTRESS_REG_BTRS_CTRL_REF_CLK_IND, val); + + switch (val) { + case 0x0: + b->ref_clk = 240; + break; + case 0x1: + b->ref_clk = 192; + break; + case 0x2: + b->ref_clk = 384; + break; + default: + dev_warn(&isp->pdev->dev, + "Unsupported ref clock, use 19.2Mhz by default.\n"); + b->ref_clk = 192; + break; + } } + b->wdt_cached_value = readl(isp->base + b->regs->wdt); /* Retry couple of times in case of CSE initialization is delayed */ do { - ret = ipu6_buttress_ipc_reset(isp, &b->cse); + ret = ipu6_buttress_ipc_reset(isp); if (ret) { dev_warn(&isp->pdev->dev, "IPC reset protocol failed, retrying\n"); @@ -901,7 +1145,7 @@ void ipu6_buttress_exit(struct ipu6_device *isp) { struct ipu6_buttress *b = &isp->buttress; - writel(0, isp->base + BUTTRESS_REG_ISR_ENABLE); + writel(0, isp->base + b->regs->irq_enable); mutex_destroy(&b->power_mutex); mutex_destroy(&b->auth_mutex); diff --git a/drivers/media/pci/intel/ipu6/ipu6-buttress.h b/drivers/media/pci/intel/ipu6/ipu6-buttress.h index 51e5ad48db82..79d8f5893686 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-buttress.h +++ b/drivers/media/pci/intel/ipu6/ipu6-buttress.h @@ -26,25 +26,50 @@ struct ipu6_buttress_ctrl { u32 freq_ctl, pwr_sts_shift, pwr_sts_mask, pwr_sts_on, pwr_sts_off; unsigned int ratio; unsigned int qos_floor; + int subsys_id; }; struct ipu6_buttress_ipc { struct completion send_complete; struct completion recv_complete; - u32 nack; - u32 nack_mask; u32 recv_data; - u32 csr_out; +}; + +struct ipu6_buttress_registers { + /* Registers */ + u32 irq_status; + u32 irq_clear; + u32 irq_enable; + u32 pwr_status; + u32 security_ctl; + u32 fw_reset_ctl; + u32 fabric_cmd; + u32 tsw_ctl; + u32 tsc_lo; + u32 wdt; + u32 btrs_ctrl; u32 csr_in; + u32 csr_out; u32 db0_in; u32 db0_out; - u32 data0_out; u32 data0_in; + u32 data0_out; + u32 sku_id; + + /* Bitmasks */ + u32 irq_is; + u32 irq_ps; + u32 irq_all; + u32 irq_events; + u32 irq_cse_ipc; + u32 irq_exec_done; + u32 irq_sai; }; struct ipu6_buttress { struct mutex power_mutex, auth_mutex, cons_mutex, ipc_mutex; - struct ipu6_buttress_ipc cse; + struct ipu6_buttress_ipc ipc; + const struct ipu6_buttress_registers *regs; struct list_head constraints; u32 wdt_cached_value; bool force_suspend; @@ -58,13 +83,12 @@ struct ipu6_ipc_buttress_bulk_msg { u8 cmd_size; }; -int ipu6_buttress_ipc_reset(struct ipu6_device *isp, - struct ipu6_buttress_ipc *ipc); -int ipu6_buttress_map_fw_image(struct ipu6_bus_device *sys, - const struct firmware *fw, - struct sg_table *sgt); -void ipu6_buttress_unmap_fw_image(struct ipu6_bus_device *sys, - struct sg_table *sgt); +int ipu6_buttress_ipc_reset(struct ipu6_device *isp); +int ipu6_map_fw_region(struct ipu6_bus_device *sys, const void *data, + size_t size, enum dma_data_direction dir, + unsigned long attrs); +void ipu6_unmap_fw_region(struct ipu6_bus_device *sys, + enum dma_data_direction dir); int ipu6_buttress_power(struct device *dev, const struct ipu6_buttress_ctrl *ctrl, bool on); bool ipu6_buttress_get_secure_mode(struct ipu6_device *isp); @@ -82,4 +106,6 @@ void ipu6_buttress_exit(struct ipu6_device *isp); void ipu6_buttress_csi_port_config(struct ipu6_device *isp, u32 legacy, u32 combo); void ipu6_buttress_restore(struct ipu6_device *isp); +void ipu7_buttress_wakeup_isys(const struct ipu6_device *isp); +u32 ipu7_buttress_get_isys_freq(struct ipu6_device *isp); #endif /* IPU6_BUTTRESS_H */ diff --git a/drivers/media/pci/intel/ipu6/ipu6-cpd.c b/drivers/media/pci/intel/ipu6/ipu6-cpd.c index b7013f6524ec..1fadb0f2f6c1 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-cpd.c +++ b/drivers/media/pci/intel/ipu6/ipu6-cpd.c @@ -16,6 +16,7 @@ #include "ipu6-bus.h" #include "ipu6-cpd.h" #include "ipu6-dma.h" +#include "ipu7-mmu-hw.h" /* 15 entries + header*/ #define MAX_PKG_DIR_ENT_CNT 16 @@ -62,6 +63,20 @@ static inline const struct ipu6_cpd_ent *ipu6_cpd_get_entry(const void *cpd, #define ipu6_cpd_get_metadata(cpd) ipu6_cpd_get_entry(cpd, METADATA_IDX) #define ipu6_cpd_get_moduledata(cpd) ipu6_cpd_get_entry(cpd, MODULEDATA_IDX) +#define IPU7_CPD_BINARY_START_IDX 1U +#define IPU7_CPD_METADATA_START_IDX 2U +#define IPU7_CPD_BINARY_NUM 2U /* ISYS + PSYS */ +#define IPU7_CPD_METADATA_ATTR 0xa +#define IPU7_CPD_METADATA_IPL 0x1c +/* + * Entries include: + * 1 manifest entry. + * 1 metadata entry for each sub system(ISYS and PSYS). + * 1 binary entry for each sub system(ISYS and PSYS). + */ +#define IPU7_CPD_ENTRY_NUM (IPU7_CPD_BINARY_NUM * 2U + 1U) +#define IPU7_MAX_MANIFEST_SIZE (SZ_4K * sizeof(u32)) + static const struct ipu6_cpd_metadata_cmpnt_hdr * ipu6_cpd_metadata_get_cmpnt(struct ipu6_device *isp, const void *metadata, unsigned int metadata_size, u8 idx) @@ -220,8 +235,9 @@ EXPORT_SYMBOL_NS_GPL(ipu6_cpd_create_pkg_dir, "INTEL_IPU6"); void ipu6_cpd_free_pkg_dir(struct ipu6_bus_device *adev) { - ipu6_dma_free(adev, adev->pkg_dir_size, adev->pkg_dir, - adev->pkg_dir_dma_addr, 0); + if (adev->pkg_dir) + ipu6_dma_free(adev, adev->pkg_dir_size, adev->pkg_dir, + adev->pkg_dir_dma_addr, 0); } EXPORT_SYMBOL_NS_GPL(ipu6_cpd_free_pkg_dir, "INTEL_IPU6"); @@ -266,7 +282,6 @@ static int ipu6_cpd_validate_moduledata(struct ipu6_device *isp, u32 moduledata_size) { const struct ipu6_cpd_module_data_hdr *mod_hdr = moduledata; - int ret; /* Ensure moduledata hdr is within moduledata */ if (moduledata_size < sizeof(*mod_hdr) || @@ -276,15 +291,9 @@ static int ipu6_cpd_validate_moduledata(struct ipu6_device *isp, } dev_dbg(&isp->pdev->dev, "FW version: %x\n", mod_hdr->fw_pkg_date); - ret = ipu6_cpd_validate_cpd(isp, moduledata + mod_hdr->hdr_len, - moduledata_size - mod_hdr->hdr_len, - moduledata_size); - if (ret) { - dev_err(&isp->pdev->dev, "Invalid CPD in moduledata\n"); - return ret; - } - - return 0; + return ipu6_cpd_validate_cpd(isp, moduledata + mod_hdr->hdr_len, + moduledata_size - mod_hdr->hdr_len, + moduledata_size); } static int ipu6_cpd_validate_metadata(struct ipu6_device *isp, @@ -316,47 +325,163 @@ static int ipu6_cpd_validate_metadata(struct ipu6_device *isp, return 0; } -int ipu6_cpd_validate_cpd_file(struct ipu6_device *isp, const void *cpd_file, - unsigned long cpd_file_size) +static struct ipu7_cpd_metadata *ipu7_cpd_get_metadata(const void *cpd, int idx) { - const struct ipu6_cpd_hdr *hdr = cpd_file; - const struct ipu6_cpd_ent *ent; - int ret; + const struct ipu6_cpd_ent *cpd_ent = + ipu6_cpd_get_entry(cpd, IPU7_CPD_METADATA_START_IDX + idx * 2); - ret = ipu6_cpd_validate_cpd(isp, cpd_file, cpd_file_size, - cpd_file_size); - if (ret) { - dev_err(&isp->pdev->dev, "Invalid CPD in file\n"); - return ret; + return (struct ipu7_cpd_metadata *)((u8 *)cpd + cpd_ent->offset); +} + +static int ipu7_cpd_validate_metadata(struct ipu6_device *isp, + const void *cpd, int idx) +{ + const struct ipu6_cpd_ent *cpd_ent = + ipu6_cpd_get_entry(cpd, IPU7_CPD_METADATA_START_IDX + idx * 2); + const struct ipu6_cpd_ent *bin_ent = + ipu6_cpd_get_entry(cpd, IPU7_CPD_BINARY_START_IDX + idx * 2); + const struct ipu7_cpd_metadata *metadata = + ipu7_cpd_get_metadata(cpd, idx); + struct device *dev = &isp->pdev->dev; + u32 offset; + + /* Sanity check for metadata size */ + if (cpd_ent->len != sizeof(struct ipu7_cpd_metadata)) { + dev_err(dev, "Invalid metadata size\n"); + return -EINVAL; } - /* Check for CPD file marker */ - if (hdr->hdr_mark != CPD_HDR_MARK) { - dev_err(&isp->pdev->dev, "Invalid CPD header\n"); + /* Validate type and length of metadata sections */ + if (metadata->attr.hdr.type != IPU7_CPD_METADATA_ATTR) { + dev_err(dev, "Invalid metadata attr type (%d)\n", + metadata->attr.hdr.type); + return -EINVAL; + } + if (metadata->attr.hdr.len != sizeof(struct ipu7_cpd_metadata_attr)) { + dev_err(dev, "Invalid metadata attr size (%d)\n", + metadata->attr.hdr.len); + return -EINVAL; + } + if (metadata->ipl.hdr.type != IPU7_CPD_METADATA_IPL) { + dev_err(dev, "Invalid metadata ipl type (%d)\n", + metadata->ipl.hdr.type); + return -EINVAL; + } + if (metadata->ipl.hdr.len != sizeof(struct ipu7_cpd_metadata_ipl)) { + dev_err(dev, "Invalid metadata ipl size (%d)\n", + metadata->ipl.hdr.len); + return -EINVAL; + } + + offset = metadata->ipl.param[0]; + if (offset > IPU7_FW_CODE_REGION_SIZE || + bin_ent->len > IPU7_FW_CODE_REGION_SIZE - offset) { + dev_err(dev, "Incorrect binary size %u and offset %u\n", + bin_ent->len, offset); return -EINVAL; } - /* Sanity check for manifest size */ + return 0; +} + +static int __ipu7_validate_cpd_file(struct ipu6_device *isp, const void *cpd_file, + unsigned long cpd_file_size) +{ + const struct ipu6_cpd_ent *ent; + const struct ipu7_cpd_hdr *hdr = cpd_file; + unsigned int i; + ent = ipu6_cpd_get_manifest(cpd_file); - if (ent->len > MAX_MANIFEST_SIZE) { + if (ent->len > IPU7_MAX_MANIFEST_SIZE) { dev_err(&isp->pdev->dev, "Invalid CPD manifest size\n"); return -EINVAL; } + /* Sanity check for CPD entry header */ + if (hdr->ent_cnt != IPU7_CPD_ENTRY_NUM) { + dev_err(&isp->pdev->dev, "Invalid CPD entry number %d\n", + hdr->ent_cnt); + return -EINVAL; + } /* Validate metadata */ + for (i = 0; i < IPU7_CPD_BINARY_NUM; i++) { + int ret = ipu7_cpd_validate_metadata(isp, cpd_file, i); + + if (ret) { + dev_err(&isp->pdev->dev, "Invalid metadata(%d)\n", i); + return ret; + } + } + return 0; +} + +static int __ipu6_validate_cpd_file(struct ipu6_device *isp, const void *cpd_file, + unsigned long cpd_file_size) +{ + const struct ipu6_cpd_ent *ent; + int ret; + + ent = ipu6_cpd_get_manifest(cpd_file); + if (ent->len > MAX_MANIFEST_SIZE) { + dev_err(&isp->pdev->dev, "Invalid CPD manifest size\n"); + return -EINVAL; + } + ent = ipu6_cpd_get_metadata(cpd_file); ret = ipu6_cpd_validate_metadata(isp, cpd_file + ent->offset, ent->len); - if (ret) { - dev_err(&isp->pdev->dev, "Invalid CPD metadata\n"); + if (ret) return ret; - } - /* Validate moduledata */ ent = ipu6_cpd_get_moduledata(cpd_file); - ret = ipu6_cpd_validate_moduledata(isp, cpd_file + ent->offset, - ent->len); + return ipu6_cpd_validate_moduledata(isp, cpd_file + ent->offset, + ent->len); +} + +int ipu6_cpd_validate_cpd_file(struct ipu6_device *isp, const void *cpd_file, + unsigned long cpd_file_size) +{ + const struct ipu6_cpd_hdr *hdr = cpd_file; + int ret; + + ret = ipu6_cpd_validate_cpd(isp, cpd_file, cpd_file_size, + cpd_file_size); if (ret) - dev_err(&isp->pdev->dev, "Invalid CPD moduledata\n"); + return ret; + + /* Check for CPD file marker */ + if (hdr->hdr_mark != CPD_HDR_MARK) { + dev_err(&isp->pdev->dev, "Invalid CPD header\n"); + return -EINVAL; + } + + if (IS_IPU7(isp)) + return __ipu7_validate_cpd_file(isp, cpd_file, cpd_file_size); + + return __ipu6_validate_cpd_file(isp, cpd_file, cpd_file_size); +} + +int ipu6_ipu7_cpd_copy_binary(const void *cpd, const char *name, void *dst, + u32 *entry) +{ + unsigned int i; + + for (i = 0; i < IPU7_CPD_BINARY_NUM; i++) { + const struct ipu7_cpd_metadata *metadata; + u8 idx = IPU7_CPD_BINARY_START_IDX + i * 2U; + const struct ipu6_cpd_ent *ent = + ipu6_cpd_get_entry(cpd, idx); + + if (strncmp(ent->name, name, sizeof(ent->name))) + continue; + + metadata = ipu7_cpd_get_metadata(cpd, i); + memcpy(dst + metadata->ipl.param[0], cpd + ent->offset, + ent->len); + *entry = metadata->ipl.param[2]; + + return 0; + } - return ret; + return -ENOENT; } +EXPORT_SYMBOL_NS_GPL(ipu6_ipu7_cpd_copy_binary, "INTEL_IPU6"); diff --git a/drivers/media/pci/intel/ipu6/ipu6-cpd.h b/drivers/media/pci/intel/ipu6/ipu6-cpd.h index e0e4fdeca902..c3a4859d8e79 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-cpd.h +++ b/drivers/media/pci/intel/ipu6/ipu6-cpd.h @@ -98,8 +98,51 @@ struct ipu6_cpd_client_pkg_hdr { u32 prog_bin_size; } __packed; +/* IPU7 */ + +struct ipu7_cpd_hdr { + u32 hdr_mark; + u32 ent_cnt; + u8 hdr_ver; + u8 ent_ver; + u8 hdr_len; + u8 rsvd; + u8 partition_name[4]; + u32 crc32; +} __packed; + +struct ipu7_cpd_metadata_hdr { + u32 type; + u32 len; +} __packed; + +struct ipu7_cpd_metadata_attr { + struct ipu7_cpd_metadata_hdr hdr; + u8 compression_type; + u8 encryption_type; + u8 rsvd[2]; + u32 uncompressed_size; + u32 compressed_size; + u32 module_id; + u8 hash[48]; +} __packed; + +struct ipu7_cpd_metadata_ipl { + struct ipu7_cpd_metadata_hdr hdr; + u32 param[4]; + u8 rsvd[8]; +} __packed; + +struct ipu7_cpd_metadata { + struct ipu7_cpd_metadata_attr attr; + struct ipu7_cpd_metadata_ipl ipl; +} __packed; + int ipu6_cpd_create_pkg_dir(struct ipu6_bus_device *adev, const void *src); void ipu6_cpd_free_pkg_dir(struct ipu6_bus_device *adev); int ipu6_cpd_validate_cpd_file(struct ipu6_device *isp, const void *cpd_file, unsigned long cpd_file_size); +int ipu6_ipu7_cpd_copy_binary(const void *cpd, const char *name, void *dst, + u32 *entry); + #endif /* IPU6_CPD_H */ diff --git a/drivers/media/pci/intel/ipu6/ipu6-dma.c b/drivers/media/pci/intel/ipu6/ipu6-dma.c index fdcdb15b073c..90f530a93779 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-dma.c +++ b/drivers/media/pci/intel/ipu6/ipu6-dma.c @@ -286,7 +286,7 @@ void ipu6_dma_free(struct ipu6_bus_device *sys, size_t size, void *vaddr, __free_buffer(pages, size, attrs); - mmu->tlb_invalidate(mmu); + mmu->ops->tlb_invalidate(mmu); __free_iova(&mmu->dmap->iovad, iova); @@ -366,11 +366,22 @@ void ipu6_dma_unmap_sg(struct ipu6_bus_device *sys, struct scatterlist *sglist, ipu6_mmu_unmap(mmu->dmap->mmu_info, PFN_PHYS(iova->pfn_lo), PFN_PHYS(iova_size(iova))); - mmu->tlb_invalidate(mmu); + mmu->ops->tlb_invalidate(mmu); __free_iova(&mmu->dmap->iovad, iova); } EXPORT_SYMBOL_NS_GPL(ipu6_dma_unmap_sg, "INTEL_IPU6"); +static struct iova *ipu7_get_fw_code_region(struct ipu6_bus_device *sys) +{ + struct ipu6_mmu *mmu = sys->mmu; + unsigned long lo, hi; + + lo = iova_pfn(&mmu->dmap->iovad, IPU7_FW_CODE_REGION_START); + hi = iova_pfn(&mmu->dmap->iovad, IPU7_FW_CODE_REGION_END) - 1U; + + return reserve_iova(&mmu->dmap->iovad, lo, hi); +} + int ipu6_dma_map_sg(struct ipu6_bus_device *sys, struct scatterlist *sglist, int nents, enum dma_data_direction dir, unsigned long attrs) @@ -397,10 +408,13 @@ int ipu6_dma_map_sg(struct ipu6_bus_device *sys, struct scatterlist *sglist, dev_dbg(dev, "dmamap trying to map %d ents %zu pages\n", nents, npages); - iova = alloc_iova(&mmu->dmap->iovad, npages, - PHYS_PFN(mmu->dmap->mmu_info->aperture_end), 0); + if (attrs & DMA_ATTR_RESERVE_REGION) + iova = ipu7_get_fw_code_region(sys); + else + iova = alloc_iova(&mmu->dmap->iovad, npages, + PHYS_PFN(mmu->dmap->mmu_info->aperture_end), 0); if (!iova) - return 0; + return -ENOMEM; dev_dbg(dev, "dmamap: iova low pfn %lu, high pfn %lu\n", iova->pfn_lo, iova->pfn_hi); diff --git a/drivers/media/pci/intel/ipu6/ipu6-dma.h b/drivers/media/pci/intel/ipu6/ipu6-dma.h index ae9b9a5df57f..e5e0e7860423 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-dma.h +++ b/drivers/media/pci/intel/ipu6/ipu6-dma.h @@ -10,6 +10,8 @@ #include "ipu6-bus.h" +#define DMA_ATTR_RESERVE_REGION BIT(31) + struct ipu6_mmu_info; struct ipu6_dma_mapping { diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c index 62ed92ff1d30..f4f1cf7c86d2 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c @@ -7,6 +7,7 @@ #include <linux/delay.h> #include <linux/device.h> #include <linux/io.h> +#include <linux/pm_runtime.h> #include <linux/spinlock.h> #include <linux/types.h> @@ -15,6 +16,7 @@ #include "ipu6-isys.h" #include "ipu6-platform-isys-csi2-reg.h" #include "ipu6-platform-regs.h" +#include "ipu7-fw-isys.h" static const char send_msg_types[N_IPU6_FW_ISYS_SEND_TYPE][32] = { "STREAM_OPEN", @@ -32,7 +34,7 @@ static int handle_proxy_response(struct ipu6_isys *isys, unsigned int req_id) struct ipu6_fw_isys_proxy_resp_info_abi *resp; int ret; - resp = ipu6_recv_get_token(isys->fwcom, IPU6_BASE_PROXY_RECV_QUEUES); + resp = ipu6_recv_get_token(isys->fwctx, IPU6_BASE_PROXY_RECV_QUEUES); if (!resp) return 1; @@ -42,7 +44,7 @@ static int handle_proxy_response(struct ipu6_isys *isys, unsigned int req_id) ret = req_id == resp->request_id ? 0 : -EIO; - ipu6_recv_put_token(isys->fwcom, IPU6_BASE_PROXY_RECV_QUEUES); + ipu6_recv_put_token(isys->fwctx, IPU6_BASE_PROXY_RECV_QUEUES); return ret; } @@ -52,7 +54,7 @@ int ipu6_fw_isys_send_proxy_token(struct ipu6_isys *isys, unsigned int index, unsigned int offset, u32 value) { - struct ipu6_fw_com_context *ctx = isys->fwcom; + struct ipu6_fw_com_context *ctx = isys->fwctx; struct device *dev = &isys->adev->auxdev.dev; struct ipu6_fw_proxy_send_queue_token *token; unsigned int timeout = 1000; @@ -90,13 +92,13 @@ int ipu6_fw_isys_send_proxy_token(struct ipu6_isys *isys, return ret; } -int ipu6_fw_isys_complex_cmd(struct ipu6_isys *isys, - const unsigned int stream_handle, - void *cpu_mapped_buf, - dma_addr_t dma_mapped_buf, - size_t size, u16 send_type) +static int ipu6_fw_isys_complex_cmd(struct ipu6_isys *isys, + const unsigned int stream_handle, + void *cpu_mapped_buf, + dma_addr_t dma_mapped_buf, + size_t size, u16 send_type) { - struct ipu6_fw_com_context *ctx = isys->fwcom; + struct ipu6_fw_com_context *ctx = isys->fwctx; struct device *dev = &isys->adev->auxdev.dev; struct ipu6_fw_send_queue_token *token; @@ -126,19 +128,11 @@ int ipu6_fw_isys_complex_cmd(struct ipu6_isys *isys, return 0; } -int ipu6_fw_isys_simple_cmd(struct ipu6_isys *isys, - const unsigned int stream_handle, u16 send_type) -{ - return ipu6_fw_isys_complex_cmd(isys, stream_handle, NULL, 0, 0, - send_type); -} - -int ipu6_fw_isys_close(struct ipu6_isys *isys) +static int ipu6_fw_isys_close(struct ipu6_isys *isys) { struct device *dev = &isys->adev->auxdev.dev; int retry = IPU6_ISYS_CLOSE_RETRY; - unsigned long flags; - void *fwcom; + void *fwctx; int ret; /* @@ -147,40 +141,36 @@ int ipu6_fw_isys_close(struct ipu6_isys *isys) * to SP icache. * spinlock to wait the interrupt handler to be finished */ - spin_lock_irqsave(&isys->power_lock, flags); - ret = ipu6_fw_com_close(isys->fwcom); - fwcom = isys->fwcom; - isys->fwcom = NULL; - spin_unlock_irqrestore(&isys->power_lock, flags); + ret = ipu6_fw_com_close(isys->fwctx); + fwctx = isys->fwctx; + isys->fwctx = NULL; if (ret) dev_err(dev, "Device close failure: %d\n", ret); /* release probably fails if the close failed. Let's try still */ do { usleep_range(400, 500); - ret = ipu6_fw_com_release(fwcom, 0); + ret = ipu6_fw_com_release(fwctx, 0); retry--; } while (ret && retry); if (ret) { dev_err(dev, "Device release time out %d\n", ret); - spin_lock_irqsave(&isys->power_lock, flags); - isys->fwcom = fwcom; - spin_unlock_irqrestore(&isys->power_lock, flags); + isys->fwctx = fwctx; } return ret; } -void ipu6_fw_isys_cleanup(struct ipu6_isys *isys) +static void ipu6_fw_isys_cleanup(struct ipu6_isys *isys) { int ret; - ret = ipu6_fw_com_release(isys->fwcom, 1); + ret = ipu6_fw_com_release(isys->fwctx, 1); if (ret < 0) dev_warn(&isys->adev->auxdev.dev, "Device busy, fw_com release failed."); - isys->fwcom = NULL; + isys->fwctx = NULL; } static void start_sp(struct ipu6_bus_device *adev) @@ -212,7 +202,7 @@ static int query_sp(struct ipu6_bus_device *adev) } static int ipu6_isys_fwcom_cfg_init(struct ipu6_isys *isys, - struct ipu6_fw_com_cfg *fwcom, + struct ipu6_fw_com_cfg *fwcom_cfg, unsigned int num_streams) { unsigned int max_send_queues, max_sram_blocks, max_devq_size; @@ -258,14 +248,16 @@ static int ipu6_isys_fwcom_cfg_init(struct ipu6_isys *isys, if (!output_queue_cfg) return -ENOMEM; - fwcom->input = input_queue_cfg; - fwcom->output = output_queue_cfg; + fwcom_cfg->input = input_queue_cfg; + fwcom_cfg->output = output_queue_cfg; - fwcom->num_input_queues = isys_fw_cfg->num_send_queues[type_proxy] + + fwcom_cfg->num_input_queues = + isys_fw_cfg->num_send_queues[type_proxy] + isys_fw_cfg->num_send_queues[type_dev] + isys_fw_cfg->num_send_queues[type_msg]; - fwcom->num_output_queues = isys_fw_cfg->num_recv_queues[type_proxy] + + fwcom_cfg->num_output_queues = + isys_fw_cfg->num_recv_queues[type_proxy] + isys_fw_cfg->num_recv_queues[type_dev] + isys_fw_cfg->num_recv_queues[type_msg]; @@ -280,7 +272,7 @@ static int ipu6_isys_fwcom_cfg_init(struct ipu6_isys *isys, isys_fw_cfg->buffer_partition.num_gda_pages[i] = 0; } - /* FW assumes proxy interface at fwcom queue 0 */ + /* FW assumes proxy interface at fwcom_cfg queue 0 */ for (i = 0; i < isys_fw_cfg->num_send_queues[type_proxy]; i++) { input_queue_cfg[i].token_size = sizeof(struct ipu6_fw_proxy_send_queue_token); @@ -314,34 +306,34 @@ static int ipu6_isys_fwcom_cfg_init(struct ipu6_isys *isys, IPU6_ISYS_SIZE_RECV_QUEUE; } - fwcom->dmem_addr = isys->pdata->ipdata->hw_variant.dmem_offset; - fwcom->specific_addr = isys_fw_cfg; - fwcom->specific_size = sizeof(*isys_fw_cfg); + fwcom_cfg->dmem_addr = isys->pdata->ipdata->hw_variant.dmem_offset; + fwcom_cfg->specific_addr = isys_fw_cfg; + fwcom_cfg->specific_size = sizeof(*isys_fw_cfg); return 0; } -int ipu6_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams) +static int ipu6_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams) { struct device *dev = &isys->adev->auxdev.dev; int retry = IPU6_ISYS_OPEN_RETRY; - struct ipu6_fw_com_cfg fwcom = { + struct ipu6_fw_com_cfg fwcom_cfg = { .cell_start = start_sp, .cell_ready = query_sp, .buttress_boot_offset = SYSCOM_BUTTRESS_FW_PARAMS_ISYS_OFFSET, }; int ret; - ipu6_isys_fwcom_cfg_init(isys, &fwcom, num_streams); + ipu6_isys_fwcom_cfg_init(isys, &fwcom_cfg, num_streams); - isys->fwcom = ipu6_fw_com_prepare(&fwcom, isys->adev, + isys->fwctx = ipu6_fw_com_prepare(&fwcom_cfg, isys->adev, isys->pdata->base); - if (!isys->fwcom) { + if (!isys->fwctx) { dev_err(dev, "isys fw com prepare failed\n"); return -EIO; } - ret = ipu6_fw_com_open(isys->fwcom); + ret = ipu6_fw_com_open(isys->fwctx); if (ret) { dev_err(dev, "isys fw com open failed %d\n", ret); return ret; @@ -349,7 +341,7 @@ int ipu6_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams) do { usleep_range(400, 500); - if (ipu6_fw_com_ready(isys->fwcom)) + if (ipu6_fw_com_ready(isys->fwctx)) break; retry--; } while (retry > 0); @@ -363,20 +355,21 @@ int ipu6_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams) return ret; } -struct ipu6_fw_isys_resp_info_abi * -ipu6_fw_isys_get_resp(void *context, unsigned int queue) +static struct ipu6_fw_isys_resp_info_abi * +ipu6_fw_isys_get_resp(struct ipu6_isys *isys) { - return ipu6_recv_get_token(context, queue); + return ipu6_recv_get_token(isys->fwctx, IPU6_BASE_MSG_RECV_QUEUES); } -void ipu6_fw_isys_put_resp(void *context, unsigned int queue) +static void ipu6_fw_isys_put_resp(struct ipu6_isys *isys) { - ipu6_recv_put_token(context, queue); + ipu6_recv_put_token(isys->fwctx, IPU6_BASE_MSG_RECV_QUEUES); } -void ipu6_fw_isys_dump_stream_cfg(struct device *dev, - struct ipu6_fw_isys_stream_cfg_data_abi *cfg) +static void ipu6_fw_isys_dump_stream_cfg(struct device *dev, + struct isys_fw_msgs *msg) { + struct ipu6_fw_isys_stream_cfg_data_abi *cfg = &msg->ipu6.stream; unsigned int i; dev_dbg(dev, "-----------------------------------------------------\n"); @@ -451,13 +444,15 @@ void ipu6_fw_isys_dump_stream_cfg(struct device *dev, dev_dbg(dev, "-----------------------------------------------------\n"); } -void -ipu6_fw_isys_dump_frame_buff_set(struct device *dev, - struct ipu6_fw_isys_frame_buff_set_abi *buf, - unsigned int outputs) +static void +ipu6_fw_isys_dump_frame_buf_set(struct device *dev, struct isys_fw_msgs *msg, + unsigned int outputs) { + struct ipu6_fw_isys_frame_buff_set_abi *buf; unsigned int i; + buf = &msg->ipu6.frame; + dev_dbg(dev, "-----------------------------------------------------\n"); dev_dbg(dev, "IPU6_FW_ISYS_FRAME_BUFF_SET\n"); @@ -485,3 +480,478 @@ ipu6_fw_isys_dump_frame_buff_set(struct device *dev, dev_dbg(dev, "-----------------------------------------------------\n"); } + +struct fwmsg { + int type; + char *msg; + bool valid_ts; +}; + +static const struct fwmsg fw_msg[] = { + {IPU6_FW_ISYS_RESP_TYPE_STREAM_OPEN_DONE, "STREAM_OPEN_DONE", 0}, + {IPU6_FW_ISYS_RESP_TYPE_STREAM_CLOSE_ACK, "STREAM_CLOSE_ACK", 0}, + {IPU6_FW_ISYS_RESP_TYPE_STREAM_START_ACK, "STREAM_START_ACK", 0}, + {IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK, + "STREAM_START_AND_CAPTURE_ACK", 0}, + {IPU6_FW_ISYS_RESP_TYPE_STREAM_STOP_ACK, "STREAM_STOP_ACK", 0}, + {IPU6_FW_ISYS_RESP_TYPE_STREAM_FLUSH_ACK, "STREAM_FLUSH_ACK", 0}, + {IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_READY, "PIN_DATA_READY", 1}, + {IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_ACK, "STREAM_CAPTURE_ACK", 0}, + {IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE, + "STREAM_START_AND_CAPTURE_DONE", 1}, + {IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_DONE, "STREAM_CAPTURE_DONE", 1}, + {IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF, "FRAME_SOF", 1}, + {IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF, "FRAME_EOF", 1}, + {IPU6_FW_ISYS_RESP_TYPE_STATS_DATA_READY, "STATS_READY", 1}, + {-1, "UNKNOWN MESSAGE", 0} +}; + +static u32 resp_type_to_index(int type) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(fw_msg); i++) + if (fw_msg[i].type == type) + return i; + + return ARRAY_SIZE(fw_msg) - 1; +} + +int ipu6_isys_isr_one(struct ipu6_bus_device *adev) +{ + struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); + struct ipu6_fw_isys_resp_info_abi *resp; + struct ipu6_isys_stream *stream; + struct ipu6_isys_csi2 *csi2 = NULL; + struct isys_fw_msgs *isys_fw_msg = NULL; + u32 index; + u64 ts; + + resp = ipu6_fw_isys_get_resp(isys); + if (!resp) + return 1; + + ts = (u64)resp->timestamp[1] << 32 | resp->timestamp[0]; + + index = resp_type_to_index(resp->type); + dev_dbg(&adev->auxdev.dev, + "FW resp %02d %s, stream %u, ts 0x%16.16llx, pin %d\n", + resp->type, fw_msg[index].msg, resp->stream_handle, + fw_msg[index].valid_ts ? ts : 0, resp->pin_id); + + if (resp->error_info.error == IPU6_FW_ISYS_ERROR_STREAM_IN_SUSPENSION) + /* Suspension is kind of special case: not enough buffers */ + dev_dbg(&adev->auxdev.dev, + "FW error resp SUSPENSION, details %d\n", + resp->error_info.error_details); + else if (resp->error_info.error) + dev_dbg(&adev->auxdev.dev, + "FW error resp error %d, details %d\n", + resp->error_info.error, resp->error_info.error_details); + + guard(spinlock_irqsave)(&isys->streams_lock); + + if (resp->stream_handle >= IPU6_ISYS_MAX_STREAMS) { + dev_err(&adev->auxdev.dev, "bad stream handle %u\n", + resp->stream_handle); + goto leave; + } + + stream = resp->stream_handle < IPU6_ISYS_MAX_STREAMS ? + isys->streams_by_handle[resp->stream_handle] : NULL; + if (!stream) { + dev_err(&adev->auxdev.dev, "stream of stream_handle %u is unused\n", + resp->stream_handle); + goto leave; + } + stream->error = resp->error_info.error; + + csi2 = ipu6_isys_subdev_to_csi2(stream->asd); + + switch (resp->type) { + case IPU6_FW_ISYS_RESP_TYPE_STREAM_OPEN_DONE: + complete(&stream->stream_open_completion); + break; + case IPU6_FW_ISYS_RESP_TYPE_STREAM_CLOSE_ACK: + complete(&stream->stream_close_completion); + break; + case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_ACK: + complete(&stream->stream_start_completion); + break; + case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK: + complete(&stream->stream_start_completion); + break; + case IPU6_FW_ISYS_RESP_TYPE_STREAM_STOP_ACK: + complete(&stream->stream_stop_completion); + break; + case IPU6_FW_ISYS_RESP_TYPE_STREAM_FLUSH_ACK: + complete(&stream->stream_stop_completion); + break; + case IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_READY: + /* + * firmware only release the capture message until software + * get pin_data_ready event + */ + if (!resp->buf_id) { + dev_warn(&adev->auxdev.dev, "%d: Invalid buf ID\n", + resp->stream_handle); + goto leave; + } + + isys_fw_msg = container_of((void *)(uintptr_t)resp->buf_id, + struct isys_fw_msgs, dummy); + + ipu6_put_fw_msg_buf(ipu6_bus_get_drvdata(adev), isys_fw_msg); + if (resp->pin_id < IPU6_ISYS_OUTPUT_PINS && + stream->output_pins_queue[resp->pin_id]) + ipu6_isys_queue_buf_ready(stream, resp); + else + dev_warn(&adev->auxdev.dev, + "%d:No queue for pin id %d\n", + resp->stream_handle, resp->pin_id); + if (csi2) + ipu6_isys_csi2_error(csi2); + + break; + case IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_ACK: + break; + case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE: + case IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_DONE: + break; + case IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF: + + ipu6_isys_csi2_sof_event_by_stream(stream); + stream->seq[stream->seq_index].sequence = + atomic_read(&stream->sequence) - 1; + stream->seq[stream->seq_index].timestamp = ts; + dev_dbg(&adev->auxdev.dev, + "sof: handle %d: (index %u), timestamp 0x%16.16llx\n", + resp->stream_handle, + stream->seq[stream->seq_index].sequence, ts); + stream->seq_index = (stream->seq_index + 1) + % IPU6_ISYS_MAX_PARALLEL_SOF; + break; + case IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF: + ipu6_isys_csi2_eof_event_by_stream(stream); + dev_dbg(&adev->auxdev.dev, + "eof: handle %d: (index %u), timestamp 0x%16.16llx\n", + resp->stream_handle, + stream->seq[stream->seq_index].sequence, ts); + break; + case IPU6_FW_ISYS_RESP_TYPE_STATS_DATA_READY: + break; + default: + dev_err(&adev->auxdev.dev, "%d:unknown response type %u\n", + resp->stream_handle, resp->type); + break; + } + +leave: + ipu6_fw_isys_put_resp(isys); + return 0; +} + +static void ipu6_isys_csi2_isr(struct ipu6_isys_csi2 *csi2) +{ + struct ipu6_isys_stream *stream; + unsigned int i; + u32 status; + + ipu6_isys_register_errors(csi2); + + status = readl(csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + + CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET); + + writel(status, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + + CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); + + scoped_guard(spinlock, &csi2->isys->streams_lock) { + for (i = 0; i < NR_OF_CSI2_VC; i++) { + if (status & IPU_CSI_RX_IRQ_FS_VC(i)) { + stream = csi2->streams_by_vc[i]; + if (!stream) + continue; + + ipu6_isys_csi2_sof_event_by_stream(stream); + } + + if (status & IPU_CSI_RX_IRQ_FE_VC(i)) { + stream = csi2->streams_by_vc[i]; + if (!stream) + continue; + + ipu6_isys_csi2_eof_event_by_stream(stream); + } + } + } +} + +irqreturn_t ipu6_isys_isr(struct ipu6_bus_device *adev) +{ + struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); + void __iomem *base = isys->pdata->base; + u32 status_sw, status_csi; + u32 ctrl0_status, ctrl0_clear; + int pm_status; + + pm_status = pm_runtime_get_if_active(&adev->auxdev.dev); + if (!pm_status) + return 0; + + ctrl0_status = isys->pdata->ipdata->csi2.ctrl0_irq_status; + ctrl0_clear = isys->pdata->ipdata->csi2.ctrl0_irq_clear; + + status_csi = readl(isys->pdata->base + ctrl0_status); + status_sw = readl(isys->pdata->base + + IPU6_REG_ISYS_UNISPART_IRQ_STATUS); + + writel(ISYS_UNISPART_IRQS & ~IPU6_ISYS_UNISPART_IRQ_SW, + base + IPU6_REG_ISYS_UNISPART_IRQ_MASK); + + do { + writel(status_csi, isys->pdata->base + ctrl0_clear); + + writel(status_sw, isys->pdata->base + + IPU6_REG_ISYS_UNISPART_IRQ_CLEAR); + + if (isys->isr_csi2_bits & status_csi) { + unsigned int i; + + for (i = 0; i < isys->pdata->ipdata->csi2.nports; i++) { + /* irq from not enabled port */ + if (!isys->csi2[i].base) + continue; + if (status_csi & IPU6_ISYS_UNISPART_IRQ_CSI2(i)) + ipu6_isys_csi2_isr(&isys->csi2[i]); + } + } + + writel(0, base + IPU6_REG_ISYS_UNISPART_SW_IRQ_REG); + + if (!ipu6_isys_isr_one(adev)) + status_sw = IPU6_ISYS_UNISPART_IRQ_SW; + else + status_sw = 0; + + status_csi = readl(isys->pdata->base + ctrl0_status); + status_sw |= readl(isys->pdata->base + + IPU6_REG_ISYS_UNISPART_IRQ_STATUS); + } while ((status_csi & isys->isr_csi2_bits) || + (status_sw & IPU6_ISYS_UNISPART_IRQ_SW)); + + writel(ISYS_UNISPART_IRQS, base + IPU6_REG_ISYS_UNISPART_IRQ_MASK); + + if (pm_status > 0) + pm_runtime_put(&adev->auxdev.dev); + + return IRQ_HANDLED; +} + +static int ipu6_isys_fw_pin_cfg(struct ipu6_isys_video *av, + struct ipu6_isys_stream *stream, + struct media_pad *src_pad, + struct v4l2_mbus_frame_desc_entry *entry, + void *__cfg) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(src_pad->entity); + struct v4l2_subdev_state *state = v4l2_subdev_get_locked_active_state(sd); + struct ipu6_fw_isys_stream_cfg_data_abi *cfg = __cfg; + struct ipu6_fw_isys_input_pin_info_abi *input_pin; + struct ipu6_fw_isys_output_pin_info_abi *output_pin; + struct ipu6_isys_queue *aq = &av->aq; + struct v4l2_mbus_framefmt *fmt; + const struct ipu6_isys_pixelformat *pfmt = + ipu6_isys_get_isys_format(ipu6_isys_get_format(av), 0); + struct v4l2_rect v4l2_crop; + struct ipu6_isys *isys = av->isys; + int input_pins = cfg->nof_input_pins++; + int output_pins; + u32 src_stream; + + src_stream = __ipu6_isys_get_src_stream_by_src_pad(state, src_pad->index); + fmt = v4l2_subdev_state_get_format(state, src_pad->index, src_stream); + v4l2_crop = *v4l2_subdev_state_get_crop(state, src_pad->index, src_stream); + + input_pin = &cfg->input_pins[input_pins]; + input_pin->input_res.width = fmt->width; + input_pin->input_res.height = fmt->height; + input_pin->dt = entry->bus.csi2.dt; + input_pin->bits_per_pix = pfmt->bpp_packed; + input_pin->mapped_dt = 0x40; /* invalid mipi data type */ + input_pin->mipi_decompression = 0; + input_pin->capture_mode = IPU6_FW_ISYS_CAPTURE_MODE_REGULAR; + input_pin->mipi_store_mode = pfmt->bpp == pfmt->bpp_packed ? + IPU6_FW_ISYS_MIPI_STORE_MODE_DISCARD_LONG_HEADER : + IPU6_FW_ISYS_MIPI_STORE_MODE_NORMAL; + input_pin->crop_first_and_last_lines = v4l2_crop.top & 1; + + output_pins = cfg->nof_output_pins++; + aq->fw_output = output_pins; + stream->output_pins_queue[output_pins] = aq; + + output_pin = &cfg->output_pins[output_pins]; + output_pin->input_pin_id = input_pins; + output_pin->output_res.width = ipu6_isys_get_frame_width(av); + output_pin->output_res.height = ipu6_isys_get_frame_height(av); + + output_pin->stride = ipu6_isys_get_bytes_per_line(av); + if (pfmt->bpp != pfmt->bpp_packed) + output_pin->pt = IPU6_FW_ISYS_PIN_TYPE_RAW_SOC; + else + output_pin->pt = IPU6_FW_ISYS_PIN_TYPE_MIPI; + output_pin->ft = pfmt->css_pixelformat; + output_pin->send_irq = 1; + memset(output_pin->ts_offsets, 0, sizeof(output_pin->ts_offsets)); + output_pin->s2m_pixel_soc_pixel_remapping = + S2M_PIXEL_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING; + output_pin->csi_be_soc_pixel_remapping = + CSI_BE_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING; + + output_pin->snoopable = true; + output_pin->error_handling_enable = false; + output_pin->sensor_type = isys->sensor_type++; + if (isys->sensor_type > isys->pdata->ipdata->sensor_type_end) + isys->sensor_type = isys->pdata->ipdata->sensor_type_start; + + return 0; +} + +static int ipu6_fw_isys_prepare_stream_cfg(struct ipu6_isys_stream *stream, + struct v4l2_mbus_frame_desc *desc, + struct isys_fw_msgs *msg) +{ + struct ipu6_fw_isys_stream_cfg_data_abi *stream_cfg; + struct device *dev = &stream->isys->adev->auxdev.dev; + int ret; + + stream_cfg = &msg->ipu6.stream; + stream_cfg->src = stream->asd->source; + stream_cfg->vc = stream->vc; + stream_cfg->isl_use = 0; + stream_cfg->sensor_type = IPU6_FW_ISYS_SENSOR_MODE_NORMAL; + + ret = ipu6_isys_fw_pins_prepare(stream, desc, ipu6_isys_fw_pin_cfg, + stream_cfg); + if (ret) + return ret; + + ipu6_fw_isys_dump_stream_cfg(dev, msg); + + stream->nr_output_pins = stream_cfg->nof_output_pins; + + return 0; +} + +static int ipu6_fw_isys_stream_open(struct ipu6_isys *isys, + const unsigned int stream_handle, + struct isys_fw_msgs *msg) +{ + return ipu6_fw_isys_complex_cmd(isys, stream_handle, + &msg->ipu6.stream, msg->dma_addr, + sizeof(msg->ipu6.stream), + IPU6_FW_ISYS_SEND_TYPE_STREAM_OPEN); +} + +static int ipu6_fw_isys_stream_close(struct ipu6_isys *isys, + const unsigned int stream_handle) +{ + return ipu6_fw_isys_complex_cmd(isys, stream_handle, NULL, 0, 0, + IPU6_FW_ISYS_SEND_TYPE_STREAM_CLOSE); +} + +static int ipu6_fw_isys_stream_flush(struct ipu6_isys *isys, + const unsigned int stream_handle) +{ + return ipu6_fw_isys_complex_cmd(isys, stream_handle, NULL, 0, 0, + IPU6_FW_ISYS_SEND_TYPE_STREAM_FLUSH); +} + +static int ipu6_fw_isys_stream_start(struct ipu6_isys *isys, + const unsigned int stream_handle, + struct isys_fw_msgs *msg, bool capture) +{ + u16 cmd_type; + + if (capture) + cmd_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_START_AND_CAPTURE; + else + cmd_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_START; + + return ipu6_fw_isys_complex_cmd(isys, stream_handle, + &msg->ipu6.stream, msg->dma_addr, + sizeof(msg->ipu6.stream), cmd_type); +} + +static int ipu6_fw_isys_stream_capture(struct ipu6_isys *isys, + const unsigned int stream_handle, + struct isys_fw_msgs *msg) +{ + return ipu6_fw_isys_complex_cmd(isys, stream_handle, + &msg->ipu6.stream, msg->dma_addr, + sizeof(msg->ipu6.stream), + IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE); +} + +static void +ipu6_isys_buf_to_fw_frame_buf_pin(struct vb2_buffer *vb, + struct ipu6_fw_isys_frame_buff_set_abi *set) +{ + struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); + struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb); + struct ipu6_isys_video_buffer *ivb = + vb2_buffer_to_ipu6_isys_video_buffer(vvb); + + set->output_pins[aq->fw_output].addr = ivb->dma_addr; + set->output_pins[aq->fw_output].out_buf_id = vb->index + 1; +} + +/* + * Convert a buffer list to a isys fw ABI framebuffer set. The + * buffer list is not modified. + */ +#define IPU6_ISYS_FRAME_NUM_THRESHOLD (30) +static void ipu6_fw_isys_prepare_buf_set(struct isys_fw_msgs *msg, + struct ipu6_isys_stream *stream, + struct ipu6_isys_buffer_list *bl) +{ + struct ipu6_fw_isys_frame_buff_set_abi *set = &msg->ipu6.frame; + struct ipu6_isys_buffer *ib; + + WARN_ON(!bl->nbufs); + + set->send_irq_sof = 1; + set->send_resp_sof = 1; + set->send_irq_eof = 0; + set->send_resp_eof = 0; + + set->send_irq_capture_ack = 1; + set->send_irq_capture_done = 0; + + set->send_resp_capture_ack = 1; + set->send_resp_capture_done = 1; + if (atomic_read(&stream->sequence) >= IPU6_ISYS_FRAME_NUM_THRESHOLD) { + set->send_resp_capture_ack = 0; + set->send_resp_capture_done = 0; + } + + list_for_each_entry(ib, &bl->head, head) { + struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib); + + ipu6_isys_buf_to_fw_frame_buf_pin(vb, set); + } +} + +const struct ipu6_fw_isys_ops ipu6_fw_isys_ops = { + .init = ipu6_fw_isys_init, + .close = ipu6_fw_isys_close, + .cleanup = ipu6_fw_isys_cleanup, + .prepare_stream_cfg = ipu6_fw_isys_prepare_stream_cfg, + .prepare_buf_set = ipu6_fw_isys_prepare_buf_set, + .stream_open = ipu6_fw_isys_stream_open, + .stream_start = ipu6_fw_isys_stream_start, + .stream_capture = ipu6_fw_isys_stream_capture, + .stream_flush = ipu6_fw_isys_stream_flush, + .stream_close = ipu6_fw_isys_stream_close, + .dump_stream_cfg = ipu6_fw_isys_dump_stream_cfg, + .dump_frame_buf_set = ipu6_fw_isys_dump_frame_buf_set, +}; diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h index b60f02076d8a..2a679d995961 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h +++ b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h @@ -8,6 +8,10 @@ struct device; struct ipu6_isys; +struct ipu6_isys_video; +struct isys_fw_msgs; +struct ipu6_isys_stream; +struct ipu6_isys_buffer_list; /* Max number of Input/Output Pins */ #define IPU6_MAX_IPINS 4 @@ -512,21 +516,6 @@ struct ipu6_fw_isys_proxy_resp_info_abi { }; /** - * struct ipu6_fw_proxy_write_queue_token - ISYS proxy write queue token - * @request_id: update id for the specific proxy write request - * @region_index: Region id for the proxy write request - * @offset: Offset of the write request according to the base address - * of the region - * @value: Value that is requested to be written with the proxy write request - */ -struct ipu6_fw_proxy_write_queue_token { - u32 request_id; - u32 region_index; - u32 offset; - u32 value; -}; - -/** * struct ipu6_fw_resp_queue_token - ISYS response queue token * @resp_info: response info */ @@ -570,27 +559,10 @@ struct ipu6_fw_proxy_send_queue_token { u32 value; }; -void -ipu6_fw_isys_dump_stream_cfg(struct device *dev, - struct ipu6_fw_isys_stream_cfg_data_abi *cfg); -void -ipu6_fw_isys_dump_frame_buff_set(struct device *dev, - struct ipu6_fw_isys_frame_buff_set_abi *buf, - unsigned int outputs); -int ipu6_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams); -int ipu6_fw_isys_close(struct ipu6_isys *isys); -int ipu6_fw_isys_simple_cmd(struct ipu6_isys *isys, - const unsigned int stream_handle, u16 send_type); -int ipu6_fw_isys_complex_cmd(struct ipu6_isys *isys, - const unsigned int stream_handle, - void *cpu_mapped_buf, dma_addr_t dma_mapped_buf, - size_t size, u16 send_type); -int ipu6_fw_isys_send_proxy_token(struct ipu6_isys *isys, - unsigned int req_id, - unsigned int index, - unsigned int offset, u32 value); -void ipu6_fw_isys_cleanup(struct ipu6_isys *isys); -struct ipu6_fw_isys_resp_info_abi * -ipu6_fw_isys_get_resp(void *context, unsigned int queue); -void ipu6_fw_isys_put_resp(void *context, unsigned int queue); +int ipu6_fw_isys_send_proxy_token(struct ipu6_isys *isys, unsigned int req_id, + unsigned int index, unsigned int offset, + u32 value); +int ipu6_isys_isr_one(struct ipu6_bus_device *adev); +irqreturn_t ipu6_isys_isr(struct ipu6_bus_device *adev); + #endif diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c index 7e539a0c6c92..0db6971d8d3e 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c @@ -11,9 +11,12 @@ #include <linux/err.h> #include <linux/io.h> #include <linux/minmax.h> +#include <linux/pm_runtime.h> #include <linux/sprintf.h> +#include <linux/string_choices.h> #include <media/media-entity.h> +#include <media/mipi-csi2.h> #include <media/v4l2-ctrls.h> #include <media/v4l2-device.h> #include <media/v4l2-event.h> @@ -23,7 +26,9 @@ #include "ipu6-isys.h" #include "ipu6-isys-csi2.h" #include "ipu6-isys-subdev.h" +#include "ipu6-isys-video.h" #include "ipu6-platform-isys-csi2-reg.h" +#include "ipu7-isys-csi2-regs.h" static const u32 csi2_supported_codes[] = { MEDIA_BUS_FMT_RGB565_1X16, @@ -228,56 +233,124 @@ void ipu6_isys_csi2_error(struct ipu6_isys_csi2 *csi2) } } -static int ipu6_isys_csi2_set_stream(struct v4l2_subdev *sd, - const struct ipu6_isys_csi2_timing *timing, - unsigned int nlanes, int enable) +static void ipu6_isys_csi2_setup_watermark(struct ipu6_isys_csi2 *csi2, + struct v4l2_subdev_state *csi2_state, + struct v4l2_subdev *remote_sd) +{ + struct device *dev = &csi2->isys->adev->auxdev.dev; + struct v4l2_control hb = { .id = V4L2_CID_HBLANK, .value = 0 }; + struct v4l2_subdev_route *route; + u32 max_stream_data_rate = 0, hblank = 0; + s64 link_freq; + int ret; + + if (IS_IPU7(csi2->asd.isys->adev->isp)) + return; + + ret = v4l2_g_ctrl(remote_sd->ctrl_handler, &hb); + if (!ret) + hblank = max(0, hb.value); + + link_freq = ipu6_isys_csi2_get_link_freq(csi2); + if (link_freq <= 0) { + csi2->watermark.force_iwake_disable = true; + dev_warn(dev, "unexpected link_freq %lld (source %s)\n", + link_freq, remote_sd->entity.name); + ipu6_isys_update_watermark_setting(csi2->isys); + return; + } + + for_each_active_route(&csi2_state->routing, route) { + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_state_get_format(csi2_state, CSI2_PAD_SINK, + route->sink_stream); + if (WARN_ON(!fmt)) + continue; + + u32 bpp = ipu6_isys_mbus_code_to_bpp(fmt->code); + u64 pixel_rate = mul_u64_u32_div(link_freq, csi2->nlanes * 2, + bpp); + u32 pixels_per_line = fmt->width + hblank; + u64 line_time_ns = div_u64(pixels_per_line * NSEC_PER_SEC, + pixel_rate); + u32 bytes_per_line = fmt->width * bpp / 8; + u32 pages_per_line = + DIV_ROUND_UP(bytes_per_line, + csi2->isys->pdata->ipdata->sram_gran_size); + u32 pb_bytes_per_line = + pages_per_line << csi2->isys->pdata->ipdata->sram_gran_shift; + u64 stream_data_rate = + div64_u64(pb_bytes_per_line * 1000, line_time_ns); + + dev_dbg(dev, "stream %u:%u -> %u:%u data rate %lld\n", + route->sink_pad, route->sink_stream, route->source_pad, + route->source_stream, stream_data_rate); + + max_stream_data_rate = max(max_stream_data_rate, + stream_data_rate); + } + + csi2->watermark.stream_data_rate = max_stream_data_rate; + + ipu6_isys_update_watermark_setting(csi2->isys); +} + +static void ipu6_isys_csi2_clear_watermark(struct ipu6_isys_csi2 *csi2) +{ + if (IS_IPU7(csi2->asd.isys->adev->isp)) + return; + + csi2->watermark.force_iwake_disable = false; + csi2->watermark.stream_data_rate = 0; + ipu6_isys_update_watermark_setting(csi2->isys); +} + +static void ipu6_isys_csi2_stream_disable(struct ipu6_isys_csi2 *csi2) { - struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd); - struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(asd); struct ipu6_isys *isys = csi2->isys; - struct device *dev = &isys->adev->auxdev.dev; struct ipu6_isys_csi2_config cfg; - unsigned int nports; - int ret = 0; - u32 mask = 0; - u32 i; - - dev_dbg(dev, "stream %s CSI2-%u with %u lanes\n", enable ? "on" : "off", - csi2->port, nlanes); + u32 mask = isys->pdata->ipdata->csi2.irq_mask; cfg.port = csi2->port; - cfg.nlanes = nlanes; + cfg.nlanes = csi2->nlanes; - mask = isys->pdata->ipdata->csi2.irq_mask; - nports = isys->pdata->ipdata->csi2.nports; + writel(0, csi2->base + CSI_REG_CSI_FE_ENABLE); + writel(0, csi2->base + CSI_REG_PPI2CSI_ENABLE); + writel(0, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + + CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET); + writel(mask, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + + CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); + writel(0, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + + CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET); + writel(0xffffffff, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + + CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); - if (!enable) { - writel(0, csi2->base + CSI_REG_CSI_FE_ENABLE); - writel(0, csi2->base + CSI_REG_PPI2CSI_ENABLE); - - writel(0, - csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + - CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET); - writel(mask, - csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + - CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); - writel(0, - csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + - CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET); - writel(0xffffffff, - csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + - CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); - - isys->phy_set_power(isys, &cfg, timing, false); - - writel(0, isys->pdata->base + CSI_REG_HUB_FW_ACCESS_PORT - (isys->pdata->ipdata->csi2.fw_access_port_ofs, - csi2->port)); - writel(0, isys->pdata->base + - CSI_REG_HUB_DRV_ACCESS_PORT(csi2->port)); + isys->phy_set_power(isys, &cfg, NULL, false); + writel(0, isys->pdata->base + CSI_REG_HUB_FW_ACCESS_PORT + (isys->pdata->ipdata->csi2.fw_access_port_ofs, csi2->port)); + writel(0, isys->pdata->base + CSI_REG_HUB_DRV_ACCESS_PORT(csi2->port)); +} + +static int ipu6_isys_csi2_stream_enable(struct ipu6_isys_csi2 *csi2) +{ + struct ipu6_isys *isys = csi2->isys; + struct ipu6_isys_csi2_timing timing = { }; + struct ipu6_isys_csi2_config cfg; + unsigned int nports; + u32 mask; + int ret; + + cfg.port = csi2->port; + cfg.nlanes = csi2->nlanes; + + ret = ipu6_isys_csi2_calc_timing(csi2, &timing, CSI2_ACCINV); + if (ret) return ret; - } + + mask = isys->pdata->ipdata->csi2.irq_mask; + nports = isys->pdata->ipdata->csi2.nports; /* reset port reset */ writel(0x1, csi2->base + CSI_REG_PORT_GPREG_SRST); @@ -285,7 +358,7 @@ static int ipu6_isys_csi2_set_stream(struct v4l2_subdev *sd, writel(0x0, csi2->base + CSI_REG_PORT_GPREG_SRST); /* enable port clock */ - for (i = 0; i < nports; i++) { + for (unsigned int i = 0; i < nports; i++) { writel(1, isys->pdata->base + CSI_REG_HUB_DRV_ACCESS_PORT(i)); writel(1, isys->pdata->base + CSI_REG_HUB_FW_ACCESS_PORT (isys->pdata->ipdata->csi2.fw_access_port_ofs, i)); @@ -329,18 +402,217 @@ static int ipu6_isys_csi2_set_stream(struct v4l2_subdev *sd, writel(CSI_SENSOR_INPUT, csi2->base + CSI_REG_CSI_FE_MUX_CTRL); writel(CSI_CNTR_SENSOR_LINE_ID | CSI_CNTR_SENSOR_FRAME_ID, csi2->base + CSI_REG_CSI_FE_SYNC_CNTR_SEL); - writel(FIELD_PREP(PPI_INTF_CONFIG_NOF_ENABLED_DLANES_MASK, nlanes - 1), + writel(FIELD_PREP(PPI_INTF_CONFIG_NOF_ENABLED_DLANES_MASK, + csi2->nlanes - 1), csi2->base + CSI_REG_PPI2CSI_CONFIG_PPI_INTF); writel(1, csi2->base + CSI_REG_PPI2CSI_ENABLE); writel(1, csi2->base + CSI_REG_CSI_FE_ENABLE); - ret = isys->phy_set_power(isys, &cfg, timing, true); + return isys->phy_set_power(isys, &cfg, &timing, true); +} + +static void ipu7_csi2_irq_enable(struct ipu6_isys_csi2 *csi2) +{ + struct ipu6_isys *isys = csi2->isys; + unsigned int offset, mask; + + /* enable CSI2 legacy error irq */ + offset = IPU7_IS_IO_CSI2_ERR_LEGACY_IRQ_CTL_BASE(csi2->port); + mask = IPU7_CSI_RX_ERROR_IRQ_MASK; + writel(mask, csi2->base + offset + IPU7_IRQ_CTL_CLEAR); + writel(mask, csi2->base + offset + IPU7_IRQ_CTL_MASK); + writel(mask, csi2->base + offset + IPU7_IRQ_CTL_ENABLE); + + /* enable CSI2 legacy sync irq */ + offset = IPU7_IS_IO_CSI2_SYNC_LEGACY_IRQ_CTL_BASE(csi2->port); + mask = IPU7_CSI_RX_SYNC_IRQ_MASK; + writel(mask, csi2->base + offset + IPU7_IRQ_CTL_CLEAR); + writel(mask, csi2->base + offset + IPU7_IRQ_CTL_MASK); + writel(mask, csi2->base + offset + IPU7_IRQ_CTL_ENABLE); + + if (!IS_IPU7_MTL(isys->adev->isp)) { + mask = IPU7P5_CSI_RX_SYNC_FE_IRQ_MASK; + writel(mask, csi2->base + offset + IPU7_IRQ1_CTL_CLEAR); + writel(mask, csi2->base + offset + IPU7_IRQ1_CTL_MASK); + writel(mask, csi2->base + offset + IPU7_IRQ1_CTL_ENABLE); + } +} + +static void ipu7_csi2_irq_disable(struct ipu6_isys_csi2 *csi2) +{ + struct ipu6_isys *isys = csi2->isys; + unsigned int offset, mask; + + /* disable CSI2 legacy error irq */ + offset = IPU7_IS_IO_CSI2_ERR_LEGACY_IRQ_CTL_BASE(csi2->port); + mask = IPU7_CSI_RX_ERROR_IRQ_MASK; + writel(mask, csi2->base + offset + IPU7_IRQ_CTL_CLEAR); + writel(0, csi2->base + offset + IPU7_IRQ_CTL_MASK); + writel(0, csi2->base + offset + IPU7_IRQ_CTL_ENABLE); + + /* disable CSI2 legacy sync irq */ + offset = IPU7_IS_IO_CSI2_SYNC_LEGACY_IRQ_CTL_BASE(csi2->port); + mask = IPU7_CSI_RX_SYNC_IRQ_MASK; + writel(mask, csi2->base + offset + IPU7_IRQ_CTL_CLEAR); + writel(0, csi2->base + offset + IPU7_IRQ_CTL_MASK); + writel(0, csi2->base + offset + IPU7_IRQ_CTL_ENABLE); + + if (!IS_IPU7_MTL(isys->adev->isp)) { + writel(mask, csi2->base + offset + IPU7_IRQ1_CTL_CLEAR); + writel(0, csi2->base + offset + IPU7_IRQ1_CTL_MASK); + writel(0, csi2->base + offset + IPU7_IRQ1_CTL_ENABLE); + } +} + +static void ipu7_isys_csi2_stream_disable(struct ipu6_isys_csi2 *csi2) +{ + struct ipu6_isys *isys = csi2->isys; + void __iomem *base = isys->pdata->base; + struct ipu6_isys_csi2_config cfg; + + cfg.port = csi2->port; + cfg.nlanes = csi2->nlanes; + + isys->phy_set_power(isys, &cfg, NULL, false); + + writel(0x4, + base + IPU7_IS_IO_GPREGS_BASE + IPU7_CLK_DIV_FACTOR_APB_CLK); + ipu7_csi2_irq_disable(csi2); +} + +static int ipu7_isys_csi2_stream_enable(struct ipu6_isys_csi2 *csi2) +{ + struct ipu6_isys *isys = csi2->isys; + void __iomem *base = isys->pdata->base; + struct ipu6_isys_csi2_config cfg; + unsigned int offset = IPU7_IS_IO_GPREGS_BASE; + int ret; + + writel(0x2, base + offset + IPU7_CLK_DIV_FACTOR_APB_CLK); + if (csi2->port == 0U && csi2->nlanes == 4U && + !IS_IPU7_MTL(isys->adev->isp)) + writel(0x1, base + offset + IPU7_CSI_PORTAB_AGGREGATION); + + /* input is coming from CSI receiver (sensor) */ + offset = IPU7_IS_IO_CSI2_ADPL_PORT_BASE(csi2->port); + writel(CSI_SENSOR_INPUT, base + offset + IPU7_CSI2_ADPL_INPUT_MODE); + writel(1, base + offset + IPU7_CSI2_ADPL_CSI_RX_ERR_IRQ_CLEAR_EN); + + cfg.port = csi2->port; + cfg.nlanes = csi2->nlanes; + + ret = isys->phy_set_power(isys, &cfg, NULL, true); if (ret) - dev_err(dev, "csi-%d phy power up failed %d\n", csi2->port, - ret); + return ret; - return ret; + ipu7_csi2_irq_enable(csi2); + + return 0; +} + +static int ipu6_isys_csi2_streaming_change(struct ipu6_isys_subdev *asd, + struct v4l2_subdev_state *state, + u32 pad, + struct v4l2_mbus_frame_desc *desc, + u8 *vc, bool enable) +{ + struct v4l2_mbus_frame_desc_entry *this_entry = NULL; + struct v4l2_subdev_route *route, *this_route = NULL; + u32 streams_enabled = 0, nodes_streaming = 0; + + for_each_active_route(&state->routing, this_route) + if (pad == this_route->source_pad) + break; + if (!this_route) { + dev_dbg(asd->sd.dev, "no route found for pad %u\n", pad); + return -EINVAL; + } + + for (unsigned int i = 0; i < desc->num_entries; i++) { + if (desc->entry[i].stream == this_route->sink_stream) { + this_entry = &desc->entry[i]; + break; + } + } + if (!this_entry) { + dev_dbg(asd->sd.dev, + "no frame descriptor entry found for stream %u\n", + this_route->sink_stream); + return -EINVAL; + } + + for_each_active_route(&state->routing, route) { + struct v4l2_mbus_frame_desc_entry *entry = NULL; + + for (unsigned int i = 0; i < desc->num_entries; i++) { + if (desc->entry[i].stream == route->sink_stream) { + entry = &desc->entry[i]; + break; + } + } + + if (!entry) { + dev_dbg(asd->sd.dev, "cannot find stream %u from frame descriptor\n", + route->sink_stream); + return -EINVAL; + } + + if (entry->bus.csi2.vc != this_entry->bus.csi2.vc) + continue; + + struct media_pad *video_pad = + media_pad_remote_pad_first(&asd->sd.entity.pads[route->source_pad]); + if (!video_pad) + return -EINVAL; + + struct ipu6_isys_video *av = + container_of_const(video_pad, struct ipu6_isys_video, + pad); + + streams_enabled++; + if (av->streaming || (enable && pad == route->source_pad)) + nodes_streaming++; + } + + if (vc) + *vc = this_entry->bus.csi2.vc; + + if (streams_enabled == nodes_streaming) { + dev_dbg(asd->sd.dev, + "changing streaming state to %s on \"%s\"\n", + str_enabled_disabled(enable), asd->sd.entity.name); + return 1; + } + + dev_dbg(asd->sd.dev, "not setting streaming %s %s (%u/%u)\n", + str_enabled_disabled(enable), enable ? "yet" : "anymore", + nodes_streaming, streams_enabled); + + return 0; +} + +static int ipu6_isys_get_frame_desc(struct v4l2_subdev *remote_sd, + unsigned int pad, + struct v4l2_mbus_frame_desc *desc) +{ + struct v4l2_subdev_format fmt = { .which = V4L2_SUBDEV_FORMAT_ACTIVE }; + int ret; + + ret = v4l2_subdev_call(remote_sd, pad, get_frame_desc, pad, desc); + if (!ret || ret != -ENOIOCTLCMD) + return ret; + + ret = v4l2_subdev_call_state_active(remote_sd, pad, get_fmt, &fmt); + if (ret) + return ret; + + desc->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2; + desc->num_entries = 1; + desc->entry[0].pixelcode = fmt.format.code; + desc->entry[0].bus.csi2.dt = ipu6_isys_mbus_code_to_mipi(fmt.format.code); + + return 0; } static int ipu6_isys_csi2_enable_streams(struct v4l2_subdev *sd, @@ -349,60 +621,174 @@ static int ipu6_isys_csi2_enable_streams(struct v4l2_subdev *sd, { struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd); struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(asd); - struct ipu6_isys_csi2_timing timing = { }; - struct v4l2_subdev *remote_sd; - struct media_pad *remote_pad; + struct ipu6_device *isp = asd->isys->adev->isp; + struct media_pad *remote_pad = + media_pad_remote_pad_first(&sd->entity.pads[CSI2_PAD_SINK]), + *vdev_pad = media_pad_remote_pad_unique(&sd->entity.pads[pad]); + struct ipu6_isys_video *av = + container_of_const(vdev_pad, struct ipu6_isys_video, pad); + struct v4l2_subdev *remote_sd = + media_entity_to_v4l2_subdev(remote_pad->entity); + struct v4l2_mbus_frame_desc desc = { 0 }; + struct ipu6_isys_stream *stream; + struct ipu6_isys_buffer_list bl; u64 sink_streams; int ret; + u8 vc; - remote_pad = media_pad_remote_pad_first(&sd->entity.pads[CSI2_PAD_SINK]); - remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); + lockdep_assert_held(&csi2->isys->stream_mutex); + + ret = ipu6_isys_get_frame_desc(remote_sd, remote_pad->index, &desc); + if (ret) + return ret; + + list_add(&av->csi2_entry, &csi2->av_head); sink_streams = v4l2_subdev_state_xlate_streams(state, pad, CSI2_PAD_SINK, &streams_mask); + csi2->stream_ids |= sink_streams; - ret = ipu6_isys_csi2_calc_timing(csi2, &timing, CSI2_ACCINV); - if (ret) + ret = ipu6_isys_csi2_streaming_change(asd, state, pad, &desc, &vc, + true); + if (!ret) return ret; + if (ret < 0) + goto err_av_del; + + ret = pm_runtime_resume_and_get(sd->dev); + if (ret < 0) + goto err_del_av; - ret = ipu6_isys_csi2_set_stream(sd, &timing, csi2->nlanes, true); + ipu6_isys_csi2_setup_watermark(csi2, state, remote_sd); + + stream = ipu6_isys_alloc_stream_firmware(csi2, state, &desc, vc); + if (IS_ERR(stream)) { + ret = PTR_ERR(stream); + dev_err(sd->dev, "allocating firmware stream failed\n"); + goto err_clear_watermark; + } + + ret = ipu6_isys_buffer_list_get(stream, &bl); + if (ret < 0) { + dev_warn(sd->dev, "no buffer available, DRIVER BUG?\n"); + goto err_free_stream_firmware; + } + + ret = ipu6_isys_start_stream_firmware(stream, &bl, &desc); if (ret) - return ret; + goto err_requeue_buffers; - ret = v4l2_subdev_enable_streams(remote_sd, remote_pad->index, - sink_streams); - if (ret) { - ipu6_isys_csi2_set_stream(sd, NULL, 0, false); - return ret; + if (!csi2->streaming_vc) { + ret = IS_IPU7(isp) ? ipu7_isys_csi2_stream_enable(csi2) : + ipu6_isys_csi2_stream_enable(csi2); + if (ret) + goto err_stop_stream_firmware; } + ret = v4l2_subdev_enable_streams(remote_sd, remote_pad->index, + csi2->stream_ids); + if (ret) + goto err_stop_stream_csi2; + + csi2->streaming_vc |= BIT(vc); + return 0; + +err_stop_stream_csi2: + if (IS_IPU7(isp)) + ipu7_isys_csi2_stream_disable(csi2); + else + ipu6_isys_csi2_stream_disable(csi2); + +err_requeue_buffers: + ipu6_isys_buffer_list_queue(&bl, IPU6_ISYS_BUFFER_LIST_FL_INCOMING, 0); + +err_stop_stream_firmware: + ipu6_isys_stop_stream_firmware(stream); + ipu6_isys_close_stream_firmware(stream); + +err_free_stream_firmware: + ipu6_isys_free_stream_firmware(stream); + +err_clear_watermark: + ipu6_isys_csi2_clear_watermark(csi2); + pm_runtime_put(sd->dev); + +err_del_av: + ipu6_isys_csi2_streaming_change(asd, state, pad, &desc, NULL, false); + csi2->stream_ids &= ~sink_streams; +err_av_del: + list_del(&av->csi2_entry); + + return ret; } static int ipu6_isys_csi2_disable_streams(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, u32 pad, u64 streams_mask) { - struct v4l2_subdev *remote_sd; - struct media_pad *remote_pad; + struct media_pad *remote_pad = + media_pad_remote_pad_first(&sd->entity.pads[CSI2_PAD_SINK]), + *vdev_pad = media_pad_remote_pad_unique(&sd->entity.pads[pad]); + struct ipu6_isys_video *av = + container_of_const(vdev_pad, struct ipu6_isys_video, pad); + struct v4l2_subdev *remote_sd = + media_entity_to_v4l2_subdev(remote_pad->entity); + struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd); + struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(asd); + struct ipu6_device *isp = asd->isys->adev->isp; + struct v4l2_mbus_frame_desc desc = { 0 }; u64 sink_streams; + int ret; + u8 vc; + + lockdep_assert_held(&csi2->isys->stream_mutex); + + ret = ipu6_isys_get_frame_desc(remote_sd, remote_pad->index, &desc); + if (ret) + return ret; sink_streams = v4l2_subdev_state_xlate_streams(state, pad, CSI2_PAD_SINK, &streams_mask); - remote_pad = media_pad_remote_pad_first(&sd->entity.pads[CSI2_PAD_SINK]); - remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); + csi2->stream_ids &= ~sink_streams; + + ret = ipu6_isys_csi2_streaming_change(asd, state, pad, &desc, &vc, + false); + if (ret <= 0) + goto out_del_csi2_entry; + + csi2->streaming_vc &= ~BIT(vc); + + struct ipu6_isys_stream *stream = + ipu6_isys_find_stream_firmware(csi2, vc); + ipu6_isys_stop_stream_firmware(stream); - ipu6_isys_csi2_set_stream(sd, NULL, 0, false); + if IS_IPU7(isp) + ipu7_isys_csi2_stream_disable(csi2); + else + ipu6_isys_csi2_stream_disable(csi2); - v4l2_subdev_disable_streams(remote_sd, remote_pad->index, sink_streams); + v4l2_subdev_disable_streams(remote_sd, remote_pad->index, + csi2->stream_ids | sink_streams); + + ipu6_isys_close_stream_firmware(stream); + ipu6_isys_free_stream_firmware(stream); + + ipu6_isys_csi2_clear_watermark(csi2); + + pm_runtime_put(sd->dev); + +out_del_csi2_entry: + list_del(&av->csi2_entry); return 0; } static int ipu6_isys_csi2_set_sel(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -454,6 +840,7 @@ static int ipu6_isys_csi2_set_sel(struct v4l2_subdev *sd, } static int ipu6_isys_csi2_get_sel(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -542,6 +929,8 @@ int ipu6_isys_csi2_init(struct ipu6_isys_csi2 *csi2, if (ret) goto fail; + INIT_LIST_HEAD(&csi2->av_head); + INIT_LIST_HEAD(&csi2->streams); csi2->asd.source = IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT0 + index; csi2->asd.supported_codes = csi2_supported_codes; snprintf(csi2->asd.sd.name, sizeof(csi2->asd.sd.name), @@ -592,56 +981,3 @@ void ipu6_isys_csi2_eof_event_by_stream(struct ipu6_isys_stream *stream) dev_dbg(dev, "eof_event::csi2-%i sequence: %i\n", csi2->port, frame_sequence); } - -int ipu6_isys_csi2_get_remote_desc(u32 source_stream, - struct ipu6_isys_csi2 *csi2, - struct media_entity *source_entity, - struct v4l2_mbus_frame_desc_entry *entry) -{ - struct v4l2_mbus_frame_desc_entry *desc_entry = NULL; - struct device *dev = &csi2->isys->adev->auxdev.dev; - struct v4l2_mbus_frame_desc desc; - struct v4l2_subdev *source; - struct media_pad *pad; - unsigned int i; - int ret; - - source = media_entity_to_v4l2_subdev(source_entity); - if (!source) - return -EPIPE; - - pad = media_pad_remote_pad_first(&csi2->asd.pad[CSI2_PAD_SINK]); - if (!pad) - return -EPIPE; - - ret = v4l2_subdev_call(source, pad, get_frame_desc, pad->index, &desc); - if (ret) - return ret; - - if (desc.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2) { - dev_err(dev, "Unsupported frame descriptor type\n"); - return -EINVAL; - } - - for (i = 0; i < desc.num_entries; i++) { - if (source_stream == desc.entry[i].stream) { - desc_entry = &desc.entry[i]; - break; - } - } - - if (!desc_entry) { - dev_err(dev, "Failed to find stream %u from remote subdev\n", - source_stream); - return -EINVAL; - } - - if (desc_entry->bus.csi2.vc >= NR_OF_CSI2_VC) { - dev_err(dev, "invalid vc %d\n", desc_entry->bus.csi2.vc); - return -EINVAL; - } - - *entry = *desc_entry; - - return 0; -} diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h index ce8eed91065c..1620ac16f90d 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h @@ -16,6 +16,9 @@ struct ipu6_isys_video; struct ipu6_isys; struct ipu6_isys_stream; +#define PHY_MODE_DPHY 0 +#define PHY_MODE_CPHY 1 + #define NR_OF_CSI2_VC 16 #define INVALID_VC_ID -1 #define NR_OF_CSI2_SINK_PADS 1 @@ -38,11 +41,22 @@ struct ipu6_isys_csi2 { struct ipu6_isys_subdev asd; struct ipu6_isys *isys; struct ipu6_isys_video av[NR_OF_CSI2_SRC_PADS]; + struct list_head av_head; + struct list_head streams; + struct ipu6_isys_stream *streams_by_vc[NR_OF_CSI2_VC]; void __iomem *base; u32 receiver_errors; unsigned int nlanes; unsigned int port; + u32 legacy_irq_mask; + unsigned int phy_mode; + struct { + u32 stream_data_rate; + bool force_iwake_disable; + } watermark; + u32 streaming_vc; + u64 stream_ids; }; struct ipu6_isys_csi2_timing { @@ -70,9 +84,5 @@ void ipu6_isys_csi2_sof_event_by_stream(struct ipu6_isys_stream *stream); void ipu6_isys_csi2_eof_event_by_stream(struct ipu6_isys_stream *stream); void ipu6_isys_register_errors(struct ipu6_isys_csi2 *csi2); void ipu6_isys_csi2_error(struct ipu6_isys_csi2 *csi2); -int ipu6_isys_csi2_get_remote_desc(u32 source_stream, - struct ipu6_isys_csi2 *csi2, - struct media_entity *source_entity, - struct v4l2_mbus_frame_desc_entry *entry); #endif /* IPU6_ISYS_CSI2_H */ diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c index fabaed63df0c..2608e3b91acb 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c @@ -21,6 +21,7 @@ #include "ipu6-dma.h" #include "ipu6-fw-isys.h" #include "ipu6-isys.h" +#include "ipu6-isys-queue.h" #include "ipu6-isys-video.h" static int ipu6_isys_buf_init(struct vb2_buffer *vb) @@ -191,8 +192,8 @@ static void flush_firmware_streamon_fail(struct ipu6_isys_stream *stream) * that contains one entry from each video buffer queue. If a buffer can't be * obtained from every queue, the buffers are returned back to the queue. */ -static int buffer_list_get(struct ipu6_isys_stream *stream, - struct ipu6_isys_buffer_list *bl) +int ipu6_isys_buffer_list_get(struct ipu6_isys_stream *stream, + struct ipu6_isys_buffer_list *bl) { struct device *dev = &stream->isys->adev->auxdev.dev; struct ipu6_isys_queue *aq; @@ -233,106 +234,53 @@ static int buffer_list_get(struct ipu6_isys_stream *stream, return 0; } -static void -ipu6_isys_buf_to_fw_frame_buf_pin(struct vb2_buffer *vb, - struct ipu6_fw_isys_frame_buff_set_abi *set) -{ - struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); - struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb); - struct ipu6_isys_video_buffer *ivb = - vb2_buffer_to_ipu6_isys_video_buffer(vvb); - - set->output_pins[aq->fw_output].addr = ivb->dma_addr; - set->output_pins[aq->fw_output].out_buf_id = vb->index + 1; -} - -/* - * Convert a buffer list to a isys fw ABI framebuffer set. The - * buffer list is not modified. - */ -#define IPU6_ISYS_FRAME_NUM_THRESHOLD (30) -void -ipu6_isys_buf_to_fw_frame_buf(struct ipu6_fw_isys_frame_buff_set_abi *set, - struct ipu6_isys_stream *stream, - struct ipu6_isys_buffer_list *bl) -{ - struct ipu6_isys_buffer *ib; - - WARN_ON(!bl->nbufs); - - set->send_irq_sof = 1; - set->send_resp_sof = 1; - set->send_irq_eof = 0; - set->send_resp_eof = 0; - - if (stream->streaming) - set->send_irq_capture_ack = 0; - else - set->send_irq_capture_ack = 1; - set->send_irq_capture_done = 0; - - set->send_resp_capture_ack = 1; - set->send_resp_capture_done = 1; - if (atomic_read(&stream->sequence) >= IPU6_ISYS_FRAME_NUM_THRESHOLD) { - set->send_resp_capture_ack = 0; - set->send_resp_capture_done = 0; - } - - list_for_each_entry(ib, &bl->head, head) { - struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib); - - ipu6_isys_buf_to_fw_frame_buf_pin(vb, set); - } -} - /* Start streaming for real. The buffer list must be available. */ -static int ipu6_isys_stream_start(struct ipu6_isys_video *av, - struct ipu6_isys_buffer_list *bl) +static int ipu6_isys_stream_start(struct ipu6_isys_video *av) { - struct ipu6_isys_stream *stream = av->stream; - struct device *dev = &stream->isys->adev->auxdev.dev; - struct ipu6_isys_buffer_list __bl; + struct ipu6_bus_device *adev = av->isys->adev; + const struct ipu6_fw_isys_ops *fw_ops = adev->auxdrv_data->fw_ops; + struct device *dev = &adev->auxdev.dev; + struct ipu6_isys_buffer_list bl; + struct isys_fw_msgs *msg; int ret; - guard(mutex)(&stream->isys->stream_mutex); - ret = ipu6_isys_video_set_streaming(av, 1, bl); + guard(mutex)(&av->isys->stream_mutex); + ret = ipu6_isys_video_set_streaming(av, 1); if (ret) - goto out_requeue; + return ret; - stream->streaming = 1; + if (!av->stream || !(BIT(av->stream->vc) & av->csi2->streaming_vc)) + return 0; + + struct ipu6_isys_stream *stream = av->stream; - bl = &__bl; + guard(mutex)(&stream->mutex); do { - struct ipu6_fw_isys_frame_buff_set_abi *buf = NULL; - struct isys_fw_msgs *msg; - u16 send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE; - - ret = buffer_list_get(stream, bl); + ret = ipu6_isys_buffer_list_get(stream, &bl); if (ret < 0) - break; + return 0; msg = ipu6_get_fw_msg_buf(stream); - if (!msg) - return -ENOMEM; - - buf = &msg->fw_msg.frame; - ipu6_isys_buf_to_fw_frame_buf(buf, stream, bl); - ipu6_fw_isys_dump_frame_buff_set(dev, buf, - stream->nr_output_pins); - ipu6_isys_buffer_list_queue(bl, IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, - 0); - ret = ipu6_fw_isys_complex_cmd(stream->isys, - stream->stream_handle, buf, - msg->dma_addr, sizeof(*buf), - send_type); - } while (!WARN_ON(ret)); + if (WARN_ON(!msg)) + goto out_requeue; + + fw_ops->prepare_buf_set(msg, stream, &bl); + fw_ops->dump_frame_buf_set(dev, msg, stream->nr_output_pins); + ipu6_isys_buffer_list_queue(&bl, + IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, 0); + ret = fw_ops->stream_capture(stream->isys, + stream->stream_handle, msg); + if (WARN_ON(ret)) + break; + } while (true); - return 0; + /* Error handling begins here. */ + ipu6_put_fw_msg_buf(stream->isys, msg); out_requeue: - if (bl && bl->nbufs) - ipu6_isys_buffer_list_queue(bl, + if (bl.nbufs) + ipu6_isys_buffer_list_queue(&bl, IPU6_ISYS_BUFFER_LIST_FL_INCOMING, VB2_BUF_STATE_QUEUED); flush_firmware_streamon_fail(stream); @@ -344,13 +292,15 @@ static void buf_queue(struct vb2_buffer *vb) { struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct ipu6_bus_device *adev = av->isys->adev; + const struct ipu6_fw_isys_ops *fw_ops = adev->auxdrv_data->fw_ops; struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb); struct ipu6_isys_video_buffer *ivb = vb2_buffer_to_ipu6_isys_video_buffer(vvb); struct ipu6_isys_buffer *ib = &ivb->ib; - struct device *dev = &av->isys->adev->auxdev.dev; - struct ipu6_fw_isys_frame_buff_set_abi *buf = NULL; + struct device *dev = &adev->auxdev.dev; struct ipu6_isys_stream *stream = av->stream; + struct ipu6_isys_csi2 *csi2; struct ipu6_isys_buffer_list bl; struct isys_fw_msgs *msg; unsigned long flags; @@ -374,7 +324,8 @@ static void buf_queue(struct vb2_buffer *vb) mutex_lock(&stream->mutex); - if (stream->nr_streaming != stream->nr_queues) { + csi2 = ipu6_isys_subdev_to_csi2(stream->asd); + if (!(BIT(stream->vc) & csi2->streaming_vc)) { dev_dbg(dev, "not streaming yet, adding to incoming\n"); goto out; } @@ -384,7 +335,7 @@ static void buf_queue(struct vb2_buffer *vb) * (above). Let's see whether all queues in the pipeline would * have a buffer. */ - ret = buffer_list_get(stream, &bl); + ret = ipu6_isys_buffer_list_get(stream, &bl); if (ret < 0) { dev_dbg(dev, "No buffers available\n"); goto out; @@ -396,9 +347,8 @@ static void buf_queue(struct vb2_buffer *vb) goto out; } - buf = &msg->fw_msg.frame; - ipu6_isys_buf_to_fw_frame_buf(buf, stream, &bl); - ipu6_fw_isys_dump_frame_buff_set(dev, buf, stream->nr_output_pins); + fw_ops->prepare_buf_set(msg, stream, &bl); + fw_ops->dump_frame_buf_set(dev, msg, stream->nr_output_pins); /* * We must queue the buffers in the buffer list to the @@ -408,9 +358,7 @@ static void buf_queue(struct vb2_buffer *vb) */ ipu6_isys_buffer_list_queue(&bl, IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, 0); - ret = ipu6_fw_isys_complex_cmd(stream->isys, stream->stream_handle, - buf, msg->dma_addr, sizeof(*buf), - IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE); + ret = fw_ops->stream_capture(stream->isys, stream->stream_handle, msg); if (ret < 0) dev_err(dev, "send stream capture failed\n"); @@ -525,7 +473,6 @@ static void return_buffers(struct ipu6_isys_queue *aq, static void ipu6_isys_stream_cleanup(struct ipu6_isys_video *av) { video_device_pipeline_stop(&av->vdev); - ipu6_isys_put_stream(av->stream); av->stream = NULL; } @@ -536,10 +483,8 @@ static int start_streaming(struct vb2_queue *q, unsigned int count) struct device *dev = &av->isys->adev->auxdev.dev; const struct ipu6_isys_pixelformat *pfmt = ipu6_isys_get_isys_format(ipu6_isys_get_format(av), 0); - struct ipu6_isys_buffer_list __bl, *bl = NULL; - struct ipu6_isys_stream *stream; struct media_pad *source_pad, *remote_pad; - int nr_queues, ret; + int ret; dev_dbg(dev, "stream: %s: width %u, height %u, css pixelformat %u\n", av->vdev.name, ipu6_isys_get_frame_width(av), @@ -559,11 +504,9 @@ static int start_streaming(struct vb2_queue *q, unsigned int count) goto out_return_buffers; } - ret = ipu6_isys_setup_video(av, remote_pad, source_pad, &nr_queues); - if (ret < 0) { - dev_dbg(dev, "failed to setup video\n"); + ret = video_device_pipeline_alloc_start(&av->vdev); + if (ret < 0) goto out_return_buffers; - } ret = ipu6_isys_link_fmt_validate(aq); if (ret) { @@ -573,55 +516,12 @@ static int start_streaming(struct vb2_queue *q, unsigned int count) goto out_pipeline_stop; } - ret = ipu6_isys_fw_open(av->isys); + ret = ipu6_isys_stream_start(av); if (ret) goto out_pipeline_stop; - stream = av->stream; - mutex_lock(&stream->mutex); - if (!stream->nr_streaming) { - ret = ipu6_isys_video_prepare_stream(av, source_pad->entity, - nr_queues); - if (ret) - goto out_fw_close; - } - - stream->nr_streaming++; - dev_dbg(dev, "queue %u of %u\n", stream->nr_streaming, - stream->nr_queues); - - list_add(&aq->node, &stream->queues); - ipu6_isys_configure_stream_watermark(av, source_pad->entity); - ipu6_isys_update_stream_watermark(av, true); - - if (stream->nr_streaming != stream->nr_queues) - goto out; - - bl = &__bl; - ret = buffer_list_get(stream, bl); - if (ret < 0) { - dev_warn(dev, "no buffer available, DRIVER BUG?\n"); - goto out; - } - - ret = ipu6_isys_stream_start(av, bl); - if (ret) - goto out_stream_start; - -out: - mutex_unlock(&stream->mutex); - return 0; -out_stream_start: - ipu6_isys_update_stream_watermark(av, false); - list_del(&aq->node); - stream->nr_streaming--; - -out_fw_close: - mutex_unlock(&stream->mutex); - ipu6_isys_fw_close(av->isys); - out_pipeline_stop: ipu6_isys_stream_cleanup(av); @@ -635,27 +535,14 @@ static void stop_streaming(struct vb2_queue *q) { struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(q); struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); - struct ipu6_isys_stream *stream = av->stream; - - mutex_lock(&stream->mutex); - - ipu6_isys_update_stream_watermark(av, false); mutex_lock(&av->isys->stream_mutex); - if (stream->nr_streaming == stream->nr_queues && stream->streaming) - ipu6_isys_video_set_streaming(av, 0, NULL); - list_del(&aq->node); + ipu6_isys_video_set_streaming(av, 0); mutex_unlock(&av->isys->stream_mutex); - stream->nr_streaming--; - stream->streaming = 0; - mutex_unlock(&stream->mutex); - ipu6_isys_stream_cleanup(av); return_buffers(aq, VB2_BUF_STATE_ERROR); - - ipu6_isys_fw_close(av->isys); } static unsigned int @@ -741,7 +628,7 @@ static void ipu6_isys_queue_buf_done(struct ipu6_isys_buffer *ib) } } -static void +void ipu6_stream_buf_ready(struct ipu6_isys_stream *stream, u8 pin_id, u32 pin_addr, u64 time, bool error_check) { diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h index dec1fed44dd2..0e0886f59150 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h @@ -60,11 +60,11 @@ struct ipu6_isys_buffer_list { void ipu6_isys_buffer_list_queue(struct ipu6_isys_buffer_list *bl, unsigned long op_flags, enum vb2_buffer_state state); -void -ipu6_isys_buf_to_fw_frame_buf(struct ipu6_fw_isys_frame_buff_set_abi *set, - struct ipu6_isys_stream *stream, +int ipu6_isys_buffer_list_get(struct ipu6_isys_stream *stream, struct ipu6_isys_buffer_list *bl); void ipu6_isys_queue_buf_ready(struct ipu6_isys_stream *stream, struct ipu6_fw_isys_resp_info_abi *info); int ipu6_isys_queue_init(struct ipu6_isys_queue *aq); +void ipu6_stream_buf_ready(struct ipu6_isys_stream *stream, u8 pin_id, + u32 pin_addr, u64 time, bool error_check); #endif /* IPU6_ISYS_QUEUE_H */ diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c index dbd6f76a066d..60070842ee68 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c @@ -160,6 +160,7 @@ u32 ipu6_isys_convert_bayer_order(u32 code, int x, int y) } int ipu6_isys_subdev_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { @@ -265,17 +266,13 @@ static int subdev_set_routing(struct v4l2_subdev *sd, return v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format); } -u32 ipu6_isys_get_src_stream_by_src_pad(struct v4l2_subdev *sd, u32 pad) +u32 __ipu6_isys_get_src_stream_by_src_pad(struct v4l2_subdev_state *state, + u32 pad) { - struct v4l2_subdev_state *state; struct v4l2_subdev_route *routes; unsigned int i; u32 source_stream = 0; - state = v4l2_subdev_lock_and_get_active_state(sd); - if (!state) - return 0; - routes = state->routing.routes; for (i = 0; i < state->routing.num_routes; i++) { if (routes[i].source_pad == pad) { @@ -284,6 +281,20 @@ u32 ipu6_isys_get_src_stream_by_src_pad(struct v4l2_subdev *sd, u32 pad) } } + return source_stream; +} + +u32 ipu6_isys_get_src_stream_by_src_pad(struct v4l2_subdev *sd, u32 pad) +{ + struct v4l2_subdev_state *state; + u32 source_stream = 0; + + state = v4l2_subdev_lock_and_get_active_state(sd); + if (!state) + return 0; + + source_stream = __ipu6_isys_get_src_stream_by_src_pad(state, pad); + v4l2_subdev_unlock_state(state); return source_stream; diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h index 35069099c364..b892d96992af 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h @@ -31,12 +31,15 @@ bool ipu6_isys_is_bayer_format(u32 code); u32 ipu6_isys_convert_bayer_order(u32 code, int x, int y); int ipu6_isys_subdev_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt); int ipu6_isys_subdev_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_mbus_code_enum *code); +u32 __ipu6_isys_get_src_stream_by_src_pad(struct v4l2_subdev_state *state, + u32 pad); u32 ipu6_isys_get_src_stream_by_src_pad(struct v4l2_subdev *sd, u32 pad); int ipu6_isys_subdev_set_routing(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c index 3ac48d2076da..129016e57446 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c @@ -432,185 +432,110 @@ unlock: return ret; } -static void get_stream_opened(struct ipu6_isys_video *av) -{ - unsigned long flags; - - spin_lock_irqsave(&av->isys->streams_lock, flags); - av->isys->stream_opened++; - spin_unlock_irqrestore(&av->isys->streams_lock, flags); -} +int ipu6_isys_fw_pins_prepare(struct ipu6_isys_stream *stream, + struct v4l2_mbus_frame_desc *desc, + int (*fw_pin_cfg)(struct ipu6_isys_video *av, + struct ipu6_isys_stream *stream, + struct media_pad *src_pad, + struct v4l2_mbus_frame_desc_entry *entry, + void *__cfg), void *stream_cfg) +{ + struct v4l2_subdev_state *csi2_state = + v4l2_subdev_get_locked_active_state(&stream->asd->sd); + struct device *dev = &stream->isys->adev->auxdev.dev; + struct ipu6_isys_queue *aq; -static void put_stream_opened(struct ipu6_isys_video *av) -{ - unsigned long flags; + list_for_each_entry(aq, &stream->queues, node) { + struct ipu6_isys_video *__av = ipu6_isys_queue_to_video(aq); + struct media_pad *remote_pad = + media_pad_remote_pad_first(&__av->pad); + u64 source_streams = 1; + unsigned int sink_stream = + __ffs(v4l2_subdev_state_xlate_streams(csi2_state, + remote_pad->index, + CSI2_PAD_SINK, + &source_streams)); + struct v4l2_mbus_frame_desc_entry *entry = NULL; + int ret; + + for (unsigned int i = 0; i < desc->num_entries; i++) { + if (desc->entry[i].stream == sink_stream) { + entry = &desc->entry[i]; + break; + } + } - spin_lock_irqsave(&av->isys->streams_lock, flags); - av->isys->stream_opened--; - spin_unlock_irqrestore(&av->isys->streams_lock, flags); -} + if (!entry) { + dev_err(dev, "cannot find frame desc entry for sink stream %u\n", + sink_stream); + return -EINVAL; + } -static int ipu6_isys_fw_pin_cfg(struct ipu6_isys_video *av, - struct ipu6_fw_isys_stream_cfg_data_abi *cfg) -{ - struct media_pad *src_pad = media_pad_remote_pad_first(&av->pad); - struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(src_pad->entity); - struct v4l2_subdev_state *state = v4l2_subdev_get_locked_active_state(sd); - struct ipu6_fw_isys_input_pin_info_abi *input_pin; - struct ipu6_fw_isys_output_pin_info_abi *output_pin; - struct ipu6_isys_stream *stream = av->stream; - struct ipu6_isys_queue *aq = &av->aq; - struct v4l2_mbus_framefmt fmt; - const struct ipu6_isys_pixelformat *pfmt = - ipu6_isys_get_isys_format(ipu6_isys_get_format(av), 0); - struct v4l2_rect v4l2_crop; - struct ipu6_isys *isys = av->isys; - int input_pins = cfg->nof_input_pins++; - int output_pins; - u32 src_stream; - - src_stream = ipu6_isys_get_src_stream_by_src_pad(sd, src_pad->index); - fmt = *v4l2_subdev_state_get_format(state, src_pad->index, src_stream); - v4l2_crop = *v4l2_subdev_state_get_crop(state, src_pad->index, src_stream); - - input_pin = &cfg->input_pins[input_pins]; - input_pin->input_res.width = fmt.width; - input_pin->input_res.height = fmt.height; - input_pin->dt = av->dt; - input_pin->bits_per_pix = pfmt->bpp_packed; - input_pin->mapped_dt = 0x40; /* invalid mipi data type */ - input_pin->mipi_decompression = 0; - input_pin->capture_mode = IPU6_FW_ISYS_CAPTURE_MODE_REGULAR; - input_pin->mipi_store_mode = pfmt->bpp == pfmt->bpp_packed ? - IPU6_FW_ISYS_MIPI_STORE_MODE_DISCARD_LONG_HEADER : - IPU6_FW_ISYS_MIPI_STORE_MODE_NORMAL; - input_pin->crop_first_and_last_lines = v4l2_crop.top & 1; - - output_pins = cfg->nof_output_pins++; - aq->fw_output = output_pins; - stream->output_pins_queue[output_pins] = aq; - - output_pin = &cfg->output_pins[output_pins]; - output_pin->input_pin_id = input_pins; - output_pin->output_res.width = ipu6_isys_get_frame_width(av); - output_pin->output_res.height = ipu6_isys_get_frame_height(av); - - output_pin->stride = ipu6_isys_get_bytes_per_line(av); - if (pfmt->bpp != pfmt->bpp_packed) - output_pin->pt = IPU6_FW_ISYS_PIN_TYPE_RAW_SOC; - else - output_pin->pt = IPU6_FW_ISYS_PIN_TYPE_MIPI; - output_pin->ft = pfmt->css_pixelformat; - output_pin->send_irq = 1; - memset(output_pin->ts_offsets, 0, sizeof(output_pin->ts_offsets)); - output_pin->s2m_pixel_soc_pixel_remapping = - S2M_PIXEL_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING; - output_pin->csi_be_soc_pixel_remapping = - CSI_BE_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING; - - output_pin->snoopable = true; - output_pin->error_handling_enable = false; - output_pin->sensor_type = isys->sensor_type++; - if (isys->sensor_type > isys->pdata->ipdata->sensor_type_end) - isys->sensor_type = isys->pdata->ipdata->sensor_type_start; + ret = fw_pin_cfg(__av, stream, remote_pad, entry, stream_cfg); + if (ret < 0) + return ret; + } return 0; } -static int start_stream_firmware(struct ipu6_isys_video *av, - struct ipu6_isys_buffer_list *bl) +int ipu6_isys_start_stream_firmware(struct ipu6_isys_stream *stream, + struct ipu6_isys_buffer_list *bl, + struct v4l2_mbus_frame_desc *desc) { - struct ipu6_fw_isys_stream_cfg_data_abi *stream_cfg; - struct ipu6_fw_isys_frame_buff_set_abi *buf = NULL; - struct ipu6_isys_stream *stream = av->stream; - struct device *dev = &av->isys->adev->auxdev.dev; + struct ipu6_bus_device *adev = stream->asd->isys->adev; + const struct ipu6_fw_isys_ops *fw_ops = adev->auxdrv_data->fw_ops; + struct device *dev = &adev->auxdev.dev; struct isys_fw_msgs *msg = NULL; - struct ipu6_isys_queue *aq; int ret, retout, tout; - u16 send_type; + bool capture = bl ? true : false; msg = ipu6_get_fw_msg_buf(stream); if (!msg) return -ENOMEM; - stream_cfg = &msg->fw_msg.stream; - stream_cfg->src = stream->stream_source; - stream_cfg->vc = stream->vc; - stream_cfg->isl_use = 0; - stream_cfg->sensor_type = IPU6_FW_ISYS_SENSOR_MODE_NORMAL; - - list_for_each_entry(aq, &stream->queues, node) { - struct ipu6_isys_video *__av = ipu6_isys_queue_to_video(aq); - - ret = ipu6_isys_fw_pin_cfg(__av, stream_cfg); - if (ret < 0) { - ipu6_put_fw_msg_buf(av->isys, (uintptr_t)stream_cfg); - return ret; - } + ret = fw_ops->prepare_stream_cfg(stream, desc, msg); + if (ret < 0) { + ipu6_put_fw_msg_buf(stream->isys, msg); + return ret; } - ipu6_fw_isys_dump_stream_cfg(dev, stream_cfg); - - stream->nr_output_pins = stream_cfg->nof_output_pins; - reinit_completion(&stream->stream_open_completion); - ret = ipu6_fw_isys_complex_cmd(av->isys, stream->stream_handle, - stream_cfg, msg->dma_addr, - sizeof(*stream_cfg), - IPU6_FW_ISYS_SEND_TYPE_STREAM_OPEN); + ret = fw_ops->stream_open(stream->isys, stream->stream_handle, msg); if (ret < 0) { dev_err(dev, "can't open stream (%d)\n", ret); - ipu6_put_fw_msg_buf(av->isys, (uintptr_t)stream_cfg); + ipu6_put_fw_msg_buf(stream->isys, msg); return ret; } - get_stream_opened(av); - tout = wait_for_completion_timeout(&stream->stream_open_completion, IPU6_FW_CALL_TIMEOUT_JIFFIES); - ipu6_put_fw_msg_buf(av->isys, (uintptr_t)stream_cfg); + ipu6_put_fw_msg_buf(stream->isys, msg); if (!tout) { dev_err(dev, "stream open time out\n"); - ret = -ETIMEDOUT; - goto out_put_stream_opened; + return -ETIMEDOUT; } if (stream->error) { dev_err(dev, "stream open error: %d\n", stream->error); - ret = -EIO; - goto out_put_stream_opened; + return -EIO; } dev_dbg(dev, "start stream: open complete\n"); - if (bl) { - msg = ipu6_get_fw_msg_buf(stream); - if (!msg) { - ret = -ENOMEM; - goto out_put_stream_opened; - } - buf = &msg->fw_msg.frame; - ipu6_isys_buf_to_fw_frame_buf(buf, stream, bl); - ipu6_isys_buffer_list_queue(bl, - IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, 0); + msg = ipu6_get_fw_msg_buf(stream); + if (!msg) { + return -ENOMEM; } - reinit_completion(&stream->stream_start_completion); + fw_ops->prepare_buf_set(msg, stream, bl); + ipu6_isys_buffer_list_queue(bl, IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, 0); - if (bl) { - send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_START_AND_CAPTURE; - ipu6_fw_isys_dump_frame_buff_set(dev, buf, - stream_cfg->nof_output_pins); - ret = ipu6_fw_isys_complex_cmd(av->isys, stream->stream_handle, - buf, msg->dma_addr, - sizeof(*buf), send_type); - } else { - send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_START; - ret = ipu6_fw_isys_simple_cmd(av->isys, stream->stream_handle, - send_type); - } + reinit_completion(&stream->stream_start_completion); + ret = fw_ops->stream_start(stream->isys, stream->stream_handle, msg, + capture); if (ret < 0) { dev_err(dev, "can't start streaming (%d)\n", ret); goto out_stream_close; @@ -635,12 +560,10 @@ static int start_stream_firmware(struct ipu6_isys_video *av, out_stream_close: reinit_completion(&stream->stream_close_completion); - retout = ipu6_fw_isys_simple_cmd(av->isys, - stream->stream_handle, - IPU6_FW_ISYS_SEND_TYPE_STREAM_CLOSE); + retout = fw_ops->stream_close(stream->isys, stream->stream_handle); if (retout < 0) { dev_dbg(dev, "can't close stream (%d)\n", retout); - goto out_put_stream_opened; + return retout; } tout = wait_for_completion_timeout(&stream->stream_close_completion, @@ -652,23 +575,19 @@ out_stream_close: else dev_dbg(dev, "stream close complete\n"); -out_put_stream_opened: - put_stream_opened(av); - return ret; } -static void stop_streaming_firmware(struct ipu6_isys_video *av) +void ipu6_isys_stop_stream_firmware(struct ipu6_isys_stream *stream) { - struct device *dev = &av->isys->adev->auxdev.dev; - struct ipu6_isys_stream *stream = av->stream; + struct ipu6_bus_device *adev = stream->asd->isys->adev; + const struct ipu6_fw_isys_ops *fw_ops = adev->auxdrv_data->fw_ops; + struct device *dev = &adev->auxdev.dev; int ret, tout; reinit_completion(&stream->stream_stop_completion); - ret = ipu6_fw_isys_simple_cmd(av->isys, stream->stream_handle, - IPU6_FW_ISYS_SEND_TYPE_STREAM_FLUSH); - + ret = fw_ops->stream_flush(stream->isys, stream->stream_handle); if (ret < 0) { dev_err(dev, "can't stop stream (%d)\n", ret); return; @@ -684,16 +603,17 @@ static void stop_streaming_firmware(struct ipu6_isys_video *av) dev_dbg(dev, "stop stream: complete\n"); } -static void close_streaming_firmware(struct ipu6_isys_video *av) +void ipu6_isys_close_stream_firmware(struct ipu6_isys_stream *stream) { - struct ipu6_isys_stream *stream = av->stream; - struct device *dev = &av->isys->adev->auxdev.dev; + struct ipu6_bus_device *adev = stream->asd->isys->adev; + const struct ipu6_fw_isys_ops *fw_ops = adev->auxdrv_data->fw_ops; + struct device *dev = &adev->auxdev.dev; + struct ipu6_isys_csi2 *csi2 = ipu6_isys_subdev_to_csi2(stream->asd); int ret, tout; reinit_completion(&stream->stream_close_completion); - ret = ipu6_fw_isys_simple_cmd(av->isys, stream->stream_handle, - IPU6_FW_ISYS_SEND_TYPE_STREAM_CLOSE); + ret = fw_ops->stream_close(stream->isys, stream->stream_handle); if (ret < 0) { dev_err(dev, "can't close stream (%d)\n", ret); return; @@ -708,349 +628,153 @@ static void close_streaming_firmware(struct ipu6_isys_video *av) else dev_dbg(dev, "close stream: complete\n"); - put_stream_opened(av); + scoped_guard(spinlock_irqsave, &stream->isys->streams_lock) { + stream->isys->streams_by_handle[stream->stream_handle] = NULL; + csi2->streams_by_vc[stream->vc] = NULL; + } } -int ipu6_isys_video_prepare_stream(struct ipu6_isys_video *av, - struct media_entity *source_entity, - int nr_queues) +struct ipu6_isys_stream * +ipu6_isys_find_stream_firmware(struct ipu6_isys_csi2 *csi2, u8 vc) { - struct ipu6_isys_stream *stream = av->stream; - struct ipu6_isys_csi2 *csi2; + struct ipu6_isys_stream *stream; - if (WARN_ON(stream->nr_streaming)) - return -EINVAL; + list_for_each_entry(stream, &csi2->streams, csi2_entry) + if (stream->vc == vc) + return stream; - stream->nr_queues = nr_queues; - atomic_set(&stream->sequence, 0); - - stream->seq_index = 0; - memset(stream->seq, 0, sizeof(stream->seq)); - - if (WARN_ON(!list_empty(&stream->queues))) - return -EINVAL; - - stream->stream_source = stream->asd->source; - csi2 = ipu6_isys_subdev_to_csi2(stream->asd); - csi2->receiver_errors = 0; - - dev_dbg(&av->isys->adev->auxdev.dev, - "prepare stream: external entity %s\n", - source_entity->name); - - return 0; + return NULL; } -void ipu6_isys_configure_stream_watermark(struct ipu6_isys_video *av, - struct media_entity *source) +void ipu6_isys_free_stream_firmware(struct ipu6_isys_stream *stream) { - struct ipu6_isys *isys = av->isys; - struct ipu6_isys_csi2 *csi2 = NULL; - struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark; - struct device *dev = &isys->adev->auxdev.dev; - struct v4l2_mbus_framefmt format; - struct v4l2_subdev *esd; - struct v4l2_control hb = { .id = V4L2_CID_HBLANK, .value = 0 }; - unsigned int bpp, lanes; - s64 link_freq = 0; - u64 pixel_rate = 0; - int ret; - - esd = media_entity_to_v4l2_subdev(source); - - av->watermark.width = ipu6_isys_get_frame_width(av); - av->watermark.height = ipu6_isys_get_frame_height(av); - av->watermark.sram_gran_shift = isys->pdata->ipdata->sram_gran_shift; - av->watermark.sram_gran_size = isys->pdata->ipdata->sram_gran_size; - - ret = v4l2_g_ctrl(esd->ctrl_handler, &hb); - if (!ret && hb.value >= 0) - av->watermark.hblank = hb.value; - else - av->watermark.hblank = 0; - - csi2 = ipu6_isys_subdev_to_csi2(av->stream->asd); - link_freq = ipu6_isys_csi2_get_link_freq(csi2); - if (link_freq > 0) { - struct v4l2_subdev_state *state = - v4l2_subdev_lock_and_get_active_state(&csi2->asd.sd); - - lanes = csi2->nlanes; - format = *v4l2_subdev_state_get_format(state, 0, - av->source_stream); - bpp = ipu6_isys_mbus_code_to_bpp(format.code); - pixel_rate = mul_u64_u32_div(link_freq, lanes * 2, bpp); + struct ipu6_isys_csi2 *csi2 = ipu6_isys_subdev_to_csi2(stream->asd); + struct ipu6_isys_queue *aq, *aq_safe; - v4l2_subdev_unlock_state(state); - } - - av->watermark.pixel_rate = pixel_rate; + list_for_each_entry_safe(aq, aq_safe, &stream->queues, node) { + struct ipu6_isys_video *av = + container_of_const(aq, struct ipu6_isys_video, aq); - if (!pixel_rate) { - mutex_lock(&iwake_watermark->mutex); - iwake_watermark->force_iwake_disable = true; - mutex_unlock(&iwake_watermark->mutex); - dev_warn(dev, "unexpected pixel_rate from %s, disable iwake.\n", - source->name); + list_del(&aq->node); + av->stream = NULL; } -} -static void calculate_stream_datarate(struct ipu6_isys_video *av) -{ - struct video_stream_watermark *watermark = &av->watermark; - const struct ipu6_isys_pixelformat *pfmt = - ipu6_isys_get_isys_format(ipu6_isys_get_format(av), 0); - u32 pages_per_line, pb_bytes_per_line, pixels_per_line, bytes_per_line; - u64 line_time_ns, stream_data_rate; - u16 shift, size; - - shift = watermark->sram_gran_shift; - size = watermark->sram_gran_size; - - pixels_per_line = watermark->width + watermark->hblank; - line_time_ns = div_u64(pixels_per_line * NSEC_PER_SEC, - watermark->pixel_rate); - bytes_per_line = watermark->width * pfmt->bpp / 8; - pages_per_line = DIV_ROUND_UP(bytes_per_line, size); - pb_bytes_per_line = pages_per_line << shift; - stream_data_rate = div64_u64(pb_bytes_per_line * 1000, line_time_ns); - - watermark->stream_data_rate = stream_data_rate; + list_del(&stream->csi2_entry); + ida_free(&csi2->isys->streams, stream->stream_handle); + kfree(stream); } -void ipu6_isys_update_stream_watermark(struct ipu6_isys_video *av, bool state) -{ - struct isys_iwake_watermark *iwake_watermark = - &av->isys->iwake_watermark; - - if (!av->watermark.pixel_rate) - return; +struct ipu6_isys_stream * +ipu6_isys_alloc_stream_firmware(struct ipu6_isys_csi2 *csi2, + struct v4l2_subdev_state *csi2_state, + struct v4l2_mbus_frame_desc *desc, + u8 vc) +{ + struct device *dev = &csi2->isys->adev->auxdev.dev; + struct ipu6_isys_stream *stream; + struct v4l2_subdev_route *route; + int ret; - if (state) { - calculate_stream_datarate(av); - mutex_lock(&iwake_watermark->mutex); - list_add(&av->watermark.stream_node, - &iwake_watermark->video_list); - mutex_unlock(&iwake_watermark->mutex); - } else { - av->watermark.stream_data_rate = 0; - mutex_lock(&iwake_watermark->mutex); - list_del(&av->watermark.stream_node); - mutex_unlock(&iwake_watermark->mutex); - } + stream = kzalloc_obj(*stream); + if (!stream) + return ERR_PTR(-ENOMEM); - update_watermark_setting(av->isys); -} + ret = ida_alloc_max(&csi2->isys->streams, IPU6_ISYS_MAX_STREAMS - 1, + GFP_KERNEL); + if (ret < 0) + goto err_free_stream; -void ipu6_isys_put_stream(struct ipu6_isys_stream *stream) -{ - struct device *dev; - unsigned int i; - unsigned long flags; + stream->stream_handle = ret; + mutex_init(&stream->mutex); + init_completion(&stream->stream_open_completion); + init_completion(&stream->stream_close_completion); + init_completion(&stream->stream_start_completion); + init_completion(&stream->stream_stop_completion); + INIT_LIST_HEAD(&stream->queues); + stream->isys = csi2->asd.isys; + stream->asd = &csi2->asd; + stream->vc = vc; - if (!stream) { - pr_err("ipu6-isys: no available stream\n"); - return; + scoped_guard(spinlock_irqsave, &stream->isys->streams_lock) { + stream->isys->streams_by_handle[stream->stream_handle] = stream; + csi2->streams_by_vc[stream->vc] = stream; } - dev = &stream->isys->adev->auxdev.dev; - - spin_lock_irqsave(&stream->isys->streams_lock, flags); - for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) { - if (&stream->isys->streams[i] == stream) { - if (stream->isys->streams_ref_count[i] > 0) - stream->isys->streams_ref_count[i]--; - else - dev_warn(dev, "invalid stream %d\n", i); - - break; - } - } - spin_unlock_irqrestore(&stream->isys->streams_lock, flags); -} + list_add(&stream->csi2_entry, &csi2->streams); -static struct ipu6_isys_stream * -ipu6_isys_get_stream(struct ipu6_isys_video *av, struct ipu6_isys_subdev *asd) -{ - struct ipu6_isys_stream *stream = NULL; - struct ipu6_isys *isys = av->isys; - unsigned long flags; - unsigned int i; - u8 vc = av->vc; + for_each_active_route(&csi2_state->routing, route) { + struct media_pad *vdev_pad = + media_pad_remote_pad_first(&csi2->asd.pad[route->source_pad]); + struct v4l2_mbus_frame_desc_entry *entry = NULL; - if (!isys) - return NULL; + for (unsigned int i = 0; i < desc->num_entries; i++) { + if (desc->entry[i].stream != route->sink_stream) + continue; - spin_lock_irqsave(&isys->streams_lock, flags); - for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) { - if (isys->streams_ref_count[i] && isys->streams[i].vc == vc && - isys->streams[i].asd == asd) { - isys->streams_ref_count[i]++; - stream = &isys->streams[i]; + entry = &desc->entry[i]; break; } - } - if (!stream) { - for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) { - if (!isys->streams_ref_count[i]) { - isys->streams_ref_count[i]++; - stream = &isys->streams[i]; - stream->vc = vc; - stream->asd = asd; - break; - } + if (!entry) { + dev_dbg(dev, "cannot find stream %u in frame desc\n", + route->sink_stream); + ret = -EINVAL; + goto err_ida_free; } - } - spin_unlock_irqrestore(&isys->streams_lock, flags); - - return stream; -} -struct ipu6_isys_stream * -ipu6_isys_query_stream_by_handle(struct ipu6_isys *isys, u8 stream_handle) -{ - unsigned long flags; - struct ipu6_isys_stream *stream = NULL; - - if (!isys) - return NULL; - - if (stream_handle >= IPU6_ISYS_MAX_STREAMS) { - dev_err(&isys->adev->auxdev.dev, - "stream_handle %d is invalid\n", stream_handle); - return NULL; - } - - spin_lock_irqsave(&isys->streams_lock, flags); - if (isys->streams_ref_count[stream_handle] > 0) { - isys->streams_ref_count[stream_handle]++; - stream = &isys->streams[stream_handle]; - } - spin_unlock_irqrestore(&isys->streams_lock, flags); - - return stream; -} - -struct ipu6_isys_stream * -ipu6_isys_query_stream_by_source(struct ipu6_isys *isys, int source, u8 vc) -{ - struct ipu6_isys_stream *stream = NULL; - unsigned long flags; - unsigned int i; - - if (!isys) - return NULL; + if (entry->bus.csi2.vc != vc) + continue; - if (source < 0) { - dev_err(&isys->adev->auxdev.dev, - "query stream with invalid port number\n"); - return NULL; - } + struct ipu6_isys_video *av = + container_of_const(vdev_pad, struct ipu6_isys_video, + pad); - spin_lock_irqsave(&isys->streams_lock, flags); - for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) { - if (!isys->streams_ref_count[i]) - continue; + list_add(&av->aq.node, &stream->queues); - if (isys->streams[i].stream_source == source && - isys->streams[i].vc == vc) { - stream = &isys->streams[i]; - isys->streams_ref_count[i]++; - break; - } + stream->nr_output_pins++; + av->stream = stream; } - spin_unlock_irqrestore(&isys->streams_lock, flags); return stream; -} - -static u64 get_stream_mask_by_pipeline(struct ipu6_isys_video *__av) -{ - struct media_pipeline *pipeline = - media_entity_pipeline(&__av->vdev.entity); - unsigned int i; - u64 stream_mask = 0; - for (i = 0; i < NR_OF_CSI2_SRC_PADS; i++) { - struct ipu6_isys_video *av = &__av->csi2->av[i]; +err_ida_free: + list_del(&stream->csi2_entry); + ida_free(&csi2->isys->streams, stream->stream_handle); - if (pipeline == media_entity_pipeline(&av->vdev.entity)) - stream_mask |= BIT_ULL(av->source_stream); - } +err_free_stream: + kfree(stream); - return stream_mask; + return ERR_PTR(ret); } -int ipu6_isys_video_set_streaming(struct ipu6_isys_video *av, int state, - struct ipu6_isys_buffer_list *bl) +int ipu6_isys_video_set_streaming(struct ipu6_isys_video *av, int state) { - struct v4l2_subdev_krouting *routing; - struct ipu6_isys_stream *stream = av->stream; - struct v4l2_subdev_state *subdev_state; struct device *dev = &av->isys->adev->auxdev.dev; struct v4l2_subdev *sd; struct media_pad *r_pad; - u32 sink_pad, sink_stream; - u64 r_stream; - u64 stream_mask = 0; int ret = 0; - dev_dbg(dev, "set stream: %d\n", state); - - sd = &stream->asd->sd; + sd = &av->csi2->asd.sd; r_pad = media_pad_remote_pad_first(&av->pad); - r_stream = ipu6_isys_get_src_stream_by_src_pad(sd, r_pad->index); - - subdev_state = v4l2_subdev_lock_and_get_active_state(sd); - routing = &subdev_state->routing; - ret = v4l2_subdev_routing_find_opposite_end(routing, r_pad->index, - r_stream, &sink_pad, - &sink_stream); - v4l2_subdev_unlock_state(subdev_state); - if (ret) - return ret; - stream_mask = get_stream_mask_by_pipeline(av); if (!state) { - stop_streaming_firmware(av); - /* stop sub-device which connects with video */ - dev_dbg(dev, "stream off entity %s pad:%d mask:0x%llx\n", - sd->name, r_pad->index, stream_mask); - ret = v4l2_subdev_disable_streams(sd, r_pad->index, - stream_mask); + dev_dbg(dev, "stream off %s pad:%d\n", sd->name, r_pad->index); + ret = v4l2_subdev_disable_streams(sd, r_pad->index, 1); if (ret) dev_err(dev, "stream off %s failed with %d\n", sd->name, ret); - - close_streaming_firmware(av); } else { - ret = start_stream_firmware(av, bl); - if (ret) { - dev_err(dev, "start stream of firmware failed\n"); - return ret; - } - /* start sub-device which connects with video */ - dev_dbg(dev, "stream on %s pad %d mask 0x%llx\n", sd->name, - r_pad->index, stream_mask); - ret = v4l2_subdev_enable_streams(sd, r_pad->index, stream_mask); - if (ret) { + dev_dbg(dev, "stream on %s pad %d\n", sd->name, r_pad->index); + ret = v4l2_subdev_enable_streams(sd, r_pad->index, 1); + if (ret) dev_err(dev, "stream on %s failed with %d\n", sd->name, ret); - goto out_media_entity_stop_streaming_firmware; - } } av->streaming = state; - return 0; - -out_media_entity_stop_streaming_firmware: - stop_streaming_firmware(av); - close_streaming_firmware(av); - return ret; } @@ -1089,148 +813,6 @@ static const struct v4l2_file_operations isys_fops = { .release = vb2_fop_release, }; -int ipu6_isys_fw_open(struct ipu6_isys *isys) -{ - struct ipu6_bus_device *adev = isys->adev; - const struct ipu6_isys_internal_pdata *ipdata = isys->pdata->ipdata; - int ret; - - ret = pm_runtime_resume_and_get(&adev->auxdev.dev); - if (ret < 0) - return ret; - - mutex_lock(&isys->mutex); - - if (isys->ref_count++) - goto unlock; - - ipu6_configure_spc(adev->isp, &ipdata->hw_variant, - IPU6_CPD_PKG_DIR_ISYS_SERVER_IDX, isys->pdata->base, - adev->pkg_dir, adev->pkg_dir_dma_addr); - - /* - * Buffers could have been left to wrong queue at last closure. - * Move them now back to empty buffer queue. - */ - ipu6_cleanup_fw_msg_bufs(isys); - - if (isys->fwcom) { - /* - * Something went wrong in previous shutdown. As we are now - * restarting isys we can safely delete old context. - */ - dev_warn(&adev->auxdev.dev, "clearing old context\n"); - ipu6_fw_isys_cleanup(isys); - } - - ret = ipu6_fw_isys_init(isys, ipdata->num_parallel_streams); - if (ret < 0) - goto out; - -unlock: - mutex_unlock(&isys->mutex); - - return 0; - -out: - isys->ref_count--; - mutex_unlock(&isys->mutex); - pm_runtime_put(&adev->auxdev.dev); - - return ret; -} - -void ipu6_isys_fw_close(struct ipu6_isys *isys) -{ - mutex_lock(&isys->mutex); - - isys->ref_count--; - if (!isys->ref_count) { - ipu6_fw_isys_close(isys); - if (isys->fwcom) { - isys->need_reset = true; - dev_warn(&isys->adev->auxdev.dev, - "failed to close fw isys\n"); - } - } - - mutex_unlock(&isys->mutex); - - if (isys->need_reset) - pm_runtime_put_sync(&isys->adev->auxdev.dev); - else - pm_runtime_put(&isys->adev->auxdev.dev); -} - -int ipu6_isys_setup_video(struct ipu6_isys_video *av, - struct media_pad *remote_pad, - struct media_pad *source_pad, int *nr_queues) -{ - const struct ipu6_isys_pixelformat *pfmt = - ipu6_isys_get_isys_format(ipu6_isys_get_format(av), 0); - struct device *dev = &av->isys->adev->auxdev.dev; - struct v4l2_mbus_frame_desc_entry entry; - struct v4l2_subdev_route *route = NULL; - struct v4l2_subdev_route *r; - struct v4l2_subdev_state *state; - struct v4l2_subdev *remote_sd = - media_entity_to_v4l2_subdev(remote_pad->entity); - struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(remote_sd); - int ret = -EINVAL; - - *nr_queues = 0; - - /* Find the root */ - state = v4l2_subdev_lock_and_get_active_state(remote_sd); - for_each_active_route(&state->routing, r) { - (*nr_queues)++; - - if (r->source_pad == remote_pad->index) - route = r; - } - - if (!route) { - v4l2_subdev_unlock_state(state); - dev_dbg(dev, "Failed to find route\n"); - return -ENODEV; - } - av->source_stream = route->sink_stream; - v4l2_subdev_unlock_state(state); - - ret = ipu6_isys_csi2_get_remote_desc(av->source_stream, - to_ipu6_isys_csi2(asd), - source_pad->entity, &entry); - if (ret == -ENOIOCTLCMD) { - av->vc = 0; - av->dt = ipu6_isys_mbus_code_to_mipi(pfmt->code); - } else if (!ret) { - dev_dbg(dev, "Framedesc: stream %u, len %u, vc %u, dt %#x\n", - entry.stream, entry.length, entry.bus.csi2.vc, - entry.bus.csi2.dt); - - av->vc = entry.bus.csi2.vc; - av->dt = entry.bus.csi2.dt; - } else { - dev_err(dev, "failed to get remote frame desc\n"); - return ret; - } - - ret = video_device_pipeline_alloc_start(&av->vdev); - if (ret < 0) { - dev_dbg(dev, "media pipeline start failed\n"); - return ret; - } - - av->stream = ipu6_isys_get_stream(av, asd); - if (!av->stream) { - video_device_pipeline_stop(&av->vdev); - dev_err(dev, "no available stream for firmware\n"); - return -EINVAL; - } - - return 0; -} - /* * Do everything that's needed to initialise things related to video * buffer queue, video node, and the related media entity. The caller @@ -1262,7 +844,7 @@ int ipu6_isys_video_init(struct ipu6_isys_video *av) ret = ipu6_isys_queue_init(&av->aq); if (ret) - goto out_free_watermark; + goto out_mutex_destroy; av->pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; ret = media_entity_pads_init(&av->vdev.entity, 1, &av->pad); @@ -1299,7 +881,7 @@ out_media_entity_cleanup: out_vb2_queue_release: vb2_queue_release(&av->aq.vbq); -out_free_watermark: +out_mutex_destroy: mutex_destroy(&av->mutex); return ret; diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.h b/drivers/media/pci/intel/ipu6/ipu6-isys-video.h index 2ff53315d7b9..2821b9b1b943 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-video.h +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.h @@ -22,6 +22,7 @@ struct file; struct ipu6_isys; struct ipu6_isys_csi2; struct ipu6_isys_subdev; +struct v4l2_mbus_frame_desc; struct ipu6_isys_pixelformat { u32 pixelformat; @@ -44,17 +45,15 @@ struct sequence_info { struct ipu6_isys_stream { struct mutex mutex; atomic_t sequence; + atomic_t buf_id; unsigned int seq_index; struct sequence_info seq[IPU6_ISYS_MAX_PARALLEL_SOF]; - int stream_source; int stream_handle; unsigned int nr_output_pins; struct ipu6_isys_subdev *asd; - - int nr_queues; /* Number of capture queues */ - int nr_streaming; - int streaming; /* Has streaming been really started? */ struct list_head queues; + struct list_head csi2_entry; + struct completion stream_open_completion; struct completion stream_close_completion; struct completion stream_start_completion; @@ -66,20 +65,9 @@ struct ipu6_isys_stream { u8 vc; }; -struct video_stream_watermark { - u32 width; - u32 height; - u32 hblank; - u32 frame_rate; - u64 pixel_rate; - u64 stream_data_rate; - u16 sram_gran_shift; - u16 sram_gran_size; - struct list_head stream_node; -}; - struct ipu6_isys_video { struct ipu6_isys_queue aq; + struct list_head csi2_entry; /* Serialise access to other fields in the struct. */ struct mutex mutex; struct media_pad pad; @@ -90,10 +78,7 @@ struct ipu6_isys_video { struct ipu6_isys_csi2 *csi2; struct ipu6_isys_stream *stream; unsigned int streaming; - struct video_stream_watermark watermark; u32 source_stream; - u8 vc; - u8 dt; }; #define ipu6_isys_queue_to_video(__aq) \ @@ -104,27 +89,34 @@ extern const struct ipu6_isys_pixelformat ipu6_isys_pfmts_packed[]; const struct ipu6_isys_pixelformat * ipu6_isys_get_isys_format(u32 pixelformat, u32 code); -int ipu6_isys_video_prepare_stream(struct ipu6_isys_video *av, - struct media_entity *source_entity, - int nr_queues); -int ipu6_isys_video_set_streaming(struct ipu6_isys_video *av, int state, - struct ipu6_isys_buffer_list *bl); +int ipu6_isys_fw_pins_prepare(struct ipu6_isys_stream *stream, + struct v4l2_mbus_frame_desc *desc, + int (*fw_pin_cfg)(struct ipu6_isys_video *av, + struct ipu6_isys_stream *stream, + struct media_pad *src_pad, + struct v4l2_mbus_frame_desc_entry *entry, + void *__cfg), void *stream_cfg); +int ipu6_isys_start_stream_firmware(struct ipu6_isys_stream *stream, + struct ipu6_isys_buffer_list *bl, + struct v4l2_mbus_frame_desc *desc); +void ipu6_isys_stop_stream_firmware(struct ipu6_isys_stream *stream); +void ipu6_isys_close_stream_firmware(struct ipu6_isys_stream *stream); +struct ipu6_isys_stream * +ipu6_isys_find_stream_firmware(struct ipu6_isys_csi2 *csi2, u8 vc); +void ipu6_isys_free_stream_firmware(struct ipu6_isys_stream *stream); +struct ipu6_isys_stream * +ipu6_isys_alloc_stream_firmware(struct ipu6_isys_csi2 *csi2, + struct v4l2_subdev_state *state, + struct v4l2_mbus_frame_desc *desc, + u8 vc); +int ipu6_isys_video_set_streaming(struct ipu6_isys_video *av, int state); int ipu6_isys_fw_open(struct ipu6_isys *isys); void ipu6_isys_fw_close(struct ipu6_isys *isys); int ipu6_isys_setup_video(struct ipu6_isys_video *av, struct media_pad *remote_pad, - struct media_pad *source_pad, int *nr_queues); + struct media_pad *source_pad); int ipu6_isys_video_init(struct ipu6_isys_video *av); void ipu6_isys_video_cleanup(struct ipu6_isys_video *av); -void ipu6_isys_put_stream(struct ipu6_isys_stream *stream); -struct ipu6_isys_stream * -ipu6_isys_query_stream_by_handle(struct ipu6_isys *isys, u8 stream_handle); -struct ipu6_isys_stream * -ipu6_isys_query_stream_by_source(struct ipu6_isys *isys, int source, u8 vc); - -void ipu6_isys_configure_stream_watermark(struct ipu6_isys_video *av, - struct media_entity *source); -void ipu6_isys_update_stream_watermark(struct ipu6_isys_video *av, bool state); u32 ipu6_isys_get_format(struct ipu6_isys_video *av); u32 ipu6_isys_get_data_size(struct ipu6_isys_video *av); diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.c b/drivers/media/pci/intel/ipu6/ipu6-isys.c index 24db2763de54..60f5f9ea2910 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.c @@ -41,6 +41,9 @@ #include "ipu6-platform-buttress-regs.h" #include "ipu6-platform-isys-csi2-reg.h" #include "ipu6-platform-regs.h" +#include "ipu7-isys-csi-phy.h" +#include "ipu7-isys-csi2-regs.h" +#include "ipu7-platform-regs.h" #define IPU6_BUTTRESS_FABIC_CONTROL 0x68 #define GDA_ENABLE_IWAKE_INDEX 2 @@ -98,9 +101,28 @@ enum ltr_did_type { LTR_TYPE_MAX }; -#define ISYS_PM_QOS_VALUE 300 +struct ltr_did { + union { + u32 value; + struct { + u8 val0; + u8 val1; + u8 val2; + u8 val3; + } bits; + } lut_ltr; + union { + u32 value; + struct { + u8 th0; + u8 th1; + u8 th2; + u8 th3; + } bits; + } lut_fill_time; +}; -static int isys_isr_one(struct ipu6_bus_device *adev); +#define ISYS_PM_QOS_VALUE 300 static int isys_complete_ext_device_registration(struct ipu6_isys *isys, @@ -132,6 +154,9 @@ isys_complete_ext_device_registration(struct ipu6_isys *isys, } isys->csi2[csi2->port].nlanes = csi2->nlanes; + isys->csi2[csi2->port].phy_mode = + csi2->bus_type == V4L2_MBUS_CSI2_DPHY ? + PHY_MODE_DPHY : PHY_MODE_CPHY; return 0; @@ -141,23 +166,6 @@ unregister_subdev: return ret; } -static void isys_stream_init(struct ipu6_isys *isys) -{ - u32 i; - - for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) { - mutex_init(&isys->streams[i].mutex); - init_completion(&isys->streams[i].stream_open_completion); - init_completion(&isys->streams[i].stream_close_completion); - init_completion(&isys->streams[i].stream_start_completion); - init_completion(&isys->streams[i].stream_stop_completion); - INIT_LIST_HEAD(&isys->streams[i].queues); - isys->streams[i].isys = isys; - isys->streams[i].stream_handle = i; - isys->streams[i].vc = INVALID_VC_ID; - } -} - static void isys_csi2_unregister_subdevices(struct ipu6_isys *isys) { const struct ipu6_isys_internal_csi2_pdata *csi2 = @@ -176,13 +184,23 @@ static int isys_csi2_register_subdevices(struct ipu6_isys *isys) int ret; for (i = 0; i < csi2_pdata->nports; i++) { - ret = ipu6_isys_csi2_init(&isys->csi2[i], isys, - isys->pdata->base + - CSI_REG_PORT_BASE(i), i); + void __iomem *base = isys->pdata->base; + + if (IS_IPU7(isys->adev->isp)) { + u32 mask = IS_IPU7_MTL(isys->adev->isp) ? + IPU7_CSI_LEGACY_IRQ_MASK(i) : + IPU7P5_CSI_LEGACY_IRQ_MASK(i); + + isys->isr_csi2_bits |= mask; + isys->csi2[i].legacy_irq_mask = mask; + } else { + base += CSI_REG_PORT_BASE(i); + isys->isr_csi2_bits |= IPU6_ISYS_UNISPART_IRQ_CSI2(i); + } + + ret = ipu6_isys_csi2_init(&isys->csi2[i], isys, base, i); if (ret) goto fail; - - isys->isr_csi2_bits |= IPU6_ISYS_UNISPART_IRQ_CSI2(i); } return 0; @@ -269,7 +287,36 @@ fail: return ret; } -static void isys_setup_hw(struct ipu6_isys *isys) +static void ipu7_isys_setup_hw(struct ipu6_isys *isys) +{ + u32 offset, mask; + void __iomem *base = isys->pdata->base; + + offset = IPU7_IS_IO_GPREGS_BASE; + + writel(0x0, base + offset + IPU7_CLK_EN_TXCLKESC); + /* Update if ISYS freq updated (0: 400/1, 1:400/2, 63:400/64) */ + writel(0x0, base + offset + IPU7_CLK_DIV_FACTOR_IS_CLK); + /* correct the initial printf configuration */ + writel(0x200, base + IPU7_IS_UC_CTRL_BASE + IPU7_REG_PRINTF_AXI_CNTL); + + offset = IPU7_IS_UC_CTRL_BASE; + mask = IPU7_IS_UC_TO_SW_IRQ_MASK; + + writel(mask, base + offset + IPU7_TO_SW_IRQ_CNTL_CLEAR); + writel(mask, base + offset + IPU7_TO_SW_IRQ_CNTL_MASK_N); + writel(mask, base + offset + IPU7_TO_SW_IRQ_CNTL_ENABLE); + + offset = IPU7_IS_IO_CSI2_LEGACY_IRQ_CTRL_BASE; + mask = IPU7_CSI_RX_LEGACY_IRQ_MASK; + + writel(mask, base + offset + IPU7_IRQ_CTL_EDGE); + writel(mask, base + offset + IPU7_IRQ_CTL_CLEAR); + writel(mask, base + offset + IPU7_IRQ_CTL_MASK); + writel(mask, base + offset + IPU7_IRQ_CTL_ENABLE); +} + +static void ipu6_isys_setup_hw(struct ipu6_isys *isys) { void __iomem *base = isys->pdata->base; const u8 *thd = isys->pdata->ipdata->hw_variant.cdc_fifo_threshold; @@ -304,118 +351,6 @@ static void isys_setup_hw(struct ipu6_isys *isys) writel(thd[i], base + IPU6_REG_ISYS_CDC_THRESHOLD(i)); } -static void ipu6_isys_csi2_isr(struct ipu6_isys_csi2 *csi2) -{ - struct ipu6_isys_stream *stream; - unsigned int i; - u32 status; - int source; - - ipu6_isys_register_errors(csi2); - - status = readl(csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + - CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET); - - writel(status, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + - CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); - - source = csi2->asd.source; - for (i = 0; i < NR_OF_CSI2_VC; i++) { - if (status & IPU_CSI_RX_IRQ_FS_VC(i)) { - stream = ipu6_isys_query_stream_by_source(csi2->isys, - source, i); - if (stream) { - ipu6_isys_csi2_sof_event_by_stream(stream); - ipu6_isys_put_stream(stream); - } - } - - if (status & IPU_CSI_RX_IRQ_FE_VC(i)) { - stream = ipu6_isys_query_stream_by_source(csi2->isys, - source, i); - if (stream) { - ipu6_isys_csi2_eof_event_by_stream(stream); - ipu6_isys_put_stream(stream); - } - } - } -} - -static irqreturn_t isys_isr(struct ipu6_bus_device *adev) -{ - struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); - void __iomem *base = isys->pdata->base; - u32 status_sw, status_csi; - u32 ctrl0_status, ctrl0_clear; - - spin_lock(&isys->power_lock); - if (!isys->power) { - spin_unlock(&isys->power_lock); - return IRQ_NONE; - } - - ctrl0_status = isys->pdata->ipdata->csi2.ctrl0_irq_status; - ctrl0_clear = isys->pdata->ipdata->csi2.ctrl0_irq_clear; - - status_csi = readl(isys->pdata->base + ctrl0_status); - status_sw = readl(isys->pdata->base + - IPU6_REG_ISYS_UNISPART_IRQ_STATUS); - - writel(ISYS_UNISPART_IRQS & ~IPU6_ISYS_UNISPART_IRQ_SW, - base + IPU6_REG_ISYS_UNISPART_IRQ_MASK); - - do { - writel(status_csi, isys->pdata->base + ctrl0_clear); - - writel(status_sw, isys->pdata->base + - IPU6_REG_ISYS_UNISPART_IRQ_CLEAR); - - if (isys->isr_csi2_bits & status_csi) { - unsigned int i; - - for (i = 0; i < isys->pdata->ipdata->csi2.nports; i++) { - /* irq from not enabled port */ - if (!isys->csi2[i].base) - continue; - if (status_csi & IPU6_ISYS_UNISPART_IRQ_CSI2(i)) - ipu6_isys_csi2_isr(&isys->csi2[i]); - } - } - - writel(0, base + IPU6_REG_ISYS_UNISPART_SW_IRQ_REG); - - if (!isys_isr_one(adev)) - status_sw = IPU6_ISYS_UNISPART_IRQ_SW; - else - status_sw = 0; - - status_csi = readl(isys->pdata->base + ctrl0_status); - status_sw |= readl(isys->pdata->base + - IPU6_REG_ISYS_UNISPART_IRQ_STATUS); - } while ((status_csi & isys->isr_csi2_bits) || - (status_sw & IPU6_ISYS_UNISPART_IRQ_SW)); - - writel(ISYS_UNISPART_IRQS, base + IPU6_REG_ISYS_UNISPART_IRQ_MASK); - - spin_unlock(&isys->power_lock); - - return IRQ_HANDLED; -} - -static void get_lut_ltrdid(struct ipu6_isys *isys, struct ltr_did *pltr_did) -{ - struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark; - struct ltr_did ltrdid_default; - - ltrdid_default.lut_ltr.value = LTR_DEFAULT_VALUE; - ltrdid_default.lut_fill_time.value = FILL_TIME_DEFAULT_VALUE; - - if (iwake_watermark->ltrdid.lut_ltr.value) - *pltr_did = iwake_watermark->ltrdid; - else - *pltr_did = ltrdid_default; -} - static int set_iwake_register(struct ipu6_isys *isys, u32 index, u32 value) { struct device *dev = &isys->adev->auxdev.dev; @@ -511,69 +446,57 @@ static void set_iwake_ltrdid(struct ipu6_isys *isys, u16 ltr, u16 did, */ static void enable_iwake(struct ipu6_isys *isys, bool enable) { - struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark; int ret; - mutex_lock(&iwake_watermark->mutex); - - if (iwake_watermark->iwake_enabled == enable) { - mutex_unlock(&iwake_watermark->mutex); + if (isys->iwake_watermark_enabled == enable) return; - } ret = set_iwake_register(isys, GDA_ENABLE_IWAKE_INDEX, enable); if (!ret) - iwake_watermark->iwake_enabled = enable; - - mutex_unlock(&iwake_watermark->mutex); + isys->iwake_watermark_enabled = enable; } -void update_watermark_setting(struct ipu6_isys *isys) +void ipu6_isys_update_watermark_setting(struct ipu6_isys *isys) { - struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark; u32 iwake_threshold, iwake_critical_threshold, page_num; struct device *dev = &isys->adev->auxdev.dev; u32 calc_fill_time_us = 0, ltr = 0, did = 0; - struct video_stream_watermark *p_watermark; enum ltr_did_type ltr_did_type; - struct list_head *stream_node; u64 isys_pb_datarate_mbs = 0; u32 mem_open_threshold = 0; struct ltr_did ltrdid; u64 threshold_bytes; u32 max_sram_size; u32 shift; + bool force_iwake_disable = false; + + lockdep_assert_held(&isys->stream_mutex); shift = isys->pdata->ipdata->sram_gran_shift; max_sram_size = isys->pdata->ipdata->max_sram_size; - mutex_lock(&iwake_watermark->mutex); - if (iwake_watermark->force_iwake_disable) { + for (unsigned int i = 0; i < isys->pdata->ipdata->csi2.nports; i++) { + isys_pb_datarate_mbs += + isys->csi2[i].watermark.stream_data_rate; + force_iwake_disable |= + isys->csi2[i].watermark.force_iwake_disable; + } + + if (force_iwake_disable) { + dev_dbg(dev, "watermark: forcing iwake disabled\n"); set_iwake_ltrdid(isys, 0, 0, LTR_IWAKE_OFF); set_iwake_register(isys, GDA_IRQ_CRITICAL_THRESHOLD_INDEX, CRITICAL_THRESHOLD_IWAKE_DISABLE); - goto unlock_exit; - } - - if (list_empty(&iwake_watermark->video_list)) { - isys_pb_datarate_mbs = 0; - } else { - list_for_each(stream_node, &iwake_watermark->video_list) { - p_watermark = list_entry(stream_node, - struct video_stream_watermark, - stream_node); - isys_pb_datarate_mbs += p_watermark->stream_data_rate; - } + return; } - mutex_unlock(&iwake_watermark->mutex); if (!isys_pb_datarate_mbs) { + dev_dbg(dev, "watermark: disabled iwake\n"); enable_iwake(isys, false); set_iwake_ltrdid(isys, 0, 0, LTR_IWAKE_OFF); - mutex_lock(&iwake_watermark->mutex); set_iwake_register(isys, GDA_IRQ_CRITICAL_THRESHOLD_INDEX, CRITICAL_THRESHOLD_IWAKE_DISABLE); - goto unlock_exit; + return; } enable_iwake(isys, true); @@ -584,7 +507,8 @@ void update_watermark_setting(struct ipu6_isys *isys) did = calc_fill_time_us * DEFAULT_DID_RATIO / 100; ltr_did_type = LTR_ENHANNCE_IWAKE; } else { - get_lut_ltrdid(isys, <rdid); + ltrdid.lut_ltr.value = LTR_DEFAULT_VALUE; + ltrdid.lut_fill_time.value = FILL_TIME_DEFAULT_VALUE; if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th0) ltr = 0; @@ -608,7 +532,6 @@ void update_watermark_setting(struct ipu6_isys *isys) iwake_threshold = max_t(u32, 1, threshold_bytes >> shift); iwake_threshold = min_t(u32, iwake_threshold, max_sram_size); - mutex_lock(&iwake_watermark->mutex); if (isys->pdata->ipdata->enhanced_iwake) { set_iwake_register(isys, GDA_IWAKE_THRESHOLD_INDEX, DEFAULT_IWAKE_THRESHOLD); @@ -630,7 +553,7 @@ void update_watermark_setting(struct ipu6_isys *isys) iwake_critical_threshold = iwake_threshold + (IS_PIXEL_BUFFER_PAGES - iwake_threshold) / 2; - dev_dbg(dev, "threshold: %u critical: %u\n", iwake_threshold, + dev_dbg(dev, "watermark: threshold: %u critical: %u\n", iwake_threshold, iwake_critical_threshold); set_iwake_register(isys, GDA_IRQ_CRITICAL_THRESHOLD_INDEX, @@ -640,32 +563,6 @@ void update_watermark_setting(struct ipu6_isys *isys) isys->adev->isp->base + REG_PKGC_PMON_CFG); writel(VAL_PKGC_PMON_CFG_START, isys->adev->isp->base + REG_PKGC_PMON_CFG); -unlock_exit: - mutex_unlock(&iwake_watermark->mutex); -} - -static void isys_iwake_watermark_init(struct ipu6_isys *isys) -{ - struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark; - - INIT_LIST_HEAD(&iwake_watermark->video_list); - mutex_init(&iwake_watermark->mutex); - - iwake_watermark->ltrdid.lut_ltr.value = 0; - iwake_watermark->isys = isys; - iwake_watermark->iwake_enabled = false; - iwake_watermark->force_iwake_disable = false; -} - -static void isys_iwake_watermark_cleanup(struct ipu6_isys *isys) -{ - struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark; - - mutex_lock(&iwake_watermark->mutex); - list_del(&iwake_watermark->video_list); - mutex_unlock(&iwake_watermark->mutex); - - mutex_destroy(&iwake_watermark->mutex); } /* The .bound() notifier callback when a match is found */ @@ -736,6 +633,10 @@ static int isys_notifier_init(struct ipu6_isys *isys) continue; ret = v4l2_fwnode_endpoint_parse(ep, &vep); + if (ret && IS_IPU7(isp)) { + vep.bus_type = V4L2_MBUS_CSI2_CPHY; + ret = v4l2_fwnode_endpoint_parse(ep, &vep); + } if (ret) { dev_err(dev, "fwnode endpoint parse failed: %d\n", ret); goto err_parse; @@ -751,6 +652,7 @@ static int isys_notifier_init(struct ipu6_isys *isys) s_asd->csi2.port = vep.base.port; s_asd->csi2.nlanes = vep.bus.mipi_csi2.num_data_lanes; + s_asd->csi2.bus_type = vep.bus_type; dev_dbg(dev, "remote endpoint port %d with %d lanes added\n", s_asd->csi2.port, s_asd->csi2.nlanes); @@ -789,7 +691,7 @@ static int isys_register_devices(struct ipu6_isys *isys) isys->media_dev.dev = dev; media_device_pci_init(&isys->media_dev, - pdev, IPU6_MEDIA_DEV_MODEL_NAME); + pdev, isys->adev->isp->model_name); strscpy(isys->v4l2_dev.name, isys->media_dev.model, sizeof(isys->v4l2_dev.name)); @@ -853,9 +755,10 @@ static void isys_unregister_devices(struct ipu6_isys *isys) static int isys_runtime_pm_resume(struct device *dev) { struct ipu6_bus_device *adev = to_ipu6_bus_device(dev); + const struct ipu6_fw_isys_ops *fw_ops = adev->auxdrv_data->fw_ops; struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); + const struct ipu6_isys_internal_pdata *ipdata = isys->pdata->ipdata; struct ipu6_device *isp = adev->isp; - unsigned long flags; int ret; ret = ipu6_mmu_hw_init(adev->mmu); @@ -866,49 +769,82 @@ static int isys_runtime_pm_resume(struct device *dev) ret = ipu6_buttress_start_tsc_sync(isp); if (ret) - return ret; + goto err_mmu_hw_cleanup; - spin_lock_irqsave(&isys->power_lock, flags); - isys->power = 1; - spin_unlock_irqrestore(&isys->power_lock, flags); + if (IS_IPU7(isp)) { + ipu7_isys_setup_hw(isys); + } else { + ipu6_isys_setup_hw(isys); + set_iwake_ltrdid(isys, 0, 0, LTR_ISYS_ON); + } - isys_setup_hw(isys); + ipu6_configure_spc(adev->isp, &ipdata->hw_variant, + IPU6_CPD_PKG_DIR_ISYS_SERVER_IDX, isys->pdata->base, + adev->pkg_dir, adev->pkg_dir_dma_addr); - set_iwake_ltrdid(isys, 0, 0, LTR_ISYS_ON); + /* + * Buffers could have been left to wrong queue at last closure. + * Move them now back to empty buffer queue. + */ + ipu6_cleanup_fw_msg_bufs(isys); - return 0; + if (isys->fwctx) { + /* + * Something went wrong in previous shutdown. As we are now + * restarting isys we can safely delete old context. + */ + dev_warn(&adev->auxdev.dev, "clearing old context\n"); + fw_ops->cleanup(isys); + } + + ret = fw_ops->init(isys, ipdata->num_parallel_streams); + if (!ret) + return 0; + + isys->phy_termcal_val = 0; + cpu_latency_qos_update_request(&isys->pm_qos, PM_QOS_DEFAULT_VALUE); + + if (!IS_IPU7(isp)) + set_iwake_ltrdid(isys, 0, 0, LTR_ISYS_OFF); + +err_mmu_hw_cleanup: + ipu6_mmu_hw_cleanup(adev->mmu); + + return ret; } static int isys_runtime_pm_suspend(struct device *dev) { struct ipu6_bus_device *adev = to_ipu6_bus_device(dev); struct ipu6_isys *isys = dev_get_drvdata(dev); - unsigned long flags; - - spin_lock_irqsave(&isys->power_lock, flags); - isys->power = 0; - spin_unlock_irqrestore(&isys->power_lock, flags); + struct ipu6_device *isp = adev->isp; + int ret = 0; - mutex_lock(&isys->mutex); - isys->need_reset = false; - mutex_unlock(&isys->mutex); + isys->adev->auxdrv_data->fw_ops->close(isys); + if (isys->fwctx) { + dev_warn(&isys->adev->auxdev.dev, "failed to close fw isys\n"); + ret = -EIO; + } isys->phy_termcal_val = 0; cpu_latency_qos_update_request(&isys->pm_qos, PM_QOS_DEFAULT_VALUE); - set_iwake_ltrdid(isys, 0, 0, LTR_ISYS_OFF); + if (!IS_IPU7(isp)) + set_iwake_ltrdid(isys, 0, 0, LTR_ISYS_OFF); ipu6_mmu_hw_cleanup(adev->mmu); - return 0; + return ret; } static int isys_suspend(struct device *dev) { struct ipu6_isys *isys = dev_get_drvdata(dev); + guard(mutex)(&isys->stream_mutex); + /* If stream is open, refuse to suspend */ - if (isys->stream_opened) + if (!ida_is_empty(&isys->streams)) return -EBUSY; return 0; @@ -1003,7 +939,7 @@ struct isys_fw_msgs *ipu6_get_fw_msg_buf(struct ipu6_isys_stream *stream) msg = list_last_entry(&isys->framebuflist, struct isys_fw_msgs, head); list_move(&msg->head, &isys->framebuflist_fw); spin_unlock_irqrestore(&isys->listlock, flags); - memset(&msg->fw_msg, 0, sizeof(msg->fw_msg)); + memset(&msg->ipu6, 0, sizeof(msg->ipu6)); return msg; } @@ -1019,30 +955,28 @@ void ipu6_cleanup_fw_msg_bufs(struct ipu6_isys *isys) spin_unlock_irqrestore(&isys->listlock, flags); } -void ipu6_put_fw_msg_buf(struct ipu6_isys *isys, uintptr_t data) +void ipu6_put_fw_msg_buf(struct ipu6_isys *isys, struct isys_fw_msgs *msg) { - struct isys_fw_msgs *msg; unsigned long flags; - void *ptr = (void *)data; - if (!ptr) + if (!msg) return; spin_lock_irqsave(&isys->listlock, flags); - msg = container_of(ptr, struct isys_fw_msgs, fw_msg.dummy); list_move(&msg->head, &isys->framebuflist); spin_unlock_irqrestore(&isys->listlock, flags); } +static const struct ipu6_auxdrv_data ipu6_isys_auxdrv_data; +static const struct ipu6_auxdrv_data ipu7_isys_auxdrv_data; + static int isys_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *auxdev_id) { const struct ipu6_isys_internal_csi2_pdata *csi2_pdata; struct ipu6_bus_device *adev = auxdev_to_adev(auxdev); struct ipu6_device *isp = adev->isp; - const struct firmware *fw; struct ipu6_isys *isys; - unsigned int i; int ret; if (!isp->bus_ready_to_probe) @@ -1052,8 +986,8 @@ static int isys_probe(struct auxiliary_device *auxdev, if (!isys) return -ENOMEM; - adev->auxdrv_data = - (const struct ipu6_auxdrv_data *)auxdev_id->driver_data; + adev->auxdrv_data = IS_IPU7(isp) ? &ipu7_isys_auxdrv_data : + &ipu6_isys_auxdrv_data; adev->auxdrv = to_auxiliary_drv(auxdev->dev.driver); isys->adev = adev; isys->pdata = adev->pdata; @@ -1068,8 +1002,6 @@ static int isys_probe(struct auxiliary_device *auxdev, isys->sensor_type = isys->pdata->ipdata->sensor_type_start; spin_lock_init(&isys->streams_lock); - spin_lock_init(&isys->power_lock); - isys->power = 0; isys->phy_termcal_val = 0; mutex_init(&isys->mutex); @@ -1083,18 +1015,7 @@ static int isys_probe(struct auxiliary_device *auxdev, dev_set_drvdata(&auxdev->dev, isys); - isys_stream_init(isys); - - if (!isp->secure_mode) { - fw = isp->cpd_fw; - ret = ipu6_buttress_map_fw_image(adev, fw, &adev->fw_sgt); - if (ret) - goto release_firmware; - - ret = ipu6_cpd_create_pkg_dir(adev, isp->cpd_fw->data); - if (ret) - goto remove_shared_buffer; - } + ida_init(&isys->streams); cpu_latency_qos_add_request(&isys->pm_qos, PM_QOS_DEFAULT_VALUE); @@ -1102,11 +1023,11 @@ static int isys_probe(struct auxiliary_device *auxdev, if (ret < 0) goto out_remove_pkg_dir_shared_buffer; - isys_iwake_watermark_init(isys); - - if (is_ipu6se(adev->isp->hw_ver)) + if (IS_IPU7(adev->isp)) + isys->phy_set_power = ipu7_isys_csi_phy_set_power; + else if (IS_IPU6SE(adev->isp)) isys->phy_set_power = ipu6_isys_jsl_phy_set_power; - else if (is_ipu6ep_mtl(adev->isp->hw_ver)) + else if (IS_IPU6EP_MTL(adev->isp)) isys->phy_set_power = ipu6_isys_dwc_phy_set_power; else isys->phy_set_power = ipu6_isys_mcd_phy_set_power; @@ -1121,17 +1042,6 @@ free_fw_msg_bufs: free_fw_msg_bufs(isys); out_remove_pkg_dir_shared_buffer: cpu_latency_qos_remove_request(&isys->pm_qos); - if (!isp->secure_mode) - ipu6_cpd_free_pkg_dir(adev); -remove_shared_buffer: - if (!isp->secure_mode) - ipu6_buttress_unmap_fw_image(adev, &adev->fw_sgt); -release_firmware: - if (!isp->secure_mode) - release_firmware(adev->fw); - - for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) - mutex_destroy(&isys->streams[i].mutex); mutex_destroy(&isys->mutex); mutex_destroy(&isys->stream_mutex); @@ -1141,10 +1051,9 @@ release_firmware: static void isys_remove(struct auxiliary_device *auxdev) { - struct ipu6_bus_device *adev = auxdev_to_adev(auxdev); struct ipu6_isys *isys = dev_get_drvdata(&auxdev->dev); - struct ipu6_device *isp = adev->isp; - unsigned int i; + + ida_destroy(&isys->streams); free_fw_msg_bufs(isys); @@ -1153,191 +1062,27 @@ static void isys_remove(struct auxiliary_device *auxdev) cpu_latency_qos_remove_request(&isys->pm_qos); - if (!isp->secure_mode) { - ipu6_cpd_free_pkg_dir(adev); - ipu6_buttress_unmap_fw_image(adev, &adev->fw_sgt); - release_firmware(adev->fw); - } - - for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) - mutex_destroy(&isys->streams[i].mutex); - - isys_iwake_watermark_cleanup(isys); mutex_destroy(&isys->stream_mutex); mutex_destroy(&isys->mutex); } -struct fwmsg { - int type; - char *msg; - bool valid_ts; -}; - -static const struct fwmsg fw_msg[] = { - {IPU6_FW_ISYS_RESP_TYPE_STREAM_OPEN_DONE, "STREAM_OPEN_DONE", 0}, - {IPU6_FW_ISYS_RESP_TYPE_STREAM_CLOSE_ACK, "STREAM_CLOSE_ACK", 0}, - {IPU6_FW_ISYS_RESP_TYPE_STREAM_START_ACK, "STREAM_START_ACK", 0}, - {IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK, - "STREAM_START_AND_CAPTURE_ACK", 0}, - {IPU6_FW_ISYS_RESP_TYPE_STREAM_STOP_ACK, "STREAM_STOP_ACK", 0}, - {IPU6_FW_ISYS_RESP_TYPE_STREAM_FLUSH_ACK, "STREAM_FLUSH_ACK", 0}, - {IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_READY, "PIN_DATA_READY", 1}, - {IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_ACK, "STREAM_CAPTURE_ACK", 0}, - {IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE, - "STREAM_START_AND_CAPTURE_DONE", 1}, - {IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_DONE, "STREAM_CAPTURE_DONE", 1}, - {IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF, "FRAME_SOF", 1}, - {IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF, "FRAME_EOF", 1}, - {IPU6_FW_ISYS_RESP_TYPE_STATS_DATA_READY, "STATS_READY", 1}, - {-1, "UNKNOWN MESSAGE", 0} +static const struct ipu6_auxdrv_data ipu6_isys_auxdrv_data = { + .isr = ipu6_isys_isr, + .isr_threaded = NULL, + .wake_isr_thread = false, + .fw_ops = &ipu6_fw_isys_ops, }; -static u32 resp_type_to_index(int type) -{ - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(fw_msg); i++) - if (fw_msg[i].type == type) - return i; - - return ARRAY_SIZE(fw_msg) - 1; -} - -static int isys_isr_one(struct ipu6_bus_device *adev) -{ - struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); - struct ipu6_fw_isys_resp_info_abi *resp; - struct ipu6_isys_stream *stream; - struct ipu6_isys_csi2 *csi2 = NULL; - u32 index; - u64 ts; - - if (!isys->fwcom) - return 1; - - resp = ipu6_fw_isys_get_resp(isys->fwcom, IPU6_BASE_MSG_RECV_QUEUES); - if (!resp) - return 1; - - ts = (u64)resp->timestamp[1] << 32 | resp->timestamp[0]; - - index = resp_type_to_index(resp->type); - dev_dbg(&adev->auxdev.dev, - "FW resp %02d %s, stream %u, ts 0x%16.16llx, pin %d\n", - resp->type, fw_msg[index].msg, resp->stream_handle, - fw_msg[index].valid_ts ? ts : 0, resp->pin_id); - - if (resp->error_info.error == IPU6_FW_ISYS_ERROR_STREAM_IN_SUSPENSION) - /* Suspension is kind of special case: not enough buffers */ - dev_dbg(&adev->auxdev.dev, - "FW error resp SUSPENSION, details %d\n", - resp->error_info.error_details); - else if (resp->error_info.error) - dev_dbg(&adev->auxdev.dev, - "FW error resp error %d, details %d\n", - resp->error_info.error, resp->error_info.error_details); - - if (resp->stream_handle >= IPU6_ISYS_MAX_STREAMS) { - dev_err(&adev->auxdev.dev, "bad stream handle %u\n", - resp->stream_handle); - goto leave; - } - - stream = ipu6_isys_query_stream_by_handle(isys, resp->stream_handle); - if (!stream) { - dev_err(&adev->auxdev.dev, "stream of stream_handle %u is unused\n", - resp->stream_handle); - goto leave; - } - stream->error = resp->error_info.error; - - csi2 = ipu6_isys_subdev_to_csi2(stream->asd); - - switch (resp->type) { - case IPU6_FW_ISYS_RESP_TYPE_STREAM_OPEN_DONE: - complete(&stream->stream_open_completion); - break; - case IPU6_FW_ISYS_RESP_TYPE_STREAM_CLOSE_ACK: - complete(&stream->stream_close_completion); - break; - case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_ACK: - complete(&stream->stream_start_completion); - break; - case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK: - complete(&stream->stream_start_completion); - break; - case IPU6_FW_ISYS_RESP_TYPE_STREAM_STOP_ACK: - complete(&stream->stream_stop_completion); - break; - case IPU6_FW_ISYS_RESP_TYPE_STREAM_FLUSH_ACK: - complete(&stream->stream_stop_completion); - break; - case IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_READY: - /* - * firmware only release the capture msg until software - * get pin_data_ready event - */ - ipu6_put_fw_msg_buf(ipu6_bus_get_drvdata(adev), resp->buf_id); - if (resp->pin_id < IPU6_ISYS_OUTPUT_PINS && - stream->output_pins_queue[resp->pin_id]) - ipu6_isys_queue_buf_ready(stream, resp); - else - dev_warn(&adev->auxdev.dev, - "%d:No queue for pin id %d\n", - resp->stream_handle, resp->pin_id); - if (csi2) - ipu6_isys_csi2_error(csi2); - - break; - case IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_ACK: - break; - case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE: - case IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_DONE: - break; - case IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF: - - ipu6_isys_csi2_sof_event_by_stream(stream); - stream->seq[stream->seq_index].sequence = - atomic_read(&stream->sequence) - 1; - stream->seq[stream->seq_index].timestamp = ts; - dev_dbg(&adev->auxdev.dev, - "sof: handle %d: (index %u), timestamp 0x%16.16llx\n", - resp->stream_handle, - stream->seq[stream->seq_index].sequence, ts); - stream->seq_index = (stream->seq_index + 1) - % IPU6_ISYS_MAX_PARALLEL_SOF; - break; - case IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF: - ipu6_isys_csi2_eof_event_by_stream(stream); - dev_dbg(&adev->auxdev.dev, - "eof: handle %d: (index %u), timestamp 0x%16.16llx\n", - resp->stream_handle, - stream->seq[stream->seq_index].sequence, ts); - break; - case IPU6_FW_ISYS_RESP_TYPE_STATS_DATA_READY: - break; - default: - dev_err(&adev->auxdev.dev, "%d:unknown response type %u\n", - resp->stream_handle, resp->type); - break; - } - - ipu6_isys_put_stream(stream); -leave: - ipu6_fw_isys_put_resp(isys->fwcom, IPU6_BASE_MSG_RECV_QUEUES); - return 0; -} - -static const struct ipu6_auxdrv_data ipu6_isys_auxdrv_data = { - .isr = isys_isr, +static const struct ipu6_auxdrv_data ipu7_isys_auxdrv_data = { + .isr = ipu7_isys_isr, .isr_threaded = NULL, .wake_isr_thread = false, + .fw_ops = &ipu7_fw_isys_ops, }; static const struct auxiliary_device_id ipu6_isys_id_table[] = { { .name = "intel_ipu6.isys", - .driver_data = (kernel_ulong_t)&ipu6_isys_auxdrv_data, }, { } }; diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.h b/drivers/media/pci/intel/ipu6/ipu6-isys.h index 7fb8cb820912..2af20f56a965 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.h +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.h @@ -4,6 +4,7 @@ #ifndef IPU6_ISYS_H #define IPU6_ISYS_H +#include <linux/idr.h> #include <linux/irqreturn.h> #include <linux/list.h> #include <linux/mutex.h> @@ -14,9 +15,11 @@ #include <media/media-device.h> #include <media/v4l2-async.h> #include <media/v4l2-device.h> +#include <media/v4l2-mediabus.h> #include "ipu6.h" #include "ipu6-fw-isys.h" +#include "ipu7-fw-isys.h" #include "ipu6-isys-csi2.h" #include "ipu6-isys-video.h" @@ -60,41 +63,10 @@ struct ipu6_bus_device; #define IPU6EP_MTL_LTR_VALUE 1023 #define IPU6EP_MTL_MIN_MEMOPEN_TH 0xc -struct ltr_did { - union { - u32 value; - struct { - u8 val0; - u8 val1; - u8 val2; - u8 val3; - } bits; - } lut_ltr; - union { - u32 value; - struct { - u8 th0; - u8 th1; - u8 th2; - u8 th3; - } bits; - } lut_fill_time; -}; - -struct isys_iwake_watermark { - bool iwake_enabled; - bool force_iwake_disable; - u32 iwake_threshold; - u64 isys_pixelbuffer_datarate; - struct ltr_did ltrdid; - struct mutex mutex; /* protect whole struct */ - struct ipu6_isys *isys; - struct list_head video_list; -}; - struct ipu6_isys_csi2_config { u32 nlanes; u32 port; + enum v4l2_mbus_type bus_type; }; struct sensor_async_sd { @@ -108,17 +80,12 @@ struct sensor_async_sd { * @media_dev: Media device * @v4l2_dev: V4L2 device * @adev: ISYS bus device - * @power: Is ISYS powered on or not? - * @isr_bits: Which bits does the ISR handle? - * @power_lock: Serialise access to power (power state in general) * @csi2_rx_ctrl_cached: cached shared value between all CSI2 receivers * @streams_lock: serialise access to streams * @streams: streams per firmware stream ID - * @fwcom: fw communication layer private pointer - * or optional external library private pointer + * @fwctx: fw communication layer context pointer * @phy_termcal_val: the termination calibration value, only used for DWC PHY * @need_reset: Isys requires d0i0->i3 transition - * @ref_count: total number of callers fw open * @mutex: serialise access isys video open/release related operations * @stream_mutex: serialise stream start and stop, queueing requests * @pdata: platform data pointer @@ -129,20 +96,17 @@ struct ipu6_isys { struct v4l2_device v4l2_dev; struct ipu6_bus_device *adev; - int power; - spinlock_t power_lock; u32 isr_csi2_bits; u32 csi2_rx_ctrl_cached; spinlock_t streams_lock; - struct ipu6_isys_stream streams[IPU6_ISYS_MAX_STREAMS]; - int streams_ref_count[IPU6_ISYS_MAX_STREAMS]; - void *fwcom; + struct ipu6_isys_stream *streams_by_handle[IPU6_ISYS_MAX_STREAMS]; + void *fwctx; u32 phy_termcal_val; + u32 phy_rext_cal; bool need_reset; bool icache_prefetch; bool csi2_cse_ipc_not_supported; - unsigned int ref_count; - unsigned int stream_opened; + bool iwake_watermark_enabled; unsigned int sensor_type; struct mutex mutex; @@ -162,26 +126,65 @@ struct ipu6_isys { struct list_head framebuflist; struct list_head framebuflist_fw; struct v4l2_async_notifier notifier; - struct isys_iwake_watermark iwake_watermark; + struct ida streams; }; struct isys_fw_msgs { union { u64 dummy; - struct ipu6_fw_isys_frame_buff_set_abi frame; - struct ipu6_fw_isys_stream_cfg_data_abi stream; - } fw_msg; + union { + struct ipu6_fw_isys_frame_buff_set_abi frame; + struct ipu6_fw_isys_stream_cfg_data_abi stream; + } ipu6; + union { + struct ipu7_fw_isys_frame_buff_set frame; + struct ipu7_fw_isys_stream_cfg stream; + } ipu7; + }; struct list_head head; dma_addr_t dma_addr; }; +struct ipu6_fw_isys_ops { + int (*init)(struct ipu6_isys *isys, unsigned int num_streams); + int (*close)(struct ipu6_isys *isys); + int (*send_cmd)(struct ipu6_isys *isys, + const unsigned int stream_handle, + void *cpu_mapped_buf, + dma_addr_t dma_mapped_buf, + size_t size, u16 send_type); + void (*cleanup)(struct ipu6_isys *isys); + int (*prepare_stream_cfg)(struct ipu6_isys_stream *stream, + struct v4l2_mbus_frame_desc *desc, + struct isys_fw_msgs *msg); + void (*prepare_buf_set)(struct isys_fw_msgs *msg, + struct ipu6_isys_stream *stream, + struct ipu6_isys_buffer_list *bl); + int (*stream_open)(struct ipu6_isys *isys, + const unsigned int stream_handle, + struct isys_fw_msgs *msg); + int (*stream_start)(struct ipu6_isys *isys, + const unsigned int stream_handle, + struct isys_fw_msgs *msg, bool capture); + int (*stream_capture)(struct ipu6_isys *isys, + const unsigned int stream_handle, + struct isys_fw_msgs *msg); + int (*stream_flush)(struct ipu6_isys *isys, + const unsigned int stream_handle); + int (*stream_close)(struct ipu6_isys *isys, + const unsigned int stream_handle); + void (*dump_stream_cfg)(struct device *dev, struct isys_fw_msgs *msg); + void (*dump_frame_buf_set)(struct device *dev, struct isys_fw_msgs *msg, + unsigned int outputs); +}; + struct isys_fw_msgs *ipu6_get_fw_msg_buf(struct ipu6_isys_stream *stream); -void ipu6_put_fw_msg_buf(struct ipu6_isys *isys, uintptr_t data); +void ipu6_put_fw_msg_buf(struct ipu6_isys *isys, struct isys_fw_msgs *msg); void ipu6_cleanup_fw_msg_bufs(struct ipu6_isys *isys); extern const struct v4l2_ioctl_ops ipu6_isys_ioctl_ops; -void update_watermark_setting(struct ipu6_isys *isys); +void ipu6_isys_update_watermark_setting(struct ipu6_isys *isys); int ipu6_isys_mcd_phy_set_power(struct ipu6_isys *isys, struct ipu6_isys_csi2_config *cfg, diff --git a/drivers/media/pci/intel/ipu6/ipu6-mmu-hw.c b/drivers/media/pci/intel/ipu6/ipu6-mmu-hw.c new file mode 100644 index 000000000000..2b395fbd5969 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-mmu-hw.c @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2026 Intel Corporation + */ +#include <asm/barrier.h> + +#include <linux/bits.h> +#include <linux/gfp.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/types.h> + +#include "ipu6.h" +#include "ipu6-dma.h" +#include "ipu6-mmu.h" +#include "ipu6-platform-regs.h" + +#define ISP_PAGE_SHIFT 12 +#define ISP_PAGE_SIZE BIT(ISP_PAGE_SHIFT) +#define ISP_PAGE_MASK (~(ISP_PAGE_SIZE - 1)) + +#define ISP_L1PT_SHIFT 22 +#define ISP_L1PT_MASK (~((1U << ISP_L1PT_SHIFT) - 1)) + +#define ISP_L2PT_SHIFT 12 +#define ISP_L2PT_MASK (~(ISP_L1PT_MASK | (~(ISP_PAGE_MASK)))) + +#define ISP_L1PT_PTES 1024 +#define ISP_L2PT_PTES 1024 + +#define ISP_PADDR_SHIFT 12 + +#define REG_TLB_INVALIDATE 0x0000 + +#define REG_L1_PHYS 0x0004 /* 27-bit pfn */ +#define REG_INFO 0x0008 + +#define TBL_PHYS_ADDR(a) ((phys_addr_t)(a) << ISP_PADDR_SHIFT) + +static struct ipu6_mmu_hw ipu6_isys_mmu_hwdata[] = { + { + .offset = IPU6_ISYS_IOMMU0_OFFSET, + .info_bits = IPU6_INFO_REQUEST_DESTINATION_IOSF, + .nr_l1streams = 16, + .l1_block_sz = { + 3, 8, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 1, 1 + }, + .nr_l2streams = 16, + .l2_block_sz = { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2 + }, + .insert_read_before_invalidate = false, + .l1_stream_id_reg_offset = + IPU6_MMU_L1_STREAM_ID_REG_OFFSET, + .l2_stream_id_reg_offset = + IPU6_MMU_L2_STREAM_ID_REG_OFFSET, + }, + { + .offset = IPU6_ISYS_IOMMU1_OFFSET, + .info_bits = 0, + .nr_l1streams = 16, + .l1_block_sz = { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 1, 1, 4 + }, + .nr_l2streams = 16, + .l2_block_sz = { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2 + }, + .insert_read_before_invalidate = false, + .l1_stream_id_reg_offset = + IPU6_MMU_L1_STREAM_ID_REG_OFFSET, + .l2_stream_id_reg_offset = + IPU6_MMU_L2_STREAM_ID_REG_OFFSET, + }, + { + .offset = IPU6_ISYS_IOMMUI_OFFSET, + .info_bits = 0, + .nr_l1streams = 0, + .nr_l2streams = 0, + .insert_read_before_invalidate = false, + }, +}; + +static struct ipu6_mmu_hw ipu6_psys_mmu_hwdata[] = { + { + .offset = IPU6_PSYS_IOMMU0_OFFSET, + .info_bits = + IPU6_INFO_REQUEST_DESTINATION_IOSF, + .nr_l1streams = 16, + .l1_block_sz = { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2 + }, + .nr_l2streams = 16, + .l2_block_sz = { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2 + }, + .insert_read_before_invalidate = false, + .l1_stream_id_reg_offset = + IPU6_MMU_L1_STREAM_ID_REG_OFFSET, + .l2_stream_id_reg_offset = + IPU6_MMU_L2_STREAM_ID_REG_OFFSET, + }, + { + .offset = IPU6_PSYS_IOMMU1_OFFSET, + .info_bits = 0, + .nr_l1streams = 32, + .l1_block_sz = { + 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 10, + 5, 4, 14, 6, 4, 14, 6, 4, 8, + 4, 2, 1, 1, 1, 1, 14 + }, + .nr_l2streams = 32, + .l2_block_sz = { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2 + }, + .insert_read_before_invalidate = false, + .l1_stream_id_reg_offset = + IPU6_MMU_L1_STREAM_ID_REG_OFFSET, + .l2_stream_id_reg_offset = + IPU6_PSYS_MMU1W_L2_STREAM_ID_REG_OFFSET, + }, + { + .offset = IPU6_PSYS_IOMMU1R_OFFSET, + .info_bits = 0, + .nr_l1streams = 16, + .l1_block_sz = { + 1, 4, 4, 4, 4, 16, 8, 4, 32, + 16, 16, 2, 2, 2, 1, 12 + }, + .nr_l2streams = 16, + .l2_block_sz = { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2 + }, + .insert_read_before_invalidate = false, + .l1_stream_id_reg_offset = + IPU6_MMU_L1_STREAM_ID_REG_OFFSET, + .l2_stream_id_reg_offset = + IPU6_MMU_L2_STREAM_ID_REG_OFFSET, + }, + { + .offset = IPU6_PSYS_IOMMUI_OFFSET, + .info_bits = 0, + .nr_l1streams = 0, + .nr_l2streams = 0, + .insert_read_before_invalidate = false, + }, +}; + +struct ipu6_mmu_hwdata { + struct ipu6_mmu_hw *hwdata; + unsigned int nr_mmus; +}; + +static const struct ipu6_mmu_hwdata ipu6_mmu_hwdata_lookup[IPU_SUBSYS_NUM] = { + [IPU_PSYS] = { + .hwdata = ipu6_psys_mmu_hwdata, + .nr_mmus = ARRAY_SIZE(ipu6_psys_mmu_hwdata), + }, + [IPU_ISYS] = { + .hwdata = ipu6_isys_mmu_hwdata, + .nr_mmus = ARRAY_SIZE(ipu6_isys_mmu_hwdata), + }, +}; + +static void __ipu6_tlb_invalidate(struct ipu6_mmu *mmu) +{ + struct ipu6_mmu_hw *mmu_hw = mmu->ipu6_mmu_hw; + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&mmu->ready_lock, flags); + if (!mmu->ready) { + spin_unlock_irqrestore(&mmu->ready_lock, flags); + return; + } + + for (i = 0; i < mmu->nr_mmus; i++) { + /* + * To avoid the HW bug induced dead lock in some of the IPU6 + * MMUs on successive invalidate calls, we need to first do a + * read to the page table base before writing the invalidate + * register. MMUs which need to implement this WA, will have + * the insert_read_before_invalidate flags set as true. + * Disregard the return value of the read. + */ + if (mmu_hw[i].insert_read_before_invalidate) + readl(mmu_hw[i].base + REG_L1_PHYS); + + writel(0xffffffff, mmu_hw[i].base + REG_TLB_INVALIDATE); + /* + * The TLB invalidation is a "single cycle" (IOMMU clock cycles) + * When the actual MMIO write reaches the IPU6 TLB Invalidate + * register, wmb() will force the TLB invalidate out if the CPU + * attempts to update the IOMMU page table (or sooner). + */ + wmb(); + } + spin_unlock_irqrestore(&mmu->ready_lock, flags); +} + +static int __ipu6_mmu_hw_init(struct ipu6_mmu *mmu) +{ + struct ipu6_mmu_info *mmu_info = mmu->dmap->mmu_info; + struct ipu6_mmu_hw *mmu_hw = mmu->ipu6_mmu_hw; + + /* Initialise the each MMU HW block */ + for (unsigned int i = 0; i < mmu->nr_mmus; i++) { + unsigned int j; + u16 block_addr; + + /* Write page table address per MMU */ + writel((phys_addr_t)mmu_info->l1_pt_dma, + mmu_hw[i].base + REG_L1_PHYS); + + /* Set info bits per MMU */ + writel(mmu_hw[i].info_bits, mmu_hw[i].base + REG_INFO); + + /* Configure MMU TLB stream configuration for L1 */ + for (j = 0, block_addr = 0; j < mmu_hw[i].nr_l1streams; + block_addr += mmu_hw[i].l1_block_sz[j], j++) { + if (block_addr > IPU6_MAX_LI_BLOCK_ADDR) { + dev_err(mmu->dev, "invalid L1 configuration\n"); + return -EINVAL; + } + + /* Write block start address for each streams */ + writel(block_addr, mmu_hw[i].base + + mmu_hw[i].l1_stream_id_reg_offset + 4 * j); + } + + /* Configure MMU TLB stream configuration for L2 */ + for (j = 0, block_addr = 0; j < mmu_hw[i].nr_l2streams; + block_addr += mmu_hw[i].l2_block_sz[j], j++) { + if (block_addr > IPU6_MAX_L2_BLOCK_ADDR) { + dev_err(mmu->dev, "invalid L2 configuration\n"); + return -EINVAL; + } + + writel(block_addr, mmu_hw[i].base + + mmu_hw[i].l2_stream_id_reg_offset + 4 * j); + } + } + + return 0; +} + +static int __ipu6_mmu_init_hw_data(struct ipu6_mmu *mmu, struct device *dev, + void __iomem *base) +{ + const struct ipu6_mmu_hwdata *lookup; + struct ipu6_mmu_hw *mmu_hw, *src; + unsigned int i, nr_mmus; + + if (mmu->mmid >= IPU_SUBSYS_NUM) + return -EINVAL; + + lookup = &ipu6_mmu_hwdata_lookup[mmu->mmid]; + src = lookup->hwdata; + nr_mmus = lookup->nr_mmus; + + mmu_hw = devm_kcalloc(dev, nr_mmus, sizeof(*mmu_hw), GFP_KERNEL); + if (!mmu_hw) + return -ENOMEM; + + for (i = 0; i < nr_mmus; i++) { + if (src[i].nr_l1streams > IPU6_MMU_MAX_TLB_L1_STREAMS || + src[i].nr_l2streams > IPU6_MMU_MAX_TLB_L2_STREAMS) + return -EINVAL; + + mmu_hw[i] = src[i]; + mmu_hw[i].base = base + src[i].offset; + } + + mmu->nr_mmus = nr_mmus; + mmu->ipu6_mmu_hw = mmu_hw; + + return 0; +} + +const struct ipu6_mmu_hw_ops ipu6_mmu_ops = { + .init_hw_data = __ipu6_mmu_init_hw_data, + .hw_init = __ipu6_mmu_hw_init, + .tlb_invalidate = __ipu6_tlb_invalidate, +}; diff --git a/drivers/media/pci/intel/ipu6/ipu6-mmu.c b/drivers/media/pci/intel/ipu6/ipu6-mmu.c index 0e6dca36b6ba..243d438786ba 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-mmu.c +++ b/drivers/media/pci/intel/ipu6/ipu6-mmu.c @@ -5,7 +5,6 @@ #include <asm/barrier.h> #include <linux/align.h> -#include <linux/atomic.h> #include <linux/bitops.h> #include <linux/bits.h> #include <linux/bug.h> @@ -13,10 +12,8 @@ #include <linux/dma-mapping.h> #include <linux/err.h> #include <linux/gfp.h> -#include <linux/io.h> #include <linux/iova.h> #include <linux/math.h> -#include <linux/minmax.h> #include <linux/mm.h> #include <linux/pfn.h> #include <linux/slab.h> @@ -51,42 +48,6 @@ #define TBL_PHYS_ADDR(a) ((phys_addr_t)(a) << ISP_PADDR_SHIFT) -static void tlb_invalidate(struct ipu6_mmu *mmu) -{ - unsigned long flags; - unsigned int i; - - spin_lock_irqsave(&mmu->ready_lock, flags); - if (!mmu->ready) { - spin_unlock_irqrestore(&mmu->ready_lock, flags); - return; - } - - for (i = 0; i < mmu->nr_mmus; i++) { - /* - * To avoid the HW bug induced dead lock in some of the IPU6 - * MMUs on successive invalidate calls, we need to first do a - * read to the page table base before writing the invalidate - * register. MMUs which need to implement this WA, will have - * the insert_read_before_invalidate flags set as true. - * Disregard the return value of the read. - */ - if (mmu->mmu_hw[i].insert_read_before_invalidate) - readl(mmu->mmu_hw[i].base + REG_L1_PHYS); - - writel(0xffffffff, mmu->mmu_hw[i].base + - REG_TLB_INVALIDATE); - /* - * The TLB invalidation is a "single cycle" (IOMMU clock cycles) - * When the actual MMIO write reaches the IPU6 TLB Invalidate - * register, wmb() will force the TLB invalidate out if the CPU - * attempts to update the IOMMU page table (or sooner). - */ - wmb(); - } - spin_unlock_irqrestore(&mmu->ready_lock, flags); -} - #ifdef DEBUG static void page_table_dump(struct ipu6_mmu_info *mmu_info) { @@ -472,55 +433,14 @@ out_free_iova: int ipu6_mmu_hw_init(struct ipu6_mmu *mmu) { - struct ipu6_mmu_info *mmu_info; unsigned long flags; - unsigned int i; - - mmu_info = mmu->dmap->mmu_info; - - /* Initialise the each MMU HW block */ - for (i = 0; i < mmu->nr_mmus; i++) { - struct ipu6_mmu_hw *mmu_hw = &mmu->mmu_hw[i]; - unsigned int j; - u16 block_addr; - - /* Write page table address per MMU */ - writel((phys_addr_t)mmu_info->l1_pt_dma, - mmu->mmu_hw[i].base + REG_L1_PHYS); - - /* Set info bits per MMU */ - writel(mmu->mmu_hw[i].info_bits, - mmu->mmu_hw[i].base + REG_INFO); - - /* Configure MMU TLB stream configuration for L1 */ - for (j = 0, block_addr = 0; j < mmu_hw->nr_l1streams; - block_addr += mmu->mmu_hw[i].l1_block_sz[j], j++) { - if (block_addr > IPU6_MAX_LI_BLOCK_ADDR) { - dev_err(mmu->dev, "invalid L1 configuration\n"); - return -EINVAL; - } - - /* Write block start address for each streams */ - writel(block_addr, mmu_hw->base + - mmu_hw->l1_stream_id_reg_offset + 4 * j); - } - - /* Configure MMU TLB stream configuration for L2 */ - for (j = 0, block_addr = 0; j < mmu_hw->nr_l2streams; - block_addr += mmu->mmu_hw[i].l2_block_sz[j], j++) { - if (block_addr > IPU6_MAX_L2_BLOCK_ADDR) { - dev_err(mmu->dev, "invalid L2 configuration\n"); - return -EINVAL; - } + int ret; - writel(block_addr, mmu_hw->base + - mmu_hw->l2_stream_id_reg_offset + 4 * j); - } - } + ret = mmu->ops->hw_init(mmu); + if (ret) + return ret; if (!mmu->trash_page) { - int ret; - mmu->trash_page = alloc_page(GFP_KERNEL); if (!mmu->trash_page) { dev_err(mmu->dev, "insufficient memory for trash buffer\n"); @@ -553,7 +473,12 @@ static struct ipu6_mmu_info *ipu6_mmu_alloc(struct ipu6_device *isp) if (!mmu_info) return NULL; - mmu_info->aperture_start = 0; + if (IS_IPU7(isp)) + mmu_info->aperture_start = isp->secure_mode ? + IPU7_FW_CODE_REGION_END : IPU7_FW_CODE_REGION_START; + else + mmu_info->aperture_start = 0; + mmu_info->aperture_end = (dma_addr_t)DMA_BIT_MASK(isp->secure_mode ? IPU6_MMU_ADDR_BITS : @@ -612,6 +537,7 @@ EXPORT_SYMBOL_NS_GPL(ipu6_mmu_hw_cleanup, "INTEL_IPU6"); static struct ipu6_dma_mapping *alloc_dma_mapping(struct ipu6_device *isp) { struct ipu6_dma_mapping *dmap; + unsigned long base_pfn; dmap = kzalloc_obj(*dmap); if (!dmap) @@ -623,7 +549,9 @@ static struct ipu6_dma_mapping *alloc_dma_mapping(struct ipu6_device *isp) return NULL; } - init_iova_domain(&dmap->iovad, SZ_4K, 1); + base_pfn = max_t(unsigned long, 1, + PFN_DOWN(dmap->mmu_info->aperture_start)); + init_iova_domain(&dmap->iovad, SZ_4K, base_pfn); dmap->mmu_info->dmap = dmap; dev_dbg(&isp->pdev->dev, "alloc mapping\n"); @@ -746,45 +674,26 @@ static void ipu6_mmu_destroy(struct ipu6_mmu *mmu) } struct ipu6_mmu *ipu6_mmu_init(struct device *dev, - void __iomem *base, int mmid, - const struct ipu6_hw_variants *hw) + void __iomem *base, int mmid) { struct ipu6_device *isp = pci_get_drvdata(to_pci_dev(dev)); - struct ipu6_mmu_pdata *pdata; struct ipu6_mmu *mmu; - unsigned int i; - - if (hw->nr_mmus > IPU6_MMU_MAX_DEVICES) - return ERR_PTR(-EINVAL); - - pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); - if (!pdata) - return ERR_PTR(-ENOMEM); - - for (i = 0; i < hw->nr_mmus; i++) { - struct ipu6_mmu_hw *pdata_mmu = &pdata->mmu_hw[i]; - const struct ipu6_mmu_hw *src_mmu = &hw->mmu_hw[i]; - - if (src_mmu->nr_l1streams > IPU6_MMU_MAX_TLB_L1_STREAMS || - src_mmu->nr_l2streams > IPU6_MMU_MAX_TLB_L2_STREAMS) - return ERR_PTR(-EINVAL); - - *pdata_mmu = *src_mmu; - pdata_mmu->base = base + src_mmu->offset; - } + int ret; mmu = devm_kzalloc(dev, sizeof(*mmu), GFP_KERNEL); if (!mmu) return ERR_PTR(-ENOMEM); + mmu->ops = IS_IPU7(isp) ? &ipu7_mmu_ops : &ipu6_mmu_ops; mmu->mmid = mmid; - mmu->mmu_hw = pdata->mmu_hw; - mmu->nr_mmus = hw->nr_mmus; - mmu->tlb_invalidate = tlb_invalidate; mmu->ready = false; INIT_LIST_HEAD(&mmu->vma_list); spin_lock_init(&mmu->ready_lock); + ret = mmu->ops->init_hw_data(mmu, dev, base); + if (ret) + return ERR_PTR(ret); + mmu->dmap = alloc_dma_mapping(isp); if (!mmu->dmap) { dev_err(dev, "can't alloc dma mapping\n"); diff --git a/drivers/media/pci/intel/ipu6/ipu6-mmu.h b/drivers/media/pci/intel/ipu6/ipu6-mmu.h index 8e40b4a66d7d..44880478d242 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-mmu.h +++ b/drivers/media/pci/intel/ipu6/ipu6-mmu.h @@ -1,19 +1,17 @@ /* SPDX-License-Identifier: GPL-2.0-only */ -/* Copyright (C) 2013--2024 Intel Corporation */ +/* Copyright (C) 2013--2026 Intel Corporation */ #ifndef IPU6_MMU_H #define IPU6_MMU_H -#define ISYS_MMID 1 -#define PSYS_MMID 0 - #include <linux/list.h> #include <linux/spinlock_types.h> #include <linux/types.h> +#include "ipu7-mmu-hw.h" + struct device; struct page; -struct ipu6_hw_variants; struct ipu6_mmu_info { struct device *dev; @@ -35,10 +33,150 @@ struct ipu6_mmu_info { struct ipu6_dma_mapping *dmap; }; +/* + * MMU Invalidation HW bug workaround by ZLW mechanism + * + * Old IPU6 MMUV2 has a bug in the invalidation mechanism which might result in + * wrong translation or replication of the translation. This will cause data + * corruption. So we cannot directly use the MMU V2 invalidation registers + * to invalidate the MMU. Instead, whenever an invalidate is called, we need to + * clear the TLB by evicting all the valid translations by filling it with trash + * buffer (which is guaranteed not to be used by any other processes). ZLW is + * used to fill the L1 and L2 caches with the trash buffer translations. ZLW + * or Zero length write, is pre-fetch mechanism to pre-fetch the pages in + * advance to the L1 and L2 caches without triggering any memory operations. + * + * In MMU V2, L1 -> 16 streams and 64 blocks, maximum 16 blocks per stream + * One L1 block has 16 entries, hence points to 16 * 4K pages + * L2 -> 16 streams and 32 blocks. 2 blocks per streams + * One L2 block maps to 1024 L1 entries, hence points to 4MB address range + * 2 blocks per L2 stream means, 1 stream points to 8MB range + * + * As we need to clear the caches and 8MB being the biggest cache size, we need + * to have trash buffer which points to 8MB address range. As these trash + * buffers are not used for any memory transactions, we need only the least + * amount of physical memory. So we reserve 8MB IOVA address range but only + * one page is reserved from physical memory. Each of this 8MB IOVA address + * range is then mapped to the same physical memory page. + */ +/* One L2 entry maps 1024 L1 entries and one L1 entry per page */ +#define IPU6_MMUV2_L2_RANGE (1024 * PAGE_SIZE) +/* Max L2 blocks per stream */ +#define IPU6_MMUV2_MAX_L2_BLOCKS 2 +/* Max L1 blocks per stream */ +#define IPU6_MMUV2_MAX_L1_BLOCKS 16 +#define IPU6_MMUV2_TRASH_RANGE (IPU6_MMUV2_L2_RANGE * IPU6_MMUV2_MAX_L2_BLOCKS) +/* Entries per L1 block */ +#define MMUV2_ENTRIES_PER_L1_BLOCK 16 +#define MMUV2_TRASH_L1_BLOCK_OFFSET (MMUV2_ENTRIES_PER_L1_BLOCK * PAGE_SIZE) +#define MMUV2_TRASH_L2_BLOCK_OFFSET IPU6_MMUV2_L2_RANGE + +/* + * In some of the IPU6 MMUs, there is provision to configure L1 and L2 page + * table caches. Both these L1 and L2 caches are divided into multiple sections + * called streams. There is maximum 16 streams for both caches. Each of these + * sections are subdivided into multiple blocks. When nr_l1streams = 0 and + * nr_l2streams = 0, means the MMU is of type MMU_V1 and do not support + * L1/L2 page table caches. + * + * L1 stream per block sizes are configurable and varies per usecase. + * L2 has constant block sizes - 2 blocks per stream. + * + * MMU1 support pre-fetching of the pages to have less cache lookup misses. To + * enable the pre-fetching, MMU1 AT (Address Translator) device registers + * need to be configured. + * + * There are four types of memory accesses which requires ZLW configuration. + * ZLW(Zero Length Write) is a mechanism to enable VT-d pre-fetching on IOMMU. + * + * 1. Sequential Access or 1D mode + * Set ZLW_EN -> 1 + * set ZLW_PAGE_CROSS_1D -> 1 + * Set ZLW_N to "N" pages so that ZLW will be inserte N pages ahead where + * N is pre-defined and hardcoded in the platform data + * Set ZLW_2D -> 0 + * + * 2. ZLW 2D mode + * Set ZLW_EN -> 1 + * set ZLW_PAGE_CROSS_1D -> 1, + * Set ZLW_N -> 0 + * Set ZLW_2D -> 1 + * + * 3. ZLW Enable (no 1D or 2D mode) + * Set ZLW_EN -> 1 + * set ZLW_PAGE_CROSS_1D -> 0, + * Set ZLW_N -> 0 + * Set ZLW_2D -> 0 + * + * 4. ZLW disable + * Set ZLW_EN -> 0 + * set ZLW_PAGE_CROSS_1D -> 0, + * Set ZLW_N -> 0 + * Set ZLW_2D -> 0 + * + * To configure the ZLW for the above memory access, four registers are + * available. Hence to track these four settings, we have the following entries + * in the struct ipu6_mmu_hw. Each of these entries are per stream and + * available only for the L1 streams. + * + * a. l1_zlw_en -> To track zlw enabled per stream (ZLW_EN) + * b. l1_zlw_1d_mode -> Track 1D mode per stream. ZLW inserted at page boundary + * c. l1_ins_zlw_ahead_pages -> to track how advance the ZLW need to be inserted + * Insert ZLW request N pages ahead address. + * d. l1_zlw_2d_mode -> To track 2D mode per stream (ZLW_2D) + * + * + * Currently L1/L2 streams, blocks, AT ZLW configurations etc. are pre-defined + * as per the usecase specific calculations. Any change to this pre-defined + * table has to happen in sync with IPU6 FW. + */ + +struct ipu6_mmu_hw { + union { + unsigned long offset; + void __iomem *base; + }; + u32 info_bits; + u8 nr_l1streams; + /* + * L1 has variable blocks per stream - total of 64 blocks and maximum of + * 16 blocks per stream. Configurable by using the block start address + * per stream. Block start address is calculated from the block size + */ + u8 l1_block_sz[IPU6_MMU_MAX_TLB_L1_STREAMS]; + /* Is ZLW is enabled in each stream */ + bool l1_zlw_en[IPU6_MMU_MAX_TLB_L1_STREAMS]; + bool l1_zlw_1d_mode[IPU6_MMU_MAX_TLB_L1_STREAMS]; + u8 l1_ins_zlw_ahead_pages[IPU6_MMU_MAX_TLB_L1_STREAMS]; + bool l1_zlw_2d_mode[IPU6_MMU_MAX_TLB_L1_STREAMS]; + + u32 l1_stream_id_reg_offset; + u32 l2_stream_id_reg_offset; + + u8 nr_l2streams; + /* + * L2 has fixed 2 blocks per stream. Block address is calculated + * from the block size + */ + u8 l2_block_sz[IPU6_MMU_MAX_TLB_L2_STREAMS]; + /* flag to track if WA is needed for successive invalidate HW bug */ + bool insert_read_before_invalidate; +}; + +struct ipu6_mmu_hw_ops { + int (*init_hw_data)(struct ipu6_mmu *mmu, struct device *dev, + void __iomem *base); + int (*hw_init)(struct ipu6_mmu *mmu); + void (*tlb_invalidate)(struct ipu6_mmu *mmu); +}; + struct ipu6_mmu { struct list_head node; - struct ipu6_mmu_hw *mmu_hw; + union { + struct ipu6_mmu_hw *ipu6_mmu_hw; + struct ipu7_mmu_hw *ipu7_mmu_hw; + }; unsigned int nr_mmus; unsigned int mmid; @@ -55,12 +193,14 @@ struct ipu6_mmu { bool ready; spinlock_t ready_lock; /* Serialize access to bool ready */ - void (*tlb_invalidate)(struct ipu6_mmu *mmu); + const struct ipu6_mmu_hw_ops *ops; }; +extern const struct ipu6_mmu_hw_ops ipu6_mmu_ops; +extern const struct ipu6_mmu_hw_ops ipu7_mmu_ops; + struct ipu6_mmu *ipu6_mmu_init(struct device *dev, - void __iomem *base, int mmid, - const struct ipu6_hw_variants *hw); + void __iomem *base, int mmid); void ipu6_mmu_cleanup(struct ipu6_mmu *mmu); int ipu6_mmu_hw_init(struct ipu6_mmu *mmu); void ipu6_mmu_hw_cleanup(struct ipu6_mmu *mmu); diff --git a/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h b/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h index efd65e494c16..57e661cc2177 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h +++ b/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h @@ -212,7 +212,7 @@ enum { #define BUTTRESS_TSW_CTL_SOFT_RESET BIT(8) #define BUTTRESS_REG_TSC_LO 0x164 -#define BUTTRESS_REG_TSC_HI 0x168 +#define BUTTRESS_TSC_HI_OFFSET 4 #define BUTTRESS_IRQS (BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING | \ BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE | \ @@ -221,4 +221,113 @@ enum { #define BUTTRESS_EVENT (BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING | \ BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE | \ BUTTRESS_ISR_SAI_VIOLATION) + +/* IPU7 */ + +/* IPU7 buttress register offsets */ +#define IPU7_BUTTRESS_REG_SKU 0x3030 +#define IPU7_BUTTRESS_REG_IRQ_STATUS 0x2000 +#define IPU7_BUTTRESS_REG_IRQ_ENABLE 0x2008 +#define IPU7_BUTTRESS_REG_IRQ_CLEAR 0x200c +#define IPU7_BUTTRESS_REG_IRQ_MASK 0x2010 +#define IPU7_BUTTRESS_REG_TSC_CMD 0x2014 +#define IPU7_BUTTRESS_REG_TSC_CTL 0x2018 +#define IPU7_BUTTRESS_REG_TSC_LO 0x201c +#define IPU7_BUTTRESS_REG_PB_TIMESTAMP_LO 0x2030 +#define IPU7_BUTTRESS_REG_PB_TIMESTAMP_VALID 0x2038 +#define IPU7_BUTTRESS_REG_IS_WORKPOINT_REQ 0x2104 +#define IPU7_BUTTRESS_REG_PS_WORKPOINT_REQ 0x2100 +#define IPU7_BUTTRESS_REG_IDLE_WDT 0x218c +#define IPU7_BUTTRESS_REG_ISYS_UCX_CTRL_STATUS 0x2200 +#define IPU7_BUTTRESS_REG_ISYS_UCX_START_ADDR 0x2204 +#define IPU7_BUTTRESS_REG_PSYS_UCX_CTRL_STATUS 0x2208 +#define IPU7_BUTTRESS_REG_PSYS_UCX_START_ADDR 0x220c +#define IPU7_BUTTRESS_REG_SECURITY_CTL 0x2318 +#define IPU7_BUTTRESS_REG_FW_RESET_CTL 0x2334 +#define IPU7_BUTTRESS_REG_FW_SOURCE_SIZE 0x2338 +#define IPU7_BUTTRESS_REG_FW_SOURCE_BASE 0x233c +#define IPU7_BUTTRESS_REG_CSE2IUDB0 0x2500 +#define IPU7_BUTTRESS_REG_CSE2IUDATA0 0x2504 +#define IPU7_BUTTRESS_REG_CSE2IUCSR 0x2508 +#define IPU7_BUTTRESS_REG_IU2CSEDB0 0x250c +#define IPU7_BUTTRESS_REG_IU2CSEDATA0 0x2510 +#define IPU7_BUTTRESS_REG_IU2CSECSR 0x2514 +#define IPU7_BUTTRESS_REG_CG_CTRL_BITS 0x3014 +#define IPU7_BUTTRESS_REG_FW_BOOT_PARAMS0 0x4000 +#define IPU7_BUTTRESS_REG_FW_BOOT_PARAMS7 0x401c +#define IPU7_BUTTRESS_REG_PWR_STATUS 0x2114 + +#define IPU7_BUTTRESS_IRQ_IPC_EXEC_DONE_BY_CSE BIT(0) +#define IPU7_BUTTRESS_IRQ_IPC_FROM_CSE_IS_WAITING BIT(1) +#define IPU7_BUTTRESS_IRQ_CSE_CSR_SET BIT(2) +#define IPU7_BUTTRESS_IRQ_SAI_VIOLATION BIT(4) +#define IPU7_BUTTRESS_IRQ_IS_IRQ BIT(30) +#define IPU7_BUTTRESS_IRQ_PS_IRQ BIT(31) + +#define IPU7_BUTTRESS_IRQS (IPU7_BUTTRESS_IRQ_IS_IRQ | \ + IPU7_BUTTRESS_IRQ_PS_IRQ | \ + IPU7_BUTTRESS_IRQ_IPC_FROM_CSE_IS_WAITING | \ + IPU7_BUTTRESS_IRQ_CSE_CSR_SET | \ + IPU7_BUTTRESS_IRQ_IPC_EXEC_DONE_BY_CSE) + +/* LNL SW workaround for PS PD hang */ +#define IPU7_BUTTRESS_CG_CTRL_PS_FSM_CG BIT(3) + +/* P-unit Bridge (PB) BAR registers */ +#define IPU7_PB_INTERRUPT_STATUS 0x0 + +/* Frequency control encoding */ +#define IPU7_FREQ_CTL_CDYN 0x80 +#define IPU7_FREQ_CTL_CDYN_SHIFT 8 +#define IPU7_BUTTRESS_IS_FREQ_CTL_RATIO_MASK GENMASK(7, 0) +#define IPU7_IS_FREQ_CTL_DEFAULT_RATIO 0x1b +#define IPU7_PS_FREQ_CTL_DEFAULT_RATIO 0x14 + +/* D2D power control */ +#define IPU7_BUTTRESS_REG_D2D_CTL 0x21d4 +#define IPU7_BUTTRESS_D2D_PWR_EN BIT(0) +#define IPU7_BUTTRESS_D2D_PWR_ACK BIT(4) + +#define IPU7_BUTTRESS_PWR_STATE_IS_PWR_SHIFT 0 +#define IPU7_BUTTRESS_PWR_STATE_IS_PWR_MASK (0x3U << 0) +#define IPU7_BUTTRESS_PWR_STATE_PS_PWR_SHIFT 4 +#define IPU7_BUTTRESS_PWR_STATE_PS_PWR_MASK (0x3U << 4) + +/* NDE */ +#define IPU7_BUTTRESS_REG_NDE_CONTROL 0x21a4 +#define IPU7_NDE_VAL_MASK GENMASK(9, 0) +#define IPU7_NDE_SCALE_MASK GENMASK(12, 10) +#define IPU7_NDE_VALID_MASK BIT(13) +#define IPU7_NDE_RESVEC_MASK GENMASK(19, 16) +#define IPU7_NDE_VAL_ACTIVE 48 +#define IPU7_NDE_SCALE_ACTIVE 2 +#define IPU7_NDE_VALID_ACTIVE 1 +#define IPU7_NDE_VAL_DEFAULT 1023 +#define IPU7_NDE_SCALE_DEFAULT 2 +#define IPU7_NDE_VALID_DEFAULT 0 +#define IPU7_NDE_RESVEC 0xe + +/* IS UCX control */ +#define IPU7_UCX_CTL_RESET BIT(0) +#define IPU7_UCX_CTL_RUN BIT(1) +#define IPU7_UCX_CTL_WAKEUP BIT(2) + +#define IPU7_BUTTRESS_REG_SLEEP_LEVEL_CFG 0x21b0 +#define IPU7_BUTTRESS_REG_SLEEP_LEVEL_STS 0x21b4 +#define IPU7_BUTTRESS_OVERRIDE_IS_CLK BIT(1) +#define IPU7_BUTTRESS_OWN_ACK_IS_CLK BIT(9) +#define IPU7_BUTTRESS_OVERRIDE_PS_CLK BIT(2) +#define IPU7_BUTTRESS_OWN_ACK_PS_CLK BIT(10) + +/* PB registers */ +#define IPU7_GLOBAL_INTERRUPT_MASK 0x8 +#define IPU7_TLBID_HASH_ENABLE_31_0 0x30 +#define IPU7_TLBID_HASH_ENABLE_63_32 0x34 +#define IPU7_TLBID_HASH_ENABLE_95_64 0x38 +#define IPU7_TLBID_HASH_ENABLE_127_96 0x3c +#define IPU7_BAR2_MISC_CONFIG 0x64 + +/* IPU7.5 */ +#define IPU7_BUTTRESS_SEL_PB_TIMESTAMP BIT(9) + #endif /* IPU6_PLATFORM_BUTTRESS_REGS_H */ diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c index 5449a2006bcc..43d951735f72 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.c +++ b/drivers/media/pci/intel/ipu6/ipu6.c @@ -19,6 +19,7 @@ #include <linux/scatterlist.h> #include <linux/slab.h> #include <linux/types.h> +#include <linux/vmalloc.h> #include <media/ipu-bridge.h> #include <media/ipu6-pci-table.h> @@ -27,13 +28,20 @@ #include "ipu6-bus.h" #include "ipu6-buttress.h" #include "ipu6-cpd.h" +#include "ipu6-dma.h" #include "ipu6-isys.h" #include "ipu6-mmu.h" #include "ipu6-platform-buttress-regs.h" #include "ipu6-platform-isys-csi2-reg.h" #include "ipu6-platform-regs.h" +#include "ipu7-isys-csi2-regs.h" #define IPU6_PCI_BAR 0 +#define IPU7_PCI_PBBAR 4 + +static int force_no_probe_ipu7 = !IS_BUILTIN(CONFIG_VIDEO_INTEL_IPU6_IPU7); +module_param(force_no_probe_ipu7, int, 0644); +MODULE_PARM_DESC(force_no_probe_ipu7, "Don't probe ipu7 and ipu7.5 devices"); struct ipu6_cell_program { u32 magic_number; @@ -73,54 +81,6 @@ struct ipu6_cell_program { static struct ipu6_isys_internal_pdata isys_ipdata = { .hw_variant = { .offset = IPU6_UNIFIED_OFFSET, - .nr_mmus = 3, - .mmu_hw = { - { - .offset = IPU6_ISYS_IOMMU0_OFFSET, - .info_bits = IPU6_INFO_REQUEST_DESTINATION_IOSF, - .nr_l1streams = 16, - .l1_block_sz = { - 3, 8, 2, 2, 2, 2, 2, 2, 1, 1, - 1, 1, 1, 1, 1, 1 - }, - .nr_l2streams = 16, - .l2_block_sz = { - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2 - }, - .insert_read_before_invalidate = false, - .l1_stream_id_reg_offset = - IPU6_MMU_L1_STREAM_ID_REG_OFFSET, - .l2_stream_id_reg_offset = - IPU6_MMU_L2_STREAM_ID_REG_OFFSET, - }, - { - .offset = IPU6_ISYS_IOMMU1_OFFSET, - .info_bits = 0, - .nr_l1streams = 16, - .l1_block_sz = { - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 1, 1, 4 - }, - .nr_l2streams = 16, - .l2_block_sz = { - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2 - }, - .insert_read_before_invalidate = false, - .l1_stream_id_reg_offset = - IPU6_MMU_L1_STREAM_ID_REG_OFFSET, - .l2_stream_id_reg_offset = - IPU6_MMU_L2_STREAM_ID_REG_OFFSET, - }, - { - .offset = IPU6_ISYS_IOMMUI_OFFSET, - .info_bits = 0, - .nr_l1streams = 0, - .nr_l2streams = 0, - .insert_read_before_invalidate = false, - }, - }, .cdc_fifos = 3, .cdc_fifo_threshold = {6, 8, 2}, .dmem_offset = IPU6_ISYS_DMEM_OFFSET, @@ -132,83 +92,12 @@ static struct ipu6_isys_internal_pdata isys_ipdata = { static struct ipu6_psys_internal_pdata psys_ipdata = { .hw_variant = { .offset = IPU6_UNIFIED_OFFSET, - .nr_mmus = 4, - .mmu_hw = { - { - .offset = IPU6_PSYS_IOMMU0_OFFSET, - .info_bits = - IPU6_INFO_REQUEST_DESTINATION_IOSF, - .nr_l1streams = 16, - .l1_block_sz = { - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2 - }, - .nr_l2streams = 16, - .l2_block_sz = { - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2 - }, - .insert_read_before_invalidate = false, - .l1_stream_id_reg_offset = - IPU6_MMU_L1_STREAM_ID_REG_OFFSET, - .l2_stream_id_reg_offset = - IPU6_MMU_L2_STREAM_ID_REG_OFFSET, - }, - { - .offset = IPU6_PSYS_IOMMU1_OFFSET, - .info_bits = 0, - .nr_l1streams = 32, - .l1_block_sz = { - 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 10, - 5, 4, 14, 6, 4, 14, 6, 4, 8, - 4, 2, 1, 1, 1, 1, 14 - }, - .nr_l2streams = 32, - .l2_block_sz = { - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2 - }, - .insert_read_before_invalidate = false, - .l1_stream_id_reg_offset = - IPU6_MMU_L1_STREAM_ID_REG_OFFSET, - .l2_stream_id_reg_offset = - IPU6_PSYS_MMU1W_L2_STREAM_ID_REG_OFFSET, - }, - { - .offset = IPU6_PSYS_IOMMU1R_OFFSET, - .info_bits = 0, - .nr_l1streams = 16, - .l1_block_sz = { - 1, 4, 4, 4, 4, 16, 8, 4, 32, - 16, 16, 2, 2, 2, 1, 12 - }, - .nr_l2streams = 16, - .l2_block_sz = { - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2 - }, - .insert_read_before_invalidate = false, - .l1_stream_id_reg_offset = - IPU6_MMU_L1_STREAM_ID_REG_OFFSET, - .l2_stream_id_reg_offset = - IPU6_MMU_L2_STREAM_ID_REG_OFFSET, - }, - { - .offset = IPU6_PSYS_IOMMUI_OFFSET, - .info_bits = 0, - .nr_l1streams = 0, - .nr_l2streams = 0, - .insert_read_before_invalidate = false, - }, - }, .dmem_offset = IPU6_PSYS_DMEM_OFFSET, }, }; -static const struct ipu6_buttress_ctrl isys_buttress_ctrl = { +static const struct ipu6_buttress_ctrl ipu6_isys_buttress_ctrl = { + .subsys_id = IPU_ISYS, .ratio = IPU6_IS_FREQ_CTL_DEFAULT_RATIO, .qos_floor = IPU6_IS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO, .freq_ctl = IPU6_BUTTRESS_REG_IS_FREQ_CTL, @@ -218,7 +107,8 @@ static const struct ipu6_buttress_ctrl isys_buttress_ctrl = { .pwr_sts_off = IPU6_BUTTRESS_PWR_STATE_DN_DONE, }; -static const struct ipu6_buttress_ctrl psys_buttress_ctrl = { +static const struct ipu6_buttress_ctrl ipu6_psys_buttress_ctrl = { + .subsys_id = IPU_PSYS, .ratio = IPU6_PS_FREQ_CTL_DEFAULT_RATIO, .qos_floor = IPU6_PS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO, .freq_ctl = IPU6_BUTTRESS_REG_PS_FREQ_CTL, @@ -228,6 +118,119 @@ static const struct ipu6_buttress_ctrl psys_buttress_ctrl = { .pwr_sts_off = IPU6_BUTTRESS_PWR_STATE_DN_DONE, }; +static const struct ipu6_buttress_ctrl ipu7_isys_buttress_ctrl = { + .subsys_id = IPU_ISYS, + .ratio = IPU7_IS_FREQ_CTL_DEFAULT_RATIO, + .qos_floor = 0, + .freq_ctl = IPU7_BUTTRESS_REG_IS_WORKPOINT_REQ, + .pwr_sts_shift = IPU7_BUTTRESS_PWR_STATE_IS_PWR_SHIFT, + .pwr_sts_mask = IPU7_BUTTRESS_PWR_STATE_IS_PWR_MASK, + .pwr_sts_on = IPU6_BUTTRESS_PWR_STATE_UP_DONE, + .pwr_sts_off = IPU6_BUTTRESS_PWR_STATE_DN_DONE, +}; + +static const struct ipu6_buttress_ctrl ipu7_psys_buttress_ctrl = { + .subsys_id = IPU_PSYS, + .ratio = IPU7_PS_FREQ_CTL_DEFAULT_RATIO, + .qos_floor = 0, + .freq_ctl = IPU7_BUTTRESS_REG_PS_WORKPOINT_REQ, + .pwr_sts_shift = IPU7_BUTTRESS_PWR_STATE_PS_PWR_SHIFT, + .pwr_sts_mask = IPU7_BUTTRESS_PWR_STATE_PS_PWR_MASK, + .pwr_sts_on = IPU6_BUTTRESS_PWR_STATE_UP_DONE, + .pwr_sts_off = IPU6_BUTTRESS_PWR_STATE_DN_DONE, +}; + +static const struct ipu6_buttress_registers ipu6_buttress_regs = { + /* Registers */ + .irq_status = BUTTRESS_REG_ISR_STATUS, + .irq_clear = BUTTRESS_REG_ISR_CLEAR, + .irq_enable = BUTTRESS_REG_ISR_ENABLE, + .pwr_status = BUTTRESS_REG_PWR_STATE, + .security_ctl = BUTTRESS_REG_SECURITY_CTL, + .fw_reset_ctl = BUTTRESS_REG_FW_RESET_CTL, + .fabric_cmd = BUTTRESS_REG_FABRIC_CMD, + .tsw_ctl = BUTTRESS_REG_TSW_CTL, + .tsc_lo = BUTTRESS_REG_TSC_LO, + .wdt = BUTTRESS_REG_WDT, + .btrs_ctrl = BUTTRESS_REG_BTRS_CTRL, + .csr_in = BUTTRESS_REG_CSE2IUCSR, + .csr_out = BUTTRESS_REG_IU2CSECSR, + .db0_in = BUTTRESS_REG_CSE2IUDB0, + .db0_out = BUTTRESS_REG_IU2CSEDB0, + .data0_in = BUTTRESS_REG_CSE2IUDATA0, + .data0_out = BUTTRESS_REG_IU2CSEDATA0, + .sku_id = BUTTRESS_REG_SKU, + + /* Bitmasks */ + .irq_is = BUTTRESS_ISR_IS_IRQ, + .irq_ps = BUTTRESS_ISR_PS_IRQ, + .irq_all = BUTTRESS_IRQS, + .irq_events = BUTTRESS_EVENT, + .irq_cse_ipc = BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING, + .irq_exec_done = BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE, + .irq_sai = BUTTRESS_ISR_SAI_VIOLATION, +}; + +static const struct ipu6_buttress_registers ipu7_buttress_regs = { + /* Registers */ + .irq_status = IPU7_BUTTRESS_REG_IRQ_STATUS, + .irq_clear = IPU7_BUTTRESS_REG_IRQ_CLEAR, + .irq_enable = IPU7_BUTTRESS_REG_IRQ_ENABLE, + .pwr_status = IPU7_BUTTRESS_REG_PWR_STATUS, + .security_ctl = IPU7_BUTTRESS_REG_SECURITY_CTL, + .fw_reset_ctl = IPU7_BUTTRESS_REG_FW_RESET_CTL, + .fabric_cmd = IPU7_BUTTRESS_REG_TSC_CMD, + .tsw_ctl = IPU7_BUTTRESS_REG_TSC_CTL, + .tsc_lo = IPU7_BUTTRESS_REG_TSC_LO, + .wdt = IPU7_BUTTRESS_REG_IDLE_WDT, + .csr_in = IPU7_BUTTRESS_REG_CSE2IUCSR, + .csr_out = IPU7_BUTTRESS_REG_IU2CSECSR, + .db0_in = IPU7_BUTTRESS_REG_CSE2IUDB0, + .db0_out = IPU7_BUTTRESS_REG_IU2CSEDB0, + .data0_in = IPU7_BUTTRESS_REG_CSE2IUDATA0, + .data0_out = IPU7_BUTTRESS_REG_IU2CSEDATA0, + .sku_id = IPU7_BUTTRESS_REG_SKU, + + /* Bitmasks */ + .irq_is = IPU7_BUTTRESS_IRQ_IS_IRQ, + .irq_ps = IPU7_BUTTRESS_IRQ_PS_IRQ, + .irq_all = IPU7_BUTTRESS_IRQS, + .irq_events = IPU7_BUTTRESS_IRQS, + .irq_cse_ipc = IPU7_BUTTRESS_IRQ_IPC_FROM_CSE_IS_WAITING, + .irq_exec_done = IPU7_BUTTRESS_IRQ_IPC_EXEC_DONE_BY_CSE, + .irq_sai = IPU7_BUTTRESS_IRQ_SAI_VIOLATION, +}; + +static const struct ipu6_buttress_registers ipu7p5_buttress_regs = { + /* Registers */ + .irq_status = IPU7_BUTTRESS_REG_IRQ_STATUS, + .irq_clear = IPU7_BUTTRESS_REG_IRQ_CLEAR, + .irq_enable = IPU7_BUTTRESS_REG_IRQ_ENABLE, + .pwr_status = IPU7_BUTTRESS_REG_PWR_STATUS, + .security_ctl = IPU7_BUTTRESS_REG_SECURITY_CTL, + .fw_reset_ctl = IPU7_BUTTRESS_REG_FW_RESET_CTL, + .fabric_cmd = IPU7_BUTTRESS_REG_TSC_CMD, + .tsw_ctl = IPU7_BUTTRESS_REG_TSC_CTL, + .tsc_lo = IPU7_BUTTRESS_REG_PB_TIMESTAMP_LO, + .wdt = IPU7_BUTTRESS_REG_IDLE_WDT, + .csr_in = IPU7_BUTTRESS_REG_CSE2IUCSR, + .csr_out = IPU7_BUTTRESS_REG_IU2CSECSR, + .db0_in = IPU7_BUTTRESS_REG_CSE2IUDB0, + .db0_out = IPU7_BUTTRESS_REG_IU2CSEDB0, + .data0_in = IPU7_BUTTRESS_REG_CSE2IUDATA0, + .data0_out = IPU7_BUTTRESS_REG_IU2CSEDATA0, + .sku_id = IPU7_BUTTRESS_REG_SKU, + + /* Bitmasks */ + .irq_is = IPU7_BUTTRESS_IRQ_IS_IRQ, + .irq_ps = IPU7_BUTTRESS_IRQ_PS_IRQ, + .irq_all = IPU7_BUTTRESS_IRQS, + .irq_events = IPU7_BUTTRESS_IRQS, + .irq_cse_ipc = IPU7_BUTTRESS_IRQ_IPC_FROM_CSE_IS_WAITING, + .irq_exec_done = IPU7_BUTTRESS_IRQ_IPC_EXEC_DONE_BY_CSE, + .irq_sai = IPU7_BUTTRESS_IRQ_SAI_VIOLATION, +}; + static void ipu6_pkg_dir_configure_spc(struct ipu6_device *isp, const struct ipu6_hw_variants *hw_variant, @@ -267,10 +270,16 @@ void ipu6_configure_spc(struct ipu6_device *isp, int pkg_dir_idx, void __iomem *base, u64 *pkg_dir, dma_addr_t pkg_dir_dma_addr) { - void __iomem *dmem_base = base + hw_variant->dmem_offset; - void __iomem *spc_regs_base = base + hw_variant->spc_offset; + void __iomem *dmem_base; + void __iomem *spc_regs_base; u32 val; + if (IS_IPU7(isp)) + return; + + dmem_base = base + hw_variant->dmem_offset; + spc_regs_base = base + hw_variant->spc_offset; + val = readl(spc_regs_base + IPU6_PSYS_REG_SPC_STATUS_CTRL); val |= IPU6_PSYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE; writel(val, spc_regs_base + IPU6_PSYS_REG_SPC_STATUS_CTRL); @@ -290,8 +299,6 @@ EXPORT_SYMBOL_NS_GPL(ipu6_configure_spc, "INTEL_IPU6"); static void ipu6_internal_pdata_init(struct ipu6_device *isp) { - u8 hw_ver = isp->hw_ver; - isys_ipdata.num_parallel_streams = IPU6_ISYS_NUM_STREAMS; isys_ipdata.sram_gran_shift = IPU6_SRAM_GRANULARITY_SHIFT; isys_ipdata.sram_gran_size = IPU6_SRAM_GRANULARITY_SIZE; @@ -314,19 +321,19 @@ static void ipu6_internal_pdata_init(struct ipu6_device *isp) IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_STATUS; isys_ipdata.csi2.ctrl0_irq_lnp = IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_LEVEL_NOT_PULSE; - isys_ipdata.enhanced_iwake = is_ipu6ep_mtl(hw_ver) || is_ipu6ep(hw_ver); + isys_ipdata.enhanced_iwake = IS_IPU6EP_MTL(isp) || IS_IPU6EP(isp); psys_ipdata.hw_variant.spc_offset = IPU6_PSYS_SPC_OFFSET; isys_ipdata.csi2.fw_access_port_ofs = CSI_REG_HUB_FW_ACCESS_PORT_OFS; - if (is_ipu6ep(hw_ver)) { + if (IS_IPU6EP(isp)) { isys_ipdata.ltr = IPU6EP_LTR_VALUE; isys_ipdata.memopen_threshold = IPU6EP_MIN_MEMOPEN_TH; } - if (is_ipu6_tgl(hw_ver)) + if (IS_IPU6_TGL(isp)) isys_ipdata.csi2.nports = IPU6_TGL_ISYS_CSI2_NPORTS; - if (is_ipu6ep_mtl(hw_ver)) { + if (IS_IPU6EP_MTL(isp)) { isys_ipdata.csi2.nports = IPU6EP_MTL_ISYS_CSI2_NPORTS; isys_ipdata.csi2.ctrl0_irq_edge = @@ -347,7 +354,7 @@ static void ipu6_internal_pdata_init(struct ipu6_device *isp) isys_ipdata.memopen_threshold = IPU6EP_MTL_MIN_MEMOPEN_TH; } - if (is_ipu6se(hw_ver)) { + if (IS_IPU6SE(isp)) { isys_ipdata.csi2.nports = IPU6SE_ISYS_CSI2_NPORTS; isys_ipdata.csi2.irq_mask = IPU6SE_CSI_RX_ERROR_IRQ_MASK; isys_ipdata.num_parallel_streams = IPU6SE_ISYS_NUM_STREAMS; @@ -363,15 +370,21 @@ static void ipu6_internal_pdata_init(struct ipu6_device *isp) isys_ipdata.max_devq_size = IPU6SE_DEV_SEND_QUEUE_SIZE; psys_ipdata.hw_variant.spc_offset = IPU6SE_PSYS_SPC_OFFSET; } + + if (IS_IPU7(isp)) { + isys_ipdata.csi2.gpreg = IPU7_IS_IO_CSI2_GPREGS_BASE; + isys_ipdata.csi2.nports = 4; + } } static struct ipu6_bus_device * ipu6_isys_init(struct pci_dev *pdev, struct device *parent, - struct ipu6_buttress_ctrl *ctrl, void __iomem *base, + const struct ipu6_buttress_ctrl *ctrl, void __iomem *base, const struct ipu6_isys_internal_pdata *ipdata) { struct device *dev = &pdev->dev; struct ipu6_bus_device *isys_adev; + struct ipu6_buttress_ctrl *devm_ctrl; struct ipu6_isys_pdata *pdata; int ret; @@ -381,6 +394,10 @@ ipu6_isys_init(struct pci_dev *pdev, struct device *parent, return ERR_PTR(ret); } + devm_ctrl = devm_kmemdup(dev, ctrl, sizeof(*ctrl), GFP_KERNEL); + if (!devm_ctrl) + return ERR_PTR(-ENOMEM); + pdata = kzalloc_obj(*pdata); if (!pdata) return ERR_PTR(-ENOMEM); @@ -388,7 +405,7 @@ ipu6_isys_init(struct pci_dev *pdev, struct device *parent, pdata->base = base; pdata->ipdata = ipdata; - isys_adev = ipu6_bus_initialize_device(pdev, parent, pdata, ctrl, + isys_adev = ipu6_bus_initialize_device(pdev, parent, pdata, devm_ctrl, IPU6_ISYS_NAME); if (IS_ERR(isys_adev)) { kfree(pdata); @@ -396,8 +413,7 @@ ipu6_isys_init(struct pci_dev *pdev, struct device *parent, "ipu6_bus_initialize_device isys failed\n"); } - isys_adev->mmu = ipu6_mmu_init(dev, base, ISYS_MMID, - &ipdata->hw_variant); + isys_adev->mmu = ipu6_mmu_init(dev, base, IPU_ISYS); if (IS_ERR(isys_adev->mmu)) { put_device(&isys_adev->auxdev.dev); return dev_err_cast_probe(dev, isys_adev->mmu, @@ -415,13 +431,19 @@ ipu6_isys_init(struct pci_dev *pdev, struct device *parent, static struct ipu6_bus_device * ipu6_psys_init(struct pci_dev *pdev, struct device *parent, - struct ipu6_buttress_ctrl *ctrl, void __iomem *base, + const struct ipu6_buttress_ctrl *ctrl, void __iomem *base, const struct ipu6_psys_internal_pdata *ipdata) { + struct device *dev = &pdev->dev; struct ipu6_bus_device *psys_adev; + struct ipu6_buttress_ctrl *devm_ctrl; struct ipu6_psys_pdata *pdata; int ret; + devm_ctrl = devm_kmemdup(dev, ctrl, sizeof(*ctrl), GFP_KERNEL); + if (!devm_ctrl) + return ERR_PTR(-ENOMEM); + pdata = kzalloc_obj(*pdata); if (!pdata) return ERR_PTR(-ENOMEM); @@ -429,7 +451,7 @@ ipu6_psys_init(struct pci_dev *pdev, struct device *parent, pdata->base = base; pdata->ipdata = ipdata; - psys_adev = ipu6_bus_initialize_device(pdev, parent, pdata, ctrl, + psys_adev = ipu6_bus_initialize_device(pdev, parent, pdata, devm_ctrl, IPU6_PSYS_NAME); if (IS_ERR(psys_adev)) { kfree(pdata); @@ -437,8 +459,7 @@ ipu6_psys_init(struct pci_dev *pdev, struct device *parent, "ipu6_bus_initialize_device psys failed\n"); } - psys_adev->mmu = ipu6_mmu_init(&pdev->dev, base, PSYS_MMID, - &ipdata->hw_variant); + psys_adev->mmu = ipu6_mmu_init(&pdev->dev, base, IPU_PSYS); if (IS_ERR(psys_adev->mmu)) { put_device(&psys_adev->auxdev.dev); return dev_err_cast_probe(&pdev->dev, psys_adev->mmu, @@ -454,12 +475,13 @@ ipu6_psys_init(struct pci_dev *pdev, struct device *parent, return psys_adev; } -static int ipu6_pci_config_setup(struct pci_dev *dev, u8 hw_ver) +static int ipu6_pci_config_setup(struct pci_dev *dev) { + struct ipu6_device *isp = pci_get_drvdata(dev); int ret; /* No PCI msi capability for IPU6EP */ - if (is_ipu6ep(hw_ver) || is_ipu6ep_mtl(hw_ver)) { + if (IS_IPU6EP(isp) || IS_IPU6EP_MTL(isp)) { /* likely do nothing as msi not enabled by default */ pci_disable_msi(dev); return 0; @@ -474,7 +496,12 @@ static int ipu6_pci_config_setup(struct pci_dev *dev, u8 hw_ver) static void ipu6_configure_vc_mechanism(struct ipu6_device *isp) { - u32 val = readl(isp->base + BUTTRESS_REG_BTRS_CTRL); + u32 val; + + if (IS_IPU7(isp)) + return; + + val = readl(isp->base + BUTTRESS_REG_BTRS_CTRL); if (IPU6_BTRS_ARB_STALL_MODE_VC0 == IPU6_BTRS_ARB_MODE_TYPE_STALL) val |= BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC0; @@ -489,70 +516,193 @@ static void ipu6_configure_vc_mechanism(struct ipu6_device *isp) writel(val, isp->base + BUTTRESS_REG_BTRS_CTRL); } +static int __ipu6_map_fw_by_sys(struct ipu6_device *isp, struct ipu6_bus_device *adev) +{ + int ret; + + ret = ipu6_map_fw_region(adev, isp->cpd_fw->data, isp->cpd_fw->size, + DMA_TO_DEVICE, 0); + if (ret) { + dev_err_probe(&isp->pdev->dev, ret, + "Firmware mapping failed\n"); + return ret; + } + + ret = ipu6_cpd_create_pkg_dir(adev, isp->cpd_fw->data); + if (ret) { + dev_err_probe(&isp->pdev->dev, ret, + "failed to create pkg dir\n"); + return ret; + } + + return 0; +} + +static int ipu6_map_fw(struct ipu6_device *isp) +{ + int ret; + + ret = __ipu6_map_fw_by_sys(isp, isp->psys); + if (ret) + return ret; + + if (!isp->secure_mode) + return __ipu6_map_fw_by_sys(isp, isp->isys); + + return 0; +} + +static int __ipu7_map_fw_non_secure(struct ipu6_device *isp) +{ + int ret; + + /* + * Allocate and map memory for running the firmware. Not + * required in secure mode, in which firmware runs in IMR. + */ + isp->fw_code_region = vmalloc(IPU7_FW_CODE_REGION_SIZE); + if (!isp->fw_code_region) + return -ENOMEM; + + ret = ipu6_ipu7_cpd_copy_binary(isp->cpd_fw->data, "isys", + isp->fw_code_region, + &isp->isys->fw_entry); + if (ret) + return ret; + + ret = ipu6_map_fw_region(isp->isys, isp->fw_code_region, + IPU7_FW_CODE_REGION_SIZE, DMA_BIDIRECTIONAL, + DMA_ATTR_RESERVE_REGION); + if (ret) + return ret; + + ret = ipu6_ipu7_cpd_copy_binary(isp->cpd_fw->data, "psys", + isp->fw_code_region, + &isp->psys->fw_entry); + if (ret) + return ret; + + return ipu6_map_fw_region(isp->psys, isp->fw_code_region, + IPU7_FW_CODE_REGION_SIZE, DMA_BIDIRECTIONAL, + DMA_ATTR_RESERVE_REGION); +} + +static int ipu7_map_fw(struct ipu6_device *isp) +{ + int ret; + + ret = isp->secure_mode ? + ipu6_map_fw_region(isp->psys, isp->cpd_fw->data, + isp->cpd_fw->size, DMA_BIDIRECTIONAL, 0) : + __ipu7_map_fw_non_secure(isp); + if (ret) { + dev_err_probe(&isp->pdev->dev, ret, + "Failed to init ipu7 firmware region\n"); + return ret; + } + + return 0; +} + static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { - struct ipu6_buttress_ctrl *isys_ctrl = NULL, *psys_ctrl = NULL; + const struct ipu6_buttress_ctrl *isys_ctrl, *psys_ctrl; struct device *dev = &pdev->dev; void __iomem *isys_base = NULL; void __iomem *psys_base = NULL; struct ipu6_device *isp; phys_addr_t phys; u32 val, version, sku_id; + unsigned long dir; int ret; + if ((id->device == PCI_DEVICE_ID_INTEL_IPU7 || + id->device == PCI_DEVICE_ID_INTEL_IPU7P5) && force_no_probe_ipu7) + return -ENODEV; + isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL); if (!isp) return -ENOMEM; - isp->pdev = pdev; - INIT_LIST_HEAD(&isp->devices); - - ret = pcim_enable_device(pdev); - if (ret) - return dev_err_probe(dev, ret, "Enable PCI device failed\n"); - - phys = pci_resource_start(pdev, IPU6_PCI_BAR); - dev_dbg(dev, "IPU6 PCI bar[%u] = %pa\n", IPU6_PCI_BAR, &phys); - - isp->base = pcim_iomap_region(pdev, IPU6_PCI_BAR, IPU6_NAME); - if (IS_ERR(isp->base)) - return dev_err_probe(dev, PTR_ERR(isp->base), - "Failed to I/O mem remapping\n"); - - pci_set_drvdata(pdev, isp); - pci_set_master(pdev); - isp->cpd_metadata_cmpnt_size = sizeof(struct ipu6_cpd_metadata_cmpnt); + isp->buttress.regs = &ipu6_buttress_regs; + isp->model_name = IPU6_MEDIA_DEV_MODEL_NAME; + isys_ctrl = &ipu6_isys_buttress_ctrl; + psys_ctrl = &ipu6_psys_buttress_ctrl; + switch (id->device) { case PCI_DEVICE_ID_INTEL_IPU6: - isp->hw_ver = IPU6_VER_6; + isp->hw_ver = IPU_VERSION_6; isp->cpd_fw_name = IPU6_FIRMWARE_NAME; break; case PCI_DEVICE_ID_INTEL_IPU6SE: - isp->hw_ver = IPU6_VER_6SE; + isp->hw_ver = IPU_VERSION_6SE; isp->cpd_fw_name = IPU6SE_FIRMWARE_NAME; isp->cpd_metadata_cmpnt_size = sizeof(struct ipu6se_cpd_metadata_cmpnt); break; case PCI_DEVICE_ID_INTEL_IPU6EP_ADLP: case PCI_DEVICE_ID_INTEL_IPU6EP_RPLP: - isp->hw_ver = IPU6_VER_6EP; + isp->hw_ver = IPU_VERSION_6EP; isp->cpd_fw_name = IPU6EP_FIRMWARE_NAME; break; case PCI_DEVICE_ID_INTEL_IPU6EP_ADLN: - isp->hw_ver = IPU6_VER_6EP; + isp->hw_ver = IPU_VERSION_6EP; isp->cpd_fw_name = IPU6EPADLN_FIRMWARE_NAME; break; case PCI_DEVICE_ID_INTEL_IPU6EP_MTL: - isp->hw_ver = IPU6_VER_6EP_MTL; + isp->hw_ver = IPU_VERSION_6EP_MTL; isp->cpd_fw_name = IPU6EPMTL_FIRMWARE_NAME; break; + case PCI_DEVICE_ID_INTEL_IPU7: + isp->hw_ver = IPU_VERSION_7; + isp->cpd_fw_name = IPU7_FIRMWARE_NAME; + isp->model_name = IPU7_MEDIA_DEV_MODEL_NAME; + isp->buttress.regs = &ipu7_buttress_regs; + isys_ctrl = &ipu7_isys_buttress_ctrl; + psys_ctrl = &ipu7_psys_buttress_ctrl; + break; + case PCI_DEVICE_ID_INTEL_IPU7P5: + isp->hw_ver = IPU_VERSION_7P5; + isp->cpd_fw_name = IPU7P5_FIRMWARE_NAME; + isp->model_name = IPU7P5_MEDIA_DEV_MODEL_NAME; + isp->buttress.regs = &ipu7p5_buttress_regs; + isys_ctrl = &ipu7_isys_buttress_ctrl; + psys_ctrl = &ipu7_psys_buttress_ctrl; + break; default: return dev_err_probe(dev, -ENODEV, "Unsupported IPU6 device %x\n", id->device); } + isp->pdev = pdev; + INIT_LIST_HEAD(&isp->devices); + + ret = pcim_enable_device(pdev); + if (ret) + return dev_err_probe(dev, ret, "Enable PCI device failed\n"); + + phys = pci_resource_start(pdev, IPU6_PCI_BAR); + dev_dbg(dev, "IPU6 PCI bar[%u] = %pa\n", IPU6_PCI_BAR, &phys); + + isp->base = pcim_iomap_region(pdev, IPU6_PCI_BAR, IPU6_NAME); + if (IS_ERR(isp->base)) + return dev_err_probe(dev, PTR_ERR(isp->base), + "Failed to I/O mem remapping\n"); + + if (IS_IPU7(isp)) { + isp->pb_base = pcim_iomap_region(pdev, IPU7_PCI_PBBAR, + IPU6_NAME); + if (IS_ERR(isp->pb_base)) + return dev_err_probe(dev, PTR_ERR(isp->pb_base), + "I/O remapping PB BAR %u failed\n", + IPU7_PCI_PBBAR); + } + + pci_set_drvdata(pdev, isp); + pci_set_master(pdev); + ipu6_internal_pdata_init(isp); isys_base = isp->base + isys_ipdata.hw_variant.offset; @@ -564,7 +714,7 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) dma_set_max_seg_size(dev, UINT_MAX); - ret = ipu6_pci_config_setup(pdev, isp->hw_ver); + ret = ipu6_pci_config_setup(pdev); if (ret) return ret; @@ -588,13 +738,6 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) goto out_ipu6_bus_del_devices; } - isys_ctrl = devm_kmemdup(dev, &isys_buttress_ctrl, - sizeof(isys_buttress_ctrl), GFP_KERNEL); - if (!isys_ctrl) { - ret = -ENOMEM; - goto out_ipu6_bus_del_devices; - } - isp->isys = ipu6_isys_init(pdev, dev, isys_ctrl, isys_base, &isys_ipdata); if (IS_ERR(isp->isys)) { @@ -602,13 +745,6 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) goto out_ipu6_bus_del_devices; } - psys_ctrl = devm_kmemdup(dev, &psys_buttress_ctrl, - sizeof(psys_buttress_ctrl), GFP_KERNEL); - if (!psys_ctrl) { - ret = -ENOMEM; - goto out_ipu6_bus_del_devices; - } - isp->psys = ipu6_psys_init(pdev, &isp->isys->auxdev.dev, psys_ctrl, psys_base, &psys_ipdata); if (IS_ERR(isp->psys)) { @@ -627,19 +763,9 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) goto out_ipu6_rpm_put; } - ret = ipu6_buttress_map_fw_image(isp->psys, isp->cpd_fw, - &isp->psys->fw_sgt); - if (ret) { - dev_err_probe(&isp->pdev->dev, ret, "failed to map fw image\n"); - goto out_ipu6_rpm_put; - } - - ret = ipu6_cpd_create_pkg_dir(isp->psys, isp->cpd_fw->data); - if (ret) { - dev_err_probe(&isp->pdev->dev, ret, - "failed to create pkg dir\n"); + ret = IS_IPU7(isp) ? ipu7_map_fw(isp) : ipu6_map_fw(isp); + if (ret) goto out_ipu6_rpm_put; - } ret = devm_request_threaded_irq(dev, pdev->irq, ipu6_buttress_isr, ipu6_buttress_isr_threaded, @@ -662,7 +788,7 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) /* Configure the arbitration mechanisms for VC requests */ ipu6_configure_vc_mechanism(isp); - val = readl(isp->base + BUTTRESS_REG_SKU); + val = readl(isp->base + isp->buttress.regs->sku_id); sku_id = FIELD_GET(GENMASK(6, 4), val); version = FIELD_GET(GENMASK(3, 0), val); dev_info(dev, "IPU%u-v%u[%x] hardware version %d\n", version, sku_id, @@ -680,10 +806,21 @@ out_free_irq: out_ipu6_rpm_put: pm_runtime_put_sync(&isp->psys->auxdev.dev); out_ipu6_bus_del_devices: + dir = IS_IPU7(isp) ? DMA_BIDIRECTIONAL : DMA_TO_DEVICE; if (!IS_ERR_OR_NULL(isp->psys)) { ipu6_cpd_free_pkg_dir(isp->psys); - ipu6_buttress_unmap_fw_image(isp->psys, &isp->psys->fw_sgt); + if (isp->psys->fw_sgt.nents) + ipu6_unmap_fw_region(isp->psys, dir); + } + if (!IS_ERR_OR_NULL(isp->isys)) { + ipu6_cpd_free_pkg_dir(isp->isys); + if (isp->isys->fw_sgt.nents) + ipu6_unmap_fw_region(isp->isys, dir); } + + vfree(isp->fw_code_region); + isp->fw_code_region = NULL; + if (!IS_ERR_OR_NULL(isp->psys) && !IS_ERR_OR_NULL(isp->psys->mmu)) ipu6_mmu_cleanup(isp->psys->mmu); if (!IS_ERR_OR_NULL(isp->isys) && !IS_ERR_OR_NULL(isp->isys->mmu)) @@ -699,13 +836,23 @@ buttress_exit: static void ipu6_pci_remove(struct pci_dev *pdev) { struct ipu6_device *isp = pci_get_drvdata(pdev); - struct ipu6_mmu *isys_mmu = isp->isys->mmu; - struct ipu6_mmu *psys_mmu = isp->psys->mmu; + unsigned long dir; devm_free_irq(&pdev->dev, pdev->irq, isp); + + dir = IS_IPU7(isp) ? DMA_BIDIRECTIONAL : DMA_TO_DEVICE; ipu6_cpd_free_pkg_dir(isp->psys); + ipu6_unmap_fw_region(isp->psys, dir); + + if (isp->isys) { + ipu6_cpd_free_pkg_dir(isp->isys); + if (isp->isys->fw_sgt.nents) + ipu6_unmap_fw_region(isp->isys, dir); + } + + vfree(isp->fw_code_region); + isp->fw_code_region = NULL; - ipu6_buttress_unmap_fw_image(isp->psys, &isp->psys->fw_sgt); ipu6_buttress_exit(isp); ipu6_bus_del_devices(pdev); @@ -715,8 +862,10 @@ static void ipu6_pci_remove(struct pci_dev *pdev) release_firmware(isp->cpd_fw); - ipu6_mmu_cleanup(psys_mmu); - ipu6_mmu_cleanup(isys_mmu); + if (isp->psys) + ipu6_mmu_cleanup(isp->psys->mmu); + if (isp->isys) + ipu6_mmu_cleanup(isp->isys->mmu); } static void ipu6_pci_reset_prepare(struct pci_dev *pdev) @@ -754,7 +903,6 @@ static int ipu6_resume(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); struct ipu6_device *isp = pci_get_drvdata(pdev); - struct ipu6_buttress *b = &isp->buttress; int ret; /* Configure the arbitration mechanisms for VC requests */ @@ -766,7 +914,7 @@ static int ipu6_resume(struct device *dev) ipu6_buttress_restore(isp); - ret = ipu6_buttress_ipc_reset(isp, &b->cse); + ret = ipu6_buttress_ipc_reset(isp); if (ret) dev_err(&isp->pdev->dev, "IPC reset protocol failed!\n"); @@ -795,10 +943,8 @@ static int ipu6_runtime_resume(struct device *dev) ipu6_buttress_restore(isp); if (isp->need_ipc_reset) { - struct ipu6_buttress *b = &isp->buttress; - isp->need_ipc_reset = false; - ret = ipu6_buttress_ipc_reset(isp, &b->cse); + ret = ipu6_buttress_ipc_reset(isp); if (ret) dev_err(&isp->pdev->dev, "IPC reset protocol failed\n"); } diff --git a/drivers/media/pci/intel/ipu6/ipu6.h b/drivers/media/pci/intel/ipu6/ipu6.h index 92e3c3414c91..72c5063d5198 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.h +++ b/drivers/media/pci/intel/ipu6/ipu6.h @@ -16,46 +16,32 @@ struct ipu6_bus_device; #define IPU6_NAME "intel-ipu6" #define IPU6_MEDIA_DEV_MODEL_NAME "ipu6" +#define IPU7_MEDIA_DEV_MODEL_NAME "ipu7" +#define IPU7P5_MEDIA_DEV_MODEL_NAME "ipu7.5" #define IPU6SE_FIRMWARE_NAME "intel/ipu/ipu6se_fw.bin" #define IPU6EP_FIRMWARE_NAME "intel/ipu/ipu6ep_fw.bin" #define IPU6_FIRMWARE_NAME "intel/ipu/ipu6_fw.bin" #define IPU6EPMTL_FIRMWARE_NAME "intel/ipu/ipu6epmtl_fw.bin" #define IPU6EPADLN_FIRMWARE_NAME "intel/ipu/ipu6epadln_fw.bin" - -enum ipu6_version { - IPU6_VER_INVALID = 0, - IPU6_VER_6 = 1, - IPU6_VER_6SE = 3, - IPU6_VER_6EP = 5, - IPU6_VER_6EP_MTL = 6, -}; - -/* - * IPU6 - TGL - * IPU6SE - JSL - * IPU6EP - ADL/RPL - * IPU6EP_MTL - MTL - */ -static inline bool is_ipu6se(u8 hw_ver) -{ - return hw_ver == IPU6_VER_6SE; -} - -static inline bool is_ipu6ep(u8 hw_ver) -{ - return hw_ver == IPU6_VER_6EP; -} - -static inline bool is_ipu6ep_mtl(u8 hw_ver) -{ - return hw_ver == IPU6_VER_6EP_MTL; -} - -static inline bool is_ipu6_tgl(u8 hw_ver) -{ - return hw_ver == IPU6_VER_6; -} +#define IPU7_FIRMWARE_NAME "intel/ipu/ipu7_fw.bin" +#define IPU7P5_FIRMWARE_NAME "intel/ipu/ipu7ptl_fw.bin" + +#define IPU_VERSION_6 BIT(0) /* TGL */ +#define IPU_VERSION_6SE BIT(1) /* JSL */ +#define IPU_VERSION_6EP BIT(2) /* ADL/RPL */ +#define IPU_VERSION_6EP_MTL BIT(3) /* MTL */ +#define IPU_VERSION_7 BIT(4) /* LNL */ +#define IPU_VERSION_7P5 BIT(5) /* PTL */ + +#define IS_IPU6_TGL(isp) ((isp)->hw_ver & IPU_VERSION_6) +#define IS_IPU6SE(isp) ((isp)->hw_ver & IPU_VERSION_6SE) +#define IS_IPU6EP(isp) ((isp)->hw_ver & IPU_VERSION_6EP) +#define IS_IPU6EP_MTL(isp) ((isp)->hw_ver & IPU_VERSION_6EP_MTL) +#define IS_IPU7(isp) ((isp)->hw_ver & \ + (IPU_VERSION_7 | IPU_VERSION_7P5)) +#define IS_IPU7_MTL(isp) ((isp)->hw_ver & IPU_VERSION_7) +#define IS_IPU7P5(isp) ((isp)->hw_ver & IPU_VERSION_7P5) /* * ISYS DMA can overshoot. For higher resolutions over allocation is one line @@ -80,14 +66,21 @@ struct ipu6_device { const struct firmware *cpd_fw; const char *cpd_fw_name; u32 cpd_metadata_cmpnt_size; + const char *model_name; void __iomem *base; + void __iomem *pb_base; bool need_ipc_reset; bool secure_mode; u8 hw_ver; bool bus_ready_to_probe; + u32 *fw_code_region; }; +#define IPU_PSYS 0 +#define IPU_ISYS 1 +#define IPU_SUBSYS_NUM 2 + #define IPU6_ISYS_NAME "isys" #define IPU6_PSYS_NAME "psys" @@ -134,141 +127,6 @@ struct ipu6_device { #define IPU6_BTRS_ARB_STALL_MODE_VC1 \ IPU6_BTRS_ARB_MODE_TYPE_REARB -/* - * MMU Invalidation HW bug workaround by ZLW mechanism - * - * Old IPU6 MMUV2 has a bug in the invalidation mechanism which might result in - * wrong translation or replication of the translation. This will cause data - * corruption. So we cannot directly use the MMU V2 invalidation registers - * to invalidate the MMU. Instead, whenever an invalidate is called, we need to - * clear the TLB by evicting all the valid translations by filling it with trash - * buffer (which is guaranteed not to be used by any other processes). ZLW is - * used to fill the L1 and L2 caches with the trash buffer translations. ZLW - * or Zero length write, is pre-fetch mechanism to pre-fetch the pages in - * advance to the L1 and L2 caches without triggering any memory operations. - * - * In MMU V2, L1 -> 16 streams and 64 blocks, maximum 16 blocks per stream - * One L1 block has 16 entries, hence points to 16 * 4K pages - * L2 -> 16 streams and 32 blocks. 2 blocks per streams - * One L2 block maps to 1024 L1 entries, hence points to 4MB address range - * 2 blocks per L2 stream means, 1 stream points to 8MB range - * - * As we need to clear the caches and 8MB being the biggest cache size, we need - * to have trash buffer which points to 8MB address range. As these trash - * buffers are not used for any memory transactions, we need only the least - * amount of physical memory. So we reserve 8MB IOVA address range but only - * one page is reserved from physical memory. Each of this 8MB IOVA address - * range is then mapped to the same physical memory page. - */ -/* One L2 entry maps 1024 L1 entries and one L1 entry per page */ -#define IPU6_MMUV2_L2_RANGE (1024 * PAGE_SIZE) -/* Max L2 blocks per stream */ -#define IPU6_MMUV2_MAX_L2_BLOCKS 2 -/* Max L1 blocks per stream */ -#define IPU6_MMUV2_MAX_L1_BLOCKS 16 -#define IPU6_MMUV2_TRASH_RANGE (IPU6_MMUV2_L2_RANGE * IPU6_MMUV2_MAX_L2_BLOCKS) -/* Entries per L1 block */ -#define MMUV2_ENTRIES_PER_L1_BLOCK 16 -#define MMUV2_TRASH_L1_BLOCK_OFFSET (MMUV2_ENTRIES_PER_L1_BLOCK * PAGE_SIZE) -#define MMUV2_TRASH_L2_BLOCK_OFFSET IPU6_MMUV2_L2_RANGE - -/* - * In some of the IPU6 MMUs, there is provision to configure L1 and L2 page - * table caches. Both these L1 and L2 caches are divided into multiple sections - * called streams. There is maximum 16 streams for both caches. Each of these - * sections are subdivided into multiple blocks. When nr_l1streams = 0 and - * nr_l2streams = 0, means the MMU is of type MMU_V1 and do not support - * L1/L2 page table caches. - * - * L1 stream per block sizes are configurable and varies per usecase. - * L2 has constant block sizes - 2 blocks per stream. - * - * MMU1 support pre-fetching of the pages to have less cache lookup misses. To - * enable the pre-fetching, MMU1 AT (Address Translator) device registers - * need to be configured. - * - * There are four types of memory accesses which requires ZLW configuration. - * ZLW(Zero Length Write) is a mechanism to enable VT-d pre-fetching on IOMMU. - * - * 1. Sequential Access or 1D mode - * Set ZLW_EN -> 1 - * set ZLW_PAGE_CROSS_1D -> 1 - * Set ZLW_N to "N" pages so that ZLW will be inserte N pages ahead where - * N is pre-defined and hardcoded in the platform data - * Set ZLW_2D -> 0 - * - * 2. ZLW 2D mode - * Set ZLW_EN -> 1 - * set ZLW_PAGE_CROSS_1D -> 1, - * Set ZLW_N -> 0 - * Set ZLW_2D -> 1 - * - * 3. ZLW Enable (no 1D or 2D mode) - * Set ZLW_EN -> 1 - * set ZLW_PAGE_CROSS_1D -> 0, - * Set ZLW_N -> 0 - * Set ZLW_2D -> 0 - * - * 4. ZLW disable - * Set ZLW_EN -> 0 - * set ZLW_PAGE_CROSS_1D -> 0, - * Set ZLW_N -> 0 - * Set ZLW_2D -> 0 - * - * To configure the ZLW for the above memory access, four registers are - * available. Hence to track these four settings, we have the following entries - * in the struct ipu6_mmu_hw. Each of these entries are per stream and - * available only for the L1 streams. - * - * a. l1_zlw_en -> To track zlw enabled per stream (ZLW_EN) - * b. l1_zlw_1d_mode -> Track 1D mode per stream. ZLW inserted at page boundary - * c. l1_ins_zlw_ahead_pages -> to track how advance the ZLW need to be inserted - * Insert ZLW request N pages ahead address. - * d. l1_zlw_2d_mode -> To track 2D mode per stream (ZLW_2D) - * - * - * Currently L1/L2 streams, blocks, AT ZLW configurations etc. are pre-defined - * as per the usecase specific calculations. Any change to this pre-defined - * table has to happen in sync with IPU6 FW. - */ -struct ipu6_mmu_hw { - union { - unsigned long offset; - void __iomem *base; - }; - u32 info_bits; - u8 nr_l1streams; - /* - * L1 has variable blocks per stream - total of 64 blocks and maximum of - * 16 blocks per stream. Configurable by using the block start address - * per stream. Block start address is calculated from the block size - */ - u8 l1_block_sz[IPU6_MMU_MAX_TLB_L1_STREAMS]; - /* Is ZLW is enabled in each stream */ - bool l1_zlw_en[IPU6_MMU_MAX_TLB_L1_STREAMS]; - bool l1_zlw_1d_mode[IPU6_MMU_MAX_TLB_L1_STREAMS]; - u8 l1_ins_zlw_ahead_pages[IPU6_MMU_MAX_TLB_L1_STREAMS]; - bool l1_zlw_2d_mode[IPU6_MMU_MAX_TLB_L1_STREAMS]; - - u32 l1_stream_id_reg_offset; - u32 l2_stream_id_reg_offset; - - u8 nr_l2streams; - /* - * L2 has fixed 2 blocks per stream. Block address is calculated - * from the block size - */ - u8 l2_block_sz[IPU6_MMU_MAX_TLB_L2_STREAMS]; - /* flag to track if WA is needed for successive invalidate HW bug */ - bool insert_read_before_invalidate; -}; - -struct ipu6_mmu_pdata { - u32 nr_mmus; - struct ipu6_mmu_hw mmu_hw[IPU6_MMU_MAX_DEVICES]; - int mmid; -}; - struct ipu6_isys_csi2_pdata { void __iomem *base; }; @@ -283,6 +141,8 @@ struct ipu6_isys_internal_csi2_pdata { u32 ctrl0_irq_lnp; u32 ctrl0_irq_status; u32 fw_access_port_ofs; + /* IPU7-specific field */ + u32 gpreg; }; struct ipu6_isys_internal_tpg_pdata { @@ -293,8 +153,6 @@ struct ipu6_isys_internal_tpg_pdata { struct ipu6_hw_variants { unsigned long offset; - u32 nr_mmus; - struct ipu6_mmu_hw mmu_hw[IPU6_MMU_MAX_DEVICES]; u8 cdc_fifos; u8 cdc_fifo_threshold[IPU6_MAX_VC_IOSF_PORTS]; u32 dmem_offset; diff --git a/drivers/media/pci/intel/ipu6/ipu7-boot.c b/drivers/media/pci/intel/ipu6/ipu7-boot.c new file mode 100644 index 000000000000..952c73efd237 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu7-boot.c @@ -0,0 +1,405 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 - 2026 Intel Corporation + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/iopoll.h> +#include <linux/types.h> + +#include "ipu6.h" +#include "ipu6-bus.h" +#include "ipu6-buttress.h" +#include "ipu6-dma.h" +#include "ipu6-isys.h" +#include "ipu6-platform-buttress-regs.h" +#include "ipu7-boot.h" +#include "ipu7-platform-regs.h" + +#define IPU7_FW_START_STOP_TIMEOUT 2000 +#define IPU7_BOOT_CELL_RESET_TIMEOUT (2 * USEC_PER_SEC) +#define IPU7_BOOT_STATE_CRITICAL(s) (((s) & 0xffff0000U) == 0xdead0000U) +#define IPU7_BOOT_STATE_READY(s) ((s) == 0x57a7e100U) +#define IPU7_BOOT_STATE_INACTIVE(s) ((s) == 0x57a7e300U) +#define IPU7_BUTTRESS_REG_FW_BOOT_PARAMS0 0x4000 +#define IPU7_BUTTRESS_FW_BOOT_PARAMS_ENTRY(i) \ + (IPU7_BUTTRESS_REG_FW_BOOT_PARAMS0 + ((i) * 4U)) + +struct boot_regs { + u32 base; + u32 dmem_address; + u32 status_ctrl_reg; + u32 fw_start_address_reg; + u32 fw_code_base_reg; +}; + +enum ipu7_boot_reg_id { + IPU7_FW_BOOT_CONFIG_ID = 0, + IPU7_FW_BOOT_STATE_ID = 1, + IPU7_FW_BOOT_SYSCOM_QUEUE_INDICES_BASE_ID = 2, + IPU7_FW_BOOT_UNTRUSTED_ADDR_MIN_ID = 3, + IPU7_FW_BOOT_MESSAGING_VERSION_ID = 4, + IPU7_FW_BOOT_ID_MAX, +}; + +enum ipu7_boot_state { + IPU7_FW_BOOT_STATE_UNINIT = 0x57a7e000U, + IPU7_FW_BOOT_STATE_READY = 0x57a7e100U, + IPU7_FW_BOOT_STATE_SHUTDOWN_CMD = 0x57a7f001U, + IPU7_FW_BOOT_STATE_INACTIVE = 0x57a7e300U, +}; + +static const struct boot_regs boot_regs[IPU_SUBSYS_NUM] = { + [IPU_ISYS] = { + .dmem_address = IPU7_ISYS_DMEM_OFFSET, + .status_ctrl_reg = IPU7_BUTTRESS_REG_ISYS_UCX_CTRL_STATUS, + .fw_start_address_reg = IPU7_BUTTRESS_REG_ISYS_UCX_START_ADDR, + .fw_code_base_reg = IPU7_IS_UC_CTRL_BASE + }, + [IPU_PSYS] = { + .dmem_address = IPU7_PSYS_DMEM_OFFSET, + .status_ctrl_reg = IPU7_BUTTRESS_REG_PSYS_UCX_CTRL_STATUS, + .fw_start_address_reg = IPU7_BUTTRESS_REG_PSYS_UCX_START_ADDR, + .fw_code_base_reg = IPU7_PS_UC_CTRL_BASE + } +}; + +static u32 get_fw_boot_reg_addr(const struct ipu6_bus_device *adev, + enum ipu7_boot_reg_id reg) +{ + u32 base = (adev->ctrl->subsys_id == IPU_ISYS) ? + 0U : (u32)IPU7_FW_BOOT_ID_MAX; + + return IPU7_BUTTRESS_FW_BOOT_PARAMS_ENTRY(base + (u32)reg); +} + +static void write_fw_boot_param(const struct ipu6_bus_device *adev, + enum ipu7_boot_reg_id reg, + u32 val) +{ + void __iomem *base = adev->isp->base; + + dev_dbg(&adev->auxdev.dev, + "write boot param reg: %d addr: %x val: 0x%x\n", + reg, get_fw_boot_reg_addr(adev, reg), val); + writel(val, base + get_fw_boot_reg_addr(adev, reg)); +} + +static u32 read_fw_boot_param(const struct ipu6_bus_device *adev, + enum ipu7_boot_reg_id reg) +{ + void __iomem *base = adev->isp->base; + + return readl(base + get_fw_boot_reg_addr(adev, reg)); +} + +static int ipu7_boot_cell_reset(const struct ipu6_bus_device *adev) +{ + const struct device *dev = &adev->auxdev.dev; + const struct boot_regs *regs = &boot_regs[adev->ctrl->subsys_id]; + u32 ucx_ctrl_status = regs->status_ctrl_reg; + u32 timeout = IPU7_BOOT_CELL_RESET_TIMEOUT; + void __iomem *base = adev->isp->base; + u32 val, val2; + int ret; + + val = readl(base + ucx_ctrl_status); + val |= IPU7_UCX_CTL_RESET; + val &= ~IPU7_UCX_CTL_RUN; + + writel(val, base + ucx_ctrl_status); + + ret = readl_poll_timeout(base + ucx_ctrl_status, val2, + (val2 & 0x3U) == (val & 0x3U), 100, timeout); + if (ret) { + dev_err(dev, "cell enter reset timeout. status: 0x%x\n", val2); + return -ETIMEDOUT; + } + + val = readl(base + ucx_ctrl_status); + val &= ~(IPU7_UCX_CTL_RESET | IPU7_UCX_CTL_RUN); + writel(val, base + ucx_ctrl_status); + + ret = readl_poll_timeout(base + ucx_ctrl_status, val2, + (val2 & 0x3U) == (val & 0x3U), 100, timeout); + if (ret) { + dev_err(dev, "cell exit reset timeout. status: 0x%x\n", val2); + return -ETIMEDOUT; + } + + return 0; +} + +static void ipu7_boot_cell_start(const struct ipu6_bus_device *adev) +{ + const struct boot_regs *regs = &boot_regs[adev->ctrl->subsys_id]; + void __iomem *base = adev->isp->base; + u32 val; + + val = readl(base + regs->status_ctrl_reg); + WARN_ON(val & (IPU7_UCX_CTL_RESET | IPU7_UCX_CTL_RUN)); + + val &= ~IPU7_UCX_CTL_RESET; + val |= IPU7_UCX_CTL_RUN; + writel(val, base + regs->status_ctrl_reg); +} + +static void ipu7_boot_cell_stop(const struct ipu6_bus_device *adev) +{ + const struct boot_regs *regs = &boot_regs[adev->ctrl->subsys_id]; + void __iomem *base = adev->isp->base; + u32 val; + + val = readl(base + regs->status_ctrl_reg); + val &= ~IPU7_UCX_CTL_RUN; + writel(val, base + regs->status_ctrl_reg); + + /* Wait for uC transactions complete */ + usleep_range(10, 20); + + val = readl(base + regs->status_ctrl_reg); + val |= IPU7_UCX_CTL_RESET; + writel(val, base + regs->status_ctrl_reg); +} + +static int ipu7_boot_cell_init(const struct ipu6_bus_device *adev) +{ + const struct boot_regs *regs = &boot_regs[adev->ctrl->subsys_id]; + struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); + struct ipu7_fw_com_context *fwctx = isys->fwctx; + void __iomem *base = adev->isp->base; + + writel(fwctx->fw_entry, base + regs->fw_start_address_reg); + + return ipu7_boot_cell_reset(adev); +} + +static void init_cfg_versions(struct ipu7_boot_abi_cfg *boot_cfg, u32 length, u8 major) +{ + boot_cfg->length = length; + boot_cfg->config_version.major = 1U; + boot_cfg->config_version.minor = 0U; + boot_cfg->config_version.subminor = 0U; + boot_cfg->config_version.patch = 0U; + + boot_cfg->client_version_support.num_versions = 1U; + boot_cfg->client_version_support.versions[0].major = major; + boot_cfg->client_version_support.versions[0].minor = 0U; + boot_cfg->client_version_support.versions[0].subminor = 0U; + boot_cfg->client_version_support.versions[0].patch = 0U; +} + +int ipu6_ipu7_init_boot_config(struct ipu6_bus_device *adev, + struct ipu7_fw_com_queue_config *qconfigs, + int num_queues, u32 uc_freq, + dma_addr_t subsys_config, u8 major) +{ + struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); + struct ipu7_fw_com_context *fwctx = isys->fwctx; + struct ipu7_boot_abi_cfg *boot_config; + struct ipu7_fw_com_queue_params_config *cfgs; + struct device *dev = &adev->auxdev.dev; + u32 total_queue_size_aligned = 0; + dma_addr_t queue_mem_dma_ptr; + void *queue_mem_ptr; + unsigned int i; + + /* Allocate boot config. */ + fwctx->boot_config_size = + sizeof(*cfgs) * num_queues + sizeof(*boot_config); + fwctx->boot_config = ipu6_dma_alloc(adev, fwctx->boot_config_size, + &fwctx->boot_config_dma_addr, + GFP_KERNEL, 0); + if (!fwctx->boot_config) { + dev_err(dev, "Failed to allocate boot config.\n"); + return -ENOMEM; + } + + boot_config = fwctx->boot_config; + memset(boot_config, 0, sizeof(*boot_config)); + init_cfg_versions(boot_config, fwctx->boot_config_size, major); + boot_config->subsys_config = subsys_config; + + boot_config->uc_tile_frequency = uc_freq; + boot_config->uc_tile_frequency_units = 0; + boot_config->fw_com_config.max_output_queues = + fwctx->num_output_queues; + boot_config->fw_com_config.max_input_queues = + fwctx->num_input_queues; + + ipu6_dma_sync_single(adev, fwctx->boot_config_dma_addr, + fwctx->boot_config_size); + + for (i = 0; i < num_queues; i++) { + u32 queue_size = qconfigs[i].max_capacity * + qconfigs[i].token_size_in_bytes; + + queue_size = ALIGN(queue_size, 64U); + total_queue_size_aligned += queue_size; + qconfigs[i].queue_size = queue_size; + } + + /* Allocate queue memory */ + fwctx->queue_mem = ipu6_dma_alloc(adev, total_queue_size_aligned, + &fwctx->queue_mem_dma_addr, + GFP_KERNEL, 0); + if (!fwctx->queue_mem) { + dev_err(dev, "Failed to allocate queue memory.\n"); + return -ENOMEM; + } + fwctx->queue_mem_size = total_queue_size_aligned; + + cfgs = ipu7_fw_com_get_queue_config(&boot_config->fw_com_config); + queue_mem_ptr = fwctx->queue_mem; + queue_mem_dma_ptr = fwctx->queue_mem_dma_addr; + for (i = 0; i < num_queues; i++) { + cfgs[i].token_array_mem = queue_mem_dma_ptr; + cfgs[i].max_capacity = qconfigs[i].max_capacity; + cfgs[i].token_size_in_bytes = qconfigs[i].token_size_in_bytes; + qconfigs[i].token_array_mem = queue_mem_ptr; + queue_mem_dma_ptr += qconfigs[i].queue_size; + queue_mem_ptr += qconfigs[i].queue_size; + } + + ipu6_dma_sync_single(adev, fwctx->queue_mem_dma_addr, + total_queue_size_aligned); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ipu6_ipu7_init_boot_config, "INTEL_IPU6"); + +void ipu6_ipu7_release_boot_config(struct ipu6_bus_device *adev) +{ + struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); + struct ipu7_fw_com_context *fwctx; + + if (!isys || !isys->fwctx) + return; + + fwctx = isys->fwctx; + + if (fwctx->queue_mem) { + ipu6_dma_free(adev, fwctx->queue_mem_size, + fwctx->queue_mem, + fwctx->queue_mem_dma_addr, 0); + fwctx->queue_mem = NULL; + fwctx->queue_mem_dma_addr = 0; + } + + if (fwctx->boot_config) { + ipu6_dma_free(adev, fwctx->boot_config_size, + fwctx->boot_config, + fwctx->boot_config_dma_addr, 0); + fwctx->boot_config = NULL; + fwctx->boot_config_dma_addr = 0; + } +} +EXPORT_SYMBOL_NS_GPL(ipu6_ipu7_release_boot_config, "INTEL_IPU6"); + +int ipu6_ipu7_boot_start_fw(const struct ipu6_bus_device *adev) +{ + const struct device *dev = &adev->auxdev.dev; + struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); + struct ipu7_fw_com_context *fwctx = isys->fwctx; + u32 timeout = IPU7_FW_START_STOP_TIMEOUT; + void __iomem *base = adev->isp->base; + u32 boot_state, last_boot_state; + u32 indices_addr, msg_ver, id; + int ret; + + ret = ipu7_boot_cell_init(adev); + if (ret) + return ret; + + /* store "uninit" state to boot state reg */ + write_fw_boot_param(adev, IPU7_FW_BOOT_STATE_ID, + IPU7_FW_BOOT_STATE_UNINIT); + /* Set registers to zero, recommended for diagnostics. */ + write_fw_boot_param(adev, + IPU7_FW_BOOT_SYSCOM_QUEUE_INDICES_BASE_ID, 0); + write_fw_boot_param(adev, IPU7_FW_BOOT_MESSAGING_VERSION_ID, 0); + /* store firmware configuration address */ + write_fw_boot_param(adev, IPU7_FW_BOOT_CONFIG_ID, + fwctx->boot_config_dma_addr); + + ipu7_boot_cell_start(adev); + + last_boot_state = IPU7_FW_BOOT_STATE_UNINIT; + while (timeout--) { + boot_state = read_fw_boot_param(adev, + IPU7_FW_BOOT_STATE_ID); + if (boot_state != last_boot_state) { + dev_dbg(dev, "boot state changed from 0x%x to 0x%x\n", + last_boot_state, boot_state); + last_boot_state = boot_state; + } + if (IPU7_BOOT_STATE_CRITICAL(boot_state) || + IPU7_BOOT_STATE_READY(boot_state)) + break; + usleep_range(1000, 1200); + } + + if (IPU7_BOOT_STATE_CRITICAL(boot_state)) { + dev_err(dev, "critical boot state error 0x%x\n", boot_state); + return -EINVAL; + } else if (!IPU7_BOOT_STATE_READY(boot_state)) { + dev_err(dev, "fw boot timeout. state: 0x%x\n", boot_state); + return -ETIMEDOUT; + } + dev_dbg(dev, "fw boot done.\n"); + + id = IPU7_FW_BOOT_SYSCOM_QUEUE_INDICES_BASE_ID; + indices_addr = read_fw_boot_param(adev, id); + fwctx->queue_indices = base + indices_addr; + dev_dbg(dev, "fw queue indices offset is 0x%x\n", indices_addr); + + msg_ver = read_fw_boot_param(adev, + IPU7_FW_BOOT_MESSAGING_VERSION_ID); + dev_dbg(dev, "ipu message version is 0x%08x\n", msg_ver); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ipu6_ipu7_boot_start_fw, "INTEL_IPU6"); + +int ipu6_ipu7_boot_stop_fw(const struct ipu6_bus_device *adev) +{ + const struct device *dev = &adev->auxdev.dev; + u32 timeout = IPU7_FW_START_STOP_TIMEOUT; + u32 boot_state; + + boot_state = read_fw_boot_param(adev, IPU7_FW_BOOT_STATE_ID); + if (IPU7_BOOT_STATE_CRITICAL(boot_state) || + !IPU7_BOOT_STATE_READY(boot_state)) { + dev_err(dev, "fw not ready for shutdown, state 0x%x\n", + boot_state); + return -EBUSY; + } + + /* Issue shutdown to start shutdown process */ + dev_dbg(dev, "stopping fw...\n"); + write_fw_boot_param(adev, IPU7_FW_BOOT_STATE_ID, + IPU7_FW_BOOT_STATE_SHUTDOWN_CMD); + while (timeout--) { + boot_state = read_fw_boot_param(adev, + IPU7_FW_BOOT_STATE_ID); + if (IPU7_BOOT_STATE_CRITICAL(boot_state) || + IPU7_BOOT_STATE_INACTIVE(boot_state)) + break; + usleep_range(1000, 1200); + } + + if (IPU7_BOOT_STATE_CRITICAL(boot_state)) { + dev_err(dev, "critical boot state error 0x%x\n", boot_state); + return -EINVAL; + } else if (!IPU7_BOOT_STATE_INACTIVE(boot_state)) { + dev_err(dev, "stop fw timeout. state: 0x%x\n", boot_state); + return -ETIMEDOUT; + } + + ipu7_boot_cell_stop(adev); + dev_dbg(dev, "stop fw done.\n"); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ipu6_ipu7_boot_stop_fw, "INTEL_IPU6"); diff --git a/drivers/media/pci/intel/ipu6/ipu7-boot.h b/drivers/media/pci/intel/ipu6/ipu7-boot.h new file mode 100644 index 000000000000..f2172b53bed5 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu7-boot.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2026 Intel Corporation */ + +#ifndef IPU7_BOOT_H +#define IPU7_BOOT_H + +#include "ipu7-fw-com.h" + +#define IPU7_BOOT_MSG_VER_MAX_ENTRIES 3U + +struct ipu7_boot_abi_version { + u8 patch; + u8 subminor; + u8 minor; + u8 major; +}; + +struct ipu7_boot_abi_msg_versions { + u8 num_versions; + u8 reserved[3]; + struct ipu7_boot_abi_version versions[IPU7_BOOT_MSG_VER_MAX_ENTRIES]; +}; + +struct ipu7_boot_abi_cfg { + u32 length; + struct ipu7_boot_abi_version config_version; + struct ipu7_boot_abi_msg_versions client_version_support; + u32 pkg_dir; + u32 subsys_config; + u32 uc_tile_frequency; + u16 checksum; + u8 uc_tile_frequency_units; + u8 padding[1]; + u32 reserved[58]; + struct ipu7_fw_com_config fw_com_config; +} __packed; + +int ipu6_ipu7_init_boot_config(struct ipu6_bus_device *adev, + struct ipu7_fw_com_queue_config *qconfigs, + int num_queues, u32 uc_freq, + dma_addr_t subsys_config, u8 major); +void ipu6_ipu7_release_boot_config(struct ipu6_bus_device *adev); +int ipu6_ipu7_boot_start_fw(const struct ipu6_bus_device *adev); +int ipu6_ipu7_boot_stop_fw(const struct ipu6_bus_device *adev); + +#endif diff --git a/drivers/media/pci/intel/ipu6/ipu7-fw-com.c b/drivers/media/pci/intel/ipu6/ipu7-fw-com.c new file mode 100644 index 000000000000..7dd1e683aa92 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu7-fw-com.c @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2026 Intel Corporation + */ + +#include <linux/io.h> + +#include "ipu7-fw-com.h" + +static void __iomem *ipu7_fw_com_get_indices(struct ipu7_fw_com_context *ctx, + u32 q) +{ + return ctx->queue_indices + (q * sizeof(struct ipu7_fw_com_queue_indices)); +} + +void *ipu7_fw_com_get_token(struct ipu7_fw_com_context *ctx, int q) +{ + struct ipu7_fw_com_queue_config *queue_params = &ctx->queue_configs[q]; + void __iomem *queue_indices = ipu7_fw_com_get_indices(ctx, q); + u32 write_index = readl(queue_indices + + offsetof(struct ipu7_fw_com_queue_indices, + write_index)); + u32 read_index = readl(queue_indices + + offsetof(struct ipu7_fw_com_queue_indices, + read_index)); + void *token = NULL; + + if (q < ctx->num_output_queues) { + /* Output queue */ + bool empty = (write_index == read_index); + + if (!empty) + token = queue_params->token_array_mem + + read_index * + queue_params->token_size_in_bytes; + } else { + /* Input queue */ + bool full = (read_index == ((write_index + 1U) % + (u32)queue_params->max_capacity)); + + if (!full) + token = queue_params->token_array_mem + + write_index * queue_params->token_size_in_bytes; + } + return token; +} +EXPORT_SYMBOL_NS_GPL(ipu7_fw_com_get_token, "INTEL_IPU6"); + +void ipu7_fw_com_put_token(struct ipu7_fw_com_context *ctx, int q) +{ + struct ipu7_fw_com_queue_config *queue_params = &ctx->queue_configs[q]; + void __iomem *queue_indices = ipu7_fw_com_get_indices(ctx, q); + u32 offset, index; + + if (q < ctx->num_output_queues) + /* Output queue */ + offset = offsetof(struct ipu7_fw_com_queue_indices, read_index); + + else + /* Input queue */ + offset = offsetof(struct ipu7_fw_com_queue_indices, write_index); + + index = readl(queue_indices + offset); + writel((index + 1U) % queue_params->max_capacity, + queue_indices + offset); +} +EXPORT_SYMBOL_NS_GPL(ipu7_fw_com_put_token, "INTEL_IPU6"); + +struct ipu7_fw_com_queue_params_config * +ipu7_fw_com_get_queue_config(struct ipu7_fw_com_config *config) +{ + return (struct ipu7_fw_com_queue_params_config *)(&config[1]); +} +EXPORT_SYMBOL_NS_GPL(ipu7_fw_com_get_queue_config, "INTEL_IPU6"); diff --git a/drivers/media/pci/intel/ipu6/ipu7-fw-com.h b/drivers/media/pci/intel/ipu6/ipu7-fw-com.h new file mode 100644 index 000000000000..097eaab99547 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu7-fw-com.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2026 Intel Corporation */ + +#ifndef IPU7_FW_COM_H +#define IPU7_FW_COM_H + +#include <linux/types.h> + +struct ipu7_fw_com_queue_config { + void *token_array_mem; + u32 queue_size; + u16 token_size_in_bytes; + u16 max_capacity; +}; + +struct ipu7_fw_com_context { + u16 num_input_queues; + u16 num_output_queues; + struct ipu7_fw_com_queue_config *queue_configs; + void __iomem *queue_indices; + dma_addr_t queue_mem_dma_addr; + void *queue_mem; + u32 queue_mem_size; + struct ipu7_boot_abi_cfg *boot_config; + dma_addr_t boot_config_dma_addr; + u32 boot_config_size; + u32 fw_entry; + struct ipu7_insys_config *fw_config; + dma_addr_t fw_config_dma_addr; +}; + +struct ipu7_fw_com_queue_params_config { + u32 token_array_mem; + u16 token_size_in_bytes; + u16 max_capacity; +}; + +struct ipu7_fw_com_config { + u16 max_output_queues; + u16 max_input_queues; +}; + +struct ipu7_fw_com_queue_indices { + u32 read_index; + u32 write_index; +}; + +void ipu7_fw_com_put_token(struct ipu7_fw_com_context *ctx, int q); +void *ipu7_fw_com_get_token(struct ipu7_fw_com_context *ctx, int q); +struct ipu7_fw_com_queue_params_config * +ipu7_fw_com_get_queue_config(struct ipu7_fw_com_config *config); + +#endif /* IPU7_FW_COM_H */ diff --git a/drivers/media/pci/intel/ipu6/ipu7-fw-isys.c b/drivers/media/pci/intel/ipu6/ipu7-fw-isys.c new file mode 100644 index 000000000000..0876cc54faa7 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu7-fw-isys.c @@ -0,0 +1,796 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2026 Intel Corporation + */ + +#include <linux/cleanup.h> +#include <linux/cacheflush.h> +#include <linux/pm_runtime.h> + +#include "ipu6-bus.h" +#include "ipu6-dma.h" +#include "ipu6-isys.h" +#include "ipu6-platform-regs.h" +#include "ipu7-boot.h" +#include "ipu7-fw-com.h" +#include "ipu7-fw-isys.h" +#include "ipu7-isys-csi2-regs.h" +#include "ipu7-platform-regs.h" + +static void ipu7_fw_isys_cleanup(struct ipu6_isys *isys) +{ + struct ipu6_bus_device *adev = isys->adev; + struct ipu7_fw_com_context *fwctx = isys->fwctx; + + if (!fwctx) + return; + + ipu6_ipu7_release_boot_config(adev); + + if (fwctx->fw_config) { + ipu6_dma_free(adev, sizeof(*fwctx->fw_config), fwctx->fw_config, + fwctx->fw_config_dma_addr, 0); + fwctx->fw_config = NULL; + fwctx->fw_config_dma_addr = 0; + } + + isys->fwctx = NULL; +} + +static int ipu7_fw_isys_open(struct ipu6_isys *isys) +{ + return ipu6_ipu7_boot_start_fw(isys->adev); +} + +static int ipu7_fw_isys_close(struct ipu6_isys *isys) +{ + int ret; + + ret = ipu6_ipu7_boot_stop_fw(isys->adev); + if (ret) + return ret; + + ipu7_fw_isys_cleanup(isys); + + return ret; +} + +static int ipu7_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams) +{ + struct ipu7_fw_com_queue_config *queue_configs; + struct ipu6_bus_device *adev = isys->adev; + struct device *dev = &adev->auxdev.dev; + struct ipu7_insys_config *fw_config; + struct ipu7_fw_com_context *fwctx; + dma_addr_t fw_config_dma_addr; + unsigned int num_queues; + u32 freq; + int ret; + + /* Allocate and init firmware context. */ + fwctx = devm_kzalloc(dev, sizeof(struct ipu7_fw_com_context), + GFP_KERNEL); + if (!fwctx) + return -ENOMEM; + + fwctx->num_input_queues = IPU7_INSYS_MAX_INPUT_QUEUES; + fwctx->num_output_queues = IPU7_INSYS_MAX_OUTPUT_QUEUES; + num_queues = fwctx->num_input_queues + fwctx->num_output_queues; + + queue_configs = devm_kcalloc(dev, num_queues, sizeof(*queue_configs), + GFP_KERNEL); + if (!queue_configs) { + ipu7_fw_isys_cleanup(isys); + return -ENOMEM; + } + fwctx->fw_entry = adev->fw_entry; + fwctx->queue_configs = queue_configs; + queue_configs[IPU7_INSYS_OUTPUT_MSG_QUEUE].max_capacity = + IPU7_ISYS_SIZE_RECV_QUEUE; + queue_configs[IPU7_INSYS_OUTPUT_MSG_QUEUE].token_size_in_bytes = + sizeof(struct ipu7_insys_resp); + queue_configs[IPU7_INSYS_OUTPUT_LOG_QUEUE].max_capacity = + IPU7_ISYS_SIZE_LOG_QUEUE; + queue_configs[IPU7_INSYS_OUTPUT_LOG_QUEUE].token_size_in_bytes = + sizeof(struct ipu7_insys_resp); + queue_configs[IPU7_INSYS_OUTPUT_RESERVED_QUEUE].max_capacity = 0; + queue_configs[IPU7_INSYS_OUTPUT_RESERVED_QUEUE].token_size_in_bytes = 0; + + queue_configs[IPU7_INSYS_INPUT_DEV_QUEUE].max_capacity = + IPU7_ISYS_MAX_STREAMS; + queue_configs[IPU7_INSYS_INPUT_DEV_QUEUE].token_size_in_bytes = + sizeof(struct ipu7_insys_send_queue_token); + + for (unsigned int i = IPU7_INSYS_INPUT_MSG_QUEUE; i < num_queues; i++) { + queue_configs[i].max_capacity = IPU7_ISYS_SIZE_SEND_QUEUE; + queue_configs[i].token_size_in_bytes = + sizeof(struct ipu7_insys_send_queue_token); + } + + /* Allocate ISYS subsys config. */ + fw_config = ipu6_dma_alloc(adev, sizeof(*fw_config), + &fw_config_dma_addr, GFP_KERNEL, 0); + if (!fw_config) { + dev_err(dev, "Failed to allocate isys subsys config.\n"); + ipu7_fw_isys_cleanup(isys); + return -ENOMEM; + } + fwctx->fw_config = fw_config; + fwctx->fw_config_dma_addr = fw_config_dma_addr; + memset(fw_config, 0, sizeof(*fw_config)); + fw_config->logger_config.use_source_severity = 0; + fw_config->logger_config.use_channels_enable_bitmask = 1; + fw_config->logger_config.channels_enable_bitmask = + IPU7_LOGGER_CFG_CHANNEL_ENABLE_SYSCOM; + fw_config->logger_config.hw_printf_buffer_base_addr = 0U; + fw_config->logger_config.hw_printf_buffer_size_bytes = 0U; + fw_config->wdt_config.wdt_timer1_us = 0; + fw_config->wdt_config.wdt_timer2_us = 0; + freq = ipu7_buttress_get_isys_freq(adev->isp); + + ipu6_dma_sync_single(adev, fw_config_dma_addr, + sizeof(struct ipu7_insys_config)); + + isys->fwctx = fwctx; + + ret = ipu6_ipu7_init_boot_config(adev, queue_configs, num_queues, + freq, fw_config_dma_addr, 1U); + if (ret) { + ipu7_fw_isys_cleanup(isys); + return ret; + } + + ret = ipu7_fw_isys_open(isys); + if (ret) + ipu7_fw_isys_cleanup(isys); + + return ret; +} + +static struct ipu7_insys_resp *ipu7_fw_isys_get_resp(struct ipu6_isys *isys) +{ + return ipu7_fw_com_get_token(isys->fwctx, IPU7_INSYS_OUTPUT_MSG_QUEUE); +} + +static void ipu7_fw_isys_put_resp(struct ipu6_isys *isys) +{ + ipu7_fw_com_put_token(isys->fwctx, IPU7_INSYS_OUTPUT_MSG_QUEUE); +} + +static int ipu7_isys_fw_pin_cfg(struct ipu6_isys_video *av, + struct ipu6_isys_stream *stream, + struct media_pad *src_pad, + struct v4l2_mbus_frame_desc_entry *entry, + void *__cfg) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(src_pad->entity); + struct v4l2_subdev_state *state = v4l2_subdev_get_locked_active_state(sd); + struct ipu7_fw_isys_stream_cfg *cfg = __cfg; + struct ipu7_fw_isys_input_pin *input_pin; + struct ipu7_fw_isys_output_pin *output_pin; + struct ipu6_isys_queue *aq = &av->aq; + struct v4l2_mbus_framefmt *fmt; + const struct ipu6_isys_pixelformat *pfmt = + ipu6_isys_get_isys_format(ipu6_isys_get_format(av), 0); + int input_pins = cfg->nof_input_pins++; + int output_pins; + u32 src_stream; + + src_stream = __ipu6_isys_get_src_stream_by_src_pad(state, src_pad->index); + fmt = v4l2_subdev_state_get_format(state, src_pad->index, src_stream); + + input_pin = &cfg->input_pins[input_pins]; + input_pin->input_res.width = fmt->width; + input_pin->input_res.height = fmt->height; + input_pin->dt = entry->bus.csi2.dt; + input_pin->disable_mipi_unpacking = 0; + if (pfmt->bpp == pfmt->bpp_packed && pfmt->bpp % BITS_PER_BYTE) + input_pin->disable_mipi_unpacking = 1; + input_pin->mapped_dt = IPU7_N_INSYS_MIPI_DATA_TYPE; + input_pin->dt_rename_mode = IPU7_INSYS_MIPI_DT_NO_RENAME; + input_pin->sync_msg_map = + IPU7_INSYS_STREAM_SYNC_MSG_SEND_RESP_SOF | + IPU7_INSYS_STREAM_SYNC_MSG_SEND_RESP_SOF_DISCARDED | + IPU7_INSYS_STREAM_SYNC_MSG_SEND_IRQ_SOF | + IPU7_INSYS_STREAM_SYNC_MSG_SEND_IRQ_SOF_DISCARDED; + + output_pins = cfg->nof_output_pins++; + aq->fw_output = output_pins; + av->stream->output_pins_queue[output_pins] = aq; + + output_pin = &cfg->output_pins[output_pins]; + memset(output_pin, 0, sizeof(*output_pin)); + output_pin->link.buffer_lines = 0; + output_pin->link.foreign_key = IPU7_MSG_LINK_FOREIGN_KEY_NONE; + output_pin->link.pbk_id = IPU7_MSG_LINK_PBK_ID_DONT_CARE; + output_pin->link.pbk_slot_id = IPU7_MSG_LINK_PBK_SLOT_ID_DONT_CARE; + output_pin->link.dest = 0; /* IPU_INSYS_OUTPUT_LINK_DEST_MEM */ + output_pin->link.use_sw_managed = 1; + output_pin->crop.line_top = 0; + output_pin->crop.line_bottom = 0; + output_pin->dpcm.enable = 0; + output_pin->ft = pfmt->css_pixelformat; + output_pin->stride = ipu6_isys_get_bytes_per_line(av); + output_pin->send_irq = 1; + output_pin->input_pin_id = input_pins; + + return 0; +} + +static int +ipu7_fw_isys_send_cmd(struct ipu6_isys *isys, const unsigned int stream_handle, + void *cpu_mapped_buf, dma_addr_t dma_mapped_buf, + size_t size, u16 send_type) +{ + struct ipu7_fw_com_context *ctx = isys->fwctx; + /*struct device *dev = &isys->adev->auxdev.dev;*/ + struct ipu7_insys_send_queue_token *token; + + if (send_type >= N_IPU7_INSYS_SEND_TYPE) + return -EINVAL; + + if (cpu_mapped_buf) + clflush_cache_range(cpu_mapped_buf, size); + + token = ipu7_fw_com_get_token(ctx, stream_handle + + IPU7_INSYS_INPUT_MSG_QUEUE); + if (!token) + return -EBUSY; + + token->addr = dma_mapped_buf; + token->buf_handle = (unsigned long)cpu_mapped_buf; + token->send_type = send_type; + token->stream_id = stream_handle; + token->flag = IPU7_INSYS_SEND_QUEUE_TOKEN_FLAG_NONE; + + ipu7_fw_com_put_token(ctx, stream_handle + IPU7_INSYS_INPUT_MSG_QUEUE); + /* now wakeup FW */ + ipu7_buttress_wakeup_isys(isys->adev->isp); + + return 0; +} + +static void ipu7_fw_isys_dump_stream_cfg(struct device *dev, + struct isys_fw_msgs *msg) +{ + struct ipu7_fw_isys_stream_cfg *cfg = &msg->ipu7.stream; + unsigned int i; + + dev_dbg(dev, "---------------------------\n"); + dev_dbg(dev, "IPU_FW_ISYS_STREAM_CFG_DATA\n"); + + dev_dbg(dev, ".port id %d\n", cfg->port_id); + dev_dbg(dev, ".vc %d\n", cfg->vc); + dev_dbg(dev, ".nof_input_pins = %d\n", cfg->nof_input_pins); + dev_dbg(dev, ".nof_output_pins = %d\n", cfg->nof_output_pins); + dev_dbg(dev, ".stream_msg_map = 0x%x\n", cfg->stream_msg_map); + + for (i = 0; i < cfg->nof_input_pins; i++) { + dev_dbg(dev, ".input_pin[%d]:\n", i); + dev_dbg(dev, "\t.dt = 0x%0x\n", + cfg->input_pins[i].dt); + dev_dbg(dev, "\t.disable_mipi_unpacking = %d\n", + cfg->input_pins[i].disable_mipi_unpacking); + dev_dbg(dev, "\t.dt_rename_mode = %d\n", + cfg->input_pins[i].dt_rename_mode); + dev_dbg(dev, "\t.mapped_dt = 0x%0x\n", + cfg->input_pins[i].mapped_dt); + dev_dbg(dev, "\t.input_res = %d x %d\n", + cfg->input_pins[i].input_res.width, + cfg->input_pins[i].input_res.height); + dev_dbg(dev, "\t.sync_msg_map = 0x%x\n", + cfg->input_pins[i].sync_msg_map); + } + + for (i = 0; i < cfg->nof_output_pins; i++) { + dev_dbg(dev, ".output_pin[%d]:\n", i); + dev_dbg(dev, "\t.input_pin_id = %d\n", + cfg->output_pins[i].input_pin_id); + dev_dbg(dev, "\t.stride = %d\n", cfg->output_pins[i].stride); + dev_dbg(dev, "\t.send_irq = %d\n", + cfg->output_pins[i].send_irq); + dev_dbg(dev, "\t.ft = %d\n", cfg->output_pins[i].ft); + + dev_dbg(dev, "\t.link.buffer_lines = %d\n", + cfg->output_pins[i].link.buffer_lines); + dev_dbg(dev, "\t.link.foreign_key = %d\n", + cfg->output_pins[i].link.foreign_key); + dev_dbg(dev, "\t.link.granularity_pointer_update = %d\n", + cfg->output_pins[i].link.granularity_pointer_update); + dev_dbg(dev, "\t.link.msg_link_streaming_mode = %d\n", + cfg->output_pins[i].link.msg_link_streaming_mode); + dev_dbg(dev, "\t.link.pbk_id = %d\n", + cfg->output_pins[i].link.pbk_id); + dev_dbg(dev, "\t.link.pbk_slot_id = %d\n", + cfg->output_pins[i].link.pbk_slot_id); + dev_dbg(dev, "\t.link.dest = %d\n", + cfg->output_pins[i].link.dest); + dev_dbg(dev, "\t.link.use_sw_managed = %d\n", + cfg->output_pins[i].link.use_sw_managed); + dev_dbg(dev, "\t.link.is_snoop = %d\n", + cfg->output_pins[i].link.is_snoop); + + dev_dbg(dev, "\t.crop.line_top = %d\n", + cfg->output_pins[i].crop.line_top); + dev_dbg(dev, "\t.crop.line_bottom = %d\n", + cfg->output_pins[i].crop.line_bottom); + + dev_dbg(dev, "\t.dpcm_enable = %d\n", + cfg->output_pins[i].dpcm.enable); + dev_dbg(dev, "\t.dpcm.type = %d\n", + cfg->output_pins[i].dpcm.type); + dev_dbg(dev, "\t.dpcm.predictor = %d\n", + cfg->output_pins[i].dpcm.predictor); + } + dev_dbg(dev, "---------------------------\n"); +} + +static void ipu7_fw_isys_dump_frame_buf_set(struct device *dev, + struct isys_fw_msgs *msg, + unsigned int outputs) +{ + struct ipu7_fw_isys_frame_buff_set *buf = &msg->ipu7.frame; + + dev_dbg(dev, "--------------------------\n"); + dev_dbg(dev, "IPU_ISYS_BUFF_SET\n"); + dev_dbg(dev, ".capture_msg_map = %d\n", buf->capture_msg_map); + dev_dbg(dev, ".frame_id = %d\n", buf->frame_id); + dev_dbg(dev, ".skip_frame = %d\n", buf->skip_frame); + + for (unsigned int i = 0; i < outputs; i++) { + dev_dbg(dev, ".output_pin[%d]:\n", i); + dev_dbg(dev, "\t.user_token = %llx\n", + buf->output_pins[i].user_token); + dev_dbg(dev, "\t.addr = 0x%x\n", buf->output_pins[i].addr); + } + dev_dbg(dev, "---------------------------\n"); +} + +static int ipu7_fw_isys_prepare_stream_cfg(struct ipu6_isys_stream *stream, + struct v4l2_mbus_frame_desc *desc, + struct isys_fw_msgs *msg) +{ + struct ipu7_fw_isys_stream_cfg *cfg = &msg->ipu7.stream; + struct device *dev = &stream->isys->adev->auxdev.dev; + int ret; + + memset(cfg, 0, sizeof(*cfg)); + cfg->port_id = stream->asd->source; + cfg->vc = stream->vc; + cfg->stream_msg_map = IPU7_INSYS_STREAM_ENABLE_MSG_SEND_RESP | + IPU7_INSYS_STREAM_ENABLE_MSG_SEND_IRQ; + + ret = ipu6_isys_fw_pins_prepare(stream, desc, ipu7_isys_fw_pin_cfg, + cfg); + if (ret) + return ret; + + stream->nr_output_pins = cfg->nof_output_pins; + + ipu7_fw_isys_dump_stream_cfg(dev, msg); + + return 0; +} + +static void +ipu7_isys_buf_to_fw_frame_buf_pin(struct vb2_buffer *vb, + struct ipu7_fw_isys_frame_buff_set *set) +{ + struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); + struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb); + struct ipu6_isys_video_buffer *ivb = + vb2_buffer_to_ipu6_isys_video_buffer(vvb); + + set->output_pins[aq->fw_output].addr = ivb->dma_addr; + set->output_pins[aq->fw_output].user_token = (u64)(uintptr_t)set; +} + +static void +ipu7_fw_isys_prepare_buf_set(struct isys_fw_msgs *msg, + struct ipu6_isys_stream *stream, + struct ipu6_isys_buffer_list *bl) +{ + struct ipu7_fw_isys_frame_buff_set *set = &msg->ipu7.frame; + struct ipu6_isys_buffer *ib; + + WARN_ON(!bl->nbufs); + + memset(set, 0, sizeof(*set)); + set->capture_msg_map = IPU7_INSYS_FRAME_ENABLE_MSG_SEND_RESP | + IPU7_INSYS_FRAME_ENABLE_MSG_SEND_IRQ; + set->frame_id = atomic_fetch_inc(&stream->buf_id) % 256; + + list_for_each_entry(ib, &bl->head, head) { + struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib); + + ipu7_isys_buf_to_fw_frame_buf_pin(vb, set); + } + + dev_dbg(&stream->isys->adev->auxdev.dev, + "ipu7 frame_buf: cap_map=0x%x fid=%u opin[0].addr=0x%x token=0x%llx\n", + set->capture_msg_map, set->frame_id, + set->output_pins[0].addr, set->output_pins[0].user_token); +} + +static int ipu7_fw_isys_stream_open(struct ipu6_isys *isys, + const unsigned int stream_handle, + struct isys_fw_msgs *msg) +{ + return ipu7_fw_isys_send_cmd(isys, stream_handle, &msg->ipu7.stream, + msg->dma_addr, sizeof(msg->ipu7.stream), + IPU7_INSYS_SEND_TYPE_STREAM_OPEN); +} + +static int ipu7_fw_isys_stream_close(struct ipu6_isys *isys, + const unsigned int stream_handle) +{ + return ipu7_fw_isys_send_cmd(isys, stream_handle, NULL, 0, 0, + IPU7_INSYS_SEND_TYPE_STREAM_CLOSE); +} + +static int ipu7_fw_isys_stream_flush(struct ipu6_isys *isys, + const unsigned int stream_handle) +{ + return ipu7_fw_isys_send_cmd(isys, stream_handle, NULL, 0, 0, + IPU7_INSYS_SEND_TYPE_STREAM_FLUSH); +} + +static int ipu7_fw_isys_stream_start(struct ipu6_isys *isys, + const unsigned int stream_handle, + struct isys_fw_msgs *msg, bool capture) +{ + return ipu7_fw_isys_send_cmd(isys, stream_handle, &msg->ipu7.frame, + msg->dma_addr, sizeof(msg->ipu7.frame), + IPU7_INSYS_SEND_TYPE_STREAM_START_AND_CAPTURE); +} + +static int ipu7_fw_isys_stream_capture(struct ipu6_isys *isys, + const unsigned int stream_handle, + struct isys_fw_msgs *msg) +{ + return ipu7_fw_isys_send_cmd(isys, stream_handle, &msg->ipu7.frame, + msg->dma_addr, sizeof(msg->ipu7.frame), + IPU7_INSYS_SEND_TYPE_STREAM_CAPTURE); +} + +const struct ipu6_fw_isys_ops ipu7_fw_isys_ops = { + .init = ipu7_fw_isys_init, + .close = ipu7_fw_isys_close, + .send_cmd = ipu7_fw_isys_send_cmd, + .cleanup = ipu7_fw_isys_cleanup, + .prepare_stream_cfg = ipu7_fw_isys_prepare_stream_cfg, + .prepare_buf_set = ipu7_fw_isys_prepare_buf_set, + .stream_open = ipu7_fw_isys_stream_open, + .stream_start = ipu7_fw_isys_stream_start, + .stream_capture = ipu7_fw_isys_stream_capture, + .stream_flush = ipu7_fw_isys_stream_flush, + .stream_close = ipu7_fw_isys_stream_close, + .dump_stream_cfg = ipu7_fw_isys_dump_stream_cfg, + .dump_frame_buf_set = ipu7_fw_isys_dump_frame_buf_set, +}; + +static const struct ipu7_csi2_error { + const char *error_string; + bool is_info_only; +} dphy_rx_errors[] = { + { "Error handler FIFO full", false }, + { "Reserved Short Packet encoding detected", true }, + { "Reserved Long Packet encoding detected", true }, + { "Received packet is too short", false}, + { "Received packet is too long", false}, + { "Short packet discarded due to errors", false }, + { "Long packet discarded due to errors", false }, + { "CSI Combo Rx interrupt", false }, + { "IDI CDC FIFO overflow(remaining bits are reserved as 0)", false }, + { "Received NULL packet", true }, + { "Received blanking packet", true }, + { "Tie to 0", true }, +}; + +static void ipu7_isys_register_errors(struct ipu6_isys_csi2 *csi2) +{ + u32 offset = IPU7_IS_IO_CSI2_ERR_LEGACY_IRQ_CTL_BASE(csi2->port); + u32 status = readl(csi2->base + offset + IPU7_IRQ_CTL_STATUS); + u32 mask = IPU7_CSI_RX_ERROR_IRQ_MASK; + + if (!status) + return; + + dev_dbg(&csi2->isys->adev->auxdev.dev, "csi2-%u error status 0x%08x\n", + csi2->port, status); + + writel(status & mask, csi2->base + offset + IPU7_IRQ_CTL_CLEAR); + csi2->receiver_errors |= status & mask; +} + +static void ipu7_isys_csi2_error(struct ipu6_isys_csi2 *csi2) +{ + u32 status; + + /* Register errors once more in case of error interrupts are disabled */ + ipu7_isys_register_errors(csi2); + status = csi2->receiver_errors; + csi2->receiver_errors = 0; + + for (unsigned int i = 0; i < ARRAY_SIZE(dphy_rx_errors); i++) { + if (status & BIT(i)) + dev_err_ratelimited(&csi2->isys->adev->auxdev.dev, + "csi2-%i error: %s\n", + csi2->port, + dphy_rx_errors[i].error_string); + } +} + +static const struct resp_to_msg { + enum ipu7_insys_resp_type type; + const char *msg; +} is_fw_msg[] = { + { IPU7_INSYS_RESP_TYPE_STREAM_OPEN_DONE, "STREAM_OPEN_DONE" }, + { IPU7_INSYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK, + "STREAM_START_AND_CAPTURE_ACK" }, + { IPU7_INSYS_RESP_TYPE_STREAM_CAPTURE_ACK, "STREAM_CAPTURE_ACK" }, + { IPU7_INSYS_RESP_TYPE_STREAM_ABORT_ACK, "STREAM_ABORT_ACK" }, + { IPU7_INSYS_RESP_TYPE_STREAM_FLUSH_ACK, "STREAM_FLUSH_ACK" }, + { IPU7_INSYS_RESP_TYPE_STREAM_CLOSE_ACK, "STREAM_CLOSE_ACK" }, + { IPU7_INSYS_RESP_TYPE_PIN_DATA_READY, "PIN_DATA_READY" }, + { IPU7_INSYS_RESP_TYPE_FRAME_SOF, "FRAME_SOF" }, + { IPU7_INSYS_RESP_TYPE_FRAME_EOF, "FRAME_EOF" }, + { IPU7_INSYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE, + "STREAM_START_AND_CAPTURE_DONE" }, + { IPU7_INSYS_RESP_TYPE_STREAM_CAPTURE_DONE, "STREAM_CAPTURE_DONE" }, + { N_IPU7_INSYS_RESP_TYPE, "N_IPU7_INSYS_RESP_TYPE" }, +}; + +static int ipu7_isys_isr_one(struct ipu6_bus_device *adev) +{ + struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); + struct ipu6_isys_stream *stream = NULL; + struct device *dev = &adev->auxdev.dev; + struct ipu6_isys_csi2 *csi2 = NULL; + struct ipu7_fw_isys_msg_err err_info; + struct isys_fw_msgs *isys_fw_msg; + struct ipu7_insys_resp *resp; + unsigned long flags; + u64 ts; + + resp = ipu7_fw_isys_get_resp(isys); + if (!resp) + return 1; + + if (resp->type >= N_IPU7_INSYS_RESP_TYPE) { + dev_err(dev, "Unknown response type %u stream %u\n", + resp->type, resp->stream_id); + ipu7_fw_isys_put_resp(isys); + return 1; + } + + err_info = resp->error_info; + ts = ((u64)resp->timestamp[1] << 32) | resp->timestamp[0]; + + if (err_info.err_group == INSYS_MSG_ERR_GROUP_CAPTURE && + err_info.err_code == INSYS_MSG_ERR_CAPTURE_SYNC_FRAME_DROP) { + /* receive a sp w/o command, firmware drop it */ + dev_dbg(dev, "FRAME DROP: %02u %s stream %u\n", + resp->type, is_fw_msg[resp->type].msg, + resp->stream_id); + dev_dbg(dev, "\tpin %u buf_id %llx frame %u\n", + resp->pin_id, resp->buf_id, resp->frame_id); + dev_dbg(dev, "\terror group %u code %u details [%u %u]\n", + err_info.err_group, err_info.err_code, + err_info.err_detail[0], err_info.err_detail[1]); + } else if (err_info.err_code) { + dev_err(dev, "%02u %s stream %u pin %u buf_id %llx frame %u\n", + resp->type, is_fw_msg[resp->type].msg, resp->stream_id, + resp->pin_id, resp->buf_id, resp->frame_id); + dev_err(dev, "\terror group %u code %u details [%u %u]\n", + err_info.err_group, err_info.err_code, + err_info.err_detail[0], err_info.err_detail[1]); + } else { + dev_dbg(dev, "%02u %s stream %u pin %u buf_id %llx frame %u\n", + resp->type, is_fw_msg[resp->type].msg, resp->stream_id, + resp->pin_id, resp->buf_id, resp->frame_id); + dev_dbg(dev, "\tts %llu\n", ts); + } + + if (resp->stream_id >= IPU7_ISYS_MAX_STREAMS) { + dev_err(dev, "bad stream handle %u\n", + resp->stream_id); + goto leave_nounlock; + } + + spin_lock_irqsave(&isys->streams_lock, flags); + + stream = resp->stream_id < IPU6_ISYS_MAX_STREAMS ? + isys->streams_by_handle[resp->stream_id] : NULL; + if (!stream) { + dev_err(dev, "stream of stream_handle %u is unused\n", + resp->stream_id); + goto leave; + } + + stream->error = err_info.err_code; + + if (stream->asd) + csi2 = ipu6_isys_subdev_to_csi2(stream->asd); + + switch (resp->type) { + case IPU7_INSYS_RESP_TYPE_STREAM_OPEN_DONE: + complete(&stream->stream_open_completion); + break; + case IPU7_INSYS_RESP_TYPE_STREAM_CLOSE_ACK: + complete(&stream->stream_close_completion); + break; + case IPU7_INSYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK: + complete(&stream->stream_start_completion); + break; + case IPU7_INSYS_RESP_TYPE_STREAM_ABORT_ACK: + complete(&stream->stream_stop_completion); + break; + case IPU7_INSYS_RESP_TYPE_STREAM_FLUSH_ACK: + complete(&stream->stream_stop_completion); + break; + case IPU7_INSYS_RESP_TYPE_PIN_DATA_READY: + /* + * firmware only release the capture msg until software + * get pin_data_ready event + */ + isys_fw_msg = container_of((void *)(uintptr_t)resp->buf_id, + struct isys_fw_msgs, dummy); + + ipu6_put_fw_msg_buf(ipu6_bus_get_drvdata(adev), isys_fw_msg); + if (resp->pin_id < IPU6_ISYS_OUTPUT_PINS) + ipu6_stream_buf_ready(stream, resp->pin_id, + resp->pin.addr, ts, 0); + else + dev_err(dev, "No handler for pin %u ready\n", + resp->pin_id); + if (csi2) + ipu7_isys_csi2_error(csi2); + + break; + case IPU7_INSYS_RESP_TYPE_STREAM_CAPTURE_ACK: + break; + case IPU7_INSYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE: + case IPU7_INSYS_RESP_TYPE_STREAM_CAPTURE_DONE: + break; + case IPU7_INSYS_RESP_TYPE_FRAME_SOF: + if (csi2) + ipu6_isys_csi2_sof_event_by_stream(stream); + + stream->seq[stream->seq_index].sequence = + atomic_read(&stream->sequence) - 1U; + stream->seq[stream->seq_index].timestamp = ts; + dev_dbg(dev, + "SOF: stream %u frame %u (index %u), ts 0x%16.16llx\n", + resp->stream_id, resp->frame_id, + stream->seq[stream->seq_index].sequence, ts); + stream->seq_index = (stream->seq_index + 1U) + % IPU6_ISYS_MAX_PARALLEL_SOF; + break; + case IPU7_INSYS_RESP_TYPE_FRAME_EOF: + if (csi2) + ipu6_isys_csi2_eof_event_by_stream(stream); + + dev_dbg(dev, "eof: stream %d(index %u) ts 0x%16.16llx\n", + resp->stream_id, + stream->seq[stream->seq_index].sequence, ts); + break; + default: + dev_err(dev, "Unknown response type %u stream %u\n", + resp->type, resp->stream_id); + break; + } + +leave: + spin_unlock_irqrestore(&isys->streams_lock, flags); + +leave_nounlock: + ipu7_fw_isys_put_resp(isys); + + return 0; +} + +#define IPU7_NR_OF_CSI2_VC 16U +static void ipu7_isys_csi2_isr(struct ipu6_isys_csi2 *csi2) +{ + struct device *dev = &csi2->isys->adev->auxdev.dev; + struct ipu6_device *isp = csi2->isys->adev->isp; + struct ipu6_isys_stream *s; + u32 sync, offset; + u32 fe = 0; + u8 vc; + + ipu7_isys_register_errors(csi2); + + offset = IPU7_IS_IO_CSI2_SYNC_LEGACY_IRQ_CTL_BASE(csi2->port); + sync = readl(csi2->base + offset + IPU7_IRQ_CTL_STATUS); + writel(sync, csi2->base + offset + IPU7_IRQ_CTL_CLEAR); + dev_dbg(dev, "csi2-%u sync status 0x%08x\n", csi2->port, sync); + + if (!IS_IPU7_MTL(isp)) { + fe = readl(csi2->base + offset + IPU7_IRQ1_CTL_STATUS); + writel(fe, csi2->base + offset + IPU7_IRQ1_CTL_CLEAR); + dev_dbg(dev, "csi2-%u FE status 0x%08x\n", csi2->port, fe); + } + + for (vc = 0; vc < IPU7_NR_OF_CSI2_VC && (sync || fe); vc++) { + s = csi2->streams_by_vc[vc]; + if (!s) + continue; + + if (!IS_IPU7_MTL(isp)) { + if (sync & IPU7P5_CSI_RX_SYNC_FS_VC & (1U << vc)) + ipu6_isys_csi2_sof_event_by_stream(s); + + if (fe & IPU7P5_CSI_RX_SYNC_FE_VC & (1U << vc)) + ipu6_isys_csi2_eof_event_by_stream(s); + } else { + if (sync & IPU7_CSI_RX_SYNC_FS_VC & (1U << (vc * 2))) + ipu6_isys_csi2_sof_event_by_stream(s); + + if (sync & IPU7_CSI_RX_SYNC_FE_VC & (2U << (vc * 2))) + ipu6_isys_csi2_eof_event_by_stream(s); + } + } +} + +static void ipu7_dispatch_csi2_isr(struct ipu6_isys *isys, u32 status) +{ + for (unsigned int i = 0; i < isys->pdata->ipdata->csi2.nports; i++) { + if (!isys->csi2[i].base) + continue; + if (status & isys->csi2[i].legacy_irq_mask) + ipu7_isys_csi2_isr(&isys->csi2[i]); + } +} + +irqreturn_t ipu7_isys_isr(struct ipu6_bus_device *adev) +{ + struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); + void __iomem *base = isys->pdata->base; + u32 status_sw, status_csi; + u32 csi_offset, sw_offset; + int pm_status; + + pm_status = pm_runtime_get_if_active(&adev->auxdev.dev); + if (!pm_status) + return 0; + + csi_offset = IPU7_IS_IO_CSI2_LEGACY_IRQ_CTRL_BASE; + sw_offset = IPU7_IS_UC_CTRL_BASE; + + status_csi = readl(base + csi_offset + IPU7_IRQ_CTL_STATUS); + status_sw = readl(base + sw_offset + IPU7_TO_SW_IRQ_CNTL_STATUS); + + if (!status_csi && !status_sw) { + if (pm_status > 0) + pm_runtime_put(&adev->auxdev.dev); + return IRQ_NONE; + } + + do { + writel(status_sw, base + sw_offset + IPU7_TO_SW_IRQ_CNTL_CLEAR); + writel(status_csi, base + csi_offset + IPU7_IRQ_CTL_CLEAR); + + if (isys->isr_csi2_bits & status_csi) + ipu7_dispatch_csi2_isr(isys, status_csi); + + if (!ipu7_isys_isr_one(adev)) + status_sw = IPU7_TO_SW_IRQ_FW; + else + status_sw = 0; + + status_csi = readl(base + csi_offset + IPU7_IRQ_CTL_STATUS); + status_sw |= readl(base + sw_offset + + IPU7_TO_SW_IRQ_CNTL_STATUS); + } while ((status_csi & isys->isr_csi2_bits) || + (status_sw & IPU7_TO_SW_IRQ_FW)); + + writel(IPU7_IS_UC_TO_SW_IRQ_MASK, + base + sw_offset + IPU7_TO_SW_IRQ_CNTL_MASK_N); + + if (pm_status > 0) + pm_runtime_put(&adev->auxdev.dev); + + return IRQ_HANDLED; +} diff --git a/drivers/media/pci/intel/ipu6/ipu7-fw-isys.h b/drivers/media/pci/intel/ipu6/ipu7-fw-isys.h new file mode 100644 index 000000000000..d5289f6add8c --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu7-fw-isys.h @@ -0,0 +1,296 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2026 Intel Corporation */ + +#ifndef IPU7_FW_ISYS_H +#define IPU7_FW_ISYS_H + +#define IPU7_FWLOG_MAX_LOGGER_SOURCES (64U) +#define IPU7_INSYS_MAX_OUTPUT_QUEUES 3U +#define IPU7_INSYS_STREAM_ID_MAX 16U +#define IPU7_INSYS_MAX_INPUT_QUEUES (IPU7_INSYS_STREAM_ID_MAX + 1U) +#define IPU7_INSYS_OUTPUT_MSG_QUEUE 0U +#define IPU7_INSYS_OUTPUT_LOG_QUEUE 1U +#define IPU7_INSYS_OUTPUT_RESERVED_QUEUE 2U +#define IPU7_INSYS_INPUT_DEV_QUEUE 3U + +#define IPU7_INSYS_INPUT_FIRST_QUEUE 3U +#define IPU7_INSYS_INPUT_MSG_QUEUE 4U +#define IPU7_INSYS_INPUT_MSG_MAX_QUEUE 16U + +#define IPU7_MSG_ERR_MAX_DETAILS 4U +#define IPU7_ISYS_SIZE_RECV_QUEUE 40U +#define IPU7_ISYS_SIZE_LOG_QUEUE 256U +#define IPU7_ISYS_SIZE_SEND_QUEUE 40U +#define IPU7_ISYS_NUM_RECV_QUEUE 1U +#define IPU7_INSYS_SEND_QUEUE_TOKEN_FLAG_NONE 0U + +#define IPU7_LOGGER_CFG_CHANNEL_ENABLE_SYSCOM BIT(1) + +#define IPU7_ISYS_MAX_STREAMS 16U +#define IPU7_MAX_OPINS 4 +#define IPU7_MAX_IPINS 4 + +#define IPU7_N_INSYS_MIPI_DATA_TYPE 0x40 + +#define IPU7_MSG_LINK_FOREIGN_KEY_NONE (65535U) +#define IPU7_MSG_LINK_PBK_ID_DONT_CARE (255U) +#define IPU7_MSG_LINK_PBK_SLOT_ID_DONT_CARE (255U) + +#define IPU7_INSYS_STREAM_SYNC_MSG_SEND_RESP_SOF BIT(0) +#define IPU7_INSYS_STREAM_SYNC_MSG_SEND_RESP_EOF BIT(1) +#define IPU7_INSYS_STREAM_SYNC_MSG_SEND_IRQ_SOF BIT(2) +#define IPU7_INSYS_STREAM_SYNC_MSG_SEND_IRQ_EOF BIT(3) +#define IPU7_INSYS_STREAM_SYNC_MSG_SEND_RESP_SOF_DISCARDED BIT(4) +#define IPU7_INSYS_STREAM_SYNC_MSG_SEND_RESP_EOF_DISCARDED BIT(5) +#define IPU7_INSYS_STREAM_SYNC_MSG_SEND_IRQ_SOF_DISCARDED BIT(6) +#define IPU7_INSYS_STREAM_SYNC_MSG_SEND_IRQ_EOF_DISCARDED BIT(7) + +#define IPU7_INSYS_STREAM_MSG_SEND_RESP_STREAM_OPEN_DONE BIT(0) +#define IPU7_INSYS_STREAM_MSG_SEND_IRQ_STREAM_OPEN_DONE BIT(1) +#define IPU7_INSYS_STREAM_MSG_SEND_RESP_STREAM_START_ACK BIT(2) +#define IPU7_INSYS_STREAM_MSG_SEND_IRQ_STREAM_START_ACK BIT(3) +#define IPU7_INSYS_STREAM_MSG_SEND_RESP_STREAM_CLOSE_ACK BIT(4) +#define IPU7_INSYS_STREAM_MSG_SEND_IRQ_STREAM_CLOSE_ACK BIT(5) +#define IPU7_INSYS_STREAM_MSG_SEND_RESP_STREAM_FLUSH_ACK BIT(6) +#define IPU7_INSYS_STREAM_MSG_SEND_IRQ_STREAM_FLUSH_ACK BIT(7) +#define IPU7_INSYS_STREAM_MSG_SEND_RESP_STREAM_ABORT_ACK BIT(8) +#define IPU7_INSYS_STREAM_MSG_SEND_IRQ_STREAM_ABORT_ACK BIT(9) + +#define IPU7_INSYS_STREAM_ENABLE_MSG_SEND_RESP ( \ + IPU7_INSYS_STREAM_MSG_SEND_RESP_STREAM_OPEN_DONE | \ + IPU7_INSYS_STREAM_MSG_SEND_RESP_STREAM_START_ACK | \ + IPU7_INSYS_STREAM_MSG_SEND_RESP_STREAM_CLOSE_ACK | \ + IPU7_INSYS_STREAM_MSG_SEND_RESP_STREAM_FLUSH_ACK | \ + IPU7_INSYS_STREAM_MSG_SEND_RESP_STREAM_ABORT_ACK) +#define IPU7_INSYS_STREAM_ENABLE_MSG_SEND_IRQ ( \ + IPU7_INSYS_STREAM_MSG_SEND_IRQ_STREAM_OPEN_DONE | \ + IPU7_INSYS_STREAM_MSG_SEND_IRQ_STREAM_START_ACK | \ + IPU7_INSYS_STREAM_MSG_SEND_IRQ_STREAM_CLOSE_ACK | \ + IPU7_INSYS_STREAM_MSG_SEND_IRQ_STREAM_FLUSH_ACK | \ + IPU7_INSYS_STREAM_MSG_SEND_IRQ_STREAM_ABORT_ACK) + +#define IPU7_INSYS_FRAME_MSG_SEND_RESP_CAPTURE_ACK BIT(0) +#define IPU7_INSYS_FRAME_MSG_SEND_IRQ_CAPTURE_ACK BIT(1) +#define IPU7_INSYS_FRAME_MSG_SEND_RESP_CAPTURE_DONE BIT(2) +#define IPU7_INSYS_FRAME_MSG_SEND_IRQ_CAPTURE_DONE BIT(3) +#define IPU7_INSYS_FRAME_MSG_SEND_RESP_PIN_DATA_READY BIT(4) +#define IPU7_INSYS_FRAME_MSG_SEND_IRQ_PIN_DATA_READY BIT(5) + +#define IPU7_INSYS_FRAME_ENABLE_MSG_SEND_RESP ( \ + IPU7_INSYS_FRAME_MSG_SEND_RESP_CAPTURE_ACK | \ + IPU7_INSYS_FRAME_MSG_SEND_RESP_CAPTURE_DONE | \ + IPU7_INSYS_FRAME_MSG_SEND_RESP_PIN_DATA_READY) +#define IPU7_INSYS_FRAME_ENABLE_MSG_SEND_IRQ ( \ + IPU7_INSYS_FRAME_MSG_SEND_IRQ_CAPTURE_ACK | \ + IPU7_INSYS_FRAME_MSG_SEND_IRQ_CAPTURE_DONE | \ + IPU7_INSYS_FRAME_MSG_SEND_IRQ_PIN_DATA_READY) + +enum ipu7_insys_send_type { + IPU7_INSYS_SEND_TYPE_STREAM_OPEN = 0, + IPU7_INSYS_SEND_TYPE_STREAM_START_AND_CAPTURE = 1, + IPU7_INSYS_SEND_TYPE_STREAM_CAPTURE = 2, + IPU7_INSYS_SEND_TYPE_STREAM_ABORT = 3, + IPU7_INSYS_SEND_TYPE_STREAM_FLUSH = 4, + IPU7_INSYS_SEND_TYPE_STREAM_CLOSE = 5, + N_IPU7_INSYS_SEND_TYPE +}; + +enum ipu7_insys_resp_type { + IPU7_INSYS_RESP_TYPE_STREAM_OPEN_DONE = 0, + IPU7_INSYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK = 1, + IPU7_INSYS_RESP_TYPE_STREAM_CAPTURE_ACK = 2, + IPU7_INSYS_RESP_TYPE_STREAM_ABORT_ACK = 3, + IPU7_INSYS_RESP_TYPE_STREAM_FLUSH_ACK = 4, + IPU7_INSYS_RESP_TYPE_STREAM_CLOSE_ACK = 5, + IPU7_INSYS_RESP_TYPE_PIN_DATA_READY = 6, + IPU7_INSYS_RESP_TYPE_FRAME_SOF = 7, + IPU7_INSYS_RESP_TYPE_FRAME_EOF = 8, + IPU7_INSYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE = 9, + IPU7_INSYS_RESP_TYPE_STREAM_CAPTURE_DONE = 10, + IPU7_INSYS_RESP_TYPE_PWM_IRQ = 11, + N_IPU7_INSYS_RESP_TYPE +}; + +enum ipu7_insys_mipi_dt_rename_mode { + IPU7_INSYS_MIPI_DT_NO_RENAME = 0, + IPU7_INSYS_MIPI_DT_RENAMED_MODE = 1, + N_IPU7_INSYS_MIPI_DT_MODE +}; + +enum insys_msg_err_capture { + INSYS_MSG_ERR_CAPTURE_OK = 0, + INSYS_MSG_ERR_CAPTURE_STREAM_ID = 1, + INSYS_MSG_ERR_CAPTURE_PAYLOAD_PTR = 2, + INSYS_MSG_ERR_CAPTURE_MEM_SLOT = 3, + INSYS_MSG_ERR_CAPTURE_STREAMING_MODE = 4, + INSYS_MSG_ERR_CAPTURE_AVAILABLE_CMD_SLOT = 5, + INSYS_MSG_ERR_CAPTURE_CONSUMED_CMD_SLOT = 6, + INSYS_MSG_ERR_CAPTURE_CMD_SLOT_PAYLOAD_PTR = 7, + INSYS_MSG_ERR_CAPTURE_CMD_PREPARE = 8, + INSYS_MSG_ERR_CAPTURE_OUTPUT_PIN = 9, + INSYS_MSG_ERR_CAPTURE_SYNC_FRAME_DROP = 10, + INSYS_MSG_ERR_CAPTURE_FRAME_MESSAGES_MAP = 11, + INSYS_MSG_ERR_CAPTURE_TIMEOUT = 12, + INSYS_MSG_ERR_CAPTURE_INVALID_STREAM_STATE = 13, + INSYS_MSG_ERR_CAPTURE_HW_ERR_MULTIBIT_PH_ERROR_DETECTED = 14, + INSYS_MSG_ERR_CAPTURE_HW_ERR_PAYLOAD_CRC_ERROR = 15, + INSYS_MSG_ERR_CAPTURE_HW_ERR_INPUT_DATA_LOSS_ELASTIC_FIFO_OVFL = 16, + INSYS_MSG_ERR_CAPTURE_HW_ERR_PIXEL_BUFFER_OVERFLOW = 17, + INSYS_MSG_ERR_CAPTURE_HW_ERR_BAD_FRAME_DIM = 18, + INSYS_MSG_ERR_CAPTURE_HW_ERR_PHY_SYNC_ERR = 19, + INSYS_MSG_ERR_CAPTURE_HW_ERR_SECURE_TOUCH = 20, + INSYS_MSG_ERR_CAPTURE_HW_ERR_MASTER_SLAVE_SYNC_ERR = 21, + INSYS_MSG_ERR_CAPTURE_FRAME_SKIP_ERR = 22, + INSYS_MSG_ERR_CAPTURE_FE_INPUT_FIFO_OVERFLOW_ERR = 23, + INSYS_MSG_ERR_CAPTURE_CMD_SUBMIT_TO_HW = 24, + INSYS_MSG_ERR_CAPTURE_N +}; + +enum insys_msg_err_groups { + INSYS_MSG_ERR_GROUP_RESERVED = 0, + INSYS_MSG_ERR_GROUP_GENERAL = 1, + INSYS_MSG_ERR_GROUP_STREAM = 2, + INSYS_MSG_ERR_GROUP_CAPTURE = 3, + INSYS_MSG_ERR_GROUP_N, +}; + +struct ipu7_fw_isys_logger_config { + u8 use_source_severity; + u8 source_severity[IPU7_FWLOG_MAX_LOGGER_SOURCES]; + u8 use_channels_enable_bitmask; + u8 channels_enable_bitmask; + u8 padding[1]; + u32 hw_printf_buffer_base_addr; + u32 hw_printf_buffer_size_bytes; +}; + +struct ipu7_wdt_abi { + u32 wdt_timer1_us; + u32 wdt_timer2_us; +}; + +struct ipu7_insys_config { + u32 timeout_val_ms; + struct ipu7_fw_isys_logger_config logger_config; + struct ipu7_wdt_abi wdt_config; +}; + +struct ipu7_insys_capture_output_pin_payload { + u64 user_token; + u32 addr; + u8 pad[4]; +}; + +struct ipu7_fw_isys_msg_err { + u32 err_group; + u32 err_code; + u32 err_detail[IPU7_MSG_ERR_MAX_DETAILS]; +}; + +struct ipu7_insys_resp { + u64 buf_id; + struct ipu7_insys_capture_output_pin_payload pin; + struct ipu7_fw_isys_msg_err error_info; + u32 timestamp[2]; + u8 type; + u8 msg_link_streaming_mode; + u8 stream_id; + u8 pin_id; + u8 frame_id; + u8 skip_frame; + u8 pad[2]; +}; + +struct ipu7_insys_resp_queue_token { + struct ipu7_insys_resp resp_info; +}; + +struct ipu7_insys_send_queue_token { + u64 buf_handle; + u32 addr; + u16 stream_id; + u8 send_type; + u8 flag; +}; + +struct ipu7_fw_isys_output_link { + u32 buffer_lines; + u16 foreign_key; + u16 granularity_pointer_update; + u8 msg_link_streaming_mode; + u8 pbk_id; + u8 pbk_slot_id; + u8 dest; + u8 use_sw_managed; + u8 is_snoop; + u8 pad[2]; +} __packed; + +struct ipu7_fw_isys_output_cropping { + u16 line_top; + u16 line_bottom; +} __packed; + +struct ipu7_fw_isys_output_dpcm { + u8 enable; + u8 type; + u8 predictor; + u8 pad; +} __packed; + +struct ipu7_fw_isys_output_pin { + struct ipu7_fw_isys_output_link link; + struct ipu7_fw_isys_output_cropping crop; + struct ipu7_fw_isys_output_dpcm dpcm; + u32 stride; + u16 ft; + u8 send_irq; + u8 input_pin_id; + u8 early_ack_en; + u8 pad[3]; +} __packed; + +struct ipu7_fw_isys_resolution { + u32 width; + u32 height; +} __packed; + +struct ipu7_fw_isys_input_pin { + struct ipu7_fw_isys_resolution input_res; + u16 sync_msg_map; + u8 dt; + u8 disable_mipi_unpacking; + u8 dt_rename_mode; + u8 mapped_dt; + u8 pad[2]; +} __packed; + +struct ipu7_fw_isys_stream_cfg { + struct ipu7_fw_isys_input_pin input_pins[IPU7_MAX_IPINS]; + struct ipu7_fw_isys_output_pin output_pins[IPU7_MAX_OPINS]; + u16 stream_msg_map; + u8 port_id; + u8 vc; + u8 nof_input_pins; + u8 nof_output_pins; + u8 pad[2]; +} __packed; + +struct ipu7_fw_isys_capture_output_pin { + u64 user_token; + u32 addr; + u8 pad[4]; +} __packed; + +struct ipu7_fw_isys_frame_buff_set { + struct ipu7_fw_isys_capture_output_pin output_pins[IPU7_MAX_OPINS]; + u8 capture_msg_map; + u8 frame_id; + u8 skip_frame; + u8 pad[5]; +} __packed; + +struct ipu6_fw_isys_ops *ipu7_fw_isys_get_ops(void); +irqreturn_t ipu7_isys_isr(struct ipu6_bus_device *adev); + +#endif diff --git a/drivers/media/pci/intel/ipu6/ipu7-isys-csi-phy.c b/drivers/media/pci/intel/ipu6/ipu7-isys-csi-phy.c new file mode 100644 index 000000000000..10273c687faa --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu7-isys-csi-phy.c @@ -0,0 +1,1072 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 - 2026 Intel Corporation + */ + +#include <linux/bitmap.h> +#include <linux/bug.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/iopoll.h> +#include <linux/kernel.h> +#include <linux/types.h> + +#include <media/mipi-csi2.h> +#include <media/v4l2-device.h> + +#include "ipu6.h" +#include "ipu6-bus.h" +#include "ipu6-isys.h" +#include "ipu6-isys-csi2.h" +#include "ipu7-isys-csi2-regs.h" +#include "ipu7-isys-csi-phy.h" + +#define PORT_A 0U +#define PORT_B 1U +#define PORT_C 2U +#define PORT_D 3U + +#define N_DATA_IDS 8U +static DECLARE_BITMAP(data_ids, N_DATA_IDS); + +struct ddlcal_counter_ref_s { + u16 min_mbps; + u16 max_mbps; + + u16 ddlcal_counter_ref; +}; + +struct ddlcal_params { + u16 min_mbps; + u16 max_mbps; + u16 oa_lanex_hsrx_cdphy_sel_fast; + u16 ddlcal_max_phase; + u16 phase_bound; + u16 ddlcal_dll_fbk; + u16 ddlcal_ddl_coarse_bank; + u16 fjump_deskew; + u16 min_eye_opening_deskew; +}; + +struct i_thssettle_params { + u16 min_mbps; + u16 max_mbps; + u16 i_thssettle; +}; + +/* lane2 for 4l3t, lane1 for 2l2t */ +struct oa_lane_clk_div_params { + u16 min_mbps; + u16 max_mbps; + u16 oa_lane_hsrx_hs_clk_div; +}; + +struct cdr_fbk_cap_prog_params { + u16 min_mbps; + u16 max_mbps; + u16 val; +}; + +static const struct ddlcal_counter_ref_s table0[] = { + { 1500, 1999, 118 }, + { 2000, 2499, 157 }, + { 2500, 3499, 196 }, + { 3500, 4499, 274 }, + { 4500, 4500, 352 }, + { } +}; + +static const struct ddlcal_params table1[] = { + { 1500, 1587, 0, 143, 167, 17, 3, 4, 29 }, + { 1588, 1687, 0, 135, 167, 15, 3, 4, 27 }, + { 1688, 1799, 0, 127, 135, 15, 2, 4, 26 }, + { 1800, 1928, 0, 119, 135, 13, 2, 3, 24 }, + { 1929, 2076, 0, 111, 135, 13, 2, 3, 23 }, + { 2077, 2249, 0, 103, 135, 11, 2, 3, 21 }, + { 2250, 2454, 0, 95, 103, 11, 1, 3, 19 }, + { 2455, 2699, 0, 87, 103, 9, 1, 3, 18 }, + { 2700, 2999, 0, 79, 103, 9, 1, 2, 16 }, + { 3000, 3229, 0, 71, 71, 7, 1, 2, 15 }, + { 3230, 3599, 1, 87, 103, 9, 1, 3, 18 }, + { 3600, 3999, 1, 79, 103, 9, 1, 2, 16 }, + { 4000, 4499, 1, 71, 103, 7, 1, 2, 15 }, + { 4500, 4500, 1, 63, 71, 7, 0, 2, 13 }, + { } +}; + +static const struct i_thssettle_params table2[] = { + { 80, 124, 24 }, + { 125, 249, 20 }, + { 250, 499, 16 }, + { 500, 749, 14 }, + { 750, 1499, 13 }, + { 1500, 4500, 12 }, + { } +}; + +static const struct oa_lane_clk_div_params table6[] = { + { 80, 159, 0x1 }, + { 160, 319, 0x2 }, + { 320, 639, 0x3 }, + { 640, 1279, 0x4 }, + { 1280, 2560, 0x5 }, + { 2561, 4500, 0x6 }, + { } +}; + +static const struct cdr_fbk_cap_prog_params table7[] = { + { 80, 919, 0 }, + { 920, 1029, 1 }, + { 1030, 1169, 2 }, + { 1170, 1349, 3 }, + { 1350, 1589, 4 }, + { 1590, 1949, 5 }, + { 1950, 2499, 6 }, + { 2500, 3500, 7 }, + { } +}; + +static void dwc_phy_write(struct ipu6_isys *isys, u32 id, u32 addr, u16 data) +{ + void __iomem *isys_base = isys->pdata->base; + void __iomem *base = isys_base + IPU7_IS_IO_CDPHY_BASE(id); + + dev_dbg(&isys->adev->auxdev.dev, "phy write: reg 0x%lx = data 0x%04x", + (unsigned long)(base + addr - isys_base), data); + writew(data, base + addr); +} + +static u16 dwc_phy_read(struct ipu6_isys *isys, u32 id, u32 addr) +{ + void __iomem *isys_base = isys->pdata->base; + void __iomem *base = isys_base + IPU7_IS_IO_CDPHY_BASE(id); + u16 data; + + data = readw(base + addr); + dev_dbg(&isys->adev->auxdev.dev, "phy read: reg 0x%lx = data 0x%04x", + (unsigned long)(base + addr - isys_base), data); + + return data; +} + +static void dwc_csi_write(struct ipu6_isys *isys, u32 id, u32 addr, u32 data) +{ + void __iomem *isys_base = isys->pdata->base; + void __iomem *base = isys_base + IPU7_IS_IO_CSI2_HOST_BASE(id); + struct device *dev = &isys->adev->auxdev.dev; + + dev_dbg(dev, "csi write: reg 0x%lx = data 0x%08x", + (unsigned long)(base + addr - isys_base), data); + writel(data, base + addr); + dev_dbg(dev, "csi read: reg 0x%lx = data 0x%08x", + (unsigned long)(base + addr - isys_base), + readl(base + addr)); +} + +static void gpreg_write(struct ipu6_isys *isys, u32 id, u32 addr, u32 data) +{ + void __iomem *isys_base = isys->pdata->base; + u32 gpreg = isys->pdata->ipdata->csi2.gpreg; + void __iomem *base = isys_base + gpreg + 0x1000 * id; + struct device *dev = &isys->adev->auxdev.dev; + + dev_dbg(dev, "gpreg write: reg 0x%lx = data 0x%08x", + (unsigned long)(base + addr - isys_base), data); + writel(data, base + addr); + dev_dbg(dev, "gpreg read: reg 0x%lx = data 0x%08x", + (unsigned long)(base + addr - isys_base), + readl(base + addr)); +} + +static u32 dwc_csi_read(struct ipu6_isys *isys, u32 id, u32 addr) +{ + void __iomem *isys_base = isys->pdata->base; + void __iomem *base = isys_base + IPU7_IS_IO_CSI2_HOST_BASE(id); + u32 data; + + data = readl(base + addr); + dev_dbg(&isys->adev->auxdev.dev, "csi read: reg 0x%lx = data 0x%x", + (unsigned long)(base + addr - isys_base), data); + + return data; +} + +static void dwc_phy_write_mask(struct ipu6_isys *isys, u32 id, u32 addr, + u16 val, u8 lo, u8 hi) +{ + u32 temp, mask; + + WARN_ON(lo > hi); + WARN_ON(hi > 15); + + mask = ((~0U - (1U << lo) + 1U)) & (~0U >> (31 - hi)); + temp = dwc_phy_read(isys, id, addr); + temp &= ~mask; + temp |= (val << lo) & mask; + dwc_phy_write(isys, id, addr, temp); +} + +static void dwc_csi_write_mask(struct ipu6_isys *isys, u32 id, u32 addr, + u32 val, u8 hi, u8 lo) +{ + u32 temp, mask; + + WARN_ON(lo > hi); + + mask = ((~0U - (1U << lo) + 1U)) & (~0U >> (31 - hi)); + temp = dwc_csi_read(isys, id, addr); + temp &= ~mask; + temp |= (val << lo) & mask; + dwc_csi_write(isys, id, addr, temp); +} + +static void ipu7_isys_csi_ctrl_cfg(struct ipu6_isys_csi2 *csi2) +{ + struct ipu6_isys *isys = csi2->isys; + struct device *dev = &isys->adev->auxdev.dev; + u32 id, lanes, phy_mode; + u32 val; + + id = csi2->port; + lanes = csi2->nlanes; + phy_mode = csi2->phy_mode; + dev_dbg(dev, "csi-%d controller init with %u lanes, phy mode %u", + id, lanes, phy_mode); + + val = dwc_csi_read(isys, id, IPU7_VERSION); + dev_dbg(dev, "csi-%d controller version = 0x%x", id, val); + + /* num of active data lanes */ + dwc_csi_write(isys, id, IPU7_N_LANES, lanes - 1); + dwc_csi_write(isys, id, IPU7_CDPHY_MODE, phy_mode); + dwc_csi_write(isys, id, IPU7_VC_EXTENSION, 0); + + /* only mask PHY_FATAL and PKT_FATAL interrupts */ + dwc_csi_write(isys, id, IPU7_INT_MSK_PHY_FATAL, 0xff); + dwc_csi_write(isys, id, IPU7_INT_MSK_PKT_FATAL, 0x3); + dwc_csi_write(isys, id, IPU7_INT_MSK_PHY, 0x0); + dwc_csi_write(isys, id, IPU7_INT_MSK_LINE, 0x0); + dwc_csi_write(isys, id, IPU7_INT_MSK_BNDRY_FRAME_FATAL, 0x0); + dwc_csi_write(isys, id, IPU7_INT_MSK_SEQ_FRAME_FATAL, 0x0); + dwc_csi_write(isys, id, IPU7_INT_MSK_CRC_FRAME_FATAL, 0x0); + dwc_csi_write(isys, id, IPU7_INT_MSK_PLD_CRC_FATAL, 0x0); + dwc_csi_write(isys, id, IPU7_INT_MSK_DATA_ID, 0x0); + dwc_csi_write(isys, id, IPU7_INT_MSK_ECC_CORRECTED, 0x0); +} + +static void ipu7_isys_csi_phy_reset(struct ipu6_isys *isys, u32 id) +{ + dwc_csi_write(isys, id, IPU7_PHY_SHUTDOWNZ, 0); + dwc_csi_write(isys, id, IPU7_DPHY_RSTZ, 0); + dwc_csi_write(isys, id, IPU7_CSI2_RESETN, 0); + gpreg_write(isys, id, IPU7_PHY_RESET, 0); + gpreg_write(isys, id, IPU7_PHY_SHUTDOWN, 0); +} + +/* 8 Data ID monitors, each Data ID is composed by pair of VC and data type */ +static int __dids_config(struct ipu6_isys_csi2 *csi2, u32 id, u8 vc, u8 dt) +{ + struct ipu6_isys *isys = csi2->isys; + u32 reg, n; + u8 lo, hi; + int ret; + + dev_dbg(&isys->adev->auxdev.dev, + "config CSI-%u with vc:%u dt:0x%02x\n", id, vc, dt); + + dwc_csi_write(isys, id, IPU7_VC_EXTENSION, 0x0); + n = find_first_zero_bit(data_ids, N_DATA_IDS); + if (n == N_DATA_IDS) + return -ENOSPC; + + ret = test_and_set_bit(n, data_ids); + if (ret) + return -EBUSY; + + reg = n < 4 ? IPU7_DATA_IDS_VC_1 : IPU7_DATA_IDS_VC_2; + lo = (n % 4) * 8; + hi = lo + 4; + dwc_csi_write_mask(isys, id, reg, vc & GENMASK(4, 0), hi, lo); + + reg = n < 4 ? IPU7_DATA_IDS_1 : IPU7_DATA_IDS_2; + lo = (n % 4) * 8; + hi = lo + 5; + dwc_csi_write_mask(isys, id, reg, dt & GENMASK(5, 0), hi, lo); + + return 0; +} + +static int ipu7_isys_csi_ctrl_dids_config(struct ipu6_isys_csi2 *csi2, u32 id) +{ + struct v4l2_mbus_frame_desc_entry *desc_entry = NULL; + struct device *dev = &csi2->isys->adev->auxdev.dev; + struct v4l2_mbus_frame_desc desc; + struct v4l2_subdev *ext_sd; + struct media_pad *pad; + int ret; + + pad = media_entity_remote_source_pad_unique(&csi2->asd.sd.entity); + if (IS_ERR(pad)) { + dev_warn(dev, "can't get remote source pad of %s (%pe)\n", + csi2->asd.sd.name, pad); + return PTR_ERR(pad); + } + + ext_sd = media_entity_to_v4l2_subdev(pad->entity); + if (WARN(!ext_sd, "Failed to get subdev for entity %s\n", + pad->entity->name)) + return -ENODEV; + + ret = v4l2_subdev_call(ext_sd, pad, get_frame_desc, pad->index, &desc); + if (ret) + return ret; + + if (desc.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2) { + dev_warn(dev, "Unsupported frame descriptor type\n"); + return -EINVAL; + } + + for (unsigned int i = 0; i < desc.num_entries; i++) { + desc_entry = &desc.entry[i]; + if (desc_entry->bus.csi2.vc < NR_OF_CSI2_VC) { + ret = __dids_config(csi2, id, desc_entry->bus.csi2.vc, + desc_entry->bus.csi2.dt); + if (ret) + return ret; + } + } + + return 0; +} + +#define CDPHY_TIMEOUT 5000000U +static int ipu7_isys_phy_ready(struct ipu6_isys *isys, u32 id) +{ + void __iomem *isys_base = isys->pdata->base; + u32 gpreg_offset = isys->pdata->ipdata->csi2.gpreg; + void __iomem *gpreg = isys_base + gpreg_offset + 0x1000 * id; + struct device *dev = &isys->adev->auxdev.dev; + u32 phy_ready; + u32 reg, rext; + int ret; + + dev_dbg(dev, "waiting phy ready...\n"); + ret = readl_poll_timeout(gpreg + IPU7_PHY_READY, phy_ready, + phy_ready & BIT(0) && phy_ready != ~0U, + 100, CDPHY_TIMEOUT); + dev_dbg(dev, "phy %u ready = 0x%08x\n", + id, readl(gpreg + IPU7_PHY_READY)); + dev_dbg(dev, "csi %u IPU7_PHY_RX = 0x%08x\n", id, + dwc_csi_read(isys, id, IPU7_PHY_RX)); + dev_dbg(dev, "csi %u IPU7_PHY_STOPSTATE = 0x%08x\n", id, + dwc_csi_read(isys, id, IPU7_PHY_STOPSTATE)); + dev_dbg(dev, "csi %u IPU7_PHY_CAL = 0x%08x\n", id, + dwc_csi_read(isys, id, IPU7_PHY_CAL)); + for (unsigned int i = 0; i < 4U; i++) { + reg = IPU7_CORE_DIG_DLANE_0_R_HS_RX_0 + (i * 0x400U); + dev_dbg(dev, "phy %u DLANE%u skewcal = 0x%04x\n", + id, i, dwc_phy_read(isys, id, reg)); + } + dev_dbg(dev, "phy %u DDLCAL = 0x%04x\n", id, + dwc_phy_read(isys, id, + IPU7_PPI_CALIBCTRL_R_COMMON_CALIBCTRL_2_5)); + dev_dbg(dev, "phy %u TERMCAL = 0x%04x\n", id, + dwc_phy_read(isys, id, IPU7_PPI_R_TERMCAL_DEBUG_0)); + dev_dbg(dev, "phy %u LPDCOCAL = 0x%04x\n", id, + dwc_phy_read(isys, id, IPU7_PPI_R_LPDCOCAL_DEBUG_RB)); + dev_dbg(dev, "phy %u HSDCOCAL = 0x%04x\n", id, + dwc_phy_read(isys, id, IPU7_PPI_R_HSDCOCAL_DEBUG_RB)); + dev_dbg(dev, "phy %u LPDCOCAL_VT = 0x%04x\n", id, + dwc_phy_read(isys, id, IPU7_PPI_R_LPDCOCAL_DEBUG_VT)); + + if (!ret) { + if (id) { + dev_dbg(dev, "ignore phy %u rext\n", id); + return 0; + } + + rext = dwc_phy_read(isys, id, + IPU7_CORE_DIG_IOCTRL_R_AFE_CB_CTRL_2_15) & + 0xfU; + dev_dbg(dev, "phy %u rext value = %u\n", id, rext); + isys->phy_rext_cal = (rext ? rext : 5); + + return 0; + } + + dev_err(dev, "wait phy ready timeout!\n"); + + return ret; +} + +static int lookup_table1(u64 mbps) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(table1); i++) { + if (mbps >= table1[i].min_mbps && mbps <= table1[i].max_mbps) + return i; + } + + return -ENXIO; +} + +static const u16 deskew_fine_mem[] = { + 0x0404, 0x040c, 0x0414, 0x041c, + 0x0423, 0x0429, 0x0430, 0x043a, + 0x0445, 0x044a, 0x0450, 0x045a, + 0x0465, 0x0469, 0x0472, 0x047a, + 0x0485, 0x0489, 0x0490, 0x049a, + 0x04a4, 0x04ac, 0x04b4, 0x04bc, + 0x04c4, 0x04cc, 0x04d4, 0x04dc, + 0x04e4, 0x04ec, 0x04f4, 0x04fc, + 0x0504, 0x050c, 0x0514, 0x051c, + 0x0523, 0x0529, 0x0530, 0x053a, + 0x0545, 0x054a, 0x0550, 0x055a, + 0x0565, 0x0569, 0x0572, 0x057a, + 0x0585, 0x0589, 0x0590, 0x059a, + 0x05a4, 0x05ac, 0x05b4, 0x05bc, + 0x05c4, 0x05cc, 0x05d4, 0x05dc, + 0x05e4, 0x05ec, 0x05f4, 0x05fc, + 0x0604, 0x060c, 0x0614, 0x061c, + 0x0623, 0x0629, 0x0632, 0x063a, + 0x0645, 0x064a, 0x0650, 0x065a, + 0x0665, 0x0669, 0x0672, 0x067a, + 0x0685, 0x0689, 0x0690, 0x069a, + 0x06a4, 0x06ac, 0x06b4, 0x06bc, + 0x06c4, 0x06cc, 0x06d4, 0x06dc, + 0x06e4, 0x06ec, 0x06f4, 0x06fc, + 0x0704, 0x070c, 0x0714, 0x071c, + 0x0723, 0x072a, 0x0730, 0x073a, + 0x0745, 0x074a, 0x0750, 0x075a, + 0x0765, 0x0769, 0x0772, 0x077a, + 0x0785, 0x0789, 0x0790, 0x079a, + 0x07a4, 0x07ac, 0x07b4, 0x07bc, + 0x07c4, 0x07cc, 0x07d4, 0x07dc, + 0x07e4, 0x07ec, 0x07f4, 0x07fc, +}; + +static void ipu7_isys_dphy_config(struct ipu6_isys *isys, u8 id, u8 lanes, + bool aggregation, u64 mbps) +{ + struct ipu6_device *isp = isys->adev->isp; + u16 hsrxval0 = 0; + u16 hsrxval1 = 0; + u16 hsrxval2 = 0; + int index; + u16 reg; + u16 val; + u32 i; + + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_RW_COMMON_7, 0, 0, 9); + if (mbps > 1500) + dwc_phy_write_mask(isys, id, IPU7_PPI_STARTUP_RW_COMMON_DPHY_7, + 40, 0, 7); + else + dwc_phy_write_mask(isys, id, IPU7_PPI_STARTUP_RW_COMMON_DPHY_7, + 104, 0, 7); + + dwc_phy_write_mask(isys, id, IPU7_PPI_STARTUP_RW_COMMON_DPHY_8, + 80, 0, 7); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_0, 191, 0, 9); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_7, 34, 7, 12); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_1, 38, 8, 15); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_2, 4, 12, 15); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_2, 2, 10, 11); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_2, 1, 8, 8); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_2, 38, 0, 7); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_2, 1, 9, 9); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_4, 10, 0, 9); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_6, 20, 0, 9); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_7, 19, 0, 6); + + for (i = 0; i < ARRAY_SIZE(table0); i++) { + if (mbps >= table0[i].min_mbps && mbps <= table0[i].max_mbps) { + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_3, + table0[i].ddlcal_counter_ref, + 0, 9); + break; + } + } + + index = lookup_table1(mbps); + if (index >= 0) { + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_1, + table1[index].phase_bound, 0, 7); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_5, + table1[index].ddlcal_dll_fbk, 4, 9); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_5, + table1[index].ddlcal_ddl_coarse_bank, 0, 3); + + reg = IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_8; + val = table1[index].oa_lanex_hsrx_cdphy_sel_fast; + for (i = 0; i < lanes + 1; i++) + dwc_phy_write_mask(isys, id, reg + (i * 0x400), val, + 12, 12); + } + + reg = IPU7_CORE_DIG_DLANE_0_RW_LP_0; + for (i = 0; i < lanes; i++) + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 6, 8, 11); + + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_2, + 0, 0, 0); + if (!IS_IPU7_MTL(isp) || id == PORT_B || id == PORT_C) { + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_2, + 1, 0, 0); + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_2, + 0, 0, 0); + } else { + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_2, + 0, 0, 0); + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_2, + 1, 0, 0); + } + + if (lanes == 4 && IS_IPU7_MTL(isp)) { + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_2, + 0, 0, 0); + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_2, + 0, 0, 0); + } + + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_RW_COMMON_6, 1, 0, 2); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_RW_COMMON_6, 1, 3, 5); + + reg = IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_12; + val = (mbps > 1500) ? 0 : 1; + for (i = 0; i < lanes + 1; i++) { + dwc_phy_write_mask(isys, id, reg + (i * 0x400), val, 1, 1); + dwc_phy_write_mask(isys, id, reg + (i * 0x400), !val, 3, 3); + } + + reg = IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_13; + val = (mbps > 1500) ? 0 : 1; + for (i = 0; i < lanes + 1; i++) { + dwc_phy_write_mask(isys, id, reg + (i * 0x400), val, 1, 1); + dwc_phy_write_mask(isys, id, reg + (i * 0x400), val, 3, 3); + } + + if (!IS_IPU7_MTL(isp) || id == PORT_B || id == PORT_C) + reg = IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_9; + else + reg = IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_9; + + for (i = 0; i < ARRAY_SIZE(table6); i++) { + if (mbps >= table6[i].min_mbps && mbps <= table6[i].max_mbps) { + dwc_phy_write_mask(isys, id, reg, + table6[i].oa_lane_hsrx_hs_clk_div, + 5, 7); + break; + } + } + + if (aggregation) { + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_RW_COMMON_0, 1, + 1, 1); + + reg = IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_15; + dwc_phy_write_mask(isys, id, reg, 3, 3, 4); + + val = (id == PORT_A) ? 3 : 0; + reg = IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_15; + dwc_phy_write_mask(isys, id, reg, val, 3, 4); + + reg = IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_15; + dwc_phy_write_mask(isys, id, reg, 3, 3, 4); + } + + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_DLANE_CLK_RW_HS_RX_0, + 28, 0, 7); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_DLANE_CLK_RW_HS_RX_7, + 6, 0, 7); + + reg = IPU7_CORE_DIG_DLANE_0_RW_HS_RX_0; + for (i = 0; i < ARRAY_SIZE(table2); i++) { + if (mbps >= table2[i].min_mbps && mbps <= table2[i].max_mbps) { + u8 j; + + for (j = 0; j < lanes; j++) + dwc_phy_write_mask(isys, id, reg + (j * 0x400), + table2[i].i_thssettle, + 8, 15); + break; + } + } + + /* deskew */ + for (i = 0; i < lanes; i++) { + reg = IPU7_CORE_DIG_DLANE_0_RW_CFG_1; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), + ((mbps > 1500) ? 0x1 : 0x2), 2, 3); + + reg = IPU7_CORE_DIG_DLANE_0_RW_HS_RX_2; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), + ((mbps > 2500) ? 0 : 1), 15, 15); + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 1, 13, 13); + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 7, 9, 12); + + reg = IPU7_CORE_DIG_DLANE_0_RW_LP_0; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 1, 12, 15); + + reg = IPU7_CORE_DIG_DLANE_0_RW_LP_2; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 0, 0, 0); + + reg = IPU7_CORE_DIG_DLANE_0_RW_HS_RX_1; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 16, 0, 7); + + reg = IPU7_CORE_DIG_DLANE_0_RW_HS_RX_3; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 2, 0, 2); + index = lookup_table1(mbps); + if (index >= 0) { + val = table1[index].fjump_deskew; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), val, + 3, 8); + } + + reg = IPU7_CORE_DIG_DLANE_0_RW_HS_RX_4; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 150, 0, 15); + + reg = IPU7_CORE_DIG_DLANE_0_RW_HS_RX_5; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 0, 0, 7); + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 1, 8, 15); + + reg = IPU7_CORE_DIG_DLANE_0_RW_HS_RX_6; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 2, 0, 7); + index = lookup_table1(mbps); + if (index >= 0) { + val = table1[index].min_eye_opening_deskew; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), val, + 8, 15); + } + reg = IPU7_CORE_DIG_DLANE_0_RW_HS_RX_7; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 0, 13, 13); + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 0, 15, 15); + + reg = IPU7_CORE_DIG_DLANE_0_RW_HS_RX_9; + index = lookup_table1(mbps); + if (index >= 0) { + val = table1[index].ddlcal_max_phase; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), + val, 0, 7); + } + } + + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_DLANE_CLK_RW_LP_0, + 1, 12, 15); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_DLANE_CLK_RW_LP_2, 0, 0, 0); + + for (i = 0; i < ARRAY_SIZE(deskew_fine_mem); i++) + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_COMMON_RW_DESKEW_FINE_MEM, + deskew_fine_mem[i], 0, 15); + + if (mbps > 1500) { + hsrxval0 = 4; + hsrxval2 = 3; + } + + if (mbps > 2500) + hsrxval1 = 2; + + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_9, + hsrxval0, 0, 2); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_9, + hsrxval0, 0, 2); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_9, + hsrxval0, 0, 2); + if (lanes == 4 && IS_IPU7_MTL(isp)) { + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_9, + hsrxval0, 0, 2); + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_9, + hsrxval0, 0, 2); + } + + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_9, + hsrxval1, 3, 4); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_9, + hsrxval1, 3, 4); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_9, + hsrxval1, 3, 4); + if (lanes == 4 && IS_IPU7_MTL(isp)) { + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_9, + hsrxval1, 3, 4); + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_9, + hsrxval1, 3, 4); + } + + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_15, + hsrxval2, 0, 2); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_15, + hsrxval2, 0, 2); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_15, + hsrxval2, 0, 2); + if (lanes == 4 && IS_IPU7_MTL(isp)) { + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_15, + hsrxval2, 0, 2); + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_15, + hsrxval2, 0, 2); + } + + /* force and override rext */ + if (isys->phy_rext_cal && id) { + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_8, + isys->phy_rext_cal, 0, 3); + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_7, + 1, 11, 11); + } +} + +static void ipu7_isys_cphy_config(struct ipu6_isys *isys, u8 id, u8 lanes, + bool aggregation, u64 mbps) +{ + struct ipu6_device *isp = isys->adev->isp; + u8 trios = 2; + u16 coarse_target; + u16 deass_thresh; + u16 delay_thresh; + u16 reset_thresh; + u16 cap_prog = 6U; + u16 reg; + u16 val; + u32 i; + u64 r64; + u32 r; + + if (IS_IPU7P5(isp)) + val = 0x15; + else + val = 0x155; + + if (IS_IPU7_MTL(isp)) + trios = 3; + + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_RW_COMMON_7, val, 0, 9); + dwc_phy_write_mask(isys, id, IPU7_PPI_STARTUP_RW_COMMON_DPHY_7, + 104, 0, 7); + dwc_phy_write_mask(isys, id, IPU7_PPI_STARTUP_RW_COMMON_DPHY_8, + 16, 0, 7); + + reg = IPU7_CORE_DIG_CLANE_0_RW_LP_0; + for (i = 0; i < trios; i++) + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 6, 8, 11); + + val = (mbps > 900U) ? 1U : 0U; + for (i = 0; i < trios; i++) { + reg = IPU7_CORE_DIG_CLANE_0_RW_HS_RX_0; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 1, 0, 0); + dwc_phy_write_mask(isys, id, reg + (i * 0x400), val, 1, 1); + + reg = IPU7_CORE_DIG_CLANE_0_RW_HS_RX_1; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 38, 0, 15); + + reg = IPU7_CORE_DIG_CLANE_0_RW_HS_RX_5; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 38, 0, 15); + + reg = IPU7_CORE_DIG_CLANE_0_RW_HS_RX_6; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 10, 0, 15); + } + + /* + * Below 900Msps, always use the same value. + * The formula is suitable for data rate 80-3500Msps. + * Timebase (us) = 1, DIV = 32, TDDL (UI) = 0.5 + */ + if (mbps >= 80U) + coarse_target = DIV_ROUND_UP_ULL(mbps, 16) - 1; + else + coarse_target = 56; + + for (i = 0; i < trios; i++) { + reg = IPU7_CORE_DIG_CLANE_0_RW_HS_RX_2 + i * 0x400; + dwc_phy_write_mask(isys, id, reg, coarse_target, 0, 15); + } + + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_2, 1, 0, 0); + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_2, 0, 0, 0); + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_2, 1, 0, 0); + + if (!IS_IPU7P5(isp) && lanes == 4) { + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_2, + 1, 0, 0); + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_2, + 0, 0, 0); + } + + for (i = 0; i < trios; i++) { + reg = IPU7_CORE_DIG_RW_TRIO0_0 + i * 0x400; + dwc_phy_write_mask(isys, id, reg, 1, 6, 8); + dwc_phy_write_mask(isys, id, reg, 1, 3, 5); + dwc_phy_write_mask(isys, id, reg, 2, 0, 2); + } + + deass_thresh = (u16)div64_u64_rem(7 * 1000 * 6, mbps * 5U, &r64) + 1; + if (r64 != 0) + deass_thresh++; + + reg = IPU7_CORE_DIG_RW_TRIO0_2; + for (i = 0; i < trios; i++) + dwc_phy_write_mask(isys, id, reg + 0x400 * i, + deass_thresh, 0, 7); + + delay_thresh = div64_u64((224U - (9U * 7U)) * 1000U, 5U * mbps) - 7u; + + if (delay_thresh < 1) + delay_thresh = 1; + + reg = IPU7_CORE_DIG_RW_TRIO0_1; + for (i = 0; i < trios; i++) + dwc_phy_write_mask(isys, id, reg + 0x400 * i, + delay_thresh, 0, 15); + + reset_thresh = (u16)div_u64_rem(2U * 5U * mbps, 7U * 1000U, &r); + if (!r) + reset_thresh--; + + if (reset_thresh < 1) + reset_thresh = 1; + + reg = IPU7_CORE_DIG_RW_TRIO0_0; + for (i = 0; i < trios; i++) + dwc_phy_write_mask(isys, id, reg + 0x400 * i, + reset_thresh, 9, 11); + + /* Tuning ITMINRX to 2 for CPHY */ + reg = IPU7_CORE_DIG_CLANE_0_RW_LP_0; + for (i = 0; i < trios; i++) + dwc_phy_write_mask(isys, id, reg + 0x400 * i, 2, 12, 15); + + reg = IPU7_CORE_DIG_CLANE_0_RW_LP_2; + for (i = 0; i < trios; i++) + dwc_phy_write_mask(isys, id, reg + 0x400 * i, 0, 0, 0); + + reg = IPU7_CORE_DIG_CLANE_0_RW_HS_RX_0; + for (i = 0; i < trios; i++) + dwc_phy_write_mask(isys, id, reg + 0x400 * i, 12, 2, 6); + + for (i = 0; i < ARRAY_SIZE(table7); i++) { + if (mbps >= table7[i].min_mbps && mbps <= table7[i].max_mbps) { + cap_prog = table7[i].val; + break; + } + } + + for (i = 0; i < (lanes + 1); i++) { + reg = IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_9 + 0x400 * i; + dwc_phy_write_mask(isys, id, reg, 4U, 0, 2); + /* Set GMODE to 2 when CPHY >= 1.5Gsps */ + if (mbps >= 1500) + dwc_phy_write_mask(isys, id, reg, 2U, 3, 4); + else + dwc_phy_write_mask(isys, id, reg, 0U, 3, 4); + + reg = IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_7 + 0x400 * i; + dwc_phy_write_mask(isys, id, reg, cap_prog, 10, 12); + } +} + +static int ipu7_isys_phy_config(struct ipu6_isys *isys, u8 id, u8 lanes, + bool aggregation) +{ + struct device *dev = &isys->adev->auxdev.dev; + u32 phy_mode; + s64 link_freq; + u64 mbps; + + if (aggregation) + link_freq = ipu6_isys_csi2_get_link_freq(&isys->csi2[0]); + else + link_freq = ipu6_isys_csi2_get_link_freq(&isys->csi2[id]); + + if (link_freq < 0) { + dev_err(dev, "get link freq failed (%lld)\n", link_freq); + return link_freq; + } + + mbps = div_u64(link_freq, 500000); + dev_dbg(dev, "config phy %u with lanes %u aggregation %d mbps %lld\n", + id, lanes, aggregation, mbps); + + dwc_phy_write_mask(isys, id, IPU7_PPI_STARTUP_RW_COMMON_DPHY_10, + 48, 0, 7); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_ANACTRL_RW_COMMON_ANACTRL_2, + 1, 12, 13); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_ANACTRL_RW_COMMON_ANACTRL_0, + 63, 2, 7); + dwc_phy_write_mask(isys, id, IPU7_PPI_STARTUP_RW_COMMON_STARTUP_1_1, + 563, 0, 11); + dwc_phy_write_mask(isys, id, IPU7_PPI_STARTUP_RW_COMMON_DPHY_2, + 5, 0, 7); + /* bypass the RCAL state (bit6) */ + if (aggregation && id != PORT_A) + dwc_phy_write_mask(isys, id, IPU7_PPI_STARTUP_RW_COMMON_DPHY_2, + 0x45, 0, 7); + + dwc_phy_write_mask(isys, id, IPU7_PPI_STARTUP_RW_COMMON_DPHY_6, + 39, 0, 7); + dwc_phy_write_mask(isys, id, IPU7_PPI_CALIBCTRL_RW_COMMON_BG_0, + 500, 0, 8); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_TERMCAL_CFG_0, 38, 0, 6); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_OFFSETCAL_CFG_0, 7, 0, 4); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_LPDCOCAL_TIMEBASE, 153, 0, 9); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_LPDCOCAL_NREF, 800, 0, 10); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_LPDCOCAL_NREF_RANGE, + 27, 0, 4); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_LPDCOCAL_TWAIT_CONFIG, + 47, 0, 8); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_LPDCOCAL_TWAIT_CONFIG, + 127, 9, 15); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_LPDCOCAL_VT_CONFIG, 47, 7, 15); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_LPDCOCAL_VT_CONFIG, 27, 2, 6); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_LPDCOCAL_VT_CONFIG, 3, 0, 1); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_LPDCOCAL_COARSE_CFG, 1, 0, 1); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_COMMON_CFG, 3, 0, 1); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_0, + 0, 10, 10); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_1, + 1, 10, 10); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_1, + 0, 15, 15); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_3, + 3, 8, 9); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_0, + 0, 15, 15); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_6, + 7, 12, 14); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_7, + 0, 8, 10); + /* resistance tuning: 1 for 45ohm, 0 for 50ohm */ + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_5, + 1, 8, 8); + + if (aggregation) + phy_mode = isys->csi2[0].phy_mode; + else + phy_mode = isys->csi2[id].phy_mode; + + if (phy_mode == PHY_MODE_DPHY) { + ipu7_isys_dphy_config(isys, id, lanes, aggregation, mbps); + } else if (phy_mode == PHY_MODE_CPHY) { + ipu7_isys_cphy_config(isys, id, lanes, aggregation, mbps); + } else { + dev_err(dev, "unsupported phy mode %d!\n", + isys->csi2[id].phy_mode); + } + + return 0; +} + +static int ipu7_isys_csi_phy_powerup(struct ipu6_isys_csi2 *csi2) +{ + struct ipu6_isys *isys = csi2->isys; + u32 lanes = csi2->nlanes; + bool aggregation = false; + u32 id = csi2->port; + int ret; + + /* lanes remapping for aggregation (port AB) mode */ + if (!IS_IPU7_MTL(isys->adev->isp) && lanes > 2 && id == PORT_A) { + aggregation = true; + lanes = 2; + } + + ipu7_isys_csi_phy_reset(isys, id); + gpreg_write(isys, id, IPU7_PHY_CLK_LANE_CONTROL, 0x1); + gpreg_write(isys, id, IPU7_PHY_CLK_LANE_FORCE_CONTROL, 0x2); + gpreg_write(isys, id, IPU7_PHY_LANE_CONTROL_EN, (1U << lanes) - 1U); + gpreg_write(isys, id, IPU7_PHY_LANE_FORCE_CONTROL, 0xf); + gpreg_write(isys, id, IPU7_PHY_MODE, csi2->phy_mode); + + /* config PORT_B if aggregation mode */ + if (aggregation) { + ipu7_isys_csi_phy_reset(isys, PORT_B); + gpreg_write(isys, PORT_B, IPU7_PHY_CLK_LANE_CONTROL, 0x0); + gpreg_write(isys, PORT_B, IPU7_PHY_LANE_CONTROL_EN, 0x3); + gpreg_write(isys, PORT_B, IPU7_PHY_CLK_LANE_FORCE_CONTROL, 0x2); + gpreg_write(isys, PORT_B, IPU7_PHY_LANE_FORCE_CONTROL, 0xf); + gpreg_write(isys, PORT_B, IPU7_PHY_MODE, csi2->phy_mode); + } + + ipu7_isys_csi_ctrl_cfg(csi2); + ipu7_isys_csi_ctrl_dids_config(csi2, id); + + ret = ipu7_isys_phy_config(isys, id, lanes, aggregation); + if (ret < 0) + return ret; + + gpreg_write(isys, id, IPU7_PHY_RESET, 1); + gpreg_write(isys, id, IPU7_PHY_SHUTDOWN, 1); + dwc_csi_write(isys, id, IPU7_DPHY_RSTZ, 1); + dwc_csi_write(isys, id, IPU7_PHY_SHUTDOWNZ, 1); + dwc_csi_write(isys, id, IPU7_CSI2_RESETN, 1); + + ret = ipu7_isys_phy_ready(isys, id); + if (ret < 0) + return ret; + + gpreg_write(isys, id, IPU7_PHY_LANE_FORCE_CONTROL, 0); + gpreg_write(isys, id, IPU7_PHY_CLK_LANE_FORCE_CONTROL, 0); + + /* config PORT_B if aggregation mode */ + if (aggregation) { + ret = ipu7_isys_phy_config(isys, PORT_B, 2, aggregation); + if (ret < 0) + return ret; + + gpreg_write(isys, PORT_B, IPU7_PHY_RESET, 1); + gpreg_write(isys, PORT_B, IPU7_PHY_SHUTDOWN, 1); + dwc_csi_write(isys, PORT_B, IPU7_DPHY_RSTZ, 1); + dwc_csi_write(isys, PORT_B, IPU7_PHY_SHUTDOWNZ, 1); + dwc_csi_write(isys, PORT_B, IPU7_CSI2_RESETN, 1); + ret = ipu7_isys_phy_ready(isys, PORT_B); + if (ret < 0) + return ret; + + gpreg_write(isys, PORT_B, IPU7_PHY_LANE_FORCE_CONTROL, 0); + gpreg_write(isys, PORT_B, IPU7_PHY_CLK_LANE_FORCE_CONTROL, 0); + } + + return 0; +} + +static void ipu7_isys_csi_phy_powerdown(struct ipu6_isys_csi2 *csi2) +{ + struct ipu6_isys *isys = csi2->isys; + + ipu7_isys_csi_phy_reset(isys, csi2->port); + if (!IS_IPU7_MTL(isys->adev->isp) && + csi2->nlanes > 2U && csi2->port == PORT_A) + ipu7_isys_csi_phy_reset(isys, PORT_B); +} + +int ipu7_isys_csi_phy_set_power(struct ipu6_isys *isys, + struct ipu6_isys_csi2_config *cfg, + const struct ipu6_isys_csi2_timing *timing, + bool on) +{ + struct ipu6_isys_csi2 *csi2 = &isys->csi2[cfg->port]; + + if (on) + return ipu7_isys_csi_phy_powerup(csi2); + + ipu7_isys_csi_phy_powerdown(csi2); + + return 0; +} diff --git a/drivers/media/pci/intel/ipu6/ipu7-isys-csi-phy.h b/drivers/media/pci/intel/ipu6/ipu7-isys-csi-phy.h new file mode 100644 index 000000000000..849fe888db07 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu7-isys-csi-phy.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2026 Intel Corporation */ + +#ifndef IPU7_ISYS_CSI_PHY_H +#define IPU7_ISYS_CSI_PHY_H + +struct ipu6_isys; +struct ipu6_isys_csi2_config; +struct ipu6_isys_csi2_timing; + +int ipu7_isys_csi_phy_set_power(struct ipu6_isys *isys, + struct ipu6_isys_csi2_config *cfg, + const struct ipu6_isys_csi2_timing *timing, + bool on); + +#endif /* IPU7_ISYS_CSI_PHY_H */ diff --git a/drivers/media/pci/intel/ipu6/ipu7-isys-csi2-regs.h b/drivers/media/pci/intel/ipu6/ipu7-isys-csi2-regs.h new file mode 100644 index 000000000000..3859401d4802 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu7-isys-csi2-regs.h @@ -0,0 +1,1189 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2020 - 2026 Intel Corporation + */ + +#ifndef IPU7_ISYS_CSI2_REG_H +#define IPU7_ISYS_CSI2_REG_H + +/* IS main regs base */ +#define IPU7_IS_MAIN_BASE 0x240000 +#define IPU7_IS_MAIN_S2B_BASE (IPU7_IS_MAIN_BASE + 0x22000) +#define IPU7_IS_MAIN_B2O_BASE (IPU7_IS_MAIN_BASE + 0x26000) +#define IPU7_IS_MAIN_ISD_M0_BASE (IPU7_IS_MAIN_BASE + 0x2b000) +#define IPU7_IS_MAIN_ISD_M1_BASE (IPU7_IS_MAIN_BASE + 0x2b100) +#define IPU7_IS_MAIN_ISD_INT_BASE (IPU7_IS_MAIN_BASE + 0x2b200) +#define IPU7_IS_MAIN_GDA_BASE (IPU7_IS_MAIN_BASE + 0x32000) +#define IPU7_IS_MAIN_GPREGS_MAIN_BASE (IPU7_IS_MAIN_BASE + 0x32500) +#define IPU7_IS_MAIN_IRQ_CTRL_BASE (IPU7_IS_MAIN_BASE + 0x32700) +#define IPU7_IS_MAIN_PWM_CTRL_BASE (IPU7_IS_MAIN_BASE + 0x32b00) + +#define IPU7_S2B_IRQ_COMMON_0_CTL_STATUS (IPU7_IS_MAIN_S2B_BASE + 0x1c) +#define IPU7_S2B_IRQ_COMMON_0_CTL_CLEAR (IPU7_IS_MAIN_S2B_BASE + 0x20) +#define IPU7_S2B_IRQ_COMMON_0_CTL_ENABLE (IPU7_IS_MAIN_S2B_BASE + 0x24) +#define IPU7_S2B_IID_IRQ_CTL_STATUS(iid) (IPU7_IS_MAIN_S2B_BASE + 0x94 + \ + 0x100 * (iid)) + +#define IPU7_B2O_IRQ_COMMON_0_CTL_STATUS (IPU7_IS_MAIN_B2O_BASE + 0x30) +#define IPU7_B2O_IRQ_COMMON_0_CTL_CLEAR (IPU7_IS_MAIN_B2O_BASE + 0x34) +#define IPU7_B2O_IRQ_COMMON_0_CTL_ENABLE (IPU7_IS_MAIN_B2O_BASE + 0x38) +#define IPU7_B2O_IID_IRQ_CTL_STATUS(oid) (IPU7_IS_MAIN_B2O_BASE + 0x3dc + \ + 0x200 * (oid)) + +#define IPU7_ISD_M0_IRQ_CTL_STATUS (IPU7_IS_MAIN_ISD_M0_BASE + 0x1c) +#define IPU7_ISD_M0_IRQ_CTL_CLEAR (IPU7_IS_MAIN_ISD_M0_BASE + 0x20) +#define IPU7_ISD_M0_IRQ_CTL_ENABLE (IPU7_IS_MAIN_ISD_M0_BASE + 0x24) + +#define IPU7_ISD_M1_IRQ_CTL_STATUS (IPU7_IS_MAIN_ISD_M1_BASE + 0x1c) +#define IPU7_ISD_M1_IRQ_CTL_CLEAR (IPU7_IS_MAIN_ISD_M1_BASE + 0x20) +#define IPU7_ISD_M1_IRQ_CTL_ENABLE (IPU7_IS_MAIN_ISD_M1_BASE + 0x24) + +#define IPU7_ISD_INT_IRQ_CTL_STATUS (IPU7_IS_MAIN_ISD_INT_BASE + 0x1c) +#define IPU7_ISD_INT_IRQ_CTL_CLEAR (IPU7_IS_MAIN_ISD_INT_BASE + 0x20) +#define IPU7_ISD_INT_IRQ_CTL_ENABLE (IPU7_IS_MAIN_ISD_INT_BASE + 0x24) + +#define IPU7_GDA_IRQ_CTL_STATUS (IPU7_IS_MAIN_GDA_BASE + 0x1c) +#define IPU7_GDA_IRQ_CTL_CLEAR (IPU7_IS_MAIN_GDA_BASE + 0x20) +#define IPU7_GDA_IRQ_CTL_ENABLE (IPU7_IS_MAIN_GDA_BASE + 0x24) + +#define IPU7_IS_MAIN_IRQ_CTL_EDGE IPU7_IS_MAIN_IRQ_CTRL_BASE +#define IPU7_IS_MAIN_IRQ_CTL_MASK (IPU7_IS_MAIN_IRQ_CTRL_BASE + 0x4) +#define IPU7_IS_MAIN_IRQ_CTL_STATUS (IPU7_IS_MAIN_IRQ_CTRL_BASE + 0x8) +#define IPU7_IS_MAIN_IRQ_CTL_CLEAR (IPU7_IS_MAIN_IRQ_CTRL_BASE + 0xc) +#define IPU7_IS_MAIN_IRQ_CTL_ENABLE (IPU7_IS_MAIN_IRQ_CTRL_BASE + 0x10) +#define IPU7_IS_MAIN_IRQ_CTL_LEVEL_NOT_PULSE (IPU7_IS_MAIN_IRQ_CTRL_BASE + 0x14) + +/* IS IO regs base */ +#define IPU7_IS_PHY_NUM 4U +#define IPU7_IS_IO_BASE 0x280000 + +/* dwc csi cdphy registers */ +#define IPU7_IS_IO_CDPHY_BASE(i) (IPU7_IS_IO_BASE + 0x10000 * (i)) +#define IPU7_PPI_STARTUP_RW_COMMON_DPHY_0 0x1800 +#define IPU7_PPI_STARTUP_RW_COMMON_DPHY_1 0x1802 +#define IPU7_PPI_STARTUP_RW_COMMON_DPHY_2 0x1804 +#define IPU7_PPI_STARTUP_RW_COMMON_DPHY_3 0x1806 +#define IPU7_PPI_STARTUP_RW_COMMON_DPHY_4 0x1808 +#define IPU7_PPI_STARTUP_RW_COMMON_DPHY_5 0x180a +#define IPU7_PPI_STARTUP_RW_COMMON_DPHY_6 0x180c +#define IPU7_PPI_STARTUP_RW_COMMON_DPHY_7 0x180e +#define IPU7_PPI_STARTUP_RW_COMMON_DPHY_8 0x1810 +#define IPU7_PPI_STARTUP_RW_COMMON_DPHY_9 0x1812 +#define IPU7_PPI_STARTUP_RW_COMMON_DPHY_A 0x1814 +#define IPU7_PPI_STARTUP_RW_COMMON_DPHY_10 0x1820 +#define IPU7_PPI_STARTUP_RW_COMMON_STARTUP_1_1 0x1822 +#define IPU7_PPI_STARTUP_RW_COMMON_STARTUP_1_2 0x1824 +#define IPU7_PPI_CALIBCTRL_RW_COMMON_CALIBCTRL_2_0 0x1840 +#define IPU7_PPI_CALIBCTRL_R_COMMON_CALIBCTRL_2_1 0x1842 +#define IPU7_PPI_CALIBCTRL_R_COMMON_CALIBCTRL_2_2 0x1844 +#define IPU7_PPI_CALIBCTRL_R_COMMON_CALIBCTRL_2_3 0x1846 +#define IPU7_PPI_CALIBCTRL_R_COMMON_CALIBCTRL_2_4 0x1848 +#define IPU7_PPI_CALIBCTRL_R_COMMON_CALIBCTRL_2_5 0x184a +#define IPU7_PPI_CALIBCTRL_RW_COMMON_BG_0 0x184c +#define IPU7_PPI_CALIBCTRL_RW_COMMON_CALIBCTRL_2_7 0x184e +#define IPU7_PPI_CALIBCTRL_RW_ADC_CFG_0 0x1850 +#define IPU7_PPI_CALIBCTRL_RW_ADC_CFG_1 0x1852 +#define IPU7_PPI_CALIBCTRL_R_ADC_DEBUG 0x1854 +#define IPU7_PPI_RW_LPDCOCAL_TOP_OVERRIDE 0x1c00 +#define IPU7_PPI_RW_LPDCOCAL_TIMEBASE 0x1c02 +#define IPU7_PPI_RW_LPDCOCAL_NREF 0x1c04 +#define IPU7_PPI_RW_LPDCOCAL_NREF_RANGE 0x1c06 +#define IPU7_PPI_RW_LPDCOCAL_NREF_TRIGGER_MAN 0x1c08 +#define IPU7_PPI_RW_LPDCOCAL_TWAIT_CONFIG 0x1c0a +#define IPU7_PPI_RW_LPDCOCAL_VT_CONFIG 0x1c0c +#define IPU7_PPI_R_LPDCOCAL_DEBUG_RB 0x1c0e +#define IPU7_PPI_RW_LPDCOCAL_COARSE_CFG 0x1c10 +#define IPU7_PPI_R_LPDCOCAL_DEBUG_COARSE_RB 0x1c12 +#define IPU7_PPI_R_LPDCOCAL_DEBUG_COARSE_MEAS_0_RB 0x1c14 +#define IPU7_PPI_R_LPDCOCAL_DEBUG_COARSE_MEAS_1_RB 0x1c16 +#define IPU7_PPI_R_LPDCOCAL_DEBUG_COARSE_FWORD_RB 0x1c18 +#define IPU7_PPI_R_LPDCOCAL_DEBUG_MEASURE_CURR_ERROR 0x1c1a +#define IPU7_PPI_R_LPDCOCAL_DEBUG_MEASURE_LAST_ERROR 0x1c1c +#define IPU7_PPI_R_LPDCOCAL_DEBUG_VT 0x1c1e +#define IPU7_PPI_RW_LB_TIMEBASE_CONFIG 0x1c20 +#define IPU7_PPI_RW_LB_STARTCMU_CONFIG 0x1c22 +#define IPU7_PPI_R_LBPULSE_COUNTER_RB 0x1c24 +#define IPU7_PPI_R_LB_START_CMU_RB 0x1c26 +#define IPU7_PPI_RW_LB_DPHY_BURST_START 0x1c28 +#define IPU7_PPI_RW_LB_CPHY_BURST_START 0x1c2a +#define IPU7_PPI_RW_DDLCAL_CFG_0 0x1c40 +#define IPU7_PPI_RW_DDLCAL_CFG_1 0x1c42 +#define IPU7_PPI_RW_DDLCAL_CFG_2 0x1c44 +#define IPU7_PPI_RW_DDLCAL_CFG_3 0x1c46 +#define IPU7_PPI_RW_DDLCAL_CFG_4 0x1c48 +#define IPU7_PPI_RW_DDLCAL_CFG_5 0x1c4a +#define IPU7_PPI_RW_DDLCAL_CFG_6 0x1c4c +#define IPU7_PPI_RW_DDLCAL_CFG_7 0x1c4e +#define IPU7_PPI_R_DDLCAL_DEBUG_0 0x1c50 +#define IPU7_PPI_R_DDLCAL_DEBUG_1 0x1c52 +#define IPU7_PPI_RW_PARITY_TEST 0x1c60 +#define IPU7_PPI_RW_STARTUP_OVR_0 0x1c62 +#define IPU7_PPI_RW_STARTUP_STATE_OVR_1 0x1c64 +#define IPU7_PPI_RW_DTB_SELECTOR 0x1c66 +#define IPU7_PPI_RW_DPHY_CLK_SPARE 0x1c6a +#define IPU7_PPI_RW_COMMON_CFG 0x1c6c +#define IPU7_PPI_RW_TERMCAL_CFG_0 0x1c80 +#define IPU7_PPI_R_TERMCAL_DEBUG_0 0x1c82 +#define IPU7_PPI_RW_TERMCAL_CTRL_0 0x1c84 +#define IPU7_PPI_RW_OFFSETCAL_CFG_0 0x1ca0 +#define IPU7_PPI_R_OFFSETCAL_DEBUG_LANE0 0x1ca2 +#define IPU7_PPI_R_OFFSETCAL_DEBUG_LANE1 0x1ca4 +#define IPU7_PPI_R_OFFSETCAL_DEBUG_LANE2 0x1ca6 +#define IPU7_PPI_R_OFFSETCAL_DEBUG_LANE3 0x1ca8 +#define IPU7_PPI_R_OFFSETCAL_DEBUG_LANE4 0x1caa +#define IPU7_PPI_RW_HSDCOCAL_CFG_O 0x1d00 +#define IPU7_PPI_RW_HSDCOCAL_CFG_1 0x1d02 +#define IPU7_PPI_RW_HSDCOCAL_CFG_2 0x1d04 +#define IPU7_PPI_RW_HSDCOCAL_CFG_3 0x1d06 +#define IPU7_PPI_RW_HSDCOCAL_CFG_4 0x1d08 +#define IPU7_PPI_RW_HSDCOCAL_CFG_5 0x1d0a +#define IPU7_PPI_RW_HSDCOCAL_CFG_6 0x1d0c +#define IPU7_PPI_RW_HSDCOCAL_CFG_7 0x1d0e +#define IPU7_PPI_RW_HSDCOCAL_CFG_8 0x1d10 +#define IPU7_PPI_R_HSDCOCAL_DEBUG_RB 0x1d12 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE0_OVR_0_0 0x2000 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE0_OVR_0_1 0x2002 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE0_OVR_0_2 0x2004 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE0_OVR_0_3 0x2006 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE0_OVR_0_4 0x2008 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE0_OVR_0_5 0x200a +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE0_OVR_0_6 0x200c +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE0_OVR_0_7 0x200e +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE0_OVR_0_8 0x2010 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE0_OVR_0_9 0x2012 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE0_OVR_0_10 0x2014 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE0_OVR_0_11 0x2016 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE0_OVR_0_12 0x2018 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE0_OVR_0_13 0x201a +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE0_OVR_0_14 0x201c +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE0_OVR_0_15 0x201e +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_0 0x2020 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_1 0x2022 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_2 0x2024 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_3 0x2026 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_4 0x2028 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_5 0x202a +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_6 0x202c +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_7 0x202e +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_8 0x2030 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_9 0x2032 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE0_OVR_1_10 0x2034 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE0_OVR_1_11 0x2036 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE0_OVR_1_12 0x2038 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE0_OVR_1_13 0x203a +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE0_OVR_1_14 0x203c +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE0_OVR_1_15 0x203e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_0 0x2040 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_1 0x2042 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_2 0x2044 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_3 0x2046 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_4 0x2048 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_5 0x204a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_6 0x204c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_7 0x204e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_8 0x2050 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_9 0x2052 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_10 0x2054 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_11 0x2056 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_12 0x2058 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_13 0x205a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_14 0x205c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_15 0x205e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_3_0 0x2060 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_3_1 0x2062 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_3_2 0x2064 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_3_3 0x2066 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_3_4 0x2068 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_3_5 0x206a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_3_6 0x206c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_3_7 0x206e +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_3_8 0x2070 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_3_9 0x2072 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_3_10 0x2074 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_3_11 0x2076 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_3_12 0x2078 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_3_13 0x207a +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_3_14 0x207c +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_3_15 0x207e +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_4_0 0x2080 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_4_1 0x2082 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_4_2 0x2084 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_4_3 0x2086 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_4_4 0x2088 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE0_OVR_5_0 0x20a0 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE0_OVR_5_1 0x20a2 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_5_2 0x20a4 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_5_3 0x20a6 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE0_OVR_5_4 0x20a8 +#define IPU7_CORE_DIG_RW_TRIO0_0 0x2100 +#define IPU7_CORE_DIG_RW_TRIO0_1 0x2102 +#define IPU7_CORE_DIG_RW_TRIO0_2 0x2104 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE1_OVR_0_0 0x2400 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE1_OVR_0_1 0x2402 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE1_OVR_0_2 0x2404 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE1_OVR_0_3 0x2406 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE1_OVR_0_4 0x2408 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE1_OVR_0_5 0x240a +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE1_OVR_0_6 0x240c +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE1_OVR_0_7 0x240e +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE1_OVR_0_8 0x2410 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE1_OVR_0_9 0x2412 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE1_OVR_0_10 0x2414 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE1_OVR_0_11 0x2416 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE1_OVR_0_12 0x2418 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE1_OVR_0_13 0x241a +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE1_OVR_0_14 0x241c +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE1_OVR_0_15 0x241e +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_0 0x2420 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_1 0x2422 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_2 0x2424 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_3 0x2426 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_4 0x2428 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_5 0x242a +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_6 0x242c +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_7 0x242e +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_8 0x2430 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_9 0x2432 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE1_OVR_1_10 0x2434 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE1_OVR_1_11 0x2436 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE1_OVR_1_12 0x2438 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE1_OVR_1_13 0x243a +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE1_OVR_1_14 0x243c +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE1_OVR_1_15 0x243e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_0 0x2440 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_1 0x2442 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_2 0x2444 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_3 0x2446 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_4 0x2448 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_5 0x244a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_6 0x244c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_7 0x244e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_8 0x2450 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_9 0x2452 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_10 0x2454 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_11 0x2456 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_12 0x2458 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_13 0x245a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_14 0x245c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_15 0x245e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_3_0 0x2460 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_3_1 0x2462 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_3_2 0x2464 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_3_3 0x2466 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_3_4 0x2468 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_3_5 0x246a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_3_6 0x246c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_3_7 0x246e +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_3_8 0x2470 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_3_9 0x2472 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_3_10 0x2474 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_3_11 0x2476 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_3_12 0x2478 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_3_13 0x247a +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_3_14 0x247c +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_3_15 0x247e +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_4_0 0x2480 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_4_1 0x2482 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_4_2 0x2484 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_4_3 0x2486 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_4_4 0x2488 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE1_OVR_5_0 0x24a0 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE1_OVR_5_1 0x24a2 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_5_2 0x24a4 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_5_3 0x24a6 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE1_OVR_5_4 0x24a8 +#define IPU7_CORE_DIG_RW_TRIO1_0 0x2500 +#define IPU7_CORE_DIG_RW_TRIO1_1 0x2502 +#define IPU7_CORE_DIG_RW_TRIO1_2 0x2504 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE2_OVR_0_0 0x2800 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE2_OVR_0_1 0x2802 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE2_OVR_0_2 0x2804 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE2_OVR_0_3 0x2806 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE2_OVR_0_4 0x2808 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE2_OVR_0_5 0x280a +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE2_OVR_0_6 0x280c +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE2_OVR_0_7 0x280e +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE2_OVR_0_8 0x2810 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE2_OVR_0_9 0x2812 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE2_OVR_0_10 0x2814 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE2_OVR_0_11 0x2816 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE2_OVR_0_12 0x2818 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE2_OVR_0_13 0x281a +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE2_OVR_0_14 0x281c +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE2_OVR_0_15 0x281e +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_0 0x2820 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_1 0x2822 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_2 0x2824 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_3 0x2826 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_4 0x2828 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_5 0x282a +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_6 0x282c +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_7 0x282e +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_8 0x2830 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_9 0x2832 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE2_OVR_1_10 0x2834 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE2_OVR_1_11 0x2836 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE2_OVR_1_12 0x2838 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE2_OVR_1_13 0x283a +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE2_OVR_1_14 0x283c +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE2_OVR_1_15 0x283e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_0 0x2840 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_1 0x2842 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_2 0x2844 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_3 0x2846 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_4 0x2848 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_5 0x284a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_6 0x284c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_7 0x284e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_8 0x2850 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_9 0x2852 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_10 0x2854 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_11 0x2856 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_12 0x2858 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_13 0x285a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_14 0x285c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_15 0x285e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_3_0 0x2860 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_3_1 0x2862 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_3_2 0x2864 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_3_3 0x2866 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_3_4 0x2868 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_3_5 0x286a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_3_6 0x286c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_3_7 0x286e +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_3_8 0x2870 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_3_9 0x2872 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_3_10 0x2874 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_3_11 0x2876 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_3_12 0x2878 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_3_13 0x287a +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_3_14 0x287c +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_3_15 0x287e +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_4_0 0x2880 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_4_1 0x2882 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_4_2 0x2884 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_4_3 0x2886 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_4_4 0x2888 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE2_OVR_5_0 0x28a0 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE2_OVR_5_1 0x28a2 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_5_2 0x28a4 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_5_3 0x28a6 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE2_OVR_5_4 0x28a8 +#define IPU7_CORE_DIG_RW_TRIO2_0 0x2900 +#define IPU7_CORE_DIG_RW_TRIO2_1 0x2902 +#define IPU7_CORE_DIG_RW_TRIO2_2 0x2904 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE3_OVR_0_0 0x2c00 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE3_OVR_0_1 0x2c02 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE3_OVR_0_2 0x2c04 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE3_OVR_0_3 0x2c06 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE3_OVR_0_4 0x2c08 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE3_OVR_0_5 0x2c0a +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE3_OVR_0_6 0x2c0c +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE3_OVR_0_7 0x2c0e +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE3_OVR_0_8 0x2c10 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE3_OVR_0_9 0x2c12 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE3_OVR_0_10 0x2c14 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE3_OVR_0_11 0x2c16 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE3_OVR_0_12 0x2c18 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE3_OVR_0_13 0x2c1a +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE3_OVR_0_14 0x2c1c +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE3_OVR_0_15 0x2c1e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_0 0x2c40 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_1 0x2c42 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_2 0x2c44 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_3 0x2c46 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_4 0x2c48 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_5 0x2c4a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_6 0x2c4c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_7 0x2c4e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_8 0x2c50 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_9 0x2c52 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_10 0x2c54 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_11 0x2c56 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_12 0x2c58 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_13 0x2c5a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_14 0x2c5c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_15 0x2c5e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_3_0 0x2c60 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_3_1 0x2c62 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_3_2 0x2c64 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_3_3 0x2c66 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_3_4 0x2c68 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_3_5 0x2c6a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_3_6 0x2c6c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_3_7 0x2c6e +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_3_8 0x2c70 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_3_9 0x2c72 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_3_10 0x2c74 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_3_11 0x2c76 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_3_12 0x2c78 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_3_13 0x2c7a +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_3_14 0x2c7c +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_3_15 0x2c7e +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_4_0 0x2c80 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_4_1 0x2c82 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_4_2 0x2c84 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_4_3 0x2c86 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_4_4 0x2c88 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_0 0x3040 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_1 0x3042 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_2 0x3044 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_3 0x3046 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_4 0x3048 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_5 0x304a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_6 0x304c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_7 0x304e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_8 0x3050 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_9 0x3052 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_10 0x3054 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_11 0x3056 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_12 0x3058 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_13 0x305a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_14 0x305c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_15 0x305e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_3_0 0x3060 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_3_1 0x3062 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_3_2 0x3064 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_3_3 0x3066 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_3_4 0x3068 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_3_5 0x306a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_3_6 0x306c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_3_7 0x306e +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_3_8 0x3070 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_3_9 0x3072 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_3_10 0x3074 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_3_11 0x3076 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_3_12 0x3078 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_3_13 0x307a +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_3_14 0x307c +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_3_15 0x307e +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_4_0 0x3080 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_4_1 0x3082 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_4_2 0x3084 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_4_3 0x3086 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_4_4 0x3088 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_CLK_OVR_0_0 0x3400 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_CLK_OVR_0_1 0x3402 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_CLK_OVR_0_2 0x3404 +#define IPU7_CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_0 0x3800 +#define IPU7_CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_1 0x3802 +#define IPU7_CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_2 0x3804 +#define IPU7_CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_3 0x3806 +#define IPU7_CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_4 0x3808 +#define IPU7_CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_5 0x380a +#define IPU7_CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_6 0x380c +#define IPU7_CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_7 0x380e +#define IPU7_CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_8 0x3810 +#define IPU7_CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_9 0x3812 +#define IPU7_CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_10 0x3814 +#define IPU7_CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_0_11 0x3816 +#define IPU7_CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_0_12 0x3818 +#define IPU7_CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_0_13 0x381a +#define IPU7_CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_0_14 0x381c +#define IPU7_CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_0_15 0x381e +#define IPU7_CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_1_0 0x3820 +#define IPU7_CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_1_1 0x3822 +#define IPU7_CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_1_2 0x3824 +#define IPU7_CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_1_3 0x3826 +#define IPU7_CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_1_4 0x3828 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_0 0x3840 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_1 0x3842 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_2 0x3844 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_3 0x3846 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_4 0x3848 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_5 0x384a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_6 0x384c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_7 0x384e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_8 0x3850 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_9 0x3852 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_10 0x3854 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_11 0x3856 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_CB_CTRL_2_12 0x3858 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_CB_CTRL_2_13 0x385a +#define IPU7_CORE_DIG_IOCTRL_R_AFE_CB_CTRL_2_14 0x385c +#define IPU7_CORE_DIG_IOCTRL_R_AFE_CB_CTRL_2_15 0x385e +#define IPU7_CORE_DIG_IOCTRL_R_AFE_CB_CTRL_3_0 0x3860 +#define IPU7_CORE_DIG_RW_COMMON_0 0x3880 +#define IPU7_CORE_DIG_RW_COMMON_1 0x3882 +#define IPU7_CORE_DIG_RW_COMMON_2 0x3884 +#define IPU7_CORE_DIG_RW_COMMON_3 0x3886 +#define IPU7_CORE_DIG_RW_COMMON_4 0x3888 +#define IPU7_CORE_DIG_RW_COMMON_5 0x388a +#define IPU7_CORE_DIG_RW_COMMON_6 0x388c +#define IPU7_CORE_DIG_RW_COMMON_7 0x388e +#define IPU7_CORE_DIG_RW_COMMON_8 0x3890 +#define IPU7_CORE_DIG_RW_COMMON_9 0x3892 +#define IPU7_CORE_DIG_RW_COMMON_10 0x3894 +#define IPU7_CORE_DIG_RW_COMMON_11 0x3896 +#define IPU7_CORE_DIG_RW_COMMON_12 0x3898 +#define IPU7_CORE_DIG_RW_COMMON_13 0x389a +#define IPU7_CORE_DIG_RW_COMMON_14 0x389c +#define IPU7_CORE_DIG_RW_COMMON_15 0x389e +#define IPU7_CORE_DIG_ANACTRL_RW_COMMON_ANACTRL_0 0x39e0 +#define IPU7_CORE_DIG_ANACTRL_RW_COMMON_ANACTRL_1 0x39e2 +#define IPU7_CORE_DIG_ANACTRL_RW_COMMON_ANACTRL_2 0x39e4 +#define IPU7_CORE_DIG_ANACTRL_RW_COMMON_ANACTRL_3 0x39e6 +#define IPU7_CORE_DIG_COMMON_RW_DESKEW_FINE_MEM 0x3fe0 +#define IPU7_CORE_DIG_COMMON_R_DESKEW_FINE_MEM 0x3fe2 +#define IPU7_PPI_RW_DPHY_LANE0_LBERT_0 0x4000 +#define IPU7_PPI_RW_DPHY_LANE0_LBERT_1 0x4002 +#define IPU7_PPI_R_DPHY_LANE0_LBERT_0 0x4004 +#define IPU7_PPI_R_DPHY_LANE0_LBERT_1 0x4006 +#define IPU7_PPI_RW_DPHY_LANE0_SPARE 0x4008 +#define IPU7_PPI_RW_DPHY_LANE1_LBERT_0 0x4400 +#define IPU7_PPI_RW_DPHY_LANE1_LBERT_1 0x4402 +#define IPU7_PPI_R_DPHY_LANE1_LBERT_0 0x4404 +#define IPU7_PPI_R_DPHY_LANE1_LBERT_1 0x4406 +#define IPU7_PPI_RW_DPHY_LANE1_SPARE 0x4408 +#define IPU7_PPI_RW_DPHY_LANE2_LBERT_0 0x4800 +#define IPU7_PPI_RW_DPHY_LANE2_LBERT_1 0x4802 +#define IPU7_PPI_R_DPHY_LANE2_LBERT_0 0x4804 +#define IPU7_PPI_R_DPHY_LANE2_LBERT_1 0x4806 +#define IPU7_PPI_RW_DPHY_LANE2_SPARE 0x4808 +#define IPU7_PPI_RW_DPHY_LANE3_LBERT_0 0x4c00 +#define IPU7_PPI_RW_DPHY_LANE3_LBERT_1 0x4c02 +#define IPU7_PPI_R_DPHY_LANE3_LBERT_0 0x4c04 +#define IPU7_PPI_R_DPHY_LANE3_LBERT_1 0x4c06 +#define IPU7_PPI_RW_DPHY_LANE3_SPARE 0x4c08 +#define IPU7_CORE_DIG_DLANE_0_RW_CFG_0 0x6000 +#define IPU7_CORE_DIG_DLANE_0_RW_CFG_1 0x6002 +#define IPU7_CORE_DIG_DLANE_0_RW_CFG_2 0x6004 +#define IPU7_CORE_DIG_DLANE_0_RW_LP_0 0x6080 +#define IPU7_CORE_DIG_DLANE_0_RW_LP_1 0x6082 +#define IPU7_CORE_DIG_DLANE_0_RW_LP_2 0x6084 +#define IPU7_CORE_DIG_DLANE_0_R_LP_0 0x60a0 +#define IPU7_CORE_DIG_DLANE_0_R_LP_1 0x60a2 +#define IPU7_CORE_DIG_DLANE_0_R_HS_TX_0 0x60e0 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_RX_0 0x6100 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_RX_1 0x6102 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_RX_2 0x6104 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_RX_3 0x6106 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_RX_4 0x6108 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_RX_5 0x610a +#define IPU7_CORE_DIG_DLANE_0_RW_HS_RX_6 0x610c +#define IPU7_CORE_DIG_DLANE_0_RW_HS_RX_7 0x610e +#define IPU7_CORE_DIG_DLANE_0_RW_HS_RX_8 0x6110 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_RX_9 0x6112 +#define IPU7_CORE_DIG_DLANE_0_R_HS_RX_0 0x6120 +#define IPU7_CORE_DIG_DLANE_0_R_HS_RX_1 0x6122 +#define IPU7_CORE_DIG_DLANE_0_R_HS_RX_2 0x6124 +#define IPU7_CORE_DIG_DLANE_0_R_HS_RX_3 0x6126 +#define IPU7_CORE_DIG_DLANE_0_R_HS_RX_4 0x6128 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_TX_0 0x6200 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_TX_1 0x6202 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_TX_2 0x6204 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_TX_3 0x6206 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_TX_4 0x6208 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_TX_5 0x620a +#define IPU7_CORE_DIG_DLANE_0_RW_HS_TX_6 0x620c +#define IPU7_CORE_DIG_DLANE_0_RW_HS_TX_7 0x620e +#define IPU7_CORE_DIG_DLANE_0_RW_HS_TX_8 0x6210 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_TX_9 0x6212 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_TX_10 0x6214 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_TX_11 0x6216 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_TX_12 0x6218 +#define IPU7_CORE_DIG_DLANE_1_RW_CFG_0 0x6400 +#define IPU7_CORE_DIG_DLANE_1_RW_CFG_1 0x6402 +#define IPU7_CORE_DIG_DLANE_1_RW_CFG_2 0x6404 +#define IPU7_CORE_DIG_DLANE_1_RW_LP_0 0x6480 +#define IPU7_CORE_DIG_DLANE_1_RW_LP_1 0x6482 +#define IPU7_CORE_DIG_DLANE_1_RW_LP_2 0x6484 +#define IPU7_CORE_DIG_DLANE_1_R_LP_0 0x64a0 +#define IPU7_CORE_DIG_DLANE_1_R_LP_1 0x64a2 +#define IPU7_CORE_DIG_DLANE_1_R_HS_TX_0 0x64e0 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_RX_0 0x6500 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_RX_1 0x6502 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_RX_2 0x6504 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_RX_3 0x6506 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_RX_4 0x6508 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_RX_5 0x650a +#define IPU7_CORE_DIG_DLANE_1_RW_HS_RX_6 0x650c +#define IPU7_CORE_DIG_DLANE_1_RW_HS_RX_7 0x650e +#define IPU7_CORE_DIG_DLANE_1_RW_HS_RX_8 0x6510 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_RX_9 0x6512 +#define IPU7_CORE_DIG_DLANE_1_R_HS_RX_0 0x6520 +#define IPU7_CORE_DIG_DLANE_1_R_HS_RX_1 0x6522 +#define IPU7_CORE_DIG_DLANE_1_R_HS_RX_2 0x6524 +#define IPU7_CORE_DIG_DLANE_1_R_HS_RX_3 0x6526 +#define IPU7_CORE_DIG_DLANE_1_R_HS_RX_4 0x6528 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_TX_0 0x6600 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_TX_1 0x6602 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_TX_2 0x6604 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_TX_3 0x6606 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_TX_4 0x6608 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_TX_5 0x660a +#define IPU7_CORE_DIG_DLANE_1_RW_HS_TX_6 0x660c +#define IPU7_CORE_DIG_DLANE_1_RW_HS_TX_7 0x660e +#define IPU7_CORE_DIG_DLANE_1_RW_HS_TX_8 0x6610 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_TX_9 0x6612 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_TX_10 0x6614 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_TX_11 0x6616 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_TX_12 0x6618 +#define IPU7_CORE_DIG_DLANE_2_RW_CFG_0 0x6800 +#define IPU7_CORE_DIG_DLANE_2_RW_CFG_1 0x6802 +#define IPU7_CORE_DIG_DLANE_2_RW_CFG_2 0x6804 +#define IPU7_CORE_DIG_DLANE_2_RW_LP_0 0x6880 +#define IPU7_CORE_DIG_DLANE_2_RW_LP_1 0x6882 +#define IPU7_CORE_DIG_DLANE_2_RW_LP_2 0x6884 +#define IPU7_CORE_DIG_DLANE_2_R_LP_0 0x68a0 +#define IPU7_CORE_DIG_DLANE_2_R_LP_1 0x68a2 +#define IPU7_CORE_DIG_DLANE_2_R_HS_TX_0 0x68e0 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_RX_0 0x6900 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_RX_1 0x6902 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_RX_2 0x6904 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_RX_3 0x6906 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_RX_4 0x6908 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_RX_5 0x690a +#define IPU7_CORE_DIG_DLANE_2_RW_HS_RX_6 0x690c +#define IPU7_CORE_DIG_DLANE_2_RW_HS_RX_7 0x690e +#define IPU7_CORE_DIG_DLANE_2_RW_HS_RX_8 0x6910 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_RX_9 0x6912 +#define IPU7_CORE_DIG_DLANE_2_R_HS_RX_0 0x6920 +#define IPU7_CORE_DIG_DLANE_2_R_HS_RX_1 0x6922 +#define IPU7_CORE_DIG_DLANE_2_R_HS_RX_2 0x6924 +#define IPU7_CORE_DIG_DLANE_2_R_HS_RX_3 0x6926 +#define IPU7_CORE_DIG_DLANE_2_R_HS_RX_4 0x6928 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_TX_0 0x6a00 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_TX_1 0x6a02 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_TX_2 0x6a04 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_TX_3 0x6a06 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_TX_4 0x6a08 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_TX_5 0x6a0a +#define IPU7_CORE_DIG_DLANE_2_RW_HS_TX_6 0x6a0c +#define IPU7_CORE_DIG_DLANE_2_RW_HS_TX_7 0x6a0e +#define IPU7_CORE_DIG_DLANE_2_RW_HS_TX_8 0x6a10 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_TX_9 0x6a12 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_TX_10 0x6a14 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_TX_11 0x6a16 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_TX_12 0x6a18 +#define IPU7_CORE_DIG_DLANE_3_RW_CFG_0 0x6c00 +#define IPU7_CORE_DIG_DLANE_3_RW_CFG_1 0x6c02 +#define IPU7_CORE_DIG_DLANE_3_RW_CFG_2 0x6c04 +#define IPU7_CORE_DIG_DLANE_3_RW_LP_0 0x6c80 +#define IPU7_CORE_DIG_DLANE_3_RW_LP_1 0x6c82 +#define IPU7_CORE_DIG_DLANE_3_RW_LP_2 0x6c84 +#define IPU7_CORE_DIG_DLANE_3_R_LP_0 0x6ca0 +#define IPU7_CORE_DIG_DLANE_3_R_LP_1 0x6ca2 +#define IPU7_CORE_DIG_DLANE_3_R_HS_TX_0 0x6ce0 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_RX_0 0x6d00 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_RX_1 0x6d02 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_RX_2 0x6d04 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_RX_3 0x6d06 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_RX_4 0x6d08 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_RX_5 0x6d0a +#define IPU7_CORE_DIG_DLANE_3_RW_HS_RX_6 0x6d0c +#define IPU7_CORE_DIG_DLANE_3_RW_HS_RX_7 0x6d0e +#define IPU7_CORE_DIG_DLANE_3_RW_HS_RX_8 0x6d10 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_RX_9 0x6d12 +#define IPU7_CORE_DIG_DLANE_3_R_HS_RX_0 0x6d20 +#define IPU7_CORE_DIG_DLANE_3_R_HS_RX_1 0x6d22 +#define IPU7_CORE_DIG_DLANE_3_R_HS_RX_2 0x6d24 +#define IPU7_CORE_DIG_DLANE_3_R_HS_RX_3 0x6d26 +#define IPU7_CORE_DIG_DLANE_3_R_HS_RX_4 0x6d28 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_TX_0 0x6e00 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_TX_1 0x6e02 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_TX_2 0x6e04 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_TX_3 0x6e06 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_TX_4 0x6e08 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_TX_5 0x6e0a +#define IPU7_CORE_DIG_DLANE_3_RW_HS_TX_6 0x6e0c +#define IPU7_CORE_DIG_DLANE_3_RW_HS_TX_7 0x6e0e +#define IPU7_CORE_DIG_DLANE_3_RW_HS_TX_8 0x6e10 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_TX_9 0x6e12 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_TX_10 0x6e14 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_TX_11 0x6e16 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_TX_12 0x6e18 +#define IPU7_CORE_DIG_DLANE_CLK_RW_CFG_0 0x7000 +#define IPU7_CORE_DIG_DLANE_CLK_RW_CFG_1 0x7002 +#define IPU7_CORE_DIG_DLANE_CLK_RW_CFG_2 0x7004 +#define IPU7_CORE_DIG_DLANE_CLK_RW_LP_0 0x7080 +#define IPU7_CORE_DIG_DLANE_CLK_RW_LP_1 0x7082 +#define IPU7_CORE_DIG_DLANE_CLK_RW_LP_2 0x7084 +#define IPU7_CORE_DIG_DLANE_CLK_R_LP_0 0x70a0 +#define IPU7_CORE_DIG_DLANE_CLK_R_LP_1 0x70a2 +#define IPU7_CORE_DIG_DLANE_CLK_R_HS_TX_0 0x70e0 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_RX_0 0x7100 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_RX_1 0x7102 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_RX_2 0x7104 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_RX_3 0x7106 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_RX_4 0x7108 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_RX_5 0x710a +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_RX_6 0x710c +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_RX_7 0x710e +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_RX_8 0x7110 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_RX_9 0x7112 +#define IPU7_CORE_DIG_DLANE_CLK_R_HS_RX_0 0x7120 +#define IPU7_CORE_DIG_DLANE_CLK_R_HS_RX_1 0x7122 +#define IPU7_CORE_DIG_DLANE_CLK_R_HS_RX_2 0x7124 +#define IPU7_CORE_DIG_DLANE_CLK_R_HS_RX_3 0x7126 +#define IPU7_CORE_DIG_DLANE_CLK_R_HS_RX_4 0x7128 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_TX_0 0x7200 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_TX_1 0x7202 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_TX_2 0x7204 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_TX_3 0x7206 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_TX_4 0x7208 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_TX_5 0x720a +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_TX_6 0x720c +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_TX_7 0x720e +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_TX_8 0x7210 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_TX_9 0x7212 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_TX_10 0x7214 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_TX_11 0x7216 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_TX_12 0x7218 +#define IPU7_PPI_RW_CPHY_TRIO0_LBERT_0 0x8000 +#define IPU7_PPI_RW_CPHY_TRIO0_LBERT_1 0x8002 +#define IPU7_PPI_R_CPHY_TRIO0_LBERT_0 0x8004 +#define IPU7_PPI_R_CPHY_TRIO0_LBERT_1 0x8006 +#define IPU7_PPI_RW_CPHY_TRIO0_SPARE 0x8008 +#define IPU7_PPI_RW_CPHY_TRIO1_LBERT_0 0x8400 +#define IPU7_PPI_RW_CPHY_TRIO1_LBERT_1 0x8402 +#define IPU7_PPI_R_CPHY_TRIO1_LBERT_0 0x8404 +#define IPU7_PPI_R_CPHY_TRIO1_LBERT_1 0x8406 +#define IPU7_PPI_RW_CPHY_TRIO1_SPARE 0x8408 +#define IPU7_PPI_RW_CPHY_TRIO2_LBERT_0 0x8800 +#define IPU7_PPI_RW_CPHY_TRIO2_LBERT_1 0x8802 +#define IPU7_PPI_R_CPHY_TRIO2_LBERT_0 0x8804 +#define IPU7_PPI_R_CPHY_TRIO2_LBERT_1 0x8806 +#define IPU7_PPI_RW_CPHY_TRIO2_SPARE 0x8808 +#define IPU7_CORE_DIG_CLANE_0_RW_CFG_0 0xa000 +#define IPU7_CORE_DIG_CLANE_0_RW_CFG_2 0xa004 +#define IPU7_CORE_DIG_CLANE_0_RW_LP_0 0xa080 +#define IPU7_CORE_DIG_CLANE_0_RW_LP_1 0xa082 +#define IPU7_CORE_DIG_CLANE_0_RW_LP_2 0xa084 +#define IPU7_CORE_DIG_CLANE_0_R_LP_0 0xa0a0 +#define IPU7_CORE_DIG_CLANE_0_R_LP_1 0xa0a2 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_RX_0 0xa100 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_RX_1 0xa102 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_RX_2 0xa104 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_RX_3 0xa106 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_RX_4 0xa108 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_RX_5 0xa10a +#define IPU7_CORE_DIG_CLANE_0_RW_HS_RX_6 0xa10c +#define IPU7_CORE_DIG_CLANE_0_R_RX_0 0xa120 +#define IPU7_CORE_DIG_CLANE_0_R_RX_1 0xa122 +#define IPU7_CORE_DIG_CLANE_0_R_TX_0 0xa124 +#define IPU7_CORE_DIG_CLANE_0_R_RX_2 0xa126 +#define IPU7_CORE_DIG_CLANE_0_R_RX_3 0xa128 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_TX_0 0xa200 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_TX_1 0xa202 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_TX_2 0xa204 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_TX_3 0xa206 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_TX_4 0xa208 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_TX_5 0xa20a +#define IPU7_CORE_DIG_CLANE_0_RW_HS_TX_6 0xa20c +#define IPU7_CORE_DIG_CLANE_0_RW_HS_TX_7 0xa20e +#define IPU7_CORE_DIG_CLANE_0_RW_HS_TX_8 0xa210 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_TX_9 0xa212 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_TX_10 0xa214 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_TX_11 0xa216 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_TX_12 0xa218 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_TX_13 0xa21a +#define IPU7_CORE_DIG_CLANE_1_RW_CFG_0 0xa400 +#define IPU7_CORE_DIG_CLANE_1_RW_CFG_2 0xa404 +#define IPU7_CORE_DIG_CLANE_1_RW_LP_0 0xa480 +#define IPU7_CORE_DIG_CLANE_1_RW_LP_1 0xa482 +#define IPU7_CORE_DIG_CLANE_1_RW_LP_2 0xa484 +#define IPU7_CORE_DIG_CLANE_1_R_LP_0 0xa4a0 +#define IPU7_CORE_DIG_CLANE_1_R_LP_1 0xa4a2 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_RX_0 0xa500 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_RX_1 0xa502 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_RX_2 0xa504 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_RX_3 0xa506 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_RX_4 0xa508 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_RX_5 0xa50a +#define IPU7_CORE_DIG_CLANE_1_RW_HS_RX_6 0xa50c +#define IPU7_CORE_DIG_CLANE_1_R_RX_0 0xa520 +#define IPU7_CORE_DIG_CLANE_1_R_RX_1 0xa522 +#define IPU7_CORE_DIG_CLANE_1_R_TX_0 0xa524 +#define IPU7_CORE_DIG_CLANE_1_R_RX_2 0xa526 +#define IPU7_CORE_DIG_CLANE_1_R_RX_3 0xa528 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_TX_0 0xa600 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_TX_1 0xa602 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_TX_2 0xa604 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_TX_3 0xa606 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_TX_4 0xa608 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_TX_5 0xa60a +#define IPU7_CORE_DIG_CLANE_1_RW_HS_TX_6 0xa60c +#define IPU7_CORE_DIG_CLANE_1_RW_HS_TX_7 0xa60e +#define IPU7_CORE_DIG_CLANE_1_RW_HS_TX_8 0xa610 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_TX_9 0xa612 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_TX_10 0xa614 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_TX_11 0xa616 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_TX_12 0xa618 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_TX_13 0xa61a +#define IPU7_CORE_DIG_CLANE_2_RW_CFG_0 0xa800 +#define IPU7_CORE_DIG_CLANE_2_RW_CFG_2 0xa804 +#define IPU7_CORE_DIG_CLANE_2_RW_LP_0 0xa880 +#define IPU7_CORE_DIG_CLANE_2_RW_LP_1 0xa882 +#define IPU7_CORE_DIG_CLANE_2_RW_LP_2 0xa884 +#define IPU7_CORE_DIG_CLANE_2_R_LP_0 0xa8a0 +#define IPU7_CORE_DIG_CLANE_2_R_LP_1 0xa8a2 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_RX_0 0xa900 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_RX_1 0xa902 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_RX_2 0xa904 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_RX_3 0xa906 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_RX_4 0xa908 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_RX_5 0xa90a +#define IPU7_CORE_DIG_CLANE_2_RW_HS_RX_6 0xa90c +#define IPU7_CORE_DIG_CLANE_2_R_RX_0 0xa920 +#define IPU7_CORE_DIG_CLANE_2_R_RX_1 0xa922 +#define IPU7_CORE_DIG_CLANE_2_R_TX_0 0xa924 +#define IPU7_CORE_DIG_CLANE_2_R_RX_2 0xa926 +#define IPU7_CORE_DIG_CLANE_2_R_RX_3 0xa928 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_TX_0 0xaa00 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_TX_1 0xaa02 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_TX_2 0xaa04 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_TX_3 0xaa06 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_TX_4 0xaa08 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_TX_5 0xaa0a +#define IPU7_CORE_DIG_CLANE_2_RW_HS_TX_6 0xaa0c +#define IPU7_CORE_DIG_CLANE_2_RW_HS_TX_7 0xaa0e +#define IPU7_CORE_DIG_CLANE_2_RW_HS_TX_8 0xaa10 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_TX_9 0xaa12 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_TX_10 0xaa14 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_TX_11 0xaa16 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_TX_12 0xaa18 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_TX_13 0xaa1a + +/* dwc csi host controller registers */ +#define IPU7_IS_IO_CSI2_HOST_BASE(i) (IPU7_IS_IO_BASE + 0x40000 + \ + 0x2000 * (i)) +#define IPU7_VERSION 0x0 +#define IPU7_N_LANES 0x4 +#define IPU7_CSI2_RESETN 0x8 +#define IPU7_INT_ST_MAIN 0xc +#define IPU7_DATA_IDS_1 0x10 +#define IPU7_DATA_IDS_2 0x14 +#define IPU7_CDPHY_MODE 0x1c +#define IPU7_DATA_IDS_VC_1 0x30 +#define IPU7_DATA_IDS_VC_2 0x34 +#define IPU7_PHY_SHUTDOWNZ 0x40 +#define IPU7_DPHY_RSTZ 0x44 +#define IPU7_PHY_RX 0x48 +#define IPU7_PHY_STOPSTATE 0x4c +#define IPU7_PHY_TEST_CTRL0 0x50 +#define IPU7_PHY_TEST_CTRL1 0x54 +#define IPU7_PPI_PG_PATTERN_VRES 0x60 +#define IPU7_PPI_PG_PATTERN_HRES 0x64 +#define IPU7_PPI_PG_CONFIG 0x68 +#define IPU7_PPI_PG_ENABLE 0x6c +#define IPU7_PPI_PG_STATUS 0x70 +#define IPU7_VC_EXTENSION 0xc8 +#define IPU7_PHY_CAL 0xcc +#define IPU7_INT_ST_PHY_FATAL 0xe0 +#define IPU7_INT_MSK_PHY_FATAL 0xe4 +#define IPU7_INT_FORCE_PHY_FATAL 0xe8 +#define IPU7_INT_ST_PKT_FATAL 0xf0 +#define IPU7_INT_MSK_PKT_FATAL 0xf4 +#define IPU7_INT_FORCE_PKT_FATAL 0xf8 +#define IPU7_INT_ST_PHY 0x110 +#define IPU7_INT_MSK_PHY 0x114 +#define IPU7_INT_FORCE_PHY 0x118 +#define IPU7_INT_ST_LINE 0x130 +#define IPU7_INT_MSK_LINE 0x134 +#define IPU7_INT_FORCE_LINE 0x138 +#define IPU7_INT_ST_BNDRY_FRAME_FATAL 0x280 +#define IPU7_INT_MSK_BNDRY_FRAME_FATAL 0x284 +#define IPU7_INT_FORCE_BNDRY_FRAME_FATAL 0x288 +#define IPU7_INT_ST_SEQ_FRAME_FATAL 0x290 +#define IPU7_INT_MSK_SEQ_FRAME_FATAL 0x294 +#define IPU7_INT_FORCE_SEQ_FRAME_FATAL 0x298 +#define IPU7_INT_ST_CRC_FRAME_FATAL 0x2a0 +#define IPU7_INT_MSK_CRC_FRAME_FATAL 0x2a4 +#define IPU7_INT_FORCE_CRC_FRAME_FATAL 0x2a8 +#define IPU7_INT_ST_PLD_CRC_FATAL 0x2b0 +#define IPU7_INT_MSK_PLD_CRC_FATAL 0x2b4 +#define IPU7_INT_FORCE_PLD_CRC_FATAL 0x2b8 +#define IPU7_INT_ST_DATA_ID 0x2c0 +#define IPU7_INT_MSK_DATA_ID 0x2c4 +#define IPU7_INT_FORCE_DATA_ID 0x2c8 +#define IPU7_INT_ST_ECC_CORRECTED 0x2d0 +#define IPU7_INT_MSK_ECC_CORRECTED 0x2d4 +#define IPU7_INT_FORCE_ECC_CORRECTED 0x2d8 +#define IPU7_SCRAMBLING 0x300 +#define IPU7_SCRAMBLING_SEED1 0x304 +#define IPU7_SCRAMBLING_SEED2 0x308 +#define IPU7_SCRAMBLING_SEED3 0x30c +#define IPU7_SCRAMBLING_SEED4 0x310 +#define IPU7_SCRAMBLING 0x300 + +#define IPU7_IS_IO_CSI2_ADPL_PORT_BASE(i) (IPU7_IS_IO_BASE + 0x40800 + \ + 0x2000 * (i)) +#define IPU7_CSI2_ADPL_INPUT_MODE 0x0 +#define IPU7_CSI2_ADPL_CSI_RX_ERR_IRQ_CLEAR_EN 0x4 +#define IPU7_CSI2_ADPL_CSI_RX_ERR_IRQ_CLEAR_ADDR 0x8 +#define IPU7_CSI2_ADPL_CSI_RX_ERR_IRQ_STATUS 0xc +#define IPU7_CSI2_ADPL_IRQ_CTL_COMMON_STATUS 0xa4 +#define IPU7_CSI2_ADPL_IRQ_CTL_COMMON_CLEAR 0xa8 +#define IPU7_CSI2_ADPL_IRQ_CTL_COMMON_ENABLE 0xac +#define IPU7_CSI2_ADPL_IRQ_CTL_FS_STATUS 0xbc +#define IPU7_CSI2_ADPL_IRQ_CTL_FS_CLEAR 0xc0 +#define IPU7_CSI2_ADPL_IRQ_CTL_FS_ENABLE 0xc4 +#define IPU7_CSI2_ADPL_IRQ_CTL_FE_STATUS 0xc8 +#define IPU7_CSI2_ADPL_IRQ_CTL_FE_CLEAR 0xcc +#define IPU7_CSI2_ADPL_IRQ_CTL_FE_ENABLE 0xd0 + +/* software control the legacy csi irq */ +#define IPU7_IS_IO_CSI2_ERR_LEGACY_IRQ_CTL_BASE(i) (IPU7_IS_IO_BASE + \ + 0x40c00 + 0x2000 * (i)) +#define IPU7_IS_IO_CSI2_SYNC_LEGACY_IRQ_CTL_BASE(i) (IPU7_IS_IO_BASE + \ + 0x40d00 + 0x2000 * (i)) +#define IPU7_IS_IO_CSI2_LEGACY_IRQ_CTRL_BASE (IPU7_IS_IO_BASE + \ + 0x49000) +#define IPU7_IS_IO_CSI2_IRQ_CTRL_BASE (IPU7_IS_IO_BASE + 0x4e100) + +#define IPU7_IRQ_CTL_EDGE 0x0 +#define IPU7_IRQ_CTL_MASK 0x4 +#define IPU7_IRQ_CTL_STATUS 0x8 +#define IPU7_IRQ_CTL_CLEAR 0xc +#define IPU7_IRQ_CTL_ENABLE 0x10 +/* FE irq for PTL */ +#define IPU7_IRQ1_CTL_MASK 0x14 +#define IPU7_IRQ1_CTL_STATUS 0x18 +#define IPU7_IRQ1_CTL_CLEAR 0x1c +#define IPU7_IRQ1_CTL_ENABLE 0x20 + +/* software to set the clock gate to use the port or mgc */ +#define IPU7_IS_IO_GPREGS_BASE (IPU7_IS_IO_BASE + 0x49200) +#define IPU7_SRST_PORT_ARB 0x0 +#define IPU7_SRST_MGC 0x4 +#define IPU7_SRST_WIDTH_CONV 0x8 +#define IPU7_SRST_CSI_IRQ 0xc +#define IPU7_SRST_CSI_LEGACY_IRQ 0x10 +#define IPU7_CLK_EN_TXCLKESC 0x14 +#define IPU7_CLK_DIV_FACTOR_IS_CLK 0x18 +#define IPU7_CLK_DIV_FACTOR_APB_CLK 0x1c +#define IPU7_CSI_PORT_CLK_GATE 0x20 +#define IPU7_CSI_PORTAB_AGGREGATION 0x24 +#define IPU7_MGC_CLK_GATE 0x2c +#define IPU7_CG_CTRL_BITS 0x3c +#define IPU7_SPARE_RW 0xf8 +#define IPU7_SPARE_RO 0xfc + +#define IPU7_IS_IO_CSI2_MPF_PORT_BASE(i) (IPU7_IS_IO_BASE + 0x53000 + \ + 0x1000 * (i)) +#define IPU7_MPF_16_IRQ_CNTRL_STATUS 0x238 +#define IPU7_MPF_16_IRQ_CNTRL_CLEAR 0x23c +#define IPU7_MPF_16_IRQ_CNTRL_ENABLE 0x240 + +/* software config the phy */ +#define IPU7_IS_IO_CSI2_GPREGS_BASE (IPU7_IS_IO_BASE + 0x53400) +#define IPU8_IS_IO_CSI2_GPREGS_BASE (IPU7_IS_IO_BASE + 0x40e00) +#define IPU7_CSI_ADAPT_LAYER_SRST 0x0 +#define IPU7_MPF_SRST_RST 0x4 +#define IPU7_CSI_ERR_IRQ_CTRL_SRST 0x8 +#define IPU7_CSI_SYNC_RC_SRST 0xc +#define IPU7_CSI_CG_CTRL_BITS 0x10 +#define IPU7_SOC_CSI2HOST_SELECT 0x14 +#define IPU7_PHY_RESET 0x18 +#define IPU7_PHY_SHUTDOWN 0x1c +#define IPU7_PHY_MODE 0x20 +#define IPU7_PHY_READY 0x24 +#define IPU7_PHY_CLK_LANE_FORCE_CONTROL 0x28 +#define IPU7_PHY_CLK_LANE_CONTROL 0x2c +#define IPU7_PHY_CLK_LANE_STATUS 0x30 +#define IPU7_PHY_LANE_RX_ESC_REQ 0x34 +#define IPU7_PHY_LANE_RX_ESC_DATA 0x38 +#define IPU7_PHY_LANE_TURNDISABLE 0x3c +#define IPU7_PHY_LANE_DIRECTION 0x40 +#define IPU7_PHY_LANE_FORCE_CONTROL 0x44 +#define IPU7_PHY_LANE_CONTROL_EN 0x48 +#define IPU7_PHY_LANE_CONTROL_DATAWIDTH 0x4c +#define IPU7_PHY_LANE_STATUS 0x50 +#define IPU7_PHY_LANE_ERR 0x54 +#define IPU7_PHY_LANE_RXALP 0x58 +#define IPU7_PHY_LANE_RXALP_NIBBLE 0x5c +#define IPU7_PHY_PARITY_ERROR 0x60 +#define IPU7_PHY_DEBUG_REGS_CLK_GATE_EN 0x64 +#define IPU7_SPARE_RW 0xf8 +#define IPU7_SPARE_RO 0xfc + +/* software not touch */ +#define IPU7_PORT_ARB_BASE (IPU7_IS_IO_BASE + 0x4e000) +#define IPU7_PORT_ARB_IRQ_CTL_STATUS 0x4 +#define IPU7_PORT_ARB_IRQ_CTL_CLEAR 0x8 +#define IPU7_PORT_ARB_IRQ_CTL_ENABLE 0xc + +#define IPU7_MGC_PPC 4U +#define IPU7_MGC_DTYPE_RAW(i) (((i) - 8) / 2) +#define IPU7_IS_IO_MGC_BASE (IPU7_IS_IO_BASE + 0x48000) +#define IPU7_MGC_KICK 0x0 +#define IPU7_MGC_ASYNC_STOP 0x4 +#define IPU7_MGC_PORT_OFFSET 0x100 +#define IPU7_MGC_CSI_PORT_MAP(i) (0x8 + (i) * 0x4) +#define IPU7_MGC_MG_PORT(i) (IPU7_IS_IO_MGC_BASE + \ + (i) * IPU7_MGC_PORT_OFFSET) +/* per mgc instance */ +#define IPU7_MGC_MG_CSI_ADAPT_LAYER_TYPE 0x28 +#define IPU7_MGC_MG_MODE 0x2c +#define IPU7_MGC_MG_INIT_COUNTER 0x30 +#define IPU7_MGC_MG_MIPI_VC 0x34 +#define IPU7_MGC_MG_MIPI_DTYPES 0x38 +#define IPU7_MGC_MG_MULTI_DTYPES_MODE 0x3c +#define IPU7_MGC_MG_NOF_FRAMES 0x40 +#define IPU7_MGC_MG_FRAME_DIM 0x44 +#define IPU7_MGC_MG_HBLANK 0x48 +#define IPU7_MGC_MG_VBLANK 0x4c +#define IPU7_MGC_MG_TPG_MODE 0x50 +#define IPU7_MGC_MG_TPG_R0 0x54 +#define IPU7_MGC_MG_TPG_G0 0x58 +#define IPU7_MGC_MG_TPG_B0 0x5c +#define IPU7_MGC_MG_TPG_R1 0x60 +#define IPU7_MGC_MG_TPG_G1 0x64 +#define IPU7_MGC_MG_TPG_B1 0x68 +#define IPU7_MGC_MG_TPG_FACTORS 0x6c +#define IPU7_MGC_MG_TPG_MASKS 0x70 +#define IPU7_MGC_MG_TPG_XY_MASK 0x74 +#define IPU7_MGC_MG_TPG_TILE_DIM 0x78 +#define IPU7_MGC_MG_PRBS_LFSR_INIT_0 0x7c +#define IPU7_MGC_MG_PRBS_LFSR_INIT_1 0x80 +#define IPU7_MGC_MG_SYNC_STOP_POINT 0x84 +#define IPU7_MGC_MG_SYNC_STOP_POINT_LOC 0x88 +#define IPU7_MGC_MG_ERR_INJECT 0x8c +#define IPU7_MGC_MG_ERR_LOCATION 0x90 +#define IPU7_MGC_MG_DTO_SPEED_CTRL_EN 0x94 +#define IPU7_MGC_MG_DTO_SPEED_CTRL_INCR_VAL 0x98 +#define IPU7_MGC_MG_HOR_LOC_STTS 0x9c +#define IPU7_MGC_MG_VER_LOC_STTS 0xa0 +#define IPU7_MGC_MG_FRAME_NUM_STTS 0xa4 +#define IPU7_MGC_MG_BUSY_STTS 0xa8 +#define IPU7_MGC_MG_STOPPED_STTS 0xac +/* tile width and height in pixels for Chess board and Color palette */ +#define IPU7_MGC_TPG_TILE_WIDTH 64U +#define IPU7_MGC_TPG_TILE_HEIGHT 64U + +#define IPU7_CSI_PORT_A_ADDR_OFFSET 0x0 +#define IPU7_CSI_PORT_B_ADDR_OFFSET 0x0 +#define IPU7_CSI_PORT_C_ADDR_OFFSET 0x0 +#define IPU7_CSI_PORT_D_ADDR_OFFSET 0x0 + +/* + * 0 - CSI RX Port 0 interrupt; + * 1 - MPF Port 0 interrupt; + * 2 - CSI RX Port 1 interrupt; + * 3 - MPF Port 1 interrupt; + * 4 - CSI RX Port 2 interrupt; + * 5 - MPF Port 2 interrupt; + * 6 - CSI RX Port 3 interrupt; + * 7 - MPF Port 3 interrupt; + * 8 - Port ARB FIFO 0 overflow; + * 9 - Port ARB FIFO 1 overflow; + * 10 - Port ARB FIFO 2 overflow; + * 11 - Port ARB FIFO 3 overflow; + * 12 - isys_cfgnoc_err_probe_intl; + * 13-15 - reserved + */ +#define IPU7_CSI_IS_IO_IRQ_MASK 0xffff + +/* Adapter layer irq */ +#define IPU7_CSI_ADPL_IRQ_MASK 0xffff + +/* sw irq from legacy irq control + * legacy irq status + * IPU7 + * 0 - CSI Port 0 error interrupt + * 1 - CSI Port 0 sync interrupt + * 2 - CSI Port 1 error interrupt + * 3 - CSI Port 1 sync interrupt + * 4 - CSI Port 2 error interrupt + * 5 - CSI Port 2 sync interrupt + * 6 - CSI Port 3 error interrupt + * 7 - CSI Port 3 sync interrupt + * IPU7P5 + * 0 - CSI Port 0 error interrupt + * 1 - CSI Port 0 fs interrupt + * 2 - CSI Port 0 fe interrupt + * 3 - CSI Port 1 error interrupt + * 4 - CSI Port 1 fs interrupt + * 5 - CSI Port 1 fe interrupt + * 6 - CSI Port 2 error interrupt + * 7 - CSI Port 2 fs interrupt + * 8 - CSI Port 2 fe interrupt + */ +#define IPU7_CSI_RX_LEGACY_IRQ_MASK 0x1ff + +/* legacy error status per port + * 0 - Error handler FIFO full; + * 1 - Reserved Short Packet encoding detected; + * 2 - Reserved Long Packet encoding detected; + * 3 - Received packet is too short (fewer data words than specified in header); + * 4 - Received packet is too long (more data words than specified in header); + * 5 - Short packet discarded due to errors; + * 6 - Long packet discarded due to errors; + * 7 - CSI Combo Rx interrupt; + * 8 - IDI CDC FIFO overflow; remaining bits are reserved and tied to 0; + */ +#define IPU7_CSI_RX_ERROR_IRQ_MASK 0xfff + +/* + * 0 - VC0 frame start received + * 1 - VC0 frame end received + * 2 - VC1 frame start received + * 3 - VC1 frame end received + * 4 - VC2 frame start received + * 5 - VC2 frame end received + * 6 - VC3 frame start received + * 7 - VC3 frame end received + * 8 - VC4 frame start received + * 9 - VC4 frame end received + * 10 - VC5 frame start received + * 11 - VC5 frame end received + * 12 - VC6 frame start received + * 13 - VC6 frame end received + * 14 - VC7 frame start received + * 15 - VC7 frame end received + * 16 - VC8 frame start received + * 17 - VC8 frame end received + * 18 - VC9 frame start received + * 19 - VC9 frame end received + * 20 - VC10 frame start received + * 21 - VC10 frame end received + * 22 - VC11 frame start received + * 23 - VC11 frame end received + * 24 - VC12 frame start received + * 25 - VC12 frame end received + * 26 - VC13 frame start received + * 27 - VC13 frame end received + * 28 - VC14 frame start received + * 29 - VC14 frame end received + * 30 - VC15 frame start received + * 31 - VC15 frame end received + */ + +#define IPU7_CSI_RX_SYNC_IRQ_MASK 0x0 +#define IPU7P5_CSI_RX_SYNC_FE_IRQ_MASK 0x0 + +#define IPU7_CSI_RX_NUM_ERRORS_IN_IRQ 12U +#define IPU7_CSI_RX_NUM_SYNC_IN_IRQ 32U + +enum IPU7_MGC_CSI_ADPL_TYPE { + IPU7_MGC_MAPPED_2_LANES = 0, + IPU7_MGC_MAPPED_4_LANES = 1, +}; + +enum IPU7_CSI2HOST_SELECTION { + IPU7_CSI2HOST_SEL_SOC = 0, + IPU7_CSI2HOST_SEL_CSI2HOST = 1, +}; + +#define IPU7_CSI_LEGACY_IRQ_MASK(port) (0x3 << ((port) * 2)) +#define IPU7P5_CSI_LEGACY_IRQ_MASK(port) (0x7 << ((port) * 3)) + +#define IPU7_ISYS_LEGACY_IRQ_CSI2(port) (0x3 << (port)) +#define IPU7P5_ISYS_LEGACY_IRQ_CSI2(port) (0x7 << (port)) + +/* ---------------------------------------------------------------- */ +#define IPU7_CSI_REG_BASE 0x220000 +#define IPU7_CSI_REG_BASE_PORT(id) ((id) * 0x1000) + +/* CSI Port General Purpose Registers */ +#define IPU7_CSI_REG_PORT_GPREG_SRST 0x0 +#define IPU7_CSI_REG_PORT_GPREG_CSI2_SLV_REG_SRST 0x4 +#define IPU7_CSI_REG_PORT_GPREG_CSI2_PORT_CONTROL 0x8 + +#define IPU7_CSI_RX_SYNC_FS_VC 0x55555555 +#define IPU7_CSI_RX_SYNC_FE_VC 0xaaaaaaaa +#define IPU7P5_CSI_RX_SYNC_FS_VC 0xffff +#define IPU7P5_CSI_RX_SYNC_FE_VC 0xffff + +#endif /* IPU7_ISYS_CSI2_REG_H */ diff --git a/drivers/media/pci/intel/ipu6/ipu7-mmu-hw.c b/drivers/media/pci/intel/ipu6/ipu7-mmu-hw.c new file mode 100644 index 000000000000..c9b015e89ee6 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu7-mmu-hw.c @@ -0,0 +1,858 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2026 Intel Corporation + */ + +#include <linux/types.h> +#include <linux/iopoll.h> + +#include "ipu6.h" +#include "ipu6-dma.h" +#include "ipu6-mmu.h" + +static struct ipu7_mmu_hw ipu7_isys_mmu_hwdata[] = { + { + .offset = IPU7_IS_MMU_FW_RD_OFFSET, + .zlx_offset = IPU7_IS_ZLX_UC_RD_OFFSET, + .uao_offset = IPU7_IS_UAO_UC_RD_OFFSET, + .info_bits = 0x20006701, + .refill = 0x00002726, + .collapse_en_bitmap = 0x0, + .l1_block = IPU7_IS_MMU_FW_RD_L1_BLOCKNR_REG, + .l2_block = IPU7_IS_MMU_FW_RD_L2_BLOCKNR_REG, + .nr_l1streams = IPU7_IS_MMU_FW_RD_STREAM_NUM, + .nr_l2streams = IPU7_IS_MMU_FW_RD_STREAM_NUM, + .l1_block_sz = { 0x0, 0x8, 0xa }, + .l2_block_sz = { 0x0, 0x2, 0x4 }, + .zlx_nr = IPU7_IS_ZLX_UC_RD_NUM, + .zlx_axi_pool = { 0x00000f30 }, + .zlx_en = { 0, 0, 0, 0 }, + .zlx_conf = { 0x0, 0x0, 0x0, 0x0 }, + .uao_p_num = IPU7_IS_UAO_UC_RD_PLANENUM, + .uao_p2tlb = { 0x61, 0x64, 0x65 }, + }, + { + .offset = IPU7_IS_MMU_FW_WR_OFFSET, + .zlx_offset = IPU7_IS_ZLX_UC_WR_OFFSET, + .uao_offset = IPU7_IS_UAO_UC_WR_OFFSET, + .info_bits = 0x20006801, + .refill = 0x00002524, + .collapse_en_bitmap = 0x0, + .l1_block = IPU7_IS_MMU_FW_WR_L1_BLOCKNR_REG, + .l2_block = IPU7_IS_MMU_FW_WR_L2_BLOCKNR_REG, + .nr_l1streams = IPU7_IS_MMU_FW_WR_STREAM_NUM, + .nr_l2streams = IPU7_IS_MMU_FW_WR_STREAM_NUM, + .l1_block_sz = { 0x0, 0x8, 0xa }, + .l2_block_sz = { 0x0, 0x2, 0x4 }, + .zlx_nr = IPU7_IS_ZLX_UC_WR_NUM, + .zlx_axi_pool = { 0x00000f20 }, + .zlx_en = { 0, 1, 1, 0 }, + .zlx_conf = { 0x0, 0x00010101, 0x00010101 }, + .uao_p_num = IPU7_IS_UAO_UC_WR_PLANENUM, + .uao_p2tlb = { 0x61, 0x62, 0x63 }, + }, + { + .offset = IPU7_IS_MMU_M0_OFFSET, + .zlx_offset = IPU7_IS_ZLX_M0_OFFSET, + .uao_offset = IPU7_IS_UAO_M0_WR_OFFSET, + .info_bits = 0x20006601, + .refill = 0x00002120, + .collapse_en_bitmap = 0x0, + .l1_block = IPU7_IS_MMU_M0_L1_BLOCKNR_REG, + .l2_block = IPU7_IS_MMU_M0_L2_BLOCKNR_REG, + .nr_l1streams = IPU7_IS_MMU_M0_STREAM_NUM, + .nr_l2streams = IPU7_IS_MMU_M0_STREAM_NUM, + .l1_block_sz = { 0x0, 0x3, 0x6, 0x8, 0xa, 0xc, 0xe, 0x10 }, + .l2_block_sz = { 0x0, 0x2, 0x4, 0x6, 0x8, 0xa, 0xc, 0xe }, + .zlx_nr = IPU7_IS_ZLX_M0_NUM, + .zlx_axi_pool = { 0x00000f10 }, + .zlx_en = { 1, 1, 1, 1, 1, 1, 1, 1 }, + .zlx_conf = { + 0x00010103, + 0x00010103, + 0x00010101, + 0x00010101, + 0x00010101, + 0x00010101, + 0x00010101, + 0x00010101, + }, + .uao_p_num = IPU7_IS_UAO_M0_WR_PLANENUM, + .uao_p2tlb = { + 0x00000049, + 0x0000004a, + 0x0000004b, + 0x0000004c, + 0x0000004d, + 0x0000004e, + 0x0000004f, + 0x00000050, + }, + }, + { + .offset = IPU7_IS_MMU_M1_OFFSET, + .zlx_offset = IPU7_IS_ZLX_M1_OFFSET, + .uao_offset = IPU7_IS_UAO_M1_WR_OFFSET, + .info_bits = 0x20006901, + .refill = 0x00002322, + .collapse_en_bitmap = 0x0, + .l1_block = IPU7_IS_MMU_M1_L1_BLOCKNR_REG, + .l2_block = IPU7_IS_MMU_M1_L2_BLOCKNR_REG, + .nr_l1streams = IPU7_IS_MMU_M1_STREAM_NUM, + .nr_l2streams = IPU7_IS_MMU_M1_STREAM_NUM, + .l1_block_sz = { + 0x0, 0x3, 0x6, 0x9, 0xc, + 0xe, 0x10, 0x12, 0x14, 0x16, + 0x18, 0x1a, 0x1c, 0x1e, 0x20, 0x22, + }, + .l2_block_sz = { + 0x0, 0x2, 0x4, 0x6, 0x8, + 0xa, 0xc, 0xe, 0x10, 0x12, + 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e, + }, + .zlx_nr = IPU7_IS_ZLX_M1_NUM, + .zlx_axi_pool = { 0x00000f20 }, + .zlx_en = { 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + }, + .zlx_conf = { + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010101, + 0x00010101, + 0x00010101, + 0x00010101, + 0x00010101, + 0x00010101, + 0x00010101, + 0x00010101, + }, + .uao_p_num = IPU7_IS_UAO_M1_WR_PLANENUM, + .uao_p2tlb = { + 0x00000051, + 0x00000052, + 0x00000053, + 0x00000054, + 0x00000055, + 0x00000056, + 0x00000057, + 0x00000058, + 0x00000059, + 0x0000005a, + 0x0000005b, + 0x0000005c, + 0x0000005d, + 0x0000005e, + 0x0000005f, + 0x00000060, + }, + }, +}; + +static struct ipu7_mmu_hw ipu7_psys_mmu_hwdata[] = { + { + .name = "PS_FW_RD", + .offset = IPU7_PS_MMU_FW_RD_OFFSET, + .zlx_offset = IPU7_PS_ZLX_FW_RD_OFFSET, + .uao_offset = IPU7_PS_UAO_FW_RD_OFFSET, + .info_bits = 0x20004801, + .refill = 0x00002726, + .collapse_en_bitmap = 0x0, + .l1_block = IPU7_PS_MMU_FW_RD_L1_BLOCKNR_REG, + .l2_block = IPU7_PS_MMU_FW_RD_L2_BLOCKNR_REG, + .nr_l1streams = IPU7_PS_MMU_FW_RD_STREAM_NUM, + .nr_l2streams = IPU7_PS_MMU_FW_RD_STREAM_NUM, + .l1_block_sz = { + 0, 0x8, 0xa, 0xc, 0xd, + 0xf, 0x11, 0x12, 0x13, 0x14, + 0x16, 0x18, 0x19, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + }, + .l2_block_sz = { + 0x0, 0x2, 0x4, 0x6, 0x8, + 0xa, 0xc, 0xe, 0x10, 0x12, + 0x14, 0x16, 0x18, 0x1a, 0x1c, + 0x1e, 0x20, 0x22, 0x24, 0x26, + }, + .zlx_nr = IPU7_PS_ZLX_FW_RD_NUM, + .zlx_axi_pool = { 0x00000f30 }, + .zlx_en = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }, + .zlx_conf = { 0x0 }, + .uao_p_num = IPU7_PS_UAO_FW_RD_PLANENUM, + .uao_p2tlb = { + 0x00000036, + 0x0000003d, + 0x0000003e, + 0x00000039, + 0x0000003f, + 0x00000040, + 0x00000041, + 0x0000003a, + 0x0000003b, + 0x00000042, + 0x00000043, + 0x00000044, + 0x0000003c, + }, + }, + { + .offset = IPU7_PS_MMU_FW_WR_OFFSET, + .zlx_offset = IPU7_PS_ZLX_FW_WR_OFFSET, + .uao_offset = IPU7_PS_UAO_FW_WR_OFFSET, + .info_bits = 0x20004601, + .refill = 0x00002322, + .collapse_en_bitmap = 0x0, + .l1_block = IPU7_PS_MMU_FW_WR_L1_BLOCKNR_REG, + .l2_block = IPU7_PS_MMU_FW_WR_L2_BLOCKNR_REG, + .nr_l1streams = IPU7_PS_MMU_FW_WR_STREAM_NUM, + .nr_l2streams = IPU7_PS_MMU_FW_WR_STREAM_NUM, + .l1_block_sz = { + 0, 0x8, 0xa, 0xc, 0xd, + 0xe, 0xf, 0x10, 0x10, 0x10, + }, + .l2_block_sz = { + 0x0, 0x2, 0x4, 0x6, 0x8, + 0xa, 0xc, 0xe, 0x10, 0x12, + }, + .zlx_nr = IPU7_PS_ZLX_FW_WR_NUM, + .zlx_axi_pool = { 0x00000f20 }, + .zlx_en = { 0, 1, 1, 0, 0, 0, 0, 0, 0, 0 }, + .zlx_conf = { 0x0, 0x00010101, 0x00010101 }, + .uao_p_num = IPU7_PS_UAO_FW_WR_PLANENUM, + .uao_p2tlb = { 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c }, + }, + { + .offset = IPU7_PS_MMU_SRT_RD_OFFSET, + .zlx_offset = IPU7_PS_ZLX_DATA_RD_OFFSET, + .uao_offset = IPU7_PS_UAO_SRT_RD_OFFSET, + .info_bits = 0x20004701, + .refill = 0x00002120, + .collapse_en_bitmap = 0x0, + .l1_block = IPU7_PS_MMU_SRT_RD_L1_BLOCKNR_REG, + .l2_block = IPU7_PS_MMU_SRT_RD_L2_BLOCKNR_REG, + .nr_l1streams = IPU7_PS_MMU_SRT_RD_STREAM_NUM, + .nr_l2streams = IPU7_PS_MMU_SRT_RD_STREAM_NUM, + .l1_block_sz = { + 0x0, 0x4, 0x6, 0x8, 0xb, + 0xd, 0xf, 0x11, 0x13, 0x15, + 0x17, 0x23, 0x2b, 0x37, 0x3f, + 0x41, 0x43, 0x44, 0x45, 0x46, + 0x47, 0x48, 0x49, 0x4a, 0x4b, + 0x4c, 0x4d, 0x4e, 0x4f, 0x50, + 0x51, 0x52, 0x53, 0x55, 0x57, + 0x59, 0x5b, 0x5d, 0x5f, 0x61, + }, + .l2_block_sz = { + 0x0, 0x2, 0x4, 0x6, 0x8, + 0xa, 0xc, 0xe, 0x10, 0x12, + 0x14, 0x16, 0x18, 0x1a, 0x1c, + 0x1e, 0x20, 0x22, 0x24, 0x26, + 0x28, 0x2a, 0x2c, 0x2e, 0x30, + 0x32, 0x34, 0x36, 0x38, 0x3a, + 0x3c, 0x3e, 0x40, 0x42, 0x44, + 0x46, 0x48, 0x4a, 0x4c, 0x4e, + }, + .zlx_nr = IPU7_PS_ZLX_DATA_RD_NUM, + .zlx_axi_pool = { 0x00000f30 }, + .zlx_en = { + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }, + .zlx_conf = { + 0x00030303, + 0x00010101, + 0x00010101, + 0x00030202, + 0x00010101, + 0x00010101, + 0x00010101, + 0x00030800, + 0x00030500, + 0x00020101, + 0x00042000, + 0x00031000, + 0x00042000, + 0x00031000, + 0x00020400, + 0x00010101, + }, + .uao_p_num = IPU7_PS_UAO_SRT_RD_PLANENUM, + .uao_p2tlb = { + 0x00000022, + 0x00000023, + 0x00000024, + 0x00000025, + 0x00000026, + 0x00000027, + 0x00000028, + 0x00000029, + 0x0000002a, + 0x0000002b, + 0x0000002c, + 0x0000002d, + 0x0000002e, + 0x0000002f, + 0x00000030, + 0x00000031, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0000001e, + 0x0000001f, + 0x00000020, + 0x00000021, + 0x00000032, + 0x00000033, + 0x00000034, + 0x00000035, + }, + }, + { + .offset = IPU7_PS_MMU_SRT_WR_OFFSET, + .zlx_offset = IPU7_PS_ZLX_DATA_WR_OFFSET, + .uao_offset = IPU7_PS_UAO_SRT_WR_OFFSET, + .info_bits = 0x20004501, + .refill = 0x00002120, + .collapse_en_bitmap = 0x0, + .l1_block = IPU7_PS_MMU_SRT_WR_L1_BLOCKNR_REG, + .l2_block = IPU7_PS_MMU_SRT_WR_L2_BLOCKNR_REG, + .nr_l1streams = IPU7_PS_MMU_SRT_WR_STREAM_NUM, + .nr_l2streams = IPU7_PS_MMU_SRT_WR_STREAM_NUM, + .l1_block_sz = { + 0x0, 0x2, 0x6, 0xa, 0xc, + 0xe, 0x10, 0x12, 0x14, 0x16, + 0x18, 0x1a, 0x1c, 0x1e, 0x20, + 0x22, 0x24, 0x26, 0x32, 0x3a, + 0x3c, 0x3e, 0x4a, 0x52, 0x58, + 0x64, 0x6c, 0x72, 0x7e, 0x86, + 0x8c, 0x8d, 0x8e, 0x8f, 0x90, + 0x91, 0x92, 0x94, 0x96, 0x98, + }, + .l2_block_sz = { + 0x0, 0x2, 0x4, 0x6, 0x8, + 0xa, 0xc, 0xe, 0x10, 0x12, + 0x14, 0x16, 0x18, 0x1a, 0x1c, + 0x1e, 0x20, 0x22, 0x24, 0x26, + 0x28, 0x2a, 0x2c, 0x2e, 0x30, + 0x32, 0x34, 0x36, 0x38, 0x3a, + 0x3c, 0x3e, 0x40, 0x42, 0x44, + 0x46, 0x48, 0x4a, 0x4c, 0x4e, + }, + .zlx_nr = IPU7_PS_ZLX_DATA_WR_NUM, + .zlx_axi_pool = { 0x00000f50 }, + .zlx_en = { + 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 0, 0, + }, + .zlx_conf = { + 0x00010102, + 0x00030103, + 0x00030103, + 0x00010101, + 0x00010101, + 0x00030101, + 0x00010101, + 0x38010101, + 0x0, + 0x0, + 0x38010101, + 0x38010101, + 0x38010101, + 0x38010101, + 0x38010101, + 0x38010101, + 0x00010101, + 0x00042000, + 0x00031000, + 0x00010101, + 0x00010101, + 0x00042000, + 0x00031000, + 0x00031000, + 0x00042000, + 0x00031000, + 0x00031000, + 0x00042000, + 0x00031000, + 0x00031000, + 0x0, + 0x0, + }, + .uao_p_num = IPU7_PS_UAO_SRT_WR_PLANENUM, + .uao_p2tlb = { + 0x00000000, + 0x00000001, + 0x00000002, + 0x00000003, + 0x00000004, + 0x00000005, + 0x00000006, + 0x00000007, + 0x00000008, + 0x00000009, + 0x0000000a, + 0x0000000b, + 0x0000000c, + 0x0000000d, + 0x0000000e, + 0x0000000f, + 0x00000010, + 0x00000011, + 0x00000012, + 0x00000013, + 0x00000014, + 0x00000015, + 0x00000016, + 0x00000017, + 0x00000018, + 0x00000019, + 0x0000001a, + 0x0000001b, + 0x0000001c, + 0x0000001d, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0000001e, + 0x0000001f, + 0x00000020, + 0x00000021, + }, + }, +}; + +static struct ipu7_mmu_hw ipu7p5_isys_mmu_hwdata[] = { + { + .name = "IS_FW_RD", + .offset = IPU7P5_IS_MMU_FW_RD_OFFSET, + .zlx_offset = IPU7P5_IS_ZLX_UC_RD_OFFSET, + .uao_offset = IPU7P5_IS_UAO_UC_RD_OFFSET, + .info_bits = 0x20005101, + .refill = 0x00002726, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU7P5_IS_MMU_FW_RD_L1_BLOCKNR_REG, + .l2_block = IPU7P5_IS_MMU_FW_RD_L2_BLOCKNR_REG, + .nr_l1streams = IPU7P5_IS_MMU_FW_RD_STREAM_NUM, + .nr_l2streams = IPU7P5_IS_MMU_FW_RD_STREAM_NUM, + .l1_block_sz = { 0x0, 0x8, 0xa }, + .l2_block_sz = { 0x0, 0x2, 0x4 }, + .zlx_nr = IPU7P5_IS_ZLX_UC_RD_NUM, + .zlx_axi_pool = { 0x00000f30 }, + .zlx_en = { 0, 1, 0, 0 }, + .zlx_conf = { 0x0 }, + .uao_p_num = IPU7P5_IS_UAO_UC_RD_PLANENUM, + .uao_p2tlb = { 0x49, 0x4c, 0x4d, 0x0 }, + }, + { + .name = "IS_FW_WR", + .offset = IPU7P5_IS_MMU_FW_WR_OFFSET, + .zlx_offset = IPU7P5_IS_ZLX_UC_WR_OFFSET, + .uao_offset = IPU7P5_IS_UAO_UC_WR_OFFSET, + .info_bits = 0x20005001, + .refill = 0x00002524, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU7P5_IS_MMU_FW_WR_L1_BLOCKNR_REG, + .l2_block = IPU7P5_IS_MMU_FW_WR_L2_BLOCKNR_REG, + .nr_l1streams = IPU7P5_IS_MMU_FW_WR_STREAM_NUM, + .nr_l2streams = IPU7P5_IS_MMU_FW_WR_STREAM_NUM, + .l1_block_sz = { 0x0, 0x8, 0xa }, + .l2_block_sz = { 0x0, 0x2, 0x4 }, + .zlx_nr = IPU7P5_IS_ZLX_UC_WR_NUM, + .zlx_axi_pool = { 0x00000f20 }, + .zlx_en = { 0, 1, 1, 0 }, + .zlx_conf = { 0x0, 0x00010101, 0x00010101, 0x0 }, + .uao_p_num = IPU7P5_IS_UAO_UC_WR_PLANENUM, + .uao_p2tlb = { 0x49, 0x4a, 0x4b, 0x0 }, + }, + { + .name = "IS_DATA_WR_ISOC", + .offset = IPU7P5_IS_MMU_M0_OFFSET, + .zlx_offset = IPU7P5_IS_ZLX_M0_OFFSET, + .uao_offset = IPU7P5_IS_UAO_M0_WR_OFFSET, + .info_bits = 0x20004e01, + .refill = 0x00002120, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU7P5_IS_MMU_M0_L1_BLOCKNR_REG, + .l2_block = IPU7P5_IS_MMU_M0_L2_BLOCKNR_REG, + .nr_l1streams = IPU7P5_IS_MMU_M0_STREAM_NUM, + .nr_l2streams = IPU7P5_IS_MMU_M0_STREAM_NUM, + .l1_block_sz = { 0x0, 0x2, 0x4, 0x6, 0x8, 0xa, 0xc, 0xe, 0x10, + 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x01e }, + .l2_block_sz = { 0x0, 0x2, 0x4, 0x6, 0x8, 0xa, 0xc, 0xe, 0x10, + 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e }, + .zlx_nr = IPU7P5_IS_ZLX_M0_NUM, + .zlx_axi_pool = { 0x00000f10 }, + .zlx_en = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, + .zlx_conf = { 0x00010103, 0x00010103, 0x00010103, 0x00010103, + 0x00010103, 0x00010103, 0x00010103, 0x00010103, + 0x00010103, 0x00010103, 0x00010103, 0x00010103, + 0x00010103, 0x00010103, 0x00010103, 0x00010103 }, + .uao_p_num = IPU7P5_IS_UAO_M0_WR_PLANENUM, + .uao_p2tlb = { 0x00000041, 0x00000042, 0x00000043, 0x00000044, + 0x00000041, 0x00000042, 0x00000043, 0x00000044, + 0x00000041, 0x00000042, 0x00000043, 0x00000044, + 0x00000041, 0x00000042, 0x00000043, 0x00000044 }, + }, + { + .name = "IS_DATA_WR_SNOOP", + .offset = IPU7P5_IS_MMU_M1_OFFSET, + .zlx_offset = IPU7P5_IS_ZLX_M1_OFFSET, + .uao_offset = IPU7P5_IS_UAO_M1_WR_OFFSET, + .info_bits = 0x20004f01, + .refill = 0x00002322, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU7P5_IS_MMU_M1_L1_BLOCKNR_REG, + .l2_block = IPU7P5_IS_MMU_M1_L2_BLOCKNR_REG, + .nr_l1streams = IPU7P5_IS_MMU_M1_STREAM_NUM, + .nr_l2streams = IPU7P5_IS_MMU_M1_STREAM_NUM, + .l1_block_sz = { 0x0, 0x2, 0x4, 0x6, 0x8, 0xa, 0xc, 0xe, 0x10, + 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e }, + .l2_block_sz = { 0x0, 0x2, 0x4, 0x6, 0x8, 0xa, 0xc, 0xe, 0x10, + 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e }, + .zlx_nr = IPU7P5_IS_ZLX_M1_NUM, + .zlx_axi_pool = { 0x00000f20 }, + .zlx_en = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, + .zlx_conf = { 0x00010103, 0x00010103, 0x00010103, 0x00010103, + 0x00010103, 0x00010103, 0x00010103, 0x00010103, + 0x00010103, 0x00010103, 0x00010103, 0x00010103, + 0x00010103, 0x00010103, 0x00010103, 0x00010103 }, + .uao_p_num = IPU7P5_IS_UAO_M1_WR_PLANENUM, + .uao_p2tlb = { 0x00000045, 0x00000046, 0x00000047, 0x00000048, + 0x00000045, 0x00000046, 0x00000047, 0x00000048, + 0x00000045, 0x00000046, 0x00000047, 0x00000048, + 0x00000045, 0x00000046, 0x00000047, 0x00000048 }, + }, +}; + +static struct ipu7_mmu_hw ipu7p5_psys_mmu_hwdata[] = { + { + .name = "PS_FW_RD", + .offset = IPU7P5_PS_MMU_FW_RD_OFFSET, + .zlx_offset = IPU7P5_PS_ZLX_FW_RD_OFFSET, + .uao_offset = IPU7P5_PS_UAO_FW_RD_OFFSET, + .info_bits = 0x20004001, + .refill = 0x00002726, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU7P5_PS_MMU_FW_RD_L1_BLOCKNR_REG, + .l2_block = IPU7P5_PS_MMU_FW_RD_L2_BLOCKNR_REG, + .nr_l1streams = IPU7P5_PS_MMU_FW_RD_STREAM_NUM, + .nr_l2streams = IPU7P5_PS_MMU_FW_RD_STREAM_NUM, + .l1_block_sz = { 0x00, 0x08, 0x0a, 0x0c, 0x0d, 0x0f, 0x11, + 0x12, 0x13, 0x14, 0x16, 0x18, 0x19, 0x1a, + 0x1a, 0x1a }, + .l2_block_sz = { 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, + 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, + 0x1c, 0x1e }, + .zlx_nr = IPU7P5_PS_ZLX_FW_RD_NUM, + .zlx_axi_pool = { 0x00000f30 }, + .zlx_en = { 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0 }, + .zlx_conf = { 0x00000000, 0x00010101, 0x00000000, 0x00000000, + 0x00010101, 0x00010101, 0x00000000, 0x00000000, + 0x00000000, 0x00010101, 0x00010101, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000 }, + .uao_p_num = IPU7P5_PS_UAO_FW_RD_PLANENUM, + .uao_p2tlb = { 0x2e, 0x35, 0x36, 0x31, 0x37, 0x38, 0x39, 0x32, + 0x33, 0x3a, 0x3b, 0x3c, 0x34, 0x00, 0x00, 0x00 }, + }, + { + .name = "PS_FW_WR", + .offset = IPU7P5_PS_MMU_FW_WR_OFFSET, + .zlx_offset = IPU7P5_PS_ZLX_FW_WR_OFFSET, + .uao_offset = IPU7P5_PS_UAO_FW_WR_OFFSET, + .info_bits = 0x20003e01, + .refill = 0x00002322, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU7P5_PS_MMU_FW_WR_L1_BLOCKNR_REG, + .l2_block = IPU7P5_PS_MMU_FW_WR_L2_BLOCKNR_REG, + .nr_l1streams = IPU7P5_PS_MMU_FW_WR_STREAM_NUM, + .nr_l2streams = IPU7P5_PS_MMU_FW_WR_STREAM_NUM, + .l1_block_sz = { 0x00, 0x08, 0x0a, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x10, 0x10 }, + .l2_block_sz = { 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, + 0x10, 0x12 }, + .zlx_nr = IPU7P5_PS_ZLX_FW_WR_NUM, + .zlx_axi_pool = { 0x00000f20 }, + .zlx_en = { 0, 1, 1, 0, 0, 0, 0, 0, 0, 0 }, + .zlx_conf = { 0x00000000, 0x00010101, 0x00010101, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000 }, + .uao_p_num = IPU7P5_PS_UAO_FW_WR_PLANENUM, + .uao_p2tlb = { 0x0000002e, 0x0000002f, 0x00000030, 0x00000031, + 0x00000032, 0x00000033, 0x00000034, 0x00000000, + 0x00000000, 0x00000000 }, + }, + { + .name = "PS_DATA_RD", + .offset = IPU7P5_PS_MMU_SRT_RD_OFFSET, + .zlx_offset = IPU7P5_PS_ZLX_DATA_RD_OFFSET, + .uao_offset = IPU7P5_PS_UAO_SRT_RD_OFFSET, + .info_bits = 0x20003f01, + .refill = 0x00002524, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU7P5_PS_MMU_SRT_RD_L1_BLOCKNR_REG, + .l2_block = IPU7P5_PS_MMU_SRT_RD_L2_BLOCKNR_REG, + .nr_l1streams = IPU7P5_PS_MMU_SRT_RD_STREAM_NUM, + .nr_l2streams = IPU7P5_PS_MMU_SRT_RD_STREAM_NUM, + .l1_block_sz = { 0x00, 0x04, 0x06, 0x08, 0x0b, 0x0d, 0x0f, 0x13, + 0x17, 0x19, 0x1b, 0x1d, 0x1f, 0x2b, 0x33, 0x3f, + 0x47, 0x49, 0x4b, 0x4c, 0x4d, 0x4e }, + .l2_block_sz = { 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, + 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e, + 0x20, 0x22, 0x24, 0x26, 0x28, 0x2a }, + .zlx_nr = IPU7P5_PS_ZLX_DATA_RD_NUM, + .zlx_axi_pool = { 0x00000f30 }, + .zlx_en = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 0, 0, 0, 0 }, + .zlx_conf = { 0x00030303, 0x00010101, 0x00010101, 0x00030202, + 0x00010101, 0x00010101, 0x00030303, 0x00030303, + 0x00010101, 0x00030800, 0x00030500, 0x00020101, + 0x00042000, 0x00031000, 0x00042000, 0x00031000, + 0x00020400, 0x00010101, 0x00000000, 0x00000000, + 0x00000000, 0x00000000 }, + .uao_p_num = IPU7P5_PS_UAO_SRT_RD_PLANENUM, + .uao_p2tlb = { 0x0000001c, 0x0000001d, 0x0000001e, 0x0000001f, + 0x00000020, 0x00000021, 0x00000022, 0x00000023, + 0x00000024, 0x00000025, 0x00000026, 0x00000027, + 0x00000028, 0x00000029, 0x0000002a, 0x0000002b, + 0x0000002c, 0x0000002d, 0x00000000, 0x00000000, + 0x00000000, 0x00000000 }, + }, + { + .name = "PS_DATA_WR", + .offset = IPU7P5_PS_MMU_SRT_WR_OFFSET, + .zlx_offset = IPU7P5_PS_ZLX_DATA_WR_OFFSET, + .uao_offset = IPU7P5_PS_UAO_SRT_WR_OFFSET, + .info_bits = 0x20003d01, + .refill = 0x00002120, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU7P5_PS_MMU_SRT_WR_L1_BLOCKNR_REG, + .l2_block = IPU7P5_PS_MMU_SRT_WR_L2_BLOCKNR_REG, + .nr_l1streams = IPU7P5_PS_MMU_SRT_WR_STREAM_NUM, + .nr_l2streams = IPU7P5_PS_MMU_SRT_WR_STREAM_NUM, + .l1_block_sz = { 0x00, 0x02, 0x06, 0x0a, 0x0c, 0x0e, 0x10, 0x12, + 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e, 0x20, 0x22, + 0x24, 0x28, 0x2a, 0x36, 0x3e, 0x40, 0x42, 0x4e, + 0x56, 0x5c, 0x68, 0x70, 0x76, 0x77, 0x78, 0x79 }, + .l2_block_sz = { 0x00, 0x02, 0x06, 0x0a, 0x0c, 0x0e, 0x10, 0x12, + 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e, 0x20, 0x22, + 0x24, 0x28, 0x2a, 0x36, 0x3e, 0x40, 0x42, 0x4e, + 0x56, 0x5c, 0x68, 0x70, 0x76, 0x77, 0x78, 0x79 }, + .zlx_nr = IPU7P5_PS_ZLX_DATA_WR_NUM, + .zlx_axi_pool = { 0x00000f50 }, + .zlx_en = { 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, + .zlx_conf = { 0x00010102, 0x00030103, 0x00030103, 0x00010101, + 0x00010101, 0x00030101, 0x00010101, 0x38010101, + 0x00000000, 0x00000000, 0x38010101, 0x38010101, + 0x38010101, 0x38010101, 0x38010101, 0x38010101, + 0x00030303, 0x00010101, 0x00042000, 0x00031000, + 0x00010101, 0x00010101, 0x00042000, 0x00031000, + 0x00031000, 0x00042000, 0x00031000, 0x00031000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000 }, + .uao_p_num = IPU7P5_PS_UAO_SRT_WR_PLANENUM, + .uao_p2tlb = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x00, 0x00, 0x00, 0x00 }, + }, +}; + +static const struct ipu7_mmu_hwdata ipu7_mmu_hwdata_lookup[IPU_SUBSYS_NUM] = { + [IPU_PSYS] = { + .hwdata = ipu7_psys_mmu_hwdata, + .nr_mmus = ARRAY_SIZE(ipu7_psys_mmu_hwdata), + }, + [IPU_ISYS] = { + .hwdata = ipu7_isys_mmu_hwdata, + .nr_mmus = ARRAY_SIZE(ipu7_isys_mmu_hwdata), + }, +}; + +static const struct ipu7_mmu_hwdata ipu7p5_mmu_hwdata_lookup[IPU_SUBSYS_NUM] = { + [IPU_PSYS] = { + .hwdata = ipu7p5_psys_mmu_hwdata, + .nr_mmus = ARRAY_SIZE(ipu7p5_psys_mmu_hwdata), + }, + [IPU_ISYS] = { + .hwdata = ipu7p5_isys_mmu_hwdata, + .nr_mmus = ARRAY_SIZE(ipu7p5_isys_mmu_hwdata), + }, +}; + +static void __ipu7_tlb_invalidate(struct ipu6_mmu *mmu) +{ + struct ipu7_mmu_hw *mmu_hw = mmu->ipu7_mmu_hw; + unsigned long flags; + unsigned int i; + int ret; + u32 val; + + spin_lock_irqsave(&mmu->ready_lock, flags); + if (!mmu->ready) { + spin_unlock_irqrestore(&mmu->ready_lock, flags); + return; + } + + for (i = 0; i < mmu->nr_mmus; i++) { + writel(0xffffffffU, mmu_hw[i].base + + IPU7_MMU_REG_INVALIDATE_0); + + /* Need check with HW, use l1streams or l2streams */ + if (mmu_hw[i].nr_l2streams > 32) + writel(0xffffffffU, mmu_hw[i].base + + IPU7_MMU_REG_INVALIDATE_1); + + /* + * The TLB invalidation is a "single cycle" (IOMMU clock cycles) + * When the actual MMIO write reaches the IPU TLB Invalidate + * register, wmb() will force the TLB invalidate out if the CPU + * attempts to update the IOMMU page table (or sooner). + */ + wmb(); + + /* wait invalidation done */ + ret = readl_poll_timeout_atomic(mmu_hw[i].base + + IPU7_MMU_REG_INVALIDATION_STATUS, + val, !(val & 0x1U), 500, + IPU7_MMU_TLB_INVALIDATE_TIMEOUT); + if (ret) + dev_err(mmu->dev, "MMU[%u] TLB invalidate failed\n", i); + } + + spin_unlock_irqrestore(&mmu->ready_lock, flags); +} + +static int __ipu7_mmu_hw_init(struct ipu6_mmu *mmu) +{ + struct ipu6_mmu_info *mmu_info; + struct ipu7_mmu_hw *mmu_hw = mmu->ipu7_mmu_hw; + unsigned int i, j; + + mmu_info = mmu->dmap->mmu_info; + for (i = 0; i < mmu->nr_mmus; i++) { + /* Write page table address per MMU */ + writel((phys_addr_t)mmu_info->l1_pt_dma, + mmu_hw[i].base + IPU7_MMU_REG_PAGE_TABLE_BASE_ADDR); + + /* Set info bits and axi_refill per MMU */ + writel(mmu_hw[i].info_bits, + mmu_hw[i].base + IPU7_MMU_REG_USER_INFO_BITS); + writel(mmu_hw[i].refill, mmu_hw[i].base + IPU7_MMU_REG_AXI_REFILL_IF_ID); + writel(mmu_hw[i].collapse_en_bitmap, + mmu_hw[i].base + IPU7_MMU_REG_COLLAPSE_ENABLE_BITMAP); + + if (mmu_hw[i].at_sp_arb_cfg) + writel(mmu_hw[i].at_sp_arb_cfg, + mmu_hw[i].base + IPU7_MMU_REG_AT_SP_ARB_CFG); + + /* Default irq configuration */ + writel(0x3ff, mmu_hw[i].base + IPU7_MMU_REG_IRQ_MASK); + writel(0x3ff, mmu_hw[i].base + IPU7_MMU_REG_IRQ_ENABLE); + + /* Configure MMU TLB stream configuration for L1/L2 */ + for (j = 0; j < mmu_hw[i].nr_l1streams; j++) { + writel(mmu_hw[i].l1_block_sz[j], mmu_hw[i].base + + mmu_hw[i].l1_block + 4U * j); + } + + for (j = 0; j < mmu_hw[i].nr_l2streams; j++) { + writel(mmu_hw[i].l2_block_sz[j], mmu_hw[i].base + + mmu_hw[i].l2_block + 4U * j); + } + + for (j = 0; j < mmu_hw[i].uao_p_num; j++) { + if (!mmu_hw[i].uao_p2tlb[j]) + continue; + writel(mmu_hw[i].uao_p2tlb[j], mmu_hw[i].uao_base + 4U * j); + } + } + + for (i = 0; i < mmu->nr_mmus; i++) { + for (j = 0; j < IPU7_ZLX_POOL_NUM; j++) { + if (!mmu_hw[i].zlx_axi_pool[j]) + continue; + writel(mmu_hw[i].zlx_axi_pool[j], + mmu_hw[i].zlx_base + IPU7_ZLX_REG_AXI_POOL + j * 0x4U); + } + + for (j = 0; j < mmu_hw[i].zlx_nr; j++) { + if (!mmu_hw[i].zlx_conf[j]) + continue; + + writel(mmu_hw[i].zlx_conf[j], + mmu_hw[i].zlx_base + IPU7_ZLX_REG_CONF + j * 0x8U); + } + + for (j = 0; j < mmu_hw[i].zlx_nr; j++) { + if (!mmu_hw[i].zlx_en[j]) + continue; + + writel(mmu_hw[i].zlx_en[j], + mmu_hw[i].zlx_base + IPU7_ZLX_REG_EN + j * 0x8U); + } + } + + return 0; +} + +static int __ipu7_mmu_init_hw_data(struct ipu6_mmu *mmu, struct device *dev, + void __iomem *base) +{ + struct ipu6_device *isp = pci_get_drvdata(to_pci_dev(dev)); + const struct ipu7_mmu_hwdata *lookup; + struct ipu7_mmu_hw *mmu_hw, *src; + unsigned int i, nr_mmus; + + if (mmu->mmid >= IPU_SUBSYS_NUM) + return -EINVAL; + + lookup = IS_IPU7P5(isp) ? ipu7p5_mmu_hwdata_lookup : + ipu7_mmu_hwdata_lookup; + + src = lookup[mmu->mmid].hwdata; + nr_mmus = lookup[mmu->mmid].nr_mmus; + + mmu_hw = devm_kcalloc(dev, nr_mmus, sizeof(*mmu_hw), GFP_KERNEL); + if (!mmu_hw) + return -ENOMEM; + + for (i = 0; i < nr_mmus; i++) { + if (src[i].nr_l1streams > IPU7_MMU_MAX_TLB_L1_STREAMS || + src[i].nr_l2streams > IPU7_MMU_MAX_TLB_L2_STREAMS) + return -EINVAL; + + mmu_hw[i] = src[i]; + mmu_hw[i].base = base + src[i].offset; + mmu_hw[i].zlx_base = base + src[i].zlx_offset; + mmu_hw[i].uao_base = base + src[i].uao_offset; + } + + mmu->nr_mmus = nr_mmus; + mmu->ipu7_mmu_hw = mmu_hw; + + return 0; +} + +const struct ipu6_mmu_hw_ops ipu7_mmu_ops = { + .init_hw_data = __ipu7_mmu_init_hw_data, + .hw_init = __ipu7_mmu_hw_init, + .tlb_invalidate = __ipu7_tlb_invalidate, +}; diff --git a/drivers/media/pci/intel/ipu6/ipu7-mmu-hw.h b/drivers/media/pci/intel/ipu6/ipu7-mmu-hw.h new file mode 100644 index 000000000000..ba31ed31b245 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu7-mmu-hw.h @@ -0,0 +1,254 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2026 Intel Corporation */ + +#ifndef IPU7_MMU_HW_H +#define IPU7_MMU_HW_H + +#define IPU7_MMU_REG_IRQ_MASK 0x2c +#define IPU7_MMU_REG_IRQ_ENABLE 0x30 +#define IPU7_MMU_REG_PAGE_TABLE_BASE_ADDR 0x08 +#define IPU7_MMU_REG_USER_INFO_BITS 0x0c +#define IPU7_MMU_REG_AXI_REFILL_IF_ID 0x10 +#define IPU7_MMU_REG_COLLAPSE_ENABLE_BITMAP 0x18 +#define IPU7_MMU_REG_AT_SP_ARB_CFG 0x20 + +#define IPU7_ZLX_REG_AXI_POOL 0x0 +#define IPU7_ZLX_REG_EN 0x20 +#define IPU7_ZLX_REG_CONF 0x24 +#define IPU7_ZLX_POOL_NUM 8U + +#define IPU7_MMU_MAX_TLB_L1_STREAMS 40U +#define IPU7_MMU_MAX_TLB_L2_STREAMS 40U +#define IPU7_UAO_PLANE_MAX_NUM 64U +#define IPU7_ZLX_MAX_NUM 32U + +#define IPU7_MMU_REG_INVALIDATE_0 0x00 +#define IPU7_MMU_REG_INVALIDATE_1 0x04 +#define IPU7_MMU_REG_INVALIDATION_STATUS 0x24 +#define IPU7_MMU_TLB_INVALIDATE_TIMEOUT 2000 + +#define IPU7_FW_CODE_REGION_SIZE 0x1000000 /* 16MB */ +#define IPU7_FW_CODE_REGION_START 0x4000000 /* 64MB */ +#define IPU7_FW_CODE_REGION_END (IPU7_FW_CODE_REGION_START + \ + IPU7_FW_CODE_REGION_SIZE) /* 80MB */ + +#define IPU7_IS_MMU_FW_RD_OFFSET 0x274000 +#define IPU7_IS_MMU_FW_RD_STREAM_NUM 3 +#define IPU7_IS_MMU_FW_RD_L1_BLOCKNR_REG 0x54 +#define IPU7_IS_MMU_FW_RD_L2_BLOCKNR_REG 0x60 + +#define IPU7_IS_MMU_FW_WR_OFFSET 0x275000 +#define IPU7_IS_MMU_FW_WR_STREAM_NUM 3 +#define IPU7_IS_MMU_FW_WR_L1_BLOCKNR_REG 0x54 +#define IPU7_IS_MMU_FW_WR_L2_BLOCKNR_REG 0x60 + +#define IPU7_IS_MMU_M0_OFFSET 0x276000 +#define IPU7_IS_MMU_M0_STREAM_NUM 8 +#define IPU7_IS_MMU_M0_L1_BLOCKNR_REG 0x54 +#define IPU7_IS_MMU_M0_L2_BLOCKNR_REG 0x74 + +#define IPU7_IS_MMU_M1_OFFSET 0x277000 +#define IPU7_IS_MMU_M1_STREAM_NUM 16 +#define IPU7_IS_MMU_M1_L1_BLOCKNR_REG 0x54 +#define IPU7_IS_MMU_M1_L2_BLOCKNR_REG 0x94 + +#define IPU7_PS_MMU_FW_RD_OFFSET 0x148000 +#define IPU7_PS_MMU_FW_RD_STREAM_NUM 20 +#define IPU7_PS_MMU_FW_RD_L1_BLOCKNR_REG 0x54 +#define IPU7_PS_MMU_FW_RD_L2_BLOCKNR_REG 0xa4 + +#define IPU7_PS_MMU_FW_WR_OFFSET 0x149000 +#define IPU7_PS_MMU_FW_WR_STREAM_NUM 10 +#define IPU7_PS_MMU_FW_WR_L1_BLOCKNR_REG 0x54 +#define IPU7_PS_MMU_FW_WR_L2_BLOCKNR_REG 0x7c + +#define IPU7_PS_MMU_SRT_RD_OFFSET 0x14a000 +#define IPU7_PS_MMU_SRT_RD_STREAM_NUM 40 +#define IPU7_PS_MMU_SRT_RD_L1_BLOCKNR_REG 0x54 +#define IPU7_PS_MMU_SRT_RD_L2_BLOCKNR_REG 0xf4 + +#define IPU7_PS_MMU_SRT_WR_OFFSET 0x14b000 +#define IPU7_PS_MMU_SRT_WR_STREAM_NUM 40 +#define IPU7_PS_MMU_SRT_WR_L1_BLOCKNR_REG 0x54 +#define IPU7_PS_MMU_SRT_WR_L2_BLOCKNR_REG 0xf4 + +#define IPU7_IS_UAO_UC_RD_OFFSET 0x27c000 +#define IPU7_IS_UAO_UC_RD_PLANENUM 4 + +#define IPU7_IS_UAO_UC_WR_OFFSET 0x27d000 +#define IPU7_IS_UAO_UC_WR_PLANENUM 4 + +#define IPU7_IS_UAO_M0_WR_OFFSET 0x27e000 +#define IPU7_IS_UAO_M0_WR_PLANENUM 8 + +#define IPU7_IS_UAO_M1_WR_OFFSET 0x27f000 +#define IPU7_IS_UAO_M1_WR_PLANENUM 16 + +#define IPU7_PS_UAO_FW_RD_OFFSET 0x156000 +#define IPU7_PS_UAO_FW_RD_PLANENUM 20 + +#define IPU7_PS_UAO_FW_WR_OFFSET 0x157000 +#define IPU7_PS_UAO_FW_WR_PLANENUM 16 + +#define IPU7_PS_UAO_SRT_RD_OFFSET 0x154000 +#define IPU7_PS_UAO_SRT_RD_PLANENUM 40 + +#define IPU7_PS_UAO_SRT_WR_OFFSET 0x155000 +#define IPU7_PS_UAO_SRT_WR_PLANENUM 40 + +#define IPU7_IS_ZLX_UC_RD_OFFSET 0x278000 +#define IPU7_IS_ZLX_UC_WR_OFFSET 0x279000 +#define IPU7_IS_ZLX_M0_OFFSET 0x27a000 +#define IPU7_IS_ZLX_M1_OFFSET 0x27b000 +#define IPU7_IS_ZLX_UC_RD_NUM 4 +#define IPU7_IS_ZLX_UC_WR_NUM 4 +#define IPU7_IS_ZLX_M0_NUM 8 +#define IPU7_IS_ZLX_M1_NUM 16 + +#define IPU7_PS_ZLX_DATA_RD_OFFSET 0x14e000 +#define IPU7_PS_ZLX_DATA_WR_OFFSET 0x14f000 +#define IPU7_PS_ZLX_FW_RD_OFFSET 0x150000 +#define IPU7_PS_ZLX_FW_WR_OFFSET 0x151000 +#define IPU7_PS_ZLX_DATA_RD_NUM 32 +#define IPU7_PS_ZLX_DATA_WR_NUM 32 +#define IPU7_PS_ZLX_FW_RD_NUM 16 +#define IPU7_PS_ZLX_FW_WR_NUM 10 + +/* IPU7P5 */ +/* IS MMU Cmd RD */ +#define IPU7P5_IS_MMU_FW_RD_OFFSET 0x274000 +#define IPU7P5_IS_MMU_FW_RD_STREAM_NUM 3 +#define IPU7P5_IS_MMU_FW_RD_L1_BLOCKNR_REG 0x54 +#define IPU7P5_IS_MMU_FW_RD_L2_BLOCKNR_REG 0x60 + +/* IS MMU Cmd WR */ +#define IPU7P5_IS_MMU_FW_WR_OFFSET 0x275000 +#define IPU7P5_IS_MMU_FW_WR_STREAM_NUM 3 +#define IPU7P5_IS_MMU_FW_WR_L1_BLOCKNR_REG 0x54 +#define IPU7P5_IS_MMU_FW_WR_L2_BLOCKNR_REG 0x60 + +/* IS MMU Data WR Snoop */ +#define IPU7P5_IS_MMU_M0_OFFSET 0x276000 +#define IPU7P5_IS_MMU_M0_STREAM_NUM 16 +#define IPU7P5_IS_MMU_M0_L1_BLOCKNR_REG 0x54 +#define IPU7P5_IS_MMU_M0_L2_BLOCKNR_REG 0x94 + +/* IS MMU Data WR ISOC */ +#define IPU7P5_IS_MMU_M1_OFFSET 0x277000 +#define IPU7P5_IS_MMU_M1_STREAM_NUM 16 +#define IPU7P5_IS_MMU_M1_L1_BLOCKNR_REG 0x54 +#define IPU7P5_IS_MMU_M1_L2_BLOCKNR_REG 0x94 + +/* PS MMU FW RD */ +#define IPU7P5_PS_MMU_FW_RD_OFFSET 0x148000 +#define IPU7P5_PS_MMU_FW_RD_STREAM_NUM 16 +#define IPU7P5_PS_MMU_FW_RD_L1_BLOCKNR_REG 0x54 +#define IPU7P5_PS_MMU_FW_RD_L2_BLOCKNR_REG 0x94 + +/* PS MMU FW WR */ +#define IPU7P5_PS_MMU_FW_WR_OFFSET 0x149000 +#define IPU7P5_PS_MMU_FW_WR_STREAM_NUM 10 +#define IPU7P5_PS_MMU_FW_WR_L1_BLOCKNR_REG 0x54 +#define IPU7P5_PS_MMU_FW_WR_L2_BLOCKNR_REG 0x7c + +/* PS MMU FW Data RD VC0 */ +#define IPU7P5_PS_MMU_SRT_RD_OFFSET 0x14a000 +#define IPU7P5_PS_MMU_SRT_RD_STREAM_NUM 22 +#define IPU7P5_PS_MMU_SRT_RD_L1_BLOCKNR_REG 0x54 +#define IPU7P5_PS_MMU_SRT_RD_L2_BLOCKNR_REG 0xac + +/* PS MMU FW Data WR VC0 */ +#define IPU7P5_PS_MMU_SRT_WR_OFFSET 0x14b000 +#define IPU7P5_PS_MMU_SRT_WR_STREAM_NUM 32 +#define IPU7P5_PS_MMU_SRT_WR_L1_BLOCKNR_REG 0x54 +#define IPU7P5_PS_MMU_SRT_WR_L2_BLOCKNR_REG 0xd4 + +/* IS UAO UC RD */ +#define IPU7P5_IS_UAO_UC_RD_OFFSET 0x27c000 +#define IPU7P5_IS_UAO_UC_RD_PLANENUM 4 + +/* IS UAO UC WR */ +#define IPU7P5_IS_UAO_UC_WR_OFFSET 0x27d000 +#define IPU7P5_IS_UAO_UC_WR_PLANENUM 4 + +/* IS UAO M0 WR */ +#define IPU7P5_IS_UAO_M0_WR_OFFSET 0x27e000 +#define IPU7P5_IS_UAO_M0_WR_PLANENUM 16 + +/* IS UAO M1 WR */ +#define IPU7P5_IS_UAO_M1_WR_OFFSET 0x27f000 +#define IPU7P5_IS_UAO_M1_WR_PLANENUM 16 + +/* PS UAO FW RD */ +#define IPU7P5_PS_UAO_FW_RD_OFFSET 0x156000 +#define IPU7P5_PS_UAO_FW_RD_PLANENUM 16 + +/* PS UAO FW WR */ +#define IPU7P5_PS_UAO_FW_WR_OFFSET 0x157000 +#define IPU7P5_PS_UAO_FW_WR_PLANENUM 10 + +/* PS UAO SRT RD */ +#define IPU7P5_PS_UAO_SRT_RD_OFFSET 0x154000 +#define IPU7P5_PS_UAO_SRT_RD_PLANENUM 22 + +/* PS UAO SRT WR */ +#define IPU7P5_PS_UAO_SRT_WR_OFFSET 0x155000 +#define IPU7P5_PS_UAO_SRT_WR_PLANENUM 32 + +#define IPU7P5_IS_ZLX_UC_RD_OFFSET 0x278000 +#define IPU7P5_IS_ZLX_UC_WR_OFFSET 0x279000 +#define IPU7P5_IS_ZLX_M0_OFFSET 0x27a000 +#define IPU7P5_IS_ZLX_M1_OFFSET 0x27b000 +#define IPU7P5_IS_ZLX_UC_RD_NUM 4 +#define IPU7P5_IS_ZLX_UC_WR_NUM 4 +#define IPU7P5_IS_ZLX_M0_NUM 16 +#define IPU7P5_IS_ZLX_M1_NUM 16 + +#define IPU7P5_PS_ZLX_DATA_RD_OFFSET 0x14e000 +#define IPU7P5_PS_ZLX_DATA_WR_OFFSET 0x14f000 +#define IPU7P5_PS_ZLX_FW_RD_OFFSET 0x150000 +#define IPU7P5_PS_ZLX_FW_WR_OFFSET 0x151000 +#define IPU7P5_PS_ZLX_DATA_RD_NUM 22 +#define IPU7P5_PS_ZLX_DATA_WR_NUM 32 +#define IPU7P5_PS_ZLX_FW_RD_NUM 16 +#define IPU7P5_PS_ZLX_FW_WR_NUM 10 + +struct ipu7_mmu_hw { + char name[32]; + + void __iomem *base; + void __iomem *zlx_base; + void __iomem *uao_base; + + u32 offset; + u32 zlx_offset; + u32 uao_offset; + + u32 info_bits; + u32 refill; + u32 collapse_en_bitmap; + u32 at_sp_arb_cfg; + + u32 l1_block; + u32 l2_block; + + u8 nr_l1streams; + u8 nr_l2streams; + u32 l1_block_sz[IPU7_MMU_MAX_TLB_L1_STREAMS]; + u32 l2_block_sz[IPU7_MMU_MAX_TLB_L2_STREAMS]; + + u8 zlx_nr; + u32 zlx_axi_pool[IPU7_ZLX_POOL_NUM]; + u32 zlx_en[IPU7_ZLX_MAX_NUM]; + u32 zlx_conf[IPU7_ZLX_MAX_NUM]; + + u32 uao_p_num; + u32 uao_p2tlb[IPU7_UAO_PLANE_MAX_NUM]; +}; + +struct ipu7_mmu_hwdata { + struct ipu7_mmu_hw *hwdata; + unsigned int nr_mmus; +}; + +#endif diff --git a/drivers/media/pci/intel/ipu6/ipu7-platform-regs.h b/drivers/media/pci/intel/ipu6/ipu7-platform-regs.h new file mode 100644 index 000000000000..39cd5c0890e5 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu7-platform-regs.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2026 Intel Corporation */ + +#ifndef IPU7_PLATFORM_REGS_H +#define IPU7_PLATFORM_REGS_H + +#define IPU7_IS_UC_CTRL_BASE 0x230000 +#define IPU7_ISYS_DMEM_OFFSET 0x200000 +#define IPU7_PS_UC_CTRL_BASE 0x130000 +#define IPU7_PSYS_DMEM_OFFSET 0x100000 + +#define IPU7_IS_IO_BASE 0x280000 +#define IPU7_IS_IO_CSI2_GPREGS_BASE (IPU7_IS_IO_BASE + 0x53400) + +#define IPU7_IS_IO_CSI2_LEGACY_IRQ_CTRL_BASE (IPU7_IS_IO_BASE + 0x49000) +#define IPU7_IRQ_CTL_EDGE 0x0 +#define IPU7_IRQ_CTL_MASK 0x4 +#define IPU7_IRQ_CTL_STATUS 0x8 +#define IPU7_IRQ_CTL_CLEAR 0xc +#define IPU7_IRQ_CTL_ENABLE 0x10 +#define IPU7_CSI_RX_LEGACY_IRQ_MASK 0x1ff + +#define IPU7_TO_SW_IRQ_CNTL_EDGE 0x4000 +#define IPU7_TO_SW_IRQ_CNTL_MASK_N 0x4004 +#define IPU7_TO_SW_IRQ_CNTL_STATUS 0x4008 +#define IPU7_TO_SW_IRQ_CNTL_CLEAR 0x400c +#define IPU7_TO_SW_IRQ_CNTL_ENABLE 0x4010 +#define IPU7_IS_UC_TO_SW_IRQ_MASK 0xf +#define IPU7_TO_SW_IRQ_FW BIT(0) +#define IPU7_REG_PRINTF_AXI_CNTL 0x301c + +#endif diff --git a/drivers/media/pci/intel/ivsc/mei_ace.c b/drivers/media/pci/intel/ivsc/mei_ace.c index b306a320b70f..bb57656fc85a 100644 --- a/drivers/media/pci/intel/ivsc/mei_ace.c +++ b/drivers/media/pci/intel/ivsc/mei_ace.c @@ -414,13 +414,13 @@ static int mei_ace_setup_dev_link(struct mei_ace *ace) /* setup link between mei_ace and mei_csi */ ace->csi_link = device_link_add(csi_dev, dev, DL_FLAG_PM_RUNTIME | DL_FLAG_RPM_ACTIVE | DL_FLAG_STATELESS); - put_device(csi_dev); if (!ace->csi_link) { ret = -EINVAL; dev_err(dev, "failed to link to %s\n", dev_name(csi_dev)); - goto err; + goto err_put; } + put_device(csi_dev); ace->csi_dev = csi_dev; return 0; diff --git a/drivers/media/pci/intel/ivsc/mei_csi.c b/drivers/media/pci/intel/ivsc/mei_csi.c index c2917e156345..005b1414a252 100644 --- a/drivers/media/pci/intel/ivsc/mei_csi.c +++ b/drivers/media/pci/intel/ivsc/mei_csi.c @@ -27,7 +27,6 @@ #include <linux/workqueue.h> #include <media/ipu-bridge.h> -#include <media/ipu6-pci-table.h> #include <media/v4l2-async.h> #include <media/v4l2-ctrls.h> #include <media/v4l2-fwnode.h> @@ -338,6 +337,7 @@ static int mei_csi_init_state(struct v4l2_subdev *sd, } static int mei_csi_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -644,13 +644,9 @@ static int mei_csi_probe(struct mei_cl_device *cldev, struct device *dev = &cldev->dev; struct pci_dev *ipu; struct mei_csi *csi; - unsigned int i; int ret; - for (i = 0, ipu = NULL; !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(); if (!ipu) return -ENODEV; diff --git a/drivers/media/pci/ivtv/ivtv-controls.c b/drivers/media/pci/ivtv/ivtv-controls.c index f087a12c4ebd..816e634de1eb 100644 --- a/drivers/media/pci/ivtv/ivtv-controls.c +++ b/drivers/media/pci/ivtv/ivtv-controls.c @@ -60,7 +60,7 @@ static int ivtv_s_video_encoding(struct cx2341x_handler *cxhdl, u32 val) format.format.width = cxhdl->width / (is_mpeg1 ? 2 : 1); format.format.height = cxhdl->height; format.format.code = MEDIA_BUS_FMT_FIXED; - v4l2_subdev_call(itv->sd_video, pad, set_fmt, NULL, &format); + v4l2_subdev_call(itv->sd_video, pad, set_fmt, NULL, NULL, &format); return 0; } diff --git a/drivers/media/pci/ivtv/ivtv-ioctl.c b/drivers/media/pci/ivtv/ivtv-ioctl.c index fc95f0bf48d5..aba55fcc1e22 100644 --- a/drivers/media/pci/ivtv/ivtv-ioctl.c +++ b/drivers/media/pci/ivtv/ivtv-ioctl.c @@ -587,7 +587,7 @@ static int ivtv_s_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *f format.format.width = fmt->fmt.pix.width; format.format.height = h; format.format.code = MEDIA_BUS_FMT_FIXED; - v4l2_subdev_call(itv->sd_video, pad, set_fmt, NULL, &format); + v4l2_subdev_call(itv->sd_video, pad, set_fmt, NULL, NULL, &format); return ivtv_g_fmt_vid_cap(file, fh, fmt); } diff --git a/drivers/media/pci/saa7134/saa7134-core.c b/drivers/media/pci/saa7134/saa7134-core.c index 2f5b258d682b..507ad23776e0 100644 --- a/drivers/media/pci/saa7134/saa7134-core.c +++ b/drivers/media/pci/saa7134/saa7134-core.c @@ -1368,7 +1368,7 @@ static int __maybe_unused saa7134_buffer_requeue(struct saa7134_dev *dev, return 0; } -static int __maybe_unused saa7134_suspend(struct device *dev_d) +static int saa7134_suspend(struct device *dev_d) { struct pci_dev *pci_dev = to_pci_dev(dev_d); struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev); @@ -1400,7 +1400,7 @@ static int __maybe_unused saa7134_suspend(struct device *dev_d) return 0; } -static int __maybe_unused saa7134_resume(struct device *dev_d) +static int saa7134_resume(struct device *dev_d) { struct v4l2_device *v4l2_dev = dev_get_drvdata(dev_d); struct saa7134_dev *dev = container_of(v4l2_dev, struct saa7134_dev, v4l2_dev); @@ -1484,14 +1484,14 @@ EXPORT_SYMBOL(saa7134_ts_unregister); /* ----------------------------------------------------------- */ -static SIMPLE_DEV_PM_OPS(saa7134_pm_ops, saa7134_suspend, saa7134_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(saa7134_pm_ops, saa7134_suspend, saa7134_resume); static struct pci_driver saa7134_pci_driver = { .name = "saa7134", .id_table = saa7134_pci_tbl, .probe = saa7134_initdev, .remove = saa7134_finidev, - .driver.pm = &saa7134_pm_ops, + .driver.pm = pm_sleep_ptr(&saa7134_pm_ops), }; static int __init saa7134_init(void) diff --git a/drivers/media/pci/saa7134/saa7134-empress.c b/drivers/media/pci/saa7134/saa7134-empress.c index 8c4f70e4177d..d04a68bb05d8 100644 --- a/drivers/media/pci/saa7134/saa7134-empress.c +++ b/drivers/media/pci/saa7134/saa7134-empress.c @@ -122,7 +122,7 @@ static int empress_s_fmt_vid_cap(struct file *file, void *priv, }; v4l2_fill_mbus_format(&format.format, &f->fmt.pix, MEDIA_BUS_FMT_FIXED); - saa_call_all(dev, pad, set_fmt, NULL, &format); + saa_call_all(dev, pad, set_fmt, NULL, NULL, &format); v4l2_fill_pix_format(&f->fmt.pix, &format.format); f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; @@ -145,7 +145,7 @@ static int empress_try_fmt_vid_cap(struct file *file, void *priv, }; v4l2_fill_mbus_format(&format.format, &f->fmt.pix, MEDIA_BUS_FMT_FIXED); - saa_call_all(dev, pad, set_fmt, &pad_state, &format); + saa_call_all(dev, pad, set_fmt, NULL, &pad_state, &format); v4l2_fill_pix_format(&f->fmt.pix, &format.format); f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; diff --git a/drivers/media/pci/saa7134/saa7134-input.c b/drivers/media/pci/saa7134/saa7134-input.c index 7f6680de3156..b2a2372d149a 100644 --- a/drivers/media/pci/saa7134/saa7134-input.c +++ b/drivers/media/pci/saa7134/saa7134-input.c @@ -769,7 +769,8 @@ int saa7134_input_init1(struct saa7134_dev *dev) } ir = kzalloc_obj(*ir); - rc = rc_allocate_device(RC_DRIVER_SCANCODE); + rc = rc_allocate_device(raw_decode ? + RC_DRIVER_IR_RAW : RC_DRIVER_SCANCODE); if (!ir || !rc) { err = -ENOMEM; goto err_out_free; @@ -792,10 +793,8 @@ int saa7134_input_init1(struct saa7134_dev *dev) rc->priv = dev; rc->open = saa7134_ir_open; rc->close = saa7134_ir_close; - if (raw_decode) { - rc->driver_type = RC_DRIVER_IR_RAW; + if (raw_decode) rc->allowed_protocols = RC_PROTO_BIT_ALL_IR_DECODER; - } rc->device_name = saa7134_boards[dev->board].name; rc->input_phys = ir->phys; diff --git a/drivers/media/pci/saa7146/mxb.c b/drivers/media/pci/saa7146/mxb.c index d759e8a87e24..129d41c1862b 100644 --- a/drivers/media/pci/saa7146/mxb.c +++ b/drivers/media/pci/saa7146/mxb.c @@ -37,7 +37,7 @@ /* global variable */ static int mxb_num; -/* initial frequence the tuner will be tuned to. +/* initial frequency the tuner will be tuned to. in verden (lower saxony, germany) 4148 is a channel called "phoenix" */ static int freq = 4148; @@ -429,7 +429,7 @@ err: /* some stuff is done via direct write to the registers */ /* this is ugly, but because of the fact that this is completely - hardware dependend, it should be done directly... */ + hardware dependent, it should be done directly... */ saa7146_write(dev, DD1_STREAM_B, 0x00000000); saa7146_write(dev, DD1_INIT, 0x02000200); saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); diff --git a/drivers/media/pci/saa7164/saa7164-core.c b/drivers/media/pci/saa7164/saa7164-core.c index ac5eb6b923e2..debcfaaf36dc 100644 --- a/drivers/media/pci/saa7164/saa7164-core.c +++ b/drivers/media/pci/saa7164/saa7164-core.c @@ -742,35 +742,6 @@ u32 saa7164_getcurrentfirmwareversion(struct saa7164_dev *dev) return reg; } -/* TODO: Debugging func, remove */ -void saa7164_dumpregs(struct saa7164_dev *dev, u32 addr) -{ - int i; - - dprintk(1, "--------------------> 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n"); - - for (i = 0; i < 0x100; i += 16) - dprintk(1, "region0[0x%08x] = %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", - i, - (u8)saa7164_readb(addr + i + 0), - (u8)saa7164_readb(addr + i + 1), - (u8)saa7164_readb(addr + i + 2), - (u8)saa7164_readb(addr + i + 3), - (u8)saa7164_readb(addr + i + 4), - (u8)saa7164_readb(addr + i + 5), - (u8)saa7164_readb(addr + i + 6), - (u8)saa7164_readb(addr + i + 7), - (u8)saa7164_readb(addr + i + 8), - (u8)saa7164_readb(addr + i + 9), - (u8)saa7164_readb(addr + i + 10), - (u8)saa7164_readb(addr + i + 11), - (u8)saa7164_readb(addr + i + 12), - (u8)saa7164_readb(addr + i + 13), - (u8)saa7164_readb(addr + i + 14), - (u8)saa7164_readb(addr + i + 15) - ); -} - static void saa7164_dump_hwdesc(struct saa7164_dev *dev) { dprintk(1, "@0x%p hwdesc sizeof(struct tmComResHWDescr) = %d bytes\n", @@ -856,7 +827,7 @@ static void saa7164_get_descriptors(struct saa7164_dev *dev) saa7164_dump_hwdesc(dev); if (dev->intfdesc.bLength != sizeof(struct tmComResInterfaceDescr)) { - printk(KERN_ERR "struct struct tmComResInterfaceDescr is mangled\n"); + printk(KERN_ERR "struct tmComResInterfaceDescr is mangled\n"); printk(KERN_ERR "Need %x got %d\n", dev->intfdesc.bLength, (u32)sizeof(struct tmComResInterfaceDescr)); } else @@ -1344,7 +1315,6 @@ static int saa7164_initdev(struct pci_dev *pci_dev, } saa7164_get_descriptors(dev); - saa7164_dumpregs(dev, 0); saa7164_getcurrentfirmwareversion(dev); saa7164_getfirmwarestatus(dev); err = saa7164_bus_setup(dev); diff --git a/drivers/media/pci/saa7164/saa7164.h b/drivers/media/pci/saa7164/saa7164.h index 94e987e7b5e5..53ef9c3343ba 100644 --- a/drivers/media/pci/saa7164/saa7164.h +++ b/drivers/media/pci/saa7164/saa7164.h @@ -490,7 +490,6 @@ extern unsigned int vbi_buffers; /* ----------------------------------------------------------- */ /* saa7164-core.c */ -void saa7164_dumpregs(struct saa7164_dev *dev, u32 addr); void saa7164_getfirmwarestatus(struct saa7164_dev *dev); u32 saa7164_getcurrentfirmwareversion(struct saa7164_dev *dev); void saa7164_histogram_update(struct saa7164_histogram *hg, u32 val); diff --git a/drivers/media/pci/tw68/tw68-core.c b/drivers/media/pci/tw68/tw68-core.c index 509d7ddec150..0909b2d9fcdd 100644 --- a/drivers/media/pci/tw68/tw68-core.c +++ b/drivers/media/pci/tw68/tw68-core.c @@ -228,7 +228,7 @@ static int tw68_initdev(struct pci_dev *pci_dev, /* pci init */ dev->pci = pci_dev; - if (pci_enable_device(pci_dev)) { + if (pcim_enable_device(pci_dev)) { err = -EIO; goto fail1; } @@ -359,7 +359,7 @@ static void tw68_finidev(struct pci_dev *pci_dev) v4l2_device_unregister(&dev->v4l2_dev); } -static int __maybe_unused tw68_suspend(struct device *dev_d) +static int tw68_suspend(struct device *dev_d) { struct pci_dev *pci_dev = to_pci_dev(dev_d); struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev); @@ -377,7 +377,7 @@ static int __maybe_unused tw68_suspend(struct device *dev_d) return 0; } -static int __maybe_unused tw68_resume(struct device *dev_d) +static int tw68_resume(struct device *dev_d) { struct v4l2_device *v4l2_dev = dev_get_drvdata(dev_d); struct tw68_dev *dev = container_of(v4l2_dev, @@ -405,14 +405,14 @@ static int __maybe_unused tw68_resume(struct device *dev_d) /* ----------------------------------------------------------- */ -static SIMPLE_DEV_PM_OPS(tw68_pm_ops, tw68_suspend, tw68_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(tw68_pm_ops, tw68_suspend, tw68_resume); static struct pci_driver tw68_pci_driver = { .name = "tw68", .id_table = tw68_pci_tbl, .probe = tw68_initdev, .remove = tw68_finidev, - .driver.pm = &tw68_pm_ops, + .driver.pm = pm_sleep_ptr(&tw68_pm_ops), }; module_pci_driver(tw68_pci_driver); diff --git a/drivers/media/platform/amd/isp4/isp4_subdev.c b/drivers/media/platform/amd/isp4/isp4_subdev.c index 6716ab9c128a..a108d90ace2d 100644 --- a/drivers/media/platform/amd/isp4/isp4_subdev.c +++ b/drivers/media/platform/amd/isp4/isp4_subdev.c @@ -906,6 +906,7 @@ static const struct v4l2_subdev_video_ops isp4sd_video_ops = { }; static int isp4sd_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) { diff --git a/drivers/media/platform/amd/isp4/isp4_video.c b/drivers/media/platform/amd/isp4/isp4_video.c index 0cebb39f98e1..856a2a0b4a12 100644 --- a/drivers/media/platform/amd/isp4/isp4_video.c +++ b/drivers/media/platform/amd/isp4/isp4_video.c @@ -240,7 +240,7 @@ static int isp4vid_set_fmt_2_isp(struct v4l2_subdev *sdev, fmt.pad = ISP4VID_PAD_VIDEO_OUTPUT; fmt.format.width = pix_fmt->width; fmt.format.height = pix_fmt->height; - return v4l2_subdev_call(sdev, pad, set_fmt, NULL, &fmt); + return v4l2_subdev_call(sdev, pad, set_fmt, NULL, NULL, &fmt); } static int isp4vid_s_fmt_vid_cap(struct file *file, void *priv, diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-core.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-core.c index ff6413fff889..553c808f8f3b 100644 --- a/drivers/media/platform/amlogic/c3/isp/c3-isp-core.c +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-core.c @@ -461,6 +461,7 @@ static void c3_isp_core_set_source_fmt(struct v4l2_subdev_state *state, } static int c3_isp_core_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/platform/amlogic/c3/isp/c3-isp-resizer.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-resizer.c index 453a889e0b27..1f9c16eb0842 100644 --- a/drivers/media/platform/amlogic/c3/isp/c3-isp-resizer.c +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-resizer.c @@ -621,6 +621,7 @@ static void c3_isp_rsz_set_source_fmt(struct v4l2_subdev_state *state, } static int c3_isp_rsz_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { @@ -633,6 +634,7 @@ static int c3_isp_rsz_set_fmt(struct v4l2_subdev *sd, } static int c3_isp_rsz_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -674,6 +676,7 @@ static int c3_isp_rsz_get_selection(struct v4l2_subdev *sd, } static int c3_isp_rsz_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/platform/amlogic/c3/mipi-adapter/c3-mipi-adap.c b/drivers/media/platform/amlogic/c3/mipi-adapter/c3-mipi-adap.c index 4bd98fb9c7e9..6f75112723b5 100644 --- a/drivers/media/platform/amlogic/c3/mipi-adapter/c3-mipi-adap.c +++ b/drivers/media/platform/amlogic/c3/mipi-adapter/c3-mipi-adap.c @@ -521,6 +521,7 @@ static int c3_mipi_adap_enum_mbus_code(struct v4l2_subdev *sd, } static int c3_mipi_adap_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/platform/amlogic/c3/mipi-csi2/c3-mipi-csi2.c b/drivers/media/platform/amlogic/c3/mipi-csi2/c3-mipi-csi2.c index b9e4ef3fc308..0c399665f7fa 100644 --- a/drivers/media/platform/amlogic/c3/mipi-csi2/c3-mipi-csi2.c +++ b/drivers/media/platform/amlogic/c3/mipi-csi2/c3-mipi-csi2.c @@ -491,6 +491,7 @@ static int c3_mipi_csi_enum_mbus_code(struct v4l2_subdev *sd, } static int c3_mipi_csi_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/platform/arm/mali-c55/mali-c55-isp.c b/drivers/media/platform/arm/mali-c55/mali-c55-isp.c index e128adf6ee37..c8464dec9a21 100644 --- a/drivers/media/platform/arm/mali-c55/mali-c55-isp.c +++ b/drivers/media/platform/arm/mali-c55/mali-c55-isp.c @@ -203,6 +203,7 @@ static int mali_c55_isp_enum_frame_size(struct v4l2_subdev *sd, } static int mali_c55_isp_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { @@ -265,6 +266,7 @@ static int mali_c55_isp_set_fmt(struct v4l2_subdev *sd, } static int mali_c55_isp_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -278,6 +280,7 @@ static int mali_c55_isp_get_selection(struct v4l2_subdev *sd, } static int mali_c55_isp_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-resizer.c b/drivers/media/platform/arm/mali-c55/mali-c55-resizer.c index 6706939b4a90..774352c8a939 100644 --- a/drivers/media/platform/arm/mali-c55/mali-c55-resizer.c +++ b/drivers/media/platform/arm/mali-c55/mali-c55-resizer.c @@ -715,6 +715,7 @@ static int mali_c55_rsz_enum_frame_size(struct v4l2_subdev *sd, } static int mali_c55_rsz_set_sink_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { @@ -773,6 +774,7 @@ static int mali_c55_rsz_set_sink_fmt(struct v4l2_subdev *sd, } static int mali_c55_rsz_set_source_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { @@ -812,6 +814,7 @@ static int mali_c55_rsz_set_source_fmt(struct v4l2_subdev *sd, } static int mali_c55_rsz_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { @@ -826,12 +829,13 @@ static int mali_c55_rsz_set_fmt(struct v4l2_subdev *sd, if (format->pad == MALI_C55_RSZ_SINK_PAD || format->pad == MALI_C55_RSZ_SINK_BYPASS_PAD) - return mali_c55_rsz_set_sink_fmt(sd, state, format); + return mali_c55_rsz_set_sink_fmt(sd, ci, state, format); - return mali_c55_rsz_set_source_fmt(sd, state, format); + return mali_c55_rsz_set_source_fmt(sd, ci, state, format); } static int mali_c55_rsz_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -850,6 +854,7 @@ static int mali_c55_rsz_get_selection(struct v4l2_subdev *sd, } static int mali_c55_rsz_set_crop(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -908,6 +913,7 @@ static int mali_c55_rsz_set_crop(struct v4l2_subdev *sd, } static int mali_c55_rsz_set_compose(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -956,6 +962,7 @@ static int mali_c55_rsz_set_compose(struct v4l2_subdev *sd, } static int mali_c55_rsz_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -963,10 +970,10 @@ static int mali_c55_rsz_set_selection(struct v4l2_subdev *sd, return -EINVAL; if (sel->target == V4L2_SEL_TGT_CROP) - return mali_c55_rsz_set_crop(sd, state, sel); + return mali_c55_rsz_set_crop(sd, ci, state, sel); if (sel->target == V4L2_SEL_TGT_COMPOSE) - return mali_c55_rsz_set_compose(sd, state, sel); + return mali_c55_rsz_set_compose(sd, ci, state, sel); return -EINVAL; } diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-tpg.c b/drivers/media/platform/arm/mali-c55/mali-c55-tpg.c index 894f4cf377af..cc390a3fc253 100644 --- a/drivers/media/platform/arm/mali-c55/mali-c55-tpg.c +++ b/drivers/media/platform/arm/mali-c55/mali-c55-tpg.c @@ -180,6 +180,7 @@ static int mali_c55_tpg_enum_frame_size(struct v4l2_subdev *sd, } static int mali_c55_tpg_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/platform/aspeed/aspeed-video.c b/drivers/media/platform/aspeed/aspeed-video.c index a292275f6b7b..7ba9314fdbd2 100644 --- a/drivers/media/platform/aspeed/aspeed-video.c +++ b/drivers/media/platform/aspeed/aspeed-video.c @@ -2267,19 +2267,19 @@ static int aspeed_video_init(struct aspeed_video *video) if (rc) goto err_unprepare_eclk; - of_reserved_mem_device_init(dev); + devm_of_reserved_mem_device_init(dev); rc = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); if (rc) { dev_err(dev, "Failed to set DMA mask\n"); - goto err_release_reserved_mem; + goto err_unprepare_vclk; } if (!aspeed_video_alloc_buf(video, &video->jpeg, VE_JPEG_HEADER_SIZE)) { dev_err(dev, "Failed to allocate DMA for JPEG header\n"); rc = -ENOMEM; - goto err_release_reserved_mem; + goto err_unprepare_vclk; } dev_info(video->dev, "alloc mem size(%d) at %pad for jpeg header\n", VE_JPEG_HEADER_SIZE, &video->jpeg.dma); @@ -2288,8 +2288,7 @@ static int aspeed_video_init(struct aspeed_video *video) return 0; -err_release_reserved_mem: - of_reserved_mem_device_release(dev); +err_unprepare_vclk: clk_unprepare(video->vclk); err_unprepare_eclk: clk_unprepare(video->eclk); @@ -2343,7 +2342,6 @@ static int aspeed_video_probe(struct platform_device *pdev) rc = aspeed_video_setup_video(video); if (rc) { aspeed_video_free_buf(video, &video->jpeg); - of_reserved_mem_device_release(&pdev->dev); clk_unprepare(video->vclk); clk_unprepare(video->eclk); return rc; @@ -2374,8 +2372,6 @@ static void aspeed_video_remove(struct platform_device *pdev) v4l2_device_unregister(v4l2_dev); aspeed_video_free_buf(video, &video->jpeg); - - of_reserved_mem_device_release(dev); } static struct platform_driver aspeed_video_driver = { diff --git a/drivers/media/platform/atmel/atmel-isi.c b/drivers/media/platform/atmel/atmel-isi.c index a05a744cbb75..fa33d11f736f 100644 --- a/drivers/media/platform/atmel/atmel-isi.c +++ b/drivers/media/platform/atmel/atmel-isi.c @@ -609,7 +609,7 @@ static int isi_try_fmt(struct atmel_isi *isi, struct v4l2_format *f, isi_try_fse(isi, isi_fmt, &pad_state); - ret = v4l2_subdev_call(isi->entity.subdev, pad, set_fmt, + ret = v4l2_subdev_call(isi->entity.subdev, pad, set_fmt, NULL, &pad_state, &format); if (ret < 0) return ret; @@ -641,7 +641,7 @@ static int isi_set_fmt(struct atmel_isi *isi, struct v4l2_format *f) v4l2_fill_mbus_format(&format.format, &f->fmt.pix, current_fmt->mbus_code); ret = v4l2_subdev_call(isi->entity.subdev, pad, - set_fmt, NULL, &format); + set_fmt, NULL, NULL, &format); if (ret < 0) return ret; @@ -1121,10 +1121,13 @@ static void isi_graph_notify_unbind(struct v4l2_async_notifier *notifier, { struct atmel_isi *isi = notifier_to_isi(notifier); + if (!video_is_registered(isi->vdev)) + return; + dev_dbg(isi->dev, "Removing %s\n", video_device_node_name(isi->vdev)); - /* Checks internally if vdev have been init or not */ video_unregister_device(isi->vdev); + isi->vdev = NULL; } static int isi_graph_notify_bound(struct v4l2_async_notifier *notifier, @@ -1323,6 +1326,8 @@ static void atmel_isi_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); v4l2_async_nf_unregister(&isi->notifier); v4l2_async_nf_cleanup(&isi->notifier); + if (isi->vdev) + video_device_release(isi->vdev); v4l2_device_unregister(&isi->v4l2_dev); } diff --git a/drivers/media/platform/broadcom/bcm2835-unicam.c b/drivers/media/platform/broadcom/bcm2835-unicam.c index 14bb916dd7b1..e7c708cdd7f8 100644 --- a/drivers/media/platform/broadcom/bcm2835-unicam.c +++ b/drivers/media/platform/broadcom/bcm2835-unicam.c @@ -1327,6 +1327,7 @@ static int unicam_subdev_enum_frame_size(struct v4l2_subdev *sd, } static int unicam_subdev_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/platform/cadence/cdns-csi2rx.c b/drivers/media/platform/cadence/cdns-csi2rx.c index 1ff2d8f78d5b..de7cce76397a 100644 --- a/drivers/media/platform/cadence/cdns-csi2rx.c +++ b/drivers/media/platform/cadence/cdns-csi2rx.c @@ -166,6 +166,10 @@ static const struct csi2rx_fmt formats[] = { { .code = MEDIA_BUS_FMT_SGBRG10_1X10, .bpp = 10, .max_pixels = 2, }, { .code = MEDIA_BUS_FMT_SGRBG10_1X10, .bpp = 10, .max_pixels = 2, }, { .code = MEDIA_BUS_FMT_SRGGB10_1X10, .bpp = 10, .max_pixels = 2, }, + { .code = MEDIA_BUS_FMT_SBGGR12_1X12, .bpp = 12, .max_pixels = 2, }, + { .code = MEDIA_BUS_FMT_SGBRG12_1X12, .bpp = 12, .max_pixels = 2, }, + { .code = MEDIA_BUS_FMT_SGRBG12_1X12, .bpp = 12, .max_pixels = 2, }, + { .code = MEDIA_BUS_FMT_SRGGB12_1X12, .bpp = 12, .max_pixels = 2, }, { .code = MEDIA_BUS_FMT_RGB565_1X16, .bpp = 16, .max_pixels = 1, }, { .code = MEDIA_BUS_FMT_RGB888_1X24, .bpp = 24, .max_pixels = 1, }, { .code = MEDIA_BUS_FMT_BGR888_1X24, .bpp = 24, .max_pixels = 1, }, @@ -622,6 +626,7 @@ static int csi2rx_set_routing(struct v4l2_subdev *subdev, } static int csi2rx_set_fmt(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/platform/cadence/cdns-csi2tx.c b/drivers/media/platform/cadence/cdns-csi2tx.c index 629b0fa838a2..07277866dca7 100644 --- a/drivers/media/platform/cadence/cdns-csi2tx.c +++ b/drivers/media/platform/cadence/cdns-csi2tx.c @@ -201,6 +201,7 @@ static int csi2tx_get_pad_format(struct v4l2_subdev *subdev, } static int csi2tx_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/platform/intel/pxa_camera.c b/drivers/media/platform/intel/pxa_camera.c index 2269d852ab0e..9e1fdd963b55 100644 --- a/drivers/media/platform/intel/pxa_camera.c +++ b/drivers/media/platform/intel/pxa_camera.c @@ -1819,7 +1819,7 @@ static int pxac_vidioc_try_fmt_vid_cap(struct file *filp, void *priv, pixfmt == V4L2_PIX_FMT_YUV422P ? 4 : 0); v4l2_fill_mbus_format(mf, pix, xlate->code); - ret = sensor_call(pcdev, pad, set_fmt, &pad_state, &format); + ret = sensor_call(pcdev, pad, set_fmt, NULL, &pad_state, &format); if (ret < 0) return ret; @@ -1882,7 +1882,7 @@ static int pxac_vidioc_s_fmt_vid_cap(struct file *filp, void *priv, xlate = pxa_mbus_xlate_by_fourcc(pcdev->user_formats, pix->pixelformat); v4l2_fill_mbus_format(&format.format, pix, xlate->code); - ret = sensor_call(pcdev, pad, set_fmt, NULL, &format); + ret = sensor_call(pcdev, pad, set_fmt, NULL, NULL, &format); if (ret < 0) { dev_warn(pcdev_to_dev(pcdev), "Failed to configure for format %x\n", @@ -2091,7 +2091,7 @@ static int pxa_camera_sensor_bound(struct v4l2_async_notifier *notifier, if (err) goto out; - err = sensor_call(pcdev, pad, set_fmt, NULL, &format); + err = sensor_call(pcdev, pad, set_fmt, NULL, NULL, &format); if (err) goto out_sensor_poweroff; diff --git a/drivers/media/platform/m2m-deinterlace.c b/drivers/media/platform/m2m-deinterlace.c index 1d0e70eaea3d..9dcc4bd6cbdd 100644 --- a/drivers/media/platform/m2m-deinterlace.c +++ b/drivers/media/platform/m2m-deinterlace.c @@ -822,7 +822,7 @@ static int queue_init(void *priv, struct vb2_queue *src_vq, q_data[V4L2_M2M_DST].width = 640; q_data[V4L2_M2M_DST].height = 480; q_data[V4L2_M2M_DST].sizeimage = (640 * 480 * 3) / 2; - q_data[V4L2_M2M_SRC].field = V4L2_FIELD_INTERLACED_TB; + q_data[V4L2_M2M_DST].field = V4L2_FIELD_INTERLACED_TB; return vb2_queue_init(dst_vq); } diff --git a/drivers/media/platform/marvell/Kconfig b/drivers/media/platform/marvell/Kconfig index d31f4730f2a3..ab58175e433c 100644 --- a/drivers/media/platform/marvell/Kconfig +++ b/drivers/media/platform/marvell/Kconfig @@ -22,6 +22,7 @@ config VIDEO_MMP_CAMERA depends on V4L_PLATFORM_DRIVERS depends on I2C && VIDEO_DEV depends on ARCH_MMP || COMPILE_TEST + depends on GPIOLIB || COMPILE_TEST depends on COMMON_CLK select VIDEO_OV7670 if MEDIA_SUBDRV_AUTOSELECT && VIDEO_CAMERA_SENSOR select I2C_GPIO diff --git a/drivers/media/platform/marvell/cafe-driver.c b/drivers/media/platform/marvell/cafe-driver.c index 22034df6cba9..13d8faaabb50 100644 --- a/drivers/media/platform/marvell/cafe-driver.c +++ b/drivers/media/platform/marvell/cafe-driver.c @@ -127,7 +127,7 @@ struct cafe_camera { * Debugging and related. */ #define cam_err(cam, fmt, arg...) \ - dev_err(&(cam)->pdev->dev, fmt, ##arg); + dev_err(&(cam)->pdev->dev, fmt, ##arg) #define cam_warn(cam, fmt, arg...) \ dev_warn(&(cam)->pdev->dev, fmt, ##arg); diff --git a/drivers/media/platform/marvell/mcam-core.c b/drivers/media/platform/marvell/mcam-core.c index b8360d37000a..34809d075a42 100644 --- a/drivers/media/platform/marvell/mcam-core.c +++ b/drivers/media/platform/marvell/mcam-core.c @@ -224,11 +224,11 @@ static void mcam_buffer_done(struct mcam_camera *cam, int frame, * Debugging and related. */ #define cam_err(cam, fmt, arg...) \ - dev_err((cam)->dev, fmt, ##arg); + dev_err((cam)->dev, fmt, ##arg) #define cam_warn(cam, fmt, arg...) \ - dev_warn((cam)->dev, fmt, ##arg); + dev_warn((cam)->dev, fmt, ##arg) #define cam_dbg(cam, fmt, arg...) \ - dev_dbg((cam)->dev, fmt, ##arg); + dev_dbg((cam)->dev, fmt, ##arg) /* @@ -406,6 +406,8 @@ static void mcam_free_dma_bufs(struct mcam_camera *cam) { int i; + cancel_work_sync(&cam->s_bh_work); + for (i = 0; i < cam->nbufs; i++) { dma_free_coherent(cam->dev, cam->dma_buf_size, cam->dma_bufs[i], cam->dma_handles[i]); @@ -1022,7 +1024,7 @@ static int mcam_cam_configure(struct mcam_camera *cam) v4l2_fill_mbus_format(&format.format, &cam->pix_format, cam->mbus_code); ret = sensor_call(cam, core, init, 0); if (ret == 0) - ret = sensor_call(cam, pad, set_fmt, NULL, &format); + ret = sensor_call(cam, pad, set_fmt, NULL, NULL, &format); /* * OV7670 does weird things if flip is set *before* format... */ @@ -1306,7 +1308,6 @@ static int mcam_setup_vb2(struct mcam_camera *cam) break; case B_vmalloc: #ifdef MCAM_MODE_VMALLOC - INIT_WORK(&cam->s_bh_work, mcam_frame_work); vq->ops = &mcam_vb2_ops; vq->mem_ops = &vb2_vmalloc_memops; cam->dma_setup = mcam_ctlr_dma_vmalloc; @@ -1362,7 +1363,7 @@ static int mcam_vidioc_try_fmt_vid_cap(struct file *filp, void *priv, f = mcam_find_format(pix->pixelformat); pix->pixelformat = f->pixelformat; v4l2_fill_mbus_format(&format.format, pix, f->mbus_code); - ret = sensor_call(cam, pad, set_fmt, &pad_state, &format); + ret = sensor_call(cam, pad, set_fmt, NULL, &pad_state, &format); v4l2_fill_pix_format(pix, &format.format); pix->bytesperline = pix->width * f->bpp; switch (f->pixelformat) { @@ -1864,6 +1865,12 @@ int mccic_register(struct mcam_camera *cam) goto out; } +#ifdef MCAM_MODE_VMALLOC + /* Init before sensor bind: armed by IRQ, cancelled on probe-error paths. */ + if (cam->buffer_mode == B_vmalloc) + INIT_WORK(&cam->s_bh_work, mcam_frame_work); +#endif + mutex_init(&cam->s_mutex); cam->state = S_NOTREADY; mcam_set_config_needed(cam, 1); @@ -1922,10 +1929,15 @@ void mccic_shutdown(struct mcam_camera *cam) * take it down again will wedge the machine, which is frowned * upon. */ + mutex_lock(&cam->s_mutex); if (!list_empty(&cam->vdev.fh_list)) { cam_warn(cam, "Removing a device with users!\n"); + /* Stop so the IRQ can't re-arm s_bh_work after the buffers are freed. */ + if (cam->state == S_STREAMING) + mcam_ctlr_stop_dma(cam); sensor_call(cam, core, s_power, 0); } + mutex_unlock(&cam->s_mutex); if (cam->buffer_mode == B_vmalloc) mcam_free_dma_bufs(cam); v4l2_ctrl_handler_free(&cam->ctrl_handler); diff --git a/drivers/media/platform/marvell/mmp-driver.c b/drivers/media/platform/marvell/mmp-driver.c index d3da7ebb4a2b..4c31b62751a1 100644 --- a/drivers/media/platform/marvell/mmp-driver.c +++ b/drivers/media/platform/marvell/mmp-driver.c @@ -25,6 +25,7 @@ #include <linux/list.h> #include <linux/pm.h> #include <linux/clk.h> +#include <linux/clk-provider.h> #include "mcam-core.h" @@ -269,8 +270,8 @@ static int mmpcam_probe(struct platform_device *pdev) /* * Add OF clock provider. */ - ret = of_clk_add_provider(pdev->dev.of_node, of_clk_src_simple_get, - mcam->mclk); + ret = devm_of_clk_add_hw_provider(&pdev->dev, of_clk_hw_simple_get, + &mcam->mclk_hw); if (ret) { dev_err(&pdev->dev, "can't add DT clock provider\n"); goto out; diff --git a/drivers/media/platform/mediatek/mdp/mtk_mdp_ipi.h b/drivers/media/platform/mediatek/mdp/mtk_mdp_ipi.h index b810c96695c8..9744e00b0066 100644 --- a/drivers/media/platform/mediatek/mdp/mtk_mdp_ipi.h +++ b/drivers/media/platform/mediatek/mdp/mtk_mdp_ipi.h @@ -56,7 +56,7 @@ struct mdp_ipi_comm { * @ipi_id : IPI_MDP * @ap_inst : AP mtk_mdp_vpu address * @vpu_inst_addr : VPU MDP instance address - * @status : VPU exeuction result + * @status : VPU execution result */ struct mdp_ipi_comm_ack { uint32_t msg_id; diff --git a/drivers/media/platform/mediatek/vcodec/decoder/vdec_ipi_msg.h b/drivers/media/platform/mediatek/vcodec/decoder/vdec_ipi_msg.h index 47070be2a991..c2c2a3a63dfd 100644 --- a/drivers/media/platform/mediatek/vcodec/decoder/vdec_ipi_msg.h +++ b/drivers/media/platform/mediatek/vcodec/decoder/vdec_ipi_msg.h @@ -53,7 +53,7 @@ struct vdec_ap_ipi_cmd { /** * struct vdec_vpu_ipi_ack - generic VPU to AP ipi command format * @msg_id : vdec_ipi_msgid - * @status : VPU exeuction result + * @status : VPU execution result * @ap_inst_addr : AP video decoder instance address */ struct vdec_vpu_ipi_ack { @@ -98,7 +98,7 @@ struct vdec_ap_ipi_dec_start { /** * struct vdec_vpu_ipi_init_ack - for VPU_IPIMSG_DEC_INIT_ACK * @msg_id : VPU_IPIMSG_DEC_INIT_ACK - * @status : VPU exeuction result + * @status : VPU execution result * @ap_inst_addr : AP vcodec_vpu_inst instance address * @vpu_inst_addr : VPU decoder instance address * @vdec_abi_version: ABI version of the firmware. Kernel can use it to diff --git a/drivers/media/platform/microchip/microchip-csi2dc.c b/drivers/media/platform/microchip/microchip-csi2dc.c index e69292f3b2a9..0368f3e80c0c 100644 --- a/drivers/media/platform/microchip/microchip-csi2dc.c +++ b/drivers/media/platform/microchip/microchip-csi2dc.c @@ -244,6 +244,7 @@ static int csi2dc_get_fmt(struct v4l2_subdev *csi2dc_sd, } static int csi2dc_set_fmt(struct v4l2_subdev *csi2dc_sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *req_fmt) { @@ -735,6 +736,7 @@ static int csi2dc_probe(struct platform_device *pdev) return 0; csi2dc_probe_cleanup_notifier: + v4l2_async_nf_unregister(&csi2dc->notifier); v4l2_async_nf_cleanup(&csi2dc->notifier); csi2dc_probe_cleanup_entity: media_entity_cleanup(&csi2dc->csi2dc_sd.entity); diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c index a7cdc743fda7..963d5fd53b58 100644 --- a/drivers/media/platform/microchip/microchip-isc-base.c +++ b/drivers/media/platform/microchip/microchip-isc-base.c @@ -8,6 +8,7 @@ * Author: Eugen Hristev <eugen.hristev@microchip.com> * */ +#include <linux/bitfield.h> #include <linux/delay.h> #include <linux/interrupt.h> #include <linux/math64.h> @@ -62,17 +63,17 @@ static inline void isc_update_awb_ctrls(struct isc_device *isc) /* In here we set our actual hw pipeline config */ regmap_write(isc->regmap, ISC_WB_O_RGR, - ((ctrls->offset[ISC_HIS_CFG_MODE_R])) | - ((ctrls->offset[ISC_HIS_CFG_MODE_GR]) << 16)); + FIELD_PREP(ISC_WB_O_LO, ctrls->offset[ISC_HIS_CFG_MODE_R]) | + FIELD_PREP(ISC_WB_O_HI, ctrls->offset[ISC_HIS_CFG_MODE_GR])); regmap_write(isc->regmap, ISC_WB_O_BGB, - ((ctrls->offset[ISC_HIS_CFG_MODE_B])) | - ((ctrls->offset[ISC_HIS_CFG_MODE_GB]) << 16)); + FIELD_PREP(ISC_WB_O_LO, ctrls->offset[ISC_HIS_CFG_MODE_B]) | + FIELD_PREP(ISC_WB_O_HI, ctrls->offset[ISC_HIS_CFG_MODE_GB])); regmap_write(isc->regmap, ISC_WB_G_RGR, - ctrls->gain[ISC_HIS_CFG_MODE_R] | - (ctrls->gain[ISC_HIS_CFG_MODE_GR] << 16)); + FIELD_PREP(ISC_WB_G_LO, ctrls->gain[ISC_HIS_CFG_MODE_R]) | + FIELD_PREP(ISC_WB_G_HI, ctrls->gain[ISC_HIS_CFG_MODE_GR])); regmap_write(isc->regmap, ISC_WB_G_BGB, - ctrls->gain[ISC_HIS_CFG_MODE_B] | - (ctrls->gain[ISC_HIS_CFG_MODE_GB] << 16)); + FIELD_PREP(ISC_WB_G_LO, ctrls->gain[ISC_HIS_CFG_MODE_B]) | + FIELD_PREP(ISC_WB_G_HI, ctrls->gain[ISC_HIS_CFG_MODE_GB])); } static inline void isc_reset_awb_ctrls(struct isc_device *isc) @@ -289,8 +290,10 @@ static int isc_configure(struct isc_device *isc) struct regmap *regmap = isc->regmap; u32 pfe_cfg0, dcfg, mask, pipeline; struct isc_subdev_entity *subdev = isc->current_subdev; + int ret; - pfe_cfg0 = isc->config.sd_format->pfe_cfg0_bps; + pfe_cfg0 = FIELD_PREP(ISC_PFE_CFG0_BPS_MASK, + isc->config.sd_format->pfe_cfg0_bps); pipeline = isc->config.bits_pipeline; dcfg = isc->config.dcfg_imode | isc->dcfg; @@ -321,7 +324,15 @@ static int isc_configure(struct isc_device *isc) isc_set_histogram(isc, false); /* Update profile */ - return isc_update_profile(isc); + ret = isc_update_profile(isc); + if (ret) { + /* flush the histogram work before the clocks are gated */ + isc_set_histogram(isc, false); + synchronize_irq(isc->irq); + cancel_work_sync(&isc->awb_work); + } + + return ret; } static int isc_prepare_streaming(struct vb2_queue *vq) @@ -425,6 +436,13 @@ static void isc_stop_streaming(struct vb2_queue *vq) /* Disable DMA interrupt */ regmap_write(isc->regmap, ISC_INTDIS, ISC_INT_DDONE); + isc_set_histogram(isc, false); + + /* let a running IRQ handler finish before the clock is disabled */ + synchronize_irq(isc->irq); + + cancel_work_sync(&isc->awb_work); + pm_runtime_put_sync(isc->dev); /* Disable stream on the sub device */ @@ -1416,7 +1434,7 @@ static void isc_awb_work(struct work_struct *w) /* streaming is not active anymore */ if (isc->stop) { mutex_unlock(&isc->awb_mutex); - return; + goto out_pm_put; } isc_update_profile(isc); @@ -1427,6 +1445,7 @@ static void isc_awb_work(struct work_struct *w) if (ctrls->awb) regmap_write(regmap, ISC_CTRLEN, ISC_CTRL_HISREQ); +out_pm_put: pm_runtime_put_sync(isc->dev); } @@ -1495,20 +1514,24 @@ static int isc_s_awb_ctrl(struct v4l2_ctrl *ctrl) if (ctrl->cluster[ISC_CTRL_GB_OFF]->is_new) ctrls->offset[ISC_HIS_CFG_MODE_GB] = isc->gb_off_ctrl->val; - isc_update_awb_ctrls(isc); - mutex_lock(&isc->awb_mutex); - if (vb2_is_streaming(&isc->vb2_vidq)) { + if (vb2_is_streaming(&isc->vb2_vidq) && !isc->stop) { + unsigned long flags; + /* - * If we are streaming, we can update profile to - * have the new settings in place. + * awb_lock keeps the DMA done IRQ from latching a + * partially written WB pipeline. */ + spin_lock_irqsave(&isc->awb_lock, flags); + isc_update_awb_ctrls(isc); + spin_unlock_irqrestore(&isc->awb_lock, flags); + isc_update_profile(isc); } else { /* - * The auto cluster will activate automatically this - * control. This has to be deactivated when not - * streaming. + * Not streaming: keep the cached values for the next + * stream start and deactivate the cluster-activated + * do_white_balance button. */ v4l2_ctrl_activate(isc->do_wb_ctrl, false); } @@ -1703,7 +1726,6 @@ static void isc_async_unbind(struct v4l2_async_notifier *notifier, { struct isc_device *isc = container_of(notifier->v4l2_dev, struct isc_device, v4l2_dev); - mutex_destroy(&isc->awb_mutex); cancel_work_sync(&isc->awb_work); video_unregister_device(&isc->video_dev); v4l2_ctrl_handler_free(&isc->ctrls.handler); @@ -1767,8 +1789,6 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier) isc->current_subdev = container_of(notifier, struct isc_subdev_entity, notifier); - mutex_init(&isc->lock); - mutex_init(&isc->awb_mutex); init_completion(&isc->comp); @@ -1787,7 +1807,7 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier) ret = vb2_queue_init(q); if (ret < 0) { dev_err(isc->dev, "vb2_queue_init() failed: %d\n", ret); - goto isc_async_complete_err; + return ret; } /* Init video dma queues */ @@ -1798,13 +1818,13 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier) ret = isc_set_default_fmt(isc); if (ret) { dev_err(isc->dev, "Could not set default format\n"); - goto isc_async_complete_err; + return ret; } ret = isc_ctrl_init(isc); if (ret) { dev_err(isc->dev, "Init isc ctrols failed: %d\n", ret); - goto isc_async_complete_err; + return ret; } /* Register video device */ @@ -1824,7 +1844,7 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier) ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); if (ret < 0) { dev_err(isc->dev, "video_register_device failed: %d\n", ret); - goto isc_async_complete_err; + return ret; } ret = isc_scaler_link(isc); @@ -1839,10 +1859,6 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier) isc_async_complete_unregister_device: video_unregister_device(vdev); - -isc_async_complete_err: - mutex_destroy(&isc->awb_mutex); - mutex_destroy(&isc->lock); return ret; } @@ -1860,6 +1876,12 @@ void microchip_isc_subdev_cleanup(struct isc_device *isc) list_for_each_entry(subdev_entity, &isc->subdev_entities, list) { v4l2_async_nf_unregister(&subdev_entity->notifier); v4l2_async_nf_cleanup(&subdev_entity->notifier); + /* + * Release the endpoint reference taken while parsing. It is + * NULL for entities the bind loop already consumed, so this + * only drops the ones left over on an early exit. + */ + of_node_put(subdev_entity->epn); } INIT_LIST_HEAD(&isc->subdev_entities); diff --git a/drivers/media/platform/microchip/microchip-isc-clk.c b/drivers/media/platform/microchip/microchip-isc-clk.c index 24358d804e75..66dc522a6190 100644 --- a/drivers/media/platform/microchip/microchip-isc-clk.c +++ b/drivers/media/platform/microchip/microchip-isc-clk.c @@ -98,15 +98,14 @@ static int isc_clk_is_enabled(struct clk_hw *hw) { struct isc_clk *isc_clk = to_isc_clk(hw); u32 status; - int ret; - ret = pm_runtime_resume_and_get(isc_clk->dev); - if (ret < 0) + /* Runs in atomic context, so must not sleep to resume the ISC. */ + if (pm_runtime_get_if_active(isc_clk->dev) <= 0) return 0; regmap_read(isc_clk->regmap, ISC_CLKSR, &status); - pm_runtime_put_sync(isc_clk->dev); + pm_runtime_put(isc_clk->dev); return status & ISC_CLK(isc_clk->id) ? 1 : 0; } diff --git a/drivers/media/platform/microchip/microchip-isc-regs.h b/drivers/media/platform/microchip/microchip-isc-regs.h index e77e1d9a1db8..fe145b142b82 100644 --- a/drivers/media/platform/microchip/microchip-isc-regs.h +++ b/drivers/media/platform/microchip/microchip-isc-regs.h @@ -31,11 +31,11 @@ #define ISC_PFE_CFG0_MODE_PROGRESSIVE (0x0 << 4) #define ISC_PFE_CFG0_MODE_MASK GENMASK(6, 4) -#define ISC_PFE_CFG0_BPS_EIGHT (0x4 << 28) -#define ISC_PFG_CFG0_BPS_NINE (0x3 << 28) -#define ISC_PFG_CFG0_BPS_TEN (0x2 << 28) -#define ISC_PFG_CFG0_BPS_ELEVEN (0x1 << 28) -#define ISC_PFG_CFG0_BPS_TWELVE (0x0 << 28) +#define ISC_PFE_CFG0_BPS_EIGHT 0x4 +#define ISC_PFE_CFG0_BPS_NINE 0x3 +#define ISC_PFE_CFG0_BPS_TEN 0x2 +#define ISC_PFE_CFG0_BPS_ELEVEN 0x1 +#define ISC_PFE_CFG0_BPS_TWELVE 0x0 #define ISC_PFE_CFG0_BPS_MASK GENMASK(30, 28) #define ISC_PFE_CFG0_COLEN BIT(12) @@ -149,6 +149,12 @@ /* ISC White Balance Gain for B, GB Register */ #define ISC_WB_G_BGB 0x0000006c +/* Each WB offset/gain register packs two 13-bit fields, low and high */ +#define ISC_WB_O_LO GENMASK(12, 0) /* R or B offset [12:0] */ +#define ISC_WB_O_HI GENMASK(28, 16) /* GR or GB offset [28:16] */ +#define ISC_WB_G_LO GENMASK(12, 0) /* R or B gain [12:0] */ +#define ISC_WB_G_HI GENMASK(28, 16) /* GR or GB gain [28:16] */ + /* ISC Color Filter Array Control Register */ #define ISC_CFA_CTRL 0x00000070 diff --git a/drivers/media/platform/microchip/microchip-isc-scaler.c b/drivers/media/platform/microchip/microchip-isc-scaler.c index e83463543e21..192532f87271 100644 --- a/drivers/media/platform/microchip/microchip-isc-scaler.c +++ b/drivers/media/platform/microchip/microchip-isc-scaler.c @@ -46,6 +46,7 @@ static int isc_scaler_get_fmt(struct v4l2_subdev *sd, } static int isc_scaler_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *req_fmt) { @@ -124,6 +125,7 @@ static int isc_scaler_enum_mbus_code(struct v4l2_subdev *sd, } static int isc_scaler_g_sel(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/platform/microchip/microchip-isc.h b/drivers/media/platform/microchip/microchip-isc.h index ad4e98a1dd8f..d7bcd74efff9 100644 --- a/drivers/media/platform/microchip/microchip-isc.h +++ b/drivers/media/platform/microchip/microchip-isc.h @@ -62,7 +62,9 @@ struct isc_subdev_entity { * @mbus_code: V4L2 media bus format code. * @cfa_baycfg: If this format is RAW BAYER, indicate the type of bayer. this is either BGBG, RGRG, etc. - * @pfe_cfg0_bps: Number of hardware data lines connected to the ISC + * @pfe_cfg0_bps: ISC_PFE_CFG0 BPS field value (e.g. ISC_PFE_CFG0_BPS_EIGHT), + written into PFE_CFG0 with FIELD_PREP(ISC_PFE_CFG0_BPS_MASK) + at configure time. * @raw: If the format is raw bayer. */ @@ -287,6 +289,7 @@ struct isc_device { u32 dcfg; struct device *dev; + int irq; struct v4l2_device v4l2_dev; struct video_device video_dev; diff --git a/drivers/media/platform/microchip/microchip-sama5d2-isc.c b/drivers/media/platform/microchip/microchip-sama5d2-isc.c index 66d3d7891991..5e41eee45dbd 100644 --- a/drivers/media/platform/microchip/microchip-sama5d2-isc.c +++ b/drivers/media/platform/microchip/microchip-sama5d2-isc.c @@ -30,6 +30,7 @@ #include <linux/interrupt.h> #include <linux/math64.h> #include <linux/module.h> +#include <linux/mutex.h> #include <linux/of.h> #include <linux/of_graph.h> #include <linux/platform_device.h> @@ -146,49 +147,49 @@ static struct isc_format sama5d2_formats_list[] = { { .fourcc = V4L2_PIX_FMT_SBGGR10, .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, - .cfa_baycfg = ISC_BAY_CFG_RGRG, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TEN, + .cfa_baycfg = ISC_BAY_CFG_BGBG, }, { .fourcc = V4L2_PIX_FMT_SGBRG10, .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TEN, .cfa_baycfg = ISC_BAY_CFG_GBGB, }, { .fourcc = V4L2_PIX_FMT_SGRBG10, .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TEN, .cfa_baycfg = ISC_BAY_CFG_GRGR, }, { .fourcc = V4L2_PIX_FMT_SRGGB10, .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TEN, .cfa_baycfg = ISC_BAY_CFG_RGRG, }, { .fourcc = V4L2_PIX_FMT_SBGGR12, .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TWELVE, .cfa_baycfg = ISC_BAY_CFG_BGBG, }, { .fourcc = V4L2_PIX_FMT_SGBRG12, .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TWELVE, .cfa_baycfg = ISC_BAY_CFG_GBGB, }, { .fourcc = V4L2_PIX_FMT_SGRBG12, .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TWELVE, .cfa_baycfg = ISC_BAY_CFG_GRGR, }, { .fourcc = V4L2_PIX_FMT_SRGGB12, .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TWELVE, .cfa_baycfg = ISC_BAY_CFG_RGRG, }, { @@ -209,7 +210,7 @@ static struct isc_format sama5d2_formats_list[] = { { .fourcc = V4L2_PIX_FMT_Y10, .mbus_code = MEDIA_BUS_FMT_Y10_1X10, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TEN, }, }; @@ -356,28 +357,30 @@ static int isc_parse_dt(struct device *dev, struct isc_device *isc) struct device_node *epn; struct isc_subdev_entity *subdev_entity; unsigned int flags; + int ret; INIT_LIST_HEAD(&isc->subdev_entities); for_each_endpoint_of_node(np, epn) { struct v4l2_fwnode_endpoint v4l2_epn = { .bus_type = 0 }; - int ret; ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(epn), &v4l2_epn); if (ret) { - of_node_put(epn); dev_err(dev, "Could not parse the endpoint\n"); - return -EINVAL; + of_node_put(epn); + ret = -EINVAL; + goto err_cleanup; } subdev_entity = devm_kzalloc(dev, sizeof(*subdev_entity), GFP_KERNEL); if (!subdev_entity) { of_node_put(epn); - return -ENOMEM; + ret = -ENOMEM; + goto err_cleanup; } - subdev_entity->epn = epn; + subdev_entity->epn = of_node_get(epn); flags = v4l2_epn.bus.parallel.flags; @@ -398,6 +401,11 @@ static int isc_parse_dt(struct device *dev, struct isc_device *isc) } return 0; + +err_cleanup: + list_for_each_entry(subdev_entity, &isc->subdev_entities, list) + of_node_put(subdev_entity->epn); + return ret; } static int microchip_isc_probe(struct platform_device *pdev) @@ -417,6 +425,14 @@ static int microchip_isc_probe(struct platform_device *pdev) platform_set_drvdata(pdev, isc); isc->dev = dev; + ret = devm_mutex_init(dev, &isc->lock); + if (ret) + return ret; + + ret = devm_mutex_init(dev, &isc->awb_mutex); + if (ret) + return ret; + io_base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(io_base)) return PTR_ERR(io_base); @@ -432,6 +448,8 @@ static int microchip_isc_probe(struct platform_device *pdev) if (irq < 0) return irq; + isc->irq = irq; + ret = devm_request_irq(dev, irq, microchip_isc_interrupt, 0, "microchip-sama5d2-isc", isc); if (ret < 0) { diff --git a/drivers/media/platform/microchip/microchip-sama7g5-isc.c b/drivers/media/platform/microchip/microchip-sama7g5-isc.c index 7383341ec51d..9e78ea07b013 100644 --- a/drivers/media/platform/microchip/microchip-sama7g5-isc.c +++ b/drivers/media/platform/microchip/microchip-sama7g5-isc.c @@ -33,6 +33,7 @@ #include <linux/interrupt.h> #include <linux/math64.h> #include <linux/module.h> +#include <linux/mutex.h> #include <linux/of.h> #include <linux/of_graph.h> #include <linux/platform_device.h> @@ -155,49 +156,49 @@ static struct isc_format sama7g5_formats_list[] = { { .fourcc = V4L2_PIX_FMT_SBGGR10, .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, - .cfa_baycfg = ISC_BAY_CFG_RGRG, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TEN, + .cfa_baycfg = ISC_BAY_CFG_BGBG, }, { .fourcc = V4L2_PIX_FMT_SGBRG10, .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TEN, .cfa_baycfg = ISC_BAY_CFG_GBGB, }, { .fourcc = V4L2_PIX_FMT_SGRBG10, .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TEN, .cfa_baycfg = ISC_BAY_CFG_GRGR, }, { .fourcc = V4L2_PIX_FMT_SRGGB10, .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TEN, .cfa_baycfg = ISC_BAY_CFG_RGRG, }, { .fourcc = V4L2_PIX_FMT_SBGGR12, .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TWELVE, .cfa_baycfg = ISC_BAY_CFG_BGBG, }, { .fourcc = V4L2_PIX_FMT_SGBRG12, .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TWELVE, .cfa_baycfg = ISC_BAY_CFG_GBGB, }, { .fourcc = V4L2_PIX_FMT_SGRBG12, .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TWELVE, .cfa_baycfg = ISC_BAY_CFG_GRGR, }, { .fourcc = V4L2_PIX_FMT_SRGGB12, .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TWELVE, .cfa_baycfg = ISC_BAY_CFG_RGRG, }, { @@ -223,7 +224,7 @@ static struct isc_format sama7g5_formats_list[] = { { .fourcc = V4L2_PIX_FMT_Y10, .mbus_code = MEDIA_BUS_FMT_Y10_1X10, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TEN, }, }; @@ -340,6 +341,7 @@ static int xisc_parse_dt(struct device *dev, struct isc_device *isc) struct isc_subdev_entity *subdev_entity; unsigned int flags; bool mipi_mode; + int ret; INIT_LIST_HEAD(&isc->subdev_entities); @@ -347,23 +349,24 @@ static int xisc_parse_dt(struct device *dev, struct isc_device *isc) for_each_endpoint_of_node(np, epn) { struct v4l2_fwnode_endpoint v4l2_epn = { .bus_type = 0 }; - int ret; ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(epn), &v4l2_epn); if (ret) { - of_node_put(epn); dev_err(dev, "Could not parse the endpoint\n"); - return -EINVAL; + of_node_put(epn); + ret = -EINVAL; + goto err_cleanup; } subdev_entity = devm_kzalloc(dev, sizeof(*subdev_entity), GFP_KERNEL); if (!subdev_entity) { of_node_put(epn); - return -ENOMEM; + ret = -ENOMEM; + goto err_cleanup; } - subdev_entity->epn = epn; + subdev_entity->epn = of_node_get(epn); flags = v4l2_epn.bus.parallel.flags; @@ -387,6 +390,11 @@ static int xisc_parse_dt(struct device *dev, struct isc_device *isc) } return 0; + +err_cleanup: + list_for_each_entry(subdev_entity, &isc->subdev_entities, list) + of_node_put(subdev_entity->epn); + return ret; } static int microchip_xisc_probe(struct platform_device *pdev) @@ -406,6 +414,14 @@ static int microchip_xisc_probe(struct platform_device *pdev) platform_set_drvdata(pdev, isc); isc->dev = dev; + ret = devm_mutex_init(dev, &isc->lock); + if (ret) + return ret; + + ret = devm_mutex_init(dev, &isc->awb_mutex); + if (ret) + return ret; + io_base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(io_base)) return PTR_ERR(io_base); @@ -421,6 +437,8 @@ static int microchip_xisc_probe(struct platform_device *pdev) if (irq < 0) return irq; + isc->irq = irq; + ret = devm_request_irq(dev, irq, microchip_isc_interrupt, 0, "microchip-sama7g5-xisc", isc); if (ret < 0) { diff --git a/drivers/media/platform/nuvoton/npcm-video.c b/drivers/media/platform/nuvoton/npcm-video.c index 52505af35c08..a6737c65d9b2 100644 --- a/drivers/media/platform/nuvoton/npcm-video.c +++ b/drivers/media/platform/nuvoton/npcm-video.c @@ -120,6 +120,7 @@ struct npcm_video { struct list_head buffers; struct mutex buffer_lock; /* buffer list lock */ + int irq; unsigned long flags; unsigned int sequence; @@ -1486,6 +1487,7 @@ static int npcm_video_start_streaming(struct vb2_queue *q, unsigned int count) } set_bit(VIDEO_STREAMING, &video->flags); + enable_irq(video->irq); return 0; } @@ -1494,6 +1496,7 @@ static void npcm_video_stop_streaming(struct vb2_queue *q) struct npcm_video *video = vb2_get_drv_priv(q); struct regmap *vcd = video->vcd_regmap; + disable_irq(video->irq); clear_bit(VIDEO_STREAMING, &video->flags); regmap_write(vcd, VCD_INTE, 0); regmap_write(vcd, VCD_STAT, VCD_STAT_CLEAR); @@ -1707,25 +1710,24 @@ static int npcm_video_init(struct npcm_video *video) dev_err(dev, "Failed to find VCD IRQ\n"); return -ENODEV; } + video->irq = irq; rc = devm_request_threaded_irq(dev, irq, NULL, npcm_video_irq, - IRQF_ONESHOT, DEVICE_NAME, video); + IRQF_ONESHOT | IRQF_NO_AUTOEN, DEVICE_NAME, video); if (rc < 0) { dev_err(dev, "Failed to request IRQ %d\n", irq); return rc; } - of_reserved_mem_device_init(dev); + devm_of_reserved_mem_device_init(dev); rc = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); if (rc) { dev_err(dev, "Failed to set DMA mask\n"); - of_reserved_mem_device_release(dev); return rc; } rc = npcm_video_ece_init(video); if (rc) { - of_reserved_mem_device_release(dev); dev_err(dev, "Failed to initialize ECE\n"); return rc; } @@ -1789,13 +1791,11 @@ static int npcm_video_probe(struct platform_device *pdev) rc = npcm_video_setup_video(video); if (rc) - goto err_release_mem; + goto err_free; dev_info(video->dev, "NPCM video driver probed\n"); return 0; -err_release_mem: - of_reserved_mem_device_release(&pdev->dev); err_free: kfree(video); return rc; @@ -1807,14 +1807,12 @@ static void npcm_video_remove(struct platform_device *pdev) struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); struct npcm_video *video = to_npcm_video(v4l2_dev); - video_unregister_device(&video->vdev); - vb2_queue_release(&video->queue); + vb2_video_unregister_device(&video->vdev); v4l2_ctrl_handler_free(&video->ctrl_handler); v4l2_device_unregister(v4l2_dev); if (video->ece.enable) npcm_video_ece_stop(video); kfree(video); - of_reserved_mem_device_release(dev); } static const struct of_device_id npcm_video_match[] = { diff --git a/drivers/media/platform/nxp/Kconfig b/drivers/media/platform/nxp/Kconfig index 40e3436669e2..5ff263fe7c6f 100644 --- a/drivers/media/platform/nxp/Kconfig +++ b/drivers/media/platform/nxp/Kconfig @@ -28,6 +28,22 @@ config VIDEO_IMX8MQ_MIPI_CSI2 Video4Linux2 driver for the MIPI CSI-2 receiver found on the i.MX8MQ SoC. +config VIDEO_IMX95_CSI_FORMATTER + tristate "NXP i.MX95 CSI Pixel Formatter driver" + depends on ARCH_MXC || COMPILE_TEST + depends on PM_CLK + depends on VIDEO_DEV + select MEDIA_CONTROLLER + select MFD_SYSCON + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + help + This driver provides support for the CSI Pixel Formatter found on + i.MX95 series SoCs. This module unpacks the pixels received from the + CSI-2 interface and reformats them to meet pixel link requirements. + + Say Y here to enable CSI Pixel Formatter module for i.MX95 SoC. + config VIDEO_IMX_MIPI_CSIS tristate "NXP MIPI CSI-2 CSIS receiver found on i.MX7 and i.MX8 models" depends on ARCH_MXC || COMPILE_TEST diff --git a/drivers/media/platform/nxp/Makefile b/drivers/media/platform/nxp/Makefile index 4d90eb713652..6410115d870e 100644 --- a/drivers/media/platform/nxp/Makefile +++ b/drivers/media/platform/nxp/Makefile @@ -6,6 +6,7 @@ obj-y += imx8-isi/ obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-media-csi.o obj-$(CONFIG_VIDEO_IMX8MQ_MIPI_CSI2) += imx8mq-mipi-csi2.o +obj-$(CONFIG_VIDEO_IMX95_CSI_FORMATTER) += imx95-csi-formatter.o obj-$(CONFIG_VIDEO_IMX_MIPI_CSIS) += imx-mipi-csis.o obj-$(CONFIG_VIDEO_IMX_PXP) += imx-pxp.o obj-$(CONFIG_VIDEO_MX2_EMMAPRP) += mx2_emmaprp.o diff --git a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c index 725e94152884..e17085935435 100644 --- a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c +++ b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c @@ -1590,15 +1590,15 @@ static void mxc_jpeg_device_run(void *priv) dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); if (!src_buf || !dst_buf) { dev_err(dev, "Null src or dst buf\n"); - goto end; + goto job_finish; } q_data_cap = mxc_jpeg_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); if (!q_data_cap) - goto end; + goto buf_finish; q_data_out = mxc_jpeg_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); if (!q_data_out) - goto end; + goto buf_finish; src_buf->sequence = q_data_out->sequence++; dst_buf->sequence = q_data_cap->sequence++; @@ -1636,11 +1636,11 @@ static void mxc_jpeg_device_run(void *priv) ctx->slot = mxc_get_free_slot(&jpeg->slot_data); if (ctx->slot < 0) { dev_err(dev, "No more free slots\n"); - goto end; + goto buf_finish; } if (!mxc_jpeg_alloc_slot_data(jpeg)) { dev_err(dev, "Cannot allocate slot data\n"); - goto end; + goto buf_finish; } mxc_jpeg_enable_slot(reg, ctx->slot); @@ -1660,8 +1660,17 @@ static void mxc_jpeg_device_run(void *priv) mxc_jpeg_dec_mode_go(dev, reg); } schedule_delayed_work(&ctx->task_timer, msecs_to_jiffies(hw_timeout)); -end: + + spin_unlock_irqrestore(&ctx->mxc_jpeg->hw_lock, flags); + return; +buf_finish: + v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR); +job_finish: spin_unlock_irqrestore(&ctx->mxc_jpeg->hw_lock, flags); + v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); } static int mxc_jpeg_decoder_cmd(struct file *file, void *priv, @@ -1798,6 +1807,8 @@ static void mxc_jpeg_stop_streaming(struct vb2_queue *q) dev_dbg(ctx->mxc_jpeg->dev, "Stop streaming ctx=%p", ctx); + cancel_delayed_work_sync(&ctx->task_timer); + /* Release all active buffers */ for (;;) { if (V4L2_TYPE_IS_OUTPUT(q->type)) diff --git a/drivers/media/platform/nxp/imx-mipi-csis.c b/drivers/media/platform/nxp/imx-mipi-csis.c index 5f9691d76434..1b9012dbae39 100644 --- a/drivers/media/platform/nxp/imx-mipi-csis.c +++ b/drivers/media/platform/nxp/imx-mipi-csis.c @@ -1104,6 +1104,7 @@ static int mipi_csis_enum_mbus_code(struct v4l2_subdev *sd, } static int mipi_csis_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *sdformat) { @@ -1225,7 +1226,7 @@ static int mipi_csis_init_state(struct v4l2_subdev *sd, V4L2_MAP_QUANTIZATION_DEFAULT(false, fmt.format.colorspace, fmt.format.ycbcr_enc); - return mipi_csis_set_fmt(sd, state, &fmt); + return mipi_csis_set_fmt(sd, NULL, state, &fmt); } static int mipi_csis_log_status(struct v4l2_subdev *sd) diff --git a/drivers/media/platform/nxp/imx7-media-csi.c b/drivers/media/platform/nxp/imx7-media-csi.c index 22c0cbdc92bf..f06b03967aef 100644 --- a/drivers/media/platform/nxp/imx7-media-csi.c +++ b/drivers/media/platform/nxp/imx7-media-csi.c @@ -1890,6 +1890,7 @@ static void imx7_csi_try_fmt(struct v4l2_subdev *sd, } static int imx7_csi_set_fmt(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/platform/nxp/imx8-isi/imx8-isi-crossbar.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c index 7bb1335f2110..8d50e5fb3b13 100644 --- a/drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c @@ -260,6 +260,7 @@ static int mxc_isi_crossbar_enum_mbus_code(struct v4l2_subdev *sd, } static int mxc_isi_crossbar_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-pipe.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-pipe.c index 934f7b356258..a12e7445073c 100644 --- a/drivers/media/platform/nxp/imx8-isi/imx8-isi-pipe.c +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-pipe.c @@ -524,6 +524,7 @@ static int mxc_isi_pipe_enum_mbus_code(struct v4l2_subdev *sd, } static int mxc_isi_pipe_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -619,6 +620,7 @@ static int mxc_isi_pipe_set_fmt(struct v4l2_subdev *sd, } static int mxc_isi_pipe_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -678,6 +680,7 @@ static int mxc_isi_pipe_get_selection(struct v4l2_subdev *sd, } static int mxc_isi_pipe_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/platform/nxp/imx8mq-mipi-csi2.c b/drivers/media/platform/nxp/imx8mq-mipi-csi2.c index 04ebed8a0493..17756b7fe1dd 100644 --- a/drivers/media/platform/nxp/imx8mq-mipi-csi2.c +++ b/drivers/media/platform/nxp/imx8mq-mipi-csi2.c @@ -597,6 +597,7 @@ static int imx8mq_mipi_csi_enum_mbus_code(struct v4l2_subdev *sd, } static int imx8mq_mipi_csi_set_fmt(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/platform/nxp/imx95-csi-formatter.c b/drivers/media/platform/nxp/imx95-csi-formatter.c new file mode 100644 index 000000000000..37dfcefbf0d1 --- /dev/null +++ b/drivers/media/platform/nxp/imx95-csi-formatter.c @@ -0,0 +1,759 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2025 NXP + */ + +#include <linux/bits.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm_clock.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> + +#include <media/mipi-csi2.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-mc.h> +#include <media/v4l2-subdev.h> + +/* CSI Pixel Formatter registers map */ + +#define CSI_VC_INTERLACED_LINE_CNT(vc) (0x00 + (vc) * 0x04) +#define INTERLACED_ODD_LINE_CNT_SET(x) FIELD_PREP(GENMASK(13, 0), (x)) +#define INTERLACED_EVEN_LINE_CNT_SET(x) FIELD_PREP(GENMASK(29, 16), (x)) + +#define CSI_VC_INTERLACED_CTRL 0x20 + +#define CSI_VC_INTERLACED_ERR 0x24 +#define CSI_VC_ERR_MASK GENMASK(7, 0) +#define CSI_VC_ERR(vc) BIT((vc)) + +#define CSI_VC_YUV420_FIRST_LINE_EVEN 0x28 +#define YUV420_FIRST_LINE_EVEN(vc) BIT((vc)) + +#define CSI_RAW32_CTRL 0x30 +#define CSI_VC_RAW32_MODE(vc) BIT((vc)) +#define CSI_VC_RAW32_SWAP_MODE(vc) BIT((vc) + 8) + +#define CSI_STREAM_FENCING_CTRL 0x34 +#define CSI_VC_STREAM_FENCING(vc) BIT((vc)) +#define CSI_VC_STREAM_FENCING_RST(vc) BIT((vc) + 8) + +#define CSI_STREAM_FENCING_STS 0x38 +#define CSI_STREAM_FENCING_STS_MASK GENMASK(7, 0) + +#define CSI_VC_NON_PIXEL_DATA_TYPE(vc) (0x40 + (vc) * 0x04) + +#define CSI_VC_PIXEL_DATA_CTRL(vc) (0x60 + (vc) * 0x04) +#define NEW_VC(vc) FIELD_PREP(GENMASK(3, 1), vc) +#define REROUTE_VC_ENABLE BIT(0) + +#define CSI_VC_ROUTE_PIXEL_DATA_TYPE(vc) (0x80 + (vc) * 0x04) + +#define CSI_VC_NON_PIXEL_DATA_CTRL(vc) (0xa0 + (vc) * 0x04) + +#define CSI_VC_PIXEL_DATA_TYPE(vc) (0xc0 + (vc) * 0x04) + +#define CSI_VC_PIXEL_DATA_TYPE_ERR(vc) (0xe0 + (vc) * 0x04) + +#define CSI_FORMATTER_PAD_SINK 0 +#define CSI_FORMATTER_PAD_SOURCE 1 +#define CSI_FORMATTER_PAD_NUM 2 + +#define CSI_FORMATTER_VC_NUM 8 /* Number of virtual channels */ + +struct csi_formatter_pix_format { + u32 code; + u32 data_type; +}; + +struct csi_formatter { + struct device *dev; + struct regmap *regs; + u32 reg_offset; + + struct v4l2_subdev sd; + struct v4l2_async_notifier notifier; + struct media_pad pads[CSI_FORMATTER_PAD_NUM]; + + struct v4l2_subdev *remote_sd; + u32 remote_pad; + + u64 enabled_streams; +}; + +struct csi_formatter_dt_index { + u8 dtype; + u8 index; +}; + +/* + * The index corresponds to the bit index in the register that enables + * the data type of pixel data transported by the Formatter. + */ +static const struct csi_formatter_dt_index csi_formatter_dt_to_index_map[] = { + { .dtype = MIPI_CSI2_DT_YUV420_8B, .index = 0 }, + { .dtype = MIPI_CSI2_DT_YUV420_8B_LEGACY, .index = 2 }, + { .dtype = MIPI_CSI2_DT_YUV422_8B, .index = 6 }, + { .dtype = MIPI_CSI2_DT_RGB444, .index = 8 }, + { .dtype = MIPI_CSI2_DT_RGB555, .index = 9 }, + { .dtype = MIPI_CSI2_DT_RGB565, .index = 10 }, + { .dtype = MIPI_CSI2_DT_RGB666, .index = 11 }, + { .dtype = MIPI_CSI2_DT_RGB888, .index = 12 }, + { .dtype = MIPI_CSI2_DT_RAW6, .index = 16 }, + { .dtype = MIPI_CSI2_DT_RAW7, .index = 17 }, + { .dtype = MIPI_CSI2_DT_RAW8, .index = 18 }, + { .dtype = MIPI_CSI2_DT_RAW10, .index = 19 }, + { .dtype = MIPI_CSI2_DT_RAW12, .index = 20 }, + { .dtype = MIPI_CSI2_DT_RAW14, .index = 21 }, + { .dtype = MIPI_CSI2_DT_RAW16, .index = 22 }, +}; + +static const struct csi_formatter_pix_format csi_formatter_formats[] = { + /* YUV formats */ + { MEDIA_BUS_FMT_UYVY8_1X16, MIPI_CSI2_DT_YUV422_8B }, + /* RGB formats */ + { MEDIA_BUS_FMT_RGB565_1X16, MIPI_CSI2_DT_RGB565 }, + { MEDIA_BUS_FMT_RGB888_1X24, MIPI_CSI2_DT_RGB888 }, + /* RAW (Bayer and greyscale) formats */ + { MEDIA_BUS_FMT_SBGGR8_1X8, MIPI_CSI2_DT_RAW8 }, + { MEDIA_BUS_FMT_SGBRG8_1X8, MIPI_CSI2_DT_RAW8 }, + { MEDIA_BUS_FMT_SGRBG8_1X8, MIPI_CSI2_DT_RAW8 }, + { MEDIA_BUS_FMT_SRGGB8_1X8, MIPI_CSI2_DT_RAW8 }, + { MEDIA_BUS_FMT_Y8_1X8, MIPI_CSI2_DT_RAW8 }, + { MEDIA_BUS_FMT_SBGGR10_1X10, MIPI_CSI2_DT_RAW10 }, + { MEDIA_BUS_FMT_SGBRG10_1X10, MIPI_CSI2_DT_RAW10 }, + { MEDIA_BUS_FMT_SGRBG10_1X10, MIPI_CSI2_DT_RAW10 }, + { MEDIA_BUS_FMT_SRGGB10_1X10, MIPI_CSI2_DT_RAW10 }, + { MEDIA_BUS_FMT_Y10_1X10, MIPI_CSI2_DT_RAW10 }, + { MEDIA_BUS_FMT_SBGGR12_1X12, MIPI_CSI2_DT_RAW12 }, + { MEDIA_BUS_FMT_SGBRG12_1X12, MIPI_CSI2_DT_RAW12 }, + { MEDIA_BUS_FMT_SGRBG12_1X12, MIPI_CSI2_DT_RAW12 }, + { MEDIA_BUS_FMT_SRGGB12_1X12, MIPI_CSI2_DT_RAW12 }, + { MEDIA_BUS_FMT_Y12_1X12, MIPI_CSI2_DT_RAW12 }, + { MEDIA_BUS_FMT_SBGGR14_1X14, MIPI_CSI2_DT_RAW14 }, + { MEDIA_BUS_FMT_SGBRG14_1X14, MIPI_CSI2_DT_RAW14 }, + { MEDIA_BUS_FMT_SGRBG14_1X14, MIPI_CSI2_DT_RAW14 }, + { MEDIA_BUS_FMT_SRGGB14_1X14, MIPI_CSI2_DT_RAW14 }, + { MEDIA_BUS_FMT_SBGGR16_1X16, MIPI_CSI2_DT_RAW16 }, + { MEDIA_BUS_FMT_SGBRG16_1X16, MIPI_CSI2_DT_RAW16 }, + { MEDIA_BUS_FMT_SGRBG16_1X16, MIPI_CSI2_DT_RAW16 }, + { MEDIA_BUS_FMT_SRGGB16_1X16, MIPI_CSI2_DT_RAW16 }, +}; + +static const struct v4l2_mbus_framefmt formatter_default_fmt = { + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .width = 1920U, + .height = 1080U, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_SMPTE170M, + .xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(V4L2_COLORSPACE_SMPTE170M), + .ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(V4L2_COLORSPACE_SMPTE170M), + .quantization = V4L2_QUANTIZATION_LIM_RANGE, +}; + +static const struct csi_formatter_pix_format *csi_formatter_find_format(u32 code) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(csi_formatter_formats); i++) { + if (code == csi_formatter_formats[i].code) + return &csi_formatter_formats[i]; + } + + return NULL; +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +static inline struct csi_formatter *sd_to_formatter(struct v4l2_subdev *sdev) +{ + return container_of(sdev, struct csi_formatter, sd); +} + +static int __csi_formatter_subdev_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_krouting *routing) +{ + 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, + &formatter_default_fmt); +} + +static int csi_formatter_subdev_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct v4l2_subdev_route routes[] = { + { + .sink_pad = CSI_FORMATTER_PAD_SINK, + .sink_stream = 0, + .source_pad = CSI_FORMATTER_PAD_SOURCE, + .source_stream = 0, + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }, + }; + + struct v4l2_subdev_krouting routing = { + .num_routes = ARRAY_SIZE(routes), + .routes = routes, + }; + + return __csi_formatter_subdev_set_routing(sd, sd_state, &routing); +} + +static int csi_formatter_subdev_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad == CSI_FORMATTER_PAD_SOURCE) { + struct v4l2_mbus_framefmt *fmt; + + if (code->index > 0) + return -EINVAL; + + fmt = v4l2_subdev_state_get_format(sd_state, code->pad, + code->stream); + code->code = fmt->code; + return 0; + } + + if (code->index >= ARRAY_SIZE(csi_formatter_formats)) + return -EINVAL; + + code->code = csi_formatter_formats[code->index].code; + + return 0; +} + +static int csi_formatter_subdev_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 v4l2_mbus_framefmt *fmt; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && + media_entity_is_streaming(&sd->entity)) + return -EBUSY; + + if (format->pad == CSI_FORMATTER_PAD_SOURCE) + return v4l2_subdev_get_fmt(sd, sd_state, format); + + if (!csi_formatter_find_format(format->format.code)) + format->format.code = csi_formatter_formats[0].code; + + v4l_bound_align_image(&format->format.width, 1, 0xffff, 2, + &format->format.height, 1, 0xffff, 0, 0); + + /* TODO: Add interlaced format support */ + format->format.field = V4L2_FIELD_NONE; + + fmt = v4l2_subdev_state_get_format(sd_state, format->pad, + format->stream); + *fmt = format->format; + + /* Propagate the format from sink stream to source stream */ + fmt = v4l2_subdev_state_get_opposite_stream_format(sd_state, format->pad, + format->stream); + *fmt = format->format; + + return 0; +} + +static int csi_formatter_subdev_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + if (which == V4L2_SUBDEV_FORMAT_ACTIVE && + media_entity_is_streaming(&sd->entity)) + return -EBUSY; + + return __csi_formatter_subdev_set_routing(sd, state, routing); +} + +static u8 csi_formatter_get_index_by_dt(struct csi_formatter *formatter, + u8 data_type) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(csi_formatter_dt_to_index_map); ++i) { + const struct csi_formatter_dt_index *entry = + &csi_formatter_dt_to_index_map[i]; + + if (data_type == entry->dtype) + return entry->index; + } + + dev_WARN(formatter->dev, "Unsupported data type 0x%x, using default\n", + data_type); + + return csi_formatter_dt_to_index_map[0].index; +} + +static int csi_formatter_get_vc(struct csi_formatter *formatter, + struct v4l2_mbus_frame_desc *fd, + unsigned int stream) +{ + struct v4l2_mbus_frame_desc_entry *entry = NULL; + u8 vc; + + for (unsigned int i = 0; i < fd->num_entries; ++i) { + if (fd->entry[i].stream == stream) { + entry = &fd->entry[i]; + break; + } + } + + if (!entry) { + dev_err(formatter->dev, + "No frame desc entry for stream %u\n", stream); + return -EPIPE; + } + + vc = entry->bus.csi2.vc; + + if (vc >= CSI_FORMATTER_VC_NUM) { + dev_err(formatter->dev, "Invalid virtual channel %u\n", vc); + return -EPIPE; + } + + return vc; +} + +static int csi_formatter_get_frame_desc(struct csi_formatter *formatter, + struct v4l2_mbus_frame_desc *fd) +{ + int ret; + + ret = v4l2_subdev_call(formatter->remote_sd, pad, get_frame_desc, + formatter->remote_pad, fd); + if (ret < 0 && ret != -ENOIOCTLCMD) { + dev_err(formatter->dev, "Failed to get frame desc: %d\n", ret); + return ret; + } + + /* + * If the source doesn't implement .get_frame_desc(), assume a single + * stream on VC 0. fd is zero-initialized, only set the fields that have + * a non-zero value. + */ + if (ret == -ENOIOCTLCMD) { + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2; + fd->num_entries = 1; + } + + return 0; +} + +static void csi_formatter_stop_stream(struct csi_formatter *formatter, + struct v4l2_subdev_state *state, + u64 stream_mask) +{ + const struct csi_formatter_pix_format *pix_fmt; + struct v4l2_mbus_frame_desc fd = {}; + struct v4l2_subdev_route *route; + struct v4l2_mbus_framefmt *fmt; + unsigned int reg; + unsigned int mask; + int vc; + int ret; + + ret = csi_formatter_get_frame_desc(formatter, &fd); + if (ret) + return; + + for_each_active_route(&state->routing, route) { + if (!(stream_mask & BIT_ULL(route->source_stream))) + continue; + + vc = csi_formatter_get_vc(formatter, &fd, route->sink_stream); + if (vc < 0) + continue; + + fmt = v4l2_subdev_state_get_format(state, route->sink_pad, + route->sink_stream); + + pix_fmt = csi_formatter_find_format(fmt->code); + if (WARN_ON(!pix_fmt)) + continue; + + reg = CSI_VC_PIXEL_DATA_TYPE(vc) + formatter->reg_offset; + mask = BIT(csi_formatter_get_index_by_dt(formatter, + pix_fmt->data_type)); + + /* Clear the data type bit to disable this VC */ + regmap_clear_bits(formatter->regs, reg, mask); + } +} + +static int csi_formatter_start_stream(struct csi_formatter *formatter, + struct v4l2_subdev_state *state, + u64 stream_mask) +{ + const struct csi_formatter_pix_format *pix_fmt; + struct v4l2_subdev_route *route; + struct v4l2_mbus_framefmt *fmt; + struct v4l2_mbus_frame_desc fd = {}; + u64 configured_streams = 0; + unsigned int reg; + unsigned int mask; + int vc; + int ret; + + ret = csi_formatter_get_frame_desc(formatter, &fd); + if (ret) + return ret; + + for_each_active_route(&state->routing, route) { + if (!(stream_mask & BIT_ULL(route->source_stream))) + continue; + + vc = csi_formatter_get_vc(formatter, &fd, route->sink_stream); + if (vc < 0) { + ret = vc; + goto err_cleanup; + } + + fmt = v4l2_subdev_state_get_format(state, route->sink_pad, + route->sink_stream); + + pix_fmt = csi_formatter_find_format(fmt->code); + if (WARN_ON(!pix_fmt)) { + ret = -EINVAL; + goto err_cleanup; + } + + reg = CSI_VC_PIXEL_DATA_TYPE(vc) + formatter->reg_offset; + mask = BIT(csi_formatter_get_index_by_dt(formatter, + pix_fmt->data_type)); + + /* Set the data type bit to enable this VC */ + regmap_set_bits(formatter->regs, reg, mask); + + configured_streams |= BIT_ULL(route->source_stream); + } + + return 0; + +err_cleanup: + csi_formatter_stop_stream(formatter, state, configured_streams); + return ret; +} + +static int csi_formatter_subdev_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct csi_formatter *formatter = sd_to_formatter(sd); + struct device *dev = formatter->dev; + u64 sink_streams; + int ret; + + sink_streams = v4l2_subdev_state_xlate_streams(state, + CSI_FORMATTER_PAD_SOURCE, + CSI_FORMATTER_PAD_SINK, + &streams_mask); + if (!sink_streams || !streams_mask) + return -EINVAL; + + if (!formatter->enabled_streams) { + ret = pm_runtime_resume_and_get(formatter->dev); + if (ret < 0) { + dev_err(dev, "Failed to resume runtime PM: %d\n", ret); + return ret; + } + } + + ret = csi_formatter_start_stream(formatter, state, streams_mask); + if (ret) + goto err_runtime_put; + + ret = v4l2_subdev_enable_streams(formatter->remote_sd, + formatter->remote_pad, + sink_streams); + if (ret) + goto err_stop_stream; + + formatter->enabled_streams |= streams_mask; + + return 0; + +err_stop_stream: + csi_formatter_stop_stream(formatter, state, streams_mask); +err_runtime_put: + if (!formatter->enabled_streams) + pm_runtime_put_autosuspend(formatter->dev); + return ret; +} + +static int csi_formatter_subdev_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct csi_formatter *formatter = sd_to_formatter(sd); + u64 sink_streams; + int ret; + + sink_streams = v4l2_subdev_state_xlate_streams(state, + CSI_FORMATTER_PAD_SOURCE, + CSI_FORMATTER_PAD_SINK, + &streams_mask); + if (!sink_streams || !streams_mask) + return -EINVAL; + + ret = v4l2_subdev_disable_streams(formatter->remote_sd, formatter->remote_pad, + sink_streams); + if (ret) + dev_err(formatter->dev, "Failed to disable streams: %d\n", ret); + + csi_formatter_stop_stream(formatter, state, streams_mask); + + formatter->enabled_streams &= ~streams_mask; + + if (!formatter->enabled_streams) + pm_runtime_put_autosuspend(formatter->dev); + + return ret; +} + +static const struct v4l2_subdev_pad_ops formatter_subdev_pad_ops = { + .enum_mbus_code = csi_formatter_subdev_enum_mbus_code, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = csi_formatter_subdev_set_fmt, + .get_frame_desc = v4l2_subdev_get_frame_desc_passthrough, + .set_routing = csi_formatter_subdev_set_routing, + .enable_streams = csi_formatter_subdev_enable_streams, + .disable_streams = csi_formatter_subdev_disable_streams, +}; + +static const struct v4l2_subdev_ops formatter_subdev_ops = { + .pad = &formatter_subdev_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops formatter_internal_ops = { + .init_state = csi_formatter_subdev_init_state, +}; + +/* ----------------------------------------------------------------------------- + * Media entity operations + */ + +static const struct media_entity_operations formatter_entity_ops = { + .link_validate = v4l2_subdev_link_validate, + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1, +}; + +static int csi_formatter_subdev_init(struct csi_formatter *formatter) +{ + struct v4l2_subdev *sd = &formatter->sd; + int ret; + + v4l2_subdev_init(sd, &formatter_subdev_ops); + + strscpy(sd->name, dev_name(formatter->dev), sizeof(sd->name)); + sd->internal_ops = &formatter_internal_ops; + + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_STREAMS; + sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; + sd->entity.ops = &formatter_entity_ops; + sd->dev = formatter->dev; + + formatter->pads[CSI_FORMATTER_PAD_SINK].flags = MEDIA_PAD_FL_SINK + | MEDIA_PAD_FL_MUST_CONNECT; + formatter->pads[CSI_FORMATTER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&sd->entity, CSI_FORMATTER_PAD_NUM, + formatter->pads); + if (ret) { + dev_err(formatter->dev, "Failed to init pads\n"); + return ret; + } + + ret = v4l2_subdev_init_finalize(sd); + if (ret) + media_entity_cleanup(&sd->entity); + + return ret; +} + +static inline struct csi_formatter * +notifier_to_csi_formatter(struct v4l2_async_notifier *n) +{ + return container_of(n, struct csi_formatter, notifier); +} + +static int csi_formatter_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_connection *asc) +{ + const unsigned int link_flags = MEDIA_LNK_FL_IMMUTABLE + | MEDIA_LNK_FL_ENABLED; + struct csi_formatter *formatter = notifier_to_csi_formatter(notifier); + struct v4l2_subdev *sdev = &formatter->sd; + struct media_pad *sink = &sdev->entity.pads[CSI_FORMATTER_PAD_SINK]; + int ret; + + formatter->remote_sd = sd; + + ret = v4l2_create_fwnode_links_to_pad(sd, sink, link_flags); + if (ret < 0) + return ret; + + formatter->remote_pad = media_pad_remote_pad_first(sink)->index; + + return 0; +} + +static const struct v4l2_async_notifier_operations formatter_notify_ops = { + .bound = csi_formatter_notify_bound, +}; + +static int csi_formatter_async_register(struct csi_formatter *formatter) +{ + struct device *dev = formatter->dev; + struct v4l2_async_connection *asc; + int ret; + + struct fwnode_handle *ep __free(fwnode_handle) = + fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!ep) + return -ENOTCONN; + + v4l2_async_subdev_nf_init(&formatter->notifier, &formatter->sd); + + asc = v4l2_async_nf_add_fwnode_remote(&formatter->notifier, ep, + struct v4l2_async_connection); + if (IS_ERR(asc)) { + ret = PTR_ERR(asc); + goto err_cleanup_notifier; + } + + formatter->notifier.ops = &formatter_notify_ops; + + ret = v4l2_async_nf_register(&formatter->notifier); + if (ret) + goto err_cleanup_notifier; + + ret = v4l2_async_register_subdev(&formatter->sd); + if (ret) + goto err_unregister_notifier; + + return 0; + +err_unregister_notifier: + v4l2_async_nf_unregister(&formatter->notifier); +err_cleanup_notifier: + v4l2_async_nf_cleanup(&formatter->notifier); + return ret; +} + +static void csi_formatter_async_unregister(struct csi_formatter *formatter) +{ + v4l2_async_unregister_subdev(&formatter->sd); + v4l2_async_nf_unregister(&formatter->notifier); + v4l2_async_nf_cleanup(&formatter->notifier); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(csi_formatter_pm_ops, + pm_clk_suspend, pm_clk_resume, NULL); + +static int csi_formatter_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct csi_formatter *formatter; + int ret; + + formatter = devm_kzalloc(dev, sizeof(*formatter), GFP_KERNEL); + if (!formatter) + return -ENOMEM; + + formatter->dev = dev; + + formatter->regs = syscon_node_to_regmap(dev->parent->of_node); + if (IS_ERR(formatter->regs)) + return dev_err_probe(dev, PTR_ERR(formatter->regs), + "Failed to get csi formatter regmap\n"); + + ret = of_property_read_u32(dev->of_node, "reg", &formatter->reg_offset); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to get csi formatter reg property\n"); + + ret = devm_pm_clk_create(dev); + if (ret) + return ret; + + ret = of_pm_clk_add_clks(dev); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to add clocks\n"); + + ret = csi_formatter_subdev_init(formatter); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to initialize formatter subdev\n"); + + platform_set_drvdata(pdev, formatter); + + /* Enable runtime PM with autosuspend. */ + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + ret = devm_pm_runtime_enable(dev); + if (ret) + goto err_pm_disable; + + ret = csi_formatter_async_register(formatter); + if (ret < 0) { + dev_err_probe(dev, ret, "Failed to register async subdevice\n"); + goto err_pm_disable; + } + + return 0; + +err_pm_disable: + pm_runtime_dont_use_autosuspend(dev); + v4l2_subdev_cleanup(&formatter->sd); + media_entity_cleanup(&formatter->sd.entity); + return ret; +} + +static void csi_formatter_remove(struct platform_device *pdev) +{ + struct csi_formatter *formatter = platform_get_drvdata(pdev); + + csi_formatter_async_unregister(formatter); + + pm_runtime_dont_use_autosuspend(&pdev->dev); + pm_runtime_suspend(&pdev->dev); + + v4l2_subdev_cleanup(&formatter->sd); + media_entity_cleanup(&formatter->sd.entity); +} + +static const struct of_device_id csi_formatter_of_match[] = { + { .compatible = "fsl,imx95-csi-formatter" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, csi_formatter_of_match); + +static struct platform_driver csi_formatter_device_driver = { + .driver = { + .name = "csi-pixel-formatter", + .of_match_table = csi_formatter_of_match, + .pm = pm_ptr(&csi_formatter_pm_ops), + }, + .probe = csi_formatter_probe, + .remove = csi_formatter_remove, +}; + +module_platform_driver(csi_formatter_device_driver); + +MODULE_AUTHOR("NXP Semiconductor, Inc."); +MODULE_DESCRIPTION("NXP i.MX95 CSI Pixel Formatter driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/qcom/camss/camss-csid.c b/drivers/media/platform/qcom/camss/camss-csid.c index 48459b46a981..a00791b00feb 100644 --- a/drivers/media/platform/qcom/camss/camss-csid.c +++ b/drivers/media/platform/qcom/camss/camss-csid.c @@ -987,6 +987,7 @@ static int csid_get_format(struct v4l2_subdev *sd, * Return -EINVAL or zero on success */ static int csid_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -1036,7 +1037,7 @@ static int csid_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) } }; - return csid_set_format(sd, fh ? fh->state : NULL, &format); + return csid_set_format(sd, NULL, fh ? fh->state : NULL, &format); } /* diff --git a/drivers/media/platform/qcom/camss/camss-csiphy.c b/drivers/media/platform/qcom/camss/camss-csiphy.c index 539ac4888b60..0dd50f3879d9 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy.c +++ b/drivers/media/platform/qcom/camss/camss-csiphy.c @@ -503,6 +503,7 @@ static int csiphy_get_format(struct v4l2_subdev *sd, * Return -EINVAL or zero on success */ static int csiphy_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -555,7 +556,7 @@ static int csiphy_init_formats(struct v4l2_subdev *sd, } }; - return csiphy_set_format(sd, fh ? fh->state : NULL, &format); + return csiphy_set_format(sd, NULL, fh ? fh->state : NULL, &format); } static bool __printf(2, 3) diff --git a/drivers/media/platform/qcom/camss/camss-ispif.c b/drivers/media/platform/qcom/camss/camss-ispif.c index 20ccd7b1f11f..f266f8532ecc 100644 --- a/drivers/media/platform/qcom/camss/camss-ispif.c +++ b/drivers/media/platform/qcom/camss/camss-ispif.c @@ -1037,6 +1037,7 @@ static int ispif_get_format(struct v4l2_subdev *sd, * Return -EINVAL or zero on success */ static int ispif_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -1085,7 +1086,7 @@ static int ispif_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) } }; - return ispif_set_format(sd, fh ? fh->state : NULL, &format); + return ispif_set_format(sd, NULL, fh ? fh->state : NULL, &format); } /* diff --git a/drivers/media/platform/qcom/camss/camss-tpg.c b/drivers/media/platform/qcom/camss/camss-tpg.c index c5b75132add4..5764a60fa34c 100644 --- a/drivers/media/platform/qcom/camss/camss-tpg.c +++ b/drivers/media/platform/qcom/camss/camss-tpg.c @@ -292,6 +292,7 @@ static int tpg_get_format(struct v4l2_subdev *sd, } static int tpg_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -322,7 +323,7 @@ static int tpg_init_formats(struct v4l2_subdev *sd, } }; - return tpg_set_format(sd, fh ? fh->state : NULL, &format); + return tpg_set_format(sd, NULL, fh ? fh->state : NULL, &format); } static int tpg_s_ctrl(struct v4l2_ctrl *ctrl) diff --git a/drivers/media/platform/qcom/camss/camss-vfe.c b/drivers/media/platform/qcom/camss/camss-vfe.c index 319d19158988..c14d97a131f6 100644 --- a/drivers/media/platform/qcom/camss/camss-vfe.c +++ b/drivers/media/platform/qcom/camss/camss-vfe.c @@ -1562,6 +1562,7 @@ static int vfe_get_format(struct v4l2_subdev *sd, } static int vfe_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel); @@ -1574,6 +1575,7 @@ static int vfe_set_selection(struct v4l2_subdev *sd, * Return -EINVAL or zero on success */ static int vfe_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -1608,7 +1610,7 @@ static int vfe_set_format(struct v4l2_subdev *sd, sel.target = V4L2_SEL_TGT_COMPOSE; sel.r.width = fmt->format.width; sel.r.height = fmt->format.height; - ret = vfe_set_selection(sd, sd_state, &sel); + ret = vfe_set_selection(sd, ci, sd_state, &sel); if (ret < 0) return ret; } @@ -1625,6 +1627,7 @@ static int vfe_set_format(struct v4l2_subdev *sd, * Return -EINVAL or zero on success */ static int vfe_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1695,6 +1698,7 @@ static int vfe_get_selection(struct v4l2_subdev *sd, * Return -EINVAL or zero on success */ static int vfe_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1721,7 +1725,7 @@ static int vfe_set_selection(struct v4l2_subdev *sd, crop.pad = MSM_VFE_PAD_SRC; crop.target = V4L2_SEL_TGT_CROP; crop.r = *rect; - ret = vfe_set_selection(sd, sd_state, &crop); + ret = vfe_set_selection(sd, ci, sd_state, &crop); } else if (sel->target == V4L2_SEL_TGT_CROP && sel->pad == MSM_VFE_PAD_SRC) { struct v4l2_subdev_format fmt = { 0 }; @@ -1742,7 +1746,7 @@ static int vfe_set_selection(struct v4l2_subdev *sd, fmt.format.width = rect->width; fmt.format.height = rect->height; - ret = vfe_set_format(sd, sd_state, &fmt); + ret = vfe_set_format(sd, ci, sd_state, &fmt); } else { ret = -EINVAL; } @@ -1772,7 +1776,7 @@ static int vfe_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) } }; - return vfe_set_format(sd, fh ? fh->state : NULL, &format); + return vfe_set_format(sd, NULL, fh ? fh->state : NULL, &format); } /* diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index 2123f6388e3d..23f3cc30a15a 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -4793,30 +4793,23 @@ static int camss_parse_endpoint_node(struct device *dev, static int camss_parse_ports(struct camss *camss) { struct device *dev = camss->dev; - struct fwnode_handle *fwnode = dev_fwnode(dev), *ep; + struct fwnode_handle *fwnode = dev_fwnode(dev); int ret; - fwnode_graph_for_each_endpoint(fwnode, ep) { + fwnode_graph_for_each_endpoint_scoped(fwnode, ep) { struct camss_async_subdev *csd; csd = v4l2_async_nf_add_fwnode_remote(&camss->notifier, ep, typeof(*csd)); - if (IS_ERR(csd)) { - ret = PTR_ERR(csd); - goto err_cleanup; - } + if (IS_ERR(csd)) + return PTR_ERR(csd); ret = camss_parse_endpoint_node(dev, ep, csd); if (ret < 0) - goto err_cleanup; + return ret; } return 0; - -err_cleanup: - fwnode_handle_put(ep); - - return ret; } /* diff --git a/drivers/media/platform/raspberrypi/rp1-cfe/csi2.c b/drivers/media/platform/raspberrypi/rp1-cfe/csi2.c index 104908afbf41..66d0dde6d840 100644 --- a/drivers/media/platform/raspberrypi/rp1-cfe/csi2.c +++ b/drivers/media/platform/raspberrypi/rp1-cfe/csi2.c @@ -404,6 +404,7 @@ static int csi2_init_state(struct v4l2_subdev *sd, } static int csi2_pad_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/platform/raspberrypi/rp1-cfe/pisp-fe.c b/drivers/media/platform/raspberrypi/rp1-cfe/pisp-fe.c index 05762b1be2bc..ea21fb1f60cf 100644 --- a/drivers/media/platform/raspberrypi/rp1-cfe/pisp-fe.c +++ b/drivers/media/platform/raspberrypi/rp1-cfe/pisp-fe.c @@ -429,6 +429,7 @@ static int pisp_fe_init_state(struct v4l2_subdev *sd, } static int pisp_fe_pad_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/platform/renesas/rcar-csi2.c b/drivers/media/platform/renesas/rcar-csi2.c index 6635f5782175..4851fe649d8a 100644 --- a/drivers/media/platform/renesas/rcar-csi2.c +++ b/drivers/media/platform/renesas/rcar-csi2.c @@ -702,6 +702,17 @@ static const struct rcar_csi2_format rcar_csi2_formats[] = { }, }; +static const struct v4l2_mbus_framefmt rcar_csi2_default_fmt = { + .width = 1920, + .height = 1080, + .code = MEDIA_BUS_FMT_RGB888_1X24, + .colorspace = V4L2_COLORSPACE_SRGB, + .field = V4L2_FIELD_NONE, + .ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT, + .quantization = V4L2_QUANTIZATION_DEFAULT, + .xfer_func = V4L2_XFER_FUNC_DEFAULT, +}; + static const struct rcar_csi2_format *rcsi2_code_to_fmt(unsigned int code) { unsigned int i; @@ -773,7 +784,7 @@ struct rcar_csi2 { int channel_vc[4]; - int stream_count; + u64 enabled_sink_streams_mask; bool cphy; unsigned short lanes; @@ -1023,17 +1034,24 @@ static int rcsi2_calc_mbps(struct rcar_csi2 *priv, */ freq = v4l2_get_link_freq(remote_pad, 0, 0); if (freq < 0) { + const struct v4l2_subdev_route *route; const struct rcar_csi2_format *format; const struct v4l2_mbus_framefmt *fmt; unsigned int lanes; unsigned int bpp; int ret; + if (state->routing.num_routes != 1) + return -EINVAL; + ret = rcsi2_get_active_lanes(priv, &lanes); if (ret) return ret; - fmt = v4l2_subdev_state_get_format(state, RCAR_CSI2_SINK); + route = &state->routing.routes[0]; + + fmt = v4l2_subdev_state_get_format(state, route->sink_pad, + route->sink_stream); if (!fmt) return -EINVAL; @@ -1062,52 +1080,93 @@ static int rcsi2_calc_mbps(struct rcar_csi2 *priv, static int rcsi2_start_receiver_gen3(struct rcar_csi2 *priv, struct v4l2_subdev_state *state) { - const struct rcar_csi2_format *format; - u32 phycnt, vcdt = 0, vcdt2 = 0, fld = 0; - const struct v4l2_mbus_framefmt *fmt; + u32 phycnt, vcdt = 0, vcdt2 = 0; + u32 fld = FLD_DET_SEL(1); + struct v4l2_mbus_frame_desc source_fd; + struct v4l2_subdev_route *route; unsigned int lanes; - unsigned int i; int mbps, ret; + u8 ch = 0; + + ret = v4l2_subdev_call(priv->remote, pad, get_frame_desc, + priv->remote_pad, &source_fd); + if (ret && ret != -ENOIOCTLCMD) + return ret; - /* Use the format on the sink pad to compute the receiver config. */ - fmt = v4l2_subdev_state_get_format(state, RCAR_CSI2_SINK); + if (ret == -ENOIOCTLCMD) { + /* Create a fallback source_fd */ + struct v4l2_mbus_frame_desc *fd = &source_fd; + const struct v4l2_subdev_route *route; + const struct rcar_csi2_format *format; + struct v4l2_mbus_framefmt *fmt; - dev_dbg(priv->dev, "Input size (%ux%u%c)\n", - fmt->width, fmt->height, - fmt->field == V4L2_FIELD_NONE ? 'p' : 'i'); + if (state->routing.num_routes != 1) + return -EINVAL; - /* Code is validated in set_fmt. */ - format = rcsi2_code_to_fmt(fmt->code); - if (!format) - return -EINVAL; + route = &state->routing.routes[0]; - /* - * Enable all supported CSI-2 channels with virtual channel and - * data type matching. - * - * NOTE: It's not possible to get individual datatype for each - * source virtual channel. Once this is possible in V4L2 - * it should be used here. - */ - for (i = 0; i < priv->info->num_channels; i++) { + fmt = v4l2_subdev_state_get_format(state, route->sink_pad, + route->sink_stream); + if (!fmt) + return -EINVAL; + + format = rcsi2_code_to_fmt(fmt->code); + if (!format) + return -EINVAL; + + memset(fd, 0, sizeof(*fd)); + + fd->num_entries = 1; + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2; + fd->entry[0].stream = 0; + fd->entry[0].pixelcode = fmt->code; + fd->entry[0].bus.csi2.vc = 0; + fd->entry[0].bus.csi2.dt = format->datatype; + } + + for_each_active_route(&state->routing, route) { + const struct v4l2_mbus_frame_desc_entry *source_entry = NULL; + const struct v4l2_mbus_framefmt *fmt; + unsigned int i; u32 vcdt_part; - if (priv->channel_vc[i] < 0) - continue; + for (i = 0; i < source_fd.num_entries; i++) { + if (source_fd.entry[i].stream == route->sink_stream) { + source_entry = &source_fd.entry[i]; + break; + } + } + + if (!source_entry) { + dev_err(priv->dev, + "Failed to find stream from source frame desc\n"); + return -EPIPE; + } - vcdt_part = VCDT_SEL_VC(priv->channel_vc[i]) | VCDT_VCDTN_EN | - VCDT_SEL_DTN_ON | VCDT_SEL_DT(format->datatype); + vcdt_part = VCDT_SEL_VC(source_entry->bus.csi2.vc) | + VCDT_VCDTN_EN | VCDT_SEL_DTN_ON | + VCDT_SEL_DT(source_entry->bus.csi2.dt); /* Store in correct reg and offset. */ - if (i < 2) - vcdt |= vcdt_part << ((i % 2) * 16); + if (ch < 2) + vcdt |= vcdt_part << ((ch % 2) * 16); else - vcdt2 |= vcdt_part << ((i % 2) * 16); - } + vcdt2 |= vcdt_part << ((ch % 2) * 16); + + fmt = v4l2_subdev_state_get_format(state, RCAR_CSI2_SINK, + route->sink_stream); + if (!fmt) + return -EINVAL; - if (fmt->field == V4L2_FIELD_ALTERNATE) - fld = FLD_DET_SEL(1) | FLD_FLD_EN(3) | FLD_FLD_EN(2) | - FLD_FLD_EN(1) | FLD_FLD_EN(0); + dev_dbg(priv->dev, "Input size (%ux%u%c)\n", + fmt->width, fmt->height, + fmt->field == V4L2_FIELD_NONE ? 'p' : 'i'); + + if (fmt->field == V4L2_FIELD_ALTERNATE) + fld |= FLD_FLD_EN(ch); + + ch++; + } /* * Get the number of active data lanes inspecting the remote mbus @@ -1822,20 +1881,12 @@ static int rcsi2_start(struct rcar_csi2 *priv, struct v4l2_subdev_state *state) return ret; } - ret = v4l2_subdev_enable_streams(priv->remote, priv->remote_pad, - BIT_ULL(0)); - if (ret) { - rcsi2_enter_standby(priv); - return ret; - } - return 0; } static void rcsi2_stop(struct rcar_csi2 *priv) { rcsi2_enter_standby(priv); - v4l2_subdev_disable_streams(priv->remote, priv->remote_pad, BIT_ULL(0)); } static int rcsi2_enable_streams(struct v4l2_subdev *sd, @@ -1843,21 +1894,32 @@ static int rcsi2_enable_streams(struct v4l2_subdev *sd, u64 source_streams_mask) { struct rcar_csi2 *priv = sd_to_csi2(sd); - int ret = 0; - - if (source_streams_mask != 1) - return -EINVAL; + u64 sink_streams; + int ret; if (!priv->remote) return -ENODEV; - if (priv->stream_count == 0) { + if (!priv->enabled_sink_streams_mask) { ret = rcsi2_start(priv, state); if (ret) return ret; } - priv->stream_count += 1; + sink_streams = v4l2_subdev_state_xlate_streams(state, + source_pad, + RCAR_CSI2_SINK, + &source_streams_mask); + + ret = v4l2_subdev_enable_streams(priv->remote, priv->remote_pad, + sink_streams); + if (ret) { + if (!priv->enabled_sink_streams_mask) + rcsi2_stop(priv); + return ret; + } + + priv->enabled_sink_streams_mask |= sink_streams; return ret; } @@ -1867,28 +1929,36 @@ static int rcsi2_disable_streams(struct v4l2_subdev *sd, u32 source_pad, u64 source_streams_mask) { struct rcar_csi2 *priv = sd_to_csi2(sd); - int ret = 0; - - if (source_streams_mask != 1) - return -EINVAL; + u64 sink_streams; + int ret; if (!priv->remote) return -ENODEV; - if (priv->stream_count == 1) + sink_streams = v4l2_subdev_state_xlate_streams(state, + source_pad, + RCAR_CSI2_SINK, + &source_streams_mask); + + if (priv->enabled_sink_streams_mask == sink_streams) rcsi2_stop(priv); - priv->stream_count -= 1; + ret = v4l2_subdev_disable_streams(priv->remote, priv->remote_pad, + sink_streams); + if (ret) + return ret; - return ret; + priv->enabled_sink_streams_mask &= ~sink_streams; + + return 0; } static int rcsi2_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { - struct rcar_csi2 *priv = sd_to_csi2(sd); - unsigned int num_pads = rcsi2_num_pads(priv); + struct v4l2_mbus_framefmt *fmt; if (format->pad > RCAR_CSI2_SINK) return v4l2_subdev_get_fmt(sd, state, format); @@ -1896,21 +1966,136 @@ static int rcsi2_set_pad_format(struct v4l2_subdev *sd, if (!rcsi2_code_to_fmt(format->format.code)) format->format.code = rcar_csi2_formats[0].code; - *v4l2_subdev_state_get_format(state, format->pad) = format->format; + /* Set sink format. */ + fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + /* Propagate the format to the source pad. */ + fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; - /* Propagate the format to the source pads. */ - for (unsigned int i = RCAR_CSI2_SOURCE_VC0; i < num_pads; i++) - *v4l2_subdev_state_get_format(state, i) = format->format; + *fmt = format->format; return 0; } +static int rcsi2_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + struct rcar_csi2 *priv = sd_to_csi2(sd); + int ret; + + if (priv->info->use_isp) { + ret = v4l2_subdev_routing_validate(sd, routing, + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); + } else { + ret = v4l2_subdev_routing_validate(sd, routing, + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1 | + V4L2_SUBDEV_ROUTING_NO_SOURCE_MULTIPLEXING); + } + + if (ret) + return ret; + + ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, + &rcar_csi2_default_fmt); + if (ret) + return ret; + + return 0; +} + +static int rcsi2_get_frame_desc_fallback(struct v4l2_subdev *sd, + unsigned int pad, + struct v4l2_mbus_frame_desc *fd) +{ + struct v4l2_subdev_route *route; + const struct rcar_csi2_format *format; + struct v4l2_subdev_state *state; + struct v4l2_mbus_framefmt *fmt; + int ret = 0; + + state = v4l2_subdev_lock_and_get_active_state(sd); + + if (state->routing.num_routes != 1) { + ret = -EINVAL; + goto out; + } + + route = &state->routing.routes[0]; + + if (route->source_pad != pad) { + ret = -EINVAL; + goto out; + } + + fmt = v4l2_subdev_state_get_format(state, route->sink_pad, + route->sink_stream); + if (!fmt) { + ret = -EINVAL; + goto out; + } + + format = rcsi2_code_to_fmt(fmt->code); + if (!format) { + ret = -EINVAL; + goto out; + } + + fd->num_entries = 1; + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2; + fd->entry[0].stream = route->source_stream; + fd->entry[0].pixelcode = fmt->code; + fd->entry[0].bus.csi2.vc = 0; + fd->entry[0].bus.csi2.dt = format->datatype; + +out: + v4l2_subdev_unlock_state(state); + + return ret; +} + +static int rcsi2_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_frame_desc *fd) +{ + struct rcar_csi2 *priv = sd_to_csi2(sd); + int ret; + + /* + * Providing a frame descriptor on the source pad only makes sense on + * Gen4. On Gen3 the CSI-2 IP is used to demux the CSI streams to each + * VIN instance, while on Gen4 this job is done by the Channel Selector + * of the ISP, and that needs the frame descriptors. So WARN if someone + * calls this on Gen3, as it indicates a driver bug. + */ + if (WARN_ON(!priv->info->use_isp)) + return -ENOTTY; + + if (WARN_ON(pad != RCAR_CSI2_SOURCE_VC0)) + return -EINVAL; + + ret = v4l2_subdev_get_frame_desc_passthrough(sd, pad, fd); + if (ret == -ENOIOCTLCMD) + ret = rcsi2_get_frame_desc_fallback(sd, pad, fd); + return ret; +} + static const struct v4l2_subdev_pad_ops rcar_csi2_pad_ops = { .enable_streams = rcsi2_enable_streams, .disable_streams = rcsi2_disable_streams, .set_fmt = rcsi2_set_pad_format, .get_fmt = v4l2_subdev_get_fmt, + + .set_routing = rcsi2_set_routing, + .get_frame_desc = rcsi2_get_frame_desc, }; static const struct v4l2_subdev_ops rcar_csi2_subdev_ops = { @@ -1920,24 +2105,23 @@ static const struct v4l2_subdev_ops rcar_csi2_subdev_ops = { static int rcsi2_init_state(struct v4l2_subdev *sd, struct v4l2_subdev_state *state) { - struct rcar_csi2 *priv = sd_to_csi2(sd); - unsigned int num_pads = rcsi2_num_pads(priv); - - static const struct v4l2_mbus_framefmt rcar_csi2_default_fmt = { - .width = 1920, - .height = 1080, - .code = MEDIA_BUS_FMT_RGB888_1X24, - .colorspace = V4L2_COLORSPACE_SRGB, - .field = V4L2_FIELD_NONE, - .ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT, - .quantization = V4L2_QUANTIZATION_DEFAULT, - .xfer_func = V4L2_XFER_FUNC_DEFAULT, + static struct v4l2_subdev_route routes[] = { + { + .sink_pad = RCAR_CSI2_SINK, + .sink_stream = 0, + .source_pad = RCAR_CSI2_SOURCE_VC0, + .source_stream = 0, + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }, }; - for (unsigned int i = RCAR_CSI2_SINK; i < num_pads; i++) - *v4l2_subdev_state_get_format(state, i) = rcar_csi2_default_fmt; + static const struct v4l2_subdev_krouting routing = { + .num_routes = ARRAY_SIZE(routes), + .routes = routes, + }; - return 0; + return v4l2_subdev_set_routing_with_fmt(sd, state, &routing, + &rcar_csi2_default_fmt); } static const struct v4l2_subdev_internal_ops rcar_csi2_internal_ops = { @@ -1971,14 +2155,44 @@ static irqreturn_t rcsi2_irq_thread(int irq, void *data) { struct v4l2_subdev_state *state; struct rcar_csi2 *priv = data; + int ret; state = v4l2_subdev_lock_and_get_active_state(&priv->subdev); + if (!priv->enabled_sink_streams_mask) + goto out; + rcsi2_stop(priv); + + ret = v4l2_subdev_disable_streams(priv->remote, priv->remote_pad, + priv->enabled_sink_streams_mask); + if (ret) { + dev_warn(priv->dev, + "Error recovery: failed to disable streams: %d\n", + ret); + goto out; + } + usleep_range(1000, 2000); - if (rcsi2_start(priv, state)) - dev_warn(priv->dev, "Failed to restart CSI-2 receiver\n"); + ret = rcsi2_start(priv, state); + if (ret) { + dev_warn(priv->dev, + "Error recovery: failed to start CSI-2 receiver: %d\n", + ret); + goto out; + } + + ret = v4l2_subdev_enable_streams(priv->remote, priv->remote_pad, + priv->enabled_sink_streams_mask); + if (ret) { + dev_warn(priv->dev, + "Error recovery: failed to start streams: %d\n", + ret); + goto out; + } + +out: v4l2_subdev_unlock_state(state); return IRQ_HANDLED; @@ -2569,8 +2783,6 @@ static int rcsi2_probe(struct platform_device *pdev) priv->dev = &pdev->dev; - priv->stream_count = 0; - ret = rcsi2_probe_resources(priv, pdev); if (ret) { dev_err(priv->dev, "Failed to get resources\n"); @@ -2590,7 +2802,8 @@ static int rcsi2_probe(struct platform_device *pdev) v4l2_set_subdevdata(&priv->subdev, &pdev->dev); snprintf(priv->subdev.name, sizeof(priv->subdev.name), "%s %s", KBUILD_MODNAME, dev_name(&pdev->dev)); - priv->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + priv->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_STREAMS; priv->subdev.entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; priv->subdev.entity.ops = &rcar_csi2_entity_ops; diff --git a/drivers/media/platform/renesas/rcar-isp/csisp.c b/drivers/media/platform/renesas/rcar-isp/csisp.c index 53ce47020d17..635eeacb3b19 100644 --- a/drivers/media/platform/renesas/rcar-isp/csisp.c +++ b/drivers/media/platform/renesas/rcar-isp/csisp.c @@ -41,6 +41,9 @@ #define ISPCS_DT_CODE03_EN0 BIT(7) #define ISPCS_DT_CODE03_DT0(dt) ((dt) & 0x3f) +/* ISP has 12 channels, of which channels 4 to 11 are connected to VINs */ +#define ISPCS_NUM_CHANNELS 12 + struct rcar_isp_format { u32 code; unsigned int datatype; @@ -123,6 +126,17 @@ static const struct rcar_isp_format rcar_isp_formats[] = { }, }; +static const struct v4l2_mbus_framefmt risp_default_fmt = { + .width = 1920, + .height = 1080, + .code = MEDIA_BUS_FMT_RGB888_1X24, + .colorspace = V4L2_COLORSPACE_SRGB, + .field = V4L2_FIELD_NONE, + .ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT, + .quantization = V4L2_QUANTIZATION_DEFAULT, + .xfer_func = V4L2_XFER_FUNC_DEFAULT, +}; + static const struct rcar_isp_format *risp_code_to_fmt(unsigned int code) { unsigned int i; @@ -214,24 +228,82 @@ static void risp_power_off(struct rcar_isp *isp) pm_runtime_put(isp->dev); } -static int risp_start(struct rcar_isp *isp, struct v4l2_subdev_state *state) +static int risp_configure_routing(struct rcar_isp *isp, + struct v4l2_subdev_state *state) { - const struct v4l2_mbus_framefmt *fmt; - const struct rcar_isp_format *format; - unsigned int vc; - u32 sel_csi = 0; + struct v4l2_mbus_frame_desc source_fd; + struct v4l2_subdev_route *route; int ret; - fmt = v4l2_subdev_state_get_format(state, RCAR_ISP_SINK); - if (!fmt) - return -EINVAL; + ret = v4l2_subdev_call(isp->remote, pad, get_frame_desc, + isp->remote_pad, &source_fd); + if (ret) + return ret; - format = risp_code_to_fmt(fmt->code); - if (!format) { - dev_err(isp->dev, "Unsupported bus format\n"); - return -EINVAL; + /* Clear the channel registers */ + for (unsigned int ch = 0; ch < ISPCS_NUM_CHANNELS; ++ch) { + risp_write_cs(isp, ISPCS_FILTER_ID_CH_REG(ch), 0); + risp_write_cs(isp, ISPCS_DT_CODE03_CH_REG(ch), 0); } + for_each_active_route(&state->routing, route) { + struct v4l2_mbus_frame_desc_entry *source_entry = NULL; + const struct rcar_isp_format *format; + const struct v4l2_mbus_framefmt *fmt; + unsigned int i; + u8 vc, dt, ch; + u32 v; + + for (i = 0; i < source_fd.num_entries; i++) { + if (source_fd.entry[i].stream == route->sink_stream) { + source_entry = &source_fd.entry[i]; + break; + } + } + + if (!source_entry) { + dev_err(isp->dev, + "Failed to find source frame desc entry for stream\n"); + return -EPIPE; + } + + vc = source_entry->bus.csi2.vc; + dt = source_entry->bus.csi2.dt; + /* Channels 4 - 11 go to VIN */ + ch = route->source_pad - 1 + 4; + + fmt = v4l2_subdev_state_get_format(state, route->sink_pad, + route->sink_stream); + if (!fmt) + return -EINVAL; + + format = risp_code_to_fmt(fmt->code); + if (!format) { + dev_err(isp->dev, "Unsupported bus format\n"); + return -EINVAL; + } + + /* VC Filtering */ + risp_write_cs(isp, ISPCS_FILTER_ID_CH_REG(ch), BIT(vc)); + + /* DT Filtering */ + risp_write_cs(isp, ISPCS_DT_CODE03_CH_REG(ch), + ISPCS_DT_CODE03_EN0 | ISPCS_DT_CODE03_DT0(dt)); + + /* Proc mode */ + v = risp_read_cs(isp, ISPPROCMODE_DT_REG(dt)); + v |= ISPPROCMODE_DT_PROC_MODE_VCn(vc, format->procmode); + risp_write_cs(isp, ISPPROCMODE_DT_REG(dt), v); + } + + return 0; +} + +static int risp_start(struct rcar_isp *isp, struct v4l2_subdev_state *state) +{ + u32 sel_csi = 0; + int ret; + ret = risp_power_on(isp); if (ret) { dev_err(isp->dev, "Failed to power on ISP\n"); @@ -245,41 +317,18 @@ static int risp_start(struct rcar_isp *isp, struct v4l2_subdev_state *state) risp_write_cs(isp, ISPINPUTSEL0_REG, risp_read_cs(isp, ISPINPUTSEL0_REG) | sel_csi); - /* Configure Channel Selector. */ - for (vc = 0; vc < 4; vc++) { - u8 ch = vc + 4; - u8 dt = format->datatype; - - risp_write_cs(isp, ISPCS_FILTER_ID_CH_REG(ch), BIT(vc)); - risp_write_cs(isp, ISPCS_DT_CODE03_CH_REG(ch), - ISPCS_DT_CODE03_EN3 | ISPCS_DT_CODE03_DT3(dt) | - ISPCS_DT_CODE03_EN2 | ISPCS_DT_CODE03_DT2(dt) | - ISPCS_DT_CODE03_EN1 | ISPCS_DT_CODE03_DT1(dt) | - ISPCS_DT_CODE03_EN0 | ISPCS_DT_CODE03_DT0(dt)); - } - - /* Setup processing method. */ - risp_write_cs(isp, ISPPROCMODE_DT_REG(format->datatype), - ISPPROCMODE_DT_PROC_MODE_VCn(3, format->procmode) | - ISPPROCMODE_DT_PROC_MODE_VCn(2, format->procmode) | - ISPPROCMODE_DT_PROC_MODE_VCn(1, format->procmode) | - ISPPROCMODE_DT_PROC_MODE_VCn(0, format->procmode)); + ret = risp_configure_routing(isp, state); + if (ret) + return ret; /* Start ISP. */ risp_write_cs(isp, ISPSTART_REG, ISPSTART_START); - ret = v4l2_subdev_enable_streams(isp->remote, isp->remote_pad, - BIT_ULL(0)); - if (ret) - risp_power_off(isp); - - return ret; + return 0; } static void risp_stop(struct rcar_isp *isp) { - v4l2_subdev_disable_streams(isp->remote, isp->remote_pad, BIT_ULL(0)); - /* Stop ISP. */ risp_write_cs(isp, ISPSTART_REG, ISPSTART_STOP); @@ -291,7 +340,8 @@ static int risp_enable_streams(struct v4l2_subdev *sd, u64 source_streams_mask) { struct rcar_isp *isp = sd_to_isp(sd); - int ret = 0; + u64 sink_streams; + int ret; if (source_streams_mask != 1) return -EINVAL; @@ -305,9 +355,22 @@ static int risp_enable_streams(struct v4l2_subdev *sd, return ret; } + sink_streams = v4l2_subdev_state_xlate_streams(state, + source_pad, + RCAR_ISP_SINK, + &source_streams_mask); + + ret = v4l2_subdev_enable_streams(isp->remote, isp->remote_pad, + sink_streams); + if (ret) { + if (isp->stream_count == 0) + risp_stop(isp); + return ret; + } + isp->stream_count += 1; - return ret; + return 0; } static int risp_disable_streams(struct v4l2_subdev *sd, @@ -315,6 +378,8 @@ static int risp_disable_streams(struct v4l2_subdev *sd, u64 source_streams_mask) { struct rcar_isp *isp = sd_to_isp(sd); + u64 sink_streams; + int ret; if (source_streams_mask != 1) return -EINVAL; @@ -322,6 +387,15 @@ static int risp_disable_streams(struct v4l2_subdev *sd, if (!isp->remote) return -ENODEV; + sink_streams = v4l2_subdev_state_xlate_streams(state, + source_pad, + RCAR_ISP_SINK, + &source_streams_mask); + + ret = v4l2_subdev_disable_streams(isp->remote, isp->remote_pad, sink_streams); + if (ret) + return ret; + if (isp->stream_count == 1) risp_stop(isp); @@ -331,10 +405,11 @@ static int risp_disable_streams(struct v4l2_subdev *sd, } static int risp_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { - struct v4l2_mbus_framefmt *framefmt; + struct v4l2_mbus_framefmt *fmt; if (format->pad > RCAR_ISP_SINK) return v4l2_subdev_get_fmt(sd, state, format); @@ -342,10 +417,41 @@ static int risp_set_pad_format(struct v4l2_subdev *sd, if (!risp_code_to_fmt(format->format.code)) format->format.code = rcar_isp_formats[0].code; - for (unsigned int i = 0; i < RCAR_ISP_NUM_PADS; i++) { - framefmt = v4l2_subdev_state_get_format(state, i); - *framefmt = format->format; - } + /* Set sink format. */ + fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + /* Propagate the format to the source pad. */ + fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + return 0; +} + +static int risp_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + int ret; + + ret = v4l2_subdev_routing_validate(sd, routing, + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1 | + V4L2_SUBDEV_ROUTING_NO_SOURCE_MULTIPLEXING); + if (ret) + return ret; + + ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, + &risp_default_fmt); + if (ret) + return ret; return 0; } @@ -356,12 +462,39 @@ static const struct v4l2_subdev_pad_ops risp_pad_ops = { .set_fmt = risp_set_pad_format, .get_fmt = v4l2_subdev_get_fmt, .link_validate = v4l2_subdev_link_validate_default, + .set_routing = risp_set_routing, }; static const struct v4l2_subdev_ops rcar_isp_subdev_ops = { .pad = &risp_pad_ops, }; +static int risp_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + static struct v4l2_subdev_route routes[] = { + { + .sink_pad = RCAR_ISP_SINK, + .sink_stream = 0, + .source_pad = RCAR_ISP_PORT0, + .source_stream = 0, + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }, + }; + + static const struct v4l2_subdev_krouting routing = { + .num_routes = ARRAY_SIZE(routes), + .routes = routes, + }; + + return v4l2_subdev_set_routing_with_fmt(sd, state, &routing, + &risp_default_fmt); +} + +static const struct v4l2_subdev_internal_ops risp_internal_ops = { + .init_state = risp_init_state, +}; + /* ----------------------------------------------------------------------------- * Async handling and registration of subdevices and links */ @@ -534,11 +667,12 @@ static int risp_probe(struct platform_device *pdev) isp->subdev.owner = THIS_MODULE; isp->subdev.dev = &pdev->dev; + isp->subdev.internal_ops = &risp_internal_ops; v4l2_subdev_init(&isp->subdev, &rcar_isp_subdev_ops); v4l2_set_subdevdata(&isp->subdev, &pdev->dev); snprintf(isp->subdev.name, sizeof(isp->subdev.name), "%s %s", KBUILD_MODNAME, dev_name(&pdev->dev)); - isp->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + isp->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS; isp->subdev.entity.function = MEDIA_ENT_F_VID_MUX; isp->subdev.entity.ops = &risp_entity_ops; diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-core.c b/drivers/media/platform/renesas/rcar-vin/rcar-core.c index c8d564aa1eba..2fcea715101c 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-core.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-core.c @@ -673,23 +673,26 @@ static int rvin_csi2_create_link(struct rvin_group *group, unsigned int id, struct media_entity *source = &group->remotes[route->csi].subdev->entity; struct media_entity *sink = &group->vin[id]->vdev.entity; struct media_pad *sink_pad = &sink->pads[0]; + struct media_pad *source_pad; + unsigned int source_idx; unsigned int channel; - int ret; - for (channel = 0; channel < 4; channel++) { - unsigned int source_idx = rvin_group_csi_channel_to_pad(channel); - struct media_pad *source_pad = &source->pads[source_idx]; + /* + * The channels from CSI-2 blocks and the VIN groups have a set of + * hardcoded routing options to choose from. We only support the routing + * where all VINs in a group are connected to the same CSI-2 block, + * and the Nth VIN in the group is connected to the Nth CSI-2 channel. + */ - /* Skip if link already exists. */ - if (media_entity_find_link(source_pad, sink_pad)) - continue; + channel = id % 4; + source_idx = rvin_group_csi_channel_to_pad(channel); + source_pad = &source->pads[source_idx]; - ret = media_create_pad_link(source, source_idx, sink, 0, 0); - if (ret) - return ret; - } + /* Skip if link already exists. */ + if (media_entity_find_link(source_pad, sink_pad)) + return 0; - return 0; + return media_create_pad_link(source, source_idx, sink, 0, 0); } static int rvin_parallel_setup_links(struct rvin_group *group) diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-dma.c b/drivers/media/platform/renesas/rcar-vin/rcar-dma.c index 73cda0e2d45a..5e515d16c164 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-dma.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-dma.c @@ -678,7 +678,7 @@ void rvin_crop_scale_comp(struct rvin_dev *vin) /* * VNIS_REG has four lowest bits always 0, i.e. the stride has to be - * aligned to 16 bytes. This is done in rvin_format_bytesperline(). + * aligned to 16 pixels. This is done in rvin_format_bytesperline(). */ fmt = rvin_format_from_pixel(vin, vin->format.pixelformat); diff --git a/drivers/media/platform/renesas/rcar_drif.c b/drivers/media/platform/renesas/rcar_drif.c index 0844934f7aa6..bd67e8e16f53 100644 --- a/drivers/media/platform/renesas/rcar_drif.c +++ b/drivers/media/platform/renesas/rcar_drif.c @@ -464,7 +464,7 @@ rcar_drif_get_fbuf(struct rcar_drif_sdr *sdr) rcar_drif_frame_buf, list); if (!fbuf) { /* - * App is late in enqueing buffers. Samples lost & there will + * App is late in enqueuing buffers. Samples lost & there will * be a gap in sequence number when app recovers */ rdrif_dbg(sdr, "\napp late: prod %u\n", sdr->produced); diff --git a/drivers/media/platform/renesas/renesas-ceu.c b/drivers/media/platform/renesas/renesas-ceu.c index 65f7659a9e02..47b9e4753301 100644 --- a/drivers/media/platform/renesas/renesas-ceu.c +++ b/drivers/media/platform/renesas/renesas-ceu.c @@ -841,12 +841,13 @@ static int __ceu_try_fmt(struct ceu_device *ceudev, struct v4l2_format *v4l2_fmt * time. */ sd_format.format.code = mbus_code; - ret = v4l2_subdev_call(v4l2_sd, pad, set_fmt, &pad_state, &sd_format); + ret = v4l2_subdev_call(v4l2_sd, pad, set_fmt, NULL, &pad_state, + &sd_format); if (ret) { if (ret == -EINVAL) { /* fallback */ sd_format.format.code = mbus_code_old; - ret = v4l2_subdev_call(v4l2_sd, pad, set_fmt, + ret = v4l2_subdev_call(v4l2_sd, pad, set_fmt, NULL, &pad_state, &sd_format); } @@ -900,7 +901,7 @@ static int ceu_set_fmt(struct ceu_device *ceudev, struct v4l2_format *v4l2_fmt) format.format.code = mbus_code; v4l2_fill_mbus_format_mplane(&format.format, &v4l2_fmt->fmt.pix_mp); - ret = v4l2_subdev_call(v4l2_sd, pad, set_fmt, NULL, &format); + ret = v4l2_subdev_call(v4l2_sd, pad, set_fmt, NULL, NULL, &format); if (ret) return ret; diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c index 798ef2916262..8c7f46ccce5f 100644 --- a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c @@ -360,7 +360,7 @@ static const struct rzg2l_cru_info rzg3e_cru_info = { .max_width = 4095, .max_height = 4095, .image_conv = ICnIPMC_C0, - .has_stride = true, + .stride_align = 128, .regs = rzg3e_cru_regs, .irq_handler = rzg3e_cru_irq, .enable_interrupts = rzg3e_cru_enable_interrupts, @@ -405,6 +405,7 @@ static const struct rzg2l_cru_info rzg2l_cru_info = { .max_width = 2800, .max_height = 4095, .image_conv = ICnMC, + .stride_align = 1, .regs = rzg2l_cru_regs, .irq_handler = rzg2l_cru_irq, .enable_interrupts = rzg2l_cru_enable_interrupts, diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h index b426bc7898bf..2c192d370dcb 100644 --- a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h @@ -75,7 +75,7 @@ struct rzg2l_cru_info { unsigned int max_height; u16 image_conv; const u16 *regs; - bool has_stride; + u8 stride_align; irqreturn_t (*irq_handler)(int irq, void *data); void (*enable_interrupts)(struct rzg2l_cru_dev *cru); void (*disable_interrupts)(struct rzg2l_cru_dev *cru); diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c index 6dc4b53607b4..c17283cba3a6 100644 --- a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c @@ -633,6 +633,7 @@ static int rzg2l_csi2_post_streamoff(struct v4l2_subdev *sd) } static int rzg2l_csi2_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -687,7 +688,7 @@ static int rzg2l_csi2_init_state(struct v4l2_subdev *sd, fmt.format.quantization = V4L2_QUANTIZATION_DEFAULT; fmt.format.xfer_func = V4L2_XFER_FUNC_DEFAULT; - return rzg2l_csi2_set_format(sd, sd_state, &fmt); + return rzg2l_csi2_set_format(sd, NULL, sd_state, &fmt); } static int rzg2l_csi2_enum_mbus_code(struct v4l2_subdev *sd, diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-ip.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-ip.c index 5f2c87858bfe..21dd0162fd6e 100644 --- a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-ip.c +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-ip.c @@ -200,6 +200,7 @@ static int rzg2l_cru_ip_s_stream(struct v4l2_subdev *sd, int enable) } static int rzg2l_cru_ip_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -300,7 +301,7 @@ static int rzg2l_cru_ip_init_state(struct v4l2_subdev *sd, fmt.format.quantization = V4L2_QUANTIZATION_DEFAULT; fmt.format.xfer_func = V4L2_XFER_FUNC_DEFAULT; - return rzg2l_cru_ip_set_format(sd, sd_state, &fmt); + return rzg2l_cru_ip_set_format(sd, NULL, sd_state, &fmt); } static const struct v4l2_subdev_video_ops rzg2l_cru_ip_video_ops = { diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c index 91eda5034248..a7b6dce66570 100644 --- a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c @@ -32,7 +32,6 @@ #define RZG2L_CRU_DEFAULT_COLORSPACE V4L2_COLORSPACE_SRGB #define RZG2L_CRU_STRIDE_MAX 32640 -#define RZG2L_CRU_STRIDE_ALIGN 128 struct rzg2l_cru_buffer { struct vb2_v4l2_buffer vb; @@ -277,11 +276,11 @@ static void rzg2l_cru_initialize_axi(struct rzg2l_cru_dev *cru) rzg2l_cru_fill_hw_slot(cru, cru->num_buf - 1); } - if (info->has_stride) { + if (info->stride_align > 1) { u32 stride = cru->format.bytesperline; u32 amnis; - stride /= RZG2L_CRU_STRIDE_ALIGN; + stride /= info->stride_align; amnis = rzg2l_cru_read(cru, AMnIS) & ~AMnIS_IS_MASK; rzg2l_cru_write(cru, AMnIS, amnis | AMnIS_IS(stride)); } @@ -849,12 +848,8 @@ static void rzg2l_cru_format_align(struct rzg2l_cru_dev *cru, v4l_bound_align_image(&pix->width, 320, info->max_width, 1, &pix->height, 240, info->max_height, 2, 0); - v4l2_fill_pixfmt(pix, pix->pixelformat, pix->width, pix->height); - - if (info->has_stride) { - pix->bytesperline = ALIGN(pix->bytesperline, RZG2L_CRU_STRIDE_ALIGN); - pix->sizeimage = pix->bytesperline * pix->height; - } + v4l2_fill_pixfmt_aligned(pix, pix->pixelformat, pix->width, pix->height, + info->stride_align); dev_dbg(cru->dev, "Format %ux%u bpl: %u size: %u\n", pix->width, pix->height, pix->bytesperline, pix->sizeimage); diff --git a/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c index b1659544eaa0..04e168b9526f 100644 --- a/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c +++ b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c @@ -157,6 +157,7 @@ static int rzv2h_ivc_enum_frame_size(struct v4l2_subdev *sd, } static int rzv2h_ivc_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/platform/renesas/sh_vou.c b/drivers/media/platform/renesas/sh_vou.c index 4ad7ae188d5b..6d4ec80edd8f 100644 --- a/drivers/media/platform/renesas/sh_vou.c +++ b/drivers/media/platform/renesas/sh_vou.c @@ -714,7 +714,7 @@ static int sh_vou_set_fmt_vid_out(struct sh_vou_device *vou_dev, mbfmt->width = geo.output.width; mbfmt->height = geo.output.height; ret = v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, pad, - set_fmt, NULL, &format); + set_fmt, NULL, NULL, &format); /* Must be implemented, so, don't check for -ENOIOCTLCMD */ if (ret < 0) return ret; @@ -974,11 +974,11 @@ static int sh_vou_s_selection(struct file *file, void *fh, * final encoder configuration. */ v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, pad, - set_selection, NULL, &sd_sel); + set_selection, NULL, NULL, &sd_sel); format.format.width = geo.output.width; format.format.height = geo.output.height; ret = v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, pad, - set_fmt, NULL, &format); + set_fmt, NULL, NULL, &format); /* Must be implemented, so, don't check for -ENOIOCTLCMD */ if (ret < 0) return ret; diff --git a/drivers/media/platform/renesas/vsp1/vsp1_brx.c b/drivers/media/platform/renesas/vsp1/vsp1_brx.c index 360a42502947..a70b82e22e69 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_brx.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_brx.c @@ -124,6 +124,7 @@ static void brx_try_format(struct vsp1_brx *brx, } static int brx_set_format(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -165,6 +166,7 @@ static int brx_set_format(struct v4l2_subdev *subdev, } static int brx_get_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -200,6 +202,7 @@ static int brx_get_selection(struct v4l2_subdev *subdev, } static int brx_set_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/platform/renesas/vsp1/vsp1_dl.c b/drivers/media/platform/renesas/vsp1/vsp1_dl.c index 6430f2ec8b32..8257759421f3 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_dl.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_dl.c @@ -649,7 +649,7 @@ static void __vsp1_dl_list_put(struct vsp1_dl_list *dl) dl->post_cmd = NULL; /* - * body0 is reused as as an optimisation as presently every display list + * body0 is reused as an optimisation as presently every display list * has at least one body, thus we reinitialise the entries list. */ dl->body0->num_entries = 0; diff --git a/drivers/media/platform/renesas/vsp1/vsp1_drm.c b/drivers/media/platform/renesas/vsp1/vsp1_drm.c index 9cd5c025d2be..adb89e699389 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_drm.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_drm.c @@ -137,7 +137,8 @@ static int vsp1_du_insert_uif(struct vsp1_device *vsp1, format.pad = UIF_PAD_SINK; - ret = v4l2_subdev_call(&uif->subdev, pad, set_fmt, NULL, &format); + ret = v4l2_subdev_call(&uif->subdev, pad, set_fmt, NULL, NULL, + &format); if (ret < 0) return ret; @@ -184,7 +185,7 @@ static int vsp1_du_pipeline_setup_rpf(struct vsp1_device *vsp1, format.format.ycbcr_enc = input->ycbcr_enc; format.format.quantization = input->quantization; - ret = v4l2_subdev_call(&rpf->entity.subdev, pad, set_fmt, NULL, + ret = v4l2_subdev_call(&rpf->entity.subdev, pad, set_fmt, NULL, NULL, &format); if (ret < 0) return ret; @@ -199,6 +200,7 @@ static int vsp1_du_pipeline_setup_rpf(struct vsp1_device *vsp1, sel.r = input->crop; ret = v4l2_subdev_call(&rpf->entity.subdev, pad, set_selection, NULL, + NULL, &sel); if (ret < 0) return ret; @@ -226,7 +228,7 @@ static int vsp1_du_pipeline_setup_rpf(struct vsp1_device *vsp1, format.format.code = MEDIA_BUS_FMT_ARGB8888_1X32; - ret = v4l2_subdev_call(&rpf->entity.subdev, pad, set_fmt, NULL, + ret = v4l2_subdev_call(&rpf->entity.subdev, pad, set_fmt, NULL, NULL, &format); if (ret < 0) return ret; @@ -240,7 +242,7 @@ static int vsp1_du_pipeline_setup_rpf(struct vsp1_device *vsp1, /* BRx sink, propagate the format from the RPF source. */ format.pad = brx_input; - ret = v4l2_subdev_call(&pipe->brx->subdev, pad, set_fmt, NULL, + ret = v4l2_subdev_call(&pipe->brx->subdev, pad, set_fmt, NULL, NULL, &format); if (ret < 0) return ret; @@ -254,6 +256,7 @@ static int vsp1_du_pipeline_setup_rpf(struct vsp1_device *vsp1, sel.r = vsp1->drm->inputs[rpf->entity.index].compose; ret = v4l2_subdev_call(&pipe->brx->subdev, pad, set_selection, NULL, + NULL, &sel); if (ret < 0) return ret; @@ -387,7 +390,7 @@ static int vsp1_du_pipeline_setup_brx(struct vsp1_device *vsp1, format.format.height = drm_pipe->height; format.format.field = V4L2_FIELD_NONE; - ret = v4l2_subdev_call(&brx->subdev, pad, set_fmt, NULL, + ret = v4l2_subdev_call(&brx->subdev, pad, set_fmt, NULL, NULL, &format); if (ret < 0) return ret; @@ -538,7 +541,8 @@ static int vsp1_du_pipeline_setup_output(struct vsp1_device *vsp1, format.format.code = MEDIA_BUS_FMT_ARGB8888_1X32; format.format.field = V4L2_FIELD_NONE; - ret = v4l2_subdev_call(&pipe->output->entity.subdev, pad, set_fmt, NULL, + ret = v4l2_subdev_call(&pipe->output->entity.subdev, pad, set_fmt, + NULL, NULL, &format); if (ret < 0) return ret; @@ -558,7 +562,7 @@ static int vsp1_du_pipeline_setup_output(struct vsp1_device *vsp1, format.format.code, pipe->output->entity.index); format.pad = LIF_PAD_SINK; - ret = v4l2_subdev_call(&pipe->lif->subdev, pad, set_fmt, NULL, + ret = v4l2_subdev_call(&pipe->lif->subdev, pad, set_fmt, NULL, NULL, &format); if (ret < 0) return ret; diff --git a/drivers/media/platform/renesas/vsp1/vsp1_entity.c b/drivers/media/platform/renesas/vsp1/vsp1_entity.c index 26b21559878d..f7359d347947 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_entity.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_entity.c @@ -300,6 +300,7 @@ int vsp1_subdev_enum_frame_size(struct v4l2_subdev *subdev, * entity's limits, and propagates the sink pad format to the source pad. */ int vsp1_subdev_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) { @@ -380,7 +381,8 @@ static int vsp1_entity_init_state(struct v4l2_subdev *subdev, : V4L2_SUBDEV_FORMAT_ACTIVE, }; - v4l2_subdev_call(subdev, pad, set_fmt, sd_state, &format); + v4l2_subdev_call(subdev, pad, set_fmt, NULL, sd_state, + &format); } return 0; diff --git a/drivers/media/platform/renesas/vsp1/vsp1_entity.h b/drivers/media/platform/renesas/vsp1/vsp1_entity.h index c0c1fe7d3e40..677a1efc104a 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_entity.h +++ b/drivers/media/platform/renesas/vsp1/vsp1_entity.h @@ -188,6 +188,7 @@ int vsp1_subdev_get_pad_format(struct v4l2_subdev *subdev, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt); int vsp1_subdev_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); int vsp1_subdev_enum_mbus_code(struct v4l2_subdev *subdev, diff --git a/drivers/media/platform/renesas/vsp1/vsp1_histo.c b/drivers/media/platform/renesas/vsp1/vsp1_histo.c index 97dbfb93abe9..bd9efb42fd1f 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_histo.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_histo.c @@ -185,6 +185,7 @@ static int histo_enum_frame_size(struct v4l2_subdev *subdev, } static int histo_get_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -306,6 +307,7 @@ static int histo_set_compose(struct v4l2_subdev *subdev, } static int histo_set_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -330,6 +332,7 @@ static int histo_set_selection(struct v4l2_subdev *subdev, } static int histo_set_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/platform/renesas/vsp1/vsp1_hsit.c b/drivers/media/platform/renesas/vsp1/vsp1_hsit.c index df069c228243..d734b5775af6 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_hsit.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_hsit.c @@ -109,6 +109,7 @@ static int hsit_enum_frame_size(struct v4l2_subdev *subdev, } static int hsit_set_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/platform/renesas/vsp1/vsp1_rwpf.c b/drivers/media/platform/renesas/vsp1/vsp1_rwpf.c index ced01870acd6..ff5894265b54 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_rwpf.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_rwpf.c @@ -110,6 +110,7 @@ static int vsp1_rwpf_enum_frame_size(struct v4l2_subdev *subdev, } static int vsp1_rwpf_set_format(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -214,6 +215,7 @@ static int vsp1_rwpf_set_format(struct v4l2_subdev *subdev, } static int vsp1_rwpf_get_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -255,6 +257,7 @@ static int vsp1_rwpf_get_selection(struct v4l2_subdev *subdev, } static int vsp1_rwpf_set_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/platform/renesas/vsp1/vsp1_sru.c b/drivers/media/platform/renesas/vsp1/vsp1_sru.c index 3fd9fde5c724..5e1cf60be311 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_sru.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_sru.c @@ -210,6 +210,7 @@ static void sru_try_format(struct vsp1_sru *sru, } static int sru_set_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/platform/renesas/vsp1/vsp1_uds.c b/drivers/media/platform/renesas/vsp1/vsp1_uds.c index 9f7bb112929e..d129964ec14d 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_uds.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_uds.c @@ -193,6 +193,7 @@ static void uds_try_format(struct vsp1_uds *uds, } static int uds_set_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/platform/renesas/vsp1/vsp1_uif.c b/drivers/media/platform/renesas/vsp1/vsp1_uif.c index 52dbfe58a70d..16ddcd594134 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_uif.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_uif.c @@ -54,6 +54,7 @@ static const unsigned int uif_codes[] = { }; static int uif_get_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -92,6 +93,7 @@ static int uif_get_selection(struct v4l2_subdev *subdev, } static int uif_set_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/platform/renesas/vsp1/vsp1_vspx.c b/drivers/media/platform/renesas/vsp1/vsp1_vspx.c index 1673479be0ff..5c39bacfb13f 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_vspx.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_vspx.c @@ -120,7 +120,8 @@ static int vsp1_vspx_rwpf_set_subdev_fmt(struct vsp1_device *vsp1, format.format.field = V4L2_FIELD_NONE; format.format.code = rwpf->fmtinfo->mbus; - return v4l2_subdev_call(&ent->subdev, pad, set_fmt, NULL, &format); + return v4l2_subdev_call(&ent->subdev, pad, set_fmt, NULL, NULL, + &format); } /* Configure the RPF->IIF->WPF pipeline for ConfigDMA or RAW image transfer. */ diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-interface.c b/drivers/media/platform/rockchip/rkcif/rkcif-interface.c index 414a9980cf2e..2cba42d7c5d9 100644 --- a/drivers/media/platform/rockchip/rkcif/rkcif-interface.c +++ b/drivers/media/platform/rockchip/rkcif/rkcif-interface.c @@ -25,6 +25,7 @@ static const struct media_entity_operations rkcif_interface_media_ops = { }; static int rkcif_interface_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { @@ -77,6 +78,7 @@ static int rkcif_interface_set_fmt(struct v4l2_subdev *sd, } static int rkcif_interface_get_sel(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -115,6 +117,7 @@ static int rkcif_interface_get_sel(struct v4l2_subdev *sd, } static int rkcif_interface_set_sel(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-csi.c b/drivers/media/platform/rockchip/rkisp1/rkisp1-csi.c index ddc6182f3e4b..9fa8a0407c90 100644 --- a/drivers/media/platform/rockchip/rkisp1/rkisp1-csi.c +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-csi.c @@ -304,6 +304,7 @@ static int rkisp1_csi_init_state(struct v4l2_subdev *sd, } static int rkisp1_csi_set_fmt(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/platform/rockchip/rkisp1/rkisp1-dev.c b/drivers/media/platform/rockchip/rkisp1/rkisp1-dev.c index 1791c02a40ae..f90e01301943 100644 --- a/drivers/media/platform/rockchip/rkisp1/rkisp1-dev.c +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-dev.c @@ -187,7 +187,6 @@ static int rkisp1_subdev_notifier_register(struct rkisp1_device *rkisp1) { struct v4l2_async_notifier *ntf = &rkisp1->notifier; struct fwnode_handle *fwnode = dev_fwnode(rkisp1->dev); - struct fwnode_handle *ep; unsigned int index = 0; int ret = 0; @@ -195,7 +194,7 @@ static int rkisp1_subdev_notifier_register(struct rkisp1_device *rkisp1) ntf->ops = &rkisp1_subdev_notifier_ops; - fwnode_graph_for_each_endpoint(fwnode, ep) { + fwnode_graph_for_each_endpoint_scoped(fwnode, ep) { struct fwnode_handle *port; struct v4l2_fwnode_endpoint vep = { }; struct rkisp1_sensor_async *rk_asd; @@ -286,7 +285,6 @@ static int rkisp1_subdev_notifier_register(struct rkisp1_device *rkisp1) } if (ret) { - fwnode_handle_put(ep); v4l2_async_nf_cleanup(ntf); return ret; } diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-isp.c b/drivers/media/platform/rockchip/rkisp1/rkisp1-isp.c index 2311672cedb1..4043a75ca3f0 100644 --- a/drivers/media/platform/rockchip/rkisp1/rkisp1-isp.c +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-isp.c @@ -817,6 +817,7 @@ static void rkisp1_isp_set_sink_fmt(struct rkisp1_isp *isp, } static int rkisp1_isp_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -834,6 +835,7 @@ static int rkisp1_isp_set_fmt(struct v4l2_subdev *sd, } static int rkisp1_isp_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -872,6 +874,7 @@ static int rkisp1_isp_get_selection(struct v4l2_subdev *sd, } static int rkisp1_isp_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/platform/rockchip/rkisp1/rkisp1-resizer.c b/drivers/media/platform/rockchip/rkisp1/rkisp1-resizer.c index 8e6b753d3081..5926917434af 100644 --- a/drivers/media/platform/rockchip/rkisp1/rkisp1-resizer.c +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-resizer.c @@ -543,6 +543,7 @@ static void rkisp1_rsz_set_sink_fmt(struct rkisp1_resizer *rsz, } static int rkisp1_rsz_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -558,6 +559,7 @@ static int rkisp1_rsz_set_fmt(struct v4l2_subdev *sd, } static int rkisp1_rsz_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -591,6 +593,7 @@ static int rkisp1_rsz_get_selection(struct v4l2_subdev *sd, } static int rkisp1_rsz_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/platform/samsung/exynos4-is/fimc-capture.c b/drivers/media/platform/samsung/exynos4-is/fimc-capture.c index d85811f4b8c5..8d7e7b190532 100644 --- a/drivers/media/platform/samsung/exynos4-is/fimc-capture.c +++ b/drivers/media/platform/samsung/exynos4-is/fimc-capture.c @@ -796,7 +796,8 @@ static int fimc_pipeline_try_format(struct fimc_ctx *ctx, sd = media_entity_to_v4l2_subdev(me); sfmt.pad = 0; - ret = v4l2_subdev_call(sd, pad, set_fmt, NULL, &sfmt); + ret = v4l2_subdev_call(sd, pad, set_fmt, NULL, NULL, + &sfmt); if (ret) return ret; @@ -804,7 +805,7 @@ static int fimc_pipeline_try_format(struct fimc_ctx *ctx, sfmt.pad = me->num_pads - 1; mf->code = tfmt->code; ret = v4l2_subdev_call(sd, pad, set_fmt, NULL, - &sfmt); + NULL, &sfmt); if (ret) return ret; } @@ -1509,6 +1510,7 @@ static int fimc_subdev_get_fmt(struct v4l2_subdev *sd, } static int fimc_subdev_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -1575,6 +1577,7 @@ static int fimc_subdev_set_fmt(struct v4l2_subdev *sd, } static int fimc_subdev_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1631,6 +1634,7 @@ static int fimc_subdev_get_selection(struct v4l2_subdev *sd, } static int fimc_subdev_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/platform/samsung/exynos4-is/fimc-isp.c b/drivers/media/platform/samsung/exynos4-is/fimc-isp.c index 3c5d7bee2655..a0a9093081dd 100644 --- a/drivers/media/platform/samsung/exynos4-is/fimc-isp.c +++ b/drivers/media/platform/samsung/exynos4-is/fimc-isp.c @@ -191,6 +191,7 @@ static void __isp_subdev_try_format(struct fimc_isp *isp, } static int fimc_isp_subdev_set_fmt(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/platform/samsung/exynos4-is/fimc-lite.c b/drivers/media/platform/samsung/exynos4-is/fimc-lite.c index 8be20fd32d1c..1b7bae15189b 100644 --- a/drivers/media/platform/samsung/exynos4-is/fimc-lite.c +++ b/drivers/media/platform/samsung/exynos4-is/fimc-lite.c @@ -1052,6 +1052,7 @@ static int fimc_lite_subdev_get_fmt(struct v4l2_subdev *sd, } static int fimc_lite_subdev_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -1113,6 +1114,7 @@ static int fimc_lite_subdev_set_fmt(struct v4l2_subdev *sd, } static int fimc_lite_subdev_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1148,6 +1150,7 @@ static int fimc_lite_subdev_get_selection(struct v4l2_subdev *sd, } static int fimc_lite_subdev_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/platform/samsung/exynos4-is/mipi-csis.c b/drivers/media/platform/samsung/exynos4-is/mipi-csis.c index 452880b5350c..da6e67a79c09 100644 --- a/drivers/media/platform/samsung/exynos4-is/mipi-csis.c +++ b/drivers/media/platform/samsung/exynos4-is/mipi-csis.c @@ -575,6 +575,7 @@ static struct v4l2_mbus_framefmt *__s5pcsis_get_format( } static int s5pcsis_set_fmt(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/platform/samsung/s3c-camif/camif-capture.c b/drivers/media/platform/samsung/s3c-camif/camif-capture.c index ed1a1d693293..98dfd394a1aa 100644 --- a/drivers/media/platform/samsung/s3c-camif/camif-capture.c +++ b/drivers/media/platform/samsung/s3c-camif/camif-capture.c @@ -1275,6 +1275,7 @@ static void __camif_subdev_try_format(struct camif_dev *camif, } static int s3c_camif_subdev_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -1342,6 +1343,7 @@ static int s3c_camif_subdev_set_fmt(struct v4l2_subdev *sd, } static int s3c_camif_subdev_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1429,6 +1431,7 @@ static void __camif_try_crop(struct camif_dev *camif, struct v4l2_rect *r) } static int s3c_camif_subdev_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/platform/samsung/s3c-camif/camif-core.c b/drivers/media/platform/samsung/s3c-camif/camif-core.c index bb06847f3a63..2ff71e375efe 100644 --- a/drivers/media/platform/samsung/s3c-camif/camif-core.c +++ b/drivers/media/platform/samsung/s3c-camif/camif-core.c @@ -227,7 +227,7 @@ static int camif_register_sensor(struct camif_dev *camif) return 0; format.pad = CAMIF_SD_PAD_SINK; - v4l2_subdev_call(&camif->subdev, pad, set_fmt, NULL, &format); + v4l2_subdev_call(&camif->subdev, pad, set_fmt, NULL, NULL, &format); v4l2_info(sd, "Initial format from sensor: %dx%d, %#x\n", format.format.width, format.format.height, diff --git a/drivers/media/platform/st/stm32/stm32-csi.c b/drivers/media/platform/st/stm32/stm32-csi.c index fd2b6dfbd44c..e71142634a17 100644 --- a/drivers/media/platform/st/stm32/stm32-csi.c +++ b/drivers/media/platform/st/stm32/stm32-csi.c @@ -736,6 +736,7 @@ static int stm32_csi_enum_mbus_code(struct v4l2_subdev *sd, } static int stm32_csi_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { @@ -825,7 +826,7 @@ static int stm32_csi_async_bound(struct v4l2_async_notifier *notifier, int remote_pad; remote_pad = media_entity_get_fwnode_pad(&s_subdev->entity, - s_subdev->fwnode, + asd->match.fwnode, MEDIA_PAD_FL_SOURCE); if (remote_pad < 0) { dev_err(csidev->dev, "Couldn't find output pad for subdev %s\n", @@ -1059,6 +1060,7 @@ static int stm32_csi_probe(struct platform_device *pdev) return 0; err_cleanup: + v4l2_async_nf_unregister(&csidev->notifier); v4l2_async_nf_cleanup(&csidev->notifier); return ret; } @@ -1067,6 +1069,8 @@ static void stm32_csi_remove(struct platform_device *pdev) { struct stm32_csi_dev *csidev = platform_get_drvdata(pdev); + v4l2_async_nf_unregister(&csidev->notifier); + v4l2_async_nf_cleanup(&csidev->notifier); v4l2_async_unregister_subdev(&csidev->sd); pm_runtime_disable(&pdev->dev); diff --git a/drivers/media/platform/st/stm32/stm32-dcmi.c b/drivers/media/platform/st/stm32/stm32-dcmi.c index aa79fe60ff0c..3d774cb4907b 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmi.c +++ b/drivers/media/platform/st/stm32/stm32-dcmi.c @@ -738,7 +738,7 @@ static int dcmi_pipeline_s_fmt(struct stm32_dcmi *dcmi, format->format.width, format->format.height); fmt.pad = pad->index; - ret = v4l2_subdev_call(subdev, pad, set_fmt, NULL, &fmt); + ret = v4l2_subdev_call(subdev, pad, set_fmt, NULL, NULL, &fmt); if (ret < 0) { dev_err(dcmi->dev, "%s: Failed to set format 0x%x %ux%u on \"%s\":%d pad (%d)\n", __func__, format->format.code, @@ -1022,6 +1022,27 @@ static void __find_outer_frame_size(struct stm32_dcmi *dcmi, *framesize = *match; } +static int dcmi_source_call_try_state_set_fmt(struct v4l2_subdev *source, + struct v4l2_subdev_format *fmt) +{ + static struct lock_class_key lock_key; + const char *lock_name = KBUILD_BASENAME ":" __stringify(__LINE__) + ":state->lock"; + struct v4l2_subdev_state *source_state; + int ret; + + source_state = __v4l2_subdev_state_alloc(source, lock_name, &lock_key); + if (IS_ERR(source_state)) + return PTR_ERR(source_state); + + v4l2_subdev_lock_state(source_state); + ret = v4l2_subdev_call(source, pad, set_fmt, NULL, source_state, fmt); + v4l2_subdev_unlock_state(source_state); + __v4l2_subdev_state_free(source_state); + + return ret; +} + static int dcmi_try_fmt(struct stm32_dcmi *dcmi, struct v4l2_format *f, const struct dcmi_format **sd_format, struct dcmi_framesize *sd_framesize) @@ -1063,8 +1084,8 @@ static int dcmi_try_fmt(struct stm32_dcmi *dcmi, struct v4l2_format *f, } v4l2_fill_mbus_format(&format.format, pix, sd_fmt->mbus_code); - ret = v4l2_subdev_call_state_try(dcmi->source, pad, set_fmt, &format); - if (ret < 0) + ret = dcmi_source_call_try_state_set_fmt(dcmi->source, &format); + if (ret) return ret; /* Update pix regarding to what sensor can do */ @@ -1224,7 +1245,7 @@ static int dcmi_set_sensor_format(struct stm32_dcmi *dcmi, } v4l2_fill_mbus_format(&format.format, pix, sd_fmt->mbus_code); - ret = v4l2_subdev_call_state_try(dcmi->source, pad, set_fmt, &format); + ret = dcmi_source_call_try_state_set_fmt(dcmi->source, &format); if (ret < 0) return ret; @@ -1246,7 +1267,7 @@ static int dcmi_get_sensor_bounds(struct stm32_dcmi *dcmi, /* * Get sensor bounds first */ - ret = v4l2_subdev_call(dcmi->source, pad, get_selection, + ret = v4l2_subdev_call(dcmi->source, pad, get_selection, NULL, NULL, &bounds); if (!ret) *r = bounds.r; diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/Makefile b/drivers/media/platform/st/stm32/stm32-dcmipp/Makefile index 159105fb40b8..e35d45a0aca2 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/Makefile +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/Makefile @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 -stm32-dcmipp-y := dcmipp-core.o dcmipp-common.o dcmipp-input.o dcmipp-byteproc.o dcmipp-bytecap.o +stm32-dcmipp-y := dcmipp-core.o dcmipp-common.o dcmipp-input.o dcmipp-byteproc.o dcmipp-capture.o +stm32-dcmipp-y += dcmipp-pixelcommon.o dcmipp-isp.o dcmipp-pixelproc.o obj-$(CONFIG_VIDEO_STM32_DCMIPP) += stm32-dcmipp.o diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c index 0c7aeb0888f6..346cfddcf229 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c @@ -25,6 +25,7 @@ #define DCMIPP_P0SCSZR_HSIZE_SHIFT 0 #define DCMIPP_P0SCSZR_VSIZE_SHIFT 16 #define DCMIPP_P0PPCR 0x5c0 +#define DCMIPP_P0PPCR_SWAPYUV BIT(0) #define DCMIPP_P0PPCR_BSM_1_2 0x1 #define DCMIPP_P0PPCR_BSM_1_4 0x2 #define DCMIPP_P0PPCR_BSM_2_4 0x3 @@ -264,6 +265,7 @@ dcmipp_byteproc_enum_frame_size(struct v4l2_subdev *sd, } static int dcmipp_byteproc_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -298,6 +300,7 @@ static int dcmipp_byteproc_set_fmt(struct v4l2_subdev *sd, } static int dcmipp_byteproc_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *s) { @@ -351,6 +354,7 @@ static int dcmipp_byteproc_get_selection(struct v4l2_subdev *sd, } static int dcmipp_byteproc_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *s) { @@ -428,9 +432,11 @@ static int dcmipp_byteproc_configure_scale_crop if (!vpix) return -EINVAL; - /* clear decimation/crop */ + /* clear decimation/crop/swap yuv */ reg_clear(byteproc, DCMIPP_P0PPCR, DCMIPP_P0PPCR_BSM_MASK); reg_clear(byteproc, DCMIPP_P0PPCR, DCMIPP_P0PPCR_LSM); + if (byteproc->ved.dcmipp->pipe_cfg->has_swapyuv) + reg_clear(byteproc, DCMIPP_P0PPCR, DCMIPP_P0PPCR_SWAPYUV); reg_write(byteproc, DCMIPP_P0SCSTR, 0); reg_write(byteproc, DCMIPP_P0SCSZR, 0); @@ -451,6 +457,17 @@ static int dcmipp_byteproc_configure_scale_crop if (vprediv == 2) val |= DCMIPP_P0PPCR_LSM | DCMIPP_P0PPCR_OELS; + /* + * Perform a SWAP YUV if input is parallel since in this mode + * the DCMIPP will swap YUV by default + */ + if (byteproc->ved.dcmipp->pipe_cfg->has_swapyuv && + (sink_fmt->code == MEDIA_BUS_FMT_YUYV8_2X8 || + sink_fmt->code == MEDIA_BUS_FMT_YVYU8_2X8 || + sink_fmt->code == MEDIA_BUS_FMT_UYVY8_2X8 || + sink_fmt->code == MEDIA_BUS_FMT_VYUY8_2X8)) + val |= DCMIPP_P0PPCR_SWAPYUV; + /* decimate using bytes and lines skipping */ if (val) { reg_set(byteproc, DCMIPP_P0PPCR, val); @@ -571,8 +588,8 @@ void dcmipp_byteproc_ent_release(struct dcmipp_ent_device *ved) } struct dcmipp_ent_device * -dcmipp_byteproc_ent_init(struct device *dev, const char *entity_name, - struct v4l2_device *v4l2_dev, void __iomem *regs) +dcmipp_byteproc_ent_init(const char *entity_name, + struct dcmipp_device *dcmipp) { struct dcmipp_byteproc_device *byteproc; const unsigned long pads_flag[] = { @@ -585,11 +602,11 @@ dcmipp_byteproc_ent_init(struct device *dev, const char *entity_name, if (!byteproc) return ERR_PTR(-ENOMEM); - byteproc->regs = regs; + byteproc->regs = dcmipp->regs; /* Initialize ved and sd */ ret = dcmipp_ent_sd_register(&byteproc->ved, &byteproc->sd, - v4l2_dev, entity_name, + &dcmipp->v4l2_dev, entity_name, MEDIA_ENT_F_PROC_VIDEO_SCALER, ARRAY_SIZE(pads_flag), pads_flag, &dcmipp_byteproc_int_ops, @@ -600,7 +617,8 @@ dcmipp_byteproc_ent_init(struct device *dev, const char *entity_name, return ERR_PTR(ret); } - byteproc->dev = dev; + byteproc->ved.dcmipp = dcmipp; + byteproc->dev = dcmipp->dev; return &byteproc->ved; } diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-capture.c index f0e809458489..fe7767f908b7 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-capture.c @@ -25,27 +25,81 @@ #define DCMIPP_CMIER_P0ALL (DCMIPP_CMIER_P0VSYNCIE |\ DCMIPP_CMIER_P0FRAMEIE |\ DCMIPP_CMIER_P0OVRIE) +#define DCMIPP_CMIER_P1FRAMEIE BIT(17) +#define DCMIPP_CMIER_P1VSYNCIE BIT(18) +#define DCMIPP_CMIER_P1OVRIE BIT(23) +#define DCMIPP_CMIER_P1ALL (DCMIPP_CMIER_P1VSYNCIE |\ + DCMIPP_CMIER_P1FRAMEIE |\ + DCMIPP_CMIER_P1OVRIE) +#define DCMIPP_CMIER_P2FRAMEIE BIT(25) +#define DCMIPP_CMIER_P2VSYNCIE BIT(26) +#define DCMIPP_CMIER_P2OVRIE BIT(31) +#define DCMIPP_CMIER_P2ALL (DCMIPP_CMIER_P2VSYNCIE |\ + DCMIPP_CMIER_P2FRAMEIE |\ + DCMIPP_CMIER_P2OVRIE) +#define DCMIPP_CMIER_PxALL(id) (((id) == 0) ? DCMIPP_CMIER_P0ALL : \ + (((id) == 1) ? DCMIPP_CMIER_P1ALL : \ + DCMIPP_CMIER_P2ALL)) #define DCMIPP_CMSR1 0x3f4 #define DCMIPP_CMSR2 0x3f8 #define DCMIPP_CMSR2_P0FRAMEF BIT(9) #define DCMIPP_CMSR2_P0VSYNCF BIT(10) #define DCMIPP_CMSR2_P0OVRF BIT(15) +#define DCMIPP_CMSR2_P1FRAMEF BIT(17) +#define DCMIPP_CMSR2_P1VSYNCF BIT(18) +#define DCMIPP_CMSR2_P1OVRF BIT(23) +#define DCMIPP_CMSR2_P2FRAMEF BIT(25) +#define DCMIPP_CMSR2_P2VSYNCF BIT(26) +#define DCMIPP_CMSR2_P2OVRF BIT(31) +#define DCMIPP_CMSR2_PxFRAMEF(id) (((id) == 0) ? DCMIPP_CMSR2_P0FRAMEF :\ + (((id) == 1) ? DCMIPP_CMSR2_P1FRAMEF :\ + DCMIPP_CMSR2_P2FRAMEF)) +#define DCMIPP_CMSR2_PxVSYNCF(id) (((id) == 0) ? DCMIPP_CMSR2_P0VSYNCF :\ + (((id) == 1) ? DCMIPP_CMSR2_P1VSYNCF :\ + DCMIPP_CMSR2_P2VSYNCF)) +#define DCMIPP_CMSR2_PxOVRF(id) (((id) == 0) ? DCMIPP_CMSR2_P0OVRF :\ + (((id) == 1) ? DCMIPP_CMSR2_P1OVRF :\ + DCMIPP_CMSR2_P2OVRF)) #define DCMIPP_CMFCR 0x3fc -#define DCMIPP_P0FSCR 0x404 -#define DCMIPP_P0FSCR_PIPEN BIT(31) -#define DCMIPP_P0FCTCR 0x500 -#define DCMIPP_P0FCTCR_CPTREQ BIT(3) +#define DCMIPP_PxFSCR(id) (0x404 + ((id) * 0x400)) +#define DCMIPP_PxFSCR_PIPEN BIT(31) +#define DCMIPP_PxFCTCR(id) (0x500 + ((id) * 0x400)) +#define DCMIPP_PxFCTCR_CPTREQ BIT(3) #define DCMIPP_P0DCCNTR 0x5b0 #define DCMIPP_P0DCLMTR 0x5b4 #define DCMIPP_P0DCLMTR_ENABLE BIT(31) #define DCMIPP_P0DCLMTR_LIMIT_MASK GENMASK(23, 0) -#define DCMIPP_P0PPM0AR1 0x5c4 -#define DCMIPP_P0SR 0x5f8 -#define DCMIPP_P0SR_CPTACT BIT(23) -struct dcmipp_bytecap_pix_map { +#define DCMIPP_PxPPM0AR1(id) (0x5c4 + ((id) * 0x400)) +#define DCMIPP_PxPPM0PR(id) (0x9cc + (((id) - 1) * 0x400)) +#define DCMIPP_P1PPM1AR1 0x9d4 +#define DCMIPP_P1PPM1PR 0x9dc +#define DCMIPP_P1PPM2AR1 0x9e4 + +#define DCMIPP_PxSR(id) (0x5f8 + ((id) * 0x400)) +#define DCMIPP_PxSR_CPTACT BIT(23) + +#define DCMIPP_PxPPCR(id) (0x9c0 + (((id) - 1) * 0x400)) +#define DCMIPP_PxPPCR_FORMAT_RGB888 0x0 +#define DCMIPP_PxPPCR_FORMAT_RGB565 0x1 +#define DCMIPP_PxPPCR_FORMAT_ARGB8888 0x2 +#define DCMIPP_PxPPCR_FORMAT_RGBA8888 0x3 +#define DCMIPP_PxPPCR_FORMAT_Y8 0x4 +#define DCMIPP_PxPPCR_FORMAT_YUV444 0x5 +#define DCMIPP_PxPPCR_FORMAT_YUYV 0x6 +#define DCMIPP_P1PPCR_FORMAT_NV61 0x7 +#define DCMIPP_P1PPCR_FORMAT_NV21 0x8 +#define DCMIPP_P1PPCR_FORMAT_YV12 0x9 +#define DCMIPP_PxPPCR_FORMAT_UYVY 0xa + +#define DCMIPP_PxPPCR_SWAPRB BIT(4) + +struct dcmipp_capture_pix_map { unsigned int code; u32 pixelformat; + u32 plane_nb; + unsigned int ppcr_fmt; + unsigned int swap_uv; }; #define PIXMAP_MBUS_PFMT(mbus, fmt) \ @@ -54,7 +108,7 @@ struct dcmipp_bytecap_pix_map { .pixelformat = V4L2_PIX_FMT_##fmt \ } -static const struct dcmipp_bytecap_pix_map dcmipp_bytecap_pix_map_list[] = { +static const struct dcmipp_capture_pix_map dcmipp_capture_dump_pix_map_list[] = { PIXMAP_MBUS_PFMT(RGB565_2X8_LE, RGB565), PIXMAP_MBUS_PFMT(RGB565_1X16, RGB565), PIXMAP_MBUS_PFMT(RGB888_1X24, RGB24), @@ -89,34 +143,51 @@ static const struct dcmipp_bytecap_pix_map dcmipp_bytecap_pix_map_list[] = { PIXMAP_MBUS_PFMT(JPEG_1X8, JPEG), }; -static const struct dcmipp_bytecap_pix_map * -dcmipp_bytecap_pix_map_by_pixelformat(u32 pixelformat) -{ - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(dcmipp_bytecap_pix_map_list); i++) { - if (dcmipp_bytecap_pix_map_list[i].pixelformat == pixelformat) - return &dcmipp_bytecap_pix_map_list[i]; +#define PIXMAP_MBUS_PIXEL_PFMT(mbus, fmt, nb_plane, pp_code, swap) \ + { \ + .code = MEDIA_BUS_FMT_##mbus, \ + .pixelformat = V4L2_PIX_FMT_##fmt, \ + .plane_nb = nb_plane, \ + .ppcr_fmt = pp_code, \ + .swap_uv = swap, \ } - return NULL; -} +static const struct dcmipp_capture_pix_map dcmipp_capture_pixel_pix_map_list[] = { + /* Coplanar formats are supported on main & aux pipe */ + PIXMAP_MBUS_PIXEL_PFMT(RGB888_1X24, RGB565, 1, DCMIPP_PxPPCR_FORMAT_RGB565, 0), + PIXMAP_MBUS_PIXEL_PFMT(YUV8_1X24, YUYV, 1, DCMIPP_PxPPCR_FORMAT_YUYV, 0), + PIXMAP_MBUS_PIXEL_PFMT(YUV8_1X24, YVYU, 1, DCMIPP_PxPPCR_FORMAT_YUYV, 1), + PIXMAP_MBUS_PIXEL_PFMT(YUV8_1X24, UYVY, 1, DCMIPP_PxPPCR_FORMAT_UYVY, 0), + PIXMAP_MBUS_PIXEL_PFMT(YUV8_1X24, VYUY, 1, DCMIPP_PxPPCR_FORMAT_UYVY, 1), + PIXMAP_MBUS_PIXEL_PFMT(YUV8_1X24, GREY, 1, DCMIPP_PxPPCR_FORMAT_Y8, 0), + PIXMAP_MBUS_PIXEL_PFMT(RGB888_1X24, RGB24, 1, DCMIPP_PxPPCR_FORMAT_RGB888, 1), + PIXMAP_MBUS_PIXEL_PFMT(RGB888_1X24, BGR24, 1, DCMIPP_PxPPCR_FORMAT_RGB888, 0), + PIXMAP_MBUS_PIXEL_PFMT(RGB888_1X24, ARGB32, 1, DCMIPP_PxPPCR_FORMAT_RGBA8888, 1), + PIXMAP_MBUS_PIXEL_PFMT(RGB888_1X24, ABGR32, 1, DCMIPP_PxPPCR_FORMAT_ARGB8888, 0), + PIXMAP_MBUS_PIXEL_PFMT(RGB888_1X24, RGBA32, 1, DCMIPP_PxPPCR_FORMAT_ARGB8888, 1), + PIXMAP_MBUS_PIXEL_PFMT(RGB888_1X24, BGRA32, 1, DCMIPP_PxPPCR_FORMAT_RGBA8888, 0), + + /* Semiplanar & planar formats (plane_nb > 1) are only supported on main pipe */ + PIXMAP_MBUS_PIXEL_PFMT(YUV8_1X24, NV12, 2, DCMIPP_P1PPCR_FORMAT_NV21, 0), + PIXMAP_MBUS_PIXEL_PFMT(YUV8_1X24, NV21, 2, DCMIPP_P1PPCR_FORMAT_NV21, 1), + PIXMAP_MBUS_PIXEL_PFMT(YUV8_1X24, NV16, 2, DCMIPP_P1PPCR_FORMAT_NV61, 0), + PIXMAP_MBUS_PIXEL_PFMT(YUV8_1X24, NV61, 2, DCMIPP_P1PPCR_FORMAT_NV61, 1), + PIXMAP_MBUS_PIXEL_PFMT(YUV8_1X24, YUV420, 3, DCMIPP_P1PPCR_FORMAT_YV12, 0), + PIXMAP_MBUS_PIXEL_PFMT(YUV8_1X24, YVU420, 3, DCMIPP_P1PPCR_FORMAT_YV12, 1), +}; struct dcmipp_buf { struct vb2_v4l2_buffer vb; bool prepared; dma_addr_t addr; size_t size; + dma_addr_t addrs[3]; + u32 strides[3]; + u64 sizes[3]; struct list_head list; }; -enum dcmipp_state { - DCMIPP_STOPPED = 0, - DCMIPP_WAIT_FOR_BUFFER, - DCMIPP_RUNNING, -}; - -struct dcmipp_bytecap_device { +struct dcmipp_capture_device { struct dcmipp_ent_device ved; struct video_device vdev; struct device *dev; @@ -131,7 +202,6 @@ struct dcmipp_bytecap_device { /* mutex used as vdev and queue lock */ struct mutex lock; u32 sequence; - struct media_pipeline pipe; struct v4l2_subdev *s_subdev; u32 s_subdev_pad_nb; @@ -147,6 +217,11 @@ struct dcmipp_bytecap_device { void __iomem *regs; + int pipe_id; + + const struct dcmipp_capture_pix_map *pix_map; + unsigned int pix_map_array_size; + u32 cmsr2; struct { @@ -162,6 +237,30 @@ struct dcmipp_bytecap_device { } count; }; +static const struct dcmipp_capture_pix_map * +dcmipp_capture_pix_map_by_pixelformat(struct dcmipp_capture_device *vcap, + u32 pixelformat) +{ + for (unsigned int i = 0; i < vcap->pix_map_array_size; i++) { + if (vcap->pix_map[i].pixelformat == pixelformat) + return &vcap->pix_map[i]; + } + + return NULL; +} + +static bool dcmipp_capture_is_format_valid(struct dcmipp_capture_device *vcap, + unsigned int pixelformat) +{ + const struct dcmipp_capture_pix_map *vpix = + dcmipp_capture_pix_map_by_pixelformat(vcap, pixelformat); + + if (!vpix || (vpix->plane_nb > 1 && vcap->pipe_id != 1)) + return false; + + return true; +} + static const struct v4l2_pix_format fmt_default = { .width = DCMIPP_FMT_WIDTH_DEFAULT, .height = DCMIPP_FMT_HEIGHT_DEFAULT, @@ -175,7 +274,74 @@ static const struct v4l2_pix_format fmt_default = { .xfer_func = DCMIPP_XFER_FUNC_DEFAULT, }; -static int dcmipp_bytecap_querycap(struct file *file, void *priv, +static inline int hdw_pixel_alignment(u32 format) +{ + /* 16 bytes alignment required by hardware */ + switch (format) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YVU420: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + case V4L2_PIX_FMT_GREY: + return 4;/* 2^4 = 16 pixels = 16 bytes */ + case V4L2_PIX_FMT_RGB565: + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_VYUY: + return 3;/* 2^3 = 8 pixels = 16 bytes */ + case V4L2_PIX_FMT_RGB24: + case V4L2_PIX_FMT_BGR24: + return 4;/* 2^4 = 16 pixels = 48 bytes */ + case V4L2_PIX_FMT_ARGB32: + case V4L2_PIX_FMT_ABGR32: + case V4L2_PIX_FMT_RGBA32: + case V4L2_PIX_FMT_BGRA32: + return 2;/* 2^2 = 4 pixels = 16 bytes */ + default: + return 0; + } +} + +static inline int frame_planes(dma_addr_t base_addr, dma_addr_t addrs[], + u32 strides[], u64 sizes[], + u32 width, u32 height, u32 format) +{ + const struct v4l2_format_info *info; + + /* Only used by dump pipe hence addrs[0] is enough */ + if (format == V4L2_PIX_FMT_JPEG) { + addrs[0] = base_addr; + return 0; + } + + info = v4l2_format_info(format); + if (!info) + return -EINVAL; + + /* Fill-in each plane information */ + addrs[0] = base_addr; + strides[0] = width * info->bpp[0]; + sizes[0] = strides[0] * height; + + if (info->comp_planes > 1) { + addrs[1] = addrs[0] + sizes[0]; + strides[1] = width * info->bpp[1] / info->hdiv; + sizes[1] = strides[1] * height / info->vdiv; + } + + if (info->comp_planes > 2) { + addrs[2] = addrs[1] + sizes[1]; + strides[2] = width * info->bpp[2] / info->hdiv; + sizes[2] = strides[2] * height / info->vdiv; + } + + return 0; +} + +static int dcmipp_capture_querycap(struct file *file, void *priv, struct v4l2_capability *cap) { strscpy(cap->driver, DCMIPP_PDEV_NAME, sizeof(cap->driver)); @@ -184,36 +350,39 @@ static int dcmipp_bytecap_querycap(struct file *file, void *priv, return 0; } -static int dcmipp_bytecap_g_fmt_vid_cap(struct file *file, void *priv, +static int dcmipp_capture_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { - struct dcmipp_bytecap_device *vcap = video_drvdata(file); + struct dcmipp_capture_device *vcap = video_drvdata(file); f->fmt.pix = vcap->format; return 0; } -static int dcmipp_bytecap_try_fmt_vid_cap(struct file *file, void *priv, +static int dcmipp_capture_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { - struct dcmipp_bytecap_device *vcap = video_drvdata(file); + struct dcmipp_capture_device *vcap = video_drvdata(file); struct v4l2_pix_format *format = &f->fmt.pix; - const struct dcmipp_bytecap_pix_map *vpix; u32 in_w, in_h; /* Don't accept a pixelformat that is not on the table */ - vpix = dcmipp_bytecap_pix_map_by_pixelformat(format->pixelformat); - if (!vpix) + if (!dcmipp_capture_is_format_valid(vcap, format->pixelformat)) format->pixelformat = fmt_default.pixelformat; /* Adjust width & height */ in_w = format->width; in_h = format->height; - v4l_bound_align_image(&format->width, DCMIPP_FRAME_MIN_WIDTH, - DCMIPP_FRAME_MAX_WIDTH, 0, &format->height, - DCMIPP_FRAME_MIN_HEIGHT, DCMIPP_FRAME_MAX_HEIGHT, - 0, 0); + format->width = clamp_t(u32, format->width, DCMIPP_FRAME_MIN_WIDTH, + vcap->pipe_id != 0 ? + DCMIPP_FRAME_MAX_WIDTH : DCMIPP_FRAME_MAX_WIDTH); + if (vcap->pipe_id != 0) + format->width = round_up(format->width, + 1 << hdw_pixel_alignment(format->pixelformat)); + format->height = clamp_t(u32, format->height, DCMIPP_FRAME_MIN_HEIGHT, + vcap->pipe_id != 0 ? + DCMIPP_FRAME_MAX_HEIGHT : DCMIPP_FRAME_MAX_HEIGHT); if (format->width != in_w || format->height != in_h) dev_dbg(vcap->dev, "resolution updated: %dx%d -> %dx%d\n", in_w, in_h, format->width, format->height); @@ -234,17 +403,17 @@ static int dcmipp_bytecap_try_fmt_vid_cap(struct file *file, void *priv, return 0; } -static int dcmipp_bytecap_s_fmt_vid_cap(struct file *file, void *priv, +static int dcmipp_capture_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { - struct dcmipp_bytecap_device *vcap = video_drvdata(file); + struct dcmipp_capture_device *vcap = video_drvdata(file); int ret; /* Do not change the format while stream is on */ if (vb2_is_busy(&vcap->queue)) return -EBUSY; - ret = dcmipp_bytecap_try_fmt_vid_cap(file, priv, f); + ret = dcmipp_capture_try_fmt_vid_cap(file, priv, f); if (ret) return ret; @@ -266,10 +435,10 @@ static int dcmipp_bytecap_s_fmt_vid_cap(struct file *file, void *priv, return 0; } -static int dcmipp_bytecap_enum_fmt_vid_cap(struct file *file, void *priv, +static int dcmipp_capture_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l2_fmtdesc *f) { - const struct dcmipp_bytecap_pix_map *vpix; + struct dcmipp_capture_device *vcap = video_drvdata(file); unsigned int index = f->index; unsigned int i, prev_pixelformat = 0; @@ -278,17 +447,20 @@ static int dcmipp_bytecap_enum_fmt_vid_cap(struct file *file, void *priv, * care of removing duplicated entries (due to support of both * parallel & csi 16 bits formats */ - for (i = 0; i < ARRAY_SIZE(dcmipp_bytecap_pix_map_list); i++) { - vpix = &dcmipp_bytecap_pix_map_list[i]; + for (i = 0; i < vcap->pix_map_array_size; i++) { + /* Only main pipe supports (Semi)-planar formats */ + if (vcap->pipe_id != 1 && vcap->pix_map[i].plane_nb > 1) + continue; + /* Skip formats not matching requested mbus code */ - if (f->mbus_code && vpix->code != f->mbus_code) + if (f->mbus_code && vcap->pix_map[i].code != f->mbus_code) continue; /* Skip duplicated pixelformat */ - if (vpix->pixelformat == prev_pixelformat) + if (vcap->pix_map[i].pixelformat == prev_pixelformat) continue; - prev_pixelformat = vpix->pixelformat; + prev_pixelformat = vcap->pix_map[i].pixelformat; if (index == 0) break; @@ -296,25 +468,25 @@ static int dcmipp_bytecap_enum_fmt_vid_cap(struct file *file, void *priv, index--; } - if (i == ARRAY_SIZE(dcmipp_bytecap_pix_map_list)) + if (i == vcap->pix_map_array_size) return -EINVAL; - f->pixelformat = vpix->pixelformat; + f->pixelformat = vcap->pix_map[i].pixelformat; return 0; } -static int dcmipp_bytecap_enum_framesizes(struct file *file, void *fh, +static int dcmipp_capture_enum_framesizes(struct file *file, void *fh, struct v4l2_frmsizeenum *fsize) { - const struct dcmipp_bytecap_pix_map *vpix; + struct dcmipp_capture_device *vcap = video_drvdata(file); + if (fsize->index) return -EINVAL; /* Only accept code in the pix map table */ - vpix = dcmipp_bytecap_pix_map_by_pixelformat(fsize->pixel_format); - if (!vpix) + if (!dcmipp_capture_is_format_valid(vcap, fsize->pixel_format)) return -EINVAL; fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; @@ -328,7 +500,7 @@ static int dcmipp_bytecap_enum_framesizes(struct file *file, void *fh, return 0; } -static const struct v4l2_file_operations dcmipp_bytecap_fops = { +static const struct v4l2_file_operations dcmipp_capture_fops = { .owner = THIS_MODULE, .open = v4l2_fh_open, .release = vb2_fop_release, @@ -338,14 +510,14 @@ static const struct v4l2_file_operations dcmipp_bytecap_fops = { .mmap = vb2_fop_mmap, }; -static const struct v4l2_ioctl_ops dcmipp_bytecap_ioctl_ops = { - .vidioc_querycap = dcmipp_bytecap_querycap, +static const struct v4l2_ioctl_ops dcmipp_capture_ioctl_ops = { + .vidioc_querycap = dcmipp_capture_querycap, - .vidioc_g_fmt_vid_cap = dcmipp_bytecap_g_fmt_vid_cap, - .vidioc_s_fmt_vid_cap = dcmipp_bytecap_s_fmt_vid_cap, - .vidioc_try_fmt_vid_cap = dcmipp_bytecap_try_fmt_vid_cap, - .vidioc_enum_fmt_vid_cap = dcmipp_bytecap_enum_fmt_vid_cap, - .vidioc_enum_framesizes = dcmipp_bytecap_enum_framesizes, + .vidioc_g_fmt_vid_cap = dcmipp_capture_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = dcmipp_capture_s_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = dcmipp_capture_try_fmt_vid_cap, + .vidioc_enum_fmt_vid_cap = dcmipp_capture_enum_fmt_vid_cap, + .vidioc_enum_framesizes = dcmipp_capture_enum_framesizes, .vidioc_reqbufs = vb2_ioctl_reqbufs, .vidioc_create_bufs = vb2_ioctl_create_bufs, @@ -358,21 +530,34 @@ static const struct v4l2_ioctl_ops dcmipp_bytecap_ioctl_ops = { .vidioc_streamoff = vb2_ioctl_streamoff, }; -static void dcmipp_start_capture(struct dcmipp_bytecap_device *vcap, +static void dcmipp_start_capture(struct dcmipp_capture_device *vcap, struct dcmipp_buf *buf) { /* Set buffer address */ - reg_write(vcap, DCMIPP_P0PPM0AR1, buf->addr); + reg_write(vcap, DCMIPP_PxPPM0AR1(vcap->pipe_id), buf->addrs[0]); - /* Set buffer size */ - reg_write(vcap, DCMIPP_P0DCLMTR, DCMIPP_P0DCLMTR_ENABLE | - ((buf->size / 4) & DCMIPP_P0DCLMTR_LIMIT_MASK)); + if (vcap->pipe_id == 0) { + /* Set buffer size */ + reg_write(vcap, DCMIPP_P0DCLMTR, DCMIPP_P0DCLMTR_ENABLE | + ((buf->size / 4) & DCMIPP_P0DCLMTR_LIMIT_MASK)); + } else { + reg_write(vcap, DCMIPP_PxPPM0PR(vcap->pipe_id), + buf->strides[0]); + + if (buf->addrs[1]) { + reg_write(vcap, DCMIPP_P1PPM1AR1, buf->addrs[1]); + reg_write(vcap, DCMIPP_P1PPM1PR, buf->strides[1]); + } + + if (buf->addrs[2]) + reg_write(vcap, DCMIPP_P1PPM2AR1, buf->addrs[2]); + } /* Capture request */ - reg_set(vcap, DCMIPP_P0FCTCR, DCMIPP_P0FCTCR_CPTREQ); + reg_set(vcap, DCMIPP_PxFCTCR(vcap->pipe_id), DCMIPP_PxFCTCR_CPTREQ); } -static void dcmipp_bytecap_all_buffers_done(struct dcmipp_bytecap_device *vcap, +static void dcmipp_capture_all_buffers_done(struct dcmipp_capture_device *vcap, enum vb2_buffer_state state) { struct dcmipp_buf *buf, *node; @@ -383,10 +568,10 @@ static void dcmipp_bytecap_all_buffers_done(struct dcmipp_bytecap_device *vcap, } } -static int dcmipp_bytecap_start_streaming(struct vb2_queue *vq, +static int dcmipp_capture_start_streaming(struct vb2_queue *vq, unsigned int count) { - struct dcmipp_bytecap_device *vcap = vb2_get_drv_priv(vq); + struct dcmipp_capture_device *vcap = vb2_get_drv_priv(vq); struct media_entity *entity = &vcap->vdev.entity; struct dcmipp_buf *buf; struct media_pad *pad; @@ -397,7 +582,7 @@ static int dcmipp_bytecap_start_streaming(struct vb2_queue *vq, /* * Get source subdev - since link is IMMUTABLE, pointer is cached - * within the dcmipp_bytecap_device structure + * within the dcmipp_capture_device structure */ if (!vcap->s_subdev) { pad = media_pad_remote_pad_first(&vcap->vdev.entity.pads[0]); @@ -416,7 +601,7 @@ static int dcmipp_bytecap_start_streaming(struct vb2_queue *vq, goto err_buffer_done; } - ret = media_pipeline_start(entity->pads, &vcap->pipe); + ret = media_pipeline_start(entity->pads, &vcap->ved.dcmipp->pipe); if (ret) { dev_dbg(vcap->dev, "%s: Failed to start streaming, media pipeline start error (%d)\n", __func__, ret); @@ -430,8 +615,25 @@ static int dcmipp_bytecap_start_streaming(struct vb2_queue *vq, spin_lock_irq(&vcap->irqlock); + if (vcap->pipe_id != 0) { + const struct dcmipp_capture_pix_map *vpix = + dcmipp_capture_pix_map_by_pixelformat(vcap, vcap->format.pixelformat); + unsigned int ppcr = 0; + + /* + * Configure the Pixel Packer + * vpix is guaranteed to be valid since pixelformat is validated + * in dcmipp_pixelcap_s_fmt_vid_cap function before + */ + ppcr = vpix->ppcr_fmt; + if (vpix->swap_uv) + ppcr |= DCMIPP_PxPPCR_SWAPRB; + + reg_write(vcap, DCMIPP_PxPPCR(vcap->pipe_id), ppcr); + } + /* Enable pipe at the end of programming */ - reg_set(vcap, DCMIPP_P0FSCR, DCMIPP_P0FSCR_PIPEN); + reg_set(vcap, DCMIPP_PxFSCR(vcap->pipe_id), DCMIPP_PxFSCR_PIPEN); /* * vb2 framework guarantee that we have at least 'min_queued_buffers' @@ -444,7 +646,9 @@ static int dcmipp_bytecap_start_streaming(struct vb2_queue *vq, dcmipp_start_capture(vcap, vcap->next); /* Enable interruptions */ - reg_set(vcap, DCMIPP_CMIER, DCMIPP_CMIER_P0ALL); + spin_lock(&vcap->vdev.v4l2_dev->lock); + reg_set(vcap, DCMIPP_CMIER, DCMIPP_CMIER_PxALL(vcap->pipe_id)); + spin_unlock(&vcap->vdev.v4l2_dev->lock); vcap->state = DCMIPP_RUNNING; @@ -462,19 +666,19 @@ err_buffer_done: * Return all buffers to vb2 in QUEUED state. * This will give ownership back to userspace */ - dcmipp_bytecap_all_buffers_done(vcap, VB2_BUF_STATE_QUEUED); + dcmipp_capture_all_buffers_done(vcap, VB2_BUF_STATE_QUEUED); vcap->active = NULL; spin_unlock_irq(&vcap->irqlock); return ret; } -static void dcmipp_dump_status(struct dcmipp_bytecap_device *vcap) +static void dcmipp_dump_status(struct dcmipp_capture_device *vcap) { struct device *dev = vcap->dev; dev_dbg(dev, "[DCMIPP_PRSR] =%#10.8x\n", reg_read(vcap, DCMIPP_PRSR)); - dev_dbg(dev, "[DCMIPP_P0SR] =%#10.8x\n", reg_read(vcap, DCMIPP_P0SR)); + dev_dbg(dev, "[DCMIPP_P0SR] =%#10.8x\n", reg_read(vcap, DCMIPP_PxSR(0))); dev_dbg(dev, "[DCMIPP_P0DCCNTR]=%#10.8x\n", reg_read(vcap, DCMIPP_P0DCCNTR)); dev_dbg(dev, "[DCMIPP_CMSR1] =%#10.8x\n", reg_read(vcap, DCMIPP_CMSR1)); @@ -485,9 +689,9 @@ static void dcmipp_dump_status(struct dcmipp_bytecap_device *vcap) * Stop the stream engine. Any remaining buffers in the stream queue are * dequeued and passed on to the vb2 framework marked as STATE_ERROR. */ -static void dcmipp_bytecap_stop_streaming(struct vb2_queue *vq) +static void dcmipp_capture_stop_streaming(struct vb2_queue *vq) { - struct dcmipp_bytecap_device *vcap = vb2_get_drv_priv(vq); + struct dcmipp_capture_device *vcap = vb2_get_drv_priv(vq); int ret; u32 status; @@ -500,29 +704,32 @@ static void dcmipp_bytecap_stop_streaming(struct vb2_queue *vq) media_pipeline_stop(vcap->vdev.entity.pads); /* Disable interruptions */ - reg_clear(vcap, DCMIPP_CMIER, DCMIPP_CMIER_P0ALL); + spin_lock(&vcap->vdev.v4l2_dev->lock); + reg_clear(vcap, DCMIPP_CMIER, DCMIPP_CMIER_PxALL(vcap->pipe_id)); + spin_unlock(&vcap->vdev.v4l2_dev->lock); /* Stop capture */ - reg_clear(vcap, DCMIPP_P0FCTCR, DCMIPP_P0FCTCR_CPTREQ); + reg_clear(vcap, DCMIPP_PxFCTCR(vcap->pipe_id), DCMIPP_PxFCTCR_CPTREQ); /* Wait until CPTACT become 0 */ - ret = readl_relaxed_poll_timeout(vcap->regs + DCMIPP_P0SR, status, - !(status & DCMIPP_P0SR_CPTACT), + ret = readl_relaxed_poll_timeout(vcap->regs + DCMIPP_PxSR(vcap->pipe_id), + status, + !(status & DCMIPP_PxSR_CPTACT), 20 * USEC_PER_MSEC, 1000 * USEC_PER_MSEC); if (ret) dev_warn(vcap->dev, "Timeout when stopping\n"); /* Disable pipe */ - reg_clear(vcap, DCMIPP_P0FSCR, DCMIPP_P0FSCR_PIPEN); + reg_clear(vcap, DCMIPP_PxFSCR(vcap->pipe_id), DCMIPP_PxFSCR_PIPEN); /* Clear any pending interrupts */ - reg_write(vcap, DCMIPP_CMFCR, DCMIPP_CMIER_P0ALL); + reg_write(vcap, DCMIPP_CMFCR, DCMIPP_CMIER_PxALL(vcap->pipe_id)); spin_lock_irq(&vcap->irqlock); /* Return all queued buffers to vb2 in ERROR state */ - dcmipp_bytecap_all_buffers_done(vcap, VB2_BUF_STATE_ERROR); + dcmipp_capture_all_buffers_done(vcap, VB2_BUF_STATE_ERROR); INIT_LIST_HEAD(&vcap->buffers); vcap->active = NULL; @@ -530,7 +737,8 @@ static void dcmipp_bytecap_stop_streaming(struct vb2_queue *vq) spin_unlock_irq(&vcap->irqlock); - dcmipp_dump_status(vcap); + if (vcap->pipe_id == 0) + dcmipp_dump_status(vcap); pm_runtime_put(vcap->dev); @@ -541,12 +749,14 @@ static void dcmipp_bytecap_stop_streaming(struct vb2_queue *vq) vcap->count.underrun, vcap->count.buffers); } -static int dcmipp_bytecap_buf_prepare(struct vb2_buffer *vb) +static int dcmipp_capture_buf_prepare(struct vb2_buffer *vb) { - struct dcmipp_bytecap_device *vcap = vb2_get_drv_priv(vb->vb2_queue); + struct dcmipp_capture_device *vcap = vb2_get_drv_priv(vb->vb2_queue); struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); struct dcmipp_buf *buf = container_of(vbuf, struct dcmipp_buf, vb); + struct v4l2_pix_format *format = &vcap->format; unsigned long size; + int ret; size = vcap->format.sizeimage; @@ -562,6 +772,26 @@ static int dcmipp_bytecap_buf_prepare(struct vb2_buffer *vb) /* Get memory addresses */ buf->addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0); buf->size = vb2_plane_size(&buf->vb.vb2_buf, 0); + + ret = frame_planes(buf->addr, + buf->addrs, buf->strides, buf->sizes, + format->width, format->height, + format->pixelformat); + if (ret) { + dev_err(vcap->dev, "%s: Unsupported pixel format (%x)\n", + __func__, format->pixelformat); + return ret; + } + + /* Check for 16 bytes alignment required by hardware */ + WARN_ON(buf->addrs[0] & 15); + if (vcap->pipe_id != 0) { + WARN_ON(buf->strides[0] & 15); + WARN_ON(buf->addrs[1] & 15); + WARN_ON(buf->strides[1] & 15); + WARN_ON(buf->addrs[2] & 15); + } + buf->prepared = true; vb2_set_plane_payload(&buf->vb.vb2_buf, 0, buf->size); @@ -573,9 +803,9 @@ static int dcmipp_bytecap_buf_prepare(struct vb2_buffer *vb) return 0; } -static void dcmipp_bytecap_buf_queue(struct vb2_buffer *vb2_buf) +static void dcmipp_capture_buf_queue(struct vb2_buffer *vb2_buf) { - struct dcmipp_bytecap_device *vcap = + struct dcmipp_capture_device *vcap = vb2_get_drv_priv(vb2_buf->vb2_queue); struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb2_buf); struct dcmipp_buf *buf = container_of(vbuf, struct dcmipp_buf, vb); @@ -599,13 +829,13 @@ static void dcmipp_bytecap_buf_queue(struct vb2_buffer *vb2_buf) spin_unlock_irq(&vcap->irqlock); } -static int dcmipp_bytecap_queue_setup(struct vb2_queue *vq, +static int dcmipp_capture_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, unsigned int *nplanes, unsigned int sizes[], struct device *alloc_devs[]) { - struct dcmipp_bytecap_device *vcap = vb2_get_drv_priv(vq); + struct dcmipp_capture_device *vcap = vb2_get_drv_priv(vq); unsigned int size; size = vcap->format.sizeimage; @@ -623,7 +853,7 @@ static int dcmipp_bytecap_queue_setup(struct vb2_queue *vq, return 0; } -static int dcmipp_bytecap_buf_init(struct vb2_buffer *vb) +static int dcmipp_capture_buf_init(struct vb2_buffer *vb) { struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); struct dcmipp_buf *buf = container_of(vbuf, struct dcmipp_buf, vb); @@ -633,19 +863,19 @@ static int dcmipp_bytecap_buf_init(struct vb2_buffer *vb) return 0; } -static const struct vb2_ops dcmipp_bytecap_qops = { - .start_streaming = dcmipp_bytecap_start_streaming, - .stop_streaming = dcmipp_bytecap_stop_streaming, - .buf_init = dcmipp_bytecap_buf_init, - .buf_prepare = dcmipp_bytecap_buf_prepare, - .buf_queue = dcmipp_bytecap_buf_queue, - .queue_setup = dcmipp_bytecap_queue_setup, +static const struct vb2_ops dcmipp_capture_qops = { + .start_streaming = dcmipp_capture_start_streaming, + .stop_streaming = dcmipp_capture_stop_streaming, + .buf_init = dcmipp_capture_buf_init, + .buf_prepare = dcmipp_capture_buf_prepare, + .buf_queue = dcmipp_capture_buf_queue, + .queue_setup = dcmipp_capture_queue_setup, }; -static void dcmipp_bytecap_release(struct video_device *vdev) +static void dcmipp_capture_release(struct video_device *vdev) { - struct dcmipp_bytecap_device *vcap = - container_of(vdev, struct dcmipp_bytecap_device, vdev); + struct dcmipp_capture_device *vcap = + container_of(vdev, struct dcmipp_capture_device, vdev); dcmipp_pads_cleanup(vcap->ved.pads); mutex_destroy(&vcap->lock); @@ -653,16 +883,16 @@ static void dcmipp_bytecap_release(struct video_device *vdev) kfree(vcap); } -void dcmipp_bytecap_ent_release(struct dcmipp_ent_device *ved) +void dcmipp_capture_ent_release(struct dcmipp_ent_device *ved) { - struct dcmipp_bytecap_device *vcap = - container_of(ved, struct dcmipp_bytecap_device, ved); + struct dcmipp_capture_device *vcap = + container_of(ved, struct dcmipp_capture_device, ved); media_entity_cleanup(ved->ent); vb2_video_unregister_device(&vcap->vdev); } -static void dcmipp_buffer_done(struct dcmipp_bytecap_device *vcap, +static void dcmipp_buffer_done(struct dcmipp_capture_device *vcap, struct dcmipp_buf *buf, size_t bytesused, int err) @@ -686,7 +916,7 @@ static void dcmipp_buffer_done(struct dcmipp_bytecap_device *vcap, /* irqlock must be held */ static void -dcmipp_bytecap_set_next_frame_or_stop(struct dcmipp_bytecap_device *vcap) +dcmipp_capture_set_next_frame_or_stop(struct dcmipp_capture_device *vcap) { if (!vcap->next && list_is_singular(&vcap->buffers)) { /* @@ -695,7 +925,7 @@ dcmipp_bytecap_set_next_frame_or_stop(struct dcmipp_bytecap_device *vcap) * for next frame). On-going frame capture will continue until * FRAME END but no further capture will be done. */ - reg_clear(vcap, DCMIPP_P0FCTCR, DCMIPP_P0FCTCR_CPTREQ); + reg_clear(vcap, DCMIPP_PxFCTCR(vcap->pipe_id), DCMIPP_PxFCTCR_CPTREQ); dev_dbg(vcap->dev, "Capture restart is deferred to next buffer queueing\n"); vcap->next = NULL; @@ -712,13 +942,19 @@ dcmipp_bytecap_set_next_frame_or_stop(struct dcmipp_bytecap_device *vcap) * This register is shadowed and will be taken into * account on next VSYNC (start of next frame) */ - reg_write(vcap, DCMIPP_P0PPM0AR1, vcap->next->addr); + reg_write(vcap, DCMIPP_PxPPM0AR1(vcap->pipe_id), vcap->next->addrs[0]); + if (vcap->pipe_id == 1) { + if (vcap->next->addrs[1]) + reg_write(vcap, DCMIPP_P1PPM1AR1, vcap->next->addrs[1]); + if (vcap->next->addrs[2]) + reg_write(vcap, DCMIPP_P1PPM2AR1, vcap->next->addrs[2]); + } dev_dbg(vcap->dev, "Write [%d] %p phy=%pad\n", vcap->next->vb.vb2_buf.index, vcap->next, &vcap->next->addr); } /* irqlock must be held */ -static void dcmipp_bytecap_process_frame(struct dcmipp_bytecap_device *vcap, +static void dcmipp_capture_process_frame(struct dcmipp_capture_device *vcap, size_t bytesused) { int err = 0; @@ -744,33 +980,43 @@ static void dcmipp_bytecap_process_frame(struct dcmipp_bytecap_device *vcap, vcap->active = NULL; } -static irqreturn_t dcmipp_bytecap_irq_thread(int irq, void *arg) +static irqreturn_t dcmipp_capture_irq_thread(int irq, void *arg) { - struct dcmipp_bytecap_device *vcap = - container_of(arg, struct dcmipp_bytecap_device, ved); + struct dcmipp_capture_device *vcap = + container_of(arg, struct dcmipp_capture_device, ved); + u32 cmsr2_pxframef; + u32 cmsr2_pxvsyncf; + u32 cmsr2_pxovrf; size_t bytesused = 0; spin_lock_irq(&vcap->irqlock); + cmsr2_pxovrf = DCMIPP_CMSR2_PxOVRF(vcap->pipe_id); + cmsr2_pxvsyncf = DCMIPP_CMSR2_PxVSYNCF(vcap->pipe_id); + cmsr2_pxframef = DCMIPP_CMSR2_PxFRAMEF(vcap->pipe_id); + /* * If we have an overrun, a frame-end will probably not be generated, * in that case the active buffer will be recycled as next buffer by * the VSYNC handler */ - if (vcap->cmsr2 & DCMIPP_CMSR2_P0OVRF) { + if (vcap->cmsr2 & cmsr2_pxovrf) { vcap->count.errors++; vcap->count.overrun++; } - if (vcap->cmsr2 & DCMIPP_CMSR2_P0FRAMEF) { + if (vcap->cmsr2 & cmsr2_pxframef) { vcap->count.frame++; /* Read captured buffer size */ - bytesused = reg_read(vcap, DCMIPP_P0DCCNTR); - dcmipp_bytecap_process_frame(vcap, bytesused); + if (vcap->pipe_id == 0) + bytesused = reg_read(vcap, DCMIPP_P0DCCNTR); + else + bytesused = vcap->format.sizeimage; + dcmipp_capture_process_frame(vcap, bytesused); } - if (vcap->cmsr2 & DCMIPP_CMSR2_P0VSYNCF) { + if (vcap->cmsr2 & cmsr2_pxvsyncf) { vcap->count.vsync++; if (vcap->state == DCMIPP_WAIT_FOR_BUFFER) { vcap->count.underrun++; @@ -787,7 +1033,7 @@ static irqreturn_t dcmipp_bytecap_irq_thread(int irq, void *arg) * active (but not used) buffer and put it back into next. */ swap(vcap->active, vcap->next); - dcmipp_bytecap_set_next_frame_or_stop(vcap); + dcmipp_capture_set_next_frame_or_stop(vcap); } out: @@ -795,13 +1041,16 @@ out: return IRQ_HANDLED; } -static irqreturn_t dcmipp_bytecap_irq_callback(int irq, void *arg) +static irqreturn_t dcmipp_capture_irq_callback(int irq, void *arg) { - struct dcmipp_bytecap_device *vcap = - container_of(arg, struct dcmipp_bytecap_device, ved); + struct dcmipp_capture_device *vcap = + container_of(arg, struct dcmipp_capture_device, ved); + struct dcmipp_ent_device *ved = arg; /* Store interrupt status register */ - vcap->cmsr2 = reg_read(vcap, DCMIPP_CMSR2) & DCMIPP_CMIER_P0ALL; + vcap->cmsr2 = ved->cmsr2 & DCMIPP_CMIER_PxALL(vcap->pipe_id); + if (!vcap->cmsr2) + return IRQ_HANDLED; vcap->count.it++; /* Clear interrupt */ @@ -810,41 +1059,52 @@ static irqreturn_t dcmipp_bytecap_irq_callback(int irq, void *arg) return IRQ_WAKE_THREAD; } -static int dcmipp_bytecap_link_validate(struct media_link *link) +static int dcmipp_capture_link_validate(struct media_link *link) { struct media_entity *entity = link->sink->entity; struct video_device *vd = media_entity_to_video_device(entity); - struct dcmipp_bytecap_device *vcap = container_of(vd, - struct dcmipp_bytecap_device, vdev); + struct dcmipp_capture_device *vcap = container_of(vd, + struct dcmipp_capture_device, vdev); struct v4l2_subdev *source_sd = media_entity_to_v4l2_subdev(link->source->entity); struct v4l2_subdev_format source_fmt = { .which = V4L2_SUBDEV_FORMAT_ACTIVE, .pad = link->source->index, }; + u32 width_aligned; int ret, i; ret = v4l2_subdev_call(source_sd, pad, get_fmt, NULL, &source_fmt); if (ret < 0) return 0; - if (source_fmt.format.width != vcap->format.width || + width_aligned = source_fmt.format.width; + + /* + * On pixel pipes there can be alignment constraints. + * Depending on the format & pixelpacker constraints, vcap width is + * different from mbus width. Compute expected vcap width based on + * mbus width + */ + if (vcap->pipe_id != 0) + width_aligned = round_up(source_fmt.format.width, + 1 << hdw_pixel_alignment(vcap->format.pixelformat)); + + if (width_aligned != vcap->format.width || source_fmt.format.height != vcap->format.height) { dev_err(vcap->dev, "Wrong width or height %ux%u (%ux%u expected)\n", vcap->format.width, vcap->format.height, - source_fmt.format.width, source_fmt.format.height); + width_aligned, source_fmt.format.height); return -EINVAL; } - for (i = 0; i < ARRAY_SIZE(dcmipp_bytecap_pix_map_list); i++) { - if (dcmipp_bytecap_pix_map_list[i].pixelformat == - vcap->format.pixelformat && - dcmipp_bytecap_pix_map_list[i].code == - source_fmt.format.code) + for (i = 0; i < vcap->pix_map_array_size; i++) { + if (vcap->pix_map[i].pixelformat == vcap->format.pixelformat && + vcap->pix_map[i].code == source_fmt.format.code) break; } - if (i == ARRAY_SIZE(dcmipp_bytecap_pix_map_list)) { + if (i == vcap->pix_map_array_size) { dev_err(vcap->dev, "mbus code 0x%x do not match capture device format (0x%x)\n", vcap->format.pixelformat, source_fmt.format.code); return -EINVAL; @@ -853,26 +1113,54 @@ static int dcmipp_bytecap_link_validate(struct media_link *link) return 0; } -static const struct media_entity_operations dcmipp_bytecap_entity_ops = { - .link_validate = dcmipp_bytecap_link_validate, +static const struct media_entity_operations dcmipp_capture_entity_ops = { + .link_validate = dcmipp_capture_link_validate, }; -struct dcmipp_ent_device *dcmipp_bytecap_ent_init(struct device *dev, - const char *entity_name, - struct v4l2_device *v4l2_dev, - void __iomem *regs) +static int dcmipp_name_to_pipe_id(const char *name) +{ + if (strstr(name, "dump")) + return 0; + else if (strstr(name, "main")) + return 1; + else if (strstr(name, "aux")) + return 2; + else + return -EINVAL; +} + +struct dcmipp_ent_device *dcmipp_capture_ent_init(const char *entity_name, + struct dcmipp_device *dcmipp) { - struct dcmipp_bytecap_device *vcap; + struct dcmipp_capture_device *vcap; + struct device *dev = dcmipp->dev; struct video_device *vdev; struct vb2_queue *q; const unsigned long pad_flag = MEDIA_PAD_FL_SINK; int ret = 0; - /* Allocate the dcmipp_bytecap_device struct */ + /* Allocate the dcmipp_capture_device struct */ vcap = kzalloc_obj(*vcap); if (!vcap) return ERR_PTR(-ENOMEM); + /* Retrieve the pipe_id */ + vcap->pipe_id = dcmipp_name_to_pipe_id(entity_name); + if (vcap->pipe_id < 0) { + ret = -EIO; + dev_err(dev, "failed to retrieve pipe_id\n"); + goto err_free_vcap; + } + + /* Initialize supported format table format */ + if (vcap->pipe_id == 0) { + vcap->pix_map = dcmipp_capture_dump_pix_map_list; + vcap->pix_map_array_size = ARRAY_SIZE(dcmipp_capture_dump_pix_map_list); + } else { + vcap->pix_map = dcmipp_capture_pixel_pix_map_list; + vcap->pix_map_array_size = ARRAY_SIZE(dcmipp_capture_pixel_pix_map_list); + } + /* Allocate the pads */ vcap->ved.pads = dcmipp_pads_init(1, &pad_flag); if (IS_ERR(vcap->ved.pads)) { @@ -880,10 +1168,12 @@ struct dcmipp_ent_device *dcmipp_bytecap_ent_init(struct device *dev, goto err_free_vcap; } + vcap->ved.dcmipp = dcmipp; + /* Initialize the media entity */ vcap->vdev.entity.name = entity_name; vcap->vdev.entity.function = MEDIA_ENT_F_IO_V4L; - vcap->vdev.entity.ops = &dcmipp_bytecap_entity_ops; + vcap->vdev.entity.ops = &dcmipp_capture_entity_ops; ret = media_entity_pads_init(&vcap->vdev.entity, 1, vcap->ved.pads); if (ret) goto err_clean_pads; @@ -898,7 +1188,7 @@ struct dcmipp_ent_device *dcmipp_bytecap_ent_init(struct device *dev, q->lock = &vcap->lock; q->drv_priv = vcap; q->buf_struct_size = sizeof(struct dcmipp_buf); - q->ops = &dcmipp_bytecap_qops; + q->ops = &dcmipp_capture_qops; q->mem_ops = &vb2_dma_contig_memops; q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; q->min_queued_buffers = 1; @@ -927,21 +1217,21 @@ struct dcmipp_ent_device *dcmipp_bytecap_ent_init(struct device *dev, /* Fill the dcmipp_ent_device struct */ vcap->ved.ent = &vcap->vdev.entity; - vcap->ved.handler = dcmipp_bytecap_irq_callback; - vcap->ved.thread_fn = dcmipp_bytecap_irq_thread; + vcap->ved.handler = dcmipp_capture_irq_callback; + vcap->ved.thread_fn = dcmipp_capture_irq_thread; vcap->dev = dev; - vcap->regs = regs; + vcap->regs = dcmipp->regs; /* Initialize the video_device struct */ vdev = &vcap->vdev; vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_IO_MC; - vdev->release = dcmipp_bytecap_release; - vdev->fops = &dcmipp_bytecap_fops; - vdev->ioctl_ops = &dcmipp_bytecap_ioctl_ops; + vdev->release = dcmipp_capture_release; + vdev->fops = &dcmipp_capture_fops; + vdev->ioctl_ops = &dcmipp_capture_ioctl_ops; vdev->lock = &vcap->lock; vdev->queue = q; - vdev->v4l2_dev = v4l2_dev; + vdev->v4l2_dev = &dcmipp->v4l2_dev; strscpy(vdev->name, entity_name, sizeof(vdev->name)); video_set_drvdata(vdev, &vcap->ved); diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h index fe5f97233f5e..007912c3a7f8 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h @@ -58,16 +58,82 @@ do { \ (fmt)->xfer_func = DCMIPP_XFER_FUNC_DEFAULT; \ } while (0) +struct dcmipp_device { + /* The platform device */ + struct platform_device pdev; + struct device *dev; + + /* Hardware resources */ + void __iomem *regs; + struct clk *mclk; + struct clk *kclk; + + /* The pipeline configuration */ + const struct dcmipp_pipeline_config *pipe_cfg; + + /* The Associated media_device parent */ + struct media_device mdev; + struct media_pipeline pipe; + + /* Internal v4l2 parent device*/ + struct v4l2_device v4l2_dev; + + /* Entities */ + struct dcmipp_ent_device **entity; + + struct v4l2_async_notifier notifier; +}; + +#define DCMIPP_ENT_LINK(src, srcpad, sink, sinkpad, link_flags) { \ + .src_ent = src, \ + .src_pad = srcpad, \ + .sink_ent = sink, \ + .sink_pad = sinkpad, \ + .flags = link_flags, \ +} + +/* Structure which describes individual configuration for each entity */ +struct dcmipp_ent_config { + const char *name; + struct dcmipp_ent_device *(*init) + (const char *entity_name, + struct dcmipp_device *dcmipp); + void (*release)(struct dcmipp_ent_device *ved); +}; + +/* Structure which describes links between entities */ +struct dcmipp_ent_link { + unsigned int src_ent; + u16 src_pad; + unsigned int sink_ent; + u16 sink_pad; + u32 flags; +}; + +/* Structure which describes the whole topology */ +struct dcmipp_pipeline_config { + const struct dcmipp_ent_config *ents; + size_t num_ents; + const struct dcmipp_ent_link *links; + size_t num_links; + u32 hw_revision; + bool has_csi2; + bool needs_mclk; + bool has_swapyuv; +}; + /** * struct dcmipp_ent_device - core struct that represents a node in the topology * * @ent: the pointer to struct media_entity for the node + * @dcmipp: the pointer to the parent dcmipp_device * @pads: the list of pads of the node * @bus: struct v4l2_mbus_config_parallel describing input bus * @bus_type: type of input bus (parallel or BT656) * @handler: irq handler dedicated to the subdev * @handler_ret: value returned by the irq handler * @thread_fn: threaded irq handler + * @cmsr2: dcmipp status reg value captured upon an interrupt * * The DCMIPP provides a single IRQ line and a IRQ status registers for all * subdevs, hence once the main irq handler (registered at probe time) is @@ -84,6 +150,7 @@ do { \ */ struct dcmipp_ent_device { struct media_entity *ent; + struct dcmipp_device *dcmipp; struct media_pad *pads; /* Parallel input device */ @@ -92,6 +159,13 @@ struct dcmipp_ent_device { irq_handler_t handler; irqreturn_t handler_ret; irq_handler_t thread_fn; + u32 cmsr2; +}; + +enum dcmipp_state { + DCMIPP_STOPPED = 0, + DCMIPP_WAIT_FOR_BUFFER, + DCMIPP_RUNNING, }; /** @@ -199,19 +273,22 @@ static inline void __reg_clear(struct device *dev, void __iomem *base, u32 reg, } /* DCMIPP subdev init / release entry points */ -struct dcmipp_ent_device *dcmipp_inp_ent_init(struct device *dev, - const char *entity_name, - struct v4l2_device *v4l2_dev, - void __iomem *regs); +struct dcmipp_ent_device *dcmipp_inp_ent_init(const char *entity_name, + struct dcmipp_device *dcmipp); void dcmipp_inp_ent_release(struct dcmipp_ent_device *ved); struct dcmipp_ent_device * -dcmipp_byteproc_ent_init(struct device *dev, const char *entity_name, - struct v4l2_device *v4l2_dev, void __iomem *regs); +dcmipp_byteproc_ent_init(const char *entity_name, + struct dcmipp_device *dcmipp); void dcmipp_byteproc_ent_release(struct dcmipp_ent_device *ved); -struct dcmipp_ent_device *dcmipp_bytecap_ent_init(struct device *dev, - const char *entity_name, - struct v4l2_device *v4l2_dev, - void __iomem *regs); -void dcmipp_bytecap_ent_release(struct dcmipp_ent_device *ved); +struct dcmipp_ent_device *dcmipp_capture_ent_init(const char *entity_name, + struct dcmipp_device *dcmipp); +void dcmipp_capture_ent_release(struct dcmipp_ent_device *ved); +struct dcmipp_ent_device *dcmipp_isp_ent_init(const char *entity_name, + struct dcmipp_device *dcmipp); +void dcmipp_isp_ent_release(struct dcmipp_ent_device *ved); +struct dcmipp_ent_device * +dcmipp_pixelproc_ent_init(const char *entity_name, + struct dcmipp_device *dcmipp); +void dcmipp_pixelproc_ent_release(struct dcmipp_ent_device *ved); #endif diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c index 516334541b2c..54e28b4a5b2c 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c @@ -25,38 +25,7 @@ #define DCMIPP_MDEV_MODEL_NAME "DCMIPP MDEV" -#define DCMIPP_ENT_LINK(src, srcpad, sink, sinkpad, link_flags) { \ - .src_ent = src, \ - .src_pad = srcpad, \ - .sink_ent = sink, \ - .sink_pad = sinkpad, \ - .flags = link_flags, \ -} - -struct dcmipp_device { - /* The platform device */ - struct platform_device pdev; - struct device *dev; - - /* Hardware resources */ - void __iomem *regs; - struct clk *mclk; - struct clk *kclk; - - /* The pipeline configuration */ - const struct dcmipp_pipeline_config *pipe_cfg; - - /* The Associated media_device parent */ - struct media_device mdev; - - /* Internal v4l2 parent device*/ - struct v4l2_device v4l2_dev; - - /* Entities */ - struct dcmipp_ent_device **entity; - - struct v4l2_async_notifier notifier; -}; +#define DCMIPP_CMSR2 0x3f8 static inline struct dcmipp_device * notifier_to_dcmipp(struct v4l2_async_notifier *n) @@ -64,35 +33,6 @@ notifier_to_dcmipp(struct v4l2_async_notifier *n) return container_of(n, struct dcmipp_device, notifier); } -/* Structure which describes individual configuration for each entity */ -struct dcmipp_ent_config { - const char *name; - struct dcmipp_ent_device *(*init) - (struct device *dev, const char *entity_name, - struct v4l2_device *v4l2_dev, void __iomem *regs); - void (*release)(struct dcmipp_ent_device *ved); -}; - -/* Structure which describes links between entities */ -struct dcmipp_ent_link { - unsigned int src_ent; - u16 src_pad; - unsigned int sink_ent; - u16 sink_pad; - u32 flags; -}; - -/* Structure which describes the whole topology */ -struct dcmipp_pipeline_config { - const struct dcmipp_ent_config *ents; - size_t num_ents; - const struct dcmipp_ent_link *links; - size_t num_links; - u32 hw_revision; - bool has_csi2; - bool needs_mclk; -}; - /* -------------------------------------------------------------------------- * Topology Configuration */ @@ -110,8 +50,8 @@ static const struct dcmipp_ent_config stm32mp13_ent_config[] = { }, { .name = "dcmipp_dump_capture", - .init = dcmipp_bytecap_ent_init, - .release = dcmipp_bytecap_ent_release, + .init = dcmipp_capture_ent_init, + .release = dcmipp_capture_ent_release, }, }; @@ -135,6 +75,11 @@ static const struct dcmipp_pipeline_config stm32mp13_pipe_cfg = { .hw_revision = DCMIPP_STM32MP13_VERR }; +#define ID_MAIN_ISP 3 +#define ID_MAIN_POSTPROC 4 +#define ID_MAIN_CAPTURE 5 +#define ID_AUX_POSTPROC 6 +#define ID_AUX_CAPTURE 7 static const struct dcmipp_ent_config stm32mp25_ent_config[] = { { .name = "dcmipp_input", @@ -148,16 +93,49 @@ static const struct dcmipp_ent_config stm32mp25_ent_config[] = { }, { .name = "dcmipp_dump_capture", - .init = dcmipp_bytecap_ent_init, - .release = dcmipp_bytecap_ent_release, + .init = dcmipp_capture_ent_init, + .release = dcmipp_capture_ent_release, + }, + { + .name = "dcmipp_main_isp", + .init = dcmipp_isp_ent_init, + .release = dcmipp_isp_ent_release, + }, + { + .name = "dcmipp_main_postproc", + .init = dcmipp_pixelproc_ent_init, + .release = dcmipp_pixelproc_ent_release, + }, + { + .name = "dcmipp_main_capture", + .init = dcmipp_capture_ent_init, + .release = dcmipp_capture_ent_release, + }, + { + .name = "dcmipp_aux_postproc", + .init = dcmipp_pixelproc_ent_init, + .release = dcmipp_pixelproc_ent_release, + }, + { + .name = "dcmipp_aux_capture", + .init = dcmipp_capture_ent_init, + .release = dcmipp_capture_ent_release, }, }; static const struct dcmipp_ent_link stm32mp25_ent_links[] = { - DCMIPP_ENT_LINK(ID_INPUT, 1, ID_DUMP_BYTEPROC, 0, - MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE), + DCMIPP_ENT_LINK(ID_INPUT, 1, ID_DUMP_BYTEPROC, 0, MEDIA_LNK_FL_ENABLED), DCMIPP_ENT_LINK(ID_DUMP_BYTEPROC, 1, ID_DUMP_CAPTURE, 0, MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE), + DCMIPP_ENT_LINK(ID_INPUT, 2, ID_MAIN_ISP, 0, 0), + DCMIPP_ENT_LINK(ID_MAIN_ISP, 1, ID_MAIN_POSTPROC, 0, + MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE), + DCMIPP_ENT_LINK(ID_MAIN_ISP, 2, ID_AUX_POSTPROC, 0, 0), + DCMIPP_ENT_LINK(ID_MAIN_POSTPROC, 1, ID_MAIN_CAPTURE, 0, + MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE), + DCMIPP_ENT_LINK(ID_INPUT, 3, ID_AUX_POSTPROC, 0, 0), + DCMIPP_ENT_LINK(ID_AUX_POSTPROC, 1, ID_AUX_CAPTURE, 0, + MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE), }; #define DCMIPP_STM32MP25_VERR 0x30 @@ -168,7 +146,8 @@ static const struct dcmipp_pipeline_config stm32mp25_pipe_cfg = { .num_links = ARRAY_SIZE(stm32mp25_ent_links), .hw_revision = DCMIPP_STM32MP25_VERR, .has_csi2 = true, - .needs_mclk = true + .needs_mclk = true, + .has_swapyuv = true }; #define LINK_FLAG_TO_STR(f) ((f) == 0 ? "" :\ @@ -221,9 +200,7 @@ static int dcmipp_create_subdevs(struct dcmipp_device *dcmipp) dev_dbg(dcmipp->dev, "add subdev %s\n", name); dcmipp->entity[i] = - dcmipp->pipe_cfg->ents[i].init(dcmipp->dev, name, - &dcmipp->v4l2_dev, - dcmipp->regs); + dcmipp->pipe_cfg->ents[i].init(name, dcmipp); if (IS_ERR(dcmipp->entity[i])) { dev_err(dcmipp->dev, "failed to init subdev %s\n", name); @@ -278,10 +255,15 @@ static irqreturn_t dcmipp_irq_callback(int irq, void *arg) struct dcmipp_ent_device *ved; irqreturn_t ret = IRQ_HANDLED; unsigned int i; + u32 cmsr2; + + /* Centralized read of CMSR2 */ + cmsr2 = reg_read(dcmipp, DCMIPP_CMSR2); /* Call irq handler of each entities of pipeline */ for (i = 0; i < dcmipp->pipe_cfg->num_ents; i++) { ved = dcmipp->entity[i]; + ved->cmsr2 = cmsr2; if (ved->handler) ved->handler_ret = ved->handler(irq, ved); else if (ved->thread_fn) diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-input.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-input.c index 7d3f5857cbfe..e66a9456deb6 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-input.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-input.c @@ -43,12 +43,18 @@ #define DCMIPP_CMCR_INSEL BIT(0) #define DCMIPP_P0FSCR 0x404 -#define DCMIPP_P0FSCR_DTMODE_MASK GENMASK(17, 16) -#define DCMIPP_P0FSCR_DTMODE_SHIFT 16 -#define DCMIPP_P0FSCR_DTMODE_DTIDA 0x00 +#define DCMIPP_P1FSCR 0x804 +#define DCMIPP_P2FSCR 0xC04 +#define DCMIPP_PXFSCR_DTMODE_MASK GENMASK(17, 16) +#define DCMIPP_PXFSCR_DTMODE_SHIFT 16 +#define DCMIPP_PXFSCR_DTMODE_DTIDA 0x00 #define DCMIPP_P0FSCR_DTMODE_ALLDT 0x03 -#define DCMIPP_P0FSCR_DTIDA_MASK GENMASK(5, 0) -#define DCMIPP_P0FSCR_DTIDA_SHIFT 0 +#define DCMIPP_PXFSCR_DTIDA_MASK GENMASK(5, 0) +#define DCMIPP_PXFSCR_DTIDA_SHIFT 0 + +#define DCMIPP_PXFSCR(a) (((a) == 0) ? DCMIPP_P0FSCR :\ + ((a) == 1) ? DCMIPP_P1FSCR :\ + DCMIPP_P2FSCR) #define IS_SINK(pad) (!(pad)) #define IS_SRC(pad) ((pad)) @@ -80,15 +86,15 @@ static const struct dcmipp_inp_pix_map dcmipp_inp_pix_map_list[] = { PIXMAP_SINK_SRC_PRCR_SWAP(RGB888_3X8, RGB888_3X8, RGB888, 0, MIPI_CSI2_DT_RGB888), PIXMAP_SINK_SRC_PRCR_SWAP(RGB888_1X24, RGB888_1X24, RGB888, 0, MIPI_CSI2_DT_RGB888), /* YUV422 */ - PIXMAP_SINK_SRC_PRCR_SWAP(YUYV8_2X8, YUYV8_2X8, YUV422, 1, MIPI_CSI2_DT_YUV422_8B), + PIXMAP_SINK_SRC_PRCR_SWAP(YUYV8_2X8, YUYV8_2X8, YUV422, 0, MIPI_CSI2_DT_YUV422_8B), PIXMAP_SINK_SRC_PRCR_SWAP(YUYV8_1X16, YUYV8_1X16, YUV422, 0, MIPI_CSI2_DT_YUV422_8B), - PIXMAP_SINK_SRC_PRCR_SWAP(YUYV8_2X8, UYVY8_2X8, YUV422, 0, MIPI_CSI2_DT_YUV422_8B), - PIXMAP_SINK_SRC_PRCR_SWAP(UYVY8_2X8, UYVY8_2X8, YUV422, 1, MIPI_CSI2_DT_YUV422_8B), + PIXMAP_SINK_SRC_PRCR_SWAP(YUYV8_2X8, UYVY8_2X8, YUV422, 1, MIPI_CSI2_DT_YUV422_8B), + PIXMAP_SINK_SRC_PRCR_SWAP(UYVY8_2X8, UYVY8_2X8, YUV422, 0, MIPI_CSI2_DT_YUV422_8B), PIXMAP_SINK_SRC_PRCR_SWAP(UYVY8_1X16, UYVY8_1X16, YUV422, 0, MIPI_CSI2_DT_YUV422_8B), - PIXMAP_SINK_SRC_PRCR_SWAP(UYVY8_2X8, YUYV8_2X8, YUV422, 0, MIPI_CSI2_DT_YUV422_8B), - PIXMAP_SINK_SRC_PRCR_SWAP(YVYU8_2X8, YVYU8_2X8, YUV422, 1, MIPI_CSI2_DT_YUV422_8B), + PIXMAP_SINK_SRC_PRCR_SWAP(UYVY8_2X8, YUYV8_2X8, YUV422, 1, MIPI_CSI2_DT_YUV422_8B), + PIXMAP_SINK_SRC_PRCR_SWAP(YVYU8_2X8, YVYU8_2X8, YUV422, 0, MIPI_CSI2_DT_YUV422_8B), PIXMAP_SINK_SRC_PRCR_SWAP(YVYU8_1X16, YVYU8_1X16, YUV422, 0, MIPI_CSI2_DT_YUV422_8B), - PIXMAP_SINK_SRC_PRCR_SWAP(VYUY8_2X8, VYUY8_2X8, YUV422, 1, MIPI_CSI2_DT_YUV422_8B), + PIXMAP_SINK_SRC_PRCR_SWAP(VYUY8_2X8, VYUY8_2X8, YUV422, 0, MIPI_CSI2_DT_YUV422_8B), PIXMAP_SINK_SRC_PRCR_SWAP(VYUY8_1X16, VYUY8_1X16, YUV422, 0, MIPI_CSI2_DT_YUV422_8B), /* GREY */ PIXMAP_SINK_SRC_PRCR_SWAP(Y8_1X8, Y8_1X8, G8, 0, MIPI_CSI2_DT_RAW8), @@ -269,11 +275,13 @@ static void dcmipp_inp_adjust_fmt(struct dcmipp_inp_device *inp, } static int dcmipp_inp_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { struct dcmipp_inp_device *inp = v4l2_get_subdevdata(sd); struct v4l2_mbus_framefmt *mf; + int pad; if (v4l2_subdev_is_streaming(sd)) return -EBUSY; @@ -298,9 +306,11 @@ static int dcmipp_inp_set_fmt(struct v4l2_subdev *sd, /* When setting the sink format, report that format on the src pad */ if (IS_SINK(fmt->pad)) { - mf = v4l2_subdev_state_get_format(sd_state, 1); - *mf = fmt->format; - dcmipp_inp_adjust_fmt(inp, mf, 1); + for (pad = 1; pad < sd->entity.num_pads; pad++) { + mf = v4l2_subdev_state_get_format(sd_state, pad); + *mf = fmt->format; + dcmipp_inp_adjust_fmt(inp, mf, pad); + } } return 0; @@ -356,8 +366,20 @@ static int dcmipp_inp_configure_parallel(struct dcmipp_inp_device *inp, val |= vpix->prcr_format << DCMIPP_PRCR_FORMAT_SHIFT; /* swap cycles */ - if (vpix->prcr_swapcycles) - val |= DCMIPP_PRCR_SWAPCYCLES; + /* + * Table dcmipp_inp_pix_map_list take into consideration that SWAPYUV + * bit is available when dealing with 16bit YUV formats. If it is not + * available (such as on stm32mp13), swapcycle setting should be + * reversed + */ + if (!inp->ved.dcmipp->pipe_cfg->has_swapyuv && + (src_fmt->code == MEDIA_BUS_FMT_YUYV8_2X8 || + src_fmt->code == MEDIA_BUS_FMT_YVYU8_2X8 || + src_fmt->code == MEDIA_BUS_FMT_UYVY8_2X8 || + src_fmt->code == MEDIA_BUS_FMT_VYUY8_2X8)) + val |= (!vpix->prcr_swapcycles ? DCMIPP_PRCR_SWAPCYCLES : 0); + else + val |= (vpix->prcr_swapcycles ? DCMIPP_PRCR_SWAPCYCLES : 0); reg_write(inp, DCMIPP_PRCR, val); @@ -371,7 +393,8 @@ static int dcmipp_inp_configure_parallel(struct dcmipp_inp_device *inp, } static int dcmipp_inp_configure_csi(struct dcmipp_inp_device *inp, - struct v4l2_subdev_state *state) + struct v4l2_subdev_state *state, + u32 pad) { const struct dcmipp_inp_pix_map *vpix; struct v4l2_mbus_framefmt *sink_fmt; @@ -379,7 +402,7 @@ static int dcmipp_inp_configure_csi(struct dcmipp_inp_device *inp, /* Get format information */ sink_fmt = v4l2_subdev_state_get_format(state, 0); - src_fmt = v4l2_subdev_state_get_format(state, 1); + src_fmt = v4l2_subdev_state_get_format(state, pad); vpix = dcmipp_inp_pix_map_by_code(sink_fmt->code, src_fmt->code); if (!vpix) { @@ -387,22 +410,28 @@ static int dcmipp_inp_configure_csi(struct dcmipp_inp_device *inp, return -EINVAL; } - /* Apply configuration on each input pipe */ - reg_clear(inp, DCMIPP_P0FSCR, - DCMIPP_P0FSCR_DTMODE_MASK | DCMIPP_P0FSCR_DTIDA_MASK); + /* Perform the configuration on the related pad/pipe */ + reg_clear(inp, DCMIPP_PXFSCR(pad - 1), + DCMIPP_PXFSCR_DTMODE_MASK | DCMIPP_PXFSCR_DTIDA_MASK); /* In case of JPEG we don't know the DT so we allow all data */ /* * TODO - check instead dt == 0 for the time being to allow other * unknown data-type */ - if (!vpix->dt) - reg_set(inp, DCMIPP_P0FSCR, - DCMIPP_P0FSCR_DTMODE_ALLDT << DCMIPP_P0FSCR_DTMODE_SHIFT); - else + if (!vpix->dt) { + if (pad != 1) { + dev_err(inp->dev, "JPEG only available on pipe 0\n"); + return -EINVAL; + } + /* Only available on Pipe #0 */ reg_set(inp, DCMIPP_P0FSCR, - vpix->dt << DCMIPP_P0FSCR_DTIDA_SHIFT | - DCMIPP_P0FSCR_DTMODE_DTIDA); + DCMIPP_P0FSCR_DTMODE_ALLDT << DCMIPP_PXFSCR_DTMODE_SHIFT); + } else { + reg_set(inp, DCMIPP_PXFSCR(pad - 1), + vpix->dt << DCMIPP_PXFSCR_DTIDA_SHIFT | + DCMIPP_PXFSCR_DTMODE_DTIDA); + } /* Select the DCMIPP CSI interface */ reg_write(inp, DCMIPP_CMCR, DCMIPP_CMCR_INSEL); @@ -420,20 +449,24 @@ static int dcmipp_inp_enable_streams(struct v4l2_subdev *sd, struct media_pad *s_pad; int ret = 0; - /* Get source subdev */ - s_pad = media_pad_remote_pad_first(&sd->entity.pads[0]); - if (!s_pad || !is_media_entity_v4l2_subdev(s_pad->entity)) - return -EINVAL; - s_subdev = media_entity_to_v4l2_subdev(s_pad->entity); - if (inp->ved.bus_type == V4L2_MBUS_PARALLEL || inp->ved.bus_type == V4L2_MBUS_BT656) ret = dcmipp_inp_configure_parallel(inp, state); else if (inp->ved.bus_type == V4L2_MBUS_CSI2_DPHY) - ret = dcmipp_inp_configure_csi(inp, state); + ret = dcmipp_inp_configure_csi(inp, state, pad); if (ret) return ret; + /* If there where no other pad enabled, then enable the source subdev */ + if (sd->enabled_pads) + return 0; + + /* Get source subdev */ + s_pad = media_pad_remote_pad_first(&sd->entity.pads[0]); + if (!s_pad || !is_media_entity_v4l2_subdev(s_pad->entity)) + return -EINVAL; + s_subdev = media_entity_to_v4l2_subdev(s_pad->entity); + ret = v4l2_subdev_enable_streams(s_subdev, s_pad->index, BIT_ULL(0)); if (ret < 0) { dev_err(inp->dev, @@ -454,6 +487,10 @@ static int dcmipp_inp_disable_streams(struct v4l2_subdev *sd, struct media_pad *s_pad; int ret; + /* Don't do anything if there are still other pads enabled */ + if ((sd->enabled_pads & ~BIT(pad))) + return 0; + /* Get source subdev */ s_pad = media_pad_remote_pad_first(&sd->entity.pads[0]); if (!s_pad || !is_media_entity_v4l2_subdev(s_pad->entity)) @@ -515,15 +552,16 @@ void dcmipp_inp_ent_release(struct dcmipp_ent_device *ved) dcmipp_ent_sd_unregister(ved, &inp->sd); } -struct dcmipp_ent_device *dcmipp_inp_ent_init(struct device *dev, - const char *entity_name, - struct v4l2_device *v4l2_dev, - void __iomem *regs) +struct dcmipp_ent_device *dcmipp_inp_ent_init(const char *entity_name, + struct dcmipp_device *dcmipp) { struct dcmipp_inp_device *inp; const unsigned long pads_flag[] = { MEDIA_PAD_FL_SINK, MEDIA_PAD_FL_SOURCE, + MEDIA_PAD_FL_SOURCE, MEDIA_PAD_FL_SOURCE, }; + struct device *dev = dcmipp->dev; + u16 num_pads = ARRAY_SIZE(pads_flag); int ret; /* Allocate the inp struct */ @@ -531,12 +569,17 @@ struct dcmipp_ent_device *dcmipp_inp_ent_init(struct device *dev, if (!inp) return ERR_PTR(-ENOMEM); - inp->regs = regs; + inp->regs = dcmipp->regs; + inp->ved.dcmipp = dcmipp; + + /* For DCMIPP without CSI2, there is only a single pipe hence 2 pads */ + if (!inp->ved.dcmipp->pipe_cfg->has_csi2) + num_pads = 2; /* Initialize ved and sd */ - ret = dcmipp_ent_sd_register(&inp->ved, &inp->sd, v4l2_dev, + ret = dcmipp_ent_sd_register(&inp->ved, &inp->sd, &dcmipp->v4l2_dev, entity_name, MEDIA_ENT_F_VID_IF_BRIDGE, - ARRAY_SIZE(pads_flag), pads_flag, + num_pads, pads_flag, &dcmipp_inp_int_ops, &dcmipp_inp_ops, NULL, NULL); if (ret) { diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-isp.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-isp.c new file mode 100644 index 000000000000..088301c20547 --- /dev/null +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-isp.c @@ -0,0 +1,493 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for STM32 Digital Camera Memory Interface Pixel Processor + * + * Copyright (C) STMicroelectronics SA 2026 + * Authors: Hugues Fruchet <hugues.fruchet@foss.st.com> + * Alain Volmat <alain.volmat@foss.st.com> + * for STMicroelectronics. + */ + +#include <linux/v4l2-mediabus.h> +#include <media/v4l2-rect.h> +#include <media/v4l2-subdev.h> + +#include "dcmipp-common.h" +#include "dcmipp-pixelcommon.h" + +#define DCMIPP_P1FSCR 0x804 +#define DCMIPP_P1FSCR_PIPEDIFF BIT(18) + +#define DCMIPP_P1SRCR 0x820 +#define DCMIPP_P1SRCR_LASTLINE_SHIFT 0 +#define DCMIPP_P1SRCR_FIRSTLINEDEL_SHIFT 12 +#define DCMIPP_P1SRCR_CROPEN BIT(15) + +#define DCMIPP_P1DECR 0x830 +#define DCMIPP_P1DECR_ENABLE BIT(0) +#define DCMIPP_P1DECR_HDEC_SHIFT 1 +#define DCMIPP_P1DECR_VDEC_SHIFT 3 + +#define DCMIPP_P1DMCR 0x870 +#define DCMIPP_P1DMCR_ENABLE BIT(0) +#define DCMIPP_P1DMCR_TYPE_SHIFT 1 +#define DCMIPP_P1DMCR_TYPE_MASK GENMASK(2, 1) +#define DCMIPP_P1DMCR_TYPE_RGGB 0x0 +#define DCMIPP_P1DMCR_TYPE_GRBG 0x1 +#define DCMIPP_P1DMCR_TYPE_GBRG 0x2 +#define DCMIPP_P1DMCR_TYPE_BGGR 0x3 + +#define ISP_MEDIA_BUS_SINK_FMT_DEFAULT MEDIA_BUS_FMT_RGB565_1X16 +#define ISP_MEDIA_BUS_SRC_FMT_DEFAULT MEDIA_BUS_FMT_RGB888_1X24 + +struct dcmipp_isp_device { + struct dcmipp_ent_device ved; + struct v4l2_subdev sd; + struct device *dev; + + void __iomem *regs; +}; + +static const struct v4l2_mbus_framefmt fmt_default = { + .width = DCMIPP_FMT_WIDTH_DEFAULT, + .height = DCMIPP_FMT_HEIGHT_DEFAULT, + .code = ISP_MEDIA_BUS_SINK_FMT_DEFAULT, + .field = V4L2_FIELD_NONE, + .colorspace = DCMIPP_COLORSPACE_DEFAULT, + .ycbcr_enc = DCMIPP_YCBCR_ENC_DEFAULT, + .quantization = DCMIPP_QUANTIZATION_DEFAULT, + .xfer_func = DCMIPP_XFER_FUNC_DEFAULT, +}; + +static inline unsigned int dcmipp_isp_set_compose(__u32 size, __u32 req) +{ + unsigned int i = 0; + + if (req > size) + return size; + + /* Maximum decimation factor is 8 */ + while (size > req && i++ < 3) + size /= 2; + + return size; +} + +static void dcmipp_isp_adjust_fmt(struct v4l2_mbus_framefmt *fmt, u32 pad) +{ + /* Only accept code in the pix map table */ + if (!dcmipp_pixelpipe_pix_map_by_code(fmt->code, DCMIPP_ISP, pad)) + fmt->code = IS_SRC(pad) ? ISP_MEDIA_BUS_SRC_FMT_DEFAULT : + ISP_MEDIA_BUS_SINK_FMT_DEFAULT; + + fmt->width = clamp_t(u32, fmt->width, DCMIPP_FRAME_MIN_WIDTH, + DCMIPP_FRAME_MAX_WIDTH) & ~1; + fmt->height = clamp_t(u32, fmt->height, DCMIPP_FRAME_MIN_HEIGHT, + DCMIPP_FRAME_MAX_HEIGHT); + + if (fmt->field == V4L2_FIELD_ANY || fmt->field == V4L2_FIELD_ALTERNATE) + fmt->field = V4L2_FIELD_NONE; + + dcmipp_colorimetry_clamp(fmt); +} + +static int dcmipp_isp_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + for (unsigned int i = 0; i < sd->entity.num_pads; i++) { + struct v4l2_mbus_framefmt *mf; + + mf = v4l2_subdev_state_get_format(state, i); + *mf = fmt_default; + mf->code = IS_SRC(i) ? ISP_MEDIA_BUS_SRC_FMT_DEFAULT : + ISP_MEDIA_BUS_SINK_FMT_DEFAULT; + + if (IS_SINK(i)) { + struct v4l2_rect r = { + .top = 0, + .left = 0, + .width = DCMIPP_FMT_WIDTH_DEFAULT, + .height = DCMIPP_FMT_HEIGHT_DEFAULT, + }; + + *v4l2_subdev_state_get_crop(state, i) = r; + *v4l2_subdev_state_get_compose(state, i) = r; + } + } + + return 0; +} + +static int dcmipp_isp_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + return dcmipp_pixelpipe_enum_mbus_code(DCMIPP_ISP, code); +} + +static int dcmipp_isp_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_size_enum *fse) +{ + return dcmipp_pixelpipe_enum_frame_size(DCMIPP_ISP, fse); +} + +static int dcmipp_isp_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE && + v4l2_subdev_is_streaming(sd)) + return -EBUSY; + + dcmipp_isp_adjust_fmt(&fmt->format, fmt->pad); + + if (IS_SINK(fmt->pad)) { + struct v4l2_mbus_framefmt *src_fmt = + v4l2_subdev_state_get_format(state, 1); + struct v4l2_rect r = { + .top = 0, + .left = 0, + .width = fmt->format.width, + .height = fmt->format.height, + }; + + /* Adjust SINK pad crop/compose */ + *v4l2_subdev_state_get_crop(state, 0) = r; + *v4l2_subdev_state_get_compose(state, 0) = r; + + /* Forward format to SRC pads */ + *src_fmt = fmt->format; + src_fmt->code = dcmipp_pixelpipe_src_format(fmt->format.code); + *v4l2_subdev_state_get_format(state, 2) = *src_fmt; + } else { + struct v4l2_mbus_framefmt *sink_fmt = + v4l2_subdev_state_get_format(state, 0); + struct v4l2_rect *compose = + v4l2_subdev_state_get_compose(state, 0); + + fmt->format = *sink_fmt; + fmt->format.code = dcmipp_pixelpipe_src_format(sink_fmt->code); + if (compose->width && compose->height) { + fmt->format.width = compose->width; + fmt->format.height = compose->height; + } + /* Set to the 2nd SRC pad */ + *v4l2_subdev_state_get_format(state, fmt->pad == 1 ? 2 : 1) = + fmt->format; + } + + /* Update the selected pad format */ + *v4l2_subdev_state_get_format(state, fmt->pad) = fmt->format; + + return 0; +} + +static void dcmipp_isp_adjust_crop(struct v4l2_rect *r, + const struct v4l2_mbus_framefmt *fmt) +{ + struct v4l2_rect crop_min = { + .width = fmt->width, + .height = DCMIPP_FRAME_MIN_HEIGHT, + }; + + /* + * Crop is related here to the statistics removal, that is + * firsts and/or lasts lines of the frame. Up to the first + * 7 lines can be skipped + */ +#define DCMIPP_ISP_MAX_TOP_CROP 7 + v4l2_rect_set_min_size(r, &crop_min); + if (r->top > DCMIPP_ISP_MAX_TOP_CROP) + r->top = DCMIPP_ISP_MAX_TOP_CROP; + if ((r->height + r->top) > fmt->height) + r->height = fmt->height - r->top; +} + +static int dcmipp_isp_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *s) +{ + struct dcmipp_isp_device *isp = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *sink_fmt, *src_fmt; + struct v4l2_rect *crop, *compose; + __u32 pad; + + if (IS_SRC(s->pad)) + return -EINVAL; + + if (s->which == V4L2_SUBDEV_FORMAT_ACTIVE && + v4l2_subdev_is_streaming(sd)) + return -EBUSY; + + crop = v4l2_subdev_state_get_crop(state, s->pad); + compose = v4l2_subdev_state_get_compose(state, s->pad); + + switch (s->target) { + case V4L2_SEL_TGT_CROP: + sink_fmt = v4l2_subdev_state_get_format(state, s->pad); + dcmipp_isp_adjust_crop(&s->r, sink_fmt); + + *crop = s->r; + *compose = s->r; + + dev_dbg(isp->dev, "s_selection: crop (%d,%d)/%ux%u\n", + crop->left, crop->top, crop->width, crop->height); + break; + case V4L2_SEL_TGT_COMPOSE: + s->r.top = 0; + s->r.left = 0; + if (s->r.width < DCMIPP_FRAME_MIN_WIDTH) + s->r.width = DCMIPP_FRAME_MIN_WIDTH; + s->r.width = dcmipp_isp_set_compose(crop->width, s->r.width); + if (s->r.height < DCMIPP_FRAME_MIN_HEIGHT) + s->r.height = DCMIPP_FRAME_MIN_HEIGHT; + s->r.height = dcmipp_isp_set_compose(crop->height, s->r.height); + *compose = s->r; + + dev_dbg(isp->dev, "s_selection: compose (%d,%d)/%ux%u\n", + compose->left, compose->top, + compose->width, compose->height); + break; + default: + return -EINVAL; + } + + /* Update the source pad size */ + for (pad = 1; pad < sd->entity.num_pads; pad++) { + src_fmt = v4l2_subdev_state_get_format(state, pad); + src_fmt->width = s->r.width; + src_fmt->height = s->r.height; + } + + return 0; +} + +#define STM32_DCMIPP_IS_BAYER_VARIANT(code, variant) \ + ((code) == MEDIA_BUS_FMT_S##variant##8_1X8 || \ + (code) == MEDIA_BUS_FMT_S##variant##10_1X10 || \ + (code) == MEDIA_BUS_FMT_S##variant##12_1X12 || \ + (code) == MEDIA_BUS_FMT_S##variant##14_1X14 || \ + (code) == MEDIA_BUS_FMT_S##variant##16_1X16) +static void dcmipp_isp_config_demosaicing(struct dcmipp_isp_device *isp, + struct v4l2_subdev_state *state) +{ + __u32 code = v4l2_subdev_state_get_format(state, 0)->code; + unsigned int val = 0; + + /* Disable demosaicing */ + reg_clear(isp, DCMIPP_P1DMCR, + DCMIPP_P1DMCR_ENABLE | DCMIPP_P1DMCR_TYPE_MASK); + + /* Only perform demosaicing if format is bayer */ + if (code < MEDIA_BUS_FMT_SBGGR8_1X8 || code >= MEDIA_BUS_FMT_JPEG_1X8) + return; + + dev_dbg(isp->dev, "Input is RawBayer, enable Demosaicing\n"); + + if (STM32_DCMIPP_IS_BAYER_VARIANT(code, BGGR)) + val = DCMIPP_P1DMCR_TYPE_BGGR << DCMIPP_P1DMCR_TYPE_SHIFT; + else if (STM32_DCMIPP_IS_BAYER_VARIANT(code, GBRG)) + val = DCMIPP_P1DMCR_TYPE_GBRG << DCMIPP_P1DMCR_TYPE_SHIFT; + else if (STM32_DCMIPP_IS_BAYER_VARIANT(code, GRBG)) + val = DCMIPP_P1DMCR_TYPE_GRBG << DCMIPP_P1DMCR_TYPE_SHIFT; + else if (STM32_DCMIPP_IS_BAYER_VARIANT(code, RGGB)) + val = DCMIPP_P1DMCR_TYPE_RGGB << DCMIPP_P1DMCR_TYPE_SHIFT; + + val |= DCMIPP_P1DMCR_ENABLE; + + reg_set(isp, DCMIPP_P1DMCR, val); +} + +static bool dcmipp_isp_is_aux_output_enabled(struct dcmipp_isp_device *isp) +{ + struct media_link *link; + + for_each_media_entity_data_link(isp->ved.ent, link) { + if (link->source != &isp->ved.pads[2]) + continue; + + if (!(link->flags & MEDIA_LNK_FL_ENABLED)) + continue; + + if (!strcmp(link->sink->entity->name, "dcmipp_aux_postproc")) + return true; + } + + return false; +} + +static void dcmipp_isp_config_decimation(struct dcmipp_isp_device *isp, + struct v4l2_subdev_state *state) +{ + struct v4l2_rect *crop = v4l2_subdev_state_get_crop(state, 0); + struct v4l2_rect *compose = v4l2_subdev_state_get_compose(state, 0); + u32 decr; + + decr = (fls(crop->width / compose->width) - 1) << DCMIPP_P1DECR_HDEC_SHIFT | + (fls(crop->height / compose->height) - 1) << DCMIPP_P1DECR_VDEC_SHIFT; + if (decr) + decr |= DCMIPP_P1DECR_ENABLE; + + reg_write(isp, DCMIPP_P1DECR, decr); +} + +static int dcmipp_isp_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct dcmipp_isp_device *isp = v4l2_get_subdevdata(sd); + struct v4l2_rect *crop = v4l2_subdev_state_get_crop(state, 0); + struct v4l2_subdev *s_subdev; + struct media_pad *s_pad; + int ret; + + /* Perform configuration only if no other pad is enabled */ + if (sd->enabled_pads) + return 0; + + /* Get source subdev */ + s_pad = media_pad_remote_pad_first(&sd->entity.pads[0]); + if (!s_pad || !is_media_entity_v4l2_subdev(s_pad->entity)) + return -EINVAL; + s_subdev = media_entity_to_v4l2_subdev(s_pad->entity); + + /* Check if link between ISP & Pipe2 postproc is enabled */ + if (dcmipp_isp_is_aux_output_enabled(isp)) + reg_clear(isp, DCMIPP_P1FSCR, DCMIPP_P1FSCR_PIPEDIFF); + else + reg_set(isp, DCMIPP_P1FSCR, DCMIPP_P1FSCR_PIPEDIFF); + + /* Configure Statistic Removal */ + crop = v4l2_subdev_state_get_crop(state, 0); + reg_write(isp, DCMIPP_P1SRCR, + ((crop->top << DCMIPP_P1SRCR_FIRSTLINEDEL_SHIFT) | + (crop->height << DCMIPP_P1SRCR_LASTLINE_SHIFT) | + DCMIPP_P1SRCR_CROPEN)); + + /* Configure Decimation */ + dcmipp_isp_config_decimation(isp, state); + + /* Configure Demosaicing */ + dcmipp_isp_config_demosaicing(isp, state); + + ret = v4l2_subdev_enable_streams(s_subdev, s_pad->index, BIT_ULL(0)); + if (ret < 0) { + dev_err(isp->dev, + "failed to start source subdev streaming (%d)\n", ret); + return ret; + } + + return 0; +} + +static int dcmipp_isp_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct dcmipp_isp_device *isp = v4l2_get_subdevdata(sd); + struct v4l2_subdev *s_subdev; + struct media_pad *s_pad; + int ret; + + /* Don't do anything if there are still other pads enabled */ + if ((sd->enabled_pads & ~BIT(pad))) + return 0; + + /* Get source subdev */ + s_pad = media_pad_remote_pad_first(&sd->entity.pads[0]); + if (!s_pad || !is_media_entity_v4l2_subdev(s_pad->entity)) + return -EINVAL; + s_subdev = media_entity_to_v4l2_subdev(s_pad->entity); + + /* Disable all blocks */ + reg_write(isp, DCMIPP_P1SRCR, 0); + reg_write(isp, DCMIPP_P1DECR, 0); + reg_write(isp, DCMIPP_P1DMCR, 0); + + ret = v4l2_subdev_disable_streams(s_subdev, s_pad->index, BIT_ULL(0)); + if (ret < 0) { + dev_err(isp->dev, + "failed to disable source subdev streaming (%d)\n", ret); + return ret; + } + + return 0; +} + +static const struct v4l2_subdev_pad_ops dcmipp_isp_pad_ops = { + .enum_mbus_code = dcmipp_isp_enum_mbus_code, + .enum_frame_size = dcmipp_isp_enum_frame_size, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = dcmipp_isp_set_fmt, + .get_selection = dcmipp_pixelpipe_get_selection, + .set_selection = dcmipp_isp_set_selection, + .enable_streams = dcmipp_isp_enable_streams, + .disable_streams = dcmipp_isp_disable_streams, +}; + +static const struct v4l2_subdev_video_ops dcmipp_isp_video_ops = { + .s_stream = v4l2_subdev_s_stream_helper, +}; + +static const struct v4l2_subdev_ops dcmipp_isp_ops = { + .pad = &dcmipp_isp_pad_ops, + .video = &dcmipp_isp_video_ops, +}; + +static void dcmipp_isp_release(struct v4l2_subdev *sd) +{ + struct dcmipp_isp_device *isp = v4l2_get_subdevdata(sd); + + kfree(isp); +} + +static const struct v4l2_subdev_internal_ops dcmipp_isp_int_ops = { + .init_state = dcmipp_isp_init_state, + .release = dcmipp_isp_release, +}; + +void dcmipp_isp_ent_release(struct dcmipp_ent_device *ved) +{ + struct dcmipp_isp_device *isp = + container_of(ved, struct dcmipp_isp_device, ved); + + dcmipp_ent_sd_unregister(ved, &isp->sd); +} + +struct dcmipp_ent_device *dcmipp_isp_ent_init(const char *entity_name, + struct dcmipp_device *dcmipp) +{ + struct dcmipp_isp_device *isp; + const unsigned long pads_flag[] = { + MEDIA_PAD_FL_SINK, MEDIA_PAD_FL_SOURCE, + MEDIA_PAD_FL_SOURCE, + }; + int ret; + + /* Allocate the isp struct */ + isp = kzalloc_obj(*isp); + if (!isp) + return ERR_PTR(-ENOMEM); + + isp->regs = dcmipp->regs; + + /* Initialize ved and sd */ + ret = dcmipp_ent_sd_register(&isp->ved, &isp->sd, + &dcmipp->v4l2_dev, entity_name, + MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER, + ARRAY_SIZE(pads_flag), pads_flag, + &dcmipp_isp_int_ops, &dcmipp_isp_ops, + NULL, NULL); + if (ret) { + kfree(isp); + return ERR_PTR(ret); + } + + isp->ved.dcmipp = dcmipp; + isp->dev = dcmipp->dev; + + return &isp->ved; +} diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelcommon.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelcommon.c new file mode 100644 index 000000000000..d6f19db848d5 --- /dev/null +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelcommon.c @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for STM32 Digital Camera Memory Interface Pixel Processor + * + * Copyright (C) STMicroelectronics SA 2026 + * Authors: Hugues Fruchet <hugues.fruchet@foss.st.com> + * Alain Volmat <alain.volmat@foss.st.com> + * for STMicroelectronics. + */ + +#include <linux/v4l2-mediabus.h> +#include <media/v4l2-rect.h> +#include <media/v4l2-subdev.h> + +#include "dcmipp-common.h" +#include "dcmipp-pixelcommon.h" + +#define DCMIPP_ENT(id, pad) (1 << (2 * (id) + (pad))) +#define DCMIPP_ISP_SINK (DCMIPP_ENT(DCMIPP_ISP, 0)) +#define DCMIPP_ISP_SRC (DCMIPP_ENT(DCMIPP_ISP, 1)) +#define DCMIPP_ISP_INOUT (DCMIPP_ISP_SINK | DCMIPP_ISP_SRC) +#define DCMIPP_MAIN_POSTPROC_SINK (DCMIPP_ENT(DCMIPP_MAIN, 0)) +#define DCMIPP_MAIN_POSTPROC_SRC (DCMIPP_ENT(DCMIPP_MAIN, 1)) +#define DCMIPP_MAIN_POSTPROC_INOUT \ + (DCMIPP_MAIN_POSTPROC_SINK | DCMIPP_MAIN_POSTPROC_SRC) +#define DCMIPP_AUX_POSTPROC_SINK (DCMIPP_ENT(DCMIPP_AUX, 0)) +#define DCMIPP_AUX_POSTPROC_SRC (DCMIPP_ENT(DCMIPP_AUX, 1)) +#define DCMIPP_AUX_POSTPROC_INOUT \ + (DCMIPP_AUX_POSTPROC_SINK | DCMIPP_AUX_POSTPROC_SRC) +#define DCMIPP_ALL_POSTPROC_SINK \ + (DCMIPP_MAIN_POSTPROC_SINK | DCMIPP_AUX_POSTPROC_SINK) +#define DCMIPP_ALL_POSTPROC_INOUT \ + (DCMIPP_MAIN_POSTPROC_INOUT | DCMIPP_AUX_POSTPROC_INOUT) + +#define PIXMAP_MBUS(mbus, applicable_pipes) \ + { \ + .code = MEDIA_BUS_FMT_##mbus, \ + .pipes = applicable_pipes, \ + } +static const struct dcmipp_pixelpipe_pix_map +dcmipp_pixel_formats_list[] = { + /* RGB formats */ + /* RGB565 / RGB888 */ + PIXMAP_MBUS(RGB565_2X8_LE, DCMIPP_AUX_POSTPROC_SINK | DCMIPP_ISP_SINK), + PIXMAP_MBUS(RGB565_1X16, DCMIPP_AUX_POSTPROC_SINK | DCMIPP_ISP_SINK), + PIXMAP_MBUS(RGB888_3X8, DCMIPP_AUX_POSTPROC_SINK | DCMIPP_ISP_SINK), + PIXMAP_MBUS(RGB888_1X24, DCMIPP_ALL_POSTPROC_INOUT | DCMIPP_ISP_INOUT), + /* YUV formats */ + PIXMAP_MBUS(YUYV8_2X8, DCMIPP_AUX_POSTPROC_SINK | DCMIPP_ISP_SINK), + PIXMAP_MBUS(UYVY8_1X16, DCMIPP_AUX_POSTPROC_SINK | DCMIPP_ISP_SINK), + PIXMAP_MBUS(YUV8_1X24, DCMIPP_ALL_POSTPROC_INOUT | DCMIPP_ISP_SRC), + /* GREY */ + PIXMAP_MBUS(Y8_1X8, DCMIPP_AUX_POSTPROC_SINK | DCMIPP_ISP_SINK), + PIXMAP_MBUS(Y10_1X10, DCMIPP_AUX_POSTPROC_SINK | DCMIPP_ISP_SINK), + PIXMAP_MBUS(Y12_1X12, DCMIPP_AUX_POSTPROC_SINK | DCMIPP_ISP_SINK), + PIXMAP_MBUS(Y14_1X14, DCMIPP_AUX_POSTPROC_SINK | DCMIPP_ISP_SINK), + /* Raw Bayer */ + /* Raw 8 */ + PIXMAP_MBUS(SBGGR8_1X8, DCMIPP_ISP_SINK), + PIXMAP_MBUS(SGBRG8_1X8, DCMIPP_ISP_SINK), + PIXMAP_MBUS(SGRBG8_1X8, DCMIPP_ISP_SINK), + PIXMAP_MBUS(SRGGB8_1X8, DCMIPP_ISP_SINK), + /* Raw 10 */ + PIXMAP_MBUS(SBGGR10_1X10, DCMIPP_ISP_SINK), + PIXMAP_MBUS(SGBRG10_1X10, DCMIPP_ISP_SINK), + PIXMAP_MBUS(SGRBG10_1X10, DCMIPP_ISP_SINK), + PIXMAP_MBUS(SRGGB10_1X10, DCMIPP_ISP_SINK), + /* Raw 12 */ + PIXMAP_MBUS(SBGGR12_1X12, DCMIPP_ISP_SINK), + PIXMAP_MBUS(SGBRG12_1X12, DCMIPP_ISP_SINK), + PIXMAP_MBUS(SGRBG12_1X12, DCMIPP_ISP_SINK), + PIXMAP_MBUS(SRGGB12_1X12, DCMIPP_ISP_SINK), + /* Raw 14 */ + PIXMAP_MBUS(SBGGR14_1X14, DCMIPP_ISP_SINK), + PIXMAP_MBUS(SGBRG14_1X14, DCMIPP_ISP_SINK), + PIXMAP_MBUS(SGRBG14_1X14, DCMIPP_ISP_SINK), + PIXMAP_MBUS(SRGGB14_1X14, DCMIPP_ISP_SINK), +}; + +const struct dcmipp_pixelpipe_pix_map * +dcmipp_pixelpipe_pix_map_by_code(__u32 code, unsigned int id, unsigned int pad) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(dcmipp_pixel_formats_list); i++) { + if (dcmipp_pixel_formats_list[i].code == code && + dcmipp_pixel_formats_list[i].pipes & DCMIPP_ENT(id, pad)) + return &dcmipp_pixel_formats_list[i]; + } + + return NULL; +} + +int dcmipp_pixelpipe_enum_mbus_code(unsigned int id, + struct v4l2_subdev_mbus_code_enum *code) +{ + unsigned int index = code->index; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(dcmipp_pixel_formats_list); i++) { + if (!(dcmipp_pixel_formats_list[i].pipes & + DCMIPP_ENT(id, code->pad))) + continue; + + if (index == 0) + break; + + index--; + } + + if (i == ARRAY_SIZE(dcmipp_pixel_formats_list)) + return -EINVAL; + + code->code = dcmipp_pixel_formats_list[i].code; + + return 0; +} + +int dcmipp_pixelpipe_enum_frame_size(unsigned int id, + struct v4l2_subdev_frame_size_enum *fse) +{ + const struct dcmipp_pixelpipe_pix_map *vpix; + + if (fse->index) + return -EINVAL; + + /* Only accept code in the pix map table */ + vpix = dcmipp_pixelpipe_pix_map_by_code(fse->code, id, fse->pad); + if (!vpix) + return -EINVAL; + + fse->min_width = DCMIPP_FRAME_MIN_WIDTH; + fse->max_width = DCMIPP_FRAME_MAX_WIDTH; + fse->min_height = DCMIPP_FRAME_MIN_HEIGHT; + fse->max_height = DCMIPP_FRAME_MAX_HEIGHT; + + return 0; +} + +int dcmipp_pixelpipe_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *s) +{ + struct v4l2_mbus_framefmt *sink_fmt; + + if (IS_SRC(s->pad)) + return -EINVAL; + + switch (s->target) { + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + s->r = *v4l2_subdev_state_get_crop(state, s->pad); + break; + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + sink_fmt = v4l2_subdev_state_get_format(state, s->pad); + s->r.top = 0; + s->r.left = 0; + s->r.width = sink_fmt->width; + s->r.height = sink_fmt->height; + break; + case V4L2_SEL_TGT_COMPOSE: + s->r = *v4l2_subdev_state_get_compose(state, s->pad); + break; + default: + return -EINVAL; + } + + return 0; +} + +__u32 dcmipp_pixelpipe_src_format(__u32 input_format) +{ + if (input_format >= MEDIA_BUS_FMT_Y8_1X8 && + input_format < MEDIA_BUS_FMT_SBGGR8_1X8) + return MEDIA_BUS_FMT_YUV8_1X24; + + return MEDIA_BUS_FMT_RGB888_1X24; +} diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelcommon.h b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelcommon.h new file mode 100644 index 000000000000..2d7c16c36c7d --- /dev/null +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelcommon.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Driver for STM32 Digital Camera Memory Interface Pixel Processor + * + * Copyright (C) STMicroelectronics SA 2026 + * Authors: Hugues Fruchet <hugues.fruchet@foss.st.com> + * Alain Volmat <alain.volmat@foss.st.com> + * for STMicroelectronics. + */ + +#ifndef _DCMIPP_PIXELCOMMON_H +#define _DCMIPP_PIXELCOMMON_H + +#define IS_SINK(pad) (!(pad)) +#define IS_SRC(pad) ((pad)) + +#define DCMIPP_ISP 0 +#define DCMIPP_MAIN 1 +#define DCMIPP_AUX 2 + +struct dcmipp_pixelpipe_pix_map { + __u32 code; + __u32 pipes; +}; + +const struct dcmipp_pixelpipe_pix_map * +dcmipp_pixelpipe_pix_map_by_code(__u32 code, unsigned int id, unsigned int pad); + +int dcmipp_pixelpipe_enum_mbus_code(unsigned int id, + struct v4l2_subdev_mbus_code_enum *code); + +int dcmipp_pixelpipe_enum_frame_size(unsigned int id, + struct v4l2_subdev_frame_size_enum *fse); + +int dcmipp_pixelpipe_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *s); + +__u32 dcmipp_pixelpipe_src_format(__u32 input_format); + +#endif diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelproc.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelproc.c new file mode 100644 index 000000000000..7d8e5f0863bf --- /dev/null +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelproc.c @@ -0,0 +1,942 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for STM32 Digital Camera Memory Interface Pixel Processor + * + * Copyright (C) STMicroelectronics SA 2026 + * Authors: Hugues Fruchet <hugues.fruchet@foss.st.com> + * Alain Volmat <alain.volmat@foss.st.com> + * for STMicroelectronics. + */ + +#include <linux/pm_runtime.h> +#include <linux/v4l2-mediabus.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> +#include <media/v4l2-rect.h> +#include <media/v4l2-subdev.h> +#include <uapi/linux/media/st/dcmipp_config.h> + +#include "dcmipp-common.h" +#include "dcmipp-pixelcommon.h" + +#define DCMIPP_P1CRSTR 0x904 +#define DCMIPP_P2CRSTR 0xD04 +#define DCMIPP_PxCRSTR(id) (((id) == 1) ? DCMIPP_P1CRSTR :\ + DCMIPP_P2CRSTR) +#define DCMIPP_PxCRSTR_HSTART_SHIFT 0 +#define DCMIPP_PxCRSTR_VSTART_SHIFT 16 +#define DCMIPP_P1CRSZR 0x908 +#define DCMIPP_P2CRSZR 0xD08 +#define DCMIPP_PxCRSZR(id) (((id) == 1) ? DCMIPP_P1CRSZR :\ + DCMIPP_P2CRSZR) +#define DCMIPP_PxCRSZR_ENABLE BIT(31) +#define DCMIPP_PxCRSZR_HSIZE_SHIFT 0 +#define DCMIPP_PxCRSZR_VSIZE_SHIFT 16 + +#define DCMIPP_P1DCCR 0x90C +#define DCMIPP_P2DCCR 0xD0C +#define DCMIPP_PxDCCR(id) (((id) == 1) ? DCMIPP_P1DCCR :\ + DCMIPP_P2DCCR) +#define DCMIPP_PxDCCR_ENABLE BIT(0) +#define DCMIPP_PxDCCR_HDEC_SHIFT 1 +#define DCMIPP_PxDCCR_VDEC_SHIFT 3 + +#define DCMIPP_P1DSCR 0x910 +#define DCMIPP_P2DSCR 0xD10 +#define DCMIPP_PxDSCR(id) (((id) == 1) ? DCMIPP_P1DSCR :\ + DCMIPP_P2DSCR) +#define DCMIPP_PxDSCR_HDIV_SHIFT 0 +#define DCMIPP_PxDSCR_VDIV_SHIFT 16 +#define DCMIPP_PxDSCR_ENABLE BIT(31) + +#define DCMIPP_P1DSRTIOR 0x914 +#define DCMIPP_P2DSRTIOR 0xD14 +#define DCMIPP_PxDSRTIOR(id) (((id) == 1) ? DCMIPP_P1DSRTIOR :\ + DCMIPP_P2DSRTIOR) +#define DCMIPP_PxDSRTIOR_HRATIO_SHIFT 0 +#define DCMIPP_PxDSRTIOR_HRATIO_MASK GENMASK(15, 0) +#define DCMIPP_PxDSRTIOR_VRATIO_SHIFT 16 +#define DCMIPP_PxDSRTIOR_VRATIO_MASK GENMASK(31, 16) + +#define DCMIPP_P1DSSZR 0x918 +#define DCMIPP_P2DSSZR 0xD18 +#define DCMIPP_PxDSSZR(id) (((id) == 1) ? DCMIPP_P1DSSZR :\ + DCMIPP_P2DSSZR) +#define DCMIPP_PxDSSZR_HSIZE_SHIFT 0 +#define DCMIPP_PxDSSZR_HSIZE_MASK GENMASK(11, 0) +#define DCMIPP_PxDSSZR_VSIZE_SHIFT 16 +#define DCMIPP_PxDSSZR_VSIZE_MASK GENMASK(27, 16) + +#define DCMIPP_P1GMCR 0x970 +#define DCMIPP_P2GMCR 0xD70 +#define DCMIPP_PxGMCR(id) (((id) == 1) ? DCMIPP_P1GMCR :\ + DCMIPP_P2GMCR) +#define DCMIPP_PxGMCR_ENABLE BIT(0) + +#define DCMIPP_P1YUVCR 0x980 +#define DCMIPP_P1YUVCR_ENABLE BIT(0) +#define DCMIPP_P1YUVCR_TYPE_RGB BIT(1) +#define DCMIPP_P1YUVCR_CLAMP BIT(2) +#define DCMIPP_P1YUVRR1 0x984 +#define DCMIPP_P1YUVRR2 0x988 +#define DCMIPP_P1YUVGR1 0x98C +#define DCMIPP_P1YUVGR2 0x990 +#define DCMIPP_P1YUVBR1 0x994 +#define DCMIPP_P1YUVBR2 0x998 + +#define PIXELPROC_MEDIA_BUS_FMT_DEFAULT MEDIA_BUS_FMT_RGB888_1X24 + +/* Macro for negative coefficient, 11 bits coded */ +#define N11(val) (((val) ^ 0x7ff) + 1) +/* Macro for added value, 10 bits coded */ +#define N10(val) (((val) ^ 0x3ff) + 1) + +/* Macro to convert row matrix to DCMIPP PxCCCyy register value */ +#define CCTBL(rr, rg, rb, ra, gr, gg, gb, ga, br, bg, bb, ba) \ + .conv_matrix = { \ + ((rg) << 16 | (rr)), ((ra) << 16 | (rb)), \ + ((gg) << 16 | (gr)), ((ga) << 16 | (gb)), \ + ((bg) << 16 | (br)), ((ba) << 16 | (bb)) } + +struct dcmipp_colorconv_config { + unsigned int conv_matrix[6]; + bool clamping; + bool clamping_as_rgb; +}; + +static const struct dcmipp_colorconv_config dcmipp_rgbfull_to_yuv601full = { + /* R G B Add */ + CCTBL(131, N11(110), N11(21), 128, /* Cr */ + 77, 150, 29, 0, /* Y */ + N11(44), N11(87), 131, 128), /* Cb */ +}; + +static const struct dcmipp_colorconv_config dcmipp_rgbfull_to_yuv601lim = { + /* R G B Add */ + CCTBL(112, N11(94), N11(18), 128, /* Cr */ + 66, 129, 25, 16, /* Y */ + N11(38), N11(74), 112, 128), /* Cb */ + .clamping = true, +}; + +static const struct dcmipp_colorconv_config dcmipp_rgbfull_to_yuv709full = { + /* R G B Add */ + CCTBL(131, N11(119), N11(12), 128, /* Cr */ + 55, 183, 18, 0, /* Y */ + N11(30), N11(101), 131, 128), /* Cb */ +}; + +static const struct dcmipp_colorconv_config dcmipp_rgbfull_to_yuv709lim = { + /* R G B Add */ + CCTBL(112, N11(102), N11(10), 128, /* Cr */ + 47, 157, 16, 16, /* Y */ + N11(26), N11(87), 112, 128), /* Cb */ + .clamping = true, +}; + +static const struct dcmipp_colorconv_config dcmipp_rgblim_to_yuv601lim = { + /* R G B Add */ + CCTBL(131, N11(110), N11(21), 128, /* Cr */ + 77, 150, 29, 0, /* Y */ + N11(44), N11(87), 131, 128), /* Cb */ + .clamping = true, +}; + +static const struct dcmipp_colorconv_config dcmipp_rgblim_to_yuv709lim = { + /* R G B Add */ + CCTBL(131, N11(119), N11(12), 128, /* Cr */ + 55, 183, 18, 0, /* Y */ + N11(30), N11(101), 131, 128), /* Cb */ + .clamping = true, +}; + +static const struct dcmipp_colorconv_config dcmipp_yuv601full_to_rgbfull = { + /* Cr Y Cb Add */ + CCTBL(351, 256, 0, N10(175), /* R */ + N11(179), 256, N11(86), 132, /* G */ + 0, 256, 443, N10(222)), /* B */ +}; + +static const struct dcmipp_colorconv_config dcmipp_yuv601lim_to_rgbfull = { + /* Cr Y Cb Add */ + CCTBL(409, 298, 0, N10(223), /* R */ + N11(208), 298, N11(100), 135, /* G */ + 0, 298, 517, N10(277)), /* B */ +}; + +static const struct dcmipp_colorconv_config dcmipp_yuv601lim_to_rgblim = { + /* Cr Y Cb Add */ + CCTBL(351, 256, 0, N10(175), /* R */ + N11(179), 256, N11(86), 132, /* G */ + 0, 256, 443, N10(222)), /* B */ + .clamping = true, + .clamping_as_rgb = true, +}; + +static const struct dcmipp_colorconv_config dcmipp_yuv709full_to_rgbfull = { + /* Cr Y Cb Add */ + CCTBL(394, 256, 0, N10(197), /* R */ + N11(118), 256, N11(47), 82, /* G */ + 0, 256, 456, N10(232)), /* B */ +}; + +static const struct dcmipp_colorconv_config dcmipp_yuv709lim_to_rgbfull = { + /* Cr Y Cb Add */ + CCTBL(459, 298, 0, N10(248), /* R */ + N11(137), 298, N11(55), 77, /* G */ + 0, 298, 541, N10(289)), /* B */ +}; + +static const struct dcmipp_colorconv_config dcmipp_yuv709lim_to_rgblim = { + /* Cr Y Cb Add */ + CCTBL(394, 256, 0, N10(197), /* R */ + N11(118), 256, N11(47), 82, /* G */ + 0, 256, 465, N10(232)), /* B */ + .clamping = true, + .clamping_as_rgb = true, +}; + +/* cconv_matrices[src_fmt][src_range][sink_fmt][sink_range] */ +static const struct dcmipp_colorconv_config *dcmipp_cconv_cfgs[3][2][3][2] = { + /* RGB */ + { + /* RGB full range */ + { + /* RGB full range => RGB */ + { + NULL, NULL, + }, + /* RGB full range => YUV601 */ + { + &dcmipp_rgbfull_to_yuv601full, + &dcmipp_rgbfull_to_yuv601lim, + }, + /* RGB full range => YUV709 */ + { + &dcmipp_rgbfull_to_yuv709full, + &dcmipp_rgbfull_to_yuv709lim, + }, + }, + /* RGB limited range */ + { + /* RGB limited range => RGB */ + { + NULL, NULL, + }, + /* RGB limited range => YUV601 */ + { + NULL, &dcmipp_rgblim_to_yuv601lim, + }, + /* RGB limited range => YUV709 */ + { + NULL, &dcmipp_rgblim_to_yuv709lim, + }, + }, + }, + /* YUV601 */ + { + /* YUV601 full range */ + { + /* YUV601 full range => RGB */ + { + &dcmipp_yuv601full_to_rgbfull, NULL, + }, + /* YUV601 full range => YUV601 */ + { + NULL, NULL, + }, + /* YUV601 full range => YUV709 */ + { + NULL, NULL, + }, + }, + /* YUV601 limited range */ + { + /* YUV601 limited range => RGB */ + { + &dcmipp_yuv601lim_to_rgbfull, + &dcmipp_yuv601lim_to_rgblim, + }, + /* YUV601 limited range => YUV601 */ + { + NULL, NULL, + }, + /* YUV601 limited range => YUV709 */ + { + NULL, NULL, + }, + }, + }, + /* YUV709 */ + { + /* YUV709 full range */ + { + /* YUV709 full range => RGB */ + { + &dcmipp_yuv709full_to_rgbfull, NULL, + }, + /* YUV709 full range => YUV601 */ + { + NULL, NULL, + }, + /* YUV709 full range => YUV709 */ + { + NULL, NULL, + }, + }, + /* YUV709 limited range */ + { + /* YUV709 limited range => RGB */ + { + &dcmipp_yuv709lim_to_rgbfull, + &dcmipp_yuv709lim_to_rgblim, + }, + /* YUV709 limited range => YUV601 */ + { + NULL, NULL, + }, + /* YUV709 limited range => YUV709 */ + { + NULL, NULL, + }, + }, + }, +}; + +enum dcmipp_cconv_fmt { + FMT_RGB = 0, + FMT_YUV601, + FMT_YUV709 +}; + +static inline enum dcmipp_cconv_fmt to_cconv_fmt(struct v4l2_mbus_framefmt *fmt) +{ + /* YUV format codes are within the 0x2xxx */ + if (fmt->code >= MEDIA_BUS_FMT_Y8_1X8 && + fmt->code < MEDIA_BUS_FMT_SBGGR8_1X8) { + if (fmt->ycbcr_enc == V4L2_YCBCR_ENC_709) + return FMT_YUV709; + else + return FMT_YUV601; + } + + /* All other formats are referred as RGB, indeed, demosaicing bloc + * generate RGB format + */ + return FMT_RGB; +}; + +#define FMT_STR(f) ({ \ + typeof(f) __f = (f); \ + (__f) == FMT_RGB ? "RGB" : \ + (__f) == FMT_YUV601 ? "YUV601" : \ + (__f) == FMT_YUV709 ? "YUV709" : "?"; }) + +enum dcmipp_cconv_range { + RANGE_FULL = 0, + RANGE_LIMITED, +}; + +static inline enum dcmipp_cconv_range +to_cconv_range(struct v4l2_mbus_framefmt *fmt) +{ + if (fmt->quantization == V4L2_QUANTIZATION_FULL_RANGE) + return RANGE_FULL; + + return RANGE_LIMITED; +}; + +#define RANGE_STR(range) ((range) == RANGE_FULL ? "full" : "limited") + +struct dcmipp_pixelproc_device { + struct dcmipp_ent_device ved; + struct v4l2_subdev sd; + struct device *dev; + bool streaming; + + void __iomem *regs; + struct v4l2_ctrl_handler ctrls; + + u32 pipe_id; +}; + +static const struct v4l2_mbus_framefmt fmt_default = { + .width = DCMIPP_FMT_WIDTH_DEFAULT, + .height = DCMIPP_FMT_HEIGHT_DEFAULT, + .code = PIXELPROC_MEDIA_BUS_FMT_DEFAULT, + .field = V4L2_FIELD_NONE, + .colorspace = DCMIPP_COLORSPACE_DEFAULT, + .ycbcr_enc = DCMIPP_YCBCR_ENC_DEFAULT, + .quantization = DCMIPP_QUANTIZATION_DEFAULT, + .xfer_func = DCMIPP_XFER_FUNC_DEFAULT, +}; + +static const struct v4l2_rect min_rect = { + .width = DCMIPP_FRAME_MIN_WIDTH, + .height = DCMIPP_FRAME_MIN_HEIGHT, + .top = 0, + .left = 0, +}; + +/* + * Downscale is a combination of both decimation block (1/2/4/8) + * and downsize block (up to 8x) for a total of maximum downscale of 64 + */ +#define DCMIPP_MAX_DECIMATION_RATIO 8 +#define DCMIPP_MAX_DOWNSIZE_RATIO 8 +#define DCMIPP_MAX_DOWNSCALE_RATIO 64 + +/* + * Functions handling controls + */ +static int dcmipp_pixelproc_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct dcmipp_pixelproc_device *pixelproc = + container_of(ctrl->handler, + struct dcmipp_pixelproc_device, ctrls); + + if (!pm_runtime_get_if_in_use(pixelproc->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_DCMIPP_PIXELPROC_GAMMA_CORRECTION_ENABLE: + reg_write(pixelproc, DCMIPP_PxGMCR(pixelproc->pipe_id), + (ctrl->val ? DCMIPP_PxGMCR_ENABLE : 0)); + break; + } + + pm_runtime_put(pixelproc->dev); + + return 0; +}; + +static const struct v4l2_ctrl_ops dcmipp_pixelproc_ctrl_ops = { + .s_ctrl = dcmipp_pixelproc_s_ctrl, +}; + +static const struct v4l2_ctrl_config dcmipp_pixelproc_ctrls[] = { + { + .ops = &dcmipp_pixelproc_ctrl_ops, + .id = V4L2_CID_DCMIPP_PIXELPROC_GAMMA_CORRECTION_ENABLE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Gamma Correction Enable", + .min = 0, + .max = 1, + .step = 1, + .def = 0, + } +}; + +static void dcmipp_pixelproc_adjust_crop(struct v4l2_rect *r, + const struct v4l2_mbus_framefmt *fmt) +{ + struct v4l2_rect src_rect = { + .top = 0, + .left = 0, + .width = fmt->width, + .height = fmt->height, + }; + + /* Disallow rectangles smaller than the minimal one. */ + v4l2_rect_set_min_size(r, &min_rect); + v4l2_rect_map_inside(r, &src_rect); +} + +static void +dcmipp_pixelproc_adjust_fmt(struct dcmipp_pixelproc_device *pixelproc, + struct v4l2_mbus_framefmt *fmt, u32 pad) +{ + const struct dcmipp_pixelpipe_pix_map *vpix; + + /* Only accept code in the pix map table */ + vpix = dcmipp_pixelpipe_pix_map_by_code(fmt->code, + pixelproc->pipe_id == 1 ? DCMIPP_MAIN : DCMIPP_AUX, + pad); + if (!vpix) + fmt->code = PIXELPROC_MEDIA_BUS_FMT_DEFAULT; + + fmt->width = clamp_t(u32, fmt->width, DCMIPP_FRAME_MIN_WIDTH, + DCMIPP_FRAME_MAX_WIDTH); + fmt->height = clamp_t(u32, fmt->height, DCMIPP_FRAME_MIN_HEIGHT, + DCMIPP_FRAME_MAX_HEIGHT); + + if (fmt->field == V4L2_FIELD_ANY || fmt->field == V4L2_FIELD_ALTERNATE) + fmt->field = V4L2_FIELD_NONE; + + dcmipp_colorimetry_clamp(fmt); +} + +static int dcmipp_pixelproc_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + unsigned int i; + + for (i = 0; i < sd->entity.num_pads; i++) { + *v4l2_subdev_state_get_format(state, i) = fmt_default; + + if (IS_SINK(i)) { + struct v4l2_rect r = { + .top = 0, + .left = 0, + .width = DCMIPP_FMT_WIDTH_DEFAULT, + .height = DCMIPP_FMT_HEIGHT_DEFAULT, + }; + *v4l2_subdev_state_get_crop(state, i) = r; + *v4l2_subdev_state_get_compose(state, i) = r; + } + } + + return 0; +} + +static int +dcmipp_pixelproc_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct dcmipp_pixelproc_device *pixelproc = v4l2_get_subdevdata(sd); + + return dcmipp_pixelpipe_enum_mbus_code(pixelproc->pipe_id == 1 ? DCMIPP_MAIN : DCMIPP_AUX, + code); +} + +static int +dcmipp_pixelproc_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct dcmipp_pixelproc_device *pixelproc = v4l2_get_subdevdata(sd); + + return dcmipp_pixelpipe_enum_frame_size(pixelproc->pipe_id == 1 ? DCMIPP_MAIN : DCMIPP_AUX, + fse); +} + +static int dcmipp_pixelproc_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + struct dcmipp_pixelproc_device *pixelproc = v4l2_get_subdevdata(sd); + + if (v4l2_subdev_is_streaming(sd)) + return -EBUSY; + + dcmipp_pixelproc_adjust_fmt(pixelproc, &fmt->format, fmt->pad); + + if (IS_SINK(fmt->pad)) { + struct v4l2_mbus_framefmt *src_fmt = + v4l2_subdev_state_get_format(state, 1); + struct v4l2_rect r = { + .top = 0, + .left = 0, + .width = fmt->format.width, + .height = fmt->format.height, + }; + + /* Adjust SINK pad crop/compose */ + *v4l2_subdev_state_get_crop(state, 0) = r; + *v4l2_subdev_state_get_compose(state, 0) = r; + + /* Forward format to SRC pad */ + *src_fmt = fmt->format; + src_fmt->code = dcmipp_pixelpipe_src_format(fmt->format.code); + } else { + struct v4l2_rect *compose = + v4l2_subdev_state_get_compose(state, 0); + + /* AUX (pipe_nb 2) cannot perform color conv */ + if (pixelproc->pipe_id == 2) { + struct v4l2_mbus_framefmt *sink_fmt = + v4l2_subdev_state_get_format(state, 0); + + fmt->format = *sink_fmt; + fmt->format.code = + dcmipp_pixelpipe_src_format(fmt->format.code); + } + + fmt->format.width = compose->width; + fmt->format.height = compose->height; + } + + /* Update the selected pad format */ + *v4l2_subdev_state_get_format(state, fmt->pad) = fmt->format; + + return 0; +} + +static int dcmipp_pixelproc_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *s) +{ + struct dcmipp_pixelproc_device *pixelproc = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *sink_fmt, *src_fmt; + struct v4l2_rect *crop, *compose; + + if (IS_SRC(s->pad)) + return -EINVAL; + + if (v4l2_subdev_is_streaming(sd)) + return -EBUSY; + + crop = v4l2_subdev_state_get_crop(state, s->pad); + compose = v4l2_subdev_state_get_compose(state, s->pad); + + switch (s->target) { + case V4L2_SEL_TGT_CROP: + sink_fmt = v4l2_subdev_state_get_format(state, s->pad); + dcmipp_pixelproc_adjust_crop(&s->r, sink_fmt); + + *crop = s->r; + *compose = s->r; + + dev_dbg(pixelproc->dev, "s_selection: crop (%d,%d)/%ux%u\n", + crop->left, crop->top, crop->width, crop->height); + break; + case V4L2_SEL_TGT_COMPOSE: + s->r.top = 0; + s->r.left = 0; + s->r.width = clamp_t(u32, s->r.width, + crop->width / DCMIPP_MAX_DOWNSCALE_RATIO, + crop->width); + s->r.height = clamp_t(u32, s->r.height, + crop->height / DCMIPP_MAX_DOWNSCALE_RATIO, + crop->height); + v4l2_rect_set_min_size(&s->r, &min_rect); + *compose = s->r; + + dev_dbg(pixelproc->dev, "s_selection: compose (%d,%d)/%ux%u\n", + compose->left, compose->top, + compose->width, compose->height); + break; + default: + return -EINVAL; + } + + /* Update the source pad size */ + src_fmt = v4l2_subdev_state_get_format(state, 1); + src_fmt->width = s->r.width; + src_fmt->height = s->r.height; + + return 0; +} + +static int +dcmipp_pixelproc_colorconv_config(struct dcmipp_pixelproc_device *pixelproc, + struct v4l2_mbus_framefmt *sink, + struct v4l2_mbus_framefmt *src) +{ + const struct dcmipp_colorconv_config *cconv_cfg; + enum dcmipp_cconv_fmt sink_fmt = to_cconv_fmt(sink); + enum dcmipp_cconv_range sink_range = to_cconv_range(sink); + enum dcmipp_cconv_fmt src_fmt = to_cconv_fmt(src); + enum dcmipp_cconv_range src_range = to_cconv_range(src); + unsigned int val = 0; + int i; + + /* Disable color conversion by default */ + reg_write(pixelproc, DCMIPP_P1YUVCR, 0); + + if (sink_fmt == src_fmt && sink_range == src_range) + return 0; + + /* color conversion */ + cconv_cfg = dcmipp_cconv_cfgs[sink_fmt][sink_range][src_fmt][src_range]; + if (!cconv_cfg) { + dev_err(pixelproc->dev, + "Unsupported color conversion %s-%s => %s-%s\n", + FMT_STR(sink_fmt), RANGE_STR(sink_range), + FMT_STR(src_fmt), RANGE_STR(src_range)); + return -EINVAL; + } + + dev_dbg(pixelproc->dev, "color conversion %s-%s => %s-%s\n", + FMT_STR(sink_fmt), RANGE_STR(sink_range), + FMT_STR(src_fmt), RANGE_STR(src_range)); + + for (i = 0; i < 6; i++) + reg_write(pixelproc, DCMIPP_P1YUVRR1 + (4 * i), + cconv_cfg->conv_matrix[i]); + + if (cconv_cfg->clamping) + val |= DCMIPP_P1YUVCR_CLAMP; + if (cconv_cfg->clamping_as_rgb) + val |= DCMIPP_P1YUVCR_TYPE_RGB; + val |= DCMIPP_P1YUVCR_ENABLE; + + reg_write(pixelproc, DCMIPP_P1YUVCR, val); + + return 0; +} + +#define DCMIPP_PIXELPROC_HVRATIO_CONS 8192 +#define DCMIPP_PIXELPROC_HVRATIO_MAX 65535 +#define DCMIPP_PIXELPROC_HVDIV_CONS 1024 +#define DCMIPP_PIXELPROC_HVDIV_MAX 1023 +static void +dcmipp_pixelproc_set_crop_downscale(struct dcmipp_pixelproc_device *pixelproc, + struct v4l2_rect *compose, + struct v4l2_rect *crop) +{ + unsigned int hratio, vratio, hdiv, vdiv; + unsigned int hdec = 0, vdec = 0; + unsigned int h_post_dec = crop->width; + unsigned int v_post_dec = crop->height; + + /* Configure cropping */ + reg_write(pixelproc, DCMIPP_PxCRSTR(pixelproc->pipe_id), + (crop->top << DCMIPP_PxCRSTR_VSTART_SHIFT) | + (crop->left << DCMIPP_PxCRSTR_HSTART_SHIFT)); + reg_write(pixelproc, DCMIPP_PxCRSZR(pixelproc->pipe_id), + (crop->width << DCMIPP_PxCRSZR_HSIZE_SHIFT) | + (crop->height << DCMIPP_PxCRSZR_VSIZE_SHIFT) | + DCMIPP_PxCRSZR_ENABLE); + + /* Compute decimation factors (HDEC/VDEC) */ + while (compose->width * DCMIPP_MAX_DOWNSIZE_RATIO < h_post_dec) { + hdec++; + h_post_dec /= 2; + } + while (compose->height * DCMIPP_MAX_DOWNSIZE_RATIO < v_post_dec) { + vdec++; + v_post_dec /= 2; + } + + /* Compute downsize factor */ + hratio = h_post_dec * DCMIPP_PIXELPROC_HVRATIO_CONS / + compose->width; + if (hratio > DCMIPP_PIXELPROC_HVRATIO_MAX) + hratio = DCMIPP_PIXELPROC_HVRATIO_MAX; + vratio = v_post_dec * DCMIPP_PIXELPROC_HVRATIO_CONS / + compose->height; + if (vratio > DCMIPP_PIXELPROC_HVRATIO_MAX) + vratio = DCMIPP_PIXELPROC_HVRATIO_MAX; + hdiv = (DCMIPP_PIXELPROC_HVDIV_CONS * compose->width) / + h_post_dec; + if (hdiv > DCMIPP_PIXELPROC_HVDIV_MAX) + hdiv = DCMIPP_PIXELPROC_HVDIV_MAX; + vdiv = (DCMIPP_PIXELPROC_HVDIV_CONS * compose->height) / + v_post_dec; + if (vdiv > DCMIPP_PIXELPROC_HVDIV_MAX) + vdiv = DCMIPP_PIXELPROC_HVDIV_MAX; + + dev_dbg(pixelproc->dev, "%s: decimation config: hdec: 0x%x, vdec: 0x%x\n", + pixelproc->sd.name, + hdec, vdec); + dev_dbg(pixelproc->dev, "%s: downsize config: hratio: 0x%x, vratio: 0x%x, hdiv: 0x%x, vdiv: 0x%x\n", + pixelproc->sd.name, + hratio, vratio, + hdiv, vdiv); + + reg_clear(pixelproc, DCMIPP_PxDCCR(pixelproc->pipe_id), + DCMIPP_PxDCCR_ENABLE); + if (hdec || vdec) + reg_write(pixelproc, DCMIPP_PxDCCR(pixelproc->pipe_id), + (hdec << DCMIPP_PxDCCR_HDEC_SHIFT) | + (vdec << DCMIPP_PxDCCR_VDEC_SHIFT) | + DCMIPP_PxDCCR_ENABLE); + + reg_clear(pixelproc, DCMIPP_PxDSCR(pixelproc->pipe_id), + DCMIPP_PxDSCR_ENABLE); + reg_write(pixelproc, DCMIPP_PxDSRTIOR(pixelproc->pipe_id), + (hratio << DCMIPP_PxDSRTIOR_HRATIO_SHIFT) | + (vratio << DCMIPP_PxDSRTIOR_VRATIO_SHIFT)); + reg_write(pixelproc, DCMIPP_PxDSSZR(pixelproc->pipe_id), + (compose->width << DCMIPP_PxDSSZR_HSIZE_SHIFT) | + (compose->height << DCMIPP_PxDSSZR_VSIZE_SHIFT)); + reg_write(pixelproc, DCMIPP_PxDSCR(pixelproc->pipe_id), + (hdiv << DCMIPP_PxDSCR_HDIV_SHIFT) | + (vdiv << DCMIPP_PxDSCR_VDIV_SHIFT) | + DCMIPP_PxDSCR_ENABLE); +} + +static int dcmipp_pixelproc_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct dcmipp_pixelproc_device *pixelproc = v4l2_get_subdevdata(sd); + struct v4l2_subdev *s_subdev; + struct media_pad *s_pad; + int ret; + + /* Get source subdev */ + s_pad = media_pad_remote_pad_first(&sd->entity.pads[0]); + if (!s_pad || !is_media_entity_v4l2_subdev(s_pad->entity)) + return -EINVAL; + s_subdev = media_entity_to_v4l2_subdev(s_pad->entity); + + /* Configure crop/downscale */ + dcmipp_pixelproc_set_crop_downscale(pixelproc, + v4l2_subdev_state_get_compose(state, 0), + v4l2_subdev_state_get_crop(state, 0)); + + /* Configure YUV Conversion (if applicable) */ + if (pixelproc->pipe_id == 1) { + ret = dcmipp_pixelproc_colorconv_config(pixelproc, + v4l2_subdev_state_get_format(state, 0), + v4l2_subdev_state_get_format(state, 1)); + if (ret) + return ret; + } + + /* Apply customized values from user when stream starts. */ + ret = v4l2_ctrl_handler_setup(pixelproc->sd.ctrl_handler); + if (ret < 0) { + dev_err(pixelproc->dev, + "failed to start source subdev streaming (%d)\n", ret); + return ret; + } + + ret = v4l2_subdev_enable_streams(s_subdev, s_pad->index, BIT_ULL(0)); + if (ret < 0) { + dev_err(pixelproc->dev, + "failed to start source subdev streaming (%d)\n", ret); + return ret; + } + + return 0; +} + +static int dcmipp_pixelproc_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct dcmipp_pixelproc_device *pixelproc = v4l2_get_subdevdata(sd); + struct v4l2_subdev *s_subdev; + struct media_pad *s_pad; + int ret; + + /* Get source subdev */ + s_pad = media_pad_remote_pad_first(&sd->entity.pads[0]); + if (!s_pad || !is_media_entity_v4l2_subdev(s_pad->entity)) + return -EINVAL; + s_subdev = media_entity_to_v4l2_subdev(s_pad->entity); + + ret = v4l2_subdev_disable_streams(s_subdev, s_pad->index, BIT_ULL(0)); + if (ret < 0) + dev_err(pixelproc->dev, + "failed to stop source subdev streaming (%d)\n", + ret); + return ret; +} + +static const struct v4l2_subdev_pad_ops dcmipp_pixelproc_pad_ops = { + .enum_mbus_code = dcmipp_pixelproc_enum_mbus_code, + .enum_frame_size = dcmipp_pixelproc_enum_frame_size, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = dcmipp_pixelproc_set_fmt, + .get_selection = dcmipp_pixelpipe_get_selection, + .set_selection = dcmipp_pixelproc_set_selection, + .enable_streams = dcmipp_pixelproc_enable_streams, + .disable_streams = dcmipp_pixelproc_disable_streams, +}; + +static const struct v4l2_subdev_core_ops dcmipp_pixelproc_core_ops = { + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops dcmipp_pixelproc_video_ops = { + .s_stream = v4l2_subdev_s_stream_helper, +}; + +static const struct v4l2_subdev_ops dcmipp_pixelproc_ops = { + .core = &dcmipp_pixelproc_core_ops, + .pad = &dcmipp_pixelproc_pad_ops, + .video = &dcmipp_pixelproc_video_ops, +}; + +static void dcmipp_pixelproc_release(struct v4l2_subdev *sd) +{ + struct dcmipp_pixelproc_device *pixelproc = v4l2_get_subdevdata(sd); + + v4l2_ctrl_handler_free(&pixelproc->ctrls); + kfree(pixelproc); +} + +static const struct v4l2_subdev_internal_ops dcmipp_pixelproc_int_ops = { + .init_state = dcmipp_pixelproc_init_state, + .release = dcmipp_pixelproc_release, +}; + +void dcmipp_pixelproc_ent_release(struct dcmipp_ent_device *ved) +{ + struct dcmipp_pixelproc_device *pixelproc = + container_of(ved, struct dcmipp_pixelproc_device, ved); + + dcmipp_ent_sd_unregister(ved, &pixelproc->sd); +} + +static int dcmipp_name_to_pipe_id(const char *name) +{ + if (strstr(name, "main")) + return 1; + else if (strstr(name, "aux")) + return 2; + else + return -EINVAL; +} + +struct dcmipp_ent_device * +dcmipp_pixelproc_ent_init(const char *entity_name, + struct dcmipp_device *dcmipp) +{ + struct dcmipp_pixelproc_device *pixelproc; + const unsigned long pads_flag[] = { + MEDIA_PAD_FL_SINK, MEDIA_PAD_FL_SOURCE, + }; + int ret, i; + + /* Allocate the pixelproc struct */ + pixelproc = kzalloc_obj(*pixelproc); + if (!pixelproc) + return ERR_PTR(-ENOMEM); + + pixelproc->regs = dcmipp->regs; + pixelproc->dev = dcmipp->dev; + + /* Pipe identifier */ + pixelproc->pipe_id = dcmipp_name_to_pipe_id(entity_name); + if (pixelproc->pipe_id != 1 && pixelproc->pipe_id != 2) { + dev_err(pixelproc->dev, "failed to retrieve pipe_id\n"); + ret = -EIO; + goto err_kfree; + } + + /* Initialize controls */ + v4l2_ctrl_handler_init(&pixelproc->ctrls, + ARRAY_SIZE(dcmipp_pixelproc_ctrls)); + + for (i = 0; i < ARRAY_SIZE(dcmipp_pixelproc_ctrls); i++) + v4l2_ctrl_new_custom(&pixelproc->ctrls, + &dcmipp_pixelproc_ctrls[i], NULL); + + pixelproc->sd.ctrl_handler = &pixelproc->ctrls; + if (pixelproc->ctrls.error) { + ret = pixelproc->ctrls.error; + dev_err(pixelproc->dev, "control initialization error %d\n", ret); + goto err_ctrl_handler_free; + } + + /* Initialize ved and sd */ + ret = dcmipp_ent_sd_register(&pixelproc->ved, &pixelproc->sd, + &dcmipp->v4l2_dev, entity_name, + MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER, + ARRAY_SIZE(pads_flag), pads_flag, + &dcmipp_pixelproc_int_ops, + &dcmipp_pixelproc_ops, + NULL, NULL); + if (ret) + goto err_ctrl_handler_free; + + pixelproc->ved.dcmipp = dcmipp; + + return &pixelproc->ved; + +err_ctrl_handler_free: + v4l2_ctrl_handler_free(&pixelproc->ctrls); +err_kfree: + kfree(pixelproc); + + return ERR_PTR(ret); +} diff --git a/drivers/media/platform/sunxi/sun4i-csi/sun4i_v4l2.c b/drivers/media/platform/sunxi/sun4i-csi/sun4i_v4l2.c index 744197b0fccb..af35ece1d5cf 100644 --- a/drivers/media/platform/sunxi/sun4i-csi/sun4i_v4l2.c +++ b/drivers/media/platform/sunxi/sun4i-csi/sun4i_v4l2.c @@ -295,6 +295,7 @@ static int sun4i_csi_subdev_get_fmt(struct v4l2_subdev *subdev, } static int sun4i_csi_subdev_set_fmt(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/platform/sunxi/sun6i-csi/sun6i_csi_bridge.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.c index d006d9dd0170..f9ee94fc1293 100644 --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.c +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.c @@ -13,26 +13,6 @@ #include "sun6i_csi_bridge.h" #include "sun6i_csi_reg.h" -/* Helpers */ - -void sun6i_csi_bridge_dimensions(struct sun6i_csi_device *csi_dev, - unsigned int *width, unsigned int *height) -{ - if (width) - *width = csi_dev->bridge.mbus_format.width; - if (height) - *height = csi_dev->bridge.mbus_format.height; -} - -void sun6i_csi_bridge_format(struct sun6i_csi_device *csi_dev, - u32 *mbus_code, u32 *field) -{ - if (mbus_code) - *mbus_code = csi_dev->bridge.mbus_format.code; - if (field) - *field = csi_dev->bridge.mbus_format.field; -} - /* Format */ static const struct sun6i_csi_bridge_format sun6i_csi_bridge_formats[] = { @@ -226,7 +206,8 @@ static void sun6i_csi_bridge_disable(struct sun6i_csi_device *csi_dev) } static void -sun6i_csi_bridge_configure_parallel(struct sun6i_csi_device *csi_dev) +sun6i_csi_bridge_configure_parallel(struct sun6i_csi_device *csi_dev, + const struct v4l2_mbus_framefmt *mbus_format) { struct device *dev = csi_dev->dev; struct regmap *regmap = csi_dev->regmap; @@ -234,11 +215,9 @@ sun6i_csi_bridge_configure_parallel(struct sun6i_csi_device *csi_dev) &csi_dev->bridge.source_parallel.endpoint; unsigned char bus_width = endpoint->bus.parallel.bus_width; unsigned int flags = endpoint->bus.parallel.flags; - u32 field; + u32 field = mbus_format->field; u32 value = SUN6I_CSI_IF_CFG_IF_CSI; - sun6i_csi_bridge_format(csi_dev, NULL, &field); - if (field == V4L2_FIELD_INTERLACED || field == V4L2_FIELD_INTERLACED_TB || field == V4L2_FIELD_INTERLACED_BT) @@ -317,13 +296,12 @@ sun6i_csi_bridge_configure_parallel(struct sun6i_csi_device *csi_dev) } static void -sun6i_csi_bridge_configure_mipi_csi2(struct sun6i_csi_device *csi_dev) +sun6i_csi_bridge_configure_mipi_csi2(struct sun6i_csi_device *csi_dev, + const struct v4l2_mbus_framefmt *mbus_format) { struct regmap *regmap = csi_dev->regmap; u32 value = SUN6I_CSI_IF_CFG_IF_MIPI; - u32 field; - - sun6i_csi_bridge_format(csi_dev, NULL, &field); + u32 field = mbus_format->field; if (field == V4L2_FIELD_INTERLACED || field == V4L2_FIELD_INTERLACED_TB || @@ -335,19 +313,20 @@ sun6i_csi_bridge_configure_mipi_csi2(struct sun6i_csi_device *csi_dev) regmap_write(regmap, SUN6I_CSI_IF_CFG_REG, value); } -static void sun6i_csi_bridge_configure_format(struct sun6i_csi_device *csi_dev) +static void +sun6i_csi_bridge_configure_format(struct sun6i_csi_device *csi_dev, + const struct v4l2_mbus_framefmt *mbus_format) { struct regmap *regmap = csi_dev->regmap; bool capture_streaming = csi_dev->capture.state.streaming; const struct sun6i_csi_bridge_format *bridge_format; const struct sun6i_csi_capture_format *capture_format; - u32 mbus_code, field, pixelformat; + u32 pixelformat; + u32 field = mbus_format->field; u8 input_format, input_yuv_seq, output_format; u32 value = 0; - sun6i_csi_bridge_format(csi_dev, &mbus_code, &field); - - bridge_format = sun6i_csi_bridge_format_find(mbus_code); + bridge_format = sun6i_csi_bridge_format_find(mbus_format->code); if (WARN_ON(!bridge_format)) return; @@ -391,16 +370,17 @@ static void sun6i_csi_bridge_configure_format(struct sun6i_csi_device *csi_dev) } static void sun6i_csi_bridge_configure(struct sun6i_csi_device *csi_dev, - struct sun6i_csi_bridge_source *source) + struct sun6i_csi_bridge_source *source, + const struct v4l2_mbus_framefmt *mbus_format) { struct sun6i_csi_bridge *bridge = &csi_dev->bridge; if (source == &bridge->source_parallel) - sun6i_csi_bridge_configure_parallel(csi_dev); + sun6i_csi_bridge_configure_parallel(csi_dev, mbus_format); else - sun6i_csi_bridge_configure_mipi_csi2(csi_dev); + sun6i_csi_bridge_configure_mipi_csi2(csi_dev, mbus_format); - sun6i_csi_bridge_configure_format(csi_dev); + sun6i_csi_bridge_configure_format(csi_dev, mbus_format); } /* V4L2 Subdev */ @@ -415,6 +395,8 @@ static int sun6i_csi_bridge_s_stream(struct v4l2_subdev *subdev, int on) struct sun6i_csi_bridge_source *source; struct v4l2_subdev *source_subdev; struct media_pad *remote_pad; + struct v4l2_subdev_state *state; + const struct v4l2_mbus_framefmt *mbus_format; int ret; /* Source */ @@ -433,6 +415,10 @@ static int sun6i_csi_bridge_s_stream(struct v4l2_subdev *subdev, int on) else source = &bridge->source_mipi_csi2; + /* Active State */ + + state = v4l2_subdev_lock_and_get_active_state(subdev); + if (!on) { v4l2_subdev_call(source_subdev, video, s_stream, 0); ret = 0; @@ -443,7 +429,7 @@ static int sun6i_csi_bridge_s_stream(struct v4l2_subdev *subdev, int on) ret = pm_runtime_resume_and_get(dev); if (ret < 0) - return ret; + goto unlock; /* Clear */ @@ -451,7 +437,9 @@ static int sun6i_csi_bridge_s_stream(struct v4l2_subdev *subdev, int on) /* Configure */ - sun6i_csi_bridge_configure(csi_dev, source); + mbus_format = v4l2_subdev_state_get_format(state, + SUN6I_CSI_BRIDGE_PAD_SINK); + sun6i_csi_bridge_configure(csi_dev, source, mbus_format); if (capture_streaming) sun6i_csi_capture_configure(csi_dev); @@ -472,7 +460,8 @@ static int sun6i_csi_bridge_s_stream(struct v4l2_subdev *subdev, int on) if (ret && ret != -ENOIOCTLCMD) goto disable; - return 0; + ret = 0; + goto unlock; disable: if (capture_streaming) @@ -482,6 +471,8 @@ disable: pm_runtime_put(dev); +unlock: + v4l2_subdev_unlock_state(state); return ret; } @@ -504,21 +495,23 @@ sun6i_csi_bridge_mbus_format_prepare(struct v4l2_mbus_framefmt *mbus_format) static int sun6i_csi_bridge_init_state(struct v4l2_subdev *subdev, struct v4l2_subdev_state *state) { - struct sun6i_csi_device *csi_dev = v4l2_get_subdevdata(subdev); - unsigned int pad = SUN6I_CSI_BRIDGE_PAD_SINK; - struct v4l2_mbus_framefmt *mbus_format = - v4l2_subdev_state_get_format(state, pad); - struct mutex *lock = &csi_dev->bridge.lock; + unsigned int pad; - mutex_lock(lock); + /* + * This subdev does not perform format conversion, + * initialize both pads identically. + */ + for (pad = 0; pad < subdev->entity.num_pads; pad++) { + struct v4l2_mbus_framefmt *mbus_format; - mbus_format->code = sun6i_csi_bridge_formats[0].mbus_code; - mbus_format->width = 1280; - mbus_format->height = 720; + mbus_format = v4l2_subdev_state_get_format(state, pad); - sun6i_csi_bridge_mbus_format_prepare(mbus_format); + mbus_format->code = sun6i_csi_bridge_formats[0].mbus_code; + mbus_format->width = 1280; + mbus_format->height = 720; - mutex_unlock(lock); + sun6i_csi_bridge_mbus_format_prepare(mbus_format); + } return 0; } @@ -536,53 +529,33 @@ sun6i_csi_bridge_enum_mbus_code(struct v4l2_subdev *subdev, return 0; } -static int sun6i_csi_bridge_get_fmt(struct v4l2_subdev *subdev, - struct v4l2_subdev_state *state, - struct v4l2_subdev_format *format) -{ - struct sun6i_csi_device *csi_dev = v4l2_get_subdevdata(subdev); - struct v4l2_mbus_framefmt *mbus_format = &format->format; - struct mutex *lock = &csi_dev->bridge.lock; - - mutex_lock(lock); - - if (format->which == V4L2_SUBDEV_FORMAT_TRY) - *mbus_format = *v4l2_subdev_state_get_format(state, - format->pad); - else - *mbus_format = csi_dev->bridge.mbus_format; - - mutex_unlock(lock); - - return 0; -} - static int sun6i_csi_bridge_set_fmt(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { - struct sun6i_csi_device *csi_dev = v4l2_get_subdevdata(subdev); - struct v4l2_mbus_framefmt *mbus_format = &format->format; - struct mutex *lock = &csi_dev->bridge.lock; + struct v4l2_mbus_framefmt *fmt; - mutex_lock(lock); + /* The format on the source pad always matches the sink pad. */ + if (format->pad != SUN6I_CSI_BRIDGE_PAD_SINK) + return v4l2_subdev_get_fmt(subdev, state, format); - sun6i_csi_bridge_mbus_format_prepare(mbus_format); + sun6i_csi_bridge_mbus_format_prepare(&format->format); - if (format->which == V4L2_SUBDEV_FORMAT_TRY) - *v4l2_subdev_state_get_format(state, format->pad) = - *mbus_format; - else - csi_dev->bridge.mbus_format = *mbus_format; + /* Set the format on the sink pad. */ + fmt = v4l2_subdev_state_get_format(state, format->pad); + *fmt = format->format; - mutex_unlock(lock); + /* Propagate the format to the source pad. */ + fmt = v4l2_subdev_state_get_format(state, SUN6I_CSI_BRIDGE_PAD_SOURCE); + *fmt = format->format; return 0; } static const struct v4l2_subdev_pad_ops sun6i_csi_bridge_pad_ops = { .enum_mbus_code = sun6i_csi_bridge_enum_mbus_code, - .get_fmt = sun6i_csi_bridge_get_fmt, + .get_fmt = v4l2_subdev_get_fmt, .set_fmt = sun6i_csi_bridge_set_fmt, }; @@ -780,8 +753,6 @@ int sun6i_csi_bridge_setup(struct sun6i_csi_device *csi_dev) }; int ret; - mutex_init(&bridge->lock); - /* V4L2 Subdev */ v4l2_subdev_init(subdev, &sun6i_csi_bridge_subdev_ops); @@ -809,6 +780,12 @@ int sun6i_csi_bridge_setup(struct sun6i_csi_device *csi_dev) if (ret < 0) return ret; + /* V4L2 Subdev finalize */ + + ret = v4l2_subdev_init_finalize(subdev); + if (ret < 0) + goto error_media_entity; + /* V4L2 Subdev */ if (csi_dev->isp_available) @@ -818,7 +795,7 @@ int sun6i_csi_bridge_setup(struct sun6i_csi_device *csi_dev) if (ret) { dev_err(dev, "failed to register v4l2 subdev: %d\n", ret); - goto error_media_entity; + goto error_subdev_finalize; } /* V4L2 Async */ @@ -852,6 +829,9 @@ error_v4l2_async_notifier: else v4l2_device_unregister_subdev(subdev); +error_subdev_finalize: + v4l2_subdev_cleanup(subdev); + error_media_entity: media_entity_cleanup(&subdev->entity); @@ -868,5 +848,7 @@ void sun6i_csi_bridge_cleanup(struct sun6i_csi_device *csi_dev) v4l2_device_unregister_subdev(subdev); + v4l2_subdev_cleanup(subdev); + media_entity_cleanup(&subdev->entity); } diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.h index 44653b38f722..a5b0a6f064dd 100644 --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.h +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.h @@ -42,20 +42,11 @@ struct sun6i_csi_bridge { struct v4l2_subdev subdev; struct v4l2_async_notifier notifier; struct media_pad pads[2]; - struct v4l2_mbus_framefmt mbus_format; - struct mutex lock; /* Mbus format lock. */ struct sun6i_csi_bridge_source source_parallel; struct sun6i_csi_bridge_source source_mipi_csi2; }; -/* Helpers */ - -void sun6i_csi_bridge_dimensions(struct sun6i_csi_device *csi_dev, - unsigned int *width, unsigned int *height); -void sun6i_csi_bridge_format(struct sun6i_csi_device *csi_dev, - u32 *mbus_code, u32 *field); - /* Format */ const struct sun6i_csi_bridge_format * diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_capture.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_capture.c index 65879f4802c0..d90abba21309 100644 --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_capture.c +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_capture.c @@ -888,14 +888,19 @@ static int sun6i_csi_capture_link_validate(struct media_link *link) media_entity_to_video_device(link->sink->entity); struct sun6i_csi_device *csi_dev = video_get_drvdata(video_dev); struct v4l2_device *v4l2_dev = csi_dev->v4l2_dev; + struct v4l2_subdev *src_subdev = + media_entity_to_v4l2_subdev(link->source->entity); const struct sun6i_csi_capture_format *capture_format; const struct sun6i_csi_bridge_format *bridge_format; unsigned int capture_width, capture_height; - unsigned int bridge_width, bridge_height; const struct v4l2_format_info *format_info; + struct v4l2_subdev_format src_fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .pad = link->source->index + }; u32 pixelformat, capture_field; - u32 mbus_code, bridge_field; bool match; + int ret; sun6i_csi_capture_dimensions(csi_dev, &capture_width, &capture_height); @@ -904,19 +909,22 @@ static int sun6i_csi_capture_link_validate(struct media_link *link) if (WARN_ON(!capture_format)) return -EINVAL; - sun6i_csi_bridge_dimensions(csi_dev, &bridge_width, &bridge_height); + /* Resolve csi bridge format. */ + ret = v4l2_subdev_call(src_subdev, pad, get_fmt, NULL, &src_fmt); + if (ret) + return ret; - sun6i_csi_bridge_format(csi_dev, &mbus_code, &bridge_field); - bridge_format = sun6i_csi_bridge_format_find(mbus_code); + bridge_format = sun6i_csi_bridge_format_find(src_fmt.format.code); if (WARN_ON(!bridge_format)) return -EINVAL; /* No cropping/scaling is supported. */ - if (capture_width != bridge_width || capture_height != bridge_height) { + if (capture_width != src_fmt.format.width || + capture_height != src_fmt.format.height) { v4l2_err(v4l2_dev, "invalid input/output dimensions: %ux%u/%ux%u\n", - bridge_width, bridge_height, capture_width, - capture_height); + src_fmt.format.width, src_fmt.format.height, + capture_width, capture_height); return -EINVAL; } @@ -947,7 +955,8 @@ static int sun6i_csi_capture_link_validate(struct media_link *link) /* With raw input mode, we need a 1:1 match between input and output. */ if (bridge_format->input_format == SUN6I_CSI_INPUT_FMT_RAW || capture_format->input_format_raw) { - match = sun6i_csi_capture_format_match(pixelformat, mbus_code); + match = sun6i_csi_capture_format_match(pixelformat, + src_fmt.format.code); if (!match) goto invalid; } diff --git a/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c b/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c index b06cb73015cd..dcc8c7eadf9a 100644 --- a/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c +++ b/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c @@ -95,12 +95,12 @@ static void sun6i_mipi_csi2_disable(struct sun6i_mipi_csi2_device *csi2_dev) SUN6I_MIPI_CSI2_CTL_EN, 0); } -static void sun6i_mipi_csi2_configure(struct sun6i_mipi_csi2_device *csi2_dev) +static void sun6i_mipi_csi2_configure(struct sun6i_mipi_csi2_device *csi2_dev, + const struct v4l2_mbus_framefmt *mbus_format) { struct regmap *regmap = csi2_dev->regmap; unsigned int lanes_count = csi2_dev->bridge.endpoint.bus.mipi_csi2.num_data_lanes; - struct v4l2_mbus_framefmt *mbus_format = &csi2_dev->bridge.mbus_format; const struct sun6i_mipi_csi2_format *format; struct device *dev = csi2_dev->dev; u32 version = 0; @@ -173,7 +173,8 @@ static int sun6i_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) struct v4l2_subdev *source_subdev = csi2_dev->bridge.source_subdev; union phy_configure_opts dphy_opts = { 0 }; struct phy_configure_opts_mipi_dphy *dphy_cfg = &dphy_opts.mipi_dphy; - struct v4l2_mbus_framefmt *mbus_format = &csi2_dev->bridge.mbus_format; + struct v4l2_subdev_state *state; + const struct v4l2_mbus_framefmt *mbus_format; const struct sun6i_mipi_csi2_format *format; struct phy *dphy = csi2_dev->dphy; struct device *dev = csi2_dev->dev; @@ -183,8 +184,12 @@ static int sun6i_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) unsigned long pixel_rate; int ret; - if (!source_subdev) - return -ENODEV; + state = v4l2_subdev_lock_and_get_active_state(subdev); + + if (!source_subdev) { + ret = -ENODEV; + goto unlock; + } if (!on) { v4l2_subdev_call(source_subdev, video, s_stream, 0); @@ -196,7 +201,7 @@ static int sun6i_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) ret = pm_runtime_resume_and_get(dev); if (ret < 0) - return ret; + goto unlock; /* Sensor Pixel Rate */ @@ -222,6 +227,8 @@ static int sun6i_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) goto error_pm; } + mbus_format = v4l2_subdev_state_get_format(state, + SUN6I_MIPI_CSI2_PAD_SINK); format = sun6i_mipi_csi2_format_find(mbus_format->code); if (WARN_ON(!format)) { ret = -ENODEV; @@ -260,7 +267,7 @@ static int sun6i_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) /* Controller */ - sun6i_mipi_csi2_configure(csi2_dev); + sun6i_mipi_csi2_configure(csi2_dev, mbus_format); sun6i_mipi_csi2_enable(csi2_dev); /* D-PHY */ @@ -277,7 +284,8 @@ static int sun6i_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) if (ret && ret != -ENOIOCTLCMD) goto disable; - return 0; + ret = 0; + goto unlock; disable: phy_power_off(dphy); @@ -286,6 +294,8 @@ disable: error_pm: pm_runtime_put(dev); +unlock: + v4l2_subdev_unlock_state(state); return ret; } @@ -308,21 +318,23 @@ sun6i_mipi_csi2_mbus_format_prepare(struct v4l2_mbus_framefmt *mbus_format) static int sun6i_mipi_csi2_init_state(struct v4l2_subdev *subdev, struct v4l2_subdev_state *state) { - struct sun6i_mipi_csi2_device *csi2_dev = v4l2_get_subdevdata(subdev); - unsigned int pad = SUN6I_MIPI_CSI2_PAD_SINK; - struct v4l2_mbus_framefmt *mbus_format = - v4l2_subdev_state_get_format(state, pad); - struct mutex *lock = &csi2_dev->bridge.lock; + unsigned int pad; - mutex_lock(lock); + /* + * This subdev does not perform format conversion, + * initialize both pads identically. + */ + for (pad = 0; pad < subdev->entity.num_pads; pad++) { + struct v4l2_mbus_framefmt *mbus_format; - mbus_format->code = sun6i_mipi_csi2_formats[0].mbus_code; - mbus_format->width = 640; - mbus_format->height = 480; + mbus_format = v4l2_subdev_state_get_format(state, pad); - sun6i_mipi_csi2_mbus_format_prepare(mbus_format); + mbus_format->code = sun6i_mipi_csi2_formats[0].mbus_code; + mbus_format->width = 640; + mbus_format->height = 480; - mutex_unlock(lock); + sun6i_mipi_csi2_mbus_format_prepare(mbus_format); + } return 0; } @@ -340,53 +352,33 @@ sun6i_mipi_csi2_enum_mbus_code(struct v4l2_subdev *subdev, return 0; } -static int sun6i_mipi_csi2_get_fmt(struct v4l2_subdev *subdev, - struct v4l2_subdev_state *state, - struct v4l2_subdev_format *format) -{ - struct sun6i_mipi_csi2_device *csi2_dev = v4l2_get_subdevdata(subdev); - struct v4l2_mbus_framefmt *mbus_format = &format->format; - struct mutex *lock = &csi2_dev->bridge.lock; - - mutex_lock(lock); - - if (format->which == V4L2_SUBDEV_FORMAT_TRY) - *mbus_format = *v4l2_subdev_state_get_format(state, - format->pad); - else - *mbus_format = csi2_dev->bridge.mbus_format; - - mutex_unlock(lock); - - return 0; -} - static int sun6i_mipi_csi2_set_fmt(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { - struct sun6i_mipi_csi2_device *csi2_dev = v4l2_get_subdevdata(subdev); - struct v4l2_mbus_framefmt *mbus_format = &format->format; - struct mutex *lock = &csi2_dev->bridge.lock; + struct v4l2_mbus_framefmt *fmt; - mutex_lock(lock); + /* The format on the source pad always matches the sink pad. */ + if (format->pad != SUN6I_MIPI_CSI2_PAD_SINK) + return v4l2_subdev_get_fmt(subdev, state, format); - sun6i_mipi_csi2_mbus_format_prepare(mbus_format); + sun6i_mipi_csi2_mbus_format_prepare(&format->format); - if (format->which == V4L2_SUBDEV_FORMAT_TRY) - *v4l2_subdev_state_get_format(state, format->pad) = - *mbus_format; - else - csi2_dev->bridge.mbus_format = *mbus_format; + /* Set the format on the sink pad. */ + fmt = v4l2_subdev_state_get_format(state, format->pad); + *fmt = format->format; - mutex_unlock(lock); + /* Propagate the format to the source pad. */ + fmt = v4l2_subdev_state_get_format(state, SUN6I_MIPI_CSI2_PAD_SOURCE); + *fmt = format->format; return 0; } static const struct v4l2_subdev_pad_ops sun6i_mipi_csi2_pad_ops = { .enum_mbus_code = sun6i_mipi_csi2_enum_mbus_code, - .get_fmt = sun6i_mipi_csi2_get_fmt, + .get_fmt = v4l2_subdev_get_fmt, .set_fmt = sun6i_mipi_csi2_set_fmt, }; @@ -502,8 +494,6 @@ static int sun6i_mipi_csi2_bridge_setup(struct sun6i_mipi_csi2_device *csi2_dev) bool notifier_registered = false; int ret; - mutex_init(&bridge->lock); - /* V4L2 Subdev */ v4l2_subdev_init(subdev, &sun6i_mipi_csi2_subdev_ops); @@ -532,6 +522,12 @@ static int sun6i_mipi_csi2_bridge_setup(struct sun6i_mipi_csi2_device *csi2_dev) if (ret) return ret; + /* V4L2 Subdev finalize */ + + ret = v4l2_subdev_init_finalize(subdev); + if (ret < 0) + goto error_media_entity_cleanup; + /* V4L2 Async */ v4l2_async_subdev_nf_init(notifier, subdev); @@ -565,6 +561,9 @@ error_v4l2_notifier_unregister: error_v4l2_notifier_cleanup: v4l2_async_nf_cleanup(notifier); + v4l2_subdev_cleanup(subdev); + +error_media_entity_cleanup: media_entity_cleanup(&subdev->entity); return ret; @@ -579,6 +578,7 @@ sun6i_mipi_csi2_bridge_cleanup(struct sun6i_mipi_csi2_device *csi2_dev) v4l2_async_unregister_subdev(subdev); v4l2_async_nf_unregister(notifier); v4l2_async_nf_cleanup(notifier); + v4l2_subdev_cleanup(subdev); media_entity_cleanup(&subdev->entity); } diff --git a/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.h b/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.h index 24b15e34b5e8..d72dfbd6a993 100644 --- a/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.h +++ b/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.h @@ -32,8 +32,6 @@ struct sun6i_mipi_csi2_bridge { struct media_pad pads[SUN6I_MIPI_CSI2_PAD_COUNT]; struct v4l2_fwnode_endpoint endpoint; struct v4l2_async_notifier notifier; - struct v4l2_mbus_framefmt mbus_format; - struct mutex lock; /* Mbus format lock. */ struct v4l2_subdev *source_subdev; }; diff --git a/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c index dbc51daa4fe3..46604dcc8dcc 100644 --- a/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c +++ b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c @@ -144,12 +144,12 @@ sun8i_a83t_mipi_csi2_disable(struct sun8i_a83t_mipi_csi2_device *csi2_dev) } static void -sun8i_a83t_mipi_csi2_configure(struct sun8i_a83t_mipi_csi2_device *csi2_dev) +sun8i_a83t_mipi_csi2_configure(struct sun8i_a83t_mipi_csi2_device *csi2_dev, + const struct v4l2_mbus_framefmt *mbus_format) { struct regmap *regmap = csi2_dev->regmap; unsigned int lanes_count = csi2_dev->bridge.endpoint.bus.mipi_csi2.num_data_lanes; - struct v4l2_mbus_framefmt *mbus_format = &csi2_dev->bridge.mbus_format; const struct sun8i_a83t_mipi_csi2_format *format; struct device *dev = csi2_dev->dev; u32 version = 0; @@ -205,7 +205,8 @@ static int sun8i_a83t_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) struct v4l2_subdev *source_subdev = csi2_dev->bridge.source_subdev; union phy_configure_opts dphy_opts = { 0 }; struct phy_configure_opts_mipi_dphy *dphy_cfg = &dphy_opts.mipi_dphy; - struct v4l2_mbus_framefmt *mbus_format = &csi2_dev->bridge.mbus_format; + struct v4l2_subdev_state *state; + const struct v4l2_mbus_framefmt *mbus_format; const struct sun8i_a83t_mipi_csi2_format *format; struct phy *dphy = csi2_dev->dphy; struct device *dev = csi2_dev->dev; @@ -215,8 +216,12 @@ static int sun8i_a83t_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) unsigned long pixel_rate; int ret; - if (!source_subdev) - return -ENODEV; + state = v4l2_subdev_lock_and_get_active_state(subdev); + + if (!source_subdev) { + ret = -ENODEV; + goto unlock; + } if (!on) { v4l2_subdev_call(source_subdev, video, s_stream, 0); @@ -228,7 +233,7 @@ static int sun8i_a83t_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) ret = pm_runtime_resume_and_get(dev); if (ret < 0) - return ret; + goto unlock; /* Sensor pixel rate */ @@ -254,6 +259,9 @@ static int sun8i_a83t_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) goto error_pm; } + mbus_format = + v4l2_subdev_state_get_format(state, + SUN8I_A83T_MIPI_CSI2_PAD_SINK); format = sun8i_a83t_mipi_csi2_format_find(mbus_format->code); if (WARN_ON(!format)) { ret = -ENODEV; @@ -292,7 +300,7 @@ static int sun8i_a83t_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) /* Controller */ - sun8i_a83t_mipi_csi2_configure(csi2_dev); + sun8i_a83t_mipi_csi2_configure(csi2_dev, mbus_format); sun8i_a83t_mipi_csi2_enable(csi2_dev); /* D-PHY */ @@ -309,7 +317,8 @@ static int sun8i_a83t_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) if (ret && ret != -ENOIOCTLCMD) goto disable; - return 0; + ret = 0; + goto unlock; disable: phy_power_off(dphy); @@ -318,6 +327,8 @@ disable: error_pm: pm_runtime_put(dev); +unlock: + v4l2_subdev_unlock_state(state); return ret; } @@ -341,22 +352,23 @@ sun8i_a83t_mipi_csi2_mbus_format_prepare(struct v4l2_mbus_framefmt *mbus_format) static int sun8i_a83t_mipi_csi2_init_state(struct v4l2_subdev *subdev, struct v4l2_subdev_state *state) { - struct sun8i_a83t_mipi_csi2_device *csi2_dev = - v4l2_get_subdevdata(subdev); - unsigned int pad = SUN8I_A83T_MIPI_CSI2_PAD_SINK; - struct v4l2_mbus_framefmt *mbus_format = - v4l2_subdev_state_get_format(state, pad); - struct mutex *lock = &csi2_dev->bridge.lock; + unsigned int pad; - mutex_lock(lock); + /* + * This subdev does not perform format conversion, + * initialize both pads identically. + */ + for (pad = 0; pad < subdev->entity.num_pads; pad++) { + struct v4l2_mbus_framefmt *mbus_format; - mbus_format->code = sun8i_a83t_mipi_csi2_formats[0].mbus_code; - mbus_format->width = 640; - mbus_format->height = 480; + mbus_format = v4l2_subdev_state_get_format(state, pad); - sun8i_a83t_mipi_csi2_mbus_format_prepare(mbus_format); + mbus_format->code = sun8i_a83t_mipi_csi2_formats[0].mbus_code; + mbus_format->width = 640; + mbus_format->height = 480; - mutex_unlock(lock); + sun8i_a83t_mipi_csi2_mbus_format_prepare(mbus_format); + } return 0; } @@ -375,55 +387,34 @@ sun8i_a83t_mipi_csi2_enum_mbus_code(struct v4l2_subdev *subdev, return 0; } -static int sun8i_a83t_mipi_csi2_get_fmt(struct v4l2_subdev *subdev, - struct v4l2_subdev_state *state, - struct v4l2_subdev_format *format) -{ - struct sun8i_a83t_mipi_csi2_device *csi2_dev = - v4l2_get_subdevdata(subdev); - struct v4l2_mbus_framefmt *mbus_format = &format->format; - struct mutex *lock = &csi2_dev->bridge.lock; - - mutex_lock(lock); - - if (format->which == V4L2_SUBDEV_FORMAT_TRY) - *mbus_format = *v4l2_subdev_state_get_format(state, - format->pad); - else - *mbus_format = csi2_dev->bridge.mbus_format; - - mutex_unlock(lock); - - return 0; -} - static int sun8i_a83t_mipi_csi2_set_fmt(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { - struct sun8i_a83t_mipi_csi2_device *csi2_dev = - v4l2_get_subdevdata(subdev); - struct v4l2_mbus_framefmt *mbus_format = &format->format; - struct mutex *lock = &csi2_dev->bridge.lock; + struct v4l2_mbus_framefmt *fmt; - mutex_lock(lock); + /* The format on the source pad always matches the sink pad. */ + if (format->pad != SUN8I_A83T_MIPI_CSI2_PAD_SINK) + return v4l2_subdev_get_fmt(subdev, state, format); - sun8i_a83t_mipi_csi2_mbus_format_prepare(mbus_format); + sun8i_a83t_mipi_csi2_mbus_format_prepare(&format->format); - if (format->which == V4L2_SUBDEV_FORMAT_TRY) - *v4l2_subdev_state_get_format(state, format->pad) = - *mbus_format; - else - csi2_dev->bridge.mbus_format = *mbus_format; + /* Set the format on the sink pad. */ + fmt = v4l2_subdev_state_get_format(state, format->pad); + *fmt = format->format; - mutex_unlock(lock); + /* Propagate the format to the source pad. */ + fmt = v4l2_subdev_state_get_format(state, + SUN8I_A83T_MIPI_CSI2_PAD_SOURCE); + *fmt = format->format; return 0; } static const struct v4l2_subdev_pad_ops sun8i_a83t_mipi_csi2_pad_ops = { .enum_mbus_code = sun8i_a83t_mipi_csi2_enum_mbus_code, - .get_fmt = sun8i_a83t_mipi_csi2_get_fmt, + .get_fmt = v4l2_subdev_get_fmt, .set_fmt = sun8i_a83t_mipi_csi2_set_fmt, }; @@ -540,8 +531,6 @@ sun8i_a83t_mipi_csi2_bridge_setup(struct sun8i_a83t_mipi_csi2_device *csi2_dev) bool notifier_registered = false; int ret; - mutex_init(&bridge->lock); - /* V4L2 Subdev */ v4l2_subdev_init(subdev, &sun8i_a83t_mipi_csi2_subdev_ops); @@ -570,6 +559,12 @@ sun8i_a83t_mipi_csi2_bridge_setup(struct sun8i_a83t_mipi_csi2_device *csi2_dev) if (ret) return ret; + /* V4L2 Subdev finalize */ + + ret = v4l2_subdev_init_finalize(subdev); + if (ret < 0) + goto error_media_entity_cleanup; + /* V4L2 Async */ v4l2_async_subdev_nf_init(notifier, subdev); @@ -603,6 +598,9 @@ error_v4l2_notifier_unregister: error_v4l2_notifier_cleanup: v4l2_async_nf_cleanup(notifier); + v4l2_subdev_cleanup(subdev); + +error_media_entity_cleanup: media_entity_cleanup(&subdev->entity); return ret; @@ -617,6 +615,7 @@ sun8i_a83t_mipi_csi2_bridge_cleanup(struct sun8i_a83t_mipi_csi2_device *csi2_dev v4l2_async_unregister_subdev(subdev); v4l2_async_nf_unregister(notifier); v4l2_async_nf_cleanup(notifier); + v4l2_subdev_cleanup(subdev); media_entity_cleanup(&subdev->entity); } diff --git a/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.h b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.h index f1e64c53434c..819527bcd64d 100644 --- a/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.h +++ b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.h @@ -33,8 +33,6 @@ struct sun8i_a83t_mipi_csi2_bridge { struct media_pad pads[SUN8I_A83T_MIPI_CSI2_PAD_COUNT]; struct v4l2_fwnode_endpoint endpoint; struct v4l2_async_notifier notifier; - struct v4l2_mbus_framefmt mbus_format; - struct mutex lock; /* Mbus format lock. */ struct v4l2_subdev *source_subdev; }; diff --git a/drivers/media/platform/synopsys/dw-mipi-csi2rx.c b/drivers/media/platform/synopsys/dw-mipi-csi2rx.c index 41e48365167e..b7247d597c9c 100644 --- a/drivers/media/platform/synopsys/dw-mipi-csi2rx.c +++ b/drivers/media/platform/synopsys/dw-mipi-csi2rx.c @@ -479,6 +479,7 @@ dw_mipi_csi2rx_enum_mbus_code(struct v4l2_subdev *sd, } static int dw_mipi_csi2rx_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/platform/synopsys/hdmirx/snps_hdmirx.c b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c index 25f8ca0d6d94..c62614077d97 100644 --- a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c +++ b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c @@ -41,6 +41,8 @@ #include <media/videobuf2-dma-contig.h> #include <media/videobuf2-v4l2.h> +#include <sound/hdmi-codec.h> + #include "snps_hdmirx.h" #include "snps_hdmirx_cec.h" @@ -132,6 +134,13 @@ struct snps_hdmirx_dev { struct delayed_work delayed_work_hotplug; struct delayed_work delayed_work_res_change; struct hdmirx_cec *cec; + struct platform_device *audio_pdev; + struct clk *audio_clk; + struct delayed_work audio_work; + u32 audio_clkrate; + u32 audio_fs; + int audio_pre_state; + bool audio_streaming; struct mutex phy_rw_lock; /* to protect phy r/w configuration */ struct mutex stream_lock; /* to lock video stream capture */ struct mutex work_lock; /* to lock the critical section of hotplug event */ @@ -156,7 +165,6 @@ struct snps_hdmirx_dev { int num_clks; u32 edid_blocks_written; u32 cur_fmt_fourcc; - u32 color_depth; spinlock_t rst_lock; /* to lock register access */ u8 edid[EDID_NUM_BLOCKS_MAX * EDID_BLOCK_SIZE]; }; @@ -380,6 +388,38 @@ static void hdmirx_toggle_polarity(struct snps_hdmirx_dev *hdmirx_dev) VPROC_HSYNC_POL_OVR_EN, 0); } +static u32 hdmirx_get_colordepth(struct snps_hdmirx_dev *hdmirx_dev) +{ + struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev; + u32 val, color_depth_reg, color_depth; + + val = hdmirx_readl(hdmirx_dev, DMA_STATUS11); + color_depth_reg = (val & HDMIRX_COLOR_DEPTH_MASK) >> 3; + + switch (color_depth_reg) { + case 0x4: + color_depth = 24; + break; + case 0x5: + color_depth = 30; + break; + case 0x6: + color_depth = 36; + break; + case 0x7: + color_depth = 48; + break; + default: + color_depth = 24; + break; + } + + v4l2_dbg(1, debug, v4l2_dev, "%s: color_depth: %d, reg_val:%d\n", + __func__, color_depth, color_depth_reg); + + return color_depth; +} + /* * When querying DV timings during preview, if the DMA's timing is stable, * we retrieve the timings directly from the DMA. However, if the current @@ -393,7 +433,7 @@ static int hdmirx_get_detected_timings(struct snps_hdmirx_dev *hdmirx_dev, struct v4l2_bt_timings *bt = &timings->bt; u32 val, tmdsqpclk_freq, pix_clk; unsigned int num_retries = 0; - u32 field_type, deframer_st; + u32 field_type, deframer_st, color_depth; u64 tmp_data, tmds_clk; bool is_dvi_mode; int ret; @@ -414,10 +454,11 @@ retry: deframer_st = hdmirx_readl(hdmirx_dev, DEFRAMER_STATUS); is_dvi_mode = !(deframer_st & OPMODE_STS_MASK); + color_depth = hdmirx_get_colordepth(hdmirx_dev); tmdsqpclk_freq = hdmirx_readl(hdmirx_dev, CMU_TMDSQPCLK_FREQ); tmds_clk = tmdsqpclk_freq * 4 * 1000; tmp_data = tmds_clk * 24; - do_div(tmp_data, hdmirx_dev->color_depth); + do_div(tmp_data, color_depth); pix_clk = tmp_data; bt->pixelclock = pix_clk; @@ -429,7 +470,7 @@ retry: v4l2_dbg(2, debug, v4l2_dev, "tmds_clk:%llu, pix_clk:%d\n", tmds_clk, pix_clk); v4l2_dbg(1, debug, v4l2_dev, "interlace:%d, fmt:%d, color:%d, mode:%s\n", bt->interlaced, hdmirx_dev->pix_fmt, - hdmirx_dev->color_depth, + color_depth, is_dvi_mode ? "dvi" : "hdmi"); v4l2_dbg(2, debug, v4l2_dev, "deframer_st:%#x\n", deframer_st); @@ -470,12 +511,12 @@ static int hdmirx_query_dv_timings(struct file *file, void *priv, int ret; if (port_no_link(hdmirx_dev)) { - v4l2_err(v4l2_dev, "%s: port has no link\n", __func__); + v4l2_dbg(1, debug, v4l2_dev, "%s: port has no link\n", __func__); return -ENOLINK; } if (signal_not_lock(hdmirx_dev)) { - v4l2_err(v4l2_dev, "%s: signal is not locked\n", __func__); + v4l2_dbg(1, debug, v4l2_dev, "%s: signal is not locked\n", __func__); return -ENOLCK; } @@ -506,9 +547,9 @@ static void hdmirx_hpd_ctrl(struct snps_hdmirx_dev *hdmirx_dev, bool en) hdmirx_writel(hdmirx_dev, CORE_CONFIG, hdmirx_dev->hpd_trigger_level_high ? en : !en); - /* 100ms delay as per HDMI spec + extra 50ms to cover internal delay */ + /* 100ms delay as per HDMI spec + extra 43ms to cover internal delay */ if (!en) - msleep(100 + 50); + msleep(jiffies_to_msecs(V4L2_SET_EDID_HPD_LOW_JIFFIES)); } static void hdmirx_write_edid_data(struct snps_hdmirx_dev *hdmirx_dev, @@ -988,36 +1029,6 @@ static void hdmirx_controller_init(struct snps_hdmirx_dev *hdmirx_dev) VS_REMAPFILTER_EN_QST | VS_FILTER_ORDER_QST(0x3)); } -static void hdmirx_get_colordepth(struct snps_hdmirx_dev *hdmirx_dev) -{ - struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev; - u32 val, color_depth_reg; - - val = hdmirx_readl(hdmirx_dev, DMA_STATUS11); - color_depth_reg = (val & HDMIRX_COLOR_DEPTH_MASK) >> 3; - - switch (color_depth_reg) { - case 0x4: - hdmirx_dev->color_depth = 24; - break; - case 0x5: - hdmirx_dev->color_depth = 30; - break; - case 0x6: - hdmirx_dev->color_depth = 36; - break; - case 0x7: - hdmirx_dev->color_depth = 48; - break; - default: - hdmirx_dev->color_depth = 24; - break; - } - - v4l2_dbg(1, debug, v4l2_dev, "%s: color_depth: %d, reg_val:%d\n", - __func__, hdmirx_dev->color_depth, color_depth_reg); -} - static void hdmirx_get_pix_fmt(struct snps_hdmirx_dev *hdmirx_dev) { struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev; @@ -1128,7 +1139,6 @@ static void hdmirx_format_change(struct snps_hdmirx_dev *hdmirx_dev) }; hdmirx_get_pix_fmt(hdmirx_dev); - hdmirx_get_colordepth(hdmirx_dev); hdmirx_get_avi_infoframe(hdmirx_dev); v4l2_dbg(1, debug, v4l2_dev, "%s: queue res_chg_event\n", __func__); @@ -1198,6 +1208,9 @@ static void hdmirx_submodule_init(struct snps_hdmirx_dev *hdmirx_dev) static int hdmirx_enum_input(struct file *file, void *priv, struct v4l2_input *input) { + struct hdmirx_stream *stream = video_drvdata(file); + struct snps_hdmirx_dev *hdmirx_dev = stream->hdmirx_dev; + if (input->index > 0) return -EINVAL; @@ -1206,6 +1219,12 @@ static int hdmirx_enum_input(struct file *file, void *priv, strscpy(input->name, "HDMI IN", sizeof(input->name)); input->capabilities = V4L2_IN_CAP_DV_TIMINGS; + input->status = 0; + if (port_no_link(hdmirx_dev)) + input->status |= V4L2_IN_ST_NO_POWER; + if (signal_not_lock(hdmirx_dev)) + input->status |= V4L2_IN_ST_NO_SIGNAL; + return 0; } @@ -2265,11 +2284,6 @@ static const struct hdmirx_cec_ops hdmirx_cec_ops = { .read = hdmirx_readl, }; -static void devm_hdmirx_of_reserved_mem_device_release(void *dev) -{ - of_reserved_mem_device_release(dev); -} - static int hdmirx_parse_dt(struct snps_hdmirx_dev *hdmirx_dev) { struct device *dev = hdmirx_dev->dev; @@ -2279,6 +2293,13 @@ static int hdmirx_parse_dt(struct snps_hdmirx_dev *hdmirx_dev) if (hdmirx_dev->num_clks < 1) return -ENODEV; + for (int i = 0; i < hdmirx_dev->num_clks; i++) { + if (!strcmp(hdmirx_dev->clks[i].id, "audio")) { + hdmirx_dev->audio_clk = hdmirx_dev->clks[i].clk; + break; + } + } + hdmirx_dev->resets[HDMIRX_RST_A].id = "axi"; hdmirx_dev->resets[HDMIRX_RST_P].id = "apb"; hdmirx_dev->resets[HDMIRX_RST_REF].id = "ref"; @@ -2316,16 +2337,9 @@ static int hdmirx_parse_dt(struct snps_hdmirx_dev *hdmirx_dev) if (!device_property_read_bool(dev, "hpd-is-active-low")) hdmirx_dev->hpd_trigger_level_high = true; - ret = of_reserved_mem_device_init(dev); - if (ret) { + ret = devm_of_reserved_mem_device_init(dev); + if (ret) dev_warn(dev, "no reserved memory for HDMIRX, use default CMA\n"); - } else { - ret = devm_add_action_or_reset(dev, - devm_hdmirx_of_reserved_mem_device_release, - dev); - if (ret) - return ret; - } return 0; } @@ -2523,10 +2537,19 @@ static void hdmirx_enable_irq(struct device *dev) msecs_to_jiffies(110)); } +static void hdmirx_audio_setup(struct snps_hdmirx_dev *hdmirx_dev, u32 fs); + static __maybe_unused int hdmirx_suspend(struct device *dev) { struct snps_hdmirx_dev *hdmirx_dev = dev_get_drvdata(dev); + /* + * Stop the audio worker before the controller clocks are gated; + * the audio path is restored and the worker re-armed from + * resume() while a capture stream is active. + */ + cancel_delayed_work_sync(&hdmirx_dev->audio_work); + hdmirx_disable_irq(dev); /* TODO store CEC HW state */ @@ -2549,6 +2572,19 @@ static __maybe_unused int hdmirx_resume(struct device *dev) hdmirx_hpd_ctrl(hdmirx_dev, true); } + /* + * hdmirx_enable() fully reset the controller, wiping the audio + * configuration. If a capture stream is active across suspend, + * re-program the audio path with the last known sample rate and + * restart the worker; its rate change and FIFO error paths + * resynchronize once the source delivers audio again. + */ + if (READ_ONCE(hdmirx_dev->audio_streaming)) { + hdmirx_audio_setup(hdmirx_dev, hdmirx_dev->audio_fs); + mod_delayed_work(system_unbound_wq, &hdmirx_dev->audio_work, + msecs_to_jiffies(200)); + } + /* TODO restore CEC HW state */ enable_irq(hdmirx_dev->cec->irq); @@ -2595,10 +2631,8 @@ static int hdmirx_setup_irq(struct snps_hdmirx_dev *hdmirx_dev, ret = devm_request_threaded_irq(dev, irq, NULL, hdmirx_dma_irq_handler, IRQF_ONESHOT, "rk_hdmirx-dma", hdmirx_dev); - if (ret) { - dev_err_probe(dev, ret, "failed to request dma irq\n"); + if (ret) return ret; - } irq = gpiod_to_irq(hdmirx_dev->detect_5v_gpio); if (irq < 0) { @@ -2609,10 +2643,11 @@ static int hdmirx_setup_irq(struct snps_hdmirx_dev *hdmirx_dev, irq_set_status_flags(irq, IRQ_NOAUTOEN); hdmirx_dev->det_irq = irq; - ret = devm_request_irq(dev, irq, hdmirx_5v_det_irq_handler, - IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, - "rk_hdmirx-5v", hdmirx_dev); - if (ret) { + ret = devm_request_any_context_irq(dev, irq, hdmirx_5v_det_irq_handler, + IRQF_TRIGGER_FALLING | + IRQF_TRIGGER_RISING, + "rk_hdmirx-5v", hdmirx_dev); + if (ret < 0) { dev_err_probe(dev, ret, "failed to request hdmirx-5v irq\n"); return ret; } @@ -2646,6 +2681,266 @@ static int hdmirx_register_cec(struct snps_hdmirx_dev *hdmirx_dev, return 0; } +#define HDMIRX_AUDIO_INIT_FIFO_STATE 128 +#define HDMIRX_AUDIO_INIT_STATE (HDMIRX_AUDIO_INIT_FIFO_STATE * 4) + +static const int hdmirx_supported_fs[] = { + 32000, 44100, 48000, 88200, 96000, 176400, 192000, 768000, -1 +}; + +static int hdmirx_audio_closest_fs(int fs) +{ + int i = 0, fs_t = hdmirx_supported_fs[0]; + + while (fs_t > 0) { + if (abs(fs - fs_t) <= 2000) + return fs_t; + fs_t = hdmirx_supported_fs[++i]; + } + return 0; +} + +/* Recover the incoming audio sample rate from the ACR N/CTS + TMDS clock. */ +static u32 hdmirx_audio_fs(struct snps_hdmirx_dev *hdmirx_dev) +{ + u64 tmds_clk, fs_audio = 0; + u32 acr_cts, acr_n, tmdsqpclk_freq; + u32 acr_pb3_0, acr_pb7_4; + + tmdsqpclk_freq = hdmirx_readl(hdmirx_dev, CMU_TMDSQPCLK_FREQ); + hdmirx_readl(hdmirx_dev, PKTDEC_ACR_PH2_1); + acr_pb3_0 = hdmirx_readl(hdmirx_dev, PKTDEC_ACR_PB3_0); + acr_pb7_4 = hdmirx_readl(hdmirx_dev, PKTDEC_ACR_PB7_4); + /* + * The packet decoder stores the ACR subpacket bytes with packet byte + * 0 in register bits [7:0], so byte-reverse each word to line the + * bytes up: CTS is packet bytes 1-3 (PKTDEC_ACR_PB3_0) and N is + * packet bytes 4-6 (PKTDEC_ACR_PB7_4), 20 bits each. readl() + * already abstracts the bus endianness, so the reversal is + * unconditional. + */ + acr_cts = swab32(acr_pb3_0) & 0xfffff; + acr_n = (swab32(acr_pb7_4) & 0x0fffff00) >> 8; + tmds_clk = tmdsqpclk_freq * 4 * 1000U; + if (acr_cts != 0) { + fs_audio = div_u64((tmds_clk * acr_n), acr_cts); + fs_audio /= 128; + fs_audio = hdmirx_audio_closest_fs(fs_audio); + } + return (u32)fs_audio; +} + +/* Nudge the audio reference clock by +/- ppm to keep the FIFO balanced. */ +static void hdmirx_audio_clk_ppm_inc(struct snps_hdmirx_dev *hdmirx_dev, int ppm) +{ + int delta, inc; + long rate = hdmirx_dev->audio_clkrate; + + if (ppm < 0) { + ppm = -ppm; + inc = -1; + } else { + inc = 1; + } + delta = (int)div64_u64((u64)rate * ppm + 500000, 1000000); + delta *= inc; + rate = hdmirx_dev->audio_clkrate + delta; + clk_set_rate(hdmirx_dev->audio_clk, rate); + hdmirx_dev->audio_clkrate = rate; +} + +static int hdmirx_audio_clk_adjust(struct snps_hdmirx_dev *hdmirx_dev, + int total_offset, int single_offset) +{ + int schedule_time = 500; + int ppm = 10; + u32 offset_abs = abs(total_offset); + + if (offset_abs > 200) { + ppm += 200; + schedule_time -= 100; + } + if (offset_abs > 100) { + ppm += 200; + schedule_time -= 100; + } + if (offset_abs > 32) { + ppm += 20; + schedule_time -= 100; + } + if (offset_abs > 16) + ppm += 20; + if (total_offset > 16 && single_offset > 0) + hdmirx_audio_clk_ppm_inc(hdmirx_dev, ppm); + else if (total_offset < -16 && single_offset < 0) + hdmirx_audio_clk_ppm_inc(hdmirx_dev, -ppm); + return schedule_time; +} + +static void hdmirx_audio_fifo_reinit(struct snps_hdmirx_dev *hdmirx_dev) +{ + hdmirx_writel(hdmirx_dev, AUDIO_FIFO_CONTROL, 1); + usleep_range(200, 210); + hdmirx_writel(hdmirx_dev, AUDIO_FIFO_CONTROL, 0); +} + +/* + * Program the audio clock, FIFO thresholds and enables for the given + * sample rate. Shared by hw_params and system resume: the controller is + * fully reset on resume, so the whole configuration must be re-applied. + */ +static void hdmirx_audio_setup(struct snps_hdmirx_dev *hdmirx_dev, u32 fs) +{ + hdmirx_dev->audio_fs = fs; + hdmirx_dev->audio_clkrate = fs * 128; + clk_set_rate(hdmirx_dev->audio_clk, hdmirx_dev->audio_clkrate); + + hdmirx_audio_fifo_reinit(hdmirx_dev); + hdmirx_writel(hdmirx_dev, AUDIO_FIFO_THR_PASS, HDMIRX_AUDIO_INIT_FIFO_STATE); + hdmirx_writel(hdmirx_dev, AUDIO_FIFO_THR, + AFIFO_THR_LOW_QST(0x20) | AFIFO_THR_HIGH_QST(0x160)); + hdmirx_writel(hdmirx_dev, AUDIO_FIFO_MUTE_THR, + AFIFO_THR_MUTE_LOW_QST(0x8) | AFIFO_THR_MUTE_HIGH_QST(0x178)); + + hdmirx_update_bits(hdmirx_dev, AUDIO_PROC_CONFIG0, I2S_EN, I2S_EN); + hdmirx_update_bits(hdmirx_dev, GLOBAL_SWENABLE, AUDIO_ENABLE, AUDIO_ENABLE); + + hdmirx_dev->audio_pre_state = 0; +} + +/* + * Periodic worker that locks the local audio clock to the source by keeping + * the audio FIFO fill level close to its target, avoiding under/overflow. + */ +static void hdmirx_audio_work(struct work_struct *work) +{ + struct snps_hdmirx_dev *hdmirx_dev = + container_of(to_delayed_work(work), struct snps_hdmirx_dev, audio_work); + unsigned long delay = 200; + int cur, total, single; + u32 fifo, fs; + + fs = hdmirx_audio_fs(hdmirx_dev); + fifo = hdmirx_readl(hdmirx_dev, AUDIO_FIFO_STATUS2); + + if (fifo & (AFIFO_UNDERFLOW_ST | AFIFO_OVERFLOW_ST)) { + if (fs) { + clk_set_rate(hdmirx_dev->audio_clk, fs * 128); + hdmirx_dev->audio_clkrate = fs * 128; + hdmirx_dev->audio_fs = fs; + } + hdmirx_audio_fifo_reinit(hdmirx_dev); + hdmirx_dev->audio_pre_state = 0; + goto out; + } + + cur = fifo & 0xffff; + total = cur - HDMIRX_AUDIO_INIT_STATE; + single = cur - hdmirx_dev->audio_pre_state; + + if (fs && abs((int)fs - (int)hdmirx_dev->audio_fs) > 1000) { + clk_set_rate(hdmirx_dev->audio_clk, fs * 128); + hdmirx_dev->audio_clkrate = fs * 128; + hdmirx_dev->audio_fs = fs; + hdmirx_audio_fifo_reinit(hdmirx_dev); + hdmirx_dev->audio_pre_state = 0; + goto out; + } + + if (cur != 0) + delay = hdmirx_audio_clk_adjust(hdmirx_dev, total, single); + hdmirx_dev->audio_pre_state = cur; +out: + /* Only re-arm while streaming; avoids a self-reschedule race with + * the cancel_delayed_work_sync() callers (hw_params and + * audio_shutdown). + */ + if (READ_ONCE(hdmirx_dev->audio_streaming)) + queue_delayed_work(system_unbound_wq, &hdmirx_dev->audio_work, + msecs_to_jiffies(delay)); +} + +static int hdmirx_audio_hw_params(struct device *dev, void *data, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *hparms) +{ + struct snps_hdmirx_dev *hdmirx_dev = dev_get_drvdata(dev); + u32 fs; + + /* Only the I2S interface (DAI 0) is wired up so far. */ + if (fmt->fmt == HDMI_SPDIF) + return -EOPNOTSUPP; + + /* + * Stop the worker before touching the shared audio state; it is + * re-armed below once the new parameters are in place. + */ + WRITE_ONCE(hdmirx_dev->audio_streaming, false); + cancel_delayed_work_sync(&hdmirx_dev->audio_work); + + fs = hdmirx_audio_fs(hdmirx_dev); + if (!fs) + fs = hparms ? hparms->sample_rate : 48000; + if (!fs) + fs = 48000; + + hdmirx_audio_setup(hdmirx_dev, fs); + + WRITE_ONCE(hdmirx_dev->audio_streaming, true); + mod_delayed_work(system_unbound_wq, &hdmirx_dev->audio_work, + msecs_to_jiffies(200)); + + dev_dbg(dev, "audio hw_params: fs=%u\n", fs); + return 0; +} + +static void hdmirx_audio_shutdown(struct device *dev, void *data) +{ + struct snps_hdmirx_dev *hdmirx_dev = dev_get_drvdata(dev); + + WRITE_ONCE(hdmirx_dev->audio_streaming, false); + cancel_delayed_work_sync(&hdmirx_dev->audio_work); + hdmirx_update_bits(hdmirx_dev, GLOBAL_SWENABLE, AUDIO_ENABLE, 0); +} + +static const struct hdmi_codec_ops hdmirx_audio_codec_ops = { + .hw_params = hdmirx_audio_hw_params, + .audio_shutdown = hdmirx_audio_shutdown, +}; + +static int hdmirx_register_audio_device(struct snps_hdmirx_dev *hdmirx_dev) +{ + struct hdmi_codec_pdata codec_data = { + .ops = &hdmirx_audio_codec_ops, + .i2s = 1, + .no_i2s_playback = 1, + .max_i2s_channels = 8, + /* + * The controller also has an S/PDIF audio interface (DAI 1 in + * the binding). Register it so DAI indexes match the binding, + * but reject its use in hw_params() until it is wired up. + */ + .spdif = 1, + .no_spdif_playback = 1, + .data = hdmirx_dev, + }; + struct platform_device *audio_pdev; + + if (!hdmirx_dev->audio_clk) + return -ENODEV; + + audio_pdev = platform_device_register_data(hdmirx_dev->dev, + HDMI_CODEC_DRV_NAME, + PLATFORM_DEVID_AUTO, + &codec_data, sizeof(codec_data)); + if (IS_ERR(audio_pdev)) + return PTR_ERR(audio_pdev); + + hdmirx_dev->audio_pdev = audio_pdev; + + return 0; +} + static int hdmirx_probe(struct platform_device *pdev) { struct snps_hdmirx_dev *hdmirx_dev; @@ -2697,6 +2992,7 @@ static int hdmirx_probe(struct platform_device *pdev) hdmirx_delayed_work_hotplug); INIT_DELAYED_WORK(&hdmirx_dev->delayed_work_res_change, hdmirx_delayed_work_res_change); + INIT_DELAYED_WORK(&hdmirx_dev->audio_work, hdmirx_audio_work); hdmirx_dev->cur_fmt_fourcc = V4L2_PIX_FMT_BGR24; hdmirx_dev->timings = cea640x480; @@ -2765,6 +3061,10 @@ static int hdmirx_probe(struct platform_device *pdev) V4L2_DEBUGFS_IF_AVI, hdmirx_dev, hdmirx_debugfs_if_read); + ret = hdmirx_register_audio_device(hdmirx_dev); + if (ret) + dev_warn(dev, "failed to register HDMI audio codec: %d\n", ret); + return 0; err_unreg_video_dev: @@ -2784,6 +3084,9 @@ static void hdmirx_remove(struct platform_device *pdev) struct device *dev = &pdev->dev; struct snps_hdmirx_dev *hdmirx_dev = dev_get_drvdata(dev); + if (hdmirx_dev->audio_pdev) + platform_device_unregister(hdmirx_dev->audio_pdev); + v4l2_debugfs_if_free(hdmirx_dev->infoframes); debugfs_remove_recursive(hdmirx_dev->debugfs_dir); diff --git a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.h b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.h index 31b887e94e87..a99f54fd1744 100644 --- a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.h +++ b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.h @@ -81,6 +81,7 @@ #define DATAPATH_ENABLE BIT(12) #define PKTFIFO_ENABLE BIT(11) #define AVPUNIT_ENABLE BIT(8) +#define AUDIO_ENABLE BIT(9) #define MAIN_ENABLE BIT(0) #define GLOBAL_TIMER_REF_BASE 0x0028 #define CORE_CONFIG 0x0050 @@ -177,20 +178,27 @@ #define VPROC_FMT_OVR_VALUE(x) UPDATE(x, 6, 4) #define VPROC_FMT_OVR_EN BIT(0) +#define AUDIO_FIFO_CONFIG 0x0460 #define AFIFO_FILL_RESTART BIT(0) +#define AUDIO_FIFO_CONTROL 0x0464 #define AFIFO_INIT_P BIT(0) +#define AUDIO_FIFO_THR_PASS 0x0468 +#define AUDIO_FIFO_THR 0x046c #define AFIFO_THR_LOW_QST_MASK GENMASK(25, 16) #define AFIFO_THR_LOW_QST(x) UPDATE(x, 25, 16) #define AFIFO_THR_HIGH_QST_MASK GENMASK(9, 0) #define AFIFO_THR_HIGH_QST(x) UPDATE(x, 9, 0) +#define AUDIO_FIFO_MUTE_THR 0x0470 #define AFIFO_THR_MUTE_LOW_QST_MASK GENMASK(25, 16) #define AFIFO_THR_MUTE_LOW_QST(x) UPDATE(x, 25, 16) #define AFIFO_THR_MUTE_HIGH_QST_MASK GENMASK(9, 0) #define AFIFO_THR_MUTE_HIGH_QST(x) UPDATE(x, 9, 0) +#define AUDIO_FIFO_STATUS2 0x0478 #define AFIFO_UNDERFLOW_ST BIT(25) #define AFIFO_OVERFLOW_ST BIT(24) +#define AUDIO_PROC_CONFIG0 0x0480 #define SPEAKER_ALLOC_OVR_EN BIT(16) #define I2S_BPCUV_EN BIT(4) #define SPDIF_EN BIT(2) diff --git a/drivers/media/platform/ti/Kconfig b/drivers/media/platform/ti/Kconfig index 1a020b2bbb4f..03df5617e99d 100644 --- a/drivers/media/platform/ti/Kconfig +++ b/drivers/media/platform/ti/Kconfig @@ -30,17 +30,6 @@ config VIDEO_TI_CAL In TI Technical Reference Manual this module is referred as Camera Interface Subsystem (CAMSS). -config VIDEO_TI_CAL_MC - bool "Media Controller centric mode by default" - depends on VIDEO_TI_CAL - default n - help - Enables Media Controller centric mode by default. - - If set, CAL driver will start in Media Controller mode by - default. Note that this behavior can be overridden via - module parameter 'mc_api'. - config VIDEO_TI_VIP tristate "TI Video Input Port" depends on VIDEO_DEV diff --git a/drivers/media/platform/ti/am437x/am437x-vpfe.c b/drivers/media/platform/ti/am437x/am437x-vpfe.c index 1ca559df7e59..3cd5ee69d3c4 100644 --- a/drivers/media/platform/ti/am437x/am437x-vpfe.c +++ b/drivers/media/platform/ti/am437x/am437x-vpfe.c @@ -1314,7 +1314,7 @@ static int __subdev_set_format(struct vpfe_device *vpfe, *mbus_fmt = *fmt; - ret = v4l2_subdev_call(sd, pad, set_fmt, NULL, &sd_fmt); + ret = v4l2_subdev_call(sd, pad, set_fmt, NULL, NULL, &sd_fmt); if (ret) return ret; diff --git a/drivers/media/platform/ti/cal/cal-camerarx.c b/drivers/media/platform/ti/cal/cal-camerarx.c index 00a71dac0ff4..a978d27fe029 100644 --- a/drivers/media/platform/ti/cal/cal-camerarx.c +++ b/drivers/media/platform/ti/cal/cal-camerarx.c @@ -762,6 +762,7 @@ static int cal_camerarx_sd_enum_frame_size(struct v4l2_subdev *sd, } static int cal_camerarx_sd_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/platform/ti/cal/cal-video.c b/drivers/media/platform/ti/cal/cal-video.c index d40e24ab1127..1990cab77344 100644 --- a/drivers/media/platform/ti/cal/cal-video.c +++ b/drivers/media/platform/ti/cal/cal-video.c @@ -123,6 +123,21 @@ static int __subdev_get_format(struct cal_ctx *ctx, return 0; } +static int cal_call_active_state_set_fmt(struct v4l2_subdev *source, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_subdev_state *source_state; + int ret; + + source_state = v4l2_subdev_lock_and_get_active_state(source); + + ret = v4l2_subdev_call(source, pad, set_fmt, NULL, source_state, fmt); + if (source_state) + v4l2_subdev_unlock_state(source_state); + + return ret; +} + static int __subdev_set_format(struct cal_ctx *ctx, struct v4l2_mbus_framefmt *fmt) { @@ -136,7 +151,7 @@ static int __subdev_set_format(struct cal_ctx *ctx, *mbus_fmt = *fmt; - ret = v4l2_subdev_call_state_active(sd, pad, set_fmt, &sd_fmt); + ret = cal_call_active_state_set_fmt(sd, &sd_fmt); if (ret) return ret; @@ -281,7 +296,7 @@ static int cal_legacy_s_fmt_vid_cap(struct file *file, void *priv, ctx->v_fmt.fmt.pix.field = sd_fmt.format.field; cal_calc_format_size(ctx, fmtinfo, &ctx->v_fmt); - v4l2_subdev_call_state_active(sd, pad, set_fmt, &sd_fmt); + cal_call_active_state_set_fmt(sd, &sd_fmt); ctx->fmtinfo = fmtinfo; *f = ctx->v_fmt; diff --git a/drivers/media/platform/ti/cal/cal.c b/drivers/media/platform/ti/cal/cal.c index b7e77b6b8950..5c82378d1eee 100644 --- a/drivers/media/platform/ti/cal/cal.c +++ b/drivers/media/platform/ti/cal/cal.c @@ -43,13 +43,7 @@ unsigned int cal_debug; module_param_named(debug, cal_debug, uint, 0644); MODULE_PARM_DESC(debug, "activates debug info"); -#ifdef CONFIG_VIDEO_TI_CAL_MC -#define CAL_MC_API_DEFAULT 1 -#else -#define CAL_MC_API_DEFAULT 0 -#endif - -bool cal_mc_api = CAL_MC_API_DEFAULT; +bool cal_mc_api = 1; module_param_named(mc_api, cal_mc_api, bool, 0444); MODULE_PARM_DESC(mc_api, "activates the MC API"); @@ -1228,6 +1222,8 @@ static int cal_probe(struct platform_device *pdev) /* Create contexts. */ if (!cal_mc_api) { + dev_warn(cal->dev, "The legacy non-MC API is deprecated\n"); + for (i = 0; i < cal->data->num_csi2_phy; ++i) { struct cal_ctx *ctx; diff --git a/drivers/media/platform/ti/davinci/vpif_capture.c b/drivers/media/platform/ti/davinci/vpif_capture.c index 91cb6223561a..397f3115af5b 100644 --- a/drivers/media/platform/ti/davinci/vpif_capture.c +++ b/drivers/media/platform/ti/davinci/vpif_capture.c @@ -1602,7 +1602,7 @@ err_cleanup: static int vpif_probe(struct platform_device *pdev) { struct vpif_subdev_info *subdevdata; - struct i2c_adapter *i2c_adap; + struct i2c_adapter *i2c_adap = NULL; int subdev_count; int res_idx = 0; int i, err; @@ -1692,9 +1692,12 @@ static int vpif_probe(struct platform_device *pdev) } } + i2c_put_adapter(i2c_adap); + return 0; probe_subdev_out: + i2c_put_adapter(i2c_adap); v4l2_async_nf_cleanup(&vpif_obj.notifier); /* free sub devices memory */ kfree(vpif_obj.sd); diff --git a/drivers/media/platform/ti/davinci/vpif_display.c b/drivers/media/platform/ti/davinci/vpif_display.c index 08877ae1513a..8bea1de6ccc8 100644 --- a/drivers/media/platform/ti/davinci/vpif_display.c +++ b/drivers/media/platform/ti/davinci/vpif_display.c @@ -1289,9 +1289,12 @@ static int vpif_probe(struct platform_device *pdev) if (err) goto probe_subdev_out; + i2c_put_adapter(i2c_adap); + return 0; probe_subdev_out: + i2c_put_adapter(i2c_adap); kfree(vpif_obj.sd); vpif_unregister: v4l2_device_unregister(&vpif_obj.v4l2_dev); diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c index 4769931b1930..022ea3d616c9 100644 --- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c +++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c @@ -226,6 +226,30 @@ static const struct ti_csi2rx_fmt ti_csi2rx_formats[] = { .bpp = 16, .size = SHIM_DMACNTX_SIZE_16, }, { + .fourcc = V4L2_PIX_FMT_SBGGR12, + .code = MEDIA_BUS_FMT_SBGGR12_1X12, + .csi_dt = MIPI_CSI2_DT_RAW12, + .bpp = 16, + .size = SHIM_DMACNTX_SIZE_16, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG12, + .code = MEDIA_BUS_FMT_SGBRG12_1X12, + .csi_dt = MIPI_CSI2_DT_RAW12, + .bpp = 16, + .size = SHIM_DMACNTX_SIZE_16, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG12, + .code = MEDIA_BUS_FMT_SGRBG12_1X12, + .csi_dt = MIPI_CSI2_DT_RAW12, + .bpp = 16, + .size = SHIM_DMACNTX_SIZE_16, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB12, + .code = MEDIA_BUS_FMT_SRGGB12_1X12, + .csi_dt = MIPI_CSI2_DT_RAW12, + .bpp = 16, + .size = SHIM_DMACNTX_SIZE_16, + }, { .fourcc = V4L2_PIX_FMT_RGB565X, .code = MEDIA_BUS_FMT_RGB565_1X16, .csi_dt = MIPI_CSI2_DT_RGB565, @@ -1043,6 +1067,7 @@ static int ti_csi2rx_enum_mbus_code(struct v4l2_subdev *subdev, } static int ti_csi2rx_sd_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/platform/ti/omap3isp/ispccdc.c b/drivers/media/platform/ti/omap3isp/ispccdc.c index 4708b6303493..f422d70ab6e4 100644 --- a/drivers/media/platform/ti/omap3isp/ispccdc.c +++ b/drivers/media/platform/ti/omap3isp/ispccdc.c @@ -2232,6 +2232,7 @@ static int ccdc_enum_frame_size(struct v4l2_subdev *sd, * Return 0 on success or a negative error code otherwise. */ static int ccdc_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -2276,6 +2277,7 @@ static int ccdc_get_selection(struct v4l2_subdev *sd, * Return 0 on success or a negative error code otherwise. */ static int ccdc_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -2346,6 +2348,7 @@ static int ccdc_get_format(struct v4l2_subdev *sd, * to the format type. */ static int ccdc_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -2472,7 +2475,7 @@ static int ccdc_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) format.format.code = MEDIA_BUS_FMT_SGRBG10_1X10; format.format.width = 4096; format.format.height = 4096; - ccdc_set_format(sd, fh ? fh->state : NULL, &format); + ccdc_set_format(sd, NULL, fh ? fh->state : NULL, &format); return 0; } diff --git a/drivers/media/platform/ti/omap3isp/ispccp2.c b/drivers/media/platform/ti/omap3isp/ispccp2.c index d668111b44f4..c0da5cca9ddc 100644 --- a/drivers/media/platform/ti/omap3isp/ispccp2.c +++ b/drivers/media/platform/ti/omap3isp/ispccp2.c @@ -776,6 +776,7 @@ static int ccp2_get_format(struct v4l2_subdev *sd, * returns zero */ static int ccp2_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -820,7 +821,7 @@ static int ccp2_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) format.format.code = MEDIA_BUS_FMT_SGRBG10_1X10; format.format.width = 4096; format.format.height = 4096; - ccp2_set_format(sd, fh ? fh->state : NULL, &format); + ccp2_set_format(sd, NULL, fh ? fh->state : NULL, &format); return 0; } diff --git a/drivers/media/platform/ti/omap3isp/ispcsi2.c b/drivers/media/platform/ti/omap3isp/ispcsi2.c index f227042b61b6..8fc2948d654c 100644 --- a/drivers/media/platform/ti/omap3isp/ispcsi2.c +++ b/drivers/media/platform/ti/omap3isp/ispcsi2.c @@ -994,6 +994,7 @@ static int csi2_get_format(struct v4l2_subdev *sd, * return -EINVAL or zero on success */ static int csi2_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -1038,7 +1039,7 @@ static int csi2_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) format.format.code = MEDIA_BUS_FMT_SGRBG10_1X10; format.format.width = 4096; format.format.height = 4096; - csi2_set_format(sd, fh ? fh->state : NULL, &format); + csi2_set_format(sd, NULL, fh ? fh->state : NULL, &format); return 0; } diff --git a/drivers/media/platform/ti/omap3isp/isppreview.c b/drivers/media/platform/ti/omap3isp/isppreview.c index 3f3b5bd9cdc7..ac5025ce4315 100644 --- a/drivers/media/platform/ti/omap3isp/isppreview.c +++ b/drivers/media/platform/ti/omap3isp/isppreview.c @@ -1925,6 +1925,7 @@ static int preview_enum_frame_size(struct v4l2_subdev *sd, * Return 0 on success or a negative error code otherwise. */ static int preview_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1968,6 +1969,7 @@ static int preview_get_selection(struct v4l2_subdev *sd, * Return 0 on success or a negative error code otherwise. */ static int preview_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -2035,6 +2037,7 @@ static int preview_get_format(struct v4l2_subdev *sd, * return -EINVAL or zero on success */ static int preview_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -2090,7 +2093,7 @@ static int preview_init_formats(struct v4l2_subdev *sd, format.format.code = MEDIA_BUS_FMT_SGRBG10_1X10; format.format.width = 4096; format.format.height = 4096; - preview_set_format(sd, fh ? fh->state : NULL, &format); + preview_set_format(sd, NULL, fh ? fh->state : NULL, &format); return 0; } diff --git a/drivers/media/platform/ti/omap3isp/ispresizer.c b/drivers/media/platform/ti/omap3isp/ispresizer.c index ad0127f5b5cb..874aa451a2d0 100644 --- a/drivers/media/platform/ti/omap3isp/ispresizer.c +++ b/drivers/media/platform/ti/omap3isp/ispresizer.c @@ -1222,6 +1222,7 @@ static void resizer_try_crop(const struct v4l2_mbus_framefmt *sink, * Return 0 on success or a negative error code otherwise. */ static int resizer_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1275,6 +1276,7 @@ static int resizer_get_selection(struct v4l2_subdev *sd, * Return 0 on success or a negative error code otherwise. */ static int resizer_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1500,6 +1502,7 @@ static int resizer_get_format(struct v4l2_subdev *sd, * return -EINVAL or zero on success */ static int resizer_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -1577,7 +1580,7 @@ static int resizer_init_formats(struct v4l2_subdev *sd, format.format.code = MEDIA_BUS_FMT_YUYV8_1X16; format.format.width = 4096; format.format.height = 4096; - resizer_set_format(sd, fh ? fh->state : NULL, &format); + resizer_set_format(sd, NULL, fh ? fh->state : NULL, &format); return 0; } diff --git a/drivers/media/platform/ti/omap3isp/ispvideo.c b/drivers/media/platform/ti/omap3isp/ispvideo.c index b946c8087c77..cd324c730318 100644 --- a/drivers/media/platform/ti/omap3isp/ispvideo.c +++ b/drivers/media/platform/ti/omap3isp/ispvideo.c @@ -839,7 +839,7 @@ isp_video_get_selection(struct file *file, void *fh, struct v4l2_selection *sel) * implemented. */ sdsel.pad = pad; - ret = v4l2_subdev_call(subdev, pad, get_selection, NULL, &sdsel); + ret = v4l2_subdev_call(subdev, pad, get_selection, NULL, NULL, &sdsel); if (!ret) sel->r = sdsel.r; if (ret != -ENOIOCTLCMD) @@ -890,7 +890,7 @@ isp_video_set_selection(struct file *file, void *fh, struct v4l2_selection *sel) sdsel.pad = pad; mutex_lock(&video->mutex); - ret = v4l2_subdev_call(subdev, pad, set_selection, NULL, &sdsel); + ret = v4l2_subdev_call(subdev, pad, set_selection, NULL, NULL, &sdsel); mutex_unlock(&video->mutex); if (!ret) sel->r = sdsel.r; diff --git a/drivers/media/platform/ti/vpe/vip.c b/drivers/media/platform/ti/vpe/vip.c index ccb688f4d7af..2bbe8eb31555 100644 --- a/drivers/media/platform/ti/vpe/vip.c +++ b/drivers/media/platform/ti/vpe/vip.c @@ -1748,7 +1748,7 @@ static int vip_s_fmt_vid_cap(struct file *file, void *priv, sfmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; sfmt.pad = 0; - ret = v4l2_subdev_call(port->subdev, pad, set_fmt, NULL, &sfmt); + ret = v4l2_subdev_call(port->subdev, pad, set_fmt, NULL, NULL, &sfmt); if (ret) { v4l2_dbg(1, debug, &dev->v4l2_dev, "set_fmt failed in subdev\n"); return ret; @@ -2620,7 +2620,7 @@ static int vip_init_port(struct vip_port *port) mbus_fmt->code = fmt->code; sd_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; sd_fmt.pad = 0; - ret = v4l2_subdev_call(port->subdev, pad, set_fmt, + ret = v4l2_subdev_call(port->subdev, pad, set_fmt, NULL, NULL, &sd_fmt); if (ret) v4l2_dbg(1, debug, &dev->v4l2_dev, "init_port set_fmt failed in subdev: (%d)\n", diff --git a/drivers/media/platform/via/via-camera.c b/drivers/media/platform/via/via-camera.c index 1b81acba7da0..6a6e10c6f649 100644 --- a/drivers/media/platform/via/via-camera.c +++ b/drivers/media/platform/via/via-camera.c @@ -249,7 +249,7 @@ static int viacam_configure_sensor(struct via_camera *cam) v4l2_fill_mbus_format(&format.format, &cam->sensor_format, cam->mbus_code); ret = sensor_call(cam, core, init, 0); if (ret == 0) - ret = sensor_call(cam, pad, set_fmt, NULL, &format); + ret = sensor_call(cam, pad, set_fmt, NULL, NULL, &format); /* * OV7670 does weird things if flip is set *before* format... */ @@ -842,7 +842,7 @@ static int viacam_do_try_fmt(struct via_camera *cam, upix->pixelformat = f->pixelformat; viacam_fmt_pre(upix, spix); v4l2_fill_mbus_format(&format.format, spix, f->mbus_code); - ret = sensor_call(cam, pad, set_fmt, &pad_state, &format); + ret = sensor_call(cam, pad, set_fmt, NULL, &pad_state, &format); v4l2_fill_pix_format(spix, &format.format); viacam_fmt_post(upix, spix); return ret; diff --git a/drivers/media/platform/video-mux.c b/drivers/media/platform/video-mux.c index cba34893258a..b253b260b605 100644 --- a/drivers/media/platform/video-mux.c +++ b/drivers/media/platform/video-mux.c @@ -146,6 +146,7 @@ static const struct v4l2_subdev_video_ops video_mux_subdev_video_ops = { }; static int video_mux_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/platform/xilinx/xilinx-csi2rxss.c b/drivers/media/platform/xilinx/xilinx-csi2rxss.c index 146131b8f37e..2f6f3af4e492 100644 --- a/drivers/media/platform/xilinx/xilinx-csi2rxss.c +++ b/drivers/media/platform/xilinx/xilinx-csi2rxss.c @@ -694,6 +694,7 @@ static int xcsi2rxss_get_format(struct v4l2_subdev *sd, } static int xcsi2rxss_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/platform/xilinx/xilinx-tpg.c b/drivers/media/platform/xilinx/xilinx-tpg.c index 7deec6e37edc..bf39ddb9c296 100644 --- a/drivers/media/platform/xilinx/xilinx-tpg.c +++ b/drivers/media/platform/xilinx/xilinx-tpg.c @@ -278,6 +278,7 @@ static int xtpg_get_format(struct v4l2_subdev *subdev, } static int xtpg_set_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/radio/si4713/radio-usb-si4713.c b/drivers/media/radio/si4713/radio-usb-si4713.c index 6e6764143e21..62877980fc04 100644 --- a/drivers/media/radio/si4713/radio-usb-si4713.c +++ b/drivers/media/radio/si4713/radio-usb-si4713.c @@ -192,7 +192,7 @@ static int si4713_send_startup_command(struct si4713_usb_device *radio) } if (time_is_before_jiffies(until_jiffies)) return -EIO; - msleep(3); + usleep_range(3000, 4000); } return retval; @@ -339,7 +339,7 @@ static int si4713_i2c_read(struct si4713_usb_device *radio, char *data, int len) data[0] = 0; return 0; } - msleep(3); + usleep_range(3000, 4000); } } diff --git a/drivers/media/radio/si4713/si4713.c b/drivers/media/radio/si4713/si4713.c index 0c0354566b0a..150d07f6b319 100644 --- a/drivers/media/radio/si4713/si4713.c +++ b/drivers/media/radio/si4713/si4713.c @@ -667,7 +667,7 @@ static int si4713_tx_tune_measure(struct si4713_device *sdev, u16 frequency, /* * si4713_tx_tune_status- Returns the status of the tx_tune_freq, tx_tune_mea or * tx_tune_power commands. This command return the current - * frequency, output voltage in dBuV, the antenna tunning + * frequency, output voltage in dBuV, the antenna tuning * capacitance value and the received noise level. The * command also clears the stcint interrupt bit when the * first bit of its arguments is high. diff --git a/drivers/media/rc/bpf-lirc.c b/drivers/media/rc/bpf-lirc.c index 2f7564f26445..14ab611e7445 100644 --- a/drivers/media/rc/bpf-lirc.c +++ b/drivers/media/rc/bpf-lirc.c @@ -148,12 +148,13 @@ static int lirc_bpf_attach(struct rc_dev *rcdev, struct bpf_prog *prog) if (ret) return ret; - raw = rcdev->raw; - if (!raw) { + if (!rcdev->registered) { ret = -ENODEV; goto unlock; } + raw = rcdev->raw; + old_array = lirc_rcu_dereference(raw->progs); if (old_array && bpf_prog_array_length(old_array) >= BPF_MAX_PROGS) { ret = -E2BIG; @@ -186,12 +187,13 @@ static int lirc_bpf_detach(struct rc_dev *rcdev, struct bpf_prog *prog) if (ret) return ret; - raw = rcdev->raw; - if (!raw) { + if (!rcdev->registered) { ret = -ENODEV; goto unlock; } + raw = rcdev->raw; + old_array = lirc_rcu_dereference(raw->progs); ret = bpf_prog_array_copy(old_array, prog, NULL, 0, &new_array); /* @@ -235,7 +237,8 @@ void lirc_bpf_free(struct rc_dev *rcdev) struct bpf_prog_array_item *item; struct bpf_prog_array *array; - array = lirc_rcu_dereference(rcdev->raw->progs); + array = rcu_replace_pointer(rcdev->raw->progs, NULL, + lockdep_is_held(&ir_raw_handler_lock)); if (!array) return; @@ -316,6 +319,11 @@ int lirc_prog_query(const union bpf_attr *attr, union bpf_attr __user *uattr) if (ret) goto put; + if (!rcdev->registered) { + ret = -ENODEV; + goto unlock; + } + progs = lirc_rcu_dereference(rcdev->raw->progs); cnt = progs ? bpf_prog_array_length(progs) : 0; diff --git a/drivers/media/rc/ene_ir.c b/drivers/media/rc/ene_ir.c index 6f7dccc965e7..f98d76277b62 100644 --- a/drivers/media/rc/ene_ir.c +++ b/drivers/media/rc/ene_ir.c @@ -1103,15 +1103,15 @@ static void ene_remove(struct pnp_dev *pnp_dev) unsigned long flags; rc_unregister_device(dev->rdev); - timer_delete_sync(&dev->tx_sim_timer); spin_lock_irqsave(&dev->hw_lock, flags); ene_rx_disable(dev); ene_rx_restore_hw_buffer(dev); spin_unlock_irqrestore(&dev->hw_lock, flags); - rc_free_device(dev->rdev); free_irq(dev->irq, dev); + timer_delete_sync(&dev->tx_sim_timer); release_region(dev->hw_io, ENE_IO_SIZE); + rc_free_device(dev->rdev); kfree(dev); } diff --git a/drivers/media/rc/fintek-cir.c b/drivers/media/rc/fintek-cir.c index c196ee923ecd..d4368181fef6 100644 --- a/drivers/media/rc/fintek-cir.c +++ b/drivers/media/rc/fintek-cir.c @@ -140,17 +140,6 @@ static int fintek_hw_detect(struct fintek_dev *fintek) ir_class = fintek_cir_reg_read(fintek, CIR_CR_CLASS); fit_dbg("ir_class reg: 0x%02x", ir_class); - switch (ir_class) { - case CLASS_RX_2TX: - case CLASS_RX_1TX: - fintek->hw_tx_capable = true; - break; - case CLASS_RX_ONLY: - default: - fintek->hw_tx_capable = false; - break; - } - chip_major = fintek_cr_read(fintek, GCR_CHIP_ID_HI); chip_minor = fintek_cr_read(fintek, GCR_CHIP_ID_LO); chip = chip_major << 8 | chip_minor; @@ -169,7 +158,6 @@ static int fintek_hw_detect(struct fintek_dev *fintek) spin_lock_irqsave(&fintek->fintek_lock, flags); fintek->chip_major = chip_major; fintek->chip_minor = chip_minor; - fintek->chip_vendor = vendor; /* * Newer reviews of this chipset uses port 8 instead of 5 @@ -496,7 +484,6 @@ static int fintek_probe(struct pnp_dev *pdev, const struct pnp_device_id *dev_id spin_lock_init(&fintek->fintek_lock); pnp_set_drvdata(pdev, fintek); - fintek->pdev = pdev; ret = fintek_hw_detect(fintek); if (ret) diff --git a/drivers/media/rc/fintek-cir.h b/drivers/media/rc/fintek-cir.h index 20696359e9ba..9ade9d7e7bfb 100644 --- a/drivers/media/rc/fintek-cir.h +++ b/drivers/media/rc/fintek-cir.h @@ -40,11 +40,9 @@ static int debug; KBUILD_MODNAME ": " text "\n" , ## __VA_ARGS__) -#define TX_BUF_LEN 256 #define RX_BUF_LEN 32 struct fintek_dev { - struct pnp_dev *pdev; struct rc_dev *rdev; spinlock_t fintek_lock; @@ -53,14 +51,6 @@ struct fintek_dev { u8 buf[RX_BUF_LEN]; unsigned int pkts; - struct { - spinlock_t lock; - u8 buf[TX_BUF_LEN]; - unsigned int buf_count; - unsigned int cur_buf_num; - wait_queue_head_t queue; - } tx; - /* Config register index/data port pair */ u32 cr_ip; u32 cr_dp; @@ -73,17 +63,8 @@ struct fintek_dev { /* hardware id */ u8 chip_major; u8 chip_minor; - u16 chip_vendor; u8 logical_dev_cir; - /* hardware features */ - bool hw_learning_capable; - bool hw_tx_capable; - - /* rx settings */ - bool learning_enabled; - bool carrier_detect_enabled; - enum { CMD_HEADER = 0, SUBCMD, @@ -92,9 +73,6 @@ struct fintek_dev { } parser_state; u8 cmd, rem; - - /* carrier period = 1 / frequency */ - u32 carrier; }; /* buffer packet constants, largely identical to mceusb.c */ diff --git a/drivers/media/rc/imon.c b/drivers/media/rc/imon.c index 049a73b5f882..2668461ed409 100644 --- a/drivers/media/rc/imon.c +++ b/drivers/media/rc/imon.c @@ -96,7 +96,6 @@ struct imon_context { bool dev_present_intf1; /* USB device presence, interface 1 */ struct mutex lock; /* to lock this object */ - wait_queue_head_t remove_ok; /* For unexpected USB disconnects */ struct usb_endpoint_descriptor *rx_endpoint_intf0; struct usb_endpoint_descriptor *rx_endpoint_intf1; @@ -378,7 +377,7 @@ static const struct usb_device_id imon_usb_id_table[] = { * SoundGraph iMON PAD (IR & LCD) * SoundGraph iMON Knob (IR only) */ - { USB_DEVICE(0x15c2, 0xffdc), + { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0xffdc, 0), .driver_info = (unsigned long)&imon_default_table }, /* @@ -387,61 +386,61 @@ static const struct usb_device_id imon_usb_id_table[] = { * Need user input to fill in details on unknown devices. */ /* SoundGraph iMON OEM Touch LCD (IR & 7" VGA LCD) */ - { USB_DEVICE(0x15c2, 0x0034), + { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x0034, 0), .driver_info = (unsigned long)&imon_DH102 }, /* SoundGraph iMON OEM Touch LCD (IR & 4.3" VGA LCD) */ - { USB_DEVICE(0x15c2, 0x0035), + { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x0035, 0), .driver_info = (unsigned long)&imon_default_table}, /* SoundGraph iMON OEM VFD (IR & VFD) */ - { USB_DEVICE(0x15c2, 0x0036), + { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x0036, 0), .driver_info = (unsigned long)&imon_OEM_VFD }, /* device specifics unknown */ - { USB_DEVICE(0x15c2, 0x0037), + { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x0037, 0), .driver_info = (unsigned long)&imon_default_table}, /* SoundGraph iMON OEM LCD (IR & LCD) */ - { USB_DEVICE(0x15c2, 0x0038), + { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x0038, 0), .driver_info = (unsigned long)&imon_default_table}, /* SoundGraph iMON UltraBay (IR & LCD) */ - { USB_DEVICE(0x15c2, 0x0039), + { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x0039, 0), .driver_info = (unsigned long)&imon_default_table}, /* device specifics unknown */ - { USB_DEVICE(0x15c2, 0x003a), + { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x003a, 0), .driver_info = (unsigned long)&imon_default_table}, /* device specifics unknown */ - { USB_DEVICE(0x15c2, 0x003b), + { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x003b, 0), .driver_info = (unsigned long)&imon_default_table}, /* SoundGraph iMON OEM Inside (IR only) */ - { USB_DEVICE(0x15c2, 0x003c), + { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x003c, 0), .driver_info = (unsigned long)&imon_default_table}, /* device specifics unknown */ - { USB_DEVICE(0x15c2, 0x003d), + { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x003d, 0), .driver_info = (unsigned long)&imon_default_table}, /* device specifics unknown */ - { USB_DEVICE(0x15c2, 0x003e), + { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x003e, 0), .driver_info = (unsigned long)&imon_default_table}, /* device specifics unknown */ - { USB_DEVICE(0x15c2, 0x003f), + { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x003f, 0), .driver_info = (unsigned long)&imon_default_table}, /* device specifics unknown */ - { USB_DEVICE(0x15c2, 0x0040), + { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x0040, 0), .driver_info = (unsigned long)&imon_default_table}, /* SoundGraph iMON MINI (IR only) */ - { USB_DEVICE(0x15c2, 0x0041), + { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x0041, 0), .driver_info = (unsigned long)&imon_default_table}, /* Antec Veris Multimedia Station EZ External (IR only) */ - { USB_DEVICE(0x15c2, 0x0042), + { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x0042, 0), .driver_info = (unsigned long)&imon_default_table}, /* Antec Veris Multimedia Station Basic Internal (IR only) */ - { USB_DEVICE(0x15c2, 0x0043), + { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x0043, 0), .driver_info = (unsigned long)&imon_default_table}, /* Antec Veris Multimedia Station Elite (IR & VFD) */ - { USB_DEVICE(0x15c2, 0x0044), + { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x0044, 0), .driver_info = (unsigned long)&imon_default_table}, /* Antec Veris Multimedia Station Premiere (IR & LCD) */ - { USB_DEVICE(0x15c2, 0x0045), + { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x0045, 0), .driver_info = (unsigned long)&imon_default_table}, /* device specifics unknown */ - { USB_DEVICE(0x15c2, 0x0046), + { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x0046, 0), .driver_info = (unsigned long)&imon_default_table}, {} }; @@ -805,19 +804,16 @@ static ssize_t associate_remote_show(struct device *d, char *buf) { struct imon_context *ictx = dev_get_drvdata(d); + int len; if (!ictx) return -ENODEV; mutex_lock(&ictx->lock); - if (ictx->rf_isassociating) - strscpy(buf, "associating\n", PAGE_SIZE); - else - strscpy(buf, "closed\n", PAGE_SIZE); - + len = sysfs_emit(buf, (ictx->rf_isassociating ? "associating\n" : "closed\n")); dev_info(d, "Visit https://www.lirc.org/html/imon-24g.html for instructions on how to associate your iMON 2.4G DT/LT remote\n"); mutex_unlock(&ictx->lock); - return strlen(buf); + return len; } static ssize_t associate_remote_store(struct device *d, @@ -2331,15 +2327,20 @@ static struct imon_context *imon_init_intf1(struct usb_interface *intf, struct usb_host_interface *iface_desc; int ret = -ENOMEM; + ret = usb_driver_claim_interface(&imon_driver, intf, ictx); + if (ret) { + dev_err(ictx->dev, "failed to claim second interface (%d)\n", ret); + return NULL; + } + rx_urb = usb_alloc_urb(0, GFP_KERNEL); if (!rx_urb) goto rx_urb_alloc_failed; mutex_lock(&ictx->lock); - if (ictx->display_type == IMON_DISPLAY_TYPE_VGA) { + if (ictx->display_type == IMON_DISPLAY_TYPE_VGA) timer_setup(&ictx->ttimer, imon_touch_display_timeout, 0); - } ictx->usbdev_intf1 = interface_to_usbdev(intf); ictx->rx_urb_intf1 = rx_urb; @@ -2366,13 +2367,14 @@ static struct imon_context *imon_init_intf1(struct usb_interface *intf, ret = usb_submit_urb(ictx->rx_urb_intf1, GFP_KERNEL); if (ret) { - pr_err("usb_submit_urb failed for intf1 (%d)\n", ret); + dev_err(ictx->dev, "usb_submit_urb failed for intf1 (%d)\n", ret); goto urb_submit_failed; } ictx->dev_present_intf1 = true; mutex_unlock(&ictx->lock); + return ictx; urb_submit_failed: @@ -2380,11 +2382,14 @@ urb_submit_failed: input_unregister_device(ictx->touch); touch_setup_failed: find_endpoint_failed: + if (ictx->display_type == IMON_DISPLAY_TYPE_VGA) + timer_delete_sync(&ictx->ttimer); ictx->usbdev_intf1 = NULL; mutex_unlock(&ictx->lock); usb_free_urb(rx_urb); ictx->rx_urb_intf1 = NULL; rx_urb_alloc_failed: + usb_driver_release_interface(&imon_driver, intf); dev_err(ictx->dev, "unable to initialize intf1, err %d\n", ret); return NULL; @@ -2419,90 +2424,50 @@ static void imon_init_display(struct imon_context *ictx, static int imon_probe(struct usb_interface *interface, const struct usb_device_id *id) { - struct usb_device *usbdev = NULL; - struct usb_host_interface *iface_desc = NULL; - struct usb_interface *first_if; + struct usb_device *usbdev; + struct usb_interface *second_if; struct device *dev = &interface->dev; - int ifnum, sysfs_err; - int ret = 0; - struct imon_context *ictx = NULL; + int sysfs_err; + struct imon_context *ictx; u16 vendor, product; - usbdev = interface_to_usbdev(interface); - iface_desc = interface->cur_altsetting; - ifnum = iface_desc->desc.bInterfaceNumber; - vendor = le16_to_cpu(usbdev->descriptor.idVendor); - product = le16_to_cpu(usbdev->descriptor.idProduct); - - dev_dbg(dev, "%s: found iMON device (%04x:%04x, intf%d)\n", - __func__, vendor, product, ifnum); + if (interface->cur_altsetting->desc.bInterfaceNumber != 0) + return -ENODEV; - first_if = usb_ifnum_to_if(usbdev, 0); - if (!first_if) { - ret = -ENODEV; - goto fail; - } + usbdev = interface_to_usbdev(interface); + vendor = le16_to_cpu(usbdev->descriptor.idVendor); + product = le16_to_cpu(usbdev->descriptor.idProduct); - if (first_if->dev.driver != interface->dev.driver) { - dev_err(&interface->dev, "inconsistent driver matching\n"); - ret = -EINVAL; - goto fail; - } + dev_dbg(dev, "found iMON device (%04x:%04x)\n", vendor, product); - if (ifnum == 0) { - ictx = imon_init_intf0(interface, id); - if (!ictx) { - pr_err("failed to initialize context!\n"); - ret = -ENODEV; - goto fail; - } - refcount_set(&ictx->users, 1); + ictx = imon_init_intf0(interface, id); + if (!ictx) + return -ENODEV; - } else { - /* this is the secondary interface on the device */ - struct imon_context *first_if_ctx = usb_get_intfdata(first_if); + refcount_set(&ictx->users, 1); - /* fail early if first intf failed to register */ - if (!first_if_ctx) { - ret = -ENODEV; - goto fail; - } + usb_set_intfdata(interface, ictx); - ictx = imon_init_intf1(interface, first_if_ctx); - if (!ictx) { - pr_err("failed to attach to context!\n"); - ret = -ENODEV; - goto fail; - } + /* Newer devices export a second interface for display/touchscreen */ + second_if = usb_ifnum_to_if(usbdev, 1); + if (second_if && imon_init_intf1(second_if, ictx)) refcount_inc(&ictx->users); + if (product == 0xffdc && ictx->rf_device) { + sysfs_err = sysfs_create_group(&interface->dev.kobj, + &imon_rf_attr_group); + if (sysfs_err) + pr_err("Could not create RF sysfs entries(%d)\n", + sysfs_err); } - usb_set_intfdata(interface, ictx); + if (ictx->display_supported) + imon_init_display(ictx, interface); - if (ifnum == 0) { - if (product == 0xffdc && ictx->rf_device) { - sysfs_err = sysfs_create_group(&interface->dev.kobj, - &imon_rf_attr_group); - if (sysfs_err) - pr_err("Could not create RF sysfs entries(%d)\n", - sysfs_err); - } - - if (ictx->display_supported) - imon_init_display(ictx, interface); - } - - dev_info(dev, "iMON device (%04x:%04x, intf%d) on usb<%d:%d> initialized\n", - vendor, product, ifnum, - usbdev->bus->busnum, usbdev->devnum); + dev_info(dev, "iMON device (%04x:%04x) on usb<%d:%d> initialized\n", + vendor, product, usbdev->bus->busnum, usbdev->devnum); return 0; - -fail: - dev_err(dev, "unable to register, err %d\n", ret); - - return ret; } /* @@ -2510,11 +2475,15 @@ fail: */ static void imon_disconnect(struct usb_interface *interface) { + struct usb_device *usbdev = interface_to_usbdev(interface); + struct usb_interface *other_if; struct imon_context *ictx; struct device *dev; int ifnum; ictx = usb_get_intfdata(interface); + if (!ictx) + return; mutex_lock(&ictx->lock); ictx->disconnected = true; @@ -2550,6 +2519,7 @@ static void imon_disconnect(struct usb_interface *interface) else if (ictx->display_type == IMON_DISPLAY_TYPE_VFD) usb_deregister_dev(interface, &imon_vfd_class); } + other_if = usb_ifnum_to_if(usbdev, 1); } else { ictx->dev_present_intf1 = false; usb_kill_urb(ictx->rx_urb_intf1); @@ -2557,13 +2527,16 @@ static void imon_disconnect(struct usb_interface *interface) timer_delete_sync(&ictx->ttimer); input_unregister_device(ictx->touch); } + other_if = usb_ifnum_to_if(usbdev, 0); } + if (other_if) + usb_driver_release_interface(&imon_driver, other_if); + if (refcount_dec_and_test(&ictx->users)) free_imon_context(ictx); - dev_dbg(dev, "%s: iMON device (intf%d) disconnected\n", - __func__, ifnum); + dev_dbg(dev, "iMON device (intf%d) disconnected\n", ifnum); } static int imon_suspend(struct usb_interface *intf, pm_message_t message) diff --git a/drivers/media/rc/ir-hix5hd2.c b/drivers/media/rc/ir-hix5hd2.c index 1b061e4a3dcf..aa3de4d57a58 100644 --- a/drivers/media/rc/ir-hix5hd2.c +++ b/drivers/media/rc/ir-hix5hd2.c @@ -316,6 +316,9 @@ static int hix5hd2_ir_probe(struct platform_device *pdev) if (ret < 0) goto clkerr; + priv->rdev = rdev; + priv->dev = dev; + if (devm_request_irq(dev, priv->irq, hix5hd2_ir_rx_interrupt, 0, pdev->name, priv) < 0) { dev_err(dev, "IRQ %d register failed\n", priv->irq); @@ -323,8 +326,6 @@ static int hix5hd2_ir_probe(struct platform_device *pdev) goto regerr; } - priv->rdev = rdev; - priv->dev = dev; platform_set_drvdata(pdev, priv); return ret; diff --git a/drivers/media/rc/ir-mce_kbd-decoder.c b/drivers/media/rc/ir-mce_kbd-decoder.c index bb2d7c37c263..d1c9263511b0 100644 --- a/drivers/media/rc/ir-mce_kbd-decoder.c +++ b/drivers/media/rc/ir-mce_kbd-decoder.c @@ -219,6 +219,7 @@ static int ir_mce_kbd_decode(struct rc_dev *dev, struct ir_raw_event ev) struct mce_kbd_dec *data = &dev->raw->mce_kbd; u32 scancode; unsigned long delay; + unsigned long flags; struct lirc_scancode lsc = {}; if (!is_timing_event(ev)) { @@ -319,7 +320,7 @@ again: scancode = data->body & 0xffffff; dev_dbg(&dev->dev, "keyboard data 0x%08x\n", data->body); - spin_lock(&data->keylock); + spin_lock_irqsave(&data->keylock, flags); if (scancode) { delay = usecs_to_jiffies(dev->timeout) + msecs_to_jiffies(100); @@ -329,7 +330,7 @@ again: } /* Pass data to keyboard buffer parser */ ir_mce_kbd_process_keyboard_data(dev, scancode); - spin_unlock(&data->keylock); + spin_unlock_irqrestore(&data->keylock, flags); lsc.rc_proto = RC_PROTO_MCIR2_KBD; break; case MCIR2_MOUSE_NBITS: diff --git a/drivers/media/rc/ir_toy.c b/drivers/media/rc/ir_toy.c index ee645882dd5a..58b23d08a897 100644 --- a/drivers/media/rc/ir_toy.c +++ b/drivers/media/rc/ir_toy.c @@ -55,7 +55,6 @@ enum state { struct irtoy { struct device *dev; - struct usb_device *usbdev; struct rc_dev *rc; struct urb *urb_in, *urb_out; @@ -373,7 +372,7 @@ static int irtoy_tx_carrier(struct rc_dev *rc, uint32_t carrier) u8 buf[3]; int err; - if (carrier < 11800) + if (carrier < 11800 || carrier > 3000000) return -EINVAL; buf[0] = 0x06; @@ -441,7 +440,6 @@ static int irtoy_probe(struct usb_interface *intf, irtoy_out_callback, irtoy); irtoy->dev = &intf->dev; - irtoy->usbdev = usbdev; irtoy->rc = rc; irtoy->urb_out = urb; irtoy->pulse = true; diff --git a/drivers/media/rc/ite-cir.c b/drivers/media/rc/ite-cir.c index 1fbafcd8219e..d0e98095d1b6 100644 --- a/drivers/media/rc/ite-cir.c +++ b/drivers/media/rc/ite-cir.c @@ -1359,7 +1359,6 @@ static int ite_probe(struct pnp_dev *pdev, const struct pnp_device_id /* set driver data into the pnp device */ pnp_set_drvdata(pdev, itdev); - itdev->pdev = pdev; /* initialize waitqueues for transmission */ init_waitqueue_head(&itdev->tx_queue); diff --git a/drivers/media/rc/ite-cir.h b/drivers/media/rc/ite-cir.h index 4b4294d77555..a8382aa9dc35 100644 --- a/drivers/media/rc/ite-cir.h +++ b/drivers/media/rc/ite-cir.h @@ -76,7 +76,6 @@ struct ite_dev_params { /* ITE CIR device structure */ struct ite_dev { - struct pnp_dev *pdev; struct rc_dev *rdev; /* sync data */ diff --git a/drivers/media/rc/lirc_dev.c b/drivers/media/rc/lirc_dev.c index 183f1939b941..f7b8f1f66d9b 100644 --- a/drivers/media/rc/lirc_dev.c +++ b/drivers/media/rc/lirc_dev.c @@ -492,6 +492,8 @@ static long lirc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) ret = -ENOTTY; else if (val <= 0) ret = -EINVAL; + else if (fh->carrier_low && fh->carrier_low > val) + ret = -EINVAL; else ret = dev->s_rx_carrier_range(dev, fh->carrier_low, val); @@ -775,6 +777,9 @@ void lirc_unregister(struct rc_dev *dev) unsigned long flags; struct lirc_fh *fh; + if (!device_is_registered(&dev->lirc_dev)) + return; + dev_dbg(&dev->dev, "lirc_dev: driver %s unregistered from minor = %d\n", dev->driver_name, MINOR(dev->lirc_dev.devt)); diff --git a/drivers/media/rc/mceusb.c b/drivers/media/rc/mceusb.c index 39ba7f6a2549..356a54f91d05 100644 --- a/drivers/media/rc/mceusb.c +++ b/drivers/media/rc/mceusb.c @@ -484,7 +484,6 @@ struct mceusb_dev { u8 cmd, rem; /* Remaining IR data bytes in packet */ struct { - u32 connected:1; u32 tx_mask_normal:1; u32 microsoft_gen1:1; u32 no_tx:1; diff --git a/drivers/media/rc/meson-ir-tx.c b/drivers/media/rc/meson-ir-tx.c index fded2c256f2a..a0d07da09f67 100644 --- a/drivers/media/rc/meson-ir-tx.c +++ b/drivers/media/rc/meson-ir-tx.c @@ -219,6 +219,9 @@ static int meson_irtx_set_carrier(struct rc_dev *rc, u32 carrier) if (carrier == 0) return -EINVAL; + if (!DIV_ROUND_CLOSEST(USEC_PER_SEC, carrier)) + return -EINVAL; + ir->carrier = carrier; meson_irtx_set_mod(ir); @@ -288,11 +291,12 @@ static int meson_irtx_mod_clock_probe(struct meson_irtx *ir, if (!np) return -ENODEV; - clock = devm_clk_get(ir->dev, "xtal"); - if (IS_ERR(clock) || clk_prepare_enable(clock)) - return -ENODEV; - *clk_nr = IRB_MOD_XTAL3_CLK; + + clock = devm_clk_get_enabled(ir->dev, "xtal"); + if (IS_ERR(clock)) + return PTR_ERR(clock); + ir->clk_rate = clk_get_rate(clock) / 3; if (ir->clk_rate < IRB_MOD_1US_CLK_RATE) { @@ -324,7 +328,7 @@ static int meson_irtx_probe(struct platform_device *pdev) irq = platform_get_irq(pdev, 0); if (irq < 0) - return -ENODEV; + return irq; ir->dev = dev; ir->carrier = MIRTX_DEFAULT_CARRIER; @@ -345,7 +349,7 @@ static int meson_irtx_probe(struct platform_device *pdev) if (ret) return dev_err_probe(dev, ret, "irq request failed\n"); - rc = rc_allocate_device(RC_DRIVER_IR_RAW_TX); + rc = devm_rc_allocate_device(dev, RC_DRIVER_IR_RAW_TX); if (!rc) return -ENOMEM; @@ -358,10 +362,8 @@ static int meson_irtx_probe(struct platform_device *pdev) rc->s_tx_duty_cycle = meson_irtx_set_duty_cycle; ret = devm_rc_register_device(dev, rc); - if (ret < 0) { - rc_free_device(rc); + if (ret < 0) return dev_err_probe(dev, ret, "rc_dev registration failed\n"); - } return 0; } diff --git a/drivers/media/rc/nuvoton-cir.h b/drivers/media/rc/nuvoton-cir.h index ed7d93beaa28..e66e3cfedeec 100644 --- a/drivers/media/rc/nuvoton-cir.h +++ b/drivers/media/rc/nuvoton-cir.h @@ -77,9 +77,6 @@ struct nvt_dev { /* hardware id */ u8 chip_major; u8 chip_minor; - - /* carrier period = 1 / frequency */ - u32 carrier; }; /* buffer packet constants */ diff --git a/drivers/media/rc/rc-ir-raw.c b/drivers/media/rc/rc-ir-raw.c index ba24c2f22d39..75dee4a483d4 100644 --- a/drivers/media/rc/rc-ir-raw.c +++ b/drivers/media/rc/rc-ir-raw.c @@ -10,7 +10,8 @@ #include <linux/sched.h> #include "rc-core-priv.h" -/* Used to keep track of IR raw clients, protected by ir_raw_handler_lock */ +/* Used to keep track of IR raw clients, protected by ir_raw_client_lock */ +static DEFINE_MUTEX(ir_raw_client_lock); static LIST_HEAD(ir_raw_client_list); /* Used to handle IR raw handler extensions */ @@ -71,9 +72,6 @@ static int ir_raw_event_thread(void *data) */ int ir_raw_event_store(struct rc_dev *dev, struct ir_raw_event *ev) { - if (!dev->raw) - return -EINVAL; - dev_dbg(&dev->dev, "sample: (%05dus %s)\n", ev->duration, TO_STR(ev->pulse)); @@ -102,9 +100,6 @@ int ir_raw_event_store_edge(struct rc_dev *dev, bool pulse) ktime_t now; struct ir_raw_event ev = {}; - if (!dev->raw) - return -EINVAL; - now = ktime_get(); ev.duration = ktime_to_us(ktime_sub(now, dev->raw->last_event)); ev.pulse = !pulse; @@ -129,9 +124,6 @@ int ir_raw_event_store_with_timeout(struct rc_dev *dev, struct ir_raw_event *ev) ktime_t now; int rc = 0; - if (!dev->raw) - return -EINVAL; - now = ktime_get(); spin_lock(&dev->raw->edge_spinlock); @@ -166,9 +158,6 @@ EXPORT_SYMBOL_GPL(ir_raw_event_store_with_timeout); */ int ir_raw_event_store_with_filter(struct rc_dev *dev, struct ir_raw_event *ev) { - if (!dev->raw) - return -EINVAL; - /* Ignore spaces in idle mode */ if (dev->idle && !ev->pulse) return 0; @@ -200,9 +189,6 @@ EXPORT_SYMBOL_GPL(ir_raw_event_store_with_filter); */ void ir_raw_event_set_idle(struct rc_dev *dev, bool idle) { - if (!dev->raw) - return; - dev_dbg(&dev->dev, "%s idle mode\n", idle ? "enter" : "leave"); if (idle) { @@ -226,7 +212,7 @@ EXPORT_SYMBOL_GPL(ir_raw_event_set_idle); */ void ir_raw_event_handle(struct rc_dev *dev) { - if (!dev->raw || !dev->raw->thread) + if (!dev->raw->thread) return; wake_up_process(dev->raw->thread); @@ -288,13 +274,6 @@ static int change_protocol(struct rc_dev *dev, u64 *rc_proto) return 0; } -static void ir_raw_disable_protocols(struct rc_dev *dev, u64 protocols) -{ - mutex_lock(&dev->lock); - dev->enabled_protocols &= ~protocols; - mutex_unlock(&dev->lock); -} - /** * ir_raw_gen_manchester() - Encode data with Manchester (bi-phase) modulation. * @ev: Pointer to pointer to next free event. *@ev is incremented for @@ -612,9 +591,6 @@ EXPORT_SYMBOL(ir_raw_encode_carrier); */ int ir_raw_event_prepare(struct rc_dev *dev) { - if (!dev) - return -EINVAL; - dev->raw = kzalloc_obj(*dev->raw); if (!dev->raw) return -ENOMEM; @@ -633,50 +609,63 @@ int ir_raw_event_register(struct rc_dev *dev) { struct task_struct *thread; + /* Holding dev->lock could result in a dead-lock */ + lockdep_assert_not_held(&dev->lock); + thread = kthread_run(ir_raw_event_thread, dev->raw, "rc%u", dev->minor); if (IS_ERR(thread)) return PTR_ERR(thread); dev->raw->thread = thread; - mutex_lock(&ir_raw_handler_lock); + mutex_lock(&ir_raw_client_lock); list_add_tail(&dev->raw->list, &ir_raw_client_list); - mutex_unlock(&ir_raw_handler_lock); + mutex_unlock(&ir_raw_client_lock); return 0; } void ir_raw_event_free(struct rc_dev *dev) { - kfree(dev->raw); - dev->raw = NULL; + if (dev->raw) { + timer_delete_sync(&dev->raw->edge_handle); + mutex_lock(&ir_raw_handler_lock); + if (dev->raw->thread) + put_task_struct(dev->raw->thread); + lirc_bpf_free(dev); + mutex_unlock(&ir_raw_handler_lock); + kfree(dev->raw); + dev->raw = NULL; + } } void ir_raw_event_unregister(struct rc_dev *dev) { struct ir_raw_handler *handler; - if (!dev || !dev->raw) - return; - + /* + * After ir_raw_event_unregister() is called, an sync + * call to ir_raw_event_handle() can still arrive. This function + * may call wake_up_process(dev->raw->thread). Ensure this memory + * is not freed by kthread_stop(). + */ + get_task_struct(dev->raw->thread); kthread_stop(dev->raw->thread); timer_delete_sync(&dev->raw->edge_handle); - mutex_lock(&ir_raw_handler_lock); + mutex_lock(&ir_raw_client_lock); list_del(&dev->raw->list); + + mutex_lock(&ir_raw_handler_lock); list_for_each_entry(handler, &ir_raw_handler_list, list) if (handler->raw_unregister && (handler->protocols & dev->enabled_protocols)) handler->raw_unregister(dev); lirc_bpf_free(dev); - - /* - * A user can be calling bpf(BPF_PROG_{QUERY|ATTACH|DETACH}), so - * ensure that the raw member is null on unlock; this is how - * "device gone" is checked. - */ mutex_unlock(&ir_raw_handler_lock); + + mutex_unlock(&ir_raw_client_lock); } /* @@ -699,15 +688,24 @@ void ir_raw_handler_unregister(struct ir_raw_handler *ir_raw_handler) struct ir_raw_event_ctrl *raw; u64 protocols = ir_raw_handler->protocols; + mutex_lock(&ir_raw_client_lock); + mutex_lock(&ir_raw_handler_lock); list_del(&ir_raw_handler->list); + atomic64_andnot(protocols, &available_protocols); + mutex_unlock(&ir_raw_handler_lock); + list_for_each_entry(raw, &ir_raw_client_list, list) { + mutex_lock(&raw->dev->lock); + mutex_lock(&ir_raw_handler_lock); if (ir_raw_handler->raw_unregister && (raw->dev->enabled_protocols & protocols)) ir_raw_handler->raw_unregister(raw->dev); - ir_raw_disable_protocols(raw->dev, protocols); + raw->dev->enabled_protocols &= ~protocols; + mutex_unlock(&ir_raw_handler_lock); + mutex_unlock(&raw->dev->lock); } - atomic64_andnot(protocols, &available_protocols); - mutex_unlock(&ir_raw_handler_lock); + + mutex_unlock(&ir_raw_client_lock); } EXPORT_SYMBOL(ir_raw_handler_unregister); diff --git a/drivers/media/rc/rc-loopback.c b/drivers/media/rc/rc-loopback.c index 53d0540717b3..9d56e77c9351 100644 --- a/drivers/media/rc/rc-loopback.c +++ b/drivers/media/rc/rc-loopback.c @@ -74,11 +74,6 @@ static int loop_set_rx_carrier_range(struct rc_dev *dev, u32 min, u32 max) { struct loopback_dev *lodev = dev->priv; - if (min < 1 || min > max) { - dev_dbg(&dev->dev, "invalid rx carrier range %u to %u\n", min, max); - return -EINVAL; - } - dev_dbg(&dev->dev, "setting rx carrier range %u to %u\n", min, max); lodev->rxcarriermin = min; lodev->rxcarriermax = max; diff --git a/drivers/media/rc/rc-main.c b/drivers/media/rc/rc-main.c index dda3479ea3ad..a867bf9e8416 100644 --- a/drivers/media/rc/rc-main.c +++ b/drivers/media/rc/rc-main.c @@ -17,9 +17,8 @@ #include <linux/module.h> #include "rc-core-priv.h" -/* Sizes are in bytes, 256 bytes allows for 32 entries on x64 */ -#define IR_TAB_MIN_SIZE 256 -#define IR_TAB_MAX_SIZE 8192 +#define IR_TAB_MIN_ENTRIES 32 +#define IR_TAB_MAX_ENTRIES 1024 static const struct { const char *name; @@ -105,7 +104,6 @@ static struct rc_map_list *seek_rc_map(const char *name) struct rc_map *rc_map_get(const char *name) { - struct rc_map_list *map; map = seek_rc_map(name); @@ -202,7 +200,7 @@ static int scancode_to_u64(const struct input_keymap_entry *ke, u64 *scancode) * ir_create_table() - initializes a scancode table * @dev: the rc_dev device * @rc_map: the rc_map to initialize - * @name: name to assign to the table + * @map_name: name to assign to the table * @rc_proto: ir type to assign to the new table * @size: initial size of the table * @@ -212,23 +210,33 @@ static int scancode_to_u64(const struct input_keymap_entry *ke, u64 *scancode) * return: zero on success or a negative error code */ static int ir_create_table(struct rc_dev *dev, struct rc_map *rc_map, - const char *name, u64 rc_proto, size_t size) + const char *map_name, u64 rc_proto, size_t size) { - rc_map->name = kstrdup(name, GFP_KERNEL); - if (!rc_map->name) + struct rc_map_table *scan; + unsigned int alloc; + char *name; + + name = kstrdup(map_name, GFP_KERNEL); + if (!name) return -ENOMEM; - rc_map->rc_proto = rc_proto; - rc_map->alloc = roundup_pow_of_two(size * sizeof(struct rc_map_table)); - rc_map->size = rc_map->alloc / sizeof(struct rc_map_table); - rc_map->scan = kmalloc(rc_map->alloc, GFP_KERNEL); - if (!rc_map->scan) { - kfree(rc_map->name); - rc_map->name = NULL; + + alloc = roundup_pow_of_two(size); + scan = kmalloc_objs(struct rc_map_table, alloc, GFP_KERNEL); + if (!scan) { + kfree(name); return -ENOMEM; } - dev_dbg(&dev->dev, "Allocated space for %u keycode entries (%u bytes)\n", - rc_map->size, rc_map->alloc); + scoped_guard(spinlock_irqsave, &rc_map->lock) { + rc_map->name = name; + rc_map->scan = scan; + rc_map->rc_proto = rc_proto; + rc_map->len = 0; + rc_map->size = alloc; + } + + dev_dbg(&dev->dev, "Allocated space for %u keycode entries (%zu bytes)\n", + alloc, alloc * sizeof(struct rc_map_table)); return 0; } @@ -236,16 +244,26 @@ static int ir_create_table(struct rc_dev *dev, struct rc_map *rc_map, * ir_free_table() - frees memory allocated by a scancode table * @rc_map: the table whose mappings need to be freed * - * This routine will free memory alloctaed for key mappings used by given + * This routine will free memory allocated for key mappings used by given * scancode table. */ static void ir_free_table(struct rc_map *rc_map) { - rc_map->size = 0; - kfree(rc_map->name); - rc_map->name = NULL; - kfree(rc_map->scan); - rc_map->scan = NULL; + struct rc_map_table *scan; + const char *name; + + scoped_guard(spinlock_irqsave, &rc_map->lock) { + name = rc_map->name; + scan = rc_map->scan; + + rc_map->size = 0; + rc_map->len = 0; + rc_map->name = NULL; + rc_map->scan = NULL; + } + + kfree(name); + kfree(scan); } /** @@ -262,38 +280,38 @@ static void ir_free_table(struct rc_map *rc_map) static int ir_resize_table(struct rc_dev *dev, struct rc_map *rc_map, gfp_t gfp_flags) { - unsigned int oldalloc = rc_map->alloc; - unsigned int newalloc = oldalloc; - struct rc_map_table *oldscan = rc_map->scan; + unsigned int newsize = rc_map->size; struct rc_map_table *newscan; + lockdep_assert_held(&rc_map->lock); + if (rc_map->size == rc_map->len) { /* All entries in use -> grow keytable */ - if (rc_map->alloc >= IR_TAB_MAX_SIZE) + if (newsize >= IR_TAB_MAX_ENTRIES) return -ENOMEM; - newalloc *= 2; - dev_dbg(&dev->dev, "Growing table to %u bytes\n", newalloc); + newsize *= 2; + + dev_dbg(&dev->dev, "Growing table to %u entries\n", newsize); } - if ((rc_map->len * 3 < rc_map->size) && (oldalloc > IR_TAB_MIN_SIZE)) { + if (rc_map->len * 3 < rc_map->size && rc_map->size > IR_TAB_MIN_ENTRIES) { /* Less than 1/3 of entries in use -> shrink keytable */ - newalloc /= 2; - dev_dbg(&dev->dev, "Shrinking table to %u bytes\n", newalloc); + newsize /= 2; + dev_dbg(&dev->dev, "Shrinking table to %u entries\n", newsize); } - if (newalloc == oldalloc) + if (newsize == rc_map->size) return 0; - newscan = kmalloc(newalloc, gfp_flags); + newscan = krealloc_array(rc_map->scan, newsize, + sizeof(struct rc_map_table), gfp_flags); if (!newscan) return -ENOMEM; - memcpy(newscan, rc_map->scan, rc_map->len * sizeof(struct rc_map_table)); rc_map->scan = newscan; - rc_map->alloc = newalloc; - rc_map->size = rc_map->alloc / sizeof(struct rc_map_table); - kfree(oldscan); + rc_map->size = newsize; + return 0; } @@ -318,6 +336,8 @@ static unsigned int ir_update_mapping(struct rc_dev *dev, int old_keycode = rc_map->scan[index].keycode; int i; + lockdep_assert_held(&rc_map->lock); + /* Did the user wish to remove the mapping? */ if (new_keycode == KEY_RESERVED || new_keycode == KEY_UNKNOWN) { dev_dbg(&dev->dev, "#%d: Deleting scan 0x%04llx\n", @@ -371,7 +391,9 @@ static unsigned int ir_establish_scancode(struct rc_dev *dev, struct rc_map *rc_map, u64 scancode, bool resize) { - unsigned int i; + unsigned int i, lo, hi; + + lockdep_assert_held(&rc_map->lock); /* * Unfortunately, some hardware-based IR decoders don't provide @@ -384,20 +406,26 @@ static unsigned int ir_establish_scancode(struct rc_dev *dev, if (dev->scancode_mask) scancode &= dev->scancode_mask; - /* First check if we already have a mapping for this ir command */ - for (i = 0; i < rc_map->len; i++) { + /* + * Binary search for an existing mapping for this ir command. + */ + lo = 0; + hi = rc_map->len; + while (lo < hi) { + i = lo + (hi - lo) / 2; if (rc_map->scan[i].scancode == scancode) return i; - - /* Keytable is sorted from lowest to highest scancode */ - if (rc_map->scan[i].scancode >= scancode) - break; + if (rc_map->scan[i].scancode < scancode) + lo = i + 1; + else + hi = i; } + i = lo; /* No previous mapping found, we might need to grow the table */ if (rc_map->size == rc_map->len) { if (!resize || ir_resize_table(dev, rc_map, GFP_ATOMIC)) - return -1U; + return UINT_MAX; } /* i is the proper index to insert our new keycode */ @@ -479,16 +507,18 @@ static int ir_setkeytable(struct rc_dev *dev, const struct rc_map *from) if (rc) return rc; - for (i = 0; i < from->size; i++) { - index = ir_establish_scancode(dev, rc_map, - from->scan[i].scancode, false); - if (index >= rc_map->len) { - rc = -ENOMEM; - break; - } + scoped_guard(spinlock_irqsave, &rc_map->lock) { + for (i = 0; i < from->size; i++) { + index = ir_establish_scancode(dev, rc_map, + from->scan[i].scancode, false); + if (index >= rc_map->len) { + rc = -ENOMEM; + break; + } - ir_update_mapping(dev, rc_map, index, - from->scan[i].keycode); + ir_update_mapping(dev, rc_map, index, + from->scan[i].keycode); + } } if (rc) @@ -524,6 +554,8 @@ static unsigned int ir_lookup_by_scancode(const struct rc_map *rc_map, { struct rc_map_table *res; + lockdep_assert_held(&rc_map->lock); + res = bsearch(&scancode, rc_map->scan, rc_map->len, sizeof(struct rc_map_table), rc_map_cmp); if (!res) @@ -1701,14 +1733,24 @@ static const struct device_type rc_dev_type = { struct rc_dev *rc_allocate_device(enum rc_driver_type type) { struct rc_dev *dev; + int ret; dev = kzalloc_obj(*dev); if (!dev) return NULL; + if (type == RC_DRIVER_IR_RAW) { + ret = ir_raw_event_prepare(dev); + if (ret < 0) { + kfree(dev); + return NULL; + } + } + if (type != RC_DRIVER_IR_RAW_TX) { dev->input_dev = input_allocate_device(); if (!dev->input_dev) { + ir_raw_event_free(dev); kfree(dev); return NULL; } @@ -1724,6 +1766,7 @@ struct rc_dev *rc_allocate_device(enum rc_driver_type type) spin_lock_init(&dev->rc_map.lock); spin_lock_init(&dev->keylock); } + mutex_init(&dev->lock); dev->dev.type = &rc_dev_type; @@ -1742,7 +1785,12 @@ void rc_free_device(struct rc_dev *dev) if (!dev) return; - input_free_device(dev->input_dev); + if (dev->input_dev) { + timer_delete_sync(&dev->timer_keyup); + timer_delete_sync(&dev->timer_repeat); + } + + input_put_device(dev->input_dev); put_device(&dev->dev); @@ -1854,6 +1902,8 @@ static int rc_setup_rx_device(struct rc_dev *dev) if (rc) return rc; + input_get_device(dev->input_dev); + /* * Default delay of 250ms is too short for some protocols, especially * since the timeout is currently set to 250ms. Increase it to 500ms, @@ -1880,10 +1930,8 @@ static void rc_free_rx_device(struct rc_dev *dev) if (!dev) return; - if (dev->input_dev) { + if (dev->input_dev) input_unregister_device(dev->input_dev); - dev->input_dev = NULL; - } ir_free_table(&dev->rc_map); } @@ -1917,19 +1965,14 @@ int rc_register_device(struct rc_dev *dev) dev->sysfs_groups[attr++] = &rc_dev_wakeup_filter_attr_grp; dev->sysfs_groups[attr++] = NULL; - if (dev->driver_type == RC_DRIVER_IR_RAW) { - rc = ir_raw_event_prepare(dev); - if (rc < 0) - goto out_minor; - } - if (dev->driver_type != RC_DRIVER_IR_RAW_TX) { rc = rc_prepare_rx_device(dev); if (rc) goto out_raw; } - dev->registered = true; + scoped_guard(mutex, &dev->lock) + dev->registered = true; rc = device_add(&dev->dev); if (rc) @@ -1949,7 +1992,7 @@ int rc_register_device(struct rc_dev *dev) if (dev->allowed_protocols != RC_PROTO_BIT_CEC) { rc = lirc_register(dev); if (rc < 0) - goto out_dev; + goto out_lirc; } if (dev->driver_type != RC_DRIVER_IR_RAW_TX) { @@ -1972,15 +2015,20 @@ int rc_register_device(struct rc_dev *dev) out_rx: rc_free_rx_device(dev); out_lirc: - if (dev->allowed_protocols != RC_PROTO_BIT_CEC) - lirc_unregister(dev); -out_dev: + scoped_guard(mutex, &dev->lock) + dev->registered = false; + + lirc_unregister(dev); device_del(&dev->dev); + /* registered already cleared above */ + goto out_free_table; out_rx_free: - ir_free_table(&dev->rc_map); + scoped_guard(mutex, &dev->lock) + dev->registered = false; +out_free_table: + if (dev->driver_type != RC_DRIVER_IR_RAW_TX) + ir_free_table(&dev->rc_map); out_raw: - ir_raw_event_free(dev); -out_minor: ida_free(&rc_ida, minor); return rc; } @@ -2018,18 +2066,18 @@ void rc_unregister_device(struct rc_dev *dev) if (!dev) return; + mutex_lock(&dev->lock); + dev->registered = false; + if (dev->users && dev->close) + dev->close(dev); + mutex_unlock(&dev->lock); + if (dev->driver_type == RC_DRIVER_IR_RAW) ir_raw_event_unregister(dev); timer_delete_sync(&dev->timer_keyup); timer_delete_sync(&dev->timer_repeat); - mutex_lock(&dev->lock); - if (dev->users && dev->close) - dev->close(dev); - dev->registered = false; - mutex_unlock(&dev->lock); - rc_free_rx_device(dev); /* diff --git a/drivers/media/rc/redrat3.c b/drivers/media/rc/redrat3.c index 3f828a564e19..3e82b5e66477 100644 --- a/drivers/media/rc/redrat3.c +++ b/drivers/media/rc/redrat3.c @@ -358,8 +358,24 @@ static void redrat3_process_ir_data(struct redrat3_dev *rr3) /* process each rr3 encoded byte into an int */ sig_size = be16_to_cpu(rr3->irdata.sig_size); + + /* + * Note we are not checking if we are reading beyond the end of the + * packet which was sent, and reading stale data. If the device + * sends a packet which is short then we get garbage IR, but no + * out of bounds read. + */ + if (sig_size > RR3_MAX_SIG_SIZE) { + dev_err(dev, "length %u is incorrect\n", sig_size); + return; + } + for (i = 0; i < sig_size; i++) { offset = rr3->irdata.sigdata[i]; + if (offset >= RR3_DRIVER_MAXLENS) { + dev_err(dev, "offset %u is incorrect\n", offset); + return; + } val = get_unaligned_be16(&rr3->irdata.lens[offset]); /* we should always get pulse/space/pulse/space samples */ @@ -757,17 +773,9 @@ static int redrat3_transmit_ir(struct rc_dev *rcdev, unsigned *txbuf, u8 curlencheck = 0; unsigned i, sendbuf_len; - if (rr3->transmitting) { - dev_warn(dev, "%s: transmitter already in use\n", __func__); - return -EAGAIN; - } - if (count > RR3_MAX_SIG_SIZE - RR3_TX_TRAILER_LEN) return -EINVAL; - /* rr3 will disable rc detector on transmit */ - rr3->transmitting = true; - sample_lens = kzalloc_objs(*sample_lens, RR3_DRIVER_MAXLENS); if (!sample_lens) return -ENOMEM; @@ -778,6 +786,9 @@ static int redrat3_transmit_ir(struct rc_dev *rcdev, unsigned *txbuf, goto out; } + /* rr3 will disable rc detector on transmit */ + rr3->transmitting = true; + for (i = 0; i < count; i++) { cur_sample_len = redrat3_us_to_len(txbuf[i]); if (cur_sample_len > 0xffff) { @@ -975,6 +986,7 @@ static int redrat3_dev_probe(struct usb_interface *intf, struct device *dev = &intf->dev; struct usb_host_interface *uhi; struct redrat3_dev *rr3; + struct rc_dev *rc; struct usb_endpoint_descriptor *ep; struct usb_endpoint_descriptor *ep_narrow = NULL; struct usb_endpoint_descriptor *ep_wide = NULL; @@ -1119,9 +1131,12 @@ static int redrat3_dev_probe(struct usb_interface *intf, return 0; led_free: + rc_unregister_device(rr3->rc); led_classdev_unregister(&rr3->led); redrat_free: + rc = rr3->rc; redrat3_delete(rr3, rr3->udev); + rc_free_device(rc); no_endpoints: return retval; @@ -1148,6 +1163,7 @@ static int redrat3_dev_suspend(struct usb_interface *intf, pm_message_t message) usb_kill_urb(rr3->narrow_urb); usb_kill_urb(rr3->wide_urb); usb_kill_urb(rr3->flash_urb); + usb_kill_urb(rr3->learn_urb); return 0; } diff --git a/drivers/media/rc/serial_ir.c b/drivers/media/rc/serial_ir.c index 992fff82b524..f6702b3df9a0 100644 --- a/drivers/media/rc/serial_ir.c +++ b/drivers/media/rc/serial_ir.c @@ -798,7 +798,7 @@ static int __init serial_ir_init_module(void) static void __exit serial_ir_exit_module(void) { - timer_delete_sync(&serial_ir.timeout_timer); + timer_shutdown_sync(&serial_ir.timeout_timer); serial_ir_exit(); } diff --git a/drivers/media/rc/streamzap.c b/drivers/media/rc/streamzap.c index 307985d74fe8..41195ad82734 100644 --- a/drivers/media/rc/streamzap.c +++ b/drivers/media/rc/streamzap.c @@ -365,6 +365,7 @@ static int streamzap_probe(struct usb_interface *intf, return 0; rc_submit_fail: + rc_unregister_device(sz->rdev); rc_free_device(sz->rdev); usb_set_intfdata(intf, NULL); rc_dev_fail: diff --git a/drivers/media/rc/sunxi-cir.c b/drivers/media/rc/sunxi-cir.c index 28e840a7e5b8..af1ee08ffdbe 100644 --- a/drivers/media/rc/sunxi-cir.c +++ b/drivers/media/rc/sunxi-cir.c @@ -374,8 +374,8 @@ static void sunxi_ir_remove(struct platform_device *pdev) struct sunxi_ir *ir = platform_get_drvdata(pdev); rc_unregister_device(ir->rc); - rc_free_device(ir->rc); sunxi_ir_hw_exit(&pdev->dev); + rc_free_device(ir->rc); } static void sunxi_ir_shutdown(struct platform_device *pdev) diff --git a/drivers/media/spi/Kconfig b/drivers/media/spi/Kconfig index 4656afae5bb4..da3e225d420d 100644 --- a/drivers/media/spi/Kconfig +++ b/drivers/media/spi/Kconfig @@ -1,15 +1,11 @@ # SPDX-License-Identifier: GPL-2.0-only if VIDEO_DEV && SPI -comment "SPI I2C drivers auto-selected by 'Autoselect ancillary drivers'" - depends on MEDIA_HIDE_ANCILLARY_SUBDRV && SPI - menu "Media SPI Adapters" config CXD2880_SPI_DRV tristate "Sony CXD2880 SPI support" depends on DVB_CORE && SPI - default m if !MEDIA_SUBDRV_AUTOSELECT help Choose if you would like to have SPI interface support for Sony CXD2880. diff --git a/drivers/media/test-drivers/vicodec/codec-fwht.h b/drivers/media/test-drivers/vicodec/codec-fwht.h index 0eab24020e9e..4b4d39031089 100644 --- a/drivers/media/test-drivers/vicodec/codec-fwht.h +++ b/drivers/media/test-drivers/vicodec/codec-fwht.h @@ -61,7 +61,7 @@ * both luma and chroma components resolutions are rounded up to * a multiple of 8 */ -#define vic_round_dim(dim, div) (round_up((dim) / (div), 8) * (div)) +#define vic_round_dim(dim, div) round_up(dim, 8 * (div)) struct fwht_cframe_hdr { u32 magic1; diff --git a/drivers/media/test-drivers/vicodec/vicodec-core.c b/drivers/media/test-drivers/vicodec/vicodec-core.c index ff9d50fb05fd..7ea024b14f0e 100644 --- a/drivers/media/test-drivers/vicodec/vicodec-core.c +++ b/drivers/media/test-drivers/vicodec/vicodec-core.c @@ -890,6 +890,8 @@ static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct vicodec_ctx *ctx = file2ctx(file); + struct vicodec_q_data *q_data_out = + get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); struct v4l2_pix_format_mplane *pix_mp; struct v4l2_pix_format *pix; @@ -900,6 +902,10 @@ static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, pix = &f->fmt.pix; pix->pixelformat = ctx->is_enc ? V4L2_PIX_FMT_FWHT : find_fmt(f->fmt.pix.pixelformat)->id; + if (ctx->is_enc) { + pix->width = q_data_out->coded_width; + pix->height = q_data_out->coded_height; + } pix->colorspace = ctx->state.colorspace; pix->xfer_func = ctx->state.xfer_func; pix->ycbcr_enc = ctx->state.ycbcr_enc; @@ -911,6 +917,10 @@ static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, pix_mp = &f->fmt.pix_mp; pix_mp->pixelformat = ctx->is_enc ? V4L2_PIX_FMT_FWHT : find_fmt(pix_mp->pixelformat)->id; + if (ctx->is_enc) { + pix_mp->width = q_data_out->coded_width; + pix_mp->height = q_data_out->coded_height; + } pix_mp->colorspace = ctx->state.colorspace; pix_mp->xfer_func = ctx->state.xfer_func; pix_mp->ycbcr_enc = ctx->state.ycbcr_enc; @@ -1595,9 +1605,9 @@ static int vicodec_start_streaming(struct vb2_queue *q, } state->ref_stride = q_data->coded_width * info->luma_alpha_step; - state->ref_frame.buf = kvmalloc(total_planes_size, GFP_KERNEL); + state->ref_frame.buf = kvzalloc(total_planes_size, GFP_KERNEL); state->ref_frame.luma = state->ref_frame.buf; - new_comp_frame = kvmalloc(ctx->comp_max_size, GFP_KERNEL); + new_comp_frame = kvzalloc(ctx->comp_max_size, GFP_KERNEL); if (!state->ref_frame.luma || !new_comp_frame) { kvfree(state->ref_frame.luma); diff --git a/drivers/media/test-drivers/vidtv/vidtv_bridge.c b/drivers/media/test-drivers/vidtv/vidtv_bridge.c index fd69b4ee16f4..4c98dfbd406f 100644 --- a/drivers/media/test-drivers/vidtv/vidtv_bridge.c +++ b/drivers/media/test-drivers/vidtv/vidtv_bridge.c @@ -44,6 +44,10 @@ #define LNB_LOW_FREQ 9750000 /* low IF frequency */ #define LNB_HIGH_FREQ 10600000 /* transition frequency */ +/* The Ku-band range covered by such an LNBf, in kHz */ +#define LNB_KU_BAND_MIN_FREQ 10700000 +#define LNB_KU_BAND_MAX_FREQ 12750000 + static unsigned int drop_tslock_prob_on_low_snr; module_param(drop_tslock_prob_on_low_snr, uint, 0444); MODULE_PARM_DESC(drop_tslock_prob_on_low_snr, @@ -367,6 +371,36 @@ static int vidtv_bridge_probe_demod(struct vidtv_dvb *dvb, u32 n) return 0; } +/* + * Reject frequencies the simulation could never tune into, as the module + * would otherwise load just fine and then never lock on anything, leaving + * no clue about what went wrong. + */ +static int vidtv_bridge_check_freqs(struct vidtv_dvb *dvb, + const unsigned int *freqs, + u32 array_sz, + u32 min_freq, + u32 max_freq, + const char *name) +{ + u32 i; + + for (i = 0; i < array_sz; i++) { + /* a zeroed entry means an unused slot */ + if (!freqs[i]) + continue; + + if (freqs[i] < min_freq || freqs[i] > max_freq) { + dev_err(&dvb->pdev->dev, + "%s[%u]: %u is out of range (%u..%u)\n", + name, i, freqs[i], min_freq, max_freq); + return -EINVAL; + } + } + + return 0; +} + static int vidtv_bridge_probe_tuner(struct vidtv_dvb *dvb, u32 n) { struct vidtv_tuner_config cfg = { @@ -374,10 +408,45 @@ static int vidtv_bridge_probe_tuner(struct vidtv_dvb *dvb, u32 n) .mock_power_up_delay_msec = mock_power_up_delay_msec, .mock_tune_delay_msec = mock_tune_delay_msec, }; + u32 min_freq = dvb->fe[n]->ops.info.frequency_min_hz; + u32 max_freq = dvb->fe[n]->ops.info.frequency_max_hz; u32 freq; + int ret; int i; - /* TODO: check if the frequencies are at a valid range */ + /* + * Terrestrial and cable frequencies are given in Hz and are used as + * is, so they have to fit within the range the demod reports to the + * DVB core: the core rejects a tuning request outside of it before + * the tuner is ever asked about the frequency. + */ + ret = vidtv_bridge_check_freqs(dvb, vidtv_valid_dvb_t_freqs, + ARRAY_SIZE(vidtv_valid_dvb_t_freqs), + min_freq, max_freq, + "vidtv_valid_dvb_t_freqs"); + if (ret) + return ret; + + ret = vidtv_bridge_check_freqs(dvb, vidtv_valid_dvb_c_freqs, + ARRAY_SIZE(vidtv_valid_dvb_c_freqs), + min_freq, max_freq, + "vidtv_valid_dvb_c_freqs"); + if (ret) + return ret; + + /* + * Satellite frequencies are given in kHz at Ku-band and are + * downconverted below, so check them against the band the simulated + * LNBf covers instead. Doing so also ensures that the frequencies + * are above the LNBf local oscillators. + */ + ret = vidtv_bridge_check_freqs(dvb, vidtv_valid_dvb_s_freqs, + ARRAY_SIZE(vidtv_valid_dvb_s_freqs), + LNB_KU_BAND_MIN_FREQ, + LNB_KU_BAND_MAX_FREQ, + "vidtv_valid_dvb_s_freqs"); + if (ret) + return ret; memcpy(cfg.vidtv_valid_dvb_t_freqs, vidtv_valid_dvb_t_freqs, @@ -474,6 +543,7 @@ fail_dmx: fail_demod_probe: for (i = i - 1; i >= 0; --i) { dvb_unregister_frontend(dvb->fe[i]); + dvb_frontend_detach(dvb->fe[i]); fail_fe: dvb_module_release(dvb->i2c_client_tuner[i]); fail_tuner_probe: @@ -550,8 +620,11 @@ static void vidtv_bridge_remove(struct platform_device *pdev) mutex_destroy(&dvb->feed_lock); + vidtv_stop_streaming(dvb); + for (i = 0; i < NUM_FE; ++i) { dvb_unregister_frontend(dvb->fe[i]); + dvb_frontend_detach(dvb->fe[i]); dvb_module_release(dvb->i2c_client_tuner[i]); dvb_module_release(dvb->i2c_client_demod[i]); } diff --git a/drivers/media/test-drivers/vidtv/vidtv_demod.c b/drivers/media/test-drivers/vidtv/vidtv_demod.c index 6e5fe402976b..3aa586004638 100644 --- a/drivers/media/test-drivers/vidtv/vidtv_demod.c +++ b/drivers/media/test-drivers/vidtv/vidtv_demod.c @@ -343,13 +343,6 @@ static int vidtv_diseqc_send_burst(struct dvb_frontend *fe, return 0; } -static void vidtv_demod_release(struct dvb_frontend *fe) -{ - struct vidtv_demod_state *state = fe->demodulator_priv; - - kfree(state); -} - static const struct dvb_frontend_ops vidtv_demod_ops = { .delsys = { SYS_DVBT, @@ -390,8 +383,6 @@ static const struct dvb_frontend_ops vidtv_demod_ops = { FE_CAN_HIERARCHY_AUTO, }, - .release = vidtv_demod_release, - .set_frontend = vidtv_demod_set_frontend, .get_frontend = vidtv_demod_get_frontend, diff --git a/drivers/media/test-drivers/vim2m.c b/drivers/media/test-drivers/vim2m.c index bb2dd11eef0e..459fd4aedf30 100644 --- a/drivers/media/test-drivers/vim2m.c +++ b/drivers/media/test-drivers/vim2m.c @@ -150,8 +150,8 @@ enum { V4L2_M2M_DST = 1, }; -#define V4L2_CID_TRANS_TIME_MSEC (V4L2_CID_USER_BASE + 0x1000) -#define V4L2_CID_TRANS_NUM_BUFS (V4L2_CID_USER_BASE + 0x1001) +#define V4L2_CID_TRANS_TIME_MSEC (V4L2_CID_USER_VIM2M_BASE + 0) +#define V4L2_CID_TRANS_NUM_BUFS (V4L2_CID_USER_VIM2M_BASE + 1) static struct vim2m_fmt *find_format(u32 fourcc) { @@ -205,6 +205,7 @@ struct vim2m_ctx { struct vim2m_dev *dev; struct v4l2_ctrl_handler hdl; + struct v4l2_ctrl *trans_num_bufs_ctrl; /* Processed buffers in this transaction */ u8 num_processed; @@ -1258,9 +1259,27 @@ static int vim2m_start_streaming(struct vb2_queue *q, unsigned int count) ctx->aborting = 0; q_data->sequence = 0; + v4l2_ctrl_grab(ctx->trans_num_bufs_ctrl, true); + return 0; } +static bool vim2m_other_queue_is_streaming(struct vim2m_ctx *ctx, + struct vb2_queue *q) +{ + struct vb2_queue *other_vq; + + if (!ctx->fh.m2m_ctx) + return false; + + if (V4L2_TYPE_IS_OUTPUT(q->type)) + other_vq = v4l2_m2m_get_dst_vq(ctx->fh.m2m_ctx); + else + other_vq = v4l2_m2m_get_src_vq(ctx->fh.m2m_ctx); + + return vb2_is_streaming(other_vq); +} + static void vim2m_stop_streaming(struct vb2_queue *q) { struct vim2m_ctx *ctx = vb2_get_drv_priv(q); @@ -1274,11 +1293,14 @@ static void vim2m_stop_streaming(struct vb2_queue *q) else vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); if (!vbuf) - return; + break; v4l2_ctrl_request_complete(vbuf->vb2_buf.req_obj.req, &ctx->hdl); v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); } + + if (!vim2m_other_queue_is_streaming(ctx, q)) + v4l2_ctrl_grab(ctx->trans_num_bufs_ctrl, false); } static void vim2m_buf_request_complete(struct vb2_buffer *vb) @@ -1380,7 +1402,8 @@ static int vim2m_open(struct file *file) vim2m_ctrl_trans_time_msec.def = default_transtime; v4l2_ctrl_new_custom(hdl, &vim2m_ctrl_trans_time_msec, NULL); - v4l2_ctrl_new_custom(hdl, &vim2m_ctrl_trans_num_bufs, NULL); + ctx->trans_num_bufs_ctrl = + v4l2_ctrl_new_custom(hdl, &vim2m_ctrl_trans_num_bufs, NULL); if (hdl->error) { rc = hdl->error; v4l2_ctrl_handler_free(hdl); @@ -1435,10 +1458,10 @@ static int vim2m_release(struct file *file) v4l2_fh_del(&ctx->fh, file); v4l2_fh_exit(&ctx->fh); - v4l2_ctrl_handler_free(&ctx->hdl); mutex_lock(&dev->dev_mutex); v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); mutex_unlock(&dev->dev_mutex); + v4l2_ctrl_handler_free(&ctx->hdl); kfree(ctx); atomic_dec(&dev->num_inst); diff --git a/drivers/media/test-drivers/vimc/vimc-debayer.c b/drivers/media/test-drivers/vimc/vimc-debayer.c index 0c2e715a8a16..cfda447a5b76 100644 --- a/drivers/media/test-drivers/vimc/vimc-debayer.c +++ b/drivers/media/test-drivers/vimc/vimc-debayer.c @@ -240,6 +240,7 @@ static void vimc_debayer_adjust_sink_fmt(struct v4l2_mbus_framefmt *fmt) } static int vimc_debayer_set_fmt(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/test-drivers/vimc/vimc-scaler.c b/drivers/media/test-drivers/vimc/vimc-scaler.c index e2037c67e423..d8dc65d33f62 100644 --- a/drivers/media/test-drivers/vimc/vimc-scaler.c +++ b/drivers/media/test-drivers/vimc/vimc-scaler.c @@ -140,6 +140,7 @@ static int vimc_scaler_enum_frame_size(struct v4l2_subdev *sd, } static int vimc_scaler_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -204,6 +205,7 @@ static int vimc_scaler_set_fmt(struct v4l2_subdev *sd, } static int vimc_scaler_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -239,6 +241,7 @@ static void vimc_scaler_adjust_sink_crop(struct v4l2_rect *r, } static int vimc_scaler_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/test-drivers/vimc/vimc-sensor.c b/drivers/media/test-drivers/vimc/vimc-sensor.c index 83dcc9d61ee0..f07f4237f6ad 100644 --- a/drivers/media/test-drivers/vimc/vimc-sensor.c +++ b/drivers/media/test-drivers/vimc/vimc-sensor.c @@ -97,18 +97,14 @@ static void vimc_sensor_update_frame_timing(struct v4l2_subdev *sd, { struct vimc_sensor_device *vsensor = container_of(sd, struct vimc_sensor_device, sd); - u64 pixel_rate = vsensor->pixel_rate->val; + u32 pixel_rate = vsensor->pixel_rate->val; u32 hts = width + vsensor->hblank->val; u32 vts = height + vsensor->vblank->val; u64 total_pixels = (u64)hts * vts; u64 frame_interval_ns; - /* Sanity check, pixel rate is fixed and fits in 32 bits. */ - if (WARN_ON(pixel_rate >= 0x100000000)) - return; - frame_interval_ns = total_pixels * NSEC_PER_SEC; - do_div(frame_interval_ns, (u32)pixel_rate); + do_div(frame_interval_ns, pixel_rate); vsensor->hw.fps_jiffies = nsecs_to_jiffies(frame_interval_ns); if (vsensor->hw.fps_jiffies == 0) vsensor->hw.fps_jiffies = 1; @@ -154,6 +150,7 @@ static u32 vimc_calc_vblank(u32 width, u32 height, } static int vimc_sensor_set_fmt(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/test-drivers/vivid/vivid-cec.c b/drivers/media/test-drivers/vivid/vivid-cec.c index 2d15fdd5d999..8d301578e2eb 100644 --- a/drivers/media/test-drivers/vivid/vivid-cec.c +++ b/drivers/media/test-drivers/vivid/vivid-cec.c @@ -23,7 +23,7 @@ struct xfer_on_bus { static bool find_dest_adap(struct vivid_dev *dev, struct cec_adapter *adap, u8 dest) { - unsigned int i, j; + unsigned int i; if (dest >= 0xf) return false; @@ -33,13 +33,12 @@ static bool find_dest_adap(struct vivid_dev *dev, cec_has_log_addr(dev->cec_rx_adap, dest)) return true; - for (i = 0, j = 0; i < dev->num_inputs; i++) { + for (i = 0; i < dev->num_inputs; i++) { unsigned int menu_idx = dev->input_is_connected_to_output[i]; if (dev->input_type[i] != HDMI) continue; - j++; if (menu_idx < FIXED_MENU_ITEMS) continue; @@ -113,7 +112,7 @@ static void adjust_sfts(struct vivid_dev *dev) int vivid_cec_bus_thread(void *_dev) { u32 last_sft; - unsigned int i, j; + unsigned int i; unsigned int dest; ktime_t start, end; s64 delta_us, retry_us; @@ -210,13 +209,12 @@ int vivid_cec_bus_thread(void *_dev) if (first_status == CEC_TX_STATUS_OK) { if (xfers_on_bus[first_idx].adap != dev->cec_rx_adap) cec_received_msg(dev->cec_rx_adap, &first_msg); - for (i = 0, j = 0; i < dev->num_inputs; i++) { + for (i = 0; i < dev->num_inputs; i++) { unsigned int menu_idx = dev->input_is_connected_to_output[i]; if (dev->input_type[i] != HDMI) continue; - j++; if (menu_idx < FIXED_MENU_ITEMS) continue; diff --git a/drivers/media/test-drivers/vivid/vivid-core.c b/drivers/media/test-drivers/vivid/vivid-core.c index 62cfb5feb2cf..c042d92db175 100644 --- a/drivers/media/test-drivers/vivid/vivid-core.c +++ b/drivers/media/test-drivers/vivid/vivid-core.c @@ -854,6 +854,7 @@ static void vivid_dev_release(struct v4l2_device *v4l2_dev) struct vivid_dev *dev = container_of(v4l2_dev, struct vivid_dev, v4l2_dev); cancel_work_sync(&dev->update_hdmi_ctrl_work); + cancel_work_sync(&dev->update_svid_ctrl_work); vivid_free_controls(dev); v4l2_device_unregister(&dev->v4l2_dev); #ifdef CONFIG_MEDIA_CONTROLLER diff --git a/drivers/media/test-drivers/vivid/vivid-vid-cap.c b/drivers/media/test-drivers/vivid/vivid-vid-cap.c index e20449084709..147af0f9b077 100644 --- a/drivers/media/test-drivers/vivid/vivid-vid-cap.c +++ b/drivers/media/test-drivers/vivid/vivid-vid-cap.c @@ -570,6 +570,7 @@ int vivid_try_fmt_vid_cap(struct file *file, void *priv, const struct vivid_fmt *fmt; unsigned bytesperline, max_bpl; unsigned factor = 1; + unsigned int vdiv = 1; unsigned w, h; unsigned p; bool user_set_csc = !!(mp->flags & V4L2_PIX_FMT_FLAG_SET_CSC); @@ -622,6 +623,18 @@ int vivid_try_fmt_vid_cap(struct file *file, void *priv, mp->height = r.height / factor; } + /* + * The chroma planes of vertically subsampled formats hold + * height / vdownsampling lines. If the height is not a multiple of + * the subsampling factor, then the buffer size calculations round + * that number down while the test pattern generator rounds it up, + * so the generator writes one line past the end of the buffer. + * Round the height down to keep both in sync. + */ + for (p = 0; p < fmt->planes; p++) + vdiv = max(vdiv, fmt->vdownsampling[p]); + mp->height = rounddown(mp->height, vdiv); + /* This driver supports custom bytesperline values */ mp->num_planes = fmt->buffers; diff --git a/drivers/media/tuners/tda18250.c b/drivers/media/tuners/tda18250.c index bd7ed50fb6e8..e4b4106e91b6 100644 --- a/drivers/media/tuners/tda18250.c +++ b/drivers/media/tuners/tda18250.c @@ -804,8 +804,7 @@ static int tda18250_probe(struct i2c_client *client) /* read the three chip ID registers */ regmap_bulk_read(dev->regmap, R00_ID1, &chip_id, 3); - dev_dbg(&client->dev, "chip_id=%02x:%02x:%02x", - chip_id[0], chip_id[1], chip_id[2]); + dev_dbg(&client->dev, "chip_id=%3phC", chip_id); switch (chip_id[0]) { case 0xc7: diff --git a/drivers/media/usb/au0828/au0828-core.c b/drivers/media/usb/au0828/au0828-core.c index 445cbeb7abae..c3826b3b8a2e 100644 --- a/drivers/media/usb/au0828/au0828-core.c +++ b/drivers/media/usb/au0828/au0828-core.c @@ -697,6 +697,10 @@ static int au0828_usb_probe(struct usb_interface *interface, retval = au0828_v4l2_device_register(interface, dev); if (retval) { au0828_usb_v4l2_media_release(dev); +#ifdef CONFIG_MEDIA_CONTROLLER + media_device_delete(dev->media_dev, KBUILD_MODNAME, + THIS_MODULE); +#endif mutex_unlock(&dev->lock); kfree(dev); return retval; diff --git a/drivers/media/usb/au0828/au0828-dvb.c b/drivers/media/usb/au0828/au0828-dvb.c index 9c95b7ceaecd..601a12e122f1 100644 --- a/drivers/media/usb/au0828/au0828-dvb.c +++ b/drivers/media/usb/au0828/au0828-dvb.c @@ -163,9 +163,6 @@ static int stop_urb_transfer(struct au0828_dev *dev) dprintk(2, "%s()\n", __func__); - if (!dev->urb_streaming) - return 0; - if (dev->bulk_timeout_running == 1) { dev->bulk_timeout_running = 0; timer_delete(&dev->bulk_timeout); @@ -179,6 +176,7 @@ static int stop_urb_transfer(struct au0828_dev *dev) kfree(dev->urbs[i]->transfer_buffer); usb_free_urb(dev->urbs[i]); + dev->urbs[i] = NULL; } } @@ -200,8 +198,10 @@ static int start_urb_transfer(struct au0828_dev *dev) for (i = 0; i < URB_COUNT; i++) { dev->urbs[i] = usb_alloc_urb(0, GFP_KERNEL); - if (!dev->urbs[i]) - return -ENOMEM; + if (!dev->urbs[i]) { + ret = -ENOMEM; + goto err; + } purb = dev->urbs[i]; @@ -217,7 +217,7 @@ static int start_urb_transfer(struct au0828_dev *dev) ret = -ENOMEM; pr_err("%s: failed big buffer allocation, err = %d\n", __func__, ret); - return ret; + goto err; } purb->status = -EINPROGRESS; @@ -235,10 +235,9 @@ static int start_urb_transfer(struct au0828_dev *dev) for (i = 0; i < URB_COUNT; i++) { ret = usb_submit_urb(dev->urbs[i], GFP_ATOMIC); if (ret != 0) { - stop_urb_transfer(dev); pr_err("%s: failed urb submission, err = %d\n", __func__, ret); - return ret; + goto err; } } @@ -249,6 +248,10 @@ static int start_urb_transfer(struct au0828_dev *dev) dev->bulk_timeout_running = 1; return 0; + +err: + stop_urb_transfer(dev); + return ret; } static void au0828_start_transport(struct au0828_dev *dev) @@ -537,6 +540,7 @@ void au0828_dvb_unregister(struct au0828_dev *dev) if (dvb->frontend == NULL) return; + timer_shutdown_sync(&dev->bulk_timeout); cancel_work_sync(&dev->restart_streaming); dvb_net_release(&dvb->net); diff --git a/drivers/media/usb/cx231xx/cx231xx-417.c b/drivers/media/usb/cx231xx/cx231xx-417.c index c695a97e202b..d0955ea18195 100644 --- a/drivers/media/usb/cx231xx/cx231xx-417.c +++ b/drivers/media/usb/cx231xx/cx231xx-417.c @@ -1666,7 +1666,7 @@ static int cx231xx_s_video_encoding(struct cx2341x_handler *cxhdl, u32 val) format.format.width = cxhdl->width / (is_mpeg1 ? 2 : 1); format.format.height = cxhdl->height; format.format.code = MEDIA_BUS_FMT_FIXED; - v4l2_subdev_call(dev->sd_cx25840, pad, set_fmt, NULL, &format); + v4l2_subdev_call(dev->sd_cx25840, pad, set_fmt, NULL, NULL, &format); return 0; } diff --git a/drivers/media/usb/cx231xx/cx231xx-audio.c b/drivers/media/usb/cx231xx/cx231xx-audio.c index b24ceef497e4..af04fb104cc4 100644 --- a/drivers/media/usb/cx231xx/cx231xx-audio.c +++ b/drivers/media/usb/cx231xx/cx231xx-audio.c @@ -581,12 +581,19 @@ static int cx231xx_audio_init(struct cx231xx *dev) dev_dbg(dev->dev, "probing for cx231xx non standard usbaudio\n"); + /* + * Extension init errors are ignored by the cx231xx core, so fini() + * must be safe even if initialization fails part way through. + */ + spin_lock_init(&adev->slock); + INIT_WORK(&dev->wq_trigger, audio_trigger); + atomic_set(&dev->stream_started, 0); + err = snd_card_new(dev->dev, index[devnr], "Cx231xx Audio", THIS_MODULE, 0, &card); if (err < 0) return err; - spin_lock_init(&adev->slock); err = snd_pcm_new(card, "Cx231xx Audio", 0, 0, 1, &pcm); if (err < 0) goto err_free_card; @@ -601,8 +608,6 @@ static int cx231xx_audio_init(struct cx231xx *dev) strscpy(card->shortname, "Cx231xx Audio", sizeof(card->shortname)); strscpy(card->longname, "Conexant cx231xx Audio", sizeof(card->longname)); - INIT_WORK(&dev->wq_trigger, audio_trigger); - err = snd_card_register(card); if (err < 0) goto err_free_card; @@ -656,8 +661,10 @@ static int cx231xx_audio_init(struct cx231xx *dev) err_free_pkt_size: kfree(adev->alt_max_pkt_size); + adev->alt_max_pkt_size = NULL; err_free_card: snd_card_free(card); + adev->sndcard = NULL; return err; } @@ -674,6 +681,8 @@ static int cx231xx_audio_fini(struct cx231xx *dev) return 0; } + disable_work_sync(&dev->wq_trigger); + if (dev->adev.sndcard) { snd_card_free_when_closed(dev->adev.sndcard); kfree(dev->adev.alt_max_pkt_size); diff --git a/drivers/media/usb/cx231xx/cx231xx-cards.c b/drivers/media/usb/cx231xx/cx231xx-cards.c index 69b24205bc56..b0941cd2a2d8 100644 --- a/drivers/media/usb/cx231xx/cx231xx-cards.c +++ b/drivers/media/usb/cx231xx/cx231xx-cards.c @@ -1826,7 +1826,7 @@ static int cx231xx_usb_probe(struct usb_interface *interface, retval = cx231xx_init_v4l2(dev, udev, interface, isoc_pipe); if (retval) - goto err_init; + goto err_video_alt; if (dev->current_pcb_config.ts1_source != 0xff) { /* compute alternate max packet sizes for TS1 */ diff --git a/drivers/media/usb/cx231xx/cx231xx-video.c b/drivers/media/usb/cx231xx/cx231xx-video.c index 70aa99fead27..e8c9c74b4b8a 100644 --- a/drivers/media/usb/cx231xx/cx231xx-video.c +++ b/drivers/media/usb/cx231xx/cx231xx-video.c @@ -909,7 +909,7 @@ static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, dev->format = format_by_fourcc(f->fmt.pix.pixelformat); v4l2_fill_mbus_format(&format.format, &f->fmt.pix, MEDIA_BUS_FMT_FIXED); - call_all(dev, pad, set_fmt, NULL, &format); + call_all(dev, pad, set_fmt, NULL, NULL, &format); v4l2_fill_pix_format(&f->fmt.pix, &format.format); return rc; @@ -950,7 +950,7 @@ static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id norm) format.format.code = MEDIA_BUS_FMT_FIXED; format.format.width = dev->width; format.format.height = dev->height; - call_all(dev, pad, set_fmt, NULL, &format); + call_all(dev, pad, set_fmt, NULL, NULL, &format); /* do mode control overrides */ cx231xx_do_mode_ctrl_overrides(dev); diff --git a/drivers/media/usb/cx231xx/cx231xx.h b/drivers/media/usb/cx231xx/cx231xx.h index 19f5036a78d7..f85d054fbb5c 100644 --- a/drivers/media/usb/cx231xx/cx231xx.h +++ b/drivers/media/usb/cx231xx/cx231xx.h @@ -463,7 +463,7 @@ struct cx231xx_i2c_xfer_data { u8 direction; /* 1 - IN, 0 - OUT */ u8 saddr_len; /* sub address len */ u16 saddr_dat; /* sub addr data */ - u8 buf_size; /* buffer size */ + u16 buf_size; /* buffer size */ u8 *p_buffer; /* pointer to the buffer */ }; diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf-i2c.c b/drivers/media/usb/dvb-usb-v2/mxl111sf-i2c.c index 100a1052dcbc..1a7135b03c33 100644 --- a/drivers/media/usb/dvb-usb-v2/mxl111sf-i2c.c +++ b/drivers/media/usb/dvb-usb-v2/mxl111sf-i2c.c @@ -755,7 +755,7 @@ exit: buf[0] = USB_WRITE_I2C_CMD; buf[1] = 0x00; - /* de-initilize I2C BUS */ + /* de-initialize I2C BUS */ buf[5] = USB_END_I2C_CMD; mxl111sf_i2c_send_data(state, 0, buf); @@ -769,7 +769,7 @@ exit: buf[6] = 0x00; buf[7] = 0x00; - /* de-initilize I2C BUS */ + /* de-initialize I2C BUS */ buf[8] = USB_END_I2C_CMD; mxl111sf_i2c_send_data(state, 0, buf); diff --git a/drivers/media/usb/dvb-usb/cxusb-analog.c b/drivers/media/usb/dvb-usb/cxusb-analog.c index 3bbee1fcbc8d..8c7d0888c721 100644 --- a/drivers/media/usb/dvb-usb/cxusb-analog.c +++ b/drivers/media/usb/dvb-usb/cxusb-analog.c @@ -1031,7 +1031,8 @@ static int cxusb_medion_try_s_fmt_vid_cap(struct file *file, subfmt.format.field = field; subfmt.format.colorspace = V4L2_COLORSPACE_SMPTE170M; - ret = v4l2_subdev_call(cxdev->cx25840, pad, set_fmt, NULL, &subfmt); + ret = v4l2_subdev_call(cxdev->cx25840, pad, set_fmt, NULL, NULL, + &subfmt); if (ret != 0) return ret; @@ -1513,7 +1514,8 @@ int cxusb_medion_analog_init(struct dvb_usb_device *dvbdev) subfmt.format.field = V4L2_FIELD_SEQ_TB; subfmt.format.colorspace = V4L2_COLORSPACE_SMPTE170M; - ret = v4l2_subdev_call(cxdev->cx25840, pad, set_fmt, NULL, &subfmt); + ret = v4l2_subdev_call(cxdev->cx25840, pad, set_fmt, NULL, NULL, + &subfmt); if (ret != 0) dev_warn(&dvbdev->udev->dev, "cx25840 format set failed (%d)\n", ret); diff --git a/drivers/media/usb/dvb-usb/dib0700_core.c b/drivers/media/usb/dvb-usb/dib0700_core.c index 1caabb51ea47..5d2fa7037c00 100644 --- a/drivers/media/usb/dvb-usb/dib0700_core.c +++ b/drivers/media/usb/dvb-usb/dib0700_core.c @@ -560,7 +560,7 @@ int dib0700_download_firmware(struct usb_device *udev, const struct firmware *fw fw_version = (buf[8] << 24) | (buf[9] << 16) | (buf[10] << 8) | buf[11]; /* set the buffer size - DVB-USB is allocating URB buffers - * only after the firwmare download was successful */ + * only after the firmware download was successful */ for (i = 0; i < dib0700_device_count; i++) { for (adap_num = 0; adap_num < dib0700_devices[i].num_adapters; adap_num++) { diff --git a/drivers/media/usb/dvb-usb/dvb-usb-firmware.c b/drivers/media/usb/dvb-usb/dvb-usb-firmware.c index 0fb3fa6100e4..675d9d1d4f47 100644 --- a/drivers/media/usb/dvb-usb/dvb-usb-firmware.c +++ b/drivers/media/usb/dvb-usb/dvb-usb-firmware.c @@ -141,6 +141,8 @@ int dvb_usb_get_hexline(const struct firmware *fw, struct hexline *hx, 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); /* hx->len -= 2; data_offs += 2; */ diff --git a/drivers/media/usb/em28xx/em28xx-camera.c b/drivers/media/usb/em28xx/em28xx-camera.c index b5f58dc6dd0f..bc55ad66658c 100644 --- a/drivers/media/usb/em28xx/em28xx-camera.c +++ b/drivers/media/usb/em28xx/em28xx-camera.c @@ -392,7 +392,7 @@ int em28xx_init_camera(struct em28xx *dev) format.format.code = MEDIA_BUS_FMT_YUYV8_2X8; format.format.width = 640; format.format.height = 480; - v4l2_subdev_call(subdev, pad, set_fmt, NULL, &format); + v4l2_subdev_call(subdev, pad, set_fmt, NULL, NULL, &format); /* NOTE: for UXGA=1600x1200 switch to 12MHz */ dev->board.xclk = EM28XX_XCLK_FREQUENCY_24MHZ; diff --git a/drivers/media/usb/em28xx/em28xx-video.c b/drivers/media/usb/em28xx/em28xx-video.c index 79af154767c1..ba992b2055dc 100644 --- a/drivers/media/usb/em28xx/em28xx-video.c +++ b/drivers/media/usb/em28xx/em28xx-video.c @@ -431,6 +431,7 @@ static void em2828X_decoder_set_std(struct em28xx *dev, v4l2_std_id norm) } else if (INPUT(dev->ctl_input)->vmux == EM2828X_TELEVISION) { em28xx_write_reg(dev, 0x7A00, 0x32); em28xx_write_reg(dev, 0x7A03, 0x09); + em28xx_write_reg(dev, 0x7A07, 0x2f); em28xx_write_reg(dev, 0x7A30, 0x2a); em28xx_write_reg(dev, 0x7A80, 0x03); em28xx_write_reg(dev, 0x7A20, 0x35); @@ -1410,6 +1411,9 @@ static int em28xx_vb2_setup(struct em28xx *dev) if (rc < 0) return rc; + if (!em28xx_vbi_supported(dev)) + return 0; + /* Setup Videobuf2 for VBI capture */ q = &v4l2->vb_vbiq; q->type = V4L2_BUF_TYPE_VBI_CAPTURE; diff --git a/drivers/media/usb/go7007/go7007-driver.c b/drivers/media/usb/go7007/go7007-driver.c index 453ab5c3aa03..7db2440886b0 100644 --- a/drivers/media/usb/go7007/go7007-driver.c +++ b/drivers/media/usb/go7007/go7007-driver.c @@ -306,10 +306,9 @@ int go7007_register_encoder(struct go7007 *go, unsigned num_i2c_devs) if (ret < 0) goto err_free_controls; - if (go->board_info->flags & GO7007_BOARD_HAS_AUDIO) { + if ((go->board_info->flags & GO7007_BOARD_HAS_AUDIO) && + go7007_snd_init(go) == 0) go->audio_enabled = 1; - go7007_snd_init(go); - } return 0; err_free_controls: diff --git a/drivers/media/usb/go7007/go7007-usb.c b/drivers/media/usb/go7007/go7007-usb.c index c0cb92fa6ab9..dc5b8750a110 100644 --- a/drivers/media/usb/go7007/go7007-usb.c +++ b/drivers/media/usb/go7007/go7007-usb.c @@ -1029,10 +1029,16 @@ static const struct i2c_algorithm go7007_usb_algo = { .functionality = go7007_usb_functionality, }; +static const struct i2c_adapter_quirks go7007_usb_quirks = { + .max_write_len = 12, + .max_read_len = 15, +}; + static struct i2c_adapter go7007_usb_adap_templ = { .owner = THIS_MODULE, .name = "WIS GO7007SB EZ-USB", .algo = &go7007_usb_algo, + .quirks = &go7007_usb_quirks, }; /********************* USB add/remove functions *********************/ @@ -1320,6 +1326,8 @@ static int go7007_usb_probe(struct usb_interface *intf, return 0; allocfail: + if (go->i2c_adapter_online) + i2c_del_adapter(&go->i2c_adapter); go7007_usb_release(go); kfree(go); return -ENOMEM; diff --git a/drivers/media/usb/go7007/go7007-v4l2.c b/drivers/media/usb/go7007/go7007-v4l2.c index 2087ffcb85a5..86b853e09ecb 100644 --- a/drivers/media/usb/go7007/go7007-v4l2.c +++ b/drivers/media/usb/go7007/go7007-v4l2.c @@ -252,7 +252,7 @@ static int set_capture_size(struct go7007 *go, struct v4l2_format *fmt, int try) go->encoder_h_halve = 0; go->encoder_v_halve = 0; go->encoder_subsample = 0; - call_all(&go->v4l2_dev, pad, set_fmt, NULL, &format); + call_all(&go->v4l2_dev, pad, set_fmt, NULL, NULL, &format); } else { if (width <= sensor_width / 4) { go->encoder_h_halve = 1; diff --git a/drivers/media/usb/go7007/s2250-board.c b/drivers/media/usb/go7007/s2250-board.c index d11f8e723624..a57ae661c02b 100644 --- a/drivers/media/usb/go7007/s2250-board.c +++ b/drivers/media/usb/go7007/s2250-board.c @@ -410,6 +410,7 @@ static int s2250_s_ctrl(struct v4l2_ctrl *ctrl) } static int s2250_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/usb/gspca/gspca.c b/drivers/media/usb/gspca/gspca.c index 594d73e50b9f..4185c9047241 100644 --- a/drivers/media/usb/gspca/gspca.c +++ b/drivers/media/usb/gspca/gspca.c @@ -1636,9 +1636,8 @@ void gspca_disconnect(struct usb_interface *intf) #endif v4l2_device_disconnect(&gspca_dev->v4l2_dev); - video_unregister_device(&gspca_dev->vdev); - mutex_unlock(&gspca_dev->usb_lock); + vb2_video_unregister_device(&gspca_dev->vdev); /* (this will call gspca_release() immediately or on last close) */ v4l2_device_put(&gspca_dev->v4l2_dev); diff --git a/drivers/media/usb/gspca/ov519.c b/drivers/media/usb/gspca/ov519.c index bffa94e76da5..57219a738c73 100644 --- a/drivers/media/usb/gspca/ov519.c +++ b/drivers/media/usb/gspca/ov519.c @@ -13,7 +13,7 @@ * Copyright (c) 1999-2006 Mark W. McClelland * Support for OV519, OV8610 Copyright (c) 2003 Joerg Heckenbach * Many improvements by Bret Wallach <bwallac1@san.rr.com> - * Color fixes by by Orion Sky Lawlor <olawlor@acm.org> (2/26/2000) + * Color fixes by Orion Sky Lawlor <olawlor@acm.org> (2/26/2000) * OV7620 fixes by Charl P. Botha <cpbotha@ieee.org> * Changes by Claudio Matsuoka <claudio@conectiva.com> * diff --git a/drivers/media/usb/gspca/w996Xcf.c b/drivers/media/usb/gspca/w996Xcf.c index 79baa0c1a031..700c39fe307c 100644 --- a/drivers/media/usb/gspca/w996Xcf.c +++ b/drivers/media/usb/gspca/w996Xcf.c @@ -407,7 +407,7 @@ static void w9968cf_set_crop_window(struct sd *sd) if (sd->sensor == SEN_OV7620) { /* - * Sigh, this is dependend on the clock / framerate changes + * Sigh, this is dependent on the clock / framerate changes * made by the frequency control, sick. * * Note we cannot use v4l2_ctrl_g_ctrl here, as we get called diff --git a/drivers/media/usb/hackrf/hackrf.c b/drivers/media/usb/hackrf/hackrf.c index a15829a60e88..70fd95f3e97d 100644 --- a/drivers/media/usb/hackrf/hackrf.c +++ b/drivers/media/usb/hackrf/hackrf.c @@ -665,7 +665,7 @@ static int hackrf_free_urbs(struct hackrf_dev *dev) static int hackrf_alloc_urbs(struct hackrf_dev *dev, bool rcv) { - int i, j; + int i; unsigned int pipe; usb_complete_t complete; @@ -681,11 +681,8 @@ static int hackrf_alloc_urbs(struct hackrf_dev *dev, bool rcv) for (i = 0; i < MAX_BULK_BUFS; i++) { dev_dbg(dev->dev, "alloc urb=%d\n", i); dev->urb_list[i] = usb_alloc_urb(0, GFP_KERNEL); - if (!dev->urb_list[i]) { - for (j = 0; j < i; j++) - usb_free_urb(dev->urb_list[j]); + if (!dev->urb_list[i]) return -ENOMEM; - } usb_fill_bulk_urb(dev->urb_list[i], dev->udev, pipe, diff --git a/drivers/media/usb/pvrusb2/pvrusb2-hdw.c b/drivers/media/usb/pvrusb2/pvrusb2-hdw.c index 3c270ef00752..c749ef7e490a 100644 --- a/drivers/media/usb/pvrusb2/pvrusb2-hdw.c +++ b/drivers/media/usb/pvrusb2/pvrusb2-hdw.c @@ -2924,7 +2924,7 @@ static void pvr2_subdev_update(struct pvr2_hdw *hdw) format.format.code = MEDIA_BUS_FMT_FIXED; pvr2_trace(PVR2_TRACE_CHIPS, "subdev v4l2 set_size(%dx%d)", format.format.width, format.format.height); - v4l2_device_call_all(&hdw->v4l2_dev, 0, pad, set_fmt, + v4l2_device_call_all(&hdw->v4l2_dev, 0, pad, set_fmt, NULL, NULL, &format); } @@ -3669,7 +3669,9 @@ static int pvr2_send_request_ex(struct pvr2_hdw *hdw, pvr2_trace( PVR2_TRACE_ERROR_LEGS, "Invalid write control endpoint"); - return -EINVAL; + hdw->ctl_write_pend_flag = 0; + status = -EINVAL; + goto done; } status = usb_submit_urb(hdw->ctl_write_urb,GFP_KERNEL); if (status < 0) { @@ -3699,7 +3701,13 @@ status); pvr2_trace( PVR2_TRACE_ERROR_LEGS, "Invalid read control endpoint"); - return -EINVAL; + hdw->ctl_read_pend_flag = 0; + status = -EINVAL; + if (hdw->ctl_write_pend_flag) { + usb_unlink_urb(hdw->ctl_write_urb); + wait_for_completion(&hdw->ctl_done); + } + goto done; } status = usb_submit_urb(hdw->ctl_read_urb,GFP_KERNEL); if (status < 0) { diff --git a/drivers/media/usb/usbtv/usbtv-core.c b/drivers/media/usb/usbtv/usbtv-core.c index 4f10f6613bc4..89c3424e7685 100644 --- a/drivers/media/usb/usbtv/usbtv-core.c +++ b/drivers/media/usb/usbtv/usbtv-core.c @@ -139,8 +139,6 @@ static void usbtv_disconnect(struct usb_interface *intf) usbtv_audio_free(usbtv); usbtv_video_free(usbtv); - usbtv->udev = NULL; - /* the usbtv structure will be deallocated when v4l2 will be done using it */ v4l2_device_put(&usbtv->v4l2_dev); diff --git a/drivers/media/usb/usbtv/usbtv-video.c b/drivers/media/usb/usbtv/usbtv-video.c index 92bc7a2509c3..2a91af8d1e30 100644 --- a/drivers/media/usb/usbtv/usbtv-video.c +++ b/drivers/media/usb/usbtv/usbtv-video.c @@ -966,5 +966,9 @@ void usbtv_video_free(struct usbtv *usbtv) vb2_video_unregister_device(&usbtv->vdev); v4l2_device_disconnect(&usbtv->v4l2_dev); + mutex_lock(&usbtv->v4l2_lock); + usbtv->udev = NULL; + mutex_unlock(&usbtv->v4l2_lock); + v4l2_device_put(&usbtv->v4l2_dev); } diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c index 65db7340ad38..c825820fd481 100644 --- a/drivers/media/v4l2-core/v4l2-common.c +++ b/drivers/media/v4l2-core/v4l2-common.c @@ -333,6 +333,7 @@ const struct v4l2_format_info *v4l2_format_info(u32 format) { .format = V4L2_PIX_FMT_YVU420, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 3, .bpp = { 1, 1, 1, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2 }, { .format = V4L2_PIX_FMT_YUV422P, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 3, .bpp = { 1, 1, 1, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 }, { .format = V4L2_PIX_FMT_GREY, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_Y12, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, /* Tiled YUV formats */ { .format = V4L2_PIX_FMT_NV12_4L4, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 2, .bpp = { 1, 2, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2 }, @@ -537,16 +538,8 @@ int v4l2_fill_pixfmt_mp_aligned(struct v4l2_pix_format_mplane *pixfmt, } EXPORT_SYMBOL_GPL(v4l2_fill_pixfmt_mp_aligned); -int v4l2_fill_pixfmt_mp(struct v4l2_pix_format_mplane *pixfmt, - u32 pixelformat, u32 width, u32 height) -{ - return v4l2_fill_pixfmt_mp_aligned(pixfmt, pixelformat, - width, height, 1); -} -EXPORT_SYMBOL_GPL(v4l2_fill_pixfmt_mp); - -int v4l2_fill_pixfmt(struct v4l2_pix_format *pixfmt, u32 pixelformat, - u32 width, u32 height) +int v4l2_fill_pixfmt_aligned(struct v4l2_pix_format *pixfmt, u32 pixelformat, + u32 width, u32 height, u8 stride_alignment) { const struct v4l2_format_info *info; int i; @@ -562,15 +555,17 @@ int v4l2_fill_pixfmt(struct v4l2_pix_format *pixfmt, u32 pixelformat, pixfmt->width = width; pixfmt->height = height; pixfmt->pixelformat = pixelformat; - pixfmt->bytesperline = v4l2_format_plane_stride(info, 0, width, 1); + pixfmt->bytesperline = v4l2_format_plane_stride(info, 0, width, + stride_alignment); pixfmt->sizeimage = 0; for (i = 0; i < info->comp_planes; i++) pixfmt->sizeimage += - v4l2_format_plane_size(info, i, width, height, 1); + v4l2_format_plane_size(info, i, width, height, + stride_alignment); return 0; } -EXPORT_SYMBOL_GPL(v4l2_fill_pixfmt); +EXPORT_SYMBOL_GPL(v4l2_fill_pixfmt_aligned); #ifdef CONFIG_MEDIA_CONTROLLER static s64 v4l2_get_link_freq_ctrl(struct v4l2_ctrl_handler *handler, diff --git a/drivers/media/v4l2-core/v4l2-ctrls-core.c b/drivers/media/v4l2-core/v4l2-ctrls-core.c index 648b88c868bc..6e751a043a21 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-core.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-core.c @@ -115,6 +115,7 @@ static void std_init_compound(const struct v4l2_ctrl *ctrl, u32 idx, struct v4l2_ctrl_fwht_params *p_fwht_params; struct v4l2_ctrl_h264_scaling_matrix *p_h264_scaling_matrix; struct v4l2_ctrl_av1_sequence *p_av1_sequence; + struct v4l2_ctrl_hevc_sps *p_hevc_sps; void *p = ptr.p + idx * ctrl->elem_size; if (ctrl->p_def.p_const) @@ -188,6 +189,12 @@ static void std_init_compound(const struct v4l2_ctrl *ctrl, u32 idx, */ memset(p_h264_scaling_matrix, 16, sizeof(*p_h264_scaling_matrix)); break; + case V4L2_CTRL_TYPE_HEVC_SPS: + p_hevc_sps = p; + + /* 4:2:0 */ + p_hevc_sps->chroma_format_idc = 1; + break; } } @@ -2554,7 +2561,10 @@ void v4l2_ctrl_cluster(unsigned ncontrols, struct v4l2_ctrl **controls) int i; /* The first control is the master control and it must not be NULL */ - if (WARN_ON(ncontrols == 0 || controls[0] == NULL)) + if (WARN_ON(ncontrols == 0)) + return; + + if (!controls[0]) return; for (i = 0; i < ncontrols; i++) { @@ -2576,8 +2586,13 @@ void v4l2_ctrl_auto_cluster(unsigned ncontrols, struct v4l2_ctrl **controls, u32 flag = 0; int i; + if (WARN_ON(ncontrols <= 1)) + return; + + if (!master) + return; + v4l2_ctrl_cluster(ncontrols, controls); - WARN_ON(ncontrols <= 1); WARN_ON(manual_val < master->minimum || manual_val > master->maximum); WARN_ON(set_volatile && !has_op(master, g_volatile_ctrl)); master->is_auto = true; diff --git a/drivers/media/v4l2-core/v4l2-mc.c b/drivers/media/v4l2-core/v4l2-mc.c index 937d358697e1..5d7fcd67dc42 100644 --- a/drivers/media/v4l2-core/v4l2-mc.c +++ b/drivers/media/v4l2-core/v4l2-mc.c @@ -324,12 +324,10 @@ EXPORT_SYMBOL_GPL(v4l_vb2q_enable_media_source); int v4l2_create_fwnode_links_to_pad(struct v4l2_subdev *src_sd, struct media_pad *sink, u32 flags) { - struct fwnode_handle *endpoint; - if (!(sink->flags & MEDIA_PAD_FL_SINK)) return -EINVAL; - fwnode_graph_for_each_endpoint(src_sd->fwnode, endpoint) { + fwnode_graph_for_each_endpoint_scoped(src_sd->fwnode, endpoint) { struct fwnode_handle *remote_ep; int src_idx, sink_idx, ret; struct media_pad *src; @@ -397,7 +395,6 @@ int v4l2_create_fwnode_links_to_pad(struct v4l2_subdev *src_sd, src_sd->entity.name, src_idx, sink->entity->name, sink_idx, ret); - fwnode_handle_put(endpoint); return ret; } } diff --git a/drivers/media/v4l2-core/v4l2-subdev.c b/drivers/media/v4l2-core/v4l2-subdev.c index e9f81b9be9e2..a07d77e584c3 100644 --- a/drivers/media/v4l2-core/v4l2-subdev.c +++ b/drivers/media/v4l2-core/v4l2-subdev.c @@ -244,20 +244,44 @@ static inline int check_format(struct v4l2_subdev *sd, check_state(sd, state, format->which, format->pad, format->stream); } +#define do_subdev_call(sd, check, o, f, args...) \ + (!(sd)->ops->o->f ? -ENOIOCTLCMD : (check) ? : \ + (sd)->ops->o->f(sd, ##args)) + static int call_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { - return check_format(sd, state, format) ? : - sd->ops->pad->get_fmt(sd, state, format); + return do_subdev_call(sd, check_format(sd, state, format), pad, get_fmt, + state, format); } static int call_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { - return check_format(sd, state, format) ? : - sd->ops->pad->set_fmt(sd, state, format); + int ret; + + if (!sd->ops->pad->set_fmt && !sd->ops->pad->get_fmt) + return -ENOIOCTLCMD; + + ret = check_format(sd, state, format); + if (ret) + return ret; + + if (sd->ops->pad->set_fmt) + return sd->ops->pad->set_fmt(sd, ci, state, format); + + return sd->ops->pad->get_fmt(sd, state, format); +} + +static int check_which_pad_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 which, + u32 pad, u32 stream) +{ + return check_which(which) ? : check_pad(sd, pad) ? : + check_state(sd, state, which, pad, stream); } static int call_enum_mbus_code(struct v4l2_subdev *sd, @@ -267,9 +291,9 @@ static int call_enum_mbus_code(struct v4l2_subdev *sd, if (!code) return -EINVAL; - return check_which(code->which) ? : check_pad(sd, code->pad) ? : - check_state(sd, state, code->which, code->pad, code->stream) ? : - sd->ops->pad->enum_mbus_code(sd, state, code); + return do_subdev_call(sd, check_which_pad_state(sd, state, code->which, + code->pad, code->stream), + pad, enum_mbus_code, state, code); } static int call_enum_frame_size(struct v4l2_subdev *sd, @@ -279,9 +303,9 @@ static int call_enum_frame_size(struct v4l2_subdev *sd, if (!fse) return -EINVAL; - return check_which(fse->which) ? : check_pad(sd, fse->pad) ? : - check_state(sd, state, fse->which, fse->pad, fse->stream) ? : - sd->ops->pad->enum_frame_size(sd, state, fse); + return do_subdev_call(sd, check_which_pad_state(sd, state, fse->which, + fse->pad, fse->stream), + pad, enum_frame_size, state, fse); } static int call_enum_frame_interval(struct v4l2_subdev *sd, @@ -291,9 +315,9 @@ static int call_enum_frame_interval(struct v4l2_subdev *sd, if (!fie) return -EINVAL; - return check_which(fie->which) ? : check_pad(sd, fie->pad) ? : - check_state(sd, state, fie->which, fie->pad, fie->stream) ? : - sd->ops->pad->enum_frame_interval(sd, state, fie); + return do_subdev_call(sd, check_which_pad_state(sd, state, fie->which, + fie->pad, fie->stream), + pad, enum_frame_interval, state, fie); } static inline int check_selection(struct v4l2_subdev *sd, @@ -308,19 +332,21 @@ static inline int check_selection(struct v4l2_subdev *sd, } static int call_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { - return check_selection(sd, state, sel) ? : - sd->ops->pad->get_selection(sd, state, sel); + return do_subdev_call(sd, check_selection(sd, state, sel), + pad, get_selection, ci, state, sel); } static int call_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { - return check_selection(sd, state, sel) ? : - sd->ops->pad->set_selection(sd, state, sel); + return do_subdev_call(sd, check_selection(sd, state, sel), + pad, set_selection, ci, state, sel); } static inline int check_frame_interval(struct v4l2_subdev *sd, @@ -338,16 +364,16 @@ static int call_get_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_frame_interval *fi) { - return check_frame_interval(sd, state, fi) ? : - sd->ops->pad->get_frame_interval(sd, state, fi); + return do_subdev_call(sd, check_frame_interval(sd, state, fi), + pad, get_frame_interval, state, fi); } static int call_set_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_frame_interval *fi) { - return check_frame_interval(sd, state, fi) ? : - sd->ops->pad->set_frame_interval(sd, state, fi); + return do_subdev_call(sd, check_frame_interval(sd, state, fi), + pad, set_frame_interval, state, fi); } static int call_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, @@ -361,6 +387,9 @@ static int call_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, return -EOPNOTSUPP; #endif + if (!sd->ops->pad->get_frame_desc) + return -ENOIOCTLCMD; + memset(fd, 0, sizeof(*fd)); ret = sd->ops->pad->get_frame_desc(sd, pad, fd); @@ -405,12 +434,12 @@ static inline int check_edid(struct v4l2_subdev *sd, static int call_get_edid(struct v4l2_subdev *sd, struct v4l2_subdev_edid *edid) { - return check_edid(sd, edid) ? : sd->ops->pad->get_edid(sd, edid); + return do_subdev_call(sd, check_edid(sd, edid), pad, get_edid, edid); } static int call_set_edid(struct v4l2_subdev *sd, struct v4l2_subdev_edid *edid) { - return check_edid(sd, edid) ? : sd->ops->pad->set_edid(sd, edid); + return do_subdev_call(sd, check_edid(sd, edid), pad, set_edid, edid); } static int call_s_dv_timings(struct v4l2_subdev *sd, unsigned int pad, @@ -419,8 +448,8 @@ static int call_s_dv_timings(struct v4l2_subdev *sd, unsigned int pad, if (!timings) return -EINVAL; - return check_pad(sd, pad) ? : - sd->ops->pad->s_dv_timings(sd, pad, timings); + return do_subdev_call(sd, check_pad(sd, pad), + pad, s_dv_timings, pad, timings); } static int call_g_dv_timings(struct v4l2_subdev *sd, unsigned int pad, @@ -429,8 +458,8 @@ static int call_g_dv_timings(struct v4l2_subdev *sd, unsigned int pad, if (!timings) return -EINVAL; - return check_pad(sd, pad) ? : - sd->ops->pad->g_dv_timings(sd, pad, timings); + return do_subdev_call(sd, check_pad(sd, pad), + pad, g_dv_timings, pad, timings); } static int call_query_dv_timings(struct v4l2_subdev *sd, unsigned int pad, @@ -439,8 +468,8 @@ static int call_query_dv_timings(struct v4l2_subdev *sd, unsigned int pad, if (!timings) return -EINVAL; - return check_pad(sd, pad) ? : - sd->ops->pad->query_dv_timings(sd, pad, timings); + return do_subdev_call(sd, check_pad(sd, pad), + pad, query_dv_timings, pad, timings); } static int call_dv_timings_cap(struct v4l2_subdev *sd, @@ -449,8 +478,8 @@ static int call_dv_timings_cap(struct v4l2_subdev *sd, if (!cap) return -EINVAL; - return check_pad(sd, cap->pad) ? : - sd->ops->pad->dv_timings_cap(sd, cap); + return do_subdev_call(sd, check_pad(sd, cap->pad), + pad, dv_timings_cap, cap); } static int call_enum_dv_timings(struct v4l2_subdev *sd, @@ -459,8 +488,8 @@ static int call_enum_dv_timings(struct v4l2_subdev *sd, if (!dvt) return -EINVAL; - return check_pad(sd, dvt->pad) ? : - sd->ops->pad->enum_dv_timings(sd, dvt); + return do_subdev_call(sd, check_pad(sd, dvt->pad), + pad, enum_dv_timings, dvt); } static int call_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, @@ -468,14 +497,17 @@ static int call_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, { memset(config, 0, sizeof(*config)); - return check_pad(sd, pad) ? : - sd->ops->pad->get_mbus_config(sd, pad, config); + return do_subdev_call(sd, check_pad(sd, pad), pad, get_mbus_config, + pad, config); } static int call_s_stream(struct v4l2_subdev *sd, int enable) { int ret; + if (!sd->ops->video->s_stream) + return -ENOIOCTLCMD; + /* * The .s_stream() operation must never be called to start or stop an * already started or stopped subdev. Catch offenders but don't return @@ -509,7 +541,7 @@ static int call_s_stream(struct v4l2_subdev *sd, int enable) * wrapper handles the case where the caller does not provide the called * subdev's state. This should be removed when all the callers are fixed. */ -#define DEFINE_STATE_WRAPPER(f, arg_type) \ +#define DEFINE_STATE_WRAPPER(f, arg_type) \ static int call_##f##_state(struct v4l2_subdev *sd, \ struct v4l2_subdev_state *_state, \ arg_type *arg) \ @@ -523,10 +555,25 @@ static int call_s_stream(struct v4l2_subdev *sd, int enable) v4l2_subdev_unlock_state(state); \ return ret; \ } +#define DEFINE_CI_STATE_WRAPPER(f, arg_type) \ + static int call_##f##_state(struct v4l2_subdev *sd, \ + const struct v4l2_subdev_client_info *ci, \ + struct v4l2_subdev_state *_state, \ + arg_type *arg) \ + { \ + struct v4l2_subdev_state *state = _state; \ + int ret; \ + if (!_state) \ + state = v4l2_subdev_lock_and_get_active_state(sd); \ + ret = call_##f(sd, ci, state, arg); \ + if (!_state && state) \ + v4l2_subdev_unlock_state(state); \ + return ret; \ + } #else /* CONFIG_MEDIA_CONTROLLER */ -#define DEFINE_STATE_WRAPPER(f, arg_type) \ +#define DEFINE_STATE_WRAPPER(f, arg_type) \ static int call_##f##_state(struct v4l2_subdev *sd, \ struct v4l2_subdev_state *state, \ arg_type *arg) \ @@ -534,15 +581,24 @@ static int call_s_stream(struct v4l2_subdev *sd, int enable) return call_##f(sd, state, arg); \ } +#define DEFINE_CI_STATE_WRAPPER(f, arg_type) \ + static int call_##f##_state(struct v4l2_subdev *sd, \ + const struct v4l2_subdev_client_info *ci, \ + struct v4l2_subdev_state *state, \ + arg_type *arg) \ + { \ + return call_##f(sd, ci, state, arg); \ + } + #endif /* CONFIG_MEDIA_CONTROLLER */ DEFINE_STATE_WRAPPER(get_fmt, struct v4l2_subdev_format); -DEFINE_STATE_WRAPPER(set_fmt, struct v4l2_subdev_format); +DEFINE_CI_STATE_WRAPPER(set_fmt, struct v4l2_subdev_format); DEFINE_STATE_WRAPPER(enum_mbus_code, struct v4l2_subdev_mbus_code_enum); DEFINE_STATE_WRAPPER(enum_frame_size, struct v4l2_subdev_frame_size_enum); DEFINE_STATE_WRAPPER(enum_frame_interval, struct v4l2_subdev_frame_interval_enum); -DEFINE_STATE_WRAPPER(get_selection, struct v4l2_subdev_selection); -DEFINE_STATE_WRAPPER(set_selection, struct v4l2_subdev_selection); +DEFINE_CI_STATE_WRAPPER(get_selection, struct v4l2_subdev_selection); +DEFINE_CI_STATE_WRAPPER(set_selection, struct v4l2_subdev_selection); static const struct v4l2_subdev_pad_ops v4l2_subdev_call_pad_wrappers = { .get_fmt = call_get_fmt_state, @@ -611,7 +667,7 @@ subdev_ioctl_get_state(struct v4l2_subdev *sd, struct v4l2_subdev_fh *subdev_fh, case VIDIOC_SUBDEV_S_FRAME_INTERVAL: { struct v4l2_subdev_frame_interval *fi = arg; - if (!(subdev_fh->client_caps & + if (!(subdev_fh->ci.caps & V4L2_SUBDEV_CLIENT_CAP_INTERVAL_USES_WHICH)) fi->which = V4L2_SUBDEV_FORMAT_ACTIVE; @@ -650,7 +706,7 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg, struct v4l2_subdev_fh *subdev_fh = to_v4l2_subdev_fh(vfh); bool ro_subdev = test_bit(V4L2_FL_SUBDEV_RO_DEVNODE, &vdev->flags); bool streams_subdev = sd->flags & V4L2_SUBDEV_FL_STREAMS; - bool client_supports_streams = subdev_fh->client_caps & + bool client_supports_streams = subdev_fh->ci.caps & V4L2_SUBDEV_CLIENT_CAP_STREAMS; int rval; @@ -817,7 +873,8 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg, memset(format->reserved, 0, sizeof(format->reserved)); memset(format->format.reserved, 0, sizeof(format->format.reserved)); - return v4l2_subdev_call(sd, pad, set_fmt, state, format); + return v4l2_subdev_call(sd, pad, set_fmt, &subdev_fh->ci, state, + format); } case VIDIOC_SUBDEV_G_CROP: { @@ -834,8 +891,8 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg, sel.stream = crop->stream; sel.target = V4L2_SEL_TGT_CROP; - rval = v4l2_subdev_call( - sd, pad, get_selection, state, &sel); + rval = v4l2_subdev_call(sd, pad, get_selection, &subdev_fh->ci, + state, &sel); crop->rect = sel.r; @@ -860,8 +917,8 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg, sel.target = V4L2_SEL_TGT_CROP; sel.r = crop->rect; - rval = v4l2_subdev_call( - sd, pad, set_selection, state, &sel); + rval = v4l2_subdev_call(sd, pad, set_selection, &subdev_fh->ci, + state, &sel); crop->rect = sel.r; @@ -931,8 +988,8 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg, sel->stream = 0; memset(sel->reserved, 0, sizeof(sel->reserved)); - return v4l2_subdev_call( - sd, pad, get_selection, state, sel); + return v4l2_subdev_call(sd, pad, get_selection, &subdev_fh->ci, + state, sel); } case VIDIOC_SUBDEV_S_SELECTION: { @@ -945,8 +1002,8 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg, sel->stream = 0; memset(sel->reserved, 0, sizeof(sel->reserved)); - return v4l2_subdev_call( - sd, pad, set_selection, state, sel); + return v4l2_subdev_call(sd, pad, set_selection, &subdev_fh->ci, + state, sel); } case VIDIOC_G_EDID: { @@ -1117,7 +1174,7 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg, case VIDIOC_SUBDEV_G_CLIENT_CAP: { struct v4l2_subdev_client_capability *client_cap = arg; - client_cap->capabilities = subdev_fh->client_caps; + client_cap->capabilities = subdev_fh->ci.caps; return 0; } @@ -1137,7 +1194,7 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg, client_cap->capabilities &= (V4L2_SUBDEV_CLIENT_CAP_STREAMS | V4L2_SUBDEV_CLIENT_CAP_INTERVAL_USES_WHICH); - subdev_fh->client_caps = client_cap->capabilities; + subdev_fh->ci.caps = client_cap->capabilities; return 0; } diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c index 6c729fcfce5d..b48cf1b7fd5f 100644 --- a/drivers/platform/x86/intel/int3472/discrete.c +++ b/drivers/platform/x86/intel/int3472/discrete.c @@ -15,6 +15,7 @@ #include <linux/platform_data/x86/int3472.h> #include <linux/platform_device.h> #include <linux/string_choices.h> +#include <linux/time64.h> #include <linux/uuid.h> /* @@ -143,6 +144,11 @@ static const char * const power_enable_hids_enable[] = { NULL }; +static const char * const power_enable_hids_vdda[] = { + "INT347E", /* ov7251 */ + NULL +}; + /** * struct int3472_gpio_map - Map GPIOs to whatever is expected by the * sensor driver (as in DT bindings) @@ -178,12 +184,12 @@ static const struct int3472_gpio_map int3472_gpio_map[] = { .type_to = INT3472_GPIO_TYPE_RESET, .con_id = "enable", }, - { /* ov08x40's handshake pin needs a 45 ms delay on some HP laptops */ - .hids = (const char * const[]) { "OVTI08F4", NULL }, - .type_from = INT3472_GPIO_TYPE_HANDSHAKE, - .type_to = INT3472_GPIO_TYPE_HANDSHAKE, - .con_id = "dvdd", - .enable_time_us = 45 * USEC_PER_MSEC, + { /* Sensors which expect "vdda" as con_id for power enable */ + .hids = power_enable_hids_vdda, + .type_from = INT3472_GPIO_TYPE_POWER_ENABLE, + .type_to = INT3472_GPIO_TYPE_POWER_ENABLE, + .con_id = "vdda", + .enable_time_us = GPIO_REGULATOR_ENABLE_TIME, }, { /* Sensors which expect "vana" as con_id for power enable */ .hids = power_enable_hids_vana, @@ -266,6 +272,10 @@ static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3 *con_id = "avdd"; *gpio_flags = GPIO_ACTIVE_HIGH; break; + case INT3472_GPIO_TYPE_POWER1: + *con_id = "dvdd"; + *gpio_flags = GPIO_ACTIVE_HIGH; + break; case INT3472_GPIO_TYPE_DOVDD: *con_id = "dovdd"; *gpio_flags = GPIO_ACTIVE_HIGH; @@ -273,8 +283,8 @@ static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3 case INT3472_GPIO_TYPE_HANDSHAKE: *con_id = "dvdd"; *gpio_flags = GPIO_ACTIVE_HIGH; - /* Setups using a handshake pin need 25 ms enable delay */ - *enable_time_us = 25 * USEC_PER_MSEC; + /* Powering up the sensor through the handshake pin takes up to 200 ms */ + *enable_time_us = 200 * USEC_PER_MSEC; break; default: *con_id = "unknown"; @@ -296,6 +306,8 @@ static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3 * 0x00 Reset * 0x01 Power down * 0x02 Strobe + * 0x07 Power 0 + * 0x08 Power 1 * 0x0b Power enable * 0x0c Clock enable * 0x0d Privacy LED @@ -328,8 +340,8 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, u8 active_value, pin, type; unsigned long gpio_flags; union acpi_object *obj; + unsigned int obj_value; struct gpio_desc *gpio; - const char *err_msg; const char *con_id; int ret; @@ -344,24 +356,27 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, &int3472_gpio_guid, 0x00, int3472->ngpios + 2, NULL, ACPI_TYPE_INTEGER); - if (!obj) { dev_warn(int3472->dev, "No _DSM entry for GPIO pin %u\n", agpio->pin_table[0]); return 1; } - type = FIELD_GET(INT3472_GPIO_DSM_TYPE, obj->integer.value); + obj_value = obj->integer.value; + + ACPI_FREE(obj); + + type = FIELD_GET(INT3472_GPIO_DSM_TYPE, obj_value); int3472_get_con_id_and_polarity(int3472, &type, &con_id, &gpio_flags, &enable_time_us); - pin = FIELD_GET(INT3472_GPIO_DSM_PIN, obj->integer.value); + pin = FIELD_GET(INT3472_GPIO_DSM_PIN, obj_value); /* Pin field is not really used under Windows and wraps around at 8 bits */ if (pin != (agpio->pin_table[0] & 0xff)) dev_dbg(int3472->dev, FW_BUG "%s %s pin number mismatch _DSM %d resource %d\n", con_id, agpio->resource_source.string_ptr, pin, agpio->pin_table[0]); - active_value = FIELD_GET(INT3472_GPIO_DSM_SENSOR_ON_VAL, obj->integer.value); + active_value = FIELD_GET(INT3472_GPIO_DSM_SENSOR_ON_VAL, obj_value); if (!active_value) gpio_flags ^= GPIO_ACTIVE_LOW; @@ -369,51 +384,57 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, agpio->resource_source.string_ptr, agpio->pin_table[0], str_high_low(gpio_flags == GPIO_ACTIVE_HIGH)); + int3472->ngpios++; + switch (type) { case INT3472_GPIO_TYPE_RESET: case INT3472_GPIO_TYPE_POWERDOWN: case INT3472_GPIO_TYPE_HOTPLUG_DETECT: ret = skl_int3472_map_gpio_to_sensor(int3472, agpio, con_id, gpio_flags); if (ret) - err_msg = "Failed to map GPIO pin to sensor\n"; + return dev_err_probe(int3472->dev, ret, + "Failed to map GPIO pin to sensor\n"); - break; + return 1; case INT3472_GPIO_TYPE_CLK_ENABLE: case INT3472_GPIO_TYPE_PRIVACY_LED: case INT3472_GPIO_TYPE_STROBE: case INT3472_GPIO_TYPE_POWER_ENABLE: + case INT3472_GPIO_TYPE_POWER1: case INT3472_GPIO_TYPE_DOVDD: case INT3472_GPIO_TYPE_HANDSHAKE: gpio = skl_int3472_gpiod_get_from_temp_lookup(int3472, agpio, con_id, gpio_flags); - if (IS_ERR(gpio)) { - ret = PTR_ERR(gpio); - err_msg = "Failed to get GPIO\n"; - break; - } + if (IS_ERR(gpio)) + return dev_err_probe(int3472->dev, PTR_ERR(gpio), + "Failed to get GPIO\n"); switch (type) { case INT3472_GPIO_TYPE_CLK_ENABLE: ret = skl_int3472_register_gpio_clock(int3472, gpio); if (ret) - err_msg = "Failed to register clock\n"; + dev_err_probe(int3472->dev, ret, + "Failed to register clock\n"); break; case INT3472_GPIO_TYPE_PRIVACY_LED: case INT3472_GPIO_TYPE_STROBE: ret = skl_int3472_register_led(int3472, gpio, con_id); if (ret) - err_msg = "Failed to register LED\n"; + dev_err_probe(int3472->dev, ret, + "Failed to register LED\n"); break; case INT3472_GPIO_TYPE_POWER_ENABLE: second_sensor = int3472->quirks.avdd_second_sensor; fallthrough; + case INT3472_GPIO_TYPE_POWER1: case INT3472_GPIO_TYPE_DOVDD: case INT3472_GPIO_TYPE_HANDSHAKE: ret = skl_int3472_register_regulator(int3472, gpio, enable_time_us, con_id, second_sensor); if (ret) - err_msg = "Failed to register regulator\n"; + dev_err_probe(int3472->dev, ret, + "Failed to register regulator\n"); break; default: /* Never reached */ @@ -424,23 +445,13 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, if (ret) gpiod_put(gpio); - break; + return ret < 0 ? ret : 1; default: dev_warn(int3472->dev, "GPIO type 0x%02x unknown; the sensor may not work\n", type); - ret = 1; - break; + return 1; } - - int3472->ngpios++; - ACPI_FREE(obj); - - if (ret < 0) - return dev_err_probe(int3472->dev, ret, err_msg); - - /* Tell acpi_dev_get_resources() to not make a copy of the resource */ - return 1; } int int3472_discrete_parse_crs(struct int3472_discrete_device *int3472) diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c index a77ed32abe55..dc777dbac61f 100644 --- a/drivers/platform/x86/intel/int3472/tps68470.c +++ b/drivers/platform/x86/intel/int3472/tps68470.c @@ -161,7 +161,7 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) regmap = devm_regmap_init_i2c(client, &tps68470_regmap_config); if (IS_ERR(regmap)) { - dev_err(&client->dev, "Failed to create regmap: %ld\n", PTR_ERR(regmap)); + dev_err(&client->dev, "Failed to create regmap: %pe\n", regmap); return PTR_ERR(regmap); } diff --git a/drivers/staging/media/atomisp/i2c/atomisp-gc2235.c b/drivers/staging/media/atomisp/i2c/atomisp-gc2235.c index f9cc5f45fe00..38f69582c174 100644 --- a/drivers/staging/media/atomisp/i2c/atomisp-gc2235.c +++ b/drivers/staging/media/atomisp/i2c/atomisp-gc2235.c @@ -520,6 +520,7 @@ static int gc2235_startup(struct v4l2_subdev *sd) } static int gc2235_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/staging/media/atomisp/i2c/atomisp-ov2722.c b/drivers/staging/media/atomisp/i2c/atomisp-ov2722.c index 2c41c496daa6..e3137fbc5802 100644 --- a/drivers/staging/media/atomisp/i2c/atomisp-ov2722.c +++ b/drivers/staging/media/atomisp/i2c/atomisp-ov2722.c @@ -623,6 +623,7 @@ static int ov2722_startup(struct v4l2_subdev *sd) } static int ov2722_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/staging/media/atomisp/pci/atomisp_cmd.c b/drivers/staging/media/atomisp/pci/atomisp_cmd.c index 6cd500d9fd26..52c71cd86dec 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_cmd.c +++ b/drivers/staging/media/atomisp/pci/atomisp_cmd.c @@ -3745,14 +3745,16 @@ static int atomisp_set_sensor_crop_and_fmt(struct atomisp_device *isp, sel.r.left = ((input->native_rect.width - sel.r.width) / 2) & ~1; sel.r.top = ((input->native_rect.height - sel.r.height) / 2) & ~1; - ret = v4l2_subdev_call(input->sensor, pad, set_selection, sd_state, &sel); + ret = v4l2_subdev_call(input->sensor, pad, set_selection, NULL, + sd_state, &sel); if (ret) dev_err(isp->dev, "Error setting crop to (%d,%d)/%ux%u: %d\n", sel.r.left, sel.r.top, sel.r.width, sel.r.height, ret); set_fmt: if (ret == 0) { - ret = v4l2_subdev_call(input->sensor, pad, set_fmt, sd_state, &format); + ret = v4l2_subdev_call(input->sensor, pad, set_fmt, NULL, + sd_state, &format); dev_dbg(isp->dev, "Set sensor format ret: %d size %dx%d\n", ret, format.format.width, format.format.height); } @@ -3765,13 +3767,16 @@ set_fmt: sd_state = v4l2_subdev_lock_and_get_active_state(input->sensor_isp); format.pad = SENSOR_ISP_PAD_SINK; - ret = v4l2_subdev_call(input->sensor_isp, pad, set_fmt, sd_state, &format); + ret = v4l2_subdev_call(input->sensor_isp, pad, set_fmt, NULL, + sd_state, &format); dev_dbg(isp->dev, "Set sensor ISP sink format ret: %d size %dx%d\n", ret, format.format.width, format.format.height); if (ret == 0) { format.pad = SENSOR_ISP_PAD_SOURCE; - ret = v4l2_subdev_call(input->sensor_isp, pad, set_fmt, sd_state, &format); + ret = v4l2_subdev_call(input->sensor_isp, pad, + set_fmt, NULL, sd_state, + &format); dev_dbg(isp->dev, "Set sensor ISP source format ret: %d size %dx%d\n", ret, format.format.width, format.format.height); } @@ -3783,7 +3788,8 @@ set_fmt: /* Propagate new fmt to CSI port */ if (ret == 0 && which == V4L2_SUBDEV_FORMAT_ACTIVE) { format.pad = CSI2_PAD_SINK; - ret = v4l2_subdev_call(input->csi_port, pad, set_fmt, NULL, &format); + ret = v4l2_subdev_call(input->csi_port, pad, set_fmt, NULL, + NULL, &format); if (ret) return ret; } diff --git a/drivers/staging/media/atomisp/pci/atomisp_csi2.c b/drivers/staging/media/atomisp/pci/atomisp_csi2.c index 95b9113d75e9..71df4ef629c2 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_csi2.c +++ b/drivers/staging/media/atomisp/pci/atomisp_csi2.c @@ -123,6 +123,7 @@ int atomisp_csi2_set_ffmt(struct v4l2_subdev *sd, * return -EINVAL or zero on success */ static int csi2_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/staging/media/atomisp/pci/atomisp_subdev.c b/drivers/staging/media/atomisp/pci/atomisp_subdev.c index 9de9cd884d99..bdd2d9b18da2 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_subdev.c +++ b/drivers/staging/media/atomisp/pci/atomisp_subdev.c @@ -259,6 +259,7 @@ static void isp_get_fmt_rect(struct v4l2_subdev *sd, } static int isp_subdev_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -452,6 +453,7 @@ get_rect: } static int isp_subdev_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -555,6 +557,7 @@ static int isp_subdev_get_format(struct v4l2_subdev *sd, * to the format type. */ static int isp_subdev_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/staging/media/atomisp/pci/atomisp_v4l2.c b/drivers/staging/media/atomisp/pci/atomisp_v4l2.c index 812230397409..476fe143a591 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_v4l2.c +++ b/drivers/staging/media/atomisp/pci/atomisp_v4l2.c @@ -913,7 +913,7 @@ static void atomisp_init_sensor(struct atomisp_input_subdev *input) sel.which = V4L2_SUBDEV_FORMAT_ACTIVE; sel.target = V4L2_SEL_TGT_NATIVE_SIZE; - err = v4l2_subdev_call(input->sensor, pad, get_selection, + err = v4l2_subdev_call(input->sensor, pad, get_selection, NULL, act_sd_state, &sel); if (err) goto unlock_act_sd_state; @@ -922,7 +922,7 @@ static void atomisp_init_sensor(struct atomisp_input_subdev *input) sel.which = V4L2_SUBDEV_FORMAT_ACTIVE; sel.target = V4L2_SEL_TGT_CROP_DEFAULT; - err = v4l2_subdev_call(input->sensor, pad, get_selection, + err = v4l2_subdev_call(input->sensor, pad, get_selection, NULL, act_sd_state, &sel); if (err) goto unlock_act_sd_state; @@ -968,7 +968,7 @@ static void atomisp_init_sensor(struct atomisp_input_subdev *input) if (!input->sensor->state_lock) v4l2_subdev_lock_state(input->try_sd_state); - err = v4l2_subdev_call(input->sensor, pad, set_selection, + err = v4l2_subdev_call(input->sensor, pad, set_selection, NULL, input->try_sd_state, &sel); if (!input->sensor->state_lock) @@ -980,7 +980,7 @@ static void atomisp_init_sensor(struct atomisp_input_subdev *input) sel.which = V4L2_SUBDEV_FORMAT_ACTIVE; sel.target = V4L2_SEL_TGT_CROP; sel.r = input->native_rect; - err = v4l2_subdev_call(input->sensor, pad, set_selection, + err = v4l2_subdev_call(input->sensor, pad, set_selection, NULL, act_sd_state, &sel); if (err) goto unlock_act_sd_state; diff --git a/drivers/staging/media/atomisp/pci/isp/kernels/s3a/s3a_1.0/ia_css_s3a_types.h b/drivers/staging/media/atomisp/pci/isp/kernels/s3a/s3a_1.0/ia_css_s3a_types.h index b8206d2f3d31..97e63082d254 100644 --- a/drivers/staging/media/atomisp/pci/isp/kernels/s3a/s3a_1.0/ia_css_s3a_types.h +++ b/drivers/staging/media/atomisp/pci/isp/kernels/s3a/s3a_1.0/ia_css_s3a_types.h @@ -102,8 +102,8 @@ struct ia_css_3a_grid_info { * awb_lg_*: Thresholds to check the saturated bayer pixels for AWB. * Condition of effective pixel for AWB level gate check: * bayer(sensor) <= awb_lg_high_raw && - * bayer(when AWB statisitcs is calculated) >= awb_lg_low && - * bayer(when AWB statisitcs is calculated) <= awb_lg_high + * bayer(when AWB statistics is calculated) >= awb_lg_low && + * bayer(when AWB statistics is calculated) <= awb_lg_high * af_fir*: Coefficients of high pass filter to calculate AF statistics. * * ISP block: S3A1(ae_y_* for AE/AF, awb_lg_* for AWB) diff --git a/drivers/staging/media/av7110/av7110.c b/drivers/staging/media/av7110/av7110.c index 06c43b5f8a87..432404eceaea 100644 --- a/drivers/staging/media/av7110/av7110.c +++ b/drivers/staging/media/av7110/av7110.c @@ -2347,7 +2347,7 @@ static int av7110_attach(struct saa7146_dev *dev, /* RESET SAA7146 */ saa7146_write(dev, MC1, MASK_31); - /* autodetection success seems to be time-dependend after reset */ + /* autodetection success seems to be time-dependent after reset */ /* Fix VSYNC level */ saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); diff --git a/drivers/staging/media/av7110/av7110_ir.c b/drivers/staging/media/av7110/av7110_ir.c index 9b7bf1857868..d0948a80d387 100644 --- a/drivers/staging/media/av7110/av7110_ir.c +++ b/drivers/staging/media/av7110/av7110_ir.c @@ -154,5 +154,3 @@ void av7110_ir_exit(struct av7110 *av7110) rc_free_device(av7110->ir.rcdev); } -//MODULE_AUTHOR("Holger Waechtler <holger@convergence.de>, Oliver Endriss <o.endriss@gmx.de>"); -//MODULE_LICENSE("GPL"); diff --git a/drivers/staging/media/av7110/sp8870.c b/drivers/staging/media/av7110/sp8870.c index 29fb4934c039..aecdbd87976f 100644 --- a/drivers/staging/media/av7110/sp8870.c +++ b/drivers/staging/media/av7110/sp8870.c @@ -92,7 +92,7 @@ static int sp8870_readreg(struct sp8870_state *state, u16 reg) return -1; } - return (b1[0] << 8 | b1[1]); + return b1[0] << 8 | b1[1]; } static int sp8870_firmware_upload(struct sp8870_state *state, const struct firmware *fw) @@ -167,7 +167,7 @@ static void sp8870_microcontroller_start(struct sp8870_state *state) static int sp8870_read_data_valid_signal(struct sp8870_state *state) { - return (sp8870_readreg(state, 0x0D02) > 0); + return sp8870_readreg(state, 0x0D02) > 0; } static int configure_reg0xc05(struct dtv_frontend_properties *p, u16 *reg0xc05) @@ -315,7 +315,6 @@ static int sp8870_init(struct dvb_frontend *fe) sp8870_wake_up(state); if (state->initialised) return 0; - state->initialised = 1; dprintk("initialising frontend...\n"); @@ -353,6 +352,8 @@ static int sp8870_init(struct dvb_frontend *fe) sp8870_writereg(state, 0x0D00, 0x010); sp8870_writereg(state, 0x0D01, 0x000); + state->initialised = 1; + return 0; } @@ -401,7 +402,7 @@ static int sp8870_read_ber(struct dvb_frontend *fe, u32 *ber) if (ret < 0) return -EIO; - tmp = ret << 6; + tmp |= ret << 6; if (tmp >= 0x3FFF0) tmp = ~0; diff --git a/drivers/staging/media/imx/imx-ic-prp.c b/drivers/staging/media/imx/imx-ic-prp.c index 2b80d54006b3..b2c648149964 100644 --- a/drivers/staging/media/imx/imx-ic-prp.c +++ b/drivers/staging/media/imx/imx-ic-prp.c @@ -11,7 +11,6 @@ #include <linux/delay.h> #include <linux/interrupt.h> #include <linux/module.h> -#include <linux/sched.h> #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/timer.h> @@ -151,6 +150,7 @@ out: } static int prp_set_fmt(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/staging/media/imx/imx-ic-prpencvf.c b/drivers/staging/media/imx/imx-ic-prpencvf.c index 77360bfe081a..3430ddeac356 100644 --- a/drivers/staging/media/imx/imx-ic-prpencvf.c +++ b/drivers/staging/media/imx/imx-ic-prpencvf.c @@ -9,9 +9,8 @@ * Copyright (c) 2012-2017 Mentor Graphics Inc. */ #include <linux/delay.h> -#include <linux/interrupt.h> #include <linux/module.h> -#include <linux/sched.h> +#include <linux/interrupt.h> #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/timer.h> @@ -919,6 +918,7 @@ static void prp_try_fmt(struct prp_priv *priv, } static int prp_set_fmt(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/staging/media/imx/imx-media-capture.c b/drivers/staging/media/imx/imx-media-capture.c index bfd71d25facc..9216a221b72f 100644 --- a/drivers/staging/media/imx/imx-media-capture.c +++ b/drivers/staging/media/imx/imx-media-capture.c @@ -5,7 +5,6 @@ * Copyright (c) 2012-2016 Mentor Graphics Inc. */ #include <linux/delay.h> -#include <linux/fs.h> #include <linux/module.h> #include <linux/pinctrl/consumer.h> #include <linux/platform_device.h> diff --git a/drivers/staging/media/imx/imx-media-csc-scaler.c b/drivers/staging/media/imx/imx-media-csc-scaler.c index 00fcdd4d0487..6f4c0b09bb36 100644 --- a/drivers/staging/media/imx/imx-media-csc-scaler.c +++ b/drivers/staging/media/imx/imx-media-csc-scaler.c @@ -7,8 +7,6 @@ */ #include <linux/module.h> #include <linux/delay.h> -#include <linux/fs.h> -#include <linux/sched.h> #include <linux/slab.h> #include <video/imx-ipu-v3.h> #include <video/imx-ipu-image-convert.h> @@ -147,6 +145,7 @@ err: v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR); v4l2_m2m_job_finish(priv->m2m_dev, ctx->fh.m2m_ctx); + kfree(run); } /* diff --git a/drivers/staging/media/imx/imx-media-csi.c b/drivers/staging/media/imx/imx-media-csi.c index ef22a083f8eb..43c637b333f2 100644 --- a/drivers/staging/media/imx/imx-media-csi.c +++ b/drivers/staging/media/imx/imx-media-csi.c @@ -1525,6 +1525,7 @@ static void csi_try_fmt(struct csi_priv *priv, } static int csi_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *sdformat) { @@ -1593,6 +1594,7 @@ out: } static int csi_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1657,6 +1659,7 @@ static int csi_set_scale(u32 *compose, u32 crop, u32 flags) } static int csi_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/staging/media/imx/imx-media-dev.c b/drivers/staging/media/imx/imx-media-dev.c index a08389b99d14..a8f39319bd8a 100644 --- a/drivers/staging/media/imx/imx-media-dev.c +++ b/drivers/staging/media/imx/imx-media-dev.c @@ -4,7 +4,6 @@ * * Copyright (c) 2016-2019 Mentor Graphics Inc. */ -#include <linux/fs.h> #include <linux/module.h> #include <linux/platform_device.h> #include <media/v4l2-async.h> diff --git a/drivers/staging/media/imx/imx-media-vdic.c b/drivers/staging/media/imx/imx-media-vdic.c index 58f1112e28e5..8dcbb6fe7649 100644 --- a/drivers/staging/media/imx/imx-media-vdic.c +++ b/drivers/staging/media/imx/imx-media-vdic.c @@ -566,6 +566,7 @@ static void vdic_try_fmt(struct vdic_priv *priv, } static int vdic_set_fmt(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/staging/media/imx/imx6-mipi-csi2.c b/drivers/staging/media/imx/imx6-mipi-csi2.c index 211f67fb92b5..f6cbf0310e89 100644 --- a/drivers/staging/media/imx/imx6-mipi-csi2.c +++ b/drivers/staging/media/imx/imx6-mipi-csi2.c @@ -525,6 +525,7 @@ static int csi2_get_fmt(struct v4l2_subdev *sd, } static int csi2_set_fmt(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/staging/media/ipu3/ipu3-css.c b/drivers/staging/media/ipu3/ipu3-css.c index 8063401246fb..b2c8fb7d6813 100644 --- a/drivers/staging/media/ipu3/ipu3-css.c +++ b/drivers/staging/media/ipu3/ipu3-css.c @@ -290,7 +290,6 @@ fail: void imgu_css_set_powerdown(struct device *dev, void __iomem *base) { - dev_dbg(dev, "%s\n", __func__); /* wait for cio idle signal */ if (imgu_hw_wait(base, IMGU_REG_CIO_GATE_BURST_STATE, IMGU_CIO_GATE_BURST_MASK, 0)) diff --git a/drivers/staging/media/ipu3/ipu3-v4l2.c b/drivers/staging/media/ipu3/ipu3-v4l2.c index 2f6041d342f4..61fdefe78c3d 100644 --- a/drivers/staging/media/ipu3/ipu3-v4l2.c +++ b/drivers/staging/media/ipu3/ipu3-v4l2.c @@ -145,6 +145,7 @@ static int imgu_subdev_get_fmt(struct v4l2_subdev *sd, } static int imgu_subdev_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -212,6 +213,7 @@ imgu_subdev_get_compose(struct imgu_v4l2_subdev *sd, } static int imgu_subdev_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -236,6 +238,7 @@ static int imgu_subdev_get_selection(struct v4l2_subdev *sd, } static int imgu_subdev_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/staging/media/ipu7/TODO b/drivers/staging/media/ipu7/TODO index dc27bb6463da..57cca4e4033f 100644 --- a/drivers/staging/media/ipu7/TODO +++ b/drivers/staging/media/ipu7/TODO @@ -1,26 +1,2 @@ -This is a list of things that need to be done to get this driver out of the -staging directory. - -- ABI headers cleanup - Cleanup the firmware ABI headers - -- Add metadata capture support - The IPU7 hardware should support metadata capture, but it is not - fully verified with IPU7 firmware ABI so far, need to add the metadata - capture support. - -- Refine CSI2 PHY code - Refine the ipu7-isys-csi2-phy.c, move the hardware specific variant - into structure, clarify and explain the PHY registers to make it more - readable. - -- Work with the common IPU module - Sakari commented much of the driver code is the same than the IPU6 driver. - IPU7 driver is expected to work with the common IPU module in future. - -- Register definition cleanup - Add IPU7 prefix for IPU7 specific registers and related macros. Some - ISYS IO sub-blocks register definitions are offset values from specific - sub-block base, but it is not clear and well suited for driver to use, - need to update the register definitions to make it more clear and - readable. +This driver will be removed soon in favour of supporting IPU7 and later +in the ipu6 driver. diff --git a/drivers/staging/media/ipu7/ipu7-isys-csi2.c b/drivers/staging/media/ipu7/ipu7-isys-csi2.c index f34eabfe8a98..fcc7efcf69c3 100644 --- a/drivers/staging/media/ipu7/ipu7-isys-csi2.c +++ b/drivers/staging/media/ipu7/ipu7-isys-csi2.c @@ -190,6 +190,7 @@ static int ipu7_isys_csi2_enable_stream(struct ipu7_isys_csi2 *csi2) } static int ipu7_isys_csi2_set_sel(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -241,6 +242,7 @@ static int ipu7_isys_csi2_set_sel(struct v4l2_subdev *sd, } static int ipu7_isys_csi2_get_sel(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/staging/media/ipu7/ipu7-isys-subdev.c b/drivers/staging/media/ipu7/ipu7-isys-subdev.c index 67a776033d5b..2cb0521b4240 100644 --- a/drivers/staging/media/ipu7/ipu7-isys-subdev.c +++ b/drivers/staging/media/ipu7/ipu7-isys-subdev.c @@ -99,6 +99,7 @@ u32 ipu7_isys_convert_bayer_order(u32 code, int x, int y) } int ipu7_isys_subdev_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/staging/media/ipu7/ipu7-isys-subdev.h b/drivers/staging/media/ipu7/ipu7-isys-subdev.h index faa50031cf24..c3585d1c4128 100644 --- a/drivers/staging/media/ipu7/ipu7-isys-subdev.h +++ b/drivers/staging/media/ipu7/ipu7-isys-subdev.h @@ -31,6 +31,7 @@ bool ipu7_isys_is_bayer_format(u32 code); u32 ipu7_isys_convert_bayer_order(u32 code, int x, int y); int ipu7_isys_subdev_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format); int ipu7_isys_subdev_enum_mbus_code(struct v4l2_subdev *sd, diff --git a/drivers/staging/media/ipu7/ipu7-isys.c b/drivers/staging/media/ipu7/ipu7-isys.c index 601e5a79ef8e..a54393fbb9db 100644 --- a/drivers/staging/media/ipu7/ipu7-isys.c +++ b/drivers/staging/media/ipu7/ipu7-isys.c @@ -590,6 +590,7 @@ static void isys_remove(struct auxiliary_device *auxdev) isys_notifier_cleanup(isys); isys_unregister_devices(isys); + ipu7_fw_isys_release(isys); cpu_latency_qos_remove_request(&isys->pm_qos); diff --git a/drivers/staging/media/ipu7/ipu7.c b/drivers/staging/media/ipu7/ipu7.c index 48a35bda4237..88e362c7ae35 100644 --- a/drivers/staging/media/ipu7/ipu7.c +++ b/drivers/staging/media/ipu7/ipu7.c @@ -39,6 +39,10 @@ #define IPU_PCI_BAR 0 #define IPU_PCI_PBBAR 4 +static int force_probe = !IS_BUILTIN(CONFIG_VIDEO_INTEL_IPU6_IPU7); +module_param(force_probe, int, 0644); +MODULE_PARM_DESC(force_probe, "Probe ipu7 and ipu7.5 with this driver instead of ipu6"); + static const unsigned int ipu7_csi_offsets[] = { IPU_CSI_PORT_A_ADDR_OFFSET, IPU_CSI_PORT_B_ADDR_OFFSET, @@ -2414,6 +2418,9 @@ static int ipu7_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) u32 is_es; int ret; + if (!force_probe) + return -ENODEV; + if (!fwnode || fwnode_property_read_u32(fwnode, "is_es", &is_es)) is_es = 0; diff --git a/drivers/staging/media/max96712/max96712.c b/drivers/staging/media/max96712/max96712.c index 0751b2e04895..94ae304ac85f 100644 --- a/drivers/staging/media/max96712/max96712.c +++ b/drivers/staging/media/max96712/max96712.c @@ -264,7 +264,6 @@ static const struct v4l2_subdev_internal_ops max96712_internal_ops = { static const struct v4l2_subdev_pad_ops max96712_pad_ops = { .get_fmt = v4l2_subdev_get_fmt, - .set_fmt = v4l2_subdev_get_fmt, }; static const struct v4l2_subdev_ops max96712_subdev_ops = { diff --git a/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_proc.c b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_proc.c index 46a334b602f1..3f376af6c228 100644 --- a/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_proc.c +++ b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_proc.c @@ -313,6 +313,7 @@ static int sun6i_isp_proc_get_fmt(struct v4l2_subdev *subdev, } static int sun6i_isp_proc_set_fmt(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/staging/media/tegra-video/csi.c b/drivers/staging/media/tegra-video/csi.c index 41d57dac61a7..5a5fca378ae6 100644 --- a/drivers/staging/media/tegra-video/csi.c +++ b/drivers/staging/media/tegra-video/csi.c @@ -170,6 +170,7 @@ static int csi_enum_frameintervals(struct v4l2_subdev *subdev, } static int csi_set_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/staging/media/tegra-video/vi.c b/drivers/staging/media/tegra-video/vi.c index 01622013c109..6a5f66da348b 100644 --- a/drivers/staging/media/tegra-video/vi.c +++ b/drivers/staging/media/tegra-video/vi.c @@ -477,7 +477,7 @@ static int __tegra_channel_try_format(struct tegra_vi_channel *chan, ret = v4l2_subdev_call(subdev, pad, enum_frame_size, sd_state, &fse); if (ret) { if (!v4l2_subdev_has_op(subdev, pad, get_selection) || - v4l2_subdev_call(subdev, pad, get_selection, NULL, &sdsel)) { + v4l2_subdev_call(subdev, pad, get_selection, NULL, NULL, &sdsel)) { try_crop->width = 0; try_crop->height = 0; } else { @@ -489,7 +489,7 @@ static int __tegra_channel_try_format(struct tegra_vi_channel *chan, try_crop->height = fse.max_height; } - ret = v4l2_subdev_call(subdev, pad, set_fmt, sd_state, &fmt); + ret = v4l2_subdev_call(subdev, pad, set_fmt, NULL, sd_state, &fmt); if (ret < 0) goto out_free; @@ -543,7 +543,7 @@ static int tegra_channel_set_format(struct file *file, void *fh, fmt.pad = 0; v4l2_fill_mbus_format(&fmt.format, pix, fmtinfo->code); subdev = tegra_channel_get_remote_source_subdev(chan); - ret = v4l2_subdev_call(subdev, pad, set_fmt, NULL, &fmt); + ret = v4l2_subdev_call(subdev, pad, set_fmt, NULL, NULL, &fmt); if (ret < 0) return ret; @@ -626,7 +626,7 @@ static int tegra_channel_g_selection(struct file *file, void *priv, * Try the get selection operation and fallback to get format if not * implemented. */ - ret = v4l2_subdev_call(subdev, pad, get_selection, NULL, &sdsel); + ret = v4l2_subdev_call(subdev, pad, get_selection, NULL, NULL, &sdsel); if (!ret) sel->r = sdsel.r; if (ret != -ENOIOCTLCMD) @@ -667,7 +667,7 @@ static int tegra_channel_s_selection(struct file *file, void *fh, if (vb2_is_busy(&chan->queue)) return -EBUSY; - ret = v4l2_subdev_call(subdev, pad, set_selection, NULL, &sdsel); + ret = v4l2_subdev_call(subdev, pad, set_selection, NULL, NULL, &sdsel); if (!ret) { sel->r = sdsel.r; /* @@ -1247,17 +1247,54 @@ static int tegra_vi_tpg_channels_alloc(struct tegra_vi *vi) return 0; } +static int tegra_vi_port_channel_alloc(struct tegra_vi *vi, + struct device_node *port) +{ + struct v4l2_fwnode_endpoint v4l2_ep = { .bus_type = 0 }; + struct device_node *parent; + struct device_node *ep; + unsigned int port_num; + unsigned int lanes; + int ret; + + if (!of_node_name_eq(port, "port")) + return 0; + + if (of_property_read_u32(port, "reg", &port_num) < 0) + return 0; + + if (port_num > vi->soc->vi_max_channels) { + dev_err(vi->dev, "invalid port num %d for %pOF\n", + port_num, port); + return -EINVAL; + } + + ep = of_get_child_by_name(port, "endpoint"); + if (!ep) + return 0; + + parent = of_graph_get_remote_port_parent(ep); + of_node_put(ep); + if (!parent) + return 0; + + ep = of_graph_get_endpoint_by_regs(parent, 0, 0); + of_node_put(parent); + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &v4l2_ep); + of_node_put(ep); + if (ret) + return 0; + + lanes = v4l2_ep.bus.mipi_csi2.num_data_lanes; + + return tegra_vi_channel_alloc(vi, port_num, port, lanes); +} + static int tegra_vi_channels_alloc(struct tegra_vi *vi) { struct device_node *node = vi->dev->of_node; - struct device_node *ep = NULL; struct device_node *ports; - struct device_node *port = NULL; - unsigned int port_num; - struct device_node *parent; - struct v4l2_fwnode_endpoint v4l2_ep = { .bus_type = 0 }; - unsigned int lanes; - int err; + struct device_node *port; int ret = 0; ports = of_get_child_by_name(node, "ports"); @@ -1265,46 +1302,15 @@ static int tegra_vi_channels_alloc(struct tegra_vi *vi) return dev_err_probe(vi->dev, -ENODEV, "%pOF: missing 'ports' node\n", node); for_each_child_of_node(ports, port) { - if (!of_node_name_eq(port, "port")) - continue; - - err = of_property_read_u32(port, "reg", &port_num); - if (err < 0) - continue; - - if (port_num > vi->soc->vi_max_channels) { - dev_err(vi->dev, "invalid port num %d for %pOF\n", - port_num, port); - ret = -EINVAL; - goto cleanup; + ret = tegra_vi_port_channel_alloc(vi, port); + if (ret) { + of_node_put(port); + break; } - - ep = of_get_child_by_name(port, "endpoint"); - if (!ep) - continue; - - parent = of_graph_get_remote_port_parent(ep); - of_node_put(ep); - if (!parent) - continue; - - ep = of_graph_get_endpoint_by_regs(parent, 0, 0); - of_node_put(parent); - err = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), - &v4l2_ep); - of_node_put(ep); - if (err) - continue; - - lanes = v4l2_ep.bus.mipi_csi2.num_data_lanes; - ret = tegra_vi_channel_alloc(vi, port_num, port, lanes); - if (ret < 0) - goto cleanup; } -cleanup: - of_node_put(port); of_node_put(ports); + return ret; } diff --git a/include/dt-bindings/media/video-interface-devices.h b/include/dt-bindings/media/video-interface-devices.h new file mode 100644 index 000000000000..d2340b457292 --- /dev/null +++ b/include/dt-bindings/media/video-interface-devices.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ +/* + * Copyright (C) 2026 Kieran Bingham <kieran.bingham@ideasonboard.com> + */ + +#ifndef __DT_BINDINGS_MEDIA_VIDEO_INTERFACE_DEVICES_H__ +#define __DT_BINDINGS_MEDIA_VIDEO_INTERFACE_DEVICES_H__ + +#define MEDIA_ORIENTATION_FRONT 0 +#define MEDIA_ORIENTATION_BACK 1 +#define MEDIA_ORIENTATION_EXTERNAL 2 + +#endif /* __DT_BINDINGS_MEDIA_VIDEO_INTERFACE_DEVICES_H__ */ diff --git a/include/linux/platform_data/x86/int3472.h b/include/linux/platform_data/x86/int3472.h index a73841dfae27..b1040e36deb8 100644 --- a/include/linux/platform_data/x86/int3472.h +++ b/include/linux/platform_data/x86/int3472.h @@ -25,6 +25,8 @@ #define INT3472_GPIO_TYPE_RESET 0x00 #define INT3472_GPIO_TYPE_POWERDOWN 0x01 #define INT3472_GPIO_TYPE_STROBE 0x02 +#define INT3472_GPIO_TYPE_POWER0 0x07 +#define INT3472_GPIO_TYPE_POWER1 0x08 #define INT3472_GPIO_TYPE_POWER_ENABLE 0x0b #define INT3472_GPIO_TYPE_CLK_ENABLE 0x0c #define INT3472_GPIO_TYPE_PRIVACY_LED 0x0d diff --git a/include/linux/property.h b/include/linux/property.h index 907c790a3f01..9143fe4f5c05 100644 --- a/include/linux/property.h +++ b/include/linux/property.h @@ -547,6 +547,11 @@ unsigned int fwnode_graph_get_endpoint_count(const struct fwnode_handle *fwnode, for (child = fwnode_graph_get_next_endpoint(fwnode, NULL); child; \ child = fwnode_graph_get_next_endpoint(fwnode, child)) +#define fwnode_graph_for_each_endpoint_scoped(fwnode, child) \ + for (struct fwnode_handle *child __free(fwnode_handle) = \ + fwnode_graph_get_next_endpoint(fwnode, NULL); \ + child; child = fwnode_graph_get_next_endpoint(fwnode, child)) + int fwnode_graph_parse_endpoint(const struct fwnode_handle *fwnode, struct fwnode_endpoint *endpoint); diff --git a/include/media/ipu-bridge.h b/include/media/ipu-bridge.h index 16fac765456e..760f0767ce58 100644 --- a/include/media/ipu-bridge.h +++ b/include/media/ipu-bridge.h @@ -17,13 +17,28 @@ #define IPU_SENSOR_ROTATION_NORMAL 0 #define IPU_SENSOR_ROTATION_INVERTED 1 -#define IPU_SENSOR_CONFIG(_HID, _NR, ...) \ - (const struct ipu_sensor_config) { \ - .hid = _HID, \ - .nr_link_freqs = _NR, \ - .link_freqs = { __VA_ARGS__ } \ +/* Flags for struct ipu_sensor_config */ +/* The sensor's CSI-2 transmitter needs a non-continuous clock */ +#define IPU_BR_FL_CSI2_CLK_NONCONTINUOUS BIT(0) + +/* + * Sensor config specific to one or more IPUs, identified by their PCI product + * IDs, with flags describing what the sensor needs there. Entries for one HID + * must be adjacent in ipu_supported_sensors[], with the IPU-specific ones + * before the generic one. + */ +#define IPU_SENSOR_CONFIG_MATCH_FL(_HID, _IDS, _FLAGS, _NR, ...) \ + (const struct ipu_sensor_config) { \ + .hid = _HID, \ + .pci_ids = _IDS, \ + .flags = _FLAGS, \ + .nr_link_freqs = _NR, \ + .link_freqs = { __VA_ARGS__ } \ } +#define IPU_SENSOR_CONFIG(_HID, _NR, ...) \ + IPU_SENSOR_CONFIG_MATCH_FL(_HID, NULL, 0, _NR, __VA_ARGS__) + #define NODE_SENSOR(_HID, _PROPS) \ (const struct software_node) { \ .name = _HID, \ @@ -64,6 +79,24 @@ enum ipu_sensor_swnodes { SWNODE_COUNT }; +enum ipu_bridge_ep_props { + IPU_BRIDGE_EP_BUS_TYPE, + IPU_BRIDGE_EP_DATA_LANES, + IPU_BRIDGE_EP_REMOTE_EP, + IPU_BRIDGE_EP_LINK_FREQUENCIES, + IPU_BRIDGE_EP_CLOCK_NONCONTINUOUS, + IPU_BRIDGE_EP_NUM_OF, + IPU_BRIDGE_EP_NUM_ENTRIES +}; + +/* + * Get the index of the next property in a property array, with a given maximum + * value. + */ +#define IPU_BRIDGE_NEXT_PROPERTY(index, max) \ + (WARN_ON((index) > IPU_BRIDGE_##max) ? \ + IPU_BRIDGE_##max : (index)++) + /* Data representation as it is in ACPI SSDB buffer */ struct ipu_sensor_ssdb { u8 version; @@ -115,6 +148,9 @@ struct ipu_node_names { struct ipu_sensor_config { const char *hid; + /* Zero-terminated list of IPU PCI product IDs, NULL for any IPU */ + const u16 *pci_ids; + const u32 flags; const u8 nr_link_freqs; const u64 link_freqs[MAX_NUM_LINK_FREQS]; }; @@ -141,7 +177,7 @@ struct ipu_sensor { const char *vcm_type; struct ipu_property_names prop_names; - struct property_entry ep_properties[5]; + struct property_entry ep_properties[IPU_BRIDGE_EP_NUM_ENTRIES]; struct property_entry dev_properties[5]; struct property_entry ipu_properties[3]; struct property_entry ivsc_properties[1]; @@ -160,6 +196,8 @@ typedef int (*ipu_parse_sensor_fwnode_t)(struct acpi_device *adev, struct ipu_bridge { struct device *dev; + /* PCI product ID of the IPU, 0 if it is not a PCI device */ + u16 pci_id; ipu_parse_sensor_fwnode_t parse_sensor_fwnode; char ipu_node_name[ACPI_ID_LEN]; struct software_node ipu_hid_node; @@ -169,11 +207,13 @@ struct ipu_bridge { }; #if IS_ENABLED(CONFIG_IPU_BRIDGE) +struct pci_dev *ipu_bridge_get_ipu6(void); int ipu_bridge_init(struct device *dev, ipu_parse_sensor_fwnode_t parse_sensor_fwnode); int ipu_bridge_parse_ssdb(struct acpi_device *adev, struct ipu_sensor *sensor); int ipu_bridge_instantiate_vcm(struct device *sensor); #else +static inline struct pci_dev *ipu_bridge_get_ipu6(void) { return NULL; } /* Use a define to avoid the @parse_sensor_fwnode argument getting evaluated */ #define ipu_bridge_init(dev, parse_sensor_fwnode) (0) static inline int ipu_bridge_instantiate_vcm(struct device *s) { return 0; } diff --git a/include/media/ipu6-pci-table.h b/include/media/ipu6-pci-table.h index 0899d9d2f978..cacb8a3170d0 100644 --- a/include/media/ipu6-pci-table.h +++ b/include/media/ipu6-pci-table.h @@ -14,6 +14,8 @@ #define PCI_DEVICE_ID_INTEL_IPU6EP_ADLN 0x462e #define PCI_DEVICE_ID_INTEL_IPU6EP_RPLP 0xa75d #define PCI_DEVICE_ID_INTEL_IPU6EP_MTL 0x7d19 +#define PCI_DEVICE_ID_INTEL_IPU7 0x645d +#define PCI_DEVICE_ID_INTEL_IPU7P5 0xb05d static const struct pci_device_id ipu6_pci_tbl[] = { { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU6) }, @@ -22,6 +24,8 @@ static const struct pci_device_id ipu6_pci_tbl[] = { { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU6EP_ADLN) }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU6EP_RPLP) }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU6EP_MTL) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU7) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU7P5) }, { } }; diff --git a/include/media/rc-map.h b/include/media/rc-map.h index d95ed3e96de2..f167c37179c8 100644 --- a/include/media/rc-map.h +++ b/include/media/rc-map.h @@ -148,7 +148,6 @@ struct rc_map_table { * @scan: pointer to struct &rc_map_table * @size: Max number of entries * @len: Number of entries that are in use - * @alloc: size of \*scan, in bytes * @rc_proto: type of the remote controller protocol, as defined at * enum &rc_proto * @name: name of the key map table @@ -158,7 +157,6 @@ struct rc_map { struct rc_map_table *scan; unsigned int size; unsigned int len; - unsigned int alloc; enum rc_proto rc_proto; const char *name; spinlock_t lock; diff --git a/include/media/v4l2-common.h b/include/media/v4l2-common.h index edd416178c33..33f5713734cb 100644 --- a/include/media/v4l2-common.h +++ b/include/media/v4l2-common.h @@ -554,15 +554,80 @@ static inline bool v4l2_is_format_bayer(const struct v4l2_format_info *f) const struct v4l2_format_info *v4l2_format_info(u32 format); void v4l2_apply_frmsize_constraints(u32 *width, u32 *height, const struct v4l2_frmsize_stepwise *frmsize); -int v4l2_fill_pixfmt(struct v4l2_pix_format *pixfmt, u32 pixelformat, - u32 width, u32 height); -int v4l2_fill_pixfmt_mp(struct v4l2_pix_format_mplane *pixfmt, u32 pixelformat, - u32 width, u32 height); -/* @stride_alignment is a power of 2 value in bytes */ + +/** + * v4l2_fill_pixfmt_aligned - Fill in a &struct v4l2_pix_format with stride + * alignment requirements + * + * @pixfmt: pointer to the &struct v4l2_pix_format to be filled + * @pixelformat: the V4L2 pixel format (V4L2_PIX_FMT_*) + * @width: image width in pixels + * @height: image height in pixels + * @stride_alignment: stride alignment in bytes, must be a power of 2 + * + * Fills all fields of @pixfmt for the given pixel format, dimensions, and + * stride alignment. Only formats stored in a single memory plane are + * supported; returns -EINVAL for multi-memory-plane formats. + * + * @pixfmt->bytesperline is set to the stride of the primary (plane 0) plane, + * rounded up to a multiple of @stride_alignment. For formats that store + * multiple component planes in a single memory buffer (e.g. YUV420), the + * alignment applied to each component plane's stride is scaled relative to + * @stride_alignment so that the chroma stride remains consistently derivable + * from the luma stride. @pixfmt->bytesperline therefore reflects only the + * primary plane stride. + * + * @pixfmt->sizeimage is set to the total size in bytes of all planes. + * + * Return: 0 on success, -EINVAL if @pixelformat is unknown or uses multiple + * memory planes. + */ +int v4l2_fill_pixfmt_aligned(struct v4l2_pix_format *pixfmt, u32 pixelformat, + u32 width, u32 height, u8 stride_alignment); + +static inline int v4l2_fill_pixfmt(struct v4l2_pix_format *pixfmt, + u32 pixelformat, u32 width, u32 height) +{ + return v4l2_fill_pixfmt_aligned(pixfmt, pixelformat, width, height, 1); +} + +/** + * v4l2_fill_pixfmt_mp_aligned - Fill in a &struct v4l2_pix_format_mplane with + * stride alignment requirements. + * + * @pixfmt: pointer to the &struct v4l2_pix_format_mplane to be filled + * @pixelformat: the V4L2 pixel format (V4L2_PIX_FMT_*) + * @width: image width in pixels + * @height: image height in pixels + * @stride_alignment: stride alignment in bytes; must be a power of 2 + * + * Fills all fields of @pixfmt for the given pixel format, dimensions, and + * stride alignment. + * + * For formats stored in a single memory plane (mem_planes == 1), the + * behaviour matches v4l2_fill_pixfmt_aligned(): plane_fmt[0].bytesperline + * is set to the primary plane stride. The strides of all components are + * aligned to the @stride_alignment. To keep the chroma strides consistently + * derivable from the luma stride, strides may be aligned to a multiple of + * the @stride_alignment instead. plane_fmt[0].sizeimage covers all + * component planes. + * + * For formats with multiple memory planes (mem_planes > 1), each plane's + * bytesperline is independently rounded up to @stride_alignment, and each + * plane's sizeimage is set to bytesperline multiplied by the plane height. + * + * Return: 0 on success, -EINVAL if @pixelformat is unknown. + */ int v4l2_fill_pixfmt_mp_aligned(struct v4l2_pix_format_mplane *pixfmt, u32 pixelformat, u32 width, u32 height, u8 stride_alignment); +static inline int v4l2_fill_pixfmt_mp(struct v4l2_pix_format_mplane *pixfmt, + u32 pixelformat, u32 width, u32 height) +{ + return v4l2_fill_pixfmt_mp_aligned(pixfmt, pixelformat, width, height, 1); +} + /** * v4l2_get_link_freq - Get link rate from transmitter * diff --git a/include/media/v4l2-ctrls.h b/include/media/v4l2-ctrls.h index 327976b14d50..cec9217d97ac 100644 --- a/include/media/v4l2-ctrls.h +++ b/include/media/v4l2-ctrls.h @@ -834,6 +834,9 @@ bool v4l2_ctrl_radio_filter(const struct v4l2_ctrl *ctrl); * * @ncontrols: The number of controls in this cluster. * @controls: The cluster control array of size @ncontrols. + * + * If controls[0] is NULL, then this function does nothing and just + * returns. */ void v4l2_ctrl_cluster(unsigned int ncontrols, struct v4l2_ctrl **controls); @@ -845,6 +848,9 @@ void v4l2_ctrl_cluster(unsigned int ncontrols, struct v4l2_ctrl **controls); * @ncontrols: The number of controls in this cluster. * @controls: The cluster control array of size @ncontrols. The first control * must be the 'auto' control (e.g. autogain, autoexposure, etc.) + * + * If controls[0] is NULL, then this function does nothing and just + * returns. * @manual_val: The value for the first control in the cluster that equals the * manual setting. * @set_volatile: If true, then all controls except the first auto control will diff --git a/include/media/v4l2-dv-timings.h b/include/media/v4l2-dv-timings.h index 2b42e5d81f9e..de5ef825a6c6 100644 --- a/include/media/v4l2-dv-timings.h +++ b/include/media/v4l2-dv-timings.h @@ -252,6 +252,20 @@ v4l2_hdmi_rx_colorimetry(const struct hdmi_avi_infoframe *avi, const struct hdmi_vendor_infoframe *hdmi, unsigned int height); +/* + * The time in milliseconds that the HPD should be pulled low when writing + * a new EDID. This will tell the HDMI source that the EDID was changed and + * that it has to be re-read. + * + * The source is supposed to re-read the EDID if the HPD is low for more than + * 100 ms, but in practice the sink should pull it low for a bit longer due + * to clock differences and imprecise video source implementations. + * + * Practice has shown that setting the delay to HZ / 7 (approx 143 ms) works + * well. + */ +#define V4L2_SET_EDID_HPD_LOW_JIFFIES (HZ / 7) + unsigned int v4l2_num_edid_blocks(const u8 *edid, unsigned int max_blocks); u16 v4l2_get_edid_phys_addr(const u8 *edid, unsigned int size, unsigned int *offset); diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h index d256b7ec8f84..5796e52678b9 100644 --- a/include/media/v4l2-subdev.h +++ b/include/media/v4l2-subdev.h @@ -735,6 +735,14 @@ struct v4l2_subdev_state { }; /** + * struct v4l2_subdev_client_info - Sub-device client information + * @caps: bitmask of ``V4L2_SUBDEV_CLIENT_CAP_*`` + */ +struct v4l2_subdev_client_info { + u64 caps; +}; + +/** * struct v4l2_subdev_pad_ops - v4l2-subdev pad level operations * * @enum_mbus_code: callback for VIDIOC_SUBDEV_ENUM_MBUS_CODE() ioctl handler @@ -747,11 +755,14 @@ struct v4l2_subdev_state { * * @get_fmt: callback for VIDIOC_SUBDEV_G_FMT() ioctl handler code. * - * @set_fmt: callback for VIDIOC_SUBDEV_S_FMT() ioctl handler code. + * @set_fmt: callback for VIDIOC_SUBDEV_S_FMT() ioctl handler code. The ci + * pointer may be NULL for in-kernel calls. * * @get_selection: callback for VIDIOC_SUBDEV_G_SELECTION() ioctl handler code. + * The ci pointer may be NULL for in-kernel calls. * * @set_selection: callback for VIDIOC_SUBDEV_S_SELECTION() ioctl handler code. + * The ci pointer may be NULL for in-kernel calls. * * @get_frame_interval: callback for VIDIOC_SUBDEV_G_FRAME_INTERVAL() * ioctl handler code. @@ -814,6 +825,11 @@ struct v4l2_subdev_state { * V4L2_SUBDEV_CAP_STREAMS sub-device capability flag can ignore the mask * argument. * + * Due to device constraints, starting the requested streams may result in + * additional streams also being started by the driver. Streams that are + * started and stopped together due to the nature of the hardware are + * called a stream group. + * * @disable_streams: Disable the streams defined in streams_mask on the given * source pad. Subdevs that implement this operation must use the active * state management provided by the subdev core (enabled through a call to @@ -823,6 +839,9 @@ struct v4l2_subdev_state { * Drivers that support only a single stream without setting the * V4L2_SUBDEV_CAP_STREAMS sub-device capability flag can ignore the mask * argument. + * + * When the requested stream are part of a stream group, they will be + * stopped once all streams in the group are stopped. */ struct v4l2_subdev_pad_ops { int (*enum_mbus_code)(struct v4l2_subdev *sd, @@ -838,12 +857,15 @@ struct v4l2_subdev_pad_ops { struct v4l2_subdev_state *state, struct v4l2_subdev_format *format); int (*set_fmt)(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format); int (*get_selection)(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel); int (*set_selection)(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel); int (*get_frame_interval)(struct v4l2_subdev *sd, @@ -1122,14 +1144,14 @@ struct v4l2_subdev { * @vfh: pointer to &struct v4l2_fh * @state: pointer to &struct v4l2_subdev_state * @owner: module pointer to the owner of this file handle - * @client_caps: bitmask of ``V4L2_SUBDEV_CLIENT_CAP_*`` + * @ci: sub-device client info related to this file handle */ struct v4l2_subdev_fh { struct v4l2_fh vfh; struct module *owner; #if defined(CONFIG_VIDEO_V4L2_SUBDEV_API) struct v4l2_subdev_state *state; - u64 client_caps; + struct v4l2_subdev_client_info ci; #endif }; @@ -1935,14 +1957,16 @@ extern const struct v4l2_subdev_ops v4l2_subdev_call_wrappers; int __result; \ if (!__sd) \ __result = -ENODEV; \ - else if (!(__sd->ops->o && __sd->ops->o->f)) \ + else if (!__sd->ops->o) \ __result = -ENOIOCTLCMD; \ else if (v4l2_subdev_call_wrappers.o && \ v4l2_subdev_call_wrappers.o->f) \ __result = v4l2_subdev_call_wrappers.o->f( \ __sd, ##args); \ - else \ + else if (__sd->ops->o->f) \ __result = __sd->ops->o->f(__sd, ##args); \ + else \ + __result = -ENOIOCTLCMD; \ __result; \ }) diff --git a/include/uapi/linux/it6625.h b/include/uapi/linux/it6625.h new file mode 100644 index 000000000000..a895658aaf6a --- /dev/null +++ b/include/uapi/linux/it6625.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +/* + * Controls header for IT6625/IT6626 driver + */ + +#ifndef _UAPI_LINUX_IT6625_H +#define _UAPI_LINUX_IT6625_H + +#include <linux/v4l2-controls.h> + +/* + * Currently detected HDMI audio sampling rate, in Hz. Read-only. + * 0 means the rate is unavailable/unknown: no audio is currently + * present on the input, the hardware reported a sample-rate id this + * driver doesn't recognize, or the status read itself failed. Never a + * literal 0 Hz sample rate. + */ +#define V4L2_CID_IT6625_AUDIO_SAMPLING_RATE (V4L2_CID_USER_IT6625_BASE + 0) + +/* + * Whether HDMI audio is currently present on the input. Read-only. + */ +#define V4L2_CID_IT6625_AUDIO_PRESENT (V4L2_CID_USER_IT6625_BASE + 1) + +#endif /* _UAPI_LINUX_IT6625_H */ diff --git a/include/uapi/linux/media/st/dcmipp_config.h b/include/uapi/linux/media/st/dcmipp_config.h new file mode 100644 index 000000000000..ad8d146820ea --- /dev/null +++ b/include/uapi/linux/media/st/dcmipp_config.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * ST DCMIPP Driver - Userspace API + * + * Copyright (C) STMicroelectronics SA 2026 + */ + +#ifndef __UAPI_DCMIPP_CONFIG_H +#define __UAPI_DCMIPP_CONFIG_H + +#include <linux/types.h> +#include <linux/v4l2-controls.h> + +#define V4L2_CID_DCMIPP_PIXELPROC_GAMMA_CORRECTION_ENABLE (V4L2_CID_USER_DCMIPP_BASE + 0x0) + +#endif /* __UAPI_DCMIPP_CONFIG_H */ diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h index affec0ab4781..d17e41d51d2e 100644 --- a/include/uapi/linux/v4l2-controls.h +++ b/include/uapi/linux/v4l2-controls.h @@ -123,6 +123,15 @@ enum v4l2_colorfx { #define V4L2_CID_USER_MEYE_BASE (V4L2_CID_USER_BASE + 0x1000) #endif +/* + * The base for the vim2m driver controls. + * We reserve 16 controls for this driver. + * The vim2m control range clashed with the meye control range, which was not + * intended, but since the meye driver has been removed, we can just keep the + * vim2m control range. + */ +#define V4L2_CID_USER_VIM2M_BASE (V4L2_CID_USER_BASE + 0x1000) + /* The base for the bttv driver controls. * We reserve 32 controls for this driver. */ #define V4L2_CID_USER_BTTV_BASE (V4L2_CID_USER_BASE + 0x1010) @@ -234,6 +243,18 @@ enum v4l2_colorfx { */ #define V4L2_CID_USER_MALI_C55_BASE (V4L2_CID_USER_BASE + 0x1230) +/* + * The base for the ST DCMIPP driver controls. + * We reserve 16 controls for this driver + */ +#define V4L2_CID_USER_DCMIPP_BASE (V4L2_CID_USER_BASE + 0x1240) + +/* + * The base for IT6625/IT6626 driver controls. + * We reserve 16 controls for this driver. + */ +#define V4L2_CID_USER_IT6625_BASE (V4L2_CID_USER_BASE + 0x1250) + /* MPEG-class control IDs */ /* The MPEG controls are applicable to all codec controls * and the 'MPEG' part of the define is historical */ |
