/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
 * Copyright (C) 2019, Google Inc.
 *
 * Pipeline handler for Rockchip ISP1
 */

#include <algorithm>
#include <map>
#include <memory>
#include <numeric>
#include <optional>
#include <queue>
#include <vector>

#include <linux/media-bus-format.h>
#include <linux/rkisp1-config.h>

#include <libcamera/base/file.h>
#include <libcamera/base/log.h>
#include <libcamera/base/utils.h>

#include <libcamera/camera.h>
#include <libcamera/color_space.h>
#include <libcamera/control_ids.h>
#include <libcamera/formats.h>
#include <libcamera/framebuffer.h>
#include <libcamera/property_ids.h>
#include <libcamera/stream.h>
#include <libcamera/transform.h>

#include <libcamera/ipa/core_ipa_interface.h>
#include <libcamera/ipa/rkisp1_ipa_interface.h>
#include <libcamera/ipa/rkisp1_ipa_proxy.h>

#include "libcamera/internal/camera.h"
#include "libcamera/internal/camera_sensor.h"
#include "libcamera/internal/camera_sensor_properties.h"
#include "libcamera/internal/converter/converter_dw100.h"
#include "libcamera/internal/delayed_controls.h"
#include "libcamera/internal/device_enumerator.h"
#include "libcamera/internal/framebuffer.h"
#include "libcamera/internal/media_device.h"
#include "libcamera/internal/media_pipeline.h"
#include "libcamera/internal/pipeline_handler.h"
#include "libcamera/internal/request.h"
#include "libcamera/internal/v4l2_subdevice.h"
#include "libcamera/internal/v4l2_videodevice.h"
#include "libcamera/internal/yaml_parser.h"

#include "rkisp1_path.h"

namespace libcamera {

LOG_DEFINE_CATEGORY(RkISP1)

class PipelineHandlerRkISP1;
class RkISP1CameraData;

struct RkISP1FrameInfo {
	unsigned int frame;
	Request *request;

	FrameBuffer *paramBuffer;
	FrameBuffer *statBuffer;
	FrameBuffer *mainPathBuffer;
	FrameBuffer *selfPathBuffer;

	bool paramDequeued;
	bool metadataProcessed;
};

class RkISP1Frames
{
public:
	RkISP1Frames(PipelineHandler *pipe);

	RkISP1FrameInfo *create(const RkISP1CameraData *data, Request *request,
				bool isRaw);
	int destroy(unsigned int frame);
	void clear();

	RkISP1FrameInfo *find(unsigned int frame);
	RkISP1FrameInfo *find(FrameBuffer *buffer);
	RkISP1FrameInfo *find(Request *request);

private:
	void recycleBuffers(const RkISP1FrameInfo &info);

	PipelineHandlerRkISP1 *pipe_;
	std::map<unsigned int, RkISP1FrameInfo> frameInfo_;
};

class RkISP1CameraData : public Camera::Private
{
public:
	RkISP1CameraData(PipelineHandler *pipe, RkISP1MainPath *mainPath,
			 RkISP1SelfPath *selfPath)
		: Camera::Private(pipe), frame_(0), frameInfo_(pipe),
		  mainPath_(mainPath), selfPath_(selfPath),
		  canUseDewarper_(false), usesDewarper_(false)
	{
	}

	PipelineHandlerRkISP1 *pipe();
	const PipelineHandlerRkISP1 *pipe() const;
	int loadIPA(unsigned int hwRevision, uint32_t supportedBlocks);

	Stream mainPathStream_;
	Stream selfPathStream_;
	std::unique_ptr<CameraSensor> sensor_;
	std::unique_ptr<DelayedControls> delayedCtrls_;
	unsigned int frame_;
	std::vector<IPABuffer> ipaBuffers_;
	RkISP1Frames frameInfo_;

	RkISP1MainPath *mainPath_;
	RkISP1SelfPath *selfPath_;

	std::unique_ptr<ipa::rkisp1::IPAProxyRkISP1> ipa_;

	ControlInfoMap ipaControls_;

	/*
	 * All entities in the pipeline, from the camera sensor to the RKISP1.
	 */
	MediaPipeline pipe_;

	bool canUseDewarper_;
	bool usesDewarper_;

private:
	void paramsComputed(unsigned int frame, unsigned int bytesused);
	void setSensorControls(unsigned int frame,
			       const ControlList &sensorControls);

	void metadataReady(unsigned int frame, const ControlList &metadata);
	int loadTuningFile(const std::string &file);
};

class RkISP1CameraConfiguration : public CameraConfiguration
{
public:
	RkISP1CameraConfiguration(Camera *camera, RkISP1CameraData *data);

	Status validate() override;

	const V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; }
	const Transform &combinedTransform() { return combinedTransform_; }

private:
	bool fitsAllPaths(const StreamConfiguration &cfg);

	/*
	 * The RkISP1CameraData instance is guaranteed to be valid as long as the
	 * corresponding Camera instance is valid. In order to borrow a
	 * reference to the camera data, store a new reference to the camera.
	 */
	std::shared_ptr<Camera> camera_;
	const RkISP1CameraData *data_;

	V4L2SubdeviceFormat sensorFormat_;
	Transform combinedTransform_;
};

namespace {

/*
 * Maximum number of requests that shall be queued into the pipeline to keep
 * the regulation fast.
 * \todo This needs revisiting as soon as buffers got decoupled from requests
 * and/or a fast path for controls was implemented.
 */
static constexpr unsigned int kRkISP1MaxQueuedRequests = 4;

/*
 * This many internal buffers (or rather parameter and statistics buffer
 * pairs) ensures that the pipeline runs smoothly, without frame drops.
 */
static constexpr unsigned int kRkISP1MinBufferCount = 4;

} /* namespace */

class PipelineHandlerRkISP1 : public PipelineHandler
{
public:
	PipelineHandlerRkISP1(CameraManager *manager);

	std::unique_ptr<CameraConfiguration> generateConfiguration(Camera *camera,
								   Span<const StreamRole> roles) override;
	int configure(Camera *camera, CameraConfiguration *config) override;

	int exportFrameBuffers(Camera *camera, Stream *stream,
			       std::vector<std::unique_ptr<FrameBuffer>> *buffers) override;

	int start(Camera *camera, const ControlList *controls) override;
	void stopDevice(Camera *camera) override;

	int queueRequestDevice(Camera *camera, Request *request) override;

	bool match(DeviceEnumerator *enumerator) override;

private:
	static constexpr Size kRkISP1PreviewSize = { 1920, 1080 };

	RkISP1CameraData *cameraData(Camera *camera)
	{
		return static_cast<RkISP1CameraData *>(camera->_d());
	}

	friend RkISP1CameraData;
	friend RkISP1CameraConfiguration;
	friend RkISP1Frames;

