diff --git a/Documentation/devicetree/bindings/phy/phy-rockchip-usbdp.yaml b/Documentation/devicetree/bindings/phy/phy-rockchip-usbdp.yaml new file mode 100644 index 000000000..dcca84d57 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/phy-rockchip-usbdp.yaml @@ -0,0 +1,166 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/phy/phy-rockchip-usbdp.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Rockchip USBDP Combo PHY with Samsung IP block + +maintainers: + - Frank Wang + - Zhang Yubing + +properties: + compatible: + enum: + - rockchip,rk3588-usbdp-phy + + reg: + maxItems: 1 + + clocks: + maxItems: 4 + + clock-names: + items: + - const: refclk + - const: immortal + - const: pclk + - const: utmi + + resets: + maxItems: 5 + + reset-names: + items: + - const: init + - const: cmn + - const: lane + - const: pcs_apb + - const: pma_apb + + rockchip,dp-lane-mux: + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 2 + maxItems: 4 + description: + An array of physical Tyep-C lanes indexes. Position of an entry determines + the dp lane index, while the value of an entry indicater physical Type-C lane. + The support dp lanes number are 2 or 4. e.g. for 2 lanes dp lanes map, we could + have "rockchip,dp-lane-mux = <2, 3>;", assuming dp lane0 on Type-C phy lane2, + dp lane1 on Type-C phy lane3. For 4 lanes dp lanes map, we could have + "rockchip,dp-lane-mux = <0, 1, 2, 3>;", assuming dp lane0 on Type-C phy lane0, + dp lane1 on Type-C phy lane1, dp lane2 on Type-C phy lane2, dp lane3 on Type-C + phy lane3. If dp lane map by DisplayPort Alt mode, this property is not need. + + rockchip,u2phy-grf: + $ref: /schemas/types.yaml#/definitions/phandle + description: + Phandle to the syscon managing the 'usb2 phy general register files'. + + rockchip,usb-grf: + $ref: /schemas/types.yaml#/definitions/phandle + description: + Phandle to the syscon managing the 'usb general register files'. + + rockchip,usbdpphy-grf: + $ref: /schemas/types.yaml#/definitions/phandle + description: + Phandle to the syscon managing the 'usbdp phy general register files'. + + rockchip,vo-grf: + $ref: /schemas/types.yaml#/definitions/phandle + description: + Phandle to the syscon managing the 'video output general register files'. + When select the dp lane mapping will request its phandle. + + sbu1-dc-gpios: + description: + GPIO connected to the SBU1 line of the USB-C connector via a big resistor + (~100K) to apply a DC offset for signalling the connector orientation. + + sbu2-dc-gpios: + description: + GPIO connected to the SBU2 line of the USB-C connector via a big resistor + (~100K) to apply a DC offset for signalling the connector orientation. + + orientation-switch: + description: Flag the port as possible handler of orientation switching + type: boolean + + mode-switch: + description: Flag the port as possible handle of altmode switching + type: boolean + + dp-port: + type: object + additionalProperties: false + + properties: + "#phy-cells": + const: 0 + + required: + - "#phy-cells" + + usb3-port: + type: object + additionalProperties: false + + properties: + "#phy-cells": + const: 0 + + required: + - "#phy-cells" + + port: + $ref: /schemas/graph.yaml#/properties/port + description: + A port node to link the PHY to a TypeC controller for the purpose of + handling orientation switching. + +required: + - compatible + - reg + - clocks + - clock-names + - resets + - reset-names + - dp-port + - usb3-port + +additionalProperties: false + +examples: + - | + #include + + usbdp_phy0: phy@fed80000 { + compatible = "rockchip,rk3588-usbdp-phy"; + reg = <0x0 0xfed80000 0x0 0x10000>; + rockchip,u2phy-grf = <&usb2phy0_grf>; + rockchip,usb-grf = <&usb_grf>; + rockchip,usbdpphy-grf = <&usbdpphy0_grf>; + rockchip,vo-grf = <&vo0_grf>; + clocks = <&cru CLK_USBDPPHY_MIPIDCPPHY_REF>, + <&cru CLK_USBDP_PHY0_IMMORTAL>, + <&cru PCLK_USBDPPHY0>; + clock-names = "refclk", "immortal", "pclk"; + resets = <&cru SRST_USBDP_COMBO_PHY0_INIT>, + <&cru SRST_USBDP_COMBO_PHY0_CMN>, + <&cru SRST_USBDP_COMBO_PHY0_LANE>, + <&cru SRST_USBDP_COMBO_PHY0_PCS>, + <&cru SRST_P_USBDPPHY0>; + reset-names = "init", "cmn", "lane", "pcs_apb", "pma_apb"; + status = "disabled"; + + usbdp_phy0_dp: dp-port { + #phy-cells = <0>; + status = "disabled"; + }; + + usbdp_phy0_u3: usb3-port { + #phy-cells = <0>; + status = "disabled"; + }; diff --git a/Documentation/devicetree/bindings/soc/rockchip/grf.yaml b/Documentation/devicetree/bindings/soc/rockchip/grf.yaml index e4fa6a07b..ce1fd5b0d 100644 --- a/Documentation/devicetree/bindings/soc/rockchip/grf.yaml +++ b/Documentation/devicetree/bindings/soc/rockchip/grf.yaml @@ -28,6 +28,9 @@ properties: - rockchip,rk3588-sys-grf - rockchip,rk3588-pcie3-phy-grf - rockchip,rk3588-pcie3-pipe-grf + - rockchip,rk3588-usb-grf + - rockchip,rk3588-usbdpphy-grf + - rockchip,rk3588-vo-grf - rockchip,rv1108-usbgrf - const: syscon - items: @@ -64,6 +67,9 @@ properties: reg: maxItems: 1 + clocks: + maxItems: 1 + "#address-cells": const: 1 @@ -245,6 +251,22 @@ allOf: unevaluatedProperties: false + - if: + properties: + compatible: + contains: + enum: + - rockchip,rk3588-vo-grf + + then: + required: + - clocks + + else: + properties: + clocks: false + + examples: - | #include diff --git a/Documentation/devicetree/bindings/sound/es8328.txt b/Documentation/devicetree/bindings/sound/es8328.txt deleted file mode 100644 index 33fbf058c..000000000 --- a/Documentation/devicetree/bindings/sound/es8328.txt +++ /dev/null @@ -1,38 +0,0 @@ -Everest ES8328 audio CODEC - -This device supports both I2C and SPI. - -Required properties: - - - compatible : Should be "everest,es8328" or "everest,es8388" - - DVDD-supply : Regulator providing digital core supply voltage 1.8 - 3.6V - - AVDD-supply : Regulator providing analog supply voltage 3.3V - - PVDD-supply : Regulator providing digital IO supply voltage 1.8 - 3.6V - - IPVDD-supply : Regulator providing analog output voltage 3.3V - - clocks : A 22.5792 or 11.2896 MHz clock - - reg : the I2C address of the device for I2C, the chip select number for SPI - -Pins on the device (for linking into audio routes): - - * LOUT1 - * LOUT2 - * ROUT1 - * ROUT2 - * LINPUT1 - * RINPUT1 - * LINPUT2 - * RINPUT2 - * Mic Bias - - -Example: - -codec: es8328@11 { - compatible = "everest,es8328"; - DVDD-supply = <®_3p3v>; - AVDD-supply = <®_3p3v>; - PVDD-supply = <®_3p3v>; - HPVDD-supply = <®_3p3v>; - clocks = <&clks 169>; - reg = <0x11>; -}; diff --git a/Documentation/devicetree/bindings/sound/everest,es8328.yaml b/Documentation/devicetree/bindings/sound/everest,es8328.yaml new file mode 100644 index 000000000..a0f4670fa --- /dev/null +++ b/Documentation/devicetree/bindings/sound/everest,es8328.yaml @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/everest,es8328.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Everest ES8328 audio CODEC + +description: + Everest Audio Codec, which can be connected via I2C or SPI. + Pins on the device (for linking into audio routes) are + * LOUT1 + * LOUT2 + * ROUT1 + * ROUT2 + * LINPUT1 + * RINPUT1 + * LINPUT2 + * RINPUT2 + * Mic Bias + +maintainers: + - David Yang + +properties: + compatible: + enum: + - everest,es8328 + - everest,es8388 + + reg: + maxItems: 1 + + "#sound-dai-cells": + const: 0 + + clocks: + items: + - description: A 22.5792 or 11.2896 MHz clock + + DVDD-supply: + description: Regulator providing digital core supply voltage 1.8 - 3.6V + + AVDD-supply: + description: Regulator providing analog supply voltage 3.3V + + PVDD-supply: + description: Regulator providing digital IO supply voltage 1.8 - 3.6V + + HPVDD-supply: + description: Regulator providing analog output voltage 3.3V + +required: + - compatible + - clocks + - DVDD-supply + - AVDD-supply + - PVDD-supply + - HPVDD-supply + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + es8328: codec@11 { + compatible = "everest,es8328"; + reg = <0x11>; + AVDD-supply = <®_3p3v>; + DVDD-supply = <®_3p3v>; + HPVDD-supply = <®_3p3v>; + PVDD-supply = <®_3p3v>; + clocks = <&clks 169>; + }; + }; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts b/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts index b9d789d57..2ef5c98c4 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts @@ -9,6 +9,7 @@ #include #include #include +#include #include "rk3588.dtsi" / { @@ -56,12 +57,78 @@ button-escape { }; }; + analog-sound { + compatible = "simple-audio-card"; + pinctrl-names = "default"; + pinctrl-0 = <&hp_detect>; + simple-audio-card,name = "RK3588 EVB1 Audio"; + simple-audio-card,aux-devs = <&_headphone>, <&_speaker>; + simple-audio-card,bitclock-master = <&masterdai>; + simple-audio-card,format = "i2s"; + simple-audio-card,frame-master = <&masterdai>; + simple-audio-card,hp-det-gpio = <&gpio1 RK_PD5 GPIO_ACTIVE_LOW>; + simple-audio-card,mclk-fs = <256>; + simple-audio-card,pin-switches = "Headphones", "Speaker"; + simple-audio-card,routing = + "Speaker Amplifier INL", "LOUT2", + "Speaker Amplifier INR", "ROUT2", + "Speaker", "Speaker Amplifier OUTL", + "Speaker", "Speaker Amplifier OUTR", + "Headphones Amplifier INL", "LOUT1", + "Headphones Amplifier INR", "ROUT1", + "Headphones", "Headphones Amplifier OUTL", + "Headphones", "Headphones Amplifier OUTR", + "LINPUT1", "Onboard Microphone", + "RINPUT1", "Onboard Microphone", + "LINPUT2", "Microphone Jack", + "RINPUT2", "Microphone Jack"; + simple-audio-card,widgets = + "Microphone", "Microphone Jack", + "Microphone", "Onboard Microphone", + "Headphone", "Headphones", + "Speaker", "Speaker"; + + simple-audio-card,cpu { + sound-dai = <&i2s0_8ch>; + }; + + masterdai: simple-audio-card,codec { + sound-dai = <&es8388>; + system-clock-frequency = <12288000>; + }; + }; + + amp_headphone: headphone-amplifier { + compatible = "simple-audio-amplifier"; + enable-gpios = <&gpio1 RK_PD2 GPIO_ACTIVE_HIGH>; + pinctrl-names = "default"; + pinctrl-0 = <&headphone_amplifier_en>; + sound-name-prefix = "Headphones Amplifier"; + }; + + amp_speaker: speaker-amplifier { + compatible = "simple-audio-amplifier"; + enable-gpios = <&gpio1 RK_PD3 GPIO_ACTIVE_HIGH>; + pinctrl-names = "default"; + pinctrl-0 = <&speaker_amplifier_en>; + sound-name-prefix = "Speaker Amplifier"; + }; + backlight: backlight { compatible = "pwm-backlight"; power-supply = <&vcc12v_dcin>; pwms = <&pwm2 0 25000 0>; }; + wlan-rfkill { + compatible = "rfkill-gpio"; + label = "rfkill-pcie-wlan"; + radio-type = "wlan"; + shutdown-gpios = <&gpio3 RK_PB1 GPIO_ACTIVE_LOW>; + pinctrl-names = "default"; + pinctrl-0 = <&wifi_pwren>, <&wifi_host_wake_irq>; + }; + pcie20_avdd0v85: pcie20-avdd0v85-regulator { compatible = "regulator-fixed"; regulator-name = "pcie20_avdd0v85"; @@ -167,46 +234,75 @@ vcc5v0_usb: vcc5v0-usb-regulator { regulator-max-microvolt = <5000000>; vin-supply = <&vcc5v0_usbdcin>; }; + + vbus5v0_typec: vbus5v0-typec { + compatible = "regulator-fixed"; + regulator-name = "vbus5v0_typec"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + enable-active-high; + gpio = <&gpio4 RK_PD0 GPIO_ACTIVE_HIGH>; + vin-supply = <&vcc5v0_usb>; + pinctrl-names = "default"; + pinctrl-0 = <&typec5v_pwren>; + }; }; &combphy0_ps { status = "okay"; }; +&combphy1_ps { + status = "okay"; +}; + &combphy2_psu { status = "okay"; }; &cpu_b0 { cpu-supply = <&vdd_cpu_big0_s0>; + mem-supply = <&vdd_cpu_big0_mem_s0>; }; &cpu_b1 { cpu-supply = <&vdd_cpu_big0_s0>; + mem-supply = <&vdd_cpu_big0_mem_s0>; }; &cpu_b2 { cpu-supply = <&vdd_cpu_big1_s0>; + mem-supply = <&vdd_cpu_big1_mem_s0>; }; &cpu_b3 { cpu-supply = <&vdd_cpu_big1_s0>; + mem-supply = <&vdd_cpu_big1_mem_s0>; }; &cpu_l0 { cpu-supply = <&vdd_cpu_lit_s0>; + mem-supply = <&vdd_cpu_lit_mem_s0>; }; &cpu_l1 { cpu-supply = <&vdd_cpu_lit_s0>; + mem-supply = <&vdd_cpu_lit_mem_s0>; }; &cpu_l2 { cpu-supply = <&vdd_cpu_lit_s0>; + mem-supply = <&vdd_cpu_lit_mem_s0>; }; &cpu_l3 { cpu-supply = <&vdd_cpu_lit_s0>; + mem-supply = <&vdd_cpu_lit_mem_s0>; +}; + +&display_subsystem { + clocks = <&hdptxphy_hdmi_clk0>; + clock-names = "hdmi0_phy_pll"; }; &gmac0 { @@ -227,6 +323,56 @@ &gmac0_rgmii_clk &i2c2 { status = "okay"; + usbc0: usb-typec@22 { + compatible = "fcs,fusb302"; + reg = <0x22>; + interrupt-parent = <&gpio3>; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&usbc0_int>; + vbus-supply = <&vbus5v0_typec>; + status = "okay"; + + usb_con: connector { + compatible = "usb-c-connector"; + label = "USB-C"; + data-role = "dual"; + power-role = "dual"; + try-power-role = "sink"; + op-sink-microwatt = <1000000>; + sink-pdos = + ; + source-pdos = + ; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + usbc0_orien_sw: endpoint { + remote-endpoint = <&usbdp_phy0_orientation_switch>; + }; + }; + + port@1 { + reg = <1>; + usbc0_role_sw: endpoint { + remote-endpoint = <&dwc3_0_role_switch>; + }; + }; + + port@2 { + reg = <2>; + dp_altmode_mux: endpoint { + remote-endpoint = <&usbdp_phy0_dp_altmode_mux>; + }; + }; + }; + }; + }; + hym8563: rtc@51 { compatible = "haoyu,hym8563"; reg = <0x51>; @@ -240,6 +386,32 @@ hym8563: rtc@51 { }; }; +&i2c7 { + status = "okay"; + + es8388: audio-codec@11 { + compatible = "everest,es8388"; + reg = <0x11>; + clocks = <&cru I2S0_8CH_MCLKOUT>; + assigned-clocks = <&cru I2S0_8CH_MCLKOUT>; + assigned-clock-rates = <12288000>; + AVDD-supply = <&avcc_1v8_codec_s0>; + DVDD-supply = <&avcc_1v8_codec_s0>; + HPVDD-supply = <&vcc_3v3_s0>; + PVDD-supply = <&vcc_3v3_s0>; + #sound-dai-cells = <0>; + }; +}; + +&i2s0_8ch { + pinctrl-0 = <&i2s0_lrck + &i2s0_mclk + &i2s0_sclk + &i2s0_sdi0 + &i2s0_sdo0>; + status = "okay"; +}; + &mdio0 { rgmii_phy: ethernet-phy@1 { /* RTL8211F */ @@ -253,6 +425,12 @@ rgmii_phy: ethernet-phy@1 { }; }; +&pcie2x1l0 { + reset-gpios = <&gpio4 RK_PA5 GPIO_ACTIVE_HIGH>; + pinctrl-0 = <&pcie2_0_rst>; + status = "okay"; +}; + &pcie2x1l1 { reset-gpios = <&gpio4 RK_PA2 GPIO_ACTIVE_HIGH>; pinctrl-names = "default"; @@ -273,6 +451,20 @@ &pcie3x4 { }; &pinctrl { + audio { + hp_detect: headphone-detect { + rockchip,pins = <1 RK_PD5 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + headphone_amplifier_en: headphone-amplifier-en { + rockchip,pins = <1 RK_PD2 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + speaker_amplifier_en: speaker-amplifier-en { + rockchip,pins = <1 RK_PD3 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + rtl8111 { rtl8111_isolate: rtl8111-isolate { rockchip,pins = <1 RK_PA4 RK_FUNC_GPIO &pcfg_pull_up>; @@ -293,6 +485,10 @@ hym8563_int: hym8563-int { }; pcie2 { + pcie2_0_rst: pcie2-0-rst { + rockchip,pins = <4 RK_PA5 RK_FUNC_GPIO &pcfg_pull_none>; + }; + pcie2_1_rst: pcie2-1-rst { rockchip,pins = <4 RK_PA2 RK_FUNC_GPIO &pcfg_pull_none>; }; @@ -313,6 +509,26 @@ vcc5v0_host_en: vcc5v0-host-en { rockchip,pins = <4 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; }; }; + + usb-typec { + usbc0_int: usbc0-int { + rockchip,pins = <3 RK_PB4 RK_FUNC_GPIO &pcfg_pull_up>; + }; + + typec5v_pwren: typec5v-pwren { + rockchip,pins = <4 RK_PD0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + + wlan { + wifi_host_wake_irq: wifi-host-wake-irq { + rockchip,pins = <3 RK_PA7 RK_FUNC_GPIO &pcfg_pull_down>; + }; + + wifi_pwren: wifi-pwren { + rockchip,pins = <3 RK_PB1 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; }; &pwm2 { @@ -943,6 +1159,22 @@ &sata0 { status = "okay"; }; +&u2phy0 { + status = "okay"; +}; + +&u2phy0_otg { + status = "okay"; +}; + +&u2phy1 { + status = "okay"; +}; + +&u2phy1_otg { + status = "okay"; +}; + &u2phy2 { status = "okay"; }; @@ -981,3 +1213,82 @@ &usb_host1_ehci { &usb_host1_ohci { status = "okay"; }; + +&usbdp_phy0 { + orientation-switch; + mode-switch; + sbu1-dc-gpios = <&gpio4 RK_PA6 GPIO_ACTIVE_HIGH>; + sbu2-dc-gpios = <&gpio4 RK_PA7 GPIO_ACTIVE_HIGH>; + status = "okay"; + + port { + #address-cells = <1>; + #size-cells = <0>; + + usbdp_phy0_orientation_switch: endpoint@0 { + reg = <0>; + remote-endpoint = <&usbc0_orien_sw>; + }; + + usbdp_phy0_dp_altmode_mux: endpoint@1 { + reg = <1>; + remote-endpoint = <&dp_altmode_mux>; + }; + }; +}; + +&usbdp_phy0_u3 { + status = "okay"; +}; + +&usbdp_phy1 { + rockchip,dp-lane-mux = <2 3>; + status = "okay"; +}; + +&usbdp_phy1_u3 { + status = "okay"; +}; + +&usb_host0_xhci { + dr_mode = "otg"; + usb-role-switch; + status = "okay"; + + port { + #address-cells = <1>; + #size-cells = <0>; + dwc3_0_role_switch: endpoint@0 { + reg = <0>; + remote-endpoint = <&usbc0_role_sw>; + }; + }; +}; + +&usb_host1_xhci { + status = "okay"; +}; + +&hdmi0 { + status = "okay"; +}; + +&hdmi0_in_vp0 { + status = "okay"; +}; + +&hdptxphy_hdmi0 { + status = "okay"; +}; + +&hdptxphy_hdmi_clk0 { + status = "okay"; +}; + +&vop_mmu { + status = "okay"; +}; + +&vop { + status = "okay"; +}; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts index 741f631db..dacf6a4d8 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts @@ -4,6 +4,7 @@ #include #include +#include #include "rk3588.dtsi" / { @@ -59,6 +60,15 @@ fan: pwm-fan { #cooling-cells = <2>; }; + vcc12v_dcin: vcc12v-dcin-regulator { + compatible = "regulator-fixed"; + regulator-name = "vcc12v_dcin"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <12000000>; + regulator-max-microvolt = <12000000>; + }; + vcc3v3_pcie2x1l0: vcc3v3-pcie2x1l0-regulator { compatible = "regulator-fixed"; enable-active-high; @@ -117,6 +127,7 @@ vcc5v0_sys: vcc5v0-sys-regulator { regulator-boot-on; regulator-min-microvolt = <5000000>; regulator-max-microvolt = <5000000>; + vin-supply = <&vcc12v_dcin>; }; vcc_1v1_nldo_s3: vcc-1v1-nldo-s3-regulator { @@ -138,36 +149,53 @@ &combphy1_ps { status = "okay"; }; +&combphy2_psu { + status = "okay"; +}; + &cpu_b0 { cpu-supply = <&vdd_cpu_big0_s0>; + mem-supply = <&vdd_cpu_big0_s0>; }; &cpu_b1 { cpu-supply = <&vdd_cpu_big0_s0>; + mem-supply = <&vdd_cpu_big0_s0>; }; &cpu_b2 { cpu-supply = <&vdd_cpu_big1_s0>; + mem-supply = <&vdd_cpu_big1_s0>; }; &cpu_b3 { cpu-supply = <&vdd_cpu_big1_s0>; + mem-supply = <&vdd_cpu_big1_s0>; }; &cpu_l0 { cpu-supply = <&vdd_cpu_lit_s0>; + mem-supply = <&vdd_cpu_lit_mem_s0>; }; &cpu_l1 { cpu-supply = <&vdd_cpu_lit_s0>; + mem-supply = <&vdd_cpu_lit_mem_s0>; }; &cpu_l2 { cpu-supply = <&vdd_cpu_lit_s0>; + mem-supply = <&vdd_cpu_lit_mem_s0>; }; &cpu_l3 { cpu-supply = <&vdd_cpu_lit_s0>; + mem-supply = <&vdd_cpu_lit_mem_s0>; +}; + +&display_subsystem { + clocks = <&hdptxphy_hdmi_clk0>; + clock-names = "hdmi0_phy_pll"; }; &i2c0 { @@ -210,6 +238,61 @@ regulator-state-mem { }; }; +&i2c4 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c4m1_xfer>; + status = "okay"; + + usbc0: usb-typec@22 { + compatible = "fcs,fusb302"; + reg = <0x22>; + interrupt-parent = <&gpio3>; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&usbc0_int>; + vbus-supply = <&vcc12v_dcin>; + status = "okay"; + + usb_con: connector { + compatible = "usb-c-connector"; + label = "USB-C"; + data-role = "dual"; + power-role = "sink"; + try-power-role = "sink"; + op-sink-microwatt = <1000000>; + sink-pdos = + , + ; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + usbc0_hs: endpoint { + remote-endpoint = <&usb_host0_xhci_drd_sw>; + }; + }; + + port@1 { + reg = <1>; + usbc0_ss: endpoint { + remote-endpoint = <&usbdp_phy0_typec_ss>; + }; + }; + + port@2 { + reg = <2>; + usbc0_sbu: endpoint { + remote-endpoint = <&usbdp_phy0_typec_sbu>; + }; + }; + }; + }; + }; +}; + &i2c6 { status = "okay"; @@ -339,6 +422,10 @@ usb { vcc5v0_host_en: vcc5v0-host-en { rockchip,pins = <4 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; }; + + usbc0_int: usbc0-int { + rockchip,pins = <3 RK_PB4 RK_FUNC_GPIO &pcfg_pull_none>; + }; }; }; @@ -731,6 +818,22 @@ &uart2 { status = "okay"; }; +&u2phy0 { + status = "okay"; +}; + +&u2phy0_otg { + status = "okay"; +}; + +&u2phy1 { + status = "okay"; +}; + +&u2phy1_otg { + status = "okay"; +}; + &u2phy2 { status = "okay"; }; @@ -750,6 +853,41 @@ &u2phy3_host { status = "okay"; }; +&usbdp_phy1 { + status = "okay"; +}; + +&usbdp_phy1_u3 { + status = "okay"; +}; + +&usbdp_phy0 { + orientation-switch; + mode-switch; + sbu1-dc-gpios = <&gpio4 RK_PA6 GPIO_ACTIVE_HIGH>; + sbu2-dc-gpios = <&gpio4 RK_PA7 GPIO_ACTIVE_HIGH>; + status = "okay"; + + port { + #address-cells = <1>; + #size-cells = <0>; + + usbdp_phy0_typec_ss: endpoint@0 { + reg = <0>; + remote-endpoint = <&usbc0_ss>; + }; + + usbdp_phy0_typec_sbu: endpoint@1 { + reg = <1>; + remote-endpoint = <&usbc0_sbu>; + }; + }; +}; + +&usbdp_phy0_u3 { + status = "okay"; +}; + &usb_host0_ehci { status = "okay"; }; @@ -758,6 +896,20 @@ &usb_host0_ohci { status = "okay"; }; +&usb_host0_xhci { + usb-role-switch; + status = "okay"; + + port { + #address-cells = <1>; + #size-cells = <0>; + + usb_host0_xhci_drd_sw: endpoint { + remote-endpoint = <&usbc0_hs>; + }; + }; +}; + &usb_host1_ehci { status = "okay"; }; @@ -765,3 +917,35 @@ &usb_host1_ehci { &usb_host1_ohci { status = "okay"; }; + +&usb_host1_xhci { + status = "okay"; +}; + +&usb_host2_xhci { + status = "okay"; +}; + +&hdmi0 { + status = "okay"; +}; + +&hdmi0_in_vp0 { + status = "okay"; +}; + +&hdptxphy_hdmi0 { + status = "okay"; +}; + +&hdptxphy_hdmi_clk0 { + status = "okay"; +}; + +&vop_mmu { + status = "okay"; +}; + +&vop { + status = "okay"; +}; diff --git a/arch/arm64/boot/dts/rockchip/rk3588.dtsi b/arch/arm64/boot/dts/rockchip/rk3588.dtsi index 5519c1430..900ac0300 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588.dtsi @@ -7,6 +7,26 @@ #include "rk3588-pinctrl.dtsi" / { + usb_host1_xhci: usb@fc400000 { + compatible = "rockchip,rk3588-dwc3", "snps,dwc3"; + reg = <0x0 0xfc400000 0x0 0x400000>; + interrupts = ; + clocks = <&cru REF_CLK_USB3OTG1>, <&cru SUSPEND_CLK_USB3OTG1>, + <&cru ACLK_USB3OTG1>; + clock-names = "ref_clk", "suspend_clk", "bus_clk"; + dr_mode = "host"; + phys = <&u2phy1_otg>, <&usbdp_phy1_u3>; + phy-names = "usb2-phy", "usb3-phy"; + phy_type = "utmi_wide"; + power-domains = <&power RK3588_PD_USB>; + resets = <&cru SRST_A_USB3OTG1>; + snps,dis_enblslpm_quirk; + snps,dis-u2-freeclk-exists-quirk; + snps,dis-del-phy-power-chg-quirk; + snps,dis-tx-ipgap-linecheck-quirk; + status = "disabled"; + }; + pcie30_phy_grf: syscon@fd5b8000 { compatible = "rockchip,rk3588-pcie3-phy-grf", "syscon"; reg = <0x0 0xfd5b8000 0x0 0x10000>; @@ -17,6 +37,37 @@ pipe_phy1_grf: syscon@fd5c0000 { reg = <0x0 0xfd5c0000 0x0 0x100>; }; + usbdpphy1_grf: syscon@fd5cc000 { + compatible = "rockchip,rk3588-usbdpphy-grf", "syscon"; + reg = <0x0 0xfd5cc000 0x0 0x4000>; + }; + + usb2phy1_grf: syscon@fd5d4000 { + compatible = "rockchip,rk3588-usb2phy-grf", "syscon", + "simple-mfd"; + reg = <0x0 0xfd5d4000 0x0 0x4000>; + #address-cells = <1>; + #size-cells = <1>; + + u2phy1: usb2-phy@4000 { + compatible = "rockchip,rk3588-usb2phy"; + reg = <0x4000 0x10>; + interrupts = ; + resets = <&cru SRST_OTGPHY_U3_1>, <&cru SRST_P_USB2PHY_U3_1_GRF0>; + reset-names = "phy", "apb"; + clocks = <&cru CLK_USB2PHY_HDPTXRXPHY_REF>; + clock-names = "phyclk"; + clock-output-names = "usb480m_phy1"; + #clock-cells = <0>; + status = "disabled"; + + u2phy1_otg: otg-port { + #phy-cells = <0>; + status = "disabled"; + }; + }; + }; + i2s8_8ch: i2s@fddc8000 { compatible = "rockchip,rk3588-i2s-tdm"; reg = <0x0 0xfddc8000 0x0 0x1000>; @@ -310,6 +361,37 @@ sata-port@0 { }; }; + usbdp_phy1: phy@fed90000 { + compatible = "rockchip,rk3588-usbdp-phy"; + reg = <0x0 0xfed90000 0x0 0x10000>; + rockchip,u2phy-grf = <&usb2phy1_grf>; + rockchip,usb-grf = <&usb_grf>; + rockchip,usbdpphy-grf = <&usbdpphy1_grf>; + rockchip,vo-grf = <&vo0_grf>; + clocks = <&cru CLK_USBDPPHY_MIPIDCPPHY_REF>, + <&cru CLK_USBDP_PHY1_IMMORTAL>, + <&cru PCLK_USBDPPHY1>, + <&u2phy1>; + clock-names = "refclk", "immortal", "pclk", "utmi"; + resets = <&cru SRST_USBDP_COMBO_PHY1_INIT>, + <&cru SRST_USBDP_COMBO_PHY1_CMN>, + <&cru SRST_USBDP_COMBO_PHY1_LANE>, + <&cru SRST_USBDP_COMBO_PHY1_PCS>, + <&cru SRST_P_USBDPPHY1>; + reset-names = "init", "cmn", "lane", "pcs_apb", "pma_apb"; + status = "disabled"; + + usbdp_phy1_dp: dp-port { + #phy-cells = <0>; + status = "disabled"; + }; + + usbdp_phy1_u3: usb3-port { + #phy-cells = <0>; + status = "disabled"; + }; + }; + combphy1_ps: phy@fee10000 { compatible = "rockchip,rk3588-naneng-combphy"; reg = <0x0 0xfee10000 0x0 0x100>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-rock-5a.dts b/arch/arm64/boot/dts/rockchip/rk3588s-rock-5a.dts index 8347adcbd..58c58ec03 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-rock-5a.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588s-rock-5a.dts @@ -114,36 +114,48 @@ vcc_1v1_nldo_s3: vcc-1v1-nldo-s3-regulator { }; }; +&combphy2_psu { + status = "okay"; +}; + &cpu_b0 { cpu-supply = <&vdd_cpu_big0_s0>; + mem-supply = <&vdd_cpu_big0_s0>; }; &cpu_b1 { cpu-supply = <&vdd_cpu_big0_s0>; + mem-supply = <&vdd_cpu_big0_s0>; }; &cpu_b2 { cpu-supply = <&vdd_cpu_big1_s0>; + mem-supply = <&vdd_cpu_big1_s0>; }; &cpu_b3 { cpu-supply = <&vdd_cpu_big1_s0>; + mem-supply = <&vdd_cpu_big1_s0>; }; &cpu_l0 { cpu-supply = <&vdd_cpu_lit_s0>; + mem-supply = <&vdd_cpu_lit_mem_s0>; }; &cpu_l1 { cpu-supply = <&vdd_cpu_lit_s0>; + mem-supply = <&vdd_cpu_lit_mem_s0>; }; &cpu_l2 { cpu-supply = <&vdd_cpu_lit_s0>; + mem-supply = <&vdd_cpu_lit_mem_s0>; }; &cpu_l3 { cpu-supply = <&vdd_cpu_lit_s0>; + mem-supply = <&vdd_cpu_lit_mem_s0>; }; &i2c0 { @@ -694,6 +706,14 @@ regulator-state-mem { }; }; +&u2phy0 { + status = "okay"; +}; + +&u2phy0_otg { + status = "okay"; +}; + &u2phy2 { status = "okay"; }; @@ -717,6 +737,15 @@ &uart2 { status = "okay"; }; +&usbdp_phy0 { + status = "okay"; + rockchip,dp-lane-mux = <2 3>; +}; + +&usbdp_phy0_u3 { + status = "okay"; +}; + &usb_host0_ehci { status = "okay"; pinctrl-names = "default"; @@ -727,6 +756,11 @@ &usb_host0_ohci { status = "okay"; }; +&usb_host0_xhci { + dr_mode = "host"; + status = "okay"; +}; + &usb_host1_ehci { status = "okay"; }; @@ -734,3 +768,7 @@ &usb_host1_ehci { &usb_host1_ohci { status = "okay"; }; + +&usb_host2_xhci { + status = "okay"; +}; diff --git a/arch/arm64/boot/dts/rockchip/rk3588s.dtsi b/arch/arm64/boot/dts/rockchip/rk3588s.dtsi index 7064c0e91..4481a2e57 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588s.dtsi @@ -8,8 +8,10 @@ #include #include #include +#include #include #include +#include / { compatible = "rockchip,rk3588"; @@ -18,6 +20,215 @@ / { #address-cells = <2>; #size-cells = <2>; + cluster0_opp_table: opp-table-cluster0 { + compatible = "operating-points-v2"; + opp-shared; + + opp-408000000 { + opp-hz = /bits/ 64 <408000000>; + opp-microvolt = <750000 750000 950000>, + <750000 750000 950000>; + clock-latency-ns = <40000>; + opp-suspend; + }; + opp-600000000 { + opp-hz = /bits/ 64 <600000000>; + opp-microvolt = <750000 750000 950000>, + <750000 750000 950000>; + clock-latency-ns = <40000>; + }; + opp-816000000 { + opp-hz = /bits/ 64 <816000000>; + opp-microvolt = <750000 750000 950000>, + <750000 750000 950000>; + clock-latency-ns = <40000>; + }; + opp-1008000000 { + opp-hz = /bits/ 64 <1008000000>; + opp-microvolt = <750000 750000 950000>, + <750000 750000 950000>; + clock-latency-ns = <40000>; + }; + opp-1200000000 { + opp-hz = /bits/ 64 <1200000000>; + opp-microvolt = <775000 775000 950000>, + <775000 775000 950000>; + clock-latency-ns = <40000>; + }; + opp-1416000000 { + opp-hz = /bits/ 64 <1416000000>; + opp-microvolt = <825000 825000 950000>, + <825000 825000 950000>; + clock-latency-ns = <40000>; + }; + opp-1608000000 { + opp-hz = /bits/ 64 <1608000000>; + opp-microvolt = <875000 875000 950000>, + <875000 875000 950000>; + clock-latency-ns = <40000>; + }; + opp-1800000000 { + opp-hz = /bits/ 64 <1800000000>; + opp-microvolt = <950000 950000 950000>, + <950000 950000 950000>; + clock-latency-ns = <40000>; + }; + }; + + cluster1_opp_table: opp-table-cluster1 { + compatible = "operating-points-v2"; + opp-shared; + + rockchip,grf = <&bigcore0_grf>; + rockchip,volt-mem-read-margin = < + 855000 1 + 765000 2 + 675000 3 + 495000 4 + >; + + rockchip,reboot-freq = <1800000000>; + + opp-408000000 { + opp-hz = /bits/ 64 <408000000>; + opp-microvolt = <600000 600000 1000000>, + <675000 675000 1000000>; + clock-latency-ns = <40000>; + opp-suspend; + }; + opp-600000000 { + opp-hz = /bits/ 64 <600000000>; + opp-microvolt = <600000 600000 1000000>, + <675000 675000 1000000>; + clock-latency-ns = <40000>; + }; + opp-816000000 { + opp-hz = /bits/ 64 <816000000>; + opp-microvolt = <600000 600000 1000000>, + <675000 675000 1000000>; + clock-latency-ns = <40000>; + }; + opp-1008000000 { + opp-hz = /bits/ 64 <1008000000>; + opp-microvolt = <625000 625000 1000000>, + <675000 675000 1000000>; + clock-latency-ns = <40000>; + }; + opp-1200000000 { + opp-hz = /bits/ 64 <1200000000>; + opp-microvolt = <650000 650000 1000000>, + <675000 675000 1000000>; + clock-latency-ns = <40000>; + }; + opp-1416000000 { + opp-hz = /bits/ 64 <1416000000>; + opp-microvolt = <675000 675000 1000000>, + <675000 675000 1000000>; + clock-latency-ns = <40000>; + }; + opp-1608000000 { + opp-hz = /bits/ 64 <1608000000>; + opp-microvolt = <700000 700000 1000000>, + <700000 700000 1000000>; + clock-latency-ns = <40000>; + }; + opp-1800000000 { + opp-hz = /bits/ 64 <1800000000>; + opp-microvolt = <775000 775000 1000000>, + <775000 775000 1000000>; + clock-latency-ns = <40000>; + }; + opp-2016000000 { + opp-hz = /bits/ 64 <2016000000>; + opp-microvolt = <850000 850000 1000000>, + <850000 850000 1000000>; + clock-latency-ns = <40000>; + }; + opp-2208000000 { + opp-hz = /bits/ 64 <2208000000>; + opp-microvolt = <925000 925000 1000000>, + <925000 925000 1000000>; + clock-latency-ns = <40000>; + }; + }; + + cluster2_opp_table: opp-table-cluster2 { + compatible = "operating-points-v2"; + opp-shared; + + rockchip,grf = <&bigcore1_grf>; + rockchip,volt-mem-read-margin = < + 855000 1 + 765000 2 + 675000 3 + 495000 4 + >; + + rockchip,reboot-freq = <1800000000>; + + opp-408000000 { + opp-hz = /bits/ 64 <408000000>; + opp-microvolt = <600000 600000 1000000>, + <675000 675000 1000000>; + clock-latency-ns = <40000>; + opp-suspend; + }; + opp-600000000 { + opp-hz = /bits/ 64 <600000000>; + opp-microvolt = <600000 600000 1000000>, + <675000 675000 1000000>; + clock-latency-ns = <40000>; + }; + opp-816000000 { + opp-hz = /bits/ 64 <816000000>; + opp-microvolt = <600000 600000 1000000>, + <675000 675000 1000000>; + clock-latency-ns = <40000>; + }; + opp-1008000000 { + opp-hz = /bits/ 64 <1008000000>; + opp-microvolt = <625000 625000 1000000>, + <675000 675000 1000000>; + clock-latency-ns = <40000>; + }; + opp-1200000000 { + opp-hz = /bits/ 64 <1200000000>; + opp-microvolt = <650000 650000 1000000>, + <675000 675000 1000000>; + clock-latency-ns = <40000>; + }; + opp-1416000000 { + opp-hz = /bits/ 64 <1416000000>; + opp-microvolt = <675000 675000 1000000>, + <675000 675000 1000000>; + clock-latency-ns = <40000>; + }; + opp-1608000000 { + opp-hz = /bits/ 64 <1608000000>; + opp-microvolt = <700000 700000 1000000>, + <700000 700000 1000000>; + clock-latency-ns = <40000>; + }; + opp-1800000000 { + opp-hz = /bits/ 64 <1800000000>; + opp-microvolt = <775000 775000 1000000>, + <775000 775000 1000000>; + clock-latency-ns = <40000>; + }; + opp-2016000000 { + opp-hz = /bits/ 64 <2016000000>; + opp-microvolt = <850000 850000 1000000>, + <850000 850000 1000000>; + clock-latency-ns = <40000>; + }; + opp-2208000000 { + opp-hz = /bits/ 64 <2208000000>; + opp-microvolt = <925000 925000 1000000>, + <925000 925000 1000000>; + clock-latency-ns = <40000>; + }; + }; + cpus { #address-cells = <1>; #size-cells = <0>; @@ -64,6 +275,7 @@ cpu_l0: cpu@0 { clocks = <&scmi_clk SCMI_CLK_CPUL>; assigned-clocks = <&scmi_clk SCMI_CLK_CPUL>; assigned-clock-rates = <816000000>; + operating-points-v2 = <&cluster0_opp_table>; cpu-idle-states = <&CPU_SLEEP>; i-cache-size = <32768>; i-cache-line-size = <64>; @@ -83,6 +295,7 @@ cpu_l1: cpu@100 { enable-method = "psci"; capacity-dmips-mhz = <530>; clocks = <&scmi_clk SCMI_CLK_CPUL>; + operating-points-v2 = <&cluster0_opp_table>; cpu-idle-states = <&CPU_SLEEP>; i-cache-size = <32768>; i-cache-line-size = <64>; @@ -102,6 +315,7 @@ cpu_l2: cpu@200 { enable-method = "psci"; capacity-dmips-mhz = <530>; clocks = <&scmi_clk SCMI_CLK_CPUL>; + operating-points-v2 = <&cluster0_opp_table>; cpu-idle-states = <&CPU_SLEEP>; i-cache-size = <32768>; i-cache-line-size = <64>; @@ -121,6 +335,7 @@ cpu_l3: cpu@300 { enable-method = "psci"; capacity-dmips-mhz = <530>; clocks = <&scmi_clk SCMI_CLK_CPUL>; + operating-points-v2 = <&cluster0_opp_table>; cpu-idle-states = <&CPU_SLEEP>; i-cache-size = <32768>; i-cache-line-size = <64>; @@ -142,6 +357,7 @@ cpu_b0: cpu@400 { clocks = <&scmi_clk SCMI_CLK_CPUB01>; assigned-clocks = <&scmi_clk SCMI_CLK_CPUB01>; assigned-clock-rates = <816000000>; + operating-points-v2 = <&cluster1_opp_table>; cpu-idle-states = <&CPU_SLEEP>; i-cache-size = <65536>; i-cache-line-size = <64>; @@ -161,6 +377,7 @@ cpu_b1: cpu@500 { enable-method = "psci"; capacity-dmips-mhz = <1024>; clocks = <&scmi_clk SCMI_CLK_CPUB01>; + operating-points-v2 = <&cluster1_opp_table>; cpu-idle-states = <&CPU_SLEEP>; i-cache-size = <65536>; i-cache-line-size = <64>; @@ -182,6 +399,7 @@ cpu_b2: cpu@600 { clocks = <&scmi_clk SCMI_CLK_CPUB23>; assigned-clocks = <&scmi_clk SCMI_CLK_CPUB23>; assigned-clock-rates = <816000000>; + operating-points-v2 = <&cluster2_opp_table>; cpu-idle-states = <&CPU_SLEEP>; i-cache-size = <65536>; i-cache-line-size = <64>; @@ -201,6 +419,7 @@ cpu_b3: cpu@700 { enable-method = "psci"; capacity-dmips-mhz = <1024>; clocks = <&scmi_clk SCMI_CLK_CPUB23>; + operating-points-v2 = <&cluster2_opp_table>; cpu-idle-states = <&CPU_SLEEP>; i-cache-size = <65536>; i-cache-line-size = <64>; @@ -362,6 +581,235 @@ spll: clock-0 { #clock-cells = <0>; }; + display_subsystem: display-subsystem { + compatible = "rockchip,display-subsystem"; + ports = <&vop_out>; + }; + + thermal_zones: thermal-zones { + soc_thermal: soc-thermal { + polling-delay-passive = <20>; /* milliseconds */ + polling-delay = <1000>; /* milliseconds */ + sustainable-power = <2100>; /* milliwatts */ + + thermal-sensors = <&tsadc 0>; + trips { + trip-point-0 { + temperature = <75000>; + hysteresis = <2000>; + type = "passive"; + }; + soc_target: trip-point-1 { + temperature = <85000>; + hysteresis = <2000>; + type = "passive"; + }; + trip-point-2 { + /* millicelsius */ + temperature = <115000>; + /* millicelsius */ + hysteresis = <2000>; + type = "critical"; + }; + }; + + cooling-maps { + map0 { + trip = <&soc_target>; + cooling-device = <&cpu_l0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_l1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_l2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_l3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_b0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_b1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_b2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_b3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + contribution = <1024>; + }; + }; + }; + + bigcore0_thermal: bigcore0-thermal { + polling-delay-passive = <20>; /* milliseconds */ + polling-delay = <1000>; /* milliseconds */ + thermal-sensors = <&tsadc 1>; + + trips { + trip-point-0 { + temperature = <75000>; + hysteresis = <2000>; + type = "passive"; + }; + b0_target: trip-point-1 { + temperature = <85000>; + hysteresis = <2000>; + type = "passive"; + }; + trip-point-2 { + /* millicelsius */ + temperature = <115000>; + /* millicelsius */ + hysteresis = <2000>; + type = "critical"; + }; + }; + + cooling-maps { + map0 { + trip = <&b0_target>; + cooling-device = <&cpu_b0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_b1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + contribution = <1024>; + }; + }; + }; + + bigcore1_thermal: bigcore1-thermal { + polling-delay-passive = <20>; /* milliseconds */ + polling-delay = <1000>; /* milliseconds */ + thermal-sensors = <&tsadc 2>; + trips { + trip-point-0 { + temperature = <75000>; + hysteresis = <2000>; + type = "passive"; + }; + b1_target: trip-point-1 { + temperature = <85000>; + hysteresis = <2000>; + type = "passive"; + }; + trip-point-2 { + /* millicelsius */ + temperature = <115000>; + /* millicelsius */ + hysteresis = <2000>; + type = "critical"; + }; + }; + + cooling-maps { + map0 { + trip = <&b1_target>; + cooling-device = <&cpu_b2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_b3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + contribution = <1024>; + }; + }; + }; + + little_core_thermal: littlecore-thermal { + polling-delay-passive = <20>; /* milliseconds */ + polling-delay = <1000>; /* milliseconds */ + thermal-sensors = <&tsadc 3>; + trips { + trip-point-0 { + temperature = <75000>; + hysteresis = <2000>; + type = "passive"; + }; + l0_target: trip-point-1 { + temperature = <85000>; + hysteresis = <2000>; + type = "passive"; + }; + trip-point-2 { + /* millicelsius */ + temperature = <115000>; + /* millicelsius */ + hysteresis = <2000>; + type = "critical"; + }; + }; + + cooling-maps { + map0 { + trip = <&l0_target>; + cooling-device = <&cpu_l0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_l1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_l2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_l3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + contribution = <1024>; + }; + }; + }; + + center_thermal: center-thermal { + polling-delay-passive = <20>; /* milliseconds */ + polling-delay = <1000>; /* milliseconds */ + thermal-sensors = <&tsadc 4>; + trips { + trip-point-0 { + temperature = <75000>; + hysteresis = <2000>; + type = "passive"; + }; + trip-point-1 { + temperature = <85000>; + hysteresis = <2000>; + type = "passive"; + }; + trip-point-2 { + /* millicelsius */ + temperature = <115000>; + /* millicelsius */ + hysteresis = <2000>; + type = "critical"; + }; + }; + }; + + gpu_thermal: gpu-thermal { + polling-delay-passive = <20>; /* milliseconds */ + polling-delay = <1000>; /* milliseconds */ + thermal-sensors = <&tsadc 5>; + trips { + trip-point-0 { + temperature = <75000>; + hysteresis = <2000>; + type = "passive"; + }; + trip-point-1 { + temperature = <85000>; + hysteresis = <2000>; + type = "passive"; + }; + trip-point-2 { + /* millicelsius */ + temperature = <115000>; + /* millicelsius */ + hysteresis = <2000>; + type = "critical"; + }; + }; + }; + + npu_thermal: npu-thermal { + polling-delay-passive = <20>; /* milliseconds */ + polling-delay = <1000>; /* milliseconds */ + thermal-sensors = <&tsadc 6>; + trips { + trip-point-0 { + temperature = <75000>; + hysteresis = <2000>; + type = "passive"; + }; + trip-point-1 { + temperature = <85000>; + hysteresis = <2000>; + type = "passive"; + }; + trip-point-2 { + /* millicelsius */ + temperature = <115000>; + /* millicelsius */ + hysteresis = <2000>; + type = "critical"; + }; + }; + }; + }; + timer { compatible = "arm,armv8-timer"; interrupts = , @@ -399,6 +847,28 @@ scmi_shmem: sram@0 { }; }; + usb_host0_xhci: usb@fc000000 { + compatible = "rockchip,rk3588-dwc3", "snps,dwc3"; + reg = <0x0 0xfc000000 0x0 0x400000>; + interrupts = ; + clocks = <&cru REF_CLK_USB3OTG0>, <&cru SUSPEND_CLK_USB3OTG0>, + <&cru ACLK_USB3OTG0>; + clock-names = "ref_clk", "suspend_clk", "bus_clk"; + dr_mode = "otg"; + phys = <&u2phy0_otg>, <&usbdp_phy0_u3>; + phy-names = "usb2-phy", "usb3-phy"; + phy_type = "utmi_wide"; + power-domains = <&power RK3588_PD_USB>; + resets = <&cru SRST_A_USB3OTG0>; + snps,dis_enblslpm_quirk; + snps,dis-u1-entry-quirk; + snps,dis-u2-entry-quirk; + snps,dis-u2-freeclk-exists-quirk; + snps,dis-del-phy-power-chg-quirk; + snps,dis-tx-ipgap-linecheck-quirk; + status = "disabled"; + }; + usb_host0_ehci: usb@fc800000 { compatible = "rockchip,rk3588-ehci", "generic-ehci"; reg = <0x0 0xfc800000 0x0 0x40000>; @@ -474,6 +944,16 @@ sys_grf: syscon@fd58c000 { reg = <0x0 0xfd58c000 0x0 0x1000>; }; + bigcore0_grf: syscon@fd590000 { + compatible = "rockchip,rk3588-bigcore0-grf", "syscon"; + reg = <0x0 0xfd590000 0x0 0x100>; + }; + + bigcore1_grf: syscon@fd592000 { + compatible = "rockchip,rk3588-bigcore1-grf", "syscon"; + reg = <0x0 0xfd592000 0x0 0x100>; + }; + php_grf: syscon@fd5b0000 { compatible = "rockchip,rk3588-php-grf", "syscon"; reg = <0x0 0xfd5b0000 0x0 0x1000>; @@ -489,6 +969,37 @@ pipe_phy2_grf: syscon@fd5c4000 { reg = <0x0 0xfd5c4000 0x0 0x100>; }; + usbdpphy0_grf: syscon@fd5c8000 { + compatible = "rockchip,rk3588-usbdpphy-grf", "syscon"; + reg = <0x0 0xfd5c8000 0x0 0x4000>; + }; + + usb2phy0_grf: syscon@fd5d0000 { + compatible = "rockchip,rk3588-usb2phy-grf", "syscon", + "simple-mfd"; + reg = <0x0 0xfd5d0000 0x0 0x4000>; + #address-cells = <1>; + #size-cells = <1>; + + u2phy0: usb2-phy@0 { + compatible = "rockchip,rk3588-usb2phy"; + reg = <0x0 0x10>; + interrupts = ; + resets = <&cru SRST_OTGPHY_U3_0>, <&cru SRST_P_USB2PHY_U3_0_GRF0>; + reset-names = "phy", "apb"; + clocks = <&cru CLK_USB2PHY_HDPTXRXPHY_REF>; + clock-names = "phyclk"; + clock-output-names = "usb480m_phy0"; + #clock-cells = <0>; + status = "disabled"; + + u2phy0_otg: otg-port { + #phy-cells = <0>; + status = "disabled"; + }; + }; + }; + usb2phy2_grf: syscon@fd5d8000 { compatible = "rockchip,rk3588-usb2phy-grf", "syscon", "simple-mfd"; reg = <0x0 0xfd5d8000 0x0 0x4000>; @@ -514,6 +1025,28 @@ u2phy2_host: host-port { }; }; + vop_grf: syscon@fd5a4000 { + compatible = "rockchip,rk3588-vop-grf", "syscon"; + reg = <0x0 0xfd5a4000 0x0 0x2000>; + }; + + vo0_grf: syscon@fd5a6000 { + compatible = "rockchip,rk3588-vo-grf", "syscon"; + reg = <0x0 0xfd5a6000 0x0 0x2000>; + clocks = <&cru PCLK_VO0GRF>; + }; + + vo1_grf: syscon@fd5a8000 { + compatible = "rockchip,rk3588-vo-grf", "syscon"; + reg = <0x0 0xfd5a8000 0x0 0x100>; + clocks = <&cru PCLK_VO1GRF>; + }; + + usb_grf: syscon@fd5ac000 { + compatible = "rockchip,rk3588-usb-grf", "syscon"; + reg = <0x0 0xfd5ac000 0x0 0x4000>; + }; + usb2phy3_grf: syscon@fd5dc000 { compatible = "rockchip,rk3588-usb2phy-grf", "syscon", "simple-mfd"; reg = <0x0 0xfd5dc000 0x0 0x4000>; @@ -539,6 +1072,11 @@ u2phy3_host: host-port { }; }; + hdptxphy0_grf: syscon@fd5e0000 { + compatible = "rockchip,rk3588-hdptxphy-grf", "syscon"; + reg = <0x0 0xfd5e0000 0x0 0x100>; + }; + ioc: syscon@fd5f0000 { compatible = "rockchip,rk3588-ioc", "syscon"; reg = <0x0 0xfd5f0000 0x0 0x10000>; @@ -962,6 +1500,112 @@ power-domain@RK3588_PD_SDMMC { }; }; + vop: vop@fdd90000 { + compatible = "rockchip,rk3588-vop"; + reg = <0x0 0xfdd90000 0x0 0x4200>, <0x0 0xfdd95000 0x0 0x1000>; + reg-names = "vop", "gamma_lut"; + interrupts = ; + clocks = <&cru ACLK_VOP>, + <&cru HCLK_VOP>, + <&cru DCLK_VOP0>, + <&cru DCLK_VOP1>, + <&cru DCLK_VOP2>, + <&cru DCLK_VOP3>, + <&cru PCLK_VOP_ROOT>, + <&cru DCLK_VOP0_SRC>, + <&cru DCLK_VOP1_SRC>, + <&cru DCLK_VOP2_SRC>; + clock-names = "aclk", + "hclk", + "dclk_vp0", + "dclk_vp1", + "dclk_vp2", + "dclk_vp3", + "pclk", + "dclk_src_vp0", + "dclk_src_vp1", + "dclk_src_vp2"; + 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 = "axi", + "ahb", + "dclk_vp0", + "dclk_vp1", + "dclk_vp2", + "dclk_vp3"; + iommus = <&vop_mmu>; + power-domains = <&power RK3588_PD_VOP>; + rockchip,grf = <&sys_grf>; + rockchip,vop-grf = <&vop_grf>; + rockchip,vo1-grf = <&vo1_grf>; + rockchip,pmu = <&pmu>; + + status = "disabled"; + + // [CC: move endpoints to rock5b dts, see rock-3a] + vop_out: ports { + #address-cells = <1>; + #size-cells = <0>; + + vp0: port@0 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + + vp0_out_hdmi0: endpoint@ROCKCHIP_VOP2_EP_HDMI0 { + reg = ; + remote-endpoint = <&hdmi0_in_vp0>; + }; + }; + + vp1: port@1 { + #address-cells = <1>; + #size-cells = <0>; + reg = <1>; + + vp1_out_hdmi0: endpoint@ROCKCHIP_VOP2_EP_HDMI0 { + reg = ; + remote-endpoint = <&hdmi0_in_vp1>; + }; + }; + + vp2: port@2 { + #address-cells = <1>; + #size-cells = <0>; + reg = <2>; + + assigned-clocks = <&cru DCLK_VOP2_SRC>; + assigned-clock-parents = <&cru PLL_V0PLL>; + + vp2_out_hdmi0: endpoint@ROCKCHIP_VOP2_EP_HDMI0 { + reg = ; + remote-endpoint = <&hdmi0_in_vp2>; + }; + }; + + vp3: port@3 { + #address-cells = <1>; + #size-cells = <0>; + reg = <3>; + }; + }; + }; + + vop_mmu: iommu@fdd97e00 { + compatible = "rockchip,rk3568-iommu"; + reg = <0x0 0xfdd97e00 0x0 0x100>, <0x0 0xfdd97f00 0x0 0x100>; + interrupts = ; + interrupt-names = "vop_mmu"; + clocks = <&cru ACLK_VOP>, <&cru HCLK_VOP>; + clock-names = "aclk", "iface"; + #iommu-cells = <0>; + status = "disabled"; + }; + i2s4_8ch: i2s@fddc0000 { compatible = "rockchip,rk3588-i2s-tdm"; reg = <0x0 0xfddc0000 0x0 0x1000>; @@ -1013,6 +1657,77 @@ i2s9_8ch: i2s@fddfc000 { status = "disabled"; }; + hdmi0: hdmi@fde80000 { + compatible = "rockchip,rk3588-dw-hdmi"; + reg = <0x0 0xfde80000 0x0 0x20000>; + interrupts = , + , + , + , + ; + clocks = <&cru PCLK_HDMITX0>, + <&cru CLK_HDMIHDP0>, + <&cru CLK_HDMITX0_EARC>, + <&cru CLK_HDMITX0_REF>, + <&cru MCLK_I2S5_8CH_TX>, + <&cru DCLK_VOP0>, + <&cru DCLK_VOP1>, + <&cru DCLK_VOP2>, + <&cru DCLK_VOP3>, + <&cru HCLK_VO1>; + clock-names = "pclk", + "hpd", + "earc", + "hdmitx_ref", + "aud", + "dclk_vp0", + "dclk_vp1", + "dclk_vp2", + "dclk_vp3", + "hclk_vo1"; + resets = <&cru SRST_HDMITX0_REF>, <&cru SRST_HDMIHDP0>; + reset-names = "ref", "hdp"; + power-domains = <&power RK3588_PD_VO1>; + pinctrl-names = "default"; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd &hdmim0_tx0_scl &hdmim0_tx0_sda>; + reg-io-width = <4>; + rockchip,grf = <&sys_grf>; + rockchip,vo1_grf = <&vo1_grf>; + phys = <&hdptxphy_hdmi0>; + phy-names = "hdmi"; + #sound-dai-cells = <0>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + hdmi0_in: port@0 { + reg = <0>; + #address-cells = <1>; + #size-cells = <0>; + + hdmi0_in_vp0: endpoint@0 { + reg = <0>; + remote-endpoint = <&vp0_out_hdmi0>; + status = "disabled"; + }; + + hdmi0_in_vp1: endpoint@1 { + reg = <1>; + remote-endpoint = <&vp1_out_hdmi0>; + status = "disabled"; + }; + + hdmi0_in_vp2: endpoint@2 { + reg = <2>; + remote-endpoint = <&vp2_out_hdmi0>; + status = "disabled"; + }; + }; + }; + }; + qos_gpu_m0: qos@fdf35000 { compatible = "rockchip,rk3588-qos", "syscon"; reg = <0x0 0xfdf35000 0x0 0x20>; @@ -2110,7 +2825,6 @@ tsadc: tsadc@fec00000 { pinctrl-1 = <&tsadc_shut>; pinctrl-names = "gpio", "otpout"; #thermal-sensor-cells = <1>; - status = "disabled"; }; saradc: adc@fec10000 { @@ -2245,6 +2959,58 @@ dmac2: dma-controller@fed10000 { #dma-cells = <1>; }; + hdptxphy_hdmi0: hdmiphy@fed60000 { + compatible = "rockchip,rk3588-hdptx-phy-hdmi"; + reg = <0x0 0xfed60000 0x0 0x2000>; + clocks = <&cru CLK_USB2PHY_HDPTXRXPHY_REF>, <&cru PCLK_HDPTX0>; + clock-names = "ref", "apb"; + resets = <&cru SRST_HDPTX0>, <&cru SRST_P_HDPTX0>, + <&cru SRST_HDPTX0_INIT>, <&cru SRST_HDPTX0_CMN>, + <&cru SRST_HDPTX0_LANE>, <&cru SRST_HDPTX0_ROPLL>, + <&cru SRST_HDPTX0_LCPLL>; + reset-names = "phy", "apb", "init", "cmn", "lane", "ropll", + "lcpll"; + rockchip,grf = <&hdptxphy0_grf>; + #phy-cells = <0>; + status = "disabled"; + + hdptxphy_hdmi_clk0: clk-port { + #clock-cells = <0>; + status = "disabled"; + }; + }; + + usbdp_phy0: phy@fed80000 { + compatible = "rockchip,rk3588-usbdp-phy"; + reg = <0x0 0xfed80000 0x0 0x10000>; + rockchip,u2phy-grf = <&usb2phy0_grf>; + rockchip,usb-grf = <&usb_grf>; + rockchip,usbdpphy-grf = <&usbdpphy0_grf>; + rockchip,vo-grf = <&vo0_grf>; + clocks = <&cru CLK_USBDPPHY_MIPIDCPPHY_REF>, + <&cru CLK_USBDP_PHY0_IMMORTAL>, + <&cru PCLK_USBDPPHY0>, + <&u2phy0>; + clock-names = "refclk", "immortal", "pclk", "utmi"; + resets = <&cru SRST_USBDP_COMBO_PHY0_INIT>, + <&cru SRST_USBDP_COMBO_PHY0_CMN>, + <&cru SRST_USBDP_COMBO_PHY0_LANE>, + <&cru SRST_USBDP_COMBO_PHY0_PCS>, + <&cru SRST_P_USBDPPHY0>; + reset-names = "init", "cmn", "lane", "pcs_apb", "pma_apb"; + status = "disabled"; + + usbdp_phy0_dp: dp-port { + #phy-cells = <0>; + status = "disabled"; + }; + + usbdp_phy0_u3: usb3-port { + #phy-cells = <0>; + status = "disabled"; + }; + }; + combphy0_ps: phy@fee00000 { compatible = "rockchip,rk3588-naneng-combphy"; reg = <0x0 0xfee00000 0x0 0x100>; diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index b1109c48f..906f2f21a 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -1451,6 +1451,7 @@ CONFIG_PHY_ROCKCHIP_INNO_USB2=y CONFIG_PHY_ROCKCHIP_INNO_DSIDPHY=m CONFIG_PHY_ROCKCHIP_NANENG_COMBO_PHY=y CONFIG_PHY_ROCKCHIP_PCIE=m +CONFIG_PHY_ROCKCHIP_SAMSUNG_HDPTX_HDMI=m CONFIG_PHY_ROCKCHIP_SNPS_PCIE3=y CONFIG_PHY_ROCKCHIP_TYPEC=y CONFIG_PHY_SAMSUNG_UFS=y diff --git a/drivers/clk/clk-composite.c b/drivers/clk/clk-composite.c index 66759fe28..478a4e594 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) @@ -119,10 +120,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 a2c2b5203..94b4fb66a 100644 --- a/drivers/clk/clk-divider.c +++ b/drivers/clk/clk-divider.c @@ -220,7 +220,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); @@ -237,7 +237,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) { @@ -473,7 +473,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-rk3588.c b/drivers/clk/rockchip/clk-rk3588.c index 6994165e0..c6b605d6d 100644 --- a/drivers/clk/rockchip/clk-rk3588.c +++ b/drivers/clk/rockchip/clk-rk3588.c @@ -1851,8 +1851,6 @@ static struct rockchip_clk_branch rk3588_clk_branches[] __initdata = { RK3588_CLKGATE_CON(56), 0, GFLAGS), GATE(PCLK_TRNG0, "pclk_trng0", "pclk_vo0_root", 0, RK3588_CLKGATE_CON(56), 1, GFLAGS), - GATE(PCLK_VO0GRF, "pclk_vo0grf", "pclk_vo0_root", CLK_IGNORE_UNUSED, - RK3588_CLKGATE_CON(55), 10, GFLAGS), COMPOSITE(CLK_I2S4_8CH_TX_SRC, "clk_i2s4_8ch_tx_src", gpll_aupll_p, 0, RK3588_CLKSEL_CON(118), 5, 1, MFLAGS, 0, 5, DFLAGS, RK3588_CLKGATE_CON(56), 11, GFLAGS), @@ -1998,8 +1996,6 @@ static struct rockchip_clk_branch rk3588_clk_branches[] __initdata = { RK3588_CLKGATE_CON(60), 9, GFLAGS), GATE(PCLK_TRNG1, "pclk_trng1", "pclk_vo1_root", 0, RK3588_CLKGATE_CON(60), 10, GFLAGS), - GATE(0, "pclk_vo1grf", "pclk_vo1_root", CLK_IGNORE_UNUSED, - RK3588_CLKGATE_CON(59), 12, GFLAGS), GATE(PCLK_S_EDP0, "pclk_s_edp0", "pclk_vo1_s_root", 0, RK3588_CLKGATE_CON(59), 14, GFLAGS), GATE(PCLK_S_EDP1, "pclk_s_edp1", "pclk_vo1_s_root", 0, @@ -2447,12 +2443,15 @@ static struct rockchip_clk_branch rk3588_clk_branches[] __initdata = { GATE_LINK(HCLK_RKVDEC1_PRE, "hclk_rkvdec1_pre", "hclk_rkvdec1_root", "hclk_vdpu_root", 0, RK3588_CLKGATE_CON(41), 4, GFLAGS), GATE_LINK(ACLK_RKVDEC1_PRE, "aclk_rkvdec1_pre", "aclk_rkvdec1_root", "aclk_vdpu_root", 0, RK3588_CLKGATE_CON(41), 5, GFLAGS), GATE_LINK(ACLK_HDCP0_PRE, "aclk_hdcp0_pre", "aclk_vo0_root", "aclk_vop_low_root", 0, RK3588_CLKGATE_CON(55), 9, GFLAGS), - GATE_LINK(HCLK_VO0, "hclk_vo0", "hclk_vo0_root", "hclk_vop_root", 0, RK3588_CLKGATE_CON(55), 5, GFLAGS), + GATE_LINK(HCLK_VO0, "hclk_vo0", "hclk_vo0_root", "hclk_vop_root", RK3588_LINKED_CLK, RK3588_CLKGATE_CON(55), 5, GFLAGS), GATE_LINK(ACLK_HDCP1_PRE, "aclk_hdcp1_pre", "aclk_hdcp1_root", "aclk_vo1usb_top_root", 0, RK3588_CLKGATE_CON(59), 6, GFLAGS), - GATE_LINK(HCLK_VO1, "hclk_vo1", "hclk_vo1_root", "hclk_vo1usb_top_root", 0, RK3588_CLKGATE_CON(59), 9, GFLAGS), + GATE_LINK(HCLK_VO1, "hclk_vo1", "hclk_vo1_root", "hclk_vo1usb_top_root", RK3588_LINKED_CLK, RK3588_CLKGATE_CON(59), 9, GFLAGS), GATE_LINK(ACLK_AV1_PRE, "aclk_av1_pre", "aclk_av1_root", "aclk_vdpu_root", 0, RK3588_CLKGATE_CON(68), 1, GFLAGS), GATE_LINK(PCLK_AV1_PRE, "pclk_av1_pre", "pclk_av1_root", "hclk_vdpu_root", 0, RK3588_CLKGATE_CON(68), 4, GFLAGS), GATE_LINK(HCLK_SDIO_PRE, "hclk_sdio_pre", "hclk_sdio_root", "hclk_nvm", 0, RK3588_CLKGATE_CON(75), 1, GFLAGS), + GATE_LINK(PCLK_VO0GRF, "pclk_vo0grf", "pclk_vo0_root", "hclk_vo0", CLK_IGNORE_UNUSED, RK3588_CLKGATE_CON(55), 10, GFLAGS), + GATE_LINK(PCLK_VO1GRF, "pclk_vo1grf", "pclk_vo1_root", "hclk_vo1", CLK_IGNORE_UNUSED, RK3588_CLKGATE_CON(59), 12, GFLAGS), + }; static void __init rk3588_clk_init(struct device_node *np) diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index f91160689..1e2552108 100644 --- a/drivers/cpufreq/Kconfig.arm +++ b/drivers/cpufreq/Kconfig.arm @@ -189,6 +189,16 @@ config ARM_RASPBERRYPI_CPUFREQ If in doubt, say N. +config ARM_ROCKCHIP_CPUFREQ + tristate "Rockchip CPUfreq driver" + depends on ARCH_ROCKCHIP && CPUFREQ_DT + select PM_OPP + help + This adds the CPUFreq driver support for Rockchip SoCs, + based on cpufreq-dt. + + If in doubt, say N. + config ARM_S3C64XX_CPUFREQ bool "Samsung S3C64XX" depends on CPU_S3C6410 diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index 8d141c71b..14fb48863 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -71,6 +71,7 @@ obj-$(CONFIG_PXA3xx) += pxa3xx-cpufreq.o obj-$(CONFIG_ARM_QCOM_CPUFREQ_HW) += qcom-cpufreq-hw.o obj-$(CONFIG_ARM_QCOM_CPUFREQ_NVMEM) += qcom-cpufreq-nvmem.o obj-$(CONFIG_ARM_RASPBERRYPI_CPUFREQ) += raspberrypi-cpufreq.o +obj-$(CONFIG_ARM_ROCKCHIP_CPUFREQ) += rockchip-cpufreq.o obj-$(CONFIG_ARM_S3C64XX_CPUFREQ) += s3c64xx-cpufreq.o obj-$(CONFIG_ARM_S5PV210_CPUFREQ) += s5pv210-cpufreq.o obj-$(CONFIG_ARM_SA1110_CPUFREQ) += sa1110-cpufreq.o diff --git a/drivers/cpufreq/cpufreq-dt-platdev.c b/drivers/cpufreq/cpufreq-dt-platdev.c index bd1e1357c..cfd35aa52 100644 --- a/drivers/cpufreq/cpufreq-dt-platdev.c +++ b/drivers/cpufreq/cpufreq-dt-platdev.c @@ -168,6 +168,8 @@ static const struct of_device_id blocklist[] __initconst = { { .compatible = "qcom,sm8450", }, { .compatible = "qcom,sm8550", }, + { .compatible = "rockchip,rk3588", }, + { .compatible = "st,stih407", }, { .compatible = "st,stih410", }, { .compatible = "st,stih418", }, diff --git a/drivers/cpufreq/rockchip-cpufreq.c b/drivers/cpufreq/rockchip-cpufreq.c new file mode 100644 index 000000000..0bf57ac85 --- /dev/null +++ b/drivers/cpufreq/rockchip-cpufreq.c @@ -0,0 +1,645 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Rockchip CPUFreq Driver. This is similar to the generic DT + * cpufreq driver, but handles the following platform specific + * quirks: + * + * * support for two regulators - one for the CPU core and one + * for the memory interface + * * reboot handler to setup the reboot frequency + * * handling of read margin registers + * + * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd + * Copyright (C) 2023 Collabora Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cpufreq-dt.h" + +#define RK3588_MEMCFG_HSSPRF_LOW 0x20 +#define RK3588_MEMCFG_HSDPRF_LOW 0x28 +#define RK3588_MEMCFG_HSDPRF_HIGH 0x2c +#define RK3588_CPU_CTRL 0x30 + +#define VOLT_RM_TABLE_END ~1 + +static struct platform_device *cpufreq_pdev; +static LIST_HEAD(priv_list); + +struct volt_rm_table { + uint32_t volt; + uint32_t rm; +}; + +struct rockchip_opp_info { + const struct rockchip_opp_data *data; + struct volt_rm_table *volt_rm_tbl; + struct regmap *grf; + u32 current_rm; + u32 reboot_freq; +}; + +struct private_data { + struct list_head node; + + cpumask_var_t cpus; + struct device *cpu_dev; + struct cpufreq_frequency_table *freq_table; +}; + +struct rockchip_opp_data { + int (*set_read_margin)(struct device *dev, struct rockchip_opp_info *opp_info, + unsigned long volt); +}; + +struct cluster_info { + struct list_head list_head; + struct rockchip_opp_info opp_info; + cpumask_t cpus; +}; +static LIST_HEAD(cluster_info_list); + +static int rk3588_cpu_set_read_margin(struct device *dev, struct rockchip_opp_info *opp_info, + unsigned long volt) +{ + bool is_found = false; + u32 rm; + int i; + + if (!opp_info->volt_rm_tbl) + return 0; + + for (i = 0; opp_info->volt_rm_tbl[i].rm != VOLT_RM_TABLE_END; i++) { + if (volt >= opp_info->volt_rm_tbl[i].volt) { + rm = opp_info->volt_rm_tbl[i].rm; + is_found = true; + break; + } + } + + if (!is_found) + return 0; + if (rm == opp_info->current_rm) + return 0; + if (!opp_info->grf) + return 0; + + dev_dbg(dev, "set rm to %d\n", rm); + regmap_write(opp_info->grf, RK3588_MEMCFG_HSSPRF_LOW, 0x001c0000 | (rm << 2)); + regmap_write(opp_info->grf, RK3588_MEMCFG_HSDPRF_LOW, 0x003c0000 | (rm << 2)); + regmap_write(opp_info->grf, RK3588_MEMCFG_HSDPRF_HIGH, 0x003c0000 | (rm << 2)); + regmap_write(opp_info->grf, RK3588_CPU_CTRL, 0x00200020); + udelay(1); + regmap_write(opp_info->grf, RK3588_CPU_CTRL, 0x00200000); + + opp_info->current_rm = rm; + + return 0; +} + +static const struct rockchip_opp_data rk3588_cpu_opp_data = { + .set_read_margin = rk3588_cpu_set_read_margin, +}; + +static const struct of_device_id rockchip_cpufreq_of_match[] = { + { + .compatible = "rockchip,rk3588", + .data = (void *)&rk3588_cpu_opp_data, + }, + {}, +}; + +static struct cluster_info *rockchip_cluster_info_lookup(int cpu) +{ + struct cluster_info *cluster; + + list_for_each_entry(cluster, &cluster_info_list, list_head) { + if (cpumask_test_cpu(cpu, &cluster->cpus)) + return cluster; + } + + return NULL; +} + +static int rockchip_cpufreq_set_volt(struct device *dev, + struct regulator *reg, + struct dev_pm_opp_supply *supply) +{ + int ret; + + ret = regulator_set_voltage_triplet(reg, supply->u_volt_min, + supply->u_volt, supply->u_volt_max); + if (ret) + dev_err(dev, "%s: failed to set voltage (%lu %lu %lu uV): %d\n", + __func__, supply->u_volt_min, supply->u_volt, + supply->u_volt_max, ret); + + return ret; +} + +static int rockchip_cpufreq_set_read_margin(struct device *dev, + struct rockchip_opp_info *opp_info, + unsigned long volt) +{ + if (opp_info->data && opp_info->data->set_read_margin) { + opp_info->data->set_read_margin(dev, opp_info, volt); + } + + return 0; +} + +static int rk_opp_config_regulators(struct device *dev, + struct dev_pm_opp *old_opp, struct dev_pm_opp *new_opp, + struct regulator **regulators, unsigned int count) +{ + struct dev_pm_opp_supply old_supplies[2]; + struct dev_pm_opp_supply new_supplies[2]; + struct regulator *vdd_reg = regulators[0]; + struct regulator *mem_reg = regulators[1]; + struct rockchip_opp_info *opp_info; + struct cluster_info *cluster; + int ret = 0; + unsigned long old_freq = dev_pm_opp_get_freq(old_opp); + unsigned long new_freq = dev_pm_opp_get_freq(new_opp); + + /* We must have two regulators here */ + WARN_ON(count != 2); + + ret = dev_pm_opp_get_supplies(old_opp, old_supplies); + if (ret) + return ret; + + ret = dev_pm_opp_get_supplies(new_opp, new_supplies); + if (ret) + return ret; + + cluster = rockchip_cluster_info_lookup(dev->id); + if (!cluster) + return -EINVAL; + opp_info = &cluster->opp_info; + + if (new_freq >= old_freq) { + ret = rockchip_cpufreq_set_volt(dev, mem_reg, &new_supplies[1]); + if (ret) + goto error; + ret = rockchip_cpufreq_set_volt(dev, vdd_reg, &new_supplies[0]); + if (ret) + goto error; + rockchip_cpufreq_set_read_margin(dev, opp_info, new_supplies[0].u_volt); + } else { + rockchip_cpufreq_set_read_margin(dev, opp_info, new_supplies[0].u_volt); + ret = rockchip_cpufreq_set_volt(dev, vdd_reg, &new_supplies[0]); + if (ret) + goto error; + ret = rockchip_cpufreq_set_volt(dev, mem_reg, &new_supplies[1]); + if (ret) + goto error; + } + + return 0; + +error: + rockchip_cpufreq_set_read_margin(dev, opp_info, old_supplies[0].u_volt); + rockchip_cpufreq_set_volt(dev, mem_reg, &old_supplies[1]); + rockchip_cpufreq_set_volt(dev, vdd_reg, &old_supplies[0]); + return ret; +} + +static void rockchip_get_opp_data(const struct of_device_id *matches, + struct rockchip_opp_info *info) +{ + const struct of_device_id *match; + struct device_node *node; + + node = of_find_node_by_path("/"); + match = of_match_node(matches, node); + if (match && match->data) + info->data = match->data; + of_node_put(node); +} + +static int rockchip_get_volt_rm_table(struct device *dev, struct device_node *np, + char *porp_name, struct volt_rm_table **table) +{ + struct volt_rm_table *rm_table; + const struct property *prop; + int count, i; + + prop = of_find_property(np, porp_name, NULL); + if (!prop) + return -EINVAL; + + if (!prop->value) + return -ENODATA; + + count = of_property_count_u32_elems(np, porp_name); + if (count < 0) + return -EINVAL; + + if (count % 2) + return -EINVAL; + + rm_table = devm_kzalloc(dev, sizeof(*rm_table) * (count / 2 + 1), + GFP_KERNEL); + if (!rm_table) + return -ENOMEM; + + for (i = 0; i < count / 2; i++) { + of_property_read_u32_index(np, porp_name, 2 * i, + &rm_table[i].volt); + of_property_read_u32_index(np, porp_name, 2 * i + 1, + &rm_table[i].rm); + } + + rm_table[i].volt = 0; + rm_table[i].rm = VOLT_RM_TABLE_END; + + *table = rm_table; + + return 0; +} + +static int rockchip_cpufreq_reboot(struct notifier_block *notifier, unsigned long event, void *cmd) +{ + struct cluster_info *cluster; + struct device *dev; + int freq, ret, cpu; + + if (event != SYS_RESTART) + return NOTIFY_DONE; + + for_each_possible_cpu(cpu) { + cluster = rockchip_cluster_info_lookup(cpu); + if (!cluster) + continue; + + dev = get_cpu_device(cpu); + if (!dev) + continue; + + freq = cluster->opp_info.reboot_freq; + + if (freq) { + ret = dev_pm_opp_set_rate(dev, freq); + if (ret) + dev_err(dev, "Failed setting reboot freq for cpu %d to %d: %d\n", + cpu, freq, ret); + dev_pm_opp_remove_table(dev); + } + } + + return NOTIFY_DONE; +} + +static int rockchip_cpufreq_cluster_init(int cpu, struct cluster_info *cluster) +{ + struct rockchip_opp_info *opp_info = &cluster->opp_info; + int reg_table_token = -EINVAL; + int opp_table_token = -EINVAL; + struct device_node *np; + struct device *dev; + const char * const reg_names[] = { "cpu", "mem", NULL }; + int ret = 0; + + dev = get_cpu_device(cpu); + if (!dev) + return -ENODEV; + + if (!of_find_property(dev->of_node, "cpu-supply", NULL)) + return -ENOENT; + + np = of_parse_phandle(dev->of_node, "operating-points-v2", 0); + if (!np) { + dev_warn(dev, "OPP-v2 not supported\n"); + return -ENOENT; + } + + reg_table_token = dev_pm_opp_set_regulators(dev, reg_names); + if (reg_table_token < 0) { + ret = reg_table_token; + dev_err_probe(dev, ret, "Failed to set opp regulators\n"); + goto np_err; + } + + ret = dev_pm_opp_of_get_sharing_cpus(dev, &cluster->cpus); + if (ret) { + dev_err_probe(dev, ret, "Failed to get sharing cpus\n"); + goto np_err; + } + + rockchip_get_opp_data(rockchip_cpufreq_of_match, opp_info); + if (opp_info->data && opp_info->data->set_read_margin) { + opp_info->current_rm = UINT_MAX; + opp_info->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); + if (IS_ERR(opp_info->grf)) + opp_info->grf = NULL; + rockchip_get_volt_rm_table(dev, np, "rockchip,volt-mem-read-margin", &opp_info->volt_rm_tbl); + + of_property_read_u32(np, "rockchip,reboot-freq", &opp_info->reboot_freq); + } + + opp_table_token = dev_pm_opp_set_config_regulators(dev, rk_opp_config_regulators); + if (opp_table_token < 0) { + ret = opp_table_token; + dev_err(dev, "Failed to set opp config regulators\n"); + goto reg_opp_table; + } + + of_node_put(np); + + return 0; + +reg_opp_table: + if (reg_table_token >= 0) + dev_pm_opp_put_regulators(reg_table_token); +np_err: + of_node_put(np); + + return ret; +} + +static struct notifier_block rockchip_cpufreq_reboot_notifier = { + .notifier_call = rockchip_cpufreq_reboot, + .priority = 0, +}; + +static struct freq_attr *cpufreq_rockchip_attr[] = { + &cpufreq_freq_attr_scaling_available_freqs, + NULL, +}; + +static int cpufreq_online(struct cpufreq_policy *policy) +{ + /* We did light-weight tear down earlier, nothing to do here */ + return 0; +} + +static int cpufreq_offline(struct cpufreq_policy *policy) +{ + /* + * Preserve policy->driver_data and don't free resources on light-weight + * tear down. + */ + return 0; +} + +static struct private_data *rockchip_cpufreq_find_data(int cpu) +{ + struct private_data *priv; + + list_for_each_entry(priv, &priv_list, node) { + if (cpumask_test_cpu(cpu, priv->cpus)) + return priv; + } + + return NULL; +} + +static int cpufreq_init(struct cpufreq_policy *policy) +{ + struct private_data *priv; + struct device *cpu_dev; + struct clk *cpu_clk; + unsigned int transition_latency; + int ret; + + priv = rockchip_cpufreq_find_data(policy->cpu); + if (!priv) { + pr_err("failed to find data for cpu%d\n", policy->cpu); + return -ENODEV; + } + cpu_dev = priv->cpu_dev; + + cpu_clk = clk_get(cpu_dev, NULL); + if (IS_ERR(cpu_clk)) { + ret = PTR_ERR(cpu_clk); + dev_err(cpu_dev, "%s: failed to get clk: %d\n", __func__, ret); + return ret; + } + + transition_latency = dev_pm_opp_get_max_transition_latency(cpu_dev); + if (!transition_latency) + transition_latency = CPUFREQ_ETERNAL; + + cpumask_copy(policy->cpus, priv->cpus); + policy->driver_data = priv; + policy->clk = cpu_clk; + policy->freq_table = priv->freq_table; + policy->suspend_freq = dev_pm_opp_get_suspend_opp_freq(cpu_dev) / 1000; + policy->cpuinfo.transition_latency = transition_latency; + policy->dvfs_possible_from_any_cpu = true; + + return 0; +} + +static int cpufreq_exit(struct cpufreq_policy *policy) +{ + clk_put(policy->clk); + return 0; +} + +static int set_target(struct cpufreq_policy *policy, unsigned int index) +{ + struct private_data *priv = policy->driver_data; + unsigned long freq = policy->freq_table[index].frequency; + + return dev_pm_opp_set_rate(priv->cpu_dev, freq * 1000); +} + +static struct cpufreq_driver rockchip_cpufreq_driver = { + .flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK | + CPUFREQ_IS_COOLING_DEV | + CPUFREQ_HAVE_GOVERNOR_PER_POLICY, + .verify = cpufreq_generic_frequency_table_verify, + .target_index = set_target, + .get = cpufreq_generic_get, + .init = cpufreq_init, + .exit = cpufreq_exit, + .online = cpufreq_online, + .offline = cpufreq_offline, + .register_em = cpufreq_register_em_with_opp, + .name = "rockchip-cpufreq", + .attr = cpufreq_rockchip_attr, + .suspend = cpufreq_generic_suspend, +}; + +static int rockchip_cpufreq_init(struct device *dev, int cpu) +{ + struct private_data *priv; + struct device *cpu_dev; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + if (!alloc_cpumask_var(&priv->cpus, GFP_KERNEL)) + return -ENOMEM; + + cpumask_set_cpu(cpu, priv->cpus); + + cpu_dev = get_cpu_device(cpu); + if (!cpu_dev) + return -EPROBE_DEFER; + priv->cpu_dev = cpu_dev; + + ret = dev_pm_opp_of_get_sharing_cpus(cpu_dev, priv->cpus); + if (ret) + return ret; + + ret = dev_pm_opp_of_cpumask_add_table(priv->cpus); + if (ret) + return ret; + + ret = dev_pm_opp_get_opp_count(cpu_dev); + if (ret <= 0) + return dev_err_probe(cpu_dev, -ENODEV, "OPP table can't be empty\n"); + + ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &priv->freq_table); + if (ret) + return dev_err_probe(cpu_dev, ret, "failed to init cpufreq table\n"); + + list_add(&priv->node, &priv_list); + + return 0; +} + +static void rockchip_cpufreq_free_list(void *data) +{ + struct cluster_info *cluster, *pos; + + list_for_each_entry_safe(cluster, pos, &cluster_info_list, list_head) { + list_del(&cluster->list_head); + } +} + +static int rockchip_cpufreq_init_list(struct device *dev) +{ + struct cluster_info *cluster; + int cpu, ret; + + for_each_possible_cpu(cpu) { + cluster = rockchip_cluster_info_lookup(cpu); + if (cluster) + continue; + + cluster = devm_kzalloc(dev, sizeof(*cluster), GFP_KERNEL); + if (!cluster) { + ret = -ENOMEM; + goto release_cluster_info; + } + + ret = rockchip_cpufreq_cluster_init(cpu, cluster); + if (ret) { + dev_err_probe(dev, ret, "Failed to initialize dvfs info cpu%d\n", cpu); + goto release_cluster_info; + } + list_add(&cluster->list_head, &cluster_info_list); + } + + return 0; + +release_cluster_info: + rockchip_cpufreq_free_list(NULL); + return ret; +} + +static void rockchip_cpufreq_unregister(void *data) +{ + cpufreq_unregister_driver(&rockchip_cpufreq_driver); +} + +static int rockchip_cpufreq_probe(struct platform_device *pdev) +{ + int ret, cpu; + + ret = rockchip_cpufreq_init_list(&pdev->dev); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&pdev->dev, rockchip_cpufreq_free_list, NULL); + if (ret) + return ret; + + ret = devm_register_reboot_notifier(&pdev->dev, &rockchip_cpufreq_reboot_notifier); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed to register reboot handler\n"); + + for_each_possible_cpu(cpu) { + ret = rockchip_cpufreq_init(&pdev->dev, cpu); + if (ret) + return ret; + } + + ret = cpufreq_register_driver(&rockchip_cpufreq_driver); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed register driver\n"); + + ret = devm_add_action_or_reset(&pdev->dev, rockchip_cpufreq_unregister, NULL); + if (ret) + return ret; + + return 0; +} + +static struct platform_driver rockchip_cpufreq_platdrv = { + .driver = { + .name = "rockchip-cpufreq", + }, + .probe = rockchip_cpufreq_probe, +}; + +static int __init rockchip_cpufreq_driver_init(void) +{ + int ret; + + if (!of_machine_is_compatible("rockchip,rk3588") && + !of_machine_is_compatible("rockchip,rk3588s")) { + return -ENODEV; + } + + ret = platform_driver_register(&rockchip_cpufreq_platdrv); + if (ret) + return ret; + + cpufreq_pdev = platform_device_register_data(NULL, "rockchip-cpufreq", -1, + NULL, 0); + if (IS_ERR(cpufreq_pdev)) { + pr_err("failed to register rockchip-cpufreq platform device\n"); + ret = PTR_ERR(cpufreq_pdev); + goto unregister_platform_driver; + } + + return 0; + +unregister_platform_driver: + platform_driver_unregister(&rockchip_cpufreq_platdrv); + return ret; +} +module_init(rockchip_cpufreq_driver_init); + +static void __exit rockchip_cpufreq_driver_exit(void) +{ + platform_device_unregister(cpufreq_pdev); + platform_driver_unregister(&rockchip_cpufreq_platdrv); +} +module_exit(rockchip_cpufreq_driver_exit) + +MODULE_AUTHOR("Finley Xiao "); +MODULE_DESCRIPTION("Rockchip cpufreq driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/bridge/synopsys/Makefile b/drivers/gpu/drm/bridge/synopsys/Makefile index ce715562e..8354e4879 100644 --- a/drivers/gpu/drm/bridge/synopsys/Makefile +++ b/drivers/gpu/drm/bridge/synopsys/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only -obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o +obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o dw-hdmi-qp.o obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o obj-$(CONFIG_DRM_DW_HDMI_GP_AUDIO) += dw-hdmi-gp-audio.o obj-$(CONFIG_DRM_DW_HDMI_I2S_AUDIO) += dw-hdmi-i2s-audio.o diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c new file mode 100644 index 000000000..935b116c2 --- /dev/null +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c @@ -0,0 +1,2997 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) Rockchip Electronics Co.Ltd + * Author: + * Algea Cao + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "dw-hdmi-qp.h" +// #include "dw-hdmi-qp-audio.h" +// #include "dw-hdmi-qp-cec.h" + +#include + +#define DDC_CI_ADDR 0x37 +#define DDC_SEGMENT_ADDR 0x30 + +#define HDMI_EDID_LEN 512 + +/* DW-HDMI Controller >= 0x200a are at least compliant with SCDC version 1 */ +#define SCDC_MIN_SOURCE_VERSION 0x1 + +#define HDMI14_MAX_TMDSCLK 340000000 +#define HDMI20_MAX_TMDSCLK_KHZ 600000 + +static const unsigned int dw_hdmi_cable[] = { + EXTCON_DISP_HDMI, + EXTCON_NONE, +}; + +static const struct drm_display_mode dw_hdmi_default_modes[] = { + /* 16 - 1920x1080@60Hz 16:9 */ + { DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2008, + 2052, 2200, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, }, + /* 2 - 720x480@60Hz 4:3 */ + { DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 27000, 720, 736, + 798, 858, 0, 480, 489, 495, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, }, + /* 4 - 1280x720@60Hz 16:9 */ + { DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1390, + 1430, 1650, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, }, + /* 31 - 1920x1080@50Hz 16:9 */ + { DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2448, + 2492, 2640, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, }, + /* 19 - 1280x720@50Hz 16:9 */ + { DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1720, + 1760, 1980, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, }, + /* 17 - 720x576@50Hz 4:3 */ + { DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 27000, 720, 732, + 796, 864, 0, 576, 581, 586, 625, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, }, + /* 2 - 720x480@60Hz 4:3 */ + { DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 27000, 720, 736, + 798, 858, 0, 480, 489, 495, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, }, +}; + +enum frl_mask { + FRL_3GBPS_3LANE = 1, + FRL_6GBPS_3LANE, + FRL_6GBPS_4LANE, + FRL_8GBPS_4LANE, + FRL_10GBPS_4LANE, + FRL_12GBPS_4LANE, +}; + +struct hdmi_vmode_qp { + bool mdataenablepolarity; + + unsigned int previous_pixelclock; + unsigned long mpixelclock; + unsigned int mpixelrepetitioninput; + unsigned int mpixelrepetitionoutput; + unsigned long previous_tmdsclock; + unsigned int mtmdsclock; +}; + +struct hdmi_qp_data_info { + unsigned int enc_in_bus_format; + unsigned int enc_out_bus_format; + unsigned int enc_in_encoding; + unsigned int enc_out_encoding; + unsigned int quant_range; + unsigned int pix_repet_factor; + struct hdmi_vmode_qp video_mode; + bool update; +}; + +struct dw_hdmi_qp_i2c { + struct i2c_adapter adap; + + struct mutex lock; /* used to serialize data transfers */ + struct completion cmp; + u32 stat; + + u8 slave_reg; + bool is_regaddr; + bool is_segment; + + unsigned int scl_high_ns; + unsigned int scl_low_ns; +}; + +// struct dw_hdmi_phy_data { +// enum dw_hdmi_phy_type type; +// const char *name; +// unsigned int gen; +// bool has_svsret; +// int (*configure)(struct dw_hdmi_qp *hdmi, +// const struct dw_hdmi_plat_data *pdata, +// unsigned long mpixelclock); +// }; + +struct dw_hdmi_qp { + struct drm_connector connector; + struct drm_bridge bridge; + struct platform_device *hdcp_dev; + struct platform_device *audio; + struct platform_device *cec; + struct device *dev; + struct dw_hdmi_qp_i2c *i2c; + + struct hdmi_qp_data_info hdmi_data; + const struct dw_hdmi_plat_data *plat_data; + + int vic; + int main_irq; + int avp_irq; + int earc_irq; + + u8 edid[HDMI_EDID_LEN]; + + struct { + const struct dw_hdmi_qp_phy_ops *ops; + const char *name; + void *data; + bool enabled; + } phy; + + struct drm_display_mode previous_mode; + + struct i2c_adapter *ddc; + void __iomem *regs; + bool sink_is_hdmi; + bool sink_has_audio; + bool dclk_en; + + struct mutex mutex; /* for state below and previous_mode */ + //[CC:] curr_conn should be removed + struct drm_connector *curr_conn;/* current connector (only valid when !disabled) */ + enum drm_connector_force force; /* mutex-protected force state */ + bool disabled; /* DRM has disabled our bridge */ + bool bridge_is_on; /* indicates the bridge is on */ + bool rxsense; /* rxsense state */ + u8 phy_mask; /* desired phy int mask settings */ + u8 mc_clkdis; /* clock disable register */ + + u32 scdc_intr; + u32 flt_intr; + u32 earc_intr; + + struct mutex audio_mutex; + unsigned int sample_rate; + unsigned int audio_cts; + unsigned int audio_n; + bool audio_enable; + void (*enable_audio)(struct dw_hdmi_qp *hdmi); + void (*disable_audio)(struct dw_hdmi_qp *hdmi); + + struct dentry *debugfs_dir; + bool scramble_low_rates; + + struct extcon_dev *extcon; + + struct regmap *regm; + + bool initialized; /* hdmi is enabled before bind */ + struct completion flt_cmp; + struct completion earc_cmp; + + struct cec_notifier *cec_notifier; + struct cec_adapter *cec_adap; + struct mutex cec_notifier_mutex; + + hdmi_codec_plugged_cb plugged_cb; + struct device *codec_dev; + enum drm_connector_status last_connector_result; +}; + +static inline void hdmi_writel(struct dw_hdmi_qp *hdmi, u32 val, int offset) +{ + regmap_write(hdmi->regm, offset, val); +} + +static inline u32 hdmi_readl(struct dw_hdmi_qp *hdmi, int offset) +{ + unsigned int val = 0; + + regmap_read(hdmi->regm, offset, &val); + + return val; +} + +static void handle_plugged_change(struct dw_hdmi_qp *hdmi, bool plugged) +{ + if (hdmi->plugged_cb && hdmi->codec_dev) + hdmi->plugged_cb(hdmi->codec_dev, plugged); +} + +int dw_hdmi_qp_set_plugged_cb(struct dw_hdmi_qp *hdmi, hdmi_codec_plugged_cb fn, + struct device *codec_dev) +{ + bool plugged; + + mutex_lock(&hdmi->mutex); + hdmi->plugged_cb = fn; + hdmi->codec_dev = codec_dev; + plugged = hdmi->last_connector_result == connector_status_connected; + handle_plugged_change(hdmi, plugged); + mutex_unlock(&hdmi->mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_plugged_cb); + +static void hdmi_modb(struct dw_hdmi_qp *hdmi, u32 data, u32 mask, u32 reg) +{ + regmap_update_bits(hdmi->regm, reg, mask, data); +} + +static void hdmi_set_cts_n(struct dw_hdmi_qp *hdmi, unsigned int cts, + unsigned int n) +{ + /* Set N */ + hdmi_modb(hdmi, n, AUDPKT_ACR_N_VALUE, AUDPKT_ACR_CONTROL0); + + /* Set CTS */ + if (cts) + hdmi_modb(hdmi, AUDPKT_ACR_CTS_OVR_EN, AUDPKT_ACR_CTS_OVR_EN_MSK, + AUDPKT_ACR_CONTROL1); + else + hdmi_modb(hdmi, 0, AUDPKT_ACR_CTS_OVR_EN_MSK, + AUDPKT_ACR_CONTROL1); + + hdmi_modb(hdmi, AUDPKT_ACR_CTS_OVR_VAL(cts), AUDPKT_ACR_CTS_OVR_VAL_MSK, + AUDPKT_ACR_CONTROL1); +} + +// static int hdmi_match_tmds_n_table(struct dw_hdmi_qp *hdmi, +// unsigned long pixel_clk, +// unsigned long freq) +// { +// const struct dw_hdmi_plat_data *plat_data = hdmi->plat_data; +// const struct dw_hdmi_audio_tmds_n *tmds_n = NULL; +// int i; +// +// if (plat_data->tmds_n_table) { +// for (i = 0; plat_data->tmds_n_table[i].tmds != 0; i++) { +// if (pixel_clk == plat_data->tmds_n_table[i].tmds) { +// tmds_n = &plat_data->tmds_n_table[i]; +// break; +// } +// } +// } +// +// if (tmds_n == NULL) { +// for (i = 0; common_tmds_n_table[i].tmds != 0; i++) { +// if (pixel_clk == common_tmds_n_table[i].tmds) { +// tmds_n = &common_tmds_n_table[i]; +// break; +// } +// } +// } +// +// if (tmds_n == NULL) +// return -ENOENT; +// +// switch (freq) { +// case 32000: +// return tmds_n->n_32k; +// case 44100: +// case 88200: +// case 176400: +// return (freq / 44100) * tmds_n->n_44k1; +// case 48000: +// case 96000: +// case 192000: +// return (freq / 48000) * tmds_n->n_48k; +// default: +// return -ENOENT; +// } +// } +// +static u64 hdmi_audio_math_diff(unsigned int freq, unsigned int n, + unsigned int pixel_clk) +{ + u64 final, diff; + u64 cts; + + final = (u64)pixel_clk * n; + + cts = final; + do_div(cts, 128 * freq); + + diff = final - (u64)cts * (128 * freq); + + return diff; +} + +static unsigned int hdmi_compute_n(struct dw_hdmi_qp *hdmi, + unsigned long pixel_clk, + unsigned long freq) +{ + unsigned int min_n = DIV_ROUND_UP((128 * freq), 1500); + unsigned int max_n = (128 * freq) / 300; + unsigned int ideal_n = (128 * freq) / 1000; + unsigned int best_n_distance = ideal_n; + unsigned int best_n = 0; + u64 best_diff = U64_MAX; + int n; + + /* If the ideal N could satisfy the audio math, then just take it */ + if (hdmi_audio_math_diff(freq, ideal_n, pixel_clk) == 0) + return ideal_n; + + for (n = min_n; n <= max_n; n++) { + u64 diff = hdmi_audio_math_diff(freq, n, pixel_clk); + + if (diff < best_diff || (diff == best_diff && + abs(n - ideal_n) < best_n_distance)) { + best_n = n; + best_diff = diff; + best_n_distance = abs(best_n - ideal_n); + } + + /* + * The best N already satisfy the audio math, and also be + * the closest value to ideal N, so just cut the loop. + */ + if ((best_diff == 0) && (abs(n - ideal_n) > best_n_distance)) + break; + } + + return best_n; +} + +static unsigned int hdmi_find_n(struct dw_hdmi_qp *hdmi, unsigned long pixel_clk, + unsigned long sample_rate) +{ + int n; + + // n = hdmi_match_tmds_n_table(hdmi, pixel_clk, sample_rate); + n = 0; + if (n > 0) + return n; + + dev_warn(hdmi->dev, "Rate %lu missing; compute N dynamically\n", + pixel_clk); + + return hdmi_compute_n(hdmi, pixel_clk, sample_rate); +} + +/* + * When transmitting IEC60958 linear PCM audio, these registers allow to + * configure the channel status information of all the channel status + * bits in the IEC60958 frame. For the moment this configuration is only + * used when the I2S audio interface, General Purpose Audio (GPA), + * or AHB audio DMA (AHBAUDDMA) interface is active + * (for S/PDIF interface this information comes from the stream). + */ +void dw_hdmi_qp_set_channel_status(struct dw_hdmi_qp *hdmi, + u8 *channel_status, bool ref2stream) +{ + mutex_lock(&hdmi->audio_mutex); + if (!hdmi->dclk_en) { + mutex_unlock(&hdmi->audio_mutex); + return; + } + + /* Set channel status */ + hdmi_writel(hdmi, channel_status[3] | (channel_status[4] << 8), + AUDPKT_CHSTATUS_OVR1); + + if (ref2stream) + hdmi_modb(hdmi, 0, + AUDPKT_PBIT_FORCE_EN_MASK | AUDPKT_CHSTATUS_OVR_EN_MASK, + AUDPKT_CONTROL0); + else + hdmi_modb(hdmi, AUDPKT_PBIT_FORCE_EN | AUDPKT_CHSTATUS_OVR_EN, + AUDPKT_PBIT_FORCE_EN_MASK | AUDPKT_CHSTATUS_OVR_EN_MASK, + AUDPKT_CONTROL0); + + mutex_unlock(&hdmi->audio_mutex); +} +EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_channel_status); + +static void hdmi_set_clk_regenerator(struct dw_hdmi_qp *hdmi, + unsigned long pixel_clk, unsigned int sample_rate) +{ + unsigned int n = 0, cts = 0; + + n = hdmi_find_n(hdmi, pixel_clk, sample_rate); + + hdmi->audio_n = n; + hdmi->audio_cts = cts; + hdmi_set_cts_n(hdmi, cts, hdmi->audio_enable ? n : 0); +} + +static void hdmi_init_clk_regenerator(struct dw_hdmi_qp *hdmi) +{ + mutex_lock(&hdmi->audio_mutex); + if (hdmi->dclk_en) + hdmi_set_clk_regenerator(hdmi, 74250000, hdmi->sample_rate); + mutex_unlock(&hdmi->audio_mutex); +} + +static void hdmi_clk_regenerator_update_pixel_clock(struct dw_hdmi_qp *hdmi) +{ + mutex_lock(&hdmi->audio_mutex); + if (hdmi->dclk_en) + hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mtmdsclock, + hdmi->sample_rate); + mutex_unlock(&hdmi->audio_mutex); +} + +void dw_hdmi_qp_set_sample_rate(struct dw_hdmi_qp *hdmi, unsigned int rate) +{ + mutex_lock(&hdmi->audio_mutex); + if (hdmi->dclk_en) { + hdmi->sample_rate = rate; + hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mtmdsclock, + hdmi->sample_rate); + } + mutex_unlock(&hdmi->audio_mutex); +} +EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_sample_rate); + +void dw_hdmi_qp_set_channel_count(struct dw_hdmi_qp *hdmi, unsigned int cnt) +{ +} +EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_channel_count); + +void dw_hdmi_qp_set_channel_allocation(struct dw_hdmi_qp *hdmi, unsigned int ca) +{ +} +EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_channel_allocation); + +void dw_hdmi_qp_set_audio_infoframe(struct dw_hdmi_qp *hdmi, + struct hdmi_codec_params *hparms) +{ + u8 infoframe_buf[HDMI_INFOFRAME_SIZE(AUDIO)]; + int ret = 0; + + ret = hdmi_audio_infoframe_pack(&hparms->cea, infoframe_buf, + sizeof(infoframe_buf)); + if (!ret) { + dev_err(hdmi->dev, "%s: Failed to pack audio infoframe: %d\n", + __func__, ret); + return; + } + + mutex_lock(&hdmi->audio_mutex); + if (!hdmi->dclk_en) { + mutex_unlock(&hdmi->audio_mutex); + return; + } + + /* + * AUDI_CONTENTS0: { RSV, HB2, HB1, RSV } + * AUDI_CONTENTS1: { PB3, PB2, PB1, PB0 } + * AUDI_CONTENTS2: { PB7, PB6, PB5, PB4 } + * + * PB0: CheckSum + * PB1: | CT3 | CT2 | CT1 | CT0 | F13 | CC2 | CC1 | CC0 | + * PB2: | F27 | F26 | F25 | SF2 | SF1 | SF0 | SS1 | SS0 | + * PB3: | F37 | F36 | F35 | F34 | F33 | F32 | F31 | F30 | + * PB4: | CA7 | CA6 | CA5 | CA4 | CA3 | CA2 | CA1 | CA0 | + * PB5: | DM_INH | LSV3 | LSV2 | LSV1 | LSV0 | F52 | F51 | F50 | + * PB6~PB10: Reserved + * + * AUDI_CONTENTS0 default value defined by HDMI specification, + * and shall only be changed for debug purposes. + * So, we only configure payload byte from PB0~PB7(2 word total). + */ + regmap_bulk_write(hdmi->regm, PKT_AUDI_CONTENTS1, &infoframe_buf[3], 2); + + /* Enable ACR, AUDI, AMD */ + hdmi_modb(hdmi, + PKTSCHED_ACR_TX_EN | PKTSCHED_AUDI_TX_EN | PKTSCHED_AMD_TX_EN, + PKTSCHED_ACR_TX_EN | PKTSCHED_AUDI_TX_EN | PKTSCHED_AMD_TX_EN, + PKTSCHED_PKT_EN); + + /* Enable AUDS */ + hdmi_modb(hdmi, PKTSCHED_AUDS_TX_EN, PKTSCHED_AUDS_TX_EN, PKTSCHED_PKT_EN); + mutex_unlock(&hdmi->audio_mutex); +} +EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_audio_infoframe); + +static void hdmi_enable_audio_clk(struct dw_hdmi_qp *hdmi, bool enable) +{ + if (enable) + hdmi_modb(hdmi, 0, + AVP_DATAPATH_PACKET_AUDIO_SWDISABLE, GLOBAL_SWDISABLE); + else + hdmi_modb(hdmi, AVP_DATAPATH_PACKET_AUDIO_SWDISABLE, + AVP_DATAPATH_PACKET_AUDIO_SWDISABLE, GLOBAL_SWDISABLE); +} + +// static void dw_hdmi_i2s_audio_enable(struct dw_hdmi_qp *hdmi) +// { +// hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n); +// hdmi_enable_audio_clk(hdmi, true); +// } +// +// static void dw_hdmi_i2s_audio_disable(struct dw_hdmi_qp *hdmi) +// { +// /* Disable AUDS, ACR, AUDI, AMD */ +// hdmi_modb(hdmi, 0, +// PKTSCHED_ACR_TX_EN | PKTSCHED_AUDS_TX_EN | +// PKTSCHED_AUDI_TX_EN | PKTSCHED_AMD_TX_EN, +// PKTSCHED_PKT_EN); +// +// hdmi_enable_audio_clk(hdmi, false); +// } + +void dw_hdmi_qp_audio_enable(struct dw_hdmi_qp *hdmi) +{ + mutex_lock(&hdmi->audio_mutex); + if (hdmi->dclk_en) { + hdmi->audio_enable = true; + if (hdmi->enable_audio) + hdmi->enable_audio(hdmi); + } + mutex_unlock(&hdmi->audio_mutex); +} +EXPORT_SYMBOL_GPL(dw_hdmi_qp_audio_enable); + +void dw_hdmi_qp_audio_disable(struct dw_hdmi_qp *hdmi) +{ + mutex_lock(&hdmi->audio_mutex); + if (hdmi->dclk_en) { + hdmi->audio_enable = false; + if (hdmi->disable_audio) + hdmi->disable_audio(hdmi); + } + mutex_unlock(&hdmi->audio_mutex); +} +EXPORT_SYMBOL_GPL(dw_hdmi_qp_audio_disable); + +static bool hdmi_bus_fmt_is_rgb(unsigned int bus_format) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_RGB888_1X24: + case MEDIA_BUS_FMT_RGB101010_1X30: + case MEDIA_BUS_FMT_RGB121212_1X36: + case MEDIA_BUS_FMT_RGB161616_1X48: + return true; + + default: + return false; + } +} + +static bool hdmi_bus_fmt_is_yuv444(unsigned int bus_format) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_YUV8_1X24: + case MEDIA_BUS_FMT_YUV10_1X30: + case MEDIA_BUS_FMT_YUV12_1X36: + case MEDIA_BUS_FMT_YUV16_1X48: + return true; + + default: + return false; + } +} + +static bool hdmi_bus_fmt_is_yuv422(unsigned int bus_format) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_UYVY10_1X20: + case MEDIA_BUS_FMT_UYVY12_1X24: + return true; + + default: + return false; + } +} + +static bool hdmi_bus_fmt_is_yuv420(unsigned int bus_format) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: + case MEDIA_BUS_FMT_UYYVYY12_0_5X36: + case MEDIA_BUS_FMT_UYYVYY16_0_5X48: + return true; + + default: + return false; + } +} + +static int hdmi_bus_fmt_color_depth(unsigned int bus_format) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_RGB888_1X24: + case MEDIA_BUS_FMT_YUV8_1X24: + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + return 8; + + case MEDIA_BUS_FMT_RGB101010_1X30: + case MEDIA_BUS_FMT_YUV10_1X30: + case MEDIA_BUS_FMT_UYVY10_1X20: + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: + return 10; + + case MEDIA_BUS_FMT_RGB121212_1X36: + case MEDIA_BUS_FMT_YUV12_1X36: + case MEDIA_BUS_FMT_UYVY12_1X24: + case MEDIA_BUS_FMT_UYYVYY12_0_5X36: + return 12; + + case MEDIA_BUS_FMT_RGB161616_1X48: + case MEDIA_BUS_FMT_YUV16_1X48: + case MEDIA_BUS_FMT_UYYVYY16_0_5X48: + return 16; + + default: + return 0; + } +} + +static void dw_hdmi_i2c_init(struct dw_hdmi_qp *hdmi) +{ + /* Software reset */ + hdmi_writel(hdmi, 0x01, I2CM_CONTROL0); + + hdmi_writel(hdmi, 0x085c085c, I2CM_FM_SCL_CONFIG0); + + hdmi_modb(hdmi, 0, I2CM_FM_EN, I2CM_INTERFACE_CONTROL0); + + /* Clear DONE and ERROR interrupts */ + hdmi_writel(hdmi, I2CM_OP_DONE_CLEAR | I2CM_NACK_RCVD_CLEAR, + MAINUNIT_1_INT_CLEAR); +} + +static int dw_hdmi_i2c_read(struct dw_hdmi_qp *hdmi, + unsigned char *buf, unsigned int length) +{ + struct dw_hdmi_qp_i2c *i2c = hdmi->i2c; + int stat; + + if (!i2c->is_regaddr) { + dev_dbg(hdmi->dev, "set read register address to 0\n"); + i2c->slave_reg = 0x00; + i2c->is_regaddr = true; + } + + while (length--) { + reinit_completion(&i2c->cmp); + + hdmi_modb(hdmi, i2c->slave_reg++ << 12, I2CM_ADDR, + I2CM_INTERFACE_CONTROL0); + + hdmi_modb(hdmi, I2CM_FM_READ, I2CM_WR_MASK, + I2CM_INTERFACE_CONTROL0); + + stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10); + if (!stat) { + dev_err(hdmi->dev, "i2c read time out!\n"); + hdmi_writel(hdmi, 0x01, I2CM_CONTROL0); + return -EAGAIN; + } + + /* Check for error condition on the bus */ + if (i2c->stat & I2CM_NACK_RCVD_IRQ) { + dev_err(hdmi->dev, "i2c read err!\n"); + hdmi_writel(hdmi, 0x01, I2CM_CONTROL0); + return -EIO; + } + + *buf++ = hdmi_readl(hdmi, I2CM_INTERFACE_RDDATA_0_3) & 0xff; + // dev_dbg(hdmi->dev, "i2c read done! i2c->stat:%02x 0x%02x\n", + // i2c->stat, hdmi_readl(hdmi, I2CM_INTERFACE_RDDATA_0_3)); + hdmi_modb(hdmi, 0, I2CM_WR_MASK, I2CM_INTERFACE_CONTROL0); + } + i2c->is_segment = false; + + return 0; +} + +static int dw_hdmi_i2c_write(struct dw_hdmi_qp *hdmi, + unsigned char *buf, unsigned int length) +{ + struct dw_hdmi_qp_i2c *i2c = hdmi->i2c; + int stat; + + if (!i2c->is_regaddr) { + /* Use the first write byte as register address */ + i2c->slave_reg = buf[0]; + length--; + buf++; + i2c->is_regaddr = true; + } + + while (length--) { + reinit_completion(&i2c->cmp); + + hdmi_writel(hdmi, *buf++, I2CM_INTERFACE_WRDATA_0_3); + hdmi_modb(hdmi, i2c->slave_reg++ << 12, I2CM_ADDR, + I2CM_INTERFACE_CONTROL0); + hdmi_modb(hdmi, I2CM_FM_WRITE, I2CM_WR_MASK, + I2CM_INTERFACE_CONTROL0); + + stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10); + if (!stat) { + dev_err(hdmi->dev, "i2c write time out!\n"); + hdmi_writel(hdmi, 0x01, I2CM_CONTROL0); + return -EAGAIN; + } + + /* Check for error condition on the bus */ + if (i2c->stat & I2CM_NACK_RCVD_IRQ) { + dev_err(hdmi->dev, "i2c write nack!\n"); + hdmi_writel(hdmi, 0x01, I2CM_CONTROL0); + return -EIO; + } + hdmi_modb(hdmi, 0, I2CM_WR_MASK, I2CM_INTERFACE_CONTROL0); + } + // dev_dbg(hdmi->dev, "i2c write done!\n"); + return 0; +} + +static int dw_hdmi_i2c_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num) +{ + struct dw_hdmi_qp *hdmi = i2c_get_adapdata(adap); + struct dw_hdmi_qp_i2c *i2c = hdmi->i2c; + u8 addr = msgs[0].addr; + int i, ret = 0; + + if (addr == DDC_CI_ADDR) + /* + * The internal I2C controller does not support the multi-byte + * read and write operations needed for DDC/CI. + * TOFIX: Blacklist the DDC/CI address until we filter out + * unsupported I2C operations. + */ + return -EOPNOTSUPP; + + // dev_dbg(hdmi->dev, "i2c xfer: num: %d, addr: %#x\n", num, addr); + + for (i = 0; i < num; i++) { + if (msgs[i].len == 0) { + dev_err(hdmi->dev, + "unsupported transfer %d/%d, no data\n", + i + 1, num); + return -EOPNOTSUPP; + } + } + + mutex_lock(&i2c->lock); + + /* Unmute DONE and ERROR interrupts */ + hdmi_modb(hdmi, I2CM_NACK_RCVD_MASK_N | I2CM_OP_DONE_MASK_N, + I2CM_NACK_RCVD_MASK_N | I2CM_OP_DONE_MASK_N, + MAINUNIT_1_INT_MASK_N); + + /* Set slave device address taken from the first I2C message */ + if (addr == DDC_SEGMENT_ADDR && msgs[0].len == 1) + addr = DDC_ADDR; + + hdmi_modb(hdmi, addr << 5, I2CM_SLVADDR, I2CM_INTERFACE_CONTROL0); + + /* Set slave device register address on transfer */ + i2c->is_regaddr = false; + + /* Set segment pointer for I2C extended read mode operation */ + i2c->is_segment = false; + + for (i = 0; i < num; i++) { + // dev_dbg(hdmi->dev, "xfer: num: %d/%d, len: %d, flags: %#x\n", + // i + 1, num, msgs[i].len, msgs[i].flags); + + if (msgs[i].addr == DDC_SEGMENT_ADDR && msgs[i].len == 1) { + i2c->is_segment = true; + hdmi_modb(hdmi, DDC_SEGMENT_ADDR, I2CM_SEG_ADDR, + I2CM_INTERFACE_CONTROL1); + hdmi_modb(hdmi, *msgs[i].buf, I2CM_SEG_PTR, + I2CM_INTERFACE_CONTROL1); + } else { + if (msgs[i].flags & I2C_M_RD) + ret = dw_hdmi_i2c_read(hdmi, msgs[i].buf, + msgs[i].len); + else + ret = dw_hdmi_i2c_write(hdmi, msgs[i].buf, + msgs[i].len); + } + if (ret < 0) + break; + } + + if (!ret) + ret = num; + + /* Mute DONE and ERROR interrupts */ + hdmi_modb(hdmi, 0, I2CM_OP_DONE_MASK_N | I2CM_NACK_RCVD_MASK_N, + MAINUNIT_1_INT_MASK_N); + + mutex_unlock(&i2c->lock); + + return ret; +} + +static u32 dw_hdmi_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm dw_hdmi_algorithm = { + .master_xfer = dw_hdmi_i2c_xfer, + .functionality = dw_hdmi_i2c_func, +}; + +static struct i2c_adapter *dw_hdmi_i2c_adapter(struct dw_hdmi_qp *hdmi) +{ + struct i2c_adapter *adap; + struct dw_hdmi_qp_i2c *i2c; + int ret; + + i2c = devm_kzalloc(hdmi->dev, sizeof(*i2c), GFP_KERNEL); + if (!i2c) + return ERR_PTR(-ENOMEM); + + mutex_init(&i2c->lock); + init_completion(&i2c->cmp); + + adap = &i2c->adap; + adap->class = I2C_CLASS_DDC; + adap->owner = THIS_MODULE; + adap->dev.parent = hdmi->dev; + adap->algo = &dw_hdmi_algorithm; + strscpy(adap->name, "ddc", sizeof(adap->name)); + i2c_set_adapdata(adap, hdmi); + + ret = i2c_add_adapter(adap); + if (ret) { + dev_warn(hdmi->dev, "cannot add %s I2C adapter\n", adap->name); + devm_kfree(hdmi->dev, i2c); + return ERR_PTR(ret); + } + + hdmi->i2c = i2c; + + dev_info(hdmi->dev, "registered %s I2C bus driver\n", adap->name); + + return adap; +} + +#define HDMI_PHY_EARC_MASK BIT(29) + +int dw_hdmi_qp_set_earc(struct dw_hdmi_qp *hdmi) +{ + u32 stat, ret; + + /* set hdmi phy earc mode */ + hdmi->phy.ops->set_mode(hdmi, hdmi->phy.data, HDMI_PHY_EARC_MASK, + true); + + ret = hdmi->phy.ops->init(hdmi, hdmi->phy.data, + &hdmi->previous_mode); + if (ret) + return ret; + + reinit_completion(&hdmi->earc_cmp); + + hdmi_modb(hdmi, EARCRX_CMDC_DISCOVERY_TIMEOUT_IRQ | + EARCRX_CMDC_DISCOVERY_DONE_IRQ, + EARCRX_CMDC_DISCOVERY_TIMEOUT_IRQ | + EARCRX_CMDC_DISCOVERY_DONE_IRQ, EARCRX_0_INT_MASK_N); + + /* start discovery */ + hdmi_modb(hdmi, EARCRX_CMDC_DISCOVERY_EN, EARCRX_CMDC_DISCOVERY_EN, + EARCRX_CMDC_CONTROL); + + /* + * The eARC TX device drives a logic-high-voltage-level + * pulse on the physical HPD connector pin, after + * at least 100 ms of low voltage level to start the + * eARC Discovery process. + */ + hdmi_modb(hdmi, EARCRX_CONNECTOR_HPD, EARCRX_CONNECTOR_HPD, + EARCRX_CMDC_CONTROL); + + stat = wait_for_completion_timeout(&hdmi->earc_cmp, HZ / 10); + if (!stat) + return -EAGAIN; + + if (hdmi->earc_intr & EARCRX_CMDC_DISCOVERY_TIMEOUT_IRQ) { + dev_err(hdmi->dev, "discovery timeout\n"); + return -ETIMEDOUT; + } else if (hdmi->earc_intr & EARCRX_CMDC_DISCOVERY_DONE_IRQ) { + dev_info(hdmi->dev, "discovery done\n"); + } else { + dev_err(hdmi->dev, "discovery failed\n"); + return -EINVAL; + } + + hdmi_writel(hdmi, 1, EARCRX_DMAC_PHY_CONTROL); + hdmi_modb(hdmi, EARCRX_CMDC_SWINIT_P, EARCRX_CMDC_SWINIT_P, + EARCRX_CMDC_CONFIG0); + + hdmi_writel(hdmi, 0xf3, EARCRX_DMAC_CONFIG); + hdmi_writel(hdmi, 0x63, EARCRX_DMAC_CONTROL0); + hdmi_writel(hdmi, 0xff, EARCRX_DMAC_CONTROL1); + + hdmi_modb(hdmi, EARCRX_XACTREAD_STOP_CFG | EARCRX_XACTREAD_RETRY_CFG | + EARCRX_CMDC_DSCVR_EARCVALID0_TO_DISC1 | EARCRX_CMDC_XACT_RESTART_EN, + EARCRX_XACTREAD_STOP_CFG | EARCRX_XACTREAD_RETRY_CFG | + EARCRX_CMDC_DSCVR_EARCVALID0_TO_DISC1 | EARCRX_CMDC_XACT_RESTART_EN, + EARCRX_CMDC_CONFIG0); + + hdmi_writel(hdmi, 0, EARCRX_DMAC_CHSTATUS_STREAMER0); + hdmi_writel(hdmi, 0x1b0e, EARCRX_DMAC_CHSTATUS_STREAMER1); + hdmi_writel(hdmi, 0, EARCRX_DMAC_CHSTATUS_STREAMER2); + hdmi_writel(hdmi, 0, EARCRX_DMAC_CHSTATUS_STREAMER3); + hdmi_writel(hdmi, 0xf2000000, EARCRX_DMAC_CHSTATUS_STREAMER4); + hdmi_writel(hdmi, 0, EARCRX_DMAC_CHSTATUS_STREAMER5); + hdmi_writel(hdmi, 0, EARCRX_DMAC_CHSTATUS_STREAMER6); + hdmi_writel(hdmi, 0, EARCRX_DMAC_CHSTATUS_STREAMER7); + hdmi_writel(hdmi, 0, EARCRX_DMAC_CHSTATUS_STREAMER8); + + return 0; +} +EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_earc); + +/* ----------------------------------------------------------------------------- + * HDMI TX Setup + */ + +static void hdmi_infoframe_set_checksum(u8 *ptr, int size) +{ + u8 csum = 0; + int i; + + ptr[3] = 0; + /* compute checksum */ + for (i = 0; i < size; i++) + csum += ptr[i]; + + ptr[3] = 256 - csum; +} + +static void hdmi_config_AVI(struct dw_hdmi_qp *hdmi, + const struct drm_connector *connector, + const struct drm_display_mode *mode) +{ + struct hdmi_avi_infoframe frame; + u32 val, i, j; + u8 buff[17]; + enum hdmi_quantization_range rgb_quant_range = + hdmi->hdmi_data.quant_range; + + /* Initialise info frame from DRM mode */ + drm_hdmi_avi_infoframe_from_display_mode(&frame, connector, mode); + + /* + * Ignore monitor selectable quantization, use quantization set + * by the user + */ + drm_hdmi_avi_infoframe_quant_range(&frame, connector, mode, rgb_quant_range); + if (hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_out_bus_format)) + frame.colorspace = HDMI_COLORSPACE_YUV444; + else if (hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_out_bus_format)) + frame.colorspace = HDMI_COLORSPACE_YUV422; + else if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format)) + frame.colorspace = HDMI_COLORSPACE_YUV420; + else + frame.colorspace = HDMI_COLORSPACE_RGB; + + /* Set up colorimetry */ + if (!hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format)) { + switch (hdmi->hdmi_data.enc_out_encoding) { + case V4L2_YCBCR_ENC_601: + if (hdmi->hdmi_data.enc_in_encoding == V4L2_YCBCR_ENC_XV601) + frame.colorimetry = HDMI_COLORIMETRY_EXTENDED; + else + frame.colorimetry = HDMI_COLORIMETRY_ITU_601; + frame.extended_colorimetry = + HDMI_EXTENDED_COLORIMETRY_XV_YCC_601; + break; + case V4L2_YCBCR_ENC_709: + if (hdmi->hdmi_data.enc_in_encoding == V4L2_YCBCR_ENC_XV709) + frame.colorimetry = HDMI_COLORIMETRY_EXTENDED; + else + frame.colorimetry = HDMI_COLORIMETRY_ITU_709; + frame.extended_colorimetry = + HDMI_EXTENDED_COLORIMETRY_XV_YCC_709; + break; + case V4L2_YCBCR_ENC_BT2020: + if (hdmi->hdmi_data.enc_in_encoding == V4L2_YCBCR_ENC_BT2020) + frame.colorimetry = HDMI_COLORIMETRY_EXTENDED; + else + frame.colorimetry = HDMI_COLORIMETRY_ITU_709; + frame.extended_colorimetry = + HDMI_EXTENDED_COLORIMETRY_BT2020; + break; + default: /* Carries no data */ + frame.colorimetry = HDMI_COLORIMETRY_ITU_601; + frame.extended_colorimetry = + HDMI_EXTENDED_COLORIMETRY_XV_YCC_601; + break; + } + } else { + if (hdmi->hdmi_data.enc_out_encoding == V4L2_YCBCR_ENC_BT2020) { + frame.colorimetry = HDMI_COLORIMETRY_EXTENDED; + frame.extended_colorimetry = + HDMI_EXTENDED_COLORIMETRY_BT2020; + } else { + frame.colorimetry = HDMI_COLORIMETRY_NONE; + frame.extended_colorimetry = + HDMI_EXTENDED_COLORIMETRY_XV_YCC_601; + } + } + + frame.scan_mode = HDMI_SCAN_MODE_NONE; + frame.video_code = hdmi->vic; + + hdmi_avi_infoframe_pack_only(&frame, buff, 17); + + /* mode which vic >= 128 must use avi version 3 */ + if (hdmi->vic >= 128) { + frame.version = 3; + buff[1] = frame.version; + buff[4] &= 0x1f; + buff[4] |= ((frame.colorspace & 0x7) << 5); + buff[7] = frame.video_code; + hdmi_infoframe_set_checksum(buff, 17); + } + + /* + * The Designware IP uses a different byte format from standard + * AVI info frames, though generally the bits are in the correct + * bytes. + */ + + val = (frame.version << 8) | (frame.length << 16); + hdmi_writel(hdmi, val, PKT_AVI_CONTENTS0); + + for (i = 0; i < 4; i++) { + for (j = 0; j < 4; j++) { + if (i * 4 + j >= 14) + break; + if (!j) + val = buff[i * 4 + j + 3]; + val |= buff[i * 4 + j + 3] << (8 * j); + } + + hdmi_writel(hdmi, val, PKT_AVI_CONTENTS1 + i * 4); + } + + hdmi_modb(hdmi, 0, PKTSCHED_AVI_FIELDRATE, PKTSCHED_PKT_CONFIG1); + + hdmi_modb(hdmi, PKTSCHED_AVI_TX_EN | PKTSCHED_GCP_TX_EN, + PKTSCHED_AVI_TX_EN | PKTSCHED_GCP_TX_EN, + PKTSCHED_PKT_EN); +} + +static void hdmi_config_CVTEM(struct dw_hdmi_qp *hdmi) +{ + u8 ds_type = 0; + u8 sync = 1; + u8 vfr = 1; + u8 afr = 0; + u8 new = 1; + u8 end = 0; + u8 data_set_length = 136; + u8 hb1[6] = { 0x80, 0, 0, 0, 0, 0x40 }; + u8 *pps_body; + u32 val, i, reg; + struct drm_display_mode *mode = &hdmi->previous_mode; + int hsync, hfront, hback; + struct dw_hdmi_link_config *link_cfg; + void *data = hdmi->plat_data->phy_data; + + hdmi_modb(hdmi, 0, PKTSCHED_EMP_CVTEM_TX_EN, PKTSCHED_PKT_EN); + + if (hdmi->plat_data->get_link_cfg) { + link_cfg = hdmi->plat_data->get_link_cfg(data); + } else { + dev_err(hdmi->dev, "can't get frl link cfg\n"); + return; + } + + if (!link_cfg->dsc_mode) { + dev_info(hdmi->dev, "don't use dsc mode\n"); + return; + } + + pps_body = link_cfg->pps_payload; + + hsync = mode->hsync_end - mode->hsync_start; + hback = mode->htotal - mode->hsync_end; + hfront = mode->hsync_start - mode->hdisplay; + + for (i = 0; i < 6; i++) { + val = i << 16 | hb1[i] << 8; + hdmi_writel(hdmi, val, PKT0_EMP_CVTEM_CONTENTS0 + i * 0x20); + } + + val = new << 7 | end << 6 | ds_type << 4 | afr << 3 | + vfr << 2 | sync << 1; + hdmi_writel(hdmi, val, PKT0_EMP_CVTEM_CONTENTS1); + + val = data_set_length << 16 | pps_body[0] << 24; + hdmi_writel(hdmi, val, PKT0_EMP_CVTEM_CONTENTS2); + + reg = PKT0_EMP_CVTEM_CONTENTS3; + for (i = 1; i < 125; i++) { + if (reg == PKT1_EMP_CVTEM_CONTENTS0 || + reg == PKT2_EMP_CVTEM_CONTENTS0 || + reg == PKT3_EMP_CVTEM_CONTENTS0 || + reg == PKT4_EMP_CVTEM_CONTENTS0 || + reg == PKT5_EMP_CVTEM_CONTENTS0) { + reg += 4; + i--; + continue; + } + if (i % 4 == 1) + val = pps_body[i]; + if (i % 4 == 2) + val |= pps_body[i] << 8; + if (i % 4 == 3) + val |= pps_body[i] << 16; + if (!(i % 4)) { + val |= pps_body[i] << 24; + hdmi_writel(hdmi, val, reg); + reg += 4; + } + } + + val = (hfront & 0xff) << 24 | pps_body[127] << 16 | + pps_body[126] << 8 | pps_body[125]; + hdmi_writel(hdmi, val, PKT4_EMP_CVTEM_CONTENTS6); + + val = (hback & 0xff) << 24 | ((hsync >> 8) & 0xff) << 16 | + (hsync & 0xff) << 8 | ((hfront >> 8) & 0xff); + hdmi_writel(hdmi, val, PKT4_EMP_CVTEM_CONTENTS7); + + val = link_cfg->hcactive << 8 | ((hback >> 8) & 0xff); + hdmi_writel(hdmi, val, PKT5_EMP_CVTEM_CONTENTS1); + + for (i = PKT5_EMP_CVTEM_CONTENTS2; i <= PKT5_EMP_CVTEM_CONTENTS7; i += 4) + hdmi_writel(hdmi, 0, i); + + hdmi_modb(hdmi, PKTSCHED_EMP_CVTEM_TX_EN, PKTSCHED_EMP_CVTEM_TX_EN, + PKTSCHED_PKT_EN); +} + +static void hdmi_config_drm_infoframe(struct dw_hdmi_qp *hdmi, + const struct drm_connector *connector) +{ + const struct drm_connector_state *conn_state = connector->state; + struct hdr_output_metadata *hdr_metadata; + struct hdmi_drm_infoframe frame; + u8 buffer[30]; + ssize_t err; + int i; + u32 val; + + if (!hdmi->plat_data->use_drm_infoframe) + return; + + hdmi_modb(hdmi, 0, PKTSCHED_DRMI_TX_EN, PKTSCHED_PKT_EN); + + if (!hdmi->connector.hdr_sink_metadata.hdmi_type1.eotf) { + DRM_DEBUG("No need to set HDR metadata in infoframe\n"); + return; + } + + if (!conn_state->hdr_output_metadata) { + DRM_DEBUG("source metadata not set yet\n"); + return; + } + + hdr_metadata = (struct hdr_output_metadata *) + conn_state->hdr_output_metadata->data; + + if (!(hdmi->connector.hdr_sink_metadata.hdmi_type1.eotf & + BIT(hdr_metadata->hdmi_metadata_type1.eotf))) { + DRM_ERROR("Not support EOTF %d\n", + hdr_metadata->hdmi_metadata_type1.eotf); + return; + } + + err = drm_hdmi_infoframe_set_hdr_metadata(&frame, conn_state); + if (err < 0) + return; + + err = hdmi_drm_infoframe_pack(&frame, buffer, sizeof(buffer)); + if (err < 0) { + dev_err(hdmi->dev, "Failed to pack drm infoframe: %zd\n", err); + return; + } + + val = (frame.version << 8) | (frame.length << 16); + hdmi_writel(hdmi, val, PKT_DRMI_CONTENTS0); + + for (i = 0; i <= frame.length; i++) { + if (i % 4 == 0) + val = buffer[3 + i]; + val |= buffer[3 + i] << ((i % 4) * 8); + + if (i % 4 == 3 || (i == (frame.length))) + hdmi_writel(hdmi, val, PKT_DRMI_CONTENTS1 + ((i / 4) * 4)); + } + + hdmi_modb(hdmi, 0, PKTSCHED_DRMI_FIELDRATE, PKTSCHED_PKT_CONFIG1); + + hdmi_modb(hdmi, PKTSCHED_DRMI_TX_EN, PKTSCHED_DRMI_TX_EN, PKTSCHED_PKT_EN); + + DRM_DEBUG("%s eotf %d end\n", __func__, + hdr_metadata->hdmi_metadata_type1.eotf); +} + +/* Filter out invalid setups to avoid configuring SCDC and scrambling */ +static bool dw_hdmi_support_scdc(struct dw_hdmi_qp *hdmi, + const struct drm_display_info *display) +{ + /* Disable if no DDC bus */ + if (!hdmi->ddc) + return false; + + /* Disable if SCDC is not supported, or if an HF-VSDB block is absent */ + if (!display->hdmi.scdc.supported || + !display->hdmi.scdc.scrambling.supported) + return false; + + /* + * Disable if display only support low TMDS rates and scrambling + * for low rates is not supported either + */ + if (!display->hdmi.scdc.scrambling.low_rates && + display->max_tmds_clock <= 340000) + return false; + + return true; +} + +static int hdmi_set_frl_mask(int frl_rate) +{ + switch (frl_rate) { + case 48: + return FRL_12GBPS_4LANE; + case 40: + return FRL_10GBPS_4LANE; + case 32: + return FRL_8GBPS_4LANE; + case 24: + return FRL_6GBPS_4LANE; + case 18: + return FRL_6GBPS_3LANE; + case 9: + return FRL_3GBPS_3LANE; + } + + return 0; +} + +static int hdmi_start_flt(struct dw_hdmi_qp *hdmi, u8 rate) +{ + u8 val; + u8 ffe_lv = 0; + int i = 0, stat; + + /* FLT_READY & FFE_LEVELS read */ + for (i = 0; i < 20; i++) { + drm_scdc_readb(hdmi->ddc, SCDC_STATUS_FLAGS_0, &val); + if (val & BIT(6)) + break; + msleep(20); + } + + if (i == 20) { + dev_err(hdmi->dev, "sink flt isn't ready\n"); + return -EINVAL; + } + + hdmi_modb(hdmi, SCDC_UPD_FLAGS_RD_IRQ, SCDC_UPD_FLAGS_RD_IRQ, + MAINUNIT_1_INT_MASK_N); + hdmi_modb(hdmi, SCDC_UPD_FLAGS_POLL_EN | SCDC_UPD_FLAGS_AUTO_CLR, + SCDC_UPD_FLAGS_POLL_EN | SCDC_UPD_FLAGS_AUTO_CLR, + SCDC_CONFIG0); + + /* max ffe level 3 */ + val = 3 << 4 | hdmi_set_frl_mask(rate); + drm_scdc_writeb(hdmi->ddc, 0x31, val); + + /* select FRL_RATE & FFE_LEVELS */ + hdmi_writel(hdmi, ffe_lv, FLT_CONFIG0); + + /* Start LTS_3 state in source DUT */ + reinit_completion(&hdmi->flt_cmp); + hdmi_modb(hdmi, FLT_EXIT_TO_LTSP_IRQ, FLT_EXIT_TO_LTSP_IRQ, + MAINUNIT_1_INT_MASK_N); + hdmi_writel(hdmi, 1, FLT_CONTROL0); + + /* wait for completed link training at source side */ + stat = wait_for_completion_timeout(&hdmi->flt_cmp, HZ * 2); + if (!stat) { + dev_err(hdmi->dev, "wait lts3 finish time out\n"); + hdmi_modb(hdmi, 0, SCDC_UPD_FLAGS_POLL_EN | + SCDC_UPD_FLAGS_AUTO_CLR, SCDC_CONFIG0); + hdmi_modb(hdmi, 0, SCDC_UPD_FLAGS_RD_IRQ, + MAINUNIT_1_INT_MASK_N); + return -EAGAIN; + } + + if (!(hdmi->flt_intr & FLT_EXIT_TO_LTSP_IRQ)) { + dev_err(hdmi->dev, "not to ltsp\n"); + hdmi_modb(hdmi, 0, SCDC_UPD_FLAGS_POLL_EN | + SCDC_UPD_FLAGS_AUTO_CLR, SCDC_CONFIG0); + hdmi_modb(hdmi, 0, SCDC_UPD_FLAGS_RD_IRQ, + MAINUNIT_1_INT_MASK_N); + return -EINVAL; + } + + return 0; +} + +#define HDMI_MODE_FRL_MASK BIT(30) + +static void hdmi_set_op_mode(struct dw_hdmi_qp *hdmi, + struct dw_hdmi_link_config *link_cfg, + const struct drm_connector *connector) +{ + int frl_rate; + int i; + + /* set sink frl mode disable and wait sink ready */ + hdmi_writel(hdmi, 0, FLT_CONFIG0); + if (dw_hdmi_support_scdc(hdmi, &connector->display_info)) + drm_scdc_writeb(hdmi->ddc, 0x31, 0); + /* + * some TVs must wait a while before switching frl mode resolution, + * or the signal may not be recognized. + */ + msleep(200); + + if (!link_cfg->frl_mode) { + dev_info(hdmi->dev, "dw hdmi qp use tmds mode\n"); + hdmi_modb(hdmi, 0, OPMODE_FRL, LINK_CONFIG0); + hdmi_modb(hdmi, 0, OPMODE_FRL_4LANES, LINK_CONFIG0); + return; + } + + if (link_cfg->frl_lanes == 4) + hdmi_modb(hdmi, OPMODE_FRL_4LANES, OPMODE_FRL_4LANES, + LINK_CONFIG0); + else + hdmi_modb(hdmi, 0, OPMODE_FRL_4LANES, LINK_CONFIG0); + + hdmi_modb(hdmi, 1, OPMODE_FRL, LINK_CONFIG0); + + frl_rate = link_cfg->frl_lanes * link_cfg->rate_per_lane; + hdmi_start_flt(hdmi, frl_rate); + + for (i = 0; i < 50; i++) { + hdmi_modb(hdmi, PKTSCHED_NULL_TX_EN, PKTSCHED_NULL_TX_EN, PKTSCHED_PKT_EN); + mdelay(1); + hdmi_modb(hdmi, 0, PKTSCHED_NULL_TX_EN, PKTSCHED_PKT_EN); + } +} + +static unsigned long +hdmi_get_tmdsclock(struct dw_hdmi_qp *hdmi, unsigned long mpixelclock) +{ + unsigned long tmdsclock = mpixelclock; + unsigned int depth = + hdmi_bus_fmt_color_depth(hdmi->hdmi_data.enc_out_bus_format); + + if (!hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_out_bus_format)) { + switch (depth) { + case 16: + tmdsclock = mpixelclock * 2; + break; + case 12: + tmdsclock = mpixelclock * 3 / 2; + break; + case 10: + tmdsclock = mpixelclock * 5 / 4; + break; + default: + break; + } + } + + return tmdsclock; +} + +//[CC:] is connector param different from hdmi->connector? +//[CC:] probably it possible to hook the whole implementation into dw-hdmi.c +static int dw_hdmi_qp_setup(struct dw_hdmi_qp *hdmi, + struct drm_connector *connector, + struct drm_display_mode *mode) +{ + int ret; + void *data = hdmi->plat_data->phy_data; + struct hdmi_vmode_qp *vmode = &hdmi->hdmi_data.video_mode; + struct dw_hdmi_link_config *link_cfg; + u8 bytes = 0; + + hdmi->vic = drm_match_cea_mode(mode); + if (!hdmi->vic) + dev_dbg(hdmi->dev, "Non-CEA mode used in HDMI\n"); + else + dev_dbg(hdmi->dev, "CEA mode used vic=%d\n", hdmi->vic); + + if (hdmi->plat_data->get_enc_out_encoding) + hdmi->hdmi_data.enc_out_encoding = + hdmi->plat_data->get_enc_out_encoding(data); + else if ((hdmi->vic == 6) || (hdmi->vic == 7) || + (hdmi->vic == 21) || (hdmi->vic == 22) || + (hdmi->vic == 2) || (hdmi->vic == 3) || + (hdmi->vic == 17) || (hdmi->vic == 18)) + hdmi->hdmi_data.enc_out_encoding = V4L2_YCBCR_ENC_601; + else + hdmi->hdmi_data.enc_out_encoding = V4L2_YCBCR_ENC_709; + + if (mode->flags & DRM_MODE_FLAG_DBLCLK) { + hdmi->hdmi_data.video_mode.mpixelrepetitionoutput = 1; + hdmi->hdmi_data.video_mode.mpixelrepetitioninput = 1; + } else { + hdmi->hdmi_data.video_mode.mpixelrepetitionoutput = 0; + hdmi->hdmi_data.video_mode.mpixelrepetitioninput = 0; + } + /* Get input format from plat data or fallback to RGB888 */ + if (hdmi->plat_data->get_input_bus_format) + hdmi->hdmi_data.enc_in_bus_format = + hdmi->plat_data->get_input_bus_format(data); + else if (hdmi->plat_data->input_bus_format) + hdmi->hdmi_data.enc_in_bus_format = + hdmi->plat_data->input_bus_format; + else + hdmi->hdmi_data.enc_in_bus_format = MEDIA_BUS_FMT_RGB888_1X24; + + /* Default to RGB888 output format */ + if (hdmi->plat_data->get_output_bus_format) + hdmi->hdmi_data.enc_out_bus_format = + hdmi->plat_data->get_output_bus_format(data); + else + hdmi->hdmi_data.enc_out_bus_format = MEDIA_BUS_FMT_RGB888_1X24; + + /* Get input encoding from plat data or fallback to none */ + if (hdmi->plat_data->get_enc_in_encoding) + hdmi->hdmi_data.enc_in_encoding = + hdmi->plat_data->get_enc_in_encoding(data); + else if (hdmi->plat_data->input_bus_encoding) + hdmi->hdmi_data.enc_in_encoding = + hdmi->plat_data->input_bus_encoding; + else + hdmi->hdmi_data.enc_in_encoding = V4L2_YCBCR_ENC_DEFAULT; + + if (hdmi->plat_data->get_quant_range) + hdmi->hdmi_data.quant_range = + hdmi->plat_data->get_quant_range(data); + else + hdmi->hdmi_data.quant_range = HDMI_QUANTIZATION_RANGE_DEFAULT; + + if (hdmi->plat_data->get_link_cfg) + link_cfg = hdmi->plat_data->get_link_cfg(data); + else + return -EINVAL; + + hdmi->phy.ops->set_mode(hdmi, hdmi->phy.data, HDMI_MODE_FRL_MASK, + link_cfg->frl_mode); + + /* + * According to the dw-hdmi specification 6.4.2 + * vp_pr_cd[3:0]: + * 0000b: No pixel repetition (pixel sent only once) + * 0001b: Pixel sent two times (pixel repeated once) + */ + hdmi->hdmi_data.pix_repet_factor = + (mode->flags & DRM_MODE_FLAG_DBLCLK) ? 1 : 0; + hdmi->hdmi_data.video_mode.mdataenablepolarity = true; + + vmode->previous_pixelclock = vmode->mpixelclock; + //[CC:] no split mode + // if (hdmi->plat_data->split_mode) + // mode->crtc_clock /= 2; + vmode->mpixelclock = mode->crtc_clock * 1000; + if ((mode->flags & DRM_MODE_FLAG_3D_MASK) == DRM_MODE_FLAG_3D_FRAME_PACKING) + vmode->mpixelclock *= 2; + dev_dbg(hdmi->dev, "final pixclk = %ld\n", vmode->mpixelclock); + vmode->previous_tmdsclock = vmode->mtmdsclock; + vmode->mtmdsclock = hdmi_get_tmdsclock(hdmi, vmode->mpixelclock); + if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format)) + vmode->mtmdsclock /= 2; + dev_info(hdmi->dev, "final tmdsclk = %d\n", vmode->mtmdsclock); + + ret = hdmi->phy.ops->init(hdmi, hdmi->phy.data, &hdmi->previous_mode); + if (ret) + return ret; + + if (hdmi->plat_data->set_grf_cfg) + hdmi->plat_data->set_grf_cfg(data); + + if (hdmi->sink_has_audio) { + dev_dbg(hdmi->dev, "sink has audio support\n"); + + /* HDMI Initialization Step E - Configure audio */ + hdmi_clk_regenerator_update_pixel_clock(hdmi); + hdmi_enable_audio_clk(hdmi, hdmi->audio_enable); + } + + /* not for DVI mode */ + if (hdmi->sink_is_hdmi) { + dev_dbg(hdmi->dev, "%s HDMI mode\n", __func__); + hdmi_modb(hdmi, 0, OPMODE_DVI, LINK_CONFIG0); + hdmi_modb(hdmi, HDCP2_BYPASS, HDCP2_BYPASS, HDCP2LOGIC_CONFIG0); + if (!link_cfg->frl_mode) { + if (vmode->mtmdsclock > HDMI14_MAX_TMDSCLK) { + drm_scdc_readb(hdmi->ddc, SCDC_SINK_VERSION, &bytes); + drm_scdc_writeb(hdmi->ddc, SCDC_SOURCE_VERSION, + min_t(u8, bytes, SCDC_MIN_SOURCE_VERSION)); + //[CC:] use dw_hdmi_set_high_tmds_clock_ratio() + drm_scdc_set_high_tmds_clock_ratio(connector, 1); + drm_scdc_set_scrambling(connector, 1); + hdmi_writel(hdmi, 1, SCRAMB_CONFIG0); + } else { + if (dw_hdmi_support_scdc(hdmi, &connector->display_info)) { + drm_scdc_set_high_tmds_clock_ratio(connector, 0); + drm_scdc_set_scrambling(connector, 0); + } + hdmi_writel(hdmi, 0, SCRAMB_CONFIG0); + } + } + /* HDMI Initialization Step F - Configure AVI InfoFrame */ + hdmi_config_AVI(hdmi, connector, mode); + hdmi_config_CVTEM(hdmi); + hdmi_config_drm_infoframe(hdmi, connector); + hdmi_set_op_mode(hdmi, link_cfg, connector); + } else { + hdmi_modb(hdmi, HDCP2_BYPASS, HDCP2_BYPASS, HDCP2LOGIC_CONFIG0); + hdmi_modb(hdmi, OPMODE_DVI, OPMODE_DVI, LINK_CONFIG0); + dev_info(hdmi->dev, "%s DVI mode\n", __func__); + } + + return 0; +} + +static enum drm_connector_status +dw_hdmi_connector_detect(struct drm_connector *connector, bool force) +{ + struct dw_hdmi_qp *hdmi = + container_of(connector, struct dw_hdmi_qp, connector); + struct dw_hdmi_qp *secondary = NULL; + enum drm_connector_status result, result_secondary; + + mutex_lock(&hdmi->mutex); + hdmi->force = DRM_FORCE_UNSPECIFIED; + mutex_unlock(&hdmi->mutex); + + if (hdmi->plat_data->left) + secondary = hdmi->plat_data->left; + else if (hdmi->plat_data->right) + secondary = hdmi->plat_data->right; + + result = hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data); + + if (secondary) { + result_secondary = secondary->phy.ops->read_hpd(secondary, secondary->phy.data); + if (result == connector_status_connected && + result_secondary == connector_status_connected) + result = connector_status_connected; + else + result = connector_status_disconnected; + } + + mutex_lock(&hdmi->mutex); + if (result != hdmi->last_connector_result) { + dev_dbg(hdmi->dev, "read_hpd result: %d", result); + handle_plugged_change(hdmi, + result == connector_status_connected); + hdmi->last_connector_result = result; + } + mutex_unlock(&hdmi->mutex); + + return result; +} + +static int +dw_hdmi_update_hdr_property(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct dw_hdmi_qp *hdmi = container_of(connector, struct dw_hdmi_qp, + connector); + void *data = hdmi->plat_data->phy_data; + const struct hdr_static_metadata *metadata = + &connector->hdr_sink_metadata.hdmi_type1; + size_t size = sizeof(*metadata); + struct drm_property *property; + struct drm_property_blob *blob; + int ret; + + if (hdmi->plat_data->get_hdr_property) + property = hdmi->plat_data->get_hdr_property(data); + else + return -EINVAL; + + if (hdmi->plat_data->get_hdr_blob) + blob = hdmi->plat_data->get_hdr_blob(data); + else + return -EINVAL; + + ret = drm_property_replace_global_blob(dev, &blob, size, metadata, + &connector->base, property); + return ret; +} + +static int dw_hdmi_connector_get_modes(struct drm_connector *connector) +{ + struct dw_hdmi_qp *hdmi = + container_of(connector, struct dw_hdmi_qp, connector); + struct hdr_static_metadata *metedata = + &connector->hdr_sink_metadata.hdmi_type1; + struct edid *edid; + struct drm_display_mode *mode; + struct drm_display_info *info = &connector->display_info; + void *data = hdmi->plat_data->phy_data; + int i, ret = 0; + + if (!hdmi->ddc) + return 0; + + memset(metedata, 0, sizeof(*metedata)); + edid = drm_get_edid(connector, hdmi->ddc); + if (edid) { + dev_dbg(hdmi->dev, "got edid: width[%d] x height[%d]\n", + edid->width_cm, edid->height_cm); + + hdmi->sink_is_hdmi = drm_detect_hdmi_monitor(edid); + hdmi->sink_has_audio = drm_detect_monitor_audio(edid); + drm_connector_update_edid_property(connector, edid); + cec_notifier_set_phys_addr_from_edid(hdmi->cec_notifier, edid); + if (hdmi->plat_data->get_edid_dsc_info) + hdmi->plat_data->get_edid_dsc_info(data, edid); + ret = drm_add_edid_modes(connector, edid); + dw_hdmi_update_hdr_property(connector); + if (ret > 0 && hdmi->plat_data->split_mode) { + struct dw_hdmi_qp *secondary = NULL; + void *secondary_data; + + if (hdmi->plat_data->left) + secondary = hdmi->plat_data->left; + else if (hdmi->plat_data->right) + secondary = hdmi->plat_data->right; + + if (!secondary) + return -ENOMEM; + secondary_data = secondary->plat_data->phy_data; + + list_for_each_entry(mode, &connector->probed_modes, head) + hdmi->plat_data->convert_to_split_mode(mode); + + secondary->sink_is_hdmi = drm_detect_hdmi_monitor(edid); + secondary->sink_has_audio = drm_detect_monitor_audio(edid); + cec_notifier_set_phys_addr_from_edid(secondary->cec_notifier, edid); + if (secondary->plat_data->get_edid_dsc_info) + secondary->plat_data->get_edid_dsc_info(secondary_data, edid); + } + kfree(edid); + } else { + hdmi->sink_is_hdmi = true; + hdmi->sink_has_audio = true; + + if (hdmi->plat_data->split_mode) { + if (hdmi->plat_data->left) { + hdmi->plat_data->left->sink_is_hdmi = true; + hdmi->plat_data->left->sink_has_audio = true; + } else if (hdmi->plat_data->right) { + hdmi->plat_data->right->sink_is_hdmi = true; + hdmi->plat_data->right->sink_has_audio = true; + } + } + + for (i = 0; i < ARRAY_SIZE(dw_hdmi_default_modes); i++) { + const struct drm_display_mode *ptr = + &dw_hdmi_default_modes[i]; + + mode = drm_mode_duplicate(connector->dev, ptr); + if (mode) { + if (!i) + mode->type = DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(connector, mode); + ret++; + } + } + if (ret > 0 && hdmi->plat_data->split_mode) { + struct drm_display_mode *mode; + + list_for_each_entry(mode, &connector->probed_modes, head) + hdmi->plat_data->convert_to_split_mode(mode); + } + info->edid_hdmi_rgb444_dc_modes = 0; + info->hdmi.y420_dc_modes = 0; + info->color_formats = 0; + + dev_info(hdmi->dev, "failed to get edid\n"); + } + + return ret; +} + +static int +dw_hdmi_atomic_connector_set_property(struct drm_connector *connector, + struct drm_connector_state *state, + struct drm_property *property, + uint64_t val) +{ + struct dw_hdmi_qp *hdmi = + container_of(connector, struct dw_hdmi_qp, connector); + const struct dw_hdmi_property_ops *ops = hdmi->plat_data->property_ops; + + if (ops && ops->set_property) + return ops->set_property(connector, state, property, + val, hdmi->plat_data->phy_data); + else + return -EINVAL; +} + +static int +dw_hdmi_atomic_connector_get_property(struct drm_connector *connector, + const struct drm_connector_state *state, + struct drm_property *property, + uint64_t *val) +{ + struct dw_hdmi_qp *hdmi = + container_of(connector, struct dw_hdmi_qp, connector); + const struct dw_hdmi_property_ops *ops = hdmi->plat_data->property_ops; + + if (ops && ops->get_property) + return ops->get_property(connector, state, property, + val, hdmi->plat_data->phy_data); + else + return -EINVAL; +} + +static int +dw_hdmi_connector_set_property(struct drm_connector *connector, + struct drm_property *property, uint64_t val) +{ + return dw_hdmi_atomic_connector_set_property(connector, NULL, + property, val); +} + +static void dw_hdmi_attach_properties(struct dw_hdmi_qp *hdmi) +{ + unsigned int color = MEDIA_BUS_FMT_RGB888_1X24; + const struct dw_hdmi_property_ops *ops = + hdmi->plat_data->property_ops; + + if (ops && ops->attach_properties) + return ops->attach_properties(&hdmi->connector, color, 0, + hdmi->plat_data->phy_data); +} + +static void dw_hdmi_destroy_properties(struct dw_hdmi_qp *hdmi) +{ + const struct dw_hdmi_property_ops *ops = + hdmi->plat_data->property_ops; + + if (ops && ops->destroy_properties) + return ops->destroy_properties(&hdmi->connector, + hdmi->plat_data->phy_data); +} + +static struct drm_encoder * +dw_hdmi_connector_best_encoder(struct drm_connector *connector) +{ + struct dw_hdmi_qp *hdmi = + container_of(connector, struct dw_hdmi_qp, connector); + + return hdmi->bridge.encoder; +} + +static bool dw_hdmi_color_changed(struct drm_connector *connector, + struct drm_atomic_state *state) +{ + struct dw_hdmi_qp *hdmi = + container_of(connector, struct dw_hdmi_qp, connector); + void *data = hdmi->plat_data->phy_data; + struct drm_connector_state *old_state = + drm_atomic_get_old_connector_state(state, connector); + struct drm_connector_state *new_state = + drm_atomic_get_new_connector_state(state, connector); + bool ret = false; + + if (hdmi->plat_data->get_color_changed) + ret = hdmi->plat_data->get_color_changed(data); + + if (new_state->colorspace != old_state->colorspace) + ret = true; + + return ret; +} + +static bool hdr_metadata_equal(const struct drm_connector_state *old_state, + const struct drm_connector_state *new_state) +{ + struct drm_property_blob *old_blob = old_state->hdr_output_metadata; + struct drm_property_blob *new_blob = new_state->hdr_output_metadata; + + if (!old_blob || !new_blob) + return old_blob == new_blob; + + if (old_blob->length != new_blob->length) + return false; + + return !memcmp(old_blob->data, new_blob->data, old_blob->length); +} + +static int dw_hdmi_connector_atomic_check(struct drm_connector *connector, + struct drm_atomic_state *state) +{ + struct drm_connector_state *old_state = + drm_atomic_get_old_connector_state(state, connector); + struct drm_connector_state *new_state = + drm_atomic_get_new_connector_state(state, connector); + struct drm_crtc *crtc = new_state->crtc; + struct drm_crtc_state *crtc_state; + struct dw_hdmi_qp *hdmi = + container_of(connector, struct dw_hdmi_qp, connector); + struct drm_display_mode *mode = NULL; + void *data = hdmi->plat_data->phy_data; + struct hdmi_vmode_qp *vmode = &hdmi->hdmi_data.video_mode; + + if (!crtc) + return 0; + + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + /* + * If HDMI is enabled in uboot, it's need to record + * drm_display_mode and set phy status to enabled. + */ + if (!vmode->mpixelclock) { + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (hdmi->plat_data->get_enc_in_encoding) + hdmi->hdmi_data.enc_in_encoding = + hdmi->plat_data->get_enc_in_encoding(data); + if (hdmi->plat_data->get_enc_out_encoding) + hdmi->hdmi_data.enc_out_encoding = + hdmi->plat_data->get_enc_out_encoding(data); + if (hdmi->plat_data->get_input_bus_format) + hdmi->hdmi_data.enc_in_bus_format = + hdmi->plat_data->get_input_bus_format(data); + if (hdmi->plat_data->get_output_bus_format) + hdmi->hdmi_data.enc_out_bus_format = + hdmi->plat_data->get_output_bus_format(data); + + mode = &crtc_state->mode; + if (hdmi->plat_data->split_mode) { + hdmi->plat_data->convert_to_origin_mode(mode); + mode->crtc_clock /= 2; + } + memcpy(&hdmi->previous_mode, mode, sizeof(hdmi->previous_mode)); + vmode->mpixelclock = mode->crtc_clock * 1000; + vmode->previous_pixelclock = mode->clock; + vmode->previous_tmdsclock = mode->clock; + vmode->mtmdsclock = hdmi_get_tmdsclock(hdmi, + vmode->mpixelclock); + if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format)) + vmode->mtmdsclock /= 2; + + /* + * If uboot logo enabled, atomic_enable won't be called, + * but atomic_disable will be called when hdmi plug out. + * That will cause dclk enable count is incorrect. So + * we should check ipi/link/video clk to determine whether + * uboot logo is enabled. + */ + if (hdmi->initialized && !hdmi->dclk_en) { + mutex_lock(&hdmi->audio_mutex); + if (hdmi->plat_data->dclk_set) + hdmi->plat_data->dclk_set(data, true); + hdmi->dclk_en = true; + mutex_unlock(&hdmi->audio_mutex); + } + } + + if (!hdr_metadata_equal(old_state, new_state) || + dw_hdmi_color_changed(connector, state)) { + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + crtc_state->mode_changed = true; + } + + return 0; +} + +static void dw_hdmi_connector_force(struct drm_connector *connector) +{ + struct dw_hdmi_qp *hdmi = + container_of(connector, struct dw_hdmi_qp, connector); + + mutex_lock(&hdmi->mutex); + + if (hdmi->force != connector->force) { + if (!hdmi->disabled && connector->force == DRM_FORCE_OFF) + extcon_set_state_sync(hdmi->extcon, EXTCON_DISP_HDMI, + false); + else if (hdmi->disabled && connector->force == DRM_FORCE_ON) + extcon_set_state_sync(hdmi->extcon, EXTCON_DISP_HDMI, + true); + } + + hdmi->force = connector->force; + mutex_unlock(&hdmi->mutex); +} + +static int dw_hdmi_qp_fill_modes(struct drm_connector *connector, u32 max_x, + u32 max_y) +{ + return drm_helper_probe_single_connector_modes(connector, 9000, 9000); +} + +static const struct drm_connector_funcs dw_hdmi_connector_funcs = { + .fill_modes = dw_hdmi_qp_fill_modes, + .detect = dw_hdmi_connector_detect, + .destroy = drm_connector_cleanup, + .force = dw_hdmi_connector_force, + .reset = drm_atomic_helper_connector_reset, + .set_property = dw_hdmi_connector_set_property, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .atomic_set_property = dw_hdmi_atomic_connector_set_property, + .atomic_get_property = dw_hdmi_atomic_connector_get_property, +}; + +static const struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs = { + .get_modes = dw_hdmi_connector_get_modes, + .best_encoder = dw_hdmi_connector_best_encoder, + .atomic_check = dw_hdmi_connector_atomic_check, +}; + +static int dw_hdmi_qp_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct dw_hdmi_qp *hdmi = bridge->driver_private; + struct drm_encoder *encoder = bridge->encoder; + struct drm_connector *connector = &hdmi->connector; + struct cec_connector_info conn_info; + struct cec_notifier *notifier; + + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) + return 0; + + connector->interlace_allowed = 1; + connector->polled = DRM_CONNECTOR_POLL_HPD; + + drm_connector_helper_add(connector, &dw_hdmi_connector_helper_funcs); + + // [CC:] use drm_connector_init_with_ddc or drmm_connector_init + // to provide ddc reference + drm_connector_init_with_ddc(bridge->dev, connector, + &dw_hdmi_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA, + hdmi->ddc); + + drm_connector_attach_encoder(connector, encoder); + dw_hdmi_attach_properties(hdmi); + + cec_fill_conn_info_from_drm(&conn_info, connector); + notifier = cec_notifier_conn_register(hdmi->dev, NULL, &conn_info); + if (!notifier) + return -ENOMEM; + + mutex_lock(&hdmi->cec_notifier_mutex); + hdmi->cec_notifier = notifier; + mutex_unlock(&hdmi->cec_notifier_mutex); + + return 0; +} + +static void dw_hdmi_qp_bridge_detach(struct drm_bridge *bridge) +{ + struct dw_hdmi_qp *hdmi = bridge->driver_private; + + mutex_lock(&hdmi->cec_notifier_mutex); + cec_notifier_conn_unregister(hdmi->cec_notifier); + hdmi->cec_notifier = NULL; + mutex_unlock(&hdmi->cec_notifier_mutex); +} + +static void dw_hdmi_qp_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *orig_mode, + const struct drm_display_mode *mode) +{ + struct dw_hdmi_qp *hdmi = bridge->driver_private; + + mutex_lock(&hdmi->mutex); + + /* Store the display mode for plugin/DKMS poweron events */ + memcpy(&hdmi->previous_mode, mode, sizeof(hdmi->previous_mode)); + if (hdmi->plat_data->split_mode) + hdmi->plat_data->convert_to_origin_mode(&hdmi->previous_mode); + + mutex_unlock(&hdmi->mutex); +} + +static enum drm_mode_status +dw_hdmi_qp_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static void dw_hdmi_qp_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_state) +{ + struct dw_hdmi_qp *hdmi = bridge->driver_private; + struct drm_atomic_state *state = old_state->base.state; + struct drm_connector *connector; + void *data = hdmi->plat_data->phy_data; + + connector = drm_atomic_get_new_connector_for_encoder(state, + bridge->encoder); + + mutex_lock(&hdmi->mutex); + hdmi->curr_conn = connector; + dw_hdmi_qp_setup(hdmi, hdmi->curr_conn, &hdmi->previous_mode); + hdmi->disabled = false; + mutex_unlock(&hdmi->mutex); + + if (!hdmi->dclk_en) { + mutex_lock(&hdmi->audio_mutex); + if (hdmi->plat_data->dclk_set) + hdmi->plat_data->dclk_set(data, true); + hdmi->dclk_en = true; + mutex_unlock(&hdmi->audio_mutex); + } + + extcon_set_state_sync(hdmi->extcon, EXTCON_DISP_HDMI, true); + handle_plugged_change(hdmi, true); +} + +static void dw_hdmi_qp_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_state) +{ + struct dw_hdmi_qp *hdmi = bridge->driver_private; + void *data = hdmi->plat_data->phy_data; + + extcon_set_state_sync(hdmi->extcon, EXTCON_DISP_HDMI, false); + handle_plugged_change(hdmi, false); + mutex_lock(&hdmi->mutex); + + hdmi->curr_conn = NULL; + + if (hdmi->dclk_en) { + mutex_lock(&hdmi->audio_mutex); + if (hdmi->plat_data->dclk_set) + hdmi->plat_data->dclk_set(data, false); + hdmi->dclk_en = false; + mutex_unlock(&hdmi->audio_mutex); + }; + + if (hdmi->phy.ops->disable) + hdmi->phy.ops->disable(hdmi, hdmi->phy.data); + hdmi->disabled = true; + mutex_unlock(&hdmi->mutex); +} + +static const struct drm_bridge_funcs dw_hdmi_bridge_funcs = { + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_reset = drm_atomic_helper_bridge_reset, + .attach = dw_hdmi_qp_bridge_attach, + .detach = dw_hdmi_qp_bridge_detach, + .mode_set = dw_hdmi_qp_bridge_mode_set, + .mode_valid = dw_hdmi_qp_bridge_mode_valid, + .atomic_enable = dw_hdmi_qp_bridge_atomic_enable, + .atomic_disable = dw_hdmi_qp_bridge_atomic_disable, +}; + +void dw_hdmi_qp_set_cec_adap(struct dw_hdmi_qp *hdmi, struct cec_adapter *adap) +{ + hdmi->cec_adap = adap; +} +EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_cec_adap); + +static irqreturn_t dw_hdmi_qp_main_hardirq(int irq, void *dev_id) +{ + struct dw_hdmi_qp *hdmi = dev_id; + struct dw_hdmi_qp_i2c *i2c = hdmi->i2c; + u32 stat; + + stat = hdmi_readl(hdmi, MAINUNIT_1_INT_STATUS); + + i2c->stat = stat & (I2CM_OP_DONE_IRQ | I2CM_READ_REQUEST_IRQ | + I2CM_NACK_RCVD_IRQ); + hdmi->scdc_intr = stat & (SCDC_UPD_FLAGS_RD_IRQ | + SCDC_UPD_FLAGS_CHG_IRQ | + SCDC_UPD_FLAGS_CLR_IRQ | + SCDC_RR_REPLY_STOP_IRQ | + SCDC_NACK_RCVD_IRQ); + hdmi->flt_intr = stat & (FLT_EXIT_TO_LTSP_IRQ | + FLT_EXIT_TO_LTS4_IRQ | + FLT_EXIT_TO_LTSL_IRQ); + + // dev_dbg(hdmi->dev, "i2c main unit irq:%#x\n", stat); + if (i2c->stat) { + hdmi_writel(hdmi, i2c->stat, MAINUNIT_1_INT_CLEAR); + complete(&i2c->cmp); + } + + if (hdmi->flt_intr) { + dev_dbg(hdmi->dev, "i2c flt irq:%#x\n", hdmi->flt_intr); + hdmi_writel(hdmi, hdmi->flt_intr, MAINUNIT_1_INT_CLEAR); + complete(&hdmi->flt_cmp); + } + + if (hdmi->scdc_intr) { + u8 val; + + dev_dbg(hdmi->dev, "i2c scdc irq:%#x\n", hdmi->scdc_intr); + hdmi_writel(hdmi, hdmi->scdc_intr, MAINUNIT_1_INT_CLEAR); + val = hdmi_readl(hdmi, SCDC_STATUS0); + + /* frl start */ + if (val & BIT(4)) { + hdmi_modb(hdmi, 0, SCDC_UPD_FLAGS_POLL_EN | + SCDC_UPD_FLAGS_AUTO_CLR, SCDC_CONFIG0); + hdmi_modb(hdmi, 0, SCDC_UPD_FLAGS_RD_IRQ, + MAINUNIT_1_INT_MASK_N); + dev_info(hdmi->dev, "frl start\n"); + } + + } + + if (stat) + return IRQ_HANDLED; + + return IRQ_NONE; +} + +static irqreturn_t dw_hdmi_qp_avp_hardirq(int irq, void *dev_id) +{ + struct dw_hdmi_qp *hdmi = dev_id; + u32 stat; + + stat = hdmi_readl(hdmi, AVP_1_INT_STATUS); + if (stat) { + dev_dbg(hdmi->dev, "HDCP irq %#x\n", stat); + stat &= ~stat; + hdmi_writel(hdmi, stat, AVP_1_INT_MASK_N); + return IRQ_WAKE_THREAD; + } + + return IRQ_NONE; +} + +static irqreturn_t dw_hdmi_qp_earc_hardirq(int irq, void *dev_id) +{ + struct dw_hdmi_qp *hdmi = dev_id; + u32 stat; + + stat = hdmi_readl(hdmi, EARCRX_0_INT_STATUS); + if (stat) { + dev_dbg(hdmi->dev, "earc irq %#x\n", stat); + stat &= ~stat; + hdmi_writel(hdmi, stat, EARCRX_0_INT_MASK_N); + return IRQ_WAKE_THREAD; + } + + return IRQ_NONE; +} + +static irqreturn_t dw_hdmi_qp_avp_irq(int irq, void *dev_id) +{ + struct dw_hdmi_qp *hdmi = dev_id; + u32 stat; + + stat = hdmi_readl(hdmi, AVP_1_INT_STATUS); + + if (!stat) + return IRQ_NONE; + + hdmi_writel(hdmi, stat, AVP_1_INT_CLEAR); + + return IRQ_HANDLED; +} + +static irqreturn_t dw_hdmi_qp_earc_irq(int irq, void *dev_id) +{ + struct dw_hdmi_qp *hdmi = dev_id; + u32 stat; + + stat = hdmi_readl(hdmi, EARCRX_0_INT_STATUS); + + if (!stat) + return IRQ_NONE; + + hdmi_writel(hdmi, stat, EARCRX_0_INT_CLEAR); + + hdmi->earc_intr = stat; + complete(&hdmi->earc_cmp); + + return IRQ_HANDLED; +} + +static int dw_hdmi_detect_phy(struct dw_hdmi_qp *hdmi) +{ + u8 phy_type; + + phy_type = hdmi->plat_data->phy_force_vendor ? + DW_HDMI_PHY_VENDOR_PHY : 0; + + if (phy_type == DW_HDMI_PHY_VENDOR_PHY) { + /* Vendor PHYs require support from the glue layer. */ + if (!hdmi->plat_data->qp_phy_ops || !hdmi->plat_data->phy_name) { + dev_err(hdmi->dev, + "Vendor HDMI PHY not supported by glue layer\n"); + return -ENODEV; + } + + hdmi->phy.ops = hdmi->plat_data->qp_phy_ops; + hdmi->phy.data = hdmi->plat_data->phy_data; + hdmi->phy.name = hdmi->plat_data->phy_name; + } + + return 0; +} + +void dw_hdmi_qp_cec_set_hpd(struct dw_hdmi_qp *hdmi, bool plug_in, bool change) +{ + enum drm_connector_status status = plug_in ? + connector_status_connected : connector_status_disconnected; + + if (!plug_in) + cec_notifier_set_phys_addr(hdmi->cec_notifier, + CEC_PHYS_ADDR_INVALID); + + if (hdmi->bridge.dev) { + if (change && hdmi->cec_adap && hdmi->cec_adap->devnode.registered) + cec_queue_pin_hpd_event(hdmi->cec_adap, plug_in, ktime_get()); + drm_bridge_hpd_notify(&hdmi->bridge, status); + } +} +EXPORT_SYMBOL_GPL(dw_hdmi_qp_cec_set_hpd); + +// static void dw_hdmi_qp_cec_enable(struct dw_hdmi_qp *hdmi) +// { +// mutex_lock(&hdmi->mutex); +// hdmi_modb(hdmi, 0, CEC_SWDISABLE, GLOBAL_SWDISABLE); +// mutex_unlock(&hdmi->mutex); +// } +// +// static void dw_hdmi_qp_cec_disable(struct dw_hdmi_qp *hdmi) +// { +// mutex_lock(&hdmi->mutex); +// hdmi_modb(hdmi, CEC_SWDISABLE, CEC_SWDISABLE, GLOBAL_SWDISABLE); +// mutex_unlock(&hdmi->mutex); +// } +// +// static const struct dw_hdmi_qp_cec_ops dw_hdmi_qp_cec_ops = { +// .enable = dw_hdmi_qp_cec_enable, +// .disable = dw_hdmi_qp_cec_disable, +// .write = hdmi_writel, +// .read = hdmi_readl, +// }; + +static const struct regmap_config hdmi_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = EARCRX_1_INT_FORCE, +}; + +struct dw_hdmi_qp_reg_table { + int reg_base; + int reg_end; +}; + +static const struct dw_hdmi_qp_reg_table hdmi_reg_table[] = { + {0x0, 0xc}, + {0x14, 0x1c}, + {0x44, 0x48}, + {0x50, 0x58}, + {0x80, 0x84}, + {0xa0, 0xc4}, + {0xe0, 0xe8}, + {0xf0, 0x118}, + {0x140, 0x140}, + {0x150, 0x150}, + {0x160, 0x168}, + {0x180, 0x180}, + {0x800, 0x800}, + {0x808, 0x808}, + {0x814, 0x814}, + {0x81c, 0x824}, + {0x834, 0x834}, + {0x840, 0x864}, + {0x86c, 0x86c}, + {0x880, 0x89c}, + {0x8e0, 0x8e8}, + {0x900, 0x900}, + {0x908, 0x90c}, + {0x920, 0x938}, + {0x920, 0x938}, + {0x960, 0x960}, + {0x968, 0x968}, + {0xa20, 0xa20}, + {0xa30, 0xa30}, + {0xa40, 0xa40}, + {0xa54, 0xa54}, + {0xa80, 0xaac}, + {0xab4, 0xab8}, + {0xb00, 0xcbc}, + {0xce0, 0xce0}, + {0xd00, 0xddc}, + {0xe20, 0xe24}, + {0xe40, 0xe44}, + {0xe4c, 0xe4c}, + {0xe60, 0xe80}, + {0xea0, 0xf24}, + {0x1004, 0x100c}, + {0x1020, 0x1030}, + {0x1040, 0x1050}, + {0x1060, 0x1068}, + {0x1800, 0x1820}, + {0x182c, 0x182c}, + {0x1840, 0x1940}, + {0x1960, 0x1a60}, + {0x1b00, 0x1b00}, + {0x1c00, 0x1c00}, + {0x3000, 0x3000}, + {0x3010, 0x3014}, + {0x3020, 0x3024}, + {0x3800, 0x3800}, + {0x3810, 0x3814}, + {0x3820, 0x3824}, + {0x3830, 0x3834}, + {0x3840, 0x3844}, + {0x3850, 0x3854}, + {0x3860, 0x3864}, + {0x3870, 0x3874}, + {0x4000, 0x4004}, + {0x4800, 0x4800}, + {0x4810, 0x4814}, +}; + +static int dw_hdmi_ctrl_show(struct seq_file *s, void *v) +{ + struct dw_hdmi_qp *hdmi = s->private; + u32 i = 0, j = 0, val = 0; + + seq_puts(s, "\n---------------------------------------------------"); + + for (i = 0; i < ARRAY_SIZE(hdmi_reg_table); i++) { + for (j = hdmi_reg_table[i].reg_base; + j <= hdmi_reg_table[i].reg_end; j += 4) { + val = hdmi_readl(hdmi, j); + + if ((j - hdmi_reg_table[i].reg_base) % 16 == 0) + seq_printf(s, "\n>>>hdmi_ctl %04x:", j); + seq_printf(s, " %08x", val); + } + } + seq_puts(s, "\n---------------------------------------------------\n"); + + return 0; +} + +static int dw_hdmi_ctrl_open(struct inode *inode, struct file *file) +{ + return single_open(file, dw_hdmi_ctrl_show, inode->i_private); +} + +static ssize_t +dw_hdmi_ctrl_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct dw_hdmi_qp *hdmi = + ((struct seq_file *)file->private_data)->private; + u32 reg, val; + char kbuf[25]; + + if (count > 24) { + dev_err(hdmi->dev, "out of buf range\n"); + return count; + } + + if (copy_from_user(kbuf, buf, count)) + return -EFAULT; + kbuf[count - 1] = '\0'; + + if (sscanf(kbuf, "%x %x", ®, &val) == -1) + return -EFAULT; + if (reg > EARCRX_1_INT_FORCE) { + dev_err(hdmi->dev, "it is no a hdmi register\n"); + return count; + } + dev_info(hdmi->dev, "/**********hdmi register config******/"); + dev_info(hdmi->dev, "\n reg=%x val=%x\n", reg, val); + hdmi_writel(hdmi, val, reg); + return count; +} + +static const struct file_operations dw_hdmi_ctrl_fops = { + .owner = THIS_MODULE, + .open = dw_hdmi_ctrl_open, + .read = seq_read, + .write = dw_hdmi_ctrl_write, + .llseek = seq_lseek, + .release = single_release, +}; + +static int dw_hdmi_status_show(struct seq_file *s, void *v) +{ + struct dw_hdmi_qp *hdmi = s->private; + u32 val; + + seq_puts(s, "PHY: "); + if (hdmi->disabled) { + seq_puts(s, "disabled\n"); + return 0; + } + seq_puts(s, "enabled\t\t\tMode: "); + if (hdmi->sink_is_hdmi) + seq_puts(s, "HDMI\n"); + else + seq_puts(s, "DVI\n"); + + if (hdmi->hdmi_data.video_mode.mpixelclock > 600000000) { + seq_printf(s, "FRL Mode Pixel Clk: %luHz\n", + hdmi->hdmi_data.video_mode.mpixelclock); + } else { + if (hdmi->hdmi_data.video_mode.mtmdsclock > 340000000) + val = hdmi->hdmi_data.video_mode.mtmdsclock / 4; + else + val = hdmi->hdmi_data.video_mode.mtmdsclock; + seq_printf(s, "TMDS Mode Pixel Clk: %luHz\t\tTMDS Clk: %uHz\n", + hdmi->hdmi_data.video_mode.mpixelclock, val); + } + + seq_puts(s, "Color Format: "); + if (hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format)) + seq_puts(s, "RGB"); + else if (hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_out_bus_format)) + seq_puts(s, "YUV444"); + else if (hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_out_bus_format)) + seq_puts(s, "YUV422"); + else if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format)) + seq_puts(s, "YUV420"); + else + seq_puts(s, "UNKNOWN"); + val = hdmi_bus_fmt_color_depth(hdmi->hdmi_data.enc_out_bus_format); + seq_printf(s, "\t\tColor Depth: %d bit\n", val); + seq_puts(s, "Colorimetry: "); + switch (hdmi->hdmi_data.enc_out_encoding) { + case V4L2_YCBCR_ENC_601: + seq_puts(s, "ITU.BT601"); + break; + case V4L2_YCBCR_ENC_709: + seq_puts(s, "ITU.BT709"); + break; + case V4L2_YCBCR_ENC_BT2020: + seq_puts(s, "ITU.BT2020"); + break; + default: /* Carries no data */ + seq_puts(s, "ITU.BT601"); + break; + } + + seq_puts(s, "\t\tEOTF: "); + + val = hdmi_readl(hdmi, PKTSCHED_PKT_EN); + if (!(val & PKTSCHED_DRMI_TX_EN)) { + seq_puts(s, "Off\n"); + return 0; + } + + val = hdmi_readl(hdmi, PKT_DRMI_CONTENTS1); + val = (val >> 8) & 0x7; + switch (val) { + case HDMI_EOTF_TRADITIONAL_GAMMA_SDR: + seq_puts(s, "SDR"); + break; + case HDMI_EOTF_TRADITIONAL_GAMMA_HDR: + seq_puts(s, "HDR"); + break; + case HDMI_EOTF_SMPTE_ST2084: + seq_puts(s, "ST2084"); + break; + case HDMI_EOTF_BT_2100_HLG: + seq_puts(s, "HLG"); + break; + default: + seq_puts(s, "Not Defined\n"); + return 0; + } + + val = hdmi_readl(hdmi, PKT_DRMI_CONTENTS1); + val = (val >> 16) & 0xffff; + seq_printf(s, "\nx0: %d", val); + val = hdmi_readl(hdmi, PKT_DRMI_CONTENTS2); + val = val & 0xffff; + seq_printf(s, "\t\t\t\ty0: %d\n", val); + val = hdmi_readl(hdmi, PKT_DRMI_CONTENTS2); + val = (val >> 16) & 0xffff; + seq_printf(s, "x1: %d", val); + val = hdmi_readl(hdmi, PKT_DRMI_CONTENTS3); + val = val & 0xffff; + seq_printf(s, "\t\t\t\ty1: %d\n", val); + val = hdmi_readl(hdmi, PKT_DRMI_CONTENTS3); + val = (val >> 16) & 0xffff; + seq_printf(s, "x2: %d", val); + val = hdmi_readl(hdmi, PKT_DRMI_CONTENTS4); + val = val & 0xffff; + seq_printf(s, "\t\t\t\ty2: %d\n", val); + val = hdmi_readl(hdmi, PKT_DRMI_CONTENTS4); + val = (val >> 16) & 0xffff; + seq_printf(s, "white x: %d", val); + val = hdmi_readl(hdmi, PKT_DRMI_CONTENTS5); + val = val & 0xffff; + seq_printf(s, "\t\t\twhite y: %d\n", val); + val = hdmi_readl(hdmi, PKT_DRMI_CONTENTS5); + val = (val >> 16) & 0xffff; + seq_printf(s, "max lum: %d", val); + val = hdmi_readl(hdmi, PKT_DRMI_CONTENTS6); + val = val & 0xffff; + seq_printf(s, "\t\t\tmin lum: %d\n", val); + val = hdmi_readl(hdmi, PKT_DRMI_CONTENTS6); + val = (val >> 16) & 0xffff; + seq_printf(s, "max cll: %d", val); + val = hdmi_readl(hdmi, PKT_DRMI_CONTENTS7); + val = val & 0xffff; + seq_printf(s, "\t\t\tmax fall: %d\n", val); + return 0; +} + +static int dw_hdmi_status_open(struct inode *inode, struct file *file) +{ + return single_open(file, dw_hdmi_status_show, inode->i_private); +} + +static const struct file_operations dw_hdmi_status_fops = { + .owner = THIS_MODULE, + .open = dw_hdmi_status_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void dw_hdmi_register_debugfs(struct device *dev, struct dw_hdmi_qp *hdmi) +{ + u8 buf[11]; + + snprintf(buf, sizeof(buf), "dw-hdmi%d", hdmi->plat_data->id); + hdmi->debugfs_dir = debugfs_create_dir(buf, NULL); + if (IS_ERR(hdmi->debugfs_dir)) { + dev_err(dev, "failed to create debugfs dir!\n"); + return; + } + + debugfs_create_file("status", 0400, hdmi->debugfs_dir, + hdmi, &dw_hdmi_status_fops); + debugfs_create_file("ctrl", 0600, hdmi->debugfs_dir, + hdmi, &dw_hdmi_ctrl_fops); +} + +static struct dw_hdmi_qp * +__dw_hdmi_probe(struct platform_device *pdev, + const struct dw_hdmi_plat_data *plat_data) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *ddc_node; + struct dw_hdmi_qp *hdmi; + // struct dw_hdmi_qp_i2s_audio_data audio; + // struct platform_device_info pdevinfo; + // struct dw_hdmi_qp_cec_data cec; + struct resource *iores = NULL; + int irq; + int ret; + + hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); + if (!hdmi) + return ERR_PTR(-ENOMEM); + + hdmi->connector.stereo_allowed = 1; + hdmi->plat_data = plat_data; + hdmi->dev = dev; + hdmi->sample_rate = 48000; + hdmi->disabled = true; + + mutex_init(&hdmi->mutex); + mutex_init(&hdmi->audio_mutex); + mutex_init(&hdmi->cec_notifier_mutex); + + ddc_node = of_parse_phandle(np, "ddc-i2c-bus", 0); + if (ddc_node) { + hdmi->ddc = of_get_i2c_adapter_by_node(ddc_node); + of_node_put(ddc_node); + if (!hdmi->ddc) { + dev_dbg(hdmi->dev, "failed to read ddc node\n"); + return ERR_PTR(-EPROBE_DEFER); + } + + } else { + dev_dbg(hdmi->dev, "no ddc property found\n"); + } + + if (!plat_data->regm) { + const struct regmap_config *reg_config; + + reg_config = &hdmi_regmap_config; + + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hdmi->regs = devm_ioremap_resource(dev, iores); + if (IS_ERR(hdmi->regs)) { + ret = PTR_ERR(hdmi->regs); + goto err_res; + } + + hdmi->regm = devm_regmap_init_mmio(dev, hdmi->regs, reg_config); + if (IS_ERR(hdmi->regm)) { + dev_err(dev, "Failed to configure regmap\n"); + ret = PTR_ERR(hdmi->regm); + goto err_res; + } + } else { + hdmi->regm = plat_data->regm; + } + + ret = dw_hdmi_detect_phy(hdmi); + if (ret < 0) + goto err_res; + + hdmi_writel(hdmi, 0, MAINUNIT_0_INT_MASK_N); + hdmi_writel(hdmi, 0, MAINUNIT_1_INT_MASK_N); + hdmi_writel(hdmi, 428571429, TIMER_BASE_CONFIG0); + if ((hdmi_readl(hdmi, CMU_STATUS) & DISPLAY_CLK_MONITOR) == DISPLAY_CLK_LOCKED) { + hdmi->initialized = true; + hdmi->disabled = false; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = irq; + goto err_res; + } + + hdmi->avp_irq = irq; + ret = devm_request_threaded_irq(dev, hdmi->avp_irq, + dw_hdmi_qp_avp_hardirq, + dw_hdmi_qp_avp_irq, IRQF_SHARED, + dev_name(dev), hdmi); + if (ret) + goto err_res; + + irq = platform_get_irq(pdev, 1); + if (irq < 0) { + ret = irq; + goto err_res; + } + + // cec.irq = irq; + + irq = platform_get_irq(pdev, 2); + if (irq < 0) { + ret = irq; + goto err_res; + } + + hdmi->earc_irq = irq; + ret = devm_request_threaded_irq(dev, hdmi->earc_irq, + dw_hdmi_qp_earc_hardirq, + dw_hdmi_qp_earc_irq, IRQF_SHARED, + dev_name(dev), hdmi); + if (ret) + goto err_res; + + irq = platform_get_irq(pdev, 3); + if (irq < 0) { + ret = irq; + goto err_res; + } + + hdmi->main_irq = irq; + ret = devm_request_threaded_irq(dev, hdmi->main_irq, + dw_hdmi_qp_main_hardirq, NULL, + IRQF_SHARED, dev_name(dev), hdmi); + if (ret) + goto err_res; + + hdmi_init_clk_regenerator(hdmi); + + /* If DDC bus is not specified, try to register HDMI I2C bus */ + if (!hdmi->ddc) { + hdmi->ddc = dw_hdmi_i2c_adapter(hdmi); + if (IS_ERR(hdmi->ddc)) + hdmi->ddc = NULL; + /* + * Read high and low time from device tree. If not available use + * the default timing scl clock rate is about 99.6KHz. + */ + if (of_property_read_u32(np, "ddc-i2c-scl-high-time-ns", + &hdmi->i2c->scl_high_ns)) + hdmi->i2c->scl_high_ns = 4708; + if (of_property_read_u32(np, "ddc-i2c-scl-low-time-ns", + &hdmi->i2c->scl_low_ns)) + hdmi->i2c->scl_low_ns = 4916; + } + + hdmi->bridge.driver_private = hdmi; + hdmi->bridge.funcs = &dw_hdmi_bridge_funcs; +#ifdef CONFIG_OF + hdmi->bridge.of_node = pdev->dev.of_node; +#endif + + if (hdmi->phy.ops->setup_hpd) + hdmi->phy.ops->setup_hpd(hdmi, hdmi->phy.data); + + hdmi->connector.ycbcr_420_allowed = hdmi->plat_data->ycbcr_420_allowed; + + // audio.hdmi = hdmi; + // audio.eld = hdmi->connector.eld; + // audio.write = hdmi_writel; + // audio.read = hdmi_readl; + // audio.mod = hdmi_modb; + // hdmi->enable_audio = dw_hdmi_i2s_audio_enable; + // hdmi->disable_audio = dw_hdmi_i2s_audio_disable; + + // memset(&pdevinfo, 0, sizeof(pdevinfo)); + // pdevinfo.parent = dev; + // pdevinfo.id = PLATFORM_DEVID_AUTO; + // pdevinfo.name = "dw-hdmi-qp-i2s-audio"; + // pdevinfo.data = &audio; + // pdevinfo.size_data = sizeof(audio); + // pdevinfo.dma_mask = DMA_BIT_MASK(32); + // hdmi->audio = platform_device_register_full(&pdevinfo); + + hdmi->extcon = devm_extcon_dev_allocate(hdmi->dev, dw_hdmi_cable); + if (IS_ERR(hdmi->extcon)) { + dev_err(hdmi->dev, "allocate extcon failed\n"); + ret = PTR_ERR(hdmi->extcon); + goto err_res; + } + + ret = devm_extcon_dev_register(hdmi->dev, hdmi->extcon); + if (ret) { + dev_err(hdmi->dev, "failed to register extcon: %d\n", ret); + goto err_res; + } + + ret = extcon_set_property_capability(hdmi->extcon, EXTCON_DISP_HDMI, + EXTCON_PROP_DISP_HPD); + if (ret) { + dev_err(hdmi->dev, + "failed to set USB property capability: %d\n", ret); + goto err_res; + } + + // cec.hdmi = hdmi; + // cec.ops = &dw_hdmi_qp_cec_ops; + // pdevinfo.name = "dw-hdmi-qp-cec"; + // pdevinfo.data = &cec; + // pdevinfo.size_data = sizeof(cec); + // pdevinfo.dma_mask = 0; + // hdmi->cec = platform_device_register_full(&pdevinfo); + + /* Reset HDMI DDC I2C master controller and mute I2CM interrupts */ + if (hdmi->i2c) + dw_hdmi_i2c_init(hdmi); + + init_completion(&hdmi->flt_cmp); + init_completion(&hdmi->earc_cmp); + + if (of_property_read_bool(np, "scramble-low-rates")) + hdmi->scramble_low_rates = true; + + dw_hdmi_register_debugfs(dev, hdmi); + + return hdmi; + +err_res: + if (hdmi->i2c) + i2c_del_adapter(&hdmi->i2c->adap); + else + i2c_put_adapter(hdmi->ddc); + + return ERR_PTR(ret); +} + +static void __dw_hdmi_remove(struct dw_hdmi_qp *hdmi) +{ + if (hdmi->avp_irq) + disable_irq(hdmi->avp_irq); + + if (hdmi->main_irq) + disable_irq(hdmi->main_irq); + + if (hdmi->earc_irq) + disable_irq(hdmi->earc_irq); + + debugfs_remove_recursive(hdmi->debugfs_dir); + + if (!hdmi->plat_data->first_screen) { + dw_hdmi_destroy_properties(hdmi); + hdmi->connector.funcs->destroy(&hdmi->connector); + } + + if (hdmi->audio && !IS_ERR(hdmi->audio)) + platform_device_unregister(hdmi->audio); + + // [CC:] dw_hdmi_rockchip_unbind() also calls drm_encoder_cleanup() + // and causes a seg fault due to NULL ptr dererence + // if (hdmi->bridge.encoder && !hdmi->plat_data->first_screen) + // hdmi->bridge.encoder->funcs->destroy(hdmi->bridge.encoder); + // + if (!IS_ERR(hdmi->cec)) + platform_device_unregister(hdmi->cec); + if (hdmi->i2c) + i2c_del_adapter(&hdmi->i2c->adap); + else + i2c_put_adapter(hdmi->ddc); +} + +/* ----------------------------------------------------------------------------- + * Bind/unbind API, used from platforms based on the component framework. + */ +struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev, + struct drm_encoder *encoder, + struct dw_hdmi_plat_data *plat_data) +{ + struct dw_hdmi_qp *hdmi; + int ret; + + hdmi = __dw_hdmi_probe(pdev, plat_data); + if (IS_ERR(hdmi)) + return hdmi; + + if (!plat_data->first_screen) { + ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL, 0); + if (ret) { + __dw_hdmi_remove(hdmi); + dev_err(hdmi->dev, "Failed to initialize bridge with drm\n"); + return ERR_PTR(ret); + } + + plat_data->connector = &hdmi->connector; + } + + if (plat_data->split_mode && !hdmi->plat_data->first_screen) { + struct dw_hdmi_qp *secondary = NULL; + + if (hdmi->plat_data->left) + secondary = hdmi->plat_data->left; + else if (hdmi->plat_data->right) + secondary = hdmi->plat_data->right; + + if (!secondary) + return ERR_PTR(-ENOMEM); + ret = drm_bridge_attach(encoder, &secondary->bridge, &hdmi->bridge, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) + return ERR_PTR(ret); + } + + return hdmi; +} +EXPORT_SYMBOL_GPL(dw_hdmi_qp_bind); + +void dw_hdmi_qp_unbind(struct dw_hdmi_qp *hdmi) +{ + __dw_hdmi_remove(hdmi); +} +EXPORT_SYMBOL_GPL(dw_hdmi_qp_unbind); + +void dw_hdmi_qp_suspend(struct device *dev, struct dw_hdmi_qp *hdmi) +{ + if (!hdmi) { + dev_warn(dev, "Hdmi has not been initialized\n"); + return; + } + + mutex_lock(&hdmi->mutex); + + /* + * When system shutdown, hdmi should be disabled. + * When system suspend, dw_hdmi_qp_bridge_disable will disable hdmi first. + * To prevent duplicate operation, we should determine whether hdmi + * has been disabled. + */ + if (!hdmi->disabled) + hdmi->disabled = true; + mutex_unlock(&hdmi->mutex); + + if (hdmi->avp_irq) + disable_irq(hdmi->avp_irq); + + if (hdmi->main_irq) + disable_irq(hdmi->main_irq); + + if (hdmi->earc_irq) + disable_irq(hdmi->earc_irq); + + pinctrl_pm_select_sleep_state(dev); +} +EXPORT_SYMBOL_GPL(dw_hdmi_qp_suspend); + +void dw_hdmi_qp_resume(struct device *dev, struct dw_hdmi_qp *hdmi) +{ + if (!hdmi) { + dev_warn(dev, "Hdmi has not been initialized\n"); + return; + } + + hdmi_writel(hdmi, 0, MAINUNIT_0_INT_MASK_N); + hdmi_writel(hdmi, 0, MAINUNIT_1_INT_MASK_N); + hdmi_writel(hdmi, 428571429, TIMER_BASE_CONFIG0); + + pinctrl_pm_select_default_state(dev); + + hdmi->cec_adap->ops->adap_enable(hdmi->cec_adap, true); + + mutex_lock(&hdmi->mutex); + if (hdmi->i2c) + dw_hdmi_i2c_init(hdmi); + if (hdmi->avp_irq) + enable_irq(hdmi->avp_irq); + + if (hdmi->main_irq) + enable_irq(hdmi->main_irq); + + if (hdmi->earc_irq) + enable_irq(hdmi->earc_irq); + + mutex_unlock(&hdmi->mutex); +} +EXPORT_SYMBOL_GPL(dw_hdmi_qp_resume); + +MODULE_AUTHOR("Algea Cao "); +MODULE_DESCRIPTION("DW HDMI QP transmitter driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:dw-hdmi-qp"); diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h new file mode 100644 index 000000000..4cac70f2d --- /dev/null +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h @@ -0,0 +1,831 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) Rockchip Electronics Co.Ltd + * Author: + * Algea Cao + */ +#ifndef __DW_HDMI_QP_H__ +#define __DW_HDMI_QP_H__ +/* Main Unit Registers */ +#define CORE_ID 0x0 +#define VER_NUMBER 0x4 +#define VER_TYPE 0x8 +#define CONFIG_REG 0xc +#define CONFIG_CEC BIT(28) +#define CONFIG_AUD_UD BIT(23) +#define CORE_TIMESTAMP_HHMM 0x14 +#define CORE_TIMESTAMP_MMDD 0x18 +#define CORE_TIMESTAMP_YYYY 0x1c +/* Reset Manager Registers */ +#define GLOBAL_SWRESET_REQUEST 0x40 +#define EARCRX_CMDC_SWINIT_P BIT(27) +#define AVP_DATAPATH_PACKET_AUDIO_SWINIT_P BIT(10) +#define GLOBAL_SWDISABLE 0x44 +#define CEC_SWDISABLE BIT(17) +#define AVP_DATAPATH_PACKET_AUDIO_SWDISABLE BIT(10) +#define AVP_DATAPATH_VIDEO_SWDISABLE BIT(6) +#define RESET_MANAGER_CONFIG0 0x48 +#define RESET_MANAGER_STATUS0 0x50 +#define RESET_MANAGER_STATUS1 0x54 +#define RESET_MANAGER_STATUS2 0x58 +/* Timer Base Registers */ +#define TIMER_BASE_CONFIG0 0x80 +#define TIMER_BASE_STATUS0 0x84 +/* CMU Registers */ +#define CMU_CONFIG0 0xa0 +#define CMU_CONFIG1 0xa4 +#define CMU_CONFIG2 0xa8 +#define CMU_CONFIG3 0xac +#define CMU_STATUS 0xb0 +#define DISPLAY_CLK_MONITOR 0x3f +#define DISPLAY_CLK_LOCKED 0X15 +#define EARC_BPCLK_OFF BIT(9) +#define AUDCLK_OFF BIT(7) +#define LINKQPCLK_OFF BIT(5) +#define VIDQPCLK_OFF BIT(3) +#define IPI_CLK_OFF BIT(1) +#define CMU_IPI_CLK_FREQ 0xb4 +#define CMU_VIDQPCLK_FREQ 0xb8 +#define CMU_LINKQPCLK_FREQ 0xbc +#define CMU_AUDQPCLK_FREQ 0xc0 +#define CMU_EARC_BPCLK_FREQ 0xc4 +/* I2CM Registers */ +#define I2CM_SM_SCL_CONFIG0 0xe0 +#define I2CM_FM_SCL_CONFIG0 0xe4 +#define I2CM_CONFIG0 0xe8 +#define I2CM_CONTROL0 0xec +#define I2CM_STATUS0 0xf0 +#define I2CM_INTERFACE_CONTROL0 0xf4 +#define I2CM_ADDR 0xff000 +#define I2CM_SLVADDR 0xfe0 +#define I2CM_WR_MASK 0x1e +#define I2CM_EXT_READ BIT(4) +#define I2CM_SHORT_READ BIT(3) +#define I2CM_FM_READ BIT(2) +#define I2CM_FM_WRITE BIT(1) +#define I2CM_FM_EN BIT(0) +#define I2CM_INTERFACE_CONTROL1 0xf8 +#define I2CM_SEG_PTR 0x7f80 +#define I2CM_SEG_ADDR 0x7f +#define I2CM_INTERFACE_WRDATA_0_3 0xfc +#define I2CM_INTERFACE_WRDATA_4_7 0x100 +#define I2CM_INTERFACE_WRDATA_8_11 0x104 +#define I2CM_INTERFACE_WRDATA_12_15 0x108 +#define I2CM_INTERFACE_RDDATA_0_3 0x10c +#define I2CM_INTERFACE_RDDATA_4_7 0x110 +#define I2CM_INTERFACE_RDDATA_8_11 0x114 +#define I2CM_INTERFACE_RDDATA_12_15 0x118 +/* SCDC Registers */ +#define SCDC_CONFIG0 0x140 +#define SCDC_I2C_FM_EN BIT(12) +#define SCDC_UPD_FLAGS_AUTO_CLR BIT(6) +#define SCDC_UPD_FLAGS_POLL_EN BIT(4) +#define SCDC_CONTROL0 0x148 +#define SCDC_STATUS0 0x150 +#define STATUS_UPDATE BIT(0) +#define FRL_START BIT(4) +#define FLT_UPDATE BIT(5) +/* FLT Registers */ +#define FLT_CONFIG0 0x160 +#define FLT_CONFIG1 0x164 +#define FLT_CONFIG2 0x168 +#define FLT_CONTROL0 0x170 +/* Main Unit 2 Registers */ +#define MAINUNIT_STATUS0 0x180 +/* Video Interface Registers */ +#define VIDEO_INTERFACE_CONFIG0 0x800 +#define VIDEO_INTERFACE_CONFIG1 0x804 +#define VIDEO_INTERFACE_CONFIG2 0x808 +#define VIDEO_INTERFACE_CONTROL0 0x80c +#define VIDEO_INTERFACE_STATUS0 0x814 +/* Video Packing Registers */ +#define VIDEO_PACKING_CONFIG0 0x81c +/* Audio Interface Registers */ +#define AUDIO_INTERFACE_CONFIG0 0x820 +#define AUD_IF_SEL_MSK 0x3 +#define AUD_IF_SPDIF 0x2 +#define AUD_IF_I2S 0x1 +#define AUD_IF_PAI 0x0 +#define AUD_FIFO_INIT_ON_OVF_MSK BIT(2) +#define AUD_FIFO_INIT_ON_OVF_EN BIT(2) +#define I2S_LINES_EN_MSK GENMASK(7, 4) +#define I2S_LINES_EN(x) BIT(x + 4) +#define I2S_BPCUV_RCV_MSK BIT(12) +#define I2S_BPCUV_RCV_EN BIT(12) +#define I2S_BPCUV_RCV_DIS 0 +#define SPDIF_LINES_EN GENMASK(19, 16) +#define AUD_FORMAT_MSK GENMASK(26, 24) +#define AUD_3DOBA (0x7 << 24) +#define AUD_3DASP (0x6 << 24) +#define AUD_MSOBA (0x5 << 24) +#define AUD_MSASP (0x4 << 24) +#define AUD_HBR (0x3 << 24) +#define AUD_DST (0x2 << 24) +#define AUD_OBA (0x1 << 24) +#define AUD_ASP (0x0 << 24) +#define AUDIO_INTERFACE_CONFIG1 0x824 +#define AUDIO_INTERFACE_CONTROL0 0x82c +#define AUDIO_FIFO_CLR_P BIT(0) +#define AUDIO_INTERFACE_STATUS0 0x834 +/* Frame Composer Registers */ +#define FRAME_COMPOSER_CONFIG0 0x840 +#define FRAME_COMPOSER_CONFIG1 0x844 +#define FRAME_COMPOSER_CONFIG2 0x848 +#define FRAME_COMPOSER_CONFIG3 0x84c +#define FRAME_COMPOSER_CONFIG4 0x850 +#define FRAME_COMPOSER_CONFIG5 0x854 +#define FRAME_COMPOSER_CONFIG6 0x858 +#define FRAME_COMPOSER_CONFIG7 0x85c +#define FRAME_COMPOSER_CONFIG8 0x860 +#define FRAME_COMPOSER_CONFIG9 0x864 +#define FRAME_COMPOSER_CONTROL0 0x86c +/* Video Monitor Registers */ +#define VIDEO_MONITOR_CONFIG0 0x880 +#define VIDEO_MONITOR_STATUS0 0x884 +#define VIDEO_MONITOR_STATUS1 0x888 +#define VIDEO_MONITOR_STATUS2 0x88c +#define VIDEO_MONITOR_STATUS3 0x890 +#define VIDEO_MONITOR_STATUS4 0x894 +#define VIDEO_MONITOR_STATUS5 0x898 +#define VIDEO_MONITOR_STATUS6 0x89c +/* HDCP2 Logic Registers */ +#define HDCP2LOGIC_CONFIG0 0x8e0 +#define HDCP2_BYPASS BIT(0) +#define HDCP2LOGIC_ESM_GPIO_IN 0x8e4 +#define HDCP2LOGIC_ESM_GPIO_OUT 0x8e8 +/* HDCP14 Registers */ +#define HDCP14_CONFIG0 0x900 +#define HDCP14_CONFIG1 0x904 +#define HDCP14_CONFIG2 0x908 +#define HDCP14_CONFIG3 0x90c +#define HDCP14_KEY_SEED 0x914 +#define HDCP14_KEY_H 0x918 +#define HDCP14_KEY_L 0x91c +#define HDCP14_KEY_STATUS 0x920 +#define HDCP14_AKSV_H 0x924 +#define HDCP14_AKSV_L 0x928 +#define HDCP14_AN_H 0x92c +#define HDCP14_AN_L 0x930 +#define HDCP14_STATUS0 0x934 +#define HDCP14_STATUS1 0x938 +/* Scrambler Registers */ +#define SCRAMB_CONFIG0 0x960 +/* Video Configuration Registers */ +#define LINK_CONFIG0 0x968 +#define OPMODE_FRL_4LANES BIT(8) +#define OPMODE_DVI BIT(4) +#define OPMODE_FRL BIT(0) +/* TMDS FIFO Registers */ +#define TMDS_FIFO_CONFIG0 0x970 +#define TMDS_FIFO_CONTROL0 0x974 +/* FRL RSFEC Registers */ +#define FRL_RSFEC_CONFIG0 0xa20 +#define FRL_RSFEC_STATUS0 0xa30 +/* FRL Packetizer Registers */ +#define FRL_PKTZ_CONFIG0 0xa40 +#define FRL_PKTZ_CONTROL0 0xa44 +#define FRL_PKTZ_CONTROL1 0xa50 +#define FRL_PKTZ_STATUS1 0xa54 +/* Packet Scheduler Registers */ +#define PKTSCHED_CONFIG0 0xa80 +#define PKTSCHED_PRQUEUE0_CONFIG0 0xa84 +#define PKTSCHED_PRQUEUE1_CONFIG0 0xa88 +#define PKTSCHED_PRQUEUE2_CONFIG0 0xa8c +#define PKTSCHED_PRQUEUE2_CONFIG1 0xa90 +#define PKTSCHED_PRQUEUE2_CONFIG2 0xa94 +#define PKTSCHED_PKT_CONFIG0 0xa98 +#define PKTSCHED_PKT_CONFIG1 0xa9c +#define PKTSCHED_DRMI_FIELDRATE BIT(13) +#define PKTSCHED_AVI_FIELDRATE BIT(12) +#define PKTSCHED_PKT_CONFIG2 0xaa0 +#define PKTSCHED_PKT_CONFIG3 0xaa4 +#define PKTSCHED_PKT_EN 0xaa8 +#define PKTSCHED_DRMI_TX_EN BIT(17) +#define PKTSCHED_AUDI_TX_EN BIT(15) +#define PKTSCHED_AVI_TX_EN BIT(13) +#define PKTSCHED_EMP_CVTEM_TX_EN BIT(10) +#define PKTSCHED_AMD_TX_EN BIT(8) +#define PKTSCHED_GCP_TX_EN BIT(3) +#define PKTSCHED_AUDS_TX_EN BIT(2) +#define PKTSCHED_ACR_TX_EN BIT(1) +#define PKTSCHED_NULL_TX_EN BIT(0) +#define PKTSCHED_PKT_CONTROL0 0xaac +#define PKTSCHED_PKT_SEND 0xab0 +#define PKTSCHED_PKT_STATUS0 0xab4 +#define PKTSCHED_PKT_STATUS1 0xab8 +#define PKT_NULL_CONTENTS0 0xb00 +#define PKT_NULL_CONTENTS1 0xb04 +#define PKT_NULL_CONTENTS2 0xb08 +#define PKT_NULL_CONTENTS3 0xb0c +#define PKT_NULL_CONTENTS4 0xb10 +#define PKT_NULL_CONTENTS5 0xb14 +#define PKT_NULL_CONTENTS6 0xb18 +#define PKT_NULL_CONTENTS7 0xb1c +#define PKT_ACP_CONTENTS0 0xb20 +#define PKT_ACP_CONTENTS1 0xb24 +#define PKT_ACP_CONTENTS2 0xb28 +#define PKT_ACP_CONTENTS3 0xb2c +#define PKT_ACP_CONTENTS4 0xb30 +#define PKT_ACP_CONTENTS5 0xb34 +#define PKT_ACP_CONTENTS6 0xb38 +#define PKT_ACP_CONTENTS7 0xb3c +#define PKT_ISRC1_CONTENTS0 0xb40 +#define PKT_ISRC1_CONTENTS1 0xb44 +#define PKT_ISRC1_CONTENTS2 0xb48 +#define PKT_ISRC1_CONTENTS3 0xb4c +#define PKT_ISRC1_CONTENTS4 0xb50 +#define PKT_ISRC1_CONTENTS5 0xb54 +#define PKT_ISRC1_CONTENTS6 0xb58 +#define PKT_ISRC1_CONTENTS7 0xb5c +#define PKT_ISRC2_CONTENTS0 0xb60 +#define PKT_ISRC2_CONTENTS1 0xb64 +#define PKT_ISRC2_CONTENTS2 0xb68 +#define PKT_ISRC2_CONTENTS3 0xb6c +#define PKT_ISRC2_CONTENTS4 0xb70 +#define PKT_ISRC2_CONTENTS5 0xb74 +#define PKT_ISRC2_CONTENTS6 0xb78 +#define PKT_ISRC2_CONTENTS7 0xb7c +#define PKT_GMD_CONTENTS0 0xb80 +#define PKT_GMD_CONTENTS1 0xb84 +#define PKT_GMD_CONTENTS2 0xb88 +#define PKT_GMD_CONTENTS3 0xb8c +#define PKT_GMD_CONTENTS4 0xb90 +#define PKT_GMD_CONTENTS5 0xb94 +#define PKT_GMD_CONTENTS6 0xb98 +#define PKT_GMD_CONTENTS7 0xb9c +#define PKT_AMD_CONTENTS0 0xba0 +#define PKT_AMD_CONTENTS1 0xba4 +#define PKT_AMD_CONTENTS2 0xba8 +#define PKT_AMD_CONTENTS3 0xbac +#define PKT_AMD_CONTENTS4 0xbb0 +#define PKT_AMD_CONTENTS5 0xbb4 +#define PKT_AMD_CONTENTS6 0xbb8 +#define PKT_AMD_CONTENTS7 0xbbc +#define PKT_VSI_CONTENTS0 0xbc0 +#define PKT_VSI_CONTENTS1 0xbc4 +#define PKT_VSI_CONTENTS2 0xbc8 +#define PKT_VSI_CONTENTS3 0xbcc +#define PKT_VSI_CONTENTS4 0xbd0 +#define PKT_VSI_CONTENTS5 0xbd4 +#define PKT_VSI_CONTENTS6 0xbd8 +#define PKT_VSI_CONTENTS7 0xbdc +#define PKT_AVI_CONTENTS0 0xbe0 +#define HDMI_FC_AVICONF0_ACTIVE_FMT_INFO_PRESENT BIT(4) +#define HDMI_FC_AVICONF0_BAR_DATA_VERT_BAR 0x04 +#define HDMI_FC_AVICONF0_BAR_DATA_HORIZ_BAR 0x08 +#define HDMI_FC_AVICONF2_IT_CONTENT_VALID 0x80 +#define PKT_AVI_CONTENTS1 0xbe4 +#define PKT_AVI_CONTENTS2 0xbe8 +#define PKT_AVI_CONTENTS3 0xbec +#define PKT_AVI_CONTENTS4 0xbf0 +#define PKT_AVI_CONTENTS5 0xbf4 +#define PKT_AVI_CONTENTS6 0xbf8 +#define PKT_AVI_CONTENTS7 0xbfc +#define PKT_SPDI_CONTENTS0 0xc00 +#define PKT_SPDI_CONTENTS1 0xc04 +#define PKT_SPDI_CONTENTS2 0xc08 +#define PKT_SPDI_CONTENTS3 0xc0c +#define PKT_SPDI_CONTENTS4 0xc10 +#define PKT_SPDI_CONTENTS5 0xc14 +#define PKT_SPDI_CONTENTS6 0xc18 +#define PKT_SPDI_CONTENTS7 0xc1c +#define PKT_AUDI_CONTENTS0 0xc20 +#define PKT_AUDI_CONTENTS1 0xc24 +#define PKT_AUDI_CONTENTS2 0xc28 +#define PKT_AUDI_CONTENTS3 0xc2c +#define PKT_AUDI_CONTENTS4 0xc30 +#define PKT_AUDI_CONTENTS5 0xc34 +#define PKT_AUDI_CONTENTS6 0xc38 +#define PKT_AUDI_CONTENTS7 0xc3c +#define PKT_NVI_CONTENTS0 0xc40 +#define PKT_NVI_CONTENTS1 0xc44 +#define PKT_NVI_CONTENTS2 0xc48 +#define PKT_NVI_CONTENTS3 0xc4c +#define PKT_NVI_CONTENTS4 0xc50 +#define PKT_NVI_CONTENTS5 0xc54 +#define PKT_NVI_CONTENTS6 0xc58 +#define PKT_NVI_CONTENTS7 0xc5c +#define PKT_DRMI_CONTENTS0 0xc60 +#define PKT_DRMI_CONTENTS1 0xc64 +#define PKT_DRMI_CONTENTS2 0xc68 +#define PKT_DRMI_CONTENTS3 0xc6c +#define PKT_DRMI_CONTENTS4 0xc70 +#define PKT_DRMI_CONTENTS5 0xc74 +#define PKT_DRMI_CONTENTS6 0xc78 +#define PKT_DRMI_CONTENTS7 0xc7c +#define PKT_GHDMI1_CONTENTS0 0xc80 +#define PKT_GHDMI1_CONTENTS1 0xc84 +#define PKT_GHDMI1_CONTENTS2 0xc88 +#define PKT_GHDMI1_CONTENTS3 0xc8c +#define PKT_GHDMI1_CONTENTS4 0xc90 +#define PKT_GHDMI1_CONTENTS5 0xc94 +#define PKT_GHDMI1_CONTENTS6 0xc98 +#define PKT_GHDMI1_CONTENTS7 0xc9c +#define PKT_GHDMI2_CONTENTS0 0xca0 +#define PKT_GHDMI2_CONTENTS1 0xca4 +#define PKT_GHDMI2_CONTENTS2 0xca8 +#define PKT_GHDMI2_CONTENTS3 0xcac +#define PKT_GHDMI2_CONTENTS4 0xcb0 +#define PKT_GHDMI2_CONTENTS5 0xcb4 +#define PKT_GHDMI2_CONTENTS6 0xcb8 +#define PKT_GHDMI2_CONTENTS7 0xcbc +/* EMP Packetizer Registers */ +#define PKT_EMP_CONFIG0 0xce0 +#define PKT_EMP_CONTROL0 0xcec +#define PKT_EMP_CONTROL1 0xcf0 +#define PKT_EMP_CONTROL2 0xcf4 +#define PKT_EMP_VTEM_CONTENTS0 0xd00 +#define PKT_EMP_VTEM_CONTENTS1 0xd04 +#define PKT_EMP_VTEM_CONTENTS2 0xd08 +#define PKT_EMP_VTEM_CONTENTS3 0xd0c +#define PKT_EMP_VTEM_CONTENTS4 0xd10 +#define PKT_EMP_VTEM_CONTENTS5 0xd14 +#define PKT_EMP_VTEM_CONTENTS6 0xd18 +#define PKT_EMP_VTEM_CONTENTS7 0xd1c +#define PKT0_EMP_CVTEM_CONTENTS0 0xd20 +#define PKT0_EMP_CVTEM_CONTENTS1 0xd24 +#define PKT0_EMP_CVTEM_CONTENTS2 0xd28 +#define PKT0_EMP_CVTEM_CONTENTS3 0xd2c +#define PKT0_EMP_CVTEM_CONTENTS4 0xd30 +#define PKT0_EMP_CVTEM_CONTENTS5 0xd34 +#define PKT0_EMP_CVTEM_CONTENTS6 0xd38 +#define PKT0_EMP_CVTEM_CONTENTS7 0xd3c +#define PKT1_EMP_CVTEM_CONTENTS0 0xd40 +#define PKT1_EMP_CVTEM_CONTENTS1 0xd44 +#define PKT1_EMP_CVTEM_CONTENTS2 0xd48 +#define PKT1_EMP_CVTEM_CONTENTS3 0xd4c +#define PKT1_EMP_CVTEM_CONTENTS4 0xd50 +#define PKT1_EMP_CVTEM_CONTENTS5 0xd54 +#define PKT1_EMP_CVTEM_CONTENTS6 0xd58 +#define PKT1_EMP_CVTEM_CONTENTS7 0xd5c +#define PKT2_EMP_CVTEM_CONTENTS0 0xd60 +#define PKT2_EMP_CVTEM_CONTENTS1 0xd64 +#define PKT2_EMP_CVTEM_CONTENTS2 0xd68 +#define PKT2_EMP_CVTEM_CONTENTS3 0xd6c +#define PKT2_EMP_CVTEM_CONTENTS4 0xd70 +#define PKT2_EMP_CVTEM_CONTENTS5 0xd74 +#define PKT2_EMP_CVTEM_CONTENTS6 0xd78 +#define PKT2_EMP_CVTEM_CONTENTS7 0xd7c +#define PKT3_EMP_CVTEM_CONTENTS0 0xd80 +#define PKT3_EMP_CVTEM_CONTENTS1 0xd84 +#define PKT3_EMP_CVTEM_CONTENTS2 0xd88 +#define PKT3_EMP_CVTEM_CONTENTS3 0xd8c +#define PKT3_EMP_CVTEM_CONTENTS4 0xd90 +#define PKT3_EMP_CVTEM_CONTENTS5 0xd94 +#define PKT3_EMP_CVTEM_CONTENTS6 0xd98 +#define PKT3_EMP_CVTEM_CONTENTS7 0xd9c +#define PKT4_EMP_CVTEM_CONTENTS0 0xda0 +#define PKT4_EMP_CVTEM_CONTENTS1 0xda4 +#define PKT4_EMP_CVTEM_CONTENTS2 0xda8 +#define PKT4_EMP_CVTEM_CONTENTS3 0xdac +#define PKT4_EMP_CVTEM_CONTENTS4 0xdb0 +#define PKT4_EMP_CVTEM_CONTENTS5 0xdb4 +#define PKT4_EMP_CVTEM_CONTENTS6 0xdb8 +#define PKT4_EMP_CVTEM_CONTENTS7 0xdbc +#define PKT5_EMP_CVTEM_CONTENTS0 0xdc0 +#define PKT5_EMP_CVTEM_CONTENTS1 0xdc4 +#define PKT5_EMP_CVTEM_CONTENTS2 0xdc8 +#define PKT5_EMP_CVTEM_CONTENTS3 0xdcc +#define PKT5_EMP_CVTEM_CONTENTS4 0xdd0 +#define PKT5_EMP_CVTEM_CONTENTS5 0xdd4 +#define PKT5_EMP_CVTEM_CONTENTS6 0xdd8 +#define PKT5_EMP_CVTEM_CONTENTS7 0xddc +/* Audio Packetizer Registers */ +#define AUDPKT_CONTROL0 0xe20 +#define AUDPKT_PBIT_FORCE_EN_MASK BIT(12) +#define AUDPKT_PBIT_FORCE_EN BIT(12) +#define AUDPKT_CHSTATUS_OVR_EN_MASK BIT(0) +#define AUDPKT_CHSTATUS_OVR_EN BIT(0) +#define AUDPKT_CONTROL1 0xe24 +#define AUDPKT_ACR_CONTROL0 0xe40 +#define AUDPKT_ACR_N_VALUE 0xfffff +#define AUDPKT_ACR_CONTROL1 0xe44 +#define AUDPKT_ACR_CTS_OVR_VAL_MSK GENMASK(23, 4) +#define AUDPKT_ACR_CTS_OVR_VAL(x) ((x) << 4) +#define AUDPKT_ACR_CTS_OVR_EN_MSK BIT(1) +#define AUDPKT_ACR_CTS_OVR_EN BIT(1) +#define AUDPKT_ACR_STATUS0 0xe4c +#define AUDPKT_CHSTATUS_OVR0 0xe60 +#define AUDPKT_CHSTATUS_OVR1 0xe64 +/* IEC60958 Byte 3: Sampleing frenuency Bits 24 to 27 */ +#define AUDPKT_CHSTATUS_SR_MASK GENMASK(3, 0) +#define AUDPKT_CHSTATUS_SR_22050 0x4 +#define AUDPKT_CHSTATUS_SR_24000 0x6 +#define AUDPKT_CHSTATUS_SR_32000 0x3 +#define AUDPKT_CHSTATUS_SR_44100 0x0 +#define AUDPKT_CHSTATUS_SR_48000 0x2 +#define AUDPKT_CHSTATUS_SR_88200 0x8 +#define AUDPKT_CHSTATUS_SR_96000 0xa +#define AUDPKT_CHSTATUS_SR_176400 0xc +#define AUDPKT_CHSTATUS_SR_192000 0xe +#define AUDPKT_CHSTATUS_SR_768000 0x9 +#define AUDPKT_CHSTATUS_SR_NOT_INDICATED 0x1 +/* IEC60958 Byte 4: Original Sampleing frenuency Bits 36 to 39 */ +#define AUDPKT_CHSTATUS_0SR_MASK GENMASK(15, 12) +#define AUDPKT_CHSTATUS_OSR_8000 0x6 +#define AUDPKT_CHSTATUS_OSR_11025 0xa +#define AUDPKT_CHSTATUS_OSR_12000 0x2 +#define AUDPKT_CHSTATUS_OSR_16000 0x8 +#define AUDPKT_CHSTATUS_OSR_22050 0xb +#define AUDPKT_CHSTATUS_OSR_24000 0x9 +#define AUDPKT_CHSTATUS_OSR_32000 0xc +#define AUDPKT_CHSTATUS_OSR_44100 0xf +#define AUDPKT_CHSTATUS_OSR_48000 0xd +#define AUDPKT_CHSTATUS_OSR_88200 0x7 +#define AUDPKT_CHSTATUS_OSR_96000 0x5 +#define AUDPKT_CHSTATUS_OSR_176400 0x3 +#define AUDPKT_CHSTATUS_OSR_192000 0x1 +#define AUDPKT_CHSTATUS_OSR_NOT_INDICATED 0x0 +#define AUDPKT_CHSTATUS_OVR2 0xe68 +#define AUDPKT_CHSTATUS_OVR3 0xe6c +#define AUDPKT_CHSTATUS_OVR4 0xe70 +#define AUDPKT_CHSTATUS_OVR5 0xe74 +#define AUDPKT_CHSTATUS_OVR6 0xe78 +#define AUDPKT_CHSTATUS_OVR7 0xe7c +#define AUDPKT_CHSTATUS_OVR8 0xe80 +#define AUDPKT_CHSTATUS_OVR9 0xe84 +#define AUDPKT_CHSTATUS_OVR10 0xe88 +#define AUDPKT_CHSTATUS_OVR11 0xe8c +#define AUDPKT_CHSTATUS_OVR12 0xe90 +#define AUDPKT_CHSTATUS_OVR13 0xe94 +#define AUDPKT_CHSTATUS_OVR14 0xe98 +#define AUDPKT_USRDATA_OVR_MSG_GENERIC0 0xea0 +#define AUDPKT_USRDATA_OVR_MSG_GENERIC1 0xea4 +#define AUDPKT_USRDATA_OVR_MSG_GENERIC2 0xea8 +#define AUDPKT_USRDATA_OVR_MSG_GENERIC3 0xeac +#define AUDPKT_USRDATA_OVR_MSG_GENERIC4 0xeb0 +#define AUDPKT_USRDATA_OVR_MSG_GENERIC5 0xeb4 +#define AUDPKT_USRDATA_OVR_MSG_GENERIC6 0xeb8 +#define AUDPKT_USRDATA_OVR_MSG_GENERIC7 0xebc +#define AUDPKT_USRDATA_OVR_MSG_GENERIC8 0xec0 +#define AUDPKT_USRDATA_OVR_MSG_GENERIC9 0xec4 +#define AUDPKT_USRDATA_OVR_MSG_GENERIC10 0xec8 +#define AUDPKT_USRDATA_OVR_MSG_GENERIC11 0xecc +#define AUDPKT_USRDATA_OVR_MSG_GENERIC12 0xed0 +#define AUDPKT_USRDATA_OVR_MSG_GENERIC13 0xed4 +#define AUDPKT_USRDATA_OVR_MSG_GENERIC14 0xed8 +#define AUDPKT_USRDATA_OVR_MSG_GENERIC15 0xedc +#define AUDPKT_USRDATA_OVR_MSG_GENERIC16 0xee0 +#define AUDPKT_USRDATA_OVR_MSG_GENERIC17 0xee4 +#define AUDPKT_USRDATA_OVR_MSG_GENERIC18 0xee8 +#define AUDPKT_USRDATA_OVR_MSG_GENERIC19 0xeec +#define AUDPKT_USRDATA_OVR_MSG_GENERIC20 0xef0 +#define AUDPKT_USRDATA_OVR_MSG_GENERIC21 0xef4 +#define AUDPKT_USRDATA_OVR_MSG_GENERIC22 0xef8 +#define AUDPKT_USRDATA_OVR_MSG_GENERIC23 0xefc +#define AUDPKT_USRDATA_OVR_MSG_GENERIC24 0xf00 +#define AUDPKT_USRDATA_OVR_MSG_GENERIC25 0xf04 +#define AUDPKT_USRDATA_OVR_MSG_GENERIC26 0xf08 +#define AUDPKT_USRDATA_OVR_MSG_GENERIC27 0xf0c +#define AUDPKT_USRDATA_OVR_MSG_GENERIC28 0xf10 +#define AUDPKT_USRDATA_OVR_MSG_GENERIC29 0xf14 +#define AUDPKT_USRDATA_OVR_MSG_GENERIC30 0xf18 +#define AUDPKT_USRDATA_OVR_MSG_GENERIC31 0xf1c +#define AUDPKT_USRDATA_OVR_MSG_GENERIC32 0xf20 +#define AUDPKT_VBIT_OVR0 0xf24 +/* CEC Registers */ +#define CEC_TX_CONTROL 0x1000 +#define CEC_STATUS 0x1004 +#define CEC_CONFIG 0x1008 +#define CEC_ADDR 0x100c +#define CEC_TX_COUNT 0x1020 +#define CEC_TX_DATA3_0 0x1024 +#define CEC_TX_DATA7_4 0x1028 +#define CEC_TX_DATA11_8 0x102c +#define CEC_TX_DATA15_12 0x1030 +#define CEC_RX_COUNT_STATUS 0x1040 +#define CEC_RX_DATA3_0 0x1044 +#define CEC_RX_DATA7_4 0x1048 +#define CEC_RX_DATA11_8 0x104c +#define CEC_RX_DATA15_12 0x1050 +#define CEC_LOCK_CONTROL 0x1054 +#define CEC_RXQUAL_BITTIME_CONFIG 0x1060 +#define CEC_RX_BITTIME_CONFIG 0x1064 +#define CEC_TX_BITTIME_CONFIG 0x1068 +/* eARC RX CMDC Registers */ +#define EARCRX_CMDC_CONFIG0 0x1800 +#define EARCRX_XACTREAD_STOP_CFG BIT(26) +#define EARCRX_XACTREAD_RETRY_CFG BIT(25) +#define EARCRX_CMDC_DSCVR_EARCVALID0_TO_DISC1 BIT(24) +#define EARCRX_CMDC_XACT_RESTART_EN BIT(18) +#define EARCRX_CMDC_CONFIG1 0x1804 +#define EARCRX_CMDC_CONTROL 0x1808 +#define EARCRX_CMDC_HEARTBEAT_LOSS_EN BIT(4) +#define EARCRX_CMDC_DISCOVERY_EN BIT(3) +#define EARCRX_CONNECTOR_HPD BIT(1) +#define EARCRX_CMDC_WHITELIST0_CONFIG 0x180c +#define EARCRX_CMDC_WHITELIST1_CONFIG 0x1810 +#define EARCRX_CMDC_WHITELIST2_CONFIG 0x1814 +#define EARCRX_CMDC_WHITELIST3_CONFIG 0x1818 +#define EARCRX_CMDC_STATUS 0x181c +#define EARCRX_CMDC_XACT_INFO 0x1820 +#define EARCRX_CMDC_XACT_ACTION 0x1824 +#define EARCRX_CMDC_HEARTBEAT_RXSTAT_SE 0x1828 +#define EARCRX_CMDC_HEARTBEAT_STATUS 0x182c +#define EARCRX_CMDC_XACT_WR0 0x1840 +#define EARCRX_CMDC_XACT_WR1 0x1844 +#define EARCRX_CMDC_XACT_WR2 0x1848 +#define EARCRX_CMDC_XACT_WR3 0x184c +#define EARCRX_CMDC_XACT_WR4 0x1850 +#define EARCRX_CMDC_XACT_WR5 0x1854 +#define EARCRX_CMDC_XACT_WR6 0x1858 +#define EARCRX_CMDC_XACT_WR7 0x185c +#define EARCRX_CMDC_XACT_WR8 0x1860 +#define EARCRX_CMDC_XACT_WR9 0x1864 +#define EARCRX_CMDC_XACT_WR10 0x1868 +#define EARCRX_CMDC_XACT_WR11 0x186c +#define EARCRX_CMDC_XACT_WR12 0x1870 +#define EARCRX_CMDC_XACT_WR13 0x1874 +#define EARCRX_CMDC_XACT_WR14 0x1878 +#define EARCRX_CMDC_XACT_WR15 0x187c +#define EARCRX_CMDC_XACT_WR16 0x1880 +#define EARCRX_CMDC_XACT_WR17 0x1884 +#define EARCRX_CMDC_XACT_WR18 0x1888 +#define EARCRX_CMDC_XACT_WR19 0x188c +#define EARCRX_CMDC_XACT_WR20 0x1890 +#define EARCRX_CMDC_XACT_WR21 0x1894 +#define EARCRX_CMDC_XACT_WR22 0x1898 +#define EARCRX_CMDC_XACT_WR23 0x189c +#define EARCRX_CMDC_XACT_WR24 0x18a0 +#define EARCRX_CMDC_XACT_WR25 0x18a4 +#define EARCRX_CMDC_XACT_WR26 0x18a8 +#define EARCRX_CMDC_XACT_WR27 0x18ac +#define EARCRX_CMDC_XACT_WR28 0x18b0 +#define EARCRX_CMDC_XACT_WR29 0x18b4 +#define EARCRX_CMDC_XACT_WR30 0x18b8 +#define EARCRX_CMDC_XACT_WR31 0x18bc +#define EARCRX_CMDC_XACT_WR32 0x18c0 +#define EARCRX_CMDC_XACT_WR33 0x18c4 +#define EARCRX_CMDC_XACT_WR34 0x18c8 +#define EARCRX_CMDC_XACT_WR35 0x18cc +#define EARCRX_CMDC_XACT_WR36 0x18d0 +#define EARCRX_CMDC_XACT_WR37 0x18d4 +#define EARCRX_CMDC_XACT_WR38 0x18d8 +#define EARCRX_CMDC_XACT_WR39 0x18dc +#define EARCRX_CMDC_XACT_WR40 0x18e0 +#define EARCRX_CMDC_XACT_WR41 0x18e4 +#define EARCRX_CMDC_XACT_WR42 0x18e8 +#define EARCRX_CMDC_XACT_WR43 0x18ec +#define EARCRX_CMDC_XACT_WR44 0x18f0 +#define EARCRX_CMDC_XACT_WR45 0x18f4 +#define EARCRX_CMDC_XACT_WR46 0x18f8 +#define EARCRX_CMDC_XACT_WR47 0x18fc +#define EARCRX_CMDC_XACT_WR48 0x1900 +#define EARCRX_CMDC_XACT_WR49 0x1904 +#define EARCRX_CMDC_XACT_WR50 0x1908 +#define EARCRX_CMDC_XACT_WR51 0x190c +#define EARCRX_CMDC_XACT_WR52 0x1910 +#define EARCRX_CMDC_XACT_WR53 0x1914 +#define EARCRX_CMDC_XACT_WR54 0x1918 +#define EARCRX_CMDC_XACT_WR55 0x191c +#define EARCRX_CMDC_XACT_WR56 0x1920 +#define EARCRX_CMDC_XACT_WR57 0x1924 +#define EARCRX_CMDC_XACT_WR58 0x1928 +#define EARCRX_CMDC_XACT_WR59 0x192c +#define EARCRX_CMDC_XACT_WR60 0x1930 +#define EARCRX_CMDC_XACT_WR61 0x1934 +#define EARCRX_CMDC_XACT_WR62 0x1938 +#define EARCRX_CMDC_XACT_WR63 0x193c +#define EARCRX_CMDC_XACT_WR64 0x1940 +#define EARCRX_CMDC_XACT_RD0 0x1960 +#define EARCRX_CMDC_XACT_RD1 0x1964 +#define EARCRX_CMDC_XACT_RD2 0x1968 +#define EARCRX_CMDC_XACT_RD3 0x196c +#define EARCRX_CMDC_XACT_RD4 0x1970 +#define EARCRX_CMDC_XACT_RD5 0x1974 +#define EARCRX_CMDC_XACT_RD6 0x1978 +#define EARCRX_CMDC_XACT_RD7 0x197c +#define EARCRX_CMDC_XACT_RD8 0x1980 +#define EARCRX_CMDC_XACT_RD9 0x1984 +#define EARCRX_CMDC_XACT_RD10 0x1988 +#define EARCRX_CMDC_XACT_RD11 0x198c +#define EARCRX_CMDC_XACT_RD12 0x1990 +#define EARCRX_CMDC_XACT_RD13 0x1994 +#define EARCRX_CMDC_XACT_RD14 0x1998 +#define EARCRX_CMDC_XACT_RD15 0x199c +#define EARCRX_CMDC_XACT_RD16 0x19a0 +#define EARCRX_CMDC_XACT_RD17 0x19a4 +#define EARCRX_CMDC_XACT_RD18 0x19a8 +#define EARCRX_CMDC_XACT_RD19 0x19ac +#define EARCRX_CMDC_XACT_RD20 0x19b0 +#define EARCRX_CMDC_XACT_RD21 0x19b4 +#define EARCRX_CMDC_XACT_RD22 0x19b8 +#define EARCRX_CMDC_XACT_RD23 0x19bc +#define EARCRX_CMDC_XACT_RD24 0x19c0 +#define EARCRX_CMDC_XACT_RD25 0x19c4 +#define EARCRX_CMDC_XACT_RD26 0x19c8 +#define EARCRX_CMDC_XACT_RD27 0x19cc +#define EARCRX_CMDC_XACT_RD28 0x19d0 +#define EARCRX_CMDC_XACT_RD29 0x19d4 +#define EARCRX_CMDC_XACT_RD30 0x19d8 +#define EARCRX_CMDC_XACT_RD31 0x19dc +#define EARCRX_CMDC_XACT_RD32 0x19e0 +#define EARCRX_CMDC_XACT_RD33 0x19e4 +#define EARCRX_CMDC_XACT_RD34 0x19e8 +#define EARCRX_CMDC_XACT_RD35 0x19ec +#define EARCRX_CMDC_XACT_RD36 0x19f0 +#define EARCRX_CMDC_XACT_RD37 0x19f4 +#define EARCRX_CMDC_XACT_RD38 0x19f8 +#define EARCRX_CMDC_XACT_RD39 0x19fc +#define EARCRX_CMDC_XACT_RD40 0x1a00 +#define EARCRX_CMDC_XACT_RD41 0x1a04 +#define EARCRX_CMDC_XACT_RD42 0x1a08 +#define EARCRX_CMDC_XACT_RD43 0x1a0c +#define EARCRX_CMDC_XACT_RD44 0x1a10 +#define EARCRX_CMDC_XACT_RD45 0x1a14 +#define EARCRX_CMDC_XACT_RD46 0x1a18 +#define EARCRX_CMDC_XACT_RD47 0x1a1c +#define EARCRX_CMDC_XACT_RD48 0x1a20 +#define EARCRX_CMDC_XACT_RD49 0x1a24 +#define EARCRX_CMDC_XACT_RD50 0x1a28 +#define EARCRX_CMDC_XACT_RD51 0x1a2c +#define EARCRX_CMDC_XACT_RD52 0x1a30 +#define EARCRX_CMDC_XACT_RD53 0x1a34 +#define EARCRX_CMDC_XACT_RD54 0x1a38 +#define EARCRX_CMDC_XACT_RD55 0x1a3c +#define EARCRX_CMDC_XACT_RD56 0x1a40 +#define EARCRX_CMDC_XACT_RD57 0x1a44 +#define EARCRX_CMDC_XACT_RD58 0x1a48 +#define EARCRX_CMDC_XACT_RD59 0x1a4c +#define EARCRX_CMDC_XACT_RD60 0x1a50 +#define EARCRX_CMDC_XACT_RD61 0x1a54 +#define EARCRX_CMDC_XACT_RD62 0x1a58 +#define EARCRX_CMDC_XACT_RD63 0x1a5c +#define EARCRX_CMDC_XACT_RD64 0x1a60 +#define EARCRX_CMDC_SYNC_CONFIG 0x1b00 +/* eARC RX DMAC Registers */ +#define EARCRX_DMAC_PHY_CONTROL 0x1c00 +#define EARCRX_DMAC_CONFIG 0x1c08 +#define EARCRX_DMAC_CONTROL0 0x1c0c +#define EARCRX_DMAC_AUDIO_EN BIT(1) +#define EARCRX_DMAC_EN BIT(0) +#define EARCRX_DMAC_CONTROL1 0x1c10 +#define EARCRX_DMAC_STATUS 0x1c14 +#define EARCRX_DMAC_CHSTATUS0 0x1c18 +#define EARCRX_DMAC_CHSTATUS1 0x1c1c +#define EARCRX_DMAC_CHSTATUS2 0x1c20 +#define EARCRX_DMAC_CHSTATUS3 0x1c24 +#define EARCRX_DMAC_CHSTATUS4 0x1c28 +#define EARCRX_DMAC_CHSTATUS5 0x1c2c +#define EARCRX_DMAC_USRDATA_MSG_HDMI_AC0 0x1c30 +#define EARCRX_DMAC_USRDATA_MSG_HDMI_AC1 0x1c34 +#define EARCRX_DMAC_USRDATA_MSG_HDMI_AC2 0x1c38 +#define EARCRX_DMAC_USRDATA_MSG_HDMI_AC3 0x1c3c +#define EARCRX_DMAC_USRDATA_MSG_HDMI_AC4 0x1c40 +#define EARCRX_DMAC_USRDATA_MSG_HDMI_AC5 0x1c44 +#define EARCRX_DMAC_USRDATA_MSG_HDMI_AC6 0x1c48 +#define EARCRX_DMAC_USRDATA_MSG_HDMI_AC7 0x1c4c +#define EARCRX_DMAC_USRDATA_MSG_HDMI_AC8 0x1c50 +#define EARCRX_DMAC_USRDATA_MSG_HDMI_AC9 0x1c54 +#define EARCRX_DMAC_USRDATA_MSG_HDMI_AC10 0x1c58 +#define EARCRX_DMAC_USRDATA_MSG_HDMI_AC11 0x1c5c +#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC1_PKT0 0x1c60 +#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC1_PKT1 0x1c64 +#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC1_PKT2 0x1c68 +#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC1_PKT3 0x1c6c +#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC1_PKT4 0x1c70 +#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC1_PKT5 0x1c74 +#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC1_PKT6 0x1c78 +#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC1_PKT7 0x1c7c +#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC1_PKT8 0x1c80 +#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC1_PKT9 0x1c84 +#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC1_PKT10 0x1c88 +#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC1_PKT11 0x1c8c +#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC2_PKT0 0x1c90 +#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC2_PKT1 0x1c94 +#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC2_PKT2 0x1c98 +#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC2_PKT3 0x1c9c +#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC2_PKT4 0x1ca0 +#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC2_PKT5 0x1ca4 +#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC2_PKT6 0x1ca8 +#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC2_PKT7 0x1cac +#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC2_PKT8 0x1cb0 +#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC2_PKT9 0x1cb4 +#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC2_PKT10 0x1cb8 +#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC2_PKT11 0x1cbc +#define EARCRX_DMAC_USRDATA_MSG_GENERIC0 0x1cc0 +#define EARCRX_DMAC_USRDATA_MSG_GENERIC1 0x1cc4 +#define EARCRX_DMAC_USRDATA_MSG_GENERIC2 0x1cc8 +#define EARCRX_DMAC_USRDATA_MSG_GENERIC3 0x1ccc +#define EARCRX_DMAC_USRDATA_MSG_GENERIC4 0x1cd0 +#define EARCRX_DMAC_USRDATA_MSG_GENERIC5 0x1cd4 +#define EARCRX_DMAC_USRDATA_MSG_GENERIC6 0x1cd8 +#define EARCRX_DMAC_USRDATA_MSG_GENERIC7 0x1cdc +#define EARCRX_DMAC_USRDATA_MSG_GENERIC8 0x1ce0 +#define EARCRX_DMAC_USRDATA_MSG_GENERIC9 0x1ce4 +#define EARCRX_DMAC_USRDATA_MSG_GENERIC10 0x1ce8 +#define EARCRX_DMAC_USRDATA_MSG_GENERIC11 0x1cec +#define EARCRX_DMAC_USRDATA_MSG_GENERIC12 0x1cf0 +#define EARCRX_DMAC_USRDATA_MSG_GENERIC13 0x1cf4 +#define EARCRX_DMAC_USRDATA_MSG_GENERIC14 0x1cf8 +#define EARCRX_DMAC_USRDATA_MSG_GENERIC15 0x1cfc +#define EARCRX_DMAC_USRDATA_MSG_GENERIC16 0x1d00 +#define EARCRX_DMAC_USRDATA_MSG_GENERIC17 0x1d04 +#define EARCRX_DMAC_USRDATA_MSG_GENERIC18 0x1d08 +#define EARCRX_DMAC_USRDATA_MSG_GENERIC19 0x1d0c +#define EARCRX_DMAC_USRDATA_MSG_GENERIC20 0x1d10 +#define EARCRX_DMAC_USRDATA_MSG_GENERIC21 0x1d14 +#define EARCRX_DMAC_USRDATA_MSG_GENERIC22 0x1d18 +#define EARCRX_DMAC_USRDATA_MSG_GENERIC23 0x1d1c +#define EARCRX_DMAC_USRDATA_MSG_GENERIC24 0x1d20 +#define EARCRX_DMAC_USRDATA_MSG_GENERIC25 0x1d24 +#define EARCRX_DMAC_USRDATA_MSG_GENERIC26 0x1d28 +#define EARCRX_DMAC_USRDATA_MSG_GENERIC27 0x1d2c +#define EARCRX_DMAC_USRDATA_MSG_GENERIC28 0x1d30 +#define EARCRX_DMAC_USRDATA_MSG_GENERIC29 0x1d34 +#define EARCRX_DMAC_USRDATA_MSG_GENERIC30 0x1d38 +#define EARCRX_DMAC_USRDATA_MSG_GENERIC31 0x1d3c +#define EARCRX_DMAC_USRDATA_MSG_GENERIC32 0x1d40 +#define EARCRX_DMAC_CHSTATUS_STREAMER0 0x1d44 +#define EARCRX_DMAC_CHSTATUS_STREAMER1 0x1d48 +#define EARCRX_DMAC_CHSTATUS_STREAMER2 0x1d4c +#define EARCRX_DMAC_CHSTATUS_STREAMER3 0x1d50 +#define EARCRX_DMAC_CHSTATUS_STREAMER4 0x1d54 +#define EARCRX_DMAC_CHSTATUS_STREAMER5 0x1d58 +#define EARCRX_DMAC_CHSTATUS_STREAMER6 0x1d5c +#define EARCRX_DMAC_CHSTATUS_STREAMER7 0x1d60 +#define EARCRX_DMAC_CHSTATUS_STREAMER8 0x1d64 +#define EARCRX_DMAC_CHSTATUS_STREAMER9 0x1d68 +#define EARCRX_DMAC_CHSTATUS_STREAMER10 0x1d6c +#define EARCRX_DMAC_CHSTATUS_STREAMER11 0x1d70 +#define EARCRX_DMAC_CHSTATUS_STREAMER12 0x1d74 +#define EARCRX_DMAC_CHSTATUS_STREAMER13 0x1d78 +#define EARCRX_DMAC_CHSTATUS_STREAMER14 0x1d7c +#define EARCRX_DMAC_USRDATA_STREAMER0 0x1d80 +/* Main Unit Interrupt Registers */ +#define MAIN_INTVEC_INDEX 0x3000 +#define MAINUNIT_0_INT_STATUS 0x3010 +#define MAINUNIT_0_INT_MASK_N 0x3014 +#define MAINUNIT_0_INT_CLEAR 0x3018 +#define MAINUNIT_0_INT_FORCE 0x301c +#define MAINUNIT_1_INT_STATUS 0x3020 +#define FLT_EXIT_TO_LTSL_IRQ BIT(22) +#define FLT_EXIT_TO_LTS4_IRQ BIT(21) +#define FLT_EXIT_TO_LTSP_IRQ BIT(20) +#define SCDC_NACK_RCVD_IRQ BIT(12) +#define SCDC_RR_REPLY_STOP_IRQ BIT(11) +#define SCDC_UPD_FLAGS_CLR_IRQ BIT(10) +#define SCDC_UPD_FLAGS_CHG_IRQ BIT(9) +#define SCDC_UPD_FLAGS_RD_IRQ BIT(8) +#define I2CM_NACK_RCVD_IRQ BIT(2) +#define I2CM_READ_REQUEST_IRQ BIT(1) +#define I2CM_OP_DONE_IRQ BIT(0) +#define MAINUNIT_1_INT_MASK_N 0x3024 +#define I2CM_NACK_RCVD_MASK_N BIT(2) +#define I2CM_READ_REQUEST_MASK_N BIT(1) +#define I2CM_OP_DONE_MASK_N BIT(0) +#define MAINUNIT_1_INT_CLEAR 0x3028 +#define I2CM_NACK_RCVD_CLEAR BIT(2) +#define I2CM_READ_REQUEST_CLEAR BIT(1) +#define I2CM_OP_DONE_CLEAR BIT(0) +#define MAINUNIT_1_INT_FORCE 0x302c +/* AVPUNIT Interrupt Registers */ +#define AVP_INTVEC_INDEX 0x3800 +#define AVP_0_INT_STATUS 0x3810 +#define AVP_0_INT_MASK_N 0x3814 +#define AVP_0_INT_CLEAR 0x3818 +#define AVP_0_INT_FORCE 0x381c +#define AVP_1_INT_STATUS 0x3820 +#define AVP_1_INT_MASK_N 0x3824 +#define HDCP14_AUTH_CHG_MASK_N BIT(6) +#define AVP_1_INT_CLEAR 0x3828 +#define AVP_1_INT_FORCE 0x382c +#define AVP_2_INT_STATUS 0x3830 +#define AVP_2_INT_MASK_N 0x3834 +#define AVP_2_INT_CLEAR 0x3838 +#define AVP_2_INT_FORCE 0x383c +#define AVP_3_INT_STATUS 0x3840 +#define AVP_3_INT_MASK_N 0x3844 +#define AVP_3_INT_CLEAR 0x3848 +#define AVP_3_INT_FORCE 0x384c +#define AVP_4_INT_STATUS 0x3850 +#define AVP_4_INT_MASK_N 0x3854 +#define AVP_4_INT_CLEAR 0x3858 +#define AVP_4_INT_FORCE 0x385c +#define AVP_5_INT_STATUS 0x3860 +#define AVP_5_INT_MASK_N 0x3864 +#define AVP_5_INT_CLEAR 0x3868 +#define AVP_5_INT_FORCE 0x386c +#define AVP_6_INT_STATUS 0x3870 +#define AVP_6_INT_MASK_N 0x3874 +#define AVP_6_INT_CLEAR 0x3878 +#define AVP_6_INT_FORCE 0x387c +/* CEC Interrupt Registers */ +#define CEC_INT_STATUS 0x4000 +#define CEC_INT_MASK_N 0x4004 +#define CEC_INT_CLEAR 0x4008 +#define CEC_INT_FORCE 0x400c +/* eARC RX Interrupt Registers */ +#define EARCRX_INTVEC_INDEX 0x4800 +#define EARCRX_0_INT_STATUS 0x4810 +#define EARCRX_CMDC_DISCOVERY_TIMEOUT_IRQ BIT(9) +#define EARCRX_CMDC_DISCOVERY_DONE_IRQ BIT(8) +#define EARCRX_0_INT_MASK_N 0x4814 +#define EARCRX_0_INT_CLEAR 0x4818 +#define EARCRX_0_INT_FORCE 0x481c +#define EARCRX_1_INT_STATUS 0x4820 +#define EARCRX_1_INT_MASK_N 0x4824 +#define EARCRX_1_INT_CLEAR 0x4828 +#define EARCRX_1_INT_FORCE 0x482c + +#endif /* __DW_HDMI_QP_H__ */ diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 52d91a0df..0d6fd9578 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -162,6 +162,8 @@ struct dw_hdmi { void __iomem *regs; bool sink_is_hdmi; bool sink_has_audio; + bool support_hdmi; + int force_output; struct pinctrl *pinctrl; struct pinctrl_state *default_state; @@ -254,6 +256,25 @@ static void hdmi_mask_writeb(struct dw_hdmi *hdmi, u8 data, unsigned int reg, hdmi_modb(hdmi, data << shift, mask, reg); } +static bool dw_hdmi_check_output_type_changed(struct dw_hdmi *hdmi) +{ + bool sink_hdmi; + + sink_hdmi = hdmi->sink_is_hdmi; + + if (hdmi->force_output == 1) + hdmi->sink_is_hdmi = true; + else if (hdmi->force_output == 2) + hdmi->sink_is_hdmi = false; + else + hdmi->sink_is_hdmi = hdmi->support_hdmi; + + if (sink_hdmi != hdmi->sink_is_hdmi) + return true; + + return false; +} + static void dw_hdmi_i2c_init(struct dw_hdmi *hdmi) { hdmi_writeb(hdmi, HDMI_PHY_I2CM_INT_ADDR_DONE_POL, @@ -2532,6 +2553,45 @@ static int dw_hdmi_connector_atomic_check(struct drm_connector *connector, return 0; } +void dw_hdmi_set_quant_range(struct dw_hdmi *hdmi) +{ + if (!hdmi->bridge_is_on) + return; + + hdmi_writeb(hdmi, HDMI_FC_GCP_SET_AVMUTE, HDMI_FC_GCP); + dw_hdmi_setup(hdmi, hdmi->curr_conn, &hdmi->previous_mode); + hdmi_writeb(hdmi, HDMI_FC_GCP_CLEAR_AVMUTE, HDMI_FC_GCP); +} +EXPORT_SYMBOL_GPL(dw_hdmi_set_quant_range); + +void dw_hdmi_set_output_type(struct dw_hdmi *hdmi, u64 val) +{ + hdmi->force_output = val; + + if (!dw_hdmi_check_output_type_changed(hdmi)) + return; + + if (!hdmi->bridge_is_on) + return; + + hdmi_writeb(hdmi, HDMI_FC_GCP_SET_AVMUTE, HDMI_FC_GCP); + dw_hdmi_setup(hdmi, hdmi->curr_conn, &hdmi->previous_mode); + hdmi_writeb(hdmi, HDMI_FC_GCP_CLEAR_AVMUTE, HDMI_FC_GCP); +} +EXPORT_SYMBOL_GPL(dw_hdmi_set_output_type); + +bool dw_hdmi_get_output_whether_hdmi(struct dw_hdmi *hdmi) +{ + return hdmi->sink_is_hdmi; +} +EXPORT_SYMBOL_GPL(dw_hdmi_get_output_whether_hdmi); + +int dw_hdmi_get_output_type_cap(struct dw_hdmi *hdmi) +{ + return hdmi->support_hdmi; +} +EXPORT_SYMBOL_GPL(dw_hdmi_get_output_type_cap); + static void dw_hdmi_connector_force(struct drm_connector *connector) { struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, @@ -3683,6 +3743,35 @@ void dw_hdmi_unbind(struct dw_hdmi *hdmi) } EXPORT_SYMBOL_GPL(dw_hdmi_unbind); +void dw_hdmi_suspend(struct dw_hdmi *hdmi) +{ + if (!hdmi) + return; + + mutex_lock(&hdmi->mutex); + + /* + * When system shutdown, hdmi should be disabled. + * When system suspend, dw_hdmi_bridge_disable will disable hdmi first. + * To prevent duplicate operation, we should determine whether hdmi + * has been disabled. + */ + if (!hdmi->disabled) { + hdmi->disabled = true; + dw_hdmi_update_power(hdmi); + dw_hdmi_update_phy_mask(hdmi); + } + mutex_unlock(&hdmi->mutex); + + //[CC: needed?] + // if (hdmi->irq) + // disable_irq(hdmi->irq); + // cancel_delayed_work(&hdmi->work); + // flush_workqueue(hdmi->workqueue); + pinctrl_pm_select_sleep_state(hdmi->dev); +} +EXPORT_SYMBOL_GPL(dw_hdmi_suspend); + void dw_hdmi_resume(struct dw_hdmi *hdmi) { dw_hdmi_init_hw(hdmi); diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h index af43a0414..8ebdec725 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h @@ -851,6 +851,10 @@ enum { HDMI_FC_AVICONF3_QUANT_RANGE_LIMITED = 0x00, HDMI_FC_AVICONF3_QUANT_RANGE_FULL = 0x04, +/* HDMI_FC_GCP */ + HDMI_FC_GCP_SET_AVMUTE = 0x2, + HDMI_FC_GCP_CLEAR_AVMUTE = 0x1, + /* FC_DBGFORCE field values */ HDMI_FC_DBGFORCE_FORCEAUDIO = 0x10, HDMI_FC_DBGFORCE_FORCEVIDEO = 0x1, diff --git a/drivers/gpu/drm/drm_displayid.c b/drivers/gpu/drm/drm_displayid.c index 9edc111be..a578f918f 100644 --- a/drivers/gpu/drm/drm_displayid.c +++ b/drivers/gpu/drm/drm_displayid.c @@ -3,6 +3,7 @@ * Copyright © 2021 Intel Corporation */ +#include "linux/export.h" #include #include #include @@ -79,6 +80,7 @@ void displayid_iter_edid_begin(const struct drm_edid *drm_edid, iter->drm_edid = drm_edid; } +EXPORT_SYMBOL_GPL(displayid_iter_edid_begin); static const struct displayid_block * displayid_iter_block(const struct displayid_iter *iter) @@ -154,11 +156,13 @@ __displayid_iter_next(struct displayid_iter *iter) return block; } } +EXPORT_SYMBOL_GPL(__displayid_iter_next); void displayid_iter_end(struct displayid_iter *iter) { memset(iter, 0, sizeof(*iter)); } +EXPORT_SYMBOL_GPL(displayid_iter_end); /* DisplayID Structure Version/Revision from the Base Section. */ u8 displayid_version(const struct displayid_iter *iter) diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c index 341550199..52f457b16 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c @@ -4,22 +4,33 @@ */ #include +#include +#include #include #include #include #include +#include #include #include +#include +#include +#include +#include #include #include #include #include #include +#include + #include "rockchip_drm_drv.h" #include "rockchip_drm_vop.h" +#define HIWORD_UPDATE(val, mask) (val | (mask) << 16) + #define RK3228_GRF_SOC_CON2 0x0408 #define RK3228_HDMI_SDAIN_MSK BIT(14) #define RK3228_HDMI_SCLIN_MSK BIT(13) @@ -30,8 +41,11 @@ #define RK3288_GRF_SOC_CON6 0x025C #define RK3288_HDMI_LCDC_SEL BIT(4) -#define RK3328_GRF_SOC_CON2 0x0408 +#define RK3288_GRF_SOC_CON16 0x03a8 +#define RK3288_HDMI_LCDC0_YUV420 BIT(2) +#define RK3288_HDMI_LCDC1_YUV420 BIT(3) +#define RK3328_GRF_SOC_CON2 0x0408 #define RK3328_HDMI_SDAIN_MSK BIT(11) #define RK3328_HDMI_SCLIN_MSK BIT(10) #define RK3328_HDMI_HPD_IOE BIT(2) @@ -55,32 +69,154 @@ #define RK3568_HDMI_SDAIN_MSK BIT(15) #define RK3568_HDMI_SCLIN_MSK BIT(14) -#define HIWORD_UPDATE(val, mask) (val | (mask) << 16) +#define RK3588_GRF_SOC_CON2 0x0308 +#define RK3588_HDMI1_HPD_INT_MSK BIT(15) +#define RK3588_HDMI1_HPD_INT_CLR BIT(14) +#define RK3588_HDMI0_HPD_INT_MSK BIT(13) +#define RK3588_HDMI0_HPD_INT_CLR BIT(12) +#define RK3588_GRF_SOC_CON7 0x031c +#define RK3588_SET_HPD_PATH_MASK (0x3 << 12) +#define RK3588_GRF_SOC_STATUS1 0x0384 +#define RK3588_HDMI0_LOW_MORETHAN100MS BIT(20) +#define RK3588_HDMI0_HPD_PORT_LEVEL BIT(19) +#define RK3588_HDMI0_IHPD_PORT BIT(18) +#define RK3588_HDMI0_OHPD_INT BIT(17) +#define RK3588_HDMI0_LEVEL_INT BIT(16) +#define RK3588_HDMI0_INTR_CHANGE_CNT (0x7 << 13) +#define RK3588_HDMI1_LOW_MORETHAN100MS BIT(28) +#define RK3588_HDMI1_HPD_PORT_LEVEL BIT(27) +#define RK3588_HDMI1_IHPD_PORT BIT(26) +#define RK3588_HDMI1_OHPD_INT BIT(25) +#define RK3588_HDMI1_LEVEL_INT BIT(24) +#define RK3588_HDMI1_INTR_CHANGE_CNT (0x7 << 21) + +#define RK3588_GRF_VO1_CON3 0x000c +#define RK3588_COLOR_FORMAT_MASK 0xf +#define RK3588_YUV444 0x2 +#define RK3588_YUV420 0x3 +#define RK3588_COMPRESSED_DATA 0xb +#define RK3588_COLOR_DEPTH_MASK (0xf << 4) +#define RK3588_8BPC (0x5 << 4) +#define RK3588_10BPC (0x6 << 4) +#define RK3588_CECIN_MASK BIT(8) +#define RK3588_SCLIN_MASK BIT(9) +#define RK3588_SDAIN_MASK BIT(10) +#define RK3588_MODE_MASK BIT(11) +#define RK3588_COMPRESS_MODE_MASK BIT(12) +#define RK3588_I2S_SEL_MASK BIT(13) +#define RK3588_SPDIF_SEL_MASK BIT(14) +#define RK3588_GRF_VO1_CON4 0x0010 +#define RK3588_HDMI21_MASK BIT(0) +#define RK3588_GRF_VO1_CON9 0x0024 +#define RK3588_HDMI0_GRANT_SEL BIT(10) +#define RK3588_HDMI0_GRANT_SW BIT(11) +#define RK3588_HDMI1_GRANT_SEL BIT(12) +#define RK3588_HDMI1_GRANT_SW BIT(13) +#define RK3588_GRF_VO1_CON6 0x0018 +#define RK3588_GRF_VO1_CON7 0x001c + +#define COLOR_DEPTH_10BIT BIT(31) +#define HDMI_FRL_MODE BIT(30) +#define HDMI_EARC_MODE BIT(29) + +#define HDMI20_MAX_RATE 600000 +#define HDMI_8K60_RATE 2376000 /** * struct rockchip_hdmi_chip_data - splite the grf setting of kind of chips * @lcdsel_grf_reg: grf register offset of lcdc select + * @ddc_en_reg: grf register offset of hdmi ddc enable * @lcdsel_big: reg value of selecting vop big for HDMI * @lcdsel_lit: reg value of selecting vop little for HDMI */ struct rockchip_hdmi_chip_data { int lcdsel_grf_reg; + int ddc_en_reg; u32 lcdsel_big; u32 lcdsel_lit; + bool split_mode; +}; + +enum hdmi_frl_rate_per_lane { + FRL_12G_PER_LANE = 12, + FRL_10G_PER_LANE = 10, + FRL_8G_PER_LANE = 8, + FRL_6G_PER_LANE = 6, + FRL_3G_PER_LANE = 3, }; struct rockchip_hdmi { struct device *dev; struct regmap *regmap; + struct regmap *vo1_regmap; struct rockchip_encoder encoder; + struct drm_device *drm_dev; const struct rockchip_hdmi_chip_data *chip_data; - const struct dw_hdmi_plat_data *plat_data; + struct dw_hdmi_plat_data *plat_data; + struct clk *aud_clk; struct clk *ref_clk; struct clk *grf_clk; + struct clk *hclk_vio; + struct clk *hclk_vo1; + struct clk *hclk_vop; + struct clk *hpd_clk; + struct clk *pclk; + struct clk *earc_clk; + struct clk *hdmitx_ref; struct dw_hdmi *hdmi; + struct dw_hdmi_qp *hdmi_qp; + struct regulator *avdd_0v9; struct regulator *avdd_1v8; struct phy *phy; + + u32 max_tmdsclk; + bool unsupported_yuv_input; + bool unsupported_deep_color; + bool skip_check_420_mode; + u8 force_output; + u8 id; + bool hpd_stat; + bool is_hdmi_qp; + bool user_split_mode; + + unsigned long bus_format; + unsigned long output_bus_format; + unsigned long enc_out_encoding; + int color_changed; + int hpd_irq; + int vp_id; + + struct drm_property *color_depth_property; + struct drm_property *hdmi_output_property; + struct drm_property *colordepth_capacity; + struct drm_property *outputmode_capacity; + struct drm_property *quant_range; + struct drm_property *hdr_panel_metadata_property; + struct drm_property *next_hdr_sink_data_property; + struct drm_property *output_hdmi_dvi; + struct drm_property *output_type_capacity; + struct drm_property *user_split_mode_prop; + + struct drm_property_blob *hdr_panel_blob_ptr; + struct drm_property_blob *next_hdr_data_ptr; + + unsigned int colordepth; + unsigned int colorimetry; + unsigned int hdmi_quant_range; + unsigned int phy_bus_width; + enum rk_if_color_format hdmi_output; + struct rockchip_drm_sub_dev sub_dev; + + u8 max_frl_rate_per_lane; + u8 max_lanes; + struct rockchip_drm_dsc_cap dsc_cap; + struct next_hdr_sink_data next_hdr_data; + struct dw_hdmi_link_config link_cfg; + struct gpio_desc *enable_gpio; + + struct delayed_work work; + struct workqueue_struct *workqueue; }; static struct rockchip_hdmi *to_rockchip_hdmi(struct drm_encoder *encoder) @@ -203,13 +339,834 @@ static const struct dw_hdmi_phy_config rockchip_phy_config[] = { /*pixelclk symbol term vlev*/ { 74250000, 0x8009, 0x0004, 0x0272}, { 148500000, 0x802b, 0x0004, 0x028d}, + { 165000000, 0x802b, 0x0004, 0x0209}, { 297000000, 0x8039, 0x0005, 0x028d}, + { 594000000, 0x8039, 0x0000, 0x019d}, { ~0UL, 0x0000, 0x0000, 0x0000} }; +enum ROW_INDEX_BPP { + ROW_INDEX_6BPP = 0, + ROW_INDEX_8BPP, + ROW_INDEX_10BPP, + ROW_INDEX_12BPP, + ROW_INDEX_23BPP, + MAX_ROW_INDEX +}; + +enum COLUMN_INDEX_BPC { + COLUMN_INDEX_8BPC = 0, + COLUMN_INDEX_10BPC, + COLUMN_INDEX_12BPC, + COLUMN_INDEX_14BPC, + COLUMN_INDEX_16BPC, + MAX_COLUMN_INDEX +}; + +#define PPS_TABLE_LEN 8 +#define PPS_BPP_LEN 4 +#define PPS_BPC_LEN 2 + +struct pps_data { + u32 pic_width; + u32 pic_height; + u32 slice_width; + u32 slice_height; + bool convert_rgb; + u8 bpc; + u8 bpp; + u8 raw_pps[128]; +}; + +/* + * Selected Rate Control Related Parameter Recommended Values + * from DSC_v1.11 spec & C Model release: DSC_model_20161212 + */ +static struct pps_data pps_datas[PPS_TABLE_LEN] = { + { + /* 7680x4320/960X96 rgb 8bpc 12bpp */ + 7680, 4320, 960, 96, 1, 8, 192, + { + 0x12, 0x00, 0x00, 0x8d, 0x30, 0xc0, 0x10, 0xe0, + 0x1e, 0x00, 0x00, 0x60, 0x03, 0xc0, 0x05, 0xa0, + 0x01, 0x55, 0x03, 0x90, 0x00, 0x0a, 0x05, 0xc9, + 0x00, 0xa0, 0x00, 0x0f, 0x01, 0x44, 0x01, 0xaa, + 0x08, 0x00, 0x10, 0xf4, 0x03, 0x0c, 0x20, 0x00, + 0x06, 0x0b, 0x0b, 0x33, 0x0e, 0x1c, 0x2a, 0x38, + 0x46, 0x54, 0x62, 0x69, 0x70, 0x77, 0x79, 0x7b, + 0x7d, 0x7e, 0x00, 0x82, 0x00, 0xc0, 0x09, 0x00, + 0x09, 0x7e, 0x19, 0xbc, 0x19, 0xba, 0x19, 0xf8, + 0x1a, 0x38, 0x1a, 0x38, 0x1a, 0x76, 0x2a, 0x76, + 0x2a, 0x76, 0x2a, 0x74, 0x3a, 0xb4, 0x52, 0xf4, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }, + }, + { + /* 7680x4320/960X96 rgb 8bpc 11bpp */ + 7680, 4320, 960, 96, 1, 8, 176, + { + 0x12, 0x00, 0x00, 0x8d, 0x30, 0xb0, 0x10, 0xe0, + 0x1e, 0x00, 0x00, 0x60, 0x03, 0xc0, 0x05, 0x28, + 0x01, 0x74, 0x03, 0x40, 0x00, 0x0f, 0x06, 0xe0, + 0x00, 0x2d, 0x00, 0x0f, 0x01, 0x44, 0x01, 0x33, + 0x0f, 0x00, 0x10, 0xf4, 0x03, 0x0c, 0x20, 0x00, + 0x06, 0x0b, 0x0b, 0x33, 0x0e, 0x1c, 0x2a, 0x38, + 0x46, 0x54, 0x62, 0x69, 0x70, 0x77, 0x79, 0x7b, + 0x7d, 0x7e, 0x00, 0x82, 0x01, 0x00, 0x09, 0x40, + 0x09, 0xbe, 0x19, 0xfc, 0x19, 0xfa, 0x19, 0xf8, + 0x1a, 0x38, 0x1a, 0x38, 0x1a, 0x76, 0x2a, 0x76, + 0x2a, 0x76, 0x2a, 0xb4, 0x3a, 0xb4, 0x52, 0xf4, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }, + }, + { + /* 7680x4320/960X96 rgb 8bpc 10bpp */ + 7680, 4320, 960, 96, 1, 8, 160, + { + 0x12, 0x00, 0x00, 0x8d, 0x30, 0xa0, 0x10, 0xe0, + 0x1e, 0x00, 0x00, 0x60, 0x03, 0xc0, 0x04, 0xb0, + 0x01, 0x9a, 0x02, 0xe0, 0x00, 0x19, 0x09, 0xb0, + 0x00, 0x12, 0x00, 0x0f, 0x01, 0x44, 0x00, 0xbb, + 0x16, 0x00, 0x10, 0xec, 0x03, 0x0c, 0x20, 0x00, + 0x06, 0x0b, 0x0b, 0x33, 0x0e, 0x1c, 0x2a, 0x38, + 0x46, 0x54, 0x62, 0x69, 0x70, 0x77, 0x79, 0x7b, + 0x7d, 0x7e, 0x00, 0xc2, 0x01, 0x00, 0x09, 0x40, + 0x09, 0xbe, 0x19, 0xfc, 0x19, 0xfa, 0x19, 0xf8, + 0x1a, 0x38, 0x1a, 0x78, 0x1a, 0x76, 0x2a, 0xb6, + 0x2a, 0xb6, 0x2a, 0xf4, 0x3a, 0xf4, 0x5b, 0x34, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }, + }, + { + /* 7680x4320/960X96 rgb 8bpc 9bpp */ + 7680, 4320, 960, 96, 1, 8, 144, + { + 0x12, 0x00, 0x00, 0x8d, 0x30, 0x90, 0x10, 0xe0, + 0x1e, 0x00, 0x00, 0x60, 0x03, 0xc0, 0x04, 0x38, + 0x01, 0xc7, 0x03, 0x16, 0x00, 0x1c, 0x08, 0xc7, + 0x00, 0x10, 0x00, 0x0f, 0x01, 0x44, 0x00, 0xaa, + 0x17, 0x00, 0x10, 0xf1, 0x03, 0x0c, 0x20, 0x00, + 0x06, 0x0b, 0x0b, 0x33, 0x0e, 0x1c, 0x2a, 0x38, + 0x46, 0x54, 0x62, 0x69, 0x70, 0x77, 0x79, 0x7b, + 0x7d, 0x7e, 0x00, 0xc2, 0x01, 0x00, 0x09, 0x40, + 0x09, 0xbe, 0x19, 0xfc, 0x19, 0xfa, 0x19, 0xf8, + 0x1a, 0x38, 0x1a, 0x78, 0x1a, 0x76, 0x2a, 0xb6, + 0x2a, 0xb6, 0x2a, 0xf4, 0x3a, 0xf4, 0x63, 0x74, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }, + }, + { + /* 7680x4320/960X96 rgb 10bpc 12bpp */ + 7680, 4320, 960, 96, 1, 10, 192, + { + 0x12, 0x00, 0x00, 0xad, 0x30, 0xc0, 0x10, 0xe0, + 0x1e, 0x00, 0x00, 0x60, 0x03, 0xc0, 0x05, 0xa0, + 0x01, 0x55, 0x03, 0x90, 0x00, 0x0a, 0x05, 0xc9, + 0x00, 0xa0, 0x00, 0x0f, 0x01, 0x44, 0x01, 0xaa, + 0x08, 0x00, 0x10, 0xf4, 0x07, 0x10, 0x20, 0x00, + 0x06, 0x0f, 0x0f, 0x33, 0x0e, 0x1c, 0x2a, 0x38, + 0x46, 0x54, 0x62, 0x69, 0x70, 0x77, 0x79, 0x7b, + 0x7d, 0x7e, 0x01, 0x02, 0x11, 0x80, 0x22, 0x00, + 0x22, 0x7e, 0x32, 0xbc, 0x32, 0xba, 0x3a, 0xf8, + 0x3b, 0x38, 0x3b, 0x38, 0x3b, 0x76, 0x4b, 0x76, + 0x4b, 0x76, 0x4b, 0x74, 0x5b, 0xb4, 0x73, 0xf4, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }, + }, + { + /* 7680x4320/960X96 rgb 10bpc 11bpp */ + 7680, 4320, 960, 96, 1, 10, 176, + { + 0x12, 0x00, 0x00, 0xad, 0x30, 0xb0, 0x10, 0xe0, + 0x1e, 0x00, 0x00, 0x60, 0x03, 0xc0, 0x05, 0x28, + 0x01, 0x74, 0x03, 0x40, 0x00, 0x0f, 0x06, 0xe0, + 0x00, 0x2d, 0x00, 0x0f, 0x01, 0x44, 0x01, 0x33, + 0x0f, 0x00, 0x10, 0xf4, 0x07, 0x10, 0x20, 0x00, + 0x06, 0x0f, 0x0f, 0x33, 0x0e, 0x1c, 0x2a, 0x38, + 0x46, 0x54, 0x62, 0x69, 0x70, 0x77, 0x79, 0x7b, + 0x7d, 0x7e, 0x01, 0x42, 0x19, 0xc0, 0x2a, 0x40, + 0x2a, 0xbe, 0x3a, 0xfc, 0x3a, 0xfa, 0x3a, 0xf8, + 0x3b, 0x38, 0x3b, 0x38, 0x3b, 0x76, 0x4b, 0x76, + 0x4b, 0x76, 0x4b, 0xb4, 0x5b, 0xb4, 0x73, 0xf4, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }, + }, + { + /* 7680x4320/960X96 rgb 10bpc 10bpp */ + 7680, 4320, 960, 96, 1, 10, 160, + { + 0x12, 0x00, 0x00, 0xad, 0x30, 0xa0, 0x10, 0xe0, + 0x1e, 0x00, 0x00, 0x60, 0x03, 0xc0, 0x04, 0xb0, + 0x01, 0x9a, 0x02, 0xe0, 0x00, 0x19, 0x09, 0xb0, + 0x00, 0x12, 0x00, 0x0f, 0x01, 0x44, 0x00, 0xbb, + 0x16, 0x00, 0x10, 0xec, 0x07, 0x10, 0x20, 0x00, + 0x06, 0x0f, 0x0f, 0x33, 0x0e, 0x1c, 0x2a, 0x38, + 0x46, 0x54, 0x62, 0x69, 0x70, 0x77, 0x79, 0x7b, + 0x7d, 0x7e, 0x01, 0xc2, 0x22, 0x00, 0x2a, 0x40, + 0x2a, 0xbe, 0x3a, 0xfc, 0x3a, 0xfa, 0x3a, 0xf8, + 0x3b, 0x38, 0x3b, 0x78, 0x3b, 0x76, 0x4b, 0xb6, + 0x4b, 0xb6, 0x4b, 0xf4, 0x63, 0xf4, 0x7c, 0x34, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }, + }, + { + /* 7680x4320/960X96 rgb 10bpc 9bpp */ + 7680, 4320, 960, 96, 1, 10, 144, + { + 0x12, 0x00, 0x00, 0xad, 0x30, 0x90, 0x10, 0xe0, + 0x1e, 0x00, 0x00, 0x60, 0x03, 0xc0, 0x04, 0x38, + 0x01, 0xc7, 0x03, 0x16, 0x00, 0x1c, 0x08, 0xc7, + 0x00, 0x10, 0x00, 0x0f, 0x01, 0x44, 0x00, 0xaa, + 0x17, 0x00, 0x10, 0xf1, 0x07, 0x10, 0x20, 0x00, + 0x06, 0x0f, 0x0f, 0x33, 0x0e, 0x1c, 0x2a, 0x38, + 0x46, 0x54, 0x62, 0x69, 0x70, 0x77, 0x79, 0x7b, + 0x7d, 0x7e, 0x01, 0xc2, 0x22, 0x00, 0x2a, 0x40, + 0x2a, 0xbe, 0x3a, 0xfc, 0x3a, 0xfa, 0x3a, 0xf8, + 0x3b, 0x38, 0x3b, 0x78, 0x3b, 0x76, 0x4b, 0xb6, + 0x4b, 0xb6, 0x4b, 0xf4, 0x63, 0xf4, 0x84, 0x74, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }, + }, +}; + +static bool hdmi_bus_fmt_is_rgb(unsigned int bus_format) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_RGB888_1X24: + case MEDIA_BUS_FMT_RGB101010_1X30: + case MEDIA_BUS_FMT_RGB121212_1X36: + case MEDIA_BUS_FMT_RGB161616_1X48: + return true; + + default: + return false; + } +} + +static bool hdmi_bus_fmt_is_yuv444(unsigned int bus_format) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_YUV8_1X24: + case MEDIA_BUS_FMT_YUV10_1X30: + case MEDIA_BUS_FMT_YUV12_1X36: + case MEDIA_BUS_FMT_YUV16_1X48: + return true; + + default: + return false; + } +} + +static bool hdmi_bus_fmt_is_yuv422(unsigned int bus_format) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_UYVY10_1X20: + case MEDIA_BUS_FMT_UYVY12_1X24: + return true; + + default: + return false; + } +} + +static bool hdmi_bus_fmt_is_yuv420(unsigned int bus_format) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: + case MEDIA_BUS_FMT_UYYVYY12_0_5X36: + case MEDIA_BUS_FMT_UYYVYY16_0_5X48: + return true; + + default: + return false; + } +} + +static int hdmi_bus_fmt_color_depth(unsigned int bus_format) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_RGB888_1X24: + case MEDIA_BUS_FMT_YUV8_1X24: + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + return 8; + + case MEDIA_BUS_FMT_RGB101010_1X30: + case MEDIA_BUS_FMT_YUV10_1X30: + case MEDIA_BUS_FMT_UYVY10_1X20: + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: + return 10; + + case MEDIA_BUS_FMT_RGB121212_1X36: + case MEDIA_BUS_FMT_YUV12_1X36: + case MEDIA_BUS_FMT_UYVY12_1X24: + case MEDIA_BUS_FMT_UYYVYY12_0_5X36: + return 12; + + case MEDIA_BUS_FMT_RGB161616_1X48: + case MEDIA_BUS_FMT_YUV16_1X48: + case MEDIA_BUS_FMT_UYYVYY16_0_5X48: + return 16; + + default: + return 0; + } +} + +static unsigned int +hdmi_get_tmdsclock(struct rockchip_hdmi *hdmi, unsigned long pixelclock) +{ + unsigned int tmdsclock = pixelclock; + unsigned int depth = + hdmi_bus_fmt_color_depth(hdmi->output_bus_format); + + if (!hdmi_bus_fmt_is_yuv422(hdmi->output_bus_format)) { + switch (depth) { + case 16: + tmdsclock = pixelclock * 2; + break; + case 12: + tmdsclock = pixelclock * 3 / 2; + break; + case 10: + tmdsclock = pixelclock * 5 / 4; + break; + default: + break; + } + } + + return tmdsclock; +} + +static int rockchip_hdmi_match_by_id(struct device *dev, const void *data) +{ + struct rockchip_hdmi *hdmi = dev_get_drvdata(dev); + const unsigned int *id = data; + + return hdmi->id == *id; +} + +static struct rockchip_hdmi * +rockchip_hdmi_find_by_id(struct device_driver *drv, unsigned int id) +{ + struct device *dev; + + dev = driver_find_device(drv, NULL, &id, rockchip_hdmi_match_by_id); + if (!dev) + return NULL; + + return dev_get_drvdata(dev); +} + +static void hdmi_select_link_config(struct rockchip_hdmi *hdmi, + struct drm_crtc_state *crtc_state, + unsigned int tmdsclk) +{ + struct drm_display_mode mode; + int max_lanes, max_rate_per_lane; + int max_dsc_lanes, max_dsc_rate_per_lane; + unsigned long max_frl_rate; + + drm_mode_copy(&mode, &crtc_state->mode); + if (hdmi->plat_data->split_mode) + drm_mode_convert_to_origin_mode(&mode); + + max_lanes = hdmi->max_lanes; + max_rate_per_lane = hdmi->max_frl_rate_per_lane; + max_frl_rate = max_lanes * max_rate_per_lane * 1000000; + + hdmi->link_cfg.dsc_mode = false; + hdmi->link_cfg.frl_lanes = max_lanes; + hdmi->link_cfg.rate_per_lane = max_rate_per_lane; + + if (!max_frl_rate || (tmdsclk < HDMI20_MAX_RATE && mode.clock < HDMI20_MAX_RATE)) { + dev_info(hdmi->dev, "use tmds mode\n"); + hdmi->link_cfg.frl_mode = false; + return; + } + + hdmi->link_cfg.frl_mode = true; + + if (!hdmi->dsc_cap.v_1p2) + return; + + max_dsc_lanes = hdmi->dsc_cap.max_lanes; + max_dsc_rate_per_lane = + hdmi->dsc_cap.max_frl_rate_per_lane; + + if (mode.clock >= HDMI_8K60_RATE && + !hdmi_bus_fmt_is_yuv420(hdmi->bus_format) && + !hdmi_bus_fmt_is_yuv422(hdmi->bus_format)) { + hdmi->link_cfg.dsc_mode = true; + hdmi->link_cfg.frl_lanes = max_dsc_lanes; + hdmi->link_cfg.rate_per_lane = max_dsc_rate_per_lane; + } else { + hdmi->link_cfg.dsc_mode = false; + hdmi->link_cfg.frl_lanes = max_lanes; + hdmi->link_cfg.rate_per_lane = max_rate_per_lane; + } +} + +///////////////////////////////////////////////////////////////////////////////////// + +static int hdmi_dsc_get_slice_height(int vactive) +{ + int slice_height; + + /* + * Slice Height determination : HDMI2.1 Section 7.7.5.2 + * Select smallest slice height >=96, that results in a valid PPS and + * requires minimum padding lines required for final slice. + * + * Assumption : Vactive is even. + */ + for (slice_height = 96; slice_height <= vactive; slice_height += 2) + if (vactive % slice_height == 0) + return slice_height; + + return 0; +} + +static int hdmi_dsc_get_num_slices(struct rockchip_hdmi *hdmi, + struct drm_crtc_state *crtc_state, + int src_max_slices, int src_max_slice_width, + int hdmi_max_slices, int hdmi_throughput) +{ +/* Pixel rates in KPixels/sec */ +#define HDMI_DSC_PEAK_PIXEL_RATE 2720000 +/* + * Rates at which the source and sink are required to process pixels in each + * slice, can be two levels: either at least 340000KHz or at least 40000KHz. + */ +#define HDMI_DSC_MAX_ENC_THROUGHPUT_0 340000 +#define HDMI_DSC_MAX_ENC_THROUGHPUT_1 400000 + +/* Spec limits the slice width to 2720 pixels */ +#define MAX_HDMI_SLICE_WIDTH 2720 + int kslice_adjust; + int adjusted_clk_khz; + int min_slices; + int target_slices; + int max_throughput; /* max clock freq. in khz per slice */ + int max_slice_width; + int slice_width; + int pixel_clock = crtc_state->mode.clock; + + if (!hdmi_throughput) + return 0; + + /* + * Slice Width determination : HDMI2.1 Section 7.7.5.1 + * kslice_adjust factor for 4:2:0, and 4:2:2 formats is 0.5, where as + * for 4:4:4 is 1.0. Multiplying these factors by 10 and later + * dividing adjusted clock value by 10. + */ + if (hdmi_bus_fmt_is_yuv444(hdmi->output_bus_format) || + hdmi_bus_fmt_is_rgb(hdmi->output_bus_format)) + kslice_adjust = 10; + else + kslice_adjust = 5; + + /* + * As per spec, the rate at which the source and the sink process + * the pixels per slice are at two levels: at least 340Mhz or 400Mhz. + * This depends upon the pixel clock rate and output formats + * (kslice adjust). + * If pixel clock * kslice adjust >= 2720MHz slices can be processed + * at max 340MHz, otherwise they can be processed at max 400MHz. + */ + + adjusted_clk_khz = DIV_ROUND_UP(kslice_adjust * pixel_clock, 10); + + if (adjusted_clk_khz <= HDMI_DSC_PEAK_PIXEL_RATE) + max_throughput = HDMI_DSC_MAX_ENC_THROUGHPUT_0; + else + max_throughput = HDMI_DSC_MAX_ENC_THROUGHPUT_1; + + /* + * Taking into account the sink's capability for maximum + * clock per slice (in MHz) as read from HF-VSDB. + */ + max_throughput = min(max_throughput, hdmi_throughput * 1000); + + min_slices = DIV_ROUND_UP(adjusted_clk_khz, max_throughput); + max_slice_width = min(MAX_HDMI_SLICE_WIDTH, src_max_slice_width); + + /* + * Keep on increasing the num of slices/line, starting from min_slices + * per line till we get such a number, for which the slice_width is + * just less than max_slice_width. The slices/line selected should be + * less than or equal to the max horizontal slices that the combination + * of PCON encoder and HDMI decoder can support. + */ + do { + if (min_slices <= 1 && src_max_slices >= 1 && hdmi_max_slices >= 1) + target_slices = 1; + else if (min_slices <= 2 && src_max_slices >= 2 && hdmi_max_slices >= 2) + target_slices = 2; + else if (min_slices <= 4 && src_max_slices >= 4 && hdmi_max_slices >= 4) + target_slices = 4; + else if (min_slices <= 8 && src_max_slices >= 8 && hdmi_max_slices >= 8) + target_slices = 8; + else if (min_slices <= 12 && src_max_slices >= 12 && hdmi_max_slices >= 12) + target_slices = 12; + else if (min_slices <= 16 && src_max_slices >= 16 && hdmi_max_slices >= 16) + target_slices = 16; + else + return 0; + + slice_width = DIV_ROUND_UP(crtc_state->mode.hdisplay, target_slices); + if (slice_width > max_slice_width) + min_slices = target_slices + 1; + } while (slice_width > max_slice_width); + + return target_slices; +} + +static int hdmi_dsc_slices(struct rockchip_hdmi *hdmi, + struct drm_crtc_state *crtc_state) +{ + int hdmi_throughput = hdmi->dsc_cap.clk_per_slice; + int hdmi_max_slices = hdmi->dsc_cap.max_slices; + int rk_max_slices = 8; + int rk_max_slice_width = 2048; + + return hdmi_dsc_get_num_slices(hdmi, crtc_state, rk_max_slices, + rk_max_slice_width, + hdmi_max_slices, hdmi_throughput); +} + +static int +hdmi_dsc_get_bpp(struct rockchip_hdmi *hdmi, int src_fractional_bpp, + int slice_width, int num_slices, bool hdmi_all_bpp, + int hdmi_max_chunk_bytes) +{ + int max_dsc_bpp, min_dsc_bpp; + int target_bytes; + bool bpp_found = false; + int bpp_decrement_x16; + int bpp_target; + int bpp_target_x16; + + /* + * Get min bpp and max bpp as per Table 7.23, in HDMI2.1 spec + * Start with the max bpp and keep on decrementing with + * fractional bpp, if supported by PCON DSC encoder + * + * for each bpp we check if no of bytes can be supported by HDMI sink + */ + + /* only 9\10\12 bpp was tested */ + min_dsc_bpp = 9; + max_dsc_bpp = 12; + + /* + * Taking into account if all dsc_all_bpp supported by HDMI2.1 sink + * Section 7.7.34 : Source shall not enable compressed Video + * Transport with bpp_target settings above 12 bpp unless + * DSC_all_bpp is set to 1. + */ + if (!hdmi_all_bpp) + max_dsc_bpp = min(max_dsc_bpp, 12); + + /* + * The Sink has a limit of compressed data in bytes for a scanline, + * as described in max_chunk_bytes field in HFVSDB block of edid. + * The no. of bytes depend on the target bits per pixel that the + * source configures. So we start with the max_bpp and calculate + * the target_chunk_bytes. We keep on decrementing the target_bpp, + * till we get the target_chunk_bytes just less than what the sink's + * max_chunk_bytes, or else till we reach the min_dsc_bpp. + * + * The decrement is according to the fractional support from PCON DSC + * encoder. For fractional BPP we use bpp_target as a multiple of 16. + * + * bpp_target_x16 = bpp_target * 16 + * So we need to decrement by {1, 2, 4, 8, 16} for fractional bpps + * {1/16, 1/8, 1/4, 1/2, 1} respectively. + */ + + bpp_target = max_dsc_bpp; + + /* src does not support fractional bpp implies decrement by 16 for bppx16 */ + if (!src_fractional_bpp) + src_fractional_bpp = 1; + bpp_decrement_x16 = DIV_ROUND_UP(16, src_fractional_bpp); + bpp_target_x16 = bpp_target * 16; + + while (bpp_target_x16 > (min_dsc_bpp * 16)) { + int bpp; + + bpp = DIV_ROUND_UP(bpp_target_x16, 16); + target_bytes = DIV_ROUND_UP((num_slices * slice_width * bpp), 8); + if (target_bytes <= hdmi_max_chunk_bytes) { + bpp_found = true; + break; + } + bpp_target_x16 -= bpp_decrement_x16; + } + if (bpp_found) + return bpp_target_x16; + + return 0; +} + +static int +dw_hdmi_dsc_bpp(struct rockchip_hdmi *hdmi, + int num_slices, int slice_width) +{ + bool hdmi_all_bpp = hdmi->dsc_cap.all_bpp; + int fractional_bpp = 0; + int hdmi_max_chunk_bytes = hdmi->dsc_cap.total_chunk_kbytes * 1024; + + return hdmi_dsc_get_bpp(hdmi, fractional_bpp, slice_width, + num_slices, hdmi_all_bpp, + hdmi_max_chunk_bytes); +} + +static int dw_hdmi_qp_set_link_cfg(struct rockchip_hdmi *hdmi, + u16 pic_width, u16 pic_height, + u16 slice_width, u16 slice_height, + u16 bits_per_pixel, u8 bits_per_component) +{ + int i; + + for (i = 0; i < PPS_TABLE_LEN; i++) + if (pic_width == pps_datas[i].pic_width && + pic_height == pps_datas[i].pic_height && + slice_width == pps_datas[i].slice_width && + slice_height == pps_datas[i].slice_height && + bits_per_component == pps_datas[i].bpc && + bits_per_pixel == pps_datas[i].bpp && + hdmi_bus_fmt_is_rgb(hdmi->output_bus_format) == pps_datas[i].convert_rgb) + break; + + if (i == PPS_TABLE_LEN) { + dev_err(hdmi->dev, "can't find pps cfg!\n"); + return -EINVAL; + } + + memcpy(hdmi->link_cfg.pps_payload, pps_datas[i].raw_pps, 128); + hdmi->link_cfg.hcactive = DIV_ROUND_UP(slice_width * (bits_per_pixel / 16), 8) * + (pic_width / slice_width); + + return 0; +} + +static void dw_hdmi_qp_dsc_configure(struct rockchip_hdmi *hdmi, + struct rockchip_crtc_state *s, + struct drm_crtc_state *crtc_state) +{ + int ret; + int slice_height; + int slice_width; + int bits_per_pixel; + int slice_count; + bool hdmi_is_dsc_1_2; + unsigned int depth = hdmi_bus_fmt_color_depth(hdmi->output_bus_format); + + if (!crtc_state) + return; + + hdmi_is_dsc_1_2 = hdmi->dsc_cap.v_1p2; + + if (!hdmi_is_dsc_1_2) + return; + + slice_height = hdmi_dsc_get_slice_height(crtc_state->mode.vdisplay); + if (!slice_height) + return; + + slice_count = hdmi_dsc_slices(hdmi, crtc_state); + if (!slice_count) + return; + + slice_width = DIV_ROUND_UP(crtc_state->mode.hdisplay, slice_count); + + bits_per_pixel = dw_hdmi_dsc_bpp(hdmi, slice_count, slice_width); + if (!bits_per_pixel) + return; + + ret = dw_hdmi_qp_set_link_cfg(hdmi, crtc_state->mode.hdisplay, + crtc_state->mode.vdisplay, slice_width, + slice_height, bits_per_pixel, depth); + + if (ret) { + dev_err(hdmi->dev, "set vdsc cfg failed\n"); + return; + } + dev_info(hdmi->dev, "dsc_enable\n"); + s->dsc_enable = 1; + s->dsc_sink_cap.version_major = 1; + s->dsc_sink_cap.version_minor = 2; + s->dsc_sink_cap.slice_width = slice_width; + s->dsc_sink_cap.slice_height = slice_height; + s->dsc_sink_cap.target_bits_per_pixel_x16 = bits_per_pixel; + s->dsc_sink_cap.block_pred = 1; + s->dsc_sink_cap.native_420 = 0; + + memcpy(&s->pps, hdmi->link_cfg.pps_payload, 128); +} +///////////////////////////////////////////////////////////////////////////////////////// + +// static int rockchip_hdmi_update_phy_table(struct rockchip_hdmi *hdmi, +// u32 *config, +// int phy_table_size) +// { +// int i; +// +// if (phy_table_size > ARRAY_SIZE(rockchip_phy_config)) { +// dev_err(hdmi->dev, "phy table array number is out of range\n"); +// return -E2BIG; +// } +// +// for (i = 0; i < phy_table_size; i++) { +// if (config[i * 4] != 0) +// rockchip_phy_config[i].mpixelclock = (u64)config[i * 4]; +// else +// rockchip_phy_config[i].mpixelclock = ~0UL; +// rockchip_phy_config[i].sym_ctr = (u16)config[i * 4 + 1]; +// rockchip_phy_config[i].term = (u16)config[i * 4 + 2]; +// rockchip_phy_config[i].vlev_ctr = (u16)config[i * 4 + 3]; +// } +// +// return 0; +// } + +static void repo_hpd_event(struct work_struct *p_work) +{ + struct rockchip_hdmi *hdmi = container_of(p_work, struct rockchip_hdmi, work.work); + bool change; + + change = drm_helper_hpd_irq_event(hdmi->drm_dev); + if (change) { + dev_dbg(hdmi->dev, "hpd stat changed:%d\n", hdmi->hpd_stat); + // dw_hdmi_qp_cec_set_hpd(hdmi->hdmi_qp, hdmi->hpd_stat, change); + } +} + +static irqreturn_t rockchip_hdmi_hardirq(int irq, void *dev_id) +{ + struct rockchip_hdmi *hdmi = dev_id; + u32 intr_stat, val; + + regmap_read(hdmi->regmap, RK3588_GRF_SOC_STATUS1, &intr_stat); + + if (intr_stat) { + dev_dbg(hdmi->dev, "hpd irq %#x\n", intr_stat); + + if (!hdmi->id) + val = HIWORD_UPDATE(RK3588_HDMI0_HPD_INT_MSK, + RK3588_HDMI0_HPD_INT_MSK); + else + val = HIWORD_UPDATE(RK3588_HDMI1_HPD_INT_MSK, + RK3588_HDMI1_HPD_INT_MSK); + regmap_write(hdmi->regmap, RK3588_GRF_SOC_CON2, val); + return IRQ_WAKE_THREAD; + } + + return IRQ_NONE; +} + +static irqreturn_t rockchip_hdmi_irq(int irq, void *dev_id) +{ + struct rockchip_hdmi *hdmi = dev_id; + u32 intr_stat, val; + int msecs; + bool stat; + + regmap_read(hdmi->regmap, RK3588_GRF_SOC_STATUS1, &intr_stat); + + if (!intr_stat) + return IRQ_NONE; + + if (!hdmi->id) { + val = HIWORD_UPDATE(RK3588_HDMI0_HPD_INT_CLR, + RK3588_HDMI0_HPD_INT_CLR); + if (intr_stat & RK3588_HDMI0_LEVEL_INT) + stat = true; + else + stat = false; + } else { + val = HIWORD_UPDATE(RK3588_HDMI1_HPD_INT_CLR, + RK3588_HDMI1_HPD_INT_CLR); + if (intr_stat & RK3588_HDMI1_LEVEL_INT) + stat = true; + else + stat = false; + } + + regmap_write(hdmi->regmap, RK3588_GRF_SOC_CON2, val); + + if (stat) { + hdmi->hpd_stat = true; + msecs = 150; + } else { + hdmi->hpd_stat = false; + msecs = 20; + } + mod_delayed_work(hdmi->workqueue, &hdmi->work, msecs_to_jiffies(msecs)); + + if (!hdmi->id) { + val = HIWORD_UPDATE(RK3588_HDMI0_HPD_INT_CLR, + RK3588_HDMI0_HPD_INT_CLR) | + HIWORD_UPDATE(0, RK3588_HDMI0_HPD_INT_MSK); + } else { + val = HIWORD_UPDATE(RK3588_HDMI1_HPD_INT_CLR, + RK3588_HDMI1_HPD_INT_CLR) | + HIWORD_UPDATE(0, RK3588_HDMI1_HPD_INT_MSK); + } + + regmap_write(hdmi->regmap, RK3588_GRF_SOC_CON2, val); + + return IRQ_HANDLED; +} + +static void init_hpd_work(struct rockchip_hdmi *hdmi) +{ + hdmi->workqueue = create_workqueue("hpd_queue"); + INIT_DELAYED_WORK(&hdmi->work, repo_hpd_event); +} + static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi) { struct device_node *np = hdmi->dev->of_node; + int ret; hdmi->regmap = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); if (IS_ERR(hdmi->regmap)) { @@ -217,6 +1174,14 @@ static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi) return PTR_ERR(hdmi->regmap); } + if (hdmi->is_hdmi_qp) { + hdmi->vo1_regmap = syscon_regmap_lookup_by_phandle(np, "rockchip,vo1_grf"); + if (IS_ERR(hdmi->vo1_regmap)) { + DRM_DEV_ERROR(hdmi->dev, "Unable to get rockchip,vo1_grf\n"); + return PTR_ERR(hdmi->vo1_regmap); + } + } + hdmi->ref_clk = devm_clk_get_optional(hdmi->dev, "ref"); if (!hdmi->ref_clk) hdmi->ref_clk = devm_clk_get_optional(hdmi->dev, "vpll"); @@ -246,6 +1211,79 @@ static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi) if (IS_ERR(hdmi->avdd_1v8)) return PTR_ERR(hdmi->avdd_1v8); + hdmi->hclk_vio = devm_clk_get(hdmi->dev, "hclk_vio"); + if (PTR_ERR(hdmi->hclk_vio) == -ENOENT) { + hdmi->hclk_vio = NULL; + } else if (PTR_ERR(hdmi->hclk_vio) == -EPROBE_DEFER) { + return -EPROBE_DEFER; + } else if (IS_ERR(hdmi->hclk_vio)) { + dev_err(hdmi->dev, "failed to get hclk_vio clock\n"); + return PTR_ERR(hdmi->hclk_vio); + } + + hdmi->hclk_vop = devm_clk_get(hdmi->dev, "hclk"); + if (PTR_ERR(hdmi->hclk_vop) == -ENOENT) { + hdmi->hclk_vop = NULL; + } else if (PTR_ERR(hdmi->hclk_vop) == -EPROBE_DEFER) { + return -EPROBE_DEFER; + } else if (IS_ERR(hdmi->hclk_vop)) { + dev_err(hdmi->dev, "failed to get hclk_vop clock\n"); + return PTR_ERR(hdmi->hclk_vop); + } + + hdmi->aud_clk = devm_clk_get_optional(hdmi->dev, "aud"); + if (IS_ERR(hdmi->aud_clk)) { + dev_err_probe(hdmi->dev, PTR_ERR(hdmi->aud_clk), + "failed to get aud_clk clock\n"); + return PTR_ERR(hdmi->aud_clk); + } + + hdmi->hpd_clk = devm_clk_get_optional(hdmi->dev, "hpd"); + if (IS_ERR(hdmi->hpd_clk)) { + dev_err_probe(hdmi->dev, PTR_ERR(hdmi->hpd_clk), + "failed to get hpd_clk clock\n"); + return PTR_ERR(hdmi->hpd_clk); + } + + hdmi->hclk_vo1 = devm_clk_get_optional(hdmi->dev, "hclk_vo1"); + if (IS_ERR(hdmi->hclk_vo1)) { + dev_err_probe(hdmi->dev, PTR_ERR(hdmi->hclk_vo1), + "failed to get hclk_vo1 clock\n"); + return PTR_ERR(hdmi->hclk_vo1); + } + + hdmi->earc_clk = devm_clk_get_optional(hdmi->dev, "earc"); + if (IS_ERR(hdmi->earc_clk)) { + dev_err_probe(hdmi->dev, PTR_ERR(hdmi->earc_clk), + "failed to get earc_clk clock\n"); + return PTR_ERR(hdmi->earc_clk); + } + + hdmi->hdmitx_ref = devm_clk_get_optional(hdmi->dev, "hdmitx_ref"); + if (IS_ERR(hdmi->hdmitx_ref)) { + dev_err_probe(hdmi->dev, PTR_ERR(hdmi->hdmitx_ref), + "failed to get hdmitx_ref clock\n"); + return PTR_ERR(hdmi->hdmitx_ref); + } + + hdmi->pclk = devm_clk_get_optional(hdmi->dev, "pclk"); + if (IS_ERR(hdmi->pclk)) { + dev_err_probe(hdmi->dev, PTR_ERR(hdmi->pclk), + "failed to get pclk clock\n"); + return PTR_ERR(hdmi->pclk); + } + + hdmi->enable_gpio = devm_gpiod_get_optional(hdmi->dev, "enable", + GPIOD_OUT_HIGH); + if (IS_ERR(hdmi->enable_gpio)) { + ret = PTR_ERR(hdmi->enable_gpio); + dev_err(hdmi->dev, "failed to request enable GPIO: %d\n", ret); + return ret; + } + + hdmi->skip_check_420_mode = + of_property_read_bool(np, "skip-check-420-mode"); + return 0; } @@ -284,9 +1322,114 @@ dw_hdmi_rockchip_mode_valid(struct dw_hdmi *dw_hdmi, void *data, return MODE_BAD; } +/* [CC:] enable downstream mode_valid() */ +// static enum drm_mode_status +// dw_hdmi_rockchip_mode_valid(struct drm_connector *connector, void *data, +// const struct drm_display_info *info, +// const struct drm_display_mode *mode) +// { +// struct drm_encoder *encoder = connector->encoder; +// enum drm_mode_status status = MODE_OK; +// struct drm_device *dev = connector->dev; +// struct rockchip_drm_private *priv = dev->dev_private; +// struct drm_crtc *crtc; +// struct rockchip_hdmi *hdmi; +// +// /* +// * Pixel clocks we support are always < 2GHz and so fit in an +// * int. We should make sure source rate does too so we don't get +// * overflow when we multiply by 1000. +// */ +// if (mode->clock > INT_MAX / 1000) +// return MODE_BAD; +// +// if (!encoder) { +// const struct drm_connector_helper_funcs *funcs; +// +// funcs = connector->helper_private; +// if (funcs->atomic_best_encoder) +// encoder = funcs->atomic_best_encoder(connector, +// connector->state); +// else +// encoder = funcs->best_encoder(connector); +// } +// +// if (!encoder || !encoder->possible_crtcs) +// return MODE_BAD; +// +// hdmi = to_rockchip_hdmi(encoder); +// +// /* +// * If sink max TMDS clock < 340MHz, we should check the mode pixel +// * clock > 340MHz is YCbCr420 or not and whether the platform supports +// * YCbCr420. +// */ +// if (!hdmi->skip_check_420_mode) { +// if (mode->clock > 340000 && +// connector->display_info.max_tmds_clock < 340000 && +// (!drm_mode_is_420(&connector->display_info, mode) || +// !connector->ycbcr_420_allowed)) +// return MODE_BAD; +// +// if (hdmi->max_tmdsclk <= 340000 && mode->clock > 340000 && +// !drm_mode_is_420(&connector->display_info, mode)) +// return MODE_BAD; +// }; +// +// if (hdmi->phy) { +// if (hdmi->is_hdmi_qp) +// phy_set_bus_width(hdmi->phy, mode->clock * 10); +// else +// phy_set_bus_width(hdmi->phy, 8); +// } +// +// /* +// * ensure all drm display mode can work, if someone want support more +// * resolutions, please limit the possible_crtc, only connect to +// * needed crtc. +// */ +// drm_for_each_crtc(crtc, connector->dev) { +// int pipe = drm_crtc_index(crtc); +// const struct rockchip_crtc_funcs *funcs = +// priv->crtc_funcs[pipe]; +// +// if (!(encoder->possible_crtcs & drm_crtc_mask(crtc))) +// continue; +// if (!funcs || !funcs->mode_valid) +// continue; +// +// status = funcs->mode_valid(crtc, mode, +// DRM_MODE_CONNECTOR_HDMIA); +// if (status != MODE_OK) +// return status; +// } +// +// return status; +// } +// static void dw_hdmi_rockchip_encoder_disable(struct drm_encoder *encoder) { + struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder); + struct drm_crtc *crtc = encoder->crtc; + struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc->state); + + if (crtc->state->active_changed) { + if (hdmi->plat_data->split_mode) { + s->output_if &= ~(VOP_OUTPUT_IF_HDMI0 | VOP_OUTPUT_IF_HDMI1); + } else { + if (!hdmi->id) + s->output_if &= ~VOP_OUTPUT_IF_HDMI1; + else + s->output_if &= ~VOP_OUTPUT_IF_HDMI0; + } + } + /* + * when plug out hdmi it will be switch cvbs and then phy bus width + * must be set as 8 + */ + if (hdmi->phy) + phy_set_bus_width(hdmi->phy, 8); } static bool @@ -302,6 +1445,27 @@ static void dw_hdmi_rockchip_encoder_mode_set(struct drm_encoder *encoder, struct drm_display_mode *adj_mode) { struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder); + struct drm_crtc *crtc; + struct rockchip_crtc_state *s; + + if (!encoder->crtc) + return; + crtc = encoder->crtc; + + if (!crtc->state) + return; + s = to_rockchip_crtc_state(crtc->state); + + if (!s) + return; + + if (hdmi->is_hdmi_qp) { + s->dsc_enable = 0; + if (hdmi->link_cfg.dsc_mode) + dw_hdmi_qp_dsc_configure(hdmi, s, crtc->state); + + phy_set_bus_width(hdmi->phy, hdmi->phy_bus_width); + } clk_set_rate(hdmi->ref_clk, adj_mode->clock * 1000); } @@ -309,14 +1473,25 @@ static void dw_hdmi_rockchip_encoder_mode_set(struct drm_encoder *encoder, static void dw_hdmi_rockchip_encoder_enable(struct drm_encoder *encoder) { struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder); + struct drm_crtc *crtc = encoder->crtc; u32 val; + int mux; int ret; + if (WARN_ON(!crtc || !crtc->state)) + return; + + if (hdmi->phy) + phy_set_bus_width(hdmi->phy, hdmi->phy_bus_width); + + clk_set_rate(hdmi->ref_clk, + crtc->state->adjusted_mode.crtc_clock * 1000); + if (hdmi->chip_data->lcdsel_grf_reg < 0) return; - ret = drm_of_encoder_active_endpoint_id(hdmi->dev->of_node, encoder); - if (ret) + mux = drm_of_encoder_active_endpoint_id(hdmi->dev->of_node, encoder); + if (mux) val = hdmi->chip_data->lcdsel_lit; else val = hdmi->chip_data->lcdsel_big; @@ -331,24 +1506,1018 @@ static void dw_hdmi_rockchip_encoder_enable(struct drm_encoder *encoder) if (ret != 0) DRM_DEV_ERROR(hdmi->dev, "Could not write to GRF: %d\n", ret); + if (hdmi->chip_data->lcdsel_grf_reg == RK3288_GRF_SOC_CON6) { + struct rockchip_crtc_state *s = + to_rockchip_crtc_state(crtc->state); + u32 mode_mask = mux ? RK3288_HDMI_LCDC1_YUV420 : + RK3288_HDMI_LCDC0_YUV420; + + if (s->output_mode == ROCKCHIP_OUT_MODE_YUV420) + val = HIWORD_UPDATE(mode_mask, mode_mask); + else + val = HIWORD_UPDATE(0, mode_mask); + + regmap_write(hdmi->regmap, RK3288_GRF_SOC_CON16, val); + } + clk_disable_unprepare(hdmi->grf_clk); DRM_DEV_DEBUG(hdmi->dev, "vop %s output to hdmi\n", ret ? "LIT" : "BIG"); } -static int -dw_hdmi_rockchip_encoder_atomic_check(struct drm_encoder *encoder, - struct drm_crtc_state *crtc_state, - struct drm_connector_state *conn_state) +static void rk3588_set_link_mode(struct rockchip_hdmi *hdmi) { - struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state); + int val; + bool is_hdmi0; - s->output_mode = ROCKCHIP_OUT_MODE_AAAA; - s->output_type = DRM_MODE_CONNECTOR_HDMIA; + if (!hdmi->id) + is_hdmi0 = true; + else + is_hdmi0 = false; + + if (!hdmi->link_cfg.frl_mode) { + val = HIWORD_UPDATE(0, RK3588_HDMI21_MASK); + if (is_hdmi0) + regmap_write(hdmi->vo1_regmap, RK3588_GRF_VO1_CON4, val); + else + regmap_write(hdmi->vo1_regmap, RK3588_GRF_VO1_CON7, val); + + val = HIWORD_UPDATE(0, RK3588_COMPRESS_MODE_MASK | RK3588_COLOR_FORMAT_MASK); + if (is_hdmi0) + regmap_write(hdmi->vo1_regmap, RK3588_GRF_VO1_CON3, val); + else + regmap_write(hdmi->vo1_regmap, RK3588_GRF_VO1_CON6, val); + + return; + } + + val = HIWORD_UPDATE(RK3588_HDMI21_MASK, RK3588_HDMI21_MASK); + if (is_hdmi0) + regmap_write(hdmi->vo1_regmap, RK3588_GRF_VO1_CON4, val); + else + regmap_write(hdmi->vo1_regmap, RK3588_GRF_VO1_CON7, val); + + if (hdmi->link_cfg.dsc_mode) { + val = HIWORD_UPDATE(RK3588_COMPRESS_MODE_MASK | RK3588_COMPRESSED_DATA, + RK3588_COMPRESS_MODE_MASK | RK3588_COLOR_FORMAT_MASK); + if (is_hdmi0) + regmap_write(hdmi->vo1_regmap, RK3588_GRF_VO1_CON3, val); + else + regmap_write(hdmi->vo1_regmap, RK3588_GRF_VO1_CON6, val); + } else { + val = HIWORD_UPDATE(0, RK3588_COMPRESS_MODE_MASK | RK3588_COLOR_FORMAT_MASK); + if (is_hdmi0) + regmap_write(hdmi->vo1_regmap, RK3588_GRF_VO1_CON3, val); + else + regmap_write(hdmi->vo1_regmap, RK3588_GRF_VO1_CON6, val); + } +} + +static void rk3588_set_color_format(struct rockchip_hdmi *hdmi, u64 bus_format, + u32 depth) +{ + u32 val = 0; + + switch (bus_format) { + case MEDIA_BUS_FMT_RGB888_1X24: + case MEDIA_BUS_FMT_RGB101010_1X30: + val = HIWORD_UPDATE(0, RK3588_COLOR_FORMAT_MASK); + break; + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: + val = HIWORD_UPDATE(RK3588_YUV420, RK3588_COLOR_FORMAT_MASK); + break; + case MEDIA_BUS_FMT_YUV8_1X24: + case MEDIA_BUS_FMT_YUV10_1X30: + val = HIWORD_UPDATE(RK3588_YUV444, RK3588_COLOR_FORMAT_MASK); + break; + default: + dev_err(hdmi->dev, "can't set correct color format\n"); + return; + } + + if (hdmi->link_cfg.dsc_mode) + val = HIWORD_UPDATE(RK3588_COMPRESSED_DATA, RK3588_COLOR_FORMAT_MASK); + + if (depth == 8) + val |= HIWORD_UPDATE(RK3588_8BPC, RK3588_COLOR_DEPTH_MASK); + else + val |= HIWORD_UPDATE(RK3588_10BPC, RK3588_COLOR_DEPTH_MASK); + + if (!hdmi->id) + regmap_write(hdmi->vo1_regmap, RK3588_GRF_VO1_CON3, val); + else + regmap_write(hdmi->vo1_regmap, RK3588_GRF_VO1_CON6, val); +} + +static void rk3588_set_grf_cfg(void *data) +{ + struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; + int color_depth; + + rk3588_set_link_mode(hdmi); + color_depth = hdmi_bus_fmt_color_depth(hdmi->bus_format); + rk3588_set_color_format(hdmi, hdmi->bus_format, color_depth); +} + +static void +dw_hdmi_rockchip_select_output(struct drm_connector_state *conn_state, + struct drm_crtc_state *crtc_state, + struct rockchip_hdmi *hdmi, + unsigned int *color_format, + unsigned int *output_mode, + unsigned long *bus_format, + unsigned int *bus_width, + unsigned long *enc_out_encoding, + unsigned int *eotf) +{ + struct drm_display_info *info = &conn_state->connector->display_info; + struct drm_display_mode mode; + struct hdr_output_metadata *hdr_metadata; + u32 vic; + unsigned long tmdsclock, pixclock; + unsigned int color_depth; + bool support_dc = false; + bool sink_is_hdmi = true; + u32 max_tmds_clock = info->max_tmds_clock; + int output_eotf; + + drm_mode_copy(&mode, &crtc_state->mode); + pixclock = mode.crtc_clock; + if (hdmi->plat_data->split_mode) { + drm_mode_convert_to_origin_mode(&mode); + pixclock /= 2; + } + + vic = drm_match_cea_mode(&mode); + + if (!hdmi->is_hdmi_qp) + sink_is_hdmi = dw_hdmi_get_output_whether_hdmi(hdmi->hdmi); + + *color_format = RK_IF_FORMAT_RGB; + + switch (hdmi->hdmi_output) { + case RK_IF_FORMAT_YCBCR_HQ: + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR444) + *color_format = RK_IF_FORMAT_YCBCR444; + else if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422) + *color_format = RK_IF_FORMAT_YCBCR422; + else if (conn_state->connector->ycbcr_420_allowed && + drm_mode_is_420(info, &mode) && + (pixclock >= 594000 && !hdmi->is_hdmi_qp)) + *color_format = RK_IF_FORMAT_YCBCR420; + break; + case RK_IF_FORMAT_YCBCR_LQ: + if (conn_state->connector->ycbcr_420_allowed && + drm_mode_is_420(info, &mode) && pixclock >= 594000) + *color_format = RK_IF_FORMAT_YCBCR420; + else if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422) + *color_format = RK_IF_FORMAT_YCBCR422; + else if (info->color_formats & DRM_COLOR_FORMAT_YCBCR444) + *color_format = RK_IF_FORMAT_YCBCR444; + break; + case RK_IF_FORMAT_YCBCR420: + if (conn_state->connector->ycbcr_420_allowed && + drm_mode_is_420(info, &mode) && pixclock >= 594000) + *color_format = RK_IF_FORMAT_YCBCR420; + break; + case RK_IF_FORMAT_YCBCR422: + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422) + *color_format = RK_IF_FORMAT_YCBCR422; + break; + case RK_IF_FORMAT_YCBCR444: + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR444) + *color_format = RK_IF_FORMAT_YCBCR444; + break; + case RK_IF_FORMAT_RGB: + default: + break; + } + + if (*color_format == RK_IF_FORMAT_RGB && + info->edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_30) + support_dc = true; + if (*color_format == RK_IF_FORMAT_YCBCR444 && + info->edid_hdmi_rgb444_dc_modes & + (DRM_EDID_HDMI_DC_Y444 | DRM_EDID_HDMI_DC_30)) + support_dc = true; + if (*color_format == RK_IF_FORMAT_YCBCR422) + support_dc = true; + if (*color_format == RK_IF_FORMAT_YCBCR420 && + info->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_30) + support_dc = true; + + if (hdmi->colordepth > 8 && support_dc) + color_depth = 10; + else + color_depth = 8; + + if (!sink_is_hdmi) { + *color_format = RK_IF_FORMAT_RGB; + color_depth = 8; + } + + *eotf = HDMI_EOTF_TRADITIONAL_GAMMA_SDR; + if (conn_state->hdr_output_metadata) { + hdr_metadata = (struct hdr_output_metadata *) + conn_state->hdr_output_metadata->data; + output_eotf = hdr_metadata->hdmi_metadata_type1.eotf; + if (output_eotf > HDMI_EOTF_TRADITIONAL_GAMMA_SDR && + output_eotf <= HDMI_EOTF_BT_2100_HLG) + *eotf = output_eotf; + } + + hdmi->colorimetry = conn_state->colorspace; + + if ((*eotf > HDMI_EOTF_TRADITIONAL_GAMMA_SDR && + conn_state->connector->hdr_sink_metadata.hdmi_type1.eotf & + BIT(*eotf)) || ((hdmi->colorimetry >= DRM_MODE_COLORIMETRY_BT2020_CYCC) && + (hdmi->colorimetry <= DRM_MODE_COLORIMETRY_BT2020_YCC))) + *enc_out_encoding = V4L2_YCBCR_ENC_BT2020; + else if ((vic == 6) || (vic == 7) || (vic == 21) || (vic == 22) || + (vic == 2) || (vic == 3) || (vic == 17) || (vic == 18)) + *enc_out_encoding = V4L2_YCBCR_ENC_601; + else + *enc_out_encoding = V4L2_YCBCR_ENC_709; + + if (*enc_out_encoding == V4L2_YCBCR_ENC_BT2020) { + /* BT2020 require color depth at lest 10bit */ + color_depth = 10; + /* We prefer use YCbCr422 to send 10bit */ + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422) + *color_format = RK_IF_FORMAT_YCBCR422; + if (hdmi->is_hdmi_qp) { + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR420) { + if (mode.clock >= 340000) + *color_format = RK_IF_FORMAT_YCBCR420; + else + *color_format = RK_IF_FORMAT_RGB; + } else { + *color_format = RK_IF_FORMAT_RGB; + } + } + } + + if (mode.flags & DRM_MODE_FLAG_DBLCLK) + pixclock *= 2; + if ((mode.flags & DRM_MODE_FLAG_3D_MASK) == + DRM_MODE_FLAG_3D_FRAME_PACKING) + pixclock *= 2; + + if (*color_format == RK_IF_FORMAT_YCBCR422 || color_depth == 8) + tmdsclock = pixclock; + else + tmdsclock = pixclock * (color_depth) / 8; + + if (*color_format == RK_IF_FORMAT_YCBCR420) + tmdsclock /= 2; + + /* XXX: max_tmds_clock of some sink is 0, we think it is 340MHz. */ + if (!max_tmds_clock) + max_tmds_clock = 340000; + + max_tmds_clock = min(max_tmds_clock, hdmi->max_tmdsclk); + + if ((tmdsclock > max_tmds_clock) && !hdmi->is_hdmi_qp) { + if (max_tmds_clock >= 594000) { + color_depth = 8; + } else if (max_tmds_clock > 340000) { + if (drm_mode_is_420(info, &mode) || tmdsclock >= 594000) + *color_format = RK_IF_FORMAT_YCBCR420; + } else { + color_depth = 8; + if (drm_mode_is_420(info, &mode) || tmdsclock >= 594000) + *color_format = RK_IF_FORMAT_YCBCR420; + } + } + + if (hdmi->is_hdmi_qp) { + if (mode.clock >= 340000) { + if (drm_mode_is_420(info, &mode)) + *color_format = RK_IF_FORMAT_YCBCR420; + else + *color_format = RK_IF_FORMAT_RGB; + } else if (tmdsclock > max_tmds_clock) { + color_depth = 8; + if (drm_mode_is_420(info, &mode)) + *color_format = RK_IF_FORMAT_YCBCR420; + } + } + + if (*color_format == RK_IF_FORMAT_YCBCR420) { + *output_mode = ROCKCHIP_OUT_MODE_YUV420; + if (color_depth > 8) + *bus_format = MEDIA_BUS_FMT_UYYVYY10_0_5X30; + else + *bus_format = MEDIA_BUS_FMT_UYYVYY8_0_5X24; + *bus_width = color_depth / 2; + } else { + *output_mode = ROCKCHIP_OUT_MODE_AAAA; + if (color_depth > 8) { + if (*color_format != RK_IF_FORMAT_RGB && + !hdmi->unsupported_yuv_input) + *bus_format = MEDIA_BUS_FMT_YUV10_1X30; + else + *bus_format = MEDIA_BUS_FMT_RGB101010_1X30; + } else { + if (*color_format != RK_IF_FORMAT_RGB && + !hdmi->unsupported_yuv_input) + *bus_format = MEDIA_BUS_FMT_YUV8_1X24; + else + *bus_format = MEDIA_BUS_FMT_RGB888_1X24; + } + if (*color_format == RK_IF_FORMAT_YCBCR422) + *bus_width = 8; + else + *bus_width = color_depth; + } + + hdmi->bus_format = *bus_format; + + if (*color_format == RK_IF_FORMAT_YCBCR422) { + if (color_depth == 12) + hdmi->output_bus_format = MEDIA_BUS_FMT_UYVY12_1X24; + else if (color_depth == 10) + hdmi->output_bus_format = MEDIA_BUS_FMT_UYVY10_1X20; + else + hdmi->output_bus_format = MEDIA_BUS_FMT_UYVY8_1X16; + } else { + hdmi->output_bus_format = *bus_format; + } +} + +static bool +dw_hdmi_rockchip_check_color(struct drm_connector_state *conn_state, + struct rockchip_hdmi *hdmi) +{ + struct drm_crtc_state *crtc_state = conn_state->crtc->state; + unsigned int colorformat; + unsigned long bus_format; + unsigned long output_bus_format = hdmi->output_bus_format; + unsigned long enc_out_encoding = hdmi->enc_out_encoding; + unsigned int eotf, bus_width; + unsigned int output_mode; + + dw_hdmi_rockchip_select_output(conn_state, crtc_state, hdmi, + &colorformat, + &output_mode, &bus_format, &bus_width, + &hdmi->enc_out_encoding, &eotf); + + if (output_bus_format != hdmi->output_bus_format || + enc_out_encoding != hdmi->enc_out_encoding) + return true; + else + return false; +} + +static int +dw_hdmi_rockchip_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state); + struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder); + unsigned int colorformat, bus_width, tmdsclk; + struct drm_display_mode mode; + unsigned int output_mode; + unsigned long bus_format; + int color_depth; + bool secondary = false; + + /* + * There are two hdmi but only one encoder in split mode, + * so we need to check twice. + */ +secondary: + drm_mode_copy(&mode, &crtc_state->mode); + + hdmi->vp_id = s->vp_id; + if (hdmi->plat_data->split_mode) + drm_mode_convert_to_origin_mode(&mode); + + dw_hdmi_rockchip_select_output(conn_state, crtc_state, hdmi, + &colorformat, + &output_mode, &bus_format, &bus_width, + &hdmi->enc_out_encoding, &s->eotf); + + s->bus_format = bus_format; + if (hdmi->is_hdmi_qp) { + color_depth = hdmi_bus_fmt_color_depth(bus_format); + tmdsclk = hdmi_get_tmdsclock(hdmi, crtc_state->mode.clock); + if (hdmi_bus_fmt_is_yuv420(hdmi->output_bus_format)) + tmdsclk /= 2; + hdmi_select_link_config(hdmi, crtc_state, tmdsclk); + + if (hdmi->link_cfg.frl_mode) { + gpiod_set_value(hdmi->enable_gpio, 0); + /* in the current version, support max 40G frl */ + if (hdmi->link_cfg.rate_per_lane >= 10) { + hdmi->link_cfg.frl_lanes = 4; + hdmi->link_cfg.rate_per_lane = 10; + } + bus_width = hdmi->link_cfg.frl_lanes * + hdmi->link_cfg.rate_per_lane * 1000000; + /* 10 bit color depth and frl mode */ + if (color_depth == 10) + bus_width |= + COLOR_DEPTH_10BIT | HDMI_FRL_MODE; + else + bus_width |= HDMI_FRL_MODE; + } else { + gpiod_set_value(hdmi->enable_gpio, 1); + bus_width = hdmi_get_tmdsclock(hdmi, mode.clock * 10); + if (hdmi_bus_fmt_is_yuv420(hdmi->output_bus_format)) + bus_width /= 2; + + if (color_depth == 10) + bus_width |= COLOR_DEPTH_10BIT; + } + } + + hdmi->phy_bus_width = bus_width; + + if (hdmi->phy) + phy_set_bus_width(hdmi->phy, bus_width); + + s->output_type = DRM_MODE_CONNECTOR_HDMIA; + s->tv_state = &conn_state->tv; + + if (hdmi->plat_data->split_mode) { + s->output_flags |= ROCKCHIP_OUTPUT_DUAL_CHANNEL_LEFT_RIGHT_MODE; + if (hdmi->plat_data->right && hdmi->id) + s->output_flags |= ROCKCHIP_OUTPUT_DATA_SWAP; + s->output_if |= VOP_OUTPUT_IF_HDMI0 | VOP_OUTPUT_IF_HDMI1; + } else { + if (!hdmi->id) + s->output_if |= VOP_OUTPUT_IF_HDMI0; + else + s->output_if |= VOP_OUTPUT_IF_HDMI1; + } + + s->output_mode = output_mode; + hdmi->bus_format = s->bus_format; + + if (hdmi->enc_out_encoding == V4L2_YCBCR_ENC_BT2020) + s->color_space = V4L2_COLORSPACE_BT2020; + else if (colorformat == RK_IF_FORMAT_RGB) + s->color_space = V4L2_COLORSPACE_DEFAULT; + else if (hdmi->enc_out_encoding == V4L2_YCBCR_ENC_709) + s->color_space = V4L2_COLORSPACE_REC709; + else + s->color_space = V4L2_COLORSPACE_SMPTE170M; + + if (hdmi->plat_data->split_mode && !secondary) { + hdmi = rockchip_hdmi_find_by_id(hdmi->dev->driver, !hdmi->id); + secondary = true; + goto secondary; + } + + return 0; +} + +static unsigned long +dw_hdmi_rockchip_get_input_bus_format(void *data) +{ + struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; + + return hdmi->bus_format; +} + +static unsigned long +dw_hdmi_rockchip_get_output_bus_format(void *data) +{ + struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; + + return hdmi->output_bus_format; +} + +static unsigned long +dw_hdmi_rockchip_get_enc_in_encoding(void *data) +{ + struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; + + return hdmi->enc_out_encoding; +} + +static unsigned long +dw_hdmi_rockchip_get_enc_out_encoding(void *data) +{ + struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; + + return hdmi->enc_out_encoding; +} + +static unsigned long +dw_hdmi_rockchip_get_quant_range(void *data) +{ + struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; + + return hdmi->hdmi_quant_range; +} + +static struct drm_property * +dw_hdmi_rockchip_get_hdr_property(void *data) +{ + struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; + + return hdmi->hdr_panel_metadata_property; +} + +static struct drm_property_blob * +dw_hdmi_rockchip_get_hdr_blob(void *data) +{ + struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; + + return hdmi->hdr_panel_blob_ptr; +} + +static bool +dw_hdmi_rockchip_get_color_changed(void *data) +{ + struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; + bool ret = false; + + if (hdmi->color_changed) + ret = true; + hdmi->color_changed = 0; + + return ret; +} + +static int +dw_hdmi_rockchip_get_yuv422_format(struct drm_connector *connector, + struct edid *edid) +{ + if (!connector || !edid) + return -EINVAL; + + return rockchip_drm_get_yuv422_format(connector, edid); +} + +static int +dw_hdmi_rockchip_get_edid_dsc_info(void *data, struct edid *edid) +{ + struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; + + if (!edid) + return -EINVAL; + + return rockchip_drm_parse_cea_ext(&hdmi->dsc_cap, + &hdmi->max_frl_rate_per_lane, + &hdmi->max_lanes, edid); +} + +static int +dw_hdmi_rockchip_get_next_hdr_data(void *data, struct edid *edid, + struct drm_connector *connector) +{ + int ret; + struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; + struct next_hdr_sink_data *sink_data = &hdmi->next_hdr_data; + size_t size = sizeof(*sink_data); + struct drm_property *property = hdmi->next_hdr_sink_data_property; + struct drm_property_blob *blob = hdmi->hdr_panel_blob_ptr; + + if (!edid) + return -EINVAL; + + rockchip_drm_parse_next_hdr(sink_data, edid); + + ret = drm_property_replace_global_blob(connector->dev, &blob, size, sink_data, + &connector->base, property); + + return ret; +}; + +static +struct dw_hdmi_link_config *dw_hdmi_rockchip_get_link_cfg(void *data) +{ + struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; + + return &hdmi->link_cfg; +} + +static int dw_hdmi_dclk_set(void *data, bool enable) +{ + struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; + char clk_name[16]; + struct clk *dclk; + int ret; + + snprintf(clk_name, sizeof(clk_name), "dclk_vp%d", hdmi->vp_id); + + dclk = devm_clk_get(hdmi->dev, clk_name); + if (IS_ERR(dclk)) { + DRM_DEV_ERROR(hdmi->dev, "failed to get %s\n", clk_name); + return PTR_ERR(dclk); + } + + if (enable) { + ret = clk_prepare_enable(dclk); + if (ret < 0) + DRM_DEV_ERROR(hdmi->dev, "failed to enable dclk for video port%d - %d\n", + hdmi->vp_id, ret); + } else { + clk_disable_unprepare(dclk); + } return 0; } +static const struct drm_prop_enum_list color_depth_enum_list[] = { + { 0, "Automatic" }, /* Prefer highest color depth */ + { 8, "24bit" }, + { 10, "30bit" }, +}; + +static const struct drm_prop_enum_list drm_hdmi_output_enum_list[] = { + { RK_IF_FORMAT_RGB, "rgb" }, + { RK_IF_FORMAT_YCBCR444, "ycbcr444" }, + { RK_IF_FORMAT_YCBCR422, "ycbcr422" }, + { RK_IF_FORMAT_YCBCR420, "ycbcr420" }, + { RK_IF_FORMAT_YCBCR_HQ, "ycbcr_high_subsampling" }, + { RK_IF_FORMAT_YCBCR_LQ, "ycbcr_low_subsampling" }, + { RK_IF_FORMAT_MAX, "invalid_output" }, +}; + +static const struct drm_prop_enum_list quant_range_enum_list[] = { + { HDMI_QUANTIZATION_RANGE_DEFAULT, "default" }, + { HDMI_QUANTIZATION_RANGE_LIMITED, "limit" }, + { HDMI_QUANTIZATION_RANGE_FULL, "full" }, +}; + +static const struct drm_prop_enum_list output_hdmi_dvi_enum_list[] = { + { 0, "auto" }, + { 1, "force_hdmi" }, + { 2, "force_dvi" }, +}; + +static const struct drm_prop_enum_list output_type_cap_list[] = { + { 0, "DVI" }, + { 1, "HDMI" }, +}; + +static void +dw_hdmi_rockchip_attach_properties(struct drm_connector *connector, + unsigned int color, int version, + void *data) +{ + struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; + struct drm_property *prop; + struct rockchip_drm_private *private = connector->dev->dev_private; + + switch (color) { + case MEDIA_BUS_FMT_RGB101010_1X30: + hdmi->hdmi_output = RK_IF_FORMAT_RGB; + hdmi->colordepth = 10; + break; + case MEDIA_BUS_FMT_YUV8_1X24: + hdmi->hdmi_output = RK_IF_FORMAT_YCBCR444; + hdmi->colordepth = 8; + break; + case MEDIA_BUS_FMT_YUV10_1X30: + hdmi->hdmi_output = RK_IF_FORMAT_YCBCR444; + hdmi->colordepth = 10; + break; + case MEDIA_BUS_FMT_UYVY10_1X20: + hdmi->hdmi_output = RK_IF_FORMAT_YCBCR422; + hdmi->colordepth = 10; + break; + case MEDIA_BUS_FMT_UYVY8_1X16: + hdmi->hdmi_output = RK_IF_FORMAT_YCBCR422; + hdmi->colordepth = 8; + break; + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + hdmi->hdmi_output = RK_IF_FORMAT_YCBCR420; + hdmi->colordepth = 8; + break; + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: + hdmi->hdmi_output = RK_IF_FORMAT_YCBCR420; + hdmi->colordepth = 10; + break; + default: + hdmi->hdmi_output = RK_IF_FORMAT_RGB; + hdmi->colordepth = 8; + } + + hdmi->bus_format = color; + + if (hdmi->hdmi_output == RK_IF_FORMAT_YCBCR422) { + if (hdmi->colordepth == 12) + hdmi->output_bus_format = MEDIA_BUS_FMT_UYVY12_1X24; + else if (hdmi->colordepth == 10) + hdmi->output_bus_format = MEDIA_BUS_FMT_UYVY10_1X20; + else + hdmi->output_bus_format = MEDIA_BUS_FMT_UYVY8_1X16; + } else { + hdmi->output_bus_format = hdmi->bus_format; + } + + /* RK3368 does not support deep color mode */ + if (!hdmi->color_depth_property && !hdmi->unsupported_deep_color) { + prop = drm_property_create_enum(connector->dev, 0, + RK_IF_PROP_COLOR_DEPTH, + color_depth_enum_list, + ARRAY_SIZE(color_depth_enum_list)); + if (prop) { + hdmi->color_depth_property = prop; + drm_object_attach_property(&connector->base, prop, 0); + } + } + + prop = drm_property_create_enum(connector->dev, 0, RK_IF_PROP_COLOR_FORMAT, + drm_hdmi_output_enum_list, + ARRAY_SIZE(drm_hdmi_output_enum_list)); + if (prop) { + hdmi->hdmi_output_property = prop; + drm_object_attach_property(&connector->base, prop, 0); + } + + prop = drm_property_create_range(connector->dev, 0, + RK_IF_PROP_COLOR_DEPTH_CAPS, + 0, 0xff); + if (prop) { + hdmi->colordepth_capacity = prop; + drm_object_attach_property(&connector->base, prop, 0); + } + + prop = drm_property_create_range(connector->dev, 0, + RK_IF_PROP_COLOR_FORMAT_CAPS, + 0, 0xf); + if (prop) { + hdmi->outputmode_capacity = prop; + drm_object_attach_property(&connector->base, prop, 0); + } + + prop = drm_property_create(connector->dev, + DRM_MODE_PROP_BLOB | + DRM_MODE_PROP_IMMUTABLE, + "HDR_PANEL_METADATA", 0); + if (prop) { + hdmi->hdr_panel_metadata_property = prop; + drm_object_attach_property(&connector->base, prop, 0); + } + + prop = drm_property_create(connector->dev, + DRM_MODE_PROP_BLOB | + DRM_MODE_PROP_IMMUTABLE, + "NEXT_HDR_SINK_DATA", 0); + if (prop) { + hdmi->next_hdr_sink_data_property = prop; + drm_object_attach_property(&connector->base, prop, 0); + } + + prop = drm_property_create_bool(connector->dev, DRM_MODE_PROP_IMMUTABLE, + "USER_SPLIT_MODE"); + if (prop) { + hdmi->user_split_mode_prop = prop; + drm_object_attach_property(&connector->base, prop, + hdmi->user_split_mode ? 1 : 0); + } + + if (!hdmi->is_hdmi_qp) { + prop = drm_property_create_enum(connector->dev, 0, + "output_hdmi_dvi", + output_hdmi_dvi_enum_list, + ARRAY_SIZE(output_hdmi_dvi_enum_list)); + if (prop) { + hdmi->output_hdmi_dvi = prop; + drm_object_attach_property(&connector->base, prop, 0); + } + + prop = drm_property_create_enum(connector->dev, 0, + "output_type_capacity", + output_type_cap_list, + ARRAY_SIZE(output_type_cap_list)); + if (prop) { + hdmi->output_type_capacity = prop; + drm_object_attach_property(&connector->base, prop, 0); + } + + prop = drm_property_create_enum(connector->dev, 0, + "hdmi_quant_range", + quant_range_enum_list, + ARRAY_SIZE(quant_range_enum_list)); + if (prop) { + hdmi->quant_range = prop; + drm_object_attach_property(&connector->base, prop, 0); + } + } + + prop = connector->dev->mode_config.hdr_output_metadata_property; + if (version >= 0x211a || hdmi->is_hdmi_qp) + drm_object_attach_property(&connector->base, prop, 0); + + if (!drm_mode_create_hdmi_colorspace_property(connector, 0)) + drm_object_attach_property(&connector->base, + connector->colorspace_property, 0); + + // [CC:] if this is not needed, also drop connector_id_prop + if (!private->connector_id_prop) + private->connector_id_prop = drm_property_create_range(connector->dev, + DRM_MODE_PROP_ATOMIC | DRM_MODE_PROP_IMMUTABLE, + "CONNECTOR_ID", 0, 0xf); + if (private->connector_id_prop) + drm_object_attach_property(&connector->base, private->connector_id_prop, hdmi->id); +} + +static void +dw_hdmi_rockchip_destroy_properties(struct drm_connector *connector, + void *data) +{ + struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; + + if (hdmi->color_depth_property) { + drm_property_destroy(connector->dev, + hdmi->color_depth_property); + hdmi->color_depth_property = NULL; + } + + if (hdmi->hdmi_output_property) { + drm_property_destroy(connector->dev, + hdmi->hdmi_output_property); + hdmi->hdmi_output_property = NULL; + } + + if (hdmi->colordepth_capacity) { + drm_property_destroy(connector->dev, + hdmi->colordepth_capacity); + hdmi->colordepth_capacity = NULL; + } + + if (hdmi->outputmode_capacity) { + drm_property_destroy(connector->dev, + hdmi->outputmode_capacity); + hdmi->outputmode_capacity = NULL; + } + + if (hdmi->quant_range) { + drm_property_destroy(connector->dev, + hdmi->quant_range); + hdmi->quant_range = NULL; + } + + if (hdmi->hdr_panel_metadata_property) { + drm_property_destroy(connector->dev, + hdmi->hdr_panel_metadata_property); + hdmi->hdr_panel_metadata_property = NULL; + } + + if (hdmi->next_hdr_sink_data_property) { + drm_property_destroy(connector->dev, + hdmi->next_hdr_sink_data_property); + hdmi->next_hdr_sink_data_property = NULL; + } + + if (hdmi->output_hdmi_dvi) { + drm_property_destroy(connector->dev, + hdmi->output_hdmi_dvi); + hdmi->output_hdmi_dvi = NULL; + } + + if (hdmi->output_type_capacity) { + drm_property_destroy(connector->dev, + hdmi->output_type_capacity); + hdmi->output_type_capacity = NULL; + } + + if (hdmi->user_split_mode_prop) { + drm_property_destroy(connector->dev, + hdmi->user_split_mode_prop); + hdmi->user_split_mode_prop = NULL; + } +} + +static int +dw_hdmi_rockchip_set_property(struct drm_connector *connector, + struct drm_connector_state *state, + struct drm_property *property, + u64 val, + void *data) +{ + struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; + struct drm_mode_config *config = &connector->dev->mode_config; + + if (property == hdmi->color_depth_property) { + hdmi->colordepth = val; + /* If hdmi is disconnected, state->crtc is null */ + if (!state->crtc) + return 0; + if (dw_hdmi_rockchip_check_color(state, hdmi)) + hdmi->color_changed++; + return 0; + } else if (property == hdmi->hdmi_output_property) { + hdmi->hdmi_output = val; + if (!state->crtc) + return 0; + if (dw_hdmi_rockchip_check_color(state, hdmi)) + hdmi->color_changed++; + return 0; + } else if (property == hdmi->quant_range) { + u64 quant_range = hdmi->hdmi_quant_range; + + hdmi->hdmi_quant_range = val; + if (quant_range != hdmi->hdmi_quant_range) + dw_hdmi_set_quant_range(hdmi->hdmi); + return 0; + } else if (property == config->hdr_output_metadata_property) { + return 0; + } else if (property == hdmi->output_hdmi_dvi) { + if (hdmi->force_output != val) + hdmi->color_changed++; + hdmi->force_output = val; + dw_hdmi_set_output_type(hdmi->hdmi, val); + return 0; + } else if (property == hdmi->colordepth_capacity) { + return 0; + } else if (property == hdmi->outputmode_capacity) { + return 0; + } else if (property == hdmi->output_type_capacity) { + return 0; + } + + DRM_ERROR("Unknown property [PROP:%d:%s]\n", + property->base.id, property->name); + + return -EINVAL; +} + +static int +dw_hdmi_rockchip_get_property(struct drm_connector *connector, + const struct drm_connector_state *state, + struct drm_property *property, + u64 *val, + void *data) +{ + struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; + struct drm_display_info *info = &connector->display_info; + struct drm_mode_config *config = &connector->dev->mode_config; + + if (property == hdmi->color_depth_property) { + *val = hdmi->colordepth; + return 0; + } else if (property == hdmi->hdmi_output_property) { + *val = hdmi->hdmi_output; + return 0; + } else if (property == hdmi->colordepth_capacity) { + *val = BIT(RK_IF_DEPTH_8); + /* RK3368 only support 8bit */ + if (hdmi->unsupported_deep_color) + return 0; + if (info->edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_30) + *val |= BIT(RK_IF_DEPTH_10); + if (info->edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_36) + *val |= BIT(RK_IF_DEPTH_12); + if (info->edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_48) + *val |= BIT(RK_IF_DEPTH_16); + if (info->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_30) + *val |= BIT(RK_IF_DEPTH_420_10); + if (info->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_36) + *val |= BIT(RK_IF_DEPTH_420_12); + if (info->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_48) + *val |= BIT(RK_IF_DEPTH_420_16); + return 0; + } else if (property == hdmi->outputmode_capacity) { + *val = BIT(RK_IF_FORMAT_RGB); + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR444) + *val |= BIT(RK_IF_FORMAT_YCBCR444); + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422) + *val |= BIT(RK_IF_FORMAT_YCBCR422); + if (connector->ycbcr_420_allowed && + info->color_formats & DRM_COLOR_FORMAT_YCBCR420) + *val |= BIT(RK_IF_FORMAT_YCBCR420); + return 0; + } else if (property == hdmi->quant_range) { + *val = hdmi->hdmi_quant_range; + return 0; + } else if (property == config->hdr_output_metadata_property) { + *val = state->hdr_output_metadata ? + state->hdr_output_metadata->base.id : 0; + return 0; + } else if (property == hdmi->output_hdmi_dvi) { + *val = hdmi->force_output; + return 0; + } else if (property == hdmi->output_type_capacity) { + *val = dw_hdmi_get_output_type_cap(hdmi->hdmi); + return 0; + } else if (property == hdmi->user_split_mode_prop) { + *val = hdmi->user_split_mode; + return 0; + } + + DRM_ERROR("Unknown property [PROP:%d:%s]\n", + property->base.id, property->name); + + return -EINVAL; +} + +static const struct dw_hdmi_property_ops dw_hdmi_rockchip_property_ops = { + .attach_properties = dw_hdmi_rockchip_attach_properties, + .destroy_properties = dw_hdmi_rockchip_destroy_properties, + .set_property = dw_hdmi_rockchip_set_property, + .get_property = dw_hdmi_rockchip_get_property, +}; + static const struct drm_encoder_helper_funcs dw_hdmi_rockchip_encoder_helper_funcs = { .mode_fixup = dw_hdmi_rockchip_encoder_mode_fixup, .mode_set = dw_hdmi_rockchip_encoder_mode_set, @@ -357,20 +2526,24 @@ static const struct drm_encoder_helper_funcs dw_hdmi_rockchip_encoder_helper_fun .atomic_check = dw_hdmi_rockchip_encoder_atomic_check, }; -static int dw_hdmi_rockchip_genphy_init(struct dw_hdmi *dw_hdmi, void *data, - const struct drm_display_info *display, - const struct drm_display_mode *mode) +static void dw_hdmi_rockchip_genphy_disable(struct dw_hdmi *dw_hdmi, void *data) { struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; - return phy_power_on(hdmi->phy); + while (hdmi->phy->power_count > 0) + phy_power_off(hdmi->phy); } -static void dw_hdmi_rockchip_genphy_disable(struct dw_hdmi *dw_hdmi, void *data) +static int dw_hdmi_rockchip_genphy_init(struct dw_hdmi *dw_hdmi, void *data, + const struct drm_display_info *display, + const struct drm_display_mode *mode) { struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; - phy_power_off(hdmi->phy); + dw_hdmi_rockchip_genphy_disable(dw_hdmi, data); + dw_hdmi_set_high_tmds_clock_ratio(dw_hdmi, display); + + return phy_power_on(hdmi->phy); } static void dw_hdmi_rk3228_setup_hpd(struct dw_hdmi *dw_hdmi, void *data) @@ -437,6 +2610,90 @@ static void dw_hdmi_rk3328_setup_hpd(struct dw_hdmi *dw_hdmi, void *data) RK3328_HDMI_HPD_IOE)); } +static void dw_hdmi_qp_rockchip_phy_disable(struct dw_hdmi_qp *dw_hdmi, + void *data) +{ + struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; + + while (hdmi->phy->power_count > 0) + phy_power_off(hdmi->phy); +} + +static int dw_hdmi_qp_rockchip_genphy_init(struct dw_hdmi_qp *dw_hdmi, void *data, + struct drm_display_mode *mode) +{ + struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; + + dw_hdmi_qp_rockchip_phy_disable(dw_hdmi, data); + + return phy_power_on(hdmi->phy); +} + +static enum drm_connector_status +dw_hdmi_rk3588_read_hpd(struct dw_hdmi_qp *dw_hdmi, void *data) +{ + u32 val; + int ret; + struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; + + regmap_read(hdmi->regmap, RK3588_GRF_SOC_STATUS1, &val); + + if (!hdmi->id) { + if (val & RK3588_HDMI0_LEVEL_INT) { + hdmi->hpd_stat = true; + ret = connector_status_connected; + } else { + hdmi->hpd_stat = false; + ret = connector_status_disconnected; + } + } else { + if (val & RK3588_HDMI1_LEVEL_INT) { + hdmi->hpd_stat = true; + ret = connector_status_connected; + } else { + hdmi->hpd_stat = false; + ret = connector_status_disconnected; + } + } + + return ret; +} + +static void dw_hdmi_rk3588_setup_hpd(struct dw_hdmi_qp *dw_hdmi, void *data) +{ + struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; + u32 val; + + if (!hdmi->id) { + val = HIWORD_UPDATE(RK3588_HDMI0_HPD_INT_CLR, + RK3588_HDMI0_HPD_INT_CLR) | + HIWORD_UPDATE(0, RK3588_HDMI0_HPD_INT_MSK); + } else { + val = HIWORD_UPDATE(RK3588_HDMI1_HPD_INT_CLR, + RK3588_HDMI1_HPD_INT_CLR) | + HIWORD_UPDATE(0, RK3588_HDMI1_HPD_INT_MSK); + } + + regmap_write(hdmi->regmap, RK3588_GRF_SOC_CON2, val); +} + +static void dw_hdmi_rk3588_phy_set_mode(struct dw_hdmi_qp *dw_hdmi, void *data, + u32 mode_mask, bool enable) +{ + struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; + + if (!hdmi->phy) + return; + + /* set phy earc/frl mode */ + if (enable) + hdmi->phy_bus_width |= mode_mask; + else + hdmi->phy_bus_width &= ~mode_mask; + + phy_set_bus_width(hdmi->phy, hdmi->phy_bus_width); +} + static const struct dw_hdmi_phy_ops rk3228_hdmi_phy_ops = { .init = dw_hdmi_rockchip_genphy_init, .disable = dw_hdmi_rockchip_genphy_disable, @@ -526,6 +2783,30 @@ static const struct dw_hdmi_plat_data rk3568_hdmi_drv_data = { .use_drm_infoframe = true, }; +static const struct dw_hdmi_qp_phy_ops rk3588_hdmi_phy_ops = { + .init = dw_hdmi_qp_rockchip_genphy_init, + .disable = dw_hdmi_qp_rockchip_phy_disable, + .read_hpd = dw_hdmi_rk3588_read_hpd, + .setup_hpd = dw_hdmi_rk3588_setup_hpd, + .set_mode = dw_hdmi_rk3588_phy_set_mode, +}; + +struct rockchip_hdmi_chip_data rk3588_hdmi_chip_data = { + .lcdsel_grf_reg = -1, + .ddc_en_reg = RK3588_GRF_VO1_CON3, + .split_mode = true, +}; + +static const struct dw_hdmi_plat_data rk3588_hdmi_drv_data = { + .phy_data = &rk3588_hdmi_chip_data, + .qp_phy_ops = &rk3588_hdmi_phy_ops, + .phy_name = "samsung_hdptx_phy", + .phy_force_vendor = true, + .ycbcr_420_allowed = true, + .is_hdmi_qp = true, + .use_drm_infoframe = true, +}; + static const struct of_device_id dw_hdmi_rockchip_dt_ids[] = { { .compatible = "rockchip,rk3228-dw-hdmi", .data = &rk3228_hdmi_drv_data @@ -542,6 +2823,9 @@ static const struct of_device_id dw_hdmi_rockchip_dt_ids[] = { { .compatible = "rockchip,rk3568-dw-hdmi", .data = &rk3568_hdmi_drv_data }, + { .compatible = "rockchip,rk3588-dw-hdmi", + .data = &rk3588_hdmi_drv_data + }, {}, }; MODULE_DEVICE_TABLE(of, dw_hdmi_rockchip_dt_ids); @@ -551,44 +2835,107 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, { struct platform_device *pdev = to_platform_device(dev); struct dw_hdmi_plat_data *plat_data; - const struct of_device_id *match; struct drm_device *drm = data; struct drm_encoder *encoder; struct rockchip_hdmi *hdmi; + struct rockchip_hdmi *secondary; int ret; + u32 val; if (!pdev->dev.of_node) return -ENODEV; - hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL); + hdmi = platform_get_drvdata(pdev); if (!hdmi) return -ENOMEM; - match = of_match_node(dw_hdmi_rockchip_dt_ids, pdev->dev.of_node); - plat_data = devm_kmemdup(&pdev->dev, match->data, - sizeof(*plat_data), GFP_KERNEL); - if (!plat_data) - return -ENOMEM; + plat_data = hdmi->plat_data; + hdmi->drm_dev = drm; - hdmi->dev = &pdev->dev; - hdmi->plat_data = plat_data; - hdmi->chip_data = plat_data->phy_data; plat_data->phy_data = hdmi; - plat_data->priv_data = hdmi; - encoder = &hdmi->encoder.encoder; + plat_data->get_input_bus_format = + dw_hdmi_rockchip_get_input_bus_format; + plat_data->get_output_bus_format = + dw_hdmi_rockchip_get_output_bus_format; + plat_data->get_enc_in_encoding = + dw_hdmi_rockchip_get_enc_in_encoding; + plat_data->get_enc_out_encoding = + dw_hdmi_rockchip_get_enc_out_encoding; + plat_data->get_quant_range = + dw_hdmi_rockchip_get_quant_range; + plat_data->get_hdr_property = + dw_hdmi_rockchip_get_hdr_property; + plat_data->get_hdr_blob = + dw_hdmi_rockchip_get_hdr_blob; + plat_data->get_color_changed = + dw_hdmi_rockchip_get_color_changed; + plat_data->get_yuv422_format = + dw_hdmi_rockchip_get_yuv422_format; + plat_data->get_edid_dsc_info = + dw_hdmi_rockchip_get_edid_dsc_info; + plat_data->get_next_hdr_data = + dw_hdmi_rockchip_get_next_hdr_data; + plat_data->get_link_cfg = dw_hdmi_rockchip_get_link_cfg; + plat_data->set_grf_cfg = rk3588_set_grf_cfg; + plat_data->convert_to_split_mode = drm_mode_convert_to_split_mode; + plat_data->convert_to_origin_mode = drm_mode_convert_to_origin_mode; + plat_data->dclk_set = dw_hdmi_dclk_set; + + plat_data->property_ops = &dw_hdmi_rockchip_property_ops; + + secondary = rockchip_hdmi_find_by_id(dev->driver, !hdmi->id); + /* If don't enable hdmi0 and hdmi1, we don't enable split mode */ + if (hdmi->chip_data->split_mode && secondary) { - encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); - rockchip_drm_encoder_set_crtc_endpoint_id(&hdmi->encoder, - dev->of_node, 0, 0); + /* + * hdmi can only attach bridge and init encoder/connector in the + * last bind hdmi in split mode, or hdmi->hdmi_qp will not be initialized + * and plat_data->left/right will be null pointer. we must check if split + * mode is on and determine the sequence of hdmi bind. + */ + if (device_property_read_bool(dev, "split-mode") || + device_property_read_bool(secondary->dev, "split-mode")) { + plat_data->split_mode = true; + secondary->plat_data->split_mode = true; + if (!secondary->plat_data->first_screen) + plat_data->first_screen = true; + } + + if (device_property_read_bool(dev, "user-split-mode") || + device_property_read_bool(secondary->dev, "user-split-mode")) { + hdmi->user_split_mode = true; + secondary->user_split_mode = true; + } + } - /* - * If we failed to find the CRTC(s) which this encoder is - * supposed to be connected to, it's because the CRTC has - * not been registered yet. Defer probing, and hope that - * the required CRTC is added later. - */ - if (encoder->possible_crtcs == 0) - return -EPROBE_DEFER; + if (!plat_data->first_screen) { + encoder = &hdmi->encoder.encoder; + encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); + rockchip_drm_encoder_set_crtc_endpoint_id(&hdmi->encoder, + dev->of_node, 0, 0); + /* + * If we failed to find the CRTC(s) which this encoder is + * supposed to be connected to, it's because the CRTC has + * not been registered yet. Defer probing, and hope that + * the required CRTC is added later. + */ + if (encoder->possible_crtcs == 0) + return -EPROBE_DEFER; + + drm_encoder_helper_add(encoder, &dw_hdmi_rockchip_encoder_helper_funcs); + // [CC:] consider using drmm_simple_encoder_alloc() + drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS); + } + + if (!plat_data->max_tmdsclk) + hdmi->max_tmdsclk = 594000; + else + hdmi->max_tmdsclk = plat_data->max_tmdsclk; + + hdmi->is_hdmi_qp = plat_data->is_hdmi_qp; + + hdmi->unsupported_yuv_input = plat_data->unsupported_yuv_input; + hdmi->unsupported_deep_color = plat_data->unsupported_deep_color; ret = rockchip_hdmi_parse_dt(hdmi); if (ret) { @@ -597,34 +2944,44 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, return ret; } - hdmi->phy = devm_phy_optional_get(dev, "hdmi"); - if (IS_ERR(hdmi->phy)) { - ret = PTR_ERR(hdmi->phy); - if (ret != -EPROBE_DEFER) - DRM_DEV_ERROR(hdmi->dev, "failed to get phy\n"); + ret = clk_prepare_enable(hdmi->aud_clk); + if (ret) { + dev_err(hdmi->dev, "Failed to enable HDMI aud_clk: %d\n", ret); return ret; } - ret = regulator_enable(hdmi->avdd_0v9); + ret = clk_prepare_enable(hdmi->hpd_clk); if (ret) { - DRM_DEV_ERROR(hdmi->dev, "failed to enable avdd0v9: %d\n", ret); - goto err_avdd_0v9; + dev_err(hdmi->dev, "Failed to enable HDMI hpd_clk: %d\n", ret); + return ret; } - ret = regulator_enable(hdmi->avdd_1v8); + ret = clk_prepare_enable(hdmi->hclk_vo1); if (ret) { - DRM_DEV_ERROR(hdmi->dev, "failed to enable avdd1v8: %d\n", ret); - goto err_avdd_1v8; + dev_err(hdmi->dev, "Failed to enable HDMI hclk_vo1: %d\n", ret); + return ret; } - ret = clk_prepare_enable(hdmi->ref_clk); + ret = clk_prepare_enable(hdmi->earc_clk); if (ret) { - DRM_DEV_ERROR(hdmi->dev, "Failed to enable HDMI reference clock: %d\n", - ret); - goto err_clk; + dev_err(hdmi->dev, "Failed to enable HDMI earc_clk: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(hdmi->hdmitx_ref); + if (ret) { + dev_err(hdmi->dev, "Failed to enable HDMI hdmitx_ref: %d\n", + ret); + return ret; + } + + ret = clk_prepare_enable(hdmi->pclk); + if (ret) { + dev_err(hdmi->dev, "Failed to enable HDMI pclk: %d\n", ret); + return ret; } - if (hdmi->chip_data == &rk3568_chip_data) { + if (hdmi->chip_data->ddc_en_reg == RK3568_GRF_VO_CON1) { regmap_write(hdmi->regmap, RK3568_GRF_VO_CON1, HIWORD_UPDATE(RK3568_HDMI_SDAIN_MSK | RK3568_HDMI_SCLIN_MSK, @@ -632,12 +2989,132 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, RK3568_HDMI_SCLIN_MSK)); } - drm_encoder_helper_add(encoder, &dw_hdmi_rockchip_encoder_helper_funcs); - drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS); + if (hdmi->is_hdmi_qp) { + if (!hdmi->id) { + val = HIWORD_UPDATE(RK3588_SCLIN_MASK, RK3588_SCLIN_MASK) | + HIWORD_UPDATE(RK3588_SDAIN_MASK, RK3588_SDAIN_MASK) | + HIWORD_UPDATE(RK3588_MODE_MASK, RK3588_MODE_MASK) | + HIWORD_UPDATE(RK3588_I2S_SEL_MASK, RK3588_I2S_SEL_MASK); + regmap_write(hdmi->vo1_regmap, RK3588_GRF_VO1_CON3, val); + + val = HIWORD_UPDATE(RK3588_SET_HPD_PATH_MASK, + RK3588_SET_HPD_PATH_MASK); + regmap_write(hdmi->regmap, RK3588_GRF_SOC_CON7, val); + + val = HIWORD_UPDATE(RK3588_HDMI0_GRANT_SEL, + RK3588_HDMI0_GRANT_SEL); + regmap_write(hdmi->vo1_regmap, RK3588_GRF_VO1_CON9, val); + } else { + val = HIWORD_UPDATE(RK3588_SCLIN_MASK, RK3588_SCLIN_MASK) | + HIWORD_UPDATE(RK3588_SDAIN_MASK, RK3588_SDAIN_MASK) | + HIWORD_UPDATE(RK3588_MODE_MASK, RK3588_MODE_MASK) | + HIWORD_UPDATE(RK3588_I2S_SEL_MASK, RK3588_I2S_SEL_MASK); + regmap_write(hdmi->vo1_regmap, RK3588_GRF_VO1_CON6, val); + + val = HIWORD_UPDATE(RK3588_SET_HPD_PATH_MASK, + RK3588_SET_HPD_PATH_MASK); + regmap_write(hdmi->regmap, RK3588_GRF_SOC_CON7, val); + + val = HIWORD_UPDATE(RK3588_HDMI1_GRANT_SEL, + RK3588_HDMI1_GRANT_SEL); + regmap_write(hdmi->vo1_regmap, RK3588_GRF_VO1_CON9, val); + } + init_hpd_work(hdmi); + } - platform_set_drvdata(pdev, hdmi); + ret = clk_prepare_enable(hdmi->hclk_vio); + if (ret) { + dev_err(hdmi->dev, "Failed to enable HDMI hclk_vio: %d\n", + ret); + return ret; + } - hdmi->hdmi = dw_hdmi_bind(pdev, encoder, plat_data); + ret = clk_prepare_enable(hdmi->hclk_vop); + if (ret) { + dev_err(hdmi->dev, "Failed to enable HDMI hclk_vop: %d\n", + ret); + return ret; + } + + ret = clk_prepare_enable(hdmi->ref_clk); + if (ret) { + DRM_DEV_ERROR(hdmi->dev, "Failed to enable HDMI reference clock: %d\n", + ret); + goto err_clk; + } + + ret = regulator_enable(hdmi->avdd_0v9); + if (ret) { + DRM_DEV_ERROR(hdmi->dev, "failed to enable avdd0v9: %d\n", ret); + goto err_avdd_0v9; + } + + ret = regulator_enable(hdmi->avdd_1v8); + if (ret) { + DRM_DEV_ERROR(hdmi->dev, "failed to enable avdd1v8: %d\n", ret); + goto err_avdd_1v8; + } + + if (!hdmi->id) + val = HIWORD_UPDATE(RK3588_HDMI0_HPD_INT_MSK, RK3588_HDMI0_HPD_INT_MSK); + else + val = HIWORD_UPDATE(RK3588_HDMI1_HPD_INT_MSK, RK3588_HDMI1_HPD_INT_MSK); + regmap_write(hdmi->regmap, RK3588_GRF_SOC_CON2, val); + + if (hdmi->is_hdmi_qp) { + hdmi->hpd_irq = platform_get_irq(pdev, 4); + if (hdmi->hpd_irq < 0) + return hdmi->hpd_irq; + + ret = devm_request_threaded_irq(hdmi->dev, hdmi->hpd_irq, + rockchip_hdmi_hardirq, + rockchip_hdmi_irq, + IRQF_SHARED, "dw-hdmi-qp-hpd", + hdmi); + if (ret) + return ret; + } + + hdmi->phy = devm_phy_optional_get(dev, "hdmi"); + if (IS_ERR(hdmi->phy)) { + hdmi->phy = devm_phy_optional_get(dev, "hdmi_phy"); + if (IS_ERR(hdmi->phy)) { + ret = PTR_ERR(hdmi->phy); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(hdmi->dev, "failed to get phy\n"); + return ret; + } + } + + if (hdmi->is_hdmi_qp) { + // [CC:] do proper error handling, e.g. clk_disable_unprepare + hdmi->hdmi_qp = dw_hdmi_qp_bind(pdev, &hdmi->encoder.encoder, plat_data); + + if (IS_ERR(hdmi->hdmi_qp)) { + ret = PTR_ERR(hdmi->hdmi_qp); + drm_encoder_cleanup(&hdmi->encoder.encoder); + } + + if (plat_data->connector) { + hdmi->sub_dev.connector = plat_data->connector; + hdmi->sub_dev.of_node = dev->of_node; + rockchip_drm_register_sub_dev(&hdmi->sub_dev); + } + + if (plat_data->split_mode && secondary) { + if (device_property_read_bool(dev, "split-mode")) { + plat_data->right = secondary->hdmi_qp; + secondary->plat_data->left = hdmi->hdmi_qp; + } else { + plat_data->left = secondary->hdmi_qp; + secondary->plat_data->right = hdmi->hdmi_qp; + } + } + + return ret; + } + + hdmi->hdmi = dw_hdmi_bind(pdev, &hdmi->encoder.encoder, plat_data); /* * If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(), @@ -648,11 +3125,24 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, goto err_bind; } + if (plat_data->connector) { + hdmi->sub_dev.connector = plat_data->connector; + hdmi->sub_dev.of_node = dev->of_node; + rockchip_drm_register_sub_dev(&hdmi->sub_dev); + } + return 0; err_bind: - drm_encoder_cleanup(encoder); + drm_encoder_cleanup(&hdmi->encoder.encoder); + clk_disable_unprepare(hdmi->aud_clk); clk_disable_unprepare(hdmi->ref_clk); + clk_disable_unprepare(hdmi->hclk_vop); + clk_disable_unprepare(hdmi->hpd_clk); + clk_disable_unprepare(hdmi->hclk_vo1); + clk_disable_unprepare(hdmi->earc_clk); + clk_disable_unprepare(hdmi->hdmitx_ref); + clk_disable_unprepare(hdmi->pclk); err_clk: regulator_disable(hdmi->avdd_1v8); err_avdd_1v8: @@ -666,9 +3156,30 @@ static void dw_hdmi_rockchip_unbind(struct device *dev, struct device *master, { struct rockchip_hdmi *hdmi = dev_get_drvdata(dev); - dw_hdmi_unbind(hdmi->hdmi); + if (hdmi->is_hdmi_qp) { + cancel_delayed_work(&hdmi->work); + flush_workqueue(hdmi->workqueue); + destroy_workqueue(hdmi->workqueue); + } + + if (hdmi->sub_dev.connector) + rockchip_drm_unregister_sub_dev(&hdmi->sub_dev); + + if (hdmi->is_hdmi_qp) + dw_hdmi_qp_unbind(hdmi->hdmi_qp); + else + dw_hdmi_unbind(hdmi->hdmi); + drm_encoder_cleanup(&hdmi->encoder.encoder); + + clk_disable_unprepare(hdmi->aud_clk); clk_disable_unprepare(hdmi->ref_clk); + clk_disable_unprepare(hdmi->hclk_vop); + clk_disable_unprepare(hdmi->hpd_clk); + clk_disable_unprepare(hdmi->hclk_vo1); + clk_disable_unprepare(hdmi->earc_clk); + clk_disable_unprepare(hdmi->hdmitx_ref); + clk_disable_unprepare(hdmi->pclk); regulator_disable(hdmi->avdd_1v8); regulator_disable(hdmi->avdd_0v9); @@ -681,30 +3192,131 @@ static const struct component_ops dw_hdmi_rockchip_ops = { static int dw_hdmi_rockchip_probe(struct platform_device *pdev) { + struct rockchip_hdmi *hdmi; + const struct of_device_id *match; + struct dw_hdmi_plat_data *plat_data; + int id; + + hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL); + if (!hdmi) + return -ENOMEM; + + id = of_alias_get_id(pdev->dev.of_node, "hdmi"); + if (id < 0) + id = 0; + + hdmi->id = id; + hdmi->dev = &pdev->dev; + + match = of_match_node(dw_hdmi_rockchip_dt_ids, pdev->dev.of_node); + plat_data = devm_kmemdup(&pdev->dev, match->data, + sizeof(*plat_data), GFP_KERNEL); + if (!plat_data) + return -ENOMEM; + + plat_data->id = hdmi->id; + hdmi->plat_data = plat_data; + hdmi->chip_data = plat_data->phy_data; + + platform_set_drvdata(pdev, hdmi); + pm_runtime_enable(&pdev->dev); + pm_runtime_get_sync(&pdev->dev); + return component_add(&pdev->dev, &dw_hdmi_rockchip_ops); } +static void dw_hdmi_rockchip_shutdown(struct platform_device *pdev) +{ + struct rockchip_hdmi *hdmi = dev_get_drvdata(&pdev->dev); + + if (!hdmi) + return; + + if (hdmi->is_hdmi_qp) { + cancel_delayed_work(&hdmi->work); + flush_workqueue(hdmi->workqueue); + dw_hdmi_qp_suspend(hdmi->dev, hdmi->hdmi_qp); + } else { + dw_hdmi_suspend(hdmi->hdmi); + } + pm_runtime_put_sync(&pdev->dev); +} + static void dw_hdmi_rockchip_remove(struct platform_device *pdev) { component_del(&pdev->dev, &dw_hdmi_rockchip_ops); + pm_runtime_disable(&pdev->dev); +} + +static int dw_hdmi_rockchip_suspend(struct device *dev) +{ + struct rockchip_hdmi *hdmi = dev_get_drvdata(dev); + + if (hdmi->is_hdmi_qp) + dw_hdmi_qp_suspend(dev, hdmi->hdmi_qp); + else + dw_hdmi_suspend(hdmi->hdmi); + + pm_runtime_put_sync(dev); + + return 0; } static int __maybe_unused dw_hdmi_rockchip_resume(struct device *dev) { struct rockchip_hdmi *hdmi = dev_get_drvdata(dev); + u32 val; - dw_hdmi_resume(hdmi->hdmi); + if (hdmi->is_hdmi_qp) { + if (!hdmi->id) { + val = HIWORD_UPDATE(RK3588_SCLIN_MASK, RK3588_SCLIN_MASK) | + HIWORD_UPDATE(RK3588_SDAIN_MASK, RK3588_SDAIN_MASK) | + HIWORD_UPDATE(RK3588_MODE_MASK, RK3588_MODE_MASK) | + HIWORD_UPDATE(RK3588_I2S_SEL_MASK, RK3588_I2S_SEL_MASK); + regmap_write(hdmi->vo1_regmap, RK3588_GRF_VO1_CON3, val); + + val = HIWORD_UPDATE(RK3588_SET_HPD_PATH_MASK, + RK3588_SET_HPD_PATH_MASK); + regmap_write(hdmi->regmap, RK3588_GRF_SOC_CON7, val); + + val = HIWORD_UPDATE(RK3588_HDMI0_GRANT_SEL, + RK3588_HDMI0_GRANT_SEL); + regmap_write(hdmi->vo1_regmap, RK3588_GRF_VO1_CON9, val); + } else { + val = HIWORD_UPDATE(RK3588_SCLIN_MASK, RK3588_SCLIN_MASK) | + HIWORD_UPDATE(RK3588_SDAIN_MASK, RK3588_SDAIN_MASK) | + HIWORD_UPDATE(RK3588_MODE_MASK, RK3588_MODE_MASK) | + HIWORD_UPDATE(RK3588_I2S_SEL_MASK, RK3588_I2S_SEL_MASK); + regmap_write(hdmi->vo1_regmap, RK3588_GRF_VO1_CON6, val); + + val = HIWORD_UPDATE(RK3588_SET_HPD_PATH_MASK, + RK3588_SET_HPD_PATH_MASK); + regmap_write(hdmi->regmap, RK3588_GRF_SOC_CON7, val); + + val = HIWORD_UPDATE(RK3588_HDMI1_GRANT_SEL, + RK3588_HDMI1_GRANT_SEL); + regmap_write(hdmi->vo1_regmap, RK3588_GRF_VO1_CON9, val); + } + + dw_hdmi_qp_resume(dev, hdmi->hdmi_qp); + drm_helper_hpd_irq_event(hdmi->drm_dev); + } else { + dw_hdmi_resume(hdmi->hdmi); + } + pm_runtime_get_sync(dev); return 0; } static const struct dev_pm_ops dw_hdmi_rockchip_pm = { - SET_SYSTEM_SLEEP_PM_OPS(NULL, dw_hdmi_rockchip_resume) + SET_SYSTEM_SLEEP_PM_OPS(dw_hdmi_rockchip_suspend, + dw_hdmi_rockchip_resume) }; struct platform_driver dw_hdmi_rockchip_pltfm_driver = { .probe = dw_hdmi_rockchip_probe, .remove_new = dw_hdmi_rockchip_remove, + .shutdown = dw_hdmi_rockchip_shutdown, .driver = { .name = "dwhdmi-rockchip", .pm = &dw_hdmi_rockchip_pm, diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c index ab55d7132..44d49c86b 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c @@ -17,8 +17,12 @@ #include #include +#include +#include +#include #include #include +//#include #include #include #include @@ -44,6 +48,629 @@ static const struct drm_driver rockchip_drm_driver; +void drm_mode_convert_to_split_mode(struct drm_display_mode *mode) +{ + u16 hactive, hfp, hsync, hbp; + + hactive = mode->hdisplay; + hfp = mode->hsync_start - mode->hdisplay; + hsync = mode->hsync_end - mode->hsync_start; + hbp = mode->htotal - mode->hsync_end; + + mode->clock *= 2; + mode->hdisplay = hactive * 2; + mode->hsync_start = mode->hdisplay + hfp * 2; + mode->hsync_end = mode->hsync_start + hsync * 2; + mode->htotal = mode->hsync_end + hbp * 2; + drm_mode_set_name(mode); +} +EXPORT_SYMBOL(drm_mode_convert_to_split_mode); + +void drm_mode_convert_to_origin_mode(struct drm_display_mode *mode) +{ + u16 hactive, hfp, hsync, hbp; + + hactive = mode->hdisplay; + hfp = mode->hsync_start - mode->hdisplay; + hsync = mode->hsync_end - mode->hsync_start; + hbp = mode->htotal - mode->hsync_end; + + mode->clock /= 2; + mode->hdisplay = hactive / 2; + mode->hsync_start = mode->hdisplay + hfp / 2; + mode->hsync_end = mode->hsync_start + hsync / 2; + mode->htotal = mode->hsync_end + hbp / 2; +} +EXPORT_SYMBOL(drm_mode_convert_to_origin_mode); + +static DEFINE_MUTEX(rockchip_drm_sub_dev_lock); +static LIST_HEAD(rockchip_drm_sub_dev_list); + +void rockchip_drm_register_sub_dev(struct rockchip_drm_sub_dev *sub_dev) +{ + mutex_lock(&rockchip_drm_sub_dev_lock); + list_add_tail(&sub_dev->list, &rockchip_drm_sub_dev_list); + mutex_unlock(&rockchip_drm_sub_dev_lock); +} +EXPORT_SYMBOL(rockchip_drm_register_sub_dev); + +void rockchip_drm_unregister_sub_dev(struct rockchip_drm_sub_dev *sub_dev) +{ + mutex_lock(&rockchip_drm_sub_dev_lock); + list_del(&sub_dev->list); + mutex_unlock(&rockchip_drm_sub_dev_lock); +} +EXPORT_SYMBOL(rockchip_drm_unregister_sub_dev); + +static int +cea_db_tag(const u8 *db) +{ + return db[0] >> 5; +} + +static int +cea_db_payload_len(const u8 *db) +{ + return db[0] & 0x1f; +} + +#define for_each_cea_db(cea, i, start, end) \ + for ((i) = (start); \ + (i) < (end) && (i) + cea_db_payload_len(&(cea)[(i)]) < (end); \ + (i) += cea_db_payload_len(&(cea)[(i)]) + 1) + +#define HDMI_NEXT_HDR_VSDB_OUI 0xd04601 + +static bool cea_db_is_hdmi_next_hdr_block(const u8 *db) +{ + unsigned int oui; + + if (cea_db_tag(db) != 0x07) + return false; + + if (cea_db_payload_len(db) < 11) + return false; + + oui = db[3] << 16 | db[2] << 8 | db[1]; + + return oui == HDMI_NEXT_HDR_VSDB_OUI; +} + +static bool cea_db_is_hdmi_forum_vsdb(const u8 *db) +{ + unsigned int oui; + + if (cea_db_tag(db) != 0x03) + return false; + + if (cea_db_payload_len(db) < 7) + return false; + + oui = db[3] << 16 | db[2] << 8 | db[1]; + + return oui == HDMI_FORUM_IEEE_OUI; +} + +static int +cea_db_offsets(const u8 *cea, int *start, int *end) +{ + /* DisplayID CTA extension blocks and top-level CEA EDID + * block header definitions differ in the following bytes: + * 1) Byte 2 of the header specifies length differently, + * 2) Byte 3 is only present in the CEA top level block. + * + * The different definitions for byte 2 follow. + * + * DisplayID CTA extension block defines byte 2 as: + * Number of payload bytes + * + * CEA EDID block defines byte 2 as: + * Byte number (decimal) within this block where the 18-byte + * DTDs begin. If no non-DTD data is present in this extension + * block, the value should be set to 04h (the byte after next). + * If set to 00h, there are no DTDs present in this block and + * no non-DTD data. + */ + if (cea[0] == 0x81) { + /* + * for_each_displayid_db() has already verified + * that these stay within expected bounds. + */ + *start = 3; + *end = *start + cea[2]; + } else if (cea[0] == 0x02) { + /* Data block offset in CEA extension block */ + *start = 4; + *end = cea[2]; + if (*end == 0) + *end = 127; + if (*end < 4 || *end > 127) + return -ERANGE; + } else { + return -EOPNOTSUPP; + } + + return 0; +} + +static u8 *find_edid_extension(const struct edid *edid, + int ext_id, int *ext_index) +{ + u8 *edid_ext = NULL; + int i; + + /* No EDID or EDID extensions */ + if (edid == NULL || edid->extensions == 0) + return NULL; + + /* Find CEA extension */ + for (i = *ext_index; i < edid->extensions; i++) { + edid_ext = (u8 *)edid + EDID_LENGTH * (i + 1); + if (edid_ext[0] == ext_id) + break; + } + + if (i >= edid->extensions) + return NULL; + + *ext_index = i + 1; + + return edid_ext; +} + +static int validate_displayid(u8 *displayid, int length, int idx) +{ + int i, dispid_length; + u8 csum = 0; + struct displayid_header *base; + + base = (struct displayid_header *)&displayid[idx]; + + DRM_DEBUG_KMS("base revision 0x%x, length %d, %d %d\n", + base->rev, base->bytes, base->prod_id, base->ext_count); + + /* +1 for DispID checksum */ + dispid_length = sizeof(*base) + base->bytes + 1; + if (dispid_length > length - idx) + return -EINVAL; + + for (i = 0; i < dispid_length; i++) + csum += displayid[idx + i]; + if (csum) { + DRM_NOTE("DisplayID checksum invalid, remainder is %d\n", csum); + return -EINVAL; + } + + return 0; +} + +static u8 *find_displayid_extension(const struct edid *edid, + int *length, int *idx, + int *ext_index) +{ + u8 *displayid = find_edid_extension(edid, 0x70, ext_index); + struct displayid_header *base; + int ret; + + if (!displayid) + return NULL; + + /* EDID extensions block checksum isn't for us */ + *length = EDID_LENGTH - 1; + *idx = 1; + + ret = validate_displayid(displayid, *length, *idx); + if (ret) + return NULL; + + base = (struct displayid_header *)&displayid[*idx]; + *length = *idx + sizeof(*base) + base->bytes; + + return displayid; +} + +static u8 *find_cea_extension(const struct edid *edid) +{ + int length, idx; + const struct displayid_block *block; + u8 *cea; + u8 *displayid; + int ext_index; + + /* Look for a top level CEA extension block */ + /* FIXME: make callers iterate through multiple CEA ext blocks? */ + ext_index = 0; + cea = find_edid_extension(edid, 0x02, &ext_index); + if (cea) + return cea; + + /* CEA blocks can also be found embedded in a DisplayID block */ + ext_index = 0; + for (;;) { + struct displayid_iter iter; + const struct drm_edid *drm_edid; + int edid_size = (edid->extensions + 1) * EDID_LENGTH; + + drm_edid = drm_edid_alloc(edid, edid_size); + if (!drm_edid) + return NULL; + displayid = find_displayid_extension(edid, &length, &idx, + &ext_index); + if (!displayid) { + drm_edid_free(drm_edid); + return NULL; + } + + displayid_iter_edid_begin(drm_edid, &iter); + idx += sizeof(struct displayid_header); + displayid_iter_for_each(block, &iter) { + if (block->tag == 0x81) { + displayid_iter_end(&iter); + drm_edid_free(drm_edid); + return (u8 *)block; + } + } + + displayid_iter_end(&iter); + drm_edid_free(drm_edid); + } + + return NULL; +} + +#define EDID_CEA_YCRCB422 (1 << 4) + +int rockchip_drm_get_yuv422_format(struct drm_connector *connector, + struct edid *edid) +{ + struct drm_display_info *info; + const u8 *edid_ext; + + if (!connector || !edid) + return -EINVAL; + + info = &connector->display_info; + + edid_ext = find_cea_extension(edid); + if (!edid_ext) + return -EINVAL; + + if (edid_ext[3] & EDID_CEA_YCRCB422) + info->color_formats |= DRM_COLOR_FORMAT_YCBCR422; + + return 0; +} +EXPORT_SYMBOL(rockchip_drm_get_yuv422_format); + +static +void get_max_frl_rate(int max_frl_rate, u8 *max_lanes, u8 *max_rate_per_lane) +{ + switch (max_frl_rate) { + case 1: + *max_lanes = 3; + *max_rate_per_lane = 3; + break; + case 2: + *max_lanes = 3; + *max_rate_per_lane = 6; + break; + case 3: + *max_lanes = 4; + *max_rate_per_lane = 6; + break; + case 4: + *max_lanes = 4; + *max_rate_per_lane = 8; + break; + case 5: + *max_lanes = 4; + *max_rate_per_lane = 10; + break; + case 6: + *max_lanes = 4; + *max_rate_per_lane = 12; + break; + case 0: + default: + *max_lanes = 0; + *max_rate_per_lane = 0; + } +} + +#define EDID_DSC_10BPC (1 << 0) +#define EDID_DSC_12BPC (1 << 1) +#define EDID_DSC_16BPC (1 << 2) +#define EDID_DSC_ALL_BPP (1 << 3) +#define EDID_DSC_NATIVE_420 (1 << 6) +#define EDID_DSC_1P2 (1 << 7) +#define EDID_DSC_MAX_FRL_RATE_MASK 0xf0 +#define EDID_DSC_MAX_SLICES 0xf +#define EDID_DSC_TOTAL_CHUNK_KBYTES 0x3f +#define EDID_MAX_FRL_RATE_MASK 0xf0 + +static +void parse_edid_forum_vsdb(struct rockchip_drm_dsc_cap *dsc_cap, + u8 *max_frl_rate_per_lane, u8 *max_lanes, + const u8 *hf_vsdb) +{ + u8 max_frl_rate; + u8 dsc_max_frl_rate; + u8 dsc_max_slices; + + if (!hf_vsdb[7]) + return; + + DRM_DEBUG_KMS("hdmi_21 sink detected. parsing edid\n"); + max_frl_rate = (hf_vsdb[7] & EDID_MAX_FRL_RATE_MASK) >> 4; + get_max_frl_rate(max_frl_rate, max_lanes, + max_frl_rate_per_lane); + + if (cea_db_payload_len(hf_vsdb) < 13) + return; + + dsc_cap->v_1p2 = hf_vsdb[11] & EDID_DSC_1P2; + + if (!dsc_cap->v_1p2) + return; + + dsc_cap->native_420 = hf_vsdb[11] & EDID_DSC_NATIVE_420; + dsc_cap->all_bpp = hf_vsdb[11] & EDID_DSC_ALL_BPP; + + if (hf_vsdb[11] & EDID_DSC_16BPC) + dsc_cap->bpc_supported = 16; + else if (hf_vsdb[11] & EDID_DSC_12BPC) + dsc_cap->bpc_supported = 12; + else if (hf_vsdb[11] & EDID_DSC_10BPC) + dsc_cap->bpc_supported = 10; + else + dsc_cap->bpc_supported = 0; + + dsc_max_frl_rate = (hf_vsdb[12] & EDID_DSC_MAX_FRL_RATE_MASK) >> 4; + get_max_frl_rate(dsc_max_frl_rate, &dsc_cap->max_lanes, + &dsc_cap->max_frl_rate_per_lane); + dsc_cap->total_chunk_kbytes = hf_vsdb[13] & EDID_DSC_TOTAL_CHUNK_KBYTES; + + dsc_max_slices = hf_vsdb[12] & EDID_DSC_MAX_SLICES; + switch (dsc_max_slices) { + case 1: + dsc_cap->max_slices = 1; + dsc_cap->clk_per_slice = 340; + break; + case 2: + dsc_cap->max_slices = 2; + dsc_cap->clk_per_slice = 340; + break; + case 3: + dsc_cap->max_slices = 4; + dsc_cap->clk_per_slice = 340; + break; + case 4: + dsc_cap->max_slices = 8; + dsc_cap->clk_per_slice = 340; + break; + case 5: + dsc_cap->max_slices = 8; + dsc_cap->clk_per_slice = 400; + break; + case 6: + dsc_cap->max_slices = 12; + dsc_cap->clk_per_slice = 400; + break; + case 7: + dsc_cap->max_slices = 16; + dsc_cap->clk_per_slice = 400; + break; + case 0: + default: + dsc_cap->max_slices = 0; + dsc_cap->clk_per_slice = 0; + } +} + +enum { + VER_26_BYTE_V0, + VER_15_BYTE_V1, + VER_12_BYTE_V1, + VER_12_BYTE_V2, +}; + +static int check_next_hdr_version(const u8 *next_hdr_db) +{ + u16 ver; + + ver = (next_hdr_db[5] & 0xf0) << 8 | next_hdr_db[0]; + + switch (ver) { + case 0x00f9: + return VER_26_BYTE_V0; + case 0x20ee: + return VER_15_BYTE_V1; + case 0x20eb: + return VER_12_BYTE_V1; + case 0x40eb: + return VER_12_BYTE_V2; + default: + return -ENOENT; + } +} + +static void parse_ver_26_v0_data(struct ver_26_v0 *hdr, const u8 *data) +{ + hdr->yuv422_12bit = data[5] & BIT(0); + hdr->support_2160p_60 = (data[5] & BIT(1)) >> 1; + hdr->global_dimming = (data[5] & BIT(2)) >> 2; + + hdr->dm_major_ver = (data[21] & 0xf0) >> 4; + hdr->dm_minor_ver = data[21] & 0xf; + + hdr->t_min_pq = (data[19] << 4) | ((data[18] & 0xf0) >> 4); + hdr->t_max_pq = (data[20] << 4) | (data[18] & 0xf); + + hdr->rx = (data[7] << 4) | ((data[6] & 0xf0) >> 4); + hdr->ry = (data[8] << 4) | (data[6] & 0xf); + hdr->gx = (data[10] << 4) | ((data[9] & 0xf0) >> 4); + hdr->gy = (data[11] << 4) | (data[9] & 0xf); + hdr->bx = (data[13] << 4) | ((data[12] & 0xf0) >> 4); + hdr->by = (data[14] << 4) | (data[12] & 0xf); + hdr->wx = (data[16] << 4) | ((data[15] & 0xf0) >> 4); + hdr->wy = (data[17] << 4) | (data[15] & 0xf); +} + +static void parse_ver_15_v1_data(struct ver_15_v1 *hdr, const u8 *data) +{ + hdr->yuv422_12bit = data[5] & BIT(0); + hdr->support_2160p_60 = (data[5] & BIT(1)) >> 1; + hdr->global_dimming = data[6] & BIT(0); + + hdr->dm_version = (data[5] & 0x1c) >> 2; + + hdr->colorimetry = data[7] & BIT(0); + + hdr->t_max_lum = (data[6] & 0xfe) >> 1; + hdr->t_min_lum = (data[7] & 0xfe) >> 1; + + hdr->rx = data[9]; + hdr->ry = data[10]; + hdr->gx = data[11]; + hdr->gy = data[12]; + hdr->bx = data[13]; + hdr->by = data[14]; +} + +static void parse_ver_12_v1_data(struct ver_12_v1 *hdr, const u8 *data) +{ + hdr->yuv422_12bit = data[5] & BIT(0); + hdr->support_2160p_60 = (data[5] & BIT(1)) >> 1; + hdr->global_dimming = data[6] & BIT(0); + + hdr->dm_version = (data[5] & 0x1c) >> 2; + + hdr->colorimetry = data[7] & BIT(0); + + hdr->t_max_lum = (data[6] & 0xfe) >> 1; + hdr->t_min_lum = (data[7] & 0xfe) >> 1; + + hdr->low_latency = data[8] & 0x3; + + hdr->unique_rx = (data[11] & 0xf8) >> 3; + hdr->unique_ry = (data[11] & 0x7) << 2 | (data[10] & BIT(0)) << 1 | + (data[9] & BIT(0)); + hdr->unique_gx = (data[9] & 0xfe) >> 1; + hdr->unique_gy = (data[10] & 0xfe) >> 1; + hdr->unique_bx = (data[8] & 0xe0) >> 5; + hdr->unique_by = (data[8] & 0x1c) >> 2; +} + +static void parse_ver_12_v2_data(struct ver_12_v2 *hdr, const u8 *data) +{ + hdr->yuv422_12bit = data[5] & BIT(0); + hdr->backlt_ctrl = (data[5] & BIT(1)) >> 1; + hdr->global_dimming = (data[6] & BIT(2)) >> 2; + + hdr->dm_version = (data[5] & 0x1c) >> 2; + hdr->backlt_min_luma = data[6] & 0x3; + hdr->interface = data[7] & 0x3; + hdr->yuv444_10b_12b = (data[8] & BIT(0)) << 1 | (data[9] & BIT(0)); + + hdr->t_min_pq_v2 = (data[6] & 0xf8) >> 3; + hdr->t_max_pq_v2 = (data[7] & 0xf8) >> 3; + + hdr->unique_rx = (data[10] & 0xf8) >> 3; + hdr->unique_ry = (data[11] & 0xf8) >> 3; + hdr->unique_gx = (data[8] & 0xfe) >> 1; + hdr->unique_gy = (data[9] & 0xfe) >> 1; + hdr->unique_bx = data[10] & 0x7; + hdr->unique_by = data[11] & 0x7; +} + +static +void parse_next_hdr_block(struct next_hdr_sink_data *sink_data, + const u8 *next_hdr_db) +{ + int version; + + version = check_next_hdr_version(next_hdr_db); + if (version < 0) + return; + + sink_data->version = version; + + switch (version) { + case VER_26_BYTE_V0: + parse_ver_26_v0_data(&sink_data->ver_26_v0, next_hdr_db); + break; + case VER_15_BYTE_V1: + parse_ver_15_v1_data(&sink_data->ver_15_v1, next_hdr_db); + break; + case VER_12_BYTE_V1: + parse_ver_12_v1_data(&sink_data->ver_12_v1, next_hdr_db); + break; + case VER_12_BYTE_V2: + parse_ver_12_v2_data(&sink_data->ver_12_v2, next_hdr_db); + break; + default: + break; + } +} + +int rockchip_drm_parse_cea_ext(struct rockchip_drm_dsc_cap *dsc_cap, + u8 *max_frl_rate_per_lane, u8 *max_lanes, + const struct edid *edid) +{ + const u8 *edid_ext; + int i, start, end; + + if (!dsc_cap || !max_frl_rate_per_lane || !max_lanes || !edid) + return -EINVAL; + + edid_ext = find_cea_extension(edid); + if (!edid_ext) + return -EINVAL; + + if (cea_db_offsets(edid_ext, &start, &end)) + return -EINVAL; + + for_each_cea_db(edid_ext, i, start, end) { + const u8 *db = &edid_ext[i]; + + if (cea_db_is_hdmi_forum_vsdb(db)) + parse_edid_forum_vsdb(dsc_cap, max_frl_rate_per_lane, + max_lanes, db); + } + + return 0; +} +EXPORT_SYMBOL(rockchip_drm_parse_cea_ext); + +int rockchip_drm_parse_next_hdr(struct next_hdr_sink_data *sink_data, + const struct edid *edid) +{ + const u8 *edid_ext; + int i, start, end; + + if (!sink_data || !edid) + return -EINVAL; + + memset(sink_data, 0, sizeof(struct next_hdr_sink_data)); + + edid_ext = find_cea_extension(edid); + if (!edid_ext) + return -EINVAL; + + if (cea_db_offsets(edid_ext, &start, &end)) + return -EINVAL; + + for_each_cea_db(edid_ext, i, start, end) { + const u8 *db = &edid_ext[i]; + + if (cea_db_is_hdmi_next_hdr_block(db)) + parse_next_hdr_block(sink_data, db); + } + + return 0; +} +EXPORT_SYMBOL(rockchip_drm_parse_next_hdr); + /* * Attach a (component) device to the shared drm dma mapping from master drm * device. This is used by the VOPs to map GEM buffers to a common DMA diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h index aeb03a572..581496f04 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h @@ -10,8 +10,11 @@ #define _ROCKCHIP_DRM_DRV_H #include +#include +#include +#include #include - +#include #include #include #include @@ -19,25 +22,367 @@ #define ROCKCHIP_MAX_FB_BUFFER 3 #define ROCKCHIP_MAX_CONNECTOR 2 #define ROCKCHIP_MAX_CRTC 4 +#define ROCKCHIP_MAX_LAYER 16 struct drm_device; struct drm_connector; struct iommu_domain; +#define VOP_COLOR_KEY_NONE (0 << 31) +#define VOP_COLOR_KEY_MASK (1 << 31) + +#define VOP_OUTPUT_IF_RGB BIT(0) +#define VOP_OUTPUT_IF_BT1120 BIT(1) +#define VOP_OUTPUT_IF_BT656 BIT(2) +#define VOP_OUTPUT_IF_LVDS0 BIT(3) +#define VOP_OUTPUT_IF_LVDS1 BIT(4) +#define VOP_OUTPUT_IF_MIPI0 BIT(5) +#define VOP_OUTPUT_IF_MIPI1 BIT(6) +#define VOP_OUTPUT_IF_eDP0 BIT(7) +#define VOP_OUTPUT_IF_eDP1 BIT(8) +#define VOP_OUTPUT_IF_DP0 BIT(9) +#define VOP_OUTPUT_IF_DP1 BIT(10) +#define VOP_OUTPUT_IF_HDMI0 BIT(11) +#define VOP_OUTPUT_IF_HDMI1 BIT(12) + +#ifndef DRM_FORMAT_NV20 +#define DRM_FORMAT_NV20 fourcc_code('N', 'V', '2', '0') /* 2x1 subsampled Cr:Cb plane */ +#endif + +#ifndef DRM_FORMAT_NV30 +#define DRM_FORMAT_NV30 fourcc_code('N', 'V', '3', '0') /* non-subsampled Cr:Cb plane */ +#endif + +#define RK_IF_PROP_COLOR_DEPTH "color_depth" +#define RK_IF_PROP_COLOR_FORMAT "color_format" +#define RK_IF_PROP_COLOR_DEPTH_CAPS "color_depth_caps" +#define RK_IF_PROP_COLOR_FORMAT_CAPS "color_format_caps" + +enum rk_if_color_depth { + RK_IF_DEPTH_8, + RK_IF_DEPTH_10, + RK_IF_DEPTH_12, + RK_IF_DEPTH_16, + RK_IF_DEPTH_420_10, + RK_IF_DEPTH_420_12, + RK_IF_DEPTH_420_16, + RK_IF_DEPTH_6, + RK_IF_DEPTH_MAX, +}; + +enum rk_if_color_format { + RK_IF_FORMAT_RGB, /* default RGB */ + RK_IF_FORMAT_YCBCR444, /* YCBCR 444 */ + RK_IF_FORMAT_YCBCR422, /* YCBCR 422 */ + RK_IF_FORMAT_YCBCR420, /* YCBCR 420 */ + RK_IF_FORMAT_YCBCR_HQ, /* Highest subsampled YUV */ + RK_IF_FORMAT_YCBCR_LQ, /* Lowest subsampled YUV */ + RK_IF_FORMAT_MAX, +}; + +struct rockchip_drm_sub_dev { + struct list_head list; + struct drm_connector *connector; + struct device_node *of_node; + void (*loader_protect)(struct drm_encoder *encoder, bool on); + void (*oob_hotplug_event)(struct drm_connector *connector); +}; + +struct rockchip_sdr2hdr_state { + int sdr2hdr_func; + + bool bt1886eotf_pre_conv_en; + bool rgb2rgb_pre_conv_en; + bool rgb2rgb_pre_conv_mode; + bool st2084oetf_pre_conv_en; + + bool bt1886eotf_post_conv_en; + bool rgb2rgb_post_conv_en; + bool rgb2rgb_post_conv_mode; + bool st2084oetf_post_conv_en; +}; + +struct rockchip_hdr_state { + bool pre_overlay; + bool hdr2sdr_en; + struct rockchip_sdr2hdr_state sdr2hdr_state; +}; + +struct rockchip_bcsh_state { + int brightness; + int contrast; + int saturation; + int sin_hue; + int cos_hue; +}; + +struct rockchip_crtc { + struct drm_crtc crtc; +#if defined(CONFIG_ROCKCHIP_DRM_DEBUG) + /** + * @vop_dump_status the status of vop dump control + * @vop_dump_list_head the list head of vop dump list + * @vop_dump_list_init_flag init once + * @vop_dump_times control the dump times + * @frme_count the frame of dump buf + */ + enum vop_dump_status vop_dump_status; + struct list_head vop_dump_list_head; + bool vop_dump_list_init_flag; + int vop_dump_times; + int frame_count; +#endif +}; + +struct rockchip_dsc_sink_cap { + /** + * @slice_width: the number of pixel columns that comprise the slice width + * @slice_height: the number of pixel rows that comprise the slice height + * @block_pred: Does block prediction + * @native_420: Does sink support DSC with 4:2:0 compression + * @bpc_supported: compressed bpc supported by sink : 10, 12 or 16 bpc + * @version_major: DSC major version + * @version_minor: DSC minor version + * @target_bits_per_pixel_x16: bits num after compress and multiply 16 + */ + u16 slice_width; + u16 slice_height; + bool block_pred; + bool native_420; + u8 bpc_supported; + u8 version_major; + u8 version_minor; + u16 target_bits_per_pixel_x16; +}; + struct rockchip_crtc_state { struct drm_crtc_state base; + int vp_id; int output_type; int output_mode; int output_bpc; int output_flags; bool enable_afbc; + + //[CC:] vop2 related change + /** + * @splice_mode: enabled when display a hdisplay > 4096 on rk3588 + */ + bool splice_mode; + + /** + * @hold_mode: enabled when it's: + * (1) mcu hold mode + * (2) mipi dsi cmd mode + * (3) edp psr mode + */ + bool hold_mode; + + struct drm_tv_connector_state *tv_state; + int left_margin; + int right_margin; + int top_margin; + int bottom_margin; + int vdisplay; + int afbdc_win_format; + int afbdc_win_width; + int afbdc_win_height; + int afbdc_win_ptr; + int afbdc_win_id; + int afbdc_en; + int afbdc_win_vir_width; + int afbdc_win_xoffset; + int afbdc_win_yoffset; + int dsp_layer_sel; + u32 output_if; u32 bus_format; u32 bus_flags; + int yuv_overlay; + int post_r2y_en; + int post_y2r_en; + int post_csc_mode; + int bcsh_en; int color_space; + int eotf; + u32 background; + u32 line_flag; + u8 mode_update; + u8 dsc_id; + + //[CC:] vop2 related changes + u8 dsc_enable; + unsigned long dsc_clk; + + u8 dsc_slice_num; + u8 dsc_pixel_num; + + u64 dsc_txp_clk_rate; + u64 dsc_pxl_clk_rate; + u64 dsc_cds_clk_rate; + + struct drm_dsc_picture_parameter_set pps; + struct rockchip_dsc_sink_cap dsc_sink_cap; + struct rockchip_hdr_state hdr; }; + #define to_rockchip_crtc_state(s) \ container_of(s, struct rockchip_crtc_state, base) +struct rockchip_drm_vcnt { + struct drm_pending_vblank_event *event; + __u32 sequence; + int pipe; +}; + +struct rockchip_logo { + dma_addr_t dma_addr; + struct drm_mm_node logo_reserved_node; + void *kvaddr; + phys_addr_t start; + phys_addr_t size; + int count; +}; + +struct loader_cubic_lut { + bool enable; + u32 offset; +}; + +struct rockchip_drm_dsc_cap { + bool v_1p2; + bool native_420; + bool all_bpp; + u8 bpc_supported; + u8 max_slices; + u8 max_lanes; + u8 max_frl_rate_per_lane; + u8 total_chunk_kbytes; + int clk_per_slice; +}; + +struct ver_26_v0 { + u8 yuv422_12bit; + u8 support_2160p_60; + u8 global_dimming; + u8 dm_major_ver; + u8 dm_minor_ver; + u16 t_min_pq; + u16 t_max_pq; + u16 rx; + u16 ry; + u16 gx; + u16 gy; + u16 bx; + u16 by; + u16 wx; + u16 wy; +} __packed; + +struct ver_15_v1 { + u8 yuv422_12bit; + u8 support_2160p_60; + u8 global_dimming; + u8 dm_version; + u8 colorimetry; + u8 t_max_lum; + u8 t_min_lum; + u8 rx; + u8 ry; + u8 gx; + u8 gy; + u8 bx; + u8 by; +} __packed; + +struct ver_12_v1 { + u8 yuv422_12bit; + u8 support_2160p_60; + u8 global_dimming; + u8 dm_version; + u8 colorimetry; + u8 low_latency; + u8 t_max_lum; + u8 t_min_lum; + u8 unique_rx; + u8 unique_ry; + u8 unique_gx; + u8 unique_gy; + u8 unique_bx; + u8 unique_by; +} __packed; + +struct ver_12_v2 { + u8 yuv422_12bit; + u8 backlt_ctrl; + u8 global_dimming; + u8 dm_version; + u8 backlt_min_luma; + u8 interface; + u8 yuv444_10b_12b; + u8 t_min_pq_v2; + u8 t_max_pq_v2; + u8 unique_rx; + u8 unique_ry; + u8 unique_gx; + u8 unique_gy; + u8 unique_bx; + u8 unique_by; +} __packed; + +struct next_hdr_sink_data { + u8 version; + struct ver_26_v0 ver_26_v0; + struct ver_15_v1 ver_15_v1; + struct ver_12_v1 ver_12_v1; + struct ver_12_v2 ver_12_v2; +} __packed; + +//[CC:] drop struct dmcfreq_vop_info +struct dmcfreq_vop_info; + +/* + * Rockchip drm private crtc funcs. + * @loader_protect: protect loader logo crtc's power + * @enable_vblank: enable crtc vblank irq. + * @disable_vblank: disable crtc vblank irq. + * @bandwidth: report present crtc bandwidth consume. + * @cancel_pending_vblank: cancel pending vblank. + * @debugfs_init: init crtc debugfs. + * @debugfs_dump: debugfs to dump crtc and plane state. + * @regs_dump: dump vop current register config. + * @mode_valid: verify that the current mode is supported. + * @crtc_close: close vop. + * @crtc_send_mcu_cmd: send mcu panel init cmd. + * @te_handler: soft te hand for cmd mode panel. + * @wait_vact_end: wait the last active line. + */ +struct rockchip_crtc_funcs { + int (*loader_protect)(struct drm_crtc *crtc, bool on); + int (*enable_vblank)(struct drm_crtc *crtc); + void (*disable_vblank)(struct drm_crtc *crtc); + size_t (*bandwidth)(struct drm_crtc *crtc, + struct drm_crtc_state *crtc_state, + struct dmcfreq_vop_info *vop_bw_info); + void (*cancel_pending_vblank)(struct drm_crtc *crtc, + struct drm_file *file_priv); + int (*debugfs_init)(struct drm_minor *minor, struct drm_crtc *crtc); + int (*debugfs_dump)(struct drm_crtc *crtc, struct seq_file *s); + void (*regs_dump)(struct drm_crtc *crtc, struct seq_file *s); + enum drm_mode_status (*mode_valid)(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + int output_type); + void (*crtc_close)(struct drm_crtc *crtc); + void (*crtc_send_mcu_cmd)(struct drm_crtc *crtc, u32 type, u32 value); + void (*te_handler)(struct drm_crtc *crtc); + int (*wait_vact_end)(struct drm_crtc *crtc, unsigned int mstimeout); + void (*crtc_standby)(struct drm_crtc *crtc, bool standby); +}; + +struct rockchip_dclk_pll { + struct clk *pll; + unsigned int use_count; +}; + /* * Rockchip drm private structure. * @@ -46,10 +391,56 @@ struct rockchip_crtc_state { * @mm_lock: protect drm_mm on multi-threads. */ struct rockchip_drm_private { + struct rockchip_logo *logo; + struct drm_fb_helper *fbdev_helper; + struct drm_gem_object *fbdev_bo; struct iommu_domain *domain; + struct gen_pool *secure_buffer_pool; struct device *iommu_dev; struct mutex mm_lock; struct drm_mm mm; + struct list_head psr_list; + struct mutex psr_list_lock; + struct mutex commit_lock; + + /* private crtc prop */ + struct drm_property *soc_id_prop; + struct drm_property *port_id_prop; + struct drm_property *aclk_prop; + struct drm_property *bg_prop; + struct drm_property *line_flag_prop; + + /* private plane prop */ + struct drm_property *eotf_prop; + struct drm_property *color_space_prop; + struct drm_property *async_commit_prop; + struct drm_property *share_id_prop; + + /* private connector prop */ + struct drm_property *connector_id_prop; + + const struct rockchip_crtc_funcs *crtc_funcs[ROCKCHIP_MAX_CRTC]; + + struct rockchip_dclk_pll default_pll; + struct rockchip_dclk_pll hdmi_pll; + + /* + * protect some shared overlay resource + * OVL_LAYER_SEL/OVL_PORT_SEL + */ + struct mutex ovl_lock; + + struct rockchip_drm_vcnt vcnt[ROCKCHIP_MAX_CRTC]; + /** + * @loader_protect + * ignore restore_fbdev_mode_atomic when in logo on state + */ + bool loader_protect; + + dma_addr_t cubic_lut_dma_addr; + void *cubic_lut_kvaddr; + struct drm_mm_node *clut_reserved_node; + struct loader_cubic_lut cubic_lut[ROCKCHIP_MAX_CRTC]; }; struct rockchip_encoder { @@ -66,16 +457,52 @@ void rockchip_drm_dma_init_device(struct drm_device *drm_dev, int rockchip_drm_wait_vact_end(struct drm_crtc *crtc, unsigned int mstimeout); int rockchip_drm_encoder_set_crtc_endpoint_id(struct rockchip_encoder *rencoder, struct device_node *np, int port, int reg); + +int rockchip_register_crtc_funcs(struct drm_crtc *crtc, + const struct rockchip_crtc_funcs *crtc_funcs); +void rockchip_unregister_crtc_funcs(struct drm_crtc *crtc); +void rockchip_drm_crtc_standby(struct drm_crtc *crtc, bool standby); + +void rockchip_drm_register_sub_dev(struct rockchip_drm_sub_dev *sub_dev); +void rockchip_drm_unregister_sub_dev(struct rockchip_drm_sub_dev *sub_dev); +struct rockchip_drm_sub_dev *rockchip_drm_get_sub_dev(struct device_node *node); +int rockchip_drm_add_modes_noedid(struct drm_connector *connector); +void rockchip_drm_te_handle(struct drm_crtc *crtc); +void drm_mode_convert_to_split_mode(struct drm_display_mode *mode); +void drm_mode_convert_to_origin_mode(struct drm_display_mode *mode); +#if IS_REACHABLE(CONFIG_DRM_ROCKCHIP) +int rockchip_drm_get_sub_dev_type(void); +#else +static inline int rockchip_drm_get_sub_dev_type(void) +{ + return DRM_MODE_CONNECTOR_Unknown; +} +#endif + int rockchip_drm_endpoint_is_subdriver(struct device_node *ep); +uint32_t rockchip_drm_get_bpp(const struct drm_format_info *info); +int rockchip_drm_get_yuv422_format(struct drm_connector *connector, + struct edid *edid); +int rockchip_drm_parse_cea_ext(struct rockchip_drm_dsc_cap *dsc_cap, + u8 *max_frl_rate_per_lane, u8 *max_lanes, + const struct edid *edid); +int rockchip_drm_parse_next_hdr(struct next_hdr_sink_data *sink_data, + const struct edid *edid); + extern struct platform_driver cdn_dp_driver; extern struct platform_driver dw_hdmi_rockchip_pltfm_driver; extern struct platform_driver dw_mipi_dsi_rockchip_driver; +extern struct platform_driver dw_mipi_dsi2_rockchip_driver; extern struct platform_driver inno_hdmi_driver; extern struct platform_driver rockchip_dp_driver; extern struct platform_driver rockchip_lvds_driver; extern struct platform_driver vop_platform_driver; -extern struct platform_driver rk3066_hdmi_driver; extern struct platform_driver vop2_platform_driver; +extern struct platform_driver rk3066_hdmi_driver; +extern struct platform_driver rockchip_rgb_driver; +extern struct platform_driver dw_dp_driver; +extern struct platform_driver vconn_platform_driver; +extern struct platform_driver vvop_platform_driver; static inline struct rockchip_encoder *to_rockchip_encoder(struct drm_encoder *encoder) { diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h index 4b2daefeb..6ebea5c36 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h @@ -15,6 +15,16 @@ #define VOP_MAJOR(version) ((version) >> 8) #define VOP_MINOR(version) ((version) & 0xff) +//[CC:] vop2 related changes +#define VOP_VERSION_RK3568 VOP_VERSION(0x40, 0x15) +#define VOP_VERSION_RK3588 VOP_VERSION(0x40, 0x17) + +#define ROCKCHIP_OUTPUT_DUAL_CHANNEL_LEFT_RIGHT_MODE BIT(0) +#define ROCKCHIP_OUTPUT_DUAL_CHANNEL_ODD_EVEN_MODE BIT(1) +#define ROCKCHIP_OUTPUT_DATA_SWAP BIT(2) +/* MIPI DSI DataStream(cmd) mode on rk3588 */ +#define ROCKCHIP_OUTPUT_MIPI_DS_MODE BIT(3) + #define NUM_YUV2YUV_COEFFICIENTS 12 /* AFBC supports a number of configurable modes. Relevant to us is block size @@ -280,11 +290,16 @@ struct vop_data { /* * display output interface supported by rockchip lcdc */ -#define ROCKCHIP_OUT_MODE_P888 0 -#define ROCKCHIP_OUT_MODE_P666 1 -#define ROCKCHIP_OUT_MODE_P565 2 +#define ROCKCHIP_OUT_MODE_P888 0 +#define ROCKCHIP_OUT_MODE_BT1120 0 +#define ROCKCHIP_OUT_MODE_P666 1 +#define ROCKCHIP_OUT_MODE_P565 2 +#define ROCKCHIP_OUT_MODE_BT656 5 +#define ROCKCHIP_OUT_MODE_S888 8 +#define ROCKCHIP_OUT_MODE_S888_DUMMY 12 +#define ROCKCHIP_OUT_MODE_YUV420 14 /* for use special outface */ -#define ROCKCHIP_OUT_MODE_AAAA 15 +#define ROCKCHIP_OUT_MODE_AAAA 15 /* output flags */ #define ROCKCHIP_OUTPUT_DSI_DUAL BIT(0) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c index 6862fb146..a073d1d5c 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c @@ -5,6 +5,8 @@ */ #include #include +#include +#include #include #include #include @@ -17,6 +19,7 @@ #include #include #include +#include #include #include @@ -159,6 +162,8 @@ struct vop2_video_port { struct drm_crtc crtc; struct vop2 *vop2; struct clk *dclk; + struct reset_control *dclk_rst; + struct clk *dclk_parent; unsigned int id; const struct vop2_video_port_data *data; @@ -191,6 +196,8 @@ struct vop2 { struct regmap *map; struct regmap *grf; + struct regmap *vop_grf; + struct regmap *vo1_grf; /* physical map length of vop2 register */ u32 len; @@ -209,14 +216,43 @@ struct vop2 { unsigned int enable_count; struct clk *hclk; struct clk *aclk; + struct clk *pclk; + // [CC:] handle all display modes + struct clk *hdmi0_phy_pll; + struct reset_control *ahb_rst; + struct reset_control *axi_rst; /* optional internal rgb encoder */ struct rockchip_rgb *rgb; + /* list_head of internal clk */ + struct list_head clk_list_head; + /* must be put at the end of the struct */ struct vop2_win win[]; }; +struct vop2_clk { + struct vop2 *vop2; + struct list_head list; + unsigned long rate; + struct clk_hw hw; + struct clk_divider div; + int div_val; + u8 parent_index; +}; + +#define to_vop2_clk(_hw) container_of(_hw, struct vop2_clk, hw) + +#define VOP2_MAX_DCLK_RATE 600000 /* kHz */ + +#define vop2_output_if_is_hdmi(x) (x == ROCKCHIP_VOP2_EP_HDMI0 || x == ROCKCHIP_VOP2_EP_HDMI1) +#define vop2_output_if_is_dp(x) (x == ROCKCHIP_VOP2_EP_DP0 || x == ROCKCHIP_VOP2_EP_DP1) +#define vop2_output_if_is_edp(x) (x == ROCKCHIP_VOP2_EP_EDP0 || x == ROCKCHIP_VOP2_EP_EDP1) +#define vop2_output_if_is_mipi(x) (x == ROCKCHIP_VOP2_EP_MIPI0 || x == ROCKCHIP_VOP2_EP_MIPI1) +#define vop2_output_if_is_lvds(x) (x == ROCKCHIP_VOP2_EP_LVDS0 || x == ROCKCHIP_VOP2_EP_LVDS1) +#define vop2_output_if_is_dpi(x) (x == ROCKCHIP_VOP2_EP_RGB0) + static struct vop2_video_port *to_vop2_video_port(struct drm_crtc *crtc) { return container_of(crtc, struct vop2_video_port, crtc); @@ -269,9 +305,16 @@ static bool vop2_cluster_window(const struct vop2_win *win) static void vop2_cfg_done(struct vop2_video_port *vp) { struct vop2 *vop2 = vp->vop2; + unsigned int bits = BIT(vp->id) | RK3568_REG_CFG_DONE__GLB_CFG_DONE_EN; - regmap_set_bits(vop2->map, RK3568_REG_CFG_DONE, - BIT(vp->id) | RK3568_REG_CFG_DONE__GLB_CFG_DONE_EN); + if (vop2->data->soc_id == 3588) { + bits |= BIT(vp->id) << 16; + // [CC:] handle splice_mode + // if (vcstate->splice_mode) + // bits |= BIT(vp_data->splice_vp_id) | (BIT(vp_data->splice_vp_id) << 16); + } + + regmap_set_bits(vop2->map, RK3568_REG_CFG_DONE, bits); } static void vop2_win_disable(struct vop2_win *win) @@ -846,16 +889,36 @@ static int vop2_core_clks_prepare_enable(struct vop2 *vop2) ret = clk_prepare_enable(vop2->aclk); if (ret < 0) { drm_err(vop2->drm, "failed to enable aclk - %d\n", ret); - goto err; + goto err_aclk; + } + + ret = clk_prepare_enable(vop2->pclk); + if (ret < 0) { + drm_err(vop2->drm, "failed to enable pclk - %d\n", ret); + goto err_pclk; } return 0; -err: + +err_pclk: + clk_disable_unprepare(vop2->aclk); +err_aclk: clk_disable_unprepare(vop2->hclk); return ret; } +static void vop2_power_domain_all_on(struct vop2 *vop2) +{ + u32 pd; + + pd = vop2_readl(vop2, RK3588_SYS_PD_CTRL); + pd |= VOP2_PD_CLUSTER0 | VOP2_PD_CLUSTER1 | VOP2_PD_CLUSTER2 | + VOP2_PD_CLUSTER3 | VOP2_PD_ESMART; + + vop2_writel(vop2, RK3588_SYS_PD_CTRL, pd); +} + static void vop2_enable(struct vop2 *vop2) { int ret; @@ -883,6 +946,9 @@ static void vop2_enable(struct vop2 *vop2) if (vop2->data->soc_id == 3566) vop2_writel(vop2, RK3568_OTP_WIN_EN, 1); + if (vop2->data->soc_id == 3588) + vop2_power_domain_all_on(vop2); + vop2_writel(vop2, RK3568_REG_CFG_DONE, RK3568_REG_CFG_DONE__GLB_CFG_DONE_EN); /* @@ -910,63 +976,11 @@ static void vop2_disable(struct vop2 *vop2) regcache_mark_dirty(vop2->map); + clk_disable_unprepare(vop2->pclk); clk_disable_unprepare(vop2->aclk); clk_disable_unprepare(vop2->hclk); } -static void vop2_crtc_atomic_disable(struct drm_crtc *crtc, - struct drm_atomic_state *state) -{ - struct vop2_video_port *vp = to_vop2_video_port(crtc); - struct vop2 *vop2 = vp->vop2; - struct drm_crtc_state *old_crtc_state; - int ret; - - vop2_lock(vop2); - - old_crtc_state = drm_atomic_get_old_crtc_state(state, crtc); - drm_atomic_helper_disable_planes_on_crtc(old_crtc_state, false); - - drm_crtc_vblank_off(crtc); - - /* - * Vop standby will take effect at end of current frame, - * if dsp hold valid irq happen, it means standby complete. - * - * we must wait standby complete when we want to disable aclk, - * if not, memory bus maybe dead. - */ - reinit_completion(&vp->dsp_hold_completion); - - vop2_crtc_enable_irq(vp, VP_INT_DSP_HOLD_VALID); - - vop2_vp_write(vp, RK3568_VP_DSP_CTRL, RK3568_VP_DSP_CTRL__STANDBY); - - ret = wait_for_completion_timeout(&vp->dsp_hold_completion, - msecs_to_jiffies(50)); - if (!ret) - drm_info(vop2->drm, "wait for vp%d dsp_hold timeout\n", vp->id); - - vop2_crtc_disable_irq(vp, VP_INT_DSP_HOLD_VALID); - - clk_disable_unprepare(vp->dclk); - - vop2->enable_count--; - - if (!vop2->enable_count) - vop2_disable(vop2); - - vop2_unlock(vop2); - - if (crtc->state->event && !crtc->state->active) { - spin_lock_irq(&crtc->dev->event_lock); - drm_crtc_send_vblank_event(crtc, crtc->state->event); - spin_unlock_irq(&crtc->dev->event_lock); - - crtc->state->event = NULL; - } -} - static int vop2_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_state *astate) { @@ -1004,6 +1018,7 @@ static int vop2_plane_atomic_check(struct drm_plane *plane, if (!pstate->visible) return 0; + // [CC:] Drop format var since it's not used anywhere, use ret var instead format = vop2_convert_format(fb->format->format); if (format < 0) return format; @@ -1027,6 +1042,35 @@ static int vop2_plane_atomic_check(struct drm_plane *plane, return -EINVAL; } + // [CC:] do we need the checks below? + bool afbc_en = rockchip_afbc(plane, fb->modifier); + struct vop2_win *win = to_vop2_win(plane); + + /* + * This is special feature at rk356x, the cluster layer only can support + * afbc format and can't support linear format; + */ + if (vp->vop2->data->soc_id == 3568) { + if (vop2_cluster_window(win) && !afbc_en) { + DRM_ERROR("Unsupported linear format at %s\n", win->data->name); + return -EINVAL; + } + } + + if (vp->vop2->data->soc_id > 3568) { + if (vop2_cluster_window(win) && !afbc_en && fb->format->is_yuv) { + DRM_ERROR("Unsupported linear yuv format at %s\n", win->data->name); + return -EINVAL; + } + + if (vop2_cluster_window(win) && !afbc_en && + (win->data->supported_rotations & pstate->rotation)) { + DRM_ERROR("Unsupported linear rotation(%d) format at %s\n", + pstate->rotation, win->data->name); + return -EINVAL; + } + } + /* * Src.x1 can be odd when do clip, but yuv plane start point * need align with 2 pixel. @@ -1107,6 +1151,7 @@ static void vop2_plane_setup_color_key(struct drm_plane *plane, u32 color_key) vop2_win_write(win, VOP2_WIN_COLOR_KEY, (r << 20) | (g << 10) | b); } +// [CC:] fix BUG: sleeping function called from invalid context static void vop2_plane_atomic_update(struct drm_plane *plane, struct drm_atomic_state *state) { @@ -1272,7 +1317,12 @@ static void vop2_plane_atomic_update(struct drm_plane *plane, vop2_win_write(win, VOP2_WIN_AFBC_ENABLE, 1); vop2_win_write(win, VOP2_WIN_AFBC_FORMAT, afbc_format); vop2_win_write(win, VOP2_WIN_AFBC_UV_SWAP, uv_swap); - vop2_win_write(win, VOP2_WIN_AFBC_AUTO_GATING_EN, 0); + + if (vop2->data->soc_id == 3566 || vop2->data->soc_id == 3568) + vop2_win_write(win, VOP2_WIN_AFBC_AUTO_GATING_EN, 0); + else + vop2_win_write(win, VOP2_WIN_AFBC_AUTO_GATING_EN, 1); + vop2_win_write(win, VOP2_WIN_AFBC_BLOCK_SPLIT_EN, 0); if (pstate->rotation & (DRM_MODE_ROTATE_270 | DRM_MODE_ROTATE_90)) { vop2_win_write(win, VOP2_WIN_AFBC_HALF_BLOCK_EN, 0); @@ -1376,9 +1426,30 @@ static bool vop2_crtc_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode, struct drm_display_mode *adj_mode) { + struct vop2_video_port *vp = to_vop2_video_port(crtc); + struct drm_connector *connector; + struct drm_connector_list_iter conn_iter; + struct drm_crtc_state *new_crtc_state = container_of(mode, struct drm_crtc_state, mode); drm_mode_set_crtcinfo(adj_mode, CRTC_INTERLACE_HALVE_V | CRTC_STEREO_DOUBLE); + if (mode->flags & DRM_MODE_FLAG_DBLCLK) + adj_mode->crtc_clock *= 2; + + drm_connector_list_iter_begin(crtc->dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { + if ((new_crtc_state->connector_mask & drm_connector_mask(connector)) && + ((connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort) || + (connector->connector_type == DRM_MODE_CONNECTOR_HDMIA))) { + drm_connector_list_iter_end(&conn_iter); + return true; + } + } + drm_connector_list_iter_end(&conn_iter); + + if (adj_mode->crtc_clock <= VOP2_MAX_DCLK_RATE) + adj_mode->crtc_clock = DIV_ROUND_UP(clk_round_rate(vp->dclk, + adj_mode->crtc_clock * 1000), 1000); return true; } @@ -1462,7 +1533,7 @@ static void vop2_post_config(struct drm_crtc *crtc) } static void rk3568_set_intf_mux(struct vop2_video_port *vp, int id, - u32 polflags) + u32 polflags, u32 invpolflags) { struct vop2 *vop2 = vp->vop2; u32 die, dip; @@ -1477,6 +1548,7 @@ static void rk3568_set_intf_mux(struct vop2_video_port *vp, int id, FIELD_PREP(RK3568_SYS_DSP_INFACE_EN_RGB_MUX, vp->id); dip &= ~RK3568_DSP_IF_POL__RGB_LVDS_PIN_POL; dip |= FIELD_PREP(RK3568_DSP_IF_POL__RGB_LVDS_PIN_POL, polflags); + // [CC:] use .grf_dclk_inv = VOP_REG(RK3588_GRF_SOC_CON1, 0x1, 14) for 3588 variant if (polflags & POLFLAG_DCLK_INV) regmap_write(vop2->grf, RK3568_GRF_VO_CON1, BIT(3 + 16) | BIT(3)); else @@ -1487,8 +1559,18 @@ static void rk3568_set_intf_mux(struct vop2_video_port *vp, int id, die |= RK3568_SYS_DSP_INFACE_EN_HDMI | FIELD_PREP(RK3568_SYS_DSP_INFACE_EN_HDMI_MUX, vp->id); dip &= ~RK3568_DSP_IF_POL__HDMI_PIN_POL; - dip |= FIELD_PREP(RK3568_DSP_IF_POL__HDMI_PIN_POL, polflags); + dip |= FIELD_PREP(RK3568_DSP_IF_POL__HDMI_PIN_POL, invpolflags); + + /* grf_hdmi0_en */ + if (vop2->vop_grf) + regmap_write(vop2->vop_grf, RK3588_GRF_VOP_CON2, BIT(1 + 16) | BIT(1)); + /* hdmi0_pin_pol */ + if (vop2->vo1_grf) + regmap_write(vop2->vo1_grf, RK3588_GRF_VO1_CON0, + RK3588_GRF_VO1_CON0__HDMI0_PIN_POL << 16 | + FIELD_PREP(RK3588_GRF_VO1_CON0__HDMI0_PIN_POL, invpolflags)); break; + // [CC:] TODO: ROCKCHIP_VOP2_EP_HDMI1 case ROCKCHIP_VOP2_EP_EDP0: die &= ~RK3568_SYS_DSP_INFACE_EN_EDP_MUX; die |= RK3568_SYS_DSP_INFACE_EN_EDP | @@ -1535,11 +1617,354 @@ static void rk3568_set_intf_mux(struct vop2_video_port *vp, int id, vop2_writel(vop2, RK3568_DSP_IF_POL, dip); } +/* + * calc the dclk on rk3588 + * the available div of dclk is 1, 2, 4 + */ +static unsigned long vop2_calc_dclk(unsigned long child_clk, unsigned long max_dclk) +{ + if (child_clk * 4 <= max_dclk) + return child_clk * 4; + else if (child_clk * 2 <= max_dclk) + return child_clk * 2; + else if (child_clk <= max_dclk) + return child_clk; + else + return 0; +} + +static struct vop2_clk *vop2_clk_get(struct vop2 *vop2, const char *name); + +static int vop2_cru_set_rate(struct vop2_clk *if_pixclk, struct vop2_clk *if_dclk) +{ + int ret = 0; + + if (if_pixclk) { + ret = clk_set_rate(if_pixclk->hw.clk, if_pixclk->rate); + if (ret < 0) { + DRM_DEV_ERROR(if_pixclk->vop2->dev, "set %s to %ld failed: %d\n", + clk_hw_get_name(&if_pixclk->hw), if_pixclk->rate, ret); + return ret; + } + } + + if (if_dclk) { + ret = clk_set_rate(if_dclk->hw.clk, if_dclk->rate); + if (ret < 0) + DRM_DEV_ERROR(if_dclk->vop2->dev, "set %s to %ld failed %d\n", + clk_hw_get_name(&if_dclk->hw), if_dclk->rate, ret); + } + + return ret; +} + +/* + * 4 pixclk/cycle on rk3588 + * RGB/eDP/HDMI: if_pixclk >= dclk_core + * DP: dp_pixclk = dclk_out <= dclk_core + * DSI: mipi_pixclk <= dclk_out <= dclk_core + */ +static unsigned long vop2_calc_cru_cfg(struct vop2_video_port *vp, int id, + int *dclk_core_div, int *dclk_out_div, + int *if_pixclk_div, int *if_dclk_div) +{ + struct vop2 *vop2 = vp->vop2; + struct drm_crtc *crtc = &vp->crtc; + struct drm_display_mode *adjusted_mode = &crtc->state->adjusted_mode; + struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc->state); + int output_mode = vcstate->output_mode; + unsigned long v_pixclk = adjusted_mode->crtc_clock * 1000LL; /* video timing pixclk */ + unsigned long dclk_core_rate = v_pixclk >> 2; + unsigned long dclk_rate = v_pixclk; + unsigned long dclk_out_rate; + unsigned long if_dclk_rate; + unsigned long if_pixclk_rate; + int K = 1; + + if (vop2_output_if_is_hdmi(id)) { + if (vop2->data->soc_id == 3588 && id == ROCKCHIP_VOP2_EP_HDMI0) { + const char *clk_src_name = "hdmi_edp0_clk_src"; + const char *clk_parent_name = "dclk"; + const char *pixclk_name = "hdmi_edp0_pixclk"; + const char *dclk_name = "hdmi_edp0_dclk"; + struct vop2_clk *if_clk_src, *if_clk_parent, *if_pixclk, *if_dclk, *dclk, *dclk_core, *dclk_out; + char clk_name[32]; + int ret; + + if_clk_src = vop2_clk_get(vop2, clk_src_name); + snprintf(clk_name, sizeof(clk_name), "%s%d", clk_parent_name, vp->id); + if_clk_parent = vop2_clk_get(vop2, clk_name); + if_pixclk = vop2_clk_get(vop2, pixclk_name); + if_dclk = vop2_clk_get(vop2, dclk_name); + if (!if_pixclk || !if_clk_parent) { + DRM_DEV_ERROR(vop2->dev, "failed to get connector interface clk\n"); + return -ENODEV; + } + + ret = clk_set_parent(if_clk_src->hw.clk, if_clk_parent->hw.clk); + if (ret < 0) { + DRM_DEV_ERROR(vop2->dev, "failed to set parent(%s) for %s: %d\n", + __clk_get_name(if_clk_parent->hw.clk), + __clk_get_name(if_clk_src->hw.clk), ret); + return ret; + } + + if (output_mode == ROCKCHIP_OUT_MODE_YUV420) + K = 2; + + if_pixclk->rate = (dclk_core_rate << 1) / K; + if_dclk->rate = dclk_core_rate / K; + + snprintf(clk_name, sizeof(clk_name), "dclk_core%d", vp->id); + dclk_core = vop2_clk_get(vop2, clk_name); + + snprintf(clk_name, sizeof(clk_name), "dclk_out%d", vp->id); + dclk_out = vop2_clk_get(vop2, clk_name); + + snprintf(clk_name, sizeof(clk_name), "dclk%d", vp->id); + dclk = vop2_clk_get(vop2, clk_name); + if (v_pixclk <= (VOP2_MAX_DCLK_RATE * 1000)) { + if (output_mode == ROCKCHIP_OUT_MODE_YUV420) + v_pixclk = v_pixclk >> 1; + } else { + v_pixclk = v_pixclk >> 2; + } + clk_set_rate(dclk->hw.clk, v_pixclk); + + if (dclk_core_rate > if_pixclk->rate) { + clk_set_rate(dclk_core->hw.clk, dclk_core_rate); + ret = vop2_cru_set_rate(if_pixclk, if_dclk); + } else { + ret = vop2_cru_set_rate(if_pixclk, if_dclk); + clk_set_rate(dclk_core->hw.clk, dclk_core_rate); + } + + *dclk_core_div = dclk_core->div_val; + *dclk_out_div = dclk_out->div_val; + *if_pixclk_div = if_pixclk->div_val; + *if_dclk_div = if_dclk->div_val; + + return dclk->rate; + } + + /* + * K = 2: dclk_core = if_pixclk_rate > if_dclk_rate + * K = 1: dclk_core = hdmie_edp_dclk > if_pixclk_rate + */ + if (output_mode == ROCKCHIP_OUT_MODE_YUV420) { + dclk_rate = dclk_rate >> 1; + K = 2; + } + + if_pixclk_rate = (dclk_core_rate << 1) / K; + if_dclk_rate = dclk_core_rate / K; + + *if_pixclk_div = dclk_rate / if_pixclk_rate; + *if_dclk_div = dclk_rate / if_dclk_rate; + *dclk_core_div = dclk_rate / dclk_core_rate; + } else if (vop2_output_if_is_edp(id)) { + /* edp_pixclk = edp_dclk > dclk_core */ + if_pixclk_rate = v_pixclk / K; + if_dclk_rate = v_pixclk / K; + dclk_rate = if_pixclk_rate * K; + *dclk_core_div = dclk_rate / dclk_core_rate; + *if_pixclk_div = dclk_rate / if_pixclk_rate; + *if_dclk_div = *if_pixclk_div; + } else if (vop2_output_if_is_dp(id)) { + if (output_mode == ROCKCHIP_OUT_MODE_YUV420) + dclk_out_rate = v_pixclk >> 3; + else + dclk_out_rate = v_pixclk >> 2; + + dclk_rate = vop2_calc_dclk(dclk_out_rate, 600000); + if (!dclk_rate) { + drm_err(vop2->drm, "DP dclk_out_rate out of range(max_dclk: 600 KHZ, dclk_out_rate: %ld KHZ)\n", + dclk_out_rate); + return -EINVAL; + } + *dclk_out_div = dclk_rate / dclk_out_rate; + *dclk_core_div = dclk_rate / dclk_core_rate; + } else if (vop2_output_if_is_mipi(id)) { + if_pixclk_rate = dclk_core_rate / K; + /* dclk_core = dclk_out * K = if_pixclk * K = v_pixclk / 4 */ + dclk_out_rate = if_pixclk_rate; + /* dclk_rate = N * dclk_core_rate N = (1,2,4 ), we get a little factor here */ + dclk_rate = vop2_calc_dclk(dclk_out_rate, 600000); + if (!dclk_rate) { + drm_err(vop2->drm, "MIPI dclk out of range(max_dclk: 600 KHZ, dclk_out_rate: %ld KHZ)\n", + dclk_out_rate); + return -EINVAL; + } + *dclk_out_div = dclk_rate / dclk_out_rate; + *dclk_core_div = dclk_rate / dclk_core_rate; + *if_pixclk_div = 1; /*mipi pixclk == dclk_out*/ + } else if (vop2_output_if_is_dpi(id)) { + dclk_rate = v_pixclk; + *dclk_core_div = dclk_rate / dclk_core_rate; + } + + *if_pixclk_div = ilog2(*if_pixclk_div); + *if_dclk_div = ilog2(*if_dclk_div); + *dclk_core_div = ilog2(*dclk_core_div); + *dclk_out_div = ilog2(*dclk_out_div); + + drm_dbg(vop2->drm, "dclk:%ld,if_pixclk_div;%d,if_dclk_div:%d\n", dclk_rate, *if_pixclk_div, *if_dclk_div); + + return dclk_rate; +} + +/* + * MIPI port mux on rk3588: + * 0: Video Port2 + * 1: Video Port3 + * 3: Video Port 1(MIPI1 only) + */ +static u32 rk3588_get_mipi_port_mux(int vp_id) +{ + if (vp_id == 1) + return 3; + else if (vp_id == 3) + return 1; + else + return 0; +} + +static u32 rk3588_get_hdmi_pol(u32 flags) +{ + u32 val; + + val = (flags & DRM_MODE_FLAG_NHSYNC) ? BIT(HSYNC_POSITIVE) : 0; + val |= (flags & DRM_MODE_FLAG_NVSYNC) ? BIT(VSYNC_POSITIVE) : 0; + + return val; +} + +static void rk3588_set_intf_mux(struct vop2_video_port *vp, int id, u32 polflags) +{ + struct vop2 *vop2 = vp->vop2; + int dclk_core_div, dclk_out_div, if_pixclk_div, if_dclk_div; + u32 die, dip, div, vp_clk_div, val; + + vop2_calc_cru_cfg(vp, id, &dclk_core_div, &dclk_out_div, &if_pixclk_div, &if_dclk_div); + + vp_clk_div = FIELD_PREP(RK3588_VP_CLK_CTRL__DCLK_CORE_DIV, dclk_core_div); + vp_clk_div |= FIELD_PREP(RK3588_VP_CLK_CTRL__DCLK_OUT_DIV, dclk_out_div); + + die = vop2_readl(vop2, RK3568_DSP_IF_EN); + dip = vop2_readl(vop2, RK3568_DSP_IF_POL); + div = vop2_readl(vop2, RK3568_DSP_IF_CTRL); + + switch (id) { + case ROCKCHIP_VOP2_EP_HDMI0: + div |= FIELD_PREP(RK3588_DSP_IF_EDP_HDMI0_DCLK_DIV, if_dclk_div); + div |= FIELD_PREP(RK3588_DSP_IF_EDP_HDMI0_PCLK_DIV, if_pixclk_div); + die &= ~RK3588_SYS_DSP_INFACE_EN_EDP_HDMI0_MUX; + die |= RK3588_SYS_DSP_INFACE_EN_HDMI0 | + FIELD_PREP(RK3588_SYS_DSP_INFACE_EN_EDP_HDMI0_MUX, vp->id); + val = rk3588_get_hdmi_pol(polflags); + regmap_write(vop2->vop_grf, RK3588_GRF_VOP_CON2, HIWORD_UPDATE(1, 1, 1)); + regmap_write(vop2->vo1_grf, RK3588_GRF_VO1_CON0, HIWORD_UPDATE(val, 6, 5)); + break; + case ROCKCHIP_VOP2_EP_HDMI1: + div |= FIELD_PREP(RK3588_DSP_IF_EDP_HDMI1_DCLK_DIV, if_dclk_div); + div |= FIELD_PREP(RK3588_DSP_IF_EDP_HDMI1_PCLK_DIV, if_pixclk_div); + die &= ~RK3588_SYS_DSP_INFACE_EN_EDP_HDMI1_MUX; + die |= RK3588_SYS_DSP_INFACE_EN_HDMI1 | + FIELD_PREP(RK3588_SYS_DSP_INFACE_EN_EDP_HDMI1_MUX, vp->id); + val = rk3588_get_hdmi_pol(polflags); + regmap_write(vop2->vop_grf, RK3588_GRF_VOP_CON2, HIWORD_UPDATE(1, 4, 4)); + regmap_write(vop2->vo1_grf, RK3588_GRF_VO1_CON0, HIWORD_UPDATE(val, 8, 7)); + break; + case ROCKCHIP_VOP2_EP_EDP0: + div |= FIELD_PREP(RK3588_DSP_IF_EDP_HDMI0_DCLK_DIV, if_dclk_div); + div |= FIELD_PREP(RK3588_DSP_IF_EDP_HDMI0_PCLK_DIV, if_pixclk_div); + die &= ~RK3588_SYS_DSP_INFACE_EN_EDP_HDMI0_MUX; + die |= RK3588_SYS_DSP_INFACE_EN_EDP0 | + FIELD_PREP(RK3588_SYS_DSP_INFACE_EN_EDP_HDMI0_MUX, vp->id); + regmap_write(vop2->vop_grf, RK3588_GRF_VOP_CON2, HIWORD_UPDATE(1, 0, 0)); + break; + case ROCKCHIP_VOP2_EP_EDP1: + div |= FIELD_PREP(RK3588_DSP_IF_EDP_HDMI0_DCLK_DIV, if_dclk_div); + div |= FIELD_PREP(RK3588_DSP_IF_EDP_HDMI0_PCLK_DIV, if_pixclk_div); + die &= ~RK3588_SYS_DSP_INFACE_EN_EDP_HDMI1_MUX; + die |= RK3588_SYS_DSP_INFACE_EN_EDP1 | + FIELD_PREP(RK3588_SYS_DSP_INFACE_EN_EDP_HDMI1_MUX, vp->id); + regmap_write(vop2->vop_grf, RK3588_GRF_VOP_CON2, HIWORD_UPDATE(1, 3, 3)); + break; + case ROCKCHIP_VOP2_EP_MIPI0: + div |= FIELD_PREP(RK3588_DSP_IF_MIPI0_PCLK_DIV, if_pixclk_div); + die &= ~RK3588_SYS_DSP_INFACE_EN_MIPI0_MUX; + val = rk3588_get_mipi_port_mux(vp->id); + die |= RK3588_SYS_DSP_INFACE_EN_MIPI0 | + FIELD_PREP(RK3588_SYS_DSP_INFACE_EN_MIPI0_MUX, !!val); + break; + case ROCKCHIP_VOP2_EP_MIPI1: + div |= FIELD_PREP(RK3588_DSP_IF_MIPI1_PCLK_DIV, if_pixclk_div); + die &= ~RK3588_SYS_DSP_INFACE_EN_MIPI1_MUX; + val = rk3588_get_mipi_port_mux(vp->id); + die |= RK3588_SYS_DSP_INFACE_EN_MIPI1 | + FIELD_PREP(RK3588_SYS_DSP_INFACE_EN_MIPI1_MUX, val); + break; + case ROCKCHIP_VOP2_EP_DP0: + die &= ~RK3588_SYS_DSP_INFACE_EN_DP0_MUX; + die |= RK3588_SYS_DSP_INFACE_EN_DP0 | + FIELD_PREP(RK3588_SYS_DSP_INFACE_EN_DP0_MUX, vp->id); + dip &= ~RK3588_DSP_IF_POL__DP0_PIN_POL; + dip |= FIELD_PREP(RK3588_DSP_IF_POL__DP0_PIN_POL, polflags); + break; + case ROCKCHIP_VOP2_EP_DP1: + die &= ~RK3588_SYS_DSP_INFACE_EN_MIPI1_MUX; + die |= RK3588_SYS_DSP_INFACE_EN_MIPI1 | + FIELD_PREP(RK3588_SYS_DSP_INFACE_EN_MIPI1_MUX, vp->id); + dip &= ~RK3588_DSP_IF_POL__DP1_PIN_POL; + dip |= FIELD_PREP(RK3588_DSP_IF_POL__DP1_PIN_POL, polflags); + break; + default: + drm_err(vop2->drm, "Invalid interface id %d on vp%d\n", id, vp->id); + return; + } + + dip |= RK3568_DSP_IF_POL__CFG_DONE_IMD; + + vop2_vp_write(vp, RK3588_VP_CLK_CTRL, vp_clk_div); + vop2_writel(vop2, RK3568_DSP_IF_EN, die); + vop2_writel(vop2, RK3568_DSP_IF_CTRL, div); + vop2_writel(vop2, RK3568_DSP_IF_POL, dip); +} + +static void vop2_set_intf_mux(struct vop2_video_port *vp, int ep_id, u32 polflags) +{ + struct vop2 *vop2 = vp->vop2; + + if (vop2->data->soc_id == 3566 || vop2->data->soc_id == 3568) { + // [CC:] drop 2nd polflags arg + rk3568_set_intf_mux(vp, ep_id, polflags, polflags); + } else if(vop2->data->soc_id == 3588) { + rk3588_set_intf_mux(vp, ep_id, polflags); + } +} + static int us_to_vertical_line(struct drm_display_mode *mode, int us) { return us * mode->clock / mode->htotal / 1000; } +// [CC:] rework virtual clock +static struct vop2_clk *vop2_clk_get(struct vop2 *vop2, const char *name) +{ + struct vop2_clk *clk, *n; + + if (!name) + return NULL; + + list_for_each_entry_safe(clk, n, &vop2->clk_list_head, list) { + if (!strcmp(clk_hw_get_name(&clk->hw), name)) + return clk; + } + + return NULL; +} + static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *state) { @@ -1564,9 +1989,11 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, u8 out_mode; u32 dsp_ctrl = 0; int act_end; - u32 val, polflags; + u32 val, polflags, invpolflags; int ret; struct drm_encoder *encoder; + char clk_name[32]; + struct vop2_clk *dclk; drm_dbg(vop2->drm, "Update mode to %dx%d%s%d, type: %d for vp%d\n", hdisplay, vdisplay, mode->flags & DRM_MODE_FLAG_INTERLACE ? "i" : "p", @@ -1597,10 +2024,20 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, if (mode->flags & DRM_MODE_FLAG_PVSYNC) polflags |= BIT(VSYNC_POSITIVE); + /* RK3588 uses inverted HDMI V/HSYNC polarity */ + if (vop2->data->soc_id == 3588) { + invpolflags = 0; + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + invpolflags |= BIT(HSYNC_POSITIVE); + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + invpolflags |= BIT(VSYNC_POSITIVE); + } else { + invpolflags = polflags; + } + drm_for_each_encoder_mask(encoder, crtc->dev, crtc_state->encoder_mask) { struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder); - - rk3568_set_intf_mux(vp, rkencoder->crtc_endpoint_id, polflags); + vop2_set_intf_mux(vp, rkencoder->crtc_endpoint_id, polflags); } if (vcstate->output_mode == ROCKCHIP_OUT_MODE_AAAA && @@ -1651,13 +2088,44 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, vop2_vp_write(vp, RK3568_VP_DSP_VTOTAL_VS_END, vtotal << 16 | vsync_len); - if (mode->flags & DRM_MODE_FLAG_DBLCLK) { - dsp_ctrl |= RK3568_VP_DSP_CTRL__CORE_DCLK_DIV; - clock *= 2; + if (vop2->data->soc_id == 3568) { + if (mode->flags & DRM_MODE_FLAG_DBLCLK) { + dsp_ctrl |= RK3568_VP_DSP_CTRL__CORE_DCLK_DIV; + // [CC:] done via mode_fixup + // clock *= 2; + } } vop2_vp_write(vp, RK3568_VP_MIPI_CTRL, 0); + snprintf(clk_name, sizeof(clk_name), "dclk%d", vp->id); + dclk = vop2_clk_get(vop2, clk_name); + if (dclk) { + /* + * use HDMI_PHY_PLL as dclk source under 4K@60 if it is available, + * otherwise use system cru as dclk source. + */ + drm_for_each_encoder_mask(encoder, crtc->dev, crtc_state->encoder_mask) { + struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder); + + // [CC:] Using PHY PLL to handle all display modes + if (rkencoder->crtc_endpoint_id == ROCKCHIP_VOP2_EP_HDMI0) { + clk_get_rate(vop2->hdmi0_phy_pll); + + if (mode->crtc_clock > VOP2_MAX_DCLK_RATE) + ret = clk_set_parent(vp->dclk, vp->dclk_parent); + else + ret = clk_set_parent(vp->dclk, vop2->hdmi0_phy_pll); + + if (ret < 0) + DRM_WARN("failed to set clock parent for %s\n", + __clk_get_name(vp->dclk)); + + clock = dclk->rate; + } + } + } + clk_set_rate(vp->dclk, clock); vop2_post_config(crtc); @@ -1668,9 +2136,71 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, drm_crtc_vblank_on(crtc); + // [CC:] needed? + ret = reset_control_assert(vp->dclk_rst); + if (ret < 0) + drm_warn(vop2->drm, "failed to assert reset: %d\n", ret); + udelay(10); + ret = reset_control_deassert(vp->dclk_rst); + if (ret < 0) + drm_warn(vop2->drm, "failed to deassert reset: %d\n", ret); + vop2_unlock(vop2); } +static void vop2_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct vop2_video_port *vp = to_vop2_video_port(crtc); + struct vop2 *vop2 = vp->vop2; + struct drm_crtc_state *old_crtc_state; + int ret; + + vop2_lock(vop2); + + old_crtc_state = drm_atomic_get_old_crtc_state(state, crtc); + drm_atomic_helper_disable_planes_on_crtc(old_crtc_state, false); + + drm_crtc_vblank_off(crtc); + + /* + * Vop standby will take effect at end of current frame, + * if dsp hold valid irq happen, it means standby complete. + * + * we must wait standby complete when we want to disable aclk, + * if not, memory bus maybe dead. + */ + reinit_completion(&vp->dsp_hold_completion); + + vop2_crtc_enable_irq(vp, VP_INT_DSP_HOLD_VALID); + + vop2_vp_write(vp, RK3568_VP_DSP_CTRL, RK3568_VP_DSP_CTRL__STANDBY); + + ret = wait_for_completion_timeout(&vp->dsp_hold_completion, + msecs_to_jiffies(50)); + if (!ret) + drm_info(vop2->drm, "wait for vp%d dsp_hold timeout\n", vp->id); + + vop2_crtc_disable_irq(vp, VP_INT_DSP_HOLD_VALID); + + clk_disable_unprepare(vp->dclk); + + vop2->enable_count--; + + if (!vop2->enable_count) + vop2_disable(vop2); + + vop2_unlock(vop2); + + if (crtc->state->event && !crtc->state->active) { + spin_lock_irq(&crtc->dev->event_lock); + drm_crtc_send_vblank_event(crtc, crtc->state->event); + spin_unlock_irq(&crtc->dev->event_lock); + + crtc->state->event = NULL; + } +} + static int vop2_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) { @@ -2104,7 +2634,43 @@ static void vop2_crtc_atomic_flush(struct drm_crtc *crtc, spin_unlock_irq(&crtc->dev->event_lock); } +static enum drm_mode_status +vop2_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode) +{ + struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc->state); + struct vop2_video_port *vp = to_vop2_video_port(crtc); + struct vop2 *vop2 = vp->vop2; + const struct vop2_data *vop2_data = vop2->data; + const struct vop2_video_port_data *vp_data = &vop2_data->vp[vp->id]; + int request_clock = mode->clock; + int clock; + + if (mode->hdisplay > vp_data->max_output.width) + return MODE_BAD_HVALUE; + + if (mode->flags & DRM_MODE_FLAG_DBLCLK) + request_clock *= 2; + + if (request_clock <= VOP2_MAX_DCLK_RATE) { + clock = request_clock; + } else { + request_clock = request_clock >> 2; + clock = clk_round_rate(vp->dclk, request_clock * 1000) / 1000; + } + + /* + * Hdmi or DisplayPort request a Accurate clock. + */ + if (vcstate->output_type == DRM_MODE_CONNECTOR_HDMIA || + vcstate->output_type == DRM_MODE_CONNECTOR_DisplayPort) + if (clock != request_clock) + return MODE_CLOCK_RANGE; + + return MODE_OK; +} + static const struct drm_crtc_helper_funcs vop2_crtc_helper_funcs = { + .mode_valid = vop2_crtc_mode_valid, .mode_fixup = vop2_crtc_mode_fixup, .atomic_check = vop2_crtc_atomic_check, .atomic_begin = vop2_crtc_atomic_begin, @@ -2299,7 +2865,7 @@ static int vop2_create_crtcs(struct vop2 *vop2) for (i = 0; i < vop2_data->nr_vps; i++) { const struct vop2_video_port_data *vp_data; struct device_node *np; - char dclk_name[9]; + char clk_name[16]; vp_data = &vop2_data->vp[i]; vp = &vop2->vps[i]; @@ -2307,13 +2873,26 @@ static int vop2_create_crtcs(struct vop2 *vop2) vp->id = vp_data->id; vp->data = vp_data; - snprintf(dclk_name, sizeof(dclk_name), "dclk_vp%d", vp->id); - vp->dclk = devm_clk_get(vop2->dev, dclk_name); + snprintf(clk_name, sizeof(clk_name), "dclk_vp%d", vp->id); + vp->dclk = devm_clk_get(vop2->dev, clk_name); if (IS_ERR(vp->dclk)) { - drm_err(vop2->drm, "failed to get %s\n", dclk_name); + drm_err(vop2->drm, "failed to get %s\n", clk_name); return PTR_ERR(vp->dclk); } + vp->dclk_rst = devm_reset_control_get_optional(vop2->dev, clk_name); + if (IS_ERR(vp->dclk_rst)) { + drm_err(vop2->drm, "failed to get dclk reset\n"); + return PTR_ERR(vp->dclk_rst); + } + + snprintf(clk_name, sizeof(clk_name), "dclk_src_vp%d", vp->id); + vp->dclk_parent = devm_clk_get_optional(vop2->dev, clk_name); + if (IS_ERR(vp->dclk_parent)) { + drm_err(vop2->drm, "failed to get %s\n", clk_name); + return PTR_ERR(vp->dclk_parent); + } + np = of_graph_get_remote_node(dev->of_node, i, -1); if (!np) { drm_dbg(vop2->drm, "%s: No remote for vp%d\n", __func__, i); @@ -2674,6 +3253,336 @@ static const struct regmap_config vop2_regmap_config = { .cache_type = REGCACHE_MAPLE, }; +/* + * BEGIN virtual clock + */ +#define PLL_RATE_MIN 30000000 + +#define cru_dbg(format, ...) do { \ + if (cru_debug) \ + pr_info("%s: " format, __func__, ## __VA_ARGS__); \ + } while (0) + +#define PNAME(x) static const char *const x[] + +enum vop_clk_branch_type { + branch_mux, + branch_divider, + branch_factor, + branch_virtual, +}; + +#define VIR(cname) \ + { \ + .branch_type = branch_virtual, \ + .name = cname, \ + } + + +#define MUX(cname, pnames, f) \ + { \ + .branch_type = branch_mux, \ + .name = cname, \ + .parent_names = pnames, \ + .num_parents = ARRAY_SIZE(pnames), \ + .flags = f, \ + } + +#define FACTOR(cname, pname, f) \ + { \ + .branch_type = branch_factor, \ + .name = cname, \ + .parent_names = (const char *[]){ pname }, \ + .num_parents = 1, \ + .flags = f, \ + } + +#define DIV(cname, pname, f, w) \ + { \ + .branch_type = branch_divider, \ + .name = cname, \ + .parent_names = (const char *[]){ pname }, \ + .num_parents = 1, \ + .flags = f, \ + .div_width = w, \ + } + +struct vop2_clk_branch { + enum vop_clk_branch_type branch_type; + const char *name; + const char *const *parent_names; + u8 num_parents; + unsigned long flags; + u8 div_shift; + u8 div_width; + u8 div_flags; +}; + +PNAME(mux_port0_dclk_src_p) = { "dclk0", "dclk1" }; +PNAME(mux_port2_dclk_src_p) = { "dclk2", "dclk1" }; +PNAME(mux_dp_pixclk_p) = { "dclk_out0", "dclk_out1", "dclk_out2" }; +PNAME(mux_hdmi_edp_clk_src_p) = { "dclk0", "dclk1", "dclk2" }; +PNAME(mux_mipi_clk_src_p) = { "dclk_out1", "dclk_out2", "dclk_out3" }; +PNAME(mux_dsc_8k_clk_src_p) = { "dclk0", "dclk1", "dclk2", "dclk3" }; +PNAME(mux_dsc_4k_clk_src_p) = { "dclk0", "dclk1", "dclk2", "dclk3" }; + +/* + * We only use this clk driver calculate the div + * of dclk_core/dclk_out/if_pixclk/if_dclk and + * the rate of the dclk from the soc. + * + * We don't touch the cru in the vop here, as + * these registers has special read andy write + * limits. + */ +static struct vop2_clk_branch rk3588_vop_clk_branches[] = { + VIR("dclk0"), + VIR("dclk1"), + VIR("dclk2"), + VIR("dclk3"), + + MUX("port0_dclk_src", mux_port0_dclk_src_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT), + DIV("dclk_core0", "port0_dclk_src", CLK_SET_RATE_PARENT, 2), + DIV("dclk_out0", "port0_dclk_src", CLK_SET_RATE_PARENT, 2), + + FACTOR("port1_dclk_src", "dclk1", CLK_SET_RATE_PARENT), + DIV("dclk_core1", "port1_dclk_src", CLK_SET_RATE_PARENT, 2), + DIV("dclk_out1", "port1_dclk_src", CLK_SET_RATE_PARENT, 2), + + MUX("port2_dclk_src", mux_port2_dclk_src_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT), + DIV("dclk_core2", "port2_dclk_src", CLK_SET_RATE_PARENT, 2), + DIV("dclk_out2", "port2_dclk_src", CLK_SET_RATE_PARENT, 2), + + FACTOR("port3_dclk_src", "dclk3", CLK_SET_RATE_PARENT), + DIV("dclk_core3", "port3_dclk_src", CLK_SET_RATE_PARENT, 2), + DIV("dclk_out3", "port3_dclk_src", CLK_SET_RATE_PARENT, 2), + + MUX("dp0_pixclk", mux_dp_pixclk_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT), + MUX("dp1_pixclk", mux_dp_pixclk_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT), + + MUX("hdmi_edp0_clk_src", mux_hdmi_edp_clk_src_p, + CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT), + DIV("hdmi_edp0_dclk", "hdmi_edp0_clk_src", 0, 2), + DIV("hdmi_edp0_pixclk", "hdmi_edp0_clk_src", CLK_SET_RATE_PARENT, 1), + + MUX("hdmi_edp1_clk_src", mux_hdmi_edp_clk_src_p, + CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT), + DIV("hdmi_edp1_dclk", "hdmi_edp1_clk_src", 0, 2), + DIV("hdmi_edp1_pixclk", "hdmi_edp1_clk_src", CLK_SET_RATE_PARENT, 1), + + MUX("mipi0_clk_src", mux_mipi_clk_src_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT), + DIV("mipi0_pixclk", "mipi0_clk_src", CLK_SET_RATE_PARENT, 2), + + MUX("mipi1_clk_src", mux_mipi_clk_src_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT), + DIV("mipi1_pixclk", "mipi1_clk_src", CLK_SET_RATE_PARENT, 2), + + FACTOR("rgb_pixclk", "port3_dclk_src", CLK_SET_RATE_PARENT), + + MUX("dsc_8k_txp_clk_src", mux_dsc_8k_clk_src_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT), + DIV("dsc_8k_txp_clk", "dsc_8k_txp_clk_src", 0, 2), + DIV("dsc_8k_pxl_clk", "dsc_8k_txp_clk_src", 0, 2), + DIV("dsc_8k_cds_clk", "dsc_8k_txp_clk_src", 0, 2), + + MUX("dsc_4k_txp_clk_src", mux_dsc_4k_clk_src_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT), + DIV("dsc_4k_txp_clk", "dsc_4k_txp_clk_src", 0, 2), + DIV("dsc_4k_pxl_clk", "dsc_4k_txp_clk_src", 0, 2), + DIV("dsc_4k_cds_clk", "dsc_4k_txp_clk_src", 0, 2), +}; + +static unsigned long clk_virtual_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct vop2_clk *vop2_clk = to_vop2_clk(hw); + + return (unsigned long)vop2_clk->rate; +} + +static long clk_virtual_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *prate) +{ + struct vop2_clk *vop2_clk = to_vop2_clk(hw); + + vop2_clk->rate = rate; + + return rate; +} + +static int clk_virtual_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + return 0; +} + +const struct clk_ops clk_virtual_ops = { + .round_rate = clk_virtual_round_rate, + .set_rate = clk_virtual_set_rate, + .recalc_rate = clk_virtual_recalc_rate, +}; + +static u8 vop2_mux_get_parent(struct clk_hw *hw) +{ + struct vop2_clk *vop2_clk = to_vop2_clk(hw); + + // cru_dbg("%s index: %d\n", clk_hw_get_name(hw), vop2_clk->parent_index); + return vop2_clk->parent_index; +} + +static int vop2_mux_set_parent(struct clk_hw *hw, u8 index) +{ + struct vop2_clk *vop2_clk = to_vop2_clk(hw); + + vop2_clk->parent_index = index; + + // cru_dbg("%s index: %d\n", clk_hw_get_name(hw), index); + return 0; +} + +static int vop2_clk_mux_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + // cru_dbg("%s %ld(min: %ld max: %ld)\n", + // clk_hw_get_name(hw), req->rate, req->min_rate, req->max_rate); + return __clk_mux_determine_rate(hw, req); +} + +static const struct clk_ops vop2_mux_clk_ops = { + .get_parent = vop2_mux_get_parent, + .set_parent = vop2_mux_set_parent, + .determine_rate = vop2_clk_mux_determine_rate, +}; + +#define div_mask(width) ((1 << (width)) - 1) + +static int vop2_div_get_val(unsigned long rate, unsigned long parent_rate) +{ + unsigned int div, value; + + div = DIV_ROUND_UP_ULL((u64)parent_rate, rate); + + value = ilog2(div); + + return value; +} + +static unsigned long vop2_clk_div_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct vop2_clk *vop2_clk = to_vop2_clk(hw); + unsigned long rate; + unsigned int div; + + div = 1 << vop2_clk->div_val; + rate = parent_rate / div; + + // cru_dbg("%s rate: %ld(prate: %ld)\n", clk_hw_get_name(hw), rate, parent_rate); + return rate; +} + +static long vop2_clk_div_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *prate) +{ + struct vop2_clk *vop2_clk = to_vop2_clk(hw); + + if (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) { + if (*prate < rate) + *prate = rate; + if ((*prate >> vop2_clk->div.width) > rate) + *prate = rate; + + if ((*prate % rate)) + *prate = rate; + + /* SOC PLL can't output a too low pll freq */ + if (*prate < PLL_RATE_MIN) + *prate = rate << vop2_clk->div.width; + } + + // cru_dbg("%s rate: %ld(prate: %ld)\n", clk_hw_get_name(hw), rate, *prate); + return rate; +} + +static int vop2_clk_div_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) +{ + struct vop2_clk *vop2_clk = to_vop2_clk(hw); + int div_val; + + div_val = vop2_div_get_val(rate, parent_rate); + vop2_clk->div_val = div_val; + + // cru_dbg("%s prate: %ld rate: %ld div_val: %d\n", + // clk_hw_get_name(hw), parent_rate, rate, div_val); + return 0; +} + +static const struct clk_ops vop2_div_clk_ops = { + .recalc_rate = vop2_clk_div_recalc_rate, + .round_rate = vop2_clk_div_round_rate, + .set_rate = vop2_clk_div_set_rate, +}; + +static struct clk *vop2_clk_register(struct vop2 *vop2, struct vop2_clk_branch *branch) +{ + struct clk_init_data init = {}; + struct vop2_clk *vop2_clk; + struct clk *clk; + + vop2_clk = devm_kzalloc(vop2->dev, sizeof(*vop2_clk), GFP_KERNEL); + if (!vop2_clk) + return ERR_PTR(-ENOMEM); + + vop2_clk->vop2 = vop2; + vop2_clk->hw.init = &init; + vop2_clk->div.shift = branch->div_shift; + vop2_clk->div.width = branch->div_width; + + init.name = branch->name; + init.flags = branch->flags; + init.num_parents = branch->num_parents; + init.parent_names = branch->parent_names; + if (branch->branch_type == branch_divider) { + init.ops = &vop2_div_clk_ops; + } else if (branch->branch_type == branch_virtual) { + init.ops = &clk_virtual_ops; + init.num_parents = 0; + init.parent_names = NULL; + } else { + init.ops = &vop2_mux_clk_ops; + } + + clk = devm_clk_register(vop2->dev, &vop2_clk->hw); + if (!IS_ERR(clk)) + list_add_tail(&vop2_clk->list, &vop2->clk_list_head); + else + DRM_DEV_ERROR(vop2->dev, "Register %s failed\n", branch->name); + + return clk; +} + +static int vop2_clk_init(struct vop2 *vop2) +{ + struct vop2_clk_branch *branch = rk3588_vop_clk_branches; + unsigned int nr_clk = ARRAY_SIZE(rk3588_vop_clk_branches); + unsigned int idx; + struct vop2_clk *clk, *n; + + INIT_LIST_HEAD(&vop2->clk_list_head); + + if (vop2->data->soc_id < 3588) + return 0; + + list_for_each_entry_safe(clk, n, &vop2->clk_list_head, list) { + list_del(&clk->list); + } + + for (idx = 0; idx < nr_clk; idx++, branch++) + vop2_clk_register(vop2, branch); + + return 0; +} +/* + * END virtual clock + */ + static int vop2_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); @@ -2726,7 +3635,12 @@ static int vop2_bind(struct device *dev, struct device *master, void *data) return PTR_ERR(vop2->lut_regs); } + // [CC:] grf -> sys_grf in downstream vop2->grf = syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,grf"); + // [CC:] vop_grf -> grf in downstream + // [CC:] make use of new grf's + vop2->vop_grf = syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,vop-grf"); + vop2->vo1_grf = syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,vo1-grf"); vop2->hclk = devm_clk_get(vop2->dev, "hclk"); if (IS_ERR(vop2->hclk)) { @@ -2740,6 +3654,31 @@ static int vop2_bind(struct device *dev, struct device *master, void *data) return PTR_ERR(vop2->aclk); } + vop2->pclk = devm_clk_get_optional(vop2->dev, "pclk"); + if (IS_ERR(vop2->pclk)) { + DRM_DEV_ERROR(vop2->dev, "failed to get pclk source\n"); + return PTR_ERR(vop2->pclk); + } + + vop2->hdmi0_phy_pll = devm_clk_get_optional(vop2->drm->dev, "hdmi0_phy_pll"); + if (IS_ERR(vop2->hdmi0_phy_pll)) { + DRM_DEV_ERROR(vop2->dev, "failed to get hdmi0_phy_pll source\n"); + return PTR_ERR(vop2->hdmi0_phy_pll); + } + + // [CC:] drop ahb_rst & axi_rst + vop2->ahb_rst = devm_reset_control_get_optional(vop2->dev, "ahb"); + if (IS_ERR(vop2->ahb_rst)) { + DRM_DEV_ERROR(vop2->dev, "failed to get ahb reset\n"); + return PTR_ERR(vop2->ahb_rst); + } + + vop2->axi_rst = devm_reset_control_get_optional(vop2->dev, "axi"); + if (IS_ERR(vop2->axi_rst)) { + DRM_DEV_ERROR(vop2->dev, "failed to get axi reset\n"); + return PTR_ERR(vop2->axi_rst); + } + vop2->irq = platform_get_irq(pdev, 0); if (vop2->irq < 0) { drm_err(vop2->drm, "cannot find irq for vop2\n"); @@ -2756,6 +3695,9 @@ static int vop2_bind(struct device *dev, struct device *master, void *data) if (ret) return ret; + // [CC:] rework virtual clock + vop2_clk_init(vop2); + ret = vop2_find_rgb_encoder(vop2); if (ret >= 0) { vop2->rgb = rockchip_rgb_init(dev, &vop2->vps[ret].crtc, diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h index 56fd31e05..cae71df81 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h @@ -12,10 +12,18 @@ #include #include -#define VOP_FEATURE_OUTPUT_10BIT BIT(0) +/* a feature to splice two windows and two vps to support resolution > 4096 */ +#define VOP_FEATURE_SPLICE BIT(5) +//[CC:] dupplicate of #define VOP_FEATURE_OUTPUT_RGB10 BIT(0) in vop.h +#define VOP_FEATURE_OUTPUT_10BIT BIT(0) +//[CC:] downstream uses #define WIN_FEATURE_AFBDC BIT(3) #define WIN_FEATURE_AFBDC BIT(0) #define WIN_FEATURE_CLUSTER BIT(1) +/* Left win in splice mode */ +#define WIN_FEATURE_SPLICE_LEFT BIT(6) + +#define HIWORD_UPDATE(v, h, l) ((GENMASK(h, l) << 16) | ((v) << (l))) /* * the delay number of a window in different mode. @@ -39,6 +47,18 @@ enum vop2_scale_down_mode { VOP2_SCALE_DOWN_AVG, }; +/* + * vop2 internal power domain id, + * should be all none zero, 0 will be treat as invalid; + */ +#define VOP2_PD_CLUSTER0 BIT(0) +#define VOP2_PD_CLUSTER1 BIT(1) +#define VOP2_PD_CLUSTER2 BIT(2) +#define VOP2_PD_CLUSTER3 BIT(3) +#define VOP2_PD_DSC_8K BIT(5) +#define VOP2_PD_DSC_4K BIT(6) +#define VOP2_PD_ESMART BIT(7) + enum vop2_win_regs { VOP2_WIN_ENABLE, VOP2_WIN_FORMAT, @@ -107,6 +127,7 @@ enum vop2_win_regs { struct vop2_win_data { const char *name; unsigned int phys_id; + uint8_t splice_win_id; u32 base; enum drm_plane_type type; @@ -129,9 +150,11 @@ struct vop2_win_data { struct vop2_video_port_data { unsigned int id; + uint8_t splice_vp_id; u32 feature; u16 gamma_lut_len; u16 cubic_lut_len; + unsigned long dclk_max; struct vop_rect max_output; const u8 pre_scan_max_dly[4]; unsigned int offset; @@ -145,7 +168,13 @@ struct vop2_data { struct vop_rect max_output; unsigned int win_size; + // [CC:] convert to an enum as it is used in conditional statements unsigned int soc_id; + + uint8_t nr_dscs; + uint8_t nr_conns; + const struct vop2_dsc_data *dsc; + const struct vop2_connector_if_data *conn; }; /* interrupt define */ @@ -450,6 +479,45 @@ enum dst_factor_mode { #define POLFLAG_DCLK_INV BIT(3) +/* RK3588 registers */ +#define RK3588_GRF_SOC_CON1 0x0304 +#define RK3588_GRF_VOP_CON2 0x08 +#define RK3588_GRF_VO1_CON0 0x00 + +#define RK3588_GRF_VO1_CON0__HDMI0_PIN_POL GENMASK(6, 5) + +#define RK3588_SYS_PD_CTRL 0x034 +#define RK3588_VP_CLK_CTRL 0x0c + +#define RK3588_VP_CLK_CTRL__DCLK_OUT_DIV GENMASK(3, 2) +#define RK3588_VP_CLK_CTRL__DCLK_CORE_DIV GENMASK(1, 0) + +#define RK3588_SYS_DSP_INFACE_EN_MIPI1_MUX GENMASK(22, 21) +#define RK3588_SYS_DSP_INFACE_EN_MIPI0_MUX GENMASK(20, 20) +#define RK3588_SYS_DSP_INFACE_EN_EDP_HDMI1_MUX GENMASK(19, 18) +#define RK3588_SYS_DSP_INFACE_EN_EDP_HDMI0_MUX GENMASK(17, 16) +#define RK3588_SYS_DSP_INFACE_EN_DP1_MUX GENMASK(15, 14) +#define RK3588_SYS_DSP_INFACE_EN_DP0_MUX GENMASK(13, 12) +#define RK3588_SYS_DSP_INFACE_EN_DPI GENMASK(9, 8) +#define RK3588_SYS_DSP_INFACE_EN_MIPI1 BIT(7) +#define RK3588_SYS_DSP_INFACE_EN_MIPI0 BIT(6) +#define RK3588_SYS_DSP_INFACE_EN_HDMI1 BIT(5) +#define RK3588_SYS_DSP_INFACE_EN_EDP1 BIT(4) +#define RK3588_SYS_DSP_INFACE_EN_HDMI0 BIT(3) +#define RK3588_SYS_DSP_INFACE_EN_EDP0 BIT(2) +#define RK3588_SYS_DSP_INFACE_EN_DP1 BIT(1) +#define RK3588_SYS_DSP_INFACE_EN_DP0 BIT(0) + +#define RK3588_DSP_IF_MIPI1_PCLK_DIV GENMASK(27, 26) +#define RK3588_DSP_IF_MIPI0_PCLK_DIV GENMASK(25, 24) +#define RK3588_DSP_IF_EDP_HDMI1_PCLK_DIV GENMASK(22, 22) +#define RK3588_DSP_IF_EDP_HDMI1_DCLK_DIV GENMASK(21, 20) +#define RK3588_DSP_IF_EDP_HDMI0_PCLK_DIV GENMASK(18, 18) +#define RK3588_DSP_IF_EDP_HDMI0_DCLK_DIV GENMASK(17, 16) + +#define RK3588_DSP_IF_POL__DP1_PIN_POL GENMASK(14, 12) +#define RK3588_DSP_IF_POL__DP0_PIN_POL GENMASK(10, 8) + enum vop2_layer_phy_id { ROCKCHIP_VOP2_CLUSTER0 = 0, ROCKCHIP_VOP2_CLUSTER1, diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c index 22288ad7f..2bc1baf5c 100644 --- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c +++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c @@ -131,6 +131,49 @@ static const struct vop2_video_port_data rk3568_vop_video_ports[] = { }, }; +static const struct vop2_video_port_data rk3588_vop_video_ports[] = { + { + .id = 0, + // .splice_vp_id = 1, + // .lut_dma_rid = 1, + .feature = VOP_FEATURE_OUTPUT_10BIT, + .gamma_lut_len = 1024, + .cubic_lut_len = 9 * 9 * 9, + // .dclk_max = 600000000, + .max_output = { 7680, 4320 }, + .pre_scan_max_dly = { 76, 65, 65, 54 }, + .offset = 0xc00, + }, { + .id = 1, + // .lut_dma_rid = 14, + .feature = VOP_FEATURE_OUTPUT_10BIT, + .gamma_lut_len = 1024, + .cubic_lut_len = 9 * 9 * 9, + // .dclk_max = 600000000, + .max_output = { 4096, 2304 }, + .pre_scan_max_dly = { 76, 65, 65, 54 }, + .offset = 0xd00, + }, { + .id = 2, + // .lut_dma_rid = 14, + .feature = VOP_FEATURE_OUTPUT_10BIT, + .gamma_lut_len = 1024, + .cubic_lut_len = 17 * 17 * 17, + // .dclk_max = 600000000, + .max_output = { 4096, 2304 }, + .pre_scan_max_dly = { 52, 52, 52, 52 }, + .offset = 0xe00, + }, { + .id = 3, + // .lut_dma_rid = 14, + .gamma_lut_len = 1024, + // .dclk_max = 200000000, + .max_output = { 2048, 1536 }, + .pre_scan_max_dly = { 52, 52, 52, 52 }, + .offset = 0xf00, + }, +}; + /* * rk3568 vop with 2 cluster, 2 esmart win, 2 smart win. * Every cluster can work as 4K win or split into two win. @@ -234,6 +277,174 @@ static const struct vop2_win_data rk3568_vop_win_data[] = { }, }; +/* + * rk3588 vop with 4 cluster, 4 esmart win. + * Every cluster can work as 4K win or split into two win. + * All win in cluster support AFBCD. + * + * Every esmart win and smart win support 4 Multi-region. + * + * Scale filter mode: + * + * * Cluster: bicubic for horizontal scale up, others use bilinear + * * ESmart: + * * nearest-neighbor/bilinear/bicubic for scale up + * * nearest-neighbor/bilinear/average for scale down + * + * AXI Read ID assignment: + * Two AXI bus: + * AXI0 is a read/write bus with a higher performance. + * AXI1 is a read only bus. + * + * Every window on a AXI bus must assigned two unique + * read id(yrgb_id/uv_id, valid id are 0x1~0xe). + * + * AXI0: + * Cluster0/1, Esmart0/1, WriteBack + * + * AXI 1: + * Cluster2/3, Esmart2/3 + */ +static const struct vop2_win_data rk3588_vop_win_data[] = { + { + .name = "Cluster0-win0", + .phys_id = ROCKCHIP_VOP2_CLUSTER0, + //[CC:] .splice_win_id = ROCKCHIP_VOP2_CLUSTER1, + .base = 0x1000, + .formats = formats_cluster, + .nformats = ARRAY_SIZE(formats_cluster), + .format_modifiers = format_modifiers_afbc, + .layer_sel_id = 0, + .supported_rotations = DRM_MODE_ROTATE_90 | DRM_MODE_ROTATE_270 | + DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y, + .type = DRM_PLANE_TYPE_OVERLAY, + // .pd_id = VOP2_PD_CLUSTER0, + // .axi_id = 0, + // .axi_yrgb_id = 2, + // .axi_uv_id = 3, + .max_upscale_factor = 4, + .max_downscale_factor = 4, + .dly = { 4, 26, 29 }, + //[CC:] WIN_FEATURE_SPLICE_LEFT + .feature = WIN_FEATURE_AFBDC | WIN_FEATURE_CLUSTER, + }, { + .name = "Cluster1-win0", + .phys_id = ROCKCHIP_VOP2_CLUSTER1, + .base = 0x1200, + .formats = formats_cluster, + .nformats = ARRAY_SIZE(formats_cluster), + .format_modifiers = format_modifiers_afbc, + .layer_sel_id = 1, + .supported_rotations = DRM_MODE_ROTATE_90 | DRM_MODE_ROTATE_270 | + DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y, + .type = DRM_PLANE_TYPE_OVERLAY, + // .pd_id = VOP2_PD_CLUSTER1, + // .axi_id = 0, + // .axi_yrgb_id = 6, + // .axi_uv_id = 7, + .max_upscale_factor = 4, + .max_downscale_factor = 4, + .dly = { 4, 26, 29 }, + .feature = WIN_FEATURE_AFBDC | WIN_FEATURE_CLUSTER, + }, { + .name = "Cluster2-win0", + .phys_id = ROCKCHIP_VOP2_CLUSTER2, + // [CC:] .splice_win_id = ROCKCHIP_VOP2_CLUSTER3, + .base = 0x1400, + .formats = formats_cluster, + .nformats = ARRAY_SIZE(formats_cluster), + .format_modifiers = format_modifiers_afbc, + .layer_sel_id = 4, + .supported_rotations = DRM_MODE_ROTATE_90 | DRM_MODE_ROTATE_270 | + DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y, + .type = DRM_PLANE_TYPE_OVERLAY, + // .pd_id = VOP2_PD_CLUSTER2, + // .axi_id = 1, + // .axi_yrgb_id = 2, + // .axi_uv_id = 3, + .max_upscale_factor = 4, + .max_downscale_factor = 4, + .dly = { 4, 26, 29 }, + //[CC:] WIN_FEATURE_SPLICE_LEFT + .feature = WIN_FEATURE_AFBDC | WIN_FEATURE_CLUSTER, + }, { + .name = "Cluster3-win0", + .phys_id = ROCKCHIP_VOP2_CLUSTER3, + .base = 0x1600, + .formats = formats_cluster, + .nformats = ARRAY_SIZE(formats_cluster), + .format_modifiers = format_modifiers_afbc, + .layer_sel_id = 5, + .supported_rotations = DRM_MODE_ROTATE_90 | DRM_MODE_ROTATE_270 | + DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y, + .type = DRM_PLANE_TYPE_OVERLAY, + // .pd_id = VOP2_PD_CLUSTER3, + // .axi_id = 1, + // .axi_yrgb_id = 6, + // .axi_uv_id = 7, + .max_upscale_factor = 4, + .max_downscale_factor = 4, + .dly = { 4, 26, 29 }, + .feature = WIN_FEATURE_AFBDC | WIN_FEATURE_CLUSTER, + }, { + .name = "Esmart0-win0", + .phys_id = ROCKCHIP_VOP2_ESMART0, + // [CC:] .splice_win_id = ROCKCHIP_VOP2_ESMART1, + .formats = formats_rk356x_esmart, + .nformats = ARRAY_SIZE(formats_rk356x_esmart), + .format_modifiers = format_modifiers, + .base = 0x1800, + .layer_sel_id = 2, + .supported_rotations = DRM_MODE_REFLECT_Y, + .type = DRM_PLANE_TYPE_PRIMARY, + // [CC:] .pd_id missing in downstream code + // .axi_id = 0, + // .axi_yrgb_id = 0x0a, + // .axi_uv_id = 0x0b, + .max_upscale_factor = 8, + .max_downscale_factor = 8, + .dly = { 23, 45, 48 }, + // .feature = WIN_FEATURE_SPLICE_LEFT | WIN_FEATURE_MULTI_AREA, + }, { + .name = "Esmart2-win0", + .phys_id = ROCKCHIP_VOP2_ESMART2, + // [CC:] .splice_win_id = ROCKCHIP_VOP2_ESMART3, + .formats = formats_rk356x_esmart, + .nformats = ARRAY_SIZE(formats_rk356x_esmart), + .format_modifiers = format_modifiers, + .base = 0x1c00, + .layer_sel_id = 6, + .supported_rotations = DRM_MODE_REFLECT_Y, + .type = DRM_PLANE_TYPE_PRIMARY, + // .pd_id = VOP2_PD_ESMART, + // .axi_id = 1, + // .axi_yrgb_id = 0x0a, + // .axi_uv_id = 0x0b, + .max_upscale_factor = 8, + .max_downscale_factor = 8, + .dly = { 23, 45, 48 }, + // .feature = WIN_FEATURE_SPLICE_LEFT | WIN_FEATURE_MULTI_AREA, + }, { + .name = "Esmart1-win0", + .phys_id = ROCKCHIP_VOP2_ESMART1, + .formats = formats_rk356x_esmart, + .nformats = ARRAY_SIZE(formats_rk356x_esmart), + .format_modifiers = format_modifiers, + .base = 0x1a00, + .layer_sel_id = 3, + .supported_rotations = DRM_MODE_REFLECT_Y, + .type = DRM_PLANE_TYPE_PRIMARY, + // .pd_id = VOP2_PD_ESMART, + // .axi_id = 0, + // .axi_yrgb_id = 0x0c, + // .axi_uv_id = 0x0d, + .max_upscale_factor = 8, + .max_downscale_factor = 8, + .dly = { 23, 45, 48 }, + // .feature = WIN_FEATURE_MULTI_AREA, + }, +}; + static const struct vop2_data rk3566_vop = { .nr_vps = 3, .max_input = { 4096, 2304 }, @@ -246,14 +457,27 @@ static const struct vop2_data rk3566_vop = { static const struct vop2_data rk3568_vop = { .nr_vps = 3, - .max_input = { 4096, 2304 }, - .max_output = { 4096, 2304 }, + .max_input = { 4096, 4320 }, + .max_output = { 4096, 4320 }, .vp = rk3568_vop_video_ports, .win = rk3568_vop_win_data, .win_size = ARRAY_SIZE(rk3568_vop_win_data), .soc_id = 3568, }; +static const struct vop2_data rk3588_vop = { + // .feature = VOP_FEATURE_SPLICE, + .nr_vps = 4, + .max_input = { 4096, 2304 }, + .max_output = { 4096, 2304 }, + .vp = rk3588_vop_video_ports, + .win = rk3588_vop_win_data, + .win_size = ARRAY_SIZE(rk3588_vop_win_data), + // .conn = rk3588_conn_if_data, + // .nr_conns = ARRAY_SIZE(rk3588_conn_if_data), + .soc_id = 3588, +}; + static const struct of_device_id vop2_dt_match[] = { { .compatible = "rockchip,rk3566-vop", @@ -261,6 +485,9 @@ static const struct of_device_id vop2_dt_match[] = { }, { .compatible = "rockchip,rk3568-vop", .data = &rk3568_vop, + }, { + .compatible = "rockchip,rk3588-vop", + .data = &rk3588_vop, }, { }, }; diff --git a/drivers/phy/rockchip/Kconfig b/drivers/phy/rockchip/Kconfig index 94360fc96..62c18e25b 100644 --- a/drivers/phy/rockchip/Kconfig +++ b/drivers/phy/rockchip/Kconfig @@ -83,6 +83,13 @@ config PHY_ROCKCHIP_PCIE help Enable this to support the Rockchip PCIe PHY. +config PHY_ROCKCHIP_SAMSUNG_HDPTX_HDMI + tristate "Rockchip Samsung HDMI/DP Combo PHY HDMI driver" + depends on OF && (ARCH_ROCKCHIP || COMPILE_TEST) + select GENERIC_PHY + help + Support for Rockchip HDMI/DP Combo PHY with Samsung IP block. + config PHY_ROCKCHIP_SNPS_PCIE3 tristate "Rockchip Snps PCIe3 PHY Driver" depends on (ARCH_ROCKCHIP && OF) || COMPILE_TEST @@ -107,3 +114,15 @@ config PHY_ROCKCHIP_USB select GENERIC_PHY help Enable this to support the Rockchip USB 2.0 PHY. + +config PHY_ROCKCHIP_USBDP + tristate "Rockchip USBDP COMBO PHY Driver" + depends on ARCH_ROCKCHIP && OF + select GENERIC_PHY + select TYPEC + help + Enable this to support the Rockchip USB3.0/DP combo PHY with + Samsung IP block. This is required for USB3 support on RK3588. + + To compile this driver as a module, choose M here: the module + will be called phy-rockchip-usbdp diff --git a/drivers/phy/rockchip/Makefile b/drivers/phy/rockchip/Makefile index 7eab12923..d266414a1 100644 --- a/drivers/phy/rockchip/Makefile +++ b/drivers/phy/rockchip/Makefile @@ -8,6 +8,8 @@ obj-$(CONFIG_PHY_ROCKCHIP_INNO_HDMI) += phy-rockchip-inno-hdmi.o obj-$(CONFIG_PHY_ROCKCHIP_INNO_USB2) += phy-rockchip-inno-usb2.o obj-$(CONFIG_PHY_ROCKCHIP_NANENG_COMBO_PHY) += phy-rockchip-naneng-combphy.o obj-$(CONFIG_PHY_ROCKCHIP_PCIE) += phy-rockchip-pcie.o +obj-$(CONFIG_PHY_ROCKCHIP_SAMSUNG_HDPTX_HDMI) += phy-rockchip-samsung-hdptx-hdmi.o obj-$(CONFIG_PHY_ROCKCHIP_SNPS_PCIE3) += phy-rockchip-snps-pcie3.o obj-$(CONFIG_PHY_ROCKCHIP_TYPEC) += phy-rockchip-typec.o obj-$(CONFIG_PHY_ROCKCHIP_USB) += phy-rockchip-usb.o +obj-$(CONFIG_PHY_ROCKCHIP_USBDP) += phy-rockchip-usbdp.o diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx-hdmi.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx-hdmi.c new file mode 100644 index 000000000..036db0877 --- /dev/null +++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx-hdmi.c @@ -0,0 +1,2347 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) Rockchip Electronics Co.Ltd + * Author: + * Algea Cao + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define UPDATE(x, h, l) (((x) << (l)) & GENMASK((h), (l))) + +#define GRF_HDPTX_CON0 0x00 +#define HDPTX_I_PLL_EN BIT(7) +#define HDPTX_I_BIAS_EN BIT(6) +#define HDPTX_I_BGR_EN BIT(5) +#define GRF_HDPTX_STATUS 0x80 +#define HDPTX_O_PLL_LOCK_DONE BIT(3) +#define HDPTX_O_PHY_CLK_RDY BIT(2) +#define HDPTX_O_PHY_RDY BIT(1) +#define HDPTX_O_SB_RDY BIT(0) + +#define CMN_REG0000 0x0000 +#define CMN_REG0001 0x0004 +#define CMN_REG0002 0x0008 +#define CMN_REG0003 0x000C +#define CMN_REG0004 0x0010 +#define CMN_REG0005 0x0014 +#define CMN_REG0006 0x0018 +#define CMN_REG0007 0x001C +#define CMN_REG0008 0x0020 +#define LCPLL_EN_MASK BIT(6) +#define LCPLL_EN(x) UPDATE(x, 4, 4) +#define LCPLL_LCVCO_MODE_EN_MASK BIT(4) +#define LCPLL_LCVCO_MODE_EN(x) UPDATE(x, 4, 4) +#define CMN_REG0009 0x0024 +#define CMN_REG000A 0x0028 +#define CMN_REG000B 0x002C +#define CMN_REG000C 0x0030 +#define CMN_REG000D 0x0034 +#define CMN_REG000E 0x0038 +#define CMN_REG000F 0x003C +#define CMN_REG0010 0x0040 +#define CMN_REG0011 0x0044 +#define CMN_REG0012 0x0048 +#define CMN_REG0013 0x004C +#define CMN_REG0014 0x0050 +#define CMN_REG0015 0x0054 +#define CMN_REG0016 0x0058 +#define CMN_REG0017 0x005C +#define CMN_REG0018 0x0060 +#define CMN_REG0019 0x0064 +#define CMN_REG001A 0x0068 +#define CMN_REG001B 0x006C +#define CMN_REG001C 0x0070 +#define CMN_REG001D 0x0074 +#define CMN_REG001E 0x0078 +#define LCPLL_PI_EN_MASK BIT(5) +#define LCPLL_PI_EN(x) UPDATE(x, 5, 5) +#define LCPLL_100M_CLK_EN_MASK BIT(0) +#define LCPLL_100M_CLK_EN(x) UPDATE(x, 0, 0) +#define CMN_REG001F 0x007C +#define CMN_REG0020 0x0080 +#define CMN_REG0021 0x0084 +#define CMN_REG0022 0x0088 +#define CMN_REG0023 0x008C +#define CMN_REG0024 0x0090 +#define CMN_REG0025 0x0094 +#define LCPLL_PMS_IQDIV_RSTN BIT(4) +#define CMN_REG0026 0x0098 +#define CMN_REG0027 0x009C +#define CMN_REG0028 0x00A0 +#define LCPLL_SDC_FRAC_EN BIT(2) +#define LCPLL_SDC_FRAC_RSTN BIT(0) +#define CMN_REG0029 0x00A4 +#define CMN_REG002A 0x00A8 +#define CMN_REG002B 0x00AC +#define CMN_REG002C 0x00B0 +#define CMN_REG002D 0x00B4 +#define LCPLL_SDC_N_MASK GENMASK(3, 1) +#define LCPLL_SDC_N(x) UPDATE(x, 3, 1) +#define CMN_REG002E 0x00B8 +#define LCPLL_SDC_NUMBERATOR_MASK GENMASK(5, 0) +#define LCPLL_SDC_NUMBERATOR(x) UPDATE(x, 5, 0) +#define CMN_REG002F 0x00BC +#define LCPLL_SDC_DENOMINATOR_MASK GENMASK(7, 2) +#define LCPLL_SDC_DENOMINATOR(x) UPDATE(x, 7, 2) +#define LCPLL_SDC_NDIV_RSTN BIT(0) +#define CMN_REG0030 0x00C0 +#define CMN_REG0031 0x00C4 +#define CMN_REG0032 0x00C8 +#define CMN_REG0033 0x00CC +#define CMN_REG0034 0x00D0 +#define CMN_REG0035 0x00D4 +#define CMN_REG0036 0x00D8 +#define CMN_REG0037 0x00DC +#define CMN_REG0038 0x00E0 +#define CMN_REG0039 0x00E4 +#define CMN_REG003A 0x00E8 +#define CMN_REG003B 0x00EC +#define CMN_REG003C 0x00F0 +#define CMN_REG003D 0x00F4 +#define ROPLL_LCVCO_EN BIT(4) +#define CMN_REG003E 0x00F8 +#define CMN_REG003F 0x00FC +#define CMN_REG0040 0x0100 +#define CMN_REG0041 0x0104 +#define CMN_REG0042 0x0108 +#define CMN_REG0043 0x010C +#define CMN_REG0044 0x0110 +#define CMN_REG0045 0x0114 +#define CMN_REG0046 0x0118 +#define CMN_REG0047 0x011C +#define CMN_REG0048 0x0120 +#define CMN_REG0049 0x0124 +#define CMN_REG004A 0x0128 +#define CMN_REG004B 0x012C +#define CMN_REG004C 0x0130 +#define CMN_REG004D 0x0134 +#define CMN_REG004E 0x0138 +#define ROPLL_PI_EN BIT(5) +#define CMN_REG004F 0x013C +#define CMN_REG0050 0x0140 +#define CMN_REG0051 0x0144 +#define CMN_REG0052 0x0148 +#define CMN_REG0053 0x014C +#define CMN_REG0054 0x0150 +#define CMN_REG0055 0x0154 +#define CMN_REG0056 0x0158 +#define CMN_REG0057 0x015C +#define CMN_REG0058 0x0160 +#define CMN_REG0059 0x0164 +#define CMN_REG005A 0x0168 +#define CMN_REG005B 0x016C +#define CMN_REG005C 0x0170 +#define ROPLL_PMS_IQDIV_RSTN BIT(5) +#define CMN_REG005D 0x0174 +#define CMN_REG005E 0x0178 +#define ROPLL_SDM_EN_MASK BIT(6) +#define ROPLL_SDM_EN(x) UPDATE(x, 6, 6) +#define ROPLL_SDM_FRAC_EN_RBR BIT(3) +#define ROPLL_SDM_FRAC_EN_HBR BIT(2) +#define ROPLL_SDM_FRAC_EN_HBR2 BIT(1) +#define ROPLL_SDM_FRAC_EN_HBR3 BIT(0) +#define CMN_REG005F 0x017C +#define CMN_REG0060 0x0180 +#define CMN_REG0061 0x0184 +#define CMN_REG0062 0x0188 +#define CMN_REG0063 0x018C +#define CMN_REG0064 0x0190 +#define ROPLL_SDM_NUM_SIGN_RBR_MASK BIT(3) +#define ROPLL_SDM_NUM_SIGN_RBR(x) UPDATE(x, 3, 3) +#define CMN_REG0065 0x0194 +#define CMN_REG0066 0x0198 +#define CMN_REG0067 0x019C +#define CMN_REG0068 0x01A0 +#define CMN_REG0069 0x01A4 +#define ROPLL_SDC_N_RBR_MASK GENMASK(2, 0) +#define ROPLL_SDC_N_RBR(x) UPDATE(x, 2, 0) +#define CMN_REG006A 0x01A8 +#define CMN_REG006B 0x01AC +#define CMN_REG006C 0x01B0 +#define CMN_REG006D 0x01B4 +#define CMN_REG006E 0x01B8 +#define CMN_REG006F 0x01BC +#define CMN_REG0070 0x01C0 +#define CMN_REG0071 0x01C4 +#define CMN_REG0072 0x01C8 +#define CMN_REG0073 0x01CC +#define CMN_REG0074 0x01D0 +#define ROPLL_SDC_NDIV_RSTN BIT(2) +#define ROPLL_SSC_EN BIT(0) +#define CMN_REG0075 0x01D4 +#define CMN_REG0076 0x01D8 +#define CMN_REG0077 0x01DC +#define CMN_REG0078 0x01E0 +#define CMN_REG0079 0x01E4 +#define CMN_REG007A 0x01E8 +#define CMN_REG007B 0x01EC +#define CMN_REG007C 0x01F0 +#define CMN_REG007D 0x01F4 +#define CMN_REG007E 0x01F8 +#define CMN_REG007F 0x01FC +#define CMN_REG0080 0x0200 +#define CMN_REG0081 0x0204 +#define OVRD_PLL_CD_CLK_EN BIT(8) +#define PLL_CD_HSCLK_EAST_EN BIT(0) +#define CMN_REG0082 0x0208 +#define CMN_REG0083 0x020C +#define CMN_REG0084 0x0210 +#define CMN_REG0085 0x0214 +#define CMN_REG0086 0x0218 +#define PLL_PCG_POSTDIV_SEL_MASK GENMASK(7, 4) +#define PLL_PCG_POSTDIV_SEL(x) UPDATE(x, 7, 4) +#define PLL_PCG_CLK_SEL_MASK GENMASK(3, 1) +#define PLL_PCG_CLK_SEL(x) UPDATE(x, 3, 1) +#define PLL_PCG_CLK_EN BIT(0) +#define CMN_REG0087 0x021C +#define PLL_FRL_MODE_EN BIT(3) +#define PLL_TX_HS_CLK_EN BIT(2) +#define CMN_REG0088 0x0220 +#define CMN_REG0089 0x0224 +#define LCPLL_ALONE_MODE BIT(1) +#define CMN_REG008A 0x0228 +#define CMN_REG008B 0x022C +#define CMN_REG008C 0x0230 +#define CMN_REG008D 0x0234 +#define CMN_REG008E 0x0238 +#define CMN_REG008F 0x023C +#define CMN_REG0090 0x0240 +#define CMN_REG0091 0x0244 +#define CMN_REG0092 0x0248 +#define CMN_REG0093 0x024C +#define CMN_REG0094 0x0250 +#define CMN_REG0095 0x0254 +#define CMN_REG0096 0x0258 +#define CMN_REG0097 0x025C +#define DIG_CLK_SEL BIT(1) +#define ROPLL_REF BIT(1) +#define LCPLL_REF 0 +#define CMN_REG0098 0x0260 +#define CMN_REG0099 0x0264 +#define CMN_ROPLL_ALONE_MODE BIT(2) +#define ROPLL_ALONE_MODE BIT(2) +#define CMN_REG009A 0x0268 +#define HS_SPEED_SEL BIT(0) +#define DIV_10_CLOCK BIT(0) +#define CMN_REG009B 0x026C +#define IS_SPEED_SEL BIT(4) +#define LINK_SYMBOL_CLOCK BIT(4) +#define LINK_SYMBOL_CLOCK1_2 0 +#define CMN_REG009C 0x0270 +#define CMN_REG009D 0x0274 +#define CMN_REG009E 0x0278 +#define CMN_REG009F 0x027C +#define CMN_REG00A0 0x0280 +#define CMN_REG00A1 0x0284 +#define CMN_REG00A2 0x0288 +#define CMN_REG00A3 0x028C +#define CMN_REG00AD 0x0290 +#define CMN_REG00A5 0x0294 +#define CMN_REG00A6 0x0298 +#define CMN_REG00A7 0x029C +#define SB_REG0100 0x0400 +#define SB_REG0101 0x0404 +#define SB_REG0102 0x0408 +#define OVRD_SB_RXTERM_EN_MASK BIT(5) +#define OVRD_SB_RXTERM_EN(x) UPDATE(x, 5, 5) +#define SB_RXTERM_EN_MASK BIT(4) +#define SB_RXTERM_EN(x) UPDATE(x, 4, 4) +#define ANA_SB_RXTERM_OFFSP_MASK GENMASK(3, 0) +#define ANA_SB_RXTERM_OFFSP(x) UPDATE(x, 3, 0) +#define SB_REG0103 0x040C +#define ANA_SB_RXTERM_OFFSN_MASK GENMASK(6, 3) +#define ANA_SB_RXTERM_OFFSN(x) UPDATE(x, 6, 3) +#define OVRD_SB_RX_RESCAL_DONE_MASK BIT(1) +#define OVRD_SB_RX_RESCAL_DONE(x) UPDATE(x, 1, 1) +#define SB_RX_RESCAL_DONE_MASK BIT(0) +#define SB_RX_RESCAL_DONE(x) UPDATE(x, 0, 0) +#define SB_REG0104 0x0410 +#define OVRD_SB_EN_MASK BIT(5) +#define OVRD_SB_EN(x) UPDATE(x, 5, 5) +#define SB_EN_MASK BIT(4) +#define SB_EN(x) UPDATE(x, 4, 4) +#define SB_REG0105 0x0414 +#define OVRD_SB_EARC_CMDC_EN_MASK BIT(6) +#define OVRD_SB_EARC_CMDC_EN(x) UPDATE(x, 6, 6) +#define SB_EARC_CMDC_EN_MASK BIT(5) +#define SB_EARC_CMDC_EN(x) UPDATE(x, 5, 5) +#define ANA_SB_TX_HLVL_PROG_MASK GENMASK(2, 0) +#define ANA_SB_TX_HLVL_PROG(x) UPDATE(x, 2, 0) +#define SB_REG0106 0x0418 +#define ANA_SB_TX_LLVL_PROG_MASK GENMASK(6, 4) +#define ANA_SB_TX_LLVL_PROG(x) UPDATE(x, 6, 4) +#define SB_REG0107 0x041C +#define SB_REG0108 0x0420 +#define SB_REG0109 0x0424 +#define ANA_SB_DMRX_AFC_DIV_RATIO_MASK GENMASK(2, 0) +#define ANA_SB_DMRX_AFC_DIV_RATIO(x) UPDATE(x, 2, 0) +#define SB_REG010A 0x0428 +#define SB_REG010B 0x042C +#define SB_REG010C 0x0430 +#define SB_REG010D 0x0434 +#define SB_REG010E 0x0438 +#define SB_REG010F 0x043C +#define OVRD_SB_VREG_EN_MASK BIT(7) +#define OVRD_SB_VREG_EN(x) UPDATE(x, 7, 7) +#define SB_VREG_EN_MASK BIT(6) +#define SB_VREG_EN(x) UPDATE(x, 6, 6) +#define OVRD_SB_VREG_LPF_BYPASS_MASK BIT(5) +#define OVRD_SB_VREG_LPF_BYPASS(x) UPDATE(x, 5, 5) +#define SB_VREG_LPF_BYPASS_MASK BIT(4) +#define SB_VREG_LPF_BYPASS(x) UPDATE(x, 4, 4) +#define ANA_SB_VREG_GAIN_CTRL_MASK GENMASK(3, 0) +#define ANA_SB_VREG_GAIN_CTRL(x) UPDATE(x, 3, 0) +#define SB_REG0110 0x0440 +#define ANA_SB_VREG_REF_SEL_MASK BIT(0) +#define ANA_SB_VREG_REF_SEL(x) UPDATE(x, 0, 0) +#define SB_REG0111 0x0444 +#define SB_REG0112 0x0448 +#define SB_REG0113 0x044C +#define SB_RX_RCAL_OPT_CODE_MASK GENMASK(5, 4) +#define SB_RX_RCAL_OPT_CODE(x) UPDATE(x, 5, 4) +#define SB_RX_RTERM_CTRL_MASK GENMASK(3, 0) +#define SB_RX_RTERM_CTRL(x) UPDATE(x, 3, 0) +#define SB_REG0114 0x0450 +#define SB_TG_SB_EN_DELAY_TIME_MASK GENMASK(5, 3) +#define SB_TG_SB_EN_DELAY_TIME(x) UPDATE(x, 5, 3) +#define SB_TG_RXTERM_EN_DELAY_TIME_MASK GENMASK(2, 0) +#define SB_TG_RXTERM_EN_DELAY_TIME(x) UPDATE(x, 2, 0) +#define SB_REG0115 0x0454 +#define SB_READY_DELAY_TIME_MASK GENMASK(5, 3) +#define SB_READY_DELAY_TIME(x) UPDATE(x, 5, 3) +#define SB_TG_OSC_EN_DELAY_TIME_MASK GENMASK(2, 0) +#define SB_TG_OSC_EN_DELAY_TIME(x) UPDATE(x, 2, 0) +#define SB_REG0116 0x0458 +#define AFC_RSTN_DELAY_TIME_MASK GENMASK(6, 4) +#define AFC_RSTN_DELAY_TIME(x) UPDATE(x, 6, 4) +#define SB_REG0117 0x045C +#define FAST_PULSE_TIME_MASK GENMASK(3, 0) +#define FAST_PULSE_TIME(x) UPDATE(x, 3, 0) +#define SB_REG0118 0x0460 +#define SB_REG0119 0x0464 +#define SB_REG011A 0x0468 +#define SB_REG011B 0x046C +#define SB_EARC_SIG_DET_BYPASS_MASK BIT(4) +#define SB_EARC_SIG_DET_BYPASS(x) UPDATE(x, 4, 4) +#define SB_AFC_TOL_MASK GENMASK(3, 0) +#define SB_AFC_TOL(x) UPDATE(x, 3, 0) +#define SB_REG011C 0x0470 +#define SB_REG011D 0x0474 +#define SB_REG011E 0x0478 +#define SB_REG011F 0x047C +#define SB_PWM_AFC_CTRL_MASK GENMASK(7, 2) +#define SB_PWM_AFC_CTRL(x) UPDATE(x, 7, 2) +#define SB_RCAL_RSTN_MASK BIT(1) +#define SB_RCAL_RSTN(x) UPDATE(x, 1, 1) +#define SB_REG0120 0x0480 +#define SB_EARC_EN_MASK BIT(1) +#define SB_EARC_EN(x) UPDATE(x, 1, 1) +#define SB_EARC_AFC_EN_MASK BIT(2) +#define SB_EARC_AFC_EN(x) UPDATE(x, 2, 2) +#define SB_REG0121 0x0484 +#define SB_REG0122 0x0488 +#define SB_REG0123 0x048C +#define OVRD_SB_READY_MASK BIT(5) +#define OVRD_SB_READY(x) UPDATE(x, 5, 5) +#define SB_READY_MASK BIT(4) +#define SB_READY(x) UPDATE(x, 4, 4) +#define SB_REG0124 0x0490 +#define SB_REG0125 0x0494 +#define SB_REG0126 0x0498 +#define SB_REG0127 0x049C +#define SB_REG0128 0x04A0 +#define SB_REG0129 0x04AD +#define LNTOP_REG0200 0x0800 +#define PROTOCOL_SEL BIT(2) +#define HDMI_MODE BIT(2) +#define HDMI_TMDS_FRL_SEL BIT(1) +#define LNTOP_REG0201 0x0804 +#define LNTOP_REG0202 0x0808 +#define LNTOP_REG0203 0x080C +#define LNTOP_REG0204 0x0810 +#define LNTOP_REG0205 0x0814 +#define LNTOP_REG0206 0x0818 +#define DATA_BUS_WIDTH (0x3 << 1) +#define WIDTH_40BIT (0x3 << 1) +#define WIDTH_36BIT (0x2 << 1) +#define DATA_BUS_SEL BIT(0) +#define DATA_BUS_36_40 BIT(0) +#define LNTOP_REG0207 0x081C +#define LANE_EN 0xf +#define ALL_LANE_EN 0xf +#define LNTOP_REG0208 0x0820 +#define LNTOP_REG0209 0x0824 +#define LNTOP_REG020A 0x0828 +#define LNTOP_REG020B 0x082C +#define LNTOP_REG020C 0x0830 +#define LNTOP_REG020D 0x0834 +#define LNTOP_REG020E 0x0838 +#define LNTOP_REG020F 0x083C +#define LNTOP_REG0210 0x0840 +#define LNTOP_REG0211 0x0844 +#define LNTOP_REG0212 0x0848 +#define LNTOP_REG0213 0x084C +#define LNTOP_REG0214 0x0850 +#define LNTOP_REG0215 0x0854 +#define LNTOP_REG0216 0x0858 +#define LNTOP_REG0217 0x085C +#define LNTOP_REG0218 0x0860 +#define LNTOP_REG0219 0x0864 +#define LNTOP_REG021A 0x0868 +#define LNTOP_REG021B 0x086C +#define LNTOP_REG021C 0x0870 +#define LNTOP_REG021D 0x0874 +#define LNTOP_REG021E 0x0878 +#define LNTOP_REG021F 0x087C +#define LNTOP_REG0220 0x0880 +#define LNTOP_REG0221 0x0884 +#define LNTOP_REG0222 0x0888 +#define LNTOP_REG0223 0x088C +#define LNTOP_REG0224 0x0890 +#define LNTOP_REG0225 0x0894 +#define LNTOP_REG0226 0x0898 +#define LNTOP_REG0227 0x089C +#define LNTOP_REG0228 0x08A0 +#define LNTOP_REG0229 0x08A4 +#define LANE_REG0300 0x0C00 +#define LANE_REG0301 0x0C04 +#define LANE_REG0302 0x0C08 +#define LANE_REG0303 0x0C0C +#define LANE_REG0304 0x0C10 +#define LANE_REG0305 0x0C14 +#define LANE_REG0306 0x0C18 +#define LANE_REG0307 0x0C1C +#define LANE_REG0308 0x0C20 +#define LANE_REG0309 0x0C24 +#define LANE_REG030A 0x0C28 +#define LANE_REG030B 0x0C2C +#define LANE_REG030C 0x0C30 +#define LANE_REG030D 0x0C34 +#define LANE_REG030E 0x0C38 +#define LANE_REG030F 0x0C3C +#define LANE_REG0310 0x0C40 +#define LANE_REG0311 0x0C44 +#define LANE_REG0312 0x0C48 +#define LN0_TX_SER_RATE_SEL_RBR BIT(5) +#define LN0_TX_SER_RATE_SEL_HBR BIT(4) +#define LN0_TX_SER_RATE_SEL_HBR2 BIT(3) +#define LN0_TX_SER_RATE_SEL_HBR3 BIT(2) +#define LANE_REG0313 0x0C4C +#define LANE_REG0314 0x0C50 +#define LANE_REG0315 0x0C54 +#define LANE_REG0316 0x0C58 +#define LANE_REG0317 0x0C5C +#define LANE_REG0318 0x0C60 +#define LANE_REG0319 0x0C64 +#define LANE_REG031A 0x0C68 +#define LANE_REG031B 0x0C6C +#define LANE_REG031C 0x0C70 +#define LANE_REG031D 0x0C74 +#define LANE_REG031E 0x0C78 +#define LANE_REG031F 0x0C7C +#define LANE_REG0320 0x0C80 +#define LANE_REG0321 0x0C84 +#define LANE_REG0322 0x0C88 +#define LANE_REG0323 0x0C8C +#define LANE_REG0324 0x0C90 +#define LANE_REG0325 0x0C94 +#define LANE_REG0326 0x0C98 +#define LANE_REG0327 0x0C9C +#define LANE_REG0328 0x0CA0 +#define LANE_REG0329 0x0CA4 +#define LANE_REG032A 0x0CA8 +#define LANE_REG032B 0x0CAC +#define LANE_REG032C 0x0CB0 +#define LANE_REG032D 0x0CB4 +#define LANE_REG0400 0x1000 +#define LANE_REG0401 0x1004 +#define LANE_REG0402 0x1008 +#define LANE_REG0403 0x100C +#define LANE_REG0404 0x1010 +#define LANE_REG0405 0x1014 +#define LANE_REG0406 0x1018 +#define LANE_REG0407 0x101C +#define LANE_REG0408 0x1020 +#define LANE_REG0409 0x1024 +#define LANE_REG040A 0x1028 +#define LANE_REG040B 0x102C +#define LANE_REG040C 0x1030 +#define LANE_REG040D 0x1034 +#define LANE_REG040E 0x1038 +#define LANE_REG040F 0x103C +#define LANE_REG0410 0x1040 +#define LANE_REG0411 0x1044 +#define LANE_REG0412 0x1048 +#define LN1_TX_SER_RATE_SEL_RBR BIT(5) +#define LN1_TX_SER_RATE_SEL_HBR BIT(4) +#define LN1_TX_SER_RATE_SEL_HBR2 BIT(3) +#define LN1_TX_SER_RATE_SEL_HBR3 BIT(2) +#define LANE_REG0413 0x104C +#define LANE_REG0414 0x1050 +#define LANE_REG0415 0x1054 +#define LANE_REG0416 0x1058 +#define LANE_REG0417 0x105C +#define LANE_REG0418 0x1060 +#define LANE_REG0419 0x1064 +#define LANE_REG041A 0x1068 +#define LANE_REG041B 0x106C +#define LANE_REG041C 0x1070 +#define LANE_REG041D 0x1074 +#define LANE_REG041E 0x1078 +#define LANE_REG041F 0x107C +#define LANE_REG0420 0x1080 +#define LANE_REG0421 0x1084 +#define LANE_REG0422 0x1088 +#define LANE_REG0423 0x108C +#define LANE_REG0424 0x1090 +#define LANE_REG0425 0x1094 +#define LANE_REG0426 0x1098 +#define LANE_REG0427 0x109C +#define LANE_REG0428 0x10A0 +#define LANE_REG0429 0x10A4 +#define LANE_REG042A 0x10A8 +#define LANE_REG042B 0x10AC +#define LANE_REG042C 0x10B0 +#define LANE_REG042D 0x10B4 +#define LANE_REG0500 0x1400 +#define LANE_REG0501 0x1404 +#define LANE_REG0502 0x1408 +#define LANE_REG0503 0x140C +#define LANE_REG0504 0x1410 +#define LANE_REG0505 0x1414 +#define LANE_REG0506 0x1418 +#define LANE_REG0507 0x141C +#define LANE_REG0508 0x1420 +#define LANE_REG0509 0x1424 +#define LANE_REG050A 0x1428 +#define LANE_REG050B 0x142C +#define LANE_REG050C 0x1430 +#define LANE_REG050D 0x1434 +#define LANE_REG050E 0x1438 +#define LANE_REG050F 0x143C +#define LANE_REG0510 0x1440 +#define LANE_REG0511 0x1444 +#define LANE_REG0512 0x1448 +#define LN2_TX_SER_RATE_SEL_RBR BIT(5) +#define LN2_TX_SER_RATE_SEL_HBR BIT(4) +#define LN2_TX_SER_RATE_SEL_HBR2 BIT(3) +#define LN2_TX_SER_RATE_SEL_HBR3 BIT(2) +#define LANE_REG0513 0x144C +#define LANE_REG0514 0x1450 +#define LANE_REG0515 0x1454 +#define LANE_REG0516 0x1458 +#define LANE_REG0517 0x145C +#define LANE_REG0518 0x1460 +#define LANE_REG0519 0x1464 +#define LANE_REG051A 0x1468 +#define LANE_REG051B 0x146C +#define LANE_REG051C 0x1470 +#define LANE_REG051D 0x1474 +#define LANE_REG051E 0x1478 +#define LANE_REG051F 0x147C +#define LANE_REG0520 0x1480 +#define LANE_REG0521 0x1484 +#define LANE_REG0522 0x1488 +#define LANE_REG0523 0x148C +#define LANE_REG0524 0x1490 +#define LANE_REG0525 0x1494 +#define LANE_REG0526 0x1498 +#define LANE_REG0527 0x149C +#define LANE_REG0528 0x14A0 +#define LANE_REG0529 0x14AD +#define LANE_REG052A 0x14A8 +#define LANE_REG052B 0x14AC +#define LANE_REG052C 0x14B0 +#define LANE_REG052D 0x14B4 +#define LANE_REG0600 0x1800 +#define LANE_REG0601 0x1804 +#define LANE_REG0602 0x1808 +#define LANE_REG0603 0x180C +#define LANE_REG0604 0x1810 +#define LANE_REG0605 0x1814 +#define LANE_REG0606 0x1818 +#define LANE_REG0607 0x181C +#define LANE_REG0608 0x1820 +#define LANE_REG0609 0x1824 +#define LANE_REG060A 0x1828 +#define LANE_REG060B 0x182C +#define LANE_REG060C 0x1830 +#define LANE_REG060D 0x1834 +#define LANE_REG060E 0x1838 +#define LANE_REG060F 0x183C +#define LANE_REG0610 0x1840 +#define LANE_REG0611 0x1844 +#define LANE_REG0612 0x1848 +#define LN3_TX_SER_RATE_SEL_RBR BIT(5) +#define LN3_TX_SER_RATE_SEL_HBR BIT(4) +#define LN3_TX_SER_RATE_SEL_HBR2 BIT(3) +#define LN3_TX_SER_RATE_SEL_HBR3 BIT(2) +#define LANE_REG0613 0x184C +#define LANE_REG0614 0x1850 +#define LANE_REG0615 0x1854 +#define LANE_REG0616 0x1858 +#define LANE_REG0617 0x185C +#define LANE_REG0618 0x1860 +#define LANE_REG0619 0x1864 +#define LANE_REG061A 0x1868 +#define LANE_REG061B 0x186C +#define LANE_REG061C 0x1870 +#define LANE_REG061D 0x1874 +#define LANE_REG061E 0x1878 +#define LANE_REG061F 0x187C +#define LANE_REG0620 0x1880 +#define LANE_REG0621 0x1884 +#define LANE_REG0622 0x1888 +#define LANE_REG0623 0x188C +#define LANE_REG0624 0x1890 +#define LANE_REG0625 0x1894 +#define LANE_REG0626 0x1898 +#define LANE_REG0627 0x189C +#define LANE_REG0628 0x18A0 +#define LANE_REG0629 0x18A4 +#define LANE_REG062A 0x18A8 +#define LANE_REG062B 0x18AC +#define LANE_REG062C 0x18B0 +#define LANE_REG062D 0x18B4 + +#define HDMI20_MAX_RATE 600000000 +#define DATA_RATE_MASK 0xFFFFFFF +#define COLOR_DEPTH_MASK BIT(31) +#define HDMI_MODE_MASK BIT(30) +#define HDMI_EARC_MASK BIT(29) + +enum hdptx_combphy_type { + SS_HDMI, + SS_DP +}; + + +struct lcpll_config { + u32 bit_rate; + u8 lcvco_mode_en; + u8 pi_en; + u8 clk_en_100m; + u8 pms_mdiv; + u8 pms_mdiv_afc; + u8 pms_pdiv; + u8 pms_refdiv; + u8 pms_sdiv; + u8 pi_cdiv_rstn; + u8 pi_cdiv_sel; + u8 sdm_en; + u8 sdm_rstn; + u8 sdc_frac_en; + u8 sdc_rstn; + u8 sdm_deno; + u8 sdm_num_sign; + u8 sdm_num; + u8 sdc_n; + u8 sdc_n2; + u8 sdc_num; + u8 sdc_deno; + u8 sdc_ndiv_rstn; + u8 ssc_en; + u8 ssc_fm_dev; + u8 ssc_fm_freq; + u8 ssc_clk_div_sel; + u8 cd_tx_ser_rate_sel; +}; + +struct ropll_config { + u32 bit_rate; + u8 pms_mdiv; + u8 pms_mdiv_afc; + u8 pms_pdiv; + u8 pms_refdiv; + u8 pms_sdiv; + u8 pms_iqdiv_rstn; + u8 ref_clk_sel; + u8 sdm_en; + u8 sdm_rstn; + u8 sdc_frac_en; + u8 sdc_rstn; + u8 sdm_clk_div; + u8 sdm_deno; + u8 sdm_num_sign; + u8 sdm_num; + u8 sdc_n; + u8 sdc_num; + u8 sdc_deno; + u8 sdc_ndiv_rstn; + u8 ssc_en; + u8 ssc_fm_dev; + u8 ssc_fm_freq; + u8 ssc_clk_div_sel; + u8 ana_cpp_ctrl; + u8 ana_lpf_c_sel; + u8 cd_tx_ser_rate_sel; +}; + +struct rockchip_hdptx_phy { + struct device *dev; + struct regmap *regmap; + struct regmap *grf; + + int irq; + int id; + + struct phy *phy; + struct clk_bulk_data *clks; + int nr_clks; + struct phy_config *phy_cfg; + + /* clk provider */ + struct clk_hw hw; + struct clk *dclk; + unsigned long rate; + + struct reset_control *phy_reset; + struct reset_control *apb_reset; + struct reset_control *cmn_reset; + struct reset_control *init_reset; + struct reset_control *lane_reset; + struct reset_control *ropll_reset; + struct reset_control *lcpll_reset; + + bool earc_en; + int count; +}; + +struct lcpll_config lcpll_cfg[] = { + { 48000000, 1, 0, 0, 0x7d, 0x7d, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 2, + 0, 0x13, 0x18, 1, 0, 0x20, 0x0c, 1, 0, + }, + { 40000000, 1, 1, 0, 0x68, 0x68, 1, 1, 0, 0, 0, 1, 1, 1, 1, 9, 0, 1, 1, + 0, 2, 3, 1, 0, 0x20, 0x0c, 1, 0, + }, + { 32000000, 1, 1, 1, 0x6b, 0x6b, 1, 1, 0, 1, 2, 1, 1, 1, 1, 9, 1, 2, 1, + 0, 0x0d, 0x18, 1, 0, 0x20, 0x0c, 1, 1, + }, + { ~0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + }, +}; + +struct ropll_config ropll_frl_cfg[] = { + { 24000000, 0x19, 0x19, 1, 1, 0, 1, 2, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, + 0, 0x20, 0x0c, 1, 0x0e, 0, 0, + }, + { 18000000, 0x7d, 0x7d, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, + 0, 0x20, 0x0c, 1, 0x0e, 0, 0, + }, + { 9000000, 0x7d, 0x7d, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, + 0, 0x20, 0x0c, 1, 0x0e, 0, 0, + }, + { ~0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + }, +}; + +struct ropll_config ropll_tmds_cfg[] = { + { 5940000, 124, 124, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 62, 1, 16, 5, 0, + 1, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, + }, + { 3712500, 155, 155, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 62, 1, 16, 5, 0, + 1, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, + }, + { 2970000, 124, 124, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 62, 1, 16, 5, 0, + 1, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, + }, + { 1620000, 135, 135, 1, 1, 3, 1, 1, 0, 1, 1, 1, 1, 4, 0, 3, 5, 5, 0x10, + 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, + }, + { 1856250, 155, 155, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 62, 1, 16, 5, 0, + 1, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, + }, + { 1540000, 193, 193, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 193, 1, 32, 2, 1, + 1, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, + }, + { 1485000, 0x7b, 0x7b, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 4, 0, 3, 5, 5, 0x10, + 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, + }, + { 1462500, 122, 122, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 244, 1, 16, 2, 1, 1, + 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, + }, + { 1190000, 149, 149, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 149, 1, 16, 2, 1, 1, + 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, + }, + { 1065000, 89, 89, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 89, 1, 16, 1, 0, 1, + 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, + }, + { 1080000, 135, 135, 1, 1, 5, 1, 1, 0, 1, 0, 1, 1, 0x9, 0, 0x05, 0, 0x14, + 0x18, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, + }, + { 855000, 214, 214, 1, 1, 11, 1, 1, 1, 1, 1, 1, 1, 214, 1, 16, 2, 1, + 1, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, + }, + { 835000, 105, 105, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 42, 1, 16, 1, 0, + 1, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, + }, + { 928125, 155, 155, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 62, 1, 16, 5, 0, + 1, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, + }, + { 742500, 124, 124, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 62, 1, 16, 5, 0, + 1, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, + }, + { 650000, 162, 162, 1, 1, 11, 1, 1, 1, 1, 1, 1, 1, 54, 0, 16, 4, 1, + 1, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, + }, + { 337500, 0x70, 0x70, 1, 1, 0xf, 1, 1, 1, 1, 1, 1, 1, 0x2, 0, 0x01, 5, 1, + 1, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, + }, + { 400000, 100, 100, 1, 1, 11, 1, 1, 0, 1, 0, 1, 1, 0x9, 0, 0x05, 0, 0x14, + 0x18, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, + }, + { 270000, 0x5a, 0x5a, 1, 1, 0xf, 1, 1, 0, 1, 0, 1, 1, 0x9, 0, 0x05, 0, 0x14, + 0x18, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, + }, + { 251750, 84, 84, 1, 1, 0xf, 1, 1, 1, 1, 1, 1, 1, 168, 1, 16, 4, 1, + 1, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, + }, + { ~0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + }, +}; + +static bool rockchip_hdptx_phy_is_accissible_reg(struct device *dev, + unsigned int reg) +{ + switch (reg) { + case 0x0000 ... 0x029c: + case 0x0400 ... 0x04a4: + case 0x0800 ... 0x08a4: + case 0x0c00 ... 0x0cb4: + case 0x1000 ... 0x10b4: + case 0x1400 ... 0x14b4: + case 0x1800 ... 0x18b4: + return true; + default: + return false; + } +} + +static const struct regmap_config rockchip_hdptx_phy_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .fast_io = true, + .max_register = 0x18b4, + .name = "hdptx-combphy", + + .readable_reg = rockchip_hdptx_phy_is_accissible_reg, + .writeable_reg = rockchip_hdptx_phy_is_accissible_reg, +}; + +static inline struct rockchip_hdptx_phy *to_rockchip_hdptx_phy(struct clk_hw *hw) +{ + return container_of(hw, struct rockchip_hdptx_phy, hw); +} + +static inline void hdptx_write(struct rockchip_hdptx_phy *hdptx, u32 reg, u8 val) +{ + regmap_write(hdptx->regmap, reg, val); +} + +static inline u8 hdptx_read(struct rockchip_hdptx_phy *hdptx, u32 reg) +{ + u32 val; + + regmap_read(hdptx->regmap, reg, &val); + + return val; +} + +static inline void hdptx_update_bits(struct rockchip_hdptx_phy *hdptx, u32 reg, + u8 mask, u8 val) +{ + regmap_update_bits(hdptx->regmap, reg, mask, val); +} + +static inline void hdptx_grf_write(struct rockchip_hdptx_phy *hdptx, u32 reg, u32 val) +{ + regmap_write(hdptx->grf, reg, val); +} + +static inline u8 hdptx_grf_read(struct rockchip_hdptx_phy *hdptx, u32 reg) +{ + u32 val; + + regmap_read(hdptx->grf, reg, &val); + + return val; +} + +static void hdptx_pre_power_up(struct rockchip_hdptx_phy *hdptx) +{ + u32 val = 0; + + reset_control_assert(hdptx->apb_reset); + udelay(20); + reset_control_deassert(hdptx->apb_reset); + + reset_control_assert(hdptx->lane_reset); + reset_control_assert(hdptx->cmn_reset); + reset_control_assert(hdptx->init_reset); + + val = (HDPTX_I_PLL_EN | HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN) << 16; + hdptx_grf_write(hdptx, GRF_HDPTX_CON0, val); +} + +static int hdptx_post_enable_lane(struct rockchip_hdptx_phy *hdptx) +{ + u32 val = 0; + int i; + + reset_control_deassert(hdptx->lane_reset); + + val = (HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN) << 16 | HDPTX_I_BIAS_EN | + HDPTX_I_BGR_EN; + hdptx_grf_write(hdptx, GRF_HDPTX_CON0, val); + + for (i = 0; i < 50; i++) { + val = hdptx_grf_read(hdptx, GRF_HDPTX_STATUS); + + if (val & HDPTX_O_PHY_RDY && val & HDPTX_O_PLL_LOCK_DONE) + break; + udelay(100); + } + + if (i == 50) { + dev_err(hdptx->dev, "hdptx phy lane can't ready!\n"); + return -EINVAL; + } + + dev_err(hdptx->dev, "hdptx phy lane locked!\n"); + + return 0; +} + +static int hdptx_post_enable_pll(struct rockchip_hdptx_phy *hdptx) +{ + u32 val = 0; + int i; + + val = (HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN) << 16 | HDPTX_I_BIAS_EN | + HDPTX_I_BGR_EN; + hdptx_grf_write(hdptx, GRF_HDPTX_CON0, val); + udelay(10); + reset_control_deassert(hdptx->init_reset); + udelay(10); + val = HDPTX_I_PLL_EN << 16 | HDPTX_I_PLL_EN; + hdptx_grf_write(hdptx, GRF_HDPTX_CON0, val); + udelay(10); + reset_control_deassert(hdptx->cmn_reset); + + for (i = 0; i < 20; i++) { + val = hdptx_grf_read(hdptx, GRF_HDPTX_STATUS); + + if (val & HDPTX_O_PHY_CLK_RDY) + break; + udelay(20); + } + + if (i == 20) { + dev_err(hdptx->dev, "hdptx phy pll can't lock!\n"); + return -EINVAL; + } + + dev_err(hdptx->dev, "hdptx phy pll locked!\n"); + + return 0; +} + +static int hdptx_post_power_up(struct rockchip_hdptx_phy *hdptx) +{ + u32 val = 0; + int i; + + val = (HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN) << 16 | HDPTX_I_BIAS_EN | + HDPTX_I_BGR_EN; + hdptx_grf_write(hdptx, GRF_HDPTX_CON0, val); + udelay(10); + reset_control_deassert(hdptx->init_reset); + udelay(10); + val = HDPTX_I_PLL_EN << 16 | HDPTX_I_PLL_EN; + hdptx_grf_write(hdptx, GRF_HDPTX_CON0, val); + udelay(10); + reset_control_deassert(hdptx->cmn_reset); + + for (i = 0; i < 20; i++) { + val = hdptx_grf_read(hdptx, GRF_HDPTX_STATUS); + + if (val & HDPTX_O_PLL_LOCK_DONE) + break; + udelay(20); + } + + if (i == 20) { + dev_err(hdptx->dev, "hdptx phy can't lock!\n"); + return -EINVAL; + } + + udelay(20); + + reset_control_deassert(hdptx->lane_reset); + + for (i = 0; i < 50; i++) { + val = hdptx_grf_read(hdptx, GRF_HDPTX_STATUS); + + if (val & HDPTX_O_PHY_RDY) + break; + udelay(100); + } + + if (i == 50) { + dev_err(hdptx->dev, "hdptx phy can't ready!\n"); + return -EINVAL; + } + + dev_err(hdptx->dev, "hdptx phy locked!\n"); + + return 0; +} + +static void hdptx_phy_disable(struct rockchip_hdptx_phy *hdptx) +{ + u32 val; + + /* reset phy and apb, or phy locked flag may keep 1 */ + reset_control_assert(hdptx->phy_reset); + udelay(20); + reset_control_deassert(hdptx->phy_reset); + + reset_control_assert(hdptx->apb_reset); + udelay(20); + reset_control_deassert(hdptx->apb_reset); + + hdptx_write(hdptx, LANE_REG0300, 0x82); + hdptx_write(hdptx, SB_REG010F, 0xc1); + hdptx_write(hdptx, SB_REG0110, 0x1); + hdptx_write(hdptx, LANE_REG0301, 0x80); + hdptx_write(hdptx, LANE_REG0401, 0x80); + hdptx_write(hdptx, LANE_REG0501, 0x80); + hdptx_write(hdptx, LANE_REG0601, 0x80); + + reset_control_assert(hdptx->lane_reset); + reset_control_assert(hdptx->cmn_reset); + reset_control_assert(hdptx->init_reset); + + val = (HDPTX_I_PLL_EN | HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN) << 16; + hdptx_grf_write(hdptx, GRF_HDPTX_CON0, val); +} + +static void hdptx_earc_config(struct rockchip_hdptx_phy *hdptx) +{ + hdptx_update_bits(hdptx, SB_REG0113, SB_RX_RCAL_OPT_CODE_MASK, + SB_RX_RCAL_OPT_CODE(1)); + hdptx_write(hdptx, SB_REG011C, 0x04); + hdptx_update_bits(hdptx, SB_REG011B, SB_AFC_TOL_MASK, + SB_AFC_TOL(3)); + hdptx_write(hdptx, SB_REG0109, 0x05); + hdptx_update_bits(hdptx, SB_REG0120, SB_EARC_EN_MASK | SB_EARC_AFC_EN_MASK, + SB_EARC_EN(1) | SB_EARC_AFC_EN(1)); + hdptx_update_bits(hdptx, SB_REG011B, SB_EARC_SIG_DET_BYPASS_MASK, + SB_EARC_SIG_DET_BYPASS(1)); + hdptx_update_bits(hdptx, SB_REG011F, SB_PWM_AFC_CTRL_MASK | SB_RCAL_RSTN_MASK, + SB_PWM_AFC_CTRL(0xc) | SB_RCAL_RSTN(1)); + hdptx_update_bits(hdptx, SB_REG0115, SB_READY_DELAY_TIME_MASK, + SB_READY_DELAY_TIME(2)); + hdptx_update_bits(hdptx, SB_REG0113, SB_RX_RTERM_CTRL_MASK, + SB_RX_RTERM_CTRL(3)); + hdptx_update_bits(hdptx, SB_REG0102, ANA_SB_RXTERM_OFFSP_MASK, + ANA_SB_RXTERM_OFFSP(3)); + hdptx_update_bits(hdptx, SB_REG0103, ANA_SB_RXTERM_OFFSN_MASK, + ANA_SB_RXTERM_OFFSN(3)); + hdptx_write(hdptx, SB_REG011A, 0x03); + hdptx_write(hdptx, SB_REG0118, 0x0a); + hdptx_write(hdptx, SB_REG011E, 0x6a); + hdptx_write(hdptx, SB_REG011D, 0x67); + hdptx_update_bits(hdptx, SB_REG0117, FAST_PULSE_TIME_MASK, + FAST_PULSE_TIME(4)); + hdptx_update_bits(hdptx, SB_REG0114, SB_TG_SB_EN_DELAY_TIME_MASK | + SB_TG_RXTERM_EN_DELAY_TIME_MASK, + SB_TG_SB_EN_DELAY_TIME(2) | + SB_TG_RXTERM_EN_DELAY_TIME(2)); + hdptx_update_bits(hdptx, SB_REG0105, ANA_SB_TX_HLVL_PROG_MASK, + ANA_SB_TX_HLVL_PROG(7)); + hdptx_update_bits(hdptx, SB_REG0106, ANA_SB_TX_LLVL_PROG_MASK, + ANA_SB_TX_LLVL_PROG(7)); + hdptx_update_bits(hdptx, SB_REG010F, ANA_SB_VREG_GAIN_CTRL_MASK, + ANA_SB_VREG_GAIN_CTRL(0)); + hdptx_update_bits(hdptx, SB_REG0110, ANA_SB_VREG_REF_SEL_MASK, + ANA_SB_VREG_REF_SEL(1)); + hdptx_update_bits(hdptx, SB_REG0115, SB_TG_OSC_EN_DELAY_TIME_MASK, + SB_TG_OSC_EN_DELAY_TIME(2)); + hdptx_update_bits(hdptx, SB_REG0116, AFC_RSTN_DELAY_TIME_MASK, + AFC_RSTN_DELAY_TIME(2)); + hdptx_update_bits(hdptx, SB_REG0109, ANA_SB_DMRX_AFC_DIV_RATIO_MASK, + ANA_SB_DMRX_AFC_DIV_RATIO(5)); + hdptx_update_bits(hdptx, SB_REG0103, OVRD_SB_RX_RESCAL_DONE_MASK, + OVRD_SB_RX_RESCAL_DONE(1)); + hdptx_update_bits(hdptx, SB_REG0104, OVRD_SB_EN_MASK, + OVRD_SB_EN(1)); + hdptx_update_bits(hdptx, SB_REG0102, OVRD_SB_RXTERM_EN_MASK, + OVRD_SB_RXTERM_EN(1)); + hdptx_update_bits(hdptx, SB_REG0105, OVRD_SB_EARC_CMDC_EN_MASK, + OVRD_SB_EARC_CMDC_EN(1)); + hdptx_update_bits(hdptx, SB_REG010F, OVRD_SB_VREG_EN_MASK | + OVRD_SB_VREG_LPF_BYPASS_MASK, + OVRD_SB_VREG_EN(1) | OVRD_SB_VREG_LPF_BYPASS(1)); + hdptx_update_bits(hdptx, SB_REG0123, OVRD_SB_READY_MASK, + OVRD_SB_READY(1)); + udelay(1000); + hdptx_update_bits(hdptx, SB_REG0103, SB_RX_RESCAL_DONE_MASK, + SB_RX_RESCAL_DONE(1)); + udelay(50); + hdptx_update_bits(hdptx, SB_REG0104, SB_EN_MASK, SB_EN(1)); + udelay(50); + hdptx_update_bits(hdptx, SB_REG0102, SB_RXTERM_EN_MASK, + SB_RXTERM_EN(1)); + udelay(50); + hdptx_update_bits(hdptx, SB_REG0105, SB_EARC_CMDC_EN_MASK, + SB_EARC_CMDC_EN(1)); + hdptx_update_bits(hdptx, SB_REG010F, SB_VREG_EN_MASK, + SB_VREG_EN(1)); + udelay(50); + hdptx_update_bits(hdptx, SB_REG010F, OVRD_SB_VREG_LPF_BYPASS_MASK, + OVRD_SB_VREG_LPF_BYPASS(1)); + udelay(250); + hdptx_update_bits(hdptx, SB_REG010F, OVRD_SB_VREG_LPF_BYPASS_MASK, + OVRD_SB_VREG_LPF_BYPASS(0)); + udelay(100); + hdptx_update_bits(hdptx, SB_REG0123, SB_READY_MASK, SB_READY(1)); +} + +static bool hdptx_phy_clk_pll_calc(unsigned int data_rate, + struct ropll_config *cfg) +{ + unsigned int fref = 24000; + unsigned int sdc; + unsigned int fout = data_rate / 2; + unsigned int fvco; + u32 mdiv, sdiv, n = 8; + unsigned long k = 0, lc, k_sub, lc_sub; + + for (sdiv = 16; sdiv >= 1; sdiv--) { + if (sdiv % 2 && sdiv != 1) + continue; + + fvco = fout * sdiv; + + if (fvco < 2000000 || fvco > 4000000) + continue; + + mdiv = DIV_ROUND_UP(fvco, fref); + if (mdiv < 20 || mdiv > 255) + continue; + + if (fref * mdiv - fvco) { + for (sdc = 264000; sdc <= 750000; sdc += fref) + if (sdc * n > fref * mdiv) + break; + + if (sdc > 750000) + continue; + + rational_best_approximation(fref * mdiv - fvco, + sdc / 16, + GENMASK(6, 0), + GENMASK(7, 0), + &k, &lc); + + rational_best_approximation(sdc * n - fref * mdiv, + sdc, + GENMASK(6, 0), + GENMASK(7, 0), + &k_sub, &lc_sub); + } + + break; + } + + if (sdiv < 1) + return false; + + if (cfg) { + cfg->pms_mdiv = mdiv; + cfg->pms_mdiv_afc = mdiv; + cfg->pms_pdiv = 1; + cfg->pms_refdiv = 1; + cfg->pms_sdiv = sdiv - 1; + + cfg->sdm_en = k > 0 ? 1 : 0; + if (cfg->sdm_en) { + cfg->sdm_deno = lc; + cfg->sdm_num_sign = 1; + cfg->sdm_num = k; + cfg->sdc_n = n - 3; + cfg->sdc_num = k_sub; + cfg->sdc_deno = lc_sub; + } + } + + return true; +} + +static int hdptx_ropll_cmn_config(struct rockchip_hdptx_phy *hdptx, unsigned long bit_rate) +{ + int bus_width = phy_get_bus_width(hdptx->phy); + u8 color_depth = (bus_width & COLOR_DEPTH_MASK) ? 1 : 0; + struct ropll_config *cfg = ropll_tmds_cfg; + struct ropll_config rc = {0}; + + dev_info(hdptx->dev, "%s bus_width:%x rate:%lu\n", __func__, bus_width, bit_rate); + hdptx->rate = bit_rate * 100; + + if (color_depth) + bit_rate = bit_rate * 10 / 8; + + for (; cfg->bit_rate != ~0; cfg++) + if (bit_rate == cfg->bit_rate) + break; + + if (cfg->bit_rate == ~0) { + if (hdptx_phy_clk_pll_calc(bit_rate, &rc)) { + cfg = &rc; + } else { + dev_err(hdptx->dev, "%s can't find pll cfg\n", __func__); + return -EINVAL; + } + } + + dev_dbg(hdptx->dev, "mdiv=%u, sdiv=%u\n", + cfg->pms_mdiv, cfg->pms_sdiv + 1); + dev_dbg(hdptx->dev, "sdm_en=%u, k_sign=%u, k=%u, lc=%u", + cfg->sdm_en, cfg->sdm_num_sign, cfg->sdm_num, cfg->sdm_deno); + dev_dbg(hdptx->dev, "n=%u, k_sub=%u, lc_sub=%u\n", + cfg->sdc_n + 3, cfg->sdc_num, cfg->sdc_deno); + + hdptx_pre_power_up(hdptx); + + reset_control_assert(hdptx->ropll_reset); + udelay(20); + reset_control_deassert(hdptx->ropll_reset); + + hdptx_write(hdptx, CMN_REG0008, 0x00); + hdptx_write(hdptx, CMN_REG0009, 0x0c); + hdptx_write(hdptx, CMN_REG000A, 0x83); + hdptx_write(hdptx, CMN_REG000B, 0x06); + hdptx_write(hdptx, CMN_REG000C, 0x20); + hdptx_write(hdptx, CMN_REG000D, 0xb8); + hdptx_write(hdptx, CMN_REG000E, 0x0f); + hdptx_write(hdptx, CMN_REG000F, 0x0f); + hdptx_write(hdptx, CMN_REG0010, 0x04); + hdptx_write(hdptx, CMN_REG0011, 0x01); + hdptx_write(hdptx, CMN_REG0012, 0x26); + hdptx_write(hdptx, CMN_REG0013, 0x22); + hdptx_write(hdptx, CMN_REG0014, 0x24); + hdptx_write(hdptx, CMN_REG0015, 0x77); + hdptx_write(hdptx, CMN_REG0016, 0x08); + hdptx_write(hdptx, CMN_REG0017, 0x20); + hdptx_write(hdptx, CMN_REG0018, 0x04); + hdptx_write(hdptx, CMN_REG0019, 0x48); + hdptx_write(hdptx, CMN_REG001A, 0x01); + hdptx_write(hdptx, CMN_REG001B, 0x00); + hdptx_write(hdptx, CMN_REG001C, 0x01); + hdptx_write(hdptx, CMN_REG001D, 0x64); + hdptx_write(hdptx, CMN_REG001E, 0x14); + hdptx_write(hdptx, CMN_REG001F, 0x00); + hdptx_write(hdptx, CMN_REG0020, 0x00); + hdptx_write(hdptx, CMN_REG0021, 0x00); + hdptx_write(hdptx, CMN_REG0022, 0x11); + hdptx_write(hdptx, CMN_REG0023, 0x00); + hdptx_write(hdptx, CMN_REG0024, 0x00); + hdptx_write(hdptx, CMN_REG0025, 0x53); + hdptx_write(hdptx, CMN_REG0026, 0x00); + hdptx_write(hdptx, CMN_REG0027, 0x00); + hdptx_write(hdptx, CMN_REG0028, 0x01); + hdptx_write(hdptx, CMN_REG0029, 0x01); + hdptx_write(hdptx, CMN_REG002A, 0x00); + hdptx_write(hdptx, CMN_REG002B, 0x00); + hdptx_write(hdptx, CMN_REG002C, 0x00); + hdptx_write(hdptx, CMN_REG002D, 0x00); + hdptx_write(hdptx, CMN_REG002E, 0x04); + hdptx_write(hdptx, CMN_REG002F, 0x00); + hdptx_write(hdptx, CMN_REG0030, 0x20); + hdptx_write(hdptx, CMN_REG0031, 0x30); + hdptx_write(hdptx, CMN_REG0032, 0x0b); + hdptx_write(hdptx, CMN_REG0033, 0x23); + hdptx_write(hdptx, CMN_REG0034, 0x00); + hdptx_write(hdptx, CMN_REG0035, 0x00); + hdptx_write(hdptx, CMN_REG0038, 0x00); + hdptx_write(hdptx, CMN_REG0039, 0x00); + hdptx_write(hdptx, CMN_REG003A, 0x00); + hdptx_write(hdptx, CMN_REG003B, 0x00); + hdptx_write(hdptx, CMN_REG003C, 0x80); + hdptx_write(hdptx, CMN_REG003D, 0x40); + hdptx_write(hdptx, CMN_REG003E, 0x0c); + hdptx_write(hdptx, CMN_REG003F, 0x83); + hdptx_write(hdptx, CMN_REG0040, 0x06); + hdptx_write(hdptx, CMN_REG0041, 0x20); + hdptx_write(hdptx, CMN_REG0042, 0x78); + hdptx_write(hdptx, CMN_REG0043, 0x00); + hdptx_write(hdptx, CMN_REG0044, 0x46); + hdptx_write(hdptx, CMN_REG0045, 0x24); + hdptx_write(hdptx, CMN_REG0046, 0xff); + hdptx_write(hdptx, CMN_REG0047, 0x00); + hdptx_write(hdptx, CMN_REG0048, 0x44); + hdptx_write(hdptx, CMN_REG0049, 0xfa); + hdptx_write(hdptx, CMN_REG004A, 0x08); + hdptx_write(hdptx, CMN_REG004B, 0x00); + hdptx_write(hdptx, CMN_REG004C, 0x01); + hdptx_write(hdptx, CMN_REG004D, 0x64); + hdptx_write(hdptx, CMN_REG004E, 0x34); + hdptx_write(hdptx, CMN_REG004F, 0x00); + hdptx_write(hdptx, CMN_REG0050, 0x00); + + hdptx_write(hdptx, CMN_REG0051, cfg->pms_mdiv); + hdptx_write(hdptx, CMN_REG0055, cfg->pms_mdiv_afc); + + hdptx_write(hdptx, CMN_REG0059, (cfg->pms_pdiv << 4) | cfg->pms_refdiv); + + hdptx_write(hdptx, CMN_REG005A, (cfg->pms_sdiv << 4)); + + hdptx_write(hdptx, CMN_REG005C, 0x25); + hdptx_write(hdptx, CMN_REG005D, 0x0c); + hdptx_write(hdptx, CMN_REG005E, 0x4f); + hdptx_update_bits(hdptx, CMN_REG005E, ROPLL_SDM_EN_MASK, + ROPLL_SDM_EN(cfg->sdm_en)); + if (!cfg->sdm_en) + hdptx_update_bits(hdptx, CMN_REG005E, 0xf, 0); + + hdptx_write(hdptx, CMN_REG005F, 0x01); + + hdptx_update_bits(hdptx, CMN_REG0064, ROPLL_SDM_NUM_SIGN_RBR_MASK, + ROPLL_SDM_NUM_SIGN_RBR(cfg->sdm_num_sign)); + hdptx_write(hdptx, CMN_REG0065, cfg->sdm_num); + hdptx_write(hdptx, CMN_REG0060, cfg->sdm_deno); + + hdptx_update_bits(hdptx, CMN_REG0069, ROPLL_SDC_N_RBR_MASK, + ROPLL_SDC_N_RBR(cfg->sdc_n)); + + hdptx_write(hdptx, CMN_REG006C, cfg->sdc_num); + hdptx_write(hdptx, CMN_REG0070, cfg->sdc_deno); + + hdptx_write(hdptx, CMN_REG006B, 0x04); + + hdptx_write(hdptx, CMN_REG0073, 0x30); + hdptx_write(hdptx, CMN_REG0074, 0x04); + hdptx_write(hdptx, CMN_REG0075, 0x20); + hdptx_write(hdptx, CMN_REG0076, 0x30); + hdptx_write(hdptx, CMN_REG0077, 0x08); + hdptx_write(hdptx, CMN_REG0078, 0x0c); + hdptx_write(hdptx, CMN_REG0079, 0x00); + hdptx_write(hdptx, CMN_REG007B, 0x00); + hdptx_write(hdptx, CMN_REG007C, 0x00); + hdptx_write(hdptx, CMN_REG007D, 0x00); + hdptx_write(hdptx, CMN_REG007E, 0x00); + hdptx_write(hdptx, CMN_REG007F, 0x00); + hdptx_write(hdptx, CMN_REG0080, 0x00); + hdptx_write(hdptx, CMN_REG0081, 0x01); + hdptx_write(hdptx, CMN_REG0082, 0x04); + hdptx_write(hdptx, CMN_REG0083, 0x24); + hdptx_write(hdptx, CMN_REG0084, 0x20); + hdptx_write(hdptx, CMN_REG0085, 0x03); + + hdptx_update_bits(hdptx, CMN_REG0086, PLL_PCG_POSTDIV_SEL_MASK, + PLL_PCG_POSTDIV_SEL(cfg->pms_sdiv)); + + hdptx_update_bits(hdptx, CMN_REG0086, PLL_PCG_CLK_SEL_MASK, + PLL_PCG_CLK_SEL(color_depth)); + + hdptx_update_bits(hdptx, CMN_REG0086, PLL_PCG_CLK_EN, PLL_PCG_CLK_EN); + + hdptx_write(hdptx, CMN_REG0087, 0x04); + hdptx_write(hdptx, CMN_REG0089, 0x00); + hdptx_write(hdptx, CMN_REG008A, 0x55); + hdptx_write(hdptx, CMN_REG008B, 0x25); + hdptx_write(hdptx, CMN_REG008C, 0x2c); + hdptx_write(hdptx, CMN_REG008D, 0x22); + hdptx_write(hdptx, CMN_REG008E, 0x14); + hdptx_write(hdptx, CMN_REG008F, 0x20); + hdptx_write(hdptx, CMN_REG0090, 0x00); + hdptx_write(hdptx, CMN_REG0091, 0x00); + hdptx_write(hdptx, CMN_REG0092, 0x00); + hdptx_write(hdptx, CMN_REG0093, 0x00); + hdptx_write(hdptx, CMN_REG0095, 0x00); + hdptx_write(hdptx, CMN_REG0097, 0x02); + hdptx_write(hdptx, CMN_REG0099, 0x04); + hdptx_write(hdptx, CMN_REG009A, 0x11); + hdptx_write(hdptx, CMN_REG009B, 0x00); + + return hdptx_post_enable_pll(hdptx); +} + +static int hdptx_ropll_tmds_mode_config(struct rockchip_hdptx_phy *hdptx, u32 rate) +{ + u32 bit_rate = rate & DATA_RATE_MASK; + + if (!(hdptx_grf_read(hdptx, GRF_HDPTX_STATUS) & HDPTX_O_PLL_LOCK_DONE)) { + int ret; + + ret = hdptx_ropll_cmn_config(hdptx, bit_rate); + if (ret) + return ret; + } + + hdptx_write(hdptx, SB_REG0114, 0x00); + hdptx_write(hdptx, SB_REG0115, 0x00); + hdptx_write(hdptx, SB_REG0116, 0x00); + hdptx_write(hdptx, SB_REG0117, 0x00); + hdptx_write(hdptx, LNTOP_REG0200, 0x06); + + if (bit_rate >= 3400000) { + /* For 1/40 bitrate clk */ + hdptx_write(hdptx, LNTOP_REG0201, 0x00); + hdptx_write(hdptx, LNTOP_REG0202, 0x00); + hdptx_write(hdptx, LNTOP_REG0203, 0x0f); + hdptx_write(hdptx, LNTOP_REG0204, 0xff); + hdptx_write(hdptx, LNTOP_REG0205, 0xff); + } else { + /* For 1/10 bitrate clk */ + hdptx_write(hdptx, LNTOP_REG0201, 0x07); + hdptx_write(hdptx, LNTOP_REG0202, 0xc1); + hdptx_write(hdptx, LNTOP_REG0203, 0xf0); + hdptx_write(hdptx, LNTOP_REG0204, 0x7c); + hdptx_write(hdptx, LNTOP_REG0205, 0x1f); + } + + hdptx_write(hdptx, LNTOP_REG0206, 0x07); + hdptx_write(hdptx, LNTOP_REG0207, 0x0f); + hdptx_write(hdptx, LANE_REG0303, 0x0c); + hdptx_write(hdptx, LANE_REG0307, 0x20); + hdptx_write(hdptx, LANE_REG030A, 0x17); + hdptx_write(hdptx, LANE_REG030B, 0x77); + hdptx_write(hdptx, LANE_REG030C, 0x77); + hdptx_write(hdptx, LANE_REG030D, 0x77); + hdptx_write(hdptx, LANE_REG030E, 0x38); + hdptx_write(hdptx, LANE_REG0310, 0x03); + hdptx_write(hdptx, LANE_REG0311, 0x0f); + hdptx_write(hdptx, LANE_REG0312, 0x00); + hdptx_write(hdptx, LANE_REG0316, 0x02); + hdptx_write(hdptx, LANE_REG031B, 0x01); + hdptx_write(hdptx, LANE_REG031E, 0x00); + hdptx_write(hdptx, LANE_REG031F, 0x15); + hdptx_write(hdptx, LANE_REG0320, 0xa0); + hdptx_write(hdptx, LANE_REG0403, 0x0c); + hdptx_write(hdptx, LANE_REG0407, 0x20); + hdptx_write(hdptx, LANE_REG040A, 0x17); + hdptx_write(hdptx, LANE_REG040B, 0x77); + hdptx_write(hdptx, LANE_REG040C, 0x77); + hdptx_write(hdptx, LANE_REG040D, 0x77); + hdptx_write(hdptx, LANE_REG040E, 0x38); + hdptx_write(hdptx, LANE_REG0410, 0x03); + hdptx_write(hdptx, LANE_REG0411, 0x0f); + hdptx_write(hdptx, LANE_REG0412, 0x00); + hdptx_write(hdptx, LANE_REG0416, 0x02); + hdptx_write(hdptx, LANE_REG041B, 0x01); + hdptx_write(hdptx, LANE_REG041E, 0x00); + hdptx_write(hdptx, LANE_REG041F, 0x15); + hdptx_write(hdptx, LANE_REG0420, 0xa0); + hdptx_write(hdptx, LANE_REG0503, 0x0c); + hdptx_write(hdptx, LANE_REG0507, 0x20); + hdptx_write(hdptx, LANE_REG050A, 0x17); + hdptx_write(hdptx, LANE_REG050B, 0x77); + hdptx_write(hdptx, LANE_REG050C, 0x77); + hdptx_write(hdptx, LANE_REG050D, 0x77); + hdptx_write(hdptx, LANE_REG050E, 0x38); + hdptx_write(hdptx, LANE_REG0510, 0x03); + hdptx_write(hdptx, LANE_REG0511, 0x0f); + hdptx_write(hdptx, LANE_REG0512, 0x00); + hdptx_write(hdptx, LANE_REG0516, 0x02); + hdptx_write(hdptx, LANE_REG051B, 0x01); + hdptx_write(hdptx, LANE_REG051E, 0x00); + hdptx_write(hdptx, LANE_REG051F, 0x15); + hdptx_write(hdptx, LANE_REG0520, 0xa0); + hdptx_write(hdptx, LANE_REG0603, 0x0c); + hdptx_write(hdptx, LANE_REG0607, 0x20); + hdptx_write(hdptx, LANE_REG060A, 0x17); + hdptx_write(hdptx, LANE_REG060B, 0x77); + hdptx_write(hdptx, LANE_REG060C, 0x77); + hdptx_write(hdptx, LANE_REG060D, 0x77); + hdptx_write(hdptx, LANE_REG060E, 0x38); + hdptx_write(hdptx, LANE_REG0610, 0x03); + hdptx_write(hdptx, LANE_REG0611, 0x0f); + hdptx_write(hdptx, LANE_REG0612, 0x00); + hdptx_write(hdptx, LANE_REG0616, 0x02); + hdptx_write(hdptx, LANE_REG061B, 0x01); + hdptx_write(hdptx, LANE_REG061E, 0x08); + hdptx_write(hdptx, LANE_REG061F, 0x15); + hdptx_write(hdptx, LANE_REG0620, 0xa0); + + hdptx_write(hdptx, LANE_REG0303, 0x2f); + hdptx_write(hdptx, LANE_REG0403, 0x2f); + hdptx_write(hdptx, LANE_REG0503, 0x2f); + hdptx_write(hdptx, LANE_REG0603, 0x2f); + hdptx_write(hdptx, LANE_REG0305, 0x03); + hdptx_write(hdptx, LANE_REG0405, 0x03); + hdptx_write(hdptx, LANE_REG0505, 0x03); + hdptx_write(hdptx, LANE_REG0605, 0x03); + hdptx_write(hdptx, LANE_REG0306, 0x1c); + hdptx_write(hdptx, LANE_REG0406, 0x1c); + hdptx_write(hdptx, LANE_REG0506, 0x1c); + hdptx_write(hdptx, LANE_REG0606, 0x1c); + + if (hdptx->earc_en) + hdptx_earc_config(hdptx); + + return hdptx_post_enable_lane(hdptx); +} + +static int hdptx_ropll_frl_mode_config(struct rockchip_hdptx_phy *hdptx, u32 rate) +{ + u32 bit_rate = rate & DATA_RATE_MASK; + u8 color_depth = (rate & COLOR_DEPTH_MASK) ? 1 : 0; + struct ropll_config *cfg = ropll_frl_cfg; + + for (; cfg->bit_rate != ~0; cfg++) + if (bit_rate == cfg->bit_rate) + break; + + if (cfg->bit_rate == ~0) { + dev_err(hdptx->dev, "%s can't find pll cfg\n", __func__); + return -EINVAL; + } + + hdptx_pre_power_up(hdptx); + + reset_control_assert(hdptx->ropll_reset); + usleep_range(10, 20); + reset_control_deassert(hdptx->ropll_reset); + + hdptx_write(hdptx, CMN_REG0008, 0x00); + hdptx_write(hdptx, CMN_REG0009, 0x0c); + hdptx_write(hdptx, CMN_REG000A, 0x83); + hdptx_write(hdptx, CMN_REG000B, 0x06); + hdptx_write(hdptx, CMN_REG000C, 0x20); + hdptx_write(hdptx, CMN_REG000D, 0xb8); + hdptx_write(hdptx, CMN_REG000E, 0x0f); + hdptx_write(hdptx, CMN_REG000F, 0x0f); + hdptx_write(hdptx, CMN_REG0010, 0x04); + hdptx_write(hdptx, CMN_REG0011, 0x00); + hdptx_write(hdptx, CMN_REG0012, 0x26); + hdptx_write(hdptx, CMN_REG0013, 0x22); + hdptx_write(hdptx, CMN_REG0014, 0x24); + hdptx_write(hdptx, CMN_REG0015, 0x77); + hdptx_write(hdptx, CMN_REG0016, 0x08); + hdptx_write(hdptx, CMN_REG0017, 0x00); + hdptx_write(hdptx, CMN_REG0018, 0x04); + hdptx_write(hdptx, CMN_REG0019, 0x48); + hdptx_write(hdptx, CMN_REG001A, 0x01); + hdptx_write(hdptx, CMN_REG001B, 0x00); + hdptx_write(hdptx, CMN_REG001C, 0x01); + hdptx_write(hdptx, CMN_REG001D, 0x64); + hdptx_write(hdptx, CMN_REG001E, 0x14); + hdptx_write(hdptx, CMN_REG001F, 0x00); + hdptx_write(hdptx, CMN_REG0020, 0x00); + hdptx_write(hdptx, CMN_REG0021, 0x00); + hdptx_write(hdptx, CMN_REG0022, 0x11); + hdptx_write(hdptx, CMN_REG0023, 0x00); + hdptx_write(hdptx, CMN_REG0025, 0x00); + hdptx_write(hdptx, CMN_REG0026, 0x53); + hdptx_write(hdptx, CMN_REG0027, 0x00); + hdptx_write(hdptx, CMN_REG0028, 0x00); + hdptx_write(hdptx, CMN_REG0029, 0x01); + hdptx_write(hdptx, CMN_REG002A, 0x01); + hdptx_write(hdptx, CMN_REG002B, 0x00); + hdptx_write(hdptx, CMN_REG002C, 0x00); + hdptx_write(hdptx, CMN_REG002D, 0x00); + hdptx_write(hdptx, CMN_REG002E, 0x00); + hdptx_write(hdptx, CMN_REG002F, 0x04); + hdptx_write(hdptx, CMN_REG0030, 0x00); + hdptx_write(hdptx, CMN_REG0031, 0x20); + hdptx_write(hdptx, CMN_REG0032, 0x30); + hdptx_write(hdptx, CMN_REG0033, 0x0b); + hdptx_write(hdptx, CMN_REG0034, 0x23); + hdptx_write(hdptx, CMN_REG0035, 0x00); + hdptx_write(hdptx, CMN_REG0038, 0x00); + hdptx_write(hdptx, CMN_REG0039, 0x00); + hdptx_write(hdptx, CMN_REG003A, 0x00); + hdptx_write(hdptx, CMN_REG003B, 0x00); + hdptx_write(hdptx, CMN_REG003C, 0x80); + hdptx_write(hdptx, CMN_REG003D, 0x40); + hdptx_write(hdptx, CMN_REG003E, 0x0c); + hdptx_write(hdptx, CMN_REG003F, 0x83); + hdptx_write(hdptx, CMN_REG0040, 0x06); + hdptx_write(hdptx, CMN_REG0041, 0x20); + hdptx_write(hdptx, CMN_REG0042, 0xb8); + hdptx_write(hdptx, CMN_REG0043, 0x00); + hdptx_write(hdptx, CMN_REG0044, 0x46); + hdptx_write(hdptx, CMN_REG0045, 0x24); + hdptx_write(hdptx, CMN_REG0046, 0xff); + hdptx_write(hdptx, CMN_REG0047, 0x00); + hdptx_write(hdptx, CMN_REG0048, 0x44); + hdptx_write(hdptx, CMN_REG0049, 0xfa); + hdptx_write(hdptx, CMN_REG004A, 0x08); + hdptx_write(hdptx, CMN_REG004B, 0x00); + hdptx_write(hdptx, CMN_REG004C, 0x01); + hdptx_write(hdptx, CMN_REG004D, 0x64); + hdptx_write(hdptx, CMN_REG004E, 0x14); + hdptx_write(hdptx, CMN_REG004F, 0x00); + hdptx_write(hdptx, CMN_REG0050, 0x00); + hdptx_write(hdptx, CMN_REG0051, cfg->pms_mdiv); + hdptx_write(hdptx, CMN_REG0055, cfg->pms_mdiv_afc); + hdptx_write(hdptx, CMN_REG0059, (cfg->pms_pdiv << 4) | cfg->pms_refdiv); + hdptx_write(hdptx, CMN_REG005A, (cfg->pms_sdiv << 4)); + hdptx_write(hdptx, CMN_REG005C, 0x25); + hdptx_write(hdptx, CMN_REG005D, 0x0c); + hdptx_update_bits(hdptx, CMN_REG005E, ROPLL_SDM_EN_MASK, + ROPLL_SDM_EN(cfg->sdm_en)); + if (!cfg->sdm_en) + hdptx_update_bits(hdptx, CMN_REG005E, 0xf, 0); + hdptx_write(hdptx, CMN_REG005F, 0x01); + hdptx_update_bits(hdptx, CMN_REG0064, ROPLL_SDM_NUM_SIGN_RBR_MASK, + ROPLL_SDM_NUM_SIGN_RBR(cfg->sdm_num_sign)); + hdptx_write(hdptx, CMN_REG0065, cfg->sdm_num); + hdptx_write(hdptx, CMN_REG0060, cfg->sdm_deno); + hdptx_update_bits(hdptx, CMN_REG0069, ROPLL_SDC_N_RBR_MASK, + ROPLL_SDC_N_RBR(cfg->sdc_n)); + hdptx_write(hdptx, CMN_REG006C, cfg->sdc_num); + hdptx_write(hdptx, CMN_REG0070, cfg->sdc_deno); + hdptx_write(hdptx, CMN_REG006B, 0x04); + hdptx_write(hdptx, CMN_REG0073, 0x30); + hdptx_write(hdptx, CMN_REG0074, 0x00); + hdptx_write(hdptx, CMN_REG0075, 0x20); + hdptx_write(hdptx, CMN_REG0076, 0x30); + hdptx_write(hdptx, CMN_REG0077, 0x08); + hdptx_write(hdptx, CMN_REG0078, 0x0c); + hdptx_write(hdptx, CMN_REG0079, 0x00); + hdptx_write(hdptx, CMN_REG007B, 0x00); + hdptx_write(hdptx, CMN_REG007C, 0x00); + hdptx_write(hdptx, CMN_REG007D, 0x00); + hdptx_write(hdptx, CMN_REG007E, 0x00); + hdptx_write(hdptx, CMN_REG007F, 0x00); + hdptx_write(hdptx, CMN_REG0080, 0x00); + hdptx_write(hdptx, CMN_REG0081, 0x09); + hdptx_write(hdptx, CMN_REG0082, 0x04); + hdptx_write(hdptx, CMN_REG0083, 0x24); + hdptx_write(hdptx, CMN_REG0084, 0x20); + hdptx_write(hdptx, CMN_REG0085, 0x03); + hdptx_write(hdptx, CMN_REG0086, 0x01); + hdptx_update_bits(hdptx, CMN_REG0086, PLL_PCG_POSTDIV_SEL_MASK, + PLL_PCG_POSTDIV_SEL(cfg->pms_sdiv)); + hdptx_update_bits(hdptx, CMN_REG0086, PLL_PCG_CLK_SEL_MASK, + PLL_PCG_CLK_SEL(color_depth)); + hdptx_write(hdptx, CMN_REG0087, 0x0c); + hdptx_write(hdptx, CMN_REG0089, 0x00); + hdptx_write(hdptx, CMN_REG008A, 0x55); + hdptx_write(hdptx, CMN_REG008B, 0x25); + hdptx_write(hdptx, CMN_REG008C, 0x2c); + hdptx_write(hdptx, CMN_REG008D, 0x22); + hdptx_write(hdptx, CMN_REG008E, 0x14); + hdptx_write(hdptx, CMN_REG008F, 0x20); + hdptx_write(hdptx, CMN_REG0090, 0x00); + hdptx_write(hdptx, CMN_REG0091, 0x00); + hdptx_write(hdptx, CMN_REG0092, 0x00); + hdptx_write(hdptx, CMN_REG0093, 0x00); + hdptx_write(hdptx, CMN_REG0094, 0x00); + hdptx_write(hdptx, CMN_REG0097, 0x02); + hdptx_write(hdptx, CMN_REG0099, 0x04); + hdptx_write(hdptx, CMN_REG009A, 0x11); + hdptx_write(hdptx, CMN_REG009B, 0x10); + hdptx_write(hdptx, SB_REG0114, 0x00); + hdptx_write(hdptx, SB_REG0115, 0x00); + hdptx_write(hdptx, SB_REG0116, 0x00); + hdptx_write(hdptx, SB_REG0117, 0x00); + hdptx_write(hdptx, LNTOP_REG0200, 0x04); + hdptx_write(hdptx, LNTOP_REG0201, 0x00); + hdptx_write(hdptx, LNTOP_REG0202, 0x00); + hdptx_write(hdptx, LNTOP_REG0203, 0xf0); + hdptx_write(hdptx, LNTOP_REG0204, 0xff); + hdptx_write(hdptx, LNTOP_REG0205, 0xff); + hdptx_write(hdptx, LNTOP_REG0206, 0x05); + hdptx_write(hdptx, LNTOP_REG0207, 0x0f); + hdptx_write(hdptx, LANE_REG0303, 0x0c); + hdptx_write(hdptx, LANE_REG0307, 0x20); + hdptx_write(hdptx, LANE_REG030A, 0x17); + hdptx_write(hdptx, LANE_REG030B, 0x77); + hdptx_write(hdptx, LANE_REG030C, 0x77); + hdptx_write(hdptx, LANE_REG030D, 0x77); + hdptx_write(hdptx, LANE_REG030E, 0x38); + hdptx_write(hdptx, LANE_REG0310, 0x03); + hdptx_write(hdptx, LANE_REG0311, 0x0f); + hdptx_write(hdptx, LANE_REG0312, 0x3c); + hdptx_write(hdptx, LANE_REG0316, 0x02); + hdptx_write(hdptx, LANE_REG031B, 0x01); + hdptx_write(hdptx, LANE_REG031F, 0x15); + hdptx_write(hdptx, LANE_REG0320, 0xa0); + hdptx_write(hdptx, LANE_REG0403, 0x0c); + hdptx_write(hdptx, LANE_REG0407, 0x20); + hdptx_write(hdptx, LANE_REG040A, 0x17); + hdptx_write(hdptx, LANE_REG040B, 0x77); + hdptx_write(hdptx, LANE_REG040C, 0x77); + hdptx_write(hdptx, LANE_REG040D, 0x77); + hdptx_write(hdptx, LANE_REG040E, 0x38); + hdptx_write(hdptx, LANE_REG0410, 0x03); + hdptx_write(hdptx, LANE_REG0411, 0x0f); + hdptx_write(hdptx, LANE_REG0412, 0x3c); + hdptx_write(hdptx, LANE_REG0416, 0x02); + hdptx_write(hdptx, LANE_REG041B, 0x01); + hdptx_write(hdptx, LANE_REG041F, 0x15); + hdptx_write(hdptx, LANE_REG0420, 0xa0); + hdptx_write(hdptx, LANE_REG0503, 0x0c); + hdptx_write(hdptx, LANE_REG0507, 0x20); + hdptx_write(hdptx, LANE_REG050A, 0x17); + hdptx_write(hdptx, LANE_REG050B, 0x77); + hdptx_write(hdptx, LANE_REG050C, 0x77); + hdptx_write(hdptx, LANE_REG050D, 0x77); + hdptx_write(hdptx, LANE_REG050E, 0x38); + hdptx_write(hdptx, LANE_REG0510, 0x03); + hdptx_write(hdptx, LANE_REG0511, 0x0f); + hdptx_write(hdptx, LANE_REG0512, 0x3c); + hdptx_write(hdptx, LANE_REG0516, 0x02); + hdptx_write(hdptx, LANE_REG051B, 0x01); + hdptx_write(hdptx, LANE_REG051F, 0x15); + hdptx_write(hdptx, LANE_REG0520, 0xa0); + hdptx_write(hdptx, LANE_REG0603, 0x0c); + hdptx_write(hdptx, LANE_REG0607, 0x20); + hdptx_write(hdptx, LANE_REG060A, 0x17); + hdptx_write(hdptx, LANE_REG060B, 0x77); + hdptx_write(hdptx, LANE_REG060C, 0x77); + hdptx_write(hdptx, LANE_REG060D, 0x77); + hdptx_write(hdptx, LANE_REG060E, 0x38); + hdptx_write(hdptx, LANE_REG0610, 0x03); + hdptx_write(hdptx, LANE_REG0611, 0x0f); + hdptx_write(hdptx, LANE_REG0612, 0x3c); + hdptx_write(hdptx, LANE_REG0616, 0x02); + hdptx_write(hdptx, LANE_REG061B, 0x01); + hdptx_write(hdptx, LANE_REG061F, 0x15); + hdptx_write(hdptx, LANE_REG0620, 0xa0); + + if (hdptx->earc_en) + hdptx_earc_config(hdptx); + + return hdptx_post_power_up(hdptx); +} + +static int hdptx_lcpll_frl_mode_config(struct rockchip_hdptx_phy *hdptx, u32 rate) +{ + u32 bit_rate = rate & DATA_RATE_MASK; + u8 color_depth = (rate & COLOR_DEPTH_MASK) ? 1 : 0; + struct lcpll_config *cfg = lcpll_cfg; + + for (; cfg->bit_rate != ~0; cfg++) + if (bit_rate == cfg->bit_rate) + break; + + if (cfg->bit_rate == ~0) + return -EINVAL; + + hdptx_pre_power_up(hdptx); + + hdptx_update_bits(hdptx, CMN_REG0008, LCPLL_EN_MASK | + LCPLL_LCVCO_MODE_EN_MASK, LCPLL_EN(1) | + LCPLL_LCVCO_MODE_EN(cfg->lcvco_mode_en)); + hdptx_write(hdptx, CMN_REG0009, 0x0c); + hdptx_write(hdptx, CMN_REG000A, 0x83); + hdptx_write(hdptx, CMN_REG000B, 0x06); + hdptx_write(hdptx, CMN_REG000C, 0x20); + hdptx_write(hdptx, CMN_REG000D, 0xb8); + hdptx_write(hdptx, CMN_REG000E, 0x0f); + hdptx_write(hdptx, CMN_REG000F, 0x0f); + hdptx_write(hdptx, CMN_REG0010, 0x04); + hdptx_write(hdptx, CMN_REG0011, 0x00); + hdptx_write(hdptx, CMN_REG0012, 0x26); + hdptx_write(hdptx, CMN_REG0013, 0x22); + hdptx_write(hdptx, CMN_REG0014, 0x24); + hdptx_write(hdptx, CMN_REG0015, 0x77); + hdptx_write(hdptx, CMN_REG0016, 0x08); + hdptx_write(hdptx, CMN_REG0017, 0x00); + hdptx_write(hdptx, CMN_REG0018, 0x04); + hdptx_write(hdptx, CMN_REG0019, 0x48); + hdptx_write(hdptx, CMN_REG001A, 0x01); + hdptx_write(hdptx, CMN_REG001B, 0x00); + hdptx_write(hdptx, CMN_REG001C, 0x01); + hdptx_write(hdptx, CMN_REG001D, 0x64); + hdptx_update_bits(hdptx, CMN_REG001E, LCPLL_PI_EN_MASK | + LCPLL_100M_CLK_EN_MASK, + LCPLL_PI_EN(cfg->pi_en) | + LCPLL_100M_CLK_EN(cfg->clk_en_100m)); + hdptx_write(hdptx, CMN_REG001F, 0x00); + hdptx_write(hdptx, CMN_REG0020, cfg->pms_mdiv); + hdptx_write(hdptx, CMN_REG0021, cfg->pms_mdiv_afc); + hdptx_write(hdptx, CMN_REG0022, (cfg->pms_pdiv << 4) | cfg->pms_refdiv); + hdptx_write(hdptx, CMN_REG0023, (cfg->pms_sdiv << 4) | cfg->pms_sdiv); + hdptx_write(hdptx, CMN_REG0025, 0x10); + hdptx_write(hdptx, CMN_REG0026, 0x53); + hdptx_write(hdptx, CMN_REG0027, 0x01); + hdptx_write(hdptx, CMN_REG0028, 0x0d); + hdptx_write(hdptx, CMN_REG0029, 0x01); + hdptx_write(hdptx, CMN_REG002A, cfg->sdm_deno); + hdptx_write(hdptx, CMN_REG002B, cfg->sdm_num_sign); + hdptx_write(hdptx, CMN_REG002C, cfg->sdm_num); + hdptx_update_bits(hdptx, CMN_REG002D, LCPLL_SDC_N_MASK, + LCPLL_SDC_N(cfg->sdc_n)); + hdptx_write(hdptx, CMN_REG002E, 0x02); + hdptx_write(hdptx, CMN_REG002F, 0x0d); + hdptx_write(hdptx, CMN_REG0030, 0x00); + hdptx_write(hdptx, CMN_REG0031, 0x20); + hdptx_write(hdptx, CMN_REG0032, 0x30); + hdptx_write(hdptx, CMN_REG0033, 0x0b); + hdptx_write(hdptx, CMN_REG0034, 0x23); + hdptx_write(hdptx, CMN_REG0035, 0x00); + hdptx_write(hdptx, CMN_REG0038, 0x00); + hdptx_write(hdptx, CMN_REG0039, 0x00); + hdptx_write(hdptx, CMN_REG003A, 0x00); + hdptx_write(hdptx, CMN_REG003B, 0x00); + hdptx_write(hdptx, CMN_REG003C, 0x80); + hdptx_write(hdptx, CMN_REG003D, 0x00); + hdptx_write(hdptx, CMN_REG003E, 0x0c); + hdptx_write(hdptx, CMN_REG003F, 0x83); + hdptx_write(hdptx, CMN_REG0040, 0x06); + hdptx_write(hdptx, CMN_REG0041, 0x20); + hdptx_write(hdptx, CMN_REG0042, 0xb8); + hdptx_write(hdptx, CMN_REG0043, 0x00); + hdptx_write(hdptx, CMN_REG0044, 0x46); + hdptx_write(hdptx, CMN_REG0045, 0x24); + hdptx_write(hdptx, CMN_REG0046, 0xff); + hdptx_write(hdptx, CMN_REG0047, 0x00); + hdptx_write(hdptx, CMN_REG0048, 0x44); + hdptx_write(hdptx, CMN_REG0049, 0xfa); + hdptx_write(hdptx, CMN_REG004A, 0x08); + hdptx_write(hdptx, CMN_REG004B, 0x00); + hdptx_write(hdptx, CMN_REG004C, 0x01); + hdptx_write(hdptx, CMN_REG004D, 0x64); + hdptx_write(hdptx, CMN_REG004E, 0x14); + hdptx_write(hdptx, CMN_REG004F, 0x00); + hdptx_write(hdptx, CMN_REG0050, 0x00); + hdptx_write(hdptx, CMN_REG0051, 0x00); + hdptx_write(hdptx, CMN_REG0055, 0x00); + hdptx_write(hdptx, CMN_REG0059, 0x11); + hdptx_write(hdptx, CMN_REG005A, 0x03); + hdptx_write(hdptx, CMN_REG005C, 0x05); + hdptx_write(hdptx, CMN_REG005D, 0x0c); + hdptx_write(hdptx, CMN_REG005E, 0x07); + hdptx_write(hdptx, CMN_REG005F, 0x01); + hdptx_write(hdptx, CMN_REG0060, 0x01); + hdptx_write(hdptx, CMN_REG0064, 0x07); + hdptx_write(hdptx, CMN_REG0065, 0x00); + hdptx_write(hdptx, CMN_REG0069, 0x00); + hdptx_write(hdptx, CMN_REG006B, 0x04); + hdptx_write(hdptx, CMN_REG006C, 0x00); + hdptx_write(hdptx, CMN_REG0070, 0x01); + hdptx_write(hdptx, CMN_REG0073, 0x30); + hdptx_write(hdptx, CMN_REG0074, 0x00); + hdptx_write(hdptx, CMN_REG0075, 0x20); + hdptx_write(hdptx, CMN_REG0076, 0x30); + hdptx_write(hdptx, CMN_REG0077, 0x08); + hdptx_write(hdptx, CMN_REG0078, 0x0c); + hdptx_write(hdptx, CMN_REG0079, 0x00); + hdptx_write(hdptx, CMN_REG007B, 0x00); + hdptx_write(hdptx, CMN_REG007C, 0x00); + hdptx_write(hdptx, CMN_REG007D, 0x00); + hdptx_write(hdptx, CMN_REG007E, 0x00); + hdptx_write(hdptx, CMN_REG007F, 0x00); + hdptx_write(hdptx, CMN_REG0080, 0x00); + hdptx_write(hdptx, CMN_REG0081, 0x09); + hdptx_write(hdptx, CMN_REG0082, 0x04); + hdptx_write(hdptx, CMN_REG0083, 0x24); + hdptx_write(hdptx, CMN_REG0084, 0x20); + hdptx_write(hdptx, CMN_REG0085, 0x03); + hdptx_write(hdptx, CMN_REG0086, 0x01); + hdptx_update_bits(hdptx, CMN_REG0086, PLL_PCG_POSTDIV_SEL_MASK, + PLL_PCG_POSTDIV_SEL(cfg->pms_sdiv)); + hdptx_update_bits(hdptx, CMN_REG0086, PLL_PCG_CLK_SEL_MASK, + PLL_PCG_CLK_SEL(color_depth)); + hdptx_write(hdptx, CMN_REG0087, 0x0c); + hdptx_write(hdptx, CMN_REG0089, 0x02); + hdptx_write(hdptx, CMN_REG008A, 0x55); + hdptx_write(hdptx, CMN_REG008B, 0x25); + hdptx_write(hdptx, CMN_REG008C, 0x2c); + hdptx_write(hdptx, CMN_REG008D, 0x22); + hdptx_write(hdptx, CMN_REG008E, 0x14); + hdptx_write(hdptx, CMN_REG008F, 0x20); + hdptx_write(hdptx, CMN_REG0090, 0x00); + hdptx_write(hdptx, CMN_REG0091, 0x00); + hdptx_write(hdptx, CMN_REG0092, 0x00); + hdptx_write(hdptx, CMN_REG0093, 0x00); + hdptx_write(hdptx, CMN_REG0095, 0x00); + hdptx_write(hdptx, CMN_REG0097, 0x00); + hdptx_write(hdptx, CMN_REG0099, 0x00); + hdptx_write(hdptx, CMN_REG009A, 0x11); + hdptx_write(hdptx, CMN_REG009B, 0x10); + hdptx_write(hdptx, SB_REG0114, 0x00); + hdptx_write(hdptx, SB_REG0115, 0x00); + hdptx_write(hdptx, SB_REG0116, 0x00); + hdptx_write(hdptx, SB_REG0117, 0x00); + hdptx_write(hdptx, LNTOP_REG0200, 0x04); + hdptx_write(hdptx, LNTOP_REG0201, 0x00); + hdptx_write(hdptx, LNTOP_REG0202, 0x00); + hdptx_write(hdptx, LNTOP_REG0203, 0xf0); + hdptx_write(hdptx, LNTOP_REG0204, 0xff); + hdptx_write(hdptx, LNTOP_REG0205, 0xff); + hdptx_write(hdptx, LNTOP_REG0206, 0x05); + hdptx_write(hdptx, LNTOP_REG0207, 0x0f); + hdptx_write(hdptx, LANE_REG0303, 0x0c); + hdptx_write(hdptx, LANE_REG0307, 0x20); + hdptx_write(hdptx, LANE_REG030A, 0x17); + hdptx_write(hdptx, LANE_REG030B, 0x77); + hdptx_write(hdptx, LANE_REG030C, 0x77); + hdptx_write(hdptx, LANE_REG030D, 0x77); + hdptx_write(hdptx, LANE_REG030E, 0x38); + hdptx_write(hdptx, LANE_REG0310, 0x03); + hdptx_write(hdptx, LANE_REG0311, 0x0f); + hdptx_write(hdptx, LANE_REG0312, 0x3c); + hdptx_write(hdptx, LANE_REG0316, 0x02); + hdptx_write(hdptx, LANE_REG031B, 0x01); + hdptx_write(hdptx, LANE_REG031F, 0x15); + hdptx_write(hdptx, LANE_REG0320, 0xa0); + hdptx_write(hdptx, LANE_REG0403, 0x0c); + hdptx_write(hdptx, LANE_REG0407, 0x20); + hdptx_write(hdptx, LANE_REG040A, 0x17); + hdptx_write(hdptx, LANE_REG040B, 0x77); + hdptx_write(hdptx, LANE_REG040C, 0x77); + hdptx_write(hdptx, LANE_REG040D, 0x77); + hdptx_write(hdptx, LANE_REG040E, 0x38); + hdptx_write(hdptx, LANE_REG0410, 0x03); + hdptx_write(hdptx, LANE_REG0411, 0x0f); + hdptx_write(hdptx, LANE_REG0412, 0x3c); + hdptx_write(hdptx, LANE_REG0416, 0x02); + hdptx_write(hdptx, LANE_REG041B, 0x01); + hdptx_write(hdptx, LANE_REG041F, 0x15); + hdptx_write(hdptx, LANE_REG0420, 0xa0); + hdptx_write(hdptx, LANE_REG0503, 0x0c); + hdptx_write(hdptx, LANE_REG0507, 0x20); + hdptx_write(hdptx, LANE_REG050A, 0x17); + hdptx_write(hdptx, LANE_REG050B, 0x77); + hdptx_write(hdptx, LANE_REG050C, 0x77); + hdptx_write(hdptx, LANE_REG050D, 0x77); + hdptx_write(hdptx, LANE_REG050E, 0x38); + hdptx_write(hdptx, LANE_REG0510, 0x03); + hdptx_write(hdptx, LANE_REG0511, 0x0f); + hdptx_write(hdptx, LANE_REG0512, 0x3c); + hdptx_write(hdptx, LANE_REG0516, 0x02); + hdptx_write(hdptx, LANE_REG051B, 0x01); + hdptx_write(hdptx, LANE_REG051F, 0x15); + hdptx_write(hdptx, LANE_REG0520, 0xa0); + hdptx_write(hdptx, LANE_REG0603, 0x0c); + hdptx_write(hdptx, LANE_REG0607, 0x20); + hdptx_write(hdptx, LANE_REG060A, 0x17); + hdptx_write(hdptx, LANE_REG060B, 0x77); + hdptx_write(hdptx, LANE_REG060C, 0x77); + hdptx_write(hdptx, LANE_REG060D, 0x77); + hdptx_write(hdptx, LANE_REG060E, 0x38); + hdptx_write(hdptx, LANE_REG0610, 0x03); + hdptx_write(hdptx, LANE_REG0611, 0x0f); + hdptx_write(hdptx, LANE_REG0612, 0x3c); + hdptx_write(hdptx, LANE_REG0616, 0x02); + hdptx_write(hdptx, LANE_REG061B, 0x01); + hdptx_write(hdptx, LANE_REG061F, 0x15); + hdptx_write(hdptx, LANE_REG0620, 0xa0); + + hdptx_write(hdptx, LANE_REG0303, 0x2f); + hdptx_write(hdptx, LANE_REG0403, 0x2f); + hdptx_write(hdptx, LANE_REG0503, 0x2f); + hdptx_write(hdptx, LANE_REG0603, 0x2f); + hdptx_write(hdptx, LANE_REG0305, 0x03); + hdptx_write(hdptx, LANE_REG0405, 0x03); + hdptx_write(hdptx, LANE_REG0505, 0x03); + hdptx_write(hdptx, LANE_REG0605, 0x03); + hdptx_write(hdptx, LANE_REG0306, 0xfc); + hdptx_write(hdptx, LANE_REG0406, 0xfc); + hdptx_write(hdptx, LANE_REG0506, 0xfc); + hdptx_write(hdptx, LANE_REG0606, 0xfc); + + hdptx_write(hdptx, LANE_REG0305, 0x4f); + hdptx_write(hdptx, LANE_REG0405, 0x4f); + hdptx_write(hdptx, LANE_REG0505, 0x4f); + hdptx_write(hdptx, LANE_REG0605, 0x4f); + hdptx_write(hdptx, LANE_REG0304, 0x14); + hdptx_write(hdptx, LANE_REG0404, 0x14); + hdptx_write(hdptx, LANE_REG0504, 0x14); + hdptx_write(hdptx, LANE_REG0604, 0x14); + + if (hdptx->earc_en) + hdptx_earc_config(hdptx); + + return hdptx_post_power_up(hdptx); +} + +static int rockchip_hdptx_phy_power_on(struct phy *phy) +{ + struct rockchip_hdptx_phy *hdptx = phy_get_drvdata(phy); + int bus_width = phy_get_bus_width(hdptx->phy); + int bit_rate = bus_width & DATA_RATE_MASK; + int ret; + + if (!hdptx->count) { + ret = clk_bulk_enable(hdptx->nr_clks, hdptx->clks); + if (ret) { + dev_err(hdptx->dev, "failed to enable clocks\n"); + return ret; + } + } + + dev_info(hdptx->dev, "bus_width:0x%x,bit_rate:%d\n", bus_width, bit_rate); + if (bus_width & HDMI_EARC_MASK) + hdptx->earc_en = true; + else + hdptx->earc_en = false; + + if (bus_width & HDMI_MODE_MASK) { + if (bit_rate > 24000000) + return hdptx_lcpll_frl_mode_config(hdptx, bus_width); + else + return hdptx_ropll_frl_mode_config(hdptx, bus_width); + } else { + return hdptx_ropll_tmds_mode_config(hdptx, bus_width); + } +} + +static int rockchip_hdptx_phy_power_off(struct phy *phy) +{ + struct rockchip_hdptx_phy *hdptx = phy_get_drvdata(phy); + + if (hdptx->count) + return 0; + + if (!(hdptx_grf_read(hdptx, GRF_HDPTX_STATUS) & HDPTX_O_PLL_LOCK_DONE)) + return 0; + + hdptx_phy_disable(hdptx); + clk_bulk_disable(hdptx->nr_clks, hdptx->clks); + + return 0; +} + +static const struct phy_ops rockchip_hdptx_phy_ops = { + .owner = THIS_MODULE, + .power_on = rockchip_hdptx_phy_power_on, + .power_off = rockchip_hdptx_phy_power_off, +}; + +static const struct of_device_id rockchip_hdptx_phy_of_match[] = { + { .compatible = "rockchip,rk3588-hdptx-phy-hdmi", + }, + {} +}; +MODULE_DEVICE_TABLE(of, rockchip_hdptx_phy_of_match); + +static void rockchip_hdptx_phy_runtime_disable(void *data) +{ + struct rockchip_hdptx_phy *hdptx = data; + + clk_bulk_unprepare(hdptx->nr_clks, hdptx->clks); + pm_runtime_disable(hdptx->dev); +} + +static unsigned long hdptx_phy_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct rockchip_hdptx_phy *hdptx = to_rockchip_hdptx_phy(hw); + + return hdptx->rate; +} + +static long hdptx_phy_clk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct ropll_config *cfg = ropll_tmds_cfg; + u32 bit_rate = rate / 100; + + if (rate > HDMI20_MAX_RATE) + return rate; + + for (; cfg->bit_rate != ~0; cfg++) + if (bit_rate == cfg->bit_rate) + break; + + if (cfg->bit_rate == ~0 && !hdptx_phy_clk_pll_calc(bit_rate, NULL)) + return -EINVAL; + + return rate; +} + +static int hdptx_phy_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct rockchip_hdptx_phy *hdptx = to_rockchip_hdptx_phy(hw); + + if (hdptx_grf_read(hdptx, GRF_HDPTX_STATUS) & HDPTX_O_PLL_LOCK_DONE) + hdptx_phy_disable(hdptx); + + rate = rate / 100; + + return hdptx_ropll_cmn_config(hdptx, rate); +} + +static int hdptx_phy_clk_enable(struct clk_hw *hw) +{ + struct rockchip_hdptx_phy *hdptx = to_rockchip_hdptx_phy(hw); + int ret; + + if (hdptx->count) { + hdptx->count++; + return 0; + } + + ret = clk_bulk_enable(hdptx->nr_clks, hdptx->clks); + if (ret) { + dev_err(hdptx->dev, "failed to enable clocks\n"); + return ret; + } + + if (hdptx->rate) { + ret = hdptx_ropll_cmn_config(hdptx, hdptx->rate / 100); + if (ret < 0) { + dev_err(hdptx->dev, "hdmi phy pll init failed\n"); + return ret; + } + } + + hdptx->count++; + + return 0; +} + +static void hdptx_phy_clk_disable(struct clk_hw *hw) +{ + struct rockchip_hdptx_phy *hdptx = to_rockchip_hdptx_phy(hw); + + if (hdptx->count > 1) { + hdptx->count--; + return; + } + + if (hdptx_grf_read(hdptx, GRF_HDPTX_STATUS) & HDPTX_O_PLL_LOCK_DONE) + hdptx_phy_disable(hdptx); + clk_bulk_disable(hdptx->nr_clks, hdptx->clks); + hdptx->count--; +} + +static const struct clk_ops hdptx_phy_clk_ops = { + .recalc_rate = hdptx_phy_clk_recalc_rate, + .round_rate = hdptx_phy_clk_round_rate, + .set_rate = hdptx_phy_clk_set_rate, + .enable = hdptx_phy_clk_enable, + .disable = hdptx_phy_clk_disable, +}; + +static int rockchip_hdptx_phy_clk_register(struct rockchip_hdptx_phy *hdptx) +{ + struct device *dev = hdptx->dev; + struct device_node *np = dev->of_node; + struct device_node *clk_np; + struct platform_device *pdev; + struct clk_init_data init = {}; + struct clk *refclk; + const char *parent_name; + int ret; + + clk_np = of_get_child_by_name(np, "clk-port"); + if (!clk_np) + return 0; + + pdev = of_platform_device_create(clk_np, NULL, dev); + if (!pdev) + return 0; + + refclk = devm_clk_get(dev, "ref"); + if (IS_ERR(refclk)) { + dev_err(dev, "failed to get ref clock\n"); + return PTR_ERR(refclk); + } + + parent_name = __clk_get_name(refclk); + + init.parent_names = &parent_name; + init.num_parents = 1; + init.flags = CLK_GET_RATE_NOCACHE; + if (!hdptx->id) + init.name = "clk_hdmiphy_pixel0"; + else + init.name = "clk_hdmiphy_pixel1"; + init.ops = &hdptx_phy_clk_ops; + + /* optional override of the clock name */ + of_property_read_string(np, "clock-output-names", &init.name); + + hdptx->hw.init = &init; + + hdptx->dclk = devm_clk_register(&pdev->dev, &hdptx->hw); + if (IS_ERR(hdptx->dclk)) { + ret = PTR_ERR(hdptx->dclk); + dev_err(dev, "failed to register clock: %d\n", ret); + return ret; + } + + ret = of_clk_add_provider(clk_np, of_clk_src_simple_get, hdptx->dclk); + if (ret) { + dev_err(dev, "failed to register OF clock provider: %d\n", ret); + return ret; + } + + return 0; +} + +static int rockchip_hdptx_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct rockchip_hdptx_phy *hdptx; + struct phy_provider *phy_provider; + struct resource *res; + void __iomem *regs; + int ret; + + hdptx = devm_kzalloc(dev, sizeof(*hdptx), GFP_KERNEL); + if (!hdptx) + return -ENOMEM; + + hdptx->dev = dev; + + hdptx->id = of_alias_get_id(dev->of_node, "hdptxhdmi"); + if (hdptx->id < 0) + hdptx->id = 0; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + ret = devm_clk_bulk_get_all(dev, &hdptx->clks); + if (ret < 1) + return dev_err_probe(dev, ret, "failed to get clocks\n"); + + hdptx->nr_clks = ret; + + ret = clk_bulk_prepare(hdptx->nr_clks, hdptx->clks); + if (ret) { + dev_err(hdptx->dev, "failed to prepare clocks\n"); + return ret; + } + + hdptx->regmap = devm_regmap_init_mmio(dev, regs, + &rockchip_hdptx_phy_regmap_config); + if (IS_ERR(hdptx->regmap)) { + ret = PTR_ERR(hdptx->regmap); + dev_err(dev, "failed to init regmap: %d\n", ret); + goto err_regsmap; + } + + hdptx->phy_reset = devm_reset_control_get(dev, "phy"); + if (IS_ERR(hdptx->phy_reset)) { + ret = PTR_ERR(hdptx->phy_reset); + dev_err(dev, "failed to get phy reset: %d\n", ret); + goto err_regsmap; + } + + hdptx->apb_reset = devm_reset_control_get(dev, "apb"); + if (IS_ERR(hdptx->apb_reset)) { + ret = PTR_ERR(hdptx->apb_reset); + dev_err(dev, "failed to get apb reset: %d\n", ret); + goto err_regsmap; + } + + hdptx->init_reset = devm_reset_control_get(dev, "init"); + if (IS_ERR(hdptx->init_reset)) { + ret = PTR_ERR(hdptx->init_reset); + dev_err(dev, "failed to get init reset: %d\n", ret); + goto err_regsmap; + } + + hdptx->cmn_reset = devm_reset_control_get(dev, "cmn"); + if (IS_ERR(hdptx->cmn_reset)) { + ret = PTR_ERR(hdptx->cmn_reset); + dev_err(dev, "failed to get apb reset: %d\n", ret); + goto err_regsmap; + } + + hdptx->lane_reset = devm_reset_control_get(dev, "lane"); + if (IS_ERR(hdptx->lane_reset)) { + ret = PTR_ERR(hdptx->lane_reset); + dev_err(dev, "failed to get lane reset: %d\n", ret); + goto err_regsmap; + } + + hdptx->ropll_reset = devm_reset_control_get(dev, "ropll"); + if (IS_ERR(hdptx->ropll_reset)) { + ret = PTR_ERR(hdptx->ropll_reset); + dev_err(dev, "failed to get ropll reset: %d\n", ret); + goto err_regsmap; + } + + hdptx->lcpll_reset = devm_reset_control_get(dev, "lcpll"); + if (IS_ERR(hdptx->lcpll_reset)) { + ret = PTR_ERR(hdptx->lcpll_reset); + dev_err(dev, "failed to get lcpll reset: %d\n", ret); + goto err_regsmap; + } + + hdptx->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); + if (IS_ERR(hdptx->grf)) { + ret = PTR_ERR(hdptx->grf); + dev_err(hdptx->dev, "Unable to get rockchip,grf\n"); + goto err_regsmap; + } + + hdptx->phy = devm_phy_create(dev, NULL, &rockchip_hdptx_phy_ops); + if (IS_ERR(hdptx->phy)) { + dev_err(dev, "failed to create HDMI PHY\n"); + ret = PTR_ERR(hdptx->phy); + goto err_regsmap; + } + + phy_set_drvdata(hdptx->phy, hdptx); + phy_set_bus_width(hdptx->phy, 8); + + pm_runtime_enable(dev); + ret = devm_add_action_or_reset(dev, rockchip_hdptx_phy_runtime_disable, + hdptx); + if (ret) + goto err_regsmap; + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(phy_provider)) { + dev_err(dev, "failed to register PHY provider\n"); + ret = PTR_ERR(phy_provider); + goto err_regsmap; + } + + reset_control_deassert(hdptx->apb_reset); + reset_control_deassert(hdptx->cmn_reset); + reset_control_deassert(hdptx->init_reset); + + ret = rockchip_hdptx_phy_clk_register(hdptx); + if (ret) + goto err_regsmap; + + platform_set_drvdata(pdev, hdptx); + dev_info(dev, "hdptx phy init success\n"); + return 0; + +err_regsmap: + clk_bulk_unprepare(hdptx->nr_clks, hdptx->clks); + return ret; +} + +static struct platform_driver rockchip_hdptx_phy_driver = { + .probe = rockchip_hdptx_phy_probe, + .driver = { + .name = "rockchip-hdptx-phy-hdmi", + .of_match_table = of_match_ptr(rockchip_hdptx_phy_of_match), + }, +}; + +module_platform_driver(rockchip_hdptx_phy_driver); + +MODULE_DESCRIPTION("Samsung HDMI-DP Transmitter Combphy Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c new file mode 100644 index 000000000..bb0beafb8 --- /dev/null +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -0,0 +1,1749 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Rockchip USBDP Combo PHY with Samsung IP block driver + * + * Copyright (C) 2021 Rockchip Electronics Co., Ltd + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* RK3588 USBDP PHY Register Definitions */ + +#define UDPHY_PCS 0x4000 +#define UDPHY_PMA 0x8000 + +/* VO0 GRF Registers */ +#define RK3588_GRF_VO0_CON0 0x0000 +#define RK3588_GRF_VO0_CON2 0x0008 +#define DP_SINK_HPD_CFG BIT(11) +#define DP_SINK_HPD_SEL BIT(10) +#define DP_AUX_DIN_SEL BIT(9) +#define DP_AUX_DOUT_SEL BIT(8) +#define DP_LANE_SEL_N(n) GENMASK(2 * (n) + 1, 2 * (n)) +#define DP_LANE_SEL_ALL GENMASK(7, 0) +#define PHY_AUX_DP_DATA_POL_NORMAL 0 +#define PHY_AUX_DP_DATA_POL_INVERT 1 + +/* PMA CMN Registers */ +#define CMN_LANE_MUX_AND_EN_OFFSET 0x0288 /* cmn_reg00A2 */ +#define CMN_DP_LANE_MUX_N(n) BIT((n) + 4) +#define CMN_DP_LANE_EN_N(n) BIT(n) +#define CMN_DP_LANE_MUX_ALL GENMASK(7, 4) +#define CMN_DP_LANE_EN_ALL GENMASK(3, 0) +#define PHY_LANE_MUX_USB 0 +#define PHY_LANE_MUX_DP 1 + +#define CMN_DP_LINK_OFFSET 0x28c /*cmn_reg00A3 */ +#define CMN_DP_TX_LINK_BW GENMASK(6, 5) +#define CMN_DP_TX_LANE_SWAP_EN BIT(2) + +#define CMN_SSC_EN_OFFSET 0x2d0 /* cmn_reg00B4 */ +#define CMN_ROPLL_SSC_EN BIT(1) +#define CMN_LCPLL_SSC_EN BIT(0) + +#define CMN_ANA_LCPLL_DONE_OFFSET 0x0350 /* cmn_reg00D4 */ +#define CMN_ANA_LCPLL_LOCK_DONE BIT(7) +#define CMN_ANA_LCPLL_AFC_DONE BIT(6) + +#define CMN_ANA_ROPLL_DONE_OFFSET 0x0354 /* cmn_reg00D5 */ +#define CMN_ANA_ROPLL_LOCK_DONE BIT(1) +#define CMN_ANA_ROPLL_AFC_DONE BIT(0) + +#define CMN_DP_RSTN_OFFSET 0x038c /* cmn_reg00E3 */ +#define CMN_DP_INIT_RSTN BIT(3) +#define CMN_DP_CMN_RSTN BIT(2) +#define CMN_CDR_WTCHDG_EN BIT(1) +#define CMN_CDR_WTCHDG_MSK_CDR_EN BIT(0) + +#define TRSV_ANA_TX_CLK_OFFSET_N(n) (0x854 + (n) * 0x800) /* trsv_reg0215 */ +#define LN_ANA_TX_SER_TXCLK_INV BIT(1) + +#define TRSV_LN0_MON_RX_CDR_DONE_OFFSET 0x0b84 /* trsv_reg02E1 */ +#define TRSV_LN0_MON_RX_CDR_LOCK_DONE BIT(0) + +#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 + +enum { + DP_BW_RBR, + DP_BW_HBR, + DP_BW_HBR2, + DP_BW_HBR3, +}; + +enum { + UDPHY_MODE_NONE = 0, + UDPHY_MODE_USB = BIT(0), + UDPHY_MODE_DP = BIT(1), + UDPHY_MODE_DP_USB = BIT(1) | BIT(0), +}; + +struct udphy_grf_reg { + unsigned int offset; + unsigned int bitend; + unsigned int bitstart; + unsigned int disable; + unsigned int enable; +}; + +struct udphy_grf_cfg { + /* u2phy-grf */ + struct udphy_grf_reg bvalid_phy_con; + struct udphy_grf_reg bvalid_grf_con; + + /* usb-grf */ + struct udphy_grf_reg usb3otg0_cfg; + struct udphy_grf_reg usb3otg1_cfg; + + /* usbdpphy-grf */ + struct udphy_grf_reg low_pwrn; + struct udphy_grf_reg rx_lfps; +}; + +struct udphy_vogrf_cfg { + /* vo-grf */ + struct udphy_grf_reg hpd_trigger; +}; + +struct dp_tx_drv_ctrl { + u32 trsv_reg0204; + u32 trsv_reg0205; + u32 trsv_reg0206; + u32 trsv_reg0207; +}; + +struct rockchip_udphy; + +struct rockchip_udphy_cfg { + /* resets to be requested */ + const char * const *rst_list; + int num_rsts; + + struct udphy_grf_cfg grfcfg; + struct udphy_vogrf_cfg vogrfcfg[2]; + const struct dp_tx_drv_ctrl (*dp_tx_ctrl_cfg[4])[4]; + const struct dp_tx_drv_ctrl (*dp_tx_ctrl_cfg_typec[4])[4]; + int (*combophy_init)(struct rockchip_udphy *udphy); + int (*dp_phy_set_rate)(struct rockchip_udphy *udphy, + struct phy_configure_opts_dp *dp); + int (*dp_phy_set_voltages)(struct rockchip_udphy *udphy, + struct phy_configure_opts_dp *dp); + int (*hpd_event_trigger)(struct rockchip_udphy *udphy, bool hpd); + int (*dplane_enable)(struct rockchip_udphy *udphy, int dp_lanes); + int (*dplane_select)(struct rockchip_udphy *udphy); +}; + +struct rockchip_udphy { + struct device *dev; + struct regmap *pma_regmap; + struct regmap *u2phygrf; + struct regmap *udphygrf; + struct regmap *usbgrf; + struct regmap *vogrf; + struct typec_switch_dev *sw; + struct typec_mux_dev *mux; + struct mutex mutex; /* mutex to protect access to individual PHYs */ + + /* clocks and rests */ + int num_clks; + struct clk_bulk_data *clks; + struct clk *refclk; + struct reset_control **rsts; + + /* PHY status management */ + bool flip; + bool mode_change; + u8 mode; + u8 status; + + /* utilized for USB */ + bool hs; /* flag for high-speed */ + + /* utilized for DP */ + struct gpio_desc *sbu1_dc_gpio; + struct gpio_desc *sbu2_dc_gpio; + u32 lane_mux_sel[4]; + 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; + u8 bw; + int id; + + /* PHY const config */ + const struct rockchip_udphy_cfg *cfgs; +}; + +static const struct dp_tx_drv_ctrl rk3588_dp_tx_drv_ctrl_rbr_hbr[4][4] = { + /* voltage swing 0, pre-emphasis 0->3 */ + { + { 0x20, 0x10, 0x42, 0xe5 }, + { 0x26, 0x14, 0x42, 0xe5 }, + { 0x29, 0x18, 0x42, 0xe5 }, + { 0x2b, 0x1c, 0x43, 0xe7 }, + }, + + /* voltage swing 1, pre-emphasis 0->2 */ + { + { 0x23, 0x10, 0x42, 0xe7 }, + { 0x2a, 0x17, 0x43, 0xe7 }, + { 0x2b, 0x1a, 0x43, 0xe7 }, + }, + + /* voltage swing 2, pre-emphasis 0->1 */ + { + { 0x27, 0x10, 0x42, 0xe7 }, + { 0x2b, 0x17, 0x43, 0xe7 }, + }, + + /* voltage swing 3, pre-emphasis 0 */ + { + { 0x29, 0x10, 0x43, 0xe7 }, + }, +}; + +static const struct dp_tx_drv_ctrl rk3588_dp_tx_drv_ctrl_rbr_hbr_typec[4][4] = { + /* voltage swing 0, pre-emphasis 0->3 */ + { + { 0x20, 0x10, 0x42, 0xe5 }, + { 0x26, 0x14, 0x42, 0xe5 }, + { 0x29, 0x18, 0x42, 0xe5 }, + { 0x2b, 0x1c, 0x43, 0xe7 }, + }, + + /* voltage swing 1, pre-emphasis 0->2 */ + { + { 0x23, 0x10, 0x42, 0xe7 }, + { 0x2a, 0x17, 0x43, 0xe7 }, + { 0x2b, 0x1a, 0x43, 0xe7 }, + }, + + /* voltage swing 2, pre-emphasis 0->1 */ + { + { 0x27, 0x10, 0x43, 0x67 }, + { 0x2b, 0x17, 0x43, 0xe7 }, + }, + + /* voltage swing 3, pre-emphasis 0 */ + { + { 0x29, 0x10, 0x43, 0xe7 }, + }, +}; + +static const struct dp_tx_drv_ctrl rk3588_dp_tx_drv_ctrl_hbr2[4][4] = { + /* voltage swing 0, pre-emphasis 0->3 */ + { + { 0x21, 0x10, 0x42, 0xe5 }, + { 0x26, 0x14, 0x42, 0xe5 }, + { 0x26, 0x16, 0x43, 0xe5 }, + { 0x2a, 0x19, 0x43, 0xe7 }, + }, + + /* voltage swing 1, pre-emphasis 0->2 */ + { + { 0x24, 0x10, 0x42, 0xe7 }, + { 0x2a, 0x17, 0x43, 0xe7 }, + { 0x2b, 0x1a, 0x43, 0xe7 }, + }, + + /* voltage swing 2, pre-emphasis 0->1 */ + { + { 0x28, 0x10, 0x42, 0xe7 }, + { 0x2b, 0x17, 0x43, 0xe7 }, + }, + + /* voltage swing 3, pre-emphasis 0 */ + { + { 0x28, 0x10, 0x43, 0xe7 }, + }, +}; + +static const struct dp_tx_drv_ctrl rk3588_dp_tx_drv_ctrl_hbr3[4][4] = { + /* voltage swing 0, pre-emphasis 0->3 */ + { + { 0x21, 0x10, 0x42, 0xe5 }, + { 0x26, 0x14, 0x42, 0xe5 }, + { 0x26, 0x16, 0x43, 0xe5 }, + { 0x29, 0x18, 0x43, 0xe7 }, + }, + + /* voltage swing 1, pre-emphasis 0->2 */ + { + { 0x24, 0x10, 0x42, 0xe7 }, + { 0x2a, 0x18, 0x43, 0xe7 }, + { 0x2b, 0x1b, 0x43, 0xe7 } + }, + + /* voltage swing 2, pre-emphasis 0->1 */ + { + { 0x27, 0x10, 0x42, 0xe7 }, + { 0x2b, 0x18, 0x43, 0xe7 } + }, + + /* voltage swing 3, pre-emphasis 0 */ + { + { 0x28, 0x10, 0x43, 0xe7 }, + }, +}; + +static const struct reg_sequence rk3588_udphy_24m_refclk_cfg[] = { + {0x0090, 0x68}, {0x0094, 0x68}, + {0x0128, 0x24}, {0x012c, 0x44}, + {0x0130, 0x3f}, {0x0134, 0x44}, + {0x015c, 0xa9}, {0x0160, 0x71}, + {0x0164, 0x71}, {0x0168, 0xa9}, + {0x0174, 0xa9}, {0x0178, 0x71}, + {0x017c, 0x71}, {0x0180, 0xa9}, + {0x018c, 0x41}, {0x0190, 0x00}, + {0x0194, 0x05}, {0x01ac, 0x2a}, + {0x01b0, 0x17}, {0x01b4, 0x17}, + {0x01b8, 0x2a}, {0x01c8, 0x04}, + {0x01cc, 0x08}, {0x01d0, 0x08}, + {0x01d4, 0x04}, {0x01d8, 0x20}, + {0x01dc, 0x01}, {0x01e0, 0x09}, + {0x01e4, 0x03}, {0x01f0, 0x29}, + {0x01f4, 0x02}, {0x01f8, 0x02}, + {0x01fc, 0x29}, {0x0208, 0x2a}, + {0x020c, 0x17}, {0x0210, 0x17}, + {0x0214, 0x2a}, {0x0224, 0x20}, + {0x03f0, 0x0a}, {0x03f4, 0x07}, + {0x03f8, 0x07}, {0x03fc, 0x0c}, + {0x0404, 0x12}, {0x0408, 0x1a}, + {0x040c, 0x1a}, {0x0410, 0x3f}, + {0x0ce0, 0x68}, {0x0ce8, 0xd0}, + {0x0cf0, 0x87}, {0x0cf8, 0x70}, + {0x0d00, 0x70}, {0x0d08, 0xa9}, + {0x1ce0, 0x68}, {0x1ce8, 0xd0}, + {0x1cf0, 0x87}, {0x1cf8, 0x70}, + {0x1d00, 0x70}, {0x1d08, 0xa9}, + {0x0a3c, 0xd0}, {0x0a44, 0xd0}, + {0x0a48, 0x01}, {0x0a4c, 0x0d}, + {0x0a54, 0xe0}, {0x0a5c, 0xe0}, + {0x0a64, 0xa8}, {0x1a3c, 0xd0}, + {0x1a44, 0xd0}, {0x1a48, 0x01}, + {0x1a4c, 0x0d}, {0x1a54, 0xe0}, + {0x1a5c, 0xe0}, {0x1a64, 0xa8} +}; + +static const struct reg_sequence rk3588_udphy_26m_refclk_cfg[] = { + {0x0830, 0x07}, {0x085c, 0x80}, + {0x1030, 0x07}, {0x105c, 0x80}, + {0x1830, 0x07}, {0x185c, 0x80}, + {0x2030, 0x07}, {0x205c, 0x80}, + {0x0228, 0x38}, {0x0104, 0x44}, + {0x0248, 0x44}, {0x038C, 0x02}, + {0x0878, 0x04}, {0x1878, 0x04}, + {0x0898, 0x77}, {0x1898, 0x77}, + {0x0054, 0x01}, {0x00e0, 0x38}, + {0x0060, 0x24}, {0x0064, 0x77}, + {0x0070, 0x76}, {0x0234, 0xE8}, + {0x0AF4, 0x15}, {0x1AF4, 0x15}, + {0x081C, 0xE5}, {0x181C, 0xE5}, + {0x099C, 0x48}, {0x199C, 0x48}, + {0x09A4, 0x07}, {0x09A8, 0x22}, + {0x19A4, 0x07}, {0x19A8, 0x22}, + {0x09B8, 0x3E}, {0x19B8, 0x3E}, + {0x09E4, 0x02}, {0x19E4, 0x02}, + {0x0A34, 0x1E}, {0x1A34, 0x1E}, + {0x0A98, 0x2F}, {0x1A98, 0x2F}, + {0x0c30, 0x0E}, {0x0C48, 0x06}, + {0x1C30, 0x0E}, {0x1C48, 0x06}, + {0x028C, 0x18}, {0x0AF0, 0x00}, + {0x1AF0, 0x00} +}; + +static const struct reg_sequence rk3588_udphy_init_sequence[] = { + {0x0104, 0x44}, {0x0234, 0xE8}, + {0x0248, 0x44}, {0x028C, 0x18}, + {0x081C, 0xE5}, {0x0878, 0x00}, + {0x0994, 0x1C}, {0x0AF0, 0x00}, + {0x181C, 0xE5}, {0x1878, 0x00}, + {0x1994, 0x1C}, {0x1AF0, 0x00}, + {0x0428, 0x60}, {0x0D58, 0x33}, + {0x1D58, 0x33}, {0x0990, 0x74}, + {0x0D64, 0x17}, {0x08C8, 0x13}, + {0x1990, 0x74}, {0x1D64, 0x17}, + {0x18C8, 0x13}, {0x0D90, 0x40}, + {0x0DA8, 0x40}, {0x0DC0, 0x40}, + {0x0DD8, 0x40}, {0x1D90, 0x40}, + {0x1DA8, 0x40}, {0x1DC0, 0x40}, + {0x1DD8, 0x40}, {0x03C0, 0x30}, + {0x03C4, 0x06}, {0x0E10, 0x00}, + {0x1E10, 0x00}, {0x043C, 0x0F}, + {0x0D2C, 0xFF}, {0x1D2C, 0xFF}, + {0x0D34, 0x0F}, {0x1D34, 0x0F}, + {0x08FC, 0x2A}, {0x0914, 0x28}, + {0x0A30, 0x03}, {0x0E38, 0x05}, + {0x0ECC, 0x27}, {0x0ED0, 0x22}, + {0x0ED4, 0x26}, {0x18FC, 0x2A}, + {0x1914, 0x28}, {0x1A30, 0x03}, + {0x1E38, 0x05}, {0x1ECC, 0x27}, + {0x1ED0, 0x22}, {0x1ED4, 0x26}, + {0x0048, 0x0F}, {0x0060, 0x3C}, + {0x0064, 0xF7}, {0x006C, 0x20}, + {0x0070, 0x7D}, {0x0074, 0x68}, + {0x0AF4, 0x1A}, {0x1AF4, 0x1A}, + {0x0440, 0x3F}, {0x10D4, 0x08}, + {0x20D4, 0x08}, {0x00D4, 0x30}, + {0x0024, 0x6e}, +}; + +static inline int grfreg_write(struct regmap *base, + const struct udphy_grf_reg *reg, bool en) +{ + u32 val, mask, tmp; + + tmp = en ? reg->enable : reg->disable; + mask = GENMASK(reg->bitend, reg->bitstart); + val = (tmp << reg->bitstart) | (mask << BIT_WRITEABLE_SHIFT); + + return regmap_write(base, reg->offset, val); +} + +static int udphy_clk_init(struct rockchip_udphy *udphy, struct device *dev) +{ + int i; + + udphy->num_clks = devm_clk_bulk_get_all(dev, &udphy->clks); + if (udphy->num_clks < 1) + return -ENODEV; + + /* used for configure phy reference clock frequency */ + for (i = 0; i < udphy->num_clks; i++) { + if (!strncmp(udphy->clks[i].id, "refclk", 6)) { + udphy->refclk = udphy->clks[i].clk; + break; + } + } + + if (!udphy->refclk) + dev_warn(udphy->dev, "no refclk found\n"); + + return 0; +} + +static int udphy_reset_init(struct rockchip_udphy *udphy, struct device *dev) +{ + const struct rockchip_udphy_cfg *cfg = udphy->cfgs; + int idx; + + udphy->rsts = devm_kcalloc(dev, cfg->num_rsts, + sizeof(*udphy->rsts), GFP_KERNEL); + if (!udphy->rsts) + return -ENOMEM; + + for (idx = 0; idx < cfg->num_rsts; idx++) { + struct reset_control *rst; + const char *name = cfg->rst_list[idx]; + + rst = devm_reset_control_get(dev, name); + if (IS_ERR(rst)) { + dev_err(dev, "failed to get %s reset\n", name); + devm_kfree(dev, (void *)udphy->rsts); + return PTR_ERR(rst); + } + + udphy->rsts[idx] = rst; + } + + return 0; +} + +static int udphy_reset_assert_all(struct rockchip_udphy *udphy) +{ + const struct rockchip_udphy_cfg *cfg = udphy->cfgs; + int idx, ret; + + for (idx = 0; idx < cfg->num_rsts; idx++) { + ret = reset_control_assert(udphy->rsts[idx]); + if (ret < 0) + return ret; + } + + return 0; +} + +static int udphy_reset_deassert_all(struct rockchip_udphy *udphy) +{ + const struct rockchip_udphy_cfg *cfg = udphy->cfgs; + int idx, ret; + + for (idx = 0; idx < cfg->num_rsts; idx++) { + ret = reset_control_deassert(udphy->rsts[idx]); + if (ret < 0) + return ret; + } + + return 0; +} + +static int udphy_get_rst_idx(const char * const *list, int num, char *name) +{ + int idx; + + for (idx = 0; idx < num; idx++) { + if (!strcmp(list[idx], name)) + return idx; + } + + return -EINVAL; +} + +static int udphy_reset_assert(struct rockchip_udphy *udphy, char *name) +{ + const struct rockchip_udphy_cfg *cfg = udphy->cfgs; + int idx; + + idx = udphy_get_rst_idx(cfg->rst_list, cfg->num_rsts, name); + if (idx < 0) + return idx; + + return reset_control_assert(udphy->rsts[idx]); +} + +static int udphy_reset_deassert(struct rockchip_udphy *udphy, char *name) +{ + const struct rockchip_udphy_cfg *cfg = udphy->cfgs; + int idx; + + idx = udphy_get_rst_idx(cfg->rst_list, cfg->num_rsts, name); + if (idx < 0) + return idx; + + return reset_control_deassert(udphy->rsts[idx]); +} + +static void udphy_u3_port_disable(struct rockchip_udphy *udphy, u8 disable) +{ + const struct rockchip_udphy_cfg *cfg = udphy->cfgs; + const struct udphy_grf_reg *preg; + + preg = udphy->id ? &cfg->grfcfg.usb3otg1_cfg : &cfg->grfcfg.usb3otg0_cfg; + grfreg_write(udphy->usbgrf, preg, disable); +} + +static void udphy_usb_bvalid_enable(struct rockchip_udphy *udphy, u8 enable) +{ + const struct rockchip_udphy_cfg *cfg = udphy->cfgs; + + grfreg_write(udphy->u2phygrf, &cfg->grfcfg.bvalid_phy_con, enable); + grfreg_write(udphy->u2phygrf, &cfg->grfcfg.bvalid_grf_con, enable); +} + +/* + * In usb/dp combo phy driver, here are 2 ways to mapping lanes. + * + * 1 Type-C Mapping table (DP_Alt_Mode V1.0b remove ABF pin mapping) + * --------------------------------------------------------------------------- + * Type-C Pin B11-B10 A2-A3 A11-A10 B2-B3 + * PHY Pad ln0(tx/rx) ln1(tx) ln2(tx/rx) ln3(tx) + * C/E(Normal) dpln3 dpln2 dpln0 dpln1 + * C/E(Flip ) dpln0 dpln1 dpln3 dpln2 + * D/F(Normal) usbrx usbtx dpln0 dpln1 + * D/F(Flip ) dpln0 dpln1 usbrx usbtx + * A(Normal ) dpln3 dpln1 dpln2 dpln0 + * A(Flip ) dpln2 dpln0 dpln3 dpln1 + * B(Normal ) usbrx usbtx dpln1 dpln0 + * B(Flip ) dpln1 dpln0 usbrx usbtx + * --------------------------------------------------------------------------- + * + * 2 Mapping the lanes in dtsi + * if all 4 lane assignment for dp 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 1 2 3> dpln0 dpln1 dpln2 dpln3 + * <2 3 0 1> dpln2 dpln3 dpln0 dpln1 + * --------------------------------------------------------------------------- + * if 2 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 1> dpln0 dpln1 usbrx usbtx + * <2 3> usbrx usbtx dpln0 dpln1 + * --------------------------------------------------------------------------- + */ + +static int udphy_dplane_select(struct rockchip_udphy *udphy) +{ + const struct rockchip_udphy_cfg *cfg = udphy->cfgs; + + if (cfg->dplane_select) + return cfg->dplane_select(udphy); + + return 0; +} + +static int udphy_dplane_get(struct rockchip_udphy *udphy) +{ + int dp_lanes; + + switch (udphy->mode) { + case UDPHY_MODE_DP: + dp_lanes = 4; + break; + case UDPHY_MODE_DP_USB: + dp_lanes = 2; + break; + case UDPHY_MODE_USB: + fallthrough; + default: + dp_lanes = 0; + break; + } + + return dp_lanes; +} + +static int udphy_dplane_enable(struct rockchip_udphy *udphy, int dp_lanes) +{ + const struct rockchip_udphy_cfg *cfg = udphy->cfgs; + int ret = 0; + + if (cfg->dplane_enable) + ret = cfg->dplane_enable(udphy, dp_lanes); + + return ret; +} + +static int upphy_set_typec_default_mapping(struct rockchip_udphy *udphy) +{ + if (udphy->flip) { + udphy->dp_lane_sel[0] = 0; + 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); + gpiod_set_value_cansleep(udphy->sbu2_dc_gpio, 0); + } else { + udphy->dp_lane_sel[0] = 2; + 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; + + return 0; +} + +static int udphy_orien_sw_set(struct typec_switch_dev *sw, + enum typec_orientation orien) +{ + struct rockchip_udphy *udphy = typec_switch_get_drvdata(sw); + + mutex_lock(&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 */ + udphy_usb_bvalid_enable(udphy, false); + goto unlock_ret; + } + + udphy->flip = (orien == TYPEC_ORIENTATION_REVERSE) ? true : false; + upphy_set_typec_default_mapping(udphy); + udphy_usb_bvalid_enable(udphy, true); + +unlock_ret: + mutex_unlock(&udphy->mutex); + return 0; +} + +static int udphy_setup_orien_switch(struct rockchip_udphy *udphy) +{ + struct typec_switch_desc sw_desc = { }; + + sw_desc.drvdata = udphy; + sw_desc.fwnode = dev_fwnode(udphy->dev); + sw_desc.set = udphy_orien_sw_set; + + udphy->sw = typec_switch_register(udphy->dev, &sw_desc); + if (IS_ERR(udphy->sw)) { + dev_err(udphy->dev, "Error register typec orientation switch: %ld\n", + PTR_ERR(udphy->sw)); + return PTR_ERR(udphy->sw); + } + + return 0; +} + +static void udphy_orien_switch_unregister(void *data) +{ + struct rockchip_udphy *udphy = data; + + typec_switch_unregister(udphy->sw); +} + +static int udphy_setup(struct rockchip_udphy *udphy) +{ + const struct rockchip_udphy_cfg *cfg = udphy->cfgs; + int ret = 0; + + ret = clk_bulk_prepare_enable(udphy->num_clks, udphy->clks); + if (ret) { + dev_err(udphy->dev, "failed to enable clk\n"); + return ret; + } + + if (cfg->combophy_init) { + ret = cfg->combophy_init(udphy); + if (ret) { + dev_err(udphy->dev, "failed to init combophy\n"); + clk_bulk_disable_unprepare(udphy->num_clks, udphy->clks); + return ret; + } + } + + return 0; +} + +static int udphy_disable(struct rockchip_udphy *udphy) +{ + clk_bulk_disable_unprepare(udphy->num_clks, udphy->clks); + udphy_reset_assert_all(udphy); + + return 0; +} + +static int udphy_parse_lane_mux_data(struct rockchip_udphy *udphy, struct device_node *np) +{ + struct property *prop; + int ret, i, len, num_lanes; + + prop = of_find_property(np, "rockchip,dp-lane-mux", &len); + if (!prop) { + dev_dbg(udphy->dev, "failed to find dp lane mux, following dp alt mode\n"); + udphy->mode = UDPHY_MODE_USB; + return 0; + } + + num_lanes = len / sizeof(u32); + + if (num_lanes != 2 && num_lanes != 4) { + dev_err(udphy->dev, "invalid number of lane mux\n"); + return -EINVAL; + } + + ret = of_property_read_u32_array(np, "rockchip,dp-lane-mux", udphy->dp_lane_sel, num_lanes); + if (ret) { + dev_err(udphy->dev, "get dp lane mux failed\n"); + return -EINVAL; + } + + for (i = 0; i < num_lanes; i++) { + int j; + + if (udphy->dp_lane_sel[i] > 3) { + dev_err(udphy->dev, "lane mux between 0 and 3, exceeding the range\n"); + return -EINVAL; + } + + udphy->lane_mux_sel[udphy->dp_lane_sel[i]] = PHY_LANE_MUX_DP; + + for (j = i + 1; j < num_lanes; j++) { + if (udphy->dp_lane_sel[i] == udphy->dp_lane_sel[j]) { + dev_err(udphy->dev, "set repeat lane mux value\n"); + return -EINVAL; + } + } + } + + udphy->mode = UDPHY_MODE_DP; + if (num_lanes == 2) { + udphy->mode |= UDPHY_MODE_USB; + udphy->flip = (udphy->lane_mux_sel[0] == PHY_LANE_MUX_DP); + } + + return 0; +} + +static int udphy_get_initial_status(struct rockchip_udphy *udphy) +{ + int ret; + u32 value; + + ret = clk_bulk_prepare_enable(udphy->num_clks, udphy->clks); + if (ret) { + dev_err(udphy->dev, "failed to enable clk\n"); + return ret; + } + + udphy_reset_deassert_all(udphy); + + regmap_read(udphy->pma_regmap, CMN_LANE_MUX_AND_EN_OFFSET, &value); + if (FIELD_GET(CMN_DP_LANE_MUX_ALL, value) && FIELD_GET(CMN_DP_LANE_EN_ALL, value)) + udphy->status = UDPHY_MODE_DP; + else + udphy_disable(udphy); + + return 0; +} + +static int udphy_parse_dt(struct rockchip_udphy *udphy, struct device *dev) +{ + struct device_node *np = dev->of_node; + enum usb_device_speed maximum_speed; + int ret; + + udphy->u2phygrf = syscon_regmap_lookup_by_phandle(np, "rockchip,u2phy-grf"); + if (IS_ERR(udphy->u2phygrf)) { + if (PTR_ERR(udphy->u2phygrf) == -ENODEV) { + dev_warn(dev, "missing u2phy-grf dt node\n"); + udphy->u2phygrf = NULL; + } else { + return PTR_ERR(udphy->u2phygrf); + } + } + + udphy->udphygrf = syscon_regmap_lookup_by_phandle(np, "rockchip,usbdpphy-grf"); + if (IS_ERR(udphy->udphygrf)) { + if (PTR_ERR(udphy->udphygrf) == -ENODEV) { + dev_warn(dev, "missing usbdpphy-grf dt node\n"); + udphy->udphygrf = NULL; + } else { + return PTR_ERR(udphy->udphygrf); + } + } + + udphy->usbgrf = syscon_regmap_lookup_by_phandle(np, "rockchip,usb-grf"); + if (IS_ERR(udphy->usbgrf)) { + if (PTR_ERR(udphy->usbgrf) == -ENODEV) { + dev_warn(dev, "missing usb-grf dt node\n"); + udphy->usbgrf = NULL; + } else { + return PTR_ERR(udphy->usbgrf); + } + } + + udphy->vogrf = syscon_regmap_lookup_by_phandle(np, "rockchip,vo-grf"); + if (IS_ERR(udphy->vogrf)) { + if (PTR_ERR(udphy->vogrf) == -ENODEV) { + dev_warn(dev, "missing vo-grf dt node\n"); + udphy->vogrf = NULL; + } else { + return PTR_ERR(udphy->vogrf); + } + } + + ret = udphy_parse_lane_mux_data(udphy, np); + if (ret) + return ret; + + udphy->sbu1_dc_gpio = devm_gpiod_get_optional(dev, "sbu1-dc", GPIOD_OUT_LOW); + if (IS_ERR(udphy->sbu1_dc_gpio)) + return PTR_ERR(udphy->sbu1_dc_gpio); + + udphy->sbu2_dc_gpio = devm_gpiod_get_optional(dev, "sbu2-dc", GPIOD_OUT_LOW); + if (IS_ERR(udphy->sbu2_dc_gpio)) + return PTR_ERR(udphy->sbu2_dc_gpio); + + if (device_property_present(dev, "maximum-speed")) { + maximum_speed = usb_get_maximum_speed(dev); + udphy->hs = maximum_speed <= USB_SPEED_HIGH ? true : false; + } + + ret = udphy_clk_init(udphy, dev); + if (ret) + return ret; + + ret = udphy_reset_init(udphy, dev); + if (ret) + return ret; + + return 0; +} + +static int udphy_power_on(struct rockchip_udphy *udphy, u8 mode) +{ + int ret; + + if (!(udphy->mode & mode)) { + dev_info(udphy->dev, "mode 0x%02x is not support\n", mode); + return 0; + } + + if (udphy->status == UDPHY_MODE_NONE) { + udphy->mode_change = false; + ret = udphy_setup(udphy); + if (ret) + return ret; + + if (udphy->mode & UDPHY_MODE_USB) + udphy_u3_port_disable(udphy, false); + } else if (udphy->mode_change) { + udphy->mode_change = false; + udphy->status = UDPHY_MODE_NONE; + if (udphy->mode == UDPHY_MODE_DP) + udphy_u3_port_disable(udphy, true); + + ret = udphy_disable(udphy); + if (ret) + return ret; + ret = udphy_setup(udphy); + if (ret) + return ret; + } + + udphy->status |= mode; + + return 0; +} + +static int udphy_power_off(struct rockchip_udphy *udphy, u8 mode) +{ + int ret; + + if (!(udphy->mode & mode)) { + dev_info(udphy->dev, "mode 0x%02x is not support\n", mode); + return 0; + } + + if (!udphy->status) + return 0; + + udphy->status &= ~mode; + + if (udphy->status == UDPHY_MODE_NONE) { + ret = udphy_disable(udphy); + if (ret) + return ret; + } + + return 0; +} + +static int rockchip_dp_phy_power_on(struct phy *phy) +{ + struct rockchip_udphy *udphy = phy_get_drvdata(phy); + int ret, dp_lanes; + + mutex_lock(&udphy->mutex); + + dp_lanes = udphy_dplane_get(udphy); + phy_set_bus_width(phy, dp_lanes); + + ret = udphy_power_on(udphy, UDPHY_MODE_DP); + if (ret) + goto unlock; + + ret = udphy_dplane_enable(udphy, dp_lanes); + if (ret) + goto unlock; + + ret = udphy_dplane_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; +} + +static int rockchip_dp_phy_power_off(struct phy *phy) +{ + struct rockchip_udphy *udphy = phy_get_drvdata(phy); + int ret; + + mutex_lock(&udphy->mutex); + ret = udphy_dplane_enable(udphy, 0); + if (ret) + goto unlock; + + ret = udphy_power_off(udphy, UDPHY_MODE_DP); + +unlock: + mutex_unlock(&udphy->mutex); + return ret; +} + +static int rockchip_dp_phy_verify_link_rate(unsigned int link_rate) +{ + switch (link_rate) { + case 1620: + case 2700: + case 5400: + case 8100: + break; + default: + return -EINVAL; + } + + return 0; +} + +static int rockchip_dp_phy_verify_config(struct rockchip_udphy *udphy, + struct phy_configure_opts_dp *dp) +{ + int i, ret; + + /* If changing link rate was required, verify it's supported. */ + ret = rockchip_dp_phy_verify_link_rate(dp->link_rate); + if (ret) + return ret; + + /* Verify lane count. */ + switch (dp->lanes) { + case 1: + case 2: + case 4: + /* valid lane count. */ + break; + default: + return -EINVAL; + } + + /* + * If changing voltages is required, check swing and pre-emphasis + * levels, per-lane. + */ + if (dp->set_voltages) { + /* Lane count verified previously. */ + for (i = 0; i < dp->lanes; i++) { + if (dp->voltage[i] > 3 || dp->pre[i] > 3) + return -EINVAL; + + /* + * Sum of voltage swing and pre-emphasis levels cannot + * exceed 3. + */ + if (dp->voltage[i] + dp->pre[i] > 3) + return -EINVAL; + } + } + + return 0; +} + +static int rockchip_dp_phy_configure(struct phy *phy, + union phy_configure_opts *opts) +{ + struct rockchip_udphy *udphy = phy_get_drvdata(phy); + const struct rockchip_udphy_cfg *cfg = udphy->cfgs; + int ret; + + ret = rockchip_dp_phy_verify_config(udphy, &opts->dp); + if (ret) + return ret; + + if (opts->dp.set_rate && cfg->dp_phy_set_rate) { + ret = cfg->dp_phy_set_rate(udphy, &opts->dp); + if (ret) { + dev_err(udphy->dev, + "rockchip_hdptx_phy_set_rate failed\n"); + return ret; + } + } + + if (opts->dp.set_voltages && cfg->dp_phy_set_voltages) { + ret = cfg->dp_phy_set_voltages(udphy, &opts->dp); + if (ret) { + dev_err(udphy->dev, + "rockchip_dp_phy_set_voltages failed\n"); + return ret; + } + } + + return 0; +} + +static const struct phy_ops rockchip_dp_phy_ops = { + .power_on = rockchip_dp_phy_power_on, + .power_off = rockchip_dp_phy_power_off, + .configure = rockchip_dp_phy_configure, + .owner = THIS_MODULE, +}; + +static int rockchip_u3phy_init(struct phy *phy) +{ + struct rockchip_udphy *udphy = phy_get_drvdata(phy); + int ret = 0; + + mutex_lock(&udphy->mutex); + /* DP only or high-speed, disable U3 port */ + if (!(udphy->mode & UDPHY_MODE_USB) || udphy->hs) { + udphy_u3_port_disable(udphy, true); + goto unlock; + } + + ret = udphy_power_on(udphy, UDPHY_MODE_USB); + +unlock: + mutex_unlock(&udphy->mutex); + return ret; +} + +static int rockchip_u3phy_exit(struct phy *phy) +{ + struct rockchip_udphy *udphy = phy_get_drvdata(phy); + int ret = 0; + + mutex_lock(&udphy->mutex); + /* DP only or high-speed */ + if (!(udphy->mode & UDPHY_MODE_USB) || udphy->hs) + goto unlock; + + ret = udphy_power_off(udphy, UDPHY_MODE_USB); + +unlock: + mutex_unlock(&udphy->mutex); + return ret; +} + +static const struct phy_ops rockchip_u3phy_ops = { + .init = rockchip_u3phy_init, + .exit = rockchip_u3phy_exit, + .owner = THIS_MODULE, +}; + +static int usbdp_typec_mux_set(struct typec_mux_dev *mux, + struct typec_mux_state *state) +{ + struct rockchip_udphy *udphy = typec_mux_get_drvdata(mux); + const struct rockchip_udphy_cfg *cfg = udphy->cfgs; + u8 mode; + + mutex_lock(&udphy->mutex); + + switch (state->mode) { + case TYPEC_DP_STATE_C: + fallthrough; + 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; + case TYPEC_DP_STATE_D: + fallthrough; + 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) { + if (cfg->hpd_event_trigger) + cfg->hpd_event_trigger(udphy, false); + } else if (data->status & DP_STATUS_IRQ_HPD) { + if (cfg->hpd_event_trigger) { + cfg->hpd_event_trigger(udphy, false); + usleep_range(750, 800); + cfg->hpd_event_trigger(udphy, true); + } + } else if (data->status & DP_STATUS_HPD_STATE) { + if (udphy->mode != mode) { + udphy->mode = mode; + udphy->mode_change = true; + } + if (cfg->hpd_event_trigger) + cfg->hpd_event_trigger(udphy, true); + } else { + if (cfg->hpd_event_trigger) + cfg->hpd_event_trigger(udphy, false); + } + } + + mutex_unlock(&udphy->mutex); + return 0; +} + +static int udphy_setup_typec_mux(struct rockchip_udphy *udphy) +{ + struct typec_mux_desc mux_desc = {}; + + mux_desc.drvdata = udphy; + mux_desc.fwnode = dev_fwnode(udphy->dev); + mux_desc.set = usbdp_typec_mux_set; + + udphy->mux = typec_mux_register(udphy->dev, &mux_desc); + if (IS_ERR(udphy->mux)) { + dev_err(udphy->dev, "Error register typec mux: %ld\n", + PTR_ERR(udphy->mux)); + return PTR_ERR(udphy->mux); + } + + return 0; +} + +static void udphy_typec_mux_unregister(void *data) +{ + struct rockchip_udphy *udphy = data; + + typec_mux_unregister(udphy->mux); +} + +static u32 udphy_dp_get_max_link_rate(struct rockchip_udphy *udphy, struct device_node *np) +{ + u32 max_link_rate; + int ret; + + ret = of_property_read_u32(np, "max-link-rate", &max_link_rate); + if (ret) + return 8100; + + ret = rockchip_dp_phy_verify_link_rate(max_link_rate); + if (ret) { + dev_warn(udphy->dev, "invalid max-link-rate value:%d\n", max_link_rate); + max_link_rate = 8100; + } + + return max_link_rate; +} + +static const struct regmap_config rockchip_udphy_pma_regmap_cfg = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .fast_io = true, + .max_register = 0x20dc, +}; + +static int rockchip_udphy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *child_np; + struct phy_provider *phy_provider; + struct resource *res; + struct rockchip_udphy *udphy; + const struct rockchip_udphy_cfg *phy_cfgs; + void __iomem *base; + int id, ret; + + udphy = devm_kzalloc(dev, sizeof(*udphy), GFP_KERNEL); + if (!udphy) + return -ENOMEM; + + id = of_alias_get_id(dev->of_node, "usbdp"); + if (id < 0) + id = 0; + udphy->id = id; + + phy_cfgs = device_get_match_data(dev); + if (!phy_cfgs) { + dev_err(dev, "missing match data\n"); + return -EINVAL; + } + + udphy->cfgs = phy_cfgs; + + base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(base)) + return PTR_ERR(base); + + udphy->pma_regmap = devm_regmap_init_mmio(dev, base + UDPHY_PMA, + &rockchip_udphy_pma_regmap_cfg); + if (IS_ERR(udphy->pma_regmap)) + return PTR_ERR(udphy->pma_regmap); + + ret = udphy_parse_dt(udphy, dev); + if (ret) + return ret; + + ret = udphy_get_initial_status(udphy); + if (ret) + return ret; + + mutex_init(&udphy->mutex); + udphy->dev = dev; + platform_set_drvdata(pdev, udphy); + + if (device_property_present(dev, "orientation-switch")) { + ret = udphy_setup_orien_switch(udphy); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, udphy_orien_switch_unregister, udphy); + if (ret) + return ret; + } + + if (device_property_present(dev, "mode-switch")) { + ret = udphy_setup_typec_mux(udphy); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, udphy_typec_mux_unregister, udphy); + if (ret) + return ret; + } + + for_each_available_child_of_node(np, child_np) { + struct phy *phy; + + if (of_node_name_eq(child_np, "dp-port")) { + phy = devm_phy_create(dev, child_np, &rockchip_dp_phy_ops); + if (IS_ERR(phy)) { + dev_err(dev, "failed to create dp phy: %pOFn\n", child_np); + goto put_child; + } + + phy_set_bus_width(phy, udphy_dplane_get(udphy)); + phy->attrs.max_link_rate = udphy_dp_get_max_link_rate(udphy, child_np); + } else if (of_node_name_eq(child_np, "usb3-port")) { + phy = devm_phy_create(dev, child_np, &rockchip_u3phy_ops); + if (IS_ERR(phy)) { + dev_err(dev, "failed to create usb phy: %pOFn\n", child_np); + goto put_child; + } + } else + continue; + + phy_set_drvdata(phy, udphy); + } + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(phy_provider)) { + dev_err(dev, "failed to register phy provider\n"); + goto put_child; + } + + return 0; + +put_child: + of_node_put(child_np); + return ret; +} + +static int rk3588_udphy_refclk_set(struct rockchip_udphy *udphy) +{ + unsigned long rate; + int ret; + + /* configure phy reference clock */ + rate = clk_get_rate(udphy->refclk); + dev_dbg(udphy->dev, "refclk freq %ld\n", rate); + + switch (rate) { + case 24000000: + ret = regmap_multi_reg_write(udphy->pma_regmap, rk3588_udphy_24m_refclk_cfg, + ARRAY_SIZE(rk3588_udphy_24m_refclk_cfg)); + if (ret) + return ret; + break; + case 26000000: + /* register default is 26MHz */ + ret = regmap_multi_reg_write(udphy->pma_regmap, rk3588_udphy_26m_refclk_cfg, + ARRAY_SIZE(rk3588_udphy_26m_refclk_cfg)); + if (ret) + return ret; + break; + default: + dev_err(udphy->dev, "unsupported refclk freq %ld\n", rate); + return -EINVAL; + } + + return 0; +} + +static int rk3588_udphy_status_check(struct rockchip_udphy *udphy) +{ + unsigned int val; + int ret; + + /* LCPLL check */ + if (udphy->mode & UDPHY_MODE_USB) { + ret = regmap_read_poll_timeout(udphy->pma_regmap, CMN_ANA_LCPLL_DONE_OFFSET, + val, (val & CMN_ANA_LCPLL_AFC_DONE) && + (val & CMN_ANA_LCPLL_LOCK_DONE), 200, 100000); + if (ret) { + dev_err(udphy->dev, "cmn ana lcpll lock timeout\n"); + return ret; + } + + if (!udphy->flip) { + ret = regmap_read_poll_timeout(udphy->pma_regmap, + TRSV_LN0_MON_RX_CDR_DONE_OFFSET, val, + val & TRSV_LN0_MON_RX_CDR_LOCK_DONE, + 200, 100000); + if (ret) + dev_err(udphy->dev, "trsv ln0 mon rx cdr lock timeout\n"); + } else { + ret = regmap_read_poll_timeout(udphy->pma_regmap, + TRSV_LN2_MON_RX_CDR_DONE_OFFSET, val, + val & TRSV_LN2_MON_RX_CDR_LOCK_DONE, + 200, 100000); + if (ret) + dev_err(udphy->dev, "trsv ln2 mon rx cdr lock timeout\n"); + } + } + + return 0; +} + +static int rk3588_udphy_init(struct rockchip_udphy *udphy) +{ + const struct rockchip_udphy_cfg *cfg = udphy->cfgs; + int ret; + + /* enable rx lfps for usb */ + if (udphy->mode & UDPHY_MODE_USB) + grfreg_write(udphy->udphygrf, &cfg->grfcfg.rx_lfps, true); + + /* Step 1: power on pma and deassert apb rstn */ + grfreg_write(udphy->udphygrf, &cfg->grfcfg.low_pwrn, true); + + udphy_reset_deassert(udphy, "pma_apb"); + udphy_reset_deassert(udphy, "pcs_apb"); + + /* Step 2: set init sequence and phy refclk */ + ret = regmap_multi_reg_write(udphy->pma_regmap, rk3588_udphy_init_sequence, + ARRAY_SIZE(rk3588_udphy_init_sequence)); + if (ret) { + dev_err(udphy->dev, "init sequence set error %d\n", ret); + goto assert_apb; + } + + ret = rk3588_udphy_refclk_set(udphy); + if (ret) { + dev_err(udphy->dev, "refclk set error %d\n", ret); + goto assert_apb; + } + + /* Step 3: configure lane mux */ + regmap_update_bits(udphy->pma_regmap, CMN_LANE_MUX_AND_EN_OFFSET, + CMN_DP_LANE_MUX_ALL | CMN_DP_LANE_EN_ALL, + FIELD_PREP(CMN_DP_LANE_MUX_N(3), udphy->lane_mux_sel[3]) | + FIELD_PREP(CMN_DP_LANE_MUX_N(2), udphy->lane_mux_sel[2]) | + FIELD_PREP(CMN_DP_LANE_MUX_N(1), udphy->lane_mux_sel[1]) | + FIELD_PREP(CMN_DP_LANE_MUX_N(0), udphy->lane_mux_sel[0]) | + FIELD_PREP(CMN_DP_LANE_EN_ALL, 0)); + + /* Step 4: deassert init rstn and wait for 200ns from datasheet */ + if (udphy->mode & UDPHY_MODE_USB) + udphy_reset_deassert(udphy, "init"); + + if (udphy->mode & UDPHY_MODE_DP) { + regmap_update_bits(udphy->pma_regmap, CMN_DP_RSTN_OFFSET, + CMN_DP_INIT_RSTN, + FIELD_PREP(CMN_DP_INIT_RSTN, 0x1)); + } + + udelay(1); + + /* Step 5: deassert cmn/lane rstn */ + if (udphy->mode & UDPHY_MODE_USB) { + udphy_reset_deassert(udphy, "cmn"); + udphy_reset_deassert(udphy, "lane"); + } + + /* Step 6: wait for lock done of pll */ + ret = rk3588_udphy_status_check(udphy); + if (ret) + goto assert_phy; + + return 0; + +assert_phy: + udphy_reset_assert(udphy, "init"); + udphy_reset_assert(udphy, "cmn"); + udphy_reset_assert(udphy, "lane"); + +assert_apb: + udphy_reset_assert(udphy, "pma_apb"); + udphy_reset_assert(udphy, "pcs_apb"); + return ret; +} + +static int rk3588_udphy_hpd_event_trigger(struct rockchip_udphy *udphy, bool hpd) +{ + const struct rockchip_udphy_cfg *cfg = udphy->cfgs; + + udphy->dp_sink_hpd_sel = true; + udphy->dp_sink_hpd_cfg = hpd; + + grfreg_write(udphy->vogrf, &cfg->vogrfcfg[udphy->id].hpd_trigger, hpd); + + return 0; +} + +static int rk3588_udphy_dplane_enable(struct rockchip_udphy *udphy, int dp_lanes) +{ + int i; + u32 val = 0; + + for (i = 0; i < dp_lanes; i++) + val |= BIT(udphy->dp_lane_sel[i]); + + regmap_update_bits(udphy->pma_regmap, CMN_LANE_MUX_AND_EN_OFFSET, CMN_DP_LANE_EN_ALL, + FIELD_PREP(CMN_DP_LANE_EN_ALL, val)); + + if (!dp_lanes) + regmap_update_bits(udphy->pma_regmap, CMN_DP_RSTN_OFFSET, + CMN_DP_CMN_RSTN, FIELD_PREP(CMN_DP_CMN_RSTN, 0x0)); + + return 0; +} + +static int rk3588_udphy_dplane_select(struct rockchip_udphy *udphy) +{ + 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, udphy->id ? RK3588_GRF_VO0_CON2 : RK3588_GRF_VO0_CON0, + ((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); + + return 0; +} + +static int rk3588_dp_phy_set_rate(struct rockchip_udphy *udphy, + struct phy_configure_opts_dp *dp) +{ + u32 val; + int ret; + + regmap_update_bits(udphy->pma_regmap, CMN_DP_RSTN_OFFSET, + CMN_DP_CMN_RSTN, FIELD_PREP(CMN_DP_CMN_RSTN, 0x0)); + + switch (dp->link_rate) { + case 1620: + udphy->bw = DP_BW_RBR; + break; + case 2700: + udphy->bw = DP_BW_HBR; + break; + case 5400: + udphy->bw = DP_BW_HBR2; + break; + case 8100: + udphy->bw = DP_BW_HBR3; + break; + default: + return -EINVAL; + } + + regmap_update_bits(udphy->pma_regmap, CMN_DP_LINK_OFFSET, CMN_DP_TX_LINK_BW, + FIELD_PREP(CMN_DP_TX_LINK_BW, udphy->bw)); + regmap_update_bits(udphy->pma_regmap, CMN_SSC_EN_OFFSET, CMN_ROPLL_SSC_EN, + FIELD_PREP(CMN_ROPLL_SSC_EN, dp->ssc)); + regmap_update_bits(udphy->pma_regmap, CMN_DP_RSTN_OFFSET, CMN_DP_CMN_RSTN, + FIELD_PREP(CMN_DP_CMN_RSTN, 0x1)); + + ret = regmap_read_poll_timeout(udphy->pma_regmap, CMN_ANA_ROPLL_DONE_OFFSET, val, + FIELD_GET(CMN_ANA_ROPLL_LOCK_DONE, val) && + FIELD_GET(CMN_ANA_ROPLL_AFC_DONE, val), + 0, 1000); + if (ret) { + dev_err(udphy->dev, "ROPLL is not lock\n"); + return ret; + } + + return 0; +} + +static void rk3588_dp_phy_set_voltage(struct rockchip_udphy *udphy, u8 bw, + u32 voltage, u32 pre, u32 lane) +{ + u32 offset = 0x800 * lane; + u32 val; + const struct rockchip_udphy_cfg *cfg = udphy->cfgs; + const struct dp_tx_drv_ctrl (*dp_ctrl)[4]; + + dp_ctrl = udphy->mux ? cfg->dp_tx_ctrl_cfg_typec[bw] : cfg->dp_tx_ctrl_cfg[bw]; + val = dp_ctrl[voltage][pre].trsv_reg0204; + regmap_write(udphy->pma_regmap, 0x0810 + offset, val); + + val = dp_ctrl[voltage][pre].trsv_reg0205; + regmap_write(udphy->pma_regmap, 0x0814 + offset, val); + + val = dp_ctrl[voltage][pre].trsv_reg0206; + regmap_write(udphy->pma_regmap, 0x0818 + offset, val); + + val = dp_ctrl[voltage][pre].trsv_reg0207; + regmap_write(udphy->pma_regmap, 0x081c + offset, val); +} + +static int rk3588_dp_phy_set_voltages(struct rockchip_udphy *udphy, + struct phy_configure_opts_dp *dp) +{ + u32 i, lane; + + for (i = 0; i < dp->lanes; i++) { + lane = udphy->dp_lane_sel[i]; + switch (dp->link_rate) { + case 1620: + case 2700: + regmap_update_bits(udphy->pma_regmap, TRSV_ANA_TX_CLK_OFFSET_N(lane), + LN_ANA_TX_SER_TXCLK_INV, + FIELD_PREP(LN_ANA_TX_SER_TXCLK_INV, + udphy->lane_mux_sel[lane])); + break; + case 5400: + case 8100: + regmap_update_bits(udphy->pma_regmap, TRSV_ANA_TX_CLK_OFFSET_N(lane), + LN_ANA_TX_SER_TXCLK_INV, + FIELD_PREP(LN_ANA_TX_SER_TXCLK_INV, 0x0)); + break; + } + + rk3588_dp_phy_set_voltage(udphy, udphy->bw, dp->voltage[i], dp->pre[i], lane); + } + + return 0; +} + +static int __maybe_unused udphy_resume(struct device *dev) +{ + struct rockchip_udphy *udphy = dev_get_drvdata(dev); + const struct rockchip_udphy_cfg *cfg = udphy->cfgs; + + if (udphy->dp_sink_hpd_sel) + cfg->hpd_event_trigger(udphy, udphy->dp_sink_hpd_cfg); + + return 0; +} + +static const struct dev_pm_ops udphy_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(NULL, udphy_resume) +}; + +static const char * const rk3588_udphy_rst_l[] = { + "init", "cmn", "lane", "pcs_apb", "pma_apb" +}; + +static const struct rockchip_udphy_cfg rk3588_udphy_cfgs = { + .num_rsts = ARRAY_SIZE(rk3588_udphy_rst_l), + .rst_list = rk3588_udphy_rst_l, + .grfcfg = { + /* u2phy-grf */ + .bvalid_phy_con = { 0x0008, 1, 0, 0x2, 0x3 }, + .bvalid_grf_con = { 0x0010, 3, 2, 0x2, 0x3 }, + + /* usb-grf */ + .usb3otg0_cfg = { 0x001c, 15, 0, 0x1100, 0x0188 }, + .usb3otg1_cfg = { 0x0034, 15, 0, 0x1100, 0x0188 }, + + /* usbdpphy-grf */ + .low_pwrn = { 0x0004, 13, 13, 0, 1 }, + .rx_lfps = { 0x0004, 14, 14, 0, 1 }, + }, + .vogrfcfg = { + { + .hpd_trigger = { 0x0000, 11, 10, 1, 3 }, + }, + { + .hpd_trigger = { 0x0008, 11, 10, 1, 3 }, + }, + }, + .dp_tx_ctrl_cfg = { + rk3588_dp_tx_drv_ctrl_rbr_hbr, + rk3588_dp_tx_drv_ctrl_rbr_hbr, + rk3588_dp_tx_drv_ctrl_hbr2, + rk3588_dp_tx_drv_ctrl_hbr3, + }, + .dp_tx_ctrl_cfg_typec = { + rk3588_dp_tx_drv_ctrl_rbr_hbr_typec, + rk3588_dp_tx_drv_ctrl_rbr_hbr_typec, + rk3588_dp_tx_drv_ctrl_hbr2, + rk3588_dp_tx_drv_ctrl_hbr3, + }, + .combophy_init = rk3588_udphy_init, + .dp_phy_set_rate = rk3588_dp_phy_set_rate, + .dp_phy_set_voltages = rk3588_dp_phy_set_voltages, + .hpd_event_trigger = rk3588_udphy_hpd_event_trigger, + .dplane_enable = rk3588_udphy_dplane_enable, + .dplane_select = rk3588_udphy_dplane_select, +}; + +static const struct of_device_id rockchip_udphy_dt_match[] = { + { + .compatible = "rockchip,rk3588-usbdp-phy", + .data = &rk3588_udphy_cfgs + }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, rockchip_udphy_dt_match); + +static struct platform_driver rockchip_udphy_driver = { + .probe = rockchip_udphy_probe, + .driver = { + .name = "rockchip-usbdp-phy", + .of_match_table = rockchip_udphy_dt_match, + .pm = &udphy_pm_ops, + }, +}; + +module_platform_driver(rockchip_udphy_driver); + +MODULE_AUTHOR("Frank Wang "); +MODULE_AUTHOR("Zhang Yubing "); +MODULE_DESCRIPTION("Rockchip USBDP Combo PHY driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 058d5b853..a3ef7d703 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -6588,9 +6588,9 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) port->partner_desc.identity = &port->partner_ident; port->port_type = port->typec_caps.type; - port->role_sw = usb_role_switch_get(port->dev); + port->role_sw = fwnode_usb_role_switch_get(tcpc->fwnode); if (!port->role_sw) - port->role_sw = fwnode_usb_role_switch_get(tcpc->fwnode); + port->role_sw = usb_role_switch_get(port->dev); if (IS_ERR(port->role_sw)) { err = PTR_ERR(port->role_sw); goto out_destroy_wq; diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h index 6a46baa07..9042039f2 100644 --- a/include/drm/bridge/dw_hdmi.h +++ b/include/drm/bridge/dw_hdmi.h @@ -6,12 +6,14 @@ #ifndef __DW_HDMI__ #define __DW_HDMI__ +#include #include struct drm_display_info; struct drm_display_mode; struct drm_encoder; struct dw_hdmi; +struct dw_hdmi_qp; struct platform_device; /** @@ -92,6 +94,13 @@ enum dw_hdmi_phy_type { DW_HDMI_PHY_VENDOR_PHY = 0xfe, }; +struct dw_hdmi_audio_tmds_n { + unsigned long tmds; + unsigned int n_32k; + unsigned int n_44k1; + unsigned int n_48k; +}; + struct dw_hdmi_mpll_config { unsigned long mpixelclock; struct { @@ -112,6 +121,15 @@ struct dw_hdmi_phy_config { u16 vlev_ctr; /* voltage level control */ }; +struct dw_hdmi_link_config { + bool dsc_mode; + bool frl_mode; + int frl_lanes; + int rate_per_lane; + int hcactive; + u8 pps_payload[128]; +}; + struct dw_hdmi_phy_ops { int (*init)(struct dw_hdmi *hdmi, void *data, const struct drm_display_info *display, @@ -123,14 +141,52 @@ struct dw_hdmi_phy_ops { void (*setup_hpd)(struct dw_hdmi *hdmi, void *data); }; +struct dw_hdmi_qp_phy_ops { + int (*init)(struct dw_hdmi_qp *hdmi, void *data, + struct drm_display_mode *mode); + void (*disable)(struct dw_hdmi_qp *hdmi, void *data); + enum drm_connector_status (*read_hpd)(struct dw_hdmi_qp *hdmi, + void *data); + void (*update_hpd)(struct dw_hdmi_qp *hdmi, void *data, + bool force, bool disabled, bool rxsense); + void (*setup_hpd)(struct dw_hdmi_qp *hdmi, void *data); + void (*set_mode)(struct dw_hdmi_qp *dw_hdmi, void *data, + u32 mode_mask, bool enable); +}; + +struct dw_hdmi_property_ops { + void (*attach_properties)(struct drm_connector *connector, + unsigned int color, int version, + void *data); + void (*destroy_properties)(struct drm_connector *connector, + void *data); + int (*set_property)(struct drm_connector *connector, + struct drm_connector_state *state, + struct drm_property *property, + u64 val, + void *data); + int (*get_property)(struct drm_connector *connector, + const struct drm_connector_state *state, + struct drm_property *property, + u64 *val, + void *data); +}; + struct dw_hdmi_plat_data { struct regmap *regm; + //[CC:] not in dowstream unsigned int output_port; + unsigned long input_bus_format; unsigned long input_bus_encoding; + unsigned int max_tmdsclk; + int id; bool use_drm_infoframe; bool ycbcr_420_allowed; + bool unsupported_yuv_input; + bool unsupported_deep_color; + bool is_hdmi_qp; /* * Private data passed to all the .mode_valid() and .configure_phy() @@ -139,6 +195,7 @@ struct dw_hdmi_plat_data { void *priv_data; /* Platform-specific mode validation (optional). */ + //[CC:] downstream changed "struct dw_hdmi *hdmi" to "struct drm_connector *connector" enum drm_mode_status (*mode_valid)(struct dw_hdmi *hdmi, void *data, const struct drm_display_info *info, const struct drm_display_mode *mode); @@ -150,18 +207,51 @@ struct dw_hdmi_plat_data { /* Vendor PHY support */ const struct dw_hdmi_phy_ops *phy_ops; + const struct dw_hdmi_qp_phy_ops *qp_phy_ops; const char *phy_name; void *phy_data; unsigned int phy_force_vendor; + /* split mode */ + bool split_mode; + bool first_screen; + struct dw_hdmi_qp *left; + struct dw_hdmi_qp *right; + /* Synopsys PHY support */ const struct dw_hdmi_mpll_config *mpll_cfg; + const struct dw_hdmi_mpll_config *mpll_cfg_420; const struct dw_hdmi_curr_ctrl *cur_ctr; const struct dw_hdmi_phy_config *phy_config; int (*configure_phy)(struct dw_hdmi *hdmi, void *data, unsigned long mpixelclock); unsigned int disable_cec : 1; + + //[CC:] 7b29b5f29585 ("drm/rockchip: dw_hdmi: Support HDMI 2.0 YCbCr 4:2:0") + unsigned long (*get_input_bus_format)(void *data); + unsigned long (*get_output_bus_format)(void *data); + unsigned long (*get_enc_in_encoding)(void *data); + unsigned long (*get_enc_out_encoding)(void *data); + + unsigned long (*get_quant_range)(void *data); + struct drm_property *(*get_hdr_property)(void *data); + struct drm_property_blob *(*get_hdr_blob)(void *data); + bool (*get_color_changed)(void *data); + int (*get_yuv422_format)(struct drm_connector *connector, + struct edid *edid); + int (*get_edid_dsc_info)(void *data, struct edid *edid); + int (*get_next_hdr_data)(void *data, struct edid *edid, + struct drm_connector *connector); + struct dw_hdmi_link_config *(*get_link_cfg)(void *data); + void (*set_grf_cfg)(void *data); + void (*convert_to_split_mode)(struct drm_display_mode *mode); + void (*convert_to_origin_mode)(struct drm_display_mode *mode); + int (*dclk_set)(void *data, bool enable); + + /* Vendor Property support */ + const struct dw_hdmi_property_ops *property_ops; + struct drm_connector *connector; }; struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev, @@ -172,6 +262,7 @@ struct dw_hdmi *dw_hdmi_bind(struct platform_device *pdev, struct drm_encoder *encoder, const struct dw_hdmi_plat_data *plat_data); +void dw_hdmi_suspend(struct dw_hdmi *hdmi); void dw_hdmi_resume(struct dw_hdmi *hdmi); void dw_hdmi_setup_rx_sense(struct dw_hdmi *hdmi, bool hpd, bool rx_sense); @@ -205,6 +296,32 @@ enum drm_connector_status dw_hdmi_phy_read_hpd(struct dw_hdmi *hdmi, void dw_hdmi_phy_update_hpd(struct dw_hdmi *hdmi, void *data, bool force, bool disabled, bool rxsense); void dw_hdmi_phy_setup_hpd(struct dw_hdmi *hdmi, void *data); +void dw_hdmi_set_quant_range(struct dw_hdmi *hdmi); +void dw_hdmi_set_output_type(struct dw_hdmi *hdmi, u64 val); +bool dw_hdmi_get_output_whether_hdmi(struct dw_hdmi *hdmi); +int dw_hdmi_get_output_type_cap(struct dw_hdmi *hdmi); +//void dw_hdmi_set_cec_adap(struct dw_hdmi *hdmi, struct cec_adapter *adap); + +void dw_hdmi_qp_unbind(struct dw_hdmi_qp *hdmi); +struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev, + struct drm_encoder *encoder, + struct dw_hdmi_plat_data *plat_data); +void dw_hdmi_qp_suspend(struct device *dev, struct dw_hdmi_qp *hdmi); +void dw_hdmi_qp_resume(struct device *dev, struct dw_hdmi_qp *hdmi); +//void dw_hdmi_qp_cec_set_hpd(struct dw_hdmi_qp *hdmi, bool plug_in, bool change); +//void dw_hdmi_qp_set_cec_adap(struct dw_hdmi_qp *hdmi, struct cec_adapter *adap); +int dw_hdmi_qp_set_earc(struct dw_hdmi_qp *hdmi); +void dw_hdmi_qp_set_sample_rate(struct dw_hdmi_qp *hdmi, unsigned int rate); +void dw_hdmi_qp_set_channel_count(struct dw_hdmi_qp *hdmi, unsigned int cnt); +void dw_hdmi_qp_set_channel_status(struct dw_hdmi_qp *hdmi, u8 *channel_status, + bool ref2stream); +void dw_hdmi_qp_set_channel_allocation(struct dw_hdmi_qp *hdmi, unsigned int ca); +//void dw_hdmi_qp_set_audio_infoframe(struct dw_hdmi_qp *hdmi, +// struct hdmi_codec_params *hparms); +//void dw_hdmi_qp_audio_enable(struct dw_hdmi_qp *hdmi); +//void dw_hdmi_qp_audio_disable(struct dw_hdmi_qp *hdmi); +int dw_hdmi_qp_set_plugged_cb(struct dw_hdmi_qp *hdmi, hdmi_codec_plugged_cb fn, + struct device *codec_dev); bool dw_hdmi_bus_fmt_is_420(struct dw_hdmi *hdmi); diff --git a/include/dt-bindings/clock/rockchip,rk3588-cru.h b/include/dt-bindings/clock/rockchip,rk3588-cru.h index 5790b1391..50ba72980 100644 --- a/include/dt-bindings/clock/rockchip,rk3588-cru.h +++ b/include/dt-bindings/clock/rockchip,rk3588-cru.h @@ -733,8 +733,9 @@ #define ACLK_AV1_PRE 718 #define PCLK_AV1_PRE 719 #define HCLK_SDIO_PRE 720 +#define PCLK_VO1GRF 721 -#define CLK_NR_CLKS (HCLK_SDIO_PRE + 1) +#define CLK_NR_CLKS (PCLK_VO1GRF + 1) /* scmi-clocks indices */ diff --git a/include/dt-bindings/soc/rockchip,vop2.h b/include/dt-bindings/soc/rockchip,vop2.h index 6e66a802b..668f199df 100644 --- a/include/dt-bindings/soc/rockchip,vop2.h +++ b/include/dt-bindings/soc/rockchip,vop2.h @@ -10,5 +10,9 @@ #define ROCKCHIP_VOP2_EP_LVDS0 5 #define ROCKCHIP_VOP2_EP_MIPI1 6 #define ROCKCHIP_VOP2_EP_LVDS1 7 +#define ROCKCHIP_VOP2_EP_HDMI1 8 +#define ROCKCHIP_VOP2_EP_EDP1 9 +#define ROCKCHIP_VOP2_EP_DP0 10 +#define ROCKCHIP_VOP2_EP_DP1 11 #endif /* __DT_BINDINGS_ROCKCHIP_VOP2_H */ diff --git a/include/linux/math.h b/include/linux/math.h index dd4152711..f80bfb375 100644 --- a/include/linux/math.h +++ b/include/linux/math.h @@ -36,6 +36,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/uapi/drm/rockchip_drm.h b/include/uapi/drm/rockchip_drm.h new file mode 100644 index 000000000..246192fa2 --- /dev/null +++ b/include/uapi/drm/rockchip_drm.h @@ -0,0 +1,134 @@ +/* + * + * Copyright (c) Fuzhou Rockchip Electronics Co.Ltd + * Authors: + * Mark Yao + * + * base on exynos_drm.h + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef _UAPI_ROCKCHIP_DRM_H +#define _UAPI_ROCKCHIP_DRM_H + +#include +#include + +/* + * Send vcnt event instead of blocking, + * like _DRM_VBLANK_EVENT + */ +#define _DRM_ROCKCHIP_VCNT_EVENT 0x80000000 +#define DRM_EVENT_ROCKCHIP_CRTC_VCNT 0xf + +/* memory type definitions. */ +enum drm_rockchip_gem_mem_type { + /* Physically Continuous memory. */ + ROCKCHIP_BO_CONTIG = 1 << 0, + /* cachable mapping. */ + ROCKCHIP_BO_CACHABLE = 1 << 1, + /* write-combine mapping. */ + ROCKCHIP_BO_WC = 1 << 2, + ROCKCHIP_BO_SECURE = 1 << 3, + /* keep kmap for cma buffer or alloc kmap for other type memory */ + ROCKCHIP_BO_ALLOC_KMAP = 1 << 4, + ROCKCHIP_BO_MASK = ROCKCHIP_BO_CONTIG | ROCKCHIP_BO_CACHABLE | + ROCKCHIP_BO_WC | ROCKCHIP_BO_SECURE | ROCKCHIP_BO_ALLOC_KMAP, +}; + +/** + * User-desired buffer creation information structure. + * + * @size: user-desired memory allocation size. + * @flags: user request for setting memory type or cache attributes. + * @handle: returned a handle to created gem object. + * - this handle will be set by gem module of kernel side. + */ +struct drm_rockchip_gem_create { + uint64_t size; + uint32_t flags; + uint32_t handle; +}; + +struct drm_rockchip_gem_phys { + uint32_t handle; + uint32_t phy_addr; +}; + +/** + * A structure for getting buffer offset. + * + * @handle: a pointer to gem object created. + * @pad: just padding to be 64-bit aligned. + * @offset: relatived offset value of the memory region allocated. + * - this value should be set by user. + */ +struct drm_rockchip_gem_map_off { + uint32_t handle; + uint32_t pad; + uint64_t offset; +}; + +/* acquire type definitions. */ +enum drm_rockchip_gem_cpu_acquire_type { + DRM_ROCKCHIP_GEM_CPU_ACQUIRE_SHARED = 0x0, + DRM_ROCKCHIP_GEM_CPU_ACQUIRE_EXCLUSIVE = 0x1, +}; + +enum rockchip_crtc_feture { + ROCKCHIP_DRM_CRTC_FEATURE_ALPHA_SCALE, + ROCKCHIP_DRM_CRTC_FEATURE_HDR10, + ROCKCHIP_DRM_CRTC_FEATURE_NEXT_HDR, +}; + +enum rockchip_plane_feture { + ROCKCHIP_DRM_PLANE_FEATURE_SCALE, + ROCKCHIP_DRM_PLANE_FEATURE_ALPHA, + ROCKCHIP_DRM_PLANE_FEATURE_HDR2SDR, + ROCKCHIP_DRM_PLANE_FEATURE_SDR2HDR, + ROCKCHIP_DRM_PLANE_FEATURE_AFBDC, + ROCKCHIP_DRM_PLANE_FEATURE_PDAF_POS, + ROCKCHIP_DRM_PLANE_FEATURE_MAX, +}; + +enum rockchip_cabc_mode { + ROCKCHIP_DRM_CABC_MODE_DISABLE, + ROCKCHIP_DRM_CABC_MODE_NORMAL, + ROCKCHIP_DRM_CABC_MODE_LOWPOWER, + ROCKCHIP_DRM_CABC_MODE_USERSPACE, +}; + +struct drm_rockchip_vcnt_event { + struct drm_pending_event base; +}; + +#define DRM_ROCKCHIP_GEM_CREATE 0x00 +#define DRM_ROCKCHIP_GEM_MAP_OFFSET 0x01 +#define DRM_ROCKCHIP_GEM_CPU_ACQUIRE 0x02 +#define DRM_ROCKCHIP_GEM_CPU_RELEASE 0x03 +#define DRM_ROCKCHIP_GEM_GET_PHYS 0x04 +#define DRM_ROCKCHIP_GET_VCNT_EVENT 0x05 + +#define DRM_IOCTL_ROCKCHIP_GEM_CREATE DRM_IOWR(DRM_COMMAND_BASE + \ + DRM_ROCKCHIP_GEM_CREATE, struct drm_rockchip_gem_create) + +#define DRM_IOCTL_ROCKCHIP_GEM_MAP_OFFSET DRM_IOWR(DRM_COMMAND_BASE + \ + DRM_ROCKCHIP_GEM_MAP_OFFSET, struct drm_rockchip_gem_map_off) + +#define DRM_IOCTL_ROCKCHIP_GEM_CPU_ACQUIRE DRM_IOWR(DRM_COMMAND_BASE + \ + DRM_ROCKCHIP_GEM_CPU_ACQUIRE, struct drm_rockchip_gem_cpu_acquire) + +#define DRM_IOCTL_ROCKCHIP_GEM_CPU_RELEASE DRM_IOWR(DRM_COMMAND_BASE + \ + DRM_ROCKCHIP_GEM_CPU_RELEASE, struct drm_rockchip_gem_cpu_release) + +#define DRM_IOCTL_ROCKCHIP_GEM_GET_PHYS DRM_IOWR(DRM_COMMAND_BASE + \ + DRM_ROCKCHIP_GEM_GET_PHYS, struct drm_rockchip_gem_phys) + +#define DRM_IOCTL_ROCKCHIP_GET_VCNT_EVENT DRM_IOWR(DRM_COMMAND_BASE + \ + DRM_ROCKCHIP_GET_VCNT_EVENT, union drm_wait_vblank) + +#endif /* _UAPI_ROCKCHIP_DRM_H */