diff options
author | Guennadi Liakhovetski <g.liakhovetski@gmx.de> | 2012-12-26 19:44:11 +0400 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@redhat.com> | 2013-06-21 23:34:17 +0400 |
commit | e09da11da49c6fd625be52d8b60bbbbe225a9db6 (patch) | |
tree | 80442f04ef06f338578c43e7eea26bca759c9104 /drivers/media | |
parent | 9aea470b399d797e88be08985c489855759c6c60 (diff) | |
download | linux-e09da11da49c6fd625be52d8b60bbbbe225a9db6.tar.xz |
[media] soc-camera: add V4L2-async support
Add support for asynchronous subdevice probing, using the v4l2-async API.
The legacy synchronous mode is still supported too, which allows to
gradually update drivers and platforms. The selected approach adds a
notifier for each struct soc_camera_device instance, i.e. for each video
device node, even when there are multiple such instances registered with a
single soc-camera host simultaneously.
Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
Acked-by: Hans Verkuil <hans.verkuil@cisco.com>
Acked-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/media')
-rw-r--r-- | drivers/media/platform/soc_camera/soc_camera.c | 527 |
1 files changed, 442 insertions, 85 deletions
diff --git a/drivers/media/platform/soc_camera/soc_camera.c b/drivers/media/platform/soc_camera/soc_camera.c index fe3930e882e1..2e47b5127d4b 100644 --- a/drivers/media/platform/soc_camera/soc_camera.c +++ b/drivers/media/platform/soc_camera/soc_camera.c @@ -21,22 +21,23 @@ #include <linux/i2c.h> #include <linux/init.h> #include <linux/list.h> -#include <linux/mutex.h> #include <linux/module.h> +#include <linux/mutex.h> #include <linux/platform_device.h> +#include <linux/pm_runtime.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> -#include <linux/pm_runtime.h> #include <linux/vmalloc.h> #include <media/soc_camera.h> +#include <media/soc_mediabus.h> +#include <media/v4l2-async.h> #include <media/v4l2-clk.h> #include <media/v4l2-common.h> #include <media/v4l2-ioctl.h> #include <media/v4l2-dev.h> #include <media/videobuf-core.h> #include <media/videobuf2-core.h> -#include <media/soc_mediabus.h> /* Default to VGA resolution */ #define DEFAULT_WIDTH 640 @@ -47,23 +48,39 @@ (icd)->vb_vidq.streaming : \ vb2_is_streaming(&(icd)->vb2_vidq)) +#define MAP_MAX_NUM 32 +static DECLARE_BITMAP(device_map, MAP_MAX_NUM); static LIST_HEAD(hosts); static LIST_HEAD(devices); -static DEFINE_MUTEX(list_lock); /* Protects the list of hosts */ +/* + * Protects lists and bitmaps of hosts and devices. + * Lock nesting: Ok to take ->host_lock under list_lock. + */ +static DEFINE_MUTEX(list_lock); + +struct soc_camera_async_client { + struct v4l2_async_subdev *sensor; + struct v4l2_async_notifier notifier; + struct platform_device *pdev; + struct list_head list; /* needed for clean up */ +}; + +static int soc_camera_video_start(struct soc_camera_device *icd); +static int video_dev_create(struct soc_camera_device *icd); int soc_camera_power_on(struct device *dev, struct soc_camera_subdev_desc *ssdd, struct v4l2_clk *clk) { int ret = clk ? v4l2_clk_enable(clk) : 0; if (ret < 0) { - dev_err(dev, "Cannot enable clock\n"); + dev_err(dev, "Cannot enable clock: %d\n", ret); return ret; } ret = regulator_bulk_enable(ssdd->num_regulators, ssdd->regulators); if (ret < 0) { dev_err(dev, "Cannot enable regulators\n"); - goto eregenable;; + goto eregenable; } if (ssdd->power) { @@ -117,6 +134,14 @@ int soc_camera_power_off(struct device *dev, struct soc_camera_subdev_desc *ssdd } EXPORT_SYMBOL(soc_camera_power_off); +int soc_camera_power_init(struct device *dev, struct soc_camera_subdev_desc *ssdd) +{ + + return devm_regulator_bulk_get(dev, ssdd->num_regulators, + ssdd->regulators); +} +EXPORT_SYMBOL(soc_camera_power_init); + static int __soc_camera_power_on(struct soc_camera_device *icd) { struct v4l2_subdev *sd = soc_camera_to_subdev(icd); @@ -532,7 +557,9 @@ static int soc_camera_add_device(struct soc_camera_device *icd) return -EBUSY; if (!icd->clk) { + mutex_lock(&ici->clk_lock); ret = ici->ops->clock_start(ici); + mutex_unlock(&ici->clk_lock); if (ret < 0) return ret; } @@ -548,8 +575,11 @@ static int soc_camera_add_device(struct soc_camera_device *icd) return 0; eadd: - if (!icd->clk) + if (!icd->clk) { + mutex_lock(&ici->clk_lock); ici->ops->clock_stop(ici); + mutex_unlock(&ici->clk_lock); + } return ret; } @@ -562,8 +592,11 @@ static void soc_camera_remove_device(struct soc_camera_device *icd) if (ici->ops->remove) ici->ops->remove(icd); - if (!icd->clk) + if (!icd->clk) { + mutex_lock(&ici->clk_lock); ici->ops->clock_stop(ici); + mutex_unlock(&ici->clk_lock); + } ici->icd = NULL; } @@ -672,8 +705,8 @@ static int soc_camera_open(struct file *file) return 0; /* - * First four errors are entered with the .host_lock held - * and use_count == 1 + * All errors are entered with the .host_lock held, first four also + * with use_count == 1 */ einitvb: esfmt: @@ -1098,7 +1131,8 @@ static int soc_camera_s_parm(struct file *file, void *fh, return -ENOIOCTLCMD; } -static int soc_camera_probe(struct soc_camera_device *icd); +static int soc_camera_probe(struct soc_camera_host *ici, + struct soc_camera_device *icd); /* So far this function cannot fail */ static void scan_add_host(struct soc_camera_host *ici) @@ -1107,12 +1141,20 @@ static void scan_add_host(struct soc_camera_host *ici) mutex_lock(&list_lock); - list_for_each_entry(icd, &devices, list) { + list_for_each_entry(icd, &devices, list) if (icd->iface == ici->nr) { + struct soc_camera_desc *sdesc = to_soc_camera_desc(icd); + struct soc_camera_subdev_desc *ssdd = &sdesc->subdev_desc; + + /* The camera could have been already on, try to reset */ + if (ssdd->reset) + ssdd->reset(icd->pdev); + icd->parent = ici->v4l2_dev.dev; - soc_camera_probe(icd); + + /* Ignore errors */ + soc_camera_probe(ici, icd); } - } mutex_unlock(&list_lock); } @@ -1125,6 +1167,7 @@ static int soc_camera_clk_enable(struct v4l2_clk *clk) { struct soc_camera_device *icd = clk->priv; struct soc_camera_host *ici; + int ret; if (!icd || !icd->parent) return -ENODEV; @@ -1138,7 +1181,10 @@ static int soc_camera_clk_enable(struct v4l2_clk *clk) * If a different client is currently being probed, the host will tell * you to go */ - return ici->ops->clock_start(ici); + mutex_lock(&ici->clk_lock); + ret = ici->ops->clock_start(ici); + mutex_unlock(&ici->clk_lock); + return ret; } static void soc_camera_clk_disable(struct v4l2_clk *clk) @@ -1151,7 +1197,9 @@ static void soc_camera_clk_disable(struct v4l2_clk *clk) ici = to_soc_camera_host(icd->parent); + mutex_lock(&ici->clk_lock); ici->ops->clock_stop(ici); + mutex_unlock(&ici->clk_lock); module_put(ici->ops->owner); } @@ -1168,18 +1216,117 @@ static const struct v4l2_clk_ops soc_camera_clk_ops = { .disable = soc_camera_clk_disable, }; +static int soc_camera_dyn_pdev(struct soc_camera_desc *sdesc, + struct soc_camera_async_client *sasc) +{ + struct platform_device *pdev; + int ret, i; + + mutex_lock(&list_lock); + i = find_first_zero_bit(device_map, MAP_MAX_NUM); + if (i < MAP_MAX_NUM) + set_bit(i, device_map); + mutex_unlock(&list_lock); + if (i >= MAP_MAX_NUM) + return -ENOMEM; + + pdev = platform_device_alloc("soc-camera-pdrv", i); + if (!pdev) + return -ENOMEM; + + ret = platform_device_add_data(pdev, sdesc, sizeof(*sdesc)); + if (ret < 0) { + platform_device_put(pdev); + return ret; + } + + sasc->pdev = pdev; + + return 0; +} + +static struct soc_camera_device *soc_camera_add_pdev(struct soc_camera_async_client *sasc) +{ + struct platform_device *pdev = sasc->pdev; + int ret; + + ret = platform_device_add(pdev); + if (ret < 0 || !pdev->dev.driver) + return NULL; + + return platform_get_drvdata(pdev); +} + +/* Locking: called with .host_lock held */ +static int soc_camera_probe_finish(struct soc_camera_device *icd) +{ + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct v4l2_mbus_framefmt mf; + int ret; + + sd->grp_id = soc_camera_grp_id(icd); + v4l2_set_subdev_hostdata(sd, icd); + + ret = v4l2_ctrl_add_handler(&icd->ctrl_handler, sd->ctrl_handler, NULL); + if (ret < 0) + return ret; + + ret = soc_camera_add_device(icd); + if (ret < 0) { + dev_err(icd->pdev, "Couldn't activate the camera: %d\n", ret); + return ret; + } + + /* At this point client .probe() should have run already */ + ret = soc_camera_init_user_formats(icd); + if (ret < 0) + goto eusrfmt; + + icd->field = V4L2_FIELD_ANY; + + ret = soc_camera_video_start(icd); + if (ret < 0) + goto evidstart; + + /* Try to improve our guess of a reasonable window format */ + if (!v4l2_subdev_call(sd, video, g_mbus_fmt, &mf)) { + icd->user_width = mf.width; + icd->user_height = mf.height; + icd->colorspace = mf.colorspace; + icd->field = mf.field; + } + soc_camera_remove_device(icd); + + return 0; + +evidstart: + soc_camera_free_user_formats(icd); +eusrfmt: + soc_camera_remove_device(icd); + + return ret; +} + #ifdef CONFIG_I2C_BOARDINFO -static int soc_camera_init_i2c(struct soc_camera_device *icd, +static int soc_camera_i2c_init(struct soc_camera_device *icd, struct soc_camera_desc *sdesc) { struct i2c_client *client; - struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct soc_camera_host *ici; struct soc_camera_host_desc *shd = &sdesc->host_desc; - struct i2c_adapter *adap = i2c_get_adapter(shd->i2c_adapter_id); + struct i2c_adapter *adap; struct v4l2_subdev *subdev; char clk_name[V4L2_SUBDEV_NAME_SIZE]; int ret; + /* First find out how we link the main client */ + if (icd->sasc) { + /* Async non-OF probing handled by the subdevice list */ + return -EPROBE_DEFER; + } + + ici = to_soc_camera_host(icd->parent); + adap = i2c_get_adapter(shd->i2c_adapter_id); if (!adap) { dev_err(icd->pdev, "Cannot get I2C adapter #%d. No driver?\n", shd->i2c_adapter_id); @@ -1212,42 +1359,202 @@ static int soc_camera_init_i2c(struct soc_camera_device *icd, return 0; ei2cnd: v4l2_clk_unregister(icd->clk); - icd->clk = NULL; eclkreg: + icd->clk = NULL; i2c_put_adapter(adap); return ret; } -static void soc_camera_free_i2c(struct soc_camera_device *icd) +static void soc_camera_i2c_free(struct soc_camera_device *icd) { struct i2c_client *client = to_i2c_client(to_soc_camera_control(icd)); - struct i2c_adapter *adap = client->adapter; + struct i2c_adapter *adap; icd->control = NULL; + if (icd->sasc) + return; + + adap = client->adapter; v4l2_device_unregister_subdev(i2c_get_clientdata(client)); i2c_unregister_device(client); i2c_put_adapter(adap); v4l2_clk_unregister(icd->clk); icd->clk = NULL; } + +/* + * V4L2 asynchronous notifier callbacks. They are all called under a v4l2-async + * internal global mutex, therefore cannot race against other asynchronous + * events. Until notifier->complete() (soc_camera_async_complete()) is called, + * the video device node is not registered and no V4L fops can occur. Unloading + * of the host driver also calls a v4l2-async function, so also there we're + * protected. + */ +static int soc_camera_async_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_subdev *asd) +{ + struct soc_camera_async_client *sasc = container_of(notifier, + struct soc_camera_async_client, notifier); + struct soc_camera_device *icd = platform_get_drvdata(sasc->pdev); + + if (asd == sasc->sensor && !WARN_ON(icd->control)) { + struct i2c_client *client = v4l2_get_subdevdata(sd); + + /* + * Only now we get subdevice-specific information like + * regulators, flags, callbacks, etc. + */ + if (client) { + struct soc_camera_desc *sdesc = to_soc_camera_desc(icd); + struct soc_camera_subdev_desc *ssdd = + soc_camera_i2c_to_desc(client); + if (ssdd) { + memcpy(&sdesc->subdev_desc, ssdd, + sizeof(sdesc->subdev_desc)); + if (ssdd->reset) + ssdd->reset(icd->pdev); + } + + icd->control = &client->dev; + } + } + + return 0; +} + +static void soc_camera_async_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_subdev *asd) +{ + struct soc_camera_async_client *sasc = container_of(notifier, + struct soc_camera_async_client, notifier); + struct soc_camera_device *icd = platform_get_drvdata(sasc->pdev); + + if (icd->clk) { + v4l2_clk_unregister(icd->clk); + icd->clk = NULL; + } +} + +static int soc_camera_async_complete(struct v4l2_async_notifier *notifier) +{ + struct soc_camera_async_client *sasc = container_of(notifier, + struct soc_camera_async_client, notifier); + struct soc_camera_device *icd = platform_get_drvdata(sasc->pdev); + + if (to_soc_camera_control(icd)) { + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + int ret; + + mutex_lock(&list_lock); + ret = soc_camera_probe(ici, icd); + mutex_unlock(&list_lock); + if (ret < 0) + return ret; + } + + return 0; +} + +static int scan_async_group(struct soc_camera_host *ici, + struct v4l2_async_subdev **asd, int size) +{ + struct soc_camera_async_subdev *sasd; + struct soc_camera_async_client *sasc; + struct soc_camera_device *icd; + struct soc_camera_desc sdesc = {.host_desc.bus_id = ici->nr,}; + char clk_name[V4L2_SUBDEV_NAME_SIZE]; + int ret, i; + + /* First look for a sensor */ + for (i = 0; i < size; i++) { + sasd = container_of(asd[i], struct soc_camera_async_subdev, asd); + if (sasd->role == SOCAM_SUBDEV_DATA_SOURCE) + break; + } + + if (i == size || asd[i]->bus_type != V4L2_ASYNC_BUS_I2C) { + /* All useless */ + dev_err(ici->v4l2_dev.dev, "No I2C data source found!\n"); + return -ENODEV; + } + + /* Or shall this be managed by the soc-camera device? */ + sasc = devm_kzalloc(ici->v4l2_dev.dev, sizeof(*sasc), GFP_KERNEL); + if (!sasc) + return -ENOMEM; + + /* HACK: just need a != NULL */ + sdesc.host_desc.board_info = ERR_PTR(-ENODATA); + + ret = soc_camera_dyn_pdev(&sdesc, sasc); + if (ret < 0) + return ret; + + sasc->sensor = &sasd->asd; + + icd = soc_camera_add_pdev(sasc); + if (!icd) { + platform_device_put(sasc->pdev); + return -ENOMEM; + } + + sasc->notifier.subdev = asd; + sasc->notifier.num_subdevs = size; + sasc->notifier.bound = soc_camera_async_bound; + sasc->notifier.unbind = soc_camera_async_unbind; + sasc->notifier.complete = soc_camera_async_complete; + + icd->sasc = sasc; + icd->parent = ici->v4l2_dev.dev; + + snprintf(clk_name, sizeof(clk_name), "%d-%04x", + sasd->asd.match.i2c.adapter_id, sasd->asd.match.i2c.address); + + icd->clk = v4l2_clk_register(&soc_camera_clk_ops, clk_name, "mclk", icd); + if (IS_ERR(icd->clk)) { + ret = PTR_ERR(icd->clk); + goto eclkreg; + } + + ret = v4l2_async_notifier_register(&ici->v4l2_dev, &sasc->notifier); + if (!ret) + return 0; + + v4l2_clk_unregister(icd->clk); +eclkreg: + icd->clk = NULL; + platform_device_unregister(sasc->pdev); + dev_err(ici->v4l2_dev.dev, "group probe failed: %d\n", ret); + + return ret; +} + +static void scan_async_host(struct soc_camera_host *ici) +{ + struct v4l2_async_subdev **asd; + int j; + + for (j = 0, asd = ici->asd; ici->asd_sizes[j]; j++) { + scan_async_group(ici, asd, ici->asd_sizes[j]); + asd += ici->asd_sizes[j]; + } +} #else -#define soc_camera_init_i2c(icd, sdesc) (-ENODEV) -#define soc_camera_free_i2c(icd) do {} while (0) +#define soc_camera_i2c_init(icd, sdesc) (-ENODEV) +#define soc_camera_i2c_free(icd) do {} while (0) +#define scan_async_host(ici) do {} while (0) #endif -static int soc_camera_video_start(struct soc_camera_device *icd); -static int video_dev_create(struct soc_camera_device *icd); /* Called during host-driver probe */ -static int soc_camera_probe(struct soc_camera_device *icd) +static int soc_camera_probe(struct soc_camera_host *ici, + struct soc_camera_device *icd) { - struct soc_camera_host *ici = to_soc_camera_host(icd->parent); struct soc_camera_desc *sdesc = to_soc_camera_desc(icd); struct soc_camera_host_desc *shd = &sdesc->host_desc; - struct soc_camera_subdev_desc *ssdd = &sdesc->subdev_desc; struct device *control = NULL; - struct v4l2_subdev *sd; - struct v4l2_mbus_framefmt mf; int ret; dev_info(icd->pdev, "Probing %s\n", dev_name(icd->pdev)); @@ -1263,10 +1570,6 @@ static int soc_camera_probe(struct soc_camera_device *icd) if (ret < 0) return ret; - /* The camera could have been already on, try to reset */ - if (ssdd->reset) - ssdd->reset(icd->pdev); - /* Must have icd->vdev before registering the device */ ret = video_dev_create(icd); if (ret < 0) @@ -1277,18 +1580,19 @@ static int soc_camera_probe(struct soc_camera_device *icd) * itself is protected against concurrent open() calls, but we also have * to protect our data also during client probing. */ - mutex_lock(&ici->host_lock); /* Non-i2c cameras, e.g., soc_camera_platform, have no board_info */ if (shd->board_info) { - ret = soc_camera_init_i2c(icd, sdesc); - if (ret < 0) + ret = soc_camera_i2c_init(icd, sdesc); + if (ret < 0 && ret != -EPROBE_DEFER) goto eadd; } else if (!shd->add_device || !shd->del_device) { ret = -EINVAL; goto eadd; } else { + mutex_lock(&ici->clk_lock); ret = ici->ops->clock_start(ici); + mutex_unlock(&ici->clk_lock); if (ret < 0) goto eadd; @@ -1312,57 +1616,33 @@ static int soc_camera_probe(struct soc_camera_device *icd) } } - sd = soc_camera_to_subdev(icd); - sd->grp_id = soc_camera_grp_id(icd); - v4l2_set_subdev_hostdata(sd, icd); - - ret = v4l2_ctrl_add_handler(&icd->ctrl_handler, sd->ctrl_handler, NULL); - if (ret < 0) - goto ectrl; - - /* At this point client .probe() should have run already */ - ret = soc_camera_init_user_formats(icd); - if (ret < 0) - goto eiufmt; - - icd->field = V4L2_FIELD_ANY; - - ret = soc_camera_video_start(icd); - if (ret < 0) - goto evidstart; - - /* Try to improve our guess of a reasonable window format */ - if (!v4l2_subdev_call(sd, video, g_mbus_fmt, &mf)) { - icd->user_width = mf.width; - icd->user_height = mf.height; - icd->colorspace = mf.colorspace; - icd->field = mf.field; - } - - if (!shd->board_info) - ici->ops->clock_stop(ici); - + mutex_lock(&ici->host_lock); + ret = soc_camera_probe_finish(icd); mutex_unlock(&ici->host_lock); + if (ret < 0) + goto efinish; return 0; -evidstart: - soc_camera_free_user_formats(icd); -eiufmt: -ectrl: +efinish: if (shd->board_info) { - soc_camera_free_i2c(icd); + soc_camera_i2c_free(icd); } else { shd->del_device(icd); module_put(control->driver->owner); enodrv: eadddev: + mutex_lock(&ici->clk_lock); ici->ops->clock_stop(ici); + mutex_unlock(&ici->clk_lock); } eadd: video_device_release(icd->vdev); icd->vdev = NULL; - mutex_unlock(&ici->host_lock); + if (icd->vdev) { + video_device_release(icd->vdev); + icd->vdev = NULL; + } evdc: v4l2_ctrl_handler_free(&icd->ctrl_handler); return ret; @@ -1370,15 +1650,15 @@ evdc: /* * This is called on device_unregister, which only means we have to disconnect - * from the host, but not remove ourselves from the device list + * from the host, but not remove ourselves from the device list. With + * asynchronous client probing this can also be called without + * soc_camera_probe_finish() having run. Careful with clean up. */ static int soc_camera_remove(struct soc_camera_device *icd) { struct soc_camera_desc *sdesc = to_soc_camera_desc(icd); struct video_device *vdev = icd->vdev; - BUG_ON(!icd->parent); - v4l2_ctrl_handler_free(&icd->ctrl_handler); if (vdev) { video_unregister_device(vdev); @@ -1386,15 +1666,27 @@ static int soc_camera_remove(struct soc_camera_device *icd) } if (sdesc->host_desc.board_info) { - soc_camera_free_i2c(icd); + soc_camera_i2c_free(icd); } else { - struct device_driver *drv = to_soc_camera_control(icd)->driver; + struct device *dev = to_soc_camera_control(icd); + struct device_driver *drv = dev ? dev->driver : NULL; if (drv) { sdesc->host_desc.del_device(icd); module_put(drv->owner); } } - soc_camera_free_user_formats(icd); + + if (icd->num_user_formats) + soc_camera_free_user_formats(icd); + + if (icd->clk) { + /* For the synchronous case */ + v4l2_clk_unregister(icd->clk); + icd->clk = NULL; + } + + if (icd->sasc) + platform_device_unregister(icd->sasc->pdev); return 0; } @@ -1505,7 +1797,18 @@ int soc_camera_host_register(struct soc_camera_host *ici) mutex_unlock(&list_lock); mutex_init(&ici->host_lock); - scan_add_host(ici); + mutex_init(&ici->clk_lock); + + if (ici->asd_sizes) + /* + * No OF, host with a list of subdevices. Don't try to mix + * modes by initialising some groups statically and some + * dynamically! + */ + scan_async_host(ici); + else + /* Legacy: static platform devices from board data */ + scan_add_host(ici); return 0; @@ -1518,13 +1821,30 @@ EXPORT_SYMBOL(soc_camera_host_register); /* Unregister all clients! */ void soc_camera_host_unregister(struct soc_camera_host *ici) { - struct soc_camera_device *icd; + struct soc_camera_device *icd, *tmp; + struct soc_camera_async_client *sasc; + LIST_HEAD(notifiers); mutex_lock(&list_lock); - list_del(&ici->list); list_for_each_entry(icd, &devices, list) - if (icd->iface == ici->nr && to_soc_camera_control(icd)) + if (icd->iface == ici->nr && icd->sasc) { + /* as long as we hold the device, sasc won't be freed */ + get_device(icd->pdev); + list_add(&icd->sasc->list, ¬ifiers); + } + mutex_unlock(&list_lock); + + list_for_each_entry(sasc, ¬ifiers, list) { + /* Must call unlocked to avoid AB-BA dead-lock */ + v4l2_async_notifier_unregister(&sasc->notifier); + put_device(&sasc->pdev->dev); + } + + mutex_lock(&list_lock); + + list_for_each_entry_safe(icd, tmp, &devices, list) + if (icd->iface == ici->nr) soc_camera_remove(icd); mutex_unlock(&list_lock); @@ -1539,6 +1859,7 @@ static int soc_camera_device_register(struct soc_camera_device *icd) struct soc_camera_device *ix; int num = -1, i; + mutex_lock(&list_lock); for (i = 0; i < 256 && num < 0; i++) { num = i; /* Check if this index is available on this interface */ @@ -1550,18 +1871,34 @@ static int soc_camera_device_register(struct soc_camera_device *icd) } } - if (num < 0) + if (num < 0) { /* * ok, we have 256 cameras on this host... * man, stay reasonable... */ + mutex_unlock(&list_lock); return -ENOMEM; + } icd->devnum = num; icd->use_count = 0; icd->host_priv = NULL; + /* + * Dynamically allocated devices set the bit earlier, but it doesn't hurt setting + * it again + */ + i = to_platform_device(icd->pdev)->id; + if (i < 0) + /* One static (legacy) soc-camera platform device */ + i = 0; + if (i >= MAP_MAX_NUM) { + mutex_unlock(&list_lock); + return -EBUSY; + } + set_bit(i, device_map); list_add_tail(&icd->list, &devices); + mutex_unlock(&list_lock); return 0; } @@ -1655,6 +1992,12 @@ static int soc_camera_pdrv_probe(struct platform_device *pdev) if (!icd) return -ENOMEM; + /* + * In the asynchronous case ssdd->num_regulators == 0 yet, so, the below + * regulator allocation is a dummy. They will be really requested later + * in soc_camera_async_bind(). Also note, that in that case regulators + * are attached to the I2C device and not to the camera platform device. + */ ret = devm_regulator_bulk_get(&pdev->dev, ssdd->num_regulators, ssdd->regulators); if (ret < 0) @@ -1679,11 +2022,25 @@ static int soc_camera_pdrv_probe(struct platform_device *pdev) static int soc_camera_pdrv_remove(struct platform_device *pdev) { struct soc_camera_device *icd = platform_get_drvdata(pdev); + int i; if (!icd) return -EINVAL; - list_del(&icd->list); + i = pdev->id; + if (i < 0) + i = 0; + + /* + * In synchronous mode with static platform devices this is called in a + * loop from drivers/base/dd.c::driver_detach(), no parallel execution, + * no need to lock. In asynchronous case the caller - + * soc_camera_host_unregister() - already holds the lock + */ + if (test_bit(i, device_map)) { + clear_bit(i, device_map); + list_del(&icd->list); + } return 0; } |