diff --git a/Documentation/devicetree/bindings/arm/rockchip.yaml b/Documentation/devicetree/bindings/arm/rockchip.yaml index 1a9dde18626d00..671b4d04350461 100644 --- a/Documentation/devicetree/bindings/arm/rockchip.yaml +++ b/Documentation/devicetree/bindings/arm/rockchip.yaml @@ -299,6 +299,11 @@ properties: - const: firefly,rk3568-roc-pc - const: rockchip,rk3568 + - description: Flipper One rev. F0B0C1 + items: + - const: flipper,one-rev-f0b0c1 + - const: rockchip,rk3576 + - description: Forlinx FET3588-C SoM items: - enum: diff --git a/Documentation/devicetree/bindings/display/flipper,one-display.yaml b/Documentation/devicetree/bindings/display/flipper,one-display.yaml new file mode 100644 index 00000000000000..0c5f7c59975f37 --- /dev/null +++ b/Documentation/devicetree/bindings/display/flipper,one-display.yaml @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/flipper,one-display.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Flipper One display + +maintainers: + - Alexey Charkov + +description: + Flipper One is a handheld network multi-tool, which includes a custom + grayscale LCD panel driven by an MCU accepting image contents over a SPI bus. + The image data must consist of a full frame in each transmission, signalled + by the "active" GPIO line. One byte per pixel is used, with 0 representing + black and 255 white, lower two bits are discarded, and the expected frame + size is 258x144 pixels of which the leftmost 256x144 are visible. + + The MCU expects SPI mode 3 (CPOL=1, CPHA=1), up to 24MHz frequency. + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +properties: + compatible: + enum: + - flipper,one-display + + reg: + maxItems: 1 + + spi-cpha: true + spi-cpol: true + + spi-max-frequency: + maximum: 24000000 + + active-gpios: + maxItems: 1 + description: Frame data is being transmitted when this line is asserted. + Deassert to signal the end of frame. + +required: + - compatible + - reg + - active-gpios + - spi-max-frequency + +unevaluatedProperties: false + +examples: + - | + #include + spi { + #address-cells = <1>; + #size-cells = <0>; + + display@0 { + compatible = "flipper,one-display"; + reg = <0>; + active-gpios = <&gpio4 6 GPIO_ACTIVE_LOW>; + spi-max-frequency = <20000000>; + spi-cpha; + spi-cpol; + spi-rx-bus-width = <0>; + }; + }; +... diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml index 2b0d9e23e9432f..1303d0e2145a97 100644 --- a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml +++ b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml @@ -83,7 +83,8 @@ properties: maxItems: 1 "#sound-dai-cells": - const: 0 + const: 1 + description: 0 for I2S, 1 for SPDIF required: - compatible @@ -144,7 +145,7 @@ examples: resets = <&cru SRST_DP0>; phys = <&usbdp_phy0 PHY_TYPE_DP>; power-domains = <&power RK3588_PD_VO0>; - #sound-dai-cells = <0>; + #sound-dai-cells = <1>; ports { #address-cells = <1>; diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip-vop2.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip-vop2.yaml index 93da1fb9adc47b..8a8993fea896dc 100644 --- a/Documentation/devicetree/bindings/display/rockchip/rockchip-vop2.yaml +++ b/Documentation/devicetree/bindings/display/rockchip/rockchip-vop2.yaml @@ -82,6 +82,26 @@ properties: - {} - {} + resets: + minItems: 5 + items: + - description: AXI clock reset. + - description: AHB clock reset. + - description: Pixel clock reset for video port 0. + - description: Pixel clock reset for video port 1. + - description: Pixel clock reset for video port 2. + - description: Pixel clock reset for video port 3. + + reset-names: + minItems: 5 + items: + - const: aclk + - const: hclk + - const: dclk_vp0 + - const: dclk_vp1 + - const: dclk_vp2 + - const: dclk_vp3 + rockchip,grf: $ref: /schemas/types.yaml#/definitions/phandle description: @@ -153,6 +173,12 @@ allOf: interrupt-names: false + resets: + maxItems: 5 + + reset-names: + maxItems: 5 + ports: required: - port@0 @@ -200,6 +226,12 @@ allOf: interrupt-names: minItems: 4 + resets: + maxItems: 5 + + reset-names: + maxItems: 5 + ports: required: - port@0 @@ -251,6 +283,12 @@ allOf: interrupt-names: false + resets: + minItems: 6 + + reset-names: + minItems: 6 + ports: required: - port@0 @@ -289,6 +327,16 @@ examples: "dclk_vp0", "dclk_vp1", "dclk_vp2"; + resets = <&cru SRST_A_VOP>, + <&cru SRST_H_VOP>, + <&cru SRST_VOP0>, + <&cru SRST_VOP1>, + <&cru SRST_VOP2>; + reset-names = "aclk", + "hclk", + "dclk_vp0", + "dclk_vp1", + "dclk_vp2"; power-domains = <&power RK3568_PD_VO>; rockchip,grf = <&grf>; iommus = <&vop_mmu>; diff --git a/Documentation/devicetree/bindings/hwmon/ti,ina4230.yaml b/Documentation/devicetree/bindings/hwmon/ti,ina4230.yaml new file mode 100644 index 00000000000000..f33e52a12657fe --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/ti,ina4230.yaml @@ -0,0 +1,134 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/ti,ina4230.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments INA4230 quad-channel power monitors + +maintainers: + - Alexey Charkov + +description: | + The INA4230 is a 48V quad-channel 16-bit current, voltage, power and energy + monitor with an I2C interface. + + Datasheet: + https://www.ti.com/product/INA4230 + +properties: + compatible: + enum: + - ti,ina4230 + + reg: + maxItems: 1 + + "#address-cells": + description: Required only if a child node is present. + const: 1 + + "#size-cells": + description: Required only if a child node is present. + const: 0 + + vs-supply: + description: phandle to the regulator that provides the VS supply typically + in range from 1.7 V to 5.5 V. + + ti,alert-polarity-active-high: + description: Alert pin is asserted based on the value of Alert polarity Bit + of the CONFIG2 register. Default value is 0, for which the alert pin + toggles from high to low during faults. When this property is set, the + corresponding register bit is set to 1, and the alert pin toggles from + low to high during faults. + $ref: /schemas/types.yaml#/definitions/flag + +patternProperties: + "^input@[0-3]$": + description: The node contains optional child nodes for four channels. + Each child node describes the information of input source. Input channels + default to enabled in the chip. Unless channels are explicitly disabled + in device-tree, input channels will be enabled. + type: object + additionalProperties: false + properties: + reg: + description: Must be 0, 1, 2 or 3, corresponding to the IN1, IN2, IN3 + or IN4 ports of the INA4230, respectively. + enum: [ 0, 1, 2, 3 ] + + label: + description: name of the input source + + shunt-resistor-micro-ohms: + description: shunt resistor value in micro-Ohm + + ti,maximum-expected-current-microamp: + description: | + This value indicates the maximum current in microamps that you can + expect to measure with ina4230 in your circuit. + + This value will be used to calculate the Current_LSB to maximize the + available precision while ensuring your expected maximum current fits + within the chip's ADC range. It will also enable built-in shunt gain + to increase ADC granularity by a factor of 4 if the provided maximum + current / shunt resistance combination does not produce more than + 20.48 mV drop at the shunt. + minimum: 32768 + maximum: 4294967295 + default: 32768000 + + required: + - reg + +required: + - compatible + - reg + +allOf: + - $ref: hwmon-common.yaml# + +unevaluatedProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + power-sensor@44 { + compatible = "ti,ina4230"; + reg = <0x44>; + vs-supply = <&vdd_3v0>; + ti,alert-polarity-active-high; + #address-cells = <1>; + #size-cells = <0>; + + input@0 { + reg = <0x0>; + /* + * Input channels are enabled by default in the device and so + * to disable, must be explicitly disabled in device-tree. + */ + status = "disabled"; + }; + + input@1 { + reg = <0x1>; + shunt-resistor-micro-ohms = <50000>; + ti,maximum-expected-current-microamp = <300000>; + }; + + input@2 { + reg = <0x2>; + label = "VDD_5V"; + shunt-resistor-micro-ohms = <10000>; + ti,maximum-expected-current-microamp = <5000000>; + }; + + input@3 { + reg = <0x3>; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/input/adc-keys.yaml b/Documentation/devicetree/bindings/input/adc-keys.yaml index 7aa078dead3781..e372ebc23d1651 100644 --- a/Documentation/devicetree/bindings/input/adc-keys.yaml +++ b/Documentation/devicetree/bindings/input/adc-keys.yaml @@ -42,6 +42,9 @@ patternProperties: linux,code: true + linux,input-type: + default: 1 # EV_KEY + press-threshold-microvolt: description: Voltage above or equal to which this key is considered pressed. No diff --git a/Documentation/devicetree/bindings/iommu/verisilicon,iommu.yaml b/Documentation/devicetree/bindings/iommu/verisilicon,iommu.yaml new file mode 100644 index 00000000000000..d3ce9e603b61d7 --- /dev/null +++ b/Documentation/devicetree/bindings/iommu/verisilicon,iommu.yaml @@ -0,0 +1,71 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iommu/verisilicon,iommu.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Verisilicon IOMMU + +maintainers: + - Benjamin Gaignard + +description: |+ + A Versilicon iommu translates io virtual addresses to physical addresses for + its associated video decoder. + +properties: + compatible: + items: + - const: rockchip,rk3588-av1-iommu + - const: verisilicon,iommu-1.2 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + items: + - description: Core clock + - description: Interface clock + + clock-names: + items: + - const: core + - const: iface + + "#iommu-cells": + const: 0 + + power-domains: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + - "#iommu-cells" + +additionalProperties: false + +examples: + - | + #include + #include + + bus { + #address-cells = <2>; + #size-cells = <2>; + + iommu@fdca0000 { + compatible = "rockchip,rk3588-av1-iommu","verisilicon,iommu-1.2"; + reg = <0x0 0xfdca0000 0x0 0x600>; + interrupts = ; + clocks = <&cru ACLK_AV1>, <&cru PCLK_AV1>; + clock-names = "core", "iface"; + #iommu-cells = <0>; + }; + }; diff --git a/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml b/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml index 4ac4a3b6f40640..836c1301d05f42 100644 --- a/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml +++ b/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml @@ -16,9 +16,14 @@ description: properties: compatible: - enum: - - fsl,imx93-mipi-csi2 - - rockchip,rk3568-mipi-csi2 + oneOf: + - const: fsl,imx93-mipi-csi2 + - items: + - enum: + - rockchip,rk3576-mipi-csi2 + - rockchip,rk3588-mipi-csi2 + - const: rockchip,rk3568-mipi-csi2 + - const: rockchip,rk3568-mipi-csi2 reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/media/rockchip,rk3588-vicap.yaml b/Documentation/devicetree/bindings/media/rockchip,rk3588-vicap.yaml new file mode 100644 index 00000000000000..5ffd41054491c6 --- /dev/null +++ b/Documentation/devicetree/bindings/media/rockchip,rk3588-vicap.yaml @@ -0,0 +1,255 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/rockchip,rk3588-vicap.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Rockchip RK3576 and RK3588 Video Capture (VICAP) + +maintainers: + - Michael Riesch + +description: + The Rockchip Video Capture (VICAP) block receives data from camera sensors, + video decoders, or other companion ICs and transfers it into system main + memory by AXI bus and/or passes it to the image signal processing (ISP) + blocks. On RK3588 it features a digital video port (DVP, a parallel video + interface) and six MIPI CSI-2 ports. RK3576 has no DVP and five MIPI CSI-2 + ports. + +properties: + compatible: + enum: + - rockchip,rk3576-vicap + - rockchip,rk3588-vicap + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + items: + - description: ACLK + - description: HCLK + - description: DCLK + - description: ICLK0 + - description: ICLK1 + + clock-names: + items: + - const: aclk + - const: hclk + - const: dclk + - const: iclk_host0 + - const: iclk_host1 + + iommus: + maxItems: 1 + + resets: + maxItems: 9 + + rockchip,grf: + $ref: /schemas/types.yaml#/definitions/phandle + description: Phandle to general register file used for video input block control. + + power-domains: + maxItems: 1 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: + The digital video port (DVP, a parallel video interface). RK3588 only. + + properties: + endpoint: + $ref: video-interfaces.yaml# + unevaluatedProperties: false + + properties: + bus-type: + enum: [5, 6] + + required: + - bus-type + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: Port connected to the MIPI CSI-2 receiver 0 output. + + properties: + endpoint: + $ref: video-interfaces.yaml# + unevaluatedProperties: false + + port@2: + $ref: /schemas/graph.yaml#/properties/port + description: Port connected to the MIPI CSI-2 receiver 1 output. + + properties: + endpoint: + $ref: video-interfaces.yaml# + unevaluatedProperties: false + + port@3: + $ref: /schemas/graph.yaml#/properties/port + description: Port connected to the MIPI CSI-2 receiver 2 output. + + properties: + endpoint: + $ref: video-interfaces.yaml# + unevaluatedProperties: false + + port@4: + $ref: /schemas/graph.yaml#/properties/port + description: Port connected to the MIPI CSI-2 receiver 3 output. + + properties: + endpoint: + $ref: video-interfaces.yaml# + unevaluatedProperties: false + + port@5: + $ref: /schemas/graph.yaml#/properties/port + description: Port connected to the MIPI CSI-2 receiver 4 output. + + properties: + endpoint: + $ref: video-interfaces.yaml# + unevaluatedProperties: false + + port@6: + $ref: /schemas/graph.yaml#/properties/port + description: + Port connected to the MIPI CSI-2 receiver 5 output. RK3588 only. + + properties: + endpoint: + $ref: video-interfaces.yaml# + unevaluatedProperties: false + +required: + - compatible + - reg + - interrupts + - clocks + - ports + +allOf: + - if: + properties: + compatible: + contains: + const: rockchip,rk3576-vicap + then: + properties: + resets: + maxItems: 8 + ports: + properties: + port@0: false + port@6: false + +additionalProperties: false + +examples: + - | + #include + #include + #include + #include + #include + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + vicap: video-capture@fdce0000 { + compatible = "rockchip,rk3588-vicap"; + reg = <0x0 0xfdce0000 0x0 0x800>; + interrupts = ; + clocks = <&cru ACLK_VICAP>, <&cru HCLK_VICAP>, + <&cru DCLK_VICAP>, <&cru ICLK_CSIHOST0>, + <&cru ICLK_CSIHOST1>; + clock-names = "aclk", "hclk", "dclk", "iclk_host0", "iclk_host1"; + iommus = <&vicap_mmu>; + power-domains = <&power RK3588_PD_VI>; + resets = <&cru SRST_A_VICAP>, <&cru SRST_H_VICAP>, + <&cru SRST_D_VICAP>, <&cru SRST_CSIHOST0_VICAP>, + <&cru SRST_CSIHOST1_VICAP>, <&cru SRST_CSIHOST2_VICAP>, + <&cru SRST_CSIHOST3_VICAP>, <&cru SRST_CSIHOST4_VICAP>, + <&cru SRST_CSIHOST5_VICAP>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + vicap_dvp: port@0 { + reg = <0>; + + vicap_dvp_input: endpoint { + bus-type = ; + bus-width = <16>; + pclk-sample = ; + remote-endpoint = <&it6801_output>; + }; + }; + + vicap_mipi0: port@1 { + reg = <1>; + + vicap_mipi0_input: endpoint { + remote-endpoint = <&csi0_output>; + }; + }; + + vicap_mipi1: port@2 { + reg = <2>; + + vicap_mipi1_input: endpoint { + remote-endpoint = <&csi1_output>; + }; + }; + + vicap_mipi2: port@3 { + reg = <3>; + + vicap_mipi2_input: endpoint { + remote-endpoint = <&csi2_output>; + }; + }; + + vicap_mipi3: port@4 { + reg = <4>; + + vicap_mipi3_input: endpoint { + remote-endpoint = <&csi3_output>; + }; + }; + + vicap_mipi4: port@5 { + reg = <5>; + + vicap_mipi4_input: endpoint { + remote-endpoint = <&csi4_output>; + }; + }; + + vicap_mipi5: port@6 { + reg = <6>; + + vicap_mipi5_input: endpoint { + remote-endpoint = <&csi5_output>; + }; + }; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/mfd/ti,bq25703a.yaml b/Documentation/devicetree/bindings/mfd/ti,bq25703a.yaml index ba14663c9266a5..cdce83f0580415 100644 --- a/Documentation/devicetree/bindings/mfd/ti,bq25703a.yaml +++ b/Documentation/devicetree/bindings/mfd/ti,bq25703a.yaml @@ -4,17 +4,16 @@ $id: http://devicetree.org/schemas/mfd/ti,bq25703a.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: BQ25703A Charger Manager/Buck/Boost Converter +title: BQ257xx Charger Manager/Buck/Boost Converter maintainers: - Chris Morgan -allOf: - - $ref: /schemas/power/supply/power-supply.yaml# - properties: compatible: - const: ti,bq25703a + enum: + - ti,bq25703a + - ti,bq25792 reg: const: 0x6b @@ -25,7 +24,6 @@ properties: powering the device. minimum: 50000 maximum: 6400000 - default: 3250000 interrupts: maxItems: 1 @@ -57,11 +55,11 @@ properties: minimum: 0 maximum: 6350000 regulator-min-microvolt: - minimum: 4480000 - maximum: 20800000 + minimum: 2800000 + maximum: 22000000 regulator-max-microvolt: - minimum: 4480000 - maximum: 20800000 + minimum: 2800000 + maximum: 22000000 enable-gpios: description: The BQ25703 may require both a register write and a GPIO @@ -74,6 +72,61 @@ properties: - regulator-min-microvolt - regulator-max-microvolt +allOf: + - $ref: /schemas/power/supply/power-supply.yaml# + - if: + properties: + compatible: + const: ti,bq25703a + then: + properties: + input-current-limit-microamp: + minimum: 50000 + maximum: 6400000 + default: 3250000 + regulators: + properties: + vbus: + properties: + regulator-min-microamp: + minimum: 0 + maximum: 6350000 + regulator-max-microamp: + minimum: 0 + maximum: 6350000 + regulator-min-microvolt: + minimum: 4480000 + maximum: 20800000 + regulator-max-microvolt: + minimum: 4480000 + maximum: 20800000 + - if: + properties: + compatible: + const: ti,bq25792 + then: + properties: + input-current-limit-microamp: + minimum: 100000 + maximum: 3300000 + default: 3000000 + regulators: + properties: + vbus: + properties: + regulator-min-microamp: + minimum: 0 + maximum: 3320000 + regulator-max-microamp: + minimum: 0 + maximum: 3320000 + regulator-min-microvolt: + minimum: 2800000 + maximum: 22000000 + regulator-max-microvolt: + minimum: 2800000 + maximum: 22000000 + unevaluatedProperties: false required: diff --git a/Documentation/devicetree/bindings/phy/phy-rockchip-usbdp.yaml b/Documentation/devicetree/bindings/phy/phy-rockchip-usbdp.yaml index 8b7059d5b1826f..f728acf057e404 100644 --- a/Documentation/devicetree/bindings/phy/phy-rockchip-usbdp.yaml +++ b/Documentation/devicetree/bindings/phy/phy-rockchip-usbdp.yaml @@ -114,6 +114,29 @@ properties: A port node to link the PHY to a TypeC controller for the purpose of handling orientation switching. + ports: + $ref: /schemas/graph.yaml#/properties/ports + properties: + port@0: + $ref: /schemas/graph.yaml#/properties/port + description: + Output endpoint of the PHY for USB (or DP when configured into 4 lane + mode), which should point to the superspeed port of a USB connector. + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: Incoming endpoint from the USB controller + + port@2: + $ref: /schemas/graph.yaml#/properties/port + description: Incoming endpoint from the DisplayPort controller + + port@3: + $ref: /schemas/graph.yaml#/properties/port + description: + Output endpoint of the PHY for DP, which should either point to the + SBU port of a USB-C connector or a DisplayPort connector input port. + required: - compatible - reg diff --git a/Documentation/devicetree/bindings/phy/rockchip-inno-csi-dphy.yaml b/Documentation/devicetree/bindings/phy/rockchip-inno-csi-dphy.yaml index 03950b3cad08c1..243863d62d8312 100644 --- a/Documentation/devicetree/bindings/phy/rockchip-inno-csi-dphy.yaml +++ b/Documentation/devicetree/bindings/phy/rockchip-inno-csi-dphy.yaml @@ -21,6 +21,7 @@ properties: - rockchip,rk3326-csi-dphy - rockchip,rk3368-csi-dphy - rockchip,rk3568-csi-dphy + - rockchip,rk3576-csi-dphy - rockchip,rk3588-csi-dphy reg: diff --git a/Documentation/devicetree/bindings/pwm/rockchip,rk3576-pwm.yaml b/Documentation/devicetree/bindings/pwm/rockchip,rk3576-pwm.yaml new file mode 100644 index 00000000000000..48d5055c8b069f --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/rockchip,rk3576-pwm.yaml @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pwm/rockchip,rk3576-pwm.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Rockchip PWMv4 controller + +maintainers: + - Nicolas Frattaroli + +description: | + The Rockchip PWMv4 controller is a PWM controller found on several Rockchip + SoCs, such as the RK3576. + + It supports both generating and capturing PWM signals. + +allOf: + - $ref: pwm.yaml# + +properties: + compatible: + items: + - const: rockchip,rk3576-pwm + + reg: + maxItems: 1 + + clocks: + items: + - description: Used to derive the PWM signal. + - description: Used as the APB bus clock. + - description: Used as an alternative to derive the PWM signal. + - description: Used as another alternative to derive the PWM signal. + + clock-names: + items: + - const: pwm + - const: pclk + - const: osc + - const: rc + + interrupts: + maxItems: 1 + + "#pwm-cells": + const: 3 + +required: + - compatible + - reg + - clocks + - clock-names + - interrupts + +additionalProperties: false + +examples: + - | + #include + #include + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + pwm@2add0000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2add0000 0x0 0x1000>; + clocks = <&cru CLK_PWM1>, <&cru PCLK_PWM1>, <&cru CLK_OSC_PWM1>, + <&cru CLK_RC_PWM1>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + #pwm-cells = <3>; + }; + }; diff --git a/Documentation/devicetree/bindings/soc/rockchip/grf.yaml b/Documentation/devicetree/bindings/soc/rockchip/grf.yaml index 2cc43742b8e3b8..1dc4f0844517d7 100644 --- a/Documentation/devicetree/bindings/soc/rockchip/grf.yaml +++ b/Documentation/devicetree/bindings/soc/rockchip/grf.yaml @@ -32,6 +32,7 @@ properties: - rockchip,rk3568-usb2phy-grf - rockchip,rk3576-bigcore-grf - rockchip,rk3576-cci-grf + - rockchip,rk3576-csidphy-grf - rockchip,rk3576-dcphy-grf - rockchip,rk3576-gpu-grf - rockchip,rk3576-hdptxphy-grf diff --git a/Documentation/devicetree/bindings/sound/nuvoton,nau8822.yaml b/Documentation/devicetree/bindings/sound/nuvoton,nau8822.yaml index cb8182bbc491fc..0a8e40a140c3a9 100644 --- a/Documentation/devicetree/bindings/sound/nuvoton,nau8822.yaml +++ b/Documentation/devicetree/bindings/sound/nuvoton,nau8822.yaml @@ -30,6 +30,20 @@ properties: clock-names: const: mclk + vdda-supply: + description: Analog power supply + + vddb-supply: + description: Digital buffer (input/output) supply + + vddc-supply: + description: Digital core supply + + vddspk-supply: + description: + Speaker supply (power supply pin for RSPKOUT, LSPKOUT, AUXOUT2 and + AUXTOUT1 output drivers) + nuvoton,spk-btl: description: If set, configure the two loudspeaker outputs as a Bridge Tied Load output diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index 28784d66ae7ba5..021032422a782b 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -598,6 +598,8 @@ patternProperties: description: Fitipower Integrated Technology Inc. "^flipkart,.*": description: Flipkart Inc. + "^flipper,.*": + description: Flipper FZCO "^focaltech,.*": description: FocalTech Systems Co.,Ltd "^forlinx,.*": diff --git a/MAINTAINERS b/MAINTAINERS index e035a3be797c4d..37364291dea1d2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7982,6 +7982,10 @@ F: include/linux/aperture.h F: include/linux/sysfb.h F: include/video/nomodeset.h +DRM DRIVER FOR THE FLIPPER ONE DISPLAY +M: Alexey Charkov +F: drivers/gpu/drm/tiny/flipper-one-display.c + DRM DRIVER FOR GENERIC EDP PANELS R: Douglas Anderson F: Documentation/devicetree/bindings/display/panel/panel-edp.yaml @@ -12583,6 +12587,13 @@ S: Maintained F: Documentation/hwmon/ina233.rst F: drivers/hwmon/pmbus/ina233.c +INA4230 HWMON DRIVER +M: Alexey Charkov +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/hwmon/ti,ina4230.yaml +F: drivers/hwmon/ina4230.c + INDEX OF FURTHER KERNEL DOCUMENTATION M: Carlos Bilbao S: Maintained @@ -23125,6 +23136,7 @@ S: Maintained F: Documentation/admin-guide/media/rkcif* F: Documentation/devicetree/bindings/media/rockchip,px30-vip.yaml F: Documentation/devicetree/bindings/media/rockchip,rk3568-vicap.yaml +F: Documentation/devicetree/bindings/media/rockchip,rk3588-vicap.yaml F: drivers/media/platform/rockchip/rkcif/ ROCKCHIP CRYPTO DRIVERS @@ -23153,6 +23165,17 @@ F: Documentation/userspace-api/media/v4l/metafmt-rkisp1.rst F: drivers/media/platform/rockchip/rkisp1 F: include/uapi/linux/rkisp1-config.h +ROCKCHIP MFPWM +M: Nicolas Frattaroli +L: linux-rockchip@lists.infradead.org +L: linux-pwm@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/pwm/rockchip,rk3576-pwm.yaml +F: drivers/counter/rockchip-pwm-capture.c +F: drivers/mfd/rockchip-mfpwm.c +F: drivers/pwm/pwm-rockchip-v4.c +F: include/linux/mfd/rockchip-mfpwm.h + ROCKCHIP RK3568 RANDOM NUMBER GENERATOR SUPPORT M: Daniel Golle M: Aurelien Jarno @@ -27957,6 +27980,14 @@ F: drivers/media/v4l2-core/v4l2-isp.c F: include/media/v4l2-isp.h F: include/uapi/linux/media/v4l2-isp.h +VERISILICON IOMMU DRIVER +M: Benjamin Gaignard +L: iommu@lists.linux.dev +S: Maintained +F: Documentation/devicetree/bindings/iommu/verisilicon,iommu.yaml +F: drivers/iommu/vsi-iommu.c +F: include/linux/vsi-iommu.h + VF610 NAND DRIVER M: Stefan Agner L: linux-mtd@lists.infradead.org diff --git a/arch/arm64/boot/dts/rockchip/Makefile b/arch/arm64/boot/dts/rockchip/Makefile index cb55c6b70d0e56..a7ee53efc38fba 100644 --- a/arch/arm64/boot/dts/rockchip/Makefile +++ b/arch/arm64/boot/dts/rockchip/Makefile @@ -167,9 +167,13 @@ dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3568-wolfvision-pf5-io-expander.dtbo dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-100ask-dshanpi-a1.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-armsom-sige5.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-armsom-sige5-v1.2-wifibt.dtbo +dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-dp-4k-hdmi-2.5k.dtbo dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-evb1-v10.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-evb1-v10-pcie1.dtbo dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-evb2-v10.dtb +dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-flipper-one-rev-f0b0c1.dtb +dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-flipper-one-i2c2-free.dtbo +dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-flipper-one-sata.dtbo dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-khadas-edge-2l.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-luckfox-omni3576.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-nanopi-m5.dtb @@ -206,6 +210,8 @@ dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-rock-5b.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-rock-5b-pcie-ep.dtbo dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-rock-5b-pcie-srns.dtbo dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-rock-5b-plus.dtb +dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-rock-5b-plus-radxa-cam4k-cam0.dtbo +dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-rock-5b-plus-radxa-cam4k-cam1.dtbo dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-rock-5t.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-tiger-haikou.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-tiger-haikou-video-demo.dtbo @@ -289,14 +295,34 @@ rk3568-wolfvision-pf5-vz-2-uhd-dtbs := rk3568-wolfvision-pf5.dtb \ rk3568-wolfvision-pf5-display-vz.dtbo \ rk3568-wolfvision-pf5-io-expander.dtbo +dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-armsom-sige5-dp-4k-hdmi-2.5k.dtb +rk3576-armsom-sige5-dp-4k-hdmi-2.5k-dtbs := rk3576-armsom-sige5.dtb \ + rk3576-dp-4k-hdmi-2.5k.dtbo + dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-armsom-sige5-v1.2-wifibt.dtb rk3576-armsom-sige5-v1.2-wifibt-dtbs := rk3576-armsom-sige5.dtb \ rk3576-armsom-sige5-v1.2-wifibt.dtbo +dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-evb1-v10-dp-4k-hdmi-2.5k.dtb +rk3576-evb1-v10-dp-4k-hdmi-2.5k-dtbs := rk3576-evb1-v10.dtb \ + rk3576-dp-4k-hdmi-2.5k.dtbo + dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-evb1-v10-pcie1.dtb rk3576-evb1-v10-pcie1-dtbs := rk3576-evb1-v10.dtb \ rk3576-evb1-v10-pcie1.dtbo +dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-flipper-one-dp-4k-hdmi-2.5k.dtb +rk3576-flipper-one-dp-4k-hdmi-2.5k-dtbs := rk3576-flipper-one-rev-f0b0c1.dtb \ + rk3576-dp-4k-hdmi-2.5k.dtbo + +dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-flipper-one-i2c2-free.dtb +rk3576-flipper-one-i2c2-free-dtbs := rk3576-flipper-one-rev-f0b0c1.dtb \ + rk3576-flipper-one-i2c2-free.dtbo + +dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-flipper-one-sata.dtb +rk3576-flipper-one-sata-dtbs := rk3576-flipper-one-rev-f0b0c1.dtb \ + rk3576-flipper-one-sata.dtbo + dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-edgeble-neu6a-wifi.dtb rk3588-edgeble-neu6a-wifi-dtbs := rk3588-edgeble-neu6a-io.dtb \ rk3588-edgeble-neu6a-wifi.dtbo @@ -321,6 +347,11 @@ dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-rock-5b-pcie-srns.dtb rk3588-rock-5b-pcie-srns-dtbs := rk3588-rock-5b.dtb \ rk3588-rock-5b-pcie-srns.dtbo +dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-rock-5b-plus-radxa-4k-cam.dtb +rk3588-rock-5b-plus-radxa-4k-cam-dtbs := rk3588-rock-5b-plus.dtb \ + rk3588-rock-5b-plus-radxa-cam4k-cam0.dtbo \ + rk3588-rock-5b-plus-radxa-cam4k-cam1.dtbo + dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-tiger-haikou-haikou-video-demo.dtb rk3588-tiger-haikou-haikou-video-demo-dtbs := rk3588-tiger-haikou.dtb \ rk3588-tiger-haikou-video-demo.dtbo diff --git a/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi b/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi index 64bdd8b7754b5a..adb52e31c0c3fb 100644 --- a/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi @@ -638,6 +638,36 @@ #iommu-cells = <0>; }; + csi: csi@fdfb0000 { + compatible = "rockchip,rk3568-mipi-csi2"; + reg = <0x0 0xfdfb0000 0x0 0x10000>; + interrupts = , + ; + interrupt-names = "err1", "err2"; + clocks = <&cru PCLK_CSI2HOST1>; + phys = <&csi_dphy>; + power-domains = <&power RK3568_PD_VI>; + resets = <&cru SRST_P_CSI2HOST1>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + csi_in: port@0 { + reg = <0>; + }; + + csi_out: port@1 { + reg = <1>; + + csi_output: endpoint { + remote-endpoint = <&vicap_mipi_input>; + }; + }; + }; + }; + vicap: video-capture@fdfe0000 { compatible = "rockchip,rk3568-vicap"; reg = <0x0 0xfdfe0000 0x0 0x200>; @@ -666,6 +696,10 @@ vicap_mipi: port@1 { reg = <1>; + + vicap_mipi_input: endpoint { + remote-endpoint = <&csi_output>; + }; }; }; }; diff --git a/arch/arm64/boot/dts/rockchip/rk3576-100ask-dshanpi-a1.dts b/arch/arm64/boot/dts/rockchip/rk3576-100ask-dshanpi-a1.dts index b19f9b6be6bf8a..a9e3beb3ee5ee6 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-100ask-dshanpi-a1.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-100ask-dshanpi-a1.dts @@ -278,6 +278,9 @@ }; &hdmi { + frl-enable-gpios = <&gpio2 RK_PB0 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmi_txm0_pins &hdmi_tx_scl &hdmi_tx_sda &hdmi_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -738,6 +741,12 @@ }; }; + hdmi { + hdmi_tx_on_h: hdmi-tx-on-h { + rockchip,pins = <2 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + pcie { pcie_reset: pcie-reset { rockchip,pins = <1 RK_PB7 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts b/arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts index 1c100ffd151869..769842159b9bef 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts @@ -272,6 +272,26 @@ cpu-supply = <&vdd_cpu_lit_s0>; }; +&dp { + status = "okay"; +}; + +&dp0_in { + dp0_in_vp1: endpoint { + remote-endpoint = <&vp1_out_dp0>; + }; +}; + +&dp0_out { + dp0_out_con: endpoint { + remote-endpoint = <&usbdp_phy_dp_in>; + }; +}; + +&dp0_sound { + status = "okay"; +}; + &gmac0 { phy-mode = "rgmii-id"; clock_in_out = "output"; @@ -304,6 +324,9 @@ }; &hdmi { + frl-enable-gpios = <&gpio2 RK_PB0 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmi_txm0_pins &hdmi_tx_scl &hdmi_tx_sda &hdmi_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -685,6 +708,7 @@ }; &i2c2 { + clock-frequency = <400000>; status = "okay"; usbc0: typec-portc@22 { @@ -719,20 +743,22 @@ port@0 { reg = <0>; - usbc0_hs_ep: endpoint { + usbc0_hs: endpoint { remote-endpoint = <&usb_drd0_hs_ep>; }; }; + port@1 { reg = <1>; - usbc0_ss_ep: endpoint { - remote-endpoint = <&usb_drd0_ss_ep>; + usbc0_ss: endpoint { + remote-endpoint = <&usbdp_phy_ss_out>; }; }; + port@2 { reg = <2>; - usbc0_dp_ep: endpoint { - remote-endpoint = <&usbdp_phy_ep>; + usbc0_sbu: endpoint { + remote-endpoint = <&usbdp_phy0_dp_out>; }; }; }; @@ -819,6 +845,12 @@ }; }; + hdmi { + hdmi_tx_on_h: hdmi-tx-on-h { + rockchip,pins = <2 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { hym8563_int: hym8563-int { rockchip,pins = <0 RK_PA0 RK_FUNC_GPIO &pcfg_pull_up>; @@ -854,10 +886,10 @@ rockchip,pins = <0 RK_PA5 RK_FUNC_GPIO &pcfg_pull_up>; }; usbc0_sbu1: usbc0-sbu1 { - rockchip,pins = <2 RK_PA6 RK_FUNC_GPIO &pcfg_pull_down>; + rockchip,pins = <2 RK_PA6 RK_FUNC_GPIO &pcfg_pull_none>; }; usbc0_sbu2: usbc0-sbu2 { - rockchip,pins = <2 RK_PA7 RK_FUNC_GPIO &pcfg_pull_down>; + rockchip,pins = <2 RK_PA7 RK_FUNC_GPIO &pcfg_pull_none>; }; }; @@ -913,6 +945,7 @@ no-sdio; no-sd; non-removable; + /delete-property/ supports-cqe; status = "okay"; }; @@ -947,6 +980,10 @@ status = "okay"; }; +&spdif_tx3 { + status = "okay"; +}; + &u2phy0 { status = "okay"; }; @@ -988,14 +1025,14 @@ port@0 { reg = <0>; usb_drd0_hs_ep: endpoint { - remote-endpoint = <&usbc0_hs_ep>; + remote-endpoint = <&usbc0_hs>; }; }; port@1 { reg = <1>; usb_drd0_ss_ep: endpoint { - remote-endpoint = <&usbc0_ss_ep>; + remote-endpoint = <&usbdp_phy_ss_in>; }; }; }; @@ -1015,9 +1052,41 @@ sbu2-dc-gpios = <&gpio2 RK_PA7 GPIO_ACTIVE_HIGH>; status = "okay"; - port { - usbdp_phy_ep: endpoint { - remote-endpoint = <&usbc0_dp_ep>; + ports { + #address-cells = <1>; + #size-cells = <0>; + + + port@0 { + reg = <0>; + + usbdp_phy_ss_out: endpoint { + remote-endpoint = <&usbc0_ss>; + }; + }; + + port@1 { + reg = <1>; + + usbdp_phy_ss_in: endpoint { + remote-endpoint = <&usb_drd0_ss_ep>; + }; + }; + + port@2 { + reg = <2>; + + usbdp_phy_dp_in: endpoint { + remote-endpoint = <&dp0_out_con>; + }; + }; + + port@3 { + reg = <3>; + + usbdp_phy0_dp_out: endpoint { + remote-endpoint = <&usbc0_sbu>; + }; }; }; }; @@ -1036,3 +1105,10 @@ remote-endpoint = <&hdmi_in_vp0>; }; }; + +&vp1 { + vp1_out_dp0: endpoint@a { + reg = ; + remote-endpoint = <&dp0_in_vp1>; + }; +}; diff --git a/arch/arm64/boot/dts/rockchip/rk3576-dp-4k-hdmi-2.5k.dtso b/arch/arm64/boot/dts/rockchip/rk3576-dp-4k-hdmi-2.5k.dtso new file mode 100644 index 00000000000000..b172c97004ce15 --- /dev/null +++ b/arch/arm64/boot/dts/rockchip/rk3576-dp-4k-hdmi-2.5k.dtso @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * DT-overlay to switch from the default HDMI 4k and DP 2.5k mode to DP 4K and + * HDMI 2.5k mode. Should be applicable for any boards having both HDMI and DP + */ + +#include + +/dts-v1/; +/plugin/; + +&dp { + status = "okay"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + dp0_in: port@0 { + reg = <0>; + }; + }; +}; + +&dp0_in { + dp0_in_vp0: endpoint { + remote-endpoint = <&vp0_out_dp0>; + }; +}; + +&hdmi { + status = "okay"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + hdmi_in: port@0 { + reg = <0>; + }; + }; +}; + +&hdmi_in { + hdmi_in_vp1: endpoint { + remote-endpoint = <&vp1_out_hdmi>; + }; +}; + +&vop { + status = "okay"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + vp0: port@0 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + }; + + vp1: port@1 { + #address-cells = <1>; + #size-cells = <0>; + reg = <1>; + }; + }; +}; + +&vp0 { + vp0_out_dp0: endpoint@a { + reg = ; + remote-endpoint = <&dp0_in_vp0>; + }; +}; + +&vp1 { + vp1_out_hdmi: endpoint@ROCKCHIP_VOP2_EP_HDMI0 { + reg = ; + remote-endpoint = <&hdmi_in_vp1>; + }; +}; diff --git a/arch/arm64/boot/dts/rockchip/rk3576-evb1-v10.dts b/arch/arm64/boot/dts/rockchip/rk3576-evb1-v10.dts index fb0dd1bc51482c..b1fc17e72d28d9 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-evb1-v10.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-evb1-v10.dts @@ -11,6 +11,7 @@ #include #include #include +#include #include "rk3576.dtsi" / { @@ -334,6 +335,26 @@ status = "okay"; }; +&dp { + status = "okay"; +}; + +&dp0_in { + dp0_in_vp1: endpoint { + remote-endpoint = <&vp1_out_dp>; + }; +}; + +&dp0_out { + dp0_out_con: endpoint { + remote-endpoint = <&usbdp_phy_dp_in>; + }; +}; + +&dp0_sound { + status = "okay"; +}; + &gmac0 { clock_in_out = "output"; phy-mode = "rgmii-rxid"; @@ -370,6 +391,9 @@ }; &hdmi { + frl-enable-gpios = <&gpio2 RK_PB0 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmi_txm0_pins &hdmi_tx_scl &hdmi_tx_sda &hdmi_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -769,6 +793,58 @@ &i2c2 { status = "okay"; + usbc0: typec-portc@4e { + compatible = "hynetek,husb311", "richtek,rt1711h"; + reg = <0x4e>; + interrupt-parent = <&gpio0>; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&usbc0_int>; + vbus-supply = <&vbus5v0_typec>; + + connector { + compatible = "usb-c-connector"; + label = "USB-C"; + data-role = "dual"; + power-role = "source"; + source-pdos = ; + + altmodes { + displayport { + svid = /bits/ 16 <0xff01>; + vdo = <0xffffffff>; + }; + }; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + usbc0_hs: endpoint { + remote-endpoint = <&usb_drd0_hs_ep>; + }; + }; + + port@1 { + reg = <1>; + usbc0_ss: endpoint { + remote-endpoint = <&usbdp_phy_ss_out>; + }; + }; + + port@2 { + reg = <2>; + usbc0_sbu: endpoint { + remote-endpoint = <&usbdp_phy_dp_out>; + }; + }; + }; + }; + }; + hym8563: rtc@51 { compatible = "haoyu,hym8563"; reg = <0x51>; @@ -810,6 +886,7 @@ assigned-clock-rates = <25000000>; pinctrl-names = "default"; pinctrl-0 = <&rgmii_phy0_rst>; + realtek,aldps-enable; reset-assert-us = <20000>; reset-deassert-us = <100000>; reset-gpios = <&gpio2 RK_PB5 GPIO_ACTIVE_LOW>; @@ -825,6 +902,7 @@ assigned-clock-rates = <25000000>; pinctrl-names = "default"; pinctrl-0 = <&rgmii_phy1_rst>; + realtek,aldps-enable; reset-assert-us = <20000>; reset-deassert-us = <100000>; reset-gpios = <&gpio3 RK_PA3 GPIO_ACTIVE_LOW>; @@ -895,6 +973,12 @@ }; }; + hdmi { + hdmi_tx_on_h: hdmi-tx-on-h { + rockchip,pins = <2 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { rtc_int: rtc-int { rockchip,pins = <0 RK_PA0 RK_FUNC_GPIO &pcfg_pull_up>; @@ -935,6 +1019,14 @@ usbc0_int: usbc0-int { rockchip,pins = <0 RK_PA5 RK_FUNC_GPIO &pcfg_pull_up>; }; + + usbc0_sbu1: usbc0-sbu1 { + rockchip,pins = <2 RK_PA6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + usbc0_sbu2: usbc0-sbu2 { + rockchip,pins = <2 RK_PA7 RK_FUNC_GPIO &pcfg_pull_none>; + }; }; wifi { @@ -995,6 +1087,10 @@ status = "okay"; }; +&spdif_tx3 { + status = "okay"; +}; + &u2phy0 { status = "okay"; }; @@ -1043,13 +1139,76 @@ }; &usbdp_phy { - rockchip,dp-lane-mux = <2 3>; + mode-switch; + orientation-switch; + pinctrl-names = "default"; + pinctrl-0 = <&usbc0_sbu1 &usbc0_sbu2>; + sbu1-dc-gpios = <&gpio2 RK_PA6 GPIO_ACTIVE_HIGH>; + sbu2-dc-gpios = <&gpio2 RK_PA7 GPIO_ACTIVE_HIGH>; status = "okay"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + + port@0 { + reg = <0>; + + usbdp_phy_ss_out: endpoint { + remote-endpoint = <&usbc0_ss>; + }; + }; + + port@1 { + reg = <1>; + + usbdp_phy_ss_in: endpoint { + remote-endpoint = <&usb_drd0_ss_ep>; + }; + }; + + port@2 { + reg = <2>; + + usbdp_phy_dp_in: endpoint { + remote-endpoint = <&dp0_out_con>; + }; + }; + + port@3 { + reg = <3>; + + usbdp_phy_dp_out: endpoint { + remote-endpoint = <&usbc0_sbu>; + }; + }; + }; }; &usb_drd0_dwc3 { - dr_mode = "host"; + usb-role-switch; + dr_mode = "otg"; status = "okay"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + usb_drd0_hs_ep: endpoint { + remote-endpoint = <&usbc0_hs>; + }; + }; + + port@1 { + reg = <1>; + usb_drd0_ss_ep: endpoint { + remote-endpoint = <&usbdp_phy_ss_in>; + }; + }; + }; }; &usb_drd1_dwc3 { @@ -1071,3 +1230,10 @@ remote-endpoint = <&hdmi_in_vp0>; }; }; + +&vp1 { + vp1_out_dp: endpoint@a { + reg = ; + remote-endpoint = <&dp0_in_vp1>; + }; +}; diff --git a/arch/arm64/boot/dts/rockchip/rk3576-evb2-v10.dts b/arch/arm64/boot/dts/rockchip/rk3576-evb2-v10.dts index 98d5d00d63b577..dfc756d5ec5e18 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-evb2-v10.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-evb2-v10.dts @@ -375,6 +375,9 @@ }; &hdmi { + frl-enable-gpios = <&gpio4 RK_PC6 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmi_txm0_pins &hdmi_tx_scl &hdmi_tx_sda &hdmi_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -832,6 +835,12 @@ }; }; + hdmi { + hdmi_tx_on_h: hdmi-tx-on-h { + rockchip,pins = <4 RK_PC6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { rtc_int: rtc-int { rockchip,pins = <0 RK_PA5 RK_FUNC_GPIO &pcfg_pull_up>; diff --git a/arch/arm64/boot/dts/rockchip/rk3576-flipper-one-i2c2-free.dtso b/arch/arm64/boot/dts/rockchip/rk3576-flipper-one-i2c2-free.dtso new file mode 100644 index 00000000000000..16f691abc26424 --- /dev/null +++ b/arch/arm64/boot/dts/rockchip/rk3576-flipper-one-i2c2-free.dtso @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * DT-overlay to disable CPU side access to all I2C2 peripherals + */ + +/dts-v1/; +/plugin/; + +&gpio_expander { + status = "disabled"; +}; + +&usbc0 { + status = "disabled"; +}; + +&ina4_1 { + status = "disabled"; +}; + +&ina4_2 { + status = "disabled"; +}; + +&ina4_3 { + status = "disabled"; +}; + +&ina4_4 { + status = "disabled"; +}; + +&ina_sys { + status = "disabled"; +}; + +&ina4_5 { + status = "disabled"; +}; + +&usbmux { + status = "disabled"; +}; + +&gauge { + status = "disabled"; +}; + +&bq25792 { + status = "disabled"; +}; diff --git a/arch/arm64/boot/dts/rockchip/rk3576-flipper-one-rev-f0b0c1.dts b/arch/arm64/boot/dts/rockchip/rk3576-flipper-one-rev-f0b0c1.dts new file mode 100644 index 00000000000000..5ba517a700ea93 --- /dev/null +++ b/arch/arm64/boot/dts/rockchip/rk3576-flipper-one-rev-f0b0c1.dts @@ -0,0 +1,1794 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Copyright (c) 2026 Flipper FZCO + */ + +/dts-v1/; + +#include +#include +#include +#include +#include +#include +#include +#include "rk3576.dtsi" + +/ { + model = "Flipper One rev. F0B0C1"; + compatible = "flipper,one-rev-f0b0c1", "rockchip,rk3576"; + + aliases { + ethernet0 = &gmac0; + ethernet1 = &gmac1; + }; + + battery: battery { + compatible = "simple-battery"; + charge-full-design-microamp-hours = <3100000>; + constant-charge-current-max-microamp = <3100000>; + constant-charge-voltage-max-microvolt = <8650000>; + device-chemistry = "lithium-ion-polymer"; + operating-range-celsius = <0 60>; + voltage-min-design-microvolt = <6000000>; + }; + + chosen: chosen { + stdout-path = "serial0:1500000n8"; + }; + + hdmi-con { + compatible = "hdmi-connector"; + type = "a"; + + port { + hdmi_con_in: endpoint { + remote-endpoint = <&hdmi_out_con>; + }; + }; + }; + + leds { + compatible = "gpio-leds"; + pinctrl-0 = <&led_cd2>, <&led_cd3>; + pinctrl-names = "default"; + + led-cd2 { + color = ; + function = LED_FUNCTION_HEARTBEAT; + gpios = <&gpio0 RK_PD2 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "heartbeat"; + }; + + led-cd3 { + color = ; + function = LED_FUNCTION_STATUS; + gpios = <&gpio0 RK_PD3 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "default-on"; + }; + }; + + vcc8v4_sys: regulator-vcc8v4-sys { + compatible = "regulator-fixed"; + regulator-name = "vcc8v4_sys"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <8400000>; + regulator-max-microvolt = <8400000>; + /* Powered by the charger's SYS output */ + }; + + vcc3v3_control: regulator-vcc3v3-control { + compatible = "regulator-fixed"; + regulator-name = "vcc3v3_control"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + vin-supply = <&vcc8v4_sys>; + }; + + gpio_vcc3v3: regulator-vcc3v3-extgpio { + compatible = "regulator-fixed"; + enable-active-high; + regulator-name = "gpio_vcc3v3"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + gpios = <&gpio_expander 0xe GPIO_ACTIVE_HIGH>; + vin-supply = <&vcc3v3_control>; + }; + + vcc3v3_m2: regulator-vcc3v3-m2 { + compatible = "regulator-fixed"; + regulator-name = "vcc3v3_m2"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + enable-active-high; + gpios = <&gpio0 RK_PB1 GPIO_ACTIVE_HIGH>; + pinctrl-0 = <&m2_pwr_en>; + pinctrl-names = "default"; + startup-delay-us = <5000>; + vin-supply = <&vcc8v4_sys>; + }; + + vcc3v3_rtc_s5: regulator-vcc3v3-rtc-s5 { + compatible = "regulator-fixed"; + regulator-name = "vcc3v3_rtc_s5"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + /* Powered by the system battery */ + }; + + vcc5v0_device_s0: regulator-vcc5v0-device-s0 { + compatible = "regulator-fixed"; + regulator-name = "vcc5v0_device"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + /* + * Controlled by the GPIO expander pin P13, but turning it off + * makes the expander I2C bus stuck and unrecoverable due to + * the USB MUX misbehavior on the same bus + */ + vin-supply = <&vcc8v4_sys>; + }; + + gpio_vcc5v0: regulator-vcc5v0-extgpio { + compatible = "regulator-fixed"; + enable-active-high; + regulator-name = "gpio_vcc5v0"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + gpios = <&gpio_expander 0xd GPIO_ACTIVE_HIGH>; + vin-supply = <&vcc5v0_device_s0>; + }; + + vusb_typea_up: regulator-vcc5v0-vusb-typea-up { + compatible = "regulator-fixed"; + enable-active-high; + regulator-name = "vusb_typea_up"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + gpios = <&gpio_expander 0xa GPIO_ACTIVE_HIGH>; + vin-supply = <&vcc5v0_device_s0>; + }; + + vcc5v0_sys_s5: regulator-vcc5v0-sys-s5 { + compatible = "regulator-fixed"; + regulator-name = "vcc5v0_sys_s5"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + /* + * Controlled by the GPIO expander pin P14, but we + * can't let this pin to ever change state, as it makes + * the system lose power + */ + vin-supply = <&vcc8v4_sys>; + }; + + vcc_2v0_pldo_s3: regulator-vcc-2v0-pldo-s3 { + compatible = "regulator-fixed"; + regulator-name = "vcc_2v0_pldo_s3"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <2000000>; + regulator-max-microvolt = <2000000>; + /* PMIC_EXT_EN_OUT */ + vin-supply = <&vcc5v0_sys_s5>; + }; + + vcc_1v1_nldo_s3: regulator-vcc-1v1-nldo-s3 { + compatible = "regulator-fixed"; + regulator-name = "vcc_1v1_nldo_s3"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <1100000>; + regulator-max-microvolt = <1100000>; + /* PMIC_EXT_EN_OUT */ + vin-supply = <&vcc5v0_sys_s5>; + }; + + vdd2l_0v9_ddr_s3: regulator-vdd2l-0v9-ddr-s3 { + compatible = "regulator-fixed"; + regulator-name = "vdd2l_0v9_ddr_s3"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <900000>; + regulator-max-microvolt = <900000>; + /* VCC_3V3_S3 */ + vin-supply = <&vcc5v0_sys_s5>; + }; + + vdd1v1_hub: regulator-vdd-1v1-hub { + compatible = "regulator-fixed"; + enable-active-high; + regulator-name = "vdd1v1_hub"; + regulator-min-microvolt = <1100000>; + regulator-max-microvolt = <1100000>; + gpios = <&gpio_expander 0x9 GPIO_ACTIVE_HIGH>; + vin-supply = <&vcc5v0_sys_s5>; + }; + + vcc_3v3_s0: regulator-vcc-3v3-s0 { + compatible = "regulator-fixed"; + regulator-name = "vcc_3v3_s0"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + /* VCCA_1V8_S0 */ + vin-supply = <&vcc_3v3_s3>; + }; + + vcc_1v8_s0: regulator-vcc-1v8-s0 { + compatible = "regulator-fixed"; + regulator-name = "vcc_1v8_s0"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + /* VCCA_1V8_S0 */ + vin-supply = <&vcc_1v8_s3>; + }; + + vccio_1v8_s0: regulator-vccio-1v8-s0 { + compatible = "regulator-fixed"; + regulator-name = "vccio_1v8_s0"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + /* VCCA_1V8_S0 */ + vin-supply = <&vcc_1v8_s3>; + }; + + vcc1v8_ufs_vccq2_s0: regulator-vcc1v8-ufs-vccq2-s0 { + compatible = "regulator-fixed"; + regulator-name = "vcc1v8_ufs_vccq2_s0"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + vin-supply = <&vcc_1v8_s3>; + }; + + rfkill-onboard-wifi { + compatible = "rfkill-gpio"; + default-blocked; + radio-type = "wlan"; + label = "Onboard WiFi+BT"; + pinctrl-0 = <&wifi_pmu_en>; + pinctrl-names = "default"; + shutdown-gpios = <&gpio1 RK_PD5 GPIO_ACTIVE_HIGH>; + }; + + rfkill-m2-wdisable1 { + compatible = "rfkill-gpio"; + radio-type = "wwan"; + label = "M.2 WWAN"; + pinctrl-0 = <&m2b_w_disable1>; + pinctrl-names = "default"; + shutdown-gpios = <&gpio2 RK_PB4 GPIO_ACTIVE_HIGH>; + }; + + rfkill-m2-wdisable2 { + compatible = "rfkill-gpio"; + radio-type = "wwan"; + label = "M.2 WWAN (WDISABLE2)"; + pinctrl-0 = <&m2b_w_disable2>; + pinctrl-names = "default"; + shutdown-gpios = <&gpio4 RK_PA0 GPIO_ACTIVE_HIGH>; + }; + + sound { + compatible = "simple-audio-card"; + simple-audio-card,name = "On-board Analog NAU8822"; + simple-audio-card,bitclock-master = <&masterdai>; + simple-audio-card,format = "i2s"; + simple-audio-card,frame-master = <&masterdai>; + simple-audio-card,mclk-fs = <256>; + simple-audio-card,pin-switches = "Headphones", "Speaker", + "Headset Microphone", "Internal Microphone"; + simple-audio-card,routing = + "Headphones", "LHP", + "Headphones", "RHP", + "Speaker", "LSPK", + "Speaker", "RSPK", + "Line Out", "AUXOUT1", + "Line Out", "AUXOUT2", + "LMICP", "Internal Microphone", + "LMICN", "Internal Microphone", + "RMICP", "Headset Microphone"; + simple-audio-card,widgets = + "Headphones", "Headphones", + "Line Out", "Line Out", + "Speaker", "Speaker", + "Microphone", "Internal Microphone", + "Microphone", "Headset Microphone"; + + simple-audio-card,cpu { + sound-dai = <&sai2>; + }; + + masterdai: simple-audio-card,codec { + sound-dai = <&nau8822>; + system-clock-frequency = <12288000>; + }; + }; + + typea_up_con: usb-a-connector { + compatible = "usb-a-connector"; + data-role = "host"; + label = "USB-A (up)"; + power-role = "source"; + vbus-supply = <&vusb_typea_up>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + typea_up_con_hs: endpoint { + remote-endpoint = <&hub_2_0_ds2>; + }; + }; + + port@1 { + reg = <1>; + typea_up_con_ss: endpoint { + remote-endpoint = <&hub_3_0_ds2>; + }; + }; + }; + }; + + typec_up_con: usb-c-connector { + compatible = "usb-c-connector"; + data-role = "host"; + label = "USB-C (up)"; + power-role = "source"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + typec_up_con_hs: endpoint { + remote-endpoint = <&hub_2_0_ds3>; + }; + }; + + port@1 { + reg = <1>; + typec_up_con_ss: endpoint { + remote-endpoint = <&usb_mux_in>; + }; + }; + }; + }; +}; + +&cpu_l0 { + cpu-supply = <&vdd_cpu_lit_s0>; +}; + +&cpu_l1 { + cpu-supply = <&vdd_cpu_lit_s0>; +}; + +&cpu_l2 { + cpu-supply = <&vdd_cpu_lit_s0>; +}; + +&cpu_l3 { + cpu-supply = <&vdd_cpu_lit_s0>; +}; + +&cpu_b0 { + cpu-supply = <&vdd_cpu_big_s0>; +}; + +&cpu_b1 { + cpu-supply = <&vdd_cpu_big_s0>; +}; + +&cpu_b2 { + cpu-supply = <&vdd_cpu_big_s0>; +}; + +&cpu_b3 { + cpu-supply = <&vdd_cpu_big_s0>; +}; + +&combphy0_ps { + status = "okay"; +}; + +&combphy1_psu { + status = "okay"; +}; + +&dp { + status = "okay"; +}; + +&dp0_in { + dp0_in_vp1: endpoint { + remote-endpoint = <&vp1_out_dp0>; + }; +}; + +&dp0_out { + dp0_out_con: endpoint { + remote-endpoint = <&usbdp_phy_dp_in>; + }; +}; + +&dp0_sound { + status = "okay"; +}; + +&gmac0 { + clock_in_out = "output"; + phy-handle = <&rgmii_phy0>; + phy-mode = "rgmii-id"; + phy-supply = <&vccio_1v8_s0>; + pinctrl-names = "default"; + pinctrl-0 = <ð0m0_miim + ð0m0_tx_bus2 + ð0m0_rx_bus2 + ð0m0_rgmii_clk + ð0m0_rgmii_bus + ðm0_clk0_25m_out>; + status = "okay"; +}; + +&gmac1 { + clock_in_out = "output"; + phy-handle = <&rgmii_phy1>; + phy-mode = "rgmii-id"; + phy-supply = <&vccio_1v8_s0>; + pinctrl-names = "default"; + pinctrl-0 = <ð1m0_miim + ð1m0_tx_bus2 + ð1m0_rx_bus2 + ð1m0_rgmii_clk + ð1m0_rgmii_bus + ðm0_clk1_25m_out>; + status = "okay"; +}; + +&gpio2 { + m2-poweroff-hog { + gpios = ; + output-low; + line-name = "M.2 Full Card Power Off Disable"; + gpio-hog; + }; +}; + +&gpio3 { + m2-reset-hog { + gpios = ; + output-low; + line-name = "M.2 Reset Disable"; + gpio-hog; + }; +}; + +&gpu { + mali-supply = <&vdd_gpu_s0>; + status = "okay"; +}; + +&hdmi { + frl-enable-gpios = <&gpio0 RK_PC3 GPIO_ACTIVE_LOW>; + status = "okay"; +}; + +&hdmi_in { + hdmi_in_vp0: endpoint { + remote-endpoint = <&vp0_out_hdmi>; + }; +}; + +&hdmi_out { + hdmi_out_con: endpoint { + remote-endpoint = <&hdmi_con_in>; + }; +}; + +&hdmi_sound { + status = "okay"; +}; + +&hdptxphy { + status = "okay"; +}; + +&i2c0 { + clock-frequency = <400000>; + pinctrl-0 = <&i2c0m1_xfer &i2c0_sda_pullup>; + pinctrl-names = "default"; + status = "okay"; + + embedded-controller@69 { + compatible = "flipper,one-mcu"; + reg = <0x69>; + interrupt-parent = <&gpio2>; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&audio_headset_int &cpu_int>; + wakeup-source; + }; +}; + +&i2c1 { + status = "okay"; + + rk806: pmic@23 { + compatible = "rockchip,rk806"; + reg = <0x23>; + interrupt-parent = <&gpio0>; + interrupts = ; + gpio-controller; + #gpio-cells = <2>; + pinctrl-names = "default"; + pinctrl-0 = <&pmic_pins>, <&rk806_dvs1_null>, + <&rk806_dvs2_null>, <&rk806_dvs3_null>; + system-power-controller; + + vcc1-supply = <&vcc5v0_sys_s5>; + vcc2-supply = <&vcc5v0_sys_s5>; + vcc3-supply = <&vcc5v0_sys_s5>; + vcc4-supply = <&vcc5v0_sys_s5>; + vcc5-supply = <&vcc5v0_sys_s5>; + vcc6-supply = <&vcc5v0_sys_s5>; + vcc7-supply = <&vcc5v0_sys_s5>; + vcc8-supply = <&vcc5v0_sys_s5>; + vcc9-supply = <&vcc5v0_sys_s5>; + vcc10-supply = <&vcc5v0_sys_s5>; + vcc11-supply = <&vcc_2v0_pldo_s3>; + vcc12-supply = <&vcc5v0_sys_s5>; + vcc13-supply = <&vcc_1v1_nldo_s3>; + vcc14-supply = <&vcc_1v1_nldo_s3>; + vcca-supply = <&vcc5v0_sys_s5>; + + rk806_dvs1_null: dvs1-null-pins { + pins = "gpio_pwrctrl1"; + function = "pin_fun0"; + }; + + rk806_dvs2_null: dvs2-null-pins { + pins = "gpio_pwrctrl2"; + function = "pin_fun0"; + }; + + rk806_dvs3_null: dvs3-null-pins { + pins = "gpio_pwrctrl3"; + function = "pin_fun0"; + }; + + rk806_dvs1_slp: dvs1-slp-pins { + pins = "gpio_pwrctrl1"; + function = "pin_fun1"; + }; + + rk806_dvs1_pwrdn: dvs1-pwrdn-pins { + pins = "gpio_pwrctrl1"; + function = "pin_fun2"; + }; + + rk806_dvs1_rst: dvs1-rst-pins { + pins = "gpio_pwrctrl1"; + function = "pin_fun3"; + }; + + rk806_dvs2_slp: dvs2-slp-pins { + pins = "gpio_pwrctrl2"; + function = "pin_fun1"; + }; + + rk806_dvs2_pwrdn: dvs2-pwrdn-pins { + pins = "gpio_pwrctrl2"; + function = "pin_fun2"; + }; + + rk806_dvs2_rst: dvs2-rst-pins { + pins = "gpio_pwrctrl2"; + function = "pin_fun3"; + }; + + rk806_dvs2_dvs: dvs2-dvs-pins { + pins = "gpio_pwrctrl2"; + function = "pin_fun4"; + }; + + rk806_dvs2_gpio: dvs2-gpio-pins { + pins = "gpio_pwrctrl2"; + function = "pin_fun5"; + }; + + rk806_dvs3_slp: dvs3-slp-pins { + pins = "gpio_pwrctrl3"; + function = "pin_fun1"; + }; + + rk806_dvs3_pwrdn: dvs3-pwrdn-pins { + pins = "gpio_pwrctrl3"; + function = "pin_fun2"; + }; + + rk806_dvs3_rst: dvs3-rst-pins { + pins = "gpio_pwrctrl3"; + function = "pin_fun3"; + }; + + rk806_dvs3_dvs: dvs3-dvs-pins { + pins = "gpio_pwrctrl3"; + function = "pin_fun4"; + }; + + rk806_dvs3_gpio: dvs3-gpio-pins { + pins = "gpio_pwrctrl3"; + function = "pin_fun5"; + }; + + regulators { + vdd_cpu_big_s0: dcdc-reg1 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <950000>; + regulator-ramp-delay = <12500>; + regulator-name = "vdd_cpu_big_s0"; + regulator-enable-ramp-delay = <400>; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdd_npu_s0: dcdc-reg2 { + regulator-boot-on; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <950000>; + regulator-ramp-delay = <12500>; + regulator-name = "vdd_npu_s0"; + regulator-enable-ramp-delay = <400>; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdd_cpu_lit_s0: dcdc-reg3 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <950000>; + regulator-ramp-delay = <12500>; + regulator-name = "vdd_cpu_lit_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + regulator-suspend-microvolt = <750000>; + }; + }; + + vcc_3v3_s3: dcdc-reg4 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-name = "vcc_3v3_s3"; + + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <3300000>; + }; + }; + + vdd_gpu_s0: dcdc-reg5 { + regulator-boot-on; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <900000>; + regulator-ramp-delay = <12500>; + regulator-name = "vdd_gpu_s0"; + regulator-enable-ramp-delay = <400>; + + regulator-state-mem { + regulator-off-in-suspend; + regulator-suspend-microvolt = <850000>; + }; + }; + + vddq_ddr_s0: dcdc-reg6 { + regulator-always-on; + regulator-boot-on; + regulator-name = "vddq_ddr_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdd_logic_s0: dcdc-reg7 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <800000>; + regulator-name = "vdd_logic_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vcc_1v8_s3: dcdc-reg8 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-name = "vcc_1v8_s3"; + + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <1800000>; + }; + }; + + vdd2_ddr_s3: dcdc-reg9 { + regulator-always-on; + regulator-boot-on; + regulator-name = "vdd2_ddr_s3"; + + regulator-state-mem { + regulator-on-in-suspend; + }; + }; + + vdd_ddr_s0: dcdc-reg10 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <1200000>; + regulator-name = "vdd_ddr_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vcca_1v8_s0: pldo-reg1 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-name = "vcca_1v8_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vcca1v8_pldo2_s0: pldo-reg2 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-name = "vcca1v8_pldo2_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdda_1v2_s0: pldo-reg3 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-name = "vdda_1v2_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vcca_3v3_s0: pldo-reg4 { + regulator-enable-ramp-delay = <500>; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-ramp-delay = <12500>; + regulator-name = "vcca_3v3_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vccio_3v3_sd_s0: pldo-reg5 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3300000>; + regulator-name = "vccio_3v3_sd_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vcca1v8_pldo6_s3: pldo-reg6 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-name = "vcca1v8_pldo6_s3"; + + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <1800000>; + }; + }; + + vdd_0v75_s3: nldo-reg1 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <750000>; + regulator-name = "vdd_0v75_s3"; + + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <750000>; + }; + }; + + vdda_ddr_pll_s0: nldo-reg2 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <850000>; + regulator-max-microvolt = <850000>; + regulator-name = "vdda_ddr_pll_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdda0v75_hdmi_s0: nldo-reg3 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <837500>; + regulator-max-microvolt = <837500>; + regulator-name = "vdda0v75_hdmi_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdda_0v85_s0: nldo-reg4 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <850000>; + regulator-max-microvolt = <850000>; + regulator-name = "vdda_0v85_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdda_0v75_s0: nldo-reg5 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <750000>; + regulator-max-microvolt = <750000>; + regulator-name = "vdda_0v75_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + }; + }; +}; + +&i2c2 { + clock-frequency = <400000>; + pinctrl-0 = <&i2c2m0_xfer_dr5>; + pinctrl-names = "default"; + status = "okay"; + + gpio_expander: gpio@20 { + compatible = "ti,tca6416"; + reg = <0x20>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupt-controller; + interrupt-parent = <&gpio2>; + interrupts = ; + pinctrl-0 = <&expander_int>; + pinctrl-names = "default"; + vcc-supply = <&vcc3v3_control>; + + usb2-switch-hog { + gpios = <8 GPIO_ACTIVE_HIGH>; + output-high; + line-name = "Main Type-C USB 2.0 pins switch to CPU"; + gpio-hog; + }; + + vcc5v0-device-s0-hog { + gpios = <0xb GPIO_ACTIVE_HIGH>; + output-high; + line-name = "Peripheral power supply"; + gpio-hog; + }; + + vcc5v0-sys-s5-hog { + gpios = <0xc GPIO_ACTIVE_HIGH>; + output-high; + line-name = "Main power supply to the system PMIC"; + gpio-hog; + }; + }; + + usbc0: typec-portc@22 { + compatible = "fcs,fusb302"; + reg = <0x22>; + interrupt-parent = <&gpio_expander>; + interrupts = <3 IRQ_TYPE_LEVEL_LOW>; + vbus-supply = <&vusb_typec>; + + connector { + compatible = "usb-c-connector"; + label = "USB-C"; + data-role = "dual"; + op-sink-microwatt = <10000000>; + pd-revision = /bits/ 8 <0x3 0x1 0x1 0x8>; + power-role = "dual"; + self-powered; + sink-pdos = ; + source-pdos = ; + try-power-role = "sink"; + + altmodes { + displayport { + svid = /bits/ 16 <0xff01>; + vdo = <0xffffffff>; + }; + }; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + usbc0_hs: endpoint { + remote-endpoint = <&usb_drd0_hs_ep>; + }; + }; + port@1 { + reg = <1>; + usbc0_ss: endpoint { + remote-endpoint = <&usbdp_phy_ss_out>; + }; + }; + port@2 { + reg = <2>; + usbc0_sbu: endpoint { + remote-endpoint = <&usbdp_phy0_dp_out>; + }; + }; + }; + }; + }; + + ina4_1: power-sensor@40 { + compatible = "ti,ina4230"; + reg = <0x40>; + vs-supply = <&vcc3v3_control>; + #address-cells = <1>; + #size-cells = <0>; + + input@0 { + reg = <0>; + label = "vdd_0v75_s3"; + shunt-resistor-micro-ohms = <50000>; + ti,maximum-expected-current-microamp = <300000>; + }; + + input@1 { + reg = <1>; + label = "vcc_3v3_control"; + shunt-resistor-micro-ohms = <10000>; + ti,maximum-expected-current-microamp = <4000000>; + }; + + input@2 { + reg = <2>; + label = "vdd0v85_ddr_s0"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <3000000>; + }; + + input@3 { + reg = <3>; + label = "vcc_3v3_s3"; + shunt-resistor-micro-ohms = <10000>; + ti,maximum-expected-current-microamp = <5000000>; + }; + }; + + ina4_2: power-sensor@41 { + compatible = "ti,ina4230"; + reg = <0x41>; + vs-supply = <&vcc3v3_control>; + #address-cells = <1>; + #size-cells = <0>; + + input@0 { + reg = <0>; + label = "vddq0v51_ddr_s0"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <3000000>; + }; + + input@1 { + reg = <1>; + label = "vdd0v75_npu_s0"; + shunt-resistor-micro-ohms = <10000>; + ti,maximum-expected-current-microamp = <5000000>; + }; + + input@2 { + reg = <2>; + label = "vdd0v75_gpu_s0"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <3000000>; + }; + + input@3 { + reg = <3>; + label = "vdd0v75_logic_s0"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <3000000>; + }; + }; + + ina4_3: power-sensor@43 { + compatible = "ti,ina4230"; + reg = <0x43>; + vs-supply = <&vcc3v3_control>; + #address-cells = <1>; + #size-cells = <0>; + + input@0 { + reg = <0>; + label = "vdd0v75_cpu_big_s0"; + shunt-resistor-micro-ohms = <10000>; + ti,maximum-expected-current-microamp = <6500000>; + }; + + input@1 { + reg = <1>; + label = "vdda_1v2_s0"; + shunt-resistor-micro-ohms = <50000>; + ti,maximum-expected-current-microamp = <300000>; + }; + + input@2 { + reg = <2>; + label = "vcca_1v8_s0"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <500000>; + }; + + input@3 { + reg = <3>; + label = "vdd0v75_cpu_lit_s0"; + shunt-resistor-micro-ohms = <10000>; + ti,maximum-expected-current-microamp = <5000000>; + }; + }; + + ina4_4: power-sensor@44 { + compatible = "ti,ina4230"; + reg = <0x44>; + vs-supply = <&vcc3v3_control>; + #address-cells = <1>; + #size-cells = <0>; + + input@0 { + reg = <0>; + label = "vdda_0v75_s0"; + shunt-resistor-micro-ohms = <50000>; + ti,maximum-expected-current-microamp = <300000>; + }; + + input@1 { + reg = <1>; + label = "vdda_0v85_s0"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <500000>; + }; + + input@2 { + reg = <2>; + label = "vdda0v75_hdmi_s0"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <500000>; + }; + + input@3 { + reg = <3>; + label = "vdda0v85_ddr_pll_s0"; + shunt-resistor-micro-ohms = <50000>; + ti,maximum-expected-current-microamp = <300000>; + }; + }; + + ina_sys: power-sensor@45 { + compatible = "ti,ina219"; + reg = <0x45>; + #io-channel-cells = <1>; + label = "vcc8v4_sys"; + shunt-resistor = <4000>; + vs-supply = <&vcc3v3_control>; + }; + + ina4_5: power-sensor@46 { + compatible = "ti,ina4230"; + reg = <0x46>; + vs-supply = <&vcc3v3_control>; + #address-cells = <1>; + #size-cells = <0>; + + input@0 { + reg = <0>; + label = "vcca_3v3_s0"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <500000>; + }; + + input@1 { + reg = <1>; + label = "vccio_3v3_sd_s0"; + shunt-resistor-micro-ohms = <50000>; + ti,maximum-expected-current-microamp = <300000>; + }; + + input@2 { + reg = <2>; + label = "vdd2_1v05_ddr_s3"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <2500000>; + }; + + input@3 { + reg = <3>; + label = "vcc_1v8_s3"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <3000000>; + }; + }; + + usbmux: usb-mux@47 { + compatible = "ti,hd3ss3220"; + reg = <0x47>; + interrupt-parent = <&gpio1>; + interrupts = ; + pinctrl-0 = <&usb_mux_int>; + pinctrl-names = "default"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + usb_mux_in: endpoint { + remote-endpoint = <&typec_up_con_ss>; + }; + }; + + port@1 { + reg = <1>; + usb_mux_out: endpoint { + remote-endpoint = <&hub_3_0_ds3>; + }; + }; + }; + }; + + gauge: fuel-gauge@55 { + compatible = "ti,bq28z610"; /* really bq28z620: tbc if we need a new compatible */ + reg = <0x55>; + monitored-battery = <&battery>; + }; + + bq25792: charger@6b { + compatible = "ti,bq25792"; + reg = <0x6b>; + input-current-limit-microamp = <3300000>; + interrupt-parent = <&gpio_expander>; + interrupts = <2 IRQ_TYPE_LEVEL_LOW>; + monitored-battery = <&battery>; + power-supplies = <&usbc0>; + + regulators { + vusb_typec: vbus { + regulator-max-microamp = <3320000>; + regulator-max-microvolt = <22000000>; + regulator-min-microamp = <0>; + regulator-min-microvolt = <2800000>; + regulator-name = "vusb_typec"; + }; + }; + }; +}; + +&i2c5 { + pinctrl-0 = <&i2c5m3_xfer>; + pinctrl-names = "default"; + status = "okay"; + + /* MIPI CSI camera + * PDN GPIO: GPIO3 RK_PC6 <&cam_pdn> + * CLK: CAM_CLK2_OUT_M0 <&cam_clk2m0_clk2> + */ +}; + +&i2c6 { + pinctrl-0 = <&i2c6m3_xfer>; + pinctrl-names = "default"; + status = "okay"; + + nau8822: audio-codec@1a { + compatible = "nuvoton,nau8822"; + reg = <0x1a>; + + /* all supplies: VCCA_3V3_S0 */ + assigned-clocks = <&cru CLK_SAI2_MCLKOUT_TO_IO>; + assigned-clock-rates = <12288000>; + clock-names = "mclk"; + clocks = <&cru CLK_SAI2_MCLKOUT_TO_IO>; + pinctrl-names = "default"; + pinctrl-0 = <&sai2m0_mclk>; + vdda-supply = <&vcca_3v3_s0>; + vddb-supply = <&vcca_3v3_s0>; + vddc-supply = <&vcca_3v3_s0>; + vddspk-supply = <&vcca_3v3_s0>; + #sound-dai-cells = <0>; + nuvoton,spk-btl; + }; +}; + +&i2c8 { + pinctrl-0 = <&i2c8m2_xfer>; + pinctrl-names = "default"; + status = "okay"; + + hym8563: rtc@51 { + compatible = "haoyu,hym8563"; + reg = <0x51>; + clock-output-names = "hym8563"; + interrupt-parent = <&gpio0>; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&rtc_int &clk_32k_pins>; + wakeup-source; + #clock-cells = <0>; + }; +}; + +&mdio0 { + rgmii_phy0: ethernet-phy@1 { + compatible = "ethernet-phy-id001c.c916"; + reg = <0x1>; + clocks = <&cru REFCLKO25M_GMAC0_OUT>; + assigned-clocks = <&cru REFCLKO25M_GMAC0_OUT>; + assigned-clock-rates = <25000000>; + interrupt-parent = <&gpio1>; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&rgmii_phy0_int &rgmii_phy0_rst>; + reset-assert-us = <20000>; + reset-deassert-us = <100000>; + reset-gpios = <&gpio1 RK_PB5 GPIO_ACTIVE_LOW>; + wakeup-source; + realtek,aldps-enable; + }; +}; + +&mdio1 { + rgmii_phy1: ethernet-phy@1 { + compatible = "ethernet-phy-id001c.c916"; + reg = <0x1>; + clocks = <&cru REFCLKO25M_GMAC1_OUT>; + assigned-clocks = <&cru REFCLKO25M_GMAC1_OUT>; + assigned-clock-rates = <25000000>; + interrupt-parent = <&gpio1>; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&rgmii_phy1_int &rgmii_phy1_rst>; + reset-assert-us = <20000>; + reset-deassert-us = <100000>; + reset-gpios = <&gpio1 RK_PB4 GPIO_ACTIVE_LOW>; + wakeup-source; + realtek,aldps-enable; + }; +}; + +&pcie0 { + pinctrl-names = "default"; + pinctrl-0 = <&pcie0m1_pins &pcie0_rst>; + reset-gpios = <&gpio1 RK_PC3 GPIO_ACTIVE_HIGH>; + vpcie3v3-supply = <&vcc3v3_m2>; + status = "okay"; +}; + +&pinctrl { + camera { + cam_pdn: cam-pdn { + rockchip,pins = <3 RK_PC6 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + + display { + spi0m0_wo_miso: spi0m0-wo-miso { + rockchip,pins = + /* spi0m0_csn0 */ + <0 RK_PC6 11 &pcfg_pull_none>, + /* spi0_clk_m0 */ + <0 RK_PC7 11 &pcfg_pull_none>, + /* spi0_mosi_m0 */ + <0 RK_PD0 11 &pcfg_pull_none>; + }; + + spi0m0_gpiomiso: spi0m0-gpiomiso { + rockchip,pins = <0 RK_PD1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + + gpio-expander { + expander_int: expander-int { + rockchip,pins = <2 RK_PA7 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + + hdmi { + hdmi_enable_frl: hdmi-enable-frl { + rockchip,pins = <0 RK_PC3 RK_FUNC_GPIO &pcfg_pull_down>; + }; + }; + + hym8563 { + rtc_int: rtc-int { + rockchip,pins = <0 RK_PA0 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + + i2c0 { + i2c0_sda_pullup: i2c0-sda-pullup { + rockchip,pins = <0 RK_PC5 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + + i2c2 { + i2c2m0_xfer_dr5: i2c2m0-xfer-dr5 { + rockchip,pins = + /* i2c2_scl_m0 */ + <0 RK_PB7 9 &pcfg_pull_none_drv_level_5_smt>, + /* i2c2_sda_m0 */ + <0 RK_PC0 9 &pcfg_pull_none_drv_level_5_smt>; + }; + }; + + leds { + led_cd2: led-cd2 { + rockchip,pins = <0 RK_PD2 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + led_cd3: led-cd3 { + rockchip,pins = <0 RK_PD3 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + + m2 { + m2_pwr_en: m2-pwren { + rockchip,pins = <0 RK_PB1 RK_FUNC_GPIO &pcfg_pull_down>; + }; + + m2b_w_disable1: m2-rfkill-wdisable1 { + rockchip,pins = <2 RK_PB4 RK_FUNC_GPIO &pcfg_pull_up>; + }; + + m2b_w_disable2: m2-rfkill-wdisable2 { + rockchip,pins = <4 RK_PA0 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + + mcu { + audio_headset_int: audio-headset-int { + rockchip,pins = <0 RK_PB5 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + cpu_int: cpu-int { + rockchip,pins = <2 RK_PA6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + + network { + rgmii_phy0_int: rgmii-phy0-int { + rockchip,pins = <1 RK_PC1 RK_FUNC_GPIO &pcfg_pull_up>; + }; + + rgmii_phy0_rst: rgmii-phy0-rst { + rockchip,pins = <1 RK_PB5 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + rgmii_phy1_int: rgmii-phy1-int { + rockchip,pins = <1 RK_PC2 RK_FUNC_GPIO &pcfg_pull_up>; + }; + + rgmii_phy1_rst: rgmii-phy1-rst { + rockchip,pins = <1 RK_PB4 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + + pcie0 { + pcie0_rst: pcie0-rst { + rockchip,pins = <1 RK_PC3 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + + usb { + hub_reset: usb-hub-reset { + rockchip,pins = <0 RK_PC4 RK_FUNC_GPIO &pcfg_pull_up>; + }; + + usb_mux_int: usb-mux-int { + rockchip,pins = <1 RK_PC0 RK_FUNC_GPIO &pcfg_pull_up>; + }; + + usbc0_sbu1: usbc0-sbu1 { + rockchip,pins = <4 RK_PC4 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + usbc0_sbu2: usbc0-sbu2 { + rockchip,pins = <4 RK_PC5 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + + wifibt { + wifi_pmu_en: wifi-pmu-en { + rockchip,pins = <1 RK_PD5 RK_FUNC_GPIO &pcfg_pull_down>; + }; + + wifi_wgpio0: bt-wake-host { + rockchip,pins = <1 RK_PC6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + wifi_wgpio1: wifi-wake-host { + rockchip,pins = <1 RK_PC7 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; +}; + +&sai0 { + /* Serial audio for M.2 WWAN */ + pinctrl-names = "default"; + pinctrl-0 = <&sai0m2_lrck + &sai0m2_sclk + &sai0m2_sdi0 + &sai0m2_sdo0>; + status = "okay"; +}; + +&sai2 { + status = "okay"; +}; + +&sai6 { + status = "okay"; +}; + +&sdmmc { + bus-width = <4>; + cap-sd-highspeed; + cd-gpios = <&gpio0 RK_PA7 GPIO_ACTIVE_LOW>; + disable-wp; + no-sdio; + no-mmc; + sd-uhs-sdr104; + vmmc-supply = <&vcc_3v3_s3>; + vqmmc-supply = <&vccio_3v3_sd_s0>; + status = "okay"; +}; + +&saradc { + vref-supply = <&vcca_1v8_s0>; + status = "okay"; +}; + +&spdif_tx3 { + status = "okay"; +}; + +&spi0 { + pinctrl-0 = <&spi0m0_wo_miso &spi0m0_gpiomiso>; + status = "okay"; + + display@0 { + compatible = "flipper,one-display"; + reg = <0>; + active-gpios = <&gpio0 RK_PD1 GPIO_ACTIVE_LOW>; + spi-cpha; + spi-cpol; + spi-max-frequency = <20000000>; + spi-rx-bus-width = <0>; + }; +}; + +&u2phy0 { + status = "okay"; +}; + +&u2phy0_otg { + status = "okay"; +}; + +&u2phy1 { + status = "okay"; +}; + +&u2phy1_otg { + status = "okay"; +}; + +&uart0 { + status = "okay"; +}; + +&uart4 { + pinctrl-0 = <&uart4m1_xfer>; + pinctrl-names = "default"; + status = "okay"; + + /* MCU interconnect */ +}; + +&ufshc { + vcc-supply = <&vcc_3v3_s3>; + vccq2-supply = <&vcc1v8_ufs_vccq2_s0>; + status = "okay"; +}; + +&usbdp_phy { + mode-switch; + orientation-switch; + pinctrl-names = "default"; + pinctrl-0 = <&usbc0_sbu1 &usbc0_sbu2>; + sbu1-dc-gpios = <&gpio4 RK_PC4 GPIO_ACTIVE_HIGH>; + sbu2-dc-gpios = <&gpio4 RK_PC5 GPIO_ACTIVE_HIGH>; + status = "okay"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + + port@0 { + reg = <0>; + + usbdp_phy_ss_out: endpoint { + remote-endpoint = <&usbc0_ss>; + }; + }; + + port@1 { + reg = <1>; + + usbdp_phy_ss_in: endpoint { + remote-endpoint = <&usb_drd0_ss_ep>; + }; + }; + + port@2 { + reg = <2>; + + usbdp_phy_dp_in: endpoint { + remote-endpoint = <&dp0_out_con>; + }; + }; + + port@3 { + reg = <3>; + + usbdp_phy0_dp_out: endpoint { + remote-endpoint = <&usbc0_sbu>; + }; + }; + }; +}; + +&usb_drd0_dwc3 { + usb-role-switch; + dr_mode = "otg"; + status = "okay"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + usb_drd0_hs_ep: endpoint { + remote-endpoint = <&usbc0_hs>; + }; + }; + + port@1 { + reg = <1>; + usb_drd0_ss_ep: endpoint { + remote-endpoint = <&usbdp_phy_ss_in>; + }; + }; + }; +}; + +&usb_drd1_dwc3 { + #address-cells = <1>; + #size-cells = <0>; + dr_mode = "host"; + pinctrl-0 = <&hub_reset>; + pinctrl-names = "default"; + status = "okay"; + + /* 2.0 hub */ + hub_2_0: hub@1 { + compatible = "usb3431,6241"; + reg = <1>; + peer-hub = <&hub_3_0>; + reset-gpios = <&gpio0 RK_PC4 GPIO_ACTIVE_LOW>; + vdd1v1-supply = <&vdd1v1_hub>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@1 { + reg = <1>; + /* ext USB port 2.54mm */ + }; + + port@2 { + reg = <2>; + hub_2_0_ds2: endpoint { + remote-endpoint = <&typea_up_con_hs>; + }; + }; + + port@3 { + reg = <3>; + hub_2_0_ds3: endpoint { + remote-endpoint = <&typec_up_con_hs>; + }; + }; + + port@4 { + reg = <4>; + /* M.2 */ + }; + }; + }; + + /* 3.0 hub */ + hub_3_0: hub@2 { + compatible = "usb3431,6341"; + reg = <2>; + #address-cells = <1>; + #size-cells = <0>; + peer-hub = <&hub_2_0>; + vdd1v1-supply = <&vdd1v1_hub>; + + device@1 { + /* WiFi-BT module connection */ + compatible = "usbe8d,7961"; + reg = <1>; + #address-cells = <2>; + #size-cells = <0>; + + interface@0 { + /* BT part */ + compatible = "usbife8d,7961.config1.0"; + reg = <0 1>; + interrupt-parent = <&gpio1>; + interrupts = ; + interrupt-names = "wakeup"; + pinctrl-0 = <&wifi_wgpio0>; + pinctrl-names = "default"; + }; + + interface@0,3 { + /* WiFi part */ + compatible = "usbife8d,7961.config1.3"; + reg = <0 3>; + interrupt-parent = <&gpio1>; + interrupts = ; + pinctrl-0 = <&wifi_wgpio1>; + pinctrl-names = "default"; + }; + }; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@2 { + reg = <2>; + hub_3_0_ds2: endpoint { + remote-endpoint = <&typea_up_con_ss>; + }; + }; + + port@3 { + reg = <3>; + hub_3_0_ds3: endpoint { + remote-endpoint = <&usb_mux_out>; + }; + }; + + port@4 { + reg = <4>; + /* M.2 */ + }; + }; + }; +}; + +&vop { + status = "okay"; +}; + +&vop_mmu { + status = "okay"; +}; + +&vp0 { + vp0_out_hdmi: endpoint@ROCKCHIP_VOP2_EP_HDMI0 { + reg = ; + remote-endpoint = <&hdmi_in_vp0>; + }; +}; + +&vp1 { + vp1_out_dp0: endpoint@a { + reg = ; + remote-endpoint = <&dp0_in_vp1>; + }; +}; diff --git a/arch/arm64/boot/dts/rockchip/rk3576-flipper-one-sata.dtso b/arch/arm64/boot/dts/rockchip/rk3576-flipper-one-sata.dtso new file mode 100644 index 00000000000000..559d64ff50fa5f --- /dev/null +++ b/arch/arm64/boot/dts/rockchip/rk3576-flipper-one-sata.dtso @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * DT-overlay to switch the M.2 slot from PCIe mode to SATA (which shares the + * same pins). + */ + +/dts-v1/; +/plugin/; + +&sata0 { + status = "okay"; +}; + +&pcie0 { + status = "disabled"; +}; diff --git a/arch/arm64/boot/dts/rockchip/rk3576-luckfox-core3576.dtsi b/arch/arm64/boot/dts/rockchip/rk3576-luckfox-core3576.dtsi index 749f0a54b478e4..861b8556831361 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-luckfox-core3576.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3576-luckfox-core3576.dtsi @@ -26,7 +26,6 @@ hdmi-con { compatible = "hdmi-connector"; - hdmi-pwr-supply = <&vcc_5v0_hdmi>; type = "a"; port { @@ -138,22 +137,6 @@ }; }; - vcc_5v0_hdmi: regulator-vcc-5v0-hdmi { - compatible = "regulator-fixed"; - enable-active-high; - gpios = <&gpio4 RK_PC6 GPIO_ACTIVE_HIGH>; - pinctrl-names = "default"; - pinctrl-0 = <&hdmi_con_en>; - regulator-min-microvolt = <5000000>; - regulator-max-microvolt = <5000000>; - regulator-name = "vcc_5v0_hdmi"; - vin-supply = <&vcc_5v0_sys>; - - regulator-state-mem { - regulator-off-in-suspend; - }; - }; - vcc_5v0_host: regulator-vcc-5v0-host { compatible = "regulator-fixed"; enable-active-high; @@ -225,12 +208,30 @@ cpu-supply = <&vdd_cpu_lit_s0>; }; +&gmac0 { + clock_in_out = "output"; + phy-mode = "rgmii-rxid"; + phy-handle = <&rgmii_phy0>; + pinctrl-names = "default"; + pinctrl-0 = <ð0m0_miim + ð0m0_tx_bus2 + ð0m0_rx_bus2 + ð0m0_rgmii_clk + ð0m0_rgmii_bus + ðm0_clk0_25m_out>; + tx_delay = <0x20>; + status = "okay"; +}; + &gpu { mali-supply = <&vdd_gpu_s0>; status = "okay"; }; &hdmi { + frl-enable-gpios = <&gpio4 RK_PC6 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmi_txm0_pins &hdmi_tx_scl &hdmi_tx_sda &hdmi_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -645,6 +646,19 @@ }; }; +&mdio0 { + rgmii_phy0: ethernet-phy@0 { + compatible = "ethernet-phy-ieee802.3-c22"; + reg = <0x0>; + clocks = <&cru REFCLKO25M_GMAC0_OUT>; + pinctrl-names = "default"; + pinctrl-0 = <&gmac0_rst>; + reset-assert-us = <20000>; + reset-deassert-us = <100000>; + reset-gpios = <&gpio2 RK_PB3 GPIO_ACTIVE_LOW>; + }; +}; + &pcie0 { pinctrl-names = "default"; pinctrl-0 = <&pcie_reset>; @@ -654,8 +668,14 @@ }; &pinctrl { + gmac { + gmac0_rst: ethphy0-rst { + rockchip,pins = <2 RK_PB3 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + hdmi { - hdmi_con_en: hdmi-con-en { + hdmi_tx_on_h: hdmi-tx-on-h { rockchip,pins = <4 RK_PC6 RK_FUNC_GPIO &pcfg_pull_none>; }; }; @@ -712,8 +732,6 @@ no-sd; no-sdio; non-removable; - vmmc-supply = <&vcc_3v3_s3>; - vqmmc-supply = <&vccio_sd_s0>; status = "okay"; }; diff --git a/arch/arm64/boot/dts/rockchip/rk3576-luckfox-omni3576.dts b/arch/arm64/boot/dts/rockchip/rk3576-luckfox-omni3576.dts index 6c75959adfe199..5ab0c7f3b54105 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-luckfox-omni3576.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-luckfox-omni3576.dts @@ -30,7 +30,41 @@ }; }; +&gmac1 { + clock_in_out = "output"; + phy-handle = <&rgmii_phy1>; + phy-mode = "rgmii-rxid"; + pinctrl-names = "default"; + pinctrl-0 = <ð1m0_miim + ð1m0_tx_bus2 + ð1m0_rx_bus2 + ð1m0_rgmii_clk + ð1m0_rgmii_bus + ðm0_clk1_25m_out>; + tx_delay = <0x20>; + status = "okay"; +}; + +&mdio1 { + rgmii_phy1: ethernet-phy@0 { + compatible = "ethernet-phy-ieee802.3-c22"; + reg = <0x0>; + clocks = <&cru REFCLKO25M_GMAC1_OUT>; + pinctrl-names = "default"; + pinctrl-0 = <&gmac1_rst>; + reset-assert-us = <20000>; + reset-deassert-us = <100000>; + reset-gpios = <&gpio2 RK_PB4 GPIO_ACTIVE_LOW>; + }; +}; + &pinctrl { + gmac { + gmac1_rst: ethphy1-rst { + rockchip,pins = <2 RK_PB4 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + leds { led_green_pin: led-green-pin { rockchip,pins = <1 RK_PD5 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3576-nanopi-m5.dts b/arch/arm64/boot/dts/rockchip/rk3576-nanopi-m5.dts index 7406a4adf8105a..227fe6f3ec7c7d 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-nanopi-m5.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-nanopi-m5.dts @@ -327,6 +327,9 @@ }; &hdmi { + frl-enable-gpios = <&gpio4 RK_PC6 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmi_txm0_pins &hdmi_tx_scl &hdmi_tx_sda &hdmi_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -813,6 +816,12 @@ }; }; + hdmi { + hdmi_tx_on_h: hdmi-tx-on-h { + rockchip,pins = <4 RK_PC6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { hym8563_int: hym8563-int { rockchip,pins = <0 RK_PA5 RK_FUNC_GPIO &pcfg_pull_up>; diff --git a/arch/arm64/boot/dts/rockchip/rk3576-nanopi-r76s.dts b/arch/arm64/boot/dts/rockchip/rk3576-nanopi-r76s.dts index 7ec27b05ff10e6..0a5cd5f6fd3349 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-nanopi-r76s.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-nanopi-r76s.dts @@ -70,7 +70,6 @@ hdmi-con { compatible = "hdmi-connector"; - hdmi-pwr-supply = <&vcc5v_hdmi_tx>; type = "a"; port { @@ -109,18 +108,6 @@ regulator-name = "vcc5v_dcin"; }; - vcc5v_hdmi_tx: regulator-vcc5v-hdmi-tx { - compatible = "regulator-fixed"; - enable-active-high; - gpios = <&gpio4 RK_PC6 GPIO_ACTIVE_HIGH>; - pinctrl-names = "default"; - pinctrl-0 = <&hdmi_tx_on_h>; - regulator-min-microvolt = <5000000>; - regulator-max-microvolt = <5000000>; - regulator-name = "vcc5v_hdmi_tx"; - vin-supply = <&vcc5v0_sys_s5>; - }; - vcc5v0_device_s0: regulator-vcc5v0-device-s0 { compatible = "regulator-fixed"; regulator-always-on; @@ -252,6 +239,9 @@ }; &hdmi { + frl-enable-gpios = <&gpio4 RK_PC6 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmi_txm0_pins &hdmi_tx_scl &hdmi_tx_sda &hdmi_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; diff --git a/arch/arm64/boot/dts/rockchip/rk3576-roc-pc.dts b/arch/arm64/boot/dts/rockchip/rk3576-roc-pc.dts index d0ab1d1e0e1195..54de3a50571f0a 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-roc-pc.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-roc-pc.dts @@ -283,6 +283,9 @@ }; &hdmi { + frl-enable-gpios = <&gpio2 RK_PB0 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmi_txm0_pins &hdmi_tx_scl &hdmi_tx_sda &hdmi_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -720,6 +723,12 @@ }; &pinctrl { + hdmi { + hdmi_tx_on_h: hdmi-tx-on-h { + rockchip,pins = <2 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { rtc_int_l: rtc-int-l { rockchip,pins = <0 RK_PA0 RK_FUNC_GPIO &pcfg_pull_up>; diff --git a/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts b/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts index 899a84b1fbf9e8..6e5c891a85f9f0 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts @@ -6,6 +6,7 @@ /dts-v1/; #include +#include #include #include #include @@ -45,6 +46,39 @@ shutdown-gpios = <&gpio2 RK_PD1 GPIO_ACTIVE_HIGH>; }; + fan: pwm-fan { + compatible = "pwm-fan"; + cooling-levels = <0 180 205 230 255>; + fan-supply = <&vcc_5v0_sys>; + pwms = <&pwm2_8ch_5 0 60000 0>; + #cooling-cells = <2>; + }; + + es8388_sound: es8388-sound { + compatible = "simple-audio-card"; + simple-audio-card,format = "i2s"; + simple-audio-card,mclk-fs = <256>; + simple-audio-card,name = "On-board Analog ES8388"; + simple-audio-card,widgets = "Microphone", "Headphone Mic", + "Headphone", "Headphone"; + simple-audio-card,routing = "Headphone", "LOUT1", + "Headphone", "ROUT1", + "Left PGA Mux", "Differential Mux", + "Differential Mux", "LINPUT1", + "Differential Mux", "RINPUT1", + "LINPUT1", "Headphone Mic", + "RINPUT1", "Headphone Mic"; + + simple-audio-card,cpu { + sound-dai = <&sai1>; + }; + + simple-audio-card,codec { + sound-dai = <&es8388>; + system-clock-frequency = <12288000>; + }; + }; + leds: leds { compatible = "gpio-leds"; pinctrl-names = "default"; @@ -65,6 +99,37 @@ }; }; + saradc_keys: adc-keys { + compatible = "adc-keys"; + io-channels = <&saradc 3>; + io-channel-names = "buttons"; + keyup-threshold-microvolt = <3000000>; + poll-interval = <100>; + + /* + * During insertion and removal of a regular set of headphones, + * i.e. one without a microphone, the voltage level briefly + * dips below the 220mV of the headset connection switch. + * By having a button definition with a KEY_RESERVED signal + * between 0 to 220, we ensure no driver implementation thinks + * that the closest thing to 0V is 220mV so clearly there must + * be a headset connected. + */ + + button-headset-disconnected { + label = "Headset Microphone Disconnected"; + linux,code = ; + press-threshold-microvolt = <0>; + }; + + button-headset-connected { + label = "Headset Microphone Connected"; + linux,code = ; + linux,input-type = ; + press-threshold-microvolt = <220000>; + }; + }; + vcc_5v0_dcin: regulator-vcc-5v0-dcin { compatible = "regulator-fixed"; regulator-always-on; @@ -272,7 +337,7 @@ &gmac0 { clock_in_out = "output"; phy-handle = <&rgmii_phy0>; - phy-mode = "rgmii-id"; + phy-mode = "rgmii-rxid"; pinctrl-names = "default"; pinctrl-0 = <ð0m0_miim ð0m0_tx_bus2 @@ -281,6 +346,8 @@ ð0m0_rgmii_bus ðm0_clk0_25m_out>; status = "okay"; + tx_delay = <0x20>; + rx_delay = <0x00>; }; &gpu { @@ -289,6 +356,9 @@ }; &hdmi { + frl-enable-gpios = <&gpio2 RK_PB0 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmi_txm0_pins &hdmi_tx_scl &hdmi_tx_sda &hdmi_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -682,6 +752,25 @@ }; }; +&i2c3 { + status = "okay"; + + es8388: audio-codec@10 { + compatible = "everest,es8388", "everest,es8328"; + reg = <0x10>; + clocks = <&cru CLK_SAI1_MCLKOUT_TO_IO>; + AVDD-supply = <&vcca_3v3_s0>; + DVDD-supply = <&vcc_3v3_s0>; + HPVDD-supply = <&vcca_3v3_s0>; + PVDD-supply = <&vcc_3v3_s0>; + assigned-clocks = <&cru CLK_SAI1_MCLKOUT_TO_IO>; + assigned-clock-rates = <12288000>; + #sound-dai-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&sai1m0_mclk>; + }; +}; + &i2c6 { pinctrl-names = "default"; pinctrl-0 = <&i2c6m3_xfer>; @@ -711,6 +800,36 @@ }; }; +&package_thermal { + polling-delay = <100>; + + trips { + package_fan0: package-fan0 { + temperature = <50000>; + hysteresis = <2000>; + type = "active"; + }; + + package_fan1: package-fan1 { + temperature = <60000>; + hysteresis = <2000>; + type = "active"; + }; + }; + + cooling-maps { + map1 { + trip = <&package_fan0>; + cooling-device = <&fan THERMAL_NO_LIMIT 1>; + }; + + map2 { + trip = <&package_fan1>; + cooling-device = <&fan 2 THERMAL_NO_LIMIT>; + }; + }; +}; + &pcie0 { pinctrl-names = "default"; pinctrl-0 = <&pcie_reset>; @@ -720,6 +839,19 @@ }; &pinctrl { + fan { + fan_pwm: fan-pwm { + rockchip,pins = + <4 RK_PC5 14 &pcfg_pull_down_drv_level_5>; + }; + }; + + hdmi { + hdmi_tx_on_h: hdmi-tx-on-h { + rockchip,pins = <2 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { hym8563_int: hym8563-int { rockchip,pins = <0 RK_PA0 RK_FUNC_GPIO &pcfg_pull_up>; @@ -770,10 +902,29 @@ }; }; +&pwm2_8ch_5 { + pinctrl-0 = <&fan_pwm>; + status = "okay"; +}; + +&sai1 { + pinctrl-names = "default"; + pinctrl-0 = <&sai1m0_lrck + &sai1m0_sclk + &sai1m0_sdi0 + &sai1m0_sdo0>; + status = "okay"; +}; + &sai6 { status = "okay"; }; +&saradc { + vref-supply = <&vcca1v8_pldo2_s0>; + status = "okay"; +}; + &sdmmc { bus-width = <4>; cap-mmc-highspeed; @@ -828,6 +979,9 @@ }; &ufshc { + vcc-supply = <&vcc_3v3_s0>; + vccq-supply = <&vcc_1v2_ufs_vccq_s0>; + vccq2-supply = <&vcc_1v8_ufs_vccq2_s0>; status = "okay"; }; diff --git a/arch/arm64/boot/dts/rockchip/rk3576.dtsi b/arch/arm64/boot/dts/rockchip/rk3576.dtsi index 28175d8200d57c..0dea3ec6df8700 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3576.dtsi @@ -344,6 +344,21 @@ }; }; + dp0_sound: dp0-sound { + compatible = "simple-audio-card"; + simple-audio-card,mclk-fs = <512>; + simple-audio-card,name = "DP0"; + status = "disabled"; + + simple-audio-card,codec { + sound-dai = <&dp 1>; + }; + + simple-audio-card,cpu { + sound-dai = <&spdif_tx3>; + }; + }; + gpu_opp_table: opp-table-gpu { compatible = "operating-points-v2"; @@ -973,11 +988,22 @@ reg = <0x0 0x26038000 0x0 0x1000>; }; + mipidphy0_grf: syscon@2603a000 { + compatible = "rockchip,rk3576-csidphy-grf", "syscon"; + reg = <0x0 0x2603a000 0x0 0x2000>; + clocks = <&cru PCLK_PMUPHY_ROOT>; + }; + ioc_grf: syscon@26040000 { compatible = "rockchip,rk3576-ioc-grf", "syscon", "simple-mfd"; reg = <0x0 0x26040000 0x0 0xc000>; }; + mipidphy1_grf: syscon@2604c000 { + compatible = "rockchip,rk3576-csidphy-grf", "syscon"; + reg = <0x0 0x2604c000 0x0 0x2000>; + }; + cru: clock-controller@27200000 { compatible = "rockchip,rk3576-cru"; reg = <0x0 0x27200000 0x0 0x50000>; @@ -1032,6 +1058,32 @@ status = "disabled"; }; + pwm0_2ch_0: pwm@27330000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x27330000 0x0 0x1000>; + clocks = <&cru CLK_PMU1PWM>, <&cru PCLK_PMU1PWM>, + <&cru CLK_PMU1PWM_OSC>, <&cru CLK_PMU1PWM_RC>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm0m0_ch0>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm0_2ch_1: pwm@27331000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x27331000 0x0 0x1000>; + clocks = <&cru CLK_PMU1PWM>, <&cru PCLK_PMU1PWM>, + <&cru CLK_PMU1PWM_OSC>, <&cru CLK_PMU1PWM_RC>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm0m0_ch1>; + #pwm-cells = <3>; + status = "disabled"; + }; + pmu: power-management@27380000 { compatible = "rockchip,rk3576-pmu", "syscon", "simple-mfd"; reg = <0x0 0x27380000 0x0 0x800>; @@ -1314,6 +1366,235 @@ #iommu-cells = <0>; }; + vicap: video-capture@27c10000 { + compatible = "rockchip,rk3576-vicap"; + reg = <0x0 0x27c10000 0x0 0x800>; + interrupts = ; + clocks = <&cru ACLK_VICAP>, <&cru HCLK_VICAP>, + <&cru DCLK_VICAP>, <&cru ICLK_CSIHOST0>, + <&cru ICLK_CSIHOST01>; + clock-names = "aclk", "hclk", "dclk", "iclk_host0", "iclk_host1"; + iommus = <&vicap_mmu>; + power-domains = <&power RK3576_PD_VI>; + resets = <&cru SRST_A_VICAP>, <&cru SRST_H_VICAP>, + <&cru SRST_D_VICAP>, <&cru SRST_VICAP_I0CLK>, + <&cru SRST_VICAP_I1CLK>, <&cru SRST_VICAP_I2CLK>, + <&cru SRST_VICAP_I3CLK>, <&cru SRST_VICAP_I4CLK>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + vicap_mipi0: port@1 { + reg = <1>; + + vicap_mipi0_input: endpoint { + remote-endpoint = <&csi0_output>; + }; + }; + + vicap_mipi1: port@2 { + reg = <2>; + + vicap_mipi1_input: endpoint { + remote-endpoint = <&csi1_output>; + }; + }; + + vicap_mipi2: port@3 { + reg = <3>; + + vicap_mipi2_input: endpoint { + remote-endpoint = <&csi2_output>; + }; + }; + + vicap_mipi3: port@4 { + reg = <4>; + + vicap_mipi3_input: endpoint { + remote-endpoint = <&csi3_output>; + }; + }; + + vicap_mipi4: port@5 { + reg = <5>; + + vicap_mipi4_input: endpoint { + remote-endpoint = <&csi4_output>; + }; + }; + }; + }; + + vicap_mmu: iommu@27c10800 { + compatible = "rockchip,rk3576-iommu", "rockchip,rk3568-iommu"; + reg = <0x0 0x27c10800 0x0 0x100>; + interrupts = ; + clocks = <&cru ACLK_VICAP>, <&cru HCLK_VICAP>; + clock-names = "aclk", "iface"; + power-domains = <&power RK3576_PD_VI>; + rockchip,disable-mmu-reset; + #iommu-cells = <0>; + status = "disabled"; + }; + + csi0: csi@27c80000 { + compatible = "rockchip,rk3576-mipi-csi2", "rockchip,rk3568-mipi-csi2"; + reg = <0x0 0x27c80000 0x0 0x10000>; + interrupts = , + ; + interrupt-names = "err1", "err2"; + clocks = <&cru PCLK_CSI_HOST_0>, <&cru ICLK_CSIHOST0>; + clock-names = "pclk", "iclk"; + phys = <&csi_dphy0>; + power-domains = <&power RK3576_PD_VI>; + resets = <&cru SRST_P_CSI_HOST_0>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + csi0_in: port@0 { + reg = <0>; + }; + + csi0_out: port@1 { + reg = <1>; + + csi0_output: endpoint { + remote-endpoint = <&vicap_mipi0_input>; + }; + }; + }; + }; + + csi1: csi@27c90000 { + compatible = "rockchip,rk3576-mipi-csi2", "rockchip,rk3568-mipi-csi2"; + reg = <0x0 0x27c90000 0x0 0x10000>; + interrupts = , + ; + interrupt-names = "err1", "err2"; + clocks = <&cru PCLK_CSI_HOST_1>; + clock-names = "pclk"; + phys = <&csi_dphy0>; + power-domains = <&power RK3576_PD_VI>; + resets = <&cru SRST_P_CSI_HOST_1>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + csi1_in: port@0 { + reg = <0>; + }; + + csi1_out: port@1 { + reg = <1>; + + csi1_output: endpoint { + remote-endpoint = <&vicap_mipi1_input>; + }; + }; + }; + }; + + csi2: csi@27ca0000 { + compatible = "rockchip,rk3576-mipi-csi2", "rockchip,rk3568-mipi-csi2"; + reg = <0x0 0x27ca0000 0x0 0x10000>; + interrupts = , + ; + interrupt-names = "err1", "err2"; + clocks = <&cru PCLK_CSI_HOST_2>; + clock-names = "pclk"; + phys = <&csi_dphy1>; + power-domains = <&power RK3576_PD_VI>; + resets = <&cru SRST_P_CSI_HOST_2>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + csi2_in: port@0 { + reg = <0>; + }; + + csi2_out: port@1 { + reg = <1>; + + csi2_output: endpoint { + remote-endpoint = <&vicap_mipi2_input>; + }; + }; + }; + }; + + csi3: csi@27cb0000 { + compatible = "rockchip,rk3576-mipi-csi2", "rockchip,rk3568-mipi-csi2"; + reg = <0x0 0x27cb0000 0x0 0x10000>; + interrupts = , + ; + interrupt-names = "err1", "err2"; + clocks = <&cru PCLK_CSI_HOST_3>; + clock-names = "pclk"; + phys = <&csi_dphy1>; + power-domains = <&power RK3576_PD_VI>; + resets = <&cru SRST_P_CSI_HOST_3>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + csi3_in: port@0 { + reg = <0>; + }; + + csi3_out: port@1 { + reg = <1>; + + csi3_output: endpoint { + remote-endpoint = <&vicap_mipi3_input>; + }; + }; + }; + }; + + csi4: csi@27cc0000 { + compatible = "rockchip,rk3576-mipi-csi2", "rockchip,rk3568-mipi-csi2"; + reg = <0x0 0x27cc0000 0x0 0x10000>; + interrupts = , + ; + interrupt-names = "err1", "err2"; + clocks = <&cru PCLK_CSI_HOST_4>; + clock-names = "pclk"; + phys = <&csi_dphy1>; + power-domains = <&power RK3576_PD_VI>; + resets = <&cru SRST_P_CSI_HOST_4>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + csi4_in: port@0 { + reg = <0>; + }; + + csi4_out: port@1 { + reg = <1>; + + csi4_output: endpoint { + remote-endpoint = <&vicap_mipi4_input>; + }; + }; + }; + }; + vop: vop@27d00000 { compatible = "rockchip,rk3576-vop"; reg = <0x0 0x27d00000 0x0 0x3000>, <0x0 0x27d05000 0x0 0x1000>; @@ -1338,6 +1619,8 @@ "dclk_vp1", "dclk_vp2", "pll_hdmiphy0"; + assigned-clocks = <&cru DCLK_VP0_SRC>, <&cru DCLK_VP1_SRC>; + assigned-clock-parents = <&cru PLL_VPLL>, <&cru PLL_VPLL>; iommus = <&vop_mmu>; power-domains = <&power RK3576_PD_VOP>; rockchip,grf = <&sys_grf>; @@ -1508,6 +1791,7 @@ resets = <&cru SRST_DP0>; phys = <&usbdp_phy PHY_TYPE_DP>; power-domains = <&power RK3576_PD_VO1>; + #sound-dai-cells = <1>; status = "disabled"; ports { @@ -2630,6 +2914,188 @@ status = "disabled"; }; + pwm1_6ch_0: pwm@2add0000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2add0000 0x0 0x1000>; + clocks = <&cru CLK_PWM1>, <&cru PCLK_PWM1>, + <&cru CLK_OSC_PWM1>, <&cru CLK_RC_PWM1>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm1m0_ch0>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm1_6ch_1: pwm@2add1000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2add1000 0x0 0x1000>; + clocks = <&cru CLK_PWM1>, <&cru PCLK_PWM1>, + <&cru CLK_OSC_PWM1>, <&cru CLK_RC_PWM1>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm1m0_ch1>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm1_6ch_2: pwm@2add2000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2add2000 0x0 0x1000>; + clocks = <&cru CLK_PWM1>, <&cru PCLK_PWM1>, + <&cru CLK_OSC_PWM1>, <&cru CLK_RC_PWM1>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm1m0_ch2>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm1_6ch_3: pwm@2add3000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2add3000 0x0 0x1000>; + clocks = <&cru CLK_PWM1>, <&cru PCLK_PWM1>, + <&cru CLK_OSC_PWM1>, <&cru CLK_RC_PWM1>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm1m0_ch3>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm1_6ch_4: pwm@2add4000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2add4000 0x0 0x1000>; + clocks = <&cru CLK_PWM1>, <&cru PCLK_PWM1>, + <&cru CLK_OSC_PWM1>, <&cru CLK_RC_PWM1>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm1m0_ch4>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm1_6ch_5: pwm@2add5000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2add5000 0x0 0x1000>; + clocks = <&cru CLK_PWM1>, <&cru PCLK_PWM1>, + <&cru CLK_OSC_PWM1>, <&cru CLK_RC_PWM1>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm1m0_ch5>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm2_8ch_0: pwm@2ade0000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2ade0000 0x0 0x1000>; + clocks = <&cru CLK_PWM2>, <&cru PCLK_PWM2>, + <&cru CLK_OSC_PWM2>, <&cru CLK_RC_PWM2>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm2m0_ch0>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm2_8ch_1: pwm@2ade1000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2ade1000 0x0 0x1000>; + clocks = <&cru CLK_PWM2>, <&cru PCLK_PWM2>, + <&cru CLK_OSC_PWM2>, <&cru CLK_RC_PWM2>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm2m0_ch1>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm2_8ch_2: pwm@2ade2000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2ade2000 0x0 0x1000>; + clocks = <&cru CLK_PWM2>, <&cru PCLK_PWM2>, + <&cru CLK_OSC_PWM2>, <&cru CLK_RC_PWM2>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm2m0_ch2>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm2_8ch_3: pwm@2ade3000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2ade3000 0x0 0x1000>; + clocks = <&cru CLK_PWM2>, <&cru PCLK_PWM2>, + <&cru CLK_OSC_PWM2>, <&cru CLK_RC_PWM2>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm2m0_ch3>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm2_8ch_4: pwm@2ade4000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2ade4000 0x0 0x1000>; + clocks = <&cru CLK_PWM2>, <&cru PCLK_PWM2>, + <&cru CLK_OSC_PWM2>, <&cru CLK_RC_PWM2>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm2m0_ch4>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm2_8ch_5: pwm@2ade5000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2ade5000 0x0 0x1000>; + clocks = <&cru CLK_PWM2>, <&cru PCLK_PWM2>, + <&cru CLK_OSC_PWM2>, <&cru CLK_RC_PWM2>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm2m0_ch5>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm2_8ch_6: pwm@2ade6000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2ade6000 0x0 0x1000>; + clocks = <&cru CLK_PWM2>, <&cru PCLK_PWM2>, + <&cru CLK_OSC_PWM2>, <&cru CLK_RC_PWM2>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm2m0_ch6>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm2_8ch_7: pwm@2ade7000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2ade7000 0x0 0x1000>; + clocks = <&cru CLK_PWM2>, <&cru PCLK_PWM2>, + <&cru CLK_OSC_PWM2>, <&cru CLK_RC_PWM2>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm2m0_ch7>; + #pwm-cells = <3>; + status = "disabled"; + }; + saradc: adc@2ae00000 { compatible = "rockchip,rk3576-saradc", "rockchip,rk3588-saradc"; reg = <0x0 0x2ae00000 0x0 0x10000>; @@ -2748,6 +3214,19 @@ status = "disabled"; }; + csi_dphy0: phy@2b030000 { + compatible = "rockchip,rk3576-csi-dphy"; + reg = <0x0 0x2b030000 0x0 0x8000>; + clocks = <&cru PCLK_CSIDPHY>; + clock-names = "pclk"; + #phy-cells = <0>; + power-domains = <&power RK3576_PD_VI>; + resets = <&cru SRST_P_CSIPHY>, <&cru SRST_SCAN_CSIPHY>; + reset-names = "apb", "phy"; + rockchip,grf = <&mipidphy0_grf>; + status = "disabled"; + }; + combphy0_ps: phy@2b050000 { compatible = "rockchip,rk3576-naneng-combphy"; reg = <0x0 0x2b050000 0x0 0x100>; @@ -2784,6 +3263,19 @@ status = "disabled"; }; + csi_dphy1: phy@2b070000 { + compatible = "rockchip,rk3576-csi-dphy"; + reg = <0x0 0x2b070000 0x0 0x8000>; + clocks = <&cru PCLK_CSIDPHY1>; + clock-names = "pclk"; + #phy-cells = <0>; + power-domains = <&power RK3576_PD_VI>; + resets = <&cru SRST_P_CSIDPHY1>, <&cru SRST_SCAN_CSIDPHY1>; + reset-names = "apb", "phy"; + rockchip,grf = <&mipidphy1_grf>; + status = "disabled"; + }; + usbdp_phy: phy@2b010000 { compatible = "rockchip,rk3576-usbdp-phy"; reg = <0x0 0x2b010000 0x0 0x10000>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-armsom-sige7.dts b/arch/arm64/boot/dts/rockchip/rk3588-armsom-sige7.dts index 39197ee198370b..c4854c0f22e26b 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-armsom-sige7.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-armsom-sige7.dts @@ -177,6 +177,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -347,6 +351,12 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { hym8563_int: hym8563-int { rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-armsom-w3.dts b/arch/arm64/boot/dts/rockchip/rk3588-armsom-w3.dts index 6ad2759ddccafe..3bbafdd89ede81 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-armsom-w3.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-armsom-w3.dts @@ -162,6 +162,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -182,6 +186,10 @@ }; &hdmi1 { + frl-enable-gpios = <&gpio4 RK_PA1 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim2_tx1_cec &hdmim0_tx1_hpd + &hdmim1_tx1_scl &hdmim1_tx1_sda &hdmi1_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -330,6 +338,16 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + hdmi1_tx_on_h: hdmi1-tx-on-h { + rockchip,pins = <4 RK_PA1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { hym8563_int: hym8563-int { rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi index 4fb8888c281c8c..43c81727354fab 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi @@ -1428,6 +1428,290 @@ clock-names = "aclk", "hclk"; power-domains = <&power RK3588_PD_AV1>; resets = <&cru SRST_A_AV1>, <&cru SRST_P_AV1>, <&cru SRST_A_AV1_BIU>, <&cru SRST_P_AV1_BIU>; + iommus = <&av1d_mmu>; + }; + + av1d_mmu: iommu@fdca0000 { + compatible = "rockchip,rk3588-av1-iommu", "verisilicon,iommu-1.2"; + reg = <0x0 0xfdca0000 0x0 0x600>; + interrupts = ; + clocks = <&cru ACLK_AV1>, <&cru PCLK_AV1>; + clock-names = "core", "iface"; + #iommu-cells = <0>; + power-domains = <&power RK3588_PD_AV1>; + }; + + vicap: video-capture@fdce0000 { + compatible = "rockchip,rk3588-vicap"; + reg = <0x0 0xfdce0000 0x0 0x800>; + interrupts = ; + clocks = <&cru ACLK_VICAP>, <&cru HCLK_VICAP>, + <&cru DCLK_VICAP>, <&cru ICLK_CSIHOST0>, + <&cru ICLK_CSIHOST1>; + clock-names = "aclk", "hclk", "dclk", "iclk_host0", "iclk_host1"; + iommus = <&vicap_mmu>; + power-domains = <&power RK3588_PD_VI>; + resets = <&cru SRST_A_VICAP>, <&cru SRST_H_VICAP>, + <&cru SRST_D_VICAP>, <&cru SRST_CSIHOST0_VICAP>, + <&cru SRST_CSIHOST1_VICAP>, <&cru SRST_CSIHOST2_VICAP>, + <&cru SRST_CSIHOST3_VICAP>, <&cru SRST_CSIHOST4_VICAP>, + <&cru SRST_CSIHOST5_VICAP>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + vicap_dvp: port@0 { + reg = <0>; + }; + + vicap_mipi0: port@1 { + reg = <1>; + +/* + vicap_mipi0_input: endpoint { + remote-endpoint = <&csi0_output>; + }; +*/ + }; + + vicap_mipi1: port@2 { + reg = <2>; + +/* + vicap_mipi1_input: endpoint { + remote-endpoint = <&csi1_output>; + }; +*/ + }; + + vicap_mipi2: port@3 { + reg = <3>; + + vicap_mipi2_input: endpoint { + remote-endpoint = <&csi2_output>; + }; + }; + + vicap_mipi3: port@4 { + reg = <4>; + + vicap_mipi3_input: endpoint { + remote-endpoint = <&csi3_output>; + }; + }; + + vicap_mipi4: port@5 { + reg = <5>; + + vicap_mipi4_input: endpoint { + remote-endpoint = <&csi4_output>; + }; + }; + + vicap_mipi5: port@6 { + reg = <6>; + + vicap_mipi5_input: endpoint { + remote-endpoint = <&csi5_output>; + }; + }; + }; + }; + + vicap_mmu: iommu@fdce0800 { + compatible = "rockchip,rk3588-iommu", "rockchip,rk3568-iommu"; + reg = <0x0 0xfdce0800 0x0 0x40>, <0x0 0xfdce0900 0x0 0x40>; + interrupts = ; + clock-names = "aclk", "iface"; + clocks = <&cru ACLK_VICAP>, <&cru HCLK_VICAP>; + #iommu-cells = <0>; + power-domains = <&power RK3588_PD_VI>; + rockchip,disable-mmu-reset; + status = "disabled"; + }; + +/* + csi0: csi@fdd10000 { + compatible = "rockchip,rk3588-mipi-csi2", "rockchip,rk3568-mipi-csi2"; + reg = <0x0 0xfdd10000 0x0 0x10000>; + interrupts = , + ; + interrupt-names = "err1", "err2"; + clocks = <&cru PCLK_CSI_HOST_0>; + phys = <&mipidcphy0 PHY_TYPE_DPHY>; + power-domains = <&power RK3588_PD_VI>; + resets = <&cru SRST_P_CSI_HOST_0>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + csi0_in: port@0 { + reg = <0>; + }; + + csi0_out: port@1 { + reg = <1>; + + csi0_output: endpoint { + remote-endpoint = <&vicap_mipi0_input>; + }; + }; + }; + }; + + csi1: csi@fdd20000 { + compatible = "rockchip,rk3588-mipi-csi2", "rockchip,rk3568-mipi-csi2"; + reg = <0x0 0xfdd20000 0x0 0x10000>; + interrupts = , + ; + interrupt-names = "err1", "err2"; + clocks = <&cru PCLK_CSI_HOST_1>; + phys = <&mipidcphy1 PHY_TYPE_DPHY>; + power-domains = <&power RK3588_PD_VI>; + resets = <&cru SRST_P_CSI_HOST_1>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + csi1_in: port@0 { + reg = <0>; + }; + + csi1_out: port@1 { + reg = <1>; + + csi1_output: endpoint { + remote-endpoint = <&vicap_mipi1_input>; + }; + }; + }; + }; +*/ + + csi2: csi@fdd30000 { + compatible = "rockchip,rk3588-mipi-csi2", "rockchip,rk3568-mipi-csi2"; + reg = <0x0 0xfdd30000 0x0 0x10000>; + interrupts = , + ; + interrupt-names = "err1", "err2"; + clocks = <&cru PCLK_CSI_HOST_2>; + phys = <&csi_dphy0>; + power-domains = <&power RK3588_PD_VI>; + resets = <&cru SRST_P_CSI_HOST_2>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + csi2_in: port@0 { + reg = <0>; + }; + + csi2_out: port@1 { + reg = <1>; + + csi2_output: endpoint { + remote-endpoint = <&vicap_mipi2_input>; + }; + }; + }; + }; + + csi3: csi@fdd40000 { + compatible = "rockchip,rk3588-mipi-csi2", "rockchip,rk3568-mipi-csi2"; + reg = <0x0 0xfdd40000 0x0 0x10000>; + interrupts = , + ; + interrupt-names = "err1", "err2"; + clocks = <&cru PCLK_CSI_HOST_3>; + phys = <&csi_dphy0>; + power-domains = <&power RK3588_PD_VI>; + resets = <&cru SRST_P_CSI_HOST_3>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + csi3_in: port@0 { + reg = <0>; + }; + + csi3_out: port@1 { + reg = <1>; + + csi3_output: endpoint { + remote-endpoint = <&vicap_mipi3_input>; + }; + }; + }; + }; + + csi4: csi@fdd50000 { + compatible = "rockchip,rk3588-mipi-csi2", "rockchip,rk3568-mipi-csi2"; + reg = <0x0 0xfdd50000 0x0 0x10000>; + interrupts = , + ; + interrupt-names = "err1", "err2"; + clocks = <&cru PCLK_CSI_HOST_4>; + phys = <&csi_dphy1>; + power-domains = <&power RK3588_PD_VI>; + resets = <&cru SRST_P_CSI_HOST_4>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + csi4_in: port@0 { + reg = <0>; + }; + + csi4_out: port@1 { + reg = <1>; + + csi4_output: endpoint { + remote-endpoint = <&vicap_mipi4_input>; + }; + }; + }; + }; + + csi5: csi@fdd60000 { + compatible = "rockchip,rk3588-mipi-csi2", "rockchip,rk3568-mipi-csi2"; + reg = <0x0 0xfdd60000 0x0 0x10000>; + interrupts = , + ; + interrupt-names = "err1", "err2"; + clocks = <&cru PCLK_CSI_HOST_5>; + phys = <&csi_dphy1>; + power-domains = <&power RK3588_PD_VI>; + resets = <&cru SRST_P_CSI_HOST_5>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + csi5_in: port@0 { + reg = <0>; + }; + + csi5_out: port@1 { + reg = <1>; + + csi5_output: endpoint { + remote-endpoint = <&vicap_mipi5_input>; + }; + }; + }; }; vop: vop@fdd90000 { @@ -1453,6 +1737,18 @@ "pll_hdmiphy0"; iommus = <&vop_mmu>; power-domains = <&power RK3588_PD_VOP>; + resets = <&cru SRST_A_VOP>, + <&cru SRST_H_VOP>, + <&cru SRST_D_VOP0>, + <&cru SRST_D_VOP1>, + <&cru SRST_D_VOP2>, + <&cru SRST_D_VOP3>; + reset-names = "aclk", + "hclk", + "dclk_vp0", + "dclk_vp1", + "dclk_vp2", + "dclk_vp3"; rockchip,grf = <&sys_grf>; rockchip,vop-grf = <&vop_grf>; rockchip,vo1-grf = <&vo1_grf>; @@ -1650,7 +1946,7 @@ phys = <&usbdp_phy0 PHY_TYPE_DP>; power-domains = <&power RK3588_PD_VO0>; resets = <&cru SRST_DP0>; - #sound-dai-cells = <0>; + #sound-dai-cells = <1>; status = "disabled"; ports { diff --git a/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-evb.dts b/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-evb.dts index 3d5c8b753208ab..66e3c20d7b4f13 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-evb.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-evb.dts @@ -125,6 +125,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB0 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -145,8 +149,10 @@ }; &hdmi1 { + frl-enable-gpios = <&gpio4 RK_PB2 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim2_tx1_cec &hdmim0_tx1_hpd &hdmim1_tx1_scl &hdmim1_tx1_sda + &hdmi1_tx_on_h>; pinctrl-names = "default"; - pinctrl-0 = <&hdmim2_tx1_cec &hdmim0_tx1_hpd &hdmim1_tx1_scl &hdmim1_tx1_sda>; status = "okay"; }; @@ -211,6 +217,16 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + hdmi1_tx_on_h: hdmi1-tx-on-h { + rockchip,pins = <4 RK_PB2 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + lcd { lcdpwr_en: lcdpwr-en { rockchip,pins = <1 RK_PC4 RK_FUNC_GPIO &pcfg_pull_down>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-genbook.dts b/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-genbook.dts index 738637ecaf557f..e05e6b2d513673 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-genbook.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-genbook.dts @@ -184,7 +184,10 @@ /* HDMI CEC is not used */ &hdmi0 { - pinctrl-0 = <&hdmim0_tx0_hpd &hdmim0_tx0_scl &hdmim0_tx0_sda>; + frl-enable-gpios = <&gpio4 RK_PB0 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_hpd &hdmim0_tx0_scl &hdmim0_tx0_sda + &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -277,6 +280,12 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + lcd { lcdpwr_en: lcdpwr-en { rockchip,pins = <0 RK_PC4 RK_FUNC_GPIO &pcfg_pull_down>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts b/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts index 09bc7b68dcc057..415bbe4df2f254 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts @@ -121,6 +121,15 @@ pwms = <&pwm2 0 25000 0>; }; + bluetooth-rfkill { + compatible = "rfkill-gpio"; + label = "rfkill-bluetooth"; + radio-type = "bluetooth"; + shutdown-gpios = <&gpio3 RK_PA6 GPIO_ACTIVE_LOW>; + pinctrl-names = "default"; + pinctrl-0 = <&bluetooth_pwren>; + }; + hdmi0-con { compatible = "hdmi-connector"; type = "a"; @@ -204,6 +213,15 @@ regulator-max-microvolt = <12000000>; }; + vcc3v3_lcd_mipi: regulator-vcc3v3-lcd-mipi { + compatible = "regulator-fixed"; + regulator-name = "vcc3v3_lcd_mipi"; + regulator-boot-on; + enable-active-high; + gpio = <&gpio1 RK_PC4 GPIO_ACTIVE_HIGH>; + vin-supply = <&vcc_3v3_s0>; + }; + vcc3v3_pcie30: regulator-vcc3v3-pcie30 { compatible = "regulator-fixed"; regulator-name = "vcc3v3_pcie30"; @@ -338,6 +356,43 @@ cpu-supply = <&vdd_cpu_lit_s0>; }; +&dsi0 { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + panel@0 { + compatible = "wanchanglong,w552793baa", "raydium,rm67200"; + reg = <0>; + backlight = <&backlight>; + pinctrl-names = "default"; + pinctrl-0 = <&lcd_rst_gpio>; + vdd-supply = <&vcc3v3_lcd_mipi>; + iovcc-supply = <&vcc3v3_lcd_mipi>; + vsp-supply = <&vcc5v0_sys>; + vsn-supply = <&vcc5v0_sys>; + reset-gpios = <&gpio2 RK_PB4 GPIO_ACTIVE_LOW>; + + port { + mipi_panel_in: endpoint { + remote-endpoint = <&dsi0_out_panel>; + }; + }; + }; +}; + +&dsi0_in { + dsi0_in_vp3: endpoint { + remote-endpoint = <&vp3_out_dsi0>; + }; +}; + +&dsi0_out { + dsi0_out_panel: endpoint { + remote-endpoint = <&mipi_panel_in>; + }; +}; + &gmac0 { clock_in_out = "output"; phy-handle = <&rgmii_phy>; @@ -360,6 +415,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -376,6 +435,10 @@ }; &hdmi1 { + frl-enable-gpios = <&gpio4 RK_PB2 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim2_tx1_cec &hdmim0_tx1_hpd + &hdmim1_tx1_scl &hdmim1_tx1_sda &hdmi1_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -442,24 +505,24 @@ port@0 { reg = <0>; - usbc0_orien_sw: endpoint { - remote-endpoint = <&usbdp_phy0_orientation_switch>; + usbc0_hs: endpoint { + remote-endpoint = <&usb_host0_xhci_to_usbc0>; }; }; port@1 { reg = <1>; - usbc0_role_sw: endpoint { - remote-endpoint = <&dwc3_0_role_switch>; + usbc0_ss: endpoint { + remote-endpoint = <&usbdp_phy0_ss>; }; }; port@2 { reg = <2>; - dp_altmode_mux: endpoint { - remote-endpoint = <&usbdp_phy0_dp_altmode_mux>; + usbc0_sbu: endpoint { + remote-endpoint = <&usbdp_phy0_sbu>; }; }; }; @@ -479,6 +542,22 @@ }; }; +&i2c6 { + status = "okay"; + + gt1x: touchscreen@14 { + compatible = "goodix,gt1158"; + reg = <0x14>; + interrupt-parent = <&gpio0>; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&touchscreen_pins>; + reset-gpios = <&gpio0 RK_PD2 GPIO_ACTIVE_HIGH>; + AVDD28-supply = <&vcc3v3_lcd_mipi>; + VDDIO-supply = <&vcc3v3_lcd_mipi>; + }; +}; + &i2c7 { status = "okay"; @@ -518,6 +597,10 @@ }; }; +&mipidcphy0 { + status = "okay"; +}; + &pcie2x1l0 { pinctrl-names = "default"; pinctrl-0 = <&pcie2_0_rst>, <&pcie2_0_wake>, <&pcie2_0_clkreq>, <&wifi_host_wake_irq>; @@ -546,7 +629,7 @@ &pcie2x1l1 { reset-gpios = <&gpio4 RK_PA2 GPIO_ACTIVE_HIGH>; pinctrl-names = "default"; - pinctrl-0 = <&pcie2_1_rst>, <&rtl8111_isolate>, <&pcie30x1m1_1_clkreqn>; + pinctrl-0 = <&pcie2_1_rst>, <&rtl8111_isolate>, <&pcie30x1m1_1_clkreqn>, <&pcie30x1m1_1_waken>; supports-clkreq; status = "okay"; }; @@ -599,6 +682,12 @@ }; }; + bluetooth { + bluetooth_pwren: bluetooth-pwren { + rockchip,pins = <3 RK_PA6 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + rtl8111 { rtl8111_isolate: rtl8111-isolate { rockchip,pins = <1 RK_PA4 RK_FUNC_GPIO &pcfg_pull_up>; @@ -612,6 +701,16 @@ }; + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + hdmi1_tx_on_h: hdmi1-tx-on-h { + rockchip,pins = <4 RK_PB2 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hdmirx { hdmirx_hpd: hdmirx-5v-detection { rockchip,pins = <2 RK_PB5 RK_FUNC_GPIO &pcfg_pull_none>; @@ -624,6 +723,12 @@ }; }; + mipi_dsi { + lcd_rst_gpio: lcd-rst { + rockchip,pins = <2 RK_PB4 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + pcie2 { pcie2_0_rst: pcie2-0-rst { rockchip,pins = <4 RK_PA5 RK_FUNC_GPIO &pcfg_pull_none>; @@ -652,6 +757,14 @@ }; }; + touchscreen { + touchscreen_pins: touchscreen-pins { + rockchip,pins = + <0 RK_PD2 RK_FUNC_GPIO &pcfg_pull_up>, + <0 RK_PD3 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + usb { vcc5v0_host_en: vcc5v0-host-en { rockchip,pins = <4 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; @@ -1398,14 +1511,14 @@ #address-cells = <1>; #size-cells = <0>; - usbdp_phy0_orientation_switch: endpoint@0 { + usbdp_phy0_ss: endpoint@0 { reg = <0>; - remote-endpoint = <&usbc0_orien_sw>; + remote-endpoint = <&usbc0_ss>; }; - usbdp_phy0_dp_altmode_mux: endpoint@1 { + usbdp_phy0_sbu: endpoint@1 { reg = <1>; - remote-endpoint = <&dp_altmode_mux>; + remote-endpoint = <&usbc0_sbu>; }; }; }; @@ -1430,9 +1543,9 @@ #address-cells = <1>; #size-cells = <0>; - dwc3_0_role_switch: endpoint@0 { + usb_host0_xhci_to_usbc0: endpoint@0 { reg = <0>; - remote-endpoint = <&usbc0_role_sw>; + remote-endpoint = <&usbc0_hs>; }; }; }; @@ -1463,3 +1576,13 @@ remote-endpoint = <&hdmi1_in_vp1>; }; }; + +&vp3 { + #address-cells = <1>; + #size-cells = <0>; + + vp3_out_dsi0: endpoint@ROCKCHIP_VOP2_EP_MIPI0 { + reg = ; + remote-endpoint = <&dsi0_in_vp3>; + }; +}; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-evb2-v10.dts b/arch/arm64/boot/dts/rockchip/rk3588-evb2-v10.dts index 60ba6ac55b2399..b7d2cb4561484b 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-evb2-v10.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-evb2-v10.dts @@ -143,6 +143,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio3 RK_PC5 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -183,6 +187,12 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <3 RK_PC5 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { hym8563_int: hym8563-int { rockchip,pins = <0 RK_PD4 RK_FUNC_GPIO &pcfg_pull_up>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-extra.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-extra.dtsi index a2640014ee0421..b4d7843918cce7 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-extra.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-extra.dtsi @@ -223,7 +223,7 @@ phys = <&usbdp_phy1 PHY_TYPE_DP>; power-domains = <&power RK3588_PD_VO0>; resets = <&cru SRST_DP1>; - #sound-dai-cells = <0>; + #sound-dai-cells = <1>; status = "disabled"; ports { diff --git a/arch/arm64/boot/dts/rockchip/rk3588-firefly-itx-3588j.dts b/arch/arm64/boot/dts/rockchip/rk3588-firefly-itx-3588j.dts index e086114c763487..e43afb0c53fbdb 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-firefly-itx-3588j.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-firefly-itx-3588j.dts @@ -322,6 +322,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PA0 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -487,6 +491,12 @@ }; }; + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PA0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { hym8563_int: hym8563-int { rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_up>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-friendlyelec-cm3588-nas.dts b/arch/arm64/boot/dts/rockchip/rk3588-friendlyelec-cm3588-nas.dts index 10a7d3691a26f8..0dce96ca8c288e 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-friendlyelec-cm3588-nas.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-friendlyelec-cm3588-nas.dts @@ -331,6 +331,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -347,6 +351,10 @@ }; &hdmi1 { + frl-enable-gpios = <&gpio4 RK_PB2 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim2_tx1_cec &hdmim0_tx1_hpd + &hdmim1_tx1_scl &hdmim1_tx1_sda &hdmi1_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -520,6 +528,16 @@ }; }; + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + hdmi1_tx_on_h: hdmi1-tx-on-h { + rockchip,pins = <4 RK_PB2 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hdmirx { hdmirx_hpd: hdmirx-5v-detection { rockchip,pins = <3 RK_PD4 RK_FUNC_GPIO &pcfg_pull_up>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-h96-max-v58.dts b/arch/arm64/boot/dts/rockchip/rk3588-h96-max-v58.dts index 73d8ce4fde2b81..7c2a1e6bcff9b1 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-h96-max-v58.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-h96-max-v58.dts @@ -210,6 +210,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -339,6 +343,12 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { hym8563_int: hym8563-int { rockchip,pins = <0 RK_PD3 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-jaguar.dts b/arch/arm64/boot/dts/rockchip/rk3588-jaguar.dts index 5f5d89a33a4ae7..033db78aff676e 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-jaguar.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-jaguar.dts @@ -278,8 +278,10 @@ &hdmi0 { /* No CEC on Jaguar */ + frl-enable-gpios = <&gpio0 RK_PD3 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_hpd &hdmim0_tx0_scl &hdmim0_tx0_sda + &hdmi0_tx_on_h>; pinctrl-names = "default"; - pinctrl-0 = <&hdmim0_tx0_hpd &hdmim0_tx0_scl &hdmim0_tx0_sda>; status = "okay"; }; @@ -571,6 +573,12 @@ }; }; + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <0 RK_PD3 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + leds { led1_pin: led1-pin { rockchip,pins = <1 RK_PD4 RK_FUNC_GPIO &pcfg_pull_none>; @@ -1134,9 +1142,6 @@ status = "okay"; port { - #address-cells = <1>; - #size-cells = <0>; - usb_host0_xhci_drd_sw: endpoint { remote-endpoint = <&usbc0_hs>; }; @@ -1149,9 +1154,6 @@ status = "okay"; port { - #address-cells = <1>; - #size-cells = <0>; - usb_host1_xhci_drd_sw: endpoint { remote-endpoint = <&usbc1_hs>; }; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-mnt-reform2.dts b/arch/arm64/boot/dts/rockchip/rk3588-mnt-reform2.dts index 78a4e896f665b5..36a5977d079520 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-mnt-reform2.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-mnt-reform2.dts @@ -148,6 +148,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PA0 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -205,6 +209,12 @@ }; }; + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PA0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + pcie2 { pcie2_0_rst: pcie2-0-rst { rockchip,pins = <3 RK_PD1 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-nanopc-t6.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-nanopc-t6.dtsi index 84b6b53f016ab1..3b0903d0e3e2a6 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-nanopc-t6.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-nanopc-t6.dtsi @@ -361,6 +361,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -381,6 +385,10 @@ }; &hdmi1 { + frl-enable-gpios = <&gpio4 RK_PB2 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim2_tx1_cec &hdmim0_tx1_hpd + &hdmim1_tx1_scl &hdmim1_tx1_sda &hdmi1_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -682,6 +690,16 @@ }; }; + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + hdmi1_tx_on_h: hdmi1-tx-on-h { + rockchip,pins = <4 RK_PB2 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hdmirx { hdmirx_hpd: hdmirx-5v-detection { rockchip,pins = <1 RK_PD5 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-max.dts b/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-max.dts index 8b1d35760c3bc9..762f9fd966a42b 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-max.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-max.dts @@ -35,6 +35,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -55,9 +59,10 @@ }; &hdmi1 { - pinctrl-names = "default"; + frl-enable-gpios = <&gpio4 RK_PB2 GPIO_ACTIVE_LOW>; pinctrl-0 = <&hdmim0_tx1_cec &hdmim0_tx1_hpd - &hdmim1_tx1_scl &hdmim1_tx1_sda>; + &hdmim1_tx1_scl &hdmim1_tx1_sda &hdmi1_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -102,6 +107,15 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + hdmi1_tx_on_h: hdmi1-tx-on-h { + rockchip,pins = <4 RK_PB2 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; usb { usb_otg_pwren: usb-otg-pwren { diff --git a/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-plus.dts b/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-plus.dts index 9950d1147e129d..7142938d297146 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-plus.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-plus.dts @@ -121,6 +121,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -141,6 +145,10 @@ }; &hdmi1 { + frl-enable-gpios = <&gpio4 RK_PB2 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim2_tx1_cec &hdmim0_tx1_hpd + &hdmim1_tx1_scl &hdmim1_tx1_sda &hdmi1_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -278,6 +286,16 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + hdmi1_tx_on_h: hdmi1-tx-on-h { + rockchip,pins = <4 RK_PB2 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { hym8563_int: hym8563-int { rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-ultra.dts b/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-ultra.dts index f8c6c080e418db..2b693dfb434c4e 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-ultra.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-ultra.dts @@ -25,9 +25,10 @@ }; &hdmi1 { - pinctrl-names = "default"; + frl-enable-gpios = <&gpio4 RK_PB2 GPIO_ACTIVE_LOW>; pinctrl-0 = <&hdmim0_tx1_cec &hdmim0_tx1_hpd - &hdmim1_tx1_scl &hdmim1_tx1_sda>; + &hdmim1_tx1_scl &hdmim1_tx1_sda &hdmi1_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -64,6 +65,12 @@ }; &pinctrl { + hdmi { + hdmi1_tx_on_h: hdmi1-tx-on-h { + rockchip,pins = <4 RK_PB2 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + usb { usb_otg_pwren: usb-otg-pwren { rockchip,pins = <4 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-roc-rt.dts b/arch/arm64/boot/dts/rockchip/rk3588-roc-rt.dts index 2d6fed2a84a38d..c50217a7b5596b 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-roc-rt.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-roc-rt.dts @@ -325,6 +325,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB0 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -345,6 +349,10 @@ }; &hdmi1 { + frl-enable-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim2_tx1_cec &hdmim0_tx1_hpd + &hdmim1_tx1_scl &hdmim1_tx1_sda &hdmi1_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -583,6 +591,16 @@ }; }; + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + hdmi1_tx_on_h: hdmi1-tx-on-h { + rockchip,pins = <4 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { hym8563_int: hym8563-int { rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_up>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-rock-5-itx.dts b/arch/arm64/boot/dts/rockchip/rk3588-rock-5-itx.dts index f7dd01d6fa0ab3..d5c0b01987fc2e 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-rock-5-itx.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-rock-5-itx.dts @@ -346,8 +346,10 @@ }; &hdmi1 { + frl-enable-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_LOW>; pinctrl-0 = <&hdmim0_tx1_cec &hdmim0_tx1_hpd - &hdmim1_tx1_scl &hdmim1_tx1_sda>; + &hdmim1_tx1_scl &hdmim1_tx1_sda &hdmi1_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -696,6 +698,12 @@ }; &pinctrl { + hdmi { + hdmi1_tx_on_h: hdmi1-tx-on-h { + rockchip,pins = <4 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { rtc_int: rtc-int { rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi index bf4a1d2e55ca30..9c069737c76579 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi @@ -177,12 +177,32 @@ cpu-supply = <&vdd_cpu_lit_s0>; }; +&dp0 { + status = "okay"; +}; + +&dp0_in { + dp0_in_vp2: endpoint { + remote-endpoint = <&vp2_out_dp0>; + }; +}; + +&dp0_out { + dp0_out_con: endpoint { + remote-endpoint = <&usbdp_phy0_dp_in>; + }; +}; + &gpu { mali-supply = <&vdd_gpu_s0>; status = "okay"; }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -203,8 +223,10 @@ }; &hdmi1 { + frl-enable-gpios = <&gpio4 RK_PA1 GPIO_ACTIVE_LOW>; pinctrl-0 = <&hdmim0_tx1_cec &hdmim0_tx1_hpd - &hdmim1_tx1_scl &hdmim1_tx1_sda>; + &hdmim1_tx1_scl &hdmim1_tx1_sda &hdmi1_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -304,6 +326,12 @@ }; }; +&i2c3 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c3m0_xfer>; + status = "okay"; +}; + &i2c4 { pinctrl-names = "default"; pinctrl-0 = <&i2c4m1_xfer>; @@ -352,21 +380,21 @@ port@0 { reg = <0>; usbc0_hs: endpoint { - remote-endpoint = <&usb_host0_xhci_to_usbc0>; + remote-endpoint = <&usb_host0_xhci_hs>; }; }; port@1 { reg = <1>; usbc0_ss: endpoint { - remote-endpoint = <&usbdp_phy0_ss>; + remote-endpoint = <&usbdp_phy0_ss_out>; }; }; port@2 { reg = <2>; usbc0_sbu: endpoint { - remote-endpoint = <&usbdp_phy0_sbu>; + remote-endpoint = <&usbdp_phy0_dp_out>; }; }; }; @@ -505,6 +533,16 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + hdmi1_tx_on_h: hdmi1-tx-on-h { + rockchip,pins = <4 RK_PA1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { hym8563_int: hym8563-int { rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; @@ -592,8 +630,8 @@ no-sdio; no-sd; non-removable; - mmc-hs400-1_8v; - mmc-hs400-enhanced-strobe; + max-frequency = <150000000>; + mmc-hs200-1_8v; status = "okay"; }; @@ -1002,18 +1040,41 @@ orientation-switch; status = "okay"; - port { + ports { #address-cells = <1>; #size-cells = <0>; - usbdp_phy0_ss: endpoint@0 { + + port@0 { reg = <0>; - remote-endpoint = <&usbc0_ss>; + + usbdp_phy0_ss_out: endpoint { + remote-endpoint = <&usbc0_ss>; + }; }; - usbdp_phy0_sbu: endpoint@1 { + port@1 { reg = <1>; - remote-endpoint = <&usbc0_sbu>; + + usbdp_phy0_ss_in: endpoint { + remote-endpoint = <&usb_host0_xhci_ss>; + }; + }; + + port@2 { + reg = <2>; + + usbdp_phy0_dp_in: endpoint { + remote-endpoint = <&dp0_out_con>; + }; + }; + + port@3 { + reg = <3>; + + usbdp_phy0_dp_out: endpoint { + remote-endpoint = <&usbc0_sbu>; + }; }; }; }; @@ -1034,9 +1095,24 @@ usb-role-switch; status = "okay"; - port { - usb_host0_xhci_to_usbc0: endpoint { - remote-endpoint = <&usbc0_hs>; + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + usb_host0_xhci_hs: endpoint { + remote-endpoint = <&usbc0_hs>; + }; + }; + + port@1 { + reg = <1>; + + usb_host0_xhci_ss: endpoint { + remote-endpoint = <&usbdp_phy0_ss_in>; + }; }; }; }; @@ -1075,3 +1151,10 @@ remote-endpoint = <&hdmi1_in_vp1>; }; }; + +&vp2 { + vp2_out_dp0: endpoint@a { + reg = ; + remote-endpoint = <&dp0_in_vp2>; + }; +}; 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 new file mode 100644 index 00000000000000..a6d52607fa93ce --- /dev/null +++ b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-plus-radxa-cam4k-cam0.dtso @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Device tree overlay for the Radxa Camera 4K attached to the CAM0 port of + * the Radxa ROCK 5B+. + */ + +/dts-v1/; +/plugin/; + +#include +#include +#include + +&{/} { + vcc_cam0: regulator-vcc-cam0 { + compatible = "regulator-fixed"; + enable-active-high; + gpio = <&gpio1 RK_PB0 GPIO_ACTIVE_HIGH>; + pinctrl-names = "default"; + pinctrl-0 = <&cam0_power0_en>; + regulator-name = "vcc_cam0"; + vin-supply = <&vcc_3v3_s3>; + }; +}; + +&i2c3 { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + imx415: camera-sensor@1a { + compatible = "sony,imx415"; + reg = <0x1a>; + assigned-clocks = <&cru CLK_MIPI_CAMARAOUT_M3>; + assigned-clock-rates = <37125000>; + avdd-supply = <&vcc_cam0>; + clocks = <&cru CLK_MIPI_CAMARAOUT_M3>; + clock-names = "inck"; // TODO should not be necessary according to binding, apply driver fix + dvdd-supply = <&vcc_cam0>; + orientation = <2>; /* External */ + ovdd-supply = <&vcc_cam0>; + pinctrl-names = "default"; + pinctrl-0 = <&cam0_rstn &mipim0_camera3_clk>; + reset-gpios = <&gpio4 RK_PA0 GPIO_ACTIVE_LOW>; + + port { + imx415_output: endpoint { + data-lanes = <1 2 3 4>; + link-frequencies = /bits/ 64 <445500000>; + remote-endpoint = <&csi2_input>; + }; + }; + }; +}; + +&pinctrl { + cam0 { + cam0_power0_en: cam0-power0-en-pinctrl { + rockchip,pins = <1 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + cam0_rstn: cam0-rstn-pinctrl { + rockchip,pins = <4 RK_PA0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; +}; + +&csi2 { + status = "okay"; +}; + +&csi2_in { + csi2_input: endpoint { + data-lanes = <1 2 3 4>; + link-frequencies = /bits/ 64 <445500000>; + remote-endpoint = <&imx415_output>; + }; +}; + +&csi_dphy0 { + status = "okay"; +}; + +&vicap { + status = "okay"; +}; + +&vicap_mmu { + status = "okay"; +}; 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 new file mode 100644 index 00000000000000..d7e9d873096540 --- /dev/null +++ b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-plus-radxa-cam4k-cam1.dtso @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Device tree overlay for the Radxa Camera 4K attached to the CAM1 port of + * the Radxa ROCK 5B+. + */ + +/dts-v1/; +/plugin/; + +#include +#include +#include + +&{/} { + vcc_cam1: regulator-vcc-cam1 { + compatible = "regulator-fixed"; + enable-active-high; + gpio = <&gpio2 RK_PA6 GPIO_ACTIVE_HIGH>; + pinctrl-names = "default"; + pinctrl-0 = <&cam1_power0_en>; + regulator-name = "vcc_cam1"; + vin-supply = <&vcc_3v3_s3>; + }; +}; + +&i2c4 { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + cam1_imx415: camera-sensor@1a { + compatible = "sony,imx415"; + reg = <0x1a>; + assigned-clocks = <&cru CLK_MIPI_CAMARAOUT_M4>; + assigned-clock-rates = <37125000>; + avdd-supply = <&vcc_cam1>; + clocks = <&cru CLK_MIPI_CAMARAOUT_M4>; + clock-names = "inck"; // TODO should not be necessary according to binding, apply driver fix + dvdd-supply = <&vcc_cam1>; + orientation = <2>; /* External */ + ovdd-supply = <&vcc_cam1>; + pinctrl-names = "default"; + pinctrl-0 = <&cam1_rstn &mipim0_camera4_clk>; + reset-gpios = <&gpio2 RK_PB0 GPIO_ACTIVE_LOW>; + + port { + cam1_imx415_output: endpoint { + data-lanes = <1 2 3 4>; + link-frequencies = /bits/ 64 <445500000>; + remote-endpoint = <&csi4_input>; + }; + }; + }; +}; + +&pinctrl { + cam1 { + cam1_power0_en: cam1-power0-en-pinctrl { + rockchip,pins = <2 RK_PA6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + cam1_rstn: cam1-rstn-pinctrl { + rockchip,pins = <2 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; +}; + +&csi4 { + status = "okay"; +}; + +&csi4_in { + csi4_input: endpoint { + data-lanes = <1 2 3 4>; + link-frequencies = /bits/ 64 <445500000>; + remote-endpoint = <&cam1_imx415_output>; + }; +}; + +&csi_dphy1 { + status = "okay"; +}; + +&vicap { + status = "okay"; +}; + +&vicap_mmu { + status = "okay"; +}; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-tiger-haikou.dts b/arch/arm64/boot/dts/rockchip/rk3588-tiger-haikou.dts index caa43d1abf1793..08b7d477d8dbc4 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-tiger-haikou.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-tiger-haikou.dts @@ -172,8 +172,9 @@ * While HDMI-CEC is present on the Q7 connector, it is not * connected on Haikou itself. */ + pinctrl-0 = <&hdmim0_tx0_hpd &hdmim1_tx0_scl &hdmim1_tx0_sda + &hdmi0_tx_on_h>; pinctrl-names = "default"; - pinctrl-0 = <&hdmim0_tx0_hpd &hdmim1_tx0_scl &hdmim1_tx0_sda>; status = "okay"; }; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-tiger.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-tiger.dtsi index a0e97481afb7d7..ea5c264d153719 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-tiger.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-tiger.dtsi @@ -148,9 +148,10 @@ }; &hdmi0 { - pinctrl-names = "default"; + frl-enable-gpios = <&gpio0 RK_PD3 GPIO_ACTIVE_LOW>; pinctrl-0 = <&hdmim1_tx0_cec &hdmim0_tx0_hpd &hdmim1_tx0_scl - &hdmim1_tx0_sda>; + &hdmim1_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; }; &i2c1 { @@ -349,6 +350,12 @@ }; }; + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <0 RK_PD3 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + leds { module_led_pin: module-led-pin { rockchip,pins = <1 RK_PD3 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-coolpi-4b.dts b/arch/arm64/boot/dts/rockchip/rk3588s-coolpi-4b.dts index 189444d2077979..16f19109fca615 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-coolpi-4b.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588s-coolpi-4b.dts @@ -251,6 +251,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB6 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -396,6 +400,12 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { hym8563_int: hym8563-int { rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_up>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-gameforce-ace.dts b/arch/arm64/boot/dts/rockchip/rk3588s-gameforce-ace.dts index 89618394c0bfb4..b657d54c2c5903 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-gameforce-ace.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588s-gameforce-ace.dts @@ -302,9 +302,6 @@ hdmi0-con { compatible = "hdmi-connector"; - ddc-en-gpios = <&gpio4 RK_PB3 GPIO_ACTIVE_HIGH>; - pinctrl-0 = <&hdmi0_en>; - pinctrl-names = "default"; type = "d"; port { @@ -514,8 +511,9 @@ &hdmi0 { no-hpd; + frl-enable-gpios = <&gpio4 RK_PB3 GPIO_ACTIVE_LOW>; pinctrl-0 = <&hdmim0_tx0_cec>, <&hdmim0_tx0_scl>, - <&hdmim0_tx0_sda>; + <&hdmim0_tx0_sda>, <&hdmi0_tx_on_h>; pinctrl-names = "default"; status = "okay"; }; @@ -893,7 +891,7 @@ }; hdmi { - hdmi0_en: hdmi0-en { + hdmi0_tx_on_h: hdmi0-tx-on-h { rockchip,pins = <4 RK_PB3 RK_FUNC_GPIO &pcfg_pull_none>; }; diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-indiedroid-nova.dts b/arch/arm64/boot/dts/rockchip/rk3588s-indiedroid-nova.dts index 174d299cc6bb94..f9e3c0134d5fbb 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-indiedroid-nova.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588s-indiedroid-nova.dts @@ -277,8 +277,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB6 GPIO_ACTIVE_LOW>; pinctrl-0 = <&hdmim0_tx0_scl>, <&hdmim0_tx0_sda>, - <&hdmim0_tx0_hpd>, <&hdmim0_tx0_cec>; + <&hdmim0_tx0_hpd>, <&hdmim0_tx0_cec>, + <&hdmi0_tx_on_h>; pinctrl-names = "default"; status = "okay"; }; @@ -517,6 +519,12 @@ }; }; + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { hym8563_int: hym8563-int { diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-khadas-edge2.dts b/arch/arm64/boot/dts/rockchip/rk3588s-khadas-edge2.dts index 2c22abaf40a82a..5afbc593341ba4 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-khadas-edge2.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588s-khadas-edge2.dts @@ -194,6 +194,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -217,10 +221,6 @@ status = "okay"; }; -&hdmi0_sound { - status = "okay"; -}; - &i2c0 { pinctrl-names = "default"; pinctrl-0 = <&i2c0m2_xfer>; @@ -282,6 +282,12 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + vdd_sd { vdd_sd_en: vdd-sd-en { rockchip,pins = <1 RK_PB6 RK_FUNC_GPIO &pcfg_pull_up>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-nanopi-r6.dtsi b/arch/arm64/boot/dts/rockchip/rk3588s-nanopi-r6.dtsi index 1b6a59f7cabce6..b9c0a1a050a969 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-nanopi-r6.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588s-nanopi-r6.dtsi @@ -236,6 +236,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB6 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -404,6 +408,12 @@ }; }; + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { rtc_int: rtc-int { rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_up>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-odroid-m2.dts b/arch/arm64/boot/dts/rockchip/rk3588s-odroid-m2.dts index a72063c5514010..4e98fa33492c6a 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-odroid-m2.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588s-odroid-m2.dts @@ -249,6 +249,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB6 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -438,6 +442,12 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + lcd { lcd_pwren: lcd-pwren { rockchip,pins = <4 RK_PA3 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5.dtsi b/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5.dtsi index dafad29f98544a..cd80cb15a405b5 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5.dtsi @@ -181,6 +181,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB6 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -381,6 +385,12 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { hym8563_int: hym8563-int { rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-cm5-base.dts b/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-cm5-base.dts index 06120b2db690a9..20da0c2b3d92ea 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-cm5-base.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-cm5-base.dts @@ -143,10 +143,11 @@ }; &hdmi0 { - pinctrl-names = "default"; + frl-enable-gpios = <&gpio4 RK_PB5 GPIO_ACTIVE_LOW>; pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd &hdmim0_tx0_scl &hdmim0_tx0_sda - &hdmi_frl_pin>; + &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -245,8 +246,8 @@ }; hdmi { - hdmi_frl_pin: hdmi-frl-pin { - rockchip,pins = <4 RK_PB5 RK_FUNC_GPIO &pcfg_pull_up>; + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB5 RK_FUNC_GPIO &pcfg_pull_none>; }; }; diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-radxa-cm5-io.dts b/arch/arm64/boot/dts/rockchip/rk3588s-radxa-cm5-io.dts index f80d5a00a4bdd3..af4a9bc015e16d 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-radxa-cm5-io.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588s-radxa-cm5-io.dts @@ -120,6 +120,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB6 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -217,6 +221,12 @@ }; }; + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + usb { vcc5v0_host_en: vcc5v0-host-en { rockchip,pins = <1 RK_PA0 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-roc-pc.dts b/arch/arm64/boot/dts/rockchip/rk3588s-roc-pc.dts index 7e179862da6e56..aa02cf510d6db5 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-roc-pc.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588s-roc-pc.dts @@ -224,6 +224,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB2 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -367,9 +371,9 @@ }; &pinctrl { - hym8563 { - hym8563_int: hym8563-int { - rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_up>; + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB2 RK_FUNC_GPIO &pcfg_pull_none>; }; }; @@ -379,6 +383,12 @@ }; }; + hym8563 { + hym8563_int: hym8563-int { + rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + leds { led_pins: led-pins { rockchip,pins = <1 RK_PD5 RK_FUNC_GPIO &pcfg_pull_none>, diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-rock-5a.dts b/arch/arm64/boot/dts/rockchip/rk3588s-rock-5a.dts index 0991f6a2119005..ffd26b43ae8c96 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-rock-5a.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588s-rock-5a.dts @@ -323,11 +323,13 @@ }; &hdmi0 { - pinctrl-names = "default"; + frl-enable-gpios = <&gpio4 RK_PB6 GPIO_ACTIVE_LOW>; pinctrl-0 = <&hdmim0_tx0_cec &hdmim1_tx0_hpd &hdmim0_tx0_scl - &hdmim0_tx0_sda>; + &hdmim0_tx0_sda + &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -373,6 +375,12 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + leds { io_led: io-led { rockchip,pins = <3 RK_PD5 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-rock-5c.dts b/arch/arm64/boot/dts/rockchip/rk3588s-rock-5c.dts index 7fe42f4ff82798..9d3d0791554d95 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-rock-5c.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588s-rock-5c.dts @@ -258,11 +258,13 @@ }; &hdmi0 { - pinctrl-names = "default"; + frl-enable-gpios = <&gpio4 RK_PB6 GPIO_ACTIVE_LOW>; pinctrl-0 = <&hdmim0_tx0_cec &hdmim1_tx0_hpd &hdmim0_tx0_scl - &hdmim0_tx0_sda>; + &hdmim0_tx0_sda + &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -461,6 +463,12 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + leds { led_pins: led-pins { rockchip,pins = <3 RK_PC4 RK_FUNC_GPIO &pcfg_pull_none>, diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index 96ce783f24e722..ba49c6abec9fa2 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -942,6 +942,7 @@ CONFIG_VIDEO_RENESAS_FDP1=m CONFIG_VIDEO_RENESAS_VSP1=m CONFIG_VIDEO_RCAR_DRIF=m CONFIG_VIDEO_ROCKCHIP_CIF=m +CONFIG_VIDEO_ROCKCHIP_VDEC=m CONFIG_VIDEO_ROCKCHIP_RGA=m CONFIG_VIDEO_SAMSUNG_EXYNOS_GSC=m CONFIG_VIDEO_SAMSUNG_S5P_JPEG=m @@ -1625,6 +1626,7 @@ CONFIG_ARM_SMMU_V3=y CONFIG_MTK_IOMMU=y CONFIG_QCOM_IOMMU=y CONFIG_APPLE_DART=m +CONFIG_VSI_IOMMU=m CONFIG_REMOTEPROC=y CONFIG_IMX_REMOTEPROC=y CONFIG_MTK_SCP=m diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 830fefb342c6b1..437e4bec454a1c 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -4090,6 +4090,49 @@ static int btusb_probe(struct usb_interface *intf, return -ENODEV; } + /* + * MediaTek MT7921U (and likely related MTK combo chips), when + * attached over the SuperSpeed lanes, present a HCI interface with + * *two* bulk OUT endpoints: the lower-numbered one is a vendor / + * diagnostic pipe that silently consumes data without ever raising + * a transfer-complete event, and the higher-numbered one is the + * actual ACL OUT. usb_find_common_endpoints() returns the first + * match, which sinks all outbound HCI traffic and leaves the device + * able to scan but unable to complete any connection. + * + * Most MT7921U deployments wire the Bluetooth function to the USB + * 2.0 differential pair and enumerate at High-Speed, where the + * descriptor only exposes a single bulk OUT and this code path is + * a no-op. + */ + if ((id->driver_info & BTUSB_MEDIATEK) && + interface_to_usbdev(intf)->speed >= USB_SPEED_SUPER) { + struct usb_host_interface *alt = intf->cur_altsetting; + struct usb_endpoint_descriptor *ep, *second_bulk_out = NULL; + int i, bulk_out_count = 0; + + for (i = 0; i < alt->desc.bNumEndpoints; i++) { + ep = &alt->endpoint[i].desc; + if (!usb_endpoint_is_bulk_out(ep)) + continue; + if (++bulk_out_count == 2) + second_bulk_out = ep; + } + + if (bulk_out_count == 2 && second_bulk_out && + second_bulk_out != data->bulk_tx_ep) { + dev_info(&intf->dev, + "btusb: MTK: SuperSpeed: using bEP 0x%02x as ACL OUT (overrides diag bEP 0x%02x)\n", + second_bulk_out->bEndpointAddress, + data->bulk_tx_ep->bEndpointAddress); + data->bulk_tx_ep = second_bulk_out; + } else if (bulk_out_count > 2) { + dev_warn(&intf->dev, + "btusb: MTK: SuperSpeed: unexpected bulk OUT count %d, leaving default selection\n", + bulk_out_count); + } + } + if (id->driver_info & BTUSB_AMP) { data->cmdreq_type = USB_TYPE_CLASS | 0x01; data->cmdreq = 0x2b; diff --git a/drivers/clk/clk-composite.c b/drivers/clk/clk-composite.c index 835b1e4e58697c..a4529172776e6b 100644 --- a/drivers/clk/clk-composite.c +++ b/drivers/clk/clk-composite.c @@ -6,6 +6,7 @@ #include #include #include +#include #include static u8 clk_composite_get_parent(struct clk_hw *hw) @@ -106,10 +107,7 @@ static int clk_composite_determine_rate(struct clk_hw *hw, if (ret) continue; - if (req->rate >= tmp_req.rate) - rate_diff = req->rate - tmp_req.rate; - else - rate_diff = tmp_req.rate - req->rate; + rate_diff = abs_diff(req->rate, tmp_req.rate); if (!rate_diff || !req->best_parent_hw || best_rate_diff > rate_diff) { diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c index b3b485d23ea859..ef163c90d7f11a 100644 --- a/drivers/clk/clk-divider.c +++ b/drivers/clk/clk-divider.c @@ -226,7 +226,7 @@ static int _div_round_up(const struct clk_div_table *table, unsigned long parent_rate, unsigned long rate, unsigned long flags) { - int div = DIV_ROUND_UP_ULL((u64)parent_rate, rate); + int div = DIV_ROUND_UP_NO_OVERFLOW(parent_rate, rate); if (flags & CLK_DIVIDER_POWER_OF_TWO) div = __roundup_pow_of_two(div); @@ -243,7 +243,7 @@ static int _div_round_closest(const struct clk_div_table *table, int up, down; unsigned long up_rate, down_rate; - up = DIV_ROUND_UP_ULL((u64)parent_rate, rate); + up = DIV_ROUND_UP_NO_OVERFLOW(parent_rate, rate); down = parent_rate / rate; if (flags & CLK_DIVIDER_POWER_OF_TWO) { @@ -414,7 +414,7 @@ int divider_get_val(unsigned long rate, unsigned long parent_rate, { unsigned int div, value; - div = DIV_ROUND_UP_ULL((u64)parent_rate, rate); + div = DIV_ROUND_UP_NO_OVERFLOW(parent_rate, rate); if (!_is_valid_div(table, div, flags)) return -EINVAL; diff --git a/drivers/clk/rockchip/clk-pll.c b/drivers/clk/rockchip/clk-pll.c index 6b853800cb6bc6..23a1d4ee9ccb97 100644 --- a/drivers/clk/rockchip/clk-pll.c +++ b/drivers/clk/rockchip/clk-pll.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include "clk.h" @@ -897,7 +898,7 @@ static void rockchip_rk3588_pll_get_params(struct rockchip_clk_pll *pll, rate->s = ((pllcon >> RK3588_PLLCON1_S_SHIFT) & RK3588_PLLCON1_S_MASK); pllcon = readl_relaxed(pll->reg_base + RK3588_PLLCON(2)); - rate->k = ((pllcon >> RK3588_PLLCON2_K_SHIFT) & RK3588_PLLCON2_K_MASK); + rate->k = (s16)((pllcon >> RK3588_PLLCON2_K_SHIFT) & RK3588_PLLCON2_K_MASK); } static unsigned long rockchip_rk3588_pll_recalc_rate(struct clk_hw *hw, unsigned long prate) @@ -913,11 +914,10 @@ static unsigned long rockchip_rk3588_pll_recalc_rate(struct clk_hw *hw, unsigned if (cur.k) { /* fractional mode */ - u64 frac_rate64 = prate * cur.k; + s64 frac_rate64 = prate * cur.k; - postdiv = cur.p * 65535; - do_div(frac_rate64, postdiv); - rate64 += frac_rate64; + postdiv = cur.p * 65536ULL; + rate64 += div_s64(frac_rate64, postdiv); } rate64 = rate64 >> cur.s; diff --git a/drivers/clk/rockchip/clk-rk3576.c b/drivers/clk/rockchip/clk-rk3576.c index 2557358e0b9d87..39b8dbd0f2af9a 100644 --- a/drivers/clk/rockchip/clk-rk3576.c +++ b/drivers/clk/rockchip/clk-rk3576.c @@ -68,13 +68,16 @@ static struct rockchip_pll_rate_table rk3576_pll_rates[] = { RK3588_PLL_RATE(1536000000, 2, 256, 1, 0), RK3588_PLL_RATE(1512000000, 2, 252, 1, 0), RK3588_PLL_RATE(1488000000, 2, 248, 1, 0), + RK3588_PLL_RATE(1478740000, 1, 123, 1, 14964), RK3588_PLL_RATE(1464000000, 2, 244, 1, 0), RK3588_PLL_RATE(1440000000, 2, 240, 1, 0), RK3588_PLL_RATE(1416000000, 2, 236, 1, 0), RK3588_PLL_RATE(1392000000, 2, 232, 1, 0), RK3588_PLL_RATE(1320000000, 2, 220, 1, 0), + RK3588_PLL_RATE(1275830000, 1, 851, 4, -29273), RK3588_PLL_RATE(1200000000, 2, 200, 1, 0), RK3588_PLL_RATE(1188000000, 2, 198, 1, 0), + RK3588_PLL_RATE(1186813000, 1, 791, 4, 13675), RK3588_PLL_RATE(1100000000, 3, 550, 2, 0), RK3588_PLL_RATE(1008000000, 2, 336, 2, 0), RK3588_PLL_RATE(1000000000, 3, 500, 2, 0), @@ -87,11 +90,43 @@ static struct rockchip_pll_rate_table rk3576_pll_rates[] = { RK3588_PLL_RATE(786000000, 1, 131, 2, 0), RK3588_PLL_RATE(785560000, 3, 392, 2, 51117), RK3588_PLL_RATE(722534400, 8, 963, 2, 24850), + RK3588_PLL_RATE(645000000, 1, 215, 3, 0), RK3588_PLL_RATE(600000000, 2, 200, 2, 0), RK3588_PLL_RATE(594000000, 2, 198, 2, 0), + RK3588_PLL_RATE(593410000, 1, 791, 5, 13981), + RK3588_PLL_RATE(593407000, 7, 173, 0, 5049), + RK3588_PLL_RATE(533250000, 4, 355, 2, 32767), + RK3588_PLL_RATE(497750000, 3, 124, 1, 28672), + RK3588_PLL_RATE(488400000, 5, 407, 2, 0), RK3588_PLL_RATE(408000000, 2, 272, 3, 0), + RK3588_PLL_RATE(368881000, 5, 307, 2, 26269), RK3588_PLL_RATE(312000000, 2, 208, 3, 0), + RK3588_PLL_RATE(304250000, 4, 811, 4, 10923), + RK3588_PLL_RATE(296703000, 5, 989, 4, 655), + RK3588_PLL_RATE(296700000, 5, 989, 4, 0), + RK3588_PLL_RATE(285500000, 3, 571, 4, 0), + RK3588_PLL_RATE(277250000, 3, 69, 1, 20480), + RK3588_PLL_RATE(262750000, 4, 701, 4, 10923), + RK3588_PLL_RATE(248880000, 1, 166, 4, -5243), + RK3588_PLL_RATE(245500000, 3, 491, 4, 0), + RK3588_PLL_RATE(241700000, 1, 81, 3, -28399), + RK3588_PLL_RATE(241500000, 1, 161, 4, 0), + RK3588_PLL_RATE(237600000, 5, 99, 1, 0), RK3588_PLL_RATE(216000000, 2, 288, 4, 0), + RK3588_PLL_RATE(193250000, 3, 773, 5, 0), + RK3588_PLL_RATE(177500000, 3, 355, 4, 0), + RK3588_PLL_RATE(174250000, 3, 87, 2, 8192), + RK3588_PLL_RATE(167000000, 3, 167, 3, 0), + RK3588_PLL_RATE(163240000, 5, 272, 3, 4369), + RK3588_PLL_RATE(148360000, 5, 989, 5, 4369), + RK3588_PLL_RATE(148352000, 1, 396, 6, -25865), + RK3588_PLL_RATE(147180000, 5, 981, 5, 13107), + RK3588_PLL_RATE(146250000, 1, 195, 5, 0), + RK3588_PLL_RATE(119000000, 3, 119, 3, 0), + RK3588_PLL_RATE(116460000, 1, 156, 5, -23593), + RK3588_PLL_RATE(108108000, 3, 865, 6, -8913), + RK3588_PLL_RATE(100700000, 2, 537, 6, 4369), + RK3588_PLL_RATE(100680000, 5, 671, 5, 13107), RK3588_PLL_RATE(96000000, 2, 256, 5, 0), { /* sentinel */ }, }; @@ -1102,10 +1137,10 @@ static struct rockchip_clk_branch rk3576_clk_branches[] __initdata = { RK3576_CLKGATE_CON(61), 8, GFLAGS), GATE(ACLK_VOP, "aclk_vop", "aclk_vop_root", 0, RK3576_CLKGATE_CON(61), 9, GFLAGS), - COMPOSITE(DCLK_VP0_SRC, "dclk_vp0_src", gpll_cpll_vpll_bpll_lpll_p, CLK_SET_RATE_NO_REPARENT, + COMPOSITE(DCLK_VP0_SRC, "dclk_vp0_src", gpll_cpll_vpll_bpll_lpll_p, CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT, RK3576_CLKSEL_CON(145), 8, 3, MFLAGS, 0, 8, DFLAGS, RK3576_CLKGATE_CON(61), 10, GFLAGS), - COMPOSITE(DCLK_VP1_SRC, "dclk_vp1_src", gpll_cpll_vpll_bpll_lpll_p, CLK_SET_RATE_NO_REPARENT, + COMPOSITE(DCLK_VP1_SRC, "dclk_vp1_src", gpll_cpll_vpll_bpll_lpll_p, CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT, RK3576_CLKSEL_CON(146), 8, 3, MFLAGS, 0, 8, DFLAGS, RK3576_CLKGATE_CON(61), 11, GFLAGS), COMPOSITE(DCLK_VP2_SRC, "dclk_vp2_src", gpll_cpll_vpll_bpll_lpll_p, CLK_SET_RATE_NO_REPARENT, diff --git a/drivers/clk/rockchip/clk.h b/drivers/clk/rockchip/clk.h index cf0f5f11c34b5a..5136bf4b5f69c8 100644 --- a/drivers/clk/rockchip/clk.h +++ b/drivers/clk/rockchip/clk.h @@ -638,7 +638,7 @@ struct rockchip_pll_rate_table { unsigned int m; unsigned int p; unsigned int s; - unsigned int k; + int k; }; }; }; diff --git a/drivers/counter/Kconfig b/drivers/counter/Kconfig index d30d22dfe57741..85adeb41aeedad 100644 --- a/drivers/counter/Kconfig +++ b/drivers/counter/Kconfig @@ -90,6 +90,17 @@ config MICROCHIP_TCB_CAPTURE To compile this driver as a module, choose M here: the module will be called microchip-tcb-capture. +config ROCKCHIP_PWM_CAPTURE + tristate "Rockchip PWM Counter Capture driver" + depends on MFD_ROCKCHIP_MFPWM + help + Generic counter framework driver for the multi-function PWM on + Rockchip SoCs such as the RK3576. + + Uses the Rockchip Multi-function PWM controller driver infrastructure + to guarantee exclusive operation with other functions of the same + device implemented by drivers in other subsystems. + config RZ_MTU3_CNT tristate "Renesas RZ/G2L MTU3a counter driver" depends on RZ_MTU3 diff --git a/drivers/counter/Makefile b/drivers/counter/Makefile index fa3c1d08f70688..2bfcfc2c584bd1 100644 --- a/drivers/counter/Makefile +++ b/drivers/counter/Makefile @@ -17,3 +17,4 @@ obj-$(CONFIG_FTM_QUADDEC) += ftm-quaddec.o obj-$(CONFIG_MICROCHIP_TCB_CAPTURE) += microchip-tcb-capture.o obj-$(CONFIG_INTEL_QEP) += intel-qep.o obj-$(CONFIG_TI_ECAP_CAPTURE) += ti-ecap-capture.o +obj-$(CONFIG_ROCKCHIP_PWM_CAPTURE) += rockchip-pwm-capture.o diff --git a/drivers/counter/rockchip-pwm-capture.c b/drivers/counter/rockchip-pwm-capture.c new file mode 100644 index 00000000000000..09a92f2bc40988 --- /dev/null +++ b/drivers/counter/rockchip-pwm-capture.c @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2025 Collabora Ltd. + * + * A counter driver for the Pulse-Width-Modulation (PWM) hardware found on + * Rockchip SoCs such as the RK3576, internally referred to as "PWM v4". It + * allows for measuring the high cycles and low cycles of a PWM signal through + * the generic counter framework, while guaranteeing exclusive use over the + * MFPWM device while the counter is enabled. + * + * Authors: + * Nicolas Frattaroli + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RKPWMC_INT_MASK (PWMV4_INT_LPC | PWMV4_INT_HPC) + +struct rockchip_pwm_capture { + struct rockchip_mfpwm_func *pwmf; + struct counter_device *counter; +}; + +static struct counter_signal rkpwmc_signals[] = { + { + .id = 0, + .name = "PWM Clock" + }, +}; + +static const enum counter_synapse_action rkpwmc_hpc_lpc_actions[] = { + COUNTER_SYNAPSE_ACTION_BOTH_EDGES, + COUNTER_SYNAPSE_ACTION_NONE, +}; + +static struct counter_synapse rkpwmc_pwm_synapses[] = { + { + .actions_list = rkpwmc_hpc_lpc_actions, + .num_actions = ARRAY_SIZE(rkpwmc_hpc_lpc_actions), + .signal = &rkpwmc_signals[0] + }, +}; + +static const enum counter_function rkpwmc_functions[] = { + COUNTER_FUNCTION_INCREASE, +}; + +static inline bool rkpwmc_is_enabled(struct rockchip_mfpwm_func *pwmf) +{ + return mfpwm_get_mode(pwmf) == PWMV4_MODE_CAPTURE; +} + +static bool rkpwmc_acquire_if_enabled(struct rockchip_pwm_capture *pc) +{ + int ret; + + ret = mfpwm_acquire(pc->pwmf); + if (ret < 0) + return false; + + if (rkpwmc_is_enabled(pc->pwmf)) + return true; + + mfpwm_release(pc->pwmf); + + return false; +} + +static int rkpwmc_enable_read(struct counter_device *counter, + struct counter_count *count, + u8 *enable) +{ + struct rockchip_pwm_capture *pc = counter_priv(counter); + + *enable = rkpwmc_is_enabled(pc->pwmf); + + return 0; +} + +static int rkpwmc_enable_write(struct counter_device *counter, + struct counter_count *count, + u8 enable) +{ + struct rockchip_pwm_capture *pc = counter_priv(counter); + int ret; + + ret = mfpwm_acquire(pc->pwmf); + if (ret) + return ret; + + if (!!enable != rkpwmc_is_enabled(pc->pwmf)) { + if (enable) { + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE, + PWMV4_EN(false)); + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_CTRL, + PWMV4_CTRL_CAP_FLAGS); + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_INT_EN, + PWMV4_INT_LPC_W(true) | + PWMV4_INT_HPC_W(true)); + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE, + PWMV4_EN(true) | PWMV4_CLK_EN(true)); + + ret = clk_enable(pc->pwmf->core); + if (ret) + goto err_release; + + ret = clk_rate_exclusive_get(pc->pwmf->core); + if (ret) + goto err_disable_pwm_clk; + + ret = mfpwm_acquire(pc->pwmf); + if (ret) + goto err_unprotect_pwm_clk; + } else { + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_INT_EN, + PWMV4_INT_LPC_W(false) | + PWMV4_INT_HPC_W(false)); + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE, + PWMV4_EN(false) | PWMV4_CLK_EN(false)); + clk_rate_exclusive_put(pc->pwmf->core); + clk_disable(pc->pwmf->core); + mfpwm_release(pc->pwmf); + } + } + + mfpwm_release(pc->pwmf); + + return 0; + +err_unprotect_pwm_clk: + clk_rate_exclusive_put(pc->pwmf->core); +err_disable_pwm_clk: + clk_disable(pc->pwmf->core); +err_release: + mfpwm_release(pc->pwmf); + + return ret; +} + +static struct counter_comp rkpwmc_ext[] = { + COUNTER_COMP_ENABLE(rkpwmc_enable_read, rkpwmc_enable_write), +}; + +enum rkpwmc_count_id { + COUNT_LPC = 0, + COUNT_HPC = 1, +}; + +static struct counter_count rkpwmc_counts[] = { + { + .id = COUNT_LPC, + .name = "Low Polarity Capture", + .functions_list = rkpwmc_functions, + .num_functions = ARRAY_SIZE(rkpwmc_functions), + .synapses = rkpwmc_pwm_synapses, + .num_synapses = ARRAY_SIZE(rkpwmc_pwm_synapses), + .ext = rkpwmc_ext, + .num_ext = ARRAY_SIZE(rkpwmc_ext), + }, + { + .id = COUNT_HPC, + .name = "High Polarity Capture", + .functions_list = rkpwmc_functions, + .num_functions = ARRAY_SIZE(rkpwmc_functions), + .synapses = rkpwmc_pwm_synapses, + .num_synapses = ARRAY_SIZE(rkpwmc_pwm_synapses), + .ext = rkpwmc_ext, + .num_ext = ARRAY_SIZE(rkpwmc_ext), + }, +}; + +static int rkpwmc_count_read(struct counter_device *counter, + struct counter_count *count, u64 *value) +{ + struct rockchip_pwm_capture *pc = counter_priv(counter); + + switch (count->id) { + case COUNT_LPC: + if (rkpwmc_acquire_if_enabled(pc)) { + *value = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_LPC); + mfpwm_release(pc->pwmf); + } else { + *value = 0; + } + return 0; + case COUNT_HPC: + if (rkpwmc_acquire_if_enabled(pc)) { + *value = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_HPC); + mfpwm_release(pc->pwmf); + } else { + *value = 0; + } + return 0; + default: + return -EINVAL; + } +} + +static const struct counter_ops rkpwmc_ops = { + .count_read = rkpwmc_count_read, +}; + +static irqreturn_t rkpwmc_irq_handler(int irq, void *data) +{ + struct rockchip_pwm_capture *pc = data; + u32 intsts; + u32 clr = 0; + + intsts = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_INTSTS); + + if (!(intsts & RKPWMC_INT_MASK)) + return IRQ_NONE; + + if (intsts & PWMV4_INT_LPC) { + clr |= PWMV4_INT_LPC; + counter_push_event(pc->counter, COUNTER_EVENT_CHANGE_OF_STATE, 0); + } + + if (intsts & PWMV4_INT_HPC) { + clr |= PWMV4_INT_HPC; + counter_push_event(pc->counter, COUNTER_EVENT_CHANGE_OF_STATE, 1); + } + + if (clr) + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_INTSTS, clr); + + /* If other interrupt status bits are set, they're not for this driver */ + if (intsts != clr) + return IRQ_NONE; + + return IRQ_HANDLED; +} + +static int rockchip_pwm_capture_probe(struct platform_device *pdev) +{ + struct rockchip_mfpwm_func *pwmf = dev_get_platdata(&pdev->dev); + struct rockchip_pwm_capture *pc; + struct counter_device *counter; + int ret; + + /* Set our (still unset) OF node to the parent MFD device's OF node */ + pdev->dev.parent->of_node_reused = true; + device_set_node(&pdev->dev, + of_fwnode_handle(no_free_ptr(pdev->dev.parent->of_node))); + + counter = devm_counter_alloc(&pdev->dev, sizeof(*pc)); + if (IS_ERR(counter)) + return PTR_ERR(counter); + + pc = counter_priv(counter); + pc->pwmf = pwmf; + + platform_set_drvdata(pdev, pc); + + /* If the counter is on at module probe, acquire it */ + rkpwmc_acquire_if_enabled(pc); + + counter->name = pdev->name; + counter->signals = rkpwmc_signals; + counter->num_signals = ARRAY_SIZE(rkpwmc_signals); + counter->ops = &rkpwmc_ops; + counter->counts = rkpwmc_counts; + counter->num_counts = ARRAY_SIZE(rkpwmc_counts); + + pc->counter = counter; + + ret = devm_counter_add(&pdev->dev, counter); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "Failed to add counter\n"); + + ret = devm_request_irq(&pdev->dev, pwmf->irq, rkpwmc_irq_handler, + IRQF_SHARED, pdev->name, pc); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed requesting IRQ\n"); + + return 0; +} + +static const struct platform_device_id rockchip_pwm_capture_id_table[] = { + { .name = "rockchip-pwm-capture", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(platform, rockchip_pwm_capture_id_table); + +static struct platform_driver rockchip_pwm_capture_driver = { + .probe = rockchip_pwm_capture_probe, + .id_table = rockchip_pwm_capture_id_table, + .driver = { + .name = "rockchip-pwm-capture", + }, +}; +module_platform_driver(rockchip_pwm_capture_driver); + +MODULE_AUTHOR("Nicolas Frattaroli "); +MODULE_DESCRIPTION("Rockchip PWM Counter Capture Driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("ROCKCHIP_MFPWM"); +MODULE_IMPORT_NS("COUNTER"); +MODULE_ALIAS("platform:rockchip-pwm-capture"); diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c index 45b37885d719dc..9553bad690d9bb 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c @@ -23,17 +23,21 @@ #include #include #include +#include #include #include #include #include #include +#include + #define DW_DP_VERSION_NUMBER 0x0000 #define DW_DP_VERSION_TYPE 0x0004 #define DW_DP_ID 0x0008 #define DW_DP_CONFIG_REG1 0x0100 +#define AUDIO_SELECT GENMASK(2, 1) #define DW_DP_CONFIG_REG2 0x0104 #define DW_DP_CONFIG_REG3 0x0108 @@ -110,6 +114,10 @@ #define HBR_MODE_ENABLE BIT(10) #define AUDIO_DATA_WIDTH GENMASK(9, 5) #define AUDIO_DATA_IN_EN GENMASK(4, 1) +#define AUDIO_DATA_IN_EN_CHANNEL12 BIT(0) +#define AUDIO_DATA_IN_EN_CHANNEL34 BIT(1) +#define AUDIO_DATA_IN_EN_CHANNEL56 BIT(2) +#define AUDIO_DATA_IN_EN_CHANNEL78 BIT(3) #define AUDIO_INF_SELECT BIT(0) #define DW_DP_SDP_VERTICAL_CTRL 0x0500 @@ -253,6 +261,8 @@ #define SDP_REG_BANK_SIZE 16 +#define DW_DP_SDP_VERSION 0x12 + struct dw_dp_link_caps { bool enhanced_framing; bool tps3_supported; @@ -305,6 +315,19 @@ struct dw_dp_hotplug { bool long_hpd; }; +enum dw_dp_audio_interface_support { + DW_DP_AUDIO_I2S_ONLY = 0, + DW_DP_AUDIO_SPDIF_ONLY = 1, + DW_DP_AUDIO_I2S_AND_SPDIF = 2, + DW_DP_AUDIO_NONE = 3, +}; + +enum dw_dp_audio_interface { + DW_DP_AUDIO_I2S = 0, + DW_DP_AUDIO_SPDIF = 1, + DW_DP_AUDIO_UNUSED, +}; + struct dw_dp { struct drm_bridge bridge; struct device *dev; @@ -320,6 +343,8 @@ struct dw_dp { int irq; struct work_struct hpd_work; struct dw_dp_hotplug hotplug; + enum dw_dp_audio_interface audio_interface; + int audio_channels; /* Serialize hpd status access */ struct mutex irq_lock; @@ -1465,6 +1490,8 @@ static ssize_t dw_dp_aux_transfer(struct drm_dp_aux *aux, if (WARN_ON(msg->size > 16)) return -E2BIG; + ACQUIRE(pm_runtime_active_auto, pm)(dp->dev); + switch (msg->request & ~DP_AUX_I2C_MOT) { case DP_AUX_NATIVE_WRITE: case DP_AUX_I2C_WRITE: @@ -1528,6 +1555,7 @@ static int dw_dp_bridge_atomic_check(struct drm_bridge *bridge, struct drm_connector_state *conn_state) { struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode; + unsigned int out_bus_format = bridge_state->output_bus_cfg.format; struct dw_dp *dp = bridge_to_dp(bridge); struct dw_dp_bridge_state *state; const struct dw_dp_output_format *fmt; @@ -1538,7 +1566,10 @@ static int dw_dp_bridge_atomic_check(struct drm_bridge *bridge, state = to_dw_dp_bridge_state(bridge_state); mode = &state->mode; - fmt = dw_dp_get_output_format(bridge_state->output_bus_cfg.format); + if (out_bus_format == MEDIA_BUS_FMT_FIXED) + out_bus_format = MEDIA_BUS_FMT_RGB888_1X24; + + fmt = dw_dp_get_output_format(out_bus_format); if (!fmt) return -EINVAL; @@ -1651,6 +1682,8 @@ static void dw_dp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_connector_state *conn_state; int ret; + pm_runtime_get_sync(dp->dev); + connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder); if (!connector) { dev_err(dp->dev, "failed to get connector\n"); @@ -1705,6 +1738,7 @@ static void dw_dp_bridge_atomic_disable(struct drm_bridge *bridge, dw_dp_link_disable(dp); bitmap_zero(dp->sdp_reg_bank, SDP_REG_BANK_SIZE); dw_dp_reset(dp); + pm_runtime_put_autosuspend(dp->dev); } static bool dw_dp_hpd_detect_link(struct dw_dp *dp, struct drm_connector *connector) @@ -1725,6 +1759,8 @@ static enum drm_connector_status dw_dp_bridge_detect(struct drm_bridge *bridge, { struct dw_dp *dp = bridge_to_dp(bridge); + ACQUIRE(pm_runtime_active_auto, pm)(dp->dev); + if (!dw_dp_hpd_detect(dp)) return connector_status_disconnected; @@ -1813,6 +1849,199 @@ static struct drm_bridge_state *dw_dp_bridge_atomic_duplicate_state(struct drm_b return &state->base; } +static void dw_dp_bridge_oob_notify(struct drm_bridge *bridge, + struct drm_connector *connector, + enum drm_connector_status status) +{ + bool hpd_high = status != connector_status_disconnected; + struct dw_dp *dp = bridge_to_dp(bridge); + + if (dp->plat_data.hpd_sw_cfg) + dp->plat_data.hpd_sw_cfg(dp->plat_data.data, hpd_high); + else + dev_err_once(dp->dev, "Missing platform handler for OOB HPD handling\n"); +} + +static int dw_dp_audio_infoframe_send(struct dw_dp *dp) +{ + struct hdmi_audio_infoframe frame; + struct dw_dp_sdp sdp; + int ret; + + ret = hdmi_audio_infoframe_init(&frame); + if (ret < 0) + return ret; + + frame.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM; + frame.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM; + frame.sample_size = HDMI_AUDIO_SAMPLE_SIZE_STREAM; + frame.channels = dp->audio_channels; + + ret = hdmi_audio_infoframe_pack_for_dp(&frame, &sdp.base, DW_DP_SDP_VERSION); + if (ret < 0) + return ret; + + sdp.flags = DW_DP_SDP_VERTICAL_INTERVAL; + + return dw_dp_send_sdp(dp, &sdp); +} + +static int dw_dp_audio_startup(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct dw_dp *dp = bridge_to_dp(bridge); + + dev_dbg(dp->dev, "audio startup\n"); + pm_runtime_get_sync(dp->dev); + + return 0; +} + +static void dw_dp_audio_unprepare(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct dw_dp *dp = bridge_to_dp(bridge); + + /* Disable all audio streams */ + regmap_update_bits(dp->regmap, DW_DP_AUD_CONFIG1, AUDIO_DATA_IN_EN, + FIELD_PREP(AUDIO_DATA_IN_EN, 0)); + + if (dp->audio_interface == DW_DP_AUDIO_SPDIF) + clk_disable_unprepare(dp->spdif_clk); + else if (dp->audio_interface == DW_DP_AUDIO_I2S) + clk_disable_unprepare(dp->i2s_clk); + + dp->audio_interface = DW_DP_AUDIO_UNUSED; +} + +static int dw_dp_audio_prepare(struct drm_bridge *bridge, + struct drm_connector *connector, + struct hdmi_codec_daifmt *daifmt, + struct hdmi_codec_params *params) +{ + struct dw_dp *dp = bridge_to_dp(bridge); + u8 audio_data_in_en, supported_audio_interfaces; + u32 cfg1; + int ret; + + /* + * prepare might be called multiple times, so release the clocks + * from previous calls to keep the calls in balance. + */ + if (dp->audio_interface != DW_DP_AUDIO_UNUSED) + dw_dp_audio_unprepare(bridge, connector); + + dp->audio_channels = params->cea.channels; + switch (params->cea.channels) { + case 1: + case 2: + audio_data_in_en = AUDIO_DATA_IN_EN_CHANNEL12; + break; + case 8: + audio_data_in_en = AUDIO_DATA_IN_EN_CHANNEL12 | + AUDIO_DATA_IN_EN_CHANNEL34 | + AUDIO_DATA_IN_EN_CHANNEL56 | + AUDIO_DATA_IN_EN_CHANNEL78; + break; + default: + dev_err(dp->dev, "invalid audio channels %d\n", dp->audio_channels); + return -EINVAL; + } + + switch (daifmt->fmt) { + case HDMI_SPDIF: + dp->audio_interface = DW_DP_AUDIO_SPDIF; + break; + case HDMI_I2S: + /* + * It is recommended to use SPDIF instead of I2S, since I2S mode requires + * manually inserting PCUV control bits from userspace and this is done + * automatically in hardware for SPDIF mode. + */ + dp->audio_interface = DW_DP_AUDIO_I2S; + break; + default: + dev_err(dp->dev, "invalid DAI format %d\n", daifmt->fmt); + return -EINVAL; + } + + regmap_read(dp->regmap, DW_DP_CONFIG_REG1, &cfg1); + supported_audio_interfaces = FIELD_GET(AUDIO_SELECT, cfg1); + + if (supported_audio_interfaces != DW_DP_AUDIO_I2S_AND_SPDIF && + supported_audio_interfaces != dp->audio_interface) { + dev_err(dp->dev, "unsupported DAI %d\n", daifmt->fmt); + return -EINVAL; + } + + clk_prepare_enable(dp->spdif_clk); + clk_prepare_enable(dp->i2s_clk); + + regmap_update_bits(dp->regmap, DW_DP_AUD_CONFIG1, + AUDIO_DATA_IN_EN | NUM_CHANNELS | AUDIO_DATA_WIDTH | + AUDIO_INF_SELECT | HBR_MODE_ENABLE, + FIELD_PREP(AUDIO_DATA_IN_EN, audio_data_in_en) | + FIELD_PREP(NUM_CHANNELS, dp->audio_channels - 1) | + FIELD_PREP(AUDIO_DATA_WIDTH, params->sample_width) | + FIELD_PREP(AUDIO_INF_SELECT, dp->audio_interface) | + FIELD_PREP(HBR_MODE_ENABLE, 0)); + + /* Wait for inf switch */ + usleep_range(20, 40); + + if (dp->audio_interface == DW_DP_AUDIO_I2S) + clk_disable_unprepare(dp->spdif_clk); + else if (dp->audio_interface == DW_DP_AUDIO_SPDIF) + clk_disable_unprepare(dp->i2s_clk); + + /* + * Send audio stream during vertical and horizontal blanking periods. + * Send out audio timestamp SDP once per video frame during the vertical + * blanking period + */ + regmap_update_bits(dp->regmap, DW_DP_SDP_VERTICAL_CTRL, + EN_AUDIO_STREAM_SDP | EN_AUDIO_TIMESTAMP_SDP, + FIELD_PREP(EN_AUDIO_STREAM_SDP, 1) | + FIELD_PREP(EN_AUDIO_TIMESTAMP_SDP, 1)); + regmap_update_bits(dp->regmap, DW_DP_SDP_HORIZONTAL_CTRL, + EN_AUDIO_STREAM_SDP, + FIELD_PREP(EN_AUDIO_STREAM_SDP, 1)); + + ret = dw_dp_audio_infoframe_send(dp); + if (ret < 0) + dev_err(dp->dev, "failed to send audio infoframe\n"); + + dev_dbg(dp->dev, "audio prepare with %d channels using DAI=%d\n", + dp->audio_channels, dp->audio_interface); + + return 0; +} + +static void dw_dp_audio_shutdown(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct dw_dp *dp = bridge_to_dp(bridge); + + dev_dbg(dp->dev, "audio shutdown\n"); + + dw_dp_audio_unprepare(bridge, connector); + pm_runtime_put_autosuspend(dp->dev); +} + +static int dw_dp_audio_mute_stream(struct drm_bridge *bridge, + struct drm_connector *connector, + bool enable, int direction) +{ + struct dw_dp *dp = bridge_to_dp(bridge); + + dev_dbg(dp->dev, "audio %smute\n", enable ? "" : "un"); + + regmap_update_bits(dp->regmap, DW_DP_AUD_CONFIG1, AUDIO_MUTE, + FIELD_PREP(AUDIO_MUTE, enable)); + + return 0; +} + static const struct drm_bridge_funcs dw_dp_bridge_funcs = { .atomic_duplicate_state = dw_dp_bridge_atomic_duplicate_state, .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, @@ -1825,6 +2054,12 @@ static const struct drm_bridge_funcs dw_dp_bridge_funcs = { .atomic_disable = dw_dp_bridge_atomic_disable, .detect = dw_dp_bridge_detect, .edid_read = dw_dp_bridge_edid_read, + .oob_notify = dw_dp_bridge_oob_notify, + + .dp_audio_startup = dw_dp_audio_startup, + .dp_audio_prepare = dw_dp_audio_prepare, + .dp_audio_shutdown = dw_dp_audio_shutdown, + .dp_audio_mute_stream = dw_dp_audio_mute_stream, }; static int dw_dp_link_retrain(struct dw_dp *dp) @@ -1961,26 +2196,37 @@ static void dw_dp_phy_exit(void *data) phy_exit(dp->phy); } +static bool dw_dp_is_routed_to_usb_c(struct drm_encoder *encoder) +{ + struct drm_bridge *last_bridge __free(drm_bridge_put) = NULL; + struct fwnode_handle *fwnode; + + last_bridge = drm_bridge_chain_get_last_bridge(encoder); + if (!last_bridge) + return false; + + fwnode = of_fwnode_handle(last_bridge->of_node); + return fwnode_device_is_compatible(fwnode, "usb-c-connector"); +} + struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, const struct dw_dp_plat_data *plat_data) { struct platform_device *pdev = to_platform_device(dev); struct dw_dp *dp; - struct drm_bridge *bridge; + struct drm_bridge *bridge, *next_bridge; void __iomem *res; int ret; - dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL); - if (!dp) - return ERR_PTR(-ENOMEM); - dp = devm_drm_bridge_alloc(dev, struct dw_dp, bridge, &dw_dp_bridge_funcs); if (IS_ERR(dp)) return ERR_CAST(dp); dp->dev = dev; dp->pixel_mode = plat_data->pixel_mode; - + dp->plat_data.hpd_sw_sel = plat_data->hpd_sw_sel; + dp->plat_data.hpd_sw_cfg = plat_data->hpd_sw_cfg; + dp->plat_data.data = plat_data->data; dp->plat_data.max_link_rate = plat_data->max_link_rate; bridge = &dp->bridge; mutex_init(&dp->irq_lock); @@ -2040,10 +2286,19 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, } bridge->of_node = dev->of_node; - bridge->ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD; + bridge->ops = DRM_BRIDGE_OP_DP_AUDIO | + DRM_BRIDGE_OP_DETECT | + DRM_BRIDGE_OP_EDID | + DRM_BRIDGE_OP_HPD; bridge->type = DRM_MODE_CONNECTOR_DisplayPort; bridge->ycbcr_420_allowed = true; + dp->audio_interface = DW_DP_AUDIO_UNUSED; + bridge->hdmi_audio_dev = dev; + bridge->hdmi_audio_max_i2s_playback_channels = 8; + bridge->hdmi_audio_dai_port = 1; + bridge->hdmi_audio_spdif_playback = true; + ret = devm_drm_bridge_add(dev, bridge); if (ret) return ERR_PTR(ret); @@ -2064,6 +2319,27 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, goto unregister_aux; } + next_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 1, 0); + if (IS_ERR(next_bridge)) { + ret = PTR_ERR(next_bridge); + dev_err_probe(dev, ret, "failed to get follow-up bridge.\n"); + goto unregister_aux; + } + + ret = drm_bridge_attach(encoder, next_bridge, bridge, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) { + dev_err_probe(dev, ret, "Failed to attach next bridge\n"); + goto unregister_aux; + } + + if (dw_dp_is_routed_to_usb_c(encoder)) { + dev_dbg(dev, "USB-C mode\n"); + + if (dp->plat_data.hpd_sw_sel) + dp->plat_data.hpd_sw_sel(dp->plat_data.data, 1); + } + dw_dp_init_hw(dp); ret = phy_init(dp->phy); @@ -2097,6 +2373,32 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, } EXPORT_SYMBOL_GPL(dw_dp_bind); +void dw_dp_unbind(struct dw_dp *dp) +{ + drm_dp_aux_unregister(&dp->aux); +} +EXPORT_SYMBOL_GPL(dw_dp_unbind); + +int dw_dp_runtime_suspend(struct dw_dp *dp) +{ + clk_disable_unprepare(dp->aux_clk); + clk_disable_unprepare(dp->apb_clk); + + return 0; +} +EXPORT_SYMBOL_GPL(dw_dp_runtime_suspend); + +int dw_dp_runtime_resume(struct dw_dp *dp) +{ + clk_prepare_enable(dp->apb_clk); + clk_prepare_enable(dp->aux_clk); + + dw_dp_init_hw(dp); + + return 0; +} +EXPORT_SYMBOL_GPL(dw_dp_runtime_resume); + MODULE_AUTHOR("Andy Yan "); MODULE_DESCRIPTION("DW DP Core Library"); MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c index d649a1cf07f5cf..47de45d6225a46 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c @@ -2,6 +2,7 @@ /* * Copyright (c) 2021-2022 Rockchip Electronics Co., Ltd. * Copyright (c) 2024 Collabora Ltd. + * Copyright (c) 2025 Amazon.com, Inc. or its affiliates. * * Author: Algea Cao * Author: Cristian Ciocaltea @@ -21,9 +22,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -39,9 +42,27 @@ #define DDC_SEGMENT_ADDR 0x30 #define HDMI14_MAX_TMDSCLK 340000000 +#define HDMI20_MAX_TMDSRATE 600000000 +#define SCDC_MAX_SOURCE_VERSION 0x1 #define SCRAMB_POLL_DELAY_MS 3000 +/* + * Recommended N and Expected CTS Values in FRL Mode. + */ +static const struct dw_hdmi_qp_audio_frl_n { + unsigned int r_bit; + unsigned int n_32k; + unsigned int n_44k1; + unsigned int n_48k; +} common_frl_n_table[] = { + { .r_bit = 3, .n_32k = 4224, .n_44k1 = 5292, .n_48k = 5760, }, + { .r_bit = 6, .n_32k = 4032, .n_44k1 = 5292, .n_48k = 6048, }, + { .r_bit = 8, .n_32k = 4032, .n_44k1 = 3969, .n_48k = 6048, }, + { .r_bit = 10, .n_32k = 3456, .n_44k1 = 3969, .n_48k = 5184, }, + { .r_bit = 12, .n_32k = 3072, .n_44k1 = 3969, .n_48k = 4752, }, +}; + /* * Unless otherwise noted, entries in this table are 100% optimization. * Values can be obtained from dw_hdmi_qp_compute_n() but that function is @@ -98,9 +119,6 @@ static const struct dw_hdmi_audio_tmds_n { /* For 297 MHz+ HDMI spec have some other rule for setting N */ { .tmds = 297000000, .n_32k = 3073, .n_44k1 = 4704, .n_48k = 5120, }, { .tmds = 594000000, .n_32k = 3073, .n_44k1 = 9408, .n_48k = 10240,}, - - /* End of table */ - { .tmds = 0, .n_32k = 0, .n_44k1 = 0, .n_48k = 0, }, }; /* @@ -119,9 +137,6 @@ static const struct dw_hdmi_audio_tmds_cts { { .tmds = 54000000, .cts_32k = 54000, .cts_44k1 = 60000, .cts_48k = 54000, }, { .tmds = 74250000, .cts_32k = 74250, .cts_44k1 = 82500, .cts_48k = 74250, }, { .tmds = 148500000, .cts_32k = 148500, .cts_44k1 = 165000, .cts_48k = 148500, }, - - /* End of table */ - { .tmds = 0, .cts_32k = 0, .cts_44k1 = 0, .cts_48k = 0, }, }; struct dw_hdmi_qp_i2c { @@ -164,10 +179,18 @@ struct dw_hdmi_qp { } phy; unsigned long ref_clk_rate; + + struct drm_connector *curr_conn; + struct delayed_work scramb_work; + bool scramb_enabled; + + struct work_struct flt_work; + bool flt_no_timeout; + struct regmap *regm; int main_irq; - unsigned long tmds_char_rate; + unsigned long long tmds_char_rate; bool no_hpd; }; @@ -215,6 +238,49 @@ static void dw_hdmi_qp_set_cts_n(struct dw_hdmi_qp *hdmi, unsigned int cts, AUDPKT_ACR_CONTROL1); } +static int dw_hdmi_qp_match_frl_n_table(struct dw_hdmi_qp *hdmi, + unsigned long r_bit, + unsigned long freq) +{ + const struct dw_hdmi_qp_audio_frl_n *frl_n = NULL; + int i, n; + + for (i = 0; i < ARRAY_SIZE(common_frl_n_table); i++) { + if (r_bit == common_frl_n_table[i].r_bit) { + frl_n = &common_frl_n_table[i]; + break; + } + } + + if (!frl_n) { + dev_err(hdmi->dev, "Unexpected FRL Rbit: %lu Gbps\n", r_bit); + return 0; + } + + switch (freq) { + case 32000: + case 64000: + case 128000: + n = (freq / 32000) * frl_n->n_32k; + break; + case 44100: + case 88200: + case 176400: + n = (freq / 44100) * frl_n->n_44k1; + break; + case 48000: + case 96000: + case 192000: + n = (freq / 48000) * frl_n->n_48k; + break; + default: + dev_err(hdmi->dev, "Unexpected FRL freq: %lu Hz\n", freq); + n = 0; + } + + return n; +} + static int dw_hdmi_qp_match_tmds_n_table(struct dw_hdmi_qp *hdmi, unsigned long pixel_clk, unsigned long freq) @@ -222,7 +288,7 @@ static int dw_hdmi_qp_match_tmds_n_table(struct dw_hdmi_qp *hdmi, const struct dw_hdmi_audio_tmds_n *tmds_n = NULL; int i; - for (i = 0; common_tmds_n_table[i].tmds != 0; i++) { + for (i = 0; i < ARRAY_SIZE(common_tmds_n_table); i++) { if (pixel_clk == common_tmds_n_table[i].tmds) { tmds_n = &common_tmds_n_table[i]; break; @@ -296,10 +362,18 @@ static unsigned int dw_hdmi_qp_compute_n(struct dw_hdmi_qp *hdmi, static unsigned int dw_hdmi_qp_find_n(struct dw_hdmi_qp *hdmi, unsigned long pixel_clk, unsigned long sample_rate) { - int n = dw_hdmi_qp_match_tmds_n_table(hdmi, pixel_clk, sample_rate); + const struct dw_hdmi_qp_link_cfg *link_cfg; + int ret; + + link_cfg = hdmi->phy.ops->get_link_cfg(hdmi, hdmi->phy.data); + if (link_cfg->frl_enabled) + return dw_hdmi_qp_match_frl_n_table(hdmi, + link_cfg->frl_rate_per_lane, + sample_rate); - if (n > 0) - return n; + ret = dw_hdmi_qp_match_tmds_n_table(hdmi, pixel_clk, sample_rate); + if (ret > 0) + return ret; dev_warn(hdmi->dev, "Rate %lu missing; compute N dynamically\n", pixel_clk); @@ -313,7 +387,7 @@ static unsigned int dw_hdmi_qp_find_cts(struct dw_hdmi_qp *hdmi, unsigned long p const struct dw_hdmi_audio_tmds_cts *tmds_cts = NULL; int i; - for (i = 0; common_tmds_cts_table[i].tmds != 0; i++) { + for (i = 0; i < ARRAY_SIZE(common_tmds_cts_table); i++) { if (pixel_clk == common_tmds_cts_table[i].tmds) { tmds_cts = &common_tmds_cts_table[i]; break; @@ -469,8 +543,10 @@ static int dw_hdmi_qp_audio_enable(struct drm_bridge *bridge, { struct dw_hdmi_qp *hdmi = dw_hdmi_qp_from_bridge(bridge); - if (hdmi->tmds_char_rate) - dw_hdmi_qp_mod(hdmi, 0, AVP_DATAPATH_PACKET_AUDIO_SWDISABLE, GLOBAL_SWDISABLE); + if (!hdmi->tmds_char_rate) + return -EOPNOTSUPP; + + dw_hdmi_qp_mod(hdmi, 0, AVP_DATAPATH_PACKET_AUDIO_SWDISABLE, GLOBAL_SWDISABLE); return 0; } @@ -484,7 +560,7 @@ static int dw_hdmi_qp_audio_prepare(struct drm_bridge *bridge, bool ref2stream = false; if (!hdmi->tmds_char_rate) - return -ENODEV; + return -EOPNOTSUPP; if (fmt->bit_clk_provider | fmt->frame_clk_provider) { dev_err(hdmi->dev, "unsupported clock settings\n"); @@ -563,7 +639,7 @@ static int dw_hdmi_qp_i2c_read(struct dw_hdmi_qp *hdmi, dev_dbg_ratelimited(hdmi->dev, "i2c read timed out\n"); else - dev_err(hdmi->dev, "i2c read timed out\n"); + dev_err_ratelimited(hdmi->dev, "i2c read timed out\n"); dw_hdmi_qp_write(hdmi, 0x01, I2CM_CONTROL0); return -EAGAIN; } @@ -574,7 +650,7 @@ static int dw_hdmi_qp_i2c_read(struct dw_hdmi_qp *hdmi, dev_dbg_ratelimited(hdmi->dev, "i2c read error\n"); else - dev_err(hdmi->dev, "i2c read error\n"); + dev_err_ratelimited(hdmi->dev, "i2c read error\n"); dw_hdmi_qp_write(hdmi, 0x01, I2CM_CONTROL0); return -EIO; } @@ -749,28 +825,597 @@ static struct i2c_adapter *dw_hdmi_qp_i2c_adapter(struct dw_hdmi_qp *hdmi) return adap; } +enum dw_hdmi_qp_frl_lts { + LTS1, /* Read EDID */ + LTS2, /* Prepare for FRL */ + LTS3, /* Training in progress */ + LTS4, /* Update FRL rate */ + LTSP, /* Training passed */ + LTSL, /* Legacy TMDS */ + LTSU, /* Undefined */ +}; + +/* + * Check sink version and FLT no-timeout mode. + */ +static int dw_hdmi_qp_frl_lts1(struct dw_hdmi_qp *hdmi) +{ + int ret; + u8 val; + + if (!hdmi->tmds_char_rate) { + dev_dbg(hdmi->dev, "lts1: hdmi disabled\n"); + return LTSL; + } + + dw_hdmi_qp_mod(hdmi, AVP_DATAPATH_VIDEO_SWDISABLE, + AVP_DATAPATH_VIDEO_SWDISABLE, GLOBAL_SWDISABLE); + + /* Reset AVP data path */ + dw_hdmi_qp_write(hdmi, AVP_DATAPATH_SWINIT_P, GLOBAL_SWRESET_REQUEST); + + ret = drm_scdc_readb(hdmi->bridge.ddc, SCDC_SINK_VERSION, &val); + if (ret) { + dev_err(hdmi->dev, "lts1: SCDC read failed\n"); + return LTSL; + } + + if (!val) { + dev_warn(hdmi->dev, "lts1: SCDC sink version is zero\n"); + return LTSL; + } + + ret = drm_scdc_writeb(hdmi->bridge.ddc, SCDC_SOURCE_VERSION, 1); + if (ret) { + dev_err(hdmi->dev, "lts1: SCDC write failed\n"); + return LTSL; + } + + ret = drm_scdc_readb(hdmi->bridge.ddc, SCDC_SOURCE_TEST_CONFIG, &val); + if (ret) { + dev_err(hdmi->dev, "lts1: SCDC read failed\n"); + return LTSL; + } + + hdmi->flt_no_timeout = !!(val & SCDC_FLT_NO_TIMEOUT); + dev_dbg(hdmi->dev, "lts1: flt_no_timeout=%d\n", hdmi->flt_no_timeout); + + return LTS2; +} + +/* + * Check if sink is ready to training. Set FRL rate & max FFE level. + */ +static int dw_hdmi_qp_frl_lts2(struct dw_hdmi_qp *hdmi) +{ + const struct dw_hdmi_qp_link_cfg *link_cfg; + int ret, i; + u8 val; + + for (i = 0; i < 20; i++) { + ret = drm_scdc_readb(hdmi->bridge.ddc, SCDC_STATUS_FLAGS_0, &val); + if (ret) { + dev_err(hdmi->dev, "lts2: SCDC read failed\n"); + return LTSL; + } + + if (val & SCDC_FLT_READY) { + link_cfg = hdmi->phy.ops->get_link_cfg(hdmi, hdmi->phy.data); + val = hdmi->phy.ops->set_ffe_level ? link_cfg->max_ffe_level : 0; + + dev_dbg(hdmi->dev, "lts2: set rate=%ux%u maxffe=%u\n", + link_cfg->frl_rate_per_lane, link_cfg->frl_lanes, val); + + ret = drm_scdc_set_frl(hdmi->curr_conn, link_cfg->frl_rate_per_lane, + link_cfg->frl_lanes, val); + if (ret) + ret = drm_scdc_writeb(hdmi->bridge.ddc, SCDC_CONFIG_0, 0); + else + ret = -EIO; + + if (ret) { + dev_err(hdmi->dev, "lts2: SCDC write failed\n"); + return LTSL; + } + + return LTS3; + } + + msleep(20); + } + + dev_err(hdmi->dev, "lts2: sink flt not ready\n"); + return LTSL; +} + +/* + * Conduct link training for the specified FRL rate. + */ +static int dw_hdmi_qp_frl_lts3(struct dw_hdmi_qp *hdmi) +{ + u8 val, ffe_lv = 0; + int i, ret; + + /* Set 2s timeout */ + i = 4000; + + while (i-- > 0 || hdmi->flt_no_timeout) { + /* Poll FLT_update flag every 2 ms or less */ + usleep_range(400, 500); + + if (!hdmi->tmds_char_rate) { + dev_dbg(hdmi->dev, "lts3: hdmi disabled\n"); + return LTSL; + } + + ret = drm_scdc_readb(hdmi->bridge.ddc, SCDC_UPDATE_0, &val); + if (ret) { + dev_err(hdmi->dev, "lts3: SCDC read failed\n"); + return LTSL; + } + + if (val & SCDC_SOURCE_TEST_UPDATE) { + u8 test_cfg; + + ret = drm_scdc_readb(hdmi->bridge.ddc, SCDC_SOURCE_TEST_CONFIG, + &test_cfg); + if (ret) { + dev_err(hdmi->dev, "lts3: SCDC read failed\n"); + return LTSL; + } + + if (hdmi->flt_no_timeout && !(test_cfg & SCDC_FLT_NO_TIMEOUT)) { + dev_dbg(hdmi->dev, "lts3: exit test mode\n"); + hdmi->flt_no_timeout = false; + } else if (!hdmi->flt_no_timeout && (test_cfg & SCDC_FLT_NO_TIMEOUT)) { + dev_dbg(hdmi->dev, "lts3: enter test mode\n"); + hdmi->flt_no_timeout = true; + } + + /* Clear SCDC_SOURCE_TEST_UPDATE flag */ + ret = drm_scdc_writeb(hdmi->bridge.ddc, SCDC_UPDATE_0, + SCDC_SOURCE_TEST_UPDATE); + if (ret) { + dev_err(hdmi->dev, "lts3: SCDC write failed\n"); + return LTSL; + } + } + + if (val & SCDC_FLT_UPDATE) { + u8 ln0, ln1, ln2, ln3; + u32 flt_cfg; + + ret = drm_scdc_get_frl_ltp_request(hdmi->curr_conn, + &ln0, &ln1, &ln2, &ln3); + if (!ret) { + dev_err(hdmi->dev, "lts3: SCDC read failed\n"); + return LTSL; + } + + dev_dbg(hdmi->dev, "lts3: ln0=0x%x ln1=0x%x ln2=0x%x ln3=0x%x\n", + ln0, ln1, ln2, ln3); + + if (!ln0 && !ln1 && !ln2 && !ln3) { + dw_hdmi_qp_write(hdmi, 0, FLT_CONFIG1); + return LTSP; + } + + if (ln0 == 0xf && ln1 == 0xf && ln2 == 0xf && ln3 == 0xf) { + dev_dbg(hdmi->dev, "lts3: rate change request\n"); + return LTS4; + } + + if (ln0 == 0xe || ln1 == 0xe || ln2 == 0xe || ln3 == 0xe) { + if (!hdmi->phy.ops->set_ffe_level) { + dev_err(hdmi->dev, "lts3: ffe level update not expected\n"); + return LTSL; + } + + if (ffe_lv >= 3) { + dev_err(hdmi->dev, "lts3: ffe level limit reached\n"); + return LTSL; + } + + ffe_lv++; + dev_dbg(hdmi->dev, "lts3: ffe level up %d\n", ffe_lv); + + ret = hdmi->phy.ops->set_ffe_level(hdmi, hdmi->phy.data, ffe_lv); + if (ret) + return LTSL; + } else { + flt_cfg = (ln3 << 16) | (ln2 << 12) | (ln1 << 8) | + (ln0 << 4) | 0xf; + + /* Support HFR1-10; send old LTP if ln0..3 == 0x3 */ + if (!hdmi->flt_no_timeout && flt_cfg == 0x3333f) + flt_cfg = dw_hdmi_qp_read(hdmi, FLT_CONFIG1); + + dw_hdmi_qp_write(hdmi, flt_cfg, FLT_CONFIG1); + } + + /* Clear FLT_update flag */ + ret = drm_scdc_writeb(hdmi->bridge.ddc, SCDC_UPDATE_0, + SCDC_FLT_UPDATE); + if (ret) { + dev_err(hdmi->dev, "lts3: SCDC write failed\n"); + return LTSL; + } + } + } + + dev_err(hdmi->dev, "lts3: timed out\n"); + return LTSL; +} + +/* + * Handle FRL rate change request from sink. + */ +static int dw_hdmi_qp_frl_lts4(struct dw_hdmi_qp *hdmi) +{ + const struct dw_hdmi_qp_link_cfg *link_cfg; + u8 try_rate_per_lane, try_lanes, max_ffe_level; + int ret; + + link_cfg = hdmi->phy.ops->get_link_cfg(hdmi, hdmi->phy.data); + + /* Choose a reduced bandwidth FRL rate */ + ret = drm_scdc_calc_lower_frl(link_cfg->frl_rate_per_lane, link_cfg->frl_lanes, + &try_rate_per_lane, &try_lanes); + if (!ret) { + dev_err(hdmi->dev, "lts4: failed to compute lower frl\n"); + return LTSL; + } + + if (try_rate_per_lane < link_cfg->min_frl_rate_per_lane || + try_lanes < link_cfg->min_frl_lanes) { + dev_err(hdmi->dev, "lts4: unsupported %ux%u rate\n", + try_rate_per_lane, try_lanes); + return LTSL; + } + + dev_dbg(hdmi->dev, "lts4: switching to %ux%u\n", try_rate_per_lane, try_lanes); + + ret = drm_scdc_writeb(hdmi->bridge.ddc, SCDC_UPDATE_0, SCDC_FLT_UPDATE); + if (ret) { + dev_err(hdmi->dev, "lts4: SCDC write failed\n"); + return LTSL; + } + + /* Disable phy */ + hdmi->phy.ops->disable(hdmi, hdmi->phy.data); + + ret = hdmi->phy.ops->set_frl_rate(hdmi, hdmi->phy.data, + try_rate_per_lane, try_lanes); + if (ret) { + dev_err(hdmi->dev, "lts4: failed to set phy link rate\n"); + return LTSL; + } + + /* Enable phy */ + hdmi->phy.ops->init(hdmi, hdmi->phy.data); + + max_ffe_level = hdmi->phy.ops->set_ffe_level ? link_cfg->max_ffe_level : 0; + + /* Set new rate */ + ret = drm_scdc_set_frl(hdmi->curr_conn, try_rate_per_lane, try_lanes, + max_ffe_level); + if (ret) + ret = drm_scdc_writeb(hdmi->bridge.ddc, SCDC_UPDATE_0, + SCDC_FLT_UPDATE); + else + ret = -EIO; + + if (ret) { + dev_err(hdmi->dev, "lts4: SCDC write failed\n"); + return LTSL; + } + + return LTS3; +} + +/* + * Training passed, wait for further changes. + */ +static int dw_hdmi_qp_frl_ltsp(struct dw_hdmi_qp *hdmi) +{ + int i, ret; + u8 val; + + ret = drm_scdc_writeb(hdmi->bridge.ddc, SCDC_UPDATE_0, SCDC_FLT_UPDATE); + if (ret) { + dev_err(hdmi->dev, "ltsp: SCDC write failed\n"); + return LTSL; + } + + /* Set 2s timeout */ + i = 4000; + + while (i--) { + /* Poll Update Flags every 2 ms or less */ + usleep_range(400, 500); + + if (!hdmi->tmds_char_rate) { + dev_dbg(hdmi->dev, "ltsp: hdmi disabled\n"); + return LTSL; + } + + ret = drm_scdc_readb(hdmi->bridge.ddc, SCDC_UPDATE_0, &val); + if (ret) { + dev_err(hdmi->dev, "ltsp: SCDC read failed\n"); + return LTSL; + } + + if (val & SCDC_FRL_START) { + dw_hdmi_qp_mod(hdmi, 0, AVP_DATAPATH_VIDEO_SWDISABLE, + GLOBAL_SWDISABLE); + + ret = drm_scdc_writeb(hdmi->bridge.ddc, SCDC_UPDATE_0, + SCDC_FRL_START); + if (ret) { + dev_err(hdmi->dev, "ltsp: SCDC write failed\n"); + return LTSL; + } + + dw_hdmi_qp_write(hdmi, PKTSCHED_GCP_CLEAR_AVMUTE, + PKTSCHED_PKT_CONTROL0); + dw_hdmi_qp_mod(hdmi, PKTSCHED_GCP_TX_EN, PKTSCHED_GCP_TX_EN, + PKTSCHED_PKT_EN); + + dev_dbg(hdmi->dev, "ltsp: flt success\n"); + break; + } + + if (val & SCDC_FLT_UPDATE) { + dw_hdmi_qp_mod(hdmi, AVP_DATAPATH_VIDEO_SWDISABLE, + AVP_DATAPATH_VIDEO_SWDISABLE, GLOBAL_SWDISABLE); + + ret = drm_scdc_writeb(hdmi->bridge.ddc, SCDC_UPDATE_0, + SCDC_FLT_UPDATE); + if (ret) { + dev_err(hdmi->dev, "ltsp: SCDC write failed\n"); + return LTSL; + } + + return LTS3; + } + } + + if (i < 0) { + dev_err(hdmi->dev, "ltsp: timed out\n"); + return LTSL; + } + + /* Ensure FLT_update flag is polled at least once every 250 ms. */ + i = 5; + + while (true) { + msleep(20); + + if (!hdmi->tmds_char_rate) { + dev_dbg(hdmi->dev, "ltsp: hdmi disabled\n"); + break; + } + + if (i) { + i--; + continue; + } + + ret = drm_scdc_readb(hdmi->bridge.ddc, SCDC_UPDATE_0, &val); + if (ret) { + dev_err(hdmi->dev, "ltsp: SCDC read failed\n"); + break; + } + + if (val & SCDC_FLT_UPDATE) { + dw_hdmi_qp_write(hdmi, PKTSCHED_GCP_SET_AVMUTE, + PKTSCHED_PKT_CONTROL0); + dw_hdmi_qp_mod(hdmi, PKTSCHED_GCP_TX_EN, PKTSCHED_GCP_TX_EN, + PKTSCHED_PKT_EN); + + msleep(50); + dw_hdmi_qp_mod(hdmi, AVP_DATAPATH_VIDEO_SWDISABLE, + AVP_DATAPATH_VIDEO_SWDISABLE, GLOBAL_SWDISABLE); + + ret = drm_scdc_writeb(hdmi->bridge.ddc, SCDC_UPDATE_0, + SCDC_FLT_UPDATE); + if (ret) { + dev_err(hdmi->dev, "ltsp: SCDC write failed\n"); + break; + } + + return LTS2; + } + + i = 5; + } + + return LTSL; +} + +/* + * Exit frl mode, i.e. for training failures or hdmi disabled. + */ +static int dw_hdmi_qp_frl_ltsl(struct dw_hdmi_qp *hdmi) +{ + enum drm_connector_status status; + + status = hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data); + + dev_dbg(hdmi->dev, "ltsl: conn_stat=%d\n", status); + + if (status != connector_status_disconnected) { + drm_scdc_set_frl(hdmi->curr_conn, 0, 0, 0); + drm_scdc_writeb(hdmi->bridge.ddc, SCDC_UPDATE_0, SCDC_FLT_UPDATE); + } + + dw_hdmi_qp_mod(hdmi, 0, AVP_DATAPATH_VIDEO_SWDISABLE, GLOBAL_SWDISABLE); + + return LTSU; +} + +static void dw_hdmi_qp_flt_work(struct work_struct *work) +{ + struct dw_hdmi_qp *hdmi = container_of(work, struct dw_hdmi_qp, flt_work); + enum dw_hdmi_qp_frl_lts state = LTS1; + + while (true) { + switch (state) { + case LTS1: + state = dw_hdmi_qp_frl_lts1(hdmi); + break; + case LTS2: + state = dw_hdmi_qp_frl_lts2(hdmi); + break; + case LTS3: + state = dw_hdmi_qp_frl_lts3(hdmi); + break; + case LTS4: + state = dw_hdmi_qp_frl_lts4(hdmi); + break; + case LTSP: + state = dw_hdmi_qp_frl_ltsp(hdmi); + break; + case LTSL: + state = dw_hdmi_qp_frl_ltsl(hdmi); + break; + case LTSU: + return; + default: + dev_err(hdmi->dev, "unexpected flt state: %d\n", state); + return; + } + } +} + +static bool dw_hdmi_qp_supports_scrambling(struct drm_display_info *display) +{ + if (!display->is_hdmi) + return false; + + return display->hdmi.scdc.supported && + display->hdmi.scdc.scrambling.supported; +} + +static int dw_hdmi_qp_set_scramb(struct dw_hdmi_qp *hdmi) +{ + bool done; + + dev_dbg(hdmi->dev, "set scrambling\n"); + + done = drm_scdc_set_high_tmds_clock_ratio(hdmi->curr_conn, true); + if (!done) + return -EIO; + + done = drm_scdc_set_scrambling(hdmi->curr_conn, true); + if (!done) { + drm_scdc_set_high_tmds_clock_ratio(hdmi->curr_conn, false); + return -EIO; + } + + schedule_delayed_work(&hdmi->scramb_work, + msecs_to_jiffies(SCRAMB_POLL_DELAY_MS)); + return 0; +} + +static void dw_hdmi_qp_scramb_work(struct work_struct *work) +{ + struct dw_hdmi_qp *hdmi = container_of(to_delayed_work(work), + struct dw_hdmi_qp, + scramb_work); + if (READ_ONCE(hdmi->scramb_enabled) && + !drm_scdc_get_scrambling_status(hdmi->curr_conn)) + dw_hdmi_qp_set_scramb(hdmi); +} + +static void dw_hdmi_qp_enable_scramb(struct dw_hdmi_qp *hdmi) +{ + int ret; + u8 ver; + + if (!dw_hdmi_qp_supports_scrambling(&hdmi->curr_conn->display_info)) + return; + + ret = drm_scdc_readb(hdmi->bridge.ddc, SCDC_SINK_VERSION, &ver); + if (ret) { + dev_err(hdmi->dev, "Failed to read SCDC_SINK_VERSION: %d\n", ret); + return; + } + + ret = drm_scdc_writeb(hdmi->bridge.ddc, SCDC_SOURCE_VERSION, + min_t(u8, ver, SCDC_MAX_SOURCE_VERSION)); + if (ret) { + dev_err(hdmi->dev, "Failed to write SCDC_SOURCE_VERSION: %d\n", ret); + return; + } + + WRITE_ONCE(hdmi->scramb_enabled, true); + + ret = dw_hdmi_qp_set_scramb(hdmi); + if (ret) { + hdmi->scramb_enabled = false; + return; + } + + dw_hdmi_qp_write(hdmi, 1, SCRAMB_CONFIG0); + + /* Wait at least 1 ms before resuming TMDS transmission */ + usleep_range(1000, 5000); +} + +static void dw_hdmi_qp_disable_scramb(struct dw_hdmi_qp *hdmi) +{ + if (!hdmi->scramb_enabled) + return; + + dev_dbg(hdmi->dev, "disable scrambling\n"); + + WRITE_ONCE(hdmi->scramb_enabled, false); + cancel_delayed_work_sync(&hdmi->scramb_work); + + dw_hdmi_qp_write(hdmi, 0, SCRAMB_CONFIG0); + + if (hdmi->curr_conn->status == connector_status_connected) { + drm_scdc_set_scrambling(hdmi->curr_conn, false); + drm_scdc_set_high_tmds_clock_ratio(hdmi->curr_conn, false); + } +} + static void dw_hdmi_qp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_atomic_state *state) { struct dw_hdmi_qp *hdmi = bridge->driver_private; - struct drm_connector_state *conn_state; - struct drm_connector *connector; + const struct drm_connector_state *conn_state; + const struct dw_hdmi_qp_link_cfg *link_cfg; + const struct drm_display_mode *mode; + struct drm_crtc_state *crtc_state; unsigned int op_mode; - connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder); - if (WARN_ON(!connector)) + hdmi->curr_conn = drm_atomic_get_new_connector_for_encoder(state, + bridge->encoder); + if (WARN_ON(!hdmi->curr_conn)) return; - conn_state = drm_atomic_get_new_connector_state(state, connector); + conn_state = drm_atomic_get_new_connector_state(state, hdmi->curr_conn); if (WARN_ON(!conn_state)) return; - if (connector->display_info.is_hdmi) { - dev_dbg(hdmi->dev, "%s mode=HDMI %s rate=%llu bpc=%u\n", __func__, + link_cfg = hdmi->phy.ops->get_link_cfg(hdmi, hdmi->phy.data); + + if (hdmi->curr_conn->display_info.is_hdmi) { + crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc); + mode = &crtc_state->mode; + dev_dbg(hdmi->dev, "%s mode=%ux%u@%uHz fmt=%s rate=%llu bpc=%u\n", __func__, + mode->hdisplay, mode->vdisplay, drm_mode_vrefresh(mode), drm_hdmi_connector_get_output_format_name(conn_state->hdmi.output_format), conn_state->hdmi.tmds_char_rate, conn_state->hdmi.output_bpc); + op_mode = 0; hdmi->tmds_char_rate = conn_state->hdmi.tmds_char_rate; + + if (!link_cfg->frl_enabled && + conn_state->hdmi.tmds_char_rate > HDMI14_MAX_TMDSCLK) + dw_hdmi_qp_enable_scramb(hdmi); } else { dev_dbg(hdmi->dev, "%s mode=DVI\n", __func__); op_mode = OPMODE_DVI; @@ -781,7 +1426,21 @@ static void dw_hdmi_qp_bridge_atomic_enable(struct drm_bridge *bridge, dw_hdmi_qp_mod(hdmi, HDCP2_BYPASS, HDCP2_BYPASS, HDCP2LOGIC_CONFIG0); dw_hdmi_qp_mod(hdmi, op_mode, OPMODE_DVI, LINK_CONFIG0); - drm_atomic_helper_connector_hdmi_update_infoframes(connector, state); + dw_hdmi_qp_mod(hdmi, + link_cfg->frl_enabled && link_cfg->frl_lanes == 4 ? OPMODE_FRL_4LANES : 0, + OPMODE_FRL_4LANES, LINK_CONFIG0); + dw_hdmi_qp_mod(hdmi, link_cfg->frl_enabled, OPMODE_FRL, LINK_CONFIG0); + + drm_atomic_helper_connector_hdmi_update_infoframes(hdmi->curr_conn, state); + + if (link_cfg->frl_enabled) { + /* Ensure phy output is stable before starting FLT */ + msleep(50); + schedule_work(&hdmi->flt_work); + } else { + dw_hdmi_qp_write(hdmi, PKTSCHED_GCP_CLEAR_AVMUTE, PKTSCHED_PKT_CONTROL0); + dw_hdmi_qp_mod(hdmi, PKTSCHED_GCP_TX_EN, PKTSCHED_GCP_TX_EN, PKTSCHED_PKT_EN); + } } static void dw_hdmi_qp_bridge_atomic_disable(struct drm_bridge *bridge, @@ -791,14 +1450,66 @@ static void dw_hdmi_qp_bridge_atomic_disable(struct drm_bridge *bridge, hdmi->tmds_char_rate = 0; + dw_hdmi_qp_write(hdmi, PKTSCHED_GCP_SET_AVMUTE, PKTSCHED_PKT_CONTROL0); + msleep(50); + + dw_hdmi_qp_disable_scramb(hdmi); + cancel_work_sync(&hdmi->flt_work); + + hdmi->curr_conn = NULL; hdmi->phy.ops->disable(hdmi, hdmi->phy.data); } -static enum drm_connector_status -dw_hdmi_qp_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector) +static int dw_hdmi_qp_reset_crtc(struct dw_hdmi_qp *hdmi, + struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx) +{ + u8 config; + int ret; + + if (hdmi->scramb_enabled) { + ret = drm_scdc_readb(hdmi->bridge.ddc, SCDC_TMDS_CONFIG, &config); + if (ret < 0) { + dev_err(hdmi->dev, "Failed to read TMDS config: %d\n", ret); + return ret; + } + + if (config & SCDC_SCRAMBLING_ENABLE) + return 0; + } else { + ret = drm_scdc_readb(hdmi->bridge.ddc, SCDC_CONFIG_1, &config); + if (ret < 0) { + dev_err(hdmi->dev, "Failed to read FRL config: %d\n", ret); + return ret; + } + + if (config & SCDC_FRL_RATE_MASK && work_busy(&hdmi->flt_work)) + return 0; + } + + drm_atomic_helper_connector_hdmi_hotplug(connector, + connector_status_connected); + /* + * Conform to HDMI 2.0 spec by ensuring scrambled data is not sent + * before configuring the sink scrambling, as well as suspending any + * TMDS transmission while changing the TMDS clock rate in the sink. + */ + + dev_dbg(hdmi->dev, "resetting crtc\n"); + + return drm_bridge_helper_reset_crtc(&hdmi->bridge, ctx); +} + +static int dw_hdmi_qp_bridge_detect_ctx(struct drm_bridge *bridge, + struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx) { struct dw_hdmi_qp *hdmi = bridge->driver_private; + const struct dw_hdmi_qp_link_cfg *link_cfg; + enum drm_connector_status status; const struct drm_edid *drm_edid; + bool frl_active; + int ret; if (hdmi->no_hpd) { drm_edid = drm_edid_read_ddc(connector, bridge->ddc); @@ -808,7 +1519,24 @@ dw_hdmi_qp_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connec return connector_status_disconnected; } - return hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data); + status = hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data); + + link_cfg = hdmi->phy.ops->get_link_cfg(hdmi, hdmi->phy.data); + frl_active = link_cfg->frl_enabled && hdmi->tmds_char_rate; + + dev_dbg(hdmi->dev, "%s status=%d frl=%u scramb=%u\n", __func__, + status, frl_active, hdmi->scramb_enabled); + + if (status == connector_status_connected && + (frl_active || hdmi->scramb_enabled)) { + ret = dw_hdmi_qp_reset_crtc(hdmi, connector, ctx); + if (ret == -EDEADLK) + return ret; + if (ret < 0) + status = connector_status_unknown; + } + + return status; } static const struct drm_edid * @@ -832,12 +1560,12 @@ dw_hdmi_qp_bridge_tmds_char_rate_valid(const struct drm_bridge *bridge, { struct dw_hdmi_qp *hdmi = bridge->driver_private; - /* - * TODO: when hdmi->no_hpd is 1 we must not support modes that - * require scrambling, including every mode with a clock above - * HDMI14_MAX_TMDSCLK. - */ - if (rate > HDMI14_MAX_TMDSCLK) { + if (hdmi->no_hpd && rate > HDMI14_MAX_TMDSCLK) { + dev_dbg(hdmi->dev, "Unsupported TMDS char rate in no_hpd mode: %lld\n", rate); + return MODE_CLOCK_HIGH; + } + + if (rate > HDMI20_MAX_TMDSRATE) { dev_dbg(hdmi->dev, "Unsupported TMDS char rate: %lld\n", rate); return MODE_CLOCK_HIGH; } @@ -845,12 +1573,47 @@ dw_hdmi_qp_bridge_tmds_char_rate_valid(const struct drm_bridge *bridge, return MODE_OK; } +static enum drm_mode_status +dw_hdmi_qp_bridge_frl_rate_valid(const struct drm_bridge *bridge, + const struct drm_display_mode *mode, + unsigned long long frl_data_rate, + unsigned long long frl_max_link_rate) +{ + struct dw_hdmi_qp *hdmi = bridge->driver_private; + const struct dw_hdmi_qp_link_cfg *link_cfg; + unsigned long long frl_bw; + + if (!hdmi->phy.ops->set_frl_rate) { + dev_dbg(hdmi->dev, "Unsupported FRL rate: %lld\n", frl_data_rate); + return MODE_CLOCK_HIGH; + } + + link_cfg = hdmi->phy.ops->get_link_cfg(hdmi, hdmi->phy.data); + + frl_bw = link_cfg->max_frl_rate_per_lane * + link_cfg->max_frl_lanes * 1000000000ULL; + if (frl_bw < frl_data_rate) { + dev_dbg(hdmi->dev, "Unsupported FRL data rate: req=%llu max=%llu\n", + frl_data_rate, frl_bw); + return MODE_CLOCK_HIGH; + } + + frl_bw = link_cfg->min_frl_rate_per_lane * + link_cfg->min_frl_lanes * 1000000000ULL; + if (frl_bw > frl_max_link_rate) { + dev_dbg(hdmi->dev, "Unsupported FRL link rate: req=%llu min=%llu\n", + frl_max_link_rate, frl_bw); + return MODE_CLOCK_LOW; + } + + return MODE_OK; +} + static int dw_hdmi_qp_bridge_clear_avi_infoframe(struct drm_bridge *bridge) { struct dw_hdmi_qp *hdmi = bridge->driver_private; - dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_AVI_TX_EN | PKTSCHED_GCP_TX_EN, - PKTSCHED_PKT_EN); + dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_AVI_TX_EN, PKTSCHED_PKT_EN); return 0; } @@ -886,11 +1649,11 @@ static int dw_hdmi_qp_bridge_clear_audio_infoframe(struct drm_bridge *bridge) { struct dw_hdmi_qp *hdmi = bridge->driver_private; - dw_hdmi_qp_mod(hdmi, 0, - PKTSCHED_ACR_TX_EN | - PKTSCHED_AUDS_TX_EN | - PKTSCHED_AUDI_TX_EN, - PKTSCHED_PKT_EN); + if (hdmi->tmds_char_rate) + dw_hdmi_qp_mod(hdmi, 0, + PKTSCHED_ACR_TX_EN | PKTSCHED_AMD_TX_EN | + PKTSCHED_AUDS_TX_EN | PKTSCHED_AUDI_TX_EN, + PKTSCHED_PKT_EN); return 0; } @@ -931,8 +1694,8 @@ static int dw_hdmi_qp_bridge_write_avi_infoframe(struct drm_bridge *bridge, dw_hdmi_qp_write_infoframe(hdmi, buffer, len, PKT_AVI_CONTENTS0); dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_AVI_FIELDRATE, PKTSCHED_PKT_CONFIG1); - dw_hdmi_qp_mod(hdmi, PKTSCHED_AVI_TX_EN | PKTSCHED_GCP_TX_EN, - PKTSCHED_AVI_TX_EN | PKTSCHED_GCP_TX_EN, PKTSCHED_PKT_EN); + dw_hdmi_qp_mod(hdmi, PKTSCHED_AVI_TX_EN, PKTSCHED_AVI_TX_EN, + PKTSCHED_PKT_EN); return 0; } @@ -989,7 +1752,10 @@ static int dw_hdmi_qp_bridge_write_audio_infoframe(struct drm_bridge *bridge, { struct dw_hdmi_qp *hdmi = bridge->driver_private; - dw_hdmi_qp_bridge_clear_audio_infoframe(bridge); + dw_hdmi_qp_mod(hdmi, 0, + PKTSCHED_ACR_TX_EN | PKTSCHED_AMD_TX_EN | + PKTSCHED_AUDS_TX_EN | PKTSCHED_AUDI_TX_EN, + PKTSCHED_PKT_EN); /* * AUDI_CONTENTS0: { RSV, HB2, HB1, RSV } @@ -1197,9 +1963,10 @@ static const struct drm_bridge_funcs dw_hdmi_qp_bridge_funcs = { .atomic_reset = drm_atomic_helper_bridge_reset, .atomic_enable = dw_hdmi_qp_bridge_atomic_enable, .atomic_disable = dw_hdmi_qp_bridge_atomic_disable, - .detect = dw_hdmi_qp_bridge_detect, + .detect_ctx = dw_hdmi_qp_bridge_detect_ctx, .edid_read = dw_hdmi_qp_bridge_edid_read, .hdmi_tmds_char_rate_valid = dw_hdmi_qp_bridge_tmds_char_rate_valid, + .hdmi_frl_rate_valid = dw_hdmi_qp_bridge_frl_rate_valid, .hdmi_clear_avi_infoframe = dw_hdmi_qp_bridge_clear_avi_infoframe, .hdmi_write_avi_infoframe = dw_hdmi_qp_bridge_write_avi_infoframe, .hdmi_clear_hdmi_infoframe = dw_hdmi_qp_bridge_clear_hdmi_infoframe, @@ -1277,7 +2044,8 @@ struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev, int ret; if (!plat_data->phy_ops || !plat_data->phy_ops->init || - !plat_data->phy_ops->disable || !plat_data->phy_ops->read_hpd) { + !plat_data->phy_ops->disable || !plat_data->phy_ops->read_hpd || + !plat_data->phy_ops->get_link_cfg) { dev_err(dev, "Missing platform PHY ops\n"); return ERR_PTR(-ENODEV); } @@ -1287,6 +2055,9 @@ struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev, if (IS_ERR(hdmi)) return ERR_CAST(hdmi); + INIT_DELAYED_WORK(&hdmi->scramb_work, dw_hdmi_qp_scramb_work); + INIT_WORK(&hdmi->flt_work, dw_hdmi_qp_flt_work); + hdmi->dev = dev; regs = devm_platform_ioremap_resource(pdev, 0); diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h index c07847e8d7dd09..dfb8cb6614ad7d 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h @@ -23,6 +23,7 @@ #define GLOBAL_SWRESET_REQUEST 0x40 #define EARCRX_CMDC_SWINIT_P BIT(27) #define AVP_DATAPATH_PACKET_AUDIO_SWINIT_P BIT(10) +#define AVP_DATAPATH_SWINIT_P BIT(6) #define GLOBAL_SWDISABLE 0x44 #define CEC_SWDISABLE BIT(17) #define AVP_DATAPATH_PACKET_AUDIO_SWDISABLE BIT(10) @@ -216,6 +217,8 @@ #define PKTSCHED_ACR_TX_EN BIT(1) #define PKTSCHED_NULL_TX_EN BIT(0) #define PKTSCHED_PKT_CONTROL0 0xaac +#define PKTSCHED_GCP_CLEAR_AVMUTE BIT(1) +#define PKTSCHED_GCP_SET_AVMUTE BIT(0) #define PKTSCHED_PKT_SEND 0xab0 #define PKTSCHED_PKT_STATUS0 0xab4 #define PKTSCHED_PKT_STATUS1 0xab8 diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c index 39cc18f78eda11..d9e5f02ccb7f50 100644 --- a/drivers/gpu/drm/display/drm_bridge_connector.c +++ b/drivers/gpu/drm/display/drm_bridge_connector.c @@ -180,6 +180,12 @@ static void drm_bridge_connector_oob_hotplug_event(struct drm_connector *connect struct drm_bridge_connector *bridge_connector = to_drm_bridge_connector(connector); + /* Notify all bridges in the pipeline of hotplug events. */ + drm_for_each_bridge_in_chain_scoped(bridge_connector->encoder, bridge) { + if (bridge->funcs->oob_notify) + bridge->funcs->oob_notify(bridge, connector, status); + } + drm_bridge_connector_handle_hpd(bridge_connector, status); } @@ -208,39 +214,6 @@ static void drm_bridge_connector_disable_hpd(struct drm_connector *connector) * Bridge Connector Functions */ -static enum drm_connector_status -drm_bridge_connector_detect(struct drm_connector *connector, bool force) -{ - struct drm_bridge_connector *bridge_connector = - to_drm_bridge_connector(connector); - struct drm_bridge *detect = bridge_connector->bridge_detect; - struct drm_bridge *hdmi = bridge_connector->bridge_hdmi; - enum drm_connector_status status; - - if (detect) { - status = detect->funcs->detect(detect, connector); - - if (hdmi) - drm_atomic_helper_connector_hdmi_hotplug(connector, status); - - drm_bridge_connector_hpd_notify(connector, status); - } else { - switch (connector->connector_type) { - case DRM_MODE_CONNECTOR_DPI: - case DRM_MODE_CONNECTOR_LVDS: - case DRM_MODE_CONNECTOR_DSI: - case DRM_MODE_CONNECTOR_eDP: - status = connector_status_connected; - break; - default: - status = connector_status_unknown; - break; - } - } - - return status; -} - static void drm_bridge_connector_force(struct drm_connector *connector) { struct drm_bridge_connector *bridge_connector = @@ -278,7 +251,6 @@ static void drm_bridge_connector_reset(struct drm_connector *connector) static const struct drm_connector_funcs drm_bridge_connector_funcs = { .reset = drm_bridge_connector_reset, - .detect = drm_bridge_connector_detect, .force = drm_bridge_connector_force, .fill_modes = drm_helper_probe_single_connector_modes, .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, @@ -291,15 +263,49 @@ static const struct drm_connector_funcs drm_bridge_connector_funcs = { * Bridge Connector Helper Functions */ +static int drm_bridge_connector_detect_ctx(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx, + bool force) +{ + struct drm_bridge_connector *bridge_connector = + to_drm_bridge_connector(connector); + struct drm_bridge *detect = bridge_connector->bridge_detect; + struct drm_bridge *hdmi = bridge_connector->bridge_hdmi; + int ret; + + if (detect) { + ret = drm_bridge_detect_ctx(detect, connector, ctx); + if (ret < 0) + return ret; + + if (hdmi) + drm_atomic_helper_connector_hdmi_hotplug(connector, ret); + + drm_bridge_connector_hpd_notify(connector, ret); + } else { + switch (connector->connector_type) { + case DRM_MODE_CONNECTOR_DPI: + case DRM_MODE_CONNECTOR_LVDS: + case DRM_MODE_CONNECTOR_DSI: + case DRM_MODE_CONNECTOR_eDP: + ret = connector_status_connected; + break; + default: + ret = connector_status_unknown; + break; + } + } + + return ret; +} + static int drm_bridge_connector_get_modes_edid(struct drm_connector *connector, struct drm_bridge *bridge) { - enum drm_connector_status status; const struct drm_edid *drm_edid; int n; - status = drm_bridge_connector_detect(connector, false); - if (status != connector_status_connected) + if (connector->status != connector_status_connected) goto no_edid; drm_edid = drm_bridge_edid_read(bridge, connector); @@ -384,6 +390,7 @@ static int drm_bridge_connector_atomic_check(struct drm_connector *connector, static const struct drm_connector_helper_funcs drm_bridge_connector_helper_funcs = { .get_modes = drm_bridge_connector_get_modes, + .detect_ctx = drm_bridge_connector_detect_ctx, .mode_valid = drm_bridge_connector_mode_valid, .enable_hpd = drm_bridge_connector_enable_hpd, .disable_hpd = drm_bridge_connector_disable_hpd, @@ -409,6 +416,27 @@ drm_bridge_connector_tmds_char_rate_valid(const struct drm_connector *connector, return MODE_OK; } +static enum drm_mode_status +drm_bridge_connector_frl_rate_valid(const struct drm_connector *connector, + const struct drm_display_mode *mode, + unsigned long long frl_data_rate, + unsigned long long frl_max_link_rate) +{ + struct drm_bridge_connector *bridge_connector = + to_drm_bridge_connector(connector); + struct drm_bridge *bridge; + + bridge = bridge_connector->bridge_hdmi; + if (!bridge) + return MODE_ERROR; + + if (bridge->funcs->hdmi_frl_rate_valid) + return bridge->funcs->hdmi_frl_rate_valid(bridge, mode, + frl_data_rate, + frl_max_link_rate); + return MODE_CLOCK_HIGH; +} + static int drm_bridge_connector_clear_avi_infoframe(struct drm_connector *connector) { struct drm_bridge_connector *bridge_connector = @@ -560,6 +588,7 @@ drm_bridge_connector_read_edid(struct drm_connector *connector) static const struct drm_connector_hdmi_funcs drm_bridge_connector_hdmi_funcs = { .tmds_char_rate_valid = drm_bridge_connector_tmds_char_rate_valid, + .frl_rate_valid = drm_bridge_connector_frl_rate_valid, .read_edid = drm_bridge_connector_read_edid, .avi = { .clear_infoframe = drm_bridge_connector_clear_avi_infoframe, diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c index 9f3b696aceeb4b..208180ccffdad0 100644 --- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c +++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c @@ -555,8 +555,30 @@ hdmi_clock_valid(const struct drm_connector *connector, const struct drm_connector_hdmi_funcs *funcs = connector->hdmi.funcs; const struct drm_display_info *info = &connector->display_info; - if (info->max_tmds_clock && clock > info->max_tmds_clock * 1000) - return MODE_CLOCK_HIGH; + if (info->max_tmds_clock && clock > info->max_tmds_clock * 1000) { + unsigned long long req_frl_bw, max_frl_bw; + + /* Check if FRL is supported by the sink */ + if (!info->hdmi.max_frl_rate_per_lane || !info->hdmi.max_lanes) + return MODE_CLOCK_HIGH; + + /* Mandate further (i.e. driver) FRL checks */ + if (!funcs || !funcs->frl_rate_valid) + return MODE_CLOCK_HIGH; + + /* + * Check required FRL bandwidth assuming 16b/18b encoding: + * bw (bps) = clock (Hz) * 3 (channels) * 8 (bit) * 18 / 16 + */ + req_frl_bw = clock * 27; + max_frl_bw = info->hdmi.max_frl_rate_per_lane * + info->hdmi.max_lanes * 1000000000ULL; + + if (req_frl_bw > max_frl_bw) + return MODE_CLOCK_HIGH; + + return funcs->frl_rate_valid(connector, mode, req_frl_bw, max_frl_bw); + } if (funcs && funcs->tmds_char_rate_valid) { enum drm_mode_status status; diff --git a/drivers/gpu/drm/display/drm_scdc_helper.c b/drivers/gpu/drm/display/drm_scdc_helper.c index df878aad4a36b2..1f10f0fb7cabb7 100644 --- a/drivers/gpu/drm/display/drm_scdc_helper.c +++ b/drivers/gpu/drm/display/drm_scdc_helper.c @@ -21,6 +21,7 @@ * DEALINGS IN THE SOFTWARE. */ +#include #include #include #include @@ -276,3 +277,188 @@ bool drm_scdc_set_high_tmds_clock_ratio(struct drm_connector *connector, return true; } EXPORT_SYMBOL(drm_scdc_set_high_tmds_clock_ratio); + +static int drm_scdc_frl_config_to_rate(u8 config, u8 *rate_per_lane, u8 *lanes) +{ + switch (config) { + case SCDC_FRL_RATE_12GBPS_4LANE: + *rate_per_lane = 12; + *lanes = 4; + return 0; + case SCDC_FRL_RATE_10GBPS_4LANE: + *rate_per_lane = 10; + *lanes = 4; + return 0; + case SCDC_FRL_RATE_8GBPS_4LANE: + *rate_per_lane = 8; + *lanes = 4; + return 0; + case SCDC_FRL_RATE_6GBPS_4LANE: + *rate_per_lane = 6; + *lanes = 4; + return 0; + case SCDC_FRL_RATE_6GBPS_3LANE: + *rate_per_lane = 6; + *lanes = 3; + return 0; + case SCDC_FRL_RATE_3GBPS_3LANE: + *rate_per_lane = 3; + *lanes = 3; + return 0; + case SCDC_FRL_RATE_DISABLE: + *rate_per_lane = 0; + *lanes = 0; + return 0; + default: + return -EINVAL; + } +} + +static int drm_scdc_frl_rate_to_config(u8 rate_per_lane, u8 lanes) +{ + if (lanes != 0 && lanes != 3 && lanes != 4) + return -EINVAL; + + switch (rate_per_lane * lanes) { + case 48: + return SCDC_FRL_RATE_12GBPS_4LANE; + case 40: + return SCDC_FRL_RATE_10GBPS_4LANE; + case 32: + return SCDC_FRL_RATE_8GBPS_4LANE; + case 24: + return SCDC_FRL_RATE_6GBPS_4LANE; + case 18: + return SCDC_FRL_RATE_6GBPS_3LANE; + case 9: + return SCDC_FRL_RATE_3GBPS_3LANE; + case 0: + return SCDC_FRL_RATE_DISABLE; + default: + return -EINVAL; + } +} + +/** + * drm_scdc_set_frl - set FRL rate and FFE + * @connector: connector + * @rate_per_lane: FRL rate for a single lane (Gbps) + * @lanes: FRL lane count (3 or 4) + * @max_ffe_level: max TxFFE level for indicated FRL Rate (0..3) + * + * Writes over SCDC the FRL config register over SCDC channel, and sets + * FRL_Rate according to rate_per_lane x lanes, as well as FFE_levels + * according to max_ffe_level. + * + * Returns: + * True if write is successful, false otherwise. + */ +bool drm_scdc_set_frl(struct drm_connector *connector, + u8 rate_per_lane, u8 lanes, u8 max_ffe_level) +{ + u8 config; + int ret; + + ret = drm_scdc_frl_rate_to_config(rate_per_lane, lanes); + if (ret < 0 || max_ffe_level > 3) { + drm_dbg_kms(connector->dev, + "[CONNECTOR:%d:%s] Invalid FRL config: rate=%ux%u ffe=%u\n", + connector->base.id, connector->name, rate_per_lane, + lanes, max_ffe_level); + return false; + } + + config = FIELD_PREP(SCDC_FRL_RATE_MASK, ret) | + FIELD_PREP(SCDC_FFE_LEVELS_MASK, max_ffe_level); + + ret = drm_scdc_writeb(connector->ddc, SCDC_CONFIG_1, config); + if (ret < 0) { + drm_dbg_kms(connector->dev, + "[CONNECTOR:%d:%s] Failed to set FRL: %d\n", + connector->base.id, connector->name, ret); + return false; + } + + return true; +} +EXPORT_SYMBOL(drm_scdc_set_frl); + +/** + * drm_scdc_calc_lower_frl - compute a reduced bandwidth FRL rate + * @in_rate_per_lane: input FRL rate for a single lane (Gbps) + * @in_lanes: input FRL lane count (3 or 4) + * @out_rate_per_lane: output FRL rate for a single lane (Gbps) + * @out_lanes: output FRL lane count (3 or 4) + * + * Determinates the FRL rate configuration with the highest bandwidth that is + * still lower than the bandwidth corresponding to the given input configuration. + * The resulting configuration is stored in out_rate_per_lane and out_lanes. + * + * Returns: + * True if computation was successful, false otherwise. + */ +bool drm_scdc_calc_lower_frl(u8 in_rate_per_lane, u8 in_lanes, + u8 *out_rate_per_lane, u8 *out_lanes) +{ + int ret; + + ret = drm_scdc_frl_rate_to_config(in_rate_per_lane, in_lanes); + if (ret < 0) + return false; + + ret--; + + if (ret <= SCDC_FRL_RATE_DISABLE) + return false; + + ret = drm_scdc_frl_config_to_rate(ret, out_rate_per_lane, out_lanes); + + return ret == 0; +} +EXPORT_SYMBOL(drm_scdc_calc_lower_frl); + +/** + * drm_scdc_get_frl_ltp_request - read LTP requested by the Sink for FRL lanes + * @connector: connector + * @ln0: output LTP request for FRL lane 0 + * @ln1: output LTP request for FRL lane 1 + * @ln2: output LTP request for FRL lane 2 + * @ln3: output LTP request for FRL lane 3 + * + * Reads over SCDC the Link Training Pattern (LTP) requested by the Sink for + * each of the four FRL lanes and stores the values in ln0..3. + * + * Returns: + * True on success, false otherwise. + */ +bool drm_scdc_get_frl_ltp_request(struct drm_connector *connector, + u8 *ln0, u8 *ln1, u8 *ln2, u8 *ln3) +{ + u8 status; + int ret; + + ret = drm_scdc_readb(connector->ddc, SCDC_STATUS_FLAGS_1, &status); + if (ret < 0) { + drm_dbg_kms(connector->dev, + "[CONNECTOR:%d:%s] Failed to read LTP{0,1}: %d\n", + connector->base.id, connector->name, ret); + return false; + } + + *ln0 = FIELD_GET(SCDC_FRL_LN0_LTP_REQ_MASK, status); + *ln1 = FIELD_GET(SCDC_FRL_LN1_LTP_REQ_MASK, status); + + ret = drm_scdc_readb(connector->ddc, SCDC_STATUS_FLAGS_2, &status); + if (ret < 0) { + drm_dbg_kms(connector->dev, + "[CONNECTOR:%d:%s] Failed to read LTP{2,3}: %d\n", + connector->base.id, connector->name, ret); + return false; + } + + *ln2 = FIELD_GET(SCDC_FRL_LN2_LTP_REQ_MASK, status); + *ln3 = FIELD_GET(SCDC_FRL_LN3_LTP_REQ_MASK, status); + + return true; +} +EXPORT_SYMBOL(drm_scdc_get_frl_ltp_request); diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index d6f512b733896d..62c3393cb6bcbd 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -1360,9 +1360,9 @@ EXPORT_SYMBOL(drm_atomic_bridge_chain_check); * @connector: attached connector * * If the bridge supports output detection, as reported by the - * DRM_BRIDGE_OP_DETECT bridge ops flag, call &drm_bridge_funcs.detect for the - * bridge and return the connection status. Otherwise return - * connector_status_unknown. + * DRM_BRIDGE_OP_DETECT bridge ops flag, call &drm_bridge_funcs.detect_ctx + * or &drm_bridge_funcs.detect for the bridge and return the connection status. + * Otherwise return connector_status_unknown. * * RETURNS: * The detection status on success, or connector_status_unknown if the bridge @@ -1371,12 +1371,68 @@ EXPORT_SYMBOL(drm_atomic_bridge_chain_check); enum drm_connector_status drm_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector) { + return drm_bridge_detect_ctx(bridge, connector, NULL); +} +EXPORT_SYMBOL_GPL(drm_bridge_detect); + +/** + * drm_bridge_detect_ctx - check if anything is attached to the bridge output + * @bridge: bridge control structure + * @connector: attached connector + * @ctx: acquire_ctx, or NULL to let this function handle locking + * + * If the bridge supports output detection, as reported by the + * DRM_BRIDGE_OP_DETECT bridge ops flag, call &drm_bridge_funcs.detect_ctx + * or &drm_bridge_funcs.detect for the bridge and return the connection status. + * Otherwise return connector_status_unknown. + * + * RETURNS: + * The detection status on success, or connector_status_unknown if the bridge + * doesn't support output detection. + * If @ctx is set, it might also return -EDEADLK. + */ +int drm_bridge_detect_ctx(struct drm_bridge *bridge, + struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx) +{ + struct drm_modeset_acquire_ctx br_ctx; + int ret; + if (!(bridge->ops & DRM_BRIDGE_OP_DETECT)) return connector_status_unknown; - return bridge->funcs->detect(bridge, connector); + if (!bridge->funcs->detect_ctx) + return bridge->funcs->detect(bridge, connector); + + if (ctx) { + ret = bridge->funcs->detect_ctx(bridge, connector, ctx); + if (ret == -EDEADLK) + return ret; + + goto out; + } + + drm_modeset_acquire_init(&br_ctx, 0); +retry: + ret = drm_modeset_lock(&connector->dev->mode_config.connection_mutex, + &br_ctx); + if (!ret) + ret = bridge->funcs->detect_ctx(bridge, connector, &br_ctx); + + if (ret == -EDEADLK) { + drm_modeset_backoff(&br_ctx); + goto retry; + } + + drm_modeset_drop_locks(&br_ctx); + drm_modeset_acquire_fini(&br_ctx); +out: + if (WARN_ON(ret < 0)) + ret = connector_status_unknown; + + return ret; } -EXPORT_SYMBOL_GPL(drm_bridge_detect); +EXPORT_SYMBOL_GPL(drm_bridge_detect_ctx); /** * drm_bridge_get_modes - fill all modes currently valid for the sink into the diff --git a/drivers/gpu/drm/drm_bridge_helper.c b/drivers/gpu/drm/drm_bridge_helper.c index 420f29cf3e5435..0a3c8fee66b325 100644 --- a/drivers/gpu/drm/drm_bridge_helper.c +++ b/drivers/gpu/drm/drm_bridge_helper.c @@ -50,8 +50,6 @@ int drm_bridge_helper_reset_crtc(struct drm_bridge *bridge, crtc = connector->state->crtc; ret = drm_atomic_helper_reset_crtc(crtc, ctx); - if (ret) - goto out; out: drm_modeset_unlock(&dev->mode_config.connection_mutex); diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig index 1479b8c4ed40ad..68e222350aeaec 100644 --- a/drivers/gpu/drm/rockchip/Kconfig +++ b/drivers/gpu/drm/rockchip/Kconfig @@ -20,7 +20,7 @@ config DRM_ROCKCHIP select DRM_INNO_HDMI if ROCKCHIP_INNO_HDMI select GENERIC_PHY if ROCKCHIP_DW_MIPI_DSI select GENERIC_PHY_MIPI_DPHY if ROCKCHIP_DW_MIPI_DSI - select SND_SOC_HDMI_CODEC if ROCKCHIP_CDN_DP && SND_SOC + select SND_SOC_HDMI_CODEC if SND_SOC && (ROCKCHIP_CDN_DP || ROCKCHIP_DW_HDMI_QP || ROCKCHIP_DW_DP) help Choose this option if you have a Rockchip soc chipset. This driver provides kernel mode setting and buffer diff --git a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c index dac3d202971eda..1ea030db19748b 100644 --- a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c @@ -7,12 +7,17 @@ */ #include +#include +#include #include #include +#include +#include #include #include #include #include +#include #include #include #include @@ -24,12 +29,58 @@ #include "rockchip_drm_drv.h" #include "rockchip_drm_vop.h" +#define ROCKCHIP_MAX_CTRLS 2 + +#define ROCKCHIP_VO_GRF_DP_SINK_HPD_SEL BIT(10) +#define ROCKCHIP_VO_GRF_DP_SINK_HPD_CFG BIT(11) + +struct rockchip_dw_dp_plat_data { + u8 num_ctrls; + u32 ctrl_ids[ROCKCHIP_MAX_CTRLS]; + u32 max_link_rate; + u8 pixel_mode; + u32 hpd_reg[ROCKCHIP_MAX_CTRLS]; +}; + struct rockchip_dw_dp { struct dw_dp *base; struct device *dev; + const struct rockchip_dw_dp_plat_data *pdata; + struct regmap *vo_grf; struct rockchip_encoder encoder; + int id; + bool hpd_sel; + bool hpd_cfg; }; +static void dw_dp_rockchip_hpd_sw_sel(void *data, bool force_hpd_from_sw) +{ + struct rockchip_dw_dp *dp = data; + u32 hpd_reg = dp->pdata->hpd_reg[dp->id]; + + dp->hpd_sel = force_hpd_from_sw; + + ACQUIRE(pm_runtime_active_auto, pm)(dp->dev); + + regmap_write(dp->vo_grf, hpd_reg, + FIELD_PREP_WM16_CONST(ROCKCHIP_VO_GRF_DP_SINK_HPD_SEL, dp->hpd_sel)); +} + +static void dw_dp_rockchip_hpd_sw_cfg(void *data, bool hpd) +{ + struct rockchip_dw_dp *dp = data; + u32 hpd_reg = dp->pdata->hpd_reg[dp->id]; + + dev_dbg(dp->dev, "Force HPD connected=%s\n", str_yes_no(hpd)); + + dp->hpd_cfg = hpd; + + ACQUIRE(pm_runtime_active_auto, pm)(dp->dev); + + regmap_write(dp->vo_grf, hpd_reg, + FIELD_PREP_WM16_CONST(ROCKCHIP_VO_GRF_DP_SINK_HPD_CFG, dp->hpd_cfg)); +} + static int dw_dp_encoder_atomic_check(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state) @@ -72,27 +123,89 @@ static const struct drm_encoder_helper_funcs dw_dp_encoder_helper_funcs = { .atomic_check = dw_dp_encoder_atomic_check, }; +static struct regmap *dp_dp_rockchip_get_vo_grf(struct rockchip_dw_dp *dp) +{ + struct device_node *np = dev_of_node(dp->dev); + struct of_phandle_args args; + struct regmap *regmap; + int ret; + + ret = of_parse_phandle_with_args(np, "phys", "#phy-cells", 0, &args); + if (ret) + return ERR_PTR(-ENODEV); + + /* + * Limit this workaround to RK3576 and RK3588, new platforms should + * add a VO GRF phandle in the DisplayPort DT node. + */ + if (!of_device_is_compatible(args.np, "rockchip,rk3576-usbdp-phy") && + !of_device_is_compatible(args.np, "rockchip,rk3588-usbdp-phy")) { + regmap = ERR_PTR(-ENODEV); + goto out_put_node; + } + + regmap = syscon_regmap_lookup_by_phandle(args.np, "rockchip,vo-grf"); + +out_put_node: + of_node_put(args.np); + return regmap; +} + static int dw_dp_rockchip_bind(struct device *dev, struct device *master, void *data) { + const struct rockchip_dw_dp_plat_data *plat_data_const; struct platform_device *pdev = to_platform_device(dev); - const struct dw_dp_plat_data *plat_data; + struct dw_dp_plat_data *plat_data; struct drm_device *drm_dev = data; struct rockchip_dw_dp *dp; struct drm_encoder *encoder; struct drm_connector *connector; - int ret; + struct resource *res; + int ret, id; + + plat_data = drmm_kzalloc(drm_dev, sizeof(*plat_data), GFP_KERNEL); + if (!plat_data) + return -ENOMEM; - dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL); + dp = drmm_kzalloc(drm_dev, sizeof(*dp), GFP_KERNEL); if (!dp) return -ENOMEM; dp->dev = dev; - platform_set_drvdata(pdev, dp); + dev_set_drvdata(dev, dp); - plat_data = of_device_get_match_data(dev); - if (!plat_data) + plat_data_const = device_get_match_data(dev); + if (!plat_data_const) return -ENODEV; + dp->pdata = plat_data_const; + + res = platform_get_mem_or_io(pdev, 0); + if (IS_ERR(res)) + return PTR_ERR(res); + + /* find the DisplayPort ID from the io address */ + dp->id = -ENODEV; + for (id = 0; id < plat_data_const->num_ctrls; id++) { + if (res->start == plat_data_const->ctrl_ids[id]) { + dp->id = id; + break; + } + } + + if (dp->id < 0) + return dp->id; + + dp->vo_grf = dp_dp_rockchip_get_vo_grf(dp); + if (IS_ERR(dp->vo_grf)) + return PTR_ERR(dp->vo_grf); + + plat_data->max_link_rate = plat_data_const->max_link_rate; + plat_data->pixel_mode = plat_data_const->pixel_mode; + plat_data->hpd_sw_sel = dw_dp_rockchip_hpd_sw_sel; + plat_data->hpd_sw_cfg = dw_dp_rockchip_hpd_sw_cfg; + plat_data->data = dp; + encoder = &dp->encoder.encoder; encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev, dev->of_node); rockchip_drm_encoder_set_crtc_endpoint_id(&dp->encoder, dev->of_node, 0, 0); @@ -103,48 +216,92 @@ static int dw_dp_rockchip_bind(struct device *dev, struct device *master, void * drm_encoder_helper_add(encoder, &dw_dp_encoder_helper_funcs); dp->base = dw_dp_bind(dev, encoder, plat_data); - if (IS_ERR(dp->base)) { - ret = PTR_ERR(dp->base); - return ret; - } + if (IS_ERR(dp->base)) + return PTR_ERR(dp->base); + + pm_runtime_use_autosuspend(dev); + pm_runtime_set_autosuspend_delay(dev, 500); + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "failed to enable runtime PM\n"); connector = drm_bridge_connector_init(drm_dev, encoder); - if (IS_ERR(connector)) { - ret = PTR_ERR(connector); - return dev_err_probe(dev, ret, "Failed to init bridge connector"); - } + if (IS_ERR(connector)) + ret = dev_err_probe(dev, PTR_ERR(connector), + "Failed to init bridge connector"); + else + ret = drm_connector_attach_encoder(connector, encoder); + + if (ret) + dw_dp_unbind(dp->base); - drm_connector_attach_encoder(connector, encoder); + return ret; +} - return 0; +static void dw_dp_rockchip_unbind(struct device *dev, struct device *master, + void *data) +{ + struct rockchip_dw_dp *dp = dev_get_drvdata(dev); + + dw_dp_unbind(dp->base); } static const struct component_ops dw_dp_rockchip_component_ops = { .bind = dw_dp_rockchip_bind, + .unbind = dw_dp_rockchip_unbind, }; static int dw_dp_probe(struct platform_device *pdev) { - struct device *dev = &pdev->dev; - - return component_add(dev, &dw_dp_rockchip_component_ops); + return component_add(&pdev->dev, &dw_dp_rockchip_component_ops); } static void dw_dp_remove(struct platform_device *pdev) { - struct rockchip_dw_dp *dp = platform_get_drvdata(pdev); + component_del(&pdev->dev, &dw_dp_rockchip_component_ops); +} + +static int dw_dp_rockchip_runtime_suspend(struct device *dev) +{ + struct rockchip_dw_dp *dp = dev_get_drvdata(dev); + return dw_dp_runtime_suspend(dp->base); +} + +static int dw_dp_rockchip_runtime_resume(struct device *dev) +{ + struct rockchip_dw_dp *dp = dev_get_drvdata(dev); + u32 hpd_reg = dp->pdata->hpd_reg[dp->id]; + int ret; + + ret = dw_dp_runtime_resume(dp->base); + if (ret) + return ret; - component_del(dp->dev, &dw_dp_rockchip_component_ops); + regmap_write(dp->vo_grf, hpd_reg, + FIELD_PREP_WM16_CONST(ROCKCHIP_VO_GRF_DP_SINK_HPD_SEL, dp->hpd_sel) | + FIELD_PREP_WM16_CONST(ROCKCHIP_VO_GRF_DP_SINK_HPD_CFG, dp->hpd_cfg)); + + return 0; } -static const struct dw_dp_plat_data rk3588_dp_plat_data = { +static const struct dev_pm_ops dw_dp_pm_ops = { + RUNTIME_PM_OPS(dw_dp_rockchip_runtime_suspend, dw_dp_rockchip_runtime_resume, NULL) +}; + +static const struct rockchip_dw_dp_plat_data rk3588_dp_plat_data = { + .num_ctrls = 2, + .ctrl_ids = {0xfde50000, 0xfde60000}, .max_link_rate = 810000, .pixel_mode = DW_DP_MP_QUAD_PIXEL, + .hpd_reg = {0x0000, 0x0008}, }; -static const struct dw_dp_plat_data rk3576_dp_plat_data = { +static const struct rockchip_dw_dp_plat_data rk3576_dp_plat_data = { + .num_ctrls = 1, + .ctrl_ids = {0x27e40000}, .max_link_rate = 810000, .pixel_mode = DW_DP_MP_DUAL_PIXEL, + .hpd_reg = {0x0000}, }; static const struct of_device_id dw_dp_of_match[] = { @@ -165,5 +322,6 @@ struct platform_driver dw_dp_driver = { .driver = { .name = "dw-dp", .of_match_table = dw_dp_of_match, + .pm = pm_ptr(&dw_dp_pm_ops), }, }; diff --git a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c index 1a09bcc96c3ece..4947b368bea040 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2021-2022 Rockchip Electronics Co., Ltd. - * Copyright (c) 2024 Collabora Ltd. + * Copyright (c) 2024-2026 Collabora Ltd. * * Author: Algea Cao * Author: Cristian Ciocaltea @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -62,13 +63,18 @@ #define RK3588_HDMI0_HPD_INT_CLR BIT(12) #define RK3588_HDMI1_HPD_INT_MSK BIT(15) #define RK3588_HDMI1_HPD_INT_CLR BIT(14) + #define RK3588_GRF_SOC_CON7 0x031c #define RK3588_HPD_HDMI0_IO_EN_MASK BIT(12) #define RK3588_HPD_HDMI1_IO_EN_MASK BIT(13) #define RK3588_GRF_SOC_STATUS1 0x0384 #define RK3588_HDMI0_LEVEL_INT BIT(16) #define RK3588_HDMI1_LEVEL_INT BIT(24) + #define RK3588_GRF_VO1_CON3 0x000c +#define RK3588_GRF_VO1_CON4 0x0010 +#define RK3588_HDMI21_MASK BIT(0) + #define RK3588_GRF_VO1_CON6 0x0018 #define RK3588_COLOR_DEPTH_MASK GENMASK(7, 4) #define RK3588_8BPC 0x0 @@ -80,25 +86,33 @@ #define RK3588_SDAIN_MASK BIT(10) #define RK3588_MODE_MASK BIT(11) #define RK3588_I2S_SEL_MASK BIT(13) + +#define RK3588_GRF_VO1_CON7 0x001c #define RK3588_GRF_VO1_CON9 0x0024 #define RK3588_HDMI0_GRANT_SEL BIT(10) #define RK3588_HDMI1_GRANT_SEL BIT(12) #define HOTPLUG_DEBOUNCE_MS 150 #define MAX_HDMI_PORT_NUM 2 +#define HDMI20_MAX_TMDS_RATE 600000000 +#define HDMI21_MAX_FRL_LANE_RATE 12 +#define HDMI21_MAX_FRL_LANE_NUM 4 +#define HDMI21_MIN_FRL_LANE_RATE 3 +#define HDMI21_MIN_FRL_LANE_NUM 3 struct rockchip_hdmi_qp { struct device *dev; struct regmap *regmap; struct regmap *vo_regmap; struct rockchip_encoder encoder; + struct drm_connector *connector; struct dw_hdmi_qp *hdmi; struct phy *phy; + struct dw_hdmi_qp_link_cfg link_cfg; struct gpio_desc *frl_enable_gpio; struct delayed_work hpd_work; int port_id; const struct rockchip_hdmi_qp_ctrl_ops *ctrl_ops; - unsigned long long tmds_char_rate; }; struct rockchip_hdmi_qp_ctrl_ops { @@ -118,16 +132,24 @@ static struct rockchip_hdmi_qp *to_rockchip_hdmi_qp(struct drm_encoder *encoder) static void dw_hdmi_qp_rockchip_encoder_enable(struct drm_encoder *encoder) { struct rockchip_hdmi_qp *hdmi = to_rockchip_hdmi_qp(encoder); + const struct dw_hdmi_qp_link_cfg *lcfg = &hdmi->link_cfg; struct drm_crtc *crtc = encoder->crtc; + struct rockchip_crtc_state *rks; - /* Unconditionally switch to TMDS as FRL is not yet supported */ - gpiod_set_value_cansleep(hdmi->frl_enable_gpio, 0); + gpiod_set_value_cansleep(hdmi->frl_enable_gpio, lcfg->frl_enabled); if (!crtc || !crtc->state) return; + rks = to_rockchip_crtc_state(crtc->state); + if (hdmi->ctrl_ops->enc_init) - hdmi->ctrl_ops->enc_init(hdmi, to_rockchip_crtc_state(crtc->state)); + hdmi->ctrl_ops->enc_init(hdmi, rks); + + dev_dbg(hdmi->dev, "%s port=%d tmds=%llu frl=%ux%u bpc=%u/%u\n", + __func__, hdmi->port_id, lcfg->tmds_char_rate, + lcfg->frl_rate_per_lane, lcfg->frl_lanes, + rks->output_bpc, lcfg->bpc); } static int @@ -135,29 +157,80 @@ dw_hdmi_qp_rockchip_encoder_atomic_check(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state) { - struct rockchip_hdmi_qp *hdmi = to_rockchip_hdmi_qp(encoder); + const struct drm_display_info *info = &conn_state->connector->display_info; + const struct drm_display_mode *adj_mode = &crtc_state->adjusted_mode; struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state); + struct rockchip_hdmi_qp *hdmi = to_rockchip_hdmi_qp(encoder); + struct dw_hdmi_qp_link_cfg *lcfg = &hdmi->link_cfg; union phy_configure_opts phy_cfg = {}; + unsigned int overscan; + enum phy_hdmi_mode mode; int ret; - if (hdmi->tmds_char_rate == conn_state->hdmi.tmds_char_rate && + overscan = min(conn_state->tv.overscan, 100u); + s->tv_margins.left = adj_mode->hdisplay * overscan / 200; + s->tv_margins.right = s->tv_margins.left; + s->tv_margins.top = adj_mode->vdisplay * overscan / 200; + s->tv_margins.bottom = s->tv_margins.top; + + if (lcfg->tmds_char_rate == conn_state->hdmi.tmds_char_rate && s->output_bpc == conn_state->hdmi.output_bpc) return 0; - phy_cfg.hdmi.tmds_char_rate = conn_state->hdmi.tmds_char_rate; phy_cfg.hdmi.bpc = conn_state->hdmi.output_bpc; - ret = phy_configure(hdmi->phy, &phy_cfg); - if (!ret) { - hdmi->tmds_char_rate = conn_state->hdmi.tmds_char_rate; - s->output_mode = ROCKCHIP_OUT_MODE_AAAA; - s->output_type = DRM_MODE_CONNECTOR_HDMIA; - s->output_bpc = conn_state->hdmi.output_bpc; + if (conn_state->hdmi.tmds_char_rate > HDMI20_MAX_TMDS_RATE || + (info->max_tmds_clock && + conn_state->hdmi.tmds_char_rate > info->max_tmds_clock * 1000)) { + mode = PHY_HDMI_MODE_FRL; + + if (info->hdmi.max_frl_rate_per_lane > lcfg->max_frl_rate_per_lane) + phy_cfg.hdmi.frl.rate_per_lane = lcfg->max_frl_rate_per_lane; + else + phy_cfg.hdmi.frl.rate_per_lane = info->hdmi.max_frl_rate_per_lane; + + if (info->hdmi.max_lanes > lcfg->max_frl_lanes) + phy_cfg.hdmi.frl.lanes = lcfg->max_frl_lanes; + else + phy_cfg.hdmi.frl.lanes = info->hdmi.max_lanes; } else { + mode = PHY_HDMI_MODE_TMDS; + + phy_cfg.hdmi.tmds_char_rate = conn_state->hdmi.tmds_char_rate; + } + + ret = phy_set_mode_ext(hdmi->phy, PHY_MODE_HDMI, mode); + if (ret) { + dev_err(hdmi->dev, "Failed to switch phy mode: %d\n", ret); + return ret; + } + + ret = phy_configure(hdmi->phy, &phy_cfg); + if (ret) { dev_err(hdmi->dev, "Failed to configure phy: %d\n", ret); + return ret; } - return ret; + lcfg->tmds_char_rate = conn_state->hdmi.tmds_char_rate; + + if (mode == PHY_HDMI_MODE_FRL) { + lcfg->frl_enabled = true; + lcfg->frl_rate_per_lane = phy_cfg.hdmi.frl.rate_per_lane; + lcfg->frl_lanes = phy_cfg.hdmi.frl.lanes; + } else { + lcfg->frl_enabled = false; + lcfg->frl_rate_per_lane = 0; + lcfg->frl_lanes = 0; + } + + lcfg->bpc = phy_cfg.hdmi.bpc; + + s->output_mode = ROCKCHIP_OUT_MODE_AAAA; + s->output_type = DRM_MODE_CONNECTOR_HDMIA; + s->output_bpc = conn_state->hdmi.output_bpc; + s->frl_enabled = lcfg->frl_enabled; + + return 0; } static const struct @@ -208,11 +281,67 @@ static void dw_hdmi_qp_rk3588_setup_hpd(struct dw_hdmi_qp *dw_hdmi, void *data) regmap_write(hdmi->regmap, RK3588_GRF_SOC_CON2, val); } +static const struct dw_hdmi_qp_link_cfg * +dw_hdmi_qp_rk3588_get_link_cfg(struct dw_hdmi_qp *dw_hdmi, void *data) +{ + struct rockchip_hdmi_qp *hdmi = (struct rockchip_hdmi_qp *)data; + + return &hdmi->link_cfg; +} + +static int dw_hdmi_qp_rk3588_set_frl_rate(struct dw_hdmi_qp *dw_hdmi, void *data, + u8 rate_per_lane, u8 lanes) +{ + struct rockchip_hdmi_qp *hdmi = (struct rockchip_hdmi_qp *)data; + union phy_configure_opts phy_cfg = {}; + int ret; + + if (!hdmi->link_cfg.frl_enabled || !rate_per_lane || !lanes) + return -EINVAL; + + phy_cfg.hdmi.frl.rate_per_lane = rate_per_lane; + phy_cfg.hdmi.frl.lanes = lanes; + phy_cfg.hdmi.bpc = hdmi->link_cfg.bpc; + + ret = phy_configure(hdmi->phy, &phy_cfg); + if (ret) { + dev_err(hdmi->dev, "Failed to set PHY FRL rate: %d\n", ret); + } else { + hdmi->link_cfg.frl_rate_per_lane = rate_per_lane; + hdmi->link_cfg.frl_lanes = lanes; + } + + return ret; +} + +static int dw_hdmi_qp_rk3588_set_ffe_level(struct dw_hdmi_qp *dw_hdmi, void *data, + u8 ffe_level) +{ + struct rockchip_hdmi_qp *hdmi = (struct rockchip_hdmi_qp *)data; + union phy_configure_opts phy_cfg = {}; + int ret; + + if (!hdmi->link_cfg.frl_enabled) + return -EINVAL; + + phy_cfg.hdmi.frl.ffe_level = ffe_level; + phy_cfg.hdmi.frl.set_ffe_level = true; + + ret = phy_configure(hdmi->phy, &phy_cfg); + if (ret) + dev_err(hdmi->dev, "Failed to set PHY FFE level: %d\n", ret); + + return ret; +} + static const struct dw_hdmi_qp_phy_ops rk3588_hdmi_phy_ops = { .init = dw_hdmi_qp_rk3588_phy_init, .disable = dw_hdmi_qp_rk3588_phy_disable, .read_hpd = dw_hdmi_qp_rk3588_read_hpd, .setup_hpd = dw_hdmi_qp_rk3588_setup_hpd, + .get_link_cfg = dw_hdmi_qp_rk3588_get_link_cfg, + .set_frl_rate = dw_hdmi_qp_rk3588_set_frl_rate, + .set_ffe_level = dw_hdmi_qp_rk3588_set_ffe_level, }; static enum drm_connector_status @@ -244,6 +373,9 @@ static const struct dw_hdmi_qp_phy_ops rk3576_hdmi_phy_ops = { .disable = dw_hdmi_qp_rk3588_phy_disable, .read_hpd = dw_hdmi_qp_rk3576_read_hpd, .setup_hpd = dw_hdmi_qp_rk3576_setup_hpd, + .get_link_cfg = dw_hdmi_qp_rk3588_get_link_cfg, + .set_frl_rate = dw_hdmi_qp_rk3588_set_frl_rate, + .set_ffe_level = dw_hdmi_qp_rk3588_set_ffe_level, }; static void dw_hdmi_qp_rk3588_hpd_work(struct work_struct *work) @@ -251,11 +383,10 @@ static void dw_hdmi_qp_rk3588_hpd_work(struct work_struct *work) struct rockchip_hdmi_qp *hdmi = container_of(work, struct rockchip_hdmi_qp, hpd_work.work); - struct drm_device *drm = hdmi->encoder.encoder.dev; bool changed; - if (drm) { - changed = drm_helper_hpd_irq_event(drm); + if (hdmi->connector) { + changed = drm_connector_helper_hpd_irq_event(hdmi->connector); if (changed) dev_dbg(hdmi->dev, "connector status changed\n"); } @@ -384,6 +515,9 @@ static void dw_hdmi_qp_rk3576_enc_init(struct rockchip_hdmi_qp *hdmi, { u32 val; + val = FIELD_PREP_WM16(RK3576_HDMI_FRL_MOD, hdmi->link_cfg.frl_enabled); + regmap_write(hdmi->vo_regmap, RK3576_VO0_GRF_SOC_CON1, val); + if (state->output_bpc == 10) val = FIELD_PREP_WM16(RK3576_COLOR_DEPTH_MASK, RK3576_10BPC); else @@ -397,6 +531,11 @@ static void dw_hdmi_qp_rk3588_enc_init(struct rockchip_hdmi_qp *hdmi, { u32 val; + val = FIELD_PREP_WM16(RK3588_HDMI21_MASK, hdmi->link_cfg.frl_enabled); + regmap_write(hdmi->vo_regmap, + hdmi->port_id ? RK3588_GRF_VO1_CON7 : RK3588_GRF_VO1_CON4, + val); + if (state->output_bpc == 10) val = FIELD_PREP_WM16(RK3588_COLOR_DEPTH_MASK, RK3588_10BPC); else @@ -421,11 +560,24 @@ static const struct rockchip_hdmi_qp_ctrl_ops rk3588_hdmi_ctrl_ops = { .hardirq_callback = dw_hdmi_qp_rk3588_hardirq, }; +enum rockchip_hdmi_qp_ffe_cfg { + FFE_LEVEL_AUTO = 0, + FFE_LEVEL_0, + FFE_LEVEL_1, + FFE_LEVEL_2, + FFE_LEVEL_3, +}; + struct rockchip_hdmi_qp_cfg { unsigned int num_ports; unsigned int port_ids[MAX_HDMI_PORT_NUM]; const struct rockchip_hdmi_qp_ctrl_ops *ctrl_ops; const struct dw_hdmi_qp_phy_ops *phy_ops; + u8 max_frl_rate_per_lane; + u8 max_frl_lanes; + u8 min_frl_rate_per_lane; + u8 min_frl_lanes; + enum rockchip_hdmi_qp_ffe_cfg max_ffe; }; static const struct rockchip_hdmi_qp_cfg rk3576_hdmi_cfg = { @@ -445,6 +597,9 @@ static const struct rockchip_hdmi_qp_cfg rk3588_hdmi_cfg = { }, .ctrl_ops = &rk3588_hdmi_ctrl_ops, .phy_ops = &rk3588_hdmi_phy_ops, + /* FIXME: Intermittent screen flicker if rate exceeds 40 Gbps */ + .max_frl_rate_per_lane = 10, + .max_frl_lanes = 4, }; static const struct of_device_id dw_hdmi_qp_rockchip_dt_ids[] = { @@ -459,14 +614,22 @@ static const struct of_device_id dw_hdmi_qp_rockchip_dt_ids[] = { }; MODULE_DEVICE_TABLE(of, dw_hdmi_qp_rockchip_dt_ids); +static u8 dw_hdmi_qp_rockchip_ffe_cfg_to_level(enum rockchip_hdmi_qp_ffe_cfg ffe) +{ + if (ffe == FFE_LEVEL_AUTO) + ffe = FFE_LEVEL_3; + + return ffe - 1; +} + static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); struct dw_hdmi_qp_plat_data plat_data = {}; const struct rockchip_hdmi_qp_cfg *cfg; + struct dw_hdmi_qp_link_cfg *lcfg; struct drm_device *drm = data; - struct drm_connector *connector; struct drm_encoder *encoder; struct rockchip_hdmi_qp *hdmi; struct resource *res; @@ -474,10 +637,10 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master, struct clk *ref_clk; int ret, irq, i; - if (!pdev->dev.of_node) + if (!dev->of_node) return -ENODEV; - hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL); + hdmi = drmm_kzalloc(drm, sizeof(*hdmi), GFP_KERNEL); if (!hdmi) return -ENOMEM; @@ -494,7 +657,7 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master, return dev_err_probe(dev, -ENODEV, "Missing platform ctrl ops\n"); hdmi->ctrl_ops = cfg->ctrl_ops; - hdmi->dev = &pdev->dev; + hdmi->dev = dev; hdmi->port_id = -ENODEV; /* Identify port ID by matching base IO address */ @@ -505,7 +668,7 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master, } } if (hdmi->port_id < 0) - return dev_err_probe(hdmi->dev, hdmi->port_id, + return dev_err_probe(dev, hdmi->port_id, "Failed to match HDMI port ID\n"); plat_data.phy_ops = cfg->phy_ops; @@ -529,37 +692,56 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master, hdmi->regmap = syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,grf"); if (IS_ERR(hdmi->regmap)) - return dev_err_probe(hdmi->dev, PTR_ERR(hdmi->regmap), + return dev_err_probe(dev, PTR_ERR(hdmi->regmap), "Unable to get rockchip,grf\n"); hdmi->vo_regmap = syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,vo-grf"); if (IS_ERR(hdmi->vo_regmap)) - return dev_err_probe(hdmi->dev, PTR_ERR(hdmi->vo_regmap), + return dev_err_probe(dev, PTR_ERR(hdmi->vo_regmap), "Unable to get rockchip,vo-grf\n"); - ret = devm_clk_bulk_get_all_enabled(hdmi->dev, &clks); + ret = devm_clk_bulk_get_all_enabled(dev, &clks); if (ret < 0) - return dev_err_probe(hdmi->dev, ret, "Failed to get clocks\n"); + return dev_err_probe(dev, ret, "Failed to get clocks\n"); - ref_clk = clk_get(hdmi->dev, "ref"); + ref_clk = clk_get(dev, "ref"); if (IS_ERR(ref_clk)) - return dev_err_probe(hdmi->dev, PTR_ERR(ref_clk), + return dev_err_probe(dev, PTR_ERR(ref_clk), "Failed to get ref clock\n"); plat_data.ref_clk_rate = clk_get_rate(ref_clk); clk_put(ref_clk); - hdmi->frl_enable_gpio = devm_gpiod_get_optional(hdmi->dev, "frl-enable", + hdmi->frl_enable_gpio = devm_gpiod_get_optional(dev, "frl-enable", GPIOD_OUT_LOW); if (IS_ERR(hdmi->frl_enable_gpio)) - return dev_err_probe(hdmi->dev, PTR_ERR(hdmi->frl_enable_gpio), + return dev_err_probe(dev, PTR_ERR(hdmi->frl_enable_gpio), "Failed to request FRL enable GPIO\n"); hdmi->phy = devm_of_phy_get_by_index(dev, dev->of_node, 0); if (IS_ERR(hdmi->phy)) - return dev_err_probe(hdmi->dev, PTR_ERR(hdmi->phy), - "Failed to get phy\n"); + return dev_err_probe(dev, PTR_ERR(hdmi->phy), "Failed to get phy\n"); + + lcfg = &hdmi->link_cfg; + lcfg->max_frl_rate_per_lane = cfg->max_frl_rate_per_lane ?: HDMI21_MAX_FRL_LANE_RATE; + lcfg->max_frl_lanes = cfg->max_frl_lanes ?: HDMI21_MAX_FRL_LANE_NUM; + lcfg->min_frl_rate_per_lane = cfg->min_frl_rate_per_lane ?: HDMI21_MIN_FRL_LANE_RATE; + lcfg->min_frl_lanes = cfg->min_frl_lanes ?: HDMI21_MIN_FRL_LANE_NUM; + + if (lcfg->max_frl_rate_per_lane < lcfg->min_frl_rate_per_lane || + lcfg->max_frl_lanes < lcfg->min_frl_lanes || + lcfg->max_frl_rate_per_lane > HDMI21_MAX_FRL_LANE_RATE || + lcfg->max_frl_rate_per_lane < HDMI21_MIN_FRL_LANE_RATE || + lcfg->max_frl_lanes > HDMI21_MAX_FRL_LANE_NUM || + lcfg->max_frl_lanes < HDMI21_MIN_FRL_LANE_NUM || + lcfg->min_frl_rate_per_lane > HDMI21_MAX_FRL_LANE_RATE || + lcfg->min_frl_rate_per_lane < HDMI21_MIN_FRL_LANE_RATE || + lcfg->min_frl_lanes > HDMI21_MAX_FRL_LANE_NUM || + lcfg->min_frl_lanes < HDMI21_MIN_FRL_LANE_NUM) + return dev_err_probe(hdmi->dev, -EINVAL, "Invalid FRL config\n"); + + lcfg->max_ffe_level = dw_hdmi_qp_rockchip_ffe_cfg_to_level(cfg->max_ffe); cfg->ctrl_ops->io_init(hdmi); @@ -577,32 +759,40 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master, if (irq < 0) return irq; - ret = devm_request_threaded_irq(hdmi->dev, irq, - cfg->ctrl_ops->hardirq_callback, - cfg->ctrl_ops->irq_callback, - IRQF_SHARED, "dw-hdmi-qp-hpd", - hdmi); - if (ret) - return ret; - drm_encoder_helper_add(encoder, &dw_hdmi_qp_rockchip_encoder_helper_funcs); - drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS); + ret = drmm_encoder_init(drm, encoder, NULL, DRM_MODE_ENCODER_TMDS, NULL); + if (ret) + return dev_err_probe(dev, ret, "Failed to init encoder\n"); platform_set_drvdata(pdev, hdmi); hdmi->hdmi = dw_hdmi_qp_bind(pdev, encoder, &plat_data); - if (IS_ERR(hdmi->hdmi)) { - drm_encoder_cleanup(encoder); - return dev_err_probe(hdmi->dev, PTR_ERR(hdmi->hdmi), - "Failed to bind dw-hdmi-qp"); - } + if (IS_ERR(hdmi->hdmi)) + return dev_err_probe(dev, PTR_ERR(hdmi->hdmi), + "Failed to bind dw-hdmi-qp\n"); - connector = drm_bridge_connector_init(drm, encoder); - if (IS_ERR(connector)) - return dev_err_probe(hdmi->dev, PTR_ERR(connector), + hdmi->connector = drm_bridge_connector_init(drm, encoder); + if (IS_ERR(hdmi->connector)) + return dev_err_probe(dev, PTR_ERR(hdmi->connector), "Failed to init bridge connector\n"); - return drm_connector_attach_encoder(connector, encoder); + ret = drm_mode_create_tv_properties_legacy(drm, 0, NULL); + if (ret) + return dev_err_probe(dev, ret, + "Failed to create TV connector properties\n"); + + drm_object_attach_property(&hdmi->connector->base, + drm->mode_config.tv_overscan_property, 0); + + ret = drm_connector_attach_encoder(hdmi->connector, encoder); + if (ret) + return dev_err_probe(dev, ret, "Failed to attach connector\n"); + + return devm_request_threaded_irq(dev, irq, + cfg->ctrl_ops->hardirq_callback, + cfg->ctrl_ops->irq_callback, + IRQF_SHARED, "dw-hdmi-qp-hpd", + hdmi); } static void dw_hdmi_qp_rockchip_unbind(struct device *dev, @@ -613,7 +803,7 @@ static void dw_hdmi_qp_rockchip_unbind(struct device *dev, cancel_delayed_work_sync(&hdmi->hpd_work); - drm_encoder_cleanup(&hdmi->encoder.encoder); + hdmi->connector = NULL; } static const struct component_ops dw_hdmi_qp_rockchip_ops = { @@ -648,8 +838,8 @@ static int __maybe_unused dw_hdmi_qp_rockchip_resume(struct device *dev) dw_hdmi_qp_resume(dev, hdmi->hdmi); - if (hdmi->encoder.encoder.dev) - drm_helper_hpd_irq_event(hdmi->encoder.encoder.dev); + if (hdmi->connector) + drm_connector_helper_hpd_irq_event(hdmi->connector); return 0; } diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h index 2e86ad00979c41..926608da21a7bc 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h @@ -10,6 +10,7 @@ #define _ROCKCHIP_DRM_DRV_H #include +#include #include #include @@ -53,6 +54,8 @@ struct rockchip_crtc_state { u32 bus_format; u32 bus_flags; int color_space; + bool frl_enabled; + struct drm_connector_tv_margins tv_margins; }; #define to_rockchip_crtc_state(s) \ container_of(s, struct rockchip_crtc_state, base) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c index 843c7ef979b218..62c0b940ef3112 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -103,6 +104,8 @@ enum vop2_afbc_format { }; #define VOP2_MAX_DCLK_RATE 600000000UL +#define VOP2_ACLK_RATE_DEFAULT 500000000UL +#define VOP2_ACLK_RATE_FRL 750000000UL /* * bus-format types. @@ -957,11 +960,24 @@ static void vop2_crtc_atomic_disable(struct drm_crtc *crtc, vop2_crtc_disable_irq(vp, VP_INT_DSP_HOLD_VALID); - if (vp->dclk_src) + if (vp->dclk_src) { + dev_info(vop2->dev, "reseting dclk parent\n"); clk_set_parent(vp->dclk, vp->dclk_src); + } clk_disable_unprepare(vp->dclk); + if (vop2->version == VOP_VERSION_RK3588) { + struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(old_crtc_state); + + if (vcstate->frl_enabled) { + vop2->frl_vp_count--; + + if (!vop2->frl_vp_count) + clk_set_rate(vop2->aclk, VOP2_ACLK_RATE_DEFAULT); + } + } + vop2->enable_count--; if (!vop2->enable_count) @@ -1441,6 +1457,9 @@ static enum drm_mode_status vop2_crtc_mode_valid(struct drm_crtc *crtc, if (mode->hdisplay > vp->data->max_output.width) return MODE_BAD_HVALUE; + if (mode->clock > vp->data->max_pixel_clock_rate / 1000) + return MODE_CLOCK_HIGH; + return MODE_OK; } @@ -1558,30 +1577,35 @@ static void vop2_post_config(struct drm_crtc *crtc) { struct vop2_video_port *vp = to_vop2_video_port(crtc); struct vop2 *vop2 = vp->vop2; + struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc->state); struct drm_display_mode *mode = &crtc->state->adjusted_mode; + const struct drm_connector_tv_margins *m = &vcstate->tv_margins; u64 bgcolor = crtc->state->background_color; u16 vtotal = mode->crtc_vtotal; u16 hdisplay = mode->crtc_hdisplay; u16 hact_st = mode->crtc_htotal - mode->crtc_hsync_start; u16 vdisplay = mode->crtc_vdisplay; u16 vact_st = mode->crtc_vtotal - mode->crtc_vsync_start; - u32 left_margin = 100, right_margin = 100; - u32 top_margin = 100, bottom_margin = 100; - u16 hsize = hdisplay * (left_margin + right_margin) / 200; - u16 vsize = vdisplay * (top_margin + bottom_margin) / 200; + u16 hsize = hdisplay; + u16 vsize = vdisplay; u16 hact_end, vact_end; u32 val; vop2->ops->setup_bg_dly(vp); + if (m->left + m->right < hdisplay) + hsize = hdisplay - m->left - m->right; + if (m->top + m->bottom < vdisplay) + vsize = vdisplay - m->top - m->bottom; + vsize = rounddown(vsize, 2); hsize = rounddown(hsize, 2); - hact_st += hdisplay * (100 - left_margin) / 200; + hact_st += m->left; hact_end = hact_st + hsize; val = hact_st << 16; val |= hact_end; vop2_vp_write(vp, RK3568_VP_POST_DSP_HACT_INFO, val); - vact_st += vdisplay * (100 - top_margin) / 200; + vact_st += m->top; vact_end = vact_st + vsize; val = vact_st << 16; val |= vact_end; @@ -1621,6 +1645,26 @@ static int us_to_vertical_line(struct drm_display_mode *mode, int us) return us * mode->clock / mode->htotal / 1000; } +static int vop2_clk_reset(struct vop2_video_port *vp) +{ + struct reset_control *rstc = vp->dclk_rst; + struct vop2 *vop2 = vp->vop2; + int ret; + + if (!rstc) + return 0; + + ret = reset_control_assert(rstc); + if (ret < 0) + drm_warn(vop2->drm, "failed to assert reset\n"); + udelay(10); + ret = reset_control_deassert(rstc); + if (ret < 0) + drm_warn(vop2->drm, "failed to deassert reset\n"); + + return ret; +} + static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *state) { @@ -1668,6 +1712,13 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, vop2->enable_count++; + if (vop2->version == VOP_VERSION_RK3588 && vcstate->frl_enabled) { + if (!vop2->frl_vp_count) + clk_set_rate(vop2->aclk, VOP2_ACLK_RATE_FRL); + + vop2->frl_vp_count++; + } + vcstate->yuv_overlay = is_yuv_output(vcstate->bus_format); vop2_crtc_enable_irq(vp, VP_INT_POST_BUF_EMPTY); @@ -1768,6 +1819,7 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, if (!vop2->pll_hdmiphy0) break; + dev_info(vop2->dev, "reparenting HDMI0 dclk\n"); if (!vp->dclk_src) vp->dclk_src = clk_get_parent(vp->dclk); @@ -1783,6 +1835,7 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, if (!vop2->pll_hdmiphy1) break; + dev_info(vop2->dev, "reparenting HDMI1 dclk\n"); if (!vp->dclk_src) vp->dclk_src = clk_get_parent(vp->dclk); @@ -1797,6 +1850,7 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, } } + dev_info(vop2->dev, "setting dclk rate=%lu (crt=%lu)\n", clock, clk_get_rate(vp->dclk)); clk_set_rate(vp->dclk, clock); vop2_post_config(crtc); @@ -1805,6 +1859,8 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, vop2_vp_write(vp, RK3568_VP_DSP_CTRL, dsp_ctrl); + vop2_clk_reset(vp); + vop2_crtc_atomic_try_set_gamma(vop2, vp, crtc, crtc_state); drm_crtc_vblank_on(crtc); @@ -2383,6 +2439,11 @@ static int vop2_create_crtcs(struct vop2 *vop2) vp->id = vp_data->id; vp->data = vp_data; + vp->dclk_rst = devm_reset_control_get_optional(vop2->dev, dclk_name); + if (IS_ERR(vp->dclk_rst)) + return dev_err_probe(drm->dev, PTR_ERR(vp->dclk_rst), + "failed to get %s reset\n", dclk_name); + snprintf(dclk_name, sizeof(dclk_name), "dclk_vp%d", vp->id); vp->dclk = devm_clk_get(vop2->dev, dclk_name); if (IS_ERR(vp->dclk)) @@ -2664,6 +2725,21 @@ static int vop2_bind(struct device *dev, struct device *master, void *data) dev_set_drvdata(dev, vop2); + vop2->resets[RST_ACLK].id = "aclk"; + vop2->resets[RST_HCLK].id = "hclk"; + ret = devm_reset_control_bulk_get_optional_exclusive(vop2->dev, + RST_VOP2_MAX, vop2->resets); + if (ret) + return dev_err_probe(drm->dev, ret, "failed to get resets\n"); + + ret = reset_control_bulk_assert(RST_VOP2_MAX, vop2->resets); + if (ret < 0) + drm_warn(vop2->drm, "failed to assert resets\n"); + udelay(10); + ret = reset_control_bulk_deassert(RST_VOP2_MAX, vop2->resets); + if (ret < 0) + drm_warn(vop2->drm, "failed to deassert resets\n"); + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vop"); if (!res) return dev_err_probe(drm->dev, -EINVAL, diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h index 37722652844a92..310e6547f3bc6e 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h @@ -8,6 +8,7 @@ #define _ROCKCHIP_DRM_VOP2_H #include +#include #include #include #include "rockchip_drm_drv.h" @@ -165,6 +166,12 @@ enum vop2_win_regs { VOP2_WIN_MAX_REG, }; +enum { + RST_ACLK, + RST_HCLK, + RST_VOP2_MAX +}; + struct vop2_regs_dump { const char *name; u32 base; @@ -225,6 +232,7 @@ struct vop2_video_port_data { u16 gamma_lut_len; u16 cubic_lut_len; struct vop_rect max_output; + u32 max_pixel_clock_rate; const u8 pre_scan_max_dly[4]; unsigned int offset; /** @@ -238,6 +246,7 @@ struct vop2_video_port { struct vop2 *vop2; struct clk *dclk; struct clk *dclk_src; + struct reset_control *dclk_rst; unsigned int id; const struct vop2_video_port_data *data; @@ -324,11 +333,13 @@ struct vop2 { * we need a ref counter here. */ unsigned int enable_count; + unsigned int frl_vp_count; struct clk *hclk; struct clk *aclk; struct clk *pclk; struct clk *pll_hdmiphy0; struct clk *pll_hdmiphy1; + struct reset_control_bulk_data resets[RST_VOP2_MAX]; /* optional internal rgb encoder */ struct rockchip_rgb *rgb; diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c index 02a788a4dfddaa..853e13977b57b5 100644 --- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c +++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c @@ -559,18 +559,21 @@ static const struct vop2_video_port_data rk3568_vop_video_ports[] = { .gamma_lut_len = 1024, .cubic_lut_len = 9 * 9 * 9, .max_output = { 4096, 2304 }, + .max_pixel_clock_rate = 600000000U, .pre_scan_max_dly = { 69, 53, 53, 42 }, .offset = 0xc00, }, { .id = 1, .gamma_lut_len = 1024, .max_output = { 2048, 1536 }, + .max_pixel_clock_rate = 200000000U, .pre_scan_max_dly = { 40, 40, 40, 40 }, .offset = 0xd00, }, { .id = 2, .gamma_lut_len = 1024, .max_output = { 1920, 1080 }, + .max_pixel_clock_rate = 150000000U, .pre_scan_max_dly = { 40, 40, 40, 40 }, .offset = 0xe00, }, @@ -775,6 +778,7 @@ static const struct vop2_video_port_data rk3576_vop_video_ports[] = { .gamma_lut_len = 1024, .cubic_lut_len = 9 * 9 * 9, /* 9x9x9 */ .max_output = { 4096, 2304 }, + .max_pixel_clock_rate = 600000000U * 2, /* 2 for pixel_rate */ /* win layer_mix hdr */ .pre_scan_max_dly = { 10, 8, 2, 0 }, .offset = 0xc00, @@ -785,6 +789,7 @@ static const struct vop2_video_port_data rk3576_vop_video_ports[] = { .gamma_lut_len = 1024, .cubic_lut_len = 729, /* 9x9x9 */ .max_output = { 2560, 1600 }, + .max_pixel_clock_rate = 300000000U, /* win layer_mix hdr */ .pre_scan_max_dly = { 10, 6, 0, 0 }, .offset = 0xd00, @@ -793,6 +798,7 @@ static const struct vop2_video_port_data rk3576_vop_video_ports[] = { .id = 2, .gamma_lut_len = 1024, .max_output = { 1920, 1080 }, + .max_pixel_clock_rate = 150000000U, /* win layer_mix hdr */ .pre_scan_max_dly = { 10, 6, 0, 0 }, .offset = 0xe00, @@ -1061,6 +1067,7 @@ static const struct vop2_video_port_data rk3588_vop_video_ports[] = { .gamma_lut_len = 1024, .cubic_lut_len = 9 * 9 * 9, /* 9x9x9 */ .max_output = { 4096, 2304 }, + .max_pixel_clock_rate = 600000000U * 4, /* hdr2sdr sdr2hdr hdr2hdr sdr2sdr */ .pre_scan_max_dly = { 76, 65, 65, 54 }, .offset = 0xc00, @@ -1070,6 +1077,7 @@ static const struct vop2_video_port_data rk3588_vop_video_ports[] = { .gamma_lut_len = 1024, .cubic_lut_len = 729, /* 9x9x9 */ .max_output = { 4096, 2304 }, + .max_pixel_clock_rate = 600000000U * 4, .pre_scan_max_dly = { 76, 65, 65, 54 }, .offset = 0xd00, }, { @@ -1078,12 +1086,14 @@ static const struct vop2_video_port_data rk3588_vop_video_ports[] = { .gamma_lut_len = 1024, .cubic_lut_len = 17 * 17 * 17, /* 17x17x17 */ .max_output = { 4096, 2304 }, + .max_pixel_clock_rate = 600000000U * 4, .pre_scan_max_dly = { 52, 52, 52, 52 }, .offset = 0xe00, }, { .id = 3, .gamma_lut_len = 1024, .max_output = { 2048, 1536 }, + .max_pixel_clock_rate = 150000000U, .pre_scan_max_dly = { 52, 52, 52, 52 }, .offset = 0xf00, }, diff --git a/drivers/gpu/drm/tiny/Kconfig b/drivers/gpu/drm/tiny/Kconfig index f0e72d4b6a4709..4835e06c0724d2 100644 --- a/drivers/gpu/drm/tiny/Kconfig +++ b/drivers/gpu/drm/tiny/Kconfig @@ -98,6 +98,20 @@ config DRM_PIXPAPER If M is selected, the module will be built as pixpaper.ko. +config TINYDRM_FLIPPER_ONE_DISPLAY + tristate "DRM support for Flipper One display" + depends on DRM && SPI + select DRM_CLIENT_SELECTION + select DRM_GEM_DMA_HELPER + select DRM_KMS_HELPER + default y + help + DRM Driver for the Flipper One display, which uses an onboard MCU + to drive a 256x144 grayscale LCD based on a framebuffer written + over SPI (full frame at a time, 6 bpp padded to 8 bpp at 258x144). + + If M is selected the module will be called flipper_one_display. + config TINYDRM_HX8357D tristate "DRM support for HX8357D display panels" depends on DRM && SPI diff --git a/drivers/gpu/drm/tiny/Makefile b/drivers/gpu/drm/tiny/Makefile index 48d30bf6152f97..5df261a871f09d 100644 --- a/drivers/gpu/drm/tiny/Makefile +++ b/drivers/gpu/drm/tiny/Makefile @@ -1,17 +1,18 @@ # SPDX-License-Identifier: GPL-2.0-only -obj-$(CONFIG_DRM_APPLETBDRM) += appletbdrm.o -obj-$(CONFIG_DRM_ARCPGU) += arcpgu.o -obj-$(CONFIG_DRM_BOCHS) += bochs.o -obj-$(CONFIG_DRM_CIRRUS_QEMU) += cirrus-qemu.o -obj-$(CONFIG_DRM_GM12U320) += gm12u320.o -obj-$(CONFIG_DRM_PANEL_MIPI_DBI) += panel-mipi-dbi.o -obj-$(CONFIG_DRM_PIXPAPER) += pixpaper.o -obj-$(CONFIG_TINYDRM_HX8357D) += hx8357d.o -obj-$(CONFIG_TINYDRM_ILI9163) += ili9163.o -obj-$(CONFIG_TINYDRM_ILI9225) += ili9225.o -obj-$(CONFIG_TINYDRM_ILI9341) += ili9341.o -obj-$(CONFIG_TINYDRM_ILI9486) += ili9486.o -obj-$(CONFIG_TINYDRM_MI0283QT) += mi0283qt.o -obj-$(CONFIG_TINYDRM_REPAPER) += repaper.o -obj-$(CONFIG_TINYDRM_SHARP_MEMORY) += sharp-memory.o +obj-$(CONFIG_DRM_APPLETBDRM) += appletbdrm.o +obj-$(CONFIG_DRM_ARCPGU) += arcpgu.o +obj-$(CONFIG_DRM_BOCHS) += bochs.o +obj-$(CONFIG_DRM_CIRRUS_QEMU) += cirrus-qemu.o +obj-$(CONFIG_DRM_GM12U320) += gm12u320.o +obj-$(CONFIG_DRM_PANEL_MIPI_DBI) += panel-mipi-dbi.o +obj-$(CONFIG_DRM_PIXPAPER) += pixpaper.o +obj-$(CONFIG_TINYDRM_FLIPPER_ONE_DISPLAY) += flipper-one-display.o +obj-$(CONFIG_TINYDRM_HX8357D) += hx8357d.o +obj-$(CONFIG_TINYDRM_ILI9163) += ili9163.o +obj-$(CONFIG_TINYDRM_ILI9225) += ili9225.o +obj-$(CONFIG_TINYDRM_ILI9341) += ili9341.o +obj-$(CONFIG_TINYDRM_ILI9486) += ili9486.o +obj-$(CONFIG_TINYDRM_MI0283QT) += mi0283qt.o +obj-$(CONFIG_TINYDRM_REPAPER) += repaper.o +obj-$(CONFIG_TINYDRM_SHARP_MEMORY) += sharp-memory.o diff --git a/drivers/gpu/drm/tiny/flipper-one-display.c b/drivers/gpu/drm/tiny/flipper-one-display.c new file mode 100644 index 00000000000000..adcd9fa8f9138e --- /dev/null +++ b/drivers/gpu/drm/tiny/flipper-one-display.c @@ -0,0 +1,366 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct fo_device { + struct drm_device drm; + struct spi_device *spi; + + const struct drm_display_mode *mode; + + struct drm_crtc crtc; + struct drm_plane plane; + struct drm_encoder encoder; + struct drm_connector connector; + + struct gpio_desc *active_gpio; + + u32 pitch; + u32 tx_buffer_size; + u8 *tx_buffer; +}; + +static inline struct fo_device *drm_to_fo_device(struct drm_device *drm) +{ + return container_of(drm, struct fo_device, drm); +} + +DEFINE_DRM_GEM_DMA_FOPS(fo_fops); + +static const struct drm_driver fo_drm_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, + .fops = &fo_fops, + DRM_GEM_DMA_DRIVER_OPS_VMAP, + DRM_FBDEV_DMA_DRIVER_OPS, + .name = "flipper_one_display", + .desc = "Flipper One LCD screen", + .major = 1, + .minor = 0, +}; + +static void fo_set_tx_buffer_data(struct fo_device *fo, + struct drm_plane_state *plane_state) +{ + struct drm_shadow_plane_state *s_plane_state = to_drm_shadow_plane_state(plane_state); + struct drm_framebuffer *fb = plane_state->fb; + struct drm_rect clip; + struct iosys_map *src = s_plane_state->data; + struct iosys_map dst; + + if (drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE)) + return; + + clip.x1 = 0; + clip.x2 = fb->width; + clip.y1 = 0; + clip.y2 = fb->height; + + iosys_map_set_vaddr(&dst, fo->tx_buffer); + drm_fb_xrgb8888_to_gray8(&dst, &fo->pitch, src, fb, &clip, &s_plane_state->fmtcnv_state); + drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE); +} + +static int fo_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_state *state) +{ + struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane); + struct fo_device *fo; + struct drm_crtc_state *crtc_state; + + fo = container_of(plane, struct fo_device, plane); + crtc_state = drm_atomic_get_new_crtc_state(state, &fo->crtc); + + return drm_atomic_helper_check_plane_state(plane_state, crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + false, false); +} + +static void fo_plane_atomic_update(struct drm_plane *plane, struct drm_atomic_state *state) +{ + struct fo_device *fo = container_of(plane, struct fo_device, plane); + struct drm_plane_state *plane_state = plane->state; + + if (!fo->crtc.state->active) + return; + + /* Populate the transmit buffer with frame data */ + fo_set_tx_buffer_data(fo, plane_state); + + spi_write(fo->spi, fo->tx_buffer, fo->tx_buffer_size); +} + +static const struct drm_plane_helper_funcs fo_plane_helper_funcs = { + .prepare_fb = drm_gem_plane_helper_prepare_fb, + .atomic_check = fo_plane_atomic_check, + .atomic_update = fo_plane_atomic_update, + DRM_GEM_SHADOW_PLANE_HELPER_FUNCS, +}; + +static bool fo_format_mod_supported(struct drm_plane *plane, u32 format, u64 modifier) +{ + return modifier == DRM_FORMAT_MOD_LINEAR; +} + +static const struct drm_plane_funcs fo_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = drm_plane_cleanup, + DRM_GEM_SHADOW_PLANE_FUNCS, + .format_mod_supported = fo_format_mod_supported, +}; + +static enum drm_mode_status fo_crtc_mode_valid(struct drm_crtc *crtc, + const struct drm_display_mode *mode) +{ + struct fo_device *fo = drm_to_fo_device(crtc->dev); + + return drm_crtc_helper_mode_valid_fixed(crtc, mode, fo->mode); +} + +static int fo_crtc_check(struct drm_crtc *crtc, struct drm_atomic_state *state) +{ + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + int ret; + + if (!crtc_state->enable) + goto out; + + ret = drm_atomic_helper_check_crtc_primary_plane(crtc_state); + if (ret) + return ret; + +out: + return drm_atomic_add_affected_planes(state, crtc); +} + +static void fo_begin_frame(struct drm_crtc *crtc, struct drm_atomic_state *state) +{ + struct fo_device *fo = drm_to_fo_device(crtc->dev); + + if (fo->active_gpio) + gpiod_set_value(fo->active_gpio, 1); +} + +static void fo_end_frame(struct drm_crtc *crtc, struct drm_atomic_state *state) +{ + struct fo_device *fo = drm_to_fo_device(crtc->dev); + + if (fo->active_gpio) + gpiod_set_value(fo->active_gpio, 0); +} + +static const struct drm_crtc_helper_funcs fo_crtc_helper_funcs = { + .mode_valid = fo_crtc_mode_valid, + .atomic_check = fo_crtc_check, + .atomic_begin = fo_begin_frame, + .atomic_flush = fo_end_frame, +}; + +static const struct drm_crtc_funcs fo_crtc_funcs = { + .reset = drm_atomic_helper_crtc_reset, + .destroy = drm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, +}; + +static const struct drm_encoder_funcs fo_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static int fo_connector_get_modes(struct drm_connector *connector) +{ + struct fo_device *fo = drm_to_fo_device(connector->dev); + + return drm_connector_helper_get_modes_fixed(connector, fo->mode); +} + +static const struct drm_connector_helper_funcs fo_connector_hfuncs = { + .get_modes = fo_connector_get_modes, +}; + +static const struct drm_connector_funcs fo_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static const struct drm_mode_config_funcs fo_mode_config_funcs = { + .fb_create = drm_gem_fb_create, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static const struct drm_display_mode fo_display_mode = { + DRM_MODE_INIT(60, 256, 144, 53, 30), +}; + +static const struct spi_device_id fo_ids[] = { + {"flipper_one_display", (kernel_ulong_t)&fo_display_mode}, + {}, +}; +MODULE_DEVICE_TABLE(spi, fo_ids); + +static const struct of_device_id fo_of_match[] = { + {.compatible = "flipper,one-display", &fo_display_mode}, + {}, +}; +MODULE_DEVICE_TABLE(of, fo_of_match); + +static const u32 fo_formats[] = { + DRM_FORMAT_XRGB8888, +}; + +static int fo_pipe_init(struct drm_device *dev, struct fo_device *fo, + const u32 *formats, unsigned int format_count, + const u64 *format_modifiers) +{ + int ret; + struct drm_encoder *encoder = &fo->encoder; + struct drm_plane *plane = &fo->plane; + struct drm_crtc *crtc = &fo->crtc; + struct drm_connector *connector = &fo->connector; + + drm_plane_helper_add(plane, &fo_plane_helper_funcs); + ret = drm_universal_plane_init(dev, plane, 0, &fo_plane_funcs, + formats, format_count, + format_modifiers, + DRM_PLANE_TYPE_PRIMARY, NULL); + if (ret) + return ret; + + drm_crtc_helper_add(crtc, &fo_crtc_helper_funcs); + ret = drm_crtc_init_with_planes(dev, crtc, plane, NULL, + &fo_crtc_funcs, NULL); + if (ret) + return ret; + + encoder->possible_crtcs = drm_crtc_mask(crtc); + ret = drm_encoder_init(dev, encoder, &fo_encoder_funcs, + DRM_MODE_ENCODER_NONE, NULL); + if (ret) + return ret; + + ret = drm_connector_init(&fo->drm, &fo->connector, + &fo_connector_funcs, + DRM_MODE_CONNECTOR_SPI); + if (ret) + return ret; + + drm_connector_helper_add(&fo->connector, &fo_connector_hfuncs); + + return drm_connector_attach_encoder(connector, encoder); +} + +static int fo_probe(struct spi_device *spi) +{ + int ret; + struct device *dev; + struct fo_device *fo; + struct drm_device *drm; + + dev = &spi->dev; + spi->mode |= SPI_MODE_3; + + ret = spi_setup(spi); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to setup spi device\n"); + + if (!dev->coherent_dma_mask) { + ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) + return dev_err_probe(dev, ret, "Failed to set dma mask\n"); + } + + fo = devm_drm_dev_alloc(dev, &fo_drm_driver, struct fo_device, drm); + if (!fo) + return -ENOMEM; + + spi_set_drvdata(spi, fo); + + fo->spi = spi; + drm = &fo->drm; + ret = drmm_mode_config_init(drm); + if (ret) + return dev_err_probe(dev, ret, "Failed to initialize drm config\n"); + + fo->active_gpio = devm_gpiod_get_optional(dev, "active", GPIOD_OUT_LOW); + if (!fo->active_gpio) + dev_warn(dev, "Active gpio not defined\n"); + + drm->mode_config.funcs = &fo_mode_config_funcs; + fo->mode = spi_get_device_match_data(spi); + /* The controller expects 3-byte aligned input lines */ + fo->pitch = roundup(fo->mode->hdisplay, 3); + fo->tx_buffer_size = (fo->pitch) * (fo->mode->vdisplay); + + fo->tx_buffer = devm_kzalloc(dev, fo->tx_buffer_size, GFP_KERNEL); + if (!fo->tx_buffer) + return -ENOMEM; + + drm->mode_config.min_width = fo->mode->hdisplay; + drm->mode_config.max_width = fo->mode->hdisplay; + drm->mode_config.min_height = fo->mode->vdisplay; + drm->mode_config.max_height = fo->mode->vdisplay; + + ret = fo_pipe_init(drm, fo, fo_formats, ARRAY_SIZE(fo_formats), NULL); + if (ret) + return dev_err_probe(dev, ret, "Failed to initialize display pipeline.\n"); + + drm_mode_config_reset(drm); + + ret = drm_dev_register(drm, 0); + if (ret) + return dev_err_probe(dev, ret, "Failed to register drm device.\n"); + + drm_client_setup(drm, NULL); + + return 0; +} + +static void fo_remove(struct spi_device *spi) +{ + struct fo_device *fo = spi_get_drvdata(spi); + + drm_dev_unplug(&fo->drm); + drm_atomic_helper_shutdown(&fo->drm); +} + +static struct spi_driver fo_spi_driver = { + .driver = { + .name = "flipper_one_display", + .of_match_table = fo_of_match, + }, + .probe = fo_probe, + .remove = fo_remove, + .id_table = fo_ids, +}; +module_spi_driver(fo_spi_driver); + +MODULE_AUTHOR("Alexey Charkov "); +MODULE_DESCRIPTION("SPI Protocol driver for the Flipper One display"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 14e4cea48acc47..79b4bd232e9c34 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2307,6 +2307,17 @@ config SENSORS_INA3221 This driver can also be built as a module. If so, the module will be called ina3221. +config SENSORS_INA4230 + tristate "Texas Instruments INA4230 Quad Current/Voltage Monitor" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for the TI INA4230 Quad + Current/Voltage Monitor. + + This driver can also be built as a module. If so, the module + will be called ina4230. + config SENSORS_SPD5118 tristate "SPD5118 Compliant Temperature Sensors" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 982ee2c6f9deb6..673ecf989fe09b 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -104,6 +104,7 @@ obj-$(CONFIG_SENSORS_INA209) += ina209.o obj-$(CONFIG_SENSORS_INA2XX) += ina2xx.o obj-$(CONFIG_SENSORS_INA238) += ina238.o obj-$(CONFIG_SENSORS_INA3221) += ina3221.o +obj-$(CONFIG_SENSORS_INA4230) += ina4230.o obj-$(CONFIG_SENSORS_INTEL_M10_BMC_HWMON) += intel-m10-bmc-hwmon.o obj-$(CONFIG_SENSORS_ISL28022) += isl28022.o obj-$(CONFIG_SENSORS_IT87) += it87.o diff --git a/drivers/hwmon/ina4230.c b/drivers/hwmon/ina4230.c new file mode 100644 index 00000000000000..b5233c004089aa --- /dev/null +++ b/drivers/hwmon/ina4230.c @@ -0,0 +1,1032 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * INA4230 Quad Current/Voltage Monitor + * + * Based on INA3221 driver by Texas Instruments Incorporated - https://www.ti.com/ + * Adapted for INA4230 by Alexey Charkov + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define INA4230_DRIVER_NAME "ina4230" + +#define INA4230_SHUNT_VOLTAGE_CH1 0x00 +#define INA4230_BUS_VOLTAGE_CH1 0x01 +#define INA4230_CURRENT_CH1 0x02 +#define INA4230_POWER_CH1 0x03 +#define INA4230_ENERGY_CH1 0x04 +#define INA4230_CALIBRATION_CH1 0x05 +#define INA4230_ALERT_LIMIT1 0x06 +#define INA4230_ALERT_CONFIG1 0x07 +#define INA4230_SHUNT_VOLTAGE_CH2 0x08 +#define INA4230_BUS_VOLTAGE_CH2 0x09 +#define INA4230_CURRENT_CH2 0x0A +#define INA4230_POWER_CH2 0x0B +#define INA4230_ENERGY_CH2 0x0C +#define INA4230_CALIBRATION_CH2 0x0D +#define INA4230_ALERT_LIMIT2 0x0E +#define INA4230_ALERT_CONFIG2 0x0F +#define INA4230_SHUNT_VOLTAGE_CH3 0x10 +#define INA4230_BUS_VOLTAGE_CH3 0x11 +#define INA4230_CURRENT_CH3 0x12 +#define INA4230_POWER_CH3 0x13 +#define INA4230_ENERGY_CH3 0x14 +#define INA4230_CALIBRATION_CH3 0x15 +#define INA4230_ALERT_LIMIT3 0x16 +#define INA4230_ALERT_CONFIG3 0x17 +#define INA4230_SHUNT_VOLTAGE_CH4 0x18 +#define INA4230_BUS_VOLTAGE_CH4 0x19 +#define INA4230_CURRENT_CH4 0x1A +#define INA4230_POWER_CH4 0x1B +#define INA4230_ENERGY_CH4 0x1C +#define INA4230_CALIBRATION_CH4 0x1D +#define INA4230_ALERT_LIMIT4 0x1E +#define INA4230_ALERT_CONFIG4 0x1F +#define INA4230_CONFIG1 0x20 +#define INA4230_CONFIG2 0x21 +#define INA4230_FLAGS 0x22 +#define INA4230_MANUFACTURER_ID 0x7E + +#define INA4230_CALIBRATION_MASK GENMASK(14, 0) + +#define INA4230_ALERT_CHANNEL_MASK GENMASK(4, 3) +#define INA4230_ALERT_MASK GENMASK(2, 0) +/* Shunt voltage over limit */ +#define INA4230_ALERT_MASK_SOL 0x1 +/* Shunt voltage under limit */ +#define INA4230_ALERT_MASK_SUL 0x2 +/* Bus voltage over limit */ +#define INA4230_ALERT_MASK_BOL 0x3 +/* Bus voltage under limit */ +#define INA4230_ALERT_MASK_BUL 0x4 +/* Power over limit */ +#define INA4230_ALERT_MASK_POL 0x5 + +#define INA4230_CONFIG1_ACTIVE_CHANNEL_MASK GENMASK(15, 12) +#define INA4230_CONFIG1_AVG_MASK GENMASK(11, 9) +#define INA4230_CONFIG1_VBUSCT_MASK GENMASK(8, 6) +#define INA4230_CONFIG1_VSHCT_MASK GENMASK(5, 3) +#define INA4230_CONFIG1_MODE_MASK GENMASK(2, 0) +#define INA4230_MODE_POWERDOWN 0 +#define INA4230_MODE_SHUNT_SINGLE 1 +#define INA4230_MODE_BUS_SINGLE 2 +#define INA4230_MODE_BUS_SHUNT_SINGLE 3 +#define INA4230_MODE_POWERDOWN1 4 +#define INA4230_MODE_SHUNT_CONTINUOUS 5 +#define INA4230_MODE_BUS_CONTINUOUS 6 +#define INA4230_MODE_BUS_SHUNT_CONTINUOUS 7 + +#define INA4230_CONFIG2_RST BIT(15) +#define INA4230_CONFIG2_ACC_RST_MASK GENMASK(11, 8) +#define INA4230_CONFIG2_CNVR_MASK BIT(7) +#define INA4230_CONFIG2_ENOF_MASK BIT(6) +#define INA4230_CONFIG2_ALERT_LATCH BIT(5) +#define INA4230_CONFIG2_ALERT_POL BIT(4) +#define INA4230_CONFIG2_RANGE_MASK GENMASK(3, 0) +#define INA4230_CONFIG2_RANGE_CH(x) \ + FIELD_PREP(INA4230_CONFIG2_RANGE_MASK, BIT((x))) + +#define INA4230_FLAGS_LIMIT4_ALERT BIT(15) +#define INA4230_FLAGS_LIMIT3_ALERT BIT(14) +#define INA4230_FLAGS_LIMIT2_ALERT BIT(13) +#define INA4230_FLAGS_LIMIT1_ALERT BIT(12) +#define INA4230_FLAGS_ENERGY_OVERFLOW_CH4 BIT(11) +#define INA4230_FLAGS_ENERGY_OVERFLOW_CH3 BIT(10) +#define INA4230_FLAGS_ENERGY_OVERFLOW_CH2 BIT(9) +#define INA4230_FLAGS_ENERGY_OVERFLOW_CH1 BIT(8) +#define INA4230_FLAGS_CVRF BIT(7) +#define INA4230_FLAGS_MATH_OVERFLOW BIT(6) + +#define INA4230_RSHUNT_DEFAULT 10000 +#define INA4230_CONFIG_DEFAULT \ + (FIELD_PREP(INA4230_CONFIG1_ACTIVE_CHANNEL_MASK, 0xF) | \ + FIELD_PREP(INA4230_CONFIG1_AVG_MASK, 0x1) | \ + FIELD_PREP(INA4230_CONFIG1_VBUSCT_MASK, 0x4) | \ + FIELD_PREP(INA4230_CONFIG1_VSHCT_MASK, 0x4) | \ + FIELD_PREP(INA4230_CONFIG1_MODE_MASK, 0x7)) +#define INA4230_CONFIG_CHx_EN(x) \ + FIELD_PREP(INA4230_CONFIG1_ACTIVE_CHANNEL_MASK, BIT((x))) + +enum ina4230_fields { + /* Alert configuration settings: channel masks */ + F_ALERT1_CH, F_ALERT2_CH, F_ALERT3_CH, F_ALERT4_CH, + /* Alert configuration settings: alert masks */ + F_ALERT1_TYPE, F_ALERT2_TYPE, F_ALERT3_TYPE, F_ALERT4_TYPE, + /* Configuration registers */ + F_CH_EN, F_AVG, F_VBUSCT, F_VSHCT, F_MODE, + F_RST, F_ACC_RST, F_CNV_ALERT, F_ENOF, F_ALERT_LATCH, F_ALERT_POL, F_RANGE, + /* Status flags */ + F_LIMIT1_ALERT, F_LIMIT2_ALERT, F_LIMIT3_ALERT, F_LIMIT4_ALERT, + F_ENERGY_OVERFLOW_CH1, F_ENERGY_OVERFLOW_CH2, F_ENERGY_OVERFLOW_CH3, F_ENERGY_OVERFLOW_CH4, + F_CVRF, F_MATH_OVERFLOW, + /* sentinel */ + F_MAX_FIELDS +}; + +static const struct reg_field ina4230_reg_fields[] = { + [F_ALERT1_CH] = REG_FIELD(INA4230_ALERT_CONFIG1, 3, 4), + [F_ALERT2_CH] = REG_FIELD(INA4230_ALERT_CONFIG2, 3, 4), + [F_ALERT3_CH] = REG_FIELD(INA4230_ALERT_CONFIG3, 3, 4), + [F_ALERT4_CH] = REG_FIELD(INA4230_ALERT_CONFIG4, 3, 4), + + [F_ALERT1_TYPE] = REG_FIELD(INA4230_ALERT_CONFIG1, 0, 2), + [F_ALERT2_TYPE] = REG_FIELD(INA4230_ALERT_CONFIG2, 0, 2), + [F_ALERT3_TYPE] = REG_FIELD(INA4230_ALERT_CONFIG3, 0, 2), + [F_ALERT4_TYPE] = REG_FIELD(INA4230_ALERT_CONFIG4, 0, 2), + + [F_CH_EN] = REG_FIELD(INA4230_CONFIG1, 12, 15), + [F_AVG] = REG_FIELD(INA4230_CONFIG1, 9, 11), + [F_VBUSCT] = REG_FIELD(INA4230_CONFIG1, 6, 8), + [F_VSHCT] = REG_FIELD(INA4230_CONFIG1, 3, 5), + [F_MODE] = REG_FIELD(INA4230_CONFIG1, 0, 2), + [F_RST] = REG_FIELD(INA4230_CONFIG2, 15, 15), + [F_ACC_RST] = REG_FIELD(INA4230_CONFIG2, 8, 11), + [F_CNV_ALERT] = REG_FIELD(INA4230_CONFIG2, 7, 7), + [F_ENOF] = REG_FIELD(INA4230_CONFIG2, 6, 6), + [F_ALERT_LATCH] = REG_FIELD(INA4230_CONFIG2, 5, 5), + [F_ALERT_POL] = REG_FIELD(INA4230_CONFIG2, 4, 4), + [F_RANGE] = REG_FIELD(INA4230_CONFIG2, 0, 3), + + [F_LIMIT1_ALERT] = REG_FIELD(INA4230_FLAGS, 12, 12), + [F_LIMIT2_ALERT] = REG_FIELD(INA4230_FLAGS, 13, 13), + [F_LIMIT3_ALERT] = REG_FIELD(INA4230_FLAGS, 14, 14), + [F_LIMIT4_ALERT] = REG_FIELD(INA4230_FLAGS, 15, 15), + [F_ENERGY_OVERFLOW_CH1] = REG_FIELD(INA4230_FLAGS, 8, 8), + [F_ENERGY_OVERFLOW_CH2] = REG_FIELD(INA4230_FLAGS, 9, 9), + [F_ENERGY_OVERFLOW_CH3] = REG_FIELD(INA4230_FLAGS, 10, 10), + [F_ENERGY_OVERFLOW_CH4] = REG_FIELD(INA4230_FLAGS, 11, 11), + [F_CVRF] = REG_FIELD(INA4230_FLAGS, 7, 7), + [F_MATH_OVERFLOW] = REG_FIELD(INA4230_FLAGS, 6, 6), +}; + +enum ina4230_channels { + INA4230_CHANNEL1, + INA4230_CHANNEL2, + INA4230_CHANNEL3, + INA4230_CHANNEL4, + INA4230_NUM_CHANNELS +}; + +/** + * struct ina4230_input - channel input source specific information + * @label: label of channel input source + * @shunt_resistor: shunt resistor value of channel input source + * @shunt_gain: gain of shunt voltage for current calculation + * @max_expected_current: maximum expected current in micro-Ampere for ADC + * calibration + * @current_lsb_uA: current LSB in micro-Amperes + * @disconnected: connection status of channel input source + */ +struct ina4230_input { + const char *label; + int shunt_resistor; + int shunt_gain; + int max_expected_current; + int current_lsb_uA; + bool disconnected; +}; + +/** + * struct ina4230_data - device specific information + * @pm_dev: Device pointer for pm runtime + * @regmap: Register map of the device + * @fields: Register fields of the device + * @inputs: Array of channel input source specific structures + * @reg_config1: cached value of CONFIG1 register + * @reg_config2: cached value of CONFIG2 register + * @alert_active_high: flag indicating alert polarity is active high + */ +struct ina4230_data { + struct device *pm_dev; + struct regmap *regmap; + struct regmap_field *fields[F_MAX_FIELDS]; + struct ina4230_input inputs[INA4230_NUM_CHANNELS]; + unsigned int reg_config1; + unsigned int reg_config2; + bool alert_active_high; +}; + +static inline bool ina4230_is_enabled(struct ina4230_data *ina, int channel) +{ + return pm_runtime_active(ina->pm_dev) && + !ina->inputs[channel].disconnected && + ina->reg_config1 & INA4230_CONFIG_CHx_EN(channel); +} + +/* Lookup table for Bus and Shunt conversion times in usec */ +static const u16 ina4230_conv_time[] = { + 140, 204, 332, 588, 1100, 2116, 4156, 8244, +}; + +/* Lookup table for number of samples used in averaging mode */ +static const int ina4230_avg_samples[] = { + 1, 4, 16, 64, 128, 256, 512, 1024, +}; + +/* Converting update_interval in msec to conversion time in usec */ +static inline u32 ina4230_interval_ms_to_conv_time(u16 config, int interval) +{ + u32 channels = hweight16(config & INA4230_CONFIG1_ACTIVE_CHANNEL_MASK); + u32 samples_idx = FIELD_GET(INA4230_CONFIG1_AVG_MASK, config); + u32 samples = ina4230_avg_samples[samples_idx]; + + /* Bisect the result to Bus and Shunt conversion times */ + return DIV_ROUND_CLOSEST(interval * 1000 / 2, channels * samples); +} + +/* Converting CONFIG register value to update_interval in usec */ +static inline u32 ina4230_reg_to_interval_us(u16 config) +{ + u32 channels = hweight16(config & INA4230_CONFIG1_ACTIVE_CHANNEL_MASK); + u32 vbus_ct_idx = FIELD_GET(INA4230_CONFIG1_VBUSCT_MASK, config); + u32 vsh_ct_idx = FIELD_GET(INA4230_CONFIG1_VSHCT_MASK, config); + u32 vbus_ct = ina4230_conv_time[vbus_ct_idx]; + u32 vsh_ct = ina4230_conv_time[vsh_ct_idx]; + + /* Calculate total conversion time */ + return channels * (vbus_ct + vsh_ct); +} + +static const u8 ina4230_calibration_reg[] = { + INA4230_CALIBRATION_CH1, + INA4230_CALIBRATION_CH2, + INA4230_CALIBRATION_CH3, + INA4230_CALIBRATION_CH4, +}; + +static int ina4230_set_calibration(struct ina4230_data *ina, int channel) +{ + struct ina4230_input *input = &ina->inputs[channel]; + u8 reg = ina4230_calibration_reg[channel]; + int shunt_range_uV, ret; + u32 calibration; + u64 n, d; + + shunt_range_uV = mult_frac(input->max_expected_current, + input->shunt_resistor, + 1000000); + input->shunt_gain = shunt_range_uV > 20480 ? 1 : 4; + ina->reg_config2 &= ~INA4230_CONFIG2_RANGE_CH(channel); + if (input->shunt_gain == 4) + ina->reg_config2 |= INA4230_CONFIG2_RANGE_CH(channel); + + ret = regmap_write(ina->regmap, INA4230_CONFIG2, ina->reg_config2); + if (ret) + return ret; + + input->current_lsb_uA = DIV_ROUND_UP(input->max_expected_current, 32768); + n = 5120000000ULL; + d = (u64)input->current_lsb_uA * input->shunt_resistor * input->shunt_gain; + /* Ensure rounding to the closest integer */ + n += d / 2; + n = div64_u64(n, d); + if (n > INA4230_CALIBRATION_MASK) { + dev_err(ina->pm_dev, + "Shunt %duOhm too low for expected current %duA, cannot calibrate channel %d\n", + input->shunt_resistor, input->max_expected_current, channel + 1); + return -ERANGE; + } + + calibration = n & INA4230_CALIBRATION_MASK; + + return regmap_write(ina->regmap, reg, calibration); +} + +static const u8 ina4230_in_reg[] = { + INA4230_BUS_VOLTAGE_CH1, + INA4230_BUS_VOLTAGE_CH2, + INA4230_BUS_VOLTAGE_CH3, + INA4230_BUS_VOLTAGE_CH4, + INA4230_SHUNT_VOLTAGE_CH1, + INA4230_SHUNT_VOLTAGE_CH2, + INA4230_SHUNT_VOLTAGE_CH3, + INA4230_SHUNT_VOLTAGE_CH4, +}; + +static const u8 ina4230_curr_reg[][INA4230_NUM_CHANNELS] = { + [hwmon_curr_input] = { INA4230_CURRENT_CH1, INA4230_CURRENT_CH2, + INA4230_CURRENT_CH3, INA4230_CURRENT_CH4 }, +}; + +static const u8 ina4230_power_reg[] = { + INA4230_POWER_CH1, INA4230_POWER_CH2, INA4230_POWER_CH3, INA4230_POWER_CH4 +}; + +static const u8 ina4230_energy_reg[] = { + INA4230_ENERGY_CH1, INA4230_ENERGY_CH2, + INA4230_ENERGY_CH3, INA4230_ENERGY_CH4 +}; + +static int ina4230_read_chip(struct device *dev, u32 attr, long *val) +{ + struct ina4230_data *ina = dev_get_drvdata(dev); + int regval; + + switch (attr) { + case hwmon_chip_samples: + regval = FIELD_GET(INA4230_CONFIG1_AVG_MASK, ina->reg_config1); + *val = ina4230_avg_samples[regval]; + return 0; + case hwmon_chip_update_interval: + /* Return in msec */ + *val = ina4230_reg_to_interval_us(ina->reg_config1); + *val = DIV_ROUND_CLOSEST(*val, 1000); + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int ina4230_read_in(struct device *dev, u32 attr, int channel, long *val) +{ + const bool is_shunt = channel > INA4230_CHANNEL4; + struct ina4230_data *ina = dev_get_drvdata(dev); + u8 reg = ina4230_in_reg[channel]; + int regval, ret; + + /* + * Translate shunt channel index to sensor channel index + */ + channel %= INA4230_NUM_CHANNELS; + + switch (attr) { + case hwmon_in_input: + if (!ina4230_is_enabled(ina, channel)) + return -ENODATA; + + ret = regmap_read(ina->regmap, reg, ®val); + if (ret) + return ret; + + /* + * Scale of shunt voltage (uV): LSB is 2.5uV or 625nV + * depending on gain setting + * Scale of bus voltage (mV): LSB is 1.6mV + */ + if (is_shunt) + *val = mult_frac((long)(int16_t)regval, + 2500 / ina->inputs[channel].shunt_gain, + 1000000); + else + *val = mult_frac((long)(int16_t)regval, + 1600, + 1000); + return 0; + case hwmon_in_enable: + *val = ina4230_is_enabled(ina, channel); + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int ina4230_read_power(struct device *dev, u32 attr, int channel, long *val) +{ + struct ina4230_data *ina = dev_get_drvdata(dev); + u8 reg = ina4230_power_reg[channel]; + int regval, ret; + + switch (attr) { + case hwmon_power_input: + if (!ina4230_is_enabled(ina, channel)) + return -ENODATA; + + ret = regmap_read(ina->regmap, reg, ®val); + if (ret) + return ret; + + *val = (int16_t)regval * + (long)ina->inputs[channel].current_lsb_uA * 32; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int ina4230_read_energy(struct device *dev, u32 attr, int channel, long *val) +{ + struct ina4230_data *ina = dev_get_drvdata(dev); + u8 reg = ina4230_energy_reg[channel]; + int ret; + __be32 regval; + + switch (attr) { + case hwmon_energy_input: + if (!ina4230_is_enabled(ina, channel)) + return -ENODATA; + + ret = regmap_noinc_read(ina->regmap, reg, ®val, sizeof(regval)); + if (ret) + return ret; + + *val = be32_to_cpu(regval) * + (long)ina->inputs[channel].current_lsb_uA * 32; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int ina4230_read_curr(struct device *dev, u32 attr, + int channel, long *val) +{ + struct ina4230_data *ina = dev_get_drvdata(dev); + u8 reg = ina4230_curr_reg[attr][channel]; + int regval, ret; + + switch (attr) { + case hwmon_curr_input: + if (!ina4230_is_enabled(ina, channel)) + return -ENODATA; + + ret = regmap_read(ina->regmap, reg, ®val); + if (ret) + return ret; + + *val = (int16_t)regval * + (long)ina->inputs[channel].current_lsb_uA / 1000; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int ina4230_write_chip(struct device *dev, u32 attr, long val) +{ + struct ina4230_data *ina = dev_get_drvdata(dev); + int idx; + u32 tmp; + + switch (attr) { + case hwmon_chip_samples: + idx = find_closest(val, ina4230_avg_samples, + ARRAY_SIZE(ina4230_avg_samples)); + + FIELD_MODIFY(INA4230_CONFIG1_AVG_MASK, &ina->reg_config1, idx); + return regmap_write(ina->regmap, INA4230_CONFIG1, ina->reg_config1); + case hwmon_chip_update_interval: + tmp = ina4230_interval_ms_to_conv_time(ina->reg_config1, val); + idx = find_closest(tmp, ina4230_conv_time, + ARRAY_SIZE(ina4230_conv_time)); + + FIELD_MODIFY(INA4230_CONFIG1_VBUSCT_MASK, &ina->reg_config1, idx); + FIELD_MODIFY(INA4230_CONFIG1_VSHCT_MASK, &ina->reg_config1, idx); + return regmap_write(ina->regmap, INA4230_CONFIG1, ina->reg_config1); + default: + return -EOPNOTSUPP; + } +} + +static int ina4230_write_enable(struct device *dev, int channel, bool enable) +{ + struct ina4230_data *ina = dev_get_drvdata(dev); + u16 config, mask = INA4230_CONFIG_CHx_EN(channel); + u16 config_old = ina->reg_config1 & mask; + u32 tmp; + int ret; + + config = enable ? mask : 0; + + /* Bypass if enable status is not being changed */ + if (config_old == config) + return 0; + + /* For enabling routine, increase refcount and resume() at first */ + if (enable) { + ret = pm_runtime_resume_and_get(ina->pm_dev); + if (ret < 0) { + dev_err(dev, "Failed to get PM runtime\n"); + return ret; + } + } + + /* Enable or disable the channel */ + tmp = (ina->reg_config1 & ~mask) | (config & mask); + ret = regmap_write(ina->regmap, INA4230_CONFIG1, tmp); + if (ret) + goto fail; + + /* Cache the latest config register value */ + ina->reg_config1 = tmp; + + /* For disabling routine, decrease refcount or suspend() at last */ + if (!enable) + pm_runtime_put_sync(ina->pm_dev); + + return 0; + +fail: + if (enable) { + dev_err(dev, "Failed to enable channel %d: error %d\n", + channel, ret); + pm_runtime_put_sync(ina->pm_dev); + } + + return ret; +} + +static int ina4230_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + int ret; + + switch (type) { + case hwmon_chip: + ret = ina4230_read_chip(dev, attr, val); + break; + case hwmon_in: + /* 0-align channel ID */ + ret = ina4230_read_in(dev, attr, channel - 1, val); + break; + case hwmon_curr: + ret = ina4230_read_curr(dev, attr, channel, val); + break; + case hwmon_power: + ret = ina4230_read_power(dev, attr, channel, val); + break; + case hwmon_energy: + ret = ina4230_read_energy(dev, attr, channel, val); + break; + default: + ret = -EOPNOTSUPP; + break; + } + return ret; +} + +static int ina4230_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + int ret; + + switch (type) { + case hwmon_chip: + ret = ina4230_write_chip(dev, attr, val); + break; + case hwmon_in: + /* 0-align channel ID */ + ret = ina4230_write_enable(dev, channel - 1, val); + break; + default: + ret = -EOPNOTSUPP; + break; + } + return ret; +} + +static int ina4230_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + struct ina4230_data *ina = dev_get_drvdata(dev); + int index = channel - 1; + + *str = ina->inputs[index].label; + + return 0; +} + +static umode_t ina4230_is_visible(const void *drvdata, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct ina4230_data *ina = drvdata; + const struct ina4230_input *input = NULL; + + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_samples: + case hwmon_chip_update_interval: + return 0644; + default: + return 0; + } + case hwmon_in: + /* Ignore in0_ */ + if (channel == 0) + return 0; + + switch (attr) { + case hwmon_in_label: + if (channel - 1 <= INA4230_CHANNEL4) + input = &ina->inputs[channel - 1]; + /* Hide label node if label is not provided */ + return (input && input->label) ? 0444 : 0; + case hwmon_in_input: + return 0444; + case hwmon_in_enable: + return 0644; + default: + return 0; + } + case hwmon_curr: + switch (attr) { + case hwmon_curr_input: + return 0444; + default: + return 0; + } + case hwmon_power: + switch (attr) { + case hwmon_power_input: + return 0444; + default: + return 0; + } + case hwmon_energy: + switch (attr) { + case hwmon_energy_input: + return 0444; + default: + return 0; + } + default: + return 0; + } +} + +static const struct hwmon_channel_info * const ina4230_info[] = { + HWMON_CHANNEL_INFO(chip, + HWMON_C_SAMPLES, + HWMON_C_UPDATE_INTERVAL), + HWMON_CHANNEL_INFO(in, + /* 0: dummy, skipped in is_visible */ + HWMON_I_INPUT, + /* 1-4: input voltage Channels */ + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + /* 5-8: shunt voltage Channels */ + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT), + HWMON_CHANNEL_INFO(curr, + /* 1-4: current channels*/ + HWMON_C_INPUT, + HWMON_C_INPUT, + HWMON_C_INPUT, + HWMON_C_INPUT), + HWMON_CHANNEL_INFO(power, + /* 1-4: power channels*/ + HWMON_P_INPUT, + HWMON_P_INPUT, + HWMON_P_INPUT, + HWMON_P_INPUT), + HWMON_CHANNEL_INFO(energy, + /* 1-4: energy channels*/ + HWMON_E_INPUT, + HWMON_E_INPUT, + HWMON_E_INPUT, + HWMON_E_INPUT), + NULL +}; + +static const struct hwmon_ops ina4230_hwmon_ops = { + .is_visible = ina4230_is_visible, + .read_string = ina4230_read_string, + .read = ina4230_read, + .write = ina4230_write, +}; + +static const struct hwmon_chip_info ina4230_chip_info = { + .ops = &ina4230_hwmon_ops, + .info = ina4230_info, +}; + +/* Extra attribute groups */ +static ssize_t ina4230_shunt_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr); + struct ina4230_data *ina = dev_get_drvdata(dev); + unsigned int channel = sd_attr->index; + struct ina4230_input *input = &ina->inputs[channel]; + + return sysfs_emit(buf, "%d\n", input->shunt_resistor); +} + +static ssize_t ina4230_shunt_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr); + struct ina4230_data *ina = dev_get_drvdata(dev); + unsigned int channel = sd_attr->index; + struct ina4230_input *input = &ina->inputs[channel]; + int val; + int ret; + + ret = kstrtoint(buf, 0, &val); + if (ret) + return ret; + + val = clamp_val(val, 1, INT_MAX); + + input->shunt_resistor = val; + ret = ina4230_set_calibration(ina, channel); + if (ret) + return ret; + + return count; +} + +/* shunt resistance */ +static SENSOR_DEVICE_ATTR_RW(shunt1_resistor, ina4230_shunt, INA4230_CHANNEL1); +static SENSOR_DEVICE_ATTR_RW(shunt2_resistor, ina4230_shunt, INA4230_CHANNEL2); +static SENSOR_DEVICE_ATTR_RW(shunt3_resistor, ina4230_shunt, INA4230_CHANNEL3); +static SENSOR_DEVICE_ATTR_RW(shunt4_resistor, ina4230_shunt, INA4230_CHANNEL4); + +static struct attribute *ina4230_attrs[] = { + &sensor_dev_attr_shunt1_resistor.dev_attr.attr, + &sensor_dev_attr_shunt2_resistor.dev_attr.attr, + &sensor_dev_attr_shunt3_resistor.dev_attr.attr, + &sensor_dev_attr_shunt4_resistor.dev_attr.attr, + NULL, +}; +ATTRIBUTE_GROUPS(ina4230); + +static const struct regmap_range ina4230_vol_ranges[] = { + regmap_reg_range(INA4230_SHUNT_VOLTAGE_CH1, INA4230_ENERGY_CH1), + regmap_reg_range(INA4230_SHUNT_VOLTAGE_CH2, INA4230_ENERGY_CH2), + regmap_reg_range(INA4230_SHUNT_VOLTAGE_CH3, INA4230_ENERGY_CH3), + regmap_reg_range(INA4230_SHUNT_VOLTAGE_CH4, INA4230_ENERGY_CH4), + regmap_reg_range(INA4230_FLAGS, INA4230_FLAGS), +}; + +static const struct regmap_access_table ina4230_volatile_table = { + .yes_ranges = ina4230_vol_ranges, + .n_yes_ranges = ARRAY_SIZE(ina4230_vol_ranges), +}; + +static const struct regmap_config ina4230_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + + .cache_type = REGCACHE_MAPLE, + .volatile_table = &ina4230_volatile_table, +}; + +static int ina4230_probe_child_from_dt(struct device *dev, + struct device_node *child, + struct ina4230_data *ina) +{ + struct ina4230_input *input; + u32 val; + int ret; + + ret = of_property_read_u32(child, "reg", &val); + if (ret) + return dev_err_probe(dev, ret, + "missing reg property of %pOFn\n", child); + else if (val > INA4230_CHANNEL4) + return dev_err_probe(dev, -EINVAL, + "invalid reg %d of %pOFn\n", val, child); + + input = &ina->inputs[val]; + + /* Log the disconnected channel input */ + if (!of_device_is_available(child)) { + input->disconnected = true; + return 0; + } + + /* Save the connected input label if available */ + of_property_read_string(child, "label", &input->label); + + /* Overwrite default shunt resistor value optionally */ + if (!of_property_read_u32(child, "shunt-resistor-micro-ohms", &val)) { + if (val < 1 || val > INT_MAX) + return dev_err_probe(dev, -EINVAL, + "invalid shunt resistor value %u of %pOFn\n", + val, child); + + input->shunt_resistor = val; + } + + /* Save the expected maxcurrent */ + if (!of_property_read_u32(child, "ti,maximum-expected-current-microamp", &val)) { + if (val < 32768 || val > INT_MAX) + return dev_err_probe(dev, -EINVAL, + "invalid max current value %u of %pOFn\n", + val, child); + + input->max_expected_current = val; + } + + return 0; +} + +static int ina4230_probe_from_dt(struct device *dev, struct ina4230_data *ina) +{ + const struct device_node *np = dev->of_node; + int ret; + + /* Compatible with non-DT platforms */ + if (!np) + return 0; + + ina->alert_active_high = of_property_read_bool(np, "ti,alert-polarity-active-high"); + + for_each_child_of_node_scoped(np, child) { + ret = ina4230_probe_child_from_dt(dev, child, ina); + if (ret) + return ret; + } + + ret = devm_regulator_get_enable_optional(dev, "vs"); + if (ret && ret != -ENODEV) + return dev_err_probe(dev, ret, "Failed to get regulator\n"); + + return 0; +} + +static int ina4230_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct ina4230_data *ina; + struct device *hwmon_dev; + int i, ret; + + ina = devm_kzalloc(dev, sizeof(*ina), GFP_KERNEL); + if (!ina) + return -ENOMEM; + + ina->regmap = devm_regmap_init_i2c(client, &ina4230_regmap_config); + if (IS_ERR(ina->regmap)) + return PTR_ERR(ina->regmap); + + ret = devm_regmap_field_bulk_alloc(dev, ina->regmap, ina->fields, + ina4230_reg_fields, + ARRAY_SIZE(ina4230_reg_fields)); + if (ret) + return ret; + + for (i = 0; i < INA4230_NUM_CHANNELS; i++) { + ina->inputs[i].shunt_resistor = INA4230_RSHUNT_DEFAULT; + /* Default for 1mA LSB current measurements */ + ina->inputs[i].max_expected_current = 32768000; + } + + ret = ina4230_probe_from_dt(dev, ina); + if (ret) + return dev_err_probe(dev, ret, + "Unable to probe from device tree\n"); + + /* The driver will be reset, so use reset value */ + ina->reg_config1 = INA4230_CONFIG_DEFAULT; + ina->reg_config2 = 0; + + if (ina->alert_active_high) + FIELD_MODIFY(INA4230_CONFIG2_ALERT_POL, &ina->reg_config2, 1); + + /* Disable channels if their inputs are disconnected */ + for (i = 0; i < INA4230_NUM_CHANNELS; i++) { + if (ina->inputs[i].disconnected) + ina->reg_config1 &= ~INA4230_CONFIG_CHx_EN(i); + } + + ina->pm_dev = dev; + dev_set_drvdata(dev, ina); + + /* Enable PM runtime -- status is suspended by default */ + pm_runtime_enable(ina->pm_dev); + + /* Initialize (resume) the device */ + for (i = 0; i < INA4230_NUM_CHANNELS; i++) { + if (ina->inputs[i].disconnected) + continue; + + /* Match the refcount with number of enabled channels */ + ret = pm_runtime_get_sync(ina->pm_dev); + if (ret < 0) + goto fail; + } + + /* Set calibration values after device resume/reset */ + for (i = 0; i < INA4230_NUM_CHANNELS; i++) { + if (!ina->inputs[i].disconnected) { + ret = ina4230_set_calibration(ina, i); + if (ret) + goto fail; + } + } + + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, ina, + &ina4230_chip_info, + ina4230_groups); + if (IS_ERR(hwmon_dev)) { + ret = dev_err_probe(dev, PTR_ERR(hwmon_dev), + "Unable to register hwmon device\n"); + goto fail; + } + + return 0; + +fail: + pm_runtime_disable(ina->pm_dev); + pm_runtime_set_suspended(ina->pm_dev); + /* pm_runtime_put_noidle() for connected channels to balance get_sync */ + for (i = 0; i < INA4230_NUM_CHANNELS; i++) { + if (!ina->inputs[i].disconnected) + pm_runtime_put_noidle(ina->pm_dev); + } + + return ret; +} + +static void ina4230_remove(struct i2c_client *client) +{ + struct ina4230_data *ina = dev_get_drvdata(&client->dev); + int i; + + pm_runtime_disable(ina->pm_dev); + pm_runtime_set_suspended(ina->pm_dev); + + /* pm_runtime_put_noidle() for connected channels to balance get_sync */ + for (i = 0; i < INA4230_NUM_CHANNELS; i++) { + if (!ina->inputs[i].disconnected) + pm_runtime_put_noidle(ina->pm_dev); + } +} + +static int ina4230_suspend(struct device *dev) +{ + struct ina4230_data *ina = dev_get_drvdata(dev); + int ret; + + /* Save config register value and enable cache-only */ + ret = regmap_read(ina->regmap, INA4230_CONFIG1, &ina->reg_config1); + if (ret) + return ret; + + regcache_cache_only(ina->regmap, true); + regcache_mark_dirty(ina->regmap); + + return 0; +} + +static int ina4230_resume(struct device *dev) +{ + struct ina4230_data *ina = dev_get_drvdata(dev); + int ret; + + regcache_cache_only(ina->regmap, false); + + /* Software reset the chip */ + ret = regmap_field_write(ina->fields[F_RST], true); + if (ret) { + dev_err(dev, "Unable to reset device\n"); + return ret; + } + + /* Restore cached register values to hardware */ + ret = regcache_sync(ina->regmap); + if (ret) + return ret; + + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(ina4230_pm, ina4230_suspend, ina4230_resume, + NULL); + +static const struct of_device_id ina4230_of_match_table[] = { + { .compatible = "ti,ina4230", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ina4230_of_match_table); + +static const struct i2c_device_id ina4230_ids[] = { + { "ina4230" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, ina4230_ids); + +static struct i2c_driver ina4230_i2c_driver = { + .probe = ina4230_probe, + .remove = ina4230_remove, + .driver = { + .name = INA4230_DRIVER_NAME, + .of_match_table = ina4230_of_match_table, + .pm = pm_ptr(&ina4230_pm), + }, + .id_table = ina4230_ids, +}; +module_i2c_driver(ina4230_i2c_driver); + +MODULE_AUTHOR("Alexey Charkov "); +MODULE_DESCRIPTION("Texas Instruments INA4230 HWMon Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/keyboard/adc-keys.c b/drivers/input/keyboard/adc-keys.c index f1753207429db0..339dd4d4a08421 100644 --- a/drivers/input/keyboard/adc-keys.c +++ b/drivers/input/keyboard/adc-keys.c @@ -19,12 +19,14 @@ struct adc_keys_button { u32 voltage; u32 keycode; + u32 type; }; struct adc_keys_state { struct iio_channel *channel; u32 num_keys; u32 last_key; + u32 last_type; u32 keyup_voltage; const struct adc_keys_button *map; }; @@ -35,6 +37,7 @@ static void adc_keys_poll(struct input_dev *input) int i, value, ret; u32 diff, closest = 0xffffffff; int keycode = 0; + u32 type = EV_KEY; ret = iio_read_channel_processed(st->channel, &value); if (unlikely(ret < 0)) { @@ -46,6 +49,7 @@ static void adc_keys_poll(struct input_dev *input) if (diff < closest) { closest = diff; keycode = st->map[i].keycode; + type = st->map[i].type; } } } @@ -54,13 +58,14 @@ static void adc_keys_poll(struct input_dev *input) keycode = 0; if (st->last_key && st->last_key != keycode) - input_report_key(input, st->last_key, 0); + input_event(input, st->last_type, st->last_key, 0); if (keycode) - input_report_key(input, keycode, 1); + input_event(input, type, keycode, 1); input_sync(input); st->last_key = keycode; + st->last_type = type; } static int adc_keys_load_keymap(struct device *dev, struct adc_keys_state *st) @@ -93,6 +98,10 @@ static int adc_keys_load_keymap(struct device *dev, struct adc_keys_state *st) return -EINVAL; } + if (fwnode_property_read_u32(child, "linux,input-type", + &map[i].type)) + map[i].type = EV_KEY; + i++; } @@ -156,9 +165,8 @@ static int adc_keys_probe(struct platform_device *pdev) input->id.product = 0x0001; input->id.version = 0x0100; - __set_bit(EV_KEY, input->evbit); for (i = 0; i < st->num_keys; i++) - __set_bit(st->map[i].keycode, input->keybit); + input_set_capability(input, st->map[i].type, st->map[i].keycode); if (device_property_read_bool(dev, "autorepeat")) __set_bit(EV_REP, input->evbit); diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 94a753fcb64fa5..b6817b25b4c7eb 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -178,6 +178,17 @@ config INPUT_E3X0_BUTTON To compile this driver as a module, choose M here: the module will be called e3x0_button. +config INPUT_FLIPPER_ONE + tristate "Flipper One input support" + depends on MFD_FLIPPER_ONE + help + Say Y here to enable support for human-machine interfacing + functions on the Flipper One networking multitool, including its + builtin buttons, touchpad, haptic feedback and headset inputs. + + To compile this driver as a module, choose M here: the + module will be called flipper-one-input. + config INPUT_PCSPKR tristate "PC Speaker support" depends on PCSPKR_PLATFORM diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index 415fc4e2918be8..704b6cc9b41c1c 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_INPUT_DA9052_ONKEY) += da9052_onkey.o obj-$(CONFIG_INPUT_DA9055_ONKEY) += da9055_onkey.o obj-$(CONFIG_INPUT_DA9063_ONKEY) += da9063_onkey.o obj-$(CONFIG_INPUT_E3X0_BUTTON) += e3x0-button.o +obj-$(CONFIG_INPUT_FLIPPER_ONE) += flipper-one-input.o obj-$(CONFIG_INPUT_DRV260X_HAPTICS) += drv260x.o obj-$(CONFIG_INPUT_DRV2665_HAPTICS) += drv2665.o obj-$(CONFIG_INPUT_DRV2667_HAPTICS) += drv2667.o diff --git a/drivers/input/misc/flipper-one-input.c b/drivers/input/misc/flipper-one-input.c new file mode 100644 index 00000000000000..eb0cbeb9c529f0 --- /dev/null +++ b/drivers/input/misc/flipper-one-input.c @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Flipper One builtin buttons and touchpad driver + * Copyright (C) 2026 Flipper FZCO + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define FO_BTN_VIEW BIT(0) +#define FO_BTN_ESCAPE BIT(1) +#define FO_BTN_POWER BIT(2) +#define FO_BTN_EDIT BIT(3) +#define FO_BTN_RUN BIT(4) +#define FO_BTN_APPSELECT BIT(5) +#define FO_BTN_BACK BIT(6) +#define FO_BTN_DOWN BIT(7) +#define FO_BTN_RIGHT BIT(8) +#define FO_BTN_CENTER BIT(9) +#define FO_BTN_LEFT BIT(10) +#define FO_BTN_UP BIT(11) +#define FO_BTN_PTT BIT(12) + +#define FO_HS_HPPRESENT BIT(0) +#define FO_HS_MICPRESENT BIT(1) +#define FO_HS_BTN_A BIT(2) +#define FO_HS_BTN_B BIT(3) +#define FO_HS_BTN_C BIT(4) +#define FO_HS_BTN_D BIT(5) + +struct fo_input { + struct input_dev *idev_btn; + struct input_dev *idev_touch; + struct input_dev *idev_headset; + struct fomcu_device *fomcu; +}; + +struct fo_irq { + const char *name; + irqreturn_t (*handler)(int, void *); +}; + +static irqreturn_t fo_input_btn_handler(int irq, void *data) +{ + struct fo_input *input = data; + struct regmap *regmap = input->fomcu->regmap; + struct input_dev *idev = input->idev_btn; + struct device *parent = idev->dev.parent; + unsigned int reg; + int err; + + err = regmap_read(regmap, FOMCU_REG_INPUT_BTNS, ®); + if (err) { + dev_err(parent, "Failed to read button states: %d\n", err); + return IRQ_NONE; + } + + input_report_key(idev, KEY_ENTER, FO_BTN_CENTER & reg); + input_report_key(idev, KEY_UP, FO_BTN_UP & reg); + input_report_key(idev, KEY_DOWN, FO_BTN_DOWN & reg); + input_report_key(idev, KEY_LEFT, FO_BTN_LEFT & reg); + input_report_key(idev, KEY_RIGHT, FO_BTN_RIGHT & reg); + input_report_key(idev, KEY_TAB, FO_BTN_APPSELECT & reg); + input_report_key(idev, KEY_BACKSPACE, FO_BTN_BACK & reg); + input_report_key(idev, KEY_A, FO_BTN_PTT & reg); + input_report_key(idev, KEY_Z, FO_BTN_ESCAPE & reg); + input_report_key(idev, KEY_X, FO_BTN_VIEW & reg); + input_report_key(idev, KEY_C, FO_BTN_POWER & reg); + input_report_key(idev, KEY_V, FO_BTN_EDIT & reg); + input_report_key(idev, KEY_B, FO_BTN_RUN & reg); + input_sync(idev); + + return IRQ_HANDLED; +} + +static irqreturn_t fo_input_touch_handler(int irq, void *data) +{ + struct fo_input *input = data; + struct regmap *regmap = input->fomcu->regmap; + struct input_dev *idev = input->idev_touch; + struct device *parent = idev->dev.parent; + uint16_t buf[3]; + int err; + + err = regmap_bulk_read(regmap, FOMCU_REG_INPUT_TOUCH_X, &buf, ARRAY_SIZE(buf)); + if (err) { + dev_err(parent, "Failed to read touch inputs: %d\n", err); + return IRQ_NONE; + } + + input_report_key(idev, BTN_TOUCH, !!buf[2]); + input_report_key(idev, BTN_TOOL_FINGER, !!buf[2]); + input_report_abs(idev, ABS_X, buf[0]); + input_report_abs(idev, ABS_Y, buf[1]); + input_report_abs(idev, ABS_PRESSURE, buf[2]); + input_sync(idev); + + return IRQ_HANDLED; +} + +static irqreturn_t fo_input_headset_handler(int irq, void *data) +{ + struct fo_input *input = data; + struct regmap *regmap = input->fomcu->regmap; + struct input_dev *idev = input->idev_headset; + struct device *parent = idev->dev.parent; + unsigned int reg; + int err; + + err = regmap_read(regmap, FOMCU_REG_INPUT_HEADSET, ®); + if (err) { + dev_err(parent, "Failed to read headset states: %d\n", err); + return IRQ_NONE; + } + + input_report_switch(idev, SW_HEADPHONE_INSERT, reg & FO_HS_HPPRESENT); + input_report_switch(idev, SW_MICROPHONE_INSERT, reg & FO_HS_MICPRESENT); + input_report_key(idev, KEY_PLAYPAUSE, reg & FO_HS_BTN_A); + input_report_key(idev, KEY_VOLUMEUP, reg & FO_HS_BTN_B); + input_report_key(idev, KEY_VOLUMEDOWN, reg & FO_HS_BTN_C); + input_report_key(idev, KEY_VOICECOMMAND, reg & FO_HS_BTN_D); + input_sync(idev); + + return IRQ_HANDLED; +} + +static const struct fo_irq fo_irqs[] = { + { .name = "flipper-one-input-btn", .handler = fo_input_btn_handler }, + { .name = "flipper-one-input-touch", .handler = fo_input_touch_handler }, + { .name = "flipper-one-input-headset", .handler = fo_input_headset_handler }, +}; + +static int fo_input_probe(struct platform_device *pdev) +{ + struct fomcu_device *fomcu = dev_get_drvdata(pdev->dev.parent); + struct device *dev = &pdev->dev; + struct fo_input *input; + struct input_dev *idev_btn, *idev_touch, *idev_headset; + int irq, err, i; + + input = devm_kzalloc(dev, sizeof(*input), GFP_KERNEL); + if (!input) + return -ENOMEM; + + input->fomcu = fomcu; + + idev_btn = devm_input_allocate_device(dev); + if (!idev_btn) { + dev_err(dev, "Failed to allocate buttons input device\n"); + return -ENOMEM; + } + input->idev_btn = idev_btn; + + idev_btn->name = "Flipper One Buttons"; + idev_btn->phys = "flipper-one-input/input0"; + idev_btn->id.bustype = BUS_I2C; + + idev_touch = devm_input_allocate_device(dev); + if (!idev_touch) { + dev_err(dev, "Failed to allocate touch input device\n"); + return -ENOMEM; + } + input->idev_touch = idev_touch; + + idev_touch->name = "Flipper One Touchpad"; + idev_touch->phys = "flipper-one-input/input1"; + idev_touch->id.bustype = BUS_I2C; + + idev_headset = devm_input_allocate_device(dev); + if (!idev_headset) { + dev_err(dev, "Failed to allocate headset input device\n"); + return -ENOMEM; + } + input->idev_headset = idev_headset; + + idev_headset->name = "Flipper One Headset"; + idev_headset->phys = "flipper-one-input/input2"; + idev_headset->id.bustype = BUS_I2C; + + /* Buttons */ + input_set_capability(idev_btn, EV_KEY, KEY_ENTER); /* D-pad center */ + input_set_capability(idev_btn, EV_KEY, KEY_UP); /* D-pad up */ + input_set_capability(idev_btn, EV_KEY, KEY_DOWN); /* D-pad down */ + input_set_capability(idev_btn, EV_KEY, KEY_LEFT); /* D-pad left */ + input_set_capability(idev_btn, EV_KEY, KEY_RIGHT); /* D-pad right */ + input_set_capability(idev_btn, EV_KEY, KEY_TAB); /* App switcher */ + input_set_capability(idev_btn, EV_KEY, KEY_BACKSPACE); /* Back */ + input_set_capability(idev_btn, EV_KEY, KEY_A); /* PTT */ + input_set_capability(idev_btn, EV_KEY, KEY_Z); /* Escape */ + input_set_capability(idev_btn, EV_KEY, KEY_X); /* View */ + input_set_capability(idev_btn, EV_KEY, KEY_C); /* Power */ + input_set_capability(idev_btn, EV_KEY, KEY_V); /* Edit */ + input_set_capability(idev_btn, EV_KEY, KEY_B); /* Run */ + + /* Touchpad */ + input_set_capability(idev_touch, EV_KEY, BTN_TOUCH); + input_set_capability(idev_touch, EV_KEY, BTN_TOOL_FINGER); + input_set_abs_params(idev_touch, ABS_X, 0, 1024, 0, 0); + input_set_abs_params(idev_touch, ABS_Y, 0, 800, 0, 0); + input_set_abs_params(idev_touch, ABS_PRESSURE, 0, 12288, 0, 0); + __set_bit(INPUT_PROP_POINTER, idev_touch->propbit); + + /* Headset */ + input_set_capability(idev_headset, EV_SW, SW_HEADPHONE_INSERT); + input_set_capability(idev_headset, EV_SW, SW_MICROPHONE_INSERT); + input_set_capability(idev_headset, EV_KEY, KEY_PLAYPAUSE); + input_set_capability(idev_headset, EV_KEY, KEY_VOLUMEUP); + input_set_capability(idev_headset, EV_KEY, KEY_VOLUMEDOWN); + input_set_capability(idev_headset, EV_KEY, KEY_VOICECOMMAND); + + device_set_wakeup_capable(dev, true); + device_wakeup_enable(dev); + + for (i = 0; i < ARRAY_SIZE(fo_irqs); i++) { + irq = platform_get_irq_byname(pdev, fo_irqs[i].name); + if (irq < 0) + return dev_err_probe(dev, irq, "Failed to get IRQ %s\n", + fo_irqs[i].name); + + err = devm_request_threaded_irq(dev, irq, NULL, fo_irqs[i].handler, + IRQF_ONESHOT | IRQF_NO_SUSPEND, + fo_irqs[i].name, input); + if (err) + return dev_err_probe(dev, err, "Failed to request IRQ %s\n", + fo_irqs[i].name); + } + + err = input_register_device(idev_btn); + if (err) + return dev_err_probe(dev, err, "Failed to register buttons input device\n"); + + err = input_register_device(idev_touch); + if (err) + return dev_err_probe(dev, err, "Failed to register touch input device\n"); + + err = input_register_device(idev_headset); + if (err) + return dev_err_probe(dev, err, "Failed to register headset input device\n"); + + return 0; +} + +static const struct platform_device_id fo_input_id_table[] = { + { "flipper-one-input", }, + { } +}; +MODULE_DEVICE_TABLE(platform, fo_input_id_table); + +static struct platform_driver fo_input_driver = { + .driver = { + .name = "flipper-one-input", + }, + .probe = fo_input_probe, + .id_table = fo_input_id_table, +}; +module_platform_driver(fo_input_driver); + +MODULE_DESCRIPTION("Flipper One buttons and touchpad driver"); +MODULE_AUTHOR("Alexey Charkov "); +MODULE_LICENSE("GPL"); diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index f86262b11416d1..18d3d68af7cdfd 100644 --- a/drivers/iommu/Kconfig +++ b/drivers/iommu/Kconfig @@ -384,6 +384,17 @@ config SPRD_IOMMU Say Y here if you want to use the multimedia devices listed above. +config VSI_IOMMU + tristate "Verisilicon IOMMU Support" + depends on (ARCH_ROCKCHIP && ARM64) || COMPILE_TEST + select IOMMU_API + help + Support for IOMMUs used by Verisilicon sub-systems like video + decoders or encoder hardware blocks. + + Say Y here if you want to use this IOMMU in front of these + hardware blocks. + config IOMMU_DEBUG_PAGEALLOC bool "Debug IOMMU mappings against page allocations" depends on DEBUG_PAGEALLOC && IOMMU_API && PAGE_EXTENSION diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile index 0275821f4ef985..887af357a7c963 100644 --- a/drivers/iommu/Makefile +++ b/drivers/iommu/Makefile @@ -36,4 +36,5 @@ obj-$(CONFIG_IOMMU_SVA) += iommu-sva.o obj-$(CONFIG_IOMMU_IOPF) += io-pgfault.o obj-$(CONFIG_SPRD_IOMMU) += sprd-iommu.o obj-$(CONFIG_APPLE_DART) += apple-dart.o +obj-$(CONFIG_VSI_IOMMU) += vsi-iommu.o obj-$(CONFIG_IOMMU_DEBUG_PAGEALLOC) += iommu-debug-pagealloc.o diff --git a/drivers/iommu/vsi-iommu.c b/drivers/iommu/vsi-iommu.c new file mode 100644 index 00000000000000..5d0721bd2c7af8 --- /dev/null +++ b/drivers/iommu/vsi-iommu.c @@ -0,0 +1,796 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (C) 2025 Collabora Ltd. + * + * IOMMU API for Verisilicon + * + * Module Authors: Yandong Lin + * Simon Xue + * Benjamin Gaignard + * + * This hardware block is using a 2 pages tables allocation structure. + * That make very similar to Rockhip iommu hardware blocks but it has + * it own driver because the registers offset and configuration bits + * are completely different. An additional reason is that this hardware + * has been developed by Verisilicon to be used by their hardware video + * decoders and not for a general purpose like Rockchip iommus. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "iommu-pages.h" + +struct vsi_iommu { + struct device *dev; + void __iomem *regs; + struct clk_bulk_data *clocks; + int num_clocks; + struct iommu_device iommu; + struct list_head node; /* entry in vsi_iommu_domain.iommus */ + struct iommu_domain *domain; /* domain to which iommu is attached */ + spinlock_t lock; /* lock to protect vsi_iommu fields */ + int irq; + bool enable; +}; + +struct vsi_iommu_domain { + struct list_head iommus; + struct device *dev; + u32 *dt; + dma_addr_t dt_dma; + struct iommu_domain domain; + u64 *pta; + dma_addr_t pta_dma; + spinlock_t lock; /* lock to protect vsi_iommu_domain fields */ +}; + +static struct iommu_domain vsi_identity_domain; + +#define NUM_DT_ENTRIES 1024 +#define NUM_PT_ENTRIES 1024 + +#define SPAGE_SIZE BIT(12) + +/* vsi iommu regs address */ +#define VSI_MMU_CONFIG1_BASE 0x1ac +#define VSI_MMU_AHB_EXCEPTION_BASE 0x380 +#define VSI_MMU_AHB_CONTROL_BASE 0x388 +#define VSI_MMU_AHB_TLB_ARRAY_BASE_L_BASE 0x38C + +/* MMU register offsets */ +#define VSI_MMU_FLUSH_BASE 0x184 +#define VSI_MMU_BIT_FLUSH BIT(4) + +#define VSI_MMU_PAGE_FAULT_ADDR 0x380 +#define VSI_MMU_STATUS_BASE 0x384 /* IRQ status */ + +#define VSI_MMU_BIT_ENABLE BIT(0) + +#define VSI_MMU_OUT_OF_BOUND BIT(28) +/* Irq mask */ +#define VSI_MMU_IRQ_MASK 0x7 + +#define VSI_DTE_PT_ADDRESS_MASK 0xffffffc0 +#define VSI_DTE_PT_VALID BIT(0) + +#define VSI_PAGE_DESC_LO_MASK 0xfffff000 +#define VSI_PAGE_DESC_HI_MASK GENMASK_ULL(39, 32) +#define VSI_PAGE_DESC_HI_SHIFT (32 - 4) + +static inline phys_addr_t vsi_dte_pt_address(u32 dte) +{ + return (phys_addr_t)dte & VSI_DTE_PT_ADDRESS_MASK; +} + +static inline u32 vsi_mk_dte(u32 dte) +{ + return (phys_addr_t)dte | VSI_DTE_PT_VALID; +} + +#define VSI_PTE_PAGE_WRITABLE BIT(2) +#define VSI_PTE_PAGE_VALID BIT(0) + +static inline phys_addr_t vsi_pte_page_address(u64 pte) +{ + return ((pte << VSI_PAGE_DESC_HI_SHIFT) & VSI_PAGE_DESC_HI_MASK) | + (pte & VSI_PAGE_DESC_LO_MASK); +} + +static u32 vsi_mk_pte(phys_addr_t page, int prot) +{ + u32 flags = 0; + + flags |= (prot & IOMMU_WRITE) ? VSI_PTE_PAGE_WRITABLE : 0; + + page = (page & VSI_PAGE_DESC_LO_MASK) | + ((page & VSI_PAGE_DESC_HI_MASK) >> VSI_PAGE_DESC_HI_SHIFT); + + return page | flags | VSI_PTE_PAGE_VALID; +} + +#define VSI_DTE_PT_VALID BIT(0) + +static inline bool vsi_dte_is_pt_valid(u32 dte) +{ + return dte & VSI_DTE_PT_VALID; +} + +static inline bool vsi_pte_is_page_valid(u32 pte) +{ + return pte & VSI_PTE_PAGE_VALID; +} + +static u32 vsi_mk_pte_invalid(u32 pte) +{ + return pte & ~VSI_PTE_PAGE_VALID; +} + +#define VSI_MASTER_TLB_MASK GENMASK_ULL(31, 10) +/* mode 0 : 4k */ +#define VSI_PTA_4K_MODE 0 + +static u64 vsi_mk_pta(dma_addr_t dt_dma) +{ + u64 val = (dt_dma & VSI_MASTER_TLB_MASK) | VSI_PTA_4K_MODE; + + return val; +} + +static struct vsi_iommu_domain *to_vsi_domain(struct iommu_domain *dom) +{ + return container_of(dom, struct vsi_iommu_domain, domain); +} + +static inline void vsi_table_flush(struct vsi_iommu_domain *vsi_domain, dma_addr_t dma, + unsigned int count) +{ + size_t size = count * sizeof(u32); /* count of u32 entry */ + + dma_sync_single_for_device(vsi_domain->dev, dma, size, DMA_TO_DEVICE); +} + +#define VSI_IOVA_DTE_MASK 0xffc00000 +#define VSI_IOVA_DTE_SHIFT 22 +#define VSI_IOVA_PTE_MASK 0x003ff000 +#define VSI_IOVA_PTE_SHIFT 12 +#define VSI_IOVA_PAGE_MASK 0x00000fff +#define VSI_IOVA_PAGE_SHIFT 0 + +static u32 vsi_iova_dte_index(u32 iova) +{ + return (iova & VSI_IOVA_DTE_MASK) >> VSI_IOVA_DTE_SHIFT; +} + +static u32 vsi_iova_pte_index(u32 iova) +{ + return (iova & VSI_IOVA_PTE_MASK) >> VSI_IOVA_PTE_SHIFT; +} + +static u32 vsi_iova_page_offset(u32 iova) +{ + return (iova & VSI_IOVA_PAGE_MASK) >> VSI_IOVA_PAGE_SHIFT; +} + +static irqreturn_t vsi_iommu_irq(int irq, void *dev_id) +{ + struct vsi_iommu *iommu = dev_id; + unsigned long flags; + dma_addr_t iova; + u32 status; + + if (pm_runtime_resume_and_get(iommu->dev) < 0) + return IRQ_NONE; + + spin_lock_irqsave(&iommu->lock, flags); + + status = readl(iommu->regs + VSI_MMU_STATUS_BASE); + if (status & VSI_MMU_IRQ_MASK) { + dev_err(iommu->dev, "unexpected int_status=%08x\n", status); + iova = readl(iommu->regs + VSI_MMU_PAGE_FAULT_ADDR); + report_iommu_fault(iommu->domain, iommu->dev, iova, status); + } + writel(0, iommu->regs + VSI_MMU_STATUS_BASE); + + spin_unlock_irqrestore(&iommu->lock, flags); + pm_runtime_put_autosuspend(iommu->dev); + + return IRQ_HANDLED; +} + +static struct vsi_iommu *vsi_iommu_get_from_dev(struct device *dev) +{ + struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev); + struct device *iommu_dev = bus_find_device_by_fwnode(&platform_bus_type, + fwspec->iommu_fwnode); + + put_device(iommu_dev); + + return iommu_dev ? dev_get_drvdata(iommu_dev) : NULL; +} + +static struct iommu_domain *vsi_iommu_domain_alloc_paging(struct device *dev) +{ + struct vsi_iommu *iommu = dev_iommu_priv_get(dev); + struct vsi_iommu_domain *vsi_domain; + + vsi_domain = kzalloc(sizeof(*vsi_domain), GFP_KERNEL); + if (!vsi_domain) + return NULL; + + vsi_domain->dev = iommu->dev; + spin_lock_init(&vsi_domain->lock); + + /* + * iommu use a 2 level pagetable. + * Each level1 (dt) and level2 (pt) table has 1024 4-byte entries. + * Allocate one 4 KiB page for each table. + */ + vsi_domain->dt = iommu_alloc_pages_sz(GFP_KERNEL | GFP_DMA32, + SPAGE_SIZE); + if (!vsi_domain->dt) + goto err_free_domain; + + vsi_domain->dt_dma = dma_map_single(vsi_domain->dev, vsi_domain->dt, + SPAGE_SIZE, DMA_TO_DEVICE); + if (dma_mapping_error(vsi_domain->dev, vsi_domain->dt_dma)) { + dev_err(dev, "DMA map error for DT\n"); + goto err_free_dt; + } + + vsi_domain->pta = iommu_alloc_pages_sz(GFP_KERNEL | GFP_DMA32, + SPAGE_SIZE); + if (!vsi_domain->pta) + goto err_unmap_dt; + + vsi_domain->pta[0] = vsi_mk_pta(vsi_domain->dt_dma); + vsi_domain->pta_dma = dma_map_single(vsi_domain->dev, vsi_domain->pta, + SPAGE_SIZE, DMA_TO_DEVICE); + if (dma_mapping_error(vsi_domain->dev, vsi_domain->pta_dma)) { + dev_err(dev, "DMA map error for PTA\n"); + goto err_free_pta; + } + + INIT_LIST_HEAD(&vsi_domain->iommus); + + vsi_domain->domain.geometry.aperture_start = 0; + vsi_domain->domain.geometry.aperture_end = DMA_BIT_MASK(32); + vsi_domain->domain.geometry.force_aperture = true; + vsi_domain->domain.pgsize_bitmap = SZ_4K; + + return &vsi_domain->domain; + +err_free_pta: + iommu_free_pages(vsi_domain->pta); +err_unmap_dt: + dma_unmap_single(vsi_domain->dev, vsi_domain->dt_dma, + SPAGE_SIZE, DMA_TO_DEVICE); +err_free_dt: + iommu_free_pages(vsi_domain->dt); +err_free_domain: + kfree(vsi_domain); + + return NULL; +} + +static phys_addr_t vsi_iommu_iova_to_phys(struct iommu_domain *domain, + dma_addr_t iova) +{ + struct vsi_iommu_domain *vsi_domain = to_vsi_domain(domain); + phys_addr_t pt_phys, phys = 0; + unsigned long flags; + u32 dte, pte; + u32 *page_table; + + spin_lock_irqsave(&vsi_domain->lock, flags); + dte = vsi_domain->dt[vsi_iova_dte_index(iova)]; + if (!vsi_dte_is_pt_valid(dte)) + goto unlock; + + pt_phys = vsi_dte_pt_address(dte); + page_table = (u32 *)phys_to_virt(pt_phys); + pte = page_table[vsi_iova_pte_index(iova)]; + if (!vsi_pte_is_page_valid(pte)) + goto unlock; + + phys = vsi_pte_page_address(pte) + vsi_iova_page_offset(iova); + +unlock: + spin_unlock_irqrestore(&vsi_domain->lock, flags); + return phys; +} + +static size_t vsi_iommu_unmap_iova(struct vsi_iommu_domain *vsi_domain, + u32 *pte_addr, dma_addr_t pte_dma, + size_t size) +{ + unsigned int pte_count; + unsigned int pte_total = size / SPAGE_SIZE; + + for (pte_count = 0; + pte_count < pte_total && pte_count < NUM_PT_ENTRIES; pte_count++) { + u32 pte = pte_addr[pte_count]; + + if (!vsi_pte_is_page_valid(pte)) + break; + + pte_addr[pte_count] = vsi_mk_pte_invalid(pte); + } + + vsi_table_flush(vsi_domain, pte_dma, pte_total); + + return pte_count * SPAGE_SIZE; +} + +static int vsi_iommu_map_iova(struct vsi_iommu_domain *vsi_domain, u32 *pte_addr, + dma_addr_t pte_dma, dma_addr_t iova, + phys_addr_t paddr, size_t size, int prot) +{ + unsigned int pte_count; + unsigned int pte_total = size / SPAGE_SIZE; + + for (pte_count = 0; + pte_count < pte_total && pte_count < NUM_PT_ENTRIES; pte_count++) { + u32 pte = pte_addr[pte_count]; + + if (vsi_pte_is_page_valid(pte)) + return (pte_count - 1) * SPAGE_SIZE; + + pte_addr[pte_count] = vsi_mk_pte(paddr, prot); + + paddr += SPAGE_SIZE; + } + + vsi_table_flush(vsi_domain, pte_dma, pte_total); + + return 0; +} + +static void vsi_iommu_flush_tlb(struct iommu_domain *domain) +{ + struct vsi_iommu_domain *vsi_domain = to_vsi_domain(domain); + struct list_head *pos; + + list_for_each(pos, &vsi_domain->iommus) { + struct vsi_iommu *iommu; + + iommu = list_entry(pos, struct vsi_iommu, node); + if (!iommu) + continue; + + if (pm_runtime_get(iommu->dev) < 0) + continue; + + spin_lock(&iommu->lock); + + if (iommu->enable) { + writel(VSI_MMU_BIT_FLUSH, iommu->regs + VSI_MMU_FLUSH_BASE); + writel(0, iommu->regs + VSI_MMU_FLUSH_BASE); + } + + spin_unlock(&iommu->lock); + + pm_runtime_put_autosuspend(iommu->dev); + } +} + +static size_t vsi_iommu_unmap(struct iommu_domain *domain, unsigned long _iova, + size_t size, size_t count, struct iommu_iotlb_gather *gather) +{ + struct vsi_iommu_domain *vsi_domain = to_vsi_domain(domain); + dma_addr_t pte_dma, iova = (dma_addr_t)_iova; + unsigned long flags; + phys_addr_t pt_phys; + u32 dte; + u32 *pte_addr; + size_t unmap_size = 0; + + spin_lock_irqsave(&vsi_domain->lock, flags); + + dte = vsi_domain->dt[vsi_iova_dte_index(iova)]; + /* Just return 0 if iova is unmapped */ + if (!vsi_dte_is_pt_valid(dte)) + goto unlock; + + pt_phys = vsi_dte_pt_address(dte); + pte_addr = (u32 *)phys_to_virt(pt_phys) + vsi_iova_pte_index(iova); + pte_dma = pt_phys + vsi_iova_pte_index(iova) * sizeof(u32); + unmap_size = vsi_iommu_unmap_iova(vsi_domain, pte_addr, pte_dma, size); + if (!unmap_size) + goto unlock; + + vsi_iommu_flush_tlb(domain); +unlock: + spin_unlock_irqrestore(&vsi_domain->lock, flags); + + return unmap_size; +} + +static u32 *vsi_dte_get_page_table(struct vsi_iommu_domain *vsi_domain, + dma_addr_t iova, gfp_t gfp) +{ + u32 *page_table, *dte_addr; + u32 dte_index, dte; + phys_addr_t pt_phys; + dma_addr_t pt_dma; + gfp_t flags; + + dte_index = vsi_iova_dte_index(iova); + dte_addr = &vsi_domain->dt[dte_index]; + dte = *dte_addr; + if (vsi_dte_is_pt_valid(dte)) + goto done; + + /* Do not allow to sleep while allocating the buffer */ + flags = (gfp & ~GFP_KERNEL) | GFP_ATOMIC | GFP_DMA32; + page_table = iommu_alloc_pages_sz(flags, PAGE_SIZE); + if (!page_table) + return ERR_PTR(-ENOMEM); + + pt_dma = dma_map_single(vsi_domain->dev, page_table, PAGE_SIZE, DMA_TO_DEVICE); + if (dma_mapping_error(vsi_domain->dev, pt_dma)) { + dev_err(vsi_domain->dev, "DMA mapping error while allocating page table\n"); + iommu_free_pages(page_table); + return ERR_PTR(-ENOMEM); + } + + dte = vsi_mk_dte(pt_dma); + *dte_addr = dte; + + vsi_table_flush(vsi_domain, + vsi_domain->dt_dma + dte_index * sizeof(u32), 1); +done: + pt_phys = vsi_dte_pt_address(dte); + return (u32 *)phys_to_virt(pt_phys); +} + +static int vsi_iommu_map(struct iommu_domain *domain, unsigned long _iova, + phys_addr_t paddr, size_t size, size_t count, + int prot, gfp_t gfp, size_t *mapped) +{ + struct vsi_iommu_domain *vsi_domain = to_vsi_domain(domain); + dma_addr_t pte_dma, iova = (dma_addr_t)_iova; + u32 *page_table, *pte_addr; + u32 dte, pte_index; + unsigned long flags; + int ret; + + spin_lock_irqsave(&vsi_domain->lock, flags); + + page_table = vsi_dte_get_page_table(vsi_domain, iova, gfp); + if (IS_ERR(page_table)) { + spin_unlock_irqrestore(&vsi_domain->lock, flags); + return PTR_ERR(page_table); + } + + dte = vsi_domain->dt[vsi_iova_dte_index(iova)]; + pte_index = vsi_iova_pte_index(iova); + pte_addr = &page_table[pte_index]; + pte_dma = vsi_dte_pt_address(dte) + pte_index * sizeof(u32); + ret = vsi_iommu_map_iova(vsi_domain, pte_addr, pte_dma, iova, + paddr, size, prot); + if (!ret) + *mapped = size; + + vsi_iommu_flush_tlb(domain); + + spin_unlock_irqrestore(&vsi_domain->lock, flags); + + return ret; +} + +static void vsi_iommu_disable(struct vsi_iommu *iommu) +{ + writel(0, iommu->regs + VSI_MMU_AHB_CONTROL_BASE); + iommu->enable = false; +} + +static int vsi_iommu_identity_attach(struct iommu_domain *domain, + struct device *dev, struct iommu_domain *old) +{ + struct vsi_iommu *iommu = dev_iommu_priv_get(dev); + struct vsi_iommu_domain *vsi_domain = to_vsi_domain(domain); + unsigned long flags; + int ret; + + ret = pm_runtime_resume_and_get(iommu->dev); + if (ret < 0) + return ret; + + spin_lock_irqsave(&vsi_domain->lock, flags); + spin_lock(&iommu->lock); + if (iommu->domain == domain) + goto unlock; + + vsi_iommu_disable(iommu); + list_del_init(&iommu->node); + + iommu->domain = domain; + +unlock: + spin_unlock(&iommu->lock); + spin_unlock_irqrestore(&vsi_domain->lock, flags); + pm_runtime_put_autosuspend(iommu->dev); + return 0; +} + +static const struct iommu_domain_ops vsi_identity_ops = { + .attach_dev = vsi_iommu_identity_attach, +}; + +static struct iommu_domain vsi_identity_domain = { + .type = IOMMU_DOMAIN_IDENTITY, + .ops = &vsi_identity_ops, +}; + +static void vsi_iommu_enable(struct vsi_iommu *iommu, struct iommu_domain *domain) +{ + struct vsi_iommu_domain *vsi_domain = to_vsi_domain(domain); + + if (domain == &vsi_identity_domain) + return; + + writel(vsi_domain->pta_dma, iommu->regs + VSI_MMU_AHB_TLB_ARRAY_BASE_L_BASE); + writel(VSI_MMU_OUT_OF_BOUND, iommu->regs + VSI_MMU_CONFIG1_BASE); + writel(VSI_MMU_BIT_ENABLE, iommu->regs + VSI_MMU_AHB_EXCEPTION_BASE); + writel(VSI_MMU_BIT_ENABLE, iommu->regs + VSI_MMU_AHB_CONTROL_BASE); + iommu->enable = true; +} + +static int vsi_iommu_attach_device(struct iommu_domain *domain, + struct device *dev, struct iommu_domain *old) +{ + struct vsi_iommu *iommu = dev_iommu_priv_get(dev); + struct vsi_iommu_domain *vsi_domain = to_vsi_domain(domain); + unsigned long flags; + int ret = 0; + + ret = pm_runtime_resume_and_get(iommu->dev); + if (ret < 0) + return ret; + + spin_lock_irqsave(&vsi_domain->lock, flags); + spin_lock(&iommu->lock); + + vsi_iommu_enable(iommu, domain); + writel(VSI_MMU_BIT_FLUSH, iommu->regs + VSI_MMU_FLUSH_BASE); + writel(0, iommu->regs + VSI_MMU_FLUSH_BASE); + + list_del_init(&iommu->node); + list_add_tail(&iommu->node, &vsi_domain->iommus); + + iommu->domain = domain; + + spin_unlock(&iommu->lock); + spin_unlock_irqrestore(&vsi_domain->lock, flags); + pm_runtime_put_autosuspend(iommu->dev); + return ret; +} + +static void vsi_iommu_domain_free(struct iommu_domain *domain) +{ + struct vsi_iommu_domain *vsi_domain = to_vsi_domain(domain); + unsigned long flags; + int i; + + spin_lock_irqsave(&vsi_domain->lock, flags); + + WARN_ON(!list_empty(&vsi_domain->iommus)); + + for (i = 0; i < NUM_DT_ENTRIES; i++) { + u32 dte = vsi_domain->dt[i]; + + if (vsi_dte_is_pt_valid(dte)) { + phys_addr_t pt_phys = vsi_dte_pt_address(dte); + u32 *page_table = phys_to_virt(pt_phys); + + dma_unmap_single(vsi_domain->dev, pt_phys, + SPAGE_SIZE, DMA_TO_DEVICE); + iommu_free_pages(page_table); + } + } + + dma_unmap_single(vsi_domain->dev, vsi_domain->dt_dma, + SPAGE_SIZE, DMA_TO_DEVICE); + iommu_free_pages(vsi_domain->dt); + + dma_unmap_single(vsi_domain->dev, vsi_domain->pta_dma, + SPAGE_SIZE, DMA_TO_DEVICE); + iommu_free_pages(vsi_domain->pta); + + spin_unlock_irqrestore(&vsi_domain->lock, flags); + + kfree(vsi_domain); +} + +static struct iommu_device *vsi_iommu_probe_device(struct device *dev) +{ + struct vsi_iommu *iommu = vsi_iommu_get_from_dev(dev); + struct device_link *link; + + link = device_link_add(dev, iommu->dev, + DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME); + if (!link) + dev_err(dev, "Unable to link %s\n", dev_name(iommu->dev)); + + dev_iommu_priv_set(dev, iommu); + return &iommu->iommu; +} + +static void vsi_iommu_release_device(struct device *dev) +{ + struct vsi_iommu *iommu = dev_iommu_priv_get(dev); + + device_link_remove(dev, iommu->dev); +} + +static int vsi_iommu_of_xlate(struct device *dev, const struct of_phandle_args *args) +{ + return iommu_fwspec_add_ids(dev, args->args, 1); +} + +static const struct iommu_ops vsi_iommu_ops = { + .identity_domain = &vsi_identity_domain, + .release_domain = &vsi_identity_domain, + .domain_alloc_paging = vsi_iommu_domain_alloc_paging, + .of_xlate = vsi_iommu_of_xlate, + .probe_device = vsi_iommu_probe_device, + .release_device = vsi_iommu_release_device, + .device_group = generic_single_device_group, + .owner = THIS_MODULE, + .default_domain_ops = &(const struct iommu_domain_ops) { + .attach_dev = vsi_iommu_attach_device, + .map_pages = vsi_iommu_map, + .unmap_pages = vsi_iommu_unmap, + .iova_to_phys = vsi_iommu_iova_to_phys, + .free = vsi_iommu_domain_free, + } +}; + +static const struct of_device_id vsi_iommu_dt_ids[] = { + { + .compatible = "verisilicon,iommu-1.2", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, vsi_iommu_dt_ids); + +static int vsi_iommu_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct vsi_iommu *iommu; + int err; + + iommu = devm_kzalloc(dev, sizeof(*iommu), GFP_KERNEL); + if (!iommu) + return -ENOMEM; + + iommu->dev = dev; + spin_lock_init(&iommu->lock); + INIT_LIST_HEAD(&iommu->node); + + iommu->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(iommu->regs)) + return -ENOMEM; + + iommu->num_clocks = devm_clk_bulk_get_all(dev, &iommu->clocks); + if (iommu->num_clocks < 0) + return iommu->num_clocks; + + err = clk_bulk_prepare(iommu->num_clocks, iommu->clocks); + if (err) + return err; + + iommu->irq = platform_get_irq(pdev, 0); + if (iommu->irq < 0) + return iommu->irq; + + err = devm_request_irq(iommu->dev, iommu->irq, vsi_iommu_irq, + IRQF_SHARED, dev_name(dev), iommu); + if (err) + goto err_unprepare_clocks; + + dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + platform_set_drvdata(pdev, iommu); + + pm_runtime_set_autosuspend_delay(dev, 100); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + + err = iommu_device_sysfs_add(&iommu->iommu, dev, NULL, dev_name(dev)); + if (err) + goto err_runtime_disable; + + err = iommu_device_register(&iommu->iommu, &vsi_iommu_ops, dev); + if (err) + goto err_remove_sysfs; + + return 0; + +err_remove_sysfs: + iommu_device_sysfs_remove(&iommu->iommu); +err_runtime_disable: + pm_runtime_disable(dev); +err_unprepare_clocks: + clk_bulk_unprepare(iommu->num_clocks, iommu->clocks); + return err; +} + +static void vsi_iommu_shutdown(struct platform_device *pdev) +{ + struct vsi_iommu *iommu = platform_get_drvdata(pdev); + + disable_irq(iommu->irq); + pm_runtime_force_suspend(&pdev->dev); +} + +static int __maybe_unused vsi_iommu_suspend(struct device *dev) +{ + struct vsi_iommu *iommu = dev_get_drvdata(dev); + + vsi_iommu_disable(iommu); + + clk_bulk_disable(iommu->num_clocks, iommu->clocks); + + return 0; +} + +static int __maybe_unused vsi_iommu_resume(struct device *dev) +{ + struct vsi_iommu *iommu = dev_get_drvdata(dev); + unsigned long flags; + int ret; + + ret = clk_bulk_enable(iommu->num_clocks, iommu->clocks); + if (ret) + return ret; + + if (iommu->domain) { + struct vsi_iommu_domain *vsi_domain = to_vsi_domain(iommu->domain); + + spin_lock_irqsave(&vsi_domain->lock, flags); + spin_lock(&iommu->lock); + vsi_iommu_enable(iommu, iommu->domain); + spin_unlock(&iommu->lock); + spin_unlock_irqrestore(&vsi_domain->lock, flags); + } + + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(vsi_iommu_pm_ops, + vsi_iommu_suspend, vsi_iommu_resume, + NULL); + +static struct platform_driver rockchip_vsi_iommu_driver = { + .probe = vsi_iommu_probe, + .shutdown = vsi_iommu_shutdown, + .driver = { + .name = "vsi_iommu", + .of_match_table = vsi_iommu_dt_ids, + .pm = pm_sleep_ptr(&vsi_iommu_pm_ops), + .suppress_bind_attrs = true, + }, +}; +module_platform_driver(rockchip_vsi_iommu_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Benjamin Gaignard "); +MODULE_DESCRIPTION("Verisilicon IOMMU driver"); diff --git a/drivers/leds/rgb/Kconfig b/drivers/leds/rgb/Kconfig index 28ef4c487367cf..3e83dea254a72e 100644 --- a/drivers/leds/rgb/Kconfig +++ b/drivers/leds/rgb/Kconfig @@ -14,6 +14,17 @@ config LEDS_GROUP_MULTICOLOR To compile this driver as a module, choose M here: the module will be called leds-group-multicolor. +config LEDS_FLIPPER_ONE + tristate "LED support for Flipper One" + depends on MFD_FLIPPER_ONE + help + This option enables support for the four RGB link LEDs on the + Flipper One networking multitool. Each LED is driven via a packed + RGB565 value written to the Flipper One MCU over I2C. + + To compile this driver as a module, choose M here: the module + will be called leds-flipper-one. + config LEDS_KTD202X tristate "LED support for KTD202x Chips" depends on I2C diff --git a/drivers/leds/rgb/Makefile b/drivers/leds/rgb/Makefile index be45991f63f50d..234682f96ba2fb 100644 --- a/drivers/leds/rgb/Makefile +++ b/drivers/leds/rgb/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_LEDS_GROUP_MULTICOLOR) += leds-group-multicolor.o +obj-$(CONFIG_LEDS_FLIPPER_ONE) += leds-flipper-one.o obj-$(CONFIG_LEDS_KTD202X) += leds-ktd202x.o obj-$(CONFIG_LEDS_LP5812) += leds-lp5812.o obj-$(CONFIG_LEDS_NCP5623) += leds-ncp5623.o diff --git a/drivers/leds/rgb/leds-flipper-one.c b/drivers/leds/rgb/leds-flipper-one.c new file mode 100644 index 00000000000000..837513486c61bd --- /dev/null +++ b/drivers/leds/rgb/leds-flipper-one.c @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Flipper One LED driver + * Copyright (C) 2026 Flipper FZCO + */ + +#include +#include +#include +#include +#include +#include + +#define FOLED_NUM_LEDS 4 +#define FOLED_NUM_COLORS 3 + +/* RGB565 component shifts and max values */ +#define FOLED_R_SHIFT 11 +#define FOLED_G_SHIFT 5 +#define FOLED_B_SHIFT 0 +#define FOLED_R_MAX 31 +#define FOLED_G_MAX 63 +#define FOLED_B_MAX 31 + +struct foled_device; + +struct foled_led { + struct foled_device *foled; + struct led_classdev_mc mc_cdev; + struct mc_subled subleds[FOLED_NUM_COLORS]; + unsigned int reg; +}; + +struct foled_device { + struct regmap *regmap; + struct foled_led leds[FOLED_NUM_LEDS]; +}; + +static const unsigned int foled_regs[FOLED_NUM_LEDS] = { + FOMCU_REG_LEDS_COLOR_LINK1, + FOMCU_REG_LEDS_COLOR_LINK2, + FOMCU_REG_LEDS_COLOR_LINK3, + FOMCU_REG_LEDS_COLOR_LINK4, +}; + +static const char * const foled_led_names[FOLED_NUM_LEDS] = { + "flipper-one:rgb:link", + "flipper-one:rgb:wifi", + "flipper-one:rgb:eth1", + "flipper-one:rgb:eth0", +}; + +static int foled_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); + struct foled_led *led = container_of(mc_cdev, struct foled_led, mc_cdev); + u16 r, g, b, rgb565; + + led_mc_calc_color_components(mc_cdev, brightness); + + r = (u16)mc_cdev->subled_info[0].brightness * FOLED_R_MAX / LED_FULL; + g = (u16)mc_cdev->subled_info[1].brightness * FOLED_G_MAX / LED_FULL; + b = (u16)mc_cdev->subled_info[2].brightness * FOLED_B_MAX / LED_FULL; + + rgb565 = (r << FOLED_R_SHIFT) | (g << FOLED_G_SHIFT) | (b << FOLED_B_SHIFT); + + return regmap_write(led->foled->regmap, led->reg, rgb565); +} + +static int foled_probe(struct platform_device *pdev) +{ + struct foled_device *foled; + struct foled_led *led; + struct led_classdev *cdev; + int i, ret; + + foled = devm_kzalloc(&pdev->dev, sizeof(*foled), GFP_KERNEL); + if (!foled) + return -ENOMEM; + + foled->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!foled->regmap) + return dev_err_probe(&pdev->dev, -ENODEV, + "Failed to get parent regmap\n"); + + for (i = 0; i < FOLED_NUM_LEDS; i++) { + led = &foled->leds[i]; + led->foled = foled; + led->reg = foled_regs[i]; + + led->subleds[0].color_index = LED_COLOR_ID_RED; + led->subleds[1].color_index = LED_COLOR_ID_GREEN; + led->subleds[2].color_index = LED_COLOR_ID_BLUE; + + led->mc_cdev.subled_info = led->subleds; + led->mc_cdev.num_colors = FOLED_NUM_COLORS; + + cdev = &led->mc_cdev.led_cdev; + cdev->name = foled_led_names[i]; + cdev->max_brightness = LED_FULL; + cdev->brightness_set_blocking = foled_brightness_set; + cdev->flags = LED_CORE_SUSPENDRESUME; + + ret = devm_led_classdev_multicolor_register(&pdev->dev, + &led->mc_cdev); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to register LED %d\n", i + 1); + } + + platform_set_drvdata(pdev, foled); + return 0; +} + +static const struct platform_device_id foled_id_table[] = { + { "flipper-one-leds" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, foled_id_table); + +static struct platform_driver foled_driver = { + .probe = foled_probe, + .driver = { + .name = "flipper-one-leds", + }, + .id_table = foled_id_table, +}; +module_platform_driver(foled_driver); + +MODULE_DESCRIPTION("Flipper One LED driver"); +MODULE_AUTHOR("Alexey Charkov "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c index 9e67160a16e468..b255a744312bd7 100644 --- a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c +++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c @@ -30,10 +30,18 @@ #define RK3568_MIPI_CTRL0_CROP_EN BIT(5) #define RK3568_MIPI_CTRL0_WRDDR(type) ((type) << 1) +#define RK3588_MIPI_CTRL0_DMA_EN BIT(28) +#define RK3588_MIPI_CTRL0_HIGH_ALIGN BIT(27) +#define RK3588_MIPI_CTRL0_WRDDR(type) ((type) << 5) +#define RK3588_MIPI_CTRL0_CROP_EN BIT(4) +#define RK3588_MIPI_CTRL0_PARSE(type) ((type) << 1) + #define RKCIF_MIPI_CTRL0_DT_ID(id) ((id) << 10) #define RKCIF_MIPI_CTRL0_VC_ID(id) ((id) << 8) #define RKCIF_MIPI_CTRL0_CAP_EN BIT(0) +#define RKCIF_MIPI_CTRL_CAP_EN BIT(0) + #define RKCIF_MIPI_INT_FRAME0_END(id) BIT(8 + (id) * 2 + 0) #define RKCIF_MIPI_INT_FRAME1_END(id) BIT(8 + (id) * 2 + 1) @@ -481,6 +489,213 @@ const struct rkcif_mipi_match_data rkcif_rk3568_vicap_mipi_match_data = { }, }; +static u32 +rkcif_rk3588_mipi_ctrl0(struct rkcif_stream *stream, + const struct rkcif_output_fmt *active_out_fmt) +{ + u32 ctrl0 = 0; + + ctrl0 |= RK3588_MIPI_CTRL0_DMA_EN; + ctrl0 |= RKCIF_MIPI_CTRL0_DT_ID(active_out_fmt->mipi.dt); + ctrl0 |= RK3588_MIPI_CTRL0_CROP_EN; + ctrl0 |= RKCIF_MIPI_CTRL0_CAP_EN; + + switch (active_out_fmt->mipi.type) { + case RKCIF_MIPI_TYPE_RAW8: + break; + case RKCIF_MIPI_TYPE_RAW10: + ctrl0 |= RK3588_MIPI_CTRL0_PARSE(0x1); + if (!active_out_fmt->mipi.compact) + ctrl0 |= RK3588_MIPI_CTRL0_WRDDR(0x1); + break; + case RKCIF_MIPI_TYPE_RAW12: + ctrl0 |= RK3588_MIPI_CTRL0_PARSE(0x2); + if (!active_out_fmt->mipi.compact) + ctrl0 |= RK3588_MIPI_CTRL0_WRDDR(0x1); + break; + case RKCIF_MIPI_TYPE_RGB888: + break; + case RKCIF_MIPI_TYPE_YUV422SP: + ctrl0 |= RK3588_MIPI_CTRL0_WRDDR(0x4); + break; + case RKCIF_MIPI_TYPE_YUV420SP: + ctrl0 |= RK3588_MIPI_CTRL0_WRDDR(0x5); + break; + case RKCIF_MIPI_TYPE_YUV400: + ctrl0 |= RK3588_MIPI_CTRL0_WRDDR(0x3); + break; + default: + break; + } + + return ctrl0; +} + +const struct rkcif_mipi_match_data rkcif_rk3576_vicap_mipi_match_data = { + .mipi_num = 5, + .mipi_ctrl0 = rkcif_rk3588_mipi_ctrl0, + .regs = { + [RKCIF_MIPI_CTRL] = 0x20, + [RKCIF_MIPI_INTEN] = 0x74, + [RKCIF_MIPI_INTSTAT] = 0x78, + }, + .regs_id = { + [RKCIF_ID0] = { + [RKCIF_MIPI_CTRL0] = 0x00, + [RKCIF_MIPI_CTRL1] = 0x04, + [RKCIF_MIPI_FRAME0_ADDR_Y] = 0x24, + [RKCIF_MIPI_FRAME0_ADDR_UV] = 0x2c, + [RKCIF_MIPI_FRAME0_VLW_Y] = 0x34, + [RKCIF_MIPI_FRAME0_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_ADDR_Y] = 0x28, + [RKCIF_MIPI_FRAME1_ADDR_UV] = 0x30, + [RKCIF_MIPI_FRAME1_VLW_Y] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_CROP_START] = 0x8c, + }, + [RKCIF_ID1] = { + [RKCIF_MIPI_CTRL0] = 0x08, + [RKCIF_MIPI_CTRL1] = 0x0c, + [RKCIF_MIPI_FRAME0_ADDR_Y] = 0x38, + [RKCIF_MIPI_FRAME0_ADDR_UV] = 0x40, + [RKCIF_MIPI_FRAME0_VLW_Y] = 0x48, + [RKCIF_MIPI_FRAME0_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_ADDR_Y] = 0x3c, + [RKCIF_MIPI_FRAME1_ADDR_UV] = 0x44, + [RKCIF_MIPI_FRAME1_VLW_Y] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_CROP_START] = 0x90, + }, + [RKCIF_ID2] = { + [RKCIF_MIPI_CTRL0] = 0x10, + [RKCIF_MIPI_CTRL1] = 0x14, + [RKCIF_MIPI_FRAME0_ADDR_Y] = 0x4c, + [RKCIF_MIPI_FRAME0_ADDR_UV] = 0x54, + [RKCIF_MIPI_FRAME0_VLW_Y] = 0x5c, + [RKCIF_MIPI_FRAME0_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_ADDR_Y] = 0x50, + [RKCIF_MIPI_FRAME1_ADDR_UV] = 0x58, + [RKCIF_MIPI_FRAME1_VLW_Y] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_CROP_START] = 0x94, + }, + [RKCIF_ID3] = { + [RKCIF_MIPI_CTRL0] = 0x18, + [RKCIF_MIPI_CTRL1] = 0x1c, + [RKCIF_MIPI_FRAME0_ADDR_Y] = 0x60, + [RKCIF_MIPI_FRAME0_ADDR_UV] = 0x68, + [RKCIF_MIPI_FRAME0_VLW_Y] = 0x70, + [RKCIF_MIPI_FRAME0_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_ADDR_Y] = 0x64, + [RKCIF_MIPI_FRAME1_ADDR_UV] = 0x6c, + [RKCIF_MIPI_FRAME1_VLW_Y] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_CROP_START] = 0x98, + }, + }, + .blocks = { + { + .offset = 0x100, + }, + { + .offset = 0x200, + }, + { + .offset = 0x300, + }, + { + .offset = 0x400, + }, + { + .offset = 0x500, + }, + }, +}; + +const struct rkcif_mipi_match_data rkcif_rk3588_vicap_mipi_match_data = { + .mipi_num = 6, + .mipi_ctrl0 = rkcif_rk3588_mipi_ctrl0, + .regs = { + [RKCIF_MIPI_CTRL] = 0x20, + [RKCIF_MIPI_INTEN] = 0x74, + [RKCIF_MIPI_INTSTAT] = 0x78, + }, + .regs_id = { + [RKCIF_ID0] = { + [RKCIF_MIPI_CTRL0] = 0x00, + [RKCIF_MIPI_CTRL1] = 0x04, + [RKCIF_MIPI_FRAME0_ADDR_Y] = 0x24, + [RKCIF_MIPI_FRAME0_ADDR_UV] = 0x2c, + [RKCIF_MIPI_FRAME0_VLW_Y] = 0x34, + [RKCIF_MIPI_FRAME0_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_ADDR_Y] = 0x28, + [RKCIF_MIPI_FRAME1_ADDR_UV] = 0x30, + [RKCIF_MIPI_FRAME1_VLW_Y] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_CROP_START] = 0x8c, + }, + [RKCIF_ID1] = { + [RKCIF_MIPI_CTRL0] = 0x08, + [RKCIF_MIPI_CTRL1] = 0x0c, + [RKCIF_MIPI_FRAME0_ADDR_Y] = 0x38, + [RKCIF_MIPI_FRAME0_ADDR_UV] = 0x40, + [RKCIF_MIPI_FRAME0_VLW_Y] = 0x48, + [RKCIF_MIPI_FRAME0_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_ADDR_Y] = 0x3c, + [RKCIF_MIPI_FRAME1_ADDR_UV] = 0x44, + [RKCIF_MIPI_FRAME1_VLW_Y] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_CROP_START] = 0x90, + }, + [RKCIF_ID2] = { + [RKCIF_MIPI_CTRL0] = 0x10, + [RKCIF_MIPI_CTRL1] = 0x14, + [RKCIF_MIPI_FRAME0_ADDR_Y] = 0x4c, + [RKCIF_MIPI_FRAME0_ADDR_UV] = 0x54, + [RKCIF_MIPI_FRAME0_VLW_Y] = 0x5c, + [RKCIF_MIPI_FRAME0_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_ADDR_Y] = 0x50, + [RKCIF_MIPI_FRAME1_ADDR_UV] = 0x58, + [RKCIF_MIPI_FRAME1_VLW_Y] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_CROP_START] = 0x94, + }, + [RKCIF_ID3] = { + [RKCIF_MIPI_CTRL0] = 0x18, + [RKCIF_MIPI_CTRL1] = 0x1c, + [RKCIF_MIPI_FRAME0_ADDR_Y] = 0x60, + [RKCIF_MIPI_FRAME0_ADDR_UV] = 0x68, + [RKCIF_MIPI_FRAME0_VLW_Y] = 0x70, + [RKCIF_MIPI_FRAME0_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_ADDR_Y] = 0x64, + [RKCIF_MIPI_FRAME1_ADDR_UV] = 0x6c, + [RKCIF_MIPI_FRAME1_VLW_Y] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_CROP_START] = 0x98, + }, + }, + .blocks = { + { + .offset = 0x100, + }, + { + .offset = 0x200, + }, + { + .offset = 0x300, + }, + { + .offset = 0x400, + }, + { + .offset = 0x500, + }, + { + .offset = 0x600, + }, + }, +}; + static inline unsigned int rkcif_mipi_get_reg(struct rkcif_interface *interface, unsigned int index) { @@ -631,6 +846,8 @@ static int rkcif_mipi_start_streaming(struct rkcif_stream *stream) rkcif_mipi_stream_write(stream, RKCIF_MIPI_CTRL1, ctrl1); rkcif_mipi_stream_write(stream, RKCIF_MIPI_CTRL0, ctrl0); + rkcif_mipi_write(interface, RKCIF_MIPI_CTRL, RKCIF_MIPI_CTRL_CAP_EN); + ret = 0; out: diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h index 7f16eadc474c3b..4e17cbc04b76e9 100644 --- a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h +++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h @@ -13,6 +13,8 @@ #include "rkcif-common.h" extern const struct rkcif_mipi_match_data rkcif_rk3568_vicap_mipi_match_data; +extern const struct rkcif_mipi_match_data rkcif_rk3576_vicap_mipi_match_data; +extern const struct rkcif_mipi_match_data rkcif_rk3588_vicap_mipi_match_data; int rkcif_mipi_register(struct rkcif_device *rkcif); diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-common.h b/drivers/media/platform/rockchip/rkcif/rkcif-common.h index dd92cfbc879f01..4d9211ba9bda8d 100644 --- a/drivers/media/platform/rockchip/rkcif/rkcif-common.h +++ b/drivers/media/platform/rockchip/rkcif/rkcif-common.h @@ -27,7 +27,7 @@ #include "rkcif-regs.h" #define RKCIF_DRIVER_NAME "rockchip-cif" -#define RKCIF_CLK_MAX 4 +#define RKCIF_CLK_MAX 5 enum rkcif_format_type { RKCIF_FMT_TYPE_INVALID, diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-dev.c b/drivers/media/platform/rockchip/rkcif/rkcif-dev.c index b4cf1146f13118..cfd6115c815dec 100644 --- a/drivers/media/platform/rockchip/rkcif/rkcif-dev.c +++ b/drivers/media/platform/rockchip/rkcif/rkcif-dev.c @@ -53,6 +53,34 @@ static const struct rkcif_match_data rk3568_vicap_match_data = { .mipi = &rkcif_rk3568_vicap_mipi_match_data, }; +static const char *const rk3576_vicap_clks[] = { + "aclk", + "hclk", + "dclk", + "iclk_host0", + "iclk_host1", +}; + +static const struct rkcif_match_data rk3576_vicap_match_data = { + .clks = rk3576_vicap_clks, + .clks_num = ARRAY_SIZE(rk3576_vicap_clks), + .mipi = &rkcif_rk3576_vicap_mipi_match_data, +}; + +static const char *const rk3588_vicap_clks[] = { + "aclk", + "hclk", + "dclk", + "iclk_host0", + "iclk_host1", +}; + +static const struct rkcif_match_data rk3588_vicap_match_data = { + .clks = rk3588_vicap_clks, + .clks_num = ARRAY_SIZE(rk3588_vicap_clks), + .mipi = &rkcif_rk3588_vicap_mipi_match_data, +}; + static const struct of_device_id rkcif_plat_of_match[] = { { .compatible = "rockchip,px30-vip", @@ -62,6 +90,14 @@ static const struct of_device_id rkcif_plat_of_match[] = { .compatible = "rockchip,rk3568-vicap", .data = &rk3568_vicap_match_data, }, + { + .compatible = "rockchip,rk3576-vicap", + .data = &rk3576_vicap_match_data, + }, + { + .compatible = "rockchip,rk3588-vicap", + .data = &rk3588_vicap_match_data, + }, {} }; MODULE_DEVICE_TABLE(of, rkcif_plat_of_match); diff --git a/drivers/media/platform/synopsys/dw-mipi-csi2rx.c b/drivers/media/platform/synopsys/dw-mipi-csi2rx.c index 02eb4a6cafadea..57dabb88ca16c7 100644 --- a/drivers/media/platform/synopsys/dw-mipi-csi2rx.c +++ b/drivers/media/platform/synopsys/dw-mipi-csi2rx.c @@ -839,6 +839,10 @@ static const struct of_device_id dw_mipi_csi2rx_of_match[] = { .compatible = "fsl,imx93-mipi-csi2", .data = &imx93_drvdata, }, + { + .compatible = "rockchip,rk3576-mipi-csi2", + .data = &rk3568_drvdata, + }, { .compatible = "rockchip,rk3568-mipi-csi2", .data = &rk3568_drvdata, diff --git a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c index 61ad20b18b8d65..4c8957505a50dc 100644 --- a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c +++ b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c @@ -506,9 +506,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 */ + /* 100ms delay as per HDMI spec + extra 50ms to cover internal delay */ if (!en) - msleep(100); + msleep(100 + 50); } static void hdmirx_write_edid_data(struct snps_hdmirx_dev *hdmirx_dev, diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 7192c9d1d268e9..83d6969faf4a55 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -539,6 +539,21 @@ config MFD_EXYNOS_LPASS SoCs (e.g. Exynos5433). Choose Y here only if you build for such Samsung SoC. +config MFD_FLIPPER_ONE + tristate "Flipper One MCU support" + depends on I2C && OF + depends on ARCH_ROCKCHIP || COMPILE_TEST + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + help + This adds support for the MCU handling human-machine interface, + power controls and USB Type-C functions on the Flipper One + networking multitool. + This driver provides common support for accessing the device. + Additional drivers must be enabled in order to use the + functionality of the device. + config MFD_GATEWORKS_GSC tristate "Gateworks System Controller" depends on I2C && OF @@ -1378,6 +1393,22 @@ config MFD_RC5T583 Additional drivers must be enabled in order to use the different functionality of the device. +config MFD_ROCKCHIP_MFPWM + tristate "Rockchip multi-function PWM controller" + depends on ARCH_ROCKCHIP || COMPILE_TEST + depends on OF + depends on HAS_IOMEM + depends on COMMON_CLK + select MFD_CORE + help + Some Rockchip SoCs, such as the RK3576, use a PWM controller that has + several different functions, such as generating PWM waveforms but also + counting waveforms. + + This driver manages the overall device, and selects between different + functionalities at runtime as needed. Drivers for them are implemented + in their respective subsystems. + config MFD_RK8XX tristate select MFD_CORE diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index e75e8045c28afa..3ff94e56e16583 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_MFD_CS42L43_I2C) += cs42l43-i2c.o obj-$(CONFIG_MFD_CS42L43_SDW) += cs42l43-sdw.o obj-$(CONFIG_MFD_ENE_KB3930) += ene-kb3930.o obj-$(CONFIG_MFD_EXYNOS_LPASS) += exynos-lpass.o +obj-$(CONFIG_MFD_FLIPPER_ONE) += flipper-one-mcu.o obj-$(CONFIG_MFD_GATEWORKS_GSC) += gateworks-gsc.o obj-$(CONFIG_MFD_MACSMC) += macsmc.o @@ -231,6 +232,7 @@ obj-$(CONFIG_MFD_PALMAS) += palmas.o obj-$(CONFIG_MFD_VIPERBOARD) += viperboard.o obj-$(CONFIG_MFD_NTXEC) += ntxec.o obj-$(CONFIG_MFD_RC5T583) += rc5t583.o rc5t583-irq.o +obj-$(CONFIG_MFD_ROCKCHIP_MFPWM) += rockchip-mfpwm.o obj-$(CONFIG_MFD_RK8XX) += rk8xx-core.o obj-$(CONFIG_MFD_RK8XX_I2C) += rk8xx-i2c.o obj-$(CONFIG_MFD_RK8XX_SPI) += rk8xx-spi.o diff --git a/drivers/mfd/bq257xx.c b/drivers/mfd/bq257xx.c index e9d49dac0a1670..054342c60b7318 100644 --- a/drivers/mfd/bq257xx.c +++ b/drivers/mfd/bq257xx.c @@ -39,6 +39,39 @@ static const struct regmap_config bq25703_regmap_config = { .val_format_endian = REGMAP_ENDIAN_LITTLE, }; +static const struct regmap_range bq25792_writeable_reg_ranges[] = { + regmap_reg_range(BQ25792_REG00_MIN_SYS_VOLTAGE, + BQ25792_REG18_NTC_CONTROL_1), + regmap_reg_range(BQ25792_REG28_CHARGER_MASK_0, + BQ25792_REG30_ADC_FUNCTION_DISABLE_1), +}; + +static const struct regmap_access_table bq25792_writeable_regs = { + .yes_ranges = bq25792_writeable_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(bq25792_writeable_reg_ranges), +}; + +static const struct regmap_range bq25792_volatile_reg_ranges[] = { + regmap_reg_range(BQ25792_REG19_ICO_CURRENT_LIMIT, + BQ25792_REG27_FAULT_FLAG_1), + regmap_reg_range(BQ25792_REG31_IBUS_ADC, + BQ25792_REG47_DPDM_DRIVER), +}; + +static const struct regmap_access_table bq25792_volatile_regs = { + .yes_ranges = bq25792_volatile_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(bq25792_volatile_reg_ranges), +}; + +static const struct regmap_config bq25792_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = BQ25792_REG48_PART_INFORMATION, + .cache_type = REGCACHE_MAPLE, + .wr_table = &bq25792_writeable_regs, + .volatile_table = &bq25792_volatile_regs, +}; + static const struct mfd_cell cells[] = { MFD_CELL_NAME("bq257xx-regulator"), MFD_CELL_NAME("bq257xx-charger"), @@ -46,6 +79,7 @@ static const struct mfd_cell cells[] = { static int bq257xx_probe(struct i2c_client *client) { + const struct regmap_config *rcfg; struct bq257xx_device *ddata; int ret; @@ -53,9 +87,21 @@ static int bq257xx_probe(struct i2c_client *client) if (!ddata) return -ENOMEM; + ddata->type = (uintptr_t)i2c_get_match_data(client); ddata->client = client; - ddata->regmap = devm_regmap_init_i2c(client, &bq25703_regmap_config); + switch (ddata->type) { + case BQ25703A: + rcfg = &bq25703_regmap_config; + break; + case BQ25792: + rcfg = &bq25792_regmap_config; + break; + default: + return dev_err_probe(&client->dev, -ENODEV, "Unsupported device type\n"); + } + + ddata->regmap = devm_regmap_init_i2c(client, rcfg); if (IS_ERR(ddata->regmap)) { return dev_err_probe(&client->dev, PTR_ERR(ddata->regmap), "Failed to allocate register map\n"); @@ -73,13 +119,15 @@ static int bq257xx_probe(struct i2c_client *client) } static const struct i2c_device_id bq257xx_i2c_ids[] = { - { "bq25703a" }, + { "bq25703a", BQ25703A }, + { "bq25792", BQ25792 }, {} }; MODULE_DEVICE_TABLE(i2c, bq257xx_i2c_ids); static const struct of_device_id bq257xx_of_match[] = { - { .compatible = "ti,bq25703a" }, + { .compatible = "ti,bq25703a", .data = (void *)BQ25703A }, + { .compatible = "ti,bq25792", .data = (void *)BQ25792 }, {} }; MODULE_DEVICE_TABLE(of, bq257xx_of_match); diff --git a/drivers/mfd/flipper-one-mcu.c b/drivers/mfd/flipper-one-mcu.c new file mode 100644 index 00000000000000..f109343e81333b --- /dev/null +++ b/drivers/mfd/flipper-one-mcu.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Flipper One MCU interconnect driver + * Copyright (C) 2026 Flipper FZCO + */ + +#include +#include +#include +#include +#include +#include +#include + +static const struct regmap_range fomcu_writeable_reg_ranges[] = { + regmap_reg_range(FOMCU_REG_INTMSK_INPUT, + FOMCU_REG_INPUT_BTNS - 1), + regmap_reg_range(FOMCU_REG_LEDS_BR_LINK, + FOMCU_REG_LEDS_COLOR_LINK4), +}; + +static const struct regmap_access_table fomcu_writeable_regs = { + .yes_ranges = fomcu_writeable_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(fomcu_writeable_reg_ranges), +}; + +static const struct regmap_range fomcu_nonvolatile_reg_ranges[] = { + regmap_reg_range(FOMCU_REG_VERSION, FOMCU_REG_VERSION + 1), + regmap_reg_range(FOMCU_REG_INTMSK_INPUT, FOMCU_REG_INPUT_BTNS - 1), +}; + +static const struct regmap_access_table fomcu_volatile_regs = { + .no_ranges = fomcu_nonvolatile_reg_ranges, + .n_no_ranges = ARRAY_SIZE(fomcu_nonvolatile_reg_ranges), +}; + +static const struct regmap_range fomcu_precious_reg_ranges[] = { + regmap_reg_range(FOMCU_REG_INTSTS_INPUT, + FOMCU_REG_INTMSK_INPUT - 1), +}; + +static const struct regmap_access_table fomcu_precious_regs = { + .yes_ranges = fomcu_precious_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(fomcu_precious_reg_ranges), +}; + +static const struct regmap_config fomcu_regmap_config = { + .name = "flipper-one-mcu", + .reg_bits = 16, + .reg_stride = 2, + .val_bits = 16, + .val_format_endian = REGMAP_ENDIAN_LITTLE, + .max_register = FOMCU_REG_MAX, + .wr_table = &fomcu_writeable_regs, + .volatile_table = &fomcu_volatile_regs, + .precious_table = &fomcu_precious_regs, +}; + +#define CAT(a, b) CAT_I(a, b) +#define CAT_I(a, b) a##b +#define FOMCU_IRQ_REG(subsys, bit) \ + REGMAP_IRQ_REG(CAT(FOMCU_INT_, CAT(subsys, CAT(_, bit))), \ + CAT(FOMCU_INTOFF_, subsys), \ + CAT(FOMCU_INTSTS_, CAT(subsys, CAT(_, bit)))) + +static const struct regmap_irq fomcu_irqs[] = { + FOMCU_IRQ_REG(INPUT, BTN), + FOMCU_IRQ_REG(INPUT, TOUCH), + FOMCU_IRQ_REG(INPUT, HEADSET), +}; + +static unsigned int irq_input_offsets[] = { FOMCU_INTOFF_INPUT }; + +static const struct regmap_irq_sub_irq_map fomcu_sub_irqs[] = { + REGMAP_IRQ_MAIN_REG_OFFSET(irq_input_offsets), +}; + +static const struct regmap_irq_chip fomcu_irq_chip = { + .name = "fomcu-irq", + .irqs = fomcu_irqs, + .num_irqs = ARRAY_SIZE(fomcu_irqs), + .main_status = FOMCU_REG_INTSTS, + .status_base = FOMCU_REG_INTSTS_INPUT, + .mask_base = FOMCU_REG_INTMSK_INPUT, + .sub_reg_offsets = &fomcu_sub_irqs[0], + .num_main_regs = 1, + .num_regs = ARRAY_SIZE(fomcu_sub_irqs), +}; + +static const struct resource fo_input_irqs[] = { + DEFINE_RES_IRQ_NAMED(FOMCU_INT_INPUT_BTN, "flipper-one-input-btn"), + DEFINE_RES_IRQ_NAMED(FOMCU_INT_INPUT_TOUCH, "flipper-one-input-touch"), + DEFINE_RES_IRQ_NAMED(FOMCU_INT_INPUT_HEADSET, "flipper-one-input-headset"), +}; + +static const struct mfd_cell cells[] = { + MFD_CELL_RES("flipper-one-input", fo_input_irqs), + MFD_CELL_NAME("flipper-one-leds"), + MFD_CELL_NAME("flipper-one-power"), + MFD_CELL_NAME("flipper-one-regulators"), + MFD_CELL_NAME("flipper-one-thermal"), + MFD_CELL_NAME("flipper-one-typec"), +}; + +static int fomcu_probe(struct i2c_client *client) +{ + struct regmap_irq_chip_data *irq_data; + struct fomcu_device *ddata; + int ret; + + ddata = devm_kzalloc(&client->dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + ddata->client = client; + + ddata->regmap = devm_regmap_init_i2c(client, &fomcu_regmap_config); + if (IS_ERR(ddata->regmap)) { + return dev_err_probe(&client->dev, PTR_ERR(ddata->regmap), + "Failed to allocate register map\n"); + } + + i2c_set_clientdata(client, ddata); + + ret = devm_regmap_add_irq_chip(&client->dev, ddata->regmap, + client->irq, IRQF_ONESHOT, 0, + &fomcu_irq_chip, &irq_data); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to add IRQ chip\n"); + + ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_AUTO, + cells, ARRAY_SIZE(cells), NULL, 0, + regmap_irq_get_domain(irq_data)); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to register child devices\n"); + + return ret; +} + +static const struct i2c_device_id fomcu_i2c_ids[] = { + { "flipper-one-mcu" }, + {} +}; +MODULE_DEVICE_TABLE(i2c, fomcu_i2c_ids); + +static const struct of_device_id fomcu_of_match[] = { + { .compatible = "flipper,one-mcu" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, fomcu_of_match); + +static struct i2c_driver fomcu_driver = { + .driver = { + .name = "flipper-one-mcu", + .of_match_table = fomcu_of_match, + }, + .probe = fomcu_probe, + .id_table = fomcu_i2c_ids, +}; +module_i2c_driver(fomcu_driver); + +MODULE_DESCRIPTION("Flipper One MCU driver"); +MODULE_AUTHOR("Alexey Charkov "); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/rk8xx-core.c b/drivers/mfd/rk8xx-core.c index 3dcf6abfda74ff..21182df45d1b6b 100644 --- a/drivers/mfd/rk8xx-core.c +++ b/drivers/mfd/rk8xx-core.c @@ -875,7 +875,7 @@ int rk8xx_probe(struct device *dev, int variant, unsigned int irq, struct regmap if (device_property_read_bool(dev, "system-power-controller") || device_property_read_bool(dev, "rockchip,system-power-controller")) { ret = devm_register_sys_off_handler(dev, - SYS_OFF_MODE_POWER_OFF_PREPARE, SYS_OFF_PRIO_HIGH, + SYS_OFF_MODE_POWER_OFF, SYS_OFF_PRIO_HIGH, &rk808_power_off, rk808); if (ret) return dev_err_probe(dev, ret, diff --git a/drivers/mfd/rockchip-mfpwm.c b/drivers/mfd/rockchip-mfpwm.c new file mode 100644 index 00000000000000..72d04982b9615e --- /dev/null +++ b/drivers/mfd/rockchip-mfpwm.c @@ -0,0 +1,357 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2025 Collabora Ltd. + * + * A driver to manage all the different functionalities exposed by Rockchip's + * PWMv4 hardware. + * + * This driver is chiefly focused on guaranteeing non-concurrent operation + * between the different device functions, as well as setting the clocks. + * It registers the device function platform devices, e.g. PWM output or + * PWM capture. + * + * Authors: + * Nicolas Frattaroli + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * struct rockchip_mfpwm - private mfpwm driver instance state struct + * @pdev: pointer to this instance's &struct platform_device + * @base: pointer to the memory mapped registers of this device + * @pwm_clk: pointer to the PLL clock the PWM signal may be derived from + * @osc_clk: pointer to the fixed crystal the PWM signal may be derived from + * @rc_clk: pointer to the RC oscillator the PWM signal may be derived from + * @chosen_clk: a clk-mux of pwm_clk, osc_clk and rc_clk + * @pclk: pointer to the APB bus clock needed for mmio register access + * @active_func: pointer to the currently active device function, or %NULL if no + * device function is currently actively using any of the shared + * resources. May only be checked/modified with @state_lock held. + * @acquire_cnt: number of times @active_func has currently mfpwm_acquire()'d + * it. Must only be checked or modified while holding @state_lock. + * @state_lock: this lock is held while either the active device function, the + * enable register, or the chosen clock is being changed. + * @irq: the IRQ number of this device + */ +struct rockchip_mfpwm { + struct platform_device *pdev; + void __iomem *base; + struct clk *pwm_clk; + struct clk *osc_clk; + struct clk *rc_clk; + struct clk *chosen_clk; + struct clk *pclk; + struct rockchip_mfpwm_func *active_func; + unsigned int acquire_cnt; + spinlock_t state_lock; + int irq; +}; + +static atomic_t subdev_id = ATOMIC_INIT(0); + +static inline struct rockchip_mfpwm *to_rockchip_mfpwm(struct platform_device *pdev) +{ + return platform_get_drvdata(pdev); +} + +static int mfpwm_check_pwmf(const struct rockchip_mfpwm_func *pwmf, + const char *fname) +{ + struct device *dev = &pwmf->parent->pdev->dev; + + if (IS_ERR_OR_NULL(pwmf)) { + dev_warn(dev, "called %s with an erroneous handle, no effect\n", + fname); + return -EINVAL; + } + + if (IS_ERR_OR_NULL(pwmf->parent)) { + dev_warn(dev, "called %s with an erroneous mfpwm_func parent, no effect\n", + fname); + return -EINVAL; + } + + return 0; +} + +__attribute__((nonnull)) +static int mfpwm_do_acquire(struct rockchip_mfpwm_func *pwmf) +{ + struct rockchip_mfpwm *mfpwm = pwmf->parent; + unsigned int cnt; + + if (mfpwm->active_func && pwmf->id != mfpwm->active_func->id) + return -EBUSY; + + if (!mfpwm->active_func) + mfpwm->active_func = pwmf; + + if (!check_add_overflow(mfpwm->acquire_cnt, 1, &cnt)) { + mfpwm->acquire_cnt = cnt; + } else { + dev_warn(&mfpwm->pdev->dev, "prevented acquire counter overflow in %s\n", + __func__); + return -EOVERFLOW; + } + + dev_dbg(&mfpwm->pdev->dev, "%d acquired mfpwm, acquires now at %u\n", + pwmf->id, mfpwm->acquire_cnt); + + return clk_enable(mfpwm->pclk); +} + +int mfpwm_acquire(struct rockchip_mfpwm_func *pwmf) +{ + struct rockchip_mfpwm *mfpwm; + unsigned long flags; + int ret = 0; + + ret = mfpwm_check_pwmf(pwmf, "mfpwm_acquire"); + if (ret) + return ret; + + mfpwm = pwmf->parent; + dev_dbg(&mfpwm->pdev->dev, "%d is attempting to acquire\n", pwmf->id); + + if (!spin_trylock_irqsave(&mfpwm->state_lock, flags)) + return -EBUSY; + + ret = mfpwm_do_acquire(pwmf); + + spin_unlock_irqrestore(&mfpwm->state_lock, flags); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(mfpwm_acquire, "ROCKCHIP_MFPWM"); + +__attribute__((nonnull)) +static void mfpwm_do_release(const struct rockchip_mfpwm_func *pwmf) +{ + struct rockchip_mfpwm *mfpwm = pwmf->parent; + + if (!mfpwm->active_func) + return; + + if (mfpwm->active_func->id != pwmf->id) + return; + + /* + * No need to check_sub_overflow here, !mfpwm->active_func above catches + * this type of problem already. + */ + mfpwm->acquire_cnt--; + + if (!mfpwm->acquire_cnt) + mfpwm->active_func = NULL; + + clk_disable(mfpwm->pclk); +} + +void mfpwm_release(const struct rockchip_mfpwm_func *pwmf) +{ + struct rockchip_mfpwm *mfpwm; + unsigned long flags; + + if (mfpwm_check_pwmf(pwmf, "mfpwm_release")) + return; + + mfpwm = pwmf->parent; + + spin_lock_irqsave(&mfpwm->state_lock, flags); + mfpwm_do_release(pwmf); + dev_dbg(&mfpwm->pdev->dev, "%d released mfpwm, acquires now at %u\n", + pwmf->id, mfpwm->acquire_cnt); + spin_unlock_irqrestore(&mfpwm->state_lock, flags); +} +EXPORT_SYMBOL_NS_GPL(mfpwm_release, "ROCKCHIP_MFPWM"); + +int mfpwm_get_mode(const struct rockchip_mfpwm_func *pwmf) +{ + struct rockchip_mfpwm *mfpwm; + int ret; + + ret = mfpwm_check_pwmf(pwmf, "mfpwm_acquire"); + if (ret) + return ret; + + mfpwm = pwmf->parent; + + guard(spinlock_irqsave)(&mfpwm->state_lock); + + if (!rockchip_pwm_v4_is_enabled(mfpwm_reg_read(mfpwm->base, PWMV4_REG_ENABLE))) + return -1; + + return mfpwm_reg_read(mfpwm->base, PWMV4_REG_CTRL) & PWMV4_MODE_MASK; +} +EXPORT_SYMBOL_NS_GPL(mfpwm_get_mode, "ROCKCHIP_MFPWM"); + +/** + * mfpwm_register_subdev - register a single mfpwm_func + * @mfpwm: pointer to the parent &struct rockchip_mfpwm + * @name: sub-device name string + * + * Allocate a single &struct mfpwm_func, fill its members with appropriate data, + * and register a new mfd cell. + * + * Returns: 0 on success, negative errno on error + */ +static int mfpwm_register_subdev(struct rockchip_mfpwm *mfpwm, + const char *name) +{ + struct rockchip_mfpwm_func *func; + struct mfd_cell cell = {}; + + func = devm_kzalloc(&mfpwm->pdev->dev, sizeof(*func), GFP_KERNEL); + if (IS_ERR(func)) + return PTR_ERR(func); + func->irq = mfpwm->irq; + func->parent = mfpwm; + func->id = atomic_inc_return(&subdev_id); + func->base = mfpwm->base; + func->core = mfpwm->chosen_clk; + cell.name = name; + cell.platform_data = func; + cell.pdata_size = sizeof(*func); + + return devm_mfd_add_devices(&mfpwm->pdev->dev, func->id, &cell, 1, NULL, + 0, NULL); +} + +static int mfpwm_register_subdevs(struct rockchip_mfpwm *mfpwm) +{ + int ret; + + ret = mfpwm_register_subdev(mfpwm, "rockchip-pwm-v4"); + if (ret) + return ret; + + ret = mfpwm_register_subdev(mfpwm, "rockchip-pwm-capture"); + if (ret) + return ret; + + return 0; +} + +static int rockchip_mfpwm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rockchip_mfpwm *mfpwm; + char *clk_mux_name; + const char *mux_p_names[3]; + int ret = 0; + + mfpwm = devm_kzalloc(&pdev->dev, sizeof(*mfpwm), GFP_KERNEL); + if (IS_ERR(mfpwm)) + return PTR_ERR(mfpwm); + + mfpwm->pdev = pdev; + + spin_lock_init(&mfpwm->state_lock); + + mfpwm->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mfpwm->base)) + return dev_err_probe(dev, PTR_ERR(mfpwm->base), + "failed to ioremap address\n"); + + mfpwm->pclk = devm_clk_get_prepared(dev, "pclk"); + if (IS_ERR(mfpwm->pclk)) + return dev_err_probe(dev, PTR_ERR(mfpwm->pclk), + "couldn't get and prepare 'pclk' clock\n"); + + mfpwm->irq = platform_get_irq(pdev, 0); + if (mfpwm->irq < 0) + return dev_err_probe(dev, mfpwm->irq, "couldn't get irq 0\n"); + + mfpwm->pwm_clk = devm_clk_get_prepared(dev, "pwm"); + if (IS_ERR(mfpwm->pwm_clk)) + return dev_err_probe(dev, PTR_ERR(mfpwm->pwm_clk), + "couldn't get and prepare 'pwm' clock\n"); + + mfpwm->osc_clk = devm_clk_get_prepared(dev, "osc"); + if (IS_ERR(mfpwm->osc_clk)) + return dev_err_probe(dev, PTR_ERR(mfpwm->osc_clk), + "couldn't get and prepare 'osc' clock\n"); + + mfpwm->rc_clk = devm_clk_get_prepared(dev, "rc"); + if (IS_ERR(mfpwm->rc_clk)) + return dev_err_probe(dev, PTR_ERR(mfpwm->rc_clk), + "couldn't get and prepare 'rc' clock\n"); + + clk_mux_name = devm_kasprintf(dev, GFP_KERNEL, "%s_chosen", dev_name(dev)); + if (!clk_mux_name) + return -ENOMEM; + + mux_p_names[0] = __clk_get_name(mfpwm->pwm_clk); + mux_p_names[1] = __clk_get_name(mfpwm->osc_clk); + mux_p_names[2] = __clk_get_name(mfpwm->rc_clk); + mfpwm->chosen_clk = clk_register_mux(dev, clk_mux_name, mux_p_names, + ARRAY_SIZE(mux_p_names), + CLK_SET_RATE_PARENT, + mfpwm->base + PWMV4_REG_CLK_CTRL, + PWMV4_CLK_SRC_SHIFT, PWMV4_CLK_SRC_WIDTH, + CLK_MUX_HIWORD_MASK, NULL); + ret = clk_prepare(mfpwm->chosen_clk); + if (ret) { + dev_err(dev, "failed to prepare PWM clock mux: %pe\n", + ERR_PTR(ret)); + return ret; + } + + platform_set_drvdata(pdev, mfpwm); + + ret = mfpwm_register_subdevs(mfpwm); + if (ret) { + dev_err(dev, "failed to register sub-devices: %pe\n", + ERR_PTR(ret)); + return ret; + } + + return ret; +} + +static void rockchip_mfpwm_remove(struct platform_device *pdev) +{ + struct rockchip_mfpwm *mfpwm = to_rockchip_mfpwm(pdev); + unsigned long flags; + + spin_lock_irqsave(&mfpwm->state_lock, flags); + + if (mfpwm->chosen_clk) { + clk_unprepare(mfpwm->chosen_clk); + clk_unregister_mux(mfpwm->chosen_clk); + } + + spin_unlock_irqrestore(&mfpwm->state_lock, flags); +} + +static const struct of_device_id rockchip_mfpwm_of_match[] = { + { + .compatible = "rockchip,rk3576-pwm", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rockchip_mfpwm_of_match); + +static struct platform_driver rockchip_mfpwm_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = rockchip_mfpwm_of_match, + }, + .probe = rockchip_mfpwm_probe, + .remove = rockchip_mfpwm_remove, +}; +module_platform_driver(rockchip_mfpwm_driver); + +MODULE_AUTHOR("Nicolas Frattaroli "); +MODULE_DESCRIPTION("Rockchip MFPWM Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mmc/host/sdhci-of-dwcmshc.c b/drivers/mmc/host/sdhci-of-dwcmshc.c index b9ecd91f44ad4a..0f9576b790602a 100644 --- a/drivers/mmc/host/sdhci-of-dwcmshc.c +++ b/drivers/mmc/host/sdhci-of-dwcmshc.c @@ -790,7 +790,7 @@ static void dwcmshc_rk3568_set_clock(struct sdhci_host *host, unsigned int clock if (clock <= 52000000) { if (host->mmc->ios.timing == MMC_TIMING_MMC_HS200 || host->mmc->ios.timing == MMC_TIMING_MMC_HS400) { - dev_err(mmc_dev(host->mmc), + dev_dbg(mmc_dev(host->mmc), "Can't reduce the clock below 52MHz in HS200/HS400 mode"); goto enable_clk; } diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c index 79c867ef64dad0..5895d2e941abc9 100644 --- a/drivers/net/phy/realtek/realtek_main.c +++ b/drivers/net/phy/realtek/realtek_main.c @@ -291,6 +291,8 @@ static int rtl821x_probe(struct phy_device *phydev) if (IS_ERR(priv->clk)) return dev_err_probe(dev, PTR_ERR(priv->clk), "failed to get phy clock\n"); + if (priv->clk) + phy_reset_after_clk_enable(phydev); priv->enable_aldps = of_property_read_bool(dev->of_node, "realtek,aldps-enable"); @@ -934,10 +936,14 @@ static int rtl8211f_suspend(struct phy_device *phydev) static int rtl821x_resume(struct phy_device *phydev) { struct rtl821x_priv *priv = phydev->priv; + bool reinit = false; int ret; - if (!phydev->wol_enabled) + if (!phydev->wol_enabled && priv->clk) { clk_prepare_enable(priv->clk); + phy_reset_after_clk_enable(phydev); + reinit = true; + } ret = genphy_resume(phydev); if (ret < 0) @@ -945,6 +951,9 @@ static int rtl821x_resume(struct phy_device *phydev) msleep(20); + if (reinit) + phy_init_hw(phydev); + return 0; } @@ -2456,7 +2465,7 @@ static struct phy_driver realtek_drvs[] = { .resume = rtl8211f_resume, .read_page = rtl821x_read_page, .write_page = rtl821x_write_page, - .flags = PHY_ALWAYS_CALL_SUSPEND, + .flags = PHY_ALWAYS_CALL_SUSPEND | PHY_RST_AFTER_CLK_EN, .led_hw_is_supported = rtl8211x_led_hw_is_supported, .led_hw_control_get = rtl8211f_led_hw_control_get, .led_hw_control_set = rtl8211f_led_hw_control_set, diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index 0b55d445895f20..c24e6e17163ddc 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -3703,7 +3703,7 @@ brcmf_cfg80211_escan_handler(struct brcmf_if *ifp, list = (struct brcmf_scan_results *) cfg->escan_info.escan_buf; if (bi_length > BRCMF_ESCAN_BUF_SIZE - list->buflen) { - bphy_err(drvr, "Buffer is too small: ignoring\n"); + wiphy_dbg((drvr)->wiphy, "Buffer is too small: ignoring\n"); goto exit; } diff --git a/drivers/pci/controller/dwc/pcie-dw-rockchip.c b/drivers/pci/controller/dwc/pcie-dw-rockchip.c index 731d93663ccae5..74598da98a7343 100644 --- a/drivers/pci/controller/dwc/pcie-dw-rockchip.c +++ b/drivers/pci/controller/dwc/pcie-dw-rockchip.c @@ -44,6 +44,7 @@ #define PCIE_CLIENT_LD_RQ_RST_GRT FIELD_PREP_WM16(BIT(3), 1) #define PCIE_CLIENT_ENABLE_LTSSM FIELD_PREP_WM16(BIT(2), 1) #define PCIE_CLIENT_DISABLE_LTSSM FIELD_PREP_WM16(BIT(2), 0) +#define PCIE_CLIENT_INTR_STATUS_MSG_RX 0x04 /* Interrupt Status Register Related to Legacy Interrupt */ #define PCIE_CLIENT_INTR_STATUS_LEGACY 0x8 @@ -63,6 +64,11 @@ /* Interrupt Mask Register Related to Miscellaneous Operation */ #define PCIE_CLIENT_INTR_MASK_MISC 0x24 +#define PCIE_CLIENT_POWER 0x2c +#define PCIE_CLIENT_MSG_GEN 0x34 +#define PME_READY_ENTER_L23 BIT(3) +#define PME_TURN_OFF FIELD_PREP_WM16(BIT(4), 1) +#define PME_TO_ACK FIELD_PREP_WM16(BIT(9), 1) /* Power Management Control Register */ #define PCIE_CLIENT_POWER_CON 0x2c @@ -70,6 +76,9 @@ #define PCIE_CLKREQ_NOT_READY FIELD_PREP_WM16(BIT(0), 0) #define PCIE_CLKREQ_PULL_DOWN FIELD_PREP_WM16(GENMASK(13, 12), 1) +/* General Debug Register */ +#define PCIE_CLIENT_GENERAL_DEBUG 0x104 + /* RASDES TBA information */ #define PCIE_CLIENT_CDM_RASDES_TBA_INFO_CMN 0x154 #define PCIE_CLIENT_CDM_RASDES_TBA_L1_1 BIT(4) @@ -111,7 +120,9 @@ struct rockchip_pcie { unsigned int clk_cnt; struct reset_control *rst; struct gpio_desc *rst_gpio; + struct regulator *vpcie3v3; struct irq_domain *irq_domain; + u32 intx; const struct rockchip_pcie_of_data *data; bool supports_clkreq; struct delayed_work trace_work; @@ -317,14 +328,14 @@ static void rockchip_pcie_ltssm_trace(struct rockchip_pcie *rockchip, static void rockchip_pcie_enable_ltssm(struct rockchip_pcie *rockchip) { - rockchip_pcie_writel_apb(rockchip, PCIE_CLIENT_ENABLE_LTSSM, - PCIE_CLIENT_GENERAL_CON); + u32 val = PCIE_CLIENT_ENABLE_LTSSM | PCIE_CLIENT_LD_RQ_RST_GRT; + rockchip_pcie_writel_apb(rockchip, val, PCIE_CLIENT_GENERAL_CON); } static void rockchip_pcie_disable_ltssm(struct rockchip_pcie *rockchip) { - rockchip_pcie_writel_apb(rockchip, PCIE_CLIENT_DISABLE_LTSSM, - PCIE_CLIENT_GENERAL_CON); + u32 val = PCIE_CLIENT_DISABLE_LTSSM | PCIE_CLIENT_LD_RQ_RST_GRT; + rockchip_pcie_writel_apb(rockchip, val, PCIE_CLIENT_GENERAL_CON); } static bool rockchip_pcie_link_up(struct dw_pcie *pci) @@ -444,8 +455,46 @@ static int rockchip_pcie_host_init(struct dw_pcie_rp *pp) return 0; } +static void rockchip_pcie_pme_turn_off(struct dw_pcie_rp *pp) +{ + struct dw_pcie *pci = to_dw_pcie_from_pp(pp); + struct rockchip_pcie *rockchip = to_rockchip_pcie(pci); + struct device *dev = rockchip->pci.dev; + u32 status; + int ret; + + /* 1. Broadcast PME_Turn_Off Message, bit 4 self-clear once done */ + rockchip_pcie_writel_apb(rockchip, PME_TURN_OFF, PCIE_CLIENT_MSG_GEN); + ret = readl_poll_timeout(rockchip->apb_base + PCIE_CLIENT_MSG_GEN, + status, !(status & BIT(4)), PCIE_PME_TO_L2_TIMEOUT_US / 10, + PCIE_PME_TO_L2_TIMEOUT_US); + if (ret) { + dev_warn(dev, "Failed to send PME_Turn_Off\n"); + return; + } + + /* 2. Wait for PME_TO_Ack, bit 9 will be set once received */ + ret = readl_poll_timeout(rockchip->apb_base + PCIE_CLIENT_INTR_STATUS_MSG_RX, + status, status & BIT(9), PCIE_PME_TO_L2_TIMEOUT_US / 10, + PCIE_PME_TO_L2_TIMEOUT_US); + if (ret) { + dev_warn(dev, "Failed to receive PME_TO_Ack\n"); + return; + } + + /* 3. Clear PME_TO_Ack and Wait for ready to enter L23 message */ + rockchip_pcie_writel_apb(rockchip, PME_TO_ACK, PCIE_CLIENT_INTR_STATUS_MSG_RX); + ret = readl_poll_timeout(rockchip->apb_base + PCIE_CLIENT_POWER, + status, status & PME_READY_ENTER_L23, + PCIE_PME_TO_L2_TIMEOUT_US / 10, + PCIE_PME_TO_L2_TIMEOUT_US); + if (ret) + dev_err(dev, "Failed to get ready to enter L23 message\n"); +} + static const struct dw_pcie_host_ops rockchip_pcie_host_ops = { .init = rockchip_pcie_host_init, + .pme_turn_off = rockchip_pcie_pme_turn_off, }; /* @@ -602,14 +651,8 @@ static int rockchip_pcie_resource_get(struct platform_device *pdev, static int rockchip_pcie_phy_init(struct rockchip_pcie *rockchip) { - struct device *dev = rockchip->pci.dev; int ret; - rockchip->phy = devm_phy_get(dev, "pcie-phy"); - if (IS_ERR(rockchip->phy)) - return dev_err_probe(dev, PTR_ERR(rockchip->phy), - "missing PHY\n"); - ret = phy_init(rockchip->phy); if (ret < 0) return ret; @@ -666,21 +709,41 @@ static irqreturn_t rockchip_pcie_ep_sys_irq_thread(int irq, void *arg) return IRQ_HANDLED; } +static void rockchip_pcie_enable_enhanced_ltssm_control_mode(struct rockchip_pcie *rockchip, u32 flags) +{ + u32 val; + + /* Enable the enhanced control mode of signal app_ltssm_enable */ + val = FIELD_PREP_WM16(PCIE_LTSSM_ENABLE_ENHANCE, 1); + if (flags) + val |= FIELD_PREP_WM16(flags, 1); + rockchip_pcie_writel_apb(rockchip, val, PCIE_CLIENT_HOT_RESET_CTRL); +} + +static void rockchip_pcie_set_controller_mode(struct rockchip_pcie *rockchip, u32 mode) +{ + rockchip_pcie_writel_apb(rockchip, PCIE_CLIENT_SET_MODE(mode), PCIE_CLIENT_GENERAL_CON); +} + +static void rockchip_pcie_unmask_dll_indicator(struct rockchip_pcie *rockchip) +{ + u32 val; + + /* unmask DLL up/down indicator and hot reset/link-down reset */ + val = FIELD_PREP_WM16(PCIE_RDLH_LINK_UP_CHGED, 0) | + FIELD_PREP_WM16(PCIE_LINK_REQ_RST_NOT_INT, 0); + rockchip_pcie_writel_apb(rockchip, val, PCIE_CLIENT_INTR_MASK_MISC); +} + static int rockchip_pcie_configure_rc(struct rockchip_pcie *rockchip) { struct dw_pcie_rp *pp; - u32 val; if (!IS_ENABLED(CONFIG_PCIE_ROCKCHIP_DW_HOST)) return -ENODEV; - /* LTSSM enable control mode */ - val = FIELD_PREP_WM16(PCIE_LTSSM_ENABLE_ENHANCE, 1); - rockchip_pcie_writel_apb(rockchip, val, PCIE_CLIENT_HOT_RESET_CTRL); - - rockchip_pcie_writel_apb(rockchip, - PCIE_CLIENT_SET_MODE(PCIE_CLIENT_MODE_RC), - PCIE_CLIENT_GENERAL_CON); + rockchip_pcie_enable_enhanced_ltssm_control_mode(rockchip, 0); + rockchip_pcie_set_controller_mode(rockchip, PCIE_CLIENT_MODE_RC); pp = &rockchip->pci.pp; pp->ops = &rockchip_pcie_host_ops; @@ -693,7 +756,6 @@ static int rockchip_pcie_configure_ep(struct platform_device *pdev, { struct device *dev = &pdev->dev; int irq, ret; - u32 val; if (!IS_ENABLED(CONFIG_PCIE_ROCKCHIP_DW_EP)) return -ENODEV; @@ -710,17 +772,8 @@ static int rockchip_pcie_configure_ep(struct platform_device *pdev, return ret; } - /* - * LTSSM enable control mode, and automatically delay link training on - * hot reset/link-down reset. - */ - val = FIELD_PREP_WM16(PCIE_LTSSM_ENABLE_ENHANCE, 1) | - FIELD_PREP_WM16(PCIE_LTSSM_APP_DLY2_EN, 1); - rockchip_pcie_writel_apb(rockchip, val, PCIE_CLIENT_HOT_RESET_CTRL); - - rockchip_pcie_writel_apb(rockchip, - PCIE_CLIENT_SET_MODE(PCIE_CLIENT_MODE_EP), - PCIE_CLIENT_GENERAL_CON); + rockchip_pcie_enable_enhanced_ltssm_control_mode(rockchip, PCIE_LTSSM_APP_DLY2_EN); + rockchip_pcie_set_controller_mode(rockchip, PCIE_CLIENT_MODE_EP); rockchip->pci.ep.ops = &rockchip_pcie_ep_ops; rockchip->pci.ep.page_size = SZ_64K; @@ -742,10 +795,7 @@ static int rockchip_pcie_configure_ep(struct platform_device *pdev, pci_epc_init_notify(rockchip->pci.ep.epc); - /* unmask DLL up/down indicator and hot reset/link-down reset */ - val = FIELD_PREP_WM16(PCIE_RDLH_LINK_UP_CHGED, 0) | - FIELD_PREP_WM16(PCIE_LINK_REQ_RST_NOT_INT, 0); - rockchip_pcie_writel_apb(rockchip, val, PCIE_CLIENT_INTR_MASK_MISC); + rockchip_pcie_unmask_dll_indicator(rockchip); return ret; } @@ -784,20 +834,40 @@ static int rockchip_pcie_probe(struct platform_device *pdev) return ret; /* DON'T MOVE ME: must be enable before PHY init */ - ret = devm_regulator_get_enable_optional(dev, "vpcie3v3"); - if (ret < 0 && ret != -ENODEV) - return dev_err_probe(dev, ret, - "failed to enable vpcie3v3 regulator\n"); + rockchip->vpcie3v3 = devm_regulator_get_optional(dev, "vpcie3v3"); + if (IS_ERR(rockchip->vpcie3v3)) { + if (PTR_ERR(rockchip->vpcie3v3) != -ENODEV) + return dev_err_probe(dev, PTR_ERR(rockchip->vpcie3v3), + "failed to get vpcie3v3 regulator\n"); + rockchip->vpcie3v3 = NULL; + } else { + ret = regulator_enable(rockchip->vpcie3v3); + if (ret) + return dev_err_probe(dev, ret, + "failed to enable vpcie3v3 regulator\n"); + } + + rockchip->phy = devm_phy_get(dev, "pcie-phy"); + if (IS_ERR(rockchip->phy)) { + ret = PTR_ERR(rockchip->phy); + dev_err_probe(dev, ret, "missing PHY\n"); + goto disable_regulator; + } ret = rockchip_pcie_phy_init(rockchip); if (ret) - return dev_err_probe(dev, ret, - "failed to initialize the phy\n"); + goto disable_regulator; ret = reset_control_deassert(rockchip->rst); if (ret) goto deinit_phy; + ret = phy_calibrate(rockchip->phy); + if (ret) { + dev_err(dev, "phy lock failed\n"); + goto assert_controller; + } + ret = rockchip_pcie_clk_init(rockchip); if (ret) goto deinit_phy; @@ -820,15 +890,123 @@ static int rockchip_pcie_probe(struct platform_device *pdev) } return 0; - +assert_controller: + reset_control_assert(rockchip->rst); deinit_clk: clk_bulk_disable_unprepare(rockchip->clk_cnt, rockchip->clks); deinit_phy: rockchip_pcie_phy_deinit(rockchip); +disable_regulator: + if (rockchip->vpcie3v3) + regulator_disable(rockchip->vpcie3v3); return ret; } + +static inline void rockchip_pcie_link_status_clear(struct rockchip_pcie *rockchip) +{ + rockchip_pcie_writel_apb(rockchip, PCIE_CLIENT_GENERAL_DEBUG, 0x0); +} + +static int rockchip_pcie_suspend(struct device *dev) +{ + struct rockchip_pcie *rockchip = dev_get_drvdata(dev); + struct dw_pcie *pci = &rockchip->pci; + int ret; + + if (rockchip->data->mode == DW_PCIE_EP_TYPE) { + dev_err(dev, "suspend is not supported in EP mode\n"); + return -EOPNOTSUPP; + } + + rockchip->intx = rockchip_pcie_readl_apb(rockchip, PCIE_CLIENT_INTR_MASK_LEGACY); + + /* All sub-devices are in D3hot by PCIe stack */ + dw_pcie_dbi_ro_wr_dis(pci); + + rockchip_pcie_link_status_clear(rockchip); + + ret = dw_pcie_suspend_noirq(pci); + if (ret) + return ret; + + gpiod_set_value_cansleep(rockchip->rst_gpio, 0); + rockchip_pcie_phy_deinit(rockchip); + clk_bulk_disable_unprepare(rockchip->clk_cnt, rockchip->clks); + reset_control_assert(rockchip->rst); + if (rockchip->vpcie3v3) + regulator_disable(rockchip->vpcie3v3); + + return 0; +} + +static int rockchip_pcie_resume(struct device *dev) +{ + struct rockchip_pcie *rockchip = dev_get_drvdata(dev); + struct dw_pcie *pci = &rockchip->pci; + int ret; + + if (rockchip->data->mode == DW_PCIE_EP_TYPE) { + dev_err(dev, "resume is not supported in EP mode\n"); + return -EOPNOTSUPP; + } + + ret = clk_bulk_prepare_enable(rockchip->clk_cnt, rockchip->clks); + if (ret) { + dev_err(dev, "clock init failed: %d\n", ret); + return ret; + } + + if (rockchip->vpcie3v3) { + ret = regulator_enable(rockchip->vpcie3v3); + if (ret) + goto err_disable_clk; + } + + ret = rockchip_pcie_phy_init(rockchip); + if (ret) { + dev_err(dev, "phy init failed: %d\n", ret); + goto err_disable_regulator; + } + + reset_control_deassert(rockchip->rst); + + rockchip_pcie_writel_apb(rockchip, FIELD_PREP_WM16(0xffff, rockchip->intx), + PCIE_CLIENT_INTR_MASK_LEGACY); + + rockchip_pcie_enable_enhanced_ltssm_control_mode(rockchip, 0); + rockchip_pcie_set_controller_mode(rockchip, PCIE_CLIENT_MODE_RC); + rockchip_pcie_unmask_dll_indicator(rockchip); + + gpiod_set_value_cansleep(rockchip->rst_gpio, 1); + + ret = dw_pcie_resume_noirq(pci); + if (ret) { + dev_err(dev, "failed to resume: %d\n", ret); + /* + * During resume, dw_pcie_wait_for_link() is called and when + * there is no device connected at all it returns -EIO with + * the message "Device found, but not active". Ignore it for + * now. + */ + if (ret != -EIO) + goto err_deinit_phy; + } + + return 0; + +err_deinit_phy: + gpiod_set_value_cansleep(rockchip->rst_gpio, 0); + rockchip_pcie_phy_deinit(rockchip); +err_disable_regulator: + if (rockchip->vpcie3v3) + regulator_disable(rockchip->vpcie3v3); +err_disable_clk: + clk_bulk_disable_unprepare(rockchip->clk_cnt, rockchip->clks); + return ret; +} + static const struct rockchip_pcie_of_data rockchip_pcie_rc_of_data_rk3568 = { .mode = DW_PCIE_RC_TYPE, }; @@ -859,11 +1037,17 @@ static const struct of_device_id rockchip_pcie_of_match[] = { {}, }; +static const struct dev_pm_ops rockchip_pcie_pm_ops = { + NOIRQ_SYSTEM_SLEEP_PM_OPS(rockchip_pcie_suspend, + rockchip_pcie_resume) +}; + static struct platform_driver rockchip_pcie_driver = { .driver = { .name = "rockchip-dw-pcie", .of_match_table = rockchip_pcie_of_match, .suppress_bind_attrs = true, + .pm = &rockchip_pcie_pm_ops, }, .probe = rockchip_pcie_probe, }; diff --git a/drivers/phy/rockchip/Kconfig b/drivers/phy/rockchip/Kconfig index 14698571b60759..39759bb2fa1d10 100644 --- a/drivers/phy/rockchip/Kconfig +++ b/drivers/phy/rockchip/Kconfig @@ -136,8 +136,10 @@ config PHY_ROCKCHIP_USBDP tristate "Rockchip USBDP COMBO PHY Driver" depends on ARCH_ROCKCHIP && OF depends on TYPEC + depends on DRM || DRM=n select GENERIC_PHY select USB_COMMON + select DRM_AUX_BRIDGE if DRM_BRIDGE help Enable this to support the Rockchip USB3.0/DP combo PHY with Samsung IP block. This is required for USB3 support on RK3588. diff --git a/drivers/phy/rockchip/phy-rockchip-inno-csidphy.c b/drivers/phy/rockchip/phy-rockchip-inno-csidphy.c index c79fb53d8ee5c5..c88ca713d76dc6 100644 --- a/drivers/phy/rockchip/phy-rockchip-inno-csidphy.c +++ b/drivers/phy/rockchip/phy-rockchip-inno-csidphy.c @@ -403,6 +403,17 @@ static const struct dphy_drv_data rk3568_mipidphy_drv_data = { .resets_num = ARRAY_SIZE(rk3368_reset_names), }; +static const struct dphy_drv_data rk3576_mipidphy_drv_data = { + .pwrctl_offset = -1, + .ths_settle_offset = RK3568_CSIDPHY_CLK_WR_THS_SETTLE, + .calib_offset = RK3568_CSIDPHY_CLK_CALIB_EN, + .hsfreq_ranges = rk1808_mipidphy_hsfreq_ranges, + .num_hsfreq_ranges = ARRAY_SIZE(rk1808_mipidphy_hsfreq_ranges), + .grf_regs = rk3588_grf_dphy_regs, + .resets = rk3588_reset_names, + .resets_num = ARRAY_SIZE(rk3588_reset_names), +}; + static const struct dphy_drv_data rk3588_mipidphy_drv_data = { .pwrctl_offset = -1, .ths_settle_offset = RK3568_CSIDPHY_CLK_WR_THS_SETTLE, @@ -435,6 +446,10 @@ static const struct of_device_id rockchip_inno_csidphy_match_id[] = { .compatible = "rockchip,rk3568-csi-dphy", .data = &rk3568_mipidphy_drv_data, }, + { + .compatible = "rockchip,rk3576-csi-dphy", + .data = &rk3576_mipidphy_drv_data, + }, { .compatible = "rockchip,rk3588-csi-dphy", .data = &rk3588_mipidphy_drv_data, diff --git a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c index 8f4c08e599aa22..42116db38863e2 100644 --- a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c +++ b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -114,6 +115,8 @@ struct rockchip_chg_det_reg { /** * struct rockchip_usb2phy_port_cfg - usb-phy port configuration. * @phy_sus: phy suspend register. + * @svbus_en: soft vbus bvalid enable register. + * @svbus_sel: soft vbus bvalid selection register. * @bvalid_det_en: vbus valid rise detection enable register. * @bvalid_det_st: vbus valid rise detection status register. * @bvalid_det_clr: vbus valid rise detection clear register. @@ -140,6 +143,8 @@ struct rockchip_chg_det_reg { */ struct rockchip_usb2phy_port_cfg { struct usb2phy_reg phy_sus; + struct usb2phy_reg svbus_en; + struct usb2phy_reg svbus_sel; struct usb2phy_reg bvalid_det_en; struct usb2phy_reg bvalid_det_st; struct usb2phy_reg bvalid_det_clr; @@ -203,6 +208,7 @@ struct rockchip_usb2phy_cfg { * @event_nb: hold event notification callback. * @state: define OTG enumeration states before device reset. * @mode: the dr_mode of the controller. + * @typec_vbus_det: whether to apply Type C logic to OTG vbus detection. */ struct rockchip_usb2phy_port { struct phy *phy; @@ -222,6 +228,7 @@ struct rockchip_usb2phy_port { struct notifier_block event_nb; enum usb_otg_state state; enum usb_dr_mode mode; + bool typec_vbus_det; }; /** @@ -495,6 +502,13 @@ static int rockchip_usb2phy_init(struct phy *phy) mutex_lock(&rport->mutex); if (rport->port_id == USB2PHY_PORT_OTG) { + if (rport->typec_vbus_det) { + if (rport->port_cfg->svbus_en.enable && + rport->port_cfg->svbus_sel.enable) { + property_enable(rphy->grf, &rport->port_cfg->svbus_en, true); + property_enable(rphy->grf, &rport->port_cfg->svbus_sel, true); + } + } if (rport->mode != USB_DR_MODE_HOST && rport->mode != USB_DR_MODE_UNKNOWN) { /* clear bvalid status and enable bvalid detect irq */ @@ -535,8 +549,7 @@ static int rockchip_usb2phy_init(struct phy *phy) if (ret) goto out; - schedule_delayed_work(&rport->otg_sm_work, - OTG_SCHEDULE_DELAY * 3); + schedule_delayed_work(&rport->otg_sm_work, 0); } else { /* If OTG works in host only mode, do nothing. */ dev_dbg(&rport->phy->dev, "mode %d\n", rport->mode); @@ -666,8 +679,17 @@ static void rockchip_usb2phy_otg_sm_work(struct work_struct *work) unsigned long delay; bool vbus_attach, sch_work, notify_charger; - vbus_attach = property_enabled(rphy->grf, - &rport->port_cfg->utmi_bvalid); + if (rport->port_cfg->svbus_en.enable && rport->typec_vbus_det) { + if (property_enabled(rphy->grf, &rport->port_cfg->svbus_en) && + property_enabled(rphy->grf, &rport->port_cfg->svbus_sel)) { + vbus_attach = true; + } else { + vbus_attach = false; + } + } else { + vbus_attach = property_enabled(rphy->grf, + &rport->port_cfg->utmi_bvalid); + } sch_work = false; notify_charger = false; @@ -1280,6 +1302,83 @@ static int rockchip_otg_event(struct notifier_block *nb, return NOTIFY_DONE; } +static const char *const rockchip_usb2phy_typec_cons[] = { + "usb-c-connector", + NULL, +}; + +static struct device_node *rockchip_usb2phy_to_controller(struct rockchip_usb2phy *rphy) +{ + struct device_node *np; + struct device_node *parent; + + for_each_node_with_property(np, "phys") { + struct of_phandle_iterator it; + int ret; + + of_for_each_phandle(&it, ret, np, "phys", NULL, 0) { + parent = of_get_parent(it.node); + if (it.node != rphy->dev->of_node && rphy->dev->of_node != parent) { + if (parent) + of_node_put(parent); + continue; + } + + /* + * Either the PHY phandle we're iterating or its parent + * matched, we don't care about which out of the two in + * particular as we just need to know it's the right + * USB controller for this PHY. + */ + of_node_put(it.node); + of_node_put(parent); + return np; + } + } + + return NULL; +} + +static bool rockchip_usb2phy_otg_is_type_c(struct rockchip_usb2phy *rphy) +{ + struct device_node *controller = rockchip_usb2phy_to_controller(rphy); + struct device_node *ports; + struct device_node *ep = NULL; + struct device_node *parent; + + if (!controller) + return false; + + ports = of_get_child_by_name(controller, "ports"); + if (ports) { + of_node_put(controller); + controller = ports; + } + + for_each_of_graph_port(controller, port) { + 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; + + if (of_device_compatible_match(parent, rockchip_usb2phy_typec_cons)) { + of_node_put(parent); + of_node_put(controller); + return true; + } + + of_node_put(parent); + } + + of_node_put(controller); + + return false; +} + static int rockchip_usb2phy_otg_port_init(struct rockchip_usb2phy *rphy, struct rockchip_usb2phy_port *rport, struct device_node *child_np) @@ -1301,6 +1400,8 @@ static int rockchip_usb2phy_otg_port_init(struct rockchip_usb2phy *rphy, mutex_init(&rport->mutex); + rport->typec_vbus_det = rockchip_usb2phy_otg_is_type_c(rphy); + rport->mode = of_usb_get_dr_mode_by_phy(child_np, -1); if (rport->mode == USB_DR_MODE_HOST || rport->mode == USB_DR_MODE_UNKNOWN) { @@ -2054,6 +2155,8 @@ static const struct rockchip_usb2phy_cfg rk3576_phy_cfgs[] = { .port_cfgs = { [USB2PHY_PORT_OTG] = { .phy_sus = { 0x0000, 8, 0, 0, 0x1d1 }, + .svbus_en = { 0x0000, 15, 15, 0, 1 }, + .svbus_sel = { 0x0000, 14, 14, 0, 1 }, .bvalid_det_en = { 0x00c0, 1, 1, 0, 1 }, .bvalid_det_st = { 0x00c4, 1, 1, 0, 1 }, .bvalid_det_clr = { 0x00c8, 1, 1, 0, 1 }, @@ -2091,6 +2194,8 @@ static const struct rockchip_usb2phy_cfg rk3576_phy_cfgs[] = { .port_cfgs = { [USB2PHY_PORT_OTG] = { .phy_sus = { 0x2000, 8, 0, 0, 0x1d1 }, + .svbus_en = { 0x2000, 15, 15, 0, 1 }, + .svbus_sel = { 0x2000, 14, 14, 0, 1 }, .bvalid_det_en = { 0x20c0, 1, 1, 0, 1 }, .bvalid_det_st = { 0x20c4, 1, 1, 0, 1 }, .bvalid_det_clr = { 0x20c8, 1, 1, 0, 1 }, diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c index 2d973bc37f076f..7e7e51f8e7e54f 100644 --- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c +++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0+ /* * Copyright (c) 2021-2022 Rockchip Electronics Co., Ltd. - * Copyright (c) 2024 Collabora Ltd. + * Copyright (c) 2024-2026 Collabora Ltd. * * Author: Algea Cao * Author: Cristian Ciocaltea @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +53,12 @@ /* CMN_REG(001e) */ #define LCPLL_PI_EN_MASK BIT(5) #define LCPLL_100M_CLK_EN_MASK BIT(0) +/* CMN_REG(0022) */ +#define ANA_LCPLL_PMS_PDIV_MASK GENMASK(7, 4) +#define ANA_LCPLL_PMS_REFDIV_MASK GENMASK(3, 0) +/* CMN_REG(0023) */ +#define LCPLL_PMS_SDIV_RBR_MASK GENMASK(7, 4) +#define LCPLL_PMS_SDIV_HBR_MASK GENMASK(3, 0) /* CMN_REG(0025) */ #define LCPLL_PMS_IQDIV_RSTN_MASK BIT(4) /* CMN_REG(0028) */ @@ -326,6 +333,7 @@ #define FRL_3G3L_RATE 900000000 #define FRL_6G3L_RATE 1800000000 #define FRL_8G4L_RATE 3200000000 +#define FRL_FFE_MAX_LEVEL 3 enum dp_link_rate { DP_BW_RBR, @@ -413,7 +421,7 @@ struct rk_hdptx_phy { /* clk provider */ struct clk_hw hw; - bool restrict_rate_change; + bool pll_config_dirty; atomic_t usage_count; @@ -459,6 +467,16 @@ static const struct ropll_config rk_hdptx_tmds_ropll_cfg[] = { { 25175000ULL, 84, 84, 1, 1, 15, 1, 168, 1, 16, 4, 1, 1, }, }; +static const struct ffe_config { + u8 pre_shoot; + u8 de_emphasis; +} rk_hdptx_frl_ffe_cfg[FRL_FFE_MAX_LEVEL + 1] = { + { 0x3, 0x4 }, + { 0x3, 0x6 }, + { 0x3, 0x8 }, + { 0x3, 0x9 }, +}; + static const struct reg_sequence rk_hdptx_common_cmn_init_seq[] = { REG_SEQ0(CMN_REG(0009), 0x0c), REG_SEQ0(CMN_REG(000a), 0x83), @@ -949,7 +967,9 @@ static void rk_hdptx_pre_power_up(struct rk_hdptx_phy *hdptx) reset_control_assert(hdptx->rsts[RST_CMN].rstc); reset_control_assert(hdptx->rsts[RST_INIT].rstc); - val = (HDPTX_I_PLL_EN | HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN) << 16; + val = (FIELD_PREP_WM16(HDPTX_I_PLL_EN, 0) | + FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 0) | + FIELD_PREP_WM16(HDPTX_I_BGR_EN, 0)); regmap_write(hdptx->grf, GRF_HDPTX_CON0, val); } @@ -960,8 +980,8 @@ static int rk_hdptx_post_enable_lane(struct rk_hdptx_phy *hdptx) reset_control_deassert(hdptx->rsts[RST_LANE].rstc); - val = (HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN) << 16 | - HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN; + val = (FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 1) | + FIELD_PREP_WM16(HDPTX_I_BGR_EN, 1)); regmap_write(hdptx->grf, GRF_HDPTX_CON0, val); /* 3 lanes FRL mode */ @@ -990,16 +1010,15 @@ static int rk_hdptx_post_enable_pll(struct rk_hdptx_phy *hdptx) u32 val; int ret; - val = (HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN) << 16 | - HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN; + val = (FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 1) | + FIELD_PREP_WM16(HDPTX_I_BGR_EN, 1)); regmap_write(hdptx->grf, GRF_HDPTX_CON0, val); usleep_range(10, 15); reset_control_deassert(hdptx->rsts[RST_INIT].rstc); usleep_range(10, 15); - val = HDPTX_I_PLL_EN << 16 | HDPTX_I_PLL_EN; - regmap_write(hdptx->grf, GRF_HDPTX_CON0, val); + regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(HDPTX_I_PLL_EN, 1)); usleep_range(10, 15); reset_control_deassert(hdptx->rsts[RST_CMN].rstc); @@ -1020,6 +1039,8 @@ static void rk_hdptx_phy_disable(struct rk_hdptx_phy *hdptx) { u32 val; + dev_dbg(hdptx->dev, "PHY disable\n"); + reset_control_assert(hdptx->rsts[RST_APB].rstc); usleep_range(20, 30); reset_control_deassert(hdptx->rsts[RST_APB].rstc); @@ -1037,7 +1058,9 @@ static void rk_hdptx_phy_disable(struct rk_hdptx_phy *hdptx) reset_control_assert(hdptx->rsts[RST_CMN].rstc); reset_control_assert(hdptx->rsts[RST_INIT].rstc); - val = (HDPTX_I_PLL_EN | HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN) << 16; + val = (FIELD_PREP_WM16(HDPTX_I_PLL_EN, 0) | + FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 0) | + FIELD_PREP_WM16(HDPTX_I_BGR_EN, 0)); regmap_write(hdptx->grf, GRF_HDPTX_CON0, val); } @@ -1135,7 +1158,7 @@ static int rk_hdptx_frl_lcpll_cmn_config(struct rk_hdptx_phy *hdptx) rk_hdptx_pre_power_up(hdptx); - regmap_write(hdptx->grf, GRF_HDPTX_CON0, LC_REF_CLK_SEL << 16); + regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(LC_REF_CLK_SEL, 0)); rk_hdptx_multi_reg_write(hdptx, rk_hdptx_common_cmn_init_seq); rk_hdptx_multi_reg_write(hdptx, rk_hdptx_frl_lcpll_cmn_init_seq); @@ -1153,9 +1176,11 @@ static int rk_hdptx_frl_lcpll_cmn_config(struct rk_hdptx_phy *hdptx) regmap_write(hdptx->regmap, CMN_REG(0020), cfg->pms_mdiv); regmap_write(hdptx->regmap, CMN_REG(0021), cfg->pms_mdiv_afc); regmap_write(hdptx->regmap, CMN_REG(0022), - (cfg->pms_pdiv << 4) | cfg->pms_refdiv); + FIELD_PREP(ANA_LCPLL_PMS_PDIV_MASK, cfg->pms_pdiv) | + FIELD_PREP(ANA_LCPLL_PMS_REFDIV_MASK, cfg->pms_refdiv)); regmap_write(hdptx->regmap, CMN_REG(0023), - (cfg->pms_sdiv << 4) | cfg->pms_sdiv); + FIELD_PREP(LCPLL_PMS_SDIV_RBR_MASK, cfg->pms_sdiv) | + FIELD_PREP(LCPLL_PMS_SDIV_HBR_MASK, cfg->pms_sdiv)); regmap_write(hdptx->regmap, CMN_REG(002a), cfg->sdm_deno); regmap_write(hdptx->regmap, CMN_REG(002b), cfg->sdm_num_sign); regmap_write(hdptx->regmap, CMN_REG(002c), cfg->sdm_num); @@ -1178,8 +1203,7 @@ static int rk_hdptx_frl_lcpll_ropll_cmn_config(struct rk_hdptx_phy *hdptx) rk_hdptx_pre_power_up(hdptx); /* ROPLL input reference clock from LCPLL (cascade mode) */ - regmap_write(hdptx->grf, GRF_HDPTX_CON0, - (LC_REF_CLK_SEL << 16) | LC_REF_CLK_SEL); + regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(LC_REF_CLK_SEL, 1)); rk_hdptx_multi_reg_write(hdptx, rk_hdptx_common_cmn_init_seq); rk_hdptx_multi_reg_write(hdptx, rk_hdptx_frl_lcpll_ropll_cmn_init_seq); @@ -1218,7 +1242,7 @@ static int rk_hdptx_tmds_ropll_cmn_config(struct rk_hdptx_phy *hdptx) rk_hdptx_pre_power_up(hdptx); - regmap_write(hdptx->grf, GRF_HDPTX_CON0, LC_REF_CLK_SEL << 16); + regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(LC_REF_CLK_SEL, 0)); rk_hdptx_multi_reg_write(hdptx, rk_hdptx_common_cmn_init_seq); rk_hdptx_multi_reg_write(hdptx, rk_hdptx_tmds_cmn_init_seq); @@ -1226,8 +1250,10 @@ static int rk_hdptx_tmds_ropll_cmn_config(struct rk_hdptx_phy *hdptx) regmap_write(hdptx->regmap, CMN_REG(0051), cfg->pms_mdiv); regmap_write(hdptx->regmap, CMN_REG(0055), cfg->pms_mdiv_afc); regmap_write(hdptx->regmap, CMN_REG(0059), - (cfg->pms_pdiv << 4) | cfg->pms_refdiv); - regmap_write(hdptx->regmap, CMN_REG(005a), cfg->pms_sdiv << 4); + FIELD_PREP(ANA_ROPLL_PMS_PDIV_MASK, cfg->pms_pdiv) | + FIELD_PREP(ANA_ROPLL_PMS_REFDIV_MASK, cfg->pms_refdiv)); + regmap_write(hdptx->regmap, CMN_REG(005a), + FIELD_PREP(ROPLL_PMS_SDIV_RBR_MASK, cfg->pms_sdiv)); regmap_update_bits(hdptx->regmap, CMN_REG(005e), ROPLL_SDM_EN_MASK, FIELD_PREP(ROPLL_SDM_EN_MASK, cfg->sdm_en)); @@ -1260,13 +1286,19 @@ static int rk_hdptx_tmds_ropll_cmn_config(struct rk_hdptx_phy *hdptx) static int rk_hdptx_pll_cmn_config(struct rk_hdptx_phy *hdptx) { + int ret; + if (hdptx->hdmi_cfg.rate <= HDMI20_MAX_RATE) - return rk_hdptx_tmds_ropll_cmn_config(hdptx); + ret = rk_hdptx_tmds_ropll_cmn_config(hdptx); + else if (hdptx->hdmi_cfg.rate == FRL_8G4L_RATE) + ret = rk_hdptx_frl_lcpll_ropll_cmn_config(hdptx); + else + ret = rk_hdptx_frl_lcpll_cmn_config(hdptx); - if (hdptx->hdmi_cfg.rate == FRL_8G4L_RATE) - return rk_hdptx_frl_lcpll_ropll_cmn_config(hdptx); + if (!ret) + hdptx->pll_config_dirty = false; - return rk_hdptx_frl_lcpll_cmn_config(hdptx); + return ret; } static int rk_hdptx_frl_lcpll_mode_config(struct rk_hdptx_phy *hdptx) @@ -1302,6 +1334,45 @@ static int rk_hdptx_tmds_ropll_mode_config(struct rk_hdptx_phy *hdptx) return rk_hdptx_post_enable_lane(hdptx); } +static int rk_hdptx_frl_ffe_config(struct rk_hdptx_phy *hdptx, u8 ffe_level) +{ + u8 val; + + if (ffe_level > FRL_FFE_MAX_LEVEL) + return -EINVAL; + + val = rk_hdptx_frl_ffe_cfg[ffe_level].pre_shoot; + + regmap_update_bits(hdptx->regmap, LANE_REG(0305), + LN_TX_DRV_PRE_LVL_CTRL_MASK, + FIELD_PREP(LN_TX_DRV_PRE_LVL_CTRL_MASK, val)); + regmap_update_bits(hdptx->regmap, LANE_REG(0405), + LN_TX_DRV_PRE_LVL_CTRL_MASK, + FIELD_PREP(LN_TX_DRV_PRE_LVL_CTRL_MASK, val)); + regmap_update_bits(hdptx->regmap, LANE_REG(0505), + LN_TX_DRV_PRE_LVL_CTRL_MASK, + FIELD_PREP(LN_TX_DRV_PRE_LVL_CTRL_MASK, val)); + regmap_update_bits(hdptx->regmap, LANE_REG(0605), + LN_TX_DRV_PRE_LVL_CTRL_MASK, + FIELD_PREP(LN_TX_DRV_PRE_LVL_CTRL_MASK, val)); + + val = rk_hdptx_frl_ffe_cfg[ffe_level].de_emphasis; + + regmap_update_bits(hdptx->regmap, LANE_REG(0304), + LN_TX_DRV_POST_LVL_CTRL_MASK, + FIELD_PREP(LN_TX_DRV_POST_LVL_CTRL_MASK, val)); + regmap_update_bits(hdptx->regmap, LANE_REG(0404), + LN_TX_DRV_POST_LVL_CTRL_MASK, + FIELD_PREP(LN_TX_DRV_POST_LVL_CTRL_MASK, val)); + regmap_update_bits(hdptx->regmap, LANE_REG(0504), + LN_TX_DRV_POST_LVL_CTRL_MASK, + FIELD_PREP(LN_TX_DRV_POST_LVL_CTRL_MASK, val)); + regmap_update_bits(hdptx->regmap, LANE_REG(0604), + LN_TX_DRV_POST_LVL_CTRL_MASK, + FIELD_PREP(LN_TX_DRV_POST_LVL_CTRL_MASK, val)); + return 0; +} + static void rk_hdptx_dp_reset(struct rk_hdptx_phy *hdptx) { reset_control_assert(hdptx->rsts[RST_LANE].rstc); @@ -1330,11 +1401,9 @@ static void rk_hdptx_dp_reset(struct rk_hdptx_phy *hdptx) FIELD_PREP(LN_TX_DRV_EI_EN_MASK, 0)); regmap_write(hdptx->grf, GRF_HDPTX_CON0, - HDPTX_I_PLL_EN << 16 | FIELD_PREP(HDPTX_I_PLL_EN, 0x0)); - regmap_write(hdptx->grf, GRF_HDPTX_CON0, - HDPTX_I_BIAS_EN << 16 | FIELD_PREP(HDPTX_I_BIAS_EN, 0x0)); - regmap_write(hdptx->grf, GRF_HDPTX_CON0, - HDPTX_I_BGR_EN << 16 | FIELD_PREP(HDPTX_I_BGR_EN, 0x0)); + FIELD_PREP_WM16(HDPTX_I_PLL_EN, 0) | + FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 0) | + FIELD_PREP_WM16(HDPTX_I_BGR_EN, 0)); } static int rk_hdptx_phy_consumer_get(struct rk_hdptx_phy *hdptx) @@ -1347,25 +1416,17 @@ static int rk_hdptx_phy_consumer_get(struct rk_hdptx_phy *hdptx) return 0; ret = regmap_read(hdptx->grf, GRF_HDPTX_STATUS, &status); - if (ret) - goto dec_usage; - - if (status & HDPTX_O_PLL_LOCK_DONE) - dev_warn(hdptx->dev, "PLL locked by unknown consumer!\n"); + if (ret) { + atomic_dec(&hdptx->usage_count); + return ret; + } - if (mode == PHY_MODE_DP) { + if (mode == PHY_MODE_DP) rk_hdptx_dp_reset(hdptx); - } else { - ret = rk_hdptx_pll_cmn_config(hdptx); - if (ret) - goto dec_usage; - } + else + rk_hdptx_pll_cmn_config(hdptx); return 0; - -dec_usage: - atomic_dec(&hdptx->usage_count); - return ret; } static int rk_hdptx_phy_consumer_put(struct rk_hdptx_phy *hdptx, bool force) @@ -1613,9 +1674,8 @@ static int rk_hdptx_dp_aux_init(struct rk_hdptx_phy *hdptx) FIELD_PREP(OVRD_SB_VREG_EN_MASK, 0x1)); regmap_write(hdptx->grf, GRF_HDPTX_CON0, - HDPTX_I_BGR_EN << 16 | FIELD_PREP(HDPTX_I_BGR_EN, 0x1)); - regmap_write(hdptx->grf, GRF_HDPTX_CON0, - HDPTX_I_BIAS_EN << 16 | FIELD_PREP(HDPTX_I_BIAS_EN, 0x1)); + FIELD_PREP_WM16(HDPTX_I_BGR_EN, 1) | + FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 1)); usleep_range(20, 25); reset_control_deassert(hdptx->rsts[RST_INIT].rstc); @@ -1656,29 +1716,13 @@ static int rk_hdptx_phy_power_on(struct phy *phy) enum phy_mode mode = phy_get_mode(phy); int ret, lane; - if (mode != PHY_MODE_DP) { - if (!hdptx->hdmi_cfg.rate && hdptx->hdmi_cfg.mode != PHY_HDMI_MODE_FRL) { - /* - * FIXME: Temporary workaround to setup TMDS char rate - * from the RK DW HDMI QP bridge driver. - * Will be removed as soon the switch to the HDMI PHY - * configuration API has been completed on both ends. - */ - hdptx->hdmi_cfg.rate = phy_get_bus_width(hdptx->phy) & 0xfffffff; - hdptx->hdmi_cfg.rate *= 100; - } - - dev_dbg(hdptx->dev, "%s rate=%llu bpc=%u\n", __func__, - hdptx->hdmi_cfg.rate, hdptx->hdmi_cfg.bpc); - } - ret = rk_hdptx_phy_consumer_get(hdptx); if (ret) return ret; if (mode == PHY_MODE_DP) { regmap_write(hdptx->grf, GRF_HDPTX_CON0, - HDPTX_MODE_SEL << 16 | FIELD_PREP(HDPTX_MODE_SEL, 0x1)); + FIELD_PREP_WM16(HDPTX_MODE_SEL, 1)); for (lane = 0; lane < 4; lane++) { regmap_update_bits(hdptx->regmap, LANE_REG(031e) + 0x400 * lane, @@ -1697,21 +1741,27 @@ static int rk_hdptx_phy_power_on(struct phy *phy) rk_hdptx_dp_pll_init(hdptx); ret = rk_hdptx_dp_aux_init(hdptx); - if (ret) - rk_hdptx_phy_consumer_put(hdptx, true); } else { - regmap_write(hdptx->grf, GRF_HDPTX_CON0, - HDPTX_MODE_SEL << 16 | FIELD_PREP(HDPTX_MODE_SEL, 0x0)); + dev_dbg(hdptx->dev, "%s rate=%llu bpc=%u\n", __func__, + hdptx->hdmi_cfg.rate, hdptx->hdmi_cfg.bpc); - if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL) - ret = rk_hdptx_frl_lcpll_mode_config(hdptx); - else - ret = rk_hdptx_tmds_ropll_mode_config(hdptx); + if (hdptx->pll_config_dirty) + ret = rk_hdptx_pll_cmn_config(hdptx); - if (ret) - rk_hdptx_phy_consumer_put(hdptx, true); + if (!ret) { + regmap_write(hdptx->grf, GRF_HDPTX_CON0, + FIELD_PREP_WM16(HDPTX_MODE_SEL, 0)); + + if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL) + ret = rk_hdptx_frl_lcpll_mode_config(hdptx); + else + ret = rk_hdptx_tmds_ropll_mode_config(hdptx); + } } + if (ret) + rk_hdptx_phy_consumer_put(hdptx, true); + return ret; } @@ -1719,6 +1769,8 @@ static int rk_hdptx_phy_power_off(struct phy *phy) { struct rk_hdptx_phy *hdptx = phy_get_drvdata(phy); + dev_dbg(hdptx->dev, "power_off\n"); + return rk_hdptx_phy_consumer_put(hdptx, false); } @@ -1732,6 +1784,13 @@ static int rk_hdptx_phy_verify_hdmi_config(struct rk_hdptx_phy *hdptx, unsigned long long frl_rate = 100000000ULL * hdmi_in->frl.lanes * hdmi_in->frl.rate_per_lane; + if (hdmi_in->frl.set_ffe_level) { + if (hdmi_in->frl.ffe_level > FRL_FFE_MAX_LEVEL) + return -EINVAL; + + return 0; + } + switch (hdmi_in->frl.rate_per_lane) { case 3: case 6: @@ -1835,8 +1894,7 @@ static int rk_hdptx_phy_set_rate(struct rk_hdptx_phy *hdptx, u32 bw, status; int ret; - regmap_write(hdptx->grf, GRF_HDPTX_CON0, - HDPTX_I_PLL_EN << 16 | FIELD_PREP(HDPTX_I_PLL_EN, 0x0)); + regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(HDPTX_I_PLL_EN, 0)); switch (dp->link_rate) { case 1620: @@ -1892,8 +1950,7 @@ static int rk_hdptx_phy_set_rate(struct rk_hdptx_phy *hdptx, regmap_update_bits(hdptx->regmap, CMN_REG(0095), DP_TX_LINK_BW_MASK, FIELD_PREP(DP_TX_LINK_BW_MASK, bw)); - regmap_write(hdptx->grf, GRF_HDPTX_CON0, - HDPTX_I_PLL_EN << 16 | FIELD_PREP(HDPTX_I_PLL_EN, 0x1)); + regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(HDPTX_I_PLL_EN, 1)); ret = regmap_read_poll_timeout(hdptx->grf, GRF_HDPTX_STATUS, status, FIELD_GET(HDPTX_O_PLL_LOCK_DONE, status), @@ -2080,9 +2137,18 @@ static int rk_hdptx_phy_configure(struct phy *phy, union phy_configure_opts *opt if (ret) { dev_err(hdptx->dev, "invalid hdmi params for phy configure\n"); } else { - hdptx->restrict_rate_change = true; - dev_dbg(hdptx->dev, "%s rate=%llu bpc=%u\n", __func__, - hdptx->hdmi_cfg.rate, hdptx->hdmi_cfg.bpc); + if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL && + opts->hdmi.frl.set_ffe_level) { + dev_dbg(hdptx->dev, "%s ffe_level=%u\n", __func__, + opts->hdmi.frl.ffe_level); + ret = rk_hdptx_frl_ffe_config(hdptx, opts->hdmi.frl.ffe_level); + } else { + hdptx->pll_config_dirty = true; + + dev_dbg(hdptx->dev, "%s %s rate=%llu bpc=%u\n", __func__, + hdptx->hdmi_cfg.mode ? "FRL" : "TMDS", + hdptx->hdmi_cfg.rate, hdptx->hdmi_cfg.bpc); + } } return ret; @@ -2151,6 +2217,8 @@ static int rk_hdptx_phy_clk_prepare(struct clk_hw *hw) { struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw); + dev_dbg(hdptx->dev, "clk_prepare\n"); + return rk_hdptx_phy_consumer_get(hdptx); } @@ -2158,6 +2226,8 @@ static void rk_hdptx_phy_clk_unprepare(struct clk_hw *hw) { struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw); + dev_dbg(hdptx->dev, "clk_unprepare\n"); + rk_hdptx_phy_consumer_put(hdptx, true); } @@ -2168,7 +2238,7 @@ static u64 rk_hdptx_phy_clk_calc_rate_from_pll_cfg(struct rk_hdptx_phy *hdptx) struct lcpll_config lcpll_hw; struct ropll_config ropll_hw; u64 fout, sdm; - u32 mode, val; + u32 mode, bpc, val; int ret, i; ret = regmap_read(hdptx->regmap, CMN_REG(0008), &mode); @@ -2204,7 +2274,7 @@ static u64 rk_hdptx_phy_clk_calc_rate_from_pll_cfg(struct rk_hdptx_phy *hdptx) ret = regmap_read(hdptx->regmap, CMN_REG(002D), &val); if (ret) return 0; - lcpll_hw.sdc_n = (val & LCPLL_SDC_N_MASK) >> 1; + lcpll_hw.sdc_n = FIELD_GET(LCPLL_SDC_N_MASK, val); for (i = 0; i < ARRAY_SIZE(rk_hdptx_frl_lcpll_cfg); i++) { const struct lcpll_config *cfg = &rk_hdptx_frl_lcpll_cfg[i]; @@ -2265,7 +2335,8 @@ static u64 rk_hdptx_phy_clk_calc_rate_from_pll_cfg(struct rk_hdptx_phy *hdptx) ret = regmap_read(hdptx->regmap, CMN_REG(0086), &val); if (ret) return 0; - ropll_hw.pms_sdiv = ((val & PLL_PCG_POSTDIV_SEL_MASK) >> 4) + 1; + ropll_hw.pms_sdiv = FIELD_GET(PLL_PCG_POSTDIV_SEL_MASK, val) + 1; + bpc = (FIELD_GET(PLL_PCG_CLK_SEL_MASK, val) << 1) + 8; fout = PLL_REF_CLK * ropll_hw.pms_mdiv; if (ropll_hw.sdm_en) { @@ -2280,7 +2351,7 @@ static u64 rk_hdptx_phy_clk_calc_rate_from_pll_cfg(struct rk_hdptx_phy *hdptx) fout = fout + sdm; } - return div_u64(fout * 2, ropll_hw.pms_sdiv * 10); + return div_u64(fout * 2 * 8, ropll_hw.pms_sdiv * 10 * bpc); } static unsigned long rk_hdptx_phy_clk_recalc_rate(struct clk_hw *hw, @@ -2292,15 +2363,16 @@ static unsigned long rk_hdptx_phy_clk_recalc_rate(struct clk_hw *hw, int ret; ret = regmap_read(hdptx->grf, GRF_HDPTX_CON0, &status); - if (ret || !(status & HDPTX_I_PLL_EN)) + if (ret || !(status & HDPTX_I_PLL_EN)) { + dev_dbg(hdptx->dev, "%s ret=%d status=%x\n", __func__, ret, status); return 0; + } rate = rk_hdptx_phy_clk_calc_rate_from_pll_cfg(hdptx); - if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL) - return rate; + dev_dbg(hdptx->dev, "%s from_pll=%llu\n", __func__, rate); - return DIV_ROUND_CLOSEST_ULL(rate * 8, hdptx->hdmi_cfg.bpc); + return rate; } static int rk_hdptx_phy_clk_determine_rate(struct clk_hw *hw, @@ -2308,31 +2380,22 @@ static int rk_hdptx_phy_clk_determine_rate(struct clk_hw *hw, { struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw); - if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL) - return hdptx->hdmi_cfg.rate; - + dev_dbg(hdptx->dev, "%s req=%lu par=%lu cfg=%llu dirty=%d\n", __func__, + req->rate, req->best_parent_rate, hdptx->hdmi_cfg.rate, hdptx->pll_config_dirty); /* - * FIXME: Temporarily allow altering TMDS char rate via CCF. - * To be dropped as soon as the RK DW HDMI QP bridge driver - * switches to make use of phy_configure(). + * For uncommitted PLL configuration, invalidate the current clock rate + * to ensure rk_hdptx_phy_clk_set_rate() will be always invoked. + * Otherwise, restrict the rate according to the PHY link setup. */ - if (!hdptx->restrict_rate_change && req->rate != hdptx->hdmi_cfg.rate) { - struct phy_configure_opts_hdmi hdmi = { - .tmds_char_rate = req->rate, - }; - int ret = rk_hdptx_phy_verify_hdmi_config(hdptx, &hdmi, &hdptx->hdmi_cfg); - - if (ret) - return ret; - } + if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL) + req->rate = hdptx->hdmi_cfg.rate; + else + req->rate = DIV_ROUND_CLOSEST_ULL(hdptx->hdmi_cfg.rate * 8, + hdptx->hdmi_cfg.bpc); - /* - * The TMDS char rate shall be adjusted via phy_configure() only, - * hence ensure rk_hdptx_phy_clk_set_rate() won't be invoked with - * a different rate argument. - */ - req->rate = DIV_ROUND_CLOSEST_ULL(hdptx->hdmi_cfg.rate * 8, hdptx->hdmi_cfg.bpc); + if (hdptx->pll_config_dirty && req->rate == clk_hw_get_rate(hw)) + req->rate = 0; return 0; } @@ -2341,18 +2404,9 @@ static int rk_hdptx_phy_clk_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw); - unsigned long long link_rate = rate; - - if (hdptx->hdmi_cfg.mode != PHY_HDMI_MODE_FRL) - link_rate = DIV_ROUND_CLOSEST_ULL(rate * hdptx->hdmi_cfg.bpc, 8); - - /* Revert any unlikely link rate change since determine_rate() */ - if (hdptx->hdmi_cfg.rate != link_rate) { - dev_warn(hdptx->dev, "Reverting unexpected rate change from %llu to %llu\n", - link_rate, hdptx->hdmi_cfg.rate); - hdptx->hdmi_cfg.rate = link_rate; - } + dev_dbg(hdptx->dev, "%s req=%lu par=%lu cfg=%llu dirty=%d\n", __func__, + rate, parent_rate, hdptx->hdmi_cfg.rate, hdptx->pll_config_dirty); /* * The link rate would be normally programmed in HW during * phy_ops.power_on() or clk_ops.prepare() callbacks, but it might @@ -2404,6 +2458,8 @@ static int rk_hdptx_phy_runtime_suspend(struct device *dev) { struct rk_hdptx_phy *hdptx = dev_get_drvdata(dev); + dev_dbg(hdptx->dev, "suspend\n"); + clk_bulk_disable_unprepare(hdptx->nr_clks, hdptx->clks); return 0; @@ -2414,6 +2470,8 @@ static int rk_hdptx_phy_runtime_resume(struct device *dev) struct rk_hdptx_phy *hdptx = dev_get_drvdata(dev); int ret; + dev_dbg(hdptx->dev, "resume\n"); + ret = clk_bulk_prepare_enable(hdptx->nr_clks, hdptx->clks); if (ret) dev_err(hdptx->dev, "Failed to enable clocks: %d\n", ret); diff --git a/drivers/phy/rockchip/phy-rockchip-snps-pcie3.c b/drivers/phy/rockchip/phy-rockchip-snps-pcie3.c index 4e8ffd173096a4..36b2142ec913ac 100644 --- a/drivers/phy/rockchip/phy-rockchip-snps-pcie3.c +++ b/drivers/phy/rockchip/phy-rockchip-snps-pcie3.c @@ -19,6 +19,9 @@ #include #include +/* Common definition */ +#define RK_SRAM_INIT_TIMEOUT_US 20000 + /* Register for RK3568 */ #define GRF_PCIE30PHY_CON1 0x4 #define GRF_PCIE30PHY_CON6 0x18 @@ -28,6 +31,7 @@ #define GRF_PCIE30PHY_WR_EN (0xf << 16) #define SRAM_INIT_DONE(reg) (reg & BIT(14)) + #define RK3568_BIFURCATION_LANE_0_1 BIT(0) /* Register for RK3588 */ @@ -39,7 +43,7 @@ #define RK3588_PCIE3PHY_GRF_PHY0_LN1_CON1 0x1104 #define RK3588_PCIE3PHY_GRF_PHY1_LN0_CON1 0x2004 #define RK3588_PCIE3PHY_GRF_PHY1_LN1_CON1 0x2104 -#define RK3588_SRAM_INIT_DONE(reg) (reg & BIT(0)) +#define RK3588_SRAM_INIT_DONE(reg) ((reg & 0xf) == 0xf) #define RK3588_BIFURCATION_LANE_0_1 BIT(0) #define RK3588_BIFURCATION_LANE_2_3 BIT(1) @@ -71,6 +75,7 @@ struct rockchip_p3phy_priv { struct rockchip_p3phy_ops { int (*phy_init)(struct rockchip_p3phy_priv *priv); + int (*phy_calibrate)(struct rockchip_p3phy_priv *priv); }; static int rockchip_p3phy_set_mode(struct phy *phy, enum phy_mode mode, int submode) @@ -97,8 +102,6 @@ static int rockchip_p3phy_rk3568_init(struct rockchip_p3phy_priv *priv) { struct phy *phy = priv->phy; bool bifurcation = false; - int ret; - u32 reg; /* Deassert PCIe PMA output clamp mode */ regmap_write(priv->phy_grf, GRF_PCIE30PHY_CON9, GRF_PCIE30PHY_DA_OCM); @@ -124,25 +127,34 @@ static int rockchip_p3phy_rk3568_init(struct rockchip_p3phy_priv *priv) reset_control_deassert(priv->p30phy); + return 0; +} + +static int rockchip_p3phy_rk3568_calibrate(struct rockchip_p3phy_priv *priv) +{ + int ret; + u32 reg; + ret = regmap_read_poll_timeout(priv->phy_grf, GRF_PCIE30PHY_STATUS0, reg, SRAM_INIT_DONE(reg), - 0, 500); + 0, RK_SRAM_INIT_TIMEOUT_US); if (ret) - dev_err(&priv->phy->dev, "%s: lock failed 0x%x, check input refclk and power supply\n", - __func__, reg); + dev_err(&priv->phy->dev, "lock failed 0x%x, check input refclk and power supply\n", + reg); + return ret; } static const struct rockchip_p3phy_ops rk3568_ops = { .phy_init = rockchip_p3phy_rk3568_init, + .phy_calibrate = rockchip_p3phy_rk3568_calibrate, }; static int rockchip_p3phy_rk3588_init(struct rockchip_p3phy_priv *priv) { u32 reg = 0; u8 mode = RK3588_LANE_AGGREGATION; /* default */ - int ret; regmap_write(priv->phy_grf, RK3588_PCIE3PHY_GRF_PHY0_LN0_CON1, priv->rx_cmn_refclk_mode[0] ? RK3588_RX_CMN_REFCLK_MODE_EN : @@ -171,6 +183,7 @@ static int rockchip_p3phy_rk3588_init(struct rockchip_p3phy_priv *priv) } reg = mode; + priv->pcie30_phymode = mode; regmap_write(priv->phy_grf, RK3588_PCIE3PHY_GRF_CMN_CON0, RK3588_PCIE30_PHY_MODE_EN | reg); @@ -184,14 +197,25 @@ static int rockchip_p3phy_rk3588_init(struct rockchip_p3phy_priv *priv) reset_control_deassert(priv->p30phy); + return 0; +} + +static int rockchip_p3phy_rk3588_calibrate(struct rockchip_p3phy_priv *priv) +{ + int ret; + u32 reg; + ret = regmap_read_poll_timeout(priv->phy_grf, RK3588_PCIE3PHY_GRF_PHY0_STATUS1, reg, RK3588_SRAM_INIT_DONE(reg), - 0, 500); - ret |= regmap_read_poll_timeout(priv->phy_grf, - RK3588_PCIE3PHY_GRF_PHY1_STATUS1, - reg, RK3588_SRAM_INIT_DONE(reg), - 0, 500); + 0, RK_SRAM_INIT_TIMEOUT_US); + if (priv->pcie30_phymode & (RK3588_LANE_AGGREGATION | RK3588_BIFURCATION_LANE_2_3)) { + ret |= regmap_read_poll_timeout(priv->phy_grf, + RK3588_PCIE3PHY_GRF_PHY1_STATUS1, + reg, RK3588_SRAM_INIT_DONE(reg), + 0, RK_SRAM_INIT_TIMEOUT_US); + } + if (ret) dev_err(&priv->phy->dev, "lock failed 0x%x, check input refclk and power supply\n", reg); @@ -200,6 +224,7 @@ static int rockchip_p3phy_rk3588_init(struct rockchip_p3phy_priv *priv) static const struct rockchip_p3phy_ops rk3588_ops = { .phy_init = rockchip_p3phy_rk3588_init, + .phy_calibrate = rockchip_p3phy_rk3588_calibrate, }; static int rockchip_p3phy_init(struct phy *phy) @@ -234,10 +259,22 @@ static int rockchip_p3phy_exit(struct phy *phy) return 0; } +static int rockchip_p3phy_calibrate(struct phy *phy) +{ + struct rockchip_p3phy_priv *priv = phy_get_drvdata(phy); + int ret = 0; + + if (priv->ops->phy_calibrate) + ret = priv->ops->phy_calibrate(priv); + + return ret; +} + static const struct phy_ops rockchip_p3phy_ops = { .init = rockchip_p3phy_init, .exit = rockchip_p3phy_exit, .set_mode = rockchip_p3phy_set_mode, + .calibrate = rockchip_p3phy_calibrate, .owner = THIS_MODULE, }; diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index fba35510d88ced..f318b04c097d00 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -6,12 +6,15 @@ * Copyright (C) 2024 Collabora Ltd */ +#include #include #include #include +#include #include #include #include +#include #include #include #include @@ -75,7 +78,6 @@ #define TRSV_LN2_MON_RX_CDR_DONE_OFFSET 0x1b84 /* trsv_reg06E1 */ #define TRSV_LN2_MON_RX_CDR_LOCK_DONE BIT(0) -#define BIT_WRITEABLE_SHIFT 16 #define PHY_AUX_DP_DATA_POL_NORMAL 0 #define PHY_AUX_DP_DATA_POL_INVERT 1 #define PHY_LANE_MUX_USB 0 @@ -104,8 +106,8 @@ struct rk_udphy_grf_reg { #define _RK_UDPHY_GEN_GRF_REG(offset, mask, disable, enable) \ {\ offset, \ - FIELD_PREP_CONST(mask, disable) | (mask << BIT_WRITEABLE_SHIFT), \ - FIELD_PREP_CONST(mask, enable) | (mask << BIT_WRITEABLE_SHIFT), \ + FIELD_PREP_WM16_CONST(mask, disable), \ + FIELD_PREP_WM16_CONST(mask, enable), \ } #define RK_UDPHY_GEN_GRF_REG(offset, bitend, bitstart, disable, enable) \ @@ -171,7 +173,7 @@ struct rk_udphy { /* PHY status management */ bool flip; - bool mode_change; + bool phy_needs_reinit; u8 mode; u8 status; @@ -185,14 +187,13 @@ struct rk_udphy { u32 dp_lane_sel[4]; u32 dp_aux_dout_sel; u32 dp_aux_din_sel; - bool dp_sink_hpd_sel; - bool dp_sink_hpd_cfg; unsigned int link_rate; unsigned int lanes; u8 bw; int id; bool dp_in_use; + int dp_lanes; /* PHY const config */ const struct rk_udphy_cfg *cfgs; @@ -350,7 +351,8 @@ static const struct reg_sequence rk_udphy_24m_refclk_cfg[] = { {0x0a64, 0xa8}, {0x1a3c, 0xd0}, {0x1a44, 0xd0}, {0x1a48, 0x01}, {0x1a4c, 0x0d}, {0x1a54, 0xe0}, - {0x1a5c, 0xe0}, {0x1a64, 0xa8} + {0x1a5c, 0xe0}, {0x1a64, 0xa8}, + {0x00d4, 0x30} }; static const struct reg_sequence rk_udphy_26m_refclk_cfg[] = { @@ -377,7 +379,7 @@ static const struct reg_sequence rk_udphy_26m_refclk_cfg[] = { {0x0c30, 0x0e}, {0x0c48, 0x06}, {0x1c30, 0x0e}, {0x1c48, 0x06}, {0x028c, 0x18}, {0x0af0, 0x00}, - {0x1af0, 0x00} + {0x1af0, 0x00}, {0x00d4, 0x33} }; static const struct reg_sequence rk_udphy_init_sequence[] = { @@ -412,8 +414,8 @@ static const struct reg_sequence rk_udphy_init_sequence[] = { {0x0070, 0x7d}, {0x0074, 0x68}, {0x0af4, 0x1a}, {0x1af4, 0x1a}, {0x0440, 0x3f}, {0x10d4, 0x08}, - {0x20d4, 0x08}, {0x00d4, 0x30}, - {0x0024, 0x6e}, + {0x20d4, 0x08}, {0x0024, 0x6e}, + {0x09c0, 0x0a}, {0x19c0, 0x0a} }; static inline int rk_udphy_grfreg_write(struct regmap *base, @@ -536,60 +538,31 @@ static void rk_udphy_usb_bvalid_enable(struct rk_udphy *udphy, u8 enable) * <0 1> dpln0 dpln1 usbrx usbtx * <2 3> usbrx usbtx dpln0 dpln1 * --------------------------------------------------------------------------- + * if 1 lane for dp function, 2 lane for usb function, define rockchip,dp-lane-mux = ; + * sample as follow: + * --------------------------------------------------------------------------- + * B11-B10 A2-A3 A11-A10 B2-B3 + * rockchip,dp-lane-mux ln0(tx/rx) ln1(tx) ln2(tx/rx) ln3(tx) + * <0> dpln0 \ usbrx usbtx + * --------------------------------------------------------------------------- */ -static void rk_udphy_dplane_select(struct rk_udphy *udphy) +static void rk_udphy_dp_lane_select(struct rk_udphy *udphy) { const struct rk_udphy_cfg *cfg = udphy->cfgs; - u32 value = 0; - - switch (udphy->mode) { - case UDPHY_MODE_DP: - value |= 2 << udphy->dp_lane_sel[2] * 2; - value |= 3 << udphy->dp_lane_sel[3] * 2; - fallthrough; - - case UDPHY_MODE_DP_USB: - value |= 0 << udphy->dp_lane_sel[0] * 2; - value |= 1 << udphy->dp_lane_sel[1] * 2; - break; - - case UDPHY_MODE_USB: - break; - - default: - break; - } - - regmap_write(udphy->vogrf, cfg->vogrfcfg[udphy->id].dp_lane_reg, - ((DP_AUX_DIN_SEL | DP_AUX_DOUT_SEL | DP_LANE_SEL_ALL) << 16) | - FIELD_PREP(DP_AUX_DIN_SEL, udphy->dp_aux_din_sel) | - FIELD_PREP(DP_AUX_DOUT_SEL, udphy->dp_aux_dout_sel) | value); -} - -static int rk_udphy_dplane_get(struct rk_udphy *udphy) -{ - int dp_lanes; - - switch (udphy->mode) { - case UDPHY_MODE_DP: - dp_lanes = 4; - break; + u32 value = FIELD_PREP_WM16(DP_LANE_SEL_ALL, 0); + int i; - case UDPHY_MODE_DP_USB: - dp_lanes = 2; - break; + for (i = 0; i < udphy->dp_lanes; i++) + value |= field_prep(DP_LANE_SEL_N(udphy->dp_lane_sel[i]), i); - case UDPHY_MODE_USB: - default: - dp_lanes = 0; - break; - } + value |= FIELD_PREP_WM16(DP_AUX_DIN_SEL, udphy->dp_aux_din_sel); + value |= FIELD_PREP_WM16(DP_AUX_DOUT_SEL, udphy->dp_aux_dout_sel); - return dp_lanes; + regmap_write(udphy->vogrf, cfg->vogrfcfg[udphy->id].dp_lane_reg, value); } -static void rk_udphy_dplane_enable(struct rk_udphy *udphy, int dp_lanes) +static void rk_udphy_dp_lane_enable(struct rk_udphy *udphy, int dp_lanes) { u32 val = 0; int i; @@ -605,17 +578,49 @@ static void rk_udphy_dplane_enable(struct rk_udphy *udphy, int dp_lanes) CMN_DP_CMN_RSTN, FIELD_PREP(CMN_DP_CMN_RSTN, 0x0)); } -static void rk_udphy_dp_hpd_event_trigger(struct rk_udphy *udphy, bool hpd) +static void rk_udphy_mode_set(struct rk_udphy *udphy, u8 mode) { - const struct rk_udphy_cfg *cfg = udphy->cfgs; + if (udphy->mode == mode) + return; - udphy->dp_sink_hpd_sel = true; - udphy->dp_sink_hpd_cfg = hpd; + udphy->phy_needs_reinit = true; + udphy->mode = mode; +} - if (!udphy->dp_in_use) - return; +static void rk_udphy_set_typec_state(struct rk_udphy *udphy, unsigned long state) +{ + u8 mode; - rk_udphy_grfreg_write(udphy->vogrf, &cfg->vogrfcfg[udphy->id].hpd_trigger, hpd); + switch (state) { + case TYPEC_DP_STATE_C: + case TYPEC_DP_STATE_E: + udphy->lane_mux_sel[0] = PHY_LANE_MUX_DP; + udphy->lane_mux_sel[1] = PHY_LANE_MUX_DP; + udphy->lane_mux_sel[2] = PHY_LANE_MUX_DP; + udphy->lane_mux_sel[3] = PHY_LANE_MUX_DP; + mode = UDPHY_MODE_DP; + udphy->dp_lanes = 4; + break; + + case TYPEC_DP_STATE_D: + default: + if (udphy->flip) { + udphy->lane_mux_sel[0] = PHY_LANE_MUX_DP; + udphy->lane_mux_sel[1] = PHY_LANE_MUX_DP; + udphy->lane_mux_sel[2] = PHY_LANE_MUX_USB; + udphy->lane_mux_sel[3] = PHY_LANE_MUX_USB; + } else { + udphy->lane_mux_sel[0] = PHY_LANE_MUX_USB; + udphy->lane_mux_sel[1] = PHY_LANE_MUX_USB; + udphy->lane_mux_sel[2] = PHY_LANE_MUX_DP; + udphy->lane_mux_sel[3] = PHY_LANE_MUX_DP; + } + mode = UDPHY_MODE_DP_USB; + udphy->dp_lanes = 2; + break; + } + + rk_udphy_mode_set(udphy, mode); } static void rk_udphy_set_typec_default_mapping(struct rk_udphy *udphy) @@ -625,10 +630,6 @@ static void rk_udphy_set_typec_default_mapping(struct rk_udphy *udphy) udphy->dp_lane_sel[1] = 1; udphy->dp_lane_sel[2] = 3; udphy->dp_lane_sel[3] = 2; - udphy->lane_mux_sel[0] = PHY_LANE_MUX_DP; - udphy->lane_mux_sel[1] = PHY_LANE_MUX_DP; - udphy->lane_mux_sel[2] = PHY_LANE_MUX_USB; - udphy->lane_mux_sel[3] = PHY_LANE_MUX_USB; udphy->dp_aux_dout_sel = PHY_AUX_DP_DATA_POL_INVERT; udphy->dp_aux_din_sel = PHY_AUX_DP_DATA_POL_INVERT; gpiod_set_value_cansleep(udphy->sbu1_dc_gpio, 1); @@ -638,40 +639,40 @@ static void rk_udphy_set_typec_default_mapping(struct rk_udphy *udphy) udphy->dp_lane_sel[1] = 3; udphy->dp_lane_sel[2] = 1; udphy->dp_lane_sel[3] = 0; - udphy->lane_mux_sel[0] = PHY_LANE_MUX_USB; - udphy->lane_mux_sel[1] = PHY_LANE_MUX_USB; - udphy->lane_mux_sel[2] = PHY_LANE_MUX_DP; - udphy->lane_mux_sel[3] = PHY_LANE_MUX_DP; udphy->dp_aux_dout_sel = PHY_AUX_DP_DATA_POL_NORMAL; udphy->dp_aux_din_sel = PHY_AUX_DP_DATA_POL_NORMAL; gpiod_set_value_cansleep(udphy->sbu1_dc_gpio, 0); gpiod_set_value_cansleep(udphy->sbu2_dc_gpio, 1); } - udphy->mode = UDPHY_MODE_DP_USB; + /* default to USB3 + DP as 4 lane USB is not supported */ + rk_udphy_set_typec_state(udphy, TYPEC_DP_STATE_D); } static int rk_udphy_orien_sw_set(struct typec_switch_dev *sw, enum typec_orientation orien) { struct rk_udphy *udphy = typec_switch_get_drvdata(sw); + bool flipped = orien == TYPEC_ORIENTATION_REVERSE; - mutex_lock(&udphy->mutex); + guard(mutex)(&udphy->mutex); if (orien == TYPEC_ORIENTATION_NONE) { gpiod_set_value_cansleep(udphy->sbu1_dc_gpio, 0); gpiod_set_value_cansleep(udphy->sbu2_dc_gpio, 0); /* unattached */ rk_udphy_usb_bvalid_enable(udphy, false); - goto unlock_ret; + + return 0; } - udphy->flip = orien == TYPEC_ORIENTATION_REVERSE; + if (udphy->flip != flipped) + udphy->phy_needs_reinit = true; + + udphy->flip = flipped; rk_udphy_set_typec_default_mapping(udphy); rk_udphy_usb_bvalid_enable(udphy, true); -unlock_ret: - mutex_unlock(&udphy->mutex); return 0; } @@ -887,7 +888,7 @@ static int rk_udphy_parse_lane_mux_data(struct rk_udphy *udphy) return 0; } - if (num_lanes != 2 && num_lanes != 4) + if (num_lanes != 1 && num_lanes != 2 && num_lanes != 4) return dev_err_probe(udphy->dev, -EINVAL, "invalid number of lane mux\n"); @@ -913,7 +914,8 @@ static int rk_udphy_parse_lane_mux_data(struct rk_udphy *udphy) } udphy->mode = UDPHY_MODE_DP; - if (num_lanes == 2) { + udphy->dp_lanes = num_lanes; + if (num_lanes == 1 || num_lanes == 2) { udphy->mode |= UDPHY_MODE_USB; udphy->flip = (udphy->lane_mux_sel[0] == PHY_LANE_MUX_DP); } @@ -1000,21 +1002,19 @@ static int rk_udphy_power_on(struct rk_udphy *udphy, u8 mode) } if (udphy->status == UDPHY_MODE_NONE) { - udphy->mode_change = false; + udphy->phy_needs_reinit = false; ret = rk_udphy_setup(udphy); if (ret) return ret; if (udphy->mode & UDPHY_MODE_USB) rk_udphy_u3_port_disable(udphy, false); - } else if (udphy->mode_change) { - udphy->mode_change = false; - udphy->status = UDPHY_MODE_NONE; + } else if (udphy->phy_needs_reinit) { + udphy->phy_needs_reinit = false; if (udphy->mode == UDPHY_MODE_DP) rk_udphy_u3_port_disable(udphy, true); - rk_udphy_disable(udphy); - ret = rk_udphy_setup(udphy); + ret = rk_udphy_init(udphy); if (ret) return ret; } @@ -1044,12 +1044,10 @@ static int rk_udphy_dp_phy_init(struct phy *phy) { struct rk_udphy *udphy = phy_get_drvdata(phy); - mutex_lock(&udphy->mutex); + guard(mutex)(&udphy->mutex); udphy->dp_in_use = true; - mutex_unlock(&udphy->mutex); - return 0; } @@ -1057,38 +1055,37 @@ static int rk_udphy_dp_phy_exit(struct phy *phy) { struct rk_udphy *udphy = phy_get_drvdata(phy); - mutex_lock(&udphy->mutex); + guard(mutex)(&udphy->mutex); + udphy->dp_in_use = false; - mutex_unlock(&udphy->mutex); + return 0; } static int rk_udphy_dp_phy_power_on(struct phy *phy) { struct rk_udphy *udphy = phy_get_drvdata(phy); - int ret, dp_lanes; - - mutex_lock(&udphy->mutex); + int ret; - dp_lanes = rk_udphy_dplane_get(udphy); - phy_set_bus_width(phy, dp_lanes); + scoped_guard(mutex, &udphy->mutex) { + phy_set_bus_width(phy, udphy->dp_lanes); - ret = rk_udphy_power_on(udphy, UDPHY_MODE_DP); - if (ret) - goto unlock; + ret = rk_udphy_power_on(udphy, UDPHY_MODE_DP); + if (ret) + return ret; - rk_udphy_dplane_enable(udphy, dp_lanes); + rk_udphy_dp_lane_enable(udphy, udphy->dp_lanes); - rk_udphy_dplane_select(udphy); + rk_udphy_dp_lane_select(udphy); + } -unlock: - mutex_unlock(&udphy->mutex); /* * If data send by aux channel too fast after phy power on, * the aux may be not ready which will cause aux error. Adding * delay to avoid this issue. */ usleep_range(10000, 11000); + return ret; } @@ -1096,10 +1093,10 @@ static int rk_udphy_dp_phy_power_off(struct phy *phy) { struct rk_udphy *udphy = phy_get_drvdata(phy); - mutex_lock(&udphy->mutex); - rk_udphy_dplane_enable(udphy, 0); + guard(mutex)(&udphy->mutex); + + rk_udphy_dp_lane_enable(udphy, 0); rk_udphy_power_off(udphy, UDPHY_MODE_DP); - mutex_unlock(&udphy->mutex); return 0; } @@ -1303,19 +1300,18 @@ static const struct phy_ops rk_udphy_dp_phy_ops = { static int rk_udphy_usb3_phy_init(struct phy *phy) { struct rk_udphy *udphy = phy_get_drvdata(phy); - int ret = 0; + int ret; + + guard(mutex)(&udphy->mutex); - mutex_lock(&udphy->mutex); /* DP only or high-speed, disable U3 port */ if (!(udphy->mode & UDPHY_MODE_USB) || udphy->hs) { rk_udphy_u3_port_disable(udphy, true); - goto unlock; + return 0; } ret = rk_udphy_power_on(udphy, UDPHY_MODE_USB); -unlock: - mutex_unlock(&udphy->mutex); return ret; } @@ -1323,15 +1319,14 @@ static int rk_udphy_usb3_phy_exit(struct phy *phy) { struct rk_udphy *udphy = phy_get_drvdata(phy); - mutex_lock(&udphy->mutex); + guard(mutex)(&udphy->mutex); + /* DP only or high-speed */ if (!(udphy->mode & UDPHY_MODE_USB) || udphy->hs) - goto unlock; + return 0; rk_udphy_power_off(udphy, UDPHY_MODE_USB); -unlock: - mutex_unlock(&udphy->mutex); return 0; } @@ -1345,58 +1340,11 @@ static int rk_udphy_typec_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state) { struct rk_udphy *udphy = typec_mux_get_drvdata(mux); - u8 mode; - mutex_lock(&udphy->mutex); + guard(mutex)(&udphy->mutex); - switch (state->mode) { - case TYPEC_DP_STATE_C: - case TYPEC_DP_STATE_E: - udphy->lane_mux_sel[0] = PHY_LANE_MUX_DP; - udphy->lane_mux_sel[1] = PHY_LANE_MUX_DP; - udphy->lane_mux_sel[2] = PHY_LANE_MUX_DP; - udphy->lane_mux_sel[3] = PHY_LANE_MUX_DP; - mode = UDPHY_MODE_DP; - break; + rk_udphy_set_typec_state(udphy, state->mode); - case TYPEC_DP_STATE_D: - default: - if (udphy->flip) { - udphy->lane_mux_sel[0] = PHY_LANE_MUX_DP; - udphy->lane_mux_sel[1] = PHY_LANE_MUX_DP; - udphy->lane_mux_sel[2] = PHY_LANE_MUX_USB; - udphy->lane_mux_sel[3] = PHY_LANE_MUX_USB; - } else { - udphy->lane_mux_sel[0] = PHY_LANE_MUX_USB; - udphy->lane_mux_sel[1] = PHY_LANE_MUX_USB; - udphy->lane_mux_sel[2] = PHY_LANE_MUX_DP; - udphy->lane_mux_sel[3] = PHY_LANE_MUX_DP; - } - mode = UDPHY_MODE_DP_USB; - break; - } - - if (state->alt && state->alt->svid == USB_TYPEC_DP_SID) { - struct typec_displayport_data *data = state->data; - - if (!data) { - rk_udphy_dp_hpd_event_trigger(udphy, false); - } else if (data->status & DP_STATUS_IRQ_HPD) { - rk_udphy_dp_hpd_event_trigger(udphy, false); - usleep_range(750, 800); - rk_udphy_dp_hpd_event_trigger(udphy, true); - } else if (data->status & DP_STATUS_HPD_STATE) { - if (udphy->mode != mode) { - udphy->mode = mode; - udphy->mode_change = true; - } - rk_udphy_dp_hpd_event_trigger(udphy, true); - } else { - rk_udphy_dp_hpd_event_trigger(udphy, false); - } - } - - mutex_unlock(&udphy->mutex); return 0; } @@ -1454,6 +1402,7 @@ static int rk_udphy_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct phy_provider *phy_provider; + struct fwnode_handle *dp_aux_ep; struct resource *res; struct rk_udphy *udphy; void __iomem *base; @@ -1512,6 +1461,18 @@ static int rk_udphy_probe(struct platform_device *pdev) return ret; } + /* + * Only register the DRM bridge, if the DP aux channel is connected. + * Some boards use the USBDP PHY only for its USB3 capabilities. + */ + dp_aux_ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 3, 0, 0); + if (dp_aux_ep) { + ret = drm_aux_bridge_register(dev); + fwnode_handle_put(dp_aux_ep); + if (ret) + return ret; + } + udphy->phy_u3 = devm_phy_create(dev, dev->of_node, &rk_udphy_usb3_phy_ops); if (IS_ERR(udphy->phy_u3)) { ret = PTR_ERR(udphy->phy_u3); @@ -1524,7 +1485,7 @@ static int rk_udphy_probe(struct platform_device *pdev) ret = PTR_ERR(udphy->phy_dp); return dev_err_probe(dev, ret, "failed to create DP phy\n"); } - phy_set_bus_width(udphy->phy_dp, rk_udphy_dplane_get(udphy)); + phy_set_bus_width(udphy->phy_dp, udphy->dp_lanes); udphy->phy_dp->attrs.max_link_rate = 8100; phy_set_drvdata(udphy->phy_dp, udphy); @@ -1537,20 +1498,6 @@ static int rk_udphy_probe(struct platform_device *pdev) return 0; } -static int __maybe_unused rk_udphy_resume(struct device *dev) -{ - struct rk_udphy *udphy = dev_get_drvdata(dev); - - if (udphy->dp_sink_hpd_sel) - rk_udphy_dp_hpd_event_trigger(udphy, udphy->dp_sink_hpd_cfg); - - return 0; -} - -static const struct dev_pm_ops rk_udphy_pm_ops = { - SET_LATE_SYSTEM_SLEEP_PM_OPS(NULL, rk_udphy_resume) -}; - static const char * const rk_udphy_rst_list[] = { "init", "cmn", "lane", "pcs_apb", "pma_apb" }; @@ -1655,7 +1602,6 @@ static struct platform_driver rk_udphy_driver = { .driver = { .name = "rockchip-usbdp-phy", .of_match_table = rk_udphy_dt_match, - .pm = &rk_udphy_pm_ops, }, }; module_platform_driver(rk_udphy_driver); diff --git a/drivers/power/supply/bq257xx_charger.c b/drivers/power/supply/bq257xx_charger.c index 02c7d8b61e82b6..7d02169248b17f 100644 --- a/drivers/power/supply/bq257xx_charger.c +++ b/drivers/power/supply/bq257xx_charger.c @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -18,20 +19,34 @@ struct bq257xx_chg; /** * struct bq257xx_chip_info - chip specific routines + * @default_iindpm_uA: default input current limit in microamps * @bq257xx_hw_init: init function for hw * @bq257xx_hw_shutdown: shutdown function for hw * @bq257xx_get_state: get and update state of hardware + * @bq257xx_get_ichg: get maximum charge current (in uA) * @bq257xx_set_ichg: set maximum charge current (in uA) + * @bq257xx_get_vbatreg: get maximum charge voltage (in uV) * @bq257xx_set_vbatreg: set maximum charge voltage (in uV) + * @bq257xx_get_iindpm: get maximum input current (in uA) * @bq257xx_set_iindpm: set maximum input current (in uA) + * @bq257xx_get_cur: get battery current from ADC (in uA) + * @bq257xx_get_vbat: get battery voltage from ADC (in uV) + * @bq257xx_get_min_vsys: get minimum system voltage (in uV) */ struct bq257xx_chip_info { + int default_iindpm_uA; int (*bq257xx_hw_init)(struct bq257xx_chg *pdata); void (*bq257xx_hw_shutdown)(struct bq257xx_chg *pdata); int (*bq257xx_get_state)(struct bq257xx_chg *pdata); + int (*bq257xx_get_ichg)(struct bq257xx_chg *pdata, int *intval); int (*bq257xx_set_ichg)(struct bq257xx_chg *pdata, int ichg); + int (*bq257xx_get_vbatreg)(struct bq257xx_chg *pdata, int *intval); int (*bq257xx_set_vbatreg)(struct bq257xx_chg *pdata, int vbatreg); + int (*bq257xx_get_iindpm)(struct bq257xx_chg *pdata, int *intval); int (*bq257xx_set_iindpm)(struct bq257xx_chg *pdata, int iindpm); + int (*bq257xx_get_cur)(struct bq257xx_chg *pdata, int *intval); + int (*bq257xx_get_vbat)(struct bq257xx_chg *pdata, int *intval); + int (*bq257xx_get_min_vsys)(struct bq257xx_chg *pdata, int *intval); }; /** @@ -40,8 +55,10 @@ struct bq257xx_chip_info { * @bq: parent MFD device * @charger: power supply device * @online: charger input is present + * @charging: charger is actively charging the battery * @fast_charge: charger is in fast charge mode * @pre_charge: charger is in pre-charge mode + * @overvoltage: overvoltage fault detected * @ov_fault: charger reports over voltage fault * @batoc_fault: charger reports battery over current fault * @oc_fault: charger reports over current fault @@ -57,8 +74,10 @@ struct bq257xx_chg { struct bq257xx_device *bq; struct power_supply *charger; bool online; + bool charging; bool fast_charge; bool pre_charge; + bool overvoltage; bool ov_fault; bool batoc_fault; bool oc_fault; @@ -70,6 +89,53 @@ struct bq257xx_chg { u32 vsys_min; }; +/** + * bq25792_read16() - Read a 16-bit value from device register + * @pdata: driver platform data + * @reg: register address to read from + * @val: pointer to store the register value + * + * Read a 16-bit big-endian value from the BQ25792 device via regmap + * and convert to CPU byte order. + * + * Return: Returns 0 on success or error on failure to read. + */ +static int bq25792_read16(struct bq257xx_chg *pdata, unsigned int reg, u16 *val) +{ + __be16 regval; + int ret; + + ret = regmap_raw_read(pdata->bq->regmap, reg, ®val, sizeof(regval)); + if (ret) + return ret; + + *val = be16_to_cpu(regval); + return 0; +} + +/** + * bq25792_write16() - Write a 16-bit value to device register + * @pdata: driver platform data + * @reg: register address to write to + * @val: 16-bit value to write in CPU byte order + * + * Convert the value to big-endian and write a 16-bit value to the + * BQ25792 device via regmap. + * + * Return: Returns 0 on success or error on failure to write. + */ +static int bq25792_write16(struct bq257xx_chg *pdata, unsigned int reg, u16 val) +{ + __be16 regval = cpu_to_be16(val); + int ret; + + ret = regmap_raw_write(pdata->bq->regmap, reg, ®val, sizeof(regval)); + if (ret) + return ret; + + return 0; +} + /** * bq25703_get_state() - Get the current state of the device * @pdata: driver platform data @@ -92,13 +158,52 @@ static int bq25703_get_state(struct bq257xx_chg *pdata) pdata->online = reg & BQ25703_STS_AC_STAT; pdata->fast_charge = reg & BQ25703_STS_IN_FCHRG; pdata->pre_charge = reg & BQ25703_STS_IN_PCHRG; + pdata->charging = pdata->fast_charge || pdata->pre_charge; pdata->ov_fault = reg & BQ25703_STS_FAULT_ACOV; pdata->batoc_fault = reg & BQ25703_STS_FAULT_BATOC; + pdata->overvoltage = pdata->ov_fault || pdata->batoc_fault; pdata->oc_fault = reg & BQ25703_STS_FAULT_ACOC; return 0; } +/** + * bq25792_get_state() - Get the current state of the device + * @pdata: driver platform data + * + * Get the current state of the BQ25792 charger by reading status + * registers. Updates the online, charging, overvoltage, and fault + * status fields in the driver data structure. + * + * Return: Returns 0 on success or error on failure to read device. + */ +static int bq25792_get_state(struct bq257xx_chg *pdata) +{ + unsigned int reg; + int ret; + + ret = regmap_read(pdata->bq->regmap, BQ25792_REG1B_CHARGER_STATUS_0, ®); + if (ret) + return ret; + + pdata->online = reg & BQ25792_REG1B_PG_STAT; + + ret = regmap_read(pdata->bq->regmap, BQ25792_REG1C_CHARGER_STATUS_1, ®); + if (ret) + return ret; + + pdata->charging = reg & BQ25792_REG1C_CHG_STAT_MASK; + + ret = regmap_read(pdata->bq->regmap, BQ25792_REG20_FAULT_STATUS_0, ®); + if (ret) + return ret; + + pdata->overvoltage = reg & BQ25792_REG20_OVERVOLTAGE_MASK; + pdata->oc_fault = reg & BQ25792_REG20_OVERCURRENT_MASK; + + return 0; +} + /** * bq25703_get_min_vsys() - Get the minimum system voltage * @pdata: driver platform data @@ -122,15 +227,39 @@ static int bq25703_get_min_vsys(struct bq257xx_chg *pdata, int *intval) return ret; } +/** + * bq25792_get_min_vsys() - Get the minimum system voltage + * @pdata: driver platform data + * @intval: pointer to store the minimum voltage value + * + * Read the current minimum system voltage setting from the device + * and return it in microvolts. + * + * Return: Returns 0 on success or error on failure to read. + */ +static int bq25792_get_min_vsys(struct bq257xx_chg *pdata, int *intval) +{ + unsigned int reg; + int ret; + + ret = regmap_read(pdata->bq->regmap, BQ25792_REG00_MIN_SYS_VOLTAGE, ®); + if (ret) + return ret; + + reg = FIELD_GET(BQ25792_REG00_VSYSMIN_MASK, reg); + *intval = (reg * BQ25792_MINVSYS_STEP_UV) + BQ25792_MINVSYS_MIN_UV; + + return ret; +} + /** * bq25703_set_min_vsys() - Set the minimum system voltage * @pdata: driver platform data * @vsys: voltage value to set in uV. * * This function takes a requested minimum system voltage value, clamps - * it between the minimum supported value by the charger and a user - * defined minimum system value, and then writes the value to the - * appropriate register. + * it between the user defined minimum system value and the maximum supported + * value by the charger, and then writes the value to the appropriate register. * * Return: Returns 0 on success or error if an error occurs. */ @@ -139,7 +268,7 @@ static int bq25703_set_min_vsys(struct bq257xx_chg *pdata, int vsys) unsigned int reg; int vsys_min = pdata->vsys_min; - vsys = clamp(vsys, BQ25703_MINVSYS_MIN_UV, vsys_min); + vsys = clamp(vsys, vsys_min, BQ25703_MINVSYS_MAX_UV); reg = ((vsys - BQ25703_MINVSYS_MIN_UV) / BQ25703_MINVSYS_STEP_UV); reg = FIELD_PREP(BQ25703_MINVSYS_MASK, reg); @@ -147,6 +276,29 @@ static int bq25703_set_min_vsys(struct bq257xx_chg *pdata, int vsys) reg); } +/** + * bq25792_set_min_vsys() - Set the minimum system voltage + * @pdata: driver platform data + * @vsys: voltage value to set in uV + * + * Set the minimum system voltage by clamping the requested value + * between device limits and writing to the appropriate register. + * + * Return: Returns 0 on success or error on failure to write. + */ +static int bq25792_set_min_vsys(struct bq257xx_chg *pdata, int vsys) +{ + unsigned int reg; + int vsys_min = pdata->vsys_min; + + vsys = clamp(vsys, vsys_min, BQ25792_MINVSYS_MAX_UV); + reg = ((vsys - BQ25792_MINVSYS_MIN_UV) / BQ25792_MINVSYS_STEP_UV); + reg = FIELD_PREP(BQ25792_REG00_VSYSMIN_MASK, reg); + + return regmap_write(pdata->bq->regmap, + BQ25792_REG00_MIN_SYS_VOLTAGE, reg); +} + /** * bq25703_get_cur() - Get the reported current from the battery * @pdata: driver platform data @@ -176,6 +328,30 @@ static int bq25703_get_cur(struct bq257xx_chg *pdata, int *intval) return ret; } +/** + * bq25792_get_cur() - Get the reported current from the battery + * @pdata: driver platform data + * @intval: pointer to store the battery current value + * + * Read the current ADC value from the device representing the battery + * charge or discharge current and return it in microamps. + * + * Return: Returns 0 on success or error on failure to read. + */ +static int bq25792_get_cur(struct bq257xx_chg *pdata, int *intval) +{ + u16 reg; + int ret; + + ret = bq25792_read16(pdata, BQ25792_REG33_IBAT_ADC, ®); + if (ret < 0) + return ret; + + *intval = (s16)reg * BQ25792_ADCIBAT_STEP_UA; + + return ret; +} + /** * bq25703_get_ichg_cur() - Get the maximum reported charge current * @pdata: driver platform data @@ -199,6 +375,30 @@ static int bq25703_get_ichg_cur(struct bq257xx_chg *pdata, int *intval) return ret; } +/** + * bq25792_get_ichg_cur() - Get the maximum reported charge current + * @pdata: driver platform data + * @intval: pointer to store the maximum charge current value + * + * Read the programmed maximum charge current limit from the device. + * + * Return: Returns 0 on success or error on failure to read value. + */ +static int bq25792_get_ichg_cur(struct bq257xx_chg *pdata, int *intval) +{ + u16 reg; + int ret; + + ret = bq25792_read16(pdata, BQ25792_REG03_CHARGE_CURRENT_LIMIT, ®); + if (ret) + return ret; + + *intval = FIELD_GET(BQ25792_REG03_ICHG_MASK, reg) * + BQ25792_ICHG_STEP_UA; + + return ret; +} + /** * bq25703_set_ichg_cur() - Set the maximum charge current * @pdata: driver platform data @@ -223,6 +423,28 @@ static int bq25703_set_ichg_cur(struct bq257xx_chg *pdata, int ichg) reg); } +/** + * bq25792_set_ichg_cur() - Set the maximum charge current + * @pdata: driver platform data + * @ichg: current value to set in uA + * + * Set the maximum charge current by clamping the requested value + * between device limits and writing to the appropriate register. + * + * Return: Returns 0 on success or error on failure to write. + */ +static int bq25792_set_ichg_cur(struct bq257xx_chg *pdata, int ichg) +{ + int ichg_max = pdata->ichg_max; + u16 reg; + + ichg = clamp(ichg, BQ25792_ICHG_MIN_UA, ichg_max); + reg = FIELD_PREP(BQ25792_REG03_ICHG_MASK, + (ichg / BQ25792_ICHG_STEP_UA)); + + return bq25792_write16(pdata, BQ25792_REG03_CHARGE_CURRENT_LIMIT, reg); +} + /** * bq25703_get_chrg_volt() - Get the maximum set charge voltage * @pdata: driver platform data @@ -246,6 +468,30 @@ static int bq25703_get_chrg_volt(struct bq257xx_chg *pdata, int *intval) return ret; } +/** + * bq25792_get_chrg_volt() - Get the maximum set charge voltage + * @pdata: driver platform data + * @intval: pointer to store the maximum charge voltage value + * + * Read the current charge voltage limit from the device. + * + * Return: Returns 0 on success or error on failure to read value. + */ +static int bq25792_get_chrg_volt(struct bq257xx_chg *pdata, int *intval) +{ + u16 reg; + int ret; + + ret = bq25792_read16(pdata, BQ25792_REG01_CHARGE_VOLTAGE_LIMIT, ®); + if (ret) + return ret; + + *intval = FIELD_GET(BQ25792_REG01_VREG_MASK, reg) * + BQ25792_VBATREG_STEP_UV; + + return ret; +} + /** * bq25703_set_chrg_volt() - Set the maximum charge voltage * @pdata: driver platform data @@ -272,6 +518,29 @@ static int bq25703_set_chrg_volt(struct bq257xx_chg *pdata, int vbat) reg); } +/** + * bq25792_set_chrg_volt() - Set the maximum charge voltage + * @pdata: driver platform data + * @vbat: voltage value to set in uV + * + * Set the maximum charge voltage by clamping the requested value + * between device limits and writing to the appropriate register. + * + * Return: Returns 0 on success or error on failure to write. + */ +static int bq25792_set_chrg_volt(struct bq257xx_chg *pdata, int vbat) +{ + int vbat_max = pdata->vbat_max; + u16 reg; + + vbat = clamp(vbat, BQ25792_VBATREG_MIN_UV, vbat_max); + + reg = FIELD_PREP(BQ25792_REG01_VREG_MASK, + (vbat / BQ25792_VBATREG_STEP_UV)); + + return bq25792_write16(pdata, BQ25792_REG01_CHARGE_VOLTAGE_LIMIT, reg); +} + /** * bq25703_get_iindpm() - Get the maximum set input current * @pdata: driver platform data @@ -300,6 +569,30 @@ static int bq25703_get_iindpm(struct bq257xx_chg *pdata, int *intval) return ret; } +/** + * bq25792_get_iindpm() - Get the maximum set input current + * @pdata: driver platform data + * @intval: pointer to store the maximum input current value + * + * Read the current input current limit from the device. + * + * Return: Returns 0 on success or error on failure to read value. + */ +static int bq25792_get_iindpm(struct bq257xx_chg *pdata, int *intval) +{ + u16 reg; + int ret; + + ret = bq25792_read16(pdata, BQ25792_REG06_INPUT_CURRENT_LIMIT, ®); + if (ret) + return ret; + + reg = FIELD_GET(BQ25792_REG06_IINDPM_MASK, reg); + *intval = reg * BQ25792_IINDPM_STEP_UA; + + return ret; +} + /** * bq25703_set_iindpm() - Set the maximum input current * @pdata: driver platform data @@ -325,6 +618,29 @@ static int bq25703_set_iindpm(struct bq257xx_chg *pdata, int iindpm) FIELD_PREP(BQ25703_IINDPM_MASK, reg)); } +/** + * bq25792_set_iindpm() - Set the maximum input current + * @pdata: driver platform data + * @iindpm: current value in uA + * + * Set the maximum input current by clamping the requested value + * between device limits and writing to the appropriate register. + * + * Return: Returns 0 on success or error on failure to write. + */ +static int bq25792_set_iindpm(struct bq257xx_chg *pdata, int iindpm) +{ + u16 reg; + int iindpm_max = pdata->iindpm_max; + + iindpm = clamp(iindpm, BQ25792_IINDPM_MIN_UA, iindpm_max); + + reg = iindpm / BQ25792_IINDPM_STEP_UA; + + return bq25792_write16(pdata, BQ25792_REG06_INPUT_CURRENT_LIMIT, + FIELD_PREP(BQ25792_REG06_IINDPM_MASK, reg)); +} + /** * bq25703_get_vbat() - Get the reported voltage from the battery * @pdata: driver platform data @@ -349,6 +665,30 @@ static int bq25703_get_vbat(struct bq257xx_chg *pdata, int *intval) return ret; } +/** + * bq25792_get_vbat() - Get the reported voltage from the battery + * @pdata: driver platform data + * @intval: pointer to store the battery voltage value + * + * Read the current ADC value representing the battery voltage + * and return it in microvolts. + * + * Return: Returns 0 on success or error on failure to read value. + */ +static int bq25792_get_vbat(struct bq257xx_chg *pdata, int *intval) +{ + u16 reg; + int ret; + + ret = bq25792_read16(pdata, BQ25792_REG3B_VBAT_ADC, ®); + if (ret) + return ret; + + *intval = reg * BQ25792_ADCVSYSVBAT_STEP_UV; + + return ret; +} + /** * bq25703_hw_init() - Set all the required registers to init the charger * @pdata: driver platform data @@ -415,6 +755,108 @@ static int bq25703_hw_init(struct bq257xx_chg *pdata) return ret; } +/** + * bq25792_hw_init() - Initialize BQ25792 hardware + * @pdata: driver platform data + * + * Initialize the BQ25792 by disabling the watchdog, enabling discharge + * current sensing with 5A limit, and configuring input current regulation. + * Set the charge current, charge voltage, minimum system voltage, and + * input current limit from platform data. Enable and configure the ADC + * to measure all available channels. + * + * Return: Returns 0 on success or error code on error. + */ +static int bq25792_hw_init(struct bq257xx_chg *pdata) +{ + struct regmap *regmap = pdata->bq->regmap; + int ret = 0; + u8 reg; + + /* Disable watchdog (TODO: make it work instead) */ + ret = regmap_write(regmap, BQ25792_REG10_CHARGER_CONTROL_1, 0); + if (ret) + return ret; + + /* + * Enable battery discharge current sensing, 5A discharge current + * limit, input current regulation and ship FET functions + */ + ret = regmap_write(regmap, BQ25792_REG14_CHARGER_CONTROL_5, + BQ25792_REG14_SFET_PRESENT | + BQ25792_REG14_EN_IBAT | + BQ25792_IBAT_5A | + BQ25792_REG14_EN_IINDPM); + if (ret) + return ret; + + if (pdata->vbat_max < 5000000) { + /* 1S batteries */ + reg = FIELD_PREP(BQ25792_REG0A_CELL_MASK, BQ25792_CELL_1S); + } else if (pdata->vbat_max < 10000000) { + /* 2S batteries */ + reg = FIELD_PREP(BQ25792_REG0A_CELL_MASK, BQ25792_CELL_2S); + } else if (pdata->vbat_max < 14000000) { + /* 3S batteries */ + reg = FIELD_PREP(BQ25792_REG0A_CELL_MASK, BQ25792_CELL_3S); + } else { + /* 4S batteries */ + reg = FIELD_PREP(BQ25792_REG0A_CELL_MASK, BQ25792_CELL_4S); + } + + /* Recharge voltage detection deglitch time (default 1024ms) */ + reg |= FIELD_PREP(BQ25792_REG0A_TRECHG_MASK, BQ25792_TRECHG_1024MS); + + /* Recharge voltage offset: 5% of the set charge voltage */ + reg |= FIELD_PREP(BQ25792_REG0A_VRECHG_MASK, + (pdata->vbat_max / 20 - BQ25792_VRECHG_MIN_UV) / BQ25792_VRECHG_STEP_UV); + + ret = regmap_write(regmap, BQ25792_REG0A_RECHARGE_CONTROL, reg); + if (ret) + return ret; + + ret = pdata->chip->bq257xx_set_ichg(pdata, pdata->ichg_max); + if (ret) + return ret; + + ret = pdata->chip->bq257xx_set_vbatreg(pdata, pdata->vbat_max); + if (ret) + return ret; + + ret = bq25792_set_min_vsys(pdata, pdata->vsys_min); + if (ret) + return ret; + + ret = pdata->chip->bq257xx_set_iindpm(pdata, pdata->iindpm_max); + if (ret) + return ret; + + /* Enable the Input Current Optimizer (the rest is at POR value) */ + ret = regmap_write(regmap, BQ25792_REG0F_CHARGER_CONTROL_0, + BQ25792_REG0F_EN_AUTO_IBATDIS | + BQ25792_REG0F_EN_CHG | + BQ25792_REG0F_EN_ICO | + BQ25792_REG0F_EN_TERM); + if (ret) + return ret; + + /* Enable the ADC. */ + ret = regmap_write(regmap, BQ25792_REG2E_ADC_CONTROL, BQ25792_REG2E_ADC_EN); + if (ret) + return ret; + + /* Clear per-channel ADC disable bits - enable all channels */ + ret = regmap_write(regmap, BQ25792_REG2F_ADC_FUNCTION_DISABLE_0, 0); + if (ret) + return ret; + + ret = regmap_write(regmap, BQ25792_REG30_ADC_FUNCTION_DISABLE_1, 0); + if (ret) + return ret; + + return ret; +} + /** * bq25703_hw_shutdown() - Set registers for shutdown * @pdata: driver platform data @@ -427,6 +869,30 @@ static void bq25703_hw_shutdown(struct bq257xx_chg *pdata) BQ25703_EN_LWPWR, BQ25703_EN_LWPWR); } +/** + * bq25792_hw_shutdown() - Shutdown BQ25792 hardware + * @pdata: driver platform data + * + * Perform hardware shutdown for the BQ25792. Currently a no-op + * as the device does not require special shutdown configuration. + */ +static void bq25792_hw_shutdown(struct bq257xx_chg *pdata) +{ + /* Nothing to do here */ +} + +/** + * bq257xx_set_charger_property() - Set a power supply property + * @psy: power supply device + * @prop: power supply property to set + * @val: value to set for the property + * + * Handle requests to set power supply properties such as input current + * limit, constant charge voltage, and constant charge current. Routes + * the request to the chip-specific implementation. + * + * Return: Returns 0 on success or -EINVAL if property is not supported. + */ static int bq257xx_set_charger_property(struct power_supply *psy, enum power_supply_property prop, const union power_supply_propval *val) @@ -450,6 +916,19 @@ static int bq257xx_set_charger_property(struct power_supply *psy, return -EINVAL; } +/** + * bq257xx_get_charger_property() - Get a power supply property + * @psy: power supply device + * @psp: power supply property to get + * @val: pointer to store the property value + * + * Handle requests to get power supply properties, including status, + * health, manufacturer, online state, and various voltage/current + * measurements. Reads current device state and routes chip-specific + * property requests to appropriate handlers. + * + * Return: Returns 0 on success or -EINVAL if property is not supported. + */ static int bq257xx_get_charger_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) @@ -465,14 +944,14 @@ static int bq257xx_get_charger_property(struct power_supply *psy, case POWER_SUPPLY_PROP_STATUS: if (!pdata->online) val->intval = POWER_SUPPLY_STATUS_DISCHARGING; - else if (pdata->fast_charge || pdata->pre_charge) + else if (pdata->charging) val->intval = POWER_SUPPLY_STATUS_CHARGING; else val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; break; case POWER_SUPPLY_PROP_HEALTH: - if (pdata->ov_fault || pdata->batoc_fault) + if (pdata->overvoltage) val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; else if (pdata->oc_fault) val->intval = POWER_SUPPLY_HEALTH_OVERCURRENT; @@ -489,22 +968,22 @@ static int bq257xx_get_charger_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: - return bq25703_get_iindpm(pdata, &val->intval); + return pdata->chip->bq257xx_get_iindpm(pdata, &val->intval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: - return bq25703_get_chrg_volt(pdata, &val->intval); + return pdata->chip->bq257xx_get_vbatreg(pdata, &val->intval); case POWER_SUPPLY_PROP_CURRENT_NOW: - return bq25703_get_cur(pdata, &val->intval); + return pdata->chip->bq257xx_get_cur(pdata, &val->intval); case POWER_SUPPLY_PROP_VOLTAGE_NOW: - return bq25703_get_vbat(pdata, &val->intval); + return pdata->chip->bq257xx_get_vbat(pdata, &val->intval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: - return bq25703_get_ichg_cur(pdata, &val->intval); + return pdata->chip->bq257xx_get_ichg(pdata, &val->intval); case POWER_SUPPLY_PROP_VOLTAGE_MIN: - return bq25703_get_min_vsys(pdata, &val->intval); + return pdata->chip->bq257xx_get_min_vsys(pdata, &val->intval); case POWER_SUPPLY_PROP_USB_TYPE: val->intval = pdata->usb_type; @@ -531,6 +1010,17 @@ static enum power_supply_property bq257xx_power_supply_props[] = { POWER_SUPPLY_PROP_USB_TYPE, }; +/** + * bq257xx_property_is_writeable() - Check if a property is writeable + * @psy: power supply device + * @prop: power supply property to check + * + * Determines which power supply properties can be written to. Only + * charge current limit, charge voltage limit, and input current + * limit are writeable. + * + * Return: Returns 1 if property is writeable, 0 otherwise. + */ static int bq257xx_property_is_writeable(struct power_supply *psy, enum power_supply_property prop) { @@ -603,6 +1093,17 @@ static void bq257xx_external_power_changed(struct power_supply *psy) power_supply_changed(psy); } +/** + * bq257xx_irq_handler_thread() - Handle charger interrupt + * @irq: interrupt number + * @private: pointer to driver private data + * + * Thread handler for charger interrupts. Triggers re-evaluation of + * external power status and updates power supply state in response + * to charger events. + * + * Return: Returns IRQ_HANDLED if interrupt was processed. + */ static irqreturn_t bq257xx_irq_handler_thread(int irq, void *private) { struct bq257xx_chg *pdata = private; @@ -628,12 +1129,35 @@ static const struct power_supply_desc bq257xx_power_supply_desc = { }; static const struct bq257xx_chip_info bq25703_chip_info = { + .default_iindpm_uA = BQ25703_IINDPM_DEFAULT_UA, .bq257xx_hw_init = &bq25703_hw_init, .bq257xx_hw_shutdown = &bq25703_hw_shutdown, .bq257xx_get_state = &bq25703_get_state, + .bq257xx_get_ichg = &bq25703_get_ichg_cur, .bq257xx_set_ichg = &bq25703_set_ichg_cur, + .bq257xx_get_vbatreg = &bq25703_get_chrg_volt, .bq257xx_set_vbatreg = &bq25703_set_chrg_volt, + .bq257xx_get_iindpm = &bq25703_get_iindpm, .bq257xx_set_iindpm = &bq25703_set_iindpm, + .bq257xx_get_cur = &bq25703_get_cur, + .bq257xx_get_vbat = &bq25703_get_vbat, + .bq257xx_get_min_vsys = &bq25703_get_min_vsys, +}; + +static const struct bq257xx_chip_info bq25792_chip_info = { + .default_iindpm_uA = BQ25792_IINDPM_DEFAULT_UA, + .bq257xx_hw_init = &bq25792_hw_init, + .bq257xx_hw_shutdown = &bq25792_hw_shutdown, + .bq257xx_get_state = &bq25792_get_state, + .bq257xx_get_ichg = &bq25792_get_ichg_cur, + .bq257xx_set_ichg = &bq25792_set_ichg_cur, + .bq257xx_get_vbatreg = &bq25792_get_chrg_volt, + .bq257xx_set_vbatreg = &bq25792_set_chrg_volt, + .bq257xx_get_iindpm = &bq25792_get_iindpm, + .bq257xx_set_iindpm = &bq25792_set_iindpm, + .bq257xx_get_cur = &bq25792_get_cur, + .bq257xx_get_vbat = &bq25792_get_vbat, + .bq257xx_get_min_vsys = &bq25792_get_min_vsys, }; /** @@ -676,11 +1200,22 @@ static int bq257xx_parse_dt(struct bq257xx_chg *pdata, "input-current-limit-microamp", &pdata->iindpm_max); if (ret) - pdata->iindpm_max = BQ25703_IINDPM_DEFAULT_UA; + pdata->iindpm_max = pdata->chip->default_iindpm_uA; return 0; } +/** + * bq257xx_charger_probe() - Probe routine for charger platform device + * @pdev: platform device + * + * Probe the charger device, allocate driver data structure, select the + * appropriate chip-specific function pointers, register the power supply, + * parse device tree properties for battery limits, initialize hardware, + * and set up the interrupt handler if available. + * + * Return: Returns 0 on success or error code on failure. + */ static int bq257xx_charger_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -696,7 +1231,17 @@ static int bq257xx_charger_probe(struct platform_device *pdev) return -ENOMEM; pdata->bq = bq; - pdata->chip = &bq25703_chip_info; + + switch (bq->type) { + case BQ25703A: + pdata->chip = &bq25703_chip_info; + break; + case BQ25792: + pdata->chip = &bq25792_chip_info; + break; + default: + return dev_err_probe(dev, -EINVAL, "Unknown chip type\n"); + } platform_set_drvdata(pdev, pdata); @@ -734,6 +1279,13 @@ static int bq257xx_charger_probe(struct platform_device *pdev) return ret; } +/** + * bq257xx_charger_shutdown() - Shutdown routine for charger platform device + * @pdev: platform device + * + * Called during system shutdown to perform charger cleanup, including + * disabling watchdog timers or other chip-specific shutdown procedures. + */ static void bq257xx_charger_shutdown(struct platform_device *pdev) { struct bq257xx_chg *pdata = platform_get_drvdata(pdev); diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 6f3147518376a0..3fe7993bf12ba7 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -625,6 +625,17 @@ config PWM_ROCKCHIP Generic PWM framework driver for the PWM controller found on Rockchip SoCs. +config PWM_ROCKCHIP_V4 + tristate "Rockchip PWM v4 support" + depends on MFD_ROCKCHIP_MFPWM + help + Generic PWM framework driver for the PWM controller found on + later Rockchip SoCs such as the RK3576. + + Uses the Rockchip Multi-function PWM controller driver infrastructure + to guarantee fearlessly concurrent operation with other functions of + the same device implemented by drivers in other subsystems. + config PWM_SAMSUNG tristate "Samsung PWM support" depends on PLAT_SAMSUNG || ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 0dc0d2b69025db..a234027dbbc651 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -56,6 +56,7 @@ obj-$(CONFIG_PWM_RENESAS_RZG2L_GPT) += pwm-rzg2l-gpt.o obj-$(CONFIG_PWM_RENESAS_RZ_MTU3) += pwm-rz-mtu3.o obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o +obj-$(CONFIG_PWM_ROCKCHIP_V4) += pwm-rockchip-v4.o obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o obj-$(CONFIG_PWM_SL28CPLD) += pwm-sl28cpld.o diff --git a/drivers/pwm/pwm-rockchip-v4.c b/drivers/pwm/pwm-rockchip-v4.c new file mode 100644 index 00000000000000..b7de72c433c5bd --- /dev/null +++ b/drivers/pwm/pwm-rockchip-v4.c @@ -0,0 +1,383 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2025 Collabora Ltd. + * + * A Pulse-Width-Modulation (PWM) generator driver for the generators found in + * Rockchip SoCs such as the RK3576, internally referred to as "PWM v4". Uses + * the MFPWM infrastructure to guarantee exclusive use over the device without + * other functions of the device from different drivers interfering with its + * operation while it's active. + * + * Technical Reference Manual: Chapter 31 of the RK3506 TRM Part 1, a SoC which + * uses the same PWM hardware and has a publicly available TRM. + * https://opensource.rock-chips.com/images/3/36/Rockchip_RK3506_TRM_Part_1_V1.2-20250811.pdf + * + * Authors: + * Nicolas Frattaroli + * + * Limitations: + * - The hardware supports both completing the currently running period + * on disable (by switching to oneshot mode with a single repetition and + * only disable when the complete irq fires), and abrupt disable (freeze). + * Only the latter is implemented in the driver. + * - When the output is disabled, the pin will remain driven to whatever state + * it last had. + * - Adjustments to the duty cycle will only take effect during the next period. + * - Adjustments to the period length will only take effect during the next + * period. + * - The hardware only supports offsets in [0, period - duty_cycle] + */ + +#include +#include +#include +#include + +struct rockchip_pwm_v4 { + struct rockchip_mfpwm_func *pwmf; + struct pwm_chip chip; +}; + +struct __packed rockchip_pwm_v4_wf { + u32 period; + u32 duty; + u32 offset; + unsigned long rate; +}; + +static inline struct rockchip_pwm_v4 *to_rockchip_pwm_v4(struct pwm_chip *chip) +{ + return pwmchip_get_drvdata(chip); +} + +/** + * rockchip_pwm_v4_round_single - convert a PWM parameter to hardware + * @rate: clock rate of the PWM clock, as per clk_get_rate + * Assumed to be <= 1GHz for overflow considerations + * @in_val: parameter in nanoseconds to convert + * + * Returns the rounded value, saturating at U32_MAX if too large + */ +static u32 rockchip_pwm_v4_round_single(unsigned long rate, u64 in_val) +{ + u64 tmp; + + tmp = mul_u64_u64_div_u64(rate, in_val, NSEC_PER_SEC); + if (tmp > U32_MAX) + tmp = U32_MAX; + + return tmp; +} + +/** + * rockchip_pwm_v4_round_params - convert PWM parameters to hardware + * @rate: PWM clock rate to do the calculations at + * @wf: pointer to the generic &struct pwm_waveform input parameters + * @wfhw: pointer to the hardware-specific &struct rockchip_pwm_v4_wf output + * parameters that the results will be stored in + * + * Convert nanosecond-based duty/period/offset parameters to the PWM hardware's + * native rounded representation in number of cycles at clock rate @rate. Should + * any of the input parameters be out of range for the hardware, the + * corresponding output parameter is the maximum permissible value for said + * parameter with considerations to the others. + */ +static void rockchip_pwm_v4_round_params(unsigned long rate, + const struct pwm_waveform *wf, + struct rockchip_pwm_v4_wf *wfhw) +{ + wfhw->period = rockchip_pwm_v4_round_single(rate, wf->period_length_ns); + + wfhw->duty = rockchip_pwm_v4_round_single(rate, wf->duty_length_ns); + + /* As per TRM, PWM_OFFSET: "The value ranges from 0 to (period-duty)" */ + wfhw->offset = rockchip_pwm_v4_round_single(rate, wf->duty_offset_ns); + if (!wfhw->period) /* Don't underflow when pwm disabled */ + wfhw->offset = 0; + else if (wfhw->offset > wfhw->period - wfhw->duty) + wfhw->offset = wfhw->period - wfhw->duty; +} + +static int rockchip_pwm_v4_round_wf_tohw(struct pwm_chip *chip, + struct pwm_device *pwm, + const struct pwm_waveform *wf, + void *_wfhw) +{ + struct rockchip_pwm_v4 *pc = to_rockchip_pwm_v4(chip); + struct rockchip_pwm_v4_wf *wfhw = _wfhw; + unsigned long rate; + + rate = clk_get_rate(pc->pwmf->core); + + /* + * It's unlikely this code path is ever taken, as current hardware does + * not expose a clock that comes anywhere close to 1GHz. However, in + * order to avoid even a theoretical overflow in parameter rounding, + * error out if this ever happens to be the case. + */ + if (rate > NSEC_PER_SEC) + return -ERANGE; + + rockchip_pwm_v4_round_params(rate, wf, wfhw); + + if (wf->period_length_ns > 0) + wfhw->rate = rate; + else + wfhw->rate = 0; + + dev_dbg(&chip->dev, + "tohw: pwm#%u: %lld/%lld [+%lld] @%lu -> DUTY: %08x, PERIOD: %08x, OFFSET: %08x\n", + pwm->hwpwm, wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns, + rate, wfhw->duty, wfhw->period, wfhw->offset); + + return 0; +} + +static int rockchip_pwm_v4_round_wf_fromhw(struct pwm_chip *chip, + struct pwm_device *pwm, + const void *_wfhw, + struct pwm_waveform *wf) +{ + const struct rockchip_pwm_v4_wf *wfhw = _wfhw; + unsigned long rate = wfhw->rate; + + if (rate) { + wf->period_length_ns = DIV_ROUND_UP((u64)wfhw->period * NSEC_PER_SEC, rate); + wf->duty_length_ns = DIV_ROUND_UP((u64)wfhw->duty * NSEC_PER_SEC, rate); + wf->duty_offset_ns = DIV_ROUND_UP((u64)wfhw->offset * NSEC_PER_SEC, rate); + } else { + wf->period_length_ns = 0; + wf->duty_length_ns = 0; + wf->duty_offset_ns = 0; + } + + dev_dbg(&chip->dev, + "fromhw: pwm#%u: DUTY: %08x, PERIOD: %08x, OFFSET: %08x @%lu -> %lld/%lld [+%lld]\n", + pwm->hwpwm, wfhw->duty, wfhw->period, wfhw->offset, rate, + wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns); + + return 0; +} + +static int rockchip_pwm_v4_read_wf(struct pwm_chip *chip, struct pwm_device *pwm, + void *_wfhw) +{ + struct rockchip_pwm_v4 *pc = to_rockchip_pwm_v4(chip); + struct rockchip_pwm_v4_wf *wfhw = _wfhw; + unsigned long rate; + int ret; + + ret = mfpwm_acquire(pc->pwmf); + if (ret) + return ret; + + rate = clk_get_rate(pc->pwmf->core); + + wfhw->period = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_PERIOD); + wfhw->duty = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_DUTY); + wfhw->offset = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_OFFSET); + if (rockchip_pwm_v4_is_enabled(mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_ENABLE))) + wfhw->rate = rate; + else + wfhw->rate = 0; + + mfpwm_release(pc->pwmf); + + return 0; +} + +static int rockchip_pwm_v4_write_wf(struct pwm_chip *chip, struct pwm_device *pwm, + const void *_wfhw) +{ + struct rockchip_pwm_v4 *pc = to_rockchip_pwm_v4(chip); + const struct rockchip_pwm_v4_wf *wfhw = _wfhw; + bool was_enabled; + int ret; + + ret = mfpwm_acquire(pc->pwmf); + if (ret) + return ret; + + was_enabled = rockchip_pwm_v4_is_enabled(mfpwm_reg_read(pc->pwmf->base, + PWMV4_REG_ENABLE)); + + /* + * "But Nicolas", you ask with valid concerns, "why would you enable the + * PWM before setting all the parameter registers?" + * + * Excellent question, Mr. Reader M. Strawman! The RK3576 TRM Part 1 + * Section 34.6.3 specifies that this is the intended order of writes. + * Doing the PWM_EN and PWM_CLK_EN writes after the params but before + * the CTRL_UPDATE_EN, or even after the CTRL_UPDATE_EN, results in + * erratic behaviour where repeated turning on and off of the PWM may + * not turn it off under all circumstances. This is also why we don't + * use relaxed writes; it's not worth the footgun. + */ + if (wfhw->rate) + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE, + FIELD_PREP_WM16(PWMV4_EN_BOTH_MASK, + PWMV4_EN_BOTH_MASK)); + else + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE, + FIELD_PREP_WM16(PWMV4_EN_BOTH_MASK, 0)); + + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_PERIOD, wfhw->period); + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_DUTY, wfhw->duty); + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_OFFSET, wfhw->offset); + + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_CTRL, PWMV4_CTRL_CONT_FLAGS); + + /* Commit new configuration to hardware output. */ + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE, + PWMV4_CTRL_UPDATE_EN); + + if (wfhw->rate) { + if (!was_enabled) { + dev_dbg(&chip->dev, "Enabling PWM output\n"); + ret = clk_enable(pc->pwmf->core); + if (ret) + goto err_mfpwm_release; + ret = clk_set_rate_exclusive(pc->pwmf->core, wfhw->rate); + if (ret) { + clk_disable(pc->pwmf->core); + goto err_mfpwm_release; + } + + /* + * Output should be on now, acquire device to guarantee + * exclusion with other device functions while it's on. + * + * It's highly unlikely that this fails, as mfpwm has + * already been acquired before, and this is just a + * usage counter increase. Not worth the added + * complexity of clearing the PWMV4_REG_ENABLE again, + * especially considering the CTRL_UPDATE_EN behaviour. + */ + ret = mfpwm_acquire(pc->pwmf); + if (ret) { + clk_rate_exclusive_put(pc->pwmf->core); + clk_disable(pc->pwmf->core); + goto err_mfpwm_release; + } + } + } else if (was_enabled) { + dev_dbg(&chip->dev, "Disabling PWM output\n"); + clk_rate_exclusive_put(pc->pwmf->core); + clk_disable(pc->pwmf->core); + /* Output is off now, extra release to balance extra acquire */ + mfpwm_release(pc->pwmf); + } + +err_mfpwm_release: + mfpwm_release(pc->pwmf); + + return ret; +} + +static const struct pwm_ops rockchip_pwm_v4_ops = { + .sizeof_wfhw = sizeof(struct rockchip_pwm_v4_wf), + .round_waveform_tohw = rockchip_pwm_v4_round_wf_tohw, + .round_waveform_fromhw = rockchip_pwm_v4_round_wf_fromhw, + .read_waveform = rockchip_pwm_v4_read_wf, + .write_waveform = rockchip_pwm_v4_write_wf, +}; + +static bool rockchip_pwm_v4_on_and_continuous(struct rockchip_pwm_v4 *pc) +{ + bool en; + u32 val; + + en = rockchip_pwm_v4_is_enabled(mfpwm_reg_read(pc->pwmf->base, + PWMV4_REG_ENABLE)); + val = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_CTRL); + + return en && ((val & PWMV4_MODE_MASK) == PWMV4_MODE_CONT); +} + +static int rockchip_pwm_v4_probe(struct platform_device *pdev) +{ + struct rockchip_mfpwm_func *pwmf = dev_get_platdata(&pdev->dev); + struct rockchip_pwm_v4 *pc; + struct pwm_chip *chip; + struct device *dev = &pdev->dev; + int ret; + + /* + * For referencing the PWM in the DT to work, we need the parent MFD + * device's OF node. + */ + dev->of_node_reused = true; + device_set_node(dev, of_fwnode_handle(dev->parent->of_node)); + + chip = devm_pwmchip_alloc(dev, 1, sizeof(*pc)); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + pc = to_rockchip_pwm_v4(chip); + pc->pwmf = pwmf; + + ret = mfpwm_acquire(pwmf); + if (ret) + return dev_err_probe(dev, ret, "Couldn't acquire mfpwm in probe\n"); + + if (!rockchip_pwm_v4_on_and_continuous(pc)) + mfpwm_release(pwmf); + else { + dev_dbg(dev, "PWM was already on at probe time\n"); + ret = clk_enable(pwmf->core); + if (ret) { + dev_err_probe(dev, ret, "Enabling pwm clock failed\n"); + goto err_mfpwm_release; + } + ret = clk_rate_exclusive_get(pc->pwmf->core); + if (ret) { + dev_err_probe(dev, ret, "Protecting pwm clock failed\n"); + goto err_clk_disable; + } + } + + platform_set_drvdata(pdev, chip); + + chip->ops = &rockchip_pwm_v4_ops; + + ret = devm_pwmchip_add(dev, chip); + if (ret) { + dev_err_probe(dev, ret, "Failed to add PWM chip\n"); + if (rockchip_pwm_v4_on_and_continuous(pc)) + goto err_rate_put; + + return ret; + } + + return 0; + +err_rate_put: + clk_rate_exclusive_put(pwmf->core); +err_clk_disable: + clk_disable(pwmf->core); +err_mfpwm_release: + mfpwm_release(pwmf); + + return ret; +} + +static const struct platform_device_id rockchip_pwm_v4_ids[] = { + { .name = "rockchip-pwm-v4", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, rockchip_pwm_v4_ids); + +static struct platform_driver rockchip_pwm_v4_driver = { + .probe = rockchip_pwm_v4_probe, + .driver = { + .name = "rockchip-pwm-v4", + }, + .id_table = rockchip_pwm_v4_ids, +}; +module_platform_driver(rockchip_pwm_v4_driver); + +MODULE_AUTHOR("Nicolas Frattaroli "); +MODULE_DESCRIPTION("Rockchip PWMv4 Driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("ROCKCHIP_MFPWM"); +MODULE_ALIAS("platform:pwm-rockchip-v4"); diff --git a/drivers/regulator/bq257xx-regulator.c b/drivers/regulator/bq257xx-regulator.c index 09c466052c0487..fec75b23cd40ff 100644 --- a/drivers/regulator/bq257xx-regulator.c +++ b/drivers/regulator/bq257xx-regulator.c @@ -15,7 +15,6 @@ #include struct bq257xx_reg_data { - struct regulator_dev *bq257xx_reg; struct gpio_desc *otg_en_gpio; struct regulator_desc desc; }; @@ -32,6 +31,32 @@ static int bq25703_vbus_get_cur_limit(struct regulator_dev *rdev) return FIELD_GET(BQ25703_OTG_CUR_MASK, reg) * BQ25703_OTG_CUR_STEP_UA; } +static int bq25792_vbus_get_cur_limit(struct regulator_dev *rdev) +{ + struct regmap *regmap = rdev_get_regmap(rdev); + int ret; + unsigned int reg; + + ret = regmap_read(regmap, BQ25792_REG0D_IOTG_REGULATION, ®); + if (ret) + return ret; + return FIELD_GET(BQ25792_REG0D_IOTG_MASK, reg) * BQ25792_OTG_CUR_STEP_UA; +} + +static int bq25792_vbus_get_voltage_sel(struct regulator_dev *rdev) +{ + struct regmap *regmap = rdev_get_regmap(rdev); + __be16 reg; + int ret; + + ret = regmap_raw_read(regmap, BQ25792_REG0B_VOTG_REGULATION, + ®, sizeof(reg)); + if (ret) + return ret; + + return FIELD_GET(BQ25792_REG0B_VOTG_MASK, be16_to_cpu(reg)); +} + /* * Check if the minimum current and maximum current requested are * sane values, then set the register accordingly. @@ -55,6 +80,37 @@ static int bq25703_vbus_set_cur_limit(struct regulator_dev *rdev, FIELD_PREP(BQ25703_OTG_CUR_MASK, reg)); } +static int bq25792_vbus_set_cur_limit(struct regulator_dev *rdev, + int min_uA, int max_uA) +{ + struct regmap *regmap = rdev_get_regmap(rdev); + unsigned int reg; + + if ((min_uA > BQ25792_OTG_CUR_MAX_UA) || + (max_uA < BQ25792_OTG_CUR_MIN_UA)) + return -EINVAL; + + reg = (max_uA / BQ25792_OTG_CUR_STEP_UA); + + /* Catch rounding errors since our step is 40000uA. */ + if ((reg * BQ25792_OTG_CUR_STEP_UA) < min_uA) + return -EINVAL; + + return regmap_write(regmap, BQ25792_REG0D_IOTG_REGULATION, + FIELD_PREP(BQ25792_REG0D_IOTG_MASK, reg)); +} + +static int bq25792_vbus_set_voltage_sel(struct regulator_dev *rdev, + unsigned int sel) +{ + struct regmap *regmap = rdev_get_regmap(rdev); + __be16 reg; + + reg = cpu_to_be16(FIELD_PREP(BQ25792_REG0B_VOTG_MASK, sel)); + return regmap_raw_write(regmap, BQ25792_REG0B_VOTG_REGULATION, + ®, sizeof(reg)); +} + static int bq25703_vbus_enable(struct regulator_dev *rdev) { struct bq257xx_reg_data *pdata = rdev_get_drvdata(rdev); @@ -102,6 +158,34 @@ static const struct regulator_desc bq25703_vbus_desc = { .vsel_mask = BQ25703_OTG_VOLT_MASK, }; +static const struct regulator_ops bq25792_vbus_ops = { + /* No GPIO for enabling the OTG regulator */ + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear, + .get_voltage_sel = bq25792_vbus_get_voltage_sel, + .set_voltage_sel = bq25792_vbus_set_voltage_sel, + .get_current_limit = bq25792_vbus_get_cur_limit, + .set_current_limit = bq25792_vbus_set_cur_limit, +}; + +static const struct regulator_desc bq25792_vbus_desc = { + .name = "vbus", + .of_match = of_match_ptr("vbus"), + .regulators_node = of_match_ptr("regulators"), + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .ops = &bq25792_vbus_ops, + .min_uV = BQ25792_OTG_VOLT_MIN_UV, + .uV_step = BQ25792_OTG_VOLT_STEP_UV, + .n_voltages = BQ25792_OTG_VOLT_NUM_VOLT, + .enable_mask = BQ25792_REG12_EN_OTG, + .enable_reg = BQ25792_REG12_CHARGER_CONTROL_3, + .enable_val = BQ25792_REG12_EN_OTG, + .disable_val = 0, +}; + /* Get optional GPIO for OTG regulator enable. */ static void bq257xx_reg_dt_parse_gpio(struct platform_device *pdev) { @@ -142,9 +226,11 @@ static void bq257xx_reg_dt_parse_gpio(struct platform_device *pdev) static int bq257xx_regulator_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; + struct bq257xx_device *bq = dev_get_drvdata(pdev->dev.parent); struct bq257xx_reg_data *pdata; struct device_node *np = dev->of_node; struct regulator_config cfg = {}; + struct regulator_dev *rdev; device_set_of_node_from_dev(&pdev->dev, pdev->dev.parent); @@ -152,7 +238,17 @@ static int bq257xx_regulator_probe(struct platform_device *pdev) if (!pdata) return -ENOMEM; - pdata->desc = bq25703_vbus_desc; + switch (bq->type) { + case BQ25703A: + pdata->desc = bq25703_vbus_desc; + break; + case BQ25792: + pdata->desc = bq25792_vbus_desc; + break; + default: + return dev_err_probe(&pdev->dev, -EINVAL, + "Unsupported device type\n"); + } platform_set_drvdata(pdev, pdata); bq257xx_reg_dt_parse_gpio(pdev); @@ -164,9 +260,9 @@ static int bq257xx_regulator_probe(struct platform_device *pdev) if (!cfg.regmap) return -ENODEV; - pdata->bq257xx_reg = devm_regulator_register(dev, &pdata->desc, &cfg); - if (IS_ERR(pdata->bq257xx_reg)) { - return dev_err_probe(&pdev->dev, PTR_ERR(pdata->bq257xx_reg), + rdev = devm_regulator_register(dev, &pdata->desc, &cfg); + if (IS_ERR(rdev)) { + return dev_err_probe(&pdev->dev, PTR_ERR(rdev), "error registering bq257xx regulator"); } diff --git a/drivers/thermal/rockchip_thermal.c b/drivers/thermal/rockchip_thermal.c index c49ddf70f86e7b..2a40339de0d0ef 100644 --- a/drivers/thermal/rockchip_thermal.c +++ b/drivers/thermal/rockchip_thermal.c @@ -1638,8 +1638,7 @@ rockchip_thermal_register_sensor(struct platform_device *pdev, if (tsadc->get_trim_code && sensor->of_node) { error = rockchip_get_efuse_value(sensor->of_node, "trim", &trim); if (error < 0 && error != -ENOENT) { - dev_err(dev, "failed reading trim of sensor %d: %pe\n", - id, ERR_PTR(error)); + dev_err_probe(dev, error, "failed reading trim of sensor %d\n", id); return error; } if (trim) { diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c index 263a89c5f32433..3ff0882f3d97c1 100644 --- a/drivers/usb/typec/altmodes/displayport.c +++ b/drivers/usb/typec/altmodes/displayport.c @@ -766,8 +766,10 @@ int dp_altmode_probe(struct typec_altmode *alt) struct dp_altmode *dp; /* Port can only be DFP_U. */ - if (typec_altmode_get_data_role(alt) != TYPEC_HOST) + if (typec_altmode_get_data_role(alt) != TYPEC_HOST) { + dev_err(alt->dev.parent->parent, "Cannot probe DP AltMode as data-role is not HOST\n"); return -EPROTO; + } /* Make sure we have compatible pin configurations */ if (!(DP_CAP_PIN_ASSIGN_DFP_D(port->vdo) & @@ -775,6 +777,7 @@ int dp_altmode_probe(struct typec_altmode *alt) !(DP_CAP_PIN_ASSIGN_UFP_D(port->vdo) & DP_CAP_PIN_ASSIGN_DFP_D(alt->vdo))) { typec_altmode_put_plug(plug); + dev_err(alt->dev.parent->parent, "Cannot probe DP AltMode due to incorrect pin assignment\n"); return -ENODEV; } diff --git a/drivers/usb/typec/tcpm/Kconfig b/drivers/usb/typec/tcpm/Kconfig index 00baa7503d4527..53abde8ebef93a 100644 --- a/drivers/usb/typec/tcpm/Kconfig +++ b/drivers/usb/typec/tcpm/Kconfig @@ -13,7 +13,9 @@ if TYPEC_TCPM config TYPEC_TCPCI tristate "Type-C Port Controller Interface driver" + depends on DRM || DRM=n depends on I2C + select DRM_AUX_HPD_BRIDGE if DRM_BRIDGE && OF select REGMAP_I2C help Type-C Port Controller driver for TCPCI-compliant controller. diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c index 9ab1277b7ed1ef..5a166caff54676 100644 --- a/drivers/usb/typec/tcpm/fusb302.c +++ b/drivers/usb/typec/tcpm/fusb302.c @@ -8,9 +8,9 @@ #include #include #include +#include #include #include -#include #include #include #include @@ -84,8 +84,7 @@ struct fusb302_chip { struct work_struct irq_work; bool irq_suspended; bool irq_while_suspended; - struct gpio_desc *gpio_int_n; - int gpio_int_n_irq; + int irq; struct extcon_dev *extcon; struct workqueue_struct *wq; @@ -150,6 +149,8 @@ static void _fusb302_log(struct fusb302_chip *chip, const char *fmt, vsnprintf(tmpbuffer, sizeof(tmpbuffer), fmt, args); + dev_dbg(chip->dev, "%s\n", tmpbuffer); + mutex_lock(&chip->logbuffer_lock); if (fusb302_log_full(chip)) { @@ -210,20 +211,28 @@ static int fusb302_debug_show(struct seq_file *s, void *v) } DEFINE_SHOW_ATTRIBUTE(fusb302_debug); -static void fusb302_debugfs_init(struct fusb302_chip *chip) +static void fusb302_debugfs_exit(void *data) +{ + struct fusb302_chip *chip = data; + + debugfs_remove(chip->dentry); +} + +static int fusb302_debugfs_init(struct fusb302_chip *chip) { char name[NAME_MAX]; + int ret; + + ret = devm_mutex_init(chip->dev, &chip->logbuffer_lock); + if (ret < 0) + return ret; - mutex_init(&chip->logbuffer_lock); snprintf(name, NAME_MAX, "fusb302-%s", dev_name(chip->dev)); chip->dentry = debugfs_create_dir(name, usb_debug_root); debugfs_create_file("log", S_IFREG | 0444, chip->dentry, chip, &fusb302_debug_fops); -} -static void fusb302_debugfs_exit(struct fusb302_chip *chip) -{ - debugfs_remove(chip->dentry); + return devm_add_action_or_reset(chip->dev, fusb302_debugfs_exit, chip); } #else @@ -231,7 +240,6 @@ static void fusb302_debugfs_exit(struct fusb302_chip *chip) static void fusb302_log(const struct fusb302_chip *chip, const char *fmt, ...) { } static void fusb302_debugfs_init(const struct fusb302_chip *chip) { } -static void fusb302_debugfs_exit(const struct fusb302_chip *chip) { } #endif @@ -1487,7 +1495,7 @@ static irqreturn_t fusb302_irq_intn(int irq, void *dev_id) unsigned long flags; /* Disable our level triggered IRQ until our irq_work has cleared it */ - disable_irq_nosync(chip->gpio_int_n_irq); + disable_irq_nosync(chip->irq); spin_lock_irqsave(&chip->irq_lock, flags); if (chip->irq_suspended) @@ -1631,27 +1639,7 @@ static void fusb302_irq_work(struct work_struct *work) } done: mutex_unlock(&chip->lock); - enable_irq(chip->gpio_int_n_irq); -} - -static int init_gpio(struct fusb302_chip *chip) -{ - struct device *dev = chip->dev; - int ret = 0; - - chip->gpio_int_n = devm_gpiod_get(dev, "fcs,int_n", GPIOD_IN); - if (IS_ERR(chip->gpio_int_n)) { - dev_err(dev, "failed to request gpio_int_n\n"); - return PTR_ERR(chip->gpio_int_n); - } - ret = gpiod_to_irq(chip->gpio_int_n); - if (ret < 0) { - dev_err(dev, - "cannot request IRQ for GPIO Int_N, ret=%d", ret); - return ret; - } - chip->gpio_int_n_irq = ret; - return 0; + enable_irq(chip->irq); } #define PDO_FIXED_FLAGS \ @@ -1686,6 +1674,13 @@ static struct fwnode_handle *fusb302_fwnode_get(struct device *dev) return fwnode; } +static void fusb302_fwnode_put(void *data) +{ + struct fusb302_chip *chip = data; + + fwnode_handle_put(chip->tcpc_dev.fwnode); +} + static int fusb302_probe(struct i2c_client *client) { struct fusb302_chip *chip; @@ -1706,7 +1701,10 @@ static int fusb302_probe(struct i2c_client *client) chip->i2c_client = client; chip->dev = &client->dev; - mutex_init(&chip->lock); + + ret = devm_mutex_init(dev, &chip->lock); + if (ret < 0) + return ret; /* * Devicetree platforms should get extcon via phandle (not yet @@ -1725,67 +1723,65 @@ static int fusb302_probe(struct i2c_client *client) if (IS_ERR(chip->vbus)) return PTR_ERR(chip->vbus); - chip->wq = create_singlethread_workqueue(dev_name(chip->dev)); + chip->wq = devm_alloc_ordered_workqueue(dev, dev_name(dev), 0); if (!chip->wq) return -ENOMEM; spin_lock_init(&chip->irq_lock); - INIT_WORK(&chip->irq_work, fusb302_irq_work); - INIT_DELAYED_WORK(&chip->bc_lvl_handler, fusb302_bc_lvl_handler_work); init_tcpc_dev(&chip->tcpc_dev); - fusb302_debugfs_init(chip); - if (client->irq) { - chip->gpio_int_n_irq = client->irq; - } else { - ret = init_gpio(chip); - if (ret < 0) - goto destroy_workqueue; - } + ret = fusb302_debugfs_init(chip); + if (ret < 0) + return ret; + + chip->irq = client->irq; + if (!chip->irq) + return dev_err_probe(chip->dev, -ENXIO, + "missing interrupt\n"); chip->tcpc_dev.fwnode = fusb302_fwnode_get(dev); if (IS_ERR(chip->tcpc_dev.fwnode)) { ret = PTR_ERR(chip->tcpc_dev.fwnode); - goto destroy_workqueue; + return ret; } + ret = devm_add_action_or_reset(dev, fusb302_fwnode_put, chip); + if (ret < 0) + return ret; + bridge_dev = devm_drm_dp_hpd_bridge_alloc(chip->dev, to_of_node(chip->tcpc_dev.fwnode)); - if (IS_ERR(bridge_dev)) { - ret = dev_err_probe(chip->dev, PTR_ERR(bridge_dev), - "failed to alloc bridge\n"); - goto fwnode_put; - } + if (IS_ERR(bridge_dev)) + return dev_err_probe(chip->dev, PTR_ERR(bridge_dev), + "failed to alloc bridge\n"); - chip->tcpm_port = tcpm_register_port(&client->dev, &chip->tcpc_dev); - if (IS_ERR(chip->tcpm_port)) { - ret = dev_err_probe(dev, PTR_ERR(chip->tcpm_port), + chip->tcpm_port = devm_tcpm_register_port(&client->dev, &chip->tcpc_dev); + if (IS_ERR(chip->tcpm_port)) + return dev_err_probe(dev, PTR_ERR(chip->tcpm_port), "cannot register tcpm port\n"); - goto fwnode_put; - } - ret = devm_drm_dp_hpd_bridge_add(chip->dev, bridge_dev); - if (ret) - goto tcpm_unregister_port; + ret = devm_delayed_work_autocancel(dev, &chip->bc_lvl_handler, + fusb302_bc_lvl_handler_work); + if (ret < 0) + return ret; + + ret = devm_work_autocancel(dev, &chip->irq_work, fusb302_irq_work); + if (ret < 0) + return ret; + + ret = devm_request_threaded_irq(dev, chip->irq, NULL, + fusb302_irq_intn, + IRQF_ONESHOT | IRQF_TRIGGER_LOW, + "fsc_interrupt_int_n", chip); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to request IRQ"); - ret = request_threaded_irq(chip->gpio_int_n_irq, NULL, fusb302_irq_intn, - IRQF_ONESHOT | IRQF_TRIGGER_LOW, - "fsc_interrupt_int_n", chip); - if (ret < 0) { - dev_err(dev, "cannot request IRQ for GPIO Int_N, ret=%d", ret); - goto tcpm_unregister_port; - } - enable_irq_wake(chip->gpio_int_n_irq); i2c_set_clientdata(client, chip); - return 0; + ret = devm_drm_dp_hpd_bridge_add(chip->dev, bridge_dev); + if (ret) + return ret; -tcpm_unregister_port: - tcpm_unregister_port(chip->tcpm_port); -fwnode_put: - fwnode_handle_put(chip->tcpc_dev.fwnode); -destroy_workqueue: - fusb302_debugfs_exit(chip); - destroy_workqueue(chip->wq); + enable_irq_wake(chip->irq); return ret; } @@ -1794,14 +1790,7 @@ static void fusb302_remove(struct i2c_client *client) { struct fusb302_chip *chip = i2c_get_clientdata(client); - disable_irq_wake(chip->gpio_int_n_irq); - free_irq(chip->gpio_int_n_irq, chip); - cancel_work_sync(&chip->irq_work); - cancel_delayed_work_sync(&chip->bc_lvl_handler); - tcpm_unregister_port(chip->tcpm_port); - fwnode_handle_put(chip->tcpc_dev.fwnode); - destroy_workqueue(chip->wq); - fusb302_debugfs_exit(chip); + disable_irq_wake(chip->irq); } static int fusb302_pm_suspend(struct device *dev) diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index 0148b8f50412b0..e6cccbd377f7db 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -5,6 +5,7 @@ * USB Type-C Port Controller Interface. */ +#include #include #include #include @@ -837,6 +838,7 @@ static int tcpci_parse_config(struct tcpci *tcpci) struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data) { + struct auxiliary_device *bridge_dev; struct tcpci *tcpci; int err; @@ -889,12 +891,23 @@ struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data) if (err < 0) return ERR_PTR(err); + bridge_dev = devm_drm_dp_hpd_bridge_alloc(tcpci->dev, to_of_node(tcpci->tcpc.fwnode)); + if (IS_ERR(bridge_dev)) + return ERR_CAST(bridge_dev); + tcpci->port = tcpm_register_port(tcpci->dev, &tcpci->tcpc); if (IS_ERR(tcpci->port)) { fwnode_handle_put(tcpci->tcpc.fwnode); return ERR_CAST(tcpci->port); } + err = devm_drm_dp_hpd_bridge_add(tcpci->dev, bridge_dev); + if (err < 0) { + tcpm_unregister_port(tcpci->port); + fwnode_handle_put(tcpci->tcpc.fwnode); + return ERR_PTR(err); + } + return tcpci; } EXPORT_SYMBOL_GPL(tcpci_register_port); diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 7ef746a90a1774..0ab22eafaf35f0 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -826,6 +826,8 @@ static void _tcpm_log(struct tcpm_port *port, const char *fmt, va_list args) vsnprintf(tmpbuffer, sizeof(tmpbuffer), fmt, args); + dev_dbg(port->dev, "%s\n", tmpbuffer); + if (tcpm_log_full(port)) { port->logbuffer_head = max(port->logbuffer_head - 1, 0); strscpy(tmpbuffer, "overflow"); @@ -1671,6 +1673,15 @@ static bool tcpm_ams_interruptible(struct tcpm_port *port) return true; } +static void tcpm_vdm_handle_ams_interruption(struct tcpm_port *port) +{ + tcpm_log(port, "VDM AMS state machine got interrupted"); + + port->vdm_state = VDM_STATE_ERR_BUSY; + tcpm_ams_finish(port); + mod_vdm_delayed_work(port, 0); +} + static int tcpm_ams_start(struct tcpm_port *port, enum tcpm_ams ams) { int ret = 0; @@ -3365,11 +3376,8 @@ static void tcpm_pd_data_request(struct tcpm_port *port, bool frs_enable; int ret; - if (tcpm_vdm_ams(port) && type != PD_DATA_VENDOR_DEF) { - port->vdm_state = VDM_STATE_ERR_BUSY; - tcpm_ams_finish(port); - mod_vdm_delayed_work(port, 0); - } + if (tcpm_vdm_ams(port) && type != PD_DATA_VENDOR_DEF) + tcpm_vdm_handle_ams_interruption(port); switch (type) { case PD_DATA_SOURCE_CAP: @@ -3566,11 +3574,8 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, * Stop VDM state machine if interrupted by other Messages while NOT_SUPP is allowed in * VDM AMS if waiting for VDM responses and will be handled later. */ - if (tcpm_vdm_ams(port) && type != PD_CTRL_NOT_SUPP && type != PD_CTRL_GOOD_CRC) { - port->vdm_state = VDM_STATE_ERR_BUSY; - tcpm_ams_finish(port); - mod_vdm_delayed_work(port, 0); - } + if (tcpm_vdm_ams(port) && type != PD_CTRL_NOT_SUPP && type != PD_CTRL_GOOD_CRC) + tcpm_vdm_handle_ams_interruption(port); switch (type) { case PD_CTRL_GOOD_CRC: @@ -3888,11 +3893,8 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port, unsigned int data_size = pd_ext_header_data_size_le(msg->ext_msg.header); /* stopping VDM state machine if interrupted by other Messages */ - if (tcpm_vdm_ams(port)) { - port->vdm_state = VDM_STATE_ERR_BUSY; - tcpm_ams_finish(port); - mod_vdm_delayed_work(port, 0); - } + if (tcpm_vdm_ams(port)) + tcpm_vdm_handle_ams_interruption(port); if (!(le16_to_cpu(msg->ext_msg.header) & PD_EXT_HDR_CHUNKED)) { tcpm_pd_handle_msg(port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS); @@ -5622,20 +5624,25 @@ static void run_state_machine(struct tcpm_port *port) tcpm_set_state(port, SNK_READY, 0); break; } + /* + * For non self-powered devices, first of all try explicitly + * requesting the source capabilities for better support of + * of non-compliant PD sources (a comment with more details is + * in the SNK_WAIT_CAPABILITIES_TIMEOUT state). + * * If VBUS has never been low, and we time out waiting * for source cap, try a soft reset first, in case we * were already in a stable contract before this boot. * Do this only once. */ - if (port->vbus_never_low) { + if (!port->self_powered) { + upcoming_state = SNK_WAIT_CAPABILITIES_TIMEOUT; + } else if (port->vbus_never_low) { port->vbus_never_low = false; upcoming_state = SNK_SOFT_RESET; } else { - if (!port->self_powered) - upcoming_state = SNK_WAIT_CAPABILITIES_TIMEOUT; - else - upcoming_state = hard_reset_state(port); + upcoming_state = hard_reset_state(port); } tcpm_set_state(port, upcoming_state, @@ -5657,10 +5664,17 @@ static void run_state_machine(struct tcpm_port *port) * and handled by all USB PD source and dual role devices * according to the specification. */ + if (port->vbus_never_low) { + port->vbus_never_low = false; + upcoming_state = SNK_SOFT_RESET; + } else { + upcoming_state = hard_reset_state(port); + } + if (tcpm_pd_send_control(port, PD_CTRL_GET_SOURCE_CAP, TCPC_TX_SOP)) - tcpm_set_state_cond(port, hard_reset_state(port), 0); + tcpm_set_state_cond(port, upcoming_state, 0); else - tcpm_set_state(port, hard_reset_state(port), + tcpm_set_state(port, upcoming_state, port->timings.sink_wait_cap_time); break; case SNK_NEGOTIATE_CAPABILITIES: @@ -8645,6 +8659,30 @@ void tcpm_unregister_port(struct tcpm_port *port) } EXPORT_SYMBOL_GPL(tcpm_unregister_port); +static void devm_tcpm_unregister_port(void *data) +{ + struct tcpm_port *port = data; + tcpm_unregister_port(port); +} + +struct tcpm_port *devm_tcpm_register_port(struct device *dev, + struct tcpc_dev *tcpc) +{ + struct tcpm_port *result; + int ret; + + result = tcpm_register_port(dev, tcpc); + if (IS_ERR(result)) + return result; + + ret = devm_add_action_or_reset(dev, devm_tcpm_unregister_port, result); + if (ret < 0) + return ERR_PTR(ret); + + return result; +} +EXPORT_SYMBOL_GPL(devm_tcpm_register_port); + MODULE_AUTHOR("Guenter Roeck "); MODULE_DESCRIPTION("USB Type-C Port Manager"); MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/core/fb_logo.c b/drivers/video/fbdev/core/fb_logo.c index 0bab8352b684a1..5568e23f8b0978 100644 --- a/drivers/video/fbdev/core/fb_logo.c +++ b/drivers/video/fbdev/core/fb_logo.c @@ -6,7 +6,15 @@ #include "fb_internal.h" bool fb_center_logo __read_mostly; -int fb_logo_count __read_mostly = -1; +/* + * fb_logo_count: number of boot logos to display + * -1 = show one logo per online CPU (default upstream behavior) + * 0 = disable logo + * >0 = show exactly this many logos + * + * Flipper: hardcode to 1 to always show single logo regardless of CPU count + */ +int fb_logo_count __read_mostly = 1; static inline unsigned int safe_shift(unsigned int d, int n) { diff --git a/include/drm/bridge/dw_dp.h b/include/drm/bridge/dw_dp.h index 25363541e69d51..3037e0290861eb 100644 --- a/include/drm/bridge/dw_dp.h +++ b/include/drm/bridge/dw_dp.h @@ -20,8 +20,15 @@ enum { struct dw_dp_plat_data { u32 max_link_rate; u8 pixel_mode; + void *data; + void (*hpd_sw_sel)(void *data, bool hpd); + void (*hpd_sw_cfg)(void *data, bool hpd); }; struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, const struct dw_dp_plat_data *plat_data); +void dw_dp_unbind(struct dw_dp *dp); + +int dw_dp_runtime_suspend(struct dw_dp *dp); +int dw_dp_runtime_resume(struct dw_dp *dp); #endif /* __DW_DP__ */ diff --git a/include/drm/bridge/dw_hdmi_qp.h b/include/drm/bridge/dw_hdmi_qp.h index 6ea9c561cfef6d..0d5f09ccb957c5 100644 --- a/include/drm/bridge/dw_hdmi_qp.h +++ b/include/drm/bridge/dw_hdmi_qp.h @@ -1,22 +1,40 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (c) 2021-2022 Rockchip Electronics Co., Ltd. - * Copyright (c) 2024 Collabora Ltd. + * Copyright (c) 2024-2026 Collabora Ltd. */ #ifndef __DW_HDMI_QP__ #define __DW_HDMI_QP__ +#include + struct device; struct drm_encoder; struct dw_hdmi_qp; struct platform_device; +struct dw_hdmi_qp_link_cfg { + unsigned long long tmds_char_rate; + bool frl_enabled; + u8 frl_rate_per_lane; + u8 frl_lanes; + u8 max_frl_rate_per_lane; + u8 max_frl_lanes; + u8 min_frl_rate_per_lane; + u8 min_frl_lanes; + u8 max_ffe_level; + unsigned int bpc; +}; + struct dw_hdmi_qp_phy_ops { int (*init)(struct dw_hdmi_qp *hdmi, void *data); void (*disable)(struct dw_hdmi_qp *hdmi, void *data); enum drm_connector_status (*read_hpd)(struct dw_hdmi_qp *hdmi, void *data); void (*setup_hpd)(struct dw_hdmi_qp *hdmi, void *data); + const struct dw_hdmi_qp_link_cfg *(*get_link_cfg)(struct dw_hdmi_qp *hdmi, void *data); + int (*set_frl_rate)(struct dw_hdmi_qp *hdmi, void *data, u8 rate_per_lane, u8 lanes); + int (*set_ffe_level)(struct dw_hdmi_qp *hdmi, void *data, u8 ffe_level); }; struct dw_hdmi_qp_plat_data { diff --git a/include/drm/display/drm_scdc.h b/include/drm/display/drm_scdc.h index 3d58f37e8ed8ed..cd842940d634f0 100644 --- a/include/drm/display/drm_scdc.h +++ b/include/drm/display/drm_scdc.h @@ -24,65 +24,91 @@ #ifndef DRM_SCDC_H #define DRM_SCDC_H -#define SCDC_SINK_VERSION 0x01 - -#define SCDC_SOURCE_VERSION 0x02 - -#define SCDC_UPDATE_0 0x10 -#define SCDC_READ_REQUEST_TEST (1 << 2) -#define SCDC_CED_UPDATE (1 << 1) -#define SCDC_STATUS_UPDATE (1 << 0) - -#define SCDC_UPDATE_1 0x11 - -#define SCDC_TMDS_CONFIG 0x20 -#define SCDC_TMDS_BIT_CLOCK_RATIO_BY_40 (1 << 1) -#define SCDC_TMDS_BIT_CLOCK_RATIO_BY_10 (0 << 1) -#define SCDC_SCRAMBLING_ENABLE (1 << 0) - -#define SCDC_SCRAMBLER_STATUS 0x21 -#define SCDC_SCRAMBLING_STATUS (1 << 0) - -#define SCDC_CONFIG_0 0x30 -#define SCDC_READ_REQUEST_ENABLE (1 << 0) - -#define SCDC_STATUS_FLAGS_0 0x40 -#define SCDC_CH2_LOCK (1 << 3) -#define SCDC_CH1_LOCK (1 << 2) -#define SCDC_CH0_LOCK (1 << 1) -#define SCDC_CH_LOCK_MASK (SCDC_CH2_LOCK | SCDC_CH1_LOCK | SCDC_CH0_LOCK) -#define SCDC_CLOCK_DETECT (1 << 0) - -#define SCDC_STATUS_FLAGS_1 0x41 - -#define SCDC_ERR_DET_0_L 0x50 -#define SCDC_ERR_DET_0_H 0x51 -#define SCDC_ERR_DET_1_L 0x52 -#define SCDC_ERR_DET_1_H 0x53 -#define SCDC_ERR_DET_2_L 0x54 -#define SCDC_ERR_DET_2_H 0x55 -#define SCDC_CHANNEL_VALID (1 << 7) - -#define SCDC_ERR_DET_CHECKSUM 0x56 - -#define SCDC_TEST_CONFIG_0 0xc0 -#define SCDC_TEST_READ_REQUEST (1 << 7) -#define SCDC_TEST_READ_REQUEST_DELAY(x) ((x) & 0x7f) - -#define SCDC_MANUFACTURER_IEEE_OUI 0xd0 -#define SCDC_MANUFACTURER_IEEE_OUI_SIZE 3 - -#define SCDC_DEVICE_ID 0xd3 -#define SCDC_DEVICE_ID_SIZE 8 - -#define SCDC_DEVICE_HARDWARE_REVISION 0xdb +#include + +#define SCDC_SINK_VERSION 0x01 + +#define SCDC_SOURCE_VERSION 0x02 + +#define SCDC_UPDATE_0 0x10 +#define SCDC_FLT_UPDATE BIT(5) +#define SCDC_FRL_START BIT(4) +#define SCDC_SOURCE_TEST_UPDATE BIT(3) +#define SCDC_READ_REQUEST_TEST BIT(2) +#define SCDC_CED_UPDATE BIT(1) +#define SCDC_STATUS_UPDATE BIT(0) + +#define SCDC_UPDATE_1 0x11 + +#define SCDC_TMDS_CONFIG 0x20 +#define SCDC_TMDS_BIT_CLOCK_RATIO_BY_40 BIT(1) +#define SCDC_SCRAMBLING_ENABLE BIT(0) + +#define SCDC_SCRAMBLER_STATUS 0x21 +#define SCDC_SCRAMBLING_STATUS BIT(0) + +#define SCDC_CONFIG_0 0x30 +#define SCDC_FLT_NO_RETRAIN BIT(1) +#define SCDC_READ_REQUEST_ENABLE BIT(0) + +#define SCDC_CONFIG_1 0x31 +#define SCDC_FFE_LEVELS_MASK GENMASK(7, 4) +#define SCDC_FRL_RATE_MASK GENMASK(3, 0) +#define SCDC_FRL_RATE_DISABLE 0 +#define SCDC_FRL_RATE_3GBPS_3LANE 1 +#define SCDC_FRL_RATE_6GBPS_3LANE 2 +#define SCDC_FRL_RATE_6GBPS_4LANE 3 +#define SCDC_FRL_RATE_8GBPS_4LANE 4 +#define SCDC_FRL_RATE_10GBPS_4LANE 5 +#define SCDC_FRL_RATE_12GBPS_4LANE 6 + +#define SCDC_SOURCE_TEST_CONFIG 0x35 +#define SCDC_FLT_NO_TIMEOUT BIT(5) + +#define SCDC_STATUS_FLAGS_0 0x40 +#define SCDC_FLT_READY BIT(6) +#define SCDC_CH2_LOCK BIT(3) +#define SCDC_CH1_LOCK BIT(2) +#define SCDC_CH0_LOCK BIT(1) +#define SCDC_CH_LOCK_MASK (SCDC_CH2_LOCK | SCDC_CH1_LOCK | SCDC_CH0_LOCK) +#define SCDC_CLOCK_DETECT BIT(0) + +#define SCDC_STATUS_FLAGS_1 0x41 +#define SCDC_FRL_LN1_LTP_REQ_MASK GENMASK(7, 4) +#define SCDC_FRL_LN0_LTP_REQ_MASK GENMASK(3, 0) + +#define SCDC_STATUS_FLAGS_2 0x42 +#define SCDC_FRL_LN3_LTP_REQ_MASK GENMASK(7, 4) +#define SCDC_FRL_LN2_LTP_REQ_MASK GENMASK(3, 0) + +#define SCDC_ERR_DET_0_L 0x50 +#define SCDC_ERR_DET_0_H 0x51 +#define SCDC_ERR_DET_1_L 0x52 +#define SCDC_ERR_DET_1_H 0x53 +#define SCDC_ERR_DET_2_L 0x54 +#define SCDC_ERR_DET_2_H 0x55 +#define SCDC_CHANNEL_VALID BIT(7) + +#define SCDC_ERR_DET_CHECKSUM 0x56 + +#define SCDC_TEST_CONFIG_0 0xc0 +#define SCDC_TEST_READ_REQUEST BIT(7) +#define SCDC_TEST_READ_REQUEST_DELAY(x) ((x) & 0x7f) + +#define SCDC_MANUFACTURER_IEEE_OUI 0xd0 +#define SCDC_MANUFACTURER_IEEE_OUI_SIZE 3 + +#define SCDC_DEVICE_ID 0xd3 +#define SCDC_DEVICE_ID_SIZE 8 + +#define SCDC_DEVICE_HARDWARE_REVISION 0xdb #define SCDC_GET_DEVICE_HARDWARE_REVISION_MAJOR(x) (((x) >> 4) & 0xf) #define SCDC_GET_DEVICE_HARDWARE_REVISION_MINOR(x) (((x) >> 0) & 0xf) -#define SCDC_DEVICE_SOFTWARE_MAJOR_REVISION 0xdc -#define SCDC_DEVICE_SOFTWARE_MINOR_REVISION 0xdd +#define SCDC_DEVICE_SOFTWARE_MAJOR_REVISION 0xdc +#define SCDC_DEVICE_SOFTWARE_MINOR_REVISION 0xdd -#define SCDC_MANUFACTURER_SPECIFIC 0xde -#define SCDC_MANUFACTURER_SPECIFIC_SIZE 34 +#define SCDC_MANUFACTURER_SPECIFIC 0xde +#define SCDC_MANUFACTURER_SPECIFIC_SIZE 34 #endif diff --git a/include/drm/display/drm_scdc_helper.h b/include/drm/display/drm_scdc_helper.h index 34600476a1b9c9..4bd1caee0d2817 100644 --- a/include/drm/display/drm_scdc_helper.h +++ b/include/drm/display/drm_scdc_helper.h @@ -77,4 +77,10 @@ bool drm_scdc_get_scrambling_status(struct drm_connector *connector); bool drm_scdc_set_scrambling(struct drm_connector *connector, bool enable); bool drm_scdc_set_high_tmds_clock_ratio(struct drm_connector *connector, bool set); +bool drm_scdc_set_frl(struct drm_connector *connector, + u8 rate_per_lane, u8 lanes, u8 max_ffe_level); +bool drm_scdc_calc_lower_frl(u8 in_rate_per_lane, u8 in_lanes, + u8 *out_rate_per_lane, u8 *out_lanes); +bool drm_scdc_get_frl_ltp_request(struct drm_connector *connector, + u8 *ln0, u8 *ln1, u8 *ln2, u8 *ln3); #endif diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index a8d67bd9ee505f..7cf37cc9d52536 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -535,10 +535,12 @@ struct drm_bridge_funcs { * * Check if anything is attached to the bridge output. * - * This callback is optional, if not implemented the bridge will be - * considered as always having a component attached to its output. - * Bridges that implement this callback shall set the - * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops. + * This is the non-atomic version of detect_ctx() callback, and is + * optional. If both are implemented, it is ignored. If none is + * implemented, the bridge will be considered as always having a + * component attached to its output. Bridges that implement this + * callback shall set the DRM_BRIDGE_OP_DETECT flag in their + * &drm_bridge->ops. * * RETURNS: * @@ -547,6 +549,32 @@ struct drm_bridge_funcs { enum drm_connector_status (*detect)(struct drm_bridge *bridge, struct drm_connector *connector); + /** + * @detect_ctx: + * + * Check if anything is attached to the bridge output. + * + * This is the atomic version of detect() callback, and is optional. + * If both are implemented, it takes precedence. If none is implemented, + * the bridge will be considered as always having a component attached + * to its output. Bridges that implement this callback shall set the + * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops. + * + * To avoid races against concurrent connector state updates, the + * helper libraries always call this with ctx set to a valid context, + * and &drm_mode_config.connection_mutex will always be locked with + * the ctx parameter set to this ctx. This allows taking additional + * locks as required. + * + * RETURNS: + * + * &drm_connector_status indicating the bridge output status, + * or the error code returned by drm_modeset_lock(), -EDEADLK. + */ + int (*detect_ctx)(struct drm_bridge *bridge, + struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx); + /** * @get_modes: * @@ -646,6 +674,20 @@ struct drm_bridge_funcs { */ void (*hpd_disable)(struct drm_bridge *bridge); + /** + * @oob_notify: + * + * Notify the bridge of out of band hot plug detection. + * + * This callback is optional, it may be implemented by bridges that + * need to be notified of display connection or disconnection for + * internal reasons. One use case is to force the DP controllers HPD + * signal for USB-C DP AltMode. + */ + void (*oob_notify)(struct drm_bridge *bridge, + struct drm_connector *connector, + enum drm_connector_status status); + /** * @hdmi_tmds_char_rate_valid: * @@ -667,6 +709,29 @@ struct drm_bridge_funcs { const struct drm_display_mode *mode, unsigned long long tmds_rate); + /** + * @hdmi_frl_rate_valid: + * + * Check whether a particular FRL data rate (given in bps) is + * supported by the driver by using a FRL link rate that doesn't + * exceed the maximum advertised by the sink (also given in bps). + * + * This callback is optional and should only be implemented by the + * bridges that take part in the HDMI connector implementation and + * need to advertise FRL support. Bridges that implement it shall + * set the DRM_BRIDGE_OP_HDMI flag in their &drm_bridge->ops. + * + * Returns: + * + * Either &drm_mode_status.MODE_OK or one of the failure reasons + * in &enum drm_mode_status. + */ + enum drm_mode_status + (*hdmi_frl_rate_valid)(const struct drm_bridge *bridge, + const struct drm_display_mode *mode, + unsigned long long frl_data_rate, + unsigned long long frl_max_link_rate); + /** * @hdmi_clear_avi_infoframe: * @@ -1002,8 +1067,8 @@ struct drm_bridge_timings { enum drm_bridge_ops { /** * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to - * its output. Bridges that set this flag shall implement the - * &drm_bridge_funcs->detect callback. + * its output. Bridges that set this flag shall implement either the + * &drm_bridge_funcs->detect or &drm_bridge_funcs->detect_ctx callbacks. */ DRM_BRIDGE_OP_DETECT = BIT(0), /** @@ -1544,6 +1609,9 @@ drm_atomic_helper_bridge_propagate_bus_fmt(struct drm_bridge *bridge, enum drm_connector_status drm_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector); +int drm_bridge_detect_ctx(struct drm_bridge *bridge, + struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx); int drm_bridge_get_modes(struct drm_bridge *bridge, struct drm_connector *connector); const struct drm_edid *drm_bridge_edid_read(struct drm_bridge *bridge, diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index f83f28cae20757..9605896287fcb6 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -1343,6 +1343,28 @@ struct drm_connector_hdmi_funcs { const struct drm_display_mode *mode, unsigned long long tmds_rate); + /** + * @frl_rate_valid: + * + * This callback is invoked at atomic_check time to figure out + * whether a particular FRL data rate (given in bps) is supported + * by the driver by using a FRL link rate that doesn't exceed the + * maximum advertised by the sink (also expressed in bps). + * + * The @frl_rate_valid callback is only mandatory when drivers + * need to advertise FRL support. + * + * Returns: + * + * Either &drm_mode_status.MODE_OK or one of the failure reasons + * in &enum drm_mode_status. + */ + enum drm_mode_status + (*frl_rate_valid)(const struct drm_connector *connector, + const struct drm_display_mode *mode, + unsigned long long frl_data_rate, + unsigned long long frl_max_link_rate); + /** * @read_edid: * diff --git a/include/linux/math.h b/include/linux/math.h index 1e8fb3efbc8ce9..d7c384fd19d181 100644 --- a/include/linux/math.h +++ b/include/linux/math.h @@ -48,6 +48,17 @@ #define DIV_ROUND_UP __KERNEL_DIV_ROUND_UP +/** + * DIV_ROUND_UP_NO_OVERFLOW - divide two numbers and always round up + * @n: numerator / dividend + * @d: denominator / divisor + * + * This functions does the same as DIV_ROUND_UP, but internally uses a + * division and a modulo operation instead of math tricks. This way it + * avoids overflowing when handling big numbers. + */ +#define DIV_ROUND_UP_NO_OVERFLOW(n, d) (((n) / (d)) + !!((n) % (d))) + #define DIV_ROUND_DOWN_ULL(ll, d) \ ({ unsigned long long _tmp = (ll); do_div(_tmp, d); _tmp; }) diff --git a/include/linux/mfd/bq257xx.h b/include/linux/mfd/bq257xx.h index 1d6ddc7fb09fcb..379ef4ee8291b5 100644 --- a/include/linux/mfd/bq257xx.h +++ b/include/linux/mfd/bq257xx.h @@ -98,7 +98,433 @@ #define BQ25703_EN_OTG_MASK BIT(12) +#define BQ25792_REG00_MIN_SYS_VOLTAGE 0x00 +#define BQ25792_REG01_CHARGE_VOLTAGE_LIMIT 0x01 +#define BQ25792_REG03_CHARGE_CURRENT_LIMIT 0x03 +#define BQ25792_REG05_INPUT_VOLTAGE_LIMIT 0x05 +#define BQ25792_REG06_INPUT_CURRENT_LIMIT 0x06 +#define BQ25792_REG08_PRECHARGE_CONTROL 0x08 +#define BQ25792_REG09_TERMINATION_CONTROL 0x09 +#define BQ25792_REG0A_RECHARGE_CONTROL 0x0a +#define BQ25792_REG0B_VOTG_REGULATION 0x0b +#define BQ25792_REG0D_IOTG_REGULATION 0x0d +#define BQ25792_REG0E_TIMER_CONTROL 0x0e +#define BQ25792_REG0F_CHARGER_CONTROL_0 0x0f +#define BQ25792_REG10_CHARGER_CONTROL_1 0x10 +#define BQ25792_REG11_CHARGER_CONTROL_2 0x11 +#define BQ25792_REG12_CHARGER_CONTROL_3 0x12 +#define BQ25792_REG13_CHARGER_CONTROL_4 0x13 +#define BQ25792_REG14_CHARGER_CONTROL_5 0x14 +/* REG15 reserved */ +#define BQ25792_REG16_TEMPERATURE_CONTROL 0x16 +#define BQ25792_REG17_NTC_CONTROL_0 0x17 +#define BQ25792_REG18_NTC_CONTROL_1 0x18 +#define BQ25792_REG19_ICO_CURRENT_LIMIT 0x19 +#define BQ25792_REG1B_CHARGER_STATUS_0 0x1b +#define BQ25792_REG1C_CHARGER_STATUS_1 0x1c +#define BQ25792_REG1D_CHARGER_STATUS_2 0x1d +#define BQ25792_REG1E_CHARGER_STATUS_3 0x1e +#define BQ25792_REG1F_CHARGER_STATUS_4 0x1f +#define BQ25792_REG20_FAULT_STATUS_0 0x20 +#define BQ25792_REG21_FAULT_STATUS_1 0x21 +#define BQ25792_REG22_CHARGER_FLAG_0 0x22 +#define BQ25792_REG23_CHARGER_FLAG_1 0x23 +#define BQ25792_REG24_CHARGER_FLAG_2 0x24 +#define BQ25792_REG25_CHARGER_FLAG_3 0x25 +#define BQ25792_REG26_FAULT_FLAG_0 0x26 +#define BQ25792_REG27_FAULT_FLAG_1 0x27 +#define BQ25792_REG28_CHARGER_MASK_0 0x28 +#define BQ25792_REG29_CHARGER_MASK_1 0x29 +#define BQ25792_REG2A_CHARGER_MASK_2 0x2a +#define BQ25792_REG2B_CHARGER_MASK_3 0x2b +#define BQ25792_REG2C_FAULT_MASK_0 0x2c +#define BQ25792_REG2D_FAULT_MASK_1 0x2d +#define BQ25792_REG2E_ADC_CONTROL 0x2e +#define BQ25792_REG2F_ADC_FUNCTION_DISABLE_0 0x2f +#define BQ25792_REG30_ADC_FUNCTION_DISABLE_1 0x30 +#define BQ25792_REG31_IBUS_ADC 0x31 +#define BQ25792_REG33_IBAT_ADC 0x33 +#define BQ25792_REG35_VBUS_ADC 0x35 +#define BQ25792_REG37_VAC1_ADC 0x37 +#define BQ25792_REG39_VAC2_ADC 0x39 +#define BQ25792_REG3B_VBAT_ADC 0x3b +#define BQ25792_REG3D_VSYS_ADC 0x3d +#define BQ25792_REG3F_TS_ADC 0x3f +#define BQ25792_REG41_TDIE_ADC 0x41 +#define BQ25792_REG43_DP_ADC 0x43 +#define BQ25792_REG45_DM_ADC 0x45 +#define BQ25792_REG47_DPDM_DRIVER 0x47 +#define BQ25792_REG48_PART_INFORMATION 0x48 + +/* Minimal System Voltage */ +#define BQ25792_REG00_VSYSMIN_MASK GENMASK(5, 0) + +#define BQ25792_MINVSYS_MIN_UV 2500000 +#define BQ25792_MINVSYS_STEP_UV 250000 +#define BQ25792_MINVSYS_MAX_UV 16000000 + +/* Charge Voltage Limit */ +#define BQ25792_REG01_VREG_MASK GENMASK(10, 0) + +#define BQ25792_VBATREG_MIN_UV 3000000 +#define BQ25792_VBATREG_STEP_UV 10000 +#define BQ25792_VBATREG_MAX_UV 18800000 + +/* Charge Current Limit */ +#define BQ25792_REG03_ICHG_MASK GENMASK(8, 0) + +#define BQ25792_ICHG_MIN_UA 50000 +#define BQ25792_ICHG_STEP_UA 10000 +#define BQ25792_ICHG_MAX_UA 5000000 + +/* Input Voltage Limit */ +#define BQ25792_REG05_VINDPM_MASK GENMASK(7, 0) + +/* Input Current Limit */ +#define BQ25792_REG06_IINDPM_MASK GENMASK(8, 0) +#define BQ25792_IINDPM_DEFAULT_UA 3000000 +#define BQ25792_IINDPM_STEP_UA 10000 +#define BQ25792_IINDPM_MIN_UA 100000 +#define BQ25792_IINDPM_MAX_UA 3300000 + +/* Precharge Control */ +#define BQ25792_REG08_VBAT_LOWV_MASK GENMASK(7, 6) +#define BQ25792_REG08_IPRECHG_MASK GENMASK(5, 0) + +/* Termination Control */ +#define BQ25792_REG09_REG_RST BIT(6) +#define BQ25792_REG09_ITERM_MASK GENMASK(4, 0) + +/* Re-charge Control */ +#define BQ25792_REG0A_CELL_MASK GENMASK(7, 6) +#define BQ25792_REG0A_TRECHG_MASK GENMASK(5, 4) +#define BQ25792_REG0A_VRECHG_MASK GENMASK(3, 0) + +#define BQ25792_CELL_1S 0 +#define BQ25792_CELL_2S 1 +#define BQ25792_CELL_3S 2 +#define BQ25792_CELL_4S 3 + +#define BQ25792_TRECHG_64MS 0 +#define BQ25792_TRECHG_256MS 1 +#define BQ25792_TRECHG_1024MS 2 +#define BQ25792_TRECHG_2048MS 3 + +#define BQ25792_VRECHG_MIN_UV 50000 +#define BQ25792_VRECHG_STEP_UV 50000 +#define BQ25792_VRECHG_MAX_UV 800000 + +/* VOTG regulation */ +#define BQ25792_REG0B_VOTG_MASK GENMASK(10, 0) + +#define BQ25792_OTG_VOLT_MIN_UV 2800000 +#define BQ25792_OTG_VOLT_STEP_UV 10000 +#define BQ25792_OTG_VOLT_MAX_UV 22000000 +#define BQ25792_OTG_VOLT_NUM_VOLT ((BQ25792_OTG_VOLT_MAX_UV \ + - BQ25792_OTG_VOLT_MIN_UV) \ + / BQ25792_OTG_VOLT_STEP_UV + 1) + +/* IOTG regulation */ +#define BQ25792_REG0D_PRECHG_TMR BIT(7) +#define BQ25792_REG0D_IOTG_MASK GENMASK(6, 0) + +#define BQ25792_OTG_CUR_MIN_UA 120000 +#define BQ25792_OTG_CUR_STEP_UA 40000 +#define BQ25792_OTG_CUR_MAX_UA 3320000 + +/* Timer Control */ +#define BQ25792_REG0E_TOPOFF_TMR_MASK GENMASK(7, 6) +#define BQ25792_REG0E_EN_TRICHG_TMR BIT(5) +#define BQ25792_REG0E_EN_PRECHG_TMR BIT(4) +#define BQ25792_REG0E_EN_CHG_TMR BIT(3) +#define BQ25792_REG0E_CHG_TMR_MASK GENMASK(2, 1) +#define BQ25792_REG0E_TMR2X_EN BIT(0) + +/* Charger Control 0 */ +#define BQ25792_REG0F_EN_AUTO_IBATDIS BIT(7) +#define BQ25792_REG0F_FORCE_IBATDIS BIT(6) +#define BQ25792_REG0F_EN_CHG BIT(5) +#define BQ25792_REG0F_EN_ICO BIT(4) +#define BQ25792_REG0F_FORCE_ICO BIT(3) +#define BQ25792_REG0F_EN_HIZ BIT(2) +#define BQ25792_REG0F_EN_TERM BIT(1) +/* bit0 reserved */ + +/* Charger Control 1 */ +#define BQ25792_REG10_VAC_OVP_MASK GENMASK(5, 4) +#define BQ25792_REG10_WD_RST BIT(3) +#define BQ25792_REG10_WATCHDOG_MASK GENMASK(2, 0) + +/* Charger Control 2 */ +#define BQ25792_REG11_FORCE_INDET BIT(7) +#define BQ25792_REG11_AUTO_INDET_EN BIT(6) +#define BQ25792_REG11_EN_12V BIT(5) +#define BQ25792_REG11_EN_9V BIT(4) +#define BQ25792_REG11_HVDCP_EN BIT(3) +#define BQ25792_REG11_SDRV_CTRL_MASK GENMASK(2, 1) +#define BQ25792_REG11_SDRV_DLY BIT(0) + +/* Charger Control 3 */ +#define BQ25792_REG12_DIS_ACDRV BIT(7) +#define BQ25792_REG12_EN_OTG BIT(6) +#define BQ25792_REG12_PFM_OTG_DIS BIT(5) +#define BQ25792_REG12_PFM_FWD_DIS BIT(4) +#define BQ25792_REG12_WKUP_DLY BIT(3) +#define BQ25792_REG12_DIS_LDO BIT(2) +#define BQ25792_REG12_DIS_OTG_OOA BIT(1) +#define BQ25792_REG12_DIS_FWD_OOA BIT(0) + +/* Charger Control 4 */ +#define BQ25792_REG13_EN_ACDRV2 BIT(7) +#define BQ25792_REG13_EN_ACDRV1 BIT(6) +#define BQ25792_REG13_PWM_FREQ BIT(5) +#define BQ25792_REG13_DIS_STAT BIT(4) +#define BQ25792_REG13_DIS_VSYS_SHORT BIT(3) +#define BQ25792_REG13_DIS_VOTG_UVP BIT(2) +#define BQ25792_REG13_FORCE_VINDPM_DET BIT(1) +#define BQ25792_REG13_EN_IBUS_OCP BIT(0) + +/* Charger Control 5 */ +#define BQ25792_REG14_SFET_PRESENT BIT(7) +/* bit6 reserved */ +#define BQ25792_REG14_EN_IBAT BIT(5) +#define BQ25792_REG14_IBAT_REG_MASK GENMASK(4, 3) +#define BQ25792_REG14_EN_IINDPM BIT(2) +#define BQ25792_REG14_EN_EXTILIM BIT(1) +#define BQ25792_REG14_EN_BATOC BIT(0) + +#define BQ25792_IBAT_3A FIELD_PREP(BQ25792_REG14_IBAT_REG_MASK, 0) +#define BQ25792_IBAT_4A FIELD_PREP(BQ25792_REG14_IBAT_REG_MASK, 1) +#define BQ25792_IBAT_5A FIELD_PREP(BQ25792_REG14_IBAT_REG_MASK, 2) +#define BQ25792_IBAT_UNLIM FIELD_PREP(BQ25792_REG14_IBAT_REG_MASK, 3) + +/* Temperature Control */ +#define BQ25792_REG16_TREG_MASK GENMASK(7, 6) +#define BQ25792_REG16_TSHUT_MASK GENMASK(5, 4) +#define BQ25792_REG16_VBUS_PD_EN BIT(3) +#define BQ25792_REG16_VAC1_PD_EN BIT(2) +#define BQ25792_REG16_VAC2_PD_EN BIT(1) + +/* NTC Control 0 */ +#define BQ25792_REG17_JEITA_VSET_MASK GENMASK(7, 5) +#define BQ25792_REG17_JEITA_ISETH_MASK GENMASK(4, 3) +#define BQ25792_REG17_JEITA_ISETC_MASK GENMASK(2, 1) + +/* NTC Control 1 */ +#define BQ25792_REG18_TS_COOL_MASK GENMASK(7, 6) +#define BQ25792_REG18_TS_WARM_MASK GENMASK(5, 4) +#define BQ25792_REG18_BHOT_MASK GENMASK(3, 2) +#define BQ25792_REG18_BCOLD BIT(1) +#define BQ25792_REG18_TS_IGNORE BIT(0) + +/* ICO Current Limit */ +#define BQ25792_REG19_ICO_ILIM_MASK GENMASK(8, 0) + +/* Charger Status 0 */ +#define BQ25792_REG1B_IINDPM_STAT BIT(7) +#define BQ25792_REG1B_VINDPM_STAT BIT(6) +#define BQ25792_REG1B_WD_STAT BIT(5) +#define BQ25792_REG1B_POORSRC_STAT BIT(4) +#define BQ25792_REG1B_PG_STAT BIT(3) +#define BQ25792_REG1B_AC2_PRESENT_STAT BIT(2) +#define BQ25792_REG1B_AC1_PRESENT_STAT BIT(1) +#define BQ25792_REG1B_VBUS_PRESENT_STAT BIT(0) + +/* Charger Status 1 */ +#define BQ25792_REG1C_CHG_STAT_MASK GENMASK(7, 5) +#define BQ25792_REG1C_VBUS_STAT_MASK GENMASK(4, 1) +#define BQ25792_REG1C_BC12_DONE_STAT BIT(0) + +/* Charger Status 2 */ +#define BQ25792_REG1D_ICO_STAT_MASK GENMASK(7, 6) +#define BQ25792_REG1D_TREG_STAT BIT(2) +#define BQ25792_REG1D_DPDM_STAT BIT(1) +#define BQ25792_REG1D_VBAT_PRESENT_STAT BIT(0) + +/* Charger Status 3 */ +#define BQ25792_REG1E_ACRB2_STAT BIT(7) +#define BQ25792_REG1E_ACRB1_STAT BIT(6) +#define BQ25792_REG1E_ADC_DONE_STAT BIT(5) +#define BQ25792_REG1E_VSYS_STAT BIT(4) +#define BQ25792_REG1E_CHG_TMR_STAT BIT(3) +#define BQ25792_REG1E_TRICHG_TMR_STAT BIT(2) +#define BQ25792_REG1E_PRECHG_TMR_STAT BIT(1) + +/* Charger Status 4 */ +#define BQ25792_REG1F_VBATOTG_LOW_STAT BIT(4) +#define BQ25792_REG1F_TS_COLD_STAT BIT(3) +#define BQ25792_REG1F_TS_COOL_STAT BIT(2) +#define BQ25792_REG1F_TS_WARM_STAT BIT(1) +#define BQ25792_REG1F_TS_HOT_STAT BIT(0) + +/* FAULT Status 0 */ +#define BQ25792_REG20_IBAT_REG_STAT BIT(7) +#define BQ25792_REG20_VBUS_OVP_STAT BIT(6) +#define BQ25792_REG20_VBAT_OVP_STAT BIT(5) +#define BQ25792_REG20_IBUS_OCP_STAT BIT(4) +#define BQ25792_REG20_IBAT_OCP_STAT BIT(3) +#define BQ25792_REG20_CONV_OCP_STAT BIT(2) +#define BQ25792_REG20_VAC2_OVP_STAT BIT(1) +#define BQ25792_REG20_VAC1_OVP_STAT BIT(0) + +#define BQ25792_REG20_OVERVOLTAGE_MASK (BQ25792_REG20_VBAT_OVP_STAT | \ + BQ25792_REG20_VAC2_OVP_STAT | \ + BQ25792_REG20_VAC1_OVP_STAT) +#define BQ25792_REG20_OVERCURRENT_MASK (BQ25792_REG20_IBAT_OCP_STAT | \ + BQ25792_REG20_CONV_OCP_STAT) + +/* FAULT Status 1 */ +#define BQ25792_REG21_VSYS_SHORT_STAT BIT(7) +#define BQ25792_REG21_VSYS_OVP_STAT BIT(6) +#define BQ25792_REG21_OTG_OVP_STAT BIT(5) +#define BQ25792_REG21_OTG_UVP_STAT BIT(4) +#define BQ25792_REG21_TSHUT_STAT BIT(2) + + +/* Charger Flag 0 */ +#define BQ25792_REG22_IINDPM_FLAG BIT(7) +#define BQ25792_REG22_VINDPM_FLAG BIT(6) +#define BQ25792_REG22_WD_FLAG BIT(5) +#define BQ25792_REG22_POORSRC_FLAG BIT(4) +#define BQ25792_REG22_PG_FLAG BIT(3) +#define BQ25792_REG22_AC2_PRESENT_FLAG BIT(2) +#define BQ25792_REG22_AC1_PRESENT_FLAG BIT(1) +#define BQ25792_REG22_VBUS_PRESENT_FLAG BIT(0) + +/* Charger Flag 1 */ +#define BQ25792_REG23_CHG_FLAG BIT(7) +#define BQ25792_REG23_ICO_FLAG BIT(6) +#define BQ25792_REG23_VBUS_FLAG BIT(4) +#define BQ25792_REG23_TREG_FLAG BIT(2) +#define BQ25792_REG23_VBAT_PRESENT_FLAG BIT(1) +#define BQ25792_REG23_BC12_DONE_FLAG BIT(0) + +/* Charger Flag 2 */ +#define BQ25792_REG24_DPDM_DONE_FLAG BIT(6) +#define BQ25792_REG24_ADC_DONE_FLAG BIT(5) +#define BQ25792_REG24_VSYS_FLAG BIT(4) +#define BQ25792_REG24_CHG_TMR_FLAG BIT(3) +#define BQ25792_REG24_TRICHG_TMR_FLAG BIT(2) +#define BQ25792_REG24_PRECHG_TMR_FLAG BIT(1) +#define BQ25792_REG24_TOPOFF_TMR_FLAG BIT(0) + +/* Charger Flag 3 */ +#define BQ25792_REG25_VBATOTG_LOW_FLAG BIT(4) +#define BQ25792_REG25_TS_COLD_FLAG BIT(3) +#define BQ25792_REG25_TS_COOL_FLAG BIT(2) +#define BQ25792_REG25_TS_WARM_FLAG BIT(1) +#define BQ25792_REG25_TS_HOT_FLAG BIT(0) + +/* FAULT Flag 0 */ +#define BQ25792_REG26_IBAT_REG_FLAG BIT(7) +#define BQ25792_REG26_VBUS_OVP_FLAG BIT(6) +#define BQ25792_REG26_VBAT_OVP_FLAG BIT(5) +#define BQ25792_REG26_IBUS_OCP_FLAG BIT(4) +#define BQ25792_REG26_IBAT_OCP_FLAG BIT(3) +#define BQ25792_REG26_CONV_OCP_FLAG BIT(2) +#define BQ25792_REG26_VAC2_OVP_FLAG BIT(1) +#define BQ25792_REG26_VAC1_OVP_FLAG BIT(0) + +/* FAULT Flag 1 */ +#define BQ25792_REG27_VSYS_SHORT_FLAG BIT(7) +#define BQ25792_REG27_VSYS_OVP_FLAG BIT(6) +#define BQ25792_REG27_OTG_OVP_FLAG BIT(5) +#define BQ25792_REG27_OTG_UVP_FLAG BIT(4) +#define BQ25792_REG27_TSHUT_FLAG BIT(2) + +/* Charger Mask 0 */ +#define BQ25792_REG28_IINDPM_MASK BIT(7) +#define BQ25792_REG28_VINDPM_MASK BIT(6) +#define BQ25792_REG28_WD_MASK BIT(5) +#define BQ25792_REG28_POORSRC_MASK BIT(4) +#define BQ25792_REG28_PG_MASK BIT(3) +#define BQ25792_REG28_AC2_PRESENT_MASK BIT(2) +#define BQ25792_REG28_AC1_PRESENT_MASK BIT(1) +#define BQ25792_REG28_VBUS_PRESENT_MASK BIT(0) + +/* Charger Mask 1 */ +#define BQ25792_REG29_CHG_MASK BIT(7) +#define BQ25792_REG29_ICO_MASK BIT(6) +#define BQ25792_REG29_VBUS_MASK BIT(4) +#define BQ25792_REG29_TREG_MASK BIT(2) +#define BQ25792_REG29_VBAT_PRESENT_MASK BIT(1) +#define BQ25792_REG29_BC12_DONE_MASK BIT(0) + +/* Charger Mask 2 */ +#define BQ25792_REG2A_DPDM_DONE_MASK BIT(6) +#define BQ25792_REG2A_ADC_DONE_MASK BIT(5) +#define BQ25792_REG2A_VSYS_MASK BIT(4) +#define BQ25792_REG2A_CHG_TMR_MASK BIT(3) +#define BQ25792_REG2A_TRICHG_TMR_MASK BIT(2) +#define BQ25792_REG2A_PRECHG_TMR_MASK BIT(1) +#define BQ25792_REG2A_TOPOFF_TMR_MASK BIT(0) + +/* Charger Mask 3 */ +#define BQ25792_REG2B_VBATOTG_LOW_MASK BIT(4) +#define BQ25792_REG2B_TS_COLD_MASK BIT(3) +#define BQ25792_REG2B_TS_COOL_MASK BIT(2) +#define BQ25792_REG2B_TS_WARM_MASK BIT(1) +#define BQ25792_REG2B_TS_HOT_MASK BIT(0) + +/* FAULT Mask 0 */ +#define BQ25792_REG2C_IBAT_REG_MASK BIT(7) +#define BQ25792_REG2C_VBUS_OVP_MASK BIT(6) +#define BQ25792_REG2C_VBAT_OVP_MASK BIT(5) +#define BQ25792_REG2C_IBUS_OCP_MASK BIT(4) +#define BQ25792_REG2C_IBAT_OCP_MASK BIT(3) +#define BQ25792_REG2C_CONV_OCP_MASK BIT(2) +#define BQ25792_REG2C_VAC2_OVP_MASK BIT(1) +#define BQ25792_REG2C_VAC1_OVP_MASK BIT(0) + +/* FAULT Mask 1 */ +#define BQ25792_REG2D_VSYS_SHORT_MASK BIT(7) +#define BQ25792_REG2D_VSYS_OVP_MASK BIT(6) +#define BQ25792_REG2D_OTG_OVP_MASK BIT(5) +#define BQ25792_REG2D_OTG_UVP_MASK BIT(4) +#define BQ25792_REG2D_TSHUT_MASK BIT(2) + +/* ADC Control */ +#define BQ25792_REG2E_ADC_EN BIT(7) +#define BQ25792_REG2E_ADC_RATE BIT(6) +#define BQ25792_REG2E_ADC_SAMPLE_MASK GENMASK(5, 4) +#define BQ25792_REG2E_ADC_AVG BIT(3) +#define BQ25792_REG2E_ADC_AVG_INIT BIT(2) + +/* ADC Function Disable 0 */ +#define BQ25792_REG2F_IBUS_ADC_DIS BIT(7) +#define BQ25792_REG2F_IBAT_ADC_DIS BIT(6) +#define BQ25792_REG2F_VBUS_ADC_DIS BIT(5) +#define BQ25792_REG2F_VBAT_ADC_DIS BIT(4) +#define BQ25792_REG2F_VSYS_ADC_DIS BIT(3) +#define BQ25792_REG2F_TS_ADC_DIS BIT(2) +#define BQ25792_REG2F_TDIE_ADC_DIS BIT(1) + +/* ADC Function Disable 1 */ +#define BQ25792_REG30_DP_ADC_DIS BIT(7) +#define BQ25792_REG30_DM_ADC_DIS BIT(6) +#define BQ25792_REG30_VAC2_ADC_DIS BIT(5) +#define BQ25792_REG30_VAC1_ADC_DIS BIT(4) + +/* 0x31-0x45: ADC result registers (16-bit, RO): single full-width field */ + +#define BQ25792_ADCVSYSVBAT_STEP_UV 1000 +#define BQ25792_ADCIBAT_STEP_UA 1000 + +/* DPDM Driver */ +#define BQ25792_REG47_DPLUS_DAC_MASK GENMASK(7, 5) +#define BQ25792_REG47_DMINUS_DAC_MASK GENMASK(4, 2) + +/* Part Information */ +#define BQ25792_REG48_PN_MASK GENMASK(5, 3) +#define BQ25792_REG48_DEV_REV_MASK GENMASK(2, 0) + +enum bq257xx_type { + BQ25703A = 1, + BQ25792, +}; + struct bq257xx_device { struct i2c_client *client; struct regmap *regmap; + enum bq257xx_type type; }; diff --git a/include/linux/mfd/flipper-one-mcu.h b/include/linux/mfd/flipper-one-mcu.h new file mode 100644 index 00000000000000..7fcd547840dfa7 --- /dev/null +++ b/include/linux/mfd/flipper-one-mcu.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Register definitions for the Flipper One MCU interconnect + * Copyright (C) 2026 Flipper FZCO + */ + +#ifndef __LINUX_MFD_FLIPPER_ONE_MCU_H +#define __LINUX_MFD_FLIPPER_ONE_MCU_H + +#include +#include + +enum fomcu_interrupts { + FOMCU_INT_INPUT_BTN, + FOMCU_INT_INPUT_TOUCH, + FOMCU_INT_INPUT_HEADSET, +}; + +#define FOMCU_REG_INTSTS 0x0000 + +#define FOMCU_REG_VERSION 0x0080 + +#define FOMCU_REG_INTSTS_INPUT 0x0100 +#define FOMCU_INTOFF_INPUT 0x0 +#define FOMCU_INTSTS_INPUT_BTN BIT(0) +#define FOMCU_INTSTS_INPUT_TOUCH BIT(1) +#define FOMCU_INTSTS_INPUT_HEADSET BIT(2) + +#define FOMCU_REG_INTMSK_INPUT 0x0180 + +#define FOMCU_REG_INPUT_BTNS 0x0200 +#define FOMCU_REG_INPUT_TOUCH_X 0x0202 +#define FOMCU_REG_INPUT_TOUCH_Y 0x0204 +#define FOMCU_REG_INPUT_TOUCH_Z 0x0206 +#define FOMCU_REG_INPUT_HEADSET 0x0208 + +#define FOMCU_REG_LEDS_BR_LINK 0x0300 +#define FOMCU_REG_LEDS_BR_POWER 0x0302 +#define FOMCU_REG_LEDS_BR_WATT 0x0304 + +/* RGB565 values per each LED */ +#define FOMCU_REG_LEDS_COLOR_LINK1 0x0310 +#define FOMCU_REG_LEDS_COLOR_LINK2 0x0312 +#define FOMCU_REG_LEDS_COLOR_LINK3 0x0314 +#define FOMCU_REG_LEDS_COLOR_LINK4 0x0316 + +#define FOMCU_REG_MAX (FOMCU_REG_LEDS_COLOR_LINK4 + 1) + +struct fomcu_device { + struct i2c_client *client; + struct regmap *regmap; +}; + +#endif /* __LINUX_MFD_FLIPPER_ONE_MCU_H */ diff --git a/include/linux/mfd/rockchip-mfpwm.h b/include/linux/mfd/rockchip-mfpwm.h new file mode 100644 index 00000000000000..dbf1588a438295 --- /dev/null +++ b/include/linux/mfd/rockchip-mfpwm.h @@ -0,0 +1,470 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2025 Collabora Ltd. + * + * Common header file for all the Rockchip Multi-function PWM controller + * drivers that are spread across subsystems. + * + * Authors: + * Nicolas Frattaroli + */ + +#ifndef __SOC_ROCKCHIP_MFPWM_H__ +#define __SOC_ROCKCHIP_MFPWM_H__ + +#include +#include +#include +#include +#include + +struct rockchip_mfpwm; + +/** + * struct rockchip_mfpwm_func - struct representing a single function driver + * + * @id: unique id for this function driver instance + * @base: pointer to start of MMIO registers + * @parent: a pointer to the parent mfpwm struct + * @irq: the shared IRQ gotten from the parent mfpwm device + * @core: a pointer to the clk mux that drives this channel's PWM + */ +struct rockchip_mfpwm_func { + int id; + void __iomem *base; + struct rockchip_mfpwm *parent; + int irq; + struct clk *core; +}; + +/* + * PWMV4 Register Definitions + * -------------------------- + * + * Attributes: + * RW - Read-Write + * RO - Read-Only + * WO - Write-Only + * W1T - Write high, Self-clearing + * W1C - Write high to clear interrupt + * + * Bit ranges to be understood with Verilog-like semantics, + * e.g. [03:00] is 4 bits: 0, 1, 2 and 3. + * + * All registers must be accessed with 32-bit width accesses only + */ + +#define PWMV4_REG_VERSION 0x000 +/* + * VERSION Register Description + * [31:24] RO | Hardware Major Version + * [23:16] RO | Hardware Minor Version + * [15:15] RO | Reserved + * [14:14] RO | Hardware supports biphasic counters + * [13:13] RO | Hardware supports filters + * [12:12] RO | Hardware supports waveform generation + * [11:11] RO | Hardware supports counter + * [10:10] RO | Hardware supports frequency metering + * [09:09] RO | Hardware supports power key functionality + * [08:08] RO | Hardware supports infrared transmissions + * [07:04] RO | Channel index of this instance + * [03:00] RO | Number of channels the base instance supports + */ + +#define PWMV4_REG_ENABLE 0x004 +/* + * ENABLE Register Description + * [31:16] WO | Write Enable Mask for the lower half of the register + * Set bit `n` here to 1 if you wish to modify bit `n >> 16` in + * the same write operation + * [15:06] RO | Reserved + * [05:05] RW | PWM Channel Counter Read Enable, 1 = enabled + */ +#define PWMV4_CHN_CNT_RD_EN(v) FIELD_PREP_WM16(BIT(5), (v)) +/* + * [04:04] W1T | PWM Globally Joined Control Enable + * 1 = this PWM channel will be enabled by a global pwm enable + * bit instead of the PWM Enable bit. + */ +#define PWMV4_GLOBAL_CTRL_EN(v) FIELD_PREP_WM16(BIT(4), (v)) +/* + * [03:03] RW | Force Clock Enable + * 0 = disabled, if the PWM channel is inactive then so is the + * clock prescale module + */ +#define PWMV4_FORCE_CLK_EN(v) FIELD_PREP_WM16(BIT(3), (v)) +/* + * [02:02] W1T | PWM Control Update Enable + * 1 = enabled, commits modifications of _CTRL, _PERIOD, _DUTY and + * _OFFSET registers once 1 is written to it + */ +#define PWMV4_CTRL_UPDATE_EN FIELD_PREP_WM16_CONST(BIT(2), 1) +/* + * [01:01] RW | PWM Enable, 1 = enabled + * If in one-shot mode, clears after end of operation + */ +#define PWMV4_EN_MASK BIT(1) +#define PWMV4_EN(v) FIELD_PREP_WM16(PWMV4_EN_MASK, \ + ((v) ? 1 : 0)) +/* + * [00:00] RW | PWM Clock Enable, 1 = enabled + * If in one-shot mode, clears after end of operation + */ +#define PWMV4_CLK_EN_MASK BIT(0) +#define PWMV4_CLK_EN(v) FIELD_PREP_WM16(PWMV4_CLK_EN_MASK, \ + ((v) ? 1 : 0)) +#define PWMV4_EN_BOTH_MASK (PWMV4_EN_MASK | PWMV4_CLK_EN_MASK) +static inline __pure bool rockchip_pwm_v4_is_enabled(unsigned int val) +{ + return (val & PWMV4_EN_BOTH_MASK); +} + +#define PWMV4_REG_CLK_CTRL 0x008 +/* + * CLK_CTRL Register Description + * [31:16] WO | Write Enable Mask for the lower half of the register + * Set bit `n` here to 1 if you wish to modify bit `n >> 16` in + * the same write operation + * [15:15] RW | Clock Global Selection + * 0 = current channel scale clock + * 1 = global channel scale clock + */ +#define PWMV4_CLK_GLOBAL(v) FIELD_PREP_WM16(BIT(15), (v)) +/* + * [14:13] RW | Clock Source Selection + * 0 = Clock from PLL, frequency can be configured + * 1 = Clock from crystal oscillator, frequency is fixed + * 2 = Clock from RC oscillator, frequency is fixed + * 3 = Reserved + * NOTE: The purpose for this clock-mux-outside-CRU construct is + * to let the SoC go into a sleep state with the PWM + * hardware still having a clock signal for IR input, which + * can then wake up the SoC. + */ +#define PWMV4_CLK_SRC_PLL 0x0U +#define PWMV4_CLK_SRC_CRYSTAL 0x1U +#define PWMV4_CLK_SRC_RC 0x2U +#define PWMV4_CLK_SRC_SHIFT 13 +#define PWMV4_CLK_SRC_WIDTH 2 +/* + * [12:04] RW | Scale Factor to apply to pre-scaled clock + * 1 <= v <= 256, v means clock divided by 2*v + */ +#define PWMV4_CLK_SCALE_F(v) FIELD_PREP_WM16(GENMASK(12, 4), (v)) +/* + * [03:03] RO | Reserved + * [02:00] RW | Prescale Factor + * v here means the input clock is divided by pow(2, v) + */ +#define PWMV4_CLK_PRESCALE_F(v) FIELD_PREP_WM16(GENMASK(2, 0), (v)) + +#define PWMV4_REG_CTRL 0x00C +/* + * CTRL Register Description + * [31:16] WO | Write Enable Mask for the lower half of the register + * Set bit `n` here to 1 if you wish to modify bit `n >> 16` in + * the same write operation + * [15:09] RO | Reserved + * [08:06] RW | PWM Input Channel Selection + * By default, the channel selects its own input, but writing v + * here selects PWM input from channel v instead. + */ +#define PWMV4_CTRL_IN_SEL(v) FIELD_PREP_WM16(GENMASK(8, 6), (v)) +/* [05:05] RW | Aligned Mode, 0 = Valid, 1 = Invalid */ +#define PWMV4_CTRL_UNALIGNED(v) FIELD_PREP_WM16(BIT(5), (v)) +/* [04:04] RW | Output Mode, 0 = Left Aligned, 1 = Centre Aligned */ +#define PWMV4_LEFT_ALIGNED 0x0U +#define PWMV4_CENTRE_ALIGNED 0x1U +#define PWMV4_CTRL_OUT_MODE(v) FIELD_PREP_WM16(BIT(4), (v)) +/* + * [03:03] RW | Inactive Polarity for when the channel is either disabled or + * has completed outputting the entire waveform in one-shot mode. + * 0 = Negative, 1 = Positive + */ +#define PWMV4_POLARITY_N 0x0U +#define PWMV4_POLARITY_P 0x1U +#define PWMV4_INACTIVE_POL(v) FIELD_PREP_WM16(BIT(3), (v)) +/* + * [02:02] RW | Duty Cycle Polarity to use at the start of the waveform. + * 0 = Negative, 1 = Positive + */ +#define PWMV4_DUTY_POL_SHIFT 2 +#define PWMV4_DUTY_POL_MASK BIT(PWMV4_DUTY_POL_SHIFT) +#define PWMV4_DUTY_POL(v) FIELD_PREP_WM16(PWMV4_DUTY_POL_MASK, \ + (v)) +/* + * [01:00] RW | PWM Mode + * 0 = One-shot mode, PWM generates waveform RPT times + * 1 = Continuous mode + * 2 = Capture mode, PWM measures cycles of input waveform + * 3 = Reserved + */ +#define PWMV4_MODE_ONESHOT 0x0U +#define PWMV4_MODE_CONT 0x1U +#define PWMV4_MODE_CAPTURE 0x2U +#define PWMV4_MODE_MASK GENMASK(1, 0) +#define PWMV4_MODE(v) FIELD_PREP_WM16(PWMV4_MODE_MASK, (v)) +#define PWMV4_CTRL_COM_FLAGS (PWMV4_INACTIVE_POL(PWMV4_POLARITY_N) | \ + PWMV4_DUTY_POL(PWMV4_POLARITY_P) | \ + PWMV4_CTRL_OUT_MODE(PWMV4_LEFT_ALIGNED) | \ + PWMV4_CTRL_UNALIGNED(true)) +#define PWMV4_CTRL_CONT_FLAGS (PWMV4_MODE(PWMV4_MODE_CONT) | \ + PWMV4_CTRL_COM_FLAGS) +#define PWMV4_CTRL_CAP_FLAGS (PWMV4_MODE(PWMV4_MODE_CAPTURE) | \ + PWMV4_CTRL_COM_FLAGS) + +#define PWMV4_REG_PERIOD 0x010 +/* + * PERIOD Register Description + * [31:00] RW | Period of the output waveform + * Constraints: should be even if CTRL_OUT_MODE is CENTRE_ALIGNED + */ + +#define PWMV4_REG_DUTY 0x014 +/* + * DUTY Register Description + * [31:00] RW | Duty cycle of the output waveform + * Constraints: should be even if CTRL_OUT_MODE is CENTRE_ALIGNED + */ + +#define PWMV4_REG_OFFSET 0x018 +/* + * OFFSET Register Description + * [31:00] RW | Offset of the output waveform, based on the PWM clock + * Constraints: 0 <= v <= (PERIOD - DUTY) + */ + +#define PWMV4_REG_RPT 0x01C +/* + * RPT Register Description + * [31:16] RW | Second dimensional of the effective number of waveform + * repetitions. Increases by one every first dimensional times. + * Value `n` means `n + 1` repetitions. The final number of + * repetitions of the waveform in one-shot mode is: + * `(first_dimensional + 1) * (second_dimensional + 1)` + * [15:00] RW | First dimensional of the effective number of waveform + * repetitions. Value `n` means `n + 1` repetitions. + */ + +#define PWMV4_REG_FILTER_CTRL 0x020 +/* + * FILTER_CTRL Register Description + * [31:16] WO | Write Enable Mask for the lower half of the register + * Set bit `n` here to 1 if you wish to modify bit `n >> 16` in + * the same write operation + * [15:10] RO | Reserved + * [09:04] RW | Filter window number + * [03:01] RO | Reserved + * [00:00] RW | Filter Enable, 0 = disabled, 1 = enabled + */ + +#define PWMV4_REG_CNT 0x024 +/* + * CNT Register Description + * [31:00] RO | Current value of the PWM Channel 0 counter in pwm clock cycles, + * 0 <= v <= 2^32-1 + */ + +#define PWMV4_REG_ENABLE_DELAY 0x028 +/* + * ENABLE_DELAY Register Description + * [31:16] RO | Reserved + * [15:00] RW | PWM enable delay, in an unknown unit but probably cycles + */ + +#define PWMV4_REG_HPC 0x02C +/* + * HPC Register Description + * [31:00] RW | Number of effective high polarity cycles of the input waveform + * in capture mode. Based on the PWM clock. 0 <= v <= 2^32-1 + */ + +#define PWMV4_REG_LPC 0x030 +/* + * LPC Register Description + * [31:00] RW | Number of effective low polarity cycles of the input waveform + * in capture mode. Based on the PWM clock. 0 <= v <= 2^32-1 + */ + +#define PWMV4_REG_BIPHASIC_CNT_CTRL0 0x040 +/* + * BIPHASIC_CNT_CTRL0 Register Description + * [31:16] WO | Write Enable Mask for the lower half of the register + * Set bit `n` here to 1 if you wish to modify bit `n >> 16` in + * the same write operation + * [15:10] RO | Reserved + * [09:09] RW | Biphasic Counter Phase Edge Selection for mode 0, + * 0 = rising edge (posedge), 1 = falling edge (negedge) + * [08:08] RW | Biphasic Counter Clock force enable, 1 = force enable + * [07:07] W1T | Synchronous Enable + * [06:06] W1T | Mode Switch + * 0 = Normal Mode, 1 = Switch timer clock and measured clock + * Constraints: "Biphasic Counter Mode" must be 0 if this is 1 + * [05:03] RW | Biphasic Counter Mode + * 0x0 = Mode 0, 0x1 = Mode 1, 0x2 = Mode 2, 0x3 = Mode 3, + * 0x4 = Mode 4, 0x5 = Reserved + * [02:02] RW | Biphasic Counter Clock Selection + * 0 = clock is from PLL and frequency can be configured + * 1 = clock is from crystal oscillator and frequency is fixed + * [01:01] RW | Biphasic Counter Continuous Mode + * [00:00] W1T | Biphasic Counter Enable + */ + +#define PWMV4_REG_BIPHASIC_CNT_CTRL1 0x044 +/* + * BIPHASIC_CNT_CTRL1 Register Description + * [31:16] WO | Write Enable Mask for the lower half of the register + * Set bit `n` here to 1 if you wish to modify bit `n >> 16` in + * the same write operation + * [15:11] RO | Reserved + * [10:04] RW | Biphasic Counter Filter Window Number + * [03:01] RO | Reserved + * [00:00] RW | Biphasic Counter Filter Enable + */ + +#define PWMV4_REG_BIPHASIC_CNT_TIMER 0x048 +/* + * BIPHASIC_CNT_TIMER Register Description + * [31:00] RW | Biphasic Counter Timer Value, in number of biphasic counter + * timer clock cycles + */ + +#define PWMV4_REG_BIPHASIC_CNT_RES 0x04C +/* + * BIPHASIC_CNT_RES Register Description + * [31:00] RO | Biphasic Counter Result Value + * Constraints: Can only be read after INTSTS[9] is asserted + */ + +#define PWMV4_REG_BIPHASIC_CNT_RES_S 0x050 +/* + * BIPHASIC_CNT_RES_S Register Description + * [31:00] RO | Biphasic Counter Result Value with synchronised processing + * Can be read in real-time if BIPHASIC_CNT_CTRL0[7] was set to 1 + */ + +#define PWMV4_REG_INTSTS 0x070 +/* + * INTSTS Register Description + * [31:10] RO | Reserved + * [09:09] W1C | Biphasic Counter Interrupt Status, 1 = interrupt asserted + * [08:08] W1C | Waveform Middle Interrupt Status, 1 = interrupt asserted + * [07:07] W1C | Waveform Max Interrupt Status, 1 = interrupt asserted + * [06:06] W1C | IR Transmission End Interrupt Status, 1 = interrupt asserted + * [05:05] W1C | Power Key Match Interrupt Status, 1 = interrupt asserted + * [04:04] W1C | Frequency Meter Interrupt Status, 1 = interrupt asserted + * [03:03] W1C | Reload Interrupt Status, 1 = interrupt asserted + * [02:02] W1C | Oneshot End Interrupt Status, 1 = interrupt asserted + * [01:01] W1C | HPC Capture Interrupt Status, 1 = interrupt asserted + * [00:00] W1C | LPC Capture Interrupt Status, 1 = interrupt asserted + */ +#define PWMV4_INT_LPC BIT(0) +#define PWMV4_INT_HPC BIT(1) +#define PWMV4_INT_LPC_W(v) FIELD_PREP_WM16(PWMV4_INT_LPC, \ + ((v) ? 1 : 0)) +#define PWMV4_INT_HPC_W(v) FIELD_PREP_WM16(PWMV4_INT_HPC, \ + ((v) ? 1 : 0)) + +#define PWMV4_REG_INT_EN 0x074 +/* + * INT_EN Register Description + * [31:16] WO | Write Enable Mask for the lower half of the register + * Set bit `n` here to 1 if you wish to modify bit `n >> 16` in + * the same write operation + * [15:10] RO | Reserved + * [09:09] RW | Biphasic Counter Interrupt Enable, 1 = enabled + * [08:08] W1C | Waveform Middle Interrupt Enable, 1 = enabled + * [07:07] W1C | Waveform Max Interrupt Enable, 1 = enabled + * [06:06] W1C | IR Transmission End Interrupt Enable, 1 = enabled + * [05:05] W1C | Power Key Match Interrupt Enable, 1 = enabled + * [04:04] W1C | Frequency Meter Interrupt Enable, 1 = enabled + * [03:03] W1C | Reload Interrupt Enable, 1 = enabled + * [02:02] W1C | Oneshot End Interrupt Enable, 1 = enabled + * [01:01] W1C | HPC Capture Interrupt Enable, 1 = enabled + * [00:00] W1C | LPC Capture Interrupt Enable, 1 = enabled + */ + +#define PWMV4_REG_INT_MASK 0x078 +/* + * INT_MASK Register Description + * [31:16] WO | Write Enable Mask for the lower half of the register + * Set bit `n` here to 1 if you wish to modify bit `n >> 16` in + * the same write operation + * [15:10] RO | Reserved + * [09:09] RW | Biphasic Counter Interrupt Masked, 1 = masked + * [08:08] W1C | Waveform Middle Interrupt Masked, 1 = masked + * [07:07] W1C | Waveform Max Interrupt Masked, 1 = masked + * [06:06] W1C | IR Transmission End Interrupt Masked, 1 = masked + * [05:05] W1C | Power Key Match Interrupt Masked, 1 = masked + * [04:04] W1C | Frequency Meter Interrupt Masked, 1 = masked + * [03:03] W1C | Reload Interrupt Masked, 1 = masked + * [02:02] W1C | Oneshot End Interrupt Masked, 1 = masked + * [01:01] W1C | HPC Capture Interrupt Masked, 1 = masked + * [00:00] W1C | LPC Capture Interrupt Masked, 1 = masked + */ + +static inline u32 mfpwm_reg_read(void __iomem *base, u32 reg) +{ + return readl(base + reg); +} + +static inline void mfpwm_reg_write(void __iomem *base, u32 reg, u32 val) +{ + writel(val, base + reg); +} + +/** + * mfpwm_acquire - try becoming the active mfpwm function device + * @pwmf: pointer to the calling driver instance's &struct rockchip_mfpwm_func + * + * mfpwm device "function" drivers must call this function before doing anything + * that either modifies or relies on the parent device's state, such as clocks, + * enabling/disabling outputs, modifying shared regs etc. + * + * The return statues should always be checked. + * + * All mfpwm_acquire() calls must be balanced with corresponding mfpwm_release() + * calls once the device is no longer making changes that affect other devices, + * or stops producing user-visible effects that depend on the current device + * state being kept as-is. (e.g. after the PWM output signal is stopped) + * + * The same device function may mfpwm_acquire() multiple times while it already + * is active, i.e. it is re-entrant, though it needs to balance this with the + * same number of mfpwm_release() calls. + * + * Context: This function does not sleep. + * + * Return: + * * %0 - success + * * %-EBUSY - a different device function is active + * * %-EOVERFLOW - the acquire counter is at its maximum + */ +extern int __must_check mfpwm_acquire(struct rockchip_mfpwm_func *pwmf); + +/** + * mfpwm_release - drop usage of active mfpwm device function by 1 + * @pwmf: pointer to the calling driver instance's &struct rockchip_mfpwm_func + * + * This is the balancing call to mfpwm_acquire(). If no users of the device + * function remain, set the mfpwm device to have no active device function, + * allowing other device functions to claim it. + */ +extern void mfpwm_release(const struct rockchip_mfpwm_func *pwmf); + +/** + * mfpwm_get_mode - get the current mode the hardware is in + * @pwmf: pointer to a &struct rockchip_mfpwm_func + * + * Check the hardware registers of the PWM hardware to determine which mode it + * is currently operating in, if any. + * + * Returns: + * - %-EINVAL if @pwmf is %NULL or an error pointer + * - %-1 if the PWM hardware is off, regardless of operating mode + * - %PWMV4_MODE_ONESHOT if PWM hardware is in one-shot output mode + * - %PWMV4_MODE_CONT if PWM hardware is in continuous output mode + * - %PWMV4_MODE_CAPTURE if PWM hardware is in capture mode + */ +extern int mfpwm_get_mode(const struct rockchip_mfpwm_func *pwmf); + +#endif /* __SOC_ROCKCHIP_MFPWM_H__ */ diff --git a/include/linux/phy/phy-hdmi.h b/include/linux/phy/phy-hdmi.h index d4cf4430ee8f3b..1d4b6247507979 100644 --- a/include/linux/phy/phy-hdmi.h +++ b/include/linux/phy/phy-hdmi.h @@ -19,6 +19,10 @@ enum phy_hdmi_mode { * @tmds_char_rate: HDMI TMDS Character Rate in Hertz. * @frl.rate_per_lane: HDMI FRL Rate per Lane in Gbps. * @frl.lanes: HDMI FRL lanes count. + * @frl.ffe_level: Transmitter Feed Forward Equalizer Level. + * Optional, only meaningful when set_ffe_level flag is on. + * @frl.set_ffe_level: Flag indicating whether or not to reconfigure ffe_level. + * All the other struct fields must be ignored when this is used. * * This structure is used to represent the configuration state of a HDMI phy. */ @@ -29,6 +33,8 @@ struct phy_configure_opts_hdmi { struct { u8 rate_per_lane; u8 lanes; + u8 ffe_level; + u8 set_ffe_level : 1; } frl; }; }; diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h index 93079450bba01f..fc60980f9f41e8 100644 --- a/include/linux/usb/tcpm.h +++ b/include/linux/usb/tcpm.h @@ -177,6 +177,9 @@ struct tcpm_port; struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc); void tcpm_unregister_port(struct tcpm_port *port); +struct tcpm_port *devm_tcpm_register_port(struct device *dev, + struct tcpc_dev *tcpc); + void tcpm_vbus_change(struct tcpm_port *port); void tcpm_cc_change(struct tcpm_port *port); void tcpm_sink_frs(struct tcpm_port *port); diff --git a/sound/soc/codecs/nau8822.c b/sound/soc/codecs/nau8822.c index a11759f85eaca1..535e43f0c26a11 100644 --- a/sound/soc/codecs/nau8822.c +++ b/sound/soc/codecs/nau8822.c @@ -10,6 +10,7 @@ // // Based on WM8974.c +#include "linux/regulator/consumer.h" #include #include #include @@ -19,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -108,6 +110,10 @@ static const struct reg_default nau8822_reg_defaults[] = { { NAU8822_REG_OUTPUT_TIEOFF, 0x0000 }, }; +static const char * const nau8822_supply_names[NAU8822_NUM_SUPPLIES] = { + "vdda", "vddb", "vddc", "vddspk", +}; + static bool nau8822_readable_reg(struct device *dev, unsigned int reg) { switch (reg) { @@ -1056,6 +1062,7 @@ static int nau8822_suspend(struct snd_soc_component *component) struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); snd_soc_dapm_force_bias_level(dapm, SND_SOC_BIAS_OFF); + regulator_bulk_disable(NAU8822_NUM_SUPPLIES, nau8822->supplies); regcache_mark_dirty(nau8822->regmap); @@ -1066,6 +1073,15 @@ static int nau8822_resume(struct snd_soc_component *component) { struct nau8822 *nau8822 = snd_soc_component_get_drvdata(component); struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + int ret = regulator_bulk_enable(NAU8822_NUM_SUPPLIES, nau8822->supplies); + + if (ret) { + dev_err(component->dev, + "Failed to enable regulators: %d\n", ret); + return ret; + } + + fsleep(100); regcache_sync(nau8822->regmap); @@ -1153,7 +1169,7 @@ static int nau8822_i2c_probe(struct i2c_client *i2c) { struct device *dev = &i2c->dev; struct nau8822 *nau8822 = dev_get_platdata(dev); - int ret; + int ret, i, vddspk; if (!nau8822) { nau8822 = devm_kzalloc(dev, sizeof(*nau8822), GFP_KERNEL); @@ -1167,6 +1183,18 @@ static int nau8822_i2c_probe(struct i2c_client *i2c) return dev_err_probe(&i2c->dev, PTR_ERR(nau8822->mclk), "Error getting mclk\n"); + for (i = 0; i < NAU8822_NUM_SUPPLIES; i++) + nau8822->supplies[i].supply = nau8822_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, NAU8822_NUM_SUPPLIES, nau8822->supplies); + if (ret) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + vddspk = regulator_get_voltage(nau8822->supplies[SUPPLY_VDDSPK].consumer); + if (vddspk < 0 && vddspk != -ENODEV) + return dev_err_probe(dev, vddspk, + "Failed to get VDDSPK voltage\n"); + nau8822->regmap = devm_regmap_init_i2c(i2c, &nau8822_regmap_config); if (IS_ERR(nau8822->regmap)) { ret = PTR_ERR(nau8822->regmap); @@ -1175,21 +1203,49 @@ static int nau8822_i2c_probe(struct i2c_client *i2c) } nau8822->dev = dev; + ret = regulator_bulk_enable(NAU8822_NUM_SUPPLIES, nau8822->supplies); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable regulators\n"); + + fsleep(100); + /* Reset the codec */ ret = regmap_write(nau8822->regmap, NAU8822_REG_RESET, 0x00); if (ret != 0) { dev_err(&i2c->dev, "Failed to issue reset: %d\n", ret); - return ret; + goto err_reg; + } + + if (vddspk > 3600000) { + ret = regmap_update_bits(nau8822->regmap, + NAU8822_REG_OUTPUT_CONTROL, + NAU8822_SPKBST | + NAU8822_AUX2BST | + NAU8822_AUX1BST, 0x7); + if (ret != 0) + return dev_err_probe(dev, ret, + "Failed to update gain boost control\n"); } ret = devm_snd_soc_register_component(dev, &soc_component_dev_nau8822, &nau8822_dai, 1); if (ret != 0) { dev_err(&i2c->dev, "Failed to register CODEC: %d\n", ret); - return ret; + goto err_reg; } return 0; + +err_reg: + regulator_bulk_disable(NAU8822_NUM_SUPPLIES, nau8822->supplies); + return ret; +} + +static void nau8822_i2c_remove(struct i2c_client *i2c) +{ + struct nau8822 *nau8822 = i2c_get_clientdata(i2c); + + regulator_bulk_disable(NAU8822_NUM_SUPPLIES, nau8822->supplies); } static const struct i2c_device_id nau8822_i2c_id[] = { @@ -1212,6 +1268,7 @@ static struct i2c_driver nau8822_i2c_driver = { .of_match_table = of_match_ptr(nau8822_of_match), }, .probe = nau8822_i2c_probe, + .remove = nau8822_i2c_remove, .id_table = nau8822_i2c_id, }; module_i2c_driver(nau8822_i2c_driver); diff --git a/sound/soc/codecs/nau8822.h b/sound/soc/codecs/nau8822.h index 13fe0a091e9ed4..cefc2c3feca28d 100644 --- a/sound/soc/codecs/nau8822.h +++ b/sound/soc/codecs/nau8822.h @@ -196,6 +196,15 @@ #define NAU8822_RAUXSMUT 0x01 +/* NAU8822_REG_OUTPUT_CONTROL (0x31) */ +#define NAU8822_AOUTIMP (1 << 0) +#define NAU8822_TSEN (1 << 1) +#define NAU8822_SPKBST (1 << 2) +#define NAU8822_AUX2BST (1 << 3) +#define NAU8822_AUX1BST (1 << 4) +#define NAU8822_RDACLMX (1 << 5) +#define NAU8822_LDACLMX (1 << 6) + /* System Clock Source */ enum { NAU8822_CLK_MCLK, @@ -211,6 +220,14 @@ struct nau8822_pll { int freq_out; }; +enum { + SUPPLY_VDDA = 0, + SUPPLY_VDDB, + SUPPLY_VDDC, + SUPPLY_VDDSPK, + NAU8822_NUM_SUPPLIES +}; + /* Codec Private Data */ struct nau8822 { struct device *dev; @@ -219,6 +236,7 @@ struct nau8822 { struct nau8822_pll pll; int sysclk; int div_id; + struct regulator_bulk_data supplies[NAU8822_NUM_SUPPLIES]; }; #endif /* __NAU8822_H__ */