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