	int initLinks(Camera *camera, const RkISP1CameraConfiguration &config);
	int createCamera(MediaEntity *sensor);
	void tryCompleteRequest(RkISP1FrameInfo *info);
	void cancelDewarpRequest(RkISP1FrameInfo *info);
	void imageBufferReady(FrameBuffer *buffer);
	void paramBufferReady(FrameBuffer *buffer);
	void statBufferReady(FrameBuffer *buffer);
	void dewarpBufferReady(FrameBuffer *buffer);
	void frameStart(uint32_t sequence);

	int allocateBuffers(Camera *camera);
	int freeBuffers(Camera *camera);

	int updateControls(RkISP1CameraData *data);

	std::shared_ptr<MediaDevice> media_;
	std::unique_ptr<V4L2Subdevice> isp_;
	std::unique_ptr<V4L2VideoDevice> param_;
	std::unique_ptr<V4L2VideoDevice> stat_;

	bool hasSelfPath_;
	bool isRaw_;

	RkISP1MainPath mainPath_;
	RkISP1SelfPath selfPath_;

	std::unique_ptr<ConverterDW100Module> dewarper_;

	/* Internal buffers used when dewarper is being used */
	std::vector<std::unique_ptr<FrameBuffer>> mainPathBuffers_;
	std::queue<FrameBuffer *> availableMainPathBuffers_;

	std::vector<std::unique_ptr<FrameBuffer>> paramBuffers_;
	std::vector<std::unique_ptr<FrameBuffer>> statBuffers_;
	std::queue<FrameBuffer *> availableParamBuffers_;
	std::queue<FrameBuffer *> availableStatBuffers_;

	Camera *activeCamera_;
};

RkISP1Frames::RkISP1Frames(PipelineHandler *pipe)
	: pipe_(static_cast<PipelineHandlerRkISP1 *>(pipe))
{
}

RkISP1FrameInfo *RkISP1Frames::create(const RkISP1CameraData *data, Request *request,
				      bool isRaw)
{
	unsigned int frame = data->frame_;

	FrameBuffer *paramBuffer = nullptr;
	FrameBuffer *statBuffer = nullptr;
	FrameBuffer *mainPathBuffer = nullptr;
	FrameBuffer *selfPathBuffer = nullptr;

	if (!isRaw) {
		if (pipe_->availableParamBuffers_.empty()) {
			LOG(RkISP1, Error) << "Parameters buffer underrun";
			return nullptr;
		}

		if (pipe_->availableStatBuffers_.empty()) {
			LOG(RkISP1, Error) << "Statistic buffer underrun";
			return nullptr;
		}

		paramBuffer = pipe_->availableParamBuffers_.front();
		pipe_->availableParamBuffers_.pop();

		statBuffer = pipe_->availableStatBuffers_.front();
		pipe_->availableStatBuffers_.pop();

		if (data->usesDewarper_) {
			mainPathBuffer = pipe_->availableMainPathBuffers_.front();
			pipe_->availableMainPathBuffers_.pop();
		}
	}

	if (!mainPathBuffer)
		mainPathBuffer = request->findBuffer(&data->mainPathStream_);
	selfPathBuffer = request->findBuffer(&data->selfPathStream_);

	auto [it, inserted] = frameInfo_.try_emplace(frame);
	ASSERT(inserted);

	auto &info = it->second;

	info.frame = frame;
	info.request = request;
	info.paramBuffer = paramBuffer;
	info.mainPathBuffer = mainPathBuffer;
	info.selfPathBuffer = selfPathBuffer;
	info.statBuffer = statBuffer;
	info.paramDequeued = false;
	info.metadataProcessed = false;

	return &info;
}

int RkISP1Frames::destroy(unsigned int frame)
{
	auto it = frameInfo_.find(frame);
	if (it == frameInfo_.end())
		return -ENOENT;

	recycleBuffers(it->second);
	frameInfo_.erase(it);

	return 0;
}

void RkISP1Frames::clear()
{
	for (const auto &[frame, info] : frameInfo_)
		recycleBuffers(info);

	frameInfo_.clear();
}

void RkISP1Frames::recycleBuffers(const RkISP1FrameInfo &info)
{
	if (info.paramBuffer)
		pipe_->availableParamBuffers_.push(info.paramBuffer);

	if (info.statBuffer)
		pipe_->availableStatBuffers_.push(info.statBuffer);

	if (info.mainPathBuffer && pipe_->cameraData(pipe_->activeCamera_)->usesDewarper_)
		pipe_->availableMainPathBuffers_.push(info.mainPathBuffer);
}

RkISP1FrameInfo *RkISP1Frames::find(unsigned int frame)
{
	auto itInfo = frameInfo_.find(frame);

	if (itInfo != frameInfo_.end())
		return &itInfo->second;

	LOG(RkISP1, Fatal) << "Can't locate info from frame";

	return nullptr;
}

RkISP1FrameInfo *RkISP1Frames::find(FrameBuffer *buffer)
{
	for (auto &[frame, info] : frameInfo_) {
		if (info.paramBuffer == buffer ||
		    info.statBuffer == buffer ||
		    info.mainPathBuffer == buffer ||
		    info.selfPathBuffer == buffer)
			return &info;
	}

	LOG(RkISP1, Fatal) << "Can't locate info from buffer";

	return nullptr;
}

RkISP1FrameInfo *RkISP1Frames::find(Request *request)
{
	for (auto &[frame, info] : frameInfo_) {
		if (info.request == request)
			return &info;
	}

	LOG(RkISP1, Fatal) << "Can't locate info from request";

	return nullptr;
}

PipelineHandlerRkISP1 *RkISP1CameraData::pipe()
{
	return static_cast<PipelineHandlerRkISP1 *>(Camera::Private::pipe());
}

const PipelineHandlerRkISP1 *RkISP1CameraData::pipe() const
{
	return static_cast<const PipelineHandlerRkISP1 *>(Camera::Private::pipe());
}

int RkISP1CameraData::loadIPA(unsigned int hwRevision, uint32_t supportedBlocks)
{
	ipa_ = pipe()->createIPA<ipa::rkisp1::IPAProxyRkISP1>(1, 1);
	if (!ipa_)
		return -ENOENT;

	ipa_->setSensorControls.connect(this, &RkISP1CameraData::setSensorControls);
	ipa_->paramsComputed.connect(this, &RkISP1CameraData::paramsComputed);
	ipa_->metadataReady.connect(this, &RkISP1CameraData::metadataReady);

	/* The IPA tuning file is made from the sensor name. */
	std::string ipaTuningFile =
		ipa_->configurationFile(sensor_->model() + ".yaml", "uncalibrated.yaml");

	IPACameraSensorInfo sensorInfo{};
	int ret = sensor_->sensorInfo(&sensorInfo);
	if (ret) {
		LOG(RkISP1, Error) << "Camera sensor information not available";
		return ret;
	}

	ret = ipa_->init({ ipaTuningFile, sensor_->model() }, hwRevision,
			 supportedBlocks, sensorInfo, sensor_->controls(),
			 &ipaControls_);
	if (ret < 0) {
		LOG(RkISP1, Error) << "IPA initialization failure";
		return ret;
	}

	ret = loadTuningFile(ipaTuningFile);
	if (ret < 0) {
		LOG(RkISP1, Error) << "Failed to load tuning file";
		return ret;
	}

	return 0;
}

