summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRaphael Gallais-Pou <[email protected]>2025-09-04 14:53:09 +0200
committerTom Rini <[email protected]>2025-11-17 10:43:52 -0600
commit35be94614733e500bc00b8297ffdecf50aea6d03 (patch)
tree56e4771f186c87781ae1efde5c42e12167eeb52d
parentb3316f6053feb3be1a731536c250942606447ea9 (diff)
video: stm32: ltdc: properly search the first available panel
Initially there was only one DSI bridge with one panel attached to this device. This explained the call to uclass_first_device_err(UCLASS_PANEL, ...) which worked fine at the time. Now that multiple bridges and panels, with different technologies, can be plugged onto the board this way to get the panel device is outdated. The lookup is done is two steps. First we circle through the UCLASS_VIDEO_BRIDGE, and once we get one, we search through its endpoints until we get a UCLASS_PANEL device available. Acked-by: Yannick Fertre <[email protected]> Signed-off-by: Raphael Gallais-Pou <[email protected]> Reviewed-by: Patrice Chotard <[email protected]>
-rw-r--r--drivers/video/stm32/stm32_ltdc.c136
1 files changed, 125 insertions, 11 deletions
diff --git a/drivers/video/stm32/stm32_ltdc.c b/drivers/video/stm32/stm32_ltdc.c
index efe9a00996e..834bfb625d2 100644
--- a/drivers/video/stm32/stm32_ltdc.c
+++ b/drivers/video/stm32/stm32_ltdc.c
@@ -17,6 +17,7 @@
#include <video_bridge.h>
#include <asm/io.h>
#include <dm/device-internal.h>
+#include <dm/uclass-internal.h>
#include <dm/device_compat.h>
#include <linux/bitops.h>
#include <linux/printk.h>
@@ -495,6 +496,101 @@ static void stm32_ltdc_set_layer1(struct stm32_ltdc_priv *priv, ulong fb_addr)
setbits_le32(priv->regs + LTDC_L1CR, LXCR_LEN);
}
+static int stm32_ltdc_get_remote_device(struct udevice *dev, ofnode ep_node,
+ enum uclass_id id, struct udevice **remote_dev)
+{
+ u32 remote_phandle;
+ ofnode remote;
+ int ret = 0;
+
+ ret = ofnode_read_u32(ep_node, "remote-endpoint", &remote_phandle);
+ if (ret) {
+ dev_err(dev, "%s(%s): Could not find remote-endpoint property\n",
+ __func__, dev_read_name(dev));
+ return ret;
+ }
+
+ remote = ofnode_get_by_phandle(remote_phandle);
+ if (!ofnode_valid(remote))
+ return -EINVAL;
+
+ while (ofnode_valid(remote)) {
+ remote = ofnode_get_parent(remote);
+ if (!ofnode_valid(remote)) {
+ dev_dbg(dev, "%s(%s): no uclass_id %d for remote-endpoint\n",
+ __func__, dev_read_name(dev), id);
+ continue;
+ }
+
+ ret = uclass_find_device_by_ofnode(id, remote, remote_dev);
+ if (*remote_dev && !ret) {
+ ret = uclass_get_device_by_ofnode(id, remote, remote_dev);
+ if (ret)
+ dev_dbg(dev, "%s(%s): failed to get remote device %s\n",
+ __func__, dev_read_name(dev), dev_read_name(*remote_dev));
+ break;
+ }
+ };
+
+ return ret;
+}
+
+static int stm32_ltdc_get_panel(struct udevice *dev, struct udevice **panel)
+{
+ ofnode ep_node, node, ports;
+ int ret = 0;
+
+ if (!dev)
+ return -EINVAL;
+
+ ports = ofnode_find_subnode(dev_ofnode(dev), "ports");
+ if (!ofnode_valid(ports)) {
+ dev_err(dev, "Remote bridge subnode\n");
+ return ret;
+ }
+
+ for (node = ofnode_first_subnode(ports);
+ ofnode_valid(node);
+ node = dev_read_next_subnode(node)) {
+ ep_node = ofnode_first_subnode(node);
+ if (!ofnode_valid(ep_node))
+ continue;
+
+ ret = stm32_ltdc_get_remote_device(dev, ep_node, UCLASS_PANEL, panel);
+ }
+
+ /* Sanity check, we can get out of the loop without having a clean ofnode */
+ if (!(*panel))
+ ret = -EINVAL;
+ else
+ if (!ofnode_valid(dev_ofnode(*panel)))
+ ret = -EINVAL;
+
+ return ret;
+}
+
+static int stm32_ltdc_display_init(struct udevice *dev, ofnode *ep_node,
+ struct udevice **panel, struct udevice **bridge)
+{
+ int ret;
+
+ if (*panel)
+ return -EINVAL;
+
+ if (IS_ENABLED(CONFIG_VIDEO_BRIDGE)) {
+ ret = stm32_ltdc_get_remote_device(dev, *ep_node, UCLASS_VIDEO_BRIDGE, bridge);
+ if (ret)
+ return ret;
+
+ ret = stm32_ltdc_get_panel(*bridge, panel);
+ } else {
+ /* no bridge, search a panel from display controller node */
+ ret = stm32_ltdc_get_remote_device(dev, *ep_node, UCLASS_PANEL, panel);
+ }
+
+ return ret;
+}
+
#if IS_ENABLED(CONFIG_TARGET_STM32F469_DISCOVERY)
static int stm32_ltdc_alloc_fb(struct udevice *dev)
{
@@ -532,6 +628,7 @@ static int stm32_ltdc_probe(struct udevice *dev)
struct display_timing timings;
struct clk pclk, bclk;
struct reset_ctl rst;
+ ofnode node, port;
ulong rate;
int ret;
@@ -568,7 +665,7 @@ static int stm32_ltdc_probe(struct udevice *dev)
}
priv->hw_version = readl(priv->regs + LTDC_IDR);
- debug("%s: LTDC hardware 0x%x\n", __func__, priv->hw_version);
+ dev_dbg(dev, "%s: LTDC hardware 0x%x\n", __func__, priv->hw_version);
switch (priv->hw_version) {
case HWVER_10200:
@@ -589,13 +686,35 @@ static int stm32_ltdc_probe(struct udevice *dev)
return -ENODEV;
}
- ret = uclass_first_device_err(UCLASS_PANEL, &panel);
- if (ret) {
- if (ret != -ENODEV)
- dev_err(dev, "panel device error %d\n", ret);
- return ret;
+ /*
+ * Try all the ports until one working.
+ *
+ * This is done in two times. First is checks for the
+ * UCLASS_VIDEO_BRIDGE available, and then for this bridge
+ * it scans for a UCLASS_PANEL.
+ */
+
+ port = dev_read_subnode(dev, "port");
+ if (!ofnode_valid(port)) {
+ dev_err(dev, "%s(%s): 'port' subnode not found\n",
+ __func__, dev_read_name(dev));
+ return -EINVAL;
}
+ for (node = ofnode_first_subnode(port);
+ ofnode_valid(node);
+ node = dev_read_next_subnode(node)) {
+ ret = stm32_ltdc_display_init(dev, &node, &panel, &bridge);
+ if (ret)
+ dev_dbg(dev, "Device failed ret=%d\n", ret);
+ else
+ break;
+ }
+
+ /* Sanity check */
+ if (ret)
+ return ret;
+
ret = panel_get_display_timing(panel, &timings);
if (ret) {
ret = ofnode_decode_display_timing(dev_ofnode(panel),
@@ -624,11 +743,6 @@ static int stm32_ltdc_probe(struct udevice *dev)
reset_deassert(&rst);
if (IS_ENABLED(CONFIG_VIDEO_BRIDGE)) {
- ret = uclass_get_device(UCLASS_VIDEO_BRIDGE, 0, &bridge);
- if (ret)
- dev_dbg(dev,
- "No video bridge, or no backlight on bridge\n");
-
if (bridge) {
ret = video_bridge_attach(bridge);
if (ret) {