summaryrefslogtreecommitdiff
path: root/test/controls
AgeCommit message (Collapse)Author
2020-05-16libcamera: Move internal headers to include/libcamera/internal/Laurent Pinchart
The libcamera internal headers are located in src/libcamera/include/. The directory is added to the compiler headers search path with a meson include_directories() directive, and internal headers are included with (e.g. for the internal semaphore.h header) #include "semaphore.h" All was well, until libcxx decided to implement the C++20 synchronization library. The __threading_support header gained a #include <semaphore.h> to include the pthread's semaphore support. As include_directories() adds src/libcamera/include/ to the compiler search path with -I, the internal semaphore.h is included instead of the pthread version. Needless to say, the compiler isn't happy. Three options have been considered to fix this issue: - Use -iquote instead of -I. The -iquote option instructs gcc to only consider the header search path for headers included with the "" version. Meson unfortunately doesn't support this option. - Rename the internal semaphore.h header. This was deemed to be the beginning of a long whack-a-mole game, where namespace clashes with system libraries would appear over time (possibly dependent on particular system configurations) and would need to be constantly fixed. - Move the internal headers to another directory to create a unique namespace through path components. This causes lots of churn in all the existing source files through the all project. The first option would be best, but isn't available to us due to missing support in meson. Even if -iquote support was added, we would need to fix the problem before a new version of meson containing the required support would be released. The third option is thus the only practical solution available. Bite the bullet, and do it, moving headers to include/libcamera/internal/. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Acked-by: Jacopo Mondi <jacopo@jmondi.org>
2020-05-13licenses: License all meson files under CC0-1.0Laurent Pinchart
In an attempt to clarify the license terms of all files in the libcamera project, the build system files deserve particular attention. While they describe how the binaries are created, they are not themselves transformed into any part of binary distributions of the software, and thus don't influence the copyright on the binary packages. They are however subject to copyright, and thus influence the distribution terms of the source packages. Most of the meson.build files would not meet the threshold of originality criteria required for copyright protection. Some of the more complex meson.build files may be eligible for copyright protection. To avoid any ambiguity and uncertainty, state our intent to not assert copyrights on the build system files by putting them in the public domain with the CC0-1.0 license. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> Acked-by: Giulio Benetti <giulio.benetti@micronovasrl.com> Acked-by: Jacopo Mondi <jacopo@jmondi.org> Acked-by: Kieran Bingham <kieran.bingham@ideasonboard.com> Acked-by: Naushir Patuck <naush@raspberrypi.com> Acked-by: Nicolas Dufresne <nicolas.dufresne@collabora.com> Acked-by: Niklas Söderlund <niklas.soderlund@ragnatech.se> Acked-by: Paul Elder <paul.elder@ideasonboard.com> Acked-by: Show Liu <show.liu@linaro.org>
2020-04-27test: Use float values for brightness, contrast and saturationLaurent Pinchart
Two tests use the brightness, contrast and saturation controls with integer failures. They were not updated by commit eff4b1aa01c1 which turned those controls into floats. This doesn't cause test failures as the control API converts the value types. For correctness, update the tests to use float values. Fixes: eff4b1aa01c1 ("libcamera: controls: Reorder and update description of existing controls") Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Jacopo Mondi <jacopo@jmondi.org>
2020-03-20test: controls: control_value: Test string control typeLaurent Pinchart
Add test cases for the string control type. As strings are implemented as char arrays, arrays of strings are not supported. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Jacopo Mondi <jacopo@jmondi.org>
2020-03-20libcamera: controls: Rename ControlRange to ControlInfoLaurent Pinchart
To prepare for storage of additional information in the ControlRange structure, rename it to ControlInfo. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Jacopo Mondi <jacopo@jmondi.org>
2020-03-20libcamera: controls: Name all ControlInfoMap instance variables infoMapLaurent Pinchart
To prepare for the rename of ControlRange to ControlInfo, rename all the ControlInfoMap instance variables currently named info to infoMap. This will help avoiding namespace clashes. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Jacopo Mondi <jacopo@jmondi.org>
2020-03-06test: controls: control_value: Expand test to cover array controlsLaurent Pinchart
Add tests to ControlValueTest to cover array controls of all supported types. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
2020-03-06test: controls: control_value: Expand test to cover all control typesLaurent Pinchart
The ControlValueTest hasn't been updated for a long time and is outdated. Improve it to support all control types, and test the type(), isArray() and toString() methods. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
2019-11-25test: controls: control_list: Add status checkJacopo Mondi
Since commit fac471e812a9 ("test: Extract CameraTest class out of camera tests to libtest") the control_list is a subclass of CameraTest, and the status returned by the base class init() operation should be inspected to avoid accessing uninitialized fields during the run() operation execution. If the VIMC test module is not loaded, executing the test results in a segfault. Fix this by adding the init() operation where to status_ flag is checked for errors. Fixes: fac471e812a9 ("test: Extract CameraTest class out of camera tests to libtest") Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
2019-11-20test: controls: Add ControlInfoMap testLaurent Pinchart
Add a test to exercise the ControlInfoMap API. This currently tests at(), count(), find() and end(). Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Jacopo Mondi <jacopo@jmondi.org> Niklas Söderlund <niklas.soderlund@ragnatech.se>
2019-11-20test: Extract CameraTest class out of camera tests to libtestLaurent Pinchart
Many tests other than the camera/ tests use a camera. To increase code sharing, move the base CameraTest class to the test library. The class becomes a helper that doesn't inherit from Test anymore (to avoid diamond inheritance issues when more such helpers will exist). Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
2019-10-13libcamera: controls: Support accessing controls by numerical IDLaurent Pinchart
The ControlList class has template get() and set() methods to get and set control values. The methods require a reference to a Control instance, which is only available when calling them with a hardcoded control. In order to support usage of ControlList for V4L2 controls, as well as serialisation and deserialisation of ControlList, we need a way to get and set control values based on a control numerical ID. Add new contains(), get() and set() overload methods to do so. As this change prepares the ControlList to be used for other objects than camera, update its documentation accordingly. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Jacopo Mondi <jacopo@jmondi.org> Tested-by: Niklas Söderlund <niklas.soderlund@ragnatech.se> Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
2019-10-05libcamera: controls: Use ControlValidator to validate ControlListLaurent Pinchart
Replace the manual validation of controls against a Camera with usage of the new ControlValidator interface. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
2019-10-05libcamera: controls: Rename ControlInfo to ControlRangeLaurent Pinchart
The ControlInfo class stores a range of valid values for a control. Its name is vague, as "info" has multiple meanings. Rename it to ControlRange. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Jacopo Mondi <jacopo@jmondi.org> Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
2019-10-05libcamera: controls: Remove ControlInfo::idLaurent Pinchart
The ControlInfo id member is only used in the toString() method of the class, and nowhere else externally. The same way that ControlValue doesn't store a ControlId, ControlInfo shouldn't. Remove it. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Jacopo Mondi <jacopo@jmondi.org> Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
2019-10-05libcamera: controls: Remove the unused ControlList::update() methodLaurent Pinchart
The ControlList::update() method is unused. While it is meant to fulfil a need of applications, having no user means that it is most probably not correctly designed. Remove the method, we will add it back later if needed. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Jacopo Mondi <jacopo@jmondi.org> Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
2019-10-04libcamera: controls: Improve the API towards applicationsLaurent Pinchart
Rework the control-related classes to improve the API towards applications. The goal is to enable writing code similar to Request *req = ...; ControlList &controls = req->controls(); controls->set(controls::AwbEnable, false); controls->set(controls::ManualExposure, 1000); ... int32_t exposure = controls->get(controls::ManualExposure); with the get and set operations ensuring type safety for the control values. This is achieved by creating the following classes: - Control defines controls and is the main way to reference a control. It is a template class to allow methods using it to refer to the control type. - ControlId is the base class of Control. It stores the control ID, name and type, and can be used in contexts where a control needs to be referenced regardless of its type (for instance in lists of controls). This class replaces ControlIdentifier. - ControlValue is kept as-is. The ControlList class now exposes two template get() and set() methods that replace the operator[]. They ensure type safety by infering the value type from the Control reference that they receive. The main way to refer to a control is now through the Control class, and optionally through its base ControlId class. The ControlId enumeration is removed, replaced by a list of global Control instances. Numerical control IDs are turned into macros, and are still exposed as they are required to communicate with IPAs (especially to deserialise control lists). They should however not be used by applications. Auto-generation of header and source files is removed for now to keep the change simple. It will be added back in the future in a more elaborate form. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
2019-10-04libcamera: controls: Use explicit 32-bit integer typesLaurent Pinchart
Make the control API more explicit when dealing with integer controls by specifying the size. We already do so for 64-bit integers, using int64_t and ControlTypeInteger64, do the same for 32-bit integers. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Jacopo Mondi <jacopo@jmondi.org> Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
2019-10-04libcamera: controls: Make ControlValue get/set accessors template methodsLaurent Pinchart
The ControlValue get accessors are implemented with functions of different names, whlie the set accessors use polymorphism to support different control types. This isn't very consistent and intuitive. Make the API clearer by using template methods. This will also have the added advantage that support for the new types will only require adding template specialisations, without adding new methods. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Jacopo Mondi <jacopo@jmondi.org> Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
2019-10-04libcamera: controls: Rename ControlValueType to ControlTypeLaurent Pinchart
The type of a control value is also the type of the control. Shorten the ControlValueType enumeration to ControlType, and rename ControlValue* to ControlType* for better clarity. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Jacopo Mondi <jacopo@jmondi.org> Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
2019-08-19libcamera: camera_manager: Construct CameraManager instances manuallyLaurent Pinchart
The CameraManager class is not supposed to be instantiated multiple times, which led to a singleton implementation. This requires a global instance of the CameraManager, which is destroyed when the global destructors are executed. Relying on global instances causes issues with cleanup, as the order in which the global destructors are run can't be controlled. In particular, the Android camera HAL implementation ends up destroying the CameraHalManager after the CameraManager, which leads to use-after-free problems. To solve this, remove the CameraManager::instance() method and make the CameraManager class instantiable directly. Multiple instances are still not allowed, and this is enforced by storing the instance pointer internally to be checked when an instance is created. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Jacopo Mondi <jacopo@jmondi.org>
2019-07-02libcamera: test: Add ControlList testsKieran Bingham
Add tests of the ControlList infrastructure and public API. Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
2019-07-02libcamera: test: Add ControlInfo testKieran Bingham
Provide an initial test coverage for the ControlInfo class. Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
2019-07-02libcamera: test: Add ControlValue testKieran Bingham
Add initial basic testing for the new ControlValue class. Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com> Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
' href='#n677'>677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
 * Copyright (C) 2019, Google Inc.
 *
 * rkisp1.cpp - Pipeline handler for Rockchip ISP1
 */

