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