Commit 28a12957 authored by Sakari Ailus's avatar Sakari Ailus Committed by Mauro Carvalho Chehab
Browse files

media: v4l: async: Allow multiple connections between entities



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 enables connecting a sub-device to one or more notifiers
simultaneously, with one or more connections per notifier. The notifier
information is moved from the sub-device to the connection and the
connections in sub-device are no longer a pointer but a linked list.

Signed-off-by: default avatarSakari 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: default avatarMauro Carvalho Chehab <mchehab@kernel.org>
parent c91fd7b7
Loading
Loading
Loading
Loading
+76 −73
Original line number Original line Diff line number Diff line
@@ -313,29 +313,43 @@ static int v4l2_async_match_notify(struct v4l2_async_notifier *notifier,
				   struct v4l2_async_connection *asc)
				   struct v4l2_async_connection *asc)
{
{
	struct v4l2_async_notifier *subdev_notifier;
	struct v4l2_async_notifier *subdev_notifier;
	bool registered = false;
	int ret;
	int ret;


	if (list_empty(&sd->asc_list)) {
		ret = v4l2_device_register_subdev(v4l2_dev, sd);
		ret = v4l2_device_register_subdev(v4l2_dev, sd);
		if (ret < 0)
		if (ret < 0)
			return ret;
			return ret;
		registered = true;
	}


	ret = v4l2_async_nf_call_bound(notifier, sd, asc);
	ret = v4l2_async_nf_call_bound(notifier, sd, asc);
	if (ret < 0)
	if (ret < 0) {
		if (asc->match.type == V4L2_ASYNC_MATCH_TYPE_FWNODE)
			dev_dbg(notifier_dev(notifier),
				"failed binding %pfw (%d)\n",
				asc->match.fwnode, ret);
		goto err_unregister_subdev;
		goto err_unregister_subdev;
	}


	if (registered) {
		/*
		/*
	 * Depending of the function of the entities involved, we may want to
		 * Depending of the function of the entities involved, we may
	 * create links between them (for example between a sensor and its lens
		 * want to create links between them (for example between a
	 * or between a sensor's source pad and the connected device's sink
		 * sensor and its lens or between a sensor's source pad and the
	 * pad).
		 * connected device's sink pad).
		 */
		 */
		ret = v4l2_async_create_ancillary_links(notifier, sd);
		ret = v4l2_async_create_ancillary_links(notifier, sd);
	if (ret)
		if (ret) {
			if (asc->match.type == V4L2_ASYNC_MATCH_TYPE_FWNODE)
				dev_dbg(notifier_dev(notifier),
					"failed creating links for %pfw (%d)\n",
					asc->match.fwnode, ret);
			goto err_call_unbind;
			goto err_call_unbind;
		}
	}


	sd->asd = asc;
	list_add(&asc->asc_subdev_entry, &sd->asc_list);
	sd->notifier = notifier;

	asc->sd = sd;
	asc->sd = sd;


	/* Move from the waiting list to notifier's done */
	/* Move from the waiting list to notifier's done */
@@ -362,8 +376,10 @@ static int v4l2_async_match_notify(struct v4l2_async_notifier *notifier,


err_call_unbind:
err_call_unbind:
	v4l2_async_nf_call_unbind(notifier, sd, asc);
	v4l2_async_nf_call_unbind(notifier, sd, asc);
	list_del(&asc->asc_subdev_entry);


err_unregister_subdev:
err_unregister_subdev:
	if (registered)
		v4l2_device_unregister_subdev(sd);
		v4l2_device_unregister_subdev(sd);


	return ret;
	return ret;
@@ -410,15 +426,16 @@ v4l2_async_nf_try_all_subdevs(struct v4l2_async_notifier *notifier)
	return 0;
	return 0;
}
}


static void v4l2_async_cleanup(struct v4l2_subdev *sd)
static void v4l2_async_unbind_subdev_one(struct v4l2_async_notifier *notifier,
					 struct v4l2_async_connection *asc)
{
{
	v4l2_device_unregister_subdev(sd);
	list_move_tail(&asc->asc_entry, &notifier->waiting_list);
	/*
	if (list_is_singular(&asc->asc_subdev_entry)) {
	 * Subdevice driver will reprobe and put the subdev back
		v4l2_async_nf_call_unbind(notifier, asc->sd, asc);
	 * onto the list
		v4l2_device_unregister_subdev(asc->sd);
	 */
		asc->sd = NULL;
	list_del_init(&sd->async_list);
	}
	sd->asd = NULL;
	list_del(&asc->asc_subdev_entry);
}
}