#include <algorithm>
#include <array>
#include <iomanip>
#include <memory>
#include <numeric>
#include <queue>

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

#include <libcamera/buffer.h>
#include <libcamera/camera.h>
#include <libcamera/control_ids.h>
#include <libcamera/formats.h>
#include <libcamera/ipa/core_ipa_interface.h>
#include <libcamera/ipa/rkisp1_ipa_interface.h>
#include <libcamera/ipa/rkisp1_ipa_proxy.h>
#include <libcamera/request.h>
#include <libcamera/stream.h>

#include "libcamera/internal/camera_sensor.h"
#include "libcamera/internal/delayed_controls.h"
#include "libcamera/internal/device_enumerator.h"
#include "libcamera/internal/ipa_manager.h"
#include "libcamera/internal/log.h"
#include "libcamera/internal/media_device.h"
#include "libcamera/internal/pipeline_handler.h"
#include "libcamera/internal/utils.h"
#include "libcamera/internal/v4l2_subdevice.h"
#include "libcamera/internal/v4l2_videodevice.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);
	int destroy(unsigned int frame);
	void clear();

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

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

class RkISP1CameraData : public CameraData
{
public:
	RkISP1CameraData(PipelineHandler *pipe, RkISP1MainPath *mainPath,
			 RkISP1SelfPath *selfPath)
		: CameraData(pipe), frame_(0), frameInfo_(pipe),
		  mainPath_(mainPath), selfPath_(selfPath)
	{
	}

