* Re: [PATCH v3 1/5] drm/bridge: Implement generic USB Type-C DP HPD bridge
From: Sebastian Reichel @ 2026-07-17 17:19 UTC (permalink / raw)
To: Chaoyi Chen
Cc: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sandy Huang, Heiko Stübner, Andy Yan, Vinod Koul,
Chaoyi Chen, Nicolas Frattaroli, linux-kernel, dri-devel,
linux-arm-kernel, linux-rockchip, linux-phy
In-Reply-To: <20260717072323.96-2-kernel@airkyi.com>
[-- Attachment #1.1: Type: text/plain, Size: 6133 bytes --]
Hello,
On Fri, Jul 17, 2026 at 03:23:19PM +0800, Chaoyi Chen wrote:
> From: Chaoyi Chen <chaoyi.chen@rock-chips.com>
>
> The HPD function of Type-C DP is implemented through
> drm_connector_oob_hotplug_event(). For embedded DP, it is required
> that the DRM connector fwnode corresponds to the Type-C port fwnode.
>
> To describe the relationship between the DP controller and the Type-C
> port device, we usually using drm_bridge to build a bridge chain.
>
> Now several USB-C controller drivers have already implemented the DP
> HPD bridge function provided by aux-hpd-bridge.c, it will build a DP
> HPD bridge on USB-C connector port device.
>
> But this requires the USB-C controller driver to manually register the
> HPD bridge. If the driver does not implement this feature, the bridge
> will not be create.
>
> So this patch implements a generic DP HPD bridge based on
> aux-hpd-bridge.c. It will monitor Type-C bus events, and when a
> Type-C port device containing the DP svid is registered, it will
> create an HPD bridge for it without the need for the USB-C controller
> driver to implement it.
>
> Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com>
> Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> Reviewed-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
> ---
Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com>
Tested-by: Sebastian Reichel <sebastian.reichel@collabora.com> # ArmSom Sige5
I gave this a test together with the RK3588/RK3576 USB-C DP AltMode
patches I'm working on. As the fusb302 does a manual registration
for the DRM bridge in its probe function, the bridge is registered
twice:
root@sige5 # cat /sys/kernel/debug/dri/bridges
...
bridge[1]: drm_aux_hpd_bridge_funcs
refcount: 4
type: [10] DP
OF: /soc/i2c@2ac50000/typec-portc@22/connector:usb-c-connector
ops: [0x4] hpd
bridge[2]: drm_aux_hpd_bridge_funcs
refcount: 2
type: [10] DP
OF: /soc/i2c@2ac50000/typec-portc@22/connector:usb-c-connector
ops: [0x4] hpd
...
Apparently the USB-C DP AltMode keeps working, so this just wastes
a few CPU cycles and some memory. So this can land and then we can
remove the manual code from the driver as a follow-up step. I also
gave that a try and things keep working. I won't send the fusb302
patch for now to ensure its not applied before this patch lands.
Greetings,
-- Sebastian
> (no changes since v3)
>
> Changes in v2:
> - Add copyright text.
> - Remove useless goto.
>
> ---
> drivers/gpu/drm/bridge/Kconfig | 10 ++++
> drivers/gpu/drm/bridge/Makefile | 1 +
> .../gpu/drm/bridge/aux-hpd-typec-dp-bridge.c | 54 +++++++++++++++++++
> 3 files changed, 65 insertions(+)
> create mode 100644 drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c
>
> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index 4a57d49b4c6d..9739b2a19758 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -30,6 +30,16 @@ config DRM_AUX_HPD_BRIDGE
> Simple bridge that terminates the bridge chain and provides HPD
> support.
>
> +if DRM_AUX_HPD_BRIDGE
> +config DRM_AUX_HPD_TYPEC_BRIDGE
> + tristate
> + depends on TYPEC || !TYPEC
> + default TYPEC
> + help
> + Simple bridge that terminates the bridge chain and provides HPD
> + support. It build bridge on each USB-C connector device node.
> +endif
> +
> menu "Display Interface Bridges"
> depends on DRM && DRM_BRIDGE
>
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index 15cc821d85b7..d88a9e1ccc9a 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -1,6 +1,7 @@
> # SPDX-License-Identifier: GPL-2.0
> obj-$(CONFIG_DRM_AUX_BRIDGE) += aux-bridge.o
> obj-$(CONFIG_DRM_AUX_HPD_BRIDGE) += aux-hpd-bridge.o
> +obj-$(CONFIG_DRM_AUX_HPD_TYPEC_BRIDGE) += aux-hpd-typec-dp-bridge.o
> obj-$(CONFIG_DRM_CHIPONE_ICN6211) += chipone-icn6211.o
> obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o
> obj-$(CONFIG_DRM_CROS_EC_ANX7688) += cros-ec-anx7688.o
> diff --git a/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c b/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c
> new file mode 100644
> index 000000000000..c09579ff7ab9
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c
> @@ -0,0 +1,54 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2026 Rockchip Electronics Co., Ltd.
> + *
> + * Author: Chaoyi Chen <chaoyi.chen@rock-chips.com>
> + */
> +#include <linux/of.h>
> +#include <linux/usb/typec_altmode.h>
> +#include <linux/usb/typec_dp.h>
> +
> +#include <drm/bridge/aux-bridge.h>
> +
> +static int drm_typec_bus_event(struct notifier_block *nb,
> + unsigned long action, void *data)
> +{
> + struct device *dev = (struct device *)data;
> + struct typec_altmode *alt = to_typec_altmode(dev);
> +
> + if (action != BUS_NOTIFY_ADD_DEVICE)
> + return NOTIFY_OK;
> +
> + /*
> + * alt->dev.parent->parent : USB-C controller device
> + * alt->dev.parent : USB-C connector device
> + */
> + if (is_typec_port_altmode(&alt->dev) && alt->svid == USB_TYPEC_DP_SID)
> + drm_dp_hpd_bridge_register(alt->dev.parent->parent,
> + to_of_node(alt->dev.parent->fwnode));
> +
> + return NOTIFY_OK;
> +}
> +
> +static struct notifier_block drm_typec_event_nb = {
> + .notifier_call = drm_typec_bus_event,
> +};
> +
> +static void drm_aux_hpd_typec_dp_bridge_module_exit(void)
> +{
> + bus_unregister_notifier(&typec_bus, &drm_typec_event_nb);
> +}
> +
> +static int __init drm_aux_hpd_typec_dp_bridge_module_init(void)
> +{
> + bus_register_notifier(&typec_bus, &drm_typec_event_nb);
> +
> + return 0;
> +}
> +
> +module_init(drm_aux_hpd_typec_dp_bridge_module_init);
> +module_exit(drm_aux_hpd_typec_dp_bridge_module_exit);
> +
> +MODULE_AUTHOR("Chaoyi Chen <chaoyi.chen@rock-chips.com>");
> +MODULE_DESCRIPTION("DRM TYPEC DP HPD BRIDGE");
> +MODULE_LICENSE("GPL");
> --
> 2.53.0
>
[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
[-- Attachment #2: Type: text/plain, Size: 112 bytes --]
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH v5 3/3] arm64: dts: qcom: glymur: Wire PCIe3a/3b to shared Gen5x8 PHY
From: Konrad Dybcio @ 2026-07-17 16:50 UTC (permalink / raw)
To: Qiang Yu, Vinod Koul, Neil Armstrong, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Manivannan Sadhasivam,
Philipp Zabel, Bjorn Andersson, Konrad Dybcio
Cc: linux-arm-msm, linux-phy, devicetree, linux-kernel
In-Reply-To: <20260717-glymur_linkmode_0717-v5-3-4f9e87a61463@oss.qualcomm.com>
On 7/17/26 11:58 AM, Qiang Yu wrote:
> Glymur's PCIe3a and PCIe3b controllers share a single Gen5x8 QMP PHY block
> that can be bifurcated into two independent x4 links, rather than each
> controller owning its own dedicated PHY.
>
> Add a pcie3_phy node describing the shared PHY block, add the missing
> PCIe3a controller node, and point both PCIe3a's and PCIe3b's port phys
> at &pcie3_phy (index 0 and 1 respectively) so each controller picks up
> its half of the bifurcated PHY. Update the GCC pipe clock parent array
> to reference the new PHY's clock outputs instead of the placeholders.
>
> Signed-off-by: Qiang Yu <qiang.yu@oss.qualcomm.com>
> ---
> arch/arm64/boot/dts/qcom/glymur-crd.dtsi | 8 +
> arch/arm64/boot/dts/qcom/glymur.dtsi | 336 ++++++++++++++++++++++++++++++-
These changes really shouldn't be in the same commit
> 2 files changed, 342 insertions(+), 2 deletions(-)
>
> diff --git a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi
> index c12808abbfe1..e8e81bf9a6a1 100644
> --- a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi
> +++ b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi
> @@ -750,6 +750,14 @@ &pcie3b {
> pinctrl-names = "default";
> };
>
> +&pcie3_phy {
> + vdda-phy-supply = <&vreg_l3c_e1_0p89>;
> + vdda-pll-supply = <&vreg_l2c_e1_1p14>;
> +
> + vdda-refgen0p9-supply = <&vreg_l1c_e1_0p82>;
> + vdda-refgen1p2-supply = <&vreg_l4f_e1_1p08>;
> +};
This node is disabled
Konrad
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* [PATCH v5 3/3] arm64: dts: qcom: glymur: Wire PCIe3a/3b to shared Gen5x8 PHY
From: Qiang Yu @ 2026-07-17 9:58 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Manivannan Sadhasivam, Philipp Zabel,
Bjorn Andersson, Konrad Dybcio
Cc: Qiang Yu, linux-arm-msm, linux-phy, devicetree, linux-kernel
In-Reply-To: <20260717-glymur_linkmode_0717-v5-0-4f9e87a61463@oss.qualcomm.com>
Glymur's PCIe3a and PCIe3b controllers share a single Gen5x8 QMP PHY block
that can be bifurcated into two independent x4 links, rather than each
controller owning its own dedicated PHY.
Add a pcie3_phy node describing the shared PHY block, add the missing
PCIe3a controller node, and point both PCIe3a's and PCIe3b's port phys
at &pcie3_phy (index 0 and 1 respectively) so each controller picks up
its half of the bifurcated PHY. Update the GCC pipe clock parent array
to reference the new PHY's clock outputs instead of the placeholders.
Signed-off-by: Qiang Yu <qiang.yu@oss.qualcomm.com>
---
arch/arm64/boot/dts/qcom/glymur-crd.dtsi | 8 +
arch/arm64/boot/dts/qcom/glymur.dtsi | 336 ++++++++++++++++++++++++++++++-
2 files changed, 342 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi
index c12808abbfe1..e8e81bf9a6a1 100644
--- a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi
+++ b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi
@@ -750,6 +750,14 @@ &pcie3b {
pinctrl-names = "default";
};
+&pcie3_phy {
+ vdda-phy-supply = <&vreg_l3c_e1_0p89>;
+ vdda-pll-supply = <&vreg_l2c_e1_1p14>;
+
+ vdda-refgen0p9-supply = <&vreg_l1c_e1_0p82>;
+ vdda-refgen1p2-supply = <&vreg_l4f_e1_1p08>;
+};
+
&pcie3b_port0 {
reset-gpios = <&tlmm 155 GPIO_ACTIVE_LOW>;
wake-gpios = <&tlmm 157 GPIO_ACTIVE_LOW>;
diff --git a/arch/arm64/boot/dts/qcom/glymur.dtsi b/arch/arm64/boot/dts/qcom/glymur.dtsi
index 6d058d591039..edd09b84b8b9 100644
--- a/arch/arm64/boot/dts/qcom/glymur.dtsi
+++ b/arch/arm64/boot/dts/qcom/glymur.dtsi
@@ -801,8 +801,8 @@ gcc: clock-controller@100000 {
<0>, /* USB 2 Phy PCIE PIPEGMUX */
<0>, /* USB 2 Phy PIPEGMUX */
<0>, /* USB 2 Phy SYS PCIE PIPEGMUX */
- <0>, /* PCIe 3a */
- <0>, /* PCIe 3b */
+ <&pcie3_phy 0>, /* PCIe 3a pipe */
+ <&pcie3_phy 1>, /* PCIe 3b pipe */
<&pcie4_phy>, /* PCIe 4 */
<&pcie5_phy>, /* PCIe 5 */
<&pcie6_phy>, /* PCIe 6 */
@@ -2329,6 +2329,62 @@ &config_noc SLAVE_QUP_0 QCOM_ICC_TAG_ALWAYS>,
};
};
+ pcie3_phy: phy@f00000 {
+ compatible = "qcom,glymur-qmp-gen5x8-pcie-phy";
+ reg = <0x0 0x00f00000 0x0 0x10000>,
+ <0x0 0x00f10000 0x0 0x10000>;
+ reg-names = "port_a",
+ "port_b";
+
+ clocks = <&gcc GCC_PCIE_PHY_3A_AUX_CLK>,
+ <&gcc GCC_PCIE_3A_CFG_AHB_CLK>,
+ <&tcsr TCSR_PCIE_3_CLKREF_EN>,
+ <&gcc GCC_PCIE_3A_PHY_RCHNG_CLK>,
+ <&gcc GCC_PCIE_3A_PIPE_CLK>,
+ <&gcc GCC_PCIE_PHY_3B_AUX_CLK>,
+ <&gcc GCC_PCIE_3B_CFG_AHB_CLK>,
+ <&gcc GCC_PCIE_3B_PHY_RCHNG_CLK>,
+ <&gcc GCC_PCIE_3B_PIPE_CLK>,
+ <&gcc GCC_PCIE_3B_PIPE_DIV2_CLK>;
+ clock-names = "aux",
+ "cfg_ahb",
+ "ref",
+ "rchng",
+ "pipe",
+ "phy_b_aux",
+ "cfg_ahb_b",
+ "rchng_b",
+ "pipe_b",
+ "pipediv2_b";
+
+ resets = <&gcc GCC_PCIE_3A_PHY_BCR>,
+ <&gcc GCC_PCIE_3A_NOCSR_COM_PHY_BCR>,
+ <&gcc GCC_PCIE_3B_PHY_BCR>,
+ <&gcc GCC_PCIE_3B_NOCSR_COM_PHY_BCR>;
+ reset-names = "phy_a",
+ "phy_a_nocsr",
+ "phy_b",
+ "phy_b_nocsr";
+
+ assigned-clocks = <&gcc GCC_PCIE_3A_PHY_RCHNG_CLK>,
+ <&gcc GCC_PCIE_3B_PHY_RCHNG_CLK>;
+ assigned-clock-rates = <100000000>, <100000000>;
+
+ power-domains = <&gcc GCC_PCIE_3A_PHY_GDSC>,
+ <&gcc GCC_PCIE_3B_PHY_GDSC>;
+ power-domain-names = "phy_a_gdsc", "phy_b_gdsc";
+
+ qcom,link-mode = <&tcsr 0x5000>;
+
+ #clock-cells = <1>;
+ clock-output-names = "pcie3a_pipe_clk",
+ "pcie3b_pipe_clk";
+
+ #phy-cells = <1>;
+
+ status = "disabled";
+ };
+
usb_hs_phy: phy@fa0000 {
compatible = "qcom,glymur-m31-eusb2-phy",
"qcom,sm8750-m31-eusb2-phy";
@@ -3691,6 +3747,282 @@ pcie3b_port0: pcie@0 {
reg = <0x0 0x0 0x0 0x0 0x0>;
bus-range = <0x01 0xff>;
+ phys = <&pcie3_phy 1>;
+
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges;
+ };
+ };
+
+ pcie3a: pci@1c10000 {
+ device_type = "pci";
+ compatible = "qcom,glymur-pcie", "qcom,pcie-x1e80100";
+ reg = <0x0 0x01c10000 0x0 0x3000>,
+ <0x0 0x70000000 0x0 0xf20>,
+ <0x0 0x70000f40 0x0 0xa8>,
+ <0x0 0x70001000 0x0 0x4000>,
+ <0x0 0x70100000 0x0 0x100000>,
+ <0x0 0x01c13000 0x0 0x1000>;
+ reg-names = "parf",
+ "dbi",
+ "elbi",
+ "atu",
+ "config",
+ "mhi";
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges = <0x01000000 0x0 0x00000000 0x0 0x70200000 0x0 0x100000>,
+ <0x02000000 0x0 0x70000000 0x0 0x70300000 0x0 0x3d00000>,
+ <0x03000000 0x7 0x00000000 0x7 0x00000000 0x0 0x40000000>,
+ <0x43000000 0x70 0x00000000 0x70 0x00000000 0x10 0x00000000>;
+
+ bus-range = <0 0xff>;
+
+ dma-coherent;
+
+ linux,pci-domain = <3>;
+ num-lanes = <8>;
+
+ operating-points-v2 = <&pcie3a_opp_table>;
+
+ msi-map = <0x0 &gic_its 0xb0000 0x10000>;
+ iommu-map = <0x0 &pcie_smmu 0x30000 0x10000>;
+
+ interrupts = <GIC_SPI 948 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 949 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 844 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 845 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 194 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 202 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 846 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 847 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 942 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "msi0",
+ "msi1",
+ "msi2",
+ "msi3",
+ "msi4",
+ "msi5",
+ "msi6",
+ "msi7",
+ "global";
+
+ #interrupt-cells = <1>;
+ interrupt-map-mask = <0 0 0 0x7>;
+ interrupt-map = <0 0 0 1 &intc 0 0 0 848 IRQ_TYPE_LEVEL_HIGH>,
+ <0 0 0 2 &intc 0 0 0 849 IRQ_TYPE_LEVEL_HIGH>,
+ <0 0 0 3 &intc 0 0 0 850 IRQ_TYPE_LEVEL_HIGH>,
+ <0 0 0 4 &intc 0 0 0 851 IRQ_TYPE_LEVEL_HIGH>;
+
+ clocks = <&gcc GCC_PCIE_3A_AUX_CLK>,
+ <&gcc GCC_PCIE_3A_CFG_AHB_CLK>,
+ <&gcc GCC_PCIE_3A_MSTR_AXI_CLK>,
+ <&gcc GCC_PCIE_3A_SLV_AXI_CLK>,
+ <&gcc GCC_PCIE_3A_SLV_Q2A_AXI_CLK>,
+ <&gcc GCC_AGGRE_NOC_PCIE_3A_WEST_SF_AXI_CLK>;
+ clock-names = "aux",
+ "cfg",
+ "bus_master",
+ "bus_slave",
+ "slave_q2a",
+ "noc_aggr";
+
+ assigned-clocks = <&gcc GCC_PCIE_3A_AUX_CLK>;
+ assigned-clock-rates = <19200000>;
+
+ interconnects = <&pcie_west_anoc MASTER_PCIE_3A QCOM_ICC_TAG_ALWAYS
+ &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>,
+ <&hsc_noc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS
+ &pcie_west_slv_noc SLAVE_PCIE_3A QCOM_ICC_TAG_ALWAYS>;
+ interconnect-names = "pcie-mem",
+ "cpu-pcie";
+
+ resets = <&gcc GCC_PCIE_3A_BCR>,
+ <&gcc GCC_PCIE_3A_LINK_DOWN_BCR>;
+ reset-names = "pci",
+ "link_down";
+
+ power-domains = <&gcc GCC_PCIE_3A_GDSC>;
+
+ eq-presets-8gts = /bits/ 16 <0x5555 0x5555 0x5555 0x5555
+ 0x5555 0x5555 0x5555 0x5555>;
+ eq-presets-16gts = /bits/ 8 <0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55>;
+ eq-presets-32gts = /bits/ 8 <0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55>;
+
+ status = "disabled";
+
+ pcie3a_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+ /* GEN 1 x1 */
+ opp-2500000-1 {
+ opp-hz = /bits/ 64 <2500000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ opp-peak-kBps = <250000 1>;
+ opp-level = <1>;
+ };
+
+ /* GEN 1 x2 */
+ opp-5000000-1 {
+ opp-hz = /bits/ 64 <5000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ opp-peak-kBps = <500000 1>;
+ opp-level = <1>;
+ };
+
+ /* GEN 1 x4 */
+ opp-10000000-1 {
+ opp-hz = /bits/ 64 <10000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ opp-peak-kBps = <1000000 1>;
+ opp-level = <1>;
+ };
+
+ /* GEN 1 x8 */
+ opp-20000000-1 {
+ opp-hz = /bits/ 64 <20000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ opp-peak-kBps = <2000000 1>;
+ opp-level = <1>;
+ };
+
+ /* GEN 2 x1 */
+ opp-5000000-2 {
+ opp-hz = /bits/ 64 <5000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ opp-peak-kBps = <500000 1>;
+ opp-level = <2>;
+ };
+
+ /* GEN 2 x2 */
+ opp-10000000-2 {
+ opp-hz = /bits/ 64 <10000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ opp-peak-kBps = <1000000 1>;
+ opp-level = <2>;
+ };
+
+ /* GEN 2 x4 */
+ opp-20000000-2 {
+ opp-hz = /bits/ 64 <20000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ opp-peak-kBps = <2000000 1>;
+ opp-level = <2>;
+ };
+
+ /* GEN 2 x8 */
+ opp-40000000-2 {
+ opp-hz = /bits/ 64 <40000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ opp-peak-kBps = <4000000 1>;
+ opp-level = <2>;
+ };
+
+ /* GEN 3 x1 */
+ opp-8000000-3 {
+ opp-hz = /bits/ 64 <8000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ opp-peak-kBps = <984500 1>;
+ opp-level = <3>;
+ };
+
+ /* GEN 3 x2 */
+ opp-16000000-3 {
+ opp-hz = /bits/ 64 <16000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ opp-peak-kBps = <1969000 1>;
+ opp-level = <3>;
+ };
+
+ /* GEN 3 x4 */
+ opp-32000000-3 {
+ opp-hz = /bits/ 64 <32000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ opp-peak-kBps = <3938000 1>;
+ opp-level = <3>;
+ };
+
+ /* GEN 3 x8 */
+ opp-64000000-3 {
+ opp-hz = /bits/ 64 <64000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ opp-peak-kBps = <7876000 1>;
+ opp-level = <3>;
+ };
+
+ /* GEN 4 x1 */
+ opp-16000000-4 {
+ opp-hz = /bits/ 64 <16000000>;
+ required-opps = <&rpmhpd_opp_svs>;
+ opp-peak-kBps = <1969000 1>;
+ opp-level = <4>;
+ };
+
+ /* GEN 4 x2 */
+ opp-32000000-4 {
+ opp-hz = /bits/ 64 <32000000>;
+ required-opps = <&rpmhpd_opp_svs>;
+ opp-peak-kBps = <3938000 1>;
+ opp-level = <4>;
+ };
+
+ /* GEN 4 x4 */
+ opp-64000000-4 {
+ opp-hz = /bits/ 64 <64000000>;
+ required-opps = <&rpmhpd_opp_svs>;
+ opp-peak-kBps = <7876000 1>;
+ opp-level = <4>;
+ };
+
+ /* GEN 4 x8 */
+ opp-128000000-4 {
+ opp-hz = /bits/ 64 <128000000>;
+ required-opps = <&rpmhpd_opp_svs>;
+ opp-peak-kBps = <15753000 1>;
+ opp-level = <4>;
+ };
+
+ /* GEN 5 x1 */
+ opp-32000000-5 {
+ opp-hz = /bits/ 64 <32000000>;
+ required-opps = <&rpmhpd_opp_nom>;
+ opp-peak-kBps = <3938000 1>;
+ opp-level = <5>;
+ };
+
+ /* GEN 5 x2 */
+ opp-64000000-5 {
+ opp-hz = /bits/ 64 <64000000>;
+ required-opps = <&rpmhpd_opp_nom>;
+ opp-peak-kBps = <7876000 1>;
+ opp-level = <5>;
+ };
+
+ /* GEN 5 x4 */
+ opp-128000000-5 {
+ opp-hz = /bits/ 64 <128000000>;
+ required-opps = <&rpmhpd_opp_nom>;
+ opp-peak-kBps = <15753000 1>;
+ opp-level = <5>;
+ };
+
+ /* GEN 5 x8 */
+ opp-256000000-5 {
+ opp-hz = /bits/ 64 <256000000>;
+ required-opps = <&rpmhpd_opp_nom>;
+ opp-peak-kBps = <31506000 1>;
+ opp-level = <5>;
+ };
+ };
+
+ pcie3a_port0: pcie@0 {
+ device_type = "pci";
+ reg = <0x0 0x0 0x0 0x0 0x0>;
+ bus-range = <0x01 0xff>;
+
+ phys = <&pcie3_phy 0>;
+
#address-cells = <3>;
#size-cells = <2>;
ranges;
--
2.34.1
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* [PATCH v5 2/3] phy: qcom: qmp-pcie: Add QMP PCIe Multi-PHY driver
From: Qiang Yu @ 2026-07-17 9:58 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Manivannan Sadhasivam, Philipp Zabel,
Bjorn Andersson, Konrad Dybcio
Cc: Qiang Yu, linux-arm-msm, linux-phy, devicetree, linux-kernel
In-Reply-To: <20260717-glymur_linkmode_0717-v5-0-4f9e87a61463@oss.qualcomm.com>
Some QMP PCIe PHY hardware blocks support multiple link topologies (e.g.
x8 or x4+x4) selected via a TCSR register. The existing single-instance
QMP PCIe PHY driver has no way to model this: it assumes a single cfg per
DT node and instantiates exactly one PHY.
Add a dedicated driver for this class of PHY. Match data carries a
per-mode cfg table; qmp_pcie_multiphy_probe() reads the current link
mode from the TCSR register pointed to by "qcom,link-mode", looks up the
corresponding cfg array, and instantiates one qmp_pcie per sub-PHY
required by that link mode, registering the clock and #phy-cells = <1> phy
providers so consumers can address individual sub-PHYs by index.
The driver inherits the phy setting and link-mode programmed by firmware,
so only the no_csr reset is used and no phy setting tables are provided.
Add the first match data and compatible, qcom,glymur-qmp-gen5x8-pcie-phy,
for the Glymur Gen5 PCIe PHY that can bifurcate into two x4 links or
operate as a single x8 link.
Signed-off-by: Qiang Yu <qiang.yu@oss.qualcomm.com>
---
drivers/phy/qualcomm/Kconfig | 11 +
drivers/phy/qualcomm/Makefile | 1 +
drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c | 770 ++++++++++++++++++++++
3 files changed, 782 insertions(+)
diff --git a/drivers/phy/qualcomm/Kconfig b/drivers/phy/qualcomm/Kconfig
index 60a0ead127fa..31241d1bbef2 100644
--- a/drivers/phy/qualcomm/Kconfig
+++ b/drivers/phy/qualcomm/Kconfig
@@ -77,6 +77,17 @@ config PHY_QCOM_QMP_PCIE
Enable this to support the QMP PCIe PHY transceiver that is used
with PCIe controllers on Qualcomm chips.
+config PHY_QCOM_QMP_PCIE_MULTIPHY
+ tristate "Qualcomm QMP PCIe Multiple Link-mode PHY Driver"
+ depends on PCI || COMPILE_TEST
+ select GENERIC_PHY
+ default PHY_QCOM_QMP
+ help
+ Enable this to support the QMP PCIe PHY transceiver that is used
+ with PCIe controllers on Qualcomm chips. This PHY is a single
+ multi-lane QMP block that can be configured either as one wide
+ link or as multiple narrower independent links (bifurcation).
+
config PHY_QCOM_QMP_PCIE_8996
tristate "Qualcomm QMP PCIe 8996 PHY Driver"
depends on PCI || COMPILE_TEST
diff --git a/drivers/phy/qualcomm/Makefile b/drivers/phy/qualcomm/Makefile
index b71a6a0bed3f..8bf887d58ee4 100644
--- a/drivers/phy/qualcomm/Makefile
+++ b/drivers/phy/qualcomm/Makefile
@@ -10,6 +10,7 @@ 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
obj-$(CONFIG_PHY_QCOM_QMP_PCIE) += phy-qcom-qmp-pcie.o
+obj-$(CONFIG_PHY_QCOM_QMP_PCIE_MULTIPHY) += phy-qcom-qmp-pcie-multiphy.o
obj-$(CONFIG_PHY_QCOM_QMP_PCIE_8996) += phy-qcom-qmp-pcie-msm8996.o
obj-$(CONFIG_PHY_QCOM_QMP_UFS) += phy-qcom-qmp-ufs.o
obj-$(CONFIG_PHY_QCOM_QMP_USB) += phy-qcom-qmp-usb.o
diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c b/drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c
new file mode 100644
index 000000000000..b79edf2b7d5f
--- /dev/null
+++ b/drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c
@@ -0,0 +1,770 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2026, Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+
+#include "phy-qcom-qmp.h"
+
+#define PHY_INIT_COMPLETE_TIMEOUT 10000
+
+enum qmp_pcie_glymur_link_mode {
+ QMP_PCIE_GLYMUR_MODE_X8,
+ QMP_PCIE_GLYMUR_MODE_X4X4,
+};
+
+enum qphy_reg_layout {
+ QPHY_PCS_STATUS,
+ QPHY_LAYOUT_SIZE
+};
+
+static const unsigned int pciephy_v8_50_regs_layout[QPHY_LAYOUT_SIZE] = {
+ [QPHY_PCS_STATUS] = QPHY_V8_50_PCS_STATUS1,
+};
+
+struct qmp_pcie_offsets {
+ u16 pcs;
+};
+
+struct qmp_phy_cfg {
+ const struct qmp_pcie_offsets *offsets;
+
+ const char * const *reg_names;
+ int num_regs;
+
+ const char * const *pd_names;
+ int num_pds;
+
+ const char * const *nocsr_reset_list;
+ int num_nocsr_resets;
+
+ const char * const *vreg_list;
+ int num_vregs;
+
+ const unsigned int *regs;
+
+ unsigned int phy_status;
+
+ const char * const *clk_list;
+ int num_clks;
+ const char * const *pipe_clk_list;
+
+ int num_pipe_clks;
+};
+
+struct qmp_pcie {
+ struct device *dev;
+
+ const struct qmp_phy_cfg *cfg;
+
+ void __iomem **base;
+
+ struct clk_bulk_data *clks;
+ struct clk_bulk_data *pipe_clks;
+
+ struct reset_control_bulk_data *nocsr_resets;
+
+ struct regulator_bulk_data *vregs;
+
+ struct device **pd_devs;
+
+ struct clk_fixed_rate pipe_clk_fixed;
+};
+
+struct qmp_pcie_multiphy {
+ struct phy **phys;
+ const struct qmp_pcie_link_mode_cfg *mode_cfg;
+
+ int num_pipe_outputs;
+ struct clk_fixed_rate *pipe_out_clks;
+};
+
+struct qmp_pcie_link_mode_cfg {
+ const struct qmp_phy_cfg * const *cfgs;
+ u32 num_phys;
+};
+
+struct qmp_pcie_match_data {
+ const struct qmp_pcie_link_mode_cfg *mode_cfgs;
+ u32 num_modes;
+};
+
+static const char * const glymur_pciephy_clk_l[] = {
+ "aux", "cfg_ahb", "ref", "rchng", "phy_b_aux",
+};
+
+static const char * const glymur_pciephy_a_clk_l[] = {
+ "aux", "cfg_ahb", "ref", "rchng",
+};
+
+static const char * const glymur_pciephy_b_clk_l[] = {
+ "phy_b_aux", "cfg_ahb_b", "ref", "rchng_b",
+};
+
+static const char * const glymur_pciephy_pipeclk_l[] = {
+ "pipe",
+};
+
+static const char * const glymur_pipephy_b_pipeclk_l[] = {
+ "pipe_b", "pipediv2_b",
+};
+
+static const char * const glymur_vreg_l[] = {
+ "vdda-phy", "vdda-pll", "vdda-refgen0p9", "vdda-refgen1p2",
+};
+
+static const char * const glymur_pciephy_a_reg_l[] = {
+ "port_a",
+};
+
+static const char * const glymur_pciephy_b_reg_l[] = {
+ "port_b",
+};
+
+static const char * const glymur_pciephy_reg_l[] = {
+ "port_a", "port_b",
+};
+
+static const char * const glymur_pciephy_a_pd_l[] = {
+ "phy_a_gdsc",
+};
+
+static const char * const glymur_pciephy_b_pd_l[] = {
+ "phy_b_gdsc",
+};
+
+static const char * const glymur_pciephy_pd_l[] = {
+ "phy_a_gdsc", "phy_b_gdsc",
+};
+
+static const char * const glymur_pciephy_a_nocsr_reset_l[] = {
+ "phy_a_nocsr",
+};
+
+static const char * const glymur_pciephy_nocsr_reset_l[] = {
+ "phy_a_nocsr", "phy_b_nocsr",
+};
+
+static const char * const glymur_pciephy_b_nocsr_reset_l[] = {
+ "phy_b_nocsr",
+};
+
+static const struct qmp_pcie_offsets glymur_pcie_offsets_v8_50 = {
+ .pcs = 0x9000,
+};
+
+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),
+ .vreg_list = glymur_vreg_l,
+ .num_vregs = ARRAY_SIZE(glymur_vreg_l),
+ .regs = pciephy_v8_50_regs_layout,
+ .phy_status = PHYSTATUS_4_20,
+ .pipe_clk_list = glymur_pciephy_pipeclk_l,
+ .num_pipe_clks = ARRAY_SIZE(glymur_pciephy_pipeclk_l),
+ .clk_list = glymur_pciephy_a_clk_l,
+ .num_clks = ARRAY_SIZE(glymur_pciephy_a_clk_l),
+};
+
+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),
+ .vreg_list = glymur_vreg_l,
+ .num_vregs = ARRAY_SIZE(glymur_vreg_l),
+ .regs = pciephy_v8_50_regs_layout,
+ .phy_status = PHYSTATUS_4_20,
+ .pipe_clk_list = glymur_pipephy_b_pipeclk_l,
+ .num_pipe_clks = ARRAY_SIZE(glymur_pipephy_b_pipeclk_l),
+ .clk_list = glymur_pciephy_b_clk_l,
+ .num_clks = ARRAY_SIZE(glymur_pciephy_b_clk_l),
+};
+
+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),
+ .nocsr_reset_list = glymur_pciephy_nocsr_reset_l,
+ .num_nocsr_resets = ARRAY_SIZE(glymur_pciephy_nocsr_reset_l),
+ .vreg_list = glymur_vreg_l,
+ .num_vregs = ARRAY_SIZE(glymur_vreg_l),
+ .regs = pciephy_v8_50_regs_layout,
+ .phy_status = PHYSTATUS_4_20,
+ .pipe_clk_list = glymur_pciephy_pipeclk_l,
+ .num_pipe_clks = ARRAY_SIZE(glymur_pciephy_pipeclk_l),
+ .clk_list = glymur_pciephy_clk_l,
+ .num_clks = ARRAY_SIZE(glymur_pciephy_clk_l),
+};
+
+static const struct qmp_phy_cfg * const glymur_qmp_gen5x8_mode_x8_cfgs[] = {
+ &glymur_qmp_gen5x8_pciephy_cfg,
+};
+
+static const struct qmp_phy_cfg * const glymur_qmp_gen5x8_mode_x4x4_cfgs[] = {
+ &glymur_qmp_gen5x4_pciephy_a_cfg,
+ &glymur_qmp_gen5x4_pciephy_b_cfg,
+};
+
+static const struct qmp_pcie_link_mode_cfg glymur_qmp_gen5x8_mode_cfgs[] = {
+ [QMP_PCIE_GLYMUR_MODE_X8] = {
+ .cfgs = glymur_qmp_gen5x8_mode_x8_cfgs,
+ .num_phys = ARRAY_SIZE(glymur_qmp_gen5x8_mode_x8_cfgs),
+ },
+ [QMP_PCIE_GLYMUR_MODE_X4X4] = {
+ .cfgs = glymur_qmp_gen5x8_mode_x4x4_cfgs,
+ .num_phys = ARRAY_SIZE(glymur_qmp_gen5x8_mode_x4x4_cfgs),
+ },
+};
+
+static const struct qmp_pcie_match_data glymur_qmp_gen5x8_match_data = {
+ .mode_cfgs = glymur_qmp_gen5x8_mode_cfgs,
+ .num_modes = ARRAY_SIZE(glymur_qmp_gen5x8_mode_cfgs),
+};
+
+static int qmp_pcie_pd_power_on(struct qmp_pcie *qmp)
+{
+ const struct qmp_phy_cfg *cfg = qmp->cfg;
+ int i, ret;
+
+ for (i = 0; i < cfg->num_pds; i++) {
+ ret = pm_runtime_get_sync(qmp->pd_devs[i]);
+ if (ret < 0) {
+ dev_err(qmp->dev, "failed to power on %s domain\n",
+ cfg->pd_names[i]);
+ goto err_power_off;
+ }
+ }
+
+ return 0;
+
+err_power_off:
+ while (--i >= 0)
+ pm_runtime_put(qmp->pd_devs[i]);
+
+ return ret;
+}
+
+static void qmp_pcie_pd_power_off(struct qmp_pcie *qmp)
+{
+ const struct qmp_phy_cfg *cfg = qmp->cfg;
+ int i;
+
+ for (i = cfg->num_pds - 1; i >= 0; i--)
+ pm_runtime_put(qmp->pd_devs[i]);
+}
+
+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;
+
+ ret = qmp_pcie_pd_power_on(qmp);
+ if (ret)
+ return ret;
+
+ ret = regulator_bulk_enable(cfg->num_vregs, qmp->vregs);
+ if (ret) {
+ dev_err(qmp->dev, "failed to enable regulators, err=%d\n", ret);
+ goto err_pd_power_off;
+ }
+
+ ret = reset_control_bulk_assert(qmp->cfg->num_nocsr_resets, qmp->nocsr_resets);
+ if (ret) {
+ dev_err(qmp->dev, "no-csr reset assert failed\n");
+ goto err_disable_regulators;
+ }
+
+ usleep_range(200, 300);
+
+ ret = clk_bulk_prepare_enable(qmp->cfg->num_clks, qmp->clks);
+ if (ret)
+ goto err_disable_regulators;
+
+ return 0;
+
+err_disable_regulators:
+ regulator_bulk_disable(cfg->num_vregs, qmp->vregs);
+err_pd_power_off:
+ qmp_pcie_pd_power_off(qmp);
+
+ return ret;
+}
+
+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);
+
+ clk_bulk_disable_unprepare(qmp->cfg->num_clks, qmp->clks);
+ regulator_bulk_disable(cfg->num_vregs, qmp->vregs);
+ qmp_pcie_pd_power_off(qmp);
+
+ return 0;
+}
+
+static int qmp_pcie_power_on(struct phy *phy)
+{
+ struct qmp_pcie *qmp = phy_get_drvdata(phy);
+ const struct qmp_phy_cfg *cfg = qmp->cfg;
+ const struct qmp_pcie_offsets *offs = cfg->offsets;
+ void __iomem *status;
+ unsigned int val;
+ int i, ret;
+
+ ret = clk_bulk_prepare_enable(qmp->cfg->num_pipe_clks, qmp->pipe_clks);
+ if (ret)
+ return ret;
+
+ ret = reset_control_bulk_deassert(qmp->cfg->num_nocsr_resets, qmp->nocsr_resets);
+ if (ret) {
+ dev_err(qmp->dev, "no-csr reset deassert failed\n");
+ goto err_disable_pipe_clk;
+ }
+
+ 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,
+ PHY_INIT_COMPLETE_TIMEOUT);
+ if (ret) {
+ dev_err(qmp->dev, "phy initialization timed-out (%s)\n",
+ cfg->reg_names[i]);
+ goto err_disable_pipe_clk;
+ }
+ }
+
+ return 0;
+
+err_disable_pipe_clk:
+ clk_bulk_disable_unprepare(qmp->cfg->num_pipe_clks, qmp->pipe_clks);
+
+ return ret;
+}
+
+
+static int qmp_pcie_power_off(struct phy *phy)
+{
+ struct qmp_pcie *qmp = phy_get_drvdata(phy);
+
+ clk_bulk_disable_unprepare(qmp->cfg->num_pipe_clks, qmp->pipe_clks);
+
+ return 0;
+}
+
+static int qmp_pcie_enable(struct phy *phy)
+{
+ int ret;
+
+ ret = qmp_pcie_init(phy);
+ if (ret)
+ return ret;
+
+ ret = qmp_pcie_power_on(phy);
+ if (ret)
+ qmp_pcie_exit(phy);
+
+ return ret;
+}
+
+static int qmp_pcie_disable(struct phy *phy)
+{
+ int ret;
+
+ ret = qmp_pcie_power_off(phy);
+ if (ret)
+ return ret;
+
+ return qmp_pcie_exit(phy);
+}
+
+static const struct phy_ops qmp_pcie_phy_ops = {
+ .power_on = qmp_pcie_enable,
+ .power_off = qmp_pcie_disable,
+ .owner = THIS_MODULE,
+};
+
+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++) {
+ 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)
+{
+ const struct qmp_phy_cfg *cfg = qmp->cfg;
+ struct device *dev = qmp->dev;
+ int i, ret;
+
+ if (!cfg->num_pds)
+ return 0;
+
+ qmp->pd_devs = devm_kcalloc(dev, cfg->num_pds, sizeof(*qmp->pd_devs),
+ GFP_KERNEL);
+ if (!qmp->pd_devs)
+ return -ENOMEM;
+
+ 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(qmp->pd_devs[i])) {
+ ret = PTR_ERR(qmp->pd_devs[i]);
+ goto err_detach;
+ }
+ }
+
+ 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;
+}
+
+static int qmp_pcie_vreg_init(struct qmp_pcie *qmp)
+{
+ const struct qmp_phy_cfg *cfg = qmp->cfg;
+ struct device *dev = qmp->dev;
+ int i;
+
+ qmp->vregs = devm_kcalloc(dev, cfg->num_vregs, sizeof(*qmp->vregs),
+ GFP_KERNEL);
+ if (!qmp->vregs)
+ return -ENOMEM;
+
+ for (i = 0; i < cfg->num_vregs; i++)
+ qmp->vregs[i].supply = cfg->vreg_list[i];
+
+ return devm_regulator_bulk_get(dev, cfg->num_vregs, qmp->vregs);
+}
+
+static int qmp_pcie_reset_init(struct qmp_pcie *qmp)
+{
+ const struct qmp_phy_cfg *cfg = qmp->cfg;
+ 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;
+
+ 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;
+}
+
+static int qmp_pcie_clk_init(struct qmp_pcie *qmp)
+{
+ const struct qmp_phy_cfg *cfg = qmp->cfg;
+ struct device *dev = qmp->dev;
+ int i, ret;
+
+ qmp->clks = devm_kcalloc(dev, cfg->num_clks, sizeof(*qmp->clks),
+ GFP_KERNEL);
+ if (!qmp->clks)
+ return -ENOMEM;
+
+ for (i = 0; i < cfg->num_clks; i++)
+ qmp->clks[i].id = cfg->clk_list[i];
+
+ ret = devm_clk_bulk_get_optional(dev, cfg->num_clks, qmp->clks);
+ if (ret)
+ return ret;
+
+ qmp->pipe_clks = devm_kcalloc(dev, cfg->num_pipe_clks,
+ sizeof(*qmp->pipe_clks), GFP_KERNEL);
+ if (!qmp->pipe_clks)
+ return -ENOMEM;
+
+ for (i = 0; i < cfg->num_pipe_clks; i++)
+ qmp->pipe_clks[i].id = cfg->pipe_clk_list[i];
+
+ return devm_clk_bulk_get_optional(dev, cfg->num_pipe_clks,
+ qmp->pipe_clks);
+}
+
+static int __phy_pipe_clk_register(struct device *dev, struct device_node *np,
+ int idx, struct clk_fixed_rate *fixed)
+{
+ struct clk_init_data init = { };
+ int ret;
+
+ ret = of_property_read_string_index(np, "clock-output-names", idx,
+ &init.name);
+ if (ret) {
+ dev_err(dev, "%pOFn: No clock-output-names\n", np);
+ return ret;
+ }
+
+ init.ops = &clk_fixed_rate_ops;
+
+ if (!fixed->fixed_rate)
+ fixed->fixed_rate = 125000000;
+
+ fixed->hw.init = &init;
+
+ return devm_clk_hw_register(dev, &fixed->hw);
+}
+
+static struct clk_hw *qmp_pcie_multiphy_clk_hw_get(struct of_phandle_args *clkspec,
+ void *data)
+{
+ struct qmp_pcie_multiphy *qmp_data = data;
+ unsigned int idx = 0;
+
+ if (clkspec->args_count)
+ idx = clkspec->args[0];
+
+ if (idx < (unsigned int)qmp_data->num_pipe_outputs)
+ return &qmp_data->pipe_out_clks[idx].hw;
+
+ return ERR_PTR(-EINVAL);
+}
+
+static int qmp_pcie_multiphy_register_clocks(struct device *dev,
+ struct device_node *np,
+ struct qmp_pcie_multiphy *qmp_data)
+{
+ int num_pipe_outputs;
+ int i, ret;
+
+ num_pipe_outputs = of_property_count_strings(np, "clock-output-names");
+ if (num_pipe_outputs < 0)
+ num_pipe_outputs = 1;
+
+ qmp_data->num_pipe_outputs = num_pipe_outputs;
+ qmp_data->pipe_out_clks = devm_kcalloc(dev, num_pipe_outputs,
+ sizeof(*qmp_data->pipe_out_clks),
+ GFP_KERNEL);
+ if (!qmp_data->pipe_out_clks)
+ return -ENOMEM;
+
+ for (i = 0; i < num_pipe_outputs; i++) {
+ ret = __phy_pipe_clk_register(dev, np, i,
+ &qmp_data->pipe_out_clks[i]);
+ if (ret)
+ return ret;
+ }
+
+ return devm_of_clk_add_hw_provider(dev, qmp_pcie_multiphy_clk_hw_get, qmp_data);
+}
+
+static int qmp_pcie_get_mmio(struct qmp_pcie *qmp)
+{
+ struct platform_device *pdev = to_platform_device(qmp->dev);
+ const struct qmp_phy_cfg *cfg = qmp->cfg;
+ struct device *dev = qmp->dev;
+ void __iomem *base;
+ int i;
+
+ qmp->base = devm_kcalloc(dev, cfg->num_regs, sizeof(*qmp->base),
+ GFP_KERNEL);
+ if (!qmp->base)
+ return -ENOMEM;
+
+ for (i = 0; i < cfg->num_regs; i++) {
+ base = devm_platform_ioremap_resource_byname(pdev, cfg->reg_names[i]);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ qmp->base[i] = base;
+ }
+
+ return 0;
+}
+
+static int qmp_pcie_read_link_mode(struct device *dev, unsigned int *link_mode)
+{
+ struct regmap *map;
+ unsigned int args[1];
+ int ret;
+
+ map = syscon_regmap_lookup_by_phandle_args(dev->of_node, "qcom,link-mode",
+ ARRAY_SIZE(args), args);
+ if (IS_ERR(map))
+ return PTR_ERR(map);
+
+ ret = regmap_read(map, args[0], link_mode);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static struct phy *qmp_pcie_multiphy_xlate(struct device *dev,
+ const struct of_phandle_args *args)
+{
+ struct qmp_pcie_multiphy *qmp_data = dev_get_drvdata(dev);
+ unsigned int idx;
+
+ if (!qmp_data || args->args_count < 1)
+ return ERR_PTR(-EINVAL);
+
+ idx = args->args[0];
+
+ if (idx < (unsigned int)qmp_data->mode_cfg->num_phys)
+ return qmp_data->phys[idx] ?: ERR_PTR(-EINVAL);
+
+ return ERR_PTR(-EINVAL);
+}
+
+static int qmp_pcie_probe_phy(struct qmp_pcie *qmp, struct device_node *np,
+ struct phy **out_phy)
+{
+ int ret;
+
+ ret = qmp_pcie_get_mmio(qmp);
+ if (ret)
+ return ret;
+
+ ret = qmp_pcie_clk_init(qmp);
+ if (ret)
+ return ret;
+
+ ret = qmp_pcie_reset_init(qmp);
+ if (ret)
+ return ret;
+
+ ret = qmp_pcie_vreg_init(qmp);
+ if (ret)
+ return ret;
+
+ ret = qmp_pcie_pd_init(qmp);
+ 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);
+
+ return 0;
+}
+
+
+static int qmp_pcie_multiphy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct phy_provider *phy_provider;
+ struct qmp_pcie_multiphy *qmp_data;
+ const struct qmp_pcie_match_data *match_data;
+ struct qmp_pcie *qmp;
+ struct phy **phys;
+ unsigned int link_mode;
+ int phy_index;
+ int ret;
+
+ qmp_data = devm_kzalloc(dev, sizeof(*qmp_data), GFP_KERNEL);
+
+ 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");
+
+ if (link_mode >= match_data->num_modes)
+ return dev_err_probe(dev, -EINVAL, "invalid qcom,link-mode: %u\n",
+ link_mode);
+
+ qmp_data->mode_cfg = &match_data->mode_cfgs[link_mode];
+
+ qmp = devm_kcalloc(dev, qmp_data->mode_cfg->num_phys, sizeof(*qmp), GFP_KERNEL);
+ if (!qmp)
+ return -ENOMEM;
+
+ phys = devm_kcalloc(dev, qmp_data->mode_cfg->num_phys, sizeof(*phys), GFP_KERNEL);
+ if (!phys)
+ return -ENOMEM;
+
+ qmp_data->phys = phys;
+ dev_set_drvdata(dev, qmp_data);
+
+ for (phy_index = 0; phy_index < qmp_data->mode_cfg->num_phys; phy_index++) {
+ qmp[phy_index].dev = dev;
+ qmp[phy_index].cfg = qmp_data->mode_cfg->cfgs[phy_index];
+ ret = qmp_pcie_probe_phy(&qmp[phy_index], dev->of_node, &phys[phy_index]);
+ if (ret)
+ return ret;
+ }
+
+ ret = qmp_pcie_multiphy_register_clocks(dev, dev->of_node, qmp_data);
+ if (ret)
+ return ret;
+
+ phy_provider = devm_of_phy_provider_register(dev, qmp_pcie_multiphy_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+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,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, qmp_pcie_multiphy_of_match_table);
+
+static struct platform_driver qmp_pcie_multiphy_driver = {
+ .probe = qmp_pcie_multiphy_probe,
+ .driver = {
+ .name = "qcom-qmp-pcie-multiphy",
+ .of_match_table = qmp_pcie_multiphy_of_match_table,
+ },
+};
+module_platform_driver(qmp_pcie_multiphy_driver);
+
+MODULE_AUTHOR("Qiang Yu <qiang.yu@oss.qualcomm.com>");
+MODULE_DESCRIPTION("Qualcomm QMP PCIe PHY driver for Glymur");
+MODULE_LICENSE("GPL");
--
2.34.1
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* [PATCH v5 0/3] Add QMP PCIe multiple link-mode PHY support
From: Qiang Yu @ 2026-07-17 9:58 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Manivannan Sadhasivam, Philipp Zabel,
Bjorn Andersson, Konrad Dybcio
Cc: Qiang Yu, linux-arm-msm, linux-phy, devicetree, linux-kernel
Hi all,
Some QMP PCIe PHY hardware blocks can be configured through a link-mode
register into different link topologies, such as a single wide link, or
multiple independent narrower links (e.g. x8 or x4+x4 mode on the Glymur
PCIe3 PHY).
Earlier revisions tried to extend the existing single-instance
phy-qcom-qmp-pcie.c driver to cover this hardware. That added a large
amount of conditional, multi-PHY logic to a driver whose data model
assumes one PHY per node.
This series adds a dedicated PHY provider driver,
phy-qcom-qmp-pcie-multiphy.c:
A single PHY provider node describes the shared hardware block. A new
"qcom,link-mode" property points at the TCSR syscon register that reports
the active topology; the driver reads it once at probe. #phy-cells = <1>
lets consumers pass a logical PHY index to obtain their sub-PHY. Match
data is indexed by link mode, and each link mode has its own array of
per-PHY config tables, so one shared provider exposes a different set of
logical PHYs depending on the active mode. The driver inherits the PHY
settings and link mode already programmed by UEFI, so only the no-CSR
reset is used, and no PHY setting tables or related structures are
defined. Each sub-PHY owns its register regions and power domain, so in a
bifurcated mode the links are brought up, and powered independently.
The driver is implemented and validated on Glymur, and is intended to
be extensible to other multi-mode QMP PCIe PHYs.
This series depends on a prerequisite patch by Krzysztof Kozlowski:
https://lore.kernel.org/r/20260420133616.88740-2-krzysztof.kozlowski@oss.qualcomm.com
Thanks,
Qiang
Signed-off-by: Qiang Yu <qiang.yu@oss.qualcomm.com>
---
Changes in v5:
- Split the Glymur multi PHY binding out of the shared qcom,sc8280xp-qmp-pcie-phy.yaml into its own schema.
- Reworked phy-qcom-qmp-pcie-multiphy.c into a standalone driver instead of extending phy-qcom-qmp-pcie.c with conditional multi-PHYlogic.
- Dropped the "link mode value" cell from qcom,link-mode.
- Removed the QMP_PHY_SELECTOR_0/1 dt-bindings macros.
- Link to v4: https://lore.kernel.org/all/20260518-link_mode_0519-v4-0-269cd73cc5d1@oss.qualcomm.com/
Changes in v4:
- Replaced the static bifurcation probe model with a link-mode
architecture: the active topology (x8 or x4+x4) is selected via a
new "qcom,link-mode" DT property and written to a TCSR register at
power-on. This replaces v3 patches 4 and 5 with four new patches
(driver refactor, secondary PHY clock/reset lists, link-mode probe
infrastructure, and Glymur config).
- Updated dt-bindings to describe "qcom,link-mode", #phy-cells = <1>,
per-mode validation rules, and added a new header with
QMP_PHY_SELECTOR_* and QMP_PCIE_GLYMUR_MODE_* macros.
- Patches 2 and 3 (multiple power-domains, multiple nocsr resets) are
unchanged from v3.
- Link to v3: https://lore.kernel.org/r/20260412-glymur_gen5x8_phy_0413-v3-0-affcebc16b8b@oss.qualcomm.com
Changes in v3:
- Add description of each power-domain.
- Add 64bit prefetchable memory range required by some EPs eg. AI100 ultra.
- Move PCIe3a after PCIe3b and move PCIe3a PHY before PCIe3b PHY.
- Link to v2: https://lore.kernel.org/all/20260323-glymur_gen5x8_phy_0323-v2-0-ce0fc07f0e52@oss.qualcomm.com/
Changes in v2:
- Remove pd_list from qmp_pcie struct as it is not used in phy driver.
- align clk-names on "
- Link to v1: https://lore.kernel.org/all/20260304-glymur_gen5x8_phy-v1-0-849e9a72e125@oss.qualcomm.com/
To: Vinod Koul <vkoul@kernel.org>
To: Neil Armstrong <neil.armstrong@linaro.org>
To: Rob Herring <robh@kernel.org>
To: Krzysztof Kozlowski <krzk+dt@kernel.org>
To: Conor Dooley <conor+dt@kernel.org>
To: Qiang Yu <qiang.yu@oss.qualcomm.com>
To: Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>
To: Philipp Zabel <p.zabel@pengutronix.de>
To: Bjorn Andersson <andersson@kernel.org>
To: Konrad Dybcio <konradybcio@kernel.org>
Cc: linux-arm-msm@vger.kernel.org
Cc: linux-phy@lists.infradead.org
Cc: devicetree@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
---
Qiang Yu (3):
dt-bindings: phy: qcom: Add Glymur QMP PCIe multiple link-mode PHY
phy: qcom: qmp-pcie: Add QMP PCIe Multi-PHY driver
arm64: dts: qcom: glymur: Wire PCIe3a/3b to shared Gen5x8 PHY
.../phy/qcom,glymur-qmp-pcie-multiphy.yaml | 176 +++++
arch/arm64/boot/dts/qcom/glymur-crd.dtsi | 8 +
arch/arm64/boot/dts/qcom/glymur.dtsi | 336 ++++++++-
drivers/phy/qualcomm/Kconfig | 11 +
drivers/phy/qualcomm/Makefile | 1 +
drivers/phy/qualcomm/phy-qcom-qmp-pcie-multiphy.c | 770 +++++++++++++++++++++
6 files changed, 1300 insertions(+), 2 deletions(-)
---
base-commit: 59ccadbf59aedee486fce5b4a5486a07c3f4b5d3
change-id: 20260716-glymur_linkmode_0717-70eacf08a5c5
Best regards,
--
Qiang Yu <qiang.yu@oss.qualcomm.com>
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* [PATCH v5 1/3] dt-bindings: phy: qcom: Add Glymur QMP PCIe multiple link-mode PHY
From: Qiang Yu @ 2026-07-17 9:58 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Manivannan Sadhasivam, Philipp Zabel,
Bjorn Andersson, Konrad Dybcio
Cc: Qiang Yu, linux-arm-msm, linux-phy, devicetree, linux-kernel
In-Reply-To: <20260717-glymur_linkmode_0717-v5-0-4f9e87a61463@oss.qualcomm.com>
Add qcom,glymur-qmp-pcie-multiphy.yaml as a standalone binding
for the Glymur Gen5 PCIe PHY hardware block. This block supports two
link modes, selected at runtime via a TCSR syscon register:
1. x8 - a single 8-lane PHY instance is exposed
2. x4+x4 - two independent 4-lane PHY instances are exposed
Keep this as a separate schema from qcom,sc8280xp-qmp-pcie-phy.yaml
rather than folding it into the shared compatible list there, since the
two PHY instances active in x8 mode require twice as many clocks,
resets, and power-domains as any other entry in that file, and adding
Glymur-specific properties like qcom,link-mode and reg-names there
would only apply to this one compatible.
Document the required clocks, resets, and power-domains for both PHY
instances, and use #phy-cells = <1>, where the cell value is the PHY
index within the active link mode.
Signed-off-by: Qiang Yu <qiang.yu@oss.qualcomm.com>
---
.../phy/qcom,glymur-qmp-pcie-multiphy.yaml | 176 +++++++++++++++++++++
1 file changed, 176 insertions(+)
diff --git a/Documentation/devicetree/bindings/phy/qcom,glymur-qmp-pcie-multiphy.yaml b/Documentation/devicetree/bindings/phy/qcom,glymur-qmp-pcie-multiphy.yaml
new file mode 100644
index 000000000000..73550f77e9b6
--- /dev/null
+++ b/Documentation/devicetree/bindings/phy/qcom,glymur-qmp-pcie-multiphy.yaml
@@ -0,0 +1,176 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/phy/qcom,glymur-qmp-pcie-multiphy.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm QMP PCIe multiple link-mode PHY controller (PCIe, Glymur)
+
+maintainers:
+ - Qiang Yu <qiang.yu@oss.qualcomm.com>
+ - Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>
+
+description: |
+ The Glymur SoC uses a single PCIe Gen5 PHY hardware block for the
+ PCIe3a/PCIe3b controllers. This block supports two link modes, selected
+ at runtime via a TCSR syscon register:
+
+ 1. x8 - a single 8-lane PHY instance is exposed (PCIe3a only)
+ 2. x4+x4 - two independent 4-lane PHY instances are exposed (PCIe3a and
+ PCIe3b)
+
+ The node always describes both PHY instances ("port a" and "port b"),
+ regardless of which link mode is active on the board.
+
+properties:
+ compatible:
+ enum:
+ - qcom,glymur-qmp-gen5x8-pcie-phy
+
+ reg:
+ minItems: 2
+ maxItems: 2
+
+ reg-names:
+ items:
+ - const: port_a
+ - const: port_b
+
+ clocks:
+ minItems: 10
+ maxItems: 10
+
+ clock-names:
+ items:
+ - const: aux
+ - const: cfg_ahb
+ - const: ref
+ - const: rchng
+ - const: pipe
+ - const: phy_b_aux
+ - const: cfg_ahb_b
+ - const: rchng_b
+ - const: pipe_b
+ - const: pipediv2_b
+
+ power-domains:
+ minItems: 2
+ maxItems: 2
+
+ power-domain-names:
+ items:
+ - const: phy_a_gdsc
+ - const: phy_b_gdsc
+
+ resets:
+ minItems: 4
+ maxItems: 4
+
+ reset-names:
+ items:
+ - const: phy_a
+ - const: phy_a_nocsr
+ - const: phy_b
+ - const: phy_b_nocsr
+
+ vdda-phy-supply: true
+
+ vdda-pll-supply: true
+
+ vdda-refgen0p9-supply: true
+
+ vdda-refgen1p2-supply: true
+
+ qcom,link-mode:
+ description:
+ Reference to a register in the TCSR syscon that reports the link
+ mode the PCIe PHY is currently configured for, either a single x8
+ link or two independent x4 links. The link mode is programmed by
+ firmware before Linux boots; this property is only used to read
+ the active link mode, specified as a phandle to the syscon and
+ the register offset.
+ $ref: /schemas/types.yaml#/definitions/phandle-array
+ items:
+ - items:
+ - description: phandle of TCSR syscon
+ - description: offset of link mode register
+
+ "#clock-cells":
+ const: 1
+
+ clock-output-names:
+ items:
+ - description: Name of the PHY A pipe clock output.
+ - description: Name of the PHY B pipe clock output.
+
+ "#phy-cells":
+ const: 1
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - clocks
+ - clock-names
+ - power-domains
+ - power-domain-names
+ - resets
+ - reset-names
+ - vdda-phy-supply
+ - vdda-pll-supply
+ - vdda-refgen0p9-supply
+ - vdda-refgen1p2-supply
+ - qcom,link-mode
+ - "#clock-cells"
+ - clock-output-names
+ - "#phy-cells"
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/qcom,glymur-gcc.h>
+ #include <dt-bindings/clock/qcom,glymur-tcsr.h>
+
+ pcie3_phy: phy@f00000 {
+ compatible = "qcom,glymur-qmp-gen5x8-pcie-phy";
+ reg = <0x00f00000 0x10000>, <0x00f10000 0x10000>;
+ reg-names = "port_a", "port_b";
+
+ clocks = <&gcc GCC_PCIE_PHY_3A_AUX_CLK>,
+ <&gcc GCC_PCIE_3A_CFG_AHB_CLK>,
+ <&tcsr TCSR_PCIE_3_CLKREF_EN>,
+ <&gcc GCC_PCIE_3A_PHY_RCHNG_CLK>,
+ <&gcc GCC_PCIE_3A_PIPE_CLK>,
+ <&gcc GCC_PCIE_PHY_3B_AUX_CLK>,
+ <&gcc GCC_PCIE_3B_CFG_AHB_CLK>,
+ <&gcc GCC_PCIE_3B_PHY_RCHNG_CLK>,
+ <&gcc GCC_PCIE_3B_PIPE_CLK>,
+ <&gcc GCC_PCIE_3B_PIPE_DIV2_CLK>;
+ clock-names = "aux", "cfg_ahb", "ref", "rchng", "pipe",
+ "phy_b_aux", "cfg_ahb_b", "rchng_b", "pipe_b",
+ "pipediv2_b";
+
+ resets = <&gcc GCC_PCIE_3A_PHY_BCR>,
+ <&gcc GCC_PCIE_3A_NOCSR_COM_PHY_BCR>,
+ <&gcc GCC_PCIE_3B_PHY_BCR>,
+ <&gcc GCC_PCIE_3B_NOCSR_COM_PHY_BCR>;
+ reset-names = "phy_a", "phy_a_nocsr", "phy_b", "phy_b_nocsr";
+
+ power-domains = <&gcc GCC_PCIE_3A_PHY_GDSC>,
+ <&gcc GCC_PCIE_3B_PHY_GDSC>;
+ power-domain-names = "phy_a_gdsc", "phy_b_gdsc";
+
+ vdda-phy-supply = <&vreg_l3c>;
+ vdda-pll-supply = <&vreg_l2c>;
+
+ vdda-refgen0p9-supply = <&vreg_l3c>;
+ vdda-refgen1p2-supply = <&vreg_l2c>;
+
+ qcom,link-mode = <&tcsr 0x5000>;
+
+ #clock-cells = <1>;
+ clock-output-names = "pcie3a_pipe_clk", "pcie3b_pipe_clk";
+
+ #phy-cells = <1>;
+ };
--
2.34.1
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* Re: [PATCH v7 04/13] dt-bindings: clk: zte: Add zx297520v3 LSP clock and reset controller
From: Krzysztof Kozlowski @ 2026-07-17 8:23 UTC (permalink / raw)
To: Stefan Dösinger
Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Brian Masney, Vinod Koul,
Neil Armstrong, Russell King, linux-clk, devicetree, linux-kernel,
linux-arm-kernel, linux-phy
In-Reply-To: <20260717-zx29clk-v7-4-408411cfcf36@gmail.com>
On Fri, Jul 17, 2026 at 12:35:40AM +0300, Stefan Dösinger wrote:
> The clock controller of the zx297520v3 Low Speed Peripherals is
> relatively clean. One register per device with gates, muxes and resets
> and for some devices a divider. There are even bits in the matrix
> controller to control propagation of clock lines down to LSP.
>
> The clocks are sorted by register address and I am convinced that the
> device list is complete. There are however a few more registers that are
> likely extra dividers for TDM and I2S devices
>
> Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
>
> ---
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Best regards,
Krzysztof
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH v7 02/13] dt-bindings: clk: zte: Add zx297520v3 top clock and reset controller
From: Krzysztof Kozlowski @ 2026-07-17 8:15 UTC (permalink / raw)
To: Stefan Dösinger
Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Brian Masney, Vinod Koul,
Neil Armstrong, Russell King, linux-clk, devicetree, linux-kernel,
linux-arm-kernel, linux-phy
In-Reply-To: <20260717-berserk-faithful-cat-a34ad8@quoll>
On 17/07/2026 08:36, Krzysztof Kozlowski wrote:
> On Fri, Jul 17, 2026 at 12:35:38AM +0300, Stefan Dösinger wrote:
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index cb50c2e3b4ea..5f962b9947ab 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -3878,10 +3878,13 @@ L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
>> S: Odd fixes
>> F: Documentation/arch/arm/zte/
>> F: Documentation/devicetree/bindings/arm/zte.yaml
>> +F: Documentation/devicetree/bindings/clock/zte,zx297520v3-topcrm.yaml
>> F: Documentation/devicetree/bindings/phy/zte,zx297520v3-usb-phy.yaml
>> F: arch/arm/boot/dts/zte/
>> F: arch/arm/mach-zte/
>> +F: include/dt-bindings/clock/zte,zx297520v3-clk.h
>> F: include/dt-bindings/phy/phy-zte-zx297520v3-usb.h
>> +F: include/dt-bindings/reset/zte,zx297520v3-reset.h
>>
>> ARM/ZYNQ ARCHITECTURE
>> M: Michal Simek <michal.simek@amd.com>
>> diff --git a/include/dt-bindings/clock/zte,zx297520v3-clk.h b/include/dt-bindings/clock/zte,zx297520v3-clk.h
>
> Same mistake. Filename MUST match the compatible.
I did not notice that you combine here all the clock IDs from other
clock controllers. The header filename is therefore fine (also reset fine).
Best regards,
Krzysztof
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH v3 4/5] drm/rockchip: cdn-dp: Support handle lane info without extcon
From: sashiko-bot @ 2026-07-17 7:56 UTC (permalink / raw)
To: Chaoyi Chen; +Cc: olteanv, linux-phy, vkoul, dri-devel, neil.armstrong
In-Reply-To: <20260717072323.96-5-kernel@airkyi.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Incorrect error checking logic in `cdn_dp_probe` skips port initialization for valid extcon devices, breaking DisplayPort for legacy boards.
- [High] Removing the `extcon` notifier without providing an alternative mechanism completely breaks hotplug detection for Type-C connections without an extcon device.
--
--- Patch [4]: [PATCH v3 4/5] drm/rockchip: cdn-dp: Support handle lane info without extcon ---
commit 2789d7ccb0bb45f82d439538cf9aeaac1da9c5e9
Author: Chaoyi Chen <chaoyi.chen@rock-chips.com>
drm/rockchip: cdn-dp: Support handle lane info without extcon
This patch adds support for getting PHY lane info without the help of extcon.
There is no extcon needed if the Type-C controller is present. The extcon
device should still be supported if the Type-C controller is not present.
> diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c
> index 190cce9530c82..74e1f5422a6e2 100644
> --- a/drivers/gpu/drm/rockchip/cdn-dp-core.c
> +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c
[ ... ]
> @@ -1027,6 +1033,9 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data)
> for (i = 0; i < dp->ports; i++) {
> port = dp->port[i];
>
> + if (!port->extcon)
> + continue;
> +
[Severity: High]
Does skipping the notifier registration when extcon is absent leave the driver
without any hotplug detection mechanism?
The driver relies on the cdn_dp_pd_event notifier (registered via extcon) to
schedule dp->event_work, which probes the hardware, updates the connection
state, and calls drm_bridge_hpd_notify().
Without an alternative HPD mechanism when extcon is absent, won't hotplug
events be silently ignored, causing displays to only be detected if plugged in
during boot or resume?
> port->event_nb.notifier_call = cdn_dp_pd_event;
> ret = devm_extcon_register_notifier(dp->dev, port->extcon,
> EXTCON_DISP_DP,
[ ... ]
> @@ -1119,14 +1128,14 @@ static int cdn_dp_probe(struct platform_device *pdev)
> PTR_ERR(phy) == -EPROBE_DEFER)
> return -EPROBE_DEFER;
>
> - if (IS_ERR(extcon) || IS_ERR(phy))
> + if (IS_ERR(phy) || PTR_ERR(extcon) != -ENODEV)
> continue;
[Severity: High]
Does evaluating PTR_ERR(extcon) != -ENODEV without first checking if extcon
is an error pointer break initialization for valid extcon devices?
When a valid extcon device is successfully found, extcon is a valid kernel
pointer. Casting this pointer to long via PTR_ERR() yields a memory address
that is not equal to -ENODEV.
Since the condition incorrectly evaluates to true, won't the loop continue
and skip port allocation for all valid extcon devices, breaking DisplayPort
on existing platforms that use extcon?
>
> port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
> if (!port)
> return -ENOMEM;
>
> - port->extcon = extcon;
> + port->extcon = IS_ERR(extcon) ? NULL : extcon;
> port->phy = phy;
> port->dp = dp;
> port->id = i;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260717072323.96-1-kernel@airkyi.com?part=4
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH v3 3/5] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge
From: sashiko-bot @ 2026-07-17 7:39 UTC (permalink / raw)
To: Chaoyi Chen; +Cc: olteanv, linux-phy, dri-devel, vkoul, neil.armstrong
In-Reply-To: <20260717072323.96-4-kernel@airkyi.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Double-free and reference count underflow of the `child_np` device tree node reference due to conflicting ownership with `drm_aux_bridge_register_from_node()`.
--
--- Patch [3]: [PATCH v3 3/5] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge ---
commit 7121c384753e095d5a040f2bd93021b7ae8110d7
Author: Chaoyi Chen <chaoyi.chen@rock-chips.com>
phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge
Using the DRM_AUX_BRIDGE helper to create the transparent DRM bridge
device.
> diff --git a/drivers/phy/rockchip/phy-rockchip-typec.c b/drivers/phy/rockchip/phy-rockchip-typec.c
> index d9701b6106d55..48070b50416e5 100644
> --- a/drivers/phy/rockchip/phy-rockchip-typec.c
> +++ b/drivers/phy/rockchip/phy-rockchip-typec.c
> @@ -1162,16 +1163,24 @@ static int rockchip_typec_phy_probe(struct platform_device *pdev)
>
> for_each_available_child_of_node(np, child_np) {
> struct phy *phy;
> + ret = 0;
>
> - if (of_node_name_eq(child_np, "dp-port"))
> + if (of_node_name_eq(child_np, "dp-port")) {
> phy = devm_phy_create(dev, child_np,
> &rockchip_dp_phy_ops);
> - else if (of_node_name_eq(child_np, "usb3-port"))
> + ret = drm_aux_bridge_register_from_node(dev, child_np);
> + } else if (of_node_name_eq(child_np, "usb3-port"))
> phy = devm_phy_create(dev, child_np,
> &rockchip_usb3_phy_ops);
> else
> continue;
>
> + if (ret) {
> + pm_runtime_disable(dev);
> + of_node_put(child_np);
[Severity: High]
Does this cause a reference count underflow for the child_np device node?
drm_aux_bridge_register_from_node() assigns the node to an auxiliary device,
and its error and release paths in drm/bridge/aux-bridge.c unconditionally
call of_node_put() to drop the reference.
However, the for_each_available_child_of_node() iterator owns the reference
to child_np during this loop iteration. Passing it to
drm_aux_bridge_register_from_node() without acquiring an additional reference
via of_node_get() appears to hand off a reference the bridge doesn't own.
If drm_aux_bridge_register_from_node() succeeds, the loop advances and drops
the iterator's reference, leading to a double-put when the bridge is
eventually released.
If drm_aux_bridge_register_from_node() fails, it drops the reference
internally. The explicit of_node_put(child_np) here in the error block would
then cause an immediate double-put.
> + return ret;
> + }
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260717072323.96-1-kernel@airkyi.com?part=3
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH v3 5/5] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection
From: sashiko-bot @ 2026-07-17 7:38 UTC (permalink / raw)
To: Chaoyi Chen; +Cc: dri-devel, olteanv, vkoul, linux-phy, neil.armstrong
In-Reply-To: <20260717072323.96-6-kernel@airkyi.com>
Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
New issues:
- [High] Buffer overflow when parsing device tree endpoints due to missing bounds check.
- [High] Broken hotplug detection for legacy extcon configurations due to unconditional port return.
- [High] NULL pointer dereference and uninitialized memory read if DT endpoints exceed valid PHYs.
- [High] Port switching during EDID read always fails because `cdn_dp_config_video` is called with a zero clock.
- [High] Stale pointers in persistent arrays cause double-free and use-after-free on component re-bind.
- [High] Workqueue `dp->event_work` is scheduled before being initialized, leading to a NULL pointer dereference.
- [Medium] Multiple encoders share a single hardware DP controller but lack an `atomic_check` callback to prevent simultaneous activation.
Pre-existing issues:
- [High] Using `devm_` APIs in the component `bind` callback causes severe resource leaks and active stale notifiers after `unbind`.
--
--- Patch [5]: [PATCH v3 5/5] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection ---
commit a0d8b7a8a3a01d68ddf971ecfdb92ccb11c60b09
Author: Chaoyi Chen <chaoyi.chen@rock-chips.com>
drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection
This patch adds support for multiple bridges to allow selecting between
different PHY ports on the RK3399 CDN-DP controller. It registers a separate
encoder and bridge for each PHY port and parses the DT to handle bridge
chain configurations.
> diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c
> index 74e1f5422a6e2..5e513f6930cb9 100644
> --- a/drivers/gpu/drm/rockchip/cdn-dp-core.c
> +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c
[ ... ]
> @@ -192,14 +193,27 @@ static int cdn_dp_get_sink_count(struct cdn_dp_device *dp, u8 *sink_count)
> static struct cdn_dp_port *cdn_dp_connected_port(struct cdn_dp_device *dp)
> {
> struct cdn_dp_port *port;
> - int i, lanes;
> + int i, lanes[MAX_PHY];
>
> for (i = 0; i < dp->ports; i++) {
> port = dp->port[i];
> - lanes = cdn_dp_get_port_lanes(port);
> - if (lanes)
> + lanes[i] = cdn_dp_get_port_lanes(port);
> + if (!dp->next_bridge_valid)
> return port;
[Severity: High]
Does this break hotplug detection for legacy extcon configurations?
By removing the check for lanes > 0, this will unconditionally return the
first port regardless of actual connection status.
> }
> +
> + if (dp->next_bridge_valid) {
> + /* If more than one port is available, pick the last active port */
> + if (dp->active_port >= 0 && lanes[dp->active_port])
> + return dp->port[dp->active_port];
> +
> + /* If the last active port is not available, pick an available port in order */
> + for (i = 0; i < dp->bridge_count; i++) {
> + if (lanes[i])
> + return dp->port[i];
> + }
> + }
[Severity: High]
Could this lead to an uninitialized memory read and NULL pointer
dereference?
If the device tree provides more endpoints than probed PHYs,
dp->bridge_count will exceed dp->ports. This loop will access
uninitialized stack memory in lanes[i] and potentially return a NULL
dp->port[i], causing a crash later in cdn_dp_bridge_edid_read() when
checking port->phy_enabled.
[ ... ]
> @@ -551,6 +599,50 @@ static bool cdn_dp_check_link_status(struct cdn_dp_device *dp)
> +static int cdn_dp_switch_port(struct cdn_dp_device *dp, struct cdn_dp_port *prev_port,
> + struct cdn_dp_port *port)
> +{
[ ... ]
> + if (!cdn_dp_check_link_status(dp)) {
> + dev_info(dp->dev, "Connected with sink; re-train link\n");
> +
> + ret = cdn_dp_train_link(dp);
> + if (ret) {
> + dev_err(dp->dev, "Training link failed: %d\n", ret);
> + goto out;
> + }
> +
> + ret = cdn_dp_set_video_status(dp, CONTROL_VIDEO_IDLE);
> + if (ret) {
> + dev_err(dp->dev, "Failed to idle video %d\n", ret);
> + goto out;
> + }
> +
> + ret = cdn_dp_config_video(dp);
> + if (ret)
> + dev_err(dp->dev, "Failed to configure video: %d\n", ret);
> + }
[Severity: High]
Will this port switch fail consistently during EDID read operations?
When cdn_dp_bridge_edid_read() calls this function, dp->mode.clock is
typically 0. A zero clock will cause cdn_dp_config_video() to spin until
timeout and return -EINVAL, thus aborting the EDID read.
[ ... ]
> @@ -828,6 +927,17 @@ static int cdn_dp_audio_mute_stream(struct drm_bridge *bridge,
> static const struct drm_bridge_funcs cdn_dp_bridge_funcs = {
> .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
> .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
> .atomic_reset = drm_atomic_helper_bridge_reset,
> .detect = cdn_dp_bridge_detect,
> .edid_read = cdn_dp_bridge_edid_read,
> .atomic_enable = cdn_dp_bridge_atomic_enable,
> .atomic_disable = cdn_dp_bridge_atomic_disable,
> .mode_valid = cdn_dp_bridge_mode_valid,
> .mode_set = cdn_dp_bridge_mode_set,
> + .hpd_notify = cdn_dp_bridge_hpd_notify,
[Severity: Medium]
Should there be an atomic_check callback here to prevent simultaneous
activation of multiple CRTCs?
Since all registered encoders share the same possible_crtcs mask for a
single underlying DP controller, userspace could potentially enable
multiple displays at once, leading to hardware register corruption.
[ ... ]
> @@ -1030,6 +1142,101 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data)
> +static int cdn_dp_parse_next_bridge_dt(struct cdn_dp_device *dp)
> +{
[ ... ]
> + for_each_of_graph_port_endpoint(port, dp_ep) {
> + struct device_node *next_bridge_node __free(device_node) =
> + of_graph_get_remote_port_parent(dp_ep);
> +
> + bridge = of_drm_find_bridge(next_bridge_node);
> + if (!bridge) {
> + ret = -EPROBE_DEFER;
> + goto out;
> + }
> +
> + dp->next_bridge_valid = true;
> + dp->next_bridge_list[count] = drm_bridge_get(bridge);
> + count++;
> + }
[Severity: High]
Could this code overflow the dp->next_bridge_list[] array?
The count variable is incremented for each endpoint and used as an index,
but it is never checked against MAX_PHY.
[ ... ]
> @@ -1030,6 +1142,101 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data)
> + for (i = 0; i < dp->bridge_count; i++) {
> + dp_bridge = devm_drm_bridge_alloc(dev, struct cdn_dp_bridge, bridge,
> + &cdn_dp_bridge_funcs);
[Severity: High]
This is a pre-existing issue, but does using devm_ APIs inside a component
bind callback cause memory leaks and use-after-free crashes?
Component unbind does not release devm_ resources, meaning structures like
dp_bridge and the extcon notifiers remain active after unbind and will
leak on subsequent re-binds.
[ ... ]
> + for (i = 0; i < dp->bridge_count; i++) {
> + encoder = &dp->bridge_list[i]->encoder.encoder;
> + bridge = &dp->bridge_list[i]->bridge;
> + next_bridge = dp->next_bridge_list[i];
> + ret = cdn_bridge_add(dev, bridge, next_bridge, encoder);
> + if (ret)
> + goto out;
> + }
> +
> + INIT_WORK(&dp->event_work, cdn_dp_pd_event_work);
[Severity: High]
Can this sequence cause a NULL pointer dereference?
By registering the bridges via cdn_bridge_add() before calling INIT_WORK,
an early HPD event can trigger cdn_dp_bridge_hpd_notify() and attempt to
schedule the uninitialized dp->event_work queue.
[ ... ]
> +out:
> + for (i = 0; i < MAX_PHY; i++)
> + drm_bridge_put(dp->next_bridge_list[i]);
> +
> + return ret;
> }
>
> static void cdn_dp_unbind(struct device *dev, struct device *master, void *data)
> {
> struct cdn_dp_device *dp = dev_get_drvdata(dev);
> - struct drm_encoder *encoder = &dp->encoder.encoder;
> + struct drm_encoder *encoder;
> + int i;
>
> cancel_work_sync(&dp->event_work);
> - encoder->funcs->destroy(encoder);
> + for (i = 0; i < dp->bridge_count; i++) {
> + encoder = &dp->bridge_list[i]->encoder.encoder;
> + encoder->funcs->destroy(encoder);
> + drm_bridge_put(dp->next_bridge_list[i]);
> + }
> +
> + for (i = 0; i < MAX_PHY; i++)
> + drm_edid_free(dp->edid_cache[i]);
[Severity: High]
Does this leave dangling pointers in persistent arrays across bind/unbind
cycles?
Because dp is allocated in probe, freeing dp->next_bridge_list and
dp->edid_cache here without setting them to NULL can lead to double-frees
or use-after-free issues if the component is rebound.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260717072323.96-1-kernel@airkyi.com?part=5
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH v3 1/5] drm/bridge: Implement generic USB Type-C DP HPD bridge
From: sashiko-bot @ 2026-07-17 7:36 UTC (permalink / raw)
To: Chaoyi Chen; +Cc: vkoul, neil.armstrong, olteanv, dri-devel, linux-phy
In-Reply-To: <20260717072323.96-2-kernel@airkyi.com>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] The module registers a bus notifier to catch device additions but fails to iterate over already-registered devices, ignoring any Type-C ports present before the module was loaded.
- [High] The module attaches devm-managed bridges to a foreign USB-C controller device but fails to clean them up on module unload or device removal, leading to resource leaks and an execute-after-free kernel panic.
- [Medium] The return value of `bus_register_notifier()` is ignored.
--
--- Patch [1]: [PATCH v3 1/5] drm/bridge: Implement generic USB Type-C DP HPD bridge ---
commit 5c3c19cbb4491c572fdd386c797dfba9412af2b3
Author: Chaoyi Chen <chaoyi.chen@rock-chips.com>
drm/bridge: Implement generic USB Type-C DP HPD bridge
This patch implements a generic DP HPD bridge based on aux-hpd-bridge.c.
It monitors Type-C bus events and automatically creates an HPD bridge when
a Type-C port device containing the DP svid is registered.
> diff --git a/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c b/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c
> @@ -0,0 +1,54 @@
[ ... ]
> +static int drm_typec_bus_event(struct notifier_block *nb,
> + unsigned long action, void *data)
> +{
> + struct device *dev = (struct device *)data;
> + struct typec_altmode *alt = to_typec_altmode(dev);
> +
> + if (action != BUS_NOTIFY_ADD_DEVICE)
> + return NOTIFY_OK;
> +
> + /*
> + * alt->dev.parent->parent : USB-C controller device
> + * alt->dev.parent : USB-C connector device
> + */
> + if (is_typec_port_altmode(&alt->dev) && alt->svid == USB_TYPEC_DP_SID)
> + drm_dp_hpd_bridge_register(alt->dev.parent->parent,
> + to_of_node(alt->dev.parent->fwnode));
[Severity: High]
Will this leak devm resources on the controller device when a port is removed
and re-added, since BUS_NOTIFY_DEL_DEVICE is ignored?
Also, because the devm cleanup callbacks are tied to the controller device
rather than this module, could this cause an execute-after-free panic?
If this module and aux-hpd-bridge are unloaded, the devres callbacks on the
controller will point to unmapped memory when the controller is later unbound.
[ ... ]
> +static int __init drm_aux_hpd_typec_dp_bridge_module_init(void)
> +{
> + bus_register_notifier(&typec_bus, &drm_typec_event_nb);
[Severity: Medium]
Is it safe to ignore the return value of bus_register_notifier() here?
If the notifier registration fails, the module initialization will succeed but
remain non-functional.
[Severity: High]
Does this code miss existing Type-C DP ports that were probed before this
module was loaded?
bus_register_notifier() only triggers notifications for devices added after
registration. Since there is no bus_for_each_dev() iteration over existing
devices on the typec_bus after registering the notifier, pre-existing ports
might never get an HPD bridge created.
> +
> + return 0;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260717072323.96-1-kernel@airkyi.com?part=1
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH v3 2/5] drm/bridge: aux: Add drm_aux_bridge_register_from_node()
From: sashiko-bot @ 2026-07-17 7:33 UTC (permalink / raw)
To: Chaoyi Chen; +Cc: vkoul, neil.armstrong, dri-devel, linux-phy, olteanv
In-Reply-To: <20260717072323.96-3-kernel@airkyi.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Unbalanced `of_node_put()` in `drm_aux_bridge_register_from_node()` and `drm_aux_bridge_release()` causes a reference count underflow for the provided `device_node`.
--
--- Patch [2]: [PATCH v3 2/5] drm/bridge: aux: Add drm_aux_bridge_register_from_node() ---
commit b2b66972200bdc4b6541ca41f8ae44c08d81a964
Author: Chaoyi Chen <chaoyi.chen@rock-chips.com>
drm/bridge: aux: Add drm_aux_bridge_register_from_node()
The drm_aux_bridge_register() uses the device->of_node as the bridge->of_node.
This patch adds drm_aux_bridge_register_from_node() to allow specifying the
of_node corresponding to the bridge.
> diff --git a/drivers/gpu/drm/bridge/aux-bridge.c b/drivers/gpu/drm/bridge/aux-bridge.c
> index 1ed21a8713bfd..06a1466f49bd2 100644
> --- a/drivers/gpu/drm/bridge/aux-bridge.c
> +++ b/drivers/gpu/drm/bridge/aux-bridge.c
[ ... ]
> @@ -62,7 +63,10 @@ int drm_aux_bridge_register(struct device *parent)
> adev->dev.parent = parent;
> adev->dev.release = drm_aux_bridge_release;
>
> - device_set_of_node_from_dev(&adev->dev, parent);
> + if (np)
> + device_set_node(&adev->dev, of_fwnode_handle(np));
[Severity: High]
Does this cause a reference count underflow for the device node?
Unlike device_set_of_node_from_dev() which safely acquires a reference to the
of_node, device_set_node() just assigns the pointer without calling
of_node_get().
If auxiliary_device_init() fails, the error path below unconditionally calls
of_node_put(adev->dev.of_node). Similarly, the pre-existing
drm_aux_bridge_release() callback drops a reference when the device is
destroyed.
Could this cause of_node_put() to drop a reference that was never acquired?
> + else
> + device_set_of_node_from_dev(&adev->dev, parent);
>
> ret = auxiliary_device_init(adev);
> if (ret) {
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260717072323.96-1-kernel@airkyi.com?part=2
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* [PATCH v3 4/5] drm/rockchip: cdn-dp: Support handle lane info without extcon
From: Chaoyi Chen @ 2026-07-17 7:23 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sandy Huang, Heiko Stübner, Andy Yan, Vinod Koul,
Chaoyi Chen, Sebastian Reichel, Nicolas Frattaroli
Cc: linux-kernel, dri-devel, linux-arm-kernel, linux-rockchip,
linux-phy
In-Reply-To: <20260717072323.96-1-kernel@airkyi.com>
From: Chaoyi Chen <chaoyi.chen@rock-chips.com>
This patch add support for get PHY lane info without help of extcon.
There is no extcon needed if the Type-C controller is present. In this
case, the lane info can be get from PHY instead of extcon.
The extcon device should still be supported if Type-C controller is
not present.
Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com>
Reviewed-by: Heiko Stuebner <heiko@sntech.de>
---
(no changes since v1)
---
drivers/gpu/drm/rockchip/cdn-dp-core.c | 25 +++++++++++++++++--------
1 file changed, 17 insertions(+), 8 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c
index 190cce9530c8..74e1f5422a6e 100644
--- a/drivers/gpu/drm/rockchip/cdn-dp-core.c
+++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c
@@ -157,6 +157,9 @@ static int cdn_dp_get_port_lanes(struct cdn_dp_port *port)
int dptx;
u8 lanes;
+ if (!edev)
+ return phy_get_bus_width(port->phy);
+
dptx = extcon_get_state(edev, EXTCON_DISP_DP);
if (dptx > 0) {
extcon_get_property(edev, EXTCON_DISP_DP,
@@ -220,7 +223,7 @@ static bool cdn_dp_check_sink_connection(struct cdn_dp_device *dp)
* some docks need more time to power up.
*/
while (time_before(jiffies, timeout)) {
- if (!extcon_get_state(port->extcon, EXTCON_DISP_DP))
+ if (port->extcon && !extcon_get_state(port->extcon, EXTCON_DISP_DP))
return false;
if (!cdn_dp_get_sink_count(dp, &sink_count))
@@ -386,11 +389,14 @@ static int cdn_dp_enable_phy(struct cdn_dp_device *dp, struct cdn_dp_port *port)
goto err_power_on;
}
- ret = extcon_get_property(port->extcon, EXTCON_DISP_DP,
- EXTCON_PROP_USB_TYPEC_POLARITY, &property);
- if (ret) {
- DRM_DEV_ERROR(dp->dev, "get property failed\n");
- goto err_power_on;
+ property.intval = 0;
+ if (port->extcon) {
+ ret = extcon_get_property(port->extcon, EXTCON_DISP_DP,
+ EXTCON_PROP_USB_TYPEC_POLARITY, &property);
+ if (ret) {
+ DRM_DEV_ERROR(dp->dev, "get property failed\n");
+ goto err_power_on;
+ }
}
port->lanes = cdn_dp_get_port_lanes(port);
@@ -1027,6 +1033,9 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data)
for (i = 0; i < dp->ports; i++) {
port = dp->port[i];
+ if (!port->extcon)
+ continue;
+
port->event_nb.notifier_call = cdn_dp_pd_event;
ret = devm_extcon_register_notifier(dp->dev, port->extcon,
EXTCON_DISP_DP,
@@ -1119,14 +1128,14 @@ static int cdn_dp_probe(struct platform_device *pdev)
PTR_ERR(phy) == -EPROBE_DEFER)
return -EPROBE_DEFER;
- if (IS_ERR(extcon) || IS_ERR(phy))
+ if (IS_ERR(phy) || PTR_ERR(extcon) != -ENODEV)
continue;
port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
if (!port)
return -ENOMEM;
- port->extcon = extcon;
+ port->extcon = IS_ERR(extcon) ? NULL : extcon;
port->phy = phy;
port->dp = dp;
port->id = i;
--
2.53.0
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* [PATCH v3 3/5] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge
From: Chaoyi Chen @ 2026-07-17 7:23 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sandy Huang, Heiko Stübner, Andy Yan, Vinod Koul,
Chaoyi Chen, Sebastian Reichel, Nicolas Frattaroli
Cc: linux-kernel, dri-devel, linux-arm-kernel, linux-rockchip,
linux-phy
In-Reply-To: <20260717072323.96-1-kernel@airkyi.com>
From: Chaoyi Chen <chaoyi.chen@rock-chips.com>
Using the DRM_AUX_BRIDGE helper to create the transparent DRM bridge
device.
Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com>
Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
(no changes since v1)
---
drivers/phy/rockchip/Kconfig | 2 ++
drivers/phy/rockchip/phy-rockchip-typec.c | 13 +++++++++++--
2 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/drivers/phy/rockchip/Kconfig b/drivers/phy/rockchip/Kconfig
index 14698571b607..9173d3b4fef4 100644
--- a/drivers/phy/rockchip/Kconfig
+++ b/drivers/phy/rockchip/Kconfig
@@ -119,6 +119,8 @@ config PHY_ROCKCHIP_SNPS_PCIE3
config PHY_ROCKCHIP_TYPEC
tristate "Rockchip TYPEC PHY Driver"
depends on OF && (ARCH_ROCKCHIP || COMPILE_TEST)
+ depends on DRM || DRM=n
+ select DRM_AUX_BRIDGE if DRM_BRIDGE
select EXTCON
select GENERIC_PHY
select RESET_CONTROLLER
diff --git a/drivers/phy/rockchip/phy-rockchip-typec.c b/drivers/phy/rockchip/phy-rockchip-typec.c
index d9701b6106d5..48070b50416e 100644
--- a/drivers/phy/rockchip/phy-rockchip-typec.c
+++ b/drivers/phy/rockchip/phy-rockchip-typec.c
@@ -54,6 +54,7 @@
#include <linux/mfd/syscon.h>
#include <linux/phy/phy.h>
+#include <drm/bridge/aux-bridge.h>
#define CMN_SSM_BANDGAP (0x21 << 2)
#define CMN_SSM_BIAS (0x22 << 2)
@@ -1162,16 +1163,24 @@ static int rockchip_typec_phy_probe(struct platform_device *pdev)
for_each_available_child_of_node(np, child_np) {
struct phy *phy;
+ ret = 0;
- if (of_node_name_eq(child_np, "dp-port"))
+ if (of_node_name_eq(child_np, "dp-port")) {
phy = devm_phy_create(dev, child_np,
&rockchip_dp_phy_ops);
- else if (of_node_name_eq(child_np, "usb3-port"))
+ ret = drm_aux_bridge_register_from_node(dev, child_np);
+ } else if (of_node_name_eq(child_np, "usb3-port"))
phy = devm_phy_create(dev, child_np,
&rockchip_usb3_phy_ops);
else
continue;
+ if (ret) {
+ pm_runtime_disable(dev);
+ of_node_put(child_np);
+ return ret;
+ }
+
if (IS_ERR(phy)) {
dev_err(dev, "failed to create phy: %pOFn\n",
child_np);
--
2.53.0
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* [PATCH v3 5/5] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection
From: Chaoyi Chen @ 2026-07-17 7:23 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sandy Huang, Heiko Stübner, Andy Yan, Vinod Koul,
Chaoyi Chen, Sebastian Reichel, Nicolas Frattaroli
Cc: linux-kernel, dri-devel, linux-arm-kernel, linux-rockchip,
linux-phy
In-Reply-To: <20260717072323.96-1-kernel@airkyi.com>
From: Chaoyi Chen <chaoyi.chen@rock-chips.com>
The RK3399 has two USB/DP combo PHY and one CDN-DP controller. And
the CDN-DP can be switched to output to one of the PHYs. If both ports
are plugged into DP, DP will select the first port for output.
This patch adds support for multiple bridges, enabling users to flexibly
select the output port. For each PHY port, a separate encoder and bridge
are registered.
The change is based on the DRM AUX HPD bridge, rather than the
extcon approach. This requires the DT to correctly describe the
connections between the first bridge in bridge chain and DP
controller. For example, the bridge chain may be like this:
PHY aux birdge -> fsa4480 analog audio switch bridge ->
onnn,nb7vpq904m USB reminder bridge -> USB-C controller AUX HPD bridge
In this case, the connection relationships among the PHY aux bridge
and the DP contorller need to be described in DT.
In addition, the cdn_dp_parse_next_bridge_dt() will parses it and
determines whether to register one or two bridges.
Since there is only one DP controller, only one of the PHY ports can
output at a time. The key is how to switch between different PHYs,
which is handled by cdn_dp_switch_port() and cdn_dp_enable().
There are two cases:
1. Neither bridge is enabled. In this case, both bridges can
independently read the EDID, and the PHY port may switch before
reading the EDID.
2. One bridge is already enabled. In this case, other bridges are not
allowed to read the EDID. So we will try to return the cached EDID.
Since the scenario of two ports plug in at the same time is rare,
I don't have a board which support two TypeC connector to test this.
Therefore, I tested forced switching on a single PHY port, as well as
output using a fake PHY port alongside a real PHY port.
Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com>
Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
Reviewed-by: Heiko Stuebner <heiko@sntech.de>
---
Changes in v3:
- Remove now-redundant call to drm_connector_attach_encoder()
Changes in v2:
- Fix the check logic for dp->active_port (0 is valid).
- Fix the uninitialized prev_port.
- Remove duplicate logic from cdn_dp_switch_port().
- Fix the prototype of cdn_dp_bridge_hpd_notify().
- Properly release connector->fwnode.
- Properly release next_bridge.
---
drivers/gpu/drm/rockchip/Kconfig | 1 +
drivers/gpu/drm/rockchip/cdn-dp-core.c | 333 ++++++++++++++++++++-----
drivers/gpu/drm/rockchip/cdn-dp-core.h | 18 +-
3 files changed, 294 insertions(+), 58 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig
index e7f49fe845ea..ce70f91c13e6 100644
--- a/drivers/gpu/drm/rockchip/Kconfig
+++ b/drivers/gpu/drm/rockchip/Kconfig
@@ -60,6 +60,7 @@ config ROCKCHIP_CDN_DP
select DRM_DISPLAY_HELPER
select DRM_BRIDGE_CONNECTOR
select DRM_DISPLAY_DP_HELPER
+ select DRM_AUX_HPD_BRIDGE
help
This selects support for Rockchip SoC specific extensions
for the cdn DP driver. If you want to enable Dp on
diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c
index 74e1f5422a6e..5e513f6930cb 100644
--- a/drivers/gpu/drm/rockchip/cdn-dp-core.c
+++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c
@@ -28,16 +28,17 @@
#include "cdn-dp-core.h"
#include "cdn-dp-reg.h"
-static inline struct cdn_dp_device *bridge_to_dp(struct drm_bridge *bridge)
+static int cdn_dp_switch_port(struct cdn_dp_device *dp, struct cdn_dp_port *prev_port,
+ struct cdn_dp_port *port);
+
+static inline struct cdn_dp_bridge *bridge_to_dp_bridge(struct drm_bridge *bridge)
{
- return container_of(bridge, struct cdn_dp_device, bridge);
+ return container_of(bridge, struct cdn_dp_bridge, bridge);
}
-static inline struct cdn_dp_device *encoder_to_dp(struct drm_encoder *encoder)
+static inline struct cdn_dp_device *bridge_to_dp(struct drm_bridge *bridge)
{
- struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder);
-
- return container_of(rkencoder, struct cdn_dp_device, encoder);
+ return bridge_to_dp_bridge(bridge)->parent;
}
#define GRF_SOC_CON9 0x6224
@@ -192,14 +193,27 @@ static int cdn_dp_get_sink_count(struct cdn_dp_device *dp, u8 *sink_count)
static struct cdn_dp_port *cdn_dp_connected_port(struct cdn_dp_device *dp)
{
struct cdn_dp_port *port;
- int i, lanes;
+ int i, lanes[MAX_PHY];
for (i = 0; i < dp->ports; i++) {
port = dp->port[i];
- lanes = cdn_dp_get_port_lanes(port);
- if (lanes)
+ lanes[i] = cdn_dp_get_port_lanes(port);
+ if (!dp->next_bridge_valid)
return port;
}
+
+ if (dp->next_bridge_valid) {
+ /* If more than one port is available, pick the last active port */
+ if (dp->active_port >= 0 && lanes[dp->active_port])
+ return dp->port[dp->active_port];
+
+ /* If the last active port is not available, pick an available port in order */
+ for (i = 0; i < dp->bridge_count; i++) {
+ if (lanes[i])
+ return dp->port[i];
+ }
+ }
+
return NULL;
}
@@ -254,12 +268,45 @@ static const struct drm_edid *
cdn_dp_bridge_edid_read(struct drm_bridge *bridge, struct drm_connector *connector)
{
struct cdn_dp_device *dp = bridge_to_dp(bridge);
- const struct drm_edid *drm_edid;
+ struct cdn_dp_bridge *dp_bridge = bridge_to_dp_bridge(bridge);
+ struct cdn_dp_port *port = dp->port[dp_bridge->id];
+ struct cdn_dp_port *prev_port = NULL;
+ const struct drm_edid *drm_edid = NULL;
+ int i, ret;
mutex_lock(&dp->lock);
+
+ /* More than one port is available */
+ if (dp->bridge_count > 1 && !port->phy_enabled) {
+ for (i = 0; i < dp->bridge_count; i++) {
+ /* Another port already enable */
+ if (dp->bridge_list[i] != dp_bridge && dp->bridge_list[i]->enabled)
+ goto get_cache;
+ /* Find already enabled port */
+ if (dp->port[i]->phy_enabled)
+ prev_port = dp->port[i];
+ }
+
+ /* Switch to current port */
+ if (prev_port) {
+ ret = cdn_dp_switch_port(dp, prev_port, port);
+ if (ret)
+ goto get_cache;
+ }
+ }
+
drm_edid = drm_edid_read_custom(connector, cdn_dp_get_edid_block, dp);
+ /* replace edid cache */
+ if (dp->edid_cache[dp_bridge->id])
+ drm_edid_free(dp->edid_cache[dp_bridge->id]);
+ dp->edid_cache[dp_bridge->id] = drm_edid_dup(drm_edid);
+
mutex_unlock(&dp->lock);
+ return drm_edid;
+get_cache:
+ drm_edid = drm_edid_dup(dp->edid_cache[dp_bridge->id]);
+ mutex_unlock(&dp->lock);
return drm_edid;
}
@@ -268,12 +315,13 @@ cdn_dp_bridge_mode_valid(struct drm_bridge *bridge,
const struct drm_display_info *display_info,
const struct drm_display_mode *mode)
{
+ struct cdn_dp_bridge *dp_bridge = bridge_to_dp_bridge(bridge);
struct cdn_dp_device *dp = bridge_to_dp(bridge);
u32 requested, actual, rate, sink_max, source_max = 0;
u8 lanes, bpc;
/* If DP is disconnected, every mode is invalid */
- if (!dp->connected)
+ if (!dp_bridge->connected || !dp->connected)
return MODE_BAD;
switch (display_info->bpc) {
@@ -551,6 +599,50 @@ static bool cdn_dp_check_link_status(struct cdn_dp_device *dp)
return drm_dp_channel_eq_ok(link_status, min(port->lanes, sink_lanes));
}
+static int cdn_dp_switch_port(struct cdn_dp_device *dp, struct cdn_dp_port *prev_port,
+ struct cdn_dp_port *port)
+{
+ int ret;
+
+ ret = cdn_dp_disable_phy(dp, prev_port);
+ if (ret)
+ goto out;
+ ret = cdn_dp_enable_phy(dp, port);
+ if (ret)
+ goto out;
+
+ ret = cdn_dp_get_sink_capability(dp);
+ if (ret) {
+ cdn_dp_disable_phy(dp, port);
+ goto out;
+ }
+
+ dp->lanes = port->lanes;
+
+ if (!cdn_dp_check_link_status(dp)) {
+ dev_info(dp->dev, "Connected with sink; re-train link\n");
+
+ ret = cdn_dp_train_link(dp);
+ if (ret) {
+ dev_err(dp->dev, "Training link failed: %d\n", ret);
+ goto out;
+ }
+
+ ret = cdn_dp_set_video_status(dp, CONTROL_VIDEO_IDLE);
+ if (ret) {
+ dev_err(dp->dev, "Failed to idle video %d\n", ret);
+ goto out;
+ }
+
+ ret = cdn_dp_config_video(dp);
+ if (ret)
+ dev_err(dp->dev, "Failed to configure video: %d\n", ret);
+ }
+
+out:
+ return ret;
+}
+
static void cdn_dp_display_info_update(struct cdn_dp_device *dp,
struct drm_display_info *display_info)
{
@@ -572,6 +664,7 @@ static void cdn_dp_display_info_update(struct cdn_dp_device *dp,
static void cdn_dp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_atomic_commit *state)
{
struct cdn_dp_device *dp = bridge_to_dp(bridge);
+ struct cdn_dp_bridge *dp_bridge = bridge_to_dp_bridge(bridge);
struct drm_connector *connector;
int ret, val;
@@ -581,7 +674,7 @@ static void cdn_dp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_at
cdn_dp_display_info_update(dp, &connector->display_info);
- ret = drm_of_encoder_active_endpoint_id(dp->dev->of_node, &dp->encoder.encoder);
+ ret = drm_of_encoder_active_endpoint_id(dp->dev->of_node, &dp_bridge->encoder.encoder);
if (ret < 0) {
DRM_DEV_ERROR(dp->dev, "Could not get vop id, %d", ret);
return;
@@ -600,6 +693,9 @@ static void cdn_dp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_at
mutex_lock(&dp->lock);
+ if (dp->next_bridge_valid)
+ dp->active_port = dp_bridge->id;
+
ret = cdn_dp_enable(dp);
if (ret) {
DRM_DEV_ERROR(dp->dev, "Failed to enable bridge %d\n",
@@ -632,6 +728,7 @@ static void cdn_dp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_at
goto out;
}
+ dp_bridge->enabled = true;
out:
mutex_unlock(&dp->lock);
}
@@ -639,9 +736,11 @@ static void cdn_dp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_at
static void cdn_dp_bridge_atomic_disable(struct drm_bridge *bridge, struct drm_atomic_commit *state)
{
struct cdn_dp_device *dp = bridge_to_dp(bridge);
+ struct cdn_dp_bridge *dp_bridge = bridge_to_dp_bridge(bridge);
int ret;
mutex_lock(&dp->lock);
+ dp_bridge->enabled = false;
if (dp->active) {
ret = cdn_dp_disable(dp);
@@ -828,6 +927,17 @@ static int cdn_dp_audio_mute_stream(struct drm_bridge *bridge,
return ret;
}
+static void cdn_dp_bridge_hpd_notify(struct drm_bridge *bridge,
+ struct drm_connector *connector,
+ enum drm_connector_status status)
+{
+ struct cdn_dp_bridge *dp_bridge = bridge_to_dp_bridge(bridge);
+ struct cdn_dp_device *dp = bridge_to_dp(bridge);
+
+ dp->bridge_list[dp_bridge->id]->connected = status == connector_status_connected;
+ schedule_work(&dp->event_work);
+}
+
static const struct drm_bridge_funcs cdn_dp_bridge_funcs = {
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
@@ -838,6 +948,7 @@ static const struct drm_bridge_funcs cdn_dp_bridge_funcs = {
.atomic_disable = cdn_dp_bridge_atomic_disable,
.mode_valid = cdn_dp_bridge_mode_valid,
.mode_set = cdn_dp_bridge_mode_set,
+ .hpd_notify = cdn_dp_bridge_hpd_notify,
.dp_audio_prepare = cdn_dp_audio_prepare,
.dp_audio_mute_stream = cdn_dp_audio_mute_stream,
@@ -886,7 +997,8 @@ static void cdn_dp_pd_event_work(struct work_struct *work)
{
struct cdn_dp_device *dp = container_of(work, struct cdn_dp_device,
event_work);
- int ret;
+ bool connected;
+ int i, ret;
mutex_lock(&dp->lock);
@@ -945,9 +1057,12 @@ static void cdn_dp_pd_event_work(struct work_struct *work)
out:
mutex_unlock(&dp->lock);
- drm_bridge_hpd_notify(&dp->bridge,
- dp->connected ? connector_status_connected
- : connector_status_disconnected);
+ for (i = 0; i < dp->bridge_count; i++) {
+ connected = dp->connected && dp->bridge_list[i]->connected;
+ drm_bridge_hpd_notify(&dp->bridge_list[i]->bridge,
+ connected ? connector_status_connected
+ : connector_status_disconnected);
+ }
}
static int cdn_dp_pd_event(struct notifier_block *nb,
@@ -967,28 +1082,16 @@ static int cdn_dp_pd_event(struct notifier_block *nb,
return NOTIFY_DONE;
}
-static int cdn_dp_bind(struct device *dev, struct device *master, void *data)
+static int cdn_bridge_add(struct device *dev,
+ struct drm_bridge *bridge,
+ struct drm_bridge *next_bridge,
+ struct drm_encoder *encoder)
{
struct cdn_dp_device *dp = dev_get_drvdata(dev);
- struct drm_encoder *encoder;
+ struct drm_device *drm_dev = dp->drm_dev;
+ struct drm_bridge *last_bridge __free(drm_bridge_put) = NULL;
struct drm_connector *connector;
- struct cdn_dp_port *port;
- struct drm_device *drm_dev = data;
- int ret, i;
-
- ret = cdn_dp_parse_dt(dp);
- if (ret < 0)
- return ret;
-
- dp->drm_dev = drm_dev;
- dp->connected = false;
- dp->active = false;
- dp->active_port = -1;
- dp->fw_loaded = false;
-
- INIT_WORK(&dp->event_work, cdn_dp_pd_event_work);
-
- encoder = &dp->encoder.encoder;
+ int ret;
encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev,
dev->of_node);
@@ -1003,26 +1106,35 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data)
drm_encoder_helper_add(encoder, &cdn_dp_encoder_helper_funcs);
- dp->bridge.ops =
- DRM_BRIDGE_OP_DETECT |
- DRM_BRIDGE_OP_EDID |
- DRM_BRIDGE_OP_HPD |
- DRM_BRIDGE_OP_DP_AUDIO;
- dp->bridge.of_node = dp->dev->of_node;
- dp->bridge.type = DRM_MODE_CONNECTOR_DisplayPort;
- dp->bridge.hdmi_audio_dev = dp->dev;
- dp->bridge.hdmi_audio_max_i2s_playback_channels = 8;
- dp->bridge.hdmi_audio_spdif_playback = 1;
- dp->bridge.hdmi_audio_dai_port = -1;
-
- ret = devm_drm_bridge_add(dev, &dp->bridge);
+ bridge->ops =
+ DRM_BRIDGE_OP_DETECT |
+ DRM_BRIDGE_OP_EDID |
+ DRM_BRIDGE_OP_HPD |
+ DRM_BRIDGE_OP_DP_AUDIO;
+ bridge->of_node = dp->dev->of_node;
+ bridge->type = DRM_MODE_CONNECTOR_DisplayPort;
+ bridge->hdmi_audio_dev = dp->dev;
+ bridge->hdmi_audio_max_i2s_playback_channels = 8;
+ bridge->hdmi_audio_spdif_playback = 1;
+ bridge->hdmi_audio_dai_port = -1;
+
+ ret = devm_drm_bridge_add(dev, bridge);
if (ret)
return ret;
- ret = drm_bridge_attach(encoder, &dp->bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
+ ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
if (ret)
return ret;
+ if (next_bridge) {
+ ret = drm_bridge_attach(encoder, next_bridge, bridge,
+ DRM_BRIDGE_ATTACH_NO_CONNECTOR);
+ if (ret)
+ return ret;
+
+ last_bridge = drm_bridge_chain_get_last_bridge(bridge->encoder);
+ }
+
connector = drm_bridge_connector_init(drm_dev, encoder);
if (IS_ERR(connector)) {
ret = PTR_ERR(connector);
@@ -1030,6 +1142,101 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data)
return ret;
}
+ if (last_bridge) {
+ fwnode_handle_put(connector->fwnode);
+ connector->fwnode = fwnode_handle_get(of_fwnode_handle(last_bridge->of_node));
+ }
+
+ return 0;
+}
+
+static int cdn_dp_parse_next_bridge_dt(struct cdn_dp_device *dp)
+{
+ struct device_node *np = dp->dev->of_node;
+ struct device_node *port __free(device_node) = of_graph_get_port_by_id(np, 1);
+ struct drm_bridge *bridge;
+ int count = 0;
+ int ret = 0;
+ int i;
+
+ /* If device use extcon, do not use hpd bridge */
+ for (i = 0; i < dp->ports; i++) {
+ if (dp->port[i]->extcon) {
+ dp->bridge_count = 1;
+ return 0;
+ }
+ }
+
+ /* One endpoint may correspond to one next bridge. */
+ for_each_of_graph_port_endpoint(port, dp_ep) {
+ struct device_node *next_bridge_node __free(device_node) =
+ of_graph_get_remote_port_parent(dp_ep);
+
+ bridge = of_drm_find_bridge(next_bridge_node);
+ if (!bridge) {
+ ret = -EPROBE_DEFER;
+ goto out;
+ }
+
+ dp->next_bridge_valid = true;
+ dp->next_bridge_list[count] = drm_bridge_get(bridge);
+ count++;
+ }
+
+out:
+ dp->bridge_count = count ? count : 1;
+ return ret;
+}
+
+static int cdn_dp_bind(struct device *dev, struct device *master, void *data)
+{
+ struct cdn_dp_device *dp = dev_get_drvdata(dev);
+ struct drm_bridge *bridge, *next_bridge;
+ struct drm_encoder *encoder;
+ struct cdn_dp_port *port;
+ struct drm_device *drm_dev = data;
+ struct cdn_dp_bridge *dp_bridge;
+ int ret, i;
+
+ ret = cdn_dp_parse_dt(dp);
+ if (ret < 0)
+ return ret;
+
+ ret = cdn_dp_parse_next_bridge_dt(dp);
+ if (ret)
+ goto out;
+
+ dp->drm_dev = drm_dev;
+ dp->connected = false;
+ dp->active = false;
+ dp->active_port = -1;
+ dp->fw_loaded = false;
+
+ for (i = 0; i < dp->bridge_count; i++) {
+ dp_bridge = devm_drm_bridge_alloc(dev, struct cdn_dp_bridge, bridge,
+ &cdn_dp_bridge_funcs);
+ if (IS_ERR(dp_bridge)) {
+ ret = PTR_ERR(dp_bridge);
+ goto out;
+ }
+ dp_bridge->id = i;
+ dp_bridge->parent = dp;
+ if (!dp->next_bridge_valid)
+ dp_bridge->connected = true;
+ dp->bridge_list[i] = dp_bridge;
+ }
+
+ for (i = 0; i < dp->bridge_count; i++) {
+ encoder = &dp->bridge_list[i]->encoder.encoder;
+ bridge = &dp->bridge_list[i]->bridge;
+ next_bridge = dp->next_bridge_list[i];
+ ret = cdn_bridge_add(dev, bridge, next_bridge, encoder);
+ if (ret)
+ goto out;
+ }
+
+ INIT_WORK(&dp->event_work, cdn_dp_pd_event_work);
+
for (i = 0; i < dp->ports; i++) {
port = dp->port[i];
@@ -1043,7 +1250,7 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data)
if (ret) {
DRM_DEV_ERROR(dev,
"register EXTCON_DISP_DP notifier err\n");
- return ret;
+ goto out;
}
}
@@ -1052,15 +1259,29 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data)
schedule_work(&dp->event_work);
return 0;
+
+out:
+ for (i = 0; i < MAX_PHY; i++)
+ drm_bridge_put(dp->next_bridge_list[i]);
+
+ return ret;
}
static void cdn_dp_unbind(struct device *dev, struct device *master, void *data)
{
struct cdn_dp_device *dp = dev_get_drvdata(dev);
- struct drm_encoder *encoder = &dp->encoder.encoder;
+ struct drm_encoder *encoder;
+ int i;
cancel_work_sync(&dp->event_work);
- encoder->funcs->destroy(encoder);
+ for (i = 0; i < dp->bridge_count; i++) {
+ encoder = &dp->bridge_list[i]->encoder.encoder;
+ encoder->funcs->destroy(encoder);
+ drm_bridge_put(dp->next_bridge_list[i]);
+ }
+
+ for (i = 0; i < MAX_PHY; i++)
+ drm_edid_free(dp->edid_cache[i]);
pm_runtime_disable(dev);
if (dp->fw_loaded)
@@ -1111,10 +1332,10 @@ static int cdn_dp_probe(struct platform_device *pdev)
int ret;
int i;
- dp = devm_drm_bridge_alloc(dev, struct cdn_dp_device, bridge,
- &cdn_dp_bridge_funcs);
- if (IS_ERR(dp))
- return PTR_ERR(dp);
+ dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
+ if (!dp)
+ return -ENOMEM;
+
dp->dev = dev;
match = of_match_node(cdn_dp_dt_ids, pdev->dev.of_node);
diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.h b/drivers/gpu/drm/rockchip/cdn-dp-core.h
index e9c30b9fd543..c10e423bbf06 100644
--- a/drivers/gpu/drm/rockchip/cdn-dp-core.h
+++ b/drivers/gpu/drm/rockchip/cdn-dp-core.h
@@ -38,6 +38,8 @@ enum vic_pxl_encoding_format {
Y_ONLY = 0x10,
};
+struct cdn_dp_device;
+
struct video_info {
bool h_sync_polarity;
bool v_sync_polarity;
@@ -63,16 +65,28 @@ struct cdn_dp_port {
u8 id;
};
+struct cdn_dp_bridge {
+ struct cdn_dp_device *parent;
+ struct drm_bridge bridge;
+ struct rockchip_encoder encoder;
+ bool connected;
+ bool enabled;
+ int id;
+};
+
struct cdn_dp_device {
struct device *dev;
struct drm_device *drm_dev;
- struct drm_bridge bridge;
- struct rockchip_encoder encoder;
+ int bridge_count;
+ struct cdn_dp_bridge *bridge_list[MAX_PHY];
+ struct drm_bridge *next_bridge_list[MAX_PHY];
+ const struct drm_edid *edid_cache[MAX_PHY];
struct drm_display_mode mode;
struct platform_device *audio_pdev;
struct work_struct event_work;
struct mutex lock;
+ bool next_bridge_valid;
bool connected;
bool active;
bool suspended;
--
2.53.0
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* [PATCH v3 2/5] drm/bridge: aux: Add drm_aux_bridge_register_from_node()
From: Chaoyi Chen @ 2026-07-17 7:23 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sandy Huang, Heiko Stübner, Andy Yan, Vinod Koul,
Chaoyi Chen, Sebastian Reichel, Nicolas Frattaroli
Cc: linux-kernel, dri-devel, linux-arm-kernel, linux-rockchip,
linux-phy
In-Reply-To: <20260717072323.96-1-kernel@airkyi.com>
From: Chaoyi Chen <chaoyi.chen@rock-chips.com>
The drm_aux_bridge_register() uses the device->of_node as the
bridge->of_node.
This patch adds drm_aux_bridge_register_from_node() to allow
specifying the of_node corresponding to the bridge.
Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com>
Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
Reviewed-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
(no changes since v3)
Changes in v2:
- Fix incorrect function names in the documentation comments.
---
drivers/gpu/drm/bridge/aux-bridge.c | 26 +++++++++++++++++++++++---
include/drm/bridge/aux-bridge.h | 6 ++++++
2 files changed, 29 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/bridge/aux-bridge.c b/drivers/gpu/drm/bridge/aux-bridge.c
index 1ed21a8713bf..06a1466f49bd 100644
--- a/drivers/gpu/drm/bridge/aux-bridge.c
+++ b/drivers/gpu/drm/bridge/aux-bridge.c
@@ -33,8 +33,9 @@ static void drm_aux_bridge_unregister_adev(void *_adev)
}
/**
- * drm_aux_bridge_register - Create a simple bridge device to link the chain
+ * drm_aux_bridge_register_from_node - Create a simple bridge device to link the chain
* @parent: device instance providing this bridge
+ * @np: device node pointer corresponding to this bridge instance
*
* Creates a simple DRM bridge that doesn't implement any drm_bridge
* operations. Such bridges merely fill a place in the bridge chain linking
@@ -42,7 +43,7 @@ static void drm_aux_bridge_unregister_adev(void *_adev)
*
* Return: zero on success, negative error code on failure
*/
-int drm_aux_bridge_register(struct device *parent)
+int drm_aux_bridge_register_from_node(struct device *parent, struct device_node *np)
{
struct auxiliary_device *adev;
int ret;
@@ -62,7 +63,10 @@ int drm_aux_bridge_register(struct device *parent)
adev->dev.parent = parent;
adev->dev.release = drm_aux_bridge_release;
- device_set_of_node_from_dev(&adev->dev, parent);
+ if (np)
+ device_set_node(&adev->dev, of_fwnode_handle(np));
+ else
+ device_set_of_node_from_dev(&adev->dev, parent);
ret = auxiliary_device_init(adev);
if (ret) {
@@ -80,6 +84,22 @@ int drm_aux_bridge_register(struct device *parent)
return devm_add_action_or_reset(parent, drm_aux_bridge_unregister_adev, adev);
}
+EXPORT_SYMBOL_GPL(drm_aux_bridge_register_from_node);
+
+/**
+ * drm_aux_bridge_register - Create a simple bridge device to link the chain
+ * @parent: device instance providing this bridge
+ *
+ * Creates a simple DRM bridge that doesn't implement any drm_bridge
+ * operations. Such bridges merely fill a place in the bridge chain linking
+ * surrounding DRM bridges.
+ *
+ * Return: zero on success, negative error code on failure
+ */
+int drm_aux_bridge_register(struct device *parent)
+{
+ return drm_aux_bridge_register_from_node(parent, NULL);
+}
EXPORT_SYMBOL_GPL(drm_aux_bridge_register);
struct drm_aux_bridge_data {
diff --git a/include/drm/bridge/aux-bridge.h b/include/drm/bridge/aux-bridge.h
index c2f5a855512f..7dd1f17a1354 100644
--- a/include/drm/bridge/aux-bridge.h
+++ b/include/drm/bridge/aux-bridge.h
@@ -13,11 +13,17 @@ struct auxiliary_device;
#if IS_ENABLED(CONFIG_DRM_AUX_BRIDGE)
int drm_aux_bridge_register(struct device *parent);
+int drm_aux_bridge_register_from_node(struct device *parent, struct device_node *np);
#else
static inline int drm_aux_bridge_register(struct device *parent)
{
return 0;
}
+
+static inline int drm_aux_bridge_register_from_node(struct device *parent, struct device_node *np)
+{
+ return 0;
+}
#endif
#if IS_ENABLED(CONFIG_DRM_AUX_HPD_BRIDGE)
--
2.53.0
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* [PATCH v3 1/5] drm/bridge: Implement generic USB Type-C DP HPD bridge
From: Chaoyi Chen @ 2026-07-17 7:23 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sandy Huang, Heiko Stübner, Andy Yan, Vinod Koul,
Chaoyi Chen, Sebastian Reichel, Nicolas Frattaroli
Cc: linux-kernel, dri-devel, linux-arm-kernel, linux-rockchip,
linux-phy
In-Reply-To: <20260717072323.96-1-kernel@airkyi.com>
From: Chaoyi Chen <chaoyi.chen@rock-chips.com>
The HPD function of Type-C DP is implemented through
drm_connector_oob_hotplug_event(). For embedded DP, it is required
that the DRM connector fwnode corresponds to the Type-C port fwnode.
To describe the relationship between the DP controller and the Type-C
port device, we usually using drm_bridge to build a bridge chain.
Now several USB-C controller drivers have already implemented the DP
HPD bridge function provided by aux-hpd-bridge.c, it will build a DP
HPD bridge on USB-C connector port device.
But this requires the USB-C controller driver to manually register the
HPD bridge. If the driver does not implement this feature, the bridge
will not be create.
So this patch implements a generic DP HPD bridge based on
aux-hpd-bridge.c. It will monitor Type-C bus events, and when a
Type-C port device containing the DP svid is registered, it will
create an HPD bridge for it without the need for the USB-C controller
driver to implement it.
Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com>
Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
(no changes since v3)
Changes in v2:
- Add copyright text.
- Remove useless goto.
---
drivers/gpu/drm/bridge/Kconfig | 10 ++++
drivers/gpu/drm/bridge/Makefile | 1 +
.../gpu/drm/bridge/aux-hpd-typec-dp-bridge.c | 54 +++++++++++++++++++
3 files changed, 65 insertions(+)
create mode 100644 drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 4a57d49b4c6d..9739b2a19758 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -30,6 +30,16 @@ config DRM_AUX_HPD_BRIDGE
Simple bridge that terminates the bridge chain and provides HPD
support.
+if DRM_AUX_HPD_BRIDGE
+config DRM_AUX_HPD_TYPEC_BRIDGE
+ tristate
+ depends on TYPEC || !TYPEC
+ default TYPEC
+ help
+ Simple bridge that terminates the bridge chain and provides HPD
+ support. It build bridge on each USB-C connector device node.
+endif
+
menu "Display Interface Bridges"
depends on DRM && DRM_BRIDGE
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index 15cc821d85b7..d88a9e1ccc9a 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_DRM_AUX_BRIDGE) += aux-bridge.o
obj-$(CONFIG_DRM_AUX_HPD_BRIDGE) += aux-hpd-bridge.o
+obj-$(CONFIG_DRM_AUX_HPD_TYPEC_BRIDGE) += aux-hpd-typec-dp-bridge.o
obj-$(CONFIG_DRM_CHIPONE_ICN6211) += chipone-icn6211.o
obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o
obj-$(CONFIG_DRM_CROS_EC_ANX7688) += cros-ec-anx7688.o
diff --git a/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c b/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c
new file mode 100644
index 000000000000..c09579ff7ab9
--- /dev/null
+++ b/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2026 Rockchip Electronics Co., Ltd.
+ *
+ * Author: Chaoyi Chen <chaoyi.chen@rock-chips.com>
+ */
+#include <linux/of.h>
+#include <linux/usb/typec_altmode.h>
+#include <linux/usb/typec_dp.h>
+
+#include <drm/bridge/aux-bridge.h>
+
+static int drm_typec_bus_event(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct device *dev = (struct device *)data;
+ struct typec_altmode *alt = to_typec_altmode(dev);
+
+ if (action != BUS_NOTIFY_ADD_DEVICE)
+ return NOTIFY_OK;
+
+ /*
+ * alt->dev.parent->parent : USB-C controller device
+ * alt->dev.parent : USB-C connector device
+ */
+ if (is_typec_port_altmode(&alt->dev) && alt->svid == USB_TYPEC_DP_SID)
+ drm_dp_hpd_bridge_register(alt->dev.parent->parent,
+ to_of_node(alt->dev.parent->fwnode));
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block drm_typec_event_nb = {
+ .notifier_call = drm_typec_bus_event,
+};
+
+static void drm_aux_hpd_typec_dp_bridge_module_exit(void)
+{
+ bus_unregister_notifier(&typec_bus, &drm_typec_event_nb);
+}
+
+static int __init drm_aux_hpd_typec_dp_bridge_module_init(void)
+{
+ bus_register_notifier(&typec_bus, &drm_typec_event_nb);
+
+ return 0;
+}
+
+module_init(drm_aux_hpd_typec_dp_bridge_module_init);
+module_exit(drm_aux_hpd_typec_dp_bridge_module_exit);
+
+MODULE_AUTHOR("Chaoyi Chen <chaoyi.chen@rock-chips.com>");
+MODULE_DESCRIPTION("DRM TYPEC DP HPD BRIDGE");
+MODULE_LICENSE("GPL");
--
2.53.0
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* [PATCH v3 0/5] drm/bridge: Implement generic USB Type-C DP HPD bridge
From: Chaoyi Chen @ 2026-07-17 7:23 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sandy Huang, Heiko Stübner, Andy Yan, Vinod Koul,
Chaoyi Chen, Sebastian Reichel, Nicolas Frattaroli
Cc: linux-kernel, dri-devel, linux-arm-kernel, linux-rockchip,
linux-phy
From: Chaoyi Chen <chaoyi.chen@rock-chips.com>
This series is split from the v15 "Add Type-C DP support for RK3399 EVB
IND board" series [1]. It focuses on the DRM bridge and Rockchip
platform CDN-DP controller changes.
[1] https://lore.kernel.org/all/20260304094152.92-1-kernel@airkyi.com/
====
1. Generic Type-C DP HPD bridge
Currently, several USB-C controller drivers register their own DP HPD
bridge via aux-hpd-bridge.c, each duplicating the same logic. For
devicetree based platforms, the USB-C controller may vary across boards,
and not every USB-C controller driver implements this feature. Patch 1
implements a generic DP HPD bridge that monitors Type-C bus events and
automatically creates an HPD bridge when a Type-C port device with DP
SVID is registered.
2. Multiple bridge model for CDN-DP
The RK3399 has two USB/DP combo PHY and one CDN-DP controller. Patch 5
introduces a multi-bridge model where each PHY port gets a separate
encoder and bridge, allowing flexible selection of the output PHY port.
This is based on the DRM AUX HPD bridge rather than extcon.
====
Patch 1 adds generic USB Type-C DP HPD bridge (Dmitry, Heikki).
Patch 2 adds new API drm_aux_bridge_register_from_node() (Neil).
Patch 3 adds DRM AUX bridge support for RK3399 USBDP PHY (Neil).
Patch 4 drops CDN-DP's extcon dependency when Type-C is present (Dmitry).
Patch 5 adds multiple bridges to support PHY port selection (Dmitry, Luca).
Changes in v3:
- Link to v2: https://lore.kernel.org/all/20260608070805.88-1-kernel@airkyi.com/
(Sorry, I forgot to add the "v2" title, so it looks like v1.)
- Remove now-redundant call to drm_connector_attach_encoder()
- Rebase to v7.2-rc2
Changes in v2:
- Link to v1: https://lore.kernel.org/all/20260521032854.103-1-kernel@airkyi.com/
- Add copyright text.
- Remove useless goto.
- Fix incorrect function names in the documentation comments.
- Fix the check logic for dp->active_port (0 is valid).
- Fix the uninitialized prev_port.
- Remove duplicate logic from cdn_dp_switch_port().
- Fix the prototype of cdn_dp_bridge_hpd_notify().
- Properly release connector->fwnode.
- Properly release next_bridge.
Chaoyi Chen (5):
drm/bridge: Implement generic USB Type-C DP HPD bridge
drm/bridge: aux: Add drm_aux_bridge_register_from_node()
phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge
drm/rockchip: cdn-dp: Support handle lane info without extcon
drm/rockchip: cdn-dp: Add multiple bridges to support PHY port
selection
drivers/gpu/drm/bridge/Kconfig | 10 +
drivers/gpu/drm/bridge/Makefile | 1 +
drivers/gpu/drm/bridge/aux-bridge.c | 26 +-
.../gpu/drm/bridge/aux-hpd-typec-dp-bridge.c | 54 +++
drivers/gpu/drm/rockchip/Kconfig | 1 +
drivers/gpu/drm/rockchip/cdn-dp-core.c | 358 ++++++++++++++----
drivers/gpu/drm/rockchip/cdn-dp-core.h | 18 +-
drivers/phy/rockchip/Kconfig | 2 +
drivers/phy/rockchip/phy-rockchip-typec.c | 13 +-
include/drm/bridge/aux-bridge.h | 6 +
10 files changed, 418 insertions(+), 71 deletions(-)
create mode 100644 drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c
--
2.53.0
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* RE: [RFC PATCH 06/10] net: pcs: xpcs: improve SGMII AN state handling for Rockchip RK3568
From: Jiawen Wu @ 2026-07-17 7:03 UTC (permalink / raw)
To: 'Coia Prant'
Cc: 'Andrew Lunn', kuba, davem, edumazet, pabeni,
andrew+netdev, robh, krzk+dt, heiko, netdev, linux-rockchip,
devicetree, linux-arm-kernel, linux-phy, 'Mengyuan Lou'
In-Reply-To: <CALj3r0hH=mF+8oVbisoECYo6HRuSdfSt_nuhFunHowyPsw2srQ@mail.gmail.com>
On Fri, Jul 17, 2026 2:29 PM, Coia Prant wrote:
> Jiawen Wu <jiawenwu@trustnetic.com> 于2026年7月17日周五 10:23写道:
> >
> > On Wed, Jul 15, 2026 7:05 AM, Coia Prant wrote:
> > > Andrew Lunn <andrew@lunn.ch> 于2026年7月15日周三 06:44写道:
> > > >
> > > > On Wed, Jul 15, 2026 at 03:08:34AM +0800, Coia Prant wrote:
> > > > > Commit 2a22b7ae2fa3 ("net: pcs: xpcs: adapt Wangxun NICs for SGMII mode")
> > > >
> > > > You do not appear to Cc: the Wangxun NIC people. It would be good to
> > > > have there comments on this change.
> > >
> > > I apologize; the output from get_maintainer.pl is very long. I’ve
> > > heard that having too many recipients can cause the PATCH to be
> > > rejected by the LKML mail server.
> > >
> > > I have added Wangxun maintainer (Jiawen Wu <jiawenwu@trustnetic.com>
> > > and Mengyuan Lou <mengyuanlou@net-swift.com>) to the CC list.
> > >
> > > > > Fixes: 2a22b7ae2fa3 ("net: pcs: xpcs: adapt Wangxun NICs for SGMII mode")
> > > > > Signed-off-by: Coia Prant <coiaprant@gmail.com>
> > > >
> > > > Please don't mix fixed and new code. Is this a real fix? Should it be
> > > > back ported to stable?
> > >
> > > I am not sure if this is a specific characteristic of Wangxun NICs, as
> > > I do not have any available for testing.
> > >
> > > The behavior of the Rockchip DW XPCS IP core matches what is described
> > > in the commit message (even though phylink brings the link to down
> > > based on the phydev link status).
> > >
> > > This appears to be a bug (at least on Rockchip platforms) or Wangxun
> > > NICs features.
> > >
> > > However, I cannot confirm whether Wangxun NICs behave the same way.
> > > Therefore, I have kept their code as is for now.
> > >
> > > Could a Wangxun NICs maintainer provide some feedback based on testing?
> > >
> > > If we can confirm that this is indeed a bug, I can submit a separate fix.
> > >
> > > I would greatly appreciate it.
> > >
> > > Thanks.
> >
> > Hi Coia,
> >
> > I have tested this patch and it works on Wangxun NICs, thanks.
> >
> > The commit 2a22b7ae2fa3 ("net: pcs: xpcs: adapt Wangxun NICs for SGMII mode")
> > is too long ago for me to recall exactly what that thought was.
> >
> > The log shows:
> >
> > "On this device, CL37_ANSGM_STS (bit[4:1] of VR_MII_AN_INTR_STS) indicates
> > the status received from remote link during the auto-negotiation, and
> > self-clear after the auto-negotiation is complete.
> > Meanwhile, CL37_ANCMPLT_INTR will be set to 1, to indicate CL37 AN is
> > complete. So add another way to get the state for CL37 SGMII."
> >
> > I tried to reproduce this issue, but it didn't seem to exist.
> > In the current logic:
> >
> > link up -> read status from CL37_ANSGM_STS -> CL37_ANCMPLT_INTR not clear
> > link down -> read status from BMCR -> CL37_ANCMPLT_INTR clear
> >
> > It also works, although CL37_ANCMPLT_INTR is not cleared every time, and AN
> > restart is absent. But BMCR looks like it only wants to be return as 0, it is
> > weird.
> >
> > So I think Wangxun NICs also can be applied to general code as well, for
> > getting state in C37 SGMII mode.
>
> Hi Jiawen,
>
> Thanks so much for testing and confirming that the patch works on Wangxun
> NICs. That's a huge relief.
>
> You're right to ask about the CL37_ANCMPLT_INTR clear and ANRESTART. Let
> me explain what I observed on RK3568.
>
> The key difference is that on RK3568's XPCS, we're working in the MAC
> side (TX_CONFIG = 0), whereas on Wangxun it seems you're in the PHY side
> (TX_CONFIG = 1). This is based on the commit 2a22b7ae2fa3 where you set
> TX_CONFIG = 1.
Get it.
>
> > However, I am wondering whether CL37_ANCMPLT_INTR clear and ANRESTART are
> > necessary.
>
> On the RK3568 (MAC side), according to the DesignWare XPCS TRM, the
> hardware behavior is as follows:
>
> - After a software reset (BMCR_RESET), ANENABLE is automatically set to 1
> (the hardware reset value). So auto-negotiation starts automatically.
>
> - When the link is up, CL37_ANSGM_STS contains the negotiated speed/duplex
> and the link status.
>
> - When the link goes down, CL37_ANSGM_STS becomes all zero, but
> CL37_ANCMPLT_INTR remains set to 1.
>
> - Crucially, the PCS does **not** automatically restart AN when the link
> comes back up. Without an explicit ANRESTART, the link stays down
> forever.
>
> So on RK3568, the ANRESTART is mandatory to recover from a link-down
> event. Without it, the link never comes back up after a cable disconnect.
>
> In my current logic:
>
> 1. If CL37_ANSGM_STS has valid speed/duplex bits, report the link as up
> and return.
>
> 2. If CL37_ANSGM_STS is zero but CL37_ANCMPLT_INTR is set, the link is
> down. We clear the interrupt and issue an ANRESTART to start a new
> negotiation.
>
> This works reliably on RK3568 in SGMII MAC side mode.
>
> Since you confirmed that the general code path also works on Wangxun, I
> wonder: does the ANRESTART also work on your side? If yes, perhaps we can
> make this the common path for all hardware, not just RK3568.
>
> If you can test the ANRESTART logic on Wangxun and it works, I'd be happy
> to submit a standalone fix that makes this the common code path.
I've test this patch with removing the restriction on Wangxun PMA. At least
ANRESTART logic does not have a negative impact.
>
> Otherwise, the current approach (skipping the ANRESTART trigger on
> Wangxun NICs in c37_sgmii_get_state) remains safe.
>
> If you're comfortable with the change, I'd appreciate it if you could add
> your Tested-by tag to the commit. I'll also make sure to include you on
> the CC list for any future revisions or related fixes.
>
> Thanks again for testing!
Thanks for the improvements.
Tested-by: Jiawen Wu <jiawenwu@trustnetic.com>
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH v7 00/13] ZTE zx297520v3 clock bindings and driver
From: Krzysztof Kozlowski @ 2026-07-17 6:37 UTC (permalink / raw)
To: Stefan Dösinger
Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Brian Masney, Vinod Koul,
Neil Armstrong, Russell King, linux-clk, devicetree, linux-kernel,
linux-arm-kernel, linux-phy
In-Reply-To: <20260717-zx29clk-v7-0-408411cfcf36@gmail.com>
On Fri, Jul 17, 2026 at 12:35:36AM +0300, Stefan Dösinger wrote:
> Hi,
>
Where did you explain merging dependencies? Patch 2 (or 3) depends on
patch 1, so do not expect maintainers to guess that.
Best regards,
Krzysztof
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH v7 02/13] dt-bindings: clk: zte: Add zx297520v3 top clock and reset controller
From: Krzysztof Kozlowski @ 2026-07-17 6:36 UTC (permalink / raw)
To: Stefan Dösinger
Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Brian Masney, Vinod Koul,
Neil Armstrong, Russell King, linux-clk, devicetree, linux-kernel,
linux-arm-kernel, linux-phy
In-Reply-To: <20260717-zx29clk-v7-2-408411cfcf36@gmail.com>
On Fri, Jul 17, 2026 at 12:35:38AM +0300, Stefan Dösinger wrote:
> diff --git a/Documentation/devicetree/bindings/clock/zte,zx297520v3-topcrm.yaml b/Documentation/devicetree/bindings/clock/zte,zx297520v3-topcrm.yaml
> new file mode 100644
> index 000000000000..454997063573
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/zte,zx297520v3-topcrm.yaml
> @@ -0,0 +1,123 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/clock/zte,zx297520v3-topcrm.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: ZTE zx297520v3 SoC top clock and reset controller
> +
> +maintainers:
> + - Stefan Dösinger <stefandoesinger@gmail.com>
> +
> +description: |
> + The zx297520v3 top clock and reset controller generates clocks for core
> + devices on the board like the main bus, USB and timers. In addition to clocks
> + it has reset controls for peripherals, a global board reset, watchdog reset
> + controls and a USB status register.
> +
> + The controller has two clock inputs: a 26 MHz and a 32 KHz external
> + oscillator. They need to be provided as input clocks. The controller provides
> + PLL output frequencies to downstream clock controllers.
> +
> + The USB PHY functionality is exposed as a child node documented in
> + zte,zx297520v3-usb-phy.yaml.
> +
> + All available clocks are defined as preprocessor macros in the
> + "include/dt-bindings/clock/zte,zx297520v3-clk.h" header. The resets are
> + defined in the "include/dt-bindings/reset/zte,zx297520v3-reset.h" header.
> +
> +properties:
> + compatible:
> + items:
> + - const: zte,zx297520v3-topcrm
> + - const: syscon
> +
> + reg:
> + maxItems: 1
> +
> + clocks:
> + items:
> + - description: 26 MHz external oscillator
> + - description: 32 KHz external oscillator
> +
> + clock-names:
> + items:
> + - const: osc26m
> + - const: osc32k
> +
> + "#address-cells":
> + const: 1
> +
> + "#size-cells":
> + const: 0
> +
> + "#clock-cells":
> + const: 1
> +
> + "#reset-cells":
> + const: 1
> +
> + syscon-reboot:
> + type: object
> + $ref: /schemas/power/reset/syscon-reboot.yaml#
> + description:
> + Reboot method for the SoC.
> +
> + usb-phy@84:
No, do not children with and without addressing. syscon-reboot should
have address as well. Or this one should not, since it looks pretty
redundant - it is deducible from the top level compatible, no? So just
usb-phy.
> + compatible = "zte,zx297520v3-usb-phy";
> + type: object
> + $ref: /schemas/phy/zte,zx297520v3-usb-phy.yaml#
> + description:
> + USB and HSIC PHY controller.
> +
> +required:
> + - compatible
> + - reg
> + - clocks
> + - clock-names
> + - syscon-reboot
> + - usb-phy@84
> + - "#address-cells"
> + - "#size-cells"
> + - "#clock-cells"
> + - "#reset-cells"
> +
> +unevaluatedProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/interrupt-controller/arm-gic.h>
> + #include <dt-bindings/reset/zte,zx297520v3-reset.h>
> + #include <dt-bindings/phy/phy-zte-zx297520v3-usb.h>
> + #include <dt-bindings/clock/zte,zx297520v3-clk.h>
> +
> + topcrm: clock-controller@13b000 {
> + compatible = "zte,zx297520v3-topcrm", "syscon";
> + reg = <0x0013b000 0x400>;
> + clocks = <&osc26m>, <&osc32k>;
> + clock-names = "osc26m", "osc32k";
> + #address-cells = <1>;
> + #size-cells = <0>;
> + #clock-cells = <1>;
> + #reset-cells = <1>;
> +
> + syscon-reboot {
> + compatible = "syscon-reboot";
> + offset = <0x0>;
> + mask = <0x1>;
> + value = <0x1>;
> + };
> +
> + usb-phy@84 {
> + reg = <0x84>;
> + interrupts = <GIC_SPI 42 IRQ_TYPE_EDGE_RISING>,
> + <GIC_SPI 43 IRQ_TYPE_EDGE_RISING>,
> + <GIC_SPI 45 IRQ_TYPE_EDGE_RISING>,
> + <GIC_SPI 46 IRQ_TYPE_EDGE_RISING>;
Here also misaligned.
> + interrupt-names = "usb-up", "usb-down", "hsic-up", "hsic-down";
> + resets = <&topcrm ZX297520V3_USB_PHY_RESET>,
> + <&topcrm ZX297520V3_HSIC_PHY_RESET>;
> + reset-names = "usb", "hsic";
> + #phy-cells = <1>;
> + };
> + };
> diff --git a/MAINTAINERS b/MAINTAINERS
> index cb50c2e3b4ea..5f962b9947ab 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -3878,10 +3878,13 @@ L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
> S: Odd fixes
> F: Documentation/arch/arm/zte/
> F: Documentation/devicetree/bindings/arm/zte.yaml
> +F: Documentation/devicetree/bindings/clock/zte,zx297520v3-topcrm.yaml
> F: Documentation/devicetree/bindings/phy/zte,zx297520v3-usb-phy.yaml
> F: arch/arm/boot/dts/zte/
> F: arch/arm/mach-zte/
> +F: include/dt-bindings/clock/zte,zx297520v3-clk.h
> F: include/dt-bindings/phy/phy-zte-zx297520v3-usb.h
> +F: include/dt-bindings/reset/zte,zx297520v3-reset.h
>
> ARM/ZYNQ ARCHITECTURE
> M: Michal Simek <michal.simek@amd.com>
> diff --git a/include/dt-bindings/clock/zte,zx297520v3-clk.h b/include/dt-bindings/clock/zte,zx297520v3-clk.h
Same mistake. Filename MUST match the compatible.
> new file mode 100644
> index 000000000000..1f010ff4e6ca
> --- /dev/null
> +++ b/include/dt-bindings/clock/zte,zx297520v3-clk.h
> @@ -0,0 +1,66 @@
> +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
> +/*
> + * Copyright (C) Stefan Dösinger.
> + */
> +
> +#ifndef __DT_BINDINGS_CLOCK_ZX297520V3_H
> +#define __DT_BINDINGS_CLOCK_ZX297520V3_H
> +
> +#define ZX297520V3_M0_WCLK 1
Start from 0 since you started resets also from 0.
> +#define ZX297520V3_SRAM1_PCLK 2
> +#define ZX297520V3_SRAM2_PCLK 3
> +#define ZX297520V3_UART0_WCLK 4
> +#define ZX297520V3_UART0_PCLK 5
> +#define ZX297520V3_I2C0_WCLK 6
> +#define ZX297520V3_I2C0_PCLK 7
> +#define ZX297520V3_RTC_WCLK 8
> +#define ZX297520V3_RTC_PCLK 9
> +#define ZX297520V3_LPM_GSM_WCLK 10
> +#define ZX297520V3_LPM_GSM_PCLK 11
> +#define ZX297520V3_LPM_LTE_WCLK 12
> +#define ZX297520V3_LPM_LTE_PCLK 13
> +#define ZX297520V3_LPM_TD_WCLK 14
> +#define ZX297520V3_LPM_TD_PCLK 15
> +#define ZX297520V3_LPM_W_WCLK 16
> +#define ZX297520V3_LPM_W_PCLK 17
> +#define ZX297520V3_TIMER_T08_WCLK 18
> +#define ZX297520V3_TIMER_T08_PCLK 19
> +#define ZX297520V3_TIMER_T09_WCLK 20
> +#define ZX297520V3_TIMER_T09_PCLK 21
> +#define ZX297520V3_MPLL 22
> +#define ZX297520V3_MPLL_D5 23
> +#define ZX297520V3_DPLL 24
> +#define ZX297520V3_GPLL 25
> +#define ZX297520V3_GPLL_D2 26
> +#define ZX297520V3_GATED_OSC26M 27
> +#define ZX297520V3_PMM_WCLK 28
> +#define ZX297520V3_PMM_PCLK 29
> +#define ZX297520V3_OUT0_WCLK 30
> +#define ZX297520V3_OUT1_WCLK 31
> +#define ZX297520V3_OUT2_WCLK 32
> +#define ZX297520V3_OUT32K_WCLK 33
> +#define ZX297520V3_RMIIPHY_WCLK 34
> +#define ZX297520V3_TIMER_T12_WCLK 35
> +#define ZX297520V3_TIMER_T12_PCLK 36
> +#define ZX297520V3_TIMER_T13_WCLK 37
> +#define ZX297520V3_TIMER_T13_PCLK 38
> +#define ZX297520V3_TIMER_T14_WCLK 39
> +#define ZX297520V3_TIMER_T14_PCLK 40
> +#define ZX297520V3_TIMER_T15_WCLK 41
> +#define ZX297520V3_TIMER_T15_PCLK 42
> +#define ZX297520V3_TIMER_T16_WCLK 43
> +#define ZX297520V3_TIMER_T16_PCLK 44
> +#define ZX297520V3_TIMER_T17_WCLK 45
> +#define ZX297520V3_TIMER_T17_PCLK 46
> +#define ZX297520V3_WDT_T18_WCLK 47
> +#define ZX297520V3_WDT_T18_PCLK 48
> +#define ZX297520V3_USIM1_WCLK 49
> +#define ZX297520V3_USIM1_PCLK 50
> +#define ZX297520V3_AHB_WCLK 51
> +#define ZX297520V3_AHB_PCLK 52
> +#define ZX297520V3_USB_WCLK 53
> +#define ZX297520V3_USB_PCLK 54
> +#define ZX297520V3_HSIC_WCLK 55
> +#define ZX297520V3_HSIC_PCLK 56
> +
> +#endif /* __DT_BINDINGS_CLOCK_ZX297520V3_H */
> diff --git a/include/dt-bindings/reset/zte,zx297520v3-reset.h b/include/dt-bindings/reset/zte,zx297520v3-reset.h
> new file mode 100644
> index 000000000000..43db72bb59de
> --- /dev/null
> +++ b/include/dt-bindings/reset/zte,zx297520v3-reset.h
Same here.
Best regards,
Krzysztof
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH v7 01/13] dt-bindings: phy: Add zx297520v3 USB phy documentation
From: Krzysztof Kozlowski @ 2026-07-17 6:32 UTC (permalink / raw)
To: Stefan Dösinger
Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Brian Masney, Vinod Koul,
Neil Armstrong, Russell King, linux-clk, devicetree, linux-kernel,
linux-arm-kernel, linux-phy
In-Reply-To: <20260717-zx29clk-v7-1-408411cfcf36@gmail.com>
On Fri, Jul 17, 2026 at 12:35:37AM +0300, Stefan Dösinger wrote:
> This binding will be used as a subnode of topcrm, which will be added in
> the next patch.
Irrelevant, drop. Also not true, because next patch is driver patch.
>
> Why is this a child node and not #phy-cells added to topcrm itself,
> like clocks and resets as writing-bindings.rst suggests? Because
> of_phy_provider_register checks if the of_node the PHY provider is added
> to belongs to the device or is a child of the device, so I can't put the
> PHY driver into an MFD (or AUX) child without its own node.
>
> Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
>
> ---
>
> How do I handle examples? It would be identical to the example in
> topcrm. The example needs the reset defines from the next patch in
> either case.
1. No, it does not need. Just use whatever numbers.
2. Other code would give you the hint: one example in parent's schema.
> ---
> .../bindings/phy/zte,zx297520v3-usb-phy.yaml | 96 ++++++++++++++++++++++
> MAINTAINERS | 2 +
> include/dt-bindings/phy/phy-zte-zx297520v3-usb.h | 12 +++
> 3 files changed, 110 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/phy/zte,zx297520v3-usb-phy.yaml b/Documentation/devicetree/bindings/phy/zte,zx297520v3-usb-phy.yaml
> new file mode 100644
> index 000000000000..8bad9365d5cc
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/phy/zte,zx297520v3-usb-phy.yaml
> @@ -0,0 +1,96 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/phy/zte,zx297520v3-usb-phy.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: ZTE zx297520v3 USB and HSIC PHY
> +
> +maintainers:
> + - Stefan Dösinger <stefandoesinger@gmail.com>
> +
> +description: |
> + This PHY is found on zx297520v3 boards. It has no configurability on its own,
> + but it does require a correct reset and wait sequence to initialize. It can
> + provide interrupt notification when USB is connected and disconnected.
> +
> + The phy is a component of the board's topcrm controller. The hardware needs to
> + be declared as a child node of the zte,zx297520v3-topcrm node.
> +
> + The register space and IRQs always account for two PHYs: One USB 2.0 OTG phy
> + and a HSIC PHY. Not all boards have both. If one is missing, the corresponding
> + ready flag will never be set.
> +
> + The "include/dt-bindings/phy/phy-zte-zx297520v3-usb.h" header contains the
> + definition for the PHY indices.
> +
> +properties:
> + compatible:
> + const: zte,zx297520v3-usb-phy
> +
> + reg:
> + maxItems: 1
> +
> + interrupts:
> + items:
> + - description: IRQ reporting USB connection
> + - description: IRQ reporting USB disconnection
> + - description: IRQ reporting HSIC connection
> + - description: IRQ reporting HSIC disconnection
> +
> + interrupt-names:
> + items:
> + - const: usb-up
> + - const: usb-down
> + - const: hsic-up
> + - const: hsic-down
> +
> + resets:
> + items:
> + - description: USB phy reset
> + - description: HSIC phy reset
Why do you reference here resets from the parent? That's a clear signal
you have one device, so one device node regardless of Linux phy core
behavior.
> +
> + reset-names:
> + items:
> + - const: usb
> + - const: hsic
> +
> + "#phy-cells":
> + const: 1
> +
> +required:
> + - compatible
> + - reg
> + - interrupts
> + - interrupt-names
> + - resets
> + - reset-names
> + - "#phy-cells"
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/interrupt-controller/arm-gic.h>
> + #include <dt-bindings/phy/phy-zte-zx297520v3-usb.h>
> +
> + topcrm: clock-controller {
> + compatible = "zte,zx297520v3-topcrm", "syscon";
> + #address-cells = <1>;
> + #size-cells = <0>;
> + #reset-cells = <1>;
Drop node, you have warnings.
> +
> + usb-phy@84 {
> + compatible = "zte,zx297520v3-usb-phy";
> + reg = <0x84>;
> + interrupts = <GIC_SPI 42 IRQ_TYPE_EDGE_RISING>,
> + <GIC_SPI 43 IRQ_TYPE_EDGE_RISING>,
> + <GIC_SPI 45 IRQ_TYPE_EDGE_RISING>,
> + <GIC_SPI 46 IRQ_TYPE_EDGE_RISING>;
Align it properly/
> + interrupt-names = "usb-up", "usb-down", "hsic-up", "hsic-down";
> + resets = <&topcrm 18>,
> + <&topcrm 20>;
Same here.
> + reset-names = "usb", "hsic";
> + #phy-cells = <1>;
> + };
> + };
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 8729cea57c3d..cb50c2e3b4ea 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -3878,8 +3878,10 @@ L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
> S: Odd fixes
> F: Documentation/arch/arm/zte/
> F: Documentation/devicetree/bindings/arm/zte.yaml
> +F: Documentation/devicetree/bindings/phy/zte,zx297520v3-usb-phy.yaml
> F: arch/arm/boot/dts/zte/
> F: arch/arm/mach-zte/
> +F: include/dt-bindings/phy/phy-zte-zx297520v3-usb.h
>
> ARM/ZYNQ ARCHITECTURE
> M: Michal Simek <michal.simek@amd.com>
> diff --git a/include/dt-bindings/phy/phy-zte-zx297520v3-usb.h b/include/dt-bindings/phy/phy-zte-zx297520v3-usb.h
> new file mode 100644
> index 000000000000..8a0a3ccbdd63
> --- /dev/null
> +++ b/include/dt-bindings/phy/phy-zte-zx297520v3-usb.h
Filename MUST match compatible.
> @@ -0,0 +1,12 @@
> +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
> +/*
> + * Copyright (C) Stefan Dösinger.
> + */
> +
> +#ifndef __DT_BINDINGS_PHY_USB_ZX297520V3_H
> +#define __DT_BINDINGS_PHY_USB_ZX297520V3_H
> +
> +#define ZX297520V3_USB_PHY 0
> +#define ZX297520V3_HSIC_PHY 1
> +
> +#endif /* __DT_BINDINGS_PHY_USB_ZX297520V3_H */
>
> --
> 2.54.0
>
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [RFC PATCH 06/10] net: pcs: xpcs: improve SGMII AN state handling for Rockchip RK3568
From: Coia Prant @ 2026-07-17 6:28 UTC (permalink / raw)
To: Jiawen Wu
Cc: Andrew Lunn, kuba, davem, edumazet, pabeni, andrew+netdev, robh,
krzk+dt, heiko, netdev, linux-rockchip, devicetree,
linux-arm-kernel, linux-phy, Mengyuan Lou
In-Reply-To: <000c01dd1593$2ac0b0f0$804212d0$@trustnetic.com>
Jiawen Wu <jiawenwu@trustnetic.com> 于2026年7月17日周五 10:23写道:
>
> On Wed, Jul 15, 2026 7:05 AM, Coia Prant wrote:
> > Andrew Lunn <andrew@lunn.ch> 于2026年7月15日周三 06:44写道:
> > >
> > > On Wed, Jul 15, 2026 at 03:08:34AM +0800, Coia Prant wrote:
> > > > Commit 2a22b7ae2fa3 ("net: pcs: xpcs: adapt Wangxun NICs for SGMII mode")
> > >
> > > You do not appear to Cc: the Wangxun NIC people. It would be good to
> > > have there comments on this change.
> >
> > I apologize; the output from get_maintainer.pl is very long. I’ve
> > heard that having too many recipients can cause the PATCH to be
> > rejected by the LKML mail server.
> >
> > I have added Wangxun maintainer (Jiawen Wu <jiawenwu@trustnetic.com>
> > and Mengyuan Lou <mengyuanlou@net-swift.com>) to the CC list.
> >
> > > > Fixes: 2a22b7ae2fa3 ("net: pcs: xpcs: adapt Wangxun NICs for SGMII mode")
> > > > Signed-off-by: Coia Prant <coiaprant@gmail.com>
> > >
> > > Please don't mix fixed and new code. Is this a real fix? Should it be
> > > back ported to stable?
> >
> > I am not sure if this is a specific characteristic of Wangxun NICs, as
> > I do not have any available for testing.
> >
> > The behavior of the Rockchip DW XPCS IP core matches what is described
> > in the commit message (even though phylink brings the link to down
> > based on the phydev link status).
> >
> > This appears to be a bug (at least on Rockchip platforms) or Wangxun
> > NICs features.
> >
> > However, I cannot confirm whether Wangxun NICs behave the same way.
> > Therefore, I have kept their code as is for now.
> >
> > Could a Wangxun NICs maintainer provide some feedback based on testing?
> >
> > If we can confirm that this is indeed a bug, I can submit a separate fix.
> >
> > I would greatly appreciate it.
> >
> > Thanks.
>
> Hi Coia,
>
> I have tested this patch and it works on Wangxun NICs, thanks.
>
> The commit 2a22b7ae2fa3 ("net: pcs: xpcs: adapt Wangxun NICs for SGMII mode")
> is too long ago for me to recall exactly what that thought was.
>
> The log shows:
>
> "On this device, CL37_ANSGM_STS (bit[4:1] of VR_MII_AN_INTR_STS) indicates
> the status received from remote link during the auto-negotiation, and
> self-clear after the auto-negotiation is complete.
> Meanwhile, CL37_ANCMPLT_INTR will be set to 1, to indicate CL37 AN is
> complete. So add another way to get the state for CL37 SGMII."
>
> I tried to reproduce this issue, but it didn't seem to exist.
> In the current logic:
>
> link up -> read status from CL37_ANSGM_STS -> CL37_ANCMPLT_INTR not clear
> link down -> read status from BMCR -> CL37_ANCMPLT_INTR clear
>
> It also works, although CL37_ANCMPLT_INTR is not cleared every time, and AN
> restart is absent. But BMCR looks like it only wants to be return as 0, it is
> weird.
>
> So I think Wangxun NICs also can be applied to general code as well, for
> getting state in C37 SGMII mode.
Hi Jiawen,
Thanks so much for testing and confirming that the patch works on Wangxun
NICs. That's a huge relief.
You're right to ask about the CL37_ANCMPLT_INTR clear and ANRESTART. Let
me explain what I observed on RK3568.
The key difference is that on RK3568's XPCS, we're working in the MAC
side (TX_CONFIG = 0), whereas on Wangxun it seems you're in the PHY side
(TX_CONFIG = 1). This is based on the commit 2a22b7ae2fa3 where you set
TX_CONFIG = 1.
> However, I am wondering whether CL37_ANCMPLT_INTR clear and ANRESTART are
> necessary.
On the RK3568 (MAC side), according to the DesignWare XPCS TRM, the
hardware behavior is as follows:
- After a software reset (BMCR_RESET), ANENABLE is automatically set to 1
(the hardware reset value). So auto-negotiation starts automatically.
- When the link is up, CL37_ANSGM_STS contains the negotiated speed/duplex
and the link status.
- When the link goes down, CL37_ANSGM_STS becomes all zero, but
CL37_ANCMPLT_INTR remains set to 1.
- Crucially, the PCS does **not** automatically restart AN when the link
comes back up. Without an explicit ANRESTART, the link stays down
forever.
So on RK3568, the ANRESTART is mandatory to recover from a link-down
event. Without it, the link never comes back up after a cable disconnect.
In my current logic:
1. If CL37_ANSGM_STS has valid speed/duplex bits, report the link as up
and return.
2. If CL37_ANSGM_STS is zero but CL37_ANCMPLT_INTR is set, the link is
down. We clear the interrupt and issue an ANRESTART to start a new
negotiation.
This works reliably on RK3568 in SGMII MAC side mode.
Since you confirmed that the general code path also works on Wangxun, I
wonder: does the ANRESTART also work on your side? If yes, perhaps we can
make this the common path for all hardware, not just RK3568.
If you can test the ANRESTART logic on Wangxun and it works, I'd be happy
to submit a standalone fix that makes this the common code path.
Otherwise, the current approach (skipping the ANRESTART trigger on
Wangxun NICs in c37_sgmii_get_state) remains safe.
If you're comfortable with the change, I'd appreciate it if you could add
your Tested-by tag to the commit. I'll also make sure to include you on
the CC list for any future revisions or related fixes.
Thanks again for testing!
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH RFC v4 9/9] arm64: dts: qcom: glymur: Wire PCIe3a/3b to shared Gen5x8 PHY
From: Qiang Yu @ 2026-07-17 3:02 UTC (permalink / raw)
To: Konrad Dybcio
Cc: Vinod Koul, Neil Armstrong, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Bjorn Andersson, Konrad Dybcio,
linux-arm-msm, linux-phy, devicetree, linux-kernel
In-Reply-To: <f83556e7-dbb1-4926-a2a0-4690cdbad941@oss.qualcomm.com>
On Fri, Jul 10, 2026 at 03:25:37PM +0200, Konrad Dybcio wrote:
> On 7/10/26 3:11 PM, Qiang Yu wrote:
> > On Fri, Jul 10, 2026 at 10:44:20AM +0200, Konrad Dybcio wrote:
> >> On 7/10/26 4:19 AM, Qiang Yu wrote:
> >>> On Mon, Jun 29, 2026 at 11:20:07AM +0200, Konrad Dybcio wrote:
> >>>> On 6/29/26 7:05 AM, Qiang Yu wrote:
> >>>>> On Wed, Jun 17, 2026 at 01:19:49PM +0200, Konrad Dybcio wrote:
> >>>>>> On 5/19/26 7:47 AM, Qiang Yu wrote:
> >>>>>>> Glymur PCIe3 uses a single shared Gen5x8 QMP PHY block. Model PCIe3a and
> >>>>>>> PCIe3b as consumers of that shared PHY provider instead of separate PHY
> >>>>>>> nodes.
>
> [...]
>
> >>>>>>> + linux,pci-domain = <3>;
> >>>>>>> + num-lanes = <8>;
> >>>>>>
> >>>>>> Is it fine to keep num-lanes 8 here even for configurations with
> >>>>>> bifurcated PHY?
> >>>>>>
> >>>>>> I would assume so, given essentially this is a x8 host, whose 4
> >>>>>> lanes may simply be effectively NC
> >>>>>>
> >>>>> Actually, on existing platforms, the PCIe3a and PCIe3b controllers are
> >>>>> never enabled at the same time. When PCIe3a is exposed, it is always in an
> >>>>> x8 slot. But if we have a x4+x4 platform in future, we can simply override
> >>>>> num-lanes to 4 in the board.dts.
> >>>>
> >>>> My question is whether that will be necessary - if yes, sure, we
> >>>> can do it, but if not, we can conclude on this early and not have
> >>>> to fight over it in a couple months
> >>>>
> >>> I think we do need to override it in that case. If both PCIe3a and PCIe3b
> >>> are enabled in x4+x4 mode but PCIe3a keeps num-lanes = <8>, userspace
> >>> will see an 8-lane slot. If an x8-capable EP is connected to that slot,
> >>> both ends will advertise x8 support, but the link is up at x4. That looks
> >>> like a genuine bug from the user's point of view.
> >>
> >> Do we know what's advertised on x86 PCs with bifurcated lanes?
> >>
> > On QCB, it advertise x8. On CRD, PCIe3a is hidden.
>
> No no, I meant to ask whether we know what the userspace gets to see on
> e.g. an AMD system with a bifurcated slot. I would expect that LnkCap
> may report a different value, but not necessarily. I would assume we
> should probably mimic whatever that does, unless the spec takes a
> stance on what should be done.
>
I don't have any AMD/Intel hardware with a bifurcated slot at hand,
and I couldn't find an lspci example showing what LnkCap reports in
that case either, so I can't confirm what x86 does here.
But can you elaborate on what is the benefits to set it to 8 in x4+x4
mode?
- Qiang Yu
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox