diff options
Diffstat (limited to 'drivers/media/v4l2-core')
-rw-r--r-- | drivers/media/v4l2-core/v4l2-async.c | 38 | ||||
-rw-r--r-- | drivers/media/v4l2-core/v4l2-cci.c | 15 | ||||
-rw-r--r-- | drivers/media/v4l2-core/v4l2-common.c | 51 | ||||
-rw-r--r-- | drivers/media/v4l2-core/v4l2-ctrls-api.c | 52 | ||||
-rw-r--r-- | drivers/media/v4l2-core/v4l2-ctrls-core.c | 11 | ||||
-rw-r--r-- | drivers/media/v4l2-core/v4l2-ctrls-defs.c | 5 | ||||
-rw-r--r-- | drivers/media/v4l2-core/v4l2-dev.c | 38 | ||||
-rw-r--r-- | drivers/media/v4l2-core/v4l2-device.c | 12 | ||||
-rw-r--r-- | drivers/media/v4l2-core/v4l2-dv-timings.c | 199 | ||||
-rw-r--r-- | drivers/media/v4l2-core/v4l2-fwnode.c | 44 | ||||
-rw-r--r-- | drivers/media/v4l2-core/v4l2-i2c.c | 2 | ||||
-rw-r--r-- | drivers/media/v4l2-core/v4l2-ioctl.c | 94 | ||||
-rw-r--r-- | drivers/media/v4l2-core/v4l2-jpeg.c | 118 | ||||
-rw-r--r-- | drivers/media/v4l2-core/v4l2-mc.c | 25 | ||||
-rw-r--r-- | drivers/media/v4l2-core/v4l2-mem2mem.c | 25 | ||||
-rw-r--r-- | drivers/media/v4l2-core/v4l2-spi.c | 6 | ||||
-rw-r--r-- | drivers/media/v4l2-core/v4l2-subdev.c | 568 |
17 files changed, 983 insertions, 320 deletions
diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c index 3ec323bd528b..ee884a8221fb 100644 --- a/drivers/media/v4l2-core/v4l2-async.c +++ b/drivers/media/v4l2-core/v4l2-async.c @@ -316,19 +316,26 @@ v4l2_async_nf_try_all_subdevs(struct v4l2_async_notifier *notifier); static int v4l2_async_create_ancillary_links(struct v4l2_async_notifier *n, struct v4l2_subdev *sd) { - struct media_link *link = NULL; - #if IS_ENABLED(CONFIG_MEDIA_CONTROLLER) + struct media_link *link; if (sd->entity.function != MEDIA_ENT_F_LENS && sd->entity.function != MEDIA_ENT_F_FLASH) return 0; - link = media_create_ancillary_link(&n->sd->entity, &sd->entity); + if (!n->sd) { + dev_warn(notifier_dev(n), + "not a sub-device notifier, not creating an ancillary link for %s!\n", + dev_name(sd->dev)); + return 0; + } -#endif + link = media_create_ancillary_link(&n->sd->entity, &sd->entity); return IS_ERR(link) ? PTR_ERR(link) : 0; +#else + return 0; +#endif } static int v4l2_async_match_notify(struct v4l2_async_notifier *notifier, @@ -341,7 +348,7 @@ static int v4l2_async_match_notify(struct v4l2_async_notifier *notifier, int ret; if (list_empty(&sd->asc_list)) { - ret = v4l2_device_register_subdev(v4l2_dev, sd); + ret = __v4l2_device_register_subdev(v4l2_dev, sd, sd->owner); if (ret < 0) return ret; registered = true; @@ -563,6 +570,7 @@ void v4l2_async_nf_init(struct v4l2_async_notifier *notifier, { INIT_LIST_HEAD(¬ifier->waiting_list); INIT_LIST_HEAD(¬ifier->done_list); + INIT_LIST_HEAD(¬ifier->notifier_entry); notifier->v4l2_dev = v4l2_dev; } EXPORT_SYMBOL(v4l2_async_nf_init); @@ -572,6 +580,7 @@ void v4l2_async_subdev_nf_init(struct v4l2_async_notifier *notifier, { INIT_LIST_HEAD(¬ifier->waiting_list); INIT_LIST_HEAD(¬ifier->done_list); + INIT_LIST_HEAD(¬ifier->notifier_entry); notifier->sd = sd; } EXPORT_SYMBOL_GPL(v4l2_async_subdev_nf_init); @@ -618,16 +627,10 @@ err_unlock: int v4l2_async_nf_register(struct v4l2_async_notifier *notifier) { - int ret; - if (WARN_ON(!notifier->v4l2_dev == !notifier->sd)) return -EINVAL; - ret = __v4l2_async_nf_register(notifier); - if (ret) - notifier->v4l2_dev = NULL; - - return ret; + return __v4l2_async_nf_register(notifier); } EXPORT_SYMBOL(v4l2_async_nf_register); @@ -639,7 +642,7 @@ __v4l2_async_nf_unregister(struct v4l2_async_notifier *notifier) v4l2_async_nf_unbind_all_subdevs(notifier); - list_del(¬ifier->notifier_entry); + list_del_init(¬ifier->notifier_entry); } void v4l2_async_nf_unregister(struct v4l2_async_notifier *notifier) @@ -787,7 +790,7 @@ v4l2_async_connection_unique(struct v4l2_subdev *sd) } EXPORT_SYMBOL_GPL(v4l2_async_connection_unique); -int v4l2_async_register_subdev(struct v4l2_subdev *sd) +int __v4l2_async_register_subdev(struct v4l2_subdev *sd, struct module *module) { struct v4l2_async_notifier *subdev_notifier; struct v4l2_async_notifier *notifier; @@ -811,6 +814,8 @@ int v4l2_async_register_subdev(struct v4l2_subdev *sd) return -EINVAL; } + sd->owner = module; + mutex_lock(&list_lock); list_for_each_entry(notifier, ¬ifier_list, notifier_entry) { @@ -853,9 +858,11 @@ err_unbind: mutex_unlock(&list_lock); + sd->owner = NULL; + return ret; } -EXPORT_SYMBOL(v4l2_async_register_subdev); +EXPORT_SYMBOL(__v4l2_async_register_subdev); void v4l2_async_unregister_subdev(struct v4l2_subdev *sd) { @@ -965,4 +972,5 @@ module_exit(v4l2_async_exit); MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>"); MODULE_AUTHOR("Sakari Ailus <sakari.ailus@linux.intel.com>"); MODULE_AUTHOR("Ezequiel Garcia <ezequiel@collabora.com>"); +MODULE_DESCRIPTION("V4L2 asynchronous subdevice registration API"); MODULE_LICENSE("GPL"); diff --git a/drivers/media/v4l2-core/v4l2-cci.c b/drivers/media/v4l2-core/v4l2-cci.c index 10005c80f43b..e9ecf4785946 100644 --- a/drivers/media/v4l2-core/v4l2-cci.c +++ b/drivers/media/v4l2-core/v4l2-cci.c @@ -12,7 +12,7 @@ #include <linux/regmap.h> #include <linux/types.h> -#include <asm/unaligned.h> +#include <linux/unaligned.h> #include <media/v4l2-cci.h> @@ -23,6 +23,15 @@ int cci_read(struct regmap *map, u32 reg, u64 *val, int *err) u8 buf[8]; int ret; + /* + * TODO: Fix smatch. Assign *val to 0 here in order to avoid + * failing a smatch check on caller when the caller proceeds to + * read *val without initialising it on caller's side. *val is set + * to a valid value whenever this function returns 0 but smatch + * can't figure that out currently. + */ + *val = 0; + if (err && *err) return *err; @@ -32,7 +41,7 @@ int cci_read(struct regmap *map, u32 reg, u64 *val, int *err) ret = regmap_bulk_read(map, reg, buf, len); if (ret) { - dev_err(regmap_get_device(map), "Error reading reg 0x%4x: %d\n", + dev_err(regmap_get_device(map), "Error reading reg 0x%04x: %d\n", reg, ret); goto out; } @@ -131,7 +140,7 @@ int cci_write(struct regmap *map, u32 reg, u64 val, int *err) ret = regmap_bulk_write(map, reg, buf, len); if (ret) - dev_err(regmap_get_device(map), "Error writing reg 0x%4x: %d\n", + dev_err(regmap_get_device(map), "Error writing reg 0x%04x: %d\n", reg, ret); out: diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c index 273d83de2a87..0a2f4f0d0a07 100644 --- a/drivers/media/v4l2-core/v4l2-common.c +++ b/drivers/media/v4l2-core/v4l2-common.c @@ -253,6 +253,8 @@ const struct v4l2_format_info *v4l2_format_info(u32 format) { .format = V4L2_PIX_FMT_RGB555, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, { .format = V4L2_PIX_FMT_BGR666, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, { .format = V4L2_PIX_FMT_BGR48_12, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 6, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_BGR48, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 6, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_RGB48, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 6, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, { .format = V4L2_PIX_FMT_ABGR64_12, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 8, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, { .format = V4L2_PIX_FMT_RGBA1010102, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, { .format = V4L2_PIX_FMT_RGBX1010102, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, @@ -263,7 +265,9 @@ const struct v4l2_format_info *v4l2_format_info(u32 format) { .format = V4L2_PIX_FMT_YVYU, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 }, { .format = V4L2_PIX_FMT_UYVY, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 }, { .format = V4L2_PIX_FMT_VYUY, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_Y210, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 }, { .format = V4L2_PIX_FMT_Y212, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_Y216, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 }, { .format = V4L2_PIX_FMT_YUV48_12, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 6, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, { .format = V4L2_PIX_FMT_MT2110T, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 2, .comp_planes = 2, .bpp = { 5, 10, 0, 0 }, .bpp_div = { 4, 4, 1, 1 }, .hdiv = 2, .vdiv = 2, .block_w = { 16, 8, 0, 0 }, .block_h = { 32, 16, 0, 0 }}, @@ -585,3 +589,50 @@ u32 v4l2_fraction_to_interval(u32 numerator, u32 denominator) return denominator ? numerator * multiplier / denominator : 0; } EXPORT_SYMBOL_GPL(v4l2_fraction_to_interval); + +int v4l2_link_freq_to_bitmap(struct device *dev, const u64 *fw_link_freqs, + unsigned int num_of_fw_link_freqs, + const s64 *driver_link_freqs, + unsigned int num_of_driver_link_freqs, + unsigned long *bitmap) +{ + unsigned int i; + + *bitmap = 0; + + if (!num_of_fw_link_freqs) { + dev_err(dev, "no link frequencies in firmware\n"); + return -ENODATA; + } + + for (i = 0; i < num_of_fw_link_freqs; i++) { + unsigned int j; + + for (j = 0; j < num_of_driver_link_freqs; j++) { + if (fw_link_freqs[i] != driver_link_freqs[j]) + continue; + + dev_dbg(dev, "enabling link frequency %lld Hz\n", + driver_link_freqs[j]); + *bitmap |= BIT(j); + break; + } + } + + if (!*bitmap) { + dev_err(dev, "no matching link frequencies found\n"); + + dev_dbg(dev, "specified in firmware:\n"); + for (i = 0; i < num_of_fw_link_freqs; i++) + dev_dbg(dev, "\t%llu Hz\n", fw_link_freqs[i]); + + dev_dbg(dev, "driver supported:\n"); + for (i = 0; i < num_of_driver_link_freqs; i++) + dev_dbg(dev, "\t%lld Hz\n", driver_link_freqs[i]); + + return -ENOENT; + } + + return 0; +} +EXPORT_SYMBOL_GPL(v4l2_link_freq_to_bitmap); diff --git a/drivers/media/v4l2-core/v4l2-ctrls-api.c b/drivers/media/v4l2-core/v4l2-ctrls-api.c index 002ea6588edf..95a2202879d8 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-api.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-api.c @@ -753,9 +753,10 @@ static int get_ctrl(struct v4l2_ctrl *ctrl, struct v4l2_ext_control *c) for (i = 0; i < master->ncontrols; i++) cur_to_new(master->cluster[i]); ret = call_op(master, g_volatile_ctrl); - new_to_user(c, ctrl); + if (!ret) + ret = new_to_user(c, ctrl); } else { - cur_to_user(c, ctrl); + ret = cur_to_user(c, ctrl); } v4l2_ctrl_unlock(master); return ret; @@ -770,7 +771,10 @@ int v4l2_g_ctrl(struct v4l2_ctrl_handler *hdl, struct v4l2_control *control) if (!ctrl || !ctrl->is_int) return -EINVAL; ret = get_ctrl(ctrl, &c); - control->value = c.value; + + if (!ret) + control->value = c.value; + return ret; } EXPORT_SYMBOL(v4l2_g_ctrl); @@ -811,10 +815,11 @@ static int set_ctrl_lock(struct v4l2_fh *fh, struct v4l2_ctrl *ctrl, int ret; v4l2_ctrl_lock(ctrl); - user_to_new(c, ctrl); - ret = set_ctrl(fh, ctrl, 0); + ret = user_to_new(c, ctrl); if (!ret) - cur_to_user(c, ctrl); + ret = set_ctrl(fh, ctrl, 0); + if (!ret) + ret = cur_to_user(c, ctrl); v4l2_ctrl_unlock(ctrl); return ret; } @@ -1052,35 +1057,40 @@ int v4l2_query_ext_ctrl(struct v4l2_ctrl_handler *hdl, struct v4l2_query_ext_ctr if (id >= node2id(hdl->ctrl_refs.prev)) { ref = NULL; /* Yes, so there is no next control */ } else if (ref) { + struct v4l2_ctrl_ref *pos = ref; + /* * We found a control with the given ID, so just get * the next valid one in the list. */ - list_for_each_entry_continue(ref, &hdl->ctrl_refs, node) { - is_compound = ref->ctrl->is_array || - ref->ctrl->type >= V4L2_CTRL_COMPOUND_TYPES; - if (id < ref->ctrl->id && - (is_compound & mask) == match) + ref = NULL; + list_for_each_entry_continue(pos, &hdl->ctrl_refs, node) { + is_compound = pos->ctrl->is_array || + pos->ctrl->type >= V4L2_CTRL_COMPOUND_TYPES; + if (id < pos->ctrl->id && + (is_compound & mask) == match) { + ref = pos; break; + } } - if (&ref->node == &hdl->ctrl_refs) - ref = NULL; } else { + struct v4l2_ctrl_ref *pos; + /* * No control with the given ID exists, so start * searching for the next largest ID. We know there * is one, otherwise the first 'if' above would have * been true. */ - list_for_each_entry(ref, &hdl->ctrl_refs, node) { - is_compound = ref->ctrl->is_array || - ref->ctrl->type >= V4L2_CTRL_COMPOUND_TYPES; - if (id < ref->ctrl->id && - (is_compound & mask) == match) + list_for_each_entry(pos, &hdl->ctrl_refs, node) { + is_compound = pos->ctrl->is_array || + pos->ctrl->type >= V4L2_CTRL_COMPOUND_TYPES; + if (id < pos->ctrl->id && + (is_compound & mask) == match) { + ref = pos; break; + } } - if (&ref->node == &hdl->ctrl_refs) - ref = NULL; } } mutex_unlock(hdl->lock); @@ -1179,7 +1189,7 @@ int v4l2_querymenu(struct v4l2_ctrl_handler *hdl, struct v4l2_querymenu *qm) return -EINVAL; /* Use mask to see if this menu item should be skipped */ - if (ctrl->menu_skip_mask & (1ULL << i)) + if (i < BITS_PER_LONG_LONG && (ctrl->menu_skip_mask & BIT_ULL(i))) return -EINVAL; /* Empty menu items should also be skipped */ if (ctrl->type == V4L2_CTRL_TYPE_MENU) { diff --git a/drivers/media/v4l2-core/v4l2-ctrls-core.c b/drivers/media/v4l2-core/v4l2-ctrls-core.c index a662fb60f73f..eeab6a5eb7ba 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-core.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-core.c @@ -295,6 +295,9 @@ void v4l2_ctrl_type_op_log(const struct v4l2_ctrl *ctrl) case V4L2_CTRL_TYPE_U32: pr_cont("%u", (unsigned)*ptr.p_u32); break; + case V4L2_CTRL_TYPE_AREA: + pr_cont("%ux%u", ptr.p_area->width, ptr.p_area->height); + break; case V4L2_CTRL_TYPE_H264_SPS: pr_cont("H264_SPS"); break; @@ -1504,11 +1507,12 @@ int check_range(enum v4l2_ctrl_type type, return 0; case V4L2_CTRL_TYPE_MENU: case V4L2_CTRL_TYPE_INTEGER_MENU: - if (min > max || def < min || def > max) + if (min > max || def < min || def > max || + min < 0 || (step && max >= BITS_PER_LONG_LONG)) return -ERANGE; /* Note: step == menu_skip_mask for menu controls. So here we check if the default value is masked out. */ - if (step && ((1 << def) & step)) + if (def < BITS_PER_LONG_LONG && (step & BIT_ULL(def))) return -EINVAL; return 0; case V4L2_CTRL_TYPE_STRING: @@ -2555,6 +2559,9 @@ int v4l2_ctrl_new_fwnode_properties(struct v4l2_ctrl_handler *hdl, const struct v4l2_ctrl_ops *ctrl_ops, const struct v4l2_fwnode_device_properties *p) { + if (hdl->error) + return hdl->error; + if (p->orientation != V4L2_FWNODE_PROPERTY_UNSET) { u32 orientation_ctrl; diff --git a/drivers/media/v4l2-core/v4l2-ctrls-defs.c b/drivers/media/v4l2-core/v4l2-ctrls-defs.c index 8696eb1cdd61..1ea52011247a 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-defs.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-defs.c @@ -970,6 +970,7 @@ const char *v4l2_ctrl_get_name(u32 id) case V4L2_CID_MPEG_VIDEO_LTR_COUNT: return "LTR Count"; case V4L2_CID_MPEG_VIDEO_FRAME_LTR_INDEX: return "Frame LTR Index"; case V4L2_CID_MPEG_VIDEO_USE_LTR_FRAMES: return "Use LTR Frames"; + case V4L2_CID_MPEG_VIDEO_AVERAGE_QP: return "Average QP Value"; case V4L2_CID_FWHT_I_FRAME_QP: return "FWHT I-Frame QP Value"; case V4L2_CID_FWHT_P_FRAME_QP: return "FWHT P-Frame QP Value"; @@ -1507,6 +1508,10 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type, *max = 0xffffffffffffLL; *step = 1; break; + case V4L2_CID_MPEG_VIDEO_AVERAGE_QP: + *type = V4L2_CTRL_TYPE_INTEGER; + *flags |= V4L2_CTRL_FLAG_READ_ONLY; + break; case V4L2_CID_PIXEL_RATE: *type = V4L2_CTRL_TYPE_INTEGER64; *flags |= V4L2_CTRL_FLAG_READ_ONLY; diff --git a/drivers/media/v4l2-core/v4l2-dev.c b/drivers/media/v4l2-core/v4l2-dev.c index d13954bd31fd..5bcaeeba4d09 100644 --- a/drivers/media/v4l2-core/v4l2-dev.c +++ b/drivers/media/v4l2-core/v4l2-dev.c @@ -93,6 +93,8 @@ static struct attribute *video_device_attrs[] = { }; ATTRIBUTE_GROUPS(video_device); +static struct dentry *v4l2_debugfs_root_dir; + /* * Active devices */ @@ -227,7 +229,7 @@ static void v4l2_device_release(struct device *cd) v4l2_device_put(v4l2_dev); } -static struct class video_class = { +static const struct class video_class = { .name = VIDEO_NAME, .dev_groups = video_device_groups, }; @@ -483,7 +485,6 @@ static const struct file_operations v4l2_fops = { #endif .release = v4l2_release, .poll = v4l2_poll, - .llseek = no_llseek, }; /** @@ -557,6 +558,7 @@ static void determine_valid_ioctls(struct video_device *vdev) bool is_tx = vdev->vfl_dir != VFL_DIR_RX; bool is_io_mc = vdev->device_caps & V4L2_CAP_IO_MC; bool has_streaming = vdev->device_caps & V4L2_CAP_STREAMING; + bool is_edid = vdev->device_caps & V4L2_CAP_EDID; bitmap_zero(valid_ioctls, BASE_VIDIOC_PRIVATE); @@ -722,6 +724,9 @@ static void determine_valid_ioctls(struct video_device *vdev) SET_VALID_IOCTL(ops, VIDIOC_PREPARE_BUF, vidioc_prepare_buf); SET_VALID_IOCTL(ops, VIDIOC_STREAMON, vidioc_streamon); SET_VALID_IOCTL(ops, VIDIOC_STREAMOFF, vidioc_streamoff); + /* VIDIOC_CREATE_BUFS support is mandatory to enable VIDIOC_REMOVE_BUFS */ + if (ops->vidioc_create_bufs) + SET_VALID_IOCTL(ops, VIDIOC_REMOVE_BUFS, vidioc_remove_bufs); } if (is_vid || is_vbi || is_meta) { @@ -781,6 +786,20 @@ static void determine_valid_ioctls(struct video_device *vdev) SET_VALID_IOCTL(ops, VIDIOC_S_TUNER, vidioc_s_tuner); SET_VALID_IOCTL(ops, VIDIOC_S_HW_FREQ_SEEK, vidioc_s_hw_freq_seek); } + if (is_edid) { + SET_VALID_IOCTL(ops, VIDIOC_G_EDID, vidioc_g_edid); + if (is_tx) { + SET_VALID_IOCTL(ops, VIDIOC_G_OUTPUT, vidioc_g_output); + SET_VALID_IOCTL(ops, VIDIOC_S_OUTPUT, vidioc_s_output); + SET_VALID_IOCTL(ops, VIDIOC_ENUMOUTPUT, vidioc_enum_output); + } + if (is_rx) { + SET_VALID_IOCTL(ops, VIDIOC_ENUMINPUT, vidioc_enum_input); + SET_VALID_IOCTL(ops, VIDIOC_G_INPUT, vidioc_g_input); + SET_VALID_IOCTL(ops, VIDIOC_S_INPUT, vidioc_s_input); + SET_VALID_IOCTL(ops, VIDIOC_S_EDID, vidioc_s_edid); + } + } bitmap_andnot(vdev->valid_ioctls, valid_ioctls, vdev->valid_ioctls, BASE_VIDIOC_PRIVATE); @@ -1036,8 +1055,10 @@ int __video_register_device(struct video_device *vdev, vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor); vdev->dev.parent = vdev->dev_parent; dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num); + mutex_lock(&videodev_lock); ret = device_register(&vdev->dev); if (ret < 0) { + mutex_unlock(&videodev_lock); pr_err("%s: device_register failed\n", __func__); goto cleanup; } @@ -1057,6 +1078,7 @@ int __video_register_device(struct video_device *vdev, /* Part 6: Activate this minor. The char device can now be used. */ set_bit(V4L2_FL_REGISTERED, &vdev->flags); + mutex_unlock(&videodev_lock); return 0; @@ -1098,6 +1120,16 @@ void video_unregister_device(struct video_device *vdev) } EXPORT_SYMBOL(video_unregister_device); +#ifdef CONFIG_DEBUG_FS +struct dentry *v4l2_debugfs_root(void) +{ + if (!v4l2_debugfs_root_dir) + v4l2_debugfs_root_dir = debugfs_create_dir("v4l2", NULL); + return v4l2_debugfs_root_dir; +} +EXPORT_SYMBOL_GPL(v4l2_debugfs_root); +#endif + #if defined(CONFIG_MEDIA_CONTROLLER) __must_check int video_device_pipeline_start(struct video_device *vdev, @@ -1202,6 +1234,8 @@ static void __exit videodev_exit(void) class_unregister(&video_class); unregister_chrdev_region(dev, VIDEO_NUM_DEVICES); + debugfs_remove_recursive(v4l2_debugfs_root_dir); + v4l2_debugfs_root_dir = NULL; } subsys_initcall(videodev_init); diff --git a/drivers/media/v4l2-core/v4l2-device.c b/drivers/media/v4l2-core/v4l2-device.c index d2e58ae91f9b..5e537454f5cd 100644 --- a/drivers/media/v4l2-core/v4l2-device.c +++ b/drivers/media/v4l2-core/v4l2-device.c @@ -108,8 +108,8 @@ void v4l2_device_unregister(struct v4l2_device *v4l2_dev) } EXPORT_SYMBOL_GPL(v4l2_device_unregister); -int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev, - struct v4l2_subdev *sd) +int __v4l2_device_register_subdev(struct v4l2_device *v4l2_dev, + struct v4l2_subdev *sd, struct module *module) { int err; @@ -125,9 +125,9 @@ int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev, * try_module_get() such sub-device owners. */ sd->owner_v4l2_dev = v4l2_dev->dev && v4l2_dev->dev->driver && - sd->owner == v4l2_dev->dev->driver->owner; + module == v4l2_dev->dev->driver->owner; - if (!sd->owner_v4l2_dev && !try_module_get(sd->owner)) + if (!sd->owner_v4l2_dev && !try_module_get(module)) return -ENODEV; sd->v4l2_dev = v4l2_dev; @@ -152,6 +152,8 @@ int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev, goto error_unregister; } + sd->owner = module; + spin_lock(&v4l2_dev->lock); list_add_tail(&sd->list, &v4l2_dev->subdevs); spin_unlock(&v4l2_dev->lock); @@ -168,7 +170,7 @@ error_module: sd->v4l2_dev = NULL; return err; } -EXPORT_SYMBOL_GPL(v4l2_device_register_subdev); +EXPORT_SYMBOL_GPL(__v4l2_device_register_subdev); static void v4l2_subdev_release(struct v4l2_subdev *sd) { diff --git a/drivers/media/v4l2-core/v4l2-dv-timings.c b/drivers/media/v4l2-core/v4l2-dv-timings.c index 942d0005c55e..d26edf157e64 100644 --- a/drivers/media/v4l2-core/v4l2-dv-timings.c +++ b/drivers/media/v4l2-core/v4l2-dv-timings.c @@ -481,25 +481,28 @@ EXPORT_SYMBOL_GPL(v4l2_calc_timeperframe); * @polarities - the horizontal and vertical polarities (same as struct * v4l2_bt_timings polarities). * @interlaced - if this flag is true, it indicates interlaced format - * @fmt - the resulting timings. + * @cap - the v4l2_dv_timings_cap capabilities. + * @timings - the resulting timings. * * This function will attempt to detect if the given values correspond to a * valid CVT format. If so, then it will return true, and fmt will be filled * in with the found CVT timings. */ -bool v4l2_detect_cvt(unsigned frame_height, - unsigned hfreq, - unsigned vsync, - unsigned active_width, +bool v4l2_detect_cvt(unsigned int frame_height, + unsigned int hfreq, + unsigned int vsync, + unsigned int active_width, u32 polarities, bool interlaced, - struct v4l2_dv_timings *fmt) + const struct v4l2_dv_timings_cap *cap, + struct v4l2_dv_timings *timings) { - int v_fp, v_bp, h_fp, h_bp, hsync; - int frame_width, image_height, image_width; + struct v4l2_dv_timings t = {}; + int v_fp, v_bp, h_fp, h_bp, hsync; + int frame_width, image_height, image_width; bool reduced_blanking; bool rb_v2 = false; - unsigned pix_clk; + unsigned int pix_clk; if (vsync < 4 || vsync > 8) return false; @@ -625,36 +628,39 @@ bool v4l2_detect_cvt(unsigned frame_height, h_fp = h_blank - hsync - h_bp; } - fmt->type = V4L2_DV_BT_656_1120; - fmt->bt.polarities = polarities; - fmt->bt.width = image_width; - fmt->bt.height = image_height; - fmt->bt.hfrontporch = h_fp; - fmt->bt.vfrontporch = v_fp; - fmt->bt.hsync = hsync; - fmt->bt.vsync = vsync; - fmt->bt.hbackporch = frame_width - image_width - h_fp - hsync; + t.type = V4L2_DV_BT_656_1120; + t.bt.polarities = polarities; + t.bt.width = image_width; + t.bt.height = image_height; + t.bt.hfrontporch = h_fp; + t.bt.vfrontporch = v_fp; + t.bt.hsync = hsync; + t.bt.vsync = vsync; + t.bt.hbackporch = frame_width - image_width - h_fp - hsync; if (!interlaced) { - fmt->bt.vbackporch = frame_height - image_height - v_fp - vsync; - fmt->bt.interlaced = V4L2_DV_PROGRESSIVE; + t.bt.vbackporch = frame_height - image_height - v_fp - vsync; + t.bt.interlaced = V4L2_DV_PROGRESSIVE; } else { - fmt->bt.vbackporch = (frame_height - image_height - 2 * v_fp - + t.bt.vbackporch = (frame_height - image_height - 2 * v_fp - 2 * vsync) / 2; - fmt->bt.il_vbackporch = frame_height - image_height - 2 * v_fp - - 2 * vsync - fmt->bt.vbackporch; - fmt->bt.il_vfrontporch = v_fp; - fmt->bt.il_vsync = vsync; - fmt->bt.flags |= V4L2_DV_FL_HALF_LINE; - fmt->bt.interlaced = V4L2_DV_INTERLACED; + t.bt.il_vbackporch = frame_height - image_height - 2 * v_fp - + 2 * vsync - t.bt.vbackporch; + t.bt.il_vfrontporch = v_fp; + t.bt.il_vsync = vsync; + t.bt.flags |= V4L2_DV_FL_HALF_LINE; + t.bt.interlaced = V4L2_DV_INTERLACED; } - fmt->bt.pixelclock = pix_clk; - fmt->bt.standards = V4L2_DV_BT_STD_CVT; + t.bt.pixelclock = pix_clk; + t.bt.standards = V4L2_DV_BT_STD_CVT; if (reduced_blanking) - fmt->bt.flags |= V4L2_DV_FL_REDUCED_BLANKING; + t.bt.flags |= V4L2_DV_FL_REDUCED_BLANKING; + if (!v4l2_valid_dv_timings(&t, cap, NULL, NULL)) + return false; + *timings = t; return true; } EXPORT_SYMBOL_GPL(v4l2_detect_cvt); @@ -699,22 +705,25 @@ EXPORT_SYMBOL_GPL(v4l2_detect_cvt); * image height, so it has to be passed explicitly. Usually * the native screen aspect ratio is used for this. If it * is not filled in correctly, then 16:9 will be assumed. - * @fmt - the resulting timings. + * @cap - the v4l2_dv_timings_cap capabilities. + * @timings - the resulting timings. * * This function will attempt to detect if the given values correspond to a * valid GTF format. If so, then it will return true, and fmt will be filled * in with the found GTF timings. */ -bool v4l2_detect_gtf(unsigned frame_height, - unsigned hfreq, - unsigned vsync, - u32 polarities, - bool interlaced, - struct v4l2_fract aspect, - struct v4l2_dv_timings *fmt) +bool v4l2_detect_gtf(unsigned int frame_height, + unsigned int hfreq, + unsigned int vsync, + u32 polarities, + bool interlaced, + struct v4l2_fract aspect, + const struct v4l2_dv_timings_cap *cap, + struct v4l2_dv_timings *timings) { + struct v4l2_dv_timings t = {}; int pix_clk; - int v_fp, v_bp, h_fp, hsync; + int v_fp, v_bp, h_fp, hsync; int frame_width, image_height, image_width; bool default_gtf; int h_blank; @@ -783,36 +792,39 @@ bool v4l2_detect_gtf(unsigned frame_height, h_fp = h_blank / 2 - hsync; - fmt->type = V4L2_DV_BT_656_1120; - fmt->bt.polarities = polarities; - fmt->bt.width = image_width; - fmt->bt.height = image_height; - fmt->bt.hfrontporch = h_fp; - fmt->bt.vfrontporch = v_fp; - fmt->bt.hsync = hsync; - fmt->bt.vsync = vsync; - fmt->bt.hbackporch = frame_width - image_width - h_fp - hsync; + t.type = V4L2_DV_BT_656_1120; + t.bt.polarities = polarities; + t.bt.width = image_width; + t.bt.height = image_height; + t.bt.hfrontporch = h_fp; + t.bt.vfrontporch = v_fp; + t.bt.hsync = hsync; + t.bt.vsync = vsync; + t.bt.hbackporch = frame_width - image_width - h_fp - hsync; if (!interlaced) { - fmt->bt.vbackporch = frame_height - image_height - v_fp - vsync; - fmt->bt.interlaced = V4L2_DV_PROGRESSIVE; + t.bt.vbackporch = frame_height - image_height - v_fp - vsync; + t.bt.interlaced = V4L2_DV_PROGRESSIVE; } else { - fmt->bt.vbackporch = (frame_height - image_height - 2 * v_fp - + t.bt.vbackporch = (frame_height - image_height - 2 * v_fp - 2 * vsync) / 2; - fmt->bt.il_vbackporch = frame_height - image_height - 2 * v_fp - - 2 * vsync - fmt->bt.vbackporch; - fmt->bt.il_vfrontporch = v_fp; - fmt->bt.il_vsync = vsync; - fmt->bt.flags |= V4L2_DV_FL_HALF_LINE; - fmt->bt.interlaced = V4L2_DV_INTERLACED; + t.bt.il_vbackporch = frame_height - image_height - 2 * v_fp - + 2 * vsync - t.bt.vbackporch; + t.bt.il_vfrontporch = v_fp; + t.bt.il_vsync = vsync; + t.bt.flags |= V4L2_DV_FL_HALF_LINE; + t.bt.interlaced = V4L2_DV_INTERLACED; } - fmt->bt.pixelclock = pix_clk; - fmt->bt.standards = V4L2_DV_BT_STD_GTF; + t.bt.pixelclock = pix_clk; + t.bt.standards = V4L2_DV_BT_STD_GTF; if (!default_gtf) - fmt->bt.flags |= V4L2_DV_FL_REDUCED_BLANKING; + t.bt.flags |= V4L2_DV_FL_REDUCED_BLANKING; + if (!v4l2_valid_dv_timings(&t, cap, NULL, NULL)) + return false; + *timings = t; return true; } EXPORT_SYMBOL_GPL(v4l2_detect_gtf); @@ -1154,3 +1166,70 @@ int v4l2_phys_addr_validate(u16 phys_addr, u16 *parent, u16 *port) return 0; } EXPORT_SYMBOL_GPL(v4l2_phys_addr_validate); + +#ifdef CONFIG_DEBUG_FS + +#define DEBUGFS_FOPS(type, flag) \ +static ssize_t \ +infoframe_read_##type(struct file *filp, \ + char __user *ubuf, size_t count, loff_t *ppos) \ +{ \ + struct v4l2_debugfs_if *infoframes = filp->private_data; \ + \ + return infoframes->if_read((flag), infoframes->priv, filp, \ + ubuf, count, ppos); \ +} \ + \ +static const struct file_operations infoframe_##type##_fops = { \ + .owner = THIS_MODULE, \ + .open = simple_open, \ + .read = infoframe_read_##type, \ +} + +DEBUGFS_FOPS(avi, V4L2_DEBUGFS_IF_AVI); +DEBUGFS_FOPS(audio, V4L2_DEBUGFS_IF_AUDIO); +DEBUGFS_FOPS(spd, V4L2_DEBUGFS_IF_SPD); +DEBUGFS_FOPS(hdmi, V4L2_DEBUGFS_IF_HDMI); + +struct v4l2_debugfs_if *v4l2_debugfs_if_alloc(struct dentry *root, u32 if_types, + void *priv, + v4l2_debugfs_if_read_t if_read) +{ + struct v4l2_debugfs_if *infoframes; + + if (IS_ERR_OR_NULL(root) || !if_types || !if_read) + return NULL; + + infoframes = kzalloc(sizeof(*infoframes), GFP_KERNEL); + if (!infoframes) + return NULL; + + infoframes->if_dir = debugfs_create_dir("infoframes", root); + infoframes->priv = priv; + infoframes->if_read = if_read; + if (if_types & V4L2_DEBUGFS_IF_AVI) + debugfs_create_file("avi", 0400, infoframes->if_dir, + infoframes, &infoframe_avi_fops); + if (if_types & V4L2_DEBUGFS_IF_AUDIO) + debugfs_create_file("audio", 0400, infoframes->if_dir, + infoframes, &infoframe_audio_fops); + if (if_types & V4L2_DEBUGFS_IF_SPD) + debugfs_create_file("spd", 0400, infoframes->if_dir, + infoframes, &infoframe_spd_fops); + if (if_types & V4L2_DEBUGFS_IF_HDMI) + debugfs_create_file("hdmi", 0400, infoframes->if_dir, + infoframes, &infoframe_hdmi_fops); + return infoframes; +} +EXPORT_SYMBOL_GPL(v4l2_debugfs_if_alloc); + +void v4l2_debugfs_if_free(struct v4l2_debugfs_if *infoframes) +{ + if (infoframes) { + debugfs_remove_recursive(infoframes->if_dir); + kfree(infoframes); + } +} +EXPORT_SYMBOL_GPL(v4l2_debugfs_if_free); + +#endif diff --git a/drivers/media/v4l2-core/v4l2-fwnode.c b/drivers/media/v4l2-core/v4l2-fwnode.c index 89c7192148df..cb153ce42c45 100644 --- a/drivers/media/v4l2-core/v4l2-fwnode.c +++ b/drivers/media/v4l2-core/v4l2-fwnode.c @@ -127,7 +127,7 @@ static int v4l2_fwnode_endpoint_parse_csi2_bus(struct fwnode_handle *fwnode, { struct v4l2_mbus_config_mipi_csi2 *bus = &vep->bus.mipi_csi2; bool have_clk_lane = false, have_data_lanes = false, - have_lane_polarities = false; + have_lane_polarities = false, have_line_orders = false; unsigned int flags = 0, lanes_used = 0; u32 array[1 + V4L2_MBUS_CSI2_MAX_DATA_LANES]; u32 clock_lane = 0; @@ -197,6 +197,17 @@ static int v4l2_fwnode_endpoint_parse_csi2_bus(struct fwnode_handle *fwnode, have_lane_polarities = true; } + rval = fwnode_property_count_u32(fwnode, "line-orders"); + if (rval > 0) { + if (rval != num_data_lanes) { + pr_warn("invalid number of line-orders entries (need %u, got %u)\n", + num_data_lanes, rval); + return -EINVAL; + } + + have_line_orders = true; + } + if (!fwnode_property_read_u32(fwnode, "clock-lanes", &v)) { clock_lane = v; pr_debug("clock lane position %u\n", v); @@ -250,6 +261,36 @@ static int v4l2_fwnode_endpoint_parse_csi2_bus(struct fwnode_handle *fwnode, } else { pr_debug("no lane polarities defined, assuming not inverted\n"); } + + if (have_line_orders) { + fwnode_property_read_u32_array(fwnode, + "line-orders", array, + num_data_lanes); + + for (i = 0; i < num_data_lanes; i++) { + static const char * const orders[] = { + "ABC", "ACB", "BAC", "BCA", "CAB", "CBA" + }; + + if (array[i] >= ARRAY_SIZE(orders)) { + pr_warn("lane %u invalid line-order assuming ABC (got %u)\n", + i, array[i]); + bus->line_orders[i] = + V4L2_MBUS_CSI2_CPHY_LINE_ORDER_ABC; + continue; + } + + bus->line_orders[i] = array[i]; + pr_debug("lane %u line order %s", i, + orders[array[i]]); + } + } else { + for (i = 0; i < num_data_lanes; i++) + bus->line_orders[i] = + V4L2_MBUS_CSI2_CPHY_LINE_ORDER_ABC; + + pr_debug("no line orders defined, assuming ABC\n"); + } } return 0; @@ -1251,6 +1292,7 @@ out_cleanup: } EXPORT_SYMBOL_GPL(v4l2_async_register_subdev_sensor); +MODULE_DESCRIPTION("V4L2 fwnode binding parsing library"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Sakari Ailus <sakari.ailus@linux.intel.com>"); MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>"); diff --git a/drivers/media/v4l2-core/v4l2-i2c.c b/drivers/media/v4l2-core/v4l2-i2c.c index b4acca75644b..586c46544255 100644 --- a/drivers/media/v4l2-core/v4l2-i2c.c +++ b/drivers/media/v4l2-core/v4l2-i2c.c @@ -100,7 +100,7 @@ struct v4l2_subdev * Register with the v4l2_device which increases the module's * use count as well. */ - if (v4l2_device_register_subdev(v4l2_dev, sd)) + if (__v4l2_device_register_subdev(v4l2_dev, sd, sd->owner)) sd = NULL; /* Decrease the module use count to match the first try_module_get. */ module_put(client->dev.driver->owner); diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c index 33076af4dfdb..0304daa8471d 100644 --- a/drivers/media/v4l2-core/v4l2-ioctl.c +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -343,8 +343,9 @@ static void v4l_print_format(const void *arg, bool write_only) case V4L2_BUF_TYPE_META_OUTPUT: meta = &p->fmt.meta; pixelformat = meta->dataformat; - pr_cont(", dataformat=%p4cc, buffersize=%u\n", - &pixelformat, meta->buffersize); + pr_cont(", dataformat=%p4cc, buffersize=%u, width=%u, height=%u, bytesperline=%u\n", + &pixelformat, meta->buffersize, meta->width, + meta->height, meta->bytesperline); break; } } @@ -483,12 +484,20 @@ static void v4l_print_create_buffers(const void *arg, bool write_only) { const struct v4l2_create_buffers *p = arg; - pr_cont("index=%d, count=%d, memory=%s, capabilities=0x%08x, max num buffers=%u", + pr_cont("index=%d, count=%d, memory=%s, capabilities=0x%08x, max num buffers=%u, ", p->index, p->count, prt_names(p->memory, v4l2_memory_names), p->capabilities, p->max_num_buffers); v4l_print_format(&p->format, write_only); } +static void v4l_print_remove_buffers(const void *arg, bool write_only) +{ + const struct v4l2_remove_buffers *p = arg; + + pr_cont("type=%s, index=%u, count=%u\n", + prt_names(p->type, v4l2_type_names), p->index, p->count); +} + static void v4l_print_streamparm(const void *arg, bool write_only) { const struct v4l2_streamparm *p = arg; @@ -1298,6 +1307,8 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_PIX_FMT_RGBX1010102: descr = "32-bit RGBX 10-10-10-2"; break; case V4L2_PIX_FMT_RGBA1010102: descr = "32-bit RGBA 10-10-10-2"; break; case V4L2_PIX_FMT_ARGB2101010: descr = "32-bit ARGB 2-10-10-10"; break; + case V4L2_PIX_FMT_BGR48: descr = "48-bit BGR 16-16-16"; break; + case V4L2_PIX_FMT_RGB48: descr = "48-bit RGB 16-16-16"; break; case V4L2_PIX_FMT_BGR48_12: descr = "12-bit Depth BGR"; break; case V4L2_PIX_FMT_ABGR64_12: descr = "12-bit Depth BGRA"; break; case V4L2_PIX_FMT_GREY: descr = "8-bit Greyscale"; break; @@ -1312,8 +1323,11 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_PIX_FMT_Y10BPACK: descr = "10-bit Greyscale (Packed)"; break; case V4L2_PIX_FMT_Y10P: descr = "10-bit Greyscale (MIPI Packed)"; break; case V4L2_PIX_FMT_IPU3_Y10: descr = "10-bit greyscale (IPU3 Packed)"; break; + case V4L2_PIX_FMT_Y12P: descr = "12-bit Greyscale (MIPI Packed)"; break; + case V4L2_PIX_FMT_Y14P: descr = "14-bit Greyscale (MIPI Packed)"; break; case V4L2_PIX_FMT_Y8I: descr = "Interleaved 8-bit Greyscale"; break; case V4L2_PIX_FMT_Y12I: descr = "Interleaved 12-bit Greyscale"; break; + case V4L2_PIX_FMT_Y16I: descr = "Interleaved 16-bit Greyscale"; break; case V4L2_PIX_FMT_Z16: descr = "16-bit Depth"; break; case V4L2_PIX_FMT_INZI: descr = "Planar 10:16 Greyscale Depth"; break; case V4L2_PIX_FMT_CNF4: descr = "4-bit Depth Confidence (Packed)"; break; @@ -1445,6 +1459,7 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_META_FMT_VIVID: descr = "Vivid Metadata"; break; case V4L2_META_FMT_RK_ISP1_PARAMS: descr = "Rockchip ISP1 3A Parameters"; break; case V4L2_META_FMT_RK_ISP1_STAT_3A: descr = "Rockchip ISP1 3A Statistics"; break; + case V4L2_META_FMT_RK_ISP1_EXT_PARAMS: descr = "Rockchip ISP1 Ext 3A Params"; break; case V4L2_PIX_FMT_NV12_8L128: descr = "NV12 (8x128 Linear)"; break; case V4L2_PIX_FMT_NV12M_8L128: descr = "NV12M (8x128 Linear)"; break; case V4L2_PIX_FMT_NV12_10BE_8L128: descr = "10-bit NV12 (8x128 Linear, BE)"; break; @@ -1452,6 +1467,16 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_PIX_FMT_Y210: descr = "10-bit YUYV Packed"; break; case V4L2_PIX_FMT_Y212: descr = "12-bit YUYV Packed"; break; case V4L2_PIX_FMT_Y216: descr = "16-bit YUYV Packed"; break; + case V4L2_META_FMT_RPI_BE_CFG: descr = "RPi PiSP BE Config format"; break; + case V4L2_META_FMT_RPI_FE_CFG: descr = "RPi PiSP FE Config format"; break; + case V4L2_META_FMT_RPI_FE_STATS: descr = "RPi PiSP FE Statistics format"; break; + case V4L2_META_FMT_GENERIC_8: descr = "8-bit Generic Metadata"; break; + case V4L2_META_FMT_GENERIC_CSI2_10: descr = "8-bit Generic Meta, 10b CSI-2"; break; + case V4L2_META_FMT_GENERIC_CSI2_12: descr = "8-bit Generic Meta, 12b CSI-2"; break; + case V4L2_META_FMT_GENERIC_CSI2_14: descr = "8-bit Generic Meta, 14b CSI-2"; break; + case V4L2_META_FMT_GENERIC_CSI2_16: descr = "8-bit Generic Meta, 16b CSI-2"; break; + case V4L2_META_FMT_GENERIC_CSI2_20: descr = "8-bit Generic Meta, 20b CSI-2"; break; + case V4L2_META_FMT_GENERIC_CSI2_24: descr = "8-bit Generic Meta, 24b CSI-2"; break; default: /* Compressed formats */ @@ -1511,6 +1536,16 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_PIX_FMT_MT2110T: descr = "Mediatek 10bit Tile Mode"; break; case V4L2_PIX_FMT_MT2110R: descr = "Mediatek 10bit Raster Mode"; break; case V4L2_PIX_FMT_HEXTILE: descr = "Hextile Compressed Format"; break; + case V4L2_PIX_FMT_PISP_COMP1_RGGB: descr = "PiSP 8b RGRG/GBGB mode1 compr"; break; + case V4L2_PIX_FMT_PISP_COMP1_GRBG: descr = "PiSP 8b GRGR/BGBG mode1 compr"; break; + case V4L2_PIX_FMT_PISP_COMP1_GBRG: descr = "PiSP 8b GBGB/RGRG mode1 compr"; break; + case V4L2_PIX_FMT_PISP_COMP1_BGGR: descr = "PiSP 8b BGBG/GRGR mode1 compr"; break; + case V4L2_PIX_FMT_PISP_COMP1_MONO: descr = "PiSP 8b monochrome mode1 compr"; break; + case V4L2_PIX_FMT_PISP_COMP2_RGGB: descr = "PiSP 8b RGRG/GBGB mode2 compr"; break; + case V4L2_PIX_FMT_PISP_COMP2_GRBG: descr = "PiSP 8b GRGR/BGBG mode2 compr"; break; + case V4L2_PIX_FMT_PISP_COMP2_GBRG: descr = "PiSP 8b GBGB/RGRG mode2 compr"; break; + case V4L2_PIX_FMT_PISP_COMP2_BGGR: descr = "PiSP 8b BGBG/GRGR mode2 compr"; break; + case V4L2_PIX_FMT_PISP_COMP2_MONO: descr = "PiSP 8b monochrome mode2 compr"; break; default: if (fmt->description[0]) return; @@ -1522,6 +1557,22 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) } } + if (fmt->type == V4L2_BUF_TYPE_META_CAPTURE) { + switch (fmt->pixelformat) { + case V4L2_META_FMT_GENERIC_8: + case V4L2_META_FMT_GENERIC_CSI2_10: + case V4L2_META_FMT_GENERIC_CSI2_12: + case V4L2_META_FMT_GENERIC_CSI2_14: + case V4L2_META_FMT_GENERIC_CSI2_16: + case V4L2_META_FMT_GENERIC_CSI2_20: + case V4L2_META_FMT_GENERIC_CSI2_24: + fmt->flags |= V4L2_FMT_FLAG_META_LINE_BASED; + break; + default: + fmt->flags &= ~V4L2_FMT_FLAG_META_LINE_BASED; + } + } + if (descr) WARN_ON(strscpy(fmt->description, descr, sz) < 0); fmt->flags |= flags; @@ -2092,6 +2143,7 @@ static int v4l_overlay(const struct v4l2_ioctl_ops *ops, static int v4l_reqbufs(const struct v4l2_ioctl_ops *ops, struct file *file, void *fh, void *arg) { + struct video_device *vfd = video_devdata(file); struct v4l2_requestbuffers *p = arg; int ret = check_fmt(file, p->type); @@ -2100,6 +2152,10 @@ static int v4l_reqbufs(const struct v4l2_ioctl_ops *ops, memset_after(p, 0, flags); + p->capabilities = 0; + if (is_valid_ioctl(vfd, VIDIOC_REMOVE_BUFS)) + p->capabilities = V4L2_BUF_CAP_SUPPORTS_REMOVE_BUFS; + return ops->vidioc_reqbufs(file, fh, p); } @@ -2133,6 +2189,7 @@ static int v4l_dqbuf(const struct v4l2_ioctl_ops *ops, static int v4l_create_bufs(const struct v4l2_ioctl_ops *ops, struct file *file, void *fh, void *arg) { + struct video_device *vfd = video_devdata(file); struct v4l2_create_buffers *create = arg; int ret = check_fmt(file, create->format.type); @@ -2143,6 +2200,10 @@ static int v4l_create_bufs(const struct v4l2_ioctl_ops *ops, v4l_sanitize_format(&create->format); + create->capabilities = 0; + if (is_valid_ioctl(vfd, VIDIOC_REMOVE_BUFS)) + create->capabilities = V4L2_BUF_CAP_SUPPORTS_REMOVE_BUFS; + ret = ops->vidioc_create_bufs(file, fh, create); if (create->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE || @@ -2161,6 +2222,17 @@ static int v4l_prepare_buf(const struct v4l2_ioctl_ops *ops, return ret ? ret : ops->vidioc_prepare_buf(file, fh, b); } +static int v4l_remove_bufs(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct v4l2_remove_buffers *remove = arg; + + if (ops->vidioc_remove_bufs) + return ops->vidioc_remove_bufs(file, fh, remove); + + return -ENOTTY; +} + static int v4l_g_parm(const struct v4l2_ioctl_ops *ops, struct file *file, void *fh, void *arg) { @@ -2910,6 +2982,7 @@ static const struct v4l2_ioctl_info v4l2_ioctls[] = { IOCTL_INFO(VIDIOC_ENUM_FREQ_BANDS, v4l_enum_freq_bands, v4l_print_freq_band, 0), IOCTL_INFO(VIDIOC_DBG_G_CHIP_INFO, v4l_dbg_g_chip_info, v4l_print_dbg_chip_info, INFO_FL_CLEAR(v4l2_dbg_chip_info, match)), IOCTL_INFO(VIDIOC_QUERY_EXT_CTRL, v4l_query_ext_ctrl, v4l_print_query_ext_ctrl, INFO_FL_CTRL | INFO_FL_CLEAR(v4l2_query_ext_ctrl, id)), + IOCTL_INFO(VIDIOC_REMOVE_BUFS, v4l_remove_bufs, v4l_print_remove_buffers, INFO_FL_PRIO | INFO_FL_QUEUE | INFO_FL_CLEAR(v4l2_remove_buffers, type)), }; #define V4L2_IOCTLS ARRAY_SIZE(v4l2_ioctls) @@ -3028,7 +3101,7 @@ static long __video_do_ioctl(struct file *file, if (v4l2_is_known_ioctl(cmd)) { info = &v4l2_ioctls[_IOC_NR(cmd)]; - if (!test_bit(_IOC_NR(cmd), vfd->valid_ioctls) && + if (!is_valid_ioctl(vfd, cmd) && !((info->flags & INFO_FL_CTRL) && vfh && vfh->ctrl_handler)) goto done; @@ -3147,13 +3220,13 @@ static int check_array_args(unsigned int cmd, void *parg, size_t *array_size, case VIDIOC_SUBDEV_S_ROUTING: { struct v4l2_subdev_routing *routing = parg; - if (routing->num_routes > 256) + if (routing->len_routes > 256) return -E2BIG; *user_ptr = u64_to_user_ptr(routing->routes); *kernel_ptr = (void **)&routing->routes; *array_size = sizeof(struct v4l2_subdev_route) - * routing->num_routes; + * routing->len_routes; ret = 1; break; } @@ -3407,11 +3480,14 @@ video_usercopy(struct file *file, unsigned int orig_cmd, unsigned long arg, * FIXME: subdev IOCTLS are partially handled here and partially in * v4l2-subdev.c and the 'always_copy' flag can only be set for IOCTLS * defined here as part of the 'v4l2_ioctls' array. As - * VIDIOC_SUBDEV_G_ROUTING needs to return results to applications even - * in case of failure, but it is not defined here as part of the + * VIDIOC_SUBDEV_[GS]_ROUTING needs to return results to applications + * even in case of failure, but it is not defined here as part of the * 'v4l2_ioctls' array, insert an ad-hoc check to address that. */ - if (err < 0 && !always_copy && cmd != VIDIOC_SUBDEV_G_ROUTING) + if (cmd == VIDIOC_SUBDEV_G_ROUTING || cmd == VIDIOC_SUBDEV_S_ROUTING) + always_copy = true; + + if (err < 0 && !always_copy) goto out; if (has_array_args) { diff --git a/drivers/media/v4l2-core/v4l2-jpeg.c b/drivers/media/v4l2-core/v4l2-jpeg.c index 94435a7b6816..6e2647323522 100644 --- a/drivers/media/v4l2-core/v4l2-jpeg.c +++ b/drivers/media/v4l2-core/v4l2-jpeg.c @@ -9,7 +9,7 @@ * [1] https://www.w3.org/Graphics/JPEG/itu-t81.pdf */ -#include <asm/unaligned.h> +#include <linux/unaligned.h> #include <linux/errno.h> #include <linux/kernel.h> #include <linux/module.h> @@ -52,6 +52,122 @@ MODULE_LICENSE("GPL"); #define COM 0xfffe /* comment */ #define TEM 0xff01 /* temporary */ +/* Luma and chroma qp tables to achieve 50% compression quality + * This is as per example in Annex K.1 of ITU-T.81 + */ +const u8 v4l2_jpeg_ref_table_luma_qt[V4L2_JPEG_PIXELS_IN_BLOCK] = { + 16, 11, 10, 16, 24, 40, 51, 61, + 12, 12, 14, 19, 26, 58, 60, 55, + 14, 13, 16, 24, 40, 57, 69, 56, + 14, 17, 22, 29, 51, 87, 80, 62, + 18, 22, 37, 56, 68, 109, 103, 77, + 24, 35, 55, 64, 81, 104, 113, 92, + 49, 64, 78, 87, 103, 121, 120, 101, + 72, 92, 95, 98, 112, 100, 103, 99 +}; +EXPORT_SYMBOL_GPL(v4l2_jpeg_ref_table_luma_qt); + +const u8 v4l2_jpeg_ref_table_chroma_qt[V4L2_JPEG_PIXELS_IN_BLOCK] = { + 17, 18, 24, 47, 99, 99, 99, 99, + 18, 21, 26, 66, 99, 99, 99, 99, + 24, 26, 56, 99, 99, 99, 99, 99, + 47, 66, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99 +}; +EXPORT_SYMBOL_GPL(v4l2_jpeg_ref_table_chroma_qt); + +/* Zigzag scan pattern indexes */ +const u8 v4l2_jpeg_zigzag_scan_index[V4L2_JPEG_PIXELS_IN_BLOCK] = { + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63 +}; +EXPORT_SYMBOL_GPL(v4l2_jpeg_zigzag_scan_index); + +/* + * Contains the data that needs to be sent in the marker segment of an + * interchange format JPEG stream or an abbreviated format table specification + * data stream. Specifies the huffman table used for encoding the luminance DC + * coefficient differences. The table represents Table K.3 of ITU-T.81 + */ +const u8 v4l2_jpeg_ref_table_luma_dc_ht[V4L2_JPEG_REF_HT_DC_LEN] = { + 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B +}; +EXPORT_SYMBOL_GPL(v4l2_jpeg_ref_table_luma_dc_ht); + +/* + * Contains the data that needs to be sent in the marker segment of an + * interchange format JPEG stream or an abbreviated format table specification + * data stream. Specifies the huffman table used for encoding the luminance AC + * coefficients. The table represents Table K.5 of ITU-T.81 + */ +const u8 v4l2_jpeg_ref_table_luma_ac_ht[V4L2_JPEG_REF_HT_AC_LEN] = { + 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, + 0x00, 0x00, 0x01, 0x7D, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, + 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, + 0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0, + 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A, + 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, 0x55, + 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, + 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, + 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, + 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, + 0xD9, 0xDA, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, + 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA +}; +EXPORT_SYMBOL_GPL(v4l2_jpeg_ref_table_luma_ac_ht); + +/* + * Contains the data that needs to be sent in the marker segment of an interchange format JPEG + * stream or an abbreviated format table specification data stream. + * Specifies the huffman table used for encoding the chrominance DC coefficient differences. + * The table represents Table K.4 of ITU-T.81 + */ +const u8 v4l2_jpeg_ref_table_chroma_dc_ht[V4L2_JPEG_REF_HT_DC_LEN] = { + 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B +}; +EXPORT_SYMBOL_GPL(v4l2_jpeg_ref_table_chroma_dc_ht); + +/* + * Contains the data that needs to be sent in the marker segment of an + * interchange format JPEG stream or an abbreviated format table specification + * data stream. Specifies the huffman table used for encoding the chrominance + * AC coefficients. The table represents Table K.6 of ITU-T.81 + */ +const u8 v4l2_jpeg_ref_table_chroma_ac_ht[V4L2_JPEG_REF_HT_AC_LEN] = { + 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, + 0x00, 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, + 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, + 0x08, 0x14, 0x42, 0x91, 0xA1, 0xB1, 0xC1, 0x09, 0x23, 0x33, 0x52, 0xF0, + 0x15, 0x62, 0x72, 0xD1, 0x0A, 0x16, 0x24, 0x34, 0xE1, 0x25, 0xF1, 0x17, + 0x18, 0x19, 0x1A, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, + 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x82, 0x83, + 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, + 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, + 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, + 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, + 0xD7, 0xD8, 0xD9, 0xDA, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, + 0xEA, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA +}; +EXPORT_SYMBOL_GPL(v4l2_jpeg_ref_table_chroma_ac_ht); + /** * struct jpeg_stream - JPEG byte stream * @curr: current position in stream diff --git a/drivers/media/v4l2-core/v4l2-mc.c b/drivers/media/v4l2-core/v4l2-mc.c index 52d349e72b8c..937d358697e1 100644 --- a/drivers/media/v4l2-core/v4l2-mc.c +++ b/drivers/media/v4l2-core/v4l2-mc.c @@ -329,7 +329,7 @@ int v4l2_create_fwnode_links_to_pad(struct v4l2_subdev *src_sd, if (!(sink->flags & MEDIA_PAD_FL_SINK)) return -EINVAL; - fwnode_graph_for_each_endpoint(dev_fwnode(src_sd->dev), endpoint) { + fwnode_graph_for_each_endpoint(src_sd->fwnode, endpoint) { struct fwnode_handle *remote_ep; int src_idx, sink_idx, ret; struct media_pad *src; @@ -337,12 +337,18 @@ int v4l2_create_fwnode_links_to_pad(struct v4l2_subdev *src_sd, src_idx = media_entity_get_fwnode_pad(&src_sd->entity, endpoint, MEDIA_PAD_FL_SOURCE); - if (src_idx < 0) + if (src_idx < 0) { + dev_dbg(src_sd->dev, "no source pad found for %pfw\n", + endpoint); continue; + } remote_ep = fwnode_graph_get_remote_endpoint(endpoint); - if (!remote_ep) + if (!remote_ep) { + dev_dbg(src_sd->dev, "no remote ep found for %pfw\n", + endpoint); continue; + } /* * ask the sink to verify it owns the remote endpoint, @@ -353,8 +359,12 @@ int v4l2_create_fwnode_links_to_pad(struct v4l2_subdev *src_sd, MEDIA_PAD_FL_SINK); fwnode_handle_put(remote_ep); - if (sink_idx < 0 || sink_idx != sink->index) + if (sink_idx < 0 || sink_idx != sink->index) { + dev_dbg(src_sd->dev, + "sink pad index mismatch or error (is %d, expected %u)\n", + sink_idx, sink->index); continue; + } /* * the source endpoint corresponds to one of its source pads, @@ -367,8 +377,13 @@ int v4l2_create_fwnode_links_to_pad(struct v4l2_subdev *src_sd, src = &src_sd->entity.pads[src_idx]; /* skip if link already exists */ - if (media_entity_find_link(src, sink)) + if (media_entity_find_link(src, sink)) { + dev_dbg(src_sd->dev, + "link %s:%d -> %s:%d already exists\n", + src_sd->entity.name, src_idx, + sink->entity->name, sink_idx); continue; + } dev_dbg(src_sd->dev, "creating link %s:%d -> %s:%d\n", src_sd->entity.name, src_idx, diff --git a/drivers/media/v4l2-core/v4l2-mem2mem.c b/drivers/media/v4l2-core/v4l2-mem2mem.c index 9e983176542b..eb22d6172462 100644 --- a/drivers/media/v4l2-core/v4l2-mem2mem.c +++ b/drivers/media/v4l2-core/v4l2-mem2mem.c @@ -1087,11 +1087,17 @@ static int v4l2_m2m_register_entity(struct media_device *mdev, entity->function = function; ret = media_entity_pads_init(entity, num_pads, pads); - if (ret) + if (ret) { + kfree(entity->name); + entity->name = NULL; return ret; + } ret = media_device_register_entity(mdev, entity); - if (ret) + if (ret) { + kfree(entity->name); + entity->name = NULL; return ret; + } return 0; } @@ -1380,6 +1386,21 @@ int v4l2_m2m_ioctl_create_bufs(struct file *file, void *priv, } EXPORT_SYMBOL_GPL(v4l2_m2m_ioctl_create_bufs); +int v4l2_m2m_ioctl_remove_bufs(struct file *file, void *priv, + struct v4l2_remove_buffers *remove) +{ + struct v4l2_fh *fh = file->private_data; + struct vb2_queue *q = v4l2_m2m_get_vq(fh->m2m_ctx, remove->type); + + if (!q) + return -EINVAL; + if (q->type != remove->type) + return -EINVAL; + + return vb2_core_remove_bufs(q, remove->index, remove->count); +} +EXPORT_SYMBOL_GPL(v4l2_m2m_ioctl_remove_bufs); + int v4l2_m2m_ioctl_querybuf(struct file *file, void *priv, struct v4l2_buffer *buf) { diff --git a/drivers/media/v4l2-core/v4l2-spi.c b/drivers/media/v4l2-core/v4l2-spi.c index eadecdff7349..1baf8e63f19e 100644 --- a/drivers/media/v4l2-core/v4l2-spi.c +++ b/drivers/media/v4l2-core/v4l2-spi.c @@ -34,7 +34,7 @@ void v4l2_spi_subdev_init(struct v4l2_subdev *sd, struct spi_device *spi, EXPORT_SYMBOL_GPL(v4l2_spi_subdev_init); struct v4l2_subdev *v4l2_spi_new_subdev(struct v4l2_device *v4l2_dev, - struct spi_master *master, + struct spi_controller *ctlr, struct spi_board_info *info) { struct v4l2_subdev *sd = NULL; @@ -45,7 +45,7 @@ struct v4l2_subdev *v4l2_spi_new_subdev(struct v4l2_device *v4l2_dev, if (info->modalias[0]) request_module(info->modalias); - spi = spi_new_device(master, info); + spi = spi_new_device(ctlr, info); if (!spi || !spi->dev.driver) goto error; @@ -59,7 +59,7 @@ struct v4l2_subdev *v4l2_spi_new_subdev(struct v4l2_device *v4l2_dev, * Register with the v4l2_device which increases the module's * use count as well. */ - if (v4l2_device_register_subdev(v4l2_dev, sd)) + if (__v4l2_device_register_subdev(v4l2_dev, sd, sd->owner)) sd = NULL; /* Decrease the module use count to match the first try_module_get. */ diff --git a/drivers/media/v4l2-core/v4l2-subdev.c b/drivers/media/v4l2-core/v4l2-subdev.c index 4c6198c48dd6..cde1774c9098 100644 --- a/drivers/media/v4l2-core/v4l2-subdev.c +++ b/drivers/media/v4l2-core/v4l2-subdev.c @@ -148,6 +148,23 @@ static int subdev_close(struct file *file) } #endif /* CONFIG_VIDEO_V4L2_SUBDEV_API */ +static void v4l2_subdev_enable_privacy_led(struct v4l2_subdev *sd) +{ +#if IS_REACHABLE(CONFIG_LEDS_CLASS) + if (!IS_ERR_OR_NULL(sd->privacy_led)) + led_set_brightness(sd->privacy_led, + sd->privacy_led->max_brightness); +#endif +} + +static void v4l2_subdev_disable_privacy_led(struct v4l2_subdev *sd) +{ +#if IS_REACHABLE(CONFIG_LEDS_CLASS) + if (!IS_ERR_OR_NULL(sd->privacy_led)) + led_set_brightness(sd->privacy_led, 0); +#endif +} + static inline int check_which(u32 which) { if (which != V4L2_SUBDEV_FORMAT_TRY && @@ -317,6 +334,11 @@ static int call_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, unsigned int i; int ret; +#if defined(CONFIG_MEDIA_CONTROLLER) + if (!(sd->entity.pads[pad].flags & MEDIA_PAD_FL_SOURCE)) + return -EOPNOTSUPP; +#endif + memset(fd, 0, sizeof(*fd)); ret = sd->ops->pad->get_frame_desc(sd, pad, fd); @@ -369,6 +391,36 @@ static int call_set_edid(struct v4l2_subdev *sd, struct v4l2_subdev_edid *edid) return check_edid(sd, edid) ? : sd->ops->pad->set_edid(sd, edid); } +static int call_s_dv_timings(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_dv_timings *timings) +{ + if (!timings) + return -EINVAL; + + return check_pad(sd, pad) ? : + sd->ops->pad->s_dv_timings(sd, pad, timings); +} + +static int call_g_dv_timings(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_dv_timings *timings) +{ + if (!timings) + return -EINVAL; + + return check_pad(sd, pad) ? : + sd->ops->pad->g_dv_timings(sd, pad, timings); +} + +static int call_query_dv_timings(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_dv_timings *timings) +{ + if (!timings) + return -EINVAL; + + return check_pad(sd, pad) ? : + sd->ops->pad->query_dv_timings(sd, pad, timings); +} + static int call_dv_timings_cap(struct v4l2_subdev *sd, struct v4l2_dv_timings_cap *cap) { @@ -404,23 +456,10 @@ static int call_s_stream(struct v4l2_subdev *sd, int enable) * The .s_stream() operation must never be called to start or stop an * already started or stopped subdev. Catch offenders but don't return * an error yet to avoid regressions. - * - * As .s_stream() is mutually exclusive with the .enable_streams() and - * .disable_streams() operation, we can use the enabled_streams field - * to store the subdev streaming state. */ - if (WARN_ON(!!sd->enabled_streams == !!enable)) + if (WARN_ON(sd->s_stream_enabled == !!enable)) return 0; -#if IS_REACHABLE(CONFIG_LEDS_CLASS) - if (!IS_ERR_OR_NULL(sd->privacy_led)) { - if (enable) - led_set_brightness(sd->privacy_led, - sd->privacy_led->max_brightness); - else - led_set_brightness(sd->privacy_led, 0); - } -#endif ret = sd->ops->video->s_stream(sd, enable); if (!enable && ret < 0) { @@ -428,8 +467,14 @@ static int call_s_stream(struct v4l2_subdev *sd, int enable) ret = 0; } - if (!ret) - sd->enabled_streams = enable ? BIT(0) : 0; + if (!ret) { + sd->s_stream_enabled = enable; + + if (enable) + v4l2_subdev_enable_privacy_led(sd); + else + v4l2_subdev_disable_privacy_led(sd); + } return ret; } @@ -487,6 +532,9 @@ static const struct v4l2_subdev_pad_ops v4l2_subdev_call_pad_wrappers = { .set_frame_interval = call_set_frame_interval, .get_edid = call_get_edid, .set_edid = call_set_edid, + .s_dv_timings = call_s_dv_timings, + .g_dv_timings = call_g_dv_timings, + .query_dv_timings = call_query_dv_timings, .dv_timings_cap = call_dv_timings_cap, .enum_dv_timings = call_enum_dv_timings, .get_frame_desc = call_get_frame_desc, @@ -648,10 +696,25 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg, return v4l2_event_dequeue(vfh, arg, file->f_flags & O_NONBLOCK); case VIDIOC_SUBSCRIBE_EVENT: - return v4l2_subdev_call(sd, core, subscribe_event, vfh, arg); + if (v4l2_subdev_has_op(sd, core, subscribe_event)) + return v4l2_subdev_call(sd, core, subscribe_event, + vfh, arg); + + if ((sd->flags & V4L2_SUBDEV_FL_HAS_EVENTS) && + vfh->ctrl_handler) + return v4l2_ctrl_subdev_subscribe_event(sd, vfh, arg); + + return -ENOIOCTLCMD; case VIDIOC_UNSUBSCRIBE_EVENT: - return v4l2_subdev_call(sd, core, unsubscribe_event, vfh, arg); + if (v4l2_subdev_has_op(sd, core, unsubscribe_event)) + return v4l2_subdev_call(sd, core, unsubscribe_event, + vfh, arg); + + if (sd->flags & V4L2_SUBDEV_FL_HAS_EVENTS) + return v4l2_event_subdev_unsubscribe(sd, vfh, arg); + + return -ENOIOCTLCMD; #ifdef CONFIG_VIDEO_ADV_DEBUG case VIDIOC_DBG_G_REGISTER: @@ -732,6 +795,7 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg, memset(&sel, 0, sizeof(sel)); sel.which = crop->which; sel.pad = crop->pad; + sel.stream = crop->stream; sel.target = V4L2_SEL_TGT_CROP; rval = v4l2_subdev_call( @@ -756,6 +820,7 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg, memset(&sel, 0, sizeof(sel)); sel.which = crop->which; sel.pad = crop->pad; + sel.stream = crop->stream; sel.target = V4L2_SEL_TGT_CROP; sel.r = crop->rect; @@ -873,16 +938,16 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg, } case VIDIOC_SUBDEV_QUERY_DV_TIMINGS: - return v4l2_subdev_call(sd, video, query_dv_timings, arg); + return v4l2_subdev_call(sd, pad, query_dv_timings, 0, arg); case VIDIOC_SUBDEV_G_DV_TIMINGS: - return v4l2_subdev_call(sd, video, g_dv_timings, arg); + return v4l2_subdev_call(sd, pad, g_dv_timings, 0, arg); case VIDIOC_SUBDEV_S_DV_TIMINGS: if (ro_subdev) return -EPERM; - return v4l2_subdev_call(sd, video, s_dv_timings, arg); + return v4l2_subdev_call(sd, pad, s_dv_timings, 0, arg); case VIDIOC_SUBDEV_G_STD: return v4l2_subdev_call(sd, video, g_std, arg); @@ -923,14 +988,10 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg, krouting = &state->routing; - if (routing->num_routes < krouting->num_routes) { - routing->num_routes = krouting->num_routes; - return -ENOSPC; - } - memcpy((struct v4l2_subdev_route *)(uintptr_t)routing->routes, krouting->routes, - krouting->num_routes * sizeof(*krouting->routes)); + min(krouting->num_routes, routing->len_routes) * + sizeof(*krouting->routes)); routing->num_routes = krouting->num_routes; return 0; @@ -952,6 +1013,9 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg, if (routing->which != V4L2_SUBDEV_FORMAT_TRY && ro_subdev) return -EPERM; + if (routing->num_routes > routing->len_routes) + return -EINVAL; + memset(routing->reserved, 0, sizeof(routing->reserved)); for (i = 0; i < routing->num_routes; ++i) { @@ -977,11 +1041,36 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg, return -EINVAL; } + /* + * If the driver doesn't support setting routing, just return + * the routing table. + */ + if (!v4l2_subdev_has_op(sd, pad, set_routing)) { + memcpy((struct v4l2_subdev_route *)(uintptr_t)routing->routes, + state->routing.routes, + min(state->routing.num_routes, routing->len_routes) * + sizeof(*state->routing.routes)); + routing->num_routes = state->routing.num_routes; + + return 0; + } + krouting.num_routes = routing->num_routes; + krouting.len_routes = routing->len_routes; krouting.routes = routes; - return v4l2_subdev_call(sd, pad, set_routing, state, + rval = v4l2_subdev_call(sd, pad, set_routing, state, routing->which, &krouting); + if (rval < 0) + return rval; + + memcpy((struct v4l2_subdev_route *)(uintptr_t)routing->routes, + state->routing.routes, + min(state->routing.num_routes, routing->len_routes) * + sizeof(*state->routing.routes)); + routing->num_routes = state->routing.num_routes; + + return 0; } case VIDIOC_SUBDEV_G_CLIENT_CAP: { @@ -1200,14 +1289,6 @@ v4l2_subdev_link_validate_get_format(struct media_pad *pad, u32 stream, struct v4l2_subdev *sd; int ret; - if (!is_media_entity_v4l2_subdev(pad->entity)) { - WARN(pad->entity->function != MEDIA_ENT_F_IO_V4L, - "Driver bug! Wrong media entity type 0x%08x, entity %s\n", - pad->entity->function, pad->entity->name); - - return -EINVAL; - } - sd = media_entity_to_v4l2_subdev(pad->entity); fmt->which = V4L2_SUBDEV_FORMAT_ACTIVE; @@ -1382,16 +1463,53 @@ int v4l2_subdev_link_validate(struct media_link *link) bool states_locked; int ret; - if (!is_media_entity_v4l2_subdev(link->sink->entity) || - !is_media_entity_v4l2_subdev(link->source->entity)) { - pr_warn_once("%s of link '%s':%u->'%s':%u is not a V4L2 sub-device, driver bug!\n", - !is_media_entity_v4l2_subdev(link->sink->entity) ? - "sink" : "source", - link->source->entity->name, link->source->index, - link->sink->entity->name, link->sink->index); - return 0; + /* + * Links are validated in the context of the sink entity. Usage of this + * helper on a sink that is not a subdev is a clear driver bug. + */ + if (WARN_ON_ONCE(!is_media_entity_v4l2_subdev(link->sink->entity))) + return -EINVAL; + + /* + * If the source is a video device, delegate link validation to it. This + * allows usage of this helper for subdev connected to a video output + * device, provided that the driver implement the video output device's + * .link_validate() operation. + */ + if (is_media_entity_v4l2_video_device(link->source->entity)) { + struct media_entity *source = link->source->entity; + + if (!source->ops || !source->ops->link_validate) { + /* + * Many existing drivers do not implement the required + * .link_validate() operation for their video devices. + * Print a warning to get the drivers fixed, and return + * 0 to avoid breaking userspace. This should + * eventually be turned into a WARN_ON() when all + * drivers will have been fixed. + */ + pr_warn_once("video device '%s' does not implement .link_validate(), driver bug!\n", + source->name); + return 0; + } + + /* + * Avoid infinite loops in case a video device incorrectly uses + * this helper function as its .link_validate() handler. + */ + if (WARN_ON(source->ops->link_validate == v4l2_subdev_link_validate)) + return -EINVAL; + + return source->ops->link_validate(link); } + /* + * If the source is still not a subdev, usage of this helper is a clear + * driver bug. + */ + if (WARN_ON(!is_media_entity_v4l2_subdev(link->source->entity))) + return -EINVAL; + sink_sd = media_entity_to_v4l2_subdev(link->sink->entity); source_sd = media_entity_to_v4l2_subdev(link->source->entity); @@ -1400,17 +1518,13 @@ int v4l2_subdev_link_validate(struct media_link *link) states_locked = sink_state && source_state; - if (states_locked) { - v4l2_subdev_lock_state(sink_state); - v4l2_subdev_lock_state(source_state); - } + if (states_locked) + v4l2_subdev_lock_states(sink_state, source_state); ret = v4l2_subdev_link_validate_locked(link, states_locked); - if (states_locked) { - v4l2_subdev_unlock_state(sink_state); - v4l2_subdev_unlock_state(source_state); - } + if (states_locked) + v4l2_subdev_unlock_states(sink_state, source_state); return ret; } @@ -1519,6 +1633,36 @@ int __v4l2_subdev_init_finalize(struct v4l2_subdev *sd, const char *name, struct lock_class_key *key) { struct v4l2_subdev_state *state; + struct device *dev = sd->dev; + bool has_disable_streams; + bool has_enable_streams; + bool has_s_stream; + + /* Check that the subdevice implements the required features */ + + has_s_stream = v4l2_subdev_has_op(sd, video, s_stream); + has_enable_streams = v4l2_subdev_has_op(sd, pad, enable_streams); + has_disable_streams = v4l2_subdev_has_op(sd, pad, disable_streams); + + if (has_enable_streams != has_disable_streams) { + dev_err(dev, + "subdev '%s' must implement both or neither of .enable_streams() and .disable_streams()\n", + sd->name); + return -EINVAL; + } + + if (sd->flags & V4L2_SUBDEV_FL_STREAMS) { + if (has_s_stream && !has_enable_streams) { + dev_err(dev, + "subdev '%s' must implement .enable/disable_streams()\n", + sd->name); + + return -EINVAL; + } + } + + if (sd->ctrl_handler) + sd->flags |= V4L2_SUBDEV_FL_HAS_EVENTS; state = __v4l2_subdev_state_alloc(sd, name, key); if (IS_ERR(state)) @@ -2063,43 +2207,55 @@ out: } EXPORT_SYMBOL_GPL(v4l2_subdev_routing_validate); -static int v4l2_subdev_enable_streams_fallback(struct v4l2_subdev *sd, u32 pad, - u64 streams_mask) +static void v4l2_subdev_collect_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask, + u64 *found_streams, + u64 *enabled_streams) { - struct device *dev = sd->entity.graph_obj.mdev->dev; - unsigned int i; - int ret; + if (!(sd->flags & V4L2_SUBDEV_FL_STREAMS)) { + *found_streams = BIT_ULL(0); + *enabled_streams = + (sd->enabled_pads & BIT_ULL(pad)) ? BIT_ULL(0) : 0; + return; + } - /* - * The subdev doesn't implement pad-based stream enable, fall back - * on the .s_stream() operation. This can only be done for subdevs that - * have a single source pad, as sd->enabled_streams is global to the - * subdev. - */ - if (!(sd->entity.pads[pad].flags & MEDIA_PAD_FL_SOURCE)) - return -EOPNOTSUPP; + *found_streams = 0; + *enabled_streams = 0; - for (i = 0; i < sd->entity.num_pads; ++i) { - if (i != pad && sd->entity.pads[i].flags & MEDIA_PAD_FL_SOURCE) - return -EOPNOTSUPP; - } + for (unsigned int i = 0; i < state->stream_configs.num_configs; ++i) { + const struct v4l2_subdev_stream_config *cfg = + &state->stream_configs.configs[i]; - if (sd->enabled_streams & streams_mask) { - dev_dbg(dev, "set of streams %#llx already enabled on %s:%u\n", - streams_mask, sd->entity.name, pad); - return -EALREADY; + if (cfg->pad != pad || !(streams_mask & BIT_ULL(cfg->stream))) + continue; + + *found_streams |= BIT_ULL(cfg->stream); + if (cfg->enabled) + *enabled_streams |= BIT_ULL(cfg->stream); } +} - /* Start streaming when the first streams are enabled. */ - if (!sd->enabled_streams) { - ret = v4l2_subdev_call(sd, video, s_stream, 1); - if (ret) - return ret; +static void v4l2_subdev_set_streams_enabled(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask, + bool enabled) +{ + if (!(sd->flags & V4L2_SUBDEV_FL_STREAMS)) { + if (enabled) + sd->enabled_pads |= BIT_ULL(pad); + else + sd->enabled_pads &= ~BIT_ULL(pad); + return; } - sd->enabled_streams |= streams_mask; + for (unsigned int i = 0; i < state->stream_configs.num_configs; ++i) { + struct v4l2_subdev_stream_config *cfg = + &state->stream_configs.configs[i]; - return 0; + if (cfg->pad == pad && (streams_mask & BIT_ULL(cfg->stream))) + cfg->enabled = enabled; + } } int v4l2_subdev_enable_streams(struct v4l2_subdev *sd, u32 pad, @@ -2107,44 +2263,44 @@ int v4l2_subdev_enable_streams(struct v4l2_subdev *sd, u32 pad, { struct device *dev = sd->entity.graph_obj.mdev->dev; struct v4l2_subdev_state *state; - u64 found_streams = 0; - unsigned int i; + bool already_streaming; + u64 enabled_streams; + u64 found_streams; + bool use_s_stream; int ret; /* A few basic sanity checks first. */ if (pad >= sd->entity.num_pads) return -EINVAL; + if (!(sd->entity.pads[pad].flags & MEDIA_PAD_FL_SOURCE)) + return -EOPNOTSUPP; + + /* + * We use a 64-bit bitmask for tracking enabled pads, so only subdevices + * with 64 pads or less can be supported. + */ + if (pad >= sizeof(sd->enabled_pads) * BITS_PER_BYTE) + return -EOPNOTSUPP; + if (!streams_mask) return 0; /* Fallback on .s_stream() if .enable_streams() isn't available. */ - if (!sd->ops->pad || !sd->ops->pad->enable_streams) - return v4l2_subdev_enable_streams_fallback(sd, pad, - streams_mask); + use_s_stream = !v4l2_subdev_has_op(sd, pad, enable_streams); - state = v4l2_subdev_lock_and_get_active_state(sd); + if (!use_s_stream) + state = v4l2_subdev_lock_and_get_active_state(sd); + else + state = NULL; /* * Verify that the requested streams exist and that they are not * already enabled. */ - for (i = 0; i < state->stream_configs.num_configs; ++i) { - struct v4l2_subdev_stream_config *cfg = - &state->stream_configs.configs[i]; - - if (cfg->pad != pad || !(streams_mask & BIT_ULL(cfg->stream))) - continue; - found_streams |= BIT_ULL(cfg->stream); - - if (cfg->enabled) { - dev_dbg(dev, "stream %u already enabled on %s:%u\n", - cfg->stream, sd->entity.name, pad); - ret = -EALREADY; - goto done; - } - } + v4l2_subdev_collect_streams(sd, state, pad, streams_mask, + &found_streams, &enabled_streams); if (found_streams != streams_mask) { dev_dbg(dev, "streams 0x%llx not found on %s:%u\n", @@ -2153,11 +2309,29 @@ int v4l2_subdev_enable_streams(struct v4l2_subdev *sd, u32 pad, goto done; } + if (enabled_streams) { + dev_dbg(dev, "streams 0x%llx already enabled on %s:%u\n", + enabled_streams, sd->entity.name, pad); + ret = -EALREADY; + goto done; + } + dev_dbg(dev, "enable streams %u:%#llx\n", pad, streams_mask); - /* Call the .enable_streams() operation. */ - ret = v4l2_subdev_call(sd, pad, enable_streams, state, pad, - streams_mask); + already_streaming = v4l2_subdev_is_streaming(sd); + + if (!use_s_stream) { + /* Call the .enable_streams() operation. */ + ret = v4l2_subdev_call(sd, pad, enable_streams, state, pad, + streams_mask); + } else { + /* Start streaming when the first pad is enabled. */ + if (!already_streaming) + ret = v4l2_subdev_call(sd, video, s_stream, 1); + else + ret = 0; + } + if (ret) { dev_dbg(dev, "enable streams %u:%#llx failed: %d\n", pad, streams_mask, ret); @@ -2165,103 +2339,68 @@ int v4l2_subdev_enable_streams(struct v4l2_subdev *sd, u32 pad, } /* Mark the streams as enabled. */ - for (i = 0; i < state->stream_configs.num_configs; ++i) { - struct v4l2_subdev_stream_config *cfg = - &state->stream_configs.configs[i]; + v4l2_subdev_set_streams_enabled(sd, state, pad, streams_mask, true); - if (cfg->pad == pad && (streams_mask & BIT_ULL(cfg->stream))) - cfg->enabled = true; - } + /* + * TODO: When all the drivers have been changed to use + * v4l2_subdev_enable_streams() and v4l2_subdev_disable_streams(), + * instead of calling .s_stream() operation directly, we can remove + * the privacy LED handling from call_s_stream() and do it here + * for all cases. + */ + if (!use_s_stream && !already_streaming) + v4l2_subdev_enable_privacy_led(sd); done: - v4l2_subdev_unlock_state(state); + if (!use_s_stream) + v4l2_subdev_unlock_state(state); return ret; } EXPORT_SYMBOL_GPL(v4l2_subdev_enable_streams); -static int v4l2_subdev_disable_streams_fallback(struct v4l2_subdev *sd, u32 pad, - u64 streams_mask) -{ - struct device *dev = sd->entity.graph_obj.mdev->dev; - unsigned int i; - int ret; - - /* - * If the subdev doesn't implement pad-based stream enable, fall back - * on the .s_stream() operation. This can only be done for subdevs that - * have a single source pad, as sd->enabled_streams is global to the - * subdev. - */ - if (!(sd->entity.pads[pad].flags & MEDIA_PAD_FL_SOURCE)) - return -EOPNOTSUPP; - - for (i = 0; i < sd->entity.num_pads; ++i) { - if (i != pad && sd->entity.pads[i].flags & MEDIA_PAD_FL_SOURCE) - return -EOPNOTSUPP; - } - - if ((sd->enabled_streams & streams_mask) != streams_mask) { - dev_dbg(dev, "set of streams %#llx already disabled on %s:%u\n", - streams_mask, sd->entity.name, pad); - return -EALREADY; - } - - /* Stop streaming when the last streams are disabled. */ - if (!(sd->enabled_streams & ~streams_mask)) { - ret = v4l2_subdev_call(sd, video, s_stream, 0); - if (ret) - return ret; - } - - sd->enabled_streams &= ~streams_mask; - - return 0; -} - int v4l2_subdev_disable_streams(struct v4l2_subdev *sd, u32 pad, u64 streams_mask) { struct device *dev = sd->entity.graph_obj.mdev->dev; struct v4l2_subdev_state *state; - u64 found_streams = 0; - unsigned int i; + u64 enabled_streams; + u64 found_streams; + bool use_s_stream; int ret; /* A few basic sanity checks first. */ if (pad >= sd->entity.num_pads) return -EINVAL; + if (!(sd->entity.pads[pad].flags & MEDIA_PAD_FL_SOURCE)) + return -EOPNOTSUPP; + + /* + * We use a 64-bit bitmask for tracking enabled pads, so only subdevices + * with 64 pads or less can be supported. + */ + if (pad >= sizeof(sd->enabled_pads) * BITS_PER_BYTE) + return -EOPNOTSUPP; + if (!streams_mask) return 0; /* Fallback on .s_stream() if .disable_streams() isn't available. */ - if (!sd->ops->pad || !sd->ops->pad->disable_streams) - return v4l2_subdev_disable_streams_fallback(sd, pad, - streams_mask); + use_s_stream = !v4l2_subdev_has_op(sd, pad, disable_streams); - state = v4l2_subdev_lock_and_get_active_state(sd); + if (!use_s_stream) + state = v4l2_subdev_lock_and_get_active_state(sd); + else + state = NULL; /* * Verify that the requested streams exist and that they are not * already disabled. */ - for (i = 0; i < state->stream_configs.num_configs; ++i) { - struct v4l2_subdev_stream_config *cfg = - &state->stream_configs.configs[i]; - - if (cfg->pad != pad || !(streams_mask & BIT_ULL(cfg->stream))) - continue; - found_streams |= BIT_ULL(cfg->stream); - - if (!cfg->enabled) { - dev_dbg(dev, "stream %u already disabled on %s:%u\n", - cfg->stream, sd->entity.name, pad); - ret = -EALREADY; - goto done; - } - } + v4l2_subdev_collect_streams(sd, state, pad, streams_mask, + &found_streams, &enabled_streams); if (found_streams != streams_mask) { dev_dbg(dev, "streams 0x%llx not found on %s:%u\n", @@ -2270,28 +2409,43 @@ int v4l2_subdev_disable_streams(struct v4l2_subdev *sd, u32 pad, goto done; } + if (enabled_streams != streams_mask) { + dev_dbg(dev, "streams 0x%llx already disabled on %s:%u\n", + streams_mask & ~enabled_streams, sd->entity.name, pad); + ret = -EALREADY; + goto done; + } + dev_dbg(dev, "disable streams %u:%#llx\n", pad, streams_mask); - /* Call the .disable_streams() operation. */ - ret = v4l2_subdev_call(sd, pad, disable_streams, state, pad, - streams_mask); + if (!use_s_stream) { + /* Call the .disable_streams() operation. */ + ret = v4l2_subdev_call(sd, pad, disable_streams, state, pad, + streams_mask); + } else { + /* Stop streaming when the last streams are disabled. */ + + if (!(sd->enabled_pads & ~BIT_ULL(pad))) + ret = v4l2_subdev_call(sd, video, s_stream, 0); + else + ret = 0; + } + if (ret) { dev_dbg(dev, "disable streams %u:%#llx failed: %d\n", pad, streams_mask, ret); goto done; } - /* Mark the streams as disabled. */ - for (i = 0; i < state->stream_configs.num_configs; ++i) { - struct v4l2_subdev_stream_config *cfg = - &state->stream_configs.configs[i]; - - if (cfg->pad == pad && (streams_mask & BIT_ULL(cfg->stream))) - cfg->enabled = false; - } + v4l2_subdev_set_streams_enabled(sd, state, pad, streams_mask, false); done: - v4l2_subdev_unlock_state(state); + if (!use_s_stream) { + if (!v4l2_subdev_is_streaming(sd)) + v4l2_subdev_disable_privacy_led(sd); + + v4l2_subdev_unlock_state(state); + } return ret; } @@ -2320,15 +2474,24 @@ int v4l2_subdev_s_stream_helper(struct v4l2_subdev *sd, int enable) if (WARN_ON(pad_index == -1)) return -EINVAL; - /* - * As there's a single source pad, just collect all the source streams. - */ - state = v4l2_subdev_lock_and_get_active_state(sd); + if (sd->flags & V4L2_SUBDEV_FL_STREAMS) { + /* + * As there's a single source pad, just collect all the source + * streams. + */ + state = v4l2_subdev_lock_and_get_active_state(sd); - for_each_active_route(&state->routing, route) - source_mask |= BIT_ULL(route->source_stream); + for_each_active_route(&state->routing, route) + source_mask |= BIT_ULL(route->source_stream); - v4l2_subdev_unlock_state(state); + v4l2_subdev_unlock_state(state); + } else { + /* + * For non-streams subdevices, there's a single implicit stream + * per pad. + */ + source_mask = BIT_ULL(0); + } if (enable) return v4l2_subdev_enable_streams(sd, pad_index, source_mask); @@ -2370,6 +2533,31 @@ void v4l2_subdev_notify_event(struct v4l2_subdev *sd, } EXPORT_SYMBOL_GPL(v4l2_subdev_notify_event); +bool v4l2_subdev_is_streaming(struct v4l2_subdev *sd) +{ + struct v4l2_subdev_state *state; + + if (!v4l2_subdev_has_op(sd, pad, enable_streams)) + return sd->s_stream_enabled; + + if (!(sd->flags & V4L2_SUBDEV_FL_STREAMS)) + return !!sd->enabled_pads; + + state = v4l2_subdev_get_locked_active_state(sd); + + for (unsigned int i = 0; i < state->stream_configs.num_configs; ++i) { + const struct v4l2_subdev_stream_config *cfg; + + cfg = &state->stream_configs.configs[i]; + + if (cfg->enabled) + return true; + } + + return false; +} +EXPORT_SYMBOL_GPL(v4l2_subdev_is_streaming); + int v4l2_subdev_get_privacy_led(struct v4l2_subdev *sd) { #if IS_REACHABLE(CONFIG_LEDS_CLASS) |