forked from Minki/linux
8ae5640f1b
Add a new helper function called v4l2_subdev_notify_event() which will deliver the specified event to both the v4l2 subdev event queue as well as to the notify callback. The former is typically used by userspace applications to listen to notification events while the later is used by bridge drivers. Combining both into the same function avoids boilerplate code in subdev drivers. Signed-off-by: Lars-Peter Clausen <lars@metafoo.de> Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com> Signed-off-by: Mauro Carvalho Chehab <mchehab@osg.samsung.com>
609 lines
14 KiB
C
609 lines
14 KiB
C
/*
|
|
* V4L2 sub-device
|
|
*
|
|
* Copyright (C) 2010 Nokia Corporation
|
|
*
|
|
* Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
|
* Sakari Ailus <sakari.ailus@iki.fi>
|
|
*
|
|
* 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 <linux/ioctl.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/types.h>
|
|
#include <linux/videodev2.h>
|
|
#include <linux/export.h>
|
|
|
|
#include <media/v4l2-ctrls.h>
|
|
#include <media/v4l2-device.h>
|
|
#include <media/v4l2-ioctl.h>
|
|
#include <media/v4l2-fh.h>
|
|
#include <media/v4l2-event.h>
|
|
|
|
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);
|
|
|
|
/**
|
|
* v4l2_subdev_notify_event() - Delivers event notification for subdevice
|
|
* @sd: The subdev for which to deliver the event
|
|
* @ev: The event to deliver
|
|
*
|
|
* Will deliver the specified event to all userspace event listeners which are
|
|
* subscribed to the v42l subdev event queue as well as to the bridge driver
|
|
* using the notify callback. The notification type for the notify callback
|
|
* will be V4L2_DEVICE_NOTIFY_EVENT.
|
|
*/
|
|
void v4l2_subdev_notify_event(struct v4l2_subdev *sd,
|
|
const struct v4l2_event *ev)
|
|
{
|
|
v4l2_event_queue(sd->devnode, ev);
|
|
v4l2_subdev_notify(sd, V4L2_DEVICE_NOTIFY_EVENT, (void *)ev);
|
|
}
|
|
EXPORT_SYMBOL_GPL(v4l2_subdev_notify_event);
|