]> git.karo-electronics.de Git - mv-sheeva.git/blobdiff - drivers/media/video/uvc/uvc_ctrl.c
Merge branch 'master' into tk71
[mv-sheeva.git] / drivers / media / video / uvc / uvc_ctrl.c
index a350fad0db432291f66a3be5073ecf0f2e4e00fe..59f8a9ad3796c3ef5960b4e77f12a7269ea7b7b9 100644 (file)
@@ -1,8 +1,8 @@
 /*
  *      uvc_ctrl.c  --  USB Video Class driver - Controls
  *
- *      Copyright (C) 2005-2009
- *          Laurent Pinchart (laurent.pinchart@skynet.be)
+ *      Copyright (C) 2005-2010
+ *          Laurent Pinchart (laurent.pinchart@ideasonboard.com)
  *
  *      This program is free software; you can redistribute it and/or modify
  *      it under the terms of the GNU General Public License as published by
@@ -643,7 +643,7 @@ static struct uvc_control_mapping uvc_ctrl_mappings[] = {
 
 static inline __u8 *uvc_ctrl_data(struct uvc_control *ctrl, int id)
 {
-       return ctrl->uvc_data + id * ctrl->info->size;
+       return ctrl->uvc_data + id * ctrl->info.size;
 }
 
 static inline int uvc_test_bit(const __u8 *data, int bit)
@@ -727,7 +727,8 @@ static const __u8 uvc_camera_guid[16] = UVC_GUID_UVC_CAMERA;
 static const __u8 uvc_media_transport_input_guid[16] =
        UVC_GUID_UVC_MEDIA_TRANSPORT_INPUT;
 
-static int uvc_entity_match_guid(struct uvc_entity *entity, __u8 guid[16])
+static int uvc_entity_match_guid(const struct uvc_entity *entity,
+       const __u8 guid[16])
 {
        switch (UVC_ENTITY_TYPE(entity)) {
        case UVC_ITT_CAMERA:
@@ -765,10 +766,10 @@ static void __uvc_find_control(struct uvc_entity *entity, __u32 v4l2_id,
 
        for (i = 0; i < entity->ncontrols; ++i) {
                ctrl = &entity->controls[i];
-               if (ctrl->info == NULL)
+               if (!ctrl->initialized)
                        continue;
 
-               list_for_each_entry(map, &ctrl->info->mappings, list) {
+               list_for_each_entry(map, &ctrl->info.mappings, list) {
                        if ((map->id == v4l2_id) && !next) {
                                *control = ctrl;
                                *mapping = map;
@@ -784,7 +785,7 @@ static void __uvc_find_control(struct uvc_entity *entity, __u32 v4l2_id,
        }
 }
 
-struct uvc_control *uvc_find_control(struct uvc_video_chain *chain,
+static struct uvc_control *uvc_find_control(struct uvc_video_chain *chain,
        __u32 v4l2_id, struct uvc_control_mapping **mapping)
 {
        struct uvc_control *ctrl = NULL;
@@ -815,36 +816,36 @@ static int uvc_ctrl_populate_cache(struct uvc_video_chain *chain,
 {
        int ret;
 
-       if (ctrl->info->flags & UVC_CONTROL_GET_DEF) {
+       if (ctrl->info.flags & UVC_CONTROL_GET_DEF) {
                ret = uvc_query_ctrl(chain->dev, UVC_GET_DEF, ctrl->entity->id,
-                                    chain->dev->intfnum, ctrl->info->selector,
+                                    chain->dev->intfnum, ctrl->info.selector,
                                     uvc_ctrl_data(ctrl, UVC_CTRL_DATA_DEF),
-                                    ctrl->info->size);
+                                    ctrl->info.size);
                if (ret < 0)
                        return ret;
        }
 
-       if (ctrl->info->flags & UVC_CONTROL_GET_MIN) {
+       if (ctrl->info.flags & UVC_CONTROL_GET_MIN) {
                ret = uvc_query_ctrl(chain->dev, UVC_GET_MIN, ctrl->entity->id,
-                                    chain->dev->intfnum, ctrl->info->selector,
+                                    chain->dev->intfnum, ctrl->info.selector,
                                     uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MIN),
-                                    ctrl->info->size);
+                                    ctrl->info.size);
                if (ret < 0)
                        return ret;
        }
-       if (ctrl->info->flags & UVC_CONTROL_GET_MAX) {
+       if (ctrl->info.flags & UVC_CONTROL_GET_MAX) {
                ret = uvc_query_ctrl(chain->dev, UVC_GET_MAX, ctrl->entity->id,
-                                    chain->dev->intfnum, ctrl->info->selector,
+                                    chain->dev->intfnum, ctrl->info.selector,
                                     uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MAX),
-                                    ctrl->info->size);
+                                    ctrl->info.size);
                if (ret < 0)
                        return ret;
        }
-       if (ctrl->info->flags & UVC_CONTROL_GET_RES) {
+       if (ctrl->info.flags & UVC_CONTROL_GET_RES) {
                ret = uvc_query_ctrl(chain->dev, UVC_GET_RES, ctrl->entity->id,
-                                    chain->dev->intfnum, ctrl->info->selector,
+                                    chain->dev->intfnum, ctrl->info.selector,
                                     uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES),
-                                    ctrl->info->size);
+                                    ctrl->info.size);
                if (ret < 0)
                        return ret;
        }
@@ -862,9 +863,15 @@ int uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
        unsigned int i;
        int ret;
 
+       ret = mutex_lock_interruptible(&chain->ctrl_mutex);
+       if (ret < 0)
+               return -ERESTARTSYS;
+
        ctrl = uvc_find_control(chain, v4l2_ctrl->id, &mapping);
-       if (ctrl == NULL)
-               return -EINVAL;
+       if (ctrl == NULL) {
+               ret = -EINVAL;
+               goto done;
+       }
 
        memset(v4l2_ctrl, 0, sizeof *v4l2_ctrl);
        v4l2_ctrl->id = mapping->id;
@@ -872,18 +879,18 @@ int uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
        strlcpy(v4l2_ctrl->name, mapping->name, sizeof v4l2_ctrl->name);
        v4l2_ctrl->flags = 0;
 
-       if (!(ctrl->info->flags & UVC_CONTROL_GET_CUR))
+       if (!(ctrl->info.flags & UVC_CONTROL_GET_CUR))
                v4l2_ctrl->flags |= V4L2_CTRL_FLAG_WRITE_ONLY;
-       if (!(ctrl->info->flags & UVC_CONTROL_SET_CUR))
+       if (!(ctrl->info.flags & UVC_CONTROL_SET_CUR))
                v4l2_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
 
        if (!ctrl->cached) {
                ret = uvc_ctrl_populate_cache(chain, ctrl);
                if (ret < 0)
-                       return ret;
+                       goto done;
        }
 
-       if (ctrl->info->flags & UVC_CONTROL_GET_DEF) {
+       if (ctrl->info.flags & UVC_CONTROL_GET_DEF) {
                v4l2_ctrl->default_value = mapping->get(mapping, UVC_GET_DEF,
                                uvc_ctrl_data(ctrl, UVC_CTRL_DATA_DEF));
        }
@@ -902,37 +909,85 @@ int uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
                        }
                }
 
-               return 0;
+               goto done;
 
        case V4L2_CTRL_TYPE_BOOLEAN:
                v4l2_ctrl->minimum = 0;
                v4l2_ctrl->maximum = 1;
                v4l2_ctrl->step = 1;
-               return 0;
+               goto done;
 
        case V4L2_CTRL_TYPE_BUTTON:
                v4l2_ctrl->minimum = 0;
                v4l2_ctrl->maximum = 0;
                v4l2_ctrl->step = 0;
-               return 0;
+               goto done;
 
        default:
                break;
        }
 
-       if (ctrl->info->flags & UVC_CONTROL_GET_MIN)
+       if (ctrl->info.flags & UVC_CONTROL_GET_MIN)
                v4l2_ctrl->minimum = mapping->get(mapping, UVC_GET_MIN,
                                     uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MIN));
 
-       if (ctrl->info->flags & UVC_CONTROL_GET_MAX)
+       if (ctrl->info.flags & UVC_CONTROL_GET_MAX)
                v4l2_ctrl->maximum = mapping->get(mapping, UVC_GET_MAX,
                                     uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MAX));
 
-       if (ctrl->info->flags & UVC_CONTROL_GET_RES)
+       if (ctrl->info.flags & UVC_CONTROL_GET_RES)
                v4l2_ctrl->step = mapping->get(mapping, UVC_GET_RES,
                                  uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES));
 
-       return 0;
+done:
+       mutex_unlock(&chain->ctrl_mutex);
+       return ret;
+}
+
+/*
+ * Mapping V4L2 controls to UVC controls can be straighforward if done well.
+ * Most of the UVC controls exist in V4L2, and can be mapped directly. Some
+ * must be grouped (for instance the Red Balance, Blue Balance and Do White
+ * Balance V4L2 controls use the White Balance Component UVC control) or
+ * otherwise translated. The approach we take here is to use a translation
+ * table for the controls that can be mapped directly, and handle the others
+ * manually.
+ */
+int uvc_query_v4l2_menu(struct uvc_video_chain *chain,
+       struct v4l2_querymenu *query_menu)
+{
+       struct uvc_menu_info *menu_info;
+       struct uvc_control_mapping *mapping;
+       struct uvc_control *ctrl;
+       u32 index = query_menu->index;
+       u32 id = query_menu->id;
+       int ret;
+
+       memset(query_menu, 0, sizeof(*query_menu));
+       query_menu->id = id;
+       query_menu->index = index;
+
+       ret = mutex_lock_interruptible(&chain->ctrl_mutex);
+       if (ret < 0)
+               return -ERESTARTSYS;
+
+       ctrl = uvc_find_control(chain, query_menu->id, &mapping);
+       if (ctrl == NULL || mapping->v4l2_type != V4L2_CTRL_TYPE_MENU) {
+               ret = -EINVAL;
+               goto done;
+       }
+
+       if (query_menu->index >= mapping->menu_count) {
+               ret = -EINVAL;
+               goto done;
+       }
+
+       menu_info = &mapping->menu_info[query_menu->index];
+       strlcpy(query_menu->name, menu_info->name, sizeof query_menu->name);
+
+done:
+       mutex_unlock(&chain->ctrl_mutex);
+       return ret;
 }
 
 
