// 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_connection *asc)
{
if (!n->ops || !n->ops->bound)
return 0;
return n->ops->bound(n, subdev, asc);
}
static void v4l2_async_nf_call_unbind(struct v4l2_async_notifier *n,
struct v4l2_subdev *subdev,
struct v4l2_async_connection *asc)
{
if (!n->ops || !n->ops->unbind)
return;
n->ops->unbind(n, subdev, asc);
}
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_connection *asc)
{
if (!n->ops || !n->ops->destroy)
return;
n->ops->destroy(asc);
}
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 (!list_empty(&sd->async_subdev_endpoint_list)) {
struct v4l2_async_subdev_endpoint *ase;
dev_dbg(sd->dev,
"v4l2-async: endpoint fwnode list available, looking for %pfw\n",
match->fwnode);
list_for_each_entry(ase, &sd->async_subdev_endpoint_list,
async_subdev_endpoint_entry) {
bool matched = ase->endpoint == match->fwnode;
dev_dbg(sd->dev,
"v4l2-async: endpoint-endpoint match %sfound with %pfw\n",
matched ? "" : "not ", ase->endpoint);
if (matched)
return true;
}
dev_dbg(sd->dev, "async: no endpoint matched\n");
return false;
}
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_connection *
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_connection *asc;
list_for_each_entry(asc, ¬ifier->waiting_list, asc_entry) {
/* bus_type has been verified valid before */
switch (asc->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, &asc->