1 /*
2  * Functions for auto gain.
3  *
4  * Copyright (C) 2010-2012 Hans de Goede <hdegoede@redhat.com>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  */
16 #include "gspca.h"
17 
18 /* auto gain and exposure algorithm based on the knee algorithm described here:
19    http://ytse.tricolour.net/docs/LowLightOptimization.html
20 
21    Returns 0 if no changes were made, 1 if the gain and or exposure settings
22    where changed. */
23 int gspca_expo_autogain(
24 			struct gspca_dev *gspca_dev,
25 			int avg_lum,
26 			int desired_avg_lum,
27 			int deadzone,
28 			int gain_knee,
29 			int exposure_knee)
30 {
31 	s32 gain, orig_gain, exposure, orig_exposure;
32 	int i, steps, retval = 0;
33 
34 	if (v4l2_ctrl_g_ctrl(gspca_dev->autogain) == 0)
35 	        return 0;
36 
37 	orig_gain = gain = v4l2_ctrl_g_ctrl(gspca_dev->gain);
38 	orig_exposure = exposure = v4l2_ctrl_g_ctrl(gspca_dev->exposure);
39 
40 	/* If we are of a multiple of deadzone, do multiple steps to reach the
41 	   desired lumination fast (with the risc of a slight overshoot) */
42 	steps = abs(desired_avg_lum - avg_lum) / deadzone;
43 
44 	PDEBUG(D_FRAM, "autogain: lum: %d, desired: %d, steps: %d",
45 		avg_lum, desired_avg_lum, steps);
46 
47 	for (i = 0; i < steps; i++) {
48 		if (avg_lum > desired_avg_lum) {
49 			if (gain > gain_knee)
50 				gain--;
51 			else if (exposure > exposure_knee)
52 				exposure--;
53 			else if (gain > gspca_dev->gain->default_value)
54 				gain--;
55 			else if (exposure > gspca_dev->exposure->minimum)
56 				exposure--;
57 			else if (gain > gspca_dev->gain->minimum)
58 				gain--;
59 			else
60 				break;
61 		} else {
62 			if (gain < gspca_dev->gain->default_value)
63 				gain++;
64 			else if (exposure < exposure_knee)
65 				exposure++;
66 			else if (gain < gain_knee)
67 				gain++;
68 			else if (exposure < gspca_dev->exposure->maximum)
69 				exposure++;
70 			else if (gain < gspca_dev->gain->maximum)
71 				gain++;
72 			else
73 				break;
74 		}
75 	}
76 
77 	if (gain != orig_gain) {
78 	        v4l2_ctrl_s_ctrl(gspca_dev->gain, gain);
79 		retval = 1;
80 	}
81 	if (exposure != orig_exposure) {
82 	        v4l2_ctrl_s_ctrl(gspca_dev->exposure, exposure);
83 		retval = 1;
84 	}
85 
86 	if (retval)
87 		PDEBUG(D_FRAM, "autogain: changed gain: %d, expo: %d",
88 			gain, exposure);
89 	return retval;
90 }
91 EXPORT_SYMBOL(gspca_expo_autogain);
92 
93 /* Autogain + exposure algorithm for cameras with a coarse exposure control
94    (usually this means we can only control the clockdiv to change exposure)
95    As changing the clockdiv so that the fps drops from 30 to 15 fps for
96    example, will lead to a huge exposure change (it effectively doubles),
97    this algorithm normally tries to only adjust the gain (between 40 and
98    80 %) and if that does not help, only then changes exposure. This leads
99    to a much more stable image then using the knee algorithm which at
100    certain points of the knee graph will only try to adjust exposure,
101    which leads to oscilating as one exposure step is huge.
102 
103    Returns 0 if no changes were made, 1 if the gain and or exposure settings
104    where changed. */
105 int gspca_coarse_grained_expo_autogain(
106 			struct gspca_dev *gspca_dev,
107 			int avg_lum,
108 			int desired_avg_lum,
109 			int deadzone)
110 {
111 	s32 gain_low, gain_high, gain, orig_gain, exposure, orig_exposure;
112 	int steps, retval = 0;
113 
114 	if (v4l2_ctrl_g_ctrl(gspca_dev->autogain) == 0)
115 	        return 0;
116 
117 	orig_gain = gain = v4l2_ctrl_g_ctrl(gspca_dev->gain);
118 	orig_exposure = exposure = v4l2_ctrl_g_ctrl(gspca_dev->exposure);
119 
120 	gain_low  = (s32)(gspca_dev->gain->maximum - gspca_dev->gain->minimum) /
121 		    5 * 2 + gspca_dev->gain->minimum;
122 	gain_high = (s32)(gspca_dev->gain->maximum - gspca_dev->gain->minimum) /
123 		    5 * 4 + gspca_dev->gain->minimum;
124 
125 	/* If we are of a multiple of deadzone, do multiple steps to reach the
126 	   desired lumination fast (with the risc of a slight overshoot) */
127 	steps = (desired_avg_lum - avg_lum) / deadzone;
128 
129 	PDEBUG(D_FRAM, "autogain: lum: %d, desired: %d, steps: %d",
130 		avg_lum, desired_avg_lum, steps);
131 
132 	if ((gain + steps) > gain_high &&
133 	    exposure < gspca_dev->exposure->maximum) {
134 		gain = gain_high;
135 		gspca_dev->exp_too_low_cnt++;
136 		gspca_dev->exp_too_high_cnt = 0;
137 	} else if ((gain + steps) < gain_low &&
138 		   exposure > gspca_dev->exposure->minimum) {
139 		gain = gain_low;
140 		gspca_dev->exp_too_high_cnt++;
141 		gspca_dev->exp_too_low_cnt = 0;
142 	} else {
143 		gain += steps;
144 		if (gain > gspca_dev->gain->maximum)
145 			gain = gspca_dev->gain->maximum;
146 		else if (gain < gspca_dev->gain->minimum)
147 			gain = gspca_dev->gain->minimum;
148 		gspca_dev->exp_too_high_cnt = 0;
149 		gspca_dev->exp_too_low_cnt = 0;
150 	}
151 
152 	if (gspca_dev->exp_too_high_cnt > 3) {
153 		exposure--;
154 		gspca_dev->exp_too_high_cnt = 0;
155 	} else if (gspca_dev->exp_too_low_cnt > 3) {
156 		exposure++;
157 		gspca_dev->exp_too_low_cnt = 0;
158 	}
159 
160 	if (gain != orig_gain) {
161 	        v4l2_ctrl_s_ctrl(gspca_dev->gain, gain);
162 		retval = 1;
163 	}
164 	if (exposure != orig_exposure) {
165 	        v4l2_ctrl_s_ctrl(gspca_dev->exposure, exposure);
166 		retval = 1;
167 	}
168 
169 	if (retval)
170 		PDEBUG(D_FRAM, "autogain: changed gain: %d, expo: %d",
171 			gain, exposure);
172 	return retval;
173 }
174 EXPORT_SYMBOL(gspca_coarse_grained_expo_autogain);
175