@@ -134,6 +134,7 @@ static bool match_fwnode(struct v4l2_async_notifier *notifier,
}
static LIST_HEAD(subdev_head);
+static LIST_HEAD(asd_head);
static LIST_HEAD(notifier_head);
static DEFINE_MUTEX(list_lock);
@@ -304,13 +305,20 @@ static int v4l2_async_match_notify(struct v4l2_async_notifier *notifier,
struct v4l2_async_notifier *subdev_notifier;
int ret;
- ret = v4l2_device_register_subdev(v4l2_dev, sd);
- if (ret < 0)
- return ret;
+ if (!asc->asd->registered) {
+ ret = v4l2_device_register_subdev(v4l2_dev, sd);
+ if (ret < 0)
+ return ret;
+ }
ret = v4l2_async_nf_call_bound(notifier, sd, asc);
if (ret < 0) {
- v4l2_device_unregister_subdev(sd);
+ if (asc->match.type == V4L2_ASYNC_MATCH_FWNODE)
+ dev_dbg(notifier_dev(notifier),
+ "failed binding %pfw (%d)\n",
+ asc->match.fwnode, ret);
+ if (!asc->asd->registered)
+ v4l2_device_unregister_subdev(sd);
return ret;
}
@@ -322,14 +330,26 @@ static int v4l2_async_match_notify(struct v4l2_async_notifier *notifier,
*/
ret = v4l2_async_create_ancillary_links(notifier, sd);
if (ret) {
+ if (asc->match.type == V4L2_ASYNC_MATCH_FWNODE)
+ dev_dbg(notifier_dev(notifier),
+ "failed creating links for %pfw (%d)\n",
+ asc->match.fwnode, ret);
v4l2_async_nf_call_unbind(notifier, sd, asc);
- v4l2_device_unregister_subdev(sd);
+ list_del(&asc->asc_subdev_list);
+ if (!asc->asd->registered)
+ v4l2_device_unregister_subdev(sd);
return ret;
}
list_del(&asc->waiting_list);
- sd->asd = asc;
- sd->notifier = notifier;
+ if (!sd->asd) {
+ WARN_ON(asc->asd->registered);
+ sd->asd = asc->asd;
+ sd->notifier = notifier;
+ asc->asd->registered = true;
+ } else {
+ WARN_ON(sd->asd != asc->asd);
+ }
/* Move from the global subdevice list to notifier's done */
list_move(&sd->async_list, ¬ifier->done_head);
@@ -403,6 +423,21 @@ static void v4l2_async_cleanup(struct v4l2_subdev *sd)
sd->asd = NULL;
}
+static void v4l2_async_unbind_subdev_one(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *sd, bool readd)
+{
+ struct v4l2_async_connection *asc, *tmp;
+
+ list_for_each_entry_safe(asc, tmp, &sd->asd->asc_head,
+ asc_subdev_list) {
+ v4l2_async_nf_call_unbind(notifier, sd, asc);
+ list_del(&asc->asc_subdev_list);
+ if (readd)
+ list_add_tail(&asc->waiting_list,
+ ¬ifier->waiting_head);
+ }
+}
+
/* Unbind all sub-devices in the notifier tree. */
static void
v4l2_async_nf_unbind_all_subdevs(struct v4l2_async_notifier *notifier,
@@ -417,10 +452,8 @@ v4l2_async_nf_unbind_all_subdevs(struct v4l2_async_notifier *notifier,
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_list,
- ¬ifier->waiting_head);
+ v4l2_async_unbind_subdev_one(notifier, sd, readd);
+
v4l2_async_cleanup(sd);
list_move(&sd->async_list, &subdev_head);
@@ -445,8 +478,9 @@ __v4l2_async_nf_has_async_subdev(struct v4l2_async_notifier *notifier,
if (WARN_ON(!sd->asd))
continue;
- if (asc_equal(&sd->asd->match, match))
- return true;
+ list_for_each_entry(asc, &sd->asd->asc_head, asc_list)
+ if (asc_equal(&asc->match, match))
+ return true;
}
return false;
@@ -534,7 +568,7 @@ static int __v4l2_async_nf_register(struct v4l2_async_notifier *notifier)
mutex_lock(&list_lock);
list_for_each_entry(asc, ¬ifier->asc_head, asc_list) {
- ret = v4l2_async_nf_asd_valid(notifier, &asc->match, true);
+ ret = v4l2_async_nf_asc_valid(notifier, &asc->match, true);
if (ret)
goto err_unlock;
@@ -604,6 +638,18 @@ void v4l2_async_nf_unregister(struct v4l2_async_notifier *notifier)
}
EXPORT_SYMBOL(v4l2_async_nf_unregister);
+static void release_async_subdev(struct kref *kref)
+{
+ struct v4l2_async_subdev *asd =
+ container_of_const(kref, struct v4l2_async_subdev, kref);
+
+ list_del(&asd->asd_list);
+
+ WARN_ON(!list_empty(&asd->asc_head));
+
+ kfree(asd);
+}
+
static void __v4l2_async_nf_cleanup(struct v4l2_async_notifier *notifier)
{
struct v4l2_async_connection *asc, *tmp;
@@ -612,17 +658,25 @@ static void __v4l2_async_nf_cleanup(struct v4l2_async_notifier *notifier)
return;
list_for_each_entry_safe(asc, tmp, ¬ifier->asc_head, asc_list) {
+ list_del(&asc->asc_list);
+ v4l2_async_nf_call_destroy(notifier, asc);
+
switch (asc->match.type) {
case V4L2_ASYNC_MATCH_FWNODE:
+ pr_debug("release async connection for fwnode %pfw\n",
+ asc->match.fwnode);
fwnode_handle_put(asc->match.fwnode);
break;
- default:
+ case V4L2_ASYNC_MATCH_I2C:
+ pr_debug("release I²C async connection\n");
break;
+ default:
+ pr_debug("release invalid async connection type %u\n",
+ asc->match.type);
}
- list_del(&asc->asc_list);
- v4l2_async_nf_call_destroy(notifier, asd);
- kfree(asd);
+ kref_put(&asc->asd->kref, release_async_subdev);
+ kfree(asc);
}
notifier->sd = NULL;
@@ -639,6 +693,72 @@ void v4l2_async_nf_cleanup(struct v4l2_async_notifier *notifier)
}
EXPORT_SYMBOL_GPL(v4l2_async_nf_cleanup);
+static bool async_subdev_has_connection(struct v4l2_async_notifier *notifier,
+ struct v4l2_async_subdev *asd,
+ struct v4l2_async_connection *asc)
+{
+ struct v4l2_async_connection *__asc;
+
+ list_for_each_entry(__asc, &asd->asc_head, asc_subdev_list) {
+
+ if (__asc->match.type != V4L2_ASYNC_MATCH_FWNODE)
+ continue;
+
+ if (__asc->match.fwnode != asc->match.fwnode)
+ continue;
+
+ dev_dbg(notifier_dev(notifier), "found!\n");
+
+ return true;
+ }
+
+ return false;
+}
+
+/* Find an async sub-device for the async connection. */
+static int v4l2_async_find_async_subdev(struct v4l2_async_notifier *notifier,
+ struct v4l2_async_connection *asc)
+{
+ struct v4l2_async_subdev *asd;
+
+ lockdep_assert_held(&list_lock);
+
+ if (asc->match.type == V4L2_ASYNC_MATCH_FWNODE)
+ dev_dbg(notifier_dev(notifier),
+ "async: looking up subdev for %pfw\n",
+ asc->match.fwnode);
+
+ /*
+ * Matching by endpoint nodes may mean there are multiple connections to
+ * a single device. This is only possible with fwnode matching.
+ */
+ if (asc->match.type == V4L2_ASYNC_MATCH_FWNODE &&
+ fwnode_graph_is_endpoint(asc->match.fwnode)) {
+ list_for_each_entry(asd, &asd_head, asd_list) {
+ if (async_subdev_has_connection(notifier, asd, asc)) {
+ kref_get(&asd->kref);
+ goto found;
+ }
+ }
+ }
+
+ dev_dbg(notifier_dev(notifier), "not found, allocating new one\n");
+
+ asd = kzalloc(sizeof(*asd), GFP_KERNEL);
+ if (!asd)
+ return -ENOMEM;
+
+ kref_init(&asd->kref);
+ INIT_LIST_HEAD(&asd->asc_head);
+ list_add(&asd->asd_list, &asd_head);
+
+found:
+ list_add(&asc->asc_subdev_list, &asd->asc_head);
+ asc->asd = asd;
+
+ return 0;
+}
+
int __v4l2_async_nf_add_connection(struct v4l2_async_notifier *notifier,
struct v4l2_async_connection *asc)
{
@@ -650,6 +770,10 @@ int __v4l2_async_nf_add_connection(struct v4l2_async_notifier *notifier,
if (ret)
goto unlock;
+ ret = v4l2_async_find_async_subdev(notifier, asc);
+ if (ret)
+ goto unlock;
+
list_add_tail(&asc->asc_list, ¬ifier->asc_head);
unlock:
@@ -797,7 +921,7 @@ int v4l2_async_register_subdev(struct v4l2_subdev *sd)
v4l2_async_nf_unbind_all_subdevs(subdev_notifier, false);
if (sd->asd)
- v4l2_async_nf_call_unbind(notifier, sd, sd->asd);
+ v4l2_async_unbind_subdev_one(notifier, sd, true);
v4l2_async_cleanup(sd);
mutex_unlock(&list_lock);
@@ -820,10 +944,12 @@ void v4l2_async_unregister_subdev(struct v4l2_subdev *sd)
if (sd->asd) {
struct v4l2_async_notifier *notifier = sd->notifier;
+ struct v4l2_async_connection *asc;
- list_add(&sd->asd->waiting_list, ¬ifier->waiting_head);
+ list_for_each_entry(asc, &sd->asd->asc_head, asc_subdev_list)
+ list_add(&asc->waiting_list, ¬ifier->waiting_head);
- v4l2_async_nf_call_unbind(notifier, sd, sd->asd);
+ v4l2_async_unbind_subdev_one(notifier, sd, true);
}
v4l2_async_cleanup(sd);
@@ -8,6 +8,7 @@
#ifndef V4L2_ASYNC_H
#define V4L2_ASYNC_H
+#include <linux/kref.h>
#include <linux/list.h>
#include <linux/mutex.h>
@@ -63,24 +64,47 @@ struct v4l2_async_match {
};
/**
- * struct v4l2_async_connection - sub-device descriptor, as known to a bridge
+ * struct v4l2_async_subdev - sub-device descriptor
+ *
+ * @kref: kref for refcounting the subdev
+ * @asd_list: Entry in the list of async sub-devices
+ * @subdev_list: used to link struct v4l2_async_subdev objects, waiting to be
+ * probed, to a notifier->waiting_head list
+ * @asc_head: head for struct v4l2_async_connection.asd_list list
+ * @registered: whether the sub-device has been registered
+ */
+struct v4l2_async_subdev {
+ struct kref kref;
+ struct list_head asd_list;
+ struct list_head subdev_list;
+ struct list_head asc_head;
+ bool registered;
+};
+
+/**
+ * struct v4l2_async_connection - sub-device connection descriptor, as known to
+ * a bridge
*
+ * @asd: the async sub-device related to this connection
* @match: struct of match type and per-bus type matching data sets
* @asc_list: used to add struct v4l2_async_connection objects to the
* master notifier @asc_list
* @waiting_list: used to link struct v4l2_async_connection objects, waiting to be
* probed, to a notifier->waiting list
+ * @asc_subdev_list: entry in struct v4l2_async_subdev.asc_head list
*
- * When this struct is used as a member in a driver specific struct,
- * the driver specific struct shall contain the &struct
- * v4l2_async_connection as its first member.
+ * When this struct is used as a member in a driver specific struct, the driver
+ * specific struct shall contain the &struct v4l2_async_connection as its first
+ * member.
*/
struct v4l2_async_connection {
+ struct v4l2_async_subdev *asd;
struct v4l2_async_match match;
/* v4l2-async core private: not to be used by drivers */
struct list_head asc_list;
struct list_head waiting_list;
+ struct list_head asc_subdev_list;
};
/**
@@ -1063,7 +1063,7 @@ struct v4l2_subdev {
struct device *dev;
struct fwnode_handle *fwnode;
struct list_head async_list;
- struct v4l2_async_connection *asd;
+ struct v4l2_async_subdev *asd;
struct v4l2_async_notifier *notifier;
struct v4l2_async_notifier *subdev_notifier;
struct v4l2_subdev_platform_data *pdata;
When the v4l2-async framework was introduced, the use case for it was to connect a camera sensor with a parallel receiver. Both tended to be rather simple devices with a single connection between them. The framework has been since improved in multiple ways but there are limitations that have remained, for instance the assumption an async sub-device is connected towards a single notifier and via a single link only. This patch adds an object that represents the device while an earlier patch in the series re-purposed the old struct v4l2_async_subdev as the connection. Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com> --- drivers/media/v4l2-core/v4l2-async.c | 168 +++++++++++++++++++++++---- include/media/v4l2-async.h | 32 ++++- include/media/v4l2-subdev.h | 2 +- 3 files changed, 176 insertions(+), 26 deletions(-)