@@ -977,14 +1032,14 @@ static int uvc_ctrl_commit_entity(struct uvc_device *dev,
 
        for (i = 0; i < entity->ncontrols; ++i) {
                ctrl = &entity->controls[i];
-               if (ctrl->info == NULL)
+               if (!ctrl->initialized)
                        continue;
 
                /* Reset the loaded flag for auto-update controls that were
                 * marked as loaded in uvc_ctrl_get/uvc_ctrl_set to prevent
                 * uvc_ctrl_get from using the cached value.
                 */
-               if (ctrl->info->flags & UVC_CONTROL_AUTO_UPDATE)
+               if (ctrl->info.flags & UVC_CONTROL_AUTO_UPDATE)
                        ctrl->loaded = 0;
 
                if (!ctrl->dirty)
@@ -992,16 +1047,16 @@ static int uvc_ctrl_commit_entity(struct uvc_device *dev,
 
                if (!rollback)
                        ret = uvc_query_ctrl(dev, UVC_SET_CUR, ctrl->entity->id,
-                               dev->intfnum, ctrl->info->selector,
+                               dev->intfnum, ctrl->info.selector,
                                uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
-                               ctrl->info->size);
+                               ctrl->info.size);
                else
                        ret = 0;
 
                if (rollback || ret < 0)
                        memcpy(uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
                               uvc_ctrl_data(ctrl, UVC_CTRL_DATA_BACKUP),
-                              ctrl->info->size);
+                              ctrl->info.size);
 
                ctrl->dirty = 0;
 
@@ -1039,14 +1094,14 @@ int uvc_ctrl_get(struct uvc_video_chain *chain,
        int ret;
 
        ctrl = uvc_find_control(chain, xctrl->id, &mapping);
-       if (ctrl == NULL || (ctrl->info->flags & UVC_CONTROL_GET_CUR) == 0)
+       if (ctrl == NULL || (ctrl->info.flags & UVC_CONTROL_GET_CUR) == 0)
                return -EINVAL;
 
        if (!ctrl->loaded) {
                ret = uvc_query_ctrl(chain->dev, UVC_GET_CUR, ctrl->entity->id,
-                               chain->dev->intfnum, ctrl->info->selector,
+                               chain->dev->intfnum, ctrl->info.selector,
                                uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
-                               ctrl->info->size);
+                               ctrl->info.size);
                if (ret < 0)
                        return ret;
 
@@ -1081,7 +1136,7 @@ int uvc_ctrl_set(struct uvc_video_chain *chain,
        int ret;
 
        ctrl = uvc_find_control(chain, xctrl->id, &mapping);
-       if (ctrl == NULL || (ctrl->info->flags & UVC_CONTROL_SET_CUR) == 0)
+       if (ctrl == NULL || (ctrl->info.flags & UVC_CONTROL_SET_CUR) == 0)
                return -EINVAL;
 
        /* Clamp out of range values. */
@@ -1127,16 +1182,16 @@ int uvc_ctrl_set(struct uvc_video_chain *chain,
         * needs to be loaded from the device to perform the read-modify-write
         * operation.
         */
-       if (!ctrl->loaded && (ctrl->info->size * 8) != mapping->size) {
-               if ((ctrl->info->flags & UVC_CONTROL_GET_CUR) == 0) {
+       if (!ctrl->loaded && (ctrl->info.size * 8) != mapping->size) {
+               if ((ctrl->info.flags & UVC_CONTROL_GET_CUR) == 0) {
                        memset(uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
-                               0, ctrl->info->size);
+                               0, ctrl->info.size);
                } else {
                        ret = uvc_query_ctrl(chain->dev, UVC_GET_CUR,
                                ctrl->entity->id, chain->dev->intfnum,
-                               ctrl->info->selector,
+                               ctrl->info.selector,
                                uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
-                               ctrl->info->size);
+                               ctrl->info.size);
                        if (ret < 0)
                                return ret;
                }
@@ -1148,7 +1203,7 @@ int uvc_ctrl_set(struct uvc_video_chain *chain,
        if (!ctrl->dirty) {
                memcpy(uvc_ctrl_data(ctrl, UVC_CTRL_DATA_BACKUP),
                       uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
-                      ctrl->info->size);
+                      ctrl->info.size);
        }
 
        mapping->set(mapping, value,
@@ -1163,12 +1218,138 @@ int uvc_ctrl_set(struct uvc_video_chain *chain,
  * Dynamic controls
  */
 
+static void uvc_ctrl_fixup_xu_info(struct uvc_device *dev,
+       const struct uvc_control *ctrl, struct uvc_control_info *info)
+{
+       struct uvc_ctrl_fixup {
+               struct usb_device_id id;
+               u8 entity;
+               u8 selector;
+               u8 flags;
+       };
+
+       static const struct uvc_ctrl_fixup fixups[] = {
+               { { USB_DEVICE(0x046d, 0x08c2) }, 9, 1,
+                       UVC_CONTROL_GET_MIN | UVC_CONTROL_GET_MAX |
+                       UVC_CONTROL_GET_DEF | UVC_CONTROL_SET_CUR |
+                       UVC_CONTROL_AUTO_UPDATE },
+               { { USB_DEVICE(0x046d, 0x08cc) }, 9, 1,
+                       UVC_CONTROL_GET_MIN | UVC_CONTROL_GET_MAX |
+                       UVC_CONTROL_GET_DEF | UVC_CONTROL_SET_CUR |
+                       UVC_CONTROL_AUTO_UPDATE },
+               { { USB_DEVICE(0x046d, 0x0994) }, 9, 1,
+                       UVC_CONTROL_GET_MIN | UVC_CONTROL_GET_MAX |
+                       UVC_CONTROL_GET_DEF | UVC_CONTROL_SET_CUR |
+                       UVC_CONTROL_AUTO_UPDATE },
+       };
+
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(fixups); ++i) {
+               if (!usb_match_one_id(dev->intf, &fixups[i].id))
+                       continue;
+
+               if (fixups[i].entity == ctrl->entity->id &&
+                   fixups[i].selector == info->selector) {
+                       info->flags = fixups[i].flags;
+                       return;
+               }
+       }
+}
+
+/*
+ * Query control information (size and flags) for XU controls.
+ */
+static int uvc_ctrl_fill_xu_info(struct uvc_device *dev,
+       const struct uvc_control *ctrl, struct uvc_control_info *info)
+{
+       u8 *data;
+       int ret;
+
+       data = kmalloc(2, GFP_KERNEL);
+       if (data == NULL)
+               return -ENOMEM;
+
+       memcpy(info->entity, ctrl->entity->extension.guidExtensionCode,
+              sizeof(info->entity));
+       info->index = ctrl->index;
+       info->selector = ctrl->index + 1;
+
+       /* Query and verify the control length (GET_LEN) */
+       ret = uvc_query_ctrl(dev, UVC_GET_LEN, ctrl->entity->id, dev->intfnum,
+                            info->selector, data, 2);
+       if (ret < 0) {
+               uvc_trace(UVC_TRACE_CONTROL,
+                         "GET_LEN failed on control %pUl/%u (%d).\n",
+                          info->entity, info->selector, ret);
+               goto done;
+       }
+
+       info->size = le16_to_cpup((__le16 *)data);
+
+       /* Query the control information (GET_INFO) */
+       ret = uvc_query_ctrl(dev, UVC_GET_INFO, ctrl->entity->id, dev->intfnum,
+                            info->selector, data, 1);
+       if (ret < 0) {
+               uvc_trace(UVC_TRACE_CONTROL,
+                         "GET_INFO failed on control %pUl/%u (%d).\n",
+                         info->entity, info->selector, ret);
+               goto done;
+       }
+
+       info->flags = UVC_CONTROL_GET_MIN | UVC_CONTROL_GET_MAX
+                   | UVC_CONTROL_GET_RES | UVC_CONTROL_GET_DEF
+                   | (data[0] & UVC_CONTROL_CAP_GET ? UVC_CONTROL_GET_CUR : 0)
+                   | (data[0] & UVC_CONTROL_CAP_SET ? UVC_CONTROL_SET_CUR : 0)
+                   | (data[0] & UVC_CONTROL_CAP_AUTOUPDATE ?
+                      UVC_CONTROL_AUTO_UPDATE : 0);
+
+       uvc_ctrl_fixup_xu_info(dev, ctrl, info);
+
+       uvc_trace(UVC_TRACE_CONTROL, "XU control %pUl/%u queried: len %u, "
+                 "flags { get %u set %u auto %u }.\n",
+                 info->entity, info->selector, info->size,
+                 (info->flags & UVC_CONTROL_GET_CUR) ? 1 : 0,
+                 (info->flags & UVC_CONTROL_SET_CUR) ? 1 : 0,
+                 (info->flags & UVC_CONTROL_AUTO_UPDATE) ? 1 : 0);
+
+done:
+       kfree(data);
+       return ret;
+}
+
+static int uvc_ctrl_add_info(struct uvc_device *dev, struct uvc_control *ctrl,
+       const struct uvc_control_info *info);
+
+static int uvc_ctrl_init_xu_ctrl(struct uvc_device *dev,
+       struct uvc_control *ctrl)
+{
+       struct uvc_control_info info;
+       int ret;
+
+       if (ctrl->initialized)
+               return 0;
+
+       ret = uvc_ctrl_fill_xu_info(dev, ctrl, &info);
+       if (ret < 0)
+               return ret;
+
+       ret = uvc_ctrl_add_info(dev, ctrl, &info);
+       if (ret < 0)
+               uvc_trace(UVC_TRACE_CONTROL, "Failed to initialize control "
+                         "%pUl/%u on device %s entity %u\n", info.entity,
+                         info.selector, dev->udev->devpath, ctrl->entity->id);
+
+       return ret;
+}
+
 int uvc_xu_ctrl_query(struct uvc_video_chain *chain,
        struct uvc_xu_control *xctrl, int set)
 {
        struct uvc_entity *entity;
        struct uvc_control *ctrl = NULL;
        unsigned int i, found = 0;
+       int restore = 0;
        __u8 *data;
        int ret;
 
@@ -1185,13 +1366,10 @@ int uvc_xu_ctrl_query(struct uvc_video_chain *chain,
                return -EINVAL;
        }
 
-       /* Find the control. */
+       /* Find the control and perform delayed initialization if needed. */
        for (i = 0; i < entity->ncontrols; ++i) {
                ctrl = &entity->controls[i];
-               if (ctrl->info == NULL)
-                       continue;
-
-               if (ctrl->info->selector == xctrl->selector) {
+               if (ctrl->index == xctrl->selector - 1) {
                        found = 1;
                        break;
                }
@@ -1203,40 +1381,48 @@ int uvc_xu_ctrl_query(struct uvc_video_chain *chain,
                return -EINVAL;
        }
 
-       /* Validate control data size. */
-       if (ctrl->info->size != xctrl->size)
-               return -EINVAL;
-
-       if ((set && !(ctrl->info->flags & UVC_CONTROL_SET_CUR)) ||
-           (!set && !(ctrl->info->flags & UVC_CONTROL_GET_CUR)))
-               return -EINVAL;
-
        if (mutex_lock_interruptible(&chain->ctrl_mutex))
                return -ERESTARTSYS;
 
+       ret = uvc_ctrl_init_xu_ctrl(chain->dev, ctrl);
+       if (ret < 0) {
+               ret = -ENOENT;
+               goto done;
+       }
+
+       /* Validate control data size. */
+       if (ctrl->info.size != xctrl->size) {
+               ret = -EINVAL;
+               goto done;
+       }
+
+       if ((set && !(ctrl->info.flags & UVC_CONTROL_SET_CUR)) ||
+           (!set && !(ctrl->info.flags & UVC_CONTROL_GET_CUR))) {
+               ret = -EINVAL;
+               goto done;
+       }
+
        memcpy(uvc_ctrl_data(ctrl, UVC_CTRL_DATA_BACKUP),
               uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
-              xctrl->size);
+              ctrl->info.size);
        data = uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT);
+       restore = set;
 
        if (set && copy_from_user(data, xctrl->data, xctrl->size)) {
                ret = -EFAULT;
-               goto out;
+               goto done;
        }
 
        ret = uvc_query_ctrl(chain->dev, set ? UVC_SET_CUR : UVC_GET_CUR,
                             xctrl->unit, chain->dev->intfnum, xctrl->selector,
                             data, xctrl->size);
        if (ret < 0)
-               goto out;
+               goto done;
 
-       if (!set && copy_to_user(xctrl->data, data, xctrl->size)) {
+       if (!set && copy_to_user(xctrl->data, data, xctrl->size))
                ret = -EFAULT;
-               goto out;
-       }
-
-out:
-       if (ret)
+done:
+       if (ret && restore)
                memcpy(uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
                       uvc_ctrl_data(ctrl, UVC_CTRL_DATA_BACKUP),
                       xctrl->size);
@@ -1271,13 +1457,13 @@ int uvc_ctrl_resume_device(struct uvc_device *dev)
                for (i = 0; i < entity->ncontrols; ++i) {
                        ctrl = &entity->controls[i];
 
-                       if (ctrl->info == NULL || !ctrl->modified ||
-                           (ctrl->info->flags & UVC_CONTROL_RESTORE) == 0)
+                       if (!ctrl->initialized || !ctrl->modified ||
+                           (ctrl->info.flags & UVC_CONTROL_RESTORE) == 0)
                                continue;
 
                        printk(KERN_INFO "restoring control %pUl/%u/%u\n",
-                               ctrl->info->entity, ctrl->info->index,
-                               ctrl->info->selector);
+                               ctrl->info.entity, ctrl->info.index,
+                               ctrl->info.selector);
                        ctrl->dirty = 1;
                }
 
@@ -1293,201 +1479,150 @@ int uvc_ctrl_resume_device(struct uvc_device *dev)
  * Control and mapping handling
  */
 
-static int uvc_ctrl_add_ctrl(struct uvc_device *dev,
-       struct uvc_control_info *info)
+/*
+ * Add control information to a given control.
+ */
+static int uvc_ctrl_add_info(struct uvc_device *dev, struct uvc_control *ctrl,
+       const struct uvc_control_info *info)
 {
-       struct uvc_entity *entity;
-       struct uvc_control *ctrl = NULL;
-       int ret = 0, found = 0;
-       unsigned int i;
-       u8 *uvc_info;
-       u8 *uvc_data;
-
-       list_for_each_entry(entity, &dev->entities, list) {
-               if (!uvc_entity_match_guid(entity, info->entity))
-                       continue;
-
-               for (i = 0; i < entity->ncontrols; ++i) {
-                       ctrl = &entity->controls[i];
-                       if (ctrl->index == info->index) {
-                               found = 1;
-                               break;
-                       }
-               }
-
-               if (found)
-                       break;
-       }
-
-       if (!found)
-               return 0;
-
-       uvc_data = kmalloc(info->size * UVC_CTRL_DATA_LAST + 1, GFP_KERNEL);
-       if (uvc_data == NULL)
-               return -ENOMEM;
-
-       uvc_info = uvc_data + info->size * UVC_CTRL_DATA_LAST;
-
-       if (UVC_ENTITY_TYPE(entity) == UVC_VC_EXTENSION_UNIT) {
-               /* Check if the device control information and length match
-                * the user supplied information.
-                */
-               ret = uvc_query_ctrl(dev, UVC_GET_LEN, ctrl->entity->id,
-                                    dev->intfnum, info->selector, uvc_data, 2);
-               if (ret < 0) {
-                       uvc_trace(UVC_TRACE_CONTROL,
-                               "GET_LEN failed on control %pUl/%u (%d).\n",
-                               info->entity, info->selector, ret);
-                       goto done;
-               }
-
-               if (info->size != le16_to_cpu(*(__le16 *)uvc_data)) {
-                       uvc_trace(UVC_TRACE_CONTROL, "Control %pUl/%u size "
-                               "doesn't match user supplied value.\n",
-                               info->entity, info->selector);
-                       ret = -EINVAL;
-                       goto done;
-               }
+       int ret = 0;
 
-               ret = uvc_query_ctrl(dev, UVC_GET_INFO, ctrl->entity->id,
-                                    dev->intfnum, info->selector, uvc_info, 1);
-               if (ret < 0) {
-                       uvc_trace(UVC_TRACE_CONTROL,
-                               "GET_INFO failed on control %pUl/%u (%d).\n",
-                               info->entity, info->selector, ret);
-                       goto done;
-               }
+       memcpy(&ctrl->info, info, sizeof(*info));
+       INIT_LIST_HEAD(&ctrl->info.mappings);
 
-               if (((info->flags & UVC_CONTROL_GET_CUR) &&
-                   !(*uvc_info & UVC_CONTROL_CAP_GET)) ||
-                   ((info->flags & UVC_CONTROL_SET_CUR) &&
-                   !(*uvc_info & UVC_CONTROL_CAP_SET))) {
-                       uvc_trace(UVC_TRACE_CONTROL, "Control %pUl/%u flags "
-                               "don't match supported operations.\n",
-                               info->entity, info->selector);
-                       ret = -EINVAL;
-                       goto done;
-               }
+       /* Allocate an array to save control values (cur, def, max, etc.) */
+       ctrl->uvc_data = kzalloc(ctrl->info.size * UVC_CTRL_DATA_LAST + 1,
+                                GFP_KERNEL);
+       if (ctrl->uvc_data == NULL) {
+               ret = -ENOMEM;
+               goto done;
        }
 
-       ctrl->info = info;
-       ctrl->uvc_data = uvc_data;
-       ctrl->uvc_info = uvc_info;
+       ctrl->initialized = 1;
 
        uvc_trace(UVC_TRACE_CONTROL, "Added control %pUl/%u to device %s "
-               "entity %u\n", ctrl->info->entity, ctrl->info->selector,
-               dev->udev->devpath, entity->id);
+               "entity %u\n", ctrl->info.entity, ctrl->info.selector,
+               dev->udev->devpath, ctrl->entity->id);
 
 done:
        if (ret < 0)
-               kfree(uvc_data);
-
+               kfree(ctrl->uvc_data);
        return ret;
 }
 
 /*
- * Add an item to the UVC control information list, and instantiate a control
- * structure for each device that supports the control.
+ * Add a control mapping to a given control.
  */
-int uvc_ctrl_add_info(struct uvc_control_info *info)
+static int __uvc_ctrl_add_mapping(struct uvc_device *dev,
+       struct uvc_control *ctrl, const struct uvc_control_mapping *mapping)
 {
-       struct uvc_control_info *ctrl;
-       struct uvc_device *dev;
-       int ret = 0;
-
-       /* Find matching controls by walking the devices, entities and
-        * controls list.
-        */
-       mutex_lock(&uvc_driver.ctrl_mutex);
+       struct uvc_control_mapping *map;
+       unsigned int size;
 
-       /* First check if the list contains a control matching the new one.
-        * Bail out if it does.
+       /* Most mappings come from static kernel data and need to be duplicated.
+        * Mappings that come from userspace will be unnecessarily duplicated,
+        * this could be optimized.
         */
-       list_for_each_entry(ctrl, &uvc_driver.controls, list) {
-               if (memcmp(ctrl->entity, info->entity, 16))
-                       continue;
+       map = kmemdup(mapping, sizeof(*mapping), GFP_KERNEL);
+       if (map == NULL)
+               return -ENOMEM;
 
-               if (ctrl->selector == info->selector) {
-                       uvc_trace(UVC_TRACE_CONTROL,
-                               "Control %pUl/%u is already defined.\n",
-                               info->entity, info->selector);
-                       ret = -EEXIST;
-                       goto end;
-               }
-               if (ctrl->index == info->index) {
-                       uvc_trace(UVC_TRACE_CONTROL,
-                               "Control %pUl/%u would overwrite index %d.\n",
-                               info->entity, info->selector, info->index);
-                       ret = -EEXIST;
-                       goto end;
-               }
+       size = sizeof(*mapping->menu_info) * mapping->menu_count;
+       map->menu_info = kmemdup(mapping->menu_info, size, GFP_KERNEL);
+       if (map->menu_info == NULL) {
+               kfree(map);
+               return -ENOMEM;
        }
 
-       list_for_each_entry(dev, &uvc_driver.devices, list)
-               uvc_ctrl_add_ctrl(dev, info);
+       if (map->get == NULL)
+               map->get = uvc_get_le_value;
+       if (map->set == NULL)
+               map->set = uvc_set_le_value;
 
-       INIT_LIST_HEAD(&info->mappings);
-       list_add_tail(&info->list, &uvc_driver.controls);
-end:
-       mutex_unlock(&uvc_driver.ctrl_mutex);
-       return ret;
+       map->ctrl = &ctrl->info;
+       list_add_tail(&map->list, &ctrl->info.mappings);
+       uvc_trace(UVC_TRACE_CONTROL,
+               "Adding mapping '%s' to control %pUl/%u.\n",
+               map->name, ctrl->info.entity, ctrl->info.selector);
+
+       return 0;
 }
 
-int uvc_ctrl_add_mapping(struct uvc_control_mapping *mapping)
+int uvc_ctrl_add_mapping(struct uvc_video_chain *chain,
+       const struct uvc_control_mapping *mapping)
 {
-       struct uvc_control_info *info;
+       struct uvc_device *dev = chain->dev;
        struct uvc_control_mapping *map;
-       int ret = -EINVAL;
-
-       if (mapping->get == NULL)
-               mapping->get = uvc_get_le_value;
-       if (mapping->set == NULL)
-               mapping->set = uvc_set_le_value;
+       struct uvc_entity *entity;
+       struct uvc_control *ctrl;
+       int found = 0;
+       int ret;
 
        if (mapping->id & ~V4L2_CTRL_ID_MASK) {
-               uvc_trace(UVC_TRACE_CONTROL, "Can't add mapping '%s' with "
-                       "invalid control id 0x%08x\n", mapping->name,
+               uvc_trace(UVC_TRACE_CONTROL, "Can't add mapping '%s', control "
+                       "id 0x%08x is invalid.\n", mapping->name,
                        mapping->id);
                return -EINVAL;
        }
 
-       mutex_lock(&uvc_driver.ctrl_mutex);
-       list_for_each_entry(info, &uvc_driver.controls, list) {
-               if (memcmp(info->entity, mapping->entity, 16) ||
-                       info->selector != mapping->selector)
-                       continue;
+       /* Search for the matching (GUID/CS) control in the given device */
+       list_for_each_entry(entity, &dev->entities, list) {
+               unsigned int i;
 
-               if (info->size * 8 < mapping->size + mapping->offset) {
-                       uvc_trace(UVC_TRACE_CONTROL,
-                               "Mapping '%s' would overflow control %pUl/%u\n",
-                               mapping->name, info->entity, info->selector);
-                       ret = -EOVERFLOW;
-                       goto end;
-               }
+               if (UVC_ENTITY_TYPE(entity) != UVC_VC_EXTENSION_UNIT ||
+                   !uvc_entity_match_guid(entity, mapping->entity))
+                       continue;
 
-               /* Check if the list contains a mapping matching the new one.
-                * Bail out if it does.
-                */
-               list_for_each_entry(map, &info->mappings, list) {
-                       if (map->id == mapping->id) {
-                               uvc_trace(UVC_TRACE_CONTROL, "Mapping '%s' is "
-                                       "already defined.\n", mapping->name);
-                               ret = -EEXIST;
-                               goto end;
+               for (i = 0; i < entity->ncontrols; ++i) {
+                       ctrl = &entity->controls[i];
+                       if (ctrl->index == mapping->selector - 1) {
+                               found = 1;
+                               break;
                        }
                }
 
-               mapping->ctrl = info;
-               list_add_tail(&mapping->list, &info->mappings);
-               uvc_trace(UVC_TRACE_CONTROL,
-                       "Adding mapping %s to control %pUl/%u.\n",
-                       mapping->name, info->entity, info->selector);
+               if (found)
+                       break;
+       }
+       if (!found)
+               return -ENOENT;
 
-               ret = 0;
-               break;
+       if (mutex_lock_interruptible(&chain->ctrl_mutex))
+               return -ERESTARTSYS;
+
+       /* Perform delayed initialization of XU controls */
+       ret = uvc_ctrl_init_xu_ctrl(dev, ctrl);
+       if (ret < 0) {
+               ret = -ENOENT;
+               goto done;
+       }
+
+       list_for_each_entry(map, &ctrl->info.mappings, list) {
+               if (mapping->id == map->id) {
+                       uvc_trace(UVC_TRACE_CONTROL, "Can't add mapping '%s', "
+                               "control id 0x%08x already exists.\n",
+                               mapping->name, mapping->id);
+                       ret = -EEXIST;
+                       goto done;
+               }
        }
-end:
-       mutex_unlock(&uvc_driver.ctrl_mutex);
+
+       /* Prevent excess memory consumption */
+       if (atomic_inc_return(&dev->nmappings) > UVC_MAX_CONTROL_MAPPINGS) {
+               atomic_dec(&dev->nmappings);
+               uvc_trace(UVC_TRACE_CONTROL, "Can't add mapping '%s', maximum "
+                       "mappings count (%u) exceeded.\n", mapping->name,
+                       UVC_MAX_CONTROL_MAPPINGS);
+               ret = -ENOMEM;
+               goto done;
+       }
+
+       ret = __uvc_ctrl_add_mapping(dev, ctrl, mapping);
+       if (ret < 0)
+               atomic_dec(&dev->nmappings);
+
+done:
+       mutex_unlock(&chain->ctrl_mutex);
        return ret;
 }
 
@@ -1496,29 +1631,49 @@ end:
  * are currently the ones that crash the camera or unconditionally return an
  * error when queried.
  */
-static void
-uvc_ctrl_prune_entity(struct uvc_device *dev, struct uvc_entity *entity)
+static void uvc_ctrl_prune_entity(struct uvc_device *dev,
+       struct uvc_entity *entity)
 {
-       static const struct {
+       struct uvc_ctrl_blacklist {
                struct usb_device_id id;
                u8 index;
-       } blacklist[] = {
+       };
+
+       static const struct uvc_ctrl_blacklist processing_blacklist[] = {
                { { USB_DEVICE(0x13d3, 0x509b) }, 9 }, /* Gain */
                { { USB_DEVICE(0x1c4f, 0x3000) }, 6 }, /* WB Temperature */
                { { USB_DEVICE(0x5986, 0x0241) }, 2 }, /* Hue */
        };
+       static const struct uvc_ctrl_blacklist camera_blacklist[] = {
+               { { USB_DEVICE(0x06f8, 0x3005) }, 9 }, /* Zoom, Absolute */
+       };
 
-       u8 *controls;
+       const struct uvc_ctrl_blacklist *blacklist;
        unsigned int size;
+       unsigned int count;
        unsigned int i;
+       u8 *controls;
 
-       if (UVC_ENTITY_TYPE(entity) != UVC_VC_PROCESSING_UNIT)
-               return;
+       switch (UVC_ENTITY_TYPE(entity)) {
+       case UVC_VC_PROCESSING_UNIT:
+               blacklist = processing_blacklist;
+               count = ARRAY_SIZE(processing_blacklist);
+               controls = entity->processing.bmControls;
+               size = entity->processing.bControlSize;
+               break;
+
+       case UVC_ITT_CAMERA:
+               blacklist = camera_blacklist;
+               count = ARRAY_SIZE(camera_blacklist);
+               controls = entity->camera.bmControls;
+               size = entity->camera.bControlSize;
+               break;
 
-       controls = entity->processing.bmControls;
-       size = entity->processing.bControlSize;
+       default:
+               return;
+       }
 
-       for (i = 0; i < ARRAY_SIZE(blacklist); ++i) {
+       for (i = 0; i < count; ++i) {
                if (!usb_match_one_id(dev->intf, &blacklist[i].id))
                        continue;
 
@@ -1533,18 +1688,55 @@ uvc_ctrl_prune_entity(struct uvc_device *dev, struct uvc_entity *entity)
        }
 }
 
+/*
+ * Add control information and hardcoded stock control mappings to the given
+ * device.
+ */
+static void uvc_ctrl_init_ctrl(struct uvc_device *dev, struct uvc_control *ctrl)
+{
+       const struct uvc_control_info *info = uvc_ctrls;
+       const struct uvc_control_info *iend = info + ARRAY_SIZE(uvc_ctrls);
+       const struct uvc_control_mapping *mapping = uvc_ctrl_mappings;
+       const struct uvc_control_mapping *mend =
+               mapping + ARRAY_SIZE(uvc_ctrl_mappings);
+
+       /* XU controls initialization requires querying the device for control
+        * information. As some buggy UVC devices will crash when queried
+        * repeatedly in a tight loop, delay XU controls initialization until
+        * first use.
+        */
+       if (UVC_ENTITY_TYPE(ctrl->entity) == UVC_VC_EXTENSION_UNIT)
+               return;
+
+       for (; info < iend; ++info) {
+               if (uvc_entity_match_guid(ctrl->entity, info->entity) &&
+                   ctrl->index == info->index) {
+                       uvc_ctrl_add_info(dev, ctrl, info);
+                       break;
+                }
+       }
+
+       if (!ctrl->initialized)
+               return;
+
+       for (; mapping < mend; ++mapping) {
+               if (uvc_entity_match_guid(ctrl->entity, mapping->entity) &&
+                   ctrl->info.selector == mapping->selector)
+                       __uvc_ctrl_add_mapping(dev, ctrl, mapping);
+       }
+}
+
 /*
  * Initialize device controls.
  */
 int uvc_ctrl_init_device(struct uvc_device *dev)
 {
-       struct uvc_control_info *info;
-       struct uvc_control *ctrl;
        struct uvc_entity *entity;
        unsigned int i;
 
        /* Walk the entities list and instantiate controls */
        list_for_each_entry(entity, &dev->entities, list) {
+               struct uvc_control *ctrl;
                unsigned int bControlSize = 0, ncontrols = 0;
                __u8 *bmControls = NULL;
 
@@ -1559,20 +1751,22 @@ int uvc_ctrl_init_device(struct uvc_device *dev)
                        bControlSize = entity->camera.bControlSize;
                }
 
+               /* Remove bogus/blacklisted controls */
                uvc_ctrl_prune_entity(dev, entity);
 
+               /* Count supported controls and allocate the controls array */
                for (i = 0; i < bControlSize; ++i)
                        ncontrols += hweight8(bmControls[i]);
-
                if (ncontrols == 0)
                        continue;
 
-               entity->controls = kzalloc(ncontrols*sizeof *ctrl, GFP_KERNEL);
+               entity->controls = kzalloc(ncontrols * sizeof(*ctrl),
+                                          GFP_KERNEL);
                if (entity->controls == NULL)
                        return -ENOMEM;
-
                entity->ncontrols = ncontrols;
 
+               /* Initialize all supported controls */
                ctrl = entity->controls;
                for (i = 0; i < bControlSize * 8; ++i) {
                        if (uvc_test_bit(bmControls, i) == 0)
@@ -1580,81 +1774,47 @@ int uvc_ctrl_init_device(struct uvc_device *dev)
 
                        ctrl->entity = entity;
                        ctrl->index = i;
+
+                       uvc_ctrl_init_ctrl(dev, ctrl);
                        ctrl++;
                }
        }
 
-       /* Walk the controls info list and associate them with the device
-        * controls, then add the device to the global device list. This has
-        * to be done while holding the controls lock, to make sure
-        * uvc_ctrl_add_info() will not get called in-between.
-        */
-       mutex_lock(&uvc_driver.ctrl_mutex);
-       list_for_each_entry(info, &uvc_driver.controls, list)
-               uvc_ctrl_add_ctrl(dev, info);
-
-       list_add_tail(&dev->list, &uvc_driver.devices);
-       mutex_unlock(&uvc_driver.ctrl_mutex);
-
        return 0;
 }
 
 /*
  * Cleanup device controls.
  */
-void uvc_ctrl_cleanup_device(struct uvc_device *dev)
+static void uvc_ctrl_cleanup_mappings(struct uvc_device *dev,
+       struct uvc_control *ctrl)
 {
-       struct uvc_entity *entity;
-       unsigned int i;
-
-       /* Remove the device from the global devices list */
-       mutex_lock(&uvc_driver.ctrl_mutex);
-       if (dev->list.next != NULL)
-               list_del(&dev->list);
-       mutex_unlock(&uvc_driver.ctrl_mutex);
-
-       list_for_each_entry(entity, &dev->entities, list) {
-               for (i = 0; i < entity->ncontrols; ++i)
-                       kfree(entity->controls[i].uvc_data);
+       struct uvc_control_mapping *mapping, *nm;
 
-               kfree(entity->controls);
+       list_for_each_entry_safe(mapping, nm, &ctrl->info.mappings, list) {
+               list_del(&mapping->list);
+               kfree(mapping->menu_info);
+               kfree(mapping);
        }
 }
 
-void uvc_ctrl_cleanup(void)
+void uvc_ctrl_cleanup_device(struct uvc_device *dev)
 {
-       struct uvc_control_info *info;
-       struct uvc_control_info *ni;
-       struct uvc_control_mapping *mapping;
-       struct uvc_control_mapping *nm;
+       struct uvc_entity *entity;
+       unsigned int i;
 
-       list_for_each_entry_safe(info, ni, &uvc_driver.controls, list) {
-               if (!(info->flags & UVC_CONTROL_EXTENSION))
-                       continue;
+       /* Free controls and control mappings for all entities. */
+       list_for_each_entry(entity, &dev->entities, list) {
+               for (i = 0; i < entity->ncontrols; ++i) {
+                       struct uvc_control *ctrl = &entity->controls[i];
+
+                       if (!ctrl->initialized)
+                               continue;
 
-               list_for_each_entry_safe(mapping, nm, &info->mappings, list) {
-                       list_del(&mapping->list);
-                       kfree(mapping->menu_info);
-                       kfree(mapping);
+                       uvc_ctrl_cleanup_mappings(dev, ctrl);
+                       kfree(ctrl->uvc_data);
                }
 
-               list_del(&info->list);
-               kfree(info);
+               kfree(entity->controls);
        }
 }
-
-void uvc_ctrl_init(void)
-{
-       struct uvc_control_info *ctrl = uvc_ctrls;
-       struct uvc_control_info *cend = ctrl + ARRAY_SIZE(uvc_ctrls);
-       struct uvc_control_mapping *mapping = uvc_ctrl_mappings;
-       struct uvc_control_mapping *mend =
-               mapping + ARRAY_SIZE(uvc_ctrl_mappings);
-
-       for (; ctrl < cend; ++ctrl)
-               uvc_ctrl_add_info(ctrl);
-
-       for (; mapping < mend; ++mapping)
-               uvc_ctrl_add_mapping(mapping);
-}
-