diff options
Diffstat (limited to 'drivers/media/v4l2-core/v4l2-async.c')
-rw-r--r-- | drivers/media/v4l2-core/v4l2-async.c | 313 |
1 files changed, 224 insertions, 89 deletions
diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c index 2b08d03b251d..a6d91370838d 100644 --- a/drivers/media/v4l2-core/v4l2-async.c +++ b/drivers/media/v4l2-core/v4l2-async.c @@ -57,6 +57,7 @@ static bool match_i2c(struct v4l2_subdev *sd, struct v4l2_async_subdev *asd) { #if IS_ENABLED(CONFIG_I2C) struct i2c_client *client = i2c_verify_client(sd->dev); + return client && asd->match.i2c.adapter_id == client->adapter->nr && asd->match.i2c.address == client->addr; @@ -89,10 +90,11 @@ 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) +static struct v4l2_async_subdev * +v4l2_async_find_match(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd) { - bool (*match)(struct v4l2_subdev *, struct v4l2_async_subdev *); + bool (*match)(struct v4l2_subdev *sd, struct v4l2_async_subdev *asd); struct v4l2_async_subdev *asd; list_for_each_entry(asd, ¬ifier->waiting, list) { @@ -124,9 +126,34 @@ static struct v4l2_async_subdev *v4l2_async_find_match( return NULL; } +/* Compare two async sub-device descriptors for equivalence */ +static bool asd_equal(struct v4l2_async_subdev *asd_x, + struct v4l2_async_subdev *asd_y) +{ + if (asd_x->match_type != asd_y->match_type) + return false; + + switch (asd_x->match_type) { + case V4L2_ASYNC_MATCH_DEVNAME: + return strcmp(asd_x->match.device_name, + asd_y->match.device_name) == 0; + case V4L2_ASYNC_MATCH_I2C: + return asd_x->match.i2c.adapter_id == + asd_y->match.i2c.adapter_id && + asd_x->match.i2c.address == + asd_y->match.i2c.address; + case V4L2_ASYNC_MATCH_FWNODE: + return asd_x->match.fwnode == asd_y->match.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) +static struct v4l2_async_notifier * +v4l2_async_find_subdev_notifier(struct v4l2_subdev *sd) { struct v4l2_async_notifier *n; @@ -138,8 +165,8 @@ static struct v4l2_async_notifier *v4l2_async_find_subdev_notifier( } /* Get v4l2_device related to the notifier if one can be found. */ -static struct v4l2_device *v4l2_async_notifier_find_v4l2_dev( - struct v4l2_async_notifier *notifier) +static struct v4l2_device * +v4l2_async_notifier_find_v4l2_dev(struct v4l2_async_notifier *notifier) { while (notifier->parent) notifier = notifier->parent; @@ -150,8 +177,8 @@ static struct v4l2_device *v4l2_async_notifier_find_v4l2_dev( /* * Return true if all child sub-device notifiers are complete, false otherwise. */ -static bool v4l2_async_notifier_can_complete( - struct v4l2_async_notifier *notifier) +static bool +v4l2_async_notifier_can_complete(struct v4l2_async_notifier *notifier) { struct v4l2_subdev *sd; @@ -174,8 +201,8 @@ static bool v4l2_async_notifier_can_complete( * 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_notifier_try_complete( - struct v4l2_async_notifier *notifier) +static int +v4l2_async_notifier_try_complete(struct v4l2_async_notifier *notifier) { /* Quick check whether there are still more sub-devices here. */ if (!list_empty(¬ifier->waiting)) @@ -196,8 +223,8 @@ static int v4l2_async_notifier_try_complete( return v4l2_async_notifier_call_complete(notifier); } -static int v4l2_async_notifier_try_all_subdevs( - struct v4l2_async_notifier *notifier); +static int +v4l2_async_notifier_try_all_subdevs(struct v4l2_async_notifier *notifier); static int v4l2_async_match_notify(struct v4l2_async_notifier *notifier, struct v4l2_device *v4l2_dev, @@ -243,8 +270,8 @@ static int v4l2_async_match_notify(struct v4l2_async_notifier *notifier, } /* Test all async sub-devices in a notifier for a match. */ -static int v4l2_async_notifier_try_all_subdevs( - struct v4l2_async_notifier *notifier) +static int +v4l2_async_notifier_try_all_subdevs(struct v4l2_async_notifier *notifier) { struct v4l2_device *v4l2_dev = v4l2_async_notifier_find_v4l2_dev(notifier); @@ -281,14 +308,17 @@ again: 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 */ + /* + * 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_notifier_unbind_all_subdevs( - struct v4l2_async_notifier *notifier) +static void +v4l2_async_notifier_unbind_all_subdevs(struct v4l2_async_notifier *notifier) { struct v4l2_subdev *sd, *tmp; @@ -308,29 +338,23 @@ static void v4l2_async_notifier_unbind_all_subdevs( notifier->parent = NULL; } -/* See if an fwnode can be found in a notifier's lists. */ -static bool __v4l2_async_notifier_fwnode_has_async_subdev( - struct v4l2_async_notifier *notifier, struct fwnode_handle *fwnode) +/* See if an async sub-device can be found in a notifier's lists. */ +static bool +__v4l2_async_notifier_has_async_subdev(struct v4l2_async_notifier *notifier, + struct v4l2_async_subdev *asd) { - struct v4l2_async_subdev *asd; + struct v4l2_async_subdev *asd_y; struct v4l2_subdev *sd; - list_for_each_entry(asd, ¬ifier->waiting, list) { - if (asd->match_type != V4L2_ASYNC_MATCH_FWNODE) - continue; - - if (asd->match.fwnode == fwnode) + list_for_each_entry(asd_y, ¬ifier->waiting, list) + if (asd_equal(asd, asd_y)) return true; - } list_for_each_entry(sd, ¬ifier->done, async_list) { if (WARN_ON(!sd->asd)) continue; - if (sd->asd->match_type != V4L2_ASYNC_MATCH_FWNODE) - continue; - - if (sd->asd->match.fwnode == fwnode) + if (asd_equal(asd, sd->asd)) return true; } @@ -338,76 +362,91 @@ static bool __v4l2_async_notifier_fwnode_has_async_subdev( } /* - * Find out whether an async sub-device was set up for an fwnode already or + * Find out whether an async sub-device was set up already or * whether it exists in a given notifier before @this_index. + * If @this_index < 0, search the notifier's entire @asd_list. */ -static bool v4l2_async_notifier_fwnode_has_async_subdev( - struct v4l2_async_notifier *notifier, struct fwnode_handle *fwnode, - unsigned int this_index) +static bool +v4l2_async_notifier_has_async_subdev(struct v4l2_async_notifier *notifier, + struct v4l2_async_subdev *asd, + int this_index) { - unsigned int j; + struct v4l2_async_subdev *asd_y; + int j = 0; lockdep_assert_held(&list_lock); - /* Check that an fwnode is not being added more than once. */ - for (j = 0; j < this_index; j++) { - struct v4l2_async_subdev *asd = notifier->subdevs[this_index]; - struct v4l2_async_subdev *other_asd = notifier->subdevs[j]; - - if (other_asd->match_type == V4L2_ASYNC_MATCH_FWNODE && - asd->match.fwnode == - other_asd->match.fwnode) + /* Check that an asd is not being added more than once. */ + list_for_each_entry(asd_y, ¬ifier->asd_list, asd_list) { + if (this_index >= 0 && j++ >= this_index) + break; + if (asd_equal(asd, asd_y)) return true; } - /* Check than an fwnode did not exist in other notifiers. */ + /* Check that an asd does not exist in other notifiers. */ list_for_each_entry(notifier, ¬ifier_list, list) - if (__v4l2_async_notifier_fwnode_has_async_subdev( - notifier, fwnode)) + if (__v4l2_async_notifier_has_async_subdev(notifier, asd)) return true; return false; } -static int __v4l2_async_notifier_register(struct v4l2_async_notifier *notifier) +static int v4l2_async_notifier_asd_valid(struct v4l2_async_notifier *notifier, + struct v4l2_async_subdev *asd, + int this_index) { struct device *dev = notifier->v4l2_dev ? notifier->v4l2_dev->dev : NULL; - struct v4l2_async_subdev *asd; - int ret; - int i; - if (notifier->num_subdevs > V4L2_MAX_SUBDEVS) + if (!asd) + return -EINVAL; + + switch (asd->match_type) { + case V4L2_ASYNC_MATCH_CUSTOM: + case V4L2_ASYNC_MATCH_DEVNAME: + case V4L2_ASYNC_MATCH_I2C: + case V4L2_ASYNC_MATCH_FWNODE: + if (v4l2_async_notifier_has_async_subdev(notifier, asd, + this_index)) { + dev_dbg(dev, "subdev descriptor already listed in this or other notifiers\n"); + return -EEXIST; + } + break; + default: + dev_err(dev, "Invalid match type %u on %p\n", + asd->match_type, asd); return -EINVAL; + } + + return 0; +} + +void v4l2_async_notifier_init(struct v4l2_async_notifier *notifier) +{ + mutex_lock(&list_lock); + + INIT_LIST_HEAD(¬ifier->asd_list); + + mutex_unlock(&list_lock); +} +EXPORT_SYMBOL(v4l2_async_notifier_init); + +static int __v4l2_async_notifier_register(struct v4l2_async_notifier *notifier) +{ + struct v4l2_async_subdev *asd; + int ret, i = 0; INIT_LIST_HEAD(¬ifier->waiting); INIT_LIST_HEAD(¬ifier->done); mutex_lock(&list_lock); - for (i = 0; i < notifier->num_subdevs; i++) { - asd = notifier->subdevs[i]; - - switch (asd->match_type) { - case V4L2_ASYNC_MATCH_CUSTOM: - case V4L2_ASYNC_MATCH_DEVNAME: - case V4L2_ASYNC_MATCH_I2C: - break; - case V4L2_ASYNC_MATCH_FWNODE: - if (v4l2_async_notifier_fwnode_has_async_subdev( - notifier, asd->match.fwnode, i)) { - dev_err(dev, - "fwnode has already been registered or in notifier's subdev list\n"); - ret = -EEXIST; - goto err_unlock; - } - break; - default: - dev_err(dev, "Invalid match type %u on %p\n", - asd->match_type, asd); - ret = -EINVAL; + list_for_each_entry(asd, ¬ifier->asd_list, asd_list) { + ret = v4l2_async_notifier_asd_valid(notifier, asd, i++); + if (ret) goto err_unlock; - } + list_add_tail(&asd->list, ¬ifier->waiting); } @@ -474,8 +513,8 @@ int v4l2_async_subdev_notifier_register(struct v4l2_subdev *sd, } EXPORT_SYMBOL(v4l2_async_subdev_notifier_register); -static void __v4l2_async_notifier_unregister( - struct v4l2_async_notifier *notifier) +static void +__v4l2_async_notifier_unregister(struct v4l2_async_notifier *notifier) { if (!notifier || (!notifier->v4l2_dev && !notifier->sd)) return; @@ -498,36 +537,132 @@ void v4l2_async_notifier_unregister(struct v4l2_async_notifier *notifier) } EXPORT_SYMBOL(v4l2_async_notifier_unregister); -void v4l2_async_notifier_cleanup(struct v4l2_async_notifier *notifier) +static void __v4l2_async_notifier_cleanup(struct v4l2_async_notifier *notifier) { - unsigned int i; + struct v4l2_async_subdev *asd, *tmp; - if (!notifier || !notifier->max_subdevs) + if (!notifier) return; - for (i = 0; i < notifier->num_subdevs; i++) { - struct v4l2_async_subdev *asd = notifier->subdevs[i]; - + list_for_each_entry_safe(asd, tmp, ¬ifier->asd_list, asd_list) { switch (asd->match_type) { case V4L2_ASYNC_MATCH_FWNODE: fwnode_handle_put(asd->match.fwnode); break; default: - WARN_ON_ONCE(true); break; } + list_del(&asd->asd_list); kfree(asd); } +} - notifier->max_subdevs = 0; - notifier->num_subdevs = 0; +void v4l2_async_notifier_cleanup(struct v4l2_async_notifier *notifier) +{ + mutex_lock(&list_lock); + + __v4l2_async_notifier_cleanup(notifier); - kvfree(notifier->subdevs); - notifier->subdevs = NULL; + mutex_unlock(&list_lock); } EXPORT_SYMBOL_GPL(v4l2_async_notifier_cleanup); +int v4l2_async_notifier_add_subdev(struct v4l2_async_notifier *notifier, + struct v4l2_async_subdev *asd) +{ + int ret; + + mutex_lock(&list_lock); + + ret = v4l2_async_notifier_asd_valid(notifier, asd, -1); + if (ret) + goto unlock; + + list_add_tail(&asd->asd_list, ¬ifier->asd_list); + +unlock: + mutex_unlock(&list_lock); + return ret; +} +EXPORT_SYMBOL_GPL(v4l2_async_notifier_add_subdev); + +struct v4l2_async_subdev * +v4l2_async_notifier_add_fwnode_subdev(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_FWNODE; + asd->match.fwnode = fwnode; + + ret = v4l2_async_notifier_add_subdev(notifier, asd); + if (ret) { + kfree(asd); + return ERR_PTR(ret); + } + + return asd; +} +EXPORT_SYMBOL_GPL(v4l2_async_notifier_add_fwnode_subdev); + +struct v4l2_async_subdev * +v4l2_async_notifier_add_i2c_subdev(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_I2C; + asd->match.i2c.adapter_id = adapter_id; + asd->match.i2c.address = address; + + ret = v4l2_async_notifier_add_subdev(notifier, asd); + if (ret) { + kfree(asd); + return ERR_PTR(ret); + } + + return asd; +} +EXPORT_SYMBOL_GPL(v4l2_async_notifier_add_i2c_subdev); + +struct v4l2_async_subdev * +v4l2_async_notifier_add_devname_subdev(struct v4l2_async_notifier *notifier, + const char *device_name, + 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_DEVNAME; + asd->match.device_name = device_name; + + ret = v4l2_async_notifier_add_subdev(notifier, asd); + if (ret) { + kfree(asd); + return ERR_PTR(ret); + } + + return asd; +} +EXPORT_SYMBOL_GPL(v4l2_async_notifier_add_devname_subdev); + int v4l2_async_register_subdev(struct v4l2_subdev *sd) { struct v4l2_async_notifier *subdev_notifier; @@ -601,7 +736,7 @@ void v4l2_async_unregister_subdev(struct v4l2_subdev *sd) mutex_lock(&list_lock); __v4l2_async_notifier_unregister(sd->subdev_notifier); - v4l2_async_notifier_cleanup(sd->subdev_notifier); + __v4l2_async_notifier_cleanup(sd->subdev_notifier); kfree(sd->subdev_notifier); sd->subdev_notifier = NULL; |