mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-09-04 20:19:47 +08:00

V4L2 async sub-device matching originally used the device nodes only. Endpoint nodes were taken into use instead as using the device nodes was problematic for it was in some cases ambiguous which link might have been in question. There is however no need to use endpoint nodes on both sides, as the async sub-device's fwnode can always be trivially obtained using fwnode_graph_get_remote_endpoint() when needed while what counts is whether or not the link is between two device nodes, i.e. the device nodes match. This will briefly break the adv748x driver but it will be fixed later in the set, by patch "media: adv748x: Return to endpoint matching". Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com> Tested-by: Philipp Zabel <p.zabel@pengutronix.de> # imx6qp Tested-by: Niklas Söderlund <niklas.soderlund@ragnatech.se> # rcar + adv746x Tested-by: Aishwarya Kothari <aishwarya.kothari@toradex.com> # Apalis i.MX6Q with TC358743 Tested-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com> # Renesas RZ/G2L SMARC Signed-off-by: Mauro Carvalho Chehab <mchehab@kernel.org>
941 lines
22 KiB
C
941 lines
22 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* V4L2 asynchronous subdevice registration API
|
|
*
|
|
* Copyright (C) 2012-2013, Guennadi Liakhovetski <g.liakhovetski@gmx.de>
|
|
*/
|
|
|
|
#include <linux/debugfs.h>
|
|
#include <linux/device.h>
|
|
#include <linux/err.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/list.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/types.h>
|
|
|
|
#include <media/v4l2-async.h>
|
|
#include <media/v4l2-device.h>
|
|
#include <media/v4l2-fwnode.h>
|
|
#include <media/v4l2-subdev.h>
|
|
|
|
#include "v4l2-subdev-priv.h"
|
|
|
|
static int v4l2_async_nf_call_bound(struct v4l2_async_notifier *n,
|
|
struct v4l2_subdev *subdev,
|
|
struct v4l2_async_subdev *asd)
|
|
{
|
|
if (!n->ops || !n->ops->bound)
|
|
return 0;
|
|
|
|
return n->ops->bound(n, subdev, asd);
|
|
}
|
|
|
|
static void v4l2_async_nf_call_unbind(struct v4l2_async_notifier *n,
|
|
struct v4l2_subdev *subdev,
|
|
struct v4l2_async_subdev *asd)
|
|
{
|
|
if (!n->ops || !n->ops->unbind)
|
|
return;
|
|
|
|
n->ops->unbind(n, subdev, asd);
|
|
}
|
|
|
|
static int v4l2_async_nf_call_complete(struct v4l2_async_notifier *n)
|
|
{
|
|
if (!n->ops || !n->ops->complete)
|
|
return 0;
|
|
|
|
return n->ops->complete(n);
|
|
}
|
|
|
|
static void v4l2_async_nf_call_destroy(struct v4l2_async_notifier *n,
|
|
struct v4l2_async_subdev *asd)
|
|
{
|
|
if (!n->ops || !n->ops->destroy)
|
|
return;
|
|
|
|
n->ops->destroy(asd);
|
|
}
|
|
|
|
static bool match_i2c(struct v4l2_async_notifier *notifier,
|
|
struct v4l2_subdev *sd,
|
|
struct v4l2_async_match_desc *match)
|
|
{
|
|
#if IS_ENABLED(CONFIG_I2C)
|
|
struct i2c_client *client = i2c_verify_client(sd->dev);
|
|
|
|
return client &&
|
|
match->i2c.adapter_id == client->adapter->nr &&
|
|
match->i2c.address == client->addr;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
static struct device *notifier_dev(struct v4l2_async_notifier *notifier)
|
|
{
|
|
if (notifier->sd)
|
|
return notifier->sd->dev;
|
|
|
|
if (notifier->v4l2_dev)
|
|
return notifier->v4l2_dev->dev;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static bool
|
|
match_fwnode_one(struct v4l2_async_notifier *notifier,
|
|
struct v4l2_subdev *sd, struct fwnode_handle *sd_fwnode,
|
|
struct v4l2_async_match_desc *match)
|
|
{
|
|
struct fwnode_handle *asd_dev_fwnode;
|
|
bool ret;
|
|
|
|
dev_dbg(notifier_dev(notifier),
|
|
"v4l2-async: fwnode match: need %pfw, trying %pfw\n",
|
|
sd_fwnode, match->fwnode);
|
|
|
|
if (sd_fwnode == match->fwnode) {
|
|
dev_dbg(notifier_dev(notifier),
|
|
"v4l2-async: direct match found\n");
|
|
return true;
|
|
}
|
|
|
|
if (!fwnode_graph_is_endpoint(match->fwnode)) {
|
|
dev_dbg(notifier_dev(notifier),
|
|
"v4l2-async: direct match not found\n");
|
|
return false;
|
|
}
|
|
|
|
asd_dev_fwnode = fwnode_graph_get_port_parent(match->fwnode);
|
|
|
|
ret = sd_fwnode == asd_dev_fwnode;
|
|
|
|
fwnode_handle_put(asd_dev_fwnode);
|
|
|
|
dev_dbg(notifier_dev(notifier),
|
|
"v4l2-async: device--endpoint match %sfound\n",
|
|
ret ? "" : "not ");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool match_fwnode(struct v4l2_async_notifier *notifier,
|
|
struct v4l2_subdev *sd,
|
|
struct v4l2_async_match_desc *match)
|
|
{
|
|
dev_dbg(notifier_dev(notifier),
|
|
"v4l2-async: matching for notifier %pfw, sd fwnode %pfw\n",
|
|
dev_fwnode(notifier_dev(notifier)), sd->fwnode);
|
|
|
|
if (match_fwnode_one(notifier, sd, sd->fwnode, match))
|
|
return true;
|
|
|
|
/* Also check the secondary fwnode. */
|
|
if (IS_ERR_OR_NULL(sd->fwnode->secondary))
|
|
return false;
|
|
|
|
dev_dbg(notifier_dev(notifier),
|
|
"v4l2-async: trying secondary fwnode match\n");
|
|
|
|
return match_fwnode_one(notifier, sd, sd->fwnode->secondary, match);
|
|
}
|
|
|
|
static LIST_HEAD(subdev_list);
|
|
static LIST_HEAD(notifier_list);
|
|
static DEFINE_MUTEX(list_lock);
|
|
|
|
static struct v4l2_async_subdev *
|
|
v4l2_async_find_match(struct v4l2_async_notifier *notifier,
|
|
struct v4l2_subdev *sd)
|
|
{
|
|
bool (*match)(struct v4l2_async_notifier *notifier,
|
|
struct v4l2_subdev *sd,
|
|
struct v4l2_async_match_desc *match);
|
|
struct v4l2_async_subdev *asd;
|
|
|
|
list_for_each_entry(asd, ¬ifier->waiting_list, waiting_entry) {
|
|
/* bus_type has been verified valid before */
|
|
switch (asd->match.type) {
|
|
case V4L2_ASYNC_MATCH_TYPE_I2C:
|
|
match = match_i2c;
|
|
break;
|
|
case V4L2_ASYNC_MATCH_TYPE_FWNODE:
|
|
match = match_fwnode;
|
|
break;
|
|
default:
|
|
/* Cannot happen, unless someone breaks us */
|
|
WARN_ON(true);
|
|
return NULL;
|
|
}
|
|
|
|
/* match cannot be NULL here */
|
|
if (match(notifier, sd, &asd->match))
|
|
return asd;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Compare two async match descriptors for equivalence */
|
|
static bool v4l2_async_match_equal(struct v4l2_async_match_desc *match1,
|
|
struct v4l2_async_match_desc *match2)
|
|
{
|
|
if (match1->type != match2->type)
|
|
return false;
|
|
|
|
switch (match1->type) {
|
|
case V4L2_ASYNC_MATCH_TYPE_I2C:
|
|
return match1->i2c.adapter_id == match2->i2c.adapter_id &&
|
|
match1->i2c.address == match2->i2c.address;
|
|
case V4L2_ASYNC_MATCH_TYPE_FWNODE:
|
|
return match1->fwnode == match2->fwnode;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Find the sub-device notifier registered by a sub-device driver. */
|
|
static struct v4l2_async_notifier *
|
|
v4l2_async_find_subdev_notifier(struct v4l2_subdev *sd)
|
|
{
|
|
struct v4l2_async_notifier *n;
|
|
|
|
list_for_each_entry(n, ¬ifier_list, notifier_entry)
|
|
if (n->sd == sd)
|
|
return n;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Get v4l2_device related to the notifier if one can be found. */
|
|
static struct v4l2_device *
|
|
v4l2_async_nf_find_v4l2_dev(struct v4l2_async_notifier *notifier)
|
|
{
|
|
while (notifier->parent)
|
|
notifier = notifier->parent;
|
|
|
|
return notifier->v4l2_dev;
|
|
}
|
|
|
|
/*
|
|
* Return true if all child sub-device notifiers are complete, false otherwise.
|
|
*/
|
|
static bool
|
|
v4l2_async_nf_can_complete(struct v4l2_async_notifier *notifier)
|
|
{
|
|
struct v4l2_subdev *sd;
|
|
|
|
if (!list_empty(¬ifier->waiting_list))
|
|
return false;
|
|
|
|
list_for_each_entry(sd, ¬ifier->done_list, async_list) {
|
|
struct v4l2_async_notifier *subdev_notifier =
|
|
v4l2_async_find_subdev_notifier(sd);
|
|
|
|
if (subdev_notifier &&
|
|
!v4l2_async_nf_can_complete(subdev_notifier))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Complete the master notifier if possible. This is done when all async
|
|
* sub-devices have been bound; v4l2_device is also available then.
|
|
*/
|
|
static int
|
|
v4l2_async_nf_try_complete(struct v4l2_async_notifier *notifier)
|
|
{
|
|
struct v4l2_async_notifier *__notifier = notifier;
|
|
|
|
/* Quick check whether there are still more sub-devices here. */
|
|
if (!list_empty(¬ifier->waiting_list))
|
|
return 0;
|
|
|
|
if (notifier->sd)
|
|
dev_dbg(notifier_dev(notifier),
|
|
"v4l2-async: trying to complete\n");
|
|
|
|
/* Check the entire notifier tree; find the root notifier first. */
|
|
while (notifier->parent)
|
|
notifier = notifier->parent;
|
|
|
|
/* This is root if it has v4l2_dev. */
|
|
if (!notifier->v4l2_dev) {
|
|
dev_dbg(notifier_dev(__notifier),
|
|
"v4l2-async: V4L2 device not available\n");
|
|
return 0;
|
|
}
|
|
|
|
/* Is everything ready? */
|
|
if (!v4l2_async_nf_can_complete(notifier))
|
|
return 0;
|
|
|
|
dev_dbg(notifier_dev(__notifier), "v4l2-async: complete\n");
|
|
|
|
return v4l2_async_nf_call_complete(notifier);
|
|
}
|
|
|
|
static int
|
|
v4l2_async_nf_try_all_subdevs(struct v4l2_async_notifier *notifier);
|
|
|
|
static int v4l2_async_create_ancillary_links(struct v4l2_async_notifier *n,
|
|
struct v4l2_subdev *sd)
|
|
{
|
|
struct media_link *link = NULL;
|
|
|
|
#if IS_ENABLED(CONFIG_MEDIA_CONTROLLER)
|
|
|
|
if (sd->entity.function != MEDIA_ENT_F_LENS &&
|
|
sd->entity.function != MEDIA_ENT_F_FLASH)
|
|
return 0;
|
|
|
|
link = media_create_ancillary_link(&n->sd->entity, &sd->entity);
|
|
|
|
#endif
|
|
|
|
return IS_ERR(link) ? PTR_ERR(link) : 0;
|
|
}
|
|
|
|
static int v4l2_async_match_notify(struct v4l2_async_notifier *notifier,
|
|
struct v4l2_device *v4l2_dev,
|
|
struct v4l2_subdev *sd,
|
|
struct v4l2_async_subdev *asd)
|
|
{
|
|
struct v4l2_async_notifier *subdev_notifier;
|
|
int ret;
|
|
|
|
ret = v4l2_device_register_subdev(v4l2_dev, sd);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = v4l2_async_nf_call_bound(notifier, sd, asd);
|
|
if (ret < 0) {
|
|
v4l2_device_unregister_subdev(sd);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Depending of the function of the entities involved, we may want to
|
|
* create links between them (for example between a sensor and its lens
|
|
* or between a sensor's source pad and the connected device's sink
|
|
* pad).
|
|
*/
|
|
ret = v4l2_async_create_ancillary_links(notifier, sd);
|
|
if (ret) {
|
|
v4l2_async_nf_call_unbind(notifier, sd, asd);
|
|
v4l2_device_unregister_subdev(sd);
|
|
return ret;
|
|
}
|
|
|
|
list_del(&asd->waiting_entry);
|
|
sd->asd = asd;
|
|
sd->notifier = notifier;
|
|
|
|
/* Move from the global subdevice list to notifier's done */
|
|
list_move(&sd->async_list, ¬ifier->done_list);
|
|
|
|
dev_dbg(notifier_dev(notifier), "v4l2-async: %s bound (ret %d)\n",
|
|
dev_name(sd->dev), ret);
|
|
|
|
/*
|
|
* See if the sub-device has a notifier. If not, return here.
|
|
*/
|
|
subdev_notifier = v4l2_async_find_subdev_notifier(sd);
|
|
if (!subdev_notifier || subdev_notifier->parent)
|
|
return 0;
|
|
|
|
/*
|
|
* Proceed with checking for the sub-device notifier's async
|
|
* sub-devices, and return the result. The error will be handled by the
|
|
* caller.
|
|
*/
|
|
subdev_notifier->parent = notifier;
|
|
|
|
return v4l2_async_nf_try_all_subdevs(subdev_notifier);
|
|
}
|
|
|
|
/* Test all async sub-devices in a notifier for a match. */
|
|
static int
|
|
v4l2_async_nf_try_all_subdevs(struct v4l2_async_notifier *notifier)
|
|
{
|
|
struct v4l2_device *v4l2_dev =
|
|
v4l2_async_nf_find_v4l2_dev(notifier);
|
|
struct v4l2_subdev *sd;
|
|
|
|
if (!v4l2_dev)
|
|
return 0;
|
|
|
|
dev_dbg(notifier_dev(notifier), "v4l2-async: trying all sub-devices\n");
|
|
|
|
again:
|
|
list_for_each_entry(sd, &subdev_list, async_list) {
|
|
struct v4l2_async_subdev *asd;
|
|
int ret;
|
|
|
|
asd = v4l2_async_find_match(notifier, sd);
|
|
if (!asd)
|
|
continue;
|
|
|
|
dev_dbg(notifier_dev(notifier),
|
|
"v4l2-async: match found, subdev %s\n", sd->name);
|
|
|
|
ret = v4l2_async_match_notify(notifier, v4l2_dev, sd, asd);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/*
|
|
* v4l2_async_match_notify() may lead to registering a
|
|
* new notifier and thus changing the async subdevs
|
|
* list. In order to proceed safely from here, restart
|
|
* parsing the list from the beginning.
|
|
*/
|
|
goto again;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void v4l2_async_cleanup(struct v4l2_subdev *sd)
|
|
{
|
|
v4l2_device_unregister_subdev(sd);
|
|
/*
|
|
* Subdevice driver will reprobe and put the subdev back
|
|
* onto the list
|
|
*/
|
|
list_del_init(&sd->async_list);
|
|
sd->asd = NULL;
|
|
}
|
|
|
|
/* Unbind all sub-devices in the notifier tree. */
|
|
static void
|
|
v4l2_async_nf_unbind_all_subdevs(struct v4l2_async_notifier *notifier,
|
|
bool readd)
|
|
{
|
|
struct v4l2_subdev *sd, *tmp;
|
|
|
|
list_for_each_entry_safe(sd, tmp, ¬ifier->done_list, async_list) {
|
|
struct v4l2_async_notifier *subdev_notifier =
|
|
v4l2_async_find_subdev_notifier(sd);
|
|
|
|
if (subdev_notifier)
|
|
v4l2_async_nf_unbind_all_subdevs(subdev_notifier, true);
|
|
|
|
v4l2_async_nf_call_unbind(notifier, sd, sd->asd);
|
|
if (readd)
|
|
list_add_tail(&sd->asd->waiting_entry,
|
|
¬ifier->waiting_list);
|
|
v4l2_async_cleanup(sd);
|
|
|
|
list_move(&sd->async_list, &subdev_list);
|
|
}
|
|
|
|
notifier->parent = NULL;
|
|
}
|
|
|
|
/* See if an async sub-device can be found in a notifier's lists. */
|
|
static bool
|
|
v4l2_async_nf_has_async_match_entry(struct v4l2_async_notifier *notifier,
|
|
struct v4l2_async_match_desc *match)
|
|
{
|
|
struct v4l2_async_subdev *asd;
|
|
struct v4l2_subdev *sd;
|
|
|
|
list_for_each_entry(asd, ¬ifier->waiting_list, waiting_entry)
|
|
if (v4l2_async_match_equal(&asd->match, match))
|
|
return true;
|
|
|
|
list_for_each_entry(sd, ¬ifier->done_list, async_list) {
|
|
if (WARN_ON(!sd->asd))
|
|
continue;
|
|
|
|
if (v4l2_async_match_equal(&sd->asd->match, match))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Find out whether an async sub-device was set up already or whether it exists
|
|
* in a given notifier. The skip_self argument is used to skip testing the same
|
|
* sub-device in the notifier in case the sub-device has been already added to
|
|
* the notifier.
|
|
*/
|
|
static bool
|
|
v4l2_async_nf_has_async_match(struct v4l2_async_notifier *notifier,
|
|
struct v4l2_async_match_desc *match,
|
|
bool skip_self)
|
|
{
|
|
struct v4l2_async_subdev *asd;
|
|
|
|
lockdep_assert_held(&list_lock);
|
|
|
|
/* Check that an asd is not being added more than once. */
|
|
list_for_each_entry(asd, ¬ifier->asd_list, asd_entry) {
|
|
if (skip_self && &asd->match == match)
|
|
continue;
|
|
if (v4l2_async_match_equal(&asd->match, match))
|
|
return true;
|
|
}
|
|
|
|
/* Check that an asd does not exist in other notifiers. */
|
|
list_for_each_entry(notifier, ¬ifier_list, notifier_entry)
|
|
if (v4l2_async_nf_has_async_match_entry(notifier, match))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static int v4l2_async_nf_match_valid(struct v4l2_async_notifier *notifier,
|
|
struct v4l2_async_match_desc *match,
|
|
bool skip_self)
|
|
{
|
|
struct device *dev = notifier_dev(notifier);
|
|
|
|
switch (match->type) {
|
|
case V4L2_ASYNC_MATCH_TYPE_I2C:
|
|
case V4L2_ASYNC_MATCH_TYPE_FWNODE:
|
|
if (v4l2_async_nf_has_async_match(notifier, match,
|
|
skip_self)) {
|
|
dev_dbg(dev, "v4l2-async: match descriptor already listed in a notifier\n");
|
|
return -EEXIST;
|
|
}
|
|
break;
|
|
default:
|
|
dev_err(dev, "v4l2-async: Invalid match type %u on %p\n",
|
|
match->type, match);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void v4l2_async_nf_init(struct v4l2_async_notifier *notifier)
|
|
{
|
|
INIT_LIST_HEAD(¬ifier->asd_list);
|
|
}
|
|
EXPORT_SYMBOL(v4l2_async_nf_init);
|
|
|
|
static int __v4l2_async_nf_register(struct v4l2_async_notifier *notifier)
|
|
{
|
|
struct v4l2_async_subdev *asd;
|
|
int ret;
|
|
|
|
INIT_LIST_HEAD(¬ifier->waiting_list);
|
|
INIT_LIST_HEAD(¬ifier->done_list);
|
|
|
|
mutex_lock(&list_lock);
|
|
|
|
list_for_each_entry(asd, ¬ifier->asd_list, asd_entry) {
|
|
ret = v4l2_async_nf_match_valid(notifier, &asd->match, true);
|
|
if (ret)
|
|
goto err_unlock;
|
|
|
|
list_add_tail(&asd->waiting_entry, ¬ifier->waiting_list);
|
|
}
|
|
|
|
ret = v4l2_async_nf_try_all_subdevs(notifier);
|
|
if (ret < 0)
|
|
goto err_unbind;
|
|
|
|
ret = v4l2_async_nf_try_complete(notifier);
|
|
if (ret < 0)
|
|
goto err_unbind;
|
|
|
|
/* Keep also completed notifiers on the list */
|
|
list_add(¬ifier->notifier_entry, ¬ifier_list);
|
|
|
|
mutex_unlock(&list_lock);
|
|
|
|
return 0;
|
|
|
|
err_unbind:
|
|
/*
|
|
* On failure, unbind all sub-devices registered through this notifier.
|
|
*/
|
|
v4l2_async_nf_unbind_all_subdevs(notifier, false);
|
|
|
|
err_unlock:
|
|
mutex_unlock(&list_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int v4l2_async_nf_register(struct v4l2_device *v4l2_dev,
|
|
struct v4l2_async_notifier *notifier)
|
|
{
|
|
int ret;
|
|
|
|
if (WARN_ON(!v4l2_dev || notifier->sd))
|
|
return -EINVAL;
|
|
|
|
notifier->v4l2_dev = v4l2_dev;
|
|
|
|
ret = __v4l2_async_nf_register(notifier);
|
|
if (ret)
|
|
notifier->v4l2_dev = NULL;
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(v4l2_async_nf_register);
|
|
|
|
int v4l2_async_subdev_nf_register(struct v4l2_subdev *sd,
|
|
struct v4l2_async_notifier *notifier)
|
|
{
|
|
int ret;
|
|
|
|
if (WARN_ON(!sd || notifier->v4l2_dev))
|
|
return -EINVAL;
|
|
|
|
notifier->sd = sd;
|
|
|
|
ret = __v4l2_async_nf_register(notifier);
|
|
if (ret)
|
|
notifier->sd = NULL;
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(v4l2_async_subdev_nf_register);
|
|
|
|
static void
|
|
__v4l2_async_nf_unregister(struct v4l2_async_notifier *notifier)
|
|
{
|
|
if (!notifier || (!notifier->v4l2_dev && !notifier->sd))
|
|
return;
|
|
|
|
v4l2_async_nf_unbind_all_subdevs(notifier, false);
|
|
|
|
notifier->sd = NULL;
|
|
notifier->v4l2_dev = NULL;
|
|
|
|
list_del(¬ifier->notifier_entry);
|
|
}
|
|
|
|
void v4l2_async_nf_unregister(struct v4l2_async_notifier *notifier)
|
|
{
|
|
mutex_lock(&list_lock);
|
|
|
|
__v4l2_async_nf_unregister(notifier);
|
|
|
|
mutex_unlock(&list_lock);
|
|
}
|
|
EXPORT_SYMBOL(v4l2_async_nf_unregister);
|
|
|
|
static void __v4l2_async_nf_cleanup(struct v4l2_async_notifier *notifier)
|
|
{
|
|
struct v4l2_async_subdev *asd, *tmp;
|
|
|
|
if (!notifier || !notifier->asd_list.next)
|
|
return;
|
|
|
|
list_for_each_entry_safe(asd, tmp, ¬ifier->asd_list, asd_entry) {
|
|
switch (asd->match.type) {
|
|
case V4L2_ASYNC_MATCH_TYPE_FWNODE:
|
|
fwnode_handle_put(asd->match.fwnode);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
list_del(&asd->asd_entry);
|
|
v4l2_async_nf_call_destroy(notifier, asd);
|
|
kfree(asd);
|
|
}
|
|
}
|
|
|
|
void v4l2_async_nf_cleanup(struct v4l2_async_notifier *notifier)
|
|
{
|
|
mutex_lock(&list_lock);
|
|
|
|
__v4l2_async_nf_cleanup(notifier);
|
|
|
|
mutex_unlock(&list_lock);
|
|
}
|
|
EXPORT_SYMBOL_GPL(v4l2_async_nf_cleanup);
|
|
|
|
|
|
static int __v4l2_async_nf_add_subdev(struct v4l2_async_notifier *notifier,
|
|
struct v4l2_async_subdev *asd)
|
|
{
|
|
int ret;
|
|
|
|
mutex_lock(&list_lock);
|
|
|
|
ret = v4l2_async_nf_match_valid(notifier, &asd->match, false);
|
|
if (ret)
|
|
goto unlock;
|
|
|
|
list_add_tail(&asd->asd_entry, ¬ifier->asd_list);
|
|
|
|
unlock:
|
|
mutex_unlock(&list_lock);
|
|
return ret;
|
|
}
|
|
|
|
struct v4l2_async_subdev *
|
|
__v4l2_async_nf_add_fwnode(struct v4l2_async_notifier *notifier,
|
|
struct fwnode_handle *fwnode,
|
|
unsigned int asd_struct_size)
|
|
{
|
|
struct v4l2_async_subdev *asd;
|
|
int ret;
|
|
|
|
asd = kzalloc(asd_struct_size, GFP_KERNEL);
|
|
if (!asd)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
asd->match.type = V4L2_ASYNC_MATCH_TYPE_FWNODE;
|
|
asd->match.fwnode = fwnode_handle_get(fwnode);
|
|
|
|
ret = __v4l2_async_nf_add_subdev(notifier, asd);
|
|
if (ret) {
|
|
fwnode_handle_put(fwnode);
|
|
kfree(asd);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
return asd;
|
|
}
|
|
EXPORT_SYMBOL_GPL(__v4l2_async_nf_add_fwnode);
|
|
|
|
struct v4l2_async_subdev *
|
|
__v4l2_async_nf_add_fwnode_remote(struct v4l2_async_notifier *notif,
|
|
struct fwnode_handle *endpoint,
|
|
unsigned int asd_struct_size)
|
|
{
|
|
struct v4l2_async_subdev *asd;
|
|
struct fwnode_handle *remote;
|
|
|
|
remote = fwnode_graph_get_remote_endpoint(endpoint);
|
|
if (!remote)
|
|
return ERR_PTR(-ENOTCONN);
|
|
|
|
asd = __v4l2_async_nf_add_fwnode(notif, remote, asd_struct_size);
|
|
/*
|
|
* Calling __v4l2_async_nf_add_fwnode grabs a refcount,
|
|
* so drop the one we got in fwnode_graph_get_remote_port_parent.
|
|
*/
|
|
fwnode_handle_put(remote);
|
|
return asd;
|
|
}
|
|
EXPORT_SYMBOL_GPL(__v4l2_async_nf_add_fwnode_remote);
|
|
|
|
struct v4l2_async_subdev *
|
|
__v4l2_async_nf_add_i2c(struct v4l2_async_notifier *notifier, int adapter_id,
|
|
unsigned short address, unsigned int asd_struct_size)
|
|
{
|
|
struct v4l2_async_subdev *asd;
|
|
int ret;
|
|
|
|
asd = kzalloc(asd_struct_size, GFP_KERNEL);
|
|
if (!asd)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
asd->match.type = V4L2_ASYNC_MATCH_TYPE_I2C;
|
|
asd->match.i2c.adapter_id = adapter_id;
|
|
asd->match.i2c.address = address;
|
|
|
|
ret = __v4l2_async_nf_add_subdev(notifier, asd);
|
|
if (ret) {
|
|
kfree(asd);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
return asd;
|
|
}
|
|
EXPORT_SYMBOL_GPL(__v4l2_async_nf_add_i2c);
|
|
|
|
int v4l2_async_register_subdev(struct v4l2_subdev *sd)
|
|
{
|
|
struct v4l2_async_notifier *subdev_notifier;
|
|
struct v4l2_async_notifier *notifier;
|
|
int ret;
|
|
|
|
/*
|
|
* No reference taken. The reference is held by the device (struct
|
|
* v4l2_subdev.dev), and async sub-device does not exist independently
|
|
* of the device at any point of time.
|
|
*
|
|
* The async sub-device shall always be registered for its device node,
|
|
* not the endpoint node.
|
|
*/
|
|
if (!sd->fwnode && sd->dev) {
|
|
sd->fwnode = dev_fwnode(sd->dev);
|
|
} else if (fwnode_graph_is_endpoint(sd->fwnode)) {
|
|
dev_warn(sd->dev, "sub-device fwnode is an endpoint!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&list_lock);
|
|
|
|
INIT_LIST_HEAD(&sd->async_list);
|
|
|
|
list_for_each_entry(notifier, ¬ifier_list, notifier_entry) {
|
|
struct v4l2_device *v4l2_dev =
|
|
v4l2_async_nf_find_v4l2_dev(notifier);
|
|
struct v4l2_async_subdev *asd;
|
|
|
|
if (!v4l2_dev)
|
|
continue;
|
|
|
|
asd = v4l2_async_find_match(notifier, sd);
|
|
if (!asd)
|
|
continue;
|
|
|
|
ret = v4l2_async_match_notify(notifier, v4l2_dev, sd, asd);
|
|
if (ret)
|
|
goto err_unbind;
|
|
|
|
ret = v4l2_async_nf_try_complete(notifier);
|
|
if (ret)
|
|
goto err_unbind;
|
|
|
|
goto out_unlock;
|
|
}
|
|
|
|
/* None matched, wait for hot-plugging */
|
|
list_add(&sd->async_list, &subdev_list);
|
|
|
|
out_unlock:
|
|
mutex_unlock(&list_lock);
|
|
|
|
return 0;
|
|
|
|
err_unbind:
|
|
/*
|
|
* Complete failed. Unbind the sub-devices bound through registering
|
|
* this async sub-device.
|
|
*/
|
|
subdev_notifier = v4l2_async_find_subdev_notifier(sd);
|
|
if (subdev_notifier)
|
|
v4l2_async_nf_unbind_all_subdevs(subdev_notifier, false);
|
|
|
|
if (sd->asd)
|
|
v4l2_async_nf_call_unbind(notifier, sd, sd->asd);
|
|
v4l2_async_cleanup(sd);
|
|
|
|
mutex_unlock(&list_lock);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(v4l2_async_register_subdev);
|
|
|
|
void v4l2_async_unregister_subdev(struct v4l2_subdev *sd)
|
|
{
|
|
if (!sd->async_list.next)
|
|
return;
|
|
|
|
v4l2_subdev_put_privacy_led(sd);
|
|
|
|
mutex_lock(&list_lock);
|
|
|
|
__v4l2_async_nf_unregister(sd->subdev_notifier);
|
|
__v4l2_async_nf_cleanup(sd->subdev_notifier);
|
|
kfree(sd->subdev_notifier);
|
|
sd->subdev_notifier = NULL;
|
|
|
|
if (sd->asd) {
|
|
struct v4l2_async_notifier *notifier = sd->notifier;
|
|
|
|
list_add(&sd->asd->waiting_entry, ¬ifier->waiting_list);
|
|
|
|
v4l2_async_nf_call_unbind(notifier, sd, sd->asd);
|
|
}
|
|
|
|
v4l2_async_cleanup(sd);
|
|
|
|
mutex_unlock(&list_lock);
|
|
}
|
|
EXPORT_SYMBOL(v4l2_async_unregister_subdev);
|
|
|
|
static void print_waiting_match(struct seq_file *s,
|
|
struct v4l2_async_match_desc *match)
|
|
{
|
|
switch (match->type) {
|
|
case V4L2_ASYNC_MATCH_TYPE_I2C:
|
|
seq_printf(s, " [i2c] dev=%d-%04x\n", match->i2c.adapter_id,
|
|
match->i2c.address);
|
|
break;
|
|
case V4L2_ASYNC_MATCH_TYPE_FWNODE: {
|
|
struct fwnode_handle *devnode, *fwnode = match->fwnode;
|
|
|
|
devnode = fwnode_graph_is_endpoint(fwnode) ?
|
|
fwnode_graph_get_port_parent(fwnode) :
|
|
fwnode_handle_get(fwnode);
|
|
|
|
seq_printf(s, " [fwnode] dev=%s, node=%pfw\n",
|
|
devnode->dev ? dev_name(devnode->dev) : "nil",
|
|
fwnode);
|
|
|
|
fwnode_handle_put(devnode);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static const char *
|
|
v4l2_async_nf_name(struct v4l2_async_notifier *notifier)
|
|
{
|
|
if (notifier->v4l2_dev)
|
|
return notifier->v4l2_dev->name;
|
|
else if (notifier->sd)
|
|
return notifier->sd->name;
|
|
else
|
|
return "nil";
|
|
}
|
|
|
|
static int pending_subdevs_show(struct seq_file *s, void *data)
|
|
{
|
|
struct v4l2_async_notifier *notif;
|
|
struct v4l2_async_subdev *asd;
|
|
|
|
mutex_lock(&list_lock);
|
|
|
|
list_for_each_entry(notif, ¬ifier_list, notifier_entry) {
|
|
seq_printf(s, "%s:\n", v4l2_async_nf_name(notif));
|
|
list_for_each_entry(asd, ¬if->waiting_list, waiting_entry)
|
|
print_waiting_match(s, &asd->match);
|
|
}
|
|
|
|
mutex_unlock(&list_lock);
|
|
|
|
return 0;
|
|
}
|
|
DEFINE_SHOW_ATTRIBUTE(pending_subdevs);
|
|
|
|
static struct dentry *v4l2_async_debugfs_dir;
|
|
|
|
static int __init v4l2_async_init(void)
|
|
{
|
|
v4l2_async_debugfs_dir = debugfs_create_dir("v4l2-async", NULL);
|
|
debugfs_create_file("pending_async_subdevices", 0444,
|
|
v4l2_async_debugfs_dir, NULL,
|
|
&pending_subdevs_fops);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __exit v4l2_async_exit(void)
|
|
{
|
|
debugfs_remove_recursive(v4l2_async_debugfs_dir);
|
|
}
|
|
|
|
subsys_initcall(v4l2_async_init);
|
|
module_exit(v4l2_async_exit);
|
|
|
|
MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>");
|
|
MODULE_AUTHOR("Sakari Ailus <sakari.ailus@linux.intel.com>");
|
|
MODULE_AUTHOR("Ezequiel Garcia <ezequiel@collabora.com>");
|
|
MODULE_LICENSE("GPL");
|