/* Unbind all sub-devices in the notifier tree. */
/* Unbind all sub-devices in the notifier tree. */
@@ -435,11 +452,7 @@ v4l2_async_nf_unbind_all_subdevs(struct v4l2_async_notifier *notifier)
		if (subdev_notifier)
		if (subdev_notifier)
			v4l2_async_nf_unbind_all_subdevs(subdev_notifier);
			v4l2_async_nf_unbind_all_subdevs(subdev_notifier);


		v4l2_async_nf_call_unbind(notifier, asc->sd, asc);
		v4l2_async_unbind_subdev_one(notifier, asc);
		v4l2_async_cleanup(asc->sd);
		list_move_tail(&asc->asc_entry, &notifier->waiting_list);
		list_move(&asc->sd->async_list, &subdev_list);
		asc->sd = NULL;
	}
	}


	notifier->parent = NULL;
	notifier->parent = NULL;
@@ -456,13 +469,9 @@ v4l2_async_nf_has_async_match_entry(struct v4l2_async_notifier *notifier,
		if (v4l2_async_match_equal(&asc->match, match))
		if (v4l2_async_match_equal(&asc->match, match))
			return true;
			return true;


	list_for_each_entry(asc, &notifier->done_list, asc_entry) {
	list_for_each_entry(asc, &notifier->done_list, asc_entry)
		if (WARN_ON(!asc->sd->asd))
			continue;

		if (v4l2_async_match_equal(&asc->match, match))
		if (v4l2_async_match_equal(&asc->match, match))
			return true;
			return true;
	}


	return false;
	return false;
}
}
@@ -642,16 +651,12 @@ static void __v4l2_async_nf_cleanup(struct v4l2_async_notifier *notifier)
	WARN_ON(!list_empty(&notifier->done_list));
	WARN_ON(!list_empty(&notifier->done_list));


	list_for_each_entry_safe(asc, tmp, &notifier->waiting_list, asc_entry) {
	list_for_each_entry_safe(asc, tmp, &notifier->waiting_list, asc_entry) {
		switch (asc->match.type) {
		case V4L2_ASYNC_MATCH_TYPE_FWNODE:
			fwnode_handle_put(asc->match.fwnode);
			break;
		default:
			break;
		}

		list_del(&asc->asc_entry);
		list_del(&asc->asc_entry);
		v4l2_async_nf_call_destroy(notifier, asc);
		v4l2_async_nf_call_destroy(notifier, asc);

		if (asc->match.type == V4L2_ASYNC_MATCH_TYPE_FWNODE)
			fwnode_handle_put(asc->match.fwnode);

		kfree(asc);
		kfree(asc);
	}
	}
}
}
@@ -666,7 +671,7 @@ void v4l2_async_nf_cleanup(struct v4l2_async_notifier *notifier)
}
}
EXPORT_SYMBOL_GPL(v4l2_async_nf_cleanup);
EXPORT_SYMBOL_GPL(v4l2_async_nf_cleanup);