int RkISP1CameraData::loadTuningFile(const std::string &path)
{
	int ret;

	if (!pipe()->dewarper_)
		/* Nothing to do without dewarper */
		return 0;

	LOG(RkISP1, Debug) << "Load tuning file " << path;

	File file(path);
	if (!file.open(File::OpenModeFlag::ReadOnly)) {
		ret = file.error();
		LOG(RkISP1, Error)
			<< "Failed to open tuning file "
			<< path << ": " << strerror(-ret);
		return ret;
	}

	std::unique_ptr<ValueNode> data = YamlParser::parse(file);
	if (!data)
		return -EINVAL;

	if (!data->contains("modules"))
		return 0;

	const auto &modules = (*data)["modules"].asList();
	for (const auto &module : modules) {
		const auto &params = module["Dewarp"];
		if (!params)
			continue;

		ret = pipe()->dewarper_->init(params);
		if (ret)
			return ret;

		LOG(RkISP1, Info) << "Dw100 dewarper initialized";

		canUseDewarper_ = true;
		return 0;
	}

	return 0;
}

void RkISP1CameraData::paramsComputed(unsigned int frame, unsigned int bytesused)
{
	PipelineHandlerRkISP1 *pipe = RkISP1CameraData::pipe();
	RkISP1FrameInfo *info = frameInfo_.find(frame);
	if (!info)
		return;

	info->paramBuffer->_d()->metadata().planes()[0].bytesused = bytesused;

	int ret = pipe->param_->queueBuffer(info->paramBuffer);
	if (ret < 0) {
		LOG(RkISP1, Error) << "Failed to queue parameter buffer: "
				   << strerror(-ret);
		return;
	}

	pipe->stat_->queueBuffer(info->statBuffer);

	if (info->mainPathBuffer)
		mainPath_->queueBuffer(info->mainPathBuffer);

	if (selfPath_ && info->selfPathBuffer)
		selfPath_->queueBuffer(info->selfPathBuffer);
}

void RkISP1CameraData::setSensorControls([[maybe_unused]] unsigned int frame,
					 const ControlList &sensorControls)
{
	delayedCtrls_->push(sensorControls);
}

void RkISP1CameraData::metadataReady(unsigned int frame, const ControlList &metadata)
{
	RkISP1FrameInfo *info = frameInfo_.find(frame);
	if (!info)
		return;

	info->request->_d()->metadata().merge(metadata);
	info->metadataProcessed = true;

	pipe()->tryCompleteRequest(info);
}

/* -----------------------------------------------------------------------------
 * Camera Configuration
 */

namespace {

/* Keep in sync with the supported raw formats in rkisp1_path.cpp. */
const std::map<PixelFormat, uint32_t> rawFormats = {
	{ formats::SBGGR8, MEDIA_BUS_FMT_SBGGR8_1X8 },
	{ formats::SGBRG8, MEDIA_BUS_FMT_SGBRG8_1X8 },
	{ formats::SGRBG8, MEDIA_BUS_FMT_SGRBG8_1X8 },
	{ formats::SRGGB8, MEDIA_BUS_FMT_SRGGB8_1X8 },
	{ formats::SBGGR10, MEDIA_BUS_FMT_SBGGR10_1X10 },
	{ formats::SGBRG10, MEDIA_BUS_FMT_SGBRG10_1X10 },
	{ formats::SGRBG10, MEDIA_BUS_FMT_SGRBG10_1X10 },
	{ formats::SRGGB10, MEDIA_BUS_FMT_SRGGB10_1X10 },
	{ formats::SBGGR12, MEDIA_BUS_FMT_SBGGR12_1X12 },
	{ formats::SGBRG12, MEDIA_BUS_FMT_SGBRG12_1X12 },
	{ formats::SGRBG12, MEDIA_BUS_FMT_SGRBG12_1X12 },
	{ formats::SRGGB12, MEDIA_BUS_FMT_SRGGB12_1X12 },
};

} /* namespace */

RkISP1CameraConfiguration::RkISP1CameraConfiguration(Camera *camera,
						     RkISP1CameraData *data)
	: CameraConfiguration()
{
	camera_ = camera->shared_from_this();
	data_ = data;
}

bool RkISP1CameraConfiguration::fitsAllPaths(const StreamConfiguration &cfg)
{
	const CameraSensor *sensor = data_->sensor_.get();
	StreamConfiguration config;

	config = cfg;
	if (data_->mainPath_->validate(sensor, sensorConfig, &config) != Valid)
		return false;

	config = cfg;
	if (data_->selfPath_ &&
	    data_->selfPath_->validate(sensor, sensorConfig, &config) != Valid)
		return false;

	return true;
}

