#include <linux/videodev2.h>
 #include <media/tuner.h>
 #include <media/v4l2-common.h>
+#include <media/v4l2-event.h>
 
 #include "go7007-priv.h"
 
 int go7007_start_encoder(struct go7007 *go)
 {
        u8 *fw;
-       int fw_len, rv = 0, i;
+       int fw_len, rv = 0, i, x, y;
        u16 intr_val, intr_data;
 
        go->modet_enable = 0;
-       if (!go->dvd_mode)
-               for (i = 0; i < 4; ++i) {
-                       if (go->modet[i].enable) {
-                               go->modet_enable = 1;
-                               continue;
+       for (i = 0; i < 4; i++)
+               go->modet[i].enable = 0;
+
+       switch (v4l2_ctrl_g_ctrl(go->modet_mode)) {
+       case V4L2_DETECT_MD_MODE_GLOBAL:
+               memset(go->modet_map, 0, sizeof(go->modet_map));
+               go->modet[0].enable = 1;
+               go->modet_enable = 1;
+               break;
+       case V4L2_DETECT_MD_MODE_REGION_GRID:
+               for (y = 0; y < go->height / 16; y++) {
+                       for (x = 0; x < go->width / 16; x++) {
+                               int idx = y * go->width / 16 + x;
+
+                               go->modet[go->modet_map[idx]].enable = 1;
                        }
-                       go->modet[i].pixel_threshold = 32767;
-                       go->modet[i].motion_threshold = 32767;
-                       go->modet[i].mb_threshold = 32767;
                }
+               go->modet_enable = 1;
+               break;
+       }
+
+       if (go->dvd_mode)
+               go->modet_enable = 0;
 
        if (go7007_construct_fw_image(go, &fw, &fw_len) < 0)
                return -1;
        }
 }
 
+static void go7007_set_motion_regions(struct go7007 *go, struct go7007_buffer *vb,
+               u32 motion_regions)
+{
+       if (motion_regions != go->modet_event_status) {
+               struct v4l2_event ev = {
+                       .type = V4L2_EVENT_MOTION_DET,
+                       .u.motion_det = {
+                               .flags = V4L2_EVENT_MD_FL_HAVE_FRAME_SEQ,
+                               .frame_sequence = vb->vb.v4l2_buf.sequence,
+                               .region_mask = motion_regions,
+                       },
+               };
+
+               v4l2_event_queue(&go->vdev, &ev);
+               go->modet_event_status = motion_regions;
+       }
+}
+
 /*
- * Deliver the last video buffer and get a new one to start writing to.
+ * Determine regions with motion and send a motion detection event
+ * in case of changes.
  */
-static struct go7007_buffer *frame_boundary(struct go7007 *go, struct go7007_buffer *vb)
+static void go7007_motion_regions(struct go7007 *go, struct go7007_buffer *vb)
 {
-       struct go7007_buffer *vb_tmp = NULL;
        u32 *bytesused = &vb->vb.v4l2_planes[0].bytesused;
+       unsigned motion[4] = { 0, 0, 0, 0 };
+       u32 motion_regions = 0;
+       unsigned stride = (go->width + 7) >> 3;
+       unsigned x, y;
        int i;
 
-       if (vb) {
-               if (vb->modet_active) {
-                       if (*bytesused + 216 < GO7007_BUF_SIZE) {
-                               for (i = 0; i < 216; ++i)
-                                       store_byte(vb, go->active_map[i]);
-                               *bytesused -= 216;
-                       } else
-                               vb->modet_active = 0;
+       for (i = 0; i < 216; ++i)
+               store_byte(vb, go->active_map[i]);
+       for (y = 0; y < go->height / 16; y++) {
+               for (x = 0; x < go->width / 16; x++) {
+                       if (!(go->active_map[y * stride + (x >> 3)] & (1 << (x & 7))))
+                               continue;
+                       motion[go->modet_map[y * (go->width / 16) + x]]++;
                }
-               vb->vb.v4l2_buf.sequence = go->next_seq++;
-               v4l2_get_timestamp(&vb->vb.v4l2_buf.timestamp);
-               vb_tmp = vb;
+       }
+       motion_regions = ((motion[0] > 0) << 0) |
+                        ((motion[1] > 0) << 1) |
+                        ((motion[2] > 0) << 2) |
+                        ((motion[3] > 0) << 3);
+       *bytesused -= 216;
+       go7007_set_motion_regions(go, vb, motion_regions);
+}
+
+/*
+ * Deliver the last video buffer and get a new one to start writing to.
+ */
+static struct go7007_buffer *frame_boundary(struct go7007 *go, struct go7007_buffer *vb)
+{
+       u32 *bytesused = &vb->vb.v4l2_planes[0].bytesused;
+       struct go7007_buffer *vb_tmp = NULL;
+
+       if (vb == NULL) {
                spin_lock(&go->spinlock);
-               list_del(&vb->list);
-               if (list_empty(&go->vidq_active))
-                       vb = NULL;
-               else
-                       vb = list_first_entry(&go->vidq_active, struct go7007_buffer, list);
-               go->active_buf = vb;
+               if (!list_empty(&go->vidq_active))
+                       vb = go->active_buf =
+                               list_first_entry(&go->vidq_active, struct go7007_buffer, list);
                spin_unlock(&go->spinlock);
-               vb2_buffer_done(&vb_tmp->vb, VB2_BUF_STATE_DONE);
+               go->next_seq++;
                return vb;
        }
+
+       vb->vb.v4l2_buf.sequence = go->next_seq++;
+       if (vb->modet_active && *bytesused + 216 < GO7007_BUF_SIZE)
+               go7007_motion_regions(go, vb);
+       else
+               go7007_set_motion_regions(go, vb, 0);
+
+       v4l2_get_timestamp(&vb->vb.v4l2_buf.timestamp);
+       vb_tmp = vb;
        spin_lock(&go->spinlock);
-       if (!list_empty(&go->vidq_active))
-               vb = go->active_buf =
-                       list_first_entry(&go->vidq_active, struct go7007_buffer, list);
+       list_del(&vb->list);
+       if (list_empty(&go->vidq_active))
+               vb = NULL;
+       else
+               vb = list_first_entry(&go->vidq_active, struct go7007_buffer, list);
+       go->active_buf = vb;
        spin_unlock(&go->spinlock);
-       go->next_seq++;
+       vb2_buffer_done(&vb_tmp->vb, VB2_BUF_STATE_DONE);
        return vb;
 }
 
 
 
 static int modet_to_package(struct go7007 *go, __le16 *code, int space)
 {
+       bool has_modet0 = go->modet[0].enable;
+       bool has_modet1 = go->modet[1].enable;
+       bool has_modet2 = go->modet[2].enable;
+       bool has_modet3 = go->modet[3].enable;
        int ret, mb, i, addr, cnt = 0;
        u16 pack[32];
        u16 thresholds[] = {
                0x200e,         0,
-               0xbf82,         go->modet[0].pixel_threshold,
-               0xbf83,         go->modet[1].pixel_threshold,
-               0xbf84,         go->modet[2].pixel_threshold,
-               0xbf85,         go->modet[3].pixel_threshold,
-               0xbf86,         go->modet[0].motion_threshold,
-               0xbf87,         go->modet[1].motion_threshold,
-               0xbf88,         go->modet[2].motion_threshold,
-               0xbf89,         go->modet[3].motion_threshold,
-               0xbf8a,         go->modet[0].mb_threshold,
-               0xbf8b,         go->modet[1].mb_threshold,
-               0xbf8c,         go->modet[2].mb_threshold,
-               0xbf8d,         go->modet[3].mb_threshold,
+               0xbf82,         has_modet0 ? go->modet[0].pixel_threshold : 32767,
+               0xbf83,         has_modet1 ? go->modet[1].pixel_threshold : 32767,
+               0xbf84,         has_modet2 ? go->modet[2].pixel_threshold : 32767,
+               0xbf85,         has_modet3 ? go->modet[3].pixel_threshold : 32767,
+               0xbf86,         has_modet0 ? go->modet[0].motion_threshold : 32767,
+               0xbf87,         has_modet1 ? go->modet[1].motion_threshold : 32767,
+               0xbf88,         has_modet2 ? go->modet[2].motion_threshold : 32767,
+               0xbf89,         has_modet3 ? go->modet[3].motion_threshold : 32767,
+               0xbf8a,         has_modet0 ? go->modet[0].mb_threshold : 32767,
+               0xbf8b,         has_modet1 ? go->modet[1].mb_threshold : 32767,
+               0xbf8c,         has_modet2 ? go->modet[2].mb_threshold : 32767,
+               0xbf8d,         has_modet3 ? go->modet[3].mb_threshold : 32767,
                0xbf8e,         0,
                0xbf8f,         0,
                0,              0,
 
 #include <media/videobuf2-vmalloc.h>
 #include <media/saa7115.h>
 
-#include "go7007.h"
 #include "go7007-priv.h"
 
 #define call_all(dev, o, f, args...) \
 static int set_capture_size(struct go7007 *go, struct v4l2_format *fmt, int try)
 {
        int sensor_height = 0, sensor_width = 0;
-       int width, height, i;
+       int width, height;
 
        if (fmt != NULL && !valid_pixelformat(fmt->fmt.pix.pixelformat))
                return -EINVAL;
        go->height = height;
        go->encoder_h_offset = go->board_info->sensor_h_offset;
        go->encoder_v_offset = go->board_info->sensor_v_offset;
-       for (i = 0; i < 4; ++i)
-               go->modet[i].enable = 0;
-       for (i = 0; i < 1624; ++i)
-               go->modet_map[i] = 0;
 
        if (go->board_info->sensor_flags & GO7007_SENSOR_SCALING) {
                struct v4l2_mbus_framefmt mbus_fmt;
        return 0;
 }
 
-#if 0
-static int clip_to_modet_map(struct go7007 *go, int region,
-               struct v4l2_clip *clip_list)
-{
-       struct v4l2_clip clip, *clip_ptr;
-       int x, y, mbnum;
-
-       /* Check if coordinates are OK and if any macroblocks are already
-        * used by other regions (besides 0) */
-       clip_ptr = clip_list;
-       while (clip_ptr) {
-               if (copy_from_user(&clip, clip_ptr, sizeof(clip)))
-                       return -EFAULT;
-               if (clip.c.left < 0 || (clip.c.left & 0xF) ||
-                               clip.c.width <= 0 || (clip.c.width & 0xF))
-                       return -EINVAL;
-               if (clip.c.left + clip.c.width > go->width)
-                       return -EINVAL;
-               if (clip.c.top < 0 || (clip.c.top & 0xF) ||
-                               clip.c.height <= 0 || (clip.c.height & 0xF))
-                       return -EINVAL;
-               if (clip.c.top + clip.c.height > go->height)
-                       return -EINVAL;
-               for (y = 0; y < clip.c.height; y += 16)
-                       for (x = 0; x < clip.c.width; x += 16) {
-                               mbnum = (go->width >> 4) *
-                                               ((clip.c.top + y) >> 4) +
-                                       ((clip.c.left + x) >> 4);
-                               if (go->modet_map[mbnum] != 0 &&
-                                               go->modet_map[mbnum] != region)
-                                       return -EBUSY;
-                       }
-               clip_ptr = clip.next;
-       }
-
-       /* Clear old region macroblocks */
-       for (mbnum = 0; mbnum < 1624; ++mbnum)
-               if (go->modet_map[mbnum] == region)
-                       go->modet_map[mbnum] = 0;
-
-       /* Claim macroblocks in this list */
-       clip_ptr = clip_list;
-       while (clip_ptr) {
-               if (copy_from_user(&clip, clip_ptr, sizeof(clip)))
-                       return -EFAULT;
-               for (y = 0; y < clip.c.height; y += 16)
-                       for (x = 0; x < clip.c.width; x += 16) {
-                               mbnum = (go->width >> 4) *
-                                               ((clip.c.top + y) >> 4) +
-                                       ((clip.c.left + x) >> 4);
-                               go->modet_map[mbnum] = region;
-                       }
-               clip_ptr = clip.next;
-       }
-       return 0;
-}
-#endif
-
 static int vidioc_querycap(struct file *file, void  *priv,
                                        struct v4l2_capability *cap)
 {
        mutex_lock(&go->hw_lock);
        go->next_seq = 0;
        go->active_buf = NULL;
+       go->modet_event_status = 0;
        q->streaming = 1;
        if (go7007_start_encoder(go) < 0)
                ret = -EIO;
        return call_all(&go->v4l2_dev, core, log_status);
 }
 
-/* FIXME:
-       Those ioctls are private, and not needed, since several standard
-       extended controls already provide streaming control.
-       So, those ioctls should be converted into vidioc_g_ext_ctrls()
-       and vidioc_s_ext_ctrls()
- */
-
-#if 0
-       case GO7007IOC_S_MD_PARAMS:
-       {
-               struct go7007_md_params *mdp = arg;
+static int vidioc_subscribe_event(struct v4l2_fh *fh,
+                               const struct v4l2_event_subscription *sub)
+{
 
-               if (mdp->region > 3)
-                       return -EINVAL;
-               if (mdp->trigger > 0) {
-                       go->modet[mdp->region].pixel_threshold =
-                                       mdp->pixel_threshold >> 1;
-                       go->modet[mdp->region].motion_threshold =
-                                       mdp->motion_threshold >> 1;
-                       go->modet[mdp->region].mb_threshold =
-                                       mdp->trigger >> 1;
-                       go->modet[mdp->region].enable = 1;
-               } else
-                       go->modet[mdp->region].enable = 0;
-               /* fall-through */
+       switch (sub->type) {
+       case V4L2_EVENT_CTRL:
+               return v4l2_ctrl_subscribe_event(fh, sub);
+       case V4L2_EVENT_MOTION_DET:
+               /* Allow for up to 30 events (1 second for NTSC) to be
+                * stored. */
+               return v4l2_event_subscribe(fh, sub, 30, NULL);
        }
-       case GO7007IOC_S_MD_REGION:
-       {
-               struct go7007_md_region *region = arg;
+       return -EINVAL;
+}
 
-               if (region->region < 1 || region->region > 3)
-                       return -EINVAL;
-               return clip_to_modet_map(go, region->region, region->clips);
+
+static int go7007_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+       struct go7007 *go =
+               container_of(ctrl->handler, struct go7007, hdl);
+       unsigned y;
+       u8 *mt;
+
+       switch (ctrl->id) {
+       case V4L2_CID_PIXEL_THRESHOLD0:
+               go->modet[0].pixel_threshold = ctrl->val;
+               break;
+       case V4L2_CID_MOTION_THRESHOLD0:
+               go->modet[0].motion_threshold = ctrl->val;
+               break;
+       case V4L2_CID_MB_THRESHOLD0:
+               go->modet[0].mb_threshold = ctrl->val;
+               break;
+       case V4L2_CID_PIXEL_THRESHOLD1:
+               go->modet[1].pixel_threshold = ctrl->val;
+               break;
+       case V4L2_CID_MOTION_THRESHOLD1:
+               go->modet[1].motion_threshold = ctrl->val;
+               break;
+       case V4L2_CID_MB_THRESHOLD1:
+               go->modet[1].mb_threshold = ctrl->val;
+               break;
+       case V4L2_CID_PIXEL_THRESHOLD2:
+               go->modet[2].pixel_threshold = ctrl->val;
+               break;
+       case V4L2_CID_MOTION_THRESHOLD2:
+               go->modet[2].motion_threshold = ctrl->val;
+               break;
+       case V4L2_CID_MB_THRESHOLD2:
+               go->modet[2].mb_threshold = ctrl->val;
+               break;
+       case V4L2_CID_PIXEL_THRESHOLD3:
+               go->modet[3].pixel_threshold = ctrl->val;
+               break;
+       case V4L2_CID_MOTION_THRESHOLD3:
+               go->modet[3].motion_threshold = ctrl->val;
+               break;
+       case V4L2_CID_MB_THRESHOLD3:
+               go->modet[3].mb_threshold = ctrl->val;
+               break;
+       case V4L2_CID_DETECT_MD_REGION_GRID:
+               mt = go->modet_map;
+               for (y = 0; y < go->height / 16; y++, mt += go->width / 16)
+                       memcpy(mt, ctrl->p_new.p_u8 + y * (720 / 16), go->width / 16);
+               break;
+       default:
+               return -EINVAL;
        }
-#endif
+       return 0;
+}
 
 static struct v4l2_file_operations go7007_fops = {
        .owner          = THIS_MODULE,
        .vidioc_enum_framesizes   = vidioc_enum_framesizes,
        .vidioc_enum_frameintervals = vidioc_enum_frameintervals,
        .vidioc_log_status        = vidioc_log_status,
-       .vidioc_subscribe_event   = v4l2_ctrl_subscribe_event,
+       .vidioc_subscribe_event   = vidioc_subscribe_event,
        .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
 };
 
        .tvnorms        = V4L2_STD_ALL,
 };
 
+static const struct v4l2_ctrl_ops go7007_ctrl_ops = {
+       .s_ctrl = go7007_s_ctrl,
+};
+
+static const struct v4l2_ctrl_config go7007_pixel_threshold0_ctrl = {
+       .ops = &go7007_ctrl_ops,
+       .id = V4L2_CID_PIXEL_THRESHOLD0,
+       .name = "Pixel Threshold Region 0",
+       .type = V4L2_CTRL_TYPE_INTEGER,
+       .def = 20,
+       .max = 32767,
+       .step = 1,
+};
+
+static const struct v4l2_ctrl_config go7007_motion_threshold0_ctrl = {
+       .ops = &go7007_ctrl_ops,
+       .id = V4L2_CID_MOTION_THRESHOLD0,
+       .name = "Motion Threshold Region 0",
+       .type = V4L2_CTRL_TYPE_INTEGER,
+       .def = 80,
+       .max = 32767,
+       .step = 1,
+};
+
+static const struct v4l2_ctrl_config go7007_mb_threshold0_ctrl = {
+       .ops = &go7007_ctrl_ops,
+       .id = V4L2_CID_MB_THRESHOLD0,
+       .name = "MB Threshold Region 0",
+       .type = V4L2_CTRL_TYPE_INTEGER,
+       .def = 200,
+       .max = 32767,
+       .step = 1,
+};
+
+static const struct v4l2_ctrl_config go7007_pixel_threshold1_ctrl = {
+       .ops = &go7007_ctrl_ops,
+       .id = V4L2_CID_PIXEL_THRESHOLD1,
+       .name = "Pixel Threshold Region 1",
+       .type = V4L2_CTRL_TYPE_INTEGER,
+       .def = 20,
+       .max = 32767,
+       .step = 1,
+};
+
+static const struct v4l2_ctrl_config go7007_motion_threshold1_ctrl = {
+       .ops = &go7007_ctrl_ops,
+       .id = V4L2_CID_MOTION_THRESHOLD1,
+       .name = "Motion Threshold Region 1",
+       .type = V4L2_CTRL_TYPE_INTEGER,
+       .def = 80,
+       .max = 32767,
+       .step = 1,
+};
+
+static const struct v4l2_ctrl_config go7007_mb_threshold1_ctrl = {
+       .ops = &go7007_ctrl_ops,
+       .id = V4L2_CID_MB_THRESHOLD1,
+       .name = "MB Threshold Region 1",
+       .type = V4L2_CTRL_TYPE_INTEGER,
+       .def = 200,
+       .max = 32767,
+       .step = 1,
+};
+
+static const struct v4l2_ctrl_config go7007_pixel_threshold2_ctrl = {
+       .ops = &go7007_ctrl_ops,
+       .id = V4L2_CID_PIXEL_THRESHOLD2,
+       .name = "Pixel Threshold Region 2",
+       .type = V4L2_CTRL_TYPE_INTEGER,
+       .def = 20,
+       .max = 32767,
+       .step = 1,
+};
+
+static const struct v4l2_ctrl_config go7007_motion_threshold2_ctrl = {
+       .ops = &go7007_ctrl_ops,
+       .id = V4L2_CID_MOTION_THRESHOLD2,
+       .name = "Motion Threshold Region 2",
+       .type = V4L2_CTRL_TYPE_INTEGER,
+       .def = 80,
+       .max = 32767,
+       .step = 1,
+};
+
+static const struct v4l2_ctrl_config go7007_mb_threshold2_ctrl = {
+       .ops = &go7007_ctrl_ops,
+       .id = V4L2_CID_MB_THRESHOLD2,
+       .name = "MB Threshold Region 2",
+       .type = V4L2_CTRL_TYPE_INTEGER,
+       .def = 200,
+       .max = 32767,
+       .step = 1,
+};
+
+static const struct v4l2_ctrl_config go7007_pixel_threshold3_ctrl = {
+       .ops = &go7007_ctrl_ops,
+       .id = V4L2_CID_PIXEL_THRESHOLD3,
+       .name = "Pixel Threshold Region 3",
+       .type = V4L2_CTRL_TYPE_INTEGER,
+       .def = 20,
+       .max = 32767,
+       .step = 1,
+};
+
+static const struct v4l2_ctrl_config go7007_motion_threshold3_ctrl = {
+       .ops = &go7007_ctrl_ops,
+       .id = V4L2_CID_MOTION_THRESHOLD3,
+       .name = "Motion Threshold Region 3",
+       .type = V4L2_CTRL_TYPE_INTEGER,
+       .def = 80,
+       .max = 32767,
+       .step = 1,
+};
+
+static const struct v4l2_ctrl_config go7007_mb_threshold3_ctrl = {
+       .ops = &go7007_ctrl_ops,
+       .id = V4L2_CID_MB_THRESHOLD3,
+       .name = "MB Threshold Region 3",
+       .type = V4L2_CTRL_TYPE_INTEGER,
+       .def = 200,
+       .max = 32767,
+       .step = 1,
+};
+
+static const struct v4l2_ctrl_config go7007_mb_regions_ctrl = {
+       .ops = &go7007_ctrl_ops,
+       .id = V4L2_CID_DETECT_MD_REGION_GRID,
+       .dims = { 576 / 16, 720 / 16 },
+       .max = 3,
+       .step = 1,
+};
+
 int go7007_v4l2_ctrl_init(struct go7007 *go)
 {
        struct v4l2_ctrl_handler *hdl = &go->hdl;
        struct v4l2_ctrl *ctrl;
 
-       v4l2_ctrl_handler_init(hdl, 13);
+       v4l2_ctrl_handler_init(hdl, 22);
        go->mpeg_video_gop_size = v4l2_ctrl_new_std(hdl, NULL,
                        V4L2_CID_MPEG_VIDEO_GOP_SIZE, 0, 34, 1, 15);
        go->mpeg_video_gop_closure = v4l2_ctrl_new_std(hdl, NULL,
                        V4L2_JPEG_ACTIVE_MARKER_DHT);
        if (ctrl)
                ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+       v4l2_ctrl_new_custom(hdl, &go7007_pixel_threshold0_ctrl, NULL);
+       v4l2_ctrl_new_custom(hdl, &go7007_motion_threshold0_ctrl, NULL);
+       v4l2_ctrl_new_custom(hdl, &go7007_mb_threshold0_ctrl, NULL);
+       v4l2_ctrl_new_custom(hdl, &go7007_pixel_threshold1_ctrl, NULL);
+       v4l2_ctrl_new_custom(hdl, &go7007_motion_threshold1_ctrl, NULL);
+       v4l2_ctrl_new_custom(hdl, &go7007_mb_threshold1_ctrl, NULL);
+       v4l2_ctrl_new_custom(hdl, &go7007_pixel_threshold2_ctrl, NULL);
+       v4l2_ctrl_new_custom(hdl, &go7007_motion_threshold2_ctrl, NULL);
+       v4l2_ctrl_new_custom(hdl, &go7007_mb_threshold2_ctrl, NULL);
+       v4l2_ctrl_new_custom(hdl, &go7007_pixel_threshold3_ctrl, NULL);
+       v4l2_ctrl_new_custom(hdl, &go7007_motion_threshold3_ctrl, NULL);
+       v4l2_ctrl_new_custom(hdl, &go7007_mb_threshold3_ctrl, NULL);
+       v4l2_ctrl_new_custom(hdl, &go7007_mb_regions_ctrl, NULL);
+       go->modet_mode = v4l2_ctrl_new_std_menu(hdl, NULL,
+                       V4L2_CID_DETECT_MD_MODE,
+                       V4L2_DETECT_MD_MODE_REGION_GRID,
+                       1 << V4L2_DETECT_MD_MODE_THRESHOLD_GRID,
+                       V4L2_DETECT_MD_MODE_DISABLED);
        if (hdl->error) {
                int rv = hdl->error;