V4L/DVB (9570): uvcvideo: Handle failed video GET_{MIN|MAX|DEF} requests more gracefully

Failed requests will now generate a one-time warning message instead of the
usual "Failed to query..." error, which should be more user-friendly. The
driver will also recover automatically from failed GET_MIN/GET_MAX requests
when the device is half-broken without requiring the MINMAX quirk (fully
broken devices still need the quirk).

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_driver.c b/drivers/media/video/uvc/uvc_driver.c
index 11398ee..1e3b4fe 100644
--- a/drivers/media/video/uvc/uvc_driver.c
+++ b/drivers/media/video/uvc/uvc_driver.c
@@ -1726,24 +1726,6 @@
  * though they are compliant.
  */
 static struct usb_device_id uvc_ids[] = {
-	/* ALi M5606 (Clevo M540SR) */
-	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
-				| USB_DEVICE_ID_MATCH_INT_INFO,
-	  .idVendor		= 0x0402,
-	  .idProduct		= 0x5606,
-	  .bInterfaceClass	= USB_CLASS_VIDEO,
-	  .bInterfaceSubClass	= 1,
-	  .bInterfaceProtocol	= 0,
-	  .driver_info		= UVC_QUIRK_PROBE_MINMAX },
-	/* Creative Live! Optia */
-	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
-				| USB_DEVICE_ID_MATCH_INT_INFO,
-	  .idVendor		= 0x041e,
-	  .idProduct		= 0x4057,
-	  .bInterfaceClass	= USB_CLASS_VIDEO,
-	  .bInterfaceSubClass	= 1,
-	  .bInterfaceProtocol	= 0,
-	  .driver_info		= UVC_QUIRK_PROBE_MINMAX },
 	/* Microsoft Lifecam NX-6000 */
 	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
 				| USB_DEVICE_ID_MATCH_INT_INFO,
@@ -1829,15 +1811,6 @@
 	  .bInterfaceSubClass   = 1,
 	  .bInterfaceProtocol   = 0,
 	  .driver_info          = UVC_QUIRK_STREAM_NO_FID },
-	/* Silicon Motion SM371 */
-	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
-				| USB_DEVICE_ID_MATCH_INT_INFO,
-	  .idVendor		= 0x090c,
-	  .idProduct		= 0xb371,
-	  .bInterfaceClass	= USB_CLASS_VIDEO,
-	  .bInterfaceSubClass	= 1,
-	  .bInterfaceProtocol	= 0,
-	  .driver_info		= UVC_QUIRK_PROBE_MINMAX },
 	/* MT6227 */
 	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
 				| USB_DEVICE_ID_MATCH_INT_INFO,
@@ -1922,105 +1895,6 @@
 	  .bInterfaceProtocol	= 0,
 	  .driver_info		= UVC_QUIRK_PROBE_MINMAX
 				| UVC_QUIRK_IGNORE_SELECTOR_UNIT},