	int loadIPA(unsigned int hwRevision);

	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_;

private:
	void queueFrameAction(unsigned int frame,
			      const ipa::rkisp1::RkISP1Action &action);

	void metadataReady(unsigned int frame, const ControlList &metadata);
};

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

	Status validate() override;

	const V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; }

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_;
};

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

	CameraConfiguration *generateConfiguration(Camera *camera,
		const StreamRoles &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 stop(Camera *camera) override;

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

	bool match(DeviceEnumerator *enumerator) override;

private:
	RkISP1CameraData *cameraData(const Camera *camera)
	{
		return static_cast<RkISP1CameraData *>(
			PipelineHandler::cameraData(camera));
	}

	friend RkISP1CameraData;
	friend RkISP1Frames;

	int initLinks(const Camera *camera, const CameraSensor *sensor,
		      const RkISP1CameraConfiguration &config);
	int createCamera(MediaEntity *sensor);
	void tryCompleteRequest(Request *request);
	void bufferReady(FrameBuffer *buffer);
	void paramReady(FrameBuffer *buffer);
	void statReady(FrameBuffer *buffer);
	void frameStart(uint32_t sequence);

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

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

	RkISP1MainPath mainPath_;
	RkISP1SelfPath selfPath_;

	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)
{
	unsigned int frame = data->frame_;

	if (pipe_->availableParamBuffers_.empty()) {
		LOG(RkISP1, Error) << "Parameters buffer underrun";
		return nullptr;
	}
	FrameBuffer *paramBuffer = pipe_->availableParamBuffers_.front();

	if (pipe_->availableStatBuffers_.empty()) {
		LOG(RkISP1, Error) << "Statisitc buffer underrun";
		return nullptr;
	}
	FrameBuffer *statBuffer = pipe_->availableStatBuffers_.front();

	FrameBuffer *mainPathBuffer = request->findBuffer(&data->mainPathStream_);
	FrameBuffer *selfPathBuffer = request->findBuffer(&data->selfPathStream_);

	pipe_->availableParamBuffers_.pop();
	pipe_->availableStatBuffers_.pop();

	RkISP1FrameInfo *info = new RkISP1FrameInfo;

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

	frameInfo_[frame] = info;

	return info;
}

