From 449e3f43505d79da70cde1f793046cc47abdb40e Mon Sep 17 00:00:00 2001 From: Akash Kumar Date: Mon, 24 Aug 2026 15:00:31 +0530 Subject: [PATCH 1/4] FROMLIST: arm64: dts: qcom: shikra: Add Support for usb0 and usb1 on shikra QLI Added changes in shikra-cqm-evk, shikra-cqs-evk, and shikra-iqs-evk which now use the upstream CYPD6129/CYPD6229 UCSI Type-C controller for USB role switching (via &i2c3/typec@40) instead of the previous PM4125-native typec block, while shikra.dtsi/shikra-evk.dtsi gain the base usb_1/usb_2 dwc3, USB HS/QMP PHY, and QUSB2 efuse-trim nodes needed to support them. All four affected board/SoC DT files were rebuilt with dtc (qcom/shikra-cqm-evk.dtb, qcom/shikra-cqs-evk.dtb, qcom/shikra-iqs-evk.dtb) with no warnings or errors. Diffstat (base..HEAD): .../devicetree/bindings/usb/cypress,cypd6129.yaml | 107 ++++++++ arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts | 94 ++++++- arch/arm64/boot/dts/qcom/shikra-cqm-som.dtsi | 13 +- arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts | 94 ++++++- arch/arm64/boot/dts/qcom/shikra-evk.dtsi | 12 + arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts | 124 ++++++++++ arch/arm64/boot/dts/qcom/shikra.dtsi | 274 ++++++++++++++----- drivers/usb/typec/ucsi/ucsi_ccg.c | 2 + 8 files changed, 600 insertions(+), 120 deletions(-) Link: https://lore.kernel.org/all/20260811-usb-shikra-v7-v7-0-753e928f37ae@oss.qualcomm.com/ https://lore.kernel.org/all/20260821-shikra-usb-dt-v7-apply-v2-0-628885fc1d0e@oss.qualcomm.com/ Signed-off-by: Akash Kumar --- .../bindings/usb/cypress,cypd6129.yaml | 107 +++++++ arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts | 93 +++++- arch/arm64/boot/dts/qcom/shikra-cqm-som.dtsi | 17 +- arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts | 98 ++++++- arch/arm64/boot/dts/qcom/shikra-evk.dtsi | 12 + arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts | 109 ++++++- arch/arm64/boot/dts/qcom/shikra.dtsi | 273 ++++++++++++------ drivers/usb/typec/ucsi/ucsi_ccg.c | 2 + 8 files changed, 590 insertions(+), 121 deletions(-) create mode 100644 Documentation/devicetree/bindings/usb/cypress,cypd6129.yaml diff --git a/Documentation/devicetree/bindings/usb/cypress,cypd6129.yaml b/Documentation/devicetree/bindings/usb/cypress,cypd6129.yaml new file mode 100644 index 000000000000..43e2c1902fd1 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/cypress,cypd6129.yaml @@ -0,0 +1,107 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/cypress,cypd6129.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Cypress cypd6129/cypd6229 Type-C Controller + +maintainers: + - Akash Kumar + +description: + The Cypress cypd6129 and cypd6229 are dual Type-C PD controllers that are + controlled via an I2C interface. + +properties: + compatible: + oneOf: + - const: cypress,cypd6129 + - items: + - const: cypress,cypd6229 + - const: cypress,cypd6129 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + pinctrl-0: true + pinctrl-1: true + + pinctrl-names: + minItems: 1 + items: + - const: default + - const: sleep + + wakeup-source: + description: enable IRQ remote wakeup, see power/wakeup-source.txt + type: boolean + +patternProperties: + '^connector@[01]$': + $ref: /schemas/connector/usb-connector.yaml# + required: + - reg + +required: + - compatible + - reg + - interrupts + +anyOf: + - required: + - connector@0 + - required: + - connector@1 + +additionalProperties: false + +examples: + - | + #include + i2c { + #address-cells = <1>; + #size-cells = <0>; + + typec@40 { + compatible = "cypress,cypd6129"; + reg = <0x40>; + interrupts-extended = <&tlmm 136 IRQ_TYPE_LEVEL_LOW>; + pinctrl-0 = <&usb0_intr_state>; + pinctrl-names = "default"; + wakeup-source; + + #address-cells = <1>; + #size-cells = <0>; + + connector@0 { + compatible = "usb-c-connector"; + reg = <0>; + label = "USB-C"; + data-role = "dual"; + power-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + endpoint { + remote-endpoint = <&usb_role_switch0>; + }; + }; + }; + }; + }; + }; diff --git a/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts b/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts index 01464ef826d9..004d58ce3446 100644 --- a/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts +++ b/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts @@ -488,12 +488,11 @@ status = "okay"; }; -&pm4125_hs_in { - remote-endpoint = <&usb_1_dwc3_hs>; -}; - -&pm4125_ss_in { - remote-endpoint = <&usb_qmpphy_out>; +&pm4125_l5 { + /* DSI VDDA - must be at NOM voltage for PHY PLL lock */ + regulator-min-microvolt = <1232000>; + regulator-max-microvolt = <1232000>; + regulator-allow-set-load; }; &qaif_cpu { @@ -524,6 +523,51 @@ status = "okay"; }; +&i2c3 { + status = "okay"; + + typec@40 { + compatible = "cypress,cypd6129"; + reg = <0x40>; + interrupts-extended = <&tlmm 136 IRQ_TYPE_LEVEL_LOW>; + pinctrl-0 = <&usb0_intr_state>; + pinctrl-names = "default"; + wakeup-source; + + #address-cells = <1>; + #size-cells = <0>; + + ccg_typec_con0: connector@0 { + compatible = "usb-c-connector"; + reg = <0>; + label = "USB-C"; + data-role = "dual"; + power-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + ucsi_ccg_port: endpoint { + remote-endpoint = <&usb_2_dwc3_hs>; + }; + }; + }; + }; + }; +}; + +&pm4125_hs_in { + remote-endpoint = <&usb_1_dwc3_hs>; +}; + +&pm4125_ss_in { + remote-endpoint = <&usb_qmpphy_out>; +}; + &sdhc_1 { vmmc-supply = <&pm4125_l20>; vqmmc-supply = <&pm4125_l14>; @@ -765,11 +809,24 @@ }; &usb_1 { - dr_mode = "otg"; + dr_mode = "peripheral"; status = "okay"; }; +&tlmm { + usb0_intr_state: usb0-intr-state { + pins = "gpio136"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; +}; + +&usb_1 { + /delete-property/ dr_mode; +}; + &usb_1_dwc3_hs { remote-endpoint = <&pm4125_hs_in>; }; @@ -782,9 +839,20 @@ status = "okay"; }; -&usb_qmpphy { - vdda-phy-supply = <&pm4125_l8>; +&usb_2 { + /delete-property/ dr_mode; + + port { + usb_2_dwc3_hs: endpoint { + remote-endpoint = <&ucsi_ccg_port>; + }; + }; +}; + +&usb_2_hsphy { + vdd-supply = <&pm4125_l12>; vdda-pll-supply = <&pm4125_l13>; + vdda-phy-dpdm-supply = <&pm4125_l21>; status = "okay"; }; @@ -804,6 +872,13 @@ remote-endpoint = <&pm4125_ss_in>; }; +&usb_qmpphy { + vdda-phy-supply = <&pm4125_l8>; + vdda-pll-supply = <&pm4125_l13>; + + status = "okay"; +}; + &vamacro { pinctrl-0 = <&dmic01_default>, <&dmic23_default>, <&swr_tx_clk>, <&swr_tx_data0>; pinctrl-names = "default"; diff --git a/arch/arm64/boot/dts/qcom/shikra-cqm-som.dtsi b/arch/arm64/boot/dts/qcom/shikra-cqm-som.dtsi index 1bf69cb7d105..fc564ab50aec 100644 --- a/arch/arm64/boot/dts/qcom/shikra-cqm-som.dtsi +++ b/arch/arm64/boot/dts/qcom/shikra-cqm-som.dtsi @@ -130,6 +130,10 @@ status = "okay"; }; +&pm4125_tz { + status = "okay"; +}; + &pm4125_typec { status = "okay"; @@ -143,18 +147,22 @@ typec-power-opmode = "default"; pd-disable; + vbus-supply = <&pm4125_vbus>; + ports { #address-cells = <1>; #size-cells = <0>; port@0 { reg = <0>; + pm4125_hs_in: endpoint { }; }; port@1 { reg = <1>; + pm4125_ss_in: endpoint { }; }; @@ -162,13 +170,12 @@ }; }; -&pm4125_tz { - status = "okay"; -}; - &pm4125_vbus { - regulator-min-microvolt = <4250000>; + regulator-min-microvolt = <5000000>; regulator-max-microvolt = <5000000>; + regulator-min-microamp = <500000>; + regulator-max-microamp = <500000>; + status = "okay"; }; diff --git a/arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts b/arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts index 5ea41a9f8241..0f016d993015 100644 --- a/arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts +++ b/arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts @@ -447,10 +447,6 @@ status = "okay"; }; -&pm4125_hs_in { - remote-endpoint = <&usb_1_dwc3_hs>; -}; - &pm4125_l5 { /* DSI VDDA - must be at NOM voltage for PHY PLL lock */ regulator-min-microvolt = <1232000>; @@ -458,10 +454,6 @@ regulator-allow-set-load; }; -&pm4125_ss_in { - remote-endpoint = <&usb_qmpphy_out>; -}; - &q6apmbedai { #address-cells = <1>; #size-cells = <0>; @@ -503,6 +495,51 @@ status = "okay"; }; +&i2c3 { + status = "okay"; + + typec@40 { + compatible = "cypress,cypd6129"; + reg = <0x40>; + interrupts-extended = <&tlmm 136 IRQ_TYPE_LEVEL_LOW>; + pinctrl-0 = <&usb0_intr_state>; + pinctrl-names = "default"; + wakeup-source; + + #address-cells = <1>; + #size-cells = <0>; + + ccg_typec_con0: connector@0 { + compatible = "usb-c-connector"; + reg = <0>; + label = "USB-C"; + data-role = "dual"; + power-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + ucsi_ccg_port: endpoint { + remote-endpoint = <&usb_2_dwc3_hs>; + }; + }; + }; + }; + }; +}; + +&pm4125_hs_in { + remote-endpoint = <&usb_1_dwc3_hs>; +}; + +&pm4125_ss_in { + remote-endpoint = <&usb_qmpphy_out>; +}; + &sdhc_1 { vmmc-supply = <&pm4125_l20>; vqmmc-supply = <&pm4125_l14>; @@ -752,11 +789,24 @@ }; &usb_1 { - dr_mode = "otg"; + dr_mode = "peripheral"; status = "okay"; }; +&tlmm { + usb0_intr_state: usb0-intr-state { + pins = "gpio136"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; +}; + +&usb_1 { + /delete-property/ dr_mode; +}; + &usb_1_dwc3_hs { remote-endpoint = <&pm4125_hs_in>; }; @@ -769,15 +819,22 @@ status = "okay"; }; -&usb_qmpphy { - vdda-phy-supply = <&pm4125_l8>; - vdda-pll-supply = <&pm4125_l13>; +&usb_2 { + /delete-property/ dr_mode; - status = "okay"; + port { + usb_2_dwc3_hs: endpoint { + remote-endpoint = <&ucsi_ccg_port>; + }; + }; }; -&usb_qmpphy_out { - remote-endpoint = <&pm4125_ss_in>; +&usb_2_hsphy { + vdd-supply = <&pm4125_l12>; + vdda-pll-supply = <&pm4125_l13>; + vdda-phy-dpdm-supply = <&pm4125_l21>; + + status = "okay"; }; &vamacro { @@ -802,3 +859,14 @@ status = "okay"; }; + +&usb_qmpphy_out { + remote-endpoint = <&pm4125_ss_in>; +}; + +&usb_qmpphy { + vdda-phy-supply = <&pm4125_l8>; + vdda-pll-supply = <&pm4125_l13>; + + status = "okay"; +}; diff --git a/arch/arm64/boot/dts/qcom/shikra-evk.dtsi b/arch/arm64/boot/dts/qcom/shikra-evk.dtsi index bd93e84c1425..bae300472676 100644 --- a/arch/arm64/boot/dts/qcom/shikra-evk.dtsi +++ b/arch/arm64/boot/dts/qcom/shikra-evk.dtsi @@ -47,3 +47,15 @@ max-speed = <3200000>; }; }; + +&usb_1 { + dr_mode = "peripheral"; + + status = "okay"; +}; + +&usb_2 { + dr_mode = "peripheral"; + + status = "okay"; +}; diff --git a/arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts b/arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts index 1f310b3f0773..21b6f2f93c97 100644 --- a/arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts +++ b/arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts @@ -600,6 +600,72 @@ status = "okay"; }; +&i2c3 { + status = "okay"; + + typec@40 { + compatible = "cypress,cypd6229", "cypress,cypd6129"; + reg = <0x40>; + interrupts-extended = <&tlmm 50 IRQ_TYPE_LEVEL_LOW>; + pinctrl-0 = <&usb0_intr_state>; + pinctrl-names = "default"; + wakeup-source; + + #address-cells = <1>; + #size-cells = <0>; + + ccg_typec_con0: connector@0 { + compatible = "usb-c-connector"; + reg = <0>; + label = "USB2-Type-C"; + data-role = "dual"; + power-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + ucsi_ccg_port0_hs: endpoint { + remote-endpoint = <&usb_2_dwc3_hs>; + }; + }; + }; + }; + + ccg_typec_con1: connector@1 { + compatible = "usb-c-connector"; + reg = <1>; + label = "USB3-Type-C"; + data-role = "dual"; + power-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + ucsi_ccg_port_1_hs: endpoint { + remote-endpoint = <&usb_1_dwc3_hs>; + }; + }; + + port@1 { + reg = <1>; + + ucsi_ccg_port_1_ss: endpoint { + remote-endpoint = <&usb_qmpphy_out>; + }; + }; + }; + }; + }; +}; + &sdhc_1 { vmmc-supply = <&pm8150_l17>; vqmmc-supply = <&pm8150_s4>; @@ -787,12 +853,25 @@ }; }; +&tlmm { + usb0_intr_state: usb0-intr-state { + pins = "gpio50"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; +}; + &usb_1 { - dr_mode = "peripheral"; + /delete-property/ dr_mode; status = "okay"; }; +&usb_1_dwc3_hs { + remote-endpoint = <&ucsi_ccg_port_1_hs>; +}; + &usb_1_hsphy { vdd-supply = <&pm8150_l4>; vdda-pll-supply = <&pm8150_l12>; @@ -801,9 +880,22 @@ status = "okay"; }; -&usb_qmpphy { - vdda-phy-supply = <&pm8150_l6>; +&usb_2 { + /delete-property/ dr_mode; + + status = "okay"; + + port { + usb_2_dwc3_hs: endpoint { + remote-endpoint = <&ucsi_ccg_port0_hs>; + }; + }; +}; + +&usb_2_hsphy { + vdd-supply = <&pm8150_l4>; vdda-pll-supply = <&pm8150_l12>; + vdda-phy-dpdm-supply = <&pm8150_l13>; status = "okay"; }; @@ -873,3 +965,14 @@ output-high; }; }; + +&usb_qmpphy { + vdda-phy-supply = <&pm8150_l6>; + vdda-pll-supply = <&pm8150_l12>; + + status = "okay"; +}; + +&usb_qmpphy_out { + remote-endpoint = <&ucsi_ccg_port_1_ss>; +}; diff --git a/arch/arm64/boot/dts/qcom/shikra.dtsi b/arch/arm64/boot/dts/qcom/shikra.dtsi index 4ed5710948f3..c9983473d469 100644 --- a/arch/arm64/boot/dts/qcom/shikra.dtsi +++ b/arch/arm64/boot/dts/qcom/shikra.dtsi @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -1331,30 +1332,30 @@ }; usb_qmpphy: phy@1615000 { - compatible = "qcom,shikra-qmp-usb3-phy"; - reg = <0x0 0x01615000 0x0 0x1000>; + compatible = "qcom,shikra-qmp-usb3-dp-phy"; + reg = <0x0 0x01615000 0x0 0x2000>; - clocks = <&gcc GCC_AHB2PHY_USB_CLK>, + clocks = <&gcc GCC_USB3_PRIM_PHY_COM_AUX_CLK>, <&gcc GCC_USB3_PRIM_CLKREF_EN>, - <&gcc GCC_USB3_PRIM_PHY_COM_AUX_CLK>, + <&gcc GCC_AHB2PHY_USB_CLK>, <&gcc GCC_USB3_PRIM_PHY_PIPE_CLK>; - clock-names = "cfg_ahb", + clock-names = "aux", "ref", - "com_aux", + "cfg_ahb", "pipe"; - resets = <&gcc GCC_USB3_PHY_PRIM_SP0_BCR>, - <&gcc GCC_USB3PHY_PHY_PRIM_SP0_BCR>; - reset-names = "phy", - "phy_phy"; + resets = <&gcc GCC_USB3PHY_PHY_PRIM_SP0_BCR>, + <&gcc GCC_USB3_DP_PHY_PRIM_BCR>, + <&gcc GCC_USB3_PHY_PRIM_SP0_BCR>; + reset-names = "phy_phy", + "dp_phy", + "phy"; - #clock-cells = <0>; - clock-output-names = "usb3_phy_pipe_clk_src"; - - #phy-cells = <0>; + #clock-cells = <1>; + #phy-cells = <1>; orientation-switch; - qcom,tcsr-reg = <&tcsr_regs 0xb244>; + qcom,tcsr-reg = <&tcsr_regs 0xb244 0xb248>; status = "disabled"; @@ -1379,6 +1380,21 @@ }; }; + usb_2_hsphy: phy@1617000 { + compatible = "qcom,shikra-qusb2-phy"; + reg = <0x0 0x01617000 0x0 0x180>; + + clocks = <&gcc GCC_AHB2PHY_USB_CLK>, + <&rpmcc RPM_SMD_XO_CLK_SRC>; + clock-names = "cfg_ahb", "ref"; + + resets = <&gcc GCC_QUSB2PHY_SEC_BCR>; + nvmem-cells = <&qusb2_hstx_trim_2>; + #phy-cells = <0>; + + status = "disabled"; + }; + system_noc: interconnect@1880000 { compatible = "qcom,shikra-sys-noc"; reg = <0x0 0x01880000 0x0 0x6a080>; @@ -1460,6 +1476,11 @@ #address-cells = <1>; #size-cells = <1>; + qusb2_hstx_trim_2: hstx-trim@25a { + reg = <0x25a 0x1>; + bits = <4 4>; + }; + qusb2_hstx_trim_1: hstx-trim@25b { reg = <0x25b 0x1>; bits = <1 4>; @@ -1797,79 +1818,6 @@ }; }; - usb_1: usb@4e00000 { - compatible = "qcom,shikra-dwc3", "qcom,snps-dwc3"; - reg = <0x0 0x04e00000 0x0 0xfc100>; - - clocks = <&gcc GCC_CFG_NOC_USB3_PRIM_AXI_CLK>, - <&gcc GCC_USB30_PRIM_MASTER_CLK>, - <&gcc GCC_SYS_NOC_USB3_PRIM_AXI_CLK>, - <&gcc GCC_USB30_PRIM_SLEEP_CLK>, - <&gcc GCC_USB30_PRIM_MOCK_UTMI_CLK>, - <&gcc GCC_USB3_PRIM_CLKREF_EN>; - clock-names = "cfg_noc", - "core", - "iface", - "sleep", - "mock_utmi", - "xo"; - - assigned-clocks = <&gcc GCC_USB30_PRIM_MOCK_UTMI_CLK>, - <&gcc GCC_USB30_PRIM_MASTER_CLK>; - assigned-clock-rates = <19200000>, <133333333>; - - interrupts-extended = <&intc GIC_SPI 255 IRQ_TYPE_LEVEL_HIGH 0>, - <&intc GIC_SPI 302 IRQ_TYPE_LEVEL_HIGH 0>, - <&intc GIC_SPI 260 IRQ_TYPE_LEVEL_HIGH 0>, - <&intc GIC_SPI 422 IRQ_TYPE_LEVEL_HIGH 0>; - interrupt-names = "dwc_usb3", - "pwr_event", - "qusb2_phy", - "ss_phy_irq"; - - iommus = <&apps_smmu 0x120 0x0>; - - phys = <&usb_1_hsphy>, <&usb_qmpphy>; - phy-names = "usb2-phy", "usb3-phy"; - - power-domains = <&gcc GCC_USB30_PRIM_GDSC>; - - resets = <&gcc GCC_USB30_PRIM_BCR>; - - snps,dis_u2_susphy_quirk; - snps,dis_enblslpm_quirk; - snps,has-lpm-erratum; - snps,hird-threshold = /bits/ 8 <0x10>; - snps,usb3_lpm_capable; - snps,parkmode-disable-ss-quirk; - - usb-role-switch; - - wakeup-source; - - status = "disabled"; - - ports { - #address-cells = <1>; - #size-cells = <0>; - - port@0 { - reg = <0>; - - usb_1_dwc3_hs: endpoint { - }; - }; - - port@1 { - reg = <1>; - - usb_1_dwc3_ss: endpoint { - remote-endpoint = <&usb_qmpphy_usb_ss_in>; - }; - }; - }; - }; - gpu: gpu@5900000 { compatible = "qcom,adreno-07000400", "qcom,adreno"; reg = <0x0 0x05900000 0x0 0x40000>, @@ -2990,7 +2938,6 @@ status = "disabled"; }; - i2c2: i2c@4a88000 { compatible = "qcom,geni-i2c"; reg = <0x0 0x4a88000 0x0 0x4000>; @@ -4979,6 +4926,154 @@ }; }; + usb_2: usb@4c00000 { + compatible = "qcom,shikra-dwc3", "qcom,snps-dwc3"; + reg = <0x0 0x04c00000 0x0 0xfc100>; + + clocks = <&gcc GCC_CFG_NOC_USB2_PRIM_AXI_CLK>, + <&gcc GCC_USB20_MASTER_CLK>, + <&gcc GCC_SYS_NOC_USB2_PRIM_AXI_CLK>, + <&gcc GCC_USB20_SLEEP_CLK>, + <&gcc GCC_USB20_MOCK_UTMI_CLK>; + clock-names = "cfg_noc", + "core", + "iface", + "sleep", + "mock_utmi"; + + assigned-clocks = <&gcc GCC_USB20_MOCK_UTMI_CLK>, + <&gcc GCC_USB20_MASTER_CLK>; + assigned-clock-rates = <19200000>, <133333333>; + + interrupts-extended = <&intc GIC_SPI 507 IRQ_TYPE_LEVEL_HIGH 0>, + <&intc GIC_SPI 509 IRQ_TYPE_LEVEL_HIGH 0>, + <&intc GIC_SPI 508 IRQ_TYPE_LEVEL_HIGH 0>, + <&mpm 59 IRQ_TYPE_LEVEL_HIGH>, + <&mpm 58 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "dwc_usb3", + "pwr_event", + "hs_phy_irq", + "dp_hs_phy_irq", + "dm_hs_phy_irq"; + + iommus = <&apps_smmu 0x140 0x0>; + + maximum-speed = "high-speed"; + + phys = <&usb_2_hsphy>; + phy-names = "usb2-phy"; + + power-domains = <&gcc GCC_USB20_GDSC>; + + qcom,select-utmi-as-pipe-clk; + resets = <&gcc GCC_USB20_BCR>; + + interconnects = <&system_noc MASTER_USB2_0 RPM_ALWAYS_TAG + &mc_virt SLAVE_EBI_CH0 RPM_ALWAYS_TAG>, + <&mem_noc MASTER_AMPSS_M0 RPM_ACTIVE_TAG + &config_noc SLAVE_USB2 RPM_ACTIVE_TAG>; + interconnect-names = "usb-ddr", "apps-usb"; + + snps,dis_u2_susphy_quirk; + snps,dis_enblslpm_quirk; + snps,has-lpm-erratum; + snps,hird-threshold = /bits/ 8 <0x10>; + + usb-role-switch; + wakeup-source; + + status = "disabled"; + + port { + usb_2_dwc3_hs: endpoint { + }; + }; + }; + + usb_1: usb@4e00000 { + compatible = "qcom,shikra-dwc3", "qcom,snps-dwc3"; + reg = <0x0 0x04e00000 0x0 0xfc100>; + + clocks = <&gcc GCC_CFG_NOC_USB3_PRIM_AXI_CLK>, + <&gcc GCC_USB30_PRIM_MASTER_CLK>, + <&gcc GCC_SYS_NOC_USB3_PRIM_AXI_CLK>, + <&gcc GCC_USB30_PRIM_SLEEP_CLK>, + <&gcc GCC_USB30_PRIM_MOCK_UTMI_CLK>; + clock-names = "cfg_noc", + "core", + "iface", + "sleep", + "mock_utmi"; + + assigned-clocks = <&gcc GCC_USB30_PRIM_MOCK_UTMI_CLK>, + <&gcc GCC_USB30_PRIM_MASTER_CLK>; + assigned-clock-rates = <19200000>, <133333333>; + + interrupts-extended = <&intc GIC_SPI 255 IRQ_TYPE_LEVEL_HIGH 0>, + <&intc GIC_SPI 302 IRQ_TYPE_LEVEL_HIGH 0>, + <&intc GIC_SPI 254 IRQ_TYPE_LEVEL_HIGH 0>, + <&mpm 91 IRQ_TYPE_LEVEL_HIGH>, + <&mpm 90 IRQ_TYPE_LEVEL_HIGH>, + <&mpm 12 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "dwc_usb3", + "pwr_event", + "hs_phy_irq", + "dp_hs_phy_irq", + "dm_hs_phy_irq", + "ss_phy_irq"; + + iommus = <&apps_smmu 0x120 0x0>; + + phys = <&usb_1_hsphy>, <&usb_qmpphy QMP_USB43DP_USB3_PHY>; + phy-names = "usb2-phy", "usb3-phy"; + + power-domains = <&gcc GCC_USB30_PRIM_GDSC>; + + resets = <&gcc GCC_USB30_PRIM_BCR>; + + interconnects = <&system_noc MASTER_USB3 RPM_ALWAYS_TAG + &mc_virt SLAVE_EBI_CH0 RPM_ALWAYS_TAG>, + <&mem_noc MASTER_AMPSS_M0 RPM_ACTIVE_TAG + &config_noc SLAVE_USB3 RPM_ACTIVE_TAG>; + interconnect-names = "usb-ddr", "apps-usb"; + + snps,dis-u1-entry-quirk; + snps,dis-u2-entry-quirk; + snps,dis_u2_susphy_quirk; + snps,dis_u3_susphy_quirk; + snps,dis_enblslpm_quirk; + snps,has-lpm-erratum; + snps,hird-threshold = /bits/ 8 <0x10>; + snps,usb3_lpm_capable; + snps,parkmode-disable-ss-quirk; + + usb-role-switch; + + wakeup-source; + + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + usb_1_dwc3_hs: endpoint { + }; + }; + + port@1 { + reg = <1>; + + usb_1_dwc3_ss: endpoint { + remote-endpoint = <&usb_qmpphy_usb_ss_in>; + }; + }; + }; + }; + remoteproc_lpaicp: remoteproc@b800000 { compatible = "qcom,shikra-lpaicp-pas"; reg = <0x0 0x0b800000 0x0 0x200000>; diff --git a/drivers/usb/typec/ucsi/ucsi_ccg.c b/drivers/usb/typec/ucsi/ucsi_ccg.c index d46ca942026e..37b96d662e59 100644 --- a/drivers/usb/typec/ucsi/ucsi_ccg.c +++ b/drivers/usb/typec/ucsi/ucsi_ccg.c @@ -1527,6 +1527,8 @@ static void ucsi_ccg_remove(struct i2c_client *client) static const struct of_device_id ucsi_ccg_of_match_table[] = { { .compatible = "cypress,cypd4226", }, + { .compatible = "cypress,cypd6129", }, + { .compatible = "cypress,cypd6229", }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, ucsi_ccg_of_match_table); From dd35a9905dab0f2cf5cfeb4b9b9b3991daf161b3 Mon Sep 17 00:00:00 2001 From: Akash Kumar Date: Wed, 9 Sep 2026 12:02:34 +0530 Subject: [PATCH 2/4] FROMLIST: pinctrl: qcom: shikra: Set intr_target_width for GPIO pingroups The Shikra GPIO PINGROUP macro configures intr_target_bit but never sets intr_target_width, leaving it at its zero-initialized default. Set it to 4 to match the width of the interrupt target field. Signed-off-by: Akash Kumar Signed-off-by: Akash Kumar --- drivers/pinctrl/qcom/pinctrl-shikra.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pinctrl/qcom/pinctrl-shikra.c b/drivers/pinctrl/qcom/pinctrl-shikra.c index 0fc98369948c..55aec2f675e6 100644 --- a/drivers/pinctrl/qcom/pinctrl-shikra.c +++ b/drivers/pinctrl/qcom/pinctrl-shikra.c @@ -44,6 +44,7 @@ .intr_status_bit = 0, \ .intr_wakeup_enable_bit = 7, \ .intr_wakeup_present_bit = 6, \ + .intr_target_width = 4, \ .intr_target_bit = 8, \ .intr_target_kpss_val = 3, \ .intr_raw_status_bit = 4, \ From a6f67470bbf240ce8c8e395bb685679069bae6d5 Mon Sep 17 00:00:00 2001 From: Akash Kumar Date: Thu, 10 Sep 2026 22:41:12 +0530 Subject: [PATCH 3/4] FROMLIST: ccg firmware update support for cyacd2 Add .cyacd2 firmware flashing support to the CCG UCSI driver, and trigger it automatically from probe when the device reports no usable firmware version. Parsing and flashing: - Parse both .cyacd2 and .cyacd firmware text formats (ccg_parse_cyacd2_text, ccg_parse_cyacd_text), auto-detecting the format from file content (ccg_content_is_cyacd2_text), and convert relative/bank-based rows into absolute flash row addresses (ccg_rows_to_absolute, ccg_parse_and_build_rows). - Add HPIv2 register access helpers (ccg_hpi_read16/write16, ccg_i2c_read, ccg_read_u16/u32) and flash primitives (ccg_cmd_write_flash_row, ccg_cmd_read_flash_row, ccg_cmd_validate_fw, ccg_cmd_reset, ccg_cmd_jump_boot_mode, ccg_enter_flashing_robust) needed to write firmware rows and validate/reset the device afterwards. - Detect hybrid flash layout and derive per-bank row ranges (ccg_is_hybrid, ccg_row_size_from_device_mode, remap_rows_to_bank), and add response/async-event draining and wait helpers (ccg_wait_success, ccg_wait_async_event, ccg_drain_responses, ccg_disable_pd_ports) to keep the flashing sequence in sync with the device. - Rework do_flash() to request the firmware image, parse it into absolute rows, enter flashing mode, write and validate the image, and reset the device, with diagnostic logging of parsed rows and readback for bring-up. - Add a "ccg_fw_file" module parameter to override the firmware file name used for flashing, for bring-up/testing. Probe-time update: - When get_fw_info() reports an empty/zero uc->fw_version at probe time, call ccg_fw_update_needed()/ccg_fw_update() to flash the device before creating/registering the UCSI interface, instead of requiring a manual "echo 1 > do_flash" after boot. - Re-run ucsi_ccg_init() and get_fw_info() after a probe-time flash since do_flash() resets the device, so the correct post-flash firmware version is used for the rest of probe. Existing behavior kept: - The "do_flash" sysfs attribute (do_flash_store -> ccg_update_firmware work item) is unchanged and still available to trigger a flash after the driver has loaded. Signed-off-by: Akash Kumar --- drivers/usb/typec/ucsi/ucsi_ccg.c | 2770 +++++++++++++++++++++-------- 1 file changed, 1985 insertions(+), 785 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi_ccg.c b/drivers/usb/typec/ucsi/ucsi_ccg.c index 37b96d662e59..e0028a0f6ed8 100644 --- a/drivers/usb/typec/ucsi/ucsi_ccg.c +++ b/drivers/usb/typec/ucsi/ucsi_ccg.c @@ -18,6 +18,13 @@ #include #include #include +#include +#include +#include +#include +#include /* min_t, simple_strtoul */ +#include /* BIT */ +#include /* isspace */ #include #include "ucsi.h" @@ -66,6 +73,103 @@ enum enum_fw_mode { #define CCGX_RAB_RESPONSE 0x007E #define ASYNC_EVENT BIT(7) +/* HPIv2 core addresses */ +#define HPI_ADDR_ENTER_FLASH 0x000A +#define HPI_ADDR_FLASH_RW_CMD 0x000C +#define HPI_ADDR_RESPONSE 0x007E +#define HPI_ADDR_FLASH_RW_MEM 0x0200 +#define CCG_DEV_MODE_FWMODE_MASK 0x03 +#define CCG_DEV_MODE_BOOT 0x00 +#define CCG_DEV_MODE_FW1 0x01 +#define CCG_DEV_MODE_FW2 0x02 + +/* Response codes (Device) */ +#define HPI_RSP_NONE 0x00 +#define HPI_RSP_SUCCESS 0x02 +#define HPI_RSP_FLASH_DATA_AVAIL 0x03 +#define HPI_RSP_INVALID_CMD 0x05 +#define HPI_RSP_INVALID_STATE 0x06 +#define HPI_RSP_FLASH_UPDATE_FAIL 0x07 +#define HPI_RSP_INVALID_FW 0x08 +#define HPI_RSP_INVALID_ARGS 0x09 +#define HPI_RSP_NOT_SUPPORTED 0x0A +#define HPI_RSP_UNDEFINED_ERR 0x0F + +/* INTR_REG bits */ +#define INTR_DEV_INTR BIT(0) + +/* HPI register addresses used to refine write filtering */ +#define HPI_ADDR_BOOT_LOADER_LAST_ROW 0x0004 +#define HPI_ADDR_FIRMWARE_BIN_LOCATION 0x0028 +/* HPIv2 device-specific registers (double-byte addressed) */ +#define HPI_ADDR_HPI_VERSION 0x003C /* 4 bytes: bit31 = Hybrid architecture */ +#define HPI_ADDR_HPI_VERSION_EXT 0x0034 /* 4 bytes: variant info (optional) */ + +/* Flash row size and layout for CCG6DF_CFP/CCG6SF_CFP */ +#define CCG_ROWS_TOTAL 512 +#define CCG_ROW_SIZE 256 +/* Metadata row indexes (not addresses) */ +#define META_IDX_FW1 0x01FF /* 511 */ +#define META_IDX_FW2 0x01FE// /* 512 */ + +/* CFP device constants (aliases) */ +#define CCG_MD_FW1_IDX META_IDX_FW1 +#define CCG_MD_FW2_IDX META_IDX_FW2 + +/* Device information and control */ +#define HPI_ADDR_DEVICE_MODE 0x0000 /* DEVICE_MODE: 1 byte */ +#define HPI_ADDR_BOOT_MODE_REASON 0x0001 /* BOOT_MODE_REASON: 1 byte */ +#define HPI_ADDR_READ_SILICON_ID 0x0002 /* READ_SILICON_ID: 2 bytes */ +#define HPI_ADDR_BOOT_LOADER_LAST_ROW 0x0004 /* BOOT_LOADER_LAST_ROW: 2 bytes */ +#define HPI_ADDR_INTR_REG 0x0006 /* INTR_REG: 1 byte */ +#define HPI_ADDR_JUMP_TO_BOOT 0x0007 /* JUMP_TO_BOOT/JUMP_TO_ALT_FW: 1 byte */ +#define HPI_ADDR_RESET 0x0008 /* RESET: 2 bytes */ +#define HPI_ADDR_ENTER_FLASHING_MODE 0x000A /* ENTER_FLASHING_MODE: 1 byte */ +#define HPI_ADDR_VALIDATE_FW 0x000B /* VALIDATE_FW: 1 byte */ +#define HPI_ADDR_FLASH_ROW_RW 0x000C /* FLASH_ROW_READ_WRITE: 4 bytes */ + +/* Versions and layout */ +#define HPI_ADDR_SLEEP_CTRL 0x002D /* SLEEP_CTRL: 1 byte */ +#define HPI_ADDR_POWER_STAT 0x002E /* POWER_STAT: 1 byte */ + +/* Flash row read/write buffer (HPIv2 dedicated region) */ +#define HPI_ADDR_FLASH_RW_MEM_BASE 0x0200 /* 0x0200–0x02FF used for one flash row */ +#define HPI_FLASH_RW_MEM_SIZE 256 /* 256 bytes window (covers row size variants) */ + +#define HPI_SIG_JUMP_TO_BOOT 'J' /* Write to HPI_ADDR_JUMP_TO_BOOT */ +#define HPI_SIG_JUMP_TO_ALT_FW 'A' /* HPIv2 only (same register) */ +#define HPI_SIG_RESET 'R' /* Byte 0 at HPI_ADDR_RESET */ +#define HPI_SIG_ENTER_FLASHING 'P' /* HPI_ADDR_ENTER_FLASHING_MODE */ +#define HPI_SIG_FLASH_RW 'F' /* Byte 0 at HPI_ADDR_FLASH_ROW_RW */ + +/* RESET types (Byte[1] to HPI_ADDR_RESET) */ +#define HPI_RESET_TYPE_I2C 0x00 +#define HPI_RESET_TYPE_DEVICE 0x01 + +/* FLASH_ROW_READ_WRITE commands (Byte[1] to HPI_ADDR_FLASH_ROW_RW) */ +#define HPI_FLASH_CMD_WRITE 0x01 + +/* PDPORT_ENABLE bitmask */ +#define HPI_PDPORT_EN_PORT0 0x01 +#define HPI_PDPORT_EN_PORT1 0x02 + +#define HPI_RSP_FW_INVALID 0x08 +#define HPI_RSP_INVALID_ARGUMENT 0x09 + +#define HPI_ADDR_PDPORT_ENABLE 0x002C +#define HPI_ADDR_FLASH_ROW_RW 0x000C +#define HPI_SIG_FLASH_RW 'F' +/* dm is HPI_ADDR_DEVICE_MODE byte */ +#define HPI_DM_HPI_VERSION(dm) (((dm) >> 7) & 0x01) /* 0=HPIv1, 1=HPIv2 */ +#define HPI_DM_ROW_SIZE(dm) (((dm) >> 4) & 0x03) /* 0=128, 1=256, 3=64 */ +#define HPI_DM_NUM_PORTS(dm) (((dm) >> 2) & 0x03) /* 0=1 port, 1=2 ports */ +#define HPI_DM_FW_MODE(dm) ((dm) & 0x03) /* 0=Boot, 1=FW1, 2=FW2 */ + +/* Optional module parameter to override firmware file for flashing */ +static char fw_file_override[128]; +module_param_string(ccg_fw_file, fw_file_override, sizeof(fw_file_override), 0644); +MODULE_PARM_DESC(ccg_fw_file, "Override CCG firmware file to flash (supports .cyacd or .cyacd2)"); + /* CCGx events & async msg codes */ #define RESET_COMPLETE 0x80 #define EVENT_INDEX RESET_COMPLETE @@ -80,6 +184,19 @@ enum enum_fw_mode { #define FW2_METADATA_ROW 0x1FE #define FW_CFG_TABLE_SIG_SIZE 256 +/* Simple container for a flash row */ +struct ccg_row { + u16 row; + u16 len; + u8 data[CCG_ROW_SIZE]; +}; + +struct ccg_row_list { + struct ccg_row *rows; + int count; + int capacity; +}; + static int secondary_fw_min_ver = 41; enum enum_flash_mode { @@ -233,6 +350,22 @@ struct ucsi_ccg { */ spinlock_t op_lock; struct op_region op_data; + bool force_once; + bool updating; + u64 last_cmd_sent; +}; + +/* Parsed row container for cyacd/cyacd2 text */ +struct ccg_row_text { + u16 row_rel; /* .cyacd2: relative row; .cyacd: absolute placed here */ + u16 bank; /* .cyacd2: bank; .cyacd: 0 */ + u8 data[CCG_ROW_SIZE]; +}; + +struct ccg_row_text_list { + struct ccg_row_text *rows; + int count; + int capacity; }; static int ccg_read(struct ucsi_ccg *uc, u16 rab, u8 *data, u32 len) @@ -280,6 +413,32 @@ static int ccg_read(struct ucsi_ccg *uc, u16 rab, u8 *data, u32 len) return 0; } +/* Minimal I2C helpers for 16-bit HPI addressing */ +static int ccg_hpi_write16(struct ucsi_ccg *uc, u16 addr, const u8 *buf, size_t len) +{ + struct i2c_msg msg; + int ret; + u8 stack_buf[2 + CCG_ROW_SIZE]; + u8 *wbuf; + + if (len > sizeof(stack_buf) - 2) + return -EINVAL; + + wbuf = stack_buf; + wbuf[0] = (u8)(addr & 0xFF); /* LSB first */ + wbuf[1] = (u8)((addr >> 8) & 0xFF); /* MSB */ + if (buf && len) + memcpy(&wbuf[2], buf, len); + + msg.addr = uc->client->addr; + msg.flags = 0; + msg.len = 2 + len; + msg.buf = wbuf; + + ret = i2c_transfer(uc->client->adapter, &msg, 1); + return (ret == 1) ? 0 : (ret < 0 ? ret : -EIO); +} + static int ccg_write(struct ucsi_ccg *uc, u16 rab, const u8 *data, u32 len) { struct i2c_client *client = uc->client; @@ -316,796 +475,1671 @@ static int ccg_write(struct ucsi_ccg *uc, u16 rab, const u8 *data, u32 len) return 0; } -static int ccg_op_region_update(struct ucsi_ccg *uc, u32 cci) +static int ccg_hpi_read16(struct ucsi_ccg *uc, u16 addr, u8 *buf, size_t len) { - u16 reg = CCGX_RAB_UCSI_DATA_BLOCK(UCSI_MESSAGE_IN); - struct op_region *data = &uc->op_data; - unsigned char *buf; - size_t size = sizeof(data->message_in); + struct i2c_msg msgs[2]; + int ret; + u8 addr_bytes[2] = { (u8)(addr & 0xFF), (u8)((addr >> 8) & 0xFF) }; + + msgs[0].addr = uc->client->addr; + msgs[0].flags = 0; + msgs[0].len = 2; + msgs[0].buf = addr_bytes; + + msgs[1].addr = uc->client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = buf; + + ret = i2c_transfer(uc->client->adapter, msgs, 2); + return (ret == 2) ? 0 : (ret < 0 ? ret : -EIO); +} - buf = kzalloc(size, GFP_ATOMIC); - if (!buf) - return -ENOMEM; - if (UCSI_CCI_LENGTH(cci)) { - int ret = ccg_read(uc, reg, (void *)buf, size); +/* Generic HPIv2 read with 16-bit address (small debug helper) */ +static int ccg_i2c_read(struct ucsi_ccg *uc, u16 reg, u8 *buf, size_t len) +{ + struct i2c_client *client = uc->client; - if (ret) { - kfree(buf); - return ret; - } - } + u8 addr_buf[2] = { reg & 0xFF, (reg >> 8) & 0xFF }; + struct i2c_msg msgs[2] = { + { .addr = client->addr, .flags = 0, .len = 2, .buf = addr_buf }, + { .addr = client->addr, .flags = I2C_M_RD, .len = len, .buf = buf }, + }; + int ret; - spin_lock(&uc->op_lock); - data->cci = cpu_to_le32(cci); - if (UCSI_CCI_LENGTH(cci)) - memcpy(&data->message_in, buf, size); - spin_unlock(&uc->op_lock); - kfree(buf); - return 0; + if (!client) + return -ENODEV; + ret = i2c_transfer(client->adapter, msgs, 2); + + return (ret == 2) ? 0 : (ret < 0 ? ret : -EIO); } -static int ucsi_ccg_init(struct ucsi_ccg *uc) +/* Convenience little-endian register readers */ +static int ccg_read_u16(struct ucsi_ccg *uc, u16 reg, u16 *val) { - unsigned int count = 10; - u8 data; - int status; - - spin_lock_init(&uc->op_lock); + u8 b[2]; + int r = ccg_i2c_read(uc, reg, b, sizeof(b)); - data = CCGX_RAB_UCSI_CONTROL_STOP; - status = ccg_write(uc, CCGX_RAB_UCSI_CONTROL, &data, sizeof(data)); - if (status < 0) - return status; + if (r) + return r; - data = CCGX_RAB_UCSI_CONTROL_START; - status = ccg_write(uc, CCGX_RAB_UCSI_CONTROL, &data, sizeof(data)); - if (status < 0) - return status; - - /* - * Flush CCGx RESPONSE queue by acking interrupts. Above ucsi control - * register write will push response which must be cleared. - */ - do { - status = ccg_read(uc, CCGX_RAB_INTR_REG, &data, sizeof(data)); - if (status < 0) - return status; + *val = (u16)b[0] | ((u16)b[1] << 8); return 0; +} - if (!(data & DEV_INT)) - return 0; +static int ccg_read_u32(struct ucsi_ccg *uc, u16 reg, u32 *val) +{ + u8 b[4]; + int r = ccg_i2c_read(uc, reg, b, sizeof(b)); - status = ccg_write(uc, CCGX_RAB_INTR_REG, &data, sizeof(data)); - if (status < 0) - return status; + if (r) + return r; - usleep_range(10000, 11000); - } while (--count); + *val = (u32)b[0] | ((u32)b[1] << 8) | ((u32)b[2] << 16) | ((u32)b[3] << 24); - return -ETIMEDOUT; + return 0; } -static void ucsi_ccg_update_get_current_cam_cmd(struct ucsi_ccg *uc, u8 *data) +/* Row size from DEVICE_MODE b5:b4 (Table 15) */ +static u16 ccg_row_size_from_device_mode(u8 devmode) { - u8 cam, new_cam; - - cam = data[0]; - new_cam = uc->orig[cam].linked_idx; - uc->updated[new_cam].active_idx = cam; - data[0] = new_cam; + switch ((devmode >> 4) & 0x3) { + case 0: return 128; + case 1: return 256; + case 3: return 64; + default: return 256; + } } -static bool ucsi_ccg_update_altmodes(struct ucsi *ucsi, - u8 recipient, - struct ucsi_altmode *orig, - struct ucsi_altmode *updated) +/* Hybrid detection (optional) */ +static bool ccg_is_hybrid(struct ucsi_ccg *uc) { - struct ucsi_ccg *uc = ucsi_get_drvdata(ucsi); - struct ucsi_ccg_altmode *alt, *new_alt; - int i, j, k = 0; - bool found = false; + u8 ver[4]; - if (recipient != UCSI_RECIPIENT_CON) - return false; + if (!ccg_hpi_read16(uc, HPI_ADDR_HPI_VERSION, ver, sizeof(ver))) { + u32 v = ver[0] | (ver[1] << 8) | (ver[2] << 16) | (ver[3] << 24); - alt = uc->orig; - new_alt = uc->updated; - memset(uc->updated, 0, sizeof(uc->updated)); + return !!(v & BIT(31)); + } + return false; +} - /* - * Copy original connector altmodes to new structure. - * We need this before second loop since second loop - * checks for duplicate altmodes. - */ - for (i = 0; i < UCSI_MAX_ALTMODES; i++) { - alt[i].svid = orig[i].svid; - alt[i].mid = orig[i].mid; - if (!alt[i].svid) - break; - } +/* Read device mode (1 byte) */ +static int ccg_read_device_mode(struct ucsi_ccg *uc, u8 *mode) +{ + return ccg_hpi_read16(uc, HPI_ADDR_DEVICE_MODE, mode, 1); +} - for (i = 0; i < UCSI_MAX_ALTMODES; i++) { - if (!alt[i].svid) - break; - - /* already checked and considered */ - if (alt[i].checked) - continue; - - if (!DP_CONF_GET_PIN_ASSIGN(alt[i].mid)) { - /* Found Non DP altmode */ - new_alt[k].svid = alt[i].svid; - new_alt[k].mid |= alt[i].mid; - new_alt[k].linked_idx = i; - alt[i].linked_idx = k; - updated[k].svid = new_alt[k].svid; - updated[k].mid = new_alt[k].mid; - k++; - continue; - } +static int ccg_wait_ready_after_reset(struct ucsi_ccg *uc, unsigned int timeout_ms) +{ + unsigned long deadline = jiffies + msecs_to_jiffies(timeout_ms); + int ret; + u8 dm; + + do { + ret = ccg_read_device_mode(uc, &dm); + if (!ret) + return 0; + msleep(25); + } while (time_before(jiffies, deadline)); + return -ETIMEDOUT; +} - for (j = i + 1; j < UCSI_MAX_ALTMODES; j++) { - if (alt[i].svid != alt[j].svid || - !DP_CONF_GET_PIN_ASSIGN(alt[j].mid)) { - continue; - } else { - /* Found duplicate DP mode */ - new_alt[k].svid = alt[i].svid; - new_alt[k].mid |= alt[i].mid | alt[j].mid; - new_alt[k].linked_idx = UCSI_MULTI_DP_INDEX; - alt[i].linked_idx = k; - alt[j].linked_idx = k; - alt[j].checked = true; - found = true; - } - } - if (found) { - uc->has_multiple_dp = true; - } else { - /* Didn't find any duplicate DP altmode */ - new_alt[k].svid = alt[i].svid; - new_alt[k].mid |= alt[i].mid; - new_alt[k].linked_idx = i; - alt[i].linked_idx = k; - } - updated[k].svid = new_alt[k].svid; - updated[k].mid = new_alt[k].mid; - k++; - } - return found; +/* Basic hex helpers, used by parsers */ +static int hex_nibble(int c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return 10 + (c - 'a'); + if (c >= 'A' && c <= 'F') + return 10 + (c - 'A'); + return -1; } -static void ucsi_ccg_update_set_new_cam_cmd(struct ucsi_ccg *uc, - struct ucsi_connector *con, - u64 *cmd) -{ - struct ucsi_ccg_altmode *new_port, *port; - struct typec_altmode *alt = NULL; - u8 new_cam, cam, pin; - bool enter_new_mode; - int i, j, k = 0xff; - - port = uc->orig; - new_cam = UCSI_SET_NEW_CAM_GET_AM(*cmd); - if (new_cam >= ARRAY_SIZE(uc->updated)) - return; - new_port = &uc->updated[new_cam]; - cam = new_port->linked_idx; - enter_new_mode = UCSI_SET_NEW_CAM_ENTER(*cmd); +static int parse_hex16(const char *s, u16 *out) +{ + int i, v, nib; + + v = 0; + for (i = 0; i < 4; i++) { + nib = hex_nibble(s[i]); + if (nib < 0) + return -EINVAL; + v = (v << 4) | nib; + } + *out = (u16)v; + return 0; +} - /* - * If CAM is UCSI_MULTI_DP_INDEX then this is DP altmode - * with multiple DP mode. Find out CAM for best pin assignment - * among all DP mode. Priorite pin E->D->C after making sure - * the partner supports that pin. - */ - if (cam == UCSI_MULTI_DP_INDEX) { - if (enter_new_mode) { - for (i = 0; con->partner_altmode[i]; i++) { - alt = con->partner_altmode[i]; - if (alt->svid == new_port->svid) - break; - } - /* - * alt will always be non NULL since this is - * UCSI_SET_NEW_CAM command and so there will be - * at least one con->partner_altmode[i] with svid - * matching with new_port->svid. - */ - for (j = 0; port[j].svid; j++) { - pin = DP_CONF_GET_PIN_ASSIGN(port[j].mid); - if (alt && port[j].svid == alt->svid && - (pin & DP_CONF_GET_PIN_ASSIGN(alt->vdo))) { - /* prioritize pin E->D->C */ - if (k == 0xff || (k != 0xff && pin > - DP_CONF_GET_PIN_ASSIGN(port[k].mid)) - ) { - k = j; - } - } - } - cam = k; - new_port->active_idx = cam; - } else { - cam = new_port->active_idx; - } - } - *cmd &= ~UCSI_SET_NEW_CAM_AM_MASK; - *cmd |= UCSI_SET_NEW_CAM_SET_AM(cam); +/* Robust APPINFO parse: optional + tolerant */ +static void ccg_try_parse_appinfo(struct device *dev, const char *line, + u32 *start_addr, u32 *size_bytes) +{ + const char *p = strchr(line, ':'); + unsigned long start = 0, size = 0; + char *endp; + + if (!p) + return; + p++; + while (*p == ' ' || *p == '\t') + p++; + + if (!strncasecmp(p, "0x", 2)) + p += 2; + + start = simple_strtoul(p, &endp, 16); + if (!endp || *endp != ',') + return; + + p = endp + 1; + while (*p == ' ' || *p == '\t') + p++; + if (!strncasecmp(p, "0x", 2)) + p += 2; + size = simple_strtoul(p, &endp, 16); + if (!endp) + return; + + *start_addr = (u32)start; + *size_bytes = (u32)size; + dev_dbg(dev, "cyacd2 APPINFO: start=0x%08x size=0x%08x\n", *start_addr, *size_bytes); } -/* - * Change the order of vdo values of NVIDIA test device FTB - * (Function Test Board) which reports altmode list with vdo=0x3 - * first and then vdo=0x. Current logic to assign mode value is - * based on order in altmode list and it causes a mismatch of CON - * and SOP altmodes since NVIDIA GPU connector has order of vdo=0x1 - * first and then vdo=0x3 - */ -static void ucsi_ccg_nvidia_altmode(struct ucsi_ccg *uc, - struct ucsi_altmode *alt, - u64 command) -{ - switch (UCSI_ALTMODE_OFFSET(command)) { - case NVIDIA_FTB_DP_OFFSET: - if (alt[0].mid == USB_TYPEC_NVIDIA_VLINK_DBG_VDO) - alt[0].mid = USB_TYPEC_NVIDIA_VLINK_DP_VDO | - DP_CAP_DP_SIGNALLING(0) | DP_CAP_USB | - DP_CONF_SET_PIN_ASSIGN(BIT(DP_PIN_ASSIGN_E)); - break; - case NVIDIA_FTB_DBG_OFFSET: - if (alt[0].mid == USB_TYPEC_NVIDIA_VLINK_DP_VDO) - alt[0].mid = USB_TYPEC_NVIDIA_VLINK_DBG_VDO; - break; - default: - break; - } +static int ccg_row_text_list_add(struct ccg_row_text_list *lst, u16 row_rel, + u16 bank, const u8 *data) +{ + if (lst->count == lst->capacity) { + int newcap = lst->capacity ? lst->capacity * 2 : 128; + struct ccg_row_text *nr = krealloc(lst->rows, newcap * sizeof(*nr), GFP_KERNEL); + + if (!nr) + return -ENOMEM; + lst->rows = nr; + lst->capacity = newcap; + } + lst->rows[lst->count].row_rel = row_rel; + lst->rows[lst->count].bank = bank; + memcpy(lst->rows[lst->count].data, data, CCG_ROW_SIZE); + lst->count++; + return 0; } -static int ucsi_ccg_read_version(struct ucsi *ucsi, u16 *version) +static inline u16 ccg_row_idx_from_rel(u16 row_rel, u16 bank, u16 rows_per_bank) +{ + return (u16)(row_rel + bank * rows_per_bank); +} + +/* Detect .cyacd2 by filename */ +static bool ccg_is_cyacd2_name(const char *name) { - struct ucsi_ccg *uc = ucsi_get_drvdata(ucsi); - u16 reg = CCGX_RAB_UCSI_DATA_BLOCK(UCSI_VERSION); + const char *dot = strrchr(name, '.'); - return ccg_read(uc, reg, (u8 *)version, sizeof(*version)); + return dot && !strcmp(dot, ".cyacd2"); } -static int ucsi_ccg_read_cci(struct ucsi *ucsi, u32 *cci) +/* Quick content probe: true if we see @APPINFO or lines starting with ':rrrrbbbb' */ +static bool ccg_content_is_cyacd2_text(const u8 *buf, size_t sz) { - struct ucsi_ccg *uc = ucsi_get_drvdata(ucsi); + const u8 *p = buf, *end = buf + min_t(size_t, sz, 4096); + + while (p < end) { + const u8 *nl = memchr(p, '\n', end - p); + size_t len = nl ? (nl - p) : (end - p); + + if (len >= 10 && p[0] == ':') { + int ok = 1; + + for (int i = 1; i < 9; i++) { + if (hex_nibble(p[i]) < 0) { + ok = 0; + break; + } + } + if (ok) + return true; + } + if (len >= 8 && p[0] == '@') { + if (!strncmp((const char *)p, "@APPINFO", 8)) + return true; + } + if (!nl) + break; + p = nl + 1; + } + return false; +} - spin_lock(&uc->op_lock); - *cci = uc->op_data.cci; - spin_unlock(&uc->op_lock); +/* Parse .cyacd2 ASCII text: @APPINFO optional; :rrrrbbbb<512 hex> rows */ +static int ccg_parse_cyacd2_text(struct device *dev, const u8 *buf, size_t sz, + struct ccg_row_text_list *out, + u32 *app_start, u32 *app_size) +{ + const u8 *p = buf, *end = buf + sz; + char line[1152]; + + memset(out, 0, sizeof(*out)); + *app_start = 0; + *app_size = 0; + + while (p < end) { + const u8 *nl = memchr(p, '\n', end - p); + size_t len = nl ? (nl - p) : (end - p); + size_t l = min_t(size_t, len, sizeof(line) - 1); + + if (l == 0) { + p = nl ? nl + 1 : end; + continue; + } + + memcpy(line, p, l); + line[l] = '\0'; + p = nl ? nl + 1 : end; + if (l && (line[l - 1] == '\r')) + line[--l] = '\0'; + + /* Trim leading spaces */ + size_t s = 0; + + while (s < l && isspace(line[s])) + s++; + if (s >= l) + continue; + + if (line[s] == '@') { + if (!strncmp(&line[s], "@APPINFO", 8)) + ccg_try_parse_appinfo(dev, &line[s], app_start, app_size); + continue; + } + + if (line[s] == ':') { + const size_t hdr_off = s + 1; + const size_t payload_off = s + 1 + 8; + u16 addr16_lo = 0, addr16_hi = 0; + u16 row_idx; + int rc; + + if (hdr_off + 8 > l) { + dev_err(dev, "cyacd2: short header line\n"); + return -EINVAL; + } + + /* first 4 hex chars: low 16 bits (rrrr) */ + rc = parse_hex16(&line[hdr_off], &addr16_lo); + if (rc) + return rc; + + /* next 4 hex chars: high 16 bits (bbbb) */ + rc = parse_hex16(&line[hdr_off + 4], &addr16_hi); + if (rc) + return rc; - return 0; + /* + * MSB+LSB row mapping: + * row index = addr16_lo + addr16_hi + * + * This yields: + * :00670000 -> 0x0067 + * :006C0000 -> 0x006C + * :00FF0000 -> 0x00FF + * :00000100 -> 0x0100 + * :00010100 -> 0x0101 + * :00FE0100 -> 0x01FE + */ + row_idx = (u16)((u32)addr16_lo + (u32)addr16_hi); + + if ((l - payload_off) != (CCG_ROW_SIZE * 2)) { + dev_err(dev, "cyacd2: payload not %dB (hex chars=%zu)\n", + CCG_ROW_SIZE, l - payload_off); + return -EINVAL; + } + + u8 data[CCG_ROW_SIZE]; + + for (int i = 0; i < CCG_ROW_SIZE; i++) { + int hi = hex_nibble(line[payload_off + 2 * i]); + int lo = hex_nibble(line[payload_off + 2 * i + 1]); + + if (hi < 0 || lo < 0) + return -EINVAL; + data[i] = (hi << 4) | lo; + } + + /* Store computed row index, ignore bank (we use direct row indices) */ + rc = ccg_row_text_list_add(out, row_idx, 0, data); + if (rc) + return rc; + } + } + + return 0; } -static int ucsi_ccg_read_message_in(struct ucsi *ucsi, void *val, size_t val_len) +/* Parse legacy .cyacd ASCII text: ":rrrr<512 hex>" */ +static int ccg_parse_cyacd_text(struct device *dev, const u8 *buf, size_t sz, + struct ccg_row_text_list *out) { - struct ucsi_ccg *uc = ucsi_get_drvdata(ucsi); + const u8 *p = buf, *end = buf + sz; + char line[1152]; - spin_lock(&uc->op_lock); - memcpy(val, uc->op_data.message_in, val_len); - spin_unlock(&uc->op_lock); + memset(out, 0, sizeof(*out)); - return 0; + while (p < end) { + const u8 *nl = memchr(p, '\n', end - p); + size_t len = nl ? (nl - p) : (end - p); + size_t l = min_t(size_t, len, sizeof(line) - 1); + + if (l == 0) { + p = nl ? nl + 1 : end; + continue; + } + memcpy(line, p, l); + line[l] = '\0'; + p = nl ? nl + 1 : end; + + if (l && (line[l - 1] == '\r')) + line[--l] = '\0'; + + size_t s = 0; + + while (s < l && isspace(line[s])) + s++; + if (s >= l) + continue; + + if (line[s] != ':') + continue; + + if (s + 1 + 4 > l) + continue; + + u16 row_abs = 0; + + if (parse_hex16(&line[s + 1], &row_abs)) + continue; + + const size_t payload_off = s + 1 + 4; + + if ((l - payload_off) != (CCG_ROW_SIZE * 2)) { + dev_err(dev, "cyacd: payload not 256B at row=0x%04x (hex=%zu)\n", + row_abs, l - payload_off); + return -EINVAL; + } + + u8 data[CCG_ROW_SIZE]; + + for (int i = 0; i < CCG_ROW_SIZE; i++) { + int hi = hex_nibble(line[payload_off + 2 * i]); + int lo = hex_nibble(line[payload_off + 2 * i + 1]); + + if (hi < 0 || lo < 0) + return -EINVAL; + data[i] = (hi << 4) | lo; + } + + if (ccg_row_text_list_add(out, row_abs, 0 /* bank=0 */, data)) + return -ENOMEM; + } + + return 0; } -static int ucsi_ccg_async_control(struct ucsi *ucsi, u64 command) +/* Row list helpers (standardized to CCG_ROW_SIZE) */ +static int ccg_row_list_add(struct ccg_row_list *lst, u16 row, const u8 *data, u16 len) { - struct ucsi_ccg *uc = ucsi_get_drvdata(ucsi); - u16 reg = CCGX_RAB_UCSI_DATA_BLOCK(UCSI_CONTROL); + if (len != CCG_ROW_SIZE) + return -EINVAL; + if (lst->count == lst->capacity) { + int newcap = lst->capacity ? lst->capacity * 2 : 64; + struct ccg_row *nr = krealloc(lst->rows, newcap * sizeof(*nr), GFP_KERNEL); + + if (!nr) + return -ENOMEM; + lst->rows = nr; + lst->capacity = newcap; + } + lst->rows[lst->count].row = row; + lst->rows[lst->count].len = len; + memcpy(lst->rows[lst->count].data, data, len); + lst->count++; + return 0; +} - /* - * UCSI may read CCI instantly after async_control, - * clear CCI to avoid caller getting wrong data before we get CCI from ISR - */ - spin_lock(&uc->op_lock); - uc->op_data.cci = 0; - spin_unlock(&uc->op_lock); +/* Build absolute row list from parsed relative rows */ +static int ccg_rows_to_absolute(const struct ccg_row_text_list *txt, + struct ccg_row_list *abs_out) +{ + memset(abs_out, 0, sizeof(*abs_out)); - return ccg_write(uc, reg, (u8 *)&command, sizeof(command)); + for (int i = 0; i < txt->count; i++) { + if (ccg_row_list_add(abs_out, + txt->rows[i].row_rel, /* direct row index */ + txt->rows[i].data, + CCG_ROW_SIZE)) + return -ENOMEM; + } + + return 0; } -static int ucsi_ccg_sync_control(struct ucsi *ucsi, u64 command, u32 *cci, - void *data, size_t size, void *msg_out, - size_t msg_out_size) +/* ===== New: unified parse entry to build absolute rows from firmware buffer ===== */ +static int ccg_parse_and_build_rows(struct device *dev, + const u8 *buf, size_t sz, + u16 fw1_start, u16 fw2_start, + u16 rows_per_bank, + u8 target_bank, + struct ccg_row_list *abs) { - struct ucsi_ccg *uc = ucsi_get_drvdata(ucsi); - struct ucsi_connector *con; - int con_index; - int ret; + int rc; + struct ccg_row_text_list txt = {0}; + + if (!buf || !sz || !abs) + return -EINVAL; + + /* Detect format by content: cyacd2 has @APPINFO or :rrrrbbbb header lines */ + if (ccg_content_is_cyacd2_text(buf, sz)) { + u32 dummy_s = 0, dummy_l = 0; + + rc = ccg_parse_cyacd2_text(dev, buf, sz, &txt, &dummy_s, &dummy_l); + if (rc) { + dev_err(dev, "parse cyacd2 text failed (%d)\n", rc); + return rc; + } + rc = ccg_rows_to_absolute(&txt, abs); + kfree(txt.rows); + return rc; + } + + /* Legacy .cyacd: row numbers are absolute indices across whole flash */ + rc = ccg_parse_cyacd_text(dev, buf, sz, &txt); + if (rc) { + dev_err(dev, "parse cyacd text failed (%d)\n", rc); + return rc; + } + memset(abs, 0, sizeof(*abs)); + for (int i = 0; i < txt.count; i++) { + if (ccg_row_list_add(abs, txt.rows[i].row_rel, txt.rows[i].data, CCG_ROW_SIZE)) { + kfree(txt.rows); + return -ENOMEM; + } + } + kfree(txt.rows); + return 0; +} - mutex_lock(&uc->lock); - pm_runtime_get_sync(uc->dev); +static void ccg_log_device_mode(struct ucsi_ccg *uc, const char *tag) +{ + u8 dm = 0; - if (UCSI_COMMAND(command) == UCSI_SET_NEW_CAM && - uc->has_multiple_dp) { - con_index = (command >> 16) & - UCSI_CMD_CONNECTOR_MASK; - if (con_index == 0) { - ret = -EINVAL; - goto err_put; - } - con = &uc->ucsi->connector[con_index - 1]; - ucsi_ccg_update_set_new_cam_cmd(uc, con, &command); - } + if (ccg_read_device_mode(uc, &dm) == 0) + dev_info(uc->dev, "%s: DEVICE_MODE=0x%02x", tag, dm); +} - ret = ucsi_sync_control_common(ucsi, command, cci, data, size, - msg_out, msg_out_size); +/* Pick firmware file based on mode (property override still wins) */ +static const char *ccg_pick_fw_name(struct device *dev, + enum enum_flash_mode mode, + char *buf, size_t bufsz) +{ + const char *prop; + + if (!device_property_read_string(dev, "firmware-name", &prop) && prop && *prop) { + strscpy(buf, prop, bufsz); + return buf; + } + switch (mode) { + case SECONDARY_BL: + case SECONDARY: return "ccg_secondary.cyacd2"; + case PRIMARY: return "ccg_primary.cyacd2"; + default: + return "ccg_secondary.cyacd2"; + } +} - switch (UCSI_COMMAND(command)) { - case UCSI_GET_CURRENT_CAM: - if (uc->has_multiple_dp) - ucsi_ccg_update_get_current_cam_cmd(uc, (u8 *)data); - break; - case UCSI_GET_ALTERNATE_MODES: - if (UCSI_ALTMODE_RECIPIENT(command) == UCSI_RECIPIENT_SOP) { - struct ucsi_altmode *alt = data; - - if (alt[0].svid == USB_TYPEC_NVIDIA_VLINK_SID) - ucsi_ccg_nvidia_altmode(uc, alt, command); - } - break; - case UCSI_GET_CAPABILITY: - if (uc->fw_build == CCG_FW_BUILD_NVIDIA_TEGRA) { - struct ucsi_capability *cap = data; +#include - cap->features &= ~UCSI_CAP_ALT_MODE_DETAILS; - } - break; - default: - break; - } +/* Compute CRC32 over contiguous rows in span */ +static u32 ccg_crc32_rows(const struct ccg_row_list *abs, + u16 base, u16 limit) +{ + u32 crc = ~0U; + u16 expected = base; + + for (int i = 0; i < abs->count; i++) { + u16 idx = abs->rows[i].row; + + if (idx < base || idx >= limit) + continue; + /* Require contiguous rows for CRC correctness */ + if (idx != expected) + break; + crc = crc32_le(crc, abs->rows[i].data, CCG_ROW_SIZE); + expected++; + } + return crc ^ ~0U; +} -err_put: - pm_runtime_put_sync(uc->dev); - mutex_unlock(&uc->lock); +/* ========================== Utility: APPINFO parser ========================== */ - return ret; +/* Parse @APPINFO line out of a cyacd2 text buffer: "@APPINFO:0x,0x" + * Returns 0 on success, -ENOENT if not found or malformed. + */ +static int ccg_parse_appinfo_text(const u8 *data, size_t size, u32 *app_start, u32 *app_size) +{ + const char *p = (const char *)data; + const char *end = p + size; + const char *tag = "@APPINFO:"; + size_t taglen = strlen(tag); + + if (!data || !app_start || !app_size) + return -EINVAL; + + while (p < end) { + const char *nl = memchr(p, '\n', end - p); + size_t linelen = nl ? (size_t)(nl - p) : (size_t)(end - p); + + if (linelen >= taglen && !memcmp(p, tag, taglen)) { + /* Expect hex values like 0x700,0x43cc */ + u32 start = 0, sizeb = 0; + /* Simple sscanf over a temporary zero-terminated buffer */ + char tmp[64]; + size_t copy = min(linelen, sizeof(tmp) - 1); + + memcpy(tmp, p, copy); + tmp[copy] = '\0'; + if (sscanf(tmp, "@APPINFO:0x%x,0x%x", &start, &sizeb) == 2) { + *app_start = start; + *app_size = sizeb; + return 0; + } + break; + } + p = nl ? (nl + 1) : end; + } + return -ENOENT; } -static const struct ucsi_operations ucsi_ccg_ops = { - .read_version = ucsi_ccg_read_version, - .read_cci = ucsi_ccg_read_cci, - .poll_cci = ucsi_ccg_read_cci, - .read_message_in = ucsi_ccg_read_message_in, - .sync_control = ucsi_ccg_sync_control, - .async_control = ucsi_ccg_async_control, - .update_altmodes = ucsi_ccg_update_altmodes -}; +/* Status read helper */ +static int ccg_try_read_cmd_status(struct ucsi_ccg *uc, u8 *status) +{ + if (!status) + return -EINVAL; -static irqreturn_t ccg_irq_handler(int irq, void *data) + return ccg_hpi_read16(uc, HPI_ADDR_RESPONSE, status, 1); +} + +static int ccg_wait_success(struct ucsi_ccg *uc, unsigned int timeout_ms) { - u16 reg = CCGX_RAB_UCSI_DATA_BLOCK(UCSI_CCI); - struct ucsi_ccg *uc = data; - u8 intr_reg; - u32 cci = 0; - int ret = 0; + unsigned long timeout = jiffies + msecs_to_jiffies(timeout_ms); + u8 status = 0xFF; - ret = ccg_read(uc, CCGX_RAB_INTR_REG, &intr_reg, sizeof(intr_reg)); - if (ret) - return ret; + do { + (void)ccg_try_read_cmd_status(uc, &status); + if (status == HPI_RSP_SUCCESS) + return 0; + usleep_range(2000, 4000); + } while (time_before(jiffies, timeout)); - if (!intr_reg) - return IRQ_HANDLED; - else if (!(intr_reg & UCSI_READ_INT)) - goto err_clear_irq; + return -ETIMEDOUT; +} - ret = ccg_read(uc, reg, (void *)&cci, sizeof(cci)); - if (ret) - goto err_clear_irq; +/* Drain pending device responses and DEV_INTR once, to avoid stale rsp values. */ +static void ccg_drain_responses(struct ucsi_ccg *uc) +{ + u8 intr = 0, resp = 0xFF; + int lim = 32; + + while (lim--) { + if (ccg_read(uc, HPI_ADDR_INTR_REG, &intr, sizeof(intr))) + break; + if (!(intr & BIT(0))) + break; + + (void)ccg_read(uc, HPI_ADDR_RESPONSE, &resp, sizeof(resp)); + /* Ack DEV_INTR by writing 1 to bit0 if your device requires it */ + intr = BIT(0); + ccg_write(uc, HPI_ADDR_INTR_REG, &intr, sizeof(intr)); + + if (resp == 0x00) + break; + usleep_range(1000, 2000); + } +} - /* - * As per CCGx UCSI interface guide, copy CCI and MESSAGE_IN - * to the OpRegion before clear the UCSI interrupt - */ - ret = ccg_op_region_update(uc, cci); - if (ret) - goto err_clear_irq; +/* Disable PD ports and wait for Success (0x02). Adds diagnostics requested. */ +static int ccg_disable_pd_ports(struct ucsi_ccg *uc) +{ + int err; + u8 rsp = 0xFF, resp_before = 0xFF; + + /* DIAG: dump RESPONSE before disabling PD */ + (void)ccg_read(uc, HPI_ADDR_RESPONSE, &resp_before, sizeof(resp_before)); + dev_info(uc->dev, "diag: RESPONSE before PD disable: 0x%02x", resp_before); + + /* Clear stale responses first */ + ccg_drain_responses(uc); + + ccg_read(uc, HPI_ADDR_RESPONSE, &resp_before, sizeof(resp_before)); + dev_info(uc->dev, "diag: RESPONSE before PD disable: 0x%02x", resp_before); + /* PDPORT_ENABLE = 0 */ + { + u8 zero = 0x00; + + err = ccg_hpi_write16(uc, HPI_ADDR_PDPORT_ENABLE, &zero, 1); + if (err) { + dev_err(uc->dev, "PDPORT_ENABLE(0) write failed (%d)", err); + return err; + } + } + /* Poll for SUCCESS up to 1500 ms – DIAG print when we see it */ + { + unsigned long end = jiffies + msecs_to_jiffies(1500); + + do { + (void)ccg_try_read_cmd_status(uc, &rsp); + if (rsp == HPI_RSP_SUCCESS) { + dev_info(uc->dev, "diag: PD disable rsp=0x%02x (SUCCESS)", rsp); + return 0; + } + usleep_range(3000, 5000); + } while (time_before(jiffies, end)); + } + + if (rsp == HPI_RSP_SUCCESS) { + dev_info(uc->dev, "diag: RESPONSE after PD disable rsp=0x%02x (SUCCESS)", rsp); + return 0; + } + dev_err(uc->dev, "PDPORT_ENABLE(0) timeout rsp=0x%02x", rsp); + return -ETIMEDOUT; +} -err_clear_irq: - ccg_write(uc, CCGX_RAB_INTR_REG, &intr_reg, sizeof(intr_reg)); +static int ccg_wait_async_event(struct ucsi_ccg *uc, + u8 *resp0, u8 *resp1, + unsigned int timeout_ms) +{ + unsigned long deadline = jiffies + msecs_to_jiffies(timeout_ms); + u8 intr; + u8 rsp2[2]; + int ret; + + if (!resp0 || !resp1) + return -EINVAL; - if (!ret) - ucsi_notify_common(uc->ucsi, cci); + *resp0 = 0; + *resp1 = 0; - return IRQ_HANDLED; + do { + /* Check interrupt register */ + ret = ccg_read(uc, HPI_ADDR_INTR_REG, &intr, sizeof(intr)); + if (ret) { + dev_err(uc->dev, "read INTR_REG failed %d\n", ret); + return ret; + } + + if (intr & INTR_DEV_INTR) { + /* Read 2-byte response */ + ret = ccg_read(uc, HPI_ADDR_RESPONSE, rsp2, sizeof(rsp2)); + if (ret) { + dev_err(uc->dev, "read RESPONSE failed %d\n", ret); + return ret; + } + + *resp0 = rsp2[0]; + *resp1 = rsp2[1]; + + /* Ack interrupt */ + intr = INTR_DEV_INTR; + ret = ccg_write(uc, HPI_ADDR_INTR_REG, &intr, sizeof(intr)); + if (ret) { + dev_err(uc->dev, + "write INTR_REG (ack) failed %d\n", ret); + return ret; + } + + /* Interpret status per HPI spec */ + if (*resp0 == 0x02 || *resp0 == 0x03) { + /* 0x02 = Success, 0x03 = Flash Data Available */ + return 0; + } + + /* Anything else is an error code, propagate it */ + dev_err(uc->dev, "HPI error: resp0=0x%02x resp1=0x%02x\n", + *resp0, *resp1); + + /* Optional: map some codes specially */ + switch (*resp0) { + case 0x05: return -EINVAL; /* Invalid Command */ + case 0x06: return -EIO; /* Invalid State */ + case 0x07: return -EIO; /* Flash Update Failed */ + case 0x08: return -EFAULT; /* Invalid FW */ + case 0x09: return -EINVAL; /* Invalid Arguments */ + case 0x0A: return -EOPNOTSUPP; /* Not Supported */ + default: + return -EIO; + } + } + + usleep_range(2000, 4000); + } while (time_before(jiffies, deadline)); + + dev_err(uc->dev, "timeout waiting for async event\n"); + return -ETIMEDOUT; } -static int ccg_request_irq(struct ucsi_ccg *uc) +static inline int ccg_wait_success_or_error(struct ucsi_ccg *uc, + const char *tag, + unsigned int timeout_ms) { - unsigned long flags = IRQF_ONESHOT; + u8 ev0 = 0, ev1 = 0; + int ret = ccg_wait_async_event(uc, &ev0, &ev1, timeout_ms); + + dev_err(uc->dev, + "AKdiag: %s: success (resp0=0x%02x resp1=0x%02x)\n", + tag ? tag : "async", ev0, ev1); + if (!ret) { + dev_err(uc->dev, + "diag: %s: success (resp0=0x%02x resp1=0x%02x)\n", + tag ? tag : "async", ev0, ev1); + return 0; + } + + if (ret > 0) { + /* HPI_RSP_* code returned */ + dev_err(uc->dev, + "diag: %s: HPI error ret=0x%02x (resp0=0x%02x resp1=0x%02x)\n", + tag ? tag : "async", ret, ev0, ev1); + } else { + dev_err(uc->dev, + "diag: %s: transport/timeout err=%d (resp0=0x%02x resp1=0x%02x)\n", + tag ? tag : "async", ret, ev0, ev1); + } + + return ret; +} - if (!dev_fwnode(uc->dev)) - flags |= IRQF_TRIGGER_HIGH; +/* Helper: wait for async event with context message, but allow non-fatal failure */ +static void ccg_wait_and_log_async(struct ucsi_ccg *uc, + const char *tag, + unsigned int timeout_ms) +{ + u8 ev0 = 0, ev1 = 0; + int ret; + + ret = ccg_wait_async_event(uc, &ev0, &ev1, timeout_ms); + if (ret) { + dev_warn(uc->dev, + "diag: %s: no async event (ret=%d)\n", + tag ? tag : "async", ret); + } else { + dev_info(uc->dev, + "diag: %s: async resp0=0x%02x resp1=0x%02x\n", + tag ? tag : "async", ev0, ev1); + } +} - return request_threaded_irq(uc->irq, NULL, ccg_irq_handler, flags, dev_name(uc->dev), uc); +static int ccg_cmd_write_flash_row(struct ucsi_ccg *uc, u16 row_idx, + const void *data, u8 flash_cmd) +{ + int ret; + u8 cmd[4] = { + HPI_SIG_FLASH_RW, /* 'F' */ + flash_cmd, /* 0x01 = write row */ + (u8)(row_idx & 0xFF), + (u8)((row_idx >> 8) & 0xFF), + }; + u8 rsp0 = 0; + + if (!data) + return -EINVAL; + + /* 13. Copy data into FLASH_RW_MEM window */ + ret = ccg_hpi_write16(uc, HPI_ADDR_FLASH_RW_MEM_BASE, + data, HPI_FLASH_RW_MEM_SIZE); + if (ret) { + dev_err(uc->dev, + "flash row 0x%04x: write data window failed (%d)", + row_idx, ret); + return ret; + } + + /* 13. Trigger write */ + ret = ccg_hpi_write16(uc, HPI_ADDR_FLASH_ROW_RW, + cmd, sizeof(cmd)); + if (ret) { + dev_err(uc->dev, + "flash row 0x%04x: trigger write failed (%d)", + row_idx, ret); + return ret; + } + + return 0; } -static void ccg_pm_workaround_work(struct work_struct *pm_work) +static int ccg_read_response(struct ucsi_ccg *uc) { - ccg_irq_handler(0, container_of(pm_work, struct ucsi_ccg, pm_work)); + unsigned long target = jiffies + msecs_to_jiffies(1000); + struct device *dev = uc->dev; + u8 intval; + int status; + + /* wait for interrupt status to get updated */ + do { + status = ccg_read(uc, CCGX_RAB_INTR_REG, &intval, + sizeof(intval)); + if (status < 0) + return status; + + if (intval & DEV_INT) + break; + usleep_range(500, 600); + } while (time_is_after_jiffies(target)); + + if (time_is_before_jiffies(target)) { + dev_err(dev, "response timeout error\n"); + return -ETIME; + } + + status = ccg_read(uc, CCGX_RAB_RESPONSE, (u8 *)&uc->dev_resp, + sizeof(uc->dev_resp)); + if (status < 0) + return status; + + status = ccg_write(uc, CCGX_RAB_INTR_REG, &intval, sizeof(intval)); + if (status < 0) + return status; + + return 0; } static int get_fw_info(struct ucsi_ccg *uc) { - int err; + struct ccg_dev_info *info = &uc->info; + int err; - err = ccg_read(uc, CCGX_RAB_READ_ALL_VER, (u8 *)(&uc->version), - sizeof(uc->version)); - if (err < 0) - return err; + err = ccg_read(uc, CCGX_RAB_READ_ALL_VER, (u8 *)(&uc->version), + sizeof(uc->version)); + if (err < 0) + return err; - uc->fw_version = CCG_VERSION(uc->version[FW2].app.ver) | - CCG_VERSION_PATCH(uc->version[FW2].app.patch); + uc->fw_version = CCG_VERSION(uc->version[FW2].app.ver) | + CCG_VERSION_PATCH(uc->version[FW2].app.patch); - err = ccg_read(uc, CCGX_RAB_DEVICE_MODE, (u8 *)(&uc->info), - sizeof(uc->info)); - if (err < 0) - return err; + err = ccg_read(uc, CCGX_RAB_DEVICE_MODE, (u8 *)(&uc->info), + sizeof(uc->info)); + if (err < 0) + return err; - return 0; + dev_err(uc->dev, "AK:ccg device info: fw_version:%u mode:%u bl_mode:%u silicon_id:0x%04x bl_last_row:0x%04x\n", + uc->fw_version, info->mode, info->bl_mode, + le16_to_cpu(info->silicon_id), le16_to_cpu(info->bl_last_row)); + + return 0; } static inline bool invalid_async_evt(int code) { - return (code >= CCG_EVENT_MAX) || (code < EVENT_INDEX); + return (code >= CCG_EVENT_MAX) || (code < EVENT_INDEX); } static void ccg_process_response(struct ucsi_ccg *uc) { - struct device *dev = uc->dev; + struct device *dev = uc->dev; + + if (uc->dev_resp.code & ASYNC_EVENT) { + if (uc->dev_resp.code == RESET_COMPLETE) { + if (test_bit(RESET_PENDING, &uc->flags)) + uc->cmd_resp = uc->dev_resp.code; + get_fw_info(uc); + } + if (invalid_async_evt(uc->dev_resp.code)) + dev_err(dev, "invalid async evt %d\n", + uc->dev_resp.code); + } else { + if (test_bit(DEV_CMD_PENDING, &uc->flags)) { + uc->cmd_resp = uc->dev_resp.code; + clear_bit(DEV_CMD_PENDING, &uc->flags); + } else { + dev_err(dev, "dev resp 0x%04x but no cmd pending\n", + uc->dev_resp.code); + } + } +} - if (uc->dev_resp.code & ASYNC_EVENT) { - if (uc->dev_resp.code == RESET_COMPLETE) { - if (test_bit(RESET_PENDING, &uc->flags)) - uc->cmd_resp = uc->dev_resp.code; - get_fw_info(uc); - } - if (invalid_async_evt(uc->dev_resp.code)) - dev_err(dev, "invalid async evt %d\n", - uc->dev_resp.code); - } else { - if (test_bit(DEV_CMD_PENDING, &uc->flags)) { - uc->cmd_resp = uc->dev_resp.code; - clear_bit(DEV_CMD_PENDING, &uc->flags); - } else { - dev_err(dev, "dev resp 0x%04x but no cmd pending\n", - uc->dev_resp.code); - } - } +/* Caller must hold uc->lock */ +static int ccg_send_command(struct ucsi_ccg *uc, struct ccg_cmd *cmd) +{ + struct device *dev = uc->dev; + int ret; + + switch (cmd->reg & 0xF000) { + case DEV_REG_IDX: + set_bit(DEV_CMD_PENDING, &uc->flags); + break; + default: + dev_err(dev, "invalid cmd register\n"); + break; + } + + ret = ccg_write(uc, cmd->reg, (u8 *)&cmd->data, cmd->len); + if (ret < 0) + return ret; + + msleep(cmd->delay); + + ret = ccg_read_response(uc); + if (ret < 0) { + dev_err(dev, "response read error\n"); + switch (cmd->reg & 0xF000) { + case DEV_REG_IDX: + clear_bit(DEV_CMD_PENDING, &uc->flags); + break; + default: + dev_err(dev, "invalid cmd register\n"); + break; + } + return -EIO; + } + ccg_process_response(uc); + + return uc->cmd_resp; } -static int ccg_read_response(struct ucsi_ccg *uc) + +static int ccg_cmd_validate_fw(struct ucsi_ccg *uc, unsigned int fwid) { - unsigned long target = jiffies + msecs_to_jiffies(1000); - struct device *dev = uc->dev; - u8 intval; - int status; + struct ccg_cmd cmd; + int ret; - /* wait for interrupt status to get updated */ - do { - status = ccg_read(uc, CCGX_RAB_INTR_REG, &intval, - sizeof(intval)); - if (status < 0) - return status; + cmd.reg = CCGX_RAB_VALIDATE_FW; + cmd.data = fwid; + cmd.len = 1; + cmd.delay = 500; - if (intval & DEV_INT) - break; - usleep_range(500, 600); - } while (time_is_after_jiffies(target)); + mutex_lock(&uc->lock); - if (time_is_before_jiffies(target)) { - dev_err(dev, "response timeout error\n"); - return -ETIME; - } + ret = ccg_send_command(uc, &cmd); - status = ccg_read(uc, CCGX_RAB_RESPONSE, (u8 *)&uc->dev_resp, - sizeof(uc->dev_resp)); - if (status < 0) - return status; + mutex_unlock(&uc->lock); - status = ccg_write(uc, CCGX_RAB_INTR_REG, &intval, sizeof(intval)); - if (status < 0) - return status; + if (ret != CMD_SUCCESS) + return ret; - return 0; + return 0; } -/* Caller must hold uc->lock */ -static int ccg_send_command(struct ucsi_ccg *uc, struct ccg_cmd *cmd) +static bool ccg_check_vendor_version(struct ucsi_ccg *uc, + struct version_format *app, + struct fw_config_table *fw_cfg) { - struct device *dev = uc->dev; - int ret; - - switch (cmd->reg & 0xF000) { - case DEV_REG_IDX: - set_bit(DEV_CMD_PENDING, &uc->flags); - break; - default: - dev_err(dev, "invalid cmd register\n"); - break; - } + struct device *dev = uc->dev; + + /* Check if the fw build is for supported vendors */ + if (le16_to_cpu(app->build) != uc->fw_build) { + dev_info(dev, "current fw is not from supported vendor\n"); + return false; + } + + /* Check if the new fw build is for supported vendors */ + if (le16_to_cpu(fw_cfg->app.build) != uc->fw_build) { + dev_info(dev, "new fw is not from supported vendor\n"); + return false; + } + return true; +} - ret = ccg_write(uc, cmd->reg, (u8 *)&cmd->data, cmd->len); - if (ret < 0) - return ret; - - msleep(cmd->delay); - - ret = ccg_read_response(uc); - if (ret < 0) { - dev_err(dev, "response read error\n"); - switch (cmd->reg & 0xF000) { - case DEV_REG_IDX: - clear_bit(DEV_CMD_PENDING, &uc->flags); - break; - default: - dev_err(dev, "invalid cmd register\n"); - break; - } - return -EIO; - } - ccg_process_response(uc); +static bool ccg_check_fw_version(struct ucsi_ccg *uc, const char *fw_name, + struct version_format *app) +{ + const struct firmware *fw = NULL; + struct device *dev = uc->dev; + struct fw_config_table fw_cfg; + u32 cur_version, new_version; + bool is_later = false; + + if (request_firmware(&fw, fw_name, dev) != 0) { + dev_err(dev, "error: Failed to open cyacd file %s\n", fw_name); + return false; + } + + /* + * check if signed fw + * last part of fw image is fw cfg table and signature + */ + if (fw->size < sizeof(fw_cfg) + FW_CFG_TABLE_SIG_SIZE) + goto out_release_firmware; + + memcpy((uint8_t *)&fw_cfg, fw->data + fw->size - + sizeof(fw_cfg) - FW_CFG_TABLE_SIG_SIZE, sizeof(fw_cfg)); + + if (fw_cfg.identity != ('F' | 'W' << 8 | 'C' << 16 | 'T' << 24)) { + dev_info(dev, "not a signed image\n"); + goto out_release_firmware; + } + + /* compare input version with FWCT version */ + cur_version = le16_to_cpu(app->build) | CCG_VERSION_PATCH(app->patch) | + CCG_VERSION(app->ver); + + new_version = le16_to_cpu(fw_cfg.app.build) | + CCG_VERSION_PATCH(fw_cfg.app.patch) | + CCG_VERSION(fw_cfg.app.ver); + + if (!ccg_check_vendor_version(uc, app, &fw_cfg)) + goto out_release_firmware; + + if (new_version > cur_version) + is_later = true; + +out_release_firmware: + release_firmware(fw); + return is_later; +} - return uc->cmd_resp; +static void ccg_dump_first_rows(struct device *dev, + const struct ccg_row_text_list *lst, + int rows_per_bank, bool is_cyacd2) +{ + int n = min(lst->count, 3); + + for (int i = 0; i < n; i++) { + u16 row_abs = is_cyacd2 + ? (lst->rows[i].row_rel + lst->rows[i].bank * rows_per_bank) + : lst->rows[i].row_rel; + dev_err(dev, + "parsed[%d]: row_rel=0x%04x bank=%u abs=0x%04x data=%02x %02x %02x %02x\n", + i, lst->rows[i].row_rel, lst->rows[i].bank, row_abs, + lst->rows[i].data[0], lst->rows[i].data[1], + lst->rows[i].data[2], lst->rows[i].data[3]); + } +} + +static bool rows_look_absolute(const struct ccg_row_list *list, u16 fw2_start) +{ + int i; + + for (i = 0; i < list->count; i++) { + u16 idx = list->rows[i].row; + + if (idx >= fw2_start) + return true; + } + return false; } -static int ccg_cmd_enter_flashing(struct ucsi_ccg *uc) +static void remap_rows_to_bank(struct ccg_row_list *list, u16 bank_base) { - struct ccg_cmd cmd; - int ret; + int i; + + for (i = 0; i < list->count; i++) + list->rows[i].row = bank_base + list->rows[i].row; +} - cmd.reg = CCGX_RAB_ENTER_FLASHING; - cmd.data = FLASH_ENTER_SIG; - cmd.len = 1; - cmd.delay = 50; - mutex_lock(&uc->lock); +static inline int ccg_active_fw_from_device_mode(u8 devmode) +{ + switch (devmode & CCG_DEV_MODE_FWMODE_MASK) { + case CCG_DEV_MODE_FW1: return 1; + case CCG_DEV_MODE_FW2: return 2; + default: return -1; + } +} - ret = ccg_send_command(uc, &cmd); +static int ccg_op_region_update(struct ucsi_ccg *uc, u32 cci) +{ + u16 reg = CCGX_RAB_UCSI_DATA_BLOCK(UCSI_MESSAGE_IN); + struct op_region *data = &uc->op_data; + unsigned char *buf; + size_t size = sizeof(data->message_in); - mutex_unlock(&uc->lock); + buf = kzalloc(size, GFP_ATOMIC); + if (!buf) + return -ENOMEM; + if (UCSI_CCI_LENGTH(cci)) { + int ret = ccg_read(uc, reg, (void *)buf, size); - if (ret != CMD_SUCCESS) { - dev_err(uc->dev, "enter flashing failed ret=%d\n", ret); - return ret; + if (ret) { + kfree(buf); + return ret; + } } + spin_lock(&uc->op_lock); + data->cci = cpu_to_le32(cci); + if (UCSI_CCI_LENGTH(cci)) + memcpy(&data->message_in, buf, size); + spin_unlock(&uc->op_lock); + kfree(buf); return 0; } -static int ccg_cmd_reset(struct ucsi_ccg *uc) +static int ucsi_ccg_init(struct ucsi_ccg *uc) { - struct ccg_cmd cmd; - u8 *p; - int ret; + unsigned int count = 10; + u8 data; + int status; + int ret = 0; - p = (u8 *)&cmd.data; - cmd.reg = CCGX_RAB_RESET_REQ; - p[0] = RESET_SIG; - p[1] = CMD_RESET_DEV; - cmd.len = 2; - cmd.delay = 5000; + spin_lock_init(&uc->op_lock); - mutex_lock(&uc->lock); + data = CCGX_RAB_UCSI_CONTROL_STOP; + status = ccg_write(uc, CCGX_RAB_UCSI_CONTROL, &data, sizeof(data)); + if (status < 0) + return status; - set_bit(RESET_PENDING, &uc->flags); + data = CCGX_RAB_UCSI_CONTROL_START; + status = ccg_write(uc, CCGX_RAB_UCSI_CONTROL, &data, sizeof(data)); + if (status < 0) + return status; - ret = ccg_send_command(uc, &cmd); - if (ret != RESET_COMPLETE) - goto err_clear_flag; + /* + * Flush CCGx RESPONSE queue by acking interrupts. Above ucsi control + * register write will push response which must be cleared. + */ + do { + status = ccg_read(uc, CCGX_RAB_INTR_REG, &data, sizeof(data)); + if (status < 0) + return status; - ret = 0; + if (!(data & DEV_INT)) + return 0; -err_clear_flag: - clear_bit(RESET_PENDING, &uc->flags); + status = ccg_write(uc, CCGX_RAB_INTR_REG, &data, sizeof(data)); + if (status < 0) + return status; - mutex_unlock(&uc->lock); + usleep_range(10000, 11000); + } while (--count); - return ret; + return -ETIMEDOUT; } -static int ccg_cmd_port_control(struct ucsi_ccg *uc, bool enable) +static void ucsi_ccg_update_get_current_cam_cmd(struct ucsi_ccg *uc, u8 *data) { - struct ccg_cmd cmd; - int ret; - - cmd.reg = CCGX_RAB_PDPORT_ENABLE; - if (enable) - cmd.data = (uc->port_num == 1) ? - PDPORT_1 : (PDPORT_1 | PDPORT_2); - else - cmd.data = 0x0; - cmd.len = 1; - cmd.delay = 10; + u8 cam, new_cam; - mutex_lock(&uc->lock); + cam = data[0]; + new_cam = uc->orig[cam].linked_idx; + uc->updated[new_cam].active_idx = cam; + data[0] = new_cam; +} - ret = ccg_send_command(uc, &cmd); +static bool ucsi_ccg_update_altmodes(struct ucsi *ucsi, + u8 recipient, + struct ucsi_altmode *orig, + struct ucsi_altmode *updated) +{ + struct ucsi_ccg *uc = ucsi_get_drvdata(ucsi); + struct ucsi_ccg_altmode *alt, *new_alt; + int i, j, k = 0; + bool found = false; + + if (recipient != UCSI_RECIPIENT_CON) + return false; + + alt = uc->orig; + new_alt = uc->updated; + memset(uc->updated, 0, sizeof(uc->updated)); + + /* + * Copy original connector altmodes to new structure. + * We need this before second loop since second loop + * checks for duplicate altmodes. + */ + for (i = 0; i < UCSI_MAX_ALTMODES; i++) { + alt[i].svid = orig[i].svid; + alt[i].mid = orig[i].mid; + if (!alt[i].svid) + break; + } - mutex_unlock(&uc->lock); + for (i = 0; i < UCSI_MAX_ALTMODES; i++) { + if (!alt[i].svid) + break; + + /* already checked and considered */ + if (alt[i].checked) + continue; + + if (!DP_CONF_GET_PIN_ASSIGN(alt[i].mid)) { + /* Found Non DP altmode */ + new_alt[k].svid = alt[i].svid; + new_alt[k].mid |= alt[i].mid; + new_alt[k].linked_idx = i; + alt[i].linked_idx = k; + updated[k].svid = new_alt[k].svid; + updated[k].mid = new_alt[k].mid; + k++; + continue; + } + + for (j = i + 1; j < UCSI_MAX_ALTMODES; j++) { + if (alt[i].svid != alt[j].svid || + !DP_CONF_GET_PIN_ASSIGN(alt[j].mid)) { + continue; + } else { + /* Found duplicate DP mode */ + new_alt[k].svid = alt[i].svid; + new_alt[k].mid |= alt[i].mid | alt[j].mid; + new_alt[k].linked_idx = UCSI_MULTI_DP_INDEX; + alt[i].linked_idx = k; + alt[j].linked_idx = k; + alt[j].checked = true; + found = true; + } + } - if (ret != CMD_SUCCESS) { - dev_err(uc->dev, "port control failed ret=%d\n", ret); - return ret; - } - return 0; + if (found) { + uc->has_multiple_dp = true; + } else { + /* Didn't find any duplicate DP altmode */ + new_alt[k].svid = alt[i].svid; + new_alt[k].mid |= alt[i].mid; + new_alt[k].linked_idx = i; + alt[i].linked_idx = k; + } + updated[k].svid = new_alt[k].svid; + updated[k].mid = new_alt[k].mid; + k++; + } + return found; } -static int ccg_cmd_jump_boot_mode(struct ucsi_ccg *uc, int bl_mode) +static void ucsi_ccg_update_set_new_cam_cmd(struct ucsi_ccg *uc, + struct ucsi_connector *con, + u64 *cmd) { - struct ccg_cmd cmd; - int ret; + struct ucsi_ccg_altmode *new_port, *port; + struct typec_altmode *alt = NULL; + u8 new_cam, cam, pin; + bool enter_new_mode; + int i, j, k = 0xff; + + port = uc->orig; + new_cam = UCSI_SET_NEW_CAM_GET_AM(*cmd); + if (new_cam >= ARRAY_SIZE(uc->updated)) + return; + new_port = &uc->updated[new_cam]; + cam = new_port->linked_idx; + enter_new_mode = UCSI_SET_NEW_CAM_ENTER(*cmd); + + /* + * If CAM is UCSI_MULTI_DP_INDEX then this is DP altmode + * with multiple DP mode. Find out CAM for best pin assignment + * among all DP mode. Priorite pin E->D->C after making sure + * the partner supports that pin. + */ + if (cam == UCSI_MULTI_DP_INDEX) { + if (enter_new_mode) { + for (i = 0; con->partner_altmode[i]; i++) { + alt = con->partner_altmode[i]; + if (alt->svid == new_port->svid) + break; + } + /* + * alt will always be non NULL since this is + * UCSI_SET_NEW_CAM command and so there will be + * at least one con->partner_altmode[i] with svid + * matching with new_port->svid. + */ + for (j = 0; port[j].svid; j++) { + pin = DP_CONF_GET_PIN_ASSIGN(port[j].mid); + if (alt && port[j].svid == alt->svid && + (pin & DP_CONF_GET_PIN_ASSIGN(alt->vdo))) { + /* prioritize pin E->D->C */ + if (k == 0xff || (k != 0xff && pin > + DP_CONF_GET_PIN_ASSIGN(port[k].mid)) + ) { + k = j; + } + } + } + cam = k; + new_port->active_idx = cam; + } else { + cam = new_port->active_idx; + } + } + *cmd &= ~UCSI_SET_NEW_CAM_AM_MASK; + *cmd |= UCSI_SET_NEW_CAM_SET_AM(cam); +} - cmd.reg = CCGX_RAB_JUMP_TO_BOOT; +/* + * Change the order of vdo values of NVIDIA test device FTB + * (Function Test Board) which reports altmode list with vdo=0x3 + * first and then vdo=0x. Current logic to assign mode value is + * based on order in altmode list and it causes a mismatch of CON + * and SOP altmodes since NVIDIA GPU connector has order of vdo=0x1 + * first and then vdo=0x3 + */ +static void ucsi_ccg_nvidia_altmode(struct ucsi_ccg *uc, + struct ucsi_altmode *alt, + u64 command) +{ + switch (UCSI_ALTMODE_OFFSET(command)) { + case NVIDIA_FTB_DP_OFFSET: + if (alt[0].mid == USB_TYPEC_NVIDIA_VLINK_DBG_VDO) + alt[0].mid = USB_TYPEC_NVIDIA_VLINK_DP_VDO | + DP_CAP_DP_SIGNALLING(0) | DP_CAP_USB | + DP_CONF_SET_PIN_ASSIGN(BIT(DP_PIN_ASSIGN_E)); + break; + case NVIDIA_FTB_DBG_OFFSET: + if (alt[0].mid == USB_TYPEC_NVIDIA_VLINK_DP_VDO) + alt[0].mid = USB_TYPEC_NVIDIA_VLINK_DBG_VDO; + break; + default: + break; + } +} - if (bl_mode) - cmd.data = TO_BOOT; - else - cmd.data = TO_ALT_FW; +static int ucsi_ccg_read_version(struct ucsi *ucsi, u16 *version) +{ + struct ucsi_ccg *uc = ucsi_get_drvdata(ucsi); + u16 reg = CCGX_RAB_UCSI_DATA_BLOCK(UCSI_VERSION); - cmd.len = 1; - cmd.delay = 100; + return ccg_read(uc, reg, (u8 *)version, sizeof(*version)); +} - mutex_lock(&uc->lock); +static int ucsi_ccg_read_cci(struct ucsi *ucsi, u32 *cci) +{ + struct ucsi_ccg *uc = ucsi_get_drvdata(ucsi); - set_bit(RESET_PENDING, &uc->flags); + spin_lock(&uc->op_lock); + *cci = uc->op_data.cci; + spin_unlock(&uc->op_lock); - ret = ccg_send_command(uc, &cmd); - if (ret != RESET_COMPLETE) - goto err_clear_flag; + return 0; +} - ret = 0; +static int ucsi_ccg_read_message_in(struct ucsi *ucsi, void *val, size_t val_len) +{ + struct ucsi_ccg *uc = ucsi_get_drvdata(ucsi); -err_clear_flag: - clear_bit(RESET_PENDING, &uc->flags); + spin_lock(&uc->op_lock); + memcpy(val, uc->op_data.message_in, val_len); + spin_unlock(&uc->op_lock); - mutex_unlock(&uc->lock); + return 0; +} - return ret; +static int ucsi_ccg_async_control(struct ucsi *ucsi, u64 command) +{ + struct ucsi_ccg *uc = ucsi_get_drvdata(ucsi); + u16 reg = CCGX_RAB_UCSI_DATA_BLOCK(UCSI_CONTROL); + + /* + * UCSI may read CCI instantly after async_control, + * clear CCI to avoid caller getting wrong data before we get CCI from ISR + */ + spin_lock(&uc->op_lock); + uc->op_data.cci = 0; + spin_unlock(&uc->op_lock); + + return ccg_write(uc, reg, (u8 *)&command, sizeof(command)); } -static int -ccg_cmd_write_flash_row(struct ucsi_ccg *uc, u16 row, - const void *data, u8 fcmd) +static int ucsi_ccg_sync_control(struct ucsi *ucsi, u64 command, u32 *cci, + void *data, size_t size, void *msg_out, + size_t msg_out_size) { - struct i2c_client *client = uc->client; - struct ccg_cmd cmd; - u8 buf[CCG4_ROW_SIZE + 2]; - u8 *p; - int ret; + struct ucsi_ccg *uc = ucsi_get_drvdata(ucsi); + struct ucsi_connector *con; + int con_index; + int ret; + + mutex_lock(&uc->lock); + pm_runtime_get_sync(uc->dev); + + if (UCSI_COMMAND(command) == UCSI_SET_NEW_CAM && + uc->has_multiple_dp) { + con_index = (command >> 16) & + UCSI_CMD_CONNECTOR_MASK; + if (con_index == 0) { + ret = -EINVAL; + goto err_put; + } + con = &uc->ucsi->connector[con_index - 1]; + ucsi_ccg_update_set_new_cam_cmd(uc, con, &command); + } + + ret = ucsi_sync_control_common(ucsi, command, cci, data, size, + msg_out, msg_out_size); - /* Copy the data into the flash read/write memory. */ - put_unaligned_le16(REG_FLASH_RW_MEM, buf); + switch (UCSI_COMMAND(command)) { + case UCSI_GET_CURRENT_CAM: + if (uc->has_multiple_dp) + ucsi_ccg_update_get_current_cam_cmd(uc, (u8 *)data); + break; + case UCSI_GET_ALTERNATE_MODES: + if (UCSI_ALTMODE_RECIPIENT(command) == UCSI_RECIPIENT_SOP) { + struct ucsi_altmode *alt = data; + + if (alt[0].svid == USB_TYPEC_NVIDIA_VLINK_SID) + ucsi_ccg_nvidia_altmode(uc, alt, command); + } + break; + case UCSI_GET_CAPABILITY: + if (uc->fw_build == CCG_FW_BUILD_NVIDIA_TEGRA) { + struct ucsi_capability *cap = data; + + cap->features &= ~UCSI_CAP_ALT_MODE_DETAILS; + } + struct ucsi_capability *cap = data; + cap->features &= ~UCSI_CAP_ALT_MODE_DETAILS; + cap->features &= ~UCSI_CAP_PDO_DETAILS; + break; + default: + break; + } - memcpy(buf + 2, data, CCG4_ROW_SIZE); +err_put: + pm_runtime_put_sync(uc->dev); + mutex_unlock(&uc->lock); - mutex_lock(&uc->lock); + return ret; +} - ret = i2c_master_send(client, buf, CCG4_ROW_SIZE + 2); - if (ret != CCG4_ROW_SIZE + 2) { - dev_err(uc->dev, "REG_FLASH_RW_MEM write fail %d\n", ret); - mutex_unlock(&uc->lock); - return ret < 0 ? ret : -EIO; - } - /* Use the FLASH_ROW_READ_WRITE register to trigger */ - /* writing of data to the desired flash row */ - p = (u8 *)&cmd.data; - cmd.reg = CCGX_RAB_FLASH_ROW_RW; - p[0] = FLASH_SIG; - p[1] = fcmd; - put_unaligned_le16(row, &p[2]); - cmd.len = 4; - cmd.delay = 50; - if (fcmd == FLASH_FWCT_SIG_WR_CMD) - cmd.delay += 400; - if (row == 510) - cmd.delay += 220; - ret = ccg_send_command(uc, &cmd); - - mutex_unlock(&uc->lock); - - if (ret != CMD_SUCCESS) { - dev_err(uc->dev, "write flash row failed ret=%d\n", ret); - return ret; - } - return 0; -} +static const struct ucsi_operations ucsi_ccg_ops = { + .read_version = ucsi_ccg_read_version, + .read_cci = ucsi_ccg_read_cci, + .poll_cci = ucsi_ccg_read_cci, + .read_message_in = ucsi_ccg_read_message_in, + .sync_control = ucsi_ccg_sync_control, + .async_control = ucsi_ccg_async_control, + .update_altmodes = ucsi_ccg_update_altmodes, +}; -static int ccg_cmd_validate_fw(struct ucsi_ccg *uc, unsigned int fwid) +static irqreturn_t ccg_irq_handler(int irq, void *data) { - struct ccg_cmd cmd; - int ret; + u16 reg = CCGX_RAB_UCSI_DATA_BLOCK(UCSI_CCI); + struct ucsi_ccg *uc = data; + u8 intr_reg; + u32 cci = 0; + int ret = 0; + + ret = ccg_read(uc, CCGX_RAB_INTR_REG, &intr_reg, sizeof(intr_reg)); + if (ret) + return ret; + + if (!intr_reg) + return IRQ_HANDLED; + else if (!(intr_reg & UCSI_READ_INT)) + goto err_clear_irq; + + ret = ccg_read(uc, reg, (void *)&cci, sizeof(cci)); + if (ret) + goto err_clear_irq; + + /* + * As per CCGx UCSI interface guide, copy CCI and MESSAGE_IN + * to the OpRegion before clear the UCSI interrupt + */ + ret = ccg_op_region_update(uc, cci); + if (ret) + goto err_clear_irq; +err_clear_irq: + ccg_write(uc, CCGX_RAB_INTR_REG, &intr_reg, sizeof(intr_reg)); - cmd.reg = CCGX_RAB_VALIDATE_FW; - cmd.data = fwid; - cmd.len = 1; - cmd.delay = 500; + if (!ret) + ucsi_notify_common(uc->ucsi, cci); - mutex_lock(&uc->lock); + return IRQ_HANDLED; +} - ret = ccg_send_command(uc, &cmd); +static int ccg_request_irq(struct ucsi_ccg *uc) +{ + unsigned long flags = IRQF_ONESHOT; - mutex_unlock(&uc->lock); + if (!dev_fwnode(uc->dev)) + flags |= IRQF_TRIGGER_HIGH; - if (ret != CMD_SUCCESS) - return ret; + return request_threaded_irq(uc->irq, NULL, ccg_irq_handler, flags, dev_name(uc->dev), uc); +} - return 0; +static void ccg_pm_workaround_work(struct work_struct *pm_work) +{ + ccg_irq_handler(0, container_of(pm_work, struct ucsi_ccg, pm_work)); } -static bool ccg_check_vendor_version(struct ucsi_ccg *uc, - struct version_format *app, - struct fw_config_table *fw_cfg) +/* Read one flash row into buf */ +static int ccg_cmd_read_flash_row(struct ucsi_ccg *uc, u16 row_idx, u8 *buf) { - struct device *dev = uc->dev; + int err; u8 rsp = 0; + + u8 rw_cmd[4] = { FLASH_SIG, FLASH_RD_CMD, (u8)(row_idx & 0xFF), (u8)(row_idx >> 8) }; + + if (!buf) + return -EINVAL; + err = ccg_hpi_write16(uc, HPI_ADDR_FLASH_RW_CMD, rw_cmd, sizeof(rw_cmd)); + if (err) + return err; + for (int i = 0; i < 200; i++) { + usleep_range(1000, 2000); + if (!ccg_try_read_cmd_status(uc, &rsp) && rsp != 0x00) + break; + } + if (rsp != HPI_RSP_FLASH_DATA_AVAIL && rsp != HPI_RSP_SUCCESS) + return (rsp == 0x00) ? -ETIMEDOUT : -EIO; + return ccg_hpi_read16(uc, HPI_ADDR_FLASH_RW_MEM, buf, CCG_ROW_SIZE); +} - /* Check if the fw build is for supported vendors */ - if (le16_to_cpu(app->build) != uc->fw_build) { - dev_info(dev, "current fw is not from supported vendor\n"); - return false; - } +/* Port control and reset via HPI (simple, non-ccg_send_command path) */ +static int ccg_cmd_port_control(struct ucsi_ccg *uc, bool enable) +{ + u8 val = enable ? (HPI_PDPORT_EN_PORT0 | HPI_PDPORT_EN_PORT1) : 0x00; - /* Check if the new fw build is for supported vendors */ - if (le16_to_cpu(fw_cfg->app.build) != uc->fw_build) { - dev_info(dev, "new fw is not from supported vendor\n"); - return false; - } - return true; + return ccg_hpi_write16(uc, HPI_ADDR_PDPORT_ENABLE, &val, 1); } -static bool ccg_check_fw_version(struct ucsi_ccg *uc, const char *fw_name, - struct version_format *app) +static int ccg_cmd_reset(struct ucsi_ccg *uc) { - const struct firmware *fw = NULL; - struct device *dev = uc->dev; - struct fw_config_table fw_cfg; - u32 cur_version, new_version; - bool is_later = false; + /* Write: Byte[0] = 'R', Byte[1] = 1 (Device Reset) to HPI_ADDR_RESET */ + u8 buf[2] = { HPI_SIG_RESET, HPI_RESET_TYPE_DEVICE }; + int ret = ccg_hpi_write16(uc, HPI_ADDR_RESET, buf, sizeof(buf)); + + /* During reset, bus can NACK. Treat transport errors as acceptable here. */ + if (ret == -ENXIO || ret == -EREMOTEIO || ret == -EIO) + return 0; + return ret; +} - if (request_firmware(&fw, fw_name, dev) != 0) { - dev_err(dev, "error: Failed to open cyacd file %s\n", fw_name); - return false; - } +static int ccg_cmd_jump_boot_mode(struct ucsi_ccg *uc, int bl_mode) +{ + struct ccg_cmd cmd; + int ret; - /* - * check if signed fw - * last part of fw image is fw cfg table and signature - */ - if (fw->size < sizeof(fw_cfg) + FW_CFG_TABLE_SIG_SIZE) - goto out_release_firmware; + cmd.reg = CCGX_RAB_JUMP_TO_BOOT; - memcpy((uint8_t *)&fw_cfg, fw->data + fw->size - - sizeof(fw_cfg) - FW_CFG_TABLE_SIG_SIZE, sizeof(fw_cfg)); + if (bl_mode) + cmd.data = TO_BOOT; + else + cmd.data = TO_ALT_FW; - if (fw_cfg.identity != ('F' | 'W' << 8 | 'C' << 16 | 'T' << 24)) { - dev_info(dev, "not a signed image\n"); - goto out_release_firmware; - } + cmd.len = 1; + cmd.delay = 100; - /* compare input version with FWCT version */ - cur_version = le16_to_cpu(app->build) | CCG_VERSION_PATCH(app->patch) | - CCG_VERSION(app->ver); + mutex_lock(&uc->lock); - new_version = le16_to_cpu(fw_cfg.app.build) | - CCG_VERSION_PATCH(fw_cfg.app.patch) | - CCG_VERSION(fw_cfg.app.ver); + set_bit(RESET_PENDING, &uc->flags); - if (!ccg_check_vendor_version(uc, app, &fw_cfg)) - goto out_release_firmware; + ret = ccg_send_command(uc, &cmd); + if (ret != RESET_COMPLETE) + goto err_clear_flag; - if (new_version > cur_version) - is_later = true; + ret = 0; -out_release_firmware: - release_firmware(fw); - return is_later; +err_clear_flag: + clear_bit(RESET_PENDING, &uc->flags); + + mutex_unlock(&uc->lock); + + return ret; } +static int ccg_enter_flashing_robust(struct ucsi_ccg *uc, u8 jump_sig) +{ + int err; + u8 devmode = 0; + u8 sig; + + /* 1. Check device mode */ + ccg_read_device_mode(uc, &devmode); + dev_info(uc->dev, + "diag: ENTER ccg_enter_flashing_robust, DEVICE_MODE=0x%02x", + devmode); + + /* 3. Disable PD ports */ + err = ccg_disable_pd_ports(uc); + if (err) { + dev_err(uc->dev, "disable PD failed: %d", err); + return err; + } + + /* Optional: clear/consume any pending async after PD disable */ + ccg_wait_success_or_error(uc, "after PD disable", 200); + + /* 5. Jump to boot/alt based on target bank. + * For now we use 'A' (alternate) as existing code. + * Later you can pass jump char from do_flash: 'J' for primary, 'A' for secondary. + */ + ccg_drain_responses(uc); + dev_info(uc->dev, + "diag: JUMP_TO_BOOT for bank switch needed:(%d)", + jump_sig); + + err = ccg_cmd_jump_boot_mode(uc, 1); + if (err) + dev_err(uc->dev, "JUMP_TO_BOOT write failed (%d)", err); + /* 6. Wait for async event for JUMP_TO_BOOT (RESET_COMPLETE etc.) */ + ccg_wait_success_or_error(uc, "after JUMP_TO_BOOT", 1000); + + /* 8. Re-read DEVICE_MODE */ + ccg_read_device_mode(uc, &devmode); + dev_info(uc->dev, + "diag: after JUMP_TO_BOOT, DEVICE_MODE=0x%02x", + devmode); + + /* Even if devmode still looks like app, we proceed; you can add + * a strict check and fail here if needed. + */ + + /* 9. Initiate flashing: write 'P' to ENTER_FLASHING_MODE (0x000A) */ + ccg_drain_responses(uc); + sig = HPI_SIG_ENTER_FLASHING; /* 'P' */ + dev_info(uc->dev, "diag: issuing ENTER_FLASHING (post-jump)"); + + err = ccg_hpi_write16(uc, HPI_ADDR_ENTER_FLASHING_MODE, &sig, 1); + if (err) { + dev_err(uc->dev, "ENTER_FLASHING write failed (%d)", err); + return err; + } + + /* 10. Wait async for ENTER_FLASHING completion */ + ccg_wait_success_or_error(uc, "after ENTER_FLASHING", 1000); + + return 0; +} + static int ccg_fw_update_needed(struct ucsi_ccg *uc, enum enum_flash_mode *mode) { @@ -1126,6 +2160,8 @@ static int ccg_fw_update_needed(struct ucsi_ccg *uc, dev_err(dev, "read device mode failed\n"); return err; } + *mode = SECONDARY; + return 0; if (memcmp(&version[FW1], "\0\0\0\0\0\0\0\0", sizeof(struct version_info)) == 0) { @@ -1155,166 +2191,284 @@ static int do_flash(struct ucsi_ccg *uc, enum enum_flash_mode mode) { struct device *dev = uc->dev; const struct firmware *fw = NULL; - const char *p, *s; - const char *eof; - int err, row, len, line_sz, line_cnt = 0; - unsigned long start_time = jiffies; - struct fw_config_table fw_cfg; - u8 fw_cfg_sig[FW_CFG_TABLE_SIG_SIZE]; - u8 *wr_buf; - - err = request_firmware(&fw, ccg_fw_names[mode], dev); + const char *fwname = NULL; + u8 devmode = 0; + u16 bl_last = 0; + u32 bin_loc = 0; + u16 fw1_start = 0, fw2_start = 0; + u16 rows_per_bank = 0; + int err = 0; + + struct ccg_row_list abs = {0}; + u8 target_bank; + u16 base, limit, meta_idx; + int i; + + err = ccg_read_device_mode(uc, &devmode); + if (err) { + dev_err(dev, "read DEVICE_MODE failed (%d)", err); + return err; + } + err = ccg_read_u16(uc, HPI_ADDR_BOOT_LOADER_LAST_ROW, &bl_last); + if (err) { + dev_err(dev, "read BL_LAST_ROW failed (%d)", err); + return err; + } + err = ccg_read_u32(uc, HPI_ADDR_FIRMWARE_BIN_LOCATION, &bin_loc); if (err) { - dev_err(dev, "request %s failed err=%d\n", - ccg_fw_names[mode], err); + dev_err(dev, "read FIRMWARE_BIN_LOCATION failed (%d)", err); return err; } - if (((uc->info.mode & CCG_DEVINFO_FWMODE_MASK) >> - CCG_DEVINFO_FWMODE_SHIFT) == FW2) { - err = ccg_cmd_port_control(uc, false); - if (err < 0) - goto release_fw; - err = ccg_cmd_jump_boot_mode(uc, 0); - if (err < 0) - goto release_fw; + { + const bool hybrid = ccg_is_hybrid(uc); + u32 loc = 0; + u16 fw1 = 0, fw2 = 0; + + if (!ccg_read_u32(uc, HPI_ADDR_FIRMWARE_BIN_LOCATION, &loc)) { + fw1 = (u16)(loc & 0xFFFF); + fw2 = (u16)((loc >> 16) & 0xFFFF); + } + + pr_err("Ak: hybrid:%d\n", hybrid); + if (hybrid) { + fw1_start = max_t(u16, (u16)(bl_last + 1), 2); + rows_per_bank = (u16)(CCG_ROWS_TOTAL / 2); + fw2_start = (u16)(fw1_start + rows_per_bank); + } else if (fw2 > fw1 && fw2 < CCG_ROWS_TOTAL) { + fw1_start = fw1; + fw2_start = fw2; + rows_per_bank = (u16)(fw2 - fw1); + } else { + fw1_start = max_t(u16, (u16)(bl_last + 1), 2); + rows_per_bank = (u16)(CCG_ROWS_TOTAL / 2); + fw2_start = (u16)(fw1_start + rows_per_bank); + } } - eof = fw->data + fw->size; + dev_info(dev, "DEVICE_MODE=0x%02x row_size=%u BL_LAST=0x%04x FW1_START=%u FW2_START=%u rows_per_bank=%u", + devmode, CCG_ROW_SIZE, bl_last, fw1_start, fw2_start, rows_per_bank); - /* - * check if signed fw - * last part of fw image is fw cfg table and signature - */ - if (fw->size < sizeof(fw_cfg) + sizeof(fw_cfg_sig)) - goto not_signed_fw; + { + if (fw_file_override[0]) + fwname = fw_file_override; + else + fwname = "ccg_secondary.cyacd2"; - memcpy((uint8_t *)&fw_cfg, fw->data + fw->size - - sizeof(fw_cfg) - sizeof(fw_cfg_sig), sizeof(fw_cfg)); + dev_info(dev, "requesting firmware: %s", fwname); + err = request_firmware(&fw, fwname, dev); + if (err) { + dev_err(dev, "request_firmware(%s) failed (%d)", fwname, err); + return err; + } - if (fw_cfg.identity != ('F' | ('W' << 8) | ('C' << 16) | ('T' << 24))) { - dev_info(dev, "not a signed image\n"); - goto not_signed_fw; - } - eof = fw->data + fw->size - sizeof(fw_cfg) - sizeof(fw_cfg_sig); - - memcpy((uint8_t *)&fw_cfg_sig, - fw->data + fw->size - sizeof(fw_cfg_sig), sizeof(fw_cfg_sig)); - - /* flash fw config table and signature first */ - err = ccg_cmd_write_flash_row(uc, 0, (u8 *)&fw_cfg, - FLASH_FWCT1_WR_CMD); - if (err) - goto release_fw; - - err = ccg_cmd_write_flash_row(uc, 0, (u8 *)&fw_cfg + CCG4_ROW_SIZE, - FLASH_FWCT2_WR_CMD); - if (err) - goto release_fw; - - err = ccg_cmd_write_flash_row(uc, 0, &fw_cfg_sig, - FLASH_FWCT_SIG_WR_CMD); - if (err) - goto release_fw; - -not_signed_fw: - wr_buf = kzalloc(CCG4_ROW_SIZE + 4, GFP_KERNEL); - if (!wr_buf) { - err = -ENOMEM; - goto release_fw; - } + err = ccg_parse_and_build_rows(dev, fw->data, fw->size, + fw1_start, fw2_start, + rows_per_bank, target_bank, + &abs); + if (err) { + dev_err(dev, "parse rows failed (%d)", err); + release_firmware(fw); + return err; + } - err = ccg_cmd_enter_flashing(uc); - if (err) - goto release_mem; - - /***************************************************************** - * CCG firmware image (.cyacd) file line format - * - * :00rrrrllll[dd....]cc/r/n - * - * :00 header - * rrrr is row number to flash (4 char) - * llll is data len to flash (4 char) - * dd is a data field represents one byte of data (512 char) - * cc is checksum (2 char) - * \r\n newline - * - * Total length: 3 + 4 + 4 + 512 + 2 + 2 = 527 - * - *****************************************************************/ - - p = strnchr(fw->data, fw->size, ':'); - if (!p) { - dev_err(dev, "Bad FW format: no ':' record header found\n"); - err = -EINVAL; - goto release_mem; - } - while (p < eof) { - s = strnchr(p + 1, eof - p - 1, ':'); + /* --- 3. Parse Firmware File --- */ + dev_info(dev, "Successfully parsed %d rows from binary firmware.", abs.count); - if (!s) - s = eof; + for (i = 0; i < min(abs.count, 256); i++) { + dev_info(dev, + "abs[%d]: row=0x%04x first4=%02x %02x %02x %02x\n", + i, abs.rows[i].row, + abs.rows[i].data[0], abs.rows[i].data[1], + abs.rows[i].data[2], abs.rows[i].data[3]); + } - line_sz = s - p; + /* Optional APPINFO logging only */ + { + u32 app_start_bytes = 0, app_size_bytes = 0; - if (line_sz != CYACD_LINE_SIZE) { - dev_err(dev, "Bad FW format line_sz=%d\n", line_sz); - err = -EINVAL; - goto release_mem; + if (!ccg_parse_appinfo_text(fw->data, fw->size, + &app_start_bytes, &app_size_bytes)) + dev_info(dev, "@APPINFO: start=0x%x size=0x%x", + app_start_bytes, app_size_bytes); } - if (hex2bin(wr_buf, p + 3, CCG4_ROW_SIZE + 4)) { - err = -EINVAL; - goto release_mem; + err = ccg_enter_flashing_robust(uc, 0); + if (err) { + dev_err(dev, "enter flashing failed (%d)", err); + kfree(abs.rows); + release_firmware(fw); + WRITE_ONCE(uc->updating, false); + return err; } - row = get_unaligned_be16(wr_buf); - len = get_unaligned_be16(&wr_buf[2]); + base = (target_bank == 0) ? fw1_start : fw2_start; + limit = base + rows_per_bank; + meta_idx = (target_bank == 1) ? META_IDX_FW1 : META_IDX_FW2; + + dev_info(dev, "target span: base=%u limit=%u BL_LAST=0x%04x", base, limit, bl_last); + dev_info(dev, "target metadata idx=0x%04x", meta_idx); + dev_info(dev, "target metadata abs.count:%d", abs.count); + + /* Clear metadata row early */ + { + u8 zero[CCG_ROW_SIZE] = {0}; + + int err = ccg_cmd_write_flash_row(uc, meta_idx, zero, FLASH_WR_CMD); - if (len != CCG4_ROW_SIZE) { - err = -EINVAL; - goto release_mem; + if (err) + dev_err(dev, "Write to row 0x%04x failed (%d)", meta_idx, err); + + ccg_wait_success_or_error(uc, "after metadata clear", 200); } - err = ccg_cmd_write_flash_row(uc, row, wr_buf + 4, - FLASH_WR_CMD); - if (err) - goto release_mem; + /* Find metadata row in parsed image */ + { + int rows_written = 0; + + for (i = 0; i < abs.count; i++) { + u16 row_idx = abs.rows[i].row; + + /* Optional: keep some safety filters */ + if (row_idx <= bl_last) /* don’t touch bootloader rows */ + continue; + if (row_idx >= CCG_ROWS_TOTAL) /* outside flash range */ + continue; + + err = ccg_cmd_write_flash_row(uc, row_idx, + abs.rows[i].data, + HPI_FLASH_CMD_WRITE); + if (err) { + dev_err(dev, "Write to row 0x%04x failed (%d)", + row_idx, err); + /* Continue on error to allow for protected row rejection */ + continue; + } + rows_written++; + } - line_cnt++; - p = s; - } + ccg_wait_success_or_error(uc, "after data write", 200); + dev_info(dev, "total %d rows flashed (including metadata) target bank:%d", + rows_written, target_bank); + } - dev_info(dev, "total %d row flashed. time: %dms\n", - line_cnt, jiffies_to_msecs(jiffies - start_time)); + /* Optional: sanity readback using direct row indices from abs[] */ + { + int max_read = min(abs.count, 256); /* limit log spam */ + + for (i = 0; i < max_read; i++) { + u16 row_idx = abs.rows[i].row; + u8 rb[4] = {0}; + + /* Same safety filters as write, if you want them */ + if (row_idx <= bl_last) + continue; + if (row_idx >= CCG_ROWS_TOTAL) + continue; + + err = ccg_cmd_read_flash_row(uc, row_idx, rb); + if (!err) { + dev_info(dev, "readback row 0x%04x first4=%02x %02x %02x %02x", + row_idx, rb[0], rb[1], rb[2], rb[3]); + } else { + dev_err(dev, "readback row 0x%04x failed (%d)", + row_idx, err); + } + } + } - err = ccg_cmd_validate_fw(uc, (mode == PRIMARY) ? FW2 : FW1); - if (err) - dev_err(dev, "%s validation failed err=%d\n", - (mode == PRIMARY) ? "FW2" : "FW1", err); - else - dev_info(dev, "%s validated\n", - (mode == PRIMARY) ? "FW2" : "FW1"); + /* --- ADD THIS SNIPPET to verify specific rows --- */ + dev_info(dev, "--- Verifying specific rows ---"); + { + u8 temp_row_buf[CCG_ROW_SIZE] = {0}; + u16 rows_to_check[] = { + 0x0067, /* row from :00670000 */ + 0x006C, /* row from :006C0000 */ + 0x00FF, /* row from :00FF0000 */ + 0x0100, /* row from :00000100 */ + 0x0101, /* row from :00010100 */ + 0x01FE, /* row from :00FE0100 */ + }; + + for (i = 0; i < ARRAY_SIZE(rows_to_check); i++) { + u16 row_to_read = rows_to_check[i]; + + err = ccg_cmd_read_flash_row(uc, row_to_read, temp_row_buf); + if (!err) { + dev_info(dev, "readback row 0x%04x first16: %*phN", + row_to_read, 16, temp_row_buf); + } else { + dev_err(dev, "readback row 0x%04x failed (%d)", + row_to_read, err); + } + } + } - err = ccg_cmd_port_control(uc, false); - if (err < 0) - goto release_mem; + // Now, call VALIDATE_FW. The device will find the metadata row you just wrote. + { + u8 validate_id = 0x01; // Should be 0x01 for primary - err = ccg_cmd_reset(uc); - if (err < 0) - goto release_mem; + err = ccg_cmd_validate_fw(uc, validate_id); + ccg_wait_success_or_error(uc, "after VALIDATE_FW", 1000); + } - err = ccg_cmd_port_control(uc, true); - if (err < 0) - goto release_mem; + { + u8 md2[CCG_ROW_SIZE] = {0}; + + ccg_cmd_read_flash_row(uc, META_IDX_FW2, md2); + dev_info(dev, "FW2 meta[14..17] seq=0x%02x%02x%02x%02x, valid=0x%02x%02x, crc=0x%02x%02x%02x%02x", + md2[0x14], md2[0x15], md2[0x16], md2[0x17], + md2[0x56], md2[0x57], + md2[0x58], md2[0x59], md2[0x5A], md2[0x5B]); + + // Read FW1 metadata + u8 md1[CCG_ROW_SIZE] = {0}; + + ccg_cmd_read_flash_row(uc, META_IDX_FW1, md1); + dev_info(dev, "FW1 meta[14..17] seq=0x%02x%02x%02x%02x, valid=0x%02x%02x, crc=0x%02x%02x%02x%02x", + md1[0x14], md1[0x15], md1[0x16], md1[0x17], + md1[0x56], md1[0x57], + md1[0x58], md1[0x59], md1[0x5A], md1[0x5B]); + } -release_mem: - kfree(wr_buf); + dev_info(dev, "diag: issuing RESET after VALIDATE_FW"); + err = ccg_cmd_reset(uc); + dev_info(dev, "diag: ccg_cmd_reset() returned %d", err); + ccg_wait_success_or_error(uc, "after RESET", 200); + + { + int j; + u8 dm2 = 0; + int dm_err; + + msleep(300); + + for (j = 0; j < 5; j++) { + dm_err = ccg_read(uc, CCGX_RAB_DEVICE_MODE, &dm2, sizeof(dm2)); + if (!dm_err) { + dev_info(dev, + "diag: post-reset DEVICE_MODE=0x%02x (read ok)", + dm2); + break; + dm_err = ccg_read(uc, CCGX_RAB_DEVICE_MODE, &dm2, sizeof(dm2)); + if (!dm_err) { + dev_info(dev, + "diag: post-reset DEVICE_MODE=0x%02x (read ok)", + dm2); + break; + } + dev_info(dev, + "diag: post-reset DEVICE_MODE read failed (%d), retry %d", + dm_err, j + 1); + msleep(100); + } + } + } -release_fw: - release_firmware(fw); - return err; + kfree(abs.rows); + release_firmware(fw); + } + return 0; } /******************************************************************************* @@ -1325,20 +2479,25 @@ static int do_flash(struct ucsi_ccg *uc, enum enum_flash_mode mode) ******************************************************************************/ static int ccg_fw_update(struct ucsi_ccg *uc, enum enum_flash_mode flash_mode) { - int err = 0; + int err = 0; + bool forced = false; + + /* detect the one-shot forced modes */ + if (uc->force_once && + (flash_mode == PRIMARY || flash_mode == SECONDARY)) + forced = true; + + WRITE_ONCE(uc->updating, true); + err = do_flash(uc, flash_mode); + WRITE_ONCE(uc->updating, false); + if (err < 0) + return err; + dev_info(uc->dev, "CCG FW update successful\n"); + + return err; +} - while (flash_mode != FLASH_NOT_NEEDED) { - err = do_flash(uc, flash_mode); - if (err < 0) - return err; - err = ccg_fw_update_needed(uc, &flash_mode); - if (err < 0) - return err; - } - dev_info(uc->dev, "CCG FW update successful\n"); - return err; -} static int ccg_restart(struct ucsi_ccg *uc) { @@ -1377,57 +2536,57 @@ static void ccg_update_firmware(struct work_struct *work) if (status < 0) return; - if (flash_mode != FLASH_NOT_NEEDED) { + //if (flash_mode != FLASH_NOT_NEEDED) { ucsi_unregister(uc->ucsi); pm_runtime_disable(uc->dev); free_irq(uc->irq, uc); ccg_fw_update(uc, flash_mode); ccg_restart(uc); - } + //} } static ssize_t do_flash_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t n) + struct device_attribute *attr, + const char *buf, size_t n) { - struct ucsi_ccg *uc = i2c_get_clientdata(to_i2c_client(dev)); - bool flash; + struct ucsi_ccg *uc = i2c_get_clientdata(to_i2c_client(dev)); + bool flash; - if (kstrtobool(buf, &flash)) - return -EINVAL; + if (kstrtobool(buf, &flash)) + return -EINVAL; - if (!flash) - return n; + if (!flash) + return n; - schedule_work(&uc->work); - return n; + schedule_work(&uc->work); + return n; } static umode_t ucsi_ccg_attrs_is_visible(struct kobject *kobj, struct attribute *attr, int idx) { - struct device *dev = kobj_to_dev(kobj); - struct ucsi_ccg *uc = i2c_get_clientdata(to_i2c_client(dev)); + struct device *dev = kobj_to_dev(kobj); + struct ucsi_ccg *uc = i2c_get_clientdata(to_i2c_client(dev)); - if (!uc->fw_build) - return 0; + //if (!uc->fw_build) + // return 0; - return attr->mode; + return attr->mode; } static DEVICE_ATTR_WO(do_flash); static struct attribute *ucsi_ccg_attrs[] = { - &dev_attr_do_flash.attr, - NULL, + &dev_attr_do_flash.attr, + NULL, }; static struct attribute_group ucsi_ccg_attr_group = { - .attrs = ucsi_ccg_attrs, - .is_visible = ucsi_ccg_attrs_is_visible, + .attrs = ucsi_ccg_attrs, + .is_visible = ucsi_ccg_attrs_is_visible, }; static const struct attribute_group *ucsi_ccg_groups[] = { - &ucsi_ccg_attr_group, - NULL, + &ucsi_ccg_attr_group, + NULL, }; static int ucsi_ccg_probe(struct i2c_client *client) @@ -1437,6 +2596,7 @@ static int ucsi_ccg_probe(struct i2c_client *client) const char *fw_name; int status; + pr_err("Ak:%s called##########################\n",__func__); uc = devm_kzalloc(dev, sizeof(*uc), GFP_KERNEL); if (!uc) return -ENOMEM; @@ -1455,8 +2615,10 @@ static int ucsi_ccg_probe(struct i2c_client *client) uc->fw_build = CCG_FW_BUILD_NVIDIA_TEGRA; else if (!strcmp(fw_name, "nvidia,gpu")) uc->fw_build = CCG_FW_BUILD_NVIDIA; - if (!uc->fw_build) + if (!uc->fw_build) { + uc->fw_build = CCG_FW_BUILD_NVIDIA; dev_err(uc->dev, "failed to get FW build information\n"); + } } /* reset ccg device and initialize ucsi */ @@ -1472,6 +2634,39 @@ static int ucsi_ccg_probe(struct i2c_client *client) return status; } + if (!uc->fw_version) { + enum enum_flash_mode flash_mode; + + dev_info(uc->dev, "fw_version is empty, flashing firmware in probe\n"); + + status = ccg_fw_update_needed(uc, &flash_mode); + if (status < 0) { + dev_err(uc->dev, "ccg_fw_update_needed failed - %d\n", status); + return status; + } + + if (flash_mode != FLASH_NOT_NEEDED) { + status = ccg_fw_update(uc, flash_mode); + if (status < 0) { + dev_err(uc->dev, "ccg_fw_update failed - %d\n", status); + return status; + } + + /* do_flash() resets the device, reinitialize ucsi control */ + status = ucsi_ccg_init(uc); + if (status < 0) { + dev_err(uc->dev, "ucsi_ccg_init failed after fw update - %d\n", status); + return status; + } + + status = get_fw_info(uc); + if (status < 0) { + dev_err(uc->dev, "get_fw_info failed after fw update - %d\n", status); + return status; + } + } + } + uc->port_num = 1; if (uc->info.mode & CCG_DEVINFO_PDPORTS_MASK) @@ -1489,20 +2684,26 @@ static int ucsi_ccg_probe(struct i2c_client *client) goto out_ucsi_destroy; } - status = ucsi_register(uc->ucsi); - if (status) - goto out_free_irq; + dev_info(uc->dev, "uc->fw_version:%d\n", uc->fw_version); i2c_set_clientdata(client, uc); + if (uc->fw_version) { + status = ucsi_register(uc->ucsi); + if (status) + goto out_free_irq; + } + + device_disable_async_suspend(uc->dev); - pm_runtime_set_active(uc->dev); - pm_runtime_enable(uc->dev); - pm_runtime_use_autosuspend(uc->dev); - pm_runtime_set_autosuspend_delay(uc->dev, 5000); - pm_runtime_idle(uc->dev); + //pm_runtime_set_active(uc->dev); + //pm_runtime_enable(uc->dev); + //pm_runtime_use_autosuspend(uc->dev); + //pm_runtime_set_autosuspend_delay(uc->dev, 5000); + //pm_runtime_idle(uc->dev); + pr_err("Ak:%s done status:%d\n",__func__, status); return 0; out_free_irq: @@ -1528,7 +2729,6 @@ static void ucsi_ccg_remove(struct i2c_client *client) static const struct of_device_id ucsi_ccg_of_match_table[] = { { .compatible = "cypress,cypd4226", }, { .compatible = "cypress,cypd6129", }, - { .compatible = "cypress,cypd6229", }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, ucsi_ccg_of_match_table); @@ -1555,7 +2755,7 @@ static int ucsi_ccg_resume(struct device *dev) static int ucsi_ccg_runtime_suspend(struct device *dev) { - return 0; + return -1; } static int ucsi_ccg_runtime_resume(struct device *dev) @@ -1568,8 +2768,8 @@ static int ucsi_ccg_runtime_resume(struct device *dev) * of missing interrupt when a device is connected for runtime resume. * Schedule a work to call ISR as a workaround. */ - if (uc->fw_build == CCG_FW_BUILD_NVIDIA && - uc->fw_version <= CCG_OLD_FW_VERSION) + //if (uc->fw_build == CCG_FW_BUILD_NVIDIA && + // uc->fw_version <= CCG_OLD_FW_VERSION) schedule_work(&uc->pm_work); return 0; @@ -1584,7 +2784,7 @@ static const struct dev_pm_ops ucsi_ccg_pm = { static struct i2c_driver ucsi_ccg_driver = { .driver = { .name = "ucsi_ccg", - .pm = &ucsi_ccg_pm, + //.pm = &ucsi_ccg_pm, .dev_groups = ucsi_ccg_groups, .acpi_match_table = amd_i2c_ucsi_match, .of_match_table = ucsi_ccg_of_match_table, From f5a1b8021dfb64f20818bbb03daf5d44c20485aa Mon Sep 17 00:00:00 2001 From: Akash Kumar Date: Thu, 10 Sep 2026 22:56:36 +0530 Subject: [PATCH 4/4] FROMLIST: arm64: dts: qcom: shikra: Add gpio-hog for VBUS boost 5V enable Add a gpio-hog for GPIO 79 (BOOST_5V) on the Shikra CQM/CQS/IQS EVK boards. This line enables the level translator that is otherwise left disabled by default, which is required in the industrial configuration. Also fix the Shikra USB3-DP combo PHY (qcom,shikra-qmp-usb3-dp-phy) which had no matching driver entry, causing the DWC3 USB controller to fail probing with a deferred-probe cascade (typec, USB, and several sync_state() consumers). Add the missing of_device_id entry and a Shikra-specific PHY config that also deasserts the third GCC reset line present on Shikra's PHY node but absent from the shared QCS615 config, which was leaving part of the PHY held in reset and causing PHY init to time out (-ETIMEDOUT). CRs-Fixed: 4652998 CRs-Fixed: 4635219 Signed-off-by: Akash Kumar Signed-off-by: Akash Kumar --- .../phy/qcom,qcs615-qmp-usb3dp-phy.yaml | 1 + arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts | 7 +++ arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts | 7 +++ arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts | 7 +++ drivers/phy/qualcomm/phy-qcom-qmp-usbc.c | 47 +++++++++++++++++++ 5 files changed, 69 insertions(+) diff --git a/Documentation/devicetree/bindings/phy/qcom,qcs615-qmp-usb3dp-phy.yaml b/Documentation/devicetree/bindings/phy/qcom,qcs615-qmp-usb3dp-phy.yaml index efb465c71c1b..f41fe2a0a67b 100644 --- a/Documentation/devicetree/bindings/phy/qcom,qcs615-qmp-usb3dp-phy.yaml +++ b/Documentation/devicetree/bindings/phy/qcom,qcs615-qmp-usb3dp-phy.yaml @@ -18,6 +18,7 @@ properties: compatible: enum: - qcom,qcs615-qmp-usb3-dp-phy + - qcom,shikra-qmp-usb3-dp-phy reg: maxItems: 1 diff --git a/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts b/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts index 004d58ce3446..751dd3d18fe0 100644 --- a/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts +++ b/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts @@ -721,6 +721,13 @@ line-name = "emac0-phy-en"; }; + vbus_boost_5v: vbus-boost-5v { + gpio-hog; + gpios = <79 GPIO_ACTIVE_HIGH>; + output-high; + line-name = "vbus-boost-5v"; + }; + i2s2_default: i2s2-default-active-state { pins = "gpio100", "gpio101", "gpio102", "gpio103"; function = "i2s2"; diff --git a/arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts b/arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts index 0f016d993015..6b90fbced90f 100644 --- a/arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts +++ b/arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts @@ -694,6 +694,13 @@ line-name = "emac0-phy-en"; }; + vbus_boost_5v: vbus-boost-5v { + gpio-hog; + gpios = <79 GPIO_ACTIVE_HIGH>; + output-high; + line-name = "vbus-boost-5v"; + }; + i2s2_default: i2s2-default-active-state { pins = "gpio100", "gpio101", "gpio102", "gpio103"; function = "i2s2"; diff --git a/arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts b/arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts index 21b6f2f93c97..dc47c429b39f 100644 --- a/arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts +++ b/arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts @@ -801,6 +801,13 @@ line-name = "emac1-phy-en"; }; + vbus_boost_5v: vbus-boost-5v { + gpio-hog; + gpios = <79 GPIO_ACTIVE_HIGH>; + output-high; + line-name = "vbus-boost-5v"; + }; + ext_mclk2_active: ext-mclk2-state { pins = "gpio110"; function = "ext_mclk"; diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-usbc.c b/drivers/phy/qualcomm/phy-qcom-qmp-usbc.c index ab3055bb5b0c..23cc73531a92 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp-usbc.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp-usbc.c @@ -516,6 +516,10 @@ static const char * const usb3dpphy_reset_l[] = { "phy_phy", "dp_phy", }; +static const char * const usb3dpphy_reset_shikra_l[] = { + "phy_phy", "dp_phy", "phy", +}; + static const struct regulator_bulk_data qmp_phy_msm8998_vreg_l[] = { { .supply = "vdda-phy", .init_load_uA = 68600 }, { .supply = "vdda-pll", .init_load_uA = 14200 }, @@ -686,6 +690,46 @@ static const struct qmp_phy_cfg qcs615_usb3dp_phy_cfg = { .num_vregs = ARRAY_SIZE(qmp_phy_qcs615_vreg_l), }; +static const struct qmp_phy_cfg shikra_usb3dp_phy_cfg = { + .offsets = &qmp_usbc_usb3dp_offsets_qcs615, + + .serdes_tbl = qcm2290_usb3_serdes_tbl, + .serdes_tbl_num = ARRAY_SIZE(qcm2290_usb3_serdes_tbl), + .tx_tbl = qcm2290_usb3_tx_tbl, + .tx_tbl_num = ARRAY_SIZE(qcm2290_usb3_tx_tbl), + .rx_tbl = qcm2290_usb3_rx_tbl, + .rx_tbl_num = ARRAY_SIZE(qcm2290_usb3_rx_tbl), + .pcs_tbl = qcm2290_usb3_pcs_tbl, + .pcs_tbl_num = ARRAY_SIZE(qcm2290_usb3_pcs_tbl), + + .regs = qmp_v3_usb3phy_regs_layout_qcm2290, + + .dp_serdes_tbl = qcs615_dp_serdes_tbl, + .dp_serdes_tbl_num = ARRAY_SIZE(qcs615_dp_serdes_tbl), + .dp_tx_tbl = qcs615_dp_tx_tbl, + .dp_tx_tbl_num = ARRAY_SIZE(qcs615_dp_tx_tbl), + + .serdes_tbl_rbr = qcs615_dp_serdes_tbl_rbr, + .serdes_tbl_rbr_num = ARRAY_SIZE(qcs615_dp_serdes_tbl_rbr), + .serdes_tbl_hbr = qcs615_dp_serdes_tbl_hbr, + .serdes_tbl_hbr_num = ARRAY_SIZE(qcs615_dp_serdes_tbl_hbr), + .serdes_tbl_hbr2 = qcs615_dp_serdes_tbl_hbr2, + .serdes_tbl_hbr2_num = ARRAY_SIZE(qcs615_dp_serdes_tbl_hbr2), + + .swing_tbl = &qcs615_dp_voltage_swing_hbr2_rbr, + .pre_emphasis_tbl = &qcs615_dp_pre_emphasis_hbr2_rbr, + + .dp_aux_init = qcs615_qmp_dp_aux_init, + .configure_dp_tx = qcs615_qmp_configure_dp_tx, + .configure_dp_phy = qcs615_qmp_configure_dp_phy, + .calibrate_dp_phy = qcs615_qmp_calibrate_dp_phy, + + .reset_list = usb3dpphy_reset_shikra_l, + .num_resets = ARRAY_SIZE(usb3dpphy_reset_shikra_l), + .vreg_list = qmp_phy_qcs615_vreg_l, + .num_vregs = ARRAY_SIZE(qmp_phy_qcs615_vreg_l), +}; + static void qmp_usbc_set_phy_mode(struct qmp_usbc *qmp, bool is_dp) { if (qmp->tcsr_map && qmp->dp_phy_mode_reg) @@ -2019,6 +2063,9 @@ static const struct of_device_id qmp_usbc_of_match_table[] = { }, { .compatible = "qcom,sdm660-qmp-usb3-phy", .data = &sdm660_usb3phy_cfg, + }, { + .compatible = "qcom,shikra-qmp-usb3-dp-phy", + .data = &shikra_usb3dp_phy_cfg, }, { .compatible = "qcom,sm6115-qmp-usb3-phy", .data = &qcm2290_usb3phy_cfg,