-	/* Acer OEM Webcam - Unknown vendor */
-	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
-				| USB_DEVICE_ID_MATCH_INT_INFO,
-	  .idVendor		= 0x5986,
-	  .idProduct		= 0x0100,
-	  .bInterfaceClass	= USB_CLASS_VIDEO,
-	  .bInterfaceSubClass	= 1,
-	  .bInterfaceProtocol	= 0,
-	  .driver_info		= UVC_QUIRK_PROBE_MINMAX },
-	/* Packard Bell OEM Webcam - Bison Electronics */
-	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
-				| USB_DEVICE_ID_MATCH_INT_INFO,
-	  .idVendor		= 0x5986,
-	  .idProduct		= 0x0101,
-	  .bInterfaceClass	= USB_CLASS_VIDEO,
-	  .bInterfaceSubClass	= 1,
-	  .bInterfaceProtocol	= 0,
-	  .driver_info		= UVC_QUIRK_PROBE_MINMAX },
-	/* Acer Crystal Eye webcam - Bison Electronics */
-	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
-				| USB_DEVICE_ID_MATCH_INT_INFO,
-	  .idVendor		= 0x5986,
-	  .idProduct		= 0x0102,
-	  .bInterfaceClass	= USB_CLASS_VIDEO,
-	  .bInterfaceSubClass	= 1,
-	  .bInterfaceProtocol	= 0,
-	  .driver_info		= UVC_QUIRK_PROBE_MINMAX },
-	/* Compaq Presario B1200 - Bison Electronics */
-	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
-				| USB_DEVICE_ID_MATCH_INT_INFO,
-	  .idVendor		= 0x5986,
-	  .idProduct		= 0x0104,
-	  .bInterfaceClass	= USB_CLASS_VIDEO,
-	  .bInterfaceSubClass	= 1,
-	  .bInterfaceProtocol	= 0,
-	  .driver_info		= UVC_QUIRK_PROBE_MINMAX },
-	/* Acer Travelmate 7720 - Bison Electronics */
-	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
-				| USB_DEVICE_ID_MATCH_INT_INFO,
-	  .idVendor		= 0x5986,
-	  .idProduct		= 0x0105,
-	  .bInterfaceClass	= USB_CLASS_VIDEO,
-	  .bInterfaceSubClass	= 1,
-	  .bInterfaceProtocol	= 0,
-	  .driver_info		= UVC_QUIRK_PROBE_MINMAX },
-	/* Medion Akoya Mini E1210 - Bison Electronics */
-	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
-				| USB_DEVICE_ID_MATCH_INT_INFO,
-	  .idVendor		= 0x5986,
-	  .idProduct		= 0x0141,
-	  .bInterfaceClass	= USB_CLASS_VIDEO,
-	  .bInterfaceSubClass	= 1,
-	  .bInterfaceProtocol	= 0,
-	  .driver_info		= UVC_QUIRK_PROBE_MINMAX },
-	/* Acer OrbiCam - Bison Electronics */
-	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
-				| USB_DEVICE_ID_MATCH_INT_INFO,
-	  .idVendor		= 0x5986,
-	  .idProduct		= 0x0200,
-	  .bInterfaceClass	= USB_CLASS_VIDEO,
-	  .bInterfaceSubClass	= 1,
-	  .bInterfaceProtocol	= 0,
-	  .driver_info		= UVC_QUIRK_PROBE_MINMAX },
-	/*  Fujitsu Amilo SI2636 - Bison Electronics */
-	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
-				| USB_DEVICE_ID_MATCH_INT_INFO,
-	  .idVendor		= 0x5986,
-	  .idProduct		= 0x0202,
-	  .bInterfaceClass	= USB_CLASS_VIDEO,
-	  .bInterfaceSubClass	= 1,
-	  .bInterfaceProtocol	= 0,
-	  .driver_info		= UVC_QUIRK_PROBE_MINMAX },
-	/*  Advent 4211 - Bison Electronics */
-	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
-				| USB_DEVICE_ID_MATCH_INT_INFO,
-	  .idVendor		= 0x5986,
-	  .idProduct		= 0x0203,
-	  .bInterfaceClass	= USB_CLASS_VIDEO,
-	  .bInterfaceSubClass	= 1,
-	  .bInterfaceProtocol	= 0,
-	  .driver_info		= UVC_QUIRK_PROBE_MINMAX },
-	/* Bison Electronics */
-	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
-				| USB_DEVICE_ID_MATCH_INT_INFO,
-	  .idVendor		= 0x5986,
-	  .idProduct		= 0x0300,
-	  .bInterfaceClass	= USB_CLASS_VIDEO,
-	  .bInterfaceSubClass	= 1,
-	  .bInterfaceProtocol	= 0,
-	  .driver_info		= UVC_QUIRK_PROBE_MINMAX },
-	/* Clevo M570TU - Bison Electronics */
-	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
-				| USB_DEVICE_ID_MATCH_INT_INFO,
-	  .idVendor		= 0x5986,
-	  .idProduct		= 0x0303,
-	  .bInterfaceClass	= USB_CLASS_VIDEO,
-	  .bInterfaceSubClass	= 1,
-	  .bInterfaceProtocol	= 0,
-	  .driver_info		= UVC_QUIRK_PROBE_MINMAX },
 	/* Generic USB Video Class */
 	{ USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, 0) },
 	{}
