--- /dev/null
+/*
+ * v4l2-event.c
+ *
+ * V4L2 events.
+ *
+ * Copyright (C) 2009--2010 Nokia Corporation.
+ *
+ * Contact: Sakari Ailus <sakari.ailus@maxwell.research.nokia.com>
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <media/v4l2-dev.h>
+#include <media/v4l2-fh.h>
+#include <media/v4l2-event.h>
+
+#include <linux/sched.h>
+#include <linux/slab.h>
+
+int v4l2_event_init(struct v4l2_fh *fh)
+{
+       fh->events = kzalloc(sizeof(*fh->events), GFP_KERNEL);
+       if (fh->events == NULL)
+               return -ENOMEM;
+
+       init_waitqueue_head(&fh->events->wait);
+
+       INIT_LIST_HEAD(&fh->events->free);
+       INIT_LIST_HEAD(&fh->events->available);
+       INIT_LIST_HEAD(&fh->events->subscribed);
+
+       fh->events->sequence = -1;
+
+       return 0;
+}
+
+int v4l2_event_alloc(struct v4l2_fh *fh, unsigned int n)
+{
+       struct v4l2_events *events = fh->events;
+       unsigned long flags;
+
+       if (!events) {
+               WARN_ON(1);
+               return -ENOMEM;
+       }
+
+       while (events->nallocated < n) {
+               struct v4l2_kevent *kev;
+
+               kev = kzalloc(sizeof(*kev), GFP_KERNEL);
+               if (kev == NULL)
+                       return -ENOMEM;
+
+               spin_lock_irqsave(&fh->vdev->fh_lock, flags);
+               list_add_tail(&kev->list, &events->free);
+               events->nallocated++;
+               spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(v4l2_event_alloc);
+
+#define list_kfree(list, type, member)                         \
+       while (!list_empty(list)) {                             \
+               type *hi;                                       \
+               hi = list_first_entry(list, type, member);      \
+               list_del(&hi->member);                          \
+               kfree(hi);                                      \
+       }
+
+void v4l2_event_free(struct v4l2_fh *fh)
+{
+       struct v4l2_events *events = fh->events;
+
+       if (!events)
+               return;
+
+       list_kfree(&events->free, struct v4l2_kevent, list);
+       list_kfree(&events->available, struct v4l2_kevent, list);
+       list_kfree(&events->subscribed, struct v4l2_subscribed_event, list);
+
+       kfree(events);
+       fh->events = NULL;
+}
+EXPORT_SYMBOL_GPL(v4l2_event_free);
+
+static int __v4l2_event_dequeue(struct v4l2_fh *fh, struct v4l2_event *event)
+{
+       struct v4l2_events *events = fh->events;
+       struct v4l2_kevent *kev;
+       unsigned long flags;
+
+       spin_lock_irqsave(&fh->vdev->fh_lock, flags);
+
+       if (list_empty(&events->available)) {
+               spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
+               return -ENOENT;
+       }
+
+       WARN_ON(events->navailable == 0);
+
+       kev = list_first_entry(&events->available, struct v4l2_kevent, list);
+       list_move(&kev->list, &events->free);
+       events->navailable--;
+
+       kev->event.pending = events->navailable;
+       *event = kev->event;
+
+       spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
+
+       return 0;
+}
+
+int v4l2_event_dequeue(struct v4l2_fh *fh, struct v4l2_event *event,
+                      int nonblocking)
+{
+       struct v4l2_events *events = fh->events;
+       int ret;
+
+       if (nonblocking)
+               return __v4l2_event_dequeue(fh, event);
+
+       do {
+               ret = wait_event_interruptible(events->wait,
+                                              events->navailable != 0);
+               if (ret < 0)
+                       return ret;
+
+               ret = __v4l2_event_dequeue(fh, event);
+       } while (ret == -ENOENT);
+
+       return ret;
+}
+
+/* Caller must hold fh->event->lock! */
+static struct v4l2_subscribed_event *v4l2_event_subscribed(
+       struct v4l2_fh *fh, u32 type)
+{
+       struct v4l2_events *events = fh->events;
+       struct v4l2_subscribed_event *sev;
+
+       WARN_ON(!spin_is_locked(&fh->vdev->fh_lock));
+
+       list_for_each_entry(sev, &events->subscribed, list) {
+               if (sev->type == type)
+                       return sev;
+       }
+
+       return NULL;
+}
+
+void v4l2_event_queue(struct video_device *vdev, const struct v4l2_event *ev)
+{
+       struct v4l2_fh *fh;
+       unsigned long flags;
+       struct timespec timestamp;
+
+       ktime_get_ts(×tamp);
+
+       spin_lock_irqsave(&vdev->fh_lock, flags);
+
+       list_for_each_entry(fh, &vdev->fh_list, list) {
+               struct v4l2_events *events = fh->events;
+               struct v4l2_kevent *kev;
+
+               /* Are we subscribed? */
+               if (!v4l2_event_subscribed(fh, ev->type))
+                       continue;
+
+               /* Increase event sequence number on fh. */
+               events->sequence++;
+
+               /* Do we have any free events? */
+               if (list_empty(&events->free))
+                       continue;
+
+               /* Take one and fill it. */
+               kev = list_first_entry(&events->free, struct v4l2_kevent, list);
+               kev->event.type = ev->type;
+               kev->event.u = ev->u;
+               kev->event.timestamp = timestamp;
+               kev->event.sequence = events->sequence;
+               list_move_tail(&kev->list, &events->available);
+
+               events->navailable++;
+
+               wake_up_all(&events->wait);
+       }
+
+       spin_unlock_irqrestore(&vdev->fh_lock, flags);
+}
+EXPORT_SYMBOL_GPL(v4l2_event_queue);
+
+int v4l2_event_pending(struct v4l2_fh *fh)
+{
+       return fh->events->navailable;
+}
+EXPORT_SYMBOL_GPL(v4l2_event_pending);
+
+int v4l2_event_subscribe(struct v4l2_fh *fh,
+                        struct v4l2_event_subscription *sub)
+{
+       struct v4l2_events *events = fh->events;
+       struct v4l2_subscribed_event *sev;
+       unsigned long flags;
+
+       if (fh->events == NULL) {
+               WARN_ON(1);
+               return -ENOMEM;
+       }
+
+       sev = kmalloc(sizeof(*sev), GFP_KERNEL);
+       if (!sev)
+               return -ENOMEM;
+
+       spin_lock_irqsave(&fh->vdev->fh_lock, flags);
+
+       if (v4l2_event_subscribed(fh, sub->type) == NULL) {
+               INIT_LIST_HEAD(&sev->list);
+               sev->type = sub->type;
+
+               list_add(&sev->list, &events->subscribed);
+               sev = NULL;
+       }
+
+       spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
+
+       kfree(sev);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(v4l2_event_subscribe);
+
+static void v4l2_event_unsubscribe_all(struct v4l2_fh *fh)
+{
+       struct v4l2_events *events = fh->events;
+       struct v4l2_subscribed_event *sev;
+       unsigned long flags;
+
+       do {
+               sev = NULL;
+
+               spin_lock_irqsave(&fh->vdev->fh_lock, flags);
+               if (!list_empty(&events->subscribed)) {
+                       sev = list_first_entry(&events->subscribed,
+                                      struct v4l2_subscribed_event, list);
+                       list_del(&sev->list);
+               }
+               spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
+               kfree(sev);
+       } while (sev);
+}
+
+int v4l2_event_unsubscribe(struct v4l2_fh *fh,
+                          struct v4l2_event_subscription *sub)
+{
+       struct v4l2_subscribed_event *sev;
+       unsigned long flags;
+
+       if (sub->type == V4L2_EVENT_ALL) {
+               v4l2_event_unsubscribe_all(fh);
+               return 0;
+       }
+
+       spin_lock_irqsave(&fh->vdev->fh_lock, flags);
+
+       sev = v4l2_event_subscribed(fh, sub->type);
+       if (sev != NULL)
+               list_del(&sev->list);
+
+       spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
+
+       kfree(sev);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(v4l2_event_unsubscribe);
 
--- /dev/null
+/*
+ * v4l2-event.h
+ *
+ * V4L2 events.
+ *
+ * Copyright (C) 2009--2010 Nokia Corporation.
+ *
+ * Contact: Sakari Ailus <sakari.ailus@maxwell.research.nokia.com>
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#ifndef V4L2_EVENT_H
+#define V4L2_EVENT_H
+
+#include <linux/types.h>
+#include <linux/videodev2.h>
+#include <linux/wait.h>
+
+struct v4l2_fh;
+struct video_device;
+
+struct v4l2_kevent {
+       struct list_head        list;
+       struct v4l2_event       event;
+};
+
+struct v4l2_subscribed_event {
+       struct list_head        list;
+       u32                     type;
+};
+
+struct v4l2_events {
+       wait_queue_head_t       wait;
+       struct list_head        subscribed; /* Subscribed events */
+       struct list_head        free; /* Events ready for use */
+       struct list_head        available; /* Dequeueable event */
+       unsigned int            navailable;
+       unsigned int            nallocated; /* Number of allocated events */
+       u32                     sequence;
+};
+
+int v4l2_event_init(struct v4l2_fh *fh);
+int v4l2_event_alloc(struct v4l2_fh *fh, unsigned int n);
+void v4l2_event_free(struct v4l2_fh *fh);
+int v4l2_event_dequeue(struct v4l2_fh *fh, struct v4l2_event *event,
+                      int nonblocking);
+void v4l2_event_queue(struct video_device *vdev, const struct v4l2_event *ev);
+int v4l2_event_pending(struct v4l2_fh *fh);
+int v4l2_event_subscribe(struct v4l2_fh *fh,
+                        struct v4l2_event_subscription *sub);
+int v4l2_event_unsubscribe(struct v4l2_fh *fh,
+                          struct v4l2_event_subscription *sub);
+
+#endif /* V4L2_EVENT_H */