CameraConfiguration::Status RkISP1CameraConfiguration::validate()
{
	const PipelineHandlerRkISP1 *pipe = data_->pipe();
	const CameraSensor *sensor = data_->sensor_.get();
	unsigned int pathCount = data_->selfPath_ ? 2 : 1;
	Status status;

	if (config_.empty())
		return Invalid;

	status = validateColorSpaces(ColorSpaceFlag::StreamsShareColorSpace);

	/*
	 * Make sure that if a sensor configuration has been requested it
	 * is valid.
	 */
	if (sensorConfig) {
		if (!sensorConfig->isValid()) {
			LOG(RkISP1, Error)
				<< "Invalid sensor configuration request";

			return Invalid;
		}

		unsigned int bitDepth = sensorConfig->bitDepth;
		if (bitDepth != 8 && bitDepth != 10 && bitDepth != 12) {
			LOG(RkISP1, Error)
				<< "Invalid sensor configuration bit depth";

			return Invalid;
		}
	}

	/* Cap the number of entries to the available streams. */
	if (config_.size() > pathCount) {
		config_.resize(pathCount);
		status = Adjusted;
	}

	/*
	 * Simultaneous capture of raw and processed streams isn't possible.
	 * Let the first stream decide on the type.
	 */
	bool isRaw = (PixelFormatInfo::info(config_[0].pixelFormat).colourEncoding ==
		      PixelFormatInfo::ColourEncodingRAW);
	if (isRaw) {
		if (config_.size() > 1) {
			config_.resize(1);
			status = Adjusted;
		}
	} else {
		/* Drop all raw configs. */
		for (auto it = config_.begin(); it != config_.end();) {
			if (PixelFormatInfo::info(it->pixelFormat).colourEncoding ==
			    PixelFormatInfo::ColourEncodingRAW) {
				it = config_.erase(it);
				status = Adjusted;
				continue;
			}
			++it;
		}
	}

	/*
	 * If the dewarper supports orientation adjustments, apply that completely
	 * there. Even if the sensor supports flips, it is beneficial to do that
	 * in the dewarper so that lens dewarping happens on the unflipped image
	 */
	bool transposeAfterIsp = false;
	bool useDewarper = (data_->canUseDewarper_ && !isRaw);
	if (useDewarper) {
		combinedTransform_ = orientation / data_->sensor_->mountingOrientation();
		if (!!(combinedTransform_ & Transform::Transpose))
			transposeAfterIsp = true;
	} else {
		Orientation requestedOrientation = orientation;
		combinedTransform_ = data_->sensor_->computeTransform(&orientation);
		if (orientation != requestedOrientation)
			status = Adjusted;
	}

	/*
	 * If there are more than one stream in the configuration figure out the
	 * order to evaluate the streams. The first stream has the highest
	 * priority but if both main path and self path can satisfy it evaluate
	 * the second stream first as the first stream is guaranteed to work
	 * with whichever path is not used by the second one.
	 */
	std::vector<unsigned int> order(config_.size());
	std::iota(order.begin(), order.end(), 0);
	if (config_.size() == 2 && fitsAllPaths(config_[0]))
		std::reverse(order.begin(), order.end());

	/*
	 * Validate the configuration against the desired path and, if the
	 * platform supports it, the dewarper. While iterating over the
	 * configurations collect the smallest common sensor format.
	 */
	Size accumulatedSensorSize;
	auto validateConfig = [&](StreamConfiguration &cfg, RkISP1Path *path,
				  Stream *stream, Status expectedStatus) {
		StreamConfiguration tryCfg = cfg;

		/* Need to validate the path before the transpose */
		if (transposeAfterIsp)
			tryCfg.size.transpose();

		Status ret = path->validate(sensor, sensorConfig, &tryCfg);
		if (ret == Invalid)
			return false;

		if (!useDewarper &&
		    (expectedStatus == Valid && ret == Adjusted))
			return false;

		Size sensorSize = tryCfg.size;

		if (useDewarper) {
			/*
			 * The dewarper output is independent of the ISP path.
			 * Reset to the originally requested size.
			 */
			tryCfg.size = cfg.size;
			bool adjusted;

			pipe->dewarper_->validateOutput(&tryCfg, &adjusted,
							Converter::Alignment::Down);
			if (expectedStatus == Valid && adjusted)
				return false;
		}

		if (tryCfg.bufferCount < kRkISP1MinBufferCount) {
			if (expectedStatus == Valid)
				return false;
			tryCfg.bufferCount = kRkISP1MinBufferCount;
		}

		cfg = tryCfg;
		cfg.setStream(stream);

		accumulatedSensorSize = std::max(accumulatedSensorSize, sensorSize);
		return true;
	};

	bool mainPathAvailable = true;
	bool selfPathAvailable = data_->selfPath_;
	RkISP1Path *mainPath = data_->mainPath_;
	RkISP1Path *selfPath = data_->selfPath_;
	Stream *mainPathStream = const_cast<Stream *>(&data_->mainPathStream_);
	Stream *selfPathStream = const_cast<Stream *>(&data_->selfPathStream_);
	for (unsigned int index : order) {
		StreamConfiguration &cfg = config_[index];

		/* Try to match stream without adjusting configuration. */
		if (mainPathAvailable) {
			if (validateConfig(cfg, mainPath, mainPathStream, Valid)) {
				mainPathAvailable = false;
				continue;
			}
		}

		if (selfPathAvailable) {
			if (validateConfig(cfg, selfPath, selfPathStream, Valid)) {
				selfPathAvailable = false;
				continue;
			}
		}

		/* Try to match stream allowing adjusting configuration. */
		if (mainPathAvailable) {
			if (validateConfig(cfg, mainPath, mainPathStream, Adjusted)) {
				mainPathAvailable = false;
				status = Adjusted;
				continue;
			}
		}

		if (selfPathAvailable) {
			if (validateConfig(cfg, selfPath, selfPathStream, Adjusted)) {
				selfPathAvailable = false;
				status = Adjusted;
				continue;
			}
		}

		/* All paths rejected configuration. */
		LOG(RkISP1, Debug) << "Camera configuration not supported "
				   << cfg.toString();
		return Invalid;
	}

	std::vector<unsigned int> mbusCodes;

	if (isRaw) {
		mbusCodes = { rawFormats.at(config_[0].pixelFormat) };
	} else {
		std::transform(rawFormats.begin(), rawFormats.end(),
			       std::back_inserter(mbusCodes),
			       [](const auto &value) { return value.second; });
	}

	sensorFormat_ = sensor->getFormat(mbusCodes, accumulatedSensorSize,
					  mainPath->maxResolution());

	if (sensorFormat_.size.isNull())
		sensorFormat_.size = sensor->resolution();

	return status;
}

/* -----------------------------------------------------------------------------
 * Pipeline Operations
 */

PipelineHandlerRkISP1::PipelineHandlerRkISP1(CameraManager *manager)
	: PipelineHandler(manager, kRkISP1MaxQueuedRequests), hasSelfPath_(true)
{
}

std::unique_ptr<CameraConfiguration>
PipelineHandlerRkISP1::generateConfiguration(Camera *camera,
					     Span<const StreamRole> roles)
{
	RkISP1CameraData *data = cameraData(camera);

	unsigned int pathCount = data->selfPath_ ? 2 : 1;
	if (roles.size() > pathCount) {
		LOG(RkISP1, Error) << "Too many stream roles requested";
		return nullptr;
	}

	std::unique_ptr<CameraConfiguration> config =
		std::make_unique<RkISP1CameraConfiguration>(camera, data);
	if (roles.empty())
		return config;

	Transform transform = Transform::Identity;
	Size previewSize = kRkISP1PreviewSize;
	bool transposeAfterIsp = false;
	if (data->canUseDewarper_) {
		transform = Orientation::Rotate0 / data->sensor_->mountingOrientation();
		if (!!(transform & Transform::Transpose))
			transposeAfterIsp = true;
	}

	/*
	 * In case of a transpose transform we need to create a path for the
	 * transposed size.
	 */
	if (transposeAfterIsp)
		previewSize.transpose();

	/*
	 * As the ISP can't output different color spaces for the main and self
	 * path, pick a sensible default color space based on the role of the
	 * first stream and use it for all streams.
	 */
	std::optional<ColorSpace> colorSpace;
	bool mainPathAvailable = true;

	for (const StreamRole role : roles) {
		Size size;

		switch (role) {
		case StreamRole::StillCapture:
			/* JPEG encoders typically expect sYCC. */
			if (!colorSpace)
				colorSpace = ColorSpace::Sycc;

			size = data->sensor_->resolution();
			break;

		case StreamRole::Viewfinder:
			/*
			 * sYCC is the YCbCr encoding of sRGB, which is commonly
			 * used by displays.
			 */
			if (!colorSpace)
				colorSpace = ColorSpace::Sycc;

			size = previewSize;
			break;

		case StreamRole::VideoRecording:
			/* Rec. 709 is a good default for HD video recording. */
			if (!colorSpace)
				colorSpace = ColorSpace::Rec709;

			size = previewSize;
			break;

		case StreamRole::Raw:
			if (roles.size() > 1) {
				LOG(RkISP1, Error)
					<< "Can't capture both raw and processed streams";
				return nullptr;
			}

			colorSpace = ColorSpace::Raw;
			size = data->sensor_->resolution();
			break;

		default:
			LOG(RkISP1, Warning)
				<< "Requested stream role not supported: " << role;
			return nullptr;
		}

		/*
		 * Prefer the main path if available, as it supports higher
		 * resolutions.
		 *
		 * \todo Using the main path unconditionally hides support for
		 * RGB (only available on the self path) in the streams formats
		 * exposed to applications. This likely calls for a better API
		 * to expose streams capabilities.
		 */
		RkISP1Path *path;
		if (mainPathAvailable) {
			path = data->mainPath_;
			mainPathAvailable = false;
		} else {
			path = data->selfPath_;
		}

		StreamConfiguration cfg =
			path->generateConfiguration(data->sensor_.get(), size, role);
		if (!cfg.pixelFormat.isValid())
			return nullptr;

		if (transposeAfterIsp && role != StreamRole::Raw)
			cfg.size.transpose();

		cfg.colorSpace = colorSpace;
		cfg.bufferCount = kRkISP1MinBufferCount;
		config->addConfiguration(cfg);
	}

	config->validate();

	return config;
}

