From 8f1842fce539827e908a1004141384a0e2b14ed8 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 10 Sep 2026 11:20:27 +0200 Subject: [PATCH 001/114] dt-bindings: phy: Document MT8195 and MT8196 DisplayPort PHYs This adds bindings for the DisplayPort and Embedded DisplayPort PHYs found in the MediaTek MT8195 SoC (and variants of) and for the Embedded DisplayPort found in the MT8196 SoC (and variants). This PHY supports varying impedance calibrations for the various signals to reach an optimal EYE signal pattern for any specific board(s), especially useful for very high bitrates such as HBR3 and higher, depending on board design. Reviewed-by: Krzysztof Kozlowski Acked-by: Manivannan Sadhasivam Signed-off-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260910092038.48291-2-angelogioacchino.delregno@collabora.com Signed-off-by: Vinod Koul --- .../bindings/phy/mediatek,mt8195-dp-phy.yaml | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 Documentation/devicetree/bindings/phy/mediatek,mt8195-dp-phy.yaml diff --git a/Documentation/devicetree/bindings/phy/mediatek,mt8195-dp-phy.yaml b/Documentation/devicetree/bindings/phy/mediatek,mt8195-dp-phy.yaml new file mode 100644 index 000000000000..1c2dc4f03cd6 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/mediatek,mt8195-dp-phy.yaml @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/phy/mediatek,mt8195-dp-phy.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MediaTek SoC DisplayPort Transmitter PHY + +maintainers: + - AngeloGioacchino Del Regno + +properties: + compatible: + enum: + - mediatek,mt8195-dp-phy + - mediatek,mt8196-edp-phy + + reg: + maxItems: 1 + + "#phy-cells": + const: 0 + + nvmem-cells: + description: PHY calibrations from eFuse for optimal EYE signal pattern + items: + - description: PHY-Global Reference Bias trim + - description: PHY-Global AUX Transmitter clock impedance adjustment + - description: Lane 0 Transmitter impedance selection (P-MOSFET) + - description: Lane 0 Transmitter impedance selection (N-MOSFET) + - description: Lane 1 Transmitter impedance selection (P-MOSFET) + - description: Lane 1 Transmitter impedance selection (N-MOSFET) + - description: Lane 2 Transmitter impedance selection (P-MOSFET) + - description: Lane 2 Transmitter impedance selection (N-MOSFET) + - description: Lane 3 Transmitter impedance selection (P-MOSFET) + - description: Lane 3 Transmitter impedance selection (N-MOSFET) + + nvmem-cell-names: + items: + - const: rbias-trim + - const: impedance-txclk + - const: impedance-lane0p + - const: impedance-lane0n + - const: impedance-lane1p + - const: impedance-lane1n + - const: impedance-lane2p + - const: impedance-lane2n + - const: impedance-lane3p + - const: impedance-lane3n + + power-domains: + maxItems: 1 + +required: + - compatible + - reg + - "#phy-cells" + +additionalProperties: false + +examples: + - | + phy@1c500000 { + compatible = "mediatek,mt8195-dp-phy"; + reg = <0x1c500000 0x2000>; + #phy-cells = <0>; + nvmem-cells = <&edp_glb_bias_trim>, <&edp_clktx_impsel>, + <&edp_imp_ln0_pmos>, <&edp_imp_ln0_nmos>, + <&edp_imp_ln1_pmos>, <&edp_imp_ln1_nmos>, + <&edp_imp_ln2_pmos>, <&edp_imp_ln2_nmos>, + <&edp_imp_ln3_pmos>, <&edp_imp_ln3_nmos>; + nvmem-cell-names = "rbias-trim", "impedance-txclk", + "impedance-lane0p", "impedance-lane0n", + "impedance-lane1p", "impedance-lane1n", + "impedance-lane2p", "impedance-lane2n", + "impedance-lane3p", "impedance-lane3n"; + }; From ae9d081be84ea1b133bb146570a5884e0abf2183 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 10 Sep 2026 11:20:28 +0200 Subject: [PATCH 002/114] phy: phy-mtk-dp: Rename regs to regmap in struct mtk_dp_phy In preparation to perform further cleanups and to extend the driver to support more SoCs, rename the `regs` member to `regmap` to improve readability, as this is a common name across many kernel drivers for a struct regmap. Reviewed-by: Manivannan Sadhasivam Signed-off-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260910092038.48291-3-angelogioacchino.delregno@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/mediatek/phy-mtk-dp.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/phy/mediatek/phy-mtk-dp.c b/drivers/phy/mediatek/phy-mtk-dp.c index d7024a144335..bf7b3a95e72d 100644 --- a/drivers/phy/mediatek/phy-mtk-dp.c +++ b/drivers/phy/mediatek/phy-mtk-dp.c @@ -79,7 +79,7 @@ XTP_LN_TX_LCTXCP1_SW3_PRE0_DEFAULT) struct mtk_dp_phy { - struct regmap *regs; + struct regmap *regmap; }; static int mtk_dp_phy_init(struct phy *phy) @@ -94,13 +94,13 @@ static int mtk_dp_phy_init(struct phy *phy) DRIVING_PARAM_8_DEFAULT }; - regmap_bulk_write(dp_phy->regs, MTK_DP_LANE0_DRIVING_PARAM_3, + regmap_bulk_write(dp_phy->regmap, MTK_DP_LANE0_DRIVING_PARAM_3, driving_params, ARRAY_SIZE(driving_params)); - regmap_bulk_write(dp_phy->regs, MTK_DP_LANE1_DRIVING_PARAM_3, + regmap_bulk_write(dp_phy->regmap, MTK_DP_LANE1_DRIVING_PARAM_3, driving_params, ARRAY_SIZE(driving_params)); - regmap_bulk_write(dp_phy->regs, MTK_DP_LANE2_DRIVING_PARAM_3, + regmap_bulk_write(dp_phy->regmap, MTK_DP_LANE2_DRIVING_PARAM_3, driving_params, ARRAY_SIZE(driving_params)); - regmap_bulk_write(dp_phy->regs, MTK_DP_LANE3_DRIVING_PARAM_3, + regmap_bulk_write(dp_phy->regmap, MTK_DP_LANE3_DRIVING_PARAM_3, driving_params, ARRAY_SIZE(driving_params)); return 0; @@ -131,10 +131,10 @@ static int mtk_dp_phy_configure(struct phy *phy, union phy_configure_opts *opts) val = BIT_RATE_HBR3; break; } - regmap_write(dp_phy->regs, MTK_DP_PHY_DIG_BIT_RATE, val); + regmap_write(dp_phy->regmap, MTK_DP_PHY_DIG_BIT_RATE, val); } - regmap_update_bits(dp_phy->regs, MTK_DP_PHY_DIG_PLL_CTL_1, + regmap_update_bits(dp_phy->regmap, MTK_DP_PHY_DIG_PLL_CTL_1, TPLL_SSC_EN, opts->dp.ssc ? TPLL_SSC_EN : 0); return 0; @@ -144,10 +144,10 @@ static int mtk_dp_phy_reset(struct phy *phy) { struct mtk_dp_phy *dp_phy = phy_get_drvdata(phy); - regmap_update_bits(dp_phy->regs, MTK_DP_PHY_DIG_SW_RST, + regmap_update_bits(dp_phy->regmap, MTK_DP_PHY_DIG_SW_RST, DP_GLB_SW_RST_PHYD, 0); usleep_range(50, 200); - regmap_update_bits(dp_phy->regs, MTK_DP_PHY_DIG_SW_RST, + regmap_update_bits(dp_phy->regmap, MTK_DP_PHY_DIG_SW_RST, DP_GLB_SW_RST_PHYD, 1); return 0; @@ -176,7 +176,7 @@ static int mtk_dp_phy_probe(struct platform_device *pdev) if (!dp_phy) return -ENOMEM; - dp_phy->regs = regs; + dp_phy->regmap = regs; phy = devm_phy_create(dev, NULL, &mtk_dp_phy_dev_ops); if (IS_ERR(phy)) return dev_err_probe(dev, PTR_ERR(phy), From 5cba2e8177fa2cf480b592ffe6d1b7f4f1267f8e Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 10 Sep 2026 11:20:29 +0200 Subject: [PATCH 003/114] phy: phy-mtk-dp: Allow probing with devicetree match Make it possible to decouple the registration of the DisplayPort PHY driver from the DisplayPort IP driver by adding a devicetree match to probe the PHY, registering an OF PHY provider and this device's own MMIO regmap - if, and only if, this PHY driver was registered with an OF match. In order to retain compatibility with older devicetrees that are not declaring the DisplayPort PHY as a separate node, the legacy code was moved in a `mtk_dp_phy_legacy_probe()` function, which gets called if the driver was registered by the DisplayPort one. Reviewed-by: Manivannan Sadhasivam Signed-off-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260910092038.48291-4-angelogioacchino.delregno@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/mediatek/phy-mtk-dp.c | 98 ++++++++++++++++++++++++++----- 1 file changed, 83 insertions(+), 15 deletions(-) diff --git a/drivers/phy/mediatek/phy-mtk-dp.c b/drivers/phy/mediatek/phy-mtk-dp.c index bf7b3a95e72d..d241004e2828 100644 --- a/drivers/phy/mediatek/phy-mtk-dp.c +++ b/drivers/phy/mediatek/phy-mtk-dp.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #define PHY_OFFSET 0x1000 @@ -79,6 +80,7 @@ XTP_LN_TX_LCTXCP1_SW3_PRE0_DEFAULT) struct mtk_dp_phy { + struct device *dev; struct regmap *regmap; }; @@ -160,43 +162,109 @@ static const struct phy_ops mtk_dp_phy_dev_ops = { .owner = THIS_MODULE, }; -static int mtk_dp_phy_probe(struct platform_device *pdev) +static void mtk_dp_phy_legacy_remove_lookup(void *data) +{ + struct phy *phy = data; + struct mtk_dp_phy *dp_phy = phy_get_drvdata(phy); + + phy_remove_lookup(phy, "dp", dev_name(dp_phy->dev)); +} + +static int mtk_dp_phy_legacy_probe(struct platform_device *pdev, struct mtk_dp_phy *dp_phy) { struct device *dev = &pdev->dev; - struct mtk_dp_phy *dp_phy; struct phy *phy; - struct regmap *regs; + int ret; - regs = *(struct regmap **)dev->platform_data; - if (!regs) - return dev_err_probe(dev, -EINVAL, - "No data passed, requires struct regmap**\n"); + dp_phy->regmap = *(struct regmap **)dev->platform_data; + if (!dp_phy->regmap) + return dev_err_probe(dev, -EINVAL, "No platform data available\n"); - dp_phy = devm_kzalloc(dev, sizeof(*dp_phy), GFP_KERNEL); - if (!dp_phy) - return -ENOMEM; - - dp_phy->regmap = regs; phy = devm_phy_create(dev, NULL, &mtk_dp_phy_dev_ops); if (IS_ERR(phy)) return dev_err_probe(dev, PTR_ERR(phy), "Failed to create DP PHY\n"); phy_set_drvdata(phy, dp_phy); - if (!dev->of_node) - phy_create_lookup(phy, "dp", dev_name(dev)); + ret = phy_create_lookup(phy, "dp", dev_name(dev)); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, mtk_dp_phy_legacy_remove_lookup, phy); + if (ret) + return ret; return 0; } +static const struct regmap_config mtk_dp_phy_regmap_cfg = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .disable_locking = true, +}; + +static int mtk_dp_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct phy_provider *provider; + struct mtk_dp_phy *dp_phy; + void __iomem *base; + struct phy *phy; + int ret; + + dp_phy = devm_kzalloc(dev, sizeof(*dp_phy), GFP_KERNEL); + if (!dp_phy) + return -ENOMEM; + + dp_phy->dev = dev; + + /* If there's no devicetree, go for legacy pdev probe */ + if (!dev->of_node) + return mtk_dp_phy_legacy_probe(pdev, dp_phy); + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + dp_phy->regmap = devm_regmap_init_mmio(dev, base, &mtk_dp_phy_regmap_cfg); + if (IS_ERR(dp_phy->regmap)) + return PTR_ERR(dp_phy->regmap); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + phy = devm_phy_create(dev, NULL, &mtk_dp_phy_dev_ops); + if (IS_ERR(phy)) + return dev_err_probe(dev, PTR_ERR(phy), + "Failed to create DP PHY\n"); + + phy_set_drvdata(phy, dp_phy); + + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(provider)) + return PTR_ERR(provider); + + return 0; +} + +static const struct of_device_id mtk_dp_phy_of_match[] = { + { .compatible = "mediatek,mt8195-dp-phy" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mtk_dp_phy_of_match); + static struct platform_driver mtk_dp_phy_driver = { .probe = mtk_dp_phy_probe, .driver = { .name = "mediatek-dp-phy", + .of_match_table = mtk_dp_phy_of_match, }, }; module_platform_driver(mtk_dp_phy_driver); +MODULE_AUTHOR("AngeloGioacchino Del Regno "); MODULE_AUTHOR("Markus Schneider-Pargmann "); -MODULE_DESCRIPTION("MediaTek DP PHY Driver"); +MODULE_DESCRIPTION("MediaTek DisplayPort PHY Driver"); MODULE_LICENSE("GPL"); From 3b7c0d1a2314a44f71003c196afa4f33c51e5067 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 10 Sep 2026 11:20:30 +0200 Subject: [PATCH 004/114] phy: phy-mtk-dp: Migrate register offsets to SoC specific pdata In preparation for adding support for newer SoCs and for adding more capabilities to this driver in an efficient manner, migrate all of the hardcoded register offsets to SoC specific pdata and assign that for both DT and platform probing. While at it also cleanup writing the driving parameters to the PHY by iterating through all lanes with a loop instead: note that for that to happen the default 0/1/2 driving parameters were added, those are really just zero. Reviewed-by: Manivannan Sadhasivam Signed-off-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260910092038.48291-5-angelogioacchino.delregno@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/mediatek/phy-mtk-dp.c | 143 ++++++++++++++++++++++++------ 1 file changed, 115 insertions(+), 28 deletions(-) diff --git a/drivers/phy/mediatek/phy-mtk-dp.c b/drivers/phy/mediatek/phy-mtk-dp.c index d241004e2828..20974650d940 100644 --- a/drivers/phy/mediatek/phy-mtk-dp.c +++ b/drivers/phy/mediatek/phy-mtk-dp.c @@ -4,6 +4,10 @@ * * Copyright (c) 2022, BayLibre Inc. * Copyright (c) 2022, MediaTek Inc. + * + * Major refactoring + * Copyright (c) 2026, Collabora Ltd. + * AngeloGioacchino Del Regno */ #include @@ -15,24 +19,29 @@ #include #include -#define PHY_OFFSET 0x1000 +#define MTK_DP_PHY_MAX_LANES 4 -#define MTK_DP_PHY_DIG_PLL_CTL_1 (PHY_OFFSET + 0x14) +/* DP_PHYD_PLL_CTL_1 */ #define TPLL_SSC_EN BIT(3) -#define MTK_DP_PHY_DIG_BIT_RATE (PHY_OFFSET + 0x3C) -#define BIT_RATE_RBR 0 -#define BIT_RATE_HBR 1 -#define BIT_RATE_HBR2 2 -#define BIT_RATE_HBR3 3 +/* DP_PHYD_BIT_RATE */ +#define PHYD_DIG_RG_BIT_RATE GENMASK(1, 0) +# define BIT_RATE_RBR 0 +# define BIT_RATE_HBR 1 +# define BIT_RATE_HBR2 2 +# define BIT_RATE_HBR3 3 -#define MTK_DP_PHY_DIG_SW_RST (PHY_OFFSET + 0x38) -#define DP_GLB_SW_RST_PHYD BIT(0) +/* DP_PHYD_SW_RST */ +#define PHYD_DIG_GLB_SW_RST_B GENMASK(7, 0) +# define DP_GLB_SW_RST_PHYD BIT(0) +# define DP_GLB_SW_RST_TFIFO_ANA BIT(1) +# define DP_GLB_SW_RST_XTAL_CLK BIT(2) +# define DP_GLB_SW_RST_MAIN_LINK BIT(3) + +#define DRIVING_PARAM_0_DEFAULT 0x0 +#define DRIVING_PARAM_1_DEFAULT 0x0 +#define DRIVING_PARAM_2_DEFAULT 0x0 -#define MTK_DP_LANE0_DRIVING_PARAM_3 (PHY_OFFSET + 0x138) -#define MTK_DP_LANE1_DRIVING_PARAM_3 (PHY_OFFSET + 0x238) -#define MTK_DP_LANE2_DRIVING_PARAM_3 (PHY_OFFSET + 0x338) -#define MTK_DP_LANE3_DRIVING_PARAM_3 (PHY_OFFSET + 0x438) #define XTP_LN_TX_LCTXC0_SW0_PRE0_DEFAULT BIT(4) #define XTP_LN_TX_LCTXC0_SW0_PRE1_DEFAULT (BIT(10) | BIT(12)) #define XTP_LN_TX_LCTXC0_SW0_PRE2_DEFAULT GENMASK(20, 19) @@ -79,15 +88,62 @@ #define DRIVING_PARAM_8_DEFAULT (XTP_LN_TX_LCTXCP1_SW2_PRE1_DEFAULT | \ XTP_LN_TX_LCTXCP1_SW3_PRE0_DEFAULT) +enum mtk_dp_phyd_dig_lane_regidx { + DP_PHYD_LAN_DRIVING_PARAM_0, + DP_PHYD_LAN_MAX +}; + +enum mtk_dp_phyd_dig_glb_regidx { + DP_PHYD_PLL_CTL_0, + DP_PHYD_PLL_CTL_1, + DP_PHYD_SW_RST, + DP_PHYD_BIT_RATE, + DP_PHYD_GLOBAL_MAX +}; + +static const u8 mt8195_phy_dig_lane_regs[DP_PHYD_LAN_MAX] = { + [DP_PHYD_LAN_DRIVING_PARAM_0] = 0x2c, +}; + +static const u8 mt8195_phy_dig_glb_regs[DP_PHYD_GLOBAL_MAX] = { + [DP_PHYD_PLL_CTL_0] = 0x10, + [DP_PHYD_PLL_CTL_1] = 0x14, + [DP_PHYD_SW_RST] = 0x38, + [DP_PHYD_BIT_RATE] = 0x3c, +}; + +/** + * struct mtk_dp_phy_pdata - Platform data and defaults for MediaTek DP/eDP PHY + * @off_dig_glb: Base offset for dptx_phyd_sifslv_dig_glb + * @off_dig_lane: Base offsets for dptx_phyd_sifslv_dig_lan (for each lane) + * @regs_dig_glb: Register (layout) offsets for dig_glb + * @regs_dig_lane: Register (layout) offsets for dig_lan + */ +struct mtk_dp_phy_pdata { + /* Register offsets */ + u16 off_dig_glb; + u16 off_dig_lane[MTK_DP_PHY_MAX_LANES]; + + /* Register maps */ + const u8 *regs_dig_glb; + const u8 *regs_dig_lane; +}; + struct mtk_dp_phy { struct device *dev; struct regmap *regmap; + const struct mtk_dp_phy_pdata *pdata; }; static int mtk_dp_phy_init(struct phy *phy) { struct mtk_dp_phy *dp_phy = phy_get_drvdata(phy); + const struct mtk_dp_phy_pdata *pdata = dp_phy->pdata; + const u32 reg = pdata->regs_dig_lane[DP_PHYD_LAN_DRIVING_PARAM_0]; static const u32 driving_params[] = { + DRIVING_PARAM_0_DEFAULT, + DRIVING_PARAM_1_DEFAULT, + DRIVING_PARAM_2_DEFAULT, DRIVING_PARAM_3_DEFAULT, DRIVING_PARAM_4_DEFAULT, DRIVING_PARAM_5_DEFAULT, @@ -95,15 +151,21 @@ static int mtk_dp_phy_init(struct phy *phy) DRIVING_PARAM_7_DEFAULT, DRIVING_PARAM_8_DEFAULT }; + int i, ret; - regmap_bulk_write(dp_phy->regmap, MTK_DP_LANE0_DRIVING_PARAM_3, - driving_params, ARRAY_SIZE(driving_params)); - regmap_bulk_write(dp_phy->regmap, MTK_DP_LANE1_DRIVING_PARAM_3, - driving_params, ARRAY_SIZE(driving_params)); - regmap_bulk_write(dp_phy->regmap, MTK_DP_LANE2_DRIVING_PARAM_3, - driving_params, ARRAY_SIZE(driving_params)); - regmap_bulk_write(dp_phy->regmap, MTK_DP_LANE3_DRIVING_PARAM_3, - driving_params, ARRAY_SIZE(driving_params)); + /* + * Assume that all lanes need the same driving parameters: this + * will bulk write from DRIVING_PARAM_0 to DRIVING_PARAM_8 on + * all lanes (a grand total of [9 * num_lanes] 32-bit writes) + */ + for (i = 0; i < MTK_DP_PHY_MAX_LANES; i++) { + ret = regmap_bulk_write(dp_phy->regmap, + pdata->off_dig_lane[i] + reg, + driving_params, + ARRAY_SIZE(driving_params)); + if (ret) + return ret; + }; return 0; } @@ -111,9 +173,12 @@ static int mtk_dp_phy_init(struct phy *phy) static int mtk_dp_phy_configure(struct phy *phy, union phy_configure_opts *opts) { struct mtk_dp_phy *dp_phy = phy_get_drvdata(phy); + const struct mtk_dp_phy_pdata *pdata = dp_phy->pdata; u32 val; if (opts->dp.set_rate) { + const u32 reg_bit_rate = pdata->regs_dig_glb[DP_PHYD_BIT_RATE]; + switch (opts->dp.link_rate) { default: dev_err(&phy->dev, @@ -133,10 +198,11 @@ static int mtk_dp_phy_configure(struct phy *phy, union phy_configure_opts *opts) val = BIT_RATE_HBR3; break; } - regmap_write(dp_phy->regmap, MTK_DP_PHY_DIG_BIT_RATE, val); + regmap_write(dp_phy->regmap, pdata->off_dig_glb + reg_bit_rate, val); } - regmap_update_bits(dp_phy->regmap, MTK_DP_PHY_DIG_PLL_CTL_1, + regmap_update_bits(dp_phy->regmap, + pdata->off_dig_glb + pdata->regs_dig_glb[DP_PHYD_PLL_CTL_1], TPLL_SSC_EN, opts->dp.ssc ? TPLL_SSC_EN : 0); return 0; @@ -145,12 +211,17 @@ static int mtk_dp_phy_configure(struct phy *phy, union phy_configure_opts *opts) static int mtk_dp_phy_reset(struct phy *phy) { struct mtk_dp_phy *dp_phy = phy_get_drvdata(phy); + const struct mtk_dp_phy_pdata *pdata = dp_phy->pdata; + const u32 reg_rst = pdata->regs_dig_glb[DP_PHYD_SW_RST]; - regmap_update_bits(dp_phy->regmap, MTK_DP_PHY_DIG_SW_RST, - DP_GLB_SW_RST_PHYD, 0); + /* Clearing bits sets reset state */ + regmap_clear_bits(dp_phy->regmap, pdata->off_dig_glb + reg_rst, DP_GLB_SW_RST_PHYD); + + /* PHYD needs 50uS to guarantee reset done */ usleep_range(50, 200); - regmap_update_bits(dp_phy->regmap, MTK_DP_PHY_DIG_SW_RST, - DP_GLB_SW_RST_PHYD, 1); + + /* Setting bits means go out of reset */ + regmap_set_bits(dp_phy->regmap, pdata->off_dig_glb + reg_rst, DP_GLB_SW_RST_PHYD); return 0; } @@ -170,12 +241,19 @@ static void mtk_dp_phy_legacy_remove_lookup(void *data) phy_remove_lookup(phy, "dp", dev_name(dp_phy->dev)); } +static const struct mtk_dp_phy_pdata mt8195_dp_phy_data; + static int mtk_dp_phy_legacy_probe(struct platform_device *pdev, struct mtk_dp_phy *dp_phy) { struct device *dev = &pdev->dev; struct phy *phy; int ret; + /* + * If legacy platform driver probe, assume this is MT8195 or compatible + * with a devicetree that was not migrated to the new, proper bindings. + */ + dp_phy->pdata = &mt8195_dp_phy_data; dp_phy->regmap = *(struct regmap **)dev->platform_data; if (!dp_phy->regmap) return dev_err_probe(dev, -EINVAL, "No platform data available\n"); @@ -235,6 +313,8 @@ static int mtk_dp_phy_probe(struct platform_device *pdev) if (ret) return ret; + dp_phy->pdata = device_get_match_data(dev); + phy = devm_phy_create(dev, NULL, &mtk_dp_phy_dev_ops); if (IS_ERR(phy)) return dev_err_probe(dev, PTR_ERR(phy), @@ -249,8 +329,15 @@ static int mtk_dp_phy_probe(struct platform_device *pdev) return 0; } +static const struct mtk_dp_phy_pdata mt8195_dp_phy_data = { + .off_dig_glb = 0x1000, + .off_dig_lane = (const u16[]) { 0x1100, 0x1200, 0x1300, 0x1400 }, + .regs_dig_glb = mt8195_phy_dig_glb_regs, + .regs_dig_lane = mt8195_phy_dig_lane_regs, +}; + static const struct of_device_id mtk_dp_phy_of_match[] = { - { .compatible = "mediatek,mt8195-dp-phy" }, + { .compatible = "mediatek,mt8195-dp-phy", .data = &mt8195_dp_phy_data }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, mtk_dp_phy_of_match); From 0ad8341428d9737c573c552855b8603cf5684565 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 10 Sep 2026 11:20:31 +0200 Subject: [PATCH 005/114] phy: phy-mtk-dp: Implement power_on and power_off PHY callbacks Add .power_on() and .power_off() callbacks to mtk_dp_phy_dev_ops to be able to call those with phy_power_on() and phy_power_off() API in the DisplayPort driver to be able to stop using all of those hardcoded register writes in that external driver. Signed-off-by: AngeloGioacchino Del Regno Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260910092038.48291-6-angelogioacchino.delregno@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/mediatek/phy-mtk-dp.c | 68 +++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/drivers/phy/mediatek/phy-mtk-dp.c b/drivers/phy/mediatek/phy-mtk-dp.c index 20974650d940..4247506ceb99 100644 --- a/drivers/phy/mediatek/phy-mtk-dp.c +++ b/drivers/phy/mediatek/phy-mtk-dp.c @@ -21,6 +21,9 @@ #define MTK_DP_PHY_MAX_LANES 4 +/* DP_PHYA_GLB_FORCE_CTRL_1 */ +#define CKM_CKTX0_EN_FORCE_MODE BIT(10) + /* DP_PHYD_PLL_CTL_1 */ #define TPLL_SSC_EN BIT(3) @@ -38,6 +41,11 @@ # define DP_GLB_SW_RST_XTAL_CLK BIT(2) # define DP_GLB_SW_RST_MAIN_LINK BIT(3) +/* DP_PHYD_AUX_RX_CTL */ +#define PHYD_DIG_DPAUX_RX_EN BIT(0) +#define PHYD_DIG_XTP_GLB_CKDET_EN BIT(1) +#define PHYD_DIG_DPAUX_RX_DEGLITCH_EN BIT(2) + #define DRIVING_PARAM_0_DEFAULT 0x0 #define DRIVING_PARAM_1_DEFAULT 0x0 #define DRIVING_PARAM_2_DEFAULT 0x0 @@ -88,6 +96,12 @@ #define DRIVING_PARAM_8_DEFAULT (XTP_LN_TX_LCTXCP1_SW2_PRE1_DEFAULT | \ XTP_LN_TX_LCTXCP1_SW3_PRE0_DEFAULT) +enum mtk_dp_phya_ana_glb_regidx { + DP_PHYA_GLB_FORCE_CTRL_0, + DP_PHYA_GLB_FORCE_CTRL_1, + DP_PHYA_GLOBAL_MAX +}; + enum mtk_dp_phyd_dig_lane_regidx { DP_PHYD_LAN_DRIVING_PARAM_0, DP_PHYD_LAN_MAX @@ -98,9 +112,15 @@ enum mtk_dp_phyd_dig_glb_regidx { DP_PHYD_PLL_CTL_1, DP_PHYD_SW_RST, DP_PHYD_BIT_RATE, + DP_PHYD_AUX_RX_CTL, DP_PHYD_GLOBAL_MAX }; +static const u8 mt8195_phy_ana_glb_regs[DP_PHYA_GLOBAL_MAX] = { + [DP_PHYA_GLB_FORCE_CTRL_0] = 0x30, + [DP_PHYA_GLB_FORCE_CTRL_1] = 0x34, +}; + static const u8 mt8195_phy_dig_lane_regs[DP_PHYD_LAN_MAX] = { [DP_PHYD_LAN_DRIVING_PARAM_0] = 0x2c, }; @@ -110,21 +130,26 @@ static const u8 mt8195_phy_dig_glb_regs[DP_PHYD_GLOBAL_MAX] = { [DP_PHYD_PLL_CTL_1] = 0x14, [DP_PHYD_SW_RST] = 0x38, [DP_PHYD_BIT_RATE] = 0x3c, + [DP_PHYD_AUX_RX_CTL] = 0x40, }; /** * struct mtk_dp_phy_pdata - Platform data and defaults for MediaTek DP/eDP PHY + * @off_ana_glb: Base offset for dptx_phyd_sifslv_ana_glb * @off_dig_glb: Base offset for dptx_phyd_sifslv_dig_glb * @off_dig_lane: Base offsets for dptx_phyd_sifslv_dig_lan (for each lane) + * @regs_ana_glb: Register (layout) offsets for ana_glb * @regs_dig_glb: Register (layout) offsets for dig_glb * @regs_dig_lane: Register (layout) offsets for dig_lan */ struct mtk_dp_phy_pdata { /* Register offsets */ + u16 off_ana_glb; u16 off_dig_glb; u16 off_dig_lane[MTK_DP_PHY_MAX_LANES]; /* Register maps */ + const u8 *regs_ana_glb; const u8 *regs_dig_glb; const u8 *regs_dig_lane; }; @@ -208,6 +233,45 @@ static int mtk_dp_phy_configure(struct phy *phy, union phy_configure_opts *opts) return 0; } +static int mtk_dp_phy_power_on(struct phy *phy) +{ + struct mtk_dp_phy *dp_phy = phy_get_drvdata(phy); + const struct mtk_dp_phy_pdata *pdata = dp_phy->pdata; + const u8 *regs_dig = pdata->regs_dig_glb; + const u8 *regs_ana = pdata->regs_ana_glb; + + /* Enable AUX Channel with RX De-Glitch and input clock detection */ + regmap_write(dp_phy->regmap, + pdata->off_dig_glb + regs_dig[DP_PHYD_AUX_RX_CTL], + PHYD_DIG_DPAUX_RX_EN | + PHYD_DIG_XTP_GLB_CKDET_EN | + PHYD_DIG_DPAUX_RX_DEGLITCH_EN); + + regmap_clear_bits(dp_phy->regmap, + pdata->off_ana_glb + regs_ana[DP_PHYA_GLB_FORCE_CTRL_1], + CKM_CKTX0_EN_FORCE_MODE); + + return 0; +} + +static int mtk_dp_phy_power_off(struct phy *phy) +{ + struct mtk_dp_phy *dp_phy = phy_get_drvdata(phy); + const struct mtk_dp_phy_pdata *pdata = dp_phy->pdata; + const u8 *regs_dig = pdata->regs_dig_glb; + const u8 *regs_ana = pdata->regs_ana_glb; + + regmap_set_bits(dp_phy->regmap, + pdata->off_ana_glb + regs_ana[DP_PHYA_GLB_FORCE_CTRL_1], + CKM_CKTX0_EN_FORCE_MODE); + + /* Disable RX unconditionally */ + regmap_write(dp_phy->regmap, + pdata->off_dig_glb + regs_dig[DP_PHYD_AUX_RX_CTL], 0); + + return 0; +} + static int mtk_dp_phy_reset(struct phy *phy) { struct mtk_dp_phy *dp_phy = phy_get_drvdata(phy); @@ -228,6 +292,8 @@ static int mtk_dp_phy_reset(struct phy *phy) static const struct phy_ops mtk_dp_phy_dev_ops = { .init = mtk_dp_phy_init, + .power_on = mtk_dp_phy_power_on, + .power_off = mtk_dp_phy_power_off, .configure = mtk_dp_phy_configure, .reset = mtk_dp_phy_reset, .owner = THIS_MODULE, @@ -330,8 +396,10 @@ static int mtk_dp_phy_probe(struct platform_device *pdev) } static const struct mtk_dp_phy_pdata mt8195_dp_phy_data = { + .off_ana_glb = 0x0, .off_dig_glb = 0x1000, .off_dig_lane = (const u16[]) { 0x1100, 0x1200, 0x1300, 0x1400 }, + .regs_ana_glb = mt8195_phy_ana_glb_regs, .regs_dig_glb = mt8195_phy_dig_glb_regs, .regs_dig_lane = mt8195_phy_dig_lane_regs, }; From 8044b21712dec852276dc63d3ebf480997b9a82e Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 10 Sep 2026 11:20:32 +0200 Subject: [PATCH 006/114] phy: phy-mtk-dp: Support set_lanes in configure and properly cleanup Add support for enabling a specifically requested number of lanes in the .configure() callback and disable all lanes in power off and reset callbacks for proper hardware cleanup. Signed-off-by: AngeloGioacchino Del Regno Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260910092038.48291-7-angelogioacchino.delregno@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/mediatek/phy-mtk-dp.c | 57 +++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/drivers/phy/mediatek/phy-mtk-dp.c b/drivers/phy/mediatek/phy-mtk-dp.c index 4247506ceb99..276391d991aa 100644 --- a/drivers/phy/mediatek/phy-mtk-dp.c +++ b/drivers/phy/mediatek/phy-mtk-dp.c @@ -10,6 +10,7 @@ * AngeloGioacchino Del Regno */ +#include #include #include #include @@ -50,6 +51,9 @@ #define DRIVING_PARAM_1_DEFAULT 0x0 #define DRIVING_PARAM_2_DEFAULT 0x0 +/* DP_PHYD_TX_CTL_0 */ +#define PHYD_TX_LN_EN GENMASK(7, 4) + #define XTP_LN_TX_LCTXC0_SW0_PRE0_DEFAULT BIT(4) #define XTP_LN_TX_LCTXC0_SW0_PRE1_DEFAULT (BIT(10) | BIT(12)) #define XTP_LN_TX_LCTXC0_SW0_PRE2_DEFAULT GENMASK(20, 19) @@ -113,6 +117,7 @@ enum mtk_dp_phyd_dig_glb_regidx { DP_PHYD_SW_RST, DP_PHYD_BIT_RATE, DP_PHYD_AUX_RX_CTL, + DP_PHYD_TX_CTL_0, DP_PHYD_GLOBAL_MAX }; @@ -131,6 +136,7 @@ static const u8 mt8195_phy_dig_glb_regs[DP_PHYD_GLOBAL_MAX] = { [DP_PHYD_SW_RST] = 0x38, [DP_PHYD_BIT_RATE] = 0x3c, [DP_PHYD_AUX_RX_CTL] = 0x40, + [DP_PHYD_TX_CTL_0] = 0x44, }; /** @@ -200,6 +206,7 @@ static int mtk_dp_phy_configure(struct phy *phy, union phy_configure_opts *opts) struct mtk_dp_phy *dp_phy = phy_get_drvdata(phy); const struct mtk_dp_phy_pdata *pdata = dp_phy->pdata; u32 val; + int i; if (opts->dp.set_rate) { const u32 reg_bit_rate = pdata->regs_dig_glb[DP_PHYD_BIT_RATE]; @@ -226,6 +233,17 @@ static int mtk_dp_phy_configure(struct phy *phy, union phy_configure_opts *opts) regmap_write(dp_phy->regmap, pdata->off_dig_glb + reg_bit_rate, val); } + if (opts->dp.set_lanes) { + const u32 reg_dig_tx_ctl = pdata->regs_dig_glb[DP_PHYD_TX_CTL_0]; + + val = 0; + for (i = 0; i < opts->dp.lanes; i++) + val |= FIELD_PREP(PHYD_TX_LN_EN, BIT(i)); + + regmap_update_bits(dp_phy->regmap, pdata->off_dig_glb + reg_dig_tx_ctl, + PHYD_TX_LN_EN, val); + } + regmap_update_bits(dp_phy->regmap, pdata->off_dig_glb + pdata->regs_dig_glb[DP_PHYD_PLL_CTL_1], TPLL_SSC_EN, opts->dp.ssc ? TPLL_SSC_EN : 0); @@ -254,12 +272,39 @@ static int mtk_dp_phy_power_on(struct phy *phy) return 0; } +static int mtk_dp_phy_disable_all_lanes(struct mtk_dp_phy *dp_phy) +{ + const struct mtk_dp_phy_pdata *pdata = dp_phy->pdata; + const u8 *regs = pdata->regs_dig_glb; + int ret; + u32 val; + + /* Get mask of currently enabled lane */ + regmap_read(dp_phy->regmap, pdata->off_dig_glb + regs[DP_PHYD_TX_CTL_0], &val); + val = FIELD_GET(PHYD_TX_LN_EN, val); + if (val == 0) + return 0; + + /* Disable all lanes (needs to be done one by one, from last to first) */ + do { + u32 lane_num = fls(val) - 1; + val &= ~BIT(lane_num); + + regmap_clear_bits(dp_phy->regmap, + pdata->off_dig_glb + regs[DP_PHYD_TX_CTL_0], + FIELD_PREP(PHYD_TX_LN_EN, BIT(lane_num))); + } while (val); + + return 0; +} + static int mtk_dp_phy_power_off(struct phy *phy) { struct mtk_dp_phy *dp_phy = phy_get_drvdata(phy); const struct mtk_dp_phy_pdata *pdata = dp_phy->pdata; const u8 *regs_dig = pdata->regs_dig_glb; const u8 *regs_ana = pdata->regs_ana_glb; + int ret; regmap_set_bits(dp_phy->regmap, pdata->off_ana_glb + regs_ana[DP_PHYA_GLB_FORCE_CTRL_1], @@ -269,6 +314,12 @@ static int mtk_dp_phy_power_off(struct phy *phy) regmap_write(dp_phy->regmap, pdata->off_dig_glb + regs_dig[DP_PHYD_AUX_RX_CTL], 0); + ret = mtk_dp_phy_disable_all_lanes(dp_phy); + if (ret) { + dev_err(dp_phy->dev, "Could not disable lanes for poweroff!\n"); + return ret; + } + return 0; } @@ -277,6 +328,7 @@ static int mtk_dp_phy_reset(struct phy *phy) struct mtk_dp_phy *dp_phy = phy_get_drvdata(phy); const struct mtk_dp_phy_pdata *pdata = dp_phy->pdata; const u32 reg_rst = pdata->regs_dig_glb[DP_PHYD_SW_RST]; + int ret; /* Clearing bits sets reset state */ regmap_clear_bits(dp_phy->regmap, pdata->off_dig_glb + reg_rst, DP_GLB_SW_RST_PHYD); @@ -287,6 +339,11 @@ static int mtk_dp_phy_reset(struct phy *phy) /* Setting bits means go out of reset */ regmap_set_bits(dp_phy->regmap, pdata->off_dig_glb + reg_rst, DP_GLB_SW_RST_PHYD); + /* Disable all lanes and continue reset even if this fails, but notify */ + ret = mtk_dp_phy_disable_all_lanes(dp_phy); + if (ret) + dev_err(dp_phy->dev, "Could not disable lanes during reset!\n"); + return 0; } From e16a43ca1e175b517c7859c21e8616679ac5bb8d Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 10 Sep 2026 11:20:33 +0200 Subject: [PATCH 007/114] phy: phy-mtk-dp: Support setting volt swing and preemphasis values Add support for honoring the set_voltages request to set the voltage swing and preemphasis values in the .configure() callback. For proper hardware cleanup, reset both in the .reset() callback. Reviewed-by: Manivannan Sadhasivam Signed-off-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260910092038.48291-8-angelogioacchino.delregno@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/mediatek/phy-mtk-dp.c | 44 ++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/drivers/phy/mediatek/phy-mtk-dp.c b/drivers/phy/mediatek/phy-mtk-dp.c index 276391d991aa..692049f78833 100644 --- a/drivers/phy/mediatek/phy-mtk-dp.c +++ b/drivers/phy/mediatek/phy-mtk-dp.c @@ -54,6 +54,11 @@ /* DP_PHYD_TX_CTL_0 */ #define PHYD_TX_LN_EN GENMASK(7, 4) +/* DP_PHYD_DRIVING_FORCE */ +#define PHYD_DP_TX_FORCE_VOLT_SWING_EN BIT(0) +#define PHYD_DP_TX_FORCE_VOLT_SWING_VAL GENMASK(2, 1) +#define PHYD_DP_TX_FORCE_PRE_EMPH_VAL GENMASK(4, 3) + #define XTP_LN_TX_LCTXC0_SW0_PRE0_DEFAULT BIT(4) #define XTP_LN_TX_LCTXC0_SW0_PRE1_DEFAULT (BIT(10) | BIT(12)) #define XTP_LN_TX_LCTXC0_SW0_PRE2_DEFAULT GENMASK(20, 19) @@ -107,6 +112,7 @@ enum mtk_dp_phya_ana_glb_regidx { }; enum mtk_dp_phyd_dig_lane_regidx { + DP_PHYD_LAN_DRIVING_FORCE, DP_PHYD_LAN_DRIVING_PARAM_0, DP_PHYD_LAN_MAX }; @@ -127,6 +133,7 @@ static const u8 mt8195_phy_ana_glb_regs[DP_PHYA_GLOBAL_MAX] = { }; static const u8 mt8195_phy_dig_lane_regs[DP_PHYD_LAN_MAX] = { + [DP_PHYD_LAN_DRIVING_FORCE] = 0x18, [DP_PHYD_LAN_DRIVING_PARAM_0] = 0x2c, }; @@ -244,6 +251,30 @@ static int mtk_dp_phy_configure(struct phy *phy, union phy_configure_opts *opts) PHYD_TX_LN_EN, val); } + if (opts->dp.set_voltages) { + const u32 reg_drv_force = pdata->regs_dig_lane[DP_PHYD_LAN_DRIVING_FORCE]; + + if (opts->dp.lanes > 4) { + dev_err(&phy->dev, "Wrong lanes config %u\n", opts->dp.lanes); + return -EINVAL; + } + + for (i = 0; i < opts->dp.lanes; i++) { + const u32 off_dig_lane = pdata->off_dig_lane[i]; + u32 val; + + val = FIELD_PREP(PHYD_DP_TX_FORCE_VOLT_SWING_VAL, opts->dp.voltage[i]); + val |= FIELD_PREP(PHYD_DP_TX_FORCE_PRE_EMPH_VAL, opts->dp.pre[i]); + val |= PHYD_DP_TX_FORCE_VOLT_SWING_EN; + + regmap_update_bits(dp_phy->regmap, off_dig_lane + reg_drv_force, + PHYD_DP_TX_FORCE_VOLT_SWING_EN | + PHYD_DP_TX_FORCE_VOLT_SWING_VAL | + PHYD_DP_TX_FORCE_PRE_EMPH_VAL, + val); + } + } + regmap_update_bits(dp_phy->regmap, pdata->off_dig_glb + pdata->regs_dig_glb[DP_PHYD_PLL_CTL_1], TPLL_SSC_EN, opts->dp.ssc ? TPLL_SSC_EN : 0); @@ -328,7 +359,8 @@ static int mtk_dp_phy_reset(struct phy *phy) struct mtk_dp_phy *dp_phy = phy_get_drvdata(phy); const struct mtk_dp_phy_pdata *pdata = dp_phy->pdata; const u32 reg_rst = pdata->regs_dig_glb[DP_PHYD_SW_RST]; - int ret; + const u32 reg_drv_force = pdata->regs_dig_lane[DP_PHYD_LAN_DRIVING_FORCE]; + int i, ret; /* Clearing bits sets reset state */ regmap_clear_bits(dp_phy->regmap, pdata->off_dig_glb + reg_rst, DP_GLB_SW_RST_PHYD); @@ -344,6 +376,16 @@ static int mtk_dp_phy_reset(struct phy *phy) if (ret) dev_err(dp_phy->dev, "Could not disable lanes during reset!\n"); + /* Reset Voltage Swing and Preemphasis values */ + for (i = 0; i < MTK_DP_PHY_MAX_LANES; i++) { + const u32 off_dig_lane = pdata->off_dig_lane[i]; + + regmap_clear_bits(dp_phy->regmap, off_dig_lane + reg_drv_force, + PHYD_DP_TX_FORCE_VOLT_SWING_EN | + PHYD_DP_TX_FORCE_VOLT_SWING_VAL | + PHYD_DP_TX_FORCE_PRE_EMPH_VAL); + } + return 0; } From 187ad0523f3517c6fe8455e5401e3a78283a5f08 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 10 Sep 2026 11:20:34 +0200 Subject: [PATCH 008/114] phy: phy-mtk-dp: Add support for digital and analog calibration Add support for reading the calibration values from eFuse: if present, write those - otherwise, rely on the defaults from SoC-specific data. This also adds support for writing the calibration values for the analog part of the PHY. Note that before this change, only default hardcoded calibration values were supported for the digital driving parameters. Signed-off-by: AngeloGioacchino Del Regno Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260910092038.48291-9-angelogioacchino.delregno@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/mediatek/phy-mtk-dp.c | 289 +++++++++++++++++++++++++++--- 1 file changed, 261 insertions(+), 28 deletions(-) diff --git a/drivers/phy/mediatek/phy-mtk-dp.c b/drivers/phy/mediatek/phy-mtk-dp.c index 692049f78833..2f4cf52d922c 100644 --- a/drivers/phy/mediatek/phy-mtk-dp.c +++ b/drivers/phy/mediatek/phy-mtk-dp.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,19 @@ #define MTK_DP_PHY_MAX_LANES 4 +/* DP_PHYA_GLB_BIAS_GEN_0 (PHYA - Analog) */ +#define XTP_GLB_BIAS_INT_R_CTRL GENMASK(20, 16) + +/* DP_PHYA_GLB_FORCE_CTRL_1 */ +#define CKM_CKTX0_EN_FORCE_MODE BIT(10) + +/* DP_PHYA_GLB_DPAUX_TX */ +#define CKM_PT0_CKTX_IMPSEL GENMASK(23, 20) + +/* DP_PHYA_LAN_LANE_TX_0 */ +#define XTP_LN_TX_IMPSEL_PMOS GENMASK(15, 12) +#define XTP_LN_TX_IMPSEL_NMOS GENMASK(19, 16) + /* DP_PHYA_GLB_FORCE_CTRL_1 */ #define CKM_CKTX0_EN_FORCE_MODE BIT(10) @@ -59,11 +73,29 @@ #define PHYD_DP_TX_FORCE_VOLT_SWING_VAL GENMASK(2, 1) #define PHYD_DP_TX_FORCE_PRE_EMPH_VAL GENMASK(4, 3) +/* + * DRIVING_PARAM_X (PHYD - Digital) + * + * Driving param registers are split in three sets, all containing settings + * for Voltage Swing and Pre-Emphasis for each lane's differential pair. + * + * All three sets share the same layout, but for different physical signals; + * In particular: + * [0-2]: LC TX CM (Minus / Negative Edge) + * [3-5]: LC TX C (Logic State Change Point) + * [6-8]: LC TX CP (Plus / Positive Edge) + * + * And they contain values for: + * [0,3,6]: Swing 0 Pre[0-3] + * [1,4,7]: Swing 1 Pre[0-2] and Swing 2 Pre0 + * [2,5,8]: Swing 2 Pre1 and Swing 3 Pre0 + */ +#define PHYD_DIG_NUM_DRV_PARA_REGS 9 #define XTP_LN_TX_LCTXC0_SW0_PRE0_DEFAULT BIT(4) #define XTP_LN_TX_LCTXC0_SW0_PRE1_DEFAULT (BIT(10) | BIT(12)) #define XTP_LN_TX_LCTXC0_SW0_PRE2_DEFAULT GENMASK(20, 19) #define XTP_LN_TX_LCTXC0_SW0_PRE3_DEFAULT GENMASK(29, 29) -#define DRIVING_PARAM_3_DEFAULT (XTP_LN_TX_LCTXC0_SW0_PRE0_DEFAULT | \ +#define MT8195_DRIVING_PARAM_3_DEFAULT (XTP_LN_TX_LCTXC0_SW0_PRE0_DEFAULT | \ XTP_LN_TX_LCTXC0_SW0_PRE1_DEFAULT | \ XTP_LN_TX_LCTXC0_SW0_PRE2_DEFAULT | \ XTP_LN_TX_LCTXC0_SW0_PRE3_DEFAULT) @@ -72,21 +104,21 @@ #define XTP_LN_TX_LCTXC0_SW1_PRE1_DEFAULT GENMASK(12, 9) #define XTP_LN_TX_LCTXC0_SW1_PRE2_DEFAULT (BIT(18) | BIT(21)) #define XTP_LN_TX_LCTXC0_SW2_PRE0_DEFAULT GENMASK(29, 29) -#define DRIVING_PARAM_4_DEFAULT (XTP_LN_TX_LCTXC0_SW1_PRE0_DEFAULT | \ +#define MT8195_DRIVING_PARAM_4_DEFAULT (XTP_LN_TX_LCTXC0_SW1_PRE0_DEFAULT | \ XTP_LN_TX_LCTXC0_SW1_PRE1_DEFAULT | \ XTP_LN_TX_LCTXC0_SW1_PRE2_DEFAULT | \ XTP_LN_TX_LCTXC0_SW2_PRE0_DEFAULT) #define XTP_LN_TX_LCTXC0_SW2_PRE1_DEFAULT (BIT(3) | BIT(5)) #define XTP_LN_TX_LCTXC0_SW3_PRE0_DEFAULT GENMASK(13, 12) -#define DRIVING_PARAM_5_DEFAULT (XTP_LN_TX_LCTXC0_SW2_PRE1_DEFAULT | \ +#define MT8195_DRIVING_PARAM_5_DEFAULT (XTP_LN_TX_LCTXC0_SW2_PRE1_DEFAULT | \ XTP_LN_TX_LCTXC0_SW3_PRE0_DEFAULT) #define XTP_LN_TX_LCTXCP1_SW0_PRE0_DEFAULT 0 #define XTP_LN_TX_LCTXCP1_SW0_PRE1_DEFAULT GENMASK(10, 10) #define XTP_LN_TX_LCTXCP1_SW0_PRE2_DEFAULT GENMASK(19, 19) #define XTP_LN_TX_LCTXCP1_SW0_PRE3_DEFAULT GENMASK(28, 28) -#define DRIVING_PARAM_6_DEFAULT (XTP_LN_TX_LCTXCP1_SW0_PRE0_DEFAULT | \ +#define MT8195_DRIVING_PARAM_6_DEFAULT (XTP_LN_TX_LCTXCP1_SW0_PRE0_DEFAULT | \ XTP_LN_TX_LCTXCP1_SW0_PRE1_DEFAULT | \ XTP_LN_TX_LCTXCP1_SW0_PRE2_DEFAULT | \ XTP_LN_TX_LCTXCP1_SW0_PRE3_DEFAULT) @@ -95,22 +127,30 @@ #define XTP_LN_TX_LCTXCP1_SW1_PRE1_DEFAULT GENMASK(10, 9) #define XTP_LN_TX_LCTXCP1_SW1_PRE2_DEFAULT GENMASK(19, 18) #define XTP_LN_TX_LCTXCP1_SW2_PRE0_DEFAULT 0 -#define DRIVING_PARAM_7_DEFAULT (XTP_LN_TX_LCTXCP1_SW1_PRE0_DEFAULT | \ +#define MT8195_DRIVING_PARAM_7_DEFAULT (XTP_LN_TX_LCTXCP1_SW1_PRE0_DEFAULT | \ XTP_LN_TX_LCTXCP1_SW1_PRE1_DEFAULT | \ XTP_LN_TX_LCTXCP1_SW1_PRE2_DEFAULT | \ XTP_LN_TX_LCTXCP1_SW2_PRE0_DEFAULT) #define XTP_LN_TX_LCTXCP1_SW2_PRE1_DEFAULT GENMASK(3, 3) #define XTP_LN_TX_LCTXCP1_SW3_PRE0_DEFAULT 0 -#define DRIVING_PARAM_8_DEFAULT (XTP_LN_TX_LCTXCP1_SW2_PRE1_DEFAULT | \ +#define MT8195_DRIVING_PARAM_8_DEFAULT (XTP_LN_TX_LCTXCP1_SW2_PRE1_DEFAULT | \ XTP_LN_TX_LCTXCP1_SW3_PRE0_DEFAULT) enum mtk_dp_phya_ana_glb_regidx { + DP_PHYA_GLB_BIAS_GEN_0, + DP_PHYA_GLB_BIAS_GEN_1, + DP_PHYA_GLB_DPAUX_TX, DP_PHYA_GLB_FORCE_CTRL_0, DP_PHYA_GLB_FORCE_CTRL_1, DP_PHYA_GLOBAL_MAX }; +enum mtk_dp_phya_ana_lane_regidx { + DP_PHYA_LAN_LANE_TX_0, + DP_PHYA_LAN_MAX +}; + enum mtk_dp_phyd_dig_lane_regidx { DP_PHYD_LAN_DRIVING_FORCE, DP_PHYD_LAN_DRIVING_PARAM_0, @@ -128,10 +168,17 @@ enum mtk_dp_phyd_dig_glb_regidx { }; static const u8 mt8195_phy_ana_glb_regs[DP_PHYA_GLOBAL_MAX] = { + [DP_PHYA_GLB_BIAS_GEN_0] = 0x0, + [DP_PHYA_GLB_BIAS_GEN_1] = 0x4, + [DP_PHYA_GLB_DPAUX_TX] = 0x8, [DP_PHYA_GLB_FORCE_CTRL_0] = 0x30, [DP_PHYA_GLB_FORCE_CTRL_1] = 0x34, }; +static const u8 mt8195_phy_ana_lane_regs[DP_PHYA_LAN_MAX] = { + [DP_PHYA_LAN_LANE_TX_0] = 0x4, +}; + static const u8 mt8195_phy_dig_lane_regs[DP_PHYD_LAN_MAX] = { [DP_PHYD_LAN_DRIVING_FORCE] = 0x18, [DP_PHYD_LAN_DRIVING_PARAM_0] = 0x2c, @@ -146,49 +193,94 @@ static const u8 mt8195_phy_dig_glb_regs[DP_PHYD_GLOBAL_MAX] = { [DP_PHYD_TX_CTL_0] = 0x44, }; +/** + * struct mtk_dp_phya_imp_sel - Per-Lane Impedance Selection + * @pmos: Impedance selection for P-Channel MOSFET + * @nmos: Impedance selection for N-Channel MOSFET + */ +struct mtk_dp_phya_imp_sel { + u8 pmos : 4; + u8 nmos : 4; +}; + /** * struct mtk_dp_phy_pdata - Platform data and defaults for MediaTek DP/eDP PHY * @off_ana_glb: Base offset for dptx_phyd_sifslv_ana_glb + * @off_ana_lane: Base offsets for dptx_phyd_sifslv_ana_lan (for each lane) * @off_dig_glb: Base offset for dptx_phyd_sifslv_dig_glb * @off_dig_lane: Base offsets for dptx_phyd_sifslv_dig_lan (for each lane) * @regs_ana_glb: Register (layout) offsets for ana_glb + * @regs_ana_lane: Register (layout) offsets for ana_lan * @regs_dig_glb: Register (layout) offsets for dig_glb * @regs_dig_lane: Register (layout) offsets for dig_lan + * @ana_bias_r: Internal resistance "R" Selection Settings (global) + * @ana_cktx_imp: TX Clock Impedance Selection Settings (global) + * @ana_lanes_imp: TX Impedance Selection Settings (for all lanes) + * @driving_params: Voltage Swing and Pre-Emphasis settings (for all lanes) */ struct mtk_dp_phy_pdata { /* Register offsets */ u16 off_ana_glb; + u16 off_ana_lane[MTK_DP_PHY_MAX_LANES]; u16 off_dig_glb; u16 off_dig_lane[MTK_DP_PHY_MAX_LANES]; /* Register maps */ const u8 *regs_ana_glb; + const u8 *regs_ana_lane; const u8 *regs_dig_glb; const u8 *regs_dig_lane; + + /* Calibration defaults */ + u8 ana_bias_r; + u8 ana_cktx_imp; + struct mtk_dp_phya_imp_sel ana_lanes_imp; + u32 driving_params[PHYD_DIG_NUM_DRV_PARA_REGS]; }; struct mtk_dp_phy { struct device *dev; struct regmap *regmap; const struct mtk_dp_phy_pdata *pdata; + + u8 ana_bias_r; + u8 ana_cktx_imp; + struct mtk_dp_phya_imp_sel ana_impsel[MTK_DP_PHY_MAX_LANES]; }; -static int mtk_dp_phy_init(struct phy *phy) +static void mtk_dp_phy_set_analog_calibration_params(struct mtk_dp_phy *dp_phy) +{ + const struct mtk_dp_phy_pdata *pdata = dp_phy->pdata; + const u8 *regs_ana_glb = pdata->regs_ana_glb; + const u8 *regs_ana_lane = pdata->regs_ana_lane; + int i; + + regmap_update_bits(dp_phy->regmap, + pdata->off_ana_glb + regs_ana_glb[DP_PHYA_GLB_BIAS_GEN_0], + XTP_GLB_BIAS_INT_R_CTRL, + FIELD_PREP(XTP_GLB_BIAS_INT_R_CTRL, dp_phy->ana_bias_r)); + + regmap_update_bits(dp_phy->regmap, + pdata->off_ana_glb + regs_ana_glb[DP_PHYA_GLB_DPAUX_TX], + CKM_PT0_CKTX_IMPSEL, + FIELD_PREP(CKM_PT0_CKTX_IMPSEL, dp_phy->ana_cktx_imp)); + + for (i = 0; i < MTK_DP_PHY_MAX_LANES; i++) { + struct mtk_dp_phya_imp_sel *ana_imp = &dp_phy->ana_impsel[i]; + u32 val = FIELD_PREP(XTP_LN_TX_IMPSEL_PMOS, ana_imp->pmos) | + FIELD_PREP(XTP_LN_TX_IMPSEL_NMOS, ana_imp->nmos); + u32 off_ana_lane = pdata->off_ana_lane[i]; + + regmap_update_bits(dp_phy->regmap, + off_ana_lane + regs_ana_lane[DP_PHYA_LAN_LANE_TX_0], + XTP_LN_TX_IMPSEL_PMOS | XTP_LN_TX_IMPSEL_NMOS, val); + } +} + +static void mtk_dp_phy_set_digital_drv_params(struct mtk_dp_phy *dp_phy) { - struct mtk_dp_phy *dp_phy = phy_get_drvdata(phy); const struct mtk_dp_phy_pdata *pdata = dp_phy->pdata; const u32 reg = pdata->regs_dig_lane[DP_PHYD_LAN_DRIVING_PARAM_0]; - static const u32 driving_params[] = { - DRIVING_PARAM_0_DEFAULT, - DRIVING_PARAM_1_DEFAULT, - DRIVING_PARAM_2_DEFAULT, - DRIVING_PARAM_3_DEFAULT, - DRIVING_PARAM_4_DEFAULT, - DRIVING_PARAM_5_DEFAULT, - DRIVING_PARAM_6_DEFAULT, - DRIVING_PARAM_7_DEFAULT, - DRIVING_PARAM_8_DEFAULT - }; int i, ret; /* @@ -196,14 +288,19 @@ static int mtk_dp_phy_init(struct phy *phy) * will bulk write from DRIVING_PARAM_0 to DRIVING_PARAM_8 on * all lanes (a grand total of [9 * num_lanes] 32-bit writes) */ - for (i = 0; i < MTK_DP_PHY_MAX_LANES; i++) { - ret = regmap_bulk_write(dp_phy->regmap, - pdata->off_dig_lane[i] + reg, - driving_params, - ARRAY_SIZE(driving_params)); - if (ret) - return ret; - }; + for (i = 0; i < MTK_DP_PHY_MAX_LANES; i++) + regmap_bulk_write(dp_phy->regmap, pdata->off_dig_lane[i] + reg, + pdata->driving_params, + ARRAY_SIZE(pdata->driving_params)); +} + +static int mtk_dp_phy_init(struct phy *phy) +{ + struct mtk_dp_phy *dp_phy = phy_get_drvdata(phy); + struct device *dev = &phy->dev; + + mtk_dp_phy_set_digital_drv_params(dp_phy); + mtk_dp_phy_set_analog_calibration_params(dp_phy); return 0; } @@ -398,6 +495,107 @@ static const struct phy_ops mtk_dp_phy_dev_ops = { .owner = THIS_MODULE, }; +static void mtk_dp_phy_get_default_cal_data(struct mtk_dp_phy *dp_phy) +{ + const struct mtk_dp_phy_pdata *pdata = dp_phy->pdata; + int i; + + dp_phy->ana_bias_r = pdata->ana_bias_r; + dp_phy->ana_cktx_imp = pdata->ana_cktx_imp; + + /* Copy the default lane impedance settings to all lanes */ + for (i = 0; i < MTK_DP_PHY_MAX_LANES; i++) + memcpy(&dp_phy->ana_impsel[i], &pdata->ana_lanes_imp, + sizeof(dp_phy->ana_impsel[0])); + + return; +} + +static int mtk_dp_phy_get_one_cal_para(struct device *dev, const char *name, u8 max_val) +{ + u8 buf_byte; + u16 buf; + int ret; + + /* + * All of the calibrations are always max 8 bits long, but some may + * be split between two different 8-bits cells: handle this corner + * case by retrying reading as u16. + */ + ret = nvmem_cell_read_u8(dev, name, &buf_byte); + if (ret) + ret = nvmem_cell_read_u16(dev, name, &buf); + else + buf = buf_byte; + + if (ret) + return dev_err_probe(dev, ret, "Cannot get calibration data for %s\n", name); + + if (buf == 0) { + dev_warn(dev, "No calibration for %s. Using defaults\n", name); + return -ENOENT; + } + + if (buf > max_val) + return dev_err_probe(dev, -ERANGE, "Bad value %u retrieved for %s\n", buf, name); + + return buf; +} + +static int mtk_dp_phy_get_calibration_data(struct mtk_dp_phy *dp_phy) +{ + char mtk_dp_cal_lane_imp_name[] = "impedance-laneXM"; + struct device *dev = dp_phy->dev; + int i, ret; + + ret = mtk_dp_phy_get_one_cal_para(dev, "rbias-trim", FIELD_MAX(XTP_GLB_BIAS_INT_R_CTRL)); + if (ret < 0) + goto end; + dp_phy->ana_bias_r = ret; + + ret = mtk_dp_phy_get_one_cal_para(dev, "impedance-txclk", FIELD_MAX(CKM_PT0_CKTX_IMPSEL)); + if (ret < 0) + goto end; + dp_phy->ana_cktx_imp = ret; + + /* Get impedance params for each lane */ + for (i = 0; i < MTK_DP_PHY_MAX_LANES; i++) { + /* P-MOSFET first */ + snprintf(mtk_dp_cal_lane_imp_name, ARRAY_SIZE(mtk_dp_cal_lane_imp_name), + "impedance-lane%dp", i); + ret = mtk_dp_phy_get_one_cal_para(dev, mtk_dp_cal_lane_imp_name, + FIELD_MAX(XTP_LN_TX_IMPSEL_PMOS)); + if (ret < 0) + goto end; + dp_phy->ana_impsel[i].pmos = ret; + + /* ...and then N-MOSFET too */ + snprintf(mtk_dp_cal_lane_imp_name, ARRAY_SIZE(mtk_dp_cal_lane_imp_name), + "impedance-lane%dn", i); + ret = mtk_dp_phy_get_one_cal_para(dev, mtk_dp_cal_lane_imp_name, + FIELD_MAX(XTP_LN_TX_IMPSEL_NMOS)); + if (ret < 0) + goto end; + dp_phy->ana_impsel[i].nmos = ret; + } +end: + if (ret < 0) { + /* + * If any of the calibration values is missing, or if there + * is no calibration at all in the eFuses, copy the default + * one entirely (as partial values shall not be mixed!) + */ + if (ret == -ENOENT) { + dev_info(dev, "Using calibration default values\n"); + mtk_dp_phy_get_default_cal_data(dp_phy); + return 0; + } + return ret; + }; + + return 0; +} + static void mtk_dp_phy_legacy_remove_lookup(void *data) { struct phy *phy = data; @@ -429,6 +627,13 @@ static int mtk_dp_phy_legacy_probe(struct platform_device *pdev, struct mtk_dp_p "Failed to create DP PHY\n"); phy_set_drvdata(phy, dp_phy); + + /* + * Set default calibration data before exposing the PHY. + * For legacy probe, mtk_dp will set calibrations from eFuse, if found. + */ + mtk_dp_phy_get_default_cal_data(dp_phy); + ret = phy_create_lookup(phy, "dp", dev_name(dev)); if (ret) return ret; @@ -480,6 +685,15 @@ static int mtk_dp_phy_probe(struct platform_device *pdev) dp_phy->pdata = device_get_match_data(dev); + if (IS_REACHABLE(CONFIG_NVMEM)) { + ret = mtk_dp_phy_get_calibration_data(dp_phy); + if (ret) + return ret; + } else { + /* Use default calibration data */ + mtk_dp_phy_get_default_cal_data(dp_phy); + } + phy = devm_phy_create(dev, NULL, &mtk_dp_phy_dev_ops); if (IS_ERR(phy)) return dev_err_probe(dev, PTR_ERR(phy), @@ -495,12 +709,31 @@ static int mtk_dp_phy_probe(struct platform_device *pdev) } static const struct mtk_dp_phy_pdata mt8195_dp_phy_data = { - .off_ana_glb = 0x0, + .off_ana_glb = 0, + .off_ana_lane = (const u16[]) { 0x100, 0x200, 0x300, 0x400 }, .off_dig_glb = 0x1000, .off_dig_lane = (const u16[]) { 0x1100, 0x1200, 0x1300, 0x1400 }, .regs_ana_glb = mt8195_phy_ana_glb_regs, + .regs_ana_lane = mt8195_phy_ana_lane_regs, .regs_dig_glb = mt8195_phy_dig_glb_regs, .regs_dig_lane = mt8195_phy_dig_lane_regs, + .ana_bias_r = 15, + .ana_cktx_imp = 8, + .ana_lanes_imp = { + .pmos = 8, + .nmos = 8, + }, + .driving_params = (const u32[]) { + [0] = 0, + [1] = 0, + [2] = 0, + [3] = MT8195_DRIVING_PARAM_3_DEFAULT, + [4] = MT8195_DRIVING_PARAM_4_DEFAULT, + [5] = MT8195_DRIVING_PARAM_5_DEFAULT, + [6] = MT8195_DRIVING_PARAM_6_DEFAULT, + [7] = MT8195_DRIVING_PARAM_7_DEFAULT, + [8] = MT8195_DRIVING_PARAM_8_DEFAULT + }, }; static const struct of_device_id mtk_dp_phy_of_match[] = { From 76178442c7c5f79bcb6383e7696248e6d9a9edf2 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 10 Sep 2026 11:20:35 +0200 Subject: [PATCH 009/114] phy: phy-mtk-dp: Rewrite and document default driving param macros Use FIELD_PREP_CONST and add nicer definitions/macros to build the default driving parameters for the PHY and, while at it, also add comments explaining what they are supposed to set in the PHY. Reviewed-by: Manivannan Sadhasivam Signed-off-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260910092038.48291-10-angelogioacchino.delregno@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/mediatek/phy-mtk-dp.c | 76 +++++++++++++++---------------- 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/drivers/phy/mediatek/phy-mtk-dp.c b/drivers/phy/mediatek/phy-mtk-dp.c index 2f4cf52d922c..8e1fc4e16ba6 100644 --- a/drivers/phy/mediatek/phy-mtk-dp.c +++ b/drivers/phy/mediatek/phy-mtk-dp.c @@ -91,51 +91,47 @@ * [2,5,8]: Swing 2 Pre1 and Swing 3 Pre0 */ #define PHYD_DIG_NUM_DRV_PARA_REGS 9 -#define XTP_LN_TX_LCTXC0_SW0_PRE0_DEFAULT BIT(4) -#define XTP_LN_TX_LCTXC0_SW0_PRE1_DEFAULT (BIT(10) | BIT(12)) -#define XTP_LN_TX_LCTXC0_SW0_PRE2_DEFAULT GENMASK(20, 19) -#define XTP_LN_TX_LCTXC0_SW0_PRE3_DEFAULT GENMASK(29, 29) -#define MT8195_DRIVING_PARAM_3_DEFAULT (XTP_LN_TX_LCTXC0_SW0_PRE0_DEFAULT | \ - XTP_LN_TX_LCTXC0_SW0_PRE1_DEFAULT | \ - XTP_LN_TX_LCTXC0_SW0_PRE2_DEFAULT | \ - XTP_LN_TX_LCTXC0_SW0_PRE3_DEFAULT) +#define XTP_LN_TX_LCTXC_SW0_PRE0 GENMASK(5, 0) +#define XTP_LN_TX_LCTXC_SW0_PRE1 GENMASK(13, 8) +#define XTP_LN_TX_LCTXC_SW0_PRE2 GENMASK(21, 16) +#define XTP_LN_TX_LCTXC_SW0_PRE3 GENMASK(29, 24) -#define XTP_LN_TX_LCTXC0_SW1_PRE0_DEFAULT GENMASK(4, 3) -#define XTP_LN_TX_LCTXC0_SW1_PRE1_DEFAULT GENMASK(12, 9) -#define XTP_LN_TX_LCTXC0_SW1_PRE2_DEFAULT (BIT(18) | BIT(21)) -#define XTP_LN_TX_LCTXC0_SW2_PRE0_DEFAULT GENMASK(29, 29) -#define MT8195_DRIVING_PARAM_4_DEFAULT (XTP_LN_TX_LCTXC0_SW1_PRE0_DEFAULT | \ - XTP_LN_TX_LCTXC0_SW1_PRE1_DEFAULT | \ - XTP_LN_TX_LCTXC0_SW1_PRE2_DEFAULT | \ - XTP_LN_TX_LCTXC0_SW2_PRE0_DEFAULT) +#define XTP_LN_TX_LCTXC_SW1_PRE0 GENMASK(5, 0) +#define XTP_LN_TX_LCTXC_SW1_PRE1 GENMASK(13, 8) +#define XTP_LN_TX_LCTXC_SW1_PRE2 GENMASK(21, 16) +#define XTP_LN_TX_LCTXC_SW2_PRE0 GENMASK(29, 24) -#define XTP_LN_TX_LCTXC0_SW2_PRE1_DEFAULT (BIT(3) | BIT(5)) -#define XTP_LN_TX_LCTXC0_SW3_PRE0_DEFAULT GENMASK(13, 12) -#define MT8195_DRIVING_PARAM_5_DEFAULT (XTP_LN_TX_LCTXC0_SW2_PRE1_DEFAULT | \ - XTP_LN_TX_LCTXC0_SW3_PRE0_DEFAULT) +#define XTP_LN_TX_LCTXC_SW2_PRE1 GENMASK(5, 0) +#define XTP_LN_TX_LCTXC_SW3_PRE0 GENMASK(13, 8) -#define XTP_LN_TX_LCTXCP1_SW0_PRE0_DEFAULT 0 -#define XTP_LN_TX_LCTXCP1_SW0_PRE1_DEFAULT GENMASK(10, 10) -#define XTP_LN_TX_LCTXCP1_SW0_PRE2_DEFAULT GENMASK(19, 19) -#define XTP_LN_TX_LCTXCP1_SW0_PRE3_DEFAULT GENMASK(28, 28) -#define MT8195_DRIVING_PARAM_6_DEFAULT (XTP_LN_TX_LCTXCP1_SW0_PRE0_DEFAULT | \ - XTP_LN_TX_LCTXCP1_SW0_PRE1_DEFAULT | \ - XTP_LN_TX_LCTXCP1_SW0_PRE2_DEFAULT | \ - XTP_LN_TX_LCTXCP1_SW0_PRE3_DEFAULT) +#define BUILD_DRIVING_PARAM_0(sw0_pre0, sw0_pre1, sw0_pre2, sw0_pre3) ( \ + FIELD_PREP_CONST(XTP_LN_TX_LCTXC_SW0_PRE0, sw0_pre0) | \ + FIELD_PREP_CONST(XTP_LN_TX_LCTXC_SW0_PRE1, sw0_pre1) | \ + FIELD_PREP_CONST(XTP_LN_TX_LCTXC_SW0_PRE2, sw0_pre2) | \ + FIELD_PREP_CONST(XTP_LN_TX_LCTXC_SW0_PRE3, sw0_pre3) \ +) -#define XTP_LN_TX_LCTXCP1_SW1_PRE0_DEFAULT 0 -#define XTP_LN_TX_LCTXCP1_SW1_PRE1_DEFAULT GENMASK(10, 9) -#define XTP_LN_TX_LCTXCP1_SW1_PRE2_DEFAULT GENMASK(19, 18) -#define XTP_LN_TX_LCTXCP1_SW2_PRE0_DEFAULT 0 -#define MT8195_DRIVING_PARAM_7_DEFAULT (XTP_LN_TX_LCTXCP1_SW1_PRE0_DEFAULT | \ - XTP_LN_TX_LCTXCP1_SW1_PRE1_DEFAULT | \ - XTP_LN_TX_LCTXCP1_SW1_PRE2_DEFAULT | \ - XTP_LN_TX_LCTXCP1_SW2_PRE0_DEFAULT) +#define BUILD_DRIVING_PARAM_12(sw1_pre0, sw1_pre1, sw1_pre2, sw2_pre0) (\ + FIELD_PREP_CONST(XTP_LN_TX_LCTXC_SW1_PRE0, sw1_pre0) | \ + FIELD_PREP_CONST(XTP_LN_TX_LCTXC_SW1_PRE1, sw1_pre1) | \ + FIELD_PREP_CONST(XTP_LN_TX_LCTXC_SW1_PRE2, sw1_pre2) | \ + FIELD_PREP_CONST(XTP_LN_TX_LCTXC_SW2_PRE0, sw2_pre0) \ +) -#define XTP_LN_TX_LCTXCP1_SW2_PRE1_DEFAULT GENMASK(3, 3) -#define XTP_LN_TX_LCTXCP1_SW3_PRE0_DEFAULT 0 -#define MT8195_DRIVING_PARAM_8_DEFAULT (XTP_LN_TX_LCTXCP1_SW2_PRE1_DEFAULT | \ - XTP_LN_TX_LCTXCP1_SW3_PRE0_DEFAULT) +#define BUILD_DRIVING_PARAM_23(sw2_pre1, sw3_pre0) ( \ + FIELD_PREP_CONST(XTP_LN_TX_LCTXC_SW2_PRE1, sw2_pre1) | \ + FIELD_PREP_CONST(XTP_LN_TX_LCTXC_SW3_PRE0, sw3_pre0) \ +) + +/* MT8195: Logic State Change Point (LC TX C) */ +#define MT8195_DRIVING_PARAM_3_DEFAULT BUILD_DRIVING_PARAM_0( 16, 20, 24, 32) +#define MT8195_DRIVING_PARAM_4_DEFAULT BUILD_DRIVING_PARAM_12(24, 30, 36, 32) +#define MT8195_DRIVING_PARAM_5_DEFAULT BUILD_DRIVING_PARAM_23(40, 48) + +/* MT8195: Positive Edge (LC TX CP) */ +#define MT8195_DRIVING_PARAM_6_DEFAULT BUILD_DRIVING_PARAM_0( 0, 4, 8, 16) +#define MT8195_DRIVING_PARAM_7_DEFAULT BUILD_DRIVING_PARAM_12(0, 6, 12, 0) +#define MT8195_DRIVING_PARAM_8_DEFAULT BUILD_DRIVING_PARAM_23(8, 0) enum mtk_dp_phya_ana_glb_regidx { DP_PHYA_GLB_BIAS_GEN_0, From d265b08b7ef1236ac04aa6cb7bcff6b2f6e53a86 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 10 Sep 2026 11:20:36 +0200 Subject: [PATCH 010/114] phy: phy-mtk-dp: Add bitrate register val definitions to SoC data In preparation for adding support for the eDP PHY found in newer SoCs, transfer the bitrate register value definitions to SoC specific data. Reviewed-by: Manivannan Sadhasivam Signed-off-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260910092038.48291-11-angelogioacchino.delregno@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/mediatek/phy-mtk-dp.c | 36 +++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/drivers/phy/mediatek/phy-mtk-dp.c b/drivers/phy/mediatek/phy-mtk-dp.c index 8e1fc4e16ba6..643666c454f4 100644 --- a/drivers/phy/mediatek/phy-mtk-dp.c +++ b/drivers/phy/mediatek/phy-mtk-dp.c @@ -44,10 +44,6 @@ /* DP_PHYD_BIT_RATE */ #define PHYD_DIG_RG_BIT_RATE GENMASK(1, 0) -# define BIT_RATE_RBR 0 -# define BIT_RATE_HBR 1 -# define BIT_RATE_HBR2 2 -# define BIT_RATE_HBR3 3 /* DP_PHYD_SW_RST */ #define PHYD_DIG_GLB_SW_RST_B GENMASK(7, 0) @@ -163,6 +159,14 @@ enum mtk_dp_phyd_dig_glb_regidx { DP_PHYD_GLOBAL_MAX }; +enum mtk_dp_phyd_bit_rate_regval { + DP_PHYD_BIT_RATE_RBR, + DP_PHYD_BIT_RATE_HBR, + DP_PHYD_BIT_RATE_HBR2, + DP_PHYD_BIT_RATE_HBR3, + DP_PHYD_BIT_RATE_MAX, +}; + static const u8 mt8195_phy_ana_glb_regs[DP_PHYA_GLOBAL_MAX] = { [DP_PHYA_GLB_BIAS_GEN_0] = 0x0, [DP_PHYA_GLB_BIAS_GEN_1] = 0x4, @@ -189,6 +193,13 @@ static const u8 mt8195_phy_dig_glb_regs[DP_PHYD_GLOBAL_MAX] = { [DP_PHYD_TX_CTL_0] = 0x44, }; +static const u8 mt8195_phy_dig_bitrate_val[DP_PHYD_BIT_RATE_MAX] = { + [DP_PHYD_BIT_RATE_RBR] = 0, + [DP_PHYD_BIT_RATE_HBR] = 1, + [DP_PHYD_BIT_RATE_HBR2] = 2, + [DP_PHYD_BIT_RATE_HBR3] = 3 +}; + /** * struct mtk_dp_phya_imp_sel - Per-Lane Impedance Selection * @pmos: Impedance selection for P-Channel MOSFET @@ -209,6 +220,7 @@ struct mtk_dp_phya_imp_sel { * @regs_ana_lane: Register (layout) offsets for ana_lan * @regs_dig_glb: Register (layout) offsets for dig_glb * @regs_dig_lane: Register (layout) offsets for dig_lan + * @val_dig_bitrate:IP Version specific register values for Bit Rate setting * @ana_bias_r: Internal resistance "R" Selection Settings (global) * @ana_cktx_imp: TX Clock Impedance Selection Settings (global) * @ana_lanes_imp: TX Impedance Selection Settings (for all lanes) @@ -227,6 +239,9 @@ struct mtk_dp_phy_pdata { const u8 *regs_dig_glb; const u8 *regs_dig_lane; + /* IP-Version specific register value arrays */ + const u8 *val_dig_bitrate; + /* Calibration defaults */ u8 ana_bias_r; u8 ana_cktx_imp; @@ -310,6 +325,7 @@ static int mtk_dp_phy_configure(struct phy *phy, union phy_configure_opts *opts) if (opts->dp.set_rate) { const u32 reg_bit_rate = pdata->regs_dig_glb[DP_PHYD_BIT_RATE]; + enum mtk_dp_phyd_bit_rate_regval regval_idx; switch (opts->dp.link_rate) { default: @@ -318,19 +334,20 @@ static int mtk_dp_phy_configure(struct phy *phy, union phy_configure_opts *opts) opts->dp.link_rate); return -EINVAL; case 1620: - val = BIT_RATE_RBR; + regval_idx = DP_PHYD_BIT_RATE_RBR; break; case 2700: - val = BIT_RATE_HBR; + regval_idx = DP_PHYD_BIT_RATE_HBR; break; case 5400: - val = BIT_RATE_HBR2; + regval_idx = DP_PHYD_BIT_RATE_HBR2; break; case 8100: - val = BIT_RATE_HBR3; + regval_idx = DP_PHYD_BIT_RATE_HBR3; break; } - regmap_write(dp_phy->regmap, pdata->off_dig_glb + reg_bit_rate, val); + regmap_write(dp_phy->regmap, pdata->off_dig_glb + reg_bit_rate, + pdata->val_dig_bitrate[regval_idx]); } if (opts->dp.set_lanes) { @@ -713,6 +730,7 @@ static const struct mtk_dp_phy_pdata mt8195_dp_phy_data = { .regs_ana_lane = mt8195_phy_ana_lane_regs, .regs_dig_glb = mt8195_phy_dig_glb_regs, .regs_dig_lane = mt8195_phy_dig_lane_regs, + .val_dig_bitrate = mt8195_phy_dig_bitrate_val, .ana_bias_r = 15, .ana_cktx_imp = 8, .ana_lanes_imp = { From 18876a08da40f4c1de3a8827dbb7d5b05b08d28b Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 10 Sep 2026 11:20:37 +0200 Subject: [PATCH 011/114] phy: phy-mtk-dp: Add PHYD Lane EN register mask to SoC data In preparation for adding support for the eDP PHY found in newer SoCs, transfer the register mask for PHYD_TX_LN_EN to SoC specific data. Signed-off-by: AngeloGioacchino Del Regno Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260910092038.48291-12-angelogioacchino.delregno@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/mediatek/phy-mtk-dp.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/phy/mediatek/phy-mtk-dp.c b/drivers/phy/mediatek/phy-mtk-dp.c index 643666c454f4..30f9fa6e6b9b 100644 --- a/drivers/phy/mediatek/phy-mtk-dp.c +++ b/drivers/phy/mediatek/phy-mtk-dp.c @@ -220,6 +220,7 @@ struct mtk_dp_phya_imp_sel { * @regs_ana_lane: Register (layout) offsets for ana_lan * @regs_dig_glb: Register (layout) offsets for dig_glb * @regs_dig_lane: Register (layout) offsets for dig_lan + * @mask_dig_tx_ln: Register mask for PHYD_TX_LN_EN field * @val_dig_bitrate:IP Version specific register values for Bit Rate setting * @ana_bias_r: Internal resistance "R" Selection Settings (global) * @ana_cktx_imp: TX Clock Impedance Selection Settings (global) @@ -239,6 +240,9 @@ struct mtk_dp_phy_pdata { const u8 *regs_dig_glb; const u8 *regs_dig_lane; + /* Register masks */ + u32 mask_dig_tx_ln; + /* IP-Version specific register value arrays */ const u8 *val_dig_bitrate; @@ -355,10 +359,10 @@ static int mtk_dp_phy_configure(struct phy *phy, union phy_configure_opts *opts) val = 0; for (i = 0; i < opts->dp.lanes; i++) - val |= FIELD_PREP(PHYD_TX_LN_EN, BIT(i)); + val |= field_prep(pdata->mask_dig_tx_ln, BIT(i)); regmap_update_bits(dp_phy->regmap, pdata->off_dig_glb + reg_dig_tx_ctl, - PHYD_TX_LN_EN, val); + pdata->mask_dig_tx_ln, val); } if (opts->dp.set_voltages) { @@ -422,7 +426,7 @@ static int mtk_dp_phy_disable_all_lanes(struct mtk_dp_phy *dp_phy) /* Get mask of currently enabled lane */ regmap_read(dp_phy->regmap, pdata->off_dig_glb + regs[DP_PHYD_TX_CTL_0], &val); - val = FIELD_GET(PHYD_TX_LN_EN, val); + val = field_get(pdata->mask_dig_tx_ln, val); if (val == 0) return 0; @@ -433,7 +437,7 @@ static int mtk_dp_phy_disable_all_lanes(struct mtk_dp_phy *dp_phy) regmap_clear_bits(dp_phy->regmap, pdata->off_dig_glb + regs[DP_PHYD_TX_CTL_0], - FIELD_PREP(PHYD_TX_LN_EN, BIT(lane_num))); + field_prep(pdata->mask_dig_tx_ln, BIT(lane_num))); } while (val); return 0; @@ -730,6 +734,7 @@ static const struct mtk_dp_phy_pdata mt8195_dp_phy_data = { .regs_ana_lane = mt8195_phy_ana_lane_regs, .regs_dig_glb = mt8195_phy_dig_glb_regs, .regs_dig_lane = mt8195_phy_dig_lane_regs, + .mask_dig_tx_ln = PHYD_TX_LN_EN, .val_dig_bitrate = mt8195_phy_dig_bitrate_val, .ana_bias_r = 15, .ana_cktx_imp = 8, From aa1dd7e53b12aa3c22b2cadf3b12f01c33fca201 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 10 Sep 2026 11:20:38 +0200 Subject: [PATCH 012/114] phy: phy-mtk-dp: Add support for MT8196 eDP PHY The MT8196 SoC features an updated PHY IP compared to the older ones, and there is one that is specific to Embedded DisplayPort. Add support for the eDP PHY found in the MediaTek MT8196 SoC and all of its variants. Reviewed-by: Manivannan Sadhasivam Signed-off-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260910092038.48291-13-angelogioacchino.delregno@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/mediatek/phy-mtk-dp.c | 66 ++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/drivers/phy/mediatek/phy-mtk-dp.c b/drivers/phy/mediatek/phy-mtk-dp.c index 30f9fa6e6b9b..6d06e1c39e3b 100644 --- a/drivers/phy/mediatek/phy-mtk-dp.c +++ b/drivers/phy/mediatek/phy-mtk-dp.c @@ -5,7 +5,7 @@ * Copyright (c) 2022, BayLibre Inc. * Copyright (c) 2022, MediaTek Inc. * - * Major refactoring + * Major refactoring and new SoCs support * Copyright (c) 2026, Collabora Ltd. * AngeloGioacchino Del Regno */ @@ -43,6 +43,7 @@ #define TPLL_SSC_EN BIT(3) /* DP_PHYD_BIT_RATE */ +#define PHYD_DIG_RG_BIT_RATE_V2 GENMASK(3, 0) #define PHYD_DIG_RG_BIT_RATE GENMASK(1, 0) /* DP_PHYD_SW_RST */ @@ -63,6 +64,7 @@ /* DP_PHYD_TX_CTL_0 */ #define PHYD_TX_LN_EN GENMASK(7, 4) +#define PHYD_TX_LN_EN_V2 GENMASK(3, 0) /* DP_PHYD_DRIVING_FORCE */ #define PHYD_DP_TX_FORCE_VOLT_SWING_EN BIT(0) @@ -129,6 +131,16 @@ #define MT8195_DRIVING_PARAM_7_DEFAULT BUILD_DRIVING_PARAM_12(0, 6, 12, 0) #define MT8195_DRIVING_PARAM_8_DEFAULT BUILD_DRIVING_PARAM_23(8, 0) +/* MT8196/MT6991: Logic State Change Point (LC TX C) */ +#define MT8196_DRIVING_PARAM_3_DEFAULT BUILD_DRIVING_PARAM_0( 10, 12, 14, 17) +#define MT8196_DRIVING_PARAM_4_DEFAULT BUILD_DRIVING_PARAM_12(14, 17, 18, 18) +#define MT8196_DRIVING_PARAM_5_DEFAULT BUILD_DRIVING_PARAM_23(21, 24) + +/* MT8196/MT6991: Positive Edge (LC TX CP) */ +#define MT8196_DRIVING_PARAM_6_DEFAULT BUILD_DRIVING_PARAM_0( 0, 2, 4, 7) +#define MT8196_DRIVING_PARAM_7_DEFAULT BUILD_DRIVING_PARAM_12(0, 3, 6, 0) +#define MT8196_DRIVING_PARAM_8_DEFAULT BUILD_DRIVING_PARAM_23(3, 0) + enum mtk_dp_phya_ana_glb_regidx { DP_PHYA_GLB_BIAS_GEN_0, DP_PHYA_GLB_BIAS_GEN_1, @@ -184,6 +196,11 @@ static const u8 mt8195_phy_dig_lane_regs[DP_PHYD_LAN_MAX] = { [DP_PHYD_LAN_DRIVING_PARAM_0] = 0x2c, }; +static const u8 mt8196_phy_dig_lane_regs[DP_PHYD_LAN_MAX] = { + [DP_PHYD_LAN_DRIVING_FORCE] = 0x30, + [DP_PHYD_LAN_DRIVING_PARAM_0] = 0x34, +}; + static const u8 mt8195_phy_dig_glb_regs[DP_PHYD_GLOBAL_MAX] = { [DP_PHYD_PLL_CTL_0] = 0x10, [DP_PHYD_PLL_CTL_1] = 0x14, @@ -193,6 +210,15 @@ static const u8 mt8195_phy_dig_glb_regs[DP_PHYD_GLOBAL_MAX] = { [DP_PHYD_TX_CTL_0] = 0x44, }; +static const u8 mt8196_phy_dig_glb_regs[DP_PHYD_GLOBAL_MAX] = { + [DP_PHYD_PLL_CTL_0] = 0x10, + [DP_PHYD_PLL_CTL_1] = 0x14, + [DP_PHYD_SW_RST] = 0x38, + [DP_PHYD_BIT_RATE] = 0x3c, + [DP_PHYD_AUX_RX_CTL] = 0x40, + [DP_PHYD_TX_CTL_0] = 0x74, +}; + static const u8 mt8195_phy_dig_bitrate_val[DP_PHYD_BIT_RATE_MAX] = { [DP_PHYD_BIT_RATE_RBR] = 0, [DP_PHYD_BIT_RATE_HBR] = 1, @@ -200,6 +226,13 @@ static const u8 mt8195_phy_dig_bitrate_val[DP_PHYD_BIT_RATE_MAX] = { [DP_PHYD_BIT_RATE_HBR3] = 3 }; +static const u8 mt8196_edp_phy_dig_bitrate_val[DP_PHYD_BIT_RATE_MAX] = { + [DP_PHYD_BIT_RATE_RBR] = 1, + [DP_PHYD_BIT_RATE_HBR] = 4, + [DP_PHYD_BIT_RATE_HBR2] = 7, + [DP_PHYD_BIT_RATE_HBR3] = 9 +}; + /** * struct mtk_dp_phya_imp_sel - Per-Lane Impedance Selection * @pmos: Impedance selection for P-Channel MOSFET @@ -755,8 +788,39 @@ static const struct mtk_dp_phy_pdata mt8195_dp_phy_data = { }, }; +static const struct mtk_dp_phy_pdata mt8196_edp_phy_data = { + .off_ana_glb = 0x400, + .off_ana_lane = (const u16[]) { 0x0, 0x100, 0x200, 0x300 }, + .off_dig_glb = 0x1400, + .off_dig_lane = (const u16[]) { 0x1000, 0x1100, 0x1200, 0x1300 }, + .regs_ana_glb = mt8195_phy_ana_glb_regs, + .regs_ana_lane = mt8195_phy_ana_lane_regs, + .regs_dig_glb = mt8196_phy_dig_glb_regs, + .regs_dig_lane = mt8196_phy_dig_lane_regs, + .mask_dig_tx_ln = PHYD_TX_LN_EN_V2, + .val_dig_bitrate = mt8196_edp_phy_dig_bitrate_val, + .ana_bias_r = 15, + .ana_cktx_imp = 8, + .ana_lanes_imp = { + .pmos = 8, + .nmos = 8, + }, + .driving_params = (const u32[]) { + [0] = 0, + [1] = 0, + [2] = 0, + [3] = MT8196_DRIVING_PARAM_3_DEFAULT, + [4] = MT8196_DRIVING_PARAM_4_DEFAULT, + [5] = MT8196_DRIVING_PARAM_5_DEFAULT, + [6] = MT8196_DRIVING_PARAM_6_DEFAULT, + [7] = MT8196_DRIVING_PARAM_7_DEFAULT, + [8] = MT8196_DRIVING_PARAM_8_DEFAULT + }, +}; + static const struct of_device_id mtk_dp_phy_of_match[] = { { .compatible = "mediatek,mt8195-dp-phy", .data = &mt8195_dp_phy_data }, + { .compatible = "mediatek,mt8196-edp-phy", .data = &mt8196_edp_phy_data }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, mtk_dp_phy_of_match); From 13e39c806c189e5baf06b6311e92af09554e3275 Mon Sep 17 00:00:00 2001 From: Sushrut Shree Trivedi Date: Mon, 31 Aug 2026 12:56:01 +0530 Subject: [PATCH 013/114] dt-bindings: phy: sc8280xp-qmp-pcie: Document Shikra PCIe phy Document the compatible of the Shikra PCIe phy which supports Gen2x1. Signed-off-by: Sushrut Shree Trivedi Reviewed-by: Bartosz Golaszewski Acked-by: Manivannan Sadhasivam Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260831-shikra-upstream-v2-1-406661d75afa@oss.qualcomm.com Signed-off-by: Vinod Koul --- .../devicetree/bindings/phy/qcom,sc8280xp-qmp-pcie-phy.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-pcie-phy.yaml b/Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-pcie-phy.yaml index b353b7bfb839..d54202f6a897 100644 --- a/Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-pcie-phy.yaml +++ b/Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-pcie-phy.yaml @@ -36,6 +36,7 @@ properties: - qcom,sdm845-qmp-pcie-phy - qcom,sdx55-qmp-pcie-phy - qcom,sdx65-qmp-gen4x2-pcie-phy + - qcom,shikra-qmp-gen2x1-pcie-phy - qcom,sm8150-qmp-gen3x1-pcie-phy - qcom,sm8150-qmp-gen3x2-pcie-phy - qcom,sm8250-qmp-gen3x1-pcie-phy @@ -169,6 +170,7 @@ allOf: - qcom,sdm845-qhp-pcie-phy - qcom,sdm845-qmp-pcie-phy - qcom,sdx55-qmp-pcie-phy + - qcom,shikra-qmp-gen2x1-pcie-phy - qcom,sm8150-qmp-gen3x1-pcie-phy - qcom,sm8150-qmp-gen3x2-pcie-phy - qcom,sm8250-qmp-gen3x1-pcie-phy From a842aa4060a50c23407f7b7dd3c75215ab7324d2 Mon Sep 17 00:00:00 2001 From: Sushrut Shree Trivedi Date: Mon, 31 Aug 2026 12:56:04 +0530 Subject: [PATCH 014/114] phy: qcom: qmp-pcie: Add QMP PCIe PHY support for Shikra Add QMP PCIe PHY Gen2x1 support. Signed-off-by: Sushrut Shree Trivedi Reviewed-by: Bartosz Golaszewski Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260831-shikra-upstream-v2-4-406661d75afa@oss.qualcomm.com Signed-off-by: Vinod Koul --- drivers/phy/qualcomm/phy-qcom-qmp-pcie.c | 73 ++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-pcie.c b/drivers/phy/qualcomm/phy-qcom-qmp-pcie.c index da83358c97ea..d30194c6ed17 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp-pcie.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp-pcie.c @@ -2417,6 +2417,50 @@ static const struct qmp_phy_init_tbl sdx65_qmp_pcie_pcs_lane1_tbl[] = { QMP_PHY_INIT_CFG(QPHY_V5_20_PCS_LANE1_INSIG_MX_CTRL2, 0x00), }; +static const struct qmp_phy_init_tbl shikra_pcie_serdes_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_V2_COM_BIAS_EN_CLKBUFLR_EN, 0x18), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_CLK_ENABLE1, 0x10), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_BG_TRIM, 0xf), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_LOCK_CMP_EN, 0x1), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_VCO_TUNE_MAP, 0x0), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_VCO_TUNE_TIMER1, 0xff), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_VCO_TUNE_TIMER2, 0x1f), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_CMN_CONFIG, 0x6), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_PLL_IVCO, 0xf), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_HSCLK_SEL, 0x0), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_SVS_MODE_CLK_SEL, 0x1), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_CORE_CLK_EN, 0x20), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_CORECLK_DIV, 0xa), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_RESETSM_CNTRL, 0x20), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_BG_TIMER, 0x9), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_SYSCLK_EN_SEL, 0xa), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_DEC_START_MODE0, 0x82), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_DIV_FRAC_START3_MODE0, 0x3), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_DIV_FRAC_START2_MODE0, 0x55), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_DIV_FRAC_START1_MODE0, 0x55), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_LOCK_CMP3_MODE0, 0x0), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_LOCK_CMP2_MODE0, 0xd), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_LOCK_CMP1_MODE0, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_CLK_SELECT, 0x35), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_SYS_CLK_CTRL, 0x2), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_SYSCLK_BUF_ENABLE, 0x1f), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_CP_CTRL_MODE0, 0x4), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_PLL_RCTRL_MODE0, 0x16), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_PLL_CCTRL_MODE0, 0x30), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_INTEGLOOP_GAIN1_MODE0, 0x0), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_INTEGLOOP_GAIN0_MODE0, 0x80), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_BIAS_EN_CTRL_BY_PSM, 0x1), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_BG_TIMER, 0xa), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_SSC_EN_CENTER, 0x1), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_SSC_PER1, 0x31), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_SSC_PER2, 0x1), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_SSC_ADJ_PER1, 0x2), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_SSC_ADJ_PER2, 0x0), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_SSC_STEP_SIZE1, 0x2f), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_SSC_STEP_SIZE2, 0x19), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_CLK_EP_DIV, 0x19), +}; + static const struct qmp_phy_init_tbl sm8450_qmp_gen3_pcie_serdes_tbl[] = { QMP_PHY_INIT_CFG(QSERDES_V5_COM_SYSCLK_EN_SEL, 0x08), QMP_PHY_INIT_CFG(QSERDES_V5_COM_CLK_SELECT, 0x34), @@ -4643,6 +4687,32 @@ static const struct qmp_phy_cfg sdm845_qhp_pciephy_cfg = { .phy_status = PHYSTATUS, }; +static const struct qmp_phy_cfg shikra_pciephy_cfg = { + .lanes = 1, + + .offsets = &qmp_pcie_offsets_v2, + + .tbls = { + .serdes = shikra_pcie_serdes_tbl, + .serdes_num = ARRAY_SIZE(shikra_pcie_serdes_tbl), + .tx = qcs615_pcie_tx_tbl, + .tx_num = ARRAY_SIZE(qcs615_pcie_tx_tbl), + .rx = qcs615_pcie_rx_tbl, + .rx_num = ARRAY_SIZE(qcs615_pcie_rx_tbl), + .pcs = qcs615_pcie_pcs_tbl, + .pcs_num = ARRAY_SIZE(qcs615_pcie_pcs_tbl), + }, + .reset_list = sdm845_pciephy_reset_l, + .num_resets = ARRAY_SIZE(sdm845_pciephy_reset_l), + .vreg_list = qmp_phy_vreg_l, + .num_vregs = ARRAY_SIZE(qmp_phy_vreg_l), + .regs = pciephy_v2_regs_layout, + + .pwrdn_ctrl = SW_PWRDN | REFCLK_DRV_DSBL, + .phy_status = PHYSTATUS, +}; + + static const struct qmp_phy_cfg sm8250_qmp_gen3x1_pciephy_cfg = { .lanes = 1, @@ -6444,6 +6514,9 @@ static const struct of_device_id qmp_pcie_of_match_table[] = { }, { .compatible = "qcom,sdx65-qmp-gen4x2-pcie-phy", .data = &sdx65_qmp_pciephy_cfg, + }, { + .compatible = "qcom,shikra-qmp-gen2x1-pcie-phy", + .data = &shikra_pciephy_cfg, }, { .compatible = "qcom,sm8150-qmp-gen3x1-pcie-phy", .data = &sm8250_qmp_gen3x1_pciephy_cfg, From 8910e02719d875aaeca451ba753dcc4721617d3d Mon Sep 17 00:00:00 2001 From: Krishna Kurapati Date: Mon, 17 Aug 2026 23:16:05 -0700 Subject: [PATCH 015/114] dt-bindings: phy: qcom,m31-eusb2-phy: Document M31 eUSB2 PHY for Maili Document the M31 eUSB2 PHY for Maili which handles the USB2 path. Use fallback to indicate the compatibility of the M31 eUSB2 PHY on Maili with that on the SM8750. Signed-off-by: Krishna Kurapati Signed-off-by: Jingyi Wang Acked-by: Krzysztof Kozlowski Acked-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260817-maili-usb-v2-1-97d7317f64b3@oss.qualcomm.com Signed-off-by: Vinod Koul --- Documentation/devicetree/bindings/phy/qcom,m31-eusb2-phy.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/phy/qcom,m31-eusb2-phy.yaml b/Documentation/devicetree/bindings/phy/qcom,m31-eusb2-phy.yaml index c0e7e2963ce6..613fa5cfe9a7 100644 --- a/Documentation/devicetree/bindings/phy/qcom,m31-eusb2-phy.yaml +++ b/Documentation/devicetree/bindings/phy/qcom,m31-eusb2-phy.yaml @@ -21,6 +21,7 @@ properties: - qcom,glymur-m31-eusb2-phy - qcom,hawi-m31-eusb2-phy - qcom,kaanapali-m31-eusb2-phy + - qcom,maili-m31-eusb2-phy - const: qcom,sm8750-m31-eusb2-phy - const: qcom,sm8750-m31-eusb2-phy From d0be0c5773af1a164bf1d70d6a9d017504c372f5 Mon Sep 17 00:00:00 2001 From: Krishna Kurapati Date: Mon, 17 Aug 2026 23:16:06 -0700 Subject: [PATCH 016/114] dt-bindings: phy: qcom,sc8280xp-qmp-usb43dp-phy: Add Maili QMP Phy Document the QMP PHY for Maili which handles the USB3 path. Use fallback to indicate the compatibility of the QMP PHY on Maili with that on Hawi. Signed-off-by: Krishna Kurapati Signed-off-by: Jingyi Wang Reviewed-by: Krzysztof Kozlowski Acked-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260817-maili-usb-v2-2-97d7317f64b3@oss.qualcomm.com Signed-off-by: Vinod Koul --- .../bindings/phy/qcom,sc8280xp-qmp-usb43dp-phy.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-usb43dp-phy.yaml b/Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-usb43dp-phy.yaml index 8adfc78108fe..1c6e8e9b9a7e 100644 --- a/Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-usb43dp-phy.yaml +++ b/Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-usb43dp-phy.yaml @@ -16,6 +16,10 @@ description: properties: compatible: oneOf: + - items: + - enum: + - qcom,maili-qmp-usb3-dp-phy + - const: qcom,hawi-qmp-usb3-dp-phy - items: - enum: - qcom,eliza-qmp-usb3-dp-phy From 858635d217cde25ba3bc4e8addfe36ada7584a41 Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Thu, 10 Sep 2026 09:15:00 +0800 Subject: [PATCH 017/114] phy: qcom: qmp: Fix alignment in pcs-v8_50 register header Fix up the whitespace so all offsets align. No functional change. Signed-off-by: Shawn Guo Reviewed-by: Manivannan Sadhasivam Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260910011500.659353-1-shengchao.guo@oss.qualcomm.com Signed-off-by: Vinod Koul --- drivers/phy/qualcomm/phy-qcom-qmp-pcs-v8_50.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-pcs-v8_50.h b/drivers/phy/qualcomm/phy-qcom-qmp-pcs-v8_50.h index 325c127e8eb7..f094517ae3c1 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp-pcs-v8_50.h +++ b/drivers/phy/qualcomm/phy-qcom-qmp-pcs-v8_50.h @@ -7,7 +7,7 @@ #define QCOM_PHY_QMP_PCS_V8_50_H_ #define QPHY_V8_50_PCS_STATUS1 0x010 -#define QPHY_V8_50_PCS_START_CONTROL 0x05c -#define QPHY_V8_50_PCS_POWER_DOWN_CONTROL 0x64 +#define QPHY_V8_50_PCS_START_CONTROL 0x05c +#define QPHY_V8_50_PCS_POWER_DOWN_CONTROL 0x64 #endif From 1499f3d3407f5310731bcdf324387c321258fd75 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Fri, 20 Feb 2026 00:38:43 +0800 Subject: [PATCH 018/114] phy: hisilicon: hi3670: Fix OF node and device reference leaks hi3670_pcie_get_resources_from_pcie() leaks two references: 1) The OF node returned by of_get_child_by_name() is never released. 2) The device returned by bus_find_device_by_of_node() is never put. Fix both leaks by calling of_node_put() right after bus_find_device_by_of_node(), and put_device() after dev_get_regmap() when the temporary pcie_dev reference is no longer needed. Fixes: 73075011ffff ("phy: HiSilicon: Add driver for Kirin 970 PCIe PHY") Suggested-by: Vladimir Oltean Signed-off-by: Haotian Zhang Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260219163843.1571-1-vulab@iscas.ac.cn Signed-off-by: Vinod Koul --- drivers/phy/hisilicon/phy-hi3670-pcie.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/phy/hisilicon/phy-hi3670-pcie.c b/drivers/phy/hisilicon/phy-hi3670-pcie.c index 7396c601d874..0b2dc590aa6f 100644 --- a/drivers/phy/hisilicon/phy-hi3670-pcie.c +++ b/drivers/phy/hisilicon/phy-hi3670-pcie.c @@ -569,6 +569,7 @@ static int hi3670_pcie_get_resources_from_pcie(struct hi3670_pcie_phy *phy) } pcie_dev = bus_find_device_by_of_node(&platform_bus_type, pcie_port); + of_node_put(pcie_port); if (!pcie_dev) { dev_err(dev, "Didn't find pcie device\n"); return -ENODEV; @@ -583,6 +584,7 @@ static int hi3670_pcie_get_resources_from_pcie(struct hi3670_pcie_phy *phy) * right regmap. So, let's use the named version. */ phy->apb = dev_get_regmap(pcie_dev, "kirin_pcie_apb"); + put_device(pcie_dev); if (!phy->apb) { dev_err(dev, "Failed to get APB regmap\n"); return -ENODEV; From 83db6a55816f72a4cc20ce073d573efaf19ec0a4 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Tue, 3 Mar 2026 20:16:02 -0800 Subject: [PATCH 019/114] phy: uniphier-usb3: no devm for nvmem_cell_get There is absolutely no reason to extend the lifetime of nvmem_cell_get to after removal of the driver. Use and free right after use. Signed-off-by: Rosen Penev Reviewed-by: Manivannan Sadhasivam Reviewed-by: Kunihiko Hayashi Link: https://patch.msgid.link/20260304041602.7539-1-rosenp@gmail.com Signed-off-by: Vinod Koul --- drivers/phy/socionext/phy-uniphier-usb3hs.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/phy/socionext/phy-uniphier-usb3hs.c b/drivers/phy/socionext/phy-uniphier-usb3hs.c index 8c8673df0084..c8971eee3640 100644 --- a/drivers/phy/socionext/phy-uniphier-usb3hs.c +++ b/drivers/phy/socionext/phy-uniphier-usb3hs.c @@ -104,11 +104,12 @@ static int uniphier_u3hsphy_get_nvparam(struct uniphier_u3hsphy_priv *priv, struct nvmem_cell *cell; u8 *buf; - cell = devm_nvmem_cell_get(priv->dev, name); + cell = nvmem_cell_get(priv->dev, name); if (IS_ERR(cell)) return PTR_ERR(cell); buf = nvmem_cell_read(cell, NULL); + nvmem_cell_put(cell); if (IS_ERR(buf)) return PTR_ERR(buf); From 955e68eb3067842067b7b5395a3f8c278428802a Mon Sep 17 00:00:00 2001 From: Gregory CLEMENT Date: Fri, 6 Mar 2026 11:23:58 +0100 Subject: [PATCH 020/114] phy: cadence: Sierra: Do not modify register when getting parent clock The get_parent() callback for the PLL_CMNLC1 clock was incorrectly writing to the register while determining the parent clock index. This unintended register access forces the PHY back into training mode. If the PHY is already configured, this unexpected change prevents it from exiting training mode. Remove the register write operation to ensure the PHY remains stable during the get_parent() callback. Fixes: da08aab940092 ("phy: cadence: Sierra: Fix to get correct parent for mux clocks") Signed-off-by: Gregory CLEMENT Reviewed-by: Manivannan Sadhasivam Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260306-fix_sierra_get_parent-v2-1-cafe89ee5382@bootlin.com Signed-off-by: Vinod Koul --- drivers/phy/cadence/phy-cadence-sierra.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/drivers/phy/cadence/phy-cadence-sierra.c b/drivers/phy/cadence/phy-cadence-sierra.c index 92ab1a31646a..dbeda7f01cbb 100644 --- a/drivers/phy/cadence/phy-cadence-sierra.c +++ b/drivers/phy/cadence/phy-cadence-sierra.c @@ -698,23 +698,16 @@ static const struct phy_ops noop_ops = { static u8 cdns_sierra_pll_mux_get_parent(struct clk_hw *hw) { struct cdns_sierra_pll_mux *mux = to_cdns_sierra_pll_mux(hw); - struct regmap_field *plllc1en_field = mux->plllc1en_field; - struct regmap_field *termen_field = mux->termen_field; struct regmap_field *field = mux->pfdclk_sel_preg; unsigned int val; int index; regmap_field_read(field, &val); - if (strstr(clk_hw_get_name(hw), clk_names[CDNS_SIERRA_PLL_CMNLC1])) { + if (strstr(clk_hw_get_name(hw), clk_names[CDNS_SIERRA_PLL_CMNLC1])) index = clk_mux_val_to_index(hw, cdns_sierra_pll_mux_table[CMN_PLLLC1], 0, val); - if (index == 1) { - regmap_field_write(plllc1en_field, 1); - regmap_field_write(termen_field, 1); - } - } else { + else index = clk_mux_val_to_index(hw, cdns_sierra_pll_mux_table[CMN_PLLLC], 0, val); - } return index; } From 9e92152cd3d64a6d1e2984f632d8d70d6fc82bfd Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Wed, 9 Sep 2026 15:08:10 -0700 Subject: [PATCH 021/114] phy: apple: atc: remove stale kernel-doc for removed struct members The @sw and @mux members were never present in struct apple_atcphy, causing kernel-doc warnings. Assisted-by: opencode:big-pickle Signed-off-by: Rosen Penev Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260909220810.52310-1-rosenp@gmail.com Signed-off-by: Vinod Koul --- drivers/phy/apple/atc.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c index 4156fabad742..f09305dd76d1 100644 --- a/drivers/phy/apple/atc.c +++ b/drivers/phy/apple/atc.c @@ -585,8 +585,6 @@ struct atcphy_mode_configuration { * @phys.dp: DisplayPort PHY instance * @phy_provider: PHY provider instance * @rcdev: Reset controller device - * @sw: Type-C switch instance - * @mux: Type-C mux instance * @lock: Mutex for synchronizing register access across PHY, Type-C switch/mux and reset controller */ struct apple_atcphy { From 21cddea2dd1f1b32f0800c119d98375533ca17e4 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Mon, 15 Jun 2026 15:01:23 +0800 Subject: [PATCH 022/114] phy: broadcom: brcm-usb: unwind late probe failures brcm_usb_phy_probe() registers a PM notifier and may create a sysfs group before registering the OF PHY provider. If provider registration fails, probe returns the error with those resources still active. Track which optional resources were installed and remove them on the late provider-registration failure path. Use the same state in remove() so cleanup matches probe progress. Signed-off-by: Pengpeng Hou Reviewed-by: Manivannan Sadhasivam Reviewed-by: Justin Chen Link: https://patch.msgid.link/20260615070123.51155-1-pengpeng@iscas.ac.cn Signed-off-by: Vinod Koul --- drivers/phy/broadcom/phy-brcm-usb.c | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/drivers/phy/broadcom/phy-brcm-usb.c b/drivers/phy/broadcom/phy-brcm-usb.c index 59d756a10d6c..e606db489698 100644 --- a/drivers/phy/broadcom/phy-brcm-usb.c +++ b/drivers/phy/broadcom/phy-brcm-usb.c @@ -71,6 +71,8 @@ struct brcm_usb_phy_data { int wake_irq; struct brcm_usb_phy phys[BRCM_USB_PHY_ID_MAX]; struct notifier_block pm_notifier; + bool pm_notifier_registered; + bool sysfs_group_created; bool pm_active; }; @@ -545,7 +547,11 @@ static int brcm_usb_phy_probe(struct platform_device *pdev) return err; priv->pm_notifier.notifier_call = brcm_pm_notifier; - register_pm_notifier(&priv->pm_notifier); + err = register_pm_notifier(&priv->pm_notifier); + if (err) + dev_warn(dev, "Error registering PM notifier\n"); + else + priv->pm_notifier_registered = true; mutex_init(&priv->mutex); @@ -561,6 +567,8 @@ static int brcm_usb_phy_probe(struct platform_device *pdev) err = sysfs_create_group(&dev->kobj, &brcm_usb_phy_group); if (err) dev_warn(dev, "Error creating sysfs attributes\n"); + else + priv->sysfs_group_created = true; /* Get piarbctl syscon if it exists */ rmap = syscon_regmap_lookup_by_phandle(dev->of_node, @@ -581,16 +589,29 @@ static int brcm_usb_phy_probe(struct platform_device *pdev) clk_disable_unprepare(priv->usb_30_clk); phy_provider = devm_of_phy_provider_register(dev, brcm_usb_phy_xlate); + err = PTR_ERR_OR_ZERO(phy_provider); + if (err) + goto err_remove_debug; - return PTR_ERR_OR_ZERO(phy_provider); + return 0; + +err_remove_debug: + if (priv->sysfs_group_created) + sysfs_remove_group(&dev->kobj, &brcm_usb_phy_group); + if (priv->pm_notifier_registered) + unregister_pm_notifier(&priv->pm_notifier); + + return err; } static void brcm_usb_phy_remove(struct platform_device *pdev) { struct brcm_usb_phy_data *priv = dev_get_drvdata(&pdev->dev); - sysfs_remove_group(&pdev->dev.kobj, &brcm_usb_phy_group); - unregister_pm_notifier(&priv->pm_notifier); + if (priv->sysfs_group_created) + sysfs_remove_group(&pdev->dev.kobj, &brcm_usb_phy_group); + if (priv->pm_notifier_registered) + unregister_pm_notifier(&priv->pm_notifier); } #ifdef CONFIG_PM_SLEEP From 2ef1e78ef7223eb16d9cf20814a1b2873456f671 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Tue, 23 Jun 2026 21:53:49 +0800 Subject: [PATCH 023/114] phy: samsung: usbdrd: propagate GS101 PIPE3 lock failures The GS101 PIPE3 initialization checks the PMA PLL lock and then checks the CDR lock. The PLL helper returns an error, but the GS101 PIPE3 init path dropped that result. The CDR helper only logged a timeout. PHY initialization could therefore still return success after either the PLL or the selected receive lane had not locked. Make the in-file SoC-specific phy_init hook return an int so helpers with real failure results can propagate them through the existing PHY init paths. Keep the register-only helpers returning success and use the new return path to report the GS101 CDR wait failure. Other SoC helper paths keep their previous success behavior. Fixes: 32267c29bc7d ("phy: exynos5-usbdrd: support Exynos USBDRD 3.1 combo phy (HS & SS)") Signed-off-by: Pengpeng Hou Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260623135349.42739-1-pengpeng@iscas.ac.cn Signed-off-by: Vinod Koul --- drivers/phy/samsung/phy-exynos5-usbdrd.c | 78 +++++++++++++++++------- 1 file changed, 55 insertions(+), 23 deletions(-) diff --git a/drivers/phy/samsung/phy-exynos5-usbdrd.c b/drivers/phy/samsung/phy-exynos5-usbdrd.c index 8711a3b62c8e..087d552eb511 100644 --- a/drivers/phy/samsung/phy-exynos5-usbdrd.c +++ b/drivers/phy/samsung/phy-exynos5-usbdrd.c @@ -471,7 +471,7 @@ struct exynos5_usbdrd_phy; struct exynos5_usbdrd_phy_config { u32 id; void (*phy_isol)(struct phy_usb_instance *inst, bool isolate); - void (*phy_init)(struct exynos5_usbdrd_phy *phy_drd); + int (*phy_init)(struct exynos5_usbdrd_phy *phy_drd); unsigned int (*set_refclk)(struct phy_usb_instance *inst); }; @@ -708,7 +708,7 @@ exynos5_usbdrd_apply_phy_tunes(struct exynos5_usbdrd_phy *phy_drd, } } -static void exynos5_usbdrd_pipe3_init(struct exynos5_usbdrd_phy *phy_drd) +static int exynos5_usbdrd_pipe3_init(struct exynos5_usbdrd_phy *phy_drd) { u32 reg; @@ -721,6 +721,8 @@ static void exynos5_usbdrd_pipe3_init(struct exynos5_usbdrd_phy *phy_drd) reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST); reg &= ~PHYTEST_POWERDOWN_SSP; writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST); + + return 0; } static void @@ -831,7 +833,7 @@ exynos5_usbdrd_usbdp_g2_v4_pma_check_pll_lock(struct exynos5_usbdrd_phy *phy_drd return err; } -static void +static int exynos5_usbdrd_usbdp_g2_v4_pma_check_cdr_lock(struct exynos5_usbdrd_phy *phy_drd) { static const unsigned int timeout_us = 40000; @@ -857,9 +859,11 @@ exynos5_usbdrd_usbdp_g2_v4_pma_check_cdr_lock(struct exynos5_usbdrd_phy *phy_drd ((phy_drd->orientation == TYPEC_ORIENTATION_NORMAL) ? 0 : 2), reg); + + return err; } -static void exynos5_usbdrd_utmi_init(struct exynos5_usbdrd_phy *phy_drd) +static int exynos5_usbdrd_utmi_init(struct exynos5_usbdrd_phy *phy_drd) { u32 reg; @@ -881,6 +885,8 @@ static void exynos5_usbdrd_utmi_init(struct exynos5_usbdrd_phy *phy_drd) reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST); reg &= ~PHYTEST_POWERDOWN_HSP; writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST); + + return 0; } static int exynos5_usbdrd_phy_init(struct phy *phy) @@ -917,7 +923,9 @@ static int exynos5_usbdrd_phy_init(struct phy *phy) writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYUTMICLKSEL); /* UTMI or PIPE3 specific init */ - inst->phy_cfg->phy_init(phy_drd); + ret = inst->phy_cfg->phy_init(phy_drd); + if (ret) + goto disable_clks; /* reference clock settings */ reg = inst->phy_cfg->set_refclk(inst); @@ -940,9 +948,10 @@ static int exynos5_usbdrd_phy_init(struct phy *phy) reg &= ~PHYCLKRST_PORTRESET; writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST); +disable_clks: clk_bulk_disable_unprepare(phy_drd->drv_data->n_clks, phy_drd->clks); - return 0; + return ret; } static int exynos5_usbdrd_phy_exit(struct phy *phy) @@ -1203,7 +1212,7 @@ static void exynos7870_usbdrd_phy_isol(struct phy_usb_instance *inst, EXYNOS7870_USB2PHY_ENABLE, val); } -static void exynos7870_usbdrd_utmi_init(struct exynos5_usbdrd_phy *phy_drd) +static int exynos7870_usbdrd_utmi_init(struct exynos5_usbdrd_phy *phy_drd) { u32 reg; @@ -1291,6 +1300,8 @@ static void exynos7870_usbdrd_utmi_init(struct exynos5_usbdrd_phy *phy_drd) if (phy_drd->drv_data->phy_tunes) exynos5_usbdrd_apply_phy_tunes(phy_drd, PTS_UTMI_POSTINIT); + + return 0; } static int exynos7870_usbdrd_phy_init(struct phy *phy) @@ -1304,11 +1315,11 @@ static int exynos7870_usbdrd_phy_init(struct phy *phy) return ret; /* UTMI or PIPE3 specific init */ - inst->phy_cfg->phy_init(phy_drd); + ret = inst->phy_cfg->phy_init(phy_drd); clk_bulk_disable_unprepare(phy_drd->drv_data->n_clks, phy_drd->clks); - return 0; + return ret; } static int exynos7870_usbdrd_phy_exit(struct phy *phy) @@ -1355,10 +1366,12 @@ static const struct phy_ops exynos7870_usbdrd_phy_ops = { .owner = THIS_MODULE, }; -static void exynos2200_usbdrd_utmi_init(struct exynos5_usbdrd_phy *phy_drd) +static int exynos2200_usbdrd_utmi_init(struct exynos5_usbdrd_phy *phy_drd) { /* Configure non-Samsung IP PHY, responsible for UTMI */ phy_init(phy_drd->hs_phy); + + return 0; } static void exynos2200_usbdrd_link_init(struct exynos5_usbdrd_phy *phy_drd) @@ -1458,11 +1471,11 @@ static int exynos2200_usbdrd_phy_init(struct phy *phy) exynos2200_usbdrd_link_attach_detach_pipe3_phy(inst); /* UTMI or PIPE3 specific init */ - inst->phy_cfg->phy_init(phy_drd); + ret = inst->phy_cfg->phy_init(phy_drd); clk_bulk_disable_unprepare(phy_drd->drv_data->n_clks, phy_drd->clks); - return 0; + return ret; } static int exynos2200_usbdrd_phy_exit(struct phy *phy) @@ -1516,7 +1529,7 @@ exynos5_usbdrd_usb_v3p1_pipe_override(struct exynos5_usbdrd_phy *phy_drd) writel(reg, regs_base + EXYNOS850_DRD_SECPMACTL); } -static void exynos850_usbdrd_utmi_init(struct exynos5_usbdrd_phy *phy_drd) +static int exynos850_usbdrd_utmi_init(struct exynos5_usbdrd_phy *phy_drd) { void __iomem *regs_base = phy_drd->reg_phy; u32 reg; @@ -1622,6 +1635,8 @@ static void exynos850_usbdrd_utmi_init(struct exynos5_usbdrd_phy *phy_drd) if (ss_ports) exynos5_usbdrd_usb_v3p1_pipe_override(phy_drd); + + return 0; } static int exynos850_usbdrd_phy_init(struct phy *phy) @@ -1635,12 +1650,13 @@ static int exynos850_usbdrd_phy_init(struct phy *phy) return ret; /* UTMI or PIPE3 specific init */ + ret = 0; scoped_guard(mutex, &phy_drd->phy_mutex) - inst->phy_cfg->phy_init(phy_drd); + ret = inst->phy_cfg->phy_init(phy_drd); clk_bulk_disable_unprepare(phy_drd->drv_data->n_clks, phy_drd->clks); - return 0; + return ret; } static int exynos850_usbdrd_phy_exit(struct phy *phy) @@ -1689,11 +1705,12 @@ static const struct phy_ops exynos850_usbdrd_phy_ops = { .owner = THIS_MODULE, }; -static void exynos5_usbdrd_gs101_pipe3_init(struct exynos5_usbdrd_phy *phy_drd) +static int exynos5_usbdrd_gs101_pipe3_init(struct exynos5_usbdrd_phy *phy_drd) { void __iomem *regs_pma = phy_drd->reg_pma; void __iomem *regs_phy = phy_drd->reg_phy; u32 reg; + int ret; exynos5_usbdrd_usbdp_g2_v4_ctrl_pma_ready(phy_drd); @@ -1715,8 +1732,11 @@ static void exynos5_usbdrd_gs101_pipe3_init(struct exynos5_usbdrd_phy *phy_drd) SECPMACTL_PMA_INIT_SW_RST); writel(reg, regs_phy + EXYNOS850_DRD_SECPMACTL); - if (!exynos5_usbdrd_usbdp_g2_v4_pma_check_pll_lock(phy_drd)) - exynos5_usbdrd_usbdp_g2_v4_pma_check_cdr_lock(phy_drd); + ret = exynos5_usbdrd_usbdp_g2_v4_pma_check_pll_lock(phy_drd); + if (ret) + return ret; + + return exynos5_usbdrd_usbdp_g2_v4_pma_check_cdr_lock(phy_drd); } static int exynos5_usbdrd_gs101_phy_init(struct phy *phy) @@ -1741,7 +1761,15 @@ static int exynos5_usbdrd_gs101_phy_init(struct phy *phy) */ exynos5_usbdrd_phy_isol(inst, false); - return exynos850_usbdrd_phy_init(phy); + ret = exynos850_usbdrd_phy_init(phy); + if (ret) { + exynos5_usbdrd_phy_isol(inst, true); + if (inst->phy_cfg->id == EXYNOS5_DRDPHY_UTMI) + regulator_bulk_disable(phy_drd->drv_data->n_regulators, + phy_drd->regulators); + } + + return ret; } static int exynos5_usbdrd_gs101_phy_exit(struct phy *phy) @@ -2277,7 +2305,7 @@ exynosautov920_usb31drd_lane0_reset(struct exynos5_usbdrd_phy *phy_drd, int val) writel(reg, reg_phy + EXYNOSAUTOV920_USB31DRD_PHY_RST_CTRL); } -static void +static int exynosautov920_usb31drd_pipe3_init(struct exynos5_usbdrd_phy *phy_drd) { void __iomem *reg_phy = phy_drd->reg_phy; @@ -2335,6 +2363,8 @@ exynosautov920_usb31drd_pipe3_init(struct exynos5_usbdrd_phy *phy_drd) /* override values for level settings */ exynosautov920_usb31drd_cr_write(phy_drd, 0x22, 0x00F5); + + return 0; } static void @@ -2353,7 +2383,7 @@ exynosautov920_usb31drd_ssphy_disable(struct exynos5_usbdrd_phy *phy_drd) writel(reg, reg_phy + EXYNOSAUTOV920_USB31DRD_PHY_CONFIG7); } -static void +static int exynosautov920_usbdrd_utmi_init(struct exynos5_usbdrd_phy *phy_drd) { void __iomem *reg_phy = phy_drd->reg_phy; @@ -2457,6 +2487,8 @@ exynosautov920_usbdrd_utmi_init(struct exynos5_usbdrd_phy *phy_drd) reg = readl(reg_phy + EXYNOS2200_DRD_CLKRST); reg |= EXYNOS2200_CLKRST_LINK_PCLK_SEL; writel(reg, reg_phy + EXYNOS2200_DRD_CLKRST); + + return 0; } static void @@ -2504,11 +2536,11 @@ static int exynosautov920_usbdrd_phy_init(struct phy *phy) inst->phy_cfg->phy_isol(inst, false); /* UTMI or PIPE3 specific init */ - inst->phy_cfg->phy_init(phy_drd); + ret = inst->phy_cfg->phy_init(phy_drd); clk_bulk_disable_unprepare(phy_drd->drv_data->n_clks, phy_drd->clks); - return 0; + return ret; } static int exynosautov920_usbdrd_phy_exit(struct phy *phy) From 516764ad1b553bb54555a3c38a3a90ae12085991 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 24 Jun 2026 22:39:11 +0800 Subject: [PATCH 024/114] phy: starfive: jh7110-dphy: fail init when the PLL does not lock stf_dphy_hw_reset() polls the DPHY PLL unlock bit when bringing the PHY out of reset, but only logs a timeout. stf_dphy_init() then continues and returns success even though the PHY lock condition was not observed. Return the poll error from the reset helper and propagate it from the PHY init path. Also make the timeout message describe the failed lock condition. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260624143911.58472-1-pengpeng@iscas.ac.cn Signed-off-by: Vinod Koul --- drivers/phy/starfive/phy-jh7110-dphy-tx.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/phy/starfive/phy-jh7110-dphy-tx.c b/drivers/phy/starfive/phy-jh7110-dphy-tx.c index 181491a93807..e9ffd0f811d2 100644 --- a/drivers/phy/starfive/phy-jh7110-dphy-tx.c +++ b/drivers/phy/starfive/phy-jh7110-dphy-tx.c @@ -209,7 +209,7 @@ static u32 stf_dphy_get_config_index(u32 bitrate) return 0; } -static void stf_dphy_hw_reset(struct stf_dphy *dphy, int assert) +static int stf_dphy_hw_reset(struct stf_dphy *dphy, int assert) { int rc; u32 status = 0; @@ -224,8 +224,12 @@ static void stf_dphy_hw_reset(struct stf_dphy *dphy, int assert) !(FIELD_GET(STF_DPHY_RGS_CDTX_PLL_UNLOCK, status)), STF_DPHY_HW_DELAY_US, STF_DPHY_HW_TIMEOUT_US); if (rc) - dev_err(dphy->dev, "MIPI dphy-tx # PLL Locked\n"); + dev_err(dphy->dev, "MIPI dphy-tx PLL failed to lock\n"); + + return rc; } + + return 0; } static int stf_dphy_configure(struct phy *phy, union phy_configure_opts *opts) @@ -313,7 +317,9 @@ static int stf_dphy_init(struct phy *phy) struct stf_dphy *dphy = phy_get_drvdata(phy); int ret; - stf_dphy_hw_reset(dphy, 1); + ret = stf_dphy_hw_reset(dphy, 1); + if (ret) + return ret; writel(FIELD_PREP(STF_DPHY_SCFG_PPI_C_READY_SEL, 0) | FIELD_PREP(STF_DPHY_SCFG_DSI_TXREADY_ESC_SEL, 0), @@ -350,7 +356,9 @@ static int stf_dphy_exit(struct phy *phy) clk_disable_unprepare(dphy->txesc_clk); - stf_dphy_hw_reset(dphy, 0); + ret = stf_dphy_hw_reset(dphy, 0); + if (ret) + return ret; return 0; } From f439ca6ea3a2e9dde6378e692615e43c451ffea4 Mon Sep 17 00:00:00 2001 From: Louis-Alexis Eyraud Date: Wed, 1 Jul 2026 17:36:49 +0200 Subject: [PATCH 025/114] dt-bindings: phy: mediatek,tphy: Add support for MT8189 SoC Add a compatible string for the MediaTek MT8189 SoC, that integrates a MediaTek generic T-PHY version 3. Signed-off-by: Louis-Alexis Eyraud Acked-by: Manivannan Sadhasivam Acked-by: Conor Dooley Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260701-mt8189-dt-bindings-tphy-v1-1-5848a2be8303@collabora.com Signed-off-by: Vinod Koul --- Documentation/devicetree/bindings/phy/mediatek,tphy.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/phy/mediatek,tphy.yaml b/Documentation/devicetree/bindings/phy/mediatek,tphy.yaml index ff5c77ef1176..8b240cfd537d 100644 --- a/Documentation/devicetree/bindings/phy/mediatek,tphy.yaml +++ b/Documentation/devicetree/bindings/phy/mediatek,tphy.yaml @@ -90,6 +90,7 @@ properties: - items: - enum: - mediatek,mt8188-tphy + - mediatek,mt8189-tphy - mediatek,mt8195-tphy - const: mediatek,generic-tphy-v3 - const: mediatek,mt2701-u3phy From 477b110dd60233f0e791fda3680bb0d96a8c9eae Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Tue, 8 Sep 2026 09:54:05 +0200 Subject: [PATCH 026/114] dt-bindings: phy: qcom,qmp-usb3-dp: Extend X1E description for USB4 Some instances of the QMP combo PHY (called USB43DP) feature a third functional sub-block, responsible for USB4/Thunderbolt 3 communication. Compared to the today's state of the binding, one more clock (P2RR2P - PHY-to-Router, Router-to-PHY) needs to be enabled for the PHY to be able to switch to USB4 mode. Allow that for X1E. Also, add a bindings define to let consumers access it. Assisted-by: Claude:sonnet-5 Reviewed-by: Krzysztof Kozlowski Acked-by: Manivannan Sadhasivam Signed-off-by: Konrad Dybcio Link: https://patch.msgid.link/20260908-topic-usb4phy-v5-1-73aac69578ef@oss.qualcomm.com Signed-off-by: Vinod Koul --- .../phy/qcom,sc8280xp-qmp-usb43dp-phy.yaml | 55 +++++++++++++++---- include/dt-bindings/phy/phy-qcom-qmp.h | 1 + 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-usb43dp-phy.yaml b/Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-usb43dp-phy.yaml index 1c6e8e9b9a7e..43e90995709f 100644 --- a/Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-usb43dp-phy.yaml +++ b/Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-usb43dp-phy.yaml @@ -57,12 +57,7 @@ properties: clock-names: minItems: 4 - items: - - const: aux - - const: ref - - const: com_aux - - const: usb3_pipe - - const: cfg_ahb + maxItems: 5 power-domains: maxItems: 1 @@ -193,21 +188,59 @@ allOf: - if: properties: compatible: - enum: - - qcom,sc7180-qmp-usb3-dp-phy - - qcom,sdm845-qmp-usb3-dp-phy + contains: + enum: + - qcom,x1e80100-qmp-usb3-dp-phy then: properties: clocks: maxItems: 5 clock-names: + items: + - const: aux + - const: ref + - const: com_aux + - const: usb3_pipe + - const: p2rr2p_pipe + + - if: + properties: + compatible: + contains: + enum: + - qcom,sc7180-qmp-usb3-dp-phy + - qcom,sdm845-qmp-usb3-dp-phy + then: + properties: + clocks: maxItems: 5 - else: + clock-names: + items: + - const: aux + - const: ref + - const: com_aux + - const: usb3_pipe + - const: cfg_ahb + + - if: + not: + properties: + compatible: + contains: + enum: + - qcom,x1e80100-qmp-usb3-dp-phy + - qcom,sc7180-qmp-usb3-dp-phy + - qcom,sdm845-qmp-usb3-dp-phy + then: properties: clocks: maxItems: 4 clock-names: - maxItems: 4 + items: + - const: aux + - const: ref + - const: com_aux + - const: usb3_pipe - if: properties: diff --git a/include/dt-bindings/phy/phy-qcom-qmp.h b/include/dt-bindings/phy/phy-qcom-qmp.h index 6b43ea9e0051..1c3ce0c02b0c 100644 --- a/include/dt-bindings/phy/phy-qcom-qmp.h +++ b/include/dt-bindings/phy/phy-qcom-qmp.h @@ -16,6 +16,7 @@ /* QMP USB4-USB3-DP PHYs */ #define QMP_USB43DP_USB3_PHY 0 #define QMP_USB43DP_DP_PHY 1 +#define QMP_USB43DP_USB4_PHY 2 /* QMP PCIE PHYs */ #define QMP_PCIE_PIPE_CLK 0 From a6b889f58add38f72fbaec36e5d7ba91535d964a Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Tue, 8 Sep 2026 09:54:06 +0200 Subject: [PATCH 027/114] phy: core: Define TBT phy_mode There exist OS-accessible USB4 and Thunderbolt PHYs that need specific configuration. Define a new phy_mode for them. Currently, USB4 and TBT3 are defined as submodes, because they're quite distinct, most visibly in the electrical/analog aspects (slightly different frequencies, timings, etc.). This results in a need to make the PHY aware of the actual mode needed (at least in the Qualcomm implementation, but I believe that'll be a general need). Newer versions of TBT are basically supersets of USB4 with higher host-side requirements, so these are not defined. This can always be changed as necessary. Reviewed-by: Dmitry Baryshkov Reviewed-by: Manivannan Sadhasivam Signed-off-by: Konrad Dybcio Link: https://patch.msgid.link/20260908-topic-usb4phy-v5-2-73aac69578ef@oss.qualcomm.com Signed-off-by: Vinod Koul --- include/linux/phy/phy-thunderbolt.h | 14 ++++++++++++++ include/linux/phy/phy.h | 2 ++ 2 files changed, 16 insertions(+) create mode 100644 include/linux/phy/phy-thunderbolt.h diff --git a/include/linux/phy/phy-thunderbolt.h b/include/linux/phy/phy-thunderbolt.h new file mode 100644 index 000000000000..5f48059814e1 --- /dev/null +++ b/include/linux/phy/phy-thunderbolt.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef __PHY_TBT_H_ +#define __PHY_TBT_H_ + +enum tbt_phy_submode { + PHY_SUBMODE_TBT3, + PHY_SUBMODE_USB4, +}; + +#endif diff --git a/include/linux/phy/phy.h b/include/linux/phy/phy.h index ea47975e288a..3a2ace2cadff 100644 --- a/include/linux/phy/phy.h +++ b/include/linux/phy/phy.h @@ -20,6 +20,7 @@ #include #include #include +#include struct phy; @@ -45,6 +46,7 @@ enum phy_mode { PHY_MODE_LVDS, PHY_MODE_DP, PHY_MODE_HDMI, + PHY_MODE_TBT, }; enum phy_media { From 89563a152973900e63a7ee0e4885292a7127b3e6 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Tue, 8 Sep 2026 09:54:07 +0200 Subject: [PATCH 028/114] phy: qualcomm: qmp-combo: Add preliminary USB4 support Some Combo PHYs (so far only on SC8280XP, X1E80100 and Glymur), come in a flavor called USB43DP, which as the name implies, features USB4, USB3 and DP signal processing capabilities. In that architecture, USB3 and USB4 PHYs share the same USB_PLL while featuring separate logic spaces. The DP part is roughly the same as on the instances without USB4. The USB4 and USB3/DP operation modes of the PHY are mutually exclusive. Only one USB protocol (and flavor of pipe clock) can be active at a given moment (not to be confused with USB3 not being able to be tunneled as USB4 packets - that of course remains possible). The DP PLL is still used for clocking tunneled DP links. It may be turned off to save power when no tunnels are active, but that's left as a TODO item for now. Due to the nature of USB4, the Type-C handling happens entirely inside the Host Router, and as such the QMPPHY's mux_set() function is nullified for the period when USB4 PHY remains active. This is strictly necessary, as the Host Router driver is going to exercise manual control over the USB4 PHY's power state, which is needed by the suspend and resume flows. Failure to control that synchronously with other parts of the code results in a SoC crash by unlocked access. Because of that, a new struct phy is spawned to expose the USB4 mode, along with a .set_mode callback to allow toggling between USB4 and TBT3 submodes. Thunderbolt 3, having a number of differences vs USB4, requires a couple specific overrides, pertaining to electrical characteristics, which are easily accommodated for. Reviewed-by: Abel Vesa Reviewed-by: Dmitry Baryshkov Reviewed-by: Manivannan Sadhasivam Signed-off-by: Konrad Dybcio Link: https://patch.msgid.link/20260908-topic-usb4phy-v5-3-73aac69578ef@oss.qualcomm.com Signed-off-by: Vinod Koul --- drivers/phy/qualcomm/phy-qcom-qmp-combo.c | 402 ++++++++++++++++++---- 1 file changed, 332 insertions(+), 70 deletions(-) diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-combo.c b/drivers/phy/qualcomm/phy-qcom-qmp-combo.c index c39ced168d03..a4f130fc33e3 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp-combo.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp-combo.c @@ -22,6 +22,7 @@ #include #include #include +#include #include @@ -66,10 +67,14 @@ #define SW_USB3PHY_RESET BIT(2) /* mux to select USB3 PHY reset control, 0:HW control, 1: software reset */ #define SW_USB3PHY_RESET_MUX BIT(3) +#define SW_USB4PHY_RESET BIT(4) +#define SW_USB4PHY_RESET_MUX BIT(5) /* QPHY_V3_DP_COM_PHY_MODE_CTRL register bits */ #define USB3_MODE BIT(0) /* enables USB3 mode */ #define DP_MODE BIT(1) /* enables DP mode */ +#define USB4_MODE BIT(2) /* mutually exclusive with the above */ +#define DP_TUNNELING_CLOCK_GEN_EN BIT(3) /* QPHY_V3_DP_COM_TYPEC_CTRL register bits */ #define SW_PORTSELECT_VAL BIT(0) @@ -82,6 +87,8 @@ enum qmpphy_mode { QMPPHY_MODE_USB3DP = 0, QMPPHY_MODE_DP_ONLY, QMPPHY_MODE_USB3_ONLY, + /* USB4 QMPPHY mode refers to both USB4 and TBT3 */ + QMPPHY_MODE_USB4, }; /* set of registers with offsets different per-PHY */ @@ -94,6 +101,7 @@ enum qphy_reg_layout { QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR, QPHY_PCS_POWER_DOWN_CONTROL, QPHY_PCS_CLAMP_ENABLE, + QPHY_PCS_USB4_CLAMP_ENABLE, QPHY_COM_RESETSM_CNTRL, QPHY_COM_C_READY_STATUS, @@ -2489,6 +2497,8 @@ struct qmp_combo_offsets { u16 dp_txb; u16 dp_dp_phy; u16 aon_toggle; + u16 usb4_serdes; + u16 usb4_pcs; }; struct qmp_phy_cfg { @@ -2536,6 +2546,18 @@ struct qmp_phy_cfg { int (*calibrate_dp_phy)(struct qmp_combo *qmp); void (*dp_aux_init)(struct qmp_combo *qmp); + /* USB4 specifics */ + const struct qmp_phy_init_tbl *usb4_serdes_tbl; + int usb4_serdes_tbl_num; + const struct qmp_phy_init_tbl *usb4_serdes_tb3_ovrd_tbl; + int usb4_serdes_tb3_ovrd_num; + const struct qmp_phy_init_tbl *usb4_tx_tbl; + int usb4_tx_tbl_num; + const struct qmp_phy_init_tbl *usb4_rx_tbl; + int usb4_rx_tbl_num; + const struct qmp_phy_init_tbl *usb4_pcs_tbl; + int usb4_pcs_tbl_num; + /* resets to be requested */ const char * const *reset_list; int num_resets; @@ -2578,8 +2600,12 @@ struct qmp_combo { void __iomem *dp_tx2; void __iomem *dp_dp_phy; + void __iomem *usb4_serdes; + void __iomem *usb4_pcs; + struct clk *pipe_clk; struct clk_bulk_data *clks; + struct clk *p2rr2p_pipe_clk; int num_clks; struct reset_control_bulk_data *resets; struct regulator_bulk_data *vregs; @@ -2598,6 +2624,10 @@ struct qmp_combo { unsigned int dp_init_count; bool dp_powered_on; + struct phy *usb4_phy; + enum tbt_phy_submode usb4_phy_submode; + unsigned int usb4_init_count; + struct clk_fixed_rate pipe_clk_fixed; struct clk_hw dp_link_hw; struct clk_hw dp_pixel_hw; @@ -4006,6 +4036,9 @@ static int qmp_combo_dp_calibrate(struct phy *phy) static int qmp_combo_com_init(struct qmp_combo *qmp, bool force) { const struct qmp_phy_cfg *cfg = qmp->cfg; + u32 dp_reset_val = SW_DPPHY_RESET_MUX | SW_DPPHY_RESET; + void __iomem *pcs_aon = qmp->pcs_aon; + void __iomem *pcs = qmp->pcs; void __iomem *com = qmp->com; void __iomem *aon_toggle; int ret; @@ -4019,6 +4052,16 @@ static int qmp_combo_com_init(struct qmp_combo *qmp, bool force) else aon_toggle = qmp->pcs_aon; + if (qmp->qmpphy_mode == QMPPHY_MODE_USB4) { + pcs = qmp->usb4_pcs; + + qphy_setbits(pcs_aon, cfg->regs[QPHY_PCS_USB4_CLAMP_ENABLE], CLAMP_EN); + + /* Do not disturb the DP PLL in case there's an active DP tunnel */ + if (readl(com + QPHY_V3_DP_COM_RESET_OVRD_CTRL) & DP_TUNNELING_CLOCK_GEN_EN) + dp_reset_val &= ~SW_DPPHY_RESET_MUX; + } + ret = regulator_bulk_enable(cfg->num_vregs, qmp->vregs); if (ret) { dev_err(qmp->dev, "failed to enable regulators, err=%d\n", ret); @@ -4052,8 +4095,9 @@ static int qmp_combo_com_init(struct qmp_combo *qmp, bool force) /* override hardware control for reset of qmp phy */ qphy_setbits(com, QPHY_V3_DP_COM_RESET_OVRD_CTRL, - SW_DPPHY_RESET_MUX | SW_DPPHY_RESET | - SW_USB3PHY_RESET_MUX | SW_USB3PHY_RESET); + dp_reset_val | + SW_USB3PHY_RESET_MUX | SW_USB3PHY_RESET | + SW_USB4PHY_RESET_MUX | SW_USB4PHY_RESET); /* override hardware control for reset of qmp phy */ if (aon_toggle && cfg->regs[QPHY_AON_TOGGLE_ENABLE]) { @@ -4069,6 +4113,10 @@ static int qmp_combo_com_init(struct qmp_combo *qmp, bool force) if (cfg->invert_cc_polarity) val |= INVERT_CC_POLARITY; + /* + * Note that in USB4 mode, the router controls pin assignments instead + * and the values of the PORTSELECT registers are ignored. + */ writel(val, com + QPHY_V3_DP_COM_TYPEC_CTRL); switch (qmp->qmpphy_mode) { @@ -4096,12 +4144,24 @@ static int qmp_combo_com_init(struct qmp_combo *qmp, bool force) qphy_clrbits(com, QPHY_V3_DP_COM_RESET_OVRD_CTRL, SW_USB3PHY_RESET_MUX | SW_USB3PHY_RESET); break; + case QMPPHY_MODE_USB4: + writel(USB4_MODE, com + QPHY_V3_DP_COM_PHY_MODE_CTRL); + + /* bring both QMP USB and QMP DP PHYs PCS block out of reset */ + /* + * TODO: disable the DP PLL to save power if there are no + * active DP tunnels after router setup has concluded + */ + qphy_clrbits(com, QPHY_V3_DP_COM_RESET_OVRD_CTRL, + SW_USB4PHY_RESET_MUX | SW_USB4PHY_RESET | + SW_DPPHY_RESET_MUX | SW_DPPHY_RESET); + break; } qphy_clrbits(com, QPHY_V3_DP_COM_SWI_CTRL, 0x03); qphy_clrbits(com, QPHY_V3_DP_COM_SW_RESET, SW_RESET); - qphy_setbits(qmp->pcs, cfg->regs[QPHY_PCS_POWER_DOWN_CONTROL], + qphy_setbits(pcs, cfg->regs[QPHY_PCS_POWER_DOWN_CONTROL], SW_PWRDN); return 0; @@ -4219,6 +4279,10 @@ static int qmp_combo_usb_power_on(struct phy *phy) struct qmp_combo *qmp = phy_get_drvdata(phy); const struct qmp_phy_cfg *cfg = qmp->cfg; void __iomem *serdes = qmp->serdes; + const struct qmp_phy_init_tbl *pcs_tbl; + const struct qmp_phy_init_tbl *rx_tbl; + const struct qmp_phy_init_tbl *serdes_tbl; + const struct qmp_phy_init_tbl *tx_tbl; void __iomem *tx = qmp->tx; void __iomem *rx = qmp->rx; void __iomem *tx2 = qmp->tx2; @@ -4226,23 +4290,54 @@ static int qmp_combo_usb_power_on(struct phy *phy) void __iomem *pcs = qmp->pcs; void __iomem *pcs_usb = qmp->pcs_usb; void __iomem *status; + int pcs_tbl_num, rx_tbl_num, serdes_tbl_num, tx_tbl_num; unsigned int val; int ret; - qmp_configure(qmp->dev, serdes, cfg->serdes_tbl, cfg->serdes_tbl_num); + if (qmp->qmpphy_mode == QMPPHY_MODE_USB4) { + pcs = qmp->usb4_pcs; + serdes = qmp->usb4_serdes; + pcs_tbl = cfg->usb4_pcs_tbl; + pcs_tbl_num = cfg->usb4_pcs_tbl_num; + + serdes_tbl = cfg->usb4_serdes_tbl; + serdes_tbl_num = cfg->usb4_serdes_tbl_num; + + rx_tbl = cfg->usb4_rx_tbl; + rx_tbl_num = cfg->usb4_rx_tbl_num; + + tx_tbl = cfg->usb4_tx_tbl; + tx_tbl_num = cfg->usb4_tx_tbl_num; + } else { + pcs_tbl = cfg->pcs_tbl; + pcs_tbl_num = cfg->pcs_tbl_num; + + serdes_tbl = cfg->serdes_tbl; + serdes_tbl_num = cfg->serdes_tbl_num; + + rx_tbl = cfg->rx_tbl; + rx_tbl_num = cfg->rx_tbl_num; + + tx_tbl = cfg->tx_tbl; + tx_tbl_num = cfg->tx_tbl_num; + } + + qmp_configure(qmp->dev, serdes, serdes_tbl, serdes_tbl_num); /* Tx, Rx, and PCS configurations */ - qmp_configure_lane(qmp->dev, tx, cfg->tx_tbl, cfg->tx_tbl_num, 1); - qmp_configure_lane(qmp->dev, tx2, cfg->tx_tbl, cfg->tx_tbl_num, 2); + qmp_configure_lane(qmp->dev, tx, tx_tbl, tx_tbl_num, 1); + qmp_configure_lane(qmp->dev, tx2, tx_tbl, tx_tbl_num, 2); - qmp_configure_lane(qmp->dev, rx, cfg->rx_tbl, cfg->rx_tbl_num, 1); - qmp_configure_lane(qmp->dev, rx2, cfg->rx_tbl, cfg->rx_tbl_num, 2); + qmp_configure_lane(qmp->dev, rx, rx_tbl, rx_tbl_num, 1); + qmp_configure_lane(qmp->dev, rx2, rx_tbl, rx_tbl_num, 2); - qmp_configure(qmp->dev, pcs, cfg->pcs_tbl, cfg->pcs_tbl_num); + qmp_configure(qmp->dev, pcs, pcs_tbl, pcs_tbl_num); qmp_configure(qmp->dev, qmp->pcs_misc, cfg->pcs_misc_tbl, cfg->pcs_misc_tbl_num); - - if (pcs_usb) + if (qmp->qmpphy_mode == QMPPHY_MODE_USB4 && qmp->usb4_phy_submode == PHY_SUBMODE_TBT3) + qmp_configure(qmp->dev, serdes, cfg->usb4_serdes_tb3_ovrd_tbl, + cfg->usb4_serdes_tb3_ovrd_num); + else if (qmp->qmpphy_mode != QMPPHY_MODE_USB4 && pcs_usb) qmp_configure(qmp->dev, pcs_usb, cfg->pcs_usb_tbl, cfg->pcs_usb_tbl_num); @@ -4275,16 +4370,20 @@ static int qmp_combo_usb_power_off(struct phy *phy) { struct qmp_combo *qmp = phy_get_drvdata(phy); const struct qmp_phy_cfg *cfg = qmp->cfg; + void __iomem *pcs = qmp->pcs; + + if (qmp->usb4_init_count) + pcs = qmp->usb4_pcs; /* PHY reset */ - qphy_setbits(qmp->pcs, cfg->regs[QPHY_SW_RESET], SW_RESET); + qphy_setbits(pcs, cfg->regs[QPHY_SW_RESET], SW_RESET); /* stop SerDes and Phy-Coding-Sublayer */ - qphy_clrbits(qmp->pcs, cfg->regs[QPHY_START_CTRL], + qphy_clrbits(pcs, cfg->regs[QPHY_START_CTRL], SERDES_START | PCS_START); /* Put PHY into POWER DOWN state: active low */ - qphy_clrbits(qmp->pcs, cfg->regs[QPHY_PCS_POWER_DOWN_CONTROL], + qphy_clrbits(pcs, cfg->regs[QPHY_PCS_POWER_DOWN_CONTROL], SW_PWRDN); return 0; @@ -4295,21 +4394,27 @@ static int qmp_combo_usb_init(struct phy *phy) struct qmp_combo *qmp = phy_get_drvdata(phy); int ret; - mutex_lock(&qmp->phy_mutex); + guard(mutex)(&qmp->phy_mutex); + + /* USB4 mode takes precedence, do not reprogram the PHY in that case */ + if (qmp->usb4_init_count) { + qmp->init_count++; + qmp->usb_init_count++; + return 0; + } + ret = qmp_combo_com_init(qmp, false); if (ret) - goto out_unlock; + return ret; ret = qmp_combo_usb_power_on(phy); if (ret) { qmp_combo_com_exit(qmp, false); - goto out_unlock; + return ret; } qmp->usb_init_count++; -out_unlock: - mutex_unlock(&qmp->phy_mutex); return ret; } @@ -4318,20 +4423,26 @@ static int qmp_combo_usb_exit(struct phy *phy) struct qmp_combo *qmp = phy_get_drvdata(phy); int ret; - mutex_lock(&qmp->phy_mutex); + guard(mutex)(&qmp->phy_mutex); + + /* USB4 mode takes precedence, do not reprogram the PHY in that case */ + if (qmp->usb4_init_count) { + qmp->init_count--; + qmp->usb_init_count--; + return 0; + } + ret = qmp_combo_usb_power_off(phy); if (ret) - goto out_unlock; + return ret; ret = qmp_combo_com_exit(qmp, false); if (ret) - goto out_unlock; + return ret; qmp->usb_init_count--; -out_unlock: - mutex_unlock(&qmp->phy_mutex); - return ret; + return 0; } static int qmp_combo_usb_set_mode(struct phy *phy, enum phy_mode mode, int submode) @@ -4343,6 +4454,125 @@ static int qmp_combo_usb_set_mode(struct phy *phy, enum phy_mode mode, int submo return 0; } +static int qmp_combo_reconfigure_phy(struct qmp_combo *qmp, enum qmpphy_mode new_mode) +{ + dev_dbg(qmp->dev, "qmp_combo_reconfigure_phy: switching from qmpphy mode %d to %d\n", + qmp->qmpphy_mode, new_mode); + + if (qmp->usb_init_count || qmp->usb4_init_count) + qmp_combo_usb_power_off(qmp->usb_phy); + + if (qmp->dp_init_count) + writel(DP_PHY_PD_CTL_PSR_PWRDN, qmp->dp_dp_phy + QSERDES_DP_PHY_PD_CTL); + + qmp_combo_com_exit(qmp, true); + + qmp->qmpphy_mode = new_mode; + + /* Now everything's powered down, power up the right PHYs */ + qmp_combo_com_init(qmp, true); + + if ((qmp->usb_init_count || qmp->usb4_init_count) && + new_mode != QMPPHY_MODE_DP_ONLY) + qmp_combo_usb_power_on(qmp->usb_phy); + + if ((new_mode == QMPPHY_MODE_USB3DP || new_mode == QMPPHY_MODE_DP_ONLY) && + qmp->dp_init_count) + qmp->cfg->dp_aux_init(qmp); + + return 0; +} + +static int qmp_combo_usb4_init(struct phy *phy) +{ + struct qmp_combo *qmp = phy_get_drvdata(phy); + int ret; + + guard(mutex)(&qmp->phy_mutex); + + if (!qmp->p2rr2p_pipe_clk) { + dev_err(qmp->dev, "missing p2rr2p_pipe clock handle. Update your Device Tree.\n"); + return -EINVAL; + } + + ret = clk_prepare_enable(qmp->p2rr2p_pipe_clk); + if (ret) { + dev_err(qmp->dev, "p2rr2p_pipe enable failed: %d\n", ret); + return ret; + } + + ret = qmp_combo_com_init(qmp, false); + if (ret) { + clk_disable_unprepare(qmp->p2rr2p_pipe_clk); + return ret; + } + + /* USB4 mode takes precedence to USB3(+DP), force reconfigure the PHY */ + ret = qmp_combo_reconfigure_phy(qmp, QMPPHY_MODE_USB4); + if (ret) { + clk_disable_unprepare(qmp->p2rr2p_pipe_clk); + qmp_combo_com_exit(qmp, false); + return ret; + } + + ret = qmp_combo_usb_power_on(phy); + if (ret) { + clk_disable_unprepare(qmp->p2rr2p_pipe_clk); + qmp_combo_com_exit(qmp, false); + return ret; + } + + /* + * Due to the SoC design, the PHY only has a single valid consumer and + * preventing it from having sole ownership of the PHY's power state + * makes suspending/resuming the router impossible. + */ + WARN_ON(qmp->usb4_init_count++); + + return 0; +} + +static int qmp_combo_usb4_exit(struct phy *phy) +{ + struct qmp_combo *qmp = phy_get_drvdata(phy); + int ret; + + guard(mutex)(&qmp->phy_mutex); + + ret = qmp_combo_usb_power_off(qmp->usb_phy); + if (ret) + return ret; + + ret = qmp_combo_com_exit(qmp, false); + if (ret) + return ret; + + /* + * Mark the USB4 PHY uninitialized and wait for a mux_set event to determine the correct + * setting. This will always be possible because USB4 requires Type-C. + */ + qmp->usb4_init_count--; + + clk_disable_unprepare(qmp->p2rr2p_pipe_clk); + + return 0; +} + +static int qmp_combo_usb4_set_mode(struct phy *phy, enum phy_mode mode, int submode) +{ + struct qmp_combo *qmp = phy_get_drvdata(phy); + + if (mode != PHY_MODE_TBT) + return -EINVAL; + + if (submode != PHY_SUBMODE_USB4 && submode != PHY_SUBMODE_TBT3) + return -EINVAL; + + qmp->usb4_phy_submode = submode; + + return 0; +} + static const struct phy_ops qmp_combo_usb_phy_ops = { .init = qmp_combo_usb_init, .exit = qmp_combo_usb_exit, @@ -4350,6 +4580,13 @@ static const struct phy_ops qmp_combo_usb_phy_ops = { .owner = THIS_MODULE, }; +static const struct phy_ops qmp_combo_usb4_phy_ops = { + .init = qmp_combo_usb4_init, + .exit = qmp_combo_usb4_exit, + .set_mode = qmp_combo_usb4_set_mode, + .owner = THIS_MODULE, +}; + static const struct phy_ops qmp_combo_dp_phy_ops = { .init = qmp_combo_dp_init, .configure = qmp_combo_dp_configure, @@ -4389,9 +4626,12 @@ static void qmp_combo_enable_autonomous_mode(struct qmp_combo *qmp) * Enable i/o clamp_n for autonomous mode * V6 and later versions use pcs aon clamp register */ - if (pcs_aon) - qphy_clrbits(pcs_aon, cfg->regs[QPHY_PCS_CLAMP_ENABLE], CLAMP_EN); - else if (pcs_misc) + if (pcs_aon) { + if (qmp->qmpphy_mode == QMPPHY_MODE_USB4) + qphy_clrbits(pcs_aon, cfg->regs[QPHY_PCS_USB4_CLAMP_ENABLE], CLAMP_EN); + else + qphy_clrbits(pcs_aon, cfg->regs[QPHY_PCS_CLAMP_ENABLE], CLAMP_EN); + } else if (pcs_misc) qphy_clrbits(pcs_misc, cfg->regs[QPHY_PCS_CLAMP_ENABLE], CLAMP_EN); } @@ -4403,9 +4643,12 @@ static void qmp_combo_disable_autonomous_mode(struct qmp_combo *qmp) void __iomem *pcs_aon = qmp->pcs_aon; /* Disable i/o clamp_n on resume for normal mode */ - if (pcs_aon) - qphy_setbits(pcs_aon, cfg->regs[QPHY_PCS_CLAMP_ENABLE], CLAMP_EN); - else if (pcs_misc) + if (pcs_aon) { + if (qmp->qmpphy_mode == QMPPHY_MODE_USB4) + qphy_setbits(pcs_aon, cfg->regs[QPHY_PCS_USB4_CLAMP_ENABLE], CLAMP_EN); + else + qphy_setbits(pcs_aon, cfg->regs[QPHY_PCS_CLAMP_ENABLE], CLAMP_EN); + } else if (pcs_misc) qphy_setbits(pcs_misc, cfg->regs[QPHY_PCS_CLAMP_ENABLE], CLAMP_EN); qphy_clrbits(pcs_usb, cfg->regs[QPHY_PCS_AUTONOMOUS_MODE_CTRL], @@ -4779,24 +5022,29 @@ static int qmp_combo_typec_switch_set(struct typec_switch_dev *sw, struct qmp_combo *qmp = typec_switch_get_drvdata(sw); const struct qmp_phy_cfg *cfg = qmp->cfg; + if (qmp->qmpphy_mode == QMPPHY_MODE_USB4) { + /* QMPPHY has no orientation handling in USB4 mode, don't cache the setting */ + qmp->orientation = TYPEC_ORIENTATION_NONE; + return 0; + } + if (orientation == qmp->orientation || orientation == TYPEC_ORIENTATION_NONE) return 0; - mutex_lock(&qmp->phy_mutex); + guard(mutex)(&qmp->phy_mutex); qmp->orientation = orientation; if (qmp->init_count) { - if (qmp->usb_init_count) + if (qmp->usb_init_count || qmp->usb4_init_count) qmp_combo_usb_power_off(qmp->usb_phy); qmp_combo_com_exit(qmp, true); qmp_combo_com_init(qmp, true); - if (qmp->usb_init_count) + if (qmp->usb_init_count || qmp->usb4_init_count) qmp_combo_usb_power_on(qmp->usb_phy); if (qmp->dp_init_count) cfg->dp_aux_init(qmp); } - mutex_unlock(&qmp->phy_mutex); return 0; } @@ -4804,7 +5052,6 @@ static int qmp_combo_typec_switch_set(struct typec_switch_dev *sw, static int qmp_combo_typec_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state) { struct qmp_combo *qmp = typec_mux_get_drvdata(mux); - const struct qmp_phy_cfg *cfg = qmp->cfg; enum qmpphy_mode new_mode; unsigned int svid; @@ -4815,6 +5062,29 @@ static int qmp_combo_typec_mux_set(struct typec_mux_dev *mux, struct typec_mux_s else svid = 0; + /* + * The USB4 router driver must excercise fine-grained control over the timing of + * USB4 QMPPHY mode entry/exit, which is difficult to otherwise ensure within Linux's + * Type-C framework if the PHY acts as a self-decisive mode mux in parallel. + * + * Keep the hardware in QMPPHY_MODE_USB4 at all times between .init and .exit of the + * (Linux) USB4 PHY and wait for the router driver to turn it off at its discretion. + * Once that happens, fall back to the usual USB3/DP/Combo mode logic. + * + * After the QMPPHY has been turned off through phy_exit(usb4_phy), the next mux_set + * will initialize it in the right mode. + */ + if (qmp->usb4_init_count) + return 0; + + /* + * Explicitly ignore TBT/USB4 mode requests that may come if the USB4 PHY hasn't been + * initialized, either due to the USB4 drivers being disabled or due to this PHY instance + * lacking USB4 support. + */ + if (svid == USB_TYPEC_TBT_SID || (!state->alt && state->mode == TYPEC_MODE_USB4)) + return 0; + if (svid == USB_TYPEC_DP_SID) { switch (state->mode) { /* DP Only */ @@ -4847,41 +5117,11 @@ static int qmp_combo_typec_mux_set(struct typec_mux_dev *mux, struct typec_mux_s return 0; } - dev_dbg(qmp->dev, "typec_mux_set: switching from qmpphy mode %d to %d\n", - qmp->qmpphy_mode, new_mode); + /* The mux still receives Type-C events, even if all PHYs are uninitialized */ + if (!qmp->init_count) + return 0; - qmp->qmpphy_mode = new_mode; - - if (qmp->init_count) { - if (qmp->usb_init_count) - qmp_combo_usb_power_off(qmp->usb_phy); - - if (qmp->dp_init_count) - writel(DP_PHY_PD_CTL_PSR_PWRDN, qmp->dp_dp_phy + QSERDES_DP_PHY_PD_CTL); - - qmp_combo_com_exit(qmp, true); - - /* Now everything's powered down, power up the right PHYs */ - qmp_combo_com_init(qmp, true); - - if (new_mode == QMPPHY_MODE_DP_ONLY) { - if (qmp->usb_init_count) - qmp->usb_init_count--; - } - - if (new_mode == QMPPHY_MODE_USB3DP || new_mode == QMPPHY_MODE_USB3_ONLY) { - qmp_combo_usb_power_on(qmp->usb_phy); - if (!qmp->usb_init_count) - qmp->usb_init_count++; - } - - if (new_mode == QMPPHY_MODE_DP_ONLY || new_mode == QMPPHY_MODE_USB3DP) { - if (qmp->dp_init_count) - cfg->dp_aux_init(qmp); - } - } - - return 0; + return qmp_combo_reconfigure_phy(qmp, new_mode); } static void qmp_combo_typec_switch_unregister(void *data) @@ -5088,6 +5328,9 @@ static int qmp_combo_parse_dt(struct qmp_combo *qmp) if (offs->aon_toggle) qmp->aon_toggle = base + offs->aon_toggle; + qmp->usb4_serdes = base + offs->usb4_serdes; + qmp->usb4_pcs = base + offs->usb4_pcs; + ret = qmp_combo_clk_init(qmp); if (ret) return ret; @@ -5098,6 +5341,12 @@ static int qmp_combo_parse_dt(struct qmp_combo *qmp) "failed to get usb3_pipe clock\n"); } + qmp->p2rr2p_pipe_clk = devm_clk_get_optional(dev, "p2rr2p_pipe"); + if (IS_ERR(qmp->p2rr2p_pipe_clk)) { + return dev_err_probe(dev, PTR_ERR(qmp->p2rr2p_pipe_clk), + "failed to get p2rr2p_pipe clock\n"); + } + return 0; } @@ -5113,6 +5362,8 @@ static struct phy *qmp_combo_phy_xlate(struct device *dev, const struct of_phand return qmp->usb_phy; case QMP_USB43DP_DP_PHY: return qmp->dp_phy; + case QMP_USB43DP_USB4_PHY: + return qmp->usb4_phy ?: ERR_PTR(-EINVAL); } return ERR_PTR(-EINVAL); @@ -5325,6 +5576,17 @@ static int qmp_combo_probe(struct platform_device *pdev) phy_set_drvdata(qmp->dp_phy, qmp); + if (qmp->cfg->usb4_serdes_tbl) { + qmp->usb4_phy = devm_phy_create(dev, dev->of_node, &qmp_combo_usb4_phy_ops); + if (IS_ERR(qmp->usb4_phy)) { + ret = PTR_ERR(qmp->usb4_phy); + dev_err(dev, "failed to create USB4 PHY: %d\n", ret); + goto err_node_put; + } + + phy_set_drvdata(qmp->usb4_phy, qmp); + } + if (usb_np == dev->of_node) phy_provider = devm_of_phy_provider_register(dev, qmp_combo_phy_xlate); else From 8c474959739a953a547f8e5a0991c59ff86a9149 Mon Sep 17 00:00:00 2001 From: Sasha Levin Date: Sun, 13 Sep 2026 13:29:29 -0400 Subject: [PATCH 029/114] phy: phy-mtk-dp: Fix pdata array members initialized as pointers Building arm/arm64/i386 allmodconfig with clang fails: drivers/phy/mediatek/phy-mtk-dp.c:761:18: error: incompatible pointer to integer conversion initializing 'u16' (aka 'unsigned short') with an expression of type 'const u16[4]' [-Wint-conversion] drivers/phy/mediatek/phy-mtk-dp.c:761:18: error: initializer element is not a compile-time constant drivers/phy/mediatek/phy-mtk-dp.c:761:18: error: suggest braces around initialization of subobject [-Werror,-Wmissing-braces] 14 errors in total, one set per offending member. off_ana_lane, off_dig_lane and driving_params are declared as arrays in struct mtk_dp_phy_pdata: u16 off_ana_lane[MTK_DP_PHY_MAX_LANES]; u16 off_dig_lane[MTK_DP_PHY_MAX_LANES]; u32 driving_params[PHYD_DIG_NUM_DRV_PARA_REGS]; but the SoC data instances initialize them with compound literals. A compound literal is an object, not an initializer list, so this is not an array initializer: the member is initialized element-wise from a scalar, the first element gets the address of the literal truncated to u16/u32, and the remaining elements are zeroed. Hence the three diagnostics above - the pointer-to-integer conversion, the address not being a compile-time constant, and the missing braces. Arrays are the right type here, since both members are indexed per lane and driving_params is sized with ARRAY_SIZE(), so drop the casts and use plain braced initializers. Found by KernelCI builds of the linus-next tree. Fixes: ae859204b519 ("phy: phy-mtk-dp: Migrate register offsets to SoC specific pdata") Fixes: 7cf38eb0ab59 ("phy: phy-mtk-dp: Add support for digital and analog calibration") Fixes: 6fe4d5beac72 ("phy: phy-mtk-dp: Add support for MT8196 eDP PHY") Assisted-by: LLM Signed-off-by: Sasha Levin Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260913172930.1150324-1-sashal@kernel.org Signed-off-by: Vinod Koul --- drivers/phy/mediatek/phy-mtk-dp.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/phy/mediatek/phy-mtk-dp.c b/drivers/phy/mediatek/phy-mtk-dp.c index 6d06e1c39e3b..ae32f9db3849 100644 --- a/drivers/phy/mediatek/phy-mtk-dp.c +++ b/drivers/phy/mediatek/phy-mtk-dp.c @@ -760,9 +760,9 @@ static int mtk_dp_phy_probe(struct platform_device *pdev) static const struct mtk_dp_phy_pdata mt8195_dp_phy_data = { .off_ana_glb = 0, - .off_ana_lane = (const u16[]) { 0x100, 0x200, 0x300, 0x400 }, + .off_ana_lane = { 0x100, 0x200, 0x300, 0x400 }, .off_dig_glb = 0x1000, - .off_dig_lane = (const u16[]) { 0x1100, 0x1200, 0x1300, 0x1400 }, + .off_dig_lane = { 0x1100, 0x1200, 0x1300, 0x1400 }, .regs_ana_glb = mt8195_phy_ana_glb_regs, .regs_ana_lane = mt8195_phy_ana_lane_regs, .regs_dig_glb = mt8195_phy_dig_glb_regs, @@ -775,7 +775,7 @@ static const struct mtk_dp_phy_pdata mt8195_dp_phy_data = { .pmos = 8, .nmos = 8, }, - .driving_params = (const u32[]) { + .driving_params = { [0] = 0, [1] = 0, [2] = 0, @@ -790,9 +790,9 @@ static const struct mtk_dp_phy_pdata mt8195_dp_phy_data = { static const struct mtk_dp_phy_pdata mt8196_edp_phy_data = { .off_ana_glb = 0x400, - .off_ana_lane = (const u16[]) { 0x0, 0x100, 0x200, 0x300 }, + .off_ana_lane = { 0x0, 0x100, 0x200, 0x300 }, .off_dig_glb = 0x1400, - .off_dig_lane = (const u16[]) { 0x1000, 0x1100, 0x1200, 0x1300 }, + .off_dig_lane = { 0x1000, 0x1100, 0x1200, 0x1300 }, .regs_ana_glb = mt8195_phy_ana_glb_regs, .regs_ana_lane = mt8195_phy_ana_lane_regs, .regs_dig_glb = mt8196_phy_dig_glb_regs, @@ -805,7 +805,7 @@ static const struct mtk_dp_phy_pdata mt8196_edp_phy_data = { .pmos = 8, .nmos = 8, }, - .driving_params = (const u32[]) { + .driving_params = { [0] = 0, [1] = 0, [2] = 0, From 8bc74189b12e3d094d45eb82946c1fa17569fc93 Mon Sep 17 00:00:00 2001 From: Vinod Koul Date: Sun, 13 Sep 2026 16:24:45 +0530 Subject: [PATCH 030/114] phy: phy-mtk-dp: drop unused variables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Recent changes in driver added unused variables causing build warnings: drivers/phy/mediatek/phy-mtk-dp.c: In function ‘mtk_dp_phy_set_digital_drv_params’: drivers/phy/mediatek/phy-mtk-dp.c:332:16: error: unused variable ‘ret’ [-Werror=unused-variable] 332 | int i, ret; | ^~~ drivers/phy/mediatek/phy-mtk-dp.c: In function ‘mtk_dp_phy_init’: drivers/phy/mediatek/phy-mtk-dp.c:348:24: error: unused variable ‘dev’ [-Werror=unused-variable] 348 | struct device *dev = &phy->dev; | ^~~ drivers/phy/mediatek/phy-mtk-dp.c: In function ‘mtk_dp_phy_disable_all_lanes’: drivers/phy/mediatek/phy-mtk-dp.c:457:13: error: unused variable ‘ret’ [-Werror=unused-variable] 457 | int ret; | ^~~ Drop these to fix these Link: https://patch.msgid.link/20260913105445.211613-1-vkoul@kernel.org Signed-off-by: Vinod Koul --- drivers/phy/mediatek/phy-mtk-dp.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/phy/mediatek/phy-mtk-dp.c b/drivers/phy/mediatek/phy-mtk-dp.c index ae32f9db3849..dbef4a4bd60a 100644 --- a/drivers/phy/mediatek/phy-mtk-dp.c +++ b/drivers/phy/mediatek/phy-mtk-dp.c @@ -329,7 +329,7 @@ static void mtk_dp_phy_set_digital_drv_params(struct mtk_dp_phy *dp_phy) { const struct mtk_dp_phy_pdata *pdata = dp_phy->pdata; const u32 reg = pdata->regs_dig_lane[DP_PHYD_LAN_DRIVING_PARAM_0]; - int i, ret; + int i; /* * Assume that all lanes need the same driving parameters: this @@ -345,7 +345,6 @@ static void mtk_dp_phy_set_digital_drv_params(struct mtk_dp_phy *dp_phy) static int mtk_dp_phy_init(struct phy *phy) { struct mtk_dp_phy *dp_phy = phy_get_drvdata(phy); - struct device *dev = &phy->dev; mtk_dp_phy_set_digital_drv_params(dp_phy); mtk_dp_phy_set_analog_calibration_params(dp_phy); @@ -454,7 +453,6 @@ static int mtk_dp_phy_disable_all_lanes(struct mtk_dp_phy *dp_phy) { const struct mtk_dp_phy_pdata *pdata = dp_phy->pdata; const u8 *regs = pdata->regs_dig_glb; - int ret; u32 val; /* Get mask of currently enabled lane */ From 3b0140f37d25b99cf4c881ce40bafd02d9781c5b Mon Sep 17 00:00:00 2001 From: Bryan O'Donoghue Date: Thu, 17 Sep 2026 00:58:38 +0100 Subject: [PATCH 031/114] dt-bindings: phy: qcom: Add CSI2 C-PHY/DPHY schema Add a base schema for the MIPI CSI2 PHYs on Qualcomm SoCs. This PHY supports both DPHY and CPHY operation. A special mode of DPHY operation - called variously split-mode or combo-mode also allows for two sensors to be connected to one PHY. The schema supports both the D-PHY and C-PHY bus types. Suggested-by: Vladimir Zapolskiy Signed-off-by: Bryan O'Donoghue Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260917-x1e-csi2-phy-v18-1-6515b5255fa9@linaro.org Signed-off-by: Vinod Koul --- .../bindings/phy/qcom,x1e80100-csi2-phy.yaml | 209 ++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 Documentation/devicetree/bindings/phy/qcom,x1e80100-csi2-phy.yaml diff --git a/Documentation/devicetree/bindings/phy/qcom,x1e80100-csi2-phy.yaml b/Documentation/devicetree/bindings/phy/qcom,x1e80100-csi2-phy.yaml new file mode 100644 index 000000000000..dc64af97da6c --- /dev/null +++ b/Documentation/devicetree/bindings/phy/qcom,x1e80100-csi2-phy.yaml @@ -0,0 +1,209 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/phy/qcom,x1e80100-csi2-phy.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm X1E80100 SoC CSI2 PHY + +maintainers: + - Bryan O'Donoghue + +description: + Qualcomm MIPI CSI2 C-PHY/D-PHY combination PHY. Connects MIPI CSI2 sensors + to Qualcomm's Camera CSI Decoder. The PHY supports both C-PHY and D-PHY + modes. + +properties: + compatible: + const: qcom,x1e80100-csi2-phy + + reg: + maxItems: 1 + + clocks: + maxItems: 3 + + clock-names: + items: + - const: core + - const: timer + - const: ahb + + interrupts: + maxItems: 1 + + operating-points-v2: true + + opp-table: + type: object + + power-domains: + items: + - description: Titan Top GDSC - Titan ISP Block, Global Distributed Switch Controller. + - description: MMCX voltage rail + - description: MXC or MXA voltage rail + + power-domain-names: + items: + - const: top + - const: mmcx + - const: mx + + vdda-0p9-supply: + description: Phandle to a 0.9V regulator supply to a PHY. + + vdda-1p2-supply: + description: Phandle to 1.2V regulator supply to a PHY. + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/$defs/port-base + description: + Sensor input. Always present. A single sensor is described by a + single endpoint with one to four data lanes, numbered 1-4. The + clock lane is position 0. + + DPHY split mode, where two independent sensors share the PHY, is + described by two endpoints. endpoint@0 uses data lanes 1 or 1-2, + clock lane position 0; endpoint@1 uses data lane 3, clock lane + position 4. + + unevaluatedProperties: false + + patternProperties: + "^endpoint(@[01])?$": + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + properties: + bus-type: + enum: + - 1 # MEDIA_BUS_TYPE_CSI2_CPHY + - 4 # MEDIA_BUS_TYPE_CSI2_DPHY + default: 4 + + data-lanes: + minItems: 1 + maxItems: 4 + items: + minimum: 1 + maximum: 4 + description: + Data lane positions following this endpoint's clock + lane. Positions are a per-port logical numbering, not + package pad or ball positions. + + required: + - data-lanes + - remote-endpoint + + allOf: + - if: + required: + - endpoint@1 + then: + properties: + endpoint@0: + properties: + data-lanes: + maxItems: 2 + items: + maximum: 2 + endpoint@1: + properties: + data-lanes: + items: + - const: 3 + required: + - endpoint@0 + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: Output to the CAMSS CSID controller. + + required: + - port@0 + - port@1 + +required: + - compatible + - reg + - clocks + - clock-names + - interrupts + - operating-points-v2 + - power-domains + - power-domain-names + - vdda-0p9-supply + - vdda-1p2-supply + - ports + +additionalProperties: false + +examples: + - | + #include + #include + #include + #include + #include + + phy@ace4000 { + compatible = "qcom,x1e80100-csi2-phy"; + reg = <0x0ace4000 0x2000>; + + clocks = <&camcc CAM_CC_CSIPHY0_CLK>, + <&camcc CAM_CC_CSI0PHYTIMER_CLK>, + <&camcc CAM_CC_CORE_AHB_CLK>; + clock-names = "core", + "timer", + "ahb"; + + interrupts = ; + + operating-points-v2 = <&csiphy_opp_table>; + + power-domains = <&camcc CAM_CC_TITAN_TOP_GDSC>, + <&rpmhpd RPMHPD_MMCX>, + <&rpmhpd RPMHPD_MX>; + power-domain-names = "top", + "mmcx", + "mx"; + + vdda-0p9-supply = <&vreg_l2c_0p9>; + vdda-1p2-supply = <&vreg_l1c_1p2>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + csiphy0_in: endpoint { + bus-type = ; + data-lanes = <1 2 3 4>; + remote-endpoint = <&sensor_out>; + }; + }; + + port@1 { + reg = <1>; + csiphy0_out: endpoint { + remote-endpoint = <&csid_in>; + }; + }; + }; + + csiphy_opp_table: opp-table { + compatible = "operating-points-v2"; + + opp-300000000 { + opp-hz = /bits/ 64 <300000000>; + required-opps = <&rpmhpd_opp_low_svs_d1>, + <&rpmhpd_opp_low_svs_d1>; + }; + }; + }; From 772ff99404a101e8d216f869c606cbd0d1d37752 Mon Sep 17 00:00:00 2001 From: Bryan O'Donoghue Date: Thu, 17 Sep 2026 00:58:39 +0100 Subject: [PATCH 032/114] phy: qcom-mipi-csi2: Add a CSI2 MIPI DPHY driver Add a new MIPI CSI2 driver in DPHY mode initially. The entire set of existing CAMSS CSI PHY init sequences are imported in order to save time and effort in later patches. The following devices are supported in this drop: "qcom,x1e80100-csi2-phy" In-line with other PHY drivers the process node is included in the name. Data-lane and clock lane positioning and polarity selection via newly amended struct phy_configure_opts_mipi_dphy{} is supported. The Qualcomm 3PH class of PHYs can do both DPHY and CPHY mode. For now only DPHY is supported. In porting some of the logic over from camss-csiphy*.c to here its also possible to rationalise some of the code. In particular use of regulator_bulk and clk_bulk as well as dropping the seemingly useless and unused interrupt handler. The PHY sequences and a lot of the logic that goes with them are well proven in CAMSS and mature so the main thing to watch out for here is how to get the right sequencing of regulators, clocks and register-writes. The register init sequence table is imported verbatim from the existing CAMSS csiphy driver. A follow-up series will rework the table to extract the repetitive per-lane pattern into a loop. Signed-off-by: Bryan O'Donoghue Reviewed-by: Manivannan Sadhasivam Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260917-x1e-csi2-phy-v18-2-6515b5255fa9@linaro.org Signed-off-by: Vinod Koul --- MAINTAINERS | 10 + drivers/phy/qualcomm/Kconfig | 15 + drivers/phy/qualcomm/Makefile | 5 + .../qualcomm/phy-qcom-mipi-csi2-3ph-dphy.c | 385 +++++++++++++++ .../phy/qualcomm/phy-qcom-mipi-csi2-core.c | 463 ++++++++++++++++++ drivers/phy/qualcomm/phy-qcom-mipi-csi2.h | 97 ++++ 6 files changed, 975 insertions(+) create mode 100644 drivers/phy/qualcomm/phy-qcom-mipi-csi2-3ph-dphy.c create mode 100644 drivers/phy/qualcomm/phy-qcom-mipi-csi2-core.c create mode 100644 drivers/phy/qualcomm/phy-qcom-mipi-csi2.h diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..881b4e92bb18 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22542,6 +22542,16 @@ S: Maintained F: Documentation/devicetree/bindings/media/qcom,*-iris.yaml F: drivers/media/platform/qcom/iris/ +QUALCOMM MIPI CSI2 PHY DRIVER +M: Bryan O'Donoghue +L: linux-phy@lists.infradead.org +L: linux-media@vger.kernel.org +L: linux-arm-msm@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/phy/qcom,*-csi2-phy.yaml +F: drivers/phy/qualcomm/phy-qcom-mipi-csi2*.c +F: drivers/phy/qualcomm/phy-qcom-mipi-csi2*.h + QUALCOMM NAND CONTROLLER DRIVER M: Manivannan Sadhasivam L: linux-mtd@lists.infradead.org diff --git a/drivers/phy/qualcomm/Kconfig b/drivers/phy/qualcomm/Kconfig index d910a5d1a1ac..d802e63ad74a 100644 --- a/drivers/phy/qualcomm/Kconfig +++ b/drivers/phy/qualcomm/Kconfig @@ -28,6 +28,21 @@ config PHY_QCOM_EDP Enable this driver to support the Qualcomm eDP PHY found in various Qualcomm chipsets. +config PHY_QCOM_MIPI_CSI2 + tristate "Qualcomm MIPI CSI2 PHY driver" + depends on ARCH_QCOM || COMPILE_TEST + depends on OF + depends on PM + depends on PM_OPP + depends on COMMON_CLK + select GENERIC_PHY + select GENERIC_PHY_MIPI_DPHY + help + Enable this to support the MIPI CSI2 PHY driver found in various + Qualcomm chipsets. This PHY is used to connect MIPI CSI2 + camera sensors to the CSI Decoder in the Qualcomm Camera Subsystem + CAMSS. + config PHY_QCOM_IPQ4019_USB tristate "Qualcomm IPQ4019 USB PHY driver" depends on OF && (ARCH_QCOM || COMPILE_TEST) diff --git a/drivers/phy/qualcomm/Makefile b/drivers/phy/qualcomm/Makefile index 8bf887d58ee4..4b5e6640a6fa 100644 --- a/drivers/phy/qualcomm/Makefile +++ b/drivers/phy/qualcomm/Makefile @@ -6,6 +6,11 @@ obj-$(CONFIG_PHY_QCOM_IPQ4019_USB) += phy-qcom-ipq4019-usb.o obj-$(CONFIG_PHY_QCOM_IPQ806X_SATA) += phy-qcom-ipq806x-sata.o obj-$(CONFIG_PHY_QCOM_M31_USB) += phy-qcom-m31.o obj-$(CONFIG_PHY_QCOM_M31_EUSB) += phy-qcom-m31-eusb2.o + +phy-qcom-mipi-csi2-objs += phy-qcom-mipi-csi2-core.o \ + phy-qcom-mipi-csi2-3ph-dphy.o +obj-$(CONFIG_PHY_QCOM_MIPI_CSI2) += phy-qcom-mipi-csi2.o + obj-$(CONFIG_PHY_QCOM_PCIE2) += phy-qcom-pcie2.o obj-$(CONFIG_PHY_QCOM_QMP_COMBO) += phy-qcom-qmp-combo.o phy-qcom-qmp-usbc.o diff --git a/drivers/phy/qualcomm/phy-qcom-mipi-csi2-3ph-dphy.c b/drivers/phy/qualcomm/phy-qcom-mipi-csi2-3ph-dphy.c new file mode 100644 index 000000000000..966d79c98f9b --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-mipi-csi2-3ph-dphy.c @@ -0,0 +1,385 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Qualcomm MSM Camera Subsystem - CSIPHY Module 3phase v1.0 + * + * Copyright (c) 2011-2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2016-2026 Linaro Ltd. + */ + +#include +#include +#include +#include + +#include "phy-qcom-mipi-csi2.h" + +#define CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(offset, n) ((offset) + 0x4 * (n)) +#define CSIPHY_3PH_CMN_CSI_COMMON_CTRL0_PHY_SW_RESET BIT(0) +#define CSIPHY_3PH_CMN_CSI_COMMON_CTRL5_CLK_ENABLE BIT(7) +#define CSIPHY_3PH_CMN_CSI_COMMON_CTRL6_COMMON_PWRDN_B BIT(0) +#define CSIPHY_3PH_CMN_CSI_COMMON_CTRL6_SHOW_REV_ID BIT(1) +#define CSIPHY_3PH_CMN_CSI_COMMON_CTRL10_IRQ_CLEAR_CMD BIT(0) +#define CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(offset, n) ((offset) + 0xb0 + 0x4 * (n)) + +#define CSIPHY_2PH_LN_CSI_2PHASE_CTRL9n(n) ((0x200 * (n)) + 0x24) + +/* + * 3 phase CSI has 19 common status regs with only 0-10 being used + * and 11-18 being reserved. + */ +#define CSI_COMMON_STATUS_NUM 11 +/* + * There are a number of common control registers + * The offset to clear the CSIPHY IRQ status starts @ 22 + * So to clear CSI_COMMON_STATUS0 this is CSI_COMMON_CONTROL22, STATUS1 is + * CONTROL23 and so on + */ +#define CSI_CTRL_STATUS_INDEX 22 + +/* + * There are 43 COMMON_CTRL registers with regs after # 33 being reserved + */ +#define CSI_CTRL_MAX 33 + +#define CSIPHY_DEFAULT_PARAMS 0 +#define CSIPHY_SETTLE_CNT_LOWER_BYTE 2 +#define CSIPHY_SKEW_CAL 7 + +/* 4nm 2PH v 2.1.2 2p5Gbps 4 lane DPHY mode */ +static const struct +mipi_csi2phy_lane_regs lane_regs_x1e80100[] = { + /* Power up lanes 2ph mode */ + {.reg_addr = 0x101c, .reg_data = 0x7a, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x1018, .reg_data = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS}, + + {.reg_addr = 0x0094, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x00a0, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0090, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0098, .reg_data = 0x08, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0094, .reg_data = 0x07, .delay_us = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0030, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0000, .reg_data = 0x8e, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0038, .reg_data = 0xfe, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x002c, .reg_data = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0034, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x001c, .reg_data = 0x0a, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0014, .reg_data = 0x60, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x003c, .reg_data = 0xb8, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0004, .reg_data = 0x0c, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0020, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0008, .reg_data = 0x10, .param_type = CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {.reg_addr = 0x0010, .reg_data = 0x52, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0094, .reg_data = 0xd7, .param_type = CSIPHY_SKEW_CAL}, + {.reg_addr = 0x005c, .reg_data = 0x00, .param_type = CSIPHY_SKEW_CAL}, + {.reg_addr = 0x0060, .reg_data = 0xbd, .param_type = CSIPHY_SKEW_CAL}, + {.reg_addr = 0x0064, .reg_data = 0x7f, .param_type = CSIPHY_SKEW_CAL}, + + {.reg_addr = 0x0e94, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0ea0, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e90, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e98, .reg_data = 0x08, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e94, .reg_data = 0x07, .delay_us = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e30, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e28, .reg_data = 0x04, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e00, .reg_data = 0x80, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e0c, .reg_data = 0xff, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e38, .reg_data = 0x1f, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e2c, .reg_data = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e34, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e1c, .reg_data = 0x0a, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e14, .reg_data = 0x60, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e3c, .reg_data = 0xb8, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e04, .reg_data = 0x0c, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e20, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e08, .reg_data = 0x10, .param_type = CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {.reg_addr = 0x0e10, .reg_data = 0x52, .param_type = CSIPHY_DEFAULT_PARAMS}, + + {.reg_addr = 0x0494, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x04a0, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0490, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0498, .reg_data = 0x08, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0494, .reg_data = 0x07, .delay_us = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0430, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0400, .reg_data = 0x8e, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0438, .reg_data = 0xfe, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x042c, .reg_data = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0434, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x041c, .reg_data = 0x0a, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0414, .reg_data = 0x60, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x043c, .reg_data = 0xb8, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0404, .reg_data = 0x0c, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0420, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0408, .reg_data = 0x10, .param_type = CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {.reg_addr = 0x0410, .reg_data = 0x52, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0494, .reg_data = 0xd7, .param_type = CSIPHY_SKEW_CAL}, + {.reg_addr = 0x045c, .reg_data = 0x00, .param_type = CSIPHY_SKEW_CAL}, + {.reg_addr = 0x0460, .reg_data = 0xbd, .param_type = CSIPHY_SKEW_CAL}, + {.reg_addr = 0x0464, .reg_data = 0x7f, .param_type = CSIPHY_SKEW_CAL}, + + {.reg_addr = 0x0894, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x08a0, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0890, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0898, .reg_data = 0x08, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0894, .reg_data = 0x07, .delay_us = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0830, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0800, .reg_data = 0x8e, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0838, .reg_data = 0xfe, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x082c, .reg_data = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0834, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x081c, .reg_data = 0x0a, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0814, .reg_data = 0x60, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x083c, .reg_data = 0xb8, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0804, .reg_data = 0x0c, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0820, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0808, .reg_data = 0x10, .param_type = CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {.reg_addr = 0x0810, .reg_data = 0x52, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0894, .reg_data = 0xd7, .param_type = CSIPHY_SKEW_CAL}, + {.reg_addr = 0x085c, .reg_data = 0x00, .param_type = CSIPHY_SKEW_CAL}, + {.reg_addr = 0x0860, .reg_data = 0xbd, .param_type = CSIPHY_SKEW_CAL}, + {.reg_addr = 0x0864, .reg_data = 0x7f, .param_type = CSIPHY_SKEW_CAL}, + + {.reg_addr = 0x0c94, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0ca0, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c90, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c98, .reg_data = 0x08, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c94, .reg_data = 0x07, .delay_us = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c30, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c00, .reg_data = 0x8e, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c38, .reg_data = 0xfe, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c2c, .reg_data = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c34, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c1c, .reg_data = 0x0a, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c14, .reg_data = 0x60, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c3c, .reg_data = 0xb8, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c04, .reg_data = 0x0c, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c20, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c08, .reg_data = 0x10, .param_type = CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {.reg_addr = 0x0c10, .reg_data = 0x52, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c94, .reg_data = 0xd7, .param_type = CSIPHY_SKEW_CAL}, + {.reg_addr = 0x0c5c, .reg_data = 0x00, .param_type = CSIPHY_SKEW_CAL}, + {.reg_addr = 0x0c60, .reg_data = 0xbd, .param_type = CSIPHY_SKEW_CAL}, + {.reg_addr = 0x0c64, .reg_data = 0x7f, .param_type = CSIPHY_SKEW_CAL}, +}; + +static inline const struct mipi_csi2phy_device_regs * +csi2phy_dev_to_regs(struct mipi_csi2phy_device *csi2phy) +{ + return &csi2phy->soc_cfg->reg_info; +} + +static void phy_qcom_mipi_csi2_hw_version_read(struct mipi_csi2phy_device *csi2phy) +{ + const struct mipi_csi2phy_device_regs *regs = csi2phy_dev_to_regs(csi2phy); + u32 tmp; + + writel(CSIPHY_3PH_CMN_CSI_COMMON_CTRL6_SHOW_REV_ID, csi2phy->base + + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 6)); + + tmp = readl_relaxed(csi2phy->base + + CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(regs->common_regs_offset, 12)); + csi2phy->hw_version = tmp; + + tmp = readl_relaxed(csi2phy->base + + CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(regs->common_regs_offset, 13)); + csi2phy->hw_version |= (tmp << 8) & 0xFF00; + + tmp = readl_relaxed(csi2phy->base + + CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(regs->common_regs_offset, 14)); + csi2phy->hw_version |= (tmp << 16) & 0xFF0000; + + tmp = readl_relaxed(csi2phy->base + + CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(regs->common_regs_offset, 15)); + csi2phy->hw_version |= (tmp << 24) & 0xFF000000; + + dev_dbg_once(csi2phy->dev, "CSIPHY 3PH HW Version = 0x%08x\n", csi2phy->hw_version); +} + +/* + * phy_qcom_mipi_csi2_reset - Perform software reset on CSIPHY module + * @phy_qcom_mipi_csi2: CSIPHY device + */ +static void phy_qcom_mipi_csi2_reset(struct mipi_csi2phy_device *csi2phy) +{ + const struct mipi_csi2phy_device_regs *regs = csi2phy_dev_to_regs(csi2phy); + + writel(CSIPHY_3PH_CMN_CSI_COMMON_CTRL0_PHY_SW_RESET, + csi2phy->base + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 0)); + usleep_range(5000, 8000); + writel(0x0, csi2phy->base + + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 0)); +} + +/* + * phy_qcom_mipi_csi2_settle_cnt_calc - Calculate settle count value + * + * Helper function to calculate settle count value. This is + * based on the CSI2 T_hs_settle parameter which in turn + * is calculated based on the CSI2 transmitter link frequency. + * + * Return settle count. + */ +static u8 phy_qcom_mipi_csi2_settle_cnt_calc(s64 link_freq, u32 timer_clk_rate) +{ + u32 t_hs_prepare_max_ps; + u32 timer_period_ps; + u32 t_hs_settle_ps; + u8 settle_cnt; + u32 ui_ps; + + ui_ps = div64_u64(PSEC_PER_SEC, link_freq); + ui_ps /= 2; + t_hs_prepare_max_ps = 85000 + 6 * ui_ps; + t_hs_settle_ps = t_hs_prepare_max_ps; + + timer_period_ps = div_u64(PSEC_PER_SEC, timer_clk_rate); + + if ((t_hs_settle_ps / timer_period_ps) < 6) + return 0; + + settle_cnt = t_hs_settle_ps / timer_period_ps - 6; + + return settle_cnt; +} + +static void +phy_qcom_mipi_csi2_gen2_config_lanes(struct mipi_csi2phy_device *csi2phy, + u8 settle_cnt) +{ + const struct mipi_csi2phy_device_regs *regs = csi2phy_dev_to_regs(csi2phy); + const struct mipi_csi2phy_lane_regs *r = regs->init_seq; + int i, array_size = regs->lane_array_size; + u32 val; + + for (i = 0; i < array_size; i++, r++) { + switch (r->param_type) { + case CSIPHY_SETTLE_CNT_LOWER_BYTE: + val = settle_cnt & 0xff; + break; + case CSIPHY_SKEW_CAL: + /* TODO: support application of skew from dt flag */ + continue; + default: + val = r->reg_data; + break; + } + writel(val, csi2phy->base + r->reg_addr); + if (r->delay_us) + udelay(r->delay_us); + } +} + +static int phy_qcom_mipi_csi2_lanes_enable(struct mipi_csi2phy_device *csi2phy, + struct mipi_csi2phy_stream_cfg *cfg) +{ + const struct mipi_csi2phy_device_regs *regs = csi2phy_dev_to_regs(csi2phy); + struct mipi_csi2phy_lanes_cfg *lane_cfg = &cfg->lane_cfg; + u8 settle_cnt; + u8 val; + int i; + + if (cfg->link_freq <= 0) + return -EINVAL; + + settle_cnt = phy_qcom_mipi_csi2_settle_cnt_calc(cfg->link_freq, csi2phy->timer_clk_rate); + if (!settle_cnt) + return -ENODEV; + + /* + * CSI_COMMON_CTRL5 is a physical lane power-up bitmap: + * - Bits [0,2,4,6] → D-PHY data lanes(LN0, LN2, LN4, LN6) + * - Bits [1,3,5] → C-PHY trio lanes(LN1, LN3, LN5) + * - Bit [7] → D-PHY clock lane(LNCK) dedicated clock enable + */ + val = BIT(lane_cfg->clk.pos); + for (i = 0; i < cfg->num_data_lanes; i++) + val |= BIT(lane_cfg->data[i].pos * 2); + + writel(val, csi2phy->base + + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 5)); + + /* Lane configuration for polarity @ CSIPHY-base + CTRL9 */ + for (i = 0; i < cfg->num_data_lanes; i++) { + if (lane_cfg->data[i].pol) { + u8 pos = lane_cfg->data[i].pos; + + writel(BIT(2), csi2phy->base + CSIPHY_2PH_LN_CSI_2PHASE_CTRL9n(pos * 2)); + } + } + + if (lane_cfg->clk.pol) + writel(BIT(2), csi2phy->base + CSIPHY_2PH_LN_CSI_2PHASE_CTRL9n(lane_cfg->clk.pos)); + + val = CSIPHY_3PH_CMN_CSI_COMMON_CTRL6_COMMON_PWRDN_B; + writel(val, csi2phy->base + + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 6)); + + val = 0x02; + writel(val, csi2phy->base + + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 7)); + + val = 0x00; + writel(val, csi2phy->base + + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 0)); + + phy_qcom_mipi_csi2_gen2_config_lanes(csi2phy, settle_cnt); + + /* IRQ_MASK registers - disable all interrupts */ + for (i = CSI_COMMON_STATUS_NUM; i < CSI_CTRL_STATUS_INDEX; i++) { + writel(0, csi2phy->base + + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, i)); + } + + return 0; +} + +static void +phy_qcom_mipi_csi2_lanes_disable(struct mipi_csi2phy_device *csi2phy, + struct mipi_csi2phy_stream_cfg *cfg) +{ + const struct mipi_csi2phy_device_regs *regs = csi2phy_dev_to_regs(csi2phy); + + writel(0, csi2phy->base + + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 5)); + + writel(0, csi2phy->base + + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 6)); +} + +static const struct mipi_csi2phy_hw_ops phy_qcom_mipi_csi2_ops_3ph_1_0 = { + .hw_version_read = phy_qcom_mipi_csi2_hw_version_read, + .reset = phy_qcom_mipi_csi2_reset, + .lanes_enable = phy_qcom_mipi_csi2_lanes_enable, + .lanes_disable = phy_qcom_mipi_csi2_lanes_disable, +}; + +static const char * const x1e_clks[] = { + "core", + "timer", + "ahb" +}; + +static const char * const x1e_supplies[] = { + "vdda-0p9", + "vdda-1p2" +}; + +static struct mipi_csi2_genpd x1e_genpds[] = { + { .name = "top", .scaled = false }, + { .name = "mmcx", .scaled = true }, + { .name = "mx", .scaled = true }, +}; + +const struct mipi_csi2phy_soc_cfg mipi_csi2_dphy_4nm_x1e = { + .ops = &phy_qcom_mipi_csi2_ops_3ph_1_0, + .reg_info = { + .init_seq = lane_regs_x1e80100, + .lane_array_size = ARRAY_SIZE(lane_regs_x1e80100), + .common_regs_offset = 0x1000, + }, + .supply_names = (const char **)x1e_supplies, + .num_supplies = ARRAY_SIZE(x1e_supplies), + .clk_names = (const char **)x1e_clks, + .num_clk = ARRAY_SIZE(x1e_clks), + .genpds = x1e_genpds, + .num_genpds = ARRAY_SIZE(x1e_genpds), +}; diff --git a/drivers/phy/qualcomm/phy-qcom-mipi-csi2-core.c b/drivers/phy/qualcomm/phy-qcom-mipi-csi2-core.c new file mode 100644 index 000000000000..c0a4fa20eaf0 --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-mipi-csi2-core.c @@ -0,0 +1,463 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2026, Linaro Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "phy-qcom-mipi-csi2.h" + +static int +phy_qcom_mipi_csi2_set_clock_rates(struct mipi_csi2phy_device *csi2phy, + s64 link_freq) +{ + struct device *dev = csi2phy->dev; + unsigned long opp_rate = link_freq / 4; + struct dev_pm_opp *opp; + long timer_rate; + int i, pstate; + int ret; + + opp = dev_pm_opp_find_freq_ceil(dev, &opp_rate); + if (IS_ERR(opp)) { + dev_err(csi2phy->dev, "Couldn't find ceiling for %lld Hz\n", + link_freq); + return PTR_ERR(opp); + } + + pstate = 0; + for (i = 0; i < csi2phy->pd_list->num_pds; i++) { + unsigned int perf; + + if (!csi2phy->soc_cfg->genpds[i].scaled) + continue; + + perf = dev_pm_opp_get_required_pstate(opp, pstate); + pstate += 1; + + ret = dev_pm_genpd_set_performance_state(csi2phy->pd_list->pd_devs[i], perf); + if (ret) { + dev_err(csi2phy->dev, "Couldn't set perf state %u\n", + perf); + dev_pm_opp_put(opp); + goto unset_pstate; + } + } + dev_pm_opp_put(opp); + + ret = dev_pm_opp_set_rate(dev, opp_rate); + if (ret) { + dev_err(csi2phy->dev, "dev_pm_opp_set_rate() fail\n"); + goto unset_opp_rate; + } + + timer_rate = clk_round_rate(csi2phy->timer_clk, link_freq / 4); + if (timer_rate <= 0) { + ret = -ENODEV; + goto unset_opp_rate; + } + + ret = clk_set_rate(csi2phy->timer_clk, timer_rate); + if (ret) + goto unset_opp_rate; + + csi2phy->timer_clk_rate = timer_rate; + + return 0; + +unset_opp_rate: + dev_pm_opp_set_rate(dev, 0); + +unset_pstate: + while (i--) { + if (!csi2phy->soc_cfg->genpds[i].scaled) + continue; + + dev_pm_genpd_set_performance_state(csi2phy->pd_list->pd_devs[i], 0); + } + + return ret; +} + +static int phy_qcom_mipi_csi2_configure(struct phy *phy, + union phy_configure_opts *opts) +{ + struct mipi_csi2phy_device *csi2phy = phy_get_drvdata(phy); + struct phy_configure_opts_mipi_dphy *dphy_cfg = &opts->mipi_dphy; + struct mipi_csi2phy_stream_cfg *stream_cfg = &csi2phy->stream_cfg; + int ret; + + ret = phy_mipi_dphy_config_validate(dphy_cfg); + if (ret) + return ret; + + if (dphy_cfg->lanes < 1 || dphy_cfg->lanes > CSI2_MAX_DATA_LANES) + return -EINVAL; + + if (dphy_cfg->lanes != stream_cfg->num_data_lanes) + return -EINVAL; + + stream_cfg->link_freq = dphy_cfg->hs_clk_rate; + + return 0; +} + +static int phy_qcom_mipi_csi2_set_mode(struct phy *phy, + enum phy_mode mode, int submode) +{ + struct mipi_csi2phy_device *csi2phy = phy_get_drvdata(phy); + + if (mode != PHY_MODE_MIPI_DPHY) + return -EOPNOTSUPP; + + csi2phy->phy_mode = mode; + return 0; +} + +static int phy_qcom_mipi_csi2_power_on(struct phy *phy) +{ + struct mipi_csi2phy_device *csi2phy = phy_get_drvdata(phy); + const struct mipi_csi2phy_hw_ops *ops = csi2phy->soc_cfg->ops; + int i, ret; + + ret = regulator_bulk_enable(csi2phy->soc_cfg->num_supplies, + csi2phy->supplies); + if (ret) + return ret; + + ret = pm_runtime_resume_and_get(csi2phy->dev); + if (ret < 0) + goto disable_regulators; + + ret = phy_qcom_mipi_csi2_set_clock_rates(csi2phy, csi2phy->stream_cfg.link_freq); + if (ret) + goto poweroff_phy; + + ret = clk_bulk_prepare_enable(csi2phy->soc_cfg->num_clk, + csi2phy->clks); + if (ret) { + dev_err(csi2phy->dev, "failed to enable clocks, %d\n", ret); + goto unset_rate; + } + + ops->reset(csi2phy); + + ops->hw_version_read(csi2phy); + + ret = ops->lanes_enable(csi2phy, &csi2phy->stream_cfg); + if (ret) + goto unset_clocks; + + return 0; + +unset_clocks: + clk_bulk_disable_unprepare(csi2phy->soc_cfg->num_clk, + csi2phy->clks); + +unset_rate: + dev_pm_opp_set_rate(csi2phy->dev, 0); + + for (i = 0; i < csi2phy->pd_list->num_pds; i++) { + if (!csi2phy->soc_cfg->genpds[i].scaled) + continue; + + dev_pm_genpd_set_performance_state(csi2phy->pd_list->pd_devs[i], 0); + } + +poweroff_phy: + pm_runtime_put_sync(csi2phy->dev); + +disable_regulators: + regulator_bulk_disable(csi2phy->soc_cfg->num_supplies, + csi2phy->supplies); + + return ret; +} + +static int phy_qcom_mipi_csi2_power_off(struct phy *phy) +{ + struct mipi_csi2phy_device *csi2phy = phy_get_drvdata(phy); + const struct mipi_csi2phy_hw_ops *ops = csi2phy->soc_cfg->ops; + int i; + + ops->lanes_disable(csi2phy, &csi2phy->stream_cfg); + + clk_bulk_disable_unprepare(csi2phy->soc_cfg->num_clk, + csi2phy->clks); + + dev_pm_opp_set_rate(csi2phy->dev, 0); + + for (i = 0; i < csi2phy->pd_list->num_pds; i++) { + if (!csi2phy->soc_cfg->genpds[i].scaled) + continue; + + dev_pm_genpd_set_performance_state(csi2phy->pd_list->pd_devs[i], 0); + } + + pm_runtime_put_sync(csi2phy->dev); + + regulator_bulk_disable(csi2phy->soc_cfg->num_supplies, + csi2phy->supplies); + + return 0; +} + +static const struct phy_ops phy_qcom_mipi_csi2_ops = { + .configure = phy_qcom_mipi_csi2_configure, + .set_mode = phy_qcom_mipi_csi2_set_mode, + .power_on = phy_qcom_mipi_csi2_power_on, + .power_off = phy_qcom_mipi_csi2_power_off, + .owner = THIS_MODULE, +}; + +static int phy_qcom_mipi_csi2_attach_pm_domains(struct mipi_csi2phy_device *csi2phy) +{ + struct dev_pm_domain_attach_data pd_data = { 0 }; + const char **pd_names; + int i; + + pd_names = devm_kzalloc(csi2phy->dev, + sizeof(char *) * csi2phy->soc_cfg->num_genpds, + GFP_KERNEL); + if (!pd_names) + return -ENOMEM; + + for (i = 0; i < csi2phy->soc_cfg->num_genpds; i++) + pd_names[i] = csi2phy->soc_cfg->genpds[i].name; + + pd_data.pd_names = pd_names; + pd_data.num_pd_names = csi2phy->soc_cfg->num_genpds; + + return devm_pm_domain_attach_list(csi2phy->dev, &pd_data, + &csi2phy->pd_list); +} + +static int phy_qcom_mipi_csi2_parse_routing(struct mipi_csi2phy_device *csi2phy) +{ + struct mipi_csi2phy_stream_cfg *stream_cfg = &csi2phy->stream_cfg; + u32 lane_polarities[CSI2_MAX_DATA_LANES + 1]; + u32 data_lanes[CSI2_MAX_DATA_LANES]; + struct device *dev = csi2phy->dev; + struct fwnode_handle *ep; + int num_polarities; + int num_data_lanes; + u32 bus_type; + int i, ret; + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 1, 0); + if (ep) { + fwnode_handle_put(ep); + dev_err(dev, "DPHY split mode is not supported\n"); + return -EOPNOTSUPP; + } + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0); + if (!ep) { + dev_err(dev, "Missing port@0\n"); + return -ENODEV; + } + + /* bus-type is optional, D-PHY is the default */ + bus_type = MEDIA_BUS_TYPE_CSI2_DPHY; + fwnode_property_read_u32(ep, "bus-type", &bus_type); + + if (bus_type != MEDIA_BUS_TYPE_CSI2_DPHY) { + ret = -EOPNOTSUPP; + dev_err(dev, "Unsupported bus-type %u\n", bus_type); + goto out_put; + } + + num_data_lanes = fwnode_property_count_u32(ep, "data-lanes"); + if (num_data_lanes < 1 || num_data_lanes > CSI2_MAX_DATA_LANES) { + ret = -EINVAL; + dev_err(dev, "Invalid data-lanes count: %d\n", num_data_lanes); + goto out_put; + } + stream_cfg->num_data_lanes = num_data_lanes; + + ret = fwnode_property_read_u32_array(ep, "data-lanes", data_lanes, + stream_cfg->num_data_lanes); + if (ret) { + dev_err(dev, "Failed to read data-lanes: %d\n", ret); + goto out_put; + } + + /* lane-polarities: optional, up to num_data_lanes + 1 entries */ + memset(lane_polarities, 0x00, sizeof(lane_polarities)); + num_polarities = fwnode_property_count_u32(ep, "lane-polarities"); + if (num_polarities > 0) { + if (num_polarities != stream_cfg->num_data_lanes + 1) { + ret = -EINVAL; + dev_err(dev, "clock+data-lane %d/polarities %d mismatch\n", + stream_cfg->num_data_lanes + 1, num_polarities); + goto out_put; + } + + ret = fwnode_property_read_u32_array(ep, "lane-polarities", lane_polarities, + num_polarities); + if (ret) { + dev_err(dev, "Failed to read lane-polarities: %d\n", ret); + goto out_put; + } + } + + csi2phy->stream_cfg.lane_cfg.clk.pos = CSI2_DEFAULT_CLK_LANE; + csi2phy->stream_cfg.lane_cfg.clk.pol = lane_polarities[0]; + + for (i = 0; i < csi2phy->stream_cfg.num_data_lanes; i++) { + if (data_lanes[i] < 1 || data_lanes[i] > CSI2_MAX_DATA_LANES) { + dev_err(dev, "Invalid lane %d\n", data_lanes[i]); + ret = -EINVAL; + goto out_put; + } + + /* Convert data-lanes = <1 2 3 4> to bit positions */ + csi2phy->stream_cfg.lane_cfg.data[i].pos = data_lanes[i] - 1; + csi2phy->stream_cfg.lane_cfg.data[i].pol = lane_polarities[i + 1]; + } + + ret = 0; + +out_put: + fwnode_handle_put(ep); + + return ret; +} + +static int phy_qcom_mipi_csi2_probe(struct platform_device *pdev) +{ + unsigned int i, num_clk, num_supplies; + struct mipi_csi2phy_device *csi2phy; + struct phy_provider *phy_provider; + struct device *dev = &pdev->dev; + struct phy *generic_phy; + int ret; + + csi2phy = devm_kzalloc(dev, sizeof(*csi2phy), GFP_KERNEL); + if (!csi2phy) + return -ENOMEM; + + csi2phy->dev = dev; + dev_set_drvdata(dev, csi2phy); + + csi2phy->soc_cfg = device_get_match_data(&pdev->dev); + + if (!csi2phy->soc_cfg) + return -EINVAL; + + num_clk = csi2phy->soc_cfg->num_clk; + + ret = phy_qcom_mipi_csi2_parse_routing(csi2phy); + if (ret) + return ret; + + ret = phy_qcom_mipi_csi2_attach_pm_domains(csi2phy); + if (ret < 0 || csi2phy->pd_list == NULL) { + if (ret == 0) + ret = -ENODEV; + return dev_err_probe(dev, ret, "Failed to attach power-domain list\n"); + } + + ret = devm_pm_runtime_enable(dev); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to enable pm runtime\n"); + + ret = devm_clk_bulk_get_all(dev, &csi2phy->clks); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get clocks\n"); + + if (num_clk != ret) { + return dev_err_probe(dev, -ENODEV, "clock count %d expected %d\n", + ret, num_clk); + } + + for (i = 0; i < num_clk; i++) { + if (!csi2phy->clks[i].id) + return dev_err_probe(dev, -EINVAL, "Missing clock-names\n"); + + if (!strcmp(csi2phy->clks[i].id, "timer")) { + csi2phy->timer_clk = csi2phy->clks[i].clk; + break; + } + } + if (!csi2phy->timer_clk) + return dev_err_probe(dev, -ENODEV, "no timer clock\n"); + + ret = devm_pm_opp_set_clkname(dev, "core"); + if (ret) + return dev_err_probe(dev, ret, "Failed to set opp clkname\n"); + + ret = devm_pm_opp_of_add_table(dev); + if (ret) + return dev_err_probe(dev, ret, "invalid OPP table in device tree\n"); + + num_supplies = csi2phy->soc_cfg->num_supplies; + csi2phy->supplies = devm_kzalloc(dev, sizeof(*csi2phy->supplies) * num_supplies, + GFP_KERNEL); + if (!csi2phy->supplies) + return -ENOMEM; + + for (i = 0; i < num_supplies; i++) + csi2phy->supplies[i].supply = csi2phy->soc_cfg->supply_names[i]; + + ret = devm_regulator_bulk_get(dev, num_supplies, csi2phy->supplies); + if (ret) + return dev_err_probe(dev, ret, + "failed to get regulator supplies\n"); + + csi2phy->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(csi2phy->base)) + return PTR_ERR(csi2phy->base); + + generic_phy = devm_phy_create(dev, NULL, &phy_qcom_mipi_csi2_ops); + if (IS_ERR(generic_phy)) { + ret = PTR_ERR(generic_phy); + return dev_err_probe(dev, ret, "failed to create phy\n"); + } + csi2phy->phy = generic_phy; + + phy_set_drvdata(generic_phy, csi2phy); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (!IS_ERR(phy_provider)) + dev_dbg(dev, "Registered MIPI CSI2 PHY device\n"); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id phy_qcom_mipi_csi2_of_match_table[] = { + { .compatible = "qcom,x1e80100-csi2-phy", .data = &mipi_csi2_dphy_4nm_x1e }, + { } +}; +MODULE_DEVICE_TABLE(of, phy_qcom_mipi_csi2_of_match_table); + +static struct platform_driver phy_qcom_mipi_csi2_driver = { + .probe = phy_qcom_mipi_csi2_probe, + .driver = { + .name = "qcom-mipi-csi2-phy", + .of_match_table = phy_qcom_mipi_csi2_of_match_table, + }, +}; + +module_platform_driver(phy_qcom_mipi_csi2_driver); + +MODULE_DESCRIPTION("Qualcomm MIPI CSI2 PHY driver"); +MODULE_AUTHOR("Bryan O'Donoghue "); +MODULE_LICENSE("GPL"); diff --git a/drivers/phy/qualcomm/phy-qcom-mipi-csi2.h b/drivers/phy/qualcomm/phy-qcom-mipi-csi2.h new file mode 100644 index 000000000000..7e55ae007370 --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-mipi-csi2.h @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * + * Qualcomm MIPI CSI2 CPHY/DPHY driver + * + * Copyright (C) 2025 Linaro Ltd. + */ +#ifndef __PHY_QCOM_MIPI_CSI2_H__ +#define __PHY_QCOM_MIPI_CSI2_H__ + +#include + +#define CSI2_MAX_DATA_LANES 4 +#define CSI2_DEFAULT_CLK_LANE 7 + +struct mipi_csi2phy_lane { + u8 pos; + u8 pol; +}; + +struct mipi_csi2phy_lanes_cfg { + struct mipi_csi2phy_lane data[CSI2_MAX_DATA_LANES]; + struct mipi_csi2phy_lane clk; +}; + +struct mipi_csi2phy_stream_cfg { + s64 link_freq; + u8 num_data_lanes; + struct mipi_csi2phy_lanes_cfg lane_cfg; +}; + +struct mipi_csi2phy_device; + +struct mipi_csi2phy_hw_ops { + void (*hw_version_read)(struct mipi_csi2phy_device *csi2phy_dev); + void (*reset)(struct mipi_csi2phy_device *csi2phy_dev); + int (*lanes_enable)(struct mipi_csi2phy_device *csi2phy_dev, + struct mipi_csi2phy_stream_cfg *cfg); + void (*lanes_disable)(struct mipi_csi2phy_device *csi2phy_dev, + struct mipi_csi2phy_stream_cfg *cfg); +}; + +struct mipi_csi2phy_lane_regs { + const s32 reg_addr; + const s32 reg_data; + const u32 delay_us; + const u32 param_type; +}; + +struct mipi_csi2phy_device_regs { + const struct mipi_csi2phy_lane_regs *init_seq; + const int lane_array_size; + const u32 common_regs_offset; +}; + +struct mipi_csi2_genpd { + const char *name; + bool scaled; +}; + +struct mipi_csi2phy_soc_cfg { + const struct mipi_csi2phy_hw_ops *ops; + const struct mipi_csi2phy_device_regs reg_info; + + const char ** const supply_names; + const unsigned int num_supplies; + + const char ** const clk_names; + const unsigned int num_clk; + + const struct mipi_csi2_genpd *genpds; + const unsigned int num_genpds; +}; + +struct mipi_csi2phy_device { + struct device *dev; + u8 phy_mode; + + struct phy *phy; + void __iomem *base; + + struct clk_bulk_data *clks; + struct clk *timer_clk; + u32 timer_clk_rate; + + struct regulator_bulk_data *supplies; + struct dev_pm_domain_list *pd_list; + + const struct mipi_csi2phy_soc_cfg *soc_cfg; + struct mipi_csi2phy_stream_cfg stream_cfg; + + u32 hw_version; +}; + +extern const struct mipi_csi2phy_soc_cfg mipi_csi2_dphy_4nm_x1e; + +#endif /* __PHY_QCOM_MIPI_CSI2_H__ */ From 090055ee629523092688da42428558ba56d4272f Mon Sep 17 00:00:00 2001 From: Bryan O'Donoghue Date: Fri, 18 Sep 2026 14:56:36 +0100 Subject: [PATCH 033/114] phy: core: Fix race-condition between _of_phy_get() and try_module_get() Sashiko asked during a patch review if the existing usage pattern had a race condition; specifically in of_phy_get() if it was possible between returning from _of_phy_get() and running try_module_get() that a module might be unbound leading to use-after-free. Looking at the code this appears to be so, there is no linkage between the phy and module under a synchronisation primitive. Using the phy_provider_mutex in phy_get() will ensure there is a link between the returned phy pointer and the module_get() bumping the module reference count. Amend phy_get(), of_phy_get() and devm_of_phy_get_by_index() to fix the same usage pattern. phy_provider_unregister() must take the phy_provider_mutex so amending phy_get()/of_phy_get() to take that same mutex guarantees there is no use-after-free. Fixes: ff764963479a1 ("drivers: phy: add generic PHY framework") Cc: stable@vger.kernel.org Reviewed-by: Manivannan Sadhasivam Reviewed-by: Loic Poulain Tested-by: Oleg Keri # Lenovo Yoga Slim 7x Gen 11 (Glymur) Signed-off-by: Bryan O'Donoghue Link: https://patch.msgid.link/20260918-b4-linux-next-25-03-13-dtsi-x1e80100-camss-v20-1-dc244e124e71@linaro.org Signed-off-by: Vinod Koul --- drivers/phy/phy-core.c | 45 ++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/drivers/phy/phy-core.c b/drivers/phy/phy-core.c index 21aaf2f76e53..89addd732bff 100644 --- a/drivers/phy/phy-core.c +++ b/drivers/phy/phy-core.c @@ -124,13 +124,13 @@ static struct phy *phy_find(struct device *dev, const char *con_id) const char *dev_id = dev_name(dev); struct phy_lookup *p, *pl = NULL; - mutex_lock(&phy_provider_mutex); + lockdep_assert_held(&phy_provider_mutex); + list_for_each_entry(p, &phys, node) if (!strcmp(p->dev_id, dev_id) && !strcmp(p->con_id, con_id)) { pl = p; break; } - mutex_unlock(&phy_provider_mutex); return pl ? pl->phy : ERR_PTR(-ENODEV); } @@ -624,6 +624,8 @@ static struct phy *_of_phy_get(struct device_node *np, int index) struct phy *phy = NULL; struct of_phandle_args args; + lockdep_assert_held(&phy_provider_mutex); + ret = of_parse_phandle_with_args(np, "phys", "#phy-cells", index, &args); if (ret) @@ -635,11 +637,10 @@ static struct phy *_of_phy_get(struct device_node *np, int index) goto out_put_node; } - mutex_lock(&phy_provider_mutex); phy_provider = of_phy_provider_lookup(args.np); if (IS_ERR(phy_provider) || !try_module_get(phy_provider->owner)) { phy = ERR_PTR(-EPROBE_DEFER); - goto out_unlock; + goto out_put_node; } if (!of_device_is_available(args.np)) { @@ -653,8 +654,6 @@ static struct phy *_of_phy_get(struct device_node *np, int index) out_put_module: module_put(phy_provider->owner); -out_unlock: - mutex_unlock(&phy_provider_mutex); out_put_node: of_node_put(args.np); @@ -678,15 +677,21 @@ struct phy *of_phy_get(struct device_node *np, const char *con_id) if (con_id) index = of_property_match_string(np, "phy-names", con_id); + mutex_lock(&phy_provider_mutex); + phy = _of_phy_get(np, index); if (IS_ERR(phy)) - return phy; + goto out_unlock; - if (!try_module_get(phy->ops->owner)) - return ERR_PTR(-EPROBE_DEFER); + if (!try_module_get(phy->ops->owner)) { + phy = ERR_PTR(-EPROBE_DEFER); + goto out_unlock; + } get_device(&phy->dev); +out_unlock: + mutex_unlock(&phy_provider_mutex); return phy; } EXPORT_SYMBOL_GPL(of_phy_get); @@ -786,6 +791,7 @@ struct phy *phy_get(struct device *dev, const char *string) struct phy *phy; struct device_link *link; + mutex_lock(&phy_provider_mutex); if (dev->of_node) { if (string) index = of_property_match_string(dev->of_node, "phy-names", @@ -796,15 +802,18 @@ struct phy *phy_get(struct device *dev, const char *string) } else { if (string == NULL) { dev_WARN(dev, "missing string\n"); - return ERR_PTR(-EINVAL); + phy = ERR_PTR(-EINVAL); + goto out_unlock; } phy = phy_find(dev, string); } if (IS_ERR(phy)) - return phy; + goto out_unlock; - if (!try_module_get(phy->ops->owner)) - return ERR_PTR(-EPROBE_DEFER); + if (!try_module_get(phy->ops->owner)) { + phy = ERR_PTR(-EPROBE_DEFER); + goto out_unlock; + } get_device(&phy->dev); @@ -813,6 +822,8 @@ struct phy *phy_get(struct device *dev, const char *string) dev_dbg(dev, "failed to create device link to %s\n", dev_name(phy->dev.parent)); +out_unlock: + mutex_unlock(&phy_provider_mutex); return phy; } EXPORT_SYMBOL_GPL(phy_get); @@ -961,15 +972,17 @@ struct phy *devm_of_phy_get_by_index(struct device *dev, struct device_node *np, if (!ptr) return ERR_PTR(-ENOMEM); + mutex_lock(&phy_provider_mutex); phy = _of_phy_get(np, index); if (IS_ERR(phy)) { devres_free(ptr); - return phy; + goto out_unlock; } if (!try_module_get(phy->ops->owner)) { devres_free(ptr); - return ERR_PTR(-EPROBE_DEFER); + phy = ERR_PTR(-EPROBE_DEFER); + goto out_unlock; } get_device(&phy->dev); @@ -982,6 +995,8 @@ struct phy *devm_of_phy_get_by_index(struct device *dev, struct device_node *np, dev_dbg(dev, "failed to create device link to %s\n", dev_name(phy->dev.parent)); +out_unlock: + mutex_unlock(&phy_provider_mutex); return phy; } EXPORT_SYMBOL_GPL(devm_of_phy_get_by_index); From 89c5ae2b19619e41f9098011667fc1f59c9f322c Mon Sep 17 00:00:00 2001 From: Bryan O'Donoghue Date: Fri, 18 Sep 2026 14:56:37 +0100 Subject: [PATCH 034/114] phy: core: Add phy_get_by_of_node() Add new function phy_get_by_of_node() allowing lookup of a phy by device_node. Separates existing logic in _of_phy_get() into an internal helper method _of_phy_get_with_args() to allow for reuse in new method. Signed-off-by: Bryan O'Donoghue Reviewed-by: Manivannan Sadhasivam Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260918-b4-linux-next-25-03-13-dtsi-x1e80100-camss-v20-2-dc244e124e71@linaro.org Signed-off-by: Vinod Koul --- drivers/phy/phy-core.c | 83 ++++++++++++++++++++++++++++++++--------- include/linux/phy/phy.h | 6 +++ 2 files changed, 71 insertions(+), 18 deletions(-) diff --git a/drivers/phy/phy-core.c b/drivers/phy/phy-core.c index 89addd732bff..42c27a2b4515 100644 --- a/drivers/phy/phy-core.c +++ b/drivers/phy/phy-core.c @@ -606,6 +606,38 @@ int phy_validate(struct phy *phy, enum phy_mode mode, int submode, } EXPORT_SYMBOL_GPL(phy_validate); +/** + * _of_phy_get_with_args() - lookup and obtain a reference to a phy by of_phandle_args + * @args: of_phandle_args to the phy + * + * Returns: the phy from the provider's of_xlate, -ENODEV if disabled, + * -EPROBE_DEFER if the provider is not yet registered. + */ +static struct phy *_of_phy_get_with_args(struct of_phandle_args *args) +{ + struct phy *phy; + struct phy_provider *phy_provider; + + lockdep_assert_held(&phy_provider_mutex); + + phy_provider = of_phy_provider_lookup(args->np); + if (IS_ERR(phy_provider) || !try_module_get(phy_provider->owner)) + return ERR_PTR(-EPROBE_DEFER); + + if (!of_device_is_available(args->np)) { + dev_warn(phy_provider->dev, "Requested PHY is disabled\n"); + phy = ERR_PTR(-ENODEV); + goto out_put_module; + } + + phy = phy_provider->of_xlate(phy_provider->dev, args); + +out_put_module: + module_put(phy_provider->owner); + + return phy; +} + /** * _of_phy_get() - lookup and obtain a reference to a phy by phandle * @np: device_node for which to get the phy @@ -620,8 +652,7 @@ EXPORT_SYMBOL_GPL(phy_validate); static struct phy *_of_phy_get(struct device_node *np, int index) { int ret; - struct phy_provider *phy_provider; - struct phy *phy = NULL; + struct phy *phy; struct of_phandle_args args; lockdep_assert_held(&phy_provider_mutex); @@ -637,22 +668,7 @@ static struct phy *_of_phy_get(struct device_node *np, int index) goto out_put_node; } - phy_provider = of_phy_provider_lookup(args.np); - if (IS_ERR(phy_provider) || !try_module_get(phy_provider->owner)) { - phy = ERR_PTR(-EPROBE_DEFER); - goto out_put_node; - } - - if (!of_device_is_available(args.np)) { - dev_warn(phy_provider->dev, "Requested PHY is disabled\n"); - phy = ERR_PTR(-ENODEV); - goto out_put_module; - } - - phy = phy_provider->of_xlate(phy_provider->dev, &args); - -out_put_module: - module_put(phy_provider->owner); + phy = _of_phy_get_with_args(&args); out_put_node: of_node_put(args.np); @@ -1001,6 +1017,37 @@ struct phy *devm_of_phy_get_by_index(struct device *dev, struct device_node *np, } EXPORT_SYMBOL_GPL(devm_of_phy_get_by_index); +/** + * phy_get_by_of_node() - lookup and obtain a reference to a phy by device_node + * @np: node containing the phy + * + * Returns: the phy associated with the device node or ERR_PTR. + */ +struct phy *phy_get_by_of_node(struct device_node *np) +{ + struct of_phandle_args args = { .np = np, .args_count = 0 }; + struct phy *phy; + + mutex_lock(&phy_provider_mutex); + + phy = _of_phy_get_with_args(&args); + if (IS_ERR(phy)) + goto out_unlock; + + if (!try_module_get(phy->ops->owner)) { + phy = ERR_PTR(-EPROBE_DEFER); + goto out_unlock; + } + + get_device(&phy->dev); + +out_unlock: + mutex_unlock(&phy_provider_mutex); + + return phy; +} +EXPORT_SYMBOL_GPL(phy_get_by_of_node); + /** * phy_create() - create a new phy * @dev: device that is creating the new phy diff --git a/include/linux/phy/phy.h b/include/linux/phy/phy.h index ea47975e288a..71c2e1639713 100644 --- a/include/linux/phy/phy.h +++ b/include/linux/phy/phy.h @@ -284,6 +284,7 @@ struct phy *devm_of_phy_optional_get(struct device *dev, struct device_node *np, const char *con_id); struct phy *devm_of_phy_get_by_index(struct device *dev, struct device_node *np, int index); +struct phy *phy_get_by_of_node(struct device_node *np); void of_phy_put(struct phy *phy); void phy_put(struct device *dev, struct phy *phy); void devm_phy_put(struct device *dev, struct phy *phy); @@ -493,6 +494,11 @@ static inline struct phy *devm_of_phy_get_by_index(struct device *dev, return ERR_PTR(-ENOSYS); } +static inline struct phy *phy_get_by_of_node(struct device_node *np) +{ + return ERR_PTR(-ENOSYS); +} + static inline void of_phy_put(struct phy *phy) { } From 2ee4e00dc1a38bf7bbe128825a63c6b523480c33 Mon Sep 17 00:00:00 2001 From: Bryan O'Donoghue Date: Fri, 18 Sep 2026 14:56:38 +0100 Subject: [PATCH 035/114] phy: core: Add devm_phy_get_by_of_node() Add a devm variant of phy_get_by_of_node() to allow for the familiar pattern of having devres automatically release resources on the driver's exit path. Reviewed-by: Manivannan Sadhasivam Signed-off-by: Bryan O'Donoghue Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260918-b4-linux-next-25-03-13-dtsi-x1e80100-camss-v20-3-dc244e124e71@linaro.org Signed-off-by: Vinod Koul --- drivers/phy/phy-core.c | 34 ++++++++++++++++++++++++++++++++++ include/linux/phy/phy.h | 7 +++++++ 2 files changed, 41 insertions(+) diff --git a/drivers/phy/phy-core.c b/drivers/phy/phy-core.c index 42c27a2b4515..f0d2b50d1c76 100644 --- a/drivers/phy/phy-core.c +++ b/drivers/phy/phy-core.c @@ -1048,6 +1048,40 @@ struct phy *phy_get_by_of_node(struct device_node *np) } EXPORT_SYMBOL_GPL(phy_get_by_of_node); +/** + * devm_phy_get_by_of_node() - devm managed lookup and obtain phy reference by device node + * @dev: device requesting the PHY + * @np: device_node of the PHY provider + * + * Returns: phy associated with the device_node or ERR_PTR. devres manages + * releasing resources. + */ +struct phy *devm_phy_get_by_of_node(struct device *dev, struct device_node *np) +{ + struct phy **ptr, *phy; + struct device_link *link; + + ptr = devres_alloc(devm_phy_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + phy = phy_get_by_of_node(np); + if (IS_ERR(phy)) { + devres_free(ptr); + return phy; + } + + *ptr = phy; + devres_add(dev, ptr); + link = device_link_add(dev, &phy->dev, DL_FLAG_STATELESS); + if (!link) + dev_dbg(dev, "failed to create device link to %s\n", + dev_name(phy->dev.parent)); + + return phy; +} +EXPORT_SYMBOL_GPL(devm_phy_get_by_of_node); + /** * phy_create() - create a new phy * @dev: device that is creating the new phy diff --git a/include/linux/phy/phy.h b/include/linux/phy/phy.h index 71c2e1639713..14b924a88411 100644 --- a/include/linux/phy/phy.h +++ b/include/linux/phy/phy.h @@ -285,6 +285,7 @@ struct phy *devm_of_phy_optional_get(struct device *dev, struct device_node *np, struct phy *devm_of_phy_get_by_index(struct device *dev, struct device_node *np, int index); struct phy *phy_get_by_of_node(struct device_node *np); +struct phy *devm_phy_get_by_of_node(struct device *dev, struct device_node *np); void of_phy_put(struct phy *phy); void phy_put(struct device *dev, struct phy *phy); void devm_phy_put(struct device *dev, struct phy *phy); @@ -499,6 +500,12 @@ static inline struct phy *phy_get_by_of_node(struct device_node *np) return ERR_PTR(-ENOSYS); } +static inline struct phy *devm_phy_get_by_of_node(struct device *dev, + struct device_node *np) +{ + return ERR_PTR(-ENOSYS); +} + static inline void of_phy_put(struct phy *phy) { } From fcce260fa4ba624dced020789d0a9a0cc1670062 Mon Sep 17 00:00:00 2001 From: Bryan O'Donoghue Date: Fri, 18 Sep 2026 14:56:39 +0100 Subject: [PATCH 036/114] phy: core: Add missing kerneldoc colon in two locations Correct missing colon after Return in kernel doc. Suggested-by: Manivannan Sadhasivam Signed-off-by: Bryan O'Donoghue Reviewed-by: Manivannan Sadhasivam Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260918-b4-linux-next-25-03-13-dtsi-x1e80100-camss-v20-4-dc244e124e71@linaro.org Signed-off-by: Vinod Koul --- drivers/phy/phy-core.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/phy/phy-core.c b/drivers/phy/phy-core.c index f0d2b50d1c76..597e6b234f5e 100644 --- a/drivers/phy/phy-core.c +++ b/drivers/phy/phy-core.c @@ -643,7 +643,7 @@ static struct phy *_of_phy_get_with_args(struct of_phandle_args *args) * @np: device_node for which to get the phy * @index: the index of the phy * - * Returns the phy associated with the given phandle value, + * Returns: the phy associated with the given phandle value, * after getting a refcount to it or -ENODEV if there is no such phy or * -EPROBE_DEFER if there is a phandle to the phy, but the device is * not yet loaded. This function uses of_xlate call back function provided @@ -681,7 +681,7 @@ static struct phy *_of_phy_get(struct device_node *np, int index) * @np: device_node for which to get the phy * @con_id: name of the phy from device's point of view * - * Returns the phy driver, after getting a refcount to it; or + * Returns: the phy driver, after getting a refcount to it; or * -ENODEV if there is no such phy. The caller is responsible for * calling of_phy_put() to release that count. */ @@ -797,7 +797,7 @@ EXPORT_SYMBOL_GPL(of_phy_simple_xlate); * @string: the phy name as given in the dt data or the name of the controller * port for non-dt case * - * Returns the phy driver, after getting a refcount to it; or + * Returns: the phy driver, after getting a refcount to it; or * -ENODEV if there is no such phy. The caller is responsible for * calling phy_put() to release that count. */ From c7f2322431cb6d108b18fb4154606b49e2bc50f7 Mon Sep 17 00:00:00 2001 From: Stefan Wahren Date: Mon, 7 Sep 2026 17:04:17 +0200 Subject: [PATCH 037/114] dt-bindings: phy: ti,tcan104x-can: Fix property constrains Microchip ATA6561 is compatible to TI TCAN1042, so the same property constrains should apply. But they use a strict scalar enum match, which makes Microchip ATA6561 fallthrough. So add a "contains" to cover also all compatibles ones. Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/linux-devicetree/20260902133525.E22DE1F000E9@smtp.kernel.org/ Fixes: 05ace63d0bcf ("dt-bindings: phy: ti,tcan104x-can: Document NXP TJA105X/1048") Signed-off-by: Stefan Wahren Reviewed-by: Krzysztof Kozlowski Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260907150420.18787-2-wahrenst@gmx.net Signed-off-by: Vinod Koul --- .../devicetree/bindings/phy/ti,tcan104x-can.yaml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/phy/ti,tcan104x-can.yaml b/Documentation/devicetree/bindings/phy/ti,tcan104x-can.yaml index 9f5c37ca6496..0cf5fa21106b 100644 --- a/Documentation/devicetree/bindings/phy/ti,tcan104x-can.yaml +++ b/Documentation/devicetree/bindings/phy/ti,tcan104x-can.yaml @@ -71,10 +71,11 @@ allOf: - if: properties: compatible: - enum: - - nxp,tjr1443 - - ti,tcan1042 - - ti,tcan1043 + contains: + enum: + - nxp,tjr1443 + - ti,tcan1042 + - ti,tcan1043 then: properties: '#phy-cells': From 7540fc0a4f667b6773d10753a602a42a3113087b Mon Sep 17 00:00:00 2001 From: Akash Sukhavasi Date: Wed, 8 Jul 2026 16:31:00 -0500 Subject: [PATCH 038/114] phy: HiSilicon: Fix error handling in hi3670_pcie_allclk_ctrl() When hi3670_pcie_pll_ctrl() fails, hi3670_pcie_allclk_ctrl() returns -EINVAL directly, skipping the close_clocks cleanup path. This leaves the PLL with its power-down bit cleared. Use goto close_clocks instead, which disables the PLL and propagates the original error code from hi3670_pcie_pll_ctrl(). Found while investigating a Smatch warning. Fixes: 73075011ffff ("phy: HiSilicon: Add driver for Kirin 970 PCIe PHY") Signed-off-by: Akash Sukhavasi Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260708-b4-hi3670-v1-1-f9236f3e585c@gmail.com Signed-off-by: Vinod Koul --- drivers/phy/hisilicon/phy-hi3670-pcie.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/phy/hisilicon/phy-hi3670-pcie.c b/drivers/phy/hisilicon/phy-hi3670-pcie.c index 0b2dc590aa6f..148763ed2c62 100644 --- a/drivers/phy/hisilicon/phy-hi3670-pcie.c +++ b/drivers/phy/hisilicon/phy-hi3670-pcie.c @@ -480,7 +480,7 @@ static int hi3670_pcie_allclk_ctrl(struct hi3670_pcie_phy *phy, bool clk_on) ret = hi3670_pcie_pll_ctrl(phy, true); if (ret) { dev_err(dev, "Failed to enable pll\n"); - return -EINVAL; + goto close_clocks; } hi3670_pcie_hp_debounce_gt(phy, true); hi3670_pcie_phyref_gt(phy, true); From 02df7fe1297d35d33c9841f345f61bf10f969300 Mon Sep 17 00:00:00 2001 From: Nikolai Burov Date: Sat, 11 Jul 2026 13:34:46 +0300 Subject: [PATCH 039/114] phy: mediatek: xsphy: clear U2 DTM0 force bits during init On mobile SoCs, the USB PHY is completely powered off during bootup. As a result, various bits in the DTM0 register are set that prevent the USB 2.0 PHY from functioning. Clear them as part of the initialization sequence. Signed-off-by: Nikolai Burov Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260711-mt6858-xsphy-poweron-v1-1-dee2fb71c7d5@jolla.com Signed-off-by: Vinod Koul --- drivers/phy/mediatek/phy-mtk-xsphy.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/phy/mediatek/phy-mtk-xsphy.c b/drivers/phy/mediatek/phy-mtk-xsphy.c index cc1d66954212..b29220bc5592 100644 --- a/drivers/phy/mediatek/phy-mtk-xsphy.c +++ b/drivers/phy/mediatek/phy-mtk-xsphy.c @@ -62,6 +62,14 @@ #define P2A6_RG_BC11_SW_EN BIT(23) #define P2A6_RG_OTG_VBUSCMP_EN BIT(20) +#define XSP_U2PHYDTM0 ((SSUSB_SIFSLV_U2PHY_COM) + 0x068) +#define P2D_FORCE_DATAIN BIT(23) +#define P2D_FORCE_DM_PULLDOWN BIT(21) +#define P2D_FORCE_DP_PULLDOWN BIT(20) +#define P2D_FORCE_XCVRSEL BIT(19) +#define P2D_FORCE_SUSPENDM BIT(18) +#define P2D_FORCE_TERMSEL BIT(17) + #define XSP_U2PHYDTM1 ((SSUSB_SIFSLV_U2PHY_COM) + 0x06C) #define P2D_FORCE_IDDIG BIT(9) #define P2D_RG_VBUSVALID BIT(5) @@ -181,6 +189,11 @@ static void u2_phy_instance_init(struct mtk_xsphy *xsphy, { void __iomem *pbase = inst->port_base; + mtk_phy_clear_bits(pbase + XSP_U2PHYDTM0, + P2D_FORCE_DATAIN | P2D_FORCE_DM_PULLDOWN | + P2D_FORCE_DP_PULLDOWN | P2D_FORCE_XCVRSEL | + P2D_FORCE_SUSPENDM | P2D_FORCE_TERMSEL); + /* DP/DM BC1.1 path Disable */ mtk_phy_clear_bits(pbase + XSP_USBPHYACR6, P2A6_RG_BC11_SW_EN); From 16ad4c3e62a5946143eb6642f46d25e46f605cd6 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Sun, 27 Sep 2026 13:12:52 +0800 Subject: [PATCH 040/114] MAINTAINERS: name the phy/linux-phy next branch The T: entry for GENERIC PHY FRAMEWORK names phy/linux-phy without a branch. The repository's HEAD points to branch master, which mainline already contains. linux-next merges the next branch of this repository (Next/Trees). Name that branch in the entry. Signed-off-by: Matthias Goergens Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260927051252.2751812-1-matthias.goergens@gmail.com Signed-off-by: Vinod Koul --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 881b4e92bb18..16c1c1fbb908 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11002,7 +11002,7 @@ R: Manivannan Sadhasivam L: linux-phy@lists.infradead.org S: Supported Q: https://patchwork.kernel.org/project/linux-phy/list/ -T: git git://git.kernel.org/pub/scm/linux/kernel/git/phy/linux-phy.git +T: git git://git.kernel.org/pub/scm/linux/kernel/git/phy/linux-phy.git next F: Documentation/devicetree/bindings/phy/ F: drivers/phy/ F: include/dt-bindings/phy/ From 861e77f3314e30228e234f866c8bb1ea82560b92 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 15 Sep 2026 15:49:48 +0200 Subject: [PATCH 041/114] dt-bindings: phy: rockchip-usbdp: add improved ports scheme Currently the Rockchip USBDP PHY is missing a documented port scheme. Meanwhile upstream RK3588 DTS files are a bit messy and use different port schemes. The upstream USBDP PHY Linux kernel driver does not yet parse the ports at all and thus does not create any implicit ABI either. But with the current mess it is not possible to properly support USB-C DP AltMode. Thus this introduces a proper port scheme following roughly the ports design of the Qualcomm QMP USB4-USB3-DP PHY controller binding with a slight difference that there is an additional port for the USB-C SBU port as the Rockchip USB-DP PHY also contains the SBU mux. Reviewed-by: Rob Herring (Arm) Signed-off-by: Sebastian Reichel Link: https://patch.msgid.link/20260915-rockchip-usbdp-cleanup-v15-1-80e2c11d1ce8@collabora.com Signed-off-by: Vinod Koul --- .../bindings/phy/phy-rockchip-usbdp.yaml | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Documentation/devicetree/bindings/phy/phy-rockchip-usbdp.yaml b/Documentation/devicetree/bindings/phy/phy-rockchip-usbdp.yaml index 8b7059d5b182..89efaf005a7b 100644 --- a/Documentation/devicetree/bindings/phy/phy-rockchip-usbdp.yaml +++ b/Documentation/devicetree/bindings/phy/phy-rockchip-usbdp.yaml @@ -110,10 +110,34 @@ properties: port: $ref: /schemas/graph.yaml#/properties/port + deprecated: true description: A port node to link the PHY to a TypeC controller for the purpose of handling orientation switching. + ports: + $ref: /schemas/graph.yaml#/properties/ports + properties: + port@0: + $ref: /schemas/graph.yaml#/properties/port + description: + Output endpoint of the PHY for USB (or DP when configured into 4 lane + mode), which should point to the superspeed port of a USB connector. + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: Incoming endpoint from the USB controller + + port@2: + $ref: /schemas/graph.yaml#/properties/port + description: Incoming endpoint from the DisplayPort controller + + port@3: + $ref: /schemas/graph.yaml#/properties/port + description: + Output endpoint of the PHY for DP Auxiliary, which should either point to + the SBU port of a USB-C connector or a DisplayPort connector input port. + required: - compatible - reg From 3f2181226d7708349c69cd4a4b21372a8049576b Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 15 Sep 2026 15:49:49 +0200 Subject: [PATCH 042/114] phy: rockchip: usbdp: Update mode_change after error handling If rk_udphy_init() or rk_udphy_setup() fails, the reinit will not be tried again. Fix this by only updating the variable after all potential errors have been handled. Note, that no errors have been seen on real hardware and failures would most likely be fatal and require at least a full reboot as the function already asserts the PHY reset lines. So this is more of a theoretical issue. Fixes: 2f70bbddeb45 ("phy: rockchip: add usbdp combo phy driver") Reported-by: Sashiko Closes: https://lore.kernel.org/linux-phy/20260612163835.8D5471F000E9@smtp.kernel.org/ Tested-by: Igor Paunovic # Orange Pi 5 Plus Signed-off-by: Sebastian Reichel Reviewed-by: Manivannan Sadhasivam Link: https://lore.kernel.org/linux-phy/20260612163835.8D5471F000E9@smtp.kernel.org/ Link: https://patch.msgid.link/20260915-rockchip-usbdp-cleanup-v15-2-80e2c11d1ce8@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 07a839e41db7..13649b52e99b 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -999,15 +999,14 @@ static int rk_udphy_power_on(struct rk_udphy *udphy, u8 mode) } if (udphy->status == UDPHY_MODE_NONE) { - udphy->mode_change = false; ret = rk_udphy_setup(udphy); if (ret) return ret; if (udphy->mode & UDPHY_MODE_USB) rk_udphy_u3_port_disable(udphy, false); - } else if (udphy->mode_change) { udphy->mode_change = false; + } else if (udphy->mode_change) { udphy->status = UDPHY_MODE_NONE; if (udphy->mode == UDPHY_MODE_DP) rk_udphy_u3_port_disable(udphy, true); @@ -1016,6 +1015,7 @@ static int rk_udphy_power_on(struct rk_udphy *udphy, u8 mode) ret = rk_udphy_setup(udphy); if (ret) return ret; + udphy->mode_change = false; } udphy->status |= mode; From b322403c294b67fd6bcf6719b4fc9f8a9e313129 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 15 Sep 2026 15:49:50 +0200 Subject: [PATCH 043/114] phy: rockchip: usbdp: Do not lose USB3 PHY status By default (i.e. without manually enabling runtime PM) DWC3 requests the USB3 PHY once and keeps it enabled all the time. When DisplayPort is being requested later on, a mode change is needed. This re-initializes the PHY. During re-initialization the status variable has incorrectly been cleared, which means the tracking information for USB3 is lost. This is not an immediate problem, since the DP side keeps the PHY enabled. But once DP is toggled off, the whole PHY will be disabled. This is a problem, because the USB side still needs it powered. Fix things by not clearing the status flags. Fixes: 2f70bbddeb45 ("phy: rockchip: add usbdp combo phy driver") Tested-by: Igor Paunovic # Orange Pi 5 Plus Signed-off-by: Sebastian Reichel Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260915-rockchip-usbdp-cleanup-v15-3-80e2c11d1ce8@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 13649b52e99b..da0fa75fc9f0 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -1007,7 +1007,6 @@ static int rk_udphy_power_on(struct rk_udphy *udphy, u8 mode) rk_udphy_u3_port_disable(udphy, false); udphy->mode_change = false; } else if (udphy->mode_change) { - udphy->status = UDPHY_MODE_NONE; if (udphy->mode == UDPHY_MODE_DP) rk_udphy_u3_port_disable(udphy, true); From a2ddfc60a7d3ba0f571f037371b261642f8ed208 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 15 Sep 2026 15:49:51 +0200 Subject: [PATCH 044/114] phy: rockchip: usbdp: Fix devm_clk_bulk_get_all check If devm_clk_bulk_get_all() returns -EPROBE_DEFER, it is replaced with -ENODEV, permanently failing the driver probe instead of allowing it to defer. Avoid masking the error code to fix the issue. This effectively drops returning -ENODEV in case no clocks are being described in DT. This special case will now be handled by the follow-up check searching for "refclk" and exit with -EINVAL. None of this will be hit in practice, since the driver is only used by RK3588 and RK3576 - on these platforms the DT is validated to contain the clocks and the clock driver is force probed early. Thus there is no need to backport this. Fixes: 2f70bbddeb45 ("phy: rockchip: add usbdp combo phy driver") Reported-by: Sashiko Closes: https://lore.kernel.org/linux-phy/20260612164107.C7DB21F000E9@smtp.kernel.org/ Tested-by: Igor Paunovic # Orange Pi 5 Plus Signed-off-by: Sebastian Reichel Reviewed-by: Manivannan Sadhasivam Link: https://lore.kernel.org/linux-phy/20260612164107.C7DB21F000E9@smtp.kernel.org/ Link: https://patch.msgid.link/20260915-rockchip-usbdp-cleanup-v15-4-80e2c11d1ce8@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index da0fa75fc9f0..c6cda0b80bfa 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -426,8 +426,8 @@ static int rk_udphy_clk_init(struct rk_udphy *udphy, struct device *dev) int i; udphy->num_clks = devm_clk_bulk_get_all(dev, &udphy->clks); - if (udphy->num_clks < 1) - return -ENODEV; + if (udphy->num_clks < 0) + return udphy->num_clks; /* used for configure phy reference clock frequency */ for (i = 0; i < udphy->num_clks; i++) { From 17cd5e026a6e806b811f915b8e02a8b9d14f6f41 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 15 Sep 2026 15:49:52 +0200 Subject: [PATCH 045/114] phy: rockchip: usbdp: Handle missing clock-names DT property gracefully The rk_udphy_clk_init() function would currently try to do a strncmp for a NULL pointer, if DT specifies 'clocks' property, but no 'clock-names' property. Fix this by making sure the clock has an id string set. Note that DT binding requires setting clock-names, so this is only a problem when booting a non-compliant device tree. Fixes: 2f70bbddeb45 ("phy: rockchip: add usbdp combo phy driver") Reported-by: Sashiko Closes: https://sashiko.dev/#/message/20260619154349.071321F000E9%40smtp.kernel.org Tested-by: Igor Paunovic # Orange Pi 5 Plus Signed-off-by: Sebastian Reichel Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260915-rockchip-usbdp-cleanup-v15-5-80e2c11d1ce8@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index c6cda0b80bfa..93364728bca4 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -431,6 +431,9 @@ static int rk_udphy_clk_init(struct rk_udphy *udphy, struct device *dev) /* used for configure phy reference clock frequency */ for (i = 0; i < udphy->num_clks; i++) { + if (!udphy->clks[i].id) + continue; + if (!strncmp(udphy->clks[i].id, "refclk", 6)) { udphy->refclk = udphy->clks[i].clk; break; From 1ab628ce8f3ab694c28dcfcd5b1eec0cf9f499b8 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 15 Sep 2026 15:49:53 +0200 Subject: [PATCH 046/114] phy: rockchip: usbdp: Drop seamless DP takeover Right now the DRM drivers do not support seamless DP takeover and I'm I'm not aware of any bootloader implementing this feature either. In any case this feature would be limited to boards using the USBDP PHY for a DP or eDP connection instead of the more commonly USB-C connector. With USB-C's DP AltMode a seamless DP takeover requires handing over the state of the TCPM state machine from the bootloader to the kernel. This in turn requires a huge amount of work to keep the state machine implementations synchronized. It's very unlikely we will see somebody implementing that in the foreseeable future. As the current code is obviously buggy and untested, let's simply drop support for seamless DP takeover. It can be re-implemented cleanly once somebody adds all missing bits. Fixes: 2f70bbddeb45 ("phy: rockchip: add usbdp combo phy driver") Reported-by: Sashiko Closes: https://lore.kernel.org/linux-phy/20260612164107.C7DB21F000E9@smtp.kernel.org/ Tested-by: Igor Paunovic # Orange Pi 5 Plus Signed-off-by: Sebastian Reichel Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260915-rockchip-usbdp-cleanup-v15-6-80e2c11d1ce8@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 31 ----------------------- 1 file changed, 31 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 93364728bca4..ce2b74a28801 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -451,11 +451,6 @@ static int rk_udphy_reset_assert_all(struct rk_udphy *udphy) return reset_control_bulk_assert(udphy->num_rsts, udphy->rsts); } -static int rk_udphy_reset_deassert_all(struct rk_udphy *udphy) -{ - return reset_control_bulk_deassert(udphy->num_rsts, udphy->rsts); -} - static int rk_udphy_reset_deassert(struct rk_udphy *udphy, char *name) { struct reset_control_bulk_data *list = udphy->rsts; @@ -923,28 +918,6 @@ static int rk_udphy_parse_lane_mux_data(struct rk_udphy *udphy) return 0; } -static int rk_udphy_get_initial_status(struct rk_udphy *udphy) -{ - int ret; - u32 value; - - ret = clk_bulk_prepare_enable(udphy->num_clks, udphy->clks); - if (ret) { - dev_err(udphy->dev, "failed to enable clk\n"); - return ret; - } - - rk_udphy_reset_deassert_all(udphy); - - regmap_read(udphy->pma_regmap, CMN_LANE_MUX_AND_EN_OFFSET, &value); - if (FIELD_GET(CMN_DP_LANE_MUX_ALL, value) && FIELD_GET(CMN_DP_LANE_EN_ALL, value)) - udphy->status = UDPHY_MODE_DP; - else - rk_udphy_disable(udphy); - - return 0; -} - static int rk_udphy_parse_dt(struct rk_udphy *udphy) { struct device *dev = udphy->dev; @@ -1494,10 +1467,6 @@ static int rk_udphy_probe(struct platform_device *pdev) if (ret) return ret; - ret = rk_udphy_get_initial_status(udphy); - if (ret) - return ret; - mutex_init(&udphy->mutex); platform_set_drvdata(pdev, udphy); From 31fadcc97c033daa0b663dbd6e6acdf3121f1975 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 15 Sep 2026 15:49:54 +0200 Subject: [PATCH 047/114] phy: rockchip: usbdp: Keep clocks running on PHY re-init When a mode change is required rk_udphy_power_on() disables the clocks and then calls rk_udphy_setup(), which then enables all the clocks again before continuing with rk_udphy_init(). Considering that rk_udphy_init() does assert the reset lines, re-enabling the clocks is just delaying things. Avoid it by directly calling rk_udphy_init(). Reviewed-by: Neil Armstrong Tested-by: Igor Paunovic # Orange Pi 5 Plus Signed-off-by: Sebastian Reichel Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260915-rockchip-usbdp-cleanup-v15-7-80e2c11d1ce8@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index ce2b74a28801..925f2d162212 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -986,8 +986,7 @@ static int rk_udphy_power_on(struct rk_udphy *udphy, u8 mode) if (udphy->mode == UDPHY_MODE_DP) rk_udphy_u3_port_disable(udphy, true); - rk_udphy_disable(udphy); - ret = rk_udphy_setup(udphy); + ret = rk_udphy_init(udphy); if (ret) return ret; udphy->mode_change = false; From 6bee51c8b7dcef4917ed9acd7a8903f263374d1b Mon Sep 17 00:00:00 2001 From: Frank Wang Date: Tue, 15 Sep 2026 15:49:55 +0200 Subject: [PATCH 048/114] phy: rockchip: usbdp: Amend SSC modulation deviation Move SSC modulation deviation into private config of clock - 24M: 0x00d4[5:0] = 0x30 - 26M: 0x00d4[5:0] = 0x33 [Taken over from rockchip's kernel tree; register 0x00d4 is not described in the TRM] Signed-off-by: Frank Wang Reviewed-by: Neil Armstrong Tested-by: Igor Paunovic # Orange Pi 5 Plus Signed-off-by: Sebastian Reichel Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260915-rockchip-usbdp-cleanup-v15-8-80e2c11d1ce8@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 925f2d162212..586363f5ca20 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -349,7 +349,8 @@ static const struct reg_sequence rk_udphy_24m_refclk_cfg[] = { {0x0a64, 0xa8}, {0x1a3c, 0xd0}, {0x1a44, 0xd0}, {0x1a48, 0x01}, {0x1a4c, 0x0d}, {0x1a54, 0xe0}, - {0x1a5c, 0xe0}, {0x1a64, 0xa8} + {0x1a5c, 0xe0}, {0x1a64, 0xa8}, + {0x00d4, 0x30} }; static const struct reg_sequence rk_udphy_26m_refclk_cfg[] = { @@ -376,7 +377,7 @@ static const struct reg_sequence rk_udphy_26m_refclk_cfg[] = { {0x0c30, 0x0e}, {0x0c48, 0x06}, {0x1c30, 0x0e}, {0x1c48, 0x06}, {0x028c, 0x18}, {0x0af0, 0x00}, - {0x1af0, 0x00} + {0x1af0, 0x00}, {0x00d4, 0x33} }; static const struct reg_sequence rk_udphy_init_sequence[] = { @@ -411,8 +412,7 @@ static const struct reg_sequence rk_udphy_init_sequence[] = { {0x0070, 0x7d}, {0x0074, 0x68}, {0x0af4, 0x1a}, {0x1af4, 0x1a}, {0x0440, 0x3f}, {0x10d4, 0x08}, - {0x20d4, 0x08}, {0x00d4, 0x30}, - {0x0024, 0x6e}, + {0x20d4, 0x08}, {0x0024, 0x6e} }; static inline int rk_udphy_grfreg_write(struct regmap *base, From 6977ac261a62399c696e5543519e3074de513eab Mon Sep 17 00:00:00 2001 From: William Wu Date: Tue, 15 Sep 2026 15:49:56 +0200 Subject: [PATCH 049/114] phy: rockchip: usbdp: Fix LFPS detect threshold control According to the LFPS Tx Low Power/LFPS Rx Detect Threshold [1], the device under test(DUT) must not respond if LFPS below the minimum LFPS Rx Detect Threshold 100mV. Test fail on Rockchip platforms, because the default LFPS detect threshold is set to 65mV. The USBDP PHY LFPS detect threshold voltage could be set to 30mV ~ 140mV, and since there could be 10-20% PVT variation, we set LFPS detect threshold voltage to 110mV. [1] https://compliance.usb.org/resources/LFPS_Rx_Tx_Low_Power_Compliance_Update_Rev5.pdf [Taken over from rockchip's kernel tree; the registers are not described in the TRM] Signed-off-by: William Wu Reviewed-by: Neil Armstrong Tested-by: Igor Paunovic # Orange Pi 5 Plus Signed-off-by: Sebastian Reichel Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260915-rockchip-usbdp-cleanup-v15-9-80e2c11d1ce8@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 586363f5ca20..665a4636d9dc 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -412,7 +412,8 @@ static const struct reg_sequence rk_udphy_init_sequence[] = { {0x0070, 0x7d}, {0x0074, 0x68}, {0x0af4, 0x1a}, {0x1af4, 0x1a}, {0x0440, 0x3f}, {0x10d4, 0x08}, - {0x20d4, 0x08}, {0x0024, 0x6e} + {0x20d4, 0x08}, {0x0024, 0x6e}, + {0x09c0, 0x0a}, {0x19c0, 0x0a} }; static inline int rk_udphy_grfreg_write(struct regmap *base, From 5def5d61ea5ac6f391364f6fdcff13a27a52026e Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 15 Sep 2026 15:49:57 +0200 Subject: [PATCH 050/114] phy: rockchip: usbdp: Add missing mode_change update rk_udphy_set_typec_default_mapping() updates the available modes, but does not set the mode_change as required. This results in missing re-initialization and thus non-working DisplayPort. Fix this issue by introducing a new helper to update the available modes. Reviewed-by: Neil Armstrong Tested-by: Igor Paunovic # Orange Pi 5 Plus Signed-off-by: Sebastian Reichel Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260915-rockchip-usbdp-cleanup-v15-10-80e2c11d1ce8@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 665a4636d9dc..b72daf80af15 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -616,6 +616,15 @@ static void rk_udphy_dp_hpd_event_trigger(struct rk_udphy *udphy, bool hpd) rk_udphy_grfreg_write(udphy->vogrf, &cfg->vogrfcfg[udphy->id].hpd_trigger, hpd); } +static void rk_udphy_mode_set(struct rk_udphy *udphy, u8 mode) +{ + if (udphy->mode == mode) + return; + + udphy->mode_change = true; + udphy->mode = mode; +} + static void rk_udphy_set_typec_default_mapping(struct rk_udphy *udphy) { if (udphy->flip) { @@ -646,7 +655,7 @@ static void rk_udphy_set_typec_default_mapping(struct rk_udphy *udphy) gpiod_set_value_cansleep(udphy->sbu2_dc_gpio, 1); } - udphy->mode = UDPHY_MODE_DP_USB; + rk_udphy_mode_set(udphy, UDPHY_MODE_DP_USB); } static int rk_udphy_orien_sw_set(struct typec_switch_dev *sw, @@ -1360,10 +1369,7 @@ static int rk_udphy_typec_mux_set(struct typec_mux_dev *mux, usleep_range(750, 800); rk_udphy_dp_hpd_event_trigger(udphy, true); } else if (data->status & DP_STATUS_HPD_STATE) { - if (udphy->mode != mode) { - udphy->mode = mode; - udphy->mode_change = true; - } + rk_udphy_mode_set(udphy, mode); rk_udphy_dp_hpd_event_trigger(udphy, true); } else { rk_udphy_dp_hpd_event_trigger(udphy, false); From ae9b60ef1d626d1a4fdf8854f66d58f6ac56e37d Mon Sep 17 00:00:00 2001 From: Zhang Yubing Date: Tue, 15 Sep 2026 15:49:58 +0200 Subject: [PATCH 051/114] phy: rockchip: usbdp: Support single-lane DP Implement support for using just a single DisplayPort line. Signed-off-by: Zhang Yubing Tested-by: Igor Paunovic # Orange Pi 5 Plus Signed-off-by: Sebastian Reichel Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260915-rockchip-usbdp-cleanup-v15-11-80e2c11d1ce8@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 64 ++++++++++------------- 1 file changed, 27 insertions(+), 37 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index b72daf80af15..4fce9cd66473 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -192,6 +192,7 @@ struct rk_udphy { int id; bool dp_in_use; + int dp_lanes; /* PHY const config */ const struct rk_udphy_cfg *cfgs; @@ -534,6 +535,13 @@ static void rk_udphy_usb_bvalid_enable(struct rk_udphy *udphy, u8 enable) * <0 1> dpln0 dpln1 usbrx usbtx * <2 3> usbrx usbtx dpln0 dpln1 * --------------------------------------------------------------------------- + * if 1 lane for dp function, 2 lane for usb function, define rockchip,dp-lane-mux = ; + * sample as follow: + * --------------------------------------------------------------------------- + * B11-B10 A2-A3 A11-A10 B2-B3 + * rockchip,dp-lane-mux ln0(tx/rx) ln1(tx) ln2(tx/rx) ln3(tx) + * <0> dpln0 \ usbrx usbtx + * --------------------------------------------------------------------------- */ static void rk_udphy_dplane_select(struct rk_udphy *udphy) @@ -541,18 +549,18 @@ static void rk_udphy_dplane_select(struct rk_udphy *udphy) const struct rk_udphy_cfg *cfg = udphy->cfgs; u32 value = 0; - switch (udphy->mode) { - case UDPHY_MODE_DP: - value |= 2 << udphy->dp_lane_sel[2] * 2; + switch (udphy->dp_lanes) { + case 4: value |= 3 << udphy->dp_lane_sel[3] * 2; + value |= 2 << udphy->dp_lane_sel[2] * 2; fallthrough; - case UDPHY_MODE_DP_USB: - value |= 0 << udphy->dp_lane_sel[0] * 2; + case 2: value |= 1 << udphy->dp_lane_sel[1] * 2; - break; + fallthrough; - case UDPHY_MODE_USB: + case 1: + value |= 0 << udphy->dp_lane_sel[0] * 2; break; default: @@ -565,28 +573,6 @@ static void rk_udphy_dplane_select(struct rk_udphy *udphy) FIELD_PREP(DP_AUX_DOUT_SEL, udphy->dp_aux_dout_sel) | value); } -static int rk_udphy_dplane_get(struct rk_udphy *udphy) -{ - int dp_lanes; - - switch (udphy->mode) { - case UDPHY_MODE_DP: - dp_lanes = 4; - break; - - case UDPHY_MODE_DP_USB: - dp_lanes = 2; - break; - - case UDPHY_MODE_USB: - default: - dp_lanes = 0; - break; - } - - return dp_lanes; -} - static void rk_udphy_dplane_enable(struct rk_udphy *udphy, int dp_lanes) { u32 val = 0; @@ -656,6 +642,7 @@ static void rk_udphy_set_typec_default_mapping(struct rk_udphy *udphy) } rk_udphy_mode_set(udphy, UDPHY_MODE_DP_USB); + udphy->dp_lanes = 2; } static int rk_udphy_orien_sw_set(struct typec_switch_dev *sw, @@ -894,7 +881,7 @@ static int rk_udphy_parse_lane_mux_data(struct rk_udphy *udphy) return 0; } - if (num_lanes != 2 && num_lanes != 4) + if (num_lanes != 1 && num_lanes != 2 && num_lanes != 4) return dev_err_probe(udphy->dev, -EINVAL, "invalid number of lane mux\n"); @@ -920,9 +907,11 @@ static int rk_udphy_parse_lane_mux_data(struct rk_udphy *udphy) } udphy->mode = UDPHY_MODE_DP; - if (num_lanes == 2) { + udphy->dp_lanes = num_lanes; + if (num_lanes == 1 || num_lanes == 2) { udphy->mode |= UDPHY_MODE_USB; - udphy->flip = (udphy->lane_mux_sel[0] == PHY_LANE_MUX_DP); + udphy->flip = (udphy->lane_mux_sel[0] == PHY_LANE_MUX_DP) || + (udphy->lane_mux_sel[1] == PHY_LANE_MUX_DP); } return 0; @@ -1049,18 +1038,17 @@ static int rk_udphy_dp_phy_exit(struct phy *phy) static int rk_udphy_dp_phy_power_on(struct phy *phy) { struct rk_udphy *udphy = phy_get_drvdata(phy); - int ret, dp_lanes; + int ret; mutex_lock(&udphy->mutex); - dp_lanes = rk_udphy_dplane_get(udphy); - phy_set_bus_width(phy, dp_lanes); + phy_set_bus_width(phy, udphy->dp_lanes); ret = rk_udphy_power_on(udphy, UDPHY_MODE_DP); if (ret) goto unlock; - rk_udphy_dplane_enable(udphy, dp_lanes); + rk_udphy_dplane_enable(udphy, udphy->dp_lanes); rk_udphy_dplane_select(udphy); @@ -1340,6 +1328,7 @@ static int rk_udphy_typec_mux_set(struct typec_mux_dev *mux, udphy->lane_mux_sel[2] = PHY_LANE_MUX_DP; udphy->lane_mux_sel[3] = PHY_LANE_MUX_DP; mode = UDPHY_MODE_DP; + udphy->dp_lanes = 4; break; case TYPEC_DP_STATE_D: @@ -1356,6 +1345,7 @@ static int rk_udphy_typec_mux_set(struct typec_mux_dev *mux, udphy->lane_mux_sel[3] = PHY_LANE_MUX_DP; } mode = UDPHY_MODE_DP_USB; + udphy->dp_lanes = 2; break; } @@ -1500,7 +1490,7 @@ static int rk_udphy_probe(struct platform_device *pdev) ret = PTR_ERR(udphy->phy_dp); return dev_err_probe(dev, ret, "failed to create DP phy\n"); } - phy_set_bus_width(udphy->phy_dp, rk_udphy_dplane_get(udphy)); + phy_set_bus_width(udphy->phy_dp, udphy->dp_lanes); udphy->phy_dp->attrs.max_link_rate = 8100; phy_set_drvdata(udphy->phy_dp, udphy); From 443997ca8d797375ff35e8f3ad3828799e88f71b Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 15 Sep 2026 15:49:59 +0200 Subject: [PATCH 052/114] phy: rockchip: usbdp: Limit DP lane count to muxed lanes In theory the DP controller could request 4 lanes when the PHY is restricted to 2 lanes as the other half is used by USB3. With the current user (DW-DP) this cannot happen, but as the check is cheap and users might change in the future protect things accordingly. Not doing so would corrupt USB3 usage by the following code configuring the voltages. Fixes: 2f70bbddeb45 ("phy: rockchip: add usbdp combo phy driver") Reported-by: Sashiko Closes: https://lore.kernel.org/linux-phy/20260612165546.98E1F1F000E9@smtp.kernel.org/ Tested-by: Igor Paunovic # Orange Pi 5 Plus Signed-off-by: Sebastian Reichel Reviewed-by: Manivannan Sadhasivam Link: https://lore.kernel.org/linux-phy/20260612165546.98E1F1F000E9@smtp.kernel.org/ Link: https://patch.msgid.link/20260915-rockchip-usbdp-cleanup-v15-12-80e2c11d1ce8@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 4fce9cd66473..2af7c3878714 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -1098,6 +1098,9 @@ static int rk_udphy_dp_phy_verify_link_rate(struct rk_udphy *udphy, static int rk_udphy_dp_phy_verify_lanes(struct rk_udphy *udphy, struct phy_configure_opts_dp *dp) { + if (dp->lanes > udphy->dp_lanes) + return -EINVAL; + switch (dp->lanes) { case 1: case 2: From ebe1e8cf3d109302c749591f6bb717cd260b00cf Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 15 Sep 2026 15:50:00 +0200 Subject: [PATCH 053/114] phy: rockchip: usbdp: Rename DP lane functions The common prefix for DisplayPort related functions is rk_udphy_dp_ (with a final _), so update the two DP lane functions to follow that scheme. Reviewed-by: Neil Armstrong Tested-by: Igor Paunovic # Orange Pi 5 Plus Signed-off-by: Sebastian Reichel Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260915-rockchip-usbdp-cleanup-v15-13-80e2c11d1ce8@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 2af7c3878714..9dae41239e48 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -544,7 +544,7 @@ static void rk_udphy_usb_bvalid_enable(struct rk_udphy *udphy, u8 enable) * --------------------------------------------------------------------------- */ -static void rk_udphy_dplane_select(struct rk_udphy *udphy) +static void rk_udphy_dp_lane_select(struct rk_udphy *udphy) { const struct rk_udphy_cfg *cfg = udphy->cfgs; u32 value = 0; @@ -573,7 +573,7 @@ static void rk_udphy_dplane_select(struct rk_udphy *udphy) FIELD_PREP(DP_AUX_DOUT_SEL, udphy->dp_aux_dout_sel) | value); } -static void rk_udphy_dplane_enable(struct rk_udphy *udphy, int dp_lanes) +static void rk_udphy_dp_lane_enable(struct rk_udphy *udphy, int dp_lanes) { u32 val = 0; int i; @@ -1048,9 +1048,9 @@ static int rk_udphy_dp_phy_power_on(struct phy *phy) if (ret) goto unlock; - rk_udphy_dplane_enable(udphy, udphy->dp_lanes); + rk_udphy_dp_lane_enable(udphy, udphy->dp_lanes); - rk_udphy_dplane_select(udphy); + rk_udphy_dp_lane_select(udphy); unlock: mutex_unlock(&udphy->mutex); @@ -1068,7 +1068,7 @@ static int rk_udphy_dp_phy_power_off(struct phy *phy) struct rk_udphy *udphy = phy_get_drvdata(phy); mutex_lock(&udphy->mutex); - rk_udphy_dplane_enable(udphy, 0); + rk_udphy_dp_lane_enable(udphy, 0); rk_udphy_power_off(udphy, UDPHY_MODE_DP); mutex_unlock(&udphy->mutex); From e8e7f3d875a302032d8b47b52b7adef16082e7f9 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 15 Sep 2026 15:50:01 +0200 Subject: [PATCH 054/114] phy: rockchip: usbdp: Use FIELD_PREP_WM16_CONST Cleanup code by replacing open-coded version of FIELD_PREP_WM16_CONST with the existing helper macro. Reviewed-by: Neil Armstrong Tested-by: Igor Paunovic # Orange Pi 5 Plus Signed-off-by: Sebastian Reichel Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260915-rockchip-usbdp-cleanup-v15-14-80e2c11d1ce8@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 9dae41239e48..d95a44835caf 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -74,7 +75,6 @@ #define TRSV_LN2_MON_RX_CDR_DONE_OFFSET 0x1b84 /* trsv_reg06E1 */ #define TRSV_LN2_MON_RX_CDR_LOCK_DONE BIT(0) -#define BIT_WRITEABLE_SHIFT 16 #define PHY_AUX_DP_DATA_POL_NORMAL 0 #define PHY_AUX_DP_DATA_POL_INVERT 1 #define PHY_LANE_MUX_USB 0 @@ -103,8 +103,8 @@ struct rk_udphy_grf_reg { #define _RK_UDPHY_GEN_GRF_REG(offset, mask, disable, enable) \ {\ offset, \ - FIELD_PREP_CONST(mask, disable) | (mask << BIT_WRITEABLE_SHIFT), \ - FIELD_PREP_CONST(mask, enable) | (mask << BIT_WRITEABLE_SHIFT), \ + FIELD_PREP_WM16_CONST(mask, disable), \ + FIELD_PREP_WM16_CONST(mask, enable), \ } #define RK_UDPHY_GEN_GRF_REG(offset, bitend, bitstart, disable, enable) \ From 9235d03d5e520178d53f5098068f2c3926457465 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 15 Sep 2026 15:50:02 +0200 Subject: [PATCH 055/114] phy: rockchip: usbdp: Cleanup DP lane selection function Use FIELD_PREP_WM16() helpers to simplify the DP lane selection logic. Reviewed-by: Neil Armstrong Tested-by: Igor Paunovic # Orange Pi 5 Plus Signed-off-by: Sebastian Reichel Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260915-rockchip-usbdp-cleanup-v15-15-80e2c11d1ce8@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 28 ++++++----------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index d95a44835caf..8874f8429a67 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -547,30 +547,16 @@ static void rk_udphy_usb_bvalid_enable(struct rk_udphy *udphy, u8 enable) static void rk_udphy_dp_lane_select(struct rk_udphy *udphy) { const struct rk_udphy_cfg *cfg = udphy->cfgs; - u32 value = 0; + u32 value = FIELD_PREP_WM16(DP_LANE_SEL_ALL, 0); + int i; - switch (udphy->dp_lanes) { - case 4: - value |= 3 << udphy->dp_lane_sel[3] * 2; - value |= 2 << udphy->dp_lane_sel[2] * 2; - fallthrough; + for (i = 0; i < udphy->dp_lanes; i++) + value |= field_prep(DP_LANE_SEL_N(udphy->dp_lane_sel[i]), i); - case 2: - value |= 1 << udphy->dp_lane_sel[1] * 2; - fallthrough; + value |= FIELD_PREP_WM16(DP_AUX_DIN_SEL, udphy->dp_aux_din_sel); + value |= FIELD_PREP_WM16(DP_AUX_DOUT_SEL, udphy->dp_aux_dout_sel); - case 1: - value |= 0 << udphy->dp_lane_sel[0] * 2; - break; - - default: - break; - } - - regmap_write(udphy->vogrf, cfg->vogrfcfg[udphy->id].dp_lane_reg, - ((DP_AUX_DIN_SEL | DP_AUX_DOUT_SEL | DP_LANE_SEL_ALL) << 16) | - FIELD_PREP(DP_AUX_DIN_SEL, udphy->dp_aux_din_sel) | - FIELD_PREP(DP_AUX_DOUT_SEL, udphy->dp_aux_dout_sel) | value); + regmap_write(udphy->vogrf, cfg->vogrfcfg[udphy->id].dp_lane_reg, value); } static void rk_udphy_dp_lane_enable(struct rk_udphy *udphy, int dp_lanes) From bcdb77332fe05d699e8351263d535ffca373e459 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 15 Sep 2026 15:50:03 +0200 Subject: [PATCH 056/114] phy: rockchip: usbdp: Register DP aux bridge Add support to use USB-C connectors with the DP altmode helper code on devicetree based platforms. To get this working there must be a DRM bridge chain from the DisplayPort controller to the USB-C connector. E.g. on Rockchip RK3576: root@rk3576 # cat /sys/kernel/debug/dri/0/encoder-0/bridges bridge[0]: dw_dp_bridge_funcs refcount: 7 type: [10] DP OF: /soc/dp@27e40000:rockchip,rk3576-dp ops: [0x47] detect edid hpd bridge[1]: drm_aux_bridge_funcs refcount: 4 type: [0] Unknown OF: /soc/phy@2b010000:rockchip,rk3576-usbdp-phy ops: [0x0] bridge[2]: drm_aux_hpd_bridge_funcs refcount: 5 type: [10] DP OF: /soc/i2c@2ac50000/typec-portc@22/connector:usb-c-connector ops: [0x4] hpd Reviewed-by: Neil Armstrong Tested-by: Igor Paunovic # Orange Pi 5 Plus Signed-off-by: Sebastian Reichel Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260915-rockchip-usbdp-cleanup-v15-16-80e2c11d1ce8@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/Kconfig | 2 ++ drivers/phy/rockchip/phy-rockchip-usbdp.c | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/drivers/phy/rockchip/Kconfig b/drivers/phy/rockchip/Kconfig index 14698571b607..39759bb2fa1d 100644 --- a/drivers/phy/rockchip/Kconfig +++ b/drivers/phy/rockchip/Kconfig @@ -136,8 +136,10 @@ config PHY_ROCKCHIP_USBDP tristate "Rockchip USBDP COMBO PHY Driver" depends on ARCH_ROCKCHIP && OF depends on TYPEC + depends on DRM || DRM=n select GENERIC_PHY select USB_COMMON + select DRM_AUX_BRIDGE if DRM_BRIDGE help Enable this to support the Rockchip USB3.0/DP combo PHY with Samsung IP block. This is required for USB3 support on RK3588. diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 8874f8429a67..fa8324879a97 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -6,6 +6,7 @@ * Copyright (C) 2024 Collabora Ltd */ +#include #include #include #include @@ -1413,6 +1414,7 @@ static int rk_udphy_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct phy_provider *phy_provider; + struct fwnode_handle *dp_aux_ep; struct resource *res; struct rk_udphy *udphy; void __iomem *base; @@ -1467,6 +1469,21 @@ static int rk_udphy_probe(struct platform_device *pdev) return ret; } + /* + * Only register the DRM bridge, if the DP aux channel is connected. + * Some boards use the USBDP PHY only for its USB3 capabilities. The + * aux bridge itself will be registered using port 0, endpoint 0, which + * is fine as that is the actual superspeed data connection shared by + * USB3 and DP based on the mux config. + */ + dp_aux_ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 3, 0, 0); + if (dp_aux_ep) { + ret = drm_aux_bridge_register(dev); + fwnode_handle_put(dp_aux_ep); + if (ret) + return ret; + } + udphy->phy_u3 = devm_phy_create(dev, dev->of_node, &rk_udphy_usb3_phy_ops); if (IS_ERR(udphy->phy_u3)) { ret = PTR_ERR(udphy->phy_u3); From 4fd2c13cbb11f5483a43cc789cb8de1c3bd23238 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 15 Sep 2026 17:06:04 +0200 Subject: [PATCH 057/114] phy: rockchip: usbdp: Drop DP HPD handling Drop the HPD handling logic from the USBDP PHY. The registers involved require the display controller power domain being enabled and thus the HPD signal should be handled by the displayport controller itself. Apart from that the HPD handling as it is done here is incorrect and misses hotplug events happening after the USB-C connector (e.g. when a USB-C to HDMI adapter is involved and the HDMI cable is replugged). Proper USB-C DP HPD support requires some restructuring of the DP controller driver, which will happen independent of this patch. The mainline kernel does not yet support USB-C DP AltMode on RK3588 and RK3576, so it is fine to drop this code without adding the counterpart in the DRM in an atomic change. Tested-by: Igor Paunovic # Orange Pi 5 Plus Signed-off-by: Sebastian Reichel Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260915-rockchip-usbdp-cleanup-part2-v15-1-8d507cb1174f@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 85 +++-------------------- 1 file changed, 9 insertions(+), 76 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index fa8324879a97..03a276c08fa8 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -127,7 +127,6 @@ struct rk_udphy_grf_cfg { struct rk_udphy_vogrf_cfg { /* vo-grf */ - struct rk_udphy_grf_reg hpd_trigger; u32 dp_lane_reg; }; @@ -185,14 +184,11 @@ struct rk_udphy { u32 dp_lane_sel[4]; u32 dp_aux_dout_sel; u32 dp_aux_din_sel; - bool dp_sink_hpd_sel; - bool dp_sink_hpd_cfg; unsigned int link_rate; unsigned int lanes; u8 bw; int id; - bool dp_in_use; int dp_lanes; /* PHY const config */ @@ -576,19 +572,6 @@ static void rk_udphy_dp_lane_enable(struct rk_udphy *udphy, int dp_lanes) CMN_DP_CMN_RSTN, FIELD_PREP(CMN_DP_CMN_RSTN, 0x0)); } -static void rk_udphy_dp_hpd_event_trigger(struct rk_udphy *udphy, bool hpd) -{ - const struct rk_udphy_cfg *cfg = udphy->cfgs; - - udphy->dp_sink_hpd_sel = true; - udphy->dp_sink_hpd_cfg = hpd; - - if (!udphy->dp_in_use) - return; - - rk_udphy_grfreg_write(udphy->vogrf, &cfg->vogrfcfg[udphy->id].hpd_trigger, hpd); -} - static void rk_udphy_mode_set(struct rk_udphy *udphy, u8 mode) { if (udphy->mode == mode) @@ -999,29 +982,6 @@ static void rk_udphy_power_off(struct rk_udphy *udphy, u8 mode) rk_udphy_disable(udphy); } -static int rk_udphy_dp_phy_init(struct phy *phy) -{ - struct rk_udphy *udphy = phy_get_drvdata(phy); - - mutex_lock(&udphy->mutex); - - udphy->dp_in_use = true; - - mutex_unlock(&udphy->mutex); - - return 0; -} - -static int rk_udphy_dp_phy_exit(struct phy *phy) -{ - struct rk_udphy *udphy = phy_get_drvdata(phy); - - mutex_lock(&udphy->mutex); - udphy->dp_in_use = false; - mutex_unlock(&udphy->mutex); - return 0; -} - static int rk_udphy_dp_phy_power_on(struct phy *phy) { struct rk_udphy *udphy = phy_get_drvdata(phy); @@ -1253,8 +1213,6 @@ static int rk_udphy_dp_phy_configure(struct phy *phy, } static const struct phy_ops rk_udphy_dp_phy_ops = { - .init = rk_udphy_dp_phy_init, - .exit = rk_udphy_dp_phy_exit, .power_on = rk_udphy_dp_phy_power_on, .power_off = rk_udphy_dp_phy_power_off, .configure = rk_udphy_dp_phy_configure, @@ -1308,6 +1266,14 @@ static int rk_udphy_typec_mux_set(struct typec_mux_dev *mux, struct rk_udphy *udphy = typec_mux_get_drvdata(mux); u8 mode; + /* + * Ignore mux events not involving DP AltMode, because + * the mode field is being reused, e.g. state->mode == 4 + * could be either TYPEC_MODE_USB4 or TYPEC_DP_STATE_C. + */ + if (!state->alt || state->alt->svid != USB_TYPEC_DP_SID) + return 0; + mutex_lock(&udphy->mutex); switch (state->mode) { @@ -1339,22 +1305,7 @@ static int rk_udphy_typec_mux_set(struct typec_mux_dev *mux, break; } - if (state->alt && state->alt->svid == USB_TYPEC_DP_SID) { - struct typec_displayport_data *data = state->data; - - if (!data) { - rk_udphy_dp_hpd_event_trigger(udphy, false); - } else if (data->status & DP_STATUS_IRQ_HPD) { - rk_udphy_dp_hpd_event_trigger(udphy, false); - usleep_range(750, 800); - rk_udphy_dp_hpd_event_trigger(udphy, true); - } else if (data->status & DP_STATUS_HPD_STATE) { - rk_udphy_mode_set(udphy, mode); - rk_udphy_dp_hpd_event_trigger(udphy, true); - } else { - rk_udphy_dp_hpd_event_trigger(udphy, false); - } - } + rk_udphy_mode_set(udphy, mode); mutex_unlock(&udphy->mutex); return 0; @@ -1509,20 +1460,6 @@ static int rk_udphy_probe(struct platform_device *pdev) return 0; } -static int __maybe_unused rk_udphy_resume(struct device *dev) -{ - struct rk_udphy *udphy = dev_get_drvdata(dev); - - if (udphy->dp_sink_hpd_sel) - rk_udphy_dp_hpd_event_trigger(udphy, udphy->dp_sink_hpd_cfg); - - return 0; -} - -static const struct dev_pm_ops rk_udphy_pm_ops = { - SET_LATE_SYSTEM_SLEEP_PM_OPS(NULL, rk_udphy_resume) -}; - static const char * const rk_udphy_rst_list[] = { "init", "cmn", "lane", "pcs_apb", "pma_apb" }; @@ -1546,7 +1483,6 @@ static const struct rk_udphy_cfg rk3576_udphy_cfgs = { }, .vogrfcfg = { { - .hpd_trigger = RK_UDPHY_GEN_GRF_REG(0x0000, 11, 10, 1, 3), .dp_lane_reg = 0x0000, }, }, @@ -1587,11 +1523,9 @@ static const struct rk_udphy_cfg rk3588_udphy_cfgs = { }, .vogrfcfg = { { - .hpd_trigger = RK_UDPHY_GEN_GRF_REG(0x0000, 11, 10, 1, 3), .dp_lane_reg = 0x0000, }, { - .hpd_trigger = RK_UDPHY_GEN_GRF_REG(0x0008, 11, 10, 1, 3), .dp_lane_reg = 0x0008, }, }, @@ -1627,7 +1561,6 @@ static struct platform_driver rk_udphy_driver = { .driver = { .name = "rockchip-usbdp-phy", .of_match_table = rk_udphy_dt_match, - .pm = &rk_udphy_pm_ops, }, }; module_platform_driver(rk_udphy_driver); From fbce9cc28cadb24026a6c061819d42e50d3bfea0 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 15 Sep 2026 17:06:05 +0200 Subject: [PATCH 058/114] phy: rockchip: usbdp: Rename mode_change to phy_needs_reinit Right now the mode_change property is set whenever the mode changes between USB-only, DP-only and USB-DP. It is needed, because on any mode change the PHY needs to be re-initialized. Apparently at least DP also requires a re-init when the cable orientation is changed, which is currently not being done (except when the orientation switch also involves a mode change). Prepare for this by renaming mode_change to phy_needs_reinit. Tested-by: Igor Paunovic # Orange Pi 5 Plus Signed-off-by: Sebastian Reichel Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260915-rockchip-usbdp-cleanup-part2-v15-2-8d507cb1174f@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 03a276c08fa8..9767fba23660 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -170,7 +170,7 @@ struct rk_udphy { /* PHY status management */ bool flip; - bool mode_change; + bool phy_needs_reinit; u8 mode; u8 status; @@ -577,7 +577,7 @@ static void rk_udphy_mode_set(struct rk_udphy *udphy, u8 mode) if (udphy->mode == mode) return; - udphy->mode_change = true; + udphy->phy_needs_reinit = true; udphy->mode = mode; } @@ -950,15 +950,15 @@ static int rk_udphy_power_on(struct rk_udphy *udphy, u8 mode) if (udphy->mode & UDPHY_MODE_USB) rk_udphy_u3_port_disable(udphy, false); - udphy->mode_change = false; - } else if (udphy->mode_change) { + udphy->phy_needs_reinit = false; + } else if (udphy->phy_needs_reinit) { if (udphy->mode == UDPHY_MODE_DP) rk_udphy_u3_port_disable(udphy, true); ret = rk_udphy_init(udphy); if (ret) return ret; - udphy->mode_change = false; + udphy->phy_needs_reinit = false; } udphy->status |= mode; From 4d8349799c4be92750c0d73c9dfac09003233146 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 15 Sep 2026 17:06:06 +0200 Subject: [PATCH 059/114] phy: rockchip: usbdp: Re-init the PHY on orientation change Changing the cable orientation reconfigures the lane muxing, which requires re-initializing the PHY. Without this DP functionality breaks, if the cable is re-plugged with swapped orientation. Tested-by: Igor Paunovic # Orange Pi 5 Plus Signed-off-by: Sebastian Reichel Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260915-rockchip-usbdp-cleanup-part2-v15-3-8d507cb1174f@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 9767fba23660..9a4ead585151 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -619,6 +619,7 @@ static int rk_udphy_orien_sw_set(struct typec_switch_dev *sw, enum typec_orientation orien) { struct rk_udphy *udphy = typec_switch_get_drvdata(sw); + bool flipped = orien == TYPEC_ORIENTATION_REVERSE; mutex_lock(&udphy->mutex); @@ -630,7 +631,10 @@ static int rk_udphy_orien_sw_set(struct typec_switch_dev *sw, goto unlock_ret; } - udphy->flip = orien == TYPEC_ORIENTATION_REVERSE; + if (udphy->flip != flipped) + udphy->phy_needs_reinit = true; + + udphy->flip = flipped; rk_udphy_set_typec_default_mapping(udphy); rk_udphy_usb_bvalid_enable(udphy, true); From cc492941f8e3523648fb1393e3e61a7602a0260d Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 15 Sep 2026 17:06:07 +0200 Subject: [PATCH 060/114] phy: rockchip: usbdp: Factor out lane_mux_sel setup Avoid describing the USB+DP lane_mux_sel logic twice by introducing a helper function to reduce code duplication. Tested-by: Igor Paunovic # Orange Pi 5 Plus Signed-off-by: Sebastian Reichel Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260915-rockchip-usbdp-cleanup-part2-v15-4-8d507cb1174f@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 81 +++++++++++------------ 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 9a4ead585151..4a073301067f 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -581,6 +581,42 @@ static void rk_udphy_mode_set(struct rk_udphy *udphy, u8 mode) udphy->mode = mode; } +static void rk_udphy_set_typec_state(struct rk_udphy *udphy, unsigned long state) +{ + u8 mode; + + switch (state) { + case TYPEC_DP_STATE_C: + case TYPEC_DP_STATE_E: + udphy->lane_mux_sel[0] = PHY_LANE_MUX_DP; + udphy->lane_mux_sel[1] = PHY_LANE_MUX_DP; + udphy->lane_mux_sel[2] = PHY_LANE_MUX_DP; + udphy->lane_mux_sel[3] = PHY_LANE_MUX_DP; + mode = UDPHY_MODE_DP; + udphy->dp_lanes = 4; + break; + + case TYPEC_DP_STATE_D: + default: + if (udphy->flip) { + udphy->lane_mux_sel[0] = PHY_LANE_MUX_DP; + udphy->lane_mux_sel[1] = PHY_LANE_MUX_DP; + udphy->lane_mux_sel[2] = PHY_LANE_MUX_USB; + udphy->lane_mux_sel[3] = PHY_LANE_MUX_USB; + } else { + udphy->lane_mux_sel[0] = PHY_LANE_MUX_USB; + udphy->lane_mux_sel[1] = PHY_LANE_MUX_USB; + udphy->lane_mux_sel[2] = PHY_LANE_MUX_DP; + udphy->lane_mux_sel[3] = PHY_LANE_MUX_DP; + } + mode = UDPHY_MODE_DP_USB; + udphy->dp_lanes = 2; + break; + } + + rk_udphy_mode_set(udphy, mode); +} + static void rk_udphy_set_typec_default_mapping(struct rk_udphy *udphy) { if (udphy->flip) { @@ -588,10 +624,6 @@ static void rk_udphy_set_typec_default_mapping(struct rk_udphy *udphy) udphy->dp_lane_sel[1] = 1; udphy->dp_lane_sel[2] = 3; udphy->dp_lane_sel[3] = 2; - udphy->lane_mux_sel[0] = PHY_LANE_MUX_DP; - udphy->lane_mux_sel[1] = PHY_LANE_MUX_DP; - udphy->lane_mux_sel[2] = PHY_LANE_MUX_USB; - udphy->lane_mux_sel[3] = PHY_LANE_MUX_USB; udphy->dp_aux_dout_sel = PHY_AUX_DP_DATA_POL_INVERT; udphy->dp_aux_din_sel = PHY_AUX_DP_DATA_POL_INVERT; gpiod_set_value_cansleep(udphy->sbu1_dc_gpio, 1); @@ -601,18 +633,14 @@ static void rk_udphy_set_typec_default_mapping(struct rk_udphy *udphy) udphy->dp_lane_sel[1] = 3; udphy->dp_lane_sel[2] = 1; udphy->dp_lane_sel[3] = 0; - udphy->lane_mux_sel[0] = PHY_LANE_MUX_USB; - udphy->lane_mux_sel[1] = PHY_LANE_MUX_USB; - udphy->lane_mux_sel[2] = PHY_LANE_MUX_DP; - udphy->lane_mux_sel[3] = PHY_LANE_MUX_DP; udphy->dp_aux_dout_sel = PHY_AUX_DP_DATA_POL_NORMAL; udphy->dp_aux_din_sel = PHY_AUX_DP_DATA_POL_NORMAL; gpiod_set_value_cansleep(udphy->sbu1_dc_gpio, 0); gpiod_set_value_cansleep(udphy->sbu2_dc_gpio, 1); } - rk_udphy_mode_set(udphy, UDPHY_MODE_DP_USB); - udphy->dp_lanes = 2; + /* default to USB3 + DP as 4 lane USB is not supported */ + rk_udphy_set_typec_state(udphy, TYPEC_DP_STATE_D); } static int rk_udphy_orien_sw_set(struct typec_switch_dev *sw, @@ -1268,7 +1296,6 @@ static int rk_udphy_typec_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state) { struct rk_udphy *udphy = typec_mux_get_drvdata(mux); - u8 mode; /* * Ignore mux events not involving DP AltMode, because @@ -1280,38 +1307,10 @@ static int rk_udphy_typec_mux_set(struct typec_mux_dev *mux, mutex_lock(&udphy->mutex); - switch (state->mode) { - case TYPEC_DP_STATE_C: - case TYPEC_DP_STATE_E: - udphy->lane_mux_sel[0] = PHY_LANE_MUX_DP; - udphy->lane_mux_sel[1] = PHY_LANE_MUX_DP; - udphy->lane_mux_sel[2] = PHY_LANE_MUX_DP; - udphy->lane_mux_sel[3] = PHY_LANE_MUX_DP; - mode = UDPHY_MODE_DP; - udphy->dp_lanes = 4; - break; - - case TYPEC_DP_STATE_D: - default: - if (udphy->flip) { - udphy->lane_mux_sel[0] = PHY_LANE_MUX_DP; - udphy->lane_mux_sel[1] = PHY_LANE_MUX_DP; - udphy->lane_mux_sel[2] = PHY_LANE_MUX_USB; - udphy->lane_mux_sel[3] = PHY_LANE_MUX_USB; - } else { - udphy->lane_mux_sel[0] = PHY_LANE_MUX_USB; - udphy->lane_mux_sel[1] = PHY_LANE_MUX_USB; - udphy->lane_mux_sel[2] = PHY_LANE_MUX_DP; - udphy->lane_mux_sel[3] = PHY_LANE_MUX_DP; - } - mode = UDPHY_MODE_DP_USB; - udphy->dp_lanes = 2; - break; - } - - rk_udphy_mode_set(udphy, mode); + rk_udphy_set_typec_state(udphy, state->mode); mutex_unlock(&udphy->mutex); + return 0; } From b1ca8de219ae6e4d9eec22acfbe4357d8addd94c Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 15 Sep 2026 17:06:08 +0200 Subject: [PATCH 061/114] phy: rockchip: usbdp: Properly handle TYPEC_STATE_SAFE and TYPEC_STATE_USB Handle TYPEC_STATE_SAFE and TYPEC_STATE_USB Type-C state events, so that the muxing is properly updated when exiting DP AltMode. Fixes: 2f70bbddeb45 ("phy: rockchip: add usbdp combo phy driver") Reported-by: Sashiko Closes: https://sashiko.dev/#/message/20260619155020.CC7361F000E9%40smtp.kernel.org Tested-by: Igor Paunovic # Orange Pi 5 Plus Signed-off-by: Sebastian Reichel Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260915-rockchip-usbdp-cleanup-part2-v15-5-8d507cb1174f@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 4a073301067f..9ba571f7b9bf 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -1292,17 +1292,26 @@ static const struct phy_ops rk_udphy_usb3_phy_ops = { .owner = THIS_MODULE, }; +static bool rk_udphy_is_supported_mode(struct typec_mux_state *state) +{ + /* Handle Safe State and USB State */ + if (state->mode < TYPEC_STATE_MODAL) + return true; + + /* Handle DP AltMode */ + if (state->alt && state->alt->svid == USB_TYPEC_DP_SID) + return true; + + return false; +} + static int rk_udphy_typec_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state) { struct rk_udphy *udphy = typec_mux_get_drvdata(mux); - /* - * Ignore mux events not involving DP AltMode, because - * the mode field is being reused, e.g. state->mode == 4 - * could be either TYPEC_MODE_USB4 or TYPEC_DP_STATE_C. - */ - if (!state->alt || state->alt->svid != USB_TYPEC_DP_SID) + /* Ignore mux events not involving USB or DP */ + if (!rk_udphy_is_supported_mode(state)) return 0; mutex_lock(&udphy->mutex); From f8e38359fde88381c3a7b1134dfd3a406a0e611d Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 15 Sep 2026 17:06:09 +0200 Subject: [PATCH 062/114] phy: rockchip: usbdp: Use guard functions for mutex Convert the driver to use guard functions for mutex handling as a small cleanup. There is a small functional change in the DP PHY power up function, which no longer sleeps if the internal powerup code returns an error. This is not a problem as the sleep is only relevant for successful power-up. Tested-by: Igor Paunovic # Orange Pi 5 Plus Signed-off-by: Sebastian Reichel Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260915-rockchip-usbdp-cleanup-part2-v15-6-8d507cb1174f@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 54 ++++++++++------------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 9ba571f7b9bf..9864024b9c0a 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -649,14 +650,15 @@ static int rk_udphy_orien_sw_set(struct typec_switch_dev *sw, struct rk_udphy *udphy = typec_switch_get_drvdata(sw); bool flipped = orien == TYPEC_ORIENTATION_REVERSE; - mutex_lock(&udphy->mutex); + guard(mutex)(&udphy->mutex); if (orien == TYPEC_ORIENTATION_NONE) { gpiod_set_value_cansleep(udphy->sbu1_dc_gpio, 0); gpiod_set_value_cansleep(udphy->sbu2_dc_gpio, 0); /* unattached */ rk_udphy_usb_bvalid_enable(udphy, false); - goto unlock_ret; + + return 0; } if (udphy->flip != flipped) @@ -666,8 +668,6 @@ static int rk_udphy_orien_sw_set(struct typec_switch_dev *sw, rk_udphy_set_typec_default_mapping(udphy); rk_udphy_usb_bvalid_enable(udphy, true); -unlock_ret: - mutex_unlock(&udphy->mutex); return 0; } @@ -1019,26 +1019,25 @@ static int rk_udphy_dp_phy_power_on(struct phy *phy) struct rk_udphy *udphy = phy_get_drvdata(phy); int ret; - mutex_lock(&udphy->mutex); + scoped_guard(mutex, &udphy->mutex) { + phy_set_bus_width(phy, udphy->dp_lanes); - phy_set_bus_width(phy, udphy->dp_lanes); + ret = rk_udphy_power_on(udphy, UDPHY_MODE_DP); + if (ret) + return ret; - ret = rk_udphy_power_on(udphy, UDPHY_MODE_DP); - if (ret) - goto unlock; + rk_udphy_dp_lane_enable(udphy, udphy->dp_lanes); - rk_udphy_dp_lane_enable(udphy, udphy->dp_lanes); + rk_udphy_dp_lane_select(udphy); + } - rk_udphy_dp_lane_select(udphy); - -unlock: - mutex_unlock(&udphy->mutex); /* * If data send by aux channel too fast after phy power on, * the aux may be not ready which will cause aux error. Adding * delay to avoid this issue. */ usleep_range(10000, 11000); + return ret; } @@ -1046,10 +1045,10 @@ static int rk_udphy_dp_phy_power_off(struct phy *phy) { struct rk_udphy *udphy = phy_get_drvdata(phy); - mutex_lock(&udphy->mutex); + guard(mutex)(&udphy->mutex); + rk_udphy_dp_lane_enable(udphy, 0); rk_udphy_power_off(udphy, UDPHY_MODE_DP); - mutex_unlock(&udphy->mutex); return 0; } @@ -1254,35 +1253,30 @@ static const struct phy_ops rk_udphy_dp_phy_ops = { static int rk_udphy_usb3_phy_init(struct phy *phy) { struct rk_udphy *udphy = phy_get_drvdata(phy); - int ret = 0; - mutex_lock(&udphy->mutex); + guard(mutex)(&udphy->mutex); + /* DP only or high-speed, disable U3 port */ if (!(udphy->mode & UDPHY_MODE_USB) || udphy->hs) { rk_udphy_u3_port_disable(udphy, true); - goto unlock; + return 0; } - ret = rk_udphy_power_on(udphy, UDPHY_MODE_USB); - -unlock: - mutex_unlock(&udphy->mutex); - return ret; + return rk_udphy_power_on(udphy, UDPHY_MODE_USB); } static int rk_udphy_usb3_phy_exit(struct phy *phy) { struct rk_udphy *udphy = phy_get_drvdata(phy); - mutex_lock(&udphy->mutex); + guard(mutex)(&udphy->mutex); + /* DP only or high-speed */ if (!(udphy->mode & UDPHY_MODE_USB) || udphy->hs) - goto unlock; + return 0; rk_udphy_power_off(udphy, UDPHY_MODE_USB); -unlock: - mutex_unlock(&udphy->mutex); return 0; } @@ -1314,12 +1308,10 @@ static int rk_udphy_typec_mux_set(struct typec_mux_dev *mux, if (!rk_udphy_is_supported_mode(state)) return 0; - mutex_lock(&udphy->mutex); + guard(mutex)(&udphy->mutex); rk_udphy_set_typec_state(udphy, state->mode); - mutex_unlock(&udphy->mutex); - return 0; } From 619233ce6b2329529c9f0ff9682d6e22d160ebd6 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 15 Sep 2026 17:06:10 +0200 Subject: [PATCH 063/114] phy: rockchip: usbdp: Hold mutex in DP PHY configure rk_udphy_dp_phy_configure() accesses some variables from the struct rk_udphy, which are updated independently from the USB-C framework. The USB-C mux/orientation switch functions already hold a mutex to ensure mutual exclusive access to the struct rk_udphy states, so simply hold the same one in the DP PHY configuration function. Reproducing problems due to this on real hardware would be really hard, but could be possible when quickly re-connecting the USB-C connector. Fixes: 2f70bbddeb45 ("phy: rockchip: add usbdp combo phy driver") Reported-by: Sashiko Closes: https://lore.kernel.org/linux-phy/20260612164627.23D391F000E9@smtp.kernel.org/ Tested-by: Igor Paunovic # Orange Pi 5 Plus Signed-off-by: Sebastian Reichel Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260915-rockchip-usbdp-cleanup-part2-v15-7-8d507cb1174f@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 9864024b9c0a..2888a0a97088 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -1153,6 +1153,8 @@ static int rk_udphy_dp_phy_configure(struct phy *phy, u32 i, val, lane; int ret; + guard(mutex)(&udphy->mutex); + if (dp->set_rate) { ret = rk_udphy_dp_phy_verify_link_rate(udphy, dp); if (ret) From 89eb5e8aec232ff494a0261f7fada6390bb3f1be Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 15 Sep 2026 17:06:11 +0200 Subject: [PATCH 064/114] phy: rockchip: usbdp: Add some extra debug messages It's useful to log PHY reinit to ease debugging issues around USB-C hotplugging. Tested-by: Igor Paunovic # Orange Pi 5 Plus Signed-off-by: Sebastian Reichel Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260915-rockchip-usbdp-cleanup-part2-v15-8-8d507cb1174f@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 2888a0a97088..23cfb9bbd334 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -461,6 +462,8 @@ static int rk_udphy_reset_deassert(struct rk_udphy *udphy, char *name) return reset_control_deassert(list[idx].rstc); } + dev_err(udphy->dev, "failed to de-assert missing reset line: %s\n", name); + return -EINVAL; } @@ -487,6 +490,8 @@ static void rk_udphy_u3_port_disable(struct rk_udphy *udphy, u8 disable) const struct rk_udphy_cfg *cfg = udphy->cfgs; const struct rk_udphy_grf_reg *preg; + dev_dbg(udphy->dev, "USB3 port %s\n", str_on_off(!disable)); + preg = udphy->id ? &cfg->grfcfg.usb3otg1_cfg : &cfg->grfcfg.usb3otg0_cfg; rk_udphy_grfreg_write(udphy->usbgrf, preg, disable); } @@ -661,8 +666,10 @@ static int rk_udphy_orien_sw_set(struct typec_switch_dev *sw, return 0; } - if (udphy->flip != flipped) + if (udphy->flip != flipped) { + dev_dbg(udphy->dev, "cable orientation changed, PHY re-init required.\n"); udphy->phy_needs_reinit = true; + } udphy->flip = flipped; rk_udphy_set_typec_default_mapping(udphy); @@ -780,6 +787,11 @@ static int rk_udphy_init(struct rk_udphy *udphy) const struct rk_udphy_cfg *cfg = udphy->cfgs; int ret; + dev_dbg(udphy->dev, "reinit PHY with USB3=%s and DP=%s (%u lanes) flipped=%s\n", + str_on_off(udphy->mode & UDPHY_MODE_USB), + str_on_off(udphy->mode & UDPHY_MODE_DP), + udphy->dp_lanes, str_yes_no(udphy->flip)); + rk_udphy_reset_assert_all(udphy); usleep_range(10000, 11000); @@ -850,6 +862,8 @@ static int rk_udphy_setup(struct rk_udphy *udphy) { int ret; + dev_dbg(udphy->dev, "enable PHY\n"); + ret = clk_bulk_prepare_enable(udphy->num_clks, udphy->clks); if (ret) { dev_err(udphy->dev, "failed to enable clk\n"); @@ -868,6 +882,7 @@ static int rk_udphy_setup(struct rk_udphy *udphy) static void rk_udphy_disable(struct rk_udphy *udphy) { + dev_dbg(udphy->dev, "disable PHY\n"); clk_bulk_disable_unprepare(udphy->num_clks, udphy->clks); rk_udphy_reset_assert_all(udphy); } @@ -1307,8 +1322,12 @@ static int rk_udphy_typec_mux_set(struct typec_mux_dev *mux, struct rk_udphy *udphy = typec_mux_get_drvdata(mux); /* Ignore mux events not involving USB or DP */ - if (!rk_udphy_is_supported_mode(state)) + if (!rk_udphy_is_supported_mode(state)) { + dev_dbg(udphy->dev, "ignore mux event with mode=%lu\n", state->mode); return 0; + } + + dev_dbg(udphy->dev, "new mode: %lu\n", state->mode); guard(mutex)(&udphy->mutex); From cf3aa34e6f0798544c26ad93a6c3c2231ab09738 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 15 Sep 2026 17:06:12 +0200 Subject: [PATCH 065/114] phy: rockchip: usbdp: Avoid xHCI SErrors The USBDP PHY provides the PIPE clock to the USB3 controller, which means the PHY must be fully running when anything tries to access the xHCI registers. When switching between USB3-only, USB3 + DP and DP-only mode, the PHY must be re-initialized resulting in a short period of the PHY being disabled. If the DWC3 driver decides to access the xHCI at this point the system will fail with an SError. This patch avoids the problems by disabling the USB3 port before re-initializing it. This does a couple of things: - forces phystatus to 0 from GRF (not from PHY) - switches PIPE clock source from PHY to UTMI (safe fallback clock) - num_u3_port=0 The last part will be ignored, as DWC3 already probed, but the clock re-routing will avoid the SError. There is a small delay afterwards to make sure the mux happened. The datasheet gives no hints how long it takes, so delay time is a guess. Fixes: 2f70bbddeb45 ("phy: rockchip: add usbdp combo phy driver") Tested-by: Igor Paunovic # Orange Pi 5 Plus Signed-off-by: Sebastian Reichel Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260915-rockchip-usbdp-cleanup-part2-v15-9-8d507cb1174f@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 23cfb9bbd334..4116b4b18882 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -999,12 +999,15 @@ static int rk_udphy_power_on(struct rk_udphy *udphy, u8 mode) rk_udphy_u3_port_disable(udphy, false); udphy->phy_needs_reinit = false; } else if (udphy->phy_needs_reinit) { - if (udphy->mode == UDPHY_MODE_DP) - rk_udphy_u3_port_disable(udphy, true); + rk_udphy_u3_port_disable(udphy, true); + udelay(10); ret = rk_udphy_init(udphy); if (ret) return ret; + + if (!udphy->hs && udphy->mode & UDPHY_MODE_USB) + rk_udphy_u3_port_disable(udphy, false); udphy->phy_needs_reinit = false; } From 35101e2f49c04bf1cfc69b8a0d3f85c742369ccc Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 15 Sep 2026 17:06:13 +0200 Subject: [PATCH 066/114] phy: rockchip: usbdp: Handle rk_udphy_reset_deassert errors Handle rk_udphy_reset_deassert returning errors to avoid theoretical (Rockchip reset controller driver does not return errors) SError. Fixes: 2f70bbddeb45 ("phy: rockchip: add usbdp combo phy driver") Reported-by: Sashiko Closes: https://sashiko.dev/#/message/20260626211151.2332F1F000E9%40smtp.kernel.org Tested-by: Igor Paunovic # Orange Pi 5 Plus Signed-off-by: Sebastian Reichel Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260915-rockchip-usbdp-cleanup-part2-v15-10-8d507cb1174f@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 25 +++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 4116b4b18882..9eb8e40f6afb 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -802,8 +802,12 @@ static int rk_udphy_init(struct rk_udphy *udphy) /* Step 1: power on pma and deassert apb rstn */ rk_udphy_grfreg_write(udphy->udphygrf, &cfg->grfcfg.low_pwrn, true); - rk_udphy_reset_deassert(udphy, "pma_apb"); - rk_udphy_reset_deassert(udphy, "pcs_apb"); + ret = rk_udphy_reset_deassert(udphy, "pma_apb"); + if (ret) + goto assert_resets; + ret = rk_udphy_reset_deassert(udphy, "pcs_apb"); + if (ret) + goto assert_resets; /* Step 2: set init sequence and phy refclk */ ret = regmap_multi_reg_write(udphy->pma_regmap, rk_udphy_init_sequence, @@ -829,8 +833,11 @@ static int rk_udphy_init(struct rk_udphy *udphy) FIELD_PREP(CMN_DP_LANE_EN_ALL, 0)); /* Step 4: deassert init rstn and wait for 200ns from datasheet */ - if (udphy->mode & UDPHY_MODE_USB) - rk_udphy_reset_deassert(udphy, "init"); + if (udphy->mode & UDPHY_MODE_USB) { + ret = rk_udphy_reset_deassert(udphy, "init"); + if (ret) + goto assert_resets; + } if (udphy->mode & UDPHY_MODE_DP) { regmap_update_bits(udphy->pma_regmap, CMN_DP_RSTN_OFFSET, @@ -842,8 +849,14 @@ static int rk_udphy_init(struct rk_udphy *udphy) /* Step 5: deassert cmn/lane rstn */ if (udphy->mode & UDPHY_MODE_USB) { - rk_udphy_reset_deassert(udphy, "cmn"); - rk_udphy_reset_deassert(udphy, "lane"); + ret = rk_udphy_reset_deassert(udphy, "cmn"); + if (ret) + goto assert_resets; + + ret = rk_udphy_reset_deassert(udphy, "lane"); + if (ret) + goto assert_resets; + } /* Step 6: wait for lock done of pll */ From fbbf6c85b08008bf68ef49931175edeaa36ba82f Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 15 Sep 2026 17:06:14 +0200 Subject: [PATCH 067/114] phy: rockchip: usbdp: Only enable USB3 when not in high-speed mode Ensure that USB3 mode is not accidently enabled during PHY re-init for systems that are configured as high-speed only via DT. Fixes: 2f70bbddeb45 ("phy: rockchip: add usbdp combo phy driver") Reported-by: Sashiko Closes: https://sashiko.dev/#/message/20260626212424.C215E1F000E9%40smtp.kernel.org Tested-by: Igor Paunovic # Orange Pi 5 Plus Signed-off-by: Sebastian Reichel Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260915-rockchip-usbdp-cleanup-part2-v15-11-8d507cb1174f@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 9eb8e40f6afb..e748acdd2d31 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -1008,7 +1008,7 @@ static int rk_udphy_power_on(struct rk_udphy *udphy, u8 mode) if (ret) return ret; - if (udphy->mode & UDPHY_MODE_USB) + if (!udphy->hs && udphy->mode & UDPHY_MODE_USB) rk_udphy_u3_port_disable(udphy, false); udphy->phy_needs_reinit = false; } else if (udphy->phy_needs_reinit) { From 48afd2d3c85aebe3303b42db30bba2963127be63 Mon Sep 17 00:00:00 2001 From: Nitin Rawat Date: Thu, 17 Sep 2026 13:36:22 +0530 Subject: [PATCH 068/114] dt-bindings: phy: qcom,sc8280xp-qmp-ufs-phy: Add QMP UFS PHY compatible for Maili Document the QMP UFS PHY compatible for Qualcomm Maili to support physical layer functionality for UFS found on the SoC. Use fallback to indicate the compatibility of the QMP UFS PHY on the Maili with that on the Hawi. Acked-by: Manivannan Sadhasivam Reviewed-by: Krzysztof Kozlowski Signed-off-by: Nitin Rawat Link: https://patch.msgid.link/20260917080622.824253-1-nitin.rawat@oss.qualcomm.com Signed-off-by: Vinod Koul --- .../devicetree/bindings/phy/qcom,sc8280xp-qmp-ufs-phy.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-ufs-phy.yaml b/Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-ufs-phy.yaml index 4efe40c0dc97..eb289c09906a 100644 --- a/Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-ufs-phy.yaml +++ b/Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-ufs-phy.yaml @@ -16,6 +16,10 @@ description: properties: compatible: oneOf: + - items: + - enum: + - qcom,maili-qmp-ufs-phy + - const: qcom,hawi-qmp-ufs-phy - items: - enum: - qcom,qcs8300-qmp-ufs-phy From 926da02a917dec4b5188b76118318b471f14dbe0 Mon Sep 17 00:00:00 2001 From: Vivek Pernamitta Date: Thu, 17 Sep 2026 16:32:35 +0530 Subject: [PATCH 069/114] dt-bindings: phy: qcom,sc8280xp-qmp-pcie-phy: Add Maili PCIe PHY compatibles Add qcom,maili-qmp-gen3x2-pcie-phy and qcom,maili-qmp-gen4x1-pcie-phy compatible strings for the Maili SoC PCIe QMP PHY. Maili reuses the Hawi QMP PCIe PHY hardware, so the compatibles use the two-item fallback format with Hawi as the fallback: qcom,maili-qmp-gen3x2-pcie-phy -> qcom,hawi-qmp-gen3x2-pcie-phy qcom,maili-qmp-gen4x1-pcie-phy -> qcom,hawi-qmp-gen4x1-pcie-phy Since the allOf conditions use 'contains:' to match against the compatible array, and Maili nodes always carry the Hawi fallback string as the second compatible. Reviewed-by: Krzysztof Kozlowski Acked-by: Manivannan Sadhasivam Signed-off-by: Vivek Pernamitta Link: https://patch.msgid.link/20260917-vdev_next-20260916_dt-v4-1-e2f22f67646e@oss.qualcomm.com Signed-off-by: Vinod Koul --- .../phy/qcom,sc8280xp-qmp-pcie-phy.yaml | 93 ++++++++++--------- 1 file changed, 51 insertions(+), 42 deletions(-) diff --git a/Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-pcie-phy.yaml b/Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-pcie-phy.yaml index d54202f6a897..c0a3e1f160ad 100644 --- a/Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-pcie-phy.yaml +++ b/Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-pcie-phy.yaml @@ -15,48 +15,57 @@ description: properties: compatible: - enum: - - qcom,eliza-qmp-gen3x1-pcie-phy - - qcom,eliza-qmp-gen3x2-pcie-phy - - qcom,glymur-qmp-gen4x2-pcie-phy - - qcom,glymur-qmp-gen5x4-pcie-phy - - qcom,hawi-qmp-gen3x2-pcie-phy - - qcom,hawi-qmp-gen4x1-pcie-phy - - qcom,kaanapali-qmp-gen3x2-pcie-phy - - qcom,qcs615-qmp-gen3x1-pcie-phy - - qcom,qcs8300-qmp-gen4x2-pcie-phy - - qcom,sa8775p-qmp-gen4x2-pcie-phy - - qcom,sa8775p-qmp-gen4x4-pcie-phy - - qcom,sar2130p-qmp-gen3x2-pcie-phy - - qcom,sc8180x-qmp-pcie-phy - - qcom,sc8280xp-qmp-gen3x1-pcie-phy - - qcom,sc8280xp-qmp-gen3x2-pcie-phy - - qcom,sc8280xp-qmp-gen3x4-pcie-phy - - qcom,sdm845-qhp-pcie-phy - - qcom,sdm845-qmp-pcie-phy - - qcom,sdx55-qmp-pcie-phy - - qcom,sdx65-qmp-gen4x2-pcie-phy - - qcom,shikra-qmp-gen2x1-pcie-phy - - qcom,sm8150-qmp-gen3x1-pcie-phy - - qcom,sm8150-qmp-gen3x2-pcie-phy - - qcom,sm8250-qmp-gen3x1-pcie-phy - - qcom,sm8250-qmp-gen3x2-pcie-phy - - qcom,sm8250-qmp-modem-pcie-phy - - qcom,sm8350-qmp-gen3x1-pcie-phy - - qcom,sm8350-qmp-gen3x2-pcie-phy - - qcom,sm8450-qmp-gen3x1-pcie-phy - - qcom,sm8450-qmp-gen4x2-pcie-phy - - qcom,sm8475-qmp-gen3x1-pcie-phy - - qcom,sm8550-qmp-gen3x2-pcie-phy - - qcom,sm8550-qmp-gen4x2-pcie-phy - - qcom,sm8650-qmp-gen3x2-pcie-phy - - qcom,sm8650-qmp-gen4x2-pcie-phy - - qcom,sm8750-qmp-gen3x2-pcie-phy - - qcom,x1e80100-qmp-gen3x2-pcie-phy - - qcom,x1e80100-qmp-gen4x2-pcie-phy - - qcom,x1e80100-qmp-gen4x4-pcie-phy - - qcom,x1e80100-qmp-gen4x8-pcie-phy - - qcom,x1p42100-qmp-gen4x4-pcie-phy + oneOf: + - items: + - enum: + - qcom,maili-qmp-gen3x2-pcie-phy + - const: qcom,hawi-qmp-gen3x2-pcie-phy + - items: + - enum: + - qcom,maili-qmp-gen4x1-pcie-phy + - const: qcom,hawi-qmp-gen4x1-pcie-phy + - enum: + - qcom,eliza-qmp-gen3x1-pcie-phy + - qcom,eliza-qmp-gen3x2-pcie-phy + - qcom,glymur-qmp-gen4x2-pcie-phy + - qcom,glymur-qmp-gen5x4-pcie-phy + - qcom,hawi-qmp-gen3x2-pcie-phy + - qcom,hawi-qmp-gen4x1-pcie-phy + - qcom,kaanapali-qmp-gen3x2-pcie-phy + - qcom,qcs615-qmp-gen3x1-pcie-phy + - qcom,qcs8300-qmp-gen4x2-pcie-phy + - qcom,sa8775p-qmp-gen4x2-pcie-phy + - qcom,sa8775p-qmp-gen4x4-pcie-phy + - qcom,sar2130p-qmp-gen3x2-pcie-phy + - qcom,sc8180x-qmp-pcie-phy + - qcom,sc8280xp-qmp-gen3x1-pcie-phy + - qcom,sc8280xp-qmp-gen3x2-pcie-phy + - qcom,sc8280xp-qmp-gen3x4-pcie-phy + - qcom,sdm845-qhp-pcie-phy + - qcom,sdm845-qmp-pcie-phy + - qcom,sdx55-qmp-pcie-phy + - qcom,sdx65-qmp-gen4x2-pcie-phy + - qcom,shikra-qmp-gen2x1-pcie-phy + - qcom,sm8150-qmp-gen3x1-pcie-phy + - qcom,sm8150-qmp-gen3x2-pcie-phy + - qcom,sm8250-qmp-gen3x1-pcie-phy + - qcom,sm8250-qmp-gen3x2-pcie-phy + - qcom,sm8250-qmp-modem-pcie-phy + - qcom,sm8350-qmp-gen3x1-pcie-phy + - qcom,sm8350-qmp-gen3x2-pcie-phy + - qcom,sm8450-qmp-gen3x1-pcie-phy + - qcom,sm8450-qmp-gen4x2-pcie-phy + - qcom,sm8475-qmp-gen3x1-pcie-phy + - qcom,sm8550-qmp-gen3x2-pcie-phy + - qcom,sm8550-qmp-gen4x2-pcie-phy + - qcom,sm8650-qmp-gen3x2-pcie-phy + - qcom,sm8650-qmp-gen4x2-pcie-phy + - qcom,sm8750-qmp-gen3x2-pcie-phy + - qcom,x1e80100-qmp-gen3x2-pcie-phy + - qcom,x1e80100-qmp-gen4x2-pcie-phy + - qcom,x1e80100-qmp-gen4x4-pcie-phy + - qcom,x1e80100-qmp-gen4x8-pcie-phy + - qcom,x1p42100-qmp-gen4x4-pcie-phy reg: minItems: 1 From 1a60038589285eec5b345f1da05ba04a185b8db5 Mon Sep 17 00:00:00 2001 From: Krishna Chaitanya Chundru Date: Mon, 21 Sep 2026 11:07:09 +0530 Subject: [PATCH 070/114] dt-bindings: phy: qcom: add Nord QMP PCIe PHY binding Document the PCIe PHY found on the Qualcomm Nord platform. Nord has a 32.0 GT/s capable QMP PCIe PHY that can operate in multiple lane configurations using four PHY segments (A/B/C/D). The active topology is selected by firmware through a TCSR configuration register and may be configured as x16, x8+x8, x8+x4+x4, or x8+x4+x2+x2. The binding describes the register regions, clocks, resets, power domains, regulators, PHY provider interface, clock provider interface, and the qcom,link-mode property used by the driver to discover the active lane configuration programmed by firmware. Signed-off-by: Krishna Chaitanya Chundru Reviewed-by: Krzysztof Kozlowski Tested-by: Shawn Guo Tested-by: Qiang Yu Link: https://patch.msgid.link/20260921-nord-v4-1-ed05c6e09240@oss.qualcomm.com Signed-off-by: Vinod Koul --- .../phy/qcom,nord-qmp-gen5x16-pcie-phy.yaml | 267 ++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 Documentation/devicetree/bindings/phy/qcom,nord-qmp-gen5x16-pcie-phy.yaml diff --git a/Documentation/devicetree/bindings/phy/qcom,nord-qmp-gen5x16-pcie-phy.yaml b/Documentation/devicetree/bindings/phy/qcom,nord-qmp-gen5x16-pcie-phy.yaml new file mode 100644 index 000000000000..e8a779bcfe83 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/qcom,nord-qmp-gen5x16-pcie-phy.yaml @@ -0,0 +1,267 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/phy/qcom,nord-qmp-gen5x16-pcie-phy.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm Nord QMP Quadfurcated PCIe multiple link-mode PHY + +maintainers: + - Krishna Chaitanya Chundru + +description: + The Qualcomm Nord PCIe PHY is a Gen5-capable QMP PCIe PHY composed + of four PHY ports (A/B/C/D). The ports can be combined to support + multiple lane topologies, including x16, x8+x8, x8+x4+x4 and + x8+x4+x2+x2 configurations. + + The selected topology is determined by the PCIe link configuration mode + programmed in TCSR registers. Depending on the selected mode, PHY ports + may operate independently or be grouped together to form wider PCIe links. + + Each port provides dedicated register space, clocks, resets and power + resources required for PHY operation. + +properties: + compatible: + enum: + - qcom,nord-qmp-gen5x16-pcie-phy + + reg: + maxItems: 4 + + reg-names: + items: + - const: port_a + - const: port_b + - const: port_c + - const: port_d + + clocks: + maxItems: 17 + + clock-names: + items: + - const: aux_a + - const: cfg_ahb_a + - const: ref + - const: rchng_a + - const: pipe_a + - const: aux_b + - const: cfg_ahb_b + - const: rchng_b + - const: pipe_b + - const: aux_c + - const: cfg_ahb_c + - const: rchng_c + - const: pipe_c + - const: aux_d + - const: cfg_ahb_d + - const: rchng_d + - const: pipe_d + + power-domains: + maxItems: 4 + + power-domain-names: + items: + - const: port_a + - const: port_b + - const: port_c + - const: port_d + + resets: + maxItems: 8 + + reset-names: + items: + - const: port_a + - const: port_a_nocsr + - const: port_b + - const: port_b_nocsr + - const: port_c + - const: port_c_nocsr + - const: port_d + - const: port_d_nocsr + + vdda-phy-a-supply: true + + vdda-phy-b-supply: true + + vdda-phy-c-supply: true + + vdda-phy-d-supply: true + + vdda-pll-a-supply: true + + vdda-pll-b-supply: true + + vdda-pll-c-supply: true + + vdda-pll-d-supply: true + + qcom,link-mode: + description: + Reference to the TCSR syscon register containing the PCIe link mode + selected by firmware. The property consists of the TCSR syscon + phandle followed by the offset of the TCSR_PCIE_LINK_CONFIG_MODE + register. + The register value selects one of these topologies. + 0 (QMP_PCIE_NORD_MODE_X16) — all ports combined as one x16 PHY + 1 (QMP_PCIE_NORD_MODE_X8_X8) — port A as x8 and ports B+C+D as x8 + 2 (QMP_PCIE_NORD_MODE_X8_X4_X4) — port A as x8, port B as x4, and ports C+D as x4 + 3 (QMP_PCIE_NORD_MODE_X8_X4_X2_X2) — port A as x8, port B as x4, and ports C and D as x2 + $ref: /schemas/types.yaml#/definitions/phandle-array + items: + - items: + - description: Phandle of the TCSR syscon + - description: Offset of TCSR_PCIE_LINK_CONFIG_MODE register + + "#clock-cells": + const: 1 + description: + Selects the pipe clock output by port index (0=A, 1=B, 2=C, 3=D). + GCC pipe-clock mux parents reference this provider. + + clock-output-names: + items: + - description: Name of the PHY A pipe clock output. + - description: Name of the PHY B pipe clock output. + - description: Name of the PHY C pipe clock output. + - description: Name of the PHY D pipe clock output. + + "#phy-cells": + const: 1 + description: + The cell is the index of the sub-PHY within the active link + mode, e.g. on nord, 0 for "port a" and 1 for "port b" etc. + +required: + - compatible + - reg + - reg-names + - clocks + - clock-names + - power-domains + - power-domain-names + - resets + - reset-names + - vdda-phy-a-supply + - vdda-phy-b-supply + - vdda-phy-c-supply + - vdda-phy-d-supply + - vdda-pll-a-supply + - vdda-pll-b-supply + - vdda-pll-c-supply + - vdda-pll-d-supply + - qcom,link-mode + - "#clock-cells" + - clock-output-names + - "#phy-cells" + +additionalProperties: false + +examples: + - | + #include + #include + + phy@1b00000 { + compatible = "qcom,nord-qmp-gen5x16-pcie-phy"; + + reg = <0x01b00000 0x10000>, + <0x01b10000 0x10000>, + <0x01b20000 0x10000>, + <0x01b30000 0x10000>; + reg-names = "port_a", + "port_b", + "port_c", + "port_d"; + + clocks = <&gcc GCC_PCIE_A_PHY_AUX_CLK>, + <&gcc GCC_PCIE_A_CFG_AHB_CLK>, + <&tcsrcc TCSR_PCIE_CLKREF_EN>, + <&gcc GCC_PCIE_A_PHY_RCHNG_CLK>, + <&gcc GCC_PCIE_A_PIPE_CLK>, + <&gcc GCC_PCIE_B_PHY_AUX_CLK>, + <&gcc GCC_PCIE_B_CFG_AHB_CLK>, + <&gcc GCC_PCIE_B_PHY_RCHNG_CLK>, + <&gcc GCC_PCIE_B_PIPE_CLK>, + <&gcc GCC_PCIE_C_PHY_AUX_CLK>, + <&gcc GCC_PCIE_C_CFG_AHB_CLK>, + <&gcc GCC_PCIE_C_PHY_RCHNG_CLK>, + <&gcc GCC_PCIE_C_PIPE_CLK>, + <&gcc GCC_PCIE_D_PHY_AUX_CLK>, + <&gcc GCC_PCIE_D_CFG_AHB_CLK>, + <&gcc GCC_PCIE_D_PHY_RCHNG_CLK>, + <&gcc GCC_PCIE_D_PIPE_CLK>; + clock-names = "aux_a", + "cfg_ahb_a", + "ref", + "rchng_a", + "pipe_a", + "aux_b", + "cfg_ahb_b", + "rchng_b", + "pipe_b", + "aux_c", + "cfg_ahb_c", + "rchng_c", + "pipe_c", + "aux_d", + "cfg_ahb_d", + "rchng_d", + "pipe_d"; + + assigned-clocks = <&gcc GCC_PCIE_A_PHY_RCHNG_CLK>, + <&gcc GCC_PCIE_B_PHY_RCHNG_CLK>, + <&gcc GCC_PCIE_C_PHY_RCHNG_CLK>, + <&gcc GCC_PCIE_D_PHY_RCHNG_CLK>; + assigned-clock-rates = <100000000>, <100000000>, + <100000000>, <100000000>; + + power-domains = <&gcc GCC_PCIE_A_PHY_GDSC>, + <&gcc GCC_PCIE_B_PHY_GDSC>, + <&gcc GCC_PCIE_C_PHY_GDSC>, + <&gcc GCC_PCIE_D_PHY_GDSC>; + power-domain-names = "port_a", + "port_b", + "port_c", + "port_d"; + + resets = <&gcc GCC_PCIE_A_PHY_COM_BCR>, + <&gcc GCC_PCIE_A_NOCSR_COM_PHY_BCR>, + <&gcc GCC_PCIE_B_PHY_COM_BCR>, + <&gcc GCC_PCIE_B_NOCSR_COM_PHY_BCR>, + <&gcc GCC_PCIE_C_PHY_COM_BCR>, + <&gcc GCC_PCIE_C_NOCSR_COM_PHY_BCR>, + <&gcc GCC_PCIE_D_PHY_COM_BCR>, + <&gcc GCC_PCIE_D_NOCSR_COM_PHY_BCR>; + reset-names = "port_a", + "port_a_nocsr", + "port_b", + "port_b_nocsr", + "port_c", + "port_c_nocsr", + "port_d", + "port_d_nocsr"; + + vdda-phy-a-supply = <&vreg_l6a_1p2>; + vdda-phy-b-supply = <&vreg_l4a_1p2>; + vdda-phy-c-supply = <&vreg_l4a_1p2>; + vdda-phy-d-supply = <&vreg_l4a_1p2>; + vdda-pll-a-supply = <&vreg_l1e_0p9>; + vdda-pll-b-supply = <&vreg_l1e_0p9>; + vdda-pll-c-supply = <&vreg_l1e_0p9>; + vdda-pll-d-supply = <&vreg_l1e_0p9>; + + qcom,link-mode = <&tcsr 0x71050>; + + #clock-cells = <1>; + clock-output-names = "pcie_a_pipe_clk", + "pcie_b_pipe_clk", + "pcie_c_pipe_clk", + "pcie_d_pipe_clk"; + + #phy-cells = <1>; + }; From 63cfcae23b2b4ca43994d507f5f04cba0306dd5a Mon Sep 17 00:00:00 2001 From: Krishna Chaitanya Chundru Date: Mon, 21 Sep 2026 11:07:12 +0530 Subject: [PATCH 071/114] phy: qcom: qmp: Move qphy_setbits/clrbits/checkbits to common header qphy_setbits(), qphy_clrbits() and qphy_checkbits() were duplicated verbatim across phy-qcom-qmp-pcie.c, phy-qcom-qmp-pcie-msm8996.c, phy-qcom-qmp-ufs.c, phy-qcom-qmp-usb.c, phy-qcom-qmp-usbc.c and phy-qcom-qmp-combo.c. Move them to the shared phy-qcom-qmp-common.h, which all of these drivers already include, and drop the local copies. This also makes the helpers available to phy-qcom-qmp-pcie-multiphy.c without introducing yet another copy. Suggested-by: Qiang Yu Reviewed-by: Manivannan Sadhasivam Reviewed-by: Shawn Guo Signed-off-by: Krishna Chaitanya Chundru Tested-by: Shawn Guo Tested-by: Qiang Yu Link: https://patch.msgid.link/20260921-nord-v4-4-ed05c6e09240@oss.qualcomm.com Signed-off-by: Vinod Koul --- drivers/phy/qualcomm/phy-qcom-qmp-combo.c | 24 -------------- drivers/phy/qualcomm/phy-qcom-qmp-common.h | 32 +++++++++++++++++++ .../phy/qualcomm/phy-qcom-qmp-pcie-msm8996.c | 24 -------------- drivers/phy/qualcomm/phy-qcom-qmp-pcie.c | 32 ------------------- drivers/phy/qualcomm/phy-qcom-qmp-ufs.c | 24 -------------- drivers/phy/qualcomm/phy-qcom-qmp-usb.c | 24 -------------- drivers/phy/qualcomm/phy-qcom-qmp-usbc.c | 24 -------------- 7 files changed, 32 insertions(+), 152 deletions(-) diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-combo.c b/drivers/phy/qualcomm/phy-qcom-qmp-combo.c index a4f130fc33e3..f38c3db2039c 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp-combo.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp-combo.c @@ -2652,30 +2652,6 @@ static void qmp_v8_dp_aux_init(struct qmp_combo *qmp); static int qmp_v8_configure_dp_clocks(struct qmp_combo *qmp); static int qmp_v8_configure_dp_phy(struct qmp_combo *qmp); -static inline void qphy_setbits(void __iomem *base, u32 offset, u32 val) -{ - u32 reg; - - reg = readl(base + offset); - reg |= val; - writel(reg, base + offset); - - /* ensure that above write is through */ - readl(base + offset); -} - -static inline void qphy_clrbits(void __iomem *base, u32 offset, u32 val) -{ - u32 reg; - - reg = readl(base + offset); - reg &= ~val; - writel(reg, base + offset); - - /* ensure that above write is through */ - readl(base + offset); -} - /* list of clocks required by phy */ static const char * const qmp_combo_phy_clk_l[] = { "aux", "cfg_ahb", "ref", "com_aux", diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-common.h b/drivers/phy/qualcomm/phy-qcom-qmp-common.h index b945fc14cece..3c0e47f4af75 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp-common.h +++ b/drivers/phy/qualcomm/phy-qcom-qmp-common.h @@ -59,4 +59,36 @@ static inline void qmp_configure(struct device *dev, void __iomem *base, qmp_configure_lane(dev, base, tbl, num, 0xff); } +static inline void qphy_setbits(void __iomem *base, u32 offset, u32 val) +{ + u32 reg; + + reg = readl(base + offset); + reg |= val; + writel(reg, base + offset); + + /* ensure that above write is through */ + readl(base + offset); +} + +static inline void qphy_clrbits(void __iomem *base, u32 offset, u32 val) +{ + u32 reg; + + reg = readl(base + offset); + reg &= ~val; + writel(reg, base + offset); + + /* ensure that above write is through */ + readl(base + offset); +} + +static inline bool qphy_checkbits(const void __iomem *base, u32 offset, u32 val) +{ + u32 reg; + + reg = readl(base + offset); + return (reg & val) == val; +} + #endif diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-pcie-msm8996.c b/drivers/phy/qualcomm/phy-qcom-qmp-pcie-msm8996.c index 37e96493b722..ed22b7a256ef 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp-pcie-msm8996.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp-pcie-msm8996.c @@ -218,30 +218,6 @@ struct qcom_qmp { int init_count; }; -static inline void qphy_setbits(void __iomem *base, u32 offset, u32 val) -{ - u32 reg; - - reg = readl(base + offset); - reg |= val; - writel(reg, base + offset); - - /* ensure that above write is through */ - readl(base + offset); -} - -static inline void qphy_clrbits(void __iomem *base, u32 offset, u32 val) -{ - u32 reg; - - reg = readl(base + offset); - reg &= ~val; - writel(reg, base + offset); - - /* ensure that above write is through */ - readl(base + offset); -} - /* list of clocks required by phy */ static const char * const msm8996_phy_clk_l[] = { "aux", "cfg_ahb", "ref", diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-pcie.c b/drivers/phy/qualcomm/phy-qcom-qmp-pcie.c index d30194c6ed17..9cfabf474fd6 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp-pcie.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp-pcie.c @@ -4088,38 +4088,6 @@ struct qmp_pcie { struct clk_fixed_rate aux_clk_fixed; }; -static bool qphy_checkbits(const void __iomem *base, u32 offset, u32 val) -{ - u32 reg; - - reg = readl(base + offset); - return (reg & val) == val; -} - -static inline void qphy_setbits(void __iomem *base, u32 offset, u32 val) -{ - u32 reg; - - reg = readl(base + offset); - reg |= val; - writel(reg, base + offset); - - /* ensure that above write is through */ - readl(base + offset); -} - -static inline void qphy_clrbits(void __iomem *base, u32 offset, u32 val) -{ - u32 reg; - - reg = readl(base + offset); - reg &= ~val; - writel(reg, base + offset); - - /* ensure that above write is through */ - readl(base + offset); -} - /* list of clocks required by phy */ static const char * const qmp_pciephy_clk_l[] = { "aux", "cfg_ahb", "ref", "refgen", "rchng", "phy_aux", diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-ufs.c b/drivers/phy/qualcomm/phy-qcom-qmp-ufs.c index d4aca22c181e..d15db9ab9a90 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp-ufs.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp-ufs.c @@ -1211,30 +1211,6 @@ struct qmp_ufs { u32 submode; }; -static inline void qphy_setbits(void __iomem *base, u32 offset, u32 val) -{ - u32 reg; - - reg = readl(base + offset); - reg |= val; - writel(reg, base + offset); - - /* ensure that above write is through */ - readl(base + offset); -} - -static inline void qphy_clrbits(void __iomem *base, u32 offset, u32 val) -{ - u32 reg; - - reg = readl(base + offset); - reg &= ~val; - writel(reg, base + offset); - - /* ensure that above write is through */ - readl(base + offset); -} - /* Regulator bulk data with load values for specific configurations */ static const struct regulator_bulk_data milos_ufsphy_vreg_l[] = { { .supply = "vdda-phy", .init_load_uA = 140120 }, diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-usb.c b/drivers/phy/qualcomm/phy-qcom-qmp-usb.c index b0790bcf0bc8..6a9faa0ce3bb 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp-usb.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp-usb.c @@ -1441,30 +1441,6 @@ struct qmp_usb { struct clk_fixed_rate pipe_clk_fixed; }; -static inline void qphy_setbits(void __iomem *base, u32 offset, u32 val) -{ - u32 reg; - - reg = readl(base + offset); - reg |= val; - writel(reg, base + offset); - - /* ensure that above write is through */ - readl(base + offset); -} - -static inline void qphy_clrbits(void __iomem *base, u32 offset, u32 val) -{ - u32 reg; - - reg = readl(base + offset); - reg &= ~val; - writel(reg, base + offset); - - /* ensure that above write is through */ - readl(base + offset); -} - /* list of clocks required by phy */ static const char * const qmp_usb_phy_clk_l[] = { "aux", "cfg_ahb", "ref", "com_aux", diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-usbc.c b/drivers/phy/qualcomm/phy-qcom-qmp-usbc.c index e3465ab7c80e..548ec97373ac 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp-usbc.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp-usbc.c @@ -474,30 +474,6 @@ struct qmp_usbc { enum typec_orientation orientation; }; -static inline void qphy_setbits(void __iomem *base, u32 offset, u32 val) -{ - u32 reg; - - reg = readl(base + offset); - reg |= val; - writel(reg, base + offset); - - /* ensure that above write is through */ - readl(base + offset); -} - -static inline void qphy_clrbits(void __iomem *base, u32 offset, u32 val) -{ - u32 reg; - - reg = readl(base + offset); - reg &= ~val; - writel(reg, base + offset); - - /* ensure that above write is through */ - readl(base + offset); -} - /* list of clocks required by phy */ static const char * const qmp_usbc_phy_clk_l[] = { "aux", "cfg_ahb", "ref", "com_aux", From 6dd77bf6a10d0eb9ed17f5b6093b3511052084db Mon Sep 17 00:00:00 2001 From: Krishna Chaitanya Chundru Date: Mon, 21 Sep 2026 11:07:13 +0530 Subject: [PATCH 072/114] phy: qcom: qmp-pcie: Refactor common multiphy handling Reuse the existing port resource arrays for Glymur register regions, power domains, and NOCSR resets instead of maintaining duplicate Glymur-specific lists. Attach each PHY instance's power domains to the device created for that PHY. This ensures that the associated GDSCs are managed with the lifetime of the individual PHY device rather than the parent multiphy platform device. Validate the clock-output-names property before using its count for allocation, and use a generic module description. Signed-off-by: Krishna Chaitanya Chundru Reviewed-by: Manivannan Sadhasivam Reviewed-by: Shawn Guo Tested-by: Shawn Guo Tested-by: Qiang Yu Link: https://patch.msgid.link/20260921-nord-v4-5-ed05c6e09240@oss.qualcomm.com Signed-off-by: Vinod Koul --- .../phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c | 102 ++++++++---------- 1 file changed, 44 insertions(+), 58 deletions(-) diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c b/drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c index e93cba4369fb..befca9b78ce9 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c @@ -113,31 +113,19 @@ static const char * const glymur_vreg_l[] = { "vdda-phy", "vdda-pll", "vdda-refgen0p9", "vdda-refgen1p2", }; -static const char * const glymur_pciephy_a_reg_l[] = { +static const char * const pciephy_port_a_reg_l[] = { "port_a", }; -static const char * const glymur_pciephy_b_reg_l[] = { +static const char * const pciephy_port_b_reg_l[] = { "port_b", }; -static const char * const glymur_pciephy_reg_l[] = { +static const char * const pciephy_port_ab_reg_l[] = { "port_a", "port_b", }; -static const char * const glymur_pciephy_a_pd_l[] = { - "port_a", -}; - -static const char * const glymur_pciephy_b_pd_l[] = { - "port_b", -}; - -static const char * const glymur_pciephy_pd_l[] = { - "port_a", "port_b", -}; - -static const char * const glymur_pciephy_a_nocsr_reset_l[] = { +static const char * const pciephy_port_a_nocsr_reset_l[] = { "port_a_nocsr", }; @@ -145,7 +133,7 @@ static const char * const glymur_pciephy_nocsr_reset_l[] = { "port_a_nocsr", "port_b_nocsr", }; -static const char * const glymur_pciephy_b_nocsr_reset_l[] = { +static const char * const pciephy_port_b_nocsr_reset_l[] = { "port_b_nocsr", }; @@ -155,12 +143,12 @@ static const struct qmp_pcie_offsets glymur_pcie_offsets_v8_50 = { static const struct qmp_phy_cfg glymur_qmp_gen5x4_pciephy_a_cfg = { .offsets = &glymur_pcie_offsets_v8_50, - .reg_names = glymur_pciephy_a_reg_l, - .num_regs = ARRAY_SIZE(glymur_pciephy_a_reg_l), - .pd_names = glymur_pciephy_a_pd_l, - .num_pds = ARRAY_SIZE(glymur_pciephy_a_pd_l), - .nocsr_reset_list = glymur_pciephy_a_nocsr_reset_l, - .num_nocsr_resets = ARRAY_SIZE(glymur_pciephy_a_nocsr_reset_l), + .reg_names = pciephy_port_a_reg_l, + .num_regs = ARRAY_SIZE(pciephy_port_a_reg_l), + .pd_names = pciephy_port_a_reg_l, + .num_pds = ARRAY_SIZE(pciephy_port_a_reg_l), + .nocsr_reset_list = pciephy_port_a_nocsr_reset_l, + .num_nocsr_resets = ARRAY_SIZE(pciephy_port_a_nocsr_reset_l), .vreg_list = glymur_vreg_l, .num_vregs = ARRAY_SIZE(glymur_vreg_l), .regs = pciephy_v8_50_regs_layout, @@ -173,12 +161,12 @@ static const struct qmp_phy_cfg glymur_qmp_gen5x4_pciephy_a_cfg = { static const struct qmp_phy_cfg glymur_qmp_gen5x4_pciephy_b_cfg = { .offsets = &glymur_pcie_offsets_v8_50, - .reg_names = glymur_pciephy_b_reg_l, - .num_regs = ARRAY_SIZE(glymur_pciephy_b_reg_l), - .pd_names = glymur_pciephy_b_pd_l, - .num_pds = ARRAY_SIZE(glymur_pciephy_b_pd_l), - .nocsr_reset_list = glymur_pciephy_b_nocsr_reset_l, - .num_nocsr_resets = ARRAY_SIZE(glymur_pciephy_b_nocsr_reset_l), + .reg_names = pciephy_port_b_reg_l, + .num_regs = ARRAY_SIZE(pciephy_port_b_reg_l), + .pd_names = pciephy_port_b_reg_l, + .num_pds = ARRAY_SIZE(pciephy_port_b_reg_l), + .nocsr_reset_list = pciephy_port_b_nocsr_reset_l, + .num_nocsr_resets = ARRAY_SIZE(pciephy_port_b_nocsr_reset_l), .vreg_list = glymur_vreg_l, .num_vregs = ARRAY_SIZE(glymur_vreg_l), .regs = pciephy_v8_50_regs_layout, @@ -191,10 +179,10 @@ static const struct qmp_phy_cfg glymur_qmp_gen5x4_pciephy_b_cfg = { static const struct qmp_phy_cfg glymur_qmp_gen5x8_pciephy_cfg = { .offsets = &glymur_pcie_offsets_v8_50, - .reg_names = glymur_pciephy_reg_l, - .num_regs = ARRAY_SIZE(glymur_pciephy_reg_l), - .pd_names = glymur_pciephy_pd_l, - .num_pds = ARRAY_SIZE(glymur_pciephy_pd_l), + .reg_names = pciephy_port_ab_reg_l, + .num_regs = ARRAY_SIZE(pciephy_port_ab_reg_l), + .pd_names = pciephy_port_ab_reg_l, + .num_pds = ARRAY_SIZE(pciephy_port_ab_reg_l), .nocsr_reset_list = glymur_pciephy_nocsr_reset_l, .num_nocsr_resets = ARRAY_SIZE(glymur_pciephy_nocsr_reset_l), .vreg_list = glymur_vreg_l, @@ -398,45 +386,39 @@ static const struct phy_ops qmp_pcie_phy_ops = { static void qmp_pcie_pd_detach(void *data) { struct qmp_pcie *qmp = data; - const struct qmp_phy_cfg *cfg = qmp->cfg; int i; - for (i = 0; i < cfg->num_pds; i++) { + for (i = 0; i < qmp->cfg->num_pds; i++) { if (!IS_ERR_OR_NULL(qmp->pd_devs[i])) dev_pm_domain_detach(qmp->pd_devs[i], true); } } -static int qmp_pcie_pd_init(struct qmp_pcie *qmp) +static int qmp_pcie_pd_init(struct qmp_pcie *qmp, struct phy *phy) { const struct qmp_phy_cfg *cfg = qmp->cfg; - struct device *dev = qmp->dev; + struct device *pd_dev = &phy->dev; int i, ret; if (!cfg->num_pds) return 0; - qmp->pd_devs = devm_kcalloc(dev, cfg->num_pds, sizeof(*qmp->pd_devs), + qmp->pd_devs = devm_kcalloc(pd_dev, cfg->num_pds, sizeof(*qmp->pd_devs), GFP_KERNEL); if (!qmp->pd_devs) return -ENOMEM; + ret = devm_add_action_or_reset(pd_dev, qmp_pcie_pd_detach, qmp); + if (ret) + return ret; + for (i = 0; i < cfg->num_pds; i++) { - qmp->pd_devs[i] = dev_pm_domain_attach_by_name(dev, - cfg->pd_names[i]); - if (IS_ERR_OR_NULL(qmp->pd_devs[i])) { - ret = PTR_ERR(qmp->pd_devs[i]) ? : -ENODATA; - goto err_detach; - } + qmp->pd_devs[i] = dev_pm_domain_attach_by_name(pd_dev, cfg->pd_names[i]); + if (IS_ERR_OR_NULL(qmp->pd_devs[i])) + return PTR_ERR_OR_ZERO(qmp->pd_devs[i]) ? : -ENODATA; } - return devm_add_action_or_reset(dev, qmp_pcie_pd_detach, qmp); - -err_detach: - while (--i >= 0) - dev_pm_domain_detach(qmp->pd_devs[i], false); - - return ret; + return 0; } static int qmp_pcie_vreg_init(struct qmp_pcie *qmp) @@ -555,6 +537,8 @@ static int qmp_pcie_multiphy_register_clocks(struct device *dev, int i, ret; num_pipe_outputs = of_property_count_strings(np, "clock-output-names"); + if (num_pipe_outputs < 0) + return num_pipe_outputs; qmp_data->num_pipe_outputs = num_pipe_outputs; qmp_data->pipe_out_clks = devm_kcalloc(dev, num_pipe_outputs, @@ -630,6 +614,7 @@ static struct phy *qmp_pcie_multiphy_xlate(struct device *dev, static int qmp_pcie_probe_phy(struct qmp_pcie *qmp, struct device_node *np, struct phy **out_phy) { + struct phy *phy; int ret; ret = qmp_pcie_get_mmio(qmp); @@ -648,15 +633,16 @@ static int qmp_pcie_probe_phy(struct qmp_pcie *qmp, struct device_node *np, if (ret) return ret; - ret = qmp_pcie_pd_init(qmp); + phy = devm_phy_create(qmp->dev, np, &qmp_pcie_phy_ops); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + ret = qmp_pcie_pd_init(qmp, phy); if (ret) return ret; - *out_phy = devm_phy_create(qmp->dev, np, &qmp_pcie_phy_ops); - if (IS_ERR(*out_phy)) - return PTR_ERR(*out_phy); - - phy_set_drvdata(*out_phy, qmp); + phy_set_drvdata(phy, qmp); + *out_phy = phy; return 0; } @@ -739,5 +725,5 @@ static struct platform_driver qmp_pcie_multiphy_driver = { module_platform_driver(qmp_pcie_multiphy_driver); MODULE_AUTHOR("Qiang Yu "); -MODULE_DESCRIPTION("Qualcomm QMP PCIe Multi-PHY driver for Glymur"); +MODULE_DESCRIPTION("Qualcomm QMP PCIe Multi-PHY driver"); MODULE_LICENSE("GPL"); From e397c8507e362a29ecfb4aef38b408f2de731526 Mon Sep 17 00:00:00 2001 From: Krishna Chaitanya Chundru Date: Mon, 21 Sep 2026 11:07:14 +0530 Subject: [PATCH 073/114] phy: qcom: qmp-pcie: Add Nord Gen5x16 PCIe multi-PHY support Add support for the Nord Gen5x16 bifurcated QMP PCIe PHY. Nord has a single PCIe PHY block split into four ports which can be used in multiple lane configurations: x16, x8+x8, x8+x4+x4 and x8+x4+x2+x2. The active topology is selected through the link mode configuration register, and the driver exposes the corresponding PHY instances based on that mode. Unlike Glymur, where the PHY is initialized by firmware, Nord requires Linux to program the PHY registers. Add the PLL, TXRXZ, PCS and PCS lane initialization tables, along with the required power-up, power-down and soft-reset sequencing. Add per-port and grouped PHY configurations for all supported Nord lane modes. Also add support for regular PHY resets in addition to the NOCSR reset. Signed-off-by: Krishna Chaitanya Chundru Reviewed-by: Manivannan Sadhasivam Reviewed-by: Shawn Guo Tested-by: Shawn Guo Tested-by: Qiang Yu Link: https://patch.msgid.link/20260921-nord-v4-6-ed05c6e09240@oss.qualcomm.com Signed-off-by: Vinod Koul --- .../phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c | 868 +++++++++++++++++- .../qualcomm/phy-qcom-qmp-pcs-pcie-v6_40.h | 55 ++ drivers/phy/qualcomm/phy-qcom-qmp-pcs-v6_40.h | 14 + .../phy-qcom-qmp-qserdes-com-pcie-v6_40.h | 94 ++ .../phy-qcom-qmp-qserdes-txrxz-pcie-v6_40.h | 194 ++++ 5 files changed, 1206 insertions(+), 19 deletions(-) create mode 100644 drivers/phy/qualcomm/phy-qcom-qmp-pcs-pcie-v6_40.h create mode 100644 drivers/phy/qualcomm/phy-qcom-qmp-pcs-v6_40.h create mode 100644 drivers/phy/qualcomm/phy-qcom-qmp-qserdes-com-pcie-v6_40.h create mode 100644 drivers/phy/qualcomm/phy-qcom-qmp-qserdes-txrxz-pcie-v6_40.h diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c b/drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c index befca9b78ce9..f1267691bf32 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c @@ -3,6 +3,7 @@ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ +#include #include #include #include @@ -22,6 +23,11 @@ #include #include "phy-qcom-qmp.h" +#include "phy-qcom-qmp-common.h" +#include "phy-qcom-qmp-pcs-v6_40.h" +#include "phy-qcom-qmp-pcs-pcie-v6_40.h" +#include "phy-qcom-qmp-qserdes-com-pcie-v6_40.h" +#include "phy-qcom-qmp-qserdes-txrxz-pcie-v6_40.h" #define PHY_INIT_COMPLETE_TIMEOUT_US 10000 @@ -30,8 +36,19 @@ enum qmp_pcie_glymur_link_mode { QMP_PCIE_GLYMUR_MODE_X4X4, }; +enum qmp_pcie_nord_link_mode { + QMP_PCIE_NORD_MODE_X16, + QMP_PCIE_NORD_MODE_X8_X8, + QMP_PCIE_NORD_MODE_X8_X4_X4, + QMP_PCIE_NORD_MODE_X8_X4_X2_X2 +}; + enum qphy_reg_layout { QPHY_PCS_STATUS, + QPHY_PCS_START_CONTROL, + QPHY_PCS_SW_RESET, + QPHY_PCS_POWER_DOWN_CONTROL, + QPHY_COM_BIAS_EN_CLKBUFLR_EN, QPHY_LAYOUT_SIZE }; @@ -39,16 +56,48 @@ static const unsigned int pciephy_v8_50_regs_layout[QPHY_LAYOUT_SIZE] = { [QPHY_PCS_STATUS] = QPHY_V8_50_PCS_STATUS1, }; +static const unsigned int nord_pciephy_v6_40_regs_layout[QPHY_LAYOUT_SIZE] = { + [QPHY_PCS_STATUS] = QPHY_V6_40_PCS_STATUS1, + [QPHY_PCS_START_CONTROL] = QPHY_V6_40_PCS_START_CONTROL, + [QPHY_PCS_SW_RESET] = QPHY_V6_40_PCS_SW_RESET, + [QPHY_PCS_POWER_DOWN_CONTROL] = QPHY_V6_40_PCS_POWER_DOWN_CONTROL, + [QPHY_COM_BIAS_EN_CLKBUFLR_EN] = QSERDES_PCIE_V6_40_COM_BIAS_EN_CLKBUFLR_EN, +}; + struct qmp_pcie_offsets { u16 pcs; + u16 pll; + u16 txrxz; + u16 pcs_lanez; +}; + +struct qmp_phy_cfg_tbls { + const struct qmp_phy_init_tbl *pll_common; + int pll_common_num; + const struct qmp_phy_init_tbl *txrxz; + int txrxz_num; + const struct qmp_phy_init_tbl *pcs; + int pcs_num; + const struct qmp_phy_init_tbl *pcs_lanez; + int pcs_lanez_num; + const struct qmp_phy_init_tbl *port_split_pre_pwrup; + int port_split_pre_pwrup_num; + const struct qmp_phy_init_tbl *port_split_post_cfg; + int port_split_post_cfg_num; }; struct qmp_phy_cfg { const struct qmp_pcie_offsets *offsets; + const struct qmp_phy_cfg_tbls tbls; + const struct qmp_phy_init_tbl * const *pll_tbls; + const int *pll_nums; + bool port_split; const char * const *reg_names; int num_regs; const char * const *pd_names; int num_pds; + const char * const *reset_list; + int num_resets; const char * const *nocsr_reset_list; int num_nocsr_resets; const char * const *vreg_list; @@ -64,9 +113,11 @@ struct qmp_phy_cfg { struct qmp_pcie { struct device *dev; const struct qmp_phy_cfg *cfg; + bool skip_init; void __iomem **base; struct clk_bulk_data *clks; struct clk_bulk_data *pipe_clks; + struct reset_control_bulk_data *resets; struct reset_control_bulk_data *nocsr_resets; struct regulator_bulk_data *vregs; struct device **pd_devs; @@ -89,6 +140,10 @@ struct qmp_pcie_match_data { u32 num_modes; }; +/* QSERDES_PCIE_V6_40_COM_BIAS_EN_CLKBUFLR_EN bits */ +#define COM_BIAS_EN BIT(0) +#define COM_BIAS_EN_MUX BIT(1) + static const char * const glymur_pciephy_clk_l[] = { "aux", "cfg_ahb", "ref", "rchng", "phy_b_aux", }; @@ -220,6 +275,702 @@ static const struct qmp_pcie_match_data glymur_qmp_gen5x8_match_data = { .num_modes = ARRAY_SIZE(glymur_qmp_gen5x8_mode_cfgs), }; +static const char * const nord_pciephy_port_a_vreg_l[] = { + "vdda-phy-a", "vdda-pll-a", +}; + +static const char * const nord_pciephy_port_b_vreg_l[] = { + "vdda-phy-b", "vdda-pll-b", +}; + +static const char * const nord_pciephy_port_c_vreg_l[] = { + "vdda-phy-c", "vdda-pll-c", +}; + +static const char * const nord_pciephy_port_d_vreg_l[] = { + "vdda-phy-d", "vdda-pll-d", +}; + +static const char * const nord_pciephy_port_cd_vreg_l[] = { + "vdda-phy-c", "vdda-pll-c", "vdda-phy-d", "vdda-pll-d", +}; + +static const char * const nord_pciephy_port_bcd_vreg_l[] = { + "vdda-phy-b", "vdda-pll-b", "vdda-phy-c", "vdda-pll-c", + "vdda-phy-d", "vdda-pll-d", +}; + +static const char * const nord_pciephy_port_abcd_vreg_l[] = { + "vdda-phy-a", "vdda-pll-a", "vdda-phy-b", "vdda-pll-b", + "vdda-phy-c", "vdda-pll-c", "vdda-phy-d", "vdda-pll-d", +}; + +static const struct qmp_pcie_offsets nord_pcie_offsets_v6_40 = { + .pll = 0x8000, + .pcs = 0x9000, + .txrxz = 0xd000, + .pcs_lanez = 0xe800, +}; + +static const struct qmp_phy_init_tbl nord_qmp_pcie_pll_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_CLK_FWD_CONFIG_1, 0x0f), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_SSC_STEP_SIZE1_MODE2, 0xab), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_SSC_STEP_SIZE2_MODE2, 0x01), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_CP_CTRL_MODE2, 0x06), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_PLL_RCTRL_MODE2, 0x16), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_PLL_CCTRL_MODE2, 0x36), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_CORECLK_DIV_MODE2, 0x04), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_LOCK_CMP1_MODE2, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_LOCK_CMP2_MODE2, 0x1a), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_DEC_START_MODE2, 0x68), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_DIV_FRAC_START1_MODE2, 0xab), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_DIV_FRAC_START2_MODE2, 0xaa), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_DIV_FRAC_START3_MODE2, 0x02), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_BIN_VCOCAL_CMP_CODE1_MODE2, 0xa4), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_BIN_VCOCAL_CMP_CODE2_MODE2, 0x18), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_SSC_STEP_SIZE1_MODE1, 0x00), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_SSC_STEP_SIZE2_MODE1, 0x03), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_CP_CTRL_MODE1, 0x06), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_PLL_RCTRL_MODE1, 0x16), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_PLL_CCTRL_MODE1, 0x36), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_CORECLK_DIV_MODE1, 0x04), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_LOCK_CMP1_MODE1, 0x04), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_LOCK_CMP2_MODE1, 0x0d), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_DEC_START_MODE1, 0x68), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_DIV_FRAC_START1_MODE1, 0xab), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_DIV_FRAC_START2_MODE1, 0xaa), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_DIV_FRAC_START3_MODE1, 0x02), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_BIN_VCOCAL_CMP_CODE1_MODE1, 0x52), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_BIN_VCOCAL_CMP_CODE2_MODE1, 0x0c), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_HSCLK_SEL_1, 0x3c), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_HSCLK_SEL_2, 0x01), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_BIN_VCOCAL_HSCLK_SEL_1, 0x3c), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_BIN_VCOCAL_HSCLK_SEL_2, 0x01), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_SSC_STEP_SIZE1_MODE0, 0xe0), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_SSC_STEP_SIZE2_MODE0, 0x01), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_CP_CTRL_MODE0, 0x06), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_PLL_RCTRL_MODE0, 0x16), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_PLL_CCTRL_MODE0, 0x36), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_CORECLK_DIV_MODE0, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_LOCK_CMP1_MODE0, 0x40), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_LOCK_CMP2_MODE0, 0x03), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_DEC_START_MODE0, 0x41), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_DIV_FRAC_START1_MODE0, 0xab), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_DIV_FRAC_START2_MODE0, 0xaa), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_DIV_FRAC_START3_MODE0, 0x01), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_BIN_VCOCAL_CMP_CODE1_MODE0, 0xd4), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_BIN_VCOCAL_CMP_CODE2_MODE0, 0x03), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_HSCLK_HS_SWITCH_SEL_1, 0x16), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_HSCLK_HS_SWITCH_SEL_2, 0x00), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_BG_TIMER, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_VCO_TUNE_CTRL, 0x40), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_SSC_EN_CENTER, 0x01), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_SSC_PER1, 0x62), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_SSC_PER2, 0x02), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_POST_DIV_MUX, 0xc0), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_BIAS_EN_CLKBUFLR_EN, 0x1f), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_CLK_ENABLE1, 0x90), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_SYS_CLK_CTRL, 0x82), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_PLL_IVCO, 0x1f), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_PLL_IVCO_MODE1, 0x1f), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_PLL_IVCO_MODE2, 0x1f), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_SYSCLK_EN_SEL, 0x08), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_LOCK_CMP_EN, 0x46), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_LOCK_CMP_CFG, 0x04), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_VCO_TUNE_MAP, 0x24), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_CLK_SELECT, 0x34), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_CORE_CLK_EN, 0xe0), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_CMN_CONFIG_1, 0x86), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_CMN_MISC1, 0x88), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_CMN_MODE, 0x14), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_VCO_DC_LEVEL_CTRL, 0x0f), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_CMN_CONFIG_2, 0x10), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_CLK_EP_DIV_MODE1, 0x14), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_CLK_EP_DIV_MODE0, 0x32), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_PSM_CAL_EN, 0x05), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_PLL_SPARE_FOR_ECO, 0x40), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_DCC_CAL_1, 0x40), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_DCC_CAL_2, 0x07), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_DCC_CAL_3, 0x60), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_DCC_CAL_8, 0x32), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_IP_CTRL_AND_DP_SEL, 0xaf), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_CMN_IETRIM, 0x0d), +}; + +static const struct qmp_phy_init_tbl nord_qmp_pcie_pll_a_ovl_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_DCC_CAL_7, 0x31), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_DCC_CAL_9, 0x31), +}; + +static const struct qmp_phy_init_tbl nord_qmp_pcie_pll_b_ovl_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_DCC_CAL_7, 0x32), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_DCC_CAL_9, 0x22), +}; + +static const struct qmp_phy_init_tbl nord_qmp_pcie_pll_c_ovl_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_DCC_CAL_7, 0x20), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_DCC_CAL_9, 0x31), +}; + +static const struct qmp_phy_init_tbl nord_qmp_pcie_pll_d_ovl_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_DCC_CAL_7, 0x22), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_COM_DCC_CAL_9, 0x22), +}; + +static const struct qmp_phy_init_tbl nord_qmp_pcie_txrxz_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_SIGDET_ENABLES, 0x1c), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE2_B0, 0xcd), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE2_B1, 0x34), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE2_B2, 0x55), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE2_B3, 0x50), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE2_B4, 0xac), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE2_B5, 0xdb), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE2_B6, 0x52), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE2_B7, 0x3e), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE3_B0, 0xce), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE3_B1, 0x55), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE3_B2, 0x6d), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE3_B3, 0x60), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE3_B4, 0x8c), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE3_B5, 0x9d), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE3_B6, 0x6d), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE3_B7, 0x26), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE4_B0, 0xfb), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE4_B1, 0xea), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE4_B2, 0xe8), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE4_B3, 0x70), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE4_B4, 0x9c), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE4_B5, 0xf4), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE4_B6, 0x6d), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE4_B7, 0x3b), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_SUMMER_CAL_SPD_MODE_RATE_0123, 0x6f), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_TX_ADAPT_POST_THRESH1, 0x02), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_TX_ADAPT_POST_THRESH2, 0x0d), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_PHPRE_CTRL, 0x20), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_TX_ADPT_CTRL, 0x30), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE_0_1_B0, 0xff), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE_0_1_B1, 0x4a), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE_0_1_B2, 0x77), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE_0_1_B3, 0x50), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE_0_1_B4, 0x9a), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE_0_1_B5, 0x47), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE_0_1_B6, 0x52), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE_0_1_B7, 0x1d), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_LANE_MODE_1, 0x05), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_LANE_MODE_2, 0x00), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_LANE_MODE_3, 0x41), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_LANE_MODE_4, 0x0f), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_TOP_LDO_CODE_CTRL1, 0x00), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_TOP_LDO_CODE_CTRL2, 0x00), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_TOP_LDO_CODE_CTRL3, 0x81), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_TOP_LDO_CODE_CTRL4, 0x8d), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_LOCK_CTRL, 0x00), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_DFE_TAP1_DAC_ENABLE, 0x1c), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_DFE_TAP2_DAC_ENABLE, 0x1c), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_DFE_TAP345_DAC_ENABLE, 0x18), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_DFE_TAP67_DAC_ENABLE, 0x10), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_CP_CUR_FLL_RATE0, 0x07), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_CP_CUR_FLL_RATE1, 0x07), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_CP_CUR_FLL_RATE2, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_CP_CUR_FLL_RATE3, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_CP_CUR_FLL_RATE4, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_CP_CUR_PLL_RATE0, 0x00), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_CP_CUR_PLL_RATE1, 0x00), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_CP_CUR_PLL_RATE2, 0x01), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_CP_CUR_PLL_RATE3, 0x03), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_CP_CUR_PLL_RATE4, 0x03), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_FLL_DIV_RATIO_RATE_0123, 0x94), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_LOOP_CCODE_RATE_01, 0x66), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_LOOP_CCODE_RATE_23, 0x77), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_LOOP_CCODE_RATE4, 0x07), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_IQTUNE_CTRL, 0x58), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_IQTUNE_MAN_INDEX, 0x0d), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_IQTUNE_ANA_CTRL, 0x00), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_LOOP_FUNC_CTRL, 0xd0), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_KP_CAL_UPPER_FREQ_DIFF_BND1_RATE0, 0xff), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_KP_CAL_UPPER_FREQ_DIFF_BND1_RATE1, 0xff), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_KP_CAL_UPPER_FREQ_DIFF_BND1_RATE2, 0xff), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_KP_CAL_UPPER_FREQ_DIFF_BND1_RATE3, 0xff), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_KP_CAL_UPPER_FREQ_DIFF_BND1_RATE4, 0xff), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_KP_CAL_LOWER_FREQ_DIFF_BND_RATE0, 0x15), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_KP_CAL_LOWER_FREQ_DIFF_BND_RATE1, 0x15), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_KP_CAL_LOWER_FREQ_DIFF_BND_RATE2, 0x10), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_KP_CAL_LOWER_FREQ_DIFF_BND_RATE3, 0x07), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_KP_CAL_LOWER_FREQ_DIFF_BND_RATE4, 0x0c), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_KP_CODE_OVRD_RATE_2_3, 0x24), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_IVCM_CAL_CTRL2, 0x83), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_IVCM_CAL_CTRL3, 0x44), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_IVCM_CAL_CTRL4, 0x04), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_LOOP_RCODE_PLL_RATE_0_1, 0x00), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_LOOP_RCODE_PLL_RATE_2_3, 0x11), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_LOOP_RCODE_PLL_RATE4, 0x02), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_LOOP_RCODE_FLL_RATE_0_1, 0x33), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_LOOP_RCODE_FLL_RATE_2_3, 0x33), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_LOOP_RCODE_FLL_RATE4, 0x03), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_LOOP_RCODE_FAST_RATE_0_1, 0x22), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_LOOP_RCODE_FAST_RATE_2_3, 0x00), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_LOOP_RCODE_FAST_RATE4, 0x00), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_IQTUNE_DIV2_CTRL_RATE0123, 0x7f), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_CTUNE_MEAS_CNT1_RATE0, 0x13), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_CTUNE_MEAS_CNT2_RATE0, 0x13), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_CTUNE_MEAS_CNT1_RATE1, 0x26), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_CTUNE_MEAS_CNT2_RATE1, 0x26), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_CTUNE_MEAS_CNT1_RATE2, 0x9c), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_CTUNE_MEAS_CNT2_RATE2, 0x3d), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_CTUNE_MEAS_CNT1_RATE3, 0x9c), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_CTUNE_MEAS_CNT2_RATE3, 0x3d), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_CTUNE_MEAS_CNT1_RATE4, 0x09), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_CTUNE_MEAS_CNT2_RATE4, 0x3d), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_EQU_ADAPTOR_CNTRL3, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_EQU_ADAPTOR_CNTRL4, 0xaa), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RX_EQU_ADAPTOR_CNTRL5, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_VGA_CAL_CNTRL1, 0xe1), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_VGA_CAL_MAN_VAL_RATE0_1, 0xdd), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_VGA_CAL_MAN_VAL_RATE2_3, 0xdc), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_VGA_CAL_MAN_VAL_RATE4, 0x05), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_CAP_CODE_OVRD_MUXES, 0x11), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_CAP_CODE_RATE_0123, 0x55), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_CAP_CODE_RATE4, 0x01), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_KVCO_IDEAL_FREQ_DIFF1_RATE0, 0x4b), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_KVCO_IDEAL_FREQ_DIFF2_RATE0, 0x00), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_KVCO_IDEAL_FREQ_DIFF1_RATE1, 0x4b), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_KVCO_IDEAL_FREQ_DIFF2_RATE1, 0x00), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_KVCO_IDEAL_FREQ_DIFF1_RATE2, 0x35), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_KVCO_IDEAL_FREQ_DIFF2_RATE2, 0x00), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_KVCO_IDEAL_FREQ_DIFF1_RATE3, 0x28), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_KVCO_IDEAL_FREQ_DIFF1_RATE4, 0x14), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_KVCO_INIT_RATE_0_1, 0x11), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_KVCO_INIT_RATE_2_3, 0x22), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_KVCO_INIT_RATE_4, 0x04), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_KVCO_CODE_OVRD_RATE0, 0x04), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_KVCO_CODE_OVRD_RATE1, 0x03), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_KVCO_CODE_OVRD_RATE2, 0x01), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_FREQ_LOCK_DET_DLY_RATE0, 0xff), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_FREQ_LOCK_DET_DLY_RATE1, 0xff), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_FREQ_LOCK_DET_DLY_RATE2, 0xff), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_FREQ_LOCK_DET_DLY_RATE3, 0xff), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_FREQ_LOCK_DET_DLY_RATE4, 0xff), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_VCTRL_RATE_0_1, 0x43), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_VCTRL_RATE_2_3, 0x44), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_TYPE_CONFIG, 0x03), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CTLE_POST_CAL_OFFSET_RATE_0_1_2, 0x67), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_EN_LOWFREQ, 0x1f), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RES_CODE_LANE_OFFSET_RX, 0x18), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_GM_CAL_RES_RATE0_1, 0x88), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_GM_CAL_RES_RATE2_3, 0x88), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_GM_CAL_RES_RATE4, 0x09), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_VTHRESH_CAL_MAN_VAL_RATE4, 0x56), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_CLKBUF_ENABLE, 0x40), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_VCO_CTUNE_UPPER_BND_RATE0, 0x1e), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_VCO_CTUNE_UPPER_BND_RATE1, 0x1e), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_VCO_CTUNE_UPPER_BND_RATE2, 0x1e), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_VCO_CTUNE_UPPER_BND_RATE3, 0x1e), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_VCO_CTUNE_UPPER_BND_RATE4, 0x1e), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_SIGDET_CNTRL, 0x2f), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_SIGDET_LVL, 0x84), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_SIGDET_CAL_CTRL1, 0x10), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_DIG_BKUP_CTRL15, 0x00), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_DIG_BKUP_CTRL16, 0x52), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RESTRIM_CAL_CTRL, 0x08), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RESTRIM_POST_CAL_OFFSET, 0x10), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_RESTRIM_VREF_SEL, 0x16), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_BLW_CTRL, 0x3c), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_BLW_MAN_VAL_RATE3, 0x17), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_BLW_MAN_VAL_RATE4, 0x17), +}; + +static const struct qmp_phy_init_tbl nord_qmp_pcie_pcs_tbl[] = { + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_POWER_STATE_CONFIG2, 0x3d), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_POWER_STATE_CONFIG5, 0xe4), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_POWER_STATE_CONFIG6, 0x1f), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_POWER_STATE_CONFIG7, 0x0d), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_MULTIPHY_CONFIG2, 0x01), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_PCS_TX_RX_CONFIG2, 0xe4), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_PCS_TX_RX_CONFIG3, 0x30), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_PCS_TX_RX_CONFIG5, 0x05), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_PCS_TX_RX_CONFIG6, 0x08), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_PCS_TX_RX_CONFIG7, 0x20), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_PCS_TX_RX_CONFIG8, 0x20), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_PCS_TX_RX_CONFIG10, 0x40), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_DIVTX_CLK_DCC_CAL_CONFIG2, 0x00), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_DIVTX_CLK_DCC_CAL_CONFIG3, 0x00), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_DIVTX_CLK_DCC_CAL_CONFIG4, 0x00), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_ENDPOINT_REFCLK_DRIVE, 0xc1), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_RX_SIGDET_LVL, 0x66), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_OSC_DTCT_ACTIONS, 0x00), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_LOCK_DETECT_CONFIG1, 0xff), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_ALIGN_DETECT_CONFIG1, 0x00), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_ALIGN_DETECT_CONFIG2, 0x04), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_ALIGN_DETECT_CONFIG3, 0x1f), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_ALIGN_DETECT_CONFIG7, 0x35), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_ALIGN_DETECT_CONFIG8, 0x3e), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_EQ_CONFIG1, 0x06), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_EQ_CONFIG2, 0x1c), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_G345_EQ_CONFIG1, 0x03), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_G345_EQ_CONFIG3, 0x00), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_G3_EQ_CONFIG1, 0x00), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_G3_EQ_CONFIG5, 0x90), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_G3_EQ_CONFIG7, 0x0d), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_G4_EQ_CONFIG1, 0x00), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_G4_EQ_CONFIG5, 0x90), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_G4_EQ_CONFIG7, 0x0d), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_G5_EQ_CONFIG1, 0x00), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_G5_EQ_CONFIG5, 0x90), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_G5_EQ_CONFIG7, 0x0d), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_COM_TEST_CONTROL1, 0x08), +}; + +static const struct qmp_phy_init_tbl nord_qmp_pcie_pcs_lanez_tbl[] = { + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_LANEZ_OUTSIG_MX_CTRL2, 0x00), + QMP_PHY_INIT_CFG(QPHY_PCIE_V6_40_PCS_LANEZ_OUTSIG_MX_CTRL5, 0x00), +}; + +static const struct qmp_phy_init_tbl nord_qmp_pcie_port_split_pre_pwrup_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_PRE_STALL_LDO_BOOST_EN, 0x80), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_LDO_TIMER_CTRL, 0x91), +}; + +static const struct qmp_phy_init_tbl nord_qmp_pcie_port_split_post_cfg_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_LDO_TIMER_CTRL, 0x11), + QMP_PHY_INIT_CFG(QSERDES_PCIE_V6_40_TXRXZ_PRE_STALL_LDO_BOOST_EN, 0x00), +}; + +static const struct qmp_phy_cfg_tbls nord_qmp_pcie_tbls = { + .pll_common = nord_qmp_pcie_pll_tbl, + .pll_common_num = ARRAY_SIZE(nord_qmp_pcie_pll_tbl), + .txrxz = nord_qmp_pcie_txrxz_tbl, + .txrxz_num = ARRAY_SIZE(nord_qmp_pcie_txrxz_tbl), + .pcs = nord_qmp_pcie_pcs_tbl, + .pcs_num = ARRAY_SIZE(nord_qmp_pcie_pcs_tbl), + .pcs_lanez = nord_qmp_pcie_pcs_lanez_tbl, + .pcs_lanez_num = ARRAY_SIZE(nord_qmp_pcie_pcs_lanez_tbl), + .port_split_pre_pwrup = nord_qmp_pcie_port_split_pre_pwrup_tbl, + .port_split_pre_pwrup_num = ARRAY_SIZE(nord_qmp_pcie_port_split_pre_pwrup_tbl), + .port_split_post_cfg = nord_qmp_pcie_port_split_post_cfg_tbl, + .port_split_post_cfg_num = ARRAY_SIZE(nord_qmp_pcie_port_split_post_cfg_tbl), +}; + +static const struct qmp_phy_init_tbl * const nord_qmp_pcie_pll_a[] = { + nord_qmp_pcie_pll_a_ovl_tbl, +}; + +static const int nord_qmp_pcie_pll_a_nums[] = { + ARRAY_SIZE(nord_qmp_pcie_pll_a_ovl_tbl), +}; + +static const struct qmp_phy_init_tbl * const nord_qmp_pcie_pll_b[] = { + nord_qmp_pcie_pll_b_ovl_tbl, +}; + +static const int nord_qmp_pcie_pll_b_nums[] = { + ARRAY_SIZE(nord_qmp_pcie_pll_b_ovl_tbl), +}; + +static const struct qmp_phy_init_tbl * const nord_qmp_pcie_pll_c[] = { + nord_qmp_pcie_pll_c_ovl_tbl, +}; + +static const int nord_qmp_pcie_pll_c_nums[] = { + ARRAY_SIZE(nord_qmp_pcie_pll_c_ovl_tbl), +}; + +static const struct qmp_phy_init_tbl * const nord_qmp_pcie_pll_d[] = { + nord_qmp_pcie_pll_d_ovl_tbl, +}; + +static const int nord_qmp_pcie_pll_d_nums[] = { + ARRAY_SIZE(nord_qmp_pcie_pll_d_ovl_tbl), +}; + +static const struct qmp_phy_init_tbl * const nord_qmp_pcie_pll_cd[] = { + nord_qmp_pcie_pll_c_ovl_tbl, nord_qmp_pcie_pll_d_ovl_tbl, +}; + +static const int nord_qmp_pcie_pll_cd_nums[] = { + ARRAY_SIZE(nord_qmp_pcie_pll_c_ovl_tbl), ARRAY_SIZE(nord_qmp_pcie_pll_d_ovl_tbl), +}; + +static const struct qmp_phy_init_tbl * const nord_qmp_pcie_pll_bcd[] = { + nord_qmp_pcie_pll_b_ovl_tbl, nord_qmp_pcie_pll_c_ovl_tbl, nord_qmp_pcie_pll_d_ovl_tbl, +}; + +static const int nord_qmp_pcie_pll_bcd_nums[] = { + ARRAY_SIZE(nord_qmp_pcie_pll_b_ovl_tbl), ARRAY_SIZE(nord_qmp_pcie_pll_c_ovl_tbl), + ARRAY_SIZE(nord_qmp_pcie_pll_d_ovl_tbl), +}; + +static const struct qmp_phy_init_tbl * const nord_qmp_pcie_pll_abcd[] = { + nord_qmp_pcie_pll_a_ovl_tbl, nord_qmp_pcie_pll_b_ovl_tbl, + nord_qmp_pcie_pll_c_ovl_tbl, nord_qmp_pcie_pll_d_ovl_tbl, +}; + +static const int nord_qmp_pcie_pll_abcd_nums[] = { + ARRAY_SIZE(nord_qmp_pcie_pll_a_ovl_tbl), ARRAY_SIZE(nord_qmp_pcie_pll_b_ovl_tbl), + ARRAY_SIZE(nord_qmp_pcie_pll_c_ovl_tbl), ARRAY_SIZE(nord_qmp_pcie_pll_d_ovl_tbl), +}; + +static const char * const nord_pciephy_port_c_reg_l[] = { "port_c" }; + +static const char * const nord_pciephy_port_d_reg_l[] = { "port_d" }; + +static const char * const nord_pciephy_port_cd_reg_l[] = { "port_c", "port_d" }; + +static const char * const nord_pciephy_port_bcd_reg_l[] = { + "port_b", "port_c", "port_d", +}; + +static const char * const nord_pciephy_port_abcd_reg_l[] = { + "port_a", "port_b", "port_c", "port_d", +}; + +static const char * const nord_pciephy_port_a_reset_l[] = { "port_a" }; + +static const char * const nord_pciephy_port_b_reset_l[] = { "port_b" }; + +static const char * const nord_pciephy_port_c_reset_l[] = { "port_c" }; + +static const char * const nord_pciephy_port_d_reset_l[] = { "port_d" }; + +static const char * const nord_pciephy_port_cd_reset_l[] = { + "port_c", "port_d", +}; + +static const char * const nord_pciephy_port_bcd_reset_l[] = { + "port_b", "port_c", "port_d", +}; + +static const char * const nord_pciephy_port_abcd_reset_l[] = { + "port_a", "port_b", "port_c", "port_d", +}; + +static const char * const nord_pciephy_port_a_nocsr_reset_l[] = { + "port_a_nocsr", +}; + +static const char * const nord_pciephy_port_b_nocsr_reset_l[] = { + "port_b_nocsr", +}; + +static const char * const nord_pciephy_port_c_nocsr_reset_l[] = { + "port_c_nocsr", +}; + +static const char * const nord_pciephy_port_d_nocsr_reset_l[] = { + "port_d_nocsr", +}; + +static const char * const nord_pciephy_port_cd_nocsr_reset_l[] = { + "port_c_nocsr", "port_d_nocsr", +}; + +static const char * const nord_pciephy_port_bcd_nocsr_reset_l[] = { + "port_b_nocsr", "port_c_nocsr", "port_d_nocsr", +}; + +static const char * const nord_pciephy_port_abcd_nocsr_reset_l[] = { + "port_a_nocsr", "port_b_nocsr", "port_c_nocsr", "port_d_nocsr", +}; + +static const char * const nord_pciephy_port_a_pd_l[] = { "port_a" }; + +static const char * const nord_pciephy_port_b_pd_l[] = { + "port_a", "port_b", +}; + +static const char * const nord_pciephy_port_c_pd_l[] = { + "port_a", "port_c", +}; + +static const char * const nord_pciephy_port_d_pd_l[] = { + "port_a", "port_d", +}; + +static const char * const nord_pciephy_port_cd_pd_l[] = { + "port_a", "port_c", "port_d", +}; + +static const char * const nord_pciephy_port_bcd_pd_l[] = { + "port_a", "port_b", "port_c", "port_d", +}; + +static const char * const nord_pciephy_port_abcd_pd_l[] = { + "port_a", "port_b", "port_c", "port_d", +}; + +static const char * const nord_pciephy_port_a_clk_l[] = { + "aux_a", "cfg_ahb_a", "rchng_a", "ref", +}; + +static const char * const nord_pciephy_port_b_clk_l[] = { + "aux_a", "aux_b", "cfg_ahb_b", "rchng_b", "ref", +}; + +static const char * const nord_pciephy_port_c_clk_l[] = { + "aux_a", "aux_c", "cfg_ahb_c", "rchng_c", "ref", +}; + +static const char * const nord_pciephy_port_d_clk_l[] = { + "aux_a", "aux_d", "cfg_ahb_d", "rchng_d", "ref", +}; + +static const char * const nord_pciephy_port_cd_clk_l[] = { + "aux_a", "aux_c", "cfg_ahb_c", "rchng_c", + "aux_d", "cfg_ahb_d", "rchng_d", "ref", +}; + +static const char * const nord_pciephy_port_bcd_clk_l[] = { + "aux_a", "aux_b", "cfg_ahb_b", "rchng_b", + "aux_c", "cfg_ahb_c", "rchng_c", + "aux_d", "cfg_ahb_d", "rchng_d", "ref", +}; + +static const char * const nord_pciephy_port_abcd_clk_l[] = { + "aux_a", "cfg_ahb_a", "rchng_a", + "aux_b", "cfg_ahb_b", "rchng_b", + "aux_c", "cfg_ahb_c", "rchng_c", + "aux_d", "cfg_ahb_d", "rchng_d", "ref", +}; + +static const char * const nord_pciephy_port_a_pipeclk_l[] = { "pipe_a" }; + +static const char * const nord_pciephy_port_b_pipeclk_l[] = { "pipe_b" }; + +static const char * const nord_pciephy_port_c_pipeclk_l[] = { "pipe_c" }; + +static const char * const nord_pciephy_port_d_pipeclk_l[] = { "pipe_d" }; + +#define NORD_QMP_PCIE_PORT_CFG(_port, _reg_l, _pd_l, _reset_l, _nocsr_reset_l, _pipeclk_l, \ + _port_split) \ +static const struct qmp_phy_cfg nord_qmp_pcie_port_##_port##_cfg = { \ + .offsets = &nord_pcie_offsets_v6_40, \ + .tbls = nord_qmp_pcie_tbls, \ + .pll_tbls = nord_qmp_pcie_pll_##_port, \ + .pll_nums = nord_qmp_pcie_pll_##_port##_nums, \ + .port_split = _port_split, \ + .reg_names = _reg_l, \ + .num_regs = ARRAY_SIZE(_reg_l), \ + .pd_names = _pd_l, \ + .num_pds = ARRAY_SIZE(_pd_l), \ + .reset_list = _reset_l, \ + .num_resets = ARRAY_SIZE(_reset_l), \ + .nocsr_reset_list = _nocsr_reset_l, \ + .num_nocsr_resets = ARRAY_SIZE(_nocsr_reset_l), \ + .vreg_list = nord_pciephy_port_##_port##_vreg_l, \ + .num_vregs = ARRAY_SIZE(nord_pciephy_port_##_port##_vreg_l), \ + .regs = nord_pciephy_v6_40_regs_layout, \ + .phy_status = PHYSTATUS_4_20, \ + .clk_list = nord_pciephy_port_##_port##_clk_l, \ + .num_clks = ARRAY_SIZE(nord_pciephy_port_##_port##_clk_l), \ + .pipe_clk_list = _pipeclk_l, \ + .num_pipe_clks = 1, \ +} + +NORD_QMP_PCIE_PORT_CFG(a, pciephy_port_a_reg_l, nord_pciephy_port_a_pd_l, + nord_pciephy_port_a_reset_l, pciephy_port_a_nocsr_reset_l, + nord_pciephy_port_a_pipeclk_l, true); +NORD_QMP_PCIE_PORT_CFG(b, pciephy_port_b_reg_l, nord_pciephy_port_b_pd_l, + nord_pciephy_port_b_reset_l, pciephy_port_b_nocsr_reset_l, + nord_pciephy_port_b_pipeclk_l, true); +NORD_QMP_PCIE_PORT_CFG(c, nord_pciephy_port_c_reg_l, nord_pciephy_port_c_pd_l, + nord_pciephy_port_c_reset_l, nord_pciephy_port_c_nocsr_reset_l, + nord_pciephy_port_c_pipeclk_l, true); +NORD_QMP_PCIE_PORT_CFG(d, nord_pciephy_port_d_reg_l, nord_pciephy_port_d_pd_l, + nord_pciephy_port_d_reset_l, nord_pciephy_port_d_nocsr_reset_l, + nord_pciephy_port_d_pipeclk_l, true); +NORD_QMP_PCIE_PORT_CFG(cd, nord_pciephy_port_cd_reg_l, nord_pciephy_port_cd_pd_l, + nord_pciephy_port_cd_reset_l, nord_pciephy_port_cd_nocsr_reset_l, + nord_pciephy_port_c_pipeclk_l, true); +NORD_QMP_PCIE_PORT_CFG(bcd, nord_pciephy_port_bcd_reg_l, nord_pciephy_port_bcd_pd_l, + nord_pciephy_port_bcd_reset_l, nord_pciephy_port_bcd_nocsr_reset_l, + nord_pciephy_port_b_pipeclk_l, true); +NORD_QMP_PCIE_PORT_CFG(abcd, nord_pciephy_port_abcd_reg_l, nord_pciephy_port_abcd_pd_l, + nord_pciephy_port_abcd_reset_l, nord_pciephy_port_abcd_nocsr_reset_l, + nord_pciephy_port_a_pipeclk_l, false); + +static const struct qmp_phy_cfg * const nord_qmp_gen5x16_mode_x16_cfgs[] = { + &nord_qmp_pcie_port_abcd_cfg, +}; + +static const struct qmp_phy_cfg * const nord_qmp_gen5x16_mode_x8x8_cfgs[] = { + &nord_qmp_pcie_port_a_cfg, + &nord_qmp_pcie_port_bcd_cfg, +}; + +static const struct qmp_phy_cfg * const nord_qmp_gen5x16_mode_x8x4x4_cfgs[] = { + &nord_qmp_pcie_port_a_cfg, + &nord_qmp_pcie_port_b_cfg, + &nord_qmp_pcie_port_cd_cfg, +}; + +static const struct qmp_phy_cfg * const nord_qmp_gen5x16_mode_x8x4x2x2_cfgs[] = { + &nord_qmp_pcie_port_a_cfg, + &nord_qmp_pcie_port_b_cfg, + &nord_qmp_pcie_port_c_cfg, + &nord_qmp_pcie_port_d_cfg, +}; + +static const struct qmp_pcie_link_mode_cfg nord_qmp_gen5x16_mode_cfgs[] = { + [QMP_PCIE_NORD_MODE_X16] = { + .cfgs = nord_qmp_gen5x16_mode_x16_cfgs, + .num_phys = ARRAY_SIZE(nord_qmp_gen5x16_mode_x16_cfgs), + }, + [QMP_PCIE_NORD_MODE_X8_X8] = { + .cfgs = nord_qmp_gen5x16_mode_x8x8_cfgs, + .num_phys = ARRAY_SIZE(nord_qmp_gen5x16_mode_x8x8_cfgs), + }, + [QMP_PCIE_NORD_MODE_X8_X4_X4] = { + .cfgs = nord_qmp_gen5x16_mode_x8x4x4_cfgs, + .num_phys = ARRAY_SIZE(nord_qmp_gen5x16_mode_x8x4x4_cfgs), + }, + [QMP_PCIE_NORD_MODE_X8_X4_X2_X2] = { + .cfgs = nord_qmp_gen5x16_mode_x8x4x2x2_cfgs, + .num_phys = ARRAY_SIZE(nord_qmp_gen5x16_mode_x8x4x2x2_cfgs), + }, +}; + +static const struct qmp_pcie_match_data nord_qmp_gen5x16_match_data = { + .mode_cfgs = nord_qmp_gen5x16_mode_cfgs, + .num_modes = ARRAY_SIZE(nord_qmp_gen5x16_mode_cfgs), +}; + +static void qmp_pcie_init_port_registers(struct qmp_pcie *qmp) +{ + const struct qmp_phy_cfg *cfg = qmp->cfg; + const struct qmp_pcie_offsets *offs = cfg->offsets; + const struct qmp_phy_cfg_tbls *tbls = &cfg->tbls; + int i; + + for (i = 0; i < cfg->num_regs; i++) { + void __iomem *port = qmp->base[i]; + + if (cfg->port_split) + qmp_configure(qmp->dev, port + offs->txrxz, + tbls->port_split_pre_pwrup, tbls->port_split_pre_pwrup_num); + + writel(SW_PWRDN | REFCLK_DRV_DSBL, + port + offs->pcs + cfg->regs[QPHY_PCS_POWER_DOWN_CONTROL]); + + if (cfg->port_split) + qphy_setbits(port + offs->pll, cfg->regs[QPHY_COM_BIAS_EN_CLKBUFLR_EN], + COM_BIAS_EN_MUX | COM_BIAS_EN); + + qmp_configure(qmp->dev, port + offs->pll, + tbls->pll_common, tbls->pll_common_num); + qmp_configure(qmp->dev, port + offs->pll, + cfg->pll_tbls[i], cfg->pll_nums[i]); + qmp_configure(qmp->dev, port + offs->txrxz, + tbls->txrxz, tbls->txrxz_num); + qmp_configure(qmp->dev, port + offs->pcs, + tbls->pcs, tbls->pcs_num); + qmp_configure(qmp->dev, port + offs->pcs_lanez, + tbls->pcs_lanez, tbls->pcs_lanez_num); + + if (cfg->port_split) + qmp_configure(qmp->dev, port + offs->txrxz, + tbls->port_split_post_cfg, tbls->port_split_post_cfg_num); + } +} + static int qmp_pcie_pd_power_on(struct qmp_pcie *qmp) { const struct qmp_phy_cfg *cfg = qmp->cfg; @@ -256,32 +1007,76 @@ static int qmp_pcie_init(struct phy *phy) { struct qmp_pcie *qmp = phy_get_drvdata(phy); const struct qmp_phy_cfg *cfg = qmp->cfg; - int ret; + const struct qmp_pcie_offsets *offs = cfg->offsets; + int i, ret; ret = qmp_pcie_pd_power_on(qmp); if (ret) return ret; + /* + * We can skip PHY initialization if all of the following conditions + * are met: + * 1. The PHY supports the nocsr_reset that preserves the PHY config. + * 2. The PHY was started (and not powered down again) by the + * bootloader, with all of the expected bits set correctly. + * In this case, we can continue without having the init sequence + * defined in the driver. + */ + qmp->skip_init = qmp->cfg->num_nocsr_resets; + for (i = 0; qmp->skip_init && i < cfg->num_regs; i++) { + void __iomem *port = qmp->base[i]; + + if (!qphy_checkbits(port + offs->pcs, cfg->regs[QPHY_PCS_START_CONTROL], + SERDES_START | PCS_START) || + !qphy_checkbits(port + offs->pcs, cfg->regs[QPHY_PCS_POWER_DOWN_CONTROL], + SW_PWRDN | REFCLK_DRV_DSBL)) + qmp->skip_init = false; + } + ret = regulator_bulk_enable(cfg->num_vregs, qmp->vregs); if (ret) { dev_err(qmp->dev, "failed to enable regulators: %d\n", ret); goto err_pd_power_off; } + /* + * Toggle BCR reset for a PHY that doesn't support the nocsr_reset, + * or has not been initialized yet. + */ + if (!qmp->skip_init) { + ret = reset_control_bulk_assert(cfg->num_resets, qmp->resets); + if (ret) { + dev_err(qmp->dev, "reset assert failed: %d\n", ret); + goto err_disable_regulators; + } + } + ret = reset_control_bulk_assert(qmp->cfg->num_nocsr_resets, qmp->nocsr_resets); if (ret) { dev_err(qmp->dev, "no-csr reset assert failed: %d\n", ret); - goto err_disable_regulators; + goto err_assert_reset; } usleep_range(200, 300); + if (!qmp->skip_init) { + ret = reset_control_bulk_deassert(cfg->num_resets, qmp->resets); + if (ret) { + dev_err(qmp->dev, "reset deassert failed: %d\n", ret); + goto err_assert_reset; + } + } + ret = clk_bulk_prepare_enable(qmp->cfg->num_clks, qmp->clks); if (ret) - goto err_disable_regulators; + goto err_assert_reset; return 0; +err_assert_reset: + if (!qmp->skip_init) + reset_control_bulk_assert(cfg->num_resets, qmp->resets); err_disable_regulators: regulator_bulk_disable(cfg->num_vregs, qmp->vregs); err_pd_power_off: @@ -295,7 +1090,8 @@ static int qmp_pcie_exit(struct phy *phy) struct qmp_pcie *qmp = phy_get_drvdata(phy); const struct qmp_phy_cfg *cfg = qmp->cfg; - reset_control_bulk_assert(qmp->cfg->num_nocsr_resets, qmp->nocsr_resets); + if (qmp->nocsr_resets) + reset_control_bulk_assert(qmp->cfg->num_nocsr_resets, qmp->nocsr_resets); clk_bulk_disable_unprepare(qmp->cfg->num_clks, qmp->clks); regulator_bulk_disable(cfg->num_vregs, qmp->vregs); @@ -323,6 +1119,20 @@ static int qmp_pcie_power_on(struct phy *phy) goto err_disable_pipe_clk; } + if (cfg->pll_tbls && !qmp->skip_init) { + qmp_pcie_init_port_registers(qmp); + + for (i = 0; i < cfg->num_regs; i++) { + qphy_clrbits(qmp->base[i] + offs->pcs, + cfg->regs[QPHY_PCS_SW_RESET], SW_RESET); + qphy_setbits(qmp->base[i] + offs->pcs, + cfg->regs[QPHY_PCS_START_CONTROL], + SERDES_START | PCS_START); + } + + usleep_range(2000, 2500); + } + for (i = 0; i < cfg->num_regs; i++) { status = qmp->base[i] + offs->pcs + cfg->regs[QPHY_PCS_STATUS]; ret = readl_poll_timeout(status, val, !(val & cfg->phy_status), 200, @@ -444,20 +1254,37 @@ static int qmp_pcie_reset_init(struct qmp_pcie *qmp) struct device *dev = qmp->dev; int i, ret; - qmp->nocsr_resets = devm_kcalloc(dev, cfg->num_nocsr_resets, - sizeof(*qmp->nocsr_resets), - GFP_KERNEL); - if (!qmp->nocsr_resets) - return -ENOMEM; + if (cfg->num_resets) { + qmp->resets = devm_kcalloc(dev, cfg->num_resets, + sizeof(*qmp->resets), GFP_KERNEL); + if (!qmp->resets) + return -ENOMEM; - for (i = 0; i < cfg->num_nocsr_resets; i++) - qmp->nocsr_resets[i].id = cfg->nocsr_reset_list[i]; + for (i = 0; i < cfg->num_resets; i++) + qmp->resets[i].id = cfg->reset_list[i]; - ret = devm_reset_control_bulk_get_exclusive(dev, - cfg->num_nocsr_resets, - qmp->nocsr_resets); - if (ret) - return dev_err_probe(dev, ret, "failed to get no-csr resets\n"); + ret = devm_reset_control_bulk_get_exclusive(dev, cfg->num_resets, + qmp->resets); + if (ret) + return dev_err_probe(dev, ret, "failed to get resets\n"); + } + + if (cfg->num_nocsr_resets) { + qmp->nocsr_resets = devm_kcalloc(dev, cfg->num_nocsr_resets, + sizeof(*qmp->nocsr_resets), + GFP_KERNEL); + if (!qmp->nocsr_resets) + return -ENOMEM; + + for (i = 0; i < cfg->num_nocsr_resets; i++) + qmp->nocsr_resets[i].id = cfg->nocsr_reset_list[i]; + + ret = devm_reset_control_bulk_get_exclusive(dev, + cfg->num_nocsr_resets, + qmp->nocsr_resets); + if (ret) + return dev_err_probe(dev, ret, "failed to get no-csr resets\n"); + } return 0; } @@ -660,14 +1487,13 @@ static int qmp_pcie_multiphy_probe(struct platform_device *pdev) int ret; qmp_data = devm_kzalloc(dev, sizeof(*qmp_data), GFP_KERNEL); + if (!qmp_data) + return -ENOMEM; match_data = of_device_get_match_data(dev); if (!match_data) return -EINVAL; - if (!qmp_data) - return -ENOMEM; - ret = qmp_pcie_read_link_mode(dev, &link_mode); if (ret) return dev_err_probe(dev, ret, "failed to read qcom,link-mode\n"); @@ -711,6 +1537,10 @@ static const struct of_device_id qmp_pcie_multiphy_of_match_table[] = { .compatible = "qcom,glymur-qmp-gen5x8-pcie-phy", .data = &glymur_qmp_gen5x8_match_data, }, + { + .compatible = "qcom,nord-qmp-gen5x16-pcie-phy", + .data = &nord_qmp_gen5x16_match_data, + }, { } }; MODULE_DEVICE_TABLE(of, qmp_pcie_multiphy_of_match_table); diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-pcs-pcie-v6_40.h b/drivers/phy/qualcomm/phy-qcom-qmp-pcs-pcie-v6_40.h new file mode 100644 index 000000000000..479f6d39d878 --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-qmp-pcs-pcie-v6_40.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef QCOM_PHY_QMP_PCS_PCIE_V6_40_H_ +#define QCOM_PHY_QMP_PCS_PCIE_V6_40_H_ + +/* PCS COM — power, start, clocks */ +#define QPHY_PCIE_V6_40_PCS_COM_POWER_STATE_CONFIG2 0x06c +#define QPHY_PCIE_V6_40_PCS_COM_POWER_STATE_CONFIG5 0x078 +#define QPHY_PCIE_V6_40_PCS_COM_POWER_STATE_CONFIG6 0x07c +#define QPHY_PCIE_V6_40_PCS_COM_POWER_STATE_CONFIG7 0x080 +#define QPHY_PCIE_V6_40_PCS_COM_MULTIPHY_CONFIG2 0x088 +#define QPHY_PCIE_V6_40_PCS_COM_PCS_TX_RX_CONFIG2 0x0a0 +#define QPHY_PCIE_V6_40_PCS_COM_PCS_TX_RX_CONFIG3 0x0a4 +#define QPHY_PCIE_V6_40_PCS_COM_PCS_TX_RX_CONFIG5 0x0ac +#define QPHY_PCIE_V6_40_PCS_COM_PCS_TX_RX_CONFIG6 0x0b0 +#define QPHY_PCIE_V6_40_PCS_COM_PCS_TX_RX_CONFIG7 0x0b4 +#define QPHY_PCIE_V6_40_PCS_COM_PCS_TX_RX_CONFIG8 0x0b8 +#define QPHY_PCIE_V6_40_PCS_COM_PCS_TX_RX_CONFIG10 0x0c0 +#define QPHY_PCIE_V6_40_PCS_COM_ENDPOINT_REFCLK_DRIVE 0x0c8 +#define QPHY_PCIE_V6_40_PCS_COM_RX_SIGDET_LVL 0x0ec +#define QPHY_PCIE_V6_40_PCS_COM_DIVTX_CLK_DCC_CAL_CONFIG2 0x0f8 +#define QPHY_PCIE_V6_40_PCS_COM_DIVTX_CLK_DCC_CAL_CONFIG3 0x0fc +#define QPHY_PCIE_V6_40_PCS_COM_DIVTX_CLK_DCC_CAL_CONFIG4 0x100 +#define QPHY_PCIE_V6_40_PCS_COM_OSC_DTCT_ACTIONS 0x194 +#define QPHY_PCIE_V6_40_PCS_COM_LOCK_DETECT_CONFIG1 0x1bc +#define QPHY_PCIE_V6_40_PCS_COM_ALIGN_DETECT_CONFIG1 0x1dc +#define QPHY_PCIE_V6_40_PCS_COM_ALIGN_DETECT_CONFIG2 0x1e0 +#define QPHY_PCIE_V6_40_PCS_COM_ALIGN_DETECT_CONFIG3 0x1e4 +#define QPHY_PCIE_V6_40_PCS_COM_ALIGN_DETECT_CONFIG7 0x1f4 +#define QPHY_PCIE_V6_40_PCS_COM_ALIGN_DETECT_CONFIG8 0x1f8 + +/* PCS COM — EQ config */ +#define QPHY_PCIE_V6_40_PCS_COM_EQ_CONFIG1 0x380 +#define QPHY_PCIE_V6_40_PCS_COM_EQ_CONFIG2 0x384 +#define QPHY_PCIE_V6_40_PCS_COM_G345_EQ_CONFIG1 0x38c +#define QPHY_PCIE_V6_40_PCS_COM_G345_EQ_CONFIG3 0x394 +#define QPHY_PCIE_V6_40_PCS_COM_G3_EQ_CONFIG1 0x3a4 +#define QPHY_PCIE_V6_40_PCS_COM_G3_EQ_CONFIG5 0x3b4 +#define QPHY_PCIE_V6_40_PCS_COM_G3_EQ_CONFIG7 0x3bc +#define QPHY_PCIE_V6_40_PCS_COM_G4_EQ_CONFIG1 0x3c8 +#define QPHY_PCIE_V6_40_PCS_COM_G4_EQ_CONFIG5 0x3d8 +#define QPHY_PCIE_V6_40_PCS_COM_G4_EQ_CONFIG7 0x3e0 +#define QPHY_PCIE_V6_40_PCS_COM_G5_EQ_CONFIG1 0x3ec +#define QPHY_PCIE_V6_40_PCS_COM_G5_EQ_CONFIG5 0x3fc +#define QPHY_PCIE_V6_40_PCS_COM_G5_EQ_CONFIG7 0x404 +#define QPHY_PCIE_V6_40_PCS_COM_TEST_CONTROL1 0x468 + +/* PCS LANEZ — output signal mux control */ +#define QPHY_PCIE_V6_40_PCS_LANEZ_OUTSIG_MX_CTRL2 0x0e0 +#define QPHY_PCIE_V6_40_PCS_LANEZ_OUTSIG_MX_CTRL5 0x0f8 + +#endif /* QCOM_PHY_QMP_PCS_PCIE_V6_40_H_ */ diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-pcs-v6_40.h b/drivers/phy/qualcomm/phy-qcom-qmp-pcs-v6_40.h new file mode 100644 index 000000000000..c7ac7b3bd97e --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-qmp-pcs-v6_40.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef QCOM_PHY_QMP_PCS_V6_40_H_ +#define QCOM_PHY_QMP_PCS_V6_40_H_ + +#define QPHY_V6_40_PCS_STATUS1 0x010 +#define QPHY_V6_40_PCS_START_CONTROL 0x05c +#define QPHY_V6_40_PCS_SW_RESET 0x060 +#define QPHY_V6_40_PCS_POWER_DOWN_CONTROL 0x064 + +#endif /* QCOM_PHY_QMP_PCS_V6_40_H_ */ diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-qserdes-com-pcie-v6_40.h b/drivers/phy/qualcomm/phy-qcom-qmp-qserdes-com-pcie-v6_40.h new file mode 100644 index 000000000000..0eeab0693e9b --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-qmp-qserdes-com-pcie-v6_40.h @@ -0,0 +1,94 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef QCOM_PHY_QMP_QSERDES_COM_PCIE_V6_40_H_ +#define QCOM_PHY_QMP_QSERDES_COM_PCIE_V6_40_H_ + +#define QSERDES_PCIE_V6_40_COM_SSC_STEP_SIZE1_MODE1 0x000 +#define QSERDES_PCIE_V6_40_COM_SSC_STEP_SIZE2_MODE1 0x004 +#define QSERDES_PCIE_V6_40_COM_CLK_EP_DIV_MODE1 0x00c +#define QSERDES_PCIE_V6_40_COM_CP_CTRL_MODE1 0x010 +#define QSERDES_PCIE_V6_40_COM_PLL_RCTRL_MODE1 0x014 +#define QSERDES_PCIE_V6_40_COM_PLL_CCTRL_MODE1 0x018 +#define QSERDES_PCIE_V6_40_COM_CORECLK_DIV_MODE1 0x01c +#define QSERDES_PCIE_V6_40_COM_LOCK_CMP1_MODE1 0x020 +#define QSERDES_PCIE_V6_40_COM_LOCK_CMP2_MODE1 0x024 +#define QSERDES_PCIE_V6_40_COM_DEC_START_MODE1 0x028 +#define QSERDES_PCIE_V6_40_COM_DIV_FRAC_START1_MODE1 0x030 +#define QSERDES_PCIE_V6_40_COM_DIV_FRAC_START2_MODE1 0x034 +#define QSERDES_PCIE_V6_40_COM_DIV_FRAC_START3_MODE1 0x038 +#define QSERDES_PCIE_V6_40_COM_HSCLK_SEL_1 0x03c +#define QSERDES_PCIE_V6_40_COM_BIN_VCOCAL_CMP_CODE1_MODE1 0x050 +#define QSERDES_PCIE_V6_40_COM_BIN_VCOCAL_CMP_CODE2_MODE1 0x054 +#define QSERDES_PCIE_V6_40_COM_BIN_VCOCAL_CMP_CODE1_MODE0 0x058 +#define QSERDES_PCIE_V6_40_COM_BIN_VCOCAL_CMP_CODE2_MODE0 0x05c +#define QSERDES_PCIE_V6_40_COM_SSC_STEP_SIZE1_MODE0 0x060 +#define QSERDES_PCIE_V6_40_COM_SSC_STEP_SIZE2_MODE0 0x064 +#define QSERDES_PCIE_V6_40_COM_CLK_EP_DIV_MODE0 0x06c +#define QSERDES_PCIE_V6_40_COM_CP_CTRL_MODE0 0x070 +#define QSERDES_PCIE_V6_40_COM_PLL_RCTRL_MODE0 0x074 +#define QSERDES_PCIE_V6_40_COM_PLL_CCTRL_MODE0 0x078 +#define QSERDES_PCIE_V6_40_COM_CORECLK_DIV_MODE0 0x07c +#define QSERDES_PCIE_V6_40_COM_LOCK_CMP1_MODE0 0x080 +#define QSERDES_PCIE_V6_40_COM_LOCK_CMP2_MODE0 0x084 +#define QSERDES_PCIE_V6_40_COM_DEC_START_MODE0 0x088 +#define QSERDES_PCIE_V6_40_COM_DIV_FRAC_START1_MODE0 0x090 +#define QSERDES_PCIE_V6_40_COM_DIV_FRAC_START2_MODE0 0x094 +#define QSERDES_PCIE_V6_40_COM_DIV_FRAC_START3_MODE0 0x098 +#define QSERDES_PCIE_V6_40_COM_HSCLK_HS_SWITCH_SEL_1 0x09c +#define QSERDES_PCIE_V6_40_COM_BG_TIMER 0x0bc +#define QSERDES_PCIE_V6_40_COM_SSC_EN_CENTER 0x0c0 +#define QSERDES_PCIE_V6_40_COM_SSC_PER1 0x0cc +#define QSERDES_PCIE_V6_40_COM_SSC_PER2 0x0d0 +#define QSERDES_PCIE_V6_40_COM_POST_DIV_MUX 0x0d8 +#define QSERDES_PCIE_V6_40_COM_BIAS_EN_CLKBUFLR_EN 0x0dc +#define QSERDES_PCIE_V6_40_COM_CLK_ENABLE1 0x0e0 +#define QSERDES_PCIE_V6_40_COM_SYS_CLK_CTRL 0x0e4 +#define QSERDES_PCIE_V6_40_COM_PLL_IVCO 0x0f4 +#define QSERDES_PCIE_V6_40_COM_CMN_IETRIM 0x0fc +#define QSERDES_PCIE_V6_40_COM_SYSCLK_EN_SEL 0x110 +#define QSERDES_PCIE_V6_40_COM_LOCK_CMP_EN 0x120 +#define QSERDES_PCIE_V6_40_COM_LOCK_CMP_CFG 0x124 +#define QSERDES_PCIE_V6_40_COM_VCO_TUNE_CTRL 0x13c +#define QSERDES_PCIE_V6_40_COM_VCO_TUNE_MAP 0x140 +#define QSERDES_PCIE_V6_40_COM_CLK_SELECT 0x164 +#define QSERDES_PCIE_V6_40_COM_CORE_CLK_EN 0x170 +#define QSERDES_PCIE_V6_40_COM_CMN_CONFIG_1 0x174 +#define QSERDES_PCIE_V6_40_COM_CMN_MISC1 0x188 +#define QSERDES_PCIE_V6_40_COM_CMN_MODE 0x18c +#define QSERDES_PCIE_V6_40_COM_BIN_VCOCAL_HSCLK_SEL_1 0x1a0 +#define QSERDES_PCIE_V6_40_COM_VCO_DC_LEVEL_CTRL 0x19c +#define QSERDES_PCIE_V6_40_COM_HSCLK_SEL_2 0x21c +#define QSERDES_PCIE_V6_40_COM_BIN_VCOCAL_CMP_CODE1_MODE2 0x220 +#define QSERDES_PCIE_V6_40_COM_BIN_VCOCAL_CMP_CODE2_MODE2 0x224 +#define QSERDES_PCIE_V6_40_COM_HSCLK_HS_SWITCH_SEL_2 0x228 +#define QSERDES_PCIE_V6_40_COM_BIN_VCOCAL_HSCLK_SEL_2 0x230 +#define QSERDES_PCIE_V6_40_COM_CMN_CONFIG_2 0x22c +#define QSERDES_PCIE_V6_40_COM_SSC_STEP_SIZE1_MODE2 0x1cc +#define QSERDES_PCIE_V6_40_COM_SSC_STEP_SIZE2_MODE2 0x1d0 +#define QSERDES_PCIE_V6_40_COM_CP_CTRL_MODE2 0x1dc +#define QSERDES_PCIE_V6_40_COM_PLL_RCTRL_MODE2 0x1e0 +#define QSERDES_PCIE_V6_40_COM_PLL_CCTRL_MODE2 0x1e4 +#define QSERDES_PCIE_V6_40_COM_CORECLK_DIV_MODE2 0x1e8 +#define QSERDES_PCIE_V6_40_COM_LOCK_CMP1_MODE2 0x1ec +#define QSERDES_PCIE_V6_40_COM_LOCK_CMP2_MODE2 0x1f0 +#define QSERDES_PCIE_V6_40_COM_DEC_START_MODE2 0x1f4 +#define QSERDES_PCIE_V6_40_COM_DIV_FRAC_START1_MODE2 0x1fc +#define QSERDES_PCIE_V6_40_COM_DIV_FRAC_START2_MODE2 0x200 +#define QSERDES_PCIE_V6_40_COM_DIV_FRAC_START3_MODE2 0x204 +#define QSERDES_PCIE_V6_40_COM_DCC_CAL_1 0x2d8 +#define QSERDES_PCIE_V6_40_COM_DCC_CAL_2 0x2dc +#define QSERDES_PCIE_V6_40_COM_DCC_CAL_3 0x2e0 +#define QSERDES_PCIE_V6_40_COM_PSM_CAL_EN 0x2f0 +#define QSERDES_PCIE_V6_40_COM_CLK_FWD_CONFIG_1 0x2f4 +#define QSERDES_PCIE_V6_40_COM_IP_CTRL_AND_DP_SEL 0x2fc +#define QSERDES_PCIE_V6_40_COM_PLL_SPARE_FOR_ECO 0x2b8 +#define QSERDES_PCIE_V6_40_COM_PLL_IVCO_MODE1 0x0f8 +#define QSERDES_PCIE_V6_40_COM_PLL_IVCO_MODE2 0x218 +#define QSERDES_PCIE_V6_40_COM_DCC_CAL_7 0x300 +#define QSERDES_PCIE_V6_40_COM_DCC_CAL_8 0x304 +#define QSERDES_PCIE_V6_40_COM_DCC_CAL_9 0x308 + +#endif /* QCOM_PHY_QMP_QSERDES_COM_PCIE_V6_40_H_ */ diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-qserdes-txrxz-pcie-v6_40.h b/drivers/phy/qualcomm/phy-qcom-qmp-qserdes-txrxz-pcie-v6_40.h new file mode 100644 index 000000000000..1de976a4d787 --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-qmp-qserdes-txrxz-pcie-v6_40.h @@ -0,0 +1,194 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef QCOM_PHY_QMP_QSERDES_TXRXZ_PCIE_V6_40_H_ +#define QCOM_PHY_QMP_QSERDES_TXRXZ_PCIE_V6_40_H_ + +/* Lane mode / LDO */ +#define QSERDES_PCIE_V6_40_TXRXZ_TOP_LDO_CODE_CTRL1 0x070 +#define QSERDES_PCIE_V6_40_TXRXZ_TOP_LDO_CODE_CTRL2 0x074 +#define QSERDES_PCIE_V6_40_TXRXZ_TOP_LDO_CODE_CTRL3 0x078 +#define QSERDES_PCIE_V6_40_TXRXZ_TOP_LDO_CODE_CTRL4 0x07c +#define QSERDES_PCIE_V6_40_TXRXZ_LANE_MODE_1 0x094 +#define QSERDES_PCIE_V6_40_TXRXZ_LANE_MODE_2 0x098 +#define QSERDES_PCIE_V6_40_TXRXZ_LANE_MODE_3 0x09c +#define QSERDES_PCIE_V6_40_TXRXZ_LANE_MODE_4 0x0a0 +#define QSERDES_PCIE_V6_40_TXRXZ_RESTRIM_VREF_SEL 0x0bc +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE_0_1_B0 0x0f4 +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE_0_1_B1 0x0f8 +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE_0_1_B2 0x0fc +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE_0_1_B3 0x100 +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE_0_1_B4 0x104 +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE_0_1_B5 0x108 +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE_0_1_B6 0x10c +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE_0_1_B7 0x110 +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE2_B0 0x114 +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE2_B1 0x118 +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE2_B2 0x11c +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE2_B3 0x120 +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE2_B4 0x124 +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE2_B5 0x128 +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE2_B6 0x12c +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE2_B7 0x130 +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE3_B0 0x134 +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE3_B1 0x138 +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE3_B2 0x13c +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE3_B3 0x140 +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE3_B4 0x144 +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE3_B5 0x148 +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE3_B6 0x14c +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE3_B7 0x150 +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE4_B0 0x154 +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE4_B1 0x158 +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE4_B2 0x15c +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE4_B3 0x160 +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE4_B4 0x164 +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE4_B5 0x168 +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE4_B6 0x16c +#define QSERDES_PCIE_V6_40_TXRXZ_RX_MODE_RATE4_B7 0x170 + +/* VCO / KVCO calibration */ +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_CTUNE_MEAS_CNT1_RATE0 0x1b4 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_CTUNE_MEAS_CNT2_RATE0 0x1b8 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_CTUNE_MEAS_CNT1_RATE1 0x1bc +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_CTUNE_MEAS_CNT2_RATE1 0x1c0 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_CTUNE_MEAS_CNT1_RATE2 0x1c4 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_CTUNE_MEAS_CNT2_RATE2 0x1c8 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_CTUNE_MEAS_CNT1_RATE3 0x1cc +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_CTUNE_MEAS_CNT2_RATE3 0x1d0 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_CTUNE_MEAS_CNT1_RATE4 0x1d4 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_CTUNE_MEAS_CNT2_RATE4 0x1d8 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_VCTRL_RATE_0_1 0x1dc +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_VCTRL_RATE_2_3 0x1e0 +#define QSERDES_PCIE_V6_40_TXRXZ_KVCO_INIT_RATE_0_1 0x1e8 +#define QSERDES_PCIE_V6_40_TXRXZ_KVCO_INIT_RATE_2_3 0x1ec +#define QSERDES_PCIE_V6_40_TXRXZ_KVCO_INIT_RATE_4 0x1f0 +#define QSERDES_PCIE_V6_40_TXRXZ_KVCO_CODE_OVRD_RATE0 0x1f4 +#define QSERDES_PCIE_V6_40_TXRXZ_KVCO_CODE_OVRD_RATE1 0x1f8 +#define QSERDES_PCIE_V6_40_TXRXZ_KVCO_CODE_OVRD_RATE2 0x1fc +#define QSERDES_PCIE_V6_40_TXRXZ_KVCO_IDEAL_FREQ_DIFF1_RATE0 0x220 +#define QSERDES_PCIE_V6_40_TXRXZ_KVCO_IDEAL_FREQ_DIFF2_RATE0 0x224 +#define QSERDES_PCIE_V6_40_TXRXZ_KVCO_IDEAL_FREQ_DIFF1_RATE1 0x228 +#define QSERDES_PCIE_V6_40_TXRXZ_KVCO_IDEAL_FREQ_DIFF2_RATE1 0x22c +#define QSERDES_PCIE_V6_40_TXRXZ_KVCO_IDEAL_FREQ_DIFF1_RATE2 0x230 +#define QSERDES_PCIE_V6_40_TXRXZ_KVCO_IDEAL_FREQ_DIFF2_RATE2 0x234 +#define QSERDES_PCIE_V6_40_TXRXZ_KVCO_IDEAL_FREQ_DIFF1_RATE3 0x238 +#define QSERDES_PCIE_V6_40_TXRXZ_KVCO_IDEAL_FREQ_DIFF1_RATE4 0x240 +#define QSERDES_PCIE_V6_40_TXRXZ_KP_CODE_OVRD_RATE_2_3 0x250 +#define QSERDES_PCIE_V6_40_TXRXZ_KP_CAL_UPPER_FREQ_DIFF_BND1_RATE0 0x258 +#define QSERDES_PCIE_V6_40_TXRXZ_KP_CAL_UPPER_FREQ_DIFF_BND1_RATE1 0x260 +#define QSERDES_PCIE_V6_40_TXRXZ_KP_CAL_UPPER_FREQ_DIFF_BND1_RATE2 0x268 +#define QSERDES_PCIE_V6_40_TXRXZ_KP_CAL_UPPER_FREQ_DIFF_BND1_RATE3 0x270 +#define QSERDES_PCIE_V6_40_TXRXZ_KP_CAL_UPPER_FREQ_DIFF_BND1_RATE4 0x278 +#define QSERDES_PCIE_V6_40_TXRXZ_KP_CAL_LOWER_FREQ_DIFF_BND_RATE0 0x280 +#define QSERDES_PCIE_V6_40_TXRXZ_KP_CAL_LOWER_FREQ_DIFF_BND_RATE1 0x284 +#define QSERDES_PCIE_V6_40_TXRXZ_KP_CAL_LOWER_FREQ_DIFF_BND_RATE2 0x288 +#define QSERDES_PCIE_V6_40_TXRXZ_KP_CAL_LOWER_FREQ_DIFF_BND_RATE3 0x28c +#define QSERDES_PCIE_V6_40_TXRXZ_KP_CAL_LOWER_FREQ_DIFF_BND_RATE4 0x290 +#define QSERDES_PCIE_V6_40_TXRXZ_RX_SUMMER_CAL_SPD_MODE_RATE_0123 0x2a8 +#define QSERDES_PCIE_V6_40_TXRXZ_RX_IVCM_CAL_CTRL2 0x2c8 +#define QSERDES_PCIE_V6_40_TXRXZ_RX_IVCM_CAL_CTRL3 0x2cc +#define QSERDES_PCIE_V6_40_TXRXZ_RX_IVCM_CAL_CTRL4 0x2d0 + +/* Signal detect */ +#define QSERDES_PCIE_V6_40_TXRXZ_SIGDET_ENABLES 0x31c +#define QSERDES_PCIE_V6_40_TXRXZ_FREQ_LOCK_DET_DLY_RATE0 0x33c +#define QSERDES_PCIE_V6_40_TXRXZ_FREQ_LOCK_DET_DLY_RATE1 0x340 +#define QSERDES_PCIE_V6_40_TXRXZ_FREQ_LOCK_DET_DLY_RATE2 0x344 +#define QSERDES_PCIE_V6_40_TXRXZ_FREQ_LOCK_DET_DLY_RATE3 0x348 +#define QSERDES_PCIE_V6_40_TXRXZ_FREQ_LOCK_DET_DLY_RATE4 0x34c + +/* CDR */ +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_LOCK_CTRL 0x364 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_CP_CUR_FLL_RATE0 0x368 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_CP_CUR_FLL_RATE1 0x36c +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_CP_CUR_FLL_RATE2 0x370 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_CP_CUR_FLL_RATE3 0x374 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_CP_CUR_FLL_RATE4 0x378 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_CP_CUR_PLL_RATE0 0x37c +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_CP_CUR_PLL_RATE1 0x380 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_CP_CUR_PLL_RATE2 0x384 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_CP_CUR_PLL_RATE3 0x388 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_CP_CUR_PLL_RATE4 0x38c +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_FLL_DIV_RATIO_RATE_0123 0x390 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_LOOP_CCODE_RATE_01 0x398 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_LOOP_CCODE_RATE_23 0x39c +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_LOOP_CCODE_RATE4 0x3a0 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_LOOP_RCODE_FAST_RATE_0_1 0x3a4 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_LOOP_RCODE_FAST_RATE_2_3 0x3a8 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_LOOP_RCODE_FAST_RATE4 0x3ac +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_LOOP_RCODE_FLL_RATE_0_1 0x3b0 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_LOOP_RCODE_FLL_RATE_2_3 0x3b4 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_LOOP_RCODE_FLL_RATE4 0x3b8 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_LOOP_RCODE_PLL_RATE_0_1 0x3bc +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_LOOP_RCODE_PLL_RATE_2_3 0x3c0 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_LOOP_RCODE_PLL_RATE4 0x3c4 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_CAP_CODE_RATE_0123 0x3c8 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_TYPE_CONFIG 0x3d0 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_EN_LOWFREQ 0x3d4 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_LOOP_FUNC_CTRL 0x3dc + +/* Rescode / residuals */ +#define QSERDES_PCIE_V6_40_TXRXZ_RES_CODE_LANE_OFFSET_RX 0x3f4 + +/* VGA / GM calibration */ +#define QSERDES_PCIE_V6_40_TXRXZ_GM_CAL_RES_RATE0_1 0x404 +#define QSERDES_PCIE_V6_40_TXRXZ_GM_CAL_RES_RATE2_3 0x408 +#define QSERDES_PCIE_V6_40_TXRXZ_GM_CAL_RES_RATE4 0x40c + +/* RX equalizer */ +#define QSERDES_PCIE_V6_40_TXRXZ_RX_EQU_ADAPTOR_CNTRL3 0x454 +#define QSERDES_PCIE_V6_40_TXRXZ_RX_EQU_ADAPTOR_CNTRL4 0x458 +#define QSERDES_PCIE_V6_40_TXRXZ_RX_EQU_ADAPTOR_CNTRL5 0x45c +#define QSERDES_PCIE_V6_40_TXRXZ_CTLE_POST_CAL_OFFSET_RATE_0_1_2 0x478 +#define QSERDES_PCIE_V6_40_TXRXZ_VGA_CAL_MAN_VAL_RATE0_1 0x490 +#define QSERDES_PCIE_V6_40_TXRXZ_VGA_CAL_MAN_VAL_RATE2_3 0x494 +#define QSERDES_PCIE_V6_40_TXRXZ_VGA_CAL_MAN_VAL_RATE4 0x498 +#define QSERDES_PCIE_V6_40_TXRXZ_VTHRESH_CAL_MAN_VAL_RATE4 0x4bc + +/* DFE */ +#define QSERDES_PCIE_V6_40_TXRXZ_DFE_TAP1_DAC_ENABLE 0x50c +#define QSERDES_PCIE_V6_40_TXRXZ_DFE_TAP2_DAC_ENABLE 0x510 +#define QSERDES_PCIE_V6_40_TXRXZ_DFE_TAP345_DAC_ENABLE 0x514 +#define QSERDES_PCIE_V6_40_TXRXZ_DFE_TAP67_DAC_ENABLE 0x518 +#define QSERDES_PCIE_V6_40_TXRXZ_PHPRE_CTRL 0x51c +#define QSERDES_PCIE_V6_40_TXRXZ_TX_ADAPT_POST_THRESH1 0x52c +#define QSERDES_PCIE_V6_40_TXRXZ_TX_ADAPT_POST_THRESH2 0x530 +#define QSERDES_PCIE_V6_40_TXRXZ_TX_ADPT_CTRL 0x53c +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_IQTUNE_CTRL 0x548 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_IQTUNE_MAN_INDEX 0x550 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_IQTUNE_ANA_CTRL 0x584 +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_IQTUNE_DIV2_CTRL_RATE0123 0x58c +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_CAP_CODE_OVRD_MUXES 0x5ac +#define QSERDES_PCIE_V6_40_TXRXZ_CDR_VCO_CAP_CODE_RATE4 0x3cc + +/* Clock buffer / LDO stall control (used by bifurcated pre/post-pwrup sequences) */ +#define QSERDES_PCIE_V6_40_TXRXZ_CLKBUF_ENABLE 0x04c +#define QSERDES_PCIE_V6_40_TXRXZ_PRE_STALL_LDO_BOOST_EN 0x06c +#define QSERDES_PCIE_V6_40_TXRXZ_LDO_TIMER_CTRL 0x0d8 + +/* Restrim / VGA / signal-detect (v2-only) */ +#define QSERDES_PCIE_V6_40_TXRXZ_RESTRIM_CAL_CTRL 0x0b0 +#define QSERDES_PCIE_V6_40_TXRXZ_RESTRIM_POST_CAL_OFFSET 0x0b8 +#define QSERDES_PCIE_V6_40_TXRXZ_SIGDET_CNTRL 0x320 +#define QSERDES_PCIE_V6_40_TXRXZ_SIGDET_LVL 0x324 +#define QSERDES_PCIE_V6_40_TXRXZ_SIGDET_CAL_CTRL1 0x32c +#define QSERDES_PCIE_V6_40_TXRXZ_VGA_CAL_CNTRL1 0x488 + +/* BLW / VCO coarse-tune bands (v2-only) */ +#define QSERDES_PCIE_V6_40_TXRXZ_BLW_CTRL 0x594 +#define QSERDES_PCIE_V6_40_TXRXZ_BLW_MAN_VAL_RATE3 0x5a4 +#define QSERDES_PCIE_V6_40_TXRXZ_BLW_MAN_VAL_RATE4 0x5a8 +#define QSERDES_PCIE_V6_40_TXRXZ_VCO_CTUNE_UPPER_BND_RATE0 0x5c4 +#define QSERDES_PCIE_V6_40_TXRXZ_VCO_CTUNE_UPPER_BND_RATE1 0x5c8 +#define QSERDES_PCIE_V6_40_TXRXZ_VCO_CTUNE_UPPER_BND_RATE2 0x5cc +#define QSERDES_PCIE_V6_40_TXRXZ_VCO_CTUNE_UPPER_BND_RATE3 0x5d0 +#define QSERDES_PCIE_V6_40_TXRXZ_VCO_CTUNE_UPPER_BND_RATE4 0x5d4 + +/* Analog backup control (v2-only) */ +#define QSERDES_PCIE_V6_40_TXRXZ_DIG_BKUP_CTRL15 0x7e8 +#define QSERDES_PCIE_V6_40_TXRXZ_DIG_BKUP_CTRL16 0x7ec + +#endif /* QCOM_PHY_QMP_QSERDES_TXRXZ_PCIE_V6_40_H_ */ From 4d1f3875f32894c4082b79540fe3d77cede6d260 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 21 Sep 2026 13:47:05 +0200 Subject: [PATCH 074/114] dt-bindings: phy: qcom,snps-eusb2-phy: Document the Nord eUSB2 PHY Add a compatible for the eUSB2 PHY found on the Qualcomm Nord SoC. It is compatible with the sm8550 eUSB2 PHY. While at it: On Nord SoC there are three eUSB2 PHYs but only two QUSB2PHY block resets: the HS-only USB20 tile has no dedicated PHY reset and is instead reset as a whole through the controller's own USB20_PRIM_BCR. Make the 'resets' property optional for qcom,nord-snps-eusb2-phy only, keeping it required for all the other compatibles. Signed-off-by: Bartosz Golaszewski Signed-off-by: Shawn Guo Reviewed-by: Krzysztof Kozlowski Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260921-nord-usb-phy-v3-1-68aa02b12b96@oss.qualcomm.com Signed-off-by: Vinod Koul --- .../bindings/phy/qcom,snps-eusb2-phy.yaml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/phy/qcom,snps-eusb2-phy.yaml b/Documentation/devicetree/bindings/phy/qcom,snps-eusb2-phy.yaml index 096f6b546632..931cd0e7d297 100644 --- a/Documentation/devicetree/bindings/phy/qcom,snps-eusb2-phy.yaml +++ b/Documentation/devicetree/bindings/phy/qcom,snps-eusb2-phy.yaml @@ -19,6 +19,7 @@ properties: - enum: - qcom,eliza-snps-eusb2-phy - qcom,milos-snps-eusb2-phy + - qcom,nord-snps-eusb2-phy - qcom,sar2130p-snps-eusb2-phy - qcom,sdx75-snps-eusb2-phy - qcom,sm8650-snps-eusb2-phy @@ -64,7 +65,17 @@ required: - clock-names - vdd-supply - vdda12-supply - - resets + +allOf: + - if: + not: + properties: + compatible: + contains: + const: qcom,nord-snps-eusb2-phy + then: + required: + - resets additionalProperties: false From 674e602140a19a63c406e1096d0aeb76e8e16af9 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 21 Sep 2026 13:47:06 +0200 Subject: [PATCH 075/114] dt-bindings: phy: qcom,sc8280xp-qmp-usb43dp-phy: Document the Nord QMP PHY Add a compatible for the USB3+DP combo QMP PHY found on the Qualcomm Nord SoC. The PHY sits behind a GDSC, so also allow (and require) power-domains for it. The block gets a standalone compatible rather than a fallback to an existing one, because it is not register-compatible with any PHY already described here. Its USB3 and DP initialisation sequences do match sm8550, but Nord places the sub-blocks at different offsets within the PHY: it exposes a dedicated USB3_PCS_AON region at 0x1e00, which sm8550 does not have at all. Falling back to sm8550 - or to any other entry - would make the driver program the wrong addresses. Signed-off-by: Bartosz Golaszewski Assisted-by: LLM Acked-by: Krzysztof Kozlowski Signed-off-by: Shawn Guo Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260921-nord-usb-phy-v3-2-68aa02b12b96@oss.qualcomm.com Signed-off-by: Vinod Koul --- .../devicetree/bindings/phy/qcom,sc8280xp-qmp-usb43dp-phy.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-usb43dp-phy.yaml b/Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-usb43dp-phy.yaml index 43e90995709f..13e64fe510b8 100644 --- a/Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-usb43dp-phy.yaml +++ b/Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-usb43dp-phy.yaml @@ -31,6 +31,7 @@ properties: - enum: - qcom,glymur-qmp-usb3-dp-phy - qcom,hawi-qmp-usb3-dp-phy + - qcom,nord-qmp-usb3-dp-phy - qcom,sar2130p-qmp-usb3-dp-phy - qcom,sc7180-qmp-usb3-dp-phy - qcom,sc7280-qmp-usb3-dp-phy @@ -249,6 +250,7 @@ allOf: enum: - qcom,glymur-qmp-usb3-dp-phy - qcom,hawi-qmp-usb3-dp-phy + - qcom,nord-qmp-usb3-dp-phy - qcom,sar2130p-qmp-usb3-dp-phy - qcom,sc8280xp-qmp-usb43dp-phy - qcom,sm6350-qmp-usb3-dp-phy From bb0417288ea40a5f57676529cae95f3c20bd697c Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 21 Sep 2026 13:47:07 +0200 Subject: [PATCH 076/114] phy: qcom: qmp-combo: Add USB3+DP PHY support for Nord Nord SoC provides two USB3+DP combo PHYs. Their initialisation sequences are identical to sm8550, but the sub-blocks are not laid out the same way: Nord adds a dedicated USB3_PCS_AON region at offset 0x1e00 and leaves everything else where sm8550 has it. None of the existing offsets tables describe that, so add a v6 table which is the v3 layout plus usb3_pcs_aon. Populating usb3_pcs_aon changes where the driver drives the I/O clamp when entering and leaving autonomous mode: it now uses the real AON region rather than falling back to PCS_MISC, which is what platforms with no AON region do. Signed-off-by: Bartosz Golaszewski Assisted-by: LLM Reviewed-by: Abel Vesa Signed-off-by: Shawn Guo Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260921-nord-usb-phy-v3-3-68aa02b12b96@oss.qualcomm.com Signed-off-by: Vinod Koul --- drivers/phy/qualcomm/phy-qcom-qmp-combo.c | 80 +++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-combo.c b/drivers/phy/qualcomm/phy-qcom-qmp-combo.c index f38c3db2039c..187f0cd11f6c 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp-combo.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp-combo.c @@ -2356,6 +2356,20 @@ static const struct qmp_phy_init_tbl hawi_usb3_pcs_usb_tbl[] = { QMP_PHY_INIT_CFG(QPHY_V10_PCS_USB3_RCVR_DTCT_DLY_U3_H, 0x00), }; +static const struct qmp_phy_init_tbl nord_usb3_tx_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_V6_TX_RES_CODE_LANE_TX, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V6_TX_RES_CODE_LANE_RX, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V6_TX_RES_CODE_LANE_OFFSET_TX, 0x1f), + QMP_PHY_INIT_CFG(QSERDES_V6_TX_RES_CODE_LANE_OFFSET_RX, 0x09), + QMP_PHY_INIT_CFG(QSERDES_V6_TX_LANE_MODE_1, 0xf5), + QMP_PHY_INIT_CFG(QSERDES_V6_TX_LANE_MODE_3, 0x3f), + QMP_PHY_INIT_CFG(QSERDES_V6_TX_LANE_MODE_4, 0x3f), + QMP_PHY_INIT_CFG(QSERDES_V6_TX_LANE_MODE_5, 0x5f), + QMP_PHY_INIT_CFG(QSERDES_V6_TX_RCV_DETECT_LVL_2, 0x14), + QMP_PHY_INIT_CFG_LANE(QSERDES_V6_TX_PI_QEC_CTRL, 0x21, 1), + QMP_PHY_INIT_CFG_LANE(QSERDES_V6_TX_PI_QEC_CTRL, 0x05, 2), +}; + /* list of regulators */ static struct regulator_bulk_data qmp_phy_vreg_l[] = { { .supply = "vdda-phy", .init_load_uA = 21800, }, @@ -2696,6 +2710,23 @@ static const struct qmp_combo_offsets qmp_combo_offsets_v5 = { .dp_dp_phy = 0x2200, }; +static const struct qmp_combo_offsets qmp_combo_offsets_v6 = { + .com = 0x0000, + .txa = 0x1200, + .rxa = 0x1400, + .txb = 0x1600, + .rxb = 0x1800, + .usb3_serdes = 0x1000, + .usb3_pcs_misc = 0x1a00, + .usb3_pcs = 0x1c00, + .usb3_pcs_aon = 0x1e00, + .usb3_pcs_usb = 0x1f00, + .dp_serdes = 0x2000, + .dp_txa = 0x2200, + .dp_txb = 0x2600, + .dp_dp_phy = 0x2a00, +}; + static const struct qmp_combo_offsets qmp_combo_offsets_v8 = { .com = 0x0000, .txa = 0x1400, @@ -3400,6 +3431,51 @@ static const struct qmp_phy_cfg glymur_usb3dpphy_cfg = { .num_vregs = ARRAY_SIZE(qmp_phy_vreg_refgen), }; +static const struct qmp_phy_cfg nord_usb3dpphy_cfg = { + .offsets = &qmp_combo_offsets_v6, + + .serdes_tbl = sm8550_usb3_serdes_tbl, + .serdes_tbl_num = ARRAY_SIZE(sm8550_usb3_serdes_tbl), + .tx_tbl = nord_usb3_tx_tbl, + .tx_tbl_num = ARRAY_SIZE(nord_usb3_tx_tbl), + .rx_tbl = sm8550_usb3_rx_tbl, + .rx_tbl_num = ARRAY_SIZE(sm8550_usb3_rx_tbl), + .pcs_tbl = sm8550_usb3_pcs_tbl, + .pcs_tbl_num = ARRAY_SIZE(sm8550_usb3_pcs_tbl), + .pcs_usb_tbl = sm8550_usb3_pcs_usb_tbl, + .pcs_usb_tbl_num = ARRAY_SIZE(sm8550_usb3_pcs_usb_tbl), + + .dp_serdes_tbl = qmp_v6_dp_serdes_tbl, + .dp_serdes_tbl_num = ARRAY_SIZE(qmp_v6_dp_serdes_tbl), + .dp_tx_tbl = qmp_v6_dp_tx_tbl, + .dp_tx_tbl_num = ARRAY_SIZE(qmp_v6_dp_tx_tbl), + + .serdes_tbl_rbr = qmp_v6_dp_serdes_tbl_rbr, + .serdes_tbl_rbr_num = ARRAY_SIZE(qmp_v6_dp_serdes_tbl_rbr), + .serdes_tbl_hbr = qmp_v6_dp_serdes_tbl_hbr, + .serdes_tbl_hbr_num = ARRAY_SIZE(qmp_v6_dp_serdes_tbl_hbr), + .serdes_tbl_hbr2 = qmp_v6_dp_serdes_tbl_hbr2, + .serdes_tbl_hbr2_num = ARRAY_SIZE(qmp_v6_dp_serdes_tbl_hbr2), + .serdes_tbl_hbr3 = qmp_v6_dp_serdes_tbl_hbr3, + .serdes_tbl_hbr3_num = ARRAY_SIZE(qmp_v6_dp_serdes_tbl_hbr3), + + .swing_hbr_rbr = &qmp_dp_v5_voltage_swing_hbr_rbr, + .pre_emphasis_hbr_rbr = &qmp_dp_v6_pre_emphasis_hbr_rbr, + .swing_hbr3_hbr2 = &qmp_dp_v5_voltage_swing_hbr3_hbr2, + .pre_emphasis_hbr3_hbr2 = &qmp_dp_v5_pre_emphasis_hbr3_hbr2, + + .dp_aux_init = qmp_v4_dp_aux_init, + .configure_dp_tx = qmp_v4_configure_dp_tx, + .configure_dp_clocks = qmp_v3_configure_dp_clocks, + .configure_dp_phy = qmp_v4_configure_dp_phy, + + .regs = qmp_v6_usb3phy_regs_layout, + .reset_list = msm8996_usb3phy_reset_l, + .num_resets = ARRAY_SIZE(msm8996_usb3phy_reset_l), + .vreg_list = qmp_phy_vreg_l, + .num_vregs = ARRAY_SIZE(qmp_phy_vreg_l), +}; + static int qmp_combo_dp_serdes_init(struct qmp_combo *qmp) { const struct qmp_phy_cfg *cfg = qmp->cfg; @@ -5597,6 +5673,10 @@ static const struct of_device_id qmp_combo_of_match_table[] = { .compatible = "qcom,hawi-qmp-usb3-dp-phy", .data = &hawi_usb3dpphy_cfg, }, + { + .compatible = "qcom,nord-qmp-usb3-dp-phy", + .data = &nord_usb3dpphy_cfg, + }, { .compatible = "qcom,sar2130p-qmp-usb3-dp-phy", .data = &sar2130p_usb3dpphy_cfg, From a1aebc3846c37ca54977a79ef3af2f667a224891 Mon Sep 17 00:00:00 2001 From: Stefan Wahren Date: Mon, 7 Sep 2026 17:04:19 +0200 Subject: [PATCH 077/114] dt-bindings: phy: ti,tcan104x-can: Document Microchip MCP2542 The Microchip MCP2542 High-speed CAN transceiver with Sleep mode is a pin-compatible alternative for the TI TCAN1042. Signed-off-by: Stefan Wahren Acked-by: Conor Dooley Link: https://patch.msgid.link/20260907150420.18787-4-wahrenst@gmx.net Signed-off-by: Vinod Koul --- Documentation/devicetree/bindings/phy/ti,tcan104x-can.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/phy/ti,tcan104x-can.yaml b/Documentation/devicetree/bindings/phy/ti,tcan104x-can.yaml index 0cf5fa21106b..1a0dc9bbe6c9 100644 --- a/Documentation/devicetree/bindings/phy/ti,tcan104x-can.yaml +++ b/Documentation/devicetree/bindings/phy/ti,tcan104x-can.yaml @@ -18,6 +18,7 @@ properties: - items: - enum: - microchip,ata6561 + - microchip,mcp2542 - ti,tcan1051 - const: ti,tcan1042 - items: From b19cc01678700920f201aaabf9880059cd513975 Mon Sep 17 00:00:00 2001 From: Vinod Koul Date: Sat, 3 Oct 2026 11:32:10 +0200 Subject: [PATCH 078/114] phy: qcom: qmp-pcie: drop unused reset names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit e397c8507e36 ("phy: qcom: qmp-pcie: Add Nord Gen5x16 PCIe multi-PHY support") added a bunch of reset names, but also added two which are not being used. This leads to compiler warnings: drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c:750:27: error: ‘nord_pciephy_port_b_nocsr_reset_l’ defined but not used [-Werror=unused-const-variable=] 750 | static const char * const nord_pciephy_port_b_nocsr_reset_l[] = { | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c:746:27: error: ‘nord_pciephy_port_a_nocsr_reset_l’ defined but not used [-Werror=unused-const-variable=] 746 | static const char * const nord_pciephy_port_a_nocsr_reset_l[] = { | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Drop these Fixes: e397c8507e36 ("phy: qcom: qmp-pcie: Add Nord Gen5x16 PCIe multi-PHY support") Signed-off-by: Vinod Koul --- drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c b/drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c index f1267691bf32..7bc62a668d34 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c @@ -743,14 +743,6 @@ static const char * const nord_pciephy_port_abcd_reset_l[] = { "port_a", "port_b", "port_c", "port_d", }; -static const char * const nord_pciephy_port_a_nocsr_reset_l[] = { - "port_a_nocsr", -}; - -static const char * const nord_pciephy_port_b_nocsr_reset_l[] = { - "port_b_nocsr", -}; - static const char * const nord_pciephy_port_c_nocsr_reset_l[] = { "port_c_nocsr", }; From 8c2b331e6327ead44e72e347ce405c21dd781a99 Mon Sep 17 00:00:00 2001 From: RD Babiera Date: Fri, 25 Sep 2026 22:30:00 +0000 Subject: [PATCH 079/114] phy: Add USB3 PHY support to Google Tensor SoC USB PHY driver Add USB3 PHY support for the Google Tensor G5 USB PHY driver. This patch adds functionality for the usb3_tca register, usb3 clock, and usb3 reset as defined in google,lga-usb-phy.yaml. Kconfig now lists USB SuperSpeed support. Refactor the probe sequence to initialize the USB2 and USB3 PHYs, and then initialize clocks and resets for both PHYs afterwards. Refactor set_vbus_valid to reduce duplicated code. Implement USB3 phy_ops for phy_init, phy_exit, and phy_power_on. combo_phy_state enum is added to track PHY bringup state across PHY API calls. google_usb_set_orientation can reprogram the TCA when the PHY is ready. google_usb_set_orientation now calls pm_runtime_get_if_active to guarantee register access. Setting orientation and pm_runtime management are now handled within phy_mutex. Signed-off-by: RD Babiera Reviewed-by: Peter Griffin Reviewed-by: Neill Kapron Link: https://patch.msgid.link/20260925222959.3607769-2-rdbabiera@google.com Signed-off-by: Vinod Koul --- drivers/phy/Kconfig | 2 +- drivers/phy/phy-google-usb.c | 449 +++++++++++++++++++++++++++++++---- 2 files changed, 405 insertions(+), 46 deletions(-) diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 19f3b7d12b7d..d2d401129af7 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -100,7 +100,7 @@ config PHY_GOOGLE_USB the G5 generation (Laguna). This driver provides the PHY interfaces to interact with the SNPS eUSB2 and USB 3.2/DisplayPort Combo PHY, both of which are integrated with the DWC3 USB DRD controller. - This driver currently supports USB high-speed. + This driver currently supports USB high-speed and SuperSpeed. config USB_LGM_PHY tristate "INTEL Lightning Mountain USB PHY Driver" diff --git a/drivers/phy/phy-google-usb.c b/drivers/phy/phy-google-usb.c index ab20bc20f19e..70d3a3833948 100644 --- a/drivers/phy/phy-google-usb.c +++ b/drivers/phy/phy-google-usb.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -16,10 +17,12 @@ #include #include #include +#include #include #include #include +/* USB_CFG_CSR */ #define USBCS_USB2PHY_CFG19_OFFSET 0x0 #define USBCS_USB2PHY_CFG19_PHY_CFG_PLL_FB_DIV GENMASK(19, 8) @@ -28,11 +31,44 @@ #define USBCS_USB2PHY_CFG21_REF_FREQ_SEL GENMASK(15, 13) #define USBCS_USB2PHY_CFG21_PHY_TX_DIG_BYPASS_SEL BIT(19) +/* USBDP_TOP */ #define USBCS_PHY_CFG1_OFFSET 0x28 +#define USBCS_PHY_CFG1_PHY0_MPLLA_SSC_EN BIT(1) +#define USBCS_PHY_CFG1_PHY0_SRAM_BYPASS_MODE GENMASK(11, 10) +#define SRAM_BYPASS_MODE_BYPASS_FIRMWARE BIT(0) +#define SRAM_BYPASS_MODE_BYPASS_CONTEXT BIT(1) #define USBCS_PHY_CFG1_SYS_VBUSVALID BIT(17) +#define USBDP_TOP_CFG_REG_OFFSET 0x44 +#define USBDP_TOP_CFG_REG_PMGT_REF_CLK_REQ_N BIT(0) + +#define PHY_POWER_CONFIG_REG1_OFFSET 0x48 +#define PHY_POWER_CONFIG_REG1_PG_MODE_EN BIT(1) +#define PHY_POWER_CONFIG_REG1_UPCS_PIPE_CONFIG GENMASK(31, 14) +#define UPCS_PIPE_CONFIG_ISO_CPM BIT(5) +#define UPCS_PIPE_CONFIG_PG_MODE_STATIC BIT(6) +#define UPCS_PIPE_CONFIG_LANE_RESET_NO_PG_EXIT BIT(9) + +/* USB3_TCA */ +#define TCA_INTR_STS_OFFSET 0x8 +#define TCA_INTR_STS_XA_ACT_EVT BIT(0) +#define TCA_TCPC_OFFSET 0x14 +#define TCA_TCPC_MUX_CONTROL GENMASK(2, 0) +#define TCA_TCPC_MUX_CONTROL_USB_ONLY 0x1 +#define TCA_TCPC_CONNECTOR_ORIENTATION BIT(3) +#define TCA_TCPC_VALID BIT(4) +#define TCA_CTRLSYNCMODE_CFG1_OFFSET 0x24 +#define TCA_CTRLSYNCMODE_CFG1_XA_TIMEOUT_VAL GENMASK(19, 0) +#define TCA_CTRLSYNCMODE_CFG1_XA_TIMEOUT_VAL_100MS 0x1e85 +#define TCA_PSTATE_0_OFFSET 0x50 +#define TCA_PSTATE_0_UPCS_LANE0_PHYSTATUS BIT(8) + +#define GPHY_TCA_DELAY_US 10 +#define GPHY_TCA_TIMEOUT_US 110000 + enum google_usb_phy_id { GOOGLE_USB2_PHY, + GOOGLE_USB3_PHY, GOOGLE_USB_PHY_NUM, }; @@ -46,53 +82,205 @@ struct google_usb_phy_instance { struct reset_control_bulk_data *rsts; }; +struct google_usb_phy_config { + const char * const *clk_names; + unsigned int num_clks; + const char * const *rst_names; + unsigned int num_rsts; +}; + +static const char * const u2phy_clk_names[] = { + "usb2", + "usb2_apb", +}; + +static const char * const u3phy_clk_names[] = { + "usb3" +}; + +static const char * const u2phy_rst_names[] = { + "usb2", + "usb2_apb", +}; + +static const char * const u3phy_rst_names[] = { + "usb3" +}; + +static const struct google_usb_phy_config phy_configs[GOOGLE_USB_PHY_NUM] = { + [GOOGLE_USB2_PHY] = { + .clk_names = u2phy_clk_names, + .num_clks = ARRAY_SIZE(u2phy_clk_names), + .rst_names = u2phy_rst_names, + .num_rsts = ARRAY_SIZE(u2phy_rst_names), + }, + [GOOGLE_USB3_PHY] = { + .clk_names = u3phy_clk_names, + .num_clks = ARRAY_SIZE(u3phy_clk_names), + .rst_names = u3phy_rst_names, + .num_rsts = ARRAY_SIZE(u3phy_rst_names), + }, +}; + +static inline void google_usb_phy_clk_disable(struct google_usb_phy_instance *inst) +{ + clk_bulk_disable_unprepare(inst->num_clks, inst->clks); +} + +DEFINE_FREE(inst_clk_disable, struct google_usb_phy_instance *, + if (_T) google_usb_phy_clk_disable(_T)) + +static inline void google_usb_phy_rst_disable(struct google_usb_phy_instance *inst) +{ + reset_control_bulk_assert(inst->num_rsts, inst->rsts); +} + +DEFINE_FREE(inst_rst_disable, struct google_usb_phy_instance *, + if (_T) google_usb_phy_rst_disable(_T)) + +/* + * combo_phy_state + * COMBO_PHY_IDLE: The ComboPHY has been torn down and USB3 has not completed + * bringup + * COMBO_PHY_INIT_DONE: The ComboPHY bringup sequence is complete. + * COMBO_PHY_TCA_READY: The PoR => NC transition is complete, and the TCA can be + * moved into USB. + */ +enum combo_phy_state { + COMBO_PHY_IDLE, + COMBO_PHY_INIT_DONE, + COMBO_PHY_TCA_READY, +}; + struct google_usb_phy { struct device *dev; struct regmap *usb_cfg_regmap; unsigned int usb2_cfg_offset; void __iomem *usbdp_top_base; + void __iomem *usb3_tca_base; struct google_usb_phy_instance *insts; /* * Protect phy registers from concurrent access, specifically via - * google_usb_set_orientation callback. + * google_usb_set_orientation callback. phy_mutex also protects + * concurrent access to phy_state. */ struct mutex phy_mutex; struct typec_switch_dev *sw; enum typec_orientation orientation; + enum combo_phy_state phy_state; }; static void set_vbus_valid(struct google_usb_phy *gphy) { u32 reg; - if (gphy->orientation == TYPEC_ORIENTATION_NONE) { - reg = readl(gphy->usbdp_top_base + USBCS_PHY_CFG1_OFFSET); + reg = readl(gphy->usbdp_top_base + USBCS_PHY_CFG1_OFFSET); + if (gphy->orientation == TYPEC_ORIENTATION_NONE) reg &= ~USBCS_PHY_CFG1_SYS_VBUSVALID; - writel(reg, gphy->usbdp_top_base + USBCS_PHY_CFG1_OFFSET); - } else { - reg = readl(gphy->usbdp_top_base + USBCS_PHY_CFG1_OFFSET); + else reg |= USBCS_PHY_CFG1_SYS_VBUSVALID; - writel(reg, gphy->usbdp_top_base + USBCS_PHY_CFG1_OFFSET); - } + writel(reg, gphy->usbdp_top_base + USBCS_PHY_CFG1_OFFSET); +} + +static void set_sram_bypass(struct google_usb_phy *gphy, u32 bypass) +{ + u32 reg; + + reg = readl(gphy->usbdp_top_base + USBCS_PHY_CFG1_OFFSET); + reg &= ~USBCS_PHY_CFG1_PHY0_SRAM_BYPASS_MODE; + reg |= FIELD_PREP(USBCS_PHY_CFG1_PHY0_SRAM_BYPASS_MODE, bypass); + writel(reg, gphy->usbdp_top_base + USBCS_PHY_CFG1_OFFSET); +} + +static void set_pmgt_ref_clk_req_n(struct google_usb_phy *gphy, bool resume) +{ + u32 reg; + + reg = readl(gphy->usbdp_top_base + USBDP_TOP_CFG_REG_OFFSET); + if (resume) + reg |= USBDP_TOP_CFG_REG_PMGT_REF_CLK_REQ_N; + else + reg &= ~USBDP_TOP_CFG_REG_PMGT_REF_CLK_REQ_N; + writel(reg, gphy->usbdp_top_base + USBDP_TOP_CFG_REG_OFFSET); +} + +static inline void disable_pmgt_ref_clk_req_n(struct google_usb_phy *gphy) +{ + set_pmgt_ref_clk_req_n(gphy, false); +} + +DEFINE_FREE(pmgt_ref_clk_req_n, struct google_usb_phy *, if (_T) disable_pmgt_ref_clk_req_n(_T)) + +static int wait_tca_xa_ack(struct google_usb_phy *gphy) +{ + int ret; + u32 reg; + + ret = readl_poll_timeout(gphy->usb3_tca_base + TCA_INTR_STS_OFFSET, + reg, !!(reg & TCA_INTR_STS_XA_ACT_EVT), + GPHY_TCA_DELAY_US, GPHY_TCA_TIMEOUT_US); + if (ret) + dev_err(gphy->dev, "tca xa_ack timeout, ret=%d\n", ret); + + return ret; +} + +static int program_tca_locked(struct google_usb_phy *gphy) + __must_hold(&gphy->phy_mutex) +{ + int ret; + u32 reg; + + reg = readl(gphy->usb3_tca_base + TCA_INTR_STS_OFFSET); + writel(reg, gphy->usb3_tca_base + TCA_INTR_STS_OFFSET); + + reg = readl(gphy->usb3_tca_base + TCA_TCPC_OFFSET); + reg &= ~TCA_TCPC_MUX_CONTROL; + reg |= FIELD_PREP(TCA_TCPC_MUX_CONTROL, TCA_TCPC_MUX_CONTROL_USB_ONLY); + if (gphy->orientation == TYPEC_ORIENTATION_REVERSE) + reg |= TCA_TCPC_CONNECTOR_ORIENTATION; + else + reg &= ~TCA_TCPC_CONNECTOR_ORIENTATION; + reg |= TCA_TCPC_VALID; + writel(reg, gphy->usb3_tca_base + TCA_TCPC_OFFSET); + + ret = wait_tca_xa_ack(gphy); + dev_dbg(gphy->dev, "TCA switch %s, mux %lu, orientation %s\n", + ret ? "failed" : "success", + FIELD_GET(TCA_TCPC_MUX_CONTROL, reg), + FIELD_GET(TCA_TCPC_CONNECTOR_ORIENTATION, reg) ? "reverse" : "normal"); + + reg = readl(gphy->usb3_tca_base + TCA_INTR_STS_OFFSET); + writel(reg, gphy->usb3_tca_base + TCA_INTR_STS_OFFSET); + + return ret; } static int google_usb_set_orientation(struct typec_switch_dev *sw, enum typec_orientation orientation) { struct google_usb_phy *gphy = typec_switch_get_drvdata(sw); + int ret = 0; dev_dbg(gphy->dev, "set orientation %d\n", orientation); + guard(mutex)(&gphy->phy_mutex); + gphy->orientation = orientation; - if (pm_runtime_suspended(gphy->dev)) - return 0; - - guard(mutex)(&gphy->phy_mutex); + if (IS_ENABLED(CONFIG_PM)) { + if (pm_runtime_get_if_active(gphy->dev) <= 0) + return 0; + } set_vbus_valid(gphy); - return 0; + if (gphy->phy_state == COMBO_PHY_TCA_READY && orientation != TYPEC_ORIENTATION_NONE) + ret = program_tca_locked(gphy); + + pm_runtime_put(gphy->dev); + + return ret; } static int google_usb2_phy_init(struct phy *_phy) @@ -122,17 +310,18 @@ static int google_usb2_phy_init(struct phy *_phy) ret = clk_bulk_prepare_enable(inst->num_clks, inst->clks); if (ret) return ret; + struct google_usb_phy_instance *clk_dev __free(inst_clk_disable) = inst; ret = reset_control_bulk_deassert(inst->num_rsts, inst->rsts); - if (ret) { - clk_bulk_disable_unprepare(inst->num_clks, inst->clks); + if (ret) return ret; - } regmap_read(gphy->usb_cfg_regmap, gphy->usb2_cfg_offset + USBCS_USB2PHY_CFG21_OFFSET, ®); reg |= USBCS_USB2PHY_CFG21_PHY_ENABLE; regmap_write(gphy->usb_cfg_regmap, gphy->usb2_cfg_offset + USBCS_USB2PHY_CFG21_OFFSET, reg); + retain_and_null_ptr(clk_dev); + return 0; } @@ -161,6 +350,128 @@ static const struct phy_ops google_usb2_phy_ops = { .exit = google_usb2_phy_exit, }; +static int google_usb3_phy_init(struct phy *_phy) +{ + struct google_usb_phy_instance *inst = phy_get_drvdata(_phy); + struct google_usb_phy *gphy = inst->parent; + int ret = 0; + u32 reg; + + dev_dbg(gphy->dev, "initializing usb3 phy\n"); + + guard(mutex)(&gphy->phy_mutex); + + if (gphy->phy_state != COMBO_PHY_IDLE) { + dev_warn(gphy->dev, "usb3 phy init called when combo phy state is not idle\n"); + return 0; + } + + reg = readl(gphy->usb3_tca_base + TCA_CTRLSYNCMODE_CFG1_OFFSET); + reg &= ~TCA_CTRLSYNCMODE_CFG1_XA_TIMEOUT_VAL; + reg |= FIELD_PREP(TCA_CTRLSYNCMODE_CFG1_XA_TIMEOUT_VAL, + TCA_CTRLSYNCMODE_CFG1_XA_TIMEOUT_VAL_100MS); + writel(reg, gphy->usb3_tca_base + TCA_CTRLSYNCMODE_CFG1_OFFSET); + + reg = readl(gphy->usbdp_top_base + PHY_POWER_CONFIG_REG1_OFFSET); + reg |= PHY_POWER_CONFIG_REG1_PG_MODE_EN; + reg &= ~PHY_POWER_CONFIG_REG1_UPCS_PIPE_CONFIG; + reg |= FIELD_PREP(PHY_POWER_CONFIG_REG1_UPCS_PIPE_CONFIG, + (UPCS_PIPE_CONFIG_ISO_CPM | + UPCS_PIPE_CONFIG_PG_MODE_STATIC | + UPCS_PIPE_CONFIG_LANE_RESET_NO_PG_EXIT)); + writel(reg, gphy->usbdp_top_base + PHY_POWER_CONFIG_REG1_OFFSET); + + set_vbus_valid(gphy); + + reg = readl(gphy->usbdp_top_base + USBCS_PHY_CFG1_OFFSET); + reg |= USBCS_PHY_CFG1_PHY0_MPLLA_SSC_EN; + writel(reg, gphy->usbdp_top_base + USBCS_PHY_CFG1_OFFSET); + + set_sram_bypass(gphy, SRAM_BYPASS_MODE_BYPASS_FIRMWARE | + SRAM_BYPASS_MODE_BYPASS_CONTEXT); + set_pmgt_ref_clk_req_n(gphy, true); + struct google_usb_phy *pmgt_ref_clk_req_dev __free(pmgt_ref_clk_req_n) = gphy; + + ret = clk_bulk_prepare_enable(inst->num_clks, inst->clks); + if (ret) + return ret; + struct google_usb_phy_instance *clk_dev __free(inst_clk_disable) = inst; + + ret = reset_control_bulk_deassert(inst->num_rsts, inst->rsts); + if (ret) + return ret; + struct google_usb_phy_instance *rst_dev __free(inst_rst_disable) = inst; + + ret = readl_poll_timeout(gphy->usb3_tca_base + TCA_PSTATE_0_OFFSET, + reg, !(reg & TCA_PSTATE_0_UPCS_LANE0_PHYSTATUS), + GPHY_TCA_DELAY_US, GPHY_TCA_TIMEOUT_US); + if (ret) { + dev_err(gphy->dev, "wait for lane0 phystatus timed out\n"); + return ret; + } + + gphy->phy_state = COMBO_PHY_INIT_DONE; + + retain_and_null_ptr(rst_dev); + retain_and_null_ptr(clk_dev); + retain_and_null_ptr(pmgt_ref_clk_req_dev); + + return 0; +} + +static int google_usb3_phy_exit(struct phy *_phy) +{ + struct google_usb_phy_instance *inst = phy_get_drvdata(_phy); + struct google_usb_phy *gphy = inst->parent; + + dev_dbg(gphy->dev, "exiting usb3 phy\n"); + + guard(mutex)(&gphy->phy_mutex); + + reset_control_bulk_assert(inst->num_rsts, inst->rsts); + clk_bulk_disable_unprepare(inst->num_clks, inst->clks); + set_pmgt_ref_clk_req_n(gphy, false); + + gphy->phy_state = COMBO_PHY_IDLE; + + return 0; +} + +static int google_usb3_phy_power_on(struct phy *_phy) +{ + struct google_usb_phy_instance *inst = phy_get_drvdata(_phy); + struct google_usb_phy *gphy = inst->parent; + int ret; + + dev_dbg(gphy->dev, "power on usb3 phy\n"); + + guard(mutex)(&gphy->phy_mutex); + + if (gphy->phy_state == COMBO_PHY_IDLE) + return -EINVAL; + + if (gphy->phy_state == COMBO_PHY_INIT_DONE) { + /* Wait for PoR -> NC transitions*/ + ret = wait_tca_xa_ack(gphy); + if (ret) { + dev_err(gphy->dev, "PoR->NC transition timeout\n"); + return ret; + } + gphy->phy_state = COMBO_PHY_TCA_READY; + } + + if (gphy->orientation != TYPEC_ORIENTATION_NONE) + return program_tca_locked(gphy); + + return 0; +} + +static const struct phy_ops google_usb3_phy_ops = { + .init = google_usb3_phy_init, + .exit = google_usb3_phy_exit, + .power_on = google_usb3_phy_power_on, +}; + static struct phy *google_usb_phy_xlate(struct device *dev, const struct of_phandle_args *args) { @@ -173,14 +484,61 @@ static struct phy *google_usb_phy_xlate(struct device *dev, return gphy->insts[args->args[0]].phy; } +static int google_usb_phy_parse_clocks(struct google_usb_phy *gphy) +{ + struct device *dev = gphy->dev; + int id, i, ret; + + for (id = 0; id < GOOGLE_USB_PHY_NUM; id++) { + const struct google_usb_phy_config *cfg = &phy_configs[id]; + struct google_usb_phy_instance *inst = &gphy->insts[id]; + + inst->num_clks = cfg->num_clks; + inst->clks = devm_kcalloc(dev, inst->num_clks, sizeof(*inst->clks), GFP_KERNEL); + if (!inst->clks) + return -ENOMEM; + + for (i = 0; i < inst->num_clks; i++) + inst->clks[i].id = cfg->clk_names[i]; + + ret = devm_clk_bulk_get(dev, inst->num_clks, inst->clks); + if (ret) + return dev_err_probe(dev, ret, "failed to get phy%d clks\n", id); + } + + return 0; +} + +static int google_usb_phy_parse_resets(struct google_usb_phy *gphy) +{ + struct device *dev = gphy->dev; + int id, i, ret; + + for (id = 0; id < GOOGLE_USB_PHY_NUM; id++) { + const struct google_usb_phy_config *cfg = &phy_configs[id]; + struct google_usb_phy_instance *inst = &gphy->insts[id]; + + inst->num_rsts = cfg->num_rsts; + inst->rsts = devm_kcalloc(dev, inst->num_rsts, sizeof(*inst->rsts), GFP_KERNEL); + if (!inst->rsts) + return -ENOMEM; + + for (i = 0; i < inst->num_rsts; i++) + inst->rsts[i].id = cfg->rst_names[i]; + ret = devm_reset_control_bulk_get_exclusive(dev, inst->num_rsts, inst->rsts); + if (ret) + return dev_err_probe(dev, ret, "failed to get phy%d resets\n", id); + } + + return 0; +} + static int google_usb_phy_probe(struct platform_device *pdev) { struct typec_switch_desc sw_desc = { }; - struct google_usb_phy_instance *inst; struct phy_provider *phy_provider; struct device *dev = &pdev->dev; struct google_usb_phy *gphy; - struct phy *phy; u32 args[1]; int ret; @@ -212,46 +570,48 @@ static int google_usb_phy_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(gphy->usbdp_top_base), "invalid usbdp top\n"); + gphy->usb3_tca_base = devm_platform_ioremap_resource_byname(pdev, + "usb3_tca"); + if (IS_ERR(gphy->usb3_tca_base)) + return dev_err_probe(dev, PTR_ERR(gphy->usb3_tca_base), + "invalid usb3 tca\n"); + gphy->insts = devm_kcalloc(dev, GOOGLE_USB_PHY_NUM, sizeof(*gphy->insts), GFP_KERNEL); if (!gphy->insts) return -ENOMEM; - inst = &gphy->insts[GOOGLE_USB2_PHY]; - inst->parent = gphy; - inst->index = GOOGLE_USB2_PHY; - phy = devm_phy_create(dev, NULL, &google_usb2_phy_ops); - if (IS_ERR(phy)) - return dev_err_probe(dev, PTR_ERR(phy), + gphy->insts[GOOGLE_USB2_PHY].phy = devm_phy_create(dev, NULL, &google_usb2_phy_ops); + gphy->insts[GOOGLE_USB2_PHY].index = GOOGLE_USB2_PHY; + gphy->insts[GOOGLE_USB2_PHY].parent = gphy; + if (IS_ERR(gphy->insts[GOOGLE_USB2_PHY].phy)) + return dev_err_probe(dev, PTR_ERR(gphy->insts[GOOGLE_USB2_PHY].phy), "failed to create usb2 phy instance\n"); - inst->phy = phy; - phy_set_drvdata(phy, inst); + phy_set_drvdata(gphy->insts[GOOGLE_USB2_PHY].phy, &gphy->insts[GOOGLE_USB2_PHY]); - inst->num_clks = 2; - inst->clks = devm_kcalloc(dev, inst->num_clks, sizeof(*inst->clks), GFP_KERNEL); - if (!inst->clks) - return -ENOMEM; - inst->clks[0].id = "usb2"; - inst->clks[1].id = "usb2_apb"; - ret = devm_clk_bulk_get(dev, inst->num_clks, inst->clks); - if (ret) - return dev_err_probe(dev, ret, "failed to get u2 phy clks\n"); + gphy->insts[GOOGLE_USB3_PHY].phy = devm_phy_create(dev, NULL, &google_usb3_phy_ops); + gphy->insts[GOOGLE_USB3_PHY].index = GOOGLE_USB3_PHY; + gphy->insts[GOOGLE_USB3_PHY].parent = gphy; + if (IS_ERR(gphy->insts[GOOGLE_USB3_PHY].phy)) + return dev_err_probe(dev, PTR_ERR(gphy->insts[GOOGLE_USB3_PHY].phy), + "failed to create usb3 phy instance\n"); + phy_set_drvdata(gphy->insts[GOOGLE_USB3_PHY].phy, &gphy->insts[GOOGLE_USB3_PHY]); - inst->num_rsts = 2; - inst->rsts = devm_kcalloc(dev, inst->num_rsts, sizeof(*inst->rsts), GFP_KERNEL); - if (!inst->rsts) - return -ENOMEM; - inst->rsts[0].id = "usb2"; - inst->rsts[1].id = "usb2_apb"; - ret = devm_reset_control_bulk_get_exclusive(dev, inst->num_rsts, inst->rsts); + ret = google_usb_phy_parse_clocks(gphy); if (ret) - return dev_err_probe(dev, ret, "failed to get u2 phy resets\n"); + return ret; + + ret = google_usb_phy_parse_resets(gphy); + if (ret) + return ret; phy_provider = devm_of_phy_provider_register(dev, google_usb_phy_xlate); if (IS_ERR(phy_provider)) return dev_err_probe(dev, PTR_ERR(phy_provider), "failed to register phy provider\n"); - pm_runtime_enable(dev); + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; sw_desc.fwnode = dev_fwnode(dev); sw_desc.drvdata = gphy; @@ -271,7 +631,6 @@ static void google_usb_phy_remove(struct platform_device *pdev) struct google_usb_phy *gphy = dev_get_drvdata(&pdev->dev); typec_switch_unregister(gphy->sw); - pm_runtime_disable(&pdev->dev); } static const struct of_device_id google_usb_phy_of_match[] = { From 261e84f9278f4373970471ad71479df20d82ed4c Mon Sep 17 00:00:00 2001 From: Jason Yang Date: Fri, 21 Aug 2026 19:44:19 +0800 Subject: [PATCH 080/114] phy: rockchip-samsung-dcphy: Enable runtime PM at PHY core level phy_create() only enables runtime PM on the new PHY device if the parent already has it enabled, and this driver enables it afterwards. The PHY device therefore never gets runtime PM, and phy_pm_runtime_get_sync() returns -ENOTSUPP for every phy_init() and phy_power_on(). The block is still resumed today, but only through the supplier link fw_devlink builds from the phys phandle, which fw_devlink=off and =permissive do not. Move devm_pm_runtime_enable() before devm_phy_create(), as done for the sibling HDPTX PHY in commit 10ba8479f460 ("phy: phy-rockchip-samsung-hdptx: Enable runtime PM at PHY core level"). Fixes: b2a1a2ae7818 ("phy: rockchip: Add Samsung MIPI D-/C-PHY driver") Signed-off-by: Jason Yang Reviewed-by: Heiko Stuebner Reviewed-by: Sebastian Reichel Link: https://patch.msgid.link/20260821-dcphy-rpm-v1-v1-1-a7db728b2539@gmail.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c b/drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c index 18f5f582d1d7..7382043e1f31 100644 --- a/drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c +++ b/drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c @@ -1610,16 +1610,16 @@ static int samsung_mipi_dcphy_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(samsung->grf_apb_rst), "Failed to get system grf_apb_rst control\n"); + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable runtime PM\n"); + samsung->phy = devm_phy_create(dev, NULL, &samsung_mipi_dcphy_ops); if (IS_ERR(samsung->phy)) return dev_err_probe(dev, PTR_ERR(samsung->phy), "Failed to create MIPI DC-PHY\n"); phy_set_drvdata(samsung->phy, samsung); - ret = devm_pm_runtime_enable(dev); - if (ret) - return dev_err_probe(dev, ret, "Failed to enable runtime PM\n"); - phy_provider = devm_of_phy_provider_register(dev, samsung_mipi_dcphy_xlate); if (IS_ERR(phy_provider)) return dev_err_probe(dev, PTR_ERR(phy_provider), From fdc340bfc7ab60c4cbdb198c9972114b51d7caa3 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 21 Aug 2026 12:08:39 +0200 Subject: [PATCH 081/114] phy: freescale: fsl-samsung-hdmi: Make sure clk_init_data is fully initialized The clk_init_data structure contains several mutually-exclusive members for different methods to specify the possible parents of a clock, prompting drivers to initialize only the members they need. However, not initializing all members may cause subtle issues, which are only exposed when CONFIG_INIT_STACK_ALL_PATTERN or CONFIG_INIT_STACK_NONE is enabled. Make sure all members are fully initialized, to avoid such bugs, and to prevent future breakage when converting drivers to a different method for specifying the parents. Signed-off-by: Geert Uytterhoeven Reviewed-by: Brian Masney Link: https://patch.msgid.link/3c3be1c676eea5d32102864c57643b1573bc0db0.1787240165.git.geert+renesas@glider.be Signed-off-by: Vinod Koul --- drivers/phy/freescale/phy-fsl-samsung-hdmi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/phy/freescale/phy-fsl-samsung-hdmi.c b/drivers/phy/freescale/phy-fsl-samsung-hdmi.c index 0f25d81de61b..ded142020e72 100644 --- a/drivers/phy/freescale/phy-fsl-samsung-hdmi.c +++ b/drivers/phy/freescale/phy-fsl-samsung-hdmi.c @@ -610,7 +610,7 @@ static int phy_clk_register(struct fsl_samsung_hdmi_phy *phy) { struct device *dev = phy->dev; struct device_node *np = dev->of_node; - struct clk_init_data init; + struct clk_init_data init = {}; const char *parent_name; struct clk *phyclk; int ret; From 91967098fd985237ba0deff97809ab52d3e51fba Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 21 Aug 2026 12:08:40 +0200 Subject: [PATCH 082/114] phy: rockchip: Make sure clk_init_data is fully initialized The clk_init_data structure contains several mutually-exclusive members for different methods to specify the possible parents of a clock, prompting drivers to initialize only the members they need. However, not initializing all members may cause subtle issues, which are only exposed when CONFIG_INIT_STACK_ALL_PATTERN or CONFIG_INIT_STACK_NONE is enabled. Make sure all members are fully initialized, to avoid such bugs, and to prevent future breakage when converting drivers to a different method for specifying the parents. Signed-off-by: Geert Uytterhoeven Reviewed-by: Heiko Stuebner Reviewed-by: Brian Masney Link: https://patch.msgid.link/adb5bd47343dd31661748d5de14cb28a2bae2d17.1787240165.git.geert+renesas@glider.be Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-inno-hdmi.c | 2 +- drivers/phy/rockchip/phy-rockchip-inno-usb2.c | 2 +- drivers/phy/rockchip/phy-rockchip-usb.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c b/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c index c3d257a0f4a6..d2c2b17df5f3 100644 --- a/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c +++ b/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c @@ -1044,7 +1044,7 @@ static int inno_hdmi_phy_clk_register(struct inno_hdmi_phy *inno) { struct device *dev = inno->dev; struct device_node *np = dev->of_node; - struct clk_init_data init; + struct clk_init_data init = {}; const char *parent_name; int ret; diff --git a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c index 7d8a533f24ae..e47b89015586 100644 --- a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c +++ b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c @@ -400,7 +400,7 @@ static int rockchip_usb2phy_clk480m_register(struct rockchip_usb2phy *rphy) { struct device_node *node = rphy->dev->of_node; - struct clk_init_data init; + struct clk_init_data init = {}; struct clk *refclk = NULL; const char *clk_name; int i; diff --git a/drivers/phy/rockchip/phy-rockchip-usb.c b/drivers/phy/rockchip/phy-rockchip-usb.c index cef96739cf3f..c5a2853a63d3 100644 --- a/drivers/phy/rockchip/phy-rockchip-usb.c +++ b/drivers/phy/rockchip/phy-rockchip-usb.c @@ -200,9 +200,9 @@ static int rockchip_usb_phy_init(struct rockchip_usb_phy_base *base, struct device_node *child) { struct rockchip_usb_phy *rk_phy; + struct clk_init_data init = {}; unsigned int reg_offset; const char *clk_name; - struct clk_init_data init; int err, i; rk_phy = devm_kzalloc(base->dev, sizeof(*rk_phy), GFP_KERNEL); From f1d07d242b4c25a259276cc2b136f49c68ee71ab Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 11:55:18 +0530 Subject: [PATCH 083/114] phy: fix typos in comments Fix typos in comments, reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Link: https://patch.msgid.link/20260907062518.12810-1-hemanth.selam@gmail.com Signed-off-by: Vinod Koul --- drivers/phy/broadcom/phy-brcm-sata.c | 2 +- drivers/phy/phy-xgene.c | 2 +- include/linux/phy/omap_control_phy.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/phy/broadcom/phy-brcm-sata.c b/drivers/phy/broadcom/phy-brcm-sata.c index fb69e21a0292..43b287a25e7a 100644 --- a/drivers/phy/broadcom/phy-brcm-sata.c +++ b/drivers/phy/broadcom/phy-brcm-sata.c @@ -334,7 +334,7 @@ static int brcm_stb_sata_16nm_ssc_init(struct brcm_sata_port *port) brcm_sata_phy_wr(port, RXPMD_REG_BANK, RXPMD_RX_CDR_CONTROL1, ~RXPMD_RX_PPM_VAL_MASK, value); - /* Set proportional loop bandwith Gen1/2/3 */ + /* Set proportional loop bandwidth Gen1/2/3 */ tmp = RXPMD_G_CDR_PROP_BW_MASK << RXPMD_G1_CDR_PROP_BW_SHIFT | RXPMD_G_CDR_PROP_BW_MASK << RXPMD_G2_CDR_PROP_BW_SHIFT | RXPMD_G_CDR_PROP_BW_MASK << RXPMD_G3_CDR_PROB_BW_SHIFT; diff --git a/drivers/phy/phy-xgene.c b/drivers/phy/phy-xgene.c index 5007dc7a357c..59f18e16bbbb 100644 --- a/drivers/phy/phy-xgene.c +++ b/drivers/phy/phy-xgene.c @@ -1669,7 +1669,7 @@ static int xgene_phy_probe(struct platform_device *pdev) /* Retrieve optional clock */ ctx->clk = clk_get(&pdev->dev, NULL); - /* Load override paramaters */ + /* Load override parameters */ xgene_phy_get_param(pdev, "apm,tx-eye-tuning", ctx->sata_param.txeyetuning, 6, default_txeye_tuning, 1); xgene_phy_get_param(pdev, "apm,tx-eye-direction", diff --git a/include/linux/phy/omap_control_phy.h b/include/linux/phy/omap_control_phy.h index aec57dd784f7..5652aa2f4915 100644 --- a/include/linux/phy/omap_control_phy.h +++ b/include/linux/phy/omap_control_phy.h @@ -12,7 +12,7 @@ enum omap_control_phy_type { OMAP_CTRL_TYPE_OTGHS = 1, /* Mailbox OTGHS_CONTROL */ OMAP_CTRL_TYPE_USB2, /* USB2_PHY, power down in CONTROL_DEV_CONF */ - OMAP_CTRL_TYPE_PIPE3, /* PIPE3 PHY, DPLL & seperate Rx/Tx power */ + OMAP_CTRL_TYPE_PIPE3, /* PIPE3 PHY, DPLL & separate Rx/Tx power */ OMAP_CTRL_TYPE_PCIE, /* RX TX control of ACSPCIE */ OMAP_CTRL_TYPE_DRA7USB2, /* USB2 PHY, power and power_aux e.g. DRA7 */ OMAP_CTRL_TYPE_AM437USB2, /* USB2 PHY, power e.g. AM437x */ From a2fd3d1f4db720ac1deb477ce4811c5e4e60e5f8 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Fri, 21 Aug 2026 15:50:27 +0200 Subject: [PATCH 084/114] phy: apple: atc: Support DUMMY PIPEHANDLER state in configure_pipehandler For both Thunderbolt and DisplayPort atcphy_configure_pipehandler is reached with a request to switch to the DUMMY state (i.e. usb2 only). With the current code this breaks USB2 when all four SS lanes are used for DisplayPort AltMode because the -EINVAL is passed all the way back to the phy_set_mode() call which results in tearing down xhci and dwc3 again. Let's actually handle that case correctly and also drop the default from the switch such that we get a compiler warning if another pipehandler state is ever added and forgotten here. Reported-by: Paul Cristian Closes: https://github.com/AsahiLinux/linux/pull/515 Fixes: 8e98ca1e74db ("phy: apple: Add Apple Type-C PHY") Cc: stable@vger.kernel.org Signed-off-by: Sven Peter Reviewed-by: Janne Grunau Link: https://patch.msgid.link/20260821-b4-atcphy-usb4-v1-1-45c0b741e0c0@kernel.org Signed-off-by: Vinod Koul --- drivers/phy/apple/atc.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c index f09305dd76d1..f9f6f5b1da52 100644 --- a/drivers/phy/apple/atc.c +++ b/drivers/phy/apple/atc.c @@ -1119,7 +1119,7 @@ static int atcphy_configure_pipehandler_dummy(struct apple_atcphy *atcphy) static int atcphy_configure_pipehandler(struct apple_atcphy *atcphy, bool host) { - int ret; + int ret = -EINVAL; lockdep_assert_held(&atcphy->lock); @@ -1134,8 +1134,10 @@ static int atcphy_configure_pipehandler(struct apple_atcphy *atcphy, bool host) ret = atcphy_configure_pipehandler_dummy(atcphy); atcphy->pipehandler_up = false; break; - default: - ret = -EINVAL; + case ATCPHY_PIPEHANDLER_STATE_DUMMY: + ret = atcphy_configure_pipehandler_dummy(atcphy); + atcphy->pipehandler_up = false; + break; } return ret; From 8ca2e111117bf1d571f7c9de5d0dd91f5b770b11 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Fri, 21 Aug 2026 15:50:28 +0200 Subject: [PATCH 085/114] phy: apple: atc: Factor out the PIPE mux sequence This sequence is already used in three places and we're about to add a fourth copy so let's factor it out to a helper. No functional change. Signed-off-by: Sven Peter Reviewed-by: Janne Grunau Link: https://patch.msgid.link/20260821-b4-atcphy-usb4-v1-2-45c0b741e0c0@kernel.org Signed-off-by: Vinod Koul --- drivers/phy/apple/atc.c | 46 +++++++++++++++++------------------------ 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c index f9f6f5b1da52..5d14ef4f4edc 100644 --- a/drivers/phy/apple/atc.c +++ b/drivers/phy/apple/atc.c @@ -970,6 +970,19 @@ static int atcphy_pipehandler_check(struct apple_atcphy *atcphy) return 0; } +static void atcphy_pipehandler_set_mux(struct apple_atcphy *atcphy, u32 data, u32 clk) +{ + mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, + FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, PIPEHANDLER_MUX_CTRL_CLK_OFF)); + udelay(10); + mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_DATA, + FIELD_PREP(PIPEHANDLER_MUX_CTRL_DATA, data)); + udelay(10); + mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, + FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, clk)); + udelay(10); +} + static int atcphy_configure_pipehandler_usb3(struct apple_atcphy *atcphy, bool host) { int ret; @@ -1052,15 +1065,8 @@ static int atcphy_configure_pipehandler_usb3(struct apple_atcphy *atcphy, bool h } /* Configure PIPE mux to USB3 PHY */ - mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, - FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, PIPEHANDLER_MUX_CTRL_CLK_OFF)); - udelay(10); - mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_DATA, - FIELD_PREP(PIPEHANDLER_MUX_CTRL_DATA, PIPEHANDLER_MUX_CTRL_DATA_USB3)); - udelay(10); - mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, - FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, PIPEHANDLER_MUX_CTRL_CLK_USB3)); - udelay(10); + atcphy_pipehandler_set_mux(atcphy, PIPEHANDLER_MUX_CTRL_DATA_USB3, + PIPEHANDLER_MUX_CTRL_CLK_USB3); /* Remove link detection override */ clear32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE, PIPEHANDLER_OVERRIDE_RXVALID); @@ -1095,15 +1101,8 @@ static int atcphy_configure_pipehandler_dummy(struct apple_atcphy *atcphy) dev_warn(atcphy->dev, "Failed to lock pipehandler"); /* Switch to dummy PHY */ - mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, - FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, PIPEHANDLER_MUX_CTRL_CLK_OFF)); - udelay(10); - mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_DATA, - FIELD_PREP(PIPEHANDLER_MUX_CTRL_DATA, PIPEHANDLER_MUX_CTRL_DATA_DUMMY)); - udelay(10); - mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, - FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, PIPEHANDLER_MUX_CTRL_CLK_DUMMY)); - udelay(10); + atcphy_pipehandler_set_mux(atcphy, PIPEHANDLER_MUX_CTRL_DATA_DUMMY, + PIPEHANDLER_MUX_CTRL_CLK_DUMMY); ret = atcphy_pipehandler_unlock(atcphy); if (ret) @@ -1147,15 +1146,8 @@ static void atcphy_setup_pipehandler(struct apple_atcphy *atcphy) { lockdep_assert_held(&atcphy->lock); - mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, - FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, PIPEHANDLER_MUX_CTRL_CLK_OFF)); - udelay(10); - mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_DATA, - FIELD_PREP(PIPEHANDLER_MUX_CTRL_DATA, PIPEHANDLER_MUX_CTRL_DATA_DUMMY)); - udelay(10); - mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, - FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, PIPEHANDLER_MUX_CTRL_CLK_DUMMY)); - udelay(10); + atcphy_pipehandler_set_mux(atcphy, PIPEHANDLER_MUX_CTRL_DATA_DUMMY, + PIPEHANDLER_MUX_CTRL_CLK_DUMMY); } static void atcphy_configure_lanes(struct apple_atcphy *atcphy, enum atcphy_mode mode) From c319906aa47f8616019b01b1dd5c14eef293bb66 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Fri, 21 Aug 2026 15:50:29 +0200 Subject: [PATCH 086/114] phy: apple: atc: Implement the USB4 pipehandler state USB3 tunneled via USB4 requires dwc3's PIPE interface to be switched to the USB4 NHI which will take care of the tunneling. Add the required bringup sequence such that USB3 tunnels work once the USB4 NHI is upstream. Signed-off-by: Sven Peter Reviewed-by: Janne Grunau Link: https://patch.msgid.link/20260821-b4-atcphy-usb4-v1-3-45c0b741e0c0@kernel.org Signed-off-by: Vinod Koul --- drivers/phy/apple/atc.c | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c index 5d14ef4f4edc..45f1280a6faa 100644 --- a/drivers/phy/apple/atc.c +++ b/drivers/phy/apple/atc.c @@ -1082,6 +1082,41 @@ static int atcphy_configure_pipehandler_usb3(struct apple_atcphy *atcphy, bool h return 0; } +static int atcphy_configure_pipehandler_usb4(struct apple_atcphy *atcphy) +{ + int ret; + + ret = atcphy_pipehandler_check(atcphy); + if (ret) + return ret; + + /* Force disable link detection */ + clear32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE_VALUES, + PIPEHANDLER_OVERRIDE_VAL_RXDETECT0 | PIPEHANDLER_OVERRIDE_VAL_RXDETECT1); + set32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE, PIPEHANDLER_OVERRIDE_RXVALID); + set32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE, PIPEHANDLER_OVERRIDE_RXDETECT); + + ret = atcphy_pipehandler_lock(atcphy); + if (ret) { + dev_err(atcphy->dev, "Failed to lock pipehandler\n"); + return ret; + } + + /* Configure PIPE mux to the USB4/Thunderbolt controller */ + atcphy_pipehandler_set_mux(atcphy, PIPEHANDLER_MUX_CTRL_DATA_USB4, + PIPEHANDLER_MUX_CTRL_CLK_USB4); + + /* Remove link detection override */ + clear32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE, PIPEHANDLER_OVERRIDE_RXVALID); + clear32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE, PIPEHANDLER_OVERRIDE_RXDETECT); + + ret = atcphy_pipehandler_unlock(atcphy); + if (ret) + dev_warn(atcphy->dev, "Failed to unlock pipehandler\n"); + + return 0; +} + static int atcphy_configure_pipehandler_dummy(struct apple_atcphy *atcphy) { int ret; @@ -1128,10 +1163,8 @@ static int atcphy_configure_pipehandler(struct apple_atcphy *atcphy, bool host) atcphy->pipehandler_up = true; break; case ATCPHY_PIPEHANDLER_STATE_USB4: - dev_warn(atcphy->dev, - "ATCPHY_PIPEHANDLER_STATE_USB4 not implemented; falling back to USB2\n"); - ret = atcphy_configure_pipehandler_dummy(atcphy); - atcphy->pipehandler_up = false; + ret = atcphy_configure_pipehandler_usb4(atcphy); + atcphy->pipehandler_up = true; break; case ATCPHY_PIPEHANDLER_STATE_DUMMY: ret = atcphy_configure_pipehandler_dummy(atcphy); From 882af55e42a4cd5bd61db0244c0b78941884518a Mon Sep 17 00:00:00 2001 From: Bruno Banelli Date: Tue, 25 Aug 2026 08:50:06 +0200 Subject: [PATCH 087/114] phy: spacemit: fix K1 USB 2.0 PHY Kconfig dependencies phy-k1-usb2.c maps its registers with devm_platform_ioremap_resource() and wraps them with devm_regmap_init_mmio(), but PHY_SPACEMIT_K1_USB2 neither depends on HAS_IOMEM nor selects REGMAP_MMIO. REGMAP_MMIO has no prompt and can only be enabled by select, so a configuration that leaves it off fails to link. In practice the symbol is usually pulled in indirectly, which is why this has gone unnoticed. PHY_SPACEMIT_K1_PCIE, directly above it in the same file, already has the HAS_IOMEM dependency; without it PHY_SPACEMIT_K1_USB2 can be built under COMPILE_TEST on architectures that provide no I/O memory. Fixes: fe4bc1a08638 ("phy: spacemit: support K1 USB2.0 PHY controller") Signed-off-by: Bruno Banelli Reviewed-by: Yixun Lan Link: https://patch.msgid.link/20260825065007.69353-2-bbanelli@gmail.com Signed-off-by: Vinod Koul --- drivers/phy/spacemit/Kconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/phy/spacemit/Kconfig b/drivers/phy/spacemit/Kconfig index 5fdf18fce499..cd7ae48d8ea2 100644 --- a/drivers/phy/spacemit/Kconfig +++ b/drivers/phy/spacemit/Kconfig @@ -18,8 +18,10 @@ config PHY_SPACEMIT_K1_USB2 tristate "SpacemiT K1 USB 2.0 PHY support" depends on (ARCH_SPACEMIT || COMPILE_TEST) && OF depends on COMMON_CLK + depends on HAS_IOMEM depends on USB_COMMON select GENERIC_PHY + select REGMAP_MMIO help Enable this to support K1 USB 2.0 PHY driver. This driver takes care of enabling and clock setup and will be used by K1 udc/ehci/otg/xhci driver. From 392101240b22256105da3b5af6348d1889c63112 Mon Sep 17 00:00:00 2001 From: Bruno Banelli Date: Tue, 25 Aug 2026 08:50:07 +0200 Subject: [PATCH 088/114] phy: spacemit: enable K1 USB 2.0 PHY by default on ARCH_SPACEMIT PHY_SPACEMIT_K1_PCIE has "default ARCH_SPACEMIT", but PHY_SPACEMIT_K1_USB2, directly below it in the same file, does not. Since the symbol is also absent from arch/riscv/configs/defconfig, a plain "make defconfig" build for a SpacemiT K1 board ends up with the USB 2.0 PHY driver disabled. dwc3-generic-plat needs that PHY, so the result is a kernel with no working USB at all on boards such as the Milk-V Jupiter, where all four USB 3.0 type-A ports hang off the single dwc3 controller. Give it the same default as its sibling. Signed-off-by: Bruno Banelli Reviewed-by: Yixun Lan Link: https://patch.msgid.link/20260825065007.69353-3-bbanelli@gmail.com Signed-off-by: Vinod Koul --- drivers/phy/spacemit/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/phy/spacemit/Kconfig b/drivers/phy/spacemit/Kconfig index cd7ae48d8ea2..b5f36621b207 100644 --- a/drivers/phy/spacemit/Kconfig +++ b/drivers/phy/spacemit/Kconfig @@ -22,6 +22,7 @@ config PHY_SPACEMIT_K1_USB2 depends on USB_COMMON select GENERIC_PHY select REGMAP_MMIO + default ARCH_SPACEMIT help Enable this to support K1 USB 2.0 PHY driver. This driver takes care of enabling and clock setup and will be used by K1 udc/ehci/otg/xhci driver. From 77a0e6ae5dec54fb6049e3ab88288bc40c81f9fd Mon Sep 17 00:00:00 2001 From: David Carlier Date: Tue, 25 Aug 2026 23:57:28 +0100 Subject: [PATCH 089/114] phy: qcom: qmp-pcie: Select MFD_SYSCON for the multi-PHY driver The driver reads the active link mode through syscon_regmap_lookup_by_phandle_args(). Without CONFIG_MFD_SYSCON the stub returning -ENOTSUPP is compiled in instead, so probe fails for every device and no PHY is ever registered. Nothing in the build catches this: the driver still compiles, and arm64 defconfig happens to enable MFD_SYSCON through an unrelated select. Fixes: 1781be3c8a5d ("phy: qcom: qmp-pcie: Add QMP PCIe Multi-PHY driver") Signed-off-by: David Carlier Reviewed-by: Manivannan Sadhasivam Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260825225729.230493-1-devnexen@gmail.com Signed-off-by: Vinod Koul --- drivers/phy/qualcomm/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/phy/qualcomm/Kconfig b/drivers/phy/qualcomm/Kconfig index d802e63ad74a..204b81ca6585 100644 --- a/drivers/phy/qualcomm/Kconfig +++ b/drivers/phy/qualcomm/Kconfig @@ -96,6 +96,7 @@ config PHY_QCOM_QMP_PCIE_MULTIPHY tristate "Qualcomm QMP PCIe Multi PHY Driver" depends on PCI || COMPILE_TEST select GENERIC_PHY + select MFD_SYSCON help Enable this to support the QMP PCIe PHY transceiver that is used with PCIe controllers on Qualcomm chips. This PHY is a single From fb0f0e0e9e5dace7a6d9cde289537980ad35db3d Mon Sep 17 00:00:00 2001 From: David Carlier Date: Tue, 25 Aug 2026 23:57:29 +0100 Subject: [PATCH 090/114] phy: qcom: qmp-pcie: Check clock-output-names count in the multi-PHY driver of_property_count_strings() returns a negative errno when the property is missing or malformed. The value was passed straight to devm_kcalloc(), whose overflow check rejected it, turning a device tree problem into a bogus -ENOMEM with no diagnostic. Fixes: 1781be3c8a5d ("phy: qcom: qmp-pcie: Add QMP PCIe Multi-PHY driver") Signed-off-by: David Carlier Reviewed-by: Manivannan Sadhasivam Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260825225729.230493-2-devnexen@gmail.com Signed-off-by: Vinod Koul --- drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c b/drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c index 7bc62a668d34..3d225c0bb80e 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c @@ -1357,7 +1357,8 @@ static int qmp_pcie_multiphy_register_clocks(struct device *dev, num_pipe_outputs = of_property_count_strings(np, "clock-output-names"); if (num_pipe_outputs < 0) - return num_pipe_outputs; + return dev_err_probe(dev, num_pipe_outputs, + "failed to read clock-output-names\n"); qmp_data->num_pipe_outputs = num_pipe_outputs; qmp_data->pipe_out_clks = devm_kcalloc(dev, num_pipe_outputs, From e95b476302852ac1fb5bffaa824ac2cd35178e58 Mon Sep 17 00:00:00 2001 From: Julien Massot Date: Wed, 26 Aug 2026 14:38:35 +0200 Subject: [PATCH 091/114] dt-bindings: phy: mediatek,xsphy: add optional repeater phys property USB2 PHY ports may be connected to an external eUSB2 repeater chip. Add an optional phys property to the port sub-node to allow referencing it, following the same pattern used by the Synopsys eUSB2 PHY binding. Signed-off-by: Julien Massot Acked-by: Conor Dooley Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260826-mtk-usb2-repeater-usb2-v1-1-e8e48e2fd6d8@collabora.com Signed-off-by: Vinod Koul --- Documentation/devicetree/bindings/phy/mediatek,xsphy.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/phy/mediatek,xsphy.yaml b/Documentation/devicetree/bindings/phy/mediatek,xsphy.yaml index 0bed847bb4ad..c00115edf7ab 100644 --- a/Documentation/devicetree/bindings/phy/mediatek,xsphy.yaml +++ b/Documentation/devicetree/bindings/phy/mediatek,xsphy.yaml @@ -108,6 +108,11 @@ patternProperties: - PHY_TYPE_USB2 - PHY_TYPE_USB3 + phys: + description: + eUSB2 repeater PHY connected between this port and the USB connector. + maxItems: 1 + # The following optional vendor properties are only for debug or HQA test mediatek,eye-src: description: From bda1af7b177a94f6d169f0b359c61e7a7d39a36e Mon Sep 17 00:00:00 2001 From: Julien Massot Date: Wed, 26 Aug 2026 14:38:36 +0200 Subject: [PATCH 092/114] phy: mediatek: xsphy: add optional repeater support for USB2 ports USB2 PHY port sub-nodes may carry an optional phys property referencing an external eUSB2 repeater. Fetch the repeater during probe using devm_of_phy_optional_get(), initialize and exit it alongside the USB2 PHY, and propagate USB mode changes to it. This follows the same pattern used by the Synopsys eUSB2 PHY driver. Signed-off-by: Julien Massot Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260826-mtk-usb2-repeater-usb2-v1-2-e8e48e2fd6d8@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/mediatek/phy-mtk-xsphy.c | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/drivers/phy/mediatek/phy-mtk-xsphy.c b/drivers/phy/mediatek/phy-mtk-xsphy.c index b29220bc5592..597186de48c5 100644 --- a/drivers/phy/mediatek/phy-mtk-xsphy.c +++ b/drivers/phy/mediatek/phy-mtk-xsphy.c @@ -102,6 +102,7 @@ struct xsphy_instance { struct phy *phy; void __iomem *port_base; struct clk *ref_clk; /* reference clock of anolog phy */ + struct phy *repeater; u32 index; u32 type; struct regmap *type_sw; @@ -404,6 +405,11 @@ static int mtk_phy_init(struct phy *phy) switch (inst->type) { case PHY_TYPE_USB2: + ret = phy_init(inst->repeater); + if (ret) { + clk_disable_unprepare(inst->ref_clk); + return ret; + } u2_phy_instance_init(xsphy, inst); u2_phy_props_set(xsphy, inst); break; @@ -451,7 +457,11 @@ static int mtk_phy_exit(struct phy *phy) { struct xsphy_instance *inst = phy_get_drvdata(phy); + if (inst->type == PHY_TYPE_USB2) + phy_exit(inst->repeater); + clk_disable_unprepare(inst->ref_clk); + return 0; } @@ -459,9 +469,16 @@ static int mtk_phy_set_mode(struct phy *phy, enum phy_mode mode, int submode) { struct xsphy_instance *inst = phy_get_drvdata(phy); struct mtk_xsphy *xsphy = dev_get_drvdata(phy->dev.parent); + int ret; - if (inst->type == PHY_TYPE_USB2) - u2_phy_instance_set_mode(xsphy, inst, mode); + if (inst->type != PHY_TYPE_USB2) + return 0; + + ret = phy_set_mode_ext(inst->repeater, mode, submode); + if (ret) + return ret; + + u2_phy_instance_set_mode(xsphy, inst, mode); return 0; } @@ -603,6 +620,11 @@ static int mtk_xsphy_probe(struct platform_device *pdev) retval = phy_type_syscon_get(inst, child_np); if (retval) return retval; + + inst->repeater = devm_of_phy_optional_get(dev, child_np, NULL); + if (IS_ERR(inst->repeater)) + return dev_err_probe(dev, PTR_ERR(inst->repeater), + "failed to get repeater\n"); } provider = devm_of_phy_provider_register(dev, mtk_phy_xlate); From d68e47a06c5cdc90cbd29ce2e74fa61eab5d47cc Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Mon, 31 Aug 2026 14:36:44 +0800 Subject: [PATCH 093/114] phy: rockchip: naneng-combphy: Fix TX detect RX termination errata Some PHY revisions may fail to detect the peer RX's termination resistor (RTERM) under certain critical temperature conditions. This causes TX detection failures on PCIe links. Add a workaround to force the RTERM detection ready signal for affected PHY revisions. This ensures reliable TX-to-RX termination detection across all operating temperature ranges. The fix applies to RK3562, RK3568, RK3576 and RK3588 SoCs which share the same PHY IP with this hardware errata. Fixes: 7160820d742a ("phy: rockchip: add naneng combo phy for RK3568") Fixes: f13bff25161b ("phy: rockchip-naneng-combo: Support rk3562") Fixes: ba8ad7eece66 ("phy: rockchip-naneng-combo: add rk3576 support") Fixes: a03c44277253 ("phy: rockchip: Add naneng combo phy support for RK3588") Signed-off-by: Shawn Lin Link: https://patch.msgid.link/1788158204-161347-1-git-send-email-shawn.lin@rock-chips.com Signed-off-by: Vinod Koul --- .../rockchip/phy-rockchip-naneng-combphy.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c b/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c index 7843356a4dd4..3102a99128f6 100644 --- a/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c +++ b/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c @@ -106,6 +106,9 @@ #define RK3568_PHYREG18 0x44 #define RK3568_PHYREG18_PLL_LOOP 0x32 +#define RK3568_PHYREG26 0x64 +#define RK3568_PHYREG26_FORCE_RTERM_DET_RDY BIT(5) + #define RK3568_PHYREG30 0x74 #define RK3568_PHYREG30_GATE_TX_PCK_SEL BIT(7) #define RK3568_PHYREG30_GATE_TX_PCK_DLY_PLL_OFF BIT(7) @@ -194,6 +197,7 @@ struct rockchip_combphy_cfg { unsigned int num_phys; unsigned int phy_ids[3]; const struct rockchip_combphy_grfcfg *grfcfg; + bool force_rxterm_det_rdy; int (*combphy_cfg)(struct rockchip_combphy_priv *priv); }; @@ -265,6 +269,17 @@ static int rockchip_combphy_init(struct phy *phy) switch (priv->type) { case PHY_TYPE_PCIE: + /* + * Hardware Errata: TX fails to detect peer RX termination. + * Some PHY revisions may fail to detect remote RX's RTERM + * (receiver termination resistor) under certain critical + * temperature conditions. Set force rterm detect ready to + * fix it. + */ + if (priv->cfg->force_rxterm_det_rdy) + rockchip_combphy_updatel(priv, RK3568_PHYREG26_FORCE_RTERM_DET_RDY, + RK3568_PHYREG26_FORCE_RTERM_DET_RDY, RK3568_PHYREG26); + fallthrough; case PHY_TYPE_USB3: case PHY_TYPE_SATA: case PHY_TYPE_SGMII: @@ -801,6 +816,7 @@ static const struct rockchip_combphy_cfg rk3562_combphy_cfgs = { }, .grfcfg = &rk3562_combphy_grfcfgs, .combphy_cfg = rk3562_combphy_cfg, + .force_rxterm_det_rdy = true, }; static int rk3568_combphy_cfg(struct rockchip_combphy_priv *priv) @@ -998,6 +1014,7 @@ static const struct rockchip_combphy_cfg rk3568_combphy_cfgs = { }, .grfcfg = &rk3568_combphy_grfcfgs, .combphy_cfg = rk3568_combphy_cfg, + .force_rxterm_det_rdy = true, }; static int rk3576_combphy_cfg(struct rockchip_combphy_priv *priv) @@ -1219,6 +1236,7 @@ static const struct rockchip_combphy_cfg rk3576_combphy_cfgs = { }, .grfcfg = &rk3576_combphy_grfcfgs, .combphy_cfg = rk3576_combphy_cfg, + .force_rxterm_det_rdy = true, }; static int rk3588_combphy_cfg(struct rockchip_combphy_priv *priv) @@ -1391,6 +1409,7 @@ static const struct rockchip_combphy_cfg rk3588_combphy_cfgs = { }, .grfcfg = &rk3588_combphy_grfcfgs, .combphy_cfg = rk3588_combphy_cfg, + .force_rxterm_det_rdy = true, }; static const struct of_device_id rockchip_combphy_of_match[] = { From 88498cfc3ce6d8ccd3080ab4cc3f394eb2754b1b Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Thu, 17 Sep 2026 14:13:04 -0700 Subject: [PATCH 094/114] phy: realtek: usb: Remove const from phy_cfg allocation type In preparation for making the devm_kmalloc family of allocators type aware, we need to make sure that the returned type from the allocation matches the type of the variable being assigned. (Before, the allocator would always return "void *", which can be implicitly cast to any pointer type.) In both the USB2 and USB3 PHY drivers, the assigned type is "struct phy_cfg *", but the converted allocation type would be "const struct phy_cfg *", as the size was taken from the local "phy_cfg", which points to const as it comes from of_device_get_match_data(). As there is no general way to remove const qualifiers, take the size from the assignment target instead. No change in allocation size results. Build tested ARCH=x86_64 allmodconfig with GCC 16.2.0: drivers/phy/realtek/phy-rtk-usb2.o drivers/phy/realtek/phy-rtk-usb3.o Assisted-by: LLM coccinelle Signed-off-by: Kees Cook Link: https://patch.msgid.link/20260917211303.i.538-kees@kernel.org Signed-off-by: Vinod Koul --- drivers/phy/realtek/phy-rtk-usb2.c | 3 ++- drivers/phy/realtek/phy-rtk-usb3.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/phy/realtek/phy-rtk-usb2.c b/drivers/phy/realtek/phy-rtk-usb2.c index 248550ef98ca..1f08b151d90a 100644 --- a/drivers/phy/realtek/phy-rtk-usb2.c +++ b/drivers/phy/realtek/phy-rtk-usb2.c @@ -1022,7 +1022,8 @@ static int rtk_usb2phy_probe(struct platform_device *pdev) return -ENOMEM; rtk_phy->dev = &pdev->dev; - rtk_phy->phy_cfg = devm_kzalloc(dev, sizeof(*phy_cfg), GFP_KERNEL); + rtk_phy->phy_cfg = devm_kzalloc(dev, sizeof(*rtk_phy->phy_cfg), + GFP_KERNEL); if (!rtk_phy->phy_cfg) return -ENOMEM; diff --git a/drivers/phy/realtek/phy-rtk-usb3.c b/drivers/phy/realtek/phy-rtk-usb3.c index cce453686db2..4231a8aa0ba5 100644 --- a/drivers/phy/realtek/phy-rtk-usb3.c +++ b/drivers/phy/realtek/phy-rtk-usb3.c @@ -576,7 +576,8 @@ static int rtk_usb3phy_probe(struct platform_device *pdev) return -ENOMEM; rtk_phy->dev = &pdev->dev; - rtk_phy->phy_cfg = devm_kzalloc(dev, sizeof(*phy_cfg), GFP_KERNEL); + rtk_phy->phy_cfg = devm_kzalloc(dev, sizeof(*rtk_phy->phy_cfg), + GFP_KERNEL); if (!rtk_phy->phy_cfg) return -ENOMEM; From c01ccf91b257741e9edc374a465126bae9ae4711 Mon Sep 17 00:00:00 2001 From: Aristo Chen Date: Thu, 3 Sep 2026 05:47:58 +0000 Subject: [PATCH 095/114] phy: freescale: fsl-samsung-hdmi: initialize default rate The HDMI PHY clock reports 74.25 MHz when cur_cfg is unset, but does not establish that rate in hardware. The common clock framework caches the reported rate when registering the clock and skips a first clk_set_rate(74250000) request because the rate appears unchanged. Consequently, a display whose initial mode uses a 74.25 MHz pixel clock can leave the PHY with its bootloader register state and receive no TMDS signal. Requesting another rate first avoids the issue because the set_rate callback configures the PHY. Program the 74.25 MHz table entry with the reference clock enabled before registering the clock provider. recalc_rate can then report the configured rate, and a skipped first rate change is safe. Also enable the reference clock when restoring that configuration on resume. Fixes: 6ad082bee902 ("phy: freescale: add Samsung HDMI PHY") Signed-off-by: Aristo Chen Link: https://patch.msgid.link/20260903054811.9720-1-aristo.chen@canonical.com Signed-off-by: Vinod Koul --- drivers/phy/freescale/phy-fsl-samsung-hdmi.c | 59 +++++++++++++++----- 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/drivers/phy/freescale/phy-fsl-samsung-hdmi.c b/drivers/phy/freescale/phy-fsl-samsung-hdmi.c index ded142020e72..02539ad0b0fe 100644 --- a/drivers/phy/freescale/phy-fsl-samsung-hdmi.c +++ b/drivers/phy/freescale/phy-fsl-samsung-hdmi.c @@ -44,6 +44,8 @@ #define MHZ (1000UL * 1000UL) #endif +#define PHY_DEFAULT_RATE 74250000 + #define PHY_PLL_DIV_REGS_NUM 7 struct phy_config { @@ -489,17 +491,6 @@ static int fsl_samsung_hdmi_phy_configure(struct fsl_samsung_hdmi_phy *phy, return ret; } -static unsigned long phy_clk_recalc_rate(struct clk_hw *hw, - unsigned long parent_rate) -{ - struct fsl_samsung_hdmi_phy *phy = to_fsl_samsung_hdmi_phy(hw); - - if (!phy->cur_cfg) - return 74250000; - - return phy->cur_cfg->pixclk; -} - /* Helper function to lookup the available fractional-divider rate */ static const struct phy_config *fsl_samsung_hdmi_phy_lookup_rate(unsigned long rate) { @@ -520,6 +511,14 @@ static const struct phy_config *fsl_samsung_hdmi_phy_lookup_rate(unsigned long r &phy_pll_cfg[i] : &phy_pll_cfg[i+1]); } +static unsigned long phy_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct fsl_samsung_hdmi_phy *phy = to_fsl_samsung_hdmi_phy(hw); + + return phy->cur_cfg->pixclk; +} + static void fsl_samsung_hdmi_calculate_phy(struct phy_config *cal_phy, unsigned long rate, u8 p, u16 m, u8 s) { @@ -640,6 +639,7 @@ static int phy_clk_register(struct fsl_samsung_hdmi_phy *phy) static int fsl_samsung_hdmi_phy_probe(struct platform_device *pdev) { + const struct phy_config *default_cfg; struct fsl_samsung_hdmi_phy *phy; int ret; @@ -664,6 +664,23 @@ static int fsl_samsung_hdmi_phy_probe(struct platform_device *pdev) return dev_err_probe(phy->dev, PTR_ERR(phy->refclk), "failed to get ref clk\n"); + /* + * Establish the rate reported by recalc_rate() before registering the + * clock. Otherwise CCF may skip the first set_rate() when it requests + * the assumed default rate, leaving the PHY unconfigured. + */ + ret = clk_prepare_enable(phy->refclk); + if (ret) + return dev_err_probe(phy->dev, ret, + "failed to enable ref clk\n"); + + default_cfg = fsl_samsung_hdmi_phy_lookup_rate(PHY_DEFAULT_RATE); + ret = fsl_samsung_hdmi_phy_configure(phy, default_cfg); + clk_disable_unprepare(phy->refclk); + if (ret) + return dev_err_probe(phy->dev, ret, + "failed to configure default rate\n"); + pm_runtime_get_noresume(phy->dev); pm_runtime_set_active(phy->dev); pm_runtime_enable(phy->dev); @@ -702,7 +719,7 @@ static int __maybe_unused fsl_samsung_hdmi_phy_suspend(struct device *dev) static int __maybe_unused fsl_samsung_hdmi_phy_resume(struct device *dev) { struct fsl_samsung_hdmi_phy *phy = dev_get_drvdata(dev); - int ret = 0; + int ret; ret = clk_prepare_enable(phy->apbclk); if (ret) { @@ -710,11 +727,23 @@ static int __maybe_unused fsl_samsung_hdmi_phy_resume(struct device *dev) return ret; } - if (phy->cur_cfg) - ret = fsl_samsung_hdmi_phy_configure(phy, phy->cur_cfg); + if (!phy->cur_cfg) + return 0; + ret = clk_prepare_enable(phy->refclk); + if (ret) { + dev_err(phy->dev, "failed to enable ref clk\n"); + goto disable_apbclk; + } + + ret = fsl_samsung_hdmi_phy_configure(phy, phy->cur_cfg); + clk_disable_unprepare(phy->refclk); + if (!ret) + return 0; + +disable_apbclk: + clk_disable_unprepare(phy->apbclk); return ret; - } static DEFINE_RUNTIME_DEV_PM_OPS(fsl_samsung_hdmi_phy_pm_ops, From c850a8821901163998ec4b7932b3d79dbe480543 Mon Sep 17 00:00:00 2001 From: Wayne Chang Date: Thu, 3 Sep 2026 15:19:34 +0800 Subject: [PATCH 096/114] dt-bindings: phy: tegra-xusb: Add support for Tegra264 Add the compatible string for the Tegra264 XUSB PHY. A dedicated compatible string is used instead of falling back to nvidia,tegra234-xusb-padctl, because Tegra264 is not programming-compatible with Tegra234, even though they share the same DT property set. The driver selects per-SoC data from the compatible string, and Tegra264 differs from Tegra234 in at least: - Tegra264 uses hardware tracking mode (trk_hw_mode), while Tegra234 disables it. - On Tegra234, CYA_TRK_CODE_UPDATE_ON_IDLE needs to be cleared so that the calibration values can be captured while the controller is still in reset. Tegra264 does not require this, and the corresponding handling is not even present for it. A fallback of nvidia,tegra234-xusb-padctl would let an OS that only knows about Tegra234 probe Tegra264 with the wrong programming sequence. This is also why Tegra234 did not fall back to nvidia,tegra194-xusb-padctl in this same binding: the DT schema is shared, but each generation needs its own compatible. Signed-off-by: Wayne Chang Acked-by: Conor Dooley Reviewed-by: Jon Hunter Tested-by: Jon Hunter Link: https://patch.msgid.link/20260903071937.533646-2-waynec@nvidia.com Signed-off-by: Vinod Koul --- .../devicetree/bindings/phy/nvidia,tegra194-xusb-padctl.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/phy/nvidia,tegra194-xusb-padctl.yaml b/Documentation/devicetree/bindings/phy/nvidia,tegra194-xusb-padctl.yaml index d8de900a4fce..f6fe3c90b5b0 100644 --- a/Documentation/devicetree/bindings/phy/nvidia,tegra194-xusb-padctl.yaml +++ b/Documentation/devicetree/bindings/phy/nvidia,tegra194-xusb-padctl.yaml @@ -45,6 +45,7 @@ properties: enum: - nvidia,tegra194-xusb-padctl - nvidia,tegra234-xusb-padctl + - nvidia,tegra264-xusb-padctl reg: items: From 9358936fa13a8d7d9fbe92762ab38271c8dcb7c0 Mon Sep 17 00:00:00 2001 From: Sing-Han Chen Date: Thu, 3 Sep 2026 15:19:35 +0800 Subject: [PATCH 097/114] phy: tegra: xusb: Use devm_clk_get_optional to fetch USB2 tracking clock The USB2 PAD tracking clock is an optional clock so use devm_clk_get_optional() so a missing clock in devicetree is represented as NULL and probe can continue. clk_prepare_enable() and clk_disable_unprepare() already treat a NULL clock as a no-op. Signed-off-by: Sing-Han Chen Signed-off-by: Wayne Chang Reviewed-by: Jon Hunter Tested-by: Jon Hunter Link: https://patch.msgid.link/20260903071937.533646-3-waynec@nvidia.com Signed-off-by: Vinod Koul --- drivers/phy/tegra/xusb-tegra186.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/phy/tegra/xusb-tegra186.c b/drivers/phy/tegra/xusb-tegra186.c index 60156aea2707..800be0cbb3f9 100644 --- a/drivers/phy/tegra/xusb-tegra186.c +++ b/drivers/phy/tegra/xusb-tegra186.c @@ -1045,7 +1045,7 @@ tegra186_usb2_pad_probe(struct tegra_xusb_padctl *padctl, goto out; } - priv->usb2_trk_clk = devm_clk_get(&pad->dev, "trk"); + priv->usb2_trk_clk = devm_clk_get_optional(&pad->dev, "trk"); if (IS_ERR(priv->usb2_trk_clk)) { err = PTR_ERR(priv->usb2_trk_clk); dev_dbg(&pad->dev, "failed to get usb2 trk clock: %d\n", err); From 475df243ffcad579c9beb6fbd47b1f9ee1045b7c Mon Sep 17 00:00:00 2001 From: Wayne Chang Date: Thu, 3 Sep 2026 15:19:36 +0800 Subject: [PATCH 098/114] phy: tegra: xusb: Increase timeout for USB2_TRK_COMPLETED polling On Tegra264, the UTMI bias tracking circuit runs at 1 MHz and needs more cycles for the biases to settle than on earlier SoCs such as Tegra234. The existing timeout is therefore not long enough and can cause spurious polling failures during bias pad power-on. Therefore, increase the timeout to 200 cycles to support Tegra264 devices Signed-off-by: Wayne Chang Reviewed-by: Jon Hunter Tested-by: Jon Hunter Link: https://patch.msgid.link/20260903071937.533646-4-waynec@nvidia.com Signed-off-by: Vinod Koul --- drivers/phy/tegra/xusb-tegra186.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/phy/tegra/xusb-tegra186.c b/drivers/phy/tegra/xusb-tegra186.c index 800be0cbb3f9..9c1bcd4a72ac 100644 --- a/drivers/phy/tegra/xusb-tegra186.c +++ b/drivers/phy/tegra/xusb-tegra186.c @@ -636,7 +636,7 @@ static void tegra186_utmi_bias_pad_power_on(struct tegra_xusb_padctl *padctl) if (padctl->soc->poll_trk_completed) { err = padctl_readl_poll(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL1, - USB2_TRK_COMPLETED, USB2_TRK_COMPLETED, 100); + USB2_TRK_COMPLETED, USB2_TRK_COMPLETED, 200); if (err) { /* The failure with polling on trk complete will not * cause the failure of powering on the bias pad. From 46fc5849823a67a4b480dec59266969a1b7f8a42 Mon Sep 17 00:00:00 2001 From: Wayne Chang Date: Thu, 3 Sep 2026 15:19:37 +0800 Subject: [PATCH 099/114] phy: tegra: xusb: Add Tegra264 support Add support for the XUSB pad controller for Tegra264. Most of the Tegra264 XUSB PADCTL registers definition and programming sequence are the same as Tegra234, Tegra264 XUSB PADCTL can share the same driver with Tegra186, Tegra194, and Tegra234 XUSB PADCTL. Signed-off-by: Wayne Chang Reviewed-by: Jon Hunter Tested-by: Jon Hunter Link: https://patch.msgid.link/20260903071937.533646-5-waynec@nvidia.com Signed-off-by: Vinod Koul --- drivers/phy/tegra/Makefile | 1 + drivers/phy/tegra/xusb-tegra186.c | 27 ++++++++++++++++++++++++++- drivers/phy/tegra/xusb.c | 6 ++++++ drivers/phy/tegra/xusb.h | 3 +++ 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/drivers/phy/tegra/Makefile b/drivers/phy/tegra/Makefile index eeeea72de117..33a695d4ed59 100644 --- a/drivers/phy/tegra/Makefile +++ b/drivers/phy/tegra/Makefile @@ -8,4 +8,5 @@ phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_210_SOC) += xusb-tegra210.o phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_186_SOC) += xusb-tegra186.o phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_194_SOC) += xusb-tegra186.o phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_234_SOC) += xusb-tegra186.o +phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_264_SOC) += xusb-tegra186.o obj-$(CONFIG_PHY_TEGRA194_P2U) += phy-tegra194-p2u.o diff --git a/drivers/phy/tegra/xusb-tegra186.c b/drivers/phy/tegra/xusb-tegra186.c index 9c1bcd4a72ac..322efdea0d80 100644 --- a/drivers/phy/tegra/xusb-tegra186.c +++ b/drivers/phy/tegra/xusb-tegra186.c @@ -1668,7 +1668,8 @@ EXPORT_SYMBOL_GPL(tegra186_xusb_padctl_soc); #endif #if IS_ENABLED(CONFIG_ARCH_TEGRA_194_SOC) || \ - IS_ENABLED(CONFIG_ARCH_TEGRA_234_SOC) + IS_ENABLED(CONFIG_ARCH_TEGRA_234_SOC) || \ + IS_ENABLED(CONFIG_ARCH_TEGRA_264_SOC) static const char * const tegra194_xusb_padctl_supply_names[] = { "avdd-usb", "vclamp-usb", @@ -1753,6 +1754,30 @@ const struct tegra_xusb_padctl_soc tegra234_xusb_padctl_soc = { .has_per_pad_term = true, }; EXPORT_SYMBOL_GPL(tegra234_xusb_padctl_soc); + +const struct tegra_xusb_padctl_soc tegra264_xusb_padctl_soc = { + .num_pads = ARRAY_SIZE(tegra194_pads), + .pads = tegra194_pads, + .ports = { + .usb2 = { + .ops = &tegra186_usb2_port_ops, + .count = 4, + }, + .usb3 = { + .ops = &tegra186_usb3_port_ops, + .count = 4, + }, + }, + .ops = &tegra186_xusb_padctl_ops, + .supply_names = tegra194_xusb_padctl_supply_names, + .num_supplies = ARRAY_SIZE(tegra194_xusb_padctl_supply_names), + .supports_gen2 = true, + .poll_trk_completed = true, + .trk_hw_mode = true, + .supports_lp_cfg_en = true, + .has_per_pad_term = true, +}; +EXPORT_SYMBOL_GPL(tegra264_xusb_padctl_soc); #endif MODULE_AUTHOR("JC Kuo "); diff --git a/drivers/phy/tegra/xusb.c b/drivers/phy/tegra/xusb.c index 9d74c0ecc31b..d0d0b252a205 100644 --- a/drivers/phy/tegra/xusb.c +++ b/drivers/phy/tegra/xusb.c @@ -77,6 +77,12 @@ static const struct of_device_id tegra_xusb_padctl_of_match[] = { .compatible = "nvidia,tegra234-xusb-padctl", .data = &tegra234_xusb_padctl_soc, }, +#endif +#if defined(CONFIG_ARCH_TEGRA_264_SOC) + { + .compatible = "nvidia,tegra264-xusb-padctl", + .data = &tegra264_xusb_padctl_soc, + }, #endif { } }; diff --git a/drivers/phy/tegra/xusb.h b/drivers/phy/tegra/xusb.h index 77609e54de66..d8d799d9e752 100644 --- a/drivers/phy/tegra/xusb.h +++ b/drivers/phy/tegra/xusb.h @@ -514,5 +514,8 @@ extern const struct tegra_xusb_padctl_soc tegra194_xusb_padctl_soc; #if defined(CONFIG_ARCH_TEGRA_234_SOC) extern const struct tegra_xusb_padctl_soc tegra234_xusb_padctl_soc; #endif +#if defined(CONFIG_ARCH_TEGRA_264_SOC) +extern const struct tegra_xusb_padctl_soc tegra264_xusb_padctl_soc; +#endif #endif /* __PHY_TEGRA_XUSB_H */ From af00a892e6515e3f08f2079578695389b01ba3a2 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 22:55:13 +0300 Subject: [PATCH 100/114] phy: hdmi: Add optional FRL TxFFE config options During HDMI 2.1 FRL link training, the source and sink can negotiate a Transmitter Feed-Forward Equalization (TxFFE) level to improve the signal quality. Starting from zero, the source may increment the TxFFE level up to a maximum agreed during the LTS3 stage if the sink keeps reporting FLT failures. TxFFE adjustment is optional and only attempted when both the source and the connected sink support it. Since the existing HDMI PHY configuration API covers the FRL rate/lane selection only, provide the following fields to the frl sub-struct of phy_configure_opts_hdmi: * ffe_level: the TxFFE level to apply, only meaningful when set_ffe_level is set. * set_ffe_level: a 1-bit flag that changes the semantics of the phy_configure() call, i.e. when set, the PHY driver must apply the new ffe_level and ignore the other frl related fields. The flag-based approach reflects an important invariant in the link training process: whenever the FRL rate or lane count changes, the TxFFE level must be reset to zero. A separate phy_configure() call with set_ffe_level can only follow after the rate has been established, making the two operations deliberately distinct. Signed-off-by: Cristian Ciocaltea Link: https://patch.msgid.link/20260901-hdptx-ffe-v2-1-3a5922c76fc5@collabora.com Signed-off-by: Vinod Koul --- include/linux/phy/phy-hdmi.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/linux/phy/phy-hdmi.h b/include/linux/phy/phy-hdmi.h index d4cf4430ee8f..1d4b62475079 100644 --- a/include/linux/phy/phy-hdmi.h +++ b/include/linux/phy/phy-hdmi.h @@ -19,6 +19,10 @@ enum phy_hdmi_mode { * @tmds_char_rate: HDMI TMDS Character Rate in Hertz. * @frl.rate_per_lane: HDMI FRL Rate per Lane in Gbps. * @frl.lanes: HDMI FRL lanes count. + * @frl.ffe_level: Transmitter Feed Forward Equalizer Level. + * Optional, only meaningful when set_ffe_level flag is on. + * @frl.set_ffe_level: Flag indicating whether or not to reconfigure ffe_level. + * All the other struct fields must be ignored when this is used. * * This structure is used to represent the configuration state of a HDMI phy. */ @@ -29,6 +33,8 @@ struct phy_configure_opts_hdmi { struct { u8 rate_per_lane; u8 lanes; + u8 ffe_level; + u8 set_ffe_level : 1; } frl; }; }; From a673b3d18e37e573d52bf41c47e75d46982f2fae Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 22:55:14 +0300 Subject: [PATCH 101/114] phy: rockchip: samsung-hdptx: Handle PHY config after module reload The pll_config_dirty mechanism introduced in commit aec3e4ce25da ("phy: rockchip: samsung-hdptx: Handle uncommitted PHY config changes") invalidates the clock rate in determine_rate() by resetting req->rate to zero, ensuring CCF will invoke set_rate() to program pending PLL configuration changes into hardware. However, after a module reload cycle the PHY PLL clock gets re-registered with CCF, which causes the framework's cached rate to also be zero. Setting req->rate to zero then has no effect, since CCF sees no difference between the requested and current rates and skips calling set_rate(), leaving the PLL unconfigured. Address this by first computing the actual target rate from the HDMI link configuration, and only then invalidating it when it matches the CCF cached rate. Fixes: aec3e4ce25da ("phy: rockchip: samsung-hdptx: Handle uncommitted PHY config changes") Signed-off-by: Cristian Ciocaltea Link: https://patch.msgid.link/20260901-hdptx-ffe-v2-2-3a5922c76fc5@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c index 24ed0d7eb6f8..b03042ec9a84 100644 --- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c +++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c @@ -2346,14 +2346,16 @@ static int rk_hdptx_phy_clk_determine_rate(struct clk_hw *hw, * to ensure rk_hdptx_phy_clk_set_rate() will be always invoked. * Otherwise, restrict the rate according to the PHY link setup. */ - if (hdptx->pll_config_dirty) - req->rate = 0; - else if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL) + + if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL) req->rate = hdptx->hdmi_cfg.rate; else req->rate = DIV_ROUND_CLOSEST_ULL(hdptx->hdmi_cfg.rate * 8, hdptx->hdmi_cfg.bpc); + if (hdptx->pll_config_dirty && req->rate == clk_hw_get_rate(hw)) + req->rate = 0; + return 0; } From 192244b9cb5d15288f318f20ad202f2274359474 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 22:55:15 +0300 Subject: [PATCH 102/114] phy: rockchip: samsung-hdptx: Add support for FRL TxFFE level control During HDMI 2.1 FRL link training, the source may need to incrementally raise the TxFFE level in response to persistent link failures reported by the sink during LTS3. The phy_configure_opts_hdmi struct now carries ffe_level and set_ffe_level fields to convey such an update independently of a full rate reconfiguration. Wire up the optional TxFFE control in the Samsung HDPTX PHY driver. Signed-off-by: Cristian Ciocaltea Link: https://patch.msgid.link/20260901-hdptx-ffe-v2-3-3a5922c76fc5@collabora.com Signed-off-by: Vinod Koul --- .../phy/rockchip/phy-rockchip-samsung-hdptx.c | 72 +++++++++++++++++-- 1 file changed, 68 insertions(+), 4 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c index b03042ec9a84..6879eecc5a9f 100644 --- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c +++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c @@ -333,6 +333,7 @@ #define FRL_3G3L_RATE 900000000 #define FRL_6G3L_RATE 1800000000 #define FRL_8G4L_RATE 3200000000 +#define FRL_FFE_MAX_LEVEL 3 enum dp_link_rate { DP_BW_RBR, @@ -466,6 +467,16 @@ static const struct ropll_config rk_hdptx_tmds_ropll_cfg[] = { { 25175000ULL, 84, 84, 1, 1, 15, 1, 168, 1, 16, 4, 1, 1, }, }; +static const struct ffe_config { + u8 pre_shoot; + u8 de_emphasis; +} rk_hdptx_frl_ffe_cfg[FRL_FFE_MAX_LEVEL + 1] = { + { 0x3, 0x4 }, + { 0x3, 0x6 }, + { 0x3, 0x8 }, + { 0x3, 0x9 }, +}; + static const struct reg_sequence rk_hdptx_common_cmn_init_seq[] = { REG_SEQ0(CMN_REG(0009), 0x0c), REG_SEQ0(CMN_REG(000a), 0x83), @@ -1321,6 +1332,45 @@ static int rk_hdptx_tmds_ropll_mode_config(struct rk_hdptx_phy *hdptx) return rk_hdptx_post_enable_lane(hdptx); } +static int rk_hdptx_frl_ffe_config(struct rk_hdptx_phy *hdptx, u8 ffe_level) +{ + u8 val; + + if (ffe_level > FRL_FFE_MAX_LEVEL) + return -EINVAL; + + val = rk_hdptx_frl_ffe_cfg[ffe_level].pre_shoot; + + regmap_update_bits(hdptx->regmap, LANE_REG(0305), + LN_TX_DRV_PRE_LVL_CTRL_MASK, + FIELD_PREP(LN_TX_DRV_PRE_LVL_CTRL_MASK, val)); + regmap_update_bits(hdptx->regmap, LANE_REG(0405), + LN_TX_DRV_PRE_LVL_CTRL_MASK, + FIELD_PREP(LN_TX_DRV_PRE_LVL_CTRL_MASK, val)); + regmap_update_bits(hdptx->regmap, LANE_REG(0505), + LN_TX_DRV_PRE_LVL_CTRL_MASK, + FIELD_PREP(LN_TX_DRV_PRE_LVL_CTRL_MASK, val)); + regmap_update_bits(hdptx->regmap, LANE_REG(0605), + LN_TX_DRV_PRE_LVL_CTRL_MASK, + FIELD_PREP(LN_TX_DRV_PRE_LVL_CTRL_MASK, val)); + + val = rk_hdptx_frl_ffe_cfg[ffe_level].de_emphasis; + + regmap_update_bits(hdptx->regmap, LANE_REG(0304), + LN_TX_DRV_POST_LVL_CTRL_MASK, + FIELD_PREP(LN_TX_DRV_POST_LVL_CTRL_MASK, val)); + regmap_update_bits(hdptx->regmap, LANE_REG(0404), + LN_TX_DRV_POST_LVL_CTRL_MASK, + FIELD_PREP(LN_TX_DRV_POST_LVL_CTRL_MASK, val)); + regmap_update_bits(hdptx->regmap, LANE_REG(0504), + LN_TX_DRV_POST_LVL_CTRL_MASK, + FIELD_PREP(LN_TX_DRV_POST_LVL_CTRL_MASK, val)); + regmap_update_bits(hdptx->regmap, LANE_REG(0604), + LN_TX_DRV_POST_LVL_CTRL_MASK, + FIELD_PREP(LN_TX_DRV_POST_LVL_CTRL_MASK, val)); + return 0; +} + static void rk_hdptx_dp_reset(struct rk_hdptx_phy *hdptx) { reset_control_assert(hdptx->rsts[RST_LANE].rstc); @@ -1735,6 +1785,13 @@ static int rk_hdptx_phy_verify_hdmi_config(struct rk_hdptx_phy *hdptx, unsigned long long frl_rate = 100000000ULL * hdmi_in->frl.lanes * hdmi_in->frl.rate_per_lane; + if (hdmi_in->frl.set_ffe_level) { + if (hdmi_in->frl.ffe_level > FRL_FFE_MAX_LEVEL) + return -EINVAL; + + return 0; + } + switch (hdmi_in->frl.rate_per_lane) { case 3: case 6: @@ -2081,11 +2138,18 @@ static int rk_hdptx_phy_configure(struct phy *phy, union phy_configure_opts *opt if (ret) { dev_err(hdptx->dev, "invalid hdmi params for phy configure\n"); } else { - hdptx->pll_config_dirty = true; + if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL && + opts->hdmi.frl.set_ffe_level) { + dev_dbg(hdptx->dev, "%s ffe_level=%u\n", __func__, + opts->hdmi.frl.ffe_level); + ret = rk_hdptx_frl_ffe_config(hdptx, opts->hdmi.frl.ffe_level); + } else { + hdptx->pll_config_dirty = true; - dev_dbg(hdptx->dev, "%s %s rate=%llu bpc=%u\n", __func__, - hdptx->hdmi_cfg.mode ? "FRL" : "TMDS", - hdptx->hdmi_cfg.rate, hdptx->hdmi_cfg.bpc); + dev_dbg(hdptx->dev, "%s %s rate=%llu bpc=%u\n", __func__, + hdptx->hdmi_cfg.mode ? "FRL" : "TMDS", + hdptx->hdmi_cfg.rate, hdptx->hdmi_cfg.bpc); + } } return ret; From 6f61db971065919bd6ca2d62fdc4be158857c403 Mon Sep 17 00:00:00 2001 From: Kartik Rajput Date: Wed, 9 Sep 2026 15:13:58 +0530 Subject: [PATCH 103/114] dt-bindings: phy: Add nvidia,tegra264-mphy Add binding for the MIPI M-PHY on NVIDIA Tegra264 SoCs. The M-PHY is a hardware block with two lanes, l0 and l1, used by the Tegra264 UFS host controller. Signed-off-by: Kartik Rajput Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260909-tegra264-mphy-v1-1-ce61a80dc065@nvidia.com Signed-off-by: Vinod Koul --- .../bindings/phy/nvidia,tegra264-mphy.yaml | 108 ++++++++++++++++++ .../dt-bindings/phy/nvidia,tegra264-mphy.h | 12 ++ 2 files changed, 120 insertions(+) create mode 100644 Documentation/devicetree/bindings/phy/nvidia,tegra264-mphy.yaml create mode 100644 include/dt-bindings/phy/nvidia,tegra264-mphy.h diff --git a/Documentation/devicetree/bindings/phy/nvidia,tegra264-mphy.yaml b/Documentation/devicetree/bindings/phy/nvidia,tegra264-mphy.yaml new file mode 100644 index 000000000000..e2fb368e4a39 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/nvidia,tegra264-mphy.yaml @@ -0,0 +1,108 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/phy/nvidia,tegra264-mphy.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NVIDIA Tegra264 MIPI M-PHY + +maintainers: + - Kartik Rajput + - Thierry Reding + +properties: + compatible: + const: nvidia,tegra264-mphy + + reg: + items: + - description: Lane 0 register window + - description: Lane 1 register window + + reg-names: + items: + - const: l0 + - const: l1 + + clocks: + maxItems: 11 + + clock-names: + items: + - const: pll + - const: tx-ref + - const: l0-tx-sym + - const: l0-tx-ls + - const: l0-tx-hs + - const: l0-tx-fifo + - const: l0-rx-sym + - const: l0-rx-ana + - const: l0-rx-ls + - const: l0-rx-hs + - const: l1-rx-ana + + resets: + maxItems: 5 + + reset-names: + items: + - const: clk-ctl + - const: l0-rx + - const: l0-tx + - const: l1-rx + - const: l1-tx + + '#phy-cells': + description: | + The cell selects one M-PHY lane direction. Valid values are defined in + : TEGRA_MPHY_L0_TX, TEGRA_MPHY_L0_RX, + TEGRA_MPHY_L1_TX and TEGRA_MPHY_L1_RX. + const: 1 + +required: + - compatible + - reg + - reg-names + - clocks + - clock-names + - resets + - reset-names + - '#phy-cells' + +additionalProperties: false + +examples: + - | + #include + #include + #include + + mphy: phy@b910000 { + compatible = "nvidia,tegra264-mphy"; + reg = <0xb910000 0x10000>, + <0xb920000 0x10000>; + reg-names = "l0", "l1"; + clocks = <&bpmp TEGRA264_CLK_MPHY_CORE_PLL_FIXED>, + <&bpmp TEGRA264_CLK_MPHY_TX_1MHZ_REF>, + <&bpmp TEGRA264_CLK_MPHY_L0_TX_SYMB>, + <&bpmp TEGRA264_CLK_MPHY_L0_TX_LS_3XBIT>, + <&bpmp TEGRA264_CLK_MPHY_L0_TX_HS_SYMB_DIV>, + <&bpmp TEGRA264_CLK_MPHY_L0_UPHY_TX_FIFO>, + <&bpmp TEGRA264_CLK_MPHY_L0_RX_SYMB>, + <&bpmp TEGRA264_CLK_MPHY_L0_RX_ANA>, + <&bpmp TEGRA264_CLK_MPHY_L0_RX_LS_BIT>, + <&bpmp TEGRA264_CLK_MPHY_L0_RX_HS_SYMB_DIV>, + <&bpmp TEGRA264_CLK_MPHY_L1_RX_ANA>; + clock-names = "pll", "tx-ref", + "l0-tx-sym", "l0-tx-ls", "l0-tx-hs", "l0-tx-fifo", + "l0-rx-sym", "l0-rx-ana", "l0-rx-ls", "l0-rx-hs", + "l1-rx-ana"; + resets = <&bpmp TEGRA264_RESET_MPHY_CLK_CTL>, + <&bpmp TEGRA264_RESET_MPHY_L0_RX>, + <&bpmp TEGRA264_RESET_MPHY_L0_TX>, + <&bpmp TEGRA264_RESET_MPHY_L1_RX>, + <&bpmp TEGRA264_RESET_MPHY_L1_TX>; + reset-names = "clk-ctl", "l0-rx", "l0-tx", "l1-rx", "l1-tx"; + #phy-cells = <1>; + }; +... diff --git a/include/dt-bindings/phy/nvidia,tegra264-mphy.h b/include/dt-bindings/phy/nvidia,tegra264-mphy.h new file mode 100644 index 000000000000..e9e253cdf9ae --- /dev/null +++ b/include/dt-bindings/phy/nvidia,tegra264-mphy.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause */ +/* Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. */ + +#ifndef DT_BINDINGS_PHY_NVIDIA_TEGRA264_MPHY_H +#define DT_BINDINGS_PHY_NVIDIA_TEGRA264_MPHY_H + +#define TEGRA_MPHY_L0_TX 0 +#define TEGRA_MPHY_L0_RX 1 +#define TEGRA_MPHY_L1_TX 2 +#define TEGRA_MPHY_L1_RX 3 + +#endif /* DT_BINDINGS_PHY_NVIDIA_TEGRA264_MPHY_H */ From 256cb243f4c21b5ba845885df9d1e79ce29eb706 Mon Sep 17 00:00:00 2001 From: Kartik Rajput Date: Wed, 9 Sep 2026 15:13:59 +0530 Subject: [PATCH 104/114] phy: tegra: Add Tegra264 MPHY driver Add a driver for the MIPI M-PHY found on NVIDIA Tegra264 SoCs. The M-PHY has two lanes, each with a transmit and a receive direction, exposed as four PHYs to the UFS host controller. Co-developed-by: Thierry Reding Signed-off-by: Thierry Reding Signed-off-by: Kartik Rajput Link: https://patch.msgid.link/20260909-tegra264-mphy-v1-2-ce61a80dc065@nvidia.com Signed-off-by: Vinod Koul --- drivers/phy/tegra/Kconfig | 11 + drivers/phy/tegra/Makefile | 1 + drivers/phy/tegra/phy-tegra-mphy.c | 459 +++++++++++++++++++++++++++++ 3 files changed, 471 insertions(+) create mode 100644 drivers/phy/tegra/phy-tegra-mphy.c diff --git a/drivers/phy/tegra/Kconfig b/drivers/phy/tegra/Kconfig index f0734415fc94..b5ed5bc0c8ef 100644 --- a/drivers/phy/tegra/Kconfig +++ b/drivers/phy/tegra/Kconfig @@ -18,3 +18,14 @@ config PHY_TEGRA194_P2U help Enable this to support the P2U (PIPE to UPHY) that is part of Tegra 19x and 234 SOCs. + +config PHY_TEGRA_MPHY + tristate "NVIDIA Tegra264 MPHY driver" + depends on ARCH_TEGRA_264_SOC || COMPILE_TEST + select GENERIC_PHY + help + Enable this to support the MIPI M-PHY on NVIDIA Tegra264 SoCs. + The M-PHY provides the physical layer for the on-die UFS host + controller. The driver manages the shared clock-control block + and per-lane resets, exposing four sub-phys (L0-TX, L0-RX, + L1-TX, L1-RX) consumed by the UFS host driver. diff --git a/drivers/phy/tegra/Makefile b/drivers/phy/tegra/Makefile index 33a695d4ed59..f5ed9582d80b 100644 --- a/drivers/phy/tegra/Makefile +++ b/drivers/phy/tegra/Makefile @@ -10,3 +10,4 @@ phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_194_SOC) += xusb-tegra186.o phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_234_SOC) += xusb-tegra186.o phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_264_SOC) += xusb-tegra186.o obj-$(CONFIG_PHY_TEGRA194_P2U) += phy-tegra194-p2u.o +obj-$(CONFIG_PHY_TEGRA_MPHY) += phy-tegra-mphy.o diff --git a/drivers/phy/tegra/phy-tegra-mphy.c b/drivers/phy/tegra/phy-tegra-mphy.c new file mode 100644 index 000000000000..01cafe1c7477 --- /dev/null +++ b/drivers/phy/tegra/phy-tegra-mphy.c @@ -0,0 +1,459 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// NVIDIA Tegra264 MPHY driver. + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MPHY_GO_BIT BIT(0) + +#define MPHY_RX_APB_CAPABILITY_88_8B 0x88 +#define RX_HS_G1_SYNC_LENGTH_CAPABILITY(x) (((x) & 0x3f) << 24) + +#define MPHY_RX_APB_CAPABILITY_94_97 0x94 +#define RX_HS_G3_SYNC_LENGTH_CAPABILITY(x) (((x) & 0x3f) << 8) +#define RX_HS_G2_SYNC_LENGTH_CAPABILITY(x) (((x) & 0x3f) << 0) + +#define MPHY_TX_APB_VENDOR0 0x100 +#define MPHY_TX_APB_VENDOR2 0x108 +#define TX_CAL_DONE BIT(19) +#define TX_CAL_EN BIT(15) + +#define MPHY_RX_APB_VENDOR2 0x184 +#define RX_CAL_DONE BIT(19) +#define RX_CAL_EN BIT(15) + +#define MPHY_RX_APB_VENDOR3 0x188 +#define RX_MPHY2UPHY_IF_OVR_CTRL BIT(26) + +#define MPHY_RX_APB_VENDOR3B 0x220 +#define MPHY_RX_APB_VENDOR49 0x254 + +#define MPHY_EQ_TIMEOUT 0xffffffff +#define MPHY_PWR_CHANGE_CLK_BOOST 0x0017 + +#define MPHY_TX_OFFSET 0x1000 +#define MPHY_RX_OFFSET 0x2000 + +struct tegra_mphy_lane { + void __iomem *regs; + void __iomem *tx_regs; + void __iomem *rx_regs; + + struct reset_control *rst_rx; + struct reset_control *rst_tx; + + struct phy *tx; + struct phy *rx; +}; + +struct tegra_mphy { + struct device *dev; + + struct tegra_mphy_lane l0; + struct tegra_mphy_lane l1; + + unsigned int num_clks; + struct clk_bulk_data *clks; + + struct reset_control *rst_clk_ctl; + + unsigned int power_count; +}; + +static int tegra_mphy_get_clocks(struct tegra_mphy *mphy) +{ + struct device *dev = mphy->dev; + int num_clks; + + num_clks = devm_clk_bulk_get_all(dev, &mphy->clks); + if (num_clks < 0) + return dev_err_probe(dev, num_clks, "failed to get clocks\n"); + + mphy->num_clks = num_clks; + + return 0; +} + +static int tegra_mphy_get_resets(struct tegra_mphy *mphy) +{ + struct device *dev = mphy->dev; + + mphy->rst_clk_ctl = devm_reset_control_get_exclusive(dev, "clk-ctl"); + if (IS_ERR(mphy->rst_clk_ctl)) + return dev_err_probe(dev, PTR_ERR(mphy->rst_clk_ctl), + "failed to get clk-ctl reset\n"); + + mphy->l0.rst_rx = devm_reset_control_get_exclusive(dev, "l0-rx"); + if (IS_ERR(mphy->l0.rst_rx)) + return dev_err_probe(dev, PTR_ERR(mphy->l0.rst_rx), "failed to get l0-rx reset\n"); + + mphy->l0.rst_tx = devm_reset_control_get_exclusive(dev, "l0-tx"); + if (IS_ERR(mphy->l0.rst_tx)) + return dev_err_probe(dev, PTR_ERR(mphy->l0.rst_tx), "failed to get l0-tx reset\n"); + + mphy->l1.rst_rx = devm_reset_control_get_exclusive(dev, "l1-rx"); + if (IS_ERR(mphy->l1.rst_rx)) + return dev_err_probe(dev, PTR_ERR(mphy->l1.rst_rx), "failed to get l1-rx reset\n"); + + mphy->l1.rst_tx = devm_reset_control_get_exclusive(dev, "l1-tx"); + if (IS_ERR(mphy->l1.rst_tx)) + return dev_err_probe(dev, PTR_ERR(mphy->l1.rst_tx), "failed to get l1-tx reset\n"); + + return 0; +} + +static int tegra_mphy_rx_write_kick_go(struct tegra_mphy_lane *lane, u32 offset, u32 value) +{ + u32 v; + + writel(value, lane->rx_regs + offset); + + v = readl(lane->rx_regs + MPHY_RX_APB_VENDOR2); + v |= MPHY_GO_BIT; + writel(v, lane->rx_regs + MPHY_RX_APB_VENDOR2); + + return readl_poll_timeout(lane->rx_regs + MPHY_RX_APB_VENDOR2, v, + (v & MPHY_GO_BIT) == 0, 25, 5000); +} + +static int tegra_mphy_rx_power_on(struct phy *phy) +{ + struct tegra_mphy *mphy = dev_get_drvdata(phy->dev.parent); + struct tegra_mphy_lane *lane = phy_get_drvdata(phy); + u32 value; + int err; + + if (mphy->power_count++ == 0) { + err = clk_bulk_prepare_enable(mphy->num_clks, mphy->clks); + if (err) { + mphy->power_count--; + dev_err(&phy->dev, "failed to enable clocks: %d\n", err); + return err; + } + reset_control_deassert(mphy->rst_clk_ctl); + } + + reset_control_deassert(lane->rst_rx); + reset_control_deassert(lane->rst_tx); + + err = tegra_mphy_rx_write_kick_go(lane, MPHY_RX_APB_VENDOR3B, MPHY_EQ_TIMEOUT); + if (err) { + dev_err(&phy->dev, "eq_timeout programming failed: %d\n", err); + goto err_reset; + } + + err = tegra_mphy_rx_write_kick_go(lane, MPHY_RX_APB_VENDOR49, MPHY_PWR_CHANGE_CLK_BOOST); + if (err) { + dev_err(&phy->dev, "pwr_change_clk_boost programming failed: %d\n", err); + goto err_reset; + } + + value = readl(lane->regs + MPHY_RX_APB_CAPABILITY_88_8B); + value &= ~RX_HS_G1_SYNC_LENGTH_CAPABILITY(~0); + value |= RX_HS_G1_SYNC_LENGTH_CAPABILITY(0xf); + writel(value, lane->regs + MPHY_RX_APB_CAPABILITY_88_8B); + + value = readl(lane->regs + MPHY_RX_APB_CAPABILITY_94_97); + value &= ~RX_HS_G3_SYNC_LENGTH_CAPABILITY(~0); + value |= RX_HS_G3_SYNC_LENGTH_CAPABILITY(0xf); + value &= ~RX_HS_G2_SYNC_LENGTH_CAPABILITY(~0); + value |= RX_HS_G2_SYNC_LENGTH_CAPABILITY(0xf); + writel(value, lane->regs + MPHY_RX_APB_CAPABILITY_94_97); + + value = readl(lane->rx_regs + MPHY_RX_APB_VENDOR3); + value |= RX_MPHY2UPHY_IF_OVR_CTRL; + writel(value, lane->rx_regs + MPHY_RX_APB_VENDOR3); + + value = readl(lane->rx_regs + MPHY_RX_APB_VENDOR2); + value |= MPHY_GO_BIT; + writel(value, lane->rx_regs + MPHY_RX_APB_VENDOR2); + + err = readl_poll_timeout(lane->rx_regs + MPHY_RX_APB_VENDOR2, value, + (value & MPHY_GO_BIT) == 0, 25, 5000); + if (err) { + dev_err(&phy->dev, "RX cap update failed: %d\n", err); + goto err_reset; + } + + return 0; + +err_reset: + reset_control_assert(lane->rst_rx); + reset_control_assert(lane->rst_tx); + + if (--mphy->power_count == 0) { + reset_control_assert(mphy->rst_clk_ctl); + clk_bulk_disable_unprepare(mphy->num_clks, mphy->clks); + } + + return err; +} + +static int tegra_mphy_rx_power_off(struct phy *phy) +{ + struct tegra_mphy *mphy = dev_get_drvdata(phy->dev.parent); + struct tegra_mphy_lane *lane = phy_get_drvdata(phy); + + if (WARN_ON(mphy->power_count == 0)) + return -EINVAL; + + reset_control_assert(lane->rst_rx); + reset_control_assert(lane->rst_tx); + + if (--mphy->power_count == 0) + clk_bulk_disable_unprepare(mphy->num_clks, mphy->clks); + + return 0; +} + +static int tegra_mphy_rx_configure(struct phy *phy, union phy_configure_opts *opts) +{ + struct tegra_mphy_lane *lane = phy_get_drvdata(phy); + u32 value; + int err; + + value = readl(lane->rx_regs + MPHY_RX_APB_VENDOR2); + value |= RX_CAL_EN; + writel(value, lane->rx_regs + MPHY_RX_APB_VENDOR2); + + value = readl(lane->rx_regs + MPHY_RX_APB_VENDOR2); + value |= MPHY_GO_BIT; + writel(value, lane->rx_regs + MPHY_RX_APB_VENDOR2); + + err = readl_poll_timeout(lane->rx_regs + MPHY_RX_APB_VENDOR2, value, + (value & MPHY_GO_BIT) == 0, 25, 5000); + if (err) + dev_err(&phy->dev, "failed to arm RX calibration: %d\n", err); + + return err; +} + +static int tegra_mphy_rx_calibrate(struct phy *phy) +{ + struct tegra_mphy_lane *lane = phy_get_drvdata(phy); + u32 value; + int err; + + err = readl_poll_timeout(lane->rx_regs + MPHY_RX_APB_VENDOR2, value, + (value & RX_CAL_DONE) != 0, 25, 100000); + if (err) { + dev_err(&phy->dev, "RX calibration timed out: %d\n", err); + return err; + } + + value = readl(lane->rx_regs + MPHY_RX_APB_VENDOR2); + value &= ~RX_CAL_EN; + writel(value, lane->rx_regs + MPHY_RX_APB_VENDOR2); + + value = readl(lane->rx_regs + MPHY_RX_APB_VENDOR2); + value |= MPHY_GO_BIT; + writel(value, lane->rx_regs + MPHY_RX_APB_VENDOR2); + + err = readl_poll_timeout(lane->rx_regs + MPHY_RX_APB_VENDOR2, value, + (value & MPHY_GO_BIT) == 0, 25, 5000); + if (err) { + dev_err(&phy->dev, "failed to clear RX calibration: %d\n", err); + return err; + } + + err = readl_poll_timeout(lane->rx_regs + MPHY_RX_APB_VENDOR2, value, + (value & RX_CAL_DONE) == 0, 25, 100000); + if (err) + dev_err(&phy->dev, "RX calibration failed to clear: %d\n", err); + + return err; +} + +static const struct phy_ops tegra_mphy_rx_ops = { + .power_on = tegra_mphy_rx_power_on, + .power_off = tegra_mphy_rx_power_off, + .configure = tegra_mphy_rx_configure, + .calibrate = tegra_mphy_rx_calibrate, +}; + +static int tegra_mphy_tx_configure(struct phy *phy, union phy_configure_opts *opts) +{ + struct tegra_mphy_lane *lane = phy_get_drvdata(phy); + u32 value; + int err; + + value = readl(lane->tx_regs + MPHY_TX_APB_VENDOR2); + value |= TX_CAL_EN; + writel(value, lane->tx_regs + MPHY_TX_APB_VENDOR2); + + value = readl(lane->tx_regs + MPHY_TX_APB_VENDOR0); + value |= MPHY_GO_BIT; + writel(value, lane->tx_regs + MPHY_TX_APB_VENDOR0); + + err = readl_poll_timeout(lane->tx_regs + MPHY_TX_APB_VENDOR0, value, + (value & MPHY_GO_BIT) == 0, 25, 5000); + if (err) + dev_err(&phy->dev, "failed to arm TX calibration: %d\n", err); + + return err; +} + +static int tegra_mphy_tx_calibrate(struct phy *phy) +{ + struct tegra_mphy_lane *lane = phy_get_drvdata(phy); + u32 value; + int err; + + err = readl_poll_timeout(lane->tx_regs + MPHY_TX_APB_VENDOR2, value, + (value & TX_CAL_DONE) != 0, 25, 50000); + if (err) { + dev_err(&phy->dev, "TX calibration timed out: %d\n", err); + return err; + } + + value = readl(lane->tx_regs + MPHY_TX_APB_VENDOR2); + value &= ~TX_CAL_EN; + writel(value, lane->tx_regs + MPHY_TX_APB_VENDOR2); + + value = readl(lane->tx_regs + MPHY_TX_APB_VENDOR0); + value |= MPHY_GO_BIT; + writel(value, lane->tx_regs + MPHY_TX_APB_VENDOR0); + + err = readl_poll_timeout(lane->tx_regs + MPHY_TX_APB_VENDOR0, value, + (value & MPHY_GO_BIT) == 0, 25, 5000); + if (err) { + dev_err(&phy->dev, "GO bit failed to clear: %d\n", err); + return err; + } + + err = readl_poll_timeout(lane->tx_regs + MPHY_TX_APB_VENDOR2, value, + (value & TX_CAL_DONE) == 0, 25, 50000); + if (err) + dev_err(&phy->dev, "TX calibration failed: %d\n", err); + + return err; +} + +static const struct phy_ops tegra_mphy_tx_ops = { + .configure = tegra_mphy_tx_configure, + .calibrate = tegra_mphy_tx_calibrate, +}; + +static struct phy *tegra_mphy_xlate(struct device *dev, const struct of_phandle_args *args) +{ + struct tegra_mphy *mphy = dev_get_drvdata(dev); + + if (args->args_count != 1) + return ERR_PTR(-EINVAL); + + switch (args->args[0]) { + case TEGRA_MPHY_L0_TX: + return mphy->l0.tx; + case TEGRA_MPHY_L0_RX: + return mphy->l0.rx; + case TEGRA_MPHY_L1_TX: + return mphy->l1.tx; + case TEGRA_MPHY_L1_RX: + return mphy->l1.rx; + default: + return ERR_PTR(-EINVAL); + } +} + +static int tegra_mphy_create_phys(struct tegra_mphy *mphy) +{ + struct device *dev = mphy->dev; + + mphy->l0.tx = devm_phy_create(dev, dev->of_node, &tegra_mphy_tx_ops); + if (IS_ERR(mphy->l0.tx)) + return dev_err_probe(dev, PTR_ERR(mphy->l0.tx), "failed to create l0-tx PHY\n"); + phy_set_drvdata(mphy->l0.tx, &mphy->l0); + + mphy->l0.rx = devm_phy_create(dev, dev->of_node, &tegra_mphy_rx_ops); + if (IS_ERR(mphy->l0.rx)) + return dev_err_probe(dev, PTR_ERR(mphy->l0.rx), "failed to create l0-rx PHY\n"); + phy_set_drvdata(mphy->l0.rx, &mphy->l0); + + mphy->l1.tx = devm_phy_create(dev, dev->of_node, &tegra_mphy_tx_ops); + if (IS_ERR(mphy->l1.tx)) + return dev_err_probe(dev, PTR_ERR(mphy->l1.tx), "failed to create l1-tx PHY\n"); + phy_set_drvdata(mphy->l1.tx, &mphy->l1); + + mphy->l1.rx = devm_phy_create(dev, dev->of_node, &tegra_mphy_rx_ops); + if (IS_ERR(mphy->l1.rx)) + return dev_err_probe(dev, PTR_ERR(mphy->l1.rx), "failed to create l1-rx PHY\n"); + phy_set_drvdata(mphy->l1.rx, &mphy->l1); + + return 0; +} + +static int tegra_mphy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct phy_provider *provider; + struct tegra_mphy *mphy; + int err; + + mphy = devm_kzalloc(dev, sizeof(*mphy), GFP_KERNEL); + if (!mphy) + return -ENOMEM; + + mphy->dev = dev; + + mphy->l0.regs = devm_platform_ioremap_resource_byname(pdev, "l0"); + if (IS_ERR(mphy->l0.regs)) + return PTR_ERR(mphy->l0.regs); + + mphy->l1.regs = devm_platform_ioremap_resource_byname(pdev, "l1"); + if (IS_ERR(mphy->l1.regs)) + return PTR_ERR(mphy->l1.regs); + + mphy->l0.tx_regs = mphy->l0.regs + MPHY_TX_OFFSET; + mphy->l0.rx_regs = mphy->l0.regs + MPHY_RX_OFFSET; + + mphy->l1.tx_regs = mphy->l1.regs + MPHY_TX_OFFSET; + mphy->l1.rx_regs = mphy->l1.regs + MPHY_RX_OFFSET; + + err = tegra_mphy_get_clocks(mphy); + if (err) + return err; + + err = tegra_mphy_get_resets(mphy); + if (err) + return err; + + err = tegra_mphy_create_phys(mphy); + if (err) + return err; + + platform_set_drvdata(pdev, mphy); + + provider = devm_of_phy_provider_register(dev, tegra_mphy_xlate); + if (IS_ERR(provider)) + return dev_err_probe(dev, PTR_ERR(provider), + "failed to register PHY\n"); + + return 0; +} + +static const struct of_device_id tegra_mphy_of_table[] = { + { .compatible = "nvidia,tegra264-mphy" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, tegra_mphy_of_table); + +static struct platform_driver tegra_mphy_driver = { + .driver = { + .name = "tegra-mphy", + .of_match_table = tegra_mphy_of_table, + }, + .probe = tegra_mphy_probe, +}; +module_platform_driver(tegra_mphy_driver); + +MODULE_AUTHOR("Thierry Reding "); +MODULE_AUTHOR("Kartik Rajput "); +MODULE_DESCRIPTION("NVIDIA Tegra MPHY driver"); +MODULE_LICENSE("GPL"); From c5e6a552846b253294d0b37f447aeea20c1938a1 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Tue, 15 Sep 2026 12:10:21 +0200 Subject: [PATCH 105/114] dt-bindings: phy: Document MT8196 MediaTek PCI-Express Gen4 S-PHY This adds bindings for the PCI-Express Gen4 S-PHY found in newer MediaTek SoCs, such as MT8196 and its variants. In the current "revision 3", depending on the specific port, this S-PHY supports up to two lanes of PCI-Express Gen 4 and both EP and RC modes. It is not clear whether revisions/versions earlier than 3 have ever been shipped in any other MediaTek SoC. Acked-by: Manivannan Sadhasivam Reviewed-by: Krzysztof Kozlowski Signed-off-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260915101022.23852-2-angelogioacchino.delregno@collabora.com Signed-off-by: Vinod Koul --- .../phy/mediatek,mt8196-pcie-sphy.yaml | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 Documentation/devicetree/bindings/phy/mediatek,mt8196-pcie-sphy.yaml diff --git a/Documentation/devicetree/bindings/phy/mediatek,mt8196-pcie-sphy.yaml b/Documentation/devicetree/bindings/phy/mediatek,mt8196-pcie-sphy.yaml new file mode 100644 index 000000000000..3fd1bfbc3822 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/mediatek,mt8196-pcie-sphy.yaml @@ -0,0 +1,93 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/phy/mediatek,mt8196-pcie-sphy.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MediaTek PCI-Express Gen4 S-PHY + +maintainers: + - AngeloGioacchino Del Regno + +description: + The MediaTek PCI-Express Gen4 S-PHY is capable of data serialization + and de-serialization with 8, 10, 128 or 130 bits (16GT/s) encoding + and decoding, has analog buffers, elastic buffers and mechanisms for + receiver detection. + This PHY supports link rates of 2.5, 5.0, 8.0 and 16.0 GT/s per-lane + in both Root Complex (RC) and End Point (EP) modes, depending on the + chosen port. + The Digital PHY (PHYD) part adheres to the Intel PIPE (PHY Interface + for the PCIe) specification. + +properties: + compatible: + const: mediatek,mt8196-pcie-sphy + + reg: + items: + - description: System Interface (SIF) registers + - description: Clock Management (CKM) registers + + reg-names: + items: + - const: sif + - const: ckm + + "#phy-cells": + const: 0 + + nvmem-cells: + items: + - description: Internal resistor selection of TX Bias Current + - description: XTP Bias V2V voltage calibration + - description: S-PLL CKTX Impedance Selection (P-MOSFET side) + - description: S-PLL CKTX Impedance Selection (N-MOSFET side) + - description: S-PLL CKTX Intermediate Transition Impedance + - description: RX Front-End Return Loss Continuous Time Linear EQ + - description: RX Front-End Return Loss Variable Gain Amplifier + - description: RX Front-End Return Loss Decision Feedback EQ + - description: RX Impedance Selection + - description: TX Impedance Selection (P-MOSFET side) + - description: TX Impedance Selection (N-MOSFET side) + - description: TX Lane 0 Switch Resistance (RSWn) + - description: TX Lane 1 Switch Resistance (RSWn) + - description: eFuse calibration table version + + nvmem-cell-names: + items: + - const: int-r + - const: xtp-vtrim + - const: cktx-pmos + - const: cktx-nmos + - const: cktx-r-mid + - const: rxfe-lanes-rl-ctle + - const: rxfe-lanes-rl-vga + - const: rxfe-lanes-rl-dfe + - const: rx-lanes-imp + - const: tx-lanes-pmos + - const: tx-lanes-nmos + - const: tx-ln0-rswn + - const: tx-ln1-rswn + - const: cal-version + + power-domains: + maxItems: 1 + +required: + - compatible + - reg + - reg-names + - "#phy-cells" + +additionalProperties: false + +examples: + - | + phy@16900000 { + compatible = "mediatek,mt8196-pcie-sphy"; + reg = <0x16900000 0x10000>, <0x16920000 0x10000>; + reg-names = "sif", "ckm"; + power-domains = <&spm 10>; + #phy-cells = <0>; + }; From e9434ed4effaf46351fce729380d3c8d7d918f20 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Tue, 15 Sep 2026 12:10:22 +0200 Subject: [PATCH 106/114] phy: mediatek: Add support for PCI-Express Gen4 S-PHY Add support for the PCI-Express Gen4 S-PHY found in the latest MediaTek SoCs, including MT6991, MT8196, MT8894 and variants. Signed-off-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260915101022.23852-3-angelogioacchino.delregno@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/mediatek/Kconfig | 12 + drivers/phy/mediatek/Makefile | 1 + drivers/phy/mediatek/phy-mtk-pcie-sphy.c | 561 +++++++++++++++++++++++ 3 files changed, 574 insertions(+) create mode 100644 drivers/phy/mediatek/phy-mtk-pcie-sphy.c diff --git a/drivers/phy/mediatek/Kconfig b/drivers/phy/mediatek/Kconfig index ba6461350951..9beffd1fbeb3 100644 --- a/drivers/phy/mediatek/Kconfig +++ b/drivers/phy/mediatek/Kconfig @@ -13,6 +13,18 @@ config PHY_MTK_PCIE callback for PCIe GEN3 port, it supports software efuse initialization. +config PHY_MTK_PCIE_SPHY + tristate "MediaTek PCIe S-PHY Driver" + depends on ARCH_MEDIATEK || COMPILE_TEST + depends on OF + select GENERIC_PHY + help + Enable this to add support for MediaTek PCIe S-PHYs used with the + PCI-Express Gen4 controllers as found in MT6991, MT8196 and others. + + To compile this driver as module, say 'M' here: the module will be + called phy-mtk-pcie-sphy. + config PHY_MTK_XFI_TPHY tristate "MediaTek 10GE SerDes XFI T-PHY driver" depends on ARCH_MEDIATEK || COMPILE_TEST diff --git a/drivers/phy/mediatek/Makefile b/drivers/phy/mediatek/Makefile index ed0da708759b..7e984c7cfea3 100644 --- a/drivers/phy/mediatek/Makefile +++ b/drivers/phy/mediatek/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_PHY_MTK_DP) += phy-mtk-dp.o obj-$(CONFIG_PHY_MTK_PCIE) += phy-mtk-pcie.o +obj-$(CONFIG_PHY_MTK_PCIE_SPHY) += phy-mtk-pcie-sphy.o obj-$(CONFIG_PHY_MTK_TPHY) += phy-mtk-tphy.o obj-$(CONFIG_PHY_MTK_UFS) += phy-mtk-ufs.o obj-$(CONFIG_PHY_MTK_XSPHY) += phy-mtk-xsphy.o diff --git a/drivers/phy/mediatek/phy-mtk-pcie-sphy.c b/drivers/phy/mediatek/phy-mtk-pcie-sphy.c new file mode 100644 index 000000000000..2bf10346ea97 --- /dev/null +++ b/drivers/phy/mediatek/phy-mtk-pcie-sphy.c @@ -0,0 +1,561 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2022 MediaTek Inc. + * Copyright (c) 2026 Collabora Ltd. + * Author: AngeloGioacchino Del Regno + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "phy-mtk-io.h" + +/* PHY System Interface (SIF) registers */ +#define PEXTP_DIG_GLB_TOP 0x20 +# define RG_XTP_BYPASS_PIPE_RST_RC BIT(17) +#define PEXTP_DIG_GLB_CKBG0 0x30 +# define RG_XTP_CKBG_XTAL_STABLE_TIME_SEL GENMASK(25, 16) +#define PEXTP_DIG_GLB_TPLL_CTL0 0x38 +# define RG_XTP_TPLL_SET_STABLE_TIME_SEL GENMASK(7, 2) +# define RG_XTP_TPLL_PWE_ON_STABLE_TIME_SEL GENMASK(9, 8) +#define PEXTP_DIG_GLB_CLKREQ_CTL 0x50 +# define RG_XTP_CKM_EN_L1S0 BIT(13) +# define RG_XTP_CKM_EN_L1S1 BIT(14) +#define PEXTP_DIG_GLB_TPLL_CTL2 0xf4 +# define RG_XTP_TPLL_ISO_EN_STABLE_TIME_SEL GENMASK(13, 12) + +/* PHY System Interface Digital registers */ +#define PEXTP_DIG_LN_TRX_PIPE_IF_17 0x30e8 +# define RG_XTP_LN_RX_LF_CTLE_CSEL_GEN4 GENMASK(14, 12) +#define PEXTP_DIG_LN_RX_F0 0x50f0 +# define RG_XTP_LN_RX_GEN1_CTLE1_CSEL GENMASK(3, 0) +# define RG_XTP_LN_RX_GEN2_CTLE1_CSEL GENMASK(7, 4) +# define RG_XTP_LN_RX_GEN3_CTLE1_CSEL GENMASK(11, 8) +# define RG_XTP_LN_RX_GEN4_CTLE1_CSEL GENMASK(15, 12) +#define PEXTP_DIG_LN_RX2_AEQ_EDGE_0 0x6004 +# define RG_XTP_LN_RX_AEQ_EGEQ_RATIO_GEN3 GENMASK(21, 16) +# define RG_XTP_LN_RX_AEQ_EGEQ_RATIO_GEN4 GENMASK(29, 24) + +/* PHY System Interface Analog registers */ +#define PEXTP_ANA_GLB_TPLL1_RSVD 0x902c +# define RG_XTP_GLB_TPLL1_P_PATH_GAIN GENMASK(2, 0) +#define PEXTP_ANA_GLB_BIAS_0 0x9060 +# define RG_XTP_GLB_BIAS_INTR_CTRL GENMASK(5, 0) +#define PEXTP_ANA_GLB_BIAS_1 0x90c0 +# define RG_XTP_GLB_BIAS_V2V_VTRIM GENMASK(9, 6) +#define PEXTP_ANA_LN_TRX_0C 0xa00c +# define RG_XTP_LN_TX_RSWN_IMPSEL GENMASK(20, 16) +#define PEXTP_ANA_LN_TRX_34 0xa034 +# define RG_XTP_LN_RX_FE BIT(15) +#define PEXTP_ANA_LN_TRX_6C 0xa06c +# define RG_XTP_LN_RX_AEQ_CTLE_ERR_TYPE GENMASK(14, 13) +# define AEQ_CTLE_SEARCH_ERR_TYPE_H1P5 0 +# define AEQ_CTLE_SEARCH_ERR_TYPE_H1P5_H2P5 1 +# define AEQ_CTLE_SEARCH_ERR_TYPE_P1P5_H2P5_H3P5 2 +#define PEXTP_ANA_LN_TRX_A0 0xa0a0 +# define RG_XTP_LN_TX_IMPSEL_PMOS GENMASK(4, 0) +# define RG_XTP_LN_TX_IMPSEL_NMOS GENMASK(11, 7) +# define RG_XTP_LN_RX_IMPSEL GENMASK(15, 12) +#define PEXTP_ANA_LN_TRX_A8 0xa0a8 +# define RG_XTP_LN_RX_LEQ_RL_CTLE_CAL GENMASK(6, 2) +# define RG_XTP_LN_RX_LEQ_RL_VGA_CAL GENMASK(11, 7) +# define RG_XTP_LN_RX_LEQ_RL_DFE_CAL GENMASK(23, 19) +#define PEXTP_DIG_LN_TX_LC_TABLE_RSWN_4 0xb004 +#define PEXTP_DIG_LN_TX_LC_TABLE_RSWN_8 0xb008 +#define PEXTP_DIG_LN_TX_LC_TABLE_RSWN_C 0xb00c +#define PEXTP_DIG_LN_TX_LC_TABLE_RSWN_10 0xb010 +#define PEXTP_DIG_LN_TX_LC_TABLE_RSWN_14 0xb014 +#define PEXTP_DIG_LN_TX_LC_TABLE_RSWN_18 0xb018 +# define RG_XTP_LN_TX_LC_PRESET_MGx_Px_CM1 GENMASK(5, 0) +# define RG_XTP_LN_TX_LC_PRESET_MGx_Px_C0 GENMASK(13, 8) +# define RG_XTP_LN_TX_LC_PRESET_MGx_Px_CP1 GENMASK(21, 16) +#define PEXTP_REG_LANE(x) ((x) * 0x100) + +/* PHY Clock Management (CKM) registers */ +#define XTP_CKM_FORCE_6 0x38 +# define RG_CKM_BIAS_WAIT_PRD_US GENMASK(21, 16) +#define XTP_CKM_REG_SPLL_FBKDIV_5 0xd4 +# define RG_CKM_CKTX_IMPSEL_PMOS GENMASK(19, 16) +# define RG_CKM_CKTX_IMPSEL_NMOS GENMASK(23, 20) +# define RG_CKM_CKTX_IMPSEL_SW GENMASK(27, 24) + +/* Calibration data from eFuses */ +#define MTK_PCIE_SPHY_CALIBRATION_MAX_DATA_LANES 2 +#define MTK_PCIE_SPHY_CALIBRATION_LAST_QUIRK_VER 4 + +/** + * struct mtk_pcie_sphy_imp_sel - Impedance Selection parameters + * @pmos: Impedance selection for P-Channel MOSFET + * @nmos: Impedance selection for N-Channel MOSFET + */ +struct mtk_pcie_sphy_imp_sel { + u8 pmos; + u8 nmos; +}; + +/** + * struct mtk_pcie_sphy_efuse - eFuse calibration data for S-PHY + * @cktx_impsel: SPLL CKTX Impedance Selection (P and N MOSFET) + * @tx_impsel: TX Impedance Selection (P and N MOSFET) + * @int_r_ctrl: Internal resistor selection of TX Bias Current + * @xtp_vtrim: XTP Bias V2V voltage calibration + * @cktx_r_mid: SPLL CKTX Intermediate Transition Impedance (Rmid) + * @rx_leq_rl_ctle: RX Front-End Return Loss Continuous Time Linear Equalization value + * @rx_leq_rl_vga: RX Front-End Return Loss Variable Gain Amplifier value + * @rx_leq_rl_dfe: RX Front-End Return Loss Decision Feedback Equalization value + * @rx_impsel: RX Impedance Selection + * @tx_rswn_impsel: TX RSWn (Switch Resistance) impedance selection + * @supported: eFuse calibration data is supported + */ +struct mtk_pcie_sphy_efuse { + struct mtk_pcie_sphy_imp_sel cktx_impsel; + struct mtk_pcie_sphy_imp_sel tx_impsel; + u8 int_r_ctrl; + u8 xtp_vtrim; + u8 cktx_r_mid; + u8 rx_leq_rl_ctle; + u8 rx_leq_rl_vga; + u8 rx_leq_rl_dfe; + u8 rx_impsel; + u8 tx_rswn_impsel[MTK_PCIE_SPHY_CALIBRATION_MAX_DATA_LANES]; + bool supported; +}; + +/** + * struct mtk_pcie_sphy - PCI-Express S-PHY driver main structure + * @dev: Pointer to device structure + * @phy: Pointer to generic phy structure + * @sif_base: IO mapped register base address of system interface + * @ckm_base: IO mapped register base address of clock management interface + * @num_lanes: Number of lanes + * @calibration: eFuse calibration data for S-PHY + */ +struct mtk_pcie_sphy { + struct device *dev; + struct phy *phy; + void __iomem *sif_base; + void __iomem *ckm_base; + u8 num_lanes; + struct mtk_pcie_sphy_efuse calibration; +}; + +static void mtk_pcie_sphy_apply_calibration(struct mtk_pcie_sphy *pcie_sphy) +{ + struct mtk_pcie_sphy_efuse *cal = &pcie_sphy->calibration; + int i; + + mtk_phy_update_field(pcie_sphy->sif_base + PEXTP_ANA_GLB_BIAS_0, + RG_XTP_GLB_BIAS_INTR_CTRL, cal->int_r_ctrl); + + mtk_phy_update_field(pcie_sphy->sif_base + PEXTP_ANA_GLB_BIAS_1, + RG_XTP_GLB_BIAS_V2V_VTRIM, cal->xtp_vtrim); + + mtk_phy_update_field(pcie_sphy->ckm_base + XTP_CKM_REG_SPLL_FBKDIV_5, + RG_CKM_CKTX_IMPSEL_PMOS, cal->cktx_impsel.pmos); + + mtk_phy_update_field(pcie_sphy->ckm_base + XTP_CKM_REG_SPLL_FBKDIV_5, + RG_CKM_CKTX_IMPSEL_NMOS, cal->cktx_impsel.nmos); + + mtk_phy_update_field(pcie_sphy->ckm_base + XTP_CKM_REG_SPLL_FBKDIV_5, + RG_CKM_CKTX_IMPSEL_SW, cal->cktx_r_mid); + + for (i = 0; i < pcie_sphy->num_lanes; i++) { + void __iomem *sif_lane_base = pcie_sphy->sif_base + PEXTP_REG_LANE(i); + + mtk_phy_update_field(sif_lane_base + PEXTP_ANA_LN_TRX_0C, + RG_XTP_LN_TX_RSWN_IMPSEL, cal->tx_rswn_impsel[i]); + + mtk_phy_update_field(sif_lane_base + PEXTP_ANA_LN_TRX_A8, + RG_XTP_LN_RX_LEQ_RL_CTLE_CAL, cal->rx_leq_rl_ctle); + + mtk_phy_update_field(sif_lane_base + PEXTP_ANA_LN_TRX_A8, + RG_XTP_LN_RX_LEQ_RL_VGA_CAL, cal->rx_leq_rl_vga); + + mtk_phy_update_field(sif_lane_base + PEXTP_ANA_LN_TRX_A8, + RG_XTP_LN_RX_LEQ_RL_DFE_CAL, cal->rx_leq_rl_dfe); + + mtk_phy_update_field(sif_lane_base + PEXTP_ANA_LN_TRX_A0, + RG_XTP_LN_RX_IMPSEL, cal->rx_impsel); + } +} + +/** + * mtk_pcie_sphy_init() - Initialize the PCI-Express S-PHY + * @phy: the phy to be initialized + * + * The hardware settings will be reset during suspend, it should be + * reinitialized when the consumer calls phy_init() again on resume. + */ +static int mtk_pcie_sphy_init(struct phy *phy) +{ + struct mtk_pcie_sphy *pcie_sphy = phy_get_drvdata(phy); + struct mtk_pcie_sphy_imp_sel tx_impsel; + int i; + + /* Set CKM Bias wait time to 4 microseconds */ + mtk_phy_update_field(pcie_sphy->ckm_base + XTP_CKM_FORCE_6, + RG_CKM_BIAS_WAIT_PRD_US, 4); + + /* TPLL needs 63 ref_ck ticks to stabilize when setting frequency */ + mtk_phy_update_field(pcie_sphy->sif_base + PEXTP_DIG_GLB_TPLL_CTL0, + RG_XTP_TPLL_SET_STABLE_TIME_SEL, 63); + + /* TPLL needs 3 ref_ck ticks to stabilize when powering on... */ + mtk_phy_update_field(pcie_sphy->sif_base + PEXTP_DIG_GLB_TPLL_CTL0, + RG_XTP_TPLL_PWE_ON_STABLE_TIME_SEL, 3); + + /* ...and the same goes for setting isolation */ + mtk_phy_update_field(pcie_sphy->sif_base + PEXTP_DIG_GLB_TPLL_CTL2, + RG_XTP_TPLL_ISO_EN_STABLE_TIME_SEL, 3); + + /* XTAL doesn't need any stabilization time */ + mtk_phy_update_field(pcie_sphy->sif_base + PEXTP_DIG_GLB_CKBG0, + RG_XTP_CKBG_XTAL_STABLE_TIME_SEL, 0); + + /* Keep pextp_ckm enabled when in L1SS_L1S1 state */ + mtk_phy_clear_bits(pcie_sphy->sif_base + PEXTP_DIG_GLB_CLKREQ_CTL, RG_XTP_CKM_EN_L1S1); + + /* Set PIPE to reset TPLL */ + mtk_phy_clear_bits(pcie_sphy->sif_base + PEXTP_DIG_GLB_TOP, RG_XTP_BYPASS_PIPE_RST_RC); + + /* Set TPLL P-Path gain compensation to 1 */ + mtk_phy_update_field(pcie_sphy->sif_base + PEXTP_ANA_GLB_TPLL1_RSVD, + RG_XTP_GLB_TPLL1_P_PATH_GAIN, 1); + + for (i = 0; i < pcie_sphy->num_lanes; i++) { + void __iomem *sif_lane_base = pcie_sphy->sif_base + PEXTP_REG_LANE(i); + + /* Set RX Lane AEQ CTRL-E Search Error type to h1.5 + h2.5 */ + mtk_phy_update_field(sif_lane_base + PEXTP_ANA_LN_TRX_6C, + RG_XTP_LN_RX_AEQ_CTLE_ERR_TYPE, + AEQ_CTLE_SEARCH_ERR_TYPE_H1P5_H2P5); + + mtk_phy_set_bits(sif_lane_base + PEXTP_ANA_LN_TRX_34, RG_XTP_LN_RX_FE); + + /* TRX: Select CTLE1 for RX Lane AutoEQ CTRL-E Setting on Gen4 */ + mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_TRX_PIPE_IF_17, + RG_XTP_LN_RX_LF_CTLE_CSEL_GEN4, 1); + + /* Set RX Lane AutoEQ CTRL-E for PCI-Express Gen1 to Gen 4 */ + mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_RX_F0, + RG_XTP_LN_RX_GEN1_CTLE1_CSEL, 13); + + mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_RX_F0, + RG_XTP_LN_RX_GEN2_CTLE1_CSEL, 13); + + mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_RX_F0, + RG_XTP_LN_RX_GEN3_CTLE1_CSEL, 13); + + mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_RX_F0, + RG_XTP_LN_RX_GEN4_CTLE1_CSEL, 0); + + /* Set RX Lane AutoEQ's Edge EQ Ratio to 22 * 0.0625 = 1.375 */ + mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_RX2_AEQ_EDGE_0, + RG_XTP_LN_RX_AEQ_EGEQ_RATIO_GEN3, 22); + + mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_RX2_AEQ_EDGE_0, + RG_XTP_LN_RX_AEQ_EGEQ_RATIO_GEN4, 22); + + /* Setup Digital lane TX Link Characteristics Table */ + mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_TX_LC_TABLE_RSWN_4, + RG_XTP_LN_TX_LC_PRESET_MGx_Px_C0, 10); + + mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_TX_LC_TABLE_RSWN_4, + RG_XTP_LN_TX_LC_PRESET_MGx_Px_CP1, 2); + + mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_TX_LC_TABLE_RSWN_8, + RG_XTP_LN_TX_LC_PRESET_MGx_Px_C0, 11); + + mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_TX_LC_TABLE_RSWN_8, + RG_XTP_LN_TX_LC_PRESET_MGx_Px_CP1, 1); + + mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_TX_LC_TABLE_RSWN_C, + RG_XTP_LN_TX_LC_PRESET_MGx_Px_C0, 12); + + mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_TX_LC_TABLE_RSWN_10, + RG_XTP_LN_TX_LC_PRESET_MGx_Px_C0, 13); + + mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_TX_LC_TABLE_RSWN_10, + RG_XTP_LN_TX_LC_PRESET_MGx_Px_CM1, 1); + + mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_TX_LC_TABLE_RSWN_14, + RG_XTP_LN_TX_LC_PRESET_MGx_Px_C0, 11); + + mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_TX_LC_TABLE_RSWN_14, + RG_XTP_LN_TX_LC_PRESET_MGx_Px_CM1, 1); + + mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_TX_LC_TABLE_RSWN_18, + RG_XTP_LN_TX_LC_PRESET_MGx_Px_C0, 10); + + mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_TX_LC_TABLE_RSWN_18, + RG_XTP_LN_TX_LC_PRESET_MGx_Px_CM1, 2); + } + + if (pcie_sphy->calibration.supported) { + mtk_pcie_sphy_apply_calibration(pcie_sphy); + + tx_impsel.pmos = pcie_sphy->calibration.tx_impsel.pmos; + tx_impsel.nmos = pcie_sphy->calibration.tx_impsel.nmos; + } else { + /* Set P=10, N=9 to prevent EMI if no calibration present */ + tx_impsel.pmos = 10; + tx_impsel.nmos = 9; + } + + /* Select TX Impedance on N and P MOSFETs */ + for (i = 0; i < pcie_sphy->num_lanes; i++) { + void __iomem *sif_lane_base = pcie_sphy->sif_base + PEXTP_REG_LANE(i); + + mtk_phy_update_field(sif_lane_base + PEXTP_ANA_LN_TRX_A0, + RG_XTP_LN_TX_IMPSEL_PMOS, tx_impsel.pmos); + + mtk_phy_update_field(sif_lane_base + PEXTP_ANA_LN_TRX_A0, + RG_XTP_LN_TX_IMPSEL_NMOS, tx_impsel.nmos); + } + + return 0; +} + +static const struct phy_ops mtk_pcie_sphy_ops = { + .init = mtk_pcie_sphy_init, + .owner = THIS_MODULE, +}; + +static int mtk_pcie_sphy_get_one_cal_para(struct device *dev, const char *name, u8 max_val) +{ + u16 buf; + u8 tmp; + int ret; + + /* + * All of the calibrations are always max 8 bits long, but some may + * be split between two different 8-bits cells: handle this corner + * case by retrying reading as u16. + */ + ret = nvmem_cell_read_u8(dev, name, &tmp); + if (ret == 0) + buf = tmp; + else + ret = nvmem_cell_read_u16(dev, name, &buf); + + if (ret == -ENOENT) { + dev_info(dev, "No calibration for %s. Using defaults\n", name); + return -ENOENT; + } else if (ret) + return dev_err_probe(dev, ret, + "Cannot get calibration data for %s\n", name); + + if (buf > max_val) + return dev_err_probe(dev, -ERANGE, + "Bad value %u retrieved for %s.\n", buf, name); + + return buf; +} + +static int mtk_pcie_sphy_get_calibration_data(struct mtk_pcie_sphy *pcie_sphy) +{ + struct mtk_pcie_sphy_efuse *cal = &pcie_sphy->calibration; + struct device *dev = pcie_sphy->dev; + u8 version; + int ret; + + if (pcie_sphy->num_lanes > MTK_PCIE_SPHY_CALIBRATION_MAX_DATA_LANES) { + dev_info(dev, "Skipping PHY calibration for more than %u lanes.\n", + MTK_PCIE_SPHY_CALIBRATION_MAX_DATA_LANES); + ret = -EOPNOTSUPP; + goto end; + } + + ret = mtk_pcie_sphy_get_one_cal_para(dev, "int-r", + FIELD_MAX(RG_XTP_GLB_BIAS_INTR_CTRL)); + if (ret < 0) + goto end; + cal->int_r_ctrl = ret; + + ret = mtk_pcie_sphy_get_one_cal_para(dev, "xtp-vtrim", + FIELD_MAX(RG_XTP_GLB_BIAS_V2V_VTRIM)); + if (ret < 0) + goto end; + cal->xtp_vtrim = ret; + + ret = mtk_pcie_sphy_get_one_cal_para(dev, "cktx-pmos", + FIELD_MAX(RG_CKM_CKTX_IMPSEL_PMOS)); + if (ret < 0) + goto end; + cal->cktx_impsel.pmos = ret; + + ret = mtk_pcie_sphy_get_one_cal_para(dev, "cktx-nmos", + FIELD_MAX(RG_CKM_CKTX_IMPSEL_NMOS)); + if (ret < 0) + goto end; + cal->cktx_impsel.nmos = ret; + + ret = mtk_pcie_sphy_get_one_cal_para(dev, "cktx-r-mid", + FIELD_MAX(RG_CKM_CKTX_IMPSEL_SW)); + if (ret < 0) + goto end; + cal->cktx_r_mid = ret; + + ret = mtk_pcie_sphy_get_one_cal_para(dev, "rxfe-lanes-rl-ctle", + FIELD_MAX(RG_XTP_LN_RX_LEQ_RL_CTLE_CAL)); + if (ret < 0) + goto end; + cal->rx_leq_rl_ctle = ret; + + ret = mtk_pcie_sphy_get_one_cal_para(dev, "rxfe-lanes-rl-vga", + FIELD_MAX(RG_XTP_LN_RX_LEQ_RL_VGA_CAL)); + if (ret < 0) + goto end; + cal->rx_leq_rl_vga = ret; + + ret = mtk_pcie_sphy_get_one_cal_para(dev, "rxfe-lanes-rl-dfe", + FIELD_MAX(RG_XTP_LN_RX_LEQ_RL_DFE_CAL)); + if (ret < 0) + goto end; + cal->rx_leq_rl_dfe = ret; + + ret = mtk_pcie_sphy_get_one_cal_para(dev, "rx-lanes-imp", + FIELD_MAX(RG_XTP_LN_RX_IMPSEL)); + if (ret < 0) + goto end; + cal->rx_impsel = ret; + + ret = mtk_pcie_sphy_get_one_cal_para(dev, "tx-lanes-pmos", + FIELD_MAX(RG_XTP_LN_TX_IMPSEL_PMOS)); + if (ret < 0) + goto end; + cal->tx_impsel.pmos = ret; + + ret = mtk_pcie_sphy_get_one_cal_para(dev, "tx-lanes-nmos", + FIELD_MAX(RG_XTP_LN_TX_IMPSEL_NMOS)); + if (ret < 0) + goto end; + cal->tx_impsel.nmos = ret; + + ret = mtk_pcie_sphy_get_one_cal_para(dev, "tx-ln0-rswn", + FIELD_MAX(RG_XTP_LN_TX_RSWN_IMPSEL)); + if (ret < 0) + goto end; + cal->tx_rswn_impsel[0] = ret; + + if (pcie_sphy->num_lanes == 2) { + ret = mtk_pcie_sphy_get_one_cal_para(dev, "tx-ln1-rswn", + FIELD_MAX(RG_XTP_LN_TX_RSWN_IMPSEL)); + if (ret < 0) + goto end; + cal->tx_rswn_impsel[1] = ret; + } + + ret = mtk_pcie_sphy_get_one_cal_para(dev, "cal-version", 15); + if (ret < 0) + goto end; + version = ret; + + /* Quirk for eFuse calibration table versions 0 to 4 */ + if ((version <= MTK_PCIE_SPHY_CALIBRATION_LAST_QUIRK_VER) && + cal->rx_leq_rl_ctle == 10) { + cal->rx_leq_rl_vga = cal->rx_leq_rl_ctle; + cal->rx_leq_rl_dfe = cal->rx_leq_rl_ctle; + } + +end: + if (ret < 0) { + /* + * If any of the calibration values is missing, or if there is + * no calibration at all in the eFuses, this is not a problem, + * as the PHY doesn't require one to actually work. + * + * Note that if even just one of the calibration values is + * missing it would not be safe to trust the correctness of + * the others as this may be a different eFuse layout. + */ + if (ret == -ENOENT || ret == -EOPNOTSUPP) { + cal->supported = false; + return 0; + } + return ret; + }; + cal->supported = true; + + return 0; +} + +static int mtk_pcie_sphy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct phy_provider *provider; + struct mtk_pcie_sphy *pcie_sphy; + u32 num_lanes; + int ret; + + pcie_sphy = devm_kzalloc(dev, sizeof(*pcie_sphy), GFP_KERNEL); + if (!pcie_sphy) + return -ENOMEM; + + pcie_sphy->sif_base = devm_platform_ioremap_resource_byname(pdev, "sif"); + if (IS_ERR(pcie_sphy->sif_base)) + return dev_err_probe(dev, PTR_ERR(pcie_sphy->sif_base), + "Failed to map phy-sif base\n"); + + pcie_sphy->ckm_base = devm_platform_ioremap_resource_byname(pdev, "ckm"); + if (IS_ERR(pcie_sphy->ckm_base)) + return dev_err_probe(dev, PTR_ERR(pcie_sphy->ckm_base), + "Failed to map phy-ckm base\n"); + + pcie_sphy->phy = devm_phy_create(dev, dev->of_node, &mtk_pcie_sphy_ops); + if (IS_ERR(pcie_sphy->phy)) + return dev_err_probe(dev, PTR_ERR(pcie_sphy->phy), + "Failed to create PCIe phy\n"); + + ret = of_property_read_u32(dev->of_node, "num-lanes", &num_lanes); + if (ret) + num_lanes = 1; + else if (num_lanes > 4) + return dev_err_probe(dev, -EINVAL, "Invalid number of lanes.\n"); + + pcie_sphy->num_lanes = num_lanes; + pcie_sphy->dev = dev; + + ret = mtk_pcie_sphy_get_calibration_data(pcie_sphy); + if (ret) + return ret; + + phy_set_drvdata(pcie_sphy->phy, pcie_sphy); + + pm_runtime_no_callbacks(dev); + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(provider)) + return dev_err_probe(dev, PTR_ERR(provider), + "Could not register PCI-Express S-PHY\n"); + + return 0; +} + +static const struct of_device_id mtk_pcie_sphy_of_match[] = { + { .compatible = "mediatek,mt8196-pcie-sphy" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mtk_pcie_sphy_of_match); + +static struct platform_driver mtk_pcie_sphy_driver = { + .probe = mtk_pcie_sphy_probe, + .driver = { + .name = "mtk-pcie-sphy", + .of_match_table = mtk_pcie_sphy_of_match, + }, +}; +module_platform_driver(mtk_pcie_sphy_driver); + +MODULE_DESCRIPTION("MediaTek PCIe SPHY driver"); +MODULE_AUTHOR("AngeloGioacchino Del Regno "); +MODULE_LICENSE("GPL"); From 40c95f05093129e75f0180938de53dac65ce7e75 Mon Sep 17 00:00:00 2001 From: Amin Gattout Date: Tue, 15 Sep 2026 12:42:47 +0200 Subject: [PATCH 107/114] dt-bindings: phy: rockchip-inno-csi-dphy: add rk3576 variant The Rockchip RK3576 features two MIPI CSI-2 DPHYs compatible with this binding. Unlike the RK3588 variant, each PHY instance only has an APB reset line, so add the new compatible to the single-reset group as well. No fallback compatible is used because the RK3576 DPHYs match neither existing variant: their control bits sit in a dedicated CSI-DPHY GRF as on RK3588 (rather than in the shared VI GRF used on RK3568), while only a single APB reset is wired up, as on RK3568. Falling back to "rockchip,rk3588-csi-dphy" would make the driver request a second, non-existent reset, and falling back to "rockchip,rk3568-csi-dphy" would make it write to the wrong GRF offsets, so dedicated match data is required. Acked-by: Conor Dooley Signed-off-by: Amin Gattout Reviewed-by: Bryan O'Donoghue Reviewed-by: Michael Riesch Link: https://patch.msgid.link/20260915-rk3576-dphy-v2-2-b56e68b54b8c@gmail.com Signed-off-by: Vinod Koul --- .../devicetree/bindings/phy/rockchip-inno-csi-dphy.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/phy/rockchip-inno-csi-dphy.yaml b/Documentation/devicetree/bindings/phy/rockchip-inno-csi-dphy.yaml index 913aa688c0ae..bb063b7aabda 100644 --- a/Documentation/devicetree/bindings/phy/rockchip-inno-csi-dphy.yaml +++ b/Documentation/devicetree/bindings/phy/rockchip-inno-csi-dphy.yaml @@ -21,6 +21,7 @@ properties: - rockchip,rk3326-csi-dphy - rockchip,rk3368-csi-dphy - rockchip,rk3568-csi-dphy + - rockchip,rk3576-csi-dphy - rockchip,rk3588-csi-dphy reg: @@ -99,6 +100,7 @@ allOf: - rockchip,rk3326-csi-dphy - rockchip,rk3368-csi-dphy - rockchip,rk3568-csi-dphy + - rockchip,rk3576-csi-dphy then: properties: resets: From 47de65075563915363ed25affff41b9b4d004d4f Mon Sep 17 00:00:00 2001 From: Amin Gattout Date: Tue, 15 Sep 2026 12:42:48 +0200 Subject: [PATCH 108/114] phy: rockchip: phy-rockchip-inno-csidphy: add support for rk3576 variant The two RK3576 MIPI CSI-2 DPHYs can be supported using the existing phy-rockchip-inno-csidphy driver, but they need their own driver data: as on RK3588 the GRF control bits live in a dedicated CSI-DPHY GRF at offset 0x0, while each PHY instance only has an APB reset line, as on RK3568. Signed-off-by: Amin Gattout Reviewed-by: Bryan O'Donoghue Reviewed-by: Michael Riesch Link: https://patch.msgid.link/20260915-rk3576-dphy-v2-3-b56e68b54b8c@gmail.com Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-inno-csidphy.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/phy/rockchip/phy-rockchip-inno-csidphy.c b/drivers/phy/rockchip/phy-rockchip-inno-csidphy.c index d989f1649b0e..b4f64ebfbcf7 100644 --- a/drivers/phy/rockchip/phy-rockchip-inno-csidphy.c +++ b/drivers/phy/rockchip/phy-rockchip-inno-csidphy.c @@ -415,6 +415,17 @@ static const struct dphy_drv_data rk3568_mipidphy_drv_data = { .resets_num = ARRAY_SIZE(rk3368_reset_names), }; +static const struct dphy_drv_data rk3576_mipidphy_drv_data = { + .pwrctl_offset = -1, + .ths_settle_offset = RK3568_CSIDPHY_CLK_WR_THS_SETTLE, + .calib_offset = RK3568_CSIDPHY_CLK_CALIB_EN, + .hsfreq_ranges = rk1808_mipidphy_hsfreq_ranges, + .num_hsfreq_ranges = ARRAY_SIZE(rk1808_mipidphy_hsfreq_ranges), + .grf_regs = rk3588_grf_dphy_regs, + .resets = rk3368_reset_names, + .resets_num = ARRAY_SIZE(rk3368_reset_names), +}; + static const struct dphy_drv_data rk3588_mipidphy_drv_data = { .pwrctl_offset = -1, .ths_settle_offset = RK3568_CSIDPHY_CLK_WR_THS_SETTLE, @@ -447,6 +458,10 @@ static const struct of_device_id rockchip_inno_csidphy_match_id[] = { .compatible = "rockchip,rk3568-csi-dphy", .data = &rk3568_mipidphy_drv_data, }, + { + .compatible = "rockchip,rk3576-csi-dphy", + .data = &rk3576_mipidphy_drv_data, + }, { .compatible = "rockchip,rk3588-csi-dphy", .data = &rk3588_mipidphy_drv_data, From e22b112f2fa565fd7d2715ac9d1bc3ba1a3c2427 Mon Sep 17 00:00:00 2001 From: Ryan Chen Date: Wed, 16 Sep 2026 08:38:01 +0800 Subject: [PATCH 109/114] dt-bindings: phy: aspeed: Document AST2700 USB3.2 PHY Document AST2700 USB3.2 PHY. This IP is connected between USB3 controller and PHY module. Reviewed-by: Krzysztof Kozlowski Acked-by: Manivannan Sadhasivam Signed-off-by: Ryan Chen Link: https://patch.msgid.link/20260916-upstream_usb3phy-v4-1-812e76037d01@aspeedtech.com Signed-off-by: Vinod Koul --- .../bindings/phy/aspeed,ast2700-usb3-phy.yaml | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 Documentation/devicetree/bindings/phy/aspeed,ast2700-usb3-phy.yaml diff --git a/Documentation/devicetree/bindings/phy/aspeed,ast2700-usb3-phy.yaml b/Documentation/devicetree/bindings/phy/aspeed,ast2700-usb3-phy.yaml new file mode 100644 index 000000000000..b83037aa0438 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/aspeed,ast2700-usb3-phy.yaml @@ -0,0 +1,48 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/phy/aspeed,ast2700-usb3-phy.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ASPEED AST2700 USB 3.2 PHY + +maintainers: + - Ryan Chen + +properties: + compatible: + const: aspeed,ast2700-usb3-phy + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + resets: + maxItems: 1 + + '#phy-cells': + const: 0 + +required: + - compatible + - reg + - clocks + - resets + - '#phy-cells' + +additionalProperties: false + +examples: + - | + #include + #include + + usb-phy@12010000 { + compatible = "aspeed,ast2700-usb3-phy"; + reg = <0x12010000 0xc0>; + clocks = <&syscon0 SCU0_CLK_GATE_PORTAUSB2CLK>; + resets = <&syscon0 SCU0_RESET_PORTA_PHY3>; + #phy-cells = <0>; + }; From ad72aff5b4b5396a2a443a8f838fcfb8067dc603 Mon Sep 17 00:00:00 2001 From: Ryan Chen Date: Wed, 16 Sep 2026 08:38:02 +0800 Subject: [PATCH 110/114] phy: aspeed: Add AST2700 USB3.2 PHY driver Add a driver for the USB3.2 PHY on the Aspeed AST2700, which is paired with the DWC3 controller. The PHY draws its configuration from an internal SRAM in both of the available modes, so the driver must wait for PHY3S00_INIT_DONE before selecting one. PHY3S00_SRAM_BYPASS selects the default design configuration and PHY3S00_SRAM_EXT_LOAD the ROM code configuration; the driver uses the latter. Signed-off-by: Ryan Chen Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260916-upstream_usb3phy-v4-2-812e76037d01@aspeedtech.com Signed-off-by: Vinod Koul --- drivers/phy/Kconfig | 1 + drivers/phy/Makefile | 1 + drivers/phy/aspeed/Kconfig | 15 ++ drivers/phy/aspeed/Makefile | 2 + drivers/phy/aspeed/phy-aspeed-usb3.c | 236 +++++++++++++++++++++++++++ 5 files changed, 255 insertions(+) create mode 100644 drivers/phy/aspeed/Kconfig create mode 100644 drivers/phy/aspeed/Makefile create mode 100644 drivers/phy/aspeed/phy-aspeed-usb3.c diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index d2d401129af7..105fbe408aa2 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -171,6 +171,7 @@ config PHY_XGENE source "drivers/phy/allwinner/Kconfig" source "drivers/phy/amlogic/Kconfig" source "drivers/phy/apple/Kconfig" +source "drivers/phy/aspeed/Kconfig" source "drivers/phy/axiado/Kconfig" source "drivers/phy/broadcom/Kconfig" source "drivers/phy/cadence/Kconfig" diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index d7aa516bcc49..c6dd02003bbe 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -23,6 +23,7 @@ obj-$(CONFIG_PHY_XGENE) += phy-xgene.o obj-$(CONFIG_GENERIC_PHY) += allwinner/ \ amlogic/ \ apple/ \ + aspeed/ \ axiado/ \ broadcom/ \ cadence/ \ diff --git a/drivers/phy/aspeed/Kconfig b/drivers/phy/aspeed/Kconfig new file mode 100644 index 000000000000..7b5f48db2be8 --- /dev/null +++ b/drivers/phy/aspeed/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only + +# +# PHY drivers for ASPEED +# + +config PHY_ASPEED_USB3 + tristate "ASPEED USB3 PHY driver" + select GENERIC_PHY + depends on (ARCH_ASPEED || COMPILE_TEST) + help + Enable this to support the USB 3.2 PHY on the Aspeed AST2700 SoC. + It supports SuperSpeedPlus Gen2x1 (10 Gbps), SuperSpeed (5 Gbps), + High Speed (480 Mbps), Full Speed (12 Mbps) and Low Speed + (1.5 Mbps), and is paired with the DWC3 USB controller. diff --git a/drivers/phy/aspeed/Makefile b/drivers/phy/aspeed/Makefile new file mode 100644 index 000000000000..d96d9d73a009 --- /dev/null +++ b/drivers/phy/aspeed/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_PHY_ASPEED_USB3) += phy-aspeed-usb3.o diff --git a/drivers/phy/aspeed/phy-aspeed-usb3.c b/drivers/phy/aspeed/phy-aspeed-usb3.c new file mode 100644 index 000000000000..6f371cae02f3 --- /dev/null +++ b/drivers/phy/aspeed/phy-aspeed-usb3.c @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2026 Aspeed Technology Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PHY3S00 0x00 +#define PHY3S00_INIT_DONE BIT(15) +#define PHY3S00_SRAM_BYPASS BIT(7) +#define PHY3S00_SRAM_EXT_LOAD BIT(6) +#define PHY3S04 0x04 +#define PHY3C00 0x08 +#define PHY3C04 0x0c +#define PHY3P00 0x10 +#define PHY3P00_RX_ADAPT_AFE_EN_G1 BIT(0) +#define PHY3P00_RX_ADAPT_AFE_EN_G2 BIT(1) +#define PHY3P00_RX_ADAPT_DFE_EN_G1 BIT(2) +#define PHY3P00_RX_ADAPT_DFE_EN_G2 BIT(3) +#define PHY3P00_RX_CDR_VCO_LOWFREQ_G1 BIT(4) +#define PHY3P00_RX_CDR_VCO_LOWFREQ_G2 BIT(5) +#define PHY3P00_RX_EQ_AFE_GAIN_G1 GENMASK(9, 6) +#define PHY3P00_RX_EQ_AFE_GAIN_G2 GENMASK(13, 10) +#define PHY3P00_RX_EQ_ATT_LVL_G1 GENMASK(16, 14) +#define PHY3P00_RX_EQ_ATT_LVL_G2 GENMASK(19, 17) +#define PHY3P00_RX_EQ_CTLE_BOOST_G1 GENMASK(24, 20) +#define PHY3P00_RX_EQ_CTLE_BOOST_G2 GENMASK(29, 25) +#define PHY3P00_RX_EQ_DELTA_IQ_G1_LO GENMASK(31, 30) + +#define PHY3P04 0x14 +#define PHY3P04_RX_EQ_DELTA_IQ_G1_HI GENMASK(1, 0) +#define PHY3P04_RX_EQ_DELTA_IQ_G2 GENMASK(5, 2) +#define PHY3P04_RX_EQ_DFE_TAP1_G1 GENMASK(13, 6) +#define PHY3P04_RX_EQ_DFE_TAP1_G2 GENMASK(21, 14) +#define PHY3P04_RX_LOS_LFPS_EN BIT(22) +#define PHY3P04_RX_LOS_THRESHOLD GENMASK(25, 23) +#define PHY3P04_RX_TERM_CTRL GENMASK(28, 26) +#define PHY3P04_TX_EQ_MAIN_G1_LO GENMASK(31, 29) + +#define PHY3P08 0x18 +#define PHY3P08_TX_EQ_MAIN_G1_HI GENMASK(1, 0) +#define PHY3P08_TX_EQ_MAIN_G2 GENMASK(6, 2) +#define PHY3P08_TX_EQ_OVRD BIT(7) +#define PHY3P08_TX_EQ_POST_G1 GENMASK(12, 9) +#define PHY3P08_TX_EQ_POST_G2 GENMASK(16, 13) +#define PHY3P08_TX_EQ_PRE_G1 GENMASK(20, 17) +#define PHY3P08_TX_EQ_PRE_G2 GENMASK(24, 21) +#define PHY3P08_TX_IBOOST_LVL GENMASK(28, 25) +#define PHY3P08_TX_TERM_CTRL GENMASK(31, 29) + +#define PHY3P0C 0x1c +#define PHY3P0C_TX_VBOOST_EN BIT(0) + +#define PHY3CMD 0x40 + +#define PHY3P_RX_EQ_CTLE_BOOST_G1_DEFAULT 0x7 +#define PHY3P_RX_EQ_CTLE_BOOST_G2_DEFAULT 0x7 +#define PHY3P_RX_EQ_DELTA_IQ_G1_DEFAULT 0x3 +#define PHY3P_RX_EQ_DELTA_IQ_G2_DEFAULT 0x5 +#define PHY3P_RX_LOS_THRESHOLD_DEFAULT 0x3 +#define PHY3P_RX_TERM_CTRL_DEFAULT 0x2 +#define PHY3P_TX_EQ_MAIN_G1_DEFAULT 0xa +#define PHY3P_TX_EQ_MAIN_G2_DEFAULT 0x9 +#define PHY3P_TX_EQ_POST_G1_DEFAULT 0x4 +#define PHY3P_TX_EQ_POST_G2_DEFAULT 0x3 +#define PHY3P_TX_EQ_PRE_G2_DEFAULT 0x2 +#define PHY3P_TX_IBOOST_LVL_DEFAULT 0xf +#define PHY3P_TX_TERM_CTRL_DEFAULT 0x2 + +#define PHY3P00_DEFAULT ( \ + PHY3P00_RX_ADAPT_AFE_EN_G1 | \ + PHY3P00_RX_ADAPT_AFE_EN_G2 | \ + PHY3P00_RX_ADAPT_DFE_EN_G1 | \ + PHY3P00_RX_ADAPT_DFE_EN_G2 | \ + FIELD_PREP(PHY3P00_RX_EQ_CTLE_BOOST_G1, PHY3P_RX_EQ_CTLE_BOOST_G1_DEFAULT) | \ + FIELD_PREP(PHY3P00_RX_EQ_CTLE_BOOST_G2, PHY3P_RX_EQ_CTLE_BOOST_G2_DEFAULT) | \ + FIELD_PREP(PHY3P00_RX_EQ_DELTA_IQ_G1_LO, \ + PHY3P_RX_EQ_DELTA_IQ_G1_DEFAULT & 0x3) \ +) + +#define PHY3P04_DEFAULT ( \ + FIELD_PREP(PHY3P04_RX_EQ_DELTA_IQ_G1_HI, \ + PHY3P_RX_EQ_DELTA_IQ_G1_DEFAULT >> 2) | \ + FIELD_PREP(PHY3P04_RX_EQ_DELTA_IQ_G2, PHY3P_RX_EQ_DELTA_IQ_G2_DEFAULT) | \ + PHY3P04_RX_LOS_LFPS_EN | \ + FIELD_PREP(PHY3P04_RX_LOS_THRESHOLD, PHY3P_RX_LOS_THRESHOLD_DEFAULT) | \ + FIELD_PREP(PHY3P04_RX_TERM_CTRL, PHY3P_RX_TERM_CTRL_DEFAULT) | \ + FIELD_PREP(PHY3P04_TX_EQ_MAIN_G1_LO, \ + PHY3P_TX_EQ_MAIN_G1_DEFAULT & 0x7) \ +) + +#define PHY3P08_DEFAULT ( \ + FIELD_PREP(PHY3P08_TX_EQ_MAIN_G1_HI, PHY3P_TX_EQ_MAIN_G1_DEFAULT >> 3) | \ + FIELD_PREP(PHY3P08_TX_EQ_MAIN_G2, PHY3P_TX_EQ_MAIN_G2_DEFAULT) | \ + FIELD_PREP(PHY3P08_TX_EQ_POST_G1, PHY3P_TX_EQ_POST_G1_DEFAULT) | \ + FIELD_PREP(PHY3P08_TX_EQ_POST_G2, PHY3P_TX_EQ_POST_G2_DEFAULT) | \ + FIELD_PREP(PHY3P08_TX_EQ_PRE_G2, PHY3P_TX_EQ_PRE_G2_DEFAULT) | \ + FIELD_PREP(PHY3P08_TX_IBOOST_LVL, PHY3P_TX_IBOOST_LVL_DEFAULT) | \ + FIELD_PREP(PHY3P08_TX_TERM_CTRL, PHY3P_TX_TERM_CTRL_DEFAULT) \ +) + +#define PHY3P0C_DEFAULT \ + PHY3P0C_TX_VBOOST_EN + +struct aspeed_usb3_phy { + void __iomem *regs; + struct reset_control *rst; + struct device *dev; + struct clk *clk; +}; + +static int aspeed_usb3_phy_init(struct phy *phy) +{ + struct aspeed_usb3_phy *aspeed_phy = phy_get_drvdata(phy); + u32 val; + int ret; + + ret = clk_prepare_enable(aspeed_phy->clk); + if (ret) { + dev_err(aspeed_phy->dev, "Failed to enable clock %d\n", ret); + return ret; + } + + ret = reset_control_deassert(aspeed_phy->rst); + if (ret) + goto err_disable_clk; + + /* Wait for USB3 PHY internal SRAM initialization done */ + ret = readl_poll_timeout(aspeed_phy->regs + PHY3S00, val, + val & PHY3S00_INIT_DONE, + USEC_PER_MSEC, 10 * USEC_PER_MSEC); + if (ret) { + dev_err(aspeed_phy->dev, "SRAM init timeout\n"); + goto err_assert_reset; + } + + val = readl(aspeed_phy->regs + PHY3S00); + val |= PHY3S00_SRAM_EXT_LOAD; + writel(val, aspeed_phy->regs + PHY3S00); + + /* Set protocol1_ext signals as default PHY3 settings based on SNPS documents. + * Including PCFGI[54]: protocol1_ext_rx_los_lfps_en for better compatibility + */ + writel(PHY3P00_DEFAULT, aspeed_phy->regs + PHY3P00); + writel(PHY3P04_DEFAULT, aspeed_phy->regs + PHY3P04); + writel(PHY3P08_DEFAULT, aspeed_phy->regs + PHY3P08); + writel(PHY3P0C_DEFAULT, aspeed_phy->regs + PHY3P0C); + + return 0; + +err_assert_reset: + reset_control_assert(aspeed_phy->rst); +err_disable_clk: + clk_disable_unprepare(aspeed_phy->clk); + return ret; +} + +static int aspeed_usb3_phy_exit(struct phy *phy) +{ + struct aspeed_usb3_phy *aspeed_phy = phy_get_drvdata(phy); + + reset_control_assert(aspeed_phy->rst); + clk_disable_unprepare(aspeed_phy->clk); + + return 0; +} + +static const struct phy_ops aspeed_usb3_phy_ops = { + .init = aspeed_usb3_phy_init, + .exit = aspeed_usb3_phy_exit, + .owner = THIS_MODULE, +}; + +static int aspeed_usb3_phy_probe(struct platform_device *pdev) +{ + struct aspeed_usb3_phy *aspeed_phy; + struct phy_provider *phy_provider; + struct device *dev = &pdev->dev; + struct phy *phy; + + aspeed_phy = devm_kzalloc(dev, sizeof(*aspeed_phy), GFP_KERNEL); + if (!aspeed_phy) + return -ENOMEM; + + aspeed_phy->dev = dev; + + aspeed_phy->clk = devm_clk_get(dev, NULL); + if (IS_ERR(aspeed_phy->clk)) + return PTR_ERR(aspeed_phy->clk); + + aspeed_phy->rst = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(aspeed_phy->rst)) + return PTR_ERR(aspeed_phy->rst); + + aspeed_phy->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(aspeed_phy->regs)) + return PTR_ERR(aspeed_phy->regs); + + phy = devm_phy_create(dev, NULL, &aspeed_usb3_phy_ops); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + phy_set_drvdata(phy, aspeed_phy); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id aspeed_usb3_phy_match_table[] = { + { + .compatible = "aspeed,ast2700-usb3-phy", + }, + { } +}; +MODULE_DEVICE_TABLE(of, aspeed_usb3_phy_match_table); + +static struct platform_driver aspeed_usb3_phy_driver = { + .probe = aspeed_usb3_phy_probe, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = aspeed_usb3_phy_match_table, + }, +}; +module_platform_driver(aspeed_usb3_phy_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ASPEED USB3.2 PHY Driver"); +MODULE_AUTHOR("Ryan Chen "); From ea01f446f0682a5e7bd7a275889a890b7e0c6e02 Mon Sep 17 00:00:00 2001 From: Ryan Chen Date: Wed, 16 Sep 2026 08:38:03 +0800 Subject: [PATCH 111/114] MAINTAINERS: Add ASPEED USB3 PHY driver Add maintainer entry for ASPEED USB3 PHY driver. Reviewed-by: Manivannan Sadhasivam Signed-off-by: Ryan Chen Link: https://patch.msgid.link/20260916-upstream_usb3phy-v4-3-812e76037d01@aspeedtech.com Signed-off-by: Vinod Koul --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 16c1c1fbb908..0570815ee980 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4095,6 +4095,14 @@ S: Maintained F: Documentation/devicetree/bindings/usb/aspeed,ast2600-udc.yaml F: drivers/usb/gadget/udc/aspeed_udc.c +ASPEED USB3 PHY DRIVER +M: Ryan Chen +L: linux-aspeed@lists.ozlabs.org (moderated for non-subscribers) +L: linux-phy@lists.infradead.org +S: Maintained +F: Documentation/devicetree/bindings/phy/aspeed,ast2700-usb3-phy.yaml +F: drivers/phy/aspeed/phy-aspeed-usb3.c + ASPEED VIDEO ENGINE DRIVER M: Eddie James L: linux-media@vger.kernel.org From d940f4f05123ecdc3d1226df76ce652c1b4d845c Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Wed, 7 Oct 2026 15:44:41 +0100 Subject: [PATCH 112/114] dt-bindings: phy: tegra194: Add compatible for Tegra238 The Tegra238 XUSB pad controller is similar to that found on the Tegra194 and Tegra234, but only has three USB2 ports and three USB3 ports. Add the Tegra238 compatible string for the XUSB pad controller and constrain the binding so that a Tegra238 device tree can only have three USB2 and three USB3 ports. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Jon Hunter Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20261007-tegra-phy-v3-1-d501de414710@nvidia.com Signed-off-by: Vinod Koul --- .../phy/nvidia,tegra194-xusb-padctl.yaml | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/Documentation/devicetree/bindings/phy/nvidia,tegra194-xusb-padctl.yaml b/Documentation/devicetree/bindings/phy/nvidia,tegra194-xusb-padctl.yaml index f6fe3c90b5b0..fbafe8eb150b 100644 --- a/Documentation/devicetree/bindings/phy/nvidia,tegra194-xusb-padctl.yaml +++ b/Documentation/devicetree/bindings/phy/nvidia,tegra194-xusb-padctl.yaml @@ -45,6 +45,7 @@ properties: enum: - nvidia,tegra194-xusb-padctl - nvidia,tegra234-xusb-padctl + - nvidia,tegra238-xusb-padctl - nvidia,tegra264-xusb-padctl reg: @@ -542,6 +543,43 @@ properties: description: connection to a USB Type C controller $ref: /schemas/graph.yaml#/properties/port +allOf: + - if: + properties: + compatible: + contains: + const: nvidia,tegra238-xusb-padctl + then: + properties: + pads: + properties: + usb2: + properties: + lanes: + properties: + usb2-3: false + usb3: + properties: + lanes: + properties: + usb3-3: false + ports: + properties: + usb2-3: false + usb3-0: + properties: + nvidia,usb2-companion: + enum: [ 0, 1, 2 ] + usb3-1: + properties: + nvidia,usb2-companion: + enum: [ 0, 1, 2 ] + usb3-2: + properties: + nvidia,usb2-companion: + enum: [ 0, 1, 2 ] + usb3-3: false + additionalProperties: false required: From c0c4091bbaa3c6dc05ecf8a639cec25eca933bfe Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Wed, 7 Oct 2026 15:44:42 +0100 Subject: [PATCH 113/114] phy: tegra: xusb: Use dev_err_probe() Use the function dev_err_probe() instead of open coding the same logic. Signed-off-by: Jon Hunter Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20261007-tegra-phy-v3-2-d501de414710@nvidia.com Signed-off-by: Vinod Koul --- drivers/phy/tegra/xusb.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/phy/tegra/xusb.c b/drivers/phy/tegra/xusb.c index d0d0b252a205..0635f0d82c96 100644 --- a/drivers/phy/tegra/xusb.c +++ b/drivers/phy/tegra/xusb.c @@ -1257,13 +1257,7 @@ static int tegra_xusb_padctl_probe(struct platform_device *pdev) err = tegra_xusb_setup_ports(padctl); if (err) { - const char *level = KERN_ERR; - - if (err == -EPROBE_DEFER) - level = KERN_DEBUG; - - dev_printk(level, &pdev->dev, - dev_fmt("failed to setup XUSB ports: %d\n"), err); + dev_err_probe(&pdev->dev, err, "failed to setup XUSB ports\n"); goto remove_pads; } From 8f56fdf3d8cb9f1dbdc93bf9ac816698aa6f70b7 Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Wed, 7 Oct 2026 15:44:43 +0100 Subject: [PATCH 114/114] phy: tegra: Add support for Tegra238 XUSB pad controller Add support for the Tegra238 XUSB pad controller by populating the Tegra238 specific SoC data for the controller. The Tegra238 has three USB2 and USB3 ports, uses the same port ops as Tegra186 and has the same supplies as Tegra194. Due to the dependencies the Tegra238 device has on the Tegra186 port ops and Tegra194 supplies, drop the compile time guards around these structures. Signed-off-by: Jon Hunter Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20261007-tegra-phy-v3-3-d501de414710@nvidia.com Signed-off-by: Vinod Koul --- drivers/phy/tegra/Makefile | 1 + drivers/phy/tegra/xusb-tegra186.c | 29 +++++++++++++++++++++++------ drivers/phy/tegra/xusb.c | 6 ++++++ drivers/phy/tegra/xusb.h | 3 +++ 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/drivers/phy/tegra/Makefile b/drivers/phy/tegra/Makefile index f5ed9582d80b..018a4cab2b40 100644 --- a/drivers/phy/tegra/Makefile +++ b/drivers/phy/tegra/Makefile @@ -8,6 +8,7 @@ phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_210_SOC) += xusb-tegra210.o phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_186_SOC) += xusb-tegra186.o phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_194_SOC) += xusb-tegra186.o phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_234_SOC) += xusb-tegra186.o +phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_238_SOC) += xusb-tegra186.o phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_264_SOC) += xusb-tegra186.o obj-$(CONFIG_PHY_TEGRA194_P2U) += phy-tegra194-p2u.o obj-$(CONFIG_PHY_TEGRA_MPHY) += phy-tegra-mphy.o diff --git a/drivers/phy/tegra/xusb-tegra186.c b/drivers/phy/tegra/xusb-tegra186.c index 322efdea0d80..aa8ab94fad4c 100644 --- a/drivers/phy/tegra/xusb-tegra186.c +++ b/drivers/phy/tegra/xusb-tegra186.c @@ -1599,7 +1599,6 @@ static const struct tegra_xusb_padctl_ops tegra186_xusb_padctl_ops = { .utmi_pad_power_down = tegra186_utmi_pad_power_down, }; -#if IS_ENABLED(CONFIG_ARCH_TEGRA_186_SOC) static const char * const tegra186_xusb_padctl_supply_names[] = { "avdd-pll-erefeut", "avdd-usb", @@ -1665,11 +1664,7 @@ const struct tegra_xusb_padctl_soc tegra186_xusb_padctl_soc = { .num_supplies = ARRAY_SIZE(tegra186_xusb_padctl_supply_names), }; EXPORT_SYMBOL_GPL(tegra186_xusb_padctl_soc); -#endif -#if IS_ENABLED(CONFIG_ARCH_TEGRA_194_SOC) || \ - IS_ENABLED(CONFIG_ARCH_TEGRA_234_SOC) || \ - IS_ENABLED(CONFIG_ARCH_TEGRA_264_SOC) static const char * const tegra194_xusb_padctl_supply_names[] = { "avdd-usb", "vclamp-usb", @@ -1755,6 +1750,29 @@ const struct tegra_xusb_padctl_soc tegra234_xusb_padctl_soc = { }; EXPORT_SYMBOL_GPL(tegra234_xusb_padctl_soc); +const struct tegra_xusb_padctl_soc tegra238_xusb_padctl_soc = { + .num_pads = ARRAY_SIZE(tegra186_pads), + .pads = tegra186_pads, + .ports = { + .usb2 = { + .ops = &tegra186_usb2_port_ops, + .count = 3, + }, + .usb3 = { + .ops = &tegra186_usb3_port_ops, + .count = 3, + }, + }, + .ops = &tegra186_xusb_padctl_ops, + .supply_names = tegra194_xusb_padctl_supply_names, + .num_supplies = ARRAY_SIZE(tegra194_xusb_padctl_supply_names), + .supports_gen2 = true, + .poll_trk_completed = true, + .trk_hw_mode = false, + .supports_lp_cfg_en = true, +}; +EXPORT_SYMBOL_GPL(tegra238_xusb_padctl_soc); + const struct tegra_xusb_padctl_soc tegra264_xusb_padctl_soc = { .num_pads = ARRAY_SIZE(tegra194_pads), .pads = tegra194_pads, @@ -1778,7 +1796,6 @@ const struct tegra_xusb_padctl_soc tegra264_xusb_padctl_soc = { .has_per_pad_term = true, }; EXPORT_SYMBOL_GPL(tegra264_xusb_padctl_soc); -#endif MODULE_AUTHOR("JC Kuo "); MODULE_DESCRIPTION("NVIDIA Tegra186 XUSB Pad Controller driver"); diff --git a/drivers/phy/tegra/xusb.c b/drivers/phy/tegra/xusb.c index 0635f0d82c96..89ce9ac87300 100644 --- a/drivers/phy/tegra/xusb.c +++ b/drivers/phy/tegra/xusb.c @@ -78,6 +78,12 @@ static const struct of_device_id tegra_xusb_padctl_of_match[] = { .data = &tegra234_xusb_padctl_soc, }, #endif +#if defined(CONFIG_ARCH_TEGRA_238_SOC) + { + .compatible = "nvidia,tegra238-xusb-padctl", + .data = &tegra238_xusb_padctl_soc, + }, +#endif #if defined(CONFIG_ARCH_TEGRA_264_SOC) { .compatible = "nvidia,tegra264-xusb-padctl", diff --git a/drivers/phy/tegra/xusb.h b/drivers/phy/tegra/xusb.h index d8d799d9e752..b9de4f3b5781 100644 --- a/drivers/phy/tegra/xusb.h +++ b/drivers/phy/tegra/xusb.h @@ -514,6 +514,9 @@ extern const struct tegra_xusb_padctl_soc tegra194_xusb_padctl_soc; #if defined(CONFIG_ARCH_TEGRA_234_SOC) extern const struct tegra_xusb_padctl_soc tegra234_xusb_padctl_soc; #endif +#if defined(CONFIG_ARCH_TEGRA_238_SOC) +extern const struct tegra_xusb_padctl_soc tegra238_xusb_padctl_soc; +#endif #if defined(CONFIG_ARCH_TEGRA_264_SOC) extern const struct tegra_xusb_padctl_soc tegra264_xusb_padctl_soc; #endif