V4L/DVB (8348): gspca: Add auto gain/exposure to sonixb and tas5110 / ov6650 sensors.

sonixb:   Do auto gain for tas5110 / ov6650 sensors.
pac207:   Move the auto_gain function to gspca.
gspca:    New function gspca_auto_gain_n_exposure().

Signed-off-by: Hans de Goede <j.w.r.degoede@hhs.nl>
Signed-off-by: Jean-Francois Moine <moinejf@free.fr>
Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
diff --git a/drivers/media/video/gspca/gspca.c b/drivers/media/video/gspca/gspca.c
index 09f190c..2ffb00a 100644
--- a/drivers/media/video/gspca/gspca.c
+++ b/drivers/media/video/gspca/gspca.c
@@ -1790,6 +1790,94 @@
 }
 EXPORT_SYMBOL(gspca_disconnect);
 
+/* -- cam driver utility functions -- */
+
+/* auto gain and exposure algorithm based on the knee algorithm described here:
+   http://ytse.tricolour.net/docs/LowLightOptimization.html
+
+   Returns 0 if no changes were made, 1 if the gain and or exposure settings
+   where changed. */
+int gspca_auto_gain_n_exposure(struct gspca_dev *gspca_dev, int avg_lum,
+	int desired_avg_lum, int deadzone, int gain_knee, int exposure_knee)
+{
+	int i, steps, gain, orig_gain, exposure, orig_exposure, autogain;
+	const struct ctrl *gain_ctrl = NULL;
+	const struct ctrl *exposure_ctrl = NULL;
+	const struct ctrl *autogain_ctrl = NULL;
+	int retval = 0;
+
+	for (i = 0; i < gspca_dev->sd_desc->nctrls; i++) {
+		if (gspca_dev->sd_desc->ctrls[i].qctrl.id == V4L2_CID_GAIN)
+			gain_ctrl = &gspca_dev->sd_desc->ctrls[i];
+		if (gspca_dev->sd_desc->ctrls[i].qctrl.id == V4L2_CID_EXPOSURE)
+			exposure_ctrl = &gspca_dev->sd_desc->ctrls[i];
+		if (gspca_dev->sd_desc->ctrls[i].qctrl.id == V4L2_CID_AUTOGAIN)
+			autogain_ctrl = &gspca_dev->sd_desc->ctrls[i];
+	}
+	if (!gain_ctrl || !exposure_ctrl || !autogain_ctrl) {
+		PDEBUG(D_ERR, "Error: gspca_auto_gain_n_exposure called "
+			"on cam without (auto)gain/exposure");
+		return 0;
+	}
+
+	if (gain_ctrl->get(gspca_dev, &gain) ||
+			exposure_ctrl->get(gspca_dev, &exposure) ||
+			autogain_ctrl->get(gspca_dev, &autogain) || !autogain)
+		return 0;
+
+	orig_gain = gain;
+	orig_exposure = exposure;
+
+	/* If we are of a multiple of deadzone, do multiple steps to reach the
+	   desired lumination fast (with the risc of a slight overshoot) */
+	steps = abs(desired_avg_lum - avg_lum) / deadzone;
+
+	PDEBUG(D_FRAM, "autogain: lum: %d, desired: %d, steps: %d\n",
+		avg_lum, desired_avg_lum, steps);
+
+	for (i = 0; i < steps; i++) {
+		if (avg_lum > desired_avg_lum) {
+			if (gain > gain_knee)
+				gain--;
+			else if (exposure > exposure_knee)
+				exposure--;
+			else if (gain > gain_ctrl->qctrl.default_value)
+				gain--;
+			else if (exposure > exposure_ctrl->qctrl.minimum)
+				exposure--;
+			else if (gain > gain_ctrl->qctrl.minimum)
+				gain--;
+			else
+				break;
+		} else {
+			if (gain < gain_ctrl->qctrl.default_value)
+				gain++;
+			else if (exposure < exposure_knee)
+				exposure++;
+			else if (gain < gain_knee)
+				gain++;
+			else if (exposure < exposure_ctrl->qctrl.maximum)
+				exposure++;
+			else if (gain < gain_ctrl->qctrl.maximum)
+				gain++;
+			else
+				break;
+		}
+	}
+
+	if (gain != orig_gain) {
+		gain_ctrl->set(gspca_dev, gain);
+		retval = 1;
+	}
+	if (exposure != orig_exposure) {
+		exposure_ctrl->set(gspca_dev, exposure);
+		retval = 1;
+	}
+
+	return retval;
+}
+EXPORT_SYMBOL(gspca_auto_gain_n_exposure);
+
 /* -- module insert / remove -- */
 static int __init gspca_init(void)
 {