18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Functions for auto gain. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2010-2012 Hans de Goede <hdegoede@redhat.com> 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci#include "gspca.h" 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci/* auto gain and exposure algorithm based on the knee algorithm described here: 108c2ecf20Sopenharmony_ci http://ytse.tricolour.net/docs/LowLightOptimization.html 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci Returns 0 if no changes were made, 1 if the gain and or exposure settings 138c2ecf20Sopenharmony_ci where changed. */ 148c2ecf20Sopenharmony_ciint gspca_expo_autogain( 158c2ecf20Sopenharmony_ci struct gspca_dev *gspca_dev, 168c2ecf20Sopenharmony_ci int avg_lum, 178c2ecf20Sopenharmony_ci int desired_avg_lum, 188c2ecf20Sopenharmony_ci int deadzone, 198c2ecf20Sopenharmony_ci int gain_knee, 208c2ecf20Sopenharmony_ci int exposure_knee) 218c2ecf20Sopenharmony_ci{ 228c2ecf20Sopenharmony_ci s32 gain, orig_gain, exposure, orig_exposure; 238c2ecf20Sopenharmony_ci int i, steps, retval = 0; 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci if (v4l2_ctrl_g_ctrl(gspca_dev->autogain) == 0) 268c2ecf20Sopenharmony_ci return 0; 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci orig_gain = gain = v4l2_ctrl_g_ctrl(gspca_dev->gain); 298c2ecf20Sopenharmony_ci orig_exposure = exposure = v4l2_ctrl_g_ctrl(gspca_dev->exposure); 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci /* If we are of a multiple of deadzone, do multiple steps to reach the 328c2ecf20Sopenharmony_ci desired lumination fast (with the risc of a slight overshoot) */ 338c2ecf20Sopenharmony_ci steps = abs(desired_avg_lum - avg_lum) / deadzone; 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci gspca_dbg(gspca_dev, D_FRAM, "autogain: lum: %d, desired: %d, steps: %d\n", 368c2ecf20Sopenharmony_ci avg_lum, desired_avg_lum, steps); 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci for (i = 0; i < steps; i++) { 398c2ecf20Sopenharmony_ci if (avg_lum > desired_avg_lum) { 408c2ecf20Sopenharmony_ci if (gain > gain_knee) 418c2ecf20Sopenharmony_ci gain--; 428c2ecf20Sopenharmony_ci else if (exposure > exposure_knee) 438c2ecf20Sopenharmony_ci exposure--; 448c2ecf20Sopenharmony_ci else if (gain > gspca_dev->gain->default_value) 458c2ecf20Sopenharmony_ci gain--; 468c2ecf20Sopenharmony_ci else if (exposure > gspca_dev->exposure->minimum) 478c2ecf20Sopenharmony_ci exposure--; 488c2ecf20Sopenharmony_ci else if (gain > gspca_dev->gain->minimum) 498c2ecf20Sopenharmony_ci gain--; 508c2ecf20Sopenharmony_ci else 518c2ecf20Sopenharmony_ci break; 528c2ecf20Sopenharmony_ci } else { 538c2ecf20Sopenharmony_ci if (gain < gspca_dev->gain->default_value) 548c2ecf20Sopenharmony_ci gain++; 558c2ecf20Sopenharmony_ci else if (exposure < exposure_knee) 568c2ecf20Sopenharmony_ci exposure++; 578c2ecf20Sopenharmony_ci else if (gain < gain_knee) 588c2ecf20Sopenharmony_ci gain++; 598c2ecf20Sopenharmony_ci else if (exposure < gspca_dev->exposure->maximum) 608c2ecf20Sopenharmony_ci exposure++; 618c2ecf20Sopenharmony_ci else if (gain < gspca_dev->gain->maximum) 628c2ecf20Sopenharmony_ci gain++; 638c2ecf20Sopenharmony_ci else 648c2ecf20Sopenharmony_ci break; 658c2ecf20Sopenharmony_ci } 668c2ecf20Sopenharmony_ci } 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci if (gain != orig_gain) { 698c2ecf20Sopenharmony_ci v4l2_ctrl_s_ctrl(gspca_dev->gain, gain); 708c2ecf20Sopenharmony_ci retval = 1; 718c2ecf20Sopenharmony_ci } 728c2ecf20Sopenharmony_ci if (exposure != orig_exposure) { 738c2ecf20Sopenharmony_ci v4l2_ctrl_s_ctrl(gspca_dev->exposure, exposure); 748c2ecf20Sopenharmony_ci retval = 1; 758c2ecf20Sopenharmony_ci } 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci if (retval) 788c2ecf20Sopenharmony_ci gspca_dbg(gspca_dev, D_FRAM, "autogain: changed gain: %d, expo: %d\n", 798c2ecf20Sopenharmony_ci gain, exposure); 808c2ecf20Sopenharmony_ci return retval; 818c2ecf20Sopenharmony_ci} 828c2ecf20Sopenharmony_ciEXPORT_SYMBOL(gspca_expo_autogain); 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci/* Autogain + exposure algorithm for cameras with a coarse exposure control 858c2ecf20Sopenharmony_ci (usually this means we can only control the clockdiv to change exposure) 868c2ecf20Sopenharmony_ci As changing the clockdiv so that the fps drops from 30 to 15 fps for 878c2ecf20Sopenharmony_ci example, will lead to a huge exposure change (it effectively doubles), 888c2ecf20Sopenharmony_ci this algorithm normally tries to only adjust the gain (between 40 and 898c2ecf20Sopenharmony_ci 80 %) and if that does not help, only then changes exposure. This leads 908c2ecf20Sopenharmony_ci to a much more stable image then using the knee algorithm which at 918c2ecf20Sopenharmony_ci certain points of the knee graph will only try to adjust exposure, 928c2ecf20Sopenharmony_ci which leads to oscillating as one exposure step is huge. 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci Returns 0 if no changes were made, 1 if the gain and or exposure settings 958c2ecf20Sopenharmony_ci where changed. */ 968c2ecf20Sopenharmony_ciint gspca_coarse_grained_expo_autogain( 978c2ecf20Sopenharmony_ci struct gspca_dev *gspca_dev, 988c2ecf20Sopenharmony_ci int avg_lum, 998c2ecf20Sopenharmony_ci int desired_avg_lum, 1008c2ecf20Sopenharmony_ci int deadzone) 1018c2ecf20Sopenharmony_ci{ 1028c2ecf20Sopenharmony_ci s32 gain_low, gain_high, gain, orig_gain, exposure, orig_exposure; 1038c2ecf20Sopenharmony_ci int steps, retval = 0; 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci if (v4l2_ctrl_g_ctrl(gspca_dev->autogain) == 0) 1068c2ecf20Sopenharmony_ci return 0; 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci orig_gain = gain = v4l2_ctrl_g_ctrl(gspca_dev->gain); 1098c2ecf20Sopenharmony_ci orig_exposure = exposure = v4l2_ctrl_g_ctrl(gspca_dev->exposure); 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci gain_low = (s32)(gspca_dev->gain->maximum - gspca_dev->gain->minimum) / 1128c2ecf20Sopenharmony_ci 5 * 2 + gspca_dev->gain->minimum; 1138c2ecf20Sopenharmony_ci gain_high = (s32)(gspca_dev->gain->maximum - gspca_dev->gain->minimum) / 1148c2ecf20Sopenharmony_ci 5 * 4 + gspca_dev->gain->minimum; 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci /* If we are of a multiple of deadzone, do multiple steps to reach the 1178c2ecf20Sopenharmony_ci desired lumination fast (with the risc of a slight overshoot) */ 1188c2ecf20Sopenharmony_ci steps = (desired_avg_lum - avg_lum) / deadzone; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci gspca_dbg(gspca_dev, D_FRAM, "autogain: lum: %d, desired: %d, steps: %d\n", 1218c2ecf20Sopenharmony_ci avg_lum, desired_avg_lum, steps); 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci if ((gain + steps) > gain_high && 1248c2ecf20Sopenharmony_ci exposure < gspca_dev->exposure->maximum) { 1258c2ecf20Sopenharmony_ci gain = gain_high; 1268c2ecf20Sopenharmony_ci gspca_dev->exp_too_low_cnt++; 1278c2ecf20Sopenharmony_ci gspca_dev->exp_too_high_cnt = 0; 1288c2ecf20Sopenharmony_ci } else if ((gain + steps) < gain_low && 1298c2ecf20Sopenharmony_ci exposure > gspca_dev->exposure->minimum) { 1308c2ecf20Sopenharmony_ci gain = gain_low; 1318c2ecf20Sopenharmony_ci gspca_dev->exp_too_high_cnt++; 1328c2ecf20Sopenharmony_ci gspca_dev->exp_too_low_cnt = 0; 1338c2ecf20Sopenharmony_ci } else { 1348c2ecf20Sopenharmony_ci gain += steps; 1358c2ecf20Sopenharmony_ci if (gain > gspca_dev->gain->maximum) 1368c2ecf20Sopenharmony_ci gain = gspca_dev->gain->maximum; 1378c2ecf20Sopenharmony_ci else if (gain < gspca_dev->gain->minimum) 1388c2ecf20Sopenharmony_ci gain = gspca_dev->gain->minimum; 1398c2ecf20Sopenharmony_ci gspca_dev->exp_too_high_cnt = 0; 1408c2ecf20Sopenharmony_ci gspca_dev->exp_too_low_cnt = 0; 1418c2ecf20Sopenharmony_ci } 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci if (gspca_dev->exp_too_high_cnt > 3) { 1448c2ecf20Sopenharmony_ci exposure--; 1458c2ecf20Sopenharmony_ci gspca_dev->exp_too_high_cnt = 0; 1468c2ecf20Sopenharmony_ci } else if (gspca_dev->exp_too_low_cnt > 3) { 1478c2ecf20Sopenharmony_ci exposure++; 1488c2ecf20Sopenharmony_ci gspca_dev->exp_too_low_cnt = 0; 1498c2ecf20Sopenharmony_ci } 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci if (gain != orig_gain) { 1528c2ecf20Sopenharmony_ci v4l2_ctrl_s_ctrl(gspca_dev->gain, gain); 1538c2ecf20Sopenharmony_ci retval = 1; 1548c2ecf20Sopenharmony_ci } 1558c2ecf20Sopenharmony_ci if (exposure != orig_exposure) { 1568c2ecf20Sopenharmony_ci v4l2_ctrl_s_ctrl(gspca_dev->exposure, exposure); 1578c2ecf20Sopenharmony_ci retval = 1; 1588c2ecf20Sopenharmony_ci } 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci if (retval) 1618c2ecf20Sopenharmony_ci gspca_dbg(gspca_dev, D_FRAM, "autogain: changed gain: %d, expo: %d\n", 1628c2ecf20Sopenharmony_ci gain, exposure); 1638c2ecf20Sopenharmony_ci return retval; 1648c2ecf20Sopenharmony_ci} 1658c2ecf20Sopenharmony_ciEXPORT_SYMBOL(gspca_coarse_grained_expo_autogain); 166