diff options
author | Linaro CI <ci_notify@linaro.org> | 2022-06-13 00:09:21 +0000 |
---|---|---|
committer | Linaro CI <ci_notify@linaro.org> | 2022-06-13 00:09:21 +0000 |
commit | a08f52df67a25c3a2de174cc6341fee57ef7d58e (patch) | |
tree | 54fd30a57a4dc4de5ec2418122ffa275157d9b0e | |
parent | ff720f22e7bfdb19a39a435af3b10fb2df10c31a (diff) | |
parent | 1071a0e5cc2702fc7771c345b8601eacbdfe0733 (diff) |
Merge remote-tracking branch 'sm8250-typec/tracking-qcomlt-sm8250-typec' into integration-linux-qcomlt
# Conflicts:
# arch/arm64/configs/defconfig
# drivers/usb/typec/mux/Kconfig
# drivers/usb/typec/mux/Makefile
-rw-r--r-- | Documentation/devicetree/bindings/usb/qcom,pmic-typec.yaml | 112 | ||||
-rw-r--r-- | arch/arm64/boot/dts/qcom/pm8150b.dtsi | 7 | ||||
-rw-r--r-- | arch/arm64/boot/dts/qcom/qrb5165-rb5.dts | 57 | ||||
-rw-r--r-- | arch/arm64/boot/dts/qcom/sm8150-mtp.dts | 44 | ||||
-rw-r--r-- | arch/arm64/boot/dts/qcom/sm8250.dtsi | 84 | ||||
-rw-r--r-- | arch/arm64/configs/defconfig | 5 | ||||
-rw-r--r-- | drivers/phy/qualcomm/Kconfig | 8 | ||||
-rw-r--r-- | drivers/phy/qualcomm/phy-qcom-qmp.c | 136 | ||||
-rw-r--r-- | drivers/usb/dwc3/dwc3-qcom.c | 118 | ||||
-rw-r--r-- | drivers/usb/typec/mux.c | 43 | ||||
-rw-r--r-- | drivers/usb/typec/mux/Kconfig | 8 | ||||
-rw-r--r-- | drivers/usb/typec/mux/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/typec/mux/nb7vpq904m.c | 260 |
13 files changed, 842 insertions, 41 deletions
diff --git a/Documentation/devicetree/bindings/usb/qcom,pmic-typec.yaml b/Documentation/devicetree/bindings/usb/qcom,pmic-typec.yaml new file mode 100644 index 000000000000..d5173f88d429 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/qcom,pmic-typec.yaml @@ -0,0 +1,112 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: "http://devicetree.org/schemas/usb/qcom,pmic-typec.yaml#" +$schema: "http://devicetree.org/meta-schemas/core.yaml#" + +title: Qualcomm PMIC based USB type C Detection Driver + +maintainers: + - Wesley Cheng <wcheng@codeaurora.org> + +description: | + Qualcomm PMIC Type C Detect + +properties: + compatible: + enum: + - qcom,pm8150b-usb-typec + + reg: + maxItems: 1 + description: Type C base address + + interrupts: + maxItems: 1 + description: CC change interrupt from PMIC + + connector: + description: Connector type for remote endpoints + type: object + + properties: + compatible: + $ref: /connector/usb-connector.yaml#/properties/compatible + enum: + - usb-c-connector + + power-role: true + data-role: true + + ports: + description: Remote endpoint connections + type: object + $ref: /connector/usb-connector.yaml#/properties/ports + + properties: + port@0: + description: Remote endpoints for the High Speed path + type: object + + port@1: + description: Remote endpoints for the Super Speed path + type: object + + properties: + endpoint@0: + description: Connection to USB type C mux node + type: object + + endpoint@1: + description: Connection to role switch node + type: object + + required: + - compatible + +required: + - compatible + - interrupts + - connector + +additionalProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/irq.h> + pm8150b { + #address-cells = <1>; + #size-cells = <0>; + pm8150b_typec: typec@1500 { + compatible = "qcom,pm8150b-usb-typec"; + reg = <0x1500>; + interrupts = <0x2 0x15 0x5 IRQ_TYPE_EDGE_RISING>; + + connector { + compatible = "usb-c-connector"; + power-role = "dual"; + data-role = "dual"; + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + }; + port@1 { + reg = <1>; + #address-cells = <1>; + #size-cells = <0>; + usb3_data_ss: endpoint@0 { + reg = <0>; + remote-endpoint = <&qmp_ss_mux>; + }; + usb3_role: endpoint@1 { + reg = <1>; + remote-endpoint = <&dwc3_drd_switch>; + }; + }; + }; + }; + }; + }; +... diff --git a/arch/arm64/boot/dts/qcom/pm8150b.dtsi b/arch/arm64/boot/dts/qcom/pm8150b.dtsi index 24c6c2c82405..5cd4268e74b8 100644 --- a/arch/arm64/boot/dts/qcom/pm8150b.dtsi +++ b/arch/arm64/boot/dts/qcom/pm8150b.dtsi @@ -59,6 +59,13 @@ reg = <0x1100>; }; + pm8150b_typec: typec@1500 { + compatible = "qcom,pm8150b-usb-typec"; + status = "disabled"; + reg = <0x1500>; + interrupts = <0x2 0x15 0x5 IRQ_TYPE_EDGE_RISING>; + }; + pm8150b_temp: temp-alarm@2400 { compatible = "qcom,spmi-temp-alarm"; reg = <0x2400>; diff --git a/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts b/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts index c828e37b0c51..c4ab09f1735a 100644 --- a/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts +++ b/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts @@ -648,12 +648,25 @@ /* LS-I2C1 */ &i2c15 { status = "okay"; + + redriver@1c { + compatible = "onnn,nb7vpq904m"; + reg = <0x1c>; + + mode-switch; + port { + }; + }; }; &mdss { status = "okay"; }; +&mdss_dp { + status = "okay"; +}; + &mdss_mdp { status = "okay"; }; @@ -805,6 +818,35 @@ }; }; +&pm8150b_typec { + status = "okay"; + connector { + compatible = "usb-c-connector"; + power-role = "dual"; + data-role = "dual"; + ports { + #address-cells = <1>; + #size-cells = <0>; + port@1 { + reg = <1>; + usb3_data_ss: endpoint@0 { + remote-endpoint = <&qmp_ss_mux>; + }; + }; + }; + }; + + port { + usb3_role: endpoint { + remote-endpoint = <&dwc3_drd_switch>; + }; + }; +}; + +&pm8150b_vbus { + status = "okay"; +}; + &pm8150l_gpios { gpio-line-names = "NC", @@ -1315,10 +1357,17 @@ &usb_1 { status = "okay"; + usb-role-switch; + port { + dwc3_drd_switch: endpoint@0 { + remote-endpoint = <&usb3_role>; + }; + }; }; &usb_1_dwc3 { - dr_mode = "peripheral"; + dr_mode = "otg"; + usb-role-switch; }; &usb_1_hsphy { @@ -1334,6 +1383,12 @@ vdda-phy-supply = <&vreg_l9a_1p2>; vdda-pll-supply = <&vreg_l18a_0p92>; + orientation-switch; + port { + qmp_ss_mux: endpoint@0 { + remote-endpoint = <&usb3_data_ss>; + }; + }; }; &usb_2 { diff --git a/arch/arm64/boot/dts/qcom/sm8150-mtp.dts b/arch/arm64/boot/dts/qcom/sm8150-mtp.dts index 46b5cf9a1192..f4fabd209b8b 100644 --- a/arch/arm64/boot/dts/qcom/sm8150-mtp.dts +++ b/arch/arm64/boot/dts/qcom/sm8150-mtp.dts @@ -419,6 +419,35 @@ vdda-pll-supply = <&vreg_l3c_1p2>; }; +&pm8150b_vbus { + status = "okay"; +}; + +&pm8150b_typec { + status = "okay"; + connector { + compatible = "usb-c-connector"; + power-role = "dual"; + data-role = "dual"; + ports { + #address-cells = <1>; + #size-cells = <0>; + port@1 { + reg = <1>; + usb3_data_ss: endpoint@0 { + remote-endpoint = <&qmp_ss_mux>; + }; + }; + }; + }; + + port { + usb3_role: endpoint { + remote-endpoint = <&dwc3_drd_switch>; + }; + }; +}; + &usb_1_hsphy { status = "okay"; vdda-pll-supply = <&vdd_usb_hs_core>; @@ -430,14 +459,27 @@ status = "okay"; vdda-phy-supply = <&vreg_l3c_1p2>; vdda-pll-supply = <&vdda_usb_ss_dp_core_1>; + orientation-switch; + port { + qmp_ss_mux: endpoint@0 { + remote-endpoint = <&usb3_data_ss>; + }; + }; }; &usb_1 { status = "okay"; + usb-role-switch; + port { + dwc3_drd_switch: endpoint@0 { + remote-endpoint = <&usb3_role>; + }; + }; }; &usb_1_dwc3 { - dr_mode = "peripheral"; + dr_mode = "otg"; + usb-role-switch; }; &wifi { diff --git a/arch/arm64/boot/dts/qcom/sm8250.dtsi b/arch/arm64/boot/dts/qcom/sm8250.dtsi index ffc9f4e0958d..49f30b7a2b80 100644 --- a/arch/arm64/boot/dts/qcom/sm8250.dtsi +++ b/arch/arm64/boot/dts/qcom/sm8250.dtsi @@ -2899,6 +2899,7 @@ resets = <&gcc GCC_USB3_DP_PHY_PRIM_BCR>, <&gcc GCC_USB3_PHY_PRIM_BCR>; reset-names = "phy", "common"; + orientation-switch; usb_1_ssphy: usb3-phy@88e9200 { reg = <0 0x088e9200 0 0x200>, @@ -2927,6 +2928,11 @@ clock-names = "pipe0"; clock-output-names = "usb3_phy_pipe_clk_src"; }; + + port { + usb_1_qmp_switch: endpoint { + }; + }; }; usb_2_qmpphy: phy@88eb000 { @@ -3526,6 +3532,13 @@ remote-endpoint = <&dsi1_in>; }; }; + + port@2 { + reg = <2>; + dpu_intf0_out: endpoint { + remote-endpoint = <&dp_in>; + }; + }; }; mdp_opp_table: mdp-opp-table { @@ -3553,6 +3566,77 @@ }; }; + mdss_dp: displayport-controller@ae90000{ + cell-index = <0>; + compatible = "qcom,sc7180-dp"; + + reg = <0 0xae90000 0 0x1400>; + + interrupt-parent = <&mdss>; + interrupts = <12>; + + clocks = <&dispcc DISP_CC_MDSS_AHB_CLK>, + <&dispcc DISP_CC_MDSS_DP_AUX_CLK>, + <&dispcc DISP_CC_MDSS_DP_LINK_CLK>, + <&dispcc DISP_CC_MDSS_DP_LINK_INTF_CLK>, + <&dispcc DISP_CC_MDSS_DP_PIXEL_CLK>; + clock-names = "core_iface", "core_aux", + "ctrl_link", "ctrl_link_iface", "stream_pixel"; + + assigned-clocks = <&dispcc DISP_CC_MDSS_DP_LINK_CLK_SRC>, + <&dispcc DISP_CC_MDSS_DP_PIXEL_CLK_SRC>; + assigned-clock-parents = <&dp_phy 0>, <&dp_phy 1>; + + phys = <&dp_phy>; + phy-names = "dp"; + + operating-points-v2 = <&dp_opp_table>; + power-domains = <&rpmhpd SM8250_MMCX>; + + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + dp_in: endpoint { + remote-endpoint = + <&dpu_intf0_out>; + }; + }; + + port@1 { + reg = <1>; + dp_out: endpoint { }; + }; + }; + + dp_opp_table: dp-opp-table { + compatible = "operating-points-v2"; + + opp-160000000 { + opp-hz = /bits/ 64 <160000000>; + required-opps = <&rpmhpd_opp_low_svs>; + }; + + opp-270000000 { + opp-hz = /bits/ 64 <270000000>; + required-opps = <&rpmhpd_opp_svs>; + }; + + opp-540000000 { + opp-hz = /bits/ 64 <540000000>; + required-opps = <&rpmhpd_opp_svs_l1>; + }; + + opp-810000000 { + opp-hz = /bits/ 64 <810000000>; + required-opps = <&rpmhpd_opp_nom>; + }; + }; + }; + dsi0: dsi@ae94000 { compatible = "qcom,mdss-dsi-ctrl"; reg = <0 0x0ae94000 0 0x400>; diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index 76e47d15dc33..e492283cbe37 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -680,6 +680,7 @@ CONFIG_REGULATOR_PWM=y CONFIG_REGULATOR_QCOM_RPMH=y CONFIG_REGULATOR_QCOM_SMD_RPM=y CONFIG_REGULATOR_QCOM_SPMI=y +CONFIG_REGULATOR_QCOM_USB_VBUS=m CONFIG_REGULATOR_RK808=y CONFIG_REGULATOR_S2MPS11=y CONFIG_REGULATOR_TPS65132=m @@ -915,12 +916,13 @@ CONFIG_USB_CONFIGFS_RNDIS=y CONFIG_USB_CONFIGFS_EEM=y CONFIG_USB_CONFIGFS_MASS_STORAGE=y CONFIG_USB_CONFIGFS_F_FS=y -CONFIG_TYPEC=m +CONFIG_TYPEC=y CONFIG_TYPEC_TCPM=m CONFIG_TYPEC_TCPCI=m CONFIG_TYPEC_FUSB302=m CONFIG_TYPEC_TPS6598X=m CONFIG_TYPEC_HD3SS3220=m +CONFIG_TYPEC_QCOM_PMIC=m CONFIG_MMC=y CONFIG_MMC_BLOCK_MINORS=32 CONFIG_MMC_ARMMMCI=y @@ -1214,6 +1216,7 @@ CONFIG_PHY_MVEBU_CP110_COMPHY=y CONFIG_PHY_MTK_TPHY=y CONFIG_PHY_QCOM_PCIE2=m CONFIG_PHY_QCOM_QMP=y +CONFIG_PHY_QCOM_QMP_TYPEC=y CONFIG_PHY_QCOM_QUSB2=m CONFIG_PHY_QCOM_UFS=y CONFIG_PHY_QCOM_USB_HS=y diff --git a/drivers/phy/qualcomm/Kconfig b/drivers/phy/qualcomm/Kconfig index 5c98850f5a36..fbc0f80852d1 100644 --- a/drivers/phy/qualcomm/Kconfig +++ b/drivers/phy/qualcomm/Kconfig @@ -58,6 +58,14 @@ config PHY_QCOM_QMP Enable this to support the QMP PHY transceiver that is used with controllers such as PCIe, UFS, and USB on Qualcomm chips. +config PHY_QCOM_QMP_TYPEC + def_bool PHY_QCOM_QMP=y && TYPEC=y || PHY_QCOM_QMP=m && TYPEC + help + Register a type C switch from the QMP PHY driver for type C + orientation support. This has dependencies with if the type C kernel + configuration is enabled or not. This support will not be present if + USB type C is disabled. + config PHY_QCOM_QUSB2 tristate "Qualcomm QUSB2 PHY Driver" depends on OF && (ARCH_QCOM || COMPILE_TEST) diff --git a/drivers/phy/qualcomm/phy-qcom-qmp.c b/drivers/phy/qualcomm/phy-qcom-qmp.c index 07d60ead8edf..31d32c67e803 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp.c @@ -19,6 +19,7 @@ #include <linux/regulator/consumer.h> #include <linux/reset.h> #include <linux/slab.h> +#include <linux/usb/typec_mux.h> #include <dt-bindings/phy/phy.h> @@ -67,6 +68,10 @@ /* QPHY_V3_PCS_MISC_CLAMP_ENABLE register bits */ #define CLAMP_EN BIT(0) /* enables i/o clamp_n */ +/* QPHY_V3_DP_COM_TYPEC_CTRL register bits */ +#define SW_PORTSELECT_VAL BIT(0) +#define SW_PORTSELECT_MUX BIT(1) + #define PHY_INIT_COMPLETE_TIMEOUT 10000 #define POWER_DOWN_DELAY_US_MIN 10 #define POWER_DOWN_DELAY_US_MAX 11 @@ -3270,6 +3275,8 @@ struct qmp_phy_dp_clks { * @phy_mutex: mutex lock for PHY common block initialization * @init_count: phy common block initialization count * @ufs_reset: optional UFS PHY reset handle + * @sw: typec switch for receiving orientation changes + * @orientation: carries current CC orientation */ struct qcom_qmp { struct device *dev; @@ -3285,6 +3292,8 @@ struct qcom_qmp { int init_count; struct reset_control *ufs_reset; + struct typec_switch_dev *sw; + enum typec_orientation orientation; }; static void qcom_qmp_v3_phy_dp_aux_init(struct qmp_phy *qphy); @@ -4728,30 +4737,26 @@ static void qcom_qmp_v3_phy_configure_dp_tx(struct qmp_phy *qphy) static bool qcom_qmp_phy_configure_dp_mode(struct qmp_phy *qphy) { + const struct phy_configure_opts_dp *dp_opts = &qphy->dp_opts; + struct qcom_qmp *qmp = qphy->qmp; u32 val; - bool reverse = false; + bool reverse = qmp->orientation == TYPEC_ORIENTATION_REVERSE; val = DP_PHY_PD_CTL_PWRDN | DP_PHY_PD_CTL_AUX_PWRDN | DP_PHY_PD_CTL_PLL_PWRDN | DP_PHY_PD_CTL_DP_CLAMP_EN; - /* - * TODO: Assume orientation is CC1 for now and two lanes, need to - * use type-c connector to understand orientation and lanes. - * - * Otherwise val changes to be like below if this code understood - * the orientation of the type-c cable. - * - * if (lane_cnt == 4 || orientation == ORIENTATION_CC2) - * val |= DP_PHY_PD_CTL_LANE_0_1_PWRDN; - * if (lane_cnt == 4 || orientation == ORIENTATION_CC1) - * val |= DP_PHY_PD_CTL_LANE_2_3_PWRDN; - * if (orientation == ORIENTATION_CC2) - * writel(0x4c, qphy->pcs + QSERDES_V3_DP_PHY_MODE); - */ + if (dp_opts->lanes == 4 || reverse) + val |= DP_PHY_PD_CTL_LANE_0_1_PWRDN; + if (dp_opts->lanes == 4 || !reverse) + val |= DP_PHY_PD_CTL_LANE_2_3_PWRDN; + val |= DP_PHY_PD_CTL_LANE_2_3_PWRDN; writel(val, qphy->pcs + QSERDES_DP_PHY_PD_CTL); - writel(0x5c, qphy->pcs + QSERDES_DP_PHY_MODE); + if (reverse) + writel(0x4c, qphy->pcs + QSERDES_DP_PHY_MODE); + else + writel(0x5c, qphy->pcs + QSERDES_DP_PHY_MODE); return reverse; } @@ -5071,6 +5076,30 @@ static int qcom_qmp_dp_phy_calibrate(struct phy *phy) return 0; } +static void qcom_qmp_phy_dp_com_reset(struct qcom_qmp *qmp, unsigned int reset_bits) +{ + void __iomem *dp_com = qmp->dp_com; + unsigned int val; + + /* override hardware control for reset of qmp phy */ + qphy_setbits(dp_com, QPHY_V3_DP_COM_RESET_OVRD_CTRL, + reset_bits); + + val = SW_PORTSELECT_MUX; + if (qmp->orientation == TYPEC_ORIENTATION_REVERSE) + val |= SW_PORTSELECT_VAL; + qphy_setbits(dp_com, QPHY_V3_DP_COM_TYPEC_CTRL, val); + + qphy_setbits(dp_com, QPHY_V3_DP_COM_PHY_MODE_CTRL, + USB3_MODE | DP_MODE); + + qphy_clrbits(dp_com, QPHY_V3_DP_COM_RESET_OVRD_CTRL, + reset_bits); + + qphy_clrbits(dp_com, QPHY_V3_DP_COM_SWI_CTRL, 0x03); + qphy_clrbits(dp_com, QPHY_V3_DP_COM_SW_RESET, SW_RESET); +} + static int qcom_qmp_phy_com_init(struct qmp_phy *qphy) { struct qcom_qmp *qmp = qphy->qmp; @@ -5118,24 +5147,9 @@ static int qcom_qmp_phy_com_init(struct qmp_phy *qphy) if (cfg->has_phy_dp_com_ctrl) { qphy_setbits(dp_com, QPHY_V3_DP_COM_POWER_DOWN_CTRL, SW_PWRDN); - /* override hardware control for reset of qmp phy */ - qphy_setbits(dp_com, QPHY_V3_DP_COM_RESET_OVRD_CTRL, - SW_DPPHY_RESET_MUX | SW_DPPHY_RESET | - SW_USB3PHY_RESET_MUX | SW_USB3PHY_RESET); - - /* Default type-c orientation, i.e CC1 */ - qphy_setbits(dp_com, QPHY_V3_DP_COM_TYPEC_CTRL, 0x02); - - qphy_setbits(dp_com, QPHY_V3_DP_COM_PHY_MODE_CTRL, - USB3_MODE | DP_MODE); - - /* bring both QMP USB and QMP DP PHYs PCS block out of reset */ - qphy_clrbits(dp_com, QPHY_V3_DP_COM_RESET_OVRD_CTRL, - SW_DPPHY_RESET_MUX | SW_DPPHY_RESET | - SW_USB3PHY_RESET_MUX | SW_USB3PHY_RESET); - - qphy_clrbits(dp_com, QPHY_V3_DP_COM_SWI_CTRL, 0x03); - qphy_clrbits(dp_com, QPHY_V3_DP_COM_SW_RESET, SW_RESET); + qcom_qmp_phy_dp_com_reset(qmp, + SW_DPPHY_RESET_MUX | SW_DPPHY_RESET | + SW_USB3PHY_RESET_MUX | SW_USB3PHY_RESET); } if (cfg->has_phy_com_ctrl) { @@ -6193,6 +6207,45 @@ static const struct dev_pm_ops qcom_qmp_phy_pm_ops = { qcom_qmp_phy_runtime_resume, NULL) }; +#if IS_ENABLED(CONFIG_PHY_QCOM_QMP_TYPEC) +static int qcom_qmp_phy_typec_switch_set(struct typec_switch_dev *sw, + enum typec_orientation orientation) +{ + struct qcom_qmp *qmp = typec_switch_get_drvdata(sw); + struct qmp_phy *qphy = qmp->phys[0]; + + qmp->orientation = orientation; + if (qmp->init_count) { + qcom_qmp_phy_disable(qphy->phy); + qcom_qmp_phy_enable(qphy->phy); + } + + return 0; +} + +static int qcom_qmp_phy_typec_switch_register(struct qcom_qmp *qmp, const struct qmp_phy_cfg *cfg) +{ + struct typec_switch_desc sw_desc; + struct device *dev = qmp->dev; + + sw_desc.drvdata = qmp; + sw_desc.fwnode = dev->fwnode; + sw_desc.set = qcom_qmp_phy_typec_switch_set; + qmp->sw = typec_switch_register(dev, &sw_desc); + if (IS_ERR(qmp->sw)) { + dev_err(dev, "Error registering typec switch: %ld\n", + PTR_ERR(qmp->sw)); + } + + return 0; +} +#else +static int qcom_qmp_phy_typec_switch_register(struct qcom_qmp *qmp, const struct qmp_phy_cfg *cfg) +{ + return 0; +} +#endif + static int qcom_qmp_phy_probe(struct platform_device *pdev) { struct qcom_qmp *qmp; @@ -6275,7 +6328,12 @@ static int qcom_qmp_phy_probe(struct platform_device *pdev) return ret; } - num = of_get_available_child_count(dev->of_node); + /* cound child nodes ingoring connection graph ports */ + num = 0; + for_each_available_child_of_node(dev->of_node, child) + if (strncmp("port", child->name, 4)) + num++; + /* do we have a rogue child node ? */ if (num > expected_phys) return -EINVAL; @@ -6302,7 +6360,10 @@ static int qcom_qmp_phy_probe(struct platform_device *pdev) serdes = usb_serdes; } - /* Create per-lane phy */ + /* Ignore conngraph nodes */ + if (!strncmp("port", child->name, 4)) + continue; + ret = qcom_qmp_phy_create(dev, child, id, serdes, cfg); if (ret) { dev_err(dev, "failed to create lane%d phy, %d\n", @@ -6332,6 +6393,9 @@ static int qcom_qmp_phy_probe(struct platform_device *pdev) id++; } + if (cfg->has_phy_dp_com_ctrl) + qcom_qmp_phy_typec_switch_register(qmp, cfg); + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); if (!IS_ERR(phy_provider)) dev_info(dev, "Registered Qcom-QMP phy\n"); diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index 6cba990da32e..5f06fb3e2b44 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -20,6 +20,8 @@ #include <linux/usb/of.h> #include <linux/reset.h> #include <linux/iopoll.h> +#include <linux/fwnode.h> +#include <linux/usb/role.h> #include "core.h" @@ -82,6 +84,9 @@ struct dwc3_qcom { struct notifier_block vbus_nb; struct notifier_block host_nb; + struct usb_role_switch *role_sw; + struct usb_role_switch *dwc3_drd_sw; + const struct dwc3_acpi_pdata *acpi_pdata; enum usb_dr_mode mode; @@ -296,6 +301,73 @@ static void dwc3_qcom_interconnect_exit(struct dwc3_qcom *qcom) icc_put(qcom->icc_path_apps); } +static int dwc3_qcom_usb_role_switch_set(struct usb_role_switch *sw, + enum usb_role role) +{ + struct dwc3_qcom *qcom = usb_role_switch_get_drvdata(sw); + struct fwnode_handle *child; + bool enable = false; + + if (!qcom->dwc3_drd_sw) { + child = device_get_next_child_node(qcom->dev, NULL); + if (child) { + qcom->dwc3_drd_sw = usb_role_switch_find_by_fwnode(child); + fwnode_handle_put(child); + if (IS_ERR(qcom->dwc3_drd_sw)) { + qcom->dwc3_drd_sw = NULL; + return 0; + } + } + } + + usb_role_switch_set_role(qcom->dwc3_drd_sw, role); + + if (role == USB_ROLE_DEVICE) + enable = true; + else + enable = false; + + qcom->mode = (role == USB_ROLE_HOST) ? USB_DR_MODE_HOST : + USB_DR_MODE_PERIPHERAL; + dwc3_qcom_vbus_override_enable(qcom, enable); + return 0; +} + +static enum usb_role dwc3_qcom_usb_role_switch_get(struct usb_role_switch *sw) +{ + struct dwc3_qcom *qcom = usb_role_switch_get_drvdata(sw); + enum usb_role role; + + switch (qcom->mode) { + case USB_DR_MODE_HOST: + role = USB_ROLE_HOST; + break; + case USB_DR_MODE_PERIPHERAL: + role = USB_ROLE_DEVICE; + break; + default: + role = USB_ROLE_DEVICE; + break; + } + + return role; +} + +static int dwc3_qcom_setup_role_switch(struct dwc3_qcom *qcom) +{ + struct usb_role_switch_desc dwc3_role_switch = {NULL}; + + dwc3_role_switch.fwnode = dev_fwnode(qcom->dev); + dwc3_role_switch.set = dwc3_qcom_usb_role_switch_set; + dwc3_role_switch.get = dwc3_qcom_usb_role_switch_get; + dwc3_role_switch.driver_data = qcom; + qcom->role_sw = usb_role_switch_register(qcom->dev, &dwc3_role_switch); + if (IS_ERR(qcom->role_sw)) + return PTR_ERR(qcom->role_sw); + + return 0; +} + static void dwc3_qcom_disable_interrupts(struct dwc3_qcom *qcom) { if (qcom->hs_phy_irq) { @@ -708,6 +780,40 @@ dwc3_qcom_create_urs_usb_platdev(struct device *dev) return acpi_create_platform_device(adev, NULL); } +static int dwc3_qcom_connector_check(struct fwnode_handle *fwnode) +{ + if (fwnode && (!fwnode_property_match_string(fwnode, "compatible", + "gpio-usb-b-connector") || + !fwnode_property_match_string(fwnode, "compatible", + "usb-c-connector"))) + return 1; + + return 0; +} + +static void *dwc3_qcom_find_usb_connector_match(struct fwnode_handle *fwnode, + const char *id, void *data) +{ + /* Check if the "connector" node is the parent of the remote endpoint */ + if (dwc3_qcom_connector_check(fwnode)) + return fwnode; + + /* else, check if it is a child node */ + fwnode = fwnode_get_named_child_node(fwnode, "connector"); + if (dwc3_qcom_connector_check(fwnode)) + return fwnode; + + return 0; +} + +static bool dwc3_qcom_find_usb_connector(struct platform_device *pdev) +{ + struct fwnode_handle *fwnode = pdev->dev.fwnode; + + return fwnode_connection_find_match(fwnode, "connector", NULL, + dwc3_qcom_find_usb_connector_match); +} + static int dwc3_qcom_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; @@ -826,8 +932,13 @@ static int dwc3_qcom_probe(struct platform_device *pdev) if (qcom->mode == USB_DR_MODE_PERIPHERAL) dwc3_qcom_vbus_override_enable(qcom, true); - /* register extcon to override sw_vbus on Vbus change later */ - ret = dwc3_qcom_register_extcon(qcom); + if (dwc3_qcom_find_usb_connector(pdev)) { + ret = dwc3_qcom_setup_role_switch(qcom); + } else { + /* register extcon to override sw_vbus on Vbus change later */ + ret = dwc3_qcom_register_extcon(qcom); + } + if (ret) goto interconnect_exit; @@ -863,6 +974,9 @@ static int dwc3_qcom_remove(struct platform_device *pdev) struct device *dev = &pdev->dev; int i; + usb_role_switch_unregister(qcom->role_sw); + usb_role_switch_put(qcom->dwc3_drd_sw); + device_remove_software_node(&qcom->dwc3->dev); of_platform_depopulate(dev); diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c index fd55c2c516a5..2296ff6d060e 100644 --- a/drivers/usb/typec/mux.c +++ b/drivers/usb/typec/mux.c @@ -141,6 +141,48 @@ void typec_switch_put(struct typec_switch *sw) } EXPORT_SYMBOL_GPL(typec_switch_put); +static ssize_t orientation_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) { + enum typec_orientation orientation = TYPEC_ORIENTATION_NONE; + struct typec_switch_dev *sw = to_typec_switch_dev(dev); + int ret; + + if (count <= 3) + return -EINVAL; + + if (!strncasecmp(buf, "cc1", 3)) + orientation = TYPEC_ORIENTATION_NORMAL; + else if (!strncasecmp(buf, "cc2", 3)) + orientation = TYPEC_ORIENTATION_REVERSE; + + dev_info(dev, "Overriding switch to %s direction", + orientation == TYPEC_ORIENTATION_REVERSE ? "CC2" : + orientation == TYPEC_ORIENTATION_NORMAL ? "CC1" : + "none"); + + ret = sw->set(sw, orientation); + if (ret < 0) + return ret; + + return count; +} + +static DEVICE_ATTR_WO(orientation); + +static struct attribute *typec_switch_attrs[] = { + &dev_attr_orientation.attr, + NULL +}; + +static struct attribute_group typec_switch_attr_group = { + .attrs = typec_switch_attrs, +}; + +static const struct attribute_group *typec_switch_attr_groups[] = { + &typec_switch_attr_group, + NULL +}; + static void typec_switch_release(struct device *dev) { kfree(to_typec_switch_dev(dev)); @@ -149,6 +191,7 @@ static void typec_switch_release(struct device *dev) const struct device_type typec_switch_dev_type = { .name = "orientation_switch", .release = typec_switch_release, + .groups = typec_switch_attr_groups, }; /** diff --git a/drivers/usb/typec/mux/Kconfig b/drivers/usb/typec/mux/Kconfig index c1076bb765b0..b35c276a286a 100644 --- a/drivers/usb/typec/mux/Kconfig +++ b/drivers/usb/typec/mux/Kconfig @@ -36,4 +36,12 @@ config TYPEC_MUX_GPIO Driver for GPIO-controlled DP aux switch, to flip DP aux lanes when orientation changes. +config TYPEC_MUX_NB7VPQ904M + tristate "On Semiconductor NB7VPQ904M Type-C redriver driver" + depends on I2C + select REGMAP_I2C + help + Say Y or M if your system has a On Semiconductor NB7VPQ904M Type-C + redriver chip found on some devices with a Type-C port. + endmenu diff --git a/drivers/usb/typec/mux/Makefile b/drivers/usb/typec/mux/Makefile index b36d91924222..82a0e50e203e 100644 --- a/drivers/usb/typec/mux/Makefile +++ b/drivers/usb/typec/mux/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_TYPEC_MUX_FSA4480) += fsa4480.o obj-$(CONFIG_TYPEC_MUX_PI3USB30532) += pi3usb30532.o obj-$(CONFIG_TYPEC_MUX_INTEL_PMC) += intel_pmc_mux.o obj-$(CONFIG_TYPEC_MUX_GPIO) += gpio_aux_switch.o +obj-$(CONFIG_TYPEC_MUX_NB7VPQ904M) += nb7vpq904m.o diff --git a/drivers/usb/typec/mux/nb7vpq904m.c b/drivers/usb/typec/mux/nb7vpq904m.c new file mode 100644 index 000000000000..c661d3349c94 --- /dev/null +++ b/drivers/usb/typec/mux/nb7vpq904m.c @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * OnSemi NB7VPQ904M Type-C nb7 driver + * + * Copyright (C) 2020 Dmitry Baryshkov <dmitry.baryshkov@linaro.org> + */ +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/usb/typec_dp.h> +#include <linux/usb/typec_mux.h> + +#define NB7_CHNA 0 +#define NB7_CHNB 1 +#define NB7_CHNC 2 +#define NB7_CHND 3 +#define NB7_IS_CHAN_AD(channel) (channel == NB7_CHNA || channel == NB7_CHND) + +#define GEN_DEV_SET_REG 0x00 + +#define GEN_DEV_SET_CHIP_EN BIT(0) +#define GEN_DEV_SET_CHNA_EN BIT(4) +#define GEN_DEV_SET_CHNB_EN BIT(5) +#define GEN_DEV_SET_CHNC_EN BIT(6) +#define GEN_DEV_SET_CHND_EN BIT(7) + +#define GEN_DEV_SET_OP_MODE_SHIFT 1 +#define GEN_DEV_SET_OP_MODE_MASK 0x0e + +#define GEN_DEV_SET_OP_MODE_DP_CC2 0 +#define GEN_DEV_SET_OP_MODE_DP_CC1 1 +#define GEN_DEV_SET_OP_MODE_DP_4LANE 2 +#define GEN_DEV_SET_OP_MODE_USB 5 + +#define EQ_SETTING_REG_BASE 0x01 +#define EQ_SETTING_REG(n) (EQ_SETTING_REG_BASE + (n) * 2) +#define EQ_SETTING_MASK 0x0e +#define EQ_SETTING_SHIFT 0x01 + +#define OUTPUT_COMPRESSION_AND_POL_REG_BASE 0x02 +#define OUTPUT_COMPRESSION_AND_POL_REG(n) (OUTPUT_COMPRESSION_AND_POL_REG_BASE + (n) * 2) +#define OUTPUT_COMPRESSION_MASK 0x06 +#define OUTPUT_COMPRESSION_SHIFT 0x01 + +#define FLAT_GAIN_REG_BASE 0x18 +#define FLAT_GAIN_REG(n) (FLAT_GAIN_REG_BASE + (n) * 2) +#define FLAT_GAIN_MASK 0x03 +#define FLAT_GAIN_SHIFT 0x00 + +#define LOSS_MATCH_REG_BASE 0x19 +#define LOSS_MATCH_REG(n) (LOSS_MATCH_REG_BASE + (n) * 2) +#define LOSS_MATCH_MASK 0x03 +#define LOSS_MATCH_SHIFT 0x00 + +#define CHIP_VERSION_REG 0x17 + +struct nb7vpq904m { + struct i2c_client *client; + struct regmap *regmap; + struct typec_switch_dev *sw; + struct typec_mux_dev *mux; +}; + +static int nb7vpq904m_sw_set(struct typec_switch_dev *sw, + enum typec_orientation orientation) +{ + struct nb7vpq904m *nb7 = typec_switch_get_drvdata(sw); + int ret; + + dev_info(&nb7->client->dev, "SW: %d\n", orientation); + + switch (orientation) { + case TYPEC_ORIENTATION_NONE: + break; + case TYPEC_ORIENTATION_NORMAL: + break; + case TYPEC_ORIENTATION_REVERSE: + break; + } + + ret = 0; + return ret; +} + +static void +nb7vpq904m_set_channel(struct nb7vpq904m *nb7, unsigned int channel, bool dp) +{ + u8 eq, out_comp, flat_gain, loss_match; + + if (dp) { + eq = NB7_IS_CHAN_AD(channel) ? 0x6 : 0x4; + out_comp = 0x3; + flat_gain = NB7_IS_CHAN_AD(channel) ? 0x2 : 0x1; + loss_match = 0x3; + } else { + eq = 0x4; + out_comp = 0x3; + flat_gain = NB7_IS_CHAN_AD(channel) ? 0x3 : 0x1; + loss_match = NB7_IS_CHAN_AD(channel) ? 0x1 : 0x3; + } + regmap_update_bits(nb7->regmap, EQ_SETTING_REG(channel), + EQ_SETTING_MASK, eq << EQ_SETTING_SHIFT); + regmap_update_bits(nb7->regmap, OUTPUT_COMPRESSION_AND_POL_REG(channel), + OUTPUT_COMPRESSION_MASK, out_comp << OUTPUT_COMPRESSION_SHIFT); + regmap_update_bits(nb7->regmap, FLAT_GAIN_REG(channel), + FLAT_GAIN_MASK, flat_gain << FLAT_GAIN_SHIFT); + regmap_update_bits(nb7->regmap, LOSS_MATCH_REG(channel), + LOSS_MATCH_MASK, loss_match << LOSS_MATCH_SHIFT); +} + +static int +nb7vpq904m_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state) +{ + struct nb7vpq904m *nb7 = typec_mux_get_drvdata(mux); + bool reverse; + + dev_info(&nb7->client->dev, "MUX: %ld\n", state->mode); + if (state->mode == TYPEC_STATE_SAFE) { + regmap_write(nb7->regmap, GEN_DEV_SET_REG, 0x0); + return 0; + } else if (state->mode == TYPEC_STATE_USB) { + regmap_write(nb7->regmap, GEN_DEV_SET_REG, + GEN_DEV_SET_CHIP_EN | + GEN_DEV_SET_CHNA_EN | + GEN_DEV_SET_CHNB_EN | + GEN_DEV_SET_CHNC_EN | + GEN_DEV_SET_CHND_EN | + (GEN_DEV_SET_OP_MODE_USB << GEN_DEV_SET_OP_MODE_SHIFT)); + nb7vpq904m_set_channel(nb7, NB7_CHNA, false); + nb7vpq904m_set_channel(nb7, NB7_CHNB, false); + nb7vpq904m_set_channel(nb7, NB7_CHNC, false); + nb7vpq904m_set_channel(nb7, NB7_CHND, false); + return 0; + } + + dev_info(&nb7->client->dev, "MUX: %ld, orient %d, alt %x\n", state->mode, typec_altmode_get_orientation(state->alt), state->alt->svid); + + if (!state->alt || state->alt->svid != USB_TYPEC_DP_SID) + return -EINVAL; + + reverse = (typec_altmode_get_orientation(state->alt) == TYPEC_ORIENTATION_REVERSE); + + switch (state->mode) { + case TYPEC_DP_STATE_C: + case TYPEC_DP_STATE_E: + regmap_write(nb7->regmap, GEN_DEV_SET_REG, + GEN_DEV_SET_CHIP_EN | + GEN_DEV_SET_CHNA_EN | + GEN_DEV_SET_CHNB_EN | + GEN_DEV_SET_CHNC_EN | + GEN_DEV_SET_CHND_EN | + (GEN_DEV_SET_OP_MODE_DP_4LANE << GEN_DEV_SET_OP_MODE_SHIFT)); + nb7vpq904m_set_channel(nb7, NB7_CHNA, true); + nb7vpq904m_set_channel(nb7, NB7_CHNB, true); + nb7vpq904m_set_channel(nb7, NB7_CHNC, true); + nb7vpq904m_set_channel(nb7, NB7_CHND, true); + break; + case TYPEC_DP_STATE_D: + regmap_write(nb7->regmap, GEN_DEV_SET_REG, + GEN_DEV_SET_CHIP_EN | + GEN_DEV_SET_CHNA_EN | + GEN_DEV_SET_CHNB_EN | + GEN_DEV_SET_CHNC_EN | + GEN_DEV_SET_CHND_EN | + ((reverse ? GEN_DEV_SET_OP_MODE_DP_CC2 : GEN_DEV_SET_OP_MODE_DP_CC1) + << GEN_DEV_SET_OP_MODE_SHIFT)); + nb7vpq904m_set_channel(nb7, NB7_CHNA, !reverse); + nb7vpq904m_set_channel(nb7, NB7_CHNB, !reverse); + nb7vpq904m_set_channel(nb7, NB7_CHNC, reverse); + nb7vpq904m_set_channel(nb7, NB7_CHND, reverse); + break; + default: + return -ENOTSUPP; + } + + return 0; +} + +static const struct regmap_config nb7_regmap = { + .max_register = 0x1f, + .reg_bits = 8, + .val_bits = 8, +}; + +static int nb7vpq904m_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct typec_switch_desc sw_desc = { }; + struct typec_mux_desc mux_desc = { }; + struct nb7vpq904m *nb7; + + nb7 = devm_kzalloc(dev, sizeof(*nb7), GFP_KERNEL); + if (!nb7) + return -ENOMEM; + + nb7->client = client; + + nb7->regmap = devm_regmap_init_i2c(client, &nb7_regmap); + if (IS_ERR(nb7->regmap)) { + dev_err(&client->dev, "Failed to allocate register map\n"); + return PTR_ERR(nb7->regmap); + } + + sw_desc.drvdata = nb7; + sw_desc.fwnode = dev->fwnode; + sw_desc.set = nb7vpq904m_sw_set; + + nb7->sw = typec_switch_register(dev, &sw_desc); + if (IS_ERR(nb7->sw)) { + dev_err(dev, "Error registering typec switch: %ld\n", + PTR_ERR(nb7->sw)); + return PTR_ERR(nb7->sw); + } + + mux_desc.drvdata = nb7; + mux_desc.fwnode = dev->fwnode; + mux_desc.set = nb7vpq904m_mux_set; + + nb7->mux = typec_mux_register(dev, &mux_desc); + if (IS_ERR(nb7->mux)) { + typec_switch_unregister(nb7->sw); + dev_err(dev, "Error registering typec mux: %ld\n", + PTR_ERR(nb7->mux)); + return PTR_ERR(nb7->mux); + } + + return 0; +} + +static int nb7vpq904m_remove(struct i2c_client *client) +{ + struct nb7vpq904m *nb7 = i2c_get_clientdata(client); + + typec_mux_unregister(nb7->mux); + typec_switch_unregister(nb7->sw); + + return 0; +} + +static const struct i2c_device_id nb7vpq904m_table[] = { + { "nb7vpq904m" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, nb7vpq904m_table); + +static struct i2c_driver nb7vpq904m_driver = { + .driver = { + .name = "nb7vpq904m", + }, + .probe_new = nb7vpq904m_probe, + .remove = nb7vpq904m_remove, + .id_table = nb7vpq904m_table, +}; + +module_i2c_driver(nb7vpq904m_driver); + +MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); +MODULE_DESCRIPTION("Pericom PI3USB30532 Type-C mux driver"); +MODULE_LICENSE("GPL"); |