diff --git a/drivers/media/video/uvc/uvc_v4l2.c b/drivers/media/video/uvc/uvc_v4l2.c
index 8361367..624bf74 100644
--- a/drivers/media/video/uvc/uvc_v4l2.c
+++ b/drivers/media/video/uvc/uvc_v4l2.c
@@ -252,7 +252,7 @@
 	if (ret < 0)
 		return ret;
 
-	if ((ret = uvc_set_video_ctrl(video, &probe, 0)) < 0)
+	if ((ret = uvc_commit_video(video, &probe)) < 0)
 		return ret;
 
 	memcpy(&video->streaming->ctrl, &probe, sizeof probe);
@@ -316,7 +316,7 @@
 		return ret;
 
 	/* Commit the new settings. */
-	if ((ret = uvc_set_video_ctrl(video, &probe, 0)) < 0)
+	if ((ret = uvc_commit_video(video, &probe)) < 0)
 		return ret;
 
 	memcpy(&video->streaming->ctrl, &probe, sizeof probe);
diff --git a/drivers/media/video/uvc/uvc_video.c b/drivers/media/video/uvc/uvc_video.c
index b7bb238..4af9470 100644
--- a/drivers/media/video/uvc/uvc_video.c
+++ b/drivers/media/video/uvc/uvc_video.c
@@ -36,15 +36,22 @@
 {
 	__u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
 	unsigned int pipe;
-	int ret;
 
 	pipe = (query & 0x80) ? usb_rcvctrlpipe(dev->udev, 0)
 			      : usb_sndctrlpipe(dev->udev, 0);
 	type |= (query & 0x80) ? USB_DIR_IN : USB_DIR_OUT;
 
-	ret = usb_control_msg(dev->udev, pipe, query, type, cs << 8,
+	return usb_control_msg(dev->udev, pipe, query, type, cs << 8,
 			unit << 8 | intfnum, data, size, timeout);
+}
 
+int uvc_query_ctrl(struct uvc_device *dev, __u8 query, __u8 unit,
+			__u8 intfnum, __u8 cs, void *data, __u16 size)
+{
+	int ret;
+
+	ret = __uvc_query_ctrl(dev, query, unit, intfnum, cs, data, size,
+				UVC_CTRL_CONTROL_TIMEOUT);
 	if (ret != size) {
 		uvc_printk(KERN_ERR, "Failed to query (%u) UVC control %u "
 			"(unit %u) : %d (exp. %u).\n", query, cs, unit, ret,
@@ -55,13 +62,6 @@
 	return 0;
 }
 
-int uvc_query_ctrl(struct uvc_device *dev, __u8 query, __u8 unit,
-			__u8 intfnum, __u8 cs, void *data, __u16 size)
-{
-	return __uvc_query_ctrl(dev, query, unit, intfnum, cs, data, size,
-				UVC_CTRL_CONTROL_TIMEOUT);
-}
-
 static void uvc_fixup_buffer_size(struct uvc_video_device *video,
 	struct uvc_streaming_control *ctrl)
 {
@@ -102,8 +102,36 @@
 	ret = __uvc_query_ctrl(video->dev, query, 0, video->streaming->intfnum,
 		probe ? VS_PROBE_CONTROL : VS_COMMIT_CONTROL, data, size,
 		UVC_CTRL_STREAMING_TIMEOUT);
-	if (ret < 0)
+
+	if ((query == GET_MIN || query == GET_MAX) && ret == 2) {
+		/* Some cameras, mostly based on Bison Electronics chipsets,
+		 * answer a GET_MIN or GET_MAX request with the wCompQuality
+		 * field only.
+		 */
+		uvc_warn_once(video->dev, UVC_WARN_MINMAX, "UVC non "
+			"compliance - GET_MIN/MAX(PROBE) incorrectly "
+			"supported. Enabling workaround.\n");
+		memset(ctrl, 0, sizeof ctrl);
+		ctrl->wCompQuality = le16_to_cpup((__le16 *)data);
+		ret = 0;
 		goto out;
+	} else if (query == GET_DEF && probe == 1) {
+		/* Many cameras don't support the GET_DEF request on their
+		 * video probe control. Warn once and return, the caller will
+		 * fall back to GET_CUR.
+		 */
+		uvc_warn_once(video->dev, UVC_WARN_PROBE_DEF, "UVC non "
+			"compliance - GET_DEF(PROBE) not supported. "
+			"Enabling workaround.\n");
+		ret = -EIO;
+		goto out;
+	} else if (ret != size) {
+		uvc_printk(KERN_ERR, "Failed to query (%u) UVC %s control : "
+			"%d (exp. %u).\n", query, probe ? "probe" : "commit",
+			ret, size);
+		ret = -EIO;
+		goto out;
+	}
 
 	ctrl->bmHint = le16_to_cpup((__le16 *)&data[0]);
 	ctrl->bFormatIndex = data[2];
@@ -138,13 +166,14 @@
 	 * Try to get the value from the format and frame descriptor.
 	 */
 	uvc_fixup_buffer_size(video, ctrl);
+	ret = 0;
 
 out:
 	kfree(data);
 	return ret;
 }
 
-int uvc_set_video_ctrl(struct uvc_video_device *video,
+static int uvc_set_video_ctrl(struct uvc_video_device *video,
 	struct uvc_streaming_control *ctrl, int probe)
 {
 	__u8 *data;
@@ -186,6 +215,12 @@
 		video->streaming->intfnum,
 		probe ? VS_PROBE_CONTROL : VS_COMMIT_CONTROL, data, size,
 		UVC_CTRL_STREAMING_TIMEOUT);
+	if (ret != size) {
+		uvc_printk(KERN_ERR, "Failed to set UVC %s control : "
+			"%d (exp. %u).\n", probe ? "probe" : "commit",
+			ret, size);
+		ret = -EIO;
+	}
 
 	kfree(data);
 	return ret;
@@ -252,6 +287,12 @@
 	return ret;
 }
 
+int uvc_commit_video(struct uvc_video_device *video,
+	struct uvc_streaming_control *probe)
+{
+	return uvc_set_video_ctrl(video, probe, 0);
+}
+
 /* ------------------------------------------------------------------------
  * Video codecs
  */
diff --git a/drivers/media/video/uvc/uvcvideo.h b/drivers/media/video/uvc/uvcvideo.h
index 9a6bc1a..595b90e 100644
--- a/drivers/media/video/uvc/uvcvideo.h
+++ b/drivers/media/video/uvc/uvcvideo.h
@@ -617,6 +617,7 @@
 struct uvc_device {
 	struct usb_device *udev;
 	struct usb_interface *intf;
+	unsigned long warnings;
 	__u32 quirks;
 	int intfnum;
 	char name[32];
@@ -679,6 +680,9 @@
 #define UVC_TRACE_SUSPEND	(1 << 8)
 #define UVC_TRACE_STATUS	(1 << 9)
 
+#define UVC_WARN_MINMAX		0
+#define UVC_WARN_PROBE_DEF	1
+
 extern unsigned int uvc_trace_param;
 
 #define uvc_trace(flag, msg...) \
@@ -687,6 +691,12 @@
 			printk(KERN_DEBUG "uvcvideo: " msg); \
 	} while (0)
 
+#define uvc_warn_once(dev, warn, msg...) \
+	do { \
+		if (!test_and_set_bit(warn, &dev->warnings)) \
+			printk(KERN_INFO "uvcvideo: " msg); \
+	} while (0)
+
 #define uvc_printk(level, msg...) \
 	printk(level "uvcvideo: " msg)
 
@@ -740,10 +750,10 @@
 extern int uvc_video_enable(struct uvc_video_device *video, int enable);
 extern int uvc_probe_video(struct uvc_video_device *video,
 		struct uvc_streaming_control *probe);
+extern int uvc_commit_video(struct uvc_video_device *video,
+		struct uvc_streaming_control *ctrl);
 extern int uvc_query_ctrl(struct uvc_device *dev, __u8 query, __u8 unit,
 		__u8 intfnum, __u8 cs, void *data, __u16 size);
-extern int uvc_set_video_ctrl(struct uvc_video_device *video,
-		struct uvc_streaming_control *ctrl, int probe);
 
 /* Status */
 extern int uvc_status_init(struct uvc_device *dev);