int RkISP1Frames::destroy(unsigned int frame)
{
	RkISP1FrameInfo *info = find(frame);
	if (!info)
		return -ENOENT;

	pipe_->availableParamBuffers_.push(info->paramBuffer);
	pipe_->availableStatBuffers_.push(info->statBuffer);

	frameInfo_.erase(info->frame);

	delete info;

	return 0;
}

void RkISP1Frames::clear()
{
	for (const auto &entry : frameInfo_) {
		RkISP1FrameInfo *info = entry.second;

		pipe_->availableParamBuffers_.push(info->paramBuffer);
		pipe_->availableStatBuffers_.push(info->statBuffer);

		delete info;
	}

	frameInfo_.clear();
}

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

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

	LOG(RkISP1, Error) << "Can't locate info from frame";
	return nullptr;
}

RkISP1FrameInfo *RkISP1Frames::find(FrameBuffer *buffer)
{
	for (auto &itInfo : frameInfo_) {
		RkISP1FrameInfo *info = itInfo.second;

		if (info->paramBuffer == buffer ||
		    info->statBuffer == buffer ||
		    info->mainPathBuffer == buffer ||
		    info->selfPathBuffer == buffer)
			return info;
	}

	LOG(RkISP1, Error) << "Can't locate info from buffer";
	return nullptr;
}