int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)
{
	RkISP1CameraConfiguration *config =
		static_cast<RkISP1CameraConfiguration *>(c);
	RkISP1CameraData *data = cameraData(camera);
	CameraSensor *sensor = data->sensor_.get();
	int ret;

	ret = initLinks(camera, *config);
	if (ret)
		return ret;

	const PixelFormat &streamFormat = config->at(0).pixelFormat;
	const PixelFormatInfo &info = PixelFormatInfo::info(streamFormat);
	isRaw_ = info.colourEncoding == PixelFormatInfo::ColourEncodingRAW;
	data->usesDewarper_ = data->canUseDewarper_ && !isRaw_;

	Transform transform = config->combinedTransform();
	bool transposeAfterIsp = false;
	if (data->usesDewarper_) {
		if (!!(transform & Transform::Transpose))
			transposeAfterIsp = true;
		transform = Transform::Identity;
	}

	/*
	 * Configure the format on the sensor output and propagate it through
	 * the pipeline.
	 */
	V4L2SubdeviceFormat format = config->sensorFormat();
	LOG(RkISP1, Debug) << "Configuring sensor with " << format;

	if (config->sensorConfig)
		ret = sensor->applyConfiguration(*config->sensorConfig,
						 transform,
						 &format);
	else
		ret = sensor->setFormat(&format, transform);

	if (ret < 0)
		return ret;

	LOG(RkISP1, Debug) << "Sensor configured with " << format;

	/* Propagate format through the internal media pipeline up to the ISP */
	ret = data->pipe_.configure(sensor, &format);
	if (ret < 0)
		return ret;

	LOG(RkISP1, Debug) << "Configuring ISP with : " << format;
	ret = isp_->setFormat(0, &format);
	if (ret < 0)
		return ret;

	Rectangle inputCrop(0, 0, format.size);
	ret = isp_->setSelection(0, V4L2_SEL_TGT_CROP, &inputCrop);
	if (ret < 0)
		return ret;

	LOG(RkISP1, Debug)
		<< "ISP input pad configured with " << format
		<< " crop " << inputCrop;

	Rectangle outputCrop = inputCrop;

	/* YUYV8_2X8 is required on the ISP source path pad for YUV output. */
	if (!isRaw_)
		format.code = MEDIA_BUS_FMT_YUYV8_2X8;

	/*
	 * On devices without DUAL_CROP (like the imx8mp) cropping needs to be
	 * done on the ISP/IS output.
	 *
	 * If the dewarper is used, the cropping shall be done by the dewarper.
	 */
	if (media_->hwRevision() == RKISP1_V_IMX8MP) {
		/* imx8mp has only a single path. */
		const auto &cfg = config->at(0);
		/*
		 * If the dewarper is used, all cropping including aspect ratio
		 * preservation shall be done there. To ensure that the output
		 * format provided by the ISP is supported by the dewarper, a
		 * minimal crop still needs to be applied on the ISP output.
		 *
		 * \todo It might be possible to allocate bigger buffers
		 * (aligned to 8 pixels) with a stride matching format.size for
		 * the ISP. The not-filled border could later be ignored by the
		 * dewarper. This way we could skip the minimal crop here and
		 * the MaximumScalerCrop would always match the isp output.
		 */
		Size ispCrop;
		if (data->usesDewarper_)
			ispCrop = dewarper_->adjustInputSize(cfg.pixelFormat,
							     format.size);
		else
			ispCrop = format.size.boundedToAspectRatio(cfg.size)
					  .alignedUpTo(2, 2);

		outputCrop = ispCrop.centeredTo(Rectangle(format.size).center());
		format.size = ispCrop;
	}

	LOG(RkISP1, Debug)
		<< "Configuring ISP output pad with " << format
		<< " crop " << outputCrop;

	ret = isp_->setSelection(2, V4L2_SEL_TGT_CROP, &outputCrop);
	if (ret < 0)
		return ret;

	format.colorSpace = config->at(0).colorSpace;
	ret = isp_->setFormat(2, &format);
	if (ret < 0)
		return ret;

	LOG(RkISP1, Debug)
		<< "ISP output pad configured with " << format
		<< " crop " << outputCrop;

	IPACameraSensorInfo sensorInfo;
	ret = data->sensor_->sensorInfo(&sensorInfo);
	if (ret)
		return ret;

	/* Apply the actual sensor crop for proper dewarp map calculation. */
	Rectangle sensorCrop = outputCrop.transformedBetween(
		inputCrop, sensorInfo.analogCrop);
	if (data->usesDewarper_)
		dewarper_->setSensorCrop(sensorCrop);
	data->properties_.set(properties::ScalerCropMaximum, sensorCrop);

	std::map<unsigned int, IPAStream> streamConfig;

	for (const StreamConfiguration &cfg : *config) {
		if (cfg.stream() == &data->mainPathStream_) {
			/*
			 * To allow for digital zoom, scaling down should happen
			 * in the dewarper, instead of the resizer. Configure
			 * the isp output to the same size as the sensor output.
			 */
			StreamConfiguration ispCfg = cfg;
			if (data->usesDewarper_) {
				ispCfg.bufferCount = kRkISP1MinBufferCount;
				ispCfg.size = format.size;
				ispCfg.stride =
					PixelFormatInfo::info(ispCfg.pixelFormat)
						.stride(ispCfg.size.width, 0);

				ret = dewarper_->configure(ispCfg, { cfg });
				if (ret)
					return ret;

				dewarper_->setTransform(cfg.stream(), config->combinedTransform());
				/*
				 * Apply a default scaler crop that keeps the
				 * aspect ratio.
				 */
				Size size = cfg.size;
				if (transposeAfterIsp)
					size.transpose();
				size = sensorCrop.size().boundedToAspectRatio(size);

				ControlList ctrls;
				ctrls.set(controls::ScalerCrop, size.centeredTo(sensorCrop.center()));
				dewarper_->setControls(cfg.stream(), ctrls);
			}

			ret = mainPath_.configure(ispCfg, format);
			streamConfig[0] = IPAStream(cfg.pixelFormat,
						    cfg.size);
		} else if (hasSelfPath_) {
			ret = selfPath_.configure(cfg, format);
			streamConfig[1] = IPAStream(cfg.pixelFormat,
						    cfg.size);
		} else {
			return -ENODEV;
		}

		if (ret)
			return ret;
	}

	V4L2DeviceFormat paramFormat;
	paramFormat.fourcc = V4L2PixelFormat(V4L2_META_FMT_RK_ISP1_EXT_PARAMS);
	ret = param_->setFormat(&paramFormat);
	if (ret)
		return ret;

	V4L2DeviceFormat statFormat;
	statFormat.fourcc = V4L2PixelFormat(V4L2_META_FMT_RK_ISP1_STAT_3A);
	ret = stat_->setFormat(&statFormat);
	if (ret)
		return ret;

	/* Inform IPA of stream configuration and sensor controls. */
	ipa::rkisp1::IPAConfigInfo ipaConfig{ sensorInfo,
					      data->sensor_->controls(),
					      paramFormat.fourcc };

	ret = data->ipa_->configure(ipaConfig, streamConfig, &data->ipaControls_);
	if (ret) {
		LOG(RkISP1, Error) << "failed configuring IPA (" << ret << ")";
		return ret;
	}

	return updateControls(data);
}

