Logitech UVC webcams can be manipulated to output RGB Bayer formats. When
such a camera is detected, the formats list is appended.
Tested on Logitech Webcam C500 ('first generation'), and HD Webcam C270
('second generation').
Signed-off-by: Peter Ross <***@xvid.org>
---
Post by Oleksij RempelI have some problems to use this patch set with guvcview. Frame rate
info seems to be corrupt for bayer. There are only some random values.
Well spotted. This is fixed in PATCHv3. The frame intervals array was not
copied correctly, leaving dwFrameInterval pointing to unallocated memory.
Have added comment explaining how this works.
Additionally the length of the array was incorretly calculated when
bFrameIntervalType=0. This doesn't occur on my test cameras, but may occur
with other devices.
Cheers,
drivers/media/usb/uvc/uvc_driver.c | 190 +++++++++++++++++++++++++++++++++++++
drivers/media/usb/uvc/uvc_video.c | 48 ++++++++++
drivers/media/usb/uvc/uvcvideo.h | 22 +++++
3 files changed, 260 insertions(+)
diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c
index b6cac17..0a590bd 100644
--- a/drivers/media/usb/uvc/uvc_driver.c
+++ b/drivers/media/usb/uvc/uvc_driver.c
@@ -1152,6 +1152,17 @@ static int uvc_parse_standard_control(struct uvc_device *dev,
else
sprintf(unit->name, "Extension %u", buffer[3]);
+ /* Detect Logitech extension units */
+ if (!memcmp(unit->extension.guidExtensionCode,
+ (const __u8[])UVC_GUID_XU_LOGITECH_VIDEO, 16)) {
+ dev->logitech_type = 1;
+ dev->logitech_video_xu = unit->id;
+ } else if (!memcmp(unit->extension.guidExtensionCode,
+ (const __u8[])UVC_GUID_XU_LOGITECH_VIDEO2, 16)) {
+ dev->logitech_type = 2;
+ dev->logitech_video_xu = unit->id;
+ }
+
list_add_tail(&unit->list, &dev->entities);
break;
@@ -1211,6 +1222,163 @@ next_descriptor:
return 0;
}
+/**
+ * Convert bFrameIntervalType to dwFrameInterval[] element size
+ */
+#define NB_FRAME_INTERVAL_ELEMS(bFrameIntervalType) \
+ ((bFrameIntervalType) ? (bFrameIntervalType) : 3)
+
+static void copy_format(struct uvc_format **dst_format, struct uvc_frame **dst_frame,
+ __u32 **dst_interval, const struct uvc_format *src)
+{
+ int i;
+ memcpy(*dst_format, src, sizeof **dst_format);
+
+ if (src->frame) {
+ memcpy(*dst_frame, src->frame, src->nframes * sizeof **dst_frame);
+ (*dst_format)->frame = *dst_frame;
+ *dst_frame += src->nframes;
+ }
+
+ if (dst_interval) {
+ for (i = 0; i < src->nframes; i++) {
+ struct uvc_frame * srcframe = &src->frame[i];
+ struct uvc_frame * dstframe = &(*dst_format)->frame[i];
+ if (srcframe->dwFrameInterval) {
+ int n = NB_FRAME_INTERVAL_ELEMS(srcframe->bFrameIntervalType);
+ memcpy(*dst_interval, srcframe->dwFrameInterval,
+ n * sizeof **dst_interval);
+
+ /* Update source frame dwFrameInterval with the new
+ interval block. This ensures RAW frames derived from
+ the source frame obtain the new intervals block. */
+ srcframe->dwFrameInterval =
+ dstframe->dwFrameInterval = *dst_interval;
+
+ *dst_interval += n;
+ }
+ }
+ }
+}
+
+static void recaclulate_frame_buffer_sizes(struct uvc_format *format)
+{
+ int i;
+ for (i = 0; i < format->nframes; i++) {
+ struct uvc_frame * frame = &format->frame[i];
+ frame->dwMaxVideoFrameBufferSize =
+ (frame->wWidth * frame->wHeight * format->bpp + 7) / 8;
+ }
+}
+
+/* For each stream, recreate the format data-structure to include RGB Bayer formats
+ */
+static void uvc_append_logitech_raw_formats(struct uvc_device *dev)
+{
+ struct uvc_streaming *stream;
+ int i, j;
+ struct uvc_format * format;
+ struct uvc_frame * frame;
+ __u32 * interval;
+
+ list_for_each_entry(stream, &dev->streams, list) {
+ struct uvc_format * srcformats = stream->format;
+ int skip = 0;
+ int nframes = 0, nintervals = 0;
+ int raw_formats = 0, raw_frames = 0;
+ unsigned int size;
+
+ if (stream->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ continue;
+
+ /* count the number of existing frames and intervals */
+
+ for (i = 0; i < stream->nformats; i++) {
+ struct uvc_format * old = &srcformats[i];
+
+ /* if the bFormatIndex high bits are in use, then we
+ * can't append RAW formats */
+ if ((old->index & (UVC_FORMATINDEX_LOGITECH_RAW|
+ UVC_FORMATINDEX_LOGITECH_10BPP))) {
+ skip = 1;
+ break;
+ }
+
+ nframes += old->nframes;
+
+ for (j = 0; j < old->nframes; j++)
+ nintervals += NB_FRAME_INTERVAL_ELEMS(old->frame[j].bFrameIntervalType);
+
+ if (!(old->flags & UVC_FMT_FLAG_COMPRESSED)) {
+ if (dev->logitech_type == 1) {
+ raw_formats++;
+ raw_frames += old->nframes;
+ }
+ raw_formats++;
+ raw_frames += old->nframes;
+ }
+ }
+
+ if (skip)
+ continue;
+
+ size = (stream->nformats + raw_formats) * sizeof(*format) +
+ (nframes + raw_frames) * sizeof(struct uvc_frame) +
+ nintervals * sizeof(__u32);
+
+ format = kzalloc(size, GFP_KERNEL);
+ if (!format)
+ return;
+
+ stream->format = format;
+ frame = (struct uvc_frame *)&format[stream->nformats + raw_formats];
+ interval = (__u32 *)&frame[nframes + raw_frames];
+
+ for (i = 0; i < stream->nformats; i++) {
+ struct uvc_format * cache_format = format;
+
+ /* copy the old format into the new kalloc block */
+ copy_format(&format, &frame, &interval, &srcformats[i]);
+ format++;
+
+ if ((srcformats[i].flags & UVC_FMT_FLAG_COMPRESSED))
+ continue;
+
+ /* append 8-bit RGB Bayer format */
+ if (dev->logitech_type == 1) {
+ copy_format(&format, &frame, NULL, cache_format);
+ format->index |= UVC_FORMATINDEX_LOGITECH_RAW;
+ format->bpp = 8;
+ format->fcc = V4L2_PIX_FMT_SRGGB8;
+ strlcpy(format->name, "RGB Bayer (RG/GB 8-bit)",
+ sizeof format->name);
+ recaclulate_frame_buffer_sizes(format);
+ format++;
+ }
+
+ /* append 10-bit RGB Bayer format */
+ copy_format(&format, &frame, NULL, cache_format);
+ format->index |= UVC_FORMATINDEX_LOGITECH_RAW;
+ format->bpp = 10;
+ if (dev->logitech_type == 1) {
+ format->index |= UVC_FORMATINDEX_LOGITECH_10BPP;
+ format->fcc = V4L2_PIX_FMT_SRGGB10_1X10;
+ strlcpy(format->name, "RGB Bayer (RG/GB 10-bit)",
+ sizeof format->name);
+ } else {
+ format->fcc = V4L2_PIX_FMT_SGBRG10_1X10;
+ strlcpy(format->name, "RGB Bayer (GB/RG 10-bit)",
+ sizeof format->name);
+ }
+ recaclulate_frame_buffer_sizes(format);
+ format++;
+ }
+
+ stream->nformats += raw_formats;
+ kfree(srcformats);
+ }
+}
+
/* ------------------------------------------------------------------------
* UVC device scan
*/
@@ -1860,6 +2028,9 @@ static int uvc_probe(struct usb_interface *intf,
goto error;
}
+ if (dev->logitech_type)
+ uvc_append_logitech_raw_formats(dev);
+
uvc_printk(KERN_INFO, "Found UVC %u.%02x device %s (%04x:%04x)\n",
dev->uvc_version >> 8, dev->uvc_version & 0xff,
udev->product ? udev->product : "<unnamed>",
@@ -1926,6 +2097,25 @@ static void uvc_disconnect(struct usb_interface *intf)
{
struct uvc_device *dev = usb_get_intfdata(intf);
+ /* Reset Logitech 'disable_video_processing' and 'raw_data_bpp' controls. */
+ if (dev->logitech_type) {
+ const struct uvc_logitech_controls * controls =
+ &logitech_controls_table[dev->logitech_type];
+ __u8 value = 0;
+
+ uvc_query_ctrl(dev, UVC_SET_CUR,
+ dev->logitech_video_xu, dev->intfnum,
+ 1 + controls->disable_video_processing,
+ &value, sizeof(value));
+
+ if (controls->raw_data_bpp) {
+ uvc_query_ctrl(dev, UVC_SET_CUR,
+ dev->logitech_video_xu, dev->intfnum,
+ 1 + controls->raw_data_bpp,
+ &value, sizeof(value));
+ }
+ }
+
/* Set the USB interface data to NULL. This can be done outside the
* lock, as there's no other reader.
*/
diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c
index 103cd4e..2dfa0cc 100644
--- a/drivers/media/usb/uvc/uvc_video.c
+++ b/drivers/media/usb/uvc/uvc_video.c
@@ -155,6 +155,11 @@ static void uvc_fixup_video_ctrl(struct uvc_streaming *stream,
}
}
+const struct uvc_logitech_controls logitech_controls_table[] = {
+ [1] = { .disable_video_processing = 4, .raw_data_bpp = 7 },
+ [2] = { .disable_video_processing = 12 },
+};
+
static int uvc_get_video_ctrl(struct uvc_streaming *stream,
struct uvc_streaming_control *ctrl, int probe, __u8 query)
{
@@ -207,6 +212,27 @@ static int uvc_get_video_ctrl(struct uvc_streaming *stream,
ctrl->bmHint = le16_to_cpup((__le16 *)&data[0]);
ctrl->bFormatIndex = data[2];
+
+ if (stream->dev->logitech_type) {
+ const struct uvc_logitech_controls * controls =
+ &logitech_controls_table[stream->dev->logitech_type];
+
+ __u8 value = 0;
+ uvc_query_ctrl(stream->dev, UVC_GET_CUR,
+ stream->dev->logitech_video_xu, stream->dev->intfnum,
+ 1 + controls->disable_video_processing,
+ &value, sizeof(value));
+ if (value)
+ ctrl->bFormatIndex |= UVC_FORMATINDEX_LOGITECH_RAW;
+
+ value = 0;
+ if (controls->raw_data_bpp)
+ uvc_query_ctrl(stream->dev, UVC_GET_CUR,
+ stream->dev->logitech_video_xu, stream->dev->intfnum,
+ 1 + controls->raw_data_bpp, &value, sizeof(value));
+ if (value)
+ ctrl->bFormatIndex |= UVC_FORMATINDEX_LOGITECH_10BPP;
+ }
ctrl->bFrameIndex = data[3];
ctrl->dwFrameInterval = le32_to_cpup((__le32 *)&data[4]);
ctrl->wKeyFrameRate = le16_to_cpup((__le16 *)&data[8]);
@@ -257,6 +283,28 @@ static int uvc_set_video_ctrl(struct uvc_streaming *stream,
*(__le16 *)&data[0] = cpu_to_le16(ctrl->bmHint);
data[2] = ctrl->bFormatIndex;
+
+ if (stream->dev->logitech_type) {
+ const struct uvc_logitech_controls * controls =
+ &logitech_controls_table[stream->dev->logitech_type];
+
+ __u8 value = !!(ctrl->bFormatIndex & UVC_FORMATINDEX_LOGITECH_RAW);
+ uvc_query_ctrl(stream->dev, UVC_SET_CUR,
+ stream->dev->logitech_video_xu, stream->dev->intfnum,
+ 1 + controls->disable_video_processing,
+ &value, sizeof(value));
+
+ if (controls->raw_data_bpp) {
+ value = !!(ctrl->bFormatIndex & UVC_FORMATINDEX_LOGITECH_10BPP);
+ uvc_query_ctrl(stream->dev, UVC_SET_CUR,
+ stream->dev->logitech_video_xu, stream->dev->intfnum,
+ 1 + controls->raw_data_bpp,
+ &value, sizeof(value));
+ }
+
+ data[2] &= ~(UVC_FORMATINDEX_LOGITECH_RAW|UVC_FORMATINDEX_LOGITECH_10BPP);
+ }
+
data[3] = ctrl->bFrameIndex;
*(__le32 *)&data[4] = cpu_to_le32(ctrl->dwFrameInterval);
*(__le16 *)&data[8] = cpu_to_le16(ctrl->wKeyFrameRate);
diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h
index 143d5e5..c5a5060 100644
--- a/drivers/media/usb/uvc/uvcvideo.h
+++ b/drivers/media/usb/uvc/uvcvideo.h
@@ -105,6 +105,13 @@
{ 'H', '2', '6', '4', 0x00, 0x00, 0x10, 0x00, \
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_XU_LOGITECH_VIDEO \
+ {0x82, 0x06, 0x61, 0x63, 0x70, 0x50, 0xab, 0x49, \
+ 0xb8, 0xcc, 0xb3, 0x85, 0x5e, 0x8d, 0x22, 0x50}
+#define UVC_GUID_XU_LOGITECH_VIDEO2 \
+ {0xA9, 0x4C, 0x5D, 0x1F, 0x11, 0xdE, 0x87, 0x44, \
+ 0x84, 0x0D, 0x50, 0x93, 0x3C, 0x8E, 0xC8, 0xD1}
+
/* ------------------------------------------------------------------------
* Driver specific constants.
*/
@@ -140,6 +147,10 @@
#define UVC_FMT_FLAG_COMPRESSED 0x00000001
#define UVC_FMT_FLAG_STREAM 0x00000002
+/* Repurpose some bFormatIndex bits to indicate additional Logitech RAW formats */
+#define UVC_FORMATINDEX_LOGITECH_10BPP 0x40
+#define UVC_FORMATINDEX_LOGITECH_RAW 0x80
+
/* ------------------------------------------------------------------------
* Structures.
*/
@@ -537,6 +548,10 @@ struct uvc_device {
__u8 *status;
struct input_dev *input;
char input_phys[64];
+
+ /* Logitech Video XU */
+ __u8 logitech_type; /* 0=not detected, 1=first generation, 2=second generation */
+ __u8 logitech_video_xu; /* XU number */
};
enum uvc_handle_state {
@@ -722,4 +737,11 @@ void uvc_debugfs_cleanup_stream(struct uvc_streaming *stream);
size_t uvc_video_stats_dump(struct uvc_streaming *stream, char *buf,
size_t size);
+struct uvc_logitech_controls {
+ __u8 disable_video_processing;
+ __u8 raw_data_bpp;
+};
+
+extern const struct uvc_logitech_controls logitech_controls_table[];
+
#endif
--
1.8.3.2
-- Peter
(A907 E02F A6E5 0CD2 34CD 20D2 6760 79C5 AC40 DD6B)