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