diff options
Diffstat (limited to 'drivers/media/video/uvc/uvc_ctrl.c')
| -rw-r--r-- | drivers/media/video/uvc/uvc_ctrl.c | 105 | 
1 files changed, 85 insertions, 20 deletions
diff --git a/drivers/media/video/uvc/uvc_ctrl.c b/drivers/media/video/uvc/uvc_ctrl.c index 27a79f087b1..a350fad0db4 100644 --- a/drivers/media/video/uvc/uvc_ctrl.c +++ b/drivers/media/video/uvc/uvc_ctrl.c @@ -606,6 +606,26 @@ static struct uvc_control_mapping uvc_ctrl_mappings[] = {  		.set		= uvc_ctrl_set_zoom,  	},  	{ +		.id		= V4L2_CID_PAN_ABSOLUTE, +		.name		= "Pan (Absolute)", +		.entity		= UVC_GUID_UVC_CAMERA, +		.selector	= UVC_CT_PANTILT_ABSOLUTE_CONTROL, +		.size		= 32, +		.offset		= 0, +		.v4l2_type	= V4L2_CTRL_TYPE_INTEGER, +		.data_type	= UVC_CTRL_DATA_TYPE_UNSIGNED, +	}, +	{ +		.id		= V4L2_CID_TILT_ABSOLUTE, +		.name		= "Tilt (Absolute)", +		.entity		= UVC_GUID_UVC_CAMERA, +		.selector	= UVC_CT_PANTILT_ABSOLUTE_CONTROL, +		.size		= 32, +		.offset		= 32, +		.v4l2_type	= V4L2_CTRL_TYPE_INTEGER, +		.data_type	= UVC_CTRL_DATA_TYPE_UNSIGNED, +	}, +	{  		.id		= V4L2_CID_PRIVACY,  		.name		= "Privacy",  		.entity		= UVC_GUID_UVC_CAMERA, @@ -623,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->data + id * ctrl->info->size; +	return ctrl->uvc_data + id * ctrl->info->size;  }  static inline int uvc_test_bit(const __u8 *data, int bit) @@ -678,6 +698,14 @@ static void uvc_set_le_value(struct uvc_control_mapping *mapping,  	int offset = mapping->offset;  	__u8 mask; +	/* According to the v4l2 spec, writing any value to a button control +	 * should result in the action belonging to the button control being +	 * triggered. UVC devices however want to see a 1 written -> override +	 * value. +	 */ +	if (mapping->v4l2_type == V4L2_CTRL_TYPE_BUTTON) +		value = -1; +  	data += offset / 8;  	offset &= 7; @@ -1265,13 +1293,15 @@ int uvc_ctrl_resume_device(struct uvc_device *dev)   * Control and mapping handling   */ -static void uvc_ctrl_add_ctrl(struct uvc_device *dev, +static int uvc_ctrl_add_ctrl(struct uvc_device *dev,  	struct uvc_control_info *info)  {  	struct uvc_entity *entity;  	struct uvc_control *ctrl = NULL; -	int ret, found = 0; +	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)) @@ -1290,56 +1320,69 @@ static void uvc_ctrl_add_ctrl(struct uvc_device *dev,  	}  	if (!found) -		return; +		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.  		 */ -		__u32 flags; -		__le16 size; -		__u8 inf; -  		ret = uvc_query_ctrl(dev, UVC_GET_LEN, ctrl->entity->id, -			dev->intfnum, info->selector, (__u8 *)&size, 2); +				     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); -			return; +			goto done;  		} -		if (info->size != le16_to_cpu(size)) { +		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); -			return; +			ret = -EINVAL; +			goto done;  		}  		ret = uvc_query_ctrl(dev, UVC_GET_INFO, ctrl->entity->id, -			dev->intfnum, info->selector, &inf, 1); +				     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); -			return; +			goto done;  		} -		flags = info->flags; -		if (((flags & UVC_CONTROL_GET_CUR) && !(inf & (1 << 0))) || -		    ((flags & UVC_CONTROL_SET_CUR) && !(inf & (1 << 1)))) { +		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); -			return; +			ret = -EINVAL; +			goto done;  		}  	}  	ctrl->info = info; -	ctrl->data = kmalloc(ctrl->info->size * UVC_CTRL_DATA_LAST, GFP_KERNEL); +	ctrl->uvc_data = uvc_data; +	ctrl->uvc_info = uvc_info; +  	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); + +done: +	if (ret < 0) +		kfree(uvc_data); + +	return ret;  }  /* @@ -1572,12 +1615,34 @@ void uvc_ctrl_cleanup_device(struct uvc_device *dev)  	list_for_each_entry(entity, &dev->entities, list) {  		for (i = 0; i < entity->ncontrols; ++i) -			kfree(entity->controls[i].data); +			kfree(entity->controls[i].uvc_data);  		kfree(entity->controls);  	}  } +void uvc_ctrl_cleanup(void) +{ +	struct uvc_control_info *info; +	struct uvc_control_info *ni; +	struct uvc_control_mapping *mapping; +	struct uvc_control_mapping *nm; + +	list_for_each_entry_safe(info, ni, &uvc_driver.controls, list) { +		if (!(info->flags & UVC_CONTROL_EXTENSION)) +			continue; + +		list_for_each_entry_safe(mapping, nm, &info->mappings, list) { +			list_del(&mapping->list); +			kfree(mapping->menu_info); +			kfree(mapping); +		} + +		list_del(&info->list); +		kfree(info); +	} +} +  void uvc_ctrl_init(void)  {  	struct uvc_control_info *ctrl = uvc_ctrls;  |