RkISP1FrameInfo *RkISP1Frames::find(Request *request)
{
	for (auto &itInfo : frameInfo_) {
		RkISP1FrameInfo *info = itInfo.second;

		if (info->request == request)
			return info;
	}

	LOG(RkISP1, Error) << "Can't locate info from request";
	return nullptr;
}

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

	ipa_->queueFrameAction.connect(this,
				       &RkISP1CameraData::queueFrameAction);

	int ret = ipa_->init(hwRevision);
	if (ret < 0) {
		LOG(RkISP1, Error) << "IPA initialization failure";
		return ret;
	}

	return 0;
}

void RkISP1CameraData::queueFrameAction(unsigned int frame,
					const ipa::rkisp1::RkISP1Action &action)
{
	switch (action.op) {
	case ipa::rkisp1::ActionV4L2Set: {
		const ControlList &controls = action.controls;
		delayedCtrls_->push(controls);
		break;
	}
	case ipa::rkisp1::ActionParamFilled: {
		PipelineHandlerRkISP1 *pipe = static_cast<PipelineHandlerRkISP1 *>(pipe_);
		RkISP1FrameInfo *info = frameInfo_.find(frame);
		if (!info)
			break;

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

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

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

		break;
	}
	case ipa::rkisp1::ActionMetadata:
		metadataReady(frame, action.controls);
		break;
	default:
		LOG(RkISP1, Error) << "Unknown action " << action.op;
		break;
	}
}

void RkISP1CameraData::metadataReady(unsigned int frame, const ControlList &metadata)
{
	PipelineHandlerRkISP1 *pipe =
		static_cast<PipelineHandlerRkISP1 *>(pipe_);

	RkISP1FrameInfo *info = frameInfo_.find(frame);
	if (!info)
		return;

	info->request->metadata() = metadata;
	info->metadataProcessed = true;

	pipe->tryCompleteRequest(info->request);
}

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

bool RkISP1CameraConfiguration::fitsAllPaths(const StreamConfiguration &cfg)
{
	StreamConfiguration config;

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

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

	return true;
}

CameraConfiguration::Status RkISP1CameraConfiguration::validate()
{
	const CameraSensor *sensor = data_->sensor_.get();
	Status status = Valid;

	if (config_.empty())
		return Invalid;

	if (transform != Transform::Identity) {
		transform = Transform::Identity;
		status = Adjusted;
	}

	/* Cap the number of entries to the available streams. */
	if (config_.size() > 2) {
		config_.resize(2);
		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());

	bool mainPathAvailable = true;
	bool selfPathAvailable = true;
	for (unsigned int index : order) {
		StreamConfiguration &cfg = config_[index];

		/* Try to match stream without adjusting configuration. */
		if (mainPathAvailable) {
			StreamConfiguration tryCfg = cfg;
			if (data_->mainPath_->validate(&tryCfg) == Valid) {
				mainPathAvailable = false;
				cfg = tryCfg;
				cfg.setStream(const_cast<Stream *>(&data_->mainPathStream_));
				continue;
			}
		}

		if (selfPathAvailable) {
			StreamConfiguration tryCfg = cfg;
			if (data_->selfPath_->validate(&tryCfg) == Valid) {
				selfPathAvailable = false;
				cfg = tryCfg;
				cfg.setStream(const_cast<Stream *>(&data_->selfPathStream_));
				continue;
			}
		}

		/* Try to match stream allowing adjusting configuration. */
		if (mainPathAvailable) {
			StreamConfiguration tryCfg = cfg;
			if (data_->mainPath_->validate(&tryCfg) == Adjusted) {
				mainPathAvailable = false;
				cfg = tryCfg;
				cfg.setStream(const_cast<Stream *>(&data_->mainPathStream_));
				status = Adjusted;
				continue;
			}
		}

		if (selfPathAvailable) {
			StreamConfiguration tryCfg = cfg;
			if (data_->selfPath_->validate(&tryCfg) == Adjusted) {
				selfPathAvailable = false;
				cfg = tryCfg;
				cfg.setStream(const_cast<Stream *>(&data_->selfPathStream_));
				status = Adjusted;
				continue;
			}
		}

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

	/* Select the sensor format. */
	Size maxSize;
	for (const StreamConfiguration &cfg : config_)
		maxSize = std::max(maxSize, cfg.size);

	sensorFormat_ = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR12_1X12,
					    MEDIA_BUS_FMT_SGBRG12_1X12,
					    MEDIA_BUS_FMT_SGRBG12_1X12,
					    MEDIA_BUS_FMT_SRGGB12_1X12,
					    MEDIA_BUS_FMT_SBGGR10_1X10,
					    MEDIA_BUS_FMT_SGBRG10_1X10,
					    MEDIA_BUS_FMT_SGRBG10_1X10,
					    MEDIA_BUS_FMT_SRGGB10_1X10,
					    MEDIA_BUS_FMT_SBGGR8_1X8,
					    MEDIA_BUS_FMT_SGBRG8_1X8,
					    MEDIA_BUS_FMT_SGRBG8_1X8,
					    MEDIA_BUS_FMT_SRGGB8_1X8 },
					  maxSize);
	if (sensorFormat_.size.isNull())
		sensorFormat_.size = sensor->resolution();

	return status;
}