static int __v4l2_async_nf_add_connection(struct v4l2_async_notifier *notifier,
static void __v4l2_async_nf_add_connection(struct v4l2_async_notifier *notifier,
					   struct v4l2_async_connection *asc)
					   struct v4l2_async_connection *asc)
{
{
	mutex_lock(&list_lock);
	mutex_lock(&list_lock);
@@ -674,8 +679,6 @@ static int __v4l2_async_nf_add_connection(struct v4l2_async_notifier *notifier,
	list_add_tail(&asc->asc_entry, &notifier->waiting_list);
	list_add_tail(&asc->asc_entry, &notifier->waiting_list);


	mutex_unlock(&list_lock);
	mutex_unlock(&list_lock);

	return 0;
}
}


struct v4l2_async_connection *
struct v4l2_async_connection *
@@ -684,21 +687,16 @@ __v4l2_async_nf_add_fwnode(struct v4l2_async_notifier *notifier,
			   unsigned int asc_struct_size)
			   unsigned int asc_struct_size)
{
{
	struct v4l2_async_connection *asc;
	struct v4l2_async_connection *asc;
	int ret;


	asc = kzalloc(asc_struct_size, GFP_KERNEL);
	asc = kzalloc(asc_struct_size, GFP_KERNEL);
	if (!asc)
	if (!asc)
		return ERR_PTR(-ENOMEM);
		return ERR_PTR(-ENOMEM);


	asc->notifier = notifier;
	asc->match.type = V4L2_ASYNC_MATCH_TYPE_FWNODE;
	asc->match.type = V4L2_ASYNC_MATCH_TYPE_FWNODE;
	asc->match.fwnode = fwnode_handle_get(fwnode);
	asc->match.fwnode = fwnode_handle_get(fwnode);


	ret = __v4l2_async_nf_add_connection(notifier, asc);
	__v4l2_async_nf_add_connection(notifier, asc);
	if (ret) {
		fwnode_handle_put(fwnode);
		kfree(asc);
		return ERR_PTR(ret);
	}


	return asc;
	return asc;
}
}
@@ -731,21 +729,17 @@ __v4l2_async_nf_add_i2c(struct v4l2_async_notifier *notifier, int adapter_id,
			unsigned short address, unsigned int asc_struct_size)
			unsigned short address, unsigned int asc_struct_size)
{
{
	struct v4l2_async_connection *asc;
	struct v4l2_async_connection *asc;
	int ret;


	asc = kzalloc(asc_struct_size, GFP_KERNEL);
	asc = kzalloc(asc_struct_size, GFP_KERNEL);
	if (!asc)
	if (!asc)
		return ERR_PTR(-ENOMEM);
		return ERR_PTR(-ENOMEM);


	asc->notifier = notifier;
	asc->match.type = V4L2_ASYNC_MATCH_TYPE_I2C;
	asc->match.type = V4L2_ASYNC_MATCH_TYPE_I2C;
	asc->match.i2c.adapter_id = adapter_id;
	asc->match.i2c.adapter_id = adapter_id;
	asc->match.i2c.address = address;
	asc->match.i2c.address = address;


	ret = __v4l2_async_nf_add_connection(notifier, asc);
	__v4l2_async_nf_add_connection(notifier, asc);
	if (ret) {
		kfree(asc);
		return ERR_PTR(ret);
	}


	return asc;
	return asc;
}
}
@@ -754,7 +748,11 @@ EXPORT_SYMBOL_GPL(__v4l2_async_nf_add_i2c);
struct v4l2_async_connection *
struct v4l2_async_connection *
v4l2_async_connection_unique(struct v4l2_subdev *sd)
v4l2_async_connection_unique(struct v4l2_subdev *sd)
{
{
	return sd->asd;
	if (!list_is_singular(&sd->asc_list))
		return NULL;

	return list_first_entry(&sd->asc_list,
				struct v4l2_async_connection, asc_subdev_entry);
}
}
EXPORT_SYMBOL_GPL(v4l2_async_connection_unique);
EXPORT_SYMBOL_GPL(v4l2_async_connection_unique);


