diff options
Diffstat (limited to 'drivers/media/platform')
-rw-r--r-- | drivers/media/platform/soc_camera/soc_camera.c | 162 | ||||
-rw-r--r-- | drivers/media/platform/soc_camera/soc_camera_platform.c | 2 |
2 files changed, 126 insertions, 38 deletions
diff --git a/drivers/media/platform/soc_camera/soc_camera.c b/drivers/media/platform/soc_camera/soc_camera.c index fa8a728b7a5d..fe3930e882e1 100644 --- a/drivers/media/platform/soc_camera/soc_camera.c +++ b/drivers/media/platform/soc_camera/soc_camera.c @@ -30,6 +30,7 @@ #include <linux/vmalloc.h> #include <media/soc_camera.h> +#include <media/v4l2-clk.h> #include <media/v4l2-common.h> #include <media/v4l2-ioctl.h> #include <media/v4l2-dev.h> @@ -50,13 +51,19 @@ static LIST_HEAD(hosts); static LIST_HEAD(devices); static DEFINE_MUTEX(list_lock); /* Protects the list of hosts */ -int soc_camera_power_on(struct device *dev, struct soc_camera_subdev_desc *ssdd) +int soc_camera_power_on(struct device *dev, struct soc_camera_subdev_desc *ssdd, + struct v4l2_clk *clk) { - int ret = regulator_bulk_enable(ssdd->num_regulators, + int ret = clk ? v4l2_clk_enable(clk) : 0; + if (ret < 0) { + dev_err(dev, "Cannot enable clock\n"); + return ret; + } + ret = regulator_bulk_enable(ssdd->num_regulators, ssdd->regulators); if (ret < 0) { dev_err(dev, "Cannot enable regulators\n"); - return ret; + goto eregenable;; } if (ssdd->power) { @@ -64,16 +71,25 @@ int soc_camera_power_on(struct device *dev, struct soc_camera_subdev_desc *ssdd) if (ret < 0) { dev_err(dev, "Platform failed to power-on the camera.\n"); - regulator_bulk_disable(ssdd->num_regulators, - ssdd->regulators); + goto epwron; } } + return 0; + +epwron: + regulator_bulk_disable(ssdd->num_regulators, + ssdd->regulators); +eregenable: + if (clk) + v4l2_clk_disable(clk); + return ret; } EXPORT_SYMBOL(soc_camera_power_on); -int soc_camera_power_off(struct device *dev, struct soc_camera_subdev_desc *ssdd) +int soc_camera_power_off(struct device *dev, struct soc_camera_subdev_desc *ssdd, + struct v4l2_clk *clk) { int ret = 0; int err; @@ -94,6 +110,9 @@ int soc_camera_power_off(struct device *dev, struct soc_camera_subdev_desc *ssdd ret = ret ? : err; } + if (clk) + v4l2_clk_disable(clk); + return ret; } EXPORT_SYMBOL(soc_camera_power_off); @@ -512,9 +531,11 @@ static int soc_camera_add_device(struct soc_camera_device *icd) if (ici->icd) return -EBUSY; - ret = ici->ops->clock_start(ici); - if (ret < 0) - return ret; + if (!icd->clk) { + ret = ici->ops->clock_start(ici); + if (ret < 0) + return ret; + } if (ici->ops->add) { ret = ici->ops->add(icd); @@ -527,7 +548,8 @@ static int soc_camera_add_device(struct soc_camera_device *icd) return 0; eadd: - ici->ops->clock_stop(ici); + if (!icd->clk) + ici->ops->clock_stop(ici); return ret; } @@ -540,7 +562,8 @@ static void soc_camera_remove_device(struct soc_camera_device *icd) if (ici->ops->remove) ici->ops->remove(icd); - ici->ops->clock_stop(ici); + if (!icd->clk) + ici->ops->clock_stop(ici); ici->icd = NULL; } @@ -1094,6 +1117,57 @@ static void scan_add_host(struct soc_camera_host *ici) mutex_unlock(&list_lock); } +/* + * It is invalid to call v4l2_clk_enable() after a successful probing + * asynchronously outside of V4L2 operations, i.e. with .host_lock not held. + */ +static int soc_camera_clk_enable(struct v4l2_clk *clk) +{ + struct soc_camera_device *icd = clk->priv; + struct soc_camera_host *ici; + + if (!icd || !icd->parent) + return -ENODEV; + + ici = to_soc_camera_host(icd->parent); + + if (!try_module_get(ici->ops->owner)) + return -ENODEV; + + /* + * If a different client is currently being probed, the host will tell + * you to go + */ + return ici->ops->clock_start(ici); +} + +static void soc_camera_clk_disable(struct v4l2_clk *clk) +{ + struct soc_camera_device *icd = clk->priv; + struct soc_camera_host *ici; + + if (!icd || !icd->parent) + return; + + ici = to_soc_camera_host(icd->parent); + + ici->ops->clock_stop(ici); + + module_put(ici->ops->owner); +} + +/* + * Eventually, it would be more logical to make the respective host the clock + * owner, but then we would have to copy this struct for each ici. Besides, it + * would introduce the circular dependency problem, unless we port all client + * drivers to release the clock, when not in use. + */ +static const struct v4l2_clk_ops soc_camera_clk_ops = { + .owner = THIS_MODULE, + .enable = soc_camera_clk_enable, + .disable = soc_camera_clk_disable, +}; + #ifdef CONFIG_I2C_BOARDINFO static int soc_camera_init_i2c(struct soc_camera_device *icd, struct soc_camera_desc *sdesc) @@ -1103,19 +1177,32 @@ static int soc_camera_init_i2c(struct soc_camera_device *icd, struct soc_camera_host_desc *shd = &sdesc->host_desc; struct i2c_adapter *adap = i2c_get_adapter(shd->i2c_adapter_id); struct v4l2_subdev *subdev; + char clk_name[V4L2_SUBDEV_NAME_SIZE]; + int ret; if (!adap) { dev_err(icd->pdev, "Cannot get I2C adapter #%d. No driver?\n", shd->i2c_adapter_id); - goto ei2cga; + return -ENODEV; } shd->board_info->platform_data = &sdesc->subdev_desc; + snprintf(clk_name, sizeof(clk_name), "%d-%04x", + shd->i2c_adapter_id, shd->board_info->addr); + + 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; + } + subdev = v4l2_i2c_new_subdev_board(&ici->v4l2_dev, adap, shd->board_info, NULL); - if (!subdev) + if (!subdev) { + ret = -ENODEV; goto ei2cnd; + } client = v4l2_get_subdevdata(subdev); @@ -1124,9 +1211,11 @@ static int soc_camera_init_i2c(struct soc_camera_device *icd, return 0; ei2cnd: + v4l2_clk_unregister(icd->clk); + icd->clk = NULL; +eclkreg: i2c_put_adapter(adap); -ei2cga: - return -ENODEV; + return ret; } static void soc_camera_free_i2c(struct soc_camera_device *icd) @@ -1139,6 +1228,8 @@ static void soc_camera_free_i2c(struct soc_camera_device *icd) v4l2_device_unregister_subdev(i2c_get_clientdata(client)); i2c_unregister_device(client); i2c_put_adapter(adap); + v4l2_clk_unregister(icd->clk); + icd->clk = NULL; } #else #define soc_camera_init_i2c(icd, sdesc) (-ENODEV) @@ -1176,26 +1267,31 @@ static int soc_camera_probe(struct soc_camera_device *icd) if (ssdd->reset) ssdd->reset(icd->pdev); - mutex_lock(&ici->host_lock); - ret = ici->ops->clock_start(ici); - mutex_unlock(&ici->host_lock); - if (ret < 0) - goto eadd; - /* Must have icd->vdev before registering the device */ ret = video_dev_create(icd); if (ret < 0) goto evdc; + /* + * ..._video_start() will create a device node, video_register_device() + * 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) - goto eadddev; + goto eadd; } else if (!shd->add_device || !shd->del_device) { ret = -EINVAL; - goto eadddev; + goto eadd; } else { + ret = ici->ops->clock_start(ici); + if (ret < 0) + goto eadd; + if (shd->module_name) ret = request_module(shd->module_name); @@ -1231,13 +1327,6 @@ static int soc_camera_probe(struct soc_camera_device *icd) icd->field = V4L2_FIELD_ANY; - /* - * ..._video_start() will create a device node, video_register_device() - * itself is protected against concurrent open() calls, but we also have - * to protect our data. - */ - mutex_lock(&ici->host_lock); - ret = soc_camera_video_start(icd); if (ret < 0) goto evidstart; @@ -1250,14 +1339,14 @@ static int soc_camera_probe(struct soc_camera_device *icd) icd->field = mf.field; } - ici->ops->clock_stop(ici); + if (!shd->board_info) + ici->ops->clock_stop(ici); mutex_unlock(&ici->host_lock); return 0; evidstart: - mutex_unlock(&ici->host_lock); soc_camera_free_user_formats(icd); eiufmt: ectrl: @@ -1266,16 +1355,15 @@ ectrl: } else { shd->del_device(icd); module_put(control->driver->owner); - } enodrv: eadddev: + ici->ops->clock_stop(ici); + } +eadd: video_device_release(icd->vdev); icd->vdev = NULL; -evdc: - mutex_lock(&ici->host_lock); - ici->ops->clock_stop(ici); mutex_unlock(&ici->host_lock); -eadd: +evdc: v4l2_ctrl_handler_free(&icd->ctrl_handler); return ret; } diff --git a/drivers/media/platform/soc_camera/soc_camera_platform.c b/drivers/media/platform/soc_camera/soc_camera_platform.c index bdf909d2ec35..ceaddfb85e49 100644 --- a/drivers/media/platform/soc_camera/soc_camera_platform.c +++ b/drivers/media/platform/soc_camera/soc_camera_platform.c @@ -54,7 +54,7 @@ static int soc_camera_platform_s_power(struct v4l2_subdev *sd, int on) { struct soc_camera_platform_info *p = v4l2_get_subdevdata(sd); - return soc_camera_set_power(p->icd->control, &p->icd->sdesc->subdev_desc, on); + return soc_camera_set_power(p->icd->control, &p->icd->sdesc->subdev_desc, NULL, on); } static struct v4l2_subdev_core_ops platform_subdev_core_ops = { |