162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Functions for auto gain. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2010-2012 Hans de Goede <hdegoede@redhat.com> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci#include "gspca.h" 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci/* auto gain and exposure algorithm based on the knee algorithm described here: 1062306a36Sopenharmony_ci http://ytse.tricolour.net/docs/LowLightOptimization.html 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci Returns 0 if no changes were made, 1 if the gain and or exposure settings 1362306a36Sopenharmony_ci where changed. */ 1462306a36Sopenharmony_ciint gspca_expo_autogain( 1562306a36Sopenharmony_ci struct gspca_dev *gspca_dev, 1662306a36Sopenharmony_ci int avg_lum, 1762306a36Sopenharmony_ci int desired_avg_lum, 1862306a36Sopenharmony_ci int deadzone, 1962306a36Sopenharmony_ci int gain_knee, 2062306a36Sopenharmony_ci int exposure_knee) 2162306a36Sopenharmony_ci{ 2262306a36Sopenharmony_ci s32 gain, orig_gain, exposure, orig_exposure; 2362306a36Sopenharmony_ci int i, steps, retval = 0; 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci if (v4l2_ctrl_g_ctrl(gspca_dev->autogain) == 0) 2662306a36Sopenharmony_ci return 0; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci orig_gain = gain = v4l2_ctrl_g_ctrl(gspca_dev->gain); 2962306a36Sopenharmony_ci orig_exposure = exposure = v4l2_ctrl_g_ctrl(gspca_dev->exposure); 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci /* If we are of a multiple of deadzone, do multiple steps to reach the 3262306a36Sopenharmony_ci desired lumination fast (with the risc of a slight overshoot) */ 3362306a36Sopenharmony_ci steps = abs(desired_avg_lum - avg_lum) / deadzone; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci gspca_dbg(gspca_dev, D_FRAM, "autogain: lum: %d, desired: %d, steps: %d\n", 3662306a36Sopenharmony_ci avg_lum, desired_avg_lum, steps); 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci for (i = 0; i < steps; i++) { 3962306a36Sopenharmony_ci if (avg_lum > desired_avg_lum) { 4062306a36Sopenharmony_ci if (gain > gain_knee) 4162306a36Sopenharmony_ci gain--; 4262306a36Sopenharmony_ci else if (exposure > exposure_knee) 4362306a36Sopenharmony_ci exposure--; 4462306a36Sopenharmony_ci else if (gain > gspca_dev->gain->default_value) 4562306a36Sopenharmony_ci gain--; 4662306a36Sopenharmony_ci else if (exposure > gspca_dev->exposure->minimum) 4762306a36Sopenharmony_ci exposure--; 4862306a36Sopenharmony_ci else if (gain > gspca_dev->gain->minimum) 4962306a36Sopenharmony_ci gain--; 5062306a36Sopenharmony_ci else 5162306a36Sopenharmony_ci break; 5262306a36Sopenharmony_ci } else { 5362306a36Sopenharmony_ci if (gain < gspca_dev->gain->default_value) 5462306a36Sopenharmony_ci gain++; 5562306a36Sopenharmony_ci else if (exposure < exposure_knee) 5662306a36Sopenharmony_ci exposure++; 5762306a36Sopenharmony_ci else if (gain < gain_knee) 5862306a36Sopenharmony_ci gain++; 5962306a36Sopenharmony_ci else if (exposure < gspca_dev->exposure->maximum) 6062306a36Sopenharmony_ci exposure++; 6162306a36Sopenharmony_ci else if (gain < gspca_dev->gain->maximum) 6262306a36Sopenharmony_ci gain++; 6362306a36Sopenharmony_ci else 6462306a36Sopenharmony_ci break; 6562306a36Sopenharmony_ci } 6662306a36Sopenharmony_ci } 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci if (gain != orig_gain) { 6962306a36Sopenharmony_ci v4l2_ctrl_s_ctrl(gspca_dev->gain, gain); 7062306a36Sopenharmony_ci retval = 1; 7162306a36Sopenharmony_ci } 7262306a36Sopenharmony_ci if (exposure != orig_exposure) { 7362306a36Sopenharmony_ci v4l2_ctrl_s_ctrl(gspca_dev->exposure, exposure); 7462306a36Sopenharmony_ci retval = 1; 7562306a36Sopenharmony_ci } 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci if (retval) 7862306a36Sopenharmony_ci gspca_dbg(gspca_dev, D_FRAM, "autogain: changed gain: %d, expo: %d\n", 7962306a36Sopenharmony_ci gain, exposure); 8062306a36Sopenharmony_ci return retval; 8162306a36Sopenharmony_ci} 8262306a36Sopenharmony_ciEXPORT_SYMBOL(gspca_expo_autogain); 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci/* Autogain + exposure algorithm for cameras with a coarse exposure control 8562306a36Sopenharmony_ci (usually this means we can only control the clockdiv to change exposure) 8662306a36Sopenharmony_ci As changing the clockdiv so that the fps drops from 30 to 15 fps for 8762306a36Sopenharmony_ci example, will lead to a huge exposure change (it effectively doubles), 8862306a36Sopenharmony_ci this algorithm normally tries to only adjust the gain (between 40 and 8962306a36Sopenharmony_ci 80 %) and if that does not help, only then changes exposure. This leads 9062306a36Sopenharmony_ci to a much more stable image then using the knee algorithm which at 9162306a36Sopenharmony_ci certain points of the knee graph will only try to adjust exposure, 9262306a36Sopenharmony_ci which leads to oscillating as one exposure step is huge. 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci Returns 0 if no changes were made, 1 if the gain and or exposure settings 9562306a36Sopenharmony_ci where changed. */ 9662306a36Sopenharmony_ciint gspca_coarse_grained_expo_autogain( 9762306a36Sopenharmony_ci struct gspca_dev *gspca_dev, 9862306a36Sopenharmony_ci int avg_lum, 9962306a36Sopenharmony_ci int desired_avg_lum, 10062306a36Sopenharmony_ci int deadzone) 10162306a36Sopenharmony_ci{ 10262306a36Sopenharmony_ci s32 gain_low, gain_high, gain, orig_gain, exposure, orig_exposure; 10362306a36Sopenharmony_ci int steps, retval = 0; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci if (v4l2_ctrl_g_ctrl(gspca_dev->autogain) == 0) 10662306a36Sopenharmony_ci return 0; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci orig_gain = gain = v4l2_ctrl_g_ctrl(gspca_dev->gain); 10962306a36Sopenharmony_ci orig_exposure = exposure = v4l2_ctrl_g_ctrl(gspca_dev->exposure); 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci gain_low = (s32)(gspca_dev->gain->maximum - gspca_dev->gain->minimum) / 11262306a36Sopenharmony_ci 5 * 2 + gspca_dev->gain->minimum; 11362306a36Sopenharmony_ci gain_high = (s32)(gspca_dev->gain->maximum - gspca_dev->gain->minimum) / 11462306a36Sopenharmony_ci 5 * 4 + gspca_dev->gain->minimum; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci /* If we are of a multiple of deadzone, do multiple steps to reach the 11762306a36Sopenharmony_ci desired lumination fast (with the risc of a slight overshoot) */ 11862306a36Sopenharmony_ci steps = (desired_avg_lum - avg_lum) / deadzone; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci gspca_dbg(gspca_dev, D_FRAM, "autogain: lum: %d, desired: %d, steps: %d\n", 12162306a36Sopenharmony_ci avg_lum, desired_avg_lum, steps); 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci if ((gain + steps) > gain_high && 12462306a36Sopenharmony_ci exposure < gspca_dev->exposure->maximum) { 12562306a36Sopenharmony_ci gain = gain_high; 12662306a36Sopenharmony_ci gspca_dev->exp_too_low_cnt++; 12762306a36Sopenharmony_ci gspca_dev->exp_too_high_cnt = 0; 12862306a36Sopenharmony_ci } else if ((gain + steps) < gain_low && 12962306a36Sopenharmony_ci exposure > gspca_dev->exposure->minimum) { 13062306a36Sopenharmony_ci gain = gain_low; 13162306a36Sopenharmony_ci gspca_dev->exp_too_high_cnt++; 13262306a36Sopenharmony_ci gspca_dev->exp_too_low_cnt = 0; 13362306a36Sopenharmony_ci } else { 13462306a36Sopenharmony_ci gain += steps; 13562306a36Sopenharmony_ci if (gain > gspca_dev->gain->maximum) 13662306a36Sopenharmony_ci gain = gspca_dev->gain->maximum; 13762306a36Sopenharmony_ci else if (gain < gspca_dev->gain->minimum) 13862306a36Sopenharmony_ci gain = gspca_dev->gain->minimum; 13962306a36Sopenharmony_ci gspca_dev->exp_too_high_cnt = 0; 14062306a36Sopenharmony_ci gspca_dev->exp_too_low_cnt = 0; 14162306a36Sopenharmony_ci } 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci if (gspca_dev->exp_too_high_cnt > 3) { 14462306a36Sopenharmony_ci exposure--; 14562306a36Sopenharmony_ci gspca_dev->exp_too_high_cnt = 0; 14662306a36Sopenharmony_ci } else if (gspca_dev->exp_too_low_cnt > 3) { 14762306a36Sopenharmony_ci exposure++; 14862306a36Sopenharmony_ci gspca_dev->exp_too_low_cnt = 0; 14962306a36Sopenharmony_ci } 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci if (gain != orig_gain) { 15262306a36Sopenharmony_ci v4l2_ctrl_s_ctrl(gspca_dev->gain, gain); 15362306a36Sopenharmony_ci retval = 1; 15462306a36Sopenharmony_ci } 15562306a36Sopenharmony_ci if (exposure != orig_exposure) { 15662306a36Sopenharmony_ci v4l2_ctrl_s_ctrl(gspca_dev->exposure, exposure); 15762306a36Sopenharmony_ci retval = 1; 15862306a36Sopenharmony_ci } 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci if (retval) 16162306a36Sopenharmony_ci gspca_dbg(gspca_dev, D_FRAM, "autogain: changed gain: %d, expo: %d\n", 16262306a36Sopenharmony_ci gain, exposure); 16362306a36Sopenharmony_ci return retval; 16462306a36Sopenharmony_ci} 16562306a36Sopenharmony_ciEXPORT_SYMBOL(gspca_coarse_grained_expo_autogain); 166