X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=blobdiff_plain;f=kernel%2Fdrivers%2Fmedia%2Fv4l2-core%2Fv4l2-subdev.c;fp=kernel%2Fdrivers%2Fmedia%2Fv4l2-core%2Fv4l2-subdev.c;h=63596063b21362fe82f51a37c0c91b5f860d926d;hb=9ca8dbcc65cfc63d6f5ef3312a33184e1d726e00;hp=0000000000000000000000000000000000000000;hpb=98260f3884f4a202f9ca5eabed40b1354c489b29;p=kvmfornfv.git diff --git a/kernel/drivers/media/v4l2-core/v4l2-subdev.c b/kernel/drivers/media/v4l2-core/v4l2-subdev.c new file mode 100644 index 000000000..63596063b --- /dev/null +++ b/kernel/drivers/media/v4l2-core/v4l2-subdev.c @@ -0,0 +1,590 @@ +/* + * V4L2 sub-device + * + * Copyright (C) 2010 Nokia Corporation + * + * Contact: Laurent Pinchart + * Sakari Ailus + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +static int subdev_fh_init(struct v4l2_subdev_fh *fh, struct v4l2_subdev *sd) +{ +#if defined(CONFIG_VIDEO_V4L2_SUBDEV_API) + fh->pad = kzalloc(sizeof(*fh->pad) * sd->entity.num_pads, GFP_KERNEL); + if (fh->pad == NULL) + return -ENOMEM; +#endif + return 0; +} + +static void subdev_fh_free(struct v4l2_subdev_fh *fh) +{ +#if defined(CONFIG_VIDEO_V4L2_SUBDEV_API) + kfree(fh->pad); + fh->pad = NULL; +#endif +} + +static int subdev_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct v4l2_subdev *sd = vdev_to_v4l2_subdev(vdev); + struct v4l2_subdev_fh *subdev_fh; +#if defined(CONFIG_MEDIA_CONTROLLER) + struct media_entity *entity = NULL; +#endif + int ret; + + subdev_fh = kzalloc(sizeof(*subdev_fh), GFP_KERNEL); + if (subdev_fh == NULL) + return -ENOMEM; + + ret = subdev_fh_init(subdev_fh, sd); + if (ret) { + kfree(subdev_fh); + return ret; + } + + v4l2_fh_init(&subdev_fh->vfh, vdev); + v4l2_fh_add(&subdev_fh->vfh); + file->private_data = &subdev_fh->vfh; +#if defined(CONFIG_MEDIA_CONTROLLER) + if (sd->v4l2_dev->mdev) { + entity = media_entity_get(&sd->entity); + if (!entity) { + ret = -EBUSY; + goto err; + } + } +#endif + + if (sd->internal_ops && sd->internal_ops->open) { + ret = sd->internal_ops->open(sd, subdev_fh); + if (ret < 0) + goto err; + } + + return 0; + +err: +#if defined(CONFIG_MEDIA_CONTROLLER) + media_entity_put(entity); +#endif + v4l2_fh_del(&subdev_fh->vfh); + v4l2_fh_exit(&subdev_fh->vfh); + subdev_fh_free(subdev_fh); + kfree(subdev_fh); + + return ret; +} + +static int subdev_close(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct v4l2_subdev *sd = vdev_to_v4l2_subdev(vdev); + struct v4l2_fh *vfh = file->private_data; + struct v4l2_subdev_fh *subdev_fh = to_v4l2_subdev_fh(vfh); + + if (sd->internal_ops && sd->internal_ops->close) + sd->internal_ops->close(sd, subdev_fh); +#if defined(CONFIG_MEDIA_CONTROLLER) + if (sd->v4l2_dev->mdev) + media_entity_put(&sd->entity); +#endif + v4l2_fh_del(vfh); + v4l2_fh_exit(vfh); + subdev_fh_free(subdev_fh); + kfree(subdev_fh); + file->private_data = NULL; + + return 0; +} + +#if defined(CONFIG_VIDEO_V4L2_SUBDEV_API) +static int check_format(struct v4l2_subdev *sd, + struct v4l2_subdev_format *format) +{ + if (format->which != V4L2_SUBDEV_FORMAT_TRY && + format->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + if (format->pad >= sd->entity.num_pads) + return -EINVAL; + + return 0; +} + +static int check_crop(struct v4l2_subdev *sd, struct v4l2_subdev_crop *crop) +{ + if (crop->which != V4L2_SUBDEV_FORMAT_TRY && + crop->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + if (crop->pad >= sd->entity.num_pads) + return -EINVAL; + + return 0; +} + +static int check_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_selection *sel) +{ + if (sel->which != V4L2_SUBDEV_FORMAT_TRY && + sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + if (sel->pad >= sd->entity.num_pads) + return -EINVAL; + + return 0; +} + +static int check_edid(struct v4l2_subdev *sd, struct v4l2_subdev_edid *edid) +{ + if (edid->pad >= sd->entity.num_pads) + return -EINVAL; + + if (edid->blocks && edid->edid == NULL) + return -EINVAL; + + return 0; +} +#endif + +static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg) +{ + struct video_device *vdev = video_devdata(file); + struct v4l2_subdev *sd = vdev_to_v4l2_subdev(vdev); + struct v4l2_fh *vfh = file->private_data; +#if defined(CONFIG_VIDEO_V4L2_SUBDEV_API) + struct v4l2_subdev_fh *subdev_fh = to_v4l2_subdev_fh(vfh); + int rval; +#endif + + switch (cmd) { + case VIDIOC_QUERYCTRL: + return v4l2_queryctrl(vfh->ctrl_handler, arg); + + case VIDIOC_QUERY_EXT_CTRL: + return v4l2_query_ext_ctrl(vfh->ctrl_handler, arg); + + case VIDIOC_QUERYMENU: + return v4l2_querymenu(vfh->ctrl_handler, arg); + + case VIDIOC_G_CTRL: + return v4l2_g_ctrl(vfh->ctrl_handler, arg); + + case VIDIOC_S_CTRL: + return v4l2_s_ctrl(vfh, vfh->ctrl_handler, arg); + + case VIDIOC_G_EXT_CTRLS: + return v4l2_g_ext_ctrls(vfh->ctrl_handler, arg); + + case VIDIOC_S_EXT_CTRLS: + return v4l2_s_ext_ctrls(vfh, vfh->ctrl_handler, arg); + + case VIDIOC_TRY_EXT_CTRLS: + return v4l2_try_ext_ctrls(vfh->ctrl_handler, arg); + + case VIDIOC_DQEVENT: + if (!(sd->flags & V4L2_SUBDEV_FL_HAS_EVENTS)) + return -ENOIOCTLCMD; + + 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); + + case VIDIOC_UNSUBSCRIBE_EVENT: + return v4l2_subdev_call(sd, core, unsubscribe_event, vfh, arg); + +#ifdef CONFIG_VIDEO_ADV_DEBUG + case VIDIOC_DBG_G_REGISTER: + { + struct v4l2_dbg_register *p = arg; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + return v4l2_subdev_call(sd, core, g_register, p); + } + case VIDIOC_DBG_S_REGISTER: + { + struct v4l2_dbg_register *p = arg; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + return v4l2_subdev_call(sd, core, s_register, p); + } +#endif + + case VIDIOC_LOG_STATUS: { + int ret; + + pr_info("%s: ================= START STATUS =================\n", + sd->name); + ret = v4l2_subdev_call(sd, core, log_status); + pr_info("%s: ================== END STATUS ==================\n", + sd->name); + return ret; + } + +#if defined(CONFIG_VIDEO_V4L2_SUBDEV_API) + case VIDIOC_SUBDEV_G_FMT: { + struct v4l2_subdev_format *format = arg; + + rval = check_format(sd, format); + if (rval) + return rval; + + return v4l2_subdev_call(sd, pad, get_fmt, subdev_fh->pad, format); + } + + case VIDIOC_SUBDEV_S_FMT: { + struct v4l2_subdev_format *format = arg; + + rval = check_format(sd, format); + if (rval) + return rval; + + return v4l2_subdev_call(sd, pad, set_fmt, subdev_fh->pad, format); + } + + case VIDIOC_SUBDEV_G_CROP: { + struct v4l2_subdev_crop *crop = arg; + struct v4l2_subdev_selection sel; + + rval = check_crop(sd, crop); + if (rval) + return rval; + + memset(&sel, 0, sizeof(sel)); + sel.which = crop->which; + sel.pad = crop->pad; + sel.target = V4L2_SEL_TGT_CROP; + + rval = v4l2_subdev_call( + sd, pad, get_selection, subdev_fh->pad, &sel); + + crop->rect = sel.r; + + return rval; + } + + case VIDIOC_SUBDEV_S_CROP: { + struct v4l2_subdev_crop *crop = arg; + struct v4l2_subdev_selection sel; + + rval = check_crop(sd, crop); + if (rval) + return rval; + + memset(&sel, 0, sizeof(sel)); + sel.which = crop->which; + sel.pad = crop->pad; + sel.target = V4L2_SEL_TGT_CROP; + sel.r = crop->rect; + + rval = v4l2_subdev_call( + sd, pad, set_selection, subdev_fh->pad, &sel); + + crop->rect = sel.r; + + return rval; + } + + case VIDIOC_SUBDEV_ENUM_MBUS_CODE: { + struct v4l2_subdev_mbus_code_enum *code = arg; + + if (code->which != V4L2_SUBDEV_FORMAT_TRY && + code->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + if (code->pad >= sd->entity.num_pads) + return -EINVAL; + + return v4l2_subdev_call(sd, pad, enum_mbus_code, subdev_fh->pad, + code); + } + + case VIDIOC_SUBDEV_ENUM_FRAME_SIZE: { + struct v4l2_subdev_frame_size_enum *fse = arg; + + if (fse->which != V4L2_SUBDEV_FORMAT_TRY && + fse->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + if (fse->pad >= sd->entity.num_pads) + return -EINVAL; + + return v4l2_subdev_call(sd, pad, enum_frame_size, subdev_fh->pad, + fse); + } + + case VIDIOC_SUBDEV_G_FRAME_INTERVAL: { + struct v4l2_subdev_frame_interval *fi = arg; + + if (fi->pad >= sd->entity.num_pads) + return -EINVAL; + + return v4l2_subdev_call(sd, video, g_frame_interval, arg); + } + + case VIDIOC_SUBDEV_S_FRAME_INTERVAL: { + struct v4l2_subdev_frame_interval *fi = arg; + + if (fi->pad >= sd->entity.num_pads) + return -EINVAL; + + return v4l2_subdev_call(sd, video, s_frame_interval, arg); + } + + case VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL: { + struct v4l2_subdev_frame_interval_enum *fie = arg; + + if (fie->which != V4L2_SUBDEV_FORMAT_TRY && + fie->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + if (fie->pad >= sd->entity.num_pads) + return -EINVAL; + + return v4l2_subdev_call(sd, pad, enum_frame_interval, subdev_fh->pad, + fie); + } + + case VIDIOC_SUBDEV_G_SELECTION: { + struct v4l2_subdev_selection *sel = arg; + + rval = check_selection(sd, sel); + if (rval) + return rval; + + return v4l2_subdev_call( + sd, pad, get_selection, subdev_fh->pad, sel); + } + + case VIDIOC_SUBDEV_S_SELECTION: { + struct v4l2_subdev_selection *sel = arg; + + rval = check_selection(sd, sel); + if (rval) + return rval; + + return v4l2_subdev_call( + sd, pad, set_selection, subdev_fh->pad, sel); + } + + case VIDIOC_G_EDID: { + struct v4l2_subdev_edid *edid = arg; + + rval = check_edid(sd, edid); + if (rval) + return rval; + + return v4l2_subdev_call(sd, pad, get_edid, edid); + } + + case VIDIOC_S_EDID: { + struct v4l2_subdev_edid *edid = arg; + + rval = check_edid(sd, edid); + if (rval) + return rval; + + return v4l2_subdev_call(sd, pad, set_edid, edid); + } + + case VIDIOC_SUBDEV_DV_TIMINGS_CAP: { + struct v4l2_dv_timings_cap *cap = arg; + + if (cap->pad >= sd->entity.num_pads) + return -EINVAL; + + return v4l2_subdev_call(sd, pad, dv_timings_cap, cap); + } + + case VIDIOC_SUBDEV_ENUM_DV_TIMINGS: { + struct v4l2_enum_dv_timings *dvt = arg; + + if (dvt->pad >= sd->entity.num_pads) + return -EINVAL; + + return v4l2_subdev_call(sd, pad, enum_dv_timings, dvt); + } + + case VIDIOC_SUBDEV_QUERY_DV_TIMINGS: + return v4l2_subdev_call(sd, video, query_dv_timings, arg); + + case VIDIOC_SUBDEV_G_DV_TIMINGS: + return v4l2_subdev_call(sd, video, g_dv_timings, arg); + + case VIDIOC_SUBDEV_S_DV_TIMINGS: + return v4l2_subdev_call(sd, video, s_dv_timings, arg); +#endif + default: + return v4l2_subdev_call(sd, core, ioctl, cmd, arg); + } + + return 0; +} + +static long subdev_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return video_usercopy(file, cmd, arg, subdev_do_ioctl); +} + +#ifdef CONFIG_COMPAT +static long subdev_compat_ioctl32(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct video_device *vdev = video_devdata(file); + struct v4l2_subdev *sd = vdev_to_v4l2_subdev(vdev); + + return v4l2_subdev_call(sd, core, compat_ioctl32, cmd, arg); +} +#endif + +static unsigned int subdev_poll(struct file *file, poll_table *wait) +{ + struct video_device *vdev = video_devdata(file); + struct v4l2_subdev *sd = vdev_to_v4l2_subdev(vdev); + struct v4l2_fh *fh = file->private_data; + + if (!(sd->flags & V4L2_SUBDEV_FL_HAS_EVENTS)) + return POLLERR; + + poll_wait(file, &fh->wait, wait); + + if (v4l2_event_pending(fh)) + return POLLPRI; + + return 0; +} + +const struct v4l2_file_operations v4l2_subdev_fops = { + .owner = THIS_MODULE, + .open = subdev_open, + .unlocked_ioctl = subdev_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl32 = subdev_compat_ioctl32, +#endif + .release = subdev_close, + .poll = subdev_poll, +}; + +#ifdef CONFIG_MEDIA_CONTROLLER +int v4l2_subdev_link_validate_default(struct v4l2_subdev *sd, + struct media_link *link, + struct v4l2_subdev_format *source_fmt, + struct v4l2_subdev_format *sink_fmt) +{ + /* The width, height and code must match. */ + if (source_fmt->format.width != sink_fmt->format.width + || source_fmt->format.height != sink_fmt->format.height + || source_fmt->format.code != sink_fmt->format.code) + return -EINVAL; + + /* The field order must match, or the sink field order must be NONE + * to support interlaced hardware connected to bridges that support + * progressive formats only. + */ + if (source_fmt->format.field != sink_fmt->format.field && + sink_fmt->format.field != V4L2_FIELD_NONE) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL_GPL(v4l2_subdev_link_validate_default); + +static int +v4l2_subdev_link_validate_get_format(struct media_pad *pad, + struct v4l2_subdev_format *fmt) +{ + if (media_entity_type(pad->entity) == MEDIA_ENT_T_V4L2_SUBDEV) { + struct v4l2_subdev *sd = + media_entity_to_v4l2_subdev(pad->entity); + + fmt->which = V4L2_SUBDEV_FORMAT_ACTIVE; + fmt->pad = pad->index; + return v4l2_subdev_call(sd, pad, get_fmt, NULL, fmt); + } + + WARN(pad->entity->type != MEDIA_ENT_T_DEVNODE_V4L, + "Driver bug! Wrong media entity type 0x%08x, entity %s\n", + pad->entity->type, pad->entity->name); + + return -EINVAL; +} + +int v4l2_subdev_link_validate(struct media_link *link) +{ + struct v4l2_subdev *sink; + struct v4l2_subdev_format sink_fmt, source_fmt; + int rval; + + rval = v4l2_subdev_link_validate_get_format( + link->source, &source_fmt); + if (rval < 0) + return 0; + + rval = v4l2_subdev_link_validate_get_format( + link->sink, &sink_fmt); + if (rval < 0) + return 0; + + sink = media_entity_to_v4l2_subdev(link->sink->entity); + + rval = v4l2_subdev_call(sink, pad, link_validate, link, + &source_fmt, &sink_fmt); + if (rval != -ENOIOCTLCMD) + return rval; + + return v4l2_subdev_link_validate_default( + sink, link, &source_fmt, &sink_fmt); +} +EXPORT_SYMBOL_GPL(v4l2_subdev_link_validate); +#endif /* CONFIG_MEDIA_CONTROLLER */ + +void v4l2_subdev_init(struct v4l2_subdev *sd, const struct v4l2_subdev_ops *ops) +{ + INIT_LIST_HEAD(&sd->list); + BUG_ON(!ops); + sd->ops = ops; + sd->v4l2_dev = NULL; + sd->flags = 0; + sd->name[0] = '\0'; + sd->grp_id = 0; + sd->dev_priv = NULL; + sd->host_priv = NULL; +#if defined(CONFIG_MEDIA_CONTROLLER) + sd->entity.name = sd->name; + sd->entity.type = MEDIA_ENT_T_V4L2_SUBDEV; +#endif +} +EXPORT_SYMBOL(v4l2_subdev_init);