diff options
Diffstat (limited to 'drivers/media/usb/gspca/autogain_functions.c')
-rw-r--r-- | drivers/media/usb/gspca/autogain_functions.c | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/drivers/media/usb/gspca/autogain_functions.c b/drivers/media/usb/gspca/autogain_functions.c new file mode 100644 index 0000000000..7ae7c43951 --- /dev/null +++ b/drivers/media/usb/gspca/autogain_functions.c @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Functions for auto gain. + * + * Copyright (C) 2010-2012 Hans de Goede <hdegoede@redhat.com> + */ +#include "gspca.h" + +/* 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_expo_autogain( + struct gspca_dev *gspca_dev, + int avg_lum, + int desired_avg_lum, + int deadzone, + int gain_knee, + int exposure_knee) +{ + s32 gain, orig_gain, exposure, orig_exposure; + int i, steps, retval = 0; + + if (v4l2_ctrl_g_ctrl(gspca_dev->autogain) == 0) + return 0; + + orig_gain = gain = v4l2_ctrl_g_ctrl(gspca_dev->gain); + orig_exposure = exposure = v4l2_ctrl_g_ctrl(gspca_dev->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; + + gspca_dbg(gspca_dev, 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 > gspca_dev->gain->default_value) + gain--; + else if (exposure > gspca_dev->exposure->minimum) + exposure--; + else if (gain > gspca_dev->gain->minimum) + gain--; + else + break; + } else { + if (gain < gspca_dev->gain->default_value) + gain++; + else if (exposure < exposure_knee) + exposure++; + else if (gain < gain_knee) + gain++; + else if (exposure < gspca_dev->exposure->maximum) + exposure++; + else if (gain < gspca_dev->gain->maximum) + gain++; + else + break; + } + } + + if (gain != orig_gain) { + v4l2_ctrl_s_ctrl(gspca_dev->gain, gain); + retval = 1; + } + if (exposure != orig_exposure) { + v4l2_ctrl_s_ctrl(gspca_dev->exposure, exposure); + retval = 1; + } + + if (retval) + gspca_dbg(gspca_dev, D_FRAM, "autogain: changed gain: %d, expo: %d\n", + gain, exposure); + return retval; +} +EXPORT_SYMBOL(gspca_expo_autogain); + +/* Autogain + exposure algorithm for cameras with a coarse exposure control + (usually this means we can only control the clockdiv to change exposure) + As changing the clockdiv so that the fps drops from 30 to 15 fps for + example, will lead to a huge exposure change (it effectively doubles), + this algorithm normally tries to only adjust the gain (between 40 and + 80 %) and if that does not help, only then changes exposure. This leads + to a much more stable image then using the knee algorithm which at + certain points of the knee graph will only try to adjust exposure, + which leads to oscillating as one exposure step is huge. + + Returns 0 if no changes were made, 1 if the gain and or exposure settings + where changed. */ +int gspca_coarse_grained_expo_autogain( + struct gspca_dev *gspca_dev, + int avg_lum, + int desired_avg_lum, + int deadzone) +{ + s32 gain_low, gain_high, gain, orig_gain, exposure, orig_exposure; + int steps, retval = 0; + + if (v4l2_ctrl_g_ctrl(gspca_dev->autogain) == 0) + return 0; + + orig_gain = gain = v4l2_ctrl_g_ctrl(gspca_dev->gain); + orig_exposure = exposure = v4l2_ctrl_g_ctrl(gspca_dev->exposure); + + gain_low = (s32)(gspca_dev->gain->maximum - gspca_dev->gain->minimum) / + 5 * 2 + gspca_dev->gain->minimum; + gain_high = (s32)(gspca_dev->gain->maximum - gspca_dev->gain->minimum) / + 5 * 4 + gspca_dev->gain->minimum; + + /* 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 = (desired_avg_lum - avg_lum) / deadzone; + + gspca_dbg(gspca_dev, D_FRAM, "autogain: lum: %d, desired: %d, steps: %d\n", + avg_lum, desired_avg_lum, steps); + + if ((gain + steps) > gain_high && + exposure < gspca_dev->exposure->maximum) { + gain = gain_high; + gspca_dev->exp_too_low_cnt++; + gspca_dev->exp_too_high_cnt = 0; + } else if ((gain + steps) < gain_low && + exposure > gspca_dev->exposure->minimum) { + gain = gain_low; + gspca_dev->exp_too_high_cnt++; + gspca_dev->exp_too_low_cnt = 0; + } else { + gain += steps; + if (gain > gspca_dev->gain->maximum) + gain = gspca_dev->gain->maximum; + else if (gain < gspca_dev->gain->minimum) + gain = gspca_dev->gain->minimum; + gspca_dev->exp_too_high_cnt = 0; + gspca_dev->exp_too_low_cnt = 0; + } + + if (gspca_dev->exp_too_high_cnt > 3) { + exposure--; + gspca_dev->exp_too_high_cnt = 0; + } else if (gspca_dev->exp_too_low_cnt > 3) { + exposure++; + gspca_dev->exp_too_low_cnt = 0; + } + + if (gain != orig_gain) { + v4l2_ctrl_s_ctrl(gspca_dev->gain, gain); + retval = 1; + } + if (exposure != orig_exposure) { + v4l2_ctrl_s_ctrl(gspca_dev->exposure, exposure); + retval = 1; + } + + if (retval) + gspca_dbg(gspca_dev, D_FRAM, "autogain: changed gain: %d, expo: %d\n", + gain, exposure); + return retval; +} +EXPORT_SYMBOL(gspca_coarse_grained_expo_autogain); |