xref: /openbmc/linux/drivers/media/usb/gspca/autogain_functions.c (revision 75bf465f0bc33e9b776a46d6a1b9b990f5fb7c37)
1*c942fddfSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
20c0d06caSMauro Carvalho Chehab /*
30c0d06caSMauro Carvalho Chehab  * Functions for auto gain.
40c0d06caSMauro Carvalho Chehab  *
50c0d06caSMauro Carvalho Chehab  * Copyright (C) 2010-2012 Hans de Goede <hdegoede@redhat.com>
60c0d06caSMauro Carvalho Chehab  */
70c0d06caSMauro Carvalho Chehab #include "gspca.h"
80c0d06caSMauro Carvalho Chehab 
90c0d06caSMauro Carvalho Chehab /* auto gain and exposure algorithm based on the knee algorithm described here:
100c0d06caSMauro Carvalho Chehab    http://ytse.tricolour.net/docs/LowLightOptimization.html
110c0d06caSMauro Carvalho Chehab 
120c0d06caSMauro Carvalho Chehab    Returns 0 if no changes were made, 1 if the gain and or exposure settings
130c0d06caSMauro Carvalho Chehab    where changed. */
gspca_expo_autogain(struct gspca_dev * gspca_dev,int avg_lum,int desired_avg_lum,int deadzone,int gain_knee,int exposure_knee)140c0d06caSMauro Carvalho Chehab int gspca_expo_autogain(
150c0d06caSMauro Carvalho Chehab 			struct gspca_dev *gspca_dev,
160c0d06caSMauro Carvalho Chehab 			int avg_lum,
170c0d06caSMauro Carvalho Chehab 			int desired_avg_lum,
180c0d06caSMauro Carvalho Chehab 			int deadzone,
190c0d06caSMauro Carvalho Chehab 			int gain_knee,
200c0d06caSMauro Carvalho Chehab 			int exposure_knee)
210c0d06caSMauro Carvalho Chehab {
220c0d06caSMauro Carvalho Chehab 	s32 gain, orig_gain, exposure, orig_exposure;
230c0d06caSMauro Carvalho Chehab 	int i, steps, retval = 0;
240c0d06caSMauro Carvalho Chehab 
250c0d06caSMauro Carvalho Chehab 	if (v4l2_ctrl_g_ctrl(gspca_dev->autogain) == 0)
260c0d06caSMauro Carvalho Chehab 		return 0;
270c0d06caSMauro Carvalho Chehab 
280c0d06caSMauro Carvalho Chehab 	orig_gain = gain = v4l2_ctrl_g_ctrl(gspca_dev->gain);
290c0d06caSMauro Carvalho Chehab 	orig_exposure = exposure = v4l2_ctrl_g_ctrl(gspca_dev->exposure);
300c0d06caSMauro Carvalho Chehab 
310c0d06caSMauro Carvalho Chehab 	/* If we are of a multiple of deadzone, do multiple steps to reach the
320c0d06caSMauro Carvalho Chehab 	   desired lumination fast (with the risc of a slight overshoot) */
330c0d06caSMauro Carvalho Chehab 	steps = abs(desired_avg_lum - avg_lum) / deadzone;
340c0d06caSMauro Carvalho Chehab 
3537d5efb0SJoe Perches 	gspca_dbg(gspca_dev, D_FRAM, "autogain: lum: %d, desired: %d, steps: %d\n",
360c0d06caSMauro Carvalho Chehab 		  avg_lum, desired_avg_lum, steps);
370c0d06caSMauro Carvalho Chehab 
380c0d06caSMauro Carvalho Chehab 	for (i = 0; i < steps; i++) {
390c0d06caSMauro Carvalho Chehab 		if (avg_lum > desired_avg_lum) {
400c0d06caSMauro Carvalho Chehab 			if (gain > gain_knee)
410c0d06caSMauro Carvalho Chehab 				gain--;
420c0d06caSMauro Carvalho Chehab 			else if (exposure > exposure_knee)
430c0d06caSMauro Carvalho Chehab 				exposure--;
440c0d06caSMauro Carvalho Chehab 			else if (gain > gspca_dev->gain->default_value)
450c0d06caSMauro Carvalho Chehab 				gain--;
460c0d06caSMauro Carvalho Chehab 			else if (exposure > gspca_dev->exposure->minimum)
470c0d06caSMauro Carvalho Chehab 				exposure--;
480c0d06caSMauro Carvalho Chehab 			else if (gain > gspca_dev->gain->minimum)
490c0d06caSMauro Carvalho Chehab 				gain--;
500c0d06caSMauro Carvalho Chehab 			else
510c0d06caSMauro Carvalho Chehab 				break;
520c0d06caSMauro Carvalho Chehab 		} else {
530c0d06caSMauro Carvalho Chehab 			if (gain < gspca_dev->gain->default_value)
540c0d06caSMauro Carvalho Chehab 				gain++;
550c0d06caSMauro Carvalho Chehab 			else if (exposure < exposure_knee)
560c0d06caSMauro Carvalho Chehab 				exposure++;
570c0d06caSMauro Carvalho Chehab 			else if (gain < gain_knee)
580c0d06caSMauro Carvalho Chehab 				gain++;
590c0d06caSMauro Carvalho Chehab 			else if (exposure < gspca_dev->exposure->maximum)
600c0d06caSMauro Carvalho Chehab 				exposure++;
610c0d06caSMauro Carvalho Chehab 			else if (gain < gspca_dev->gain->maximum)
620c0d06caSMauro Carvalho Chehab 				gain++;
630c0d06caSMauro Carvalho Chehab 			else
640c0d06caSMauro Carvalho Chehab 				break;
650c0d06caSMauro Carvalho Chehab 		}
660c0d06caSMauro Carvalho Chehab 	}
670c0d06caSMauro Carvalho Chehab 
680c0d06caSMauro Carvalho Chehab 	if (gain != orig_gain) {
690c0d06caSMauro Carvalho Chehab 		v4l2_ctrl_s_ctrl(gspca_dev->gain, gain);
700c0d06caSMauro Carvalho Chehab 		retval = 1;
710c0d06caSMauro Carvalho Chehab 	}
720c0d06caSMauro Carvalho Chehab 	if (exposure != orig_exposure) {
730c0d06caSMauro Carvalho Chehab 		v4l2_ctrl_s_ctrl(gspca_dev->exposure, exposure);
740c0d06caSMauro Carvalho Chehab 		retval = 1;
750c0d06caSMauro Carvalho Chehab 	}
760c0d06caSMauro Carvalho Chehab 
770c0d06caSMauro Carvalho Chehab 	if (retval)
7837d5efb0SJoe Perches 		gspca_dbg(gspca_dev, D_FRAM, "autogain: changed gain: %d, expo: %d\n",
790c0d06caSMauro Carvalho Chehab 			  gain, exposure);
800c0d06caSMauro Carvalho Chehab 	return retval;
810c0d06caSMauro Carvalho Chehab }
820c0d06caSMauro Carvalho Chehab EXPORT_SYMBOL(gspca_expo_autogain);
830c0d06caSMauro Carvalho Chehab 
840c0d06caSMauro Carvalho Chehab /* Autogain + exposure algorithm for cameras with a coarse exposure control
850c0d06caSMauro Carvalho Chehab    (usually this means we can only control the clockdiv to change exposure)
860c0d06caSMauro Carvalho Chehab    As changing the clockdiv so that the fps drops from 30 to 15 fps for
870c0d06caSMauro Carvalho Chehab    example, will lead to a huge exposure change (it effectively doubles),
880c0d06caSMauro Carvalho Chehab    this algorithm normally tries to only adjust the gain (between 40 and
890c0d06caSMauro Carvalho Chehab    80 %) and if that does not help, only then changes exposure. This leads
900c0d06caSMauro Carvalho Chehab    to a much more stable image then using the knee algorithm which at
910c0d06caSMauro Carvalho Chehab    certain points of the knee graph will only try to adjust exposure,
923e4d8f48SMauro Carvalho Chehab    which leads to oscillating as one exposure step is huge.
930c0d06caSMauro Carvalho Chehab 
940c0d06caSMauro Carvalho Chehab    Returns 0 if no changes were made, 1 if the gain and or exposure settings
950c0d06caSMauro Carvalho Chehab    where changed. */
gspca_coarse_grained_expo_autogain(struct gspca_dev * gspca_dev,int avg_lum,int desired_avg_lum,int deadzone)960c0d06caSMauro Carvalho Chehab int gspca_coarse_grained_expo_autogain(
970c0d06caSMauro Carvalho Chehab 			struct gspca_dev *gspca_dev,
980c0d06caSMauro Carvalho Chehab 			int avg_lum,
990c0d06caSMauro Carvalho Chehab 			int desired_avg_lum,
1000c0d06caSMauro Carvalho Chehab 			int deadzone)
1010c0d06caSMauro Carvalho Chehab {
1020c0d06caSMauro Carvalho Chehab 	s32 gain_low, gain_high, gain, orig_gain, exposure, orig_exposure;
1030c0d06caSMauro Carvalho Chehab 	int steps, retval = 0;
1040c0d06caSMauro Carvalho Chehab 
1050c0d06caSMauro Carvalho Chehab 	if (v4l2_ctrl_g_ctrl(gspca_dev->autogain) == 0)
1060c0d06caSMauro Carvalho Chehab 		return 0;
1070c0d06caSMauro Carvalho Chehab 
1080c0d06caSMauro Carvalho Chehab 	orig_gain = gain = v4l2_ctrl_g_ctrl(gspca_dev->gain);
1090c0d06caSMauro Carvalho Chehab 	orig_exposure = exposure = v4l2_ctrl_g_ctrl(gspca_dev->exposure);
1100c0d06caSMauro Carvalho Chehab 
1110d5e8c43SHans Verkuil 	gain_low  = (s32)(gspca_dev->gain->maximum - gspca_dev->gain->minimum) /
1120c0d06caSMauro Carvalho Chehab 		    5 * 2 + gspca_dev->gain->minimum;
1130d5e8c43SHans Verkuil 	gain_high = (s32)(gspca_dev->gain->maximum - gspca_dev->gain->minimum) /
1140c0d06caSMauro Carvalho Chehab 		    5 * 4 + gspca_dev->gain->minimum;
1150c0d06caSMauro Carvalho Chehab 
1160c0d06caSMauro Carvalho Chehab 	/* If we are of a multiple of deadzone, do multiple steps to reach the
1170c0d06caSMauro Carvalho Chehab 	   desired lumination fast (with the risc of a slight overshoot) */
1180c0d06caSMauro Carvalho Chehab 	steps = (desired_avg_lum - avg_lum) / deadzone;
1190c0d06caSMauro Carvalho Chehab 
12037d5efb0SJoe Perches 	gspca_dbg(gspca_dev, D_FRAM, "autogain: lum: %d, desired: %d, steps: %d\n",
1210c0d06caSMauro Carvalho Chehab 		  avg_lum, desired_avg_lum, steps);
1220c0d06caSMauro Carvalho Chehab 
1230c0d06caSMauro Carvalho Chehab 	if ((gain + steps) > gain_high &&
1240c0d06caSMauro Carvalho Chehab 	    exposure < gspca_dev->exposure->maximum) {
1250c0d06caSMauro Carvalho Chehab 		gain = gain_high;
1260c0d06caSMauro Carvalho Chehab 		gspca_dev->exp_too_low_cnt++;
1270c0d06caSMauro Carvalho Chehab 		gspca_dev->exp_too_high_cnt = 0;
1280c0d06caSMauro Carvalho Chehab 	} else if ((gain + steps) < gain_low &&
1290c0d06caSMauro Carvalho Chehab 		   exposure > gspca_dev->exposure->minimum) {
1300c0d06caSMauro Carvalho Chehab 		gain = gain_low;
1310c0d06caSMauro Carvalho Chehab 		gspca_dev->exp_too_high_cnt++;
1320c0d06caSMauro Carvalho Chehab 		gspca_dev->exp_too_low_cnt = 0;
1330c0d06caSMauro Carvalho Chehab 	} else {
1340c0d06caSMauro Carvalho Chehab 		gain += steps;
1350c0d06caSMauro Carvalho Chehab 		if (gain > gspca_dev->gain->maximum)
1360c0d06caSMauro Carvalho Chehab 			gain = gspca_dev->gain->maximum;
1370c0d06caSMauro Carvalho Chehab 		else if (gain < gspca_dev->gain->minimum)
1380c0d06caSMauro Carvalho Chehab 			gain = gspca_dev->gain->minimum;
1390c0d06caSMauro Carvalho Chehab 		gspca_dev->exp_too_high_cnt = 0;
1400c0d06caSMauro Carvalho Chehab 		gspca_dev->exp_too_low_cnt = 0;
1410c0d06caSMauro Carvalho Chehab 	}
1420c0d06caSMauro Carvalho Chehab 
1430c0d06caSMauro Carvalho Chehab 	if (gspca_dev->exp_too_high_cnt > 3) {
1440c0d06caSMauro Carvalho Chehab 		exposure--;
1450c0d06caSMauro Carvalho Chehab 		gspca_dev->exp_too_high_cnt = 0;
1460c0d06caSMauro Carvalho Chehab 	} else if (gspca_dev->exp_too_low_cnt > 3) {
1470c0d06caSMauro Carvalho Chehab 		exposure++;
1480c0d06caSMauro Carvalho Chehab 		gspca_dev->exp_too_low_cnt = 0;
1490c0d06caSMauro Carvalho Chehab 	}
1500c0d06caSMauro Carvalho Chehab 
1510c0d06caSMauro Carvalho Chehab 	if (gain != orig_gain) {
1520c0d06caSMauro Carvalho Chehab 		v4l2_ctrl_s_ctrl(gspca_dev->gain, gain);
1530c0d06caSMauro Carvalho Chehab 		retval = 1;
1540c0d06caSMauro Carvalho Chehab 	}
1550c0d06caSMauro Carvalho Chehab 	if (exposure != orig_exposure) {
1560c0d06caSMauro Carvalho Chehab 		v4l2_ctrl_s_ctrl(gspca_dev->exposure, exposure);
1570c0d06caSMauro Carvalho Chehab 		retval = 1;
1580c0d06caSMauro Carvalho Chehab 	}
1590c0d06caSMauro Carvalho Chehab 
1600c0d06caSMauro Carvalho Chehab 	if (retval)
16137d5efb0SJoe Perches 		gspca_dbg(gspca_dev, D_FRAM, "autogain: changed gain: %d, expo: %d\n",
1620c0d06caSMauro Carvalho Chehab 			  gain, exposure);
1630c0d06caSMauro Carvalho Chehab 	return retval;
1640c0d06caSMauro Carvalho Chehab }
1650c0d06caSMauro Carvalho Chehab EXPORT_SYMBOL(gspca_coarse_grained_expo_autogain);
166