/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
 * Copyright (C) 2021, Google Inc.
 *
 * camera_sensor_helper.cpp - Helper class that performs sensor-specific
 * parameter computations
 */
#include "camera_sensor_helper.h"

#include <cmath>

#include <libcamera/base/log.h>

/**
 * \file camera_sensor_helper.h
 * \brief Helper class that performs sensor-specific parameter computations
 *
 * Computation of sensor configuration parameters is a sensor specific
 * operation. Each CameraHelper derived class computes the value of
 * configuration parameters, for example the analogue gain value, using
 * sensor-specific functions and constants.
 *
 * Every subclass of CameraSensorHelper shall be registered with libipa using
 * the REGISTER_CAMERA_SENSOR_HELPER() macro.
 */

namespace libcamera {

LOG_DEFINE_CATEGORY(CameraSensorHelper)

namespace ipa {

/**
 * \class CameraSensorHelper
 * \brief Base class for computing sensor tuning parameters using
 * sensor-specific constants
 *
 * Instances derived from CameraSensorHelper class are sensor-specific.
 * Each supported sensor will have an associated base class defined.
 */

/**
 * \brief Construct a CameraSensorHelper instance
 *
 * CameraSensorHelper derived class instances shall never be constructed
 * manually but always through the CameraSensorHelperFactoryBase::create()
 * function.
 */

/**
 * \brief Compute gain code from the analogue gain absolute value
 * \param[in] gain The real gain to pass
 *
 * This function aims to abstract the calculation of the gain letting the IPA
 * use the real gain for its estimations.
 *
 * \return The gain code to pass to V4L2
 */
uint32_t CameraSensorHelper::gainCode(double gain) const
{
	const AnalogueGainConstants &k = gainConstants_;

	switch (gainType_) {
	case AnalogueGainLinear:
		ASSERT(k.linear.m0 == 0 || k.linear.m1 == 0);

		return (k.linear.c0 - k.linear.c1 * gain) /
		       (k.linear.m1 * gain - k.linear.m0);

	case AnalogueGainExponential:
		ASSERT(k.exp.a != 0 && k.exp.m != 0);

		return std::log2(gain / k.exp.a) / k.exp.m;

	default:
		ASSERT(false);
		return 0;
	}
}

/**
 * \brief Compute the real gain from the V4L2 subdev control gain code
 * \param[in] gainCode The V4L2 subdev control gain
 *
 * This function aims to abstract the calculation of the gain letting the IPA
 * use the real gain for its estimations. It is the counterpart of the function
 * CameraSensorHelper::gainCode.
 *
 * \return The real gain
 */
double CameraSensorHelper::gain(uint32_t gainCode) const
{
	const AnalogueGainConstants &k = gainConstants_;
	double gain = static_cast<double>(gainCode);

	switch (gainType_) {
	case AnalogueGainLinear:
		ASSERT(k.linear.m0 == 0 || k.linear.m1 == 0);

		return (k.linear.m0 * gain + k.linear.c0) /
		       (k.linear.m1 * gain + k.linear.c1);

	case AnalogueGainExponential:
		ASSERT(k.exp.a != 0 && k.exp.m != 0);

		return k.exp.a * std::exp2(k.exp.m * gain);

	default:
		ASSERT(false);
		return 0.0;
	}
}

/**
 * \enum CameraSensorHelper::AnalogueGainType
 * \brief The gain calculation modes as defined by the MIPI CCS
 *
 * Describes the image sensor analogue gain capabilities.
 * Two modes are possible, depending on the sensor: Linear and Exponential.
 */

/**
 * \var CameraSensorHelper::AnalogueGainLinear
 * \brief Gain is computed using linear gain estimation
 *
 * The relationship between the integer gain parameter and the resulting gain
 * multiplier is given by the following equation:
 *
 * \f$gain=\frac{m0x+c0}{m1x+c1}\f$
 *
 * Where 'x' is the gain control parameter, and m0, m1, c0 and c1 are
 * image-sensor-specific constants of the sensor.
 * These constants are static parameters, and for any given image sensor either
 * m0 or m1 shall be zero.
 *
 * The full Gain equation therefore reduces to either:
 *
 * \f$gain=\frac{c0}{m1x+c1}\f$ or \f$\frac{m0x+c0}{c1}\f$
 */

/**
 * \var CameraSensorHelper::AnalogueGainExponential
 * \brief Gain is expressed using an exponential model
 *
 * The relationship between the integer gain parameter and the resulting gain
 * multiplier is given by the following equation:
 *
 * \f$gain = a \cdot 2^{m \cdot x}\f$
 *
 * Where 'x' is the gain control parameter, and 'a' and 'm' are image
 * sensor-specific constants.
 *
 * This is a subset of the MIPI CCS exponential gain model with the linear
 * factor 'a' being a constant, but with the exponent being configurable
 * through the 'm' coefficient.
 *
 * When the gain is expressed in dB, 'a' is equal to 1 and 'm' to
 * \f$log_{2}{10^{\frac{1}{20}}}\f$.
 */

/**
 * \struct CameraSensorHelper::AnalogueGainLinearConstants
 * \brief Analogue gain constants for the linear gain model
 *
 * \var CameraSensorHelper::AnalogueGainLinearConstants::m0
 * \brief Constant used in the linear gain coding/decoding
 *
 * \note Either m0 or m1 shall be zero.
 *
 * \var CameraSensorHelper::AnalogueGainLinearConstants::c0
 * \brief Constant used in the linear gain coding/decoding
 *
 * \var CameraSensorHelper::AnalogueGainLinearConstants::m1
 * \brief Constant used in the linear gain coding/decoding
 *
 * \note Either m0 or m1 shall be zero.
 *
 * \var CameraSensorHelper::AnalogueGainLinearConstants::c1
 * \brief Constant used in the linear gain coding/decoding
 */

/**
 * \struct CameraSensorHelper::AnalogueGainExpConstants
 * \brief Analogue gain constants for the exponential gain model
 *
 * \var CameraSensorHelper::AnalogueGainExpConstants::a
 * \brief Constant used in the exponential gain coding/decoding
 *
 * \var CameraSensorHelper::AnalogueGainExpConstants::m
 * \brief Constant used in the exponential gain coding/decoding
 */

/**
 * \struct CameraSensorHelper::AnalogueGainConstants
 * \brief Analogue gain model constants
 *
 * This union stores the constants used to calculate the analogue gain. The
 * CameraSensorHelper::gainType_ variable selects which union member is valid.
 *
 * \var CameraSensorHelper::AnalogueGainConstants::linear
 * \brief Constants for the linear gain model
 *
 * \var CameraSensorHelper::AnalogueGainConstants::exp
 * \brief Constants for the exponential gain model
 */

/**
 * \var CameraSensorHelper::gainType_
 * \brief The analogue gain model type
 */

/**
 * \var CameraSensorHelper::gainConstants_
 * \brief The analogue gain parameters used for calculation
 *
 * The analogue gain is calculated through a formula, and its parameters are
 * sensor specific. Use this variable to store the values at init time.
 */

/**
 * \class CameraSensorHelperFactoryBase
 * \brief Base class for camera sensor helper factories
 *
 * The CameraSensorHelperFactoryBase class is the base of all specializations of
 * the CameraSensorHelperFactory class template. It implements the factory
 * registration, maintains a registry of factories, and provides access to the
 * registered factories.
 */

/**
 * \brief Construct a camera sensor helper factory base
 * \param[in] name Name of the camera sensor helper class
 *
 * Creating an instance of the factory base registers it with the global list of
 * factories, accessible through the factories() function.
 *
 * The factory \a name is used to look up factories and shall be unique.
 */
CameraSensorHelperFactoryBase::CameraSensorHelperFactoryBase(const std::string name)
	: name_(name)
{
	registerType(this);
}

/**
 * \brief Create an instance of the CameraSensorHelper corresponding to
 * a named factory
 * \param[in] name Name of the factory
 *
 * \return A unique pointer to a new instance of the CameraSensorHelper subclass
 * corresponding to the named factory or a null pointer if no such factory
 * exists
 */
std::unique_ptr<CameraSensorHelper> CameraSensorHelperFactoryBase::create(const std::string &name)
{
	const std::vector<CameraSensorHelperFactoryBase *> &factories =
		CameraSensorHelperFactoryBase::factories();

	for (const CameraSensorHelperFactoryBase *factory : factories) {
		if (name != factory->name_)
			continue;

		return factory->createInstance();
	}

	return nullptr;
}

/**
 * \brief Add a camera sensor helper class to the registry
 * \param[in] factory Factory to use to construct the camera sensor helper
 *
 * The caller is responsible to guarantee the uniqueness of the camera sensor
 * helper name.
 */
void CameraSensorHelperFactoryBase::registerType(CameraSensorHelperFactoryBase *factory)
{
	std::vector<CameraSensorHelperFactoryBase *> &factories =
		CameraSensorHelperFactoryBase::factories();

	factories.push_back(factory);
}

/**
 * \brief Retrieve the list of all camera sensor helper factories
 * \return The list of camera sensor helper factories
 */
std::vector<CameraSensorHelperFactoryBase *> &CameraSensorHelperFactoryBase::factories()
{
	/*
	 * The static factories map is defined inside the function to ensure
	 * it gets initialized on first use, without any dependency on link
	 * order.
	 */
	static std::vector<CameraSensorHelperFactoryBase *> factories;
	return factories;
}

/**
 * \class CameraSensorHelperFactory
 * \brief Registration of CameraSensorHelperFactory classes and creation of instances
 * \tparam _Helper The camera sensor helper class type for this factory
 *
 * To facilitate discovery and instantiation of CameraSensorHelper classes, the
 * CameraSensorHelperFactory class implements auto-registration of camera sensor
 * helpers. Each CameraSensorHelper subclass shall register itself using the
 * REGISTER_CAMERA_SENSOR_HELPER() macro, which will create a corresponding
 * instance of a CameraSensorHelperFactory subclass and register it with the
 * static list of factories.
 */

/**
 * \fn CameraSensorHelperFactory::CameraSensorHelperFactory(const char *name)
 * \brief Construct a camera sensor helper factory
 * \param[in] name Name of the camera sensor helper class
 *
 * Creating an instance of the factory registers it with the global list of
 * factories, accessible through the CameraSensorHelperFactoryBase::factories()
 * function.
 *
 * The factory \a name is used to look up factories and shall be unique.
 */

/**
 * \fn CameraSensorHelperFactory::createInstance() const
 * \brief Create an instance of the CameraSensorHelper corresponding to the
 * factory
 *
 * \return A unique pointer to a newly constructed instance of the
 * CameraSensorHelper subclass corresponding to the factory
 */

/**
 * \def REGISTER_CAMERA_SENSOR_HELPER
 * \brief Register a camera sensor helper with the camera sensor helper factory
 * \param[in] name Sensor model name used to register the class
 * \param[in] helper Class name of CameraSensorHelper derived class to register
 *
 * Register a CameraSensorHelper subclass with the factory and make it available
 * to try and match sensors.
 */

/* -----------------------------------------------------------------------------
 * Sensor-specific subclasses
 */

#ifndef __DOXYGEN__

/*
 * Helper function to compute the m parameter of the exponential gain model
 * when the gain code is expressed in dB.
 */
static constexpr double expGainDb(double step)
{
	constexpr double log2_10 = 3.321928094887362;

	/*
	 * The gain code is expressed in step * dB (e.g. in 0.1 dB steps):
	 *
	 * G_code = G_dB/step = 20/step*log10(G_linear)
	 *
	 * Inverting the formula, we get
	 *
	 * G_linear = 10^(step/20*G_code) = 2^(log2(10)*step/20*G_code)
	 */
	return log2_10 * step / 20;
}

class CameraSensorHelperAr0521 : public CameraSensorHelper
{
public:
	uint32_t gainCode(double gain) const override;
	double gain(uint32_t gainCode) const override;

private:
	static constexpr double kStep_ = 16;
};

uint32_t CameraSensorHelperAr0521::gainCode(double gain) const
{
	gain = std::clamp(gain, 1.0, 15.5);
	unsigned int coarse = std::log2(gain);
	unsigned int fine = (gain / (1 << coarse) - 1) * kStep_;

	return (coarse << 4) | (fine & 0xf);
}

double CameraSensorHelperAr0521::gain(uint32_t gainCode) const
{
	unsigned int coarse = gainCode >> 4;
	unsigned int fine = gainCode & 0xf;

	return (1 << coarse) * (1 + fine / kStep_);
}

REGISTER_CAMERA_SENSOR_HELPER("ar0521", CameraSensorHelperAr0521)

class CameraSensorHelperImx219 : public CameraSensorHelper
{
public:
	CameraSensorHelperImx219()
	{
		gainType_ = AnalogueGainLinear;
		gainConstants_.linear = { 0, 256, -1, 256 };
	}
};
REGISTER_CAMERA_SENSOR_HELPER("imx219", CameraSensorHelperImx219)

class CameraSensorHelperImx258 : public CameraSensorHelper
{
public:
	CameraSensorHelperImx258()
	{
		gainType_ = AnalogueGainLinear;
		gainConstants_.linear = { 0, 512, -1, 512 };
	}
};
REGISTER_CAMERA_SENSOR_HELPER("imx258", CameraSensorHelperImx258)

class CameraSensorHelperImx290 : public CameraSensorHelper
{
public:
	CameraSensorHelperImx290()
	{
		gainType_ = AnalogueGainExponential;
		gainConstants_.exp = { 1.0, expGainDb(0.3) };
	}
};
REGISTER_CAMERA_SENSOR_HELPER("imx290", CameraSensorHelperImx290)

class CameraSensorHelperImx296 : public CameraSensorHelper
{
public:
	CameraSensorHelperImx296()
	{
		gainType_ = AnalogueGainExponential;
		gainConstants_.exp = { 1.0, expGainDb(0.1) };
	}
};
REGISTER_CAMERA_SENSOR_HELPER("imx296", CameraSensorHelperImx296)

class CameraSensorHelperImx327 : public CameraSensorHelperImx290
{
};
REGISTER_CAMERA_SENSOR_HELPER("imx327", CameraSensorHelperImx327)

class CameraSensorHelperImx477 : public CameraSensorHelper
{
public:
	CameraSensorHelperImx477()
	{
		gainType_ = AnalogueGainLinear;
		gainConstants_.linear = { 0, 1024, -1, 1024 };
	}
};
REGISTER_CAMERA_SENSOR_HELPER("imx477", CameraSensorHelperImx477)

class CameraSensorHelperOv2685 : public CameraSensorHelper
{
public:
	CameraSensorHelperOv2685()
	{
		/*
		 * The Sensor Manual doesn't appear to document the gain model.
		 * This has been validated with some empirical testing only.
		 */
		gainType_ = AnalogueGainLinear;
		gainConstants_.linear = { 1, 0, 0, 128 };
	}
};
REGISTER_CAMERA_SENSOR_HELPER("ov2685", CameraSensorHelperOv2685)

class CameraSensorHelperOv2740 : public CameraSensorHelper
{
public:
	CameraSensorHelperOv2740()
	{
		gainType_ = AnalogueGainLinear;
		gainConstants_.linear = { 1, 0, 0, 128 };
	}
};
REGISTER_CAMERA_SENSOR_HELPER("ov2740", CameraSensorHelperOv2740)

class CameraSensorHelperOv4689 : public CameraSensorHelper
{
public:
	CameraSensorHelperOv4689()
	{
		gainType_ = AnalogueGainLinear;
		gainConstants_.linear = { 1, 0, 0, 128 };
	}
};
REGISTER_CAMERA_SENSOR_HELPER("ov4689", CameraSensorHelperOv4689)

class CameraSensorHelperOv5640 : public CameraSensorHelper
{
public:
	CameraSensorHelperOv5640()
	{
		gainType_ = AnalogueGainLinear;
		gainConstants_.linear = { 1, 0, 0, 16 };
	}
};
REGISTER_CAMERA_SENSOR_HELPER("ov5640", CameraSensorHelperOv5640)

class CameraSensorHelperOv5647 : public CameraSensorHelper
{
public:
	CameraSensorHelperOv5647()
	{
		gainType_ = AnalogueGainLinear;
		gainConstants_.linear = { 1, 0, 0, 16 };
	}
};
REGISTER_CAMERA_SENSOR_HELPER("ov5647", CameraSensorHelperOv5647)

class CameraSensorHelperOv5670 : public CameraSensorHelper
{
public:
	CameraSensorHelperOv5670()
	{
		gainType_ = AnalogueGainLinear;
		gainConstants_.linear = { 1, 0, 0, 128 };
	}
};
REGISTER_CAMERA_SENSOR_HELPER("ov5670", CameraSensorHelperOv5670)

class CameraSensorHelperOv5675 : public CameraSensorHelper
{
public:
	CameraSensorHelperOv5675()
	{
		gainType_ = AnalogueGainLinear;
		gainConstants_.linear = { 1, 0, 0, 128 };
	}
};
REGISTER_CAMERA_SENSOR_HELPER("ov5675", CameraSensorHelperOv5675)

class CameraSensorHelperOv5693 : public CameraSensorHelper
{
public:
	CameraSensorHelperOv5693()
	{
		gainType_ = AnalogueGainLinear;
		gainConstants_.linear = { 1, 0, 0, 16 };
	}
};
REGISTER_CAMERA_SENSOR_HELPER("ov5693", CameraSensorHelperOv5693)

class CameraSensorHelperOv64a40 : public CameraSensorHelper
{
public:
	CameraSensorHelperOv64a40()
	{
		gainType_ = AnalogueGainLinear;
		gainConstants_.linear = { 1, 0, 0, 128 };
	}
};
REGISTER_CAMERA_SENSOR_HELPER("ov64a40", CameraSensorHelperOv64a40)

class CameraSensorHelperOv8858 : public CameraSensorHelper
{
public:
	CameraSensorHelperOv8858()
	{
		gainType_ = AnalogueGainLinear;

		/*
		 * \todo Validate the selected 1/128 step value as it differs
		 * from what the sensor manual describes.
		 *
		 * See: https://patchwork.linuxtv.org/project/linux-media/patch/20221106171129.166892-2-nicholas@rothemail.net/#142267
		 */
		gainConstants_.linear = { 1, 0, 0, 128 };
	}
};
REGISTER_CAMERA_SENSOR_HELPER("ov8858", CameraSensorHelperOv8858)

class CameraSensorHelperOv8865 : public CameraSensorHelper
{
public:
	CameraSensorHelperOv8865()
	{
		gainType_ = AnalogueGainLinear;
		gainConstants_.linear = { 1, 0, 0, 128 };
	}
};
REGISTER_CAMERA_SENSOR_HELPER("ov8865", CameraSensorHelperOv8865)

class CameraSensorHelperOv13858 : public CameraSensorHelper
{
public:
	CameraSensorHelperOv13858()
	{
		gainType_ = AnalogueGainLinear;
		gainConstants_.linear = { 1, 0, 0, 128 };
	}
};
REGISTER_CAMERA_SENSOR_HELPER("ov13858", CameraSensorHelperOv13858)

#endif /* __DOXYGEN__ */

} /* namespace ipa */

} /* namespace libcamera */
