From: Michael Trimarchi <mich...@amarulasolutions.com>

The video link framework bases a port-endpoint gragh in DTB to
connect the video components in uclass like: video, display, bridge,
and panel.

Using the port-endpoint gragh, we manage multiple video link and
user can select one of them for splash screen.

Signed-off-by: Ye Li <ye...@nxp.com>
Signed-off-by: Michael Trimarchi <mich...@amarulasolutions.com>
Signed-off-by: Dario Binacchi <dario.binac...@amarulasolutions.com>
---

 common/stdio.c             |   4 +
 drivers/video/Kconfig      |   6 +
 drivers/video/Makefile     |   1 +
 drivers/video/video_link.c | 529 +++++++++++++++++++++++++++++++++++++
 include/video_link.h       |  19 ++
 5 files changed, 559 insertions(+)
 create mode 100644 drivers/video/video_link.c
 create mode 100644 include/video_link.h

diff --git a/common/stdio.c b/common/stdio.c
index a61220ce4b9c..66360c97ed12 100644
--- a/common/stdio.c
+++ b/common/stdio.c
@@ -17,6 +17,7 @@
 #include <stdio_dev.h>
 #include <serial.h>
 #include <splash.h>
+#include <video_link.h>
 #include <i2c.h>
 #include <asm/global_data.h>
 #include <dm/device-internal.h>
@@ -340,6 +341,9 @@ int stdio_add_devices(void)
                struct udevice *vdev;
                int ret;
 
