[{"id":33122,"web_url":"https://patchwork.libcamera.org/comment/33122/","msgid":"<2a0b1026-a6cb-4a7b-9808-224d9ed31254@ideasonboard.com>","date":"2025-01-21T15:53:34","subject":"Re: [PATCH v2 1/2] libcamera: pipeline: Add C3 ISP pipeline handler","submitter":{"id":156,"url":"https://patchwork.libcamera.org/api/people/156/","name":"Dan Scally","email":"dan.scally@ideasonboard.com"},"content":"Hello Keke - thank you for the patches.\n\nOn 27/12/2024 10:58, Keke Li wrote:\n> The Amlogic C3 ISP pipeline handler supports\n> 3-channel image output, 1-channel 3A statistical\n> information ouput and 1-channel parameters input.\n>\n> Signed-off-by: Keke Li <keke.li@amlogic.com>\n> ---\n>   include/libcamera/ipa/c3isp.mojom         |   42 +\n>   include/libcamera/ipa/meson.build         |    1 +\n>   include/linux/videodev2.h                 |    4 +\n>   meson_options.txt                         |    1 +\n>   src/libcamera/pipeline/c3-isp/c3-isp.cpp  | 1161 +++++++++++++++++++++\n>   src/libcamera/pipeline/c3-isp/meson.build |    5 +\n>   6 files changed, 1214 insertions(+)\n>   create mode 100644 include/libcamera/ipa/c3isp.mojom\n>   create mode 100644 src/libcamera/pipeline/c3-isp/c3-isp.cpp\n>   create mode 100644 src/libcamera/pipeline/c3-isp/meson.build\n>\n> diff --git a/include/libcamera/ipa/c3isp.mojom b/include/libcamera/ipa/c3isp.mojom\n> new file mode 100644\n> index 00000000..e41fb243\n> --- /dev/null\n> +++ b/include/libcamera/ipa/c3isp.mojom\n> @@ -0,0 +1,42 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +\n> +/*\n> + * \\todo Document the interface and remove the related EXCLUDE_PATTERNS entry.\n> + */\nWell we haven't gotten around to documenting the interface yet so the related EXCLUDE_PATTERNS entry \nis still there, and this will need adding to it. In Documentation/doxyfile-common.in under the \nEXCLUDE_PATTERNS entry you should add \"@TOP_BUILDDIR@/include/libcamera/ipa/c3*.h \\\". That will \nsilence the warnings from Doxygen on build.\n> +\n> +module ipa.c3isp;\n> +\n> +import \"include/libcamera/ipa/core.mojom\";\n> +\n> +struct IPAConfigInfo {\n> +\tlibcamera.IPACameraSensorInfo sensorInfo;\n> +\tlibcamera.ControlInfoMap sensorControls;\n> +\tuint32 paramFormat;\n> +};\n> +\n> +interface IPAC3ISPInterface {\n> +\tinit(libcamera.IPASettings settings,\n> +\t     uint32 hwRevision,\n> +\t     libcamera.IPACameraSensorInfo sensorInfo,\n> +\t     libcamera.ControlInfoMap sensorControls)\n> +\t\t=> (int32 ret, libcamera.ControlInfoMap ipaControls);\n> +\tstart() => (int32 ret);\n> +\tstop();\n> +\n> +\tconfigure(IPAConfigInfo configInfo)\n> +\t\t=> (int32 ret, libcamera.ControlInfoMap ipaControls);\n> +\n> +\tmapBuffers(array<libcamera.IPABuffer> buffers);\n> +\tunmapBuffers(array<uint32> ids);\n> +\n> +\t[async] queueRequest(uint32 frame, libcamera.ControlList reqControls);\n> +\t[async] fillParamsBuffer(uint32 frame, uint32 bufferId);\ns/fillParamsBuffer/computeParams please\n> +\t[async] processStatsBuffer(uint32 frame, uint32 bufferId,\ns/processStatsBuffer/processStats please\n> +\t\t\t\t   libcamera.ControlList sensorControls);\n> +};\n> +\n> +interface IPAC3ISPEventInterface {\n> +\tparamsBufferReady(uint32 frame, uint32 bytesused);\ns/paramsBufferReady/paramsComputed please\n> +\tsetSensorControls(uint32 frame, libcamera.ControlList sensorControls);\n> +\tmetadataReady(uint32 frame, libcamera.ControlList metadata);\n> +};\n> diff --git a/include/libcamera/ipa/meson.build b/include/libcamera/ipa/meson.build\n> index 3129f119..39c55c07 100644\n> --- a/include/libcamera/ipa/meson.build\n> +++ b/include/libcamera/ipa/meson.build\n> @@ -63,6 +63,7 @@ libcamera_ipa_headers += custom_target('core_ipa_serializer_h',\n>   \n>   # Mapping from pipeline handler name to mojom file\n>   pipeline_ipa_mojom_mapping = {\n> +    'c3-isp': 'c3isp.mojom',\n>       'ipu3': 'ipu3.mojom',\n>       'mali-c55': 'mali-c55.mojom',\n>       'rkisp1': 'rkisp1.mojom',\n> diff --git a/include/linux/videodev2.h b/include/linux/videodev2.h\n> index d2653b2e..3227ac1e 100644\n> --- a/include/linux/videodev2.h\n> +++ b/include/linux/videodev2.h\n> @@ -831,6 +831,10 @@ struct v4l2_pix_format {\n>   #define V4L2_META_FMT_RK_ISP1_STAT_3A\tv4l2_fourcc('R', 'K', '1', 'S') /* Rockchip ISP1 3A Statistics */\n>   #define V4L2_META_FMT_RK_ISP1_EXT_PARAMS\tv4l2_fourcc('R', 'K', '1', 'E') /* Rockchip ISP1 3a Extensible Parameters */\n>   \n> +/* Vendor specific - used for C3 ISP */\n> +#define V4L2_META_FMT_C3ISP_PARAMS\tv4l2_fourcc('C', '3', 'P', 'M') /* Amlogic C3 ISP Parameters */\n> +#define V4L2_META_FMT_C3ISP_STATS\tv4l2_fourcc('C', '3', 'S', 'T') /* Amlogic C3 ISP Statistics */\n> +\nSeparate commits for this change please; in fact the series could do with being broken down quite a \nbit more generally I think...in its current form the pipeline handler would be unusable at this \ncommit because loadIPA() would never work\n>   /* Vendor specific - used for RaspberryPi PiSP */\n>   #define V4L2_META_FMT_RPI_BE_CFG\tv4l2_fourcc('R', 'P', 'B', 'C') /* PiSP BE configuration */\n>   \n> diff --git a/meson_options.txt b/meson_options.txt\n> index 1dc3b4cd..d59f4c04 100644\n> --- a/meson_options.txt\n> +++ b/meson_options.txt\n> @@ -46,6 +46,7 @@ option('pipelines',\n>           choices : [\n>               'all',\n>               'auto',\n> +            'c3-isp',\n>               'imx8-isi',\n>               'ipu3',\n>               'mali-c55',\n> diff --git a/src/libcamera/pipeline/c3-isp/c3-isp.cpp b/src/libcamera/pipeline/c3-isp/c3-isp.cpp\n> new file mode 100644\n> index 00000000..524027d4\n> --- /dev/null\n> +++ b/src/libcamera/pipeline/c3-isp/c3-isp.cpp\n> @@ -0,0 +1,1161 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2024, Amlogic Inc.\n> + *\n> + * Pipeline Handler for Amlogic C3 ISP\n> + */\n> +\n> +#include <algorithm>\n> +#include <array>\n> +#include <map>\n> +#include <memory>\n> +#include <queue>\n> +#include <set>\n> +#include <string>\n> +\n> +#include <linux/media-bus-format.h>\n> +#include <linux/media.h>\n> +\n> +#include <libcamera/base/log.h>\n> +\n> +#include <libcamera/camera.h>\n> +#include <libcamera/formats.h>\n> +#include <libcamera/geometry.h>\n> +#include <libcamera/stream.h>\n> +\n> +#include <libcamera/ipa/c3isp_ipa_interface.h>\n> +#include <libcamera/ipa/c3isp_ipa_proxy.h>\n> +\n> +#include \"libcamera/internal/bayer_format.h\"\n> +#include \"libcamera/internal/camera.h\"\n> +#include \"libcamera/internal/camera_sensor.h\"\n> +#include \"libcamera/internal/delayed_controls.h\"\n> +#include \"libcamera/internal/device_enumerator.h\"\n> +#include \"libcamera/internal/framebuffer.h\"\n> +#include \"libcamera/internal/ipa_manager.h\"\n> +#include \"libcamera/internal/media_device.h\"\n> +#include \"libcamera/internal/pipeline_handler.h\"\n> +#include \"libcamera/internal/v4l2_subdevice.h\"\n> +#include \"libcamera/internal/v4l2_videodevice.h\"\n> +\n> +namespace libcamera {\n> +\n> +LOG_DEFINE_CATEGORY(C3ISP)\n> +\n> +class PipelineHandlerC3ISP;\n> +class C3ISPCameraData;\n> +\n> +const std::map<libcamera::PixelFormat, unsigned int> C3ISPFmtToCode = {\n> +\t{ formats::R8, MEDIA_BUS_FMT_YUV8_1X24 },\n> +\t{ formats::NV12, MEDIA_BUS_FMT_YUV8_1X24 },\n> +\t{ formats::NV21, MEDIA_BUS_FMT_YUV8_1X24 },\n> +\t{ formats::NV16, MEDIA_BUS_FMT_YUV8_1X24 },\n> +\t{ formats::NV61, MEDIA_BUS_FMT_YUV8_1X24 },\n> +};\n> +\n> +constexpr Size kC3ISPMinSize = { 160, 120 };\n> +constexpr Size kC3ISPMaxSize = { 2888, 2240 };\n> +\n> +struct C3ISPFrameInfo {\n> +\tunsigned int frame;\n> +\tRequest *request;\n> +\n> +\tFrameBuffer *paramBuffer;\n> +\tFrameBuffer *statBuffer;\n> +\tFrameBuffer *viewBuffer;\n> +\tFrameBuffer *stillBuffer;\n> +\tFrameBuffer *videoBuffer;\n> +\n> +\tbool paramDequeued;\n> +\tbool metadataProcessed;\n> +};\n> +\n> +class C3ISPFrames\n> +{\n> +public:\n> +\tC3ISPFrames(PipelineHandler *pipe);\n> +\n> +\tC3ISPFrameInfo *create(const C3ISPCameraData *data, Request *request);\n> +\tint destroy(unsigned int frame);\n> +\tvoid clear();\n> +\n> +\tC3ISPFrameInfo *find(unsigned int frame);\n> +\tC3ISPFrameInfo *find(FrameBuffer *buffer);\n> +\tC3ISPFrameInfo *find(Request *request);\n> +\n> +private:\n> +\tPipelineHandlerC3ISP *pipe_;\n> +\tstd::map<unsigned int, C3ISPFrameInfo *> frameInfo_;\n> +};\n> +\n> +class C3ISPCameraData : public Camera::Private\n> +{\n> +public:\n> +\tC3ISPCameraData(PipelineHandler *pipe, MediaEntity *entity)\n> +\t\t: Camera::Private(pipe), entity_(entity), frame_(0), frameInfo_(pipe)\n> +\t{\n> +\t}\n> +\n> +\tint init();\n> +\tPipelineHandlerC3ISP *pipe();\n> +\tint loadIPA(unsigned int hwRevision);\n> +\n> +\tMediaEntity *entity_;\n> +\tstd::unique_ptr<CameraSensor> sensor_;\n> +\tstd::unique_ptr<DelayedControls> delayedCtrls_;\n> +\tstd::unique_ptr<V4L2Subdevice> csi_;\n> +\tstd::unique_ptr<V4L2Subdevice> adap_;\n> +\tStream viewStream;\n> +\tStream stillStream;\n> +\tStream videoStream;\n> +\tunsigned int frame_;\n> +\tstd::vector<IPABuffer> ipaBuffers_;\n> +\tC3ISPFrames frameInfo_;\n> +\n> +\tstd::unique_ptr<ipa::c3isp::IPAProxyC3ISP> ipa_;\n> +\n> +private:\n> +\tvoid paramFilled(unsigned int frame, unsigned int bytesused);\n> +\tvoid setSensorControls(unsigned int frame,\n> +\t\t\t       const ControlList &sensorControls);\n> +\tvoid metadataReady(unsigned int frame, const ControlList &metadata);\n> +};\n> +\n> +class C3ISPCameraConfiguration : public CameraConfiguration\n> +{\n> +public:\n> +\tC3ISPCameraConfiguration(C3ISPCameraData *data)\n> +\t\t: CameraConfiguration(), data_(data)\n> +\t{\n> +\t}\n> +\n> +\tStatus validate() override;\n> +\n> +\tV4L2SubdeviceFormat sensorFormat_;\n> +\n> +private:\n> +\tstatic constexpr unsigned int kMaxStreams = 3;\n> +\n> +\tconst C3ISPCameraData *data_;\n> +};\n> +\n> +class PipelineHandlerC3ISP : public PipelineHandler\n> +{\n> +public:\n> +\tPipelineHandlerC3ISP(CameraManager *manager);\n> +\n> +\tstd::unique_ptr<CameraConfiguration> generateConfiguration(Camera *camera,\n> +\t\t\t\t\t\t\t\t   Span<const StreamRole> roles) override;\n> +\tint configure(Camera *camera, CameraConfiguration *config) override;\n> +\n> +\tint exportFrameBuffers(Camera *camera, Stream *stream,\n> +\t\t\t       std::vector<std::unique_ptr<FrameBuffer>> *buffers) override;\n> +\n> +\tint start(Camera *camera, const ControlList *controls) override;\n> +\tvoid stopDevice(Camera *camera) override;\n> +\n> +\tint queueRequestDevice(Camera *camera, Request *request) override;\n> +\n> +\tvoid bufferReady(FrameBuffer *buffer);\n> +\tvoid statReady(FrameBuffer *buffer);\n> +\tvoid paramReady(FrameBuffer *buffer);\n> +\n> +\tbool match(DeviceEnumerator *enumerator) override;\n> +\n> +private:\n> +\tfriend C3ISPCameraData;\n> +\tfriend C3ISPFrames;\n> +\n> +\tstruct C3ISPPipe {\n> +\t\tstd::unique_ptr<V4L2Subdevice> resizer;\n> +\t\tstd::unique_ptr<V4L2VideoDevice> cap;\n> +\t\tStream *stream;\n> +\t};\n> +\n> +\tenum {\n> +\t\tC3ISPVIEW,\n> +\t\tC3ISPSTILL,\n> +\t\tC3ISPVIDEO,\n> +\t\tC3ISPNumPipes,\n> +\t};\n> +\n> +\tC3ISPCameraData *cameraData(Camera *camera)\n> +\t{\n> +\t\treturn static_cast<C3ISPCameraData *>(camera->_d());\n> +\t}\n> +\n> +\tC3ISPPipe *pipeFromStream(C3ISPCameraData *data, Stream *stream)\n> +\t{\n> +\t\tif (stream == &data->viewStream)\n> +\t\t\treturn &pipes_[C3ISPVIEW];\n> +\t\telse if (stream == &data->stillStream)\n> +\t\t\treturn &pipes_[C3ISPSTILL];\n> +\t\telse if (stream == &data->videoStream)\n> +\t\t\treturn &pipes_[C3ISPVIDEO];\n> +\t\telse\n> +\t\t\tLOG(C3ISP, Fatal) << \"Invalid stream: \" << stream;\n> +\n> +\t\treturn nullptr;\n> +\t}\n> +\n> +\tC3ISPPipe *pipeFromStream(C3ISPCameraData *data, const Stream *stream)\n> +\t{\n> +\t\treturn pipeFromStream(data, const_cast<Stream *>(stream));\n> +\t}\n> +\n> +\tvoid resetPipes()\n> +\t{\n> +\t\tfor (C3ISPPipe &pipe : pipes_)\n> +\t\t\tpipe.stream = nullptr;\n> +\t}\n> +\n> +\tint pipeStart();\n> +\tvoid pipeStop();\n> +\n> +\tint setConfigStreams(CameraConfiguration *config);\n> +\tint configureProcessedStream(C3ISPCameraData *data,\n> +\t\t\t\t     const StreamConfiguration &config,\n> +\t\t\t\t     V4L2SubdeviceFormat &subdevFormat);\n> +\n> +\tbool createCamera(MediaLink *ispLink);\n> +\tvoid tryCompleteRequest(C3ISPFrameInfo *info);\n> +\tint allocateBuffers(Camera *camera);\n> +\tint freeBuffers(Camera *camera);\n> +\n> +\tMediaDevice *media_;\n> +\tstd::unique_ptr<V4L2Subdevice> isp_;\n> +\tstd::unique_ptr<V4L2VideoDevice> param_;\n> +\tstd::unique_ptr<V4L2VideoDevice> stat_;\n> +\n> +\tstd::vector<std::unique_ptr<FrameBuffer>> paramBuffers_;\n> +\tstd::vector<std::unique_ptr<FrameBuffer>> statBuffers_;\n> +\tstd::queue<FrameBuffer *> availableParamBuffers_;\n> +\tstd::queue<FrameBuffer *> availableStatBuffers_;\n> +\n> +\tstd::vector<Stream *> streams_;\n> +\tstd::array<C3ISPPipe, C3ISPNumPipes> pipes_;\n> +\n> +\tCamera *activeCamera_;\n> +};\n> +\n> +C3ISPFrames::C3ISPFrames(PipelineHandler *pipe)\n> +\t: pipe_(static_cast<PipelineHandlerC3ISP *>(pipe))\n> +{\n> +}\n> +\n> +C3ISPFrameInfo *C3ISPFrames::create(const C3ISPCameraData *data, Request *request)\n> +{\n> +\tunsigned int frame = data->frame_;\n> +\n> +\tFrameBuffer *paramBuffer = nullptr;\n> +\tFrameBuffer *statBuffer = nullptr;\n> +\n> +\tif (pipe_->availableParamBuffers_.empty()) {\n> +\t\tLOG(C3ISP, Error) << \"Param buffer queue empty\";\n> +\t\treturn nullptr;\n> +\t}\n> +\n> +\tif (pipe_->availableStatBuffers_.empty()) {\n> +\t\tLOG(C3ISP, Error) << \"Stat buffer queue empty\";\n> +\t\treturn nullptr;\n> +\t}\n> +\n> +\tparamBuffer = pipe_->availableParamBuffers_.front();\n> +\tpipe_->availableParamBuffers_.pop();\n> +\n> +\tstatBuffer = pipe_->availableStatBuffers_.front();\n> +\tpipe_->availableStatBuffers_.pop();\n> +\n> +\tFrameBuffer *viewBuffer = request->findBuffer(&data->viewStream);\n> +\tFrameBuffer *stillBuffer = request->findBuffer(&data->stillStream);\n> +\tFrameBuffer *videoBuffer = request->findBuffer(&data->videoStream);\n> +\n> +\tC3ISPFrameInfo *info = new C3ISPFrameInfo;\n> +\n> +\tinfo->frame = frame;\n> +\tinfo->request = request;\n> +\tinfo->paramBuffer = paramBuffer;\n> +\tinfo->statBuffer = statBuffer;\n> +\tinfo->viewBuffer = viewBuffer;\n> +\tinfo->stillBuffer = stillBuffer;\n> +\tinfo->videoBuffer = videoBuffer;\n> +\tinfo->paramDequeued = false;\n> +\tinfo->metadataProcessed = false;\n> +\n> +\tframeInfo_[frame] = info;\n> +\n> +\treturn info;\n> +}\n> +\n> +int C3ISPFrames::destroy(unsigned int frame)\n> +{\n> +\tC3ISPFrameInfo *info = find(frame);\n> +\tif (!info)\n> +\t\treturn -ENOENT;\n> +\n> +\tpipe_->availableParamBuffers_.push(info->paramBuffer);\n> +\tpipe_->availableStatBuffers_.push(info->statBuffer);\n> +\n> +\tframeInfo_.erase(info->frame);\n> +\n> +\tdelete info;\n> +\n> +\treturn 0;\n> +}\n> +\n> +void C3ISPFrames::clear()\n> +{\n> +\tfor (const auto &entry : frameInfo_) {\n> +\t\tC3ISPFrameInfo *info = entry.second;\n> +\n> +\t\tpipe_->availableParamBuffers_.push(info->paramBuffer);\n> +\t\tpipe_->availableStatBuffers_.push(info->statBuffer);\n> +\n> +\t\tdelete info;\n> +\t}\n> +\n> +\tframeInfo_.clear();\n> +}\n> +\n> +C3ISPFrameInfo *C3ISPFrames::find(unsigned int frame)\n> +{\n> +\tauto itInfo = frameInfo_.find(frame);\n> +\n> +\tif (itInfo != frameInfo_.end())\n> +\t\treturn itInfo->second;\n> +\n> +\tLOG(C3ISP, Fatal) << \"Can't locate info from frame\";\n> +\n> +\treturn nullptr;\n> +}\n> +\n> +C3ISPFrameInfo *C3ISPFrames::find(FrameBuffer *buffer)\n> +{\n> +\tfor (auto &itInfo : frameInfo_) {\n> +\t\tC3ISPFrameInfo *info = itInfo.second;\n> +\n> +\t\tif (info->paramBuffer == buffer ||\n> +\t\t    info->statBuffer == buffer ||\n> +\t\t    info->viewBuffer == buffer ||\n> +\t\t    info->stillBuffer == buffer ||\n> +\t\t    info->videoBuffer == buffer)\n> +\t\t\treturn info;\n> +\t}\n> +\n> +\tLOG(C3ISP, Fatal) << \"Can't locate info from buffer\";\n> +\n> +\treturn nullptr;\n> +}\n> +\n> +C3ISPFrameInfo *C3ISPFrames::find(Request *request)\n> +{\n> +\tfor (auto &itInfo : frameInfo_) {\n> +\t\tC3ISPFrameInfo *info = itInfo.second;\n> +\n> +\t\tif (info->request == request)\n> +\t\t\treturn info;\n> +\t}\n> +\n> +\tLOG(C3ISP, Fatal) << \"Can't locate info from request\";\n> +\n> +\treturn nullptr;\n> +}\n> +\n> +int C3ISPCameraData::init()\n> +{\n> +\tint ret;\n> +\n> +\t/* Register a CameraSensor */\n> +\tsensor_ = CameraSensorFactoryBase::create(entity_);\n> +\tif (!sensor_)\n> +\t\treturn ret;\nret is uninitialised here. If sensor is a nullptr return -ENODEV. This is the only use of ret in the \nfunction, so the variable can be dropped.\n> +\n> +\tconst MediaPad *sensorSrc = entity_->getPadByIndex(0);\n> +\tMediaEntity *csiEntity = sensorSrc->links()[0]->sink()->entity();\n> +\n> +\tcsi_ = std::make_unique<V4L2Subdevice>(csiEntity);\n> +\tif (csi_->open()) {\n> +\t\tLOG(C3ISP, Error) << \"Failed to open CSI-2 subdevice\";\n> +\t\treturn false;\n> +\t}\n> +\n> +\tconst MediaPad *csiSrc = csiEntity->getPadByIndex(1);\n> +\tMediaEntity *adapEntity = csiSrc->links()[0]->sink()->entity();\n> +\n> +\tadap_ = std::make_unique<V4L2Subdevice>(adapEntity);\n> +\tif (adap_->open()) {\n> +\t\tLOG(C3ISP, Error) << \"Failed to open adapter subdevice\";\n> +\t\treturn false;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +PipelineHandlerC3ISP *C3ISPCameraData::pipe()\n> +{\n> +\treturn static_cast<PipelineHandlerC3ISP *>(Camera::Private::pipe());\n> +}\n> +\n> +int C3ISPCameraData::loadIPA(unsigned int hwRevision)\n> +{\n> +\tipa_ = IPAManager::createIPA<ipa::c3isp::IPAProxyC3ISP>(pipe(), 1, 1);\n> +\tif (!ipa_)\n> +\t\treturn -ENOENT;\n> +\n> +\tipa_->setSensorControls.connect(this, &C3ISPCameraData::setSensorControls);\n> +\tipa_->paramsBufferReady.connect(this, &C3ISPCameraData::paramFilled);\n> +\tipa_->metadataReady.connect(this, &C3ISPCameraData::metadataReady);\n> +\n> +\t/*\n> +\t * The API tuning file is made from the sensor name unless the\n> +\t * environment variable overrides it.\n> +\t */\n> +\tstd::string ipaTuningFile;\n> +\tchar const *configFromEnv = utils::secure_getenv(\"LIBCAMERA_C3ISP_TUNING_FILE\");\n> +\tif (!configFromEnv || *configFromEnv == '\\0') {\n> +\t\tipaTuningFile = ipa_->configurationFile(sensor_->model() + \".yaml\");\n> +\t\tif (ipaTuningFile.empty())\n> +\t\t\tipaTuningFile = ipa_->configurationFile(\"uncalibrated.yaml\");\n\nipa_->configurationFile() has second parameter (which has a default, allowing it to work here) \nallowing you to pass a default filename as the second parameter, so this can just be\n\n\nipaTuningFile = ipa_->configurationFile(sensor_->model() + \".yaml\", \"uncalibrated.yaml\");\n\n> +\t} else {\n> +\t\tipaTuningFile = std::string(configFromEnv);\n> +\t}\n> +\n> +\tIPACameraSensorInfo sensorInfo{};\n> +\tint ret = sensor_->sensorInfo(&sensorInfo);\n> +\tif (ret) {\n> +\t\tLOG(C3ISP, Error) << \"Invalid semsor information\";\ns/semsor/sensor\n> +\t\treturn ret;\n> +\t}\n> +\n> +\tret = ipa_->init({ ipaTuningFile, sensor_->model() }, hwRevision,\n> +\t\t\t sensorInfo, sensor_->controls(), &controlInfo_);\n> +\tif (ret) {\n> +\t\tLOG(C3ISP, Error) << \"Failed to initialize IPA\";\n> +\t\treturn ret;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +void C3ISPCameraData::paramFilled(unsigned int frame, unsigned int bytesused)\n> +{\n> +\tPipelineHandlerC3ISP *pipe = C3ISPCameraData::pipe();\n> +\tC3ISPFrameInfo *info = frameInfo_.find(frame);\n> +\tif (!info)\n> +\t\treturn;\n> +\n> +\tinfo->paramBuffer->_d()->metadata().planes()[0].bytesused = bytesused;\n> +\tpipe->param_->queueBuffer(info->paramBuffer);\n> +\tpipe->stat_->queueBuffer(info->statBuffer);\n> +\n> +\tif (info->viewBuffer)\n> +\t\tpipe->pipes_[PipelineHandlerC3ISP::C3ISPVIEW].cap->queueBuffer(info->viewBuffer);\n> +\n> +\tif (info->stillBuffer)\n> +\t\tpipe->pipes_[PipelineHandlerC3ISP::C3ISPSTILL].cap->queueBuffer(info->stillBuffer);\n> +\n> +\tif (info->videoBuffer)\n> +\t\tpipe->pipes_[PipelineHandlerC3ISP::C3ISPVIDEO].cap->queueBuffer(info->videoBuffer);\n> +}\n> +\n> +void C3ISPCameraData::setSensorControls([[maybe_unused]] unsigned int frame,\n> +\t\t\t\t\tconst ControlList &sensorControls)\n> +{\n> +\tdelayedCtrls_->push(sensorControls);\n> +}\n> +\n> +void C3ISPCameraData::metadataReady(unsigned int frame, const ControlList &metadata)\n> +{\n> +\tC3ISPFrameInfo *info = frameInfo_.find(frame);\n> +\tif (!info)\n> +\t\treturn;\n> +\n> +\tinfo->request->metadata().merge(metadata);\n> +\tinfo->metadataProcessed = true;\n> +\n> +\tpipe()->tryCompleteRequest(info);\n> +}\n> +\n> +namespace {\n> +\n> +const std::map<PixelFormat, uint32_t> rawFormats = {\n> +\t{ formats::SBGGR10, MEDIA_BUS_FMT_SBGGR10_1X10 },\n> +\t{ formats::SGBRG10, MEDIA_BUS_FMT_SGBRG10_1X10 },\n> +\t{ formats::SGRBG10, MEDIA_BUS_FMT_SGRBG10_1X10 },\n> +\t{ formats::SRGGB10, MEDIA_BUS_FMT_SRGGB10_1X10 },\n> +\t{ formats::SBGGR12, MEDIA_BUS_FMT_SBGGR12_1X12 },\n> +\t{ formats::SGBRG12, MEDIA_BUS_FMT_SGBRG12_1X12 },\n> +\t{ formats::SGRBG12, MEDIA_BUS_FMT_SGRBG12_1X12 },\n> +\t{ formats::SRGGB12, MEDIA_BUS_FMT_SRGGB12_1X12 },\n> +};\n> +\n> +};\n> +\n> +CameraConfiguration::Status C3ISPCameraConfiguration::validate()\n> +{\n> +\tconst CameraSensor *sensor = data_->sensor_.get();\n> +\tStatus status = Valid;\n> +\n> +\tif (config_.empty())\n> +\t\treturn Invalid;\n> +\n> +\tif (config_.size() > kMaxStreams) {\n> +\t\tconfig_.resize(kMaxStreams);\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\tSize maxSize;\n> +\n> +\tfor (StreamConfiguration &config : config_) {\n> +\t\tconst auto it = C3ISPFmtToCode.find(config.pixelFormat);\n> +\t\tif (it == C3ISPFmtToCode.end()) {\n> +\t\t\tLOG(C3ISP, Debug)\n> +\t\t\t\t<< \"Format adjusted to \" << formats::NV12;\n> +\t\t\tconfig.pixelFormat = formats::NV12;\n> +\t\t\tstatus = Adjusted;\n> +\t\t}\n> +\n> +\t\tSize size = std::clamp(config.size, kC3ISPMinSize, kC3ISPMaxSize);\n> +\t\tif (size != config.size) {\n> +\t\t\tLOG(C3ISP, Debug)\n> +\t\t\t\t<< \"Size adjusted to \" << size;\n> +\t\t\tconfig.size = size;\n> +\t\t\tstatus = Adjusted;\n> +\t\t}\n> +\n> +\t\tmaxSize = std::max(maxSize, config.size);\n> +\t}\n> +\n> +\tstd::vector<unsigned int> mbusCodes;\n> +\n> +\tstd::transform(rawFormats.begin(), rawFormats.end(),\n> +\t\t       std::back_inserter(mbusCodes),\n> +\t\t       [](const auto &value) { return value.second; });\n> +\n> +\tsensorFormat_ = sensor->getFormat(mbusCodes, maxSize);\n> +\n> +\tif (sensorFormat_.size.isNull())\n> +\t\tsensorFormat_.size = sensor->resolution();\n> +\n> +\treturn status;\n> +}\n> +\n> +PipelineHandlerC3ISP::PipelineHandlerC3ISP(CameraManager *manager)\n> +\t: PipelineHandler(manager)\n> +{\n> +}\n> +\n> +std::unique_ptr<CameraConfiguration>\n> +PipelineHandlerC3ISP::generateConfiguration(Camera *camera,\n> +\t\t\t\t\t    Span<const StreamRole> roles)\n> +{\n> +\tC3ISPCameraData *data = cameraData(camera);\n> +\tstd::unique_ptr<CameraConfiguration> config =\n> +\t\tstd::make_unique<C3ISPCameraConfiguration>(data);\n> +\n> +\tif (roles.empty())\n> +\t\treturn config;\n> +\n> +\tstreams_.clear();\n> +\n> +\tfor (const StreamRole &role : roles) {\n> +\t\tPixelFormat pixelFormat;\n> +\t\tSize size = std::min(Size{ 1920, 1080 }, data->sensor_->resolution());\n> +\n> +\t\tswitch (role) {\n> +\t\tcase StreamRole::StillCapture:\n> +\t\t\tpixelFormat = formats::NV12;\n> +\t\t\tstreams_.push_back(&data->stillStream);\n> +\t\t\tbreak;\n> +\n> +\t\tcase StreamRole::VideoRecording:\n> +\t\t\tpixelFormat = formats::NV12;\n> +\t\t\tstreams_.push_back(&data->videoStream);\n> +\t\t\tbreak;\n> +\n> +\t\tcase StreamRole::Viewfinder:\n> +\t\t\tpixelFormat = formats::NV12;\n> +\t\t\tstreams_.push_back(&data->viewStream);\n> +\t\t\tbreak;\n> +\n> +\t\tdefault:\n> +\t\t\tLOG(C3ISP, Error) << \"Invalid stream role: \" << role;\n> +\t\t\treturn nullptr;\nWe can't get a RAW image out of the ISP at all then? Is it a hardware limitation? Or the kernel driver?\n> +\t\t}\n> +\n> +\t\tstd::map<PixelFormat, std::vector<SizeRange>> formats;\n> +\t\tfor (const auto &c3Format : C3ISPFmtToCode) {\n> +\t\t\tPixelFormat pixFmt = c3Format.first;\n> +\t\t\tSize maxSize = std::min(kC3ISPMaxSize, data->sensor_->resolution());\n> +\t\t\tformats[pixFmt] = { kC3ISPMinSize, maxSize };\n> +\t\t}\n> +\n> +\t\tStreamFormats streamFormats(formats);\n> +\t\tStreamConfiguration cfg(streamFormats);\n> +\t\tcfg.pixelFormat = pixelFormat;\n> +\t\tcfg.bufferCount = 4;\n> +\t\tcfg.size = size;\n> +\n> +\t\tconfig->addConfiguration(cfg);\n> +\t}\n> +\n> +\tif (config->validate() == CameraConfiguration::Invalid)\n> +\t\treturn nullptr;\n> +\n> +\treturn config;\n> +}\n> +\n> +int PipelineHandlerC3ISP::configureProcessedStream(C3ISPCameraData *data,\n> +\t\t\t\t\t\t   const StreamConfiguration &config,\n> +\t\t\t\t\t\t   V4L2SubdeviceFormat &subdevFormat)\n> +{\n> +\tStream *stream = config.stream();\n> +\tC3ISPPipe *pipe = pipeFromStream(data, stream);\n> +\tV4L2SubdeviceFormat rszFormat;\n> +\n> +\tconst MediaEntity *resizerEntity = pipe->resizer->entity();\n> +\tint ret = resizerEntity->getPadByIndex(0)->links()[0]->setEnabled(true);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tret = resizerEntity->getPadByIndex(1)->links()[0]->setEnabled(true);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\trszFormat.code = C3ISPFmtToCode.find(config.pixelFormat)->second;\n> +\trszFormat.size = subdevFormat.size;\n> +\trszFormat.colorSpace = subdevFormat.colorSpace;\n> +\n> +\tret = pipe->resizer->setFormat(0, &rszFormat);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tRectangle cropRect = { 0, 0, rszFormat.size };\n> +\tret = pipe->resizer->setSelection(0, V4L2_SEL_TGT_CROP, &cropRect);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tRectangle scaleRect = { 0, 0, config.size };\n> +\tret = pipe->resizer->setSelection(0, V4L2_SEL_TGT_COMPOSE, &scaleRect);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\treturn 0;\n> +}\n> +\n> +int PipelineHandlerC3ISP::setConfigStreams(CameraConfiguration *config)\n> +{\n> +\tif (config->size() != streams_.size()) {\n> +\t\tLOG(C3ISP, Error) << \"Invalid configuration size: \" << config->size();\n> +\t\treturn -EINVAL;\n> +\t}\n> +\n> +\tfor (unsigned int i = 0; i < config->size(); i++)\n> +\t\tconfig->at(i).setStream(streams_[i]);\nThis implies that it doesn't matter which stream goes with whichever config; is that right? There's \nno difference in the capabilities between the three output pipelines? And you want identical \nbehaviour regardless of the selected stream role? For example if I'm following correctly you're \nconfiguring the largest possible resolution on the sensor and downscaling that to the requested \nconfiguration size - and that should happen regardless of the user asking for a stream for \nStillCapture / VideoRecording or Viewfinder?\n> +\n> +\treturn 0;\n> +}\n> +\n> +int PipelineHandlerC3ISP::configure(Camera *camera, CameraConfiguration *config)\n> +{\n> +\tresetPipes();\n> +\n> +\tint ret = media_->disableLinks();\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\t/*\n> +\t * The stream has been set to nullptr in Camera::configure,\n> +\t * so need to set stream.\n> +\t */\n> +\tret = setConfigStreams(config);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\t/* Link the graph */\n> +\tC3ISPCameraData *data = cameraData(camera);\n> +\n> +\tconst MediaEntity *csiEntity = data->csi_->entity();\n> +\tret = csiEntity->getPadByIndex(0)->links()[0]->setEnabled(true);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tconst MediaEntity *adapEntity = data->adap_->entity();\n> +\tret = adapEntity->getPadByIndex(0)->links()[0]->setEnabled(true);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tconst MediaEntity *ispEntity = isp_->entity();\n> +\tret = ispEntity->getPadByIndex(0)->links()[0]->setEnabled(true);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tret = ispEntity->getPadByIndex(1)->links()[0]->setEnabled(true);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tret = ispEntity->getPadByIndex(2)->links()[0]->setEnabled(true);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tC3ISPCameraConfiguration *c3Config = static_cast<C3ISPCameraConfiguration *>(config);\n> +\tV4L2SubdeviceFormat subdevFormat = c3Config->sensorFormat_;\n> +\n> +\tret = data->sensor_->setFormat(&subdevFormat);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tret = data->csi_->setFormat(0, &subdevFormat);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tret = data->adap_->setFormat(0, &subdevFormat);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tret = isp_->setFormat(0, &subdevFormat);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tV4L2SubdeviceFormat ispSrcVideoFormat = subdevFormat;\n> +\tispSrcVideoFormat.code = MEDIA_BUS_FMT_YUV8_1X24;\n> +\tret = isp_->setFormat(3, &ispSrcVideoFormat);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tV4L2DeviceFormat paramFormat;\n> +\tparamFormat.fourcc = V4L2PixelFormat(V4L2_META_FMT_C3ISP_PARAMS);\n> +\tret = param_->setFormat(&paramFormat);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tV4L2DeviceFormat statFormat;\n> +\tstatFormat.fourcc = V4L2PixelFormat(V4L2_META_FMT_C3ISP_STATS);\n> +\tret = stat_->setFormat(&statFormat);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tfor (const StreamConfiguration &streamConfig : *config) {\n> +\t\tStream *stream = streamConfig.stream();\n> +\t\tC3ISPPipe *pipe = pipeFromStream(data, stream);\n> +\n> +\t\tret = configureProcessedStream(data, streamConfig, subdevFormat);\n> +\t\tif (ret) {\n> +\t\t\tLOG(C3ISP, Error) << \"Failed to configure process stream\";\n> +\t\t\treturn ret;\n> +\t\t}\nYou don't have an accompanying \"configureRAWStream()\" so here I'd probably just call the function \n\"configureStream()\" or \"configurePipe()\"\n> +\n> +\t\tV4L2DeviceFormat captureFormat;\n> +\t\tcaptureFormat.fourcc = pipe->cap->toV4L2PixelFormat(streamConfig.pixelFormat);\n> +\t\tcaptureFormat.size = streamConfig.size;\n> +\n> +\t\tret = pipe->cap->setFormat(&captureFormat);\n> +\t\tif (ret)\n> +\t\t\treturn ret;\nI think I'd just move that inside \"configureStream/Pipe()\"\n> +\n> +\t\tpipe->stream = stream;\n> +\t}\n> +\n> +\t/* Configure IPA module */\n> +\tipa::c3isp::IPAConfigInfo ipaConfig{};\n> +\n> +\tret = data->sensor_->sensorInfo(&ipaConfig.sensorInfo);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tipaConfig.sensorControls = data->sensor_->controls();\n> +\n> +\tret = data->ipa_->configure(ipaConfig, &data->controlInfo_);\n> +\tif (ret) {\n> +\t\tLOG(C3ISP, Error) << \"Failed to configure IPA\";\n> +\t\treturn ret;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +int PipelineHandlerC3ISP::exportFrameBuffers(Camera *camera, Stream *stream,\n> +\t\t\t\t\t     std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n> +{\n> +\tC3ISPPipe *pipe = pipeFromStream(cameraData(camera), stream);\n> +\tunsigned int count = stream->configuration().bufferCount;\n> +\n> +\treturn pipe->cap->exportBuffers(count, buffers);\n> +}\n> +\n> +int PipelineHandlerC3ISP::allocateBuffers(Camera *camera)\n> +{\n> +\tC3ISPCameraData *data = cameraData(camera);\n> +\tunsigned int ipaBufferId = 1;\n> +\n> +\tint ret = param_->allocateBuffers(4, &paramBuffers_);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\tret = stat_->allocateBuffers(4, &statBuffers_);\n> +\tif (ret < 0) {\n> +\t\tparamBuffers_.clear();\n> +\t\tstatBuffers_.clear();\n> +\t\treturn ret;\n> +\t}\n> +\n> +\tfor (std::unique_ptr<FrameBuffer> &buffer : paramBuffers_) {\n> +\t\tbuffer->setCookie(ipaBufferId++);\n> +\t\tdata->ipaBuffers_.emplace_back(buffer->cookie(), buffer->planes());\n> +\t\tavailableParamBuffers_.push(buffer.get());\n> +\t}\n> +\n> +\tfor (std::unique_ptr<FrameBuffer> &buffer : statBuffers_) {\n> +\t\tbuffer->setCookie(ipaBufferId++);\n> +\t\tdata->ipaBuffers_.emplace_back(buffer->cookie(), buffer->planes());\n> +\t\tavailableStatBuffers_.push(buffer.get());\n> +\t}\n> +\n> +\tdata->ipa_->mapBuffers(data->ipaBuffers_);\n> +\n> +\treturn 0;\n> +}\n> +\n> +int PipelineHandlerC3ISP::freeBuffers(Camera *camera)\n> +{\n> +\tC3ISPCameraData *data = cameraData(camera);\n> +\n> +\twhile (!availableStatBuffers_.empty())\n> +\t\tavailableStatBuffers_.pop();\n> +\n> +\twhile (!availableParamBuffers_.empty())\n> +\t\tavailableParamBuffers_.pop();\n> +\n> +\tparamBuffers_.clear();\n> +\tstatBuffers_.clear();\n> +\n> +\tstd::vector<unsigned int> ids;\n> +\tfor (IPABuffer &ipabuf : data->ipaBuffers_)\n> +\t\tids.push_back(ipabuf.id);\n> +\n> +\tdata->ipa_->unmapBuffers(ids);\n> +\tdata->ipaBuffers_.clear();\n> +\n> +\tif (param_->releaseBuffers())\n> +\t\tLOG(C3ISP, Error) << \"Failed to release param buffers\";\n> +\n> +\tif (stat_->releaseBuffers())\n> +\t\tLOG(C3ISP, Error) << \"Failed to release stat buffers\";\n> +\n> +\treturn 0;\n> +}\n> +\n> +int PipelineHandlerC3ISP::pipeStart()\nThe name implies it's starting one pipe rather than all of them.\n> +{\n> +\tfor (C3ISPPipe &pipe : pipes_) {\n> +\t\tif (!pipe.stream)\n> +\t\t\tcontinue;\n> +\n> +\t\tStream *stream = pipe.stream;\n> +\n> +\t\tint ret = pipe.cap->importBuffers(stream->configuration().bufferCount);\n> +\t\tif (ret) {\n> +\t\t\tLOG(C3ISP, Error) << \"Failed to import buffers\";\n> +\t\t\treturn ret;\n> +\t\t}\n> +\n> +\t\tret = pipe.cap->streamOn();\n> +\t\tif (ret) {\n> +\t\t\tLOG(C3ISP, Error) << \"Failed to start stream\";\n> +\t\t\treturn ret;\n> +\t\t}\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +void PipelineHandlerC3ISP::pipeStop()\n> +{\n> +\tfor (C3ISPPipe &pipe : pipes_) {\n> +\t\tif (!pipe.stream)\n> +\t\t\tcontinue;\n> +\n> +\t\tpipe.cap->streamOff();\n> +\t\tpipe.cap->releaseBuffers();\n> +\t}\n> +}\n> +\n> +int PipelineHandlerC3ISP::start([[maybe_unused]] Camera *camera,\n> +\t\t\t\t[[maybe_unused]] const ControlList *controls)\n> +{\n> +\tC3ISPCameraData *data = cameraData(camera);\n> +\tint ret;\n> +\n> +\tret = allocateBuffers(camera);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\tret = data->ipa_->start();\n> +\tif (ret) {\n> +\t\tLOG(C3ISP, Error) << \"Failed to start IPA\";\n> +\t\tgoto error;\n> +\t}\n> +\n> +\tdata->frame_ = 0;\n> +\n> +\tret = param_->streamOn();\n> +\tif (ret) {\n> +\t\tLOG(C3ISP, Error) << \"Failed to start param\";\n> +\t\tgoto error;\n> +\t}\n> +\n> +\tret = stat_->streamOn();\n> +\tif (ret) {\n> +\t\tLOG(C3ISP, Error) << \"Failed to start stat\";\n> +\t\tgoto error;\n> +\t}\n> +\n> +\tret = pipeStart();\n> +\tif (ret) {\n> +\t\tLOG(C3ISP, Error) << \"Failed to start pipe\";\n> +\t\tgoto error;\n> +\t}\n> +\n> +\tret = isp_->setFrameStartEnabled(true);\n> +\tif (ret) {\n> +\t\tLOG(C3ISP, Error) << \"Failed to set frame start\";\n> +\t\tgoto error;\n> +\t}\n> +\n> +\tactiveCamera_ = camera;\n> +\n> +\treturn 0;\n> +\n> +error:\n> +\tpipeStop();\n> +\tstat_->streamOff();\n> +\tparam_->streamOff();\n> +\tdata->ipa_->stop();\n> +\tfreeBuffers(camera);\n> +\tLOG(C3ISP, Error) << \"Failed to start camera \" << camera->id();\n> +\n> +\treturn ret;\n> +}\n> +\n> +void PipelineHandlerC3ISP::stopDevice([[maybe_unused]] Camera *camera)\n> +{\n> +\tC3ISPCameraData *data = cameraData(camera);\n> +\n> +\tisp_->setFrameStartEnabled(false);\n> +\n> +\tdata->ipa_->stop();\n> +\n> +\tpipeStop();\n> +\n> +\tstat_->streamOff();\n> +\tparam_->streamOff();\n> +\n> +\tdata->frameInfo_.clear();\n> +\n> +\tfreeBuffers(camera);\n> +\n> +\tactiveCamera_ = nullptr;\n> +}\n> +\n> +int PipelineHandlerC3ISP::queueRequestDevice(Camera *camera, Request *request)\n> +{\n> +\tC3ISPCameraData *data = cameraData(camera);\n> +\n> +\tC3ISPFrameInfo *info = data->frameInfo_.create(data, request);\n> +\tif (!info)\n> +\t\treturn -ENOENT;\n> +\n> +\tdata->ipa_->queueRequest(data->frame_, request->controls());\n> +\n> +\tdata->ipa_->fillParamsBuffer(data->frame_, info->paramBuffer->cookie());\n> +\n> +\tdata->frame_++;\n> +\n> +\treturn 0;\n> +}\n> +\n> +void PipelineHandlerC3ISP::tryCompleteRequest(C3ISPFrameInfo *info)\n> +{\n> +\tC3ISPCameraData *data = cameraData(activeCamera_);\n> +\tRequest *request = info->request;\n> +\n> +\tif (request->hasPendingBuffers())\n> +\t\treturn;\n> +\n> +\tif (!info->metadataProcessed)\n> +\t\treturn;\n> +\n> +\tif (!info->paramDequeued)\n> +\t\treturn;\n> +\n> +\tdata->frameInfo_.destroy(info->frame);\n> +\n> +\tcompleteRequest(request);\n> +}\n> +\n> +void PipelineHandlerC3ISP::bufferReady(FrameBuffer *buffer)\n> +{\n> +\tC3ISPCameraData *data = cameraData(activeCamera_);\n> +\n> +\tC3ISPFrameInfo *info = data->frameInfo_.find(buffer);\n> +\tif (!info)\n> +\t\treturn;\n> +\n> +\tconst FrameMetadata &metadata = buffer->metadata();\n> +\tRequest *request = buffer->request();\n> +\n> +\tif (metadata.status != FrameMetadata::FrameCancelled) {\n> +\t\trequest->metadata().set(controls::SensorTimestamp,\n> +\t\t\t\t\tmetadata.timestamp);\n> +\t}\n> +\n> +\tcompleteBuffer(request, buffer);\n> +\ttryCompleteRequest(info);\n> +}\n> +\n> +void PipelineHandlerC3ISP::statReady(FrameBuffer *buffer)\n> +{\n> +\tC3ISPCameraData *data = cameraData(activeCamera_);\n> +\n> +\tC3ISPFrameInfo *info = data->frameInfo_.find(buffer);\n> +\tif (!info)\n> +\t\treturn;\n> +\n> +\tif (buffer->metadata().status == FrameMetadata::FrameCancelled) {\n> +\t\tinfo->metadataProcessed = true;\n> +\t\ttryCompleteRequest(info);\n> +\t\treturn;\n> +\t}\n> +\n> +\tif (data->frame_ <= buffer->metadata().sequence)\n> +\t\tdata->frame_ = buffer->metadata().sequence + 1;\n> +\n> +\tdata->ipa_->processStatsBuffer(info->frame, info->statBuffer->cookie(),\n> +\t\t\t\t       data->delayedCtrls_->get(buffer->metadata().sequence));\n> +}\n> +\n> +void PipelineHandlerC3ISP::paramReady(FrameBuffer *buffer)\n> +{\n> +\tC3ISPCameraData *data = cameraData(activeCamera_);\n> +\n> +\tC3ISPFrameInfo *info = data->frameInfo_.find(buffer);\n> +\tif (!info)\n> +\t\treturn;\n> +\n> +\tinfo->paramDequeued = true;\n> +\ttryCompleteRequest(info);\n> +}\n> +\n> +bool PipelineHandlerC3ISP::createCamera(MediaLink *ispLink)\n> +{\n> +\tMediaEntity *adap = ispLink->source()->entity();\n> +\tconst MediaPad *adapSink = adap->getPadByIndex(0);\n> +\tMediaEntity *csi = adapSink->links()[0]->source()->entity();\n> +\tconst MediaPad *csiSink = csi->getPadByIndex(0);\n> +\tMediaEntity *sensor = csiSink->links()[0]->source()->entity();\n\n\nMight be nice to validate that sensor->function() is MEDIA_ENT_F_CAM_SENSOR here...it's unlikely not \nto be, but the surety is nice.\n\n> +\n> +\tstd::unique_ptr<C3ISPCameraData> data =\n> +\t\tstd::make_unique<C3ISPCameraData>(this, sensor);\n> +\n> +\tif (data->init())\n> +\t\treturn false;\n> +\n> +\t/* Generic values for sensor */\n> +\tstd::unordered_map<uint32_t, DelayedControls::ControlParams> params = {\n> +\t\t{ V4L2_CID_ANALOGUE_GAIN, { 1, false } },\n> +\t\t{ V4L2_CID_EXPOSURE, { 2, false } },\n> +\t};\n\nThings have recently been updated so that you can fetch the delay values from a database:\n\n\nhttps://git.libcamera.org/libcamera/libcamera.git/tree/src/libcamera/pipeline/mali-c55/mali-c55.cpp#n1606\n\n> +\n> +\tdata->delayedCtrls_ = std::make_unique<DelayedControls>(data->sensor_->device(), params);\n> +\tisp_->frameStart.connect(data->delayedCtrls_.get(), &DelayedControls::applyControls);\n> +\n> +\tint ret = data->loadIPA(media_->hwRevision());\n> +\tif (ret)\n> +\t\treturn false;\n> +\n> +\tstd::set<Stream *> streams{ &data->viewStream, &data->stillStream, &data->videoStream };\n> +\n> +\tstd::shared_ptr<Camera> camera = Camera::create(std::move(data), sensor->name(), streams);\n> +\n> +\tregisterCamera(std::move(camera));\n> +\n> +\treturn true;\n> +}\n> +\n> +bool PipelineHandlerC3ISP::match(DeviceEnumerator *enumerator)\n> +{\n> +\tconst MediaPad *ispSink;\n> +\n> +\tDeviceMatch dm(\"c3-isp\");\n> +\tdm.add(\"c3-mipi-csi2\");\n> +\tdm.add(\"c3-mipi-adapter\");\n> +\tdm.add(\"c3-isp-core\");\n> +\n> +\tmedia_ = acquireMediaDevice(enumerator, dm);\n> +\tif (!media_)\n> +\t\treturn false;\n> +\n> +\tisp_ = V4L2Subdevice::fromEntityName(media_, \"c3-isp-core\");\n> +\tif (isp_->open() < 0)\n> +\t\treturn false;\n> +\n> +\tstat_ = V4L2VideoDevice::fromEntityName(media_, \"c3-isp-stats\");\n> +\tif (stat_->open() < 0)\n> +\t\treturn false;\n> +\n> +\tstat_->bufferReady.connect(this, &PipelineHandlerC3ISP::statReady);\n> +\n> +\tparam_ = V4L2VideoDevice::fromEntityName(media_, \"c3-isp-params\");\n> +\tif (param_->open() < 0)\n> +\t\treturn false;\n> +\n> +\tparam_->bufferReady.connect(this, &PipelineHandlerC3ISP::paramReady);\n> +\n> +\tC3ISPPipe *viewPipe = &pipes_[C3ISPVIEW];\n> +\tviewPipe->resizer = V4L2Subdevice::fromEntityName(media_, \"c3-isp-resizer0\");\n> +\tif (viewPipe->resizer->open() < 0)\n> +\t\treturn false;\n> +\n> +\tviewPipe->cap = V4L2VideoDevice::fromEntityName(media_, \"c3-isp-cap0\");\n> +\tif (viewPipe->cap->open() < 0)\n> +\t\treturn false;\n> +\n> +\tviewPipe->cap->bufferReady.connect(this, &PipelineHandlerC3ISP::bufferReady);\n> +\n> +\tC3ISPPipe *stillPipe = &pipes_[C3ISPSTILL];\n> +\tstillPipe->resizer = V4L2Subdevice::fromEntityName(media_, \"c3-isp-resizer1\");\n> +\tif (stillPipe->resizer->open() < 0)\n> +\t\treturn false;\n> +\n> +\tstillPipe->cap = V4L2VideoDevice::fromEntityName(media_, \"c3-isp-cap1\");\n> +\tif (stillPipe->cap->open() < 0)\n> +\t\treturn false;\n> +\n> +\tstillPipe->cap->bufferReady.connect(this, &PipelineHandlerC3ISP::bufferReady);\n> +\n> +\tC3ISPPipe *videoPipe = &pipes_[C3ISPVIDEO];\n> +\tvideoPipe->resizer = V4L2Subdevice::fromEntityName(media_, \"c3-isp-resizer2\");\n> +\tif (videoPipe->resizer->open() < 0)\n> +\t\treturn false;\n> +\n> +\tvideoPipe->cap = V4L2VideoDevice::fromEntityName(media_, \"c3-isp-cap2\");\n> +\tif (videoPipe->cap->open() < 0)\n> +\t\treturn false;\n> +\n> +\tvideoPipe->cap->bufferReady.connect(this, &PipelineHandlerC3ISP::bufferReady);\n> +\nSetting up the pipes here lends itself to a loop; up to you though.\n> +\tispSink = isp_->entity()->getPadByIndex(0);\n> +\tif (!ispSink || ispSink->links().empty())\n> +\t\treturn false;\n> +\n> +\tif (!createCamera(ispSink->links()[0])) {\n> +\t\tLOG(C3ISP, Error) << \"Failed to create camera\";\n> +\t\treturn false;\n> +\t}\n> +\n> +\treturn true;\n> +}\n> +\n> +REGISTER_PIPELINE_HANDLER(PipelineHandlerC3ISP, \"c3isp\")\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/libcamera/pipeline/c3-isp/meson.build b/src/libcamera/pipeline/c3-isp/meson.build\n> new file mode 100644\n> index 00000000..5f8b23f1\n> --- /dev/null\n> +++ b/src/libcamera/pipeline/c3-isp/meson.build\n> @@ -0,0 +1,5 @@\n> +# SPDX-License-Identifier: CC0-1.0\n> +\n> +libcamera_internal_sources += files([\n> +    'c3-isp.cpp'\n> +])","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 8EB2ABD160\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 21 Jan 2025 15:53:40 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 80F1D6854C;\n\tTue, 21 Jan 2025 16:53:39 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 099CE68503\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 21 Jan 2025 16:53:37 +0100 (CET)","from [192.168.0.43]\n\t(cpc141996-chfd3-2-0-cust928.12-3.cable.virginm.net [86.13.91.161])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 4FE83788;\n\tTue, 21 Jan 2025 16:52:35 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"CQzUqjBf\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1737474755;\n\tbh=yKHhOETgnH9w1xtUae6DGDTtDwKLOuVu5sEFQJHjGW0=;\n\th=Date:Subject:To:Cc:References:From:In-Reply-To:From;\n\tb=CQzUqjBfMW9JxKqyCvkH8TaclRjZyICsBJlXCixdxDt+I/snBN/qzxnmHdId3LjoI\n\tESRaE9D9Wbjo5jJyv+bcz9YFY47U9zAtvvxgNp4dqeC3609tkomBOzOh35/My917NH\n\tRqoUrqeacgEHaKn1IjooW9NUnlRWNhu0vq66kR7k=","Message-ID":"<2a0b1026-a6cb-4a7b-9808-224d9ed31254@ideasonboard.com>","Date":"Tue, 21 Jan 2025 15:53:34 +0000","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH v2 1/2] libcamera: pipeline: Add C3 ISP pipeline handler","To":"Keke Li <keke.li@amlogic.com>, libcamera-devel@lists.libcamera.org","Cc":"kieran.bingham@ideasonboard.com, laurent.pinchart@ideasonboard.com,\n\tjacopo.mondi@ideasonboard.com","References":"<20241227105840.159559-1-keke.li@amlogic.com>\n\t<20241227105840.159559-2-keke.li@amlogic.com>","Content-Language":"en-US","From":"Dan Scally <dan.scally@ideasonboard.com>","Autocrypt":"addr=dan.scally@ideasonboard.com; keydata=\n\txsFNBGLydlEBEADa5O2s0AbUguprfvXOQun/0a8y2Vk6BqkQALgeD6KnXSWwaoCULp18etYW\n\tB31bfgrdphXQ5kUQibB0ADK8DERB4wrzrUb5CMxLBFE7mQty+v5NsP0OFNK9XTaAOcmD+Ove\n\teIjYvqurAaro91jrRVrS1gBRxIFqyPgNvwwL+alMZhn3/2jU2uvBmuRrgnc/e9cHKiuT3Dtq\n\tMHGPKL2m+plk+7tjMoQFfexoQ1JKugHAjxAhJfrkXh6uS6rc01bYCyo7ybzg53m1HLFJdNGX\n\tsUKR+dQpBs3SY4s66tc1sREJqdYyTsSZf80HjIeJjU/hRunRo4NjRIJwhvnK1GyjOvvuCKVU\n\tRWpY8dNjNu5OeAfdrlvFJOxIE9M8JuYCQTMULqd1NuzbpFMjc9524U3Cngs589T7qUMPb1H1\n\tNTA81LmtJ6Y+IV5/kiTUANflpzBwhu18Ok7kGyCq2a2jsOcVmk8gZNs04gyjuj8JziYwwLbf\n\tvzABwpFVcS8aR+nHIZV1HtOzyw8CsL8OySc3K9y+Y0NRpziMRvutrppzgyMb9V+N31mK9Mxl\n\t1YkgaTl4ciNWpdfUe0yxH03OCuHi3922qhPLF4XX5LN+NaVw5Xz2o3eeWklXdouxwV7QlN33\n\tu4+u2FWzKxDqO6WLQGjxPE0mVB4Gh5Pa1Vb0ct9Ctg0qElvtGQARAQABzShEYW4gU2NhbGx5\n\tIDxkYW4uc2NhbGx5QGlkZWFzb25ib2FyZC5jb20+wsGNBBMBCAA3FiEEsdtt8OWP7+8SNfQe\n\tkiQuh/L+GMQFAmLydlIFCQWjmoACGwMECwkIBwUVCAkKCwUWAgMBAAAKCRCSJC6H8v4YxDI2\n\tEAC2Gz0iyaXJkPInyshrREEWbo0CA6v5KKf3I/HlMPqkZ48bmGoYm4mEQGFWZJAT3K4ir8bg\n\tcEfs9V54gpbrZvdwS4abXbUK4WjKwEs8HK3XJv1WXUN2bsz5oEJWZUImh9gD3naiLLI9QMMm\n\tw/aZkT+NbN5/2KvChRWhdcha7+2Te4foOY66nIM+pw2FZM6zIkInLLUik2zXOhaZtqdeJZQi\n\tHSPU9xu7TRYN4cvdZAnSpG7gQqmLm5/uGZN1/sB3kHTustQtSXKMaIcD/DMNI3JN/t+RJVS7\n\tc0Jh/ThzTmhHyhxx3DRnDIy7kwMI4CFvmhkVC2uNs9kWsj1DuX5kt8513mvfw2OcX9UnNKmZ\n\tnhNCuF6DxVrL8wjOPuIpiEj3V+K7DFF1Cxw1/yrLs8dYdYh8T8vCY2CHBMsqpESROnTazboh\n\tAiQ2xMN1cyXtX11Qwqm5U3sykpLbx2BcmUUUEAKNsM//Zn81QXKG8vOx0ZdMfnzsCaCzt8f6\n\t9dcDBBI3tJ0BI9ByiocqUoL6759LM8qm18x3FYlxvuOs4wSGPfRVaA4yh0pgI+ModVC2Pu3y\n\tejE/IxeatGqJHh6Y+iJzskdi27uFkRixl7YJZvPJAbEn7kzSi98u/5ReEA8Qhc8KO/B7wprj\n\txjNMZNYd0Eth8+WkixHYj752NT5qshKJXcyUU87BTQRi8nZSARAAx0BJayh1Fhwbf4zoY56x\n\txHEpT6DwdTAYAetd3yiKClLVJadYxOpuqyWa1bdfQWPb+h4MeXbWw/53PBgn7gI2EA7ebIRC\n\tPJJhAIkeym7hHZoxqDQTGDJjxFEL11qF+U3rhWiL2Zt0Pl+zFq0eWYYVNiXjsIS4FI2+4m16\n\ttPbDWZFJnSZ828VGtRDQdhXfx3zyVX21lVx1bX4/OZvIET7sVUufkE4hrbqrrufre7wsjD1t\n\t8MQKSapVrr1RltpzPpScdoxknOSBRwOvpp57pJJe5A0L7+WxJ+vQoQXj0j+5tmIWOAV1qBQp\n\thyoyUk9JpPfntk2EKnZHWaApFp5TcL6c5LhUvV7F6XwOjGPuGlZQCWXee9dr7zym8iR3irWT\n\t+49bIh5PMlqSLXJDYbuyFQHFxoiNdVvvf7etvGfqFYVMPVjipqfEQ38ST2nkzx+KBICz7uwj\n\tJwLBdTXzGFKHQNckGMl7F5QdO/35An/QcxBnHVMXqaSd12tkJmoRVWduwuuoFfkTY5mUV3uX\n\txGj3iVCK4V+ezOYA7c2YolfRCNMTza6vcK/P4tDjjsyBBZrCCzhBvd4VVsnnlZhVaIxoky4K\n\taL+AP+zcQrUZmXmgZjXOLryGnsaeoVrIFyrU6ly90s1y3KLoPsDaTBMtnOdwxPmo1xisH8oL\n\ta/VRgpFBfojLPxMAEQEAAcLBfAQYAQgAJhYhBLHbbfDlj+/vEjX0HpIkLofy/hjEBQJi8nZT\n\tBQkFo5qAAhsMAAoJEJIkLofy/hjEXPcQAMIPNqiWiz/HKu9W4QIf1OMUpKn3YkVIj3p3gvfM\n\tRes4fGX94Ji599uLNrPoxKyaytC4R6BTxVriTJjWK8mbo9jZIRM4vkwkZZ2bu98EweSucxbp\n\tvjESsvMXGgxniqV/RQ/3T7LABYRoIUutARYq58p5HwSP0frF0fdFHYdTa2g7MYZl1ur2JzOC\n\tFHRpGadlNzKDE3fEdoMobxHB3Lm6FDml5GyBAA8+dQYVI0oDwJ3gpZPZ0J5Vx9RbqXe8RDuR\n\tdu90hvCJkq7/tzSQ0GeD3BwXb9/R/A4dVXhaDd91Q1qQXidI+2jwhx8iqiYxbT+DoAUkQRQy\n\txBtoCM1CxH7u45URUgD//fxYr3D4B1SlonA6vdaEdHZOGwECnDpTxecENMbz/Bx7qfrmd901\n\tD+N9SjIwrbVhhSyUXYnSUb8F+9g2RDY42Sk7GcYxIeON4VzKqWM7hpkXZ47pkK0YodO+dRKM\n\tyMcoUWrTK0Uz6UzUGKoJVbxmSW/EJLEGoI5p3NWxWtScEVv8mO49gqQdrRIOheZycDmHnItt\n\t9Qjv00uFhEwv2YfiyGk6iGF2W40s2pH2t6oeuGgmiZ7g6d0MEK8Ql/4zPItvr1c1rpwpXUC1\n\tu1kQWgtnNjFHX3KiYdqjcZeRBiry1X0zY+4Y24wUU0KsEewJwjhmCKAsju1RpdlPg2kC","In-Reply-To":"<20241227105840.159559-2-keke.li@amlogic.com>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"7bit","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":33295,"web_url":"https://patchwork.libcamera.org/comment/33295/","msgid":"<2bef60d0-0109-4c00-959e-bfe309da356e@amlogic.com>","date":"2025-02-05T09:08:14","subject":"Re: [PATCH v2 1/2] libcamera: pipeline: Add C3 ISP pipeline handler","submitter":{"id":217,"url":"https://patchwork.libcamera.org/api/people/217/","name":"Keke Li","email":"keke.li@amlogic.com"},"content":"Hi Dan\n\nThanks for your reply.\n\nOn 2025/1/21 23:53, Dan Scally wrote:\n> [ EXTERNAL EMAIL ]\n>\n> Hello Keke - thank you for the patches.\n>\n> On 27/12/2024 10:58, Keke Li wrote:\n>> The Amlogic C3 ISP pipeline handler supports\n>> 3-channel image output, 1-channel 3A statistical\n>> information ouput and 1-channel parameters input.\n>>\n>> Signed-off-by: Keke Li <keke.li@amlogic.com>\n>> ---\n>>   include/libcamera/ipa/c3isp.mojom         |   42 +\n>>   include/libcamera/ipa/meson.build         |    1 +\n>>   include/linux/videodev2.h                 |    4 +\n>>   meson_options.txt                         |    1 +\n>>   src/libcamera/pipeline/c3-isp/c3-isp.cpp  | 1161 +++++++++++++++++++++\n>>   src/libcamera/pipeline/c3-isp/meson.build |    5 +\n>>   6 files changed, 1214 insertions(+)\n>>   create mode 100644 include/libcamera/ipa/c3isp.mojom\n>>   create mode 100644 src/libcamera/pipeline/c3-isp/c3-isp.cpp\n>>   create mode 100644 src/libcamera/pipeline/c3-isp/meson.build\n>>\n>> diff --git a/include/libcamera/ipa/c3isp.mojom \n>> b/include/libcamera/ipa/c3isp.mojom\n>> new file mode 100644\n>> index 00000000..e41fb243\n>> --- /dev/null\n>> +++ b/include/libcamera/ipa/c3isp.mojom\n>> @@ -0,0 +1,42 @@\n>> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n>> +\n>> +/*\n>> + * \\todo Document the interface and remove the related \n>> EXCLUDE_PATTERNS entry.\n>> + */\n> Well we haven't gotten around to documenting the interface yet so the \n> related EXCLUDE_PATTERNS entry\n> is still there, and this will need adding to it. In \n> Documentation/doxyfile-common.in under the\n> EXCLUDE_PATTERNS entry you should add \n> \"@TOP_BUILDDIR@/include/libcamera/ipa/c3*.h \\\". That will\n> silence the warnings from Doxygen on build.\n\n\nWill add \"@TOP_BUILDDIR@/include/libcamera/ipa/c3*.h \\\"\n\n>> +\n>> +module ipa.c3isp;\n>> +\n>> +import \"include/libcamera/ipa/core.mojom\";\n>> +\n>> +struct IPAConfigInfo {\n>> +     libcamera.IPACameraSensorInfo sensorInfo;\n>> +     libcamera.ControlInfoMap sensorControls;\n>> +     uint32 paramFormat;\n>> +};\n>> +\n>> +interface IPAC3ISPInterface {\n>> +     init(libcamera.IPASettings settings,\n>> +          uint32 hwRevision,\n>> +          libcamera.IPACameraSensorInfo sensorInfo,\n>> +          libcamera.ControlInfoMap sensorControls)\n>> +             => (int32 ret, libcamera.ControlInfoMap ipaControls);\n>> +     start() => (int32 ret);\n>> +     stop();\n>> +\n>> +     configure(IPAConfigInfo configInfo)\n>> +             => (int32 ret, libcamera.ControlInfoMap ipaControls);\n>> +\n>> +     mapBuffers(array<libcamera.IPABuffer> buffers);\n>> +     unmapBuffers(array<uint32> ids);\n>> +\n>> +     [async] queueRequest(uint32 frame, libcamera.ControlList \n>> reqControls);\n>> +     [async] fillParamsBuffer(uint32 frame, uint32 bufferId);\n> s/fillParamsBuffer/computeParams please\n\n\nWill use computeParams\n\n>> +     [async] processStatsBuffer(uint32 frame, uint32 bufferId,\n> s/processStatsBuffer/processStats please\n>> + libcamera.ControlList sensorControls);\n>> +};\n>> +\n>> +interface IPAC3ISPEventInterface {\n>> +     paramsBufferReady(uint32 frame, uint32 bytesused);\n> s/paramsBufferReady/paramsComputed please\n\n\nWill use paramsComputed\n\n>> +     setSensorControls(uint32 frame, libcamera.ControlList \n>> sensorControls);\n>> +     metadataReady(uint32 frame, libcamera.ControlList metadata);\n>> +};\n>> diff --git a/include/libcamera/ipa/meson.build \n>> b/include/libcamera/ipa/meson.build\n>> index 3129f119..39c55c07 100644\n>> --- a/include/libcamera/ipa/meson.build\n>> +++ b/include/libcamera/ipa/meson.build\n>> @@ -63,6 +63,7 @@ libcamera_ipa_headers += \n>> custom_target('core_ipa_serializer_h',\n>>\n>>   # Mapping from pipeline handler name to mojom file\n>>   pipeline_ipa_mojom_mapping = {\n>> +    'c3-isp': 'c3isp.mojom',\n>>       'ipu3': 'ipu3.mojom',\n>>       'mali-c55': 'mali-c55.mojom',\n>>       'rkisp1': 'rkisp1.mojom',\n>> diff --git a/include/linux/videodev2.h b/include/linux/videodev2.h\n>> index d2653b2e..3227ac1e 100644\n>> --- a/include/linux/videodev2.h\n>> +++ b/include/linux/videodev2.h\n>> @@ -831,6 +831,10 @@ struct v4l2_pix_format {\n>>   #define V4L2_META_FMT_RK_ISP1_STAT_3A       v4l2_fourcc('R', 'K', \n>> '1', 'S') /* Rockchip ISP1 3A Statistics */\n>>   #define V4L2_META_FMT_RK_ISP1_EXT_PARAMS    v4l2_fourcc('R', 'K', \n>> '1', 'E') /* Rockchip ISP1 3a Extensible Parameters */\n>>\n>> +/* Vendor specific - used for C3 ISP */\n>> +#define V4L2_META_FMT_C3ISP_PARAMS   v4l2_fourcc('C', '3', 'P', 'M') \n>> /* Amlogic C3 ISP Parameters */\n>> +#define V4L2_META_FMT_C3ISP_STATS    v4l2_fourcc('C', '3', 'S', 'T') \n>> /* Amlogic C3 ISP Statistics */\n>> +\n> Separate commits for this change please; in fact the series could do \n> with being broken down quite a\n> bit more generally I think...in its current form the pipeline handler \n> would be unusable at this\n> commit because loadIPA() would never work\n\n\nWill separate commits for this change.\n\n>>   /* Vendor specific - used for RaspberryPi PiSP */\n>>   #define V4L2_META_FMT_RPI_BE_CFG    v4l2_fourcc('R', 'P', 'B', 'C') \n>> /* PiSP BE configuration */\n>>\n>> diff --git a/meson_options.txt b/meson_options.txt\n>> index 1dc3b4cd..d59f4c04 100644\n>> --- a/meson_options.txt\n>> +++ b/meson_options.txt\n>> @@ -46,6 +46,7 @@ option('pipelines',\n>>           choices : [\n>>               'all',\n>>               'auto',\n>> +            'c3-isp',\n>>               'imx8-isi',\n>>               'ipu3',\n>>               'mali-c55',\n>> diff --git a/src/libcamera/pipeline/c3-isp/c3-isp.cpp \n>> b/src/libcamera/pipeline/c3-isp/c3-isp.cpp\n>> new file mode 100644\n>> index 00000000..524027d4\n>> --- /dev/null\n>> +++ b/src/libcamera/pipeline/c3-isp/c3-isp.cpp\n>> @@ -0,0 +1,1161 @@\n>> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n>> +/*\n>> + * Copyright (C) 2024, Amlogic Inc.\n>> + *\n>> + * Pipeline Handler for Amlogic C3 ISP\n>> + */\n>> +\n>> +#include <algorithm>\n>> +#include <array>\n>> +#include <map>\n>> +#include <memory>\n>> +#include <queue>\n>> +#include <set>\n>> +#include <string>\n>> +\n>> +#include <linux/media-bus-format.h>\n>> +#include <linux/media.h>\n>> +\n>> +#include <libcamera/base/log.h>\n>> +\n>> +#include <libcamera/camera.h>\n>> +#include <libcamera/formats.h>\n>> +#include <libcamera/geometry.h>\n>> +#include <libcamera/stream.h>\n>> +\n>> +#include <libcamera/ipa/c3isp_ipa_interface.h>\n>> +#include <libcamera/ipa/c3isp_ipa_proxy.h>\n>> +\n>> +#include \"libcamera/internal/bayer_format.h\"\n>> +#include \"libcamera/internal/camera.h\"\n>> +#include \"libcamera/internal/camera_sensor.h\"\n>> +#include \"libcamera/internal/delayed_controls.h\"\n>> +#include \"libcamera/internal/device_enumerator.h\"\n>> +#include \"libcamera/internal/framebuffer.h\"\n>> +#include \"libcamera/internal/ipa_manager.h\"\n>> +#include \"libcamera/internal/media_device.h\"\n>> +#include \"libcamera/internal/pipeline_handler.h\"\n>> +#include \"libcamera/internal/v4l2_subdevice.h\"\n>> +#include \"libcamera/internal/v4l2_videodevice.h\"\n>> +\n>> +namespace libcamera {\n>> +\n>> +LOG_DEFINE_CATEGORY(C3ISP)\n>> +\n>> +class PipelineHandlerC3ISP;\n>> +class C3ISPCameraData;\n>> +\n>> +const std::map<libcamera::PixelFormat, unsigned int> C3ISPFmtToCode = {\n>> +     { formats::R8, MEDIA_BUS_FMT_YUV8_1X24 },\n>> +     { formats::NV12, MEDIA_BUS_FMT_YUV8_1X24 },\n>> +     { formats::NV21, MEDIA_BUS_FMT_YUV8_1X24 },\n>> +     { formats::NV16, MEDIA_BUS_FMT_YUV8_1X24 },\n>> +     { formats::NV61, MEDIA_BUS_FMT_YUV8_1X24 },\n>> +};\n>> +\n>> +constexpr Size kC3ISPMinSize = { 160, 120 };\n>> +constexpr Size kC3ISPMaxSize = { 2888, 2240 };\n>> +\n>> +struct C3ISPFrameInfo {\n>> +     unsigned int frame;\n>> +     Request *request;\n>> +\n>> +     FrameBuffer *paramBuffer;\n>> +     FrameBuffer *statBuffer;\n>> +     FrameBuffer *viewBuffer;\n>> +     FrameBuffer *stillBuffer;\n>> +     FrameBuffer *videoBuffer;\n>> +\n>> +     bool paramDequeued;\n>> +     bool metadataProcessed;\n>> +};\n>> +\n>> +class C3ISPFrames\n>> +{\n>> +public:\n>> +     C3ISPFrames(PipelineHandler *pipe);\n>> +\n>> +     C3ISPFrameInfo *create(const C3ISPCameraData *data, Request \n>> *request);\n>> +     int destroy(unsigned int frame);\n>> +     void clear();\n>> +\n>> +     C3ISPFrameInfo *find(unsigned int frame);\n>> +     C3ISPFrameInfo *find(FrameBuffer *buffer);\n>> +     C3ISPFrameInfo *find(Request *request);\n>> +\n>> +private:\n>> +     PipelineHandlerC3ISP *pipe_;\n>> +     std::map<unsigned int, C3ISPFrameInfo *> frameInfo_;\n>> +};\n>> +\n>> +class C3ISPCameraData : public Camera::Private\n>> +{\n>> +public:\n>> +     C3ISPCameraData(PipelineHandler *pipe, MediaEntity *entity)\n>> +             : Camera::Private(pipe), entity_(entity), frame_(0), \n>> frameInfo_(pipe)\n>> +     {\n>> +     }\n>> +\n>> +     int init();\n>> +     PipelineHandlerC3ISP *pipe();\n>> +     int loadIPA(unsigned int hwRevision);\n>> +\n>> +     MediaEntity *entity_;\n>> +     std::unique_ptr<CameraSensor> sensor_;\n>> +     std::unique_ptr<DelayedControls> delayedCtrls_;\n>> +     std::unique_ptr<V4L2Subdevice> csi_;\n>> +     std::unique_ptr<V4L2Subdevice> adap_;\n>> +     Stream viewStream;\n>> +     Stream stillStream;\n>> +     Stream videoStream;\n>> +     unsigned int frame_;\n>> +     std::vector<IPABuffer> ipaBuffers_;\n>> +     C3ISPFrames frameInfo_;\n>> +\n>> +     std::unique_ptr<ipa::c3isp::IPAProxyC3ISP> ipa_;\n>> +\n>> +private:\n>> +     void paramFilled(unsigned int frame, unsigned int bytesused);\n>> +     void setSensorControls(unsigned int frame,\n>> +                            const ControlList &sensorControls);\n>> +     void metadataReady(unsigned int frame, const ControlList \n>> &metadata);\n>> +};\n>> +\n>> +class C3ISPCameraConfiguration : public CameraConfiguration\n>> +{\n>> +public:\n>> +     C3ISPCameraConfiguration(C3ISPCameraData *data)\n>> +             : CameraConfiguration(), data_(data)\n>> +     {\n>> +     }\n>> +\n>> +     Status validate() override;\n>> +\n>> +     V4L2SubdeviceFormat sensorFormat_;\n>> +\n>> +private:\n>> +     static constexpr unsigned int kMaxStreams = 3;\n>> +\n>> +     const C3ISPCameraData *data_;\n>> +};\n>> +\n>> +class PipelineHandlerC3ISP : public PipelineHandler\n>> +{\n>> +public:\n>> +     PipelineHandlerC3ISP(CameraManager *manager);\n>> +\n>> +     std::unique_ptr<CameraConfiguration> \n>> generateConfiguration(Camera *camera,\n>> + Span<const StreamRole> roles) override;\n>> +     int configure(Camera *camera, CameraConfiguration *config) \n>> override;\n>> +\n>> +     int exportFrameBuffers(Camera *camera, Stream *stream,\n>> + std::vector<std::unique_ptr<FrameBuffer>> *buffers) override;\n>> +\n>> +     int start(Camera *camera, const ControlList *controls) override;\n>> +     void stopDevice(Camera *camera) override;\n>> +\n>> +     int queueRequestDevice(Camera *camera, Request *request) override;\n>> +\n>> +     void bufferReady(FrameBuffer *buffer);\n>> +     void statReady(FrameBuffer *buffer);\n>> +     void paramReady(FrameBuffer *buffer);\n>> +\n>> +     bool match(DeviceEnumerator *enumerator) override;\n>> +\n>> +private:\n>> +     friend C3ISPCameraData;\n>> +     friend C3ISPFrames;\n>> +\n>> +     struct C3ISPPipe {\n>> +             std::unique_ptr<V4L2Subdevice> resizer;\n>> +             std::unique_ptr<V4L2VideoDevice> cap;\n>> +             Stream *stream;\n>> +     };\n>> +\n>> +     enum {\n>> +             C3ISPVIEW,\n>> +             C3ISPSTILL,\n>> +             C3ISPVIDEO,\n>> +             C3ISPNumPipes,\n>> +     };\n>> +\n>> +     C3ISPCameraData *cameraData(Camera *camera)\n>> +     {\n>> +             return static_cast<C3ISPCameraData *>(camera->_d());\n>> +     }\n>> +\n>> +     C3ISPPipe *pipeFromStream(C3ISPCameraData *data, Stream *stream)\n>> +     {\n>> +             if (stream == &data->viewStream)\n>> +                     return &pipes_[C3ISPVIEW];\n>> +             else if (stream == &data->stillStream)\n>> +                     return &pipes_[C3ISPSTILL];\n>> +             else if (stream == &data->videoStream)\n>> +                     return &pipes_[C3ISPVIDEO];\n>> +             else\n>> +                     LOG(C3ISP, Fatal) << \"Invalid stream: \" << stream;\n>> +\n>> +             return nullptr;\n>> +     }\n>> +\n>> +     C3ISPPipe *pipeFromStream(C3ISPCameraData *data, const Stream \n>> *stream)\n>> +     {\n>> +             return pipeFromStream(data, const_cast<Stream *>(stream));\n>> +     }\n>> +\n>> +     void resetPipes()\n>> +     {\n>> +             for (C3ISPPipe &pipe : pipes_)\n>> +                     pipe.stream = nullptr;\n>> +     }\n>> +\n>> +     int pipeStart();\n>> +     void pipeStop();\n>> +\n>> +     int setConfigStreams(CameraConfiguration *config);\n>> +     int configureProcessedStream(C3ISPCameraData *data,\n>> +                                  const StreamConfiguration &config,\n>> +                                  V4L2SubdeviceFormat &subdevFormat);\n>> +\n>> +     bool createCamera(MediaLink *ispLink);\n>> +     void tryCompleteRequest(C3ISPFrameInfo *info);\n>> +     int allocateBuffers(Camera *camera);\n>> +     int freeBuffers(Camera *camera);\n>> +\n>> +     MediaDevice *media_;\n>> +     std::unique_ptr<V4L2Subdevice> isp_;\n>> +     std::unique_ptr<V4L2VideoDevice> param_;\n>> +     std::unique_ptr<V4L2VideoDevice> stat_;\n>> +\n>> +     std::vector<std::unique_ptr<FrameBuffer>> paramBuffers_;\n>> +     std::vector<std::unique_ptr<FrameBuffer>> statBuffers_;\n>> +     std::queue<FrameBuffer *> availableParamBuffers_;\n>> +     std::queue<FrameBuffer *> availableStatBuffers_;\n>> +\n>> +     std::vector<Stream *> streams_;\n>> +     std::array<C3ISPPipe, C3ISPNumPipes> pipes_;\n>> +\n>> +     Camera *activeCamera_;\n>> +};\n>> +\n>> +C3ISPFrames::C3ISPFrames(PipelineHandler *pipe)\n>> +     : pipe_(static_cast<PipelineHandlerC3ISP *>(pipe))\n>> +{\n>> +}\n>> +\n>> +C3ISPFrameInfo *C3ISPFrames::create(const C3ISPCameraData *data, \n>> Request *request)\n>> +{\n>> +     unsigned int frame = data->frame_;\n>> +\n>> +     FrameBuffer *paramBuffer = nullptr;\n>> +     FrameBuffer *statBuffer = nullptr;\n>> +\n>> +     if (pipe_->availableParamBuffers_.empty()) {\n>> +             LOG(C3ISP, Error) << \"Param buffer queue empty\";\n>> +             return nullptr;\n>> +     }\n>> +\n>> +     if (pipe_->availableStatBuffers_.empty()) {\n>> +             LOG(C3ISP, Error) << \"Stat buffer queue empty\";\n>> +             return nullptr;\n>> +     }\n>> +\n>> +     paramBuffer = pipe_->availableParamBuffers_.front();\n>> +     pipe_->availableParamBuffers_.pop();\n>> +\n>> +     statBuffer = pipe_->availableStatBuffers_.front();\n>> +     pipe_->availableStatBuffers_.pop();\n>> +\n>> +     FrameBuffer *viewBuffer = request->findBuffer(&data->viewStream);\n>> +     FrameBuffer *stillBuffer = \n>> request->findBuffer(&data->stillStream);\n>> +     FrameBuffer *videoBuffer = \n>> request->findBuffer(&data->videoStream);\n>> +\n>> +     C3ISPFrameInfo *info = new C3ISPFrameInfo;\n>> +\n>> +     info->frame = frame;\n>> +     info->request = request;\n>> +     info->paramBuffer = paramBuffer;\n>> +     info->statBuffer = statBuffer;\n>> +     info->viewBuffer = viewBuffer;\n>> +     info->stillBuffer = stillBuffer;\n>> +     info->videoBuffer = videoBuffer;\n>> +     info->paramDequeued = false;\n>> +     info->metadataProcessed = false;\n>> +\n>> +     frameInfo_[frame] = info;\n>> +\n>> +     return info;\n>> +}\n>> +\n>> +int C3ISPFrames::destroy(unsigned int frame)\n>> +{\n>> +     C3ISPFrameInfo *info = find(frame);\n>> +     if (!info)\n>> +             return -ENOENT;\n>> +\n>> + pipe_->availableParamBuffers_.push(info->paramBuffer);\n>> +     pipe_->availableStatBuffers_.push(info->statBuffer);\n>> +\n>> +     frameInfo_.erase(info->frame);\n>> +\n>> +     delete info;\n>> +\n>> +     return 0;\n>> +}\n>> +\n>> +void C3ISPFrames::clear()\n>> +{\n>> +     for (const auto &entry : frameInfo_) {\n>> +             C3ISPFrameInfo *info = entry.second;\n>> +\n>> + pipe_->availableParamBuffers_.push(info->paramBuffer);\n>> + pipe_->availableStatBuffers_.push(info->statBuffer);\n>> +\n>> +             delete info;\n>> +     }\n>> +\n>> +     frameInfo_.clear();\n>> +}\n>> +\n>> +C3ISPFrameInfo *C3ISPFrames::find(unsigned int frame)\n>> +{\n>> +     auto itInfo = frameInfo_.find(frame);\n>> +\n>> +     if (itInfo != frameInfo_.end())\n>> +             return itInfo->second;\n>> +\n>> +     LOG(C3ISP, Fatal) << \"Can't locate info from frame\";\n>> +\n>> +     return nullptr;\n>> +}\n>> +\n>> +C3ISPFrameInfo *C3ISPFrames::find(FrameBuffer *buffer)\n>> +{\n>> +     for (auto &itInfo : frameInfo_) {\n>> +             C3ISPFrameInfo *info = itInfo.second;\n>> +\n>> +             if (info->paramBuffer == buffer ||\n>> +                 info->statBuffer == buffer ||\n>> +                 info->viewBuffer == buffer ||\n>> +                 info->stillBuffer == buffer ||\n>> +                 info->videoBuffer == buffer)\n>> +                     return info;\n>> +     }\n>> +\n>> +     LOG(C3ISP, Fatal) << \"Can't locate info from buffer\";\n>> +\n>> +     return nullptr;\n>> +}\n>> +\n>> +C3ISPFrameInfo *C3ISPFrames::find(Request *request)\n>> +{\n>> +     for (auto &itInfo : frameInfo_) {\n>> +             C3ISPFrameInfo *info = itInfo.second;\n>> +\n>> +             if (info->request == request)\n>> +                     return info;\n>> +     }\n>> +\n>> +     LOG(C3ISP, Fatal) << \"Can't locate info from request\";\n>> +\n>> +     return nullptr;\n>> +}\n>> +\n>> +int C3ISPCameraData::init()\n>> +{\n>> +     int ret;\n>> +\n>> +     /* Register a CameraSensor */\n>> +     sensor_ = CameraSensorFactoryBase::create(entity_);\n>> +     if (!sensor_)\n>> +             return ret;\n> ret is uninitialised here. If sensor is a nullptr return -ENODEV. This \n> is the only use of ret in the\n> function, so the variable can be dropped.\n\n\nWill drop ret\n\n>> +\n>> +     const MediaPad *sensorSrc = entity_->getPadByIndex(0);\n>> +     MediaEntity *csiEntity = sensorSrc->links()[0]->sink()->entity();\n>> +\n>> +     csi_ = std::make_unique<V4L2Subdevice>(csiEntity);\n>> +     if (csi_->open()) {\n>> +             LOG(C3ISP, Error) << \"Failed to open CSI-2 subdevice\";\n>> +             return false;\n>> +     }\n>> +\n>> +     const MediaPad *csiSrc = csiEntity->getPadByIndex(1);\n>> +     MediaEntity *adapEntity = csiSrc->links()[0]->sink()->entity();\n>> +\n>> +     adap_ = std::make_unique<V4L2Subdevice>(adapEntity);\n>> +     if (adap_->open()) {\n>> +             LOG(C3ISP, Error) << \"Failed to open adapter subdevice\";\n>> +             return false;\n>> +     }\n>> +\n>> +     return 0;\n>> +}\n>> +\n>> +PipelineHandlerC3ISP *C3ISPCameraData::pipe()\n>> +{\n>> +     return static_cast<PipelineHandlerC3ISP \n>> *>(Camera::Private::pipe());\n>> +}\n>> +\n>> +int C3ISPCameraData::loadIPA(unsigned int hwRevision)\n>> +{\n>> +     ipa_ = IPAManager::createIPA<ipa::c3isp::IPAProxyC3ISP>(pipe(), \n>> 1, 1);\n>> +     if (!ipa_)\n>> +             return -ENOENT;\n>> +\n>> +     ipa_->setSensorControls.connect(this, \n>> &C3ISPCameraData::setSensorControls);\n>> +     ipa_->paramsBufferReady.connect(this, \n>> &C3ISPCameraData::paramFilled);\n>> +     ipa_->metadataReady.connect(this, \n>> &C3ISPCameraData::metadataReady);\n>> +\n>> +     /*\n>> +      * The API tuning file is made from the sensor name unless the\n>> +      * environment variable overrides it.\n>> +      */\n>> +     std::string ipaTuningFile;\n>> +     char const *configFromEnv = \n>> utils::secure_getenv(\"LIBCAMERA_C3ISP_TUNING_FILE\");\n>> +     if (!configFromEnv || *configFromEnv == '\\0') {\n>> +             ipaTuningFile = \n>> ipa_->configurationFile(sensor_->model() + \".yaml\");\n>> +             if (ipaTuningFile.empty())\n>> +                     ipaTuningFile = \n>> ipa_->configurationFile(\"uncalibrated.yaml\");\n>\n> ipa_->configurationFile() has second parameter (which has a default, \n> allowing it to work here)\n> allowing you to pass a default filename as the second parameter, so \n> this can just be\n>\n>\n> ipaTuningFile = ipa_->configurationFile(sensor_->model() + \".yaml\", \n> \"uncalibrated.yaml\");\n>\n\nOK,  will add the second parameter.\n\n>> +     } else {\n>> +             ipaTuningFile = std::string(configFromEnv);\n>> +     }\n>> +\n>> +     IPACameraSensorInfo sensorInfo{};\n>> +     int ret = sensor_->sensorInfo(&sensorInfo);\n>> +     if (ret) {\n>> +             LOG(C3ISP, Error) << \"Invalid semsor information\";\n> s/semsor/sensor\n\n\nWill use \"sensor\"\n\n>> +             return ret;\n>> +     }\n>> +\n>> +     ret = ipa_->init({ ipaTuningFile, sensor_->model() }, hwRevision,\n>> +                      sensorInfo, sensor_->controls(), &controlInfo_);\n>> +     if (ret) {\n>> +             LOG(C3ISP, Error) << \"Failed to initialize IPA\";\n>> +             return ret;\n>> +     }\n>> +\n>> +     return 0;\n>> +}\n>> +\n>> +void C3ISPCameraData::paramFilled(unsigned int frame, unsigned int \n>> bytesused)\n>> +{\n>> +     PipelineHandlerC3ISP *pipe = C3ISPCameraData::pipe();\n>> +     C3ISPFrameInfo *info = frameInfo_.find(frame);\n>> +     if (!info)\n>> +             return;\n>> +\n>> + info->paramBuffer->_d()->metadata().planes()[0].bytesused = bytesused;\n>> +     pipe->param_->queueBuffer(info->paramBuffer);\n>> +     pipe->stat_->queueBuffer(info->statBuffer);\n>> +\n>> +     if (info->viewBuffer)\n>> + \n>> pipe->pipes_[PipelineHandlerC3ISP::C3ISPVIEW].cap->queueBuffer(info->viewBuffer);\n>> +\n>> +     if (info->stillBuffer)\n>> + \n>> pipe->pipes_[PipelineHandlerC3ISP::C3ISPSTILL].cap->queueBuffer(info->stillBuffer);\n>> +\n>> +     if (info->videoBuffer)\n>> + \n>> pipe->pipes_[PipelineHandlerC3ISP::C3ISPVIDEO].cap->queueBuffer(info->videoBuffer);\n>> +}\n>> +\n>> +void C3ISPCameraData::setSensorControls([[maybe_unused]] unsigned \n>> int frame,\n>> +                                     const ControlList &sensorControls)\n>> +{\n>> +     delayedCtrls_->push(sensorControls);\n>> +}\n>> +\n>> +void C3ISPCameraData::metadataReady(unsigned int frame, const \n>> ControlList &metadata)\n>> +{\n>> +     C3ISPFrameInfo *info = frameInfo_.find(frame);\n>> +     if (!info)\n>> +             return;\n>> +\n>> +     info->request->metadata().merge(metadata);\n>> +     info->metadataProcessed = true;\n>> +\n>> +     pipe()->tryCompleteRequest(info);\n>> +}\n>> +\n>> +namespace {\n>> +\n>> +const std::map<PixelFormat, uint32_t> rawFormats = {\n>> +     { formats::SBGGR10, MEDIA_BUS_FMT_SBGGR10_1X10 },\n>> +     { formats::SGBRG10, MEDIA_BUS_FMT_SGBRG10_1X10 },\n>> +     { formats::SGRBG10, MEDIA_BUS_FMT_SGRBG10_1X10 },\n>> +     { formats::SRGGB10, MEDIA_BUS_FMT_SRGGB10_1X10 },\n>> +     { formats::SBGGR12, MEDIA_BUS_FMT_SBGGR12_1X12 },\n>> +     { formats::SGBRG12, MEDIA_BUS_FMT_SGBRG12_1X12 },\n>> +     { formats::SGRBG12, MEDIA_BUS_FMT_SGRBG12_1X12 },\n>> +     { formats::SRGGB12, MEDIA_BUS_FMT_SRGGB12_1X12 },\n>> +};\n>> +\n>> +};\n>> +\n>> +CameraConfiguration::Status C3ISPCameraConfiguration::validate()\n>> +{\n>> +     const CameraSensor *sensor = data_->sensor_.get();\n>> +     Status status = Valid;\n>> +\n>> +     if (config_.empty())\n>> +             return Invalid;\n>> +\n>> +     if (config_.size() > kMaxStreams) {\n>> +             config_.resize(kMaxStreams);\n>> +             status = Adjusted;\n>> +     }\n>> +\n>> +     Size maxSize;\n>> +\n>> +     for (StreamConfiguration &config : config_) {\n>> +             const auto it = C3ISPFmtToCode.find(config.pixelFormat);\n>> +             if (it == C3ISPFmtToCode.end()) {\n>> +                     LOG(C3ISP, Debug)\n>> +                             << \"Format adjusted to \" << formats::NV12;\n>> +                     config.pixelFormat = formats::NV12;\n>> +                     status = Adjusted;\n>> +             }\n>> +\n>> +             Size size = std::clamp(config.size, kC3ISPMinSize, \n>> kC3ISPMaxSize);\n>> +             if (size != config.size) {\n>> +                     LOG(C3ISP, Debug)\n>> +                             << \"Size adjusted to \" << size;\n>> +                     config.size = size;\n>> +                     status = Adjusted;\n>> +             }\n>> +\n>> +             maxSize = std::max(maxSize, config.size);\n>> +     }\n>> +\n>> +     std::vector<unsigned int> mbusCodes;\n>> +\n>> +     std::transform(rawFormats.begin(), rawFormats.end(),\n>> +                    std::back_inserter(mbusCodes),\n>> +                    [](const auto &value) { return value.second; });\n>> +\n>> +     sensorFormat_ = sensor->getFormat(mbusCodes, maxSize);\n>> +\n>> +     if (sensorFormat_.size.isNull())\n>> +             sensorFormat_.size = sensor->resolution();\n>> +\n>> +     return status;\n>> +}\n>> +\n>> +PipelineHandlerC3ISP::PipelineHandlerC3ISP(CameraManager *manager)\n>> +     : PipelineHandler(manager)\n>> +{\n>> +}\n>> +\n>> +std::unique_ptr<CameraConfiguration>\n>> +PipelineHandlerC3ISP::generateConfiguration(Camera *camera,\n>> +                                         Span<const StreamRole> roles)\n>> +{\n>> +     C3ISPCameraData *data = cameraData(camera);\n>> +     std::unique_ptr<CameraConfiguration> config =\n>> + std::make_unique<C3ISPCameraConfiguration>(data);\n>> +\n>> +     if (roles.empty())\n>> +             return config;\n>> +\n>> +     streams_.clear();\n>> +\n>> +     for (const StreamRole &role : roles) {\n>> +             PixelFormat pixelFormat;\n>> +             Size size = std::min(Size{ 1920, 1080 }, \n>> data->sensor_->resolution());\n>> +\n>> +             switch (role) {\n>> +             case StreamRole::StillCapture:\n>> +                     pixelFormat = formats::NV12;\n>> + streams_.push_back(&data->stillStream);\n>> +                     break;\n>> +\n>> +             case StreamRole::VideoRecording:\n>> +                     pixelFormat = formats::NV12;\n>> + streams_.push_back(&data->videoStream);\n>> +                     break;\n>> +\n>> +             case StreamRole::Viewfinder:\n>> +                     pixelFormat = formats::NV12;\n>> + streams_.push_back(&data->viewStream);\n>> +                     break;\n>> +\n>> +             default:\n>> +                     LOG(C3ISP, Error) << \"Invalid stream role: \" << \n>> role;\n>> +                     return nullptr;\n> We can't get a RAW image out of the ISP at all then? Is it a hardware \n> limitation? Or the kernel driver?\n\n\nNow the driver doesn't support RAW image format,\nI will add this function in next version.\n\n>> +             }\n>> +\n>> +             std::map<PixelFormat, std::vector<SizeRange>> formats;\n>> +             for (const auto &c3Format : C3ISPFmtToCode) {\n>> +                     PixelFormat pixFmt = c3Format.first;\n>> +                     Size maxSize = std::min(kC3ISPMaxSize, \n>> data->sensor_->resolution());\n>> +                     formats[pixFmt] = { kC3ISPMinSize, maxSize };\n>> +             }\n>> +\n>> +             StreamFormats streamFormats(formats);\n>> +             StreamConfiguration cfg(streamFormats);\n>> +             cfg.pixelFormat = pixelFormat;\n>> +             cfg.bufferCount = 4;\n>> +             cfg.size = size;\n>> +\n>> +             config->addConfiguration(cfg);\n>> +     }\n>> +\n>> +     if (config->validate() == CameraConfiguration::Invalid)\n>> +             return nullptr;\n>> +\n>> +     return config;\n>> +}\n>> +\n>> +int PipelineHandlerC3ISP::configureProcessedStream(C3ISPCameraData \n>> *data,\n>> +                                                const \n>> StreamConfiguration &config,\n>> + V4L2SubdeviceFormat &subdevFormat)\n>> +{\n>> +     Stream *stream = config.stream();\n>> +     C3ISPPipe *pipe = pipeFromStream(data, stream);\n>> +     V4L2SubdeviceFormat rszFormat;\n>> +\n>> +     const MediaEntity *resizerEntity = pipe->resizer->entity();\n>> +     int ret = \n>> resizerEntity->getPadByIndex(0)->links()[0]->setEnabled(true);\n>> +     if (ret)\n>> +             return ret;\n>> +\n>> +     ret = \n>> resizerEntity->getPadByIndex(1)->links()[0]->setEnabled(true);\n>> +     if (ret)\n>> +             return ret;\n>> +\n>> +     rszFormat.code = C3ISPFmtToCode.find(config.pixelFormat)->second;\n>> +     rszFormat.size = subdevFormat.size;\n>> +     rszFormat.colorSpace = subdevFormat.colorSpace;\n>> +\n>> +     ret = pipe->resizer->setFormat(0, &rszFormat);\n>> +     if (ret)\n>> +             return ret;\n>> +\n>> +     Rectangle cropRect = { 0, 0, rszFormat.size };\n>> +     ret = pipe->resizer->setSelection(0, V4L2_SEL_TGT_CROP, \n>> &cropRect);\n>> +     if (ret)\n>> +             return ret;\n>> +\n>> +     Rectangle scaleRect = { 0, 0, config.size };\n>> +     ret = pipe->resizer->setSelection(0, V4L2_SEL_TGT_COMPOSE, \n>> &scaleRect);\n>> +     if (ret)\n>> +             return ret;\n>> +\n>> +     return 0;\n>> +}\n>> +\n>> +int PipelineHandlerC3ISP::setConfigStreams(CameraConfiguration *config)\n>> +{\n>> +     if (config->size() != streams_.size()) {\n>> +             LOG(C3ISP, Error) << \"Invalid configuration size: \" << \n>> config->size();\n>> +             return -EINVAL;\n>> +     }\n>> +\n>> +     for (unsigned int i = 0; i < config->size(); i++)\n>> +             config->at(i).setStream(streams_[i]);\n> This implies that it doesn't matter which stream goes with whichever \n> config; is that right? There's\n\n\nNo,  I have push the stream into streams_  in \"generateConfiguration\" \nfunction.\n\n> no difference in the capabilities between the three output pipelines? \n> And you want identical\n\n\nNo,  the maximum width of cap0 and cap1 is 1920,\n\nthe maximum width of cap1 is 2888.\n\n> behaviour regardless of the selected stream role? For example if I'm \n> following correctly you're\n\n\nNo, I have assigned the stream to the stream role in \n\"generateConfiguration\" function.\n\n> configuring the largest possible resolution on the sensor and \n> downscaling that to the requested\n> configuration size - and that should happen regardless of the user \n> asking for a stream for\n> StillCapture / VideoRecording or Viewfinder?\n\n\nNo,  I have assigned the stream to the stream role in \n\"generateConfiguration\" function.\n\nStillCapture:  data->stillStream\n\nVideoRecording: data->videoStream\n\nViewfinder: data-> viewStream\n\n\nI will check these configurations.\n\n>> +\n>> +     return 0;\n>> +}\n>> +\n>> +int PipelineHandlerC3ISP::configure(Camera *camera, \n>> CameraConfiguration *config)\n>> +{\n>> +     resetPipes();\n>> +\n>> +     int ret = media_->disableLinks();\n>> +     if (ret)\n>> +             return ret;\n>> +\n>> +     /*\n>> +      * The stream has been set to nullptr in Camera::configure,\n>> +      * so need to set stream.\n>> +      */\n>> +     ret = setConfigStreams(config);\n>> +     if (ret)\n>> +             return ret;\n>> +\n>> +     /* Link the graph */\n>> +     C3ISPCameraData *data = cameraData(camera);\n>> +\n>> +     const MediaEntity *csiEntity = data->csi_->entity();\n>> +     ret = csiEntity->getPadByIndex(0)->links()[0]->setEnabled(true);\n>> +     if (ret)\n>> +             return ret;\n>> +\n>> +     const MediaEntity *adapEntity = data->adap_->entity();\n>> +     ret = adapEntity->getPadByIndex(0)->links()[0]->setEnabled(true);\n>> +     if (ret)\n>> +             return ret;\n>> +\n>> +     const MediaEntity *ispEntity = isp_->entity();\n>> +     ret = ispEntity->getPadByIndex(0)->links()[0]->setEnabled(true);\n>> +     if (ret)\n>> +             return ret;\n>> +\n>> +     ret = ispEntity->getPadByIndex(1)->links()[0]->setEnabled(true);\n>> +     if (ret)\n>> +             return ret;\n>> +\n>> +     ret = ispEntity->getPadByIndex(2)->links()[0]->setEnabled(true);\n>> +     if (ret)\n>> +             return ret;\n>> +\n>> +     C3ISPCameraConfiguration *c3Config = \n>> static_cast<C3ISPCameraConfiguration *>(config);\n>> +     V4L2SubdeviceFormat subdevFormat = c3Config->sensorFormat_;\n>> +\n>> +     ret = data->sensor_->setFormat(&subdevFormat);\n>> +     if (ret)\n>> +             return ret;\n>> +\n>> +     ret = data->csi_->setFormat(0, &subdevFormat);\n>> +     if (ret)\n>> +             return ret;\n>> +\n>> +     ret = data->adap_->setFormat(0, &subdevFormat);\n>> +     if (ret)\n>> +             return ret;\n>> +\n>> +     ret = isp_->setFormat(0, &subdevFormat);\n>> +     if (ret)\n>> +             return ret;\n>> +\n>> +     V4L2SubdeviceFormat ispSrcVideoFormat = subdevFormat;\n>> +     ispSrcVideoFormat.code = MEDIA_BUS_FMT_YUV8_1X24;\n>> +     ret = isp_->setFormat(3, &ispSrcVideoFormat);\n>> +     if (ret)\n>> +             return ret;\n>> +\n>> +     V4L2DeviceFormat paramFormat;\n>> +     paramFormat.fourcc = V4L2PixelFormat(V4L2_META_FMT_C3ISP_PARAMS);\n>> +     ret = param_->setFormat(&paramFormat);\n>> +     if (ret)\n>> +             return ret;\n>> +\n>> +     V4L2DeviceFormat statFormat;\n>> +     statFormat.fourcc = V4L2PixelFormat(V4L2_META_FMT_C3ISP_STATS);\n>> +     ret = stat_->setFormat(&statFormat);\n>> +     if (ret)\n>> +             return ret;\n>> +\n>> +     for (const StreamConfiguration &streamConfig : *config) {\n>> +             Stream *stream = streamConfig.stream();\n>> +             C3ISPPipe *pipe = pipeFromStream(data, stream);\n>> +\n>> +             ret = configureProcessedStream(data, streamConfig, \n>> subdevFormat);\n>> +             if (ret) {\n>> +                     LOG(C3ISP, Error) << \"Failed to configure \n>> process stream\";\n>> +                     return ret;\n>> +             }\n> You don't have an accompanying \"configureRAWStream()\" so here I'd \n> probably just call the function\n> \"configureStream()\" or \"configurePipe()\"\n\n\nOK, will check here.\n\n>> +\n>> +             V4L2DeviceFormat captureFormat;\n>> +             captureFormat.fourcc = \n>> pipe->cap->toV4L2PixelFormat(streamConfig.pixelFormat);\n>> +             captureFormat.size = streamConfig.size;\n>> +\n>> +             ret = pipe->cap->setFormat(&captureFormat);\n>> +             if (ret)\n>> +                     return ret;\n> I think I'd just move that inside \"configureStream/Pipe()\"\n\n\nWill move it inside \"configureStream/Pipe()\".\n\n>> +\n>> +             pipe->stream = stream;\n>> +     }\n>> +\n>> +     /* Configure IPA module */\n>> +     ipa::c3isp::IPAConfigInfo ipaConfig{};\n>> +\n>> +     ret = data->sensor_->sensorInfo(&ipaConfig.sensorInfo);\n>> +     if (ret)\n>> +             return ret;\n>> +\n>> +     ipaConfig.sensorControls = data->sensor_->controls();\n>> +\n>> +     ret = data->ipa_->configure(ipaConfig, &data->controlInfo_);\n>> +     if (ret) {\n>> +             LOG(C3ISP, Error) << \"Failed to configure IPA\";\n>> +             return ret;\n>> +     }\n>> +\n>> +     return 0;\n>> +}\n>> +\n>> +int PipelineHandlerC3ISP::exportFrameBuffers(Camera *camera, Stream \n>> *stream,\n>> + std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n>> +{\n>> +     C3ISPPipe *pipe = pipeFromStream(cameraData(camera), stream);\n>> +     unsigned int count = stream->configuration().bufferCount;\n>> +\n>> +     return pipe->cap->exportBuffers(count, buffers);\n>> +}\n>> +\n>> +int PipelineHandlerC3ISP::allocateBuffers(Camera *camera)\n>> +{\n>> +     C3ISPCameraData *data = cameraData(camera);\n>> +     unsigned int ipaBufferId = 1;\n>> +\n>> +     int ret = param_->allocateBuffers(4, &paramBuffers_);\n>> +     if (ret < 0)\n>> +             return ret;\n>> +\n>> +     ret = stat_->allocateBuffers(4, &statBuffers_);\n>> +     if (ret < 0) {\n>> +             paramBuffers_.clear();\n>> +             statBuffers_.clear();\n>> +             return ret;\n>> +     }\n>> +\n>> +     for (std::unique_ptr<FrameBuffer> &buffer : paramBuffers_) {\n>> +             buffer->setCookie(ipaBufferId++);\n>> + data->ipaBuffers_.emplace_back(buffer->cookie(), buffer->planes());\n>> +             availableParamBuffers_.push(buffer.get());\n>> +     }\n>> +\n>> +     for (std::unique_ptr<FrameBuffer> &buffer : statBuffers_) {\n>> +             buffer->setCookie(ipaBufferId++);\n>> + data->ipaBuffers_.emplace_back(buffer->cookie(), buffer->planes());\n>> +             availableStatBuffers_.push(buffer.get());\n>> +     }\n>> +\n>> +     data->ipa_->mapBuffers(data->ipaBuffers_);\n>> +\n>> +     return 0;\n>> +}\n>> +\n>> +int PipelineHandlerC3ISP::freeBuffers(Camera *camera)\n>> +{\n>> +     C3ISPCameraData *data = cameraData(camera);\n>> +\n>> +     while (!availableStatBuffers_.empty())\n>> +             availableStatBuffers_.pop();\n>> +\n>> +     while (!availableParamBuffers_.empty())\n>> +             availableParamBuffers_.pop();\n>> +\n>> +     paramBuffers_.clear();\n>> +     statBuffers_.clear();\n>> +\n>> +     std::vector<unsigned int> ids;\n>> +     for (IPABuffer &ipabuf : data->ipaBuffers_)\n>> +             ids.push_back(ipabuf.id);\n>> +\n>> +     data->ipa_->unmapBuffers(ids);\n>> +     data->ipaBuffers_.clear();\n>> +\n>> +     if (param_->releaseBuffers())\n>> +             LOG(C3ISP, Error) << \"Failed to release param buffers\";\n>> +\n>> +     if (stat_->releaseBuffers())\n>> +             LOG(C3ISP, Error) << \"Failed to release stat buffers\";\n>> +\n>> +     return 0;\n>> +}\n>> +\n>> +int PipelineHandlerC3ISP::pipeStart()\n> The name implies it's starting one pipe rather than all of them.\n\n\nWill use \"pipesStart()\".\n\n>> +{\n>> +     for (C3ISPPipe &pipe : pipes_) {\n>> +             if (!pipe.stream)\n>> +                     continue;\n>> +\n>> +             Stream *stream = pipe.stream;\n>> +\n>> +             int ret = \n>> pipe.cap->importBuffers(stream->configuration().bufferCount);\n>> +             if (ret) {\n>> +                     LOG(C3ISP, Error) << \"Failed to import buffers\";\n>> +                     return ret;\n>> +             }\n>> +\n>> +             ret = pipe.cap->streamOn();\n>> +             if (ret) {\n>> +                     LOG(C3ISP, Error) << \"Failed to start stream\";\n>> +                     return ret;\n>> +             }\n>> +     }\n>> +\n>> +     return 0;\n>> +}\n>> +\n>> +void PipelineHandlerC3ISP::pipeStop()\n>> +{\n>> +     for (C3ISPPipe &pipe : pipes_) {\n>> +             if (!pipe.stream)\n>> +                     continue;\n>> +\n>> +             pipe.cap->streamOff();\n>> +             pipe.cap->releaseBuffers();\n>> +     }\n>> +}\n>> +\n>> +int PipelineHandlerC3ISP::start([[maybe_unused]] Camera *camera,\n>> +                             [[maybe_unused]] const ControlList \n>> *controls)\n>> +{\n>> +     C3ISPCameraData *data = cameraData(camera);\n>> +     int ret;\n>> +\n>> +     ret = allocateBuffers(camera);\n>> +     if (ret < 0)\n>> +             return ret;\n>> +\n>> +     ret = data->ipa_->start();\n>> +     if (ret) {\n>> +             LOG(C3ISP, Error) << \"Failed to start IPA\";\n>> +             goto error;\n>> +     }\n>> +\n>> +     data->frame_ = 0;\n>> +\n>> +     ret = param_->streamOn();\n>> +     if (ret) {\n>> +             LOG(C3ISP, Error) << \"Failed to start param\";\n>> +             goto error;\n>> +     }\n>> +\n>> +     ret = stat_->streamOn();\n>> +     if (ret) {\n>> +             LOG(C3ISP, Error) << \"Failed to start stat\";\n>> +             goto error;\n>> +     }\n>> +\n>> +     ret = pipeStart();\n>> +     if (ret) {\n>> +             LOG(C3ISP, Error) << \"Failed to start pipe\";\n>> +             goto error;\n>> +     }\n>> +\n>> +     ret = isp_->setFrameStartEnabled(true);\n>> +     if (ret) {\n>> +             LOG(C3ISP, Error) << \"Failed to set frame start\";\n>> +             goto error;\n>> +     }\n>> +\n>> +     activeCamera_ = camera;\n>> +\n>> +     return 0;\n>> +\n>> +error:\n>> +     pipeStop();\n>> +     stat_->streamOff();\n>> +     param_->streamOff();\n>> +     data->ipa_->stop();\n>> +     freeBuffers(camera);\n>> +     LOG(C3ISP, Error) << \"Failed to start camera \" << camera->id();\n>> +\n>> +     return ret;\n>> +}\n>> +\n>> +void PipelineHandlerC3ISP::stopDevice([[maybe_unused]] Camera *camera)\n>> +{\n>> +     C3ISPCameraData *data = cameraData(camera);\n>> +\n>> +     isp_->setFrameStartEnabled(false);\n>> +\n>> +     data->ipa_->stop();\n>> +\n>> +     pipeStop();\n>> +\n>> +     stat_->streamOff();\n>> +     param_->streamOff();\n>> +\n>> +     data->frameInfo_.clear();\n>> +\n>> +     freeBuffers(camera);\n>> +\n>> +     activeCamera_ = nullptr;\n>> +}\n>> +\n>> +int PipelineHandlerC3ISP::queueRequestDevice(Camera *camera, Request \n>> *request)\n>> +{\n>> +     C3ISPCameraData *data = cameraData(camera);\n>> +\n>> +     C3ISPFrameInfo *info = data->frameInfo_.create(data, request);\n>> +     if (!info)\n>> +             return -ENOENT;\n>> +\n>> +     data->ipa_->queueRequest(data->frame_, request->controls());\n>> +\n>> +     data->ipa_->fillParamsBuffer(data->frame_, \n>> info->paramBuffer->cookie());\n>> +\n>> +     data->frame_++;\n>> +\n>> +     return 0;\n>> +}\n>> +\n>> +void PipelineHandlerC3ISP::tryCompleteRequest(C3ISPFrameInfo *info)\n>> +{\n>> +     C3ISPCameraData *data = cameraData(activeCamera_);\n>> +     Request *request = info->request;\n>> +\n>> +     if (request->hasPendingBuffers())\n>> +             return;\n>> +\n>> +     if (!info->metadataProcessed)\n>> +             return;\n>> +\n>> +     if (!info->paramDequeued)\n>> +             return;\n>> +\n>> +     data->frameInfo_.destroy(info->frame);\n>> +\n>> +     completeRequest(request);\n>> +}\n>> +\n>> +void PipelineHandlerC3ISP::bufferReady(FrameBuffer *buffer)\n>> +{\n>> +     C3ISPCameraData *data = cameraData(activeCamera_);\n>> +\n>> +     C3ISPFrameInfo *info = data->frameInfo_.find(buffer);\n>> +     if (!info)\n>> +             return;\n>> +\n>> +     const FrameMetadata &metadata = buffer->metadata();\n>> +     Request *request = buffer->request();\n>> +\n>> +     if (metadata.status != FrameMetadata::FrameCancelled) {\n>> + request->metadata().set(controls::SensorTimestamp,\n>> +                                     metadata.timestamp);\n>> +     }\n>> +\n>> +     completeBuffer(request, buffer);\n>> +     tryCompleteRequest(info);\n>> +}\n>> +\n>> +void PipelineHandlerC3ISP::statReady(FrameBuffer *buffer)\n>> +{\n>> +     C3ISPCameraData *data = cameraData(activeCamera_);\n>> +\n>> +     C3ISPFrameInfo *info = data->frameInfo_.find(buffer);\n>> +     if (!info)\n>> +             return;\n>> +\n>> +     if (buffer->metadata().status == FrameMetadata::FrameCancelled) {\n>> +             info->metadataProcessed = true;\n>> +             tryCompleteRequest(info);\n>> +             return;\n>> +     }\n>> +\n>> +     if (data->frame_ <= buffer->metadata().sequence)\n>> +             data->frame_ = buffer->metadata().sequence + 1;\n>> +\n>> +     data->ipa_->processStatsBuffer(info->frame, \n>> info->statBuffer->cookie(),\n>> + data->delayedCtrls_->get(buffer->metadata().sequence));\n>> +}\n>> +\n>> +void PipelineHandlerC3ISP::paramReady(FrameBuffer *buffer)\n>> +{\n>> +     C3ISPCameraData *data = cameraData(activeCamera_);\n>> +\n>> +     C3ISPFrameInfo *info = data->frameInfo_.find(buffer);\n>> +     if (!info)\n>> +             return;\n>> +\n>> +     info->paramDequeued = true;\n>> +     tryCompleteRequest(info);\n>> +}\n>> +\n>> +bool PipelineHandlerC3ISP::createCamera(MediaLink *ispLink)\n>> +{\n>> +     MediaEntity *adap = ispLink->source()->entity();\n>> +     const MediaPad *adapSink = adap->getPadByIndex(0);\n>> +     MediaEntity *csi = adapSink->links()[0]->source()->entity();\n>> +     const MediaPad *csiSink = csi->getPadByIndex(0);\n>> +     MediaEntity *sensor = csiSink->links()[0]->source()->entity();\n>\n>\n> Might be nice to validate that sensor->function() is \n> MEDIA_ENT_F_CAM_SENSOR here...it's unlikely not\n> to be, but the surety is nice.\n>\nWill validate the sensor->function.\n>> +\n>> +     std::unique_ptr<C3ISPCameraData> data =\n>> +             std::make_unique<C3ISPCameraData>(this, sensor);\n>> +\n>> +     if (data->init())\n>> +             return false;\n>> +\n>> +     /* Generic values for sensor */\n>> +     std::unordered_map<uint32_t, DelayedControls::ControlParams> \n>> params = {\n>> +             { V4L2_CID_ANALOGUE_GAIN, { 1, false } },\n>> +             { V4L2_CID_EXPOSURE, { 2, false } },\n>> +     };\n>\n> Things have recently been updated so that you can fetch the delay \n> values from a database:\n>\n>\n> https://git.libcamera.org/libcamera/libcamera.git/tree/src/libcamera/pipeline/mali-c55/mali-c55.cpp#n1606 \n>\n>\n\nWill refer to the latest mali-c55.cpp\n\n>> +\n>> +     data->delayedCtrls_ = \n>> std::make_unique<DelayedControls>(data->sensor_->device(), params);\n>> +     isp_->frameStart.connect(data->delayedCtrls_.get(), \n>> &DelayedControls::applyControls);\n>> +\n>> +     int ret = data->loadIPA(media_->hwRevision());\n>> +     if (ret)\n>> +             return false;\n>> +\n>> +     std::set<Stream *> streams{ &data->viewStream, \n>> &data->stillStream, &data->videoStream };\n>> +\n>> +     std::shared_ptr<Camera> camera = \n>> Camera::create(std::move(data), sensor->name(), streams);\n>> +\n>> +     registerCamera(std::move(camera));\n>> +\n>> +     return true;\n>> +}\n>> +\n>> +bool PipelineHandlerC3ISP::match(DeviceEnumerator *enumerator)\n>> +{\n>> +     const MediaPad *ispSink;\n>> +\n>> +     DeviceMatch dm(\"c3-isp\");\n>> +     dm.add(\"c3-mipi-csi2\");\n>> +     dm.add(\"c3-mipi-adapter\");\n>> +     dm.add(\"c3-isp-core\");\n>> +\n>> +     media_ = acquireMediaDevice(enumerator, dm);\n>> +     if (!media_)\n>> +             return false;\n>> +\n>> +     isp_ = V4L2Subdevice::fromEntityName(media_, \"c3-isp-core\");\n>> +     if (isp_->open() < 0)\n>> +             return false;\n>> +\n>> +     stat_ = V4L2VideoDevice::fromEntityName(media_, \"c3-isp-stats\");\n>> +     if (stat_->open() < 0)\n>> +             return false;\n>> +\n>> +     stat_->bufferReady.connect(this, \n>> &PipelineHandlerC3ISP::statReady);\n>> +\n>> +     param_ = V4L2VideoDevice::fromEntityName(media_, \"c3-isp-params\");\n>> +     if (param_->open() < 0)\n>> +             return false;\n>> +\n>> +     param_->bufferReady.connect(this, \n>> &PipelineHandlerC3ISP::paramReady);\n>> +\n>> +     C3ISPPipe *viewPipe = &pipes_[C3ISPVIEW];\n>> +     viewPipe->resizer = V4L2Subdevice::fromEntityName(media_, \n>> \"c3-isp-resizer0\");\n>> +     if (viewPipe->resizer->open() < 0)\n>> +             return false;\n>> +\n>> +     viewPipe->cap = V4L2VideoDevice::fromEntityName(media_, \n>> \"c3-isp-cap0\");\n>> +     if (viewPipe->cap->open() < 0)\n>> +             return false;\n>> +\n>> +     viewPipe->cap->bufferReady.connect(this, \n>> &PipelineHandlerC3ISP::bufferReady);\n>> +\n>> +     C3ISPPipe *stillPipe = &pipes_[C3ISPSTILL];\n>> +     stillPipe->resizer = V4L2Subdevice::fromEntityName(media_, \n>> \"c3-isp-resizer1\");\n>> +     if (stillPipe->resizer->open() < 0)\n>> +             return false;\n>> +\n>> +     stillPipe->cap = V4L2VideoDevice::fromEntityName(media_, \n>> \"c3-isp-cap1\");\n>> +     if (stillPipe->cap->open() < 0)\n>> +             return false;\n>> +\n>> +     stillPipe->cap->bufferReady.connect(this, \n>> &PipelineHandlerC3ISP::bufferReady);\n>> +\n>> +     C3ISPPipe *videoPipe = &pipes_[C3ISPVIDEO];\n>> +     videoPipe->resizer = V4L2Subdevice::fromEntityName(media_, \n>> \"c3-isp-resizer2\");\n>> +     if (videoPipe->resizer->open() < 0)\n>> +             return false;\n>> +\n>> +     videoPipe->cap = V4L2VideoDevice::fromEntityName(media_, \n>> \"c3-isp-cap2\");\n>> +     if (videoPipe->cap->open() < 0)\n>> +             return false;\n>> +\n>> +     videoPipe->cap->bufferReady.connect(this, \n>> &PipelineHandlerC3ISP::bufferReady);\n>> +\n> Setting up the pipes here lends itself to a loop; up to you though.\n\n\nOK, will check here.\n\n>> +     ispSink = isp_->entity()->getPadByIndex(0);\n>> +     if (!ispSink || ispSink->links().empty())\n>> +             return false;\n>> +\n>> +     if (!createCamera(ispSink->links()[0])) {\n>> +             LOG(C3ISP, Error) << \"Failed to create camera\";\n>> +             return false;\n>> +     }\n>> +\n>> +     return true;\n>> +}\n>> +\n>> +REGISTER_PIPELINE_HANDLER(PipelineHandlerC3ISP, \"c3isp\")\n>> +\n>> +} /* namespace libcamera */\n>> diff --git a/src/libcamera/pipeline/c3-isp/meson.build \n>> b/src/libcamera/pipeline/c3-isp/meson.build\n>> new file mode 100644\n>> index 00000000..5f8b23f1\n>> --- /dev/null\n>> +++ b/src/libcamera/pipeline/c3-isp/meson.build\n>> @@ -0,0 +1,5 @@\n>> +# SPDX-License-Identifier: CC0-1.0\n>> +\n>> +libcamera_internal_sources += files([\n>> +    'c3-isp.cpp'\n>> +])","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id B378AC32F2\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed,  5 Feb 2025 09:08:28 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id D72D0685C6;\n\tWed,  5 Feb 2025 10:08:27 +0100 (CET)","from HK3PR03CU002.outbound.protection.outlook.com\n\t(mail-eastasiaazlp170110002.outbound.protection.outlook.com\n\t[IPv6:2a01:111:f403:c400::2])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id D0AFC68559\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed,  5 Feb 2025 10:08:24 +0100 (CET)","from TYSPR03MB8627.apcprd03.prod.outlook.com (2603:1096:405:8a::9)\n\tby SEZPR03MB7297.apcprd03.prod.outlook.com (2603:1096:101:12e::13)\n\twith Microsoft SMTP Server (version=TLS1_2,\n\tcipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.8398.26;\n\tWed, 5 Feb 2025 09:08:18 +0000","from TYSPR03MB8627.apcprd03.prod.outlook.com\n\t([fe80::cf16:aa54:9bd5:26f]) by\n\tTYSPR03MB8627.apcprd03.prod.outlook.com\n\t([fe80::cf16:aa54:9bd5:26f%4]) with mapi id 15.20.8398.021;\n\tWed, 5 Feb 2025 09:08:17 +0000"],"Authentication-Results":["lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=amlogic.com header.i=@amlogic.com\n\theader.b=\"TzZs2Lqp\"; dkim-atps=neutral","dkim=none (message not signed)\n\theader.d=none;dmarc=none action=none header.from=amlogic.com;"],"ARC-Seal":"i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none;\n\tb=zR7WS3+FqAQy59XrSMI38gRUvNzlfaou8d7+sG+faarakVZvBkqg2tidkEQZC2sYCa3UvWeZ1+403c4slplRHaTNKOXKM2Fffj5an7477pxtIiZFPv/azR105A81+ae9c7D7xTHIrQbcu9WPARMYOyRFqGzlao+FU7GKTtYnLHW9GcxR3HzvOl3ets2Z6dvpwcfdQgjJCy0yMfaMGrtLFKYJYXtteWvH4aJ03ncIjFK23HKfLGgsxPhpNLWIzcVn/p2kz492vxSv/uzgQPbqmawlGNkBgfrt9XV9b0V+U9bZwt+U9nYUsKn9sjxYaVGqo7acDfrrUrQR2iom9XDpug==","ARC-Message-Signature":"i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com;\n\ts=arcselector10001;\n\th=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1;\n\tbh=TrVeVwYToIzabGupsyGwu7BIUkM7HZSKl4VHqqe+fPc=;\n\tb=E4reQ/4Jj0D0Kn3EOhmEtuYcd1fpZZ0qlp1jgQvhTjMc8yEhzisKp9GK/4wNctXJy1btD+GgABf9WqPiY/u3T0fWLcrQpIw/7xjdKCGW0jLSWMU6IBOU9daN56fK3VjSGmxcB6ejCU8mJr4NWbP/XMR4KPJpczlJhFBZzh4FHI71IaFcj+QS3hnjc5atykUuIyfc9KuRNy0CFkrzPsnVr7/bS50n5ftGmjoC179t/baAXDrIFGTafRgUriE6Rs0nCFReBW5vQeVHZPYBmN72DcTySAvoSxYGjcRORJQntG25CUqcShJEGtz2VitwcN+isSJYztu3zTP8NDYJvoBwwg==","ARC-Authentication-Results":"i=1; mx.microsoft.com 1; spf=pass\n\tsmtp.mailfrom=amlogic.com;\n\tdmarc=pass action=none header.from=amlogic.com; \n\tdkim=pass header.d=amlogic.com; arc=none","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=amlogic.com;\n\ts=selector1;\n\th=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck;\n\tbh=TrVeVwYToIzabGupsyGwu7BIUkM7HZSKl4VHqqe+fPc=;\n\tb=TzZs2LqpA6PQgXegn3Klia6uWy0Re4tjaLVOze/SGjE3wkgWHBruzEJQD9KXTapM1X6vm9rE0yPYAU+NeHWbQAwxZmLCEN1KzKO+5ByIr+P1o6CroRtbE+HUROjH7VkrqEJ+zuUim7k3schs0cMoO8siF4XAVjFFraB2mDCTK4UhPPbglAgRT0Woa9w8NoTrWf4FwrtOUQ/3v15BDwMVMSkTnihr5uQ6qXjorOps5LFF7ZpQ4Pc+0vVQW2qnVPLF2i3vvrJu+KqKOBPyaQ8K5zhJ1NorJJUmM0YQuNfSU2F2fbqpzbhtaF7jmgpeg3XeHfpq4CaNrE5H8oaMPQmlQQ==","Message-ID":"<2bef60d0-0109-4c00-959e-bfe309da356e@amlogic.com>","Date":"Wed, 5 Feb 2025 17:08:14 +0800","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH v2 1/2] libcamera: pipeline: Add C3 ISP pipeline handler","To":"Dan Scally <dan.scally@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","Cc":"kieran.bingham@ideasonboard.com, laurent.pinchart@ideasonboard.com,\n\tjacopo.mondi@ideasonboard.com","References":"<20241227105840.159559-1-keke.li@amlogic.com>\n\t<20241227105840.159559-2-keke.li@amlogic.com>\n\t<2a0b1026-a6cb-4a7b-9808-224d9ed31254@ideasonboard.com>","Content-Language":"en-US","From":"Keke Li <keke.li@amlogic.com>","In-Reply-To":"<2a0b1026-a6cb-4a7b-9808-224d9ed31254@ideasonboard.com>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"8bit","X-ClientProxiedBy":"SI2PR01CA0034.apcprd01.prod.exchangelabs.com\n\t(2603:1096:4:192::9) To TYSPR03MB8627.apcprd03.prod.outlook.com\n\t(2603:1096:405:8a::9)","MIME-Version":"1.0","X-MS-PublicTrafficType":"Email","X-MS-TrafficTypeDiagnostic":"TYSPR03MB8627:EE_|SEZPR03MB7297:EE_","X-MS-Office365-Filtering-Correlation-Id":"384144fa-93ab-403e-0619-08dd45c4a114","X-MS-Exchange-SenderADCheck":"1","X-MS-Exchange-AntiSpam-Relay":"0","X-Microsoft-Antispam":"BCL:0;ARA:13230040|1800799024|376014|366016;","X-Microsoft-Antispam-Message-Info":"=?utf-8?q?TvXMC/4EBps1mzphMZ2Nh/wKeY3y?=\n\t=?utf-8?q?wdtE3n6i53HfXbPWf72+IJGXRrHbtVUbKHeQJ7Wd0iYVjO8v96RMMLvw?=\n\t=?utf-8?q?CCaOemeB/foIESytJABtrm9jD3wK7s8kPQZcTgyj19oiWKVruk9ZSoxu?=\n\t=?utf-8?q?fVYUNRyy19PL/GZ0osxoj0U33YnCyFStf/q7hDHN3kheE9yJmHi+6vV5?=\n\t=?utf-8?q?xmtIBnxdPDDsyznBqLHllAWMjjuzrsswlyNnaxXIYUMiHE/N7dNk3Tk1?=\n\t=?utf-8?q?rqqevN9NtxjM0cn803rFnED3tDenS2fjLZiltuLE5CNS83cW9VOX9DHY?=\n\t=?utf-8?q?BvWFheDyUr9s5vR//Iy5GXoAUOVwTL7QchIu7+dBm9GK2iFziwkMEpYr?=\n\t=?utf-8?q?RagTJyjgNLbtnKZvfgJz5juhmqMm1lVQpuF9jXFPBcKUcepHWtML8Ara?=\n\t=?utf-8?q?26d/JwA3JYbg4+m3FTZLGwIUiDmm1tdhKdpCsBYe72icdsBu9e9z2hWd?=\n\t=?utf-8?q?zW7FDNlZfCJIdpYxHg4LjNnc4L1rFnmvwd4nlB6jawjZZK9xI0WaTaNh?=\n\t=?utf-8?q?r0YKQWYi16fONeTix3orMn8jVvtXFTCxsMc+jAlZP7+onajSnTUVdv7Z?=\n\t=?utf-8?q?eLe+fSJnBoTvGtlO9jmcRBWs63pjEJsv605f4kETXOb/7BXTyDWKX0oN?=\n\t=?utf-8?q?GjXdU9j6159VBM2jtf3R1UUxZRRO2Hn4+0IhfVOJXha+wvZ0jlo6sF6Z?=\n\t=?utf-8?q?6z1CdePrihMI82iiva3q5tfcxCGNznJrYKYPCfbkh4qXYuelnv9gHyOP?=\n\t=?utf-8?q?FYD35nbeWoQdsDsYemNfSj+44/Zwfc8jleo7im9w27cbu70rziAI/xVx?=\n\t=?utf-8?q?PzPzpU9UjIJqv8Spyp2wYKUk1wmHCDl1c+tlGsqh9QHqIuNHKpy0TTdi?=\n\t=?utf-8?q?87SO+wcIkkxs8MBSaDqUgNtU6HCj7jbHBmVuyhjnBtP24QXdMyLcuJUV?=\n\t=?utf-8?q?qgMy3TKo/DQw0gf7j5KDv36jxsYHb6LoP9tqY26nOEvvhCitPrkG/8B0?=\n\t=?utf-8?q?VO7nC6B2URAfFiI88R911qRah1TkfcRX2qa7Mr4cJH2MhAj4f/UhPYy/?=\n\t=?utf-8?q?YyH+/eFtbL1gi7V9D4W9u7tFlTOtIujniPmQyZHtUPS8vGqkqXFvTBG3?=\n\t=?utf-8?q?WomGEEmLqmflLnXlPe4I+0B27uUlL9SZFeK0D4MPgm8c+r1RcQ4ji7tV?=\n\t=?utf-8?q?qYJBxVm+5YXE7oiKQbzpaditrKvN2YV2C17Kpf+K4fz9xfYvtVH1M/10?=\n\t=?utf-8?q?blENNpbD6Zb0pTB+ppu5+yX1l93nRBRtsanN2IJrhqbSGIZKPTBsGdHs?=\n\t=?utf-8?q?e85/X/9ibmoYHF4wleueW9a21oAvYePFCt740Vhl0Ek2+MGzPwIdg+Cf?=\n\t=?utf-8?q?PF+ztfoexZ/Ox/Kjh5qMSa+h7mNCfW79qCoYJ7Re/uAVdbfQow=3D=3D?=","X-Forefront-Antispam-Report":"CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:;\n\tIPV:NLI; SFV:NSPM; H:TYSPR03MB8627.apcprd03.prod.outlook.com; PTR:;\n\tCAT:NONE; \n\tSFS:(13230040)(1800799024)(376014)(366016); DIR:OUT; SFP:1102; ","X-MS-Exchange-AntiSpam-MessageData-ChunkCount":"1","X-MS-Exchange-AntiSpam-MessageData-0":"=?utf-8?q?70vLCCzqBR8hO0fJZ3qdMbqAL?=\n\t=?utf-8?q?z0PFnT8g8ZyvoVeQM51CgXeEcYU1BPTFDEOQm4w358SYtsDbxt3Y/KKc?=\n\t=?utf-8?q?AyE/3+BcAzXZTg67TvDYCQD2pAyPX1jgQJumO8dvL1ON9Cj0PdN6rV7C?=\n\t=?utf-8?q?Ii7YND2jW4Gvo2Ci0hHSxlYLZXwEZOYnLAXESmS5a9qTDC+XP8MYNE40?=\n\t=?utf-8?q?uC81Ax0t/vTyQsFjvzgO9/hqSEn4UUtAr4Qr+snZwfnZogYjsWNwgZ2+?=\n\t=?utf-8?q?c280/NxJZt/9hClJcdQh8elUFIEb5nfdlJ9LrLuy6nnsMNIqzSep+Snf?=\n\t=?utf-8?q?I/fyF+XLq/T2w0o6GdIK2v1a/Kono86VYC73rfSt1AsNOYiiqKTTvFIe?=\n\t=?utf-8?q?/E/By9GsRYZ/C2ZqNWyoAiMEcM+kDxEV13B6zqnHtSvdTDM8zPwMXveT?=\n\t=?utf-8?q?HX9PzWyXBYzGJdBdlRKVnj8jVeSoHA4qWlk2mngepM7ktjmaR+j8nVkb?=\n\t=?utf-8?q?Nb7wpFZOXvW7FyvXqFJ7lipVJJrbtQD5H4M32Pf5zl7ZuBov350ci8ib?=\n\t=?utf-8?q?8uytXjusvKaPgCo4ecqBGNmMfUf5R0ud5O6UCKQWcsjjjlZaXaOLDntZ?=\n\t=?utf-8?q?s3Hd5V4YHBJjfJj70JNaMkFCtektck9LgUeCvhkPBqmJBOJto80oFbeP?=\n\t=?utf-8?q?RuXe7BMqfvdCgmLWLGeN58V4RhL6KQVEgTWgrgwSKhrC+Kvroc4NsIm9?=\n\t=?utf-8?q?XRnFLIyh+kU5+DWvAEAB1T0qV7BtUw3mHHDeSNeVvWY1th4EcIZxZSRS?=\n\t=?utf-8?q?tB26Fw2qa0ATEkPLYmQnjeFpGBK0+LsxB7j5sCJ2uLSm8MI3aDjBkEDs?=\n\t=?utf-8?q?CKo+WbqHyJkNALsIPjAJPOjfkedBe33fvorrPOsRMtwLG6J8WEkFiVb6?=\n\t=?utf-8?q?ms28tf8sM1erfctDofqM0gpetcxeYFL9DmikMDqQgEvyVakdPeDu3svA?=\n\t=?utf-8?q?LrJRTrnMVyJ6aiI/V7mYi1J6RJuTWP+OLxvDKucvWXxeK3L+f3Koz5DT?=\n\t=?utf-8?q?lCvpAstkmUd+hxn0hmKgKrWidUMrdKHirhUmy6vyw9kKRfAQ8HJyEh8T?=\n\t=?utf-8?q?WM8BGIxusDHPnLk9PnCTrLzFK30y7cwjI/xmfgQkjXJ8RNinmbyd5zp+?=\n\t=?utf-8?q?aQhv9GpVO+eruG9tvAigpAtn6PVcZeTImxbMJAwqtte2rCW4iViJpi6V?=\n\t=?utf-8?q?RSD0kkpvjcfG4wleTMn0oxrdSKn0BJYQLmWqmL2j6nOaU71C1zASQgEp?=\n\t=?utf-8?q?N4Amv3//urQnafLpPJnSyTUUah/q6bzGV1HP9XijyP9rDzHc3/NNomeM?=\n\t=?utf-8?q?nhglWuUriGWY2/aaxODChiVl3RfY4NTwfPZA2NgLcaD8IAqebet4sZS0?=\n\t=?utf-8?q?bK/q1aB6tITtJKpU39S+eomk1OpKPRPqZzaXDGtd/GDF3BWOe0RfMrTk?=\n\t=?utf-8?q?sCxBT6fypjluSzGHiqyzsKisFXZ9N/q5HIhmWDpvxi4X+RYaLy97vOhR?=\n\t=?utf-8?q?9nse8W/BtcX6fwT2q8TkaKI0H6RXSxfdW5x5xxWAH8RVatpmE/LPScH7?=\n\t=?utf-8?q?RlymkzdMtBEL9z6wwvNNQNvmqPoz9HijwF0jBRExJCZcYo4jBuO4uEo+?=\n\t=?utf-8?q?hwmkIsuU7wsQt5Ghyt9oi+fGKtsuoonMcDblwf6oI7URkRqsgSgwG7ZE?=\n\t=?utf-8?q?LSpSSFp?=","X-OriginatorOrg":"amlogic.com","X-MS-Exchange-CrossTenant-Network-Message-Id":"384144fa-93ab-403e-0619-08dd45c4a114","X-MS-Exchange-CrossTenant-AuthSource":"TYSPR03MB8627.apcprd03.prod.outlook.com","X-MS-Exchange-CrossTenant-AuthAs":"Internal","X-MS-Exchange-CrossTenant-OriginalArrivalTime":"05 Feb 2025 09:08:17.7076\n\t(UTC)","X-MS-Exchange-CrossTenant-FromEntityHeader":"Hosted","X-MS-Exchange-CrossTenant-Id":"0df2add9-25ca-4b3a-acb4-c99ddf0b1114","X-MS-Exchange-CrossTenant-MailboxType":"HOSTED","X-MS-Exchange-CrossTenant-UserPrincipalName":"+edcrsu5LS7vbK+Ningut4ji6yE4H2QLrqzGeBGS7Rt7cYduKH18cw+M0NSdbuSqEKfUB6a2r8/980VsZdpASg==","X-MS-Exchange-Transport-CrossTenantHeadersStamped":"SEZPR03MB7297","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]