From 298aa3e57730507f7c1299ab5bc1a824262945bc Mon Sep 17 00:00:00 2001 From: Akash Kumar Date: Fri, 21 Aug 2026 18:54:17 +0530 Subject: [PATCH 1/9] Revert "FROMLIST: arm64: dts: qcom: Add changes for usb on IQS platform" This reverts commit 1c76289b040adcf557c1acca22bc44ce0beff332. This out-of-tree USB DT change for the IQS platform is being superseded by the upstream-accepted lore.kernel.org USB series for Shikra, which reintroduces the usb_1/usb_qmpphy nodes with equivalent supply wiring on shikra-iqs-evk in an upstream-aligned form. Revert it here so the upstream series can be applied cleanly on top without duplicate/conflicting nodes. Signed-off-by: Akash Kumar --- arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts b/arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts index 050039ccce83b..53909aa82b038 100644 --- a/arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts +++ b/arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts @@ -764,27 +764,6 @@ }; }; -&usb_1 { - dr_mode = "otg"; - - status = "okay"; -}; - -&usb_1_hsphy { - vdd-supply = <&pm8150_l4>; - vdda-pll-supply = <&pm8150_l12>; - vdda-phy-dpdm-supply = <&pm8150_l13>; - - status = "okay"; -}; - -&usb_qmpphy { - vdda-phy-supply = <&pm8150_l6>; - vdda-pll-supply = <&pm8150_l12>; - - status = "okay"; -}; - &vamacro { clocks = <&q6prmcc LPASS_CLK_ID_TX_CORE_MCLK LPASS_CLK_ATTRIBUTE_COUPLE_NO>, <&q6prmcc LPASS_CLK_ID_TX_CORE_NPL_MCLK LPASS_CLK_ATTRIBUTE_COUPLE_NO>; From 454514722b90693adcc2a4472828ca6e6ae5f21c Mon Sep 17 00:00:00 2001 From: Akash Kumar Date: Fri, 21 Aug 2026 19:02:53 +0530 Subject: [PATCH 2/9] Revert "FROMLIST: arm64: dts: qcom: Add USB changes for Shikra" This reverts commit 90ffd3d3086a7e7fc11550ae687a87f8fe3f5485. This out-of-tree USB DT change for the Shikra CQM/CQS platforms is being superseded by the upstream-accepted lore.kernel.org USB series, which reintroduces the usb_1_hsphy/usb_qmpphy nodes and PM4125 supply wiring in an upstream-aligned form. Revert it here so the upstream series can be applied cleanly on top without duplicate/conflicting nodes. Signed-off-by: Akash Kumar --- arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts | 15 --- arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts | 15 --- arch/arm64/boot/dts/qcom/shikra.dtsi | 139 -------------------- 3 files changed, 169 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts b/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts index 6650540ed42bb..7111be6c253a3 100644 --- a/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts +++ b/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts @@ -730,25 +730,10 @@ remote-endpoint = <&pm4125_hs_in>; }; -&usb_1_hsphy { - vdd-supply = <&pm4125_l12>; - vdda-pll-supply = <&pm4125_l13>; - vdda-phy-dpdm-supply = <&pm4125_l21>; - - status = "okay"; -}; - &usb_qmpphy_out { 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>, <&tx_swr_active>; pinctrl-names = "default"; diff --git a/arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts b/arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts index 852753402dcd5..342343a1eee87 100644 --- a/arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts +++ b/arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts @@ -653,25 +653,10 @@ remote-endpoint = <&pm4125_hs_in>; }; -&usb_1_hsphy { - vdd-supply = <&pm4125_l12>; - vdda-pll-supply = <&pm4125_l13>; - vdda-phy-dpdm-supply = <&pm4125_l21>; - - status = "okay"; -}; - &usb_qmpphy_out { 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>, <&tx_swr_active>; pinctrl-names = "default"; diff --git a/arch/arm64/boot/dts/qcom/shikra.dtsi b/arch/arm64/boot/dts/qcom/shikra.dtsi index 141fbc51b0768..836dcf9967920 100644 --- a/arch/arm64/boot/dts/qcom/shikra.dtsi +++ b/arch/arm64/boot/dts/qcom/shikra.dtsi @@ -1239,70 +1239,6 @@ #power-domain-cells = <1>; }; - usb_1_hsphy: phy@1613000 { - compatible = "qcom,shikra-qusb2-phy"; - reg = <0x0 0x01613000 0x0 0x180>; - - clocks = <&gcc GCC_AHB2PHY_USB_CLK>, - <&rpmcc RPM_SMD_XO_CLK_SRC>; - clock-names = "cfg_ahb", "ref"; - - resets = <&gcc GCC_QUSB2PHY_PRIM_BCR>; - nvmem-cells = <&qusb2_hstx_trim_1>; - #phy-cells = <0>; - - status = "disabled"; - }; - - usb_qmpphy: phy@1615000 { - compatible = "qcom,shikra-qmp-usb3-phy"; - reg = <0x0 0x01615000 0x0 0x1000>; - - clocks = <&gcc GCC_AHB2PHY_USB_CLK>, - <&gcc GCC_USB3_PRIM_CLKREF_EN>, - <&gcc GCC_USB3_PRIM_PHY_COM_AUX_CLK>, - <&gcc GCC_USB3_PRIM_PHY_PIPE_CLK>; - clock-names = "cfg_ahb", - "ref", - "com_aux", - "pipe"; - - resets = <&gcc GCC_USB3_PHY_PRIM_SP0_BCR>, - <&gcc GCC_USB3PHY_PHY_PRIM_SP0_BCR>; - reset-names = "phy", - "phy_phy"; - - #clock-cells = <0>; - clock-output-names = "usb3_phy_pipe_clk_src"; - - #phy-cells = <0>; - orientation-switch; - - qcom,tcsr-reg = <&tcsr_regs 0xb244>; - - status = "disabled"; - - ports { - #address-cells = <1>; - #size-cells = <0>; - - port@0 { - reg = <0>; - - usb_qmpphy_out: endpoint { - }; - }; - - port@1 { - reg = <1>; - - usb_qmpphy_usb_ss_in: endpoint { - remote-endpoint = <&usb_1_dwc3_ss>; - }; - }; - }; - }; - system_noc: interconnect@1880000 { compatible = "qcom,shikra-sys-noc"; reg = <0x0 0x01880000 0x0 0x6a080>; @@ -2602,81 +2538,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 254 IRQ_TYPE_LEVEL_HIGH 0>, - <&intc GIC_SPI 422 IRQ_TYPE_LEVEL_HIGH 0>; - interrupt-names = "dwc_usb3", - "pwr_event", - "qusb2_phy", - "hs_phy_irq", - "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>; - }; - }; - }; - }; - bam_dmux_dma: dma-controller@6044000 { compatible = "qcom,bam-v1.7.0"; reg = <0x0 0x06044000 0x0 0x19000>; From df9b54b74077cc569f970dd096098f1eb97ede18 Mon Sep 17 00:00:00 2001 From: Akash Kumar Date: Fri, 21 Aug 2026 21:53:46 +0530 Subject: [PATCH 3/9] Revert "PENDING: arm64: dts: qcom: Add typec role switching changes to shikra" This reverts the pm4125_hs_in/pm4125_ss_in typec role-switch wiring introduced by commit 102ec26f93ef3d6010a7693a061bcec8838fc536: - Remove the &pm4125_typec connector node and &pm4125_vbus regulator node (with the pm4125_hs_in/pm4125_ss_in endpoint labels) from shikra-cqm-som.dtsi. - Remove the &pm4125_hs_in/&pm4125_ss_in remote-endpoint stanzas and the &usb_1_dwc3_hs/&usb_qmpphy_out remote-endpoint stanzas from shikra-cqm-evk.dts and shikra-cqs-evk.dts. - Restore usb_1's dr_mode to "peripheral" in both files. The &wifi node and all other content are left untouched. Signed-off-by: Akash Kumar --- arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts | 18 +-------- arch/arm64/boot/dts/qcom/shikra-cqm-som.dtsi | 40 -------------------- arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts | 18 +-------- 3 files changed, 2 insertions(+), 74 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts b/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts index 7111be6c253a3..832f4fd72ec8a 100644 --- a/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts +++ b/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts @@ -463,10 +463,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>; @@ -474,10 +470,6 @@ regulator-allow-set-load; }; -&pm4125_ss_in { - remote-endpoint = <&usb_qmpphy_out>; -}; - &qaif_cpu { status = "okay"; @@ -721,19 +713,11 @@ }; &usb_1 { - dr_mode = "otg"; + dr_mode = "peripheral"; status = "okay"; }; -&usb_1_dwc3_hs { - remote-endpoint = <&pm4125_hs_in>; -}; - -&usb_qmpphy_out { - remote-endpoint = <&pm4125_ss_in>; -}; - &vamacro { pinctrl-0 = <&dmic01_default>, <&dmic23_default>, <&tx_swr_active>; 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 9c87316444c80..151dcfc6f41ad 100644 --- a/arch/arm64/boot/dts/qcom/shikra-cqm-som.dtsi +++ b/arch/arm64/boot/dts/qcom/shikra-cqm-som.dtsi @@ -220,50 +220,10 @@ status = "okay"; }; -&pm4125_typec { - status = "okay"; - - connector { - compatible = "usb-c-connector"; - - power-role = "dual"; - data-role = "dual"; - self-powered; - - typec-power-opmode = "default"; - pd-disable; - - ports { - #address-cells = <1>; - #size-cells = <0>; - - port@0 { - reg = <0>; - pm4125_hs_in: endpoint { - }; - }; - - port@1 { - reg = <1>; - pm4125_ss_in: endpoint { - }; - }; - }; - }; -}; - &pm4125_tz { status = "okay"; }; -&pm4125_vbus { - regulator-min-microvolt = <5000000>; - regulator-max-microvolt = <5000000>; - regulator-min-microamp = <500000>; - regulator-max-microamp = <500000>; - status = "okay"; -}; - &pm8005_regulators { status = "disabled"; }; diff --git a/arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts b/arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts index 342343a1eee87..118f0655277c3 100644 --- a/arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts +++ b/arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts @@ -444,10 +444,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>; @@ -455,10 +451,6 @@ regulator-allow-set-load; }; -&pm4125_ss_in { - remote-endpoint = <&usb_qmpphy_out>; -}; - &q6apmbedai { #address-cells = <1>; #size-cells = <0>; @@ -644,19 +636,11 @@ }; &usb_1 { - dr_mode = "otg"; + dr_mode = "peripheral"; status = "okay"; }; -&usb_1_dwc3_hs { - remote-endpoint = <&pm4125_hs_in>; -}; - -&usb_qmpphy_out { - remote-endpoint = <&pm4125_ss_in>; -}; - &vamacro { pinctrl-0 = <&dmic01_default>, <&dmic23_default>, <&tx_swr_active>; pinctrl-names = "default"; From 338be0ffb57d5e6a75e4a75bc9c45f4770590c8e Mon Sep 17 00:00:00 2001 From: Krishna Kurapati Date: Tue, 11 Aug 2026 16:29:07 +0530 Subject: [PATCH 4/9] FROMLIST: arm64: dts: qcom: Add support for usb nodes on Shikra Add support for both USB controllers and their respective phys on Shikra. Link: https://lore.kernel.org/all/20260811-usb-shikra-v7-v7-0-753e928f37ae@oss.qualcomm.com/ Reviewed-by: Konrad Dybcio Reviewed-by: Manivannan Sadhasivam Signed-off-by: Krishna Kurapati Signed-off-by: Akash Kumar --- arch/arm64/boot/dts/qcom/shikra.dtsi | 233 +++++++++++++++++++++++++++ 1 file changed, 233 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/shikra.dtsi b/arch/arm64/boot/dts/qcom/shikra.dtsi index 836dcf9967920..5fa35d8e16ce5 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 @@ -1239,6 +1240,85 @@ #power-domain-cells = <1>; }; + usb_1_hsphy: phy@1613000 { + compatible = "qcom,shikra-qusb2-phy"; + reg = <0x0 0x01613000 0x0 0x180>; + + clocks = <&gcc GCC_AHB2PHY_USB_CLK>, + <&rpmcc RPM_SMD_XO_CLK_SRC>; + clock-names = "cfg_ahb", "ref"; + + resets = <&gcc GCC_QUSB2PHY_PRIM_BCR>; + nvmem-cells = <&qusb2_hstx_trim_1>; + #phy-cells = <0>; + + status = "disabled"; + }; + + usb_qmpphy: phy@1615000 { + compatible = "qcom,shikra-qmp-usb3-dp-phy"; + reg = <0x0 0x01615000 0x0 0x2000>; + + clocks = <&gcc GCC_USB3_PRIM_PHY_COM_AUX_CLK>, + <&gcc GCC_USB3_PRIM_CLKREF_EN>, + <&gcc GCC_AHB2PHY_USB_CLK>, + <&gcc GCC_USB3_PRIM_PHY_PIPE_CLK>; + clock-names = "aux", + "ref", + "cfg_ahb", + "pipe"; + + 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 = <1>; + #phy-cells = <1>; + orientation-switch; + + qcom,tcsr-reg = <&tcsr_regs 0xb244 0xb248>; + + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + usb_qmpphy_out: endpoint { + }; + }; + + port@1 { + reg = <1>; + + usb_qmpphy_usb_ss_in: endpoint { + remote-endpoint = <&usb_1_dwc3_ss>; + }; + }; + }; + }; + + 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>; @@ -1320,6 +1400,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>; @@ -2726,6 +2811,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>; From d1472912af9b8c3deb992385a7e1e97128a6f3f6 Mon Sep 17 00:00:00 2001 From: Krishna Kurapati Date: Tue, 11 Aug 2026 16:29:08 +0530 Subject: [PATCH 5/9] FROMLIST: arm64: dts: qcom: Enable USB controllers on Shikra platforms On Shikra CQS/CQM platforms, usb-role-switch is handled by PM4125 on primary Type-C port and Cypress PD controller CYPD6129 on second Type-C port. On Shikra IQS platform, usb-role-switch is handled by Cypress PD controller CYPD6129 on both Type-C ports. Since those changes are not yet present, enabling both USB controllers in device mode. Link: https://lore.kernel.org/all/20260811-usb-shikra-v7-v7-0-753e928f37ae@oss.qualcomm.com/ Reviewed-by: Manivannan Sadhasivam Signed-off-by: Krishna Kurapati Reviewed-by: Dmitry Baryshkov Signed-off-by: Akash Kumar --- arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts | 23 +++++++++++++++++++++ arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts | 23 +++++++++++++++++++++ arch/arm64/boot/dts/qcom/shikra-evk.dtsi | 12 +++++++++++ arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts | 23 +++++++++++++++++++++ 4 files changed, 81 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts b/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts index 832f4fd72ec8a..2f91ed04af1b0 100644 --- a/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts +++ b/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts @@ -718,6 +718,29 @@ status = "okay"; }; +&usb_1_hsphy { + vdd-supply = <&pm4125_l12>; + vdda-pll-supply = <&pm4125_l13>; + vdda-phy-dpdm-supply = <&pm4125_l21>; + + status = "okay"; +}; + +&usb_2_hsphy { + vdd-supply = <&pm4125_l12>; + vdda-pll-supply = <&pm4125_l13>; + vdda-phy-dpdm-supply = <&pm4125_l21>; + + status = "okay"; +}; + +&usb_qmpphy { + vdda-phy-supply = <&pm4125_l8>; + vdda-pll-supply = <&pm4125_l13>; + + status = "okay"; +}; + &vamacro { pinctrl-0 = <&dmic01_default>, <&dmic23_default>, <&tx_swr_active>; pinctrl-names = "default"; diff --git a/arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts b/arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts index 118f0655277c3..4cf25a291fabf 100644 --- a/arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts +++ b/arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts @@ -641,6 +641,29 @@ status = "okay"; }; +&usb_1_hsphy { + vdd-supply = <&pm4125_l12>; + vdda-pll-supply = <&pm4125_l13>; + vdda-phy-dpdm-supply = <&pm4125_l21>; + + status = "okay"; +}; + +&usb_2_hsphy { + vdd-supply = <&pm4125_l12>; + vdda-pll-supply = <&pm4125_l13>; + vdda-phy-dpdm-supply = <&pm4125_l21>; + + status = "okay"; +}; + +&usb_qmpphy { + vdda-phy-supply = <&pm4125_l8>; + vdda-pll-supply = <&pm4125_l13>; + + status = "okay"; +}; + &vamacro { pinctrl-0 = <&dmic01_default>, <&dmic23_default>, <&tx_swr_active>; pinctrl-names = "default"; diff --git a/arch/arm64/boot/dts/qcom/shikra-evk.dtsi b/arch/arm64/boot/dts/qcom/shikra-evk.dtsi index 5ed8506c98792..57769b9af718c 100644 --- a/arch/arm64/boot/dts/qcom/shikra-evk.dtsi +++ b/arch/arm64/boot/dts/qcom/shikra-evk.dtsi @@ -39,3 +39,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 53909aa82b038..1473fe64120ac 100644 --- a/arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts +++ b/arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts @@ -764,6 +764,29 @@ }; }; +&usb_1_hsphy { + vdd-supply = <&pm8150_l4>; + vdda-pll-supply = <&pm8150_l12>; + vdda-phy-dpdm-supply = <&pm8150_l13>; + + status = "okay"; +}; + +&usb_2_hsphy { + vdd-supply = <&pm8150_l4>; + vdda-pll-supply = <&pm8150_l12>; + vdda-phy-dpdm-supply = <&pm8150_l13>; + + status = "okay"; +}; + +&usb_qmpphy { + vdda-phy-supply = <&pm8150_l6>; + vdda-pll-supply = <&pm8150_l12>; + + status = "okay"; +}; + &vamacro { clocks = <&q6prmcc LPASS_CLK_ID_TX_CORE_MCLK LPASS_CLK_ATTRIBUTE_COUPLE_NO>, <&q6prmcc LPASS_CLK_ID_TX_CORE_NPL_MCLK LPASS_CLK_ATTRIBUTE_COUPLE_NO>; From e4045ef830faede0e5fe17f6cb4070e7601b0da8 Mon Sep 17 00:00:00 2001 From: Akash Kumar Date: Fri, 21 Aug 2026 19:34:31 +0530 Subject: [PATCH 6/9] FROMLIST: dt-bindings: usb: Add Cypress cypd6129/cypd6229 Type-C controller Add the device-tree binding documentation for the Cypress cypd6129 and cypd6229 dual Type-C PD controllers. These are used on Shikra CQM/CQS/IQS platforms to handle usb-role-switch for the USB Type-C ports over an I2C interface, similarly to the existing cypd4226 binding. cypd6229 is a variant of cypd6129 and is described with a "cypress,cypd6129" fallback compatible string. Link: https://lore.kernel.org/all/20260820145036.2035641-4-akash.kumar@oss.qualcomm.com/ Signed-off-by: Akash Kumar --- .../bindings/usb/cypress,cypd6129.yaml | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) 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 0000000000000..43e2c1902fd1b --- /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>; + }; + }; + }; + }; + }; + }; From 6df63678136ce168f7d5de0b24fee9dc2ce96fba Mon Sep 17 00:00:00 2001 From: Akash Kumar Date: Fri, 21 Aug 2026 19:34:32 +0530 Subject: [PATCH 7/9] FROMLIST: usb: typec: ucsi: ccg: Add support for cypd6129/cypd6229 Add cypd6129 and cypd6229 compatible strings to the of_device_id match table so the driver binds to boards describing these Cypress PD controllers in their device tree. No other driver changes are needed since the chip is accessed through the same generic UCSI/HPI I2C register protocol as the existing cypd4226 support. Link: https://lore.kernel.org/all/20260820145036.2035641-4-akash.kumar@oss.qualcomm.com/ Reviewed-by: Abel Vesa Signed-off-by: Akash Kumar --- drivers/usb/typec/ucsi/ucsi_ccg.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/typec/ucsi/ucsi_ccg.c b/drivers/usb/typec/ucsi/ucsi_ccg.c index 9d7b834a76fa8..3d4cf117e35f6 100644 --- a/drivers/usb/typec/ucsi/ucsi_ccg.c +++ b/drivers/usb/typec/ucsi/ucsi_ccg.c @@ -1524,6 +1524,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 d485bb7d9e3790775e488e16b95442a50f55de2d Mon Sep 17 00:00:00 2001 From: Akash Kumar Date: Tue, 1 Sep 2026 17:07:45 +0530 Subject: [PATCH 8/9] FROMLIST: arm64: dts: qcom: shikra: Wire up usb-role-switch for USB Type-C ports On Shikra CQS/CQM platforms, usb-role-switch is handled by PM4125 on the primary Type-C port and Cypress PD controller CYPD6129 on the second Type-C port. On Shikra IQS platform, usb-role-switch is handled by Cypress PD controller CYPD6129 on both Type-C ports. Add the CYPD6129 typec node under i2c3, wire its connector endpoints to the corresponding DWC3 controller ports via remote-endpoint phandles, and switch the associated USB controllers to OTG mode so role switching can take effect. Link: https://lore.kernel.org/all/20260820145036.2035641-4-akash.kumar@oss.qualcomm.com/ Signed-off-by: Akash Kumar --- arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts | 88 +++++++++++++-- arch/arm64/boot/dts/qcom/shikra-cqm-som.dtsi | 45 ++++++++ arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts | 88 +++++++++++++-- arch/arm64/boot/dts/qcom/shikra-evk.dtsi | 4 +- arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts | 111 +++++++++++++++++-- 5 files changed, 311 insertions(+), 25 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts b/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts index 2f91ed04af1b0..0298d466f8765 100644 --- a/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts +++ b/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts @@ -515,6 +515,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>; @@ -713,11 +758,24 @@ }; &usb_1 { - dr_mode = "peripheral"; + dr_mode = "otg"; status = "okay"; }; +&tlmm { + usb0_intr_state: usb0-intr-state { + pins = "gpio136"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; +}; + +&usb_1_dwc3_hs { + remote-endpoint = <&pm4125_hs_in>; +}; + &usb_1_hsphy { vdd-supply = <&pm4125_l12>; vdda-pll-supply = <&pm4125_l13>; @@ -726,17 +784,20 @@ status = "okay"; }; -&usb_2_hsphy { - vdd-supply = <&pm4125_l12>; - vdda-pll-supply = <&pm4125_l13>; - vdda-phy-dpdm-supply = <&pm4125_l21>; +&usb_2 { + dr_mode = "otg"; - status = "okay"; + port { + usb_2_dwc3_hs: endpoint { + remote-endpoint = <&ucsi_ccg_port>; + }; + }; }; -&usb_qmpphy { - vdda-phy-supply = <&pm4125_l8>; +&usb_2_hsphy { + vdd-supply = <&pm4125_l12>; vdda-pll-supply = <&pm4125_l13>; + vdda-phy-dpdm-supply = <&pm4125_l21>; status = "okay"; }; @@ -759,3 +820,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-cqm-som.dtsi b/arch/arm64/boot/dts/qcom/shikra-cqm-som.dtsi index 151dcfc6f41ad..0e823a45244c2 100644 --- a/arch/arm64/boot/dts/qcom/shikra-cqm-som.dtsi +++ b/arch/arm64/boot/dts/qcom/shikra-cqm-som.dtsi @@ -224,6 +224,51 @@ status = "okay"; }; +&pm4125_typec { + status = "okay"; + + connector { + compatible = "usb-c-connector"; + + power-role = "dual"; + data-role = "dual"; + self-powered; + + 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 { + }; + }; + }; + }; +}; + +&pm4125_vbus { + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-min-microamp = <500000>; + regulator-max-microamp = <500000>; + + status = "okay"; +}; + &pm8005_regulators { status = "disabled"; }; diff --git a/arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts b/arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts index 4cf25a291fabf..f3bf1c98c0ad4 100644 --- a/arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts +++ b/arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts @@ -481,6 +481,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>; @@ -636,11 +681,24 @@ }; &usb_1 { - dr_mode = "peripheral"; + dr_mode = "otg"; status = "okay"; }; +&tlmm { + usb0_intr_state: usb0-intr-state { + pins = "gpio136"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; +}; + +&usb_1_dwc3_hs { + remote-endpoint = <&pm4125_hs_in>; +}; + &usb_1_hsphy { vdd-supply = <&pm4125_l12>; vdda-pll-supply = <&pm4125_l13>; @@ -649,17 +707,20 @@ status = "okay"; }; -&usb_2_hsphy { - vdd-supply = <&pm4125_l12>; - vdda-pll-supply = <&pm4125_l13>; - vdda-phy-dpdm-supply = <&pm4125_l21>; +&usb_2 { + dr_mode = "otg"; - status = "okay"; + port { + usb_2_dwc3_hs: endpoint { + remote-endpoint = <&ucsi_ccg_port>; + }; + }; }; -&usb_qmpphy { - vdda-phy-supply = <&pm4125_l8>; +&usb_2_hsphy { + vdd-supply = <&pm4125_l12>; vdda-pll-supply = <&pm4125_l13>; + vdda-phy-dpdm-supply = <&pm4125_l21>; status = "okay"; }; @@ -687,3 +748,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 57769b9af718c..8d5916c4f9272 100644 --- a/arch/arm64/boot/dts/qcom/shikra-evk.dtsi +++ b/arch/arm64/boot/dts/qcom/shikra-evk.dtsi @@ -41,13 +41,13 @@ }; &usb_1 { - dr_mode = "peripheral"; + dr_mode = "otg"; status = "okay"; }; &usb_2 { - dr_mode = "peripheral"; + dr_mode = "otg"; 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 1473fe64120ac..fc05fea5f4ab5 100644 --- a/arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts +++ b/arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts @@ -615,6 +615,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>; @@ -764,15 +830,24 @@ }; }; -&usb_1_hsphy { - vdd-supply = <&pm8150_l4>; - vdda-pll-supply = <&pm8150_l12>; - vdda-phy-dpdm-supply = <&pm8150_l13>; +&tlmm { + usb0_intr_state: usb0-intr-state { + pins = "gpio50"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; +}; +&usb_1 { status = "okay"; }; -&usb_2_hsphy { +&usb_1_dwc3_hs { + remote-endpoint = <&ucsi_ccg_port_1_hs>; +}; + +&usb_1_hsphy { vdd-supply = <&pm8150_l4>; vdda-pll-supply = <&pm8150_l12>; vdda-phy-dpdm-supply = <&pm8150_l13>; @@ -780,9 +855,20 @@ status = "okay"; }; -&usb_qmpphy { - vdda-phy-supply = <&pm8150_l6>; +&usb_2 { + 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"; }; @@ -839,3 +925,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>; +}; From 82c893d7a9d5fce2431862385c692d8e192999ca Mon Sep 17 00:00:00 2001 From: Akash Kumar Date: Wed, 2 Sep 2026 12:58:16 +0530 Subject: [PATCH 9/9] ccg firmware update support for cyacd2 --- drivers/usb/typec/ucsi/ucsi_ccg.c | 2767 +++++++++++++++++++++-------- 1 file changed, 1984 insertions(+), 783 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi_ccg.c b/drivers/usb/typec/ucsi/ucsi_ccg.c index 3d4cf117e35f6..0b85a7064f338 100644 --- a/drivers/usb/typec/ucsi/ucsi_ccg.c +++ b/drivers/usb/typec/ucsi/ucsi_ccg.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -17,6 +18,13 @@ #include #include #include +#include +#include +#include +#include +#include /* min_t, simple_strtoul */ +#include /* BIT */ +#include /* isspace */ #include #include "ucsi.h" @@ -65,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 @@ -79,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 { @@ -232,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) @@ -279,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; @@ -315,794 +475,1669 @@ 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) +/* ===== 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); +/* 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) { - 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); - /* 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) { @@ -1123,6 +2158,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) { @@ -1152,166 +2189,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; } /******************************************************************************* @@ -1322,20 +2477,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) { @@ -1374,57 +2534,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) @@ -1434,6 +2594,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; @@ -1452,8 +2613,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 */ @@ -1469,6 +2632,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) @@ -1486,20 +2682,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: @@ -1525,7 +2727,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); @@ -1552,7 +2753,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) @@ -1565,8 +2766,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; @@ -1581,7 +2782,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,