diff options
author | David S. Miller <davem@davemloft.net> | 2012-10-09 13:14:32 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2012-10-09 13:14:32 -0400 |
commit | 8dd9117cc7a021ced1c5cf177e2d44dd92b88617 (patch) | |
tree | cad990f58f9ec6d400226dda86718fc10781416e /drivers/media/usb/uvc | |
parent | 16e310ae6ed352c4963b1f2413fcd88fa693eeda (diff) | |
parent | 547b1e81afe3119f7daf702cc03b158495535a25 (diff) |
Merge git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux
Pulled mainline in order to get the UAPI infrastructure already
merged before I pull in David Howells's UAPI trees for networking.
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/media/usb/uvc')
-rw-r--r-- | drivers/media/usb/uvc/Kconfig | 19 | ||||
-rw-r--r-- | drivers/media/usb/uvc/Makefile | 6 | ||||
-rw-r--r-- | drivers/media/usb/uvc/uvc_ctrl.c | 2165 | ||||
-rw-r--r-- | drivers/media/usb/uvc/uvc_debugfs.c | 136 | ||||
-rw-r--r-- | drivers/media/usb/uvc/uvc_driver.c | 2462 | ||||
-rw-r--r-- | drivers/media/usb/uvc/uvc_entity.c | 126 | ||||
-rw-r--r-- | drivers/media/usb/uvc/uvc_isight.c | 137 | ||||
-rw-r--r-- | drivers/media/usb/uvc/uvc_queue.c | 360 | ||||
-rw-r--r-- | drivers/media/usb/uvc/uvc_status.c | 237 | ||||
-rw-r--r-- | drivers/media/usb/uvc/uvc_v4l2.c | 1317 | ||||
-rw-r--r-- | drivers/media/usb/uvc/uvc_video.c | 1879 | ||||
-rw-r--r-- | drivers/media/usb/uvc/uvcvideo.h | 718 |
12 files changed, 9562 insertions, 0 deletions
diff --git a/drivers/media/usb/uvc/Kconfig b/drivers/media/usb/uvc/Kconfig new file mode 100644 index 00000000000..541c9f1e4c6 --- /dev/null +++ b/drivers/media/usb/uvc/Kconfig @@ -0,0 +1,19 @@ +config USB_VIDEO_CLASS + tristate "USB Video Class (UVC)" + select VIDEOBUF2_VMALLOC + ---help--- + Support for the USB Video Class (UVC). Currently only video + input devices, such as webcams, are supported. + + For more information see: <http://linux-uvc.berlios.de/> + +config USB_VIDEO_CLASS_INPUT_EVDEV + bool "UVC input events device support" + default y + depends on USB_VIDEO_CLASS + depends on USB_VIDEO_CLASS=INPUT || INPUT=y + ---help--- + This option makes USB Video Class devices register an input device + to report button events. + + If you are in doubt, say Y. diff --git a/drivers/media/usb/uvc/Makefile b/drivers/media/usb/uvc/Makefile new file mode 100644 index 00000000000..c26d12fdb8f --- /dev/null +++ b/drivers/media/usb/uvc/Makefile @@ -0,0 +1,6 @@ +uvcvideo-objs := uvc_driver.o uvc_queue.o uvc_v4l2.o uvc_video.o uvc_ctrl.o \ + uvc_status.o uvc_isight.o uvc_debugfs.o +ifeq ($(CONFIG_MEDIA_CONTROLLER),y) +uvcvideo-objs += uvc_entity.o +endif +obj-$(CONFIG_USB_VIDEO_CLASS) += uvcvideo.o diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c new file mode 100644 index 00000000000..f7061a5ef1d --- /dev/null +++ b/drivers/media/usb/uvc/uvc_ctrl.c @@ -0,0 +1,2165 @@ +/* + * uvc_ctrl.c -- USB Video Class driver - Controls + * + * 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/usb.h> +#include <linux/videodev2.h> +#include <linux/vmalloc.h> +#include <linux/wait.h> +#include <linux/atomic.h> +#include <media/v4l2-ctrls.h> + +#include "uvcvideo.h" + +#define UVC_CTRL_DATA_CURRENT 0 +#define UVC_CTRL_DATA_BACKUP 1 +#define UVC_CTRL_DATA_MIN 2 +#define UVC_CTRL_DATA_MAX 3 +#define UVC_CTRL_DATA_RES 4 +#define UVC_CTRL_DATA_DEF 5 +#define UVC_CTRL_DATA_LAST 6 + +/* ------------------------------------------------------------------------ + * Controls + */ + +static struct uvc_control_info uvc_ctrls[] = { + { + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_BRIGHTNESS_CONTROL, + .index = 0, + .size = 2, + .flags = UVC_CTRL_FLAG_SET_CUR + | UVC_CTRL_FLAG_GET_RANGE + | UVC_CTRL_FLAG_RESTORE, + }, + { + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_CONTRAST_CONTROL, + .index = 1, + .size = 2, + .flags = UVC_CTRL_FLAG_SET_CUR + | UVC_CTRL_FLAG_GET_RANGE + | UVC_CTRL_FLAG_RESTORE, + }, + { + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_HUE_CONTROL, + .index = 2, + .size = 2, + .flags = UVC_CTRL_FLAG_SET_CUR + | UVC_CTRL_FLAG_GET_RANGE + | UVC_CTRL_FLAG_RESTORE + | UVC_CTRL_FLAG_AUTO_UPDATE, + }, + { + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_SATURATION_CONTROL, + .index = 3, + .size = 2, + .flags = UVC_CTRL_FLAG_SET_CUR + | UVC_CTRL_FLAG_GET_RANGE + | UVC_CTRL_FLAG_RESTORE, + }, + { + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_SHARPNESS_CONTROL, + .index = 4, + .size = 2, + .flags = UVC_CTRL_FLAG_SET_CUR + | UVC_CTRL_FLAG_GET_RANGE + | UVC_CTRL_FLAG_RESTORE, + }, + { + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_GAMMA_CONTROL, + .index = 5, + .size = 2, + .flags = UVC_CTRL_FLAG_SET_CUR + | UVC_CTRL_FLAG_GET_RANGE + | UVC_CTRL_FLAG_RESTORE, + }, + { + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_WHITE_BALANCE_TEMPERATURE_CONTROL, + .index = 6, + .size = 2, + .flags = UVC_CTRL_FLAG_SET_CUR + | UVC_CTRL_FLAG_GET_RANGE + | UVC_CTRL_FLAG_RESTORE + | UVC_CTRL_FLAG_AUTO_UPDATE, + }, + { + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_WHITE_BALANCE_COMPONENT_CONTROL, + .index = 7, + .size = 4, + .flags = UVC_CTRL_FLAG_SET_CUR + | UVC_CTRL_FLAG_GET_RANGE + | UVC_CTRL_FLAG_RESTORE + | UVC_CTRL_FLAG_AUTO_UPDATE, + }, + { + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_BACKLIGHT_COMPENSATION_CONTROL, + .index = 8, + .size = 2, + .flags = UVC_CTRL_FLAG_SET_CUR + | UVC_CTRL_FLAG_GET_RANGE + | UVC_CTRL_FLAG_RESTORE, + }, + { + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_GAIN_CONTROL, + .index = 9, + .size = 2, + .flags = UVC_CTRL_FLAG_SET_CUR + | UVC_CTRL_FLAG_GET_RANGE + | UVC_CTRL_FLAG_RESTORE, + }, + { + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_POWER_LINE_FREQUENCY_CONTROL, + .index = 10, + .size = 1, + .flags = UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_CUR + | UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_RESTORE, + }, + { + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_HUE_AUTO_CONTROL, + .index = 11, + .size = 1, + .flags = UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_CUR + | UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_RESTORE, + }, + { + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL, + .index = 12, + .size = 1, + .flags = UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_CUR + | UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_RESTORE, + }, + { + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_WHITE_BALANCE_COMPONENT_AUTO_CONTROL, + .index = 13, + .size = 1, + .flags = UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_CUR + | UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_RESTORE, + }, + { + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_DIGITAL_MULTIPLIER_CONTROL, + .index = 14, + .size = 2, + .flags = UVC_CTRL_FLAG_SET_CUR + | UVC_CTRL_FLAG_GET_RANGE + | UVC_CTRL_FLAG_RESTORE, + }, + { + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_DIGITAL_MULTIPLIER_LIMIT_CONTROL, + .index = 15, + .size = 2, + .flags = UVC_CTRL_FLAG_SET_CUR + | UVC_CTRL_FLAG_GET_RANGE + | UVC_CTRL_FLAG_RESTORE, + }, + { + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_ANALOG_VIDEO_STANDARD_CONTROL, + .index = 16, + .size = 1, + .flags = UVC_CTRL_FLAG_GET_CUR, + }, + { + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_ANALOG_LOCK_STATUS_CONTROL, + .index = 17, + .size = 1, + .flags = UVC_CTRL_FLAG_GET_CUR, + }, + { + .entity = UVC_GUID_UVC_CAMERA, + .selector = UVC_CT_SCANNING_MODE_CONTROL, + .index = 0, + .size = 1, + .flags = UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_CUR + | UVC_CTRL_FLAG_RESTORE, + }, + { + .entity = UVC_GUID_UVC_CAMERA, + .selector = UVC_CT_AE_MODE_CONTROL, + .index = 1, + .size = 1, + .flags = UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_CUR + | UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_GET_RES + | UVC_CTRL_FLAG_RESTORE, + }, + { + .entity = UVC_GUID_UVC_CAMERA, + .selector = UVC_CT_AE_PRIORITY_CONTROL, + .index = 2, + .size = 1, + .flags = UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_CUR + | UVC_CTRL_FLAG_RESTORE, + }, + { + .entity = UVC_GUID_UVC_CAMERA, + .selector = UVC_CT_EXPOSURE_TIME_ABSOLUTE_CONTROL, + .index = 3, + .size = 4, + .flags = UVC_CTRL_FLAG_SET_CUR + | UVC_CTRL_FLAG_GET_RANGE + | UVC_CTRL_FLAG_RESTORE, + }, + { + .entity = UVC_GUID_UVC_CAMERA, + .selector = UVC_CT_EXPOSURE_TIME_RELATIVE_CONTROL, + .index = 4, + .size = 1, + .flags = UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_RESTORE, + }, + { + .entity = UVC_GUID_UVC_CAMERA, + .selector = UVC_CT_FOCUS_ABSOLUTE_CONTROL, + .index = 5, + .size = 2, + .flags = UVC_CTRL_FLAG_SET_CUR + | UVC_CTRL_FLAG_GET_RANGE + | UVC_CTRL_FLAG_RESTORE + | UVC_CTRL_FLAG_AUTO_UPDATE, + }, + { + .entity = UVC_GUID_UVC_CAMERA, + .selector = UVC_CT_FOCUS_RELATIVE_CONTROL, + .index = 6, + .size = 2, + .flags = UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_MIN + | UVC_CTRL_FLAG_GET_MAX | UVC_CTRL_FLAG_GET_RES + | UVC_CTRL_FLAG_GET_DEF + | UVC_CTRL_FLAG_AUTO_UPDATE, + }, + { + .entity = UVC_GUID_UVC_CAMERA, + .selector = UVC_CT_IRIS_ABSOLUTE_CONTROL, + .index = 7, + .size = 2, + .flags = UVC_CTRL_FLAG_SET_CUR + | UVC_CTRL_FLAG_GET_RANGE + | UVC_CTRL_FLAG_RESTORE + | UVC_CTRL_FLAG_AUTO_UPDATE, + }, + { + .entity = UVC_GUID_UVC_CAMERA, + .selector = UVC_CT_IRIS_RELATIVE_CONTROL, + .index = 8, + .size = 1, + .flags = UVC_CTRL_FLAG_SET_CUR + | UVC_CTRL_FLAG_AUTO_UPDATE, + }, + { + .entity = UVC_GUID_UVC_CAMERA, + .selector = UVC_CT_ZOOM_ABSOLUTE_CONTROL, + .index = 9, + .size = 2, + .flags = UVC_CTRL_FLAG_SET_CUR + | UVC_CTRL_FLAG_GET_RANGE + | UVC_CTRL_FLAG_RESTORE + | UVC_CTRL_FLAG_AUTO_UPDATE, + }, + { + .entity = UVC_GUID_UVC_CAMERA, + .selector = UVC_CT_ZOOM_RELATIVE_CONTROL, + .index = 10, + .size = 3, + .flags = UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_MIN + | UVC_CTRL_FLAG_GET_MAX | UVC_CTRL_FLAG_GET_RES + | UVC_CTRL_FLAG_GET_DEF + | UVC_CTRL_FLAG_AUTO_UPDATE, + }, + { + .entity = UVC_GUID_UVC_CAMERA, + .selector = UVC_CT_PANTILT_ABSOLUTE_CONTROL, + .index = 11, + .size = 8, + .flags = UVC_CTRL_FLAG_SET_CUR + | UVC_CTRL_FLAG_GET_RANGE + | UVC_CTRL_FLAG_RESTORE + | UVC_CTRL_FLAG_AUTO_UPDATE, + }, + { + .entity = UVC_GUID_UVC_CAMERA, + .selector = UVC_CT_PANTILT_RELATIVE_CONTROL, + .index = 12, + .size = 4, + .flags = UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_MIN + | UVC_CTRL_FLAG_GET_MAX | UVC_CTRL_FLAG_GET_RES + | UVC_CTRL_FLAG_GET_DEF + | UVC_CTRL_FLAG_AUTO_UPDATE, + }, + { + .entity = UVC_GUID_UVC_CAMERA, + .selector = UVC_CT_ROLL_ABSOLUTE_CONTROL, + .index = 13, + .size = 2, + .flags = UVC_CTRL_FLAG_SET_CUR + | UVC_CTRL_FLAG_GET_RANGE + | UVC_CTRL_FLAG_RESTORE + | UVC_CTRL_FLAG_AUTO_UPDATE, + }, + { + .entity = UVC_GUID_UVC_CAMERA, + .selector = UVC_CT_ROLL_RELATIVE_CONTROL, + .index = 14, + .size = 2, + .flags = UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_MIN + | UVC_CTRL_FLAG_GET_MAX | UVC_CTRL_FLAG_GET_RES + | UVC_CTRL_FLAG_GET_DEF + | UVC_CTRL_FLAG_AUTO_UPDATE, + }, + { + .entity = UVC_GUID_UVC_CAMERA, + .selector = UVC_CT_FOCUS_AUTO_CONTROL, + .index = 17, + .size = 1, + .flags = UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_CUR + | UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_RESTORE, + }, + { + .entity = UVC_GUID_UVC_CAMERA, + .selector = UVC_CT_PRIVACY_CONTROL, + .index = 18, + .size = 1, + .flags = UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_CUR + | UVC_CTRL_FLAG_RESTORE + | UVC_CTRL_FLAG_AUTO_UPDATE, + }, +}; + +static struct uvc_menu_info power_line_frequency_controls[] = { + { 0, "Disabled" }, + { 1, "50 Hz" }, + { 2, "60 Hz" }, +}; + +static struct uvc_menu_info exposure_auto_controls[] = { + { 2, "Auto Mode" }, + { 1, "Manual Mode" }, + { 4, "Shutter Priority Mode" }, + { 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 UVC_GET_CUR: + return (zoom == 0) ? 0 : (zoom > 0 ? data[2] : -data[2]); + + case UVC_GET_MIN: + case UVC_GET_MAX: + case UVC_GET_RES: + case UVC_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((int)abs(value), 0xff); +} + +static struct uvc_control_mapping uvc_ctrl_mappings[] = { + { + .id = V4L2_CID_BRIGHTNESS, + .name = "Brightness", + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_BRIGHTNESS_CONTROL, + .size = 16, + .offset = 0, + .v4l2_type = V4L2_CTRL_TYPE_INTEGER, + .data_type = UVC_CTRL_DATA_TYPE_SIGNED, + }, + { + .id = V4L2_CID_CONTRAST, + .name = "Contrast", + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_CONTRAST_CONTROL, + .size = 16, + .offset = 0, + .v4l2_type = V4L2_CTRL_TYPE_INTEGER, + .data_type = UVC_CTRL_DATA_TYPE_UNSIGNED, + }, + { + .id = V4L2_CID_HUE, + .name = "Hue", + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_HUE_CONTROL, + .size = 16, + .offset = 0, + .v4l2_type = V4L2_CTRL_TYPE_INTEGER, + .data_type = UVC_CTRL_DATA_TYPE_SIGNED, + .master_id = V4L2_CID_HUE_AUTO, + .master_manual = 0, + }, + { + .id = V4L2_CID_SATURATION, + .name = "Saturation", + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_SATURATION_CONTROL, + .size = 16, + .offset = 0, + .v4l2_type = V4L2_CTRL_TYPE_INTEGER, + .data_type = UVC_CTRL_DATA_TYPE_UNSIGNED, + }, + { + .id = V4L2_CID_SHARPNESS, + .name = "Sharpness", + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_SHARPNESS_CONTROL, + .size = 16, + .offset = 0, + .v4l2_type = V4L2_CTRL_TYPE_INTEGER, + .data_type = UVC_CTRL_DATA_TYPE_UNSIGNED, + }, + { + .id = V4L2_CID_GAMMA, + .name = "Gamma", + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_GAMMA_CONTROL, + .size = 16, + .offset = 0, + .v4l2_type = V4L2_CTRL_TYPE_INTEGER, + .data_type = UVC_CTRL_DATA_TYPE_UNSIGNED, + }, + { + .id = V4L2_CID_BACKLIGHT_COMPENSATION, + .name = "Backlight Compensation", + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_BACKLIGHT_COMPENSATION_CONTROL, + .size = 16, + .offset = 0, + .v4l2_type = V4L2_CTRL_TYPE_INTEGER, + .data_type = UVC_CTRL_DATA_TYPE_UNSIGNED, + }, + { + .id = V4L2_CID_GAIN, + .name = "Gain", + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_GAIN_CONTROL, + .size = 16, + .offset = 0, + .v4l2_type = V4L2_CTRL_TYPE_INTEGER, + .data_type = UVC_CTRL_DATA_TYPE_UNSIGNED, + }, + { + .id = V4L2_CID_POWER_LINE_FREQUENCY, + .name = "Power Line Frequency", + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_POWER_LINE_FREQUENCY_CONTROL, + .size = 2, + .offset = 0, + .v4l2_type = V4L2_CTRL_TYPE_MENU, + .data_type = UVC_CTRL_DATA_TYPE_ENUM, + .menu_info = power_line_frequency_controls, + .menu_count = ARRAY_SIZE(power_line_frequency_controls), + }, + { + .id = V4L2_CID_HUE_AUTO, + .name = "Hue, Auto", + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_HUE_AUTO_CONTROL, + .size = 1, + .offset = 0, + .v4l2_type = V4L2_CTRL_TYPE_BOOLEAN, + .data_type = UVC_CTRL_DATA_TYPE_BOOLEAN, + .slave_ids = { V4L2_CID_HUE, }, + }, + { + .id = V4L2_CID_EXPOSURE_AUTO, + .name = "Exposure, Auto", + .entity = UVC_GUID_UVC_CAMERA, + .selector = UVC_CT_AE_MODE_CONTROL, + .size = 4, + .offset = 0, + .v4l2_type = V4L2_CTRL_TYPE_MENU, + .data_type = UVC_CTRL_DATA_TYPE_BITMASK, + .menu_info = exposure_auto_controls, + .menu_count = ARRAY_SIZE(exposure_auto_controls), + .slave_ids = { V4L2_CID_EXPOSURE_ABSOLUTE, }, + }, + { + .id = V4L2_CID_EXPOSURE_AUTO_PRIORITY, + .name = "Exposure, Auto Priority", + .entity = UVC_GUID_UVC_CAMERA, + .selector = UVC_CT_AE_PRIORITY_CONTROL, + .size = 1, + .offset = 0, + .v4l2_type = V4L2_CTRL_TYPE_BOOLEAN, + .data_type = UVC_CTRL_DATA_TYPE_BOOLEAN, + }, + { + .id = V4L2_CID_EXPOSURE_ABSOLUTE, + .name = "Exposure (Absolute)", + .entity = UVC_GUID_UVC_CAMERA, + .selector = UVC_CT_EXPOSURE_TIME_ABSOLUTE_CONTROL, + .size = 32, + .offset = 0, + .v4l2_type = V4L2_CTRL_TYPE_INTEGER, + .data_type = UVC_CTRL_DATA_TYPE_UNSIGNED, + .master_id = V4L2_CID_EXPOSURE_AUTO, + .master_manual = V4L2_EXPOSURE_MANUAL, + }, + { + .id = V4L2_CID_AUTO_WHITE_BALANCE, + .name = "White Balance Temperature, Auto", + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL, + .size = 1, + .offset = 0, + .v4l2_type = V4L2_CTRL_TYPE_BOOLEAN, + .data_type = UVC_CTRL_DATA_TYPE_BOOLEAN, + .slave_ids = { V4L2_CID_WHITE_BALANCE_TEMPERATURE, }, + }, + { + .id = V4L2_CID_WHITE_BALANCE_TEMPERATURE, + .name = "White Balance Temperature", + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_WHITE_BALANCE_TEMPERATURE_CONTROL, + .size = 16, + .offset = 0, + .v4l2_type = V4L2_CTRL_TYPE_INTEGER, + .data_type = UVC_CTRL_DATA_TYPE_UNSIGNED, + .master_id = V4L2_CID_AUTO_WHITE_BALANCE, + .master_manual = 0, + }, + { + .id = V4L2_CID_AUTO_WHITE_BALANCE, + .name = "White Balance Component, Auto", + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_WHITE_BALANCE_COMPONENT_AUTO_CONTROL, + .size = 1, + .offset = 0, + .v4l2_type = V4L2_CTRL_TYPE_BOOLEAN, + .data_type = UVC_CTRL_DATA_TYPE_BOOLEAN, + .slave_ids = { V4L2_CID_BLUE_BALANCE, + V4L2_CID_RED_BALANCE }, + }, + { + .id = V4L2_CID_BLUE_BALANCE, + .name = "White Balance Blue Component", + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_WHITE_BALANCE_COMPONENT_CONTROL, + .size = 16, + .offset = 0, + .v4l2_type = V4L2_CTRL_TYPE_INTEGER, + .data_type = UVC_CTRL_DATA_TYPE_SIGNED, + .master_id = V4L2_CID_AUTO_WHITE_BALANCE, + .master_manual = 0, + }, + { + .id = V4L2_CID_RED_BALANCE, + .name = "White Balance Red Component", + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_WHITE_BALANCE_COMPONENT_CONTROL, + .size = 16, + .offset = 16, + .v4l2_type = V4L2_CTRL_TYPE_INTEGER, + .data_type = UVC_CTRL_DATA_TYPE_SIGNED, + .master_id = V4L2_CID_AUTO_WHITE_BALANCE, + .master_manual = 0, + }, + { + .id = V4L2_CID_FOCUS_ABSOLUTE, + .name = "Focus (absolute)", + .entity = UVC_GUID_UVC_CAMERA, + .selector = UVC_CT_FOCUS_ABSOLUTE_CONTROL, + .size = 16, + .offset = 0, + .v4l2_type = V4L2_CTRL_TYPE_INTEGER, + .data_type = UVC_CTRL_DATA_TYPE_UNSIGNED, + .master_id = V4L2_CID_FOCUS_AUTO, + .master_manual = 0, + }, + { + .id = V4L2_CID_FOCUS_AUTO, + .name = "Focus, Auto", + .entity = UVC_GUID_UVC_CAMERA, + .selector = UVC_CT_FOCUS_AUTO_CONTROL, + .size = 1, + .offset = 0, + .v4l2_type = V4L2_CTRL_TYPE_BOOLEAN, + .data_type = UVC_CTRL_DATA_TYPE_BOOLEAN, + .slave_ids = { V4L2_CID_FOCUS_ABSOLUTE, }, + }, + { + .id = V4L2_CID_IRIS_ABSOLUTE, + .name = "Iris, Absolute", + .entity = UVC_GUID_UVC_CAMERA, + .selector = UVC_CT_IRIS_ABSOLUTE_CONTROL, + .size = 16, + .offset = 0, + .v4l2_type = V4L2_CTRL_TYPE_INTEGER, + .data_type = UVC_CTRL_DATA_TYPE_UNSIGNED, + }, + { + .id = V4L2_CID_IRIS_RELATIVE, + .name = "Iris, Relative", + .entity = UVC_GUID_UVC_CAMERA, + .selector = UVC_CT_IRIS_RELATIVE_CONTROL, + .size = 8, + .offset = 0, + .v4l2_type = V4L2_CTRL_TYPE_INTEGER, + .data_type = UVC_CTRL_DATA_TYPE_SIGNED, + }, + { + .id = V4L2_CID_ZOOM_ABSOLUTE, + .name = "Zoom, Absolute", + .entity = UVC_GUID_UVC_CAMERA, + .selector = UVC_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 = UVC_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_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, + .selector = UVC_CT_PRIVACY_CONTROL, + .size = 1, + .offset = 0, + .v4l2_type = V4L2_CTRL_TYPE_BOOLEAN, + .data_type = UVC_CTRL_DATA_TYPE_BOOLEAN, + }, +}; + +/* ------------------------------------------------------------------------ + * Utility functions + */ + +static inline __u8 *uvc_ctrl_data(struct uvc_control *ctrl, int id) +{ + return ctrl->uvc_data + id * ctrl->info.size; +} + +static inline int uvc_test_bit(const __u8 *data, int bit) +{ + return (data[bit >> 3] >> (bit & 7)) & 1; +} + +static inline void uvc_clear_bit(__u8 *data, int bit) +{ + data[bit >> 3] &= ~(1 << (bit & 7)); +} + +/* Extract the bit string specified by mapping->offset and mapping->size + * from the little-endian data stored at 'data' and return the result as + * a signed 32bit integer. Sign extension will be performed if the mapping + * references a signed data type. + */ +static __s32 uvc_get_le_value(struct uvc_control_mapping *mapping, + __u8 query, const __u8 *data) +{ + int bits = mapping->size; + int offset = mapping->offset; + __s32 value = 0; + __u8 mask; + + data += offset / 8; + offset &= 7; + mask = ((1LL << bits) - 1) << offset; + + for (; bits > 0; data++) { + __u8 byte = *data & mask; + value |= offset > 0 ? (byte >> offset) : (byte << (-offset)); + bits -= 8 - (offset > 0 ? offset : 0); + offset -= 8; + mask = (1 << bits) - 1; + } + + /* Sign-extend the value if needed. */ + if (mapping->data_type == UVC_CTRL_DATA_TYPE_SIGNED) + value |= -(value & (1 << (mapping->size - 1))); + + return value; +} + +/* 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(struct uvc_control_mapping *mapping, + __s32 value, __u8 *data) +{ + int bits = mapping->size; + 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; + + for (; bits > 0; data++) { + mask = ((1LL << bits) - 1) << offset; + *data = (*data & ~mask) | ((value << offset) & mask); + value >>= offset ? offset : 8; + bits -= 8 - offset; + offset = 0; + } +} + +/* ------------------------------------------------------------------------ + * Terminal and unit management + */ + +static const __u8 uvc_processing_guid[16] = UVC_GUID_UVC_PROCESSING; +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(const struct uvc_entity *entity, + const __u8 guid[16]) +{ + switch (UVC_ENTITY_TYPE(entity)) { + case UVC_ITT_CAMERA: + return memcmp(uvc_camera_guid, guid, 16) == 0; + + case UVC_ITT_MEDIA_TRANSPORT_INPUT: + return memcmp(uvc_media_transport_input_guid, guid, 16) == 0; + + case UVC_VC_PROCESSING_UNIT: + return memcmp(uvc_processing_guid, guid, 16) == 0; + + case UVC_VC_EXTENSION_UNIT: + return memcmp(entity->extension.guidExtensionCode, + guid, 16) == 0; + + default: + return 0; + } +} + +/* ------------------------------------------------------------------------ + * UVC Controls + */ + +static void __uvc_find_control(struct uvc_entity *entity, __u32 v4l2_id, + struct uvc_control_mapping **mapping, struct uvc_control **control, + int next) +{ + struct uvc_control *ctrl; + struct uvc_control_mapping *map; + unsigned int i; + + if (entity == NULL) + return; + + for (i = 0; i < entity->ncontrols; ++i) { + ctrl = &entity->controls[i]; + if (!ctrl->initialized) + continue; + + list_for_each_entry(map, &ctrl->info.mappings, list) { + if ((map->id == v4l2_id) && !next) { + *control = ctrl; + *mapping = map; + return; + } + + if ((*mapping == NULL || (*mapping)->id > map->id) && + (map->id > v4l2_id) && next) { + *control = ctrl; + *mapping = map; + } + } + } +} + +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; + struct uvc_entity *entity; + int next = v4l2_id & V4L2_CTRL_FLAG_NEXT_CTRL; + + *mapping = NULL; + + /* Mask the query flags. */ + v4l2_id &= V4L2_CTRL_ID_MASK; + + /* Find the control. */ + list_for_each_entry(entity, &chain->entities, chain) { + __uvc_find_control(entity, v4l2_id, mapping, &ctrl, next); + if (ctrl && !next) + return ctrl; + } + + if (ctrl == NULL && !next) + uvc_trace(UVC_TRACE_CONTROL, "Control 0x%08x not found.\n", + v4l2_id); + + return ctrl; +} + +static int uvc_ctrl_populate_cache(struct uvc_video_chain *chain, + struct uvc_control *ctrl) +{ + int ret; + + if (ctrl->info.flags & UVC_CTRL_FLAG_GET_DEF) { + ret = uvc_query_ctrl(chain->dev, UVC_GET_DEF, ctrl->entity->id, + chain->dev->intfnum, ctrl->info.selector, + uvc_ctrl_data(ctrl, UVC_CTRL_DATA_DEF), + ctrl->info.size); + if (ret < 0) + return ret; + } + + if (ctrl->info.flags & UVC_CTRL_FLAG_GET_MIN) { + ret = uvc_query_ctrl(chain->dev, UVC_GET_MIN, ctrl->entity->id, + chain->dev->intfnum, ctrl->info.selector, + uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MIN), + ctrl->info.size); + if (ret < 0) + return ret; + } + if (ctrl->info.flags & UVC_CTRL_FLAG_GET_MAX) { + ret = uvc_query_ctrl(chain->dev, UVC_GET_MAX, ctrl->entity->id, + chain->dev->intfnum, ctrl->info.selector, + uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MAX), + ctrl->info.size); + if (ret < 0) + return ret; + } + if (ctrl->info.flags & UVC_CTRL_FLAG_GET_RES) { + ret = uvc_query_ctrl(chain->dev, UVC_GET_RES, ctrl->entity->id, + chain->dev->intfnum, ctrl->info.selector, + uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES), + ctrl->info.size); + if (ret < 0) { + if (UVC_ENTITY_TYPE(ctrl->entity) != + UVC_VC_EXTENSION_UNIT) + return ret; + + /* GET_RES is mandatory for XU controls, but some + * cameras still choke on it. Ignore errors and set the + * resolution value to zero. + */ + uvc_warn_once(chain->dev, UVC_WARN_XU_GET_RES, + "UVC non compliance - GET_RES failed on " + "an XU control. Enabling workaround.\n"); + memset(uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES), 0, + ctrl->info.size); + } + } + + ctrl->cached = 1; + return 0; +} + +static int __uvc_ctrl_get(struct uvc_video_chain *chain, + struct uvc_control *ctrl, struct uvc_control_mapping *mapping, + s32 *value) +{ + struct uvc_menu_info *menu; + unsigned int i; + int ret; + + if ((ctrl->info.flags & UVC_CTRL_FLAG_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, + uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT), + ctrl->info.size); + if (ret < 0) + return ret; + + ctrl->loaded = 1; + } + + *value = mapping->get(mapping, UVC_GET_CUR, + uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT)); + + if (mapping->v4l2_type == V4L2_CTRL_TYPE_MENU) { + menu = mapping->menu_info; + for (i = 0; i < mapping->menu_count; ++i, ++menu) { + if (menu->value == *value) { + *value = i; + break; + } + } + } + + return 0; +} + +static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain, + struct uvc_control *ctrl, + struct uvc_control_mapping *mapping, + struct v4l2_queryctrl *v4l2_ctrl) +{ + struct uvc_control_mapping *master_map = NULL; + struct uvc_control *master_ctrl = NULL; + struct uvc_menu_info *menu; + unsigned int i; + + memset(v4l2_ctrl, 0, sizeof *v4l2_ctrl); + v4l2_ctrl->id = mapping->id; + v4l2_ctrl->type = mapping->v4l2_type; + strlcpy(v4l2_ctrl->name, mapping->name, sizeof v4l2_ctrl->name); + v4l2_ctrl->flags = 0; + + if (!(ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR)) + v4l2_ctrl->flags |= V4L2_CTRL_FLAG_WRITE_ONLY; + if (!(ctrl->info.flags & UVC_CTRL_FLAG_SET_CUR)) + v4l2_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + if (mapping->master_id) + __uvc_find_control(ctrl->entity, mapping->master_id, + &master_map, &master_ctrl, 0); + if (master_ctrl && (master_ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR)) { + s32 val; + int ret = __uvc_ctrl_get(chain, master_ctrl, master_map, &val); + if (ret < 0) + return ret; + + if (val != mapping->master_manual) + v4l2_ctrl->flags |= V4L2_CTRL_FLAG_INACTIVE; + } + + if (!ctrl->cached) { + int ret = uvc_ctrl_populate_cache(chain, ctrl); + if (ret < 0) + return ret; + } + + if (ctrl->info.flags & UVC_CTRL_FLAG_GET_DEF) { + v4l2_ctrl->default_value = mapping->get(mapping, UVC_GET_DEF, + uvc_ctrl_data(ctrl, UVC_CTRL_DATA_DEF)); + } + + switch (mapping->v4l2_type) { + case V4L2_CTRL_TYPE_MENU: + v4l2_ctrl->minimum = 0; + v4l2_ctrl->maximum = mapping->menu_count - 1; + v4l2_ctrl->step = 1; + + menu = mapping->menu_info; + for (i = 0; i < mapping->menu_count; ++i, ++menu) { + if (menu->value == v4l2_ctrl->default_value) { + v4l2_ctrl->default_value = i; + break; + } + } + + return 0; + + case V4L2_CTRL_TYPE_BOOLEAN: + v4l2_ctrl->minimum = 0; + v4l2_ctrl->maxi |