From: "Luca Ceresoli" <luca.ceresoli@bootlin.com>
To: "Chaoyi Chen" <kernel@airkyi.com>,
"Heikki Krogerus" <heikki.krogerus@linux.intel.com>,
"Greg Kroah-Hartman" <gregkh@linuxfoundation.org>,
"Dmitry Baryshkov" <dmitry.baryshkov@oss.qualcomm.com>,
"Peter Chen" <hzpeterchen@gmail.com>,
"Rob Herring" <robh@kernel.org>,
"Krzysztof Kozlowski" <krzk+dt@kernel.org>,
"Conor Dooley" <conor+dt@kernel.org>,
"Vinod Koul" <vkoul@kernel.org>,
"Kishon Vijay Abraham I" <kishon@kernel.org>,
"Heiko Stuebner" <heiko@sntech.de>,
"Sandy Huang" <hjc@rock-chips.com>,
"Andy Yan" <andy.yan@rock-chips.com>,
"Yubing Zhang" <yubing.zhang@rock-chips.com>,
"Frank Wang" <frank.wang@rock-chips.com>,
"Andrzej Hajda" <andrzej.hajda@intel.com>,
"Neil Armstrong" <neil.armstrong@linaro.org>,
"Robert Foss" <rfoss@kernel.org>,
"Laurent Pinchart" <Laurent.pinchart@ideasonboard.com>,
"Jonas Karlman" <jonas@kwiboo.se>,
"Jernej Skrabec" <jernej.skrabec@gmail.com>,
"Maarten Lankhorst" <maarten.lankhorst@linux.intel.com>,
"Maxime Ripard" <mripard@kernel.org>,
"Thomas Zimmermann" <tzimmermann@suse.de>,
"David Airlie" <airlied@gmail.com>,
"Simona Vetter" <simona@ffwll.ch>,
"Amit Sunil Dhamne" <amitsd@google.com>,
"Chaoyi Chen" <chaoyi.chen@rock-chips.com>,
"Dragan Simic" <dsimic@manjaro.org>,
"Johan Jonker" <jbx6244@gmail.com>,
"Diederik de Haas" <didi.debian@cknow.org>,
"Peter Robinson" <pbrobinson@gmail.com>
Cc: <linux-usb@vger.kernel.org>, <devicetree@vger.kernel.org>,
<linux-kernel@vger.kernel.org>, <linux-phy@lists.infradead.org>,
<linux-arm-kernel@lists.infradead.org>,
<linux-rockchip@lists.infradead.org>,
<dri-devel@lists.freedesktop.org>
Subject: Re: [PATCH v9 08/10] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection
Date: Tue, 11 Nov 2025 16:14:11 +0100 [thread overview]
Message-ID: <DE5YP3AVGOG3.OHP68Z0F6KBU@bootlin.com> (raw)
In-Reply-To: <20251111105040.94-9-kernel@airkyi.com>
Hello Chaoyi,
On Tue Nov 11, 2025 at 11:50 AM CET, Chaoyi Chen wrote:
> 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>
[...]
> @@ -966,28 +1084,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 = NULL;
> struct drm_connector *connector;
> - struct cdn_dp_port *port;
> - struct drm_device *drm_dev = data;
> - int ret, i;
[...]
> + if (next_bridge) {
> + ret = drm_bridge_attach(encoder, next_bridge, bridge,
> + DRM_BRIDGE_ATTACH_NO_CONNECTOR);
> + if (ret)
> + return ret;
> +
> + last_bridge = next_bridge;
> + while (drm_bridge_get_next_bridge(last_bridge))
> + last_bridge = drm_bridge_get_next_bridge(last_bridge);
DRM bridges are now refcounted, and you are not calling drm_bridge_get()
and drm_bridge_put() here. But here you can use
drm_bridge_chain_get_last_bridge() which will simplify your job.
Don't forget to call drm_bridge_put() on the returned bridge when the
bridge is not referenced anymore. This should be as easy as adding a
cleanup action on the variable declaration above:
- struct drm_bridge *last_bridge = NULL;
+ struct drm_bridge *last_bridge __free(drm_bridge_put) = NULL;
> @@ -1029,8 +1147,102 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data)
> return ret;
> }
>
> + if (last_bridge)
> + connector->fwnode = fwnode_handle_get(of_fwnode_handle(last_bridge->of_node));
> +
> drm_connector_attach_encoder(connector, encoder);
>
> + 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].bridge = bridge;
You are storing a reference to a drm_bridge, so have to increment the
refcount:
dp->next_bridge_list[count].bridge = drm_bridge_get(bridge);
^^^^^^^^^^^^^^
FYI there is a plan to replace of_drm_find_bridge() with a function that
increases the bridge refcount before returning the bridge, but it's not
there yet. When that will happen, the explicit drm_bridge_get() won't be
needed anymore and this code can be updated accordingly.
Also you have to call drm_bridge_put() to release that reference when the
pointer goes away. I guess that should happen in cdn_dp_unbind().
> +static int cdn_dp_bind(struct device *dev, struct device *master, void *data)
> +{
In this function you do:
...(see below)...
> + 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);
1. compute the next bridges and store them in dp->next_bridge_list[]
...
> + if (ret)
> + return ret;
> +
> + 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))
> + return PTR_ERR(dp_bridge);
> + 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].bridge;
> + ret = cdn_bridge_add(dev, bridge, next_bridge, encoder);
...
2. pass the dp->next_bridge_list[i].bridge to cdn_bridge_add
3. not use dp->next_bridge_list[i] elsewhere
So you may want to change this function to parse into a local array, with
function scope. If you do this, the drm_bridge_get/put() I mentioned above
should still exist, but would be localized to this function, thus even
easier to handle.
Even better, you can parse the DT one bridge at a time inside the for loop,
so you don't need to store any next_bridge pointer array. This will need a
bit of rework of cdn_dp_parse_next_bridge_dt() though, and I haven't
checked in detail so it might be not worth.
[...]
> +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_next_bridge {
> + struct cdn_dp_device *parent;
> + struct drm_bridge *bridge;
> + int id;
The @parent and @id fields are unused if I'm not mistaken.
If it is the case then you can... (see below)
> 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 cdn_dp_next_bridge next_bridge_list[MAX_PHY];
...replace this line with:
struct drm_bridge *next_bridge[MAX_PHY];
Unless of course you just don't store the next_bridge at all, as I
suggested above, and which looks way easier and more efficient.
Best regards,
Luca
--
Luca Ceresoli, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
WARNING: multiple messages have this Message-ID (diff)
From: "Luca Ceresoli" <luca.ceresoli@bootlin.com>
To: "Chaoyi Chen" <kernel@airkyi.com>,
"Heikki Krogerus" <heikki.krogerus@linux.intel.com>,
"Greg Kroah-Hartman" <gregkh@linuxfoundation.org>,
"Dmitry Baryshkov" <dmitry.baryshkov@oss.qualcomm.com>,
"Peter Chen" <hzpeterchen@gmail.com>,
"Rob Herring" <robh@kernel.org>,
"Krzysztof Kozlowski" <krzk+dt@kernel.org>,
"Conor Dooley" <conor+dt@kernel.org>,
"Vinod Koul" <vkoul@kernel.org>,
"Kishon Vijay Abraham I" <kishon@kernel.org>,
"Heiko Stuebner" <heiko@sntech.de>,
"Sandy Huang" <hjc@rock-chips.com>,
"Andy Yan" <andy.yan@rock-chips.com>,
"Yubing Zhang" <yubing.zhang@rock-chips.com>,
"Frank Wang" <frank.wang@rock-chips.com>,
"Andrzej Hajda" <andrzej.hajda@intel.com>,
"Neil Armstrong" <neil.armstrong@linaro.org>,
"Robert Foss" <rfoss@kernel.org>,
"Laurent Pinchart" <Laurent.pinchart@ideasonboard.com>,
"Jonas Karlman" <jonas@kwiboo.se>,
"Jernej Skrabec" <jernej.skrabec@gmail.com>,
"Maarten Lankhorst" <maarten.lankhorst@linux.intel.com>,
"Maxime Ripard" <mripard@kernel.org>,
"Thomas Zimmermann" <tzimmermann@suse.de>,
"David Airlie" <airlied@gmail.com>,
"Simona Vetter" <simona@ffwll.ch>,
"Amit Sunil Dhamne" <amitsd@google.com>,
"Chaoyi Chen" <chaoyi.chen@rock-chips.com>,
"Dragan Simic" <dsimic@manjaro.org>,
"Johan Jonker" <jbx6244@gmail.com>,
"Diederik de Haas" <didi.debian@cknow.org>,
"Peter Robinson" <pbrobinson@gmail.com>
Cc: <linux-usb@vger.kernel.org>, <devicetree@vger.kernel.org>,
<linux-kernel@vger.kernel.org>, <linux-phy@lists.infradead.org>,
<linux-arm-kernel@lists.infradead.org>,
<linux-rockchip@lists.infradead.org>,
<dri-devel@lists.freedesktop.org>
Subject: Re: [PATCH v9 08/10] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection
Date: Tue, 11 Nov 2025 16:14:11 +0100 [thread overview]
Message-ID: <DE5YP3AVGOG3.OHP68Z0F6KBU@bootlin.com> (raw)
In-Reply-To: <20251111105040.94-9-kernel@airkyi.com>
Hello Chaoyi,
On Tue Nov 11, 2025 at 11:50 AM CET, Chaoyi Chen wrote:
> 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>
[...]
> @@ -966,28 +1084,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 = NULL;
> struct drm_connector *connector;
> - struct cdn_dp_port *port;
> - struct drm_device *drm_dev = data;
> - int ret, i;
[...]
> + if (next_bridge) {
> + ret = drm_bridge_attach(encoder, next_bridge, bridge,
> + DRM_BRIDGE_ATTACH_NO_CONNECTOR);
> + if (ret)
> + return ret;
> +
> + last_bridge = next_bridge;
> + while (drm_bridge_get_next_bridge(last_bridge))
> + last_bridge = drm_bridge_get_next_bridge(last_bridge);
DRM bridges are now refcounted, and you are not calling drm_bridge_get()
and drm_bridge_put() here. But here you can use
drm_bridge_chain_get_last_bridge() which will simplify your job.
Don't forget to call drm_bridge_put() on the returned bridge when the
bridge is not referenced anymore. This should be as easy as adding a
cleanup action on the variable declaration above:
- struct drm_bridge *last_bridge = NULL;
+ struct drm_bridge *last_bridge __free(drm_bridge_put) = NULL;
> @@ -1029,8 +1147,102 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data)
> return ret;
> }
>
> + if (last_bridge)
> + connector->fwnode = fwnode_handle_get(of_fwnode_handle(last_bridge->of_node));
> +
> drm_connector_attach_encoder(connector, encoder);
>
> + 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].bridge = bridge;
You are storing a reference to a drm_bridge, so have to increment the
refcount:
dp->next_bridge_list[count].bridge = drm_bridge_get(bridge);
^^^^^^^^^^^^^^
FYI there is a plan to replace of_drm_find_bridge() with a function that
increases the bridge refcount before returning the bridge, but it's not
there yet. When that will happen, the explicit drm_bridge_get() won't be
needed anymore and this code can be updated accordingly.
Also you have to call drm_bridge_put() to release that reference when the
pointer goes away. I guess that should happen in cdn_dp_unbind().
> +static int cdn_dp_bind(struct device *dev, struct device *master, void *data)
> +{
In this function you do:
...(see below)...
> + 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);
1. compute the next bridges and store them in dp->next_bridge_list[]
...
> + if (ret)
> + return ret;
> +
> + 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))
> + return PTR_ERR(dp_bridge);
> + 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].bridge;
> + ret = cdn_bridge_add(dev, bridge, next_bridge, encoder);
...
2. pass the dp->next_bridge_list[i].bridge to cdn_bridge_add
3. not use dp->next_bridge_list[i] elsewhere
So you may want to change this function to parse into a local array, with
function scope. If you do this, the drm_bridge_get/put() I mentioned above
should still exist, but would be localized to this function, thus even
easier to handle.
Even better, you can parse the DT one bridge at a time inside the for loop,
so you don't need to store any next_bridge pointer array. This will need a
bit of rework of cdn_dp_parse_next_bridge_dt() though, and I haven't
checked in detail so it might be not worth.
[...]
> +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_next_bridge {
> + struct cdn_dp_device *parent;
> + struct drm_bridge *bridge;
> + int id;
The @parent and @id fields are unused if I'm not mistaken.
If it is the case then you can... (see below)
> 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 cdn_dp_next_bridge next_bridge_list[MAX_PHY];
...replace this line with:
struct drm_bridge *next_bridge[MAX_PHY];
Unless of course you just don't store the next_bridge at all, as I
suggested above, and which looks way easier and more efficient.
Best regards,
Luca
--
Luca Ceresoli, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
WARNING: multiple messages have this Message-ID (diff)
From: "Luca Ceresoli" <luca.ceresoli@bootlin.com>
To: "Chaoyi Chen" <kernel@airkyi.com>,
"Heikki Krogerus" <heikki.krogerus@linux.intel.com>,
"Greg Kroah-Hartman" <gregkh@linuxfoundation.org>,
"Dmitry Baryshkov" <dmitry.baryshkov@oss.qualcomm.com>,
"Peter Chen" <hzpeterchen@gmail.com>,
"Rob Herring" <robh@kernel.org>,
"Krzysztof Kozlowski" <krzk+dt@kernel.org>,
"Conor Dooley" <conor+dt@kernel.org>,
"Vinod Koul" <vkoul@kernel.org>,
"Kishon Vijay Abraham I" <kishon@kernel.org>,
"Heiko Stuebner" <heiko@sntech.de>,
"Sandy Huang" <hjc@rock-chips.com>,
"Andy Yan" <andy.yan@rock-chips.com>,
"Yubing Zhang" <yubing.zhang@rock-chips.com>,
"Frank Wang" <frank.wang@rock-chips.com>,
"Andrzej Hajda" <andrzej.hajda@intel.com>,
"Neil Armstrong" <neil.armstrong@linaro.org>,
"Robert Foss" <rfoss@kernel.org>,
"Laurent Pinchart" <Laurent.pinchart@ideasonboard.com>,
"Jonas Karlman" <jonas@kwiboo.se>,
"Jernej Skrabec" <jernej.skrabec@gmail.com>,
"Maarten Lankhorst" <maarten.lankhorst@linux.intel.com>,
"Maxime Ripard" <mripard@kernel.org>,
"Thomas Zimmermann" <tzimmermann@suse.de>,
"David Airlie" <airlied@gmail.com>,
"Simona Vetter" <simona@ffwll.ch>,
"Amit Sunil Dhamne" <amitsd@google.com>,
"Chaoyi Chen" <chaoyi.chen@rock-chips.com>,
"Dragan Simic" <dsimic@manjaro.org>,
"Johan Jonker" <jbx6244@gmail.com>,
"Diederik de Haas" <didi.debian@cknow.org>,
"Peter Robinson" <pbrobinson@gmail.com>
Cc: <linux-usb@vger.kernel.org>, <devicetree@vger.kernel.org>,
<linux-kernel@vger.kernel.org>, <linux-phy@lists.infradead.org>,
<linux-arm-kernel@lists.infradead.org>,
<linux-rockchip@lists.infradead.org>,
<dri-devel@lists.freedesktop.org>
Subject: Re: [PATCH v9 08/10] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection
Date: Tue, 11 Nov 2025 16:14:11 +0100 [thread overview]
Message-ID: <DE5YP3AVGOG3.OHP68Z0F6KBU@bootlin.com> (raw)
In-Reply-To: <20251111105040.94-9-kernel@airkyi.com>
Hello Chaoyi,
On Tue Nov 11, 2025 at 11:50 AM CET, Chaoyi Chen wrote:
> 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>
[...]
> @@ -966,28 +1084,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 = NULL;
> struct drm_connector *connector;
> - struct cdn_dp_port *port;
> - struct drm_device *drm_dev = data;
> - int ret, i;
[...]
> + if (next_bridge) {
> + ret = drm_bridge_attach(encoder, next_bridge, bridge,
> + DRM_BRIDGE_ATTACH_NO_CONNECTOR);
> + if (ret)
> + return ret;
> +
> + last_bridge = next_bridge;
> + while (drm_bridge_get_next_bridge(last_bridge))
> + last_bridge = drm_bridge_get_next_bridge(last_bridge);
DRM bridges are now refcounted, and you are not calling drm_bridge_get()
and drm_bridge_put() here. But here you can use
drm_bridge_chain_get_last_bridge() which will simplify your job.
Don't forget to call drm_bridge_put() on the returned bridge when the
bridge is not referenced anymore. This should be as easy as adding a
cleanup action on the variable declaration above:
- struct drm_bridge *last_bridge = NULL;
+ struct drm_bridge *last_bridge __free(drm_bridge_put) = NULL;
> @@ -1029,8 +1147,102 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data)
> return ret;
> }
>
> + if (last_bridge)
> + connector->fwnode = fwnode_handle_get(of_fwnode_handle(last_bridge->of_node));
> +
> drm_connector_attach_encoder(connector, encoder);
>
> + 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].bridge = bridge;
You are storing a reference to a drm_bridge, so have to increment the
refcount:
dp->next_bridge_list[count].bridge = drm_bridge_get(bridge);
^^^^^^^^^^^^^^
FYI there is a plan to replace of_drm_find_bridge() with a function that
increases the bridge refcount before returning the bridge, but it's not
there yet. When that will happen, the explicit drm_bridge_get() won't be
needed anymore and this code can be updated accordingly.
Also you have to call drm_bridge_put() to release that reference when the
pointer goes away. I guess that should happen in cdn_dp_unbind().
> +static int cdn_dp_bind(struct device *dev, struct device *master, void *data)
> +{
In this function you do:
...(see below)...
> + 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);
1. compute the next bridges and store them in dp->next_bridge_list[]
...
> + if (ret)
> + return ret;
> +
> + 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))
> + return PTR_ERR(dp_bridge);
> + 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].bridge;
> + ret = cdn_bridge_add(dev, bridge, next_bridge, encoder);
...
2. pass the dp->next_bridge_list[i].bridge to cdn_bridge_add
3. not use dp->next_bridge_list[i] elsewhere
So you may want to change this function to parse into a local array, with
function scope. If you do this, the drm_bridge_get/put() I mentioned above
should still exist, but would be localized to this function, thus even
easier to handle.
Even better, you can parse the DT one bridge at a time inside the for loop,
so you don't need to store any next_bridge pointer array. This will need a
bit of rework of cdn_dp_parse_next_bridge_dt() though, and I haven't
checked in detail so it might be not worth.
[...]
> +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_next_bridge {
> + struct cdn_dp_device *parent;
> + struct drm_bridge *bridge;
> + int id;
The @parent and @id fields are unused if I'm not mistaken.
If it is the case then you can... (see below)
> 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 cdn_dp_next_bridge next_bridge_list[MAX_PHY];
...replace this line with:
struct drm_bridge *next_bridge[MAX_PHY];
Unless of course you just don't store the next_bridge at all, as I
suggested above, and which looks way easier and more efficient.
Best regards,
Luca
--
Luca Ceresoli, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
next prev parent reply other threads:[~2025-11-11 15:14 UTC|newest]
Thread overview: 69+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-11-11 10:50 [PATCH v9 00/10] Add Type-C DP support for RK3399 EVB IND board Chaoyi Chen
2025-11-11 10:50 ` Chaoyi Chen
2025-11-11 10:50 ` Chaoyi Chen
2025-11-11 10:50 ` [PATCH v9 01/10] usb: typec: Add notifier functions Chaoyi Chen
2025-11-11 10:50 ` Chaoyi Chen
2025-11-11 10:50 ` Chaoyi Chen
2025-11-17 8:33 ` Heikki Krogerus
2025-11-17 8:33 ` Heikki Krogerus
2025-11-17 8:33 ` Heikki Krogerus
2025-11-17 8:41 ` Chaoyi Chen
2025-11-17 8:41 ` Chaoyi Chen
2025-11-17 8:41 ` Chaoyi Chen
2025-11-11 10:50 ` [PATCH v9 02/10] usb: typec: Export all typec device types Chaoyi Chen
2025-11-11 10:50 ` Chaoyi Chen
2025-11-11 10:50 ` Chaoyi Chen
2025-11-11 10:50 ` [PATCH v9 03/10] drm/bridge: Implement generic USB Type-C DP HPD bridge Chaoyi Chen
2025-11-11 10:50 ` Chaoyi Chen
2025-11-11 10:50 ` Chaoyi Chen
2025-11-17 8:40 ` Heikki Krogerus
2025-11-17 8:40 ` Heikki Krogerus
2025-11-17 8:40 ` Heikki Krogerus
2025-11-17 8:47 ` Chaoyi Chen
2025-11-17 8:47 ` Chaoyi Chen
2025-11-17 8:47 ` Chaoyi Chen
2025-11-11 10:50 ` [PATCH v9 04/10] dt-bindings: phy: rockchip: rk3399-typec-phy: Support mode-switch Chaoyi Chen
2025-11-11 10:50 ` Chaoyi Chen
2025-11-11 10:50 ` Chaoyi Chen
2025-11-11 11:09 ` Krzysztof Kozlowski
2025-11-11 11:09 ` Krzysztof Kozlowski
2025-11-11 11:09 ` Krzysztof Kozlowski
2025-11-12 2:50 ` Chaoyi Chen
2025-11-12 2:50 ` Chaoyi Chen
2025-11-12 2:50 ` Chaoyi Chen
2025-11-11 10:50 ` [PATCH v9 05/10] phy: rockchip: phy-rockchip-typec: Add typec_mux/typec_switch support Chaoyi Chen
2025-11-11 10:50 ` Chaoyi Chen
2025-11-11 10:50 ` Chaoyi Chen
2025-11-11 10:50 ` [PATCH v9 06/10] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge Chaoyi Chen
2025-11-11 10:50 ` Chaoyi Chen
2025-11-11 10:50 ` Chaoyi Chen
2025-11-18 9:08 ` Neil Armstrong
2025-11-18 9:08 ` Neil Armstrong
2025-11-18 9:08 ` Neil Armstrong
2025-11-18 9:37 ` Chaoyi Chen
2025-11-18 9:37 ` Chaoyi Chen
2025-11-18 9:37 ` Chaoyi Chen
2025-11-11 10:50 ` [PATCH v9 07/10] drm/rockchip: cdn-dp: Support handle lane info without extcon Chaoyi Chen
2025-11-11 10:50 ` Chaoyi Chen
2025-11-11 10:50 ` Chaoyi Chen
2025-11-11 10:50 ` [PATCH v9 08/10] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection Chaoyi Chen
2025-11-11 10:50 ` Chaoyi Chen
2025-11-11 10:50 ` Chaoyi Chen
2025-11-11 15:14 ` Luca Ceresoli [this message]
2025-11-11 15:14 ` Luca Ceresoli
2025-11-11 15:14 ` Luca Ceresoli
2025-11-12 1:37 ` Chaoyi Chen
2025-11-12 1:37 ` Chaoyi Chen
2025-11-12 1:37 ` Chaoyi Chen
2025-11-17 1:33 ` Chaoyi Chen
2025-11-17 1:33 ` Chaoyi Chen
2025-11-17 1:33 ` Chaoyi Chen
2025-11-19 19:49 ` Luca Ceresoli
2025-11-19 19:49 ` Luca Ceresoli
2025-11-19 19:49 ` Luca Ceresoli
2025-11-11 10:50 ` [PATCH v9 09/10] arm64: dts: rockchip: Add missing dp_out port for RK3399 CDN-DP Chaoyi Chen
2025-11-11 10:50 ` Chaoyi Chen
2025-11-11 10:50 ` Chaoyi Chen
2025-11-11 10:50 ` [PATCH v9 10/10] arm64: dts: rockchip: rk3399-evb-ind: Add support for DisplayPort Chaoyi Chen
2025-11-11 10:50 ` Chaoyi Chen
2025-11-11 10:50 ` Chaoyi Chen
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=DE5YP3AVGOG3.OHP68Z0F6KBU@bootlin.com \
--to=luca.ceresoli@bootlin.com \
--cc=Laurent.pinchart@ideasonboard.com \
--cc=airlied@gmail.com \
--cc=amitsd@google.com \
--cc=andrzej.hajda@intel.com \
--cc=andy.yan@rock-chips.com \
--cc=chaoyi.chen@rock-chips.com \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=didi.debian@cknow.org \
--cc=dmitry.baryshkov@oss.qualcomm.com \
--cc=dri-devel@lists.freedesktop.org \
--cc=dsimic@manjaro.org \
--cc=frank.wang@rock-chips.com \
--cc=gregkh@linuxfoundation.org \
--cc=heikki.krogerus@linux.intel.com \
--cc=heiko@sntech.de \
--cc=hjc@rock-chips.com \
--cc=hzpeterchen@gmail.com \
--cc=jbx6244@gmail.com \
--cc=jernej.skrabec@gmail.com \
--cc=jonas@kwiboo.se \
--cc=kernel@airkyi.com \
--cc=kishon@kernel.org \
--cc=krzk+dt@kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-phy@lists.infradead.org \
--cc=linux-rockchip@lists.infradead.org \
--cc=linux-usb@vger.kernel.org \
--cc=maarten.lankhorst@linux.intel.com \
--cc=mripard@kernel.org \
--cc=neil.armstrong@linaro.org \
--cc=pbrobinson@gmail.com \
--cc=rfoss@kernel.org \
--cc=robh@kernel.org \
--cc=simona@ffwll.ch \
--cc=tzimmermann@suse.de \
--cc=vkoul@kernel.org \
--cc=yubing.zhang@rock-chips.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.