/* SPDX-License-Identifier: BSD-2-Clause */
/*
 * Copyright (C) 2019, Raspberry Pi (Trading) Limited
 *
 * cam_helper_imx219.cpp - camera helper for imx219 sensor
 */

#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

/*
 * We have observed that the imx219 embedded data stream randomly returns junk
 * register values. Do not rely on embedded data until this has been resolved.
 */
#define ENABLE_EMBEDDED_DATA 0

#include "cam_helper.hpp"
#if ENABLE_EMBEDDED_DATA
#include "md_parser.hpp"
#endif

using namespace RPiController;

/* Metadata parser implementation specific to Sony IMX219 sensors. */

class MdParserImx219 : public MdParserSmia
{
public:
	MdParserImx219();
	Status Parse(libcamera::Span<const uint8_t> buffer) override;
	Status GetExposureLines(unsigned int &lines) override;
	Status GetGainCode(unsigned int &gain_code) override;
private:
	/* Offset of the register's value in the metadata block. */
	int reg_offsets_[3];
	/* Value of the register, once read from the metadata block. */
	int reg_values_[3];
};

class CamHelperImx219 : public CamHelper
{
public:
	CamHelperImx219();
	uint32_t GainCode(double gain) const override;
	double Gain(uint32_t gain_code) const override;
	unsigned int MistrustFramesModeSwitch() const override;
	bool SensorEmbeddedDataPresent() const override;

private:
	/*
	 * Smallest difference between the frame length and integration time,
	 * in units of lines.
	 */
	static constexpr int frameIntegrationDiff = 4;
};

CamHelperImx219::CamHelperImx219()
#if ENABLE_EMBEDDED_DATA
	: CamHelper(new MdParserImx219(), frameIntegrationDiff)
#else
	: CamHelper(nullptr, frameIntegrationDiff)
#endif
{
}

uint32_t CamHelperImx219::GainCode(double gain) const
{
	return (uint32_t)(256 - 256 / gain);
}

double CamHelperImx219::Gain(uint32_t gain_code) const
{
	return 256.0 / (256 - gain_code);
}

unsigned int CamHelperImx219::MistrustFramesModeSwitch() const
{
	/*
	 * For reasons unknown, we do occasionally get a bogus metadata frame
	 * at a mode switch (though not at start-up). Possibly warrants some
	 * investigation, though not a big deal.
	 */
	return 1;
}

bool CamHelperImx219::SensorEmbeddedDataPresent() const
{
	return ENABLE_EMBEDDED_DATA;
}

static CamHelper *Create()
{
	return new CamHelperImx219();
}

static RegisterCamHelper reg("imx219", &Create);

/*
 * We care about one gain register and a pair of exposure registers. Their I2C
 * addresses from the Sony IMX219 datasheet:
 */
#define GAIN_REG 0x157
#define EXPHI_REG 0x15A
#define EXPLO_REG 0x15B

/*
 * Index of each into the reg_offsets and reg_values arrays. Must be in
 * register address order.
 */
#define GAIN_INDEX 0
#define EXPHI_INDEX 1
#define EXPLO_INDEX 2

MdParserImx219::MdParserImx219()
{
	reg_offsets_[0] = reg_offsets_[1] = reg_offsets_[2] = -1;
}

MdParser::Status MdParserImx219::Parse(libcamera::Span<const uint8_t> buffer)
{
	bool try_again = false;

	if (reset_) {
		/*
		 * Search again through the metadata for the gain and exposure
		 * registers.
		 */
		assert(bits_per_pixel_);
		assert(num_lines_ || buffer_size_bytes_);
		/* Need to be ordered */
		uint32_t regs[3] = { GAIN_REG, EXPHI_REG, EXPLO_REG };
		reg_offsets_[0] = reg_offsets_[1] = reg_offsets_[2] = -1;
		int ret = static_cast<int>(findRegs(buffer,
						    regs, reg_offsets_, 3));
		/*
		 * > 0 means "worked partially but parse again next time",
		 * < 0 means "hard error".
		 */
		if (ret > 0)
			try_again = true;
		else if (ret < 0)
			return ERROR;
	}

	for (int i = 0; i < 3; i++) {
		if (reg_offsets_[i] == -1)
			continue;

		reg_values_[i] = buffer[reg_offsets_[i]];
	}

	/* Re-parse next time if we were unhappy in some way. */
	reset_ = try_again;

	return OK;
}

MdParser::Status MdParserImx219::GetExposureLines(unsigned int &lines)
{
	if (reg_offsets_[EXPHI_INDEX] == -1 || reg_offsets_[EXPLO_INDEX] == -1)
		return NOTFOUND;

	lines = reg_values_[EXPHI_INDEX] * 256 + reg_values_[EXPLO_INDEX];

	return OK;
}

MdParser::Status MdParserImx219::GetGainCode(unsigned int &gain_code)
{
	if (reg_offsets_[GAIN_INDEX] == -1)
		return NOTFOUND;

	gain_code = reg_values_[GAIN_INDEX];

	return OK;
}