Hello,
Despite the SN65DSI86 being a DSI to eDP bridge, it can also operate in DisplayPort mode. This patch series adds support for this feature to the ti-sn65dsi86 driver.
The series starts with miscellaneous fixes and improvements in patch 01/11 to 04/11. Patch 05/11 then moves to the panel-bridge helper, and is followed by patches 06/11 and 07/11 that refactor the code to prepare for the next steps.
As my goal is to use the sn65dsi86 with the R-Car DU driver, which requires DRM_BRIDGE_ATTACH_NO_CONNECTOR support, the series continues with support for this feature. In patch 08/11 the bridge connector operations are added, and in 09/11 the connector creation is made optional.
Patch 10/11 then implements supports for DisplayPort mode, enabled automatically when the next component in the pipeline isn't a panel. Finally, patch 11/11 adds hotplug detection support, which isn't very useful for eDP, but is needed for DisplayPort.
This series is an RFC as I haven't been able to test it fully yet. The platform I'm working on is missing support for three other components in the display pipeline (I'm on it), which are required in order to run tests. I would however like to receive feedback on the approach already, in case problems would need to be solved.
Laurent Pinchart (11): dt-bindings: drm/bridge: ti-sn65dsi8: Make enable GPIO optional drm/bridge: ti-sn65dsi86: Make enable GPIO optional drm/bridge: ti-sn65dsi86: Unregister AUX adapter in remove() drm/bridge: ti-sn65dsi86: Use bitmask to store valid rates drm/bridge: ti-sn65dsi86: Wrap panel with panel-bridge drm/bridge: ti-sn65dsi86: Group code in sections drm/bridge: ti-sn65dsi86: Split connector creation to a function drm/bridge: ti-sn65dsi86: Implement bridge connector operations drm/bridge: ti-sn65dsi86: Make connector creation optional drm/bridge: ti-sn65dsi86: Support DisplayPort (non-eDP) mode drm/bridge: ti-sn65dsi86: Support hotplug detection
.../bindings/display/bridge/ti,sn65dsi86.yaml | 1 - drivers/gpu/drm/bridge/ti-sn65dsi86.c | 266 ++++++++++++------ 2 files changed, 183 insertions(+), 84 deletions(-)
The SN65DSI86 EN pin can be hardwired to a high level, or connected to a global reset signal, not controllable by the kernel. Make it optional in those cases.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com --- .../devicetree/bindings/display/bridge/ti,sn65dsi86.yaml | 1 - 1 file changed, 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/display/bridge/ti,sn65dsi86.yaml b/Documentation/devicetree/bindings/display/bridge/ti,sn65dsi86.yaml index 26932d2e86ab..2506765cb338 100644 --- a/Documentation/devicetree/bindings/display/bridge/ti,sn65dsi86.yaml +++ b/Documentation/devicetree/bindings/display/bridge/ti,sn65dsi86.yaml @@ -150,7 +150,6 @@ properties: required: - compatible - reg - - enable-gpios - vccio-supply - vpll-supply - vcca-supply
On Mon, Mar 22, 2021 at 8:32 AM Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com wrote:
The SN65DSI86 EN pin can be hardwired to a high level, or connected to a global reset signal, not controllable by the kernel. Make it optional in those cases.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
Reviewed-by: Jagan Teki jagan@amarulasolutions.com
Quoting Laurent Pinchart (2021-03-21 20:01:18)
The SN65DSI86 EN pin can be hardwired to a high level, or connected to a global reset signal, not controllable by the kernel. Make it optional in those cases.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
Reviewed-by: Stephen Boyd swboyd@chromium.org
Hi,
On Sun, Mar 21, 2021 at 8:02 PM Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com wrote:
The SN65DSI86 EN pin can be hardwired to a high level, or connected to a global reset signal, not controllable by the kernel. Make it optional in those cases.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
.../devicetree/bindings/display/bridge/ti,sn65dsi86.yaml | 1 - 1 file changed, 1 deletion(-)
Reviewed-by: Douglas Anderson dianders@chromium.org
On Mon, 22 Mar 2021 05:01:18 +0200, Laurent Pinchart wrote:
The SN65DSI86 EN pin can be hardwired to a high level, or connected to a global reset signal, not controllable by the kernel. Make it optional in those cases.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
.../devicetree/bindings/display/bridge/ti,sn65dsi86.yaml | 1 - 1 file changed, 1 deletion(-)
Acked-by: Rob Herring robh@kernel.org
The enable signal may not be controllable by the kernel. Make it optional.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com --- drivers/gpu/drm/bridge/ti-sn65dsi86.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index f27306c51e4d..da78a12e58b5 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -1245,8 +1245,8 @@ static int ti_sn_bridge_probe(struct i2c_client *client,
dev_set_drvdata(&client->dev, pdata);
- pdata->enable_gpio = devm_gpiod_get(pdata->dev, "enable", - GPIOD_OUT_LOW); + pdata->enable_gpio = devm_gpiod_get_optional(pdata->dev, "enable", + GPIOD_OUT_LOW); if (IS_ERR(pdata->enable_gpio)) { DRM_ERROR("failed to get enable gpio from DT\n"); ret = PTR_ERR(pdata->enable_gpio);
On Mon, Mar 22, 2021 at 8:32 AM Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com wrote:
The enable signal may not be controllable by the kernel. Make it optional.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
Reviewed-by: Jagan Teki jagan@amarulasolutions.com
Quoting Laurent Pinchart (2021-03-21 20:01:19)
The enable signal may not be controllable by the kernel. Make it optional.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
Reviewed-by: Stephen Boyd swboyd@chromium.org
Hi,
On Sun, Mar 21, 2021 at 8:02 PM Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com wrote:
The enable signal may not be controllable by the kernel. Make it optional.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
drivers/gpu/drm/bridge/ti-sn65dsi86.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
Reviewed-by: Douglas Anderson dianders@chromium.org
The AUX adapter registered in probe() need to be unregistered in remove(). Do so.
Fixes: b814ec6d4535 ("drm/bridge: ti-sn65dsi86: Implement AUX channel") Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com --- drivers/gpu/drm/bridge/ti-sn65dsi86.c | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index da78a12e58b5..c45420a50e73 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -1307,6 +1307,9 @@ static int ti_sn_bridge_remove(struct i2c_client *client) return -EINVAL;
kfree(pdata->edid); + + drm_dp_aux_unregister(&pdata->aux); + ti_sn_debugfs_remove(pdata);
of_node_put(pdata->host_node);
Quoting Laurent Pinchart (2021-03-21 20:01:20)
The AUX adapter registered in probe() need to be unregistered in remove(). Do so.
Fixes: b814ec6d4535 ("drm/bridge: ti-sn65dsi86: Implement AUX channel") Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
Reviewed-by: Stephen Boyd swboyd@chromium.org
Hi,
On Sun, Mar 21, 2021 at 8:02 PM Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com wrote:
The AUX adapter registered in probe() need to be unregistered in remove(). Do so.
Fixes: b814ec6d4535 ("drm/bridge: ti-sn65dsi86: Implement AUX channel") Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
drivers/gpu/drm/bridge/ti-sn65dsi86.c | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index da78a12e58b5..c45420a50e73 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -1307,6 +1307,9 @@ static int ti_sn_bridge_remove(struct i2c_client *client) return -EINVAL;
kfree(pdata->edid);
drm_dp_aux_unregister(&pdata->aux);
ti_sn_debugfs_remove(pdata); of_node_put(pdata->host_node);
Good catch. One question, though. I know DRM sometimes has different conventions than the rest of the kernel, but I always look for the "remove" to be backwards of probe. That means that your code (and probably most of the remove function) should come _after_ the drm_bridge_remove(), right? ...since drm_bridge_add() was the last thing in probe then drm_bridge_remove() should be the first thing in remove?
-Doug
Hi Doug,
On Tue, Mar 23, 2021 at 02:08:42PM -0700, Doug Anderson wrote:
On Sun, Mar 21, 2021 at 8:02 PM Laurent Pinchart wrote:
The AUX adapter registered in probe() need to be unregistered in remove(). Do so.
Fixes: b814ec6d4535 ("drm/bridge: ti-sn65dsi86: Implement AUX channel") Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
drivers/gpu/drm/bridge/ti-sn65dsi86.c | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index da78a12e58b5..c45420a50e73 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -1307,6 +1307,9 @@ static int ti_sn_bridge_remove(struct i2c_client *client) return -EINVAL;
kfree(pdata->edid);
drm_dp_aux_unregister(&pdata->aux);
ti_sn_debugfs_remove(pdata); of_node_put(pdata->host_node);
Good catch. One question, though. I know DRM sometimes has different conventions than the rest of the kernel, but I always look for the "remove" to be backwards of probe. That means that your code (and probably most of the remove function) should come _after_ the drm_bridge_remove(), right? ...since drm_bridge_add() was the last thing in probe then drm_bridge_remove() should be the first thing in remove?
I agree in theory, yes. However, in practice, if you remove a bridge that is currently in use, all hell will break lose. And if the bridge isn't being used, it makes no difference. Still, it's worth changing the order of operations to move drm_bridge_remove() first, as it won't hurt in any case and is logically better. It's not an issue introduced by this series though, so how how about it on top, or in parallel ? You can even submit a patch if you want :-)
Hi,
On Tue, Mar 23, 2021 at 2:42 PM Laurent Pinchart laurent.pinchart@ideasonboard.com wrote:
Hi Doug,
On Tue, Mar 23, 2021 at 02:08:42PM -0700, Doug Anderson wrote:
On Sun, Mar 21, 2021 at 8:02 PM Laurent Pinchart wrote:
The AUX adapter registered in probe() need to be unregistered in remove(). Do so.
Fixes: b814ec6d4535 ("drm/bridge: ti-sn65dsi86: Implement AUX channel") Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
drivers/gpu/drm/bridge/ti-sn65dsi86.c | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index da78a12e58b5..c45420a50e73 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -1307,6 +1307,9 @@ static int ti_sn_bridge_remove(struct i2c_client *client) return -EINVAL;
kfree(pdata->edid);
drm_dp_aux_unregister(&pdata->aux);
ti_sn_debugfs_remove(pdata); of_node_put(pdata->host_node);
Good catch. One question, though. I know DRM sometimes has different conventions than the rest of the kernel, but I always look for the "remove" to be backwards of probe. That means that your code (and probably most of the remove function) should come _after_ the drm_bridge_remove(), right? ...since drm_bridge_add() was the last thing in probe then drm_bridge_remove() should be the first thing in remove?
I agree in theory, yes. However, in practice, if you remove a bridge that is currently in use, all hell will break lose. And if the bridge isn't being used, it makes no difference. Still, it's worth changing the order of operations to move drm_bridge_remove() first, as it won't hurt in any case and is logically better. It's not an issue introduced by this series though, so how how about it on top, or in parallel ?
Sure, it can be a separate patch. I'd kinda prefer it be a patch _before_ ${SUBJECT} patch, though. Specifically it's harder for me to reason about whether your new function call is in the right place and won't cause any problems with the order being all jumbled. If we fix the order first then it's easy to reason about your patch.
You can even submit a patch if you want :-)
Happy to post it up if it won't cause more confusion w/ you posting your next version and trying to figure out what to base it on (since it will definitely conflict with your series).
-Doug
Hi Doug,
On Tue, Mar 23, 2021 at 03:55:05PM -0700, Doug Anderson wrote:
On Tue, Mar 23, 2021 at 2:42 PM Laurent Pinchart wrote:
On Tue, Mar 23, 2021 at 02:08:42PM -0700, Doug Anderson wrote:
On Sun, Mar 21, 2021 at 8:02 PM Laurent Pinchart wrote:
The AUX adapter registered in probe() need to be unregistered in remove(). Do so.
Fixes: b814ec6d4535 ("drm/bridge: ti-sn65dsi86: Implement AUX channel") Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
drivers/gpu/drm/bridge/ti-sn65dsi86.c | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index da78a12e58b5..c45420a50e73 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -1307,6 +1307,9 @@ static int ti_sn_bridge_remove(struct i2c_client *client) return -EINVAL;
kfree(pdata->edid);
drm_dp_aux_unregister(&pdata->aux);
ti_sn_debugfs_remove(pdata); of_node_put(pdata->host_node);
Good catch. One question, though. I know DRM sometimes has different conventions than the rest of the kernel, but I always look for the "remove" to be backwards of probe. That means that your code (and probably most of the remove function) should come _after_ the drm_bridge_remove(), right? ...since drm_bridge_add() was the last thing in probe then drm_bridge_remove() should be the first thing in remove?
I agree in theory, yes. However, in practice, if you remove a bridge that is currently in use, all hell will break lose. And if the bridge isn't being used, it makes no difference. Still, it's worth changing the order of operations to move drm_bridge_remove() first, as it won't hurt in any case and is logically better. It's not an issue introduced by this series though, so how how about it on top, or in parallel ?
Sure, it can be a separate patch. I'd kinda prefer it be a patch _before_ ${SUBJECT} patch, though. Specifically it's harder for me to reason about whether your new function call is in the right place and won't cause any problems with the order being all jumbled. If we fix the order first then it's easy to reason about your patch.
You can even submit a patch if you want :-)
Happy to post it up if it won't cause more confusion w/ you posting your next version and trying to figure out what to base it on (since it will definitely conflict with your series).
I'll need quite a bit of time before v2, as I'd like to test it, and that requires finishing support for the DSI bridge and the display controller :-) Please feel free to post a patch if you have time, I think it could get merged in drm-misc quite quickly.
Hi,
On Tue, Mar 23, 2021 at 4:03 PM Laurent Pinchart laurent.pinchart@ideasonboard.com wrote:
Hi Doug,
On Tue, Mar 23, 2021 at 03:55:05PM -0700, Doug Anderson wrote:
On Tue, Mar 23, 2021 at 2:42 PM Laurent Pinchart wrote:
On Tue, Mar 23, 2021 at 02:08:42PM -0700, Doug Anderson wrote:
On Sun, Mar 21, 2021 at 8:02 PM Laurent Pinchart wrote:
The AUX adapter registered in probe() need to be unregistered in remove(). Do so.
Fixes: b814ec6d4535 ("drm/bridge: ti-sn65dsi86: Implement AUX channel") Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
drivers/gpu/drm/bridge/ti-sn65dsi86.c | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index da78a12e58b5..c45420a50e73 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -1307,6 +1307,9 @@ static int ti_sn_bridge_remove(struct i2c_client *client) return -EINVAL;
kfree(pdata->edid);
drm_dp_aux_unregister(&pdata->aux);
ti_sn_debugfs_remove(pdata); of_node_put(pdata->host_node);
Good catch. One question, though. I know DRM sometimes has different conventions than the rest of the kernel, but I always look for the "remove" to be backwards of probe. That means that your code (and probably most of the remove function) should come _after_ the drm_bridge_remove(), right? ...since drm_bridge_add() was the last thing in probe then drm_bridge_remove() should be the first thing in remove?
I agree in theory, yes. However, in practice, if you remove a bridge that is currently in use, all hell will break lose. And if the bridge isn't being used, it makes no difference. Still, it's worth changing the order of operations to move drm_bridge_remove() first, as it won't hurt in any case and is logically better. It's not an issue introduced by this series though, so how how about it on top, or in parallel ?
Sure, it can be a separate patch. I'd kinda prefer it be a patch _before_ ${SUBJECT} patch, though. Specifically it's harder for me to reason about whether your new function call is in the right place and won't cause any problems with the order being all jumbled. If we fix the order first then it's easy to reason about your patch.
You can even submit a patch if you want :-)
Happy to post it up if it won't cause more confusion w/ you posting your next version and trying to figure out what to base it on (since it will definitely conflict with your series).
I'll need quite a bit of time before v2, as I'd like to test it, and that requires finishing support for the DSI bridge and the display controller :-) Please feel free to post a patch if you have time, I think it could get merged in drm-misc quite quickly.
I haven't forgotten about this and I've got it written, but I'm trying to put it together with the work I'm doing to fix EDID reading and that's still going to take me a while longer. I'm out tomorrow but _hoping_ that I'll be able to at least get a new patch series (at least RFC quality) next week...
-Doug
Hi Doug,
On Thu, Mar 25, 2021 at 05:43:22PM -0700, Doug Anderson wrote:
On Tue, Mar 23, 2021 at 4:03 PM Laurent Pinchart wrote:
On Tue, Mar 23, 2021 at 03:55:05PM -0700, Doug Anderson wrote:
On Tue, Mar 23, 2021 at 2:42 PM Laurent Pinchart wrote:
On Tue, Mar 23, 2021 at 02:08:42PM -0700, Doug Anderson wrote:
On Sun, Mar 21, 2021 at 8:02 PM Laurent Pinchart wrote:
The AUX adapter registered in probe() need to be unregistered in remove(). Do so.
Fixes: b814ec6d4535 ("drm/bridge: ti-sn65dsi86: Implement AUX channel") Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
drivers/gpu/drm/bridge/ti-sn65dsi86.c | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index da78a12e58b5..c45420a50e73 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -1307,6 +1307,9 @@ static int ti_sn_bridge_remove(struct i2c_client *client) return -EINVAL;
kfree(pdata->edid);
drm_dp_aux_unregister(&pdata->aux);
ti_sn_debugfs_remove(pdata); of_node_put(pdata->host_node);
Good catch. One question, though. I know DRM sometimes has different conventions than the rest of the kernel, but I always look for the "remove" to be backwards of probe. That means that your code (and probably most of the remove function) should come _after_ the drm_bridge_remove(), right? ...since drm_bridge_add() was the last thing in probe then drm_bridge_remove() should be the first thing in remove?
I agree in theory, yes. However, in practice, if you remove a bridge that is currently in use, all hell will break lose. And if the bridge isn't being used, it makes no difference. Still, it's worth changing the order of operations to move drm_bridge_remove() first, as it won't hurt in any case and is logically better. It's not an issue introduced by this series though, so how how about it on top, or in parallel ?
Sure, it can be a separate patch. I'd kinda prefer it be a patch _before_ ${SUBJECT} patch, though. Specifically it's harder for me to reason about whether your new function call is in the right place and won't cause any problems with the order being all jumbled. If we fix the order first then it's easy to reason about your patch.
You can even submit a patch if you want :-)
Happy to post it up if it won't cause more confusion w/ you posting your next version and trying to figure out what to base it on (since it will definitely conflict with your series).
I'll need quite a bit of time before v2, as I'd like to test it, and that requires finishing support for the DSI bridge and the display controller :-) Please feel free to post a patch if you have time, I think it could get merged in drm-misc quite quickly.
I haven't forgotten about this and I've got it written, but I'm trying to put it together with the work I'm doing to fix EDID reading and that's still going to take me a while longer. I'm out tomorrow but _hoping_ that I'll be able to at least get a new patch series (at least RFC quality) next week...
No worries at all, it will take a few weeks at least before I get the display controller and DSI working on my board, so you're not blocking me :-)
The valid rates are stored in an array of 8 booleans. Replace it with a bitmask to save space.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com --- drivers/gpu/drm/bridge/ti-sn65dsi86.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index c45420a50e73..1d1be791d5ba 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -557,9 +557,9 @@ static int ti_sn_bridge_calc_min_dp_rate_idx(struct ti_sn_bridge *pdata) return i; }
-static void ti_sn_bridge_read_valid_rates(struct ti_sn_bridge *pdata, - bool rate_valid[]) +static unsigned int ti_sn_bridge_read_valid_rates(struct ti_sn_bridge *pdata) { + unsigned int valid_rates = 0; unsigned int rate_per_200khz; unsigned int rate_mhz; u8 dpcd_val; @@ -599,13 +599,13 @@ static void ti_sn_bridge_read_valid_rates(struct ti_sn_bridge *pdata, j < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut); j++) { if (ti_sn_bridge_dp_rate_lut[j] == rate_mhz) - rate_valid[j] = true; + valid_rates |= BIT(j); } }
for (i = 0; i < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut); i++) { - if (rate_valid[i]) - return; + if (valid_rates & BIT(i)) + return valid_rates; } DRM_DEV_ERROR(pdata->dev, "No matching eDP rates in table; falling back\n"); @@ -627,15 +627,17 @@ static void ti_sn_bridge_read_valid_rates(struct ti_sn_bridge *pdata, (int)dpcd_val); fallthrough; case DP_LINK_BW_5_4: - rate_valid[7] = 1; + valid_rates |= BIT(7); fallthrough; case DP_LINK_BW_2_7: - rate_valid[4] = 1; + valid_rates |= BIT(4); fallthrough; case DP_LINK_BW_1_62: - rate_valid[1] = 1; + valid_rates |= BIT(1); break; } + + return valid_rates; }
static void ti_sn_bridge_set_video_timings(struct ti_sn_bridge *pdata) @@ -753,8 +755,8 @@ static int ti_sn_link_training(struct ti_sn_bridge *pdata, int dp_rate_idx, static void ti_sn_bridge_enable(struct drm_bridge *bridge) { struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge); - bool rate_valid[ARRAY_SIZE(ti_sn_bridge_dp_rate_lut)] = { }; const char *last_err_str = "No supported DP rate"; + unsigned int valid_rates; int dp_rate_idx; unsigned int val; int ret = -EINVAL; @@ -793,13 +795,13 @@ static void ti_sn_bridge_enable(struct drm_bridge *bridge) regmap_update_bits(pdata->regmap, SN_SSC_CONFIG_REG, DP_NUM_LANES_MASK, val);
- ti_sn_bridge_read_valid_rates(pdata, rate_valid); + valid_rates = ti_sn_bridge_read_valid_rates(pdata);
/* Train until we run out of rates */ for (dp_rate_idx = ti_sn_bridge_calc_min_dp_rate_idx(pdata); dp_rate_idx < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut); dp_rate_idx++) { - if (!rate_valid[dp_rate_idx]) + if (!(valid_rates & BIT(dp_rate_idx))) continue;
ret = ti_sn_link_training(pdata, dp_rate_idx, &last_err_str);
Quoting Laurent Pinchart (2021-03-21 20:01:21)
The valid rates are stored in an array of 8 booleans. Replace it with a bitmask to save space.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
Reviewed-by: Stephen Boyd swboyd@chromium.org
Hi,
On Sun, Mar 21, 2021 at 8:02 PM Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com wrote:
The valid rates are stored in an array of 8 booleans. Replace it with a bitmask to save space.
I'm curious: do you have evidence that this does anything useful? I guess you're expecting it to save .text space, right? Stack usage and execution time differences should be irrelevant--it's not in a critical section and the difference should be tiny anyway. As far as .text segment goes, it's not obvious to me that the compiler will use fewer instructions to manipulate bits compared to booleans.
Doing a super simple "ls -ah" on vmlinux (unstripped):
Before: 224820232 bytes After: 224820376 bytes
...so your change made it _bigger_. OK, so running "strip --strip-debug" on those:
Before: 26599464 bytes After: 26599464 bytes
...so exactly the same. I tried finding some evidence using "readelf -ah":
Before: [ 2] .text PROGBITS ffffffc010010000 00020000 0000000000b03508 0000000000000000 WAX 0 0 65536 [ 3] .rodata PROGBITS ffffffc010b20000 00b30000 00000000002e84b3 0000000000000000 WAMS 0 0 4096
After: [ 2] .text PROGBITS ffffffc010010000 00020000 0000000000b03508 0000000000000000 WAX 0 0 65536 [ 3] .rodata PROGBITS ffffffc010b20000 00b30000 00000000002e84b3 0000000000000000 WAMS 0 0 4096
Maybe you have some evidence showing an improvement? Ah, OK. I disassembled ti_sn_bridge_enable() and your patch saves 12 bytes, but I guess maybe alignment washes it out in reality...
In terms of readability / conventions, I personally find this change a bit of a wash. I mean, I guess I originally implemented it as an array and I thought that was the most readable, but I like bitfields fine too. If everyone loves it then I won't object, but to me it feels like touching lines of code for something that's personal preference. ;-)
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
drivers/gpu/drm/bridge/ti-sn65dsi86.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index c45420a50e73..1d1be791d5ba 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -557,9 +557,9 @@ static int ti_sn_bridge_calc_min_dp_rate_idx(struct ti_sn_bridge *pdata) return i; }
-static void ti_sn_bridge_read_valid_rates(struct ti_sn_bridge *pdata,
bool rate_valid[])
+static unsigned int ti_sn_bridge_read_valid_rates(struct ti_sn_bridge *pdata) {
unsigned int valid_rates = 0; unsigned int rate_per_200khz; unsigned int rate_mhz; u8 dpcd_val;
@@ -599,13 +599,13 @@ static void ti_sn_bridge_read_valid_rates(struct ti_sn_bridge *pdata, j < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut); j++) { if (ti_sn_bridge_dp_rate_lut[j] == rate_mhz)
rate_valid[j] = true;
valid_rates |= BIT(j); } } for (i = 0; i < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut); i++) {
if (rate_valid[i])
return;
if (valid_rates & BIT(i))
return valid_rates; } DRM_DEV_ERROR(pdata->dev, "No matching eDP rates in table; falling back\n");
@@ -627,15 +627,17 @@ static void ti_sn_bridge_read_valid_rates(struct ti_sn_bridge *pdata, (int)dpcd_val); fallthrough; case DP_LINK_BW_5_4:
rate_valid[7] = 1;
valid_rates |= BIT(7); fallthrough; case DP_LINK_BW_2_7:
rate_valid[4] = 1;
valid_rates |= BIT(4); fallthrough; case DP_LINK_BW_1_62:
rate_valid[1] = 1;
valid_rates |= BIT(1); break; }
return valid_rates;
}
static void ti_sn_bridge_set_video_timings(struct ti_sn_bridge *pdata) @@ -753,8 +755,8 @@ static int ti_sn_link_training(struct ti_sn_bridge *pdata, int dp_rate_idx, static void ti_sn_bridge_enable(struct drm_bridge *bridge) { struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
bool rate_valid[ARRAY_SIZE(ti_sn_bridge_dp_rate_lut)] = { }; const char *last_err_str = "No supported DP rate";
unsigned int valid_rates; int dp_rate_idx; unsigned int val; int ret = -EINVAL;
@@ -793,13 +795,13 @@ static void ti_sn_bridge_enable(struct drm_bridge *bridge) regmap_update_bits(pdata->regmap, SN_SSC_CONFIG_REG, DP_NUM_LANES_MASK, val);
ti_sn_bridge_read_valid_rates(pdata, rate_valid);
valid_rates = ti_sn_bridge_read_valid_rates(pdata); /* Train until we run out of rates */ for (dp_rate_idx = ti_sn_bridge_calc_min_dp_rate_idx(pdata); dp_rate_idx < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut); dp_rate_idx++) {
if (!rate_valid[dp_rate_idx])
if (!(valid_rates & BIT(dp_rate_idx))) continue; ret = ti_sn_link_training(pdata, dp_rate_idx, &last_err_str);
In any case, since it does save 12 bytes:
Reviewed-by: Douglas Anderson dianders@chromium.org
Hi Doug,
On Tue, Mar 23, 2021 at 02:08:55PM -0700, Doug Anderson wrote:
On Sun, Mar 21, 2021 at 8:02 PM Laurent Pinchart wrote:
The valid rates are stored in an array of 8 booleans. Replace it with a bitmask to save space.
I'm curious: do you have evidence that this does anything useful? I guess you're expecting it to save .text space, right? Stack usage and execution time differences should be irrelevant--it's not in a critical section and the difference should be tiny anyway. As far as .text segment goes, it's not obvious to me that the compiler will use fewer instructions to manipulate bits compared to booleans.
Doing a super simple "ls -ah" on vmlinux (unstripped):
Before: 224820232 bytes After: 224820376 bytes
...so your change made it _bigger_. OK, so running "strip --strip-debug" on those:
Before: 26599464 bytes After: 26599464 bytes
...so exactly the same. I tried finding some evidence using "readelf -ah":
Before: [ 2] .text PROGBITS ffffffc010010000 00020000 0000000000b03508 0000000000000000 WAX 0 0 65536 [ 3] .rodata PROGBITS ffffffc010b20000 00b30000 00000000002e84b3 0000000000000000 WAMS 0 0 4096
After: [ 2] .text PROGBITS ffffffc010010000 00020000 0000000000b03508 0000000000000000 WAX 0 0 65536 [ 3] .rodata PROGBITS ffffffc010b20000 00b30000 00000000002e84b3 0000000000000000 WAMS 0 0 4096
Maybe you have some evidence showing an improvement? Ah, OK. I disassembled ti_sn_bridge_enable() and your patch saves 12 bytes, but I guess maybe alignment washes it out in reality...
In terms of readability / conventions, I personally find this change a bit of a wash. I mean, I guess I originally implemented it as an array and I thought that was the most readable, but I like bitfields fine too. If everyone loves it then I won't object, but to me it feels like touching lines of code for something that's personal preference. ;-)
You're right that the .text and CPU time improvements were not my target. I was focussed on stack usage, as that's a limited resource in the kernel. I don't have any evidence that we would be close to any limit, so it's tiny, and if you or anyone else have a strong opinion that an array of booleans is better due to readability concerns, I can drop this change. I only thought about those poor 7 bits in every bool that sat there unused, feeling useless :-)
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
drivers/gpu/drm/bridge/ti-sn65dsi86.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index c45420a50e73..1d1be791d5ba 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -557,9 +557,9 @@ static int ti_sn_bridge_calc_min_dp_rate_idx(struct ti_sn_bridge *pdata) return i; }
-static void ti_sn_bridge_read_valid_rates(struct ti_sn_bridge *pdata,
bool rate_valid[])
+static unsigned int ti_sn_bridge_read_valid_rates(struct ti_sn_bridge *pdata) {
unsigned int valid_rates = 0; unsigned int rate_per_200khz; unsigned int rate_mhz; u8 dpcd_val;
@@ -599,13 +599,13 @@ static void ti_sn_bridge_read_valid_rates(struct ti_sn_bridge *pdata, j < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut); j++) { if (ti_sn_bridge_dp_rate_lut[j] == rate_mhz)
rate_valid[j] = true;
valid_rates |= BIT(j); } } for (i = 0; i < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut); i++) {
if (rate_valid[i])
return;
if (valid_rates & BIT(i))
return valid_rates; } DRM_DEV_ERROR(pdata->dev, "No matching eDP rates in table; falling back\n");
@@ -627,15 +627,17 @@ static void ti_sn_bridge_read_valid_rates(struct ti_sn_bridge *pdata, (int)dpcd_val); fallthrough; case DP_LINK_BW_5_4:
rate_valid[7] = 1;
valid_rates |= BIT(7); fallthrough; case DP_LINK_BW_2_7:
rate_valid[4] = 1;
valid_rates |= BIT(4); fallthrough; case DP_LINK_BW_1_62:
rate_valid[1] = 1;
valid_rates |= BIT(1); break; }
return valid_rates;
}
static void ti_sn_bridge_set_video_timings(struct ti_sn_bridge *pdata) @@ -753,8 +755,8 @@ static int ti_sn_link_training(struct ti_sn_bridge *pdata, int dp_rate_idx, static void ti_sn_bridge_enable(struct drm_bridge *bridge) { struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
bool rate_valid[ARRAY_SIZE(ti_sn_bridge_dp_rate_lut)] = { }; const char *last_err_str = "No supported DP rate";
unsigned int valid_rates; int dp_rate_idx; unsigned int val; int ret = -EINVAL;
@@ -793,13 +795,13 @@ static void ti_sn_bridge_enable(struct drm_bridge *bridge) regmap_update_bits(pdata->regmap, SN_SSC_CONFIG_REG, DP_NUM_LANES_MASK, val);
ti_sn_bridge_read_valid_rates(pdata, rate_valid);
valid_rates = ti_sn_bridge_read_valid_rates(pdata); /* Train until we run out of rates */ for (dp_rate_idx = ti_sn_bridge_calc_min_dp_rate_idx(pdata); dp_rate_idx < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut); dp_rate_idx++) {
if (!rate_valid[dp_rate_idx])
if (!(valid_rates & BIT(dp_rate_idx))) continue; ret = ti_sn_link_training(pdata, dp_rate_idx, &last_err_str);
In any case, since it does save 12 bytes:
Reviewed-by: Douglas Anderson dianders@chromium.org
Hi,
On Tue, Mar 23, 2021 at 2:46 PM Laurent Pinchart laurent.pinchart@ideasonboard.com wrote:
Hi Doug,
On Tue, Mar 23, 2021 at 02:08:55PM -0700, Doug Anderson wrote:
On Sun, Mar 21, 2021 at 8:02 PM Laurent Pinchart wrote:
The valid rates are stored in an array of 8 booleans. Replace it with a bitmask to save space.
I'm curious: do you have evidence that this does anything useful? I guess you're expecting it to save .text space, right? Stack usage and execution time differences should be irrelevant--it's not in a critical section and the difference should be tiny anyway. As far as .text segment goes, it's not obvious to me that the compiler will use fewer instructions to manipulate bits compared to booleans.
Doing a super simple "ls -ah" on vmlinux (unstripped):
Before: 224820232 bytes After: 224820376 bytes
...so your change made it _bigger_. OK, so running "strip --strip-debug" on those:
Before: 26599464 bytes After: 26599464 bytes
...so exactly the same. I tried finding some evidence using "readelf -ah":
Before: [ 2] .text PROGBITS ffffffc010010000 00020000 0000000000b03508 0000000000000000 WAX 0 0 65536 [ 3] .rodata PROGBITS ffffffc010b20000 00b30000 00000000002e84b3 0000000000000000 WAMS 0 0 4096
After: [ 2] .text PROGBITS ffffffc010010000 00020000 0000000000b03508 0000000000000000 WAX 0 0 65536 [ 3] .rodata PROGBITS ffffffc010b20000 00b30000 00000000002e84b3 0000000000000000 WAMS 0 0 4096
Maybe you have some evidence showing an improvement? Ah, OK. I disassembled ti_sn_bridge_enable() and your patch saves 12 bytes, but I guess maybe alignment washes it out in reality...
In terms of readability / conventions, I personally find this change a bit of a wash. I mean, I guess I originally implemented it as an array and I thought that was the most readable, but I like bitfields fine too. If everyone loves it then I won't object, but to me it feels like touching lines of code for something that's personal preference. ;-)
You're right that the .text and CPU time improvements were not my target. I was focussed on stack usage, as that's a limited resource in the kernel. I don't have any evidence that we would be close to any limit, so it's tiny, and if you or anyone else have a strong opinion that an array of booleans is better due to readability concerns, I can drop this change. I only thought about those poor 7 bits in every bool that sat there unused, feeling useless :-)
LOL. Thinking about it a bit more, I guess I feel a bit lame saying that the array of booleans is more readable. I guess I'd call them equivalently readable. So I guess the downside of this patch is just churn. If someone is maintaining a downstream kernel, it's an extra patch to take. If someone is running "git blame" it's an extra layer to walk back to find the history of the code. That being said, it's really not a big deal. I'll leave it up to you if you want to include the patch in your next version or if my arguments have convinced you. ;-)
-Doug
Hi Doug,
On Tue, Mar 23, 2021 at 10:10 PM Doug Anderson dianders@chromium.org wrote:
On Sun, Mar 21, 2021 at 8:02 PM Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com wrote:
The valid rates are stored in an array of 8 booleans. Replace it with a bitmask to save space.
I'm curious: do you have evidence that this does anything useful? I guess you're expecting it to save .text space, right? Stack usage and execution time differences should be irrelevant--it's not in a critical section and the difference should be tiny anyway. As far as .text segment goes, it's not obvious to me that the compiler will use fewer instructions to manipulate bits compared to booleans.
Doing a super simple "ls -ah" on vmlinux (unstripped):
Before: 224820232 bytes After: 224820376 bytes
...so your change made it _bigger_. OK, so running "strip --strip-debug" on those:
Before: 26599464 bytes After: 26599464 bytes
I've been surprised by the counter-intuitive impact of similar changes before, too. The result may also differ a lot between arm32 or arm64.
...so exactly the same. I tried finding some evidence using "readelf -ah":
Before: [ 2] .text PROGBITS ffffffc010010000 00020000 0000000000b03508 0000000000000000 WAX 0 0 65536 [ 3] .rodata PROGBITS ffffffc010b20000 00b30000 00000000002e84b3 0000000000000000 WAMS 0 0 4096
After: [ 2] .text PROGBITS ffffffc010010000 00020000 0000000000b03508 0000000000000000 WAX 0 0 65536 [ 3] .rodata PROGBITS ffffffc010b20000 00b30000 00000000002e84b3 0000000000000000 WAMS 0 0 4096
Maybe you have some evidence showing an improvement? Ah, OK. I disassembled ti_sn_bridge_enable() and your patch saves 12 bytes, but I guess maybe alignment washes it out in reality...
Yes, arm64 is bad w.r.t. this.
Gr{oetje,eeting}s,
Geert
To simplify interfacing with the panel, wrap it in a panel-bridge and let the DRM bridge helpers handle chaining of operations.
This also prepares for support of DRM_BRIDGE_ATTACH_NO_CONNECTOR, which requires all components in the display pipeline to be represented by bridges.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com --- drivers/gpu/drm/bridge/ti-sn65dsi86.c | 30 +++++++++++++++++++-------- 1 file changed, 21 insertions(+), 9 deletions(-)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index 1d1be791d5ba..c21a7f7d452b 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -124,6 +124,7 @@ * @edid: Detected EDID of eDP panel. * @refclk: Our reference clock. * @panel: Our panel. + * @next_bridge: The next bridge. * @enable_gpio: The GPIO we toggle to enable the bridge. * @supplies: Data for bulk enabling/disabling our regulators. * @dp_lanes: Count of dp_lanes we're using. @@ -152,6 +153,7 @@ struct ti_sn_bridge { struct mipi_dsi_device *dsi; struct clk *refclk; struct drm_panel *panel; + struct drm_bridge *next_bridge; struct gpio_desc *enable_gpio; struct regulator_bulk_data supplies[SN_REGULATOR_SUPPLY_NUM]; int dp_lanes; @@ -287,7 +289,7 @@ static int ti_sn_bridge_connector_get_modes(struct drm_connector *connector) } }
- return drm_panel_get_modes(pdata->panel, connector); + return drm_bridge_get_modes(pdata->next_bridge, connector); }
static enum drm_mode_status @@ -418,8 +420,18 @@ static int ti_sn_bridge_attach(struct drm_bridge *bridge, } pdata->dsi = dsi;
+ /* Attach the next bridge */ + ret = drm_bridge_attach(bridge->encoder, pdata->next_bridge, + &pdata->bridge, flags); + if (ret < 0) { + DRM_ERROR("failed to attach next bridge\n"); + goto err_dsi_detach; + } + return 0;
+err_dsi_detach: + mipi_dsi_detach(dsi); err_dsi_attach: mipi_dsi_device_unregister(dsi); err_dsi_host: @@ -431,16 +443,12 @@ static void ti_sn_bridge_disable(struct drm_bridge *bridge) { struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
- drm_panel_disable(pdata->panel); - /* disable video stream */ regmap_update_bits(pdata->regmap, SN_ENH_FRAME_REG, VSTREAM_ENABLE, 0); /* semi auto link training mode OFF */ regmap_write(pdata->regmap, SN_ML_TX_MODE_REG, 0); /* disable DP PLL */ regmap_write(pdata->regmap, SN_PLL_ENABLE_REG, 0); - - drm_panel_unprepare(pdata->panel); }
static u32 ti_sn_bridge_get_dsi_freq(struct ti_sn_bridge *pdata) @@ -819,8 +827,6 @@ static void ti_sn_bridge_enable(struct drm_bridge *bridge) /* enable video stream */ regmap_update_bits(pdata->regmap, SN_ENH_FRAME_REG, VSTREAM_ENABLE, VSTREAM_ENABLE); - - drm_panel_enable(pdata->panel); }
static void ti_sn_bridge_pre_enable(struct drm_bridge *bridge) @@ -850,8 +856,6 @@ static void ti_sn_bridge_pre_enable(struct drm_bridge *bridge) */ regmap_update_bits(pdata->regmap, SN_HPD_DISABLE_REG, HPD_DISABLE, HPD_DISABLE); - - drm_panel_prepare(pdata->panel); }
static void ti_sn_bridge_post_disable(struct drm_bridge *bridge) @@ -1245,6 +1249,14 @@ static int ti_sn_bridge_probe(struct i2c_client *client, return ret; }
+ pdata->next_bridge = devm_drm_panel_bridge_add(pdata->dev, + pdata->panel); + if (IS_ERR(pdata->next_bridge)) { + DRM_ERROR("failed to create panel bridge\n"); + ret = PTR_ERR(pdata->next_bridge); + return ret; + } + dev_set_drvdata(&client->dev, pdata);
pdata->enable_gpio = devm_gpiod_get_optional(pdata->dev, "enable",
On Mon, Mar 22, 2021 at 8:32 AM Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com wrote:
To simplify interfacing with the panel, wrap it in a panel-bridge and let the DRM bridge helpers handle chaining of operations.
This also prepares for support of DRM_BRIDGE_ATTACH_NO_CONNECTOR, which requires all components in the display pipeline to be represented by bridges.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
Reviewed-by: Jagan Teki jagan@amarulasolutions.com
Quoting Laurent Pinchart (2021-03-21 20:01:22)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index 1d1be791d5ba..c21a7f7d452b 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -418,8 +420,18 @@ static int ti_sn_bridge_attach(struct drm_bridge *bridge, } pdata->dsi = dsi;
/* Attach the next bridge */
ret = drm_bridge_attach(bridge->encoder, pdata->next_bridge,
&pdata->bridge, flags);
if (ret < 0) {
DRM_ERROR("failed to attach next bridge\n");
Can this be pushed into drm_bridge_attach() instead of in each caller?
goto err_dsi_detach;
}
return 0;
+err_dsi_detach:
mipi_dsi_detach(dsi);
err_dsi_attach: mipi_dsi_device_unregister(dsi); err_dsi_host: static void ti_sn_bridge_post_disable(struct drm_bridge *bridge) @@ -1245,6 +1249,14 @@ static int ti_sn_bridge_probe(struct i2c_client *client, return ret; }
pdata->next_bridge = devm_drm_panel_bridge_add(pdata->dev,
pdata->panel);
if (IS_ERR(pdata->next_bridge)) {
DRM_ERROR("failed to create panel bridge\n");
ret = PTR_ERR(pdata->next_bridge);
return ret;
Just return PTR_ERR(pdata->next_bridge)?
}
dev_set_drvdata(&client->dev, pdata);
Hi Stephen,
On Tue, Mar 23, 2021 at 12:14:04AM -0700, Stephen Boyd wrote:
Quoting Laurent Pinchart (2021-03-21 20:01:22)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index 1d1be791d5ba..c21a7f7d452b 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -418,8 +420,18 @@ static int ti_sn_bridge_attach(struct drm_bridge *bridge, } pdata->dsi = dsi;
/* Attach the next bridge */
ret = drm_bridge_attach(bridge->encoder, pdata->next_bridge,
&pdata->bridge, flags);
if (ret < 0) {
DRM_ERROR("failed to attach next bridge\n");
Can this be pushed into drm_bridge_attach() instead of in each caller?
Good idea.
goto err_dsi_detach;
}
return 0;
+err_dsi_detach:
mipi_dsi_detach(dsi);
err_dsi_attach: mipi_dsi_device_unregister(dsi); err_dsi_host: static void ti_sn_bridge_post_disable(struct drm_bridge *bridge) @@ -1245,6 +1249,14 @@ static int ti_sn_bridge_probe(struct i2c_client *client, return ret; }
pdata->next_bridge = devm_drm_panel_bridge_add(pdata->dev,
pdata->panel);
if (IS_ERR(pdata->next_bridge)) {
DRM_ERROR("failed to create panel bridge\n");
ret = PTR_ERR(pdata->next_bridge);
return ret;
Just return PTR_ERR(pdata->next_bridge)?
Indeed. Bad copy and paste.
}
dev_set_drvdata(&client->dev, pdata);
Hi,
On Sun, Mar 21, 2021 at 8:02 PM Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com wrote:
To simplify interfacing with the panel, wrap it in a panel-bridge and let the DRM bridge helpers handle chaining of operations.
This also prepares for support of DRM_BRIDGE_ATTACH_NO_CONNECTOR, which requires all components in the display pipeline to be represented by bridges.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
drivers/gpu/drm/bridge/ti-sn65dsi86.c | 30 +++++++++++++++++++-------- 1 file changed, 21 insertions(+), 9 deletions(-)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index 1d1be791d5ba..c21a7f7d452b 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -124,6 +124,7 @@
- @edid: Detected EDID of eDP panel.
- @refclk: Our reference clock.
- @panel: Our panel.
- @next_bridge: The next bridge.
To make it easier for folks who don't work with DRM all day, could you somehow clarify which direction "next" is talking about. AKA the next "outward" (towards the sink) or the next "inward" (toward the source)?
- @enable_gpio: The GPIO we toggle to enable the bridge.
- @supplies: Data for bulk enabling/disabling our regulators.
- @dp_lanes: Count of dp_lanes we're using.
@@ -152,6 +153,7 @@ struct ti_sn_bridge { struct mipi_dsi_device *dsi; struct clk *refclk; struct drm_panel *panel;
struct drm_bridge *next_bridge;
There's no reason to store the "panel" pointer anymore, right? It can just be a local variable in probe?
@@ -850,8 +856,6 @@ static void ti_sn_bridge_pre_enable(struct drm_bridge *bridge) */ regmap_update_bits(pdata->regmap, SN_HPD_DISABLE_REG, HPD_DISABLE, HPD_DISABLE);
drm_panel_prepare(pdata->panel);
Ugh, I guess conflicts with my EDID patch [1] which assumes that this function will directly turn the panel on. I'll see if I can find some solution...
[1] https://lore.kernel.org/r/20210304155144.3.I60a7fb23ce4589006bc95c64ab8d15c7...
Hi Doug,
On Wed, Mar 24, 2021 at 03:44:39PM -0700, Doug Anderson wrote:
On Sun, Mar 21, 2021 at 8:02 PM Laurent Pinchart wrote:
To simplify interfacing with the panel, wrap it in a panel-bridge and let the DRM bridge helpers handle chaining of operations.
This also prepares for support of DRM_BRIDGE_ATTACH_NO_CONNECTOR, which requires all components in the display pipeline to be represented by bridges.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
drivers/gpu/drm/bridge/ti-sn65dsi86.c | 30 +++++++++++++++++++-------- 1 file changed, 21 insertions(+), 9 deletions(-)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index 1d1be791d5ba..c21a7f7d452b 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -124,6 +124,7 @@
- @edid: Detected EDID of eDP panel.
- @refclk: Our reference clock.
- @panel: Our panel.
- @next_bridge: The next bridge.
To make it easier for folks who don't work with DRM all day, could you somehow clarify which direction "next" is talking about. AKA the next "outward" (towards the sink) or the next "inward" (toward the source)?
Sure, I'll expand the comment.
- @enable_gpio: The GPIO we toggle to enable the bridge.
- @supplies: Data for bulk enabling/disabling our regulators.
- @dp_lanes: Count of dp_lanes we're using.
@@ -152,6 +153,7 @@ struct ti_sn_bridge { struct mipi_dsi_device *dsi; struct clk *refclk; struct drm_panel *panel;
struct drm_bridge *next_bridge;
There's no reason to store the "panel" pointer anymore, right? It can just be a local variable in probe?
Good point, I'll fix that.
@@ -850,8 +856,6 @@ static void ti_sn_bridge_pre_enable(struct drm_bridge *bridge) */ regmap_update_bits(pdata->regmap, SN_HPD_DISABLE_REG, HPD_DISABLE, HPD_DISABLE);
drm_panel_prepare(pdata->panel);
Ugh, I guess conflicts with my EDID patch [1] which assumes that this function will directly turn the panel on. I'll see if I can find some solution...
[1] https://lore.kernel.org/r/20210304155144.3.I60a7fb23ce4589006bc95c64ab8d15c7...
Would using the drm_bridge_connector help ? It's a helper that creates a connector based on a chain of bridges. It implements the .get_modes() connector operation (see drm_bridge_connector_get_modes()), based on the .get_edid() operation provided by the bridges. As it has a full view of the chain, it could enable bridges prior to reading the EDID, and then power them off, including the panel-bridge.
Reorganize the functions in sections, related to connector operations, bridge operations, AUX adapter, GPIO controller and probe & remove.
This prepares for proper support of DRM_BRIDGE_ATTACH_NO_CONNECTOR that will add more functions, to ensure that the code will stay readable.
No functional change intended.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com --- drivers/gpu/drm/bridge/ti-sn65dsi86.c | 75 +++++++++++++++++---------- 1 file changed, 47 insertions(+), 28 deletions(-)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index c21a7f7d452b..7f5d53c74978 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -261,7 +261,10 @@ static void ti_sn_debugfs_remove(struct ti_sn_bridge *pdata) pdata->debugfs = NULL; }
-/* Connector funcs */ +/* ----------------------------------------------------------------------------- + * DRM Connector Operations + */ + static struct ti_sn_bridge * connector_to_ti_sn_bridge(struct drm_connector *connector) { @@ -328,25 +331,15 @@ static const struct drm_connector_funcs ti_sn_bridge_connector_funcs = { .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, };
+/*------------------------------------------------------------------------------ + * DRM Bridge Operations + */ + static struct ti_sn_bridge *bridge_to_ti_sn_bridge(struct drm_bridge *bridge) { return container_of(bridge, struct ti_sn_bridge, bridge); }
-static int ti_sn_bridge_parse_regulators(struct ti_sn_bridge *pdata) -{ - unsigned int i; - const char * const ti_sn_bridge_supply_names[] = { - "vcca", "vcc", "vccio", "vpll", - }; - - for (i = 0; i < SN_REGULATOR_SUPPLY_NUM; i++) - pdata->supplies[i].supply = ti_sn_bridge_supply_names[i]; - - return devm_regulator_bulk_get(pdata->dev, SN_REGULATOR_SUPPLY_NUM, - pdata->supplies); -} - static int ti_sn_bridge_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flags flags) { @@ -875,6 +868,10 @@ static const struct drm_bridge_funcs ti_sn_bridge_funcs = { .post_disable = ti_sn_bridge_post_disable, };
+/* ----------------------------------------------------------------------------- + * AUX Adapter + */ + static struct ti_sn_bridge *aux_to_ti_sn_bridge(struct drm_dp_aux *aux) { return container_of(aux, struct ti_sn_bridge, aux); @@ -973,19 +970,9 @@ static ssize_t ti_sn_aux_transfer(struct drm_dp_aux *aux, return len; }
-static int ti_sn_bridge_parse_dsi_host(struct ti_sn_bridge *pdata) -{ - struct device_node *np = pdata->dev->of_node; - - pdata->host_node = of_graph_get_remote_node(np, 0, 0); - - if (!pdata->host_node) { - DRM_ERROR("remote dsi host node not found\n"); - return -ENODEV; - } - - return 0; -} +/* ----------------------------------------------------------------------------- + * GPIO Controller + */
#if defined(CONFIG_OF_GPIO)
@@ -1168,6 +1155,10 @@ static inline int ti_sn_setup_gpio_controller(struct ti_sn_bridge *pdata)
#endif
+/* ----------------------------------------------------------------------------- + * Probe & Remove + */ + static void ti_sn_bridge_parse_lanes(struct ti_sn_bridge *pdata, struct device_node *np) { @@ -1217,6 +1208,34 @@ static void ti_sn_bridge_parse_lanes(struct ti_sn_bridge *pdata, pdata->ln_polrs = ln_polrs; }
+static int ti_sn_bridge_parse_regulators(struct ti_sn_bridge *pdata) +{ + unsigned int i; + const char * const ti_sn_bridge_supply_names[] = { + "vcca", "vcc", "vccio", "vpll", + }; + + for (i = 0; i < SN_REGULATOR_SUPPLY_NUM; i++) + pdata->supplies[i].supply = ti_sn_bridge_supply_names[i]; + + return devm_regulator_bulk_get(pdata->dev, SN_REGULATOR_SUPPLY_NUM, + pdata->supplies); +} + +static int ti_sn_bridge_parse_dsi_host(struct ti_sn_bridge *pdata) +{ + struct device_node *np = pdata->dev->of_node; + + pdata->host_node = of_graph_get_remote_node(np, 0, 0); + + if (!pdata->host_node) { + DRM_ERROR("remote dsi host node not found\n"); + return -ENODEV; + } + + return 0; +} + static int ti_sn_bridge_probe(struct i2c_client *client, const struct i2c_device_id *id) {
Quoting Laurent Pinchart (2021-03-21 20:01:23)
Reorganize the functions in sections, related to connector operations, bridge operations, AUX adapter, GPIO controller and probe & remove.
This prepares for proper support of DRM_BRIDGE_ATTACH_NO_CONNECTOR that will add more functions, to ensure that the code will stay readable.
No functional change intended.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
Reviewed-by: Stephen Boyd swboyd@chromium.org
Hi,
On Sun, Mar 21, 2021 at 8:02 PM Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com wrote:
Reorganize the functions in sections, related to connector operations, bridge operations, AUX adapter, GPIO controller and probe & remove.
This prepares for proper support of DRM_BRIDGE_ATTACH_NO_CONNECTOR that will add more functions, to ensure that the code will stay readable.
No functional change intended.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
drivers/gpu/drm/bridge/ti-sn65dsi86.c | 75 +++++++++++++++++---------- 1 file changed, 47 insertions(+), 28 deletions(-)
Reviewed-by: Douglas Anderson dianders@chromium.org
To prepare for making connector creation option, move connector creation out of ti_sn_bridge_attach to a separate function.
No functional change intended.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com --- drivers/gpu/drm/bridge/ti-sn65dsi86.c | 31 ++++++++++++++++++--------- 1 file changed, 21 insertions(+), 10 deletions(-)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index 7f5d53c74978..dc300fab4319 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -331,6 +331,25 @@ static const struct drm_connector_funcs ti_sn_bridge_connector_funcs = { .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, };
+static int ti_sn_bridge_connector_init(struct ti_sn_bridge *pdata) +{ + int ret; + + ret = drm_connector_init(pdata->bridge.dev, &pdata->connector, + &ti_sn_bridge_connector_funcs, + DRM_MODE_CONNECTOR_eDP); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + return ret; + } + + drm_connector_helper_add(&pdata->connector, + &ti_sn_bridge_connector_helper_funcs); + drm_connector_attach_encoder(&pdata->connector, pdata->bridge.encoder); + + return 0; +} + /*------------------------------------------------------------------------------ * DRM Bridge Operations */ @@ -357,17 +376,9 @@ static int ti_sn_bridge_attach(struct drm_bridge *bridge, return -EINVAL; }
- ret = drm_connector_init(bridge->dev, &pdata->connector, - &ti_sn_bridge_connector_funcs, - DRM_MODE_CONNECTOR_eDP); - if (ret) { - DRM_ERROR("Failed to initialize connector with drm\n"); + ret = ti_sn_bridge_connector_init(pdata); + if (ret < 0) return ret; - } - - drm_connector_helper_add(&pdata->connector, - &ti_sn_bridge_connector_helper_funcs); - drm_connector_attach_encoder(&pdata->connector, bridge->encoder);
/* * TODO: ideally finding host resource and dsi dev registration needs
Quoting Laurent Pinchart (2021-03-21 20:01:24)
To prepare for making connector creation option, move connector creation out of ti_sn_bridge_attach to a separate function.
No functional change intended.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
Reviewed-by: Stephen Boyd swboyd@chromium.org
Hi,
On Sun, Mar 21, 2021 at 8:02 PM Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com wrote:
To prepare for making connector creation option, move connector creation out of ti_sn_bridge_attach to a separate function.
No functional change intended.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
drivers/gpu/drm/bridge/ti-sn65dsi86.c | 31 ++++++++++++++++++--------- 1 file changed, 21 insertions(+), 10 deletions(-)
Reviewed-by: Douglas Anderson dianders@chromium.org
Implement the bridge connector-related .get_edid() operation, and report the related bridge capabilities and type. The .get_edid() operation is implemented with the same backend as the EDID retrieval from the connector .get_modes() operation.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com --- drivers/gpu/drm/bridge/ti-sn65dsi86.c | 30 ++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index dc300fab4319..6f6e075544e8 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -261,6 +261,18 @@ static void ti_sn_debugfs_remove(struct ti_sn_bridge *pdata) pdata->debugfs = NULL; }
+static struct edid *__ti_sn_bridge_get_edid(struct ti_sn_bridge *pdata, + struct drm_connector *connector) +{ + struct edid *edid; + + pm_runtime_get_sync(pdata->dev); + edid = drm_get_edid(connector, &pdata->aux.ddc); + pm_runtime_put(pdata->dev); + + return edid; +} + /* ----------------------------------------------------------------------------- * DRM Connector Operations */ @@ -277,11 +289,8 @@ static int ti_sn_bridge_connector_get_modes(struct drm_connector *connector) struct edid *edid = pdata->edid; int num, ret;
- if (!edid) { - pm_runtime_get_sync(pdata->dev); - edid = pdata->edid = drm_get_edid(connector, &pdata->aux.ddc); - pm_runtime_put(pdata->dev); - } + if (!edid) + edid = pdata->edid = __ti_sn_bridge_get_edid(pdata, connector);
if (edid && drm_edid_is_valid(edid)) { ret = drm_connector_update_edid_property(connector, edid); @@ -871,12 +880,21 @@ static void ti_sn_bridge_post_disable(struct drm_bridge *bridge) pm_runtime_put_sync(pdata->dev); }
+static struct edid *ti_sn_bridge_get_edid(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge); + + return __ti_sn_bridge_get_edid(pdata, connector); +} + static const struct drm_bridge_funcs ti_sn_bridge_funcs = { .attach = ti_sn_bridge_attach, .pre_enable = ti_sn_bridge_pre_enable, .enable = ti_sn_bridge_enable, .disable = ti_sn_bridge_disable, .post_disable = ti_sn_bridge_post_disable, + .get_edid = ti_sn_bridge_get_edid, };
/* ----------------------------------------------------------------------------- @@ -1335,6 +1353,8 @@ static int ti_sn_bridge_probe(struct i2c_client *client,
pdata->bridge.funcs = &ti_sn_bridge_funcs; pdata->bridge.of_node = client->dev.of_node; + pdata->bridge.ops = DRM_BRIDGE_OP_EDID; + pdata->bridge.type = DRM_MODE_CONNECTOR_eDP;
drm_bridge_add(&pdata->bridge);
Quoting Laurent Pinchart (2021-03-21 20:01:25)
Implement the bridge connector-related .get_edid() operation, and report the related bridge capabilities and type. The .get_edid() operation is implemented with the same backend as the EDID retrieval from the connector .get_modes() operation.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
Reviewed-by: Stephen Boyd swboyd@chromium.org
Hi,
On Sun, Mar 21, 2021 at 8:02 PM Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com wrote:
Implement the bridge connector-related .get_edid() operation, and report the related bridge capabilities and type. The .get_edid() operation is implemented with the same backend as the EDID retrieval from the connector .get_modes() operation.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
drivers/gpu/drm/bridge/ti-sn65dsi86.c | 30 ++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index dc300fab4319..6f6e075544e8 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -261,6 +261,18 @@ static void ti_sn_debugfs_remove(struct ti_sn_bridge *pdata) pdata->debugfs = NULL; }
+static struct edid *__ti_sn_bridge_get_edid(struct ti_sn_bridge *pdata,
struct drm_connector *connector)
+{
struct edid *edid;
pm_runtime_get_sync(pdata->dev);
edid = drm_get_edid(connector, &pdata->aux.ddc);
pm_runtime_put(pdata->dev);
As mentioned in my patch [1], the above is a bit iffy for eDP. Specifically just doing a pm_runtime_get_sync() isn't enough to actually be able to read the EDID. We also need to do any panel power sequencing and potentially set the "ignore HPD" bit in the bridge to actually read the EDID.
I'll try to find some time to make this better--let's see if I get distracted before I manage it.
return edid;
+}
/* -----------------------------------------------------------------------------
- DRM Connector Operations
*/ @@ -277,11 +289,8 @@ static int ti_sn_bridge_connector_get_modes(struct drm_connector *connector) struct edid *edid = pdata->edid; int num, ret;
if (!edid) {
pm_runtime_get_sync(pdata->dev);
edid = pdata->edid = drm_get_edid(connector, &pdata->aux.ddc);
pm_runtime_put(pdata->dev);
}
if (!edid)
edid = pdata->edid = __ti_sn_bridge_get_edid(pdata, connector);
It feels weird to me that we are now exposing the get_edid() function directly but we still need to implement get_modes(). I guess this is because we might need to fallback to the hardcoded modes? ...but that seems like it would be a common pattern for eDP bridges...
if (edid && drm_edid_is_valid(edid)) { ret = drm_connector_update_edid_property(connector, edid);
@@ -871,12 +880,21 @@ static void ti_sn_bridge_post_disable(struct drm_bridge *bridge) pm_runtime_put_sync(pdata->dev); }
+static struct edid *ti_sn_bridge_get_edid(struct drm_bridge *bridge,
struct drm_connector *connector)
+{
struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
return __ti_sn_bridge_get_edid(pdata, connector);
+}
static const struct drm_bridge_funcs ti_sn_bridge_funcs = { .attach = ti_sn_bridge_attach, .pre_enable = ti_sn_bridge_pre_enable, .enable = ti_sn_bridge_enable, .disable = ti_sn_bridge_disable, .post_disable = ti_sn_bridge_post_disable,
.get_edid = ti_sn_bridge_get_edid,
};
/* ----------------------------------------------------------------------------- @@ -1335,6 +1353,8 @@ static int ti_sn_bridge_probe(struct i2c_client *client,
pdata->bridge.funcs = &ti_sn_bridge_funcs; pdata->bridge.of_node = client->dev.of_node;
pdata->bridge.ops = DRM_BRIDGE_OP_EDID;
pdata->bridge.type = DRM_MODE_CONNECTOR_eDP;
Even with the few comments above, I think this is still fine to move us in the right direction. Unless I manage to fix up the EDID reading (in which case your patch would conflict and would need to be tweaked), then:
Reviewed-by: Douglas Anderson dianders@chromium.org
[1] https://lore.kernel.org/r/20210304155144.3.I60a7fb23ce4589006bc95c64ab8d15c7...
Hi Doug,
On Wed, Mar 24, 2021 at 03:46:28PM -0700, Doug Anderson wrote:
On Sun, Mar 21, 2021 at 8:02 PM Laurent Pinchart wrote:
Implement the bridge connector-related .get_edid() operation, and report the related bridge capabilities and type. The .get_edid() operation is implemented with the same backend as the EDID retrieval from the connector .get_modes() operation.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
drivers/gpu/drm/bridge/ti-sn65dsi86.c | 30 ++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index dc300fab4319..6f6e075544e8 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -261,6 +261,18 @@ static void ti_sn_debugfs_remove(struct ti_sn_bridge *pdata) pdata->debugfs = NULL; }
+static struct edid *__ti_sn_bridge_get_edid(struct ti_sn_bridge *pdata,
struct drm_connector *connector)
+{
struct edid *edid;
pm_runtime_get_sync(pdata->dev);
edid = drm_get_edid(connector, &pdata->aux.ddc);
pm_runtime_put(pdata->dev);
As mentioned in my patch [1], the above is a bit iffy for eDP. Specifically just doing a pm_runtime_get_sync() isn't enough to actually be able to read the EDID. We also need to do any panel power sequencing and potentially set the "ignore HPD" bit in the bridge to actually read the EDID.
I'll try to find some time to make this better--let's see if I get distracted before I manage it.
I've replied to your review of 05/11 with a possible solution. Fingers crossed :-)
return edid;
+}
/* -----------------------------------------------------------------------------
- DRM Connector Operations
*/ @@ -277,11 +289,8 @@ static int ti_sn_bridge_connector_get_modes(struct drm_connector *connector) struct edid *edid = pdata->edid; int num, ret;
if (!edid) {
pm_runtime_get_sync(pdata->dev);
edid = pdata->edid = drm_get_edid(connector, &pdata->aux.ddc);
pm_runtime_put(pdata->dev);
}
if (!edid)
edid = pdata->edid = __ti_sn_bridge_get_edid(pdata, connector);
It feels weird to me that we are now exposing the get_edid() function directly but we still need to implement get_modes(). I guess this is because we might need to fallback to the hardcoded modes? ...but that seems like it would be a common pattern for eDP bridges...
Bridges are moving from creating the connector internally to providing a set of bridge operations to support connector creation externally (by the drm_bridge_connector helper, or by display drivers directly if needed). During the transition, both need to be implemented, hence the bridge .get_edid() operation for the new model, and the connector .get_modes() operation for the old model.
if (edid && drm_edid_is_valid(edid)) { ret = drm_connector_update_edid_property(connector, edid);
@@ -871,12 +880,21 @@ static void ti_sn_bridge_post_disable(struct drm_bridge *bridge) pm_runtime_put_sync(pdata->dev); }
+static struct edid *ti_sn_bridge_get_edid(struct drm_bridge *bridge,
struct drm_connector *connector)
+{
struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
return __ti_sn_bridge_get_edid(pdata, connector);
+}
static const struct drm_bridge_funcs ti_sn_bridge_funcs = { .attach = ti_sn_bridge_attach, .pre_enable = ti_sn_bridge_pre_enable, .enable = ti_sn_bridge_enable, .disable = ti_sn_bridge_disable, .post_disable = ti_sn_bridge_post_disable,
.get_edid = ti_sn_bridge_get_edid,
};
/* ----------------------------------------------------------------------------- @@ -1335,6 +1353,8 @@ static int ti_sn_bridge_probe(struct i2c_client *client,
pdata->bridge.funcs = &ti_sn_bridge_funcs; pdata->bridge.of_node = client->dev.of_node;
pdata->bridge.ops = DRM_BRIDGE_OP_EDID;
pdata->bridge.type = DRM_MODE_CONNECTOR_eDP;
Even with the few comments above, I think this is still fine to move us in the right direction. Unless I manage to fix up the EDID reading (in which case your patch would conflict and would need to be tweaked), then:
Reviewed-by: Douglas Anderson dianders@chromium.org
[1] https://lore.kernel.org/r/20210304155144.3.I60a7fb23ce4589006bc95c64ab8d15c7...
Now that the driver supports the connector-related bridge operations, make the connector creation optional. This enables usage of the sn65dsi86 with the DRM bridge connector helper.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com --- drivers/gpu/drm/bridge/ti-sn65dsi86.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index 6f6e075544e8..e2527d597ccb 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -380,15 +380,12 @@ static int ti_sn_bridge_attach(struct drm_bridge *bridge, .node = NULL, };
- if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) { - DRM_ERROR("Fix bridge driver to make connector optional!"); - return -EINVAL; + if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) { + ret = ti_sn_bridge_connector_init(pdata); + if (ret < 0) + return ret; }
- ret = ti_sn_bridge_connector_init(pdata); - if (ret < 0) - return ret; - /* * TODO: ideally finding host resource and dsi dev registration needs * to be done in bridge probe. But some existing DSI host drivers will
Despite the SN65DSI86 being an eDP bridge, on some systems its output is routed to a DisplayPort connector. Enable DisplayPort mode when the next component in the display pipeline is not a panel, and disable eDP features in that case.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com --- drivers/gpu/drm/bridge/ti-sn65dsi86.c | 32 ++++++++++++++++++++------- 1 file changed, 24 insertions(+), 8 deletions(-)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index e2527d597ccb..f792227142a7 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -55,6 +55,7 @@ #define SN_LN_ASSIGN_REG 0x59 #define LN_ASSIGN_WIDTH 2 #define SN_ENH_FRAME_REG 0x5A +#define ASSR_CONTROL BIT(0) #define VSTREAM_ENABLE BIT(3) #define LN_POLRS_OFFSET 4 #define LN_POLRS_MASK 0xf0 @@ -86,6 +87,8 @@ #define SN_DATARATE_CONFIG_REG 0x94 #define DP_DATARATE_MASK GENMASK(7, 5) #define DP_DATARATE(x) ((x) << 5) +#define SN_TRAINING_SETTING_REG 0x95 +#define SCRAMBLE_DISABLE BIT(4) #define SN_ML_TX_MODE_REG 0x96 #define ML_TX_MAIN_LINK_OFF 0 #define ML_TX_NORMAL_MODE BIT(0) @@ -723,6 +726,11 @@ static int ti_sn_link_training(struct ti_sn_bridge *pdata, int dp_rate_idx, regmap_update_bits(pdata->regmap, SN_DATARATE_CONFIG_REG, DP_DATARATE_MASK, DP_DATARATE(dp_rate_idx));
+ /* For DisplayPort, use the standard DP scrambler seed. */ + if (pdata->bridge.type == DRM_MODE_CONNECTOR_DisplayPort) + regmap_update_bits(pdata->regmap, SN_ENH_FRAME_REG, + ASSR_CONTROL, 0); + /* enable DP PLL */ regmap_write(pdata->regmap, SN_PLL_ENABLE_REG, 1);
@@ -734,6 +742,11 @@ static int ti_sn_link_training(struct ti_sn_bridge *pdata, int dp_rate_idx, goto exit; }
+ /* For DisplayPort, disable scrambling mode. */ + if (pdata->bridge.type == DRM_MODE_CONNECTOR_DisplayPort) + regmap_update_bits(pdata->regmap, SN_TRAINING_SETTING_REG, + SCRAMBLE_DISABLE, SCRAMBLE_DISABLE); + /* * We'll try to link train several times. As part of link training * the bridge chip will write DP_SET_POWER_D0 to DP_SET_POWER. If @@ -1288,18 +1301,20 @@ static int ti_sn_bridge_probe(struct i2c_client *client, pdata->dev = &client->dev;
ret = drm_of_find_panel_or_bridge(pdata->dev->of_node, 1, 0, - &pdata->panel, NULL); + &pdata->panel, &pdata->next_bridge); if (ret) { DRM_ERROR("could not find any panel node\n"); return ret; }
- pdata->next_bridge = devm_drm_panel_bridge_add(pdata->dev, - pdata->panel); - if (IS_ERR(pdata->next_bridge)) { - DRM_ERROR("failed to create panel bridge\n"); - ret = PTR_ERR(pdata->next_bridge); - return ret; + if (!pdata->next_bridge) { + pdata->next_bridge = devm_drm_panel_bridge_add(pdata->dev, + pdata->panel); + if (IS_ERR(pdata->next_bridge)) { + DRM_ERROR("failed to create panel bridge\n"); + ret = PTR_ERR(pdata->next_bridge); + return ret; + } }
dev_set_drvdata(&client->dev, pdata); @@ -1351,7 +1366,8 @@ static int ti_sn_bridge_probe(struct i2c_client *client, pdata->bridge.funcs = &ti_sn_bridge_funcs; pdata->bridge.of_node = client->dev.of_node; pdata->bridge.ops = DRM_BRIDGE_OP_EDID; - pdata->bridge.type = DRM_MODE_CONNECTOR_eDP; + pdata->bridge.type = pdata->panel ? DRM_MODE_CONNECTOR_eDP + : DRM_MODE_CONNECTOR_DisplayPort;
drm_bridge_add(&pdata->bridge);
Hi,
On Sun, Mar 21, 2021 at 8:02 PM Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com wrote:
Despite the SN65DSI86 being an eDP bridge, on some systems its output is routed to a DisplayPort connector. Enable DisplayPort mode when the next component in the display pipeline is not a panel, and disable eDP features in that case.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
drivers/gpu/drm/bridge/ti-sn65dsi86.c | 32 ++++++++++++++++++++------- 1 file changed, 24 insertions(+), 8 deletions(-)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index e2527d597ccb..f792227142a7 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -55,6 +55,7 @@ #define SN_LN_ASSIGN_REG 0x59 #define LN_ASSIGN_WIDTH 2 #define SN_ENH_FRAME_REG 0x5A +#define ASSR_CONTROL BIT(0) #define VSTREAM_ENABLE BIT(3) #define LN_POLRS_OFFSET 4 #define LN_POLRS_MASK 0xf0 @@ -86,6 +87,8 @@ #define SN_DATARATE_CONFIG_REG 0x94 #define DP_DATARATE_MASK GENMASK(7, 5) #define DP_DATARATE(x) ((x) << 5) +#define SN_TRAINING_SETTING_REG 0x95 +#define SCRAMBLE_DISABLE BIT(4) #define SN_ML_TX_MODE_REG 0x96 #define ML_TX_MAIN_LINK_OFF 0 #define ML_TX_NORMAL_MODE BIT(0) @@ -723,6 +726,11 @@ static int ti_sn_link_training(struct ti_sn_bridge *pdata, int dp_rate_idx, regmap_update_bits(pdata->regmap, SN_DATARATE_CONFIG_REG, DP_DATARATE_MASK, DP_DATARATE(dp_rate_idx));
/* For DisplayPort, use the standard DP scrambler seed. */
if (pdata->bridge.type == DRM_MODE_CONNECTOR_DisplayPort)
regmap_update_bits(pdata->regmap, SN_ENH_FRAME_REG,
ASSR_CONTROL, 0);
I don't actually know anything about DP scrambler seeds. However:
1. From reading the docs, this field seems to be documented to be "read only" unless:
1a) The "TEST2" pin is pulled high when you power on the bridge. 1b) You set "ASSR_OVERRIDE" (page select to page 7, write to register 0x16, page select back to page 0).
I don't know if TEST2 is being pulled high in your hardware, but at least I can see that 1b) isn't done. So I'm guessing that this line is a no-op? If I had to guess from all the hoops they're making you jump through there's some sort of errata around standard scrambling on this bridge chip. Are you sure it works OK?
2. The docs I see claim that this field is 2 bits big. It seems like it would be nice to honor. Yeah, it's silly because 0x11 and 0x10 are "reserved" so it's really more like a 1-bit field, but still seems like it would be better to set both bits, or at least add a comment explaining why you're not matching the datasheet.
3. Your patch doesn't seem to touch the bit of code in ti_sn_bridge_enable() that says this:
/** * The SN65DSI86 only supports ASSR Display Authentication method and * this method is enabled by default. An eDP panel must support this * authentication method. We need to enable this method in the eDP panel * at DisplayPort address 0x0010A prior to link training. */ drm_dp_dpcd_writeb(&pdata->aux, DP_EDP_CONFIGURATION_SET, DP_ALTERNATE_SCRAMBLER_RESET_ENABLE);
Won't that be a problem?
/* enable DP PLL */ regmap_write(pdata->regmap, SN_PLL_ENABLE_REG, 1);
@@ -734,6 +742,11 @@ static int ti_sn_link_training(struct ti_sn_bridge *pdata, int dp_rate_idx, goto exit; }
/* For DisplayPort, disable scrambling mode. */
if (pdata->bridge.type == DRM_MODE_CONNECTOR_DisplayPort)
regmap_update_bits(pdata->regmap, SN_TRAINING_SETTING_REG,
SCRAMBLE_DISABLE, SCRAMBLE_DISABLE);
I'm assuming that this is the important part of your patch? Would be sorta nice to include the "why" in your comment. Why do you want to disable scrambling mode for DP but not for eDP? Maybe you care about compatibility but not EMI if you're hooking up to random DP things?
-Doug
Hi Doug,
On Wed, Mar 24, 2021 at 03:47:07PM -0700, Doug Anderson wrote:
On Sun, Mar 21, 2021 at 8:02 PM Laurent Pinchart wrote:
Despite the SN65DSI86 being an eDP bridge, on some systems its output is routed to a DisplayPort connector. Enable DisplayPort mode when the next component in the display pipeline is not a panel, and disable eDP features in that case.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
drivers/gpu/drm/bridge/ti-sn65dsi86.c | 32 ++++++++++++++++++++------- 1 file changed, 24 insertions(+), 8 deletions(-)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index e2527d597ccb..f792227142a7 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -55,6 +55,7 @@ #define SN_LN_ASSIGN_REG 0x59 #define LN_ASSIGN_WIDTH 2 #define SN_ENH_FRAME_REG 0x5A +#define ASSR_CONTROL BIT(0) #define VSTREAM_ENABLE BIT(3) #define LN_POLRS_OFFSET 4 #define LN_POLRS_MASK 0xf0 @@ -86,6 +87,8 @@ #define SN_DATARATE_CONFIG_REG 0x94 #define DP_DATARATE_MASK GENMASK(7, 5) #define DP_DATARATE(x) ((x) << 5) +#define SN_TRAINING_SETTING_REG 0x95 +#define SCRAMBLE_DISABLE BIT(4) #define SN_ML_TX_MODE_REG 0x96 #define ML_TX_MAIN_LINK_OFF 0 #define ML_TX_NORMAL_MODE BIT(0) @@ -723,6 +726,11 @@ static int ti_sn_link_training(struct ti_sn_bridge *pdata, int dp_rate_idx, regmap_update_bits(pdata->regmap, SN_DATARATE_CONFIG_REG, DP_DATARATE_MASK, DP_DATARATE(dp_rate_idx));
/* For DisplayPort, use the standard DP scrambler seed. */
if (pdata->bridge.type == DRM_MODE_CONNECTOR_DisplayPort)
regmap_update_bits(pdata->regmap, SN_ENH_FRAME_REG,
ASSR_CONTROL, 0);
I don't actually know anything about DP scrambler seeds. However:
- From reading the docs, this field seems to be documented to be
"read only" unless:
1a) The "TEST2" pin is pulled high when you power on the bridge. 1b) You set "ASSR_OVERRIDE" (page select to page 7, write to register 0x16, page select back to page 0).
I don't know if TEST2 is being pulled high in your hardware, but at least I can see that 1b) isn't done. So I'm guessing that this line is a no-op? If I had to guess from all the hoops they're making you jump through there's some sort of errata around standard scrambling on this bridge chip. Are you sure it works OK?
Good question :-) We managed to get the SN65DSI86 to work with an external DP monitor yesterday, so it's possible (some modes don't operate correctly yet, but I assume that to be an issue with the DSI encoder).
The TEST2 pin is strapped to ground on the board.
According to the DisplayPort specification, eDP and DP use different scrambler seeds to prevent interoperability between an eDP source and a DP sink. I'll check what happens without this change.
- The docs I see claim that this field is 2 bits big. It seems like
it would be nice to honor. Yeah, it's silly because 0x11 and 0x10 are "reserved" so it's really more like a 1-bit field, but still seems like it would be better to set both bits, or at least add a comment explaining why you're not matching the datasheet.
Sure.
- Your patch doesn't seem to touch the bit of code in
ti_sn_bridge_enable() that says this:
/**
- The SN65DSI86 only supports ASSR Display Authentication method and
- this method is enabled by default. An eDP panel must support this
- authentication method. We need to enable this method in the eDP panel
- at DisplayPort address 0x0010A prior to link training.
*/ drm_dp_dpcd_writeb(&pdata->aux, DP_EDP_CONFIGURATION_SET, DP_ALTERNATE_SCRAMBLER_RESET_ENABLE);
Won't that be a problem?
I'll have a look.
/* enable DP PLL */ regmap_write(pdata->regmap, SN_PLL_ENABLE_REG, 1);
@@ -734,6 +742,11 @@ static int ti_sn_link_training(struct ti_sn_bridge *pdata, int dp_rate_idx, goto exit; }
/* For DisplayPort, disable scrambling mode. */
if (pdata->bridge.type == DRM_MODE_CONNECTOR_DisplayPort)
regmap_update_bits(pdata->regmap, SN_TRAINING_SETTING_REG,
SCRAMBLE_DISABLE, SCRAMBLE_DISABLE);
I'm assuming that this is the important part of your patch? Would be sorta nice to include the "why" in your comment. Why do you want to disable scrambling mode for DP but not for eDP? Maybe you care about compatibility but not EMI if you're hooking up to random DP things?
I'll investigate and include proper documentation in v2 (or drop the change altogether if it's not required).
Hi Doug, Laurent,
Quoting Laurent Pinchart (2021-06-23 14:59:00)
Hi Doug,
On Wed, Mar 24, 2021 at 03:47:07PM -0700, Doug Anderson wrote:
On Sun, Mar 21, 2021 at 8:02 PM Laurent Pinchart wrote:
Despite the SN65DSI86 being an eDP bridge, on some systems its output is routed to a DisplayPort connector. Enable DisplayPort mode when the next component in the display pipeline is not a panel, and disable eDP features in that case.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
drivers/gpu/drm/bridge/ti-sn65dsi86.c | 32 ++++++++++++++++++++------- 1 file changed, 24 insertions(+), 8 deletions(-)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index e2527d597ccb..f792227142a7 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -55,6 +55,7 @@ #define SN_LN_ASSIGN_REG 0x59 #define LN_ASSIGN_WIDTH 2 #define SN_ENH_FRAME_REG 0x5A +#define ASSR_CONTROL BIT(0) #define VSTREAM_ENABLE BIT(3) #define LN_POLRS_OFFSET 4 #define LN_POLRS_MASK 0xf0 @@ -86,6 +87,8 @@ #define SN_DATARATE_CONFIG_REG 0x94 #define DP_DATARATE_MASK GENMASK(7, 5) #define DP_DATARATE(x) ((x) << 5) +#define SN_TRAINING_SETTING_REG 0x95 +#define SCRAMBLE_DISABLE BIT(4) #define SN_ML_TX_MODE_REG 0x96 #define ML_TX_MAIN_LINK_OFF 0 #define ML_TX_NORMAL_MODE BIT(0) @@ -723,6 +726,11 @@ static int ti_sn_link_training(struct ti_sn_bridge *pdata, int dp_rate_idx, regmap_update_bits(pdata->regmap, SN_DATARATE_CONFIG_REG, DP_DATARATE_MASK, DP_DATARATE(dp_rate_idx));
/* For DisplayPort, use the standard DP scrambler seed. */
if (pdata->bridge.type == DRM_MODE_CONNECTOR_DisplayPort)
regmap_update_bits(pdata->regmap, SN_ENH_FRAME_REG,
ASSR_CONTROL, 0);
I don't actually know anything about DP scrambler seeds. However:
- From reading the docs, this field seems to be documented to be
"read only" unless:
1a) The "TEST2" pin is pulled high when you power on the bridge. 1b) You set "ASSR_OVERRIDE" (page select to page 7, write to register 0x16, page select back to page 0).
I don't know if TEST2 is being pulled high in your hardware, but at least I can see that 1b) isn't done. So I'm guessing that this line is a no-op? If I had to guess from all the hoops they're making you jump through there's some sort of errata around standard scrambling on this bridge chip. Are you sure it works OK?
Good question :-) We managed to get the SN65DSI86 to work with an external DP monitor yesterday, so it's possible (some modes don't operate correctly yet, but I assume that to be an issue with the DSI encoder).
The TEST2 pin is strapped to ground on the board.
According to the DisplayPort specification, eDP and DP use different scrambler seeds to prevent interoperability between an eDP source and a DP sink. I'll check what happens without this change.
Without this change, the display still works...
- The docs I see claim that this field is 2 bits big. It seems like
it would be nice to honor. Yeah, it's silly because 0x11 and 0x10 are "reserved" so it's really more like a 1-bit field, but still seems like it would be better to set both bits, or at least add a comment explaining why you're not matching the datasheet.
Sure.
- Your patch doesn't seem to touch the bit of code in
ti_sn_bridge_enable() that says this:
/**
- The SN65DSI86 only supports ASSR Display Authentication method and
- this method is enabled by default. An eDP panel must support this
- authentication method. We need to enable this method in the eDP panel
- at DisplayPort address 0x0010A prior to link training.
*/ drm_dp_dpcd_writeb(&pdata->aux, DP_EDP_CONFIGURATION_SET, DP_ALTERNATE_SCRAMBLER_RESET_ENABLE);
Won't that be a problem?
I'll have a look.
I'm not sure I yet fully understand the requirements here, but could it be that only supporting ASSR is why the scrambling is disabled below?
Commenting out that write does not affect the bring up of my DP monitor.
/* enable DP PLL */ regmap_write(pdata->regmap, SN_PLL_ENABLE_REG, 1);
@@ -734,6 +742,11 @@ static int ti_sn_link_training(struct ti_sn_bridge *pdata, int dp_rate_idx, goto exit; }
/* For DisplayPort, disable scrambling mode. */
if (pdata->bridge.type == DRM_MODE_CONNECTOR_DisplayPort)
regmap_update_bits(pdata->regmap, SN_TRAINING_SETTING_REG,
SCRAMBLE_DISABLE, SCRAMBLE_DISABLE);
I'm assuming that this is the important part of your patch? Would be sorta nice to include the "why" in your comment. Why do you want to disable scrambling mode for DP but not for eDP? Maybe you care about compatibility but not EMI if you're hooking up to random DP things?
I'll investigate and include proper documentation in v2 (or drop the change altogether if it's not required).
And indeed, this part is important. If I drop this hunk - then I get no display output.
I'm afraid I don't (yet) know the reasons 'why' to extend the comment, beyond "Scrambling is not supported for DP".
If anyone already does, please feel free to provide the text, and I'll include it in the next revision, or I'll try to do some more digging into this part.
-- Kieran
-- Regards,
Laurent Pinchart
Hi,
On Wed, Feb 23, 2022 at 10:05 AM Kieran Bingham kieran.bingham@ideasonboard.com wrote:
/* For DisplayPort, disable scrambling mode. */
if (pdata->bridge.type == DRM_MODE_CONNECTOR_DisplayPort)
regmap_update_bits(pdata->regmap, SN_TRAINING_SETTING_REG,
SCRAMBLE_DISABLE, SCRAMBLE_DISABLE);
I'm assuming that this is the important part of your patch? Would be sorta nice to include the "why" in your comment. Why do you want to disable scrambling mode for DP but not for eDP? Maybe you care about compatibility but not EMI if you're hooking up to random DP things?
I'll investigate and include proper documentation in v2 (or drop the change altogether if it's not required).
And indeed, this part is important. If I drop this hunk - then I get no display output.
I'm afraid I don't (yet) know the reasons 'why' to extend the comment, beyond "Scrambling is not supported for DP".
If anyone already does, please feel free to provide the text, and I'll include it in the next revision, or I'll try to do some more digging into this part.
I don't know _tons_ about it, but I later learned that the "alternate" scrambler is used for eDP and the normal scrambler is used for DP. I don't have any background about why they are different other than what looks to be intentionally making the two things incompatible.
...so I guess that would make it pretty clear why you can't use the alternate scrambler for DP. I haven't personally done the research to know if you can be officially DP compliant with the scrambler disabled. I also don't know why the ti-sn65dsi86 makes it so difficult to switch to the standard scrambler or if it works at all... ;-)
-Doug
On Wed, Feb 23, 2022 at 10:20:18AM -0800, Doug Anderson wrote:
On Wed, Feb 23, 2022 at 10:05 AM Kieran Bingham wrote:
/* For DisplayPort, disable scrambling mode. */
if (pdata->bridge.type == DRM_MODE_CONNECTOR_DisplayPort)
regmap_update_bits(pdata->regmap, SN_TRAINING_SETTING_REG,
SCRAMBLE_DISABLE, SCRAMBLE_DISABLE);
I'm assuming that this is the important part of your patch? Would be sorta nice to include the "why" in your comment. Why do you want to disable scrambling mode for DP but not for eDP? Maybe you care about compatibility but not EMI if you're hooking up to random DP things?
I'll investigate and include proper documentation in v2 (or drop the change altogether if it's not required).
And indeed, this part is important. If I drop this hunk - then I get no display output.
I'm afraid I don't (yet) know the reasons 'why' to extend the comment, beyond "Scrambling is not supported for DP".
If anyone already does, please feel free to provide the text, and I'll include it in the next revision, or I'll try to do some more digging into this part.
I don't know _tons_ about it, but I later learned that the "alternate" scrambler is used for eDP and the normal scrambler is used for DP. I don't have any background about why they are different other than what looks to be intentionally making the two things incompatible.
I think it was done for DRM purpose, to prevent signals meant for a panel to be connected to a device that could capture video from a DP source.
...so I guess that would make it pretty clear why you can't use the alternate scrambler for DP. I haven't personally done the research to know if you can be officially DP compliant with the scrambler disabled. I also don't know why the ti-sn65dsi86 makes it so difficult to switch to the standard scrambler or if it works at all... ;-)
When the SN65DSI86 is used in DisplayPort mode, its output is likely routed to a DisplayPort connector, which can benefit from hotplug detection. Support it in such cases, with polling mode only for now.
The implementation is limited to the bridge operations, as the connector operations are legacy and new users should use DRM_BRIDGE_ATTACH_NO_CONNECTOR.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com --- drivers/gpu/drm/bridge/ti-sn65dsi86.c | 46 +++++++++++++++++++-------- 1 file changed, 33 insertions(+), 13 deletions(-)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index f792227142a7..72f6362adf44 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -167,6 +167,8 @@ struct ti_sn_bridge { struct gpio_chip gchip; DECLARE_BITMAP(gchip_output, SN_NUM_GPIOS); #endif + + bool no_hpd; };
static const struct regmap_range ti_sn_bridge_volatile_ranges[] = { @@ -862,23 +864,28 @@ static void ti_sn_bridge_pre_enable(struct drm_bridge *bridge) ti_sn_bridge_set_refclk_freq(pdata);
/* - * HPD on this bridge chip is a bit useless. This is an eDP bridge - * so the HPD is an internal signal that's only there to signal that - * the panel is done powering up. ...but the bridge chip debounces - * this signal by between 100 ms and 400 ms (depending on process, - * voltage, and temperate--I measured it at about 200 ms). One + * As this is an eDP bridge, the output will be connected to a fixed + * panel in most systems. HPD is in that case only an internal signal + * to signal that the panel is done powering up. The bridge chip + * debounces this signal by between 100 ms and 400 ms (depending on + * process, voltage, and temperate--I measured it at about 200 ms). One * particular panel asserted HPD 84 ms after it was powered on meaning * that we saw HPD 284 ms after power on. ...but the same panel said * that instead of looking at HPD you could just hardcode a delay of - * 200 ms. We'll assume that the panel driver will have the hardcoded - * delay in its prepare and always disable HPD. + * 200 ms. HPD is thus a bit useless. For this type of use cases, we'll + * assume that the panel driver will have the hardcoded delay in its + * prepare and always disable HPD. * - * If HPD somehow makes sense on some future panel we'll have to - * change this to be conditional on someone specifying that HPD should - * be used. + * However, on some systems, the output is connected to a DisplayPort + * connector. HPD is needed in such cases. To accommodate both use + * cases, enable HPD only when requested. */ - regmap_update_bits(pdata->regmap, SN_HPD_DISABLE_REG, HPD_DISABLE, - HPD_DISABLE); + if (pdata->no_hpd) + regmap_update_bits(pdata->regmap, SN_HPD_DISABLE_REG, + HPD_DISABLE, HPD_DISABLE); + else + regmap_update_bits(pdata->regmap, SN_HPD_DISABLE_REG, + HPD_DISABLE, 0); }
static void ti_sn_bridge_post_disable(struct drm_bridge *bridge) @@ -890,6 +897,15 @@ static void ti_sn_bridge_post_disable(struct drm_bridge *bridge) pm_runtime_put_sync(pdata->dev); }
+static enum drm_connector_status ti_sn_bridge_detect(struct drm_bridge *bridge) +{ + struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge); + int val; + + regmap_read(pdata->regmap, SN_HPD_DISABLE_REG, &val); + return val ? connector_status_connected : connector_status_disconnected; +} + static struct edid *ti_sn_bridge_get_edid(struct drm_bridge *bridge, struct drm_connector *connector) { @@ -904,6 +920,7 @@ static const struct drm_bridge_funcs ti_sn_bridge_funcs = { .enable = ti_sn_bridge_enable, .disable = ti_sn_bridge_disable, .post_disable = ti_sn_bridge_post_disable, + .detect = ti_sn_bridge_detect, .get_edid = ti_sn_bridge_get_edid, };
@@ -1327,6 +1344,8 @@ static int ti_sn_bridge_probe(struct i2c_client *client, return ret; }
+ pdata->no_hpd = of_property_read_bool(pdata->dev->of_node, "no-hpd"); + ti_sn_bridge_parse_lanes(pdata, client->dev.of_node);
ret = ti_sn_bridge_parse_regulators(pdata); @@ -1365,7 +1384,8 @@ static int ti_sn_bridge_probe(struct i2c_client *client,
pdata->bridge.funcs = &ti_sn_bridge_funcs; pdata->bridge.of_node = client->dev.of_node; - pdata->bridge.ops = DRM_BRIDGE_OP_EDID; + pdata->bridge.ops = (pdata->no_hpd ? 0 : DRM_BRIDGE_OP_DETECT) + | DRM_BRIDGE_OP_EDID; pdata->bridge.type = pdata->panel ? DRM_MODE_CONNECTOR_eDP : DRM_MODE_CONNECTOR_DisplayPort;
Quoting Laurent Pinchart (2021-03-21 20:01:28)
When the SN65DSI86 is used in DisplayPort mode, its output is likely routed to a DisplayPort connector, which can benefit from hotplug detection. Support it in such cases, with polling mode only for now.
The implementation is limited to the bridge operations, as the connector operations are legacy and new users should use DRM_BRIDGE_ATTACH_NO_CONNECTOR.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
Reviewed-by: Stephen Boyd swboyd@chromium.org
drivers/gpu/drm/bridge/ti-sn65dsi86.c | 46 +++++++++++++++++++-------- 1 file changed, 33 insertions(+), 13 deletions(-)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index f792227142a7..72f6362adf44 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -890,6 +897,15 @@ static void ti_sn_bridge_post_disable(struct drm_bridge *bridge) pm_runtime_put_sync(pdata->dev); }
+static enum drm_connector_status ti_sn_bridge_detect(struct drm_bridge *bridge) +{
struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
int val;
regmap_read(pdata->regmap, SN_HPD_DISABLE_REG, &val);
return val ? connector_status_connected : connector_status_disconnected;
+}
static struct edid *ti_sn_bridge_get_edid(struct drm_bridge *bridge, struct drm_connector *connector) { @@ -904,6 +920,7 @@ static const struct drm_bridge_funcs ti_sn_bridge_funcs = { .enable = ti_sn_bridge_enable, .disable = ti_sn_bridge_disable, .post_disable = ti_sn_bridge_post_disable,
.detect = ti_sn_bridge_detect, .get_edid = ti_sn_bridge_get_edid,
};
@@ -1327,6 +1344,8 @@ static int ti_sn_bridge_probe(struct i2c_client *client, return ret; }
pdata->no_hpd = of_property_read_bool(pdata->dev->of_node, "no-hpd");
I see that we missed adding this property to the DTS file but skated by because it was the default in the driver. I don't think it's a big deal just something we should fix in sc7180-trogdor.dtsi before this patch is merged.
ti_sn_bridge_parse_lanes(pdata, client->dev.of_node); ret = ti_sn_bridge_parse_regulators(pdata);
Hi,
On Sun, Mar 21, 2021 at 8:02 PM Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com wrote:
When the SN65DSI86 is used in DisplayPort mode, its output is likely routed to a DisplayPort connector, which can benefit from hotplug detection. Support it in such cases, with polling mode only for now.
The implementation is limited to the bridge operations, as the connector operations are legacy and new users should use DRM_BRIDGE_ATTACH_NO_CONNECTOR.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
drivers/gpu/drm/bridge/ti-sn65dsi86.c | 46 +++++++++++++++++++-------- 1 file changed, 33 insertions(+), 13 deletions(-)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index f792227142a7..72f6362adf44 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -167,6 +167,8 @@ struct ti_sn_bridge { struct gpio_chip gchip; DECLARE_BITMAP(gchip_output, SN_NUM_GPIOS); #endif
bool no_hpd;
This structure is documented by kernel-doc, but you didn't add your new member.
};
static const struct regmap_range ti_sn_bridge_volatile_ranges[] = { @@ -862,23 +864,28 @@ static void ti_sn_bridge_pre_enable(struct drm_bridge *bridge) ti_sn_bridge_set_refclk_freq(pdata);
/*
* HPD on this bridge chip is a bit useless. This is an eDP bridge
* so the HPD is an internal signal that's only there to signal that
* the panel is done powering up. ...but the bridge chip debounces
* this signal by between 100 ms and 400 ms (depending on process,
* voltage, and temperate--I measured it at about 200 ms). One
* As this is an eDP bridge, the output will be connected to a fixed
* panel in most systems. HPD is in that case only an internal signal
* to signal that the panel is done powering up. The bridge chip
* debounces this signal by between 100 ms and 400 ms (depending on
* process, voltage, and temperate--I measured it at about 200 ms). One * particular panel asserted HPD 84 ms after it was powered on meaning * that we saw HPD 284 ms after power on. ...but the same panel said * that instead of looking at HPD you could just hardcode a delay of
* 200 ms. We'll assume that the panel driver will have the hardcoded
* delay in its prepare and always disable HPD.
* 200 ms. HPD is thus a bit useless. For this type of use cases, we'll
* assume that the panel driver will have the hardcoded delay in its
* prepare and always disable HPD. *
* If HPD somehow makes sense on some future panel we'll have to
* change this to be conditional on someone specifying that HPD should
* be used.
* However, on some systems, the output is connected to a DisplayPort
* connector. HPD is needed in such cases. To accommodate both use
* cases, enable HPD only when requested. */
regmap_update_bits(pdata->regmap, SN_HPD_DISABLE_REG, HPD_DISABLE,
HPD_DISABLE);
if (pdata->no_hpd)
regmap_update_bits(pdata->regmap, SN_HPD_DISABLE_REG,
HPD_DISABLE, HPD_DISABLE);
else
regmap_update_bits(pdata->regmap, SN_HPD_DISABLE_REG,
HPD_DISABLE, 0);
Optionally you could skip the "else". HPD enabled is the default state and, in general, we don't exhaustively init all registers and rely on the power-on defaults for ones we don't explicitly control.
}
static void ti_sn_bridge_post_disable(struct drm_bridge *bridge) @@ -890,6 +897,15 @@ static void ti_sn_bridge_post_disable(struct drm_bridge *bridge) pm_runtime_put_sync(pdata->dev); }
+static enum drm_connector_status ti_sn_bridge_detect(struct drm_bridge *bridge) +{
struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
int val;
regmap_read(pdata->regmap, SN_HPD_DISABLE_REG, &val);
return val ? connector_status_connected : connector_status_disconnected;
I would have expected that you would have used the interrupt signal, but I guess it just polls in this case. I suppose polling has the advantage that it's simpler... Maybe throw in a comment about why IRQ isn't being used?
+}
static struct edid *ti_sn_bridge_get_edid(struct drm_bridge *bridge, struct drm_connector *connector) { @@ -904,6 +920,7 @@ static const struct drm_bridge_funcs ti_sn_bridge_funcs = { .enable = ti_sn_bridge_enable, .disable = ti_sn_bridge_disable, .post_disable = ti_sn_bridge_post_disable,
.detect = ti_sn_bridge_detect, .get_edid = ti_sn_bridge_get_edid,
};
@@ -1327,6 +1344,8 @@ static int ti_sn_bridge_probe(struct i2c_client *client, return ret; }
pdata->no_hpd = of_property_read_bool(pdata->dev->of_node, "no-hpd");
ti_sn_bridge_parse_lanes(pdata, client->dev.of_node); ret = ti_sn_bridge_parse_regulators(pdata);
@@ -1365,7 +1384,8 @@ static int ti_sn_bridge_probe(struct i2c_client *client,
pdata->bridge.funcs = &ti_sn_bridge_funcs; pdata->bridge.of_node = client->dev.of_node;
pdata->bridge.ops = DRM_BRIDGE_OP_EDID;
pdata->bridge.ops = (pdata->no_hpd ? 0 : DRM_BRIDGE_OP_DETECT)
Checking for "no_hpd" here is not the right test IIUC. You want to check for eDP vs. DP (AKA whether a panel is downstream of you or a connector). Specifically if downstream of you is a panel then (I believe) HPD won't assert until you turn on the panel and you won't turn on the panel (which happens in pre_enable, right?) until HPD fires, so you've got a chicken-and-egg problem. If downstream of you is a connector, though, then by definition HPD has to just work without pre_enable running so then you're OK.
I guess then you'd need to figure out what to do if someone wants to use "HPD" on eDP. Do you need to put a polling loop in pre_enable then? Or you could just punt not support this case until someone needs it.
| DRM_BRIDGE_OP_EDID;
IMO somewhere in here if HPD is being used like this you should throw in a call to pm_runtime_get_sync(). I guess in your solution the regulators (for the bridge, not the panel) and enable pin are just left on all the time, but plausibly someone might want to build a system to use HPD and also have the enable pin and/or regulators controlled by this driver, right?
-Doug
Hi Doug,
On Wed, Mar 24, 2021 at 03:47:38PM -0700, Doug Anderson wrote:
On Sun, Mar 21, 2021 at 8:02 PM Laurent Pinchart wrote:
When the SN65DSI86 is used in DisplayPort mode, its output is likely routed to a DisplayPort connector, which can benefit from hotplug detection. Support it in such cases, with polling mode only for now.
The implementation is limited to the bridge operations, as the connector operations are legacy and new users should use DRM_BRIDGE_ATTACH_NO_CONNECTOR.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
drivers/gpu/drm/bridge/ti-sn65dsi86.c | 46 +++++++++++++++++++-------- 1 file changed, 33 insertions(+), 13 deletions(-)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index f792227142a7..72f6362adf44 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -167,6 +167,8 @@ struct ti_sn_bridge { struct gpio_chip gchip; DECLARE_BITMAP(gchip_output, SN_NUM_GPIOS); #endif
bool no_hpd;
This structure is documented by kernel-doc, but you didn't add your new member.
Oops, sorry.
};
static const struct regmap_range ti_sn_bridge_volatile_ranges[] = { @@ -862,23 +864,28 @@ static void ti_sn_bridge_pre_enable(struct drm_bridge *bridge) ti_sn_bridge_set_refclk_freq(pdata);
/*
* HPD on this bridge chip is a bit useless. This is an eDP bridge
* so the HPD is an internal signal that's only there to signal that
* the panel is done powering up. ...but the bridge chip debounces
* this signal by between 100 ms and 400 ms (depending on process,
* voltage, and temperate--I measured it at about 200 ms). One
* As this is an eDP bridge, the output will be connected to a fixed
* panel in most systems. HPD is in that case only an internal signal
* to signal that the panel is done powering up. The bridge chip
* debounces this signal by between 100 ms and 400 ms (depending on
* process, voltage, and temperate--I measured it at about 200 ms). One * particular panel asserted HPD 84 ms after it was powered on meaning * that we saw HPD 284 ms after power on. ...but the same panel said * that instead of looking at HPD you could just hardcode a delay of
* 200 ms. We'll assume that the panel driver will have the hardcoded
* delay in its prepare and always disable HPD.
* 200 ms. HPD is thus a bit useless. For this type of use cases, we'll
* assume that the panel driver will have the hardcoded delay in its
* prepare and always disable HPD. *
* If HPD somehow makes sense on some future panel we'll have to
* change this to be conditional on someone specifying that HPD should
* be used.
* However, on some systems, the output is connected to a DisplayPort
* connector. HPD is needed in such cases. To accommodate both use
* cases, enable HPD only when requested. */
regmap_update_bits(pdata->regmap, SN_HPD_DISABLE_REG, HPD_DISABLE,
HPD_DISABLE);
if (pdata->no_hpd)
regmap_update_bits(pdata->regmap, SN_HPD_DISABLE_REG,
HPD_DISABLE, HPD_DISABLE);
else
regmap_update_bits(pdata->regmap, SN_HPD_DISABLE_REG,
HPD_DISABLE, 0);
Optionally you could skip the "else". HPD enabled is the default state and, in general, we don't exhaustively init all registers and rely on the power-on defaults for ones we don't explicitly control.
OK.
}
static void ti_sn_bridge_post_disable(struct drm_bridge *bridge) @@ -890,6 +897,15 @@ static void ti_sn_bridge_post_disable(struct drm_bridge *bridge) pm_runtime_put_sync(pdata->dev); }
+static enum drm_connector_status ti_sn_bridge_detect(struct drm_bridge *bridge) +{
struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
int val;
regmap_read(pdata->regmap, SN_HPD_DISABLE_REG, &val);
return val ? connector_status_connected : connector_status_disconnected;
I would have expected that you would have used the interrupt signal, but I guess it just polls in this case. I suppose polling has the advantage that it's simpler... Maybe throw in a comment about why IRQ isn't being used?
Correct, I didn't want to include IRQ support yet. I'll add a TODO comment.
+}
static struct edid *ti_sn_bridge_get_edid(struct drm_bridge *bridge, struct drm_connector *connector) { @@ -904,6 +920,7 @@ static const struct drm_bridge_funcs ti_sn_bridge_funcs = { .enable = ti_sn_bridge_enable, .disable = ti_sn_bridge_disable, .post_disable = ti_sn_bridge_post_disable,
.detect = ti_sn_bridge_detect, .get_edid = ti_sn_bridge_get_edid,
};
u> >
@@ -1327,6 +1344,8 @@ static int ti_sn_bridge_probe(struct i2c_client *client, return ret; }
pdata->no_hpd = of_property_read_bool(pdata->dev->of_node, "no-hpd");
ti_sn_bridge_parse_lanes(pdata, client->dev.of_node); ret = ti_sn_bridge_parse_regulators(pdata);
@@ -1365,7 +1384,8 @@ static int ti_sn_bridge_probe(struct i2c_client *client,
pdata->bridge.funcs = &ti_sn_bridge_funcs; pdata->bridge.of_node = client->dev.of_node;
pdata->bridge.ops = DRM_BRIDGE_OP_EDID;
pdata->bridge.ops = (pdata->no_hpd ? 0 : DRM_BRIDGE_OP_DETECT)
Checking for "no_hpd" here is not the right test IIUC. You want to check for eDP vs. DP (AKA whether a panel is downstream of you or a connector). Specifically if downstream of you is a panel then (I believe) HPD won't assert until you turn on the panel and you won't turn on the panel (which happens in pre_enable, right?) until HPD fires, so you've got a chicken-and-egg problem. If downstream of you is a connector, though, then by definition HPD has to just work without pre_enable running so then you're OK.
Agreed. It's even more true now that your rework has landed, as in the eDP case EDID is handled by the panel driver. I'll rework this.
Should I also condition setting HPD_DISABLE to the presence of a panel then ? I could drop of_property_read_bool() and set
pdata->no_hpd = !!panel;
I guess then you'd need to figure out what to do if someone wants to use "HPD" on eDP. Do you need to put a polling loop in pre_enable then? Or you could just punt not support this case until someone needs it.
I think I'll stop short of saving the world this time, yes :-) We'll see what to do when this case arises.
| DRM_BRIDGE_OP_EDID;
IMO somewhere in here if HPD is being used like this you should throw in a call to pm_runtime_get_sync(). I guess in your solution the regulators (for the bridge, not the panel) and enable pin are just left on all the time,
Correct, on my development board the SN65DSI86 is on all the time, I can't control that.
but plausibly someone might want to build a system to use HPD and also have the enable pin and/or regulators controlled by this driver, right?
True. DRM doesn't make this very easy, as, as far as I can tell, there's no standard infrastructure for userspace to register an interest in HPD that could be notified to bridges. I think it should be fixable, but it's out of scope for this series :-) Should I still add a pm_runtime_get_sync() at probe time, or leave this to be addressed by someone who will need to implement power control ?
Hi,
On Wed, Jun 23, 2021 at 4:26 PM Laurent Pinchart laurent.pinchart@ideasonboard.com wrote:
@@ -1365,7 +1384,8 @@ static int ti_sn_bridge_probe(struct i2c_client *client,
pdata->bridge.funcs = &ti_sn_bridge_funcs; pdata->bridge.of_node = client->dev.of_node;
pdata->bridge.ops = DRM_BRIDGE_OP_EDID;
pdata->bridge.ops = (pdata->no_hpd ? 0 : DRM_BRIDGE_OP_DETECT)
Checking for "no_hpd" here is not the right test IIUC. You want to check for eDP vs. DP (AKA whether a panel is downstream of you or a connector). Specifically if downstream of you is a panel then (I believe) HPD won't assert until you turn on the panel and you won't turn on the panel (which happens in pre_enable, right?) until HPD fires, so you've got a chicken-and-egg problem. If downstream of you is a connector, though, then by definition HPD has to just work without pre_enable running so then you're OK.
Agreed. It's even more true now that your rework has landed, as in the eDP case EDID is handled by the panel driver. I'll rework this.
Should I also condition setting HPD_DISABLE to the presence of a panel then ? I could drop of_property_read_bool() and set
pdata->no_hpd = !!panel;
I guess then you'd need to figure out what to do if someone wants to use "HPD" on eDP. Do you need to put a polling loop in pre_enable then? Or you could just punt not support this case until someone needs it.
I think I'll stop short of saving the world this time, yes :-) We'll see what to do when this case arises.
How about as a compromise you still call of_property_read_bool() but add some type of warning in the logs if someone didn't set "no-hpd" but they have a panel?
| DRM_BRIDGE_OP_EDID;
IMO somewhere in here if HPD is being used like this you should throw in a call to pm_runtime_get_sync(). I guess in your solution the regulators (for the bridge, not the panel) and enable pin are just left on all the time,
Correct, on my development board the SN65DSI86 is on all the time, I can't control that.
but plausibly someone might want to build a system to use HPD and also have the enable pin and/or regulators controlled by this driver, right?
True. DRM doesn't make this very easy, as, as far as I can tell, there's no standard infrastructure for userspace to register an interest in HPD that could be notified to bridges. I think it should be fixable, but it's out of scope for this series :-) Should I still add a pm_runtime_get_sync() at probe time, or leave this to be addressed by someone who will need to implement power control ?
IMO if you've detected you're running in DP mode you should add the pm_runtime_get_sync() in probe to keep it powered all the time and that seems the simplest. Technically I guess that's not really required since you're polling and you could power off between polls, but then you'd have to re-init a bunch of your state each time you polled too. If you ever transitioned to using an IRQ for HPD then you'd have to keep it always powered anyway.
-Doug
Hi All,
I'm working to respin the remainder of these patches, now that I have IRQ based HPD working on the SN65DSI86, and the (non-eDP) mode is used for Renesas R-Car boards.
Quoting Doug Anderson (2021-06-24 00:51:12)
Hi,
On Wed, Jun 23, 2021 at 4:26 PM Laurent Pinchart laurent.pinchart@ideasonboard.com wrote:
@@ -1365,7 +1384,8 @@ static int ti_sn_bridge_probe(struct i2c_client *client,
pdata->bridge.funcs = &ti_sn_bridge_funcs; pdata->bridge.of_node = client->dev.of_node;
pdata->bridge.ops = DRM_BRIDGE_OP_EDID;
pdata->bridge.ops = (pdata->no_hpd ? 0 : DRM_BRIDGE_OP_DETECT)
Checking for "no_hpd" here is not the right test IIUC. You want to check for eDP vs. DP (AKA whether a panel is downstream of you or a connector). Specifically if downstream of you is a panel then (I believe) HPD won't assert until you turn on the panel and you won't turn on the panel (which happens in pre_enable, right?) until HPD fires, so you've got a chicken-and-egg problem. If downstream of you is a connector, though, then by definition HPD has to just work without pre_enable running so then you're OK.
Agreed. It's even more true now that your rework has landed, as in the eDP case EDID is handled by the panel driver. I'll rework this.
Should I also condition setting HPD_DISABLE to the presence of a panel then ? I could drop of_property_read_bool() and set
pdata->no_hpd = !!panel;
I guess then you'd need to figure out what to do if someone wants to use "HPD" on eDP. Do you need to put a polling loop in pre_enable then? Or you could just punt not support this case until someone needs it.
I think I'll stop short of saving the world this time, yes :-) We'll see what to do when this case arises.
How about as a compromise you still call of_property_read_bool() but add some type of warning in the logs if someone didn't set "no-hpd" but they have a panel?
Would a mix of the two work well?
What about:
pdata->no_hpd = of_property_read_bool(np, "no-hpd"); if (panel && !pdata->no_hpd) { DRM_ERROR("Panels will not function with HPD. Enforcing no-hpd\n"); pdata->no_hpd = true; }
That leaves it still optional to DP connectors, but enforced on eDP?
| DRM_BRIDGE_OP_EDID;
IMO somewhere in here if HPD is being used like this you should throw in a call to pm_runtime_get_sync(). I guess in your solution the regulators (for the bridge, not the panel) and enable pin are just left on all the time,
Correct, on my development board the SN65DSI86 is on all the time, I can't control that.
but plausibly someone might want to build a system to use HPD and also have the enable pin and/or regulators controlled by this driver, right?
True. DRM doesn't make this very easy, as, as far as I can tell, there's no standard infrastructure for userspace to register an interest in HPD that could be notified to bridges. I think it should be fixable, but it's out of scope for this series :-) Should I still add a pm_runtime_get_sync() at probe time, or leave this to be addressed by someone who will need to implement power control ?
IMO if you've detected you're running in DP mode you should add the pm_runtime_get_sync() in probe to keep it powered all the time and that seems the simplest. Technically I guess that's not really required since you're polling and you could power off between polls, but then you'd have to re-init a bunch of your state each time you polled too. If you ever transitioned to using an IRQ for HPD then you'd have to keep it always powered anyway.
Hrm ... that's precisely what I've done. It's not IRQ based HPD...
So would you like to see something like this during ti_sn_bridge_probe()?
/* The device must remain powered up for HPD to be supported. */ if (!pdata->no_hpd) pm_runtime_get_sync(pdata->dev);
-- Regards
Kieran
-Doug
Hi,
On Wed, Feb 23, 2022 at 9:43 AM Kieran Bingham kieran.bingham@ideasonboard.com wrote:
Hi All,
I'm working to respin the remainder of these patches, now that I have IRQ based HPD working on the SN65DSI86, and the (non-eDP) mode is used for Renesas R-Car boards.
Quoting Doug Anderson (2021-06-24 00:51:12)
Hi,
On Wed, Jun 23, 2021 at 4:26 PM Laurent Pinchart laurent.pinchart@ideasonboard.com wrote:
@@ -1365,7 +1384,8 @@ static int ti_sn_bridge_probe(struct i2c_client *client,
pdata->bridge.funcs = &ti_sn_bridge_funcs; pdata->bridge.of_node = client->dev.of_node;
pdata->bridge.ops = DRM_BRIDGE_OP_EDID;
pdata->bridge.ops = (pdata->no_hpd ? 0 : DRM_BRIDGE_OP_DETECT)
Checking for "no_hpd" here is not the right test IIUC. You want to check for eDP vs. DP (AKA whether a panel is downstream of you or a connector). Specifically if downstream of you is a panel then (I believe) HPD won't assert until you turn on the panel and you won't turn on the panel (which happens in pre_enable, right?) until HPD fires, so you've got a chicken-and-egg problem. If downstream of you is a connector, though, then by definition HPD has to just work without pre_enable running so then you're OK.
Agreed. It's even more true now that your rework has landed, as in the eDP case EDID is handled by the panel driver. I'll rework this.
Should I also condition setting HPD_DISABLE to the presence of a panel then ? I could drop of_property_read_bool() and set
pdata->no_hpd = !!panel;
I guess then you'd need to figure out what to do if someone wants to use "HPD" on eDP. Do you need to put a polling loop in pre_enable then? Or you could just punt not support this case until someone needs it.
I think I'll stop short of saving the world this time, yes :-) We'll see what to do when this case arises.
How about as a compromise you still call of_property_read_bool() but add some type of warning in the logs if someone didn't set "no-hpd" but they have a panel?
Would a mix of the two work well?
What about:
pdata->no_hpd = of_property_read_bool(np, "no-hpd"); if (panel && !pdata->no_hpd) { DRM_ERROR("Panels will not function with HPD. Enforcing no-hpd\n"); pdata->no_hpd = true; }
That leaves it still optional to DP connectors, but enforced on eDP?
Yeah, that's fine with me. Nits would be to use "warn" instead of "error" since this isn't fatal and use the non-SHOUTING versions of the prints since the SHOUTING versions are deprecated.
| DRM_BRIDGE_OP_EDID;
IMO somewhere in here if HPD is being used like this you should throw in a call to pm_runtime_get_sync(). I guess in your solution the regulators (for the bridge, not the panel) and enable pin are just left on all the time,
Correct, on my development board the SN65DSI86 is on all the time, I can't control that.
but plausibly someone might want to build a system to use HPD and also have the enable pin and/or regulators controlled by this driver, right?
True. DRM doesn't make this very easy, as, as far as I can tell, there's no standard infrastructure for userspace to register an interest in HPD that could be notified to bridges. I think it should be fixable, but it's out of scope for this series :-) Should I still add a pm_runtime_get_sync() at probe time, or leave this to be addressed by someone who will need to implement power control ?
IMO if you've detected you're running in DP mode you should add the pm_runtime_get_sync() in probe to keep it powered all the time and that seems the simplest. Technically I guess that's not really required since you're polling and you could power off between polls, but then you'd have to re-init a bunch of your state each time you polled too. If you ever transitioned to using an IRQ for HPD then you'd have to keep it always powered anyway.
Hrm ... that's precisely what I've done. It's not IRQ based HPD...
So would you like to see something like this during ti_sn_bridge_probe()?
/* The device must remain powered up for HPD to be supported. */ if (!pdata->no_hpd) pm_runtime_get_sync(pdata->dev);
Yeah, seems reasonable. Probably you'd want to add a devm action to put it too?
-Doug
Hi Doug,
Quoting Doug Anderson (2022-02-23 18:25:13)
Hi,
On Wed, Feb 23, 2022 at 9:43 AM Kieran Bingham kieran.bingham@ideasonboard.com wrote:
Hi All,
I'm working to respin the remainder of these patches, now that I have IRQ based HPD working on the SN65DSI86, and the (non-eDP) mode is used for Renesas R-Car boards.
Quoting Doug Anderson (2021-06-24 00:51:12)
Hi,
On Wed, Jun 23, 2021 at 4:26 PM Laurent Pinchart laurent.pinchart@ideasonboard.com wrote:
@@ -1365,7 +1384,8 @@ static int ti_sn_bridge_probe(struct i2c_client *client,
pdata->bridge.funcs = &ti_sn_bridge_funcs; pdata->bridge.of_node = client->dev.of_node;
pdata->bridge.ops = DRM_BRIDGE_OP_EDID;
pdata->bridge.ops = (pdata->no_hpd ? 0 : DRM_BRIDGE_OP_DETECT)
Checking for "no_hpd" here is not the right test IIUC. You want to check for eDP vs. DP (AKA whether a panel is downstream of you or a connector). Specifically if downstream of you is a panel then (I believe) HPD won't assert until you turn on the panel and you won't turn on the panel (which happens in pre_enable, right?) until HPD fires, so you've got a chicken-and-egg problem. If downstream of you is a connector, though, then by definition HPD has to just work without pre_enable running so then you're OK.
Agreed. It's even more true now that your rework has landed, as in the eDP case EDID is handled by the panel driver. I'll rework this.
Should I also condition setting HPD_DISABLE to the presence of a panel then ? I could drop of_property_read_bool() and set
pdata->no_hpd = !!panel;
I guess then you'd need to figure out what to do if someone wants to use "HPD" on eDP. Do you need to put a polling loop in pre_enable then? Or you could just punt not support this case until someone needs it.
I think I'll stop short of saving the world this time, yes :-) We'll see what to do when this case arises.
How about as a compromise you still call of_property_read_bool() but add some type of warning in the logs if someone didn't set "no-hpd" but they have a panel?
Would a mix of the two work well?
What about:
pdata->no_hpd = of_property_read_bool(np, "no-hpd"); if (panel && !pdata->no_hpd) { DRM_ERROR("Panels will not function with HPD. Enforcing no-hpd\n"); pdata->no_hpd = true; }
That leaves it still optional to DP connectors, but enforced on eDP?
Yeah, that's fine with me. Nits would be to use "warn" instead of "error" since this isn't fatal and use the non-SHOUTING versions of the prints since the SHOUTING versions are deprecated.
Could you clarify this please? The whole driver uses DRM_ERROR style. Is there a new debug macro somewhere?
| DRM_BRIDGE_OP_EDID;
IMO somewhere in here if HPD is being used like this you should throw in a call to pm_runtime_get_sync(). I guess in your solution the regulators (for the bridge, not the panel) and enable pin are just left on all the time,
Correct, on my development board the SN65DSI86 is on all the time, I can't control that.
but plausibly someone might want to build a system to use HPD and also have the enable pin and/or regulators controlled by this driver, right?
True. DRM doesn't make this very easy, as, as far as I can tell, there's no standard infrastructure for userspace to register an interest in HPD that could be notified to bridges. I think it should be fixable, but it's out of scope for this series :-) Should I still add a pm_runtime_get_sync() at probe time, or leave this to be addressed by someone who will need to implement power control ?
IMO if you've detected you're running in DP mode you should add the pm_runtime_get_sync() in probe to keep it powered all the time and that seems the simplest. Technically I guess that's not really required since you're polling and you could power off between polls, but then you'd have to re-init a bunch of your state each time you polled too. If you ever transitioned to using an IRQ for HPD then you'd have to keep it always powered anyway.
Hrm ... that's precisely what I've done. It's not IRQ based HPD...
So would you like to see something like this during ti_sn_bridge_probe()?
/* The device must remain powered up for HPD to be supported. */ if (!pdata->no_hpd) pm_runtime_get_sync(pdata->dev);
Yeah, seems reasonable. Probably you'd want to add a devm action to put it too?
Ok, looking at this now - then I should be able to get these updated patches out.
-- Thanks.
Kieran
-Doug
Hi,
On Fri, Mar 4, 2022 at 7:46 AM Kieran Bingham kieran.bingham@ideasonboard.com wrote:
What about:
pdata->no_hpd = of_property_read_bool(np, "no-hpd"); if (panel && !pdata->no_hpd) { DRM_ERROR("Panels will not function with HPD. Enforcing no-hpd\n"); pdata->no_hpd = true; }
That leaves it still optional to DP connectors, but enforced on eDP?
Yeah, that's fine with me. Nits would be to use "warn" instead of "error" since this isn't fatal and use the non-SHOUTING versions of the prints since the SHOUTING versions are deprecated.
Could you clarify this please? The whole driver uses DRM_ERROR style. Is there a new debug macro somewhere?
Mostly looking at commit 306589856399 ("drm/print: Add deprecation notes to DRM_...() functions"), which I added a few months ago. Despite the fact that I added the documentation, though, I'm not the one driving the transition away from the SHOUTing versions. If you look through history you can see this is driven by more senior DRM people.
IMO it's fine to transition slowly to the new non-shouting versions and it doesn't bother me to have some code in a file using the old SHOUTing versions and some using the newer functions. Basically for new code or when we're touching code anyway we do the transition then. That being said, if you want to
-Doug
dri-devel@lists.freedesktop.org