PipelineHandlerRkISP1::PipelineHandlerRkISP1(CameraManager *manager)
	: PipelineHandler(manager)
{
}

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

CameraConfiguration *PipelineHandlerRkISP1::generateConfiguration(Camera *camera,
	const StreamRoles &roles)
{
	RkISP1CameraData *data = cameraData(camera);
	CameraConfiguration *config = new RkISP1CameraConfiguration(camera, data);
	if (roles.empty())
		return config;

	bool mainPathAvailable = true;
	bool selfPathAvailable = true;
	for (const StreamRole role : roles) {
		bool useMainPath;

		switch (role) {
		case StreamRole::StillCapture: {
			useMainPath = mainPathAvailable;
			break;
		}
		case StreamRole::Viewfinder:
		case StreamRole::VideoRecording: {
			useMainPath = !selfPathAvailable;
			break;
		}
		default:
			LOG(RkISP1, Warning)
				<< "Requested stream role not supported: " << role;
			delete config;
			return nullptr;
		}

		StreamConfiguration cfg;
		if (useMainPath) {
			cfg = data->mainPath_->generateConfiguration(
				data->sensor_->resolution());
			mainPathAvailable = false;
		} else {
			cfg = data->selfPath_->generateConfiguration(
				data->sensor_->resolution());
			selfPathAvailable = false;
		}

		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, sensor, *config);
	if (ret)
		return ret;

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

	ret = sensor->setFormat(&format);
	if (ret < 0)
		return ret;

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

	ret = isp_->setFormat(0, &format);
	if (ret < 0)
		return ret;

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

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

	/* YUYV8_2X8 is required on the ISP source path pad for YUV output. */
	format.mbus_code = MEDIA_BUS_FMT_YUYV8_2X8;
	LOG(RkISP1, Debug)
		<< "Configuring ISP output pad with " << format.toString()
		<< " crop " << rect.toString();

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

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

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

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

	for (const StreamConfiguration &cfg : *config) {
		if (cfg.stream() == &data->mainPathStream_) {
			ret = mainPath_.configure(cfg, format);
			streamConfig[0] = IPAStream(cfg.pixelFormat,
						    cfg.size);
		} else {
			ret = selfPath_.configure(cfg, format);
			streamConfig[1] = IPAStream(cfg.pixelFormat,
						    cfg.size);
		}

		if (ret)
			return ret;
	}

	V4L2DeviceFormat paramFormat;
	paramFormat.fourcc = V4L2PixelFormat(V4L2_META_FMT_RK_ISP1_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. */
	CameraSensorInfo sensorInfo = {};
	ret = data->sensor_->sensorInfo(&sensorInfo);
	if (ret) {
		/* \todo Turn this into a hard failure. */
		LOG(RkISP1, Warning) << "Camera sensor information not available";
		sensorInfo = {};
		ret = 0;
	}

	std::map<uint32_t, ControlInfoMap> entityControls;
	entityControls.emplace(0, data->sensor_->controls());

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

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_)
		return mainPath_.exportBuffers(count, buffers);
	else if (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;

	unsigned int maxCount = std::max({
		data->mainPathStream_.configuration().bufferCount,
		data->selfPathStream_.configuration().bufferCount,
	});

	ret = param_->allocateBuffers(maxCount, &paramBuffers_);
	if (ret < 0)
		goto error;

	ret = stat_->allocateBuffers(maxCount, &statBuffers_);
	if (ret < 0)
		goto error;

	for (std::unique_ptr<FrameBuffer> &buffer : paramBuffers_) {
		buffer->setCookie(ipaBufferId++);
		data->ipaBuffers_.emplace_back(buffer->cookie(),
					       buffer->planes());
		availableParamBuffers_.push(buffer.get());
	}

	for (std::unique_ptr<FrameBuffer> &buffer : statBuffers_) {
		buffer->setCookie(ipaBufferId++);
		data->ipaBuffers_.emplace_back(buffer->cookie(),
					       buffer->planes());
		availableStatBuffers_.push(buffer.get());
	}

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

	return 0;

error:
	paramBuffers_.clear();
	statBuffers_.clear();

	return ret;
}

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

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

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

	paramBuffers_.clear();
	statBuffers_.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);
	int ret;

	/* Allocate buffers for internal pipeline usage. */
	ret = allocateBuffers(camera);
	if (ret)
		return ret;

	ret = data->ipa_->start();
	if (ret) {
		freeBuffers(camera);
		LOG(RkISP1, Error)
			<< "Failed to start IPA " << camera->id();
		return ret;
	}

	data->frame_ = 0;

	ret = param_->streamOn();
	if (ret) {
		data->ipa_->stop();
		freeBuffers(camera);
		LOG(RkISP1, Error)
			<< "Failed to start parameters " << camera->id();
		return ret;
	}

	ret = stat_->streamOn();
	if (ret) {
		param_->streamOff();
		data->ipa_->stop();
		freeBuffers(camera);
		LOG(RkISP1, Error)
			<< "Failed to start statistics " << camera->id();
		return ret;
	}

	if (data->mainPath_->isEnabled()) {
		ret = mainPath_.start();
		if (ret) {
			param_->streamOff();
			stat_->streamOff();
			data->ipa_->stop();
			freeBuffers(camera);
			return ret;
		}
	}

	if (data->selfPath_->isEnabled()) {
		ret = selfPath_.start();
		if (ret) {
			mainPath_.stop();
			param_->streamOff();
			stat_->streamOff();
			data->ipa_->stop();
			freeBuffers(camera);
			return ret;
		}
	}

	isp_->setFrameStartEnabled(true);

	activeCamera_ = camera;
	return ret;
}

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

	isp_->setFrameStartEnabled(false);

	data->ipa_->stop();

	selfPath_.stop();
	mainPath_.stop();

	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();

	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);
	if (!info)
		return -ENOENT;

	ipa::rkisp1::RkISP1Event ev;
	ev.op = ipa::rkisp1::EventQueueRequest;
	ev.frame = data->frame_;
	ev.bufferId = info->paramBuffer->cookie();
	ev.controls = request->controls();
	data->ipa_->processEvent(ev);

	data->frame_++;

	return 0;
}

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

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

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

	/*
	 * Configure the sensor links: enable the link corresponding to this
	 * camera.
	 */
	const MediaPad *pad = isp_->entity()->getPadByIndex(0);
	for (MediaLink *link : pad->links()) {
		if (link->source()->entity() != sensor->entity())
			continue;

		LOG(RkISP1, Debug)
			<< "Enabling link from sensor '"
			<< link->source()->entity()->name()
			<< "' to ISP";

		ret = link->setEnabled(true);
		if (ret < 0)
			return ret;
	}

	for (const StreamConfiguration &cfg : config) {
		if (cfg.stream() == &data->mainPathStream_)
			ret = data->mainPath_->setEnabled(true);
		else if (cfg.stream() == &data->selfPathStream_)
			ret = data->selfPath_->setEnabled(true);
		else
			return -EINVAL;

		if (ret < 0)
			return ret;
	}

	return 0;
}

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

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

	ControlInfoMap::Map ctrls;
	ctrls.emplace(std::piecewise_construct,
		      std::forward_as_tuple(&controls::AeEnable),
		      std::forward_as_tuple(false, true));

	data->controlInfo_ = std::move(ctrls);

	data->sensor_ = std::make_unique<CameraSensor>(sensor);
	ret = data->sensor_->init();
	if (ret)
		return ret;

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

	/*
	 * \todo Read dealy values from the sensor itself or from a
	 * a sensor database. For now use generic values taken from
	 * the Raspberry Pi and listed as generic values.
	 */
	std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
		{ V4L2_CID_ANALOGUE_GAIN, { 1, false } },
		{ V4L2_CID_EXPOSURE, { 2, false } },
	};

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

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

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

	return 0;
}

