mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-05-06 00:47:56 -04:00
Merge tag 'tags/media-next-uvc-20240617-2' of git://git.kernel.org/pub/scm/linux/kernel/git/pinchartl/linux.git into media_stage
uvcvideo fixes and improvements: - add/fix quirks - improve timestamp handling - improve Power Line Frequency handling Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
This commit is contained in:
@@ -459,6 +459,94 @@ static void uvc_ctrl_set_rel_speed(struct uvc_control_mapping *mapping,
|
||||
data[first+1] = min_t(int, abs(value), 0xff);
|
||||
}
|
||||
|
||||
static const struct uvc_control_mapping uvc_ctrl_power_line_mapping_limited = {
|
||||
.id = V4L2_CID_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_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_60HZ,
|
||||
V4L2_CID_POWER_LINE_FREQUENCY_50HZ),
|
||||
};
|
||||
|
||||
static const struct uvc_control_mapping uvc_ctrl_power_line_mapping_uvc11 = {
|
||||
.id = V4L2_CID_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_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_60HZ,
|
||||
V4L2_CID_POWER_LINE_FREQUENCY_DISABLED),
|
||||
};
|
||||
|
||||
static const struct uvc_control_mapping uvc_ctrl_power_line_mapping_uvc15 = {
|
||||
.id = V4L2_CID_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_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_AUTO,
|
||||
V4L2_CID_POWER_LINE_FREQUENCY_DISABLED),
|
||||
};
|
||||
|
||||
static const struct uvc_control_mapping *uvc_ctrl_filter_plf_mapping(
|
||||
struct uvc_video_chain *chain, struct uvc_control *ctrl)
|
||||
{
|
||||
const struct uvc_control_mapping *out_mapping =
|
||||
&uvc_ctrl_power_line_mapping_uvc11;
|
||||
u8 *buf __free(kfree) = NULL;
|
||||
u8 init_val;
|
||||
int ret;
|
||||
|
||||
buf = kmalloc(sizeof(*buf), GFP_KERNEL);
|
||||
if (!buf)
|
||||
return NULL;
|
||||
|
||||
/* Save the current PLF value, so we can restore it. */
|
||||
ret = uvc_query_ctrl(chain->dev, UVC_GET_CUR, ctrl->entity->id,
|
||||
chain->dev->intfnum, ctrl->info.selector,
|
||||
buf, sizeof(*buf));
|
||||
/* If we cannot read the control skip it. */
|
||||
if (ret)
|
||||
return NULL;
|
||||
init_val = *buf;
|
||||
|
||||
/* If PLF value cannot be set to off, it is limited. */
|
||||
*buf = V4L2_CID_POWER_LINE_FREQUENCY_DISABLED;
|
||||
ret = uvc_query_ctrl(chain->dev, UVC_SET_CUR, ctrl->entity->id,
|
||||
chain->dev->intfnum, ctrl->info.selector,
|
||||
buf, sizeof(*buf));
|
||||
if (ret)
|
||||
return &uvc_ctrl_power_line_mapping_limited;
|
||||
|
||||
/* UVC 1.1 does not define auto, we can exit. */
|
||||
if (chain->dev->uvc_version < 0x150)
|
||||
goto end;
|
||||
|
||||
/* Check if the device supports auto. */
|
||||
*buf = V4L2_CID_POWER_LINE_FREQUENCY_AUTO;
|
||||
ret = uvc_query_ctrl(chain->dev, UVC_SET_CUR, ctrl->entity->id,
|
||||
chain->dev->intfnum, ctrl->info.selector,
|
||||
buf, sizeof(*buf));
|
||||
if (!ret)
|
||||
out_mapping = &uvc_ctrl_power_line_mapping_uvc15;
|
||||
|
||||
end:
|
||||
/* Restore initial value and add mapping. */
|
||||
*buf = init_val;
|
||||
uvc_query_ctrl(chain->dev, UVC_SET_CUR, ctrl->entity->id,
|
||||
chain->dev->intfnum, ctrl->info.selector,
|
||||
buf, sizeof(*buf));
|
||||
|
||||
return out_mapping;
|
||||
}
|
||||
|
||||
static const struct uvc_control_mapping uvc_ctrl_mappings[] = {
|
||||
{
|
||||
.id = V4L2_CID_BRIGHTNESS,
|
||||
@@ -748,52 +836,11 @@ static const struct uvc_control_mapping uvc_ctrl_mappings[] = {
|
||||
.v4l2_type = V4L2_CTRL_TYPE_BOOLEAN,
|
||||
.data_type = UVC_CTRL_DATA_TYPE_BOOLEAN,
|
||||
},
|
||||
};
|
||||
|
||||
const struct uvc_control_mapping uvc_ctrl_power_line_mapping_limited = {
|
||||
.id = V4L2_CID_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_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_60HZ,
|
||||
V4L2_CID_POWER_LINE_FREQUENCY_50HZ),
|
||||
};
|
||||
|
||||
const struct uvc_control_mapping uvc_ctrl_power_line_mapping_uvc11 = {
|
||||
.id = V4L2_CID_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_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_60HZ,
|
||||
V4L2_CID_POWER_LINE_FREQUENCY_DISABLED),
|
||||
};
|
||||
|
||||
static const struct uvc_control_mapping *uvc_ctrl_mappings_uvc11[] = {
|
||||
&uvc_ctrl_power_line_mapping_uvc11,
|
||||
NULL, /* Sentinel */
|
||||
};
|
||||
|
||||
static const struct uvc_control_mapping uvc_ctrl_power_line_mapping_uvc15 = {
|
||||
.id = V4L2_CID_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_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_AUTO,
|
||||
V4L2_CID_POWER_LINE_FREQUENCY_DISABLED),
|
||||
};
|
||||
|
||||
static const struct uvc_control_mapping *uvc_ctrl_mappings_uvc15[] = {
|
||||
&uvc_ctrl_power_line_mapping_uvc15,
|
||||
NULL, /* Sentinel */
|
||||
{
|
||||
.entity = UVC_GUID_UVC_PROCESSING,
|
||||
.selector = UVC_PU_POWER_LINE_FREQUENCY_CONTROL,
|
||||
.filter_mapping = uvc_ctrl_filter_plf_mapping,
|
||||
},
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
@@ -2031,7 +2078,13 @@ static int uvc_ctrl_get_flags(struct uvc_device *dev,
|
||||
else
|
||||
ret = uvc_query_ctrl(dev, UVC_GET_INFO, ctrl->entity->id,
|
||||
dev->intfnum, info->selector, data, 1);
|
||||
if (!ret)
|
||||
|
||||
if (!ret) {
|
||||
info->flags &= ~(UVC_CTRL_FLAG_GET_CUR |
|
||||
UVC_CTRL_FLAG_SET_CUR |
|
||||
UVC_CTRL_FLAG_AUTO_UPDATE |
|
||||
UVC_CTRL_FLAG_ASYNCHRONOUS);
|
||||
|
||||
info->flags |= (data[0] & UVC_CONTROL_CAP_GET ?
|
||||
UVC_CTRL_FLAG_GET_CUR : 0)
|
||||
| (data[0] & UVC_CONTROL_CAP_SET ?
|
||||
@@ -2040,6 +2093,7 @@ static int uvc_ctrl_get_flags(struct uvc_device *dev,
|
||||
UVC_CTRL_FLAG_AUTO_UPDATE : 0)
|
||||
| (data[0] & UVC_CONTROL_CAP_ASYNCHRONOUS ?
|
||||
UVC_CTRL_FLAG_ASYNCHRONOUS : 0);
|
||||
}
|
||||
|
||||
kfree(data);
|
||||
return ret;
|
||||
@@ -2591,7 +2645,6 @@ static void uvc_ctrl_prune_entity(struct uvc_device *dev,
|
||||
static void uvc_ctrl_init_ctrl(struct uvc_video_chain *chain,
|
||||
struct uvc_control *ctrl)
|
||||
{
|
||||
const struct uvc_control_mapping **mappings;
|
||||
unsigned int i;
|
||||
|
||||
/*
|
||||
@@ -2623,46 +2676,16 @@ static void uvc_ctrl_init_ctrl(struct uvc_video_chain *chain,
|
||||
if (!ctrl->initialized)
|
||||
return;
|
||||
|
||||
/*
|
||||
* First check if the device provides a custom mapping for this control,
|
||||
* used to override standard mappings for non-conformant devices. Don't
|
||||
* process standard mappings if a custom mapping is found. This
|
||||
* mechanism doesn't support combining standard and custom mappings for
|
||||
* a single control.
|
||||
*/
|
||||
if (chain->dev->info->mappings) {
|
||||
bool custom = false;
|
||||
|
||||
for (i = 0; chain->dev->info->mappings[i]; ++i) {
|
||||
const struct uvc_control_mapping *mapping =
|
||||
chain->dev->info->mappings[i];
|
||||
|
||||
if (uvc_entity_match_guid(ctrl->entity, mapping->entity) &&
|
||||
ctrl->info.selector == mapping->selector) {
|
||||
__uvc_ctrl_add_mapping(chain, ctrl, mapping);
|
||||
custom = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (custom)
|
||||
return;
|
||||
}
|
||||
|
||||
/* Process common mappings next. */
|
||||
/* Process common mappings. */
|
||||
for (i = 0; i < ARRAY_SIZE(uvc_ctrl_mappings); ++i) {
|
||||
const struct uvc_control_mapping *mapping = &uvc_ctrl_mappings[i];
|
||||
|
||||
if (uvc_entity_match_guid(ctrl->entity, mapping->entity) &&
|
||||
ctrl->info.selector == mapping->selector)
|
||||
__uvc_ctrl_add_mapping(chain, ctrl, mapping);
|
||||
}
|
||||
|
||||
/* Finally process version-specific mappings. */
|
||||
mappings = chain->dev->uvc_version < 0x0150
|
||||
? uvc_ctrl_mappings_uvc11 : uvc_ctrl_mappings_uvc15;
|
||||
|
||||
for (i = 0; mappings[i]; ++i) {
|
||||
const struct uvc_control_mapping *mapping = mappings[i];
|
||||
/* Let the device provide a custom mapping. */
|
||||
if (mapping->filter_mapping) {
|
||||
mapping = mapping->filter_mapping(chain, ctrl);
|
||||
if (!mapping)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (uvc_entity_match_guid(ctrl->entity, mapping->entity) &&
|
||||
ctrl->info.selector == mapping->selector)
|
||||
|
||||
@@ -687,16 +687,26 @@ static int uvc_parse_streaming(struct uvc_device *dev,
|
||||
goto error;
|
||||
}
|
||||
|
||||
size = nformats * sizeof(*format) + nframes * sizeof(*frame)
|
||||
/*
|
||||
* Allocate memory for the formats, the frames and the intervals,
|
||||
* plus any required padding to guarantee that everything has the
|
||||
* correct alignment.
|
||||
*/
|
||||
size = nformats * sizeof(*format);
|
||||
size = ALIGN(size, __alignof__(*frame)) + nframes * sizeof(*frame);
|
||||
size = ALIGN(size, __alignof__(*interval))
|
||||
+ nintervals * sizeof(*interval);
|
||||
|
||||
format = kzalloc(size, GFP_KERNEL);
|
||||
if (format == NULL) {
|
||||
if (!format) {
|
||||
ret = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
frame = (struct uvc_frame *)&format[nformats];
|
||||
interval = (u32 *)&frame[nframes];
|
||||
frame = (void *)format + nformats * sizeof(*format);
|
||||
frame = PTR_ALIGN(frame, __alignof__(*frame));
|
||||
interval = (void *)frame + nframes * sizeof(*frame);
|
||||
interval = PTR_ALIGN(interval, __alignof__(*interval));
|
||||
|
||||
streaming->formats = format;
|
||||
streaming->nformats = 0;
|
||||
@@ -2390,20 +2400,6 @@ MODULE_PARM_DESC(timeout, "Streaming control requests timeout");
|
||||
* Driver initialization and cleanup
|
||||
*/
|
||||
|
||||
static const struct uvc_device_info uvc_ctrl_power_line_limited = {
|
||||
.mappings = (const struct uvc_control_mapping *[]) {
|
||||
&uvc_ctrl_power_line_mapping_limited,
|
||||
NULL, /* Sentinel */
|
||||
},
|
||||
};
|
||||
|
||||
static const struct uvc_device_info uvc_ctrl_power_line_uvc11 = {
|
||||
.mappings = (const struct uvc_control_mapping *[]) {
|
||||
&uvc_ctrl_power_line_mapping_uvc11,
|
||||
NULL, /* Sentinel */
|
||||
},
|
||||
};
|
||||
|
||||
static const struct uvc_device_info uvc_quirk_probe_minmax = {
|
||||
.quirks = UVC_QUIRK_PROBE_MINMAX,
|
||||
};
|
||||
@@ -2434,33 +2430,17 @@ static const struct uvc_device_info uvc_quirk_force_y8 = {
|
||||
* though they are compliant.
|
||||
*/
|
||||
static const struct usb_device_id uvc_ids[] = {
|
||||
/* Quanta USB2.0 HD UVC Webcam */
|
||||
/* Quanta ACER HD User Facing */
|
||||
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
|
||||
| USB_DEVICE_ID_MATCH_INT_INFO,
|
||||
.idVendor = 0x0408,
|
||||
.idProduct = 0x3090,
|
||||
.bInterfaceClass = USB_CLASS_VIDEO,
|
||||
.bInterfaceSubClass = 1,
|
||||
.bInterfaceProtocol = 0,
|
||||
.driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_limited },
|
||||
/* Quanta USB2.0 HD UVC Webcam */
|
||||
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
|
||||
| USB_DEVICE_ID_MATCH_INT_INFO,
|
||||
.idVendor = 0x0408,
|
||||
.idProduct = 0x4030,
|
||||
.bInterfaceClass = USB_CLASS_VIDEO,
|
||||
.bInterfaceSubClass = 1,
|
||||
.bInterfaceProtocol = 0,
|
||||
.driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_limited },
|
||||
/* Quanta USB2.0 HD UVC Webcam */
|
||||
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
|
||||
| USB_DEVICE_ID_MATCH_INT_INFO,
|
||||
.idVendor = 0x0408,
|
||||
.idProduct = 0x4034,
|
||||
.idProduct = 0x4035,
|
||||
.bInterfaceClass = USB_CLASS_VIDEO,
|
||||
.bInterfaceSubClass = 1,
|
||||
.bInterfaceProtocol = UVC_PC_PROTOCOL_15,
|
||||
.driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_limited },
|
||||
.driver_info = (kernel_ulong_t)&(const struct uvc_device_info){
|
||||
.uvc_version = 0x010a,
|
||||
} },
|
||||
/* LogiLink Wireless Webcam */
|
||||
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
|
||||
| USB_DEVICE_ID_MATCH_INT_INFO,
|
||||
@@ -2580,7 +2560,17 @@ static const struct usb_device_id uvc_ids[] = {
|
||||
.bInterfaceClass = USB_CLASS_VIDEO,
|
||||
.bInterfaceSubClass = 1,
|
||||
.bInterfaceProtocol = 0,
|
||||
.driver_info = UVC_INFO_QUIRK(UVC_QUIRK_RESTORE_CTRLS_ON_INIT) },
|
||||
.driver_info = UVC_INFO_QUIRK(UVC_QUIRK_RESTORE_CTRLS_ON_INIT
|
||||
| UVC_QUIRK_INVALID_DEVICE_SOF) },
|
||||
/* Logitech HD Pro Webcam C922 */
|
||||
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
|
||||
| USB_DEVICE_ID_MATCH_INT_INFO,
|
||||
.idVendor = 0x046d,
|
||||
.idProduct = 0x085c,
|
||||
.bInterfaceClass = USB_CLASS_VIDEO,
|
||||
.bInterfaceSubClass = 1,
|
||||
.bInterfaceProtocol = 0,
|
||||
.driver_info = UVC_INFO_QUIRK(UVC_QUIRK_INVALID_DEVICE_SOF) },
|
||||
/* Logitech Rally Bar Huddle */
|
||||
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
|
||||
| USB_DEVICE_ID_MATCH_INT_INFO,
|
||||
@@ -2617,42 +2607,6 @@ static const struct usb_device_id uvc_ids[] = {
|
||||
.bInterfaceSubClass = 1,
|
||||
.bInterfaceProtocol = 0,
|
||||
.driver_info = UVC_INFO_QUIRK(UVC_QUIRK_RESTRICT_FRAME_RATE) },
|
||||
/* Chicony EasyCamera */
|
||||
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
|
||||
| USB_DEVICE_ID_MATCH_INT_INFO,
|
||||
.idVendor = 0x04f2,
|
||||
.idProduct = 0xb5eb,
|
||||
.bInterfaceClass = USB_CLASS_VIDEO,
|
||||
.bInterfaceSubClass = 1,
|
||||
.bInterfaceProtocol = 0,
|
||||
.driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_limited },
|
||||
/* Chicony Electronics Co., Ltd Integrated Camera */
|
||||
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
|
||||
| USB_DEVICE_ID_MATCH_INT_INFO,
|
||||
.idVendor = 0x04f2,
|
||||
.idProduct = 0xb67c,
|
||||
.bInterfaceClass = USB_CLASS_VIDEO,
|
||||
.bInterfaceSubClass = 1,
|
||||
.bInterfaceProtocol = UVC_PC_PROTOCOL_15,
|
||||
.driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_uvc11 },
|
||||
/* Chicony EasyCamera */
|
||||
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
|
||||
| USB_DEVICE_ID_MATCH_INT_INFO,
|
||||
.idVendor = 0x04f2,
|
||||
.idProduct = 0xb6ba,
|
||||
.bInterfaceClass = USB_CLASS_VIDEO,
|
||||
.bInterfaceSubClass = 1,
|
||||
.bInterfaceProtocol = 0,
|
||||
.driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_limited },
|
||||
/* Chicony EasyCamera */
|
||||
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
|
||||
| USB_DEVICE_ID_MATCH_INT_INFO,
|
||||
.idVendor = 0x04f2,
|
||||
.idProduct = 0xb746,
|
||||
.bInterfaceClass = USB_CLASS_VIDEO,
|
||||
.bInterfaceSubClass = 1,
|
||||
.bInterfaceProtocol = 0,
|
||||
.driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_limited },
|
||||
/* Alcor Micro AU3820 (Future Boy PC USB Webcam) */
|
||||
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
|
||||
| USB_DEVICE_ID_MATCH_INT_INFO,
|
||||
@@ -3037,15 +2991,6 @@ static const struct usb_device_id uvc_ids[] = {
|
||||
.bInterfaceSubClass = 1,
|
||||
.bInterfaceProtocol = 0,
|
||||
.driver_info = UVC_INFO_QUIRK(UVC_QUIRK_FORCE_BPP) },
|
||||
/* SunplusIT Inc HD Camera */
|
||||
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
|
||||
| USB_DEVICE_ID_MATCH_INT_INFO,
|
||||
.idVendor = 0x2b7e,
|
||||
.idProduct = 0xb752,
|
||||
.bInterfaceClass = USB_CLASS_VIDEO,
|
||||
.bInterfaceSubClass = 1,
|
||||
.bInterfaceProtocol = UVC_PC_PROTOCOL_15,
|
||||
.driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_uvc11 },
|
||||
/* Insta360 Link */
|
||||
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
|
||||
| USB_DEVICE_ID_MATCH_INT_INFO,
|
||||
@@ -3055,51 +3000,6 @@ static const struct usb_device_id uvc_ids[] = {
|
||||
.bInterfaceSubClass = 1,
|
||||
.bInterfaceProtocol = 0,
|
||||
.driver_info = UVC_INFO_QUIRK(UVC_QUIRK_DISABLE_AUTOSUSPEND) },
|
||||
/* Lenovo Integrated Camera */
|
||||
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
|
||||
| USB_DEVICE_ID_MATCH_INT_INFO,
|
||||
.idVendor = 0x30c9,
|
||||
.idProduct = 0x0093,
|
||||
.bInterfaceClass = USB_CLASS_VIDEO,
|
||||
.bInterfaceSubClass = 1,
|
||||
.bInterfaceProtocol = UVC_PC_PROTOCOL_15,
|
||||
.driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_uvc11 },
|
||||
/* Sonix Technology USB 2.0 Camera */
|
||||
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
|
||||
| USB_DEVICE_ID_MATCH_INT_INFO,
|
||||
.idVendor = 0x3277,
|
||||
.idProduct = 0x0072,
|
||||
.bInterfaceClass = USB_CLASS_VIDEO,
|
||||
.bInterfaceSubClass = 1,
|
||||
.bInterfaceProtocol = 0,
|
||||
.driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_limited },
|
||||
/* Shine-Optics Integrated Camera */
|
||||
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
|
||||
| USB_DEVICE_ID_MATCH_INT_INFO,
|
||||
.idVendor = 0x3277,
|
||||
.idProduct = 0x009e,
|
||||
.bInterfaceClass = USB_CLASS_VIDEO,
|
||||
.bInterfaceSubClass = 1,
|
||||
.bInterfaceProtocol = UVC_PC_PROTOCOL_15,
|
||||
.driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_uvc11 },
|
||||
/* Acer EasyCamera */
|
||||
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
|
||||
| USB_DEVICE_ID_MATCH_INT_INFO,
|
||||
.idVendor = 0x5986,
|
||||
.idProduct = 0x1172,
|
||||
.bInterfaceClass = USB_CLASS_VIDEO,
|
||||
.bInterfaceSubClass = 1,
|
||||
.bInterfaceProtocol = 0,
|
||||
.driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_limited },
|
||||
/* Acer EasyCamera */
|
||||
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
|
||||
| USB_DEVICE_ID_MATCH_INT_INFO,
|
||||
.idVendor = 0x5986,
|
||||
.idProduct = 0x1180,
|
||||
.bInterfaceClass = USB_CLASS_VIDEO,
|
||||
.bInterfaceSubClass = 1,
|
||||
.bInterfaceProtocol = 0,
|
||||
.driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_limited },
|
||||
/* Intel D410/ASR depth camera */
|
||||
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
|
||||
| USB_DEVICE_ID_MATCH_INT_INFO,
|
||||
|
||||
@@ -214,13 +214,13 @@ static void uvc_fixup_video_ctrl(struct uvc_streaming *stream,
|
||||
* Compute a bandwidth estimation by multiplying the frame
|
||||
* size by the number of video frames per second, divide the
|
||||
* result by the number of USB frames (or micro-frames for
|
||||
* high-speed devices) per second and add the UVC header size
|
||||
* (assumed to be 12 bytes long).
|
||||
* high- and super-speed devices) per second and add the UVC
|
||||
* header size (assumed to be 12 bytes long).
|
||||
*/
|
||||
bandwidth = frame->wWidth * frame->wHeight / 8 * format->bpp;
|
||||
bandwidth *= 10000000 / interval + 1;
|
||||
bandwidth /= 1000;
|
||||
if (stream->dev->udev->speed == USB_SPEED_HIGH)
|
||||
if (stream->dev->udev->speed >= USB_SPEED_HIGH)
|
||||
bandwidth /= 8;
|
||||
bandwidth += 12;
|
||||
|
||||
@@ -466,18 +466,49 @@ static inline ktime_t uvc_video_get_time(void)
|
||||
return ktime_get_real();
|
||||
}
|
||||
|
||||
static void uvc_video_clock_add_sample(struct uvc_clock *clock,
|
||||
const struct uvc_clock_sample *sample)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
/*
|
||||
* If we write new data on the position where we had the last
|
||||
* overflow, remove the overflow pointer. There is no SOF overflow
|
||||
* in the whole circular buffer.
|
||||
*/
|
||||
if (clock->head == clock->last_sof_overflow)
|
||||
clock->last_sof_overflow = -1;
|
||||
|
||||
spin_lock_irqsave(&clock->lock, flags);
|
||||
|
||||
if (clock->count > 0 && clock->last_sof > sample->dev_sof) {
|
||||
/*
|
||||
* Remove data from the circular buffer that is older than the
|
||||
* last SOF overflow. We only support one SOF overflow per
|
||||
* circular buffer.
|
||||
*/
|
||||
if (clock->last_sof_overflow != -1)
|
||||
clock->count = (clock->head - clock->last_sof_overflow
|
||||
+ clock->size) % clock->size;
|
||||
clock->last_sof_overflow = clock->head;
|
||||
}
|
||||
|
||||
/* Add sample. */
|
||||
clock->samples[clock->head] = *sample;
|
||||
clock->head = (clock->head + 1) % clock->size;
|
||||
clock->count = min(clock->count + 1, clock->size);
|
||||
|
||||
spin_unlock_irqrestore(&clock->lock, flags);
|
||||
}
|
||||
|
||||
static void
|
||||
uvc_video_clock_decode(struct uvc_streaming *stream, struct uvc_buffer *buf,
|
||||
const u8 *data, int len)
|
||||
{
|
||||
struct uvc_clock_sample *sample;
|
||||
struct uvc_clock_sample sample;
|
||||
unsigned int header_size;
|
||||
bool has_pts = false;
|
||||
bool has_scr = false;
|
||||
unsigned long flags;
|
||||
ktime_t time;
|
||||
u16 host_sof;
|
||||
u16 dev_sof;
|
||||
|
||||
switch (data[1] & (UVC_STREAM_PTS | UVC_STREAM_SCR)) {
|
||||
case UVC_STREAM_PTS | UVC_STREAM_SCR:
|
||||
@@ -522,14 +553,51 @@ uvc_video_clock_decode(struct uvc_streaming *stream, struct uvc_buffer *buf,
|
||||
* all the data packets of the same frame contains the same SOF. In that
|
||||
* case only the first one will match the host_sof.
|
||||
*/
|
||||
dev_sof = get_unaligned_le16(&data[header_size - 2]);
|
||||
if (dev_sof == stream->clock.last_sof)
|
||||
sample.dev_sof = get_unaligned_le16(&data[header_size - 2]);
|
||||
if (sample.dev_sof == stream->clock.last_sof)
|
||||
return;
|
||||
|
||||
stream->clock.last_sof = dev_sof;
|
||||
sample.dev_stc = get_unaligned_le32(&data[header_size - 6]);
|
||||
|
||||
host_sof = usb_get_current_frame_number(stream->dev->udev);
|
||||
time = uvc_video_get_time();
|
||||
/*
|
||||
* STC (Source Time Clock) is the clock used by the camera. The UVC 1.5
|
||||
* standard states that it "must be captured when the first video data
|
||||
* of a video frame is put on the USB bus". This is generally understood
|
||||
* as requiring devices to clear the payload header's SCR bit before
|
||||
* the first packet containing video data.
|
||||
*
|
||||
* Most vendors follow that interpretation, but some (namely SunplusIT
|
||||
* on some devices) always set the `UVC_STREAM_SCR` bit, fill the SCR
|
||||
* field with 0's,and expect that the driver only processes the SCR if
|
||||
* there is data in the packet.
|
||||
*
|
||||
* Ignore all the hardware timestamp information if we haven't received
|
||||
* any data for this frame yet, the packet contains no data, and both
|
||||
* STC and SOF are zero. This heuristics should be safe on compliant
|
||||
* devices. This should be safe with compliant devices, as in the very
|
||||
* unlikely case where a UVC 1.1 device would send timing information
|
||||
* only before the first packet containing data, and both STC and SOF
|
||||
* happen to be zero for a particular frame, we would only miss one
|
||||
* clock sample from many and the clock recovery algorithm wouldn't
|
||||
* suffer from this condition.
|
||||
*/
|
||||
if (buf && buf->bytesused == 0 && len == header_size &&
|
||||
sample.dev_stc == 0 && sample.dev_sof == 0)
|
||||
return;
|
||||
|
||||
sample.host_sof = usb_get_current_frame_number(stream->dev->udev);
|
||||
|
||||
/*
|
||||
* On some devices, like the Logitech C922, the device SOF does not run
|
||||
* at a stable rate of 1kHz. For those devices use the host SOF instead.
|
||||
* In the tests performed so far, this improves the timestamp precision.
|
||||
* This is probably explained by a small packet handling jitter from the
|
||||
* host, but the exact reason hasn't been fully determined.
|
||||
*/
|
||||
if (stream->dev->quirks & UVC_QUIRK_INVALID_DEVICE_SOF)
|
||||
sample.dev_sof = sample.host_sof;
|
||||
|
||||
sample.host_time = uvc_video_get_time();
|
||||
|
||||
/*
|
||||
* The UVC specification allows device implementations that can't obtain
|
||||
@@ -552,46 +620,29 @@ uvc_video_clock_decode(struct uvc_streaming *stream, struct uvc_buffer *buf,
|
||||
* the 8 LSBs of the delta are kept.
|
||||
*/
|
||||
if (stream->clock.sof_offset == (u16)-1) {
|
||||
u16 delta_sof = (host_sof - dev_sof) & 255;
|
||||
u16 delta_sof = (sample.host_sof - sample.dev_sof) & 255;
|
||||
if (delta_sof >= 10)
|
||||
stream->clock.sof_offset = delta_sof;
|
||||
else
|
||||
stream->clock.sof_offset = 0;
|
||||
}
|
||||
|
||||
dev_sof = (dev_sof + stream->clock.sof_offset) & 2047;
|
||||
|
||||
spin_lock_irqsave(&stream->clock.lock, flags);
|
||||
|
||||
sample = &stream->clock.samples[stream->clock.head];
|
||||
sample->dev_stc = get_unaligned_le32(&data[header_size - 6]);
|
||||
sample->dev_sof = dev_sof;
|
||||
sample->host_sof = host_sof;
|
||||
sample->host_time = time;
|
||||
|
||||
/* Update the sliding window head and count. */
|
||||
stream->clock.head = (stream->clock.head + 1) % stream->clock.size;
|
||||
|
||||
if (stream->clock.count < stream->clock.size)
|
||||
stream->clock.count++;
|
||||
|
||||
spin_unlock_irqrestore(&stream->clock.lock, flags);
|
||||
sample.dev_sof = (sample.dev_sof + stream->clock.sof_offset) & 2047;
|
||||
uvc_video_clock_add_sample(&stream->clock, &sample);
|
||||
stream->clock.last_sof = sample.dev_sof;
|
||||
}
|
||||
|
||||
static void uvc_video_clock_reset(struct uvc_streaming *stream)
|
||||
static void uvc_video_clock_reset(struct uvc_clock *clock)
|
||||
{
|
||||
struct uvc_clock *clock = &stream->clock;
|
||||
|
||||
clock->head = 0;
|
||||
clock->count = 0;
|
||||
clock->last_sof = -1;
|
||||
clock->last_sof_overflow = -1;
|
||||
clock->sof_offset = -1;
|
||||
}
|
||||
|
||||
static int uvc_video_clock_init(struct uvc_streaming *stream)
|
||||
static int uvc_video_clock_init(struct uvc_clock *clock)
|
||||
{
|
||||
struct uvc_clock *clock = &stream->clock;
|
||||
|
||||
spin_lock_init(&clock->lock);
|
||||
clock->size = 32;
|
||||
|
||||
@@ -600,15 +651,15 @@ static int uvc_video_clock_init(struct uvc_streaming *stream)
|
||||
if (clock->samples == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
uvc_video_clock_reset(stream);
|
||||
uvc_video_clock_reset(clock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void uvc_video_clock_cleanup(struct uvc_streaming *stream)
|
||||
static void uvc_video_clock_cleanup(struct uvc_clock *clock)
|
||||
{
|
||||
kfree(stream->clock.samples);
|
||||
stream->clock.samples = NULL;
|
||||
kfree(clock->samples);
|
||||
clock->samples = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -709,11 +760,11 @@ void uvc_video_clock_update(struct uvc_streaming *stream,
|
||||
unsigned long flags;
|
||||
u64 timestamp;
|
||||
u32 delta_stc;
|
||||
u32 y1, y2;
|
||||
u32 y1;
|
||||
u32 x1, x2;
|
||||
u32 mean;
|
||||
u32 sof;
|
||||
u64 y;
|
||||
u64 y, y2;
|
||||
|
||||
if (!uvc_hw_timestamps_param)
|
||||
return;
|
||||
@@ -728,11 +779,11 @@ void uvc_video_clock_update(struct uvc_streaming *stream,
|
||||
|
||||
spin_lock_irqsave(&clock->lock, flags);
|
||||
|
||||
if (clock->count < clock->size)
|
||||
if (clock->count < 2)
|
||||
goto done;
|
||||
|
||||
first = &clock->samples[clock->head];
|
||||
last = &clock->samples[(clock->head - 1) % clock->size];
|
||||
first = &clock->samples[(clock->head - clock->count + clock->size) % clock->size];
|
||||
last = &clock->samples[(clock->head - 1 + clock->size) % clock->size];
|
||||
|
||||
/* First step, PTS to SOF conversion. */
|
||||
delta_stc = buf->pts - (1UL << 31);
|
||||
@@ -746,6 +797,18 @@ void uvc_video_clock_update(struct uvc_streaming *stream,
|
||||
if (y2 < y1)
|
||||
y2 += 2048 << 16;
|
||||
|
||||
/*
|
||||
* Have at least 1/4 of a second of timestamps before we
|
||||
* try to do any calculation. Otherwise we do not have enough
|
||||
* precision. This value was determined by running Android CTS
|
||||
* on different devices.
|
||||
*
|
||||
* dev_sof runs at 1KHz, and we have a fixed point precision of
|
||||
* 16 bits.
|
||||
*/
|
||||
if ((y2 - y1) < ((1000 / 4) << 16))
|
||||
goto done;
|
||||
|
||||
y = (u64)(y2 - y1) * (1ULL << 31) + (u64)y1 * (u64)x2
|
||||
- (u64)y2 * (u64)x1;
|
||||
y = div_u64(y, x2 - x1);
|
||||
@@ -753,7 +816,7 @@ void uvc_video_clock_update(struct uvc_streaming *stream,
|
||||
sof = y;
|
||||
|
||||
uvc_dbg(stream->dev, CLOCK,
|
||||
"%s: PTS %u y %llu.%06llu SOF %u.%06llu (x1 %u x2 %u y1 %u y2 %u SOF offset %u)\n",
|
||||
"%s: PTS %u y %llu.%06llu SOF %u.%06llu (x1 %u x2 %u y1 %u y2 %llu SOF offset %u)\n",
|
||||
stream->dev->name, buf->pts,
|
||||
y >> 16, div_u64((y & 0xffff) * 1000000, 65536),
|
||||
sof >> 16, div_u64(((u64)sof & 0xffff) * 1000000LLU, 65536),
|
||||
@@ -768,7 +831,7 @@ void uvc_video_clock_update(struct uvc_streaming *stream,
|
||||
goto done;
|
||||
|
||||
y1 = NSEC_PER_SEC;
|
||||
y2 = (u32)ktime_to_ns(ktime_sub(last->host_time, first->host_time)) + y1;
|
||||
y2 = ktime_to_ns(ktime_sub(last->host_time, first->host_time)) + y1;
|
||||
|
||||
/*
|
||||
* Interpolated and host SOF timestamps can wrap around at slightly
|
||||
@@ -789,7 +852,7 @@ void uvc_video_clock_update(struct uvc_streaming *stream,
|
||||
timestamp = ktime_to_ns(first->host_time) + y - y1;
|
||||
|
||||
uvc_dbg(stream->dev, CLOCK,
|
||||
"%s: SOF %u.%06llu y %llu ts %llu buf ts %llu (x1 %u/%u/%u x2 %u/%u/%u y1 %u y2 %u)\n",
|
||||
"%s: SOF %u.%06llu y %llu ts %llu buf ts %llu (x1 %u/%u/%u x2 %u/%u/%u y1 %u y2 %llu)\n",
|
||||
stream->dev->name,
|
||||
sof >> 16, div_u64(((u64)sof & 0xffff) * 1000000LLU, 65536),
|
||||
y, timestamp, vbuf->vb2_buf.timestamp,
|
||||
@@ -2071,7 +2134,7 @@ int uvc_video_resume(struct uvc_streaming *stream, int reset)
|
||||
|
||||
stream->frozen = 0;
|
||||
|
||||
uvc_video_clock_reset(stream);
|
||||
uvc_video_clock_reset(&stream->clock);
|
||||
|
||||
if (!uvc_queue_streaming(&stream->queue))
|
||||
return 0;
|
||||
@@ -2220,7 +2283,7 @@ int uvc_video_start_streaming(struct uvc_streaming *stream)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = uvc_video_clock_init(stream);
|
||||
ret = uvc_video_clock_init(&stream->clock);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
@@ -2238,7 +2301,7 @@ int uvc_video_start_streaming(struct uvc_streaming *stream)
|
||||
error_video:
|
||||
usb_set_interface(stream->dev->udev, stream->intfnum, 0);
|
||||
error_commit:
|
||||
uvc_video_clock_cleanup(stream);
|
||||
uvc_video_clock_cleanup(&stream->clock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -2266,5 +2329,5 @@ void uvc_video_stop_streaming(struct uvc_streaming *stream)
|
||||
usb_clear_halt(stream->dev->udev, pipe);
|
||||
}
|
||||
|
||||
uvc_video_clock_cleanup(stream);
|
||||
uvc_video_clock_cleanup(&stream->clock);
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
#define UVC_QUIRK_WAKE_AUTOSUSPEND 0x00002000
|
||||
#define UVC_QUIRK_NO_RESET_RESUME 0x00004000
|
||||
#define UVC_QUIRK_DISABLE_AUTOSUSPEND 0x00008000
|
||||
#define UVC_QUIRK_INVALID_DEVICE_SOF 0x00010000
|
||||
|
||||
/* Format flags */
|
||||
#define UVC_FMT_FLAG_COMPRESSED 0x00000001
|
||||
@@ -86,7 +87,9 @@
|
||||
|
||||
struct gpio_desc;
|
||||
struct sg_table;
|
||||
struct uvc_control;
|
||||
struct uvc_device;
|
||||
struct uvc_video_chain;
|
||||
|
||||
/*
|
||||
* TODO: Put the most frequently accessed fields at the beginning of
|
||||
@@ -125,6 +128,9 @@ struct uvc_control_mapping {
|
||||
s32 master_manual;
|
||||
u32 slave_ids[2];
|
||||
|
||||
const struct uvc_control_mapping *(*filter_mapping)
|
||||
(struct uvc_video_chain *chain,
|
||||
struct uvc_control *ctrl);
|
||||
s32 (*get)(struct uvc_control_mapping *mapping, u8 query,
|
||||
const u8 *data);
|
||||
void (*set)(struct uvc_control_mapping *mapping, s32 value,
|
||||
@@ -500,6 +506,7 @@ struct uvc_streaming {
|
||||
unsigned int head;
|
||||
unsigned int count;
|
||||
unsigned int size;
|
||||
unsigned int last_sof_overflow;
|
||||
|
||||
u16 last_sof;
|
||||
u16 sof_offset;
|
||||
@@ -524,7 +531,6 @@ struct uvc_device_info {
|
||||
u32 quirks;
|
||||
u32 meta_format;
|
||||
u16 uvc_version;
|
||||
const struct uvc_control_mapping **mappings;
|
||||
};
|
||||
|
||||
struct uvc_status_streaming {
|
||||
@@ -750,8 +756,6 @@ int uvc_status_start(struct uvc_device *dev, gfp_t flags);
|
||||
void uvc_status_stop(struct uvc_device *dev);
|
||||
|
||||
/* Controls */
|
||||
extern const struct uvc_control_mapping uvc_ctrl_power_line_mapping_limited;
|
||||
extern const struct uvc_control_mapping uvc_ctrl_power_line_mapping_uvc11;
|
||||
extern const struct v4l2_subscribed_event_ops uvc_ctrl_sub_ev_ops;
|
||||
|
||||
int uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
|
||||
|
||||
Reference in New Issue
Block a user