int PipelineHandlerRkISP1::exportFrameBuffers([[maybe_unused]] Camera *camera, Stream *stream,
					      std::vector<std::unique_ptr<FrameBuffer>> *buffers)
{
	RkISP1CameraData *data = cameraData(camera);
	unsigned int count = stream->configuration().bufferCount;

	if (stream == &data->mainPathStream_) {
		/*
		 * Currently, i.MX8MP is the only platform with DW100 dewarper.
		 * It has mainpath and no self path. Hence, export buffers from
		 * dewarper just for the main path stream, for now.
		 */
		if (data->usesDewarper_)
			return dewarper_->exportBuffers(&data->mainPathStream_, count, buffers);
		else
			return mainPath_.exportBuffers(count, buffers);
	} else if (hasSelfPath_ && stream == &data->selfPathStream_) {
		return selfPath_.exportBuffers(count, buffers);
	}

	return -EINVAL;
}

int PipelineHandlerRkISP1::allocateBuffers(Camera *camera)
{
	RkISP1CameraData *data = cameraData(camera);
	unsigned int ipaBufferId = 1;
	int ret;

	auto errorCleanup = utils::scope_exit{ [&]() {
		paramBuffers_.clear();
		statBuffers_.clear();
		mainPathBuffers_.clear();
	} };

	if (!isRaw_) {
		ret = param_->allocateBuffers(kRkISP1MinBufferCount, &paramBuffers_);
		if (ret < 0)
			return ret;

		ret = stat_->allocateBuffers(kRkISP1MinBufferCount, &statBuffers_);
		if (ret < 0)
			return ret;
	}

	/* If the dewarper is being used, allocate internal buffers for ISP. */
	if (data->usesDewarper_) {
		ret = mainPath_.exportBuffers(kRkISP1MinBufferCount, &mainPathBuffers_);
		if (ret < 0)
			return ret;

		for (std::unique_ptr<FrameBuffer> &buffer : mainPathBuffers_)
			availableMainPathBuffers_.push(buffer.get());
	}

	auto pushBuffers = [&](const std::vector<std::unique_ptr<FrameBuffer>> &buffers,
			       std::queue<FrameBuffer *> &queue) {
		for (const std::unique_ptr<FrameBuffer> &buffer : buffers) {
			Span<const FrameBuffer::Plane> planes = buffer->planes();

			buffer->setCookie(ipaBufferId++);
			data->ipaBuffers_.emplace_back(buffer->cookie(),
						       std::vector<FrameBuffer::Plane>{ planes.begin(),
											planes.end() });
			queue.push(buffer.get());
		}
	};

	pushBuffers(paramBuffers_, availableParamBuffers_);
	pushBuffers(statBuffers_, availableStatBuffers_);

	data->ipa_->mapBuffers(data->ipaBuffers_);

	errorCleanup.release();
	return 0;
}

int PipelineHandlerRkISP1::freeBuffers(Camera *camera)
{
	RkISP1CameraData *data = cameraData(camera);

	while (!availableStatBuffers_.empty())
		availableStatBuffers_.pop();

	while (!availableParamBuffers_.empty())
		availableParamBuffers_.pop();

	while (!availableMainPathBuffers_.empty())
		availableMainPathBuffers_.pop();

	paramBuffers_.clear();
	statBuffers_.clear();
	mainPathBuffers_.clear();

	std::vector<unsigned int> ids;
	for (IPABuffer &ipabuf : data->ipaBuffers_)
		ids.push_back(ipabuf.id);

	data->ipa_->unmapBuffers(ids);
	data->ipaBuffers_.clear();

	if (param_->releaseBuffers())
		LOG(RkISP1, Error) << "Failed to release parameters buffers";

	if (stat_->releaseBuffers())
		LOG(RkISP1, Error) << "Failed to release stat buffers";

	return 0;
}

int PipelineHandlerRkISP1::start(Camera *camera, [[maybe_unused]] const ControlList *controls)
{
	RkISP1CameraData *data = cameraData(camera);
	utils::ScopeExitActions actions;
	int ret;

	/* Allocate buffers for internal pipeline usage. */
	ret = allocateBuffers(camera);
	if (ret)
		return ret;
	actions += [&]() { freeBuffers(camera); };

	ret = data->ipa_->start();
	if (ret) {
		LOG(RkISP1, Error)
			<< "Failed to start IPA " << camera->id();
		return ret;
	}
	actions += [&]() { data->ipa_->stop(); };

	data->frame_ = 0;

	if (!isRaw_) {
		ret = param_->streamOn();
		if (ret) {
			LOG(RkISP1, Error)
				<< "Failed to start parameters " << camera->id();
			return ret;
		}
		actions += [&]() { param_->streamOff(); };

		ret = stat_->streamOn();
		if (ret) {
			LOG(RkISP1, Error)
				<< "Failed to start statistics " << camera->id();
			return ret;
		}
		actions += [&]() { stat_->streamOff(); };

		if (data->usesDewarper_) {
			if (controls)
				dewarper_->setControls(&data->mainPathStream_, *controls);

			ret = dewarper_->start();
			if (ret) {
				LOG(RkISP1, Error) << "Failed to start dewarper";
				return ret;
			}
			actions += [&]() { dewarper_->stop(); };
		}
	}

	if (data->mainPath_->isEnabled()) {
		ret = mainPath_.start(data->mainPathStream_.configuration().bufferCount);
		if (ret)
			return ret;
		actions += [&]() { mainPath_.stop(); };
	}

	if (hasSelfPath_ && data->selfPath_->isEnabled()) {
		ret = selfPath_.start(data->selfPathStream_.configuration().bufferCount);
		if (ret)
			return ret;
	}

	isp_->setFrameStartEnabled(true);

	activeCamera_ = camera;

	actions.release();
	return 0;
}