bool PipelineHandlerRkISP1::match(DeviceEnumerator *enumerator)
{
	const MediaPad *pad;

	DeviceMatch dm("rkisp1");
	dm.add("rkisp1_isp");
	dm.add("rkisp1_resizer_selfpath");
	dm.add("rkisp1_resizer_mainpath");
	dm.add("rkisp1_selfpath");
	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;
	}

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

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

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

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

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

	mainPath_.bufferReady().connect(this, &PipelineHandlerRkISP1::bufferReady);
	selfPath_.bufferReady().connect(this, &PipelineHandlerRkISP1::bufferReady);
	stat_->bufferReady.connect(this, &PipelineHandlerRkISP1::statReady);
	param_->bufferReady.connect(this, &PipelineHandlerRkISP1::paramReady);

	/*
	 * Enumerate all sensors connected to the ISP and create one
	 * camera instance for each of them.
	 */
	pad = isp_->entity()->getPadByIndex(0);
	if (!pad)
		return false;

	bool registered = false;
	for (MediaLink *link : pad->links()) {
		if (!createCamera(link->source()->entity()))
			registered = true;
	}

	return registered;
}

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

void PipelineHandlerRkISP1::tryCompleteRequest(Request *request)
{
	RkISP1CameraData *data = cameraData(activeCamera_);
	RkISP1FrameInfo *info = data->frameInfo_.find(request);
	if (!info)
		return;

	if (request->hasPendingBuffers())
		return;

	if (!info->metadataProcessed)
		return;

	if (!info->paramDequeued)
		return;

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

	completeRequest(request);
}

void PipelineHandlerRkISP1::bufferReady(FrameBuffer *buffer)
{
	Request *request = buffer->request();

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

void PipelineHandlerRkISP1::paramReady(FrameBuffer *buffer)
{
	if (buffer->metadata().status == FrameMetadata::FrameCancelled)
		return;

	ASSERT(activeCamera_);
	RkISP1CameraData *data = cameraData(activeCamera_);

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

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

void PipelineHandlerRkISP1::statReady(FrameBuffer *buffer)
{
	if (buffer->metadata().status == FrameMetadata::FrameCancelled)
		return;

	ASSERT(activeCamera_);
	RkISP1CameraData *data = cameraData(activeCamera_);

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

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

	ipa::rkisp1::RkISP1Event ev;
	ev.op = ipa::rkisp1::EventSignalStatBuffer;
	ev.frame = info->frame;
	ev.bufferId = info->statBuffer->cookie();
	data->ipa_->processEvent(ev);
}

REGISTER_PIPELINE_HANDLER(PipelineHandlerRkISP1)

} /* namespace libcamera */