V4L/DVB (9903): uvcvideo: V4L2 zoom controls support

Add support for absolute and continuous zoom controls (mapped to absolute
and relative UVC zoom controls).

Signed-off-by: Laurent Pinchart <laurent.pinchart@skynet.be>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
diff --git a/drivers/media/video/uvc/uvc_ctrl.c b/drivers/media/video/uvc/uvc_ctrl.c
index 335cb00..2208165 100644
--- a/drivers/media/video/uvc/uvc_ctrl.c
+++ b/drivers/media/video/uvc/uvc_ctrl.c
@@ -327,6 +327,31 @@
 	{ 8, "Aperture Priority Mode" },
 };
 
+static __s32 uvc_ctrl_get_zoom(struct uvc_control_mapping *mapping,
+	__u8 query, const __u8 *data)
+{
+	__s8 zoom = (__s8)data[0];
+
+	switch (query) {
+	case GET_CUR:
+		return (zoom == 0) ? 0 : (zoom > 0 ? data[2] : -data[2]);
+
+	case GET_MIN:
+	case GET_MAX:
+	case GET_RES:
+	case GET_DEF:
+	default:
+		return data[2];
+	}
+}
+
+static void uvc_ctrl_set_zoom(struct uvc_control_mapping *mapping,
+	__s32 value, __u8 *data)
+{
+	data[0] = value == 0 ? 0 : (value > 0) ? 1 : 0xff;
+	data[2] = min(abs(value), 0xff);
+}
+
 static struct uvc_control_mapping uvc_ctrl_mappings[] = {
 	{
 		.id		= V4L2_CID_BRIGHTNESS,
@@ -533,6 +558,28 @@
 		.data_type	= UVC_CTRL_DATA_TYPE_BOOLEAN,
 	},
 	{
+		.id		= V4L2_CID_ZOOM_ABSOLUTE,
+		.name		= "Zoom, Absolute",
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= CT_ZOOM_ABSOLUTE_CONTROL,
+		.size		= 16,
+		.offset		= 0,
+		.v4l2_type	= V4L2_CTRL_TYPE_INTEGER,
+		.data_type	= UVC_CTRL_DATA_TYPE_UNSIGNED,
+	},
+	{
+		.id		= V4L2_CID_ZOOM_CONTINUOUS,
+		.name		= "Zoom, Continuous",
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= CT_ZOOM_RELATIVE_CONTROL,
+		.size		= 0,
+		.offset		= 0,
+		.v4l2_type	= V4L2_CTRL_TYPE_INTEGER,
+		.data_type	= UVC_CTRL_DATA_TYPE_SIGNED,
+		.get		= uvc_ctrl_get_zoom,
+		.set		= uvc_ctrl_set_zoom,
+	},
+	{
 		.id		= V4L2_CID_PRIVACY,
 		.name		= "Privacy",
 		.entity		= UVC_GUID_UVC_CAMERA,
@@ -568,8 +615,8 @@
  * a signed 32bit integer. Sign extension will be performed if the mapping
  * references a signed data type.
  */
-static __s32 uvc_get_le_value(const __u8 *data,
-	struct uvc_control_mapping *mapping)
+static __s32 uvc_get_le_value(struct uvc_control_mapping *mapping,
+	__u8 query, const __u8 *data)
 {
 	int bits = mapping->size;
 	int offset = mapping->offset;
@@ -598,8 +645,8 @@
 /* Set the bit string specified by mapping->offset and mapping->size
  * in the little-endian data stored at 'data' to the value 'value'.
  */
-static void uvc_set_le_value(__s32 value, __u8 *data,
-	struct uvc_control_mapping *mapping)
+static void uvc_set_le_value(struct uvc_control_mapping *mapping,
+	__s32 value, __u8 *data)
 {
 	int bits = mapping->size;
 	int offset = mapping->offset;
@@ -751,7 +798,7 @@
 				video->dev->intfnum, ctrl->info->selector,
 				data, ctrl->info->size)) < 0)
 			goto out;
-		v4l2_ctrl->default_value = uvc_get_le_value(data, mapping);
+		v4l2_ctrl->default_value = mapping->get(mapping, GET_DEF, data);
 	}
 
 	switch (mapping->v4l2_type) {
@@ -787,21 +834,21 @@
 				video->dev->intfnum, ctrl->info->selector,
 				data, ctrl->info->size)) < 0)
 			goto out;