@@ -762,8 +760,11 @@ int v4l2_async_register_subdev(struct v4l2_subdev *sd)
{
{
	struct v4l2_async_notifier *subdev_notifier;
	struct v4l2_async_notifier *subdev_notifier;
	struct v4l2_async_notifier *notifier;
	struct v4l2_async_notifier *notifier;
	struct v4l2_async_connection *asc;
	int ret;
	int ret;


	INIT_LIST_HEAD(&sd->asc_list);

	/*
	/*
	 * No reference taken. The reference is held by the device (struct
	 * No reference taken. The reference is held by the device (struct
	 * v4l2_subdev.dev), and async sub-device does not exist independently
	 * v4l2_subdev.dev), and async sub-device does not exist independently
@@ -786,7 +787,6 @@ int v4l2_async_register_subdev(struct v4l2_subdev *sd)
	list_for_each_entry(notifier, &notifier_list, notifier_entry) {
	list_for_each_entry(notifier, &notifier_list, notifier_entry) {
		struct v4l2_device *v4l2_dev =
		struct v4l2_device *v4l2_dev =
			v4l2_async_nf_find_v4l2_dev(notifier);
			v4l2_async_nf_find_v4l2_dev(notifier);
		struct v4l2_async_connection *asc;


		if (!v4l2_dev)
		if (!v4l2_dev)
			continue;
			continue;
@@ -823,11 +823,8 @@ int v4l2_async_register_subdev(struct v4l2_subdev *sd)
	if (subdev_notifier)
	if (subdev_notifier)
		v4l2_async_nf_unbind_all_subdevs(subdev_notifier);
		v4l2_async_nf_unbind_all_subdevs(subdev_notifier);


	if (sd->asd) {
	if (asc)
		v4l2_async_nf_call_unbind(notifier, sd, sd->asd);
		v4l2_async_unbind_subdev_one(notifier, asc);
		sd->asd->sd = NULL;
	}
	v4l2_async_cleanup(sd);


	mutex_unlock(&list_lock);
	mutex_unlock(&list_lock);


@@ -837,6 +834,8 @@ EXPORT_SYMBOL(v4l2_async_register_subdev);


void v4l2_async_unregister_subdev(struct v4l2_subdev *sd)
void v4l2_async_unregister_subdev(struct v4l2_subdev *sd)
{
{
	struct v4l2_async_connection *asc, *asc_tmp;

	if (!sd->async_list.next)
	if (!sd->async_list.next)
		return;
		return;


@@ -849,15 +848,19 @@ void v4l2_async_unregister_subdev(struct v4l2_subdev *sd)
	kfree(sd->subdev_notifier);
	kfree(sd->subdev_notifier);
	sd->subdev_notifier = NULL;
	sd->subdev_notifier = NULL;


	if (sd->asd) {
	if (sd->asc_list.next) {
		struct v4l2_async_notifier *notifier = sd->notifier;
		list_for_each_entry_safe(asc, asc_tmp, &sd->asc_list,
					 asc_subdev_entry) {
			list_move(&asc->asc_entry,
				  &asc->notifier->waiting_list);


		list_move(&sd->asd->asc_entry, &notifier->waiting_list);
			v4l2_async_unbind_subdev_one(asc->notifier, asc);
		v4l2_async_nf_call_unbind(notifier, sd, sd->asd);
			list_del(&asc->asc_subdev_entry);
		sd->asd->sd = NULL;
		}
	}
	}


	v4l2_async_cleanup(sd);
	list_del(&sd->async_list);
	sd->async_list.next = NULL;


	mutex_unlock(&list_lock);
	mutex_unlock(&list_lock);
}
}
+11 −6
Original line number Original line Diff line number Diff line
@@ -62,27 +62,32 @@ struct v4l2_async_match_desc {
};
};


/**
/**
 * struct v4l2_async_connection - connection descriptor, as known to a bridge
 * struct v4l2_async_connection - sub-device connection descriptor, as known to
 *				  a bridge
 *
 *
 * @match:	struct of match type and per-bus type matching data sets
 * @match:	struct of match type and per-bus type matching data sets
 * @notifier:	the async notifier the connection is related to
 * @asc_entry:	used to add struct v4l2_async_connection objects to the
 * @asc_entry:	used to add struct v4l2_async_connection objects to the
 *		notifier @waiting_list or @done_list
 *		notifier @waiting_list or @done_list
 * @asc_subdev_entry:	entry in struct v4l2_async_subdev.asc_list list
 * @sd:		the related sub-device
 * @sd:		the related sub-device
 *
 *
 * When this struct is used as a member in a driver specific struct,
 * When this struct is used as a member in a driver specific struct, the driver
 * the driver specific struct shall contain the &struct
 * specific struct shall contain the &struct v4l2_async_connection as its first
 * v4l2_async_connection as its first member.
 * member.
 */
 */
struct v4l2_async_connection {
struct v4l2_async_connection {
	struct v4l2_async_match_desc match;
	struct v4l2_async_match_desc match;
	struct v4l2_async_notifier *notifier;
	struct list_head asc_entry;
	struct list_head asc_entry;
	struct list_head asc_subdev_entry;
	struct v4l2_subdev *sd;
	struct v4l2_subdev *sd;
};
};


/**
/**
 * struct v4l2_async_notifier_operations - Asynchronous V4L2 notifier operations
 * struct v4l2_async_notifier_operations - Asynchronous V4L2 notifier operations
 * @bound:	a subdevice driver has successfully probed one of the subdevices
 * @bound:	a sub-device has been bound by the given connection
 * @complete:	All subdevices have been probed successfully. The complete
 * @complete:	All connections have been bound successfully. The complete
 *		callback is only executed for the root notifier.
 *		callback is only executed for the root notifier.
 * @unbind:	a subdevice is leaving
 * @unbind:	a subdevice is leaving
 * @destroy:	the asc is about to be freed
 * @destroy:	the asc is about to be freed
+3 −4
Original line number Original line Diff line number Diff line
@@ -1022,10 +1022,10 @@ struct v4l2_subdev_platform_data {
 *	    either dev->of_node->fwnode or dev->fwnode (whichever is non-NULL).
 *	    either dev->of_node->fwnode or dev->fwnode (whichever is non-NULL).
 * @async_list: Links this subdev to a global subdev_list or
 * @async_list: Links this subdev to a global subdev_list or
 *		@notifier->done_list list.
 *		@notifier->done_list list.
 * @asd: Pointer to respective &struct v4l2_async_connection.
 * @notifier: Pointer to the managing notifier.
 * @subdev_notifier: A sub-device notifier implicitly registered for the sub-
 * @subdev_notifier: A sub-device notifier implicitly registered for the sub-
 *		     device using v4l2_async_register_subdev_sensor().
 *		     device using v4l2_async_register_subdev_sensor().
 * @asc_list: Async connection list, of &struct
 *	      v4l2_async_connection.subdev_entry.
 * @pdata: common part of subdevice platform data
 * @pdata: common part of subdevice platform data
 * @state_lock: A pointer to a lock used for all the subdev's states, set by the
 * @state_lock: A pointer to a lock used for all the subdev's states, set by the
 *		driver. This is	optional. If NULL, each state instance will get
 *		driver. This is	optional. If NULL, each state instance will get
@@ -1065,9 +1065,8 @@ struct v4l2_subdev {
	struct device *dev;
	struct device *dev;
	struct fwnode_handle *fwnode;
	struct fwnode_handle *fwnode;
	struct list_head async_list;
	struct list_head async_list;
	struct v4l2_async_connection *asd;
	struct v4l2_async_notifier *notifier;
	struct v4l2_async_notifier *subdev_notifier;
	struct v4l2_async_notifier *subdev_notifier;
	struct list_head asc_list;
	struct v4l2_subdev_platform_data *pdata;
	struct v4l2_subdev_platform_data *pdata;
	struct mutex *state_lock;
	struct mutex *state_lock;