Extend the notifier with DT node matching support, and add helper functions to build the notifier and link entities based on a graph representation in DT. Signed-off-by: Laurent Pinchart <laurent.pinchart+renesas@xxxxxxxxxxxxxxxx> --- drivers/video/display/display-core.c | 334 +++++++++++++++++++++++++++++++ drivers/video/display/display-notifier.c | 187 +++++++++++++++++ include/video/display.h | 45 +++++ 3 files changed, 566 insertions(+) diff --git a/drivers/video/display/display-core.c b/drivers/video/display/display-core.c index c3b47d3..328ead7 100644 --- a/drivers/video/display/display-core.c +++ b/drivers/video/display/display-core.c @@ -14,6 +14,7 @@ #include <linux/export.h> #include <linux/kernel.h> #include <linux/module.h> +#include <linux/of.h> #include <linux/slab.h> #include <media/media-device.h> @@ -315,6 +316,184 @@ void display_entity_unregister(struct display_entity *entity) EXPORT_SYMBOL_GPL(display_entity_unregister); /* ----------------------------------------------------------------------------- + * OF Helpers + */ + +#ifdef CONFIG_OF + +/** + * display_of_get_next_endpoint() - get next endpoint node + * @parent: pointer to the parent device node + * @prev: previous endpoint node, or NULL to get first + * + * Return: An 'endpoint' node pointer with refcount incremented. Refcount + * of the passed @prev node is not decremented, the caller have to use + * of_node_put() on it when done. + */ +struct device_node * +display_of_get_next_endpoint(const struct device_node *parent, + struct device_node *prev) +{ + struct device_node *endpoint; + struct device_node *port = NULL; + + if (!parent) + return NULL; + + if (!prev) { + struct device_node *node; + /* + * It's the first call, we have to find a port subnode + * within this node or within an optional 'ports' node. + */ + node = of_get_child_by_name(parent, "ports"); + if (node) + parent = node; + + port = of_get_child_by_name(parent, "port"); + + if (port) { + /* Found a port, get an endpoint. */ + endpoint = of_get_next_child(port, NULL); + of_node_put(port); + } else { + endpoint = NULL; + } + + if (!endpoint) + pr_err("%s(): no endpoint nodes specified for %s\n", + __func__, parent->full_name); + of_node_put(node); + } else { + port = of_get_parent(prev); + if (!port) + /* Hm, has someone given us the root node ?... */ + return NULL; + + /* Avoid dropping prev node refcount to 0. */ + of_node_get(prev); + endpoint = of_get_next_child(port, prev); + if (endpoint) { + of_node_put(port); + return endpoint; + } + + /* No more endpoints under this port, try the next one. */ + do { + port = of_get_next_child(parent, port); + if (!port) + return NULL; + } while (of_node_cmp(port->name, "port")); + + /* Pick up the first endpoint in this port. */ + endpoint = of_get_next_child(port, NULL); + of_node_put(port); + } + + return endpoint; +} + +/** + * display_of_get_remote_port_parent() - get remote port's parent node + * @node: pointer to a local endpoint device_node + * + * Return: Remote device node associated with remote endpoint node linked + * to @node. Use of_node_put() on it when done. + */ +struct device_node * +display_of_get_remote_port_parent(const struct device_node *node) +{ + struct device_node *np; + unsigned int depth; + + /* Get remote endpoint node. */ + np = of_parse_phandle(node, "remote-endpoint", 0); + + /* Walk 3 levels up only if there is 'ports' node. */ + for (depth = 3; depth && np; depth--) { + np = of_get_next_parent(np); + if (depth == 2 && of_node_cmp(np->name, "ports")) + break; + } + return np; +} + +/** + * struct display_of_link - a link between two endpoints + * @local_node: pointer to device_node of this endpoint + * @local_port: identifier of the port this endpoint belongs to + * @remote_node: pointer to device_node of the remote endpoint + * @remote_port: identifier of the port the remote endpoint belongs to + */ +struct display_of_link { + struct device_node *local_node; + unsigned int local_port; + struct device_node *remote_node; + unsigned int remote_port; +}; + +/** + * display_of_parse_link() - parse a link between two endpoints + * @node: pointer to the endpoint at the local end of the link + * @link: pointer to the display OF link data structure + * + * Fill the link structure with the local and remote nodes and port numbers. + * The local_node and remote_node fields are set to point to the local and + * remote port parent nodes respectively (the port parent node being the parent + * node of the port node if that node isn't a 'ports' node, or the grand-parent + * node of the port node otherwise). + * + * A reference is taken to both the local and remote nodes, the caller must use + * display_of_put_link() to drop the references when done with the link. + * + * Return: 0 on success, or -ENOLINK if the remote endpoint can't be found. + */ +static int display_of_parse_link(const struct device_node *node, + struct display_of_link *link) +{ + struct device_node *np; + + memset(link, 0, sizeof(*link)); + + np = of_get_parent(node); + of_property_read_u32(np, "reg", &link->local_port); + np = of_get_next_parent(np); + if (of_node_cmp(np->name, "ports") == 0) + np = of_get_next_parent(np); + link->local_node = np; + + np = of_parse_phandle(node, "remote-endpoint", 0); + if (!np) { + of_node_put(link->local_node); + return -ENOLINK; + } + + np = of_get_parent(np); + of_property_read_u32(np, "reg", &link->remote_port); + np = of_get_next_parent(np); + if (of_node_cmp(np->name, "ports") == 0) + np = of_get_next_parent(np); + link->remote_node = np; + + return 0; +} + +/** + * display_of_put_link() - drop references to nodes in a link + * @link: pointer to the display OF link data structure + * + * Drop references to the local and remote nodes in the link. This function must + * be called on every link parsed with display_of_parse_link(). + */ +static void display_of_put_link(struct display_of_link *link) +{ + of_node_put(link->local_node); + of_node_put(link->remote_node); +} + +#endif /* CONFIG_OF */ + +/* ----------------------------------------------------------------------------- * Graph Helpers */ @@ -420,6 +599,161 @@ int display_entity_link_graph(struct device *dev, struct list_head *entities) } EXPORT_SYMBOL_GPL(display_entity_link_graph); +#ifdef CONFIG_OF + +static int display_of_entity_link_entity(struct device *dev, + struct display_entity *entity, + struct list_head *entities, + struct display_entity *root) +{ + u32 link_flags = MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED; + const struct device_node *node = entity->dev->of_node; + struct media_entity *local = &entity->entity; + struct device_node *ep = NULL; + int ret = 0; + + dev_dbg(dev, "creating links for entity %s\n", local->name); + + while (1) { + struct media_entity *remote = NULL; + struct media_pad *remote_pad; + struct media_pad *local_pad; + struct display_of_link link; + struct display_entity *ent; + struct device_node *next; + + /* Get the next endpoint and parse its link. */ + next = display_of_get_next_endpoint(node, ep); + if (next == NULL) + break; + + of_node_put(ep); + ep = next; + + dev_dbg(dev, "processing endpoint %s\n", ep->full_name); + + ret = display_of_parse_link(ep, &link); + if (ret < 0) { + dev_err(dev, "failed to parse link for %s\n", + ep->full_name); + continue; + } + + /* Skip source pads, they will be processed from the other end of + * the link. + */ + if (link.local_port >= local->num_pads) { + dev_err(dev, "invalid port number %u on %s\n", + link.local_port, link.local_node->full_name); + display_of_put_link(&link); + ret = -EINVAL; + break; + } + + local_pad = &local->pads[link.local_port]; + + if (local_pad->flags & MEDIA_PAD_FL_SOURCE) { + dev_dbg(dev, "skipping source port %s:%u\n", + link.local_node->full_name, link.local_port); + display_of_put_link(&link); + continue; + } + + /* Find the remote entity. If not found, just skip the link as + * it goes out of scope of the entities handled by the notifier. + */ + list_for_each_entry(ent, entities, list) { + if (ent->dev->of_node == link.remote_node) { + remote = &ent->entity; + break; + } + } + + if (root->dev->of_node == link.remote_node) + remote = &root->entity; + + if (remote == NULL) { + dev_dbg(dev, "no entity found for %s\n", + link.remote_node->full_name); + display_of_put_link(&link); + continue; + } + + if (link.remote_port >= remote->num_pads) { + dev_err(dev, "invalid port number %u on %s\n", + link.remote_port, link.remote_node->full_name); + display_of_put_link(&link); + ret = -EINVAL; + break; + } + + remote_pad = &remote->pads[link.remote_port]; + + display_of_put_link(&link); + + /* Create the media link. */ + dev_dbg(dev, "creating %s:%u -> %s:%u link\n", + remote->name, remote_pad->index, + local->name, local_pad->index); + + ret = media_entity_create_link(remote, remote_pad->index, + local, local_pad->index, + link_flags); + if (ret < 0) { + dev_err(dev, + "failed to create %s:%u -> %s:%u link\n", + remote->name, remote_pad->index, + local->name, local_pad->index); + break; + } + } + + of_node_put(ep); + return ret; +} + +/** + * display_of_entity_link_graph - Link all entities in a graph + * @dev: device used to print debugging and error messages + * @root: optional root display entity + * @entities: list of display entities in the graph + * + * This function creates media controller links for all entities in a graph + * based on the device tree graph representation. It relies on all entities + * having been instantiated from the device tree. + * + * The list of entities is typically taken directly from a display notifier + * done list. It will thus not include any display entity not handled by the + * notifier, such as entities directly accessible by the caller without going + * through the notification process. The optional root entity parameter can be + * used to pass such a display entity and include it in the graph. For all + * practical purpose the root entity is handled is if it was part of the + * entities list. + * + * Return 0 on success or a negative error code otherwise. + */ +int display_of_entity_link_graph(struct device *dev, struct list_head *entities, + struct display_entity *root) +{ + struct display_entity *entity; + int ret; + + list_for_each_entry(entity, entities, list) { + if (WARN_ON(entity->match->type != DISPLAY_ENTITY_BUS_DT)) + return -EINVAL; + + ret = display_of_entity_link_entity(dev, entity, entities, + root); + if (ret < 0) + return ret; + } + + return display_of_entity_link_entity(dev, root, entities, root); +} +EXPORT_SYMBOL_GPL(display_of_entity_link_graph); + +#endif + MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@xxxxxxxxxxxxxxxx>"); MODULE_DESCRIPTION("Display Core"); MODULE_LICENSE("GPL"); diff --git a/drivers/video/display/display-notifier.c b/drivers/video/display/display-notifier.c index 2d752b3..6bede03 100644 --- a/drivers/video/display/display-notifier.c +++ b/drivers/video/display/display-notifier.c @@ -16,6 +16,7 @@ #include <linux/list.h> #include <linux/mutex.h> #include <linux/of.h> +#include <linux/slab.h> #include <video/display.h> @@ -36,6 +37,14 @@ static bool match_platform(struct device *dev, return !strcmp(match->match.platform.name, dev_name(dev)); } +static bool match_dt(struct device *dev, struct display_entity_match *match) +{ + pr_debug("%s: matching device node '%s' with node '%s'\n", __func__, + dev->of_node->full_name, match->match.dt.node->full_name); + + return match->match.dt.node == dev->of_node; +} + static struct display_entity_match * display_entity_notifier_match(struct display_entity_notifier *notifier, struct display_entity *entity) @@ -52,6 +61,9 @@ display_entity_notifier_match(struct display_entity_notifier *notifier, case DISPLAY_ENTITY_BUS_PLATFORM: match_func = match_platform; break; + case DISPLAY_ENTITY_BUS_DT: + match_func = match_dt; + break; } if (match_func(entity->dev, match)) @@ -158,6 +170,7 @@ int display_entity_register_notifier(struct display_entity_notifier *notifier) switch (match->type) { case DISPLAY_ENTITY_BUS_PLATFORM: + case DISPLAY_ENTITY_BUS_DT: break; default: dev_err(notifier->dev, @@ -272,6 +285,180 @@ int display_entity_build_notifier(struct display_entity_notifier *notifier, EXPORT_SYMBOL_GPL(display_entity_build_notifier); /* ----------------------------------------------------------------------------- + * OF Support + */ + +#ifdef CONFIG_OF + +struct display_entity_of { + struct list_head list; + struct device_node *node; +}; + +static struct display_entity_of * +display_of_find_entity(struct list_head *entities, + const struct device_node *node) +{ + struct display_entity_of *entity; + + list_for_each_entry(entity, entities, list) { + if (entity->node == node) + return entity; + } + + return NULL; +} + +static int display_of_parse_dt(struct display_entity_notifier *notifier, + struct list_head *entities, + struct device_node *node) +{ + struct display_entity_of *entity; + struct device_node *remote; + struct device_node *ep = NULL; + struct device_node *next; + unsigned int num_entities = 0; + int ret = 0; + + /* Walk the device tree and build a list of nodes. */ + dev_dbg(notifier->dev, "parsing node %s\n", node->full_name); + + while (1) { + next = display_of_get_next_endpoint(node, ep); + if (next == NULL) + break; + + of_node_put(ep); + ep = next; + + dev_dbg(notifier->dev, "handling endpoint %s\n", ep->full_name); + + remote = display_of_get_remote_port_parent(ep); + if (remote == NULL) + continue; + + /* Skip entities that we have already processed. */ + if (display_of_find_entity(entities, remote) || remote == node) { + dev_dbg(notifier->dev, + "entity %s already in list, skipping\n", + remote->full_name); + continue; + } + + entity = kzalloc(sizeof(*entity), GFP_KERNEL); + if (entity == NULL) { + of_node_put(remote); + ret = -ENOMEM; + break; + } + + dev_dbg(notifier->dev, "adding remote entity %s to list\n", + remote->full_name); + + entity->node = remote; + list_add_tail(&entity->list, entities); + num_entities++; + } + + of_node_put(ep); + + if (ret < 0) + return ret; + + return num_entities; +} + +/** + * display_of_entity_build_notifier - build a notifier from device tree + * @notifier: display entity notifier to be built + * @node: device tree node + * + * Before registering a notifier drivers must initialize the notifier's list of + * entities. This helper function simplifies building the list of entities for + * drivers that use a device tree representation of the graph. + * + * The function allocates an array of struct display_entity_match, initialize it + * from the device tree, and sets the notifier entities and num_entities fields. + * + * The entities array is allocated using the managed memory allocation API on + * the notifier device, which must be initialized before calling this function. + * + * Return 0 on success or a negative error code on error. + */ +int display_of_entity_build_notifier(struct display_entity_notifier *notifier, + struct device_node *node) +{ + struct display_entity_match *matches; + struct display_entity_of *entity; + struct display_entity_of *next; + unsigned int num_entities = 0; + LIST_HEAD(entities); + unsigned int i; + int ret; + + /* Add an initial entity that stores the device tree node pointer to the + * list. + */ + entity = kzalloc(sizeof(*entity), GFP_KERNEL); + if (entity == NULL) + return -ENOMEM; + + entity->node = node; + list_add_tail(&entity->list, &entities); + + /* Parse all entities in the list. New entities will be added at the + * tail when parsing the device tree and will just be processed by the + * next iterations. + */ + list_for_each_entry(entity, &entities, list) { + ret = display_of_parse_dt(notifier, &entities, entity->node); + if (ret < 0) + goto error; + + num_entities += ret; + } + + /* Allocate the entity matches array and fill it. */ + matches = devm_kzalloc(notifier->dev, sizeof(*notifier->entities) * + num_entities, GFP_KERNEL); + if (matches == NULL) { + ret = -ENOMEM; + goto error; + } + + i = 0; + list_for_each_entry_safe(entity, next, &entities, list) { + struct display_entity_match *match; + + /* Don't add the initial node to the matches array. */ + if (entity->node != node) { + match = &matches[i++]; + match->type = DISPLAY_ENTITY_BUS_DT; + match->match.dt.node = entity->node; + } + + list_del(&entity->list); + kfree(entity); + } + + notifier->num_entities = num_entities; + notifier->entities = matches; + + return 0; + +error: + list_for_each_entry_safe(entity, next, &entities, list) { + list_del(&entity->list); + kfree(entity); + } + + return ret; +} +EXPORT_SYMBOL_GPL(display_of_entity_build_notifier); + +#endif /* CONFIG_OF */ + +/* ----------------------------------------------------------------------------- * Entity Registration */ diff --git a/include/video/display.h b/include/video/display.h index 58ff0d1..36ff637 100644 --- a/include/video/display.h +++ b/include/video/display.h @@ -22,6 +22,7 @@ * Display Entity */ +struct device_node; struct display_entity; struct display_entity_match; struct display_entity_notify; @@ -145,12 +146,33 @@ int display_entity_get_params(struct display_entity *entity, unsigned int port, int display_entity_set_stream(struct display_entity *entity, unsigned int port, enum display_entity_stream_state state); +#ifdef CONFIG_OF +struct device_node * +display_of_get_next_endpoint(const struct device_node *parent, + struct device_node *prev); +struct device_node * +display_of_get_remote_port_parent(const struct device_node *node); +#else +static inline struct device_node * +display_of_get_next_endpoint(const struct device_node *parent, + struct device_node *prev) +{ + return NULL; +} +static inline struct device_node * +display_of_get_remote_port_parent(const struct device_node *node) +{ + return NULL; +} +#endif + /* ----------------------------------------------------------------------------- * Notifier */ enum display_entity_bus_type { DISPLAY_ENTITY_BUS_PLATFORM, + DISPLAY_ENTITY_BUS_DT, }; /** @@ -167,6 +189,9 @@ struct display_entity_match { struct { const char *name; } platform; + struct { + const struct device_node *node; + } dt; } match; struct list_head list; @@ -226,4 +251,24 @@ int display_entity_build_notifier(struct display_entity_notifier *notifier, const struct display_entity_graph_data *graph); int display_entity_link_graph(struct device *dev, struct list_head *entities); +#ifdef CONFIG_OF +int display_of_entity_build_notifier(struct display_entity_notifier *notifier, + struct device_node *node); +int display_of_entity_link_graph(struct device *dev, struct list_head *entities, + struct display_entity *root); +#else +static inline int +display_of_entity_build_notifier(struct display_entity_notifier *notifier, + struct device_node *node) +{ + return -ENOSYS; +} +static inline int +display_of_entity_link_graph(struct device *dev,struct list_head *entities, + struct display_entity *root) +{ + return -ENOSYS; +} +#endif + #endif /* __DISPLAY_H__ */ -- 1.8.1.5 _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/dri-devel