+               if (IS_ENABLED(CONFIG_VIDEO_LINK))
+                       video_link_init();
+
                if (!IS_ENABLED(CONFIG_SYS_CONSOLE_IS_IN_ENV)) {
                        for (ret = uclass_first_device_check(UCLASS_VIDEO,
                                                             &vdev);
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 6e79694fd192..cafdc15f9fe2 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -833,6 +833,12 @@ config VIDEO_SEPS525
 source "drivers/video/zynqmp/Kconfig"
 source "drivers/video/nexell/Kconfig"
 
+config VIDEO_LINK
+       bool "Enable video link framework support"
+       help
+          This option enables a video link framework basing on port-endpoint 
graph
+          to connect video components.
+
 config CONSOLE_SCROLL_LINES
        int "Number of lines to scroll the console by"
        default 1
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index f3f70cd04a17..9fd3645994f1 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_$(SPL_TPL_)SIMPLE_PANEL) += simple_panel.o
 obj-$(CONFIG_VIDEO_LOGO) += u_boot_logo.o
 obj-$(CONFIG_$(SPL_TPL_)BMP) += bmp.o
 
+obj-$(CONFIG_VIDEO_LINK) += video_link.o
 endif
 
 obj-$(CONFIG_BACKLIGHT_LM3533) += lm3533_backlight.o
diff --git a/drivers/video/video_link.c b/drivers/video/video_link.c
new file mode 100644
index 000000000000..001a759faab9
--- /dev/null
+++ b/drivers/video/video_link.c
@@ -0,0 +1,529 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2020 NXP
+ *
+ */
+
+#include <command.h>
+#include <linux/errno.h>
+
+#include <dm.h>
+#include <dm/uclass-internal.h>
+#include <dm/device-internal.h>
+#include <dm/ofnode.h>
+#include <dm/read.h>
+#include <video.h>
+#include <panel.h>
+#include <env.h>
+
+struct of_endpoint {
+       unsigned int port;
+       unsigned int id;
+       ofnode local_node;
+};
+
+#define MAX_LINKS 3
+#define MAX_LINK_DEVICES 5
+
+struct video_link {
+       struct udevice *link_devs[MAX_LINK_DEVICES];
+       int dev_num;
+};
+
+struct video_link video_links[MAX_LINKS];
+struct video_link temp_stack;
+ulong video_links_num;
+ulong curr_video_link;
+bool video_off;
+
+ofnode ofnode_get_child_by_name(ofnode parent, const char *name)
+{
+       ofnode child;
+       const char *child_name;
+
+       for (child = ofnode_first_subnode(parent);
+            ofnode_valid(child);
+            child = ofnode_next_subnode(child)) {
+               child_name = ofnode_get_name(child);
+
+               if (!strncmp(child_name, name, strlen(name)))
+                       break;
+       }
+       return child;
+}
+
+ofnode ofnode_graph_get_next_endpoint(ofnode parent,
+                                     ofnode prev)
+{
+       ofnode endpoint;
+       ofnode port;
+       const char *name;
+
+       if (!ofnode_valid(prev)) {
+               ofnode node;
+
+               node = ofnode_find_subnode(parent, "ports");
+               if (ofnode_valid(node))
+                       parent = node;
+
+               port = ofnode_get_child_by_name(parent, "port");
+               if (!ofnode_valid(port)) {
+                       debug("no port node found in 0x%lx\n", 
parent.of_offset);
+                       return ofnode_null();
+               }
+
+               endpoint = ofnode_first_subnode(port);
+               if (ofnode_valid(endpoint)) {
+                       debug("get next endpoint %s\n", 
ofnode_get_name(endpoint));
+                       return endpoint;
+               }
+       } else {
+               port = ofnode_get_parent(prev);
+               endpoint = ofnode_next_subnode(prev);
+               if (ofnode_valid(endpoint)) {
+                       debug("get next endpoint %s\n", 
ofnode_get_name(endpoint));
+                       return endpoint;
+               }
+       }
+
+       debug("port %s\n", ofnode_get_name(port));
+
+       while (1) {
+               do {
+                       port = ofnode_next_subnode(port);
+                       if (!ofnode_valid(port))
+                               return ofnode_null();
+
+                       name = ofnode_get_name(port);
+               } while (strncmp(name, "port", 4));
+
+               /*
+                * Now that we have a port node, get the next endpoint by
+                * getting the next child. If the previous endpoint is NULL this
+                * will return the first child.
+                */
+               endpoint = ofnode_first_subnode(port);
+               if (ofnode_valid(endpoint)) {
+                       debug("get next endpoint %s\n", 
ofnode_get_name(endpoint));
+                       return endpoint;
+               }
+       }
+
+       return ofnode_null();
+}
+
+#define for_each_endpoint_of_node(parent, child) \
+       for (child = ofnode_graph_get_next_endpoint(parent, ofnode_null()); 
ofnode_valid(child); \
+            child = ofnode_graph_get_next_endpoint(parent, child))
+
+int ofnode_graph_get_endpoint_count(ofnode node)
+{
+       ofnode endpoint;
+       int num = 0;
+
+       for_each_endpoint_of_node(node, endpoint)
+               num++;
+
+       return num;
+}
+
+int ofnode_graph_parse_endpoint(ofnode node,
+                               struct of_endpoint *endpoint)
+{
+       ofnode port_node = ofnode_get_parent(node);
+
+       memset(endpoint, 0, sizeof(*endpoint));
+
+       endpoint->local_node = node;
+       /*
+        * It doesn't matter whether the two calls below succeed.
+        * If they don't then the default value 0 is used.
+        */
+       ofnode_read_u32(port_node, "reg", &endpoint->port);
+       ofnode_read_u32(node, "reg", &endpoint->id);
+
+       return 0;
+}
+
+ofnode ofnode_graph_get_endpoint_by_regs(const ofnode parent,
+                                        int port_reg, int reg)
+{
+       struct of_endpoint endpoint;
+       ofnode node;
+
+       for_each_endpoint_of_node(parent, node) {
+               ofnode_graph_parse_endpoint(node, &endpoint);
+               if (((port_reg == -1) || endpoint.port == port_reg) &&
+                   ((reg == -1) || endpoint.id == reg)) {
+                       debug("get node %s\n", ofnode_get_name(node));
+
+                       return node;
+               }
+       }
+
+       return ofnode_null();
+}
+
+ofnode ofnode_graph_get_remote_endpoint(ofnode node)
+{
+       ofnode remote;
+       u32 phandle;
+       int ret;
+
+       ret = ofnode_read_u32(node, "remote-endpoint", &phandle);
+       if (ret) {
+               printf("required remote-endpoint property isn't provided\n");
+               return ofnode_null();
+       }
+
+       remote = ofnode_get_by_phandle(phandle);
+       if (!ofnode_valid(remote)) {
+               printf("failed to find remote-endpoint\n");
+               return ofnode_null();
+       }
+
+       return remote;
+}
+
+ofnode ofnode_graph_get_port_parent(ofnode node)
+{
+       unsigned int depth;
+
+       if (!ofnode_valid(node))
+               return ofnode_null();
+
+       /*
+        * Preserve usecount for passed in node as of_get_next_parent()
+        * will do of_node_put() on it.
+        */
+
+       /* Walk 3 levels up only if there is 'ports' node. */
+       for (depth = 3; depth && ofnode_valid(node); depth--) {
+               node = ofnode_get_parent(node);
+               const char *name = ofnode_get_name(node);
+
+               if (depth == 2 && strcmp(name, "ports"))
+                       break;
+       }
+       return node;
+}
+
+ofnode ofnode_graph_get_remote_port_parent(ofnode node)
+{
+       ofnode np, pp;
+
+       /* Get remote endpoint node. */
+       np = ofnode_graph_get_remote_endpoint(node);
+
+       pp = ofnode_graph_get_port_parent(np);
+
+       return pp;
+}
+
+int find_device_by_ofnode(ofnode node, struct udevice **pdev)
+{
+       int ret;
+
+       if (!ofnode_is_enabled(node))
+               return -2;
+
+       ret = uclass_find_device_by_ofnode(UCLASS_DISPLAY, node, pdev);
+       if (!ret)
+               return 0;
+
+       ret = uclass_find_device_by_ofnode(UCLASS_DSI_HOST, node, pdev);
+       if (!ret)
+               return 0;
+
+       ret = uclass_find_device_by_ofnode(UCLASS_VIDEO_BRIDGE, node, pdev);
+       if (!ret)
+               return 0;
+
+       ret = uclass_find_device_by_ofnode(UCLASS_PANEL, node, pdev);
+       if (!ret)
+               return 0;
+
+       return -1;
+}
+
+static void video_link_stack_push(struct udevice *dev)
+{
+       if (temp_stack.dev_num < MAX_LINK_DEVICES) {
+               temp_stack.link_devs[temp_stack.dev_num] = dev;
+               temp_stack.dev_num++;
+       }
+}
+
+static void video_link_stack_pop(void)
+{
+       if (temp_stack.dev_num > 0) {
+               temp_stack.link_devs[temp_stack.dev_num] = NULL;
+               temp_stack.dev_num--;
+       }
+}
+
+static int duplicate_video_link(void)
+{
+       if (video_links_num < MAX_LINKS) {
+               video_links[video_links_num] = temp_stack;
+               video_links_num++;
+
+               debug("duplicate links num %lu,  temp_stack num %d\n",
+                     video_links_num, temp_stack.dev_num);
+               return 0;
+       }
+
+       return -ENODEV;
+}
+
+static void video_link_add_node(struct udevice *peer_dev, struct udevice *dev, 
ofnode dev_node)
+{
+       int ret = 0;
+       ofnode remote, endpoint_node;
+       struct udevice *remote_dev;
+       bool find = false;
+
+       debug("endpoint cnt %d\n", ofnode_graph_get_endpoint_count(dev_node));
+
+       video_link_stack_push(dev);
+
+       for_each_endpoint_of_node(dev_node, endpoint_node) {
+               remote = ofnode_graph_get_remote_port_parent(endpoint_node);
+               if (!ofnode_valid(remote))
+                       continue;
+
+               debug("remote %s\n", ofnode_get_name(remote));
+               ret = find_device_by_ofnode(remote, &remote_dev);
+               if (!ret) {
+                       debug("remote dev %s\n", remote_dev->name);
+
+                       if (peer_dev && peer_dev == remote_dev)
+                               continue;
+
+                       /* it is possible that ofnode of remote_dev is not 
equal to remote */
+                       video_link_add_node(dev, remote_dev, remote);
+
+                       find = true;
+               }
+       }
+
+       /* leaf node or no valid new endpoint, now copy the entire stack to a 
new video link */
+       if (!find) {
+               ret = duplicate_video_link();
+               if (ret)
+                       printf("video link is full\n");
+       }
+
+       video_link_stack_pop();
+}
+
+struct udevice *video_link_get_next_device(struct udevice *curr_dev)
+{
+       int i, ret;
+
+       if (video_off)
+               return NULL;
+
+       if (curr_video_link >= video_links_num) {
+               printf("current video link is not correct\n");
+               return NULL;
+       }
+
+       for (i = 0; i < video_links[curr_video_link].dev_num; i++) {
+               if (video_links[curr_video_link].link_devs[i] == curr_dev) {
+                       if ((i + 1) < video_links[curr_video_link].dev_num) {
+                               ret = 
device_probe(video_links[curr_video_link].link_devs[i + 1]);
+                               if (ret) {
+                                       printf("probe device is failed, ret 
%d\n", ret);
+                                       return NULL;
+                               }
+
+                               return video_links[curr_video_link].link_devs[i 
+ 1];
+                       }
+
+                       debug("fail to find next device, already last one\n");
+                       return NULL;
+               }
+       }
+
+       return NULL;
+}
+
+struct udevice *video_link_get_video_device(void)
+{
+       int ret;
+
+       if (video_off)
+               return NULL;
+
+       if (curr_video_link >= video_links_num)
+               return NULL;
+
+       if (video_links[curr_video_link].dev_num == 0)
+               return NULL;
+
+       ret = device_probe(video_links[curr_video_link].link_devs[0]);
+       if (ret) {
+               printf("probe video device failed, ret %d\n", ret);
+               return NULL;
+       }
+
+       return video_links[curr_video_link].link_devs[0];
+}
+
+int video_link_get_display_timings(struct display_timing *timings)
+{
+       int i = 0;
+       int ret;
+       struct udevice *dev;
+
+       if (video_off)
+               return -EPERM;
+
+       if (curr_video_link >= video_links_num)
+               return -ENODEV;
+
+       if (video_links[curr_video_link].dev_num == 0)
+               return -ENODEV;
+
+       for (i = video_links[curr_video_link].dev_num - 1; i >= 0 ; i--) {
+               dev = video_links[curr_video_link].link_devs[i];
+               if (device_get_uclass_id(dev) == UCLASS_PANEL) {
+                       ret = 
device_probe(video_links[curr_video_link].link_devs[i]);
+                       if (ret) {
+                               printf("fail to probe panel device %s\n", 
dev->name);
+                               return ret;
+                       }
+
+                       ret = panel_get_display_timing(dev, timings);
+                       if (ret) {
+                               ret = 
ofnode_decode_display_timing(dev_ofnode(dev), 0, timings);
+                               if (ret) {
+                                       printf("fail to get panel timing %s\n", 
dev->name);
+                                       return ret;
+                               }
+                       }
+
+                       return 0;
+               } else if (device_get_uclass_id(dev) == UCLASS_DISPLAY ||
+                          device_get_uclass_id(dev) == UCLASS_VIDEO) {
+                       ret = ofnode_decode_display_timing(dev_ofnode(dev), 0, 
timings);
+                       if (!ret)
+                               return 0;
+               }
+       }
+
+       return -EINVAL;
+}
+
+static void list_videolink(bool current_only)
+{
+       ulong index = 0;
+       int j;
+       bool match;
+
+       /* dump the link */
+       debug("video link number: %lu\n", video_links_num);
+
+       for (index = 0; index < video_links_num; index++) {
+               match = false;
+               if (curr_video_link == index)
+                       match = true;
+               else if (current_only)
+                       continue;
+
+               printf("[%c]-Video Link %lu", (match) ? '*' : ' ', index);
+
+               if (match) {
+                       struct udevice *video_dev = 
video_link_get_video_device();
+
+                       if (video_dev) {
+                               printf(" (%u x %u)", video_get_xsize(video_dev),
+                                      video_get_ysize(video_dev));
+                       }
+               }
+
+               printf("\n");
+
+               for (j = 0; j < video_links[index].dev_num; j++) {
+                       printf("\t[%d] %s, %s\n", j, 
video_links[index].link_devs[j]->name,
+                              
dev_get_uclass_name(video_links[index].link_devs[j]));
+               }
+       }
+}
+
+static int do_videolink(struct cmd_tbl *cmdtp, int flag, int argc, char * 
const argv[])
+{
+       char cmd = 'l';
+       int ret = 0;
+
+       if (argc > 1)
+               cmd = argv[1][0];
+
+       switch (cmd) {
+       case 'l':               /* list */
+               list_videolink(false);
+               break;
+       default:
+               ret = CMD_RET_USAGE;
+               break;
+       }
+
+       return ret;
+}
+
+int video_link_init(void)
+{
+       struct udevice *dev;
+       ulong env_id;
+       int off;
+
+       memset(&video_links, 0, sizeof(video_links));
+       memset(&temp_stack, 0, sizeof(temp_stack));
+
+       for (uclass_find_first_device(UCLASS_VIDEO, &dev);
+            dev;
+            uclass_find_next_device(&dev)) {
+               video_link_add_node(NULL, dev, dev_ofnode(dev));
+       }
+
+       if (video_links_num == 0) {
+               printf("Fail to setup video link\n");
+               return -ENODEV;
+       }
+
+       /* Read the env variable for default video link */
+       off = env_get_yesno("video_off");
+       if (off == 1) {
+               video_off = true;
+               return 0;
+       }
+
+       env_id = env_get_ulong("video_link", 10, 0);
+       if (env_id < video_links_num)
+               curr_video_link = env_id;
+
+       list_videolink(true);
+
+       return 0;
+}
+
+int video_link_shut_down(void)
+{
+       struct udevice *video_dev = video_link_get_video_device();
+
+       if (video_dev)
+               device_remove(video_dev, DM_REMOVE_NORMAL);
+
+       return 0;
+}
+
+#if CONFIG_IS_ENABLED(SYS_LONGHELP)
+static char video_link_help_text[] =
+       "list\n"
+       "    -  show video link info, set video_link variable to select link";
+#endif
+
+U_BOOT_CMD(videolink,  5,      1,      do_videolink,
+          "list and select video link", video_link_help_text
+);
diff --git a/include/video_link.h b/include/video_link.h
new file mode 100644
index 000000000000..5350bfa9e9d1
--- /dev/null
+++ b/include/video_link.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2020 NXP
+ */
+
+#ifndef __VIDEO_LINK
+#define __VIDEO_LINK
+
+int video_link_init(void);
+
+int video_link_shut_down(void);
+
+struct udevice *video_link_get_next_device(struct udevice *curr_dev);
+
+struct udevice *video_link_get_video_device(void);
+
+int video_link_get_display_timings(struct display_timing *timings);
+
+#endif
-- 
2.43.0

Reply via email to