-		v4l2_ctrl->minimum = uvc_get_le_value(data, mapping);
+		v4l2_ctrl->minimum = mapping->get(mapping, GET_MIN, data);
 	}
 	if (ctrl->info->flags & UVC_CONTROL_GET_MAX) {
 		if ((ret = uvc_query_ctrl(video->dev, GET_MAX, ctrl->entity->id,
 				video->dev->intfnum, ctrl->info->selector,
 				data, ctrl->info->size)) < 0)
 			goto out;
-		v4l2_ctrl->maximum = uvc_get_le_value(data, mapping);
+		v4l2_ctrl->maximum = mapping->get(mapping, GET_MAX, data);
 	}
 	if (ctrl->info->flags & UVC_CONTROL_GET_RES) {
 		if ((ret = uvc_query_ctrl(video->dev, GET_RES, ctrl->entity->id,
 				video->dev->intfnum, ctrl->info->selector,
 				data, ctrl->info->size)) < 0)
 			goto out;
-		v4l2_ctrl->step = uvc_get_le_value(data, mapping);
+		v4l2_ctrl->step = mapping->get(mapping, GET_RES, data);
 	}
 
 	ret = 0;
@@ -938,8 +985,8 @@
 		ctrl->loaded = 1;
 	}
 
-	xctrl->value = uvc_get_le_value(
-		uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT), mapping);
+	xctrl->value = mapping->get(mapping, GET_CUR,
+		uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT));
 
 	if (mapping->v4l2_type == V4L2_CTRL_TYPE_MENU) {
 		menu = mapping->menu_info;
@@ -995,8 +1042,8 @@
 		       ctrl->info->size);
 	}
 
-	uvc_set_le_value(value,
-		uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT), mapping);
+	mapping->set(mapping, value,
+		uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT));
 
 	ctrl->dirty = 1;
 	ctrl->modified = 1;
@@ -1272,6 +1319,11 @@
 	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;
+
 	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,
diff --git a/drivers/media/video/uvc/uvc_v4l2.c b/drivers/media/video/uvc/uvc_v4l2.c
index 7b1c136..2e1fd1b 100644
--- a/drivers/media/video/uvc/uvc_v4l2.c
+++ b/drivers/media/video/uvc/uvc_v4l2.c
@@ -914,7 +914,7 @@
 		if (!capable(CAP_SYS_ADMIN))
 			return -EPERM;
 
-		info = kmalloc(sizeof *info, GFP_KERNEL);
+		info = kzalloc(sizeof *info, GFP_KERNEL);
 		if (info == NULL)
 			return -ENOMEM;
 
@@ -941,7 +941,7 @@
 		if (!capable(CAP_SYS_ADMIN))
 			return -EPERM;
 
-		map = kmalloc(sizeof *map, GFP_KERNEL);
+		map = kzalloc(sizeof *map, GFP_KERNEL);
 		if (map == NULL)
 			return -ENOMEM;
 
diff --git a/drivers/media/video/uvc/uvcvideo.h b/drivers/media/video/uvc/uvcvideo.h
index 00d5937..2a5bdac 100644
--- a/drivers/media/video/uvc/uvcvideo.h
+++ b/drivers/media/video/uvc/uvcvideo.h
@@ -384,6 +384,11 @@
 
 	struct uvc_menu_info *menu_info;
 	__u32 menu_count;
+
+	__s32 (*get) (struct uvc_control_mapping *mapping, __u8 query,
+		      const __u8 *data);
+	void (*set) (struct uvc_control_mapping *mapping, __s32 value,
+		     __u8 *data);
 };
 
 struct uvc_control {