void PipelineHandlerRkISP1::stopDevice(Camera *camera)
{
	RkISP1CameraData *data = cameraData(camera);
	int ret;

	isp_->setFrameStartEnabled(false);

	data->ipa_->stop();

	if (hasSelfPath_)
		selfPath_.stop();
	mainPath_.stop();

	if (!isRaw_) {
		ret = stat_->streamOff();
		if (ret)
			LOG(RkISP1, Warning)
				<< "Failed to stop statistics for " << camera->id();

		ret = param_->streamOff();
		if (ret)
			LOG(RkISP1, Warning)
				<< "Failed to stop parameters for " << camera->id();

		if (data->usesDewarper_)
			dewarper_->stop();
	}

	ASSERT(data->queuedRequests_.empty());
	data->frameInfo_.clear();

	freeBuffers(camera);

	activeCamera_ = nullptr;
}

int PipelineHandlerRkISP1::queueRequestDevice(Camera *camera, Request *request)
{
	RkISP1CameraData *data = cameraData(camera);

	RkISP1FrameInfo *info = data->frameInfo_.create(data, request, isRaw_);
	if (!info)
		return -ENOENT;

	data->ipa_->queueRequest(data->frame_, request->controls());
	if (isRaw_) {
		if (info->mainPathBuffer)
			data->mainPath_->queueBuffer(info->mainPathBuffer);

		if (data->selfPath_ && info->selfPathBuffer)
			data->selfPath_->queueBuffer(info->selfPathBuffer);
	} else {
		data->ipa_->computeParams(data->frame_,
					  info->paramBuffer->cookie());
	}

	data->frame_++;

	return 0;
}

/* -----------------------------------------------------------------------------
 * Match and Setup
 */

int PipelineHandlerRkISP1::initLinks(Camera *camera,
				     const RkISP1CameraConfiguration &config)
{
	RkISP1CameraData *data = cameraData(camera);
	int ret;

	ret = media_->disableLinks();
	if (ret < 0)
		return ret;

	/*
	 * Configure the sensor links: enable the links corresponding to this
	 * pipeline all the way up to the ISP, through any connected CSI receiver.
	 */
	ret = data->pipe_.initLinks();
	if (ret) {
		LOG(RkISP1, Error) << "Failed to set up pipe links";
		return ret;
	}

	/* Configure the paths after the ISP */
	for (const StreamConfiguration &cfg : config) {
		if (cfg.stream() == &data->mainPathStream_)
			ret = data->mainPath_->setEnabled(true);
		else if (hasSelfPath_ && cfg.stream() == &data->selfPathStream_)
			ret = data->selfPath_->setEnabled(true);
		else
			return -EINVAL;

		if (ret < 0)
			return ret;
	}

	return 0;
}

/**
 * \brief Update the camera controls
 * \param[in] data The camera data
 *
 * Compute the camera controls by calculating controls which the pipeline
 * is reponsible for and merge them with the controls computed by the IPA.
 *
 * This function needs data->ipaControls_ to be refreshed when a new
 * configuration is applied to the camera by the IPA configure() function.
 *
 * Always call this function after IPA configure() to make sure to have a
 * properly refreshed IPA controls list.
 *
 * \return 0 on success or a negative error code otherwise
 */
int PipelineHandlerRkISP1::updateControls(RkISP1CameraData *data)
{
	ControlInfoMap::Map controls;

	if (data->usesDewarper_)
		dewarper_->updateControlInfos(&data->mainPathStream_, controls);

	/* Add the IPA registered controls to list of camera controls. */
	for (const auto &ipaControl : data->ipaControls_)
		controls[ipaControl.first] = ipaControl.second;

	data->controlInfo_ = ControlInfoMap(std::move(controls),
					    controls::controls);

	return 0;
}

/*
 * By default we assume all the blocks that were included in the first
 * extensible parameters series are available. That is the lower 20bits.
 */
const uint32_t kDefaultExtParamsBlocks = 0xfffff;

int PipelineHandlerRkISP1::createCamera(MediaEntity *sensor)
{
	int ret;

	std::unique_ptr<RkISP1CameraData> data =
		std::make_unique<RkISP1CameraData>(this, &mainPath_,
						   hasSelfPath_ ? &selfPath_ : nullptr);

	/* Identify the pipeline path between the sensor and the rkisp1_isp */
	ret = data->pipe_.init(sensor, "rkisp1_isp");
	if (ret) {
		LOG(RkISP1, Error) << "Failed to identify path from sensor to sink";
		return ret;
	}

	data->sensor_ = CameraSensorFactoryBase::create(sensor);
	if (!data->sensor_)
		return -ENODEV;

	/* Initialize the camera properties. */
	data->properties_ = data->sensor_->properties();

	const CameraSensorProperties::SensorDelays &delays = data->sensor_->sensorDelays();
	std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
		{ V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },
		{ V4L2_CID_EXPOSURE, { delays.exposureDelay, false } },
		{ V4L2_CID_VBLANK, { delays.vblankDelay, true } },
	};

	data->delayedCtrls_ =
		std::make_unique<DelayedControls>(data->sensor_->device(),
						  params);
	isp_->frameStart.connect(data->delayedCtrls_.get(),
				 &DelayedControls::applyControls);

	uint32_t supportedBlocks = kDefaultExtParamsBlocks;

	auto &controls = param_->controls();
	if (controls.find(RKISP1_CID_SUPPORTED_PARAMS_BLOCKS) != controls.end()) {
		auto list = param_->getControls({ { RKISP1_CID_SUPPORTED_PARAMS_BLOCKS } });
		if (!list.empty())
			supportedBlocks = static_cast<uint32_t>(
				list.get(RKISP1_CID_SUPPORTED_PARAMS_BLOCKS)
					.get<int32_t>());
	} else {
		LOG(RkISP1, Error)
			<< "Failed to query supported params blocks. Falling back to defaults.";
	}

	ret = data->loadIPA(media_->hwRevision(), supportedBlocks);
	if (ret)
		return ret;

	updateControls(data.get());

	std::set<Stream *> streams{
		&data->mainPathStream_,
		&data->selfPathStream_,
	};
	const std::string &id = data->sensor_->id();
	std::shared_ptr<Camera> camera =
		Camera::create(std::move(data), id, streams);

	registerCamera(std::move(camera));

	return 0;
}

bool PipelineHandlerRkISP1::match(DeviceEnumerator *enumerator)
{
	DeviceMatch dm("rkisp1");
	dm.add("rkisp1_isp");
	dm.add("rkisp1_resizer_mainpath");
	dm.add("rkisp1_mainpath");
	dm.add("rkisp1_stats");
	dm.add("rkisp1_params");

	media_ = acquireMediaDevice(enumerator, dm);
	if (!media_)
		return false;

	if (!media_->hwRevision()) {
		LOG(RkISP1, Error)
			<< "The rkisp1 driver is too old, v5.11 or newer is required";
		return false;
	}

	hasSelfPath_ = !!media_->getEntityByName("rkisp1_selfpath");

	/* Create the V4L2 subdevices we will need. */
	isp_ = V4L2Subdevice::fromEntityName(media_.get(), "rkisp1_isp");
	if (isp_->open() < 0)
		return false;

	/* Locate and open the stats and params video nodes. */
	stat_ = V4L2VideoDevice::fromEntityName(media_.get(), "rkisp1_stats");
	if (stat_->open() < 0)
		return false;

	param_ = V4L2VideoDevice::fromEntityName(media_.get(), "rkisp1_params");
	if (param_->open() < 0)
		return false;

	/* Locate and open the ISP main and self paths. */
	if (!mainPath_.init(media_))
		return false;

	if (hasSelfPath_ && !selfPath_.init(media_))
		return false;

	mainPath_.bufferReady().connect(this, &PipelineHandlerRkISP1::imageBufferReady);
	if (hasSelfPath_)
		selfPath_.bufferReady().connect(this, &PipelineHandlerRkISP1::imageBufferReady);
	stat_->bufferReady.connect(this, &PipelineHandlerRkISP1::statBufferReady);
	param_->bufferReady.connect(this, &PipelineHandlerRkISP1::paramBufferReady);

	dewarper_ = ConverterDW100Module::createModule(enumerator);
	if (dewarper_) {
		dewarper_->outputBufferReady.connect(
			this, &PipelineHandlerRkISP1::dewarpBufferReady);
		LOG(RkISP1, Debug) << "Found DW100 dewarper";
	}

	/*
	 * Enumerate all sensors connected to the ISP and create one
	 * camera instance for each of them.
	 */
	bool registered = false;

	for (MediaEntity *entity : media_->locateEntities(MEDIA_ENT_F_CAM_SENSOR)) {
		LOG(RkISP1, Debug) << "Identified " << entity->name();
		if (!createCamera(entity))
			registered = true;
	}

	return registered;
}

/* -----------------------------------------------------------------------------
 * Buffer Handling
 */

void PipelineHandlerRkISP1::tryCompleteRequest(RkISP1FrameInfo *info)
{
	RkISP1CameraData *data = cameraData(activeCamera_);
	Request *request = info->request;

	if (request->hasPendingBuffers())
		return;

	if (!info->metadataProcessed)
		return;

	if (!isRaw_ && !info->paramDequeued)
		return;

	data->frameInfo_.destroy(info->frame);

	completeRequest(request);
}

void PipelineHandlerRkISP1::cancelDewarpRequest(RkISP1FrameInfo *info)
{
	RkISP1CameraData *data = cameraData(activeCamera_);
	Request *request = info->request;
	/*
	 * i.MX8MP is the only known platform with dewarper. It has
	 * no self path. Hence, only main path buffer completion is
	 * required.
	 *
	 * Also, we cannot completeBuffer(request, buffer) as buffer
	 * here, is an internal buffer (between ISP and dewarper) and
	 * is not associated to the any specific request. The request
	 * buffer associated with main path stream is the one that
	 * is required to be completed (not the internal buffer).
	 */
	for (auto [stream, buffer] : request->buffers()) {
		if (stream == &data->mainPathStream_) {
			buffer->_d()->cancel();
			completeBuffer(request, buffer);
		}
	}

	tryCompleteRequest(info);
}

void PipelineHandlerRkISP1::imageBufferReady(FrameBuffer *buffer)
{
	ASSERT(activeCamera_);
	RkISP1CameraData *data = cameraData(activeCamera_);

	RkISP1FrameInfo *info = data->frameInfo_.find(buffer);
	if (!info)
		return;

	const FrameMetadata &metadata = buffer->metadata();
	Request *request = info->request;

	if (metadata.status != FrameMetadata::FrameCancelled) {
		/*
		 * Record the sensor's timestamp in the request metadata.
		 *
		 * \todo The sensor timestamp should be better estimated by connecting
		 * to the V4L2Device::frameStart signal.
		 */
		request->_d()->metadata().set(controls::SensorTimestamp,
					      metadata.timestamp);

		if (isRaw_) {
			const ControlList &ctrls =
				data->delayedCtrls_->get(metadata.sequence);
			data->ipa_->processStats(info->frame, 0, ctrls);
		}
	} else {
		if (isRaw_)
			info->metadataProcessed = true;
	}

	if (!data->usesDewarper_) {
		completeBuffer(request, buffer);
		tryCompleteRequest(info);

		return;
	}

	/* Do not queue cancelled frames to dewarper. */
	if (metadata.status == FrameMetadata::FrameCancelled) {
		cancelDewarpRequest(info);
		return;
	}

	dewarper_->setControls(&data->mainPathStream_, request->controls());

	/*
	 * Queue input and output buffers to the dewarper. The output
	 * buffers for the dewarper are the buffers of the request, supplied
	 * by the application.
	 */
	int ret = dewarper_->queueBuffers(buffer, request->buffers());
	if (ret < 0) {
		LOG(RkISP1, Error) << "Failed to queue buffers to dewarper: -"
				   << strerror(-ret);

		cancelDewarpRequest(info);

		return;
	}

	dewarper_->populateMetadata(&data->mainPathStream_, request->_d()->metadata());
}

void PipelineHandlerRkISP1::dewarpBufferReady(FrameBuffer *buffer)
{
	ASSERT(activeCamera_);
	RkISP1CameraData *data = cameraData(activeCamera_);
	Request *request = buffer->request();

	RkISP1FrameInfo *info = data->frameInfo_.find(buffer->request());
	if (!info)
		return;

	completeBuffer(request, buffer);
	tryCompleteRequest(info);
}

void PipelineHandlerRkISP1::paramBufferReady(FrameBuffer *buffer)
{
	ASSERT(activeCamera_);
	RkISP1CameraData *data = cameraData(activeCamera_);

	RkISP1FrameInfo *info = data->frameInfo_.find(buffer);
	if (!info)
		return;

	info->paramDequeued = true;
	tryCompleteRequest(info);
}

void PipelineHandlerRkISP1::statBufferReady(FrameBuffer *buffer)
{
	ASSERT(activeCamera_);
	RkISP1CameraData *data = cameraData(activeCamera_);

	RkISP1FrameInfo *info = data->frameInfo_.find(buffer);
	if (!info)
		return;

	if (buffer->metadata().status == FrameMetadata::FrameCancelled) {
		info->metadataProcessed = true;
		tryCompleteRequest(info);
		return;
	}

	if (data->frame_ <= buffer->metadata().sequence)
		data->frame_ = buffer->metadata().sequence + 1;

	data->ipa_->processStats(info->frame, info->statBuffer->cookie(),
				 data->delayedCtrls_->get(buffer->metadata().sequence));
}

REGISTER_PIPELINE_HANDLER(PipelineHandlerRkISP1, "rkisp1")